Files
soul-yongping/miniprogram/pages/referral/referral.js

944 lines
34 KiB
JavaScript
Raw Normal View History

/**
* Soul创业派对 - 分销中心页
*
* 可见数据
* - 绑定用户数当前有效绑定
* - 通过链接进的人数总访问量
* - 带来的付款人数已转化购买
* - 收益统计90%归分发者
*/
const app = getApp()
Page({
data: {
statusBarHeight: 44,
isLoggedIn: false,
userInfo: null,
// === 核心可见数据 ===
bindingCount: 0, // 绑定用户数(当前有效)
visitCount: 0, // 通过链接进的人数
paidCount: 0, // 带来的付款人数
unboughtCount: 0, // 待购买人数(绑定但未付款)
expiredCount: 0, // 已过期人数
// === 收益数据 ===
totalCommission: 0, // 累计佣金总额(所有获得的佣金)
availableEarnings: 0, // 可提现金额(未申请提现的佣金)- 字符串格式用于显示
availableEarningsNum: 0, // 可提现金额 - 数字格式用于判断
pendingWithdrawAmount: 0, // 待审核金额(已申请提现但未审核)
withdrawnEarnings: 0, // 已提现金额
earnings: 0, // 已结算收益(保留兼容)
pendingEarnings: 0, // 待结算收益(保留兼容)
shareRate: 90, // 分成比例90%),从 referral/data 或 config 获取
minWithdrawAmount: 10, // 最低提现金额,从 referral/data 获取
bindingDays: 30, // 绑定期天数,从 referral/data 获取
userDiscount: 5, // 好友购买优惠%,从 referral/data 获取
hasWechatId: false, // 是否已绑定微信号(未绑定时需引导去设置)
// === 统计数据 ===
referralCount: 0, // 总推荐人数
expiringCount: 0, // 即将过期人数
// 邀请码
referralCode: '',
// 绑定用户列表
showBindingList: true,
activeTab: 'active',
activeBindings: [],
convertedBindings: [],
expiredBindings: [],
currentBindings: [],
totalBindings: 0,
// 收益明细
earningsDetails: [],
// 海报
showPosterModal: false,
isGeneratingPoster: false,
posterQrSrc: '',
posterReferralLink: '',
posterNickname: '',
posterNicknameInitial: '',
posterCaseCount: 62,
},
onLoad() {
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
this.initData()
// 启用分享到朋友圈(需同时有 onShareAppMessage 和 onShareTimelinemenus 在 Android 支持iOS 为 Beta
wx.showShareMenu({ menus: ['shareAppMessage', 'shareTimeline'] })
},
onShow() {
// 从设置页返回时同步微信号绑定状态,便于提现按钮立即更新
const hasWechatId = !!(app.globalData.userInfo?.wechat || app.globalData.userInfo?.wechatId || wx.getStorageSync('user_wechat'))
this.setData({ hasWechatId })
this.initData()
},
// 初始化数据
async initData() {
const { isLoggedIn, userInfo } = app.globalData
if (isLoggedIn && userInfo) {
// 显示加载提示
wx.showLoading({
title: '加载中...',
mask: true // 防止触摸穿透
})
// 生成邀请码
const referralCode = userInfo.referralCode || 'SOUL' + (userInfo.id || Date.now().toString(36)).toUpperCase().slice(-6)
console.log('[Referral] 开始加载分销数据userId:', userInfo.id)
// 从API获取真实数据
let realData = null
try {
// app.request 第一个参数是 URL 字符串(会自动拼接 baseUrl
const res = await app.request('/api/miniprogram/referral/data?userId=' + userInfo.id)
console.log('[Referral] API返回:', JSON.stringify(res).substring(0, 200))
if (res && res.success && res.data) {
realData = res.data
console.log('[Referral] ✅ 获取推广数据成功')
console.log('[Referral] - bindingCount:', realData.bindingCount)
console.log('[Referral] - paidCount:', realData.paidCount)
console.log('[Referral] - earnings:', realData.earnings)
console.log('[Referral] - expiringCount:', realData.stats?.expiringCount)
} else {
console.log('[Referral] ❌ API返回格式错误:', res?.error || 'unknown')
}
} catch (e) {
console.log('[Referral] ❌ API调用失败:', e.message || e)
console.log('[Referral] 错误详情:', e)
}
// 使用真实数据或默认值
let activeBindings = realData?.activeUsers || []
let convertedBindings = realData?.convertedUsers || []
let expiredBindings = realData?.expiredUsers || []
console.log('[Referral] activeBindings:', activeBindings.length)
console.log('[Referral] convertedBindings:', convertedBindings.length)
console.log('[Referral] expiredBindings:', expiredBindings.length)
// 计算即将过期的数量7天内
const expiringCount = realData?.stats?.expiringCount || activeBindings.filter(b => b.daysRemaining <= 7 && b.daysRemaining > 0).length
console.log('[Referral] expiringCount:', expiringCount)
// 计算各类统计
const bindingCount = realData?.bindingCount || activeBindings.length
const paidCount = realData?.paidCount || convertedBindings.length
const expiredCount = realData?.expiredCount || expiredBindings.length
const unboughtCount = bindingCount - paidCount // 绑定中但未付款的
// 格式化用户数据
const formatUser = (user, type) => {
const formatted = {
id: user.id,
nickname: user.nickname || '用户' + (user.id || '').slice(-4),
avatar: user.avatar,
status: type,
daysRemaining: user.daysRemaining || 0,
bindingDate: user.bindingDate ? this.formatDate(user.bindingDate) : '--',
expiryDate: user.expiryDate ? this.formatDate(user.expiryDate) : '--',
commission: (user.commission || 0).toFixed(2),
orderAmount: (user.orderAmount || 0).toFixed(2),
purchaseCount: user.purchaseCount || 0,
conversionDate: user.conversionDate ? this.formatDate(user.conversionDate) : '--'
}
console.log('[Referral] 格式化用户:', formatted.nickname, formatted.status, formatted.daysRemaining + '天')
return formatted
}
// 格式化金额(保留两位小数)
const formatMoney = (num) => {
return typeof num === 'number' ? num.toFixed(2) : '0.00'
}
// ✅ 可提现金额 = 累计佣金 - 已提现金额 - 待审核金额,且不低于 0防止数据不同步时出现负数
const totalCommissionNum = realData?.totalCommission || 0
const withdrawnNum = realData?.withdrawnEarnings || 0
const pendingWithdrawNum = realData?.pendingWithdrawAmount || 0
const availableEarningsNum = Math.max(0, totalCommissionNum - withdrawnNum - pendingWithdrawNum)
const minWithdrawAmount = realData?.minWithdrawAmount || 10
console.log('=== [Referral] 收益计算(完整版)===')
console.log('累计佣金 (totalCommission):', totalCommissionNum)
console.log('已提现金额 (withdrawnEarnings):', withdrawnNum)
console.log('待审核金额 (pendingWithdrawAmount):', pendingWithdrawNum)
console.log('可提现金额 = 累计 - 已提现 - 待审核 =', totalCommissionNum, '-', withdrawnNum, '-', pendingWithdrawNum, '=', availableEarningsNum)
console.log('最低提现金额 (minWithdrawAmount):', minWithdrawAmount)
console.log('按钮判断:', availableEarningsNum, '>=', minWithdrawAmount, '=', availableEarningsNum >= minWithdrawAmount)
console.log('✅ 按钮应该:', availableEarningsNum >= minWithdrawAmount ? '🟢 启用(绿色)' : '⚫ 禁用(灰色)')
const hasWechatId = !!(userInfo?.wechat || userInfo?.wechatId || wx.getStorageSync('user_wechat'))
this.setData({
isLoggedIn: true,
userInfo,
hasWechatId,
// 核心可见数据
bindingCount,
visitCount: realData?.visitCount || 0,
paidCount,
unboughtCount: expiringCount, // "即将过期"显示的是 expiringCount
expiredCount,
// 收益数据 - 格式化为两位小数
totalCommission: formatMoney(totalCommissionNum),
availableEarnings: formatMoney(availableEarningsNum), // ✅ 使用计算后的可提现金额
availableEarningsNum: availableEarningsNum, // ✅ 数字格式用于按钮判断
pendingWithdrawAmount: formatMoney(pendingWithdrawNum),
withdrawnEarnings: formatMoney(realData?.withdrawnEarnings || 0),
earnings: formatMoney(realData?.earnings || 0),
pendingEarnings: formatMoney(realData?.pendingEarnings || 0),
shareRate: realData?.shareRate ?? 90,
minWithdrawAmount: minWithdrawAmount,
bindingDays: realData?.bindingDays ?? 30,
userDiscount: realData?.userDiscount ?? 5,
// 统计
referralCount: realData?.referralCount || realData?.stats?.totalBindings || activeBindings.length + convertedBindings.length,
expiringCount,
referralCode,
activeBindings: activeBindings.map(u => formatUser(u, 'active')),
convertedBindings: convertedBindings.map(u => formatUser(u, 'converted')),
expiredBindings: expiredBindings.map(u => formatUser(u, 'expired')),
currentBindings: activeBindings.map(u => formatUser(u, 'active')),
totalBindings: activeBindings.length + convertedBindings.length + expiredBindings.length,
// 收益明细
earningsDetails: (realData?.earningsDetails || []).map(item => {
// 解析商品描述,获取书名和章节
const productInfo = this.parseProductDescription(item.description, item.productType)
return {
id: item.id,
productType: item.productType,
bookTitle: productInfo.bookTitle,
chapterTitle: productInfo.chapterTitle,
commission: (item.commission || 0).toFixed(2),
payTime: item.payTime ? this.formatDate(item.payTime) : '--',
buyerNickname: item.buyerNickname || '用户',
buyerAvatar: item.buyerAvatar
}
})
})
console.log('[Referral] ✅ 数据设置完成')
console.log('[Referral] - 绑定中:', this.data.bindingCount)
console.log('[Referral] - 即将过期:', this.data.expiringCount)
console.log('[Referral] - 收益:', this.data.earnings)
console.log('=== [Referral] 按钮状态验证 ===')
console.log('累计佣金 (totalCommission):', this.data.totalCommission)
console.log('待审核金额 (pendingWithdrawAmount):', this.data.pendingWithdrawAmount)
console.log('可提现金额 (availableEarnings 显示):', this.data.availableEarnings)
console.log('可提现金额 (availableEarningsNum 判断):', this.data.availableEarningsNum, typeof this.data.availableEarningsNum)
console.log('最低提现金额 (minWithdrawAmount):', this.data.minWithdrawAmount, typeof this.data.minWithdrawAmount)
console.log('按钮启用条件:', this.data.availableEarningsNum, '>=', this.data.minWithdrawAmount, '=', this.data.availableEarningsNum >= this.data.minWithdrawAmount)
console.log('✅ 最终结果: 按钮应该', this.data.availableEarningsNum >= this.data.minWithdrawAmount ? '🟢 启用' : '⚫ 禁用')
// 隐藏加载提示
wx.hideLoading()
} else {
// 未登录时也隐藏loading
this.setData({ isLoading: false })
}
},
// 切换Tab
switchTab(e) {
const tab = e.currentTarget.dataset.tab
let currentBindings = []
if (tab === 'active') {
currentBindings = this.data.activeBindings
} else if (tab === 'converted') {
currentBindings = this.data.convertedBindings
} else {
currentBindings = this.data.expiredBindings
}
this.setData({ activeTab: tab, currentBindings })
},
// 切换绑定列表显示
toggleBindingList() {
this.setData({ showBindingList: !this.data.showBindingList })
},
// 复制邀请链接
copyLink() {
const link = `https://soul.quwanzhi.com/?ref=${this.data.referralCode}`
wx.setClipboardData({
data: link,
success: () => wx.showToast({ title: '链接已复制', icon: 'success' })
})
},
// 分享到朋友圈 - 1:1 迁移 Next.js 的 handleShareToWechat
shareToWechat() {
const { referralCode } = this.data
const referralLink = `https://soul.quwanzhi.com/?ref=${referralCode}`
// 与 Next.js 完全相同的文案
const shareText = `📖 推荐一本好书《一场SOUL的创业实验场》
这是卡若每天早上6-9点在Soul派对房分享的真实商业故事55个真实案例讲透创业的底层逻辑
👉 点击阅读: ${referralLink}
#创业 #商业思维 #Soul派对`
wx.setClipboardData({
data: shareText,
success: () => {
wx.showModal({
title: '朋友圈文案已复制!',
content: '打开微信 → 发朋友圈 → 粘贴即可',
showCancel: false,
confirmText: '知道了'
})
}
})
},
// 更多分享方式 - 1:1 迁移 Next.js 的 handleShare
handleMoreShare() {
const { referralCode } = this.data
const referralLink = `https://soul.quwanzhi.com/?ref=${referralCode}`
// 与 Next.js 完全相同的文案
const shareText = `我正在读《一场SOUL的创业实验场》每天6-9点的真实商业故事推荐给你${referralLink}`
wx.setClipboardData({
data: shareText,
success: () => {
wx.showToast({
title: '分享文案已复制',
icon: 'success',
duration: 2000
})
}
})
},
// 生成推广海报 - 1:1 对齐 Next.js 设计
async generatePoster() {
wx.showLoading({ title: '生成中...', mask: true })
this.setData({ showPosterModal: true, isGeneratingPoster: true })
try {
const { referralCode, userInfo } = this.data
const nickname = userInfo?.nickname || '用户'
const scene = `ref=${referralCode}`
console.log('[Poster] 请求小程序码, scene:', scene)
// 调用后端接口生成「小程序码」(官方 getwxacodeunlimit不再使用 H5 二维码
const res = await app.request('/api/miniprogram/qrcode', {
method: 'POST',
data: {
scene, // ref=XXXX
page: 'pages/index/index',
width: 280,
},
})
if (!res || !res.success || !res.image) {
console.error('[Poster] 生成小程序码失败:', res)
throw new Error(res?.error || '生成小程序码失败')
}
// 后端返回的是 data:image/png;base64,... 需要先写入本地临时文件,再作为 <image> 的 src
const base64Data = String(res.image).replace(/^data:image\/\w+;base64,/, '')
const fs = wx.getFileSystemManager()
const filePath = `${wx.env.USER_DATA_PATH}/poster_qrcode_${Date.now()}.png`
await new Promise((resolve, reject) => {
fs.writeFile({
filePath,
data: base64Data,
encoding: 'base64',
success: () => resolve(true),
fail: (err) => {
console.error('[Poster] 小程序码写入本地失败:', err)
reject(err)
},
})
})
console.log('[Poster] 小程序码已保存到本地:', filePath)
this.setData({
posterQrSrc: filePath,
posterReferralLink: '', // 小程序版本不再使用 H5 链接
posterNickname: nickname,
posterNicknameInitial: (nickname || '用').charAt(0),
isGeneratingPoster: false
})
wx.hideLoading()
} catch (e) {
console.error('[Poster] 生成二维码失败:', e)
wx.hideLoading()
wx.showToast({ title: '生成失败', icon: 'none' })
this.setData({ showPosterModal: false, isGeneratingPoster: false, posterQrSrc: '', posterReferralLink: '' })
}
},
// 绘制数据卡片
drawDataCard(ctx, x, y, width, height, value, label, color) {
// 卡片背景
ctx.setFillStyle('rgba(255,255,255,0.05)')
this.drawRoundRect(ctx, x, y, width, height, 8)
ctx.setStrokeStyle('rgba(255,255,255,0.1)')
ctx.setLineWidth(1)
ctx.stroke()
// 数值
ctx.setFillStyle(color)
ctx.setFontSize(24)
ctx.setTextAlign('center')
ctx.fillText(value, x + width / 2, y + 24)
// 标签
ctx.setFillStyle('rgba(255,255,255,0.5)')
ctx.setFontSize(10)
ctx.fillText(label, x + width / 2, y + 40)
},
// 绘制圆角矩形
drawRoundRect(ctx, x, y, width, height, radius) {
ctx.beginPath()
ctx.moveTo(x + radius, y)
ctx.lineTo(x + width - radius, y)
ctx.arc(x + width - radius, y + radius, radius, -Math.PI / 2, 0)
ctx.lineTo(x + width, y + height - radius)
ctx.arc(x + width - radius, y + height - radius, radius, 0, Math.PI / 2)
ctx.lineTo(x + radius, y + height)
ctx.arc(x + radius, y + height - radius, radius, Math.PI / 2, Math.PI)
ctx.lineTo(x, y + radius)
ctx.arc(x + radius, y + radius, radius, Math.PI, Math.PI * 1.5)
ctx.closePath()
ctx.fill()
},
// 光晕(替代 createRadialGradient用同心圆叠加模拟模糊
// centerX/centerY: 圆心坐标radius: 最大半径rgb: [r,g,b]maxAlpha: 最内层透明度
drawGlow(ctx, centerX, centerY, radius, rgb, maxAlpha = 0.10) {
const steps = 14
for (let i = steps; i >= 1; i--) {
const r = (radius * i) / steps
const alpha = (maxAlpha * i) / steps
ctx.setFillStyle(`rgba(${rgb[0]},${rgb[1]},${rgb[2]},${alpha})`)
ctx.beginPath()
ctx.arc(centerX, centerY, r, 0, Math.PI * 2)
ctx.fill()
}
},
// 绘制二维码支持Base64和URL两种格式
async drawQRCode(ctx, qrcodeImage, x, y, size) {
return new Promise((resolve) => {
if (!qrcodeImage) {
console.log('[Poster] 无二维码数据,绘制占位符')
this.drawQRPlaceholder(ctx, x, y, size)
resolve()
return
}
// 判断是Base64还是URL
if (qrcodeImage.startsWith('data:image') || !qrcodeImage.startsWith('http')) {
// Base64格式小程序码
console.log('[Poster] 绘制Base64二维码')
const fs = wx.getFileSystemManager()
const filePath = `${wx.env.USER_DATA_PATH}/qrcode_promo_${Date.now()}.png`
const base64Data = qrcodeImage.replace(/^data:image\/\w+;base64,/, '')
fs.writeFile({
filePath,
data: base64Data,
encoding: 'base64',
success: () => {
console.log('[Poster] ✅ Base64写入成功')
ctx.drawImage(filePath, x, y, size, size)
resolve()
},
fail: (err) => {
console.error('[Poster] ❌ Base64写入失败:', err)
this.drawQRPlaceholder(ctx, x, y, size)
resolve()
}
})
} else {
// URL格式第三方二维码
console.log('[Poster] 下载在线二维码:', qrcodeImage)
wx.downloadFile({
url: qrcodeImage,
success: (res) => {
if (res.statusCode === 200) {
console.log('[Poster] ✅ 二维码下载成功')
ctx.drawImage(res.tempFilePath, x, y, size, size)
resolve()
} else {
console.error('[Poster] ❌ 二维码下载失败, status:', res.statusCode)
this.drawQRPlaceholder(ctx, x, y, size)
resolve()
}
},
fail: (err) => {
console.error('[Poster] ❌ 二维码下载失败:', err)
this.drawQRPlaceholder(ctx, x, y, size)
resolve()
}
})
}
})
},
// 绘制小程序码占位符
drawQRPlaceholder(ctx, x, y, size) {
// 绘制占位符方框
ctx.setFillStyle('rgba(200,200,200,0.3)')
this.drawRoundRect(ctx, x, y, size, size, 8)
ctx.setFillStyle('#00CED1')
ctx.setFontSize(11)
ctx.setTextAlign('center')
ctx.fillText('小程序码', x + size / 2, y + size / 2)
},
// 关闭海报弹窗
closePosterModal() {
this.setData({ showPosterModal: false })
},
// 保存海报
savePoster() {
const { posterQrSrc } = this.data
if (!posterQrSrc) {
wx.showToast({ title: '二维码未生成', icon: 'none' })
return
}
wx.showLoading({ title: '保存中...', mask: true })
wx.downloadFile({
url: posterQrSrc,
success: (res) => {
if (res.statusCode !== 200) {
wx.hideLoading()
wx.showToast({ title: '下载失败', icon: 'none' })
return
}
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
wx.hideLoading()
wx.showToast({ title: '已保存到相册', icon: 'success' })
},
fail: (err) => {
wx.hideLoading()
if (String(err.errMsg || '').includes('auth deny')) {
wx.showModal({
title: '提示',
content: '需要相册权限才能保存二维码',
confirmText: '去设置',
success: (r) => {
if (r.confirm) wx.openSetting()
}
})
return
}
wx.showToast({ title: '保存失败', icon: 'none' })
}
})
},
fail: () => {
wx.hideLoading()
wx.showToast({ title: '下载失败', icon: 'none' })
}
})
},
// 预览二维码
previewPosterQr() {
const { posterQrSrc } = this.data
if (!posterQrSrc) return
wx.previewImage({ urls: [posterQrSrc] })
},
// 阻止冒泡
stopPropagation() {},
// 分享到朋友圈 - 随机文案
shareToMoments() {
// 10条随机文案基于书的内容
const shareTexts = [
`🔥 在派对房里听到的真实故事比虚构的小说精彩100倍\n\n电动车出租月入5万、私域一年赚1000万、一个人的公司月入10万...\n\n62个真实案例搜"Soul创业派对"小程序看全部!\n\n#创业 #私域 #商业`,
`💡 今天终于明白:会赚钱的人,都在用"流量杠杆"\n\n抖音、Soul、飞书...同一套内容,撬动不同平台的流量。\n\n《Soul创业派对》里的实战方法受用终身\n\n#流量 #副业 #创业派对`,
`📚 一个70后大健康私域一个月150万流水是怎么做到的\n\n答案在《Soul创业派对》第9章全是干货。\n\n搜小程序"Soul创业派对",我在里面等你\n\n#大健康 #私域运营 #真实案例`,
`🎯 "分钱不是分你的钱,是分不属于对方的钱"\n\n这句话改变了我对商业合作的认知。\n\n推荐《Soul创业派对》创业者必读\n\n#云阿米巴 #商业思维 #创业`,
`✨ 资源整合高手的社交方法论,在派对房里学到了\n\n"先让对方赚到钱,自己才能长久赚钱"\n\n这本《Soul创业派对》每章都是实战经验\n\n#资源整合 #社交 #创业故事`,
`🚀 AI工具推广一个隐藏的高利润赛道\n\n客单价高、复购率高、需求旺盛...\n\n《Soul创业派对》里的商业机会你发现了吗\n\n#AI #副业 #商业机会`,
`💰 美业整合:一个人的公司如何月入十万?\n\n不开店、不囤货、轻资产运营...\n\n《Soul创业派对》告诉你答案\n\n#美业 #轻创业 #月入十万`,
`🌟 3000万流水是怎么跑出来的\n\n不是靠运气,是靠系统。\n\n《Soul创业派对》里的电商底层逻辑值得反复看\n\n#电商 #创业 #商业系统`,
`📖 "人与人之间的关系,归根结底就三个东西:利益、情感、价值观"\n\n在派对房里聊出的金句都在《Soul创业派对》里\n\n#人性 #商业 #创业派对`,
`🔔 未来职业的三个方向:技术型、资源型、服务型\n\n你属于哪一种?\n\n《Soul创业派对》帮你找到答案\n\n#职业规划 #创业 #未来`
]
// 随机选择一条文案
const randomIndex = Math.floor(Math.random() * shareTexts.length)
const shareText = shareTexts[randomIndex]
wx.setClipboardData({
data: shareText,
success: () => {
wx.showModal({
title: '文案已复制',
content: '请打开微信朋友圈,粘贴分享文案,配合推广海报一起发布效果更佳!\n\n再次点击可获取新的随机文案',
showCancel: false,
confirmText: '去发朋友圈'
})
}
})
},
// 提现 - 直接到微信零钱
async handleWithdraw() {
const availableEarnings = this.data.availableEarningsNum || 0
const minWithdrawAmount = this.data.minWithdrawAmount || 10
const hasWechatId = this.data.hasWechatId
if (availableEarnings <= 0) {
wx.showToast({ title: '暂无可提现收益', icon: 'none' })
return
}
// 任意金额可提现,不再设最低限额
// 未绑定微信号时引导去设置
if (!hasWechatId) {
wx.showModal({
title: '请先绑定微信号',
content: '提现需先绑定微信号,便于到账核对。请到「设置」中绑定后再提现。',
confirmText: '去绑定',
cancelText: '取消',
success: (res) => {
if (res.confirm) wx.navigateTo({ url: '/pages/settings/settings' })
}
})
return
}
wx.showModal({
title: '确认提现',
content: `将提现 ¥${availableEarnings.toFixed(2)} 到您的微信零钱`,
confirmText: '立即提现',
success: async (res) => {
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: '提现中...' })
try {
const userId = app.globalData.userInfo?.id
if (!userId) {
wx.hideLoading()
wx.showToast({ title: '请先登录', icon: 'none' })
return
}
const res = await app.request('/api/miniprogram/withdraw', {
method: 'POST',
data: { userId, amount }
})
wx.hideLoading()
if (res.success) {
wx.showModal({
title: '提现申请已提交 ✅',
content: res.message || '正在审核中,通过后会自动到账您的微信零钱',
showCancel: false,
confirmText: '知道了'
})
// 刷新数据(此时待审核金额会增加,可提现金额会减少)
this.initData()
} else {
if (res.needBind || res.needBindWechat) {
wx.showModal({
title: res.needBindWechat ? '请先绑定微信号' : '需要绑定微信',
content: res.needBindWechat ? '请到「设置」中绑定微信号后再提现,便于到账核对。' : '请先在设置中绑定微信账号后再提现',
confirmText: '去绑定',
success: (modalRes) => {
if (modalRes.confirm) wx.navigateTo({ url: '/pages/settings/settings' })
}
})
} else {
wx.showToast({ title: res.message || res.error || '提现失败', icon: 'none', duration: 3000 })
}
}
} catch (e) {
wx.hideLoading()
console.error('[Referral] 提现失败:', e)
wx.showToast({ title: '提现失败,请重试', icon: 'none' })
}
},
// 显示通知
showNotification() {
wx.showToast({ title: '暂无新消息', icon: 'none' })
},
// 显示设置
showSettings() {
wx.showActionSheet({
itemList: ['自动提现设置', '收益通知设置'],
success: (res) => {
if (res.tapIndex === 0) {
this.showAutoWithdrawSettings()
} else {
this.showNotificationSettings()
}
}
})
},
// 自动提现设置
async showAutoWithdrawSettings() {
const app = getApp()
const { userInfo } = app.globalData
if (!userInfo) {
wx.showToast({ title: '请先登录', icon: 'none' })
return
}
// 获取当前设置
let autoWithdrawEnabled = wx.getStorageSync(`autoWithdraw_${userInfo.id}`) || false
let autoWithdrawThreshold = wx.getStorageSync(`autoWithdrawThreshold_${userInfo.id}`) || this.data.minWithdrawAmount || 10
wx.showModal({
title: '自动提现设置',
content: `当前状态:${autoWithdrawEnabled ? '已开启' : '已关闭'}\n自动提现阈值:¥${autoWithdrawThreshold}\n\n开启后,当可提现金额达到阈值时将自动发起提现申请。`,
confirmText: autoWithdrawEnabled ? '关闭' : '开启',
cancelText: '修改阈值',
success: (res) => {
if (res.confirm) {
// 切换开关
this.toggleAutoWithdraw(!autoWithdrawEnabled, autoWithdrawThreshold)
} else if (res.cancel) {
// 修改阈值
this.setAutoWithdrawThreshold(autoWithdrawEnabled, autoWithdrawThreshold)
}
}
})
},
// 切换自动提现开关
toggleAutoWithdraw(enabled, threshold) {
const app = getApp()
const { userInfo } = app.globalData
wx.setStorageSync(`autoWithdraw_${userInfo.id}`, enabled)
wx.showToast({
title: enabled ? '自动提现已开启' : '自动提现已关闭',
icon: 'success'
})
// 如果开启,检查当前金额是否达到阈值
if (enabled && this.data.availableEarningsNum >= threshold) {
wx.showModal({
title: '提示',
content: `当前可提现金额¥${this.data.availableEarnings}已达到阈值¥${threshold},是否立即提现?`,
success: (res) => {
if (res.confirm) {
this.handleWithdraw()
}
}
})
}
},
// 设置自动提现阈值
setAutoWithdrawThreshold(currentEnabled, currentThreshold) {
const minAmount = this.data.minWithdrawAmount || 10
wx.showModal({
title: '设置提现阈值',
content: `请输入自动提现金额阈值(最低¥${minAmount}`,
editable: true,
placeholderText: currentThreshold.toString(),
success: (res) => {
if (res.confirm && res.content) {
const threshold = parseFloat(res.content)
if (isNaN(threshold) || threshold < minAmount) {
wx.showToast({
title: `请输入不小于¥${minAmount}的金额`,
icon: 'none'
})
return
}
const app = getApp()
const { userInfo } = app.globalData
wx.setStorageSync(`autoWithdrawThreshold_${userInfo.id}`, threshold)
wx.showToast({
title: `阈值已设置为¥${threshold}`,
icon: 'success'
})
// 重新显示设置界面
setTimeout(() => {
this.showAutoWithdrawSettings()
}, 1500)
}
}
})
},
// 收益通知设置
showNotificationSettings() {
const app = getApp()
const { userInfo } = app.globalData
if (!userInfo) {
wx.showToast({ title: '请先登录', icon: 'none' })
return
}
// 获取当前设置
let notifyEnabled = wx.getStorageSync(`earningsNotify_${userInfo.id}`) !== false // 默认开启
wx.showModal({
title: '收益通知设置',
content: `当前状态:${notifyEnabled ? '已开启' : '已关闭'}\n\n开启后,将在有新收益时收到小程序通知提醒。`,
confirmText: notifyEnabled ? '关闭通知' : '开启通知',
success: (res) => {
if (res.confirm) {
const newState = !notifyEnabled
wx.setStorageSync(`earningsNotify_${userInfo.id}`, newState)
wx.showToast({
title: newState ? '收益通知已开启' : '收益通知已关闭',
icon: 'success'
})
// 如果开启,请求通知权限
if (newState) {
wx.requestSubscribeMessage({
tmplIds: [''] // 需要配置模板ID
}).catch(() => {
// 用户拒绝授权,不影响功能
})
}
}
}
})
},
// 分享 - 带推荐码(优先用页面数据,空时用 app.getMyReferralCode
onShareAppMessage() {
const ref = this.data.referralCode || app.getMyReferralCode()
console.log('[Referral] 分享给好友,推荐码:', ref)
return {
title: 'Soul创业派对 - 来自派对房的真实商业故事',
path: ref ? `/pages/index/index?ref=${ref}` : '/pages/index/index'
// 不设置 imageUrl使用小程序默认截图
// 如需自定义图片,请将图片放在 /assets/ 目录并配置路径
}
},
// 分享到朋友圈
onShareTimeline() {
const ref = this.data.referralCode || app.getMyReferralCode()
console.log('[Referral] 分享到朋友圈,推荐码:', ref)
return {
title: `Soul创业派对 - 62个真实商业案例`,
query: ref ? `ref=${ref}` : ''
// 不设置 imageUrl使用小程序默认截图
}
},
goBack() {
getApp().goBackOrToHome()
},
// 解析商品描述,获取书名和章节
parseProductDescription(description, productType) {
if (!description) {
return {
bookTitle: '未知商品',
chapterTitle: ''
}
}
// 匹配格式:《书名》- 章节名
const match = description.match(/《(.+?)》(?:\s*-\s*(.+))?/)
if (match) {
return {
bookTitle: match[1] || '未知书籍',
chapterTitle: match[2] || (productType === 'fullbook' ? '全书购买' : '')
}
}
// 如果匹配失败,直接返回原始描述
return {
bookTitle: description.split('-')[0] || description,
chapterTitle: description.split('-')[1] || ''
}
},
// 格式化日期
formatDate(dateStr) {
if (!dateStr) return '--'
const d = new Date(dateStr)
const month = (d.getMonth() + 1).toString().padStart(2, '0')
const day = d.getDate().toString().padStart(2, '0')
return `${month}-${day}`
}
})