Files
soul-yongping/miniprogram/pages/referral/referral.js
卡若 a228911170 推广中心优化 + 用户资料功能
1. 推广中心优化:
   - 增加"待购买"、"已过期"统计
   - 访问量单独显示
   - 移除10元提现门槛,有收益即可提现
   - 复制文案去掉"专属邀请码"

2. 我的页面:
   - 支持获取微信头像(原生能力)
   - 支持获取微信昵称
   - 新增用户更新API

3. 后台API:
   - referral/data返回expiredCount和expiredUsers
   - 新增user/update接口同步用户信息
2026-01-29 11:35:56 +08:00

516 lines
15 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, // 已过期人数
// === 收益数据 ===
earnings: 0, // 已结算收益
pendingEarnings: 0, // 待结算收益
withdrawnEarnings: 0, // 已提现金额
shareRate: 90, // 分成比例90%
// === 统计数据 ===
referralCount: 0, // 总推荐人数
expiringCount: 0, // 即将过期人数
// 邀请码
referralCode: '',
// 绑定用户列表
showBindingList: true,
activeTab: 'active',
activeBindings: [],
convertedBindings: [],
expiredBindings: [],
currentBindings: [],
totalBindings: 0,
// 收益明细
earningsDetails: [],
// 海报
showPosterModal: false,
isGeneratingPoster: false
},
onLoad() {
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
this.initData()
},
onShow() {
this.initData()
},
// 初始化数据
async initData() {
const { isLoggedIn, userInfo } = app.globalData
if (isLoggedIn && userInfo) {
// 生成邀请码
const referralCode = userInfo.referralCode || 'SOUL' + (userInfo.id || Date.now().toString(36)).toUpperCase().slice(-6)
// 尝试从API获取真实数据
let realData = null
try {
const res = await app.request('/api/referral/data', {
method: 'GET',
data: { userId: userInfo.id }
})
if (res.success) {
realData = res.data
console.log('[Referral] 获取推广数据成功:', realData)
}
} catch (e) {
console.log('[Referral] 获取推广数据失败,使用本地数据')
}
// 使用真实数据或默认值
let activeBindings = realData?.activeUsers || []
let convertedBindings = realData?.convertedUsers || []
let expiredBindings = realData?.expiredUsers || []
// 兼容旧字段名
if (!activeBindings.length && realData?.activeBindings) {
activeBindings = realData.activeBindings
}
if (!convertedBindings.length && realData?.convertedBindings) {
convertedBindings = realData.convertedBindings
}
if (!expiredBindings.length && realData?.expiredBindings) {
expiredBindings = realData.expiredBindings
}
const expiringCount = activeBindings.filter(b => b.daysRemaining <= 7 && b.daysRemaining > 0).length
// 计算各类统计
const bindingCount = realData?.bindingCount || activeBindings.length
const paidCount = realData?.paidCount || convertedBindings.length
const expiredCount = realData?.expiredCount || expiredBindings.length
const unboughtCount = bindingCount // 绑定中但未付款的
// 格式化用户数据
const formatUser = (user, type) => ({
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) : '--',
commission: user.commission || 0,
orderAmount: user.orderAmount || 0
})
this.setData({
isLoggedIn: true,
userInfo,
// 核心可见数据
bindingCount,
visitCount: realData?.visitCount || 0,
paidCount,
unboughtCount,
expiredCount,
// 收益数据
earnings: realData?.earnings || 0,
pendingEarnings: realData?.pendingEarnings || 0,
withdrawnEarnings: realData?.withdrawnEarnings || 0,
shareRate: realData?.shareRate || 90,
// 统计
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 || []
})
}
},
// 切换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' })
})
},
// 生成推广海报
async generatePoster() {
wx.showLoading({ title: '生成中...' })
this.setData({ showPosterModal: true, isGeneratingPoster: true })
try {
const ctx = wx.createCanvasContext('promoPosterCanvas', this)
const { userInfo, earnings, referralCount, distributorShare } = this.data
const userId = userInfo?.id || ''
// 获取小程序码(带推荐人参数)
let qrcodeImage = null
try {
const scene = userId ? `ref=${userId.slice(0,20)}` : 'ref=soul'
const qrRes = await app.request('/api/miniprogram/qrcode', {
method: 'POST',
data: { scene, page: 'pages/index/index', width: 280 }
})
if (qrRes.success && qrRes.image) {
qrcodeImage = qrRes.image
}
} catch (e) {
console.log('[Poster] 获取小程序码失败,使用占位符')
}
// 海报尺寸 300x450
const width = 300
const height = 450
// 背景渐变
const grd = ctx.createLinearGradient(0, 0, 0, height)
grd.addColorStop(0, '#0f0c29')
grd.addColorStop(0.5, '#302b63')
grd.addColorStop(1, '#24243e')
ctx.setFillStyle(grd)
ctx.fillRect(0, 0, width, height)
// 顶部装饰
ctx.setFillStyle('#FFD700')
ctx.fillRect(0, 0, width, 5)
// 标题
ctx.setFillStyle('#FFD700')
ctx.setFontSize(20)
ctx.fillText('📚 Soul创业派对', 20, 45)
// 副标题
ctx.setFillStyle('rgba(255,255,255,0.8)')
ctx.setFontSize(12)
ctx.fillText('来自派对房的真实商业故事', 20, 70)
// 书籍介绍区域
ctx.setFillStyle('rgba(255,255,255,0.05)')
ctx.fillRect(15, 90, width - 30, 100)
ctx.setFillStyle('#ffffff')
ctx.setFontSize(14)
ctx.fillText('✨ 62个真实商业案例', 25, 115)
ctx.fillText('💡 私域运营实战经验', 25, 140)
ctx.fillText('🎯 从0到1创业方法论', 25, 165)
// 推广者信息
ctx.setFillStyle('#00CED1')
ctx.setFontSize(13)
ctx.fillText(`推荐人: ${userInfo?.nickname || '创业者'}`, 20, 220)
// 统计数据
ctx.setFillStyle('rgba(255,255,255,0.6)')
ctx.setFontSize(11)
ctx.fillText(`已推荐 ${referralCount} 位好友阅读`, 20, 245)
// 优惠信息
ctx.setFillStyle('rgba(255,215,0,0.15)')
ctx.fillRect(15, 265, width - 30, 50)
ctx.setFillStyle('#FFD700')
ctx.setFontSize(14)
ctx.fillText('🎁 专属福利', 25, 290)
ctx.setFillStyle('#ffffff')
ctx.setFontSize(12)
ctx.fillText('通过此码购买立享5%优惠', 25, 308)
// 底部区域
ctx.setFillStyle('rgba(0,206,209,0.1)')
ctx.fillRect(0, height - 80, width, 80)
// 底部提示
ctx.setFillStyle('#ffffff')
ctx.setFontSize(13)
ctx.fillText('长按识别 立即购买', 20, height - 50)
ctx.setFillStyle('rgba(255,255,255,0.6)')
ctx.setFontSize(11)
ctx.fillText('扫码立即阅读', 20, height - 28)
// 绘制小程序码
const drawQRCode = () => {
return new Promise((resolve) => {
if (qrcodeImage) {
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: () => {
ctx.drawImage(filePath, width - 75, height - 70, 60, 60)
resolve()
},
fail: () => {
this.drawQRPlaceholder(ctx, width, height)
resolve()
}
})
} else {
this.drawQRPlaceholder(ctx, width, height)
resolve()
}
})
}
await drawQRCode()
ctx.draw(true, () => {
wx.hideLoading()
this.setData({ isGeneratingPoster: false })
})
} catch (e) {
console.error('生成海报失败:', e)
wx.hideLoading()
wx.showToast({ title: '生成失败', icon: 'none' })
this.setData({ showPosterModal: false, isGeneratingPoster: false })
}
},
// 绘制小程序码占位符
drawQRPlaceholder(ctx, width, height) {
ctx.setFillStyle('#ffffff')
ctx.beginPath()
ctx.arc(width - 45, height - 40, 30, 0, Math.PI * 2)
ctx.fill()
ctx.setFillStyle('#00CED1')
ctx.setFontSize(9)
ctx.fillText('扫码', width - 52, height - 42)
ctx.fillText('购买', width - 52, height - 30)
},
// 关闭海报弹窗
closePosterModal() {
this.setData({ showPosterModal: false })
},
// 保存海报
savePoster() {
wx.canvasToTempFilePath({
canvasId: 'promoPosterCanvas',
success: (res) => {
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
wx.showToast({ title: '已保存到相册', icon: 'success' })
this.setData({ showPosterModal: false })
},
fail: (err) => {
if (err.errMsg.includes('auth deny')) {
wx.showModal({
title: '提示',
content: '需要相册权限才能保存海报',
confirmText: '去设置',
success: (res) => {
if (res.confirm) {
wx.openSetting()
}
}
})
} else {
wx.showToast({ title: '保存失败', icon: 'none' })
}
}
})
},
fail: () => {
wx.showToast({ title: '生成图片失败', icon: 'none' })
}
}, this)
},
// 阻止冒泡
stopPropagation() {},
// 分享到朋友圈
shareToMoments() {
const shareText = `🔥 发现一本超棒的创业实战书《Soul创业派对》\n\n💡 62个真实商业案例从私域运营到资源整合干货满满\n\n🎁 ${this.data.userInfo?.nickname || '卡若'} 推荐,通过海报扫码购买立享优惠!\n\n#创业派对 #私域运营 #商业案例`
wx.setClipboardData({
data: shareText,
success: () => {
wx.showModal({
title: '文案已复制',
content: '请打开微信朋友圈,粘贴分享文案,配合推广海报一起发布效果更佳',
showCancel: false,
confirmText: '知道了'
})
}
})
},
// 提现 - 直接到微信零钱(无门槛)
async handleWithdraw() {
const pendingEarnings = parseFloat(this.data.pendingEarnings) || 0
if (pendingEarnings <= 0) {
wx.showToast({ title: '暂无可提现收益', icon: 'none' })
return
}
// 确认提现
wx.showModal({
title: '确认提现',
content: `将提现 ¥${pendingEarnings.toFixed(2)} 到您的微信零钱`,
confirmText: '立即提现',
success: async (res) => {
if (res.confirm) {
await this.doWithdraw(pendingEarnings)
}
}
})
},
// 执行提现
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/withdraw', {
method: 'POST',
data: { userId, amount }
})
wx.hideLoading()
if (res.success) {
wx.showModal({
title: '提现成功 🎉',
content: `¥${amount.toFixed(2)} 已到账您的微信零钱`,
showCancel: false,
confirmText: '好的'
})
// 刷新数据
this.initData()
} else {
if (res.needBind) {
wx.showModal({
title: '需要绑定微信',
content: '请先在设置中绑定微信账号后再提现',
confirmText: '去绑定',
success: (modalRes) => {
if (modalRes.confirm) {
wx.navigateTo({ url: '/pages/settings/settings' })
}
}
})
} else {
wx.showToast({ title: res.error || '提现失败', icon: 'none' })
}
}
} 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) {
wx.showToast({ title: '自动提现功能开发中', icon: 'none' })
} else {
wx.showToast({ title: '通知设置开发中', icon: 'none' })
}
}
})
},
// 分享 - 带推荐码
onShareAppMessage() {
return {
title: '📚 Soul创业派对 - 来自派对房的真实商业故事',
path: `/pages/index/index?ref=${this.data.referralCode}`,
imageUrl: '/assets/share-cover.png'
}
},
// 分享到朋友圈
onShareTimeline() {
return {
title: `Soul创业派对 - 62个真实商业案例`,
query: `ref=${this.data.referralCode}`
}
},
goBack() {
wx.navigateBack()
},
// 格式化日期
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}`
}
})