feat: 首页推荐逻辑-排除序言尾声,精选按点击量,小程序接入featuredSections
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3,7 +3,72 @@ import fs from 'fs'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { query } from '@/lib/db'
|
import { query } from '@/lib/db'
|
||||||
|
|
||||||
|
/** 精选推荐:按 user_tracks 的 view_chapter 点击量排序,排除序言/尾声/附录 */
|
||||||
|
async function getFeaturedSections(): Promise<Array<{ id: string; title: string; tag: string; tagClass: string; part: string }>> {
|
||||||
|
const tags = [
|
||||||
|
{ tag: '热门', tagClass: 'tag-pink' },
|
||||||
|
{ tag: '推荐', tagClass: 'tag-purple' },
|
||||||
|
{ tag: '精选', tagClass: 'tag-free' }
|
||||||
|
]
|
||||||
|
try {
|
||||||
|
// 优先按 view_chapter 点击量排序
|
||||||
|
const rows = (await query(`
|
||||||
|
SELECT c.id, c.section_title, c.part_title, c.is_free,
|
||||||
|
COALESCE(t.cnt, 0) as view_count
|
||||||
|
FROM chapters c
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT chapter_id, COUNT(*) as cnt
|
||||||
|
FROM user_tracks
|
||||||
|
WHERE action = 'view_chapter' AND chapter_id IS NOT NULL
|
||||||
|
GROUP BY chapter_id
|
||||||
|
) t ON c.id = t.chapter_id
|
||||||
|
WHERE c.id NOT IN ('preface','epilogue')
|
||||||
|
AND c.id NOT LIKE 'appendix-%' AND c.id NOT LIKE 'appendix_%'
|
||||||
|
AND (c.part_title NOT LIKE '%序言%' AND c.part_title NOT LIKE '%尾声%')
|
||||||
|
ORDER BY view_count DESC, c.updated_at DESC
|
||||||
|
LIMIT 3
|
||||||
|
`)) as any[]
|
||||||
|
if (rows && rows.length > 0) {
|
||||||
|
return rows.map((r, i) => ({
|
||||||
|
id: r.id,
|
||||||
|
title: r.section_title || r.title || '',
|
||||||
|
part: (r.part_title || '真实的行业').replace(/^第[一二三四五六七八九十]+篇|?/, '').trim() || '真实的行业',
|
||||||
|
tag: tags[i]?.tag || '推荐',
|
||||||
|
tagClass: tags[i]?.tagClass || 'tag-purple'
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log('[All Chapters API] 精选推荐查询失败:', (e as Error).message)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const fallback = (await query(`
|
||||||
|
SELECT id, section_title, part_title, is_free
|
||||||
|
FROM chapters
|
||||||
|
WHERE id NOT IN ('preface','epilogue')
|
||||||
|
AND id NOT LIKE 'appendix-%' AND id NOT LIKE 'appendix_%'
|
||||||
|
AND (part_title NOT LIKE '%序言%' AND part_title NOT LIKE '%尾声%')
|
||||||
|
ORDER BY updated_at DESC
|
||||||
|
LIMIT 3
|
||||||
|
`)) as any[]
|
||||||
|
if (fallback?.length > 0) {
|
||||||
|
return fallback.map((r, i) => ({
|
||||||
|
id: r.id,
|
||||||
|
title: r.section_title || r.title || '',
|
||||||
|
part: (r.part_title || '真实的行业').replace(/^第[一二三四五六七八九十]+篇|?/, '').trim() || '真实的行业',
|
||||||
|
tag: tags[i]?.tag || '推荐',
|
||||||
|
tagClass: tags[i]?.tagClass || 'tag-purple'
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
return [
|
||||||
|
{ id: '1.1', title: '荷包:电动车出租的被动收入模式', tag: '免费', tagClass: 'tag-free', part: '真实的人' },
|
||||||
|
{ id: '3.1', title: '3000万流水如何跑出来', tag: '热门', tagClass: 'tag-pink', part: '真实的行业' },
|
||||||
|
{ id: '8.1', title: '流量杠杆:抖音、Soul、飞书', tag: '推荐', tagClass: 'tag-purple', part: '真实的赚钱' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
|
const featuredSections = await getFeaturedSections()
|
||||||
try {
|
try {
|
||||||
// 方案1: 优先从数据库读取章节数据
|
// 方案1: 优先从数据库读取章节数据
|
||||||
try {
|
try {
|
||||||
@@ -47,7 +112,8 @@ export async function GET() {
|
|||||||
data: allChapters,
|
data: allChapters,
|
||||||
chapters: allChapters,
|
chapters: allChapters,
|
||||||
total: allChapters.length,
|
total: allChapters.length,
|
||||||
source: 'database'
|
source: 'database',
|
||||||
|
featuredSections
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (dbError) {
|
} catch (dbError) {
|
||||||
@@ -91,7 +157,8 @@ export async function GET() {
|
|||||||
data: allChapters,
|
data: allChapters,
|
||||||
chapters: allChapters,
|
chapters: allChapters,
|
||||||
total: allChapters.length,
|
total: allChapters.length,
|
||||||
source: 'database'
|
source: 'database',
|
||||||
|
featuredSections
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (e2) {
|
} catch (e2) {
|
||||||
@@ -144,7 +211,8 @@ export async function GET() {
|
|||||||
chapters: allChapters,
|
chapters: allChapters,
|
||||||
total: allChapters.length,
|
total: allChapters.length,
|
||||||
source: 'file',
|
source: 'file',
|
||||||
path: usedPath
|
path: usedPath,
|
||||||
|
featuredSections
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +225,8 @@ export async function GET() {
|
|||||||
data: defaultChapters,
|
data: defaultChapters,
|
||||||
chapters: defaultChapters,
|
chapters: defaultChapters,
|
||||||
total: defaultChapters.length,
|
total: defaultChapters.length,
|
||||||
source: 'default'
|
source: 'default',
|
||||||
|
featuredSections
|
||||||
})
|
})
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -171,7 +240,8 @@ export async function GET() {
|
|||||||
chapters: defaultChapters,
|
chapters: defaultChapters,
|
||||||
total: defaultChapters.length,
|
total: defaultChapters.length,
|
||||||
source: 'fallback',
|
source: 'fallback',
|
||||||
warning: '使用默认数据'
|
warning: '使用默认数据',
|
||||||
|
featuredSections
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,22 @@
|
|||||||
// app/api/book/latest-chapters/route.ts
|
// app/api/book/latest-chapters/route.ts
|
||||||
// 获取最新章节:有2日内更新则取最新3章,否则随机取免费章节
|
// 获取最新章节:有2日内更新则取最新3章,否则随机取免费章节
|
||||||
|
// 排除序言、尾声、附录,只推荐正文章节
|
||||||
|
|
||||||
import { NextResponse } from 'next/server'
|
import { NextResponse } from 'next/server'
|
||||||
import { query } from '@/lib/db'
|
import { query } from '@/lib/db'
|
||||||
|
|
||||||
const TWO_DAYS_MS = 2 * 24 * 60 * 60 * 1000
|
const TWO_DAYS_MS = 2 * 24 * 60 * 60 * 1000
|
||||||
|
|
||||||
|
/** 是否应排除(序言、尾声、附录等特殊章节) */
|
||||||
|
function isExcludedChapter(id: string, partTitle: string): boolean {
|
||||||
|
const lowerId = String(id || '').toLowerCase()
|
||||||
|
if (lowerId === 'preface' || lowerId === 'epilogue') return true
|
||||||
|
if (lowerId.startsWith('appendix-') || lowerId.startsWith('appendix_')) return true
|
||||||
|
const pt = String(partTitle || '')
|
||||||
|
if (/序言|尾声/.test(pt)) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
let allChapters: Array<{
|
let allChapters: Array<{
|
||||||
@@ -26,15 +37,17 @@ export async function GET() {
|
|||||||
`)) as any[]
|
`)) as any[]
|
||||||
|
|
||||||
if (dbRows?.length > 0) {
|
if (dbRows?.length > 0) {
|
||||||
allChapters = dbRows.map((row: any) => ({
|
allChapters = dbRows
|
||||||
id: row.id,
|
.map((row: any) => ({
|
||||||
title: row.section_title || row.title || '',
|
id: row.id,
|
||||||
part: row.part_title || '真实的行业',
|
title: row.section_title || row.title || '',
|
||||||
isFree: !!row.is_free,
|
part: row.part_title || '真实的行业',
|
||||||
price: row.price || 0,
|
isFree: !!row.is_free,
|
||||||
updatedAt: row.updated_at || row.created_at,
|
price: row.price || 0,
|
||||||
createdAt: row.created_at
|
updatedAt: row.updated_at || row.created_at,
|
||||||
}))
|
createdAt: row.created_at
|
||||||
|
}))
|
||||||
|
.filter((c) => !isExcludedChapter(c.id, c.part))
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('[latest-chapters] 数据库读取失败:', (e as Error).message)
|
console.log('[latest-chapters] 数据库读取失败:', (e as Error).message)
|
||||||
|
|||||||
@@ -145,15 +145,19 @@ Page({
|
|||||||
this.setData({ latestSection: selected, latestLabel: '为你推荐' })
|
this.setData({ latestSection: selected, latestLabel: '为你推荐' })
|
||||||
},
|
},
|
||||||
|
|
||||||
// 加载书籍数据
|
// 加载书籍数据(含精选推荐,按后端点击量排序)
|
||||||
async loadBookData() {
|
async loadBookData() {
|
||||||
try {
|
try {
|
||||||
const res = await app.request('/api/book/all-chapters')
|
const res = await app.request('/api/book/all-chapters')
|
||||||
if (res && res.data) {
|
if (res && res.data) {
|
||||||
this.setData({
|
const setData = {
|
||||||
bookData: res.data,
|
bookData: res.data,
|
||||||
totalSections: res.totalSections || 62
|
totalSections: res.totalSections || res.data?.length || 62
|
||||||
})
|
}
|
||||||
|
if (res.featuredSections && res.featuredSections.length) {
|
||||||
|
setData.featuredSections = res.featuredSections
|
||||||
|
}
|
||||||
|
this.setData(setData)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('加载书籍数据失败:', e)
|
console.error('加载书籍数据失败:', e)
|
||||||
|
|||||||
Reference in New Issue
Block a user