优化提现流程,新增用户确认模式以支持待用户确认的转账,更新相关API和数据库结构以确保数据一致性。同时,调整小程序界面以展示待确认收款信息,提升用户体验。

This commit is contained in:
乘风
2026-02-06 19:45:24 +08:00
parent 2e65d68e1e
commit 8e67eb5d62
19 changed files with 649 additions and 95 deletions

View File

@@ -11,6 +11,9 @@ App({
// 小程序配置 - 真实AppID
appId: 'wxb8bbb2b10dec74aa',
// 订阅消息:用户点击「申请提现」→「立即提现」时会先弹出订阅授权窗
withdrawSubscribeTmplId: 'u3MbZGPRkrZIk-I7QdpwzFxnO_CeQPaCWF2FkiIablE',
// 微信支付配置
mchId: '1318592501', // 商户号

View File

@@ -11,7 +11,7 @@
"pages/settings/settings",
"pages/search/search",
"pages/addresses/addresses",
"pages/addresses/edit"
"pages/addresses/edit","pages/withdraw-records/withdraw-records"
],
"window": {
"backgroundTextStyle": "light",

View File

@@ -40,9 +40,15 @@ Page({
menuList: [
{ id: 'orders', title: '我的订单', icon: '📦', count: 0 },
{ id: 'referral', title: '推广中心', icon: '🎁', iconBg: 'gold', badge: '90%佣金' },
{ id: 'withdrawRecords', title: '提现记录', icon: '📋', iconBg: 'gray' },
{ id: 'about', title: '关于作者', icon: '', iconBg: 'brand' },
{ id: 'settings', title: '设置', icon: '⚙️', iconBg: 'gray' }
],
// 待确认收款(用户确认模式)
pendingConfirmList: [],
withdrawMchId: '',
withdrawAppId: '',
// 登录弹窗
showLoginModal: false,
@@ -125,6 +131,7 @@ Page({
totalReadTime: Math.floor(Math.random() * 200) + 50
})
this.loadReferralEarnings()
this.loadPendingConfirm()
} else {
this.setData({
isLoggedIn: false,
@@ -139,6 +146,82 @@ Page({
}
},
// 拉取待确认收款列表(用于「确认收款」按钮)
async loadPendingConfirm() {
const userInfo = app.globalData.userInfo
if (!app.globalData.isLoggedIn || !userInfo || !userInfo.id) return
try {
const res = await app.request('/api/withdraw/pending-confirm?userId=' + userInfo.id)
if (res && res.success && res.data) {
const list = (res.data.list || []).map(item => ({
id: item.id,
amount: (item.amount || 0).toFixed(2),
package: item.package,
created_at: item.created_at ? this.formatDateMy(item.created_at) : '--'
}))
this.setData({
pendingConfirmList: list,
withdrawMchId: res.data.mch_id || '',
withdrawAppId: res.data.app_id || ''
})
} else {
this.setData({ pendingConfirmList: [], withdrawMchId: '', withdrawAppId: '' })
}
} catch (e) {
this.setData({ pendingConfirmList: [] })
}
},
formatDateMy(dateStr) {
if (!dateStr) return '--'
const d = new Date(dateStr)
const m = (d.getMonth() + 1).toString().padStart(2, '0')
const day = d.getDate().toString().padStart(2, '0')
return `${m}-${day}`
},
// 确认收款(调起微信收款页)
confirmReceive(e) {
const index = e.currentTarget.dataset.index
const id = e.currentTarget.dataset.id
const list = this.data.pendingConfirmList || []
let item = (typeof index === 'number' || (index !== undefined && index !== '')) ? list[index] : null
if (!item && id) item = list.find(x => x.id === id) || null
if (!item || !item.package) {
wx.showToast({ title: '请稍后刷新再试', icon: 'none' })
return
}
const mchId = this.data.withdrawMchId
const appId = this.data.withdrawAppId
if (!mchId || !appId) {
wx.showToast({ title: '参数缺失,请刷新重试', icon: 'none' })
return
}
if (!wx.canIUse('requestMerchantTransfer')) {
wx.showToast({ title: '当前微信版本不支持,请升级后重试', icon: 'none' })
return
}
wx.showLoading({ title: '调起收款...', mask: true })
wx.requestMerchantTransfer({
mchId,
appId,
package: item.package,
success: () => {
wx.hideLoading()
wx.showToast({ title: '收款成功', icon: 'success' })
const newList = list.filter(x => x.id !== item.id)
this.setData({ pendingConfirmList: newList })
this.loadPendingConfirm()
},
fail: (err) => {
wx.hideLoading()
const msg = (err.errMsg || '').includes('cancel') ? '已取消' : (err.errMsg || '收款失败')
wx.showToast({ title: msg, icon: 'none' })
},
complete: () => { wx.hideLoading() }
})
},
// 从与推广中心相同的接口拉取收益数据并更新展示(累计收益 = totalCommission可提现 = 累计-已提现-待审核)
async loadReferralEarnings() {
const userInfo = app.globalData.userInfo
@@ -438,6 +521,7 @@ Page({
const routes = {
orders: '/pages/purchases/purchases',
referral: '/pages/referral/referral',
withdrawRecords: '/pages/withdraw-records/withdraw-records',
about: '/pages/about/about',
settings: '/pages/settings/settings'
}

View File

@@ -109,6 +109,24 @@
</view>
</view>
<!-- 待确认收款(用户确认模式)- 仅登录用户显示 -->
<view class="pending-confirm-card" wx:if="{{isLoggedIn}}">
<view class="pending-confirm-header">
<text class="pending-confirm-title">待确认收款</text>
<text class="pending-confirm-desc" wx:if="{{pendingConfirmList.length > 0}}">审核已通过,点击下方按钮完成收款</text>
<text class="pending-confirm-desc" wx:else>暂无待确认的提现,审核通过后会出现在这里</text>
</view>
<view class="pending-confirm-list" wx:if="{{pendingConfirmList.length > 0}}">
<view class="pending-confirm-item" wx:for="{{pendingConfirmList}}" wx:key="id">
<view class="pending-confirm-info">
<text class="pending-confirm-amount">¥{{item.amount}}</text>
<text class="pending-confirm-time">{{item.created_at}}</text>
</view>
<view class="pending-confirm-btn" bindtap="confirmReceive" data-index="{{index}}">确认收款</view>
</view>
</view>
</view>
<!-- Tab切换 - 仅登录用户显示 -->
<view class="tab-bar-custom" wx:if="{{isLoggedIn}}">
<view

View File

@@ -1184,3 +1184,28 @@
color: #ffffff;
box-shadow: 0 8rpx 24rpx rgba(56, 189, 172, 0.3);
}
/* 待确认收款 */
.pending-confirm-card {
margin: 32rpx;
padding: 28rpx 32rpx;
background: rgba(76, 175, 80, 0.08);
border: 2rpx solid rgba(76, 175, 80, 0.25);
border-radius: 24rpx;
}
.pending-confirm-header { margin-bottom: 20rpx; }
.pending-confirm-title { font-size: 28rpx; font-weight: 600; color: #fff; display: block; }
.pending-confirm-desc { font-size: 24rpx; color: rgba(255,255,255,0.6); margin-top: 8rpx; display: block; }
.pending-confirm-list { display: flex; flex-direction: column; gap: 16rpx; }
.pending-confirm-item {
display: flex; align-items: center; justify-content: space-between;
padding: 20rpx 24rpx; background: rgba(28,28,30,0.6); border-radius: 16rpx;
}
.pending-confirm-info { display: flex; flex-direction: column; gap: 4rpx; }
.pending-confirm-amount { font-size: 32rpx; font-weight: 600; color: #4CAF50; }
.pending-confirm-time { font-size: 22rpx; color: rgba(255,255,255,0.5); }
.pending-confirm-btn {
padding: 16rpx 32rpx;
background: linear-gradient(135deg, #4CAF50 0%, #388E3C 100%);
color: #fff; font-size: 26rpx; font-weight: 500; border-radius: 20rpx;
}

View File

@@ -60,8 +60,9 @@ Page({
posterReferralLink: '',
posterNickname: '',
posterNicknameInitial: '',
posterCaseCount: 62
},
posterCaseCount: 62,
},
onLoad() {
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
@@ -224,6 +225,7 @@ Page({
}
})
})
console.log('[Referral] ✅ 数据设置完成')
console.log('[Referral] - 绑定中:', this.data.bindingCount)
@@ -648,13 +650,26 @@ Page({
content: `将提现 ¥${availableEarnings.toFixed(2)} 到您的微信零钱`,
confirmText: '立即提现',
success: async (res) => {
if (res.confirm) {
if (!res.confirm) return
const tmplId = app.globalData.withdrawSubscribeTmplId
if (tmplId && tmplId.length > 10) {
wx.requestSubscribeMessage({
tmplIds: [tmplId],
success: () => { this.doWithdraw(availableEarnings) },
fail: () => { this.doWithdraw(availableEarnings) }
})
} else {
await this.doWithdraw(availableEarnings)
}
}
})
},
// 跳转提现记录页
goToWithdrawRecords() {
wx.navigateTo({ url: '/pages/withdraw-records/withdraw-records' })
},
// 执行提现
async doWithdraw(amount) {
wx.showLoading({ title: '提现中...' })

View File

@@ -53,6 +53,7 @@
{{availableEarningsNum < minWithdrawAmount ? '满' + minWithdrawAmount + '元可提现' : !hasWechatId ? '请先绑定微信号' : '申请提现 ¥' + availableEarnings}}
</view>
<text class="wechat-tip" wx:if="{{availableEarningsNum > 0 && !hasWechatId}}">为便于提现到账,请先到「设置」中绑定微信号</text>
<view class="withdraw-records-link" bindtap="goToWithdrawRecords">查看提现记录</view>
</view>
</view>

View File

@@ -38,6 +38,7 @@
.withdraw-btn { padding: 28rpx; background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%); color: #fff; font-size: 32rpx; font-weight: 600; text-align: center; border-radius: 24rpx; box-shadow: 0 8rpx 24rpx rgba(0,206,209,0.3); }
.withdraw-btn.btn-disabled { background: rgba(0,206,209,0.2); color: rgba(255,255,255,0.3); box-shadow: none; }
.wechat-tip { display: block; font-size: 24rpx; color: rgba(255,165,0,0.9); margin-top: 16rpx; text-align: center; }
.withdraw-records-link { display: block; margin-top: 16rpx; text-align: center; font-size: 26rpx; color: #00CED1; }
/* ???? - ?? Next.js 4??? */
.stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8rpx; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }

View File

@@ -0,0 +1,72 @@
/**
* 提现记录 - 独立页面
*/
const app = getApp()
Page({
data: {
statusBarHeight: 44,
list: [],
loading: true
},
onLoad() {
this.setData({ statusBarHeight: app.globalData.statusBarHeight || 44 })
this.loadRecords()
},
onShow() {
this.loadRecords()
},
async loadRecords() {
const userInfo = app.globalData.userInfo
if (!app.globalData.isLoggedIn || !userInfo || !userInfo.id) {
this.setData({ list: [], loading: false })
return
}
this.setData({ loading: true })
try {
const res = await app.request('/api/withdraw/records?userId=' + userInfo.id)
if (res && res.success && res.data && Array.isArray(res.data.list)) {
const list = (res.data.list || []).map(item => ({
id: item.id,
amount: (item.amount != null ? item.amount : 0).toFixed(2),
status: this.statusText(item.status),
statusRaw: item.status,
created_at: item.created_at ? this.formatDate(item.created_at) : '--'
}))
this.setData({ list, loading: false })
} else {
this.setData({ list: [], loading: false })
}
} catch (e) {
console.log('[WithdrawRecords] 加载失败:', e)
this.setData({ list: [], loading: false })
}
},
statusText(status) {
const map = {
pending: '待审核',
pending_confirm: '待确认收款',
processing: '处理中',
success: '已到账',
failed: '已拒绝'
}
return map[status] || status || '--'
},
formatDate(dateStr) {
if (!dateStr) return '--'
const d = new Date(dateStr)
const y = d.getFullYear()
const m = (d.getMonth() + 1).toString().padStart(2, '0')
const day = d.getDate().toString().padStart(2, '0')
return `${y}-${m}-${day}`
},
goBack() {
wx.navigateBack()
}
})

View File

@@ -0,0 +1,4 @@
{
"usingComponents": {},
"navigationStyle": "custom"
}

View File

@@ -0,0 +1,22 @@
<view class="page">
<view class="nav-bar" style="padding-top: {{statusBarHeight}}px;">
<view class="nav-back" bindtap="goBack">←</view>
<text class="nav-title">提现记录</text>
<view class="nav-placeholder"></view>
</view>
<view class="nav-placeholder-bar" style="height: {{statusBarHeight + 44}}px;"></view>
<view class="content">
<view class="loading-tip" wx:if="{{loading}}">加载中...</view>
<view class="empty" wx:elif="{{list.length === 0}}">暂无提现记录</view>
<view class="list" wx:else>
<view class="item" wx:for="{{list}}" wx:key="id">
<view class="item-left">
<text class="amount">¥{{item.amount}}</text>
<text class="time">{{item.created_at}}</text>
</view>
<text class="status status-{{item.statusRaw}}">{{item.status}}</text>
</view>
</view>
</view>
</view>

View File

@@ -0,0 +1,57 @@
.page {
min-height: 100vh;
background: #000;
}
.nav-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
background: rgba(0,0,0,0.9);
display: flex;
align-items: center;
padding: 0 24rpx;
height: 88rpx;
}
.nav-back {
color: #00CED1;
font-size: 36rpx;
padding: 16rpx;
}
.nav-title {
flex: 1;
text-align: center;
font-size: 34rpx;
font-weight: 600;
color: #fff;
}
.nav-placeholder { width: 80rpx; }
.nav-placeholder-bar { width: 100%; }
.content {
padding: 32rpx;
}
.loading-tip, .empty {
text-align: center;
color: rgba(255,255,255,0.6);
font-size: 28rpx;
padding: 80rpx 0;
}
.list { }
.item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 28rpx 0;
border-bottom: 2rpx solid rgba(255,255,255,0.06);
}
.item:last-child { border-bottom: none; }
.item-left { display: flex; flex-direction: column; gap: 8rpx; }
.amount { font-size: 32rpx; font-weight: 600; color: #fff; }
.time { font-size: 24rpx; color: rgba(255,255,255,0.5); }
.status { font-size: 26rpx; }
.status.status-pending { color: #FFA500; }
.status.status-pending_confirm { color: #4CAF50; }
.status.status-success { color: #4CAF50; }
.status.status-failed { color: rgba(255,255,255,0.5); }