# 笔临 v1.1 作家日更工具包 设计规格 **版本**:1.0 **日期**:2026-07-08 **范围**:§10 章节模板 + §11.1 书籍导入(txt/md/docx)+ §20.2 投稿导出预设 + §20.6 全局灵感捕获增强 **前置**:v1.0.0 已交付(`4adafa1`:P7 角色关系图谱 + P9 写作分析) **参考**:`design/readme.md` v1.1 §10 / §11.1 / §20.2 / §20.6、`design/ui_pc.html` `#templateModal` / `#importModal` / `#exportModal` / `#inspirationModal` **目标版本**:v1.1.0 --- ## 1. 背景与目标 ### 1.1 阶段定位 v1.0.0 已完成「看数据」能力(关系图谱、写作分析、POV)。`design/readme.md` §20.7 审查清单仍有多项 **有 UI 原型、无真实实现** 的作家刚需: - 主页「导入书籍」为 `comingSoon` - 章节模板 / 快速新建章仅有 `#templateModal` 静态原型 - 导出模态「复制到剪贴板(网文投稿格式)」无后端 - 灵感捕获 `Ctrl+Shift+I` 仅编辑器内可用,保存后会跳转侧栏打断心流 本阶段(v1.1.0)交付 **作家日更工具包**,形成「写得更快 → 发得更顺 → 灵感不漏」闭环。 ### 1.2 已确认决策(Brainstorming 2026-07-08) | 维度 | 决策 | |------|------| | 方向 | **方案 B**:作家日更工具包 | | MVP 边界 | **方案 A(完整四模块)**:模板 + txt/md/docx 导入 + 投稿预设 + 灵感增强 | | 模板存储 | `GlobalSettings.chapterTemplates[]`;内置模板代码常量合并展示 | | 模板解析 | **渲染进程** `chapter-template.ts` 替换变量后 `chapter.create` + `chapter.update` | | docx 解析 | **mammoth**(docx → HTML,保留 bold/italic) | | md 解析 | **marked**(md → HTML) | | 导入分章 | 自动正则 / 双换行 / 自定义正则 三档 | | 大文件 | >20MB 导入前二次确认;进度条 IPC 事件 | | 投稿导出 | **单章**复制剪贴板 + 另存 `.txt`;内置「通用网文」预设 | | 投稿预设存储 | `GlobalSettings.submissionPresets[]` | | 灵感增强 | 快捷键全局可用;未开书时模态内选书;保存后仅 toast、不跳转 | | 置顶 | 灵感模态最高 z-index;不新建独立 BrowserWindow | ### 1.3 验收标准 用户能完成: 1. 在章节列表点击「快速新建」,选择模板并输入标题,创建章节且编辑器自动填入模板正文(含变量替换) 2. 在设置页创建/编辑/删除自定义章节模板;内置模板只读展示 3. 主页「导入书籍」选择 `.txt` / `.md` / `.docx`,预览分章结果,确认后创建新书并导入全部章节 4. 导入 >20MB 文件前看到确认提示;导入过程显示进度 5. 编辑器内 `Ctrl+Shift+E` 打开导出模态,选择「通用网文」预设,**复制到剪贴板** 得到段首空两格 + `# 第N章 标题` 格式纯文本 6. 设置页自定义投稿预设(标题格式、段首缩进、段落间距),导出时应用 7. 在主页按 `Ctrl+Shift+I` 唤起灵感捕获,选择书籍后保存,仅 toast 确认、焦点不打断原界面 8. 编辑器内灵感捕获行为与 7 一致(不再自动跳转灵感面板) ### 1.4 本规格不实现(明确排除) | 项 | 归属 | |----|------| | `.novel` / `.novel-all` 项目包导入导出 | v1.2+ | | PDF / Word 全书导出、分卷批量投稿复制 | v1.2+ | | §11.2 完整导出矩阵(docx 目录、PDF 排版) | v1.2+ | | §20.5 敏感词合规、预设内敏感词替换规则 | v1.2+ | | §20.3 角色弧线简报 | v1.2+(图谱深化线) | | §5.7 故事时间线、§5.8 连续阅读模式 | v1.2+ | | 导入 `.novel` 包 | v1.2+ | | 平台 API 直连投稿 | 永久 YAGNI | | 模板变量 `{outlineItem}` 自动关联当前章大纲 | v1.1 留空字符串(无关联时);手动关联后续 | --- ## 2. 架构 ### 2.1 进程模型 ``` ┌─────────────────────────────────────────────────────────────────────┐ │ 渲染进程 │ │ · TemplateQuickCreateModal / TemplateSettingsTab │ │ · ImportBookModal │ │ · ExportModal + SubmissionPresetSettingsTab │ │ · InspirationModal(增强:全局唤起、选书、无跳转) │ │ · chapter-template.ts / submission-formatter.ts(纯函数) │ └──────────────────────────────┬──────────────────────────────────────┘ │ IPC ┌────────────────────────────▼────────────────────────────────────────┐ │ 主进程 │ │ · ImportService(读文件、分章、创建书+卷+章批量写入) │ │ · ExportService(HTML→纯文本、应用投稿预设、写剪贴板/文件) │ │ · BookRegistryService(已有 create/open) │ │ · GlobalSettingsService(扩展 chapterTemplates / submissionPresets)│ │ · dialog.showOpenDialog / clipboard │ └─────────────────────────────────────────────────────────────────────┘ ``` ### 2.2 模块边界 | 模块 | 职责 | 依赖 | |------|------|------| | `chapter-template.ts` | 变量替换、章节号计算 | `GlobalSettings`, 书籍 volumes/chapters/settings | | `ImportService` | 解析文件、分章、创建书 | `BookRegistry`, `ChapterRepository`, mammoth, marked | | `ExportService` | 单章格式化、剪贴板、存盘 | `ChapterRepository`, `GlobalSettings` | | `SubmissionFormatter` | HTML 剥离、段首缩进、标题行 | 纯函数,主/渲染均可调用 | | `TemplateQuickCreateModal` | 选模板 → IPC 创建章 | `chapter.create/update` | | `ImportBookModal` | 选文件 → preview → execute | `import.*` | | `ExportModal` | 选预设 → 复制/存盘 | `export.*` | **原则**: - 导入/导出重 IO 与解析放主进程;模板变量替换可在渲染进程(减少 IPC 往返) - 导入失败整事务回滚:书未创建或创建后写章失败则 `registry.delete(bookId)` - 剪贴板写入仅主进程(Electron `clipboard` API) --- ## 3. 数据模型 ### 3.1 GlobalSettings 扩展 ```typescript export interface ChapterTemplate { id: string name: string body: string defaultStatus?: ChapterStatus builtin?: boolean } export interface SubmissionPreset { id: string name: string titleFormat: string // 默认 "# 第{chapterNumber}章 {chapterTitle}" indentParagraphs: boolean // 段首两全角空格 includeAuthorsNote: boolean // 保留「作者的话」段(检测 —— 分隔) paragraphGap: 'single' | 'double' } // GlobalSettings 追加: chapterTemplates?: ChapterTemplate[] submissionPresets?: SubmissionPreset[] defaultSubmissionPresetId?: string ``` **内置模板**(`src/shared/builtin-templates.ts`,不写入 JSON): | id | name | body 摘要 | |----|------|-----------| | `builtin-generic` | 通用模板 | `## 第{chapterNumber}章 {chapterTitle}\n\n` | | `builtin-webnovel` | 网文常用 | 正文 + `\n\n——\n作者的话:\n` | **内置投稿预设**: | id | name | 规则 | |----|------|------| | `builtin-webnovel` | 通用网文 | `titleFormat: "# 第{chapterNumber}章 {chapterTitle}"`, `indentParagraphs: true`, `paragraphGap: 'double'` | `GlobalSettingsService` 读取时合并内置项;用户不可删除内置项。 ### 3.2 模板变量 | 变量 | 来源 | |------|------| | `{chapterNumber}` | 目标卷 `chapters.length + 1` | | `{chapterTitle}` | 用户输入 | | `{penName}` | `settings.penName` | | `{date}` | `localDateString()` | | `{volumeName}` | 当前卷 `name` | | `{previousChapterTitle}` | 同卷上一章 `title`,首章 `""` | | `{outlineItem}` | v1.1 固定 `""`(章节创建时无 outline 关联) | | `{characterList}` | 当前卷内 `settings.type=character` 且 `chapterIds` 与卷内任一章有交集的 `name`,逗号连接 | ### 3.3 导入分章 ```typescript export type ImportSplitMode = 'auto' | 'paragraph' | 'regex' export interface ImportChapterPreview { title: string contentHtml: string wordCount: number } export interface ImportPreviewResult { fileName: string fileSizeBytes: number chapters: ImportChapterPreview[] warnings: string[] } export interface ImportExecuteParams { filePath: string bookName: string category: string splitMode: ImportSplitMode customRegex?: string } ``` **自动分章正则**(依次尝试,命中即用): 1. `/^#\s*第.+章[^\n]*/gm`(Markdown 标题) 2. `/^第[一二三四五六七八九十百千零〇\d]+章[^\n]*/gm`(纯文本章标题行) **paragraph 模式**:按 `\n\s*\n` 分段;标题取段首非空行,截断 50 字。 **首章前导内容**:合并入第一章 `warnings` 提示,不丢弃。 --- ## 4. IPC ### 4.1 通道 ```typescript IMPORT_PICK_FILE: 'import:pickFile' IMPORT_PREVIEW: 'import:preview' IMPORT_EXECUTE: 'import:execute' IMPORT_PROGRESS: 'import:progress' // main → renderer event EXPORT_FORMAT_CHAPTER: 'export:formatChapter' EXPORT_COPY_CLIPBOARD: 'export:copyClipboard' EXPORT_SAVE_TXT: 'export:saveTxt' ``` ### 4.2 载荷 ```typescript // import:pickFile → { canceled, filePath? } // import:preview → { filePath, splitMode, customRegex? } → ImportPreviewResult // import:execute → ImportExecuteParams → { bookId } // import:progress → { phase: 'parsing'|'writing', current, total } // export:formatChapter → { bookId, chapterId, presetId } → { text } // export:copyClipboard → { text } → void // export:saveTxt → { text, defaultName } → { savedPath? } ``` --- ## 5. UI 规格 ### 5.1 TemplateQuickCreateModal - 入口:章节列表底部 `data-testid="chapter-quick-new"` - 布局:参考 `#templateModal` 卡片网格 + 标题输入 - 流程:选模板 → 输入标题 → 创建 → `switchTarget` 到新章 ### 5.2 设置页 — 章节模板 Tab - `data-testid="settings-chapter-templates"` - 列表:内置 + 自定义;自定义可编辑 body(textarea)、删除 - 新建:`setting-template-add` ### 5.3 ImportBookModal - 入口:主页 `home.importBook`(替换 comingSoon) - `data-testid="import-book-modal"` - 字段:文件路径(浏览)、书名、分类、分章规则下拉、自定义正则输入(regex 模式显示) - 预览区:章数 + 前 5 章标题列表 - >20MB:`import-large-file-confirm` 对话框 - 进度:`import-progress-bar` ### 5.4 ExportModal - 入口:编辑器工具栏 `data-testid="export-chapter"` + 快捷键 `exportBook` - `data-testid="export-modal"` - 折叠区「投稿预设」:`export-preset-select` - 操作:`export-copy-clipboard` / `export-save-txt` - 预览:格式化后前 500 字纯文本 ### 5.5 设置页 — 投稿预设 Tab - `data-testid="settings-submission-presets"` - 字段:名称、标题格式、段首缩进 checkbox、段落间距 select ### 5.6 InspirationModal 增强 - `App.tsx`:`captureInspiration` 移除 `view !== 'editor'` 限制 - 未开书:显示 `inspiration-book-select`(书籍列表下拉,默认 `lastOpenedAt` 最新) - 保存后:删除 `switchTarget` + `setSidebarPanel('inspiration')`;仅 `showToast` + `setOpen(false)` - 样式:`inspiration-modal--capture` 提升 z-index(高于 TopBar) --- ## 6. 依赖 | 包 | 用途 | |----|------| | `mammoth` | docx → HTML | | `marked` | md → HTML | 均在主进程 `ImportService` 使用;electron-vite 打包时 external 或 bundle 按现有惯例。 --- ## 7. 测试 | 用例 ID | 层级 | 场景 | 预期 | |---------|------|------|------| | UT-TEMPL-01 | 单元 | 解析含 `{chapterNumber}` `{chapterTitle}` | 正确替换 | | UT-TEMPL-02 | 单元 | `{volumeName}` 替换 | 卷名正确 | | UT-TEMPL-03 | 单元 | `{characterList}` 卷内角色 | 逗号列表 | | IT-TEMPL-01 | 集成 | 模板快速新建写入 DB | content 含替换后正文 | | UT-IMPORT-01 | 单元 | txt 自动分章 3 章 | `chapters.length === 3` | | UT-IMPORT-02 | 单元 | md 转 HTML | 含 `

