feat: init push online

This commit is contained in:
chenhao 2026-05-19 15:52:01 +08:00
parent 88d41de408
commit f52191e065
105 changed files with 1645 additions and 978 deletions

32
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@vant/area-data": "^2.1.0", "@vant/area-data": "^2.1.0",
"@zxing/library": "^0.21.3",
"image-conversion": "^2.1.1", "image-conversion": "^2.1.1",
"pinia": "^3.0.4", "pinia": "^3.0.4",
"postcss-px-to-viewport-8-plugin": "^1.2.5", "postcss-px-to-viewport-8-plugin": "^1.2.5",
@ -629,6 +630,28 @@
"integrity": "sha512-ksNyrmRQzWJJ8n3cRDuSF7zNNontuJg1YHnmWRJd2AMu8Ij2bqwiiri2lH5rHtYPZjj4STkNcgcmiQqlOjiYGg==", "integrity": "sha512-ksNyrmRQzWJJ8n3cRDuSF7zNNontuJg1YHnmWRJd2AMu8Ij2bqwiiri2lH5rHtYPZjj4STkNcgcmiQqlOjiYGg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@zxing/library": {
"version": "0.21.3",
"resolved": "https://registry.npmmirror.com/@zxing/library/-/library-0.21.3.tgz",
"integrity": "sha512-hZHqFe2JyH/ZxviJZosZjV+2s6EDSY0O24R+FQmlWZBZXP9IqMo7S3nb3+2LBWxodJQkSurdQGnqE7KXqrYgow==",
"license": "MIT",
"dependencies": {
"ts-custom-error": "^3.2.1"
},
"engines": {
"node": ">= 10.4.0"
},
"optionalDependencies": {
"@zxing/text-encoding": "~0.9.0"
}
},
"node_modules/@zxing/text-encoding": {
"version": "0.9.0",
"resolved": "https://registry.npmmirror.com/@zxing/text-encoding/-/text-encoding-0.9.0.tgz",
"integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==",
"license": "(Unlicense OR Apache-2.0)",
"optional": true
},
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.16.0", "version": "8.16.0",
"resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.16.0.tgz", "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.16.0.tgz",
@ -1629,6 +1652,15 @@
"url": "https://github.com/sponsors/SuperchupuDev" "url": "https://github.com/sponsors/SuperchupuDev"
} }
}, },
"node_modules/ts-custom-error": {
"version": "3.3.1",
"resolved": "https://registry.npmmirror.com/ts-custom-error/-/ts-custom-error-3.3.1.tgz",
"integrity": "sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/tslib": { "node_modules/tslib": {
"version": "2.8.1", "version": "2.8.1",
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",

View File

@ -10,6 +10,7 @@
}, },
"dependencies": { "dependencies": {
"@vant/area-data": "^2.1.0", "@vant/area-data": "^2.1.0",
"@zxing/library": "^0.21.3",
"image-conversion": "^2.1.1", "image-conversion": "^2.1.1",
"pinia": "^3.0.4", "pinia": "^3.0.4",
"postcss-px-to-viewport-8-plugin": "^1.2.5", "postcss-px-to-viewport-8-plugin": "^1.2.5",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 793 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 876 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 691 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

BIN
public/img/logo-lr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 912 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 413 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 579 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 611 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 558 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 949 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 259 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 404 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 501 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 365 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

View File

@ -16,9 +16,9 @@ const ERR_CODE = {
SERVER_ERROR: 500, SERVER_ERROR: 500,
}; };
function getHeaders() { function getHeaders(contentType = 'application/json') {
const headers = { const headers = {
'Content-Type': 'application/json', 'Content-Type': contentType,
}; };
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
if (token) { if (token) {
@ -137,6 +137,26 @@ export const post = (url, data, timeout = DEFAULT_TIMEOUT) => request(url, data,
export const put = (url, data, timeout = DEFAULT_TIMEOUT) => request(url, data, 'PUT', timeout); export const put = (url, data, timeout = DEFAULT_TIMEOUT) => request(url, data, 'PUT', timeout);
export const del = (url, data, timeout = DEFAULT_TIMEOUT) => request(url, data, 'DELETE', timeout); export const del = (url, data, timeout = DEFAULT_TIMEOUT) => request(url, data, 'DELETE', timeout);
/**
* POST FormData 请求
* @param {string} url - 请求路径
* @param {FormData} formData - FormData 对象
* @param {number} timeout - 超时时间 ms
* @returns {Promise}
*/
export function postForm(url, formData, timeout = DEFAULT_TIMEOUT) {
const headers = {};
const token = localStorage.getItem('token');
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
return fetchWithTimeout(BASE_URL + url, {
method: 'POST',
headers,
body: formData,
}, timeout).then(handleResponse).catch(handleError);
}
/** /**
* 上传文件 * 上传文件
* @param {string} url - 请求路径 * @param {string} url - 请求路径
@ -175,4 +195,4 @@ export function upload(url, files, timeout = 30000) {
.catch(handleError); .catch(handleError);
} }
export default { get, post, put, del, upload, request }; export default { get, post, put, del, upload, postForm, request };

View File

@ -1,13 +1,17 @@
<template> <template>
<div class="base-list"> <div class="base-list" ref="listRef">
<div v-if="loading && list.length === 0" class="loading-wrap"> <div v-if="loading && list.length === 0" class="loading-wrap">
<van-loading size="24px">加载中...</van-loading> <van-loading size="24px">加载中...</van-loading>
</div> </div>
<template v-for="(item, index) in list" :key="item.id"> <template v-for="(item, index) in list" :key="item.id">
<slot :item="item" :index="index" /> <slot :item="item" :index="index" />
</template> </template>
<div v-if="loading && list.length > 0" class="loading-wrap">
<van-loading size="24px">加载中...</van-loading>
</div>
<div v-if="finished && list.length > 0" class="finished-text">{{ finishedText }}</div> <div v-if="finished && list.length > 0" class="finished-text">{{ finishedText }}</div>
<div v-if="finished && list.length === 0 && !loading" class="empty-text">暂无数据</div> <div v-if="finished && list.length === 0 && !loading" class="empty-text">暂无数据</div>
<div ref="sentinel" class="sentinel"></div>
</div> </div>
</template> </template>
@ -29,6 +33,7 @@ export default {
list: [], list: [],
page: 1, page: 1,
requestId: 0, requestId: 0,
observer: null,
} }
}, },
emits: ['update:list', 'load', 'refresh'], emits: ['update:list', 'load', 'refresh'],
@ -46,9 +51,32 @@ export default {
} }
}, },
mounted() { mounted() {
this.$nextTick(() => {
this.observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && !this.loading && !this.finished) {
this.loadMore() this.loadMore()
}
}, { rootMargin: '100px' })
if (this.$refs.sentinel) {
this.observer.observe(this.$refs.sentinel)
}
})
window.addEventListener('scroll', this.onWindowScroll)
},
beforeUnmount() {
this.observer?.disconnect()
window.removeEventListener('scroll', this.onWindowScroll)
}, },
methods: { methods: {
onWindowScroll() {
if (this.finished || this.loading) return
const el = this.$refs.sentinel
if (!el) return
const rect = el.getBoundingClientRect()
if (rect.top < window.innerHeight) {
this.loadMore()
}
},
loadMore() { loadMore() {
if (this.loading) return if (this.loading) return
const currentRequestId = this.requestId const currentRequestId = this.requestId
@ -58,7 +86,7 @@ export default {
.then(res => { .then(res => {
if (currentRequestId !== this.requestId) return if (currentRequestId !== this.requestId) return
const data = this.parseData(res) const data = this.parseData(res)
console.log(data); // console.log(data);
const safeData = data || [] const safeData = data || []
if (this.page === 1) { if (this.page === 1) {
@ -86,7 +114,6 @@ export default {
this._skipWatch = true this._skipWatch = true
this.loadMore() this.loadMore()
this.$emit('refresh', this.list) this.$emit('refresh', this.list)
// $nextTick
this._skipWatch = false this._skipWatch = false
}, },
}, },

View File

@ -1,6 +1,6 @@
<template> <template>
<!-- $isWechat() ? '' : title || --> <!-- $isWechat() ? '' : title || -->
<van-nav-bar fixed :title="$route.meta.title" :safe-area-inset-top="true" placeholder left-arrow left-text="返回" <van-nav-bar fixed :title="title || $route.meta.title" :safe-area-inset-top="true" placeholder left-arrow left-text="返回"
@click-left="back ? back() : $router.back();" :class="$isWechat() ? 'wechat-nav' : ''"> @click-left="back ? back() : $router.back();" :class="$isWechat() ? 'wechat-nav' : ''">
<template #right> <template #right>
<slot v-if="$slots.right" name="right" /> <slot v-if="$slots.right" name="right" />

View File

@ -44,8 +44,22 @@ const router = createRouter({
app.use(router); app.use(router);
import pinia from './stores'; import pinia from './stores';
import { useDatadicStore, dictCache } from './stores/datadic';
import { useUserStore } from './stores/user';
app.use(pinia); app.use(pinia);
const datadicStore = useDatadicStore()
datadicStore.init()
const userStore = useUserStore()
app.config.globalProperties.$datadic = {
get: (code) => dictCache[code] || null,
getContent: (code) => dictCache[code] ? dictCache[code].contents : '',
}
app.config.globalProperties.$userInfo = userStore.getUserInfo;
app.config.globalProperties.$showDialog = showDialog; app.config.globalProperties.$showDialog = showDialog;
app.config.globalProperties.$showConfirmDialog = showConfirmDialog; app.config.globalProperties.$showConfirmDialog = showConfirmDialog;
app.config.globalProperties.$showNotify = showNotify; app.config.globalProperties.$showNotify = showNotify;
@ -152,6 +166,7 @@ app.config.globalProperties.$token = (e) => {
}; };
app.config.globalProperties.$file = (e) => { app.config.globalProperties.$file = (e) => {
if (!e) return '/img/avatar.png'; if (!e) return '/img/avatar.png';
if (typeof e !== 'string') return '/img/avatar.png';
if (e.startsWith('http://') || e.startsWith('https://')) if (e.startsWith('http://') || e.startsWith('https://'))
return e; return e;
else else
@ -315,7 +330,7 @@ app.config.globalProperties.$getShareLink = () => {
} }
// CH // CH
import { post, get, put, del, request } from './api/http'; import { post, get, put, del, request, postForm } from './api/http';
import { isLogin, formatGMT } from './api/user'; import { isLogin, formatGMT } from './api/user';
app.config.globalProperties.$post = post; app.config.globalProperties.$post = post;
@ -323,6 +338,7 @@ app.config.globalProperties.$get = get;
app.config.globalProperties.$put = put; app.config.globalProperties.$put = put;
app.config.globalProperties.$del = del; app.config.globalProperties.$del = del;
app.config.globalProperties.$request = request; app.config.globalProperties.$request = request;
app.config.globalProperties.$postForm = postForm;
app.config.globalProperties.$isLogin = isLogin; app.config.globalProperties.$isLogin = isLogin;
app.config.globalProperties.$formatGMT = formatGMT; app.config.globalProperties.$formatGMT = formatGMT;
app.config.globalProperties.$formatCellphone = (e) => { app.config.globalProperties.$formatCellphone = (e) => {

View File

@ -205,7 +205,7 @@ const routes = [
{ {
path: '/GoodsDetail', path: '/GoodsDetail',
name: 'GoodsDetail', name: 'GoodsDetail',
component: () => import('./views/Goods/NGoodsDetail.vue'), component: () => import('./views/Goods/GoodsDetail.vue'),
meta: { title: '商品详情' } meta: { title: '商品详情' }
}, },
{ {
@ -268,6 +268,12 @@ const routes = [
component: () => import('./views/Merchant/MerchantIntroduction.vue'), component: () => import('./views/Merchant/MerchantIntroduction.vue'),
meta: { title: '商家资料' } meta: { title: '商家资料' }
}, },
{
path: '/QrReader',
name: 'QrReader',
component: () => import('./views/Operations/QrReader.vue'),
meta: { title: '扫一扫' }
},
{ {
path: '/Checkout', path: '/Checkout',
name: 'Checkout', name: 'Checkout',

38
src/stores/datadic.js Normal file
View File

@ -0,0 +1,38 @@
import { defineStore } from 'pinia'
import { get } from '@/api/http'
// Module level cache for quick access
export let dictCache = {}
export const useDatadicStore = defineStore('datadic', {
state: () => ({
dicts: {},
loaded: false
}),
getters: {
getByCode: (state) => (code) => {
return dictCache[code] || null
},
getContent: (code) => {
const item = dictCache[code]
return item ? item.contents : ''
}
},
actions: {
async init() {
if (this.loaded) return
try {
const res = await get('/v1/client/CDatadicsClient')
if (res.status === 200 && res.data) {
res.data.forEach(item => {
dictCache[item.code] = item
})
this.dicts = dictCache
this.loaded = true
}
} catch (e) {
console.error('数据字典加载失败', e)
}
}
}
})

View File

@ -2,4 +2,7 @@ import { createPinia } from 'pinia';
const pinia = createPinia(); const pinia = createPinia();
export { useDatadicStore } from './datadic';
export { useUserStore } from './user';
export default pinia; export default pinia;

View File

@ -1,18 +1,55 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { post } from '@/api/http';
export const useUserStore = defineStore('user', { export const useUserStore = defineStore('user', {
state: () => ({ state: () => ({
user: null,
token: localStorage.getItem('token') || '', token: localStorage.getItem('token') || '',
// 用户基本信息
user: null,
cellphone: localStorage.getItem('cellphone') || '',
nickname: localStorage.getItem('nickname') || '',
userimg: localStorage.getItem('userimg') || '',
// 扩展字段
isshop: localStorage.getItem('isshop') || '',
iscenter: localStorage.getItem('iscenter') || '',
user_id: localStorage.getItem('user_id') || '',
huiyuankaid: localStorage.getItem('huiyuankaid') || '',
}), }),
getters: { getters: {
isLogin: (state) => !!state.token, isLogin: (state) => !!state.token,
getUser: (state) => state.user,
getToken: (state) => state.token, getToken: (state) => state.token,
getUser: (state) => state.user,
getUserInfo: (state) => ({
cellphone: state.cellphone,
nickname: state.nickname,
userimg: state.userimg,
isshop: state.isshop,
iscenter: state.iscenter,
user_id: state.user_id,
huiyuankaid: state.huiyuankaid,
}),
}, },
actions: { actions: {
setUser(user) { setUser(user) {
this.user = user; this.user = user;
if (user) {
// 同步到 localStorage 保持兼容
this.cellphone = user.cellphone || '';
this.nickname = user.nickname || '';
this.userimg = user.userimg || '';
this.isshop = user.isshop || '';
this.iscenter = user.col2 || '';
this.user_id = user.id || '';
this.huiyuankaid = user.huiyuankaid || '';
localStorage.setItem('cellphone', this.cellphone);
localStorage.setItem('nickname', this.nickname);
localStorage.setItem('userimg', this.userimg);
localStorage.setItem('isshop', this.isshop);
localStorage.setItem('iscenter', this.iscenter);
localStorage.setItem('user_id', this.user_id);
localStorage.setItem('huiyuankaid', this.huiyuankaid);
}
}, },
setToken(token) { setToken(token) {
this.token = token; this.token = token;
@ -22,11 +59,36 @@ export const useUserStore = defineStore('user', {
localStorage.removeItem('token'); localStorage.removeItem('token');
} }
}, },
/**
* 登录后获取用户信息
*/
async fetchUserInfo() {
try {
const res = await post('/v1/client/UserClient/info');
if (res.status === 200 && res.data) {
this.setUser(res.data);
}
} catch (err) {
console.error('fetchUserInfo failed:', err);
}
},
clearUser() { clearUser() {
this.user = null; this.user = null;
this.token = ''; this.token = '';
localStorage.removeItem('token'); this.cellphone = '';
localStorage.removeItem('member_username'); this.nickname = '';
this.userimg = '';
this.isshop = '';
this.iscenter = '';
this.user_id = '';
this.huiyuankaid = '';
// 清除 localStorage
const keys = [
'token', 'cellphone', 'nickname', 'userimg',
'isshop', 'iscenter', 'user_id', 'huiyuankaid', 'member_username'
];
keys.forEach(key => localStorage.removeItem(key));
} }
} }
}); });

View File

@ -3115,12 +3115,14 @@
.line1 { .line1 {
.type { .type {
.bs; // .bs;
.box; .box;
.box-center-center; .box-center-center;
width: 10vw; color: #000000;
// width: 10vw;
height: 4.8vw; height: 4.8vw;
background-color: #222222; padding: 0 1.2vw;
background-color: #ffc6d4;
border-radius: 1.333vw; border-radius: 1.333vw;
margin-right: 1.2vw; margin-right: 1.2vw;
} }
@ -3737,11 +3739,13 @@
width: 86.67vw; width: 86.67vw;
margin-top: -1.33vw; margin-top: -1.33vw;
padding: 7.8vw 3.33vw 0; padding: 7.8vw 3.33vw 0;
white-space: nowrap;
.detail { .detail {
.box; .box;
.box-align-center; .box-align-center;
margin-bottom: 3.07vw; margin-bottom: 3.07vw;
white-space: nowrap;
span { span {
color: #000000; color: #000000;
@ -5110,6 +5114,47 @@
} }
} }
} }
.vanupup {
background-color: #ffffff;
border-radius: 1.6vw;
padding: 8.93vw 10vw;
.code_box {
.box;
.box-tb;
.top {
.box;
.box-center-center;
margin-bottom: 6.93vw;
font-size: 4.27vw;
color: #333333;
position: relative;
.van-icon {
position: absolute;
right: 0vw;
}
}
.ewm_box {
.box;
.box-tb;
.box-align-center;
padding: 5.2vw 4.53vw;
background-color: #ffffff;
border-radius: 1.6vw;
border: solid 0.53vw #4c4c4c;
img {
width: 57.6vw;
height: 57.33vw;
margin-bottom: 4vw;
}
}
}
}
} }
.opera { .opera {
@ -5364,6 +5409,10 @@
.f5; .f5;
padding: 7.6vw 4vw; padding: 7.6vw 4vw;
.van-radio {
margin-left: 1.2vw;
}
.shopinfo { .shopinfo {
.box; .box;
.box-align-center; .box-align-center;
@ -5448,6 +5497,11 @@
padding: 4vw 3.07vw; padding: 4vw 3.07vw;
margin-top: 4vw; margin-top: 4vw;
.van-cell {
padding: 2.67vw 0;
align-items: center
}
.price { .price {
.box; .box;
.box-align-center; .box-align-center;

View File

@ -1434,8 +1434,8 @@ img {
} }
.top { .top {
height: 30.4vw; height: 20.4vw;
padding: 19.067vw 0 0 2vw; padding: 10.067vw 0 0 2vw;
.box; .box;
img { img {
@ -2289,3 +2289,123 @@ img {
} }
} }
} }
.shareimgbox {
.box;
.box-tb;
.box-align-center;
padding: 6.8vw 9.2vw;
width: 80vw;
min-width: 80vw;
max-width: 80vw;
max-height: 90vh;
overflow-y: auto;
background-color: #ffffff;
border-radius: 2.67vw;
.share_box {
.box;
.box-tb;
.box-align-center;
width: 100%;
flex-shrink: 0;
}
.logo {
height: 10.53vw;
margin-bottom: 4vw;
}
.share_userinfo {
.box;
.box-align-center;
position: relative;
img {
height: 9.33vw;
border-radius: 50%;
position: relative;
z-index: 2;
}
.name {
.box;
.box-center-center;
color: #222222;
position: relative;
z-index: 1;
background-color: #fde5eb;
border-radius: 3.33vw;
// padding: .667vw 0;
// padding: 1.2vw 4vw 6vw;
padding-left: 6vw;
padding-right: 4vw;
margin-left: -3.33vw;
height: 8vw;
text-align: center;
span {
line-height: 8vw;
margin-bottom: 2.4vw;
}
}
}
.goods_info {
.box;
.box-tb;
.box-align-center;
margin-top: 4vw;
.goods_img {
width: 61.47vw;
height: 61.47vw;
background-color: #ffffff;
border-radius: 2.67vw;
border: solid 0.27vw #841e36;
}
.goods_name {
width: 61.47vw;
margin-top: 3.33vw;
.text-hide(2);
line-height: 4vw;
height: 8vw;
text-align: center;
}
.goods_price {
margin-top: 1.33vw;
}
}
.qrcode {
.box;
.box-tb;
.box-align-center;
margin-top: 3.33vw;
padding-bottom: 3vw;
img {
width: 17.07vw;
height: 17.07vw;
margin-bottom: 1.2vw;
}
}
.share_result {
margin-top: 4vw;
text-align: center;
img {
width: 100%;
border-radius: 2.67vw;
}
.tips {
margin-top: 2vw;
color: #999;
font-size: 3.2vw;
}
}
}

View File

@ -118,19 +118,13 @@
<!-- 用户协议弹窗 --> <!-- 用户协议弹窗 -->
<van-action-sheet v-model:show="showContract" safe-area-inset-bottom title="用户协议" closeable <van-action-sheet v-model:show="showContract" safe-area-inset-bottom title="用户协议" closeable
style="min-height: 50%; padding: 0px 10px 10px 10px"> style="min-height: 50%; padding: 0px 10px 10px 10px">
<div class="w100 html"> <div class="w100 html" v-html="$datadic.getContent('code_yhxy')"></div>
<p>用户协议内容...</p>
<p>这里是用户协议的详细条款...</p>
</div>
</van-action-sheet> </van-action-sheet>
<!-- 隐私政策弹窗 --> <!-- 隐私政策弹窗 -->
<van-action-sheet v-model:show="showPolicy" safe-area-inset-bottom title="隐私政策" closeable <van-action-sheet v-model:show="showPolicy" safe-area-inset-bottom title="隐私政策" closeable
style="min-height: 50%; padding: 0px 10px 10px 10px"> style="min-height: 50%; padding: 0px 10px 10px 10px">
<div class="w100 html"> <div class="w100 html" v-html="$datadic.getContent('code_yszc')"></div>
<p>隐私政策内容...</p>
<p>这里是隐私政策的详细条款...</p>
</div>
</van-action-sheet> </van-action-sheet>
</template> </template>
@ -247,6 +241,7 @@ export default {
} }
const userStore = useUserStore() const userStore = useUserStore()
userStore.setToken(data.data.token) userStore.setToken(data.data.token)
userStore.fetchUserInfo()
this.$showSuccessToast?.(data.message) this.$showSuccessToast?.(data.message)
location.replace('#/My') location.replace('#/My')
}).catch(err => { }).catch(err => {
@ -273,6 +268,7 @@ export default {
} }
const userStore = useUserStore() const userStore = useUserStore()
userStore.setToken(data.data.token) userStore.setToken(data.data.token)
userStore.fetchUserInfo()
this.$showSuccessToast?.(data.message) this.$showSuccessToast?.(data.message)
location.replace('#/My') location.replace('#/My')
}).catch(err => { }).catch(err => {

View File

@ -229,20 +229,23 @@ export default {
this.saving = true; this.saving = true;
const params = { const params = {
realname: this.tempData.realname, "realname": this.tempData.realname,
certno: this.tempData.idnumber, "idnumber": this.tempData.idnumber,
bankid: this.tempData.bankid || 0, "bankid": this.tempData.bankid || 0,
bankname: this.tempData.bankname || '', "bankname": this.tempData.bankname || '',
bankcardnumber: this.tempData.bankcardnumber, "bankcardnumber": this.tempData.bankcardnumber,
bankcellphone: this.tempData.bankcellphone, "bankcellphone": this.tempData.bankcellphone,
sort: 0, "sort": 0,
certtype: '身份证', "certtype": '00',
certvaliditytype: this.checked === 1 ? 1 : 0, "certvaliditytype": this.checked === 1 ? "1" : "0",
certbegindate: this.tempData.certbegindate ? this.tempData.certbegindate.replaceAll('-', '') : '', "certbegindate": this.tempData.certbegindate ? this.tempData.certbegindate.replaceAll('-', '') : '',
certenddate: this.checked === 1 ? '' : (this.tempData.certenddate ? this.tempData.certenddate.replaceAll('-', '') : ''), "certenddate": this.checked === 1 ? '' : (this.tempData.certenddate ? this.tempData.certenddate.replaceAll('-', '') : ''),
cardtype: 1, "cardtype": "1",
provid: this.tempData.provid || '', "provid": this.tempData.provid || '',
areaid: this.tempData.areaid || '' "areaid": this.tempData.areaid || '',
"isshopreceive": Boolean(this.$ls.get('isshop'))
// "isshopreceive": false
}; };
this.$post('/v1/client/DUserbankcardsClient', params).then(res => { this.$post('/v1/client/DUserbankcardsClient', params).then(res => {

View File

@ -66,12 +66,12 @@
<div class="name">{{ item.name }}</div> <div class="name">{{ item.name }}</div>
<div class="box box-pack-between"> <div class="box box-pack-between">
<s>{{ item.originalprice?.toFixed(2) }}</s> <s>{{ item.originalprice?.toFixed(2) }}</s>
<div class="zeng_box"> <!-- <div class="zeng_box">
<span></span> <span></span>
<div> <div>
{{ item.gongxianzhi }}贡献值 {{ item.gongxianzhi }}贡献值
</div> </div>
</div> </div> -->
</div> </div>
<div class="price"> <div class="price">
<b><span></span>{{ item.saleprice?.toFixed(2) }}</b> <b><span></span>{{ item.saleprice?.toFixed(2) }}</b>
@ -138,6 +138,11 @@ export default {
if (this.$route.query.id) { if (this.$route.query.id) {
this.changeMainById(Number(this.$route.query.id)); this.changeMainById(Number(this.$route.query.id));
} }
// URLSortType=2
if (this.$route.query.SortType == 2) {
this.searchParams.SortType = 2;
this.searchParams.IsDesc = true;
}
}, },
async loadCategories() { async loadCategories() {
try { try {

View File

@ -1,219 +1,160 @@
<template> <template>
<BasePage> <BasePage>
<div class="GoodsDetail"> <div class="goodsdetails">
<div class="t">
<img :src="$file(data.img)" alt="">
</div>
<!-- 海报 --> <div class="container">
<div <div class="goodsshow">
style="position: fixed;left: 0;top: 0; z-index: 0;width: 0px;height: 0px;opacity: 0; overflow: hidden;"> <div class="t">
<div id="bsCard" <div class="tit">
style="width: 260px; margin: 0 auto; overflow: hidden; border-radius: 10px;height: 450px;">
<div class="b_l_w"
style="background-color: #f2f1f7;height: max-content;padding-bottom: 15px;font-family: 'PingFang SC';">
<div class="b_l_w" style="padding: 10px 20px">
<img :src="$file(data.img)" style="width: 100%;height: 220px;" />
</div>
<div class="b_l"
style="background: #daa366;margin-left: 10px;width: 240px;padding: 5px 10px;border-radius: 10px;">
<div class="b_l">
<img :src="$file($ls.get('userimg'))" width="40" height="40"
style="border-radius: 100%; vertical-align: top" />
</div>
<div class="b_l"
style=" line-height: 40px;padding-left: 5px;font-size: 14px;font-weight: bold;color: #fff;">
{{ $ls.get('nickname')?.length > 8 ? $ls.get('nickname').substring(0, 6)
+ "..." : $ls.get('nickname') }}
友情推荐
</div>
</div>
<div class="b_l_w" style="padding: 5px 10px">
<div class="b_l"
style="width: 120px;font-size: 12px;overflow: hidden;text-overflow: ellipsis;display: -webkit-box;-webkit-box-orient: vertical;-webkit-line-clamp: 2;line-clamp: 2;">
{{ data.name }} {{ data.name }}
</div> </div>
<div class="b_l" style="height: 30px;width: 1px;background: #d3d3d3;margin: 5px 7px;"></div> <div class="desc" v-if="data.description">
<div class="b_r" style="width: 95px; line-height: 40px; font-size: 12px"> {{ data.description }}
价格 <span style="color: #fd3c2b">{{ data.saleprice }}</span>
</div> </div>
</div> </div>
<div class="b_l_w" style="padding: 0 10px">
<div class="b_r" style="margin-top: 0px; margin-right: 10px"> <div class="price_box">
<img :src="$file($ls.get('shareLogo'))" width="100" /> <div class="price">
¥{{ data.saleprice?.toFixed(2) }}
</div> </div>
<div class="b_l" <div class="oprice">
style="width: 78px;text-align: center;padding-top: 5px;height: 95px;font-size: 12px;"> ¥{{ data.originalprice?.toFixed(2) }}
<vue-qr :text="shareLink" backgroundColor="rgb(255,255,255,0)" style="width: 100%;" </div>
colorLight="rgb(255,255,255,0)"></vue-qr> <div class="sales r">
已售{{ data.salenums }}
</div>
</div>
<div class="deduct_box">
<div class="point">
<img src="/img/de-i1.png" alt="">
积分抵{{ data.deductjifen?.toFixed(2) || '0.00' }}
</div>
<div class="vip">
<img src="/img/de-i2.png" alt="">
会员卡额度抵{{ data.deducthuiyuanka?.toFixed(2) || '0.00' }}
</div>
</div>
<hr>
<div class="gt">
<div>
<img src="/img/gt-i1.png" alt="">
正品保障
</div>
<div>
<img src="/img/gt-i1.png" alt="">
快速发货
</div>
<div>
<img src="/img/gt-i1.png" alt="">
售后无忧
</div>
</div>
</div>
<div class="spec_box" @click="showSpecs = true">
<img src="/img/spec.png" alt="">
<span>{{ selectedSku }}</span>
<van-icon class="r" name="arrow"></van-icon>
</div>
<div class="prodetail">
<div class="tit">
<img src="/img/pro.png" alt="">
<span>产品详情</span>
</div>
<div class="vhtml" v-html="data.contents"></div>
</div>
</div>
<van-action-bar safe-area-inset-bottom placeholder>
<van-action-bar-icon icon="/img/vb-home.png" text="首页" @click="$navigate('Home')" />
<van-action-bar-icon icon="/img/vb-service.png" text="客服" @click="$goService()" />
<van-action-bar-icon icon="/img/vb-share.png" text="分享" @click="beforeShare" />
<van-action-bar-button color="#ca2904" type="danger" text="立即购买" @click="showSpecs = true" />
</van-action-bar>
<van-share-sheet v-model:show="showShare" title="立即分享给好友" :options="options" @select="onSelect" />
<van-popup v-model:show="showSpecs" position="bottom" closeable round>
<div class="showspec">
<div class="goods">
<img :src="$file(tempSku.img || data.img)" alt="">
<div>
<div class="s_price">¥{{ (tempSku.saleprice * tempSku.Qty).toFixed(2) }}</div>
<div class="s_stock">剩余{{ tempSku.saleleft }}</div>
<div class="s_spec">已选{{ tempSku.skuname }}</div>
</div>
</div>
<div class="spec_box" v-for="spec in specSelect">
<span class="title">
{{ spec.name }}
</span>
<div class="speccc">
<div v-for="child in spec.children" class="spec_de"
:class="selectedSpecs[spec.name] === child.name ? 'active' : 'inactive'"
@click="selectSpec(spec.name, child.name)">
{{ child.name }}
</div>
</div>
</div>
<div class="buynums box">
<span class="title">
购买数量
</span>
<van-stepper class="r" integer v-model="tempSku.Qty" min="1" max="99" theme="round" button-size="22"
disable-input />
</div>
<van-action-bar safe-area-inset-bottom placeholder>
<van-action-bar-button color="#ca2904" type="danger" text="立即购买" @click="submit" />
</van-action-bar>
</div>
</van-popup>
<van-popup v-model:show="show" class="shareimgbox" style="max-width: 80vw;">
<div class="share_box" id="bsCard">
<img class="logo" src="/img/logo-lr.png" alt="">
<div class="share_userinfo">
<img :src="$file($userInfo.userimg)" alt="">
<div class="name"><span>{{ $userInfo.nickname }}友情推荐</span></div>
</div>
<div class="goods_info">
<img class="goods_img" :src="$file(data.img)" alt="">
<div class="goods_name">{{ data.name }}</div>
<div class="goods_price">¥{{ data.saleprice?.toFixed(2) || '0.00' }}</div>
</div>
<div class="qrcode">
<vue-qr :margin="0" :text="shareLink" backgroundColor="rgb(255,255,255)"
colorLight="rgb(255,255,255)"></vue-qr>
长按扫码购买 长按扫码购买
</div> </div>
</div> </div>
</div> <div class="share_result" v-if="ShareImg">
</div> <img :src="ShareImg" alt="分享海报">
</div> <!-- <div class="tips">长按保存图片</div> -->
<van-popup v-model:show="showShareImg" style="width: 66.667vw;border-radius: 3.333vw;">
<img class="w" :src="ShareImg">
</van-popup>
<van-swipe class="banner" :autoplay="3000" indicator-color="white">
<van-swipe-item v-for="item in bannerImages"><img class="w" :src="$file(item)"></van-swipe-item>
<template #indicator="{ active, total }">
<div class="custom-indicator">{{ active + 1 }}/{{ total }}</div>
</template>
</van-swipe>
<div class="mx-auto">
<div class="top">
<div class="c">
<div class="name">
{{ data.name }}
</div>
<div class="remark">
{{ data.description }}
</div>
<div class="price" v-if="data.saleprice">
<b><span></span>{{ data.saleprice?.toFixed(2) }}</b>
<s>{{ data.originalprice?.toFixed(2) }}</s>
<p>已售 {{ data.salenums }}</p>
</div>
<!-- <div class="give">
<div>
<img src="/img/coin.png">
果实可抵{{ $trunc(data.saleprice * data.FruitRate / 100, 2).toFixed(2) }}
</div>
</div> -->
<div class="label">
<span><img src="/img/GroupShopDetail-i2.png">品质保障</span>
<span><img src="/img/GroupShopDetail-i2.png">正品包邮</span>
<span><img src="/img/GroupShopDetail-i2.png">售后无忧</span>
</div>
</div>
</div>
<div class="_sku" v-if="data.ForbiddenRule && data.ForbiddenRule.length > 0">
<van-cell icon="/img/notShip-i1.png" is-link @click="showNotShip = true">
<template #title>
不支持发货区域
<span class="span" v-for="list in data.ForbiddenRule">
<span v-for="(item, index) in list.Region">
{{ item.RegionName }}<font v-if="index < list.Region.length - 1"></font>
</span>
</span>
</template>
</van-cell>
</div>
<div class="_sku">
<van-cell icon="bars" @click="showSpecs = true" :title="selectedSku" is-link />
</div>
<div class="_video" v-if="videoList.length > 0" @click="showVideoList = true">
<img src="/img/GoodsDetail-video.png">
<p>视频介绍</p>
</div>
<div class="html">
<div class="title">
<div class="t">
<img src="/img/GroupShopDetail-tit-i3.png">
<b>产品详情</b>
</div>
</div>
<div class="w100" v-html="data.contents"></div>
</div>
</div>
<van-share-sheet v-model:show="showShare" title="立即分享给好友" v-bind:options="options" @select="onSelect">
</van-share-sheet>
<van-popup v-model:show="showTip" style="background: transparent;">
<img src="/img/fenxiangtishi2.png" class="w" @click="showTip = false" style="height: 100vh;">
</van-popup>
<van-action-bar class="b_l_w" safe-area-inset-bottom placeholder>
<van-action-bar-icon @click="$goService()" icon="/img/GroupShopDetail-footer-i1.png" text="客服" />
<van-action-bar-icon @click="beforeShare" icon="/img/GroupShopDetail-footer-i2.png" text="分享" />
<van-action-bar-button @click="showSpecs = true" color="#d0241c" text="立即购买" />
</van-action-bar>
<van-popup v-model:show="showSpecs" position="bottom" closeable round>
<div class="showSpecs">
<div class="_product">
<img :src="$file(tempSku.img || data.img)">
<div class="_c">
<div class="price">
<span></span>{{ (tempSku.saleprice * tempSku.Qty).toFixed(2) }}
</div>
<div class="d">
<div class="selected box box-pack-between">
已选:{{ tempSku.skuname }}
<span>库存:{{ tempSku.saleleft }}</Span>
</div>
</div>
</div>
</div>
<div v-for="(spec, index) in specSelect">
<div class="specs">
{{ spec.name }}
</div>
<div class="label">
<a v-for="(child, childIndex) in spec.children"
:class="selectedSpecs[spec.name] === child.name ? 'a' : ''"
@click="selectSpec(spec.name, child.name)">
{{ child.name }}
</a>
</div>
</div>
<div class="number" v-if="data.SpuArea != 'gift'">
购买数量
<van-stepper v-model="tempSku.Qty" min="1" max="99" input-width="8vw" allow-empty="true"
theme="round" button-size="5vw" disable-input />
</div>
<van-action-bar class="b_l_w" safe-area-inset-bottom placeholder>
<van-action-bar-button @click="submit" color="linear-gradient(to right, #ff6034, #ee0a24)"
text="立即购买" />
</van-action-bar>
</div> </div>
</van-popup> </van-popup>
<van-popup v-model:show="showNotShip" class="showNotShip">
<img src="/img/notShip-i2.png">
<div class="c">
<div class="text">
不支持发货区域
<span class="span" v-for="list in data.ForbiddenRule">
<span v-for="(item, index) in list.Region">
{{ item.RegionName }}<font v-if="index < list.Region.length - 1">;</font>
</span>
</span>
</div>
<a class="btn" @click="showNotShip = false">
好的知道了
</a>
</div>
</van-popup>
<van-action-sheet v-model:show="showVideoList" class="popup-GoodsDetail-videoList" safe-area-inset-bottom
title="视频介绍列表" closeable style="padding: 0 0 0; min-height: 60%">
<van-cell v-for="item in videoList" :icon="$file(item.Poster)" center :title="item.Name"
:label="formatTime(item.Duration)" @click="video = item; showVideo = true" />
</van-action-sheet>
<van-popup v-model:show="showVideo" closeable @click-overlay="video = {}" @click-close-icon="video = {}">
<video v-if="video.Video" :poster="$file(video.Poster)" controls="" style="width: 100%;height: auto;">
<source :src="$file(video.Video)" type="video/mp4" />
</video>
</van-popup>
</div> </div>
</BasePage> </BasePage>
</template> </template>
<script> <script>
@ -227,35 +168,21 @@ export default {
}, },
data() { data() {
return { return {
data: { data: {},
},
specSelect: [], specSelect: [],
show: false,
specCombinations: [], specCombinations: [],
tempSku: {}, tempSku: {},
showSpecs: false, showSpecs: false,
selectedSku: '请选择规格分类', selectedSku: '请选择规格分类',
selectedSpecs: {}, selectedSpecs: {},
showNotShip: false,
videoList: [],
video: {},
showVideoList: false,
showVideo: false,
showShare: false, showShare: false,
showTip: false,
showShareImg: false,
ShareImg: '',
options: [ options: [
{ name: "分享海报", icon: "/img/ff3.png" } { name: '分享海报', icon: '/img/share.png' },
], ],
shareLink: location.origin, shareLink: '',
cartLoading: false ShareImg: '',
} loading: false
},
computed: {
bannerImages() {
if (!this.data.imgs) return [this.data.img];
const imgs = this.data.imgs.split(';').filter(Boolean);
return imgs.length > 0 ? imgs : [this.data.img];
} }
}, },
methods: { methods: {
@ -264,7 +191,8 @@ export default {
this.data = res.data; this.data = res.data;
this.specSelect = this.data.specSelect || []; this.specSelect = this.data.specSelect || [];
this.specCombinations = this.data.specCombinations || []; this.specCombinations = this.data.specCombinations || [];
this.shareLink = `${location.origin}/GoodsDetail?id=${this.data.id}`; const recommend = localStorage.getItem('member_username') || '';
this.shareLink = `${location.origin}/#/GoodsDetail?id=${this.data.id}${recommend ? '&RecommendCode=' + recommend : ''}`;
this.$emit('updateShare', { title: this.data.name, imageUrl: this.data.img, link: this.shareLink }); this.$emit('updateShare', { title: this.data.name, imageUrl: this.data.img, link: this.shareLink });
this.initDefaultSku(); this.initDefaultSku();
}); });
@ -274,6 +202,13 @@ export default {
if (mainSku) { if (mainSku) {
this.tempSku = { ...mainSku, Qty: 1 }; this.tempSku = { ...mainSku, Qty: 1 };
this.selectedSku = "已选:" + mainSku.skuname; this.selectedSku = "已选:" + mainSku.skuname;
// skuname
this.specSelect.forEach(spec => {
const child = spec.children?.find(c => mainSku.skuname.includes(c.name));
if (child) {
this.selectedSpecs[spec.name] = child.name;
}
});
} }
}, },
selectSpec(specName, specValue) { selectSpec(specName, specValue) {
@ -298,10 +233,16 @@ export default {
this.selectedSku = "已选:" + matchedSku.skuname; this.selectedSku = "已选:" + matchedSku.skuname;
} }
}, },
onSelect(option, index) { onSelect(option) {
this.showShare = false; this.showShare = false;
if (option.name === "分享海报") { if (option.name === "分享海报") {
this.show = true;
this.loading = true;
this.ShareImg = '';
// DOM
setTimeout(() => {
this.generateShareImg(); this.generateShareImg();
}, 200);
} else if (option.name == "复制链接") { } else if (option.name == "复制链接") {
this.$copyText(location.href); this.$copyText(location.href);
this.$showToast({ type: "success", message: "复制成功" }); this.$showToast({ type: "success", message: "复制成功" });
@ -314,13 +255,14 @@ export default {
this.$showFailToast("该规格库存不足,无法购买"); this.$showFailToast("该规格库存不足,无法购买");
return; return;
} }
this.$navigate(`/TradeConfirm?id=${this.data.id}&skuid=${this.tempSku.skuid}`); this.$navigate(`/TradeConfirm?id=${this.data.id}&buynums=${this.tempSku.Qty}`);
}, },
beforeShare() { beforeShare() {
this.showShare = true; this.showShare = true;
}, },
async generateShareImg() { async generateShareImg() {
var shareContent = document.querySelector('#bsCard'); var shareContent = document.querySelector('#bsCard');
if (!shareContent) return;
var width = shareContent.offsetWidth; var width = shareContent.offsetWidth;
var height = shareContent.offsetHeight; var height = shareContent.offsetHeight;
try { try {
@ -332,19 +274,12 @@ export default {
allowTaint: true, allowTaint: true,
}); });
this.ShareImg = canvasToDataURL(canvas, "image/png", 1.0); this.ShareImg = canvasToDataURL(canvas, "image/png", 1.0);
this.showShareImg = true; this.loading = false;
document.querySelector('.share_box').style.display = 'none';
} catch (e) { } catch (e) {
console.error(e); console.error(e);
this.loading = false;
} }
},
formatTime(seconds) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = seconds % 60;
return `${this.pad(hours)}:${this.pad(minutes)}:${this.pad(secs)}`;
},
pad(num) {
return num.toString().padStart(2, '0');
} }
} }
} }

