feat: 完整重构小程序匹配功能 + 修复UI对齐 + 文章数据API

主要更新:
1. 按H5网页端完全重构匹配功能(match页面)
   - 4种匹配类型: 创业合伙/资源对接/导师顾问/团队招募
   - 资源对接等类型弹出手机号/微信号输入框
   - 去掉重新匹配按钮,改为返回按钮

2. 修复所有卡片对齐和宽度问题
   - 目录页附录卡片居中
   - 首页阅读进度卡片满宽度
   - 我的页面菜单卡片对齐
   - 推广中心分享卡片统一宽度

3. 修复目录页图标和文字对齐
   - section-icon固定40rpx宽高
   - section-title与图标垂直居中

4. 更新真实完整文章标题(62篇)
   - 从book目录读取真实markdown文件名
   - 替换之前的简化标题

5. 新增文章数据API
   - /api/db/chapters - 获取完整书籍结构
   - 支持按ID获取单篇文章内容
This commit is contained in:
卡若
2026-01-21 15:49:12 +08:00
parent 1ee25e3dab
commit b60edb3d47
197 changed files with 34430 additions and 7345 deletions

View File

@@ -1,343 +1,487 @@
// pages/read/read.js
/**
* Soul创业实验 - 阅读页
* 开发: 卡若
* 技术支持: 存客宝
*/
const app = getApp()
const paymentUtil = require('../../utils/payment')
Page({
data: {
chapterId: '',
chapterInfo: {},
contentHtml: '',
// 系统信息
statusBarHeight: 44,
navBarHeight: 88,
// 章节信息
sectionId: '',
section: null,
partTitle: '',
chapterTitle: '',
// 内容
content: '',
previewContent: '',
contentParagraphs: [],
previewParagraphs: [],
loading: true,
hasPrev: false,
hasNext: false,
isBookmarked: false,
showCatalog: false,
allChapters: []
// 用户状态
isLoggedIn: false,
hasFullBook: false,
canAccess: false,
purchasedCount: 0,
// 阅读进度
readingProgress: 0,
showPaywall: false,
// 上一篇/下一篇
prevSection: null,
nextSection: null,
// 价格
sectionPrice: 1,
fullBookPrice: 9.9,
totalSections: 62,
// 弹窗
showShareModal: false,
showLoginModal: false,
isPaying: false,
// 免费章节
freeIds: ['preface', 'epilogue', '1.1', 'appendix-1', 'appendix-2', 'appendix-3']
},
onLoad(options) {
const chapterId = options.id
if (chapterId) {
this.setData({ chapterId })
this.loadChapter(chapterId)
this.loadAllChapters()
this.checkBookmark(chapterId)
const { id, ref } = options
this.setData({
statusBarHeight: app.globalData.statusBarHeight,
navBarHeight: app.globalData.navBarHeight,
sectionId: id
})
// 保存推荐码
if (ref) {
wx.setStorageSync('referral_code', ref)
}
this.initSection(id)
},
onPageScroll(e) {
// 计算阅读进度
const query = wx.createSelectorQuery()
query.select('.page').boundingClientRect()
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
this.setData({ readingProgress: progress })
}
})
},
// 初始化章节
async initSection(id) {
this.setData({ loading: true })
try {
// 模拟获取章节数据
const section = this.getSectionInfo(id)
const { isLoggedIn, hasFullBook, purchasedSections } = app.globalData
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)
} catch (e) {
console.error('初始化章节失败:', e)
wx.showToast({ title: '加载失败', icon: 'none' })
} finally {
this.setData({ loading: false })
}
},
// 加载章节内容
loadChapter(chapterId) {
this.setData({ loading: true })
wx.showLoading({ title: '加载中...', mask: true })
wx.request({
url: `${app.globalData.apiBase}/book/chapter/${chapterId}`,
header: {
'Authorization': `Bearer ${wx.getStorageSync('token')}`
},
success: (res) => {
if (res.statusCode === 200) {
const chapter = res.data
// 检查是否需要购买
if (chapter.needPurchase && !this.checkPurchased()) {
this.showPurchaseModal()
return
}
this.setData({
chapterInfo: {
title: chapter.title,
updateTime: chapter.updateTime,
words: chapter.words,
readTime: Math.ceil(chapter.words / 300)
},
contentHtml: this.markdownToHtml(chapter.content),
hasPrev: !!chapter.prevChapterId,
hasNext: !!chapter.nextChapterId,
loading: false
})
// 记录阅读进度
this.recordReadProgress(chapterId)
} else {
wx.showToast({
title: '章节加载失败',
icon: 'none'
})
}
},
fail: () => {
// 使用Mock数据
this.loadMockChapter(chapterId)
},
complete: () => {
wx.hideLoading()
// 获取章节信息
getSectionInfo(id) {
// 特殊章节
if (id === 'preface') {
return { id: 'preface', title: '为什么我每天早上6点在Soul开播?', isFree: true, price: 0 }
}
if (id === 'epilogue') {
return { id: 'epilogue', title: '这本书的真实目的', isFree: true, price: 0 }
}
if (id.startsWith('appendix')) {
const appendixTitles = {
'appendix-1': 'Soul派对房精选对话',
'appendix-2': '创业者自检清单',
'appendix-3': '本书提到的工具和资源'
}
})
return { id, title: appendixTitles[id] || '附录', isFree: true, price: 0 }
}
// 普通章节
return {
id: id,
title: this.getSectionTitle(id),
isFree: id === '1.1',
price: 1
}
},
// 加载Mock章节
loadMockChapter(chapterId) {
const mockContent = `
# 这是章节标题
// 获取章节标题
getSectionTitle(id) {
const titles = {
'1.1': '荷包:电动车出租的被动收入模式',
'1.2': '老墨:资源整合高手的社交方法',
'1.3': '笑声背后的MBTI',
'1.4': '人性的三角结构:利益、情感、价值观',
'1.5': '沟通差的问题:为什么你说的别人听不懂',
'2.1': '相亲故事:你以为找的是人,实际是在找模式',
'2.2': '找工作迷茫者:为什么简历解决不了人生',
'2.3': '撸运费险:小钱困住大脑的真实心理',
'2.4': '游戏上瘾的年轻人:不是游戏吸引他,是生活没吸引力',
'2.5': '健康焦虑(我的糖尿病经历):疾病是人生的第一次清醒',
'3.1': '3000万流水如何跑出来(退税模式解析)',
'8.1': '流量杠杆:抖音、Soul、飞书',
'9.14': '大健康私域一个月150万的70后'
}
return titles[id] || `章节 ${id}`
},
这是第一段内容,介绍了关于私域运营的基本概念...
## 第一小节
详细内容描述...
### 要点总结
1. 第一点
2. 第二点
3. 第三点
**重点强调的内容**
> 引用的内容或者金句
`
// 加载内容
async loadContent(id) {
try {
// 尝试从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,
previewContent: lines.slice(0, previewCount).join('\n'),
contentParagraphs: lines,
previewParagraphs: lines.slice(0, previewCount),
partTitle: res.partTitle || '',
chapterTitle: res.chapterTitle || ''
})
return
}
} catch (e) {
console.log('API加载失败使用示例内容')
}
// 使用示例内容
const sampleContent = this.getSampleContent(id)
const lines = sampleContent.split('\n').filter(line => line.trim())
const previewCount = Math.ceil(lines.length * 0.2)
this.setData({
chapterInfo: {
title: '第一章|我是谁',
updateTime: '2天前',
words: 3200,
readTime: 11
},
contentHtml: this.markdownToHtml(mockContent),
hasPrev: false,
hasNext: true,
loading: false
content: sampleContent,
previewContent: lines.slice(0, previewCount).join('\n'),
contentParagraphs: lines,
previewParagraphs: lines.slice(0, previewCount)
})
},
// Markdown转HTML简单实现
markdownToHtml(markdown) {
if (!markdown) return ''
// 获取示例内容
getSampleContent(id) {
return `这是《一场SOUL的创业实验场》的章节内容。
在Soul派对房里每天早上6点到9点我都会和几百个陌生人分享真实的商业故事。
这不是一本教你成功的鸡汤书,而是一本记录真实创业经历的实战手册。
我见过太多人在创业路上摔倒,不是因为他们不够努力,而是因为他们不懂得商业的底层逻辑。
商业的本质是什么?是流量、是供应链、是人脉,但更重要的是——认知。
当你的认知提升了,你才能看到别人看不到的机会。
这本书会告诉你:
1. 如何找到真正赚钱的模式
2. 如何避免常见的创业陷阱
3. 如何构建自己的商业体系
希望这本书能帮助你少走弯路,在创业路上走得更稳、更远。
${id === 'preface' || id === 'epilogue' || id.startsWith('appendix') || id === '1.1'
? '这是免费章节的完整内容。欢迎来到Soul创业实验场'
: '这是付费章节的预览内容。购买后可查看完整内容。'}`
},
// 加载导航
loadNavigation(id) {
const sectionOrder = [
'preface', '1.1', '1.2', '1.3', '1.4', '1.5',
'2.1', '2.2', '2.3', '2.4', '2.5',
'3.1', '3.2', '3.3', '3.4',
'4.1', '4.2', '4.3', '4.4', '4.5',
'5.1', '5.2', '5.3', '5.4', '5.5',
'6.1', '6.2', '6.3', '6.4',
'7.1', '7.2', '7.3', '7.4', '7.5',
'8.1', '8.2', '8.3', '8.4', '8.5', '8.6',
'9.1', '9.2', '9.3', '9.4', '9.5', '9.6', '9.7', '9.8', '9.9', '9.10', '9.11', '9.12', '9.13', '9.14',
'10.1', '10.2', '10.3', '10.4',
'11.1', '11.2', '11.3', '11.4', '11.5',
'epilogue'
]
let html = markdown
.replace(/### (.*)/g, '<h3>$1</h3>')
.replace(/## (.*)/g, '<h2>$1</h2>')
.replace(/# (.*)/g, '<h1>$1</h1>')
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.*?)\*/g, '<em>$1</em>')
.replace(/> (.*)/g, '<blockquote>$1</blockquote>')
.replace(/\n/g, '<br/>')
const currentIndex = sectionOrder.indexOf(id)
const prevId = currentIndex > 0 ? sectionOrder[currentIndex - 1] : null
const nextId = currentIndex < sectionOrder.length - 1 ? sectionOrder[currentIndex + 1] : null
return html
},
// 检查是否已购买
checkPurchased() {
return paymentUtil.checkPurchaseStatus()
},
// 显示购买弹窗
showPurchaseModal() {
wx.showModal({
title: '需要购买',
content: '此章节需要购买完整版才能阅读',
confirmText: '立即购买',
success: (res) => {
if (res.confirm) {
this.purchase()
} else {
wx.navigateBack()
}
}
})
},
// 购买
purchase() {
paymentUtil.purchaseFullBook(
() => {
wx.showToast({
title: '购买成功',
icon: 'success'
})
// 重新加载章节
setTimeout(() => {
this.loadChapter(this.data.chapterId)
}, 1500)
},
() => {
wx.showToast({
title: '购买失败',
icon: 'none'
})
}
)
},
// 记录阅读进度
recordReadProgress(chapterId) {
wx.request({
url: `${app.globalData.apiBase}/user/read-progress`,
method: 'POST',
header: {
'Authorization': `Bearer ${wx.getStorageSync('token')}`
},
data: {
chapterId,
timestamp: Date.now()
}
})
},
// 加载所有章节
loadAllChapters() {
wx.request({
url: `${app.globalData.apiBase}/book/chapters`,
success: (res) => {
if (res.statusCode === 200) {
this.setData({
allChapters: res.data.chapters || []
})
}
}
})
},
// 检查书签
checkBookmark(chapterId) {
const bookmarks = wx.getStorageSync('bookmarks') || []
const isBookmarked = bookmarks.includes(chapterId)
this.setData({ isBookmarked })
},
// 上一章
prevChapter() {
if (!this.data.hasPrev) return
// TODO: 获取上一章ID
wx.showToast({
title: '功能开发中',
icon: 'none'
})
},
// 下一章
nextChapter() {
if (!this.data.hasNext) return
// TODO: 获取下一章ID
wx.showToast({
title: '功能开发中',
icon: 'none'
this.setData({
prevSection: prevId ? { id: prevId, title: this.getSectionTitle(prevId) } : null,
nextSection: nextId ? { id: nextId, title: this.getSectionTitle(nextId) } : null
})
},
// 返回
goBack() {
wx.navigateBack()
wx.navigateBack({
fail: () => wx.switchTab({ url: '/pages/chapters/chapters' })
})
},
// 显示菜单
showMenu() {
wx.showActionSheet({
itemList: ['调整字体', '夜间模式', '分享好友'],
success: (res) => {
switch(res.tapIndex) {
case 0:
this.adjustFont()
break
case 1:
this.toggleNightMode()
break
case 2:
this.share()
break
}
// 分享弹窗
showShare() {
this.setData({ showShareModal: true })
},
closeShareModal() {
this.setData({ showShareModal: false })
},
// 复制链接
copyLink() {
const userInfo = app.globalData.userInfo
const referralCode = userInfo?.referralCode || ''
const shareUrl = `https://soul.ckb.fit/read/${this.data.sectionId}${referralCode ? '?ref=' + referralCode : ''}`
wx.setClipboardData({
data: shareUrl,
success: () => {
wx.showToast({ title: '链接已复制', icon: 'success' })
this.setData({ showShareModal: false })
}
})
},
// 书签
bookmark() {
const chapterId = this.data.chapterId
let bookmarks = wx.getStorageSync('bookmarks') || []
// 分享到微信
onShareAppMessage() {
const { section, sectionId } = this.data
const userInfo = app.globalData.userInfo
const referralCode = userInfo?.referralCode || ''
if (this.data.isBookmarked) {
// 移除书签
bookmarks = bookmarks.filter(id => id !== chapterId)
wx.showToast({
title: '已移除书签',
icon: 'success'
})
} else {
// 添加书签
bookmarks.push(chapterId)
wx.showToast({
title: '已添加书签',
icon: 'success'
})
return {
title: `📚 ${section?.title || '推荐阅读'}`,
path: `/pages/read/read?id=${sectionId}${referralCode ? '&ref=' + referralCode : ''}`
}
},
// 显示登录弹窗
showLoginModal() {
this.setData({ showLoginModal: true })
},
closeLoginModal() {
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' })
}
} catch (e) {
wx.showToast({ title: '登录失败', icon: 'none' })
}
},
// 手机号登录
async handlePhoneLogin(e) {
if (!e.detail.code) {
return this.handleWechatLogin()
}
wx.setStorageSync('bookmarks', bookmarks)
this.setData({
isBookmarked: !this.data.isBookmarked
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' })
}
} catch (e) {
wx.showToast({ title: '登录失败', icon: 'none' })
}
},
// 购买章节 - 直接调起支付
async handlePurchaseSection() {
if (!this.data.isLoggedIn) {
this.setData({ showLoginModal: true })
return
}
await this.processPayment('section', this.data.sectionId, this.data.section.price)
},
// 购买全书 - 直接调起支付
async handlePurchaseFullBook() {
if (!this.data.isLoggedIn) {
this.setData({ showLoginModal: true })
return
}
await this.processPayment('fullbook', null, this.data.fullBookPrice)
},
// 处理支付
async processPayment(type, sectionId, amount) {
this.setData({ isPaying: true })
try {
// 创建订单
let paymentData = null
try {
const res = await app.request('/api/payment/create-order', {
method: 'POST',
data: { type, sectionId, amount }
})
if (res.success && res.data) {
paymentData = res.data
}
} catch (apiError) {
console.log('API创建订单失败使用模拟支付')
}
// 如果API不可用使用模拟支付
if (!paymentData) {
paymentData = {
isMock: true,
orderId: 'mock_' + Date.now()
}
}
// 调用微信支付或模拟支付
if (paymentData.isMock) {
// 模拟支付确认
const confirmRes = await new Promise((resolve) => {
wx.showModal({
title: '确认支付',
content: `支付 ¥${amount} 购买${type === 'section' ? '本章' : '全书'}\n(测试环境模拟支付)`,
success: (res) => resolve(res.confirm)
})
})
if (confirmRes) {
// 模拟支付成功,更新本地数据
this.mockPaymentSuccess(type, sectionId)
wx.showToast({ title: '购买成功', icon: 'success' })
}
} else {
// 真实微信支付
await this.callWechatPay(paymentData)
wx.showToast({ title: '购买成功', icon: 'success' })
}
// 刷新页面
this.initSection(this.data.sectionId)
} catch (e) {
if (e.errMsg && e.errMsg.includes('cancel')) {
wx.showToast({ title: '已取消支付', icon: 'none' })
} else {
wx.showToast({ title: '支付失败', icon: 'none' })
}
} finally {
this.setData({ isPaying: false })
}
},
// 模拟支付成功
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
const userInfo = app.globalData.userInfo || {}
userInfo.purchasedSections = purchasedSections
app.globalData.userInfo = userInfo
wx.setStorageSync('userInfo', userInfo)
}
}
},
// 调用微信支付
callWechatPay(paymentData) {
return new Promise((resolve, reject) => {
wx.requestPayment({
timeStamp: paymentData.timeStamp,
nonceStr: paymentData.nonceStr,
package: paymentData.package,
signType: paymentData.signType || 'MD5',
paySign: paymentData.paySign,
success: resolve,
fail: reject
})
})
},
// 笔记
note() {
wx.navigateTo({
url: `/pages/note/edit?chapterId=${this.data.chapterId}`
})
// 跳转到上一篇
goToPrev() {
if (this.data.prevSection) {
wx.redirectTo({ url: `/pages/read/read?id=${this.data.prevSection.id}` })
}
},
// 显示目录
showCatalog() {
this.setData({ showCatalog: true })
// 跳转到下一篇
goToNext() {
if (this.data.nextSection) {
wx.redirectTo({ url: `/pages/read/read?id=${this.data.nextSection.id}` })
}
},
// 隐藏目录
hideCatalog() {
this.setData({ showCatalog: false })
},
// 选择章节
selectChapter(e) {
const chapterId = e.currentTarget.dataset.id
this.setData({
chapterId,
showCatalog: false
})
this.loadChapter(chapterId)
this.checkBookmark(chapterId)
},
// 分享
share() {
wx.showShareMenu({
withShareTicket: true,
menus: ['shareAppMessage', 'shareTimeline']
})
},
// 跳转到推广页面
// 跳转到推广中心
goToReferral() {
wx.switchTab({
url: '/pages/my/my?tab=referral'
})
wx.navigateTo({ url: '/pages/referral/referral' })
},
// 阻止冒泡
stopPropagation() {},
// 分享给好友
onShareAppMessage() {
const userInfo = app.getUserInfo()
const inviteCode = userInfo ? userInfo.inviteCode : ''
return {
title: this.data.chapterInfo.title,
path: `/pages/read/read?id=${this.data.chapterId}&invite=${inviteCode}`,
imageUrl: '/assets/images/share-chapter.png'
}
}
stopPropagation() {}
})

View File

@@ -0,0 +1,7 @@
{
"usingComponents": {},
"enablePullDownRefresh": false,
"backgroundTextStyle": "light",
"backgroundColor": "#000000",
"navigationStyle": "custom"
}

View File

@@ -1,112 +1,204 @@
<!--pages/read/read.wxml-->
<view class="container read-container">
<!--Soul创业实验 - 阅读页 1:1还原Web版本-->
<view class="page">
<!-- 阅读进度条 -->
<view class="progress-bar-fixed" style="top: {{statusBarHeight}}px;">
<view class="progress-fill" style="width: {{readingProgress}}%;"></view>
</view>
<!-- 顶部导航栏 -->
<view class="read-header">
<view class="header-left" bindtap="goBack">
<text class="back-icon">←</text>
</view>
<view class="header-title">{{chapterInfo.title}}</view>
<view class="header-right" bindtap="showMenu">
<text class="menu-icon">⋯</text>
<view class="nav-bar" style="padding-top: {{statusBarHeight}}px;">
<view class="nav-content">
<view class="nav-back" bindtap="goBack">
<text class="back-arrow">←</text>
</view>
<view class="nav-info">
<text class="nav-part" wx:if="{{partTitle}}">{{partTitle}}</text>
<text class="nav-chapter" wx:if="{{chapterTitle}}">{{chapterTitle}}</text>
</view>
<view class="nav-share" bindtap="showShare">
<text class="share-icon">↗</text>
</view>
</view>
</view>
<!-- 章节内容 -->
<scroll-view class="content-scroll" scroll-y enhanced show-scrollbar="{{false}}">
<!-- 骨架屏 -->
<view class="content-skeleton" wx:if="{{loading}}">
<view class="skeleton skeleton-title"></view>
<view class="skeleton skeleton-line"></view>
<view class="skeleton skeleton-line"></view>
<view class="skeleton skeleton-line short"></view>
<view class="skeleton skeleton-line"></view>
<view class="skeleton skeleton-line"></view>
<!-- 导航栏占位 -->
<view class="nav-placeholder" style="height: {{statusBarHeight + 44}}px;"></view>
<!-- 阅读内容 -->
<view class="read-content">
<!-- 章节标题 -->
<view class="chapter-header">
<view class="chapter-meta">
<text class="chapter-id">{{section.id}}</text>
<text class="tag tag-free" wx:if="{{section.isFree}}">免费</text>
</view>
<text class="chapter-title">{{section.title}}</text>
</view>
<!-- 实际内容 -->
<view class="content-wrapper" wx:if="{{!loading}}">
<view class="chapter-title">{{chapterInfo.title}}</view>
<view class="chapter-meta">
<text class="meta-item">{{chapterInfo.updateTime}}</text>
<text class="meta-divider">·</text>
<text class="meta-item">{{chapterInfo.words}}字</text>
<text class="meta-divider">·</text>
<text class="meta-item">{{chapterInfo.readTime}}分钟</text>
</view>
<!-- 加载状态 -->
<view class="loading-state" wx:if="{{loading}}">
<view class="skeleton skeleton-1"></view>
<view class="skeleton skeleton-2"></view>
<view class="skeleton skeleton-3"></view>
<view class="skeleton skeleton-4"></view>
<view class="skeleton skeleton-5"></view>
</view>
<view class="chapter-content markdown-body">
<rich-text nodes="{{contentHtml}}"></rich-text>
<!-- 完整内容 - 有权限 -->
<view class="article" wx:if="{{!loading && canAccess}}">
<view class="paragraph" wx:for="{{contentParagraphs}}" wx:key="index" wx:if="{{item}}">
{{item}}
</view>
<!-- 章节导航 -->
<view class="chapter-nav">
<button
class="nav-btn prev-btn {{!hasPrev ? 'disabled' : ''}}"
bindtap="prevChapter"
disabled="{{!hasPrev}}"
>
上一章
</button>
<button
class="nav-btn next-btn {{!hasNext ? 'disabled' : ''}}"
bindtap="nextChapter"
disabled="{{!hasNext}}"
>
下一章
</button>
</view>
<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.title}}</text>
</view>
<view class="nav-btn-placeholder" wx:else></view>
<!-- 推广提示 -->
<view class="promotion-tip card">
<view class="tip-icon">💰</view>
<view class="tip-content">
<view class="tip-title">喜欢这本书?</view>
<view class="tip-desc">分享给朋友每笔成交您可获得90%佣金</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 class="share-tip" bindtap="showShare">
<view class="tip-content">
<text class="tip-title">觉得不错?分享给好友</text>
<text class="tip-desc">好友购买你获得90%佣金</text>
</view>
<view class="tip-btn">分享赚钱</view>
</view>
<button class="tip-btn" bindtap="goToReferral">去分享</button>
</view>
</view>
</scroll-view>
<!-- 底部工具栏 -->
<view class="read-toolbar glass-effect">
<view class="toolbar-item" bindtap="bookmark">
<text class="toolbar-icon">{{isBookmarked ? '🔖' : '📑'}}</text>
<text class="toolbar-label">书签</text>
<!-- 预览内容 + 付费墙 - 无权限 -->
<view class="article preview" wx:if="{{!loading && !canAccess}}">
<view class="paragraph" wx:for="{{previewParagraphs}}" wx:key="index" wx:if="{{item}}">
{{item}}
</view>
<!-- 渐变遮罩 -->
<view class="fade-mask"></view>
<!-- 付费墙 -->
<view class="paywall" wx:if="{{showPaywall}}">
<view class="paywall-icon">🔒</view>
<text class="paywall-title">解锁完整内容</text>
<text class="paywall-desc">
已阅读20%{{isLoggedIn ? '购买后继续阅读' : '登录并购买后继续阅读'}}
</text>
<!-- 未登录时显示登录按钮 -->
<view class="login-prompt" wx:if="{{!isLoggedIn}}">
<view class="login-btn" bindtap="showLoginModal">
<text class="login-btn-text">请先登录</text>
</view>
</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">分享给好友购买你可获得90%佣金</text>
</view>
</view>
<view class="toolbar-item" bindtap="note">
<text class="toolbar-icon">📝</text>
<text class="toolbar-label">笔记</text>
</view>
<!-- 分享弹窗 -->
<view class="modal-overlay" wx:if="{{showShareModal}}" bindtap="closeShareModal">
<view class="modal-content share-modal" catchtap="stopPropagation">
<view class="modal-header">
<text class="modal-title">分享文章</text>
<view class="modal-close" bindtap="closeShareModal">✕</view>
</view>
<view class="share-link-box">
<text class="link-label">你的专属分享链接</text>
<text class="link-url">https://soul.ckb.fit/read/{{sectionId}}</text>
<text class="link-tip">邀请码: 好友购买你获得90%佣金</text>
</view>
<view class="share-buttons">
<view class="share-btn" bindtap="copyLink">
<view class="share-btn-icon icon-copy">📋</view>
<text class="share-btn-text">复制链接</text>
</view>
<button class="share-btn" open-type="share">
<view class="share-btn-icon icon-wechat">微</view>
<text class="share-btn-text">微信好友</text>
</button>
<view class="share-btn" bindtap="goToReferral">
<view class="share-btn-icon icon-poster">🖼️</view>
<text class="share-btn-text">生成海报</text>
</view>
</view>
</view>
<view class="toolbar-item" bindtap="showCatalog">
<text class="toolbar-icon">📚</text>
<text class="toolbar-label">目录</text>
</view>
<!-- 登录弹窗 -->
<view class="modal-overlay" wx:if="{{showLoginModal}}" bindtap="closeLoginModal">
<view class="modal-content login-modal" 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">
<text class="btn-wechat-icon">微</text>
<text>微信快捷登录</text>
</button>
<button class="btn-phone" open-type="getPhoneNumber" bindgetphonenumber="handlePhoneLogin">
<text class="btn-phone-icon">📱</text>
<text>手机号登录</text>
</button>
<text class="login-notice">登录即表示同意《用户协议》和《隐私政策》</text>
</view>
<view class="toolbar-item" bindtap="share">
<text class="toolbar-icon">📤</text>
<text class="toolbar-label">分享</text>
</view>
<!-- 支付中提示 -->
<view class="modal-overlay" wx:if="{{isPaying}}" catchtap="">
<view class="loading-box">
<view class="loading-spinner"></view>
<text class="loading-text">支付处理中...</text>
</view>
</view>
</view>
<!-- 目录弹窗 -->
<view class="catalog-modal" wx:if="{{showCatalog}}" bindtap="hideCatalog">
<view class="catalog-content" catchtap="stopPropagation">
<view class="catalog-header">
<text class="catalog-title">目录</text>
<text class="catalog-close" bindtap="hideCatalog">×</text>
</view>
<scroll-view class="catalog-list" scroll-y>
<view
class="catalog-item {{item.id === chapterId ? 'active' : ''}}"
wx:for="{{allChapters}}"
wx:key="id"
bindtap="selectChapter"
data-id="{{item.id}}"
>
<text class="catalog-item-title">{{item.title}}</text>
<text class="catalog-item-icon" wx:if="{{item.id === chapterId}}">📖</text>
</view>
</scroll-view>
</view>
</view>

File diff suppressed because it is too large Load Diff