feat: 数据概览简化 + 用户管理增加余额/提现列
- 数据概览:去掉代付统计独立卡片,总收入中以小标签显示代付金额 - 数据概览:移除余额统计区块(余额改在用户管理中展示) - 数据概览:恢复转化率卡片(唯一付费用户/总用户) - 用户管理:用户列表新增「余额/提现」列,显示钱包余额和已提现金额 - 后端:DBUsersList 增加 user_balances 查询,返回 walletBalance 字段 - 后端:User model 添加 WalletBalance 非数据库字段 - 包含之前的小程序埋点和管理后台点击统计面板 Made-with: Cursor
This commit is contained in:
167
miniprogram/pages/wallet/wallet.js
Normal file
167
miniprogram/pages/wallet/wallet.js
Normal file
@@ -0,0 +1,167 @@
|
||||
const app = getApp()
|
||||
const { trackClick } = require('../../utils/trackClick')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
statusBarHeight: 0,
|
||||
balance: 0,
|
||||
balanceText: '0.00',
|
||||
totalRecharged: '0.00',
|
||||
totalGifted: '0.00',
|
||||
totalRefunded: '0.00',
|
||||
transactions: [],
|
||||
loading: true,
|
||||
rechargeAmounts: [10, 30, 50, 1000],
|
||||
selectedAmount: 30,
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.setData({ statusBarHeight: app.globalData.statusBarHeight || 44 })
|
||||
this.loadBalance()
|
||||
this.loadTransactions()
|
||||
},
|
||||
|
||||
async loadBalance() {
|
||||
if (!app.globalData.isLoggedIn || !app.globalData.userInfo) return
|
||||
const userId = app.globalData.userInfo.id
|
||||
try {
|
||||
const res = await app.request({ url: `/api/miniprogram/balance?userId=${userId}`, silent: true })
|
||||
if (res && res.data) {
|
||||
this.setData({
|
||||
balance: res.data.balance || 0,
|
||||
balanceText: (res.data.balance || 0).toFixed(2),
|
||||
totalRecharged: (res.data.totalRecharged || 0).toFixed(2),
|
||||
totalGifted: (res.data.totalGifted || 0).toFixed(2),
|
||||
totalRefunded: (res.data.totalRefunded || 0).toFixed(2),
|
||||
loading: false,
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
this.setData({ loading: false })
|
||||
}
|
||||
},
|
||||
|
||||
async loadTransactions() {
|
||||
if (!app.globalData.isLoggedIn || !app.globalData.userInfo) return
|
||||
const userId = app.globalData.userInfo.id
|
||||
try {
|
||||
const res = await app.request({ url: `/api/miniprogram/balance/transactions?userId=${userId}`, silent: true })
|
||||
if (res && res.data) {
|
||||
const list = (res.data || []).map(t => ({
|
||||
...t,
|
||||
amountText: (t.amount || 0).toFixed(2),
|
||||
amountSign: (t.amount || 0) >= 0 ? '+' : '',
|
||||
description: t.description || (t.type === 'recharge' ? '充值' : t.type === 'gift' ? '赠送' : t.type === 'refund' ? '退款' : t.type === 'consume' ? '阅读消费' : '其他'),
|
||||
}))
|
||||
this.setData({ transactions: list })
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[Wallet] load transactions failed', e)
|
||||
}
|
||||
},
|
||||
|
||||
selectAmount(e) {
|
||||
trackClick('wallet', 'tab_click', '选择金额' + (e.currentTarget.dataset.amount || ''))
|
||||
this.setData({ selectedAmount: parseInt(e.currentTarget.dataset.amount) })
|
||||
},
|
||||
|
||||
async handleRecharge() {
|
||||
trackClick('wallet', 'btn_click', '充值')
|
||||
if (!app.globalData.isLoggedIn || !app.globalData.userInfo) {
|
||||
wx.showToast({ title: '请先登录', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const userId = app.globalData.userInfo.id
|
||||
const amount = this.data.selectedAmount
|
||||
|
||||
wx.showLoading({ title: '创建订单...' })
|
||||
try {
|
||||
const res = await app.request({
|
||||
url: '/api/miniprogram/balance/recharge',
|
||||
method: 'POST',
|
||||
data: { userId, amount }
|
||||
})
|
||||
wx.hideLoading()
|
||||
if (res && res.data && res.data.orderSn) {
|
||||
// Trigger WeChat Pay for the recharge order
|
||||
const payRes = await app.request({
|
||||
url: '/api/miniprogram/pay',
|
||||
method: 'POST',
|
||||
data: {
|
||||
openId: app.globalData.openId,
|
||||
productType: 'balance_recharge',
|
||||
productId: res.data.orderSn,
|
||||
amount: amount,
|
||||
description: `余额充值 ¥${amount}`,
|
||||
userId: userId,
|
||||
}
|
||||
})
|
||||
if (payRes && payRes.payParams) {
|
||||
wx.requestPayment({
|
||||
...payRes.payParams,
|
||||
success: async () => {
|
||||
// Confirm the recharge
|
||||
await app.request({
|
||||
url: '/api/miniprogram/balance/recharge/confirm',
|
||||
method: 'POST',
|
||||
data: { orderSn: res.data.orderSn }
|
||||
})
|
||||
wx.showToast({ title: '充值成功', icon: 'success' })
|
||||
this.loadBalance()
|
||||
this.loadTransactions()
|
||||
},
|
||||
fail: () => {
|
||||
wx.showToast({ title: '支付取消', icon: 'none' })
|
||||
}
|
||||
})
|
||||
} else {
|
||||
wx.showToast({ title: '创建支付失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '充值失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
|
||||
async handleRefund() {
|
||||
trackClick('wallet', 'btn_click', '退款')
|
||||
if (this.data.balance <= 0) {
|
||||
wx.showToast({ title: '余额为零', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const userId = app.globalData.userInfo.id
|
||||
const balance = this.data.balance
|
||||
const refundAmount = (balance * 0.9).toFixed(2)
|
||||
|
||||
wx.showModal({
|
||||
title: '余额退款',
|
||||
content: `退回全部余额 ¥${balance.toFixed(2)}\n实际到账 ¥${refundAmount}(9折)\n\n退款将在1-3个工作日内原路返回`,
|
||||
confirmText: '确认退款',
|
||||
success: async (res) => {
|
||||
if (!res.confirm) return
|
||||
wx.showLoading({ title: '处理中...' })
|
||||
try {
|
||||
const result = await app.request({
|
||||
url: '/api/miniprogram/balance/refund',
|
||||
method: 'POST',
|
||||
data: { userId, amount: balance }
|
||||
})
|
||||
wx.hideLoading()
|
||||
if (result && result.data) {
|
||||
wx.showToast({ title: result.data.message || '退款成功', icon: 'success' })
|
||||
this.loadBalance()
|
||||
this.loadTransactions()
|
||||
}
|
||||
} catch (e) {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '退款失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
goBack() {
|
||||
wx.navigateBack()
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user