View File

@ -1,245 +0,0 @@
<template>
<BasePage>
<div class="goodsdetails">
<div class="t">
<img :src="$file(data.img)" alt="">
</div>
<div class="container">
<div class="goodsshow">
<div class="t">
<div class="tit">
{{ data.name }}
</div>
<div class="desc" v-if="data.description">
{{ data.description }}
</div>
</div>
<div class="price_box">
<div class="price">
¥{{ data.saleprice?.toFixed(2) }}
</div>
<div class="oprice">
¥{{ data.originalprice?.toFixed(2) }}
</div>
<div class="sales r">
已售{{ data.salenums }}
</div>
</div>
<div class="deduct_box">
<div class="point">
<img src="/img/de-i1.png" alt="">
积分抵1999.00
</div>
<div class="vip">
<img src="/img/de-i2.png" alt="">
会员卡额度抵1999.00
</div>
</div>
<hr>
<div class="gt">
<div>
<img src="/img/gt-i1.png" alt="">
正品保障
</div>
<div>
<img src="/img/gt-i1.png" alt="">
快速发货
</div>
<div>
<img src="/img/gt-i1.png" alt="">
售后无忧
</div>
</div>
</div>
<div class="spec_box" @click="showSpecs = true">
<img src="/img/spec.png" alt="">
<span>{{ selectedSku }}</span>
<van-icon class="r" name="arrow"></van-icon>
</div>
<div class="prodetail">
<div class="tit">
<img src="/img/pro.png" alt="">
<span>产品详情</span>
</div>
<div class="vhtml" v-html="data.contents"></div>
</div>
</div>
<van-action-bar safe-area-inset-bottom placeholder>
<van-action-bar-icon icon="/img/vb-home.png" text="首页" @click="$navigate('Home')" />
<van-action-bar-icon icon="/img/vb-service.png" text="客服" @click="$goService()" />
<van-action-bar-icon icon="/img/vb-share.png" text="分享" @click="beforeShare" />
<van-action-bar-button color="#ca2904" type="danger" text="立即购买" @click="showSpecs = true" />
</van-action-bar>
<van-share-sheet v-model:show="showShare" title="立即分享给好友" :options="options" @select="onSelect" />
<van-popup v-model:show="showSpecs" position="bottom" closeable round>
<div class="showspec">
<div class="goods">
<img :src="$file(tempSku.img || data.img)" alt="">
<div>
<div class="s_price">¥{{ (tempSku.saleprice * tempSku.Qty).toFixed(2) }}</div>
<div class="s_stock">剩余{{ tempSku.saleleft }}</div>
<div class="s_spec">已选{{ tempSku.skuname }}</div>
</div>
</div>
<div class="spec_box" v-for="spec in specSelect">
<span class="title">
{{ spec.name }}
</span>
<div class="speccc">
<div v-for="child in spec.children" class="spec_de"
:class="selectedSpecs[spec.name] === child.name ? 'active' : 'inactive'"
@click="selectSpec(spec.name, child.name)">
{{ child.name }}
</div>
</div>
</div>
<div class="buynums box">
<span class="title">
购买数量
</span>
<van-stepper class="r" integer v-model="tempSku.Qty" min="1" max="99" theme="round" button-size="22"
disable-input />
</div>
<van-action-bar safe-area-inset-bottom placeholder>
<van-action-bar-button color="#ca2904" type="danger" text="立即购买" @click="submit" />
</van-action-bar>
</div>
</van-popup>
</div>
</BasePage>
</template>
<script>
import { html2canvas, canvasToDataURL } from '@/utils/html2image';
export default {
name: 'GoodsDetail',
emits: ['updateShare'],
mounted() {
this.init();
},
data() {
return {
data: {},
specSelect: [],
specCombinations: [],
tempSku: {},
showSpecs: false,
selectedSku: '请选择规格分类',
selectedSpecs: {},
showShare: false,
options: [
{ name: '分享海报', icon: '/img/share.png' },
],
shareLink: location.origin
}
},
methods: {
init() {
this.$get(`/v1/client/EProsClient/${this.$route.query.id}`).then(res => {
this.data = res.data;
this.specSelect = this.data.specSelect || [];
this.specCombinations = this.data.specCombinations || [];
this.shareLink = `${location.origin}/GoodsDetail?id=${this.data.id}`;
this.$emit('updateShare', { title: this.data.name, imageUrl: this.data.img, link: this.shareLink });
this.initDefaultSku();
});
},
initDefaultSku() {
const mainSku = this.specCombinations.find(s => s.ismain) || this.specCombinations[0];
if (mainSku) {
this.tempSku = { ...mainSku, Qty: 1 };
this.selectedSku = "已选:" + mainSku.skuname;
// skuname
this.specSelect.forEach(spec => {
const child = spec.children?.find(c => mainSku.skuname.includes(c.name));
if (child) {
this.selectedSpecs[spec.name] = child.name;
}
});
}
},
selectSpec(specName, specValue) {
this.selectedSpecs[specName] = specValue;
this.refreshSku();
},
refreshSku() {
const specKeys = Object.keys(this.selectedSpecs);
if (specKeys.length === 0) return;
let matchedSku = this.specCombinations.find(combo => {
return specKeys.every(key => {
const spec = this.specSelect.find(s => s.name === key);
if (!spec) return false;
const specValue = this.selectedSpecs[key];
return combo.skuname === specValue;
});
});
if (matchedSku) {
this.tempSku = { ...matchedSku, Qty: this.tempSku.Qty || 1 };
this.selectedSku = "已选:" + matchedSku.skuname;
}
},
onSelect(option) {
this.showShare = false;
if (option.name === "分享海报") {
this.generateShareImg();
} else if (option.name == "复制链接") {
this.$copyText(location.href);
this.$showToast({ type: "success", message: "复制成功" });
} else if (option.name == "分享好友") {
this.showTip = true;
}
},
submit() {
if (this.tempSku.saleleft < 1) {
this.$showFailToast("该规格库存不足,无法购买");
return;
}
this.$navigate(`/TradeConfirm?id=${this.data.id}&buynums=${this.tempSku.Qty}`);
},
beforeShare() {
this.showShare = true;
},
async generateShareImg() {
var shareContent = document.querySelector('#bsCard');
if (!shareContent) return;
var width = shareContent.offsetWidth;
var height = shareContent.offsetHeight;
try {
var canvas = await html2canvas(shareContent, {
scale: 2,
width: width,
height: height,
useCORS: true,
allowTaint: true,
});
this.ShareImg = canvasToDataURL(canvas, "image/png", 1.0);
this.showShareImg = true;
} catch (e) {
console.error(e);
}
}
}
}
</script>

