feat: 管理后台改造 + 小程序最新章节逻辑 + 变更文档
【soul-admin 管理后台】 - 交易中心 → 推广中心(侧边栏与页面标题) - 移除 5 个冗余按钮,仅保留「API 接口」 - 删除按钮改为悬停显示 - 免费/付费可点击切换(单击切换,双击付费可设金额) - 加号移至章节右侧(序言、附录等),小节内移除加号 - 章节与小节支持拖拽排序 - 持续隐藏「上传内容」等按钮,解决双页面问题 【小程序首页 - 最新章节】 - latest-chapters API: 2 日内有新章取最新 3 章,否则随机免费章 - 首页 Banner 调用 /api/book/latest-chapters - 标签动态显示「最新更新」或「为你推荐」 【开发文档】 - 新增 soul-admin变更记录_v2026-02.md Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -77,59 +77,72 @@ Page({
|
||||
this.setData({ loading: true })
|
||||
|
||||
try {
|
||||
// 获取书籍数据
|
||||
await this.loadBookData()
|
||||
// 计算推荐章节
|
||||
this.computeLatestSection()
|
||||
await this.loadLatestSection()
|
||||
} catch (e) {
|
||||
console.error('初始化失败:', e)
|
||||
this.computeLatestSectionFallback()
|
||||
} finally {
|
||||
this.setData({ loading: false })
|
||||
}
|
||||
},
|
||||
|
||||
// 计算推荐章节(根据用户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
|
||||
// 从后端获取最新章节(2日内有新章取最新3章,否则随机免费章)
|
||||
async loadLatestSection() {
|
||||
try {
|
||||
const res = await app.request('/api/book/latest-chapters')
|
||||
if (res && res.success && res.banner) {
|
||||
this.setData({
|
||||
latestSection: res.banner,
|
||||
latestLabel: res.label || '最新更新'
|
||||
})
|
||||
return
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('latest-chapters API 失败,使用兜底逻辑:', e.message)
|
||||
}
|
||||
this.computeLatestSectionFallback()
|
||||
},
|
||||
|
||||
// 兜底:API 失败时从 bookData 计算(随机选免费章节)
|
||||
computeLatestSectionFallback() {
|
||||
const bookData = app.globalData.bookData || this.data.bookData || []
|
||||
let sections = []
|
||||
if (Array.isArray(bookData)) {
|
||||
sections = bookData.map(s => ({
|
||||
id: s.id,
|
||||
title: s.title || s.sectionTitle,
|
||||
part: s.part || s.sectionTitle || '真实的行业',
|
||||
isFree: s.isFree,
|
||||
price: s.price
|
||||
}))
|
||||
} else if (bookData && typeof bookData === 'object') {
|
||||
const parts = bookData.parts || (Array.isArray(bookData) ? bookData : [])
|
||||
if (Array.isArray(parts)) {
|
||||
parts.forEach(p => {
|
||||
(p.chapters || p.sections || []).forEach(c => {
|
||||
(c.sections || [c]).forEach(s => {
|
||||
sections.push({
|
||||
id: s.id,
|
||||
title: s.title || s.section_title,
|
||||
part: p.title || p.part_title || c.title || '',
|
||||
isFree: s.isFree,
|
||||
price: s.price
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 根据种子选择章节
|
||||
const index = seed % candidates.length
|
||||
const selected = candidates[index]
|
||||
|
||||
// 设置标签(如果有新增章节显示"最新更新",否则显示"推荐阅读")
|
||||
const label = candidates === allSections ? '推荐阅读' : '为你推荐'
|
||||
|
||||
this.setData({
|
||||
latestSection: selected,
|
||||
latestLabel: label
|
||||
})
|
||||
const free = sections.filter(s => s.isFree !== false && (s.price === 0 || !s.price))
|
||||
const candidates = free.length > 0 ? free : sections
|
||||
if (candidates.length === 0) {
|
||||
this.setData({ latestSection: { id: '1.1', title: '开始阅读', part: '真实的人' }, latestLabel: '为你推荐' })
|
||||
return
|
||||
}
|
||||
const idx = Math.floor(Math.random() * candidates.length)
|
||||
const selected = { id: candidates[idx].id, title: candidates[idx].title, part: candidates[idx].part || '真实的行业' }
|
||||
this.setData({ latestSection: selected, latestLabel: '为你推荐' })
|
||||
},
|
||||
|
||||
// 加载书籍数据
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<!-- Banner卡片 - 最新章节 -->
|
||||
<view class="banner-card" bindtap="goToRead" data-id="{{latestSection.id}}">
|
||||
<view class="banner-glow"></view>
|
||||
<view class="banner-tag">最新更新</view>
|
||||
<view class="banner-tag">{{latestLabel}}</view>
|
||||
<view class="banner-title">{{latestSection.title}}</view>
|
||||
<view class="banner-part">{{latestSection.part}}</view>
|
||||
<view class="banner-action">
|
||||
|
||||
Reference in New Issue
Block a user