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

939 lines
34 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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%
minWithdrawAmount: 10, // 最低提现金额(从后端获取)
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()
},
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,
// 统计
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 (availableEarnings < minWithdrawAmount) {
wx.showToast({ title: `${minWithdrawAmount}元可提现`, 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(() => {
// 用户拒绝授权,不影响功能
})
}
}
}
})
},
// 分享 - 带推荐码
onShareAppMessage() {
console.log('[Referral] 分享给好友,推荐码:', this.data.referralCode)
return {
title: 'Soul创业派对 - 来自派对房的真实商业故事',
path: `/pages/index/index?ref=${this.data.referralCode}`
// 不设置 imageUrl使用小程序默认截图
// 如需自定义图片,请将图片放在 /assets/ 目录并配置路径
}
},
// 分享到朋友圈
onShareTimeline() {
console.log('[Referral] 分享到朋友圈,推荐码:', this.data.referralCode)
return {
title: `Soul创业派对 - 62个真实商业案例`,
query: `ref=${this.data.referralCode}`
// 不设置 imageUrl使用小程序默认截图
}
},
goBack() {
wx.navigateBack()
},
// 解析商品描述,获取书名和章节
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}`
}
})