import { request } from '../../utils/request' import { PagedResult, OrderItem } from '../../types/models' import { fenToYuan, formatDateTime, formatCountdown } from '../../utils/format' import { normalizePaged, mergePaged, hasMorePaged } from '../../utils/paged' import { navScroll } from '../../utils/nav' import { logger } from '../../utils/logger' // 日志模块名 const LOG = 'order' const STATUS_FILTERS = [ { name: '全部', value: 0 }, { name: '待支付', value: 1 }, { name: '已支付', value: 2 }, { name: '申请退款中', value: 6 }, { name: '已取消', value: 3 }, { name: '已过期', value: 4 }, { name: '已退款', value: 5 }, ] const STATUS_TEXT: Record = { 1: '待支付', 2: '已支付', 3: '已取消', 4: '已过期', 5: '已退款', 6: '申请退款中' } // 零彩色状态语义:待支付/申请退款中 = 实心黑(进行中);已支付/已退款 = 描边;已取消/已过期 = 降透明 const STATUS_CLS: Record = { 1: 'pill--ink', 2: 'pill--line', 3: 'pill--dim', 4: 'pill--dim', 5: 'pill--line pill--dim', 6: 'pill--ink', } // 退款结果轮询间隔:仅在列表存在「申请退款中」订单时才发请求 const REFUND_POLL_MS = 10 * 1000 // 设备是否 iOS:iOS 虚拟支付订单退款需走 App Store(后端返回 71004),前端不展示退款入口 const IS_IOS = (() => { try { return wx.getSystemInfoSync().platform === 'ios' } catch (e) { return false } })() Page({ data: { filters: STATUS_FILTERS, activeFilter: 0, list: [] as OrderItem[], loading: true, loadingMore: false, page: 1, pageSize: 20, hasMore: true, // 滚动收缩:由 utils/nav.ts 的共享 handler 驱动 navScrolled: false, // 取消订单确认 cancelVisible: false, cancelOrderNo: '', cancelButtons: [ { text: '再想想', value: '' }, { text: '确认取消', value: 'ok', type: 'main' }, ], // 退款申请(抽屉 + 原因输入) refundVisible: false, refundOrderNo: '', refundReason: '', refundSubmitting: false, }, // scroll-view 滚动 → 导航收缩为玻璃小标题 onNavScroll: navScroll, // 抽屉内容点击不穿透到遮罩 noop() {}, onShow() { this.refresh() this.startCountdown() this.startRefundWatch() }, onHide() { this.stopCountdown() this.stopRefundWatch() }, onUnload() { this.stopCountdown() this.stopRefundWatch() }, refresh(): Promise { this.setData({ page: 1, hasMore: true, loading: this.data.list.length === 0 }) return this.fetchList(1) }, onReachBottom() { if (!this.data.hasMore || this.data.loading || this.data.loadingMore) return this.fetchList(this.data.page + 1) }, // 待支付倒计时需要每秒走动,仅在有倒计时项时才 setData startCountdown() { this.stopCountdown() ;(this as any).tickTimer = setInterval(() => this.tickCountdown(), 1000) }, stopCountdown() { const timer = (this as any).tickTimer if (timer) { clearInterval(timer) ;(this as any).tickTimer = null } }, tickCountdown() { const list = this.data.list if (!list.length) return const now = Math.floor(Date.now() / 1000) let changed = false const next = list.map((it: any) => { if (it.order_status !== 1 || !it.expire_time) return it const left = it.expire_time - now const text = left > 0 ? formatCountdown(left) : '' if (text === it._countdown) return it changed = true return { ...it, _countdown: text } }) if (changed) this.setData({ list: next }) }, // ===== 退款结果跟踪:申请退款中的订单只等待结果(不可再次发起退款),定期核对是否已出结果 ===== startRefundWatch() { this.stopRefundWatch() ;(this as any).refundTimer = setInterval(() => this.pollRefunding(), REFUND_POLL_MS) }, stopRefundWatch() { const timer = (this as any).refundTimer if (timer) { clearInterval(timer) ;(this as any).refundTimer = null } }, // 仅当列表存在「申请退款中」订单时才发请求:命中说明仍未出结果,缺失即视为状态已流转 pollRefunding() { const tracked = this.data.list.filter((it) => it.order_status === 6).map((it) => it.order_no) if (!tracked.length) return request>({ url: '/order', data: { status: 6, page: 1, page_size: 50 }, showError: false, }) .then((res) => { const still = new Set(normalizePaged(res).list.map((it) => it.order_no)) const changed = tracked.filter((no) => !still.has(no)) if (changed.length) this.settleRefundResult(changed) }) .catch((e: any) => logger.warn(LOG, '退款结果轮询失败(静默降级), code=', e && e.code)) }, // 核对流转后的最新状态并刷新列表:5=退款成功(终态)、2=退款未通过可重新申请;仍为 6 则继续等待 settleRefundResult(orderNos: string[]) { request>({ url: '/order', data: { page: 1, page_size: 50 }, showError: false }) .then((res) => { const list = normalizePaged(res).list orderNos.forEach((no) => { const latest = list.find((it) => it.order_no === no) if (!latest) { logger.warn(LOG, '退款结果核对:未定位到订单最新状态,仅刷新列表, orderNo=', no) return } if (latest.order_status === 6) return if (latest.order_status === 5) { logger.info(LOG, '退款成功, orderNo=', no) wx.showToast({ title: '退款成功', icon: 'success' }) } else if (latest.order_status === 2) { logger.info(LOG, '退款未通过,可重新申请, orderNo=', no) wx.showToast({ title: '退款未通过,可重新申请', icon: 'none' }) } else { logger.info(LOG, '退款状态已更新, orderNo=', no, 'status=', latest.order_status) wx.showToast({ title: '退款状态已更新', icon: 'none' }) } }) this.refresh() }) .catch((e: any) => logger.warn(LOG, '退款结果核对失败(静默降级), code=', e && e.code)) }, // 返回列表请求的 Promise:退款结果核对后需要等列表刷新完成 fetchList(page: number): Promise { const params: Record = { page, page_size: this.data.pageSize } const filter = this.data.activeFilter if (filter > 0) params.status = filter // 请求序号:切换筛选时旧响应可能后到,序号不匹配直接丢弃 const seq = ((this as any).reqSeq || 0) + 1 ;(this as any).reqSeq = seq if (page > 1) this.setData({ loadingMore: true }) return request>({ url: '/order', data: params }) .then((res) => { if ((this as any).reqSeq !== seq) return const { list: raw, total } = normalizePaged(res) logger.debug(LOG, '订单加载成功, 筛选=', filter, 'page=', page, '条数=', raw && raw.length) const now = Math.floor(Date.now() / 1000) const list = raw.map((it) => ({ ...it, _statusText: STATUS_TEXT[it.order_status] || '', _statusCls: STATUS_CLS[it.order_status] || 'pill--line', _amount: fenToYuan(it.pay_amount), _time: formatDateTime(it.created_at), _orderTail: `…${it.order_no.slice(-4)}`, _countdown: it.order_status === 1 && it.expire_time > now ? formatCountdown(it.expire_time - now) : '', _benefit: it.member_days > 0 ? `${it.member_days} 天会员` : '', _canCancel: it.order_status === 1 && it.pay_channel !== 2, // 退款入口:仅「已支付」可申请;iOS 虚拟支付订单需走 App Store(后端 71004),不展示入口 _canRefund: it.order_status === 2 && !(it.pay_channel === 2 && IS_IOS), _payChannelText: it.pay_channel === 2 ? '虚拟支付' : '', })) const merged = mergePaged(this.data.list, list, page === 1) this.setData({ list: merged, hasMore: hasMorePaged(merged, total, this.data.pageSize), loading: false, loadingMore: false, page, }) }) .catch((e: any) => { if ((this as any).reqSeq !== seq) return logger.warn(LOG, '订单加载失败, page=', page, 'code=', e && e.code) this.setData({ loading: false, loadingMore: false }) }) }, onFilterTap(e: WechatMiniprogram.TouchEvent) { const index = Number(e.currentTarget.dataset.index) if (index === this.data.activeFilter) return this.setData({ activeFilter: index, list: [], loading: true }) this.refresh() }, onCancel(e: WechatMiniprogram.TouchEvent) { const orderNo = e.currentTarget.dataset.no as string this.setData({ cancelVisible: true, cancelOrderNo: orderNo }) }, onCancelCancel() { this.setData({ cancelVisible: false }) }, onCancelConfirm(ev: WechatMiniprogram.CustomEvent) { const orderNo = this.data.cancelOrderNo this.setData({ cancelVisible: false }) if (ev.detail.value !== 'ok' || !orderNo) return request({ url: `/order/${orderNo}/cancel`, method: 'POST' }) .then(() => { logger.info(LOG, '取消订单成功, orderNo=', orderNo) wx.showToast({ title: '已取消', icon: 'success' }) this.refresh() }) .catch((err: any) => logger.warn(LOG, '取消订单失败, orderNo=', orderNo, 'code=', err && err.code)) }, onPay() { wx.showToast({ title: '请返回商城重新下单支付', icon: 'none' }) }, onRefund(e: WechatMiniprogram.TouchEvent) { const orderNo = e.currentTarget.dataset.no as string this.setData({ refundVisible: true, refundOrderNo: orderNo, refundReason: '' }) }, onRefundClose() { this.setData({ refundVisible: false }) }, onRefundInput(e: WechatMiniprogram.Input) { this.setData({ refundReason: e.detail.value }) }, onRefundSubmit() { if (this.data.refundSubmitting) return const orderNo = this.data.refundOrderNo const reason = (this.data.refundReason || '').trim() if (!reason) { wx.showToast({ title: '请填写退款原因', icon: 'none' }) return } this.setData({ refundSubmitting: true }) request({ url: '/order/refund', method: 'POST', data: { order_no: orderNo, reason } }) .then(() => { logger.info(LOG, '退款申请提交成功, orderNo=', orderNo) wx.showToast({ title: '退款申请已提交,等待审核', icon: 'none' }) this.setData({ refundVisible: false, refundSubmitting: false }) this.refresh() }) .catch((err: any) => { logger.warn(LOG, '退款申请失败, orderNo=', orderNo, 'code=', err && err.code) this.setData({ refundSubmitting: false }) }) }, onGoMall() { wx.navigateTo({ url: '/pages/mall/mall', fail: (e) => logger.warn(LOG, 'navigateTo 商城失败, err=', e && e.errMsg), }) }, })