更新小程序配置,重构页面结构,删除不再使用的地址管理和章节页面,优化项目结构以提升可维护性;调整全局样式,增强组件的可复用性和一致性。

This commit is contained in:
2026-02-03 11:35:38 +08:00
parent d74410cfb5
commit a7d781a25b
79 changed files with 10610 additions and 3518 deletions

View File

@@ -1,130 +0,0 @@
const app = getApp()
Page({
data: {
statusBarHeight: 44,
navBarHeight: 88,
totalSections: 62,
bookData: [],
expandedPart: 'part-1',
hasFullBook: false,
purchasedSections: [],
appendixList: [
{ id: 'appendix-1', title: '附录1Soul派对房精选对话' },
{ id: 'appendix-2', title: '附录2创业者自检清单' },
{ id: 'appendix-3', title: '附录3本书提到的工具和资源' }
]
},
onLoad() {
this.setNavBarHeight()
this.loadChapters()
this.syncUserStatus()
},
onShow() {
if (typeof this.getTabBar === 'function' && this.getTabBar()) this.getTabBar().setData({ selected: 1 })
this.setNavBarHeight()
this.syncUserStatus()
},
setNavBarHeight() {
const statusBarHeight = app.globalData.statusBarHeight || 44
const navBarHeight = app.globalData.navBarHeight || (statusBarHeight + 44)
this.setData({ statusBarHeight, navBarHeight })
},
loadChapters() {
app.request('/api/book/all-chapters').then((res) => {
if (res && res.data && Array.isArray(res.data)) {
const bookData = this.normalizeBookData(res.data)
const totalSections = res.totalSections || res.total || res.data.length || 62
this.setData({ bookData, totalSections })
} else if (res && res.chapters) {
const bookData = this.normalizeBookData(res.chapters)
this.setData({ bookData, totalSections: res.chapters.length || 62 })
}
}).catch(() => {})
},
normalizeBookData(list) {
if (!Array.isArray(list) || list.length === 0) return []
const partOrder = ['part-1', 'part-2', 'part-3', 'part-4', 'part-5']
const partTitles = ['真实的人', '真实的行业', '真实的错误', '真实的赚钱', '真实的未来']
const partMap = {}
const subtitles = ['人性观察与社交逻辑', '社会运作的底层规则', '错过机会比失败更贵', '所有行业的杠杆结构', '人与系统的关系']
partOrder.forEach((id, i) => {
partMap[id] = {
id,
number: String(i + 1).padStart(2, '0'),
title: partTitles[i] || ('篇' + (i + 1)),
subtitle: subtitles[i] || '',
chapters: []
}
})
list.forEach((s) => {
let partId = s.partId || s.part_id
if (!partId && s.id) {
const num = String(s.id).split('.')[0]
partId = partOrder[parseInt(num, 10) - 1] || 'part-1'
}
partId = partId || 'part-1'
if (!partMap[partId]) partMap[partId] = { id: partId, number: '99', title: '其他', subtitle: '', chapters: [] }
const chId = s.chapterId || s.chapter_id || 'ch1'
let ch = partMap[partId].chapters.find(c => c.id === chId)
if (!ch) {
ch = { id: chId, title: s.chapterTitle || s.chapter_title || '章节', sections: [] }
partMap[partId].chapters.push(ch)
}
ch.sections.push({
id: s.id,
title: s.sectionTitle || s.title || s.section_title || '',
isFree: !!s.isFree || !!s.is_free,
price: s.price != null ? s.price : 1
})
})
const out = partOrder.map(id => partMap[id]).filter(p => p.chapters.length > 0)
out.forEach(p => {
p.sectionCount = p.chapters.reduce((acc, ch) => acc + (ch.sections ? ch.sections.length : 0), 0)
})
if (out.length === 0) {
const sections = list.map(s => ({ id: s.id, title: s.sectionTitle || s.title || s.section_title || '', isFree: !!s.isFree || !!s.is_free, price: s.price != null ? s.price : 1 }))
const single = { id: 'part-1', number: '01', title: '全部', subtitle: '', sectionCount: sections.length, chapters: [{ id: 'ch1', title: '章节', sections }] }
return [single]
}
return out
},
syncUserStatus() {
const { hasFullBook, purchasedSections } = app.globalData
this.setData({ hasFullBook: !!hasFullBook, purchasedSections: purchasedSections || [] })
},
hasPurchased(sectionId) {
const { hasFullBook, purchasedSections } = app.globalData
if (hasFullBook) return true
return (purchasedSections || []).indexOf(sectionId) >= 0
},
togglePart(e) {
const id = e.currentTarget.dataset.id
const expandedPart = this.data.expandedPart === id ? '' : id
this.setData({ expandedPart })
},
goToSearch() {
wx.navigateTo({ url: '/pages/search/search' })
},
goToRead(e) {
const id = e.currentTarget.dataset.id
if (!id) return
wx.navigateTo({ url: '/pages/read/read?id=' + encodeURIComponent(id) })
},
onPullDownRefresh() {
this.loadChapters()
this.syncUserStatus()
wx.stopPullDownRefresh()
}
})