View File

@ -27,7 +27,7 @@
<div @click="$navigate('MerchantCashout')"> <div @click="$navigate('MerchantCashout')">
<span>提现中金额 <span>提现中金额
</span> </span>
<p>1000.00</p> <p>{{ data.withdrawing?.toFixed(2) }}</p>
</div> </div>
</div> </div>
</div> </div>
@ -232,6 +232,8 @@ export default {
this.$ls.set('merchant_shopname', res.data.shopname); this.$ls.set('merchant_shopname', res.data.shopname);
this.$ls.set('merchant_shopimg', res.data.shopimg); this.$ls.set('merchant_shopimg', res.data.shopimg);
this.$ls.set('merchant_cellphone', res.data.cellphone); this.$ls.set('merchant_cellphone', res.data.cellphone);
this.link = `${location.origin}${location.pathname}#/Checkout?id=${res.data.userid}`
}), }),
this.getCount() this.getCount()
] ]

View File

@ -4,33 +4,34 @@
<div class="top"> <div class="top">
<span> <span>
提现中 提现中
<b class="red">960.00</b> <b class="red">{{ statistics.withdrawing?.toFixed(2) || '0.00' }}</b>
</span> </span>
<span class="r"> <span class="r">
累计提现 累计提现
<b>5960.25</b> <b>{{ statistics.withdrawtotal?.toFixed(2) || '0.00' }}</b>
</span> </span>
</div> </div>
<div class="list"> <BaseList ref="listRef" class="list" url="/v1/client/DShopsClient/withdraw">
<template #default="{ item }">
<div class="item"> <div class="item">
<div class="line"> <div class="line">
<b class="state">提现中</b> <b class="state">{{ item.statename }}</b>
<b class="r money red"><span>960</span> <b class="r money red"><span>{{ item.wdnums?.toFixed(2) }}</span>
</b> </b>
</div> </div>
<div class="record"> <div class="record">
<div class="left"> <div class="left">
提现2024.06.05 00:00:00 提现{{ $formatGMT(item.addtime, 'yyyy-MM-dd HH:ss') }}
</div>
<div class="right">
发放:2024.06.05 05:25:35
</div> </div>
<div class="right" v-if="item.trantime">
发放:{{ $formatGMT(item.trantime, 'yyyy-MM-dd HH:ss') }}
</div> </div>
</div> </div>
</div> </div>
</template>
</BaseList>
</div> </div>
</BasePage> </BasePage>
</template> </template>
@ -38,7 +39,19 @@
<script> <script>
export default { export default {
data() { data() {
return {} return {
statistics: {}
}
},
mounted() {
this.init()
},
methods: {
init() {
this.$get('/v1/client/DShopsClient/withdrawstatistics').then(res => {
this.statistics = res.data || {}
})
}
} }
} }
</script> </script>

