优化小程序支付流程,新增订单插入逻辑,确保支付成功后更新订单状态并处理佣金分配。同时,重构阅读页面,增强权限管理和阅读追踪功能,提升用户体验。
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,
|
||||
|
||||
// 阅读进度
|
||||
@@ -55,89 +68,143 @@ 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,
|
||||
fullBookPrice: config.prices.fullbook
|
||||
})
|
||||
|
||||
// 【标准流程】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
|
||||
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(`/api/book/chapter/${id}`)
|
||||
|
||||
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
|
||||
}
|
||||
},
|
||||
|
||||
@@ -421,21 +488,24 @@ Page({
|
||||
this.setData({ showLoginModal: false })
|
||||
},
|
||||
|
||||
// 微信登录
|
||||
// 从服务端刷新购买状态,避免登录后误用旧数据导致误解锁
|
||||
// 【重构】微信登录(标准流程)
|
||||
async handleWechatLogin() {
|
||||
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 })
|
||||
await this.onLoginSuccess()
|
||||
wx.showToast({ title: '登录成功', icon: 'success' })
|
||||
|
||||
} catch (e) {
|
||||
console.error('[Read] 登录失败:', e)
|
||||
wx.showToast({ title: '登录失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
|
||||
// 手机号登录
|
||||
// 【重构】手机号登录(标准流程)
|
||||
async handlePhoneLogin(e) {
|
||||
if (!e.detail.code) {
|
||||
return this.handleWechatLogin()
|
||||
@@ -443,16 +513,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 +612,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/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 +689,8 @@ Page({
|
||||
? '《一场Soul的创业实验》全书'
|
||||
: `章节${sectionId}-${sectionTitle.length > 20 ? sectionTitle.slice(0, 20) + '...' : sectionTitle}`
|
||||
|
||||
// 邀请码:谁邀请了我(从落地页 ref 或 storage 带入),用于订单分销归属
|
||||
const referralCode = wx.getStorageSync('referral_code') || ''
|
||||
const res = await app.request('/api/miniprogram/pay', {
|
||||
method: 'POST',
|
||||
data: {
|
||||
@@ -564,7 +699,8 @@ Page({
|
||||
productId: sectionId,
|
||||
amount,
|
||||
description,
|
||||
userId: app.globalData.userInfo?.id || ''
|
||||
userId: app.globalData.userInfo?.id || '',
|
||||
referralCode: referralCode || undefined
|
||||
}
|
||||
})
|
||||
|
||||
@@ -607,13 +743,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 +781,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/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 +1108,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))
|
||||
}
|
||||
})
|
||||
|
||||
1055
miniprogram/pages/read/read.js.backup
Normal file
1055
miniprogram/pages/read/read.js.backup
Normal file
File diff suppressed because it is too large
Load Diff
@@ -35,7 +35,7 @@
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-state" wx:if="{{loading}}">
|
||||
<view class="loading-state" wx:if="{{accessState === 'unknown' && loading}}">
|
||||
<view class="skeleton skeleton-1"></view>
|
||||
<view class="skeleton skeleton-2"></view>
|
||||
<view class="skeleton skeleton-3"></view>
|
||||
@@ -43,8 +43,8 @@
|
||||
<view class="skeleton skeleton-5"></view>
|
||||
</view>
|
||||
|
||||
<!-- 完整内容 - 有权限 -->
|
||||
<view class="article" wx:if="{{!loading && canAccess}}">
|
||||
<!-- 完整内容 - 免费或已购买 -->
|
||||
<view class="article" wx:if="{{accessState === 'free' || accessState === 'unlocked_purchased'}}">
|
||||
<view class="paragraph" wx:for="{{contentParagraphs}}" wx:key="index" wx:if="{{item}}">
|
||||
{{item}}
|
||||
</view>
|
||||
@@ -95,8 +95,8 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 预览内容 + 付费墙 - 无权限 -->
|
||||
<view class="article preview" wx:if="{{!loading && !canAccess}}">
|
||||
<!-- 预览内容 + 付费墙 - 未登录 -->
|
||||
<view class="article preview" wx:if="{{accessState === 'locked_not_login'}}">
|
||||
<view class="paragraph" wx:for="{{previewParagraphs}}" wx:key="index" wx:if="{{item}}">
|
||||
{{item}}
|
||||
</view>
|
||||
@@ -104,46 +104,18 @@
|
||||
<!-- 渐变遮罩 -->
|
||||
<view class="fade-mask"></view>
|
||||
|
||||
<!-- 付费墙 -->
|
||||
<view class="paywall" wx:if="{{showPaywall}}">
|
||||
<!-- 付费墙 - 未登录 -->
|
||||
<view class="paywall">
|
||||
<view class="paywall-icon">🔒</view>
|
||||
<text class="paywall-title">解锁完整内容</text>
|
||||
<text class="paywall-desc">
|
||||
已阅读20%,{{isLoggedIn ? '购买后继续阅读' : '登录并购买后继续阅读'}}
|
||||
</text>
|
||||
<text class="paywall-title">登录后继续阅读</text>
|
||||
<text class="paywall-desc">已阅读20%,登录后查看完整内容</text>
|
||||
|
||||
<!-- 未登录时显示登录按钮 -->
|
||||
<view class="login-prompt" wx:if="{{!isLoggedIn}}">
|
||||
<view class="login-btn" bindtap="showLoginModal">
|
||||
<text class="login-btn-text">请先登录</text>
|
||||
</view>
|
||||
<view class="login-btn" bindtap="showLoginModal">
|
||||
<text class="login-btn-text">立即登录</text>
|
||||
</view>
|
||||
|
||||
<!-- 已登录显示购买选项 -->
|
||||
<view class="purchase-options" wx:else>
|
||||
<!-- 购买本章 - 直接调起支付 -->
|
||||
<view class="purchase-btn purchase-section" bindtap="handlePurchaseSection">
|
||||
<text class="btn-label">购买本章</text>
|
||||
<text class="btn-price brand-color">¥{{section.price}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 解锁全书 - 只有购买超过3章才显示 -->
|
||||
<view class="purchase-btn purchase-fullbook" bindtap="handlePurchaseFullBook" wx:if="{{purchasedCount >= 3}}">
|
||||
<view class="btn-left">
|
||||
<text class="btn-sparkle">✨</text>
|
||||
<text class="btn-label">解锁全部 {{totalSections}} 章</text>
|
||||
</view>
|
||||
<view class="btn-right">
|
||||
<text class="btn-price">¥{{fullBookPrice}}</text>
|
||||
<text class="btn-discount">省82%</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<text class="paywall-tip">分享给好友一起学习</text>
|
||||
</view>
|
||||
|
||||
<!-- 章节导航 - 付费内容也显示 -->
|
||||
<!-- 章节导航 -->
|
||||
<view class="chapter-nav chapter-nav-locked">
|
||||
<view class="nav-buttons">
|
||||
<view
|
||||
@@ -173,6 +145,97 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 预览内容 + 付费墙 - 已登录未购买 -->
|
||||
<view class="article preview" wx:if="{{accessState === 'locked_not_purchased'}}">
|
||||
<view class="paragraph" wx:for="{{previewParagraphs}}" wx:key="index" wx:if="{{item}}">
|
||||
{{item}}
|
||||
</view>
|
||||
|
||||
<!-- 渐变遮罩 -->
|
||||
<view class="fade-mask"></view>
|
||||
|
||||
<!-- 付费墙 - 已登录未购买 -->
|
||||
<view class="paywall">
|
||||
<view class="paywall-icon">🔒</view>
|
||||
<text class="paywall-title">解锁完整内容</text>
|
||||
<text class="paywall-desc">已阅读20%,购买后继续阅读</text>
|
||||
|
||||
<!-- 购买选项 -->
|
||||
<view class="purchase-options">
|
||||
<!-- 购买本章 - 直接调起支付 -->
|
||||
<view class="purchase-btn purchase-section" bindtap="handlePurchaseSection">
|
||||
<text class="btn-label">购买本章</text>
|
||||
<text class="btn-price brand-color">¥{{section.price}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 解锁全书 - 只有购买超过3章才显示 -->
|
||||
<view class="purchase-btn purchase-fullbook" bindtap="handlePurchaseFullBook" wx:if="{{purchasedCount >= 3}}">
|
||||
<view class="btn-left">
|
||||
<text class="btn-sparkle">✨</text>
|
||||
<text class="btn-label">解锁全部 {{totalSections}} 章</text>
|
||||
</view>
|
||||
<view class="btn-right">
|
||||
<text class="btn-price">¥{{fullBookPrice}}</text>
|
||||
<text class="btn-discount">省82%</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<text class="paywall-tip">分享给好友一起学习,还能赚取佣金</text>
|
||||
</view>
|
||||
|
||||
<!-- 章节导航 -->
|
||||
<view class="chapter-nav chapter-nav-locked">
|
||||
<view class="nav-buttons">
|
||||
<view
|
||||
class="nav-btn nav-prev {{!prevSection ? 'nav-disabled' : ''}}"
|
||||
bindtap="goToPrev"
|
||||
wx:if="{{prevSection}}"
|
||||
>
|
||||
<text class="btn-label">上一篇</text>
|
||||
<text class="btn-title">章节 {{prevSection.id}}</text>
|
||||
</view>
|
||||
<view class="nav-btn-placeholder" wx:else></view>
|
||||
|
||||
<view
|
||||
class="nav-btn nav-next"
|
||||
bindtap="goToNext"
|
||||
wx:if="{{nextSection}}"
|
||||
>
|
||||
<text class="btn-label">下一篇</text>
|
||||
<view class="btn-row">
|
||||
<text class="btn-title">{{nextSection.title}}</text>
|
||||
<text class="btn-arrow">→</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="nav-btn nav-end" wx:else>
|
||||
<text class="btn-end-text">已是最后一篇 🎉</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 错误状态 - 网络异常 -->
|
||||
<view class="article preview" wx:if="{{accessState === 'error'}}">
|
||||
<view class="paragraph" wx:for="{{previewParagraphs}}" wx:key="index" wx:if="{{item}}">
|
||||
{{item}}
|
||||
</view>
|
||||
|
||||
<!-- 渐变遮罩 -->
|
||||
<view class="fade-mask"></view>
|
||||
|
||||
<!-- 错误提示 -->
|
||||
<view class="paywall">
|
||||
<view class="paywall-icon">⚠️</view>
|
||||
<text class="paywall-title">网络异常</text>
|
||||
<text class="paywall-desc">无法确认权限,请检查网络后重试</text>
|
||||
|
||||
<view class="login-btn" bindtap="handleRetry">
|
||||
<text class="login-btn-text">重新加载</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 海报生成弹窗 -->
|
||||
|
||||
Reference in New Issue
Block a user