View File

@@ -1,4 +0,0 @@
{
"navigationBarTitleText": "目录",
"usingComponents": {}
}

View File

@@ -1,84 +0,0 @@
<view class="page">
<view class="nav-placeholder" style="height: {{navBarHeight || (statusBarHeight + 44)}}px;"></view>
<view class="header">
<view class="header-inner safe-header-right">
<view class="header-placeholder"></view>
<text class="header-title">目录</text>
<view class="header-btn" bindtap="goToSearch">🔍</view>
</view>
</view>
<view class="book-card">
<view class="book-icon">📖</view>
<view class="book-info">
<text class="book-title">一场SOUL的创业实验场</text>
<text class="book-desc">来自Soul派对房的真实商业故事</text>
</view>
<view class="book-stat">
<text class="book-num">{{totalSections}}</text>
<text class="book-label">章节</text>
</view>
</view>
<view class="preface-row" bindtap="goToRead" data-id="preface">
<view class="preface-left">
<view class="preface-icon">📄</view>
<text class="preface-text">序言为什么我每天早上6点在Soul开播?</text>
</view>
<text class="tag-free">免费</text>
<text class="arrow"></text>
</view>
<view class="parts" wx:for="{{bookData}}" wx:key="id">
<view class="part-head" data-id="{{item.id}}" bindtap="togglePart">
<view class="part-left">
<view class="part-num">{{item.number}}</view>
<view class="part-info">
<text class="part-title">{{item.title}}</text>
<text class="part-subtitle">{{item.subtitle}}</text>
</view>
</view>
<text class="part-count">{{item.sectionCount != null ? item.sectionCount : (item.chapters && item.chapters.length)}}章</text>
<text class="arrow {{expandedPart === item.id ? 'expanded' : ''}}"></text>
</view>
<view class="part-body" wx:if="{{expandedPart === item.id}}">
<view class="chapter-block" wx:for="{{item.chapters}}" wx:for-item="ch" wx:key="id">
<view class="chapter-title">{{ch.title}}</view>
<view
class="section-row"
wx:for="{{ch.sections}}"
wx:for-item="sec"
wx:key="id"
bindtap="goToRead"
data-id="{{sec.id}}"
>
<text class="section-lock">{{sec.isFree || (hasFullBook || (purchasedSections && purchasedSections.indexOf(sec.id) >= 0)) ? '✓' : '🔒'}}</text>
<text class="section-text {{sec.isFree || (hasFullBook || (purchasedSections && purchasedSections.indexOf(sec.id) >= 0)) ? '' : 'locked'}}">{{sec.id}} {{sec.title}}</text>
<text class="section-tag" wx:if="{{sec.isFree}}">免费</text>
<text class="section-tag purchased" wx:elif="{{hasFullBook || (purchasedSections && purchasedSections.indexOf(sec.id) >= 0)}}">已购</text>
<text class="section-price" wx:else>¥{{sec.price}}</text>
<text class="arrow"></text>
</view>
</view>
</view>
</view>
<view class="preface-row" bindtap="goToRead" data-id="epilogue">
<view class="preface-left">
<view class="preface-icon">📄</view>
<text class="preface-text">尾声|这本书的真实目的</text>
</view>
<text class="tag-free">免费</text>
<text class="arrow"></text>
</view>
<view class="appendix-block">
<text class="appendix-label">附录</text>
<view class="appendix-item" wx:for="{{appendixList}}" wx:key="id" bindtap="goToRead" data-id="{{item.id}}">
<text class="appendix-title">{{item.title}}</text>
<text class="arrow"></text>
</view>
</view>
<view class="bottom-space"></view>
</view>

View File

