264 lines
8.7 KiB
TypeScript
264 lines
8.7 KiB
TypeScript
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'
|
||
import { navScroll } from '../../utils/nav'
|
||
import { logger } from '../../utils/logger'
|
||
|
||
// 日志模块名
|
||
const LOG = 'mall'
|
||
|
||
const GOODS_ICONS: Record<number, string> = {
|
||
[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,
|
||
// 滚动收缩:由 utils/nav.ts 的共享 handler 驱动
|
||
navScrolled: false,
|
||
},
|
||
|
||
// scroll-view 滚动 → 导航收缩为玻璃小标题
|
||
onNavScroll: navScroll,
|
||
|
||
onLoad(query: Record<string, string>) {
|
||
logger.debug(LOG, 'onLoad, query=', JSON.stringify(query))
|
||
if (query.tab === 'flow') this.setData({ view: 'flow' })
|
||
},
|
||
|
||
onShow() {
|
||
this.fetchUser()
|
||
this.fetchQuota()
|
||
this.fetchGoods()
|
||
if (this.data.view === 'flow') this.fetchFlow()
|
||
},
|
||
|
||
fetchUser() {
|
||
request<UserInfo>({ url: '/user/info', showError: false })
|
||
.then((user) => {
|
||
logger.debug(LOG, '用户信息加载成功, vipType=', user.vip_type)
|
||
const isVip = user.vip_type !== 1
|
||
this.setData({
|
||
userInfo: user,
|
||
isVip,
|
||
vipExpireText: isVip && user.vip_expire ? `有效期至 ${formatDate(user.vip_expire)}` : '',
|
||
})
|
||
})
|
||
.catch((e: any) => logger.warn(LOG, '用户信息加载失败(静默降级), code=', e && e.code))
|
||
},
|
||
|
||
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((e: any) => logger.warn(LOG, '额度加载失败(静默降级), code=', e && e.code))
|
||
},
|
||
|
||
fetchGoods() {
|
||
const params: Record<string, any> = { page: 1, page_size: 50 }
|
||
const filter = this.data.activeFilter
|
||
if (filter > 0) params.goods_type = filter
|
||
request<PagedResult<GoodsItem>>({ url: '/goods', data: params, auth: false })
|
||
.then((res) => {
|
||
const goods = normalizePaged<GoodsItem>(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),
|
||
}))
|
||
logger.debug(LOG, '商品列表加载成功, 条数=', goods.length)
|
||
this.setData({ goods, loading: false })
|
||
})
|
||
.catch((e: any) => {
|
||
logger.warn(LOG, '商品列表加载失败, code=', e && e.code)
|
||
this.setData({ loading: false })
|
||
})
|
||
},
|
||
|
||
fetchFlow() {
|
||
request<PagedResult<QuotaFlowItem>>({ url: '/quota/flow', data: { page: 1, page_size: 50 } })
|
||
.then((res) => {
|
||
const list = normalizePaged<QuotaFlowItem>(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((e: any) => logger.warn(LOG, '额度流水加载失败(静默降级), code=', e && e.code))
|
||
},
|
||
|
||
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<VirtualPayResult>({
|
||
url: '/order/virtual',
|
||
method: 'POST',
|
||
data: {
|
||
goods_code: this.data.orderGoods.goods_code,
|
||
platform: normalizePlatform(),
|
||
},
|
||
})
|
||
.then((res) => {
|
||
logger.info(LOG, '下单成功, orderNo=', res.order_no)
|
||
this.setData({ orderVisible: false, submitting: false })
|
||
this.doPayment(res)
|
||
})
|
||
.catch((e: any) => {
|
||
logger.warn(LOG, '下单失败, code=', e && e.code)
|
||
this.setData({ submitting: false })
|
||
})
|
||
},
|
||
|
||
doPayment(res: VirtualPayResult) {
|
||
logger.debug(LOG, '拉起虚拟支付, orderNo=', res.order_no)
|
||
wx.requestVirtualPayment({
|
||
signData: res.sign_data,
|
||
paySig: res.pay_sig,
|
||
signature: res.signature,
|
||
mode: 'short_series_goods',
|
||
success: () => {
|
||
// 拉起成功仅代表进入支付页,最终到账以后端发货推送为准,轮询订单确认
|
||
logger.info(LOG, '支付页拉起成功, orderNo=', res.order_no)
|
||
this.pollOrder(res.order_no)
|
||
},
|
||
fail: (e: any) => {
|
||
logger.error(LOG, '拉起支付失败, orderNo=', res.order_no, 'err=', e && e.errMsg)
|
||
},
|
||
})
|
||
},
|
||
|
||
pollOrder(orderNo: string) {
|
||
this.clearPayTimer()
|
||
let count = 0
|
||
;(this as any).payTimer = setInterval(() => {
|
||
count++
|
||
request<PagedResult<OrderItem>>({ url: '/order', data: { page: 1, page_size: 50 }, showError: false })
|
||
.then((res) => {
|
||
const order = normalizePaged<OrderItem>(res).list.find((it) => it.order_no === orderNo)
|
||
if (order && order.order_status === 2) {
|
||
this.clearPayTimer()
|
||
logger.info(LOG, '支付成功, orderNo=', orderNo)
|
||
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()
|
||
logger.warn(LOG, '订单未成功支付, orderNo=', orderNo, 'status=', order.order_status)
|
||
const msg = order.order_status === 5 ? '订单已退款' : '订单未支付成功'
|
||
wx.showToast({ title: msg, icon: 'none' })
|
||
} else if (count >= 10) {
|
||
logger.warn(LOG, '支付轮询超时, orderNo=', orderNo)
|
||
this.finishPayTimeout()
|
||
}
|
||
})
|
||
.catch((e: any) => {
|
||
logger.warn(LOG, '支付轮询请求失败, orderNo=', orderNo, 'code=', e && e.code)
|
||
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()
|
||
},
|
||
})
|