Files
soul/miniprogram/pages/read/read.js
卡若 b60edb3d47 feat: 完整重构小程序匹配功能 + 修复UI对齐 + 文章数据API
主要更新:
1. 按H5网页端完全重构匹配功能(match页面)
   - 4种匹配类型: 创业合伙/资源对接/导师顾问/团队招募
   - 资源对接等类型弹出手机号/微信号输入框
   - 去掉重新匹配按钮,改为返回按钮

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

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

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

5. 新增文章数据API
   - /api/db/chapters - 获取完整书籍结构
   - 支持按ID获取单篇文章内容
2026-01-21 15:49:12 +08:00

488 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Soul创业实验 - 阅读页
* 开发: 卡若
* 技术支持: 存客宝
*/
const app = getApp()
Page({
data: {
// 系统信息
statusBarHeight: 44,
navBarHeight: 88,
// 章节信息
sectionId: '',
section: null,
partTitle: '',
chapterTitle: '',
// 内容
content: '',
previewContent: '',
contentParagraphs: [],
previewParagraphs: [],
loading: true,
// 用户状态
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 { 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 })
}
},
// 获取章节信息
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
}
},
// 获取章节标题
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}`
},
// 加载内容
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({
content: sampleContent,
previewContent: lines.slice(0, previewCount).join('\n'),
contentParagraphs: lines,
previewParagraphs: lines.slice(0, previewCount)
})
},
// 获取示例内容
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'
]
const currentIndex = sectionOrder.indexOf(id)
const prevId = currentIndex > 0 ? sectionOrder[currentIndex - 1] : null
const nextId = currentIndex < sectionOrder.length - 1 ? sectionOrder[currentIndex + 1] : null
this.setData({
prevSection: prevId ? { id: prevId, title: this.getSectionTitle(prevId) } : null,
nextSection: nextId ? { id: nextId, title: this.getSectionTitle(nextId) } : null
})
},
// 返回
goBack() {
wx.navigateBack({
fail: () => wx.switchTab({ url: '/pages/chapters/chapters' })
})
},
// 分享弹窗
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 })
}
})
},
// 分享到微信
onShareAppMessage() {
const { section, sectionId } = this.data
const userInfo = app.globalData.userInfo
const referralCode = userInfo?.referralCode || ''
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()
}
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
})
})
},
// 跳转到上一篇
goToPrev() {
if (this.data.prevSection) {
wx.redirectTo({ url: `/pages/read/read?id=${this.data.prevSection.id}` })
}
},
// 跳转到下一篇
goToNext() {
if (this.data.nextSection) {
wx.redirectTo({ url: `/pages/read/read?id=${this.data.nextSection.id}` })
}
},
// 跳转到推广中心
goToReferral() {
wx.navigateTo({ url: '/pages/referral/referral' })
},
// 阻止冒泡
stopPropagation() {}
})