chore: 清理敏感与开发文档,仅同步代码
- 永久忽略并从仓库移除 开发文档/ - 移除并忽略 .env 与小程序私有配置 - 同步小程序/管理端/API与脚本改动 Made-with: Cursor
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
const app = getApp()
|
||||
const { trackClick } = require('../../utils/trackClick')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
@@ -16,6 +17,7 @@ Page({
|
||||
// 用户状态
|
||||
isLoggedIn: false,
|
||||
hasFullBook: false,
|
||||
isVip: false,
|
||||
purchasedSections: [],
|
||||
|
||||
// 书籍数据:以后台内容管理为准,仅用接口 /api/miniprogram/book/all-chapters 返回的数据
|
||||
@@ -26,14 +28,13 @@ Page({
|
||||
expandedPart: null,
|
||||
|
||||
// 附录
|
||||
appendixList: [
|
||||
{ id: 'appendix-1', title: '附录1|Soul派对房精选对话' },
|
||||
{ id: 'appendix-2', title: '附录2|创业者自检清单' },
|
||||
{ id: 'appendix-3', title: '附录3|本书提到的工具和资源' }
|
||||
],
|
||||
appendixList: [],
|
||||
|
||||
// 每日新增章节
|
||||
dailyChapters: []
|
||||
dailyChapters: [],
|
||||
|
||||
// 审核模式
|
||||
auditMode: false
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
@@ -43,6 +44,7 @@ Page({
|
||||
navBarHeight: app.globalData.navBarHeight
|
||||
})
|
||||
this.updateUserStatus()
|
||||
this.loadVipStatus()
|
||||
this.loadChaptersOnce()
|
||||
},
|
||||
|
||||
@@ -74,6 +76,7 @@ Page({
|
||||
|
||||
const totalSections = res.total ?? rows.length
|
||||
app.globalData.bookData = rows
|
||||
app.globalData.totalSections = totalSections
|
||||
wx.setStorageSync('bookData', rows)
|
||||
|
||||
// bookData:过滤序言/尾声/附录,按 part 聚合,篇章顺序按 sort_order 与后台一致(含「2026每日派对干货」等)
|
||||
@@ -105,13 +108,19 @@ Page({
|
||||
})
|
||||
}
|
||||
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,
|
||||
title: r.sectionTitle || r.section_title || r.title || '',
|
||||
isFree: r.isFree === true || (r.price !== undefined && r.price === 0),
|
||||
price: r.price ?? 1,
|
||||
isNew: r.isNew === true || r.is_new === true
|
||||
isNew: r.isNew === true || r.is_new === true,
|
||||
isPremium
|
||||
})
|
||||
})
|
||||
const partList = Array.from(partMap.values())
|
||||
@@ -125,6 +134,16 @@ Page({
|
||||
}))
|
||||
|
||||
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))
|
||||
@@ -143,6 +162,7 @@ Page({
|
||||
this.setData({
|
||||
bookData,
|
||||
totalSections,
|
||||
appendixList,
|
||||
dailyChapters: daily,
|
||||
expandedPart: this.data.expandedPart
|
||||
})
|
||||
@@ -166,17 +186,40 @@ Page({
|
||||
tabBar.setData({ selected: 1 })
|
||||
}
|
||||
}
|
||||
this.setData({ auditMode: app.globalData.auditMode })
|
||||
this.updateUserStatus()
|
||||
this.loadVipStatus()
|
||||
},
|
||||
|
||||
// 拉取 VIP 状态(isVip=会员,hasFullBook=9.9 买断)
|
||||
async loadVipStatus() {
|
||||
const userId = app.globalData.userInfo?.id
|
||||
if (!userId) return
|
||||
try {
|
||||
const res = await app.request({ url: `/api/miniprogram/vip/status?userId=${userId}`, silent: true, timeout: 3000 })
|
||||
if (res?.success) {
|
||||
app.globalData.isVip = !!res.data?.isVip
|
||||
app.globalData.vipExpireDate = res.data?.expireDate || ''
|
||||
this.setData({ isVip: app.globalData.isVip })
|
||||
const userInfo = app.globalData.userInfo || {}
|
||||
userInfo.isVip = app.globalData.isVip
|
||||
userInfo.vipExpireDate = app.globalData.vipExpireDate
|
||||
wx.setStorageSync('userInfo', userInfo)
|
||||
}
|
||||
} catch (e) {
|
||||
// 静默失败不影响目录展示
|
||||
}
|
||||
},
|
||||
|
||||
// 更新用户状态
|
||||
updateUserStatus() {
|
||||
const { isLoggedIn, hasFullBook, purchasedSections } = app.globalData
|
||||
this.setData({ isLoggedIn, hasFullBook, purchasedSections })
|
||||
const { isLoggedIn, hasFullBook, purchasedSections, isVip } = app.globalData
|
||||
this.setData({ isLoggedIn, hasFullBook, purchasedSections, isVip })
|
||||
},
|
||||
|
||||
// 切换展开状态
|
||||
togglePart(e) {
|
||||
trackClick('chapters', 'tab_click', e.currentTarget.dataset.id || '篇章')
|
||||
const partId = e.currentTarget.dataset.id
|
||||
this.setData({
|
||||
expandedPart: this.data.expandedPart === partId ? null : partId
|
||||
@@ -187,13 +230,15 @@ Page({
|
||||
goToRead(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
const mid = e.currentTarget.dataset.mid || app.getSectionMid(id)
|
||||
trackClick('chapters', 'card_click', id || '章节')
|
||||
const q = mid ? `mid=${mid}` : `id=${id}`
|
||||
wx.navigateTo({ url: `/pages/read/read?${q}` })
|
||||
},
|
||||
|
||||
// 检查是否已购买
|
||||
hasPurchased(sectionId) {
|
||||
if (this.data.hasFullBook) return true
|
||||
hasPurchased(sectionId, isPremium) {
|
||||
if (this.data.isVip) return true
|
||||
if (!isPremium && this.data.hasFullBook) return true
|
||||
return this.data.purchasedSections.includes(sectionId)
|
||||
},
|
||||
|
||||
@@ -204,6 +249,7 @@ Page({
|
||||
|
||||
// 跳转到搜索页
|
||||
goToSearch() {
|
||||
trackClick('chapters', 'nav_click', '搜索')
|
||||
wx.navigateTo({ url: '/pages/search/search' })
|
||||
},
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<view class="nav-bar" style="padding-top: {{statusBarHeight}}px;">
|
||||
<view class="nav-content">
|
||||
<view class="nav-left">
|
||||
<view class="search-btn" bindtap="goToSearch">
|
||||
<view class="search-btn" wx:if="{{searchEnabled}}" bindtap="goToSearch">
|
||||
<text class="search-icon">🔍</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -17,8 +17,14 @@
|
||||
<!-- 导航栏占位 -->
|
||||
<view class="nav-placeholder" style="height: {{statusBarHeight + 44}}px;"></view>
|
||||
|
||||
<!-- 目录加载中 -->
|
||||
<view class="parts-loading" wx:if="{{partsLoading}}">
|
||||
<view class="parts-loading-spinner"></view>
|
||||
<text class="parts-loading-text">加载目录中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 书籍信息卡 -->
|
||||
<view class="book-info-card card-gradient">
|
||||
<view class="book-info-card card-gradient" wx:if="{{!partsLoading}}">
|
||||
<view class="book-icon">
|
||||
<view class="book-icon-inner">📚</view>
|
||||
</view>
|
||||
@@ -33,9 +39,34 @@
|
||||
</view>
|
||||
|
||||
<!-- 目录内容 -->
|
||||
<view class="chapters-content">
|
||||
<!-- 序言 -->
|
||||
<view class="chapter-item" bindtap="goToRead" data-id="preface">
|
||||
<view class="chapters-content" wx:if="{{!partsLoading}}">
|
||||
<!-- 每日新增(最近更新章节快捷入口) -->
|
||||
<view class="daily-section" wx:if="{{dailyChapters.length > 0}}">
|
||||
<view class="daily-header">
|
||||
<text class="daily-title">每日新增</text>
|
||||
<text class="daily-badge">+{{dailyChapters.length}}</text>
|
||||
</view>
|
||||
<view class="daily-list">
|
||||
<view
|
||||
class="daily-item"
|
||||
wx:for="{{dailyChapters}}"
|
||||
wx:key="id"
|
||||
bindtap="goToRead"
|
||||
data-id="{{item.id}}"
|
||||
data-mid="{{item.mid}}"
|
||||
>
|
||||
<view class="daily-dot"></view>
|
||||
<view class="daily-content">
|
||||
<text class="daily-item-title">{{item.title}}</text>
|
||||
<text class="daily-item-meta">{{item.dateStr}} · ¥{{item.price}}</text>
|
||||
</view>
|
||||
<text class="daily-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 序言(优先传 mid,阅读页用 by-mid 请求) -->
|
||||
<view class="chapter-item" bindtap="goToRead" data-id="preface" data-mid="{{fixedSectionsMap.preface}}">
|
||||
<view class="item-left">
|
||||
<view class="item-icon icon-brand">📖</view>
|
||||
<text class="item-title">序言|为什么我每天早上6点在Soul开播?</text>
|
||||
@@ -59,27 +90,30 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="part-right">
|
||||
<text class="part-count">{{item.chapters.length}}章</text>
|
||||
<text class="part-count">{{item.chapters.length || item.chapterCount}}章</text>
|
||||
<text class="part-arrow {{expandedPart === item.id ? 'arrow-down' : ''}}">→</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 章节列表 - 展开时显示 -->
|
||||
<!-- 章节列表 - 展开时显示,懒加载 -->
|
||||
<block wx:if="{{expandedPart === item.id}}">
|
||||
<view class="chapters-list">
|
||||
<view wx:if="{{item.chapters.length === 0}}" class="chapters-loading">加载中...</view>
|
||||
<block wx:for="{{item.chapters}}" wx:key="id" wx:for-item="chapter">
|
||||
<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}}" 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>
|
||||
<text class="section-lock {{section.isFree || isVip || (!section.isPremium && hasFullBook) || purchasedSections.indexOf(section.id) > -1 ? 'lock-open' : 'lock-closed'}}">{{section.isFree || isVip || (!section.isPremium && hasFullBook) || purchasedSections.indexOf(section.id) > -1 ? '○' : '●'}}</text>
|
||||
<text class="section-title {{section.isFree || isVip || (!section.isPremium && hasFullBook) || purchasedSections.indexOf(section.id) > -1 ? '' : 'text-muted'}}">{{section.id}} {{section.title}}</text>
|
||||
<text wx:if="{{section.isNew}}" class="tag tag-new">NEW</text>
|
||||
<text wx:if="{{section.isPremium}}" class="tag tag-vip">增值</text>
|
||||
</view>
|
||||
<view class="section-right">
|
||||
<text wx:if="{{section.isFree}}" class="tag tag-free">免费</text>
|
||||
<text wx:elif="{{hasFullBook || purchasedSections.indexOf(section.id) > -1}}" class="tag tag-purchased">已购</text>
|
||||
<text wx:elif="{{isVip || (!section.isPremium && hasFullBook) || purchasedSections.indexOf(section.id) > -1}}" class="tag tag-purchased">已解锁</text>
|
||||
<text wx:elif="{{auditMode}}" class="tag tag-purchased">待开放</text>
|
||||
<text wx:else class="section-price">¥{{section.price}}</text>
|
||||
<text class="section-arrow">›</text>
|
||||
</view>
|
||||
@@ -92,8 +126,8 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 尾声 -->
|
||||
<view class="chapter-item" bindtap="goToRead" data-id="epilogue">
|
||||
<!-- 尾声(优先传 mid) -->
|
||||
<view class="chapter-item" bindtap="goToRead" data-id="epilogue" data-mid="{{fixedSectionsMap.epilogue}}">
|
||||
<view class="item-left">
|
||||
<view class="item-icon icon-brand">📖</view>
|
||||
<text class="item-title">尾声|这本书的真实目的</text>
|
||||
@@ -114,6 +148,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>
|
||||
|
||||
@@ -75,6 +75,34 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ===== 目录加载中 ===== */
|
||||
.parts-loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 120rpx 0;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.parts-loading-spinner {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border: 6rpx solid rgba(255, 255, 255, 0.1);
|
||||
border-top-color: #00CED1;
|
||||
border-radius: 50%;
|
||||
animation: parts-spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
.parts-loading-text {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
@keyframes parts-spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* ===== 书籍信息卡 ===== */
|
||||
.book-info-card {
|
||||
display: flex;
|
||||
@@ -146,6 +174,89 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* ===== 每日新增 ===== */
|
||||
.daily-section {
|
||||
margin-bottom: 32rpx;
|
||||
padding: 24rpx;
|
||||
background: #1c1c1e;
|
||||
border-radius: 24rpx;
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.daily-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.daily-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.daily-badge {
|
||||
font-size: 22rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
background: #F6AD55;
|
||||
color: #ffffff;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.daily-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.daily-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx 0;
|
||||
border-bottom: 1rpx solid rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.daily-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.daily-dot {
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(0, 206, 209, 0.6);
|
||||
margin-right: 20rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.daily-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4rpx;
|
||||
}
|
||||
|
||||
.daily-item-title {
|
||||
font-size: 26rpx;
|
||||
color: #ffffff;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.daily-item-meta {
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.daily-arrow {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
/* ===== 章节项 ===== */
|
||||
.chapter-item {
|
||||
display: flex;
|
||||
@@ -232,6 +343,14 @@
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
.tag-vip {
|
||||
background: rgba(255, 215, 0, 0.12);
|
||||
color: #FFD700;
|
||||
font-size: 20rpx;
|
||||
padding: 2rpx 8rpx;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
.text-brand {
|
||||
color: #00CED1;
|
||||
}
|
||||
@@ -331,6 +450,12 @@
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
.chapters-loading {
|
||||
padding: 24rpx;
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.chapter-group {
|
||||
background: rgba(28, 28, 30, 0.5);
|
||||
border-radius: 16rpx;
|
||||
|
||||
Reference in New Issue
Block a user