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:
@@ -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() {}
|
||||
})
|
||||
|
||||
7
miniprogram/pages/read/read.json
Normal file
7
miniprogram/pages/read/read.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"usingComponents": {},
|
||||
"enablePullDownRefresh": false,
|
||||
"backgroundTextStyle": "light",
|
||||
"backgroundColor": "#000000",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user