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:
@@ -1,9 +1,18 @@
|
||||
/**
|
||||
* Soul创业派对 - 阅读页
|
||||
* Soul创业派对 - 阅读页(标准流程版)
|
||||
* 开发: 卡若
|
||||
* 技术支持: 存客宝
|
||||
*
|
||||
* 更新: 2026-02-04
|
||||
* - 引入权限管理器(chapterAccessManager)统一权限判断
|
||||
* - 引入阅读追踪器(readingTracker)记录阅读进度、时长、是否读完
|
||||
* - 使用状态机(accessState)规范权限流转
|
||||
* - 异常统一保守处理,避免误解锁
|
||||
*/
|
||||
|
||||
import accessManager from '../../utils/chapterAccessManager'
|
||||
import readingTracker from '../../utils/readingTracker'
|
||||
|
||||
const app = getApp()
|
||||
|
||||
Page({
|
||||
@@ -25,10 +34,14 @@ Page({
|
||||
previewParagraphs: [],
|
||||
loading: true,
|
||||
|
||||
// 【新增】权限状态机(替代 canAccess)
|
||||
// unknown: 加载中 | free: 免费 | locked_not_login: 未登录 | locked_not_purchased: 未购买 | unlocked_purchased: 已购买 | error: 错误
|
||||
accessState: 'unknown',
|
||||
|
||||
// 用户状态
|
||||
isLoggedIn: false,
|
||||
hasFullBook: false,
|
||||
canAccess: false,
|
||||
canAccess: false, // 保留兼容性,从 accessState 派生
|
||||
purchasedCount: 0,
|
||||
|
||||
// 阅读进度
|
||||
@@ -47,6 +60,7 @@ Page({
|
||||
// 弹窗
|
||||
showShareModal: false,
|
||||
showLoginModal: false,
|
||||
agreeProtocol: false,
|
||||
showPosterModal: false,
|
||||
isPaying: false,
|
||||
isGeneratingPoster: false,
|
||||
@@ -55,89 +69,147 @@ Page({
|
||||
freeIds: ['preface', 'epilogue', '1.1', 'appendix-1', 'appendix-2', 'appendix-3']
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
async onLoad(options) {
|
||||
const { id, ref } = options
|
||||
|
||||
this.setData({
|
||||
statusBarHeight: app.globalData.statusBarHeight,
|
||||
navBarHeight: app.globalData.navBarHeight,
|
||||
sectionId: id
|
||||
sectionId: id,
|
||||
loading: true,
|
||||
accessState: 'unknown'
|
||||
})
|
||||
|
||||
// 处理推荐码绑定
|
||||
// 处理推荐码绑定(异步不阻塞)
|
||||
if (ref) {
|
||||
console.log('[Read] 检测到推荐码:', ref)
|
||||
wx.setStorageSync('referral_code', ref)
|
||||
app.handleReferralCode({ query: { ref } })
|
||||
}
|
||||
|
||||
// 加载免费章节配置
|
||||
this.loadFreeChaptersConfig()
|
||||
|
||||
this.initSection(id)
|
||||
try {
|
||||
// 【标准流程】1. 拉取最新配置(免费列表、价格)
|
||||
const config = await accessManager.fetchLatestConfig()
|
||||
this.setData({
|
||||
freeIds: config.freeChapters,
|
||||
sectionPrice: config.prices?.section ?? 1,
|
||||
fullBookPrice: config.prices?.fullbook ?? 9.9
|
||||
})
|
||||
|
||||
// 【标准流程】2. 确定权限状态
|
||||
const accessState = await accessManager.determineAccessState(id, config.freeChapters)
|
||||
const canAccess = accessManager.canAccessFullContent(accessState)
|
||||
|
||||
this.setData({
|
||||
accessState,
|
||||
canAccess,
|
||||
isLoggedIn: !!app.globalData.userInfo?.id,
|
||||
showPaywall: !canAccess
|
||||
})
|
||||
|
||||
// 【标准流程】3. 加载内容
|
||||
await this.loadContent(id, accessState)
|
||||
|
||||
// 【标准流程】4. 如果有权限,初始化阅读追踪
|
||||
if (canAccess) {
|
||||
readingTracker.init(id)
|
||||
}
|
||||
|
||||
// 5. 加载导航
|
||||
this.loadNavigation(id)
|
||||
|
||||
} catch (e) {
|
||||
console.error('[Read] 初始化失败:', e)
|
||||
wx.showToast({ title: '加载失败,请重试', icon: 'none' })
|
||||
this.setData({ accessState: 'error', loading: false })
|
||||
} finally {
|
||||
this.setData({ loading: false })
|
||||
}
|
||||
},
|
||||
|
||||
// 从后端加载免费章节配置
|
||||
async loadFreeChaptersConfig() {
|
||||
try {
|
||||
const res = await app.request('/api/db/config')
|
||||
if (res.success && res.freeChapters) {
|
||||
this.setData({ freeIds: res.freeChapters })
|
||||
console.log('[Read] 加载免费章节配置:', res.freeChapters)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('[Read] 使用默认免费章节配置')
|
||||
}
|
||||
},
|
||||
|
||||
onPageScroll(e) {
|
||||
// 计算阅读进度
|
||||
// 只在有权限时追踪阅读进度
|
||||
if (!accessManager.canAccessFullContent(this.data.accessState)) {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取滚动信息并更新追踪器
|
||||
const query = wx.createSelectorQuery()
|
||||
query.select('.page').boundingClientRect()
|
||||
query.selectViewport().scrollOffset()
|
||||
query.exec((res) => {
|
||||
if (res[0]) {
|
||||
const scrollTop = e.scrollTop
|
||||
const pageHeight = res[0].height - this.data.statusBarHeight - 200
|
||||
const progress = pageHeight > 0 ? Math.min((scrollTop / pageHeight) * 100, 100) : 0
|
||||
if (res[0] && res[1]) {
|
||||
const scrollInfo = {
|
||||
scrollTop: res[1].scrollTop,
|
||||
scrollHeight: res[0].height,
|
||||
clientHeight: res[1].height
|
||||
}
|
||||
|
||||
// 计算进度条显示(用于 UI)
|
||||
const totalScrollable = scrollInfo.scrollHeight - scrollInfo.clientHeight
|
||||
const progress = totalScrollable > 0
|
||||
? Math.min((scrollInfo.scrollTop / totalScrollable) * 100, 100)
|
||||
: 0
|
||||
this.setData({ readingProgress: progress })
|
||||
|
||||
// 更新阅读追踪器(记录最大进度、判断是否读完)
|
||||
readingTracker.updateProgress(scrollInfo)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 初始化章节
|
||||
async initSection(id) {
|
||||
this.setData({ loading: true })
|
||||
|
||||
// 【重构】加载章节内容(专注于内容加载,权限判断已在 onLoad 中由 accessManager 完成)
|
||||
async loadContent(id, accessState) {
|
||||
try {
|
||||
// 模拟获取章节数据
|
||||
const section = this.getSectionInfo(id)
|
||||
const { isLoggedIn, hasFullBook, purchasedSections } = app.globalData
|
||||
const sectionPrice = this.data.sectionPrice ?? 1
|
||||
if (section.price === undefined || section.price === null) {
|
||||
section.price = sectionPrice
|
||||
}
|
||||
this.setData({ section })
|
||||
|
||||
const isFree = this.data.freeIds.includes(id)
|
||||
const isPurchased = hasFullBook || (purchasedSections && purchasedSections.includes(id))
|
||||
const canAccess = isFree || isPurchased
|
||||
const purchasedCount = purchasedSections?.length || 0
|
||||
|
||||
this.setData({
|
||||
section,
|
||||
isLoggedIn,
|
||||
hasFullBook,
|
||||
canAccess,
|
||||
purchasedCount,
|
||||
showPaywall: !canAccess
|
||||
})
|
||||
|
||||
// 加载内容
|
||||
await this.loadContent(id)
|
||||
|
||||
// 获取上一篇/下一篇
|
||||
this.loadNavigation(id)
|
||||
// 从 API 获取内容
|
||||
const res = await app.request({ url: `/api/miniprogram/book/chapter/${id}`, silent: true })
|
||||
|
||||
if (res && res.content) {
|
||||
const lines = res.content.split('\n').filter(line => line.trim())
|
||||
const previewCount = Math.ceil(lines.length * 0.2)
|
||||
|
||||
this.setData({
|
||||
content: res.content,
|
||||
contentParagraphs: lines,
|
||||
previewParagraphs: lines.slice(0, previewCount),
|
||||
partTitle: res.partTitle || '',
|
||||
chapterTitle: res.chapterTitle || ''
|
||||
})
|
||||
|
||||
// 如果有权限,标记为已读
|
||||
if (accessManager.canAccessFullContent(accessState)) {
|
||||
app.markSectionAsRead(id)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('初始化章节失败:', e)
|
||||
wx.showToast({ title: '加载失败', icon: 'none' })
|
||||
} finally {
|
||||
this.setData({ loading: false })
|
||||
console.error('[Read] 加载内容失败:', e)
|
||||
// 尝试从本地缓存加载
|
||||
const cacheKey = `chapter_${id}`
|
||||
try {
|
||||
const cached = wx.getStorageSync(cacheKey)
|
||||
if (cached && cached.content) {
|
||||
const lines = cached.content.split('\n').filter(line => line.trim())
|
||||
const previewCount = Math.ceil(lines.length * 0.2)
|
||||
|
||||
this.setData({
|
||||
content: cached.content,
|
||||
contentParagraphs: lines,
|
||||
previewParagraphs: lines.slice(0, previewCount)
|
||||
})
|
||||
console.log('[Read] 从本地缓存加载成功')
|
||||
}
|
||||
} catch (cacheErr) {
|
||||
console.warn('[Read] 本地缓存也失败:', cacheErr)
|
||||
}
|
||||
throw e
|
||||
}
|
||||
},
|
||||
|
||||
@@ -237,7 +309,7 @@ Page({
|
||||
reject(new Error('请求超时'))
|
||||
}, timeout)
|
||||
|
||||
app.request(`/api/book/chapter/${id}`)
|
||||
app.request(`/api/miniprogram/book/chapter/${id}`)
|
||||
.then(res => {
|
||||
clearTimeout(timer)
|
||||
resolve(res)
|
||||
@@ -412,30 +484,54 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
// 显示登录弹窗
|
||||
// 显示登录弹窗(每次打开协议未勾选,符合审核要求)
|
||||
showLoginModal() {
|
||||
this.setData({ showLoginModal: true })
|
||||
try {
|
||||
this.setData({ showLoginModal: true, agreeProtocol: false })
|
||||
} catch (e) {
|
||||
console.error('[Read] showLoginModal error:', e)
|
||||
this.setData({ showLoginModal: true })
|
||||
}
|
||||
},
|
||||
|
||||
closeLoginModal() {
|
||||
this.setData({ showLoginModal: false })
|
||||
},
|
||||
|
||||
// 微信登录
|
||||
toggleAgree() {
|
||||
this.setData({ agreeProtocol: !this.data.agreeProtocol })
|
||||
},
|
||||
|
||||
openUserProtocol() {
|
||||
wx.navigateTo({ url: '/pages/agreement/agreement' })
|
||||
},
|
||||
|
||||
openPrivacy() {
|
||||
wx.navigateTo({ url: '/pages/privacy/privacy' })
|
||||
},
|
||||
|
||||
// 从服务端刷新购买状态,避免登录后误用旧数据导致误解锁
|
||||
// 【重构】微信登录(须先勾选同意协议,符合审核要求)
|
||||
async handleWechatLogin() {
|
||||
if (!this.data.agreeProtocol) {
|
||||
wx.showToast({ title: '请先阅读并同意用户协议和隐私政策', icon: 'none' })
|
||||
return
|
||||
}
|
||||
try {
|
||||
const result = await app.login()
|
||||
if (result) {
|
||||
this.setData({ showLoginModal: false })
|
||||
this.initSection(this.data.sectionId)
|
||||
wx.showToast({ title: '登录成功', icon: 'success' })
|
||||
}
|
||||
if (!result) return
|
||||
|
||||
this.setData({ showLoginModal: false, agreeProtocol: false })
|
||||
await this.onLoginSuccess()
|
||||
wx.showToast({ title: '登录成功', icon: 'success' })
|
||||
|
||||
} catch (e) {
|
||||
wx.showToast({ title: '登录失败', icon: 'none' })
|
||||
console.error('[Read] 登录失败:', e)
|
||||
wx.showToast({ title: '登录失败,请重试', icon: 'none' })
|
||||
}
|
||||
},
|
||||
|
||||
// 手机号登录
|
||||
// 【重构】手机号登录(标准流程)
|
||||
async handlePhoneLogin(e) {
|
||||
if (!e.detail.code) {
|
||||
return this.handleWechatLogin()
|
||||
@@ -443,16 +539,59 @@ Page({
|
||||
|
||||
try {
|
||||
const result = await app.loginWithPhone(e.detail.code)
|
||||
if (result) {
|
||||
this.setData({ showLoginModal: false })
|
||||
this.initSection(this.data.sectionId)
|
||||
wx.showToast({ title: '登录成功', icon: 'success' })
|
||||
}
|
||||
if (!result) return
|
||||
|
||||
this.setData({ showLoginModal: false })
|
||||
await this.onLoginSuccess()
|
||||
wx.showToast({ title: '登录成功', icon: 'success' })
|
||||
|
||||
} catch (e) {
|
||||
console.error('[Read] 手机号登录失败:', e)
|
||||
wx.showToast({ title: '登录失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
|
||||
// 【新增】登录成功后的标准处理流程
|
||||
async onLoginSuccess() {
|
||||
wx.showLoading({ title: '更新状态中...', mask: true })
|
||||
|
||||
try {
|
||||
// 1. 刷新用户购买状态(从 orders 表拉取最新)
|
||||
await accessManager.refreshUserPurchaseStatus()
|
||||
|
||||
// 2. 重新拉取免费列表(极端情况:刚登录时当前章节可能改免费了)
|
||||
const config = await accessManager.fetchLatestConfig()
|
||||
this.setData({ freeIds: config.freeChapters })
|
||||
|
||||
// 3. 重新判断当前章节权限
|
||||
const newAccessState = await accessManager.determineAccessState(
|
||||
this.data.sectionId,
|
||||
config.freeChapters
|
||||
)
|
||||
const canAccess = accessManager.canAccessFullContent(newAccessState)
|
||||
|
||||
this.setData({
|
||||
accessState: newAccessState,
|
||||
canAccess,
|
||||
isLoggedIn: true,
|
||||
showPaywall: !canAccess
|
||||
})
|
||||
|
||||
// 4. 如果已解锁,重新加载内容并初始化阅读追踪
|
||||
if (canAccess) {
|
||||
await this.loadContent(this.data.sectionId, newAccessState)
|
||||
readingTracker.init(this.data.sectionId)
|
||||
}
|
||||
|
||||
wx.hideLoading()
|
||||
|
||||
} catch (e) {
|
||||
wx.hideLoading()
|
||||
console.error('[Read] 登录后更新状态失败:', e)
|
||||
wx.showToast({ title: '状态更新失败,请重试', icon: 'none' })
|
||||
}
|
||||
},
|
||||
|
||||
// 购买章节 - 直接调起支付
|
||||
async handlePurchaseSection() {
|
||||
console.log('[Pay] 点击购买章节按钮')
|
||||
@@ -499,18 +638,38 @@ Page({
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否已购买(避免重复购买)
|
||||
if (type === 'section' && sectionId) {
|
||||
const purchasedSections = app.globalData.purchasedSections || []
|
||||
if (purchasedSections.includes(sectionId)) {
|
||||
wx.showToast({ title: '已购买过此章节', icon: 'none' })
|
||||
return
|
||||
// ✅ 从服务器查询是否已购买(基于 orders 表)
|
||||
try {
|
||||
wx.showLoading({ title: '检查购买状态...', mask: true })
|
||||
const userId = app.globalData.userInfo?.id
|
||||
|
||||
if (userId) {
|
||||
const checkRes = await app.request(`/api/miniprogram/user/purchase-status?userId=${userId}`)
|
||||
|
||||
if (checkRes.success && checkRes.data) {
|
||||
// 更新本地购买状态
|
||||
app.globalData.hasFullBook = checkRes.data.hasFullBook
|
||||
app.globalData.purchasedSections = checkRes.data.purchasedSections || []
|
||||
|
||||
// 检查是否已购买
|
||||
if (type === 'section' && sectionId) {
|
||||
if (checkRes.data.purchasedSections.includes(sectionId)) {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '已购买过此章节', icon: 'none' })
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'fullbook' && checkRes.data.hasFullBook) {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '已购买全书', icon: 'none' })
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'fullbook' && app.globalData.hasFullBook) {
|
||||
wx.showToast({ title: '已购买全书', icon: 'none' })
|
||||
return
|
||||
} catch (e) {
|
||||
console.warn('[Pay] 查询购买状态失败,继续支付流程:', e)
|
||||
// 查询失败不影响支付
|
||||
}
|
||||
|
||||
this.setData({ isPaying: true })
|
||||
@@ -556,6 +715,8 @@ Page({
|
||||
? '《一场Soul的创业实验》全书'
|
||||
: `章节${sectionId}-${sectionTitle.length > 20 ? sectionTitle.slice(0, 20) + '...' : sectionTitle}`
|
||||
|
||||
// 邀请码:谁邀请了我(从落地页 ref 或 storage 带入),会写入订单 referrer_id / referral_code 便于分销与对账
|
||||
const referralCode = wx.getStorageSync('referral_code') || ''
|
||||
const res = await app.request('/api/miniprogram/pay', {
|
||||
method: 'POST',
|
||||
data: {
|
||||
@@ -564,7 +725,8 @@ Page({
|
||||
productId: sectionId,
|
||||
amount,
|
||||
description,
|
||||
userId: app.globalData.userInfo?.id || ''
|
||||
userId: app.globalData.userInfo?.id || '',
|
||||
referralCode: referralCode || undefined
|
||||
}
|
||||
})
|
||||
|
||||
@@ -607,13 +769,10 @@ Page({
|
||||
try {
|
||||
await this.callWechatPay(paymentData)
|
||||
|
||||
// 4. 支付成功,更新本地数据
|
||||
// 4. 【标准流程】支付成功后刷新权限并解锁内容
|
||||
console.log('[Pay] 微信支付成功!')
|
||||
this.mockPaymentSuccess(type, sectionId)
|
||||
wx.showToast({ title: '购买成功', icon: 'success' })
|
||||
await this.onPaymentSuccess()
|
||||
|
||||
// 5. 刷新页面
|
||||
this.initSection(this.data.sectionId)
|
||||
} catch (payErr) {
|
||||
console.error('[Pay] 微信支付调起失败:', payErr)
|
||||
if (payErr.errMsg && payErr.errMsg.includes('cancel')) {
|
||||
@@ -648,25 +807,95 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
// 模拟支付成功
|
||||
mockPaymentSuccess(type, sectionId) {
|
||||
if (type === 'fullbook') {
|
||||
app.globalData.hasFullBook = true
|
||||
const userInfo = app.globalData.userInfo || {}
|
||||
userInfo.hasFullBook = true
|
||||
app.globalData.userInfo = userInfo
|
||||
wx.setStorageSync('userInfo', userInfo)
|
||||
} else if (sectionId) {
|
||||
const purchasedSections = app.globalData.purchasedSections || []
|
||||
if (!purchasedSections.includes(sectionId)) {
|
||||
purchasedSections.push(sectionId)
|
||||
app.globalData.purchasedSections = purchasedSections
|
||||
// 【新增】支付成功后的标准处理流程
|
||||
async onPaymentSuccess() {
|
||||
wx.showLoading({ title: '确认购买中...', mask: true })
|
||||
|
||||
try {
|
||||
// 1. 等待服务端处理支付回调(1-2秒)
|
||||
await this.sleep(2000)
|
||||
|
||||
// 2. 刷新用户购买状态
|
||||
await accessManager.refreshUserPurchaseStatus()
|
||||
|
||||
// 3. 重新判断当前章节权限(应为 unlocked_purchased)
|
||||
let newAccessState = await accessManager.determineAccessState(
|
||||
this.data.sectionId,
|
||||
this.data.freeIds
|
||||
)
|
||||
|
||||
// 如果权限未生效,再重试一次(可能回调延迟)
|
||||
if (newAccessState !== 'unlocked_purchased') {
|
||||
console.log('[Pay] 权限未生效,1秒后重试...')
|
||||
await this.sleep(1000)
|
||||
newAccessState = await accessManager.determineAccessState(
|
||||
this.data.sectionId,
|
||||
this.data.freeIds
|
||||
)
|
||||
}
|
||||
|
||||
const canAccess = accessManager.canAccessFullContent(newAccessState)
|
||||
|
||||
this.setData({
|
||||
accessState: newAccessState,
|
||||
canAccess,
|
||||
showPaywall: !canAccess
|
||||
})
|
||||
|
||||
// 4. 重新加载全文
|
||||
await this.loadContent(this.data.sectionId, newAccessState)
|
||||
|
||||
// 5. 初始化阅读追踪
|
||||
if (canAccess) {
|
||||
readingTracker.init(this.data.sectionId)
|
||||
}
|
||||
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '购买成功', icon: 'success' })
|
||||
|
||||
} catch (e) {
|
||||
wx.hideLoading()
|
||||
console.error('[Pay] 支付后更新失败:', e)
|
||||
wx.showModal({
|
||||
title: '提示',
|
||||
content: '购买成功,但内容加载失败,请返回重新进入',
|
||||
showCancel: false
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// ✅ 刷新用户购买状态(从服务器获取最新数据)
|
||||
async refreshUserPurchaseStatus() {
|
||||
try {
|
||||
const userId = app.globalData.userInfo?.id
|
||||
if (!userId) {
|
||||
console.warn('[Pay] 用户未登录,无法刷新购买状态')
|
||||
return
|
||||
}
|
||||
|
||||
// 调用专门的购买状态查询接口
|
||||
const res = await app.request(`/api/miniprogram/user/purchase-status?userId=${userId}`)
|
||||
|
||||
if (res.success && res.data) {
|
||||
// 更新全局购买状态
|
||||
app.globalData.hasFullBook = res.data.hasFullBook
|
||||
app.globalData.purchasedSections = res.data.purchasedSections || []
|
||||
|
||||
// 更新用户信息中的购买记录
|
||||
const userInfo = app.globalData.userInfo || {}
|
||||
userInfo.purchasedSections = purchasedSections
|
||||
userInfo.hasFullBook = res.data.hasFullBook
|
||||
userInfo.purchasedSections = res.data.purchasedSections || []
|
||||
app.globalData.userInfo = userInfo
|
||||
wx.setStorageSync('userInfo', userInfo)
|
||||
|
||||
console.log('[Pay] ✅ 购买状态已刷新:', {
|
||||
hasFullBook: res.data.hasFullBook,
|
||||
purchasedCount: res.data.purchasedSections.length
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[Pay] 刷新购买状态失败:', e)
|
||||
// 刷新失败时不影响用户体验,只是记录日志
|
||||
}
|
||||
},
|
||||
|
||||
@@ -905,5 +1134,63 @@ Page({
|
||||
},
|
||||
|
||||
// 阻止冒泡
|
||||
stopPropagation() {}
|
||||
stopPropagation() {},
|
||||
|
||||
// 【新增】页面隐藏时上报阅读进度
|
||||
onHide() {
|
||||
readingTracker.onPageHide()
|
||||
},
|
||||
|
||||
// 【新增】页面卸载时清理追踪器
|
||||
onUnload() {
|
||||
readingTracker.cleanup()
|
||||
},
|
||||
|
||||
// 【新增】重试加载(当 accessState 为 error 时)
|
||||
async handleRetry() {
|
||||
wx.showLoading({ title: '重试中...', mask: true })
|
||||
|
||||
try {
|
||||
// 重新拉取配置
|
||||
const config = await accessManager.fetchLatestConfig()
|
||||
this.setData({ freeIds: config.freeChapters })
|
||||
|
||||
// 重新判断权限
|
||||
const newAccessState = await accessManager.determineAccessState(
|
||||
this.data.sectionId,
|
||||
config.freeChapters
|
||||
)
|
||||
const canAccess = accessManager.canAccessFullContent(newAccessState)
|
||||
|
||||
this.setData({
|
||||
accessState: newAccessState,
|
||||
canAccess,
|
||||
showPaywall: !canAccess
|
||||
})
|
||||
|
||||
// 重新加载内容
|
||||
await this.loadContent(this.data.sectionId, newAccessState)
|
||||
|
||||
// 如果有权限,初始化阅读追踪
|
||||
if (canAccess) {
|
||||
readingTracker.init(this.data.sectionId)
|
||||
}
|
||||
|
||||
// 加载导航
|
||||
this.loadNavigation(this.data.sectionId)
|
||||
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '加载成功', icon: 'success' })
|
||||
|
||||
} catch (e) {
|
||||
wx.hideLoading()
|
||||
console.error('[Read] 重试失败:', e)
|
||||
wx.showToast({ title: '重试失败,请检查网络', icon: 'none' })
|
||||
}
|
||||
},
|
||||
|
||||
// 工具:延迟
|
||||
sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user