diff --git a/miniprogram/app.json b/miniprogram/app.json index 15f078c..602dbd5 100755 --- a/miniprogram/app.json +++ b/miniprogram/app.json @@ -10,7 +10,8 @@ "pages/terms/terms", "pages/privacy/privacy", "pages/analyze/analyze", - "pages/coupon/coupon" + "pages/coupon/coupon", + "pages/order/order" ], "window": { "navigationBarTextStyle": "black", @@ -25,4 +26,4 @@ "useExtendedLib" : { "weui": true } -} \ No newline at end of file +} diff --git a/miniprogram/app.ts b/miniprogram/app.ts index 20b0c26..3d08779 100755 --- a/miniprogram/app.ts +++ b/miniprogram/app.ts @@ -78,7 +78,7 @@ App({ this.globalData.token = authInfo.token this.globalData.userInfo = authInfo.userInfo // 初始化词典等级 - this.globalData.dictLevel = authInfo.dictLevel || 'PRIMARY' + this.globalData.dictLevel = authInfo.dictLevel || 'LEVEL1' console.log('登录状态有效,自动登录') } else { console.log('Token 已过期,清理本地数据') @@ -101,7 +101,7 @@ App({ this.globalData.token = loginData.access_token this.globalData.userInfo = loginData.userInfo // 更新词典等级 - this.globalData.dictLevel = loginData.dict_level || 'PRIMARY' + this.globalData.dictLevel = loginData.dict_level || 'LEVEL1' // 存储到本地 wx.setStorageSync('token', loginData.access_token) diff --git a/miniprogram/pages/coupon/coupon.wxml b/miniprogram/pages/coupon/coupon.wxml index 86aa0e4..abda8d7 100644 --- a/miniprogram/pages/coupon/coupon.wxml +++ b/miniprogram/pages/coupon/coupon.wxml @@ -1,6 +1,6 @@ - + {{item.title}} {{item.points}} diff --git a/miniprogram/pages/coupon/coupon.wxss b/miniprogram/pages/coupon/coupon.wxss index cb6daff..78192a7 100644 --- a/miniprogram/pages/coupon/coupon.wxss +++ b/miniprogram/pages/coupon/coupon.wxss @@ -11,7 +11,7 @@ } .coupon_box{ -background: linear-gradient(to right, #FF4B2B, #FF416C); +background: linear-gradient(to right, #3e9eff, #5cbffc); width: 40%; border-radius: 12rpx; text-align: center; @@ -21,6 +21,10 @@ background: linear-gradient(to right, #FF4B2B, #FF416C); margin: 5% 5% 0 5%; } +.coupon_box.one_time{ + background: linear-gradient(to right, #FF4B2B, #FF416C); +} + .coupon_box::before{ content: ''; position: absolute; diff --git a/miniprogram/pages/order/order.json b/miniprogram/pages/order/order.json new file mode 100644 index 0000000..4c57ff5 --- /dev/null +++ b/miniprogram/pages/order/order.json @@ -0,0 +1,16 @@ +{ + "usingComponents": { + "t-cell": "tdesign-miniprogram/cell/cell", + "t-dialog": "tdesign-miniprogram/dialog/dialog", + "t-checkbox": "tdesign-miniprogram/checkbox/checkbox", + "t-checkbox-group": "tdesign-miniprogram/checkbox-group/checkbox-group", + "t-skeleton": "tdesign-miniprogram/skeleton/skeleton" + }, + "navigationBarTitleText": "订单", + "navigationBarTextStyle": "black", + "navigationBarBackgroundColor": "#ffffff", + "backgroundColor": "#ffffff", + "backgroundTextStyle": "light", + "enablePullDownRefresh": true, + "onReachBottomDistance": 50 +} diff --git a/miniprogram/pages/order/order.ts b/miniprogram/pages/order/order.ts new file mode 100644 index 0000000..e9407f1 --- /dev/null +++ b/miniprogram/pages/order/order.ts @@ -0,0 +1,148 @@ +import apiManager from '../../utils/api' + +Page({ + data: { + orders: [] as Array, + page: 1, + size: 20, + total: 0, + hasMore: true, + isLoading: false, + refundDialogVisible: false, + refundOrderId: '', + refundAmountCents: 0, + selectedReasons: [] as Array, + reasonText: '', + reasonMax: 200 + }, + + onLoad() { + this.loadOrders(1) + }, + + onPullDownRefresh() { + this.setData({ page: 1 }) + this.loadOrders(1).finally(() => wx.stopPullDownRefresh()) + }, + + onReachBottom() { + if (this.data.hasMore && !this.data.isLoading) { + const next = this.data.page + 1 + this.loadOrders(next) + } + }, + + async loadOrders(page: number) { + this.setData({ isLoading: page === 1 }) + try { + const res = await apiManager.listOrders(page, this.data.size) + const items = (res.items || []).map((it: any) => ({ + ...it, + displayTime: this.formatTime(it.created_time), + displayAmount: this.formatAmount(it.amount_cents), + })) + console.log(items) + if (page === 1) { + this.setData({ orders: items, page, total: res.total || items.length, hasMore: page * this.data.size < (res.total || items.length), isLoading: false }) + } else { + const merged = [...this.data.orders, ...items] + this.setData({ orders: merged, page, total: res.total || merged.length, hasMore: page * this.data.size < (res.total || merged.length), isLoading: false }) + } + } catch (e) { + this.setData({ isLoading: false }) + wx.showToast({ title: '加载失败', icon: 'none' }) + } + }, + + formatTime(t: string) { + try { + const d = new Date(t) + const y = d.getFullYear() + const m = `${d.getMonth() + 1}`.padStart(2, '0') + const dd = `${d.getDate()}`.padStart(2, '0') + const hh = `${d.getHours()}`.padStart(2, '0') + const mm = `${d.getMinutes()}`.padStart(2, '0') + return `${y}-${m}-${dd} ${hh}:${mm}` + } catch (_) { + return t + } + }, + + formatAmount(cents: number) { + if (!cents && cents !== 0) return '' + return (cents / 100).toFixed(2) + }, + + async onRefundTap(e: any) { + const id = e.currentTarget.dataset.id + try { + const detail = await apiManager.getOrderDetail(id) + const updated = this.data.orders.map((it: any) => it.id === id ? { + ...it, + refund_status: detail.refund_status || it.refund_status, + amount_cents: detail.amount_cents ?? it.amount_cents, + displayAmount: this.formatAmount(detail.amount_cents ?? it.amount_cents), + refundable_amount_cents: detail.refundable_amount_cents ?? it.refundable_amount_cents, + displayRefundable: this.formatAmount(detail.refundable_amount_cents ?? 0), + can_refund: detail.can_refund ?? it.can_refund + } : it) + this.setData({ orders: updated }) + if (detail.refund_status) { + wx.showToast({ title: `退款状态:${detail.refund_status}`, icon: 'none' }) + return + } + if (detail.can_refund) { + this.setData({ refundOrderId: id, refundAmountCents: Number(detail.refundable_amount_cents || 0), selectedReasons: [], reasonText: '', refundDialogVisible: true }) + } else { + wx.showToast({ title: '当前订单不可退款', icon: 'none' }) + } + } catch (_) { + wx.showToast({ title: '获取订单详情失败', icon: 'none' }) + } + }, + + onReasonGroupChange(e: any) { + this.setData({ selectedReasons: e.detail.value || [] }) + }, + + onReasonTextInput(e: any) { + const v = e.detail.value || '' + const max = this.data.reasonMax + this.setData({ reasonText: v.length > max ? v.slice(0, max) : v }) + }, + + closeRefundDialog() { + this.setData({ refundDialogVisible: false }) + }, + + async confirmRefundDialog() { + const sel = this.data.selectedReasons + const txt = (this.data.reasonText || '').trim() + if ((!sel || sel.length === 0) && !txt) { + wx.showToast({ title: '请至少选择或填写一项理由', icon: 'none' }) + return + } + const reason = [...sel, txt].filter(Boolean).join('|') + try { + const r = await apiManager.refund(this.data.refundOrderId, reason, this.data.refundAmountCents) + wx.showToast({ title: '已提交退款申请', icon: 'none' }) + this.setData({ refundDialogVisible: false }) + const outRefundNo = r && (r.out_refund_no || (r.data && r.data.out_refund_no)) + if (outRefundNo) { + setTimeout(async () => { + try { + const s = await apiManager.getRefundStatus(outRefundNo) + const status = s && (s.status || '') + const updated = this.data.orders.map((it: any) => it.id === this.data.refundOrderId ? { ...it, refund_status: status, can_refund: false } : it) + this.setData({ orders: updated }) + wx.showToast({ title: status ? `退款状态:${status}` : '查询成功', icon: 'none' }) + } catch (_) { + wx.showToast({ title: '查询失败', icon: 'none' }) + } + }, 1000) + } + } catch (e) { + wx.showToast({ title: '提交失败', icon: 'none' }) + } + } +}) diff --git a/miniprogram/pages/order/order.wxml b/miniprogram/pages/order/order.wxml new file mode 100644 index 0000000..35908fa --- /dev/null +++ b/miniprogram/pages/order/order.wxml @@ -0,0 +1,39 @@ + + + + + 没有记录 + + + + + ¥{{item.displayAmount}} · 积分 {{item.points}} + · 可退 ¥{{item.displayRefundable}} + + + 退款 + {{item.refund_status}} + + + + + + + + + 误操作下单 + 功能未达到预期 + 重复支付或订单异常 + + + 至少选择一项或填写说明 + + + diff --git a/miniprogram/pages/order/order.wxss b/miniprogram/pages/order/order.wxss new file mode 100644 index 0000000..b2f653b --- /dev/null +++ b/miniprogram/pages/order/order.wxss @@ -0,0 +1,10 @@ +.order-container { padding: 24rpx; } +.skeleton-wrap { padding: 24rpx; } +.empty-wrap { text-align: center; color: #999; padding: 80rpx 0; } +.order-list { display: flex; flex-direction: column; gap: 8rpx; } +.order-desc { color: #666; font-size: 26rpx; } +.refund-btn { color: #0052d9; font-size: 26rpx; padding: 8rpx 16rpx; border: 1rpx solid #0052d9; border-radius: 8rpx; } +.refund-status { color: #999; font-size: 26rpx; padding: 8rpx 0; } +.refund-content { padding: 12rpx 0; } +.refund-textarea { width: 100%; min-height: 160rpx; border: 1rpx solid #ddd; border-radius: 8rpx; padding: 12rpx; box-sizing: border-box; margin-top: 12rpx; } +.reason-tip { color: #999; font-size: 24rpx; margin-top: 8rpx; } diff --git a/miniprogram/pages/profile/profile.ts b/miniprogram/pages/profile/profile.ts index ef5e8ea..f27a60b 100755 --- a/miniprogram/pages/profile/profile.ts +++ b/miniprogram/pages/profile/profile.ts @@ -56,7 +56,7 @@ Page({ }, // 词典等级配置 - dictLevel: 'PRIMARY', + dictLevel: 'LEVEL1', dictLevelOptions: apiManager.getDictLevelOptions(), // 应用信息 @@ -213,7 +213,7 @@ Page({ const userData = { isLoggedIn: app.globalData.isLoggedIn || false, userInfo: app.globalData.userInfo || null, - dictLevel: app.globalData.dictLevel || wx.getStorageSync('dictLevel') || 'PRIMARY' + dictLevel: app.globalData.dictLevel || wx.getStorageSync('dictLevel') || 'LEVEL1' } console.log('更新用户信息:', userData) diff --git a/miniprogram/pages/profile/profile.wxml b/miniprogram/pages/profile/profile.wxml index c6355e9..2932c77 100755 --- a/miniprogram/pages/profile/profile.wxml +++ b/miniprogram/pages/profile/profile.wxml @@ -74,12 +74,17 @@ + + + + + - + diff --git a/miniprogram/pages/upload/upload.ts b/miniprogram/pages/upload/upload.ts index 243547f..cb86727 100755 --- a/miniprogram/pages/upload/upload.ts +++ b/miniprogram/pages/upload/upload.ts @@ -827,10 +827,27 @@ Page({ console.error('跳转结果页面失败:', error) this.setData({ isProcessing: false }) this.resetPageState() - wx.showToast({ - title: '上传失败', - icon: 'none' - }) + const msg = (error as any)?.message || '' + if (typeof msg === 'string' && msg.indexOf('积分') !== -1) { + wx.showModal({ + title: '积分不足', + content: '您的积分不足,是否前往购买?', + confirmText: '购买', + cancelText: '取消', + success: (res) => { + if (res.confirm) { + wx.navigateTo({ url: '/pages/coupon/coupon' }) + } else { + // wx.showToast({ title: '已取消', icon: 'none' }) + } + } + }) + } else { + wx.showToast({ + title: msg || '上传失败', + icon: 'none' + }) + } } }, diff --git a/miniprogram/utils/api.ts b/miniprogram/utils/api.ts index 9e70bfd..2828619 100755 --- a/miniprogram/utils/api.ts +++ b/miniprogram/utils/api.ts @@ -412,8 +412,25 @@ class ApiManager { success: true }); - // 直接返回响应数据,由调用方处理业务逻辑 - resolve(res.data); + if (res.statusCode === 200) { + // 直接返回响应数据,由调用方处理业务逻辑 + resolve(res.data); + } else if (res.statusCode === 403) { + const response = res.data as IApiResponse + const errorMsg = response.msg || '请求失败' + console.error('403 错误:', errorMsg, response) + reject(new Error(errorMsg)) + } else if (res.statusCode === 404 ) { + const response = res.data as IApiResponse + const errorMsg = response.msg || '请求失败' + console.error('404 错误:', errorMsg, response) + reject(new Error(errorMsg)) + } else { + const response = res.data as IApiResponse + const errorMsg = response.msg || '请求失败' + console.error('微信云托管请求错误:', errorMsg, response) + reject(new Error(errorMsg)) + } }, fail: (error: any) => { if (showLoading) { @@ -1785,7 +1802,7 @@ class ApiManager { try { console.log('开始获取图片文本和评分信息', { imageId }); const app = getApp() - const dictLevel = app.globalData.dictLevel || wx.getStorageSync('dictLevel') || 'PRIMARY' + const dictLevel = app.globalData.dictLevel || wx.getStorageSync('dictLevel') || 'LEVEL1' const response = await this.request<{ image_file_id: string, assessments: Array<{ @@ -1925,6 +1942,28 @@ class ApiManager { return response.data } + async listOrders(page: number = 1, size: number = 20): Promise<{ items: Array<{ id: string; created_time: string; amount_cents: number; points?: number | null; product_id?: string | null; refund_status?: string | null }>; total: number }>{ + const response = await this.request<{ items: Array<{ id: string; created_time: string; amount_cents: number; points?: number | null; product_id?: string | null; refund_status?: string | null }>; total: number }>(`/api/v1/wxpay/order/list`, 'POST', { page, size }) + return response.data + } + + async getOrderDetail(out_trade_no: string): Promise<{ out_trade_no: string; transaction_id?: string | null; trade_state: string; amount_cents: number; description: string; can_refund?: boolean | null; refund_status?: string | null; refundable_amount_cents?: number | null; refundable_amount_points?: number | null }>{ + const response = await this.request<{ out_trade_no: string; transaction_id?: string | null; trade_state: string; amount_cents: number; description: string; can_refund?: boolean | null; refund_status?: string | null; refundable_amount_cents?: number | null; refundable_amount_points?: number | null }>(`/api/v1/wxpay/order/${out_trade_no}/details`, 'GET') + return response.data + } + + async refund(out_trade_no: string, reason: string, amount_cents?: number): Promise { + const payload: any = { out_trade_no, reason } + if (typeof amount_cents === 'number') payload.amount_cents = amount_cents + const response = await this.request(`/api/v1/wxpay/refund`, 'POST', payload) + return response.data + } + + async getRefundStatus(out_refund_no: string): Promise<{ out_refund_no: string; status: string }>{ + const response = await this.request<{ out_refund_no: string; status: string }>(`/api/v1/wxpay/refund/${out_refund_no}`, 'GET') + return response.data + } + } // 导出单例