264 lines
7.2 KiB
TypeScript
264 lines
7.2 KiB
TypeScript
|
|
// app/api/book/chapters/route.ts
|
|||
|
|
// 章节管理API - 支持列表查询、新增、编辑
|
|||
|
|
// 开发: 卡若
|
|||
|
|
// 日期: 2026-01-25
|
|||
|
|
|
|||
|
|
import { NextRequest, NextResponse } from 'next/server'
|
|||
|
|
import { query } from '@/lib/db'
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* GET - 获取章节列表
|
|||
|
|
* 支持参数:
|
|||
|
|
* - partId: 按篇筛选
|
|||
|
|
* - status: 按状态筛选 (draft/published/archived)
|
|||
|
|
* - page: 页码
|
|||
|
|
* - pageSize: 每页数量
|
|||
|
|
*/
|
|||
|
|
export async function GET(req: NextRequest) {
|
|||
|
|
try {
|
|||
|
|
const { searchParams } = new URL(req.url)
|
|||
|
|
const partId = searchParams.get('partId')
|
|||
|
|
const status = searchParams.get('status') || 'published'
|
|||
|
|
const page = parseInt(searchParams.get('page') || '1')
|
|||
|
|
const pageSize = parseInt(searchParams.get('pageSize') || '100')
|
|||
|
|
|
|||
|
|
let sql = `
|
|||
|
|
SELECT id, part_id, part_title, chapter_id, chapter_title, section_title,
|
|||
|
|
word_count, is_free, price, sort_order, status, created_at, updated_at
|
|||
|
|
FROM chapters
|
|||
|
|
WHERE 1=1
|
|||
|
|
`
|
|||
|
|
const params: any[] = []
|
|||
|
|
|
|||
|
|
if (partId) {
|
|||
|
|
sql += ' AND part_id = ?'
|
|||
|
|
params.push(partId)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (status && status !== 'all') {
|
|||
|
|
sql += ' AND status = ?'
|
|||
|
|
params.push(status)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
sql += ' ORDER BY sort_order ASC'
|
|||
|
|
sql += ' LIMIT ? OFFSET ?'
|
|||
|
|
params.push(pageSize, (page - 1) * pageSize)
|
|||
|
|
|
|||
|
|
const results = await query(sql, params) as any[]
|
|||
|
|
|
|||
|
|
// 获取总数
|
|||
|
|
let countSql = 'SELECT COUNT(*) as total FROM chapters WHERE 1=1'
|
|||
|
|
const countParams: any[] = []
|
|||
|
|
if (partId) {
|
|||
|
|
countSql += ' AND part_id = ?'
|
|||
|
|
countParams.push(partId)
|
|||
|
|
}
|
|||
|
|
if (status && status !== 'all') {
|
|||
|
|
countSql += ' AND status = ?'
|
|||
|
|
countParams.push(status)
|
|||
|
|
}
|
|||
|
|
const countResult = await query(countSql, countParams) as any[]
|
|||
|
|
const total = countResult[0]?.total || 0
|
|||
|
|
|
|||
|
|
return NextResponse.json({
|
|||
|
|
success: true,
|
|||
|
|
data: {
|
|||
|
|
list: results,
|
|||
|
|
total,
|
|||
|
|
page,
|
|||
|
|
pageSize,
|
|||
|
|
totalPages: Math.ceil(total / pageSize)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('[Chapters API] 获取列表失败:', error)
|
|||
|
|
return NextResponse.json(
|
|||
|
|
{ success: false, error: '获取章节列表失败' },
|
|||
|
|
{ status: 500 }
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* POST - 新增章节
|
|||
|
|
*/
|
|||
|
|
export async function POST(req: NextRequest) {
|
|||
|
|
try {
|
|||
|
|
const body = await req.json()
|
|||
|
|
const {
|
|||
|
|
id,
|
|||
|
|
partId,
|
|||
|
|
partTitle,
|
|||
|
|
chapterId,
|
|||
|
|
chapterTitle,
|
|||
|
|
sectionTitle,
|
|||
|
|
content,
|
|||
|
|
isFree = false,
|
|||
|
|
price = 1,
|
|||
|
|
sortOrder,
|
|||
|
|
status = 'published'
|
|||
|
|
} = body
|
|||
|
|
|
|||
|
|
// 验证必填字段
|
|||
|
|
if (!id || !partId || !partTitle || !chapterId || !chapterTitle || !sectionTitle || !content) {
|
|||
|
|
return NextResponse.json(
|
|||
|
|
{ success: false, error: '缺少必填字段' },
|
|||
|
|
{ status: 400 }
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查ID是否已存在
|
|||
|
|
const existing = await query('SELECT id FROM chapters WHERE id = ?', [id]) as any[]
|
|||
|
|
if (existing.length > 0) {
|
|||
|
|
return NextResponse.json(
|
|||
|
|
{ success: false, error: '章节ID已存在' },
|
|||
|
|
{ status: 400 }
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 计算字数
|
|||
|
|
const wordCount = content.replace(/\s/g, '').length
|
|||
|
|
|
|||
|
|
// 计算排序顺序(如果未提供)
|
|||
|
|
let order = sortOrder
|
|||
|
|
if (order === undefined || order === null) {
|
|||
|
|
const maxOrder = await query(
|
|||
|
|
'SELECT MAX(sort_order) as maxOrder FROM chapters WHERE part_id = ?',
|
|||
|
|
[partId]
|
|||
|
|
) as any[]
|
|||
|
|
order = (maxOrder[0]?.maxOrder || 0) + 1
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await query(`
|
|||
|
|
INSERT INTO chapters (id, part_id, part_title, chapter_id, chapter_title, section_title, content, word_count, is_free, price, sort_order, status)
|
|||
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|||
|
|
`, [id, partId, partTitle, chapterId, chapterTitle, sectionTitle, content, wordCount, isFree, isFree ? 0 : price, order, status])
|
|||
|
|
|
|||
|
|
console.log('[Chapters API] 新增章节成功:', id)
|
|||
|
|
|
|||
|
|
return NextResponse.json({
|
|||
|
|
success: true,
|
|||
|
|
message: '章节创建成功',
|
|||
|
|
data: { id, wordCount, sortOrder: order }
|
|||
|
|
})
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('[Chapters API] 新增章节失败:', error)
|
|||
|
|
return NextResponse.json(
|
|||
|
|
{ success: false, error: '新增章节失败' },
|
|||
|
|
{ status: 500 }
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* PUT - 编辑章节
|
|||
|
|
*/
|
|||
|
|
export async function PUT(req: NextRequest) {
|
|||
|
|
try {
|
|||
|
|
const body = await req.json()
|
|||
|
|
const { id, ...updates } = body
|
|||
|
|
|
|||
|
|
if (!id) {
|
|||
|
|
return NextResponse.json(
|
|||
|
|
{ success: false, error: '缺少章节ID' },
|
|||
|
|
{ status: 400 }
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查章节是否存在
|
|||
|
|
const existing = await query('SELECT id FROM chapters WHERE id = ?', [id]) as any[]
|
|||
|
|
if (existing.length === 0) {
|
|||
|
|
return NextResponse.json(
|
|||
|
|
{ success: false, error: '章节不存在' },
|
|||
|
|
{ status: 404 }
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 构建更新语句
|
|||
|
|
const allowedFields = ['part_id', 'part_title', 'chapter_id', 'chapter_title', 'section_title', 'content', 'is_free', 'price', 'sort_order', 'status']
|
|||
|
|
const fieldMapping: Record<string, string> = {
|
|||
|
|
partId: 'part_id',
|
|||
|
|
partTitle: 'part_title',
|
|||
|
|
chapterId: 'chapter_id',
|
|||
|
|
chapterTitle: 'chapter_title',
|
|||
|
|
sectionTitle: 'section_title',
|
|||
|
|
isFree: 'is_free',
|
|||
|
|
sortOrder: 'sort_order'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const setClauses: string[] = []
|
|||
|
|
const params: any[] = []
|
|||
|
|
|
|||
|
|
for (const [key, value] of Object.entries(updates)) {
|
|||
|
|
const dbField = fieldMapping[key] || key
|
|||
|
|
if (allowedFields.includes(dbField) && value !== undefined) {
|
|||
|
|
setClauses.push(`${dbField} = ?`)
|
|||
|
|
params.push(value)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果更新了content,重新计算字数
|
|||
|
|
if (updates.content) {
|
|||
|
|
const wordCount = updates.content.replace(/\s/g, '').length
|
|||
|
|
setClauses.push('word_count = ?')
|
|||
|
|
params.push(wordCount)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (setClauses.length === 0) {
|
|||
|
|
return NextResponse.json(
|
|||
|
|
{ success: false, error: '没有可更新的字段' },
|
|||
|
|
{ status: 400 }
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
params.push(id)
|
|||
|
|
await query(`UPDATE chapters SET ${setClauses.join(', ')} WHERE id = ?`, params)
|
|||
|
|
|
|||
|
|
console.log('[Chapters API] 更新章节成功:', id)
|
|||
|
|
|
|||
|
|
return NextResponse.json({
|
|||
|
|
success: true,
|
|||
|
|
message: '章节更新成功'
|
|||
|
|
})
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('[Chapters API] 更新章节失败:', error)
|
|||
|
|
return NextResponse.json(
|
|||
|
|
{ success: false, error: '更新章节失败' },
|
|||
|
|
{ status: 500 }
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* DELETE - 删除章节(软删除,改状态为archived)
|
|||
|
|
*/
|
|||
|
|
export async function DELETE(req: NextRequest) {
|
|||
|
|
try {
|
|||
|
|
const { searchParams } = new URL(req.url)
|
|||
|
|
const id = searchParams.get('id')
|
|||
|
|
|
|||
|
|
if (!id) {
|
|||
|
|
return NextResponse.json(
|
|||
|
|
{ success: false, error: '缺少章节ID' },
|
|||
|
|
{ status: 400 }
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 软删除:改状态为archived
|
|||
|
|
await query("UPDATE chapters SET status = 'archived' WHERE id = ?", [id])
|
|||
|
|
|
|||
|
|
console.log('[Chapters API] 删除章节成功:', id)
|
|||
|
|
|
|||
|
|
return NextResponse.json({
|
|||
|
|
success: true,
|
|||
|
|
message: '章节已删除'
|
|||
|
|
})
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('[Chapters API] 删除章节失败:', error)
|
|||
|
|
return NextResponse.json(
|
|||
|
|
{ success: false, error: '删除章节失败' },
|
|||
|
|
{ status: 500 }
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
}
|