first commit

This commit is contained in:
2026-09-11 01:45:04 +08:00
commit 0a8d60d87f
145 changed files with 40978 additions and 0 deletions
+216
View File
@@ -0,0 +1,216 @@
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'
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,
loading: true,
},
onLoad(query: Record<string, string>) {
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) => {
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 })
.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<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 = 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<PagedResult<QuotaFlowItem>>({ url: '/quota/flow', data: { page: 1, page_size: 50 } })
.then((res) => {
const list = 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.orderGoods) return
request<VirtualPayResult>({
url: '/order/virtual',
method: 'POST',
data: {
goods_code: this.data.orderGoods.goods_code,
platform: normalizePlatform(),
},
})
.then((res) => {
this.setData({ orderVisible: false })
this.doPayment(res)
})
.catch(() => {})
},
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) {
let count = 0
const timer = setInterval(() => {
count++
request<PagedResult<OrderItem>>({ url: '/order', data: { page: 1, page_size: 50 }, showError: false })
.then((res) => {
const order = res.list.find((it) => it.order_no === orderNo)
if (order && order.order_status === 2) {
clearInterval(timer)
wx.showToast({ title: '支付成功', icon: 'success' })
this.fetchQuota()
this.fetchUser()
} else if (order && (order.order_status === 3 || order.order_status === 4 || order.order_status === 5)) {
clearInterval(timer)
const msg = order.order_status === 5 ? '订单已退款' : '订单未支付成功'
wx.showToast({ title: msg, icon: 'none' })
} else if (count >= 10) {
this.finishPayTimeout(timer)
}
})
.catch(() => {
if (count >= 10) this.finishPayTimeout(timer)
})
}, 1500)
},
finishPayTimeout(timer: ReturnType<typeof setInterval>) {
clearInterval(timer)
wx.showToast({ title: '到账可能有延迟,请稍后查看', icon: 'none' })
this.fetchQuota()
this.fetchUser()
},
})