From ceaae40b54029ac96877e0996cc0447f5cb6cda3 Mon Sep 17 00:00:00 2001 From: Administrator Date: Sun, 13 Sep 2026 20:52:43 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=86=E7=BA=B2=E7=94=9F=E6=88=90=E6=8C=89?= =?UTF-8?q?=E5=A4=A7=E7=BA=B2=E8=A7=84=E5=88=92=E7=9A=84=E6=AF=8F=E5=8D=B7?= =?UTF-8?q?=E7=AB=A0=E6=95=B0=E5=87=BA=E7=AB=A0=EF=BC=8C=E5=A4=A7=E7=BA=B2?= =?UTF-8?q?=E9=A1=B5=E5=8F=AF=E7=BC=96=E8=BE=91=E8=AE=A1=E5=88=92=E7=AB=A0?= =?UTF-8?q?=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - volumes 表新增 chapter_count 列(增量迁移),应用大纲时落库保存 - 细纲生成目标章数优先级:大纲计划章数 > 卷内已有章节数 > 默认 10 - outline 路由支持手动设置 chapter_count;大纲页每卷卡片新增「计划章数」输入框 --- server/src/db/schema.ts | 6 ++++-- server/src/db/types.ts | 1 + server/src/routes/generate.ts | 12 +++++++++--- server/src/routes/outline.ts | 7 ++++--- web/src/api/types.ts | 1 + web/src/components/OutlineView.tsx | 15 +++++++++++++-- 6 files changed, 32 insertions(+), 10 deletions(-) diff --git a/server/src/db/schema.ts b/server/src/db/schema.ts index 398a2ca..7dd863d 100644 --- a/server/src/db/schema.ts +++ b/server/src/db/schema.ts @@ -55,7 +55,8 @@ export function migrate() { project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE, title TEXT NOT NULL, summary TEXT NOT NULL DEFAULT '', - sort INTEGER NOT NULL DEFAULT 0 + sort INTEGER NOT NULL DEFAULT 0, + chapter_count INTEGER ); CREATE TABLE IF NOT EXISTS beats ( @@ -126,7 +127,8 @@ export function migrate() { ['beats', 'pov', "ALTER TABLE beats ADD COLUMN pov TEXT NOT NULL DEFAULT ''"], ['beats', 'emotional_arc', "ALTER TABLE beats ADD COLUMN emotional_arc TEXT NOT NULL DEFAULT ''"], ['beats', 'purpose', "ALTER TABLE beats ADD COLUMN purpose TEXT NOT NULL DEFAULT ''"], - ['beats', 'tension', 'ALTER TABLE beats ADD COLUMN tension INTEGER NOT NULL DEFAULT 0'] + ['beats', 'tension', 'ALTER TABLE beats ADD COLUMN tension INTEGER NOT NULL DEFAULT 0'], + ['volumes', 'chapter_count', 'ALTER TABLE volumes ADD COLUMN chapter_count INTEGER'] ] for (const [table, col, ddl] of addColumns) { const cols = db.prepare(`PRAGMA table_info(${table})`).all() as unknown as { name: string }[] diff --git a/server/src/db/types.ts b/server/src/db/types.ts index d12930e..ca26081 100644 --- a/server/src/db/types.ts +++ b/server/src/db/types.ts @@ -47,6 +47,7 @@ export interface VolumeRow { title: string summary: string sort: number + chapter_count: number | null } export interface BeatRow { diff --git a/server/src/routes/generate.ts b/server/src/routes/generate.ts index 5692537..b31d69f 100644 --- a/server/src/routes/generate.ts +++ b/server/src/routes/generate.ts @@ -202,9 +202,9 @@ generateRouter.post('/generate/beats', (req, res) => { }) .join('\n') - // 本卷目标章数:应用大纲时创建的空章节数;没有章节信息时默认 10 + // 本卷目标章数:优先用大纲规划的每卷章数,其次本卷已有章节数,都没有时默认 10 const existing = bundle.chapters.filter((c: ChapterRow) => c.volume_id === volume.id).length - const chapterCount = existing > 0 ? existing : 10 + const chapterCount = volume.chapter_count && volume.chapter_count > 0 ? volume.chapter_count : existing > 0 ? existing : 10 const messages = beatsMessages( toProjectLike(bundle.project), { title: volume.title, summary: volume.summary, chapter_count: chapterCount }, @@ -393,7 +393,13 @@ generateRouter.post('/generate/apply/volumes', (req, res) => { db.prepare('DELETE FROM beats WHERE project_id = ?').run(projectId) db.prepare('DELETE FROM volumes WHERE project_id = ?').run(projectId) items.forEach((v, i) => { - db.prepare('INSERT INTO volumes (project_id, title, summary, sort) VALUES (?,?,?,?)').run(projectId, v.title, v.summary, i + 1) + db.prepare('INSERT INTO volumes (project_id, title, summary, sort, chapter_count) VALUES (?,?,?,?,?)').run( + projectId, + v.title, + v.summary, + i + 1, + v.chapter_count ?? null + ) }) db.exec('COMMIT') } catch (e) { diff --git a/server/src/routes/outline.ts b/server/src/routes/outline.ts index bf20d76..9a38ed3 100644 --- a/server/src/routes/outline.ts +++ b/server/src/routes/outline.ts @@ -9,7 +9,8 @@ export const outlineRouter = Router() const volumeInput = z.object({ title: z.string().min(1).max(100), summary: z.string().max(4000).default(''), - sort: z.number().int().default(0) + sort: z.number().int().default(0), + chapter_count: z.number().int().min(1).max(2000).nullable().optional() }) outlineRouter.post('/projects/:projectId/volumes', (req, res) => { @@ -17,8 +18,8 @@ outlineRouter.post('/projects/:projectId/volumes', (req, res) => { const parsed = volumeInput.safeParse(req.body) if (!parsed.success) return res.status(400).json({ error: parsed.error.issues[0].message }) const info = db - .prepare('INSERT INTO volumes (project_id, title, summary, sort) VALUES (?,?,?,?)') - .run(projectId, parsed.data.title, parsed.data.summary, parsed.data.sort) + .prepare('INSERT INTO volumes (project_id, title, summary, sort, chapter_count) VALUES (?,?,?,?,?)') + .run(projectId, parsed.data.title, parsed.data.summary, parsed.data.sort, parsed.data.chapter_count ?? null) res.json(db.prepare('SELECT * FROM volumes WHERE id = ?').get(info.lastInsertRowid)) }) diff --git a/web/src/api/types.ts b/web/src/api/types.ts index 45cf1fa..fbe880a 100644 --- a/web/src/api/types.ts +++ b/web/src/api/types.ts @@ -48,6 +48,7 @@ export interface Volume { title: string summary: string sort: number + chapter_count: number | null } export interface Beat { diff --git a/web/src/components/OutlineView.tsx b/web/src/components/OutlineView.tsx index 3ba1e13..57a9b65 100644 --- a/web/src/components/OutlineView.tsx +++ b/web/src/components/OutlineView.tsx @@ -48,8 +48,19 @@ export default function OutlineView({ saveVolume(v.id, { title: t })} placeholder="卷名" bordered={false} style={{ fontWeight: 700, fontSize: 15 }} /> saveVolume(v.id, { summary: s })} multiline minRows={2} placeholder="本卷大纲:核心冲突、关键事件、结尾钩子" style={{ color: '#555' }} /> -
- 本卷 {bundle.chapters.filter((c) => c.volume_id === v.id).length} 章(生成细纲后自动创建) +
+ 本卷已有 {bundle.chapters.filter((c) => c.volume_id === v.id).length} 章 · 计划章数 + { + const n = parseInt(t, 10) + saveVolume(v.id, { chapter_count: Number.isFinite(n) && n > 0 ? n : null }) + }} + placeholder="自动" + size="small" + style={{ width: 64 }} + /> + (生成细纲时按此数量出章;留空则按已有章节数,都没有时默认 10)
))