diff --git a/public/img/paycode.jpg b/public/img/paycode.jpg index 0ca3cf3..c445dcb 100644 Binary files a/public/img/paycode.jpg and b/public/img/paycode.jpg differ diff --git a/src/api/http.js b/src/api/http.js index 79ac8d9..ec9ed4d 100644 --- a/src/api/http.js +++ b/src/api/http.js @@ -62,17 +62,20 @@ function handleResponse(response) { /** * 处理 token 失效 */ -function handleUnauthorized() { - // 如果 localStorage 中本来就没有 token,说明是未登录状态,不做处理 - if (!localStorage.getItem('member_token')) { +function handleUnauthorized(requestToken) { + // 只有本次请求发出时携带的 token 失效,才清除登录状态 + if (!requestToken) { + return; + } + + // 如果本地 token 已经被新的登录流程更新,不处理旧请求的 401/403 + if (localStorage.getItem('member_token') !== requestToken) { return; } - // 清除登录状态 localStorage.removeItem('member_token'); localStorage.removeItem('member_username'); - // 跳转到登录页 if (window.location.hash !== '#/Login') { window.location.href = '#/Login'; } @@ -81,10 +84,10 @@ function handleUnauthorized() { /** * 统一错误处理 */ -function handleError(err) { +function handleError(err, requestToken) { // token 失效 if (err.status === ERR_CODE.UNAUTHORIZED || err.status === ERR_CODE.FORBIDDEN) { - handleUnauthorized(); + handleUnauthorized(requestToken); } return Promise.reject(err); } @@ -120,14 +123,16 @@ function fetchWithTimeout(url, options, timeout = DEFAULT_TIMEOUT) { * @returns {Promise} */ export function request(url, data, method = 'POST', timeout = DEFAULT_TIMEOUT) { + const requestToken = localStorage.getItem('member_token'); return fetchWithTimeout(BASE_URL + url, { method, headers: getHeaders(), body: JSON.stringify(data), - }, timeout).then(handleResponse).catch(handleError); + }, timeout).then(handleResponse).catch(err => handleError(err, requestToken)); } export const get = (url, params, timeout = DEFAULT_TIMEOUT) => { + const requestToken = localStorage.getItem('member_token'); let query = ''; if (params) { query = '?' + new URLSearchParams(params).toString(); @@ -135,7 +140,7 @@ export const get = (url, params, timeout = DEFAULT_TIMEOUT) => { return fetchWithTimeout(BASE_URL + url + query, { method: 'GET', headers: getHeaders(), - }, timeout).then(handleResponse).catch(handleError); + }, timeout).then(handleResponse).catch(err => handleError(err, requestToken)); }; export const post = (url, data, timeout = DEFAULT_TIMEOUT) => request(url, data, 'POST', timeout); @@ -159,7 +164,7 @@ export function postForm(url, formData, timeout = DEFAULT_TIMEOUT) { method: 'POST', headers, body: formData, - }, timeout).then(handleResponse).catch(handleError); + }, timeout).then(handleResponse).catch(err => handleError(err, token)); } /** diff --git a/src/main.js b/src/main.js index a4ac13d..fc2901b 100644 --- a/src/main.js +++ b/src/main.js @@ -52,16 +52,16 @@ datadicStore.init() const userStore = useUserStore() // 全局前置守卫 -router.beforeEach((to, from, next) => { - if (to.meta.noLogin) { - return next(); - } - const isLogin = userStore.isLogin; - if (!isLogin) { - return next({ name: 'Login', query: { redirect: to.fullPath } }); - } - next(); -}); +// router.beforeEach((to, from, next) => { +// if (to.meta.noLogin) { +// return next(); +// } +// const isLogin = userStore.isLogin; +// if (!isLogin) { +// return next({ name: 'Login', query: { redirect: to.fullPath } }); +// } +// next(); +// }); app.config.globalProperties.$datadic = { get: (code) => dictCache[code] || null, diff --git a/src/router.js b/src/router.js index 832bfd8..c0485e5 100644 --- a/src/router.js +++ b/src/router.js @@ -320,20 +320,6 @@ const routes = [ component: () => import('./views/User/Checkout/CheckoutTrade.vue'), meta: { title: '订单详情' } }, - // { - // path: '/Refresh', - // name: 'Refresh', - // component: { render: () => null }, - // meta: { noLogin: true }, - // beforeRouteEnter(to, from, next) { - // next(vm => { - // setTimeout(() => { - // const redirect = to.query.redirect || '/Home' - // vm.$router.replace(decodeURIComponent(redirect)) - // }, 50) - // }) - // } - // }, { path: '/SyncAuth', name: 'SyncAuth', diff --git a/src/styles/ch.less b/src/styles/ch.less index a90a8e5..21c1644 100644 --- a/src/styles/ch.less +++ b/src/styles/ch.less @@ -3426,6 +3426,11 @@ .top { .box; .box-align-center; + position: fixed; + left: 0; + right: 0; + top: calc(var(--van-nav-bar-height, 46px) + env(safe-area-inset-top)); + z-index: 998; padding: 0 4vw; height: 12vw; background-color: #ffffff; @@ -3452,7 +3457,7 @@ } .list { - padding: 0 4vw; + padding: 16vw 4vw 0; margin-top: 4vw; .item { diff --git a/src/views/404.vue b/src/views/404.vue index b7e3b43..d209de8 100644 --- a/src/views/404.vue +++ b/src/views/404.vue @@ -17,7 +17,7 @@ export default { }, methods: { back() { - this.$router.back(); + this.$navigate('/My'); } } } diff --git a/src/views/Cashout/Cashout.vue b/src/views/Cashout/Cashout.vue index 97a35ac..fb20b25 100644 --- a/src/views/Cashout/Cashout.vue +++ b/src/views/Cashout/Cashout.vue @@ -86,6 +86,12 @@ export default { Promise.all([ this.$get('/v1/client/DUsersClient').then(data => { this.wallet.Balance = data.data.zijin; + if (data.data.userlevelname === '会员') { + this.$showFailToast('您还未购买礼包产品成为VIP,暂不能提现!') + setTimeout(() => { + this.$navigate('/My') + }, 1500); + } }).catch(err => { this.$showFailToast(err.message); }), @@ -94,6 +100,7 @@ export default { }) ]) + }, selectCard(item) { this.card = item; diff --git a/src/views/Merchant/MerchantTradeDetail.vue b/src/views/Merchant/MerchantTradeDetail.vue index 3b15980..47b3054 100644 --- a/src/views/Merchant/MerchantTradeDetail.vue +++ b/src/views/Merchant/MerchantTradeDetail.vue @@ -26,9 +26,9 @@ 付款时间:

{{ $formatGMT(data.paytime, 'yyyy-MM-dd HH:mm:ss') }}

-
+
支付方式: -

{{ data.payway }}

+

{{ data.paychannelname }}

交易单号: @@ -53,13 +53,13 @@ 用户实付金额:

¥{{ data.paymoney?.toFixed(2) }}

+ +
+
商家惠利比例:

{{ data.discountratio }}%

-
- -
商家惠利金额:

-¥{{ data.discount }}

diff --git a/src/views/Merchant/PayCode.vue b/src/views/Merchant/PayCode.vue index fd0770b..9f34f87 100644 --- a/src/views/Merchant/PayCode.vue +++ b/src/views/Merchant/PayCode.vue @@ -24,9 +24,7 @@
- {{ loading ? - '生成中...' : ('保存图片') - }} + 长按图片保存
@@ -69,47 +67,40 @@ export default { this.bgLoaded = true }, + async generateImage() { if (this.loading) return this.loading = true try { await this.$nextTick() - // Wait for fonts with fallback for WeChat/Safari compatibility - if (document.fonts && document.fonts.ready) { - await document.fonts.ready - } - await new Promise(resolve => setTimeout(resolve, 500)) const el = this.$refs.paycodeOriginalRef - if (!el) { - throw new Error('Element not found') - } - this.generatedImage = await toDataURL(el, { format: 'png', pixelRatio: 3, useCORS: true }) + if (!el) throw new Error('Element not found') + + // 等待元素内所有 加载完成(覆盖背景、商家头像、vue - qr 渲染完成的 img) + const imgs = Array.from(el.querySelectorAll('img')) + await Promise.all(imgs.map(img => { + if (img.complete && img.naturalWidth > 0) return + Promise.resolve() + return new Promise(resolve => { + img.onload = img.onerror = resolve + }) + })) + + // 等 vue-qr 内部的 canvas 真正画出来(vue-qr 内部用 canvas 绘图) + await new Promise(resolve => setTimeout(resolve, 100)) + + this.generatedImage = await toDataURL(el, { + format: 'png', + pixelRatio: 3, useCORS: true + }) } catch (e) { console.error('生成收款码失败:', e) this.$showFailToast('生成失败') } finally { this.loading = false } - }, + } - async handleSave() { - if (this.loading) return - try { - this.loading = true - if (!this.generatedImage) { - await this.generateImage() - } - if (this.generatedImage) { - downloadByDataURL(this.generatedImage, `收款码_${this.shopname}`) - this.$showSuccessToast('保存成功') - } - } catch (e) { - console.error('保存失败:', e) - this.$showFailToast('保存失败') - } finally { - this.loading = false - } - }, }, } diff --git a/src/views/Merchant/Statistics.vue b/src/views/Merchant/Statistics.vue index 7d54777..3ab8547 100644 --- a/src/views/Merchant/Statistics.vue +++ b/src/views/Merchant/Statistics.vue @@ -75,19 +75,19 @@
- {{ active == 0 ? '今日' : '本月' }}营业额(元) + {{ active == 0 ? '当日' : '当月' }}营业额(元) {{ chartData.yingyee?.toFixed(2) }}
- {{ active == 0 ? '今日' : '本月' }}抵扣积分 + {{ active == 0 ? '当日' : '当月' }}应收(元) {{ chartData.yingshou?.toFixed(2) }}
- {{ active == 0 ? '今日' : '本月' }}订单数 + {{ active == 0 ? '当日' : '当月' }}订单数 {{ chartData.dingdanshu }}
- {{ active == 0 ? '今日' : '本月' }}收入(元) + {{ active == 0 ? '当日' : '当月' }}惠利金额(元) {{ chartData.youhui?.toFixed(2) }}
@@ -173,10 +173,24 @@ export default { this.chart = window.echarts.init(this.$refs.chartRef); } const dates = this.chartData.orderchart.map(item => item.adddate?.slice(5)); - const values = this.chartData.orderchart.map(item => item.ordermoney); + const values = this.chartData.orderchart.map(item => Number(item.ordermoney || 0)); + this.chart.clear(); + this.chart.off('click'); + this.chart.getZr().off('click'); this.chart.setOption({ tooltip: { - trigger: 'axis' + show: true, + // tooltip 触发方式:item 表示点击/滑过数据点显示 + trigger: 'item', + triggerOn: 'mousemove|click', + renderMode: 'html', + confine: true, + // tooltip 弹框样式在这里配置,不写则使用 ECharts 默认样式 + // extraCssText: 'z-index:999999;box-shadow:0 2px 8px rgba(0,0,0,.15);', + // tooltip 展示内容在这里配置 + formatter(params) { + return `${params.name}
${params.seriesName}:${Number(params.value || 0).toFixed(2)}`; + } }, grid: { left: '12%', @@ -200,9 +214,12 @@ export default { } }, series: [{ + name: '营业额', data: values, type: 'line', smooth: true, + showSymbol: true, + symbolSize: 6, lineStyle: { color: '#e4bb4e', width: 2 }, itemStyle: { color: '#e4bb4e' }, areaStyle: { @@ -213,6 +230,19 @@ export default { } }] }); + // 扩大点击范围:点击图表区域时,自动显示离点击位置最近的数据点 tooltip + this.chart.getZr().on('click', event => { + if (!values.length) return; + const pointInPixel = [event.offsetX, event.offsetY]; + if (!this.chart.containPixel('grid', pointInPixel)) return; + const pointInGrid = this.chart.convertFromPixel({ seriesIndex: 0 }, pointInPixel); + const dataIndex = Math.max(0, Math.min(values.length - 1, Math.round(pointInGrid[0]))); + this.chart.dispatchAction({ + type: 'showTip', + seriesIndex: 0, + dataIndex + }); + }); }, getCount() { this.$get('/v1/client/DShopsClient/statistics').then(res => { diff --git a/src/views/Trade/Pay.vue b/src/views/Trade/Pay.vue index 21e0713..ed8ddaf 100644 --- a/src/views/Trade/Pay.vue +++ b/src/views/Trade/Pay.vue @@ -38,7 +38,8 @@ - + @@ -232,6 +233,17 @@ export default { } .pay { + &::before { + content: ''; + width: 100vw; + height: 100vh; + position: fixed; + left: 0; + top: 0; + background: #fff; + z-index: -1; + } + text-align: center; padding-top: 16.667vw; diff --git a/src/views/Trade/TradeDetail.vue b/src/views/Trade/TradeDetail.vue index b6fa79d..5e88042 100644 --- a/src/views/Trade/TradeDetail.vue +++ b/src/views/Trade/TradeDetail.vue @@ -7,7 +7,7 @@ {{ data.statename }} -
+
@@ -22,7 +22,15 @@
-
+
+ +
+
+ {{ data.shopname }} +
+
+
+
@@ -146,6 +154,10 @@ 发货时间 {{ $formatGMT(data.exporttime, 'yyyy-MM-dd HH:mm:ss') }}

+

+ 完成时间 + {{ $formatGMT(data.paytime, 'yyyy-MM-dd HH:mm:ss') }} +

完成时间 {{ $formatGMT(data.receipttime, 'yyyy-MM-dd HH:mm:ss') }} diff --git a/src/views/Trade/Tradelist.vue b/src/views/Trade/Tradelist.vue index 317b23a..d9f6f85 100644 --- a/src/views/Trade/Tradelist.vue +++ b/src/views/Trade/Tradelist.vue @@ -37,8 +37,9 @@ {{ item.proskuname }}

x{{ item.buynums }}

-
- ¥{{ item.proskusaleprice?.toFixed(2) }} +
+ ¥{{ item.proskusaleprice?.toFixed(2) + }} {{ item.proskusaleprice?.toFixed(2) }}积分

惠利{{ item.discountratio }}%

@@ -54,9 +55,10 @@ {{ item.statename }}
- - - + + + diff --git a/src/views/User/Checkout/Checkout.vue b/src/views/User/Checkout/Checkout.vue index 88ba8fe..1371fb0 100644 --- a/src/views/User/Checkout/Checkout.vue +++ b/src/views/User/Checkout/Checkout.vue @@ -88,7 +88,8 @@ export default { "shopuserid": '' }, jifenDeduction: 0, - quanDeduction: 0 + quanDeduction: 0, + lastChecked: '' } }, computed: { @@ -119,16 +120,21 @@ export default { }) }, 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'; + const target = type === 'jifen' ? '1' : '2'; + if (type === 'jifen' && (!this.data.user.xiaofeijifen || this.data.user.xiaofeijifen <= 0)) return; + if (type === 'quan' && (!this.data.user.xiaofeiquan || this.data.user.xiaofeiquan <= 0)) return; + + if (this.lastChecked === target) { + // 再次点击同一项:取消选中 + this.checked = ''; this.req.isjifen = false; + this.req.ishuiyuanka = false; + this.lastChecked = ''; + } else { + this.checked = target; + this.req.isjifen = target === '1'; + this.req.ishuiyuanka = target === '2'; + this.lastChecked = target; } }, calcDeduction() { diff --git a/src/views/User/My.vue b/src/views/User/My.vue index 69da65f..25a1a8b 100644 --- a/src/views/User/My.vue +++ b/src/views/User/My.vue @@ -222,14 +222,11 @@ export default { components: { ManagerPopup }, computed: { }, - beforeRouteEnter(to, from, next) { - if (!localStorage.getItem('member_token')) { - next({ name: 'Login', query: { redirect: to.fullPath } }); - } else { - next(); - } - }, mounted() { + if (!localStorage.getItem('member_token')) { + this.$router.replace({ name: 'Login', query: { redirect: this.$route.fullPath } }); + return; + } this.init(); this.$get('/v1/client/HWxinfoClient/qrcode').then(res => { this.FollowQRCode = res.data; diff --git a/src/views/User/ShopInfo.vue b/src/views/User/ShopInfo.vue index fe7b441..f6f7348 100644 --- a/src/views/User/ShopInfo.vue +++ b/src/views/User/ShopInfo.vue @@ -150,9 +150,9 @@ export default { }, }); }); + } else { + window.location.href = `https://uri.amap.com/navigation?to=${lon},${lat},${encodeURIComponent(fullAddress)}&mode=car&src=shop`; } - } else { - window.open(`https://uri.amap.com/navigation?to=${lon},${lat},${fullAddress}&mode=car&src=shop`, '_blank'); } }, } diff --git a/src/views/User/Wallet/Balance.vue b/src/views/User/Wallet/Balance.vue index 77b9ca6..0c3f3f2 100644 --- a/src/views/User/Wallet/Balance.vue +++ b/src/views/User/Wallet/Balance.vue @@ -180,7 +180,7 @@ {{ data.total.totalsum?.toFixed(2) }} -
@@ -317,6 +317,7 @@ export default { loading: false, showTerm: false, list: [], + isVIP: '' } }, methods: { @@ -326,11 +327,23 @@ export default { }).catch(err => { this.$showFailToast(err.message || '加载失败'); }) - - this.$get('/v1/client/DUsermoneysClient/select').then(res => { - // console.log(res.data); + this.$get('/v1/client/DUsersClient').then(data => { + this.isVIP = data.data.userlevelname; }) + // this.$get('/v1/client/DUsermoneysClient/select').then(res => { + // // console.log(res.data); + // }) + }, + toCashout() { + if (this.isVIP === '会员') { + this.$showFailToast('您还未购买礼包产品成为VIP,暂不能提现!') + setTimeout(() => { + // this.$navigate('/My') + }, 1500); + return + } + this.$navigate('Cashout'); }, onconfirm(value) { this.date = this.currentDate;