@@ -1,48 +0,0 @@
.page { min-height: 100vh; background: #000; padding-bottom: 200rpx; }
.nav-placeholder { width: 100%; }
.header { background: rgba(0,0,0,0.9); border-bottom: 2rpx solid rgba(255,255,255,0.05); }
.header-inner { display: flex; align-items: center; justify-content: space-between; padding: 24rpx 32rpx; }
.header-placeholder { width: 64rpx; }
.header-title { font-size: 36rpx; font-weight: 600; color: #00CED1; }
.header-btn { width: 64rpx; height: 64rpx; border-radius: 50%; background: #2c2c2e; display: flex; align-items: center; justify-content: center; font-size: 32rpx; }
.book-card { margin: 32rpx; padding: 32rpx; border-radius: 32rpx; background: linear-gradient(135deg, #1c1c1e 0%, #2c2c2e 100%); border: 2rpx solid rgba(0,206,209,0.2); display: flex; align-items: center; gap: 24rpx; }
.book-icon { width: 96rpx; height: 96rpx; border-radius: 24rpx; background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%); display: flex; align-items: center; justify-content: center; font-size: 48rpx; }
.book-info { flex: 1; }
.book-title { font-size: 32rpx; font-weight: 600; color: #fff; display: block; }
.book-desc { font-size: 22rpx; color: rgba(255,255,255,0.4); margin-top: 8rpx; display: block; }
.book-stat { text-align: right; }
.book-num { font-size: 40rpx; font-weight: 700; color: #00CED1; display: block; }
.book-label { font-size: 20rpx; color: rgba(255,255,255,0.4); }
.preface-row { margin: 0 32rpx 24rpx; padding: 24rpx 32rpx; border-radius: 24rpx; background: #1c1c1e; border: 2rpx solid rgba(255,255,255,0.05); display: flex; align-items: center; }
.preface-left { display: flex; align-items: center; gap: 24rpx; flex: 1; }
.preface-icon { width: 64rpx; height: 64rpx; border-radius: 16rpx; background: rgba(0,206,209,0.2); display: flex; align-items: center; justify-content: center; font-size: 32rpx; }
.preface-text { font-size: 28rpx; color: #fff; }
.tag-free { font-size: 22rpx; color: #00CED1; background: rgba(0,206,209,0.1); padding: 6rpx 16rpx; border-radius: 8rpx; margin-right: 16rpx; }
.arrow { font-size: 32rpx; color: rgba(255,255,255,0.4); }
.arrow.expanded { transform: rotate(90deg); }
.parts { margin: 0 32rpx 24rpx; }
.part-head { padding: 24rpx 32rpx; border-radius: 24rpx; background: #1c1c1e; border: 2rpx solid rgba(255,255,255,0.05); display: flex; align-items: center; }
.part-left { display: flex; align-items: center; gap: 24rpx; flex: 1; }
.part-num { width: 64rpx; height: 64rpx; border-radius: 16rpx; background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%); display: flex; align-items: center; justify-content: center; font-size: 28rpx; font-weight: 700; color: #fff; }
.part-info { }
.part-title { font-size: 28rpx; font-weight: 600; color: #fff; display: block; }
.part-subtitle { font-size: 20rpx; color: rgba(255,255,255,0.4); }
.part-count { font-size: 22rpx; color: rgba(255,255,255,0.4); margin-right: 16rpx; }
.part-body { margin-top: 16rpx; margin-left: 24rpx; padding: 16rpx; border-radius: 16rpx; background: rgba(28,28,30,0.5); border: 2rpx solid rgba(255,255,255,0.05); }
.chapter-block { margin-bottom: 24rpx; }
.chapter-block:last-child { margin-bottom: 0; }
.chapter-title { font-size: 24rpx; color: rgba(255,255,255,0.6); padding: 16rpx 0; border-bottom: 2rpx solid rgba(255,255,255,0.05); margin-bottom: 8rpx; }
.section-row { display: flex; align-items: center; padding: 20rpx 24rpx; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
.section-row:last-child { border-bottom: none; }
.section-lock { font-size: 28rpx; margin-right: 16rpx; }
.section-text { flex: 1; font-size: 24rpx; color: #fff; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.section-text.locked { color: rgba(255,255,255,0.5); }
.section-tag { font-size: 20rpx; color: #00CED1; background: rgba(0,206,209,0.1); padding: 4rpx 12rpx; border-radius: 6rpx; margin-right: 8rpx; }
.section-tag.purchased { background: transparent; }
.section-price { font-size: 20rpx; color: rgba(255,255,255,0.4); margin-right: 8rpx; }
.appendix-block { margin: 0 32rpx 24rpx; padding: 24rpx; border-radius: 24rpx; background: #1c1c1e; border: 2rpx solid rgba(255,255,255,0.05); }
.appendix-label { font-size: 24rpx; font-weight: 500; color: rgba(255,255,255,0.5); display: block; margin-bottom: 16rpx; }
.appendix-item { display: flex; align-items: center; justify-content: space-between; padding: 20rpx 0; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
.appendix-item:last-child { border-bottom: none; }
.appendix-title { font-size: 24rpx; color: rgba(255,255,255,0.8); }
.bottom-space { height: 40rpx; }