Files
soul/app/api/book/latest-chapters/route.ts
卡若 7551840c86 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>
2026-02-21 20:44:38 +08:00

97 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// app/api/book/latest-chapters/route.ts
// 获取最新章节有2日内更新则取最新3章否则随机取免费章节
import { NextResponse } from 'next/server'
import { query } from '@/lib/db'
const TWO_DAYS_MS = 2 * 24 * 60 * 60 * 1000
export async function GET() {
try {
let allChapters: Array<{
id: string
title: string
part: string
isFree: boolean
price: number
updatedAt: Date | string | null
createdAt: Date | string | null
}> = []
try {
const dbRows = (await query(`
SELECT id, part_title, section_title, is_free, price, created_at, updated_at
FROM chapters
ORDER BY sort_order ASC, id ASC
`)) as any[]
if (dbRows?.length > 0) {
allChapters = dbRows.map((row: any) => ({
id: row.id,
title: row.section_title || row.title || '',
part: row.part_title || '真实的行业',
isFree: !!row.is_free,
price: row.price || 0,
updatedAt: row.updated_at || row.created_at,
createdAt: row.created_at
}))
}
} catch (e) {
console.log('[latest-chapters] 数据库读取失败:', (e as Error).message)
}
if (allChapters.length === 0) {
return NextResponse.json({
success: true,
banner: { id: '1.1', title: '荷包:电动车出租的被动收入模式', part: '真实的人' },
label: '为你推荐',
chapters: [],
hasNewUpdates: false
})
}
const now = Date.now()
const sorted = [...allChapters].sort((a, b) => {
const ta = a.updatedAt ? new Date(a.updatedAt).getTime() : 0
const tb = b.updatedAt ? new Date(b.updatedAt).getTime() : 0
return tb - ta
})
const mostRecentTime = sorted[0]?.updatedAt ? new Date(sorted[0].updatedAt).getTime() : 0
const hasNewUpdates = now - mostRecentTime < TWO_DAYS_MS
let banner: { id: string; title: string; part: string }
let label: string
let chapters: typeof allChapters
if (hasNewUpdates && sorted.length > 0) {
chapters = sorted.slice(0, 3)
banner = { id: chapters[0].id, title: chapters[0].title, part: chapters[0].part }
label = '最新更新'
} else {
const freeChapters = allChapters.filter((c) => c.isFree || c.price === 0)
const candidates = freeChapters.length > 0 ? freeChapters : allChapters
const shuffled = [...candidates].sort(() => Math.random() - 0.5)
chapters = shuffled.slice(0, 3)
banner = chapters[0]
? { id: chapters[0].id, title: chapters[0].title, part: chapters[0].part }
: { id: allChapters[0].id, title: allChapters[0].title, part: allChapters[0].part }
label = '为你推荐'
}
return NextResponse.json({
success: true,
banner,
label,
chapters: chapters.map((c) => ({ id: c.id, title: c.title, part: c.part, isFree: c.isFree })),
hasNewUpdates
})
} catch (error) {
console.error('[latest-chapters] Error:', error)
return NextResponse.json(
{ success: false, error: '获取失败' },
{ status: 500 }
)
}
}