Files
zt-closet-wxmini-frontend/miniprogram/pages/order/order.ts
T
2026-09-13 02:28:46 +08:00

193 lines
6.4 KiB
TypeScript

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 { logger } from '../../utils/logger'
// 日志模块名
const LOG = 'order'
const STATUS_FILTERS = [
{ name: '全部', value: 0 },
{ name: '待支付', value: 1 },
{ name: '已支付', value: 2 },
{ name: '已取消', value: 3 },
{ name: '已过期', value: 4 },
{ name: '已退款', value: 5 },
]
const STATUS_TEXT: Record<number, string> = { 1: '待支付', 2: '已支付', 3: '已取消', 4: '已过期', 5: '已退款' }
const STATUS_TYPE: Record<number, string> = { 1: 'orange', 2: 'green', 3: 'gray', 4: 'gray', 5: 'blue' }
Page({
data: {
filters: STATUS_FILTERS,
activeFilter: 0,
list: [] as OrderItem[],
loading: true,
loadingMore: false,
page: 1,
pageSize: 20,
hasMore: true,
},
onShow() {
this.refresh()
this.startCountdown()
},
onHide() {
this.stopCountdown()
},
onUnload() {
this.stopCountdown()
},
refresh() {
this.setData({ page: 1, hasMore: true, loading: this.data.list.length === 0 })
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 })
},
fetchList(page: number) {
const params: Record<string, any> = { 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 })
request<PagedResult<OrderItem>>({ url: '/order', data: params })
.then((res) => {
if ((this as any).reqSeq !== seq) return
const { list: raw, total } = normalizePaged<OrderItem>(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] || '',
_statusType: STATUS_TYPE[it.order_status] || 'gray',
_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,
_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
wx.showModal({
title: '取消订单',
content: '确认取消该订单?',
success: (res) => {
if (!res.confirm) return
request({ url: `/order/${orderNo}/cancel`, method: 'POST' })
.then(() => {
logger.info(LOG, '取消订单成功, orderNo=', orderNo)
wx.showToast({ title: '已取消', icon: 'success' })
this.refresh()
})
.catch((e: any) => logger.warn(LOG, '取消订单失败, orderNo=', orderNo, 'code=', e && e.code))
},
fail: (e) => logger.warn(LOG, '取消订单弹窗失败, err=', e && e.errMsg),
})
},
onPay() {
wx.showToast({ title: '请返回商城重新下单支付', icon: 'none' })
},
onRefund(e: WechatMiniprogram.TouchEvent) {
const orderNo = e.currentTarget.dataset.no as string
wx.showModal({
title: '申请退款',
editable: true,
placeholderText: '请填写退款原因',
success: (res) => {
if (!res.confirm) return
const reason = (res.content || '').trim()
if (!reason) {
wx.showToast({ title: '请填写退款原因', icon: 'none' })
return
}
request({ url: '/order/refund', method: 'POST', data: { order_no: orderNo, reason } })
.then(() => {
logger.info(LOG, '退款申请提交成功, orderNo=', orderNo)
wx.showToast({ title: '退款申请已提交,等待审核', icon: 'none' })
this.refresh()
})
.catch((e: any) => logger.warn(LOG, '退款申请失败, orderNo=', orderNo, 'code=', e && e.code))
},
fail: (e) => logger.warn(LOG, '退款弹窗失败, err=', e && e.errMsg),
})
},
onGoMall() {
wx.navigateTo({
url: '/pages/mall/mall',
fail: (e) => logger.warn(LOG, 'navigateTo 商城失败, err=', e && e.errMsg),
})
},
})