939 lines
34 KiB
JavaScript
939 lines
34 KiB
JavaScript
/**
|
||
* 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}`
|
||
}
|
||
})
|