删除多个不再使用的文件,包括二维码测试页面、底部菜单配置说明、图标对齐说明等,优化项目结构以提升可维护性。

This commit is contained in:
乘风
2026-02-04 15:25:42 +08:00
parent 09e2c02f9f
commit 722185da78
22 changed files with 1972 additions and 425 deletions

View File

@@ -49,7 +49,12 @@ Page({
// 海报
showPosterModal: false,
isGeneratingPoster: false
isGeneratingPoster: false,
posterQrSrc: '',
posterReferralLink: '',
posterNickname: '',
posterNicknameInitial: '',
posterCaseCount: 62
},
onLoad() {
@@ -68,19 +73,28 @@ Page({
// 生成邀请码
const referralCode = userInfo.referralCode || 'SOUL' + (userInfo.id || Date.now().toString(36)).toUpperCase().slice(-6)
// 尝试从API获取真实数据
console.log('[Referral] 开始加载分销数据userId:', userInfo.id)
// 从API获取真实数据
let realData = null
try {
const res = await app.request('/api/referral/data', {
method: 'GET',
data: { userId: userInfo.id }
})
if (res.success) {
// app.request 第一个参数是 URL 字符串(会自动拼接 baseUrl
const res = await app.request('/api/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] 获取推广数据成功:', realData)
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] 获取推广数据失败,使用本地数据')
console.log('[Referral] ❌ API调用失败:', e.message || e)
console.log('[Referral] 错误详情:', e)
}
// 使用真实数据或默认值
@@ -88,36 +102,41 @@ Page({
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
}
console.log('[Referral] activeBindings:', activeBindings.length)
console.log('[Referral] convertedBindings:', convertedBindings.length)
console.log('[Referral] expiredBindings:', expiredBindings.length)
const expiringCount = activeBindings.filter(b => b.daysRemaining <= 7 && b.daysRemaining > 0).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 // 绑定中但未付款的
const unboughtCount = bindingCount - paidCount // 绑定中但未付款的
// 格式化用户数据
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
})
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) : '--',
commission: (user.commission || 0).toFixed(2),
orderAmount: (user.orderAmount || 0).toFixed(2)
}
console.log('[Referral] 格式化用户:', formatted.nickname, formatted.status, formatted.daysRemaining + '天')
return formatted
}
// 格式化金额(保留两位小数)
const formatMoney = (num) => {
return typeof num === 'number' ? num.toFixed(2) : '0.00'
}
this.setData({
isLoggedIn: true,
@@ -127,13 +146,13 @@ Page({
bindingCount,
visitCount: realData?.visitCount || 0,
paidCount,
unboughtCount,
unboughtCount: expiringCount, // "即将过期"显示的是 expiringCount
expiredCount,
// 收益数据
earnings: realData?.earnings || 0,
pendingEarnings: realData?.pendingEarnings || 0,
withdrawnEarnings: realData?.withdrawnEarnings || 0,
// 收益数据 - 格式化为两位小数
earnings: formatMoney(realData?.earnings || 0),
pendingEarnings: formatMoney(realData?.pendingEarnings || 0),
withdrawnEarnings: formatMoney(realData?.withdrawnEarnings || 0),
shareRate: realData?.shareRate || 90,
// 统计
@@ -148,8 +167,19 @@ Page({
totalBindings: activeBindings.length + convertedBindings.length + expiredBindings.length,
// 收益明细
earningsDetails: realData?.earningsDetails || []
earningsDetails: (realData?.earningsDetails || []).map(item => ({
id: item.id,
productType: item.productType,
commission: (item.commission || 0).toFixed(2),
payTime: item.payTime ? this.formatDate(item.payTime) : '--',
buyerNickname: item.buyerNickname
}))
})
console.log('[Referral] ✅ 数据设置完成')
console.log('[Referral] - 绑定中:', this.data.bindingCount)
console.log('[Referral] - 即将过期:', this.data.expiringCount)
console.log('[Referral] - 收益:', this.data.earnings)
}
},
@@ -182,165 +212,202 @@ Page({
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 ctx = wx.createCanvasContext('promoPosterCanvas', this)
const { userInfo, earnings, referralCount, distributorShare } = this.data
const userId = userInfo?.id || ''
// 获取小程序码(带推荐人参数)
let qrcodeImage = null
try {
// scene格式ref=用户ID前20位
const scene = userId ? `ref=${userId.slice(0,20)}` : 'ref=soul'
console.log('[Poster] 请求小程序码, scene:', scene)
const qrRes = await app.request('/api/miniprogram/qrcode', {
method: 'POST',
data: {
scene,
page: 'pages/index/index',
width: 280
}
})
console.log('[Poster] 小程序码响应:', qrRes?.success, qrRes?.image?.length)
if (qrRes && qrRes.success && qrRes.image) {
qrcodeImage = qrRes.image
console.log('[Poster] 小程序码获取成功')
} else {
console.log('[Poster] 响应无效:', qrRes)
}
} catch (e) {
console.error('[Poster] 获取小程序码失败:', e)
// Next.js 的实现:只生成一个二维码图片(不做画布海报)
const { referralCode, userInfo } = this.data
const referralLink = `https://soul.quwanzhi.com/?ref=${referralCode}`
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=320x320&data=${encodeURIComponent(referralLink)}`
const nickname = userInfo?.nickname || '用户'
this.setData({
posterQrSrc: qrUrl,
posterReferralLink: referralLink,
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
}
// 海报尺寸 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)
// 判断是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()
}
})
}
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()
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(9)
ctx.fillText('扫码', width - 52, height - 42)
ctx.fillText('购买', width - 52, height - 30)
ctx.setFontSize(11)
ctx.setTextAlign('center')
ctx.fillText('小程序码', x + size / 2, y + size / 2)
},
// 关闭海报弹窗
@@ -350,37 +417,57 @@ Page({
// 保存海报
savePoster() {
wx.canvasToTempFilePath({
canvasId: 'promoPosterCanvas',
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' })
this.setData({ showPosterModal: false })
},
fail: (err) => {
if (err.errMsg.includes('auth deny')) {
wx.hideLoading()
if (String(err.errMsg || '').includes('auth deny')) {
wx.showModal({
title: '提示',
content: '需要相册权限才能保存海报',
content: '需要相册权限才能保存二维码',
confirmText: '去设置',
success: (res) => {
if (res.confirm) {
wx.openSetting()
}
success: (r) => {
if (r.confirm) wx.openSetting()
}
})
} else {
wx.showToast({ title: '保存失败', icon: 'none' })
return
}
wx.showToast({ title: '保存失败', icon: 'none' })
}
})
},
fail: () => {
wx.showToast({ title: '生成图片失败', icon: 'none' })
wx.hideLoading()
wx.showToast({ title: '下载失败', icon: 'none' })
}
}, this)
})
},
// 预览二维码
previewPosterQr() {
const { posterQrSrc } = this.data
if (!posterQrSrc) return
wx.previewImage({ urls: [posterQrSrc] })
},
// 阻止冒泡
@@ -523,18 +610,22 @@ Page({
// 分享 - 带推荐码
onShareAppMessage() {
console.log('[Referral] 分享给好友,推荐码:', this.data.referralCode)
return {
title: '📚 Soul创业派对 - 来自派对房的真实商业故事',
path: `/pages/index/index?ref=${this.data.referralCode}`,
imageUrl: '/assets/share-cover.png'
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使用小程序默认截图
}
},

View File

@@ -1,4 +1,6 @@
{
"usingComponents": {},
"navigationStyle": "custom"
"navigationStyle": "custom",
"enableShareAppMessage": true,
"enableShareTimeline": true
}

View File

@@ -2,34 +2,43 @@
<view class="page">
<!-- 导航栏 -->
<view class="nav-bar" style="padding-top: {{statusBarHeight}}px;">
<view class="nav-back" bindtap="goBack">
<text class="back-icon"></text>
</view>
<text class="nav-title">推广中心</text>
<view class="nav-right">
<view class="nav-btn" bindtap="showNotification">🔔</view>
<view class="nav-btn" bindtap="showSettings">⚙️</view>
<view class="nav-left">
<view class="nav-back" bindtap="goBack">
<image class="nav-icon" src="/assets/icons/chevron-left.svg" mode="aspectFit"></image>
</view>
<view class="nav-btn" bindtap="showNotification">
<image class="nav-icon" src="/assets/icons/bell.svg" mode="aspectFit"></image>
</view>
<view class="nav-btn" bindtap="showSettings">
<image class="nav-icon" src="/assets/icons/settings.svg" mode="aspectFit"></image>
</view>
</view>
<text class="nav-title">分销中心</text>
<view class="nav-right-placeholder"></view>
</view>
<view style="height: {{statusBarHeight + 44}}px;"></view>
<view class="content">
<!-- 过期提醒横幅 -->
<view class="expiring-banner" wx:if="{{expiringCount > 0}}">
<view class="banner-icon">⚠️</view>
<view class="banner-icon">
<image class="icon-bell-warning" src="/assets/icons/bell.svg" mode="aspectFit"></image>
</view>
<view class="banner-content">
<text class="banner-title">{{expiringCount}} 位用户绑定即将过期</text>
<text class="banner-desc">30天内未付款将解除绑定关系</text>
</view>
</view>
<!-- 收益卡片 -->
<!-- 收益卡片 - 对齐 Next.js -->
<view class="earnings-card">
<view class="earnings-bg"></view>
<view class="earnings-main">
<view class="earnings-header">
<view class="earnings-left">
<view class="wallet-icon">💰</view>
<view class="wallet-icon">
<image class="icon-wallet" src="/assets/icons/wallet.svg" mode="aspectFit"></image>
</view>
<view class="earnings-info">
<text class="earnings-label">累计收益</text>
<text class="commission-rate">{{shareRate}}% 返利</text>
@@ -40,58 +49,44 @@
<text class="pending-text">待结算: ¥{{pendingEarnings}}</text>
</view>
</view>
<view class="earnings-detail">
<text class="detail-item">已提现: ¥{{withdrawnEarnings}}</text>
</view>
<view class="withdraw-btn {{pendingEarnings <= 0 ? 'btn-disabled' : ''}}" bindtap="handleWithdraw">
{{pendingEarnings <= 0 ? '暂无收益' : '立即提现 ¥' + pendingEarnings}}
<view class="withdraw-btn {{earnings < 10 ? 'btn-disabled' : ''}}" bindtap="handleWithdraw">
{{earnings < 10 ? '满10元可提现' : '申请提现'}}
</view>
</view>
</view>
<!-- 核心数据统计(重点可见数据) -->
<!-- 数据统计 - 对齐 Next.js -->
<view class="stats-grid">
<view class="stat-card highlight">
<text class="stat-value brand">{{bindingCount}}</text>
<view class="stat-card">
<text class="stat-value">{{bindingCount}}</text>
<text class="stat-label">绑定中</text>
<text class="stat-tip">当前有效绑定</text>
</view>
<view class="stat-card highlight">
<text class="stat-value gold">{{paidCount}}</text>
<view class="stat-card">
<text class="stat-value">{{paidCount}}</text>
<text class="stat-label">已付款</text>
<text class="stat-tip">成功转化</text>
</view>
<view class="stat-card">
<text class="stat-value orange">{{unboughtCount}}</text>
<text class="stat-label">待购买</text>
<text class="stat-tip">绑定未付款</text>
<text class="stat-label">即将过期</text>
</view>
<view class="stat-card">
<text class="stat-value gray">{{expiredCount}}</text>
<text class="stat-label">已过期</text>
<text class="stat-tip">绑定已失效</text>
<text class="stat-value">{{referralCount}}</text>
<text class="stat-label">总邀请</text>
</view>
</view>
<!-- 访问量统计 -->
<view class="visit-stat">
<text class="visit-label">总访问量</text>
<text class="visit-value">{{visitCount}}</text>
<text class="visit-tip">人通过你的链接进入</text>
</view>
<!-- 推广规则 -->
<!-- 推广规则 - 顺序调整到前面 -->
<view class="rules-card">
<view class="rules-header">
<view class="rules-icon">📋</view>
<view class="rules-icon">
<image class="icon-alert" src="/assets/icons/alert-circle.svg" mode="aspectFit"></image>
</view>
<text class="rules-title">推广规则</text>
</view>
<view class="rules-list">
<text class="rule-item">• <text class="brand">链接带ID</text>:谁发的链接,进的人就绑谁</text>
<text class="rule-item">• <text class="brand">一级、一月</text>只有一级分销绑定有效期30天</text>
<text class="rule-item">• <text class="orange">长期不发</text>:别人发得多,过期后客户会被「抢走」</text>
<text class="rule-item">• <text class="gold">每天发</text>:持续发的人绑定续期,收益越来越高</text>
<text class="rule-item">• <text class="brand">{{shareRate}}%给分发</text>:好友付款后,你得 {{shareRate}}% 收益</text>
<text class="rule-item">• 好友通过你的链接购买,<text class="gold">立享5%优惠</text></text>
<text class="rule-item">• 好友成功付款后,你获得 <text class="brand">{{shareRate}}%</text> 收益</text>
<text class="rule-item">• 绑定期<text class="brand">30天</text>,期满未付款自动解除</text>
</view>
</view>
@@ -99,7 +94,7 @@
<view class="binding-card">
<view class="binding-header" bindtap="toggleBindingList">
<view class="binding-title">
<text class="binding-icon">👥</text>
<image class="binding-icon-img" src="/assets/icons/users.svg" mode="aspectFit"></image>
<text class="title-text">绑定用户</text>
<text class="binding-count">({{totalBindings}})</text>
</view>
@@ -166,58 +161,168 @@
</block>
</view>
<!-- 分享按钮 -->
<!-- 我的邀请码 - 移到绑定用户后面 -->
<view class="invite-card">
<view class="invite-header">
<text class="invite-title">我的邀请码</text>
<view class="invite-code-box">
<text class="invite-code">{{referralCode}}</text>
</view>
</view>
<text class="invite-tip">好友通过你的链接购买<text class="gold">立省5%</text>,你获得<text class="brand">{{shareRate}}%</text>收益</text>
</view>
<!-- 分享按钮 - 1:1 对齐 Next.js -->
<view class="share-section">
<view class="share-item" bindtap="generatePoster">
<view class="share-icon poster">🖼️</view>
<view class="share-icon poster">
<image class="icon-share-btn" src="/assets/icons/image.svg" mode="aspectFit"></image>
</view>
<view class="share-info">
<text class="share-title">生成推广海报</text>
<text class="share-desc">一键生成精美海报分享</text>
</view>
<text class="share-arrow"></text>
<image class="share-arrow-icon" src="/assets/icons/arrow-right.svg" mode="aspectFit"></image>
</view>
<button class="share-item share-btn-wechat" open-type="share">
<view class="share-icon wechat">💬</view>
<view class="share-info">
<text class="share-title">分享给好友</text>
<text class="share-desc">直接发送小程序卡片</text>
<view class="share-item" bindtap="shareToWechat">
<view class="share-icon wechat">
<image class="icon-share-btn" src="/assets/icons/message-circle.svg" mode="aspectFit"></image>
</view>
<text class="share-arrow">→</text>
</button>
<view class="share-item" bindtap="shareToMoments">
<view class="share-icon link">📝</view>
<view class="share-info">
<text class="share-title">复制朋友圈文案</text>
<text class="share-desc">一键复制推广文案</text>
<text class="share-title">分享到朋友圈</text>
<text class="share-desc">复制文案发朋友圈</text>
</view>
<text class="share-arrow"></text>
<image class="share-arrow-icon" src="/assets/icons/arrow-right.svg" mode="aspectFit"></image>
</view>
<view class="share-item" bindtap="handleMoreShare">
<view class="share-icon link">
<image class="icon-share-btn" src="/assets/icons/share.svg" mode="aspectFit"></image>
</view>
<view class="share-info">
<text class="share-title">更多分享方式</text>
<text class="share-desc">使用系统分享功能</text>
</view>
<image class="share-arrow-icon" src="/assets/icons/arrow-right.svg" mode="aspectFit"></image>
</view>
</view>
<!-- 收益明细 - 移到分享按钮后面 -->
<view class="earnings-detail-card" wx:if="{{earningsDetails.length > 0}}">
<view class="detail-header">
<text class="detail-title">收益明细</text>
</view>
<view class="detail-list">
<view class="detail-item" wx:for="{{earningsDetails}}" wx:key="id">
<view class="detail-left">
<view class="detail-icon">
<image class="icon-gift" src="/assets/icons/gift.svg" mode="aspectFit"></image>
</view>
<view class="detail-info">
<text class="detail-type">{{item.productType === 'fullbook' ? '整本书购买' : '单节购买'}}</text>
<text class="detail-time">{{item.payTime}}</text>
</view>
</view>
<text class="detail-amount">+¥{{item.commission}}</text>
</view>
</view>
</view>
<!-- 空状态 - 对齐 Next.js -->
<view class="empty-earnings" wx:if="{{earningsDetails.length === 0 && activeBindings.length === 0}}">
<view class="empty-icon-wrapper">
<image class="empty-gift-icon" src="/assets/icons/gift.svg" mode="aspectFit"></image>
</view>
<text class="empty-title">暂无收益记录</text>
<text class="empty-desc">分享专属链接,好友购买即可获得 {{shareRate}}% 返利</text>
</view>
</view>
<!-- 海报生成弹窗 -->
<!-- 海报生成弹窗 - 优化小程序显示 -->
<view class="modal-overlay" wx:if="{{showPosterModal}}" bindtap="closePosterModal">
<view class="modal-content poster-modal" catchtap="stopPropagation">
<view class="modal-header">
<text class="modal-title">推广海报</text>
<view class="modal-close" bindtap="closePosterModal">✕</view>
</view>
<view class="poster-dialog" catchtap="stopPropagation">
<view class="poster-close" bindtap="closePosterModal">✕</view>
<!-- 海报预览 -->
<view class="poster-preview">
<canvas canvas-id="promoPosterCanvas" class="poster-canvas" style="width: 300px; height: 450px;"></canvas>
</view>
<!-- 上半部分:海报内容(不使用画布,纯布局 + 二维码图片) -->
<view class="poster-card">
<!-- 装饰光效 -->
<view class="poster-glow poster-glow-left"></view>
<view class="poster-glow poster-glow-right"></view>
<view class="poster-ring"></view>
<view class="poster-actions">
<view class="poster-btn btn-save" bindtap="savePoster">
<text class="btn-icon">💾</text>
<text>保存到相册</text>
<view class="poster-inner">
<!-- 顶部标签 -->
<view class="poster-badges">
<text class="poster-badge poster-badge-gold">真实商业案例</text>
<text class="poster-badge poster-badge-brand">每日更新</text>
</view>
<!-- 标题 -->
<view class="poster-title">
<text class="poster-title-line1">一场SOUL的</text>
<text class="poster-title-line2">创业实验场</text>
</view>
<text class="poster-subtitle">来自Soul派对房的真实商业故事</text>
<!-- 核心数据 -->
<view class="poster-stats">
<view class="poster-stat">
<text class="poster-stat-value poster-stat-gold">{{posterCaseCount}}</text>
<text class="poster-stat-label">真实案例</text>
</view>
<view class="poster-stat">
<text class="poster-stat-value poster-stat-brand">5%</text>
<text class="poster-stat-label">好友优惠</text>
</view>
<view class="poster-stat">
<text class="poster-stat-value poster-stat-pink">90%</text>
<text class="poster-stat-label">你的收益</text>
</view>
</view>
<!-- 标签 -->
<view class="poster-tags">
<text class="poster-tag">人性观察</text>
<text class="poster-tag">行业揭秘</text>
<text class="poster-tag">赚钱逻辑</text>
<text class="poster-tag">创业复盘</text>
<text class="poster-tag">资源对接</text>
</view>
<!-- 推荐人 -->
<view class="poster-recommender">
<view class="poster-avatar">
<text class="poster-avatar-text">{{posterNicknameInitial}}</text>
</view>
<text class="poster-recommender-text">{{posterNickname}} 推荐你来读</text>
</view>
<!-- 优惠说明 -->
<view class="poster-discount">
<text class="poster-discount-text">通过我的链接购买,<text class="poster-discount-highlight">立省5%</text>
</text>
</view>
<!-- 二维码 -->
<view class="poster-qr-wrap" bindtap="previewPosterQr">
<image
class="poster-qr-img"
src="{{posterQrSrc}}"
mode="aspectFit"
show-menu-by-longpress="true"
></image>
</view>
<text class="poster-qr-tip">长按识别 · 立即试读</text>
<text class="poster-code">邀请码: {{referralCode}}</text>
</view>
</view>
<text class="poster-tip">保存后可分享到朋友圈或发送给好友</text>
<!-- 下半部分:白色操作区 -->
<view class="poster-footer">
<text class="poster-footer-tip">长按上方图片保存,或截图分享</text>
<view class="poster-footer-btn" bindtap="closePosterModal">关闭</view>
</view>
</view>
</view>
</view>

View File

@@ -1,148 +1,218 @@
/* 分销中心页面样式 - 1:1还原Web版本 */
.page { min-height: 100vh; background: #000; padding-bottom: 64rpx; }
/* 导航栏 */
.nav-bar { position: fixed; top: 0; left: 0; right: 0; z-index: 100; background: rgba(0,0,0,0.9); backdrop-filter: blur(40rpx); display: flex; align-items: center; justify-content: space-between; padding: 0 32rpx; height: 88rpx; }
.nav-back { width: 64rpx; height: 64rpx; background: #1c1c1e; border-radius: 50%; display: flex; align-items: center; justify-content: center; }
.back-icon { font-size: 40rpx; color: rgba(255,255,255,0.6); font-weight: 300; }
.nav-title { font-size: 34rpx; font-weight: 600; color: #fff; }
.nav-right { display: flex; gap: 16rpx; }
.nav-btn { width: 64rpx; height: 64rpx; background: #1c1c1e; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 28rpx; }
.content { padding: 24rpx; width: 100%; box-sizing: border-box; }
/* 过期提醒横幅 */
.expiring-banner { display: flex; align-items: center; gap: 24rpx; padding: 24rpx; background: rgba(255,165,0,0.1); border: 2rpx solid rgba(255,165,0,0.3); border-radius: 24rpx; margin-bottom: 24rpx; }
.banner-icon { width: 80rpx; height: 80rpx; background: rgba(255,165,0,0.2); border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 40rpx; flex-shrink: 0; }
.banner-content { flex: 1; }
.banner-title { font-size: 28rpx; font-weight: 500; color: #fff; display: block; }
.banner-desc { font-size: 24rpx; color: rgba(255,165,0,0.8); margin-top: 4rpx; display: block; }
/* 收益卡片 */
.earnings-card { position: relative; background: linear-gradient(135deg, rgba(0,206,209,0.15) 0%, rgba(32,178,170,0.1) 50%, rgba(0,139,139,0.05) 100%); border: 2rpx solid rgba(0,206,209,0.2); border-radius: 32rpx; padding: 40rpx; margin-bottom: 24rpx; overflow: hidden; width: 100%; box-sizing: border-box; }
.earnings-bg { position: absolute; top: -50rpx; right: -50rpx; width: 200rpx; height: 200rpx; background: rgba(0,206,209,0.1); border-radius: 50%; filter: blur(60rpx); }
.earnings-main { position: relative; }
.earnings-header { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 32rpx; }
.earnings-left { display: flex; align-items: center; gap: 16rpx; }
.wallet-icon { width: 80rpx; height: 80rpx; background: rgba(0,206,209,0.2); border-radius: 20rpx; display: flex; align-items: center; justify-content: center; font-size: 40rpx; }
.earnings-info { display: flex; flex-direction: column; gap: 4rpx; }
.earnings-label { font-size: 24rpx; color: rgba(255,255,255,0.6); }
.commission-rate { font-size: 24rpx; color: #00CED1; font-weight: 500; }
.earnings-right { text-align: right; }
.earnings-value { font-size: 56rpx; font-weight: 700; color: #fff; display: block; }
.pending-text { font-size: 22rpx; color: rgba(255,255,255,0.5); }
.withdraw-btn { padding: 24rpx; background: #00CED1; color: #000; font-size: 28rpx; font-weight: 600; text-align: center; border-radius: 24rpx; }
.withdraw-btn.btn-disabled { background: rgba(0,206,209,0.3); color: rgba(0,0,0,0.5); }
/* 收益详情 */
.earnings-detail { padding-top: 16rpx; border-top: 2rpx solid rgba(255,255,255,0.1); margin-bottom: 24rpx; }
.detail-item { font-size: 24rpx; color: rgba(255,255,255,0.5); }
/* 核心数据统计 */
.stats-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16rpx; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
.stat-card { background: #1c1c1e; border-radius: 24rpx; padding: 28rpx 20rpx; text-align: center; position: relative; }
.stat-card.highlight { background: linear-gradient(135deg, rgba(0,206,209,0.1) 0%, rgba(0,206,209,0.05) 100%); border: 2rpx solid rgba(0,206,209,0.2); }
.stat-value { font-size: 48rpx; font-weight: 700; color: #fff; display: block; }
.stat-value.brand { color: #00CED1; }
.stat-value.gold { color: #FFD700; }
.stat-value.orange { color: #FFA500; }
.stat-value.gray { color: #9E9E9E; }
.stat-label { font-size: 24rpx; color: rgba(255,255,255,0.7); margin-top: 8rpx; display: block; font-weight: 500; }
.stat-tip { font-size: 20rpx; color: rgba(255,255,255,0.4); margin-top: 4rpx; display: block; }
/* 访问量统计 */
.visit-stat { display: flex; align-items: center; justify-content: center; gap: 12rpx; padding: 20rpx 32rpx; background: rgba(255,255,255,0.05); border-radius: 16rpx; margin-bottom: 24rpx; }
.visit-label { font-size: 24rpx; color: rgba(255,255,255,0.5); }
.visit-value { font-size: 32rpx; font-weight: 700; color: #00CED1; }
.visit-tip { font-size: 24rpx; color: rgba(255,255,255,0.5); }
/* 推广规则 */
.rules-card { background: rgba(0,206,209,0.05); border: 2rpx solid rgba(0,206,209,0.2); border-radius: 24rpx; padding: 24rpx; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
.rules-header { display: flex; align-items: center; gap: 16rpx; margin-bottom: 16rpx; }
.rules-icon { width: 56rpx; height: 56rpx; background: rgba(0,206,209,0.2); border-radius: 16rpx; display: flex; align-items: center; justify-content: center; font-size: 28rpx; }
.rules-title { font-size: 28rpx; font-weight: 500; color: #fff; }
.rules-list { padding-left: 8rpx; }
.rule-item { font-size: 24rpx; color: rgba(255,255,255,0.6); line-height: 2; display: block; margin-bottom: 4rpx; }
.rule-item .gold { color: #FFD700; font-weight: 500; }
.rule-item .brand { color: #00CED1; font-weight: 500; }
.rule-item .orange { color: #FFA500; font-weight: 500; }
/* 绑定用户卡片 */
.binding-card { background: #1c1c1e; border-radius: 32rpx; overflow: hidden; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
.binding-header { display: flex; align-items: center; justify-content: space-between; padding: 28rpx 32rpx; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
.binding-title { display: flex; align-items: center; gap: 12rpx; }
.binding-icon { font-size: 36rpx; }
.title-text { font-size: 30rpx; font-weight: 600; color: #fff; }
.binding-count { font-size: 26rpx; color: rgba(255,255,255,0.5); }
.toggle-icon { font-size: 24rpx; color: rgba(255,255,255,0.5); }
/* Tab切换 */
.binding-tabs { display: flex; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
.tab-item { flex: 1; padding: 24rpx 0; text-align: center; font-size: 26rpx; color: rgba(255,255,255,0.5); position: relative; }
.tab-item.tab-active { color: #00CED1; }
.tab-item.tab-active::after { content: ''; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 80rpx; height: 4rpx; background: #00CED1; border-radius: 4rpx; }
/* 用户列表 */
.binding-list { max-height: 640rpx; overflow-y: auto; }
.empty-state { padding: 80rpx 0; text-align: center; }
.empty-icon { font-size: 64rpx; display: block; margin-bottom: 16rpx; }
.empty-text { font-size: 26rpx; color: rgba(255,255,255,0.5); }
.binding-item { display: flex; align-items: center; padding: 24rpx 32rpx; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
.binding-item:last-child { border-bottom: none; }
.user-avatar { width: 80rpx; height: 80rpx; border-radius: 50%; background: rgba(0,206,209,0.2); display: flex; align-items: center; justify-content: center; font-size: 28rpx; font-weight: 600; color: #00CED1; margin-right: 24rpx; flex-shrink: 0; }
.user-avatar.avatar-converted { background: rgba(76,175,80,0.2); color: #4CAF50; }
.user-avatar.avatar-expired { background: rgba(158,158,158,0.2); color: #9E9E9E; }
.user-info { flex: 1; }
.user-name { font-size: 28rpx; color: #fff; font-weight: 500; display: block; }
.user-time { font-size: 22rpx; color: rgba(255,255,255,0.5); margin-top: 4rpx; display: block; }
.user-status { text-align: right; }
.status-amount { font-size: 28rpx; color: #4CAF50; font-weight: 600; display: block; }
.status-order { font-size: 22rpx; color: rgba(255,255,255,0.5); }
.status-tag { font-size: 22rpx; padding: 8rpx 16rpx; border-radius: 16rpx; }
.status-tag.tag-green { background: rgba(76,175,80,0.2); color: #4CAF50; }
.status-tag.tag-orange { background: rgba(255,165,0,0.2); color: #FFA500; }
.status-tag.tag-red { background: rgba(244,67,54,0.2); color: #F44336; }
/* 邀请码卡片 */
.invite-card { background: #1c1c1e; border-radius: 32rpx; padding: 32rpx; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
.invite-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16rpx; }
.invite-title { font-size: 28rpx; font-weight: 600; color: #fff; }
.invite-code-box { background: rgba(0,206,209,0.15); padding: 12rpx 24rpx; border-radius: 16rpx; }
.invite-code { font-size: 26rpx; font-weight: 600; color: #00CED1; font-family: monospace; letter-spacing: 2rpx; }
.invite-tip { font-size: 24rpx; color: rgba(255,255,255,0.5); }
.invite-tip .gold { color: #FFD700; }
.invite-tip .brand { color: #00CED1; }
/* 分享区域 */
.share-section { display: flex; flex-direction: column; gap: 16rpx; width: 100%; }
.share-item { display: flex; align-items: center; background: #1c1c1e; border-radius: 24rpx; padding: 24rpx 32rpx; border: none; margin: 0; text-align: left; width: 100%; box-sizing: border-box; }
.share-item::after { border: none; }
.share-icon { width: 96rpx; height: 96rpx; border-radius: 20rpx; display: flex; align-items: center; justify-content: center; font-size: 48rpx; margin-right: 24rpx; flex-shrink: 0; }
.share-icon.poster { background: rgba(103,58,183,0.2); }
.share-icon.wechat { background: rgba(7,193,96,0.2); }
.share-icon.link { background: rgba(158,158,158,0.2); }
.share-info { flex: 1; text-align: left; }
.share-title { font-size: 28rpx; color: #fff; font-weight: 500; display: block; text-align: left; }
.share-desc { font-size: 22rpx; color: rgba(255,255,255,0.5); margin-top: 4rpx; display: block; text-align: left; }
.share-arrow { font-size: 28rpx; color: rgba(255,255,255,0.3); flex-shrink: 0; }
.share-btn-wechat { line-height: normal; font-size: inherit; padding: 24rpx 32rpx !important; margin: 0 !important; width: 100% !important; }
/* 弹窗 */
.modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.7); backdrop-filter: blur(20rpx); display: flex; align-items: flex-end; justify-content: center; z-index: 1000; }
.modal-content { width: 100%; max-width: 750rpx; background: #1c1c1e; border-radius: 48rpx 48rpx 0 0; padding: 48rpx; padding-bottom: calc(48rpx + env(safe-area-inset-bottom)); animation: slideUp 0.3s ease; }
@keyframes slideUp { from { transform: translateY(100%); } to { transform: translateY(0); } }
.modal-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 32rpx; }
.modal-title { font-size: 36rpx; font-weight: 600; color: #fff; }
.modal-close { width: 64rpx; height: 64rpx; border-radius: 50%; background: rgba(255,255,255,0.1); display: flex; align-items: center; justify-content: center; font-size: 28rpx; color: rgba(255,255,255,0.6); }
/* 海报弹窗 */
.poster-modal { padding-bottom: calc(64rpx + env(safe-area-inset-bottom)); }
.poster-preview { display: flex; justify-content: center; margin: 32rpx 0; padding: 24rpx; background: rgba(0,0,0,0.3); border-radius: 24rpx; }
.poster-canvas { border-radius: 16rpx; box-shadow: 0 16rpx 48rpx rgba(0,0,0,0.5); }
.poster-actions { display: flex; gap: 24rpx; margin-bottom: 24rpx; }
.poster-btn { flex: 1; display: flex; align-items: center; justify-content: center; gap: 12rpx; padding: 28rpx; border-radius: 24rpx; font-size: 30rpx; font-weight: 500; color: #fff; }
.btn-save { background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%); }
.btn-icon { font-size: 32rpx; }
.poster-tip { font-size: 24rpx; color: rgba(255,255,255,0.4); text-align: center; display: block; }
/* ???????? - 1:1??Web?? */
.page { min-height: 100vh; background: #000; padding-bottom: 64rpx; }
/* ??? */
.nav-bar { position: fixed; top: 0; left: 0; right: 0; z-index: 100; background: rgba(0,0,0,0.9); backdrop-filter: blur(40rpx); display: flex; align-items: center; justify-content: space-between; padding: 0 32rpx; height: 88rpx; }
.nav-left { display: flex; gap: 16rpx; align-items: center; }
.nav-back { width: 64rpx; height: 64rpx; background: #1c1c1e; border-radius: 50%; display: flex; align-items: center; justify-content: center; }
.nav-icon { width: 40rpx; height: 40rpx; display: block; filter: brightness(0) saturate(100%) invert(60%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(95%) contrast(85%); }
.nav-title { font-size: 34rpx; font-weight: 600; color: #fff; flex: 1; text-align: center; }
.nav-right-placeholder { width: 144rpx; }
.nav-btn { width: 64rpx; height: 64rpx; background: #1c1c1e; border-radius: 50%; display: flex; align-items: center; justify-content: center; }
.content { padding: 24rpx; width: 100%; box-sizing: border-box; }
/* ?????? */
.expiring-banner { display: flex; align-items: center; gap: 24rpx; padding: 24rpx; background: rgba(255,165,0,0.1); border: 2rpx solid rgba(255,165,0,0.3); border-radius: 24rpx; margin-bottom: 24rpx; }
.banner-icon { width: 80rpx; height: 80rpx; background: rgba(255,165,0,0.2); border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.icon-bell-warning { width: 40rpx; height: 40rpx; display: block; filter: brightness(0) saturate(100%) invert(64%) sepia(89%) saturate(1363%) hue-rotate(4deg) brightness(101%) contrast(102%); }
.banner-content { flex: 1; }
.banner-title { font-size: 28rpx; font-weight: 500; color: #fff; display: block; }
.banner-desc { font-size: 24rpx; color: rgba(255,165,0,0.8); margin-top: 4rpx; display: block; }
/* ???? - ?? Next.js */
.earnings-card { position: relative; background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 32rpx; padding: 48rpx; margin-bottom: 24rpx; overflow: hidden; width: 100%; box-sizing: border-box; }
.earnings-bg { position: absolute; top: 0; right: 0; width: 256rpx; height: 256rpx; background: rgba(0,206,209,0.15); border-radius: 50%; filter: blur(100rpx); }
.earnings-main { position: relative; }
.earnings-header { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 32rpx; }
.earnings-left { display: flex; align-items: center; gap: 16rpx; }
.wallet-icon { width: 80rpx; height: 80rpx; background: rgba(0,206,209,0.2); border-radius: 20rpx; display: flex; align-items: center; justify-content: center; }
.icon-wallet { width: 40rpx; height: 40rpx; display: block; filter: brightness(0) saturate(100%) invert(72%) sepia(54%) saturate(2933%) hue-rotate(134deg) brightness(101%) contrast(101%); }
.earnings-info { display: flex; flex-direction: column; gap: 8rpx; }
.earnings-label { font-size: 24rpx; color: rgba(255,255,255,0.6); }
.commission-rate { font-size: 24rpx; color: #00CED1; font-weight: 500; }
.earnings-right { text-align: right; }
.earnings-value { font-size: 60rpx; font-weight: 700; color: #fff; display: block; line-height: 1; }
.pending-text { font-size: 24rpx; color: rgba(255,255,255,0.5); margin-top: 8rpx; display: block; }
.withdraw-btn { padding: 28rpx; background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%); color: #fff; font-size: 32rpx; font-weight: 600; text-align: center; border-radius: 24rpx; box-shadow: 0 8rpx 24rpx rgba(0,206,209,0.3); }
.withdraw-btn.btn-disabled { background: rgba(0,206,209,0.2); color: rgba(255,255,255,0.3); box-shadow: none; }
/* ???? - ?? Next.js 4??? */
.stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8rpx; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
.stat-card { background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 24rpx; padding: 24rpx 12rpx; text-align: center; }
.stat-value { font-size: 40rpx; font-weight: 700; color: #fff; display: block; }
.stat-value.orange { color: #FFA500; }
.stat-label { font-size: 20rpx; color: rgba(255,255,255,0.6); margin-top: 8rpx; display: block; }
/* ????? */
.visit-stat { display: flex; align-items: center; justify-content: center; gap: 12rpx; padding: 20rpx 32rpx; background: rgba(255,255,255,0.05); border-radius: 16rpx; margin-bottom: 24rpx; }
.visit-label { font-size: 24rpx; color: rgba(255,255,255,0.5); }
.visit-value { font-size: 32rpx; font-weight: 700; color: #00CED1; }
.visit-tip { font-size: 24rpx; color: rgba(255,255,255,0.5); }
/* ???? - ?? Next.js */
.rules-card { background: rgba(0,206,209,0.05); border: 2rpx solid rgba(0,206,209,0.2); border-radius: 24rpx; padding: 32rpx; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
.rules-header { display: flex; align-items: center; gap: 16rpx; margin-bottom: 16rpx; }
.rules-icon { width: 64rpx; height: 64rpx; background: rgba(0,206,209,0.2); border-radius: 16rpx; display: flex; align-items: center; justify-content: center; }
.icon-alert { width: 32rpx; height: 32rpx; display: block; filter: brightness(0) saturate(100%) invert(72%) sepia(54%) saturate(2933%) hue-rotate(134deg) brightness(101%) contrast(101%); }
.rules-title { font-size: 28rpx; font-weight: 500; color: #fff; }
.rules-list { padding-left: 8rpx; }
.rule-item { font-size: 24rpx; color: rgba(255,255,255,0.6); line-height: 2; display: block; margin-bottom: 4rpx; }
.rule-item .gold { color: #FFD700; font-weight: 500; }
.rule-item .brand { color: #00CED1; font-weight: 500; }
.rule-item .orange { color: #FFA500; font-weight: 500; }
/* ?????? - ?? Next.js */
.binding-card { background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 32rpx; overflow: hidden; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
.binding-header { display: flex; align-items: center; justify-content: space-between; padding: 28rpx 32rpx; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
.binding-title { display: flex; align-items: center; gap: 12rpx; }
.binding-icon-img { width: 40rpx; height: 40rpx; display: block; filter: brightness(0) saturate(100%) invert(72%) sepia(54%) saturate(2933%) hue-rotate(134deg) brightness(101%) contrast(101%); }
.title-text { font-size: 30rpx; font-weight: 600; color: #fff; }
.binding-count { font-size: 26rpx; color: rgba(255,255,255,0.5); }
.toggle-icon { font-size: 24rpx; color: rgba(255,255,255,0.5); }
/* Tab?? */
.binding-tabs { display: flex; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
.tab-item { flex: 1; padding: 24rpx 0; text-align: center; font-size: 26rpx; color: rgba(255,255,255,0.5); position: relative; }
.tab-item.tab-active { color: #00CED1; }
.tab-item.tab-active::after { content: ''; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 80rpx; height: 4rpx; background: #00CED1; border-radius: 4rpx; }
/* ???? */
.binding-list { max-height: 640rpx; overflow-y: auto; }
.empty-state { padding: 80rpx 0; text-align: center; }
.empty-icon { font-size: 64rpx; display: block; margin-bottom: 16rpx; }
.empty-text { font-size: 26rpx; color: rgba(255,255,255,0.5); }
.binding-item { display: flex; align-items: center; padding: 24rpx 32rpx; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
.binding-item:last-child { border-bottom: none; }
.user-avatar { width: 80rpx; height: 80rpx; border-radius: 50%; background: rgba(0,206,209,0.2); display: flex; align-items: center; justify-content: center; font-size: 28rpx; font-weight: 600; color: #00CED1; margin-right: 24rpx; flex-shrink: 0; }
.user-avatar.avatar-converted { background: rgba(76,175,80,0.2); color: #4CAF50; }
.user-avatar.avatar-expired { background: rgba(158,158,158,0.2); color: #9E9E9E; }
.user-info { flex: 1; }
.user-name { font-size: 28rpx; color: #fff; font-weight: 500; display: block; }
.user-time { font-size: 22rpx; color: rgba(255,255,255,0.5); margin-top: 4rpx; display: block; }
.user-status { text-align: right; }
.status-amount { font-size: 28rpx; color: #4CAF50; font-weight: 600; display: block; }
.status-order { font-size: 22rpx; color: rgba(255,255,255,0.5); }
.status-tag { font-size: 22rpx; padding: 8rpx 16rpx; border-radius: 16rpx; }
.status-tag.tag-green { background: rgba(76,175,80,0.2); color: #4CAF50; }
.status-tag.tag-orange { background: rgba(255,165,0,0.2); color: #FFA500; }
.status-tag.tag-red { background: rgba(244,67,54,0.2); color: #F44336; }
/* ????? - ?? Next.js */
.invite-card { background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 32rpx; padding: 40rpx; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
.invite-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 24rpx; }
.invite-title { font-size: 30rpx; font-weight: 600; color: #fff; }
.invite-code-box { background: rgba(0,206,209,0.2); padding: 12rpx 24rpx; border-radius: 16rpx; }
.invite-code { font-size: 28rpx; font-weight: 600; color: #00CED1; font-family: 'Courier New', monospace; letter-spacing: 2rpx; }
.invite-tip { font-size: 24rpx; color: rgba(255,255,255,0.6); line-height: 1.5; display: block; }
.invite-tip .gold { color: #FFD700; }
.invite-tip .brand { color: #00CED1; }
/* ?????? - ?? Next.js */
.earnings-detail-card { background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 32rpx; overflow: hidden; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
.detail-header { padding: 40rpx 40rpx 24rpx; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
.detail-title { font-size: 30rpx; font-weight: 600; color: #fff; }
.detail-list { max-height: 480rpx; overflow-y: auto; }
.detail-item { display: flex; align-items: center; justify-content: space-between; padding: 32rpx 40rpx; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
.detail-item:last-child { border-bottom: none; }
.detail-left { display: flex; align-items: center; gap: 24rpx; flex: 1; }
.detail-icon { width: 80rpx; height: 80rpx; border-radius: 20rpx; background: rgba(0,206,209,0.2); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.icon-gift { width: 40rpx; height: 40rpx; display: block; filter: brightness(0) saturate(100%) invert(72%) sepia(54%) saturate(2933%) hue-rotate(134deg) brightness(101%) contrast(101%); }
.detail-info { flex: 1; }
.detail-type { font-size: 28rpx; color: #fff; font-weight: 500; display: block; }
.detail-time { font-size: 24rpx; color: rgba(255,255,255,0.5); margin-top: 4rpx; display: block; }
.detail-amount { font-size: 30rpx; font-weight: 600; color: #00CED1; }
/* ???? - ?? Next.js */
.share-section { display: flex; flex-direction: column; gap: 12rpx; width: 100%; margin-bottom: 24rpx; }
.share-item { display: flex; align-items: center; background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 24rpx; padding: 32rpx; border: none; margin: 0; text-align: left; width: 100%; box-sizing: border-box; }
.share-item::after { border: none; }
.share-icon { width: 96rpx; height: 96rpx; border-radius: 20rpx; display: flex; align-items: center; justify-content: center; margin-right: 24rpx; flex-shrink: 0; }
.share-icon.poster { background: rgba(103,58,183,0.2); }
.share-icon.wechat { background: rgba(7,193,96,0.2); }
.share-icon.link { background: rgba(158,158,158,0.2); }
.icon-share-btn { width: 48rpx; height: 48rpx; display: block; }
.share-icon.poster .icon-share-btn { filter: brightness(0) saturate(100%) invert(37%) sepia(73%) saturate(2296%) hue-rotate(252deg) brightness(96%) contrast(92%); }
.share-icon.wechat .icon-share-btn { filter: brightness(0) saturate(100%) invert(58%) sepia(91%) saturate(1255%) hue-rotate(105deg) brightness(96%) contrast(97%); }
.share-icon.link .icon-share-btn { filter: brightness(0) saturate(100%) invert(60%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(95%) contrast(85%); }
.share-info { flex: 1; text-align: left; }
.share-title { font-size: 28rpx; color: #fff; font-weight: 500; display: block; text-align: left; }
.share-desc { font-size: 22rpx; color: rgba(255,255,255,0.5); margin-top: 4rpx; display: block; text-align: left; }
.share-arrow-icon { width: 40rpx; height: 40rpx; display: block; flex-shrink: 0; filter: brightness(0) saturate(100%) invert(60%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(95%) contrast(85%); }
.share-btn-wechat { line-height: normal; font-size: inherit; padding: 24rpx 32rpx !important; margin: 0 !important; width: 100% !important; }
/* ?????????????? + ???? + ???????? */
/* ???????? backdrop-filter??????????????? */
.modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.8); display: flex; align-items: center; justify-content: center; z-index: 1000; padding: 32rpx; box-sizing: border-box; }
.poster-dialog { width: 686rpx; border-radius: 24rpx; overflow: hidden; position: relative; background: transparent; }
.poster-close { position: absolute; top: 20rpx; right: 20rpx; width: 56rpx; height: 56rpx; border-radius: 28rpx; background: rgba(0,0,0,0.25); color: rgba(255,255,255,0.9); display: flex; align-items: center; justify-content: center; z-index: 5; font-size: 28rpx; }
/* ???? */
.poster-card { position: relative; background: linear-gradient(135deg, #0a1628 0%, #0f2137 50%, #1a3a5c 100%); color: #fff; padding: 44rpx 40rpx 36rpx; }
.poster-inner { position: relative; z-index: 2; display: flex; flex-direction: column; align-items: center; text-align: center; }
/* ???? */
/* ???????? filter: blur ??????????????? + ???? */
.poster-glow { position: absolute; width: 320rpx; height: 320rpx; border-radius: 50%; opacity: 0.6; z-index: 1; }
.poster-glow-left { top: -120rpx; left: -160rpx; background: rgba(0,206,209,0.12); box-shadow: 0 0 140rpx 40rpx rgba(0,206,209,0.18); }
.poster-glow-right { bottom: -140rpx; right: -160rpx; background: rgba(255,215,0,0.10); box-shadow: 0 0 160rpx 50rpx rgba(255,215,0,0.14); }
.poster-ring { position: absolute; width: 520rpx; height: 520rpx; border-radius: 50%; border: 2rpx solid rgba(0,206,209,0.06); left: 50%; top: 50%; transform: translate(-50%, -50%); z-index: 1; }
/* ???? */
.poster-badges { display: flex; gap: 16rpx; margin-bottom: 24rpx; }
.poster-badge { padding: 10rpx 22rpx; font-size: 20rpx; font-weight: 700; border-radius: 999rpx; border: 2rpx solid transparent; }
.poster-badge-gold { background: rgba(255,215,0,0.18); color: #FFD700; border-color: rgba(255,215,0,0.28); }
.poster-badge-brand { background: rgba(0,206,209,0.18); color: #00CED1; border-color: rgba(0,206,209,0.28); }
/* ?? */
.poster-title { margin-bottom: 8rpx; }
.poster-title-line1 { display: block; font-size: 44rpx; font-weight: 900; line-height: 1.1; color: #00CED1; }
.poster-title-line2 { display: block; font-size: 44rpx; font-weight: 900; line-height: 1.1; color: #fff; margin-top: 6rpx; }
.poster-subtitle { display: block; font-size: 22rpx; color: rgba(255,255,255,0.6); margin-bottom: 28rpx; }
/* ???? */
.poster-stats { width: 100%; display: flex; gap: 16rpx; justify-content: center; margin-bottom: 24rpx; }
.poster-stat { flex: 1; max-width: 190rpx; background: rgba(255,255,255,0.05); border: 2rpx solid rgba(255,255,255,0.10); border-radius: 16rpx; padding: 18rpx 10rpx; }
.poster-stat-value { display: block; font-size: 44rpx; font-weight: 900; line-height: 1; }
.poster-stat-label { display: block; font-size: 20rpx; color: rgba(255,255,255,0.5); margin-top: 8rpx; }
.poster-stat-gold { color: #FFD700; }
.poster-stat-brand { color: #00CED1; }
.poster-stat-pink { color: #E91E63; }
/* ?? */
.poster-tags { display: flex; flex-wrap: wrap; justify-content: center; gap: 10rpx; margin: 0 24rpx 26rpx; }
.poster-tag { font-size: 20rpx; color: rgba(255,255,255,0.7); background: rgba(255,255,255,0.05); border: 2rpx solid rgba(255,255,255,0.10); border-radius: 12rpx; padding: 6rpx 14rpx; }
/* ??? */
.poster-recommender { display: flex; align-items: center; gap: 12rpx; background: rgba(0,206,209,0.10); border: 2rpx solid rgba(0,206,209,0.20); border-radius: 999rpx; padding: 12rpx 22rpx; margin-bottom: 22rpx; }
.poster-avatar { width: 44rpx; height: 44rpx; border-radius: 22rpx; background: rgba(0,206,209,0.30); display: flex; align-items: center; justify-content: center; }
.poster-avatar-text { font-size: 20rpx; font-weight: 800; color: #00CED1; }
.poster-recommender-text { font-size: 22rpx; color: #00CED1; }
/* ??? */
.poster-discount { width: 100%; background: linear-gradient(90deg, rgba(255,215,0,0.10) 0%, rgba(233,30,99,0.10) 100%); border: 2rpx solid rgba(255,215,0,0.20); border-radius: 18rpx; padding: 18rpx 18rpx; margin-bottom: 26rpx; }
.poster-discount-text { font-size: 22rpx; color: rgba(255,255,255,0.80); }
.poster-discount-highlight { color: #00CED1; font-weight: 800; }
/* ??? */
.poster-qr-wrap { background: #fff; padding: 14rpx; border-radius: 16rpx; margin-bottom: 12rpx; box-shadow: 0 16rpx 40rpx rgba(0,0,0,0.35); }
.poster-qr-img { width: 240rpx; height: 240rpx; display: block; }
.poster-qr-tip { font-size: 20rpx; color: rgba(255,255,255,0.40); margin-bottom: 8rpx; }
.poster-code { font-size: 22rpx; font-family: monospace; letter-spacing: 2rpx; color: rgba(0,206,209,0.80); }
/* ??????? */
.poster-footer { background: #fff; padding: 28rpx 28rpx 32rpx; display: flex; flex-direction: column; gap: 18rpx; }
.poster-footer-tip { font-size: 22rpx; color: rgba(0,0,0,0.55); text-align: center; }
.poster-footer-btn { height: 72rpx; border-radius: 18rpx; border: 2rpx solid rgba(0,0,0,0.15); display: flex; align-items: center; justify-content: center; font-size: 28rpx; color: rgba(0,0,0,0.75); background: #fff; }
/* ????- ?? Next.js */
.empty-earnings { background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 32rpx; padding: 64rpx 40rpx; text-align: center; margin-bottom: 24rpx; }
.empty-icon-wrapper { width: 128rpx; height: 128rpx; border-radius: 50%; background: rgba(28, 28, 30, 0.8); display: flex; align-items: center; justify-content: center; margin: 0 auto 32rpx; }
.empty-gift-icon { width: 64rpx; height: 64rpx; display: block; filter: brightness(0) saturate(100%) invert(60%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(95%) contrast(85%); }
.empty-title { font-size: 30rpx; font-weight: 500; color: #fff; display: block; margin-bottom: 16rpx; }
.empty-desc { font-size: 26rpx; color: rgba(255,255,255,0.6); display: block; line-height: 1.5; }