# Conflicts:
#	api/app/controller/api/Analyze.php
#	api/app/controller/api/AppConfig.php
#	api/app/controller/api/Payment.php
#	api/app/controller/api/Test.php
#	miniprogram/app.js
#	miniprogram/pages/enterprise/index.js
#	miniprogram/pages/enterprise/resume-history.js
#	miniprogram/pages/index/result.js
#	miniprogram/pages/index/result.wxml
#	miniprogram/pages/index/result.wxss
#	miniprogram/pages/promo/poster.js
#	miniprogram/pages/recharge/index.js
#	miniprogram/pages/result/disc.wxss
#	miniprogram/pages/result/mbti.wxss
#	miniprogram/pages/result/pdp.wxss
#	miniprogram/pages/result/resume.js
#	miniprogram/utils/payment.js
#	miniprogram/utils/share.js
This commit is contained in:
Ghost
2026-03-24 10:14:19 +08:00
346 changed files with 65613 additions and 48669 deletions

View File

@@ -1,270 +1,270 @@
// pages/promo/index.js
const app = getApp()
const { request } = require('../../utils/request')
Page({
data: {
balance: '0.00',
totalEarned: '0.00',
pendingAmount: '0.00',
bindingCount: 0,
paidCount: 0,
expiringCount: 0,
activeTab: 0,
userList: [],
listTotal: 0,
listPage: 1,
listLoading: false,
listFinished: false,
commissionRate: '',
bindingDays: '',
testCommissionType: '',
testCommissionRate: '',
testCommissionAmount: '',
testNoPayment: false,
withdrawMinYuan: '1.00',
withdrawMaxYuan: '',
withdrawFeePct: 0,
requireWithdrawAudit: true,
showWithdrawDialog: false,
withdrawAmountInput: '',
withdrawError: '',
withdrawFeeYuan: '0.00',
withdrawActualYuan: '0.00'
},
onLoad() {
this.loadStats()
this.loadBindings(true)
},
onShow() {
this.loadStats()
},
/** 加载推广统计数据 */
loadStats() {
request({
url: '/api/distribution/stats',
method: 'GET',
success: (res) => {
const payload = res && res.data
if (payload && payload.code === 200 && payload.data) {
const d = payload.data
const title = d.promoCenterTitle || '推广中心'
wx.setNavigationBarTitle({ title })
this.setData({
balance: d.walletBalance,
totalEarned: d.totalEarned,
pendingAmount: d.pendingAmount,
bindingCount: d.bindingCount,
paidCount: d.paidCount,
expiringCount: d.expiringCount,
totalInvite: d.totalInvite,
commissionRate: d.commissionRate,
bindingDays: d.bindingDays,
testCommissionType: d.testCommissionType,
testCommissionRate: d.testCommissionRate,
testCommissionAmount: d.testCommissionAmount,
testNoPayment: d.testNoPayment,
withdrawMinYuan: d.withdrawMinYuan != null ? d.withdrawMinYuan : '1.00',
withdrawMaxYuan: d.withdrawMaxYuan != null && d.withdrawMaxYuan !== '' ? d.withdrawMaxYuan : '',
withdrawFeePct: d.withdrawFeePct != null ? d.withdrawFeePct : 0,
requireWithdrawAudit: d.requireWithdrawAudit !== false,
})
}
}
})
},
/** 加载绑定用户列表tab: 0=绑定中 1=已付款 2=已过期) */
loadBindings(reset = false) {
if (this.data.listLoading) return
if (!reset && this.data.listFinished) return
const page = reset ? 1 : this.data.listPage
this.setData({ listLoading: true })
request({
url: `/api/distribution/bindings?tab=${this.data.activeTab}&page=${page}&pageSize=10`,
method: 'GET',
success: (res) => {
const payload = res && res.data
if (payload && payload.code === 200 && payload.data) {
const { list, total } = payload.data
const formatted = (list || []).map(item => ({
...item,
createdAtStr: item.createdAt ? this._fmtTimestamp(item.createdAt) : ''
}))
const newList = reset ? formatted : [...this.data.userList, ...formatted]
this.setData({
userList: newList,
listTotal: total,
listPage: page + 1,
listFinished: newList.length >= total,
})
}
},
complete: () => {
this.setData({ listLoading: false })
}
})
},
/** 时间戳格式化为 YYYY-MM-DD */
_fmtTimestamp(ts) {
if (!ts) return ''
const d = new Date(ts * 1000)
if (isNaN(d.getTime())) return ''
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${y}-${m}-${day}`
},
/** 切换用户列表 Tab */
switchTab(e) {
const index = parseInt(e.currentTarget.dataset.index)
if (index === this.data.activeTab) return
this.setData({ activeTab: index, userList: [], listPage: 1, listFinished: false })
this.loadBindings(true)
},
/** 上拉加载更多 */
onReachBottom() {
this.loadBindings(false)
},
/** 申请提现:打开自定义金额弹框 */
handleWithdraw() {
const balance = parseFloat(this.data.balance)
if (balance < 1) {
wx.showToast({ title: '余额不足1元暂无法提现', icon: 'none' })
return
}
const pct = this.data.withdrawFeePct || 0
const feeYuan = (balance * pct / 100).toFixed(2)
const actualYuan = (balance - balance * pct / 100).toFixed(2)
this.setData({
showWithdrawDialog: true,
withdrawAmountInput: this.data.balance,
withdrawFeeYuan: feeYuan,
withdrawActualYuan: actualYuan,
withdrawError: ''
})
},
/** 关闭提现弹框 */
closeWithdrawDialog() {
this.setData({
showWithdrawDialog: false,
withdrawError: ''
})
},
/** 输入金额:实时计算手续费与实际到账 */
onWithdrawInput(e) {
const raw = e.detail.value
const val = parseFloat(raw)
const pct = this.data.withdrawFeePct || 0
let feeYuan = '0.00'
let actualYuan = '0.00'
if (raw !== '' && !isNaN(val) && val >= 0) {
const fee = val * pct / 100
feeYuan = fee.toFixed(2)
actualYuan = (val - fee).toFixed(2)
}
this.setData({
withdrawAmountInput: raw,
withdrawFeeYuan: feeYuan,
withdrawActualYuan: actualYuan,
withdrawError: ''
})
},
/** 确认提现(使用用户填写的金额) */
confirmWithdraw() {
const balance = parseFloat(this.data.balance)
const val = parseFloat(this.data.withdrawAmountInput)
const minYuan = parseFloat(this.data.withdrawMinYuan) || 1
const pct = this.data.withdrawFeePct || 0
if (isNaN(val)) {
this.setData({ withdrawError: '请输入正确的金额' })
return
}
if (val < 1) {
this.setData({ withdrawError: '单次提现金额至少 1 元' })
return
}
if (val > balance) {
this.setData({ withdrawError: '不可超过当前可提现金额' })
return
}
const actualYuan = val - val * pct / 100
if (actualYuan < minYuan) {
this.setData({ withdrawError: `实际到账金额不得低于最低提现金额 ¥${minYuan.toFixed(2)}` })
return
}
const amountFen = Math.floor(val * 100)
this.setData({ withdrawError: '' })
request({
url: '/api/distribution/withdraw',
method: 'POST',
data: { amountFen },
success: (r) => {
const payload = r && r.data
if (payload && payload.code === 200) {
const msg = payload.msg || payload.message || ''
wx.showToast({ title: '申请已提交', icon: 'success' })
this.setData({ showWithdrawDialog: false })
this.loadStats()
// 免审核且已自动发起微信转账时,自动进入提现记录页
if (msg.indexOf('已自动发起') !== -1) {
setTimeout(() => {
wx.navigateTo({ url: '/pages/promo/withdrawals' })
}, 500)
}
} else {
const errMsg = (payload && (payload.msg || payload.message)) || '申请失败,请稍后重试'
this.setData({ withdrawError: errMsg })
}
}
})
},
/** 查看提现记录 */
goToWithdrawHistory() {
wx.navigateTo({ url: '/pages/promo/withdrawals' })
},
/** 生成海报 */
generatePoster() {
wx.navigateTo({ url: '/pages/promo/poster' })
},
/** 分享到朋友圈:引导用户使用右上角菜单 */
shareToTimeline() {
wx.showToast({ title: '请点击右上角 ··· 选择「分享到朋友圈」', icon: 'none', duration: 2500 })
},
/** 分享给好友 */
onShareAppMessage() {
const { getSharePathByScope } = require('../../utils/share')
return {
title: '神仙团队AI性格测试 - 发现你的内在潜能',
path: getSharePathByScope('/pages/index/index')
}
},
/** 分享到朋友圈 */
onShareTimeline() {
const { buildShareQuery } = require('../../utils/share')
return {
title: '神仙团队AI性格测试 - 发现你的内在潜能',
query: buildShareQuery()
}
}
})
// pages/promo/index.js
const app = getApp()
const { request } = require('../../utils/request')
Page({
data: {
balance: '0.00',
totalEarned: '0.00',
pendingAmount: '0.00',
bindingCount: 0,
paidCount: 0,
expiringCount: 0,
activeTab: 0,
userList: [],
listTotal: 0,
listPage: 1,
listLoading: false,
listFinished: false,
commissionRate: '',
bindingDays: '',
testCommissionType: '',
testCommissionRate: '',
testCommissionAmount: '',
testNoPayment: false,
withdrawMinYuan: '1.00',
withdrawMaxYuan: '',
withdrawFeePct: 0,
requireWithdrawAudit: true,
showWithdrawDialog: false,
withdrawAmountInput: '',
withdrawError: '',
withdrawFeeYuan: '0.00',
withdrawActualYuan: '0.00'
},
onLoad() {
this.loadStats()
this.loadBindings(true)
},
onShow() {
this.loadStats()
},
/** 加载推广统计数据 */
loadStats() {
request({
url: '/api/distribution/stats',
method: 'GET',
success: (res) => {
const payload = res && res.data
if (payload && payload.code === 200 && payload.data) {
const d = payload.data
const title = d.promoCenterTitle || '推广中心'
wx.setNavigationBarTitle({ title })
this.setData({
balance: d.walletBalance,
totalEarned: d.totalEarned,
pendingAmount: d.pendingAmount,
bindingCount: d.bindingCount,
paidCount: d.paidCount,
expiringCount: d.expiringCount,
totalInvite: d.totalInvite,
commissionRate: d.commissionRate,
bindingDays: d.bindingDays,
testCommissionType: d.testCommissionType,
testCommissionRate: d.testCommissionRate,
testCommissionAmount: d.testCommissionAmount,
testNoPayment: d.testNoPayment,
withdrawMinYuan: d.withdrawMinYuan != null ? d.withdrawMinYuan : '1.00',
withdrawMaxYuan: d.withdrawMaxYuan != null && d.withdrawMaxYuan !== '' ? d.withdrawMaxYuan : '',
withdrawFeePct: d.withdrawFeePct != null ? d.withdrawFeePct : 0,
requireWithdrawAudit: d.requireWithdrawAudit !== false,
})
}
}
})
},
/** 加载绑定用户列表tab: 0=绑定中 1=已付款 2=已过期) */
loadBindings(reset = false) {
if (this.data.listLoading) return
if (!reset && this.data.listFinished) return
const page = reset ? 1 : this.data.listPage
this.setData({ listLoading: true })
request({
url: `/api/distribution/bindings?tab=${this.data.activeTab}&page=${page}&pageSize=10`,
method: 'GET',
success: (res) => {
const payload = res && res.data
if (payload && payload.code === 200 && payload.data) {
const { list, total } = payload.data
const formatted = (list || []).map(item => ({
...item,
createdAtStr: item.createdAt ? this._fmtTimestamp(item.createdAt) : ''
}))
const newList = reset ? formatted : [...this.data.userList, ...formatted]
this.setData({
userList: newList,
listTotal: total,
listPage: page + 1,
listFinished: newList.length >= total,
})
}
},
complete: () => {
this.setData({ listLoading: false })
}
})
},
/** 时间戳格式化为 YYYY-MM-DD */
_fmtTimestamp(ts) {
if (!ts) return ''
const d = new Date(ts * 1000)
if (isNaN(d.getTime())) return ''
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${y}-${m}-${day}`
},
/** 切换用户列表 Tab */
switchTab(e) {
const index = parseInt(e.currentTarget.dataset.index)
if (index === this.data.activeTab) return
this.setData({ activeTab: index, userList: [], listPage: 1, listFinished: false })
this.loadBindings(true)
},
/** 上拉加载更多 */
onReachBottom() {
this.loadBindings(false)
},
/** 申请提现:打开自定义金额弹框 */
handleWithdraw() {
const balance = parseFloat(this.data.balance)
if (balance < 1) {
wx.showToast({ title: '余额不足1元暂无法提现', icon: 'none' })
return
}
const pct = this.data.withdrawFeePct || 0
const feeYuan = (balance * pct / 100).toFixed(2)
const actualYuan = (balance - balance * pct / 100).toFixed(2)
this.setData({
showWithdrawDialog: true,
withdrawAmountInput: this.data.balance,
withdrawFeeYuan: feeYuan,
withdrawActualYuan: actualYuan,
withdrawError: ''
})
},
/** 关闭提现弹框 */
closeWithdrawDialog() {
this.setData({
showWithdrawDialog: false,
withdrawError: ''
})
},
/** 输入金额:实时计算手续费与实际到账 */
onWithdrawInput(e) {
const raw = e.detail.value
const val = parseFloat(raw)
const pct = this.data.withdrawFeePct || 0
let feeYuan = '0.00'
let actualYuan = '0.00'
if (raw !== '' && !isNaN(val) && val >= 0) {
const fee = val * pct / 100
feeYuan = fee.toFixed(2)
actualYuan = (val - fee).toFixed(2)
}
this.setData({
withdrawAmountInput: raw,
withdrawFeeYuan: feeYuan,
withdrawActualYuan: actualYuan,
withdrawError: ''
})
},
/** 确认提现(使用用户填写的金额) */
confirmWithdraw() {
const balance = parseFloat(this.data.balance)
const val = parseFloat(this.data.withdrawAmountInput)
const minYuan = parseFloat(this.data.withdrawMinYuan) || 1
const pct = this.data.withdrawFeePct || 0
if (isNaN(val)) {
this.setData({ withdrawError: '请输入正确的金额' })
return
}
if (val < 1) {
this.setData({ withdrawError: '单次提现金额至少 1 元' })
return
}
if (val > balance) {
this.setData({ withdrawError: '不可超过当前可提现金额' })
return
}
const actualYuan = val - val * pct / 100
if (actualYuan < minYuan) {
this.setData({ withdrawError: `实际到账金额不得低于最低提现金额 ¥${minYuan.toFixed(2)}` })
return
}
const amountFen = Math.floor(val * 100)
this.setData({ withdrawError: '' })
request({
url: '/api/distribution/withdraw',
method: 'POST',
data: { amountFen },
success: (r) => {
const payload = r && r.data
if (payload && payload.code === 200) {
const msg = payload.msg || payload.message || ''
wx.showToast({ title: '申请已提交', icon: 'success' })
this.setData({ showWithdrawDialog: false })
this.loadStats()
// 免审核且已自动发起微信转账时,自动进入提现记录页
if (msg.indexOf('已自动发起') !== -1) {
setTimeout(() => {
wx.navigateTo({ url: '/pages/promo/withdrawals' })
}, 500)
}
} else {
const errMsg = (payload && (payload.msg || payload.message)) || '申请失败,请稍后重试'
this.setData({ withdrawError: errMsg })
}
}
})
},
/** 查看提现记录 */
goToWithdrawHistory() {
wx.navigateTo({ url: '/pages/promo/withdrawals' })
},
/** 生成海报 */
generatePoster() {
wx.navigateTo({ url: '/pages/promo/poster' })
},
/** 分享到朋友圈:引导用户使用右上角菜单 */
shareToTimeline() {
wx.showToast({ title: '请点击右上角 ··· 选择「分享到朋友圈」', icon: 'none', duration: 2500 })
},
/** 分享给好友 */
onShareAppMessage() {
const { getSharePathByScope } = require('../../utils/share')
return {
title: '神仙团队性格测试 - 发现你的内在潜能',
path: getSharePathByScope('/pages/index/index')
}
},
/** 分享到朋友圈 */
onShareTimeline() {
const { buildShareQuery } = require('../../utils/share')
return {
title: '神仙团队性格测试 - 发现你的内在潜能',
query: buildShareQuery()
}
}
})

