chore: 停止上传开发文档并同步代码
- 从仓库索引移除 开发文档/(本地保留) - 忽略 wechat/info.log 与 soul-api-linux - 同步小程序/管理端/API改动 Made-with: Cursor
This commit is contained in:
@@ -20,34 +20,21 @@ Page({
|
||||
isVip: false,
|
||||
purchasedSections: [],
|
||||
|
||||
// 懒加载:篇章列表(不含章节详情),展开时再请求 chapters-by-part
|
||||
// 书籍数据:以后台内容管理为准,仅用接口 /api/miniprogram/book/all-chapters 返回的数据
|
||||
totalSections: 0,
|
||||
bookData: [],
|
||||
|
||||
// 展开状态
|
||||
// 展开状态:默认不展开任何篇章,直接显示目录
|
||||
expandedPart: null,
|
||||
|
||||
// 已加载的篇章章节缓存 { partId: chapters }
|
||||
_loadedChapters: {},
|
||||
|
||||
// 固定模块 id -> mid(序言/尾声/附录,供 goToRead 传 mid)
|
||||
fixedSectionsMap: {},
|
||||
|
||||
// 附录
|
||||
appendixList: [
|
||||
{ id: 'appendix-1', title: '附录1|Soul派对房精选对话' },
|
||||
{ id: 'appendix-2', title: '附录2|创业者自检清单' },
|
||||
{ id: 'appendix-3', title: '附录3|本书提到的工具和资源' }
|
||||
],
|
||||
appendixList: [],
|
||||
|
||||
// 每日新增章节(懒加载后暂无,可后续用 latest-chapters 补充)
|
||||
// 每日新增章节
|
||||
dailyChapters: [],
|
||||
|
||||
// book/parts 加载中
|
||||
partsLoading: true,
|
||||
|
||||
// 功能配置(搜索开关)
|
||||
searchEnabled: true
|
||||
// 审核模式
|
||||
auditMode: false
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
@@ -58,115 +45,74 @@ Page({
|
||||
})
|
||||
this.updateUserStatus()
|
||||
this.loadVipStatus()
|
||||
this.loadParts()
|
||||
this.loadDailyChapters()
|
||||
this.loadFeatureConfig()
|
||||
this.loadChaptersOnce()
|
||||
},
|
||||
|
||||
async loadFeatureConfig() {
|
||||
// 固定模块(序言、尾声、附录)不参与中间篇章
|
||||
_isFixedPart(pt) {
|
||||
if (!pt) return false
|
||||
const p = String(pt).toLowerCase().replace(/[_\s||]/g, '')
|
||||
return p.includes('序言') || p.includes('尾声') || p.includes('附录')
|
||||
},
|
||||
|
||||
// 一次请求拉取全量目录,以后台内容管理为准;同时更新 totalSections / bookData / dailyChapters
|
||||
async loadChaptersOnce() {
|
||||
try {
|
||||
if (app.globalData.features && typeof app.globalData.features.searchEnabled === 'boolean') {
|
||||
this.setData({ searchEnabled: app.globalData.features.searchEnabled })
|
||||
const res = await app.request({ url: '/api/miniprogram/book/all-chapters', silent: true })
|
||||
const rows = (res && res.data) || (res && res.chapters) || []
|
||||
|
||||
// 无数据时清空目录,避免展示旧数据
|
||||
if (rows.length === 0) {
|
||||
app.globalData.bookData = []
|
||||
wx.setStorageSync('bookData', [])
|
||||
this.setData({
|
||||
bookData: [],
|
||||
totalSections: 0,
|
||||
dailyChapters: [],
|
||||
expandedPart: null
|
||||
})
|
||||
return
|
||||
}
|
||||
const res = await app.request({ url: '/api/miniprogram/config', silent: true })
|
||||
const features = (res && res.features) || {}
|
||||
const searchEnabled = features.searchEnabled !== false
|
||||
if (!app.globalData.features) app.globalData.features = {}
|
||||
app.globalData.features.searchEnabled = searchEnabled
|
||||
this.setData({ searchEnabled })
|
||||
} catch (e) {
|
||||
this.setData({ searchEnabled: true })
|
||||
}
|
||||
},
|
||||
|
||||
// 懒加载:仅拉取篇章列表 + totalSections + fixedSections
|
||||
// 优先 book/parts,404 或失败时降级为 all-chapters 推导
|
||||
async loadParts() {
|
||||
this.setData({ partsLoading: true })
|
||||
try {
|
||||
let res
|
||||
try {
|
||||
res = await app.request({ url: '/api/miniprogram/book/parts', silent: true })
|
||||
} catch (e) {
|
||||
console.log('[Chapters] book/parts 失败,降级 all-chapters:', e?.message || e)
|
||||
res = null
|
||||
}
|
||||
let parts = []
|
||||
let totalSections = 0
|
||||
let fixedSections = []
|
||||
if (res?.success && Array.isArray(res.parts) && res.parts.length > 0) {
|
||||
parts = res.parts
|
||||
totalSections = res.totalSections ?? 0
|
||||
fixedSections = res.fixedSections || []
|
||||
} else {
|
||||
// 降级:从 all-chapters 推导 parts
|
||||
const allRes = await app.request({ url: '/api/miniprogram/book/all-chapters', silent: true })
|
||||
const list = (allRes?.data || allRes?.chapters || [])
|
||||
totalSections = list.length
|
||||
const pt = (c) => (c.partTitle || c.part_title || '').toLowerCase()
|
||||
const exclude = (c) => !pt(c).includes('序言') && !pt(c).includes('尾声') && !pt(c).includes('附录')
|
||||
const partMap = new Map()
|
||||
list.filter(exclude).forEach(c => {
|
||||
const pid = c.partId || c.part_id || 'default'
|
||||
const ptitle = c.partTitle || c.part_title || '未分类'
|
||||
if (!partMap.has(pid)) partMap.set(pid, { id: pid, title: ptitle, subtitle: '', chapterCount: 0 })
|
||||
partMap.get(pid).chapterCount++
|
||||
})
|
||||
parts = Array.from(partMap.values())
|
||||
}
|
||||
const numbers = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二']
|
||||
const fixedMap = {}
|
||||
fixedSections.forEach(f => { fixedMap[f.id] = f.mid })
|
||||
const appendixList = [
|
||||
{ id: 'appendix-1', title: '附录1|Soul派对房精选对话', mid: fixedMap['appendix-1'] },
|
||||
{ id: 'appendix-2', title: '附录2|创业者自检清单', mid: fixedMap['appendix-2'] },
|
||||
{ id: 'appendix-3', title: '附录3|本书提到的工具和资源', mid: fixedMap['appendix-3'] }
|
||||
]
|
||||
const bookData = parts.map((p, idx) => ({
|
||||
id: p.id,
|
||||
number: numbers[idx] || String(idx + 1),
|
||||
title: p.title,
|
||||
subtitle: p.subtitle || '',
|
||||
chapterCount: p.chapterCount || 0,
|
||||
chapters: [] // 展开时懒加载
|
||||
}))
|
||||
const totalSections = res.total ?? rows.length
|
||||
app.globalData.bookData = rows
|
||||
app.globalData.totalSections = totalSections
|
||||
this.setData({
|
||||
bookData,
|
||||
totalSections,
|
||||
fixedSectionsMap: fixedMap,
|
||||
appendixList,
|
||||
_loadedChapters: {},
|
||||
partsLoading: false
|
||||
})
|
||||
} catch (e) {
|
||||
console.log('[Chapters] 加载篇章失败:', e)
|
||||
this.setData({ bookData: [], totalSections: 0, partsLoading: false })
|
||||
}
|
||||
},
|
||||
wx.setStorageSync('bookData', rows)
|
||||
|
||||
// 展开时懒加载该篇章的章节(含 mid,供阅读页 by-mid 请求)
|
||||
async loadChaptersByPart(partId) {
|
||||
if (this.data._loadedChapters[partId]) return
|
||||
try {
|
||||
const res = await app.request({
|
||||
url: `/api/miniprogram/book/chapters-by-part?partId=${encodeURIComponent(partId)}`,
|
||||
silent: true
|
||||
})
|
||||
const rows = (res && res.data) || []
|
||||
const chMap = new Map()
|
||||
rows.forEach(r => {
|
||||
// bookData:过滤序言/尾声/附录,按 part 聚合,篇章顺序按 sort_order 与后台一致(含「2026每日派对干货」等)
|
||||
const filtered = rows.filter(r => !this._isFixedPart(r.partTitle || r.part_title))
|
||||
const partMap = new Map()
|
||||
const numbers = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二']
|
||||
filtered.forEach((r) => {
|
||||
const pid = r.partId || r.part_id || 'part-1'
|
||||
const cid = r.chapterId || r.chapter_id || 'chapter-1'
|
||||
if (!chMap.has(cid)) {
|
||||
chMap.set(cid, {
|
||||
const sortOrder = r.sectionOrder ?? r.sort_order ?? 999999
|
||||
if (!partMap.has(pid)) {
|
||||
const partIdx = partMap.size
|
||||
partMap.set(pid, {
|
||||
id: pid,
|
||||
number: numbers[partIdx] || String(partIdx + 1),
|
||||
title: r.partTitle || r.part_title || '未分类',
|
||||
subtitle: r.chapterTitle || r.chapter_title || '',
|
||||
chapters: new Map(),
|
||||
minSortOrder: sortOrder
|
||||
})
|
||||
}
|
||||
const part = partMap.get(pid)
|
||||
if (sortOrder < part.minSortOrder) part.minSortOrder = sortOrder
|
||||
if (!part.chapters.has(cid)) {
|
||||
part.chapters.set(cid, {
|
||||
id: cid,
|
||||
title: r.chapterTitle || r.chapter_title || '未分类',
|
||||
sections: []
|
||||
})
|
||||
}
|
||||
const ch = chMap.get(cid)
|
||||
const isPremium = r.editionPremium === true || r.edition_premium === true || r.edition_premium === 1 || r.edition_premium === '1'
|
||||
const ch = part.chapters.get(cid)
|
||||
const isPremium =
|
||||
r.editionPremium === true ||
|
||||
r.edition_premium === true ||
|
||||
r.edition_premium === 1 ||
|
||||
r.edition_premium === '1'
|
||||
ch.sections.push({
|
||||
id: r.id,
|
||||
mid: r.mid ?? r.MID ?? 0,
|
||||
@@ -177,54 +123,57 @@ Page({
|
||||
isPremium
|
||||
})
|
||||
})
|
||||
const chapters = Array.from(chMap.values())
|
||||
const loaded = { ...this.data._loadedChapters, [partId]: chapters }
|
||||
const bookData = this.data.bookData.map(p =>
|
||||
p.id === partId ? { ...p, chapters } : p
|
||||
)
|
||||
const bookDataFlat = app.globalData.bookData || []
|
||||
rows.forEach(r => {
|
||||
const idx = bookDataFlat.findIndex(c => c.id === r.id)
|
||||
if (idx >= 0) bookDataFlat[idx] = { ...bookDataFlat[idx], ...r }
|
||||
else bookDataFlat.push(r)
|
||||
})
|
||||
app.globalData.bookData = bookDataFlat
|
||||
wx.setStorageSync('bookData', bookDataFlat)
|
||||
this.setData({ bookData, _loadedChapters: loaded })
|
||||
} catch (e) {
|
||||
console.log('[Chapters] 加载章节失败:', e)
|
||||
}
|
||||
},
|
||||
const partList = Array.from(partMap.values())
|
||||
partList.sort((a, b) => (a.minSortOrder ?? 999999) - (b.minSortOrder ?? 999999))
|
||||
const bookData = partList.map((p, idx) => ({
|
||||
id: p.id,
|
||||
number: numbers[idx] || String(idx + 1),
|
||||
title: p.title,
|
||||
subtitle: p.subtitle,
|
||||
chapters: Array.from(p.chapters.values())
|
||||
}))
|
||||
|
||||
onPullDownRefresh() {
|
||||
Promise.all([this.loadParts(), this.loadDailyChapters()])
|
||||
.then(() => wx.stopPullDownRefresh())
|
||||
.catch(() => wx.stopPullDownRefresh())
|
||||
},
|
||||
|
||||
// 每日新增:用 latest-chapters 接口,展示最近更新章节
|
||||
async loadDailyChapters() {
|
||||
try {
|
||||
const res = await app.request({ url: '/api/miniprogram/book/latest-chapters', silent: true })
|
||||
const list = (res && res.data) ? res.data : []
|
||||
const pt = (c) => (c.partTitle || c.part_title || '').toLowerCase()
|
||||
const exclude = c => !pt(c).includes('序言') && !pt(c).includes('尾声') && !pt(c).includes('附录')
|
||||
const daily = list
|
||||
.filter(exclude)
|
||||
.slice(0, 10)
|
||||
const baseSort = 62
|
||||
const appendixList = rows
|
||||
.filter(r => {
|
||||
const partTitle = String(r.partTitle || r.part_title || '')
|
||||
return partTitle.includes('附录')
|
||||
})
|
||||
.sort((a, b) => (a.sort_order ?? a.sectionOrder ?? 999999) - (b.sort_order ?? b.sectionOrder ?? 999999))
|
||||
.map(c => ({
|
||||
id: c.id,
|
||||
title: c.section_title || c.sectionTitle || c.title || c.chapterTitle || '附录'
|
||||
}))
|
||||
const daily = rows
|
||||
.filter(r => (r.sectionOrder ?? r.sort_order ?? 0) > baseSort)
|
||||
.sort((a, b) => new Date(b.updatedAt || b.updated_at || 0) - new Date(a.updatedAt || a.updated_at || 0))
|
||||
.slice(0, 20)
|
||||
.map(c => {
|
||||
const d = new Date(c.updatedAt || c.updated_at || Date.now())
|
||||
const title = c.section_title || c.sectionTitle || c.title || c.chapterTitle || ''
|
||||
return {
|
||||
id: c.id,
|
||||
mid: c.mid ?? c.MID ?? 0,
|
||||
title,
|
||||
title: c.section_title || c.title || c.sectionTitle,
|
||||
price: c.price ?? 1,
|
||||
dateStr: `${d.getMonth() + 1}/${d.getDate()}`
|
||||
}
|
||||
})
|
||||
this.setData({ dailyChapters: daily })
|
||||
} catch (e) { console.log('[Chapters] 加载每日新增失败:', e) }
|
||||
|
||||
this.setData({
|
||||
bookData,
|
||||
totalSections,
|
||||
appendixList,
|
||||
dailyChapters: daily,
|
||||
expandedPart: this.data.expandedPart
|
||||
})
|
||||
} catch (e) {
|
||||
console.log('[Chapters] 加载目录失败:', e)
|
||||
this.setData({ bookData: [], totalSections: 0 })
|
||||
}
|
||||
},
|
||||
|
||||
onPullDownRefresh() {
|
||||
this.loadChaptersOnce().then(() => wx.stopPullDownRefresh()).catch(() => wx.stopPullDownRefresh())
|
||||
},
|
||||
|
||||
onShow() {
|
||||
@@ -237,6 +186,7 @@ Page({
|
||||
tabBar.setData({ selected: 1 })
|
||||
}
|
||||
}
|
||||
this.setData({ auditMode: app.globalData.auditMode })
|
||||
this.updateUserStatus()
|
||||
this.loadVipStatus()
|
||||
},
|
||||
@@ -267,15 +217,13 @@ Page({
|
||||
this.setData({ isLoggedIn, hasFullBook, purchasedSections, isVip })
|
||||
},
|
||||
|
||||
// 切换展开状态,展开时懒加载该篇章章节
|
||||
async togglePart(e) {
|
||||
// 切换展开状态
|
||||
togglePart(e) {
|
||||
trackClick('chapters', 'tab_click', e.currentTarget.dataset.id || '篇章')
|
||||
const partId = e.currentTarget.dataset.id
|
||||
const isExpanding = this.data.expandedPart !== partId
|
||||
this.setData({
|
||||
expandedPart: isExpanding ? partId : null
|
||||
expandedPart: this.data.expandedPart === partId ? null : partId
|
||||
})
|
||||
if (isExpanding) await this.loadChaptersByPart(partId)
|
||||
},
|
||||
|
||||
// 跳转到阅读页(优先传 mid,与分享逻辑一致)
|
||||
@@ -301,7 +249,6 @@ Page({
|
||||
|
||||
// 跳转到搜索页
|
||||
goToSearch() {
|
||||
if (!this.data.searchEnabled) return
|
||||
trackClick('chapters', 'nav_click', '搜索')
|
||||
wx.navigateTo({ url: '/pages/search/search' })
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user