feat: 支持章节通过 mid 进行访问,优化阅读跳转逻辑。新增章节数据结构,包含章节的 mid 信息,提升用户体验。更新 API 以支持通过 mid 查询章节内容,确保兼容性与灵活性。
This commit is contained in:
@@ -7,9 +7,9 @@ App({
|
||||
globalData: {
|
||||
// API基础地址 - 连接真实后端
|
||||
// baseUrl: 'https://soulapi.quwanzhi.com',
|
||||
baseUrl: 'https://souldev.quwanzhi.com',
|
||||
// baseUrl: 'https://souldev.quwanzhi.com',
|
||||
// baseUrl: 'http://localhost:3006',
|
||||
// baseUrl: 'http://localhost:8080',
|
||||
baseUrl: 'http://localhost:8080',
|
||||
|
||||
// 小程序配置 - 真实AppID
|
||||
appId: 'wxb8bbb2b10dec74aa',
|
||||
@@ -31,6 +31,7 @@ App({
|
||||
|
||||
// 购买记录
|
||||
purchasedSections: [],
|
||||
sectionMidMap: {}, // id -> mid,来自 purchase-status
|
||||
hasFullBook: false,
|
||||
matchCount: 0,
|
||||
matchQuota: null,
|
||||
@@ -100,6 +101,7 @@ App({
|
||||
const val = part.slice(eq + 1)
|
||||
if (key === 'ref') refCode = val
|
||||
if (key === 'id' && val) this.globalData.initialSectionId = val
|
||||
if (key === 'mid' && val) this.globalData.initialSectionMid = parseInt(val, 10) || 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,6 +180,13 @@ App({
|
||||
}
|
||||
},
|
||||
|
||||
// 根据业务 id 从 bookData 查 mid(用于跳转)
|
||||
getSectionMid(sectionId) {
|
||||
const list = this.globalData.bookData || []
|
||||
const ch = list.find(c => c.id === sectionId)
|
||||
return ch?.mid || 0
|
||||
},
|
||||
|
||||
// 获取当前用户的邀请码(用于分享带 ref,未登录返回空字符串)
|
||||
getMyReferralCode() {
|
||||
const user = this.globalData.userInfo
|
||||
|
||||
@@ -6,200 +6,66 @@
|
||||
*/
|
||||
|
||||
const app = getApp()
|
||||
const PART_NUMBERS = { 'part-1': '一', 'part-2': '二', 'part-3': '三', 'part-4': '四', 'part-5': '五' }
|
||||
|
||||
function buildNestedBookData(list) {
|
||||
const parts = {}
|
||||
const appendices = []
|
||||
let epilogueMid = 0
|
||||
let prefaceMid = 0
|
||||
list.forEach(ch => {
|
||||
if (ch.id === 'preface') {
|
||||
prefaceMid = ch.mid || 0
|
||||
return
|
||||
}
|
||||
const section = {
|
||||
id: ch.id,
|
||||
mid: ch.mid || 0,
|
||||
title: ch.sectionTitle || ch.chapterTitle || ch.id,
|
||||
isFree: !!ch.isFree,
|
||||
price: ch.price != null ? Number(ch.price) : 1
|
||||
}
|
||||
if (ch.id === 'epilogue') {
|
||||
epilogueMid = ch.mid || 0
|
||||
return
|
||||
}
|
||||
if ((ch.id || '').startsWith('appendix')) {
|
||||
appendices.push({ id: ch.id, mid: ch.mid || 0, title: ch.sectionTitle || ch.chapterTitle || ch.id })
|
||||
return
|
||||
}
|
||||
if (!ch.partId || ch.id === 'preface') return
|
||||
const pid = ch.partId
|
||||
const cid = ch.chapterId || 'chapter-' + (ch.id || '').split('.')[0]
|
||||
if (!parts[pid]) {
|
||||
parts[pid] = { id: pid, number: PART_NUMBERS[pid] || pid, title: ch.partTitle || pid, subtitle: ch.chapterTitle || '', chapters: {} }
|
||||
}
|
||||
if (!parts[pid].chapters[cid]) {
|
||||
parts[pid].chapters[cid] = { id: cid, title: ch.chapterTitle || cid, sections: [] }
|
||||
}
|
||||
parts[pid].chapters[cid].sections.push(section)
|
||||
})
|
||||
const bookData = Object.values(parts)
|
||||
.sort((a, b) => (a.id || '').localeCompare(b.id || ''))
|
||||
.map(p => ({
|
||||
...p,
|
||||
chapters: Object.values(p.chapters).sort((a, b) => (a.id || '').localeCompare(b.id || ''))
|
||||
}))
|
||||
return { bookData, appendixList: appendices, epilogueMid, prefaceMid }
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
// 系统信息
|
||||
statusBarHeight: 44,
|
||||
navBarHeight: 88,
|
||||
|
||||
// 用户状态
|
||||
isLoggedIn: false,
|
||||
hasFullBook: false,
|
||||
purchasedSections: [],
|
||||
|
||||
// 书籍数据 - 完整真实标题
|
||||
totalSections: 62,
|
||||
bookData: [
|
||||
{
|
||||
id: 'part-1',
|
||||
number: '一',
|
||||
title: '真实的人',
|
||||
subtitle: '人与人之间的底层逻辑',
|
||||
chapters: [
|
||||
{
|
||||
id: 'chapter-1',
|
||||
title: '第1章|人与人之间的底层逻辑',
|
||||
sections: [
|
||||
{ id: '1.1', title: '荷包:电动车出租的被动收入模式', isFree: true, price: 1 },
|
||||
{ id: '1.2', title: '老墨:资源整合高手的社交方法', isFree: false, price: 1 },
|
||||
{ id: '1.3', title: '笑声背后的MBTI:为什么ENTJ适合做资源,INTP适合做系统', isFree: false, price: 1 },
|
||||
{ id: '1.4', title: '人性的三角结构:利益、情感、价值观', isFree: false, price: 1 },
|
||||
{ id: '1.5', title: '沟通差的问题:为什么你说的别人听不懂', isFree: false, price: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'chapter-2',
|
||||
title: '第2章|人性困境案例',
|
||||
sections: [
|
||||
{ id: '2.1', title: '相亲故事:你以为找的是人,实际是在找模式', isFree: false, price: 1 },
|
||||
{ id: '2.2', title: '找工作迷茫者:为什么简历解决不了人生', isFree: false, price: 1 },
|
||||
{ id: '2.3', title: '撸运费险:小钱困住大脑的真实心理', isFree: false, price: 1 },
|
||||
{ id: '2.4', title: '游戏上瘾的年轻人:不是游戏吸引他,是生活没吸引力', isFree: false, price: 1 },
|
||||
{ id: '2.5', title: '健康焦虑(我的糖尿病经历):疾病是人生的第一次清醒', isFree: false, price: 1 }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'part-2',
|
||||
number: '二',
|
||||
title: '真实的行业',
|
||||
subtitle: '电商、内容、传统行业解析',
|
||||
chapters: [
|
||||
{
|
||||
id: 'chapter-3',
|
||||
title: '第3章|电商篇',
|
||||
sections: [
|
||||
{ id: '3.1', title: '3000万流水如何跑出来(退税模式解析)', isFree: false, price: 1 },
|
||||
{ id: '3.2', title: '供应链之王 vs 打工人:利润不在前端', isFree: false, price: 1 },
|
||||
{ id: '3.3', title: '社区团购的底层逻辑', isFree: false, price: 1 },
|
||||
{ id: '3.4', title: '跨境电商与退税套利', isFree: false, price: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'chapter-4',
|
||||
title: '第4章|内容商业篇',
|
||||
sections: [
|
||||
{ id: '4.1', title: '旅游号:30天10万粉的真实逻辑', isFree: false, price: 1 },
|
||||
{ id: '4.2', title: '做号工厂:如何让一个号变成一个机器', isFree: false, price: 1 },
|
||||
{ id: '4.3', title: '情绪内容为什么比专业内容更赚钱', isFree: false, price: 1 },
|
||||
{ id: '4.4', title: '猫与宠物号:为什么宠物赛道永不过时', isFree: false, price: 1 },
|
||||
{ id: '4.5', title: '直播间里的三种人:演员、技术工、系统流', isFree: false, price: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'chapter-5',
|
||||
title: '第5章|传统行业篇',
|
||||
sections: [
|
||||
{ id: '5.1', title: '拍卖行抱朴:一天240万的摇号生意', isFree: false, price: 1 },
|
||||
{ id: '5.2', title: '土地拍卖:招拍挂背后的游戏规则', isFree: false, price: 1 },
|
||||
{ id: '5.3', title: '地摊经济数字化:一个月900块的餐车生意', isFree: false, price: 1 },
|
||||
{ id: '5.4', title: '不良资产拍卖:我错过的一个亿佣金', isFree: false, price: 1 },
|
||||
{ id: '5.5', title: '桶装水李总:跟物业合作的轻资产模式', isFree: false, price: 1 }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'part-3',
|
||||
number: '三',
|
||||
title: '真实的错误',
|
||||
subtitle: '我和别人犯过的错',
|
||||
chapters: [
|
||||
{
|
||||
id: 'chapter-6',
|
||||
title: '第6章|我人生错过的4件大钱',
|
||||
sections: [
|
||||
{ id: '6.1', title: '电商财税窗口:2016年的千万级机会', isFree: false, price: 1 },
|
||||
{ id: '6.2', title: '供应链金融:我不懂的杠杆游戏', isFree: false, price: 1 },
|
||||
{ id: '6.3', title: '内容红利:2019年我为什么没做抖音', isFree: false, price: 1 },
|
||||
{ id: '6.4', title: '数据资产化:我还在观望的未来机会', isFree: false, price: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'chapter-7',
|
||||
title: '第7章|别人犯的错误',
|
||||
sections: [
|
||||
{ id: '7.1', title: '投资房年轻人的迷茫:资金 vs 能力', isFree: false, price: 1 },
|
||||
{ id: '7.2', title: '信息差骗局:永远有人靠卖学习赚钱', isFree: false, price: 1 },
|
||||
{ id: '7.3', title: '在Soul找恋爱但想赚钱的人', isFree: false, price: 1 },
|
||||
{ id: '7.4', title: '创业者的三种死法:冲动、轻信、没结构', isFree: false, price: 1 },
|
||||
{ id: '7.5', title: '人情生意的终点:关系越多亏得越多', isFree: false, price: 1 }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'part-4',
|
||||
number: '四',
|
||||
title: '真实的赚钱',
|
||||
subtitle: '底层结构与真实案例',
|
||||
chapters: [
|
||||
{
|
||||
id: 'chapter-8',
|
||||
title: '第8章|底层结构',
|
||||
sections: [
|
||||
{ id: '8.1', title: '流量杠杆:抖音、Soul、飞书', isFree: false, price: 1 },
|
||||
{ id: '8.2', title: '价格杠杆:供应链与信息差', isFree: false, price: 1 },
|
||||
{ id: '8.3', title: '时间杠杆:自动化 + AI', isFree: false, price: 1 },
|
||||
{ id: '8.4', title: '情绪杠杆:咨询、婚恋、生意场', isFree: false, price: 1 },
|
||||
{ id: '8.5', title: '社交杠杆:认识谁比你会什么更重要', isFree: false, price: 1 },
|
||||
{ id: '8.6', title: '云阿米巴:分不属于自己的钱', isFree: false, price: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'chapter-9',
|
||||
title: '第9章|我在Soul上亲访的赚钱案例',
|
||||
sections: [
|
||||
{ id: '9.1', title: '游戏账号私域:账号即资产', isFree: false, price: 1 },
|
||||
{ id: '9.2', title: '健康包模式:高复购、高毛利', isFree: false, price: 1 },
|
||||
{ id: '9.3', title: '药物私域:长期关系赛道', isFree: false, price: 1 },
|
||||
{ id: '9.4', title: '残疾机构合作:退税 × AI × 人力成本', isFree: false, price: 1 },
|
||||
{ id: '9.5', title: '私域银行:粉丝即小股东', isFree: false, price: 1 },
|
||||
{ id: '9.6', title: 'Soul派对房:陌生人成交的最快场景', isFree: false, price: 1 },
|
||||
{ id: '9.7', title: '飞书中台:从聊天到成交的流程化体系', isFree: false, price: 1 },
|
||||
{ id: '9.8', title: '餐饮女孩:6万营收、1万利润的死撑生意', isFree: false, price: 1 },
|
||||
{ id: '9.9', title: '电竞生态:从陪玩到签约到酒店的完整链条', isFree: false, price: 1 },
|
||||
{ id: '9.10', title: '淘客大佬:损耗30%的白色通道', isFree: false, price: 1 },
|
||||
{ id: '9.11', title: '蔬菜供应链:农户才是最赚钱的人', isFree: false, price: 1 },
|
||||
{ id: '9.12', title: '美业整合:一个人的公司如何月入十万', isFree: false, price: 1 },
|
||||
{ id: '9.13', title: 'AI工具推广:一个隐藏的高利润赛道', isFree: false, price: 1 },
|
||||
{ id: '9.14', title: '大健康私域:一个月150万的70后', isFree: false, price: 1 }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'part-5',
|
||||
number: '五',
|
||||
title: '真实的社会',
|
||||
subtitle: '未来职业与商业生态',
|
||||
chapters: [
|
||||
{
|
||||
id: 'chapter-10',
|
||||
title: '第10章|未来职业的变化趋势',
|
||||
sections: [
|
||||
{ id: '10.1', title: 'AI时代:哪些工作会消失,哪些会崛起', isFree: false, price: 1 },
|
||||
{ id: '10.2', title: '一人公司:为什么越来越多人选择单干', isFree: false, price: 1 },
|
||||
{ id: '10.3', title: '为什么链接能力会成为第一价值', isFree: false, price: 1 },
|
||||
{ id: '10.4', title: '新型公司:Soul-飞书-线下的三位一体', isFree: false, price: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'chapter-11',
|
||||
title: '第11章|中国社会商业生态的未来',
|
||||
sections: [
|
||||
{ id: '11.1', title: '私域经济:为什么流量越来越贵', isFree: false, price: 1 },
|
||||
{ id: '11.2', title: '银发经济与孤独经济:两个被忽视的万亿市场', isFree: false, price: 1 },
|
||||
{ id: '11.3', title: '流量红利的终局', isFree: false, price: 1 },
|
||||
{ id: '11.4', title: '大模型 + 供应链的组合拳', isFree: false, price: 1 },
|
||||
{ id: '11.5', title: '社会分层的最终逻辑', isFree: false, price: 1 }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
// 展开状态
|
||||
expandedPart: 'part-1',
|
||||
|
||||
// 附录
|
||||
appendixList: [
|
||||
{ id: 'appendix-1', title: '附录1|Soul派对房精选对话' },
|
||||
{ id: 'appendix-2', title: '附录2|创业者自检清单' },
|
||||
{ id: 'appendix-3', title: '附录3|本书提到的工具和资源' }
|
||||
]
|
||||
bookData: [],
|
||||
expandedPart: null,
|
||||
appendixList: [],
|
||||
epilogueMid: 0,
|
||||
prefaceMid: 0
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
@@ -208,10 +74,44 @@ Page({
|
||||
navBarHeight: app.globalData.navBarHeight
|
||||
})
|
||||
this.updateUserStatus()
|
||||
this.loadAndEnrichBookData()
|
||||
},
|
||||
|
||||
async loadAndEnrichBookData() {
|
||||
try {
|
||||
let list = app.globalData.bookData || []
|
||||
if (!list.length) {
|
||||
const res = await app.request('/api/miniprogram/book/all-chapters')
|
||||
if (res?.data) {
|
||||
list = res.data
|
||||
app.globalData.bookData = list
|
||||
}
|
||||
}
|
||||
if (!list.length) {
|
||||
this.setData({ bookData: [], appendixList: [] })
|
||||
return
|
||||
}
|
||||
const { bookData, appendixList, epilogueMid, prefaceMid } = buildNestedBookData(list)
|
||||
const firstPartId = bookData[0]?.id || null
|
||||
this.setData({
|
||||
bookData,
|
||||
appendixList,
|
||||
epilogueMid,
|
||||
prefaceMid,
|
||||
totalSections: list.length,
|
||||
expandedPart: firstPartId || this.data.expandedPart
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('[Chapters] 加载目录失败:', e)
|
||||
this.setData({ bookData: [], appendixList: [] })
|
||||
}
|
||||
},
|
||||
|
||||
onShow() {
|
||||
// 设置TabBar选中状态
|
||||
this.updateUserStatus()
|
||||
if (!app.globalData.bookData?.length) {
|
||||
this.loadAndEnrichBookData()
|
||||
}
|
||||
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
||||
const tabBar = this.getTabBar()
|
||||
if (tabBar.updateSelected) {
|
||||
@@ -220,7 +120,6 @@ Page({
|
||||
tabBar.setData({ selected: 1 })
|
||||
}
|
||||
}
|
||||
this.updateUserStatus()
|
||||
},
|
||||
|
||||
// 更新用户状态
|
||||
@@ -237,10 +136,11 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
// 跳转到阅读页
|
||||
goToRead(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
wx.navigateTo({ url: `/pages/read/read?id=${id}` })
|
||||
const mid = e.currentTarget.dataset.mid || app.getSectionMid(id)
|
||||
const q = mid ? `mid=${mid}` : `id=${id}`
|
||||
wx.navigateTo({ url: `/pages/read/read?${q}` })
|
||||
},
|
||||
|
||||
// 检查是否已购买
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<!-- 目录内容 -->
|
||||
<view class="chapters-content">
|
||||
<!-- 序言 -->
|
||||
<view class="chapter-item" bindtap="goToRead" data-id="preface">
|
||||
<view class="chapter-item" bindtap="goToRead" data-id="preface" data-mid="{{prefaceMid}}">
|
||||
<view class="item-left">
|
||||
<view class="item-icon icon-brand">📖</view>
|
||||
<text class="item-title">序言|为什么我每天早上6点在Soul开播?</text>
|
||||
@@ -71,7 +71,7 @@
|
||||
<view class="chapter-header">{{chapter.title}}</view>
|
||||
<view class="section-list">
|
||||
<block wx:for="{{chapter.sections}}" wx:key="id" wx:for-item="section">
|
||||
<view class="section-item" bindtap="goToRead" data-id="{{section.id}}">
|
||||
<view class="section-item" bindtap="goToRead" data-id="{{section.id}}" data-mid="{{section.mid}}">
|
||||
<view class="section-left">
|
||||
<text class="section-lock {{section.isFree || hasFullBook || purchasedSections.indexOf(section.id) > -1 ? 'lock-open' : 'lock-closed'}}">{{section.isFree || hasFullBook || purchasedSections.indexOf(section.id) > -1 ? '○' : '●'}}</text>
|
||||
<text class="section-title {{section.isFree || hasFullBook || purchasedSections.indexOf(section.id) > -1 ? '' : 'text-muted'}}">{{section.id}} {{section.title}}</text>
|
||||
@@ -92,7 +92,7 @@
|
||||
</view>
|
||||
|
||||
<!-- 尾声 -->
|
||||
<view class="chapter-item" bindtap="goToRead" data-id="epilogue">
|
||||
<view class="chapter-item" bindtap="goToRead" data-id="epilogue" data-mid="{{epilogueMid}}">
|
||||
<view class="item-left">
|
||||
<view class="item-icon icon-brand">📖</view>
|
||||
<text class="item-title">尾声|这本书的真实目的</text>
|
||||
@@ -113,6 +113,7 @@
|
||||
wx:key="id"
|
||||
bindtap="goToRead"
|
||||
data-id="{{item.id}}"
|
||||
data-mid="{{item.mid}}"
|
||||
>
|
||||
<text class="appendix-text">{{item.title}}</text>
|
||||
<text class="appendix-arrow">→</text>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
console.log('[Index] ===== 首页文件开始加载 =====')
|
||||
|
||||
const app = getApp()
|
||||
const PART_NUMBERS = { 'part-1': '一', 'part-2': '二', 'part-3': '三', 'part-4': '四', 'part-5': '五' }
|
||||
|
||||
Page({
|
||||
data: {
|
||||
@@ -19,31 +20,13 @@ Page({
|
||||
hasFullBook: false,
|
||||
readCount: 0,
|
||||
|
||||
// 书籍数据
|
||||
totalSections: 62,
|
||||
bookData: [],
|
||||
|
||||
// 推荐章节
|
||||
featuredSections: [
|
||||
{ id: '1.1', title: '荷包:电动车出租的被动收入模式', tag: '免费', tagClass: 'tag-free', part: '真实的人' },
|
||||
{ id: '3.1', title: '3000万流水如何跑出来', tag: '热门', tagClass: 'tag-pink', part: '真实的行业' },
|
||||
{ id: '8.1', title: '流量杠杆:抖音、Soul、飞书', tag: '推荐', tagClass: 'tag-purple', part: '真实的赚钱' }
|
||||
],
|
||||
|
||||
// 最新章节(动态计算)
|
||||
featuredSections: [],
|
||||
latestSection: null,
|
||||
latestLabel: '最新更新',
|
||||
|
||||
// 内容概览
|
||||
partsList: [
|
||||
{ id: 'part-1', number: '一', title: '真实的人', subtitle: '人与人之间的底层逻辑' },
|
||||
{ id: 'part-2', number: '二', title: '真实的行业', subtitle: '电商、内容、传统行业解析' },
|
||||
{ id: 'part-3', number: '三', title: '真实的错误', subtitle: '我和别人犯过的错' },
|
||||
{ id: 'part-4', number: '四', title: '真实的赚钱', subtitle: '底层结构与真实案例' },
|
||||
{ id: 'part-5', number: '五', title: '真实的社会', subtitle: '未来职业与商业生态' }
|
||||
],
|
||||
|
||||
// 加载状态
|
||||
partsList: [],
|
||||
prefaceMid: 0,
|
||||
loading: true
|
||||
},
|
||||
|
||||
@@ -98,10 +81,7 @@ Page({
|
||||
this.setData({ loading: true })
|
||||
|
||||
try {
|
||||
// 获取书籍数据
|
||||
await this.loadBookData()
|
||||
// 计算推荐章节
|
||||
this.computeLatestSection()
|
||||
} catch (e) {
|
||||
console.error('初始化失败:', e)
|
||||
} finally {
|
||||
@@ -109,62 +89,82 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
// 计算推荐章节(根据用户ID随机、优先未付款)
|
||||
computeLatestSection() {
|
||||
const { hasFullBook, purchasedSections } = app.globalData
|
||||
const userId = app.globalData.userInfo?.id || wx.getStorageSync('userId') || 'guest'
|
||||
|
||||
// 所有章节列表
|
||||
const allSections = [
|
||||
{ id: '9.14', title: '大健康私域:一个月150万的70后', part: '真实的赚钱' },
|
||||
{ id: '9.13', title: 'AI工具推广:一个隐藏的高利润赛道', part: '真实的赚钱' },
|
||||
{ id: '9.12', title: '美业整合:一个人的公司如何月入十万', part: '真实的赚钱' },
|
||||
{ id: '8.6', title: '云阿米巴:分不属于自己的钱', part: '真实的赚钱' },
|
||||
{ id: '8.1', title: '流量杠杆:抖音、Soul、飞书', part: '真实的赚钱' },
|
||||
{ id: '3.1', title: '3000万流水如何跑出来', part: '真实的行业' },
|
||||
{ id: '5.1', title: '拍卖行抱朴:一天240万的摇号生意', part: '真实的行业' },
|
||||
{ id: '4.1', title: '旅游号:30天10万粉的真实逻辑', part: '真实的行业' }
|
||||
]
|
||||
|
||||
// 用户ID生成的随机种子(同一用户每天看到的不同)
|
||||
const today = new Date().toISOString().split('T')[0]
|
||||
const seed = (userId + today).split('').reduce((a, b) => a + b.charCodeAt(0), 0)
|
||||
|
||||
// 筛选未付款章节
|
||||
let candidates = allSections
|
||||
if (!hasFullBook) {
|
||||
const purchased = purchasedSections || []
|
||||
const unpurchased = allSections.filter(s => !purchased.includes(s.id))
|
||||
if (unpurchased.length > 0) {
|
||||
candidates = unpurchased
|
||||
}
|
||||
}
|
||||
|
||||
// 根据种子选择章节
|
||||
const index = seed % candidates.length
|
||||
const selected = candidates[index]
|
||||
|
||||
// 设置标签(如果有新增章节显示"最新更新",否则显示"推荐阅读")
|
||||
const label = candidates === allSections ? '推荐阅读' : '为你推荐'
|
||||
|
||||
this.setData({
|
||||
latestSection: selected,
|
||||
latestLabel: label
|
||||
})
|
||||
},
|
||||
|
||||
// 加载书籍数据
|
||||
async loadBookData() {
|
||||
try {
|
||||
const res = await app.request('/api/miniprogram/book/all-chapters')
|
||||
if (res && res.data) {
|
||||
this.setData({
|
||||
bookData: res.data,
|
||||
totalSections: res.totalSections || 62
|
||||
const [chaptersRes, hotRes] = await Promise.all([
|
||||
app.request('/api/miniprogram/book/all-chapters'),
|
||||
app.request('/api/miniprogram/book/hot')
|
||||
])
|
||||
const list = chaptersRes?.data || []
|
||||
const hotList = hotRes?.data || []
|
||||
app.globalData.bookData = list
|
||||
|
||||
const toSection = (ch) => ({
|
||||
id: ch.id,
|
||||
mid: ch.mid || 0,
|
||||
title: ch.sectionTitle || ch.chapterTitle || ch.id,
|
||||
part: ch.partTitle || ''
|
||||
})
|
||||
|
||||
let featuredSections = []
|
||||
if (hotList.length >= 3) {
|
||||
const freeCh = list.find(c => c.isFree || c.id === '1.1' || c.id === 'preface')
|
||||
const picks = []
|
||||
if (freeCh) picks.push({ ...toSection(freeCh), tag: '免费', tagClass: 'tag-free' })
|
||||
hotList.slice(0, 3 - picks.length).forEach((ch, i) => {
|
||||
if (!picks.find(p => p.id === ch.id)) {
|
||||
picks.push({ ...toSection(ch), tag: i === 0 ? '热门' : '推荐', tagClass: i === 0 ? 'tag-pink' : 'tag-purple' })
|
||||
}
|
||||
})
|
||||
featuredSections = picks.slice(0, 3)
|
||||
}
|
||||
if (featuredSections.length < 3 && list.length > 0) {
|
||||
const fallback = list.filter(c => c.id && !['preface', 'epilogue'].includes(c.id)).slice(0, 3)
|
||||
featuredSections = fallback.map((ch, i) => ({
|
||||
...toSection(ch),
|
||||
tag: ch.isFree ? '免费' : (i === 0 ? '热门' : '推荐'),
|
||||
tagClass: ch.isFree ? 'tag-free' : (i === 0 ? 'tag-pink' : 'tag-purple')
|
||||
}))
|
||||
}
|
||||
|
||||
const partMap = {}
|
||||
list.forEach(ch => {
|
||||
if (!ch.partId || ch.id === 'preface' || ch.id === 'epilogue' || (ch.id || '').startsWith('appendix')) return
|
||||
if (!partMap[ch.partId]) {
|
||||
partMap[ch.partId] = { id: ch.partId, number: PART_NUMBERS[ch.partId] || ch.partId, title: ch.partTitle || ch.partId, subtitle: ch.chapterTitle || '' }
|
||||
}
|
||||
})
|
||||
const partsList = Object.values(partMap).sort((a, b) => (a.id || '').localeCompare(b.id || ''))
|
||||
|
||||
const paidCandidates = list.filter(c => c.id && !['preface', 'epilogue'].includes(c.id) && !(c.id || '').startsWith('appendix') && c.partId)
|
||||
const { hasFullBook, purchasedSections } = app.globalData
|
||||
let candidates = paidCandidates
|
||||
if (!hasFullBook && purchasedSections?.length) {
|
||||
const unpurchased = paidCandidates.filter(c => !purchasedSections.includes(c.id))
|
||||
if (unpurchased.length > 0) candidates = unpurchased
|
||||
}
|
||||
const userId = app.globalData.userInfo?.id || wx.getStorageSync('userId') || 'guest'
|
||||
const today = new Date().toISOString().split('T')[0]
|
||||
const seed = (userId + today).split('').reduce((a, b) => a + b.charCodeAt(0), 0)
|
||||
const selectedCh = candidates[seed % Math.max(candidates.length, 1)]
|
||||
const latestSection = selectedCh ? { ...toSection(selectedCh), mid: selectedCh.mid || 0 } : null
|
||||
const latestLabel = candidates.length === paidCandidates.length ? '推荐阅读' : '为你推荐'
|
||||
|
||||
const prefaceCh = list.find(c => c.id === 'preface')
|
||||
const prefaceMid = prefaceCh?.mid || 0
|
||||
|
||||
this.setData({
|
||||
bookData: list,
|
||||
totalSections: list.length || 62,
|
||||
featuredSections,
|
||||
partsList,
|
||||
latestSection,
|
||||
latestLabel,
|
||||
prefaceMid
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('加载书籍数据失败:', e)
|
||||
this.setData({ featuredSections: [], partsList: [], latestSection: null })
|
||||
}
|
||||
},
|
||||
|
||||
@@ -189,10 +189,11 @@ Page({
|
||||
wx.navigateTo({ url: '/pages/search/search' })
|
||||
},
|
||||
|
||||
// 跳转到阅读页
|
||||
goToRead(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
wx.navigateTo({ url: `/pages/read/read?id=${id}` })
|
||||
const mid = e.currentTarget.dataset.mid || app.getSectionMid(id)
|
||||
const q = mid ? `mid=${mid}` : `id=${id}`
|
||||
wx.navigateTo({ url: `/pages/read/read?${q}` })
|
||||
},
|
||||
|
||||
// 跳转到匹配页
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<!-- 主内容区 -->
|
||||
<view class="main-content">
|
||||
<!-- Banner卡片 - 最新章节 -->
|
||||
<view class="banner-card" bindtap="goToRead" data-id="{{latestSection.id}}">
|
||||
<view wx:if="{{latestSection}}" class="banner-card" bindtap="goToRead" data-id="{{latestSection.id}}" data-mid="{{latestSection.mid}}">
|
||||
<view class="banner-glow"></view>
|
||||
<view class="banner-tag">最新更新</view>
|
||||
<view class="banner-title">{{latestSection.title}}</view>
|
||||
@@ -95,6 +95,7 @@
|
||||
wx:key="id"
|
||||
bindtap="goToRead"
|
||||
data-id="{{item.id}}"
|
||||
data-mid="{{item.mid}}"
|
||||
>
|
||||
<view class="featured-content">
|
||||
<view class="featured-meta">
|
||||
@@ -132,7 +133,7 @@
|
||||
</view>
|
||||
|
||||
<!-- 序言入口 -->
|
||||
<view class="preface-card" bindtap="goToRead" data-id="preface">
|
||||
<view class="preface-card" bindtap="goToRead" data-id="preface" data-mid="{{prefaceMid}}">
|
||||
<view class="preface-content">
|
||||
<text class="preface-title">序言</text>
|
||||
<text class="preface-desc">为什么我每天早上6点在Soul开播?</text>
|
||||
|
||||
@@ -119,6 +119,7 @@ Page({
|
||||
if (res.success && res.data) {
|
||||
app.globalData.hasFullBook = res.data.hasFullBook || false
|
||||
app.globalData.purchasedSections = res.data.purchasedSections || []
|
||||
app.globalData.sectionMidMap = res.data.sectionMidMap || {}
|
||||
app.globalData.matchCount = res.data.matchCount ?? 0
|
||||
app.globalData.matchQuota = res.data.matchQuota || null
|
||||
const userInfo = app.globalData.userInfo || {}
|
||||
@@ -138,7 +139,8 @@ Page({
|
||||
if (isLoggedIn && userInfo) {
|
||||
const readIds = app.globalData.readSectionIds || []
|
||||
const recentList = readIds.slice(-5).reverse().map(id => ({
|
||||
id: id,
|
||||
id,
|
||||
mid: app.getSectionMid(id),
|
||||
title: `章节 ${id}`
|
||||
}))
|
||||
|
||||
@@ -625,10 +627,11 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
// 跳转到阅读页
|
||||
goToRead(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
wx.navigateTo({ url: `/pages/read/read?id=${id}` })
|
||||
const mid = e.currentTarget.dataset.mid
|
||||
const q = mid ? `mid=${mid}` : `id=${id}`
|
||||
wx.navigateTo({ url: `/pages/read/read?${q}` })
|
||||
},
|
||||
|
||||
// 跳转到目录
|
||||
|
||||
@@ -193,6 +193,7 @@
|
||||
wx:key="id"
|
||||
bindtap="goToRead"
|
||||
data-id="{{item.id}}"
|
||||
data-mid="{{item.mid}}"
|
||||
>
|
||||
<view class="recent-left">
|
||||
<text class="recent-index">{{index + 1}}</text>
|
||||
|
||||
@@ -22,11 +22,24 @@ Page({
|
||||
async loadOrders() {
|
||||
this.setData({ loading: true })
|
||||
try {
|
||||
// 模拟订单数据
|
||||
const purchasedSections = app.globalData.purchasedSections || []
|
||||
let purchasedSections = app.globalData.purchasedSections || []
|
||||
let sectionMidMap = app.globalData.sectionMidMap || {}
|
||||
const userId = app.globalData.userInfo?.id
|
||||
if (userId) {
|
||||
try {
|
||||
const res = await app.request(`/api/miniprogram/user/purchase-status?userId=${encodeURIComponent(userId)}`)
|
||||
if (res?.success && res.data) {
|
||||
purchasedSections = res.data.purchasedSections || []
|
||||
sectionMidMap = res.data.sectionMidMap || {}
|
||||
app.globalData.purchasedSections = purchasedSections
|
||||
app.globalData.sectionMidMap = sectionMidMap
|
||||
}
|
||||
} catch (_) { /* 使用缓存 */ }
|
||||
}
|
||||
const orders = purchasedSections.map((id, index) => ({
|
||||
id: `order_${index}`,
|
||||
sectionId: id,
|
||||
mid: sectionMidMap[id] || 0,
|
||||
title: `章节 ${id}`,
|
||||
amount: 1,
|
||||
status: 'completed',
|
||||
@@ -42,7 +55,9 @@ Page({
|
||||
|
||||
goToRead(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
wx.navigateTo({ url: `/pages/read/read?id=${id}` })
|
||||
const mid = e.currentTarget.dataset.mid
|
||||
const q = mid ? `mid=${mid}` : `id=${id}`
|
||||
wx.navigateTo({ url: `/pages/read/read?${q}` })
|
||||
},
|
||||
|
||||
goBack() { wx.navigateBack() },
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
</view>
|
||||
|
||||
<view class="orders-list" wx:elif="{{orders.length > 0}}">
|
||||
<view class="order-item" wx:for="{{orders}}" wx:key="id" bindtap="goToRead" data-id="{{item.sectionId}}">
|
||||
<view class="order-item" wx:for="{{orders}}" wx:key="id" bindtap="goToRead" data-id="{{item.sectionId}}"
|
||||
data-mid="{{item.mid}}">
|
||||
<view class="order-info">
|
||||
<text class="order-title">{{item.title}}</text>
|
||||
<text class="order-time">{{item.createTime}}</text>
|
||||
|
||||
@@ -73,25 +73,29 @@ Page({
|
||||
},
|
||||
|
||||
async onLoad(options) {
|
||||
// 扫码进入时:options.id 无;id 可能在 app.globalData.initialSectionId(App 解析 query.scene)
|
||||
// 或直接在 options.scene 中(页面 query 为 ?scene=id%3D1.1,后端把 & 转成 _)
|
||||
// 支持 mid(优先)或 id:mid 用于新链接,id 兼容旧链接
|
||||
// 扫码进入时:mid/id 可能在 options、app.globalData.initialSectionMid/initialSectionId、或 scene 中
|
||||
let mid = options.mid ? parseInt(options.mid, 10) : (app.globalData.initialSectionMid || 0)
|
||||
let id = options.id || app.globalData.initialSectionId
|
||||
if (!id && options.scene) {
|
||||
if ((!mid || !id) && options.scene) {
|
||||
const scene = (typeof options.scene === 'string' ? decodeURIComponent(options.scene) : '').trim()
|
||||
const parts = scene.split(/[&_]/)
|
||||
for (const part of parts) {
|
||||
const eq = part.indexOf('=')
|
||||
if (eq > 0 && part.slice(0, eq) === 'id') {
|
||||
id = part.slice(eq + 1)
|
||||
break
|
||||
if (eq > 0) {
|
||||
const k = part.slice(0, eq)
|
||||
const v = part.slice(eq + 1)
|
||||
if (k === 'mid') mid = parseInt(v, 10) || 0
|
||||
if (k === 'id' && v) id = v
|
||||
}
|
||||
}
|
||||
}
|
||||
if (app.globalData.initialSectionMid) delete app.globalData.initialSectionMid
|
||||
if (app.globalData.initialSectionId) delete app.globalData.initialSectionId
|
||||
const ref = options.ref
|
||||
|
||||
if (!id) {
|
||||
console.warn('[Read] 未获取到章节 id,options:', options)
|
||||
if (!mid && !id) {
|
||||
console.warn('[Read] 未获取到章节 mid/id,options:', options)
|
||||
wx.showToast({ title: '章节参数缺失', icon: 'none' })
|
||||
this.setData({ accessState: 'error', loading: false })
|
||||
return
|
||||
@@ -100,12 +104,12 @@ Page({
|
||||
this.setData({
|
||||
statusBarHeight: app.globalData.statusBarHeight,
|
||||
navBarHeight: app.globalData.navBarHeight,
|
||||
sectionId: id,
|
||||
sectionId: '', // 加载后填充
|
||||
sectionMid: mid || null,
|
||||
loading: true,
|
||||
accessState: 'unknown'
|
||||
})
|
||||
|
||||
// 处理推荐码绑定(异步不阻塞)
|
||||
if (ref) {
|
||||
console.log('[Read] 检测到推荐码:', ref)
|
||||
wx.setStorageSync('referral_code', ref)
|
||||
@@ -113,7 +117,6 @@ Page({
|
||||
}
|
||||
|
||||
try {
|
||||
// 【标准流程】1. 拉取最新配置(免费列表、价格)
|
||||
const config = await accessManager.fetchLatestConfig()
|
||||
this.setData({
|
||||
freeIds: config.freeChapters,
|
||||
@@ -121,8 +124,19 @@ Page({
|
||||
fullBookPrice: config.prices?.fullbook ?? 9.9
|
||||
})
|
||||
|
||||
// 【标准流程】2. 确定权限状态
|
||||
const accessState = await accessManager.determineAccessState(id, config.freeChapters)
|
||||
// 先拉取章节获取 id(mid 时必需;id 时可直接用)
|
||||
let resolvedId = id
|
||||
let prefetchedChapter = null
|
||||
if (mid && !id) {
|
||||
const chRes = await app.request(`/api/miniprogram/book/chapter/by-mid/${mid}`)
|
||||
if (chRes && chRes.id) {
|
||||
resolvedId = chRes.id
|
||||
prefetchedChapter = chRes
|
||||
}
|
||||
}
|
||||
this.setData({ sectionId: resolvedId })
|
||||
|
||||
const accessState = await accessManager.determineAccessState(resolvedId, config.freeChapters)
|
||||
const canAccess = accessManager.canAccessFullContent(accessState)
|
||||
|
||||
this.setData({
|
||||
@@ -132,16 +146,13 @@ Page({
|
||||
showPaywall: !canAccess
|
||||
})
|
||||
|
||||
// 【标准流程】3. 加载内容
|
||||
await this.loadContent(id, accessState)
|
||||
await this.loadContent(mid, resolvedId, accessState, prefetchedChapter)
|
||||
|
||||
// 【标准流程】4. 如果有权限,初始化阅读追踪
|
||||
if (canAccess) {
|
||||
readingTracker.init(id)
|
||||
readingTracker.init(resolvedId)
|
||||
}
|
||||
|
||||
// 5. 加载导航
|
||||
this.loadNavigation(id)
|
||||
this.loadNavigation(resolvedId)
|
||||
|
||||
} catch (e) {
|
||||
console.error('[Read] 初始化失败:', e)
|
||||
@@ -184,8 +195,8 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
// 【重构】加载章节内容(专注于内容加载,权限判断已在 onLoad 中由 accessManager 完成)
|
||||
async loadContent(id, accessState) {
|
||||
// 【重构】加载章节内容。mid 优先用 by-mid 接口,id 用旧接口;prefetched 避免重复请求
|
||||
async loadContent(mid, id, accessState, prefetched) {
|
||||
try {
|
||||
const section = this.getSectionInfo(id)
|
||||
const sectionPrice = this.data.sectionPrice ?? 1
|
||||
@@ -194,25 +205,29 @@ Page({
|
||||
}
|
||||
this.setData({ section })
|
||||
|
||||
// 从 API 获取内容
|
||||
const res = await app.request(`/api/miniprogram/book/chapter/${id}`)
|
||||
let res = prefetched
|
||||
if (!res) {
|
||||
res = mid
|
||||
? await app.request(`/api/miniprogram/book/chapter/by-mid/${mid}`)
|
||||
: await app.request(`/api/miniprogram/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({
|
||||
const updates = {
|
||||
content: res.content,
|
||||
contentParagraphs: lines,
|
||||
previewParagraphs: lines.slice(0, previewCount),
|
||||
partTitle: res.partTitle || '',
|
||||
chapterTitle: res.chapterTitle || ''
|
||||
})
|
||||
|
||||
// 如果有权限,标记为已读
|
||||
}
|
||||
if (res.mid) updates.sectionMid = res.mid
|
||||
this.setData(updates)
|
||||
if (accessManager.canAccessFullContent(accessState)) {
|
||||
app.markSectionAsRead(id)
|
||||
}
|
||||
setTimeout(() => this.drawShareCard(), 600)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[Read] 加载内容失败:', e)
|
||||
@@ -284,50 +299,6 @@ Page({
|
||||
}
|
||||
return titles[id] || `章节 ${id}`
|
||||
},
|
||||
|
||||
// 加载内容 - 三级降级方案:API → 本地缓存 → 备用API
|
||||
async loadContent(id) {
|
||||
const cacheKey = `chapter_${id}`
|
||||
|
||||
// 1. 优先从API获取
|
||||
try {
|
||||
const res = await this.fetchChapterWithTimeout(id, 5000)
|
||||
if (res && res.content) {
|
||||
this.setData({ section: this.getSectionInfo(id) })
|
||||
this.setChapterContent(res)
|
||||
wx.setStorageSync(cacheKey, res)
|
||||
console.log('[Read] 从API加载成功:', id)
|
||||
setTimeout(() => this.drawShareCard(), 600)
|
||||
return
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[Read] API加载失败,尝试本地缓存:', e.message)
|
||||
}
|
||||
|
||||
// 2. API失败,尝试从本地缓存读取
|
||||
try {
|
||||
const cached = wx.getStorageSync(cacheKey)
|
||||
if (cached && cached.content) {
|
||||
this.setData({ section: this.getSectionInfo(id) })
|
||||
this.setChapterContent(cached)
|
||||
console.log('[Read] 从本地缓存加载成功:', id)
|
||||
this.silentRefresh(id)
|
||||
setTimeout(() => this.drawShareCard(), 600)
|
||||
return
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[Read] 本地缓存读取失败')
|
||||
}
|
||||
|
||||
// 3. 都失败,显示加载中并持续重试
|
||||
this.setData({
|
||||
contentParagraphs: ['章节内容加载中...', '正在尝试连接服务器,请稍候...'],
|
||||
previewParagraphs: ['章节内容加载中...']
|
||||
})
|
||||
|
||||
// 延迟重试(最多3次)
|
||||
this.retryLoadContent(id, 3)
|
||||
},
|
||||
|
||||
// 带超时的章节请求
|
||||
fetchChapterWithTimeout(id, timeout = 5000) {
|
||||
@@ -405,7 +376,7 @@ Page({
|
||||
},
|
||||
|
||||
|
||||
// 加载导航
|
||||
// 加载导航(prevSection/nextSection 含 mid 时用于跳转,否则用 id)
|
||||
loadNavigation(id) {
|
||||
const sectionOrder = [
|
||||
'preface', '1.1', '1.2', '1.3', '1.4', '1.5',
|
||||
@@ -421,14 +392,19 @@ Page({
|
||||
'11.1', '11.2', '11.3', '11.4', '11.5',
|
||||
'epilogue'
|
||||
]
|
||||
|
||||
const bookData = app.globalData.bookData || []
|
||||
const idToMid = {}
|
||||
bookData.forEach(ch => {
|
||||
if (ch.id && ch.mid) idToMid[ch.id] = ch.mid
|
||||
})
|
||||
|
||||
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
|
||||
prevSection: prevId ? { id: prevId, mid: idToMid[prevId], title: this.getSectionTitle(prevId) } : null,
|
||||
nextSection: nextId ? { id: nextId, mid: idToMid[nextId], title: this.getSectionTitle(nextId) } : null
|
||||
})
|
||||
},
|
||||
|
||||
@@ -543,14 +519,13 @@ Page({
|
||||
|
||||
// 统一分享配置(底部「推荐给好友」与右下角分享按钮均走此配置,由 onShareAppMessage 使用)
|
||||
getShareConfig() {
|
||||
const { section, sectionId, shareImagePath } = this.data
|
||||
const { section, sectionId, sectionMid, shareImagePath } = this.data
|
||||
const ref = app.getMyReferralCode()
|
||||
const shareTitle = section?.title
|
||||
? `📚 ${section.title.length > 20 ? section.title.slice(0, 20) + '...' : section.title}`
|
||||
: '📚 Soul创业派对 - 真实商业故事'
|
||||
const path = ref
|
||||
? `/pages/read/read?id=${sectionId}&ref=${ref}`
|
||||
: `/pages/read/read?id=${sectionId}`
|
||||
const q = sectionMid ? `mid=${sectionMid}` : `id=${sectionId}`
|
||||
const path = ref ? `/pages/read/read?${q}&ref=${ref}` : `/pages/read/read?${q}`
|
||||
return {
|
||||
title: shareTitle,
|
||||
path,
|
||||
@@ -563,11 +538,12 @@ Page({
|
||||
},
|
||||
|
||||
onShareTimeline() {
|
||||
const { section, sectionId } = this.data
|
||||
const { section, sectionId, sectionMid } = this.data
|
||||
const ref = app.getMyReferralCode()
|
||||
const q = sectionMid ? `mid=${sectionMid}` : `id=${sectionId}`
|
||||
return {
|
||||
title: `${section?.title || 'Soul创业派对'} - 来自派对房的真实故事`,
|
||||
query: ref ? `id=${sectionId}&ref=${ref}` : `id=${sectionId}`
|
||||
query: ref ? `${q}&ref=${ref}` : q
|
||||
}
|
||||
},
|
||||
|
||||
@@ -666,7 +642,7 @@ Page({
|
||||
|
||||
// 4. 如果已解锁,重新加载内容并初始化阅读追踪
|
||||
if (canAccess) {
|
||||
await this.loadContent(this.data.sectionId, newAccessState)
|
||||
await this.loadContent(this.data.sectionMid, this.data.sectionId, newAccessState, null)
|
||||
readingTracker.init(this.data.sectionId)
|
||||
}
|
||||
|
||||
@@ -737,6 +713,7 @@ Page({
|
||||
// 更新本地购买状态
|
||||
app.globalData.hasFullBook = checkRes.data.hasFullBook
|
||||
app.globalData.purchasedSections = checkRes.data.purchasedSections || []
|
||||
app.globalData.sectionMidMap = checkRes.data.sectionMidMap || {}
|
||||
|
||||
// 检查是否已购买
|
||||
if (type === 'section' && sectionId) {
|
||||
@@ -953,7 +930,7 @@ Page({
|
||||
})
|
||||
|
||||
// 4. 重新加载全文
|
||||
await this.loadContent(this.data.sectionId, newAccessState)
|
||||
await this.loadContent(this.data.sectionMid, this.data.sectionId, newAccessState, null)
|
||||
|
||||
// 5. 初始化阅读追踪
|
||||
if (canAccess) {
|
||||
@@ -990,6 +967,7 @@ Page({
|
||||
// 更新全局购买状态
|
||||
app.globalData.hasFullBook = res.data.hasFullBook
|
||||
app.globalData.purchasedSections = res.data.purchasedSections || []
|
||||
app.globalData.sectionMidMap = res.data.sectionMidMap || {}
|
||||
app.globalData.matchCount = res.data.matchCount ?? 0
|
||||
app.globalData.matchQuota = res.data.matchQuota || null
|
||||
|
||||
@@ -1027,17 +1005,19 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
// 跳转到上一篇
|
||||
goToPrev() {
|
||||
if (this.data.prevSection) {
|
||||
wx.redirectTo({ url: `/pages/read/read?id=${this.data.prevSection.id}` })
|
||||
const s = this.data.prevSection
|
||||
if (s) {
|
||||
const q = s.mid ? `mid=${s.mid}` : `id=${s.id}`
|
||||
wx.redirectTo({ url: `/pages/read/read?${q}` })
|
||||
}
|
||||
},
|
||||
|
||||
// 跳转到下一篇
|
||||
goToNext() {
|
||||
if (this.data.nextSection) {
|
||||
wx.redirectTo({ url: `/pages/read/read?id=${this.data.nextSection.id}` })
|
||||
const s = this.data.nextSection
|
||||
if (s) {
|
||||
const q = s.mid ? `mid=${s.mid}` : `id=${s.id}`
|
||||
wx.redirectTo({ url: `/pages/read/read?${q}` })
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1051,7 +1031,7 @@ Page({
|
||||
wx.showLoading({ title: '生成中...' })
|
||||
this.setData({ showPosterModal: true, isGeneratingPoster: true })
|
||||
|
||||
const { section, contentParagraphs, sectionId } = this.data
|
||||
const { section, contentParagraphs, sectionId, sectionMid } = this.data
|
||||
const userInfo = app.globalData.userInfo
|
||||
const userId = userInfo?.id || ''
|
||||
const safeParagraphs = contentParagraphs || []
|
||||
@@ -1059,7 +1039,9 @@ Page({
|
||||
// 通过 GET 接口下载二维码图片,得到 tempFilePath 便于开发工具与真机统一用 drawImage 绘制
|
||||
let qrcodeTempPath = null
|
||||
try {
|
||||
const scene = userId ? `id=${sectionId}&ref=${userId.slice(0, 10)}` : `id=${sectionId}`
|
||||
const scene = sectionMid
|
||||
? (userId ? `mid=${sectionMid}&ref=${userId.slice(0, 10)}` : `mid=${sectionMid}`)
|
||||
: (userId ? `id=${sectionId}&ref=${userId.slice(0, 10)}` : `id=${sectionId}`)
|
||||
const baseUrl = app.globalData.baseUrl || ''
|
||||
const url = `${baseUrl}/api/miniprogram/qrcode/image?scene=${encodeURIComponent(scene)}&page=${encodeURIComponent('pages/read/read')}&width=280`
|
||||
qrcodeTempPath = await new Promise((resolve) => {
|
||||
@@ -1272,7 +1254,7 @@ Page({
|
||||
})
|
||||
|
||||
// 重新加载内容
|
||||
await this.loadContent(this.data.sectionId, newAccessState)
|
||||
await this.loadContent(this.data.sectionMid, this.data.sectionId, newAccessState, null)
|
||||
|
||||
// 如果有权限,初始化阅读追踪
|
||||
if (canAccess) {
|
||||
|
||||
@@ -96,10 +96,11 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
// 跳转阅读
|
||||
goToRead(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
wx.navigateTo({ url: `/pages/read/read?id=${id}` })
|
||||
const mid = e.currentTarget.dataset.mid
|
||||
const q = mid ? `mid=${mid}` : `id=${id}`
|
||||
wx.navigateTo({ url: `/pages/read/read?${q}` })
|
||||
},
|
||||
|
||||
// 返回上一页
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
wx:key="id"
|
||||
bindtap="goToRead"
|
||||
data-id="{{item.id}}"
|
||||
data-mid="{{item.mid}}"
|
||||
>
|
||||
<view class="chapter-rank">{{index + 1}}</view>
|
||||
<view class="chapter-info">
|
||||
@@ -83,6 +84,7 @@
|
||||
wx:key="id"
|
||||
bindtap="goToRead"
|
||||
data-id="{{item.id}}"
|
||||
data-mid="{{item.mid}}"
|
||||
>
|
||||
<view class="result-header">
|
||||
<text class="result-chapter">{{item.chapterLabel}}</text>
|
||||
|
||||
@@ -23,12 +23,19 @@
|
||||
"condition": {
|
||||
"miniprogram": {
|
||||
"list": [
|
||||
{
|
||||
"name": "pages/chapters/chapters",
|
||||
"pathName": "pages/chapters/chapters",
|
||||
"query": "",
|
||||
"scene": null,
|
||||
"launchMode": "default"
|
||||
},
|
||||
{
|
||||
"name": "ces",
|
||||
"pathName": "pages/read/read",
|
||||
"query": "scene=id%3D1.1",
|
||||
"scene": null,
|
||||
"launchMode": "default"
|
||||
"launchMode": "default",
|
||||
"scene": null
|
||||
},
|
||||
{
|
||||
"name": "pages/read/read",
|
||||
|
||||
@@ -150,6 +150,7 @@ class ChapterAccessManager {
|
||||
if (res.success && res.data) {
|
||||
app.globalData.hasFullBook = res.data.hasFullBook || false
|
||||
app.globalData.purchasedSections = res.data.purchasedSections || []
|
||||
app.globalData.sectionMidMap = res.data.sectionMidMap || {}
|
||||
app.globalData.matchCount = res.data.matchCount ?? 0
|
||||
app.globalData.matchQuota = res.data.matchQuota || null
|
||||
|
||||
|
||||
Reference in New Issue
Block a user