View File

@ -10,20 +10,20 @@
<van-icon @click="onShowTerm" name="question-o"></van-icon> <van-icon @click="onShowTerm" name="question-o"></van-icon>
</div> </div>
<span class="perform"> <span class="perform">
¥{{ data.total?.Balance.toFixed(2) }} ¥{{ data.total.income?.toFixed(2) }}
</span> </span>
<div class="btm"> <div class="btm">
<div> <div>
<span>昨日收入</span> <span>昨日收入</span>
<span> <span>
¥{{ data.total?.YesterdayAmount.toFixed(2) }} ¥{{ data.total.incomeyestoday?.toFixed(2) }}
</span> </span>
</div> </div>
<div class="r" style="justify-content: end;" @click="$navigate('MerchantCashout')"> <div class="r" style="justify-content: end;" @click="$navigate('MerchantCashout')">
<span>提现中</span> <span>提现中</span>
<span> <span>
¥{{ data.total?.CashAmount.toFixed(2) }} ¥{{ data.total?.withdrawing?.toFixed(2) }}
<van-icon name="arrow"></van-icon> <van-icon name="arrow"></van-icon>
</span> </span>
</div> </div>
@ -51,27 +51,30 @@
<div class="income"> <div class="income">
<span>当月收款</span> <span>当月收款</span>
<span> <span>
¥{{ data.total?.MonthBalance?.toFixed(2) }} ¥{{ data.total?.incomemonth?.toFixed(2) }}
</span> </span>
</div> </div>
</div> </div>
</div> </div>
<div class="details_box"> <div class="details_box">
<van-empty v-if="records.length === 0" description="暂无记录" />
<template v-else>
<div class="item" v-for="item in records"> <div class="item" v-for="item in records">
<div class="line1"> <div class="line1">
<span class="type">{{ type[item.RecordSource] }}</span> <span class="type">{{ item.typename }}</span>
<p>剩余{{ item.CurValue }}</p> <p>剩余{{ item.balance }}</p>
<span :class="item.OpeType > 0 ? 'income' : 'expend'">{{ item.OpeType > 0 ? <span :class="item.nums > 0 ? 'income' : 'expend'">{{ item.nums > 0 ? '+' : '' }}
'+' : '-' }}{{ item.RecordValue?.toFixed(2) }}</span> {{ item.nums?.toFixed(2) }}</span>
</div> </div>
<div> <div>
购买时间{{ item.CTime }} 操作时间{{ item.addtime }}
</div> </div>
<div> <div>
订单编号{{ item.TradeCode }} 订单编号{{ item.ordernum }}
</div> </div>
</div> </div>
</template>
</div> </div>
</div> </div>
@ -83,7 +86,7 @@
</van-popup> </van-popup>
<van-action-sheet v-model:show="showTerm" safe-area-inset-bottom title="营业收入说明" closeable <van-action-sheet v-model:show="showTerm" safe-area-inset-bottom title="营业收入说明" closeable
style="min-height: 50%; padding: 0px 10px 10px 10px"> style="min-height: 50%; padding: 0px 10px 10px 10px">
<div class="w100 html" v-html="data.Term" /> <div class="w100 html" v-html="$datadic.getContent('code_yysrsm')" />
</van-action-sheet> </van-action-sheet>
</template> </template>
@ -91,41 +94,57 @@
export default { export default {
name: 'MerchantIncome', name: 'MerchantIncome',
mounted() { mounted() {
this.init();
}, },
data() { data() {
return { return {
data: { data: {
total: { total: {
Balance: 12868.50, balance: 0,
YesterdayAmount: 568.00, incometoday: 0,
CashAmount: 2000.00, incomeyestoday: 0,
MonthBalance: 8650.00 incomemonth: 0,
withdrawing: 0
}
}, },
Term: '营业收入说明:商家通过平台交易获得的收入,每日自动转入绑定银行账户。' records: [],
},
records: [
{ RecordSource: 'sale', OpeType: 1, RecordValue: 128.00, CurValue: 12868.50, TradeCode: 'T20260418001', CTime: '2024-04-18 10:30:00' },
{ RecordSource: 'sale', OpeType: 1, RecordValue: 256.00, CurValue: 12868.50, TradeCode: 'T20260418002', CTime: '2024-04-18 11:20:00' },
{ RecordSource: 'cashout', OpeType: 0, RecordValue: 500.00, CurValue: 12868.50, TradeCode: 'T20260417001', CTime: '2024-04-17 18:00:00' },
{ RecordSource: 'sale', OpeType: 1, RecordValue: 89.00, CurValue: 12868.50, TradeCode: 'T20260417002', CTime: '2024-04-17 14:30:00' },
],
date: [`${new Date().getFullYear()}`, `${new Date().getMonth() + 1}`], date: [`${new Date().getFullYear()}`, `${new Date().getMonth() + 1}`],
currentDate: [`${new Date().getFullYear()}`, `${new Date().getMonth() + 1}`], currentDate: [`${new Date().getFullYear()}`, `${new Date().getMonth() + 1}`],
show: false, show: false,
showDate: false, showDate: false,
loading: false, loading: false,
showTerm: false, showTerm: false,
type: { sale: '收入', cashout: '提现', manual: "其他" } param: {
year: new Date().getFullYear(),
month: new Date().getMonth() + 1,
}
} }
}, },
methods: { methods: {
init() {
Promise.all([
this.$get('/v1/client/DShopsClient/moneystatistics', this.param),
this.$get('/v1/client/DShopsClient/money', this.param)
]).then(([total, recordsRes]) => {
this.data.total = total.data;
this.records = recordsRes.data;
}).catch(err => {
this.$showFailToast(err.message || '加载失败');
})
},
onconfirm(value) { onconfirm(value) {
this.date = this.currentDate; this.date = this.currentDate;
this.param = {
year: parseInt(this.currentDate[0]),
month: parseInt(this.currentDate[1]),
}
this.showDate = false; this.showDate = false;
this.init();
}, },
onShowTerm() { onShowTerm() {
this.showTerm = true; this.showTerm = true;
} }
} },
} }
</script> </script>