` | | UT-IMPORT-03 | 单元 | docx mammoth | 非空 HTML | | IT-IMPORT-01 | 集成 | execute 创建书+章 | `registry.open` 章数匹配 | | UT-EXPORT-01 | 单元 | 段首空两格 | 每段 `  ` 前缀 | | UT-EXPORT-02 | 单元 | 标题格式 | `# 第1章 标题` | | IT-EXPORT-01 | 集成 | copyClipboard | 剪贴板非空 | | E2E-TEMPL-01 | E2E | 快速新建章 | 编辑器有模板内容 | | E2E-IMPORT-01 | E2E | 导入 txt | 新书可见多章 | | E2E-EXPORT-01 | E2E | 复制投稿格式 | 剪贴板含章标题 | | E2E-INSP-01 | E2E | 主页 Ctrl+Shift+I 保存 | toast,不跳编辑器 | --- ## 8. i18n 键(节选) ```json "template.title": "章节模板", "template.quickNew": "模板新建章", "template.builtin": "内置", "template.custom": "自定义", "import.title": "导入书籍", "import.pickFile": "浏览文件", "import.splitAuto": "自动识别章节", "import.splitParagraph": "双换行分段", "import.splitRegex": "自定义正则", "import.largeFileConfirm": "文件较大({{size}}),导入可能需要 30 秒至 2 分钟,是否继续?", "import.progress": "正在导入 {{current}}/{{total}}", "export.title": "导出章节", "export.submissionPresets": "投稿预设", "export.copyClipboard": "复制到剪贴板", "export.saveTxt": "另存为文本", "export.copied": "已复制到剪贴板", "submission.title": "投稿预设", "submission.titleFormat": "标题格式", "submission.indent": "段首空两格", "inspiration.selectBook": "关联书籍", "inspiration.saved": "灵感已保存" ``` --- ## 9. 文件清单 | 文件 | 职责 | |------|------| | `src/shared/types.ts` | `ChapterTemplate`, `SubmissionPreset`, Import/Export DTO | | `src/shared/builtin-templates.ts` | 内置模板与投稿预设 | | `src/shared/ipc-channels.ts` | `import.*` / `export.*` | | `src/main/services/import.service.ts` | 解析 + 分章 + 创建书 | | `src/main/services/export.service.ts` | 格式化 + 剪贴板/存盘 | | `src/main/lib/submission-formatter.ts` | HTML → 投稿纯文本 | | `src/main/lib/chapter-splitter.ts` | 分章逻辑 | | `src/main/ipc/handlers/import.handler.ts` | 导入 IPC | | `src/main/ipc/handlers/export.handler.ts` | 导出 IPC | | `src/renderer/lib/chapter-template.ts` | 模板变量替换 | | `src/renderer/components/chapter/TemplateQuickCreateModal.tsx` | 快速新建 | | `src/renderer/components/import/ImportBookModal.tsx` | 导入 UI | | `src/renderer/components/export/ExportModal.tsx` | 导出 UI | | `src/renderer/components/settings/TemplateSettingsTab.tsx` | 模板管理 | | `src/renderer/components/settings/SubmissionPresetTab.tsx` | 投稿预设管理 | | `src/renderer/components/inspiration/InspirationModal.tsx` | 增强 | | `src/renderer/components/home/HomePage.tsx` | 接入导入 | | `src/renderer/components/layout/EditorLayout.tsx` | 导出入口 | | `tests/main/chapter-template.test.ts` | UT-TEMPL-* | | `tests/main/chapter-splitter.test.ts` | UT-IMPORT-01 | | `tests/main/submission-formatter.test.ts` | UT-EXPORT-* | | `tests/main/import.service.test.ts` | IT-IMPORT-01 | | `e2e/chapter-template.spec.ts` | E2E-TEMPL-01 | | `e2e/import-book.spec.ts` | E2E-IMPORT-01 | | `e2e/export-submission.spec.ts` | E2E-EXPORT-01 | | `e2e/inspiration-capture.spec.ts` | E2E-INSP-01 | --- ## 10. 实现顺序建议 1. 类型 + 内置常量 + `chapter-template.ts` + 快速新建 UI 2. `SubmissionFormatter` + ExportService + ExportModal + 设置 Tab 3. InspirationModal 增强(独立、改动小) 4. ImportService + ImportBookModal(依赖 mammoth/marked,放最后) **目标版本号**:实现完成后 `package.json` / README / 关于页 → **v1.1.0** --- ## 11. Spec 自检 | 检查项 | 状态 | |--------|------| | 无 TBD/TODO 占位 | ✅ | | 与方案 A 四模块一致 | ✅ | | `.novel` 明确排除 | ✅ | | 单章投稿导出范围明确 | ✅ | | IPC/数据模型无矛盾 | ✅ | | 测试用例覆盖四模块 | ✅ |