feat: 小程序阅读记录与资料链路、管理端用户规则、API/VIP/推荐与运营脚本
- miniprogram: reading-records、imageUrl/mpNavigate、多页资料与 VIP 展示调整 - soul-admin: Users/Settings/UserDetailModal、dist 构建产物更新 - soul-api: user/vip/referral/ckb/db、MBTI 头像管理、user_rule_completion、迁移 SQL - .cursor: karuo-party 与飞书文档;.gitignore 忽略 .tmp_skill_bundle Made-with: Cursor
This commit is contained in:
178
miniprogram/pages/reading-records/reading-records.js
Normal file
178
miniprogram/pages/reading-records/reading-records.js
Normal file
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* 阅读记录:最近阅读 + 已读章节(与「我的」数据源一致)
|
||||
*/
|
||||
const app = getApp()
|
||||
const { cleanSingleLineField } = require('../../utils/contentParser.js')
|
||||
|
||||
function titleFromBookData(sectionId, bookFlat) {
|
||||
const row = bookFlat.find((s) => s.id === sectionId)
|
||||
return cleanSingleLineField(
|
||||
row?.sectionTitle || row?.section_title || row?.title || row?.chapterTitle || ''
|
||||
) || `章节 ${sectionId}`
|
||||
}
|
||||
|
||||
function midFromBookData(sectionId, bookFlat) {
|
||||
const row = bookFlat.find((s) => s.id === sectionId)
|
||||
return row?.mid ?? row?.MID ?? 0
|
||||
}
|
||||
|
||||
function mergeRecentFromLocal(apiList) {
|
||||
const normalized = Array.isArray(apiList)
|
||||
? apiList.map((item) => ({
|
||||
id: item.id,
|
||||
mid: item.mid,
|
||||
title: cleanSingleLineField(item.title || '') || `章节 ${item.id}`
|
||||
}))
|
||||
: []
|
||||
if (normalized.length > 0) return normalized
|
||||
try {
|
||||
const progressData = wx.getStorageSync('reading_progress') || {}
|
||||
const bookFlat = Array.isArray(app.globalData.bookData) ? app.globalData.bookData : []
|
||||
return Object.keys(progressData)
|
||||
.map((id) => ({
|
||||
id,
|
||||
ts: progressData[id]?.lastOpenAt || progressData[id]?.last_open_at || 0
|
||||
}))
|
||||
.filter((e) => e.id)
|
||||
.sort((a, b) => b.ts - a.ts)
|
||||
.slice(0, 20)
|
||||
.map((e) => ({
|
||||
id: e.id,
|
||||
mid: midFromBookData(e.id, bookFlat),
|
||||
title: titleFromBookData(e.id, bookFlat)
|
||||
}))
|
||||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
statusBarHeight: 44,
|
||||
isLoggedIn: false,
|
||||
focus: 'all',
|
||||
recentList: [],
|
||||
readAllList: [],
|
||||
recentSectionTitle: '最近阅读',
|
||||
readSectionTitle: '已读章节'
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const focus = (options.focus === 'recent' || options.focus === 'read') ? options.focus : 'all'
|
||||
this.setData({
|
||||
statusBarHeight: app.globalData.statusBarHeight || 44,
|
||||
focus
|
||||
})
|
||||
this._applyMpUiTitles()
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.setData({ isLoggedIn: !!(app.globalData.isLoggedIn && app.globalData.userInfo?.id) })
|
||||
this._applyMpUiTitles()
|
||||
if (this.data.isLoggedIn) this.loadData()
|
||||
},
|
||||
|
||||
_applyMpUiTitles() {
|
||||
const my = app.globalData.configCache?.mpConfig?.mpUi?.myPage || {}
|
||||
this.setData({
|
||||
recentSectionTitle: my.recentReadTitle || '最近阅读',
|
||||
readSectionTitle: my.readStatLabel || '已读章节'
|
||||
})
|
||||
},
|
||||
|
||||
async _ensureBookFlat() {
|
||||
let flat = Array.isArray(app.globalData.bookData) ? app.globalData.bookData : []
|
||||
if (flat.length) return flat
|
||||
try {
|
||||
const r = await app.request({ url: '/api/miniprogram/book/all-chapters', silent: true })
|
||||
const list = r?.data
|
||||
if (Array.isArray(list) && list.length) {
|
||||
app.globalData.bookData = list
|
||||
return list
|
||||
}
|
||||
} catch (_) {}
|
||||
return []
|
||||
},
|
||||
|
||||
async loadData() {
|
||||
const userId = app.globalData.userInfo?.id
|
||||
if (!userId) return
|
||||
try {
|
||||
const res = await app.request({
|
||||
url: `/api/miniprogram/user/dashboard-stats?userId=${encodeURIComponent(userId)}`,
|
||||
silent: true
|
||||
})
|
||||
const bookFlat = await this._ensureBookFlat()
|
||||
let recent = []
|
||||
let readIds = []
|
||||
if (res?.success && res.data) {
|
||||
const apiRecent = Array.isArray(res.data.recentChapters) ? res.data.recentChapters : []
|
||||
recent = mergeRecentFromLocal(apiRecent)
|
||||
readIds = Array.isArray(res.data.readSectionIds) ? res.data.readSectionIds.filter(Boolean) : []
|
||||
} else {
|
||||
recent = mergeRecentFromLocal([])
|
||||
}
|
||||
try {
|
||||
const progressData = wx.getStorageSync('reading_progress') || {}
|
||||
const fromKeys = Object.keys(progressData).filter(Boolean)
|
||||
const stored = wx.getStorageSync('readSectionIds')
|
||||
const fromStored = Array.isArray(stored) ? stored.filter(Boolean) : []
|
||||
const fromGlobal = Array.isArray(app.globalData.readSectionIds)
|
||||
? app.globalData.readSectionIds.filter(Boolean)
|
||||
: []
|
||||
readIds = [...new Set([...readIds, ...fromGlobal, ...fromStored, ...fromKeys])]
|
||||
} catch (_) {}
|
||||
if (readIds.length === 0 && recent.length > 0) {
|
||||
readIds = recent.map((r) => r.id)
|
||||
}
|
||||
const readAllList = readIds.map((id) => ({
|
||||
id,
|
||||
mid: midFromBookData(id, bookFlat),
|
||||
title: titleFromBookData(id, bookFlat)
|
||||
}))
|
||||
this.setData({ recentList: recent, readAllList })
|
||||
} catch (e) {
|
||||
console.warn('[reading-records]', e)
|
||||
try {
|
||||
const bookFlat = await this._ensureBookFlat()
|
||||
let readIds = Array.isArray(app.globalData.readSectionIds) ? [...app.globalData.readSectionIds] : []
|
||||
if (!readIds.length) {
|
||||
try {
|
||||
const stored = wx.getStorageSync('readSectionIds')
|
||||
if (Array.isArray(stored)) readIds = [...stored]
|
||||
} catch (_) {}
|
||||
}
|
||||
const recent = mergeRecentFromLocal([])
|
||||
if (!readIds.length && recent.length) readIds = recent.map((r) => r.id)
|
||||
const readAllList = readIds.map((id) => ({
|
||||
id,
|
||||
mid: midFromBookData(id, bookFlat),
|
||||
title: titleFromBookData(id, bookFlat)
|
||||
}))
|
||||
this.setData({ recentList: recent, readAllList })
|
||||
} catch (_) {
|
||||
this.setData({ recentList: [], readAllList: [] })
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
goRead(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
const mid = e.currentTarget.dataset.mid
|
||||
if (!id) return
|
||||
const q = mid ? `mid=${mid}` : `id=${id}`
|
||||
wx.navigateTo({ url: `/pages/read/read?${q}` })
|
||||
},
|
||||
|
||||
goChapters() {
|
||||
wx.switchTab({ url: '/pages/chapters/chapters' })
|
||||
},
|
||||
|
||||
goLogin() {
|
||||
wx.switchTab({ url: '/pages/my/my' })
|
||||
},
|
||||
|
||||
goBack() {
|
||||
getApp().goBackOrToHome()
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user