miniprogram: 用永平版本替换(含超级个体、会员详情、提现等)

- 来源: 一场soul的创业实验-永平/soul/miniprogram
- 新增: addresses/agreement/privacy/withdraw-records 等页面
- 新增: components/icon, utils/chapterAccessManager, readingTracker
- 删除: 上传脚本、部署说明等冗余文件
- 同步永平最新结构和功能

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
卡若
2026-02-24 14:35:58 +08:00
parent b038a042c2
commit e5e6ffd7b1
99 changed files with 8370 additions and 3550 deletions

View File

@@ -1,46 +1,62 @@
/**
* Soul创业派对 - 我的页面
* 开发: 卡若
* 技术支持: 存客宝
*/
const app = getApp()
Page({
data: {
// 系统信息
statusBarHeight: 44,
navBarHeight: 88,
// 用户状态
isLoggedIn: false,
userInfo: null,
// 统计数据
totalSections: 62,
purchasedCount: 0,
readCount: 0,
referralCount: 0,
earnings: 0,
pendingEarnings: 0,
// VIP状态
isVip: false,
vipExpireDate: '',
vipDaysRemaining: 0,
vipPrice: 1980,
earnings: '-',
pendingEarnings: '-',
earningsLoading: true,
earningsRefreshing: false,
// 阅读统计
totalReadTime: 0,
matchHistory: 0,
activeTab: 'overview',
// 最近阅读
recentChapters: [],
// 功能配置
matchEnabled: false,
// VIP状态
isVip: false,
vipExpireDate: '',
// 待确认收款
pendingConfirmList: [],
withdrawMchId: '',
withdrawAppId: '',
// 未登录假资料(展示用)
guestNickname: '游客',
guestAvatar: '',
// 章节映射表id->title
chapterMap: {},
menuList: [
{ id: 'orders', title: '我的订单', icon: '📦', count: 0 },
{ id: 'about', title: '关于作者', icon: '👤', iconBg: 'brand' }
],
// 登录弹窗
showLoginModal: false,
isLoggingIn: false
isLoggingIn: false,
// 用户须主动勾选同意协议(审核要求:不得默认同意)
agreeProtocol: false,
// 修改昵称弹窗
showNicknameModal: false,
editingNickname: ''
},
onLoad() {
@@ -48,182 +64,489 @@ Page({
statusBarHeight: app.globalData.statusBarHeight,
navBarHeight: app.globalData.navBarHeight
})
this.loadChapterMap()
this.loadFeatureConfig()
this.initUserStatus()
},
onShow() {
// 设置TabBar选中状态根据 matchEnabled 动态设置)
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
this.getTabBar().setData({ selected: 3 })
const tabBar = this.getTabBar()
if (tabBar.updateSelected) {
tabBar.updateSelected()
} else {
const selected = tabBar.data.matchEnabled ? 3 : 2
tabBar.setData({ selected })
}
}
this.initUserStatus()
},
// 加载章节名称映射
async loadChapterMap() {
async loadFeatureConfig() {
try {
const res = await app.request('/api/book/all-chapters')
if (res && res.data) {
const map = {}
const sections = Array.isArray(res.data) ? res.data : []
sections.forEach(s => {
if (s.id) map[s.id] = s.title || s.sectionTitle || `章节 ${s.id}`
})
this.setData({ chapterMap: map })
// 有了映射后刷新最近阅读
this.refreshRecentChapters()
}
} catch (e) {
console.log('[My] 加载章节数据失败', e)
const res = await app.request('/api/miniprogram/config')
const features = (res && res.features) || (res && res.data && res.data.features) || {}
this.setData({ matchEnabled: features.matchEnabled === true })
} catch (error) {
console.log('加载功能配置失败:', error)
this.setData({ matchEnabled: false })
}
},
// 刷新最近阅读列表(用真实标题)
refreshRecentChapters() {
const { purchasedSections } = app.globalData
const map = this.data.chapterMap
const recentList = (purchasedSections || []).slice(-5).reverse().map(id => ({
id,
title: map[id] || `章节 ${id}`
}))
this.setData({ recentChapters: recentList })
},
// 初始化用户状态
async initUserStatus() {
const { isLoggedIn, userInfo, hasFullBook, purchasedSections } = app.globalData
initUserStatus() {
const { isLoggedIn, userInfo } = app.globalData
if (isLoggedIn && userInfo) {
const map = this.data.chapterMap
const recentList = (purchasedSections || []).slice(-5).reverse().map(id => ({
id,
title: map[id] || `章节 ${id}`
const readIds = app.globalData.readSectionIds || []
const recentList = readIds.slice(-5).reverse().map(id => ({
id: id,
title: `章节 ${id}`
}))
const userId = userInfo.id || ''
const userIdShort = userId.length > 20 ? userId.slice(0, 10) + '...' + userId.slice(-6) : userId
const userWechat = wx.getStorageSync('user_wechat') || userInfo.wechat || ''
// 先设基础信息;收益由 loadMyEarnings 专用接口拉取,加载前用 - 占位
this.setData({
isLoggedIn: true,
userInfo,
userIdShort,
userWechat,
purchasedCount: hasFullBook ? this.data.totalSections : (purchasedSections?.length || 0),
readCount: Math.min(app.getReadCount(), this.data.totalSections || 62),
referralCount: userInfo.referralCount || 0,
earnings: userInfo.earnings || 0,
pendingEarnings: userInfo.pendingEarnings || 0,
earnings: '-',
pendingEarnings: '-',
earningsLoading: true,
recentChapters: recentList,
totalReadTime: Math.floor(Math.random() * 200) + 50
})
// 查询VIP状态
this.loadVipStatus(userId)
this.loadMyEarnings()
this.loadPendingConfirm()
this.loadVipStatus()
} else {
this.setData({
isLoggedIn: false, userInfo: null, userIdShort: '',
purchasedCount: 0, referralCount: 0, earnings: 0, pendingEarnings: 0,
recentChapters: [], isVip: false
isLoggedIn: false,
userInfo: null,
userIdShort: '',
readCount: app.getReadCount(),
referralCount: 0,
earnings: '-',
pendingEarnings: '-',
earningsLoading: false,
recentChapters: []
})
}
},
// 查询VIP状态
async loadVipStatus(userId) {
// 拉取待确认收款列表(用于「确认收款」按钮)
async loadPendingConfirm() {
const userInfo = app.globalData.userInfo
if (!app.globalData.isLoggedIn || !userInfo || !userInfo.id) return
try {
const res = await app.request(`/api/vip/status?userId=${userId}`)
if (res && res.success) {
const res = await app.request({ url: '/api/miniprogram/withdraw/pending-confirm?userId=' + userInfo.id, silent: true })
if (res && res.success && res.data) {
const list = (res.data.list || []).map(item => ({
id: item.id,
amount: (item.amount || 0).toFixed(2),
package: item.package,
createdAt: (item.createdAt ?? item.created_at) ? this.formatDateMy(item.createdAt ?? item.created_at) : '--'
}))
this.setData({
isVip: res.data.isVip,
vipExpireDate: res.data.expireDate || '',
vipDaysRemaining: res.data.daysRemaining || 0,
vipPrice: res.data.price || 1980
pendingConfirmList: list,
withdrawMchId: res.data.mchId ?? res.data.mch_id ?? '',
withdrawAppId: res.data.appId ?? res.data.app_id ?? ''
})
} else {
this.setData({ pendingConfirmList: [], withdrawMchId: '', withdrawAppId: '' })
}
} catch (e) {
console.log('[My] VIP状态查询失败', e)
this.setData({ pendingConfirmList: [] })
}
},
// 微信原生获取头像
async onChooseAvatar(e) {
const avatarUrl = e.detail.avatarUrl
if (!avatarUrl) return
wx.showLoading({ title: '更新中...', mask: true })
formatDateMy(dateStr) {
if (!dateStr) return '--'
const d = new Date(dateStr)
const m = (d.getMonth() + 1).toString().padStart(2, '0')
const day = d.getDate().toString().padStart(2, '0')
return `${m}-${day}`
},
// 确认收款:有 package 时调起微信收款页,成功后记录;无 package 时仅调用后端记录「已确认收款」
async confirmReceive(e) {
const index = e.currentTarget.dataset.index
const id = e.currentTarget.dataset.id
const list = this.data.pendingConfirmList || []
let item = (typeof index === 'number' || (index !== undefined && index !== '')) ? list[index] : null
if (!item && id) item = list.find(x => x.id === id) || null
if (!item) {
wx.showToast({ title: '请稍后刷新再试', icon: 'none' })
return
}
const mchId = this.data.withdrawMchId
const appId = this.data.withdrawAppId
const hasPackage = item.package && mchId && appId && wx.canIUse('requestMerchantTransfer')
const recordConfirmReceived = async () => {
const userInfo = app.globalData.userInfo
if (userInfo && userInfo.id) {
try {
await app.request({
url: '/api/miniprogram/withdraw/confirm-received',
method: 'POST',
data: { withdrawalId: item.id, userId: userInfo.id }
})
} catch (e) { /* 仅记录,不影响前端展示 */ }
}
const newList = list.filter(x => x.id !== item.id)
this.setData({ pendingConfirmList: newList })
this.loadPendingConfirm()
}
if (hasPackage) {
wx.showLoading({ title: '调起收款...', mask: true })
wx.requestMerchantTransfer({
mchId,
appId,
package: item.package,
success: async () => {
wx.hideLoading()
wx.showToast({ title: '收款成功', icon: 'success' })
await recordConfirmReceived()
},
fail: (err) => {
wx.hideLoading()
const msg = (err.errMsg || '').includes('cancel') ? '已取消' : (err.errMsg || '收款失败')
wx.showToast({ title: msg, icon: 'none' })
},
complete: () => { wx.hideLoading() }
})
return
}
// 无 package 时仅记录「确认已收款」(当前直接打款无 package用户点按钮即记录
wx.showLoading({ title: '提交中...', mask: true })
try {
await recordConfirmReceived()
wx.hideLoading()
wx.showToast({ title: '已记录确认收款', icon: 'success' })
} catch (e) {
wx.hideLoading()
wx.showToast({ title: (e && e.message) || '操作失败', icon: 'none' })
}
},
// 专用接口:拉取「我的收益」卡片数据(累计、可提现、推荐人数)
async loadMyEarnings() {
const userInfo = app.globalData.userInfo
if (!app.globalData.isLoggedIn || !userInfo || !userInfo.id) {
this.setData({ earningsLoading: false })
return
}
const formatMoney = (num) => (typeof num === 'number' ? num.toFixed(2) : '0.00')
try {
const res = await app.request({ url: '/api/miniprogram/earnings?userId=' + userInfo.id, silent: true })
if (!res || !res.success || !res.data) {
this.setData({ earningsLoading: false, earnings: '0.00', pendingEarnings: '0.00' })
return
}
const d = res.data
this.setData({
earnings: formatMoney(d.totalCommission),
pendingEarnings: formatMoney(d.availableEarnings),
referralCount: d.referralCount ?? this.data.referralCount,
earningsLoading: false,
earningsRefreshing: false
})
} catch (e) {
console.log('[My] 拉取我的收益失败:', e && e.message)
this.setData({
earningsLoading: false,
earningsRefreshing: false,
earnings: '0.00',
pendingEarnings: '0.00'
})
}
},
// 点击刷新图标:刷新我的收益
async refreshEarnings() {
if (!this.data.isLoggedIn) return
if (this.data.earningsRefreshing) return
this.setData({ earningsRefreshing: true })
wx.showToast({ title: '刷新中...', icon: 'loading', duration: 2000 })
await this.loadMyEarnings()
wx.showToast({ title: '已刷新', icon: 'success' })
},
// 微信原生获取头像button open-type="chooseAvatar" 回调)
async onChooseAvatar(e) {
const tempAvatarUrl = e.detail.avatarUrl
if (!tempAvatarUrl) return
wx.showLoading({ title: '上传中...', mask: true })
try {
// 1. 先上传图片到服务器
console.log('[My] 开始上传头像:', tempAvatarUrl)
const uploadRes = await new Promise((resolve, reject) => {
wx.uploadFile({
url: app.globalData.baseUrl + '/api/miniprogram/upload',
filePath: tempAvatarUrl,
name: 'file',
formData: {
folder: 'avatars'
},
success: (res) => {
try {
const data = JSON.parse(res.data)
if (data.success) {
resolve(data)
} else {
reject(new Error(data.error || '上传失败'))
}
} catch (err) {
reject(new Error('解析响应失败'))
}
},
fail: (err) => {
reject(err)
}
})
})
// 2. 获取上传后的完整URL
const avatarUrl = app.globalData.baseUrl + uploadRes.data.url
console.log('[My] 头像上传成功:', avatarUrl)
// 3. 更新本地头像
const userInfo = this.data.userInfo
userInfo.avatar = avatarUrl
this.setData({ userInfo })
app.globalData.userInfo = userInfo
wx.setStorageSync('userInfo', userInfo)
await app.request('/api/user/update', {
method: 'POST', data: { userId: userInfo.id, avatar: avatarUrl }
// 4. 同步到服务器数据库
await app.request('/api/miniprogram/user/update', {
method: 'POST',
data: { userId: userInfo.id, avatar: avatarUrl }
})
wx.hideLoading()
wx.showToast({ title: '头像已获取', icon: 'success' })
wx.showToast({ title: '头像更新成功', icon: 'success' })
} catch (e) {
wx.hideLoading()
wx.showToast({ title: '头像已更新', icon: 'success' })
console.error('[My] 上传头像失败:', e)
wx.showToast({
title: e.message || '上传失败,请重试',
icon: 'none'
})
}
},
// 点击昵称修改
// 微信原生获取昵称回调(针对 input type="nickname" 的 bindblur 或 bindchange
async handleNicknameChange(nickname) {
if (!nickname || nickname === this.data.userInfo?.nickname) return
try {
const userInfo = this.data.userInfo
userInfo.nickname = nickname
this.setData({ userInfo })
app.globalData.userInfo = userInfo
wx.setStorageSync('userInfo', userInfo)
// 同步到服务器
await app.request('/api/miniprogram/user/update', {
method: 'POST',
data: { userId: userInfo.id, nickname }
})
wx.showToast({ title: '昵称已更新', icon: 'success' })
} catch (e) {
console.error('[My] 同步昵称失败:', e)
}
},
// 打开昵称修改弹窗
editNickname() {
wx.showModal({
title: '修改昵称',
editable: true,
placeholderText: '请输入昵称',
success: async (res) => {
if (res.confirm && res.content) {
const newNickname = res.content.trim()
if (newNickname.length < 1 || newNickname.length > 20) {
wx.showToast({ title: '昵称1-20个字符', icon: 'none' }); return
}
const userInfo = this.data.userInfo
userInfo.nickname = newNickname
this.setData({ userInfo })
app.globalData.userInfo = userInfo
wx.setStorageSync('userInfo', userInfo)
try {
await app.request('/api/user/update', {
method: 'POST', data: { userId: userInfo.id, nickname: newNickname }
})
} catch (e) { console.log('同步昵称失败', e) }
wx.showToast({ title: '昵称已更新', icon: 'success' })
this.setData({
showNicknameModal: true,
editingNickname: this.data.userInfo?.nickname || ''
})
},
// 关闭昵称弹窗
closeNicknameModal() {
this.setData({
showNicknameModal: false,
editingNickname: ''
})
},
// 阻止事件冒泡
stopPropagation() {},
// 昵称输入实时更新
onNicknameInput(e) {
this.setData({
editingNickname: e.detail.value
})
},
// 昵称变化(微信自动填充时触发)
onNicknameChange(e) {
const nickname = e.detail.value
console.log('[My] 昵称已自动填充:', nickname)
this.setData({
editingNickname: nickname
})
// 自动填充时也尝试直接同步
this.handleNicknameChange(nickname)
},
// 确认修改昵称
async confirmNickname() {
const newNickname = this.data.editingNickname.trim()
if (!newNickname) {
wx.showToast({ title: '昵称不能为空', icon: 'none' })
return
}
if (newNickname.length < 1 || newNickname.length > 20) {
wx.showToast({ title: '昵称1-20个字符', icon: 'none' })
return
}
// 关闭弹窗
this.closeNicknameModal()
// 显示加载
wx.showLoading({ title: '更新中...', mask: true })
try {
// 1. 同步到服务器
const res = await app.request('/api/miniprogram/user/update', {
method: 'POST',
data: {
userId: this.data.userInfo.id,
nickname: newNickname
}
})
if (res && res.success) {
// 2. 更新本地状态
const userInfo = this.data.userInfo
userInfo.nickname = newNickname
this.setData({ userInfo })
// 3. 更新全局和缓存
app.globalData.userInfo = userInfo
wx.setStorageSync('userInfo', userInfo)
wx.hideLoading()
wx.showToast({ title: '昵称已修改', icon: 'success' })
} else {
throw new Error(res?.message || '更新失败')
}
} catch (e) {
wx.hideLoading()
console.error('[My] 修改昵称失败:', e)
wx.showToast({ title: '修改失败,请重试', icon: 'none' })
}
},
// 复制用户ID
copyUserId() {
const userId = this.data.userInfo?.id || ''
if (!userId) {
wx.showToast({ title: '暂无ID', icon: 'none' })
return
}
wx.setClipboardData({
data: userId,
success: () => {
wx.showToast({ title: 'ID已复制', icon: 'success' })
}
})
},
copyUserId() {
const userId = this.data.userInfo?.id || ''
if (!userId) { wx.showToast({ title: '暂无ID', icon: 'none' }); return }
wx.setClipboardData({ data: userId, success: () => wx.showToast({ title: 'ID已复制', icon: 'success' }) })
// 切换Tab
switchTab(e) {
const tab = e.currentTarget.dataset.tab
this.setData({ activeTab: tab })
},
switchTab(e) { this.setData({ activeTab: e.currentTarget.dataset.tab }) },
showLogin() { this.setData({ showLoginModal: true }) },
closeLoginModal() { if (!this.data.isLoggingIn) this.setData({ showLoginModal: false }) },
// 显示登录弹窗(每次打开时协议未勾选,符合审核要求)
showLogin() {
try {
this.setData({ showLoginModal: true, agreeProtocol: false })
} catch (e) {
console.error('[My] showLogin error:', e)
this.setData({ showLoginModal: true })
}
},
// 切换协议勾选(用户主动勾选,非默认同意)
toggleAgree() {
this.setData({ agreeProtocol: !this.data.agreeProtocol })
},
// 打开用户协议页(审核要求:点击《用户协议》需有响应)
openUserProtocol() {
wx.navigateTo({ url: '/pages/agreement/agreement' })
},
// 打开隐私政策页(审核要求:点击《隐私政策》需有响应)
openPrivacy() {
wx.navigateTo({ url: '/pages/privacy/privacy' })
},
// 关闭登录弹窗
closeLoginModal() {
if (this.data.isLoggingIn) return
this.setData({ showLoginModal: false })
},
// 微信登录(须已勾选同意协议,且做好错误处理避免审核报错)
async handleWechatLogin() {
if (!this.data.agreeProtocol) {
wx.showToast({ title: '请先阅读并同意用户协议和隐私政策', icon: 'none' })
return
}
this.setData({ isLoggingIn: true })
try {
const result = await app.login()
if (result) {
this.initUserStatus()
this.setData({ showLoginModal: false })
this.setData({ showLoginModal: false, agreeProtocol: false })
wx.showToast({ title: '登录成功', icon: 'success' })
} else {
wx.showToast({ title: '登录失败,请重试', icon: 'none' })
}
} catch (e) {
console.error('[My] 微信登录错误:', e)
wx.showToast({ title: '登录失败,请重试', icon: 'none' })
} finally { this.setData({ isLoggingIn: false }) }
} finally {
this.setData({ isLoggingIn: false })
}
},
// 手机号登录(需要用户授权)
async handlePhoneLogin(e) {
if (!e.detail.code) return this.handleWechatLogin()
// 检查是否有授权code
if (!e.detail.code) {
// 用户拒绝授权或获取失败,尝试使用微信登录
console.log('手机号授权失败,尝试微信登录')
return this.handleWechatLogin()
}
this.setData({ isLoggingIn: true })
try {
const result = await app.loginWithPhone(e.detail.code)
if (result) {
@@ -234,71 +557,75 @@ Page({
wx.showToast({ title: '登录失败,请重试', icon: 'none' })
}
} catch (e) {
console.error('手机号登录错误:', e)
wx.showToast({ title: '登录失败,请重试', icon: 'none' })
} finally { this.setData({ isLoggingIn: false }) }
} finally {
this.setData({ isLoggingIn: false })
}
},
// 点击菜单
handleMenuTap(e) {
const id = e.currentTarget.dataset.id
if (!this.data.isLoggedIn && id !== 'about') { this.showLogin(); return }
const routes = { orders: '/pages/purchases/purchases', about: '/pages/about/about' }
if (routes[id]) wx.navigateTo({ url: routes[id] })
if (!this.data.isLoggedIn && id !== 'about') {
this.showLogin()
return
}
const routes = {
orders: '/pages/purchases/purchases',
referral: '/pages/referral/referral',
withdrawRecords: '/pages/withdraw-records/withdraw-records',
about: '/pages/about/about',
settings: '/pages/settings/settings'
}
if (routes[id]) {
wx.navigateTo({ url: routes[id] })
}
},
// 跳转VIP页面
goToVip() {
if (!this.data.isLoggedIn) { this.showLogin(); return }
wx.navigateTo({ url: '/pages/vip/vip' })
// 跳转到阅读页
goToRead(e) {
const id = e.currentTarget.dataset.id
wx.navigateTo({ url: `/pages/read/read?id=${id}` })
},
bindWechat() {
wx.showModal({
title: '绑定微信号', editable: true, placeholderText: '请输入微信号',
success: async (res) => {
if (res.confirm && res.content) {
const wechat = res.content.trim()
if (!wechat) return
try {
wx.setStorageSync('user_wechat', wechat)
const userInfo = this.data.userInfo
userInfo.wechat = wechat
this.setData({ userInfo, userWechat: wechat })
app.globalData.userInfo = userInfo
wx.setStorageSync('userInfo', userInfo)
await app.request('/api/user/update', {
method: 'POST', data: { userId: userInfo.id, wechat }
})
wx.showToast({ title: '绑定成功', icon: 'success' })
} catch (e) { wx.showToast({ title: '已保存到本地', icon: 'success' }) }
}
}
})
// 跳转到目录
goToChapters() {
wx.switchTab({ url: '/pages/chapters/chapters' })
},
clearCache() {
wx.showModal({
title: '清除缓存', content: '确定要清除本地缓存吗?不会影响账号数据',
success: (res) => {
if (res.confirm) {
const userInfo = wx.getStorageSync('userInfo')
const token = wx.getStorageSync('token')
wx.clearStorageSync()
if (userInfo) wx.setStorageSync('userInfo', userInfo)
if (token) wx.setStorageSync('token', token)
wx.showToast({ title: '缓存已清除', icon: 'success' })
}
}
})
// 跳转到关于页
goToAbout() {
wx.navigateTo({ url: '/pages/about/about' })
},
goToRead(e) { wx.navigateTo({ url: `/pages/read/read?id=${e.currentTarget.dataset.id}` }) },
goToChapters() { wx.switchTab({ url: '/pages/chapters/chapters' }) },
goToAbout() { wx.navigateTo({ url: '/pages/about/about' }) },
goToMatch() { wx.switchTab({ url: '/pages/match/match' }) },
// 跳转到匹配
goToMatch() {
wx.switchTab({ url: '/pages/match/match' })
},
// 跳转到推广中心
goToReferral() {
if (!this.data.isLoggedIn) {
this.showLogin()
return
}
wx.navigateTo({ url: '/pages/referral/referral' })
},
// 跳转到找伙伴页面
goToMatch() {
wx.switchTab({ url: '/pages/match/match' })
},
// 退出登录
handleLogout() {
wx.showModal({
title: '退出登录', content: '确定要退出登录吗?',
title: '退出登录',
content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
app.logout()
@@ -309,5 +636,80 @@ Page({
})
},
// VIP状态查询
async loadVipStatus() {
const userId = app.globalData.userInfo?.id
if (!userId) return
try {
const res = await app.request({ url: `/api/vip/status?userId=${userId}`, silent: true })
if (res?.success) {
this.setData({ isVip: res.data?.isVip, vipExpireDate: res.data?.expireDate || '' })
}
} catch (e) { console.log('[My] VIP查询失败', e) }
},
// 头像点击:已登录弹出选项(改头像/进VIP
onAvatarTap() {
if (!this.data.isLoggedIn) { this.showLogin(); return }
wx.showActionSheet({
itemList: ['获取微信头像', '开通/管理VIP'],
success: (res) => {
if (res.tapIndex === 0) this.chooseAvatarFallback()
if (res.tapIndex === 1) this.goToVip()
}
})
},
chooseAvatarFallback() {
wx.chooseMedia({
count: 1, mediaType: ['image'], sourceType: ['album', 'camera'],
success: async (res) => {
const tempPath = res.tempFiles[0].tempFilePath
const userInfo = this.data.userInfo
userInfo.avatar = tempPath
this.setData({ userInfo })
app.globalData.userInfo = userInfo
wx.setStorageSync('userInfo', userInfo)
try {
await app.request('/api/user/update', { method: 'POST', data: { userId: userInfo.id, avatar: tempPath } })
} catch (e) { console.log('头像同步失败', e) }
wx.showToast({ title: '头像已更新', icon: 'success' })
}
})
},
goToVip() {
if (!this.data.isLoggedIn) { this.showLogin(); return }
wx.navigateTo({ url: '/pages/vip/vip' })
},
async handleWithdraw() {
if (!this.data.isLoggedIn) { this.showLogin(); return }
const amount = parseFloat(this.data.pendingEarnings)
if (isNaN(amount) || amount <= 0) {
wx.showToast({ title: '暂无可提现金额', icon: 'none' })
return
}
wx.showModal({
title: '申请提现',
content: `确认提现 ¥${amount.toFixed(2)} `,
success: async (res) => {
if (!res.confirm) return
wx.showLoading({ title: '提交中...', mask: true })
try {
const userId = app.globalData.userInfo?.id
await app.request({ url: '/api/withdraw', method: 'POST', data: { userId, amount } })
wx.hideLoading()
wx.showToast({ title: '提现申请已提交', icon: 'success' })
this.loadMyEarnings()
} catch (e) {
wx.hideLoading()
wx.showToast({ title: e.message || '提现失败', icon: 'none' })
}
}
})
},
// 阻止冒泡
stopPropagation() {}
})

View File

@@ -1,143 +1,131 @@
<!--Soul创业实验 - 我的页面-->
<!--pages/my/my.wxml-->
<!--Soul创业实验 - 我的页面 1:1还原Web版本-->
<view class="page page-transition">
<!-- 自定义导航栏 -->
<view class="nav-bar" style="padding-top: {{statusBarHeight}}px;">
<view class="nav-content">
<text class="nav-title brand-color">我的</text>
<text class="nav-title-left brand-color">我的</text>
</view>
</view>
<!-- 导航栏占位 -->
<view class="nav-placeholder" style="height: {{statusBarHeight + 44}}px;"></view>
<!-- 未登录 -->
<view class="user-card card-gradient login-card" wx:if="{{!isLoggedIn}}">
<view class="login-prompt">
<view class="login-icon-large">🔐</view>
<text class="login-title">登录 Soul创业实验</text>
<text class="login-desc">登录后可购买章节、解锁更多内容</text>
<button class="btn-wechat-large" bindtap="handleWechatLogin">
<text class="btn-icon">微</text>
<text>微信快捷登录</text>
</button>
</view>
</view>
<!-- 已登录 - 用户卡片 -->
<view class="user-card card-gradient" wx:else>
<!-- 用户卡片 - 未登录:假资料界面,名字旁点击登录打开弹窗 -->
<view class="user-card card-gradient" wx:if="{{!isLoggedIn}}">
<view class="user-header-row">
<button class="avatar-btn-simple" open-type="chooseAvatar" bindchooseavatar="onChooseAvatar">
<view class="avatar {{isVip ? 'avatar-vip' : 'avatar-normal'}}">
<image class="avatar-img" wx:if="{{userInfo.avatar}}" src="{{userInfo.avatar}}" mode="aspectFill"/>
<text class="avatar-text" wx:else>{{userInfo.nickname[0] || '微'}}</text>
<view class="vip-badge" wx:if="{{isVip}}">VIP</view>
</view>
</button>
<view class="avatar avatar-placeholder">
<image class="avatar-img" wx:if="{{guestAvatar}}" src="{{guestAvatar}}" mode="aspectFill"/>
<text class="avatar-text" wx:else>{{guestNickname[0] || '游'}}</text>
</view>
<view class="user-info-block">
<view class="user-name-row">
<text class="user-name" bindtap="editNickname">{{userInfo.nickname || '点击设置昵称'}}</text>
<text class="edit-icon-small">✎</text>
<view class="vip-tag" wx:if="{{isVip}}">创业伙伴</view>
<text class="user-name">{{guestNickname}}</text>
<view class="btn-login-inline" bindtap="showLogin">点击登录</view>
</view>
<view class="user-id-row" bindtap="copyUserId">
<text class="user-id">{{userWechat ? '微信: ' + userWechat : 'ID: ' + userIdShort}}</text>
<text class="copy-icon">📋</text>
<view class="user-id-row">
<text class="user-id user-id-guest">登录后查看完整信息</text>
</view>
</view>
</view>
<view class="stats-grid">
<view class="stat-item">
<text class="stat-value brand-color">{{purchasedCount}}</text>
<text class="stat-label">已章节</text>
<text class="stat-value brand-color">--</text>
<text class="stat-label">已章节</text>
</view>
<view class="stat-item">
<text class="stat-value brand-color">--</text>
<text class="stat-label">推荐好友</text>
</view>
<view class="stat-item">
<text class="stat-value gold-color">--</text>
<text class="stat-label">待领收益</text>
</view>
</view>
</view>
<!-- 用户卡片 - 已登录状态 -->
<view class="user-card card-gradient" wx:else>
<view class="user-header-row">
<!-- 头像 - 点击进VIP/设置头像 -->
<view class="avatar-wrap" bindtap="onAvatarTap">
<view class="avatar {{isVip ? 'avatar-vip' : ''}}">
<image class="avatar-img" wx:if="{{userInfo.avatar}}" src="{{userInfo.avatar}}" mode="aspectFill"/>
<text class="avatar-text" wx:else>{{userInfo.nickname[0] || '用'}}</text>
</view>
<view class="vip-badge" wx:if="{{isVip}}">VIP</view>
<view class="vip-badge vip-badge-gray" wx:else bindtap="goToVip">VIP</view>
</view>
<!-- 用户信息 -->
<view class="user-info-block">
<view class="user-name-row">
<text class="user-name" bindtap="editNickname">{{userInfo.nickname || '点击设置昵称'}}</text>
<view class="vip-tags-row">
<view class="vip-tag-mini {{isVip ? 'vip-tag-active' : ''}}" bindtap="goToVip">会员</view>
<view class="vip-tag-mini {{isVip ? 'vip-tag-active' : ''}}" bindtap="goToVip">匹配</view>
<view class="vip-tag-mini {{isVip ? 'vip-tag-active' : ''}}" bindtap="goToVip">排行</view>
</view>
<view class="become-vip-chip" wx:if="{{!isVip}}" bindtap="goToVip">
<text class="chip-star">⭐</text><text class="chip-text">成为会员</text>
</view>
</view>
<view class="user-id-row" bindtap="copyUserId">
<text class="user-id">{{userWechat ? '微信: ' + userWechat : 'ID: ' + userIdShort}}</text>
</view>
</view>
</view>
<view class="stats-grid">
<view class="stat-item">
<text class="stat-value brand-color">{{readCount}}</text>
<text class="stat-label">已读章节</text>
</view>
<view class="stat-item">
<text class="stat-value brand-color">{{referralCount}}</text>
<text class="stat-label">推荐好友</text>
</view>
<view class="stat-item">
<view class="stat-item" bindtap="goToReferral">
<text class="stat-value gold-color">{{pendingEarnings > 0 ? '¥' + pendingEarnings : '--'}}</text>
<text class="stat-label">我的收益</text>
</view>
</view>
</view>
<!-- VIP入口卡片 -->
<view class="vip-card" wx:if="{{isLoggedIn}}" bindtap="goToVip">
<view class="vip-card-inner" wx:if="{{!isVip}}">
<view class="vip-card-left">
<text class="vip-card-icon">👑</text>
<view class="vip-card-info">
<text class="vip-card-title">开通VIP会员</text>
<text class="vip-card-desc">解锁全部章节 · 匹配创业伙伴</text>
</view>
</view>
<view class="vip-card-price">
<text class="vip-price-num">¥{{vipPrice}}</text>
<text class="vip-price-unit">/年</text>
</view>
</view>
<view class="vip-card-inner vip-active" wx:else>
<view class="vip-card-left">
<text class="vip-card-icon">👑</text>
<view class="vip-card-info">
<text class="vip-card-title gold-color">VIP会员</text>
<text class="vip-card-desc">剩余 {{vipDaysRemaining}} 天</text>
</view>
</view>
<text class="vip-manage-btn">管理 →</text>
</view>
</view>
<!-- Tab切换 -->
<view class="tab-bar-custom" wx:if="{{isLoggedIn}}">
<view class="tab-item {{activeTab === 'overview' ? 'tab-active' : ''}}" bindtap="switchTab" data-tab="overview">概览</view>
<view class="tab-item {{activeTab === 'footprint' ? 'tab-active' : ''}}" bindtap="switchTab" data-tab="footprint">
<text class="tab-icon">👣</text><text>我的足迹</text>
</view>
</view>
<!-- 概览内容 -->
<view class="tab-content" wx:if="{{activeTab === 'overview' && isLoggedIn}}">
<!-- 统一内容区 - 仅登录用户显示 -->
<view class="tab-content" wx:if="{{isLoggedIn}}">
<!-- 菜单:我的订单 + 设置 -->
<view class="menu-card card">
<view class="menu-item" wx:for="{{menuList}}" wx:key="id" bindtap="handleMenuTap" data-id="{{item.id}}">
<view class="menu-item" bindtap="handleMenuTap" data-id="orders">
<view class="menu-left">
<view class="menu-icon {{item.iconBg === 'brand' ? 'icon-brand' : ''}}">{{item.icon}}</view>
<text class="menu-title">{{item.title}}</text>
<view class="menu-icon icon-brand">📦</view>
<text class="menu-title">我的订单</text>
</view>
<view class="menu-right">
<text class="menu-arrow">→</text>
</view>
</view>
<view class="menu-item" bindtap="handleMenuTap" data-id="settings">
<view class="menu-left">
<view class="menu-icon icon-gray">⚙️</view>
<text class="menu-title">设置</text>
</view>
<view class="menu-right">
<text class="menu-count" wx:if="{{item.count !== undefined}}">{{item.count}}笔</text>
<text class="menu-arrow">→</text>
</view>
</view>
</view>
<view class="settings-card card">
<view class="card-title"><text class="title-icon">⚙️</text><text>账号设置</text></view>
<view class="settings-list">
<view class="settings-item" bindtap="bindWechat">
<text class="settings-label">绑定微信号</text>
<view class="settings-right">
<text class="settings-value">{{userWechat || '未绑定'}}</text>
<text class="menu-arrow">→</text>
</view>
</view>
<view class="settings-item" bindtap="clearCache">
<text class="settings-label">清除缓存</text>
<text class="menu-arrow">→</text>
</view>
<view class="settings-item logout-item" bindtap="handleLogout">
<text class="settings-label logout-text">退出登录</text>
</view>
</view>
</view>
</view>
<!-- 足迹内容 -->
<view class="tab-content" wx:if="{{activeTab === 'footprint' && isLoggedIn}}">
<!-- 阅读统计 -->
<view class="stats-card card">
<view class="card-title"><text class="title-icon">👁️</text><text>阅读统计</text></view>
<view class="card-title">
<text class="title-icon">👁️</text>
<text>阅读统计</text>
</view>
<view class="stats-row">
<view class="stat-box">
<text class="stat-icon brand-color">📖</text>
<text class="stat-num">{{purchasedCount}}</text>
<text class="stat-num">{{readCount}}</text>
<text class="stat-text">已读章节</text>
</view>
<view class="stat-box">
@@ -145,7 +133,7 @@
<text class="stat-num">{{totalReadTime}}</text>
<text class="stat-text">阅读分钟</text>
</view>
<view class="stat-box">
<view class="stat-box" wx:if="{{matchEnabled}}">
<text class="stat-icon pink-color">👥</text>
<text class="stat-num">{{matchHistory}}</text>
<text class="stat-text">匹配伙伴</text>
@@ -153,10 +141,20 @@
</view>
</view>
<!-- 最近阅读 -->
<view class="recent-card card">
<view class="card-title"><text class="title-icon">📖</text><text>最近阅读</text></view>
<view class="card-title">
<text class="title-icon">📖</text>
<text>最近阅读</text>
</view>
<view class="recent-list" wx:if="{{recentChapters.length > 0}}">
<view class="recent-item" wx:for="{{recentChapters}}" wx:key="id" bindtap="goToRead" data-id="{{item.id}}">
<view
class="recent-item"
wx:for="{{recentChapters}}"
wx:key="id"
bindtap="goToRead"
data-id="{{item.id}}"
>
<view class="recent-left">
<text class="recent-index">{{index + 1}}</text>
<text class="recent-title">{{item.title}}</text>
@@ -171,30 +169,78 @@
</view>
</view>
<view class="match-card card">
<view class="card-title"><text class="title-icon">👥</text><text>匹配记录</text></view>
<view class="empty-state">
<text class="empty-icon">👥</text>
<text class="empty-text">暂无匹配记录</text>
<view class="empty-btn" bindtap="goToMatch">去匹配 →</view>
<!-- 关于作者(最底部) -->
<view class="menu-card card" style="margin-top: 16rpx;">
<view class="menu-item" bindtap="handleMenuTap" data-id="about">
<view class="menu-left">
<view class="menu-icon icon-brand"></view>
<text class="menu-title">关于作者</text>
</view>
<view class="menu-right">
<text class="menu-arrow">→</text>
</view>
</view>
</view>
</view>
<!-- 登录弹窗 -->
<!-- 登录弹窗:可取消,用户主动选择是否登录 -->
<view class="modal-overlay" wx:if="{{showLoginModal}}" bindtap="closeLoginModal">
<view class="modal-content" catchtap="stopPropagation">
<view class="modal-content login-modal-content" catchtap="stopPropagation">
<view class="modal-close" bindtap="closeLoginModal">✕</view>
<view class="login-icon">🔐</view>
<text class="login-title">登录 Soul创业实验</text>
<text class="login-desc">登录后可购买章节、解锁更多内容</text>
<button class="btn-wechat" bindtap="handleWechatLogin" disabled="{{isLoggingIn}}">
<button
class="btn-wechat {{agreeProtocol ? '' : 'btn-wechat-disabled'}}"
bindtap="handleWechatLogin"
disabled="{{isLoggingIn || !agreeProtocol}}"
>
<text class="btn-wechat-icon">微</text>
<text>{{isLoggingIn ? '登录中...' : '微信快捷登录'}}</text>
</button>
<text class="login-notice">登录即表示同意《用户协议》和《隐私政策》</text>
<view class="login-modal-cancel" bindtap="closeLoginModal">取消</view>
<view class="login-agree-row" catchtap="toggleAgree">
<view class="agree-checkbox {{agreeProtocol ? 'agree-checked' : ''}}">{{agreeProtocol ? '✓' : ''}}</view>
<text class="agree-text">我已阅读并同意</text>
<text class="agree-link" catchtap="openUserProtocol">《用户协议》</text>
<text class="agree-text">和</text>
<text class="agree-link" catchtap="openPrivacy">《隐私政策》</text>
</view>
</view>
</view>
<!-- 修改昵称弹窗 -->
<view class="modal-overlay" wx:if="{{showNicknameModal}}" bindtap="closeNicknameModal">
<view class="modal-content nickname-modal" catchtap="stopPropagation">
<view class="modal-close" bindtap="closeNicknameModal">✕</view>
<view class="modal-header">
<text class="modal-icon">✏️</text>
<text class="modal-title">修改昵称</text>
</view>
<view class="nickname-input-wrap">
<input
class="nickname-input"
type="nickname"
value="{{editingNickname}}"
placeholder="点击输入昵称"
placeholder-class="nickname-placeholder"
bindchange="onNicknameChange"
bindinput="onNicknameInput"
maxlength="20"
/>
<text class="input-tip">微信用户可点击自动填充昵称</text>
</view>
<view class="modal-actions">
<view class="modal-btn modal-btn-cancel" bindtap="closeNicknameModal">取消</view>
<view class="modal-btn modal-btn-confirm" bindtap="confirmNickname">确定</view>
</view>
</view>
</view>
<!-- 底部留白 -->
<view class="bottom-space"></view>
</view>

View File

@@ -25,7 +25,8 @@
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
justify-content: flex-start;
padding: 0 32rpx;
}
.nav-title {
@@ -33,6 +34,11 @@
font-weight: 600;
}
.nav-title-left {
font-size: 36rpx;
font-weight: 600;
}
.brand-color {
color: #00CED1;
}
@@ -54,6 +60,40 @@
margin: 32rpx;
padding: 32rpx;
}
.margin-partner-badge{
}
/* 创业伙伴按钮 - inline 布局 */
.partner-badge {
display: flex;
align-items: center;
gap: 6rpx;
padding: 10rpx 18rpx;
background: rgba(0, 206, 209, 0.15);
border: 1rpx solid rgba(0, 206, 209, 0.3);
border-radius: 20rpx;
flex-shrink: 0;
transition: all 0.2s;
margin-top:-20px;
}
.partner-badge:active {
background: rgba(0, 206, 209, 0.3);
transform: scale(0.95);
}
.partner-icon {
font-size: 18rpx;
color: #00CED1;
line-height: 1;
}
.partner-text {
font-size: 20rpx;
font-weight: 500;
color: #00CED1;
line-height: 1;
white-space: nowrap;
}
.card-gradient {
background: linear-gradient(135deg, #1c1c1e 0%, #2c2c2e 100%);
@@ -65,7 +105,7 @@
.user-header-row {
display: flex;
align-items: center;
gap: 24rpx;
gap: 20rpx;
margin-bottom: 32rpx;
width: 100%;
}
@@ -81,7 +121,7 @@
/* 头像按钮样式 - 简化版 */
.avatar-btn-simple {
flex-shrink: 0;
width: 120rpx;
width: 60rpx!important;
height: 120rpx;
min-width: 120rpx;
min-height: 120rpx;
@@ -98,17 +138,6 @@
}
.avatar-btn-simple::after { border: none; }
/* 用户名样式 */
.user-name {
font-size: 32rpx;
font-weight: 600;
color: #fff;
max-width: 300rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.edit-icon-small {
font-size: 24rpx;
color: rgba(255,255,255,0.5);
@@ -119,18 +148,20 @@
width: 120rpx;
height: 120rpx;
border-radius: 50%;
border: 4rpx solid #00CED1;
border: 3rpx solid #00CED1;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, rgba(0, 206, 209, 0.2) 0%, transparent 100%);
overflow: hidden;
box-sizing: border-box;
flex-shrink: 0;
}
.avatar-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-edit-hint {
@@ -163,9 +194,10 @@
}
.avatar-text {
font-size: 44rpx;
font-size: 48rpx;
font-weight: 700;
color: #00CED1;
line-height: 1;
}
.user-info-block {
@@ -173,27 +205,26 @@
display: flex;
flex-direction: column;
justify-content: center;
gap: 10rpx;
gap: 12rpx;
min-width: 0;
padding-top: 4rpx;
}
.user-name-row {
display: flex;
align-items: center;
gap: 10rpx;
line-height: 1.2;
gap: 8rpx;
line-height: 1.3;
}
.user-name {
font-size: 36rpx;
font-size: 34rpx;
font-weight: 600;
color: #ffffff;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 320rpx;
line-height: 1.3;
flex: 1;
min-width: 0;
}
.edit-name-icon {
@@ -206,11 +237,23 @@
display: flex;
align-items: center;
gap: 8rpx;
line-height: 1.3;
}
.user-id {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.4);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
min-width: 0;
}
.copy-icon {
font-size: 22rpx;
opacity: 0.5;
flex-shrink: 0;
}
.user-wechat {
@@ -237,11 +280,6 @@
color: #00CED1;
}
.user-id {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.35);
}
/* 兼容旧样式 */
.user-header {
display: flex;
@@ -267,67 +305,24 @@
margin-top: 4rpx;
}
/* ===== 登录卡片样式 ===== */
.login-card {
min-height: 400rpx;
display: flex;
align-items: center;
justify-content: center;
/* ===== 登录假资料样式 ===== */
.avatar-placeholder {
cursor: default;
}
.login-prompt {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
padding: 40rpx 0;
width: 100%;
.user-id-guest {
color: rgba(255, 255, 255, 0.35) !important;
}
.login-icon-large {
font-size: 80rpx;
margin-bottom: 32rpx;
}
.login-title {
font-size: 36rpx;
.btn-login-inline {
flex-shrink: 0;
margin-left: 16rpx;
padding: 10rpx 24rpx;
background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%);
color: #000;
font-size: 24rpx;
font-weight: 600;
color: #ffffff;
margin-bottom: 16rpx;
}
.login-desc {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.5);
margin-bottom: 48rpx;
line-height: 1.6;
}
.btn-wechat-large {
display: flex;
align-items: center;
justify-content: center;
gap: 16rpx;
width: 80%;
padding: 28rpx 0;
background: linear-gradient(135deg, #07C160 0%, #06AD56 100%);
border-radius: 48rpx;
border: none;
color: #ffffff;
font-size: 32rpx;
font-weight: 600;
}
.btn-wechat-large .btn-icon {
width: 48rpx;
height: 48rpx;
background: rgba(255, 255, 255, 0.2);
border-radius: 8rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
font-weight: 700;
border-radius: 24rpx;
}
.user-name {
@@ -368,14 +363,12 @@
grid-template-columns: repeat(3, 1fr);
gap: 16rpx;
padding-top: 32rpx;
border-top: 2rpx solid rgba(255, 255, 255, 0.1);
border-top: 2rpx solid rgba(255, 255, 255, 0.05);
}
.stat-item {
text-align: center;
padding: 16rpx;
background: rgba(255, 255, 255, 0.05);
border-radius: 16rpx;
padding: 16rpx 8rpx;
}
.stat-value {
@@ -389,7 +382,7 @@
color: rgba(255, 255, 255, 0.4);
}
/* ===== 收益卡片 ===== */
/* ===== 收益卡片 - 艺术化设计 ===== */
.earnings-card {
margin: 32rpx;
padding: 32rpx;
@@ -398,63 +391,117 @@
border: 2rpx solid rgba(0, 206, 209, 0.2);
position: relative;
overflow: hidden;
box-shadow: 0 16rpx 32rpx rgba(0, 0, 0, 0.3);
}
.earnings-bg {
/* 背景装饰圆 */
.bg-decoration {
position: absolute;
border-radius: 50%;
z-index: 0;
}
.bg-decoration-gold {
top: 0;
right: 0;
width: 256rpx;
height: 256rpx;
background: linear-gradient(135deg, rgba(255, 215, 0, 0.1) 0%, transparent 100%);
border-radius: 50%;
transform: translate(50%, -50%);
}
.bg-decoration-brand {
bottom: 0;
left: 0;
width: 192rpx;
height: 192rpx;
background: linear-gradient(45deg, rgba(0, 206, 209, 0.1) 0%, transparent 100%);
transform: translate(-50%, 50%);
}
.earnings-content {
position: relative;
z-index: 1;
}
/* 标题行 */
.earnings-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 32rpx;
margin-bottom: 40rpx;
}
.earnings-title-wrap {
display: flex;
align-items: center;
gap: 12rpx;
}
.earnings-icon {
font-size: 32rpx;
}
.earnings-title {
display: flex;
align-items: center;
gap: 16rpx;
}
.title-icon {
font-size: 36rpx;
}
.title-text {
font-size: 28rpx;
font-weight: 500;
font-size: 30rpx;
font-weight: 600;
color: #ffffff;
}
.earnings-link {
display: flex;
align-items: center;
gap: 8rpx;
gap: 6rpx;
padding: 8rpx 16rpx;
background: rgba(0, 206, 209, 0.1);
border-radius: 16rpx;
}
.earnings-link:active {
background: rgba(0, 206, 209, 0.2);
}
.link-text {
font-size: 24rpx;
color: #00CED1;
font-weight: 500;
}
.link-arrow {
font-size: 24rpx;
font-size: 20rpx;
font-weight: 600;
}
/* 我的收益 - 刷新图标 */
.earnings-refresh-wrap {
display: flex;
align-items: center;
justify-content: center;
width: 64rpx;
height: 64rpx;
border-radius: 50%;
background: rgba(0, 206, 209, 0.15);
}
.earnings-refresh-wrap:active {
background: rgba(0, 206, 209, 0.3);
}
.earnings-refresh-icon {
font-size: 36rpx;
font-weight: 600;
color: #00CED1;
}
.earnings-refresh-spin {
animation: earnings-spin 0.8s linear infinite;
}
@keyframes earnings-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* 收益数据 */
.earnings-data {
display: flex;
align-items: flex-end;
@@ -462,48 +509,79 @@
margin-bottom: 32rpx;
}
.data-item {
.earnings-main,
.earnings-secondary {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.data-label {
font-size: 22rpx;
color: rgba(255, 255, 255, 0.5);
margin-bottom: 8rpx;
.earnings-main {
flex: 1;
}
.data-value {
font-size: 40rpx;
.earnings-secondary {
flex: 1;
}
.earnings-label {
font-size: 24rpx;
font-weight: 500;
color: rgba(255, 255, 255, 0.6);
letter-spacing: 0.5rpx;
}
.earnings-amount-large {
font-size: 64rpx;
font-weight: 700;
line-height: 1;
display: block;
}
.earnings-amount-medium {
font-size: 48rpx;
font-weight: 700;
line-height: 1;
color: #ffffff;
}
/* 渐变文字效果 */
.gold-gradient {
background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
background: linear-gradient(90deg, #FFD700 0%, #FFA500 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
font-size: 56rpx;
color: transparent;
}
.earnings-btn {
/* 操作按钮 */
.earnings-action {
display: flex;
align-items: center;
justify-content: center;
gap: 16rpx;
padding: 24rpx;
background: linear-gradient(135deg, rgba(255, 215, 0, 0.8) 0%, rgba(255, 165, 0, 0.8) 100%);
background: linear-gradient(90deg, rgba(255, 215, 0, 0.9) 0%, rgba(255, 165, 0, 0.9) 100%);
border-radius: 24rpx;
font-weight: 600;
font-weight: 700;
box-shadow: 0 4rpx 12rpx rgba(255, 215, 0, 0.3);
margin-top: 8rpx;
}
.btn-icon {
font-size: 28rpx;
.earnings-action:active {
opacity: 0.85;
transform: scale(0.98);
}
.btn-text {
font-size: 28rpx;
.action-icon {
font-size: 32rpx;
}
.action-text {
font-size: 30rpx;
font-weight: 700;
color: #000000;
letter-spacing: 1rpx;
}
/* ===== 推广入口 ===== */
@@ -643,20 +721,33 @@
}
.menu-icon {
width: 48rpx;
height: 48rpx;
width: 52rpx;
height: 52rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
background: rgba(255, 255, 255, 0.1);
font-size: 32rpx;
flex-shrink: 0;
}
/* 有背景的图标样式 */
.icon-brand,
.icon-gold,
.icon-gray {
width: 52rpx;
height: 52rpx;
font-size: 20rpx;
}
.icon-brand {
background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%);
}
.icon-gold {
background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
}
.icon-gray {
background: rgba(128, 128, 128, 0.2);
}
@@ -708,6 +799,11 @@
gap: 24rpx;
}
/* 两列布局(当找伙伴功能关闭时) */
.stats-row-two-cols {
grid-template-columns: repeat(2, 1fr) !important;
}
.stat-box {
display: flex;
flex-direction: column;
@@ -919,12 +1015,52 @@
font-size: 32rpx;
}
.login-notice {
display: block;
/* 登录弹窗内取消按钮 */
.login-modal-cancel {
margin-top: 24rpx;
padding: 24rpx;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.5);
text-align: center;
}
/* 协议勾选行(审核:用户须主动勾选,协议可点击查看) */
.login-agree-row {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
margin-top: 32rpx;
font-size: 22rpx;
color: rgba(255, 255, 255, 0.3);
text-align: center;
color: rgba(255, 255, 255, 0.5);
}
.agree-checkbox {
width: 32rpx;
height: 32rpx;
border: 2rpx solid rgba(255, 255, 255, 0.5);
border-radius: 6rpx;
margin-right: 12rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 22rpx;
color: #fff;
flex-shrink: 0;
}
.agree-checked {
background: #00CED1;
border-color: #00CED1;
}
.agree-text {
color: rgba(255, 255, 255, 0.6);
}
.agree-link {
color: #00CED1;
text-decoration: underline;
padding: 0 4rpx;
}
.btn-wechat-disabled {
opacity: 0.6;
}
/* ===== 底部留白 ===== */
@@ -995,141 +1131,243 @@
color: #FFD700;
}
/* 账号设置 */
.settings-card {
margin-top: 24rpx;
/* ===== 修改昵称弹窗 ===== */
.nickname-modal {
width: 600rpx;
max-width: 90%;
}
.settings-list {
margin-top: 16rpx;
}
.settings-item {
.modal-header {
display: flex;
justify-content: space-between;
flex-direction: column;
align-items: center;
padding: 28rpx 0;
border-bottom: 1rpx solid rgba(255, 255, 255, 0.06);
margin-bottom: 40rpx;
}
.settings-item:last-child {
border-bottom: none;
.modal-icon {
font-size: 60rpx;
margin-bottom: 16rpx;
}
.settings-label {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.85);
}
.settings-right {
display: flex;
align-items: center;
gap: 12rpx;
}
.settings-value {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.4);
}
.logout-item {
justify-content: center;
margin-top: 16rpx;
border-bottom: none;
}
.logout-text {
color: #ff4d4f;
font-size: 28rpx;
}
/* === VIP 头像标识 === */
.avatar-normal {
border: 4rpx solid rgba(255,255,255,0.2);
}
.avatar-vip {
border: 4rpx solid #FFD700;
box-shadow: 0 0 16rpx rgba(255,215,0,0.5);
position: relative;
}
.vip-badge {
position: absolute;
bottom: -4rpx;
right: -4rpx;
background: linear-gradient(135deg, #FFD700, #FFA500);
color: #000;
font-size: 18rpx;
font-weight: bold;
padding: 2rpx 10rpx;
border-radius: 12rpx;
line-height: 1.4;
}
.vip-tag {
background: linear-gradient(135deg, #FFD700, #FFA500);
color: #000;
font-size: 20rpx;
.modal-title {
font-size: 32rpx;
color: #ffffff;
font-weight: 600;
padding: 4rpx 14rpx;
border-radius: 16rpx;
margin-left: 12rpx;
}
/* === VIP入口卡片 === */
.vip-card {
margin: 16rpx 24rpx;
border-radius: 20rpx;
overflow: hidden;
.nickname-input-wrap {
margin-bottom: 40rpx;
}
.vip-card-inner {
.nickname-input {
width: 100%;
height: 88rpx;
padding: 0 24rpx;
background: rgba(255, 255, 255, 0.05);
border: 2rpx solid rgba(56, 189, 172, 0.3);
border-radius: 12rpx;
font-size: 28rpx;
color: #ffffff;
box-sizing: border-box;
}
.nickname-placeholder {
color: rgba(255, 255, 255, 0.3);
}
.input-tip {
display: block;
margin-top: 12rpx;
font-size: 22rpx;
color: rgba(56, 189, 172, 0.6);
text-align: center;
}
.modal-actions {
display: flex;
gap: 20rpx;
}
.modal-btn {
flex: 1;
height: 80rpx;
display: flex;
align-items: center;
justify-content: space-between;
justify-content: center;
border-radius: 12rpx;
font-size: 28rpx;
font-weight: 500;
transition: all 0.3s;
}
.modal-btn-cancel {
background: rgba(255, 255, 255, 0.05);
color: rgba(255, 255, 255, 0.5);
border: 2rpx solid rgba(255, 255, 255, 0.1);
}
.modal-btn-confirm {
background: linear-gradient(135deg, #38bdac 0%, #2da396 100%);
color: #ffffff;
box-shadow: 0 8rpx 24rpx rgba(56, 189, 172, 0.3);
}
/* 待确认收款 */
.pending-confirm-card {
margin: 32rpx;
padding: 28rpx 32rpx;
background: rgba(76, 175, 80, 0.08);
border: 2rpx solid rgba(76, 175, 80, 0.25);
border-radius: 24rpx;
}
.pending-confirm-header { margin-bottom: 20rpx; }
.pending-confirm-title { font-size: 28rpx; font-weight: 600; color: #fff; display: block; }
.pending-confirm-desc { font-size: 24rpx; color: rgba(255,255,255,0.6); margin-top: 8rpx; display: block; }
.pending-confirm-list { display: flex; flex-direction: column; gap: 16rpx; }
.pending-confirm-item {
display: flex; align-items: center; justify-content: space-between;
padding: 20rpx 24rpx; background: rgba(28,28,30,0.6); border-radius: 16rpx;
}
.pending-confirm-info { display: flex; flex-direction: column; gap: 4rpx; }
.pending-confirm-amount { font-size: 32rpx; font-weight: 600; color: #4CAF50; }
.pending-confirm-time { font-size: 22rpx; color: rgba(255,255,255,0.5); }
.pending-confirm-btn {
padding: 16rpx 32rpx;
background: linear-gradient(135deg, #4CAF50 0%, #388E3C 100%);
color: #fff; font-size: 26rpx; font-weight: 500; border-radius: 20rpx;
}
/* ===== 收益面板(内嵌) ===== */
.earnings-inline {
margin: 0 32rpx 24rpx;
padding: 24rpx 28rpx;
background: linear-gradient(135deg, rgba(255,215,0,0.12), rgba(255,165,0,0.08));
border: 1rpx solid rgba(255,215,0,0.25);
border-radius: 20rpx;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
border-radius: 24rpx;
border: 2rpx solid rgba(0, 206, 209, 0.15);
}
.vip-card-inner.vip-active {
background: linear-gradient(135deg, rgba(255,215,0,0.2), rgba(255,165,0,0.12));
border-color: rgba(255,215,0,0.4);
}
.vip-card-left {
.earnings-inline-row {
display: flex;
align-items: center;
gap: 16rpx;
}
.vip-card-icon {
font-size: 44rpx;
}
.vip-card-info {
.earnings-inline-item {
display: flex;
flex-direction: column;
gap: 4rpx;
flex: 1;
}
.vip-card-title {
font-size: 30rpx;
font-weight: 600;
color: rgba(255,255,255,0.95);
}
.vip-card-desc {
.earnings-inline-label {
font-size: 22rpx;
color: rgba(255,255,255,0.5);
margin-top: 4rpx;
}
.vip-card-price {
display: flex;
align-items: baseline;
}
.vip-price-num {
.earnings-inline-val {
font-size: 36rpx;
font-weight: bold;
color: #FFD700;
font-weight: 700;
color: #fff;
}
.vip-price-unit {
font-size: 22rpx;
color: rgba(255,215,0,0.7);
margin-left: 4rpx;
.earnings-inline-divider {
width: 2rpx;
height: 48rpx;
background: rgba(255,255,255,0.1);
flex-shrink: 0;
}
.vip-manage-btn {
.earnings-inline-btn {
padding: 12rpx 28rpx;
background: linear-gradient(90deg, #FFD700 0%, #FFA500 100%);
border-radius: 20rpx;
font-size: 26rpx;
color: #FFD700;
font-weight: 600;
color: #000;
flex-shrink: 0;
}
.earnings-inline-refresh {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
color: #00CED1;
flex-shrink: 0;
}
.pending-inline {
margin-top: 16rpx;
padding-top: 16rpx;
border-top: 1rpx solid rgba(255,255,255,0.08);
}
.pending-inline-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8rpx 0;
}
.pending-inline-text {
font-size: 24rpx;
color: #4CAF50;
}
.pending-inline-btn {
padding: 8rpx 20rpx;
background: #4CAF50;
color: #fff;
font-size: 22rpx;
border-radius: 12rpx;
}
/* VIP头像标识 */
.avatar-wrap { position: relative; }
.avatar-vip { border: 4rpx solid #FFD700 !important; box-shadow: 0 0 20rpx rgba(255,215,0,0.4); }
.vip-badge { position: absolute; bottom: -4rpx; right: -4rpx; background: linear-gradient(135deg, #FFD700, #FFA500); color: #000; font-size: 16rpx; font-weight: bold; padding: 2rpx 8rpx; border-radius: 10rpx; line-height: 1.4; }
.vip-badge-gray { background: rgba(255,255,255,0.2); color: rgba(255,255,255,0.5); }
/* 会员权益小标签 */
.vip-tags-row {
display: flex;
gap: 6rpx;
margin-left: 8rpx;
flex-shrink: 0;
}
.vip-tag-mini {
padding: 2rpx 10rpx;
font-size: 18rpx;
border-radius: 8rpx;
background: rgba(255,255,255,0.08);
color: rgba(255,255,255,0.3);
border: 1rpx solid rgba(255,255,255,0.1);
}
.vip-tag-active {
background: rgba(255,215,0,0.15);
color: #FFD700;
border-color: rgba(255,215,0,0.3);
}
/* 阅读统计 */
.stats-card { padding: 24rpx 28rpx; }
.stats-row { display: flex; gap: 16rpx; margin-top: 16rpx; }
.stat-box {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 6rpx;
padding: 20rpx 12rpx;
border-radius: 16rpx;
background: rgba(139,92,246,0.06);
}
.stat-icon { font-size: 32rpx; }
.stat-num { font-size: 36rpx; font-weight: bold; color: #fff; }
.stat-text { font-size: 22rpx; color: rgba(255,255,255,0.5); }
.pink-color { color: #ec4899; }
/* 成为会员小按钮 */
.become-vip-chip {
display: inline-flex;
align-items: center;
gap: 4rpx;
padding: 4rpx 14rpx;
border-radius: 20rpx;
background: linear-gradient(135deg, rgba(245,166,35,.15), rgba(245,166,35,.08));
border: 1rpx solid rgba(245,166,35,.3);
margin-left: 8rpx;
}
.chip-star { font-size: 18rpx; }
.chip-text { font-size: 20rpx; color: #f5a623; font-weight: 600; white-space: nowrap; }