View File

@ -4,8 +4,7 @@
<div class="intro-header"> <div class="intro-header">
<div class="shop-avatar"> <div class="shop-avatar">
<van-uploader v-model="formData.AvatarUploader" :after-read="uploadShopImg" :max-count="1" upload-icon="plus"> <van-uploader v-model="formData.AvatarUploader" :after-read="uploadShopImg" :max-count="1" upload-icon="plus">
<img v-if="formData.shopimg" :src="$file(formData.shopimg)" class="avatar-img" /> <div class="avatar-placeholder">
<div v-else class="avatar-placeholder">
<van-icon name="photograph" size="20" /> <van-icon name="photograph" size="20" />
</div> </div>
</van-uploader> </van-uploader>
@ -23,10 +22,10 @@
</div> </div>
<van-cell-group inset class="info-group"> <van-cell-group inset class="info-group">
<van-field v-model="formData.shopname" label="店铺名称" placeholder="请输入店铺名称" required /> <van-field v-model="formData.shopname" label="店铺名称" placeholder="请输入店铺名称" required />
<van-field v-model="formData.huili" label="惠利比例" placeholder="请输入惠利比例" type="number" required>
<template #append>%</template>
</van-field>
<van-field v-model="formData.cellphone" label="联系电话" placeholder="请输入联系电话" type="tel" required /> <van-field v-model="formData.cellphone" label="联系电话" placeholder="请输入联系电话" type="tel" required />
<van-field v-model="formData.huili" label="惠利比例" placeholder="请输入惠利比例" type="number">
<template #extra><span style="color:#666">%</span></template>
</van-field>
</van-cell-group> </van-cell-group>
</div> </div>
@ -36,11 +35,12 @@
<span class="readonly-tag">仅展示</span> <span class="readonly-tag">仅展示</span>
</div> </div>
<van-cell-group inset class="info-group"> <van-cell-group inset class="info-group">
<van-field v-model="formData.legalname" label="法人姓名" readonly /> <van-field v-model="formData.legalpersonname" label="法人姓名" readonly />
<van-field v-model="formData.legalphone" label="法人手机号" readonly /> <van-field v-model="formData.legalpersonphone" label="法人手机号" readonly />
<van-field label="法人身份证照片" readonly> <van-field label="法人身份证照片" readonly>
<template #input> <template #input>
<div v-if="formData.legalidimg && formData.legalidimg.length" class="img-thumb" @click="previewImage(formData.legalidimg[0].url)"> <div v-if="formData.legalidimg && formData.legalidimg.length" class="img-thumb"
@click="previewImage(formData.legalidimg[0].url)">
<img :src="formData.legalidimg[0].url" /> <img :src="formData.legalidimg[0].url" />
</div> </div>
<span v-else class="empty">暂无</span> <span v-else class="empty">暂无</span>
@ -48,7 +48,8 @@
</van-field> </van-field>
<van-field label="营业执照照片" readonly> <van-field label="营业执照照片" readonly>
<template #input> <template #input>
<div v-if="formData.licenseimg && formData.licenseimg.length" class="img-thumb" @click="previewImage(formData.licenseimg[0].url)"> <div v-if="formData.licenseimg && formData.licenseimg.length" class="img-thumb"
@click="previewImage(formData.licenseimg[0].url)">
<img :src="formData.licenseimg[0].url" /> <img :src="formData.licenseimg[0].url" />
</div> </div>
<span v-else class="empty">暂无</span> <span v-else class="empty">暂无</span>
@ -64,10 +65,10 @@
<span class="readonly-tag">仅展示</span> <span class="readonly-tag">仅展示</span>
</div> </div>
<van-cell-group inset class="info-group"> <van-cell-group inset class="info-group">
<van-field v-model="formData.bankname" label="账户名称" readonly /> <van-field v-model="formData.bankrealname" label="账户名称" readonly />
<van-field v-model="formData.bankcard" label="银行卡号" readonly /> <van-field v-model="formData.bankcardnumber" label="银行卡号" readonly />
<van-field v-model="formData.subbank" label="支行名称" readonly /> <!-- <van-field v-model="formData.subbank" label="支行名称" readonly /> -->
<van-field v-model="formData.bankphone" label="银行预留手机号" readonly /> <van-field v-model="formData.bankcellphone" label="银行预留手机号" readonly />
</van-cell-group> </van-cell-group>
</div> </div>
@ -95,15 +96,15 @@ export default {
AvatarUploader: [], AvatarUploader: [],
huili: 20, huili: 20,
cellphone: '', cellphone: '',
legalname: '', legalpersonname: '',
legalphone: '', legalpersonphone: '',
legalidimg: [], legalidimg: [],
licenseimg: [], licenseimg: [],
province: '', province: '',
bankname: '', bankname: '',
bankcard: '', bankcardnumber: '',
subbank: '', subbank: '',
bankphone: '', bankcellphone: '',
} }
} }
}, },
@ -112,32 +113,37 @@ export default {
}, },
methods: { methods: {
init() { init() {
this.$get('/v1/client/DShopsClient/my').then(res => { this.$get('/v1/client/DShopsClient').then(res => {
const data = res.data const data = res.data
this.formData.shopname = data.shopname this.formData.shopname = data.shopname
this.formData.shopimg = data.shopimg this.formData.shopimg = data.shopimg
this.formData.huili = data.huili || 20 this.formData.huili = data.feeratio || 20
this.formData.cellphone = data.cellphone this.formData.cellphone = data.cellphone
this.formData.legalname = data.legalname this.formData.legalpersonname = data.legalpersonname
this.formData.legalphone = data.legalphone this.formData.legalpersonphone = data.legalpersonphone
this.formData.province = data.province this.formData.province = data.shopprovince + data.shopcity + data.shopcounty
this.formData.bankname = data.bankname this.formData.bankrealname = data.bankrealname
this.formData.bankcard = data.bankcard this.formData.bankcardnumber = data.bankcardnumber
this.formData.subbank = data.subbank this.formData.subbank = data.subbank
this.formData.bankphone = data.bankphone this.formData.bankcellphone = data.bankcellphone
if (data.shopimg) { if (data.shopimg) {
this.formData.AvatarUploader = [{ url: this.$file(data.shopimg), isImage: true }] this.formData.AvatarUploader = [{ url: this.$file(data.shopimg), isImage: true }]
} }
if (data.legalidimg) { if (data.legalpersonidimg) {
this.formData.legalidimg = [{ url: this.$file(data.legalidimg), isImage: true }] this.formData.legalidimg = [{ url: this.$file(data.legalpersonidimg), isImage: true }]
} }
if (data.licenseimg) { if (data.businesslicenseimg) {
this.formData.licenseimg = [{ url: this.$file(data.licenseimg), isImage: true }] this.formData.licenseimg = [{ url: this.$file(data.businesslicenseimg), isImage: true }]
} }
}) })
}, },
uploadShopImg(file) { uploadShopImg(file) {
if (!file || !file.file) {
this.formData.shopimg = ''
this.formData.AvatarUploader = []
return
}
this.formData.shopimg = file.file this.formData.shopimg = file.file
}, },
previewImage(url) { previewImage(url) {
@ -146,16 +152,13 @@ export default {
async save() { async save() {
this.saving = true this.saving = true
try { try {
const fd = new FormData()
fd.append('shopname', this.formData.shopname)
fd.append('shopphone', this.formData.cellphone)
if (this.formData.shopimg instanceof File) { if (this.formData.shopimg instanceof File) {
const url = await this.uploadFile('/v1/client/DShopsClient/shopimg', this.formData.shopimg) fd.append('file', this.formData.shopimg)
this.formData.shopimg = url
} }
await this.$postForm('/v1/client/DShopsClient/shopinfo', fd)
await this.$put('/v1/client/DShopsClient', {
shopname: this.formData.shopname,
huili: this.formData.huili,
cellphone: this.formData.cellphone,
})
this.$showSuccessToast('保存成功') this.$showSuccessToast('保存成功')
} catch (err) { } catch (err) {
this.$showFailToast(err.message || '保存失败') this.$showFailToast(err.message || '保存失败')
@ -213,7 +216,8 @@ export default {
border: 3px solid rgba(255, 255, 255, 0.4); border: 3px solid rgba(255, 255, 255, 0.4);
} }
.avatar-placeholder { .avatar-placeholder,
.van-uploader__preview-image {
width: 18vw; width: 18vw;
height: 18vw; height: 18vw;
border-radius: 50%; border-radius: 50%;
@ -287,6 +291,7 @@ export default {
width: 26vw; width: 26vw;
font-size: 3.73vw; font-size: 3.73vw;
color: #666; color: #666;
white-space: nowrap;
} }
:deep(.van-field__control) { :deep(.van-field__control) {
@ -299,6 +304,17 @@ export default {
} }
} }
:deep(.van-field__append) {
color: #666;
font-size: 3.73vw;
padding-left: 4px;
}
:deep(.van-field__extra) {
color: #666;
font-size: 3.73vw;
}
:deep(.van-field__control:disabled) { :deep(.van-field__control:disabled) {
color: #999; color: #999;
background: transparent; background: transparent;

View File

@ -1,12 +1,17 @@
<template> <template>
<BasePage> <BasePage>
<div class="merchanttradedetail"> <div class="merchanttradedetail">
<div class="state" :class="getStateClass(data.state)"> <div class="state" :class="getStateClass(data.state)" v-if="type !== 'user'">
<img :src="data.state >= 3 ? '/img/checked.png' : '/img/loading.png'" alt=""> <img :src="data.state >= 3 ? '/img/checked.png' : '/img/loading.png'" alt="">
<b>{{ data.statename }}</b> <b>{{ data.statename }}</b>
</div> </div>
<div class="containar"> <div class="state box-tb box-align-center" v-else>
<img :src="data.state >= 3 ? '/img/checked.png' : '/img/loading.png'" alt="">
<b style="margin:2.4vw 0 0;">交易完成</b>
</div>
<div class="containar" v-if="type !== 'user'">
<div class="line"></div> <div class="line"></div>
<div class="details_box"> <div class="details_box">
<div class="detail"> <div class="detail">
@ -21,39 +26,43 @@
<span>付款时间</span> <span>付款时间</span>
<p>{{ $formatGMT(data.paytime, 'yyyy-MM-dd HH:mm:ss') }}</p> <p>{{ $formatGMT(data.paytime, 'yyyy-MM-dd HH:mm:ss') }}</p>
</div> </div>
<div class="detail" v-if="data.payway">
<span>支付方式</span>
<p>{{ data.payway }}</p>
</div>
<div class="detail"> <div class="detail">
<span>交易单号</span> <span>交易单号</span>
<p>{{ data.ordernum }} <van-icon name="copy" @click="copyOrdernum" /></p> <p>{{ data.ordernum }}<span @click.stop="copyOrdernum">复制</span></p>
</div> </div>
<hr> <hr>
<div class="detail"> <div class="detail">
<span>买单商品总额</span> <span>买单商品总额</span>
<p>¥{{ data.ordermoney }}</p> <p>¥{{ data.ordermoney?.toFixed(2) }}</p>
</div> </div>
<div class="detail" v-if="data.payjifen"> <div class="detail" v-if="data.payjifen > 0">
<span>用户积分抵扣</span> <span>用户积分抵扣</span>
<p>{{ data.payjifen }}积分</p> <p>-{{ data.payjifen?.toFixed(2) }}积分</p>
</div> </div>
<div class="detail" v-if="data.payquan"> <div class="detail" v-if="data.payquan > 0">
<span>会员卡余额抵扣</span> <span>会员卡余额抵扣</span>
<p>¥{{ data.payquan }}</p> <p>-¥{{ data.payquan?.toFixed(2) }}</p>
</div> </div>
<div class="detail"> <div class="detail">
<span>用户实付金额</span> <span>用户实付金额</span>
<p>¥{{ data.paymoney }}</p> <p>¥{{ data.paymoney?.toFixed(2) }}</p>
</div> </div>
<hr>
<div class="detail" v-if="data.discountratio"> <div class="detail" v-if="data.discountratio">
<span>商家让利比例</span> <span>商家让利比例</span>
<p>{{ data.discountratio }}%</p> <p>{{ data.discountratio }}%</p>
</div> </div>
<hr>
<div class="detail"> <div class="detail">
<span>商家让利金额</span> <span>商家让利金额</span>
<p>¥{{ data.discount }}</p> <p>-¥{{ data.discount }}</p>
</div> </div>
<div class="detail"> <div class="detail">
<span>商家应收金额</span> <span>商家应收金额</span>
@ -61,6 +70,59 @@
</div> </div>
</div> </div>
</div> </div>
<div class="containar" v-else>
<div class="line"></div>
<div class="details_box">
<div class="detail">
<span>商户名称</span>
<p>{{ data.shopname }}</p>
</div>
<div class="detail" v-if="type === '2'">
<span>用户账号</span>
<p>{{ $formatCellphone(data.usercellphone) }}</p>
</div>
<div class="detail">
<span>下单时间</span>
<p>{{ $formatGMT(data.addtime, 'yyyy-MM-dd HH:mm:ss') }}</p>
</div>
<div class="detail">
<span>付款时间</span>
<p>{{ $formatGMT(data.paytime, 'yyyy-MM-dd HH:mm:ss') }}</p>
</div>
<div class="detail" v-if="data.payway">
<span>支付方式</span>
<p>{{ data.payway }}</p>
</div>
<div class="detail">
<span>交易单号</span>
<p>{{ data.ordernum }}<span @click.stop="copyOrdernum">复制</span></p>
</div>
<hr>
<div class="detail">
<span>商品总额</span>
<p>¥{{ data.ordermoney?.toFixed(2) }}</p>
</div>
<div class="detail" v-if="data.payjifen">
<span>积分抵扣</span>
<p>-{{ data.payjifen?.toFixed(2) }}积分</p>
</div>
<div class="detail" v-if="data.payquan">
<span>会员卡余额抵扣</span>
<p>-{{ data.payquan?.toFixed(2) }}积分</p>
</div>
<div class="detail">
<span>实付金额</span>
<p class="red">¥{{ data.paymoney?.toFixed(2) }}</p>
</div>
<div class="detail" v-if="data.discountratio">
<span>让利比例</span>
<p>{{ data.discountratio }}%</p>
</div>
</div>
</div>
</div> </div>
</BasePage> </BasePage>
</template> </template>
@ -70,7 +132,8 @@ export default {
name: 'MerchantTradeDetail', name: 'MerchantTradeDetail',
data() { data() {
return { return {
data: {} data: {},
type: this.$route.query.type,
} }
}, },
mounted() { mounted() {
@ -79,7 +142,12 @@ export default {
methods: { methods: {
init() { init() {
const ordernum = this.$route.query.id; const ordernum = this.$route.query.id;
this.$get(`/v1/client/DShopsClient/order/${ordernum}`).then(res => { const typeParam = this.$route.query.type;
const type = typeParam === 'user' ? '2' : (typeParam || '1');
const api = type === '2'
? `/v1/client/FOrdersshopClient/${ordernum}`
: `/v1/client/DShopsClient/order/${ordernum}`;
this.$get(api).then(res => {
this.data = res.data || {}; this.data = res.data || {};
}).catch(err => { }).catch(err => {
this.$showFailToast(err.message || '加载失败'); this.$showFailToast(err.message || '加载失败');

View File

@ -5,12 +5,12 @@
<div class="top"> <div class="top">
<div class="info_box"> <div class="info_box">
<img class="icon" src="" alt=""> <img class="icon" :src="$file(userimg || '/img/default-avatar.png')" alt="">
<div class="inf"> <div class="inf">
<b>奖励一个挖野菜</b> <b>{{ nickname || '未登录' }}</b>
<div> <div>
<img src="/img/phone.png" alt=""> <img src="/img/phone.png" alt="">
12568951256 {{ cellphone || '暂无手机号' }}
</div> </div>
</div> </div>
</div> </div>
@ -32,7 +32,7 @@
<van-icon name="arrow"></van-icon> <van-icon name="arrow"></van-icon>
</div> </div>
<b> <b>
2565.00 {{ zijin?.toFixed(2) || '0.00' }}
</b> </b>
</div> </div>
@ -43,13 +43,13 @@
<van-icon name="arrow"></van-icon> <van-icon name="arrow"></van-icon>
</div> </div>
<b> <b>
2565.00 {{ xiaofeiquan?.toFixed(2) || '0.00' }}
</b> </b>
</div> </div>
</div> </div>
<div class="fun_box"> <div class="fun_box">
<div class="item"> <div class="item" @click="$navigate('QrReader')">
<div class="left"> <div class="left">
<b> <b>
扫一扫 扫一扫
@ -94,10 +94,16 @@ export default {
components: { ManagerPopup }, components: { ManagerPopup },
data() { data() {
return { return {
managerPopupVisible: false managerPopupVisible: false,
userimg: this.$ls.get('userimg') || '',
nickname: this.$ls.get('nickname') || '',
cellphone: this.$ls.get('cellphone') || '',
zijin: 0,
xiaofeiquan: 0,
} }
}, },
mounted() { mounted() {
this.init()
window.addEventListener('showManagerPopup', this.onShowManagerPopup) window.addEventListener('showManagerPopup', this.onShowManagerPopup)
window.addEventListener('closeManagerPopup', this.onCloseManagerPopup) window.addEventListener('closeManagerPopup', this.onCloseManagerPopup)
}, },
@ -106,6 +112,19 @@ export default {
window.removeEventListener('closeManagerPopup', this.onCloseManagerPopup) window.removeEventListener('closeManagerPopup', this.onCloseManagerPopup)
}, },
methods: { methods: {
init() {
this.$get('/v1/client/DUsersClient').then(res => {
this.userimg = res.data.userimg
this.nickname = res.data.nickname
this.cellphone = res.data.cellphone
this.zijin = res.data.zijin
this.xiaofeiquan = res.data.xiaofeiquan
// localStorage
this.$ls.set('userimg', res.data.userimg)
this.$ls.set('nickname', res.data.nickname)
this.$ls.set('cellphone', res.data.cellphone)
})
},
onShowManagerPopup() { onShowManagerPopup() {
this.managerPopupVisible = true this.managerPopupVisible = true
}, },

View File

@ -0,0 +1,357 @@
<template>
<div class="qr-scanner">
<video ref="videoRef" class="video" playsinline autoplay></video>
<!-- 遮罩层 -->
<div class="mask">
<!-- 中间扫码区域 -->
<div class="scan-area">
<div class="corner left-top"></div>
<div class="corner left-bottom"></div>
<div class="corner right-top"></div>
<div class="corner right-bottom"></div>
<!-- 扫描线动画 -->
<div class="scan-line" v-if="isScanning"></div>
</div>
</div>
<!-- 顶部提示 -->
<div class="header">
<div class="back" @click="$router.back()">
<van-icon name="arrow-left" />
</div>
<div class="title">扫一扫</div>
<div class="placeholder"></div>
</div>
<!-- 底部提示 -->
<div class="footer">
<div class="tip" v-if="!isScanning && !result && !error">将二维码放入框内即可自动扫描</div>
<div class="error" v-if="error">{{ error }}</div>
<div class="result" v-if="result">
<div class="label">扫描结果</div>
<div class="value">{{ result }}</div>
</div>
</div>
<!-- 开始扫描提示 -->
<div class="start-tip" v-if="!isScanning && !result" @click="startScan">
<van-icon name="scan" size="32" />
<span>点击开始扫描</span>
</div>
<!-- 核销确认弹框 -->
<van-dialog v-model:show="showConfirm" title="确认核销" show-cancel-button @confirm="confirmVerify"
@cancel="cancelVerify">
<div style="padding: 20px; text-align: center;">
是否确认核销此礼品券<br>
券码{{ pendingSysid }}
</div>
</van-dialog>
</div>
</template>
<script>
import { BrowserMultiFormatReader, NotFoundException } from '@zxing/library';
export default {
name: 'QrReader',
data() {
return {
videoRef: null,
result: '',
isScanning: false,
error: '',
codeReader: null,
stream: null,
showConfirm: false,
pendingSysid: '',
}
},
mounted() {
this.videoRef = this.$refs.videoRef;
},
beforeUnmount() {
this.stopScan();
},
methods: {
async startScan() {
this.error = '';
this.result = '';
try {
this.codeReader = new BrowserMultiFormatReader();
await this.codeReader.decodeFromVideoDevice(
null,
this.videoRef,
(decodedText, err) => {
if (decodedText) {
this.result = decodedText;
this.handleScanResult(decodedText);
this.stopScan();
}
if (err && !(err instanceof NotFoundException)) {
console.error('扫码错误:', err);
}
}
);
this.isScanning = true;
} catch (e) {
console.error(e);
if (e.name === 'NotAllowedError' || e.name === 'PermissionDeniedError') {
this.error = '摄像头权限被拒绝,请在设置中允许访问摄像头';
} else if (e.name === 'NotFoundError' || e.name === 'DevicesNotFoundError') {
this.error = '未找到摄像头设备';
} else {
this.error = e.message || '无法访问摄像头,请确保已授予摄像头权限';
}
}
},
stopScan() {
if (this.codeReader) {
this.codeReader.reset();
}
this.isScanning = false;
},
handleScanResult(text) {
const str = String(text);
//
if (str.includes('/Checkout?id=')) {
const id = str.split('/Checkout?id=')[1];
window.location.href = `#/Checkout?id=${id}`;
return;
}
//
const match = str.match(/DUserlipinquansClient\/([^/]+)\/state/);
if (match) {
this.pendingSysid = match[1];
this.showConfirm = true;
return;
}
// http
if (str.startsWith('http://') || str.startsWith('https://')) {
window.location.href = str;
return;
}
//
this.result = str;
},
confirmVerify() {
this.showConfirm = false;
this.$put(`/v1/client/DUserlipinquansClient/${this.pendingSysid}/state`).then(res => {
if (res.status === 200) {
this.$showSuccessToast('核销成功');
this.result = '核销成功';
this.$navigate('CertificateRecord?type=verif')
// setTimeout(() => {
// this.$router.back();
// }, 1500);
}
}).catch(err => {
this.$showFailToast(err.message || '核销失败');
this.error = err.message || '核销失败';
});
},
cancelVerify() {
this.showConfirm = false;
this.pendingSysid = '';
},
},
}
</script>
<style scoped>
.qr-scanner {
position: fixed;
inset: 0;
background: #000;
z-index: 1000;
}
.video {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.mask {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.5);
}
.scan-area {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 60vw;
height: 60vw;
max-width: 250px;
max-height: 250px;
background: transparent;
}
.scan-area::before {
content: '';
position: absolute;
inset: 0;
background: transparent;
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5);
}
.corner {
position: absolute;
width: 24px;
height: 24px;
border-color: #fff;
border-style: solid;
}
.left-top {
top: 0;
left: 0;
border-width: 3px 0 0 3px;
border-radius: 4px 0 0 0;
}
.left-bottom {
bottom: 0;
left: 0;
border-width: 0 0 3px 3px;
border-radius: 0 0 0 4px;
}
.right-top {
top: 0;
right: 0;
border-width: 3px 3px 0 0;
border-radius: 0 4px 0 0;
}
.right-bottom {
bottom: 0;
right: 0;
border-width: 0 3px 3px 0;
border-radius: 0 0 4px 0;
}
.scan-line {
position: absolute;
top: 0;
left: 10%;
right: 10%;
height: 2px;
background: linear-gradient(90deg, transparent, #07c160, transparent);
animation: scanMove 2s linear infinite;
}
@keyframes scanMove {
0% {
top: 0;
}
100% {
top: 100%;
}
}
.header {
position: absolute;
top: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
padding-top: calc(12px + env(safe-area-inset-top));
background: rgba(0, 0, 0, 0.3);
}
.back {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 20px;
}
.title {
color: #fff;
font-size: 17px;
font-weight: 500;
}
.placeholder {
width: 32px;
}
.footer {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 20px;
padding-bottom: calc(20px + env(safe-area-inset-bottom));
text-align: center;
}
.tip {
color: #fff;
font-size: 14px;
text-align: center;
}
.error {
color: #ff4d4f;
font-size: 14px;
text-align: center;
margin-top: 8px;
}
.result {
background: rgba(255, 255, 255, 0.9);
border-radius: 8px;
padding: 12px;
margin-bottom: 16px;
}
.result .label {
font-size: 14px;
color: #666;
margin-bottom: 4px;
}
.result .value {
font-size: 16px;
color: #333;
word-break: break-all;
}
.controls {
position: absolute;
bottom: 100px;
left: 50%;
transform: translateX(-50%);
}
.start-tip {
position: absolute;
bottom: 120px;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
color: #fff;
font-size: 14px;
}
</style>

View File

@ -152,9 +152,6 @@ export default {
this.$get('/v1/client/CNewsClient', { page: 1, pageSize: 3, pid: 3 }).then(data => { this.$get('/v1/client/CNewsClient', { page: 1, pageSize: 3, pid: 3 }).then(data => {
this.data.News = data.data.items; this.data.News = data.data.items;
}), }),
this.$get('/v1/client/CDatadicsClient/code_sp').then(data => {
this.data.Video = data.data;
}),
this.$get('/v1/client/CNewsClient?pid=2').then(data => { this.$get('/v1/client/CNewsClient?pid=2').then(data => {
this.data.Trends = data.data.items; this.data.Trends = data.data.items;
}), }),
@ -164,6 +161,9 @@ export default {
]).catch(err => { ]).catch(err => {
this.$showFailToast(err.message || '数据加载失败'); this.$showFailToast(err.message || '数据加载失败');
}); });
//
this.data.Video = this.$datadic.getContent('code_sp')
this.data.slideBg = this.$datadic.getContent('code_yqmbj')
}, },
create() { create() {
// this.showcreate = true; // this.showcreate = true;

View File

@ -40,8 +40,7 @@
<div class="deduction"> <div class="deduction">
<div class="top"> <div class="top">
<span>商品金额</span> <span>商品金额</span>
<b class="r"><span></span>{{ ((product.saleprice || 0) * (product.buynums || 1))?.toFixed(2) <b class="r"><span></span>{{ totalPrice }}</b>
}}</b>
</div> </div>
<van-radio-group v-model="checked" checked-color="#ca2904"> <van-radio-group v-model="checked" checked-color="#ca2904">
<van-radio name="1" label-position="left"> <van-radio name="1" label-position="left">
@ -52,7 +51,8 @@
<span>使用积分抵扣</span> <span>使用积分抵扣</span>
</div> </div>
<p> <p>
(当前可用10.00) (当前可用{{ (userInfo.xiaofeijifen || 0)?.toFixed(2) }}本次可抵{{
actualJifenDeduct?.toFixed(2) }})
</p> </p>
</div> </div>
</template> </template>
@ -65,24 +65,26 @@
<span>使用会员卡抵扣</span> <span>使用会员卡抵扣</span>
</div> </div>
<p> <p>
(当前可用10.00) (当前可用{{ (userInfo.xiaofeiquan || 0)?.toFixed(2) }}本次可抵{{
actualQuanDeduct?.toFixed(2) }})
</p> </p>
</div> </div>
</template> </template>
</van-radio> </van-radio>
</van-radio-group> </van-radio-group>
<div class="count"> <div class="count">
<div> <div v-if="freight > 0">
<span>运费</span> <span>运费</span>
<span class="price"> <span class="price">{{ freight.toFixed(2) }}</span>
{{ product.expressprice ? '¥' + product.expressprice.toFixed(2) : '¥0.00' }} </div>
</span> <div v-if="checked">
<span>抵扣金额</span>
<span class="price">-{{ actualDeduct?.toFixed(2) || '0.00' }}</span>
</div> </div>
<div> <div>
<span>实付金额</span> <span>实付金额</span>
<span class="price"> <span class="price">
{{ product.saleprice ? '¥' + (product.saleprice * product.buynums).toFixed(2) : '¥0.00' {{ actualPay?.toFixed(2) || '0.00' }}
}}
</span> </span>
</div> </div>
</div> </div>
@ -94,7 +96,7 @@
</div> </div>
</div> </div>
<van-submit-bar class="b_l_w" placeholder :price="useFruitPrice * 100" :tip="`商品总数:${totalQuantity}件`" <van-submit-bar class="b_l_w" placeholder :price="actualPay * 100" :tip="`商品总数:${totalQuantity}件`"
tip-icon="info-o" safe-area-inset-bottom> tip-icon="info-o" safe-area-inset-bottom>
<template #button> <template #button>
<van-button round color="rgb(227, 83, 33)" :loading="isloading" @click="submitOrder" <van-button round color="rgb(227, 83, 33)" :loading="isloading" @click="submitOrder"
@ -114,6 +116,7 @@ export default {
name: 'TradeConfirm', name: 'TradeConfirm',
mounted() { mounted() {
this.loadProduct(); this.loadProduct();
this.loadUserInfo();
}, },
data() { data() {
return { return {
@ -124,9 +127,9 @@ export default {
useCoupon: 0, useCoupon: 0,
fruit: 0, fruit: 0,
fruitData: {}, fruitData: {},
isFruit: false,
isloading: false, isloading: false,
checked: 1, checked: '',
userInfo: {},
}; };
}, },
computed: { computed: {
@ -136,8 +139,45 @@ export default {
totalPrice() { totalPrice() {
return ((this.product.saleprice || 0) * (this.product.buynums || 1)).toFixed(2); return ((this.product.saleprice || 0) * (this.product.buynums || 1)).toFixed(2);
}, },
//
goodsAmount() {
return (this.product.saleprice || 0) * (this.product.buynums || 1);
},
//
freight() {
return this.product.expressprice || 0;
},
//
maxJifenDeduct() {
return (this.product.deductjifen || 0) * (this.product.buynums || 1);
},
//
maxQuanDeduct() {
return (this.product.deducthuiyuanka || 0) * (this.product.buynums || 1);
},
//
actualJifenDeduct() {
const max = this.maxJifenDeduct;
const available = this.userInfo.xiaofeijifen || 0;
return Math.min(max, available, this.goodsAmount);
},
//
actualQuanDeduct() {
const max = this.maxQuanDeduct;
const available = this.userInfo.xiaofeiquan || 0;
return Math.min(max, available, this.goodsAmount);
},
//
actualDeduct() {
if (!this.checked) return 0;
return this.checked === '1' ? this.actualJifenDeduct : this.actualQuanDeduct;
},
// = + -
actualPay() {
return this.goodsAmount + this.freight - this.actualDeduct;
},
useFruitPrice() { useFruitPrice() {
return this.totalPrice; return this.actualPay;
}, },
}, },
methods: { methods: {
@ -164,10 +204,17 @@ export default {
skuname: sku?.skuname || '默认规格', skuname: sku?.skuname || '默认规格',
buynums: parseInt(this.$route.query.buynums) || 1, buynums: parseInt(this.$route.query.buynums) || 1,
typename: data.typename || '商品', typename: data.typename || '商品',
expressprice: data.expressprice || 0 expressprice: data.expressprice || 0,
deductjifen: data.deductjifen || 0,
deducthuiyuanka: data.deducthuiyuanka || 0
}; };
}); });
}, },
loadUserInfo() {
this.$get('/v1/client/DUsersClient').then(res => {
this.userInfo = res.data;
}).catch(() => { });
},
onAddressConfirm(addr) { onAddressConfirm(addr) {
this.address = addr; this.address = addr;
}, },
@ -191,7 +238,8 @@ export default {
city: this.address.city, city: this.address.city,
county: this.address.county, county: this.address.county,
address: this.address.ReceiveAddress, address: this.address.ReceiveAddress,
isquan: this.isFruit ishuiyuanka: this.checked === '2',
isjifen: this.checked === '1'
}; };
this.$post('/v1/client/FOrdersClient', form).then(data => { this.$post('/v1/client/FOrdersClient', form).then(data => {
this.$showSuccessToast('提交成功'); this.$showSuccessToast('提交成功');
@ -202,10 +250,6 @@ export default {
this.isloading = false; this.isloading = false;
}); });
}, },
toggleClick() {
if (!this.exchangePoint)
this.isFruit = !this.isFruit;
}
}, },
}; };
</script> </script>

View File

@ -1,67 +1,63 @@
<template> <template>
<BasePage :back="back" title="订单详情"> <BasePage :back="back" title="订单详情">
<div class="tradelist-details" v-if="data.ordernum" <div class="tradelist-details" v-if="data.ordernum"
:style="`background: url(/img/mallDetail-bg.png) no-repeat;background-size: 100% auto;`"> :style="`background: url(/img/mallDetail-bg.jpg) no-repeat;background-size: 100% auto;`">
<div class="top"> <div class="top">
<img :src="`/img/mallDetail-i${data.state}.png`"> <img :src="`/img/mallDetail-i${data.state}.png` || '/img/mallDetail-i0.png'">
<b style="#d2220d">{{ data.statename }}</b> <b style="#d2220d">{{ data.statename }}</b>
</div> </div>
<div class="_address"> <div class="_goods">
<img src="/img/tradelistDetail-i5.png"> <img :src="$file(data.proimg)">
<div class="c">
<div>
{{ data.receiptrealname }}
<span>{{ data.receiptcellphone }}</span>
</div>
<p>
{{ data.receiptprovince }}{{ data.receiptcity }}{{ data.receiptcounty }}{{ data.receiptaddress
}}
</p>
</div>
</div>
<div class="_goods" v-for="item in data.orderdetails" :key="item.id">
<img :src="$file(item.proimg)">
<div class="c"> <div class="c">
<div class="name"> <div class="name">
<span>{{ item.proname }}</span> <span>{{ data.proname }}</span>
</div> </div>
<div class="s"> <div class="s">
<p>{{ item.proskuname?.split(';').join('/') }}</p> <p>{{ data.proskuname?.split(';').join('/') }}</p>
</div> </div>
<div class="price"> <div class="price">
<b> <b>
<span></span>{{ item.saleprice?.toFixed(2) }} <span></span>{{ data.proskusaleprice?.toFixed(2) }}
</b> </b>
<p>x{{ item.buynums }}</p> <p>x{{ data.buynums }}</p>
</div> </div>
</div> </div>
</div> </div>
<div class="_detail"> <div class="_detail">
<div class="p"> <div class="p">
<p>
订单号
<span>{{ data.ordernum }} <img @click="$copyText(data.ordernum); $showSuccessToast('复制成功')"
src="/img/copy.png"></span>
</p>
<p>
商品总数
<span>{{data.orderdetails?.reduce((sum, sku) => sum + sku.buynums, 0)}}</span>
</p>
<p> <p>
商品总价 商品总价
<span>{{ data.orderoriginalmoney?.toFixed(2) }}</span> <span>{{ data.ordermoney?.toFixed(2) }}</span>
</p>
<p v-if="data.payjifen > 0">
积分抵扣
<span>-{{ data.payjifen?.toFixed(2) }}</span>
</p> </p>
<p v-if="data.payquan > 0"> <p v-if="data.payquan > 0">
优惠券抵扣 会员卡额度抵扣
<span>-{{ data.payquan?.toFixed(2) }}</span> <span>-{{ data.payquan?.toFixed(2) }}</span>
</p> </p>
<p> <p>
实付金额 实付金额
<b style="color: #f00;">{{ data.needpay?.toFixed(2) }}</b> <b style="color: #f00;">{{ data.realmoney?.toFixed(2) }}</b>
</p> </p>
<p v-if="data.zongsongjifen">
赠送积分
<span>{{ data.zongsongjifen }}</span>
</p>
<hr style="margin:1.67vw 0;height: 1px;border: none;background: #f5f5f580;">
<p>
订单号
<span>{{ data.ordernum }} <img @click="$copyText(data.ordernum); $showSuccessToast('复制成功')"
src="/img/copy.png"></span>
</p>
<!-- <p>
商品总数
<span>{{ data.buynums }}</span>
</p> -->
<p> <p>
下单时间 下单时间
<span>{{ $formatGMT(data.addtime, 'yyyy-MM-dd HH:mm:ss') }}</span> <span>{{ $formatGMT(data.addtime, 'yyyy-MM-dd HH:mm:ss') }}</span>
@ -117,6 +113,7 @@ export default {
try { try {
const res = await this.$get(`/v1/client/FOrdersClient/${this.ordernum}`); const res = await this.$get(`/v1/client/FOrdersClient/${this.ordernum}`);
this.data = res.data; this.data = res.data;
this.data.orderdetails = res.data.items;
} catch (err) { } catch (err) {
this.$showFailToast(err.message || '加载失败'); this.$showFailToast(err.message || '加载失败');
} }

View File

@ -41,14 +41,14 @@
<p>x{{ item.buynums }}</p> <p>x{{ item.buynums }}</p>
</div> </div>
<div class="concession"> <div class="concession">
<span>¥{{ item.realmoney?.toFixed(2) }}</span> <span>¥{{ item.proskusaleprice?.toFixed(2) }}</span>
<p v-if="item.discountratio">{{ item.discountratio }}%让利</p> <p v-if="item.discountratio">{{ item.discountratio }}%让利</p>
</div> </div>
</div> </div>
</div> </div>
<div class="price"> <div class="price">
实付<span><b>9999.05</b></span> 实付<span><b>{{ item.realmoney?.toFixed(2) }}</b></span>
</div> </div>
<div class="state_box"> <div class="state_box">
@ -57,10 +57,11 @@
</b> </b>
<div class="btn_box"> <div class="btn_box">
<button v-if="item.state === 0">取消订单</button> <button v-if="item.state === 0" @click="cancelTrade(item)">取消订单</button>
<button v-if="item.state === 3">查看物流</button> <!-- <button v-if="item.state === 3">查看物流</button> -->
<button @click="$navigate(`TradeDetail?ordernum=${item.ordernum}`)">查看详情</button> <button @click="$navigate(`TradeDetail?ordernum=${item.ordernum}`)">查看详情</button>
<button v-if="item.state === 0">立即支付</button> <button v-if="item.state === 3" @click="confirmReceipt(item)">确认收货</button>
<button v-if="item.state === 0" @click="$navigate(`Pay?ordernum=${item.ordernum}`)">立即支付</button>
</div> </div>
</div> </div>
</div> </div>
@ -85,7 +86,7 @@
</div> </div>
<div class="price"> <div class="price">
实付<span><b>{{ item.ordermoney?.toFixed(2) }}</b></span> 实付<span><b>{{ item.realmoney?.toFixed(2) }}</b></span>
</div> </div>
<div class="detail_box"> <div class="detail_box">
@ -111,7 +112,7 @@
</b> </b>
<div class="btn_box"> <div class="btn_box">
<button @click="$navigate(`CheckoutTrade?ordernum=${item.ordernum}&type=user`)">查看详情</button> <button @click="$navigate(`MerchantTradeDetail?id=${item.ordernum}&type=user`)">查看详情</button>
</div> </div>
</div> </div>
</div> </div>
@ -198,6 +199,18 @@ export default {
}, },
onRefresh() { onRefresh() {
}, },
confirmReceipt(item) {
this.$showConfirmDialog({
title: "是否确认收货",
}).then(() => {
this.$put(`/v1/client/FOrdersClient/${item.ordernum}/receipt`).then(() => {
this.$showSuccessToast('确认收货成功');
this.$refs.baseList?.refresh();
}).catch(err => {
this.$showFailToast(err.message || '操作失败');
});
}).catch(() => { });
},
cancelTrade(item) { cancelTrade(item) {
this.$showConfirmDialog({ this.$showConfirmDialog({
title: "是否确认取消订单", title: "是否确认取消订单",

View File

@ -1,14 +1,14 @@
<template> <template>
<BasePage> <BasePage>
<div class="checkout"> <div class="checkout" v-if="data.shop && data.shop.shopname">
<div class="shopinfo"> <div class="shopinfo">
<img class="icon" :src="$file()" alt=""> <img class="icon" :src="$file(data.shop.shopimg)" alt="">
<div class="inf"> <div class="inf">
<b>完美日记旗舰店</b> <b>{{ data.shop.shopname }}</b>
<div class="hl"> <div class="hl">
<img src="/img/buybill.png" alt=""> <img src="/img/buybill.png" alt="">
<span>20%</span> <span>{{ data.shop.feeratio }}%</span>
</div> </div>
</div> </div>
</div> </div>
@ -19,27 +19,29 @@
</b> </b>
<div class="input_box"> <div class="input_box">
¥<input type="number" placeholder="请输入买单金额"> ¥<input type="number" v-model="req.ordermoney" placeholder="请输入买单金额" @input="calcDeduction">
</div> </div>
</div> </div>
<div class="deduction_box"> <div class="deduction_box">
<van-radio-group v-model="checked"> <van-radio-group v-model="checked">
<van-cell-group inset center> <van-cell-group inset center>
<van-cell center title="积分抵扣" icon="/img/point_icon.png" clickable @click="checked = '1'"> <van-cell center title="积分抵扣" icon="/img/point_icon.png">
<template #right-icon> <template #right-icon>
<span class="canuse">25000.00可用</span> <span class="cantuse" v-if="!data.user.xiaofeijifen || data.user.xiaofeijifen <= 0">暂无可用</span>
<span class="cantuse">暂无可用</span> <span class="canuse" v-else-if="checked !== '1'">{{ data.user.xiaofeijifen?.toFixed(2) }}可用</span>
<span v-if="checked === '1'" class="use">-500.00</span> <span class="use" v-else>-{{ jifenDeduction.toFixed(2) }}</span>
<van-radio name="1" /> <van-radio name="1" @click="toggleDeduction('jifen')"
:disabled="!data.user.xiaofeijifen || data.user.xiaofeijifen <= 0" />
</template> </template>
</van-cell> </van-cell>
<van-cell title="会员卡抵扣" icon="/img/vip_icon.png" clickable @click="checked = '2'"> <van-cell title="会员卡抵扣" icon="/img/vip_icon.png">
<template #right-icon> <template #right-icon>
<span class="canuse">25000.00可用</span> <span class="cantuse" v-if="!data.user.xiaofeiquan || data.user.xiaofeiquan <= 0">暂无可用</span>
<span class="cantuse">暂无可用</span> <span class="canuse" v-else-if="checked !== '2'">{{ data.user.xiaofeiquan?.toFixed(2) }}可用</span>
<span v-if="checked === '2'" class="use">-500.00</span> <span class="use" v-else>-{{ quanDeduction.toFixed(2) }}</span>
<van-radio name="2" /> <van-radio name="2" @click="toggleDeduction('quan')"
:disabled="!data.user.xiaofeiquan || data.user.xiaofeiquan <= 0" />
</template> </template>
</van-cell> </van-cell>
</van-cell-group> </van-cell-group>
@ -50,11 +52,11 @@
<p> <p>
实付 实付
</p> </p>
<span><b>500.00</b></span> <span><b>{{ actualPay.toFixed(2) }}</b></span>
</div> </div>
</div> </div>
<button class="payfor"> <button class="payfor" @click="toPay">
去支付 去支付
</button> </button>
</div> </div>
@ -65,7 +67,74 @@
export default { export default {
data() { data() {
return { return {
checked: '1', checked: '',
id: this.$route.query.id,
data: {
user: {},
shop: {}
},
req: {
ordermoney: '',
"isjifen": false,
"ishuiyuanka": false,
"shopuserid": ''
},
jifenDeduction: 0,
quanDeduction: 0
}
},
computed: {
actualPay() {
let pay = this.req.ordermoney || 0;
if (this.checked === '1') {
pay -= this.jifenDeduction;
} else if (this.checked === '2') {
pay -= this.quanDeduction;
}
return Math.max(0, pay);
}
},
mounted() {
this.getShopInfo();
},
methods: {
getShopInfo() {
this.$get('/v1/client/DUsersClient').then(res => {
this.data.user = res.data;
})
if (this.id)
this.$get(`/v1/client/DShopsClient/${this.id}`).then(res => {
console.log(res);
this.data.shop = res.data;
this.req.shopuserid = res.data.userid;
})
},
toggleDeduction(type) {
if (type === 'jifen') {
if (!this.data.user.xiaofeijifen || this.data.user.xiaofeijifen <= 0) return;
this.checked = '1'
this.req.isjifen = this.checked === '1';
this.req.ishuiyuanka = false;
} else if (type === 'quan') {
if (!this.data.user.xiaofeiquan || this.data.user.xiaofeiquan <= 0) return;
this.checked = '2';
this.req.ishuiyuanka = this.checked === '2';
this.req.isjifen = false;
}
},
calcDeduction() {
const money = this.req.ordermoney || 0;
// 20%50% = * 10%
this.jifenDeduction = Math.min(money * 0.1, this.data.user.xiaofeijifen || 0);
// 20%40% = * 8%
this.quanDeduction = Math.min(money * 0.08, this.data.user.xiaofeiquan || 0);
},
toPay() {
this.req.isjifen = this.checked === '1';
this.req.ishuiyuanka = this.checked === '2';
this.$post('/v1/client/FOrdersshopClient', this.req).then(res => {
this.$navigate(`Pay?ordernum=${res.data}`)
})
} }
} }
} }

View File

@ -4,8 +4,8 @@
<div class="ewm_bg" style="font-family: 'PingFang SC'"> <div class="ewm_bg" style="font-family: 'PingFang SC'">
<div class="d1"> <div class="d1">
<div class="w100"> <div class="w100">
<!-- <img id="invite_bg" :src="$file(data.Icon)" /> --> <img id="invite_bg" :src="$file($datadic.getContent('code_yqmbj'))" crossorigin="anonymous" />
<img id="invite_bg" src="/img/invite_bg.jpg" crossorigin="anonymous"> <!-- <img id="invite_bg" src="/img/invite_bg.jpg" crossorigin="anonymous"> -->
</div> </div>
<img class="icon" :src="$file(data.Avatar) + '?t=1'" crossorigin="anonymous"> <img class="icon" :src="$file(data.Avatar) + '?t=1'" crossorigin="anonymous">
<div class="name"> <div class="name">

View File

@ -269,7 +269,10 @@ export default {
this.$ls.set('isshop', data.data.isshop); this.$ls.set('isshop', data.data.isshop);
this.$ls.set('iscenter', data.data.col2); this.$ls.set('iscenter', data.data.col2);
this.$ls.set('user_id', data.data.id); this.$ls.set('user_id', data.data.id);
this.$ls.set('huiyuankaid', data.data.huiyuankaid) this.$ls.set('huiyuankaid', data.data.huiyuankaid);
this.$ls.set('user_cv', data.data.xiaofeiquan);
this.$ls.set('user_point', data.data.xiaofeijifen);
}), }),
] ]
) )
@ -317,40 +320,19 @@ export default {
this.tempData.userimg = file.file; this.tempData.userimg = file.file;
}, },
save() { save() {
if (this.tempData.userimg instanceof File) {
const fd = new FormData(); const fd = new FormData();
fd.append('nickname', this.tempData.nickname);
fd.append('cellphone', this.tempData.cellphone);
if (this.tempData.userimg instanceof File) {
fd.append('file', this.tempData.userimg); fd.append('file', this.tempData.userimg);
fetch(`${import.meta.env.VITE_API_URL}/v1/client/DUsersClient/userimg`, { }
method: 'POST', this.$postForm('/v1/client/DUsersClient/userinfo', fd).then(() => {
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` },
body: fd,
}).then(res => res.json()).then(res => {
if (res.status !== 200) throw new Error(res.message);
this.tempData.userimg = res.data;
}).then(() => {
return this.$put('/v1/client/DUsersClient', {
nickname: this.tempData.nickname,
cellphone: this.tempData.cellphone,
});
}).then(() => {
this.$showSuccessToast('保存成功');
this.showInfo = false;
window.location.reload();
}).catch(err => {
this.$showFailToast(err.message || err.message || '保存失败');
});
} else {
this.$put('/v1/client/DUsersClient', {
nickname: this.tempData.nickname,
cellphone: this.tempData.cellphone,
}).then(() => {
this.$showSuccessToast('保存成功'); this.$showSuccessToast('保存成功');
this.showInfo = false; this.showInfo = false;
window.location.reload(); window.location.reload();
}).catch(err => { }).catch(err => {
this.$showFailToast(err.message || '保存失败'); this.$showFailToast(err.message || '保存失败');
}); });
}
}, },
valid(field, msg) { valid(field, msg) {
if (!field) { if (!field) {

View File

@ -36,7 +36,7 @@
<van-action-sheet v-model:show="showTerm" safe-area-inset-bottom :title="`礼包业绩说明`" closeable <van-action-sheet v-model:show="showTerm" safe-area-inset-bottom :title="`礼包业绩说明`" closeable
style="min-height: 50%; padding: 0px 10px 10px 10px"> style="min-height: 50%; padding: 0px 10px 10px 10px">
<div class="w100 html" v-html="data.Term" /> <div class="w100 html" v-html="$datadic.getContent('code_lbyjsm')" />
</van-action-sheet> </van-action-sheet>
</div> </div>
</BasePage> </BasePage>

View File

@ -282,7 +282,7 @@
</van-popup> </van-popup>
<van-action-sheet v-model:show="showTerm" safe-area-inset-bottom :title="`${this.$route.meta.title}说明`" <van-action-sheet v-model:show="showTerm" safe-area-inset-bottom :title="`${this.$route.meta.title}说明`"
closeable style="min-height: 50%; padding: 0px 10px 10px 10px"> closeable style="min-height: 50%; padding: 0px 10px 10px 10px">
<div class="w100 html" v-html="data.Term" /> <div class="w100 html" v-html="$datadic.getContent('code_yesm')" />
</van-action-sheet> </van-action-sheet>
</BasePage> </BasePage>

Some files were not shown because too many files have changed in this diff Show More