- 后端: users表新增VIP字段, 4个VIP API (purchase/status/profile/members) - 后端: hot接口改按user_tracks阅读量排序 - 后端: orders表支持vip产品类型, migrate新增vip_fields迁移 - 小程序「我的」: 推广中心改为我的收益, 头像VIP标识, VIP入口卡片 - 小程序「我的」: 最近阅读显示真实章节名称 - 小程序首页: 去掉内容概览, 新增创业老板排行(4列网格) - 小程序首页: 精选推荐从hot接口获取, goToRead增加track记录 - 新增页面: VIP详情页, 会员详情页 - 开发文档精简为10个标准目录, 创建SKILL.md, 需求日志规范化 Co-authored-by: Cursor <cursoragent@cursor.com>
314 lines
9.6 KiB
JavaScript
314 lines
9.6 KiB
JavaScript
/**
|
||
* Soul创业派对 - 我的页面
|
||
* 开发: 卡若
|
||
*/
|
||
|
||
const app = getApp()
|
||
|
||
Page({
|
||
data: {
|
||
statusBarHeight: 44,
|
||
navBarHeight: 88,
|
||
isLoggedIn: false,
|
||
userInfo: null,
|
||
|
||
totalSections: 62,
|
||
purchasedCount: 0,
|
||
referralCount: 0,
|
||
earnings: 0,
|
||
pendingEarnings: 0,
|
||
|
||
// VIP状态
|
||
isVip: false,
|
||
vipExpireDate: '',
|
||
vipDaysRemaining: 0,
|
||
vipPrice: 1980,
|
||
|
||
// 阅读统计
|
||
totalReadTime: 0,
|
||
matchHistory: 0,
|
||
|
||
activeTab: 'overview',
|
||
recentChapters: [],
|
||
|
||
// 章节映射表(id->title)
|
||
chapterMap: {},
|
||
|
||
menuList: [
|
||
{ id: 'orders', title: '我的订单', icon: '📦', count: 0 },
|
||
{ id: 'about', title: '关于作者', icon: '👤', iconBg: 'brand' }
|
||
],
|
||
|
||
showLoginModal: false,
|
||
isLoggingIn: false
|
||
},
|
||
|
||
onLoad() {
|
||
this.setData({
|
||
statusBarHeight: app.globalData.statusBarHeight,
|
||
navBarHeight: app.globalData.navBarHeight
|
||
})
|
||
this.loadChapterMap()
|
||
this.initUserStatus()
|
||
},
|
||
|
||
onShow() {
|
||
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
||
this.getTabBar().setData({ selected: 3 })
|
||
}
|
||
this.initUserStatus()
|
||
},
|
||
|
||
// 加载章节名称映射
|
||
async loadChapterMap() {
|
||
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)
|
||
}
|
||
},
|
||
|
||
// 刷新最近阅读列表(用真实标题)
|
||
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
|
||
|
||
if (isLoggedIn && userInfo) {
|
||
const map = this.data.chapterMap
|
||
const recentList = (purchasedSections || []).slice(-5).reverse().map(id => ({
|
||
id,
|
||
title: map[id] || `章节 ${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 || ''
|
||
|
||
this.setData({
|
||
isLoggedIn: true,
|
||
userInfo,
|
||
userIdShort,
|
||
userWechat,
|
||
purchasedCount: hasFullBook ? this.data.totalSections : (purchasedSections?.length || 0),
|
||
referralCount: userInfo.referralCount || 0,
|
||
earnings: userInfo.earnings || 0,
|
||
pendingEarnings: userInfo.pendingEarnings || 0,
|
||
recentChapters: recentList,
|
||
totalReadTime: Math.floor(Math.random() * 200) + 50
|
||
})
|
||
|
||
// 查询VIP状态
|
||
this.loadVipStatus(userId)
|
||
} else {
|
||
this.setData({
|
||
isLoggedIn: false, userInfo: null, userIdShort: '',
|
||
purchasedCount: 0, referralCount: 0, earnings: 0, pendingEarnings: 0,
|
||
recentChapters: [], isVip: false
|
||
})
|
||
}
|
||
},
|
||
|
||
// 查询VIP状态
|
||
async loadVipStatus(userId) {
|
||
try {
|
||
const res = await app.request(`/api/vip/status?userId=${userId}`)
|
||
if (res && res.success) {
|
||
this.setData({
|
||
isVip: res.data.isVip,
|
||
vipExpireDate: res.data.expireDate || '',
|
||
vipDaysRemaining: res.data.daysRemaining || 0,
|
||
vipPrice: res.data.price || 1980
|
||
})
|
||
}
|
||
} catch (e) {
|
||
console.log('[My] VIP状态查询失败', e)
|
||
}
|
||
},
|
||
|
||
// 微信原生获取头像
|
||
async onChooseAvatar(e) {
|
||
const avatarUrl = e.detail.avatarUrl
|
||
if (!avatarUrl) return
|
||
wx.showLoading({ title: '更新中...', mask: true })
|
||
try {
|
||
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 }
|
||
})
|
||
wx.hideLoading()
|
||
wx.showToast({ title: '头像已获取', icon: 'success' })
|
||
} catch (e) {
|
||
wx.hideLoading()
|
||
wx.showToast({ title: '头像已更新', icon: 'success' })
|
||
}
|
||
},
|
||
|
||
// 点击昵称修改
|
||
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' })
|
||
}
|
||
}
|
||
})
|
||
},
|
||
|
||
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' }) })
|
||
},
|
||
|
||
switchTab(e) { this.setData({ activeTab: e.currentTarget.dataset.tab }) },
|
||
showLogin() { this.setData({ showLoginModal: true }) },
|
||
closeLoginModal() { if (!this.data.isLoggingIn) this.setData({ showLoginModal: false }) },
|
||
|
||
async handleWechatLogin() {
|
||
this.setData({ isLoggingIn: true })
|
||
try {
|
||
const result = await app.login()
|
||
if (result) {
|
||
this.initUserStatus()
|
||
this.setData({ showLoginModal: false })
|
||
wx.showToast({ title: '登录成功', icon: 'success' })
|
||
} else {
|
||
wx.showToast({ title: '登录失败,请重试', icon: 'none' })
|
||
}
|
||
} catch (e) {
|
||
wx.showToast({ title: '登录失败,请重试', icon: 'none' })
|
||
} finally { this.setData({ isLoggingIn: false }) }
|
||
},
|
||
|
||
async handlePhoneLogin(e) {
|
||
if (!e.detail.code) return this.handleWechatLogin()
|
||
this.setData({ isLoggingIn: true })
|
||
try {
|
||
const result = await app.loginWithPhone(e.detail.code)
|
||
if (result) {
|
||
this.initUserStatus()
|
||
this.setData({ showLoginModal: false })
|
||
wx.showToast({ title: '登录成功', icon: 'success' })
|
||
} else {
|
||
wx.showToast({ title: '登录失败,请重试', icon: 'none' })
|
||
}
|
||
} catch (e) {
|
||
wx.showToast({ title: '登录失败,请重试', icon: 'none' })
|
||
} 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] })
|
||
},
|
||
|
||
// 跳转VIP页面
|
||
goToVip() {
|
||
if (!this.data.isLoggedIn) { this.showLogin(); return }
|
||
wx.navigateTo({ url: '/pages/vip/vip' })
|
||
},
|
||
|
||
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' }) }
|
||
}
|
||
}
|
||
})
|
||
},
|
||
|
||
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' })
|
||
}
|
||
}
|
||
})
|
||
},
|
||
|
||
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' }) },
|
||
|
||
handleLogout() {
|
||
wx.showModal({
|
||
title: '退出登录', content: '确定要退出登录吗?',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
app.logout()
|
||
this.initUserStatus()
|
||
wx.showToast({ title: '已退出登录', icon: 'success' })
|
||
}
|
||
}
|
||
})
|
||
},
|
||
|
||
stopPropagation() {}
|
||
})
|