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())}` }, })