import { request } from '../../utils/request' import { QuotaItem, UserInfo, GoodsItem, QuotaFlowItem, PagedResult, VirtualPayResult, OrderItem } from '../../types/models' import { QUOTA_NAMES, GoodsType, GOODS_TYPE_NAMES } from '../../utils/config' import { fenToYuan, formatDate, formatDateTime } from '../../utils/format' import { normalizePaged } from '../../utils/paged' const GOODS_ICONS: Record = { [GoodsType.MEMBER]: 'icon-crown', [GoodsType.UPLOAD]: 'icon-camera', [GoodsType.AI]: 'icon-sparkles', [GoodsType.CLOSET_SHARE]: 'icon-hanger', [GoodsType.OUTFIT_SHARE]: 'icon-shirt', } const GOODS_FILTERS = [ { name: '全部', value: 0 }, { name: '会员包', value: 1 }, { name: '上传包', value: 2 }, { name: 'AI 推荐包', value: 3 }, { name: '衣柜分享包', value: 4 }, { name: '搭配分享包', value: 5 }, ] type PayPlatform = 'android' | 'ios' | 'harmony' | 'windows' // 归一化平台:后端仅接受 android/ios/harmony/windows,devtools/mac 等调试环境默认按 android 处理 function normalizePlatform(): PayPlatform { let p = 'android' try { p = wx.getSystemInfoSync().platform } catch (e) { // 获取失败时回退 android,避免请求体缺失导致参数校验失败 } if (p === 'ios' || p === 'harmony' || p === 'windows') return p return 'android' } Page({ data: { view: 'mall' as 'mall' | 'flow', userInfo: {} as UserInfo, isVip: false, vipExpireText: '', quotas: [] as QuotaItem[], goodsFilters: GOODS_FILTERS, activeFilter: 0, goods: [] as GoodsItem[], flowList: [] as QuotaFlowItem[], goodsIcons: GOODS_ICONS, orderVisible: false, orderGoods: null as GoodsItem | null, submitting: false, // 下单防连点:连点「立即支付」会重复创建虚拟支付订单 loading: true, }, onLoad(query: Record) { if (query.tab === 'flow') this.setData({ view: 'flow' }) }, onShow() { this.fetchUser() this.fetchQuota() this.fetchGoods() if (this.data.view === 'flow') this.fetchFlow() }, fetchUser() { request({ url: '/user/info', showError: false }) .then((user) => { const isVip = user.vip_type !== 1 this.setData({ userInfo: user, isVip, vipExpireText: isVip && user.vip_expire ? `有效期至 ${formatDate(user.vip_expire)}` : '', }) }) .catch(() => {}) }, fetchQuota() { request<{ quotas: QuotaItem[] }>({ url: '/quota/info', showError: false, silentAuth: true }) .then((res) => { const quotas = res.quotas.map((q) => ({ ...q, _name: QUOTA_NAMES[q.quota_type] || '额度', _percent: q.total > 0 ? Math.round((q.used / q.total) * 100) : 0, })) this.setData({ quotas }) }) .catch(() => {}) }, fetchGoods() { const params: Record = { page: 1, page_size: 50 } const filter = this.data.activeFilter if (filter > 0) params.goods_type = filter request>({ url: '/goods', data: params, auth: false }) .then((res) => { const goods = normalizePaged(res).list.map((g) => ({ ...g, _icon: GOODS_ICONS[g.goods_type] || 'icon-crown', _typeName: GOODS_TYPE_NAMES[g.goods_type] || '', _priceText: fenToYuan(g.discount_price), _originText: fenToYuan(g.price), })) this.setData({ goods, loading: false }) }) .catch(() => this.setData({ loading: false })) }, fetchFlow() { request>({ url: '/quota/flow', data: { page: 1, page_size: 50 } }) .then((res) => { const list = normalizePaged(res).list.map((it) => ({ ...it, _time: formatDateTime(it.created_at), _sign: it.change_num > 0 ? `+${it.change_num}` : `${it.change_num}`, _name: QUOTA_NAMES[it.quota_type] || '额度', })) this.setData({ flowList: list }) }) .catch(() => {}) }, onGoodsFilter(e: WechatMiniprogram.TouchEvent) { const index = Number(e.currentTarget.dataset.index) if (index === this.data.activeFilter) return this.setData({ activeFilter: index, loading: true }) this.fetchGoods() }, onTapMember() { this.setData({ activeFilter: 1, loading: true }) this.fetchGoods() }, onSwitchMall() { this.setData({ view: 'mall' }) }, onSwitchFlow() { this.setData({ view: 'flow' }) this.fetchFlow() }, onBuy(e: WechatMiniprogram.TouchEvent) { const goods = e.currentTarget.dataset.goods as GoodsItem this.setData({ orderGoods: goods, orderVisible: true }) }, onCloseOrder() { this.setData({ orderVisible: false }) }, noop() {}, confirmPay() { if (this.data.submitting) return if (!this.data.orderGoods) return this.setData({ submitting: true }) request({ url: '/order/virtual', method: 'POST', data: { goods_code: this.data.orderGoods.goods_code, platform: normalizePlatform(), }, }) .then((res) => { this.setData({ orderVisible: false, submitting: false }) this.doPayment(res) }) .catch(() => this.setData({ submitting: false })) }, doPayment(res: VirtualPayResult) { wx.requestVirtualPayment({ signData: res.sign_data, paySig: res.pay_sig, signature: res.signature, mode: 'short_series_goods', success: () => { // 拉起成功仅代表进入支付页,最终到账以后端发货推送为准,轮询订单确认 this.pollOrder(res.order_no) }, fail: () => {}, }) }, pollOrder(orderNo: string) { this.clearPayTimer() let count = 0 ;(this as any).payTimer = setInterval(() => { count++ request>({ url: '/order', data: { page: 1, page_size: 50 }, showError: false }) .then((res) => { const order = normalizePaged(res).list.find((it) => it.order_no === orderNo) if (order && order.order_status === 2) { this.clearPayTimer() wx.showToast({ title: '支付成功', icon: 'success' }) this.fetchQuota() this.fetchUser() } else if (order && (order.order_status === 3 || order.order_status === 4 || order.order_status === 5)) { this.clearPayTimer() const msg = order.order_status === 5 ? '订单已退款' : '订单未支付成功' wx.showToast({ title: msg, icon: 'none' }) } else if (count >= 10) { this.finishPayTimeout() } }) .catch(() => { if (count >= 10) this.finishPayTimeout() }) }, 1500) }, clearPayTimer() { const timer = (this as any).payTimer if (timer) { clearInterval(timer) ;(this as any).payTimer = null } }, finishPayTimeout() { this.clearPayTimer() wx.showToast({ title: '到账可能有延迟,请稍后查看', icon: 'none' }) this.fetchQuota() this.fetchUser() }, onUnload() { // 离开商城后停止支付轮询,避免对已卸载页面 setData this.clearPayTimer() }, })