View File

@@ -1,5 +1,5 @@
{
"navigationBarTitleText": "推广中心",
"navigationBarBackgroundColor": "#FFFFFF",
"navigationBarTextStyle": "black"
{
"navigationBarTitleText": "推广中心",
"navigationBarBackgroundColor": "#FFFFFF",
"navigationBarTextStyle": "black"
}

View File

@@ -1,219 +1,219 @@
<!--pages/promo/index.wxml-->
<view class="container">
<!-- 顶部统计卡片 -->
<view class="hero-card">
<view class="hero-header-row">
<view class="hero-left">
<view class="wallet-icon">
<text class="emoji">🧧</text>
</view>
<view class="title-text">
<text class="label">可提现金额</text>
<view class="badge">
<view class="dot"></view>
<text class="badge-text">{{commissionRate}}% 高额返利</text>
</view>
</view>
</view>
<view class="hero-right">
<text class="amount">¥{{balance}}</text>
<text class="amount-sub">累计: ¥{{totalEarned}} | 待审核: ¥{{pendingAmount}}</text>
</view>
</view>
<button class="withdraw-btn {{balance > 0 ? '' : 'disabled'}}" bindtap="handleWithdraw">
<text>{{balance > 0 ? '立即提现' : '暂无提现'}}</text>
</button>
<view class="record-link" bindtap="goToWithdrawHistory">
<text>查看提现记录</text>
<text class="arrow"></text>
</view>
</view>
<!-- 自定义提现金额弹框 -->
<view class="withdraw-mask" wx:if="{{showWithdrawDialog}}">
<view class="withdraw-dialog">
<view class="dialog-title">申请提现</view>
<view class="dialog-sub">可提现 ¥{{balance}},请输入本次提现金额</view>
<view class="amount-input-row">
<text class="currency">¥</text>
<input
class="amount-input"
type="digit"
focus="true"
value="{{withdrawAmountInput}}"
placeholder="至少 1.00 元"
bindinput="onWithdrawInput"
/>
</view>
<view class="amount-hint">
<text>本次最高可提 ¥{{balance}}</text>
</view>
<view class="fee-actual-row" wx:if="{{withdrawFeePct > 0}}">
<text>手续费 ¥{{withdrawFeeYuan}}</text>
<text class="actual">实际到账 ¥{{withdrawActualYuan}}</text>
</view>
<view class="error-text" wx:if="{{withdrawError}}">
<text>{{withdrawError}}</text>
</view>
<view class="dialog-actions">
<view class="btn cancel" bindtap="closeWithdrawDialog">取消</view>
<view class="btn confirm" bindtap="confirmWithdraw">确定</view>
</view>
</view>
</view>
<!-- 四格统计 -->
<view class="stats-grid">
<view class="stat-item">
<text class="stat-val">{{bindingCount}}</text>
<text class="stat-label">绑定中</text>
</view>
<view class="stat-item">
<text class="stat-val">{{paidCount}}</text>
<text class="stat-label">已付款</text>
</view>
<view class="stat-item">
<text class="stat-val highlight">{{expiringCount}}</text>
<text class="stat-label">即将过期</text>
</view>
<view class="stat-item">
<text class="stat-val">{{totalInvite}}</text>
<text class="stat-label">总邀请</text>
</view>
</view>
<!-- 收益规则 -->
<view class="section rule-section">
<view class="section-header">
<view class="header-icon red">
<text class="emoji-sm">🛡️</text>
</view>
<text class="section-title">推广收益规则</text>
</view>
<view class="rule-list">
<view class="rule-item">
<text class="rule-bullet">✨</text>
<text class="rule-text">用户绑定有效期为 <text class="highlight">{{bindingDays}}天</text>,期满自动解除</text>
</view>
<view class="rule-item">
<text class="rule-bullet">💰</text>
<text class="rule-text">单笔提现:最低 <text class="highlight">¥{{withdrawMinYuan}}</text></text>
<text class="rule-text" wx:if="{{withdrawMaxYuan}}">最高 <text class="highlight">¥{{withdrawMaxYuan}}</text></text>
<text class="rule-text" wx:else>不设上限</text>
</view>
<view class="rule-item" wx:if="{{withdrawFeePct > 0}}">
<text class="rule-bullet">📋</text>
<text class="rule-text">提现手续费:<text class="highlight">{{withdrawFeePct}}%</text></text>
</view>
</view>
</view>
<!-- 绑定用户列表 -->
<view class="section user-section">
<view class="section-header border-b">
<view class="header-left">
<text class="emoji-sm">👥</text>
<text class="section-title">绑定用户</text>
<text class="count-label">({{totalInvite}})</text>
</view>
</view>
<!-- Tab 切换 -->
<view class="tab-bar">
<view class="tab-item {{activeTab === 0 ? 'active' : ''}}" bindtap="switchTab" data-index="0">
绑定中 ({{bindingCount}})
</view>
<view class="tab-item {{activeTab === 1 ? 'active' : ''}}" bindtap="switchTab" data-index="1">
已付款 ({{paidCount}})
</view>
<view class="tab-item {{activeTab === 2 ? 'active' : ''}}" bindtap="switchTab" data-index="2">
已过期 ({{expiringCount}})
</view>
</view>
<!-- 空状态 -->
<view class="empty-state" wx:if="{{userList.length === 0}}">
<view class="empty-icon">
<text class="emoji-lg">🔍</text>
</view>
<text class="empty-text">目前还没有绑定的用户哦</text>
<text class="empty-sub">快去分享链接邀请好友吧</text>
</view>
<!-- 用户列表 -->
<view class="user-list" wx:else>
<view class="user-item" wx:for="{{userList}}" wx:key="id">
<!-- 头像 -->
<image wx:if="{{item.avatar}}" class="user-avatar" src="{{item.avatar}}" mode="aspectFill"/>
<view wx:else class="user-avatar user-avatar-placeholder">
<text class="user-avatar-letter">{{item.nickname ? item.nickname[0] : '?'}}</text>
</view>
<!-- 昵称 + 绑定时间 -->
<view class="user-info">
<text class="user-name">{{item.nickname || '微信用户'}}</text>
<text class="user-time">绑定于 {{item.createdAtStr}}</text>
</view>
<!-- 状态 / 剩余天数 -->
<view class="user-badge {{item.status === 'active' ? 'badge-active' : (item.status === 'expired' ? 'badge-expired' : 'badge-paid')}}">
<text wx:if="{{item.status === 'active'}}">{{item.remainDays}}天到期</text>
<text wx:elif="{{item.status === 'expired'}}">已过期</text>
<text wx:else>已付款</text>
</view>
</view>
<!-- 加载更多 / 没有更多 -->
<view class="list-footer" wx:if="{{listLoading}}">
<text class="list-footer-text">加载中...</text>
</view>
<view class="list-footer" wx:elif="{{listFinished && userList.length > 0}}">
<text class="list-footer-text">— 已加载全部 —</text>
</view>
</view>
</view>
<!-- 操作菜单 -->
<view class="menu-card">
<view class="menu-item" bindtap="generatePoster">
<view class="menu-icon-wrap rose">
<text class="emoji-sm">🎨</text>
</view>
<view class="menu-info">
<text class="menu-name">生成推广海报</text>
<text class="menu-desc">一键生成您的专属精美海报</text>
</view>
<text class="menu-arrow"></text>
</view>
<view class="menu-item" bindtap="shareToTimeline">
<view class="menu-icon-wrap emerald">
<text class="emoji-sm">💬</text>
</view>
<view class="menu-info">
<text class="menu-name">分享到朋友圈</text>
<text class="menu-desc">通过右上角菜单分享到朋友圈</text>
</view>
<text class="menu-arrow"></text>
</view>
<button class="menu-item share-btn" open-type="share">
<view class="menu-icon-wrap violet">
<text class="emoji-sm">📤</text>
</view>
<view class="menu-info">
<text class="menu-name">分享给好友</text>
<text class="menu-desc">通过系统面板发送给微信好友</text>
</view>
<text class="menu-arrow"></text>
</button>
</view>
<!-- 底部提示 -->
<view class="footer-tip">
<text>分享专属链接,好友通过链接点击后自动绑定</text>
<text>购买任意测评即可获得 <text class="highlight">90%</text> 的现金返利</text>
</view>
<view class="safe-bottom"></view>
</view>
<!--pages/promo/index.wxml-->
<view class="container">
<!-- 顶部统计卡片 -->
<view class="hero-card">
<view class="hero-header-row">
<view class="hero-left">
<view class="wallet-icon">
<text class="emoji">🧧</text>
</view>
<view class="title-text">
<text class="label">可提现金额</text>
<view class="badge">
<view class="dot"></view>
<text class="badge-text">{{commissionRate}}% 高额返利</text>
</view>
</view>
</view>
<view class="hero-right">
<text class="amount">¥{{balance}}</text>
<text class="amount-sub">累计: ¥{{totalEarned}} | 待审核: ¥{{pendingAmount}}</text>
</view>
</view>
<button class="withdraw-btn {{balance > 0 ? '' : 'disabled'}}" bindtap="handleWithdraw">
<text>{{balance > 0 ? '立即提现' : '暂无提现'}}</text>
</button>
<view class="record-link" bindtap="goToWithdrawHistory">
<text>查看提现记录</text>
<text class="arrow"></text>
</view>
</view>
<!-- 自定义提现金额弹框 -->
<view class="withdraw-mask" wx:if="{{showWithdrawDialog}}">
<view class="withdraw-dialog">
<view class="dialog-title">申请提现</view>
<view class="dialog-sub">可提现 ¥{{balance}},请输入本次提现金额</view>
<view class="amount-input-row">
<text class="currency">¥</text>
<input
class="amount-input"
type="digit"
focus="true"
value="{{withdrawAmountInput}}"
placeholder="至少 1.00 元"
bindinput="onWithdrawInput"
/>
</view>
<view class="amount-hint">
<text>本次最高可提 ¥{{balance}}</text>
</view>
<view class="fee-actual-row" wx:if="{{withdrawFeePct > 0}}">
<text>手续费 ¥{{withdrawFeeYuan}}</text>
<text class="actual">实际到账 ¥{{withdrawActualYuan}}</text>
</view>
<view class="error-text" wx:if="{{withdrawError}}">
<text>{{withdrawError}}</text>
</view>
<view class="dialog-actions">
<view class="btn cancel" bindtap="closeWithdrawDialog">取消</view>
<view class="btn confirm" bindtap="confirmWithdraw">确定</view>
</view>
</view>
</view>
<!-- 四格统计 -->
<view class="stats-grid">
<view class="stat-item">
<text class="stat-val">{{bindingCount}}</text>
<text class="stat-label">绑定中</text>
</view>
<view class="stat-item">
<text class="stat-val">{{paidCount}}</text>
<text class="stat-label">已付款</text>
</view>
<view class="stat-item">
<text class="stat-val highlight">{{expiringCount}}</text>
<text class="stat-label">即将过期</text>
</view>
<view class="stat-item">
<text class="stat-val">{{totalInvite}}</text>
<text class="stat-label">总邀请</text>
</view>
</view>
<!-- 收益规则 -->
<view class="section rule-section">
<view class="section-header">
<view class="header-icon red">
<text class="emoji-sm">🛡️</text>
</view>
<text class="section-title">推广收益规则</text>
</view>
<view class="rule-list">
<view class="rule-item">
<text class="rule-bullet">✨</text>
<text class="rule-text">用户绑定有效期为 <text class="highlight">{{bindingDays}}天</text>,期满自动解除</text>
</view>
<view class="rule-item">
<text class="rule-bullet">💰</text>
<text class="rule-text">单笔提现:最低 <text class="highlight">¥{{withdrawMinYuan}}</text></text>
<text class="rule-text" wx:if="{{withdrawMaxYuan}}">最高 <text class="highlight">¥{{withdrawMaxYuan}}</text></text>
<text class="rule-text" wx:else>不设上限</text>
</view>
<view class="rule-item" wx:if="{{withdrawFeePct > 0}}">
<text class="rule-bullet">📋</text>
<text class="rule-text">提现手续费:<text class="highlight">{{withdrawFeePct}}%</text></text>
</view>
</view>
</view>
<!-- 绑定用户列表 -->
<view class="section user-section">
<view class="section-header border-b">
<view class="header-left">
<text class="emoji-sm">👥</text>
<text class="section-title">绑定用户</text>
<text class="count-label">({{totalInvite}})</text>
</view>
</view>
<!-- Tab 切换 -->
<view class="tab-bar">
<view class="tab-item {{activeTab === 0 ? 'active' : ''}}" bindtap="switchTab" data-index="0">
绑定中 ({{bindingCount}})
</view>
<view class="tab-item {{activeTab === 1 ? 'active' : ''}}" bindtap="switchTab" data-index="1">
已付款 ({{paidCount}})
</view>
<view class="tab-item {{activeTab === 2 ? 'active' : ''}}" bindtap="switchTab" data-index="2">
已过期 ({{expiringCount}})
</view>
</view>
<!-- 空状态 -->
<view class="empty-state" wx:if="{{userList.length === 0}}">
<view class="empty-icon">
<text class="emoji-lg">🔍</text>
</view>
<text class="empty-text">目前还没有绑定的用户哦</text>
<text class="empty-sub">快去分享链接邀请好友吧</text>
</view>
<!-- 用户列表 -->
<view class="user-list" wx:else>
<view class="user-item" wx:for="{{userList}}" wx:key="id">
<!-- 头像 -->
<image wx:if="{{item.avatar}}" class="user-avatar" src="{{item.avatar}}" mode="aspectFill"/>
<view wx:else class="user-avatar user-avatar-placeholder">
<text class="user-avatar-letter">{{item.nickname ? item.nickname[0] : '?'}}</text>
</view>
<!-- 昵称 + 绑定时间 -->
<view class="user-info">
<text class="user-name">{{item.nickname || '微信用户'}}</text>
<text class="user-time">绑定于 {{item.createdAtStr}}</text>
</view>
<!-- 状态 / 剩余天数 -->
<view class="user-badge {{item.status === 'active' ? 'badge-active' : (item.status === 'expired' ? 'badge-expired' : 'badge-paid')}}">
<text wx:if="{{item.status === 'active'}}">{{item.remainDays}}天到期</text>
<text wx:elif="{{item.status === 'expired'}}">已过期</text>
<text wx:else>已付款</text>
</view>
</view>
<!-- 加载更多 / 没有更多 -->
<view class="list-footer" wx:if="{{listLoading}}">
<text class="list-footer-text">加载中...</text>
</view>
<view class="list-footer" wx:elif="{{listFinished && userList.length > 0}}">
<text class="list-footer-text">— 已加载全部 —</text>
</view>
</view>
</view>
<!-- 操作菜单 -->
<view class="menu-card">
<view class="menu-item" bindtap="generatePoster">
<view class="menu-icon-wrap rose">
<text class="emoji-sm">🎨</text>
</view>
<view class="menu-info">
<text class="menu-name">生成推广海报</text>
<text class="menu-desc">一键生成您的专属精美海报</text>
</view>
<text class="menu-arrow"></text>
</view>
<view class="menu-item" bindtap="shareToTimeline">
<view class="menu-icon-wrap emerald">
<text class="emoji-sm">💬</text>
</view>
<view class="menu-info">
<text class="menu-name">分享到朋友圈</text>
<text class="menu-desc">通过右上角菜单分享到朋友圈</text>
</view>
<text class="menu-arrow"></text>
</view>
<button class="menu-item share-btn" open-type="share">
<view class="menu-icon-wrap violet">
<text class="emoji-sm">📤</text>
</view>
<view class="menu-info">
<text class="menu-name">分享给好友</text>
<text class="menu-desc">通过系统面板发送给微信好友</text>
</view>
<text class="menu-arrow"></text>
</button>
</view>
<!-- 底部提示 -->
<view class="footer-tip">
<text>分享专属链接,好友通过链接点击后自动绑定</text>
<text>购买任意测评即可获得 <text class="highlight">90%</text> 的现金返利</text>
</view>
<view class="safe-bottom"></view>
</view>

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"navigationBarTitleText": "生成推广海报",
"navigationBarBackgroundColor": "#FFFFFF",
"navigationBarTextStyle": "black",
"usingComponents": {}
{
"navigationBarTitleText": "生成推广海报",
"navigationBarBackgroundColor": "#FFFFFF",
"navigationBarTextStyle": "black",
"usingComponents": {}
}

View File

@@ -1,16 +1,16 @@
<view class="container">
<view class="poster-preview">
<image wx:if="{{posterUrl}}" class="poster-img" src="{{posterUrl}}" mode="widthFix" />
<view wx:if="{{loading}}" class="loading-mask">
<view class="loading-spinner"></view>
<text class="loading-text">{{loadingText}}</text>
</view>
</view>
<view class="action-bar">
<button class="save-btn" bindtap="savePoster" disabled="{{loading || !posterUrl}}">
<text>保存海报分享</text>
</button>
<view class="tip-text">保存到相册后可分享至朋友圈</view>
<view class="safe-bottom"></view>
</view>
</view>
<view class="container">
<view class="poster-preview">
<image wx:if="{{posterUrl}}" class="poster-img" src="{{posterUrl}}" mode="widthFix" />
<view wx:if="{{loading}}" class="loading-mask">
<view class="loading-spinner"></view>
<text class="loading-text">{{loadingText}}</text>
</view>
</view>
<view class="action-bar">
<button class="save-btn" bindtap="savePoster" disabled="{{loading || !posterUrl}}">
<text>保存海报分享</text>
</button>
<view class="tip-text">保存到相册后可分享至朋友圈</view>
<view class="safe-bottom"></view>
</view>
</view>

View File

@@ -1,83 +1,83 @@
.container {
min-height: 100vh;
background-color: #f8fafc;
display: flex;
flex-direction: column;
align-items: center;
padding: 40rpx 0;
}
.poster-preview {
position: relative;
width: 600rpx;
min-height: 800rpx;
background-color: #fff;
border-radius: 32rpx;
box-shadow: 0 20rpx 50rpx rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.poster-img {
width: 100%;
display: block;
}
.loading-mask {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background-color: rgba(255, 255, 255, 0.9);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 10;
}
.loading-spinner {
width: 60rpx; height: 60rpx;
border: 4rpx solid #f3f4f6;
border-top: 4rpx solid #f43f5e;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 20rpx;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text { font-size: 24rpx; color: #64748b; }
.action-bar {
position: fixed;
bottom: 0; left: 0; right: 0;
background-color: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
padding: 40rpx;
display: flex;
flex-direction: column;
align-items: center;
border-radius: 40rpx 40rpx 0 0;
box-shadow: 0 -10rpx 30rpx rgba(0, 0, 0, 0.05);
}
.save-btn {
width: 100% !important;
height: 100rpx;
background: #f43f5e;
color: #fff;
border-radius: 24rpx;
font-size: 32rpx;
font-weight: bold;
box-shadow: 0 10rpx 20rpx rgba(244, 63, 94, 0.2);
margin-bottom: 20rpx;
}
.save-btn[disabled] {
background: #cbd5e1;
box-shadow: none;
}
.tip-text { font-size: 24rpx; color: #94a3b8; margin-bottom: 20rpx; }
.safe-bottom { height: env(safe-area-inset-bottom); }
.container {
min-height: 100vh;
background-color: #f8fafc;
display: flex;
flex-direction: column;
align-items: center;
padding: 40rpx 0;
}
.poster-preview {
position: relative;
width: 600rpx;
min-height: 800rpx;
background-color: #fff;
border-radius: 32rpx;
box-shadow: 0 20rpx 50rpx rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.poster-img {
width: 100%;
display: block;
}
.loading-mask {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background-color: rgba(255, 255, 255, 0.9);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 10;
}
.loading-spinner {
width: 60rpx; height: 60rpx;
border: 4rpx solid #f3f4f6;
border-top: 4rpx solid #f43f5e;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 20rpx;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text { font-size: 24rpx; color: #64748b; }
.action-bar {
position: fixed;
bottom: 0; left: 0; right: 0;
background-color: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
padding: 40rpx;
display: flex;
flex-direction: column;
align-items: center;
border-radius: 40rpx 40rpx 0 0;
box-shadow: 0 -10rpx 30rpx rgba(0, 0, 0, 0.05);
}
.save-btn {
width: 100% !important;
height: 100rpx;
background: #f43f5e;
color: #fff;
border-radius: 24rpx;
font-size: 32rpx;
font-weight: bold;
box-shadow: 0 10rpx 20rpx rgba(244, 63, 94, 0.2);
margin-bottom: 20rpx;
}
.save-btn[disabled] {
background: #cbd5e1;
box-shadow: none;
}
.tip-text { font-size: 24rpx; color: #94a3b8; margin-bottom: 20rpx; }
.safe-bottom { height: env(safe-area-inset-bottom); }

View File

@@ -1,236 +1,236 @@
// pages/promo/withdrawals.js - 提现记录列表页
const { request } = require('../../utils/request')
Page({
data: {
list: [],
page: 1,
pageSize: 10,
total: 0,
loading: false,
finished: false,
// 确认收款按钮 loading 状态(按单条)
confirmingId: null
},
onLoad() {
this.loadData(true)
},
// 加载提现记录列表reset 为 true 时重置分页)
loadData(reset = false) {
if (this.data.loading) return
if (!reset && this.data.finished) return
const page = reset ? 1 : this.data.page
this.setData({ loading: true })
request({
url: `/api/distribution/withdrawals?page=${page}&pageSize=${this.data.pageSize}`,
method: 'GET',
success: (res) => {
const payload = res && res.data
if (payload && payload.code === 200 && payload.data) {
const { list = [], total = 0 } = payload.data
const mapped = list.map(item => {
const statusCode = this._normalizeStatusCode(item.status)
const amountYuan = item.amountYuan != null
? item.amountYuan
: (item.amountFen != null ? (item.amountFen / 100).toFixed(2) : '')
const balanceAfterYuan = item.balanceAfterYuan != null
? item.balanceAfterYuan
: (item.balanceAfterFen != null ? (item.balanceAfterFen / 100).toFixed(2) : '')
const feeYuan = item.feeYuan != null
? item.feeYuan
: (item.feeFen != null ? (item.feeFen / 100).toFixed(2) : '0.00')
return {
...item,
statusCode,
amountYuan,
balanceAfterYuan,
feeYuan,
// 申请时间:用 createdAt
applyAtStr: this._fmtTime(item.createdAt),
// 处理时间:用 transferAt如有
handleAtStr: this._fmtTime(item.transferAt),
statusTagText: this._statusTagText(statusCode),
statusTagClass: this._statusTagClass(statusCode)
}
})
const newList = reset ? mapped : [...this.data.list, ...mapped]
this.setData({
list: newList,
total,
page: page + 1,
finished: newList.length >= total
})
}
},
complete: () => {
this.setData({ loading: false })
}
})
},
// 时间戳格式化
_fmtTime(ts) {
if (!ts) return ''
const d = new Date(ts * 1000)
if (isNaN(d.getTime())) return ''
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return `${y}-${m}-${day} ${hh}:${mm}`
},
// 将后端 status数字 / 中文 / 英文)统一转成数字枚举:
// 0=审核中1=已驳回2=待收款3=已收款4=已过期
_normalizeStatusCode(status) {
if (typeof status === 'number') return status
if (typeof status === 'string') {
const s = status.trim()
if (/^\d+$/.test(s)) return parseInt(s, 10)
switch (s) {
case 'pending':
case '审核中':
return 0
case 'rejected':
case '已驳回':
return 1
case '待收款':
return 2
case 'transferred':
case '已收款':
return 3
case '已过期':
return 4
default:
return 0
}
}
return 0
},
// 状态中文文案
_statusTagText(code) {
switch (code) {
case 0:
return '待审核'
case 1:
return '已驳回'
case 2:
return '待收款'
case 3:
return '已收款'
case 4:
return '已过期'
default:
return ''
}
},
// 顶部右侧小标签样式
_statusTagClass(code) {
switch (code) {
case 0:
return 'tag-pending'
case 1:
return 'tag-rejected'
case 2:
return 'tag-wait'
case 3:
return 'tag-transferred'
case 4:
return 'tag-expired'
default:
return ''
}
},
// 用户点击单条「确认收款」
handleConfirmReceipt(e) {
const id = e.currentTarget.dataset.id
if (!id || this.data.confirmingId === id) return
const item = (this.data.list || []).find(x => String(x.id) === String(id))
if (!item) {
wx.showToast({ title: '记录不存在', icon: 'none' })
return
}
// 兼容驼峰 / 下划线字段
const packageInfo = item.packageInfo || item.package_info
if (!packageInfo) {
wx.showToast({ title: '该提现单无法确认收款,请联系客服', icon: 'none' })
return
}
if (!wx.canIUse || !wx.canIUse('requestMerchantTransfer')) {
wx.showModal({
content: '当前微信版本过低,无法使用确认收款功能,请升级微信后重试',
showCancel: false
})
return
}
this.setData({ confirmingId: id })
wx.showLoading({ title: '正在调起确认收款...' })
// 获取 AppID优先记录里的其次运行时获取
let appId = item.appId || item.app_id
try {
if (!appId && wx.getAccountInfoSync) {
const info = wx.getAccountInfoSync()
appId = info && info.miniProgram && info.miniProgram.appId
}
} catch (err) {}
// 商户号:从记录中取(后端已写入 mch_id
const mchId = item.mchId || item.mch_id
wx.requestMerchantTransfer({
mchId,
appId,
// 文档里字段名是 package对应后台返回的 package_info
package: packageInfo,
success: (res) => {
console.log('requestMerchantTransfer success', res)
wx.showToast({ title: '已调起收款确认', icon: 'none', duration: 2000 })
// 主动查询转账状态并更新订单实现及时刷新参考商户单号查询https://pay.weixin.qq.com/doc/v3/merchant/4012716437
request({
url: '/api/distribution/withdrawals/query-transfer',
method: 'POST',
data: { id: item.id },
success: (r) => {
const p = r && r.data
if (p && p.code === 200 && p.data && p.data.status === 3) {
wx.showToast({ title: '已收款', icon: 'success' })
}
},
complete: () => {
this.loadData(true)
}
})
},
fail: (err) => {
console.error('requestMerchantTransfer fail', err)
wx.showToast({ title: '调起失败,请稍后重试', icon: 'none' })
},
complete: () => {
wx.hideLoading()
this.setData({ confirmingId: null })
}
})
},
onReachBottom() {
this.loadData(false)
}
})
// pages/promo/withdrawals.js - 提现记录列表页
const { request } = require('../../utils/request')
Page({
data: {
list: [],
page: 1,
pageSize: 10,
total: 0,
loading: false,
finished: false,
// 确认收款按钮 loading 状态(按单条)
confirmingId: null
},
onLoad() {
this.loadData(true)
},
// 加载提现记录列表reset 为 true 时重置分页)
loadData(reset = false) {
if (this.data.loading) return
if (!reset && this.data.finished) return
const page = reset ? 1 : this.data.page
this.setData({ loading: true })
request({
url: `/api/distribution/withdrawals?page=${page}&pageSize=${this.data.pageSize}`,
method: 'GET',
success: (res) => {
const payload = res && res.data
if (payload && payload.code === 200 && payload.data) {
const { list = [], total = 0 } = payload.data
const mapped = list.map(item => {
const statusCode = this._normalizeStatusCode(item.status)
const amountYuan = item.amountYuan != null
? item.amountYuan
: (item.amountFen != null ? (item.amountFen / 100).toFixed(2) : '')
const balanceAfterYuan = item.balanceAfterYuan != null
? item.balanceAfterYuan
: (item.balanceAfterFen != null ? (item.balanceAfterFen / 100).toFixed(2) : '')
const feeYuan = item.feeYuan != null
? item.feeYuan
: (item.feeFen != null ? (item.feeFen / 100).toFixed(2) : '0.00')
return {
...item,
statusCode,
amountYuan,
balanceAfterYuan,
feeYuan,
// 申请时间:用 createdAt
applyAtStr: this._fmtTime(item.createdAt),
// 处理时间:用 transferAt如有
handleAtStr: this._fmtTime(item.transferAt),
statusTagText: this._statusTagText(statusCode),
statusTagClass: this._statusTagClass(statusCode)
}
})
const newList = reset ? mapped : [...this.data.list, ...mapped]
this.setData({
list: newList,
total,
page: page + 1,
finished: newList.length >= total
})
}
},
complete: () => {
this.setData({ loading: false })
}
})
},
// 时间戳格式化
_fmtTime(ts) {
if (!ts) return ''
const d = new Date(ts * 1000)
if (isNaN(d.getTime())) return ''
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return `${y}-${m}-${day} ${hh}:${mm}`
},
// 将后端 status数字 / 中文 / 英文)统一转成数字枚举:
// 0=审核中1=已驳回2=待收款3=已收款4=已过期
_normalizeStatusCode(status) {
if (typeof status === 'number') return status
if (typeof status === 'string') {
const s = status.trim()
if (/^\d+$/.test(s)) return parseInt(s, 10)
switch (s) {
case 'pending':
case '审核中':
return 0
case 'rejected':
case '已驳回':
return 1
case '待收款':
return 2
case 'transferred':
case '已收款':
return 3
case '已过期':
return 4
default:
return 0
}
}
return 0
},
// 状态中文文案
_statusTagText(code) {
switch (code) {
case 0:
return '待审核'
case 1:
return '已驳回'
case 2:
return '待收款'
case 3:
return '已收款'
case 4:
return '已过期'
default:
return ''
}
},
// 顶部右侧小标签样式
_statusTagClass(code) {
switch (code) {
case 0:
return 'tag-pending'
case 1:
return 'tag-rejected'
case 2:
return 'tag-wait'
case 3:
return 'tag-transferred'
case 4:
return 'tag-expired'
default:
return ''
}
},
// 用户点击单条「确认收款」
handleConfirmReceipt(e) {
const id = e.currentTarget.dataset.id
if (!id || this.data.confirmingId === id) return
const item = (this.data.list || []).find(x => String(x.id) === String(id))
if (!item) {
wx.showToast({ title: '记录不存在', icon: 'none' })
return
}
// 兼容驼峰 / 下划线字段
const packageInfo = item.packageInfo || item.package_info
if (!packageInfo) {
wx.showToast({ title: '该提现单无法确认收款,请联系客服', icon: 'none' })
return
}
if (!wx.canIUse || !wx.canIUse('requestMerchantTransfer')) {
wx.showModal({
content: '当前微信版本过低,无法使用确认收款功能,请升级微信后重试',
showCancel: false
})
return
}
this.setData({ confirmingId: id })
wx.showLoading({ title: '正在调起确认收款...' })
// 获取 AppID优先记录里的其次运行时获取
let appId = item.appId || item.app_id
try {
if (!appId && wx.getAccountInfoSync) {
const info = wx.getAccountInfoSync()
appId = info && info.miniProgram && info.miniProgram.appId
}
} catch (err) {}
// 商户号:从记录中取(后端已写入 mch_id
const mchId = item.mchId || item.mch_id
wx.requestMerchantTransfer({
mchId,
appId,
// 文档里字段名是 package对应后台返回的 package_info
package: packageInfo,
success: (res) => {
console.log('requestMerchantTransfer success', res)
wx.showToast({ title: '已调起收款确认', icon: 'none', duration: 2000 })
// 主动查询转账状态并更新订单实现及时刷新参考商户单号查询https://pay.weixin.qq.com/doc/v3/merchant/4012716437
request({
url: '/api/distribution/withdrawals/query-transfer',
method: 'POST',
data: { id: item.id },
success: (r) => {
const p = r && r.data
if (p && p.code === 200 && p.data && p.data.status === 3) {
wx.showToast({ title: '已收款', icon: 'success' })
}
},
complete: () => {
this.loadData(true)
}
})
},
fail: (err) => {
console.error('requestMerchantTransfer fail', err)
wx.showToast({ title: '调起失败,请稍后重试', icon: 'none' })
},
complete: () => {
wx.hideLoading()
this.setData({ confirmingId: null })
}
})
},
onReachBottom() {
this.loadData(false)
}
})

View File

@@ -1,5 +1,5 @@
{
"navigationBarTitleText": "提现记录",
"usingComponents": {}
}
{
"navigationBarTitleText": "提现记录",
"usingComponents": {}
}

View File

@@ -1,77 +1,77 @@
<!-- pages/promo/withdrawals.wxml - 提现记录列表 -->
<view class="page">
<!-- 顶部说明 -->
<!-- 空状态 -->
<view class="empty" wx:if="{{!loading && list.length === 0}}">
<view class="empty-icon">
<text>📭</text>
</view>
<text class="empty-text">暂无提现记录</text>
<text class="empty-sub">成功发起提现后,将在这里展示进度</text>
</view>
<!-- 列表(参考碎片时间提现列表样式结构) -->
<view class="list" wx:else>
<view class="item" wx:for="{{list}}" wx:key="id">
<!-- 顶部:单号 + 状态标签 -->
<view class="item-header">
<text class="order-no">单号:{{item.out_bill_no}}</text>
<view class="status-tag {{item.statusTagClass}}">
<text>{{item.statusTagText}}</text>
</view>
</view>
<!-- 中部:金额 + 手续费 + 提现后余额 -->
<view class="item-top">
<view class="amount-block">
<text class="amount">¥{{item.amountYuan}}</text>
</view>
<view class="fee-block" wx:if="{{item.feeYuan && item.feeYuan !== '0.00'}}">
<text class="fee-label">手续费</text>
<text class="fee-value">¥{{item.feeYuan}}</text>
</view>
<view class="balance-block" wx:if="{{item.balanceAfterYuan}}">
<text class="balance-label">提现后余额</text>
<text class="balance-value">¥{{item.balanceAfterYuan}}</text>
</view>
</view>
<!-- 时间信息 -->
<view class="time-block">
<text class="time-row">申请时间:{{item.applyAtStr}}</text>
<text class="time-row" wx:if="{{item.handleAtStr}}">处理时间:{{item.handleAtStr}}</text>
</view>
<!-- 拒绝原因 -->
<view class="reject-box" wx:if="{{item.status === 'rejected' && item.auditNote}}">
<text class="reject-title">拒绝原因:</text>
<text class="reject-text">{{item.auditNote}}</text>
</view>
<!-- 每条记录自己的确认收款按钮:只有「待收款」(statusCode=2) 需要用户确认 -->
<view class="confirm-wrapper" wx:if="{{item.statusCode === 2}}">
<button
class="confirm-btn"
bindtap="handleConfirmReceipt"
data-id="{{item.id}}"
loading="{{confirmingId === item.id}}"
disabled="{{confirmingId === item.id}}"
>
确认收款
</button>
</view>
</view>
<view class="footer" wx:if="{{loading}}">
<text class="footer-text">加载中...</text>
</view>
<view class="footer" wx:elif="{{finished}}">
<text class="footer-text">— 已加载全部 —</text>
</view>
</view>
<view class="safe-bottom"></view>
</view>
<!-- pages/promo/withdrawals.wxml - 提现记录列表 -->
<view class="page">
<!-- 顶部说明 -->
<!-- 空状态 -->
<view class="empty" wx:if="{{!loading && list.length === 0}}">
<view class="empty-icon">
<text>📭</text>
</view>
<text class="empty-text">暂无提现记录</text>
<text class="empty-sub">成功发起提现后,将在这里展示进度</text>
</view>
<!-- 列表(参考碎片时间提现列表样式结构) -->
<view class="list" wx:else>
<view class="item" wx:for="{{list}}" wx:key="id">
<!-- 顶部:单号 + 状态标签 -->
<view class="item-header">
<text class="order-no">单号:{{item.out_bill_no}}</text>
<view class="status-tag {{item.statusTagClass}}">
<text>{{item.statusTagText}}</text>
</view>
</view>
<!-- 中部:金额 + 手续费 + 提现后余额 -->
<view class="item-top">
<view class="amount-block">
<text class="amount">¥{{item.amountYuan}}</text>
</view>
<view class="fee-block" wx:if="{{item.feeYuan && item.feeYuan !== '0.00'}}">
<text class="fee-label">手续费</text>
<text class="fee-value">¥{{item.feeYuan}}</text>
</view>
<view class="balance-block" wx:if="{{item.balanceAfterYuan}}">
<text class="balance-label">提现后余额</text>
<text class="balance-value">¥{{item.balanceAfterYuan}}</text>
</view>
</view>
<!-- 时间信息 -->
<view class="time-block">
<text class="time-row">申请时间:{{item.applyAtStr}}</text>
<text class="time-row" wx:if="{{item.handleAtStr}}">处理时间:{{item.handleAtStr}}</text>
</view>
<!-- 拒绝原因 -->
<view class="reject-box" wx:if="{{item.status === 'rejected' && item.auditNote}}">
<text class="reject-title">拒绝原因:</text>
<text class="reject-text">{{item.auditNote}}</text>
</view>
<!-- 每条记录自己的确认收款按钮:只有「待收款」(statusCode=2) 需要用户确认 -->
<view class="confirm-wrapper" wx:if="{{item.statusCode === 2}}">
<button
class="confirm-btn"
bindtap="handleConfirmReceipt"
data-id="{{item.id}}"
loading="{{confirmingId === item.id}}"
disabled="{{confirmingId === item.id}}"
>
确认收款
</button>
</view>
</view>
<view class="footer" wx:if="{{loading}}">
<text class="footer-text">加载中...</text>
</view>
<view class="footer" wx:elif="{{finished}}">
<text class="footer-text">— 已加载全部 —</text>
</view>
</view>
<view class="safe-bottom"></view>
</view>

View File

@@ -1,244 +1,244 @@
.page {
min-height: 100vh;
background: #f9fafb;
padding: 24rpx 32rpx 32rpx;
box-sizing: border-box;
}
.header {
margin-bottom: 24rpx;
}
.title {
font-size: 32rpx;
font-weight: 700;
color: #111827;
}
.sub-title {
margin-top: 6rpx;
font-size: 24rpx;
color: #6b7280;
}
.empty {
margin-top: 80rpx;
display: flex;
flex-direction: column;
align-items: center;
color: #9ca3af;
}
.empty-icon {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
background: #f3f4f6;
display: flex;
align-items: center;
justify-content: center;
font-size: 56rpx;
margin-bottom: 16rpx;
}
.empty-text {
font-size: 28rpx;
margin-bottom: 4rpx;
color: #4b5563;
}
.empty-sub {
font-size: 24rpx;
color: #9ca3af;
}
.list {
margin-top: 12rpx;
}
.item {
background: #ffffff;
border-radius: 24rpx;
padding: 24rpx 28rpx;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 16rpx rgba(15, 23, 42, 0.04);
}
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
}
.order-no {
font-size: 24rpx;
color: #6b7280;
}
.status-tag {
padding: 6rpx 16rpx;
border-radius: 999rpx;
font-size: 22rpx;
}
.tag-pending {
background: #e5e7eb;
color: #4b5563;
}
.tag-approved {
background: #dbeafe;
color: #1d4ed8;
}
.tag-wait {
background: #dbeafe;
color: #1d4ed8;
}
.tag-rejected {
background: #fee2e2;
color: #b91c1c;
}
.tag-transferred {
background: #dcfce7;
color: #15803d;
}
.tag-expired {
background: #f3f4f6;
color: #9ca3af;
}
.item-top {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.amount-block {
display: flex;
flex-direction: column;
gap: 6rpx;
}
.amount {
font-size: 32rpx;
font-weight: 700;
color: #111827;
}
.fee-block {
display: flex;
flex-direction: column;
gap: 4rpx;
}
.fee-label {
font-size: 22rpx;
color: #9ca3af;
}
.fee-value {
font-size: 26rpx;
color: #6b7280;
}
.balance-block {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.balance-label {
font-size: 22rpx;
color: #9ca3af;
}
.balance-value {
margin-top: 4rpx;
font-size: 26rpx;
color: #111827;
}
.time-block {
margin-top: 16rpx;
display: flex;
flex-direction: column;
gap: 6rpx;
}
.time-row {
font-size: 22rpx;
color: #6b7280;
}
.reject-box {
margin-top: 16rpx;
padding: 16rpx;
border-radius: 12rpx;
background: #fef2f2;
}
.reject-title {
font-size: 24rpx;
font-weight: 600;
color: #b91c1c;
}
.reject-text {
font-size: 24rpx;
color: #b91c1c;
}
.item-bottom {
margin-top: 10rpx;
}
.note {
font-size: 24rpx;
color: #6b7280;
}
.footer {
padding: 16rpx 0;
text-align: center;
}
.footer-text {
font-size: 24rpx;
color: #9ca3af;
}
.confirm-wrapper {
margin-top: 20rpx;
padding: 0 8rpx 4rpx;
}
.confirm-btn {
width: 100%;
height: 80rpx;
border-radius: 999rpx;
background: linear-gradient(135deg, #FF6B8A 0%, #FF8E53 100%);
color: #ffffff;
font-size: 30rpx;
font-weight: 600;
box-shadow: 0 10rpx 18rpx rgba(255, 107, 138, 0.35);
border: none;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
}
.confirm-btn[disabled] {
opacity: 0.7;
box-shadow: none;
}
.safe-bottom {
height: calc(32rpx + env(safe-area-inset-bottom));
}
.page {
min-height: 100vh;
background: #f9fafb;
padding: 24rpx 32rpx 32rpx;
box-sizing: border-box;
}
.header {
margin-bottom: 24rpx;
}
.title {
font-size: 32rpx;
font-weight: 700;
color: #111827;
}
.sub-title {
margin-top: 6rpx;
font-size: 24rpx;
color: #6b7280;
}
.empty {
margin-top: 80rpx;
display: flex;
flex-direction: column;
align-items: center;
color: #9ca3af;
}
.empty-icon {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
background: #f3f4f6;
display: flex;
align-items: center;
justify-content: center;
font-size: 56rpx;
margin-bottom: 16rpx;
}
.empty-text {
font-size: 28rpx;
margin-bottom: 4rpx;
color: #4b5563;
}
.empty-sub {
font-size: 24rpx;
color: #9ca3af;
}
.list {
margin-top: 12rpx;
}
.item {
background: #ffffff;
border-radius: 24rpx;
padding: 24rpx 28rpx;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 16rpx rgba(15, 23, 42, 0.04);
}
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
}
.order-no {
font-size: 24rpx;
color: #6b7280;
}
.status-tag {
padding: 6rpx 16rpx;
border-radius: 999rpx;
font-size: 22rpx;
}
.tag-pending {
background: #e5e7eb;
color: #4b5563;
}
.tag-approved {
background: #dbeafe;
color: #1d4ed8;
}
.tag-wait {
background: #dbeafe;
color: #1d4ed8;
}
.tag-rejected {
background: #fee2e2;
color: #b91c1c;
}
.tag-transferred {
background: #dcfce7;
color: #15803d;
}
.tag-expired {
background: #f3f4f6;
color: #9ca3af;
}
.item-top {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.amount-block {
display: flex;
flex-direction: column;
gap: 6rpx;
}
.amount {
font-size: 32rpx;
font-weight: 700;
color: #111827;
}
.fee-block {
display: flex;
flex-direction: column;
gap: 4rpx;
}
.fee-label {
font-size: 22rpx;
color: #9ca3af;
}
.fee-value {
font-size: 26rpx;
color: #6b7280;
}
.balance-block {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.balance-label {
font-size: 22rpx;
color: #9ca3af;
}
.balance-value {
margin-top: 4rpx;
font-size: 26rpx;
color: #111827;
}
.time-block {
margin-top: 16rpx;
display: flex;
flex-direction: column;
gap: 6rpx;
}
.time-row {
font-size: 22rpx;
color: #6b7280;
}
.reject-box {
margin-top: 16rpx;
padding: 16rpx;
border-radius: 12rpx;
background: #fef2f2;
}
.reject-title {
font-size: 24rpx;
font-weight: 600;
color: #b91c1c;
}
.reject-text {
font-size: 24rpx;
color: #b91c1c;
}
.item-bottom {
margin-top: 10rpx;
}
.note {
font-size: 24rpx;
color: #6b7280;
}
.footer {
padding: 16rpx 0;
text-align: center;
}
.footer-text {
font-size: 24rpx;
color: #9ca3af;
}
.confirm-wrapper {
margin-top: 20rpx;
padding: 0 8rpx 4rpx;
}
.confirm-btn {
width: 100%;
height: 80rpx;
border-radius: 999rpx;
background: linear-gradient(135deg, #FF6B8A 0%, #FF8E53 100%);
color: #ffffff;
font-size: 30rpx;
font-weight: 600;
box-shadow: 0 10rpx 18rpx rgba(255, 107, 138, 0.35);
border: none;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
}
.confirm-btn[disabled] {
opacity: 0.7;
box-shadow: none;
}
.safe-bottom {
height: calc(32rpx + env(safe-area-inset-bottom));
}