1
This commit is contained in:
182
new-soul/miniprogram/pages/referral/earnings-detail-styles.wxss
Normal file
182
new-soul/miniprogram/pages/referral/earnings-detail-styles.wxss
Normal file
@@ -0,0 +1,182 @@
|
||||
/* ===================================
|
||||
收益明细卡片样式 - 重构版
|
||||
创建时间:2026-02-04
|
||||
说明:修复布局错乱问题,优化文本显示
|
||||
=================================== */
|
||||
|
||||
/* 收益明细卡片容器 */
|
||||
.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;
|
||||
padding: 16rpx 0;
|
||||
}
|
||||
|
||||
/* ===================================
|
||||
收益明细列表项 - 核心样式
|
||||
=================================== */
|
||||
|
||||
/* 列表项容器 - 使用flex布局 */
|
||||
.earnings-detail-card .detail-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
padding: 24rpx 40rpx;
|
||||
background: transparent;
|
||||
border-bottom: 2rpx solid rgba(255,255,255,0.03);
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.earnings-detail-card .detail-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.earnings-detail-card .detail-item:active {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* 头像容器 - 固定宽度,不收缩 */
|
||||
.earnings-detail-card .detail-avatar-wrap {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.earnings-detail-card .detail-avatar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid rgba(56, 189, 172, 0.2);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.earnings-detail-card .detail-avatar-text {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #38bdac 0%, #2da396 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* 详细信息容器 - 占据剩余空间,允许收缩 */
|
||||
.earnings-detail-card .detail-content {
|
||||
flex: 1;
|
||||
min-width: 0; /* 关键:允许flex子元素收缩到比内容更小 */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
/* 顶部行:昵称 + 金额 */
|
||||
.earnings-detail-card .detail-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
/* 买家昵称 - 允许收缩,显示省略号 */
|
||||
.earnings-detail-card .detail-buyer {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #ffffff;
|
||||
flex: 1;
|
||||
min-width: 0; /* 关键:允许收缩 */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 佣金金额 - 固定宽度,不收缩 */
|
||||
.earnings-detail-card .detail-amount {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #38bdac;
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 商品信息行:书名 + 章节 */
|
||||
.earnings-detail-card .detail-product {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 4rpx;
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
min-width: 0; /* 关键:允许收缩 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 书名 - 限制最大宽度,显示省略号 */
|
||||
.earnings-detail-card .detail-book {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-weight: 500;
|
||||
max-width: 50%; /* 限制书名最多占一半宽度 */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
/* 章节 - 占据剩余空间,显示省略号 */
|
||||
.earnings-detail-card .detail-chapter {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
flex: 1;
|
||||
min-width: 0; /* 关键:允许收缩 */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 时间信息 */
|
||||
.earnings-detail-card .detail-time {
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
/* ===================================
|
||||
响应式优化
|
||||
=================================== */
|
||||
|
||||
/* 小屏幕优化 */
|
||||
@media (max-width: 375px) {
|
||||
.earnings-detail-card .detail-buyer {
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.earnings-detail-card .detail-amount {
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.earnings-detail-card .detail-book {
|
||||
max-width: 45%;
|
||||
}
|
||||
}
|
||||
909
new-soul/miniprogram/pages/referral/referral.js
Normal file
909
new-soul/miniprogram/pages/referral/referral.js
Normal file
@@ -0,0 +1,909 @@
|
||||
/**
|
||||
* Soul创业派对 - 分销中心页
|
||||
*
|
||||
* 可见数据:
|
||||
* - 绑定用户数(当前有效绑定)
|
||||
* - 通过链接进的人数(总访问量)
|
||||
* - 带来的付款人数(已转化购买)
|
||||
* - 收益统计(90%归分发者)
|
||||
*/
|
||||
const app = getApp()
|
||||
const { trackClick } = require('../../utils/trackClick')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
statusBarHeight: 44,
|
||||
isLoggedIn: false,
|
||||
userInfo: null,
|
||||
|
||||
// === 核心可见数据 ===
|
||||
bindingCount: 0, // 绑定用户数(当前有效)
|
||||
visitCount: 0, // 通过链接进的人数
|
||||
paidCount: 0, // 带来的付款人数
|
||||
unboughtCount: 0, // 待购买人数(绑定但未付款)
|
||||
expiredCount: 0, // 已过期人数
|
||||
|
||||
// === 收益数据 ===
|
||||
totalCommission: 0, // 累计佣金总额(所有获得的佣金)
|
||||
availableEarnings: 0, // 可提现金额(未申请提现的佣金)- 字符串格式用于显示
|
||||
availableEarningsNum: 0, // 可提现金额 - 数字格式用于判断
|
||||
pendingWithdrawAmount: 0, // 待审核金额(已申请提现但未审核)
|
||||
withdrawnEarnings: 0, // 已提现金额
|
||||
earnings: 0, // 已结算收益(保留兼容)
|
||||
pendingEarnings: 0, // 待结算收益(保留兼容)
|
||||
shareRate: 90, // 分成比例(90%),从 referral/data 或 config 获取
|
||||
minWithdrawAmount: 10, // 最低提现金额,从 referral/data 获取
|
||||
bindingDays: 30, // 绑定期天数,从 referral/data 获取
|
||||
userDiscount: 5, // 好友购买优惠%,从 referral/data 获取
|
||||
hasWechatId: false, // 是否已绑定微信号(未绑定时需引导去设置)
|
||||
|
||||
// === 统计数据 ===
|
||||
referralCount: 0, // 总推荐人数
|
||||
expiringCount: 0, // 即将过期人数
|
||||
|
||||
// 邀请码
|
||||
referralCode: '',
|
||||
|
||||
// 绑定用户列表
|
||||
showBindingList: true,
|
||||
activeTab: 'active',
|
||||
activeBindings: [],
|
||||
convertedBindings: [],
|
||||
expiredBindings: [],
|
||||
currentBindings: [],
|
||||
totalBindings: 0,
|
||||
|
||||
// 收益明细
|
||||
earningsDetails: [],
|
||||
|
||||
// 海报
|
||||
showPosterModal: false,
|
||||
isGeneratingPoster: false,
|
||||
posterQrSrc: '',
|
||||
posterReferralLink: '',
|
||||
posterNickname: '',
|
||||
posterNicknameInitial: '',
|
||||
posterCaseCount: 62
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
|
||||
this.initData()
|
||||
// 启用分享到朋友圈(需同时有 onShareAppMessage 和 onShareTimeline;menus 在 Android 支持,iOS 为 Beta)
|
||||
wx.showShareMenu({ menus: ['shareAppMessage', 'shareTimeline'] })
|
||||
},
|
||||
|
||||
onShow() {
|
||||
// 从设置页返回时同步微信号绑定状态,便于提现按钮立即更新
|
||||
const hasWechatId = !!(app.globalData.userInfo?.wechat || app.globalData.userInfo?.wechatId || wx.getStorageSync('user_wechat'))
|
||||
this.setData({ hasWechatId })
|
||||
this.initData()
|
||||
},
|
||||
|
||||
// 初始化数据
|
||||
async initData() {
|
||||
const { isLoggedIn, userInfo } = app.globalData
|
||||
if (isLoggedIn && userInfo) {
|
||||
// 显示加载提示
|
||||
wx.showLoading({
|
||||
title: '加载中...',
|
||||
mask: true // 防止触摸穿透
|
||||
})
|
||||
|
||||
// 生成邀请码
|
||||
const referralCode = userInfo.referralCode || 'SOUL' + (userInfo.id || Date.now().toString(36)).toUpperCase().slice(-6)
|
||||
|
||||
// 从API获取真实数据
|
||||
let realData = null
|
||||
try {
|
||||
// app.request 第一个参数是 URL 字符串(会自动拼接 baseUrl)
|
||||
const res = await app.request('/api/miniprogram/referral/data?userId=' + userInfo.id)
|
||||
|
||||
if (res && res.success && res.data) {
|
||||
realData = res.data
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[Referral] 加载分销数据失败:', e && e.message ? e.message : e)
|
||||
}
|
||||
|
||||
// 使用真实数据或默认值
|
||||
let activeBindings = realData?.activeUsers || []
|
||||
let convertedBindings = realData?.convertedUsers || []
|
||||
let expiredBindings = realData?.expiredUsers || []
|
||||
|
||||
// 计算即将过期的数量(7天内)
|
||||
const expiringCount = realData?.stats?.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 - 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) : '--'
|
||||
}
|
||||
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
|
||||
|
||||
const hasWechatId = !!(userInfo?.wechat || userInfo?.wechatId || wx.getStorageSync('user_wechat'))
|
||||
this.setData({
|
||||
isLoggedIn: true,
|
||||
userInfo,
|
||||
hasWechatId,
|
||||
|
||||
// 核心可见数据
|
||||
bindingCount,
|
||||
visitCount: realData?.visitCount || 0,
|
||||
paidCount,
|
||||
unboughtCount: expiringCount, // "即将过期"显示的是 expiringCount
|
||||
expiredCount,
|
||||
|
||||
// 收益数据 - 格式化为两位小数
|
||||
totalCommission: formatMoney(totalCommissionNum),
|
||||
availableEarnings: formatMoney(availableEarningsNum), // ✅ 使用计算后的可提现金额
|
||||
availableEarningsNum: availableEarningsNum, // ✅ 数字格式用于按钮判断
|
||||
pendingWithdrawAmount: formatMoney(pendingWithdrawNum),
|
||||
withdrawnEarnings: formatMoney(realData?.withdrawnEarnings || 0),
|
||||
earnings: formatMoney(realData?.earnings || 0),
|
||||
pendingEarnings: formatMoney(realData?.pendingEarnings || 0),
|
||||
shareRate: realData?.shareRate ?? 90,
|
||||
minWithdrawAmount: minWithdrawAmount,
|
||||
bindingDays: realData?.bindingDays ?? 30,
|
||||
userDiscount: realData?.userDiscount ?? 5,
|
||||
|
||||
// 统计
|
||||
referralCount: realData?.referralCount || realData?.stats?.totalBindings || activeBindings.length + convertedBindings.length,
|
||||
expiringCount,
|
||||
|
||||
referralCode,
|
||||
activeBindings: activeBindings.map(u => formatUser(u, 'active')),
|
||||
convertedBindings: convertedBindings.map(u => formatUser(u, 'converted')),
|
||||
expiredBindings: expiredBindings.map(u => formatUser(u, 'expired')),
|
||||
currentBindings: activeBindings.map(u => formatUser(u, 'active')),
|
||||
totalBindings: activeBindings.length + convertedBindings.length + expiredBindings.length,
|
||||
|
||||
// 收益明细
|
||||
earningsDetails: (realData?.earningsDetails || []).map(item => {
|
||||
// 解析商品描述,获取书名和章节
|
||||
const productInfo = this.parseProductDescription(item.description, item.productType)
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
productType: item.productType,
|
||||
bookTitle: productInfo.bookTitle,
|
||||
chapterTitle: productInfo.chapterTitle,
|
||||
commission: (item.commission || 0).toFixed(2),
|
||||
payTime: item.payTime ? this.formatDate(item.payTime) : '--',
|
||||
buyerNickname: item.buyerNickname || '用户',
|
||||
buyerAvatar: item.buyerAvatar
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 隐藏加载提示
|
||||
wx.hideLoading()
|
||||
} else {
|
||||
// 未登录时也隐藏loading
|
||||
this.setData({ isLoading: false })
|
||||
}
|
||||
},
|
||||
|
||||
// 切换Tab
|
||||
switchTab(e) {
|
||||
trackClick('referral', 'tab_click', e.currentTarget.dataset.tab || 'tab')
|
||||
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() {
|
||||
trackClick('referral', 'btn_click', '复制链接')
|
||||
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() {
|
||||
trackClick('referral', 'btn_click', '分享朋友圈文案')
|
||||
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() {
|
||||
trackClick('referral', 'btn_click', '更多分享')
|
||||
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() {
|
||||
trackClick('referral', 'btn_click', '生成海报')
|
||||
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() {
|
||||
trackClick('referral', 'btn_click', '保存海报')
|
||||
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() {
|
||||
trackClick('referral', 'btn_click', '提现')
|
||||
const availableEarnings = this.data.availableEarningsNum || 0
|
||||
const minWithdrawAmount = this.data.minWithdrawAmount || 10
|
||||
const hasWechatId = this.data.hasWechatId
|
||||
|
||||
if (availableEarnings <= 0) {
|
||||
wx.showToast({ title: '暂无可提现收益', icon: 'none' })
|
||||
return
|
||||
}
|
||||
// 任意金额可提现,不再设最低限额
|
||||
|
||||
// 未绑定微信号时引导去设置
|
||||
if (!hasWechatId) {
|
||||
wx.showModal({
|
||||
title: '请先绑定微信号',
|
||||
content: '提现需先绑定微信号,便于到账核对。请到「设置」中绑定后再提现。',
|
||||
confirmText: '去绑定',
|
||||
cancelText: '取消',
|
||||
success: (res) => {
|
||||
if (res.confirm) wx.navigateTo({ url: '/pages/settings/settings' })
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
wx.showModal({
|
||||
title: '确认提现',
|
||||
content: `将提现 ¥${availableEarnings.toFixed(2)} 到您的微信零钱`,
|
||||
confirmText: '立即提现',
|
||||
success: async (res) => {
|
||||
if (!res.confirm) return
|
||||
const tmplId = app.globalData.withdrawSubscribeTmplId
|
||||
if (tmplId && tmplId.length > 10) {
|
||||
wx.requestSubscribeMessage({
|
||||
tmplIds: [tmplId],
|
||||
success: () => { this.doWithdraw(availableEarnings) },
|
||||
fail: () => { this.doWithdraw(availableEarnings) }
|
||||
})
|
||||
} else {
|
||||
await this.doWithdraw(availableEarnings)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 跳转提现记录页
|
||||
goToWithdrawRecords() {
|
||||
trackClick('referral', 'btn_click', '提现记录')
|
||||
wx.navigateTo({ url: '/pages/withdraw-records/withdraw-records' })
|
||||
},
|
||||
|
||||
// 执行提现
|
||||
async doWithdraw(amount) {
|
||||
wx.showLoading({ title: '提现中...' })
|
||||
|
||||
try {
|
||||
const userId = app.globalData.userInfo?.id
|
||||
if (!userId) {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '请先登录', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
const res = await app.request('/api/miniprogram/withdraw', {
|
||||
method: 'POST',
|
||||
data: { userId, amount }
|
||||
})
|
||||
|
||||
wx.hideLoading()
|
||||
|
||||
if (res.success) {
|
||||
wx.showModal({
|
||||
title: '提现申请已提交 ✅',
|
||||
content: res.message || '正在审核中,通过后会自动到账您的微信零钱',
|
||||
showCancel: false,
|
||||
confirmText: '知道了'
|
||||
})
|
||||
|
||||
// 刷新数据(此时待审核金额会增加,可提现金额会减少)
|
||||
this.initData()
|
||||
} else {
|
||||
if (res.needBind || res.needBindWechat) {
|
||||
wx.showModal({
|
||||
title: res.needBindWechat ? '请先绑定微信号' : '需要绑定微信',
|
||||
content: res.needBindWechat ? '请到「设置」中绑定微信号后再提现,便于到账核对。' : '请先在设置中绑定微信账号后再提现',
|
||||
confirmText: '去绑定',
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) wx.navigateTo({ url: '/pages/settings/settings' })
|
||||
}
|
||||
})
|
||||
} else {
|
||||
wx.showToast({ title: res.message || res.error || '提现失败', icon: 'none', duration: 3000 })
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
wx.hideLoading()
|
||||
console.error('[Referral] 提现失败:', e)
|
||||
wx.showToast({ title: '提现失败,请重试', icon: 'none' })
|
||||
}
|
||||
},
|
||||
|
||||
// 显示通知
|
||||
showNotification() {
|
||||
wx.showToast({ title: '暂无新消息', icon: 'none' })
|
||||
},
|
||||
|
||||
// 显示设置
|
||||
showSettings() {
|
||||
wx.showActionSheet({
|
||||
itemList: ['自动提现设置', '收益通知设置'],
|
||||
success: (res) => {
|
||||
if (res.tapIndex === 0) {
|
||||
this.showAutoWithdrawSettings()
|
||||
} else {
|
||||
this.showNotificationSettings()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 自动提现设置
|
||||
async showAutoWithdrawSettings() {
|
||||
const app = getApp()
|
||||
const { userInfo } = app.globalData
|
||||
|
||||
if (!userInfo) {
|
||||
wx.showToast({ title: '请先登录', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
// 获取当前设置
|
||||
let autoWithdrawEnabled = wx.getStorageSync(`autoWithdraw_${userInfo.id}`) || false
|
||||
let autoWithdrawThreshold = wx.getStorageSync(`autoWithdrawThreshold_${userInfo.id}`) || this.data.minWithdrawAmount || 10
|
||||
|
||||
wx.showModal({
|
||||
title: '自动提现设置',
|
||||
content: `当前状态:${autoWithdrawEnabled ? '已开启' : '已关闭'}\n自动提现阈值:¥${autoWithdrawThreshold}\n\n开启后,当可提现金额达到阈值时将自动发起提现申请。`,
|
||||
confirmText: autoWithdrawEnabled ? '关闭' : '开启',
|
||||
cancelText: '修改阈值',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 切换开关
|
||||
this.toggleAutoWithdraw(!autoWithdrawEnabled, autoWithdrawThreshold)
|
||||
} else if (res.cancel) {
|
||||
// 修改阈值
|
||||
this.setAutoWithdrawThreshold(autoWithdrawEnabled, autoWithdrawThreshold)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 切换自动提现开关
|
||||
toggleAutoWithdraw(enabled, threshold) {
|
||||
const app = getApp()
|
||||
const { userInfo } = app.globalData
|
||||
|
||||
wx.setStorageSync(`autoWithdraw_${userInfo.id}`, enabled)
|
||||
|
||||
wx.showToast({
|
||||
title: enabled ? '自动提现已开启' : '自动提现已关闭',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
// 如果开启,检查当前金额是否达到阈值
|
||||
if (enabled && this.data.availableEarningsNum >= threshold) {
|
||||
wx.showModal({
|
||||
title: '提示',
|
||||
content: `当前可提现金额¥${this.data.availableEarnings}已达到阈值¥${threshold},是否立即提现?`,
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
this.handleWithdraw()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 设置自动提现阈值
|
||||
setAutoWithdrawThreshold(currentEnabled, currentThreshold) {
|
||||
const minAmount = this.data.minWithdrawAmount || 10
|
||||
|
||||
wx.showModal({
|
||||
title: '设置提现阈值',
|
||||
content: `请输入自动提现金额阈值(最低¥${minAmount})`,
|
||||
editable: true,
|
||||
placeholderText: currentThreshold.toString(),
|
||||
success: (res) => {
|
||||
if (res.confirm && res.content) {
|
||||
const threshold = parseFloat(res.content)
|
||||
|
||||
if (isNaN(threshold) || threshold < minAmount) {
|
||||
wx.showToast({
|
||||
title: `请输入不小于¥${minAmount}的金额`,
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const app = getApp()
|
||||
const { userInfo } = app.globalData
|
||||
|
||||
wx.setStorageSync(`autoWithdrawThreshold_${userInfo.id}`, threshold)
|
||||
|
||||
wx.showToast({
|
||||
title: `阈值已设置为¥${threshold}`,
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
// 重新显示设置界面
|
||||
setTimeout(() => {
|
||||
this.showAutoWithdrawSettings()
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 收益通知设置
|
||||
showNotificationSettings() {
|
||||
const app = getApp()
|
||||
const { userInfo } = app.globalData
|
||||
|
||||
if (!userInfo) {
|
||||
wx.showToast({ title: '请先登录', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
// 获取当前设置
|
||||
let notifyEnabled = wx.getStorageSync(`earningsNotify_${userInfo.id}`) !== false // 默认开启
|
||||
|
||||
wx.showModal({
|
||||
title: '收益通知设置',
|
||||
content: `当前状态:${notifyEnabled ? '已开启' : '已关闭'}\n\n开启后,将在有新收益时收到小程序通知提醒。`,
|
||||
confirmText: notifyEnabled ? '关闭通知' : '开启通知',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
const newState = !notifyEnabled
|
||||
wx.setStorageSync(`earningsNotify_${userInfo.id}`, newState)
|
||||
|
||||
wx.showToast({
|
||||
title: newState ? '收益通知已开启' : '收益通知已关闭',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
// 如果开启,请求通知权限
|
||||
if (newState) {
|
||||
wx.requestSubscribeMessage({
|
||||
tmplIds: [''] // 需要配置模板ID
|
||||
}).catch(() => {
|
||||
// 用户拒绝授权,不影响功能
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 分享 - 带推荐码(优先用页面数据,空时用 app.getMyReferralCode)
|
||||
onShareAppMessage() {
|
||||
const ref = this.data.referralCode || app.getMyReferralCode()
|
||||
console.log('[Referral] 分享给好友,推荐码:', ref)
|
||||
return {
|
||||
title: 'Soul创业派对 - 来自派对房的真实商业故事',
|
||||
path: ref ? `/pages/index/index?ref=${ref}` : '/pages/index/index'
|
||||
// 不设置 imageUrl,使用小程序默认截图
|
||||
// 如需自定义图片,请将图片放在 /assets/ 目录并配置路径
|
||||
}
|
||||
},
|
||||
|
||||
// 分享到朋友圈
|
||||
onShareTimeline() {
|
||||
const ref = this.data.referralCode || app.getMyReferralCode()
|
||||
console.log('[Referral] 分享到朋友圈,推荐码:', ref)
|
||||
return {
|
||||
title: `Soul创业派对 - 62个真实商业案例`,
|
||||
query: ref ? `ref=${ref}` : ''
|
||||
// 不设置 imageUrl,使用小程序默认截图
|
||||
}
|
||||
},
|
||||
|
||||
goBack() {
|
||||
getApp().goBackOrToHome()
|
||||
},
|
||||
|
||||
// 解析商品描述,获取书名和章节
|
||||
parseProductDescription(description, productType) {
|
||||
if (!description) {
|
||||
return {
|
||||
bookTitle: '未知商品',
|
||||
chapterTitle: ''
|
||||
}
|
||||
}
|
||||
|
||||
// 匹配格式:《书名》- 章节名
|
||||
const match = description.match(/《(.+?)》(?:\s*-\s*(.+))?/)
|
||||
|
||||
if (match) {
|
||||
return {
|
||||
bookTitle: match[1] || '未知书籍',
|
||||
chapterTitle: match[2] || (productType === 'fullbook' ? '全书购买' : '')
|
||||
}
|
||||
}
|
||||
|
||||
// 如果匹配失败,直接返回原始描述
|
||||
return {
|
||||
bookTitle: description.split('-')[0] || description,
|
||||
chapterTitle: description.split('-')[1] || ''
|
||||
}
|
||||
},
|
||||
|
||||
// 格式化日期
|
||||
formatDate(dateStr) {
|
||||
if (!dateStr) return '--'
|
||||
const d = new Date(dateStr)
|
||||
const month = (d.getMonth() + 1).toString().padStart(2, '0')
|
||||
const day = d.getDate().toString().padStart(2, '0')
|
||||
return `${month}-${day}`
|
||||
}
|
||||
})
|
||||
6
new-soul/miniprogram/pages/referral/referral.json
Normal file
6
new-soul/miniprogram/pages/referral/referral.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"usingComponents": {},
|
||||
"navigationStyle": "custom",
|
||||
"enableShareAppMessage": true,
|
||||
"enableShareTimeline": true
|
||||
}
|
||||
332
new-soul/miniprogram/pages/referral/referral.wxml
Normal file
332
new-soul/miniprogram/pages/referral/referral.wxml
Normal file
@@ -0,0 +1,332 @@
|
||||
<!--分销中心 - 按照Web版本1:1还原-->
|
||||
<view class="page">
|
||||
<!-- 导航栏 -->
|
||||
<view class="nav-bar" style="padding-top: {{statusBarHeight}}px;">
|
||||
<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>
|
||||
<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">
|
||||
<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">{{bindingDays}}天内未付款将解除绑定关系</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">
|
||||
<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>
|
||||
</view>
|
||||
</view>
|
||||
<view class="earnings-right">
|
||||
<text class="earnings-value">¥{{availableEarnings}}</text>
|
||||
<text class="pending-text">累计: ¥{{totalCommission}} | 待审核: ¥{{pendingWithdrawAmount}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="withdraw-btn {{availableEarningsNum <= 0 || !hasWechatId ? 'btn-disabled' : ''}}" bindtap="handleWithdraw">
|
||||
{{availableEarningsNum <= 0 ? '暂无可提现' : !hasWechatId ? '请先绑定微信号' : '申请提现 ¥' + availableEarnings}}
|
||||
</view>
|
||||
<text class="wechat-tip" wx:if="{{availableEarningsNum > 0 && !hasWechatId}}">为便于提现到账,请先到「设置」中绑定微信号</text>
|
||||
<view class="withdraw-records-link" bindtap="goToWithdrawRecords">查看提现记录</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 数据统计 - 对齐 Next.js -->
|
||||
<view class="stats-grid">
|
||||
<view class="stat-card">
|
||||
<text class="stat-value">{{bindingCount}}</text>
|
||||
<text class="stat-label">绑定中</text>
|
||||
</view>
|
||||
<view class="stat-card">
|
||||
<text class="stat-value">{{paidCount}}</text>
|
||||
<text class="stat-label">已付款</text>
|
||||
</view>
|
||||
<view class="stat-card">
|
||||
<text class="stat-value orange">{{unboughtCount}}</text>
|
||||
<text class="stat-label">即将过期</text>
|
||||
</view>
|
||||
<view class="stat-card">
|
||||
<text class="stat-value">{{referralCount}}</text>
|
||||
<text class="stat-label">总邀请</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 推广规则 - 顺序调整到前面 -->
|
||||
<view class="rules-card">
|
||||
<view class="rules-header">
|
||||
<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="gold">立享{{userDiscount}}%优惠</text></text>
|
||||
<text class="rule-item">• 好友成功付款后,你获得 <text class="brand">{{shareRate}}%</text> 收益</text>
|
||||
<text class="rule-item">• 绑定期<text class="brand">{{bindingDays}}天</text>,期满未付款自动解除</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 绑定用户列表 -->
|
||||
<view class="binding-card">
|
||||
<view class="binding-header" bindtap="toggleBindingList">
|
||||
<view class="binding-title">
|
||||
<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>
|
||||
<text class="toggle-icon">{{showBindingList ? '▲' : '▼'}}</text>
|
||||
</view>
|
||||
|
||||
<block wx:if="{{showBindingList}}">
|
||||
<!-- Tab切换 -->
|
||||
<view class="binding-tabs">
|
||||
<view
|
||||
class="tab-item {{activeTab === 'active' ? 'tab-active' : ''}}"
|
||||
bindtap="switchTab"
|
||||
data-tab="active"
|
||||
>绑定中 ({{activeBindings.length}})</view>
|
||||
<view
|
||||
class="tab-item {{activeTab === 'converted' ? 'tab-active' : ''}}"
|
||||
bindtap="switchTab"
|
||||
data-tab="converted"
|
||||
>已付款 ({{convertedBindings.length}})</view>
|
||||
<view
|
||||
class="tab-item {{activeTab === 'expired' ? 'tab-active' : ''}}"
|
||||
bindtap="switchTab"
|
||||
data-tab="expired"
|
||||
>已过期 ({{expiredBindings.length}})</view>
|
||||
</view>
|
||||
|
||||
<!-- 用户列表 -->
|
||||
<view class="binding-list">
|
||||
<block wx:if="{{currentBindings.length === 0}}">
|
||||
<view class="empty-state">
|
||||
<icon name="user" size="80" color="#3a3a3c" customClass="empty-icon"></icon>
|
||||
<text class="empty-text">暂无用户</text>
|
||||
</view>
|
||||
</block>
|
||||
<block wx:else>
|
||||
<view
|
||||
class="binding-item"
|
||||
wx:for="{{currentBindings}}"
|
||||
wx:key="id"
|
||||
>
|
||||
<view class="user-avatar {{item.status === 'converted' ? 'avatar-converted' : item.status === 'expired' ? 'avatar-expired' : ''}}">
|
||||
<text wx:if="{{item.status === 'converted'}}">✓</text>
|
||||
<text wx:elif="{{item.status === 'expired'}}">⏰</text>
|
||||
<text wx:else>{{item.nickname[0] || '用'}}</text>
|
||||
</view>
|
||||
<view class="user-info">
|
||||
<text class="user-name">{{item.nickname || '匿名用户'}}</text>
|
||||
<text class="user-time">绑定于 {{item.bindingDate}}</text>
|
||||
</view>
|
||||
<view class="user-status">
|
||||
<block wx:if="{{item.status === 'converted'}}">
|
||||
<text class="status-amount">+¥{{item.commission}}</text>
|
||||
<text class="status-order">已购{{item.purchaseCount || 1}}次</text>
|
||||
</block>
|
||||
<block wx:elif="{{item.status === 'expired'}}">
|
||||
<text class="status-tag tag-gray">已过期</text>
|
||||
<text class="status-time">{{item.expiryDate}}</text>
|
||||
</block>
|
||||
<block wx:else>
|
||||
<text class="status-tag {{item.daysRemaining <= 3 ? 'tag-red' : item.daysRemaining <= 7 ? 'tag-orange' : 'tag-green'}}">
|
||||
{{item.daysRemaining}}天
|
||||
</text>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<!-- 分享按钮 - 1:1 对齐 Next.js -->
|
||||
<view class="share-section">
|
||||
<view class="share-item" bindtap="generatePoster">
|
||||
<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>
|
||||
<image class="share-arrow-icon" src="/assets/icons/arrow-right.svg" mode="aspectFit"></image>
|
||||
</view>
|
||||
|
||||
<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>
|
||||
<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 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-avatar-wrap">
|
||||
<image
|
||||
class="detail-avatar"
|
||||
wx:if="{{item.buyerAvatar}}"
|
||||
src="{{item.buyerAvatar}}"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view class="detail-avatar-text" wx:else>
|
||||
{{item.buyerNickname.charAt(0)}}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 详细信息 -->
|
||||
<view class="detail-content">
|
||||
<view class="detail-top">
|
||||
<text class="detail-buyer">{{item.buyerNickname}}</text>
|
||||
<text class="detail-amount">+¥{{item.commission}}</text>
|
||||
</view>
|
||||
<view class="detail-product">
|
||||
<text class="detail-book">{{item.bookTitle}}</text>
|
||||
<text class="detail-chapter" wx:if="{{item.chapterTitle}}"> - {{item.chapterTitle}}</text>
|
||||
</view>
|
||||
<text class="detail-time">{{item.payTime}}</text>
|
||||
</view>
|
||||
</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="poster-dialog" catchtap="stopPropagation">
|
||||
<view class="poster-close" bindtap="closePosterModal">✕</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-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">{{userDiscount}}%</text>
|
||||
<text class="poster-stat-label">好友优惠</text>
|
||||
</view>
|
||||
<view class="poster-stat">
|
||||
<text class="poster-stat-value poster-stat-pink">{{shareRate}}%</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">立省{{userDiscount}}%</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>
|
||||
|
||||
<!-- 下半部分:白色操作区 -->
|
||||
<view class="poster-footer">
|
||||
<text class="poster-footer-tip">长按上方图片保存,或截图分享</text>
|
||||
<view class="poster-footer-btn" bindtap="closePosterModal">关闭</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
302
new-soul/miniprogram/pages/referral/referral.wxss
Normal file
302
new-soul/miniprogram/pages/referral/referral.wxss
Normal file
@@ -0,0 +1,302 @@
|
||||
/* ???????? - 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; }
|
||||
.wechat-tip { display: block; font-size: 24rpx; color: rgba(255,165,0,0.9); margin-top: 16rpx; text-align: center; }
|
||||
.withdraw-records-link { display: block; margin-top: 16rpx; text-align: center; font-size: 26rpx; color: #00CED1; }
|
||||
|
||||
/* ???? - ?? 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); gap: 24rpx; }
|
||||
.binding-item:last-child { border-bottom: none; }
|
||||
|
||||
/* ?? */
|
||||
.user-avatar { width: 88rpx; height: 88rpx; border-radius: 50%; background: rgba(0,206,209,0.2); display: flex; align-items: center; justify-content: center; font-size: 32rpx; font-weight: 600; color: #00CED1; 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; min-width: 0; display: flex; flex-direction: column; gap: 4rpx; }
|
||||
.user-name { font-size: 28rpx; color: #fff; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.user-time { font-size: 22rpx; color: rgba(255,255,255,0.5); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
|
||||
/* ???? */
|
||||
.user-status { flex-shrink: 0; text-align: right; display: flex; flex-direction: column; align-items: flex-end; gap: 4rpx; min-width: 100rpx; }
|
||||
.status-amount { font-size: 28rpx; color: #4CAF50; font-weight: 600; white-space: nowrap; }
|
||||
.status-order { font-size: 20rpx; color: rgba(255,255,255,0.5); white-space: nowrap; }
|
||||
.status-time { font-size: 20rpx; color: rgba(255,255,255,0.5); white-space: nowrap; }
|
||||
|
||||
/* ???? */
|
||||
.status-tag { font-size: 24rpx; font-weight: 600; padding: 6rpx 16rpx; border-radius: 16rpx; white-space: nowrap; }
|
||||
.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; }
|
||||
.status-tag.tag-gray { background: rgba(158,158,158,0.2); color: #9E9E9E; }
|
||||
|
||||
/* ????? - ?? 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; }
|
||||
|
||||
/* ?????? - ??? */
|
||||
.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; padding: 16rpx 0; }
|
||||
|
||||
/* ??????? */
|
||||
.earnings-detail-card .detail-item { display: flex; align-items: center; gap: 24rpx; padding: 24rpx 40rpx; background: transparent; border-bottom: 2rpx solid rgba(255,255,255,0.03); }
|
||||
.earnings-detail-card .detail-item:last-child { border-bottom: none; }
|
||||
.earnings-detail-card .detail-item:active { background: rgba(255, 255, 255, 0.05); }
|
||||
|
||||
/* ???? */
|
||||
.earnings-detail-card .detail-avatar-wrap { width: 88rpx; height: 88rpx; flex-shrink: 0; }
|
||||
.earnings-detail-card .detail-avatar { width: 100%; height: 100%; border-radius: 50%; border: 2rpx solid rgba(56, 189, 172, 0.2); }
|
||||
.earnings-detail-card .detail-avatar-text { width: 100%; height: 100%; border-radius: 50%; background: linear-gradient(135deg, #38bdac 0%, #2da396 100%); display: flex; align-items: center; justify-content: center; font-size: 36rpx; font-weight: 700; color: #ffffff; }
|
||||
|
||||
/* ???? */
|
||||
.earnings-detail-card .detail-content { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 8rpx; }
|
||||
.earnings-detail-card .detail-top { display: flex; align-items: center; justify-content: space-between; gap: 16rpx; }
|
||||
.earnings-detail-card .detail-buyer { font-size: 28rpx; font-weight: 500; color: #ffffff; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.earnings-detail-card .detail-amount { font-size: 32rpx; font-weight: 700; color: #38bdac; flex-shrink: 0; white-space: nowrap; }
|
||||
|
||||
/* ???? */
|
||||
.earnings-detail-card .detail-product { display: flex; align-items: baseline; gap: 4rpx; font-size: 24rpx; color: rgba(255, 255, 255, 0.6); min-width: 0; overflow: hidden; }
|
||||
.earnings-detail-card .detail-book { color: rgba(255, 255, 255, 0.7); font-weight: 500; max-width: 50%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.earnings-detail-card .detail-chapter { color: rgba(255, 255, 255, 0.5); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.earnings-detail-card .detail-time { font-size: 22rpx; color: rgba(255, 255, 255, 0.4); }
|
||||
|
||||
/* ???? - ?? 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; }
|
||||
|
||||
| ||||