224 lines
7.5 KiB
JavaScript
224 lines
7.5 KiB
JavaScript
const { request, cents, clientRequestId, ensureLogin } = require('../../utils/api.js')
|
|
|
|
const providers = [
|
|
{ value: 'MEITUAN', label: '美团' },
|
|
{ value: 'DIANPING', label: '大众点评' },
|
|
{ value: 'DOUYIN', label: '抖音' },
|
|
{ value: 'KUAISHOU', label: '快手' },
|
|
]
|
|
|
|
const providerLabels = Object.fromEntries(providers.map((item) => [item.value, item.label]))
|
|
|
|
Page({
|
|
data: {
|
|
storeId: '',
|
|
storeName: '',
|
|
loading: false,
|
|
redeeming: false,
|
|
errorMessage: '',
|
|
canRedeem: false,
|
|
rangeDays: 30,
|
|
ranges: [7, 30, 90],
|
|
summary: {
|
|
orderTotal: 0,
|
|
collectedAmountText: '¥0.00',
|
|
payingMemberTotal: 0,
|
|
voucherSucceeded: 0,
|
|
availableRoomTotal: 0,
|
|
attentionRoomTotal: 0,
|
|
},
|
|
paymentChannels: [],
|
|
dailyRevenue: [],
|
|
providers,
|
|
providerIndex: 0,
|
|
pendingOrders: [],
|
|
orderIndex: 0,
|
|
voucherCode: '',
|
|
manualNote: '',
|
|
redemptionRecords: [],
|
|
},
|
|
|
|
async onLoad(options) {
|
|
this.setData({
|
|
storeId: options.storeId || '',
|
|
storeName: options.storeName || '',
|
|
})
|
|
await this.loadBusiness()
|
|
},
|
|
|
|
async onPullDownRefresh() {
|
|
await this.loadBusiness()
|
|
wx.stopPullDownRefresh()
|
|
},
|
|
|
|
async loadBusiness() {
|
|
if (!this.data.storeId) {
|
|
this.setData({ errorMessage: '缺少门店信息' })
|
|
return
|
|
}
|
|
this.setData({ loading: true, errorMessage: '' })
|
|
try {
|
|
await ensureLogin()
|
|
const me = await request('/auth/me')
|
|
const access = me.data?.access || { roles: [], capabilities: [] }
|
|
const canRedeem = access.capabilities.includes('store.operation.write')
|
|
|| access.capabilities.includes('tenant.manage')
|
|
|| access.roles.includes('PLATFORM_ADMIN')
|
|
this.setData({ canRedeem })
|
|
const storeId = encodeURIComponent(this.data.storeId)
|
|
const to = new Date()
|
|
const from = new Date(to.getTime() - this.data.rangeDays * 86400000)
|
|
const [statistics, orders, records] = await Promise.all([
|
|
request(`/management/statistics?storeId=${storeId}&from=${encodeURIComponent(from.toISOString())}&to=${encodeURIComponent(to.toISOString())}`),
|
|
canRedeem
|
|
? request(`/orders?page=1&pageSize=50&storeId=${storeId}&status=PENDING_PAYMENT`)
|
|
: Promise.resolve({ data: { items: [] } }),
|
|
request(`/management/third-party/records?storeId=${storeId}`),
|
|
])
|
|
this.presentStatistics(statistics.data || {})
|
|
const pendingOrders = (orders.data?.items || []).map((item) => {
|
|
const unpaidAmountCents = Math.max(0, Number(item.totalAmountCents || 0) - Number(item.paidAmountCents || 0))
|
|
return {
|
|
...item,
|
|
unpaidAmountCents,
|
|
label: `${item.orderNo} · ${item.roomName || item.roomNo || '房间'} · ${cents(unpaidAmountCents)}`,
|
|
}
|
|
})
|
|
const redemptionRecords = (records.data?.redemptions || []).map((item) => ({
|
|
...item,
|
|
providerText: providerLabels[item.provider] || item.provider,
|
|
statusText: this.redemptionStatus(item.status),
|
|
timeText: this.formatTime(item.completedAt || item.createdAt),
|
|
}))
|
|
this.setData({
|
|
pendingOrders,
|
|
orderIndex: Math.min(this.data.orderIndex, Math.max(0, pendingOrders.length - 1)),
|
|
redemptionRecords,
|
|
})
|
|
} catch (error) {
|
|
this.setData({ errorMessage: error.message || '经营数据加载失败' })
|
|
} finally {
|
|
this.setData({ loading: false })
|
|
}
|
|
},
|
|
|
|
presentStatistics(data) {
|
|
const summary = data.summary || {}
|
|
this.setData({
|
|
summary: {
|
|
...summary,
|
|
collectedAmountText: cents(summary.collectedAmountCents),
|
|
},
|
|
paymentChannels: (data.paymentChannels || []).map((item) => ({
|
|
...item,
|
|
amountText: cents(item.amountCents),
|
|
})),
|
|
dailyRevenue: (data.dailyRevenue || []).slice(-14).reverse().map((item) => ({
|
|
...item,
|
|
amountText: cents(item.amountCents),
|
|
})),
|
|
})
|
|
},
|
|
|
|
selectRange(event) {
|
|
const rangeDays = Number(event.currentTarget.dataset.days)
|
|
if (!rangeDays || rangeDays === this.data.rangeDays) return
|
|
this.setData({ rangeDays })
|
|
this.loadBusiness()
|
|
},
|
|
|
|
selectProvider(event) {
|
|
this.setData({ providerIndex: Number(event.detail.value) || 0 })
|
|
},
|
|
|
|
selectOrder(event) {
|
|
this.setData({ orderIndex: Number(event.detail.value) || 0 })
|
|
},
|
|
|
|
inputVoucherCode(event) {
|
|
this.setData({ voucherCode: String(event.detail.value || '').trim() })
|
|
},
|
|
|
|
inputManualNote(event) {
|
|
this.setData({ manualNote: String(event.detail.value || '').trim() })
|
|
},
|
|
|
|
scanVoucher() {
|
|
if (!this.data.canRedeem) return
|
|
wx.scanCode({
|
|
scanType: ['barCode', 'qrCode'],
|
|
success: ({ result }) => this.setData({ voucherCode: String(result || '').trim() }),
|
|
fail: (error) => {
|
|
if (!String(error.errMsg || '').includes('cancel')) {
|
|
this.setData({ errorMessage: '扫码失败,请手工输入券码' })
|
|
}
|
|
},
|
|
})
|
|
},
|
|
|
|
redeemVoucher(event) {
|
|
if (!this.data.canRedeem || this.data.redeeming) return
|
|
const manual = event.currentTarget.dataset.mode === 'manual'
|
|
const order = this.data.pendingOrders[this.data.orderIndex]
|
|
const provider = this.data.providers[this.data.providerIndex]
|
|
const voucherCode = this.data.voucherCode.trim()
|
|
const manualNote = this.data.manualNote.trim()
|
|
if (!order || !provider || voucherCode.length < 4) {
|
|
this.setData({ errorMessage: '请选择待支付订单并输入有效券码' })
|
|
return
|
|
}
|
|
if (manual && !manualNote) {
|
|
this.setData({ errorMessage: '人工核销必须填写确认说明' })
|
|
return
|
|
}
|
|
const title = manual ? '人工确认核销' : '在线验券'
|
|
wx.showModal({
|
|
title,
|
|
content: `确认将${provider.label}券用于订单 ${order.orderNo},抵扣 ${cents(order.unpaidAmountCents)} 吗?`,
|
|
success: ({ confirm }) => {
|
|
if (confirm) this.submitRedemption({ manual, order, provider, voucherCode, manualNote })
|
|
},
|
|
})
|
|
},
|
|
|
|
async submitRedemption({ manual, order, provider, voucherCode, manualNote }) {
|
|
this.setData({ redeeming: true, errorMessage: '' })
|
|
try {
|
|
const payload = {
|
|
provider: provider.value,
|
|
voucherCode,
|
|
orderId: order.id,
|
|
clientRequestId: clientRequestId(manual ? 'manager-manual-redeem' : 'manager-redeem'),
|
|
...(manual ? { amountCents: order.unpaidAmountCents, note: manualNote } : {}),
|
|
}
|
|
const endpoint = manual
|
|
? '/management/group-vouchers/redeem-manual'
|
|
: '/management/group-vouchers/redeem'
|
|
const response = await request(endpoint, { method: 'POST', data: payload })
|
|
const result = response.data || {}
|
|
if (result.status !== 'SUCCEEDED') {
|
|
throw new Error(result.failureCode ? `验券未成功:${result.failureCode}` : '验券正在处理中')
|
|
}
|
|
this.setData({ voucherCode: '', manualNote: '' })
|
|
wx.showToast({ title: '核销成功', icon: 'success' })
|
|
await this.loadBusiness()
|
|
} catch (error) {
|
|
this.setData({ errorMessage: error.message || '验券失败' })
|
|
} finally {
|
|
this.setData({ redeeming: false })
|
|
}
|
|
},
|
|
|
|
redemptionStatus(status) {
|
|
return { SUCCEEDED: '成功', FAILED: '失败', PENDING: '处理中' }[status] || status
|
|
},
|
|
|
|
formatTime(value) {
|
|
if (!value) return ''
|
|
const date = new Date(value)
|
|
if (Number.isNaN(date.getTime())) return String(value)
|
|
const pad = (part) => String(part).padStart(2, '0')
|
|
return `${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`
|
|
},
|
|
})
|