docs: 添加笔临 P0/P1 设计规格
定义 Electron+React 地基与写作核心的架构、IPC、数据模型与验收标准,作为全量实现的第一阶段依据。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
.worktrees/
|
||||||
|
worktrees/
|
||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
out/
|
out/
|
||||||
|
|||||||
+218
-22
@@ -1,9 +1,11 @@
|
|||||||
# 笔临——长篇创作智能协作平台 完整设计文档
|
# 笔临——长篇创作智能协作平台 完整设计文档
|
||||||
|
|
||||||
**版本**:1.0
|
**版本**:1.1
|
||||||
**更新日期**:2026年6月25日
|
**更新日期**:2026年7月2日
|
||||||
**定位**:跨平台长篇小说写作系统,以“人本创作”为核心,通过深度上下文感知与创作流优化,解决AI辅助写作中“难用、不敢用、割裂”的痛点。
|
**定位**:跨平台长篇小说写作系统,以“人本创作”为核心,通过深度上下文感知与创作流优化,解决AI辅助写作中“难用、不敢用、割裂”的痛点。
|
||||||
|
|
||||||
|
> **v1.1 变更摘要**:以资深连载作者工作流为基准,补全引用面板/全局搜索/AI 会话/地标待办/更新计划等 UI 规格;统一主题命名;修正驾驶舱进入逻辑;新增平台投稿与衔接检查等作家刚需能力。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. 项目概述
|
## 1. 项目概述
|
||||||
@@ -66,15 +68,17 @@
|
|||||||
|
|
||||||
#### 3.1.2 七套内置主题
|
#### 3.1.2 七套内置主题
|
||||||
|
|
||||||
| 主题名称 | 类型 | 主背景色 | 主文字色 | 强调色 | 适用场景 |
|
主题命名采用文学意象,与 UI 原型(`ui_pc.html`)保持一致:
|
||||||
|----------|------|----------|----------|--------|----------|
|
|
||||||
| **暗夜黑** | 深色 | `#1a1a2e` | `#e0e0e0` | `#6c63ff` | 夜间长时间写作 |
|
| 主题名称 | 内部 ID | 类型 | 主背景色 | 主文字色 | 强调色 | 适用场景 |
|
||||||
| **深海蓝** | 深色 | `#0d1b2a` | `#c5d5e8` | `#4ea8de` | 沉浸式创作 |
|
|----------|---------|------|----------|----------|--------|----------|
|
||||||
| **石墨灰** | 深色 | `#2d2d2d` | `#cccccc` | `#ff6b6b` | 中性低调偏好 |
|
| **墨砚**(默认) | `default` | 深色 | `#1c1816` | `#e8e0d8` | `#c0504a` | 夜间长时间写作,偏暖深棕 |
|
||||||
| **暖米纸** | 浅色 | `#f5f0e8` | `#3a3a3a` | `#b8860b` | 模拟纸质书写作 |
|
| **竹语** | `bamboo` | 深色 | `#1a1e18` | `#e0e4d8` | `#7a9a60` | 竹林雨后,舒缓护眼 |
|
||||||
| **冷瓷白** | 浅色 | `#fafafa` | `#2c2c2c` | `#2563eb` | 日间清爽写作 |
|
| **月夜** | `moonlit` | 深色 | `#1a1e24` | `#d8dce4` | `#8098b8` | 月光静谧,冷调沉浸 |
|
||||||
| **护眼绿** | 护眼 | `#e8f0e0` | `#2d3a2d` | `#4a7c59` | 缓解视疲劳 |
|
| **宣纸** | `ricepaper` | 浅色 | `#f4efe4` | `#3a3028` | `#8b4513` | 模拟古籍书卷写作 |
|
||||||
| **琥珀暮色** | 特殊 | `#2b1f1a` | `#e8d5c0` | `#d4a574` | 温暖怀旧氛围 |
|
| **晨雾** | `mist` | 浅色 | `#eff0f2` | `#2c3038` | `#6878a0` | 日间清爽写作 |
|
||||||
|
| **茶园** | `teagarden` | 护眼 | `#eef0e4` | `#2d3a28` | `#688848` | 缓解视疲劳 |
|
||||||
|
| **暮光** | `twilight` | 特殊 | `#1f1a16` | `#e8dcc8` | `#c88850` | 温暖怀旧氛围 |
|
||||||
|
|
||||||
#### 3.1.3 CSS 变量体系
|
#### 3.1.3 CSS 变量体系
|
||||||
|
|
||||||
@@ -181,6 +185,9 @@
|
|||||||
| 全局搜索 | `Ctrl+Shift+F` | 是 |
|
| 全局搜索 | `Ctrl+Shift+F` | 是 |
|
||||||
| 快照保存 | `Ctrl+Alt+S` | 是 |
|
| 快照保存 | `Ctrl+Alt+S` | 是 |
|
||||||
| 弹出灵感快速捕获窗 | `Ctrl+Shift+I` | 是 |
|
| 弹出灵感快速捕获窗 | `Ctrl+Shift+I` | 是 |
|
||||||
|
| 打开引用面板 | `Ctrl+Shift+P` | 是 |
|
||||||
|
| 插入写作地标 | `Ctrl+Shift+M` | 是 |
|
||||||
|
| 打开地标清单 | `Ctrl+Shift+L` | 是 |
|
||||||
| 导出当前书籍 | `Ctrl+Shift+E` | 是 |
|
| 导出当前书籍 | `Ctrl+Shift+E` | 是 |
|
||||||
| 关闭当前标签 | `Ctrl+W` | 是 |
|
| 关闭当前标签 | `Ctrl+W` | 是 |
|
||||||
| 朗读(播放/暂停) | `Ctrl+Shift+R` | 是 |
|
| 朗读(播放/暂停) | `Ctrl+Shift+R` | 是 |
|
||||||
@@ -277,15 +284,22 @@
|
|||||||
- **项目封面与简介**:可选,用于个人识别。
|
- **项目封面与简介**:可选,用于个人识别。
|
||||||
- **全书字数目标准**:可选设定总目标字数,用于进度推算。
|
- **全书字数目标准**:可选设定总目标字数,用于进度推算。
|
||||||
|
|
||||||
### 4.3 项目驾驶舱(首页)
|
### 4.3 项目驾驶舱
|
||||||
|
|
||||||
打开书籍后首先进入仪表盘视图,展示:
|
**进入逻辑**(作家友好优先):
|
||||||
|
|
||||||
|
- **首次打开某本书**:展示驾驶舱全屏模态(见 UI 顶栏「驾驶舱」按钮),帮助作者建立全书全局认知。
|
||||||
|
- **日常再次打开**:默认**直接恢复上次编辑位置**(章节 + 光标偏移),不打断写作心流;驾驶舱随时可通过顶栏按钮唤起。
|
||||||
|
- 可在书籍设置中切换为「每次打开均显示驾驶舱」(适合开书/planning 阶段)。
|
||||||
|
|
||||||
|
驾驶舱展示内容:
|
||||||
|
|
||||||
- **写作日历热力图**:记录每日写作字数,空白日灰色显示。
|
- **写作日历热力图**:记录每日写作字数,空白日灰色显示。
|
||||||
- **核心统计卡片**:今日字数、本周字数、全书总字数、距目标字数、预计完本日期(基于近30天日均速度估算)。
|
- **核心统计卡片**:今日字数、本周字数、全书总字数、距目标字数、预计完本日期(基于近30天日均速度估算)。
|
||||||
|
- **更新计划摘要**:日更/隔日更目标、存稿缓冲(已定稿未发布章节数)、连续达标天数(见 20.1)。
|
||||||
- **结构速览**:各卷字数占比饼图,可点击下钻。
|
- **结构速览**:各卷字数占比饼图,可点击下钻。
|
||||||
- **伏笔/角色状态摘要**:已埋伏笔数、已回收数、处于"遗忘"状态的伏笔数(超过20章未提及且未标记已回收)。
|
- **伏笔/角色状态摘要**:已埋伏笔数、已回收数、处于"遗忘"状态的伏笔数(超过20章未提及且未标记已回收)。
|
||||||
- **快速入口**:"回到上次编辑位置"、"处理被遗忘的伏笔"、"查看近期新增设定"、"今日大纲任务"。
|
- **快速入口**:"回到上次编辑位置"、"处理被遗忘的伏笔"、"查看近期新增设定"、"今日大纲任务"、"检查章间衔接"(见 20.4)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -331,6 +345,21 @@
|
|||||||
| E2E-BATCH-01 | E2E | Ctrl+点击选择第2、4、7章,批量改为"定稿" | 三章状态同步更新为定稿 |
|
| E2E-BATCH-01 | E2E | Ctrl+点击选择第2、4、7章,批量改为"定稿" | 三章状态同步更新为定稿 |
|
||||||
| E2E-BATCH-02 | E2E | Shift+点击选择5章,批量导出为 .txt | 生成包含5章内容的 .txt 文件 |
|
| E2E-BATCH-02 | E2E | Shift+点击选择5章,批量导出为 .txt | 生成包含5章内容的 .txt 文件 |
|
||||||
|
|
||||||
|
#### 5.2.4 章节元数据(作家视角)
|
||||||
|
|
||||||
|
每章除正文外,支持以下元数据(在章节右键菜单或章节属性侧栏编辑):
|
||||||
|
|
||||||
|
| 字段 | 说明 | 用途 |
|
||||||
|
|------|------|------|
|
||||||
|
| `pov_character_id` | 叙事视角人物(可选,关联设定角色) | 视角分布统计、AI 向导式写作默认视角 |
|
||||||
|
| `scene_count` | 场景数(自动检测 `* * *` 分隔符,可手动校正) | 节奏分析、单章结构把控 |
|
||||||
|
| `publish_status` | `draft` / `ready` / `published` | 存稿缓冲统计、更新计划追踪 |
|
||||||
|
| `platform_note` | 作者的话(独立于正文,导出时可选择是否包含) | 网文平台互动、读者说明 |
|
||||||
|
| `avg_sentence_length` | 本章平均句长(自动计算,只读) | 与全书文风对比 |
|
||||||
|
|
||||||
|
- 章节列表可按 `publish_status`、`pov_character_id`、自定义标签筛选。
|
||||||
|
- 拖拽重排章节后,若影响全局章节编号,弹出确认:「是否自动重编号?」(可选保留原编号仅改排序)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 5.3 章节编辑器
|
### 5.3 章节编辑器
|
||||||
@@ -399,6 +428,27 @@
|
|||||||
| E2E-SPELL-01 | E2E | 输入"他在在看书",开启拼写检查 | "在在"以蓝色波浪线标识 |
|
| E2E-SPELL-01 | E2E | 输入"他在在看书",开启拼写检查 | "在在"以蓝色波浪线标识 |
|
||||||
| E2E-TTS-01 | E2E | 点击朗读按钮 | 从光标位置开始朗读,当前句高亮,再次点击暂停 |
|
| E2E-TTS-01 | E2E | 点击朗读按钮 | 从光标位置开始朗读,当前句高亮,再次点击暂停 |
|
||||||
|
|
||||||
|
#### 5.3.8 引用面板(设定/大纲/灵感侧栏)
|
||||||
|
|
||||||
|
**入口**:编辑器工具栏「引用」按钮,或快捷键 `Ctrl+Shift+P`(Pin 面板,可自定义)。
|
||||||
|
|
||||||
|
**布局**:编辑器右侧(可与 AI 面板互斥显示,或窄屏时覆盖 AI 面板):
|
||||||
|
|
||||||
|
- 顶部 Tab:设定 / 大纲 / 灵感,支持跨 Tab 搜索。
|
||||||
|
- 列表项展示:名称 + 摘要前 40 字;点击展开详情。
|
||||||
|
- **钉住模式**:勾选后,切换章节时保持同一引用条目可见。
|
||||||
|
- **拖拽插入**:将设定描述、大纲要点拖入正文光标处,插入为纯文本(不带格式块)。
|
||||||
|
- **反向高亮**:正文中已 `@提及` 的设定名,点击可跳转设定详情(TipTap Mention 扩展)。
|
||||||
|
|
||||||
|
#### 5.3.9 写作地标与待办
|
||||||
|
|
||||||
|
**插入**:编辑器右键 →「插入地标」,或快捷键 `Ctrl+Shift+M`。
|
||||||
|
|
||||||
|
- 地标类型:`todo`(待写/待改)、`foreshadow`(伏笔提醒)、`research`(待查证)、`custom`。
|
||||||
|
- 地标在正文以行内标记显示(如 `[TODO: 补写林远与长老对话]`),不进入导出正文(除非用户勾选「包含地标」)。
|
||||||
|
- **地标清单**:章节侧栏底部「地标」按钮,或全局 `Ctrl+Shift+L` 打开清单面板,按类型/章节分组,点击跳转。
|
||||||
|
- 完成地标后勾选关闭,可选自动归档到章节摘要。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 5.4 版本历史与内容追溯
|
### 5.4 版本历史与内容追溯
|
||||||
@@ -450,14 +500,23 @@
|
|||||||
| E2E-COV-01 | E2E | 创建大纲条目不关联章节,查看大纲面板 | 条目显示橙色边框和警告图标 |
|
| E2E-COV-01 | E2E | 创建大纲条目不关联章节,查看大纲面板 | 条目显示橙色边框和警告图标 |
|
||||||
| E2E-COV-02 | E2E | 筛选"未覆盖" | 仅显示未关联章节的大纲条目 |
|
| E2E-COV-02 | E2E | 筛选"未覆盖" | 仅显示未关联章节的大纲条目 |
|
||||||
|
|
||||||
#### 5.5.4 设定管理
|
#### 5.5.6 大纲-正文对照视图
|
||||||
|
|
||||||
|
**入口**:大纲面板顶部「对照模式」按钮。
|
||||||
|
|
||||||
|
- 分屏布局:左 35% 大纲树(当前筛选结果),右 65% 关联章节正文片段。
|
||||||
|
- 选中大纲条目时,右侧滚动至对应章节段落;无关联时显示「尚未写作」占位。
|
||||||
|
- 底部对比条:预期字数 vs 实际字数 vs 偏差百分比(>20% 标红)。
|
||||||
|
- 支持从对照视图直接「生成章节骨架」或「跳转编辑器」。
|
||||||
|
|
||||||
|
#### 5.5.7 设定管理
|
||||||
|
|
||||||
- 分类组织:角色、地点、物品、技能、势力、自定义类别。
|
- 分类组织:角色、地点、物品、技能、势力、自定义类别。
|
||||||
- 每个设定条目:名称、描述、属性字段(可自定义)、关联章节列表(自动记录提及该设定的章节,点击跳转)。
|
- 每个设定条目:名称、描述、属性字段(可自定义)、关联章节列表(自动记录提及该设定的章节,点击跳转)。
|
||||||
- **写作时引用**:编辑器侧栏可调出设定面板,快速查看并拖拽内容。
|
- **写作时引用**:编辑器侧栏可调出设定面板,快速查看并拖拽内容。
|
||||||
- **反向链接**:设定条目页显示"提及章节"和"被AI用作上下文注入的轮次"。
|
- **反向链接**:设定条目页显示"提及章节"和"被AI用作上下文注入的轮次"。
|
||||||
|
|
||||||
#### 5.5.5 灵感碎片
|
#### 5.5.8 灵感碎片
|
||||||
|
|
||||||
- **快速捕获**:全局快捷键(`Ctrl+Shift+I`)呼出小窗,立即记录想法;支持语音输入转文字。
|
- **快速捕获**:全局快捷键(`Ctrl+Shift+I`)呼出小窗,立即记录想法;支持语音输入转文字。
|
||||||
- **灵感条目**:标题、内容、创建时间、标签(自定义)。
|
- **灵感条目**:标题、内容、创建时间、标签(自定义)。
|
||||||
@@ -470,10 +529,19 @@
|
|||||||
|
|
||||||
#### 5.6.1 全局搜索
|
#### 5.6.1 全局搜索
|
||||||
|
|
||||||
- 支持跨章节、大纲、设定、灵感全文搜索。
|
**入口**:`Ctrl+Shift+F` 打开全局搜索浮层(模态,不离开当前编辑上下文)。
|
||||||
|
|
||||||
|
- 支持跨章节、大纲、设定、灵感、地标全文搜索。
|
||||||
- 搜索结果:按类型分组,每条结果显示所在位置、前后20字上下文。
|
- 搜索结果:按类型分组,每条结果显示所在位置、前后20字上下文。
|
||||||
- 高级搜索:正则表达式支持、区分大小写、全词匹配。
|
- 高级搜索:正则表达式支持、区分大小写、全词匹配、限定分卷/发表状态。
|
||||||
- 替换功能:支持单次替换、批量替换,批量替换前展示预览列表并需确认。
|
- 替换功能:支持单次替换、批量替换,批量替换前展示预览列表并需确认。
|
||||||
|
- 搜索历史:保留最近 10 条,下拉快速复用。
|
||||||
|
|
||||||
|
**UI 规格**(见 `ui_pc.html` `#searchModal`):
|
||||||
|
|
||||||
|
- 顶部:搜索框 + 范围筛选(全书/当前卷/当前章)+ 高级选项折叠区。
|
||||||
|
- 中部:结果列表,支持键盘 ↑↓ 选择、Enter 跳转。
|
||||||
|
- 底部:替换区(可选展开),含「预览变更」按钮。
|
||||||
|
|
||||||
#### 5.6.2 命名一致性检查(AI驱动)
|
#### 5.6.2 命名一致性检查(AI驱动)
|
||||||
|
|
||||||
@@ -562,6 +630,10 @@
|
|||||||
|
|
||||||
- **独立会话**:每本书拥有独立的多个会话,支持新建、重命名、归档、导出(导出为`.txt`或`.md`对话记录)。
|
- **独立会话**:每本书拥有独立的多个会话,支持新建、重命名、归档、导出(导出为`.txt`或`.md`对话记录)。
|
||||||
- **会话列表**:显示会话摘要(模型、创建时间、最后活跃时间),归档会话折叠。
|
- **会话列表**:显示会话摘要(模型、创建时间、最后活跃时间),归档会话折叠。
|
||||||
|
- **UI 入口**(见 `ui_pc.html` AI 面板顶部):
|
||||||
|
- 会话下拉选择器 +「新建会话」按钮。
|
||||||
|
- 会话名可 inline 重命名;右键归档/导出/删除。
|
||||||
|
- 当前会话上下文摘要一行展示,点击展开「上下文编辑器」。
|
||||||
- **上下文管理**:
|
- **上下文管理**:
|
||||||
- **上下文源**:当前书籍的章节(可勾选范围)、大纲(可勾选条目)、设定(可勾选条目)、灵感(可勾选条目)、附件。
|
- **上下文源**:当前书籍的章节(可勾选范围)、大纲(可勾选条目)、设定(可勾选条目)、灵感(可勾选条目)、附件。
|
||||||
- **上下文注入策略**:创建会话时,根据勾选内容初始化;**提供"更新上下文"按钮**,点击后重新提取截至最新的章节摘要、大纲、设定,注入会话。在每轮对话前,系统自动检索最高重要度的知识条目(角色、伏笔等)注入,而非一次性凝固。
|
- **上下文注入策略**:创建会话时,根据勾选内容初始化;**提供"更新上下文"按钮**,点击后重新提取截至最新的章节摘要、大纲、设定,注入会话。在每轮对话前,系统自动检索最高重要度的知识条目(角色、伏笔等)注入,而非一次性凝固。
|
||||||
@@ -590,6 +662,17 @@
|
|||||||
|
|
||||||
### 6.5 AI 写作模式
|
### 6.5 AI 写作模式
|
||||||
|
|
||||||
|
**模式入口**(AI 面板输入框上方 Segmented Control):
|
||||||
|
|
||||||
|
| 模式 | 适用场景 | 入口标签 |
|
||||||
|
|------|----------|----------|
|
||||||
|
| 对话 | 自由问答、快捷命令 | 默认 |
|
||||||
|
| 交互式 | 逐场景共创、可控性最高 | 「交互写作」 |
|
||||||
|
| 自动 | 有大纲目标、希望 AI 连续成章 | 「自动写作」 |
|
||||||
|
| 向导 | 新手第一部作品 | 「向导模式」 |
|
||||||
|
|
||||||
|
切换模式时保留当前会话历史,但生成交互流程按所选模式重新初始化。
|
||||||
|
|
||||||
#### 6.5.1 交互式写作
|
#### 6.5.1 交互式写作
|
||||||
|
|
||||||
- **启动条件**:需存在至少1章已完成章节,或有大纲/设定内容,否则引导用户先创建。
|
- **启动条件**:需存在至少1章已完成章节,或有大纲/设定内容,否则引导用户先创建。
|
||||||
@@ -639,6 +722,10 @@
|
|||||||
|
|
||||||
- **每章生成后自动抽取**:角色状态变化、新埋伏笔、人际关系演变、新出现地点、关键事实。
|
- **每章生成后自动抽取**:角色状态变化、新埋伏笔、人际关系演变、新出现地点、关键事实。
|
||||||
- **抽取条目人工审核**:在"知识库面板"展示所有抽取项,用户可编辑、合并、删除、打回重抽。
|
- **抽取条目人工审核**:在"知识库面板"展示所有抽取项,用户可编辑、合并、删除、打回重抽。
|
||||||
|
- **审核工作流 UI**:
|
||||||
|
- 每条待审核项显示:来源章节、置信度、建议操作(采纳/编辑/忽略/合并到已有条目)。
|
||||||
|
- 批量操作:「全部采纳高置信度」「忽略本批」。
|
||||||
|
- 审核完成后条目进入正式知识库,影响后续 AI 注入优先级。
|
||||||
- **重要度标注**:用户可为每条知识打1-5星重要度,影响后续检索注入的优先级。
|
- **重要度标注**:用户可为每条知识打1-5星重要度,影响后续检索注入的优先级。
|
||||||
- **知识库全局视图**:
|
- **知识库全局视图**:
|
||||||
- 角色维度:当前状态、装备、关系网络(简易图谱)。
|
- 角色维度:当前状态、装备、关系网络(简易图谱)。
|
||||||
@@ -747,7 +834,9 @@
|
|||||||
|
|
||||||
- **写作速度仪表盘**:按小时统计平均写作速度,展示效率曲线和每日时段热力图。
|
- **写作速度仪表盘**:按小时统计平均写作速度,展示效率曲线和每日时段热力图。
|
||||||
- **习惯追踪**:连续写作天数、平均每次写作时长、最喜欢写作的时段。
|
- **习惯追踪**:连续写作天数、平均每次写作时长、最喜欢写作的时段。
|
||||||
- **字数分布**:按卷、按人物视角章节字数统计。
|
- **字数分布**:按卷、按人物视角(POV)章节字数统计。
|
||||||
|
- **入口**:驾驶舱内嵌卡片 + 书籍设置面板「数据分析」Tab;暂不做独立全屏页(避免功能分散)。
|
||||||
|
- **角色弧线简报**(轻量):选定主角后,展示「首次出场 → 关键转折 → 最近状态」三节时间线,数据来源于知识库角色状态变更记录(见 20.3)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1182,9 +1271,10 @@ graph TB
|
|||||||
{
|
{
|
||||||
"penName": "string",
|
"penName": "string",
|
||||||
"onboardingCompleted": false,
|
"onboardingCompleted": false,
|
||||||
"theme": "暗夜黑",
|
"theme": "default",
|
||||||
"language": "zh-CN",
|
"language": "zh-CN",
|
||||||
"dailyWordGoal": 0,
|
"dailyWordGoal": 0,
|
||||||
|
"updateSchedule": "daily",
|
||||||
"shortcuts": {},
|
"shortcuts": {},
|
||||||
"aiBackend": "cloud",
|
"aiBackend": "cloud",
|
||||||
"aiTimeout": 60,
|
"aiTimeout": 60,
|
||||||
@@ -1214,13 +1304,18 @@ CREATE TABLE chapters (
|
|||||||
title TEXT NOT NULL DEFAULT '',
|
title TEXT NOT NULL DEFAULT '',
|
||||||
content TEXT NOT NULL DEFAULT '',
|
content TEXT NOT NULL DEFAULT '',
|
||||||
status TEXT NOT NULL DEFAULT 'draft', -- draft/reviewing/final
|
status TEXT NOT NULL DEFAULT 'draft', -- draft/reviewing/final
|
||||||
|
publish_status TEXT NOT NULL DEFAULT 'draft', -- draft/ready/published
|
||||||
|
pov_character_id TEXT DEFAULT NULL, -- 叙事视角角色
|
||||||
|
platform_note TEXT DEFAULT '', -- 作者的话
|
||||||
word_count INTEGER NOT NULL DEFAULT 0,
|
word_count INTEGER NOT NULL DEFAULT 0,
|
||||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||||
summary TEXT DEFAULT '',
|
summary TEXT DEFAULT '',
|
||||||
story_time TEXT DEFAULT NULL, -- 故事时间标记(V2.0新增)
|
story_time TEXT DEFAULT NULL,
|
||||||
|
cursor_offset INTEGER DEFAULT 0, -- 上次编辑光标位置
|
||||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
FOREIGN KEY (volume_id) REFERENCES volumes(id) ON DELETE SET NULL
|
FOREIGN KEY (volume_id) REFERENCES volumes(id) ON DELETE SET NULL,
|
||||||
|
FOREIGN KEY (pov_character_id) REFERENCES settings(id) ON DELETE SET NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE chapter_tags (
|
CREATE TABLE chapter_tags (
|
||||||
@@ -1236,6 +1331,8 @@ CREATE TABLE bookmarks (
|
|||||||
chapter_id TEXT NOT NULL,
|
chapter_id TEXT NOT NULL,
|
||||||
position INTEGER NOT NULL, -- 字符偏移
|
position INTEGER NOT NULL, -- 字符偏移
|
||||||
label TEXT NOT NULL,
|
label TEXT NOT NULL,
|
||||||
|
landmark_type TEXT NOT NULL DEFAULT 'todo', -- todo/foreshadow/research/custom
|
||||||
|
resolved INTEGER NOT NULL DEFAULT 0,
|
||||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
FOREIGN KEY (chapter_id) REFERENCES chapters(id) ON DELETE CASCADE
|
FOREIGN KEY (chapter_id) REFERENCES chapters(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
@@ -1302,11 +1399,35 @@ CREATE TABLE ai_sessions (
|
|||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
name TEXT NOT NULL DEFAULT '',
|
name TEXT NOT NULL DEFAULT '',
|
||||||
model TEXT NOT NULL DEFAULT '',
|
model TEXT NOT NULL DEFAULT '',
|
||||||
|
mode TEXT NOT NULL DEFAULT 'chat', -- chat/interactive/auto/wizard
|
||||||
|
context_config TEXT DEFAULT '{}', -- JSON: 勾选的上下文源
|
||||||
archived INTEGER NOT NULL DEFAULT 0,
|
archived INTEGER NOT NULL DEFAULT 0,
|
||||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
last_active TEXT NOT NULL DEFAULT (datetime('now'))
|
last_active TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE ai_messages (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
session_id TEXT NOT NULL,
|
||||||
|
role TEXT NOT NULL, -- user/assistant/system
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
|
FOREIGN KEY (session_id) REFERENCES ai_sessions(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE knowledge_entries (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
type TEXT NOT NULL, -- character/foreshadow/location/relationship/fact
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
content TEXT DEFAULT '',
|
||||||
|
importance INTEGER NOT NULL DEFAULT 3, -- 1-5
|
||||||
|
status TEXT NOT NULL DEFAULT 'pending', -- pending/approved/rejected
|
||||||
|
source_chapter_id TEXT,
|
||||||
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
|
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
|
FOREIGN KEY (source_chapter_id) REFERENCES chapters(id) ON DELETE SET NULL
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE writing_logs (
|
CREATE TABLE writing_logs (
|
||||||
date TEXT NOT NULL,
|
date TEXT NOT NULL,
|
||||||
book_id TEXT NOT NULL,
|
book_id TEXT NOT NULL,
|
||||||
@@ -1330,3 +1451,78 @@ CREATE TABLE timeline_events (
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 20. 作家工作流专项设计(v1.1 新增)
|
||||||
|
|
||||||
|
> 本章从**日更连载作者**真实工作习惯出发,补全 v1.0 功能清单中「有概念无落地」或「作家强需求但未覆盖」的能力。
|
||||||
|
|
||||||
|
### 20.1 更新计划与存稿管理
|
||||||
|
|
||||||
|
长篇连载作者的核心焦虑不是「写不出来」,而是「断更」和「存稿不够」。
|
||||||
|
|
||||||
|
| 能力 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **更新频率目标** | 全局设置 `updateSchedule`:`daily`(日更)/ `alternate`(隔日更)/ `weekly`(周更)/ `none`;驾驶舱显示「距下次应发布还有 X 天」。 |
|
||||||
|
| **存稿缓冲** | 统计 `publish_status = ready` 的章节数;低于阈值(默认 3 章)时在状态栏软性提醒,不阻断写作。 |
|
||||||
|
| **发布标记** | 章节右键「标记为已发布」,自动记录发布日期;不影响定稿状态,二者独立。 |
|
||||||
|
| **日更目标** | 沿用 `dailyWordGoal`;顶栏进度条(UI 已实现)+ 设置页可配置。 |
|
||||||
|
|
||||||
|
### 20.2 平台投稿导出预设
|
||||||
|
|
||||||
|
- 在导出模态中增加「投稿预设」折叠区:
|
||||||
|
- **通用网文**:纯文本、章节标题 `# 第N章 标题`、段首空两格、UTF-8。
|
||||||
|
- **自定义预设**:用户保存标题格式、是否含作者的话、段落间距、敏感词替换规则。
|
||||||
|
- 导出前可选「复制到剪贴板」以便直接粘贴到平台后台(比下载文件更常用)。
|
||||||
|
- 首版不做平台 API 直连(YAGNI),仅优化「复制即用」体验。
|
||||||
|
|
||||||
|
### 20.3 角色弧线简报
|
||||||
|
|
||||||
|
- 从知识库角色状态变更自动聚合,无需作者手动维护时间线。
|
||||||
|
- 在设定面板角色详情页增加「弧线」Tab:关键节点列表 + 一键跳转对应章节。
|
||||||
|
- 驾驶舱「角色状态摘要」可下钻至弧线简报。
|
||||||
|
|
||||||
|
### 20.4 章间衔接检查
|
||||||
|
|
||||||
|
连载作者常见痛点:隔日续写时忘记上一章怎么收的。
|
||||||
|
|
||||||
|
- **触发**:打开新章编辑时(或手动点击驾驶舱「检查章间衔接」),若上一章存在且本章字数 < 200,自动展示衔接卡片:
|
||||||
|
- 上一章末 300 字(高亮)
|
||||||
|
- 本章已写开头(如有)
|
||||||
|
- AI 可选建议:「上一章悬念点」+「建议本章开头方向」(需 AI 可用,离线时仅展示文本)
|
||||||
|
- 卡片可关闭且「本章不再提示」,不强制打断。
|
||||||
|
|
||||||
|
### 20.5 敏感词与合规提示(可选开启)
|
||||||
|
|
||||||
|
- 设置 → 编辑器 →「合规检查」开关,默认关闭。
|
||||||
|
- 基于本地词库 + 用户自定义词库,对当前章做软性下划线提示(不阻断、不自动删改)。
|
||||||
|
- 导出时可选择「导出前扫描并确认」。
|
||||||
|
|
||||||
|
### 20.6 灵感快速捕获 UI
|
||||||
|
|
||||||
|
- 全局快捷键 `Ctrl+Shift+I` 唤起**全局置顶小窗**(见 `ui_pc.html` `#inspirationModal`):
|
||||||
|
- 单行/多行输入、可选标签、可选关联当前书籍。
|
||||||
|
- 支持语音输入转文字(系统 API)。
|
||||||
|
- 保存后 toast 确认,不打断当前编辑器焦点(保存后自动关闭,焦点回编辑器)。
|
||||||
|
|
||||||
|
### 20.7 设计审查清单(作家视角)
|
||||||
|
|
||||||
|
| 审查项 | v1.0 状态 | v1.1 处理 |
|
||||||
|
|--------|-----------|-----------|
|
||||||
|
| 主题命名 UI/文档不一致 | ❌ | ✅ 统一为七套文学意象主题 |
|
||||||
|
| 打开书籍强制进驾驶舱 | ⚠️ 打断心流 | ✅ 改为恢复上次编辑位,驾驶舱按需唤起 |
|
||||||
|
| 引用面板 | 文档有,UI 无 | ✅ 补 UI 规格与原型 |
|
||||||
|
| 全局搜索 | 快捷键有,UI 无 | ✅ 补 `#searchModal` 原型 |
|
||||||
|
| AI 多会话 | 文档有,UI 无 | ✅ 补会话选择器规格 |
|
||||||
|
| AI 写作模式切换 | 文档有,UI 无 | ✅ 补 Segmented Control 规格 |
|
||||||
|
| 地标/待办 | 文档简略 | ✅ 扩展类型与工作流 |
|
||||||
|
| 大纲-正文对照 | 文档有,UI 无 | ✅ 补入口与布局规格 |
|
||||||
|
| 导入书籍 | 主页按钮有,模态无 | ✅ 补 `#importModal` 原型 |
|
||||||
|
| 更新计划/存稿 | 未覆盖 | ✅ 新增 20.1 |
|
||||||
|
| 章间衔接 | 未覆盖 | ✅ 新增 20.4 |
|
||||||
|
| 平台投稿 | 仅通用导出 | ✅ 新增 20.2 |
|
||||||
|
| 知识库审核流 | 概念有,UI 弱 | ✅ 补审核工作流 |
|
||||||
|
| POV/发表状态 | 未覆盖 | ✅ 新增章节元数据 5.2.4 |
|
||||||
|
| i18n 首版范围 | UI 含繁体选项 | ✅ UI 移除繁体,保持 zh-CN / en |
|
||||||
|
| 笔名编辑 | 仅 onboarding | ✅ 通用设置增加笔名字段 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|||||||
+191
-4
@@ -1136,6 +1136,30 @@
|
|||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ============ V1.1 NEW UI ============ */
|
||||||
|
.ai-session-bar { display:flex;align-items:center;gap:6px;padding:8px 10px;border-bottom:1px solid var(--border);flex-shrink:0; }
|
||||||
|
.ai-session-bar select { flex:1;font-size:11px;padding:4px 8px;background:var(--bg-tertiary);color:#fff;border:1px solid var(--border);border-radius:4px; }
|
||||||
|
.ai-mode-tabs { display:flex;gap:2px;padding:6px 8px;border-bottom:1px solid var(--border);flex-shrink:0; }
|
||||||
|
.ai-mode-tab { flex:1;padding:5px 4px;font-size:10px;text-align:center;border-radius:4px;cursor:pointer;color:var(--text-muted);background:transparent;border:none;transition:var(--transition); }
|
||||||
|
.ai-mode-tab.active { background:var(--accent);color:#fff;font-weight:600; }
|
||||||
|
.context-preview { padding:6px 10px;font-size:10px;color:var(--text-muted);border-bottom:1px solid var(--border);cursor:pointer;flex-shrink:0; }
|
||||||
|
.context-preview:hover { color:var(--accent-light); }
|
||||||
|
#reference-panel { width:0;overflow:hidden;transition:width var(--transition-slow);border-left:1px solid var(--border);background:var(--bg-secondary);display:flex;flex-direction:column;flex-shrink:0; }
|
||||||
|
#reference-panel.open { width:240px; }
|
||||||
|
.ref-tabs { display:flex;border-bottom:1px solid var(--border); }
|
||||||
|
.ref-tab { flex:1;padding:8px 4px;font-size:10px;text-align:center;cursor:pointer;color:var(--text-muted);border:none;background:transparent; }
|
||||||
|
.ref-tab.active { color:var(--accent-light);border-bottom:2px solid var(--accent); }
|
||||||
|
.ref-list { flex:1;overflow-y:auto;padding:6px; }
|
||||||
|
.ref-item { padding:8px;margin-bottom:4px;background:var(--bg-tertiary);border-radius:var(--radius-sm);font-size:11px;cursor:grab;color:var(--text-secondary); }
|
||||||
|
.ref-item:hover { background:var(--bg-hover);color:#fff; }
|
||||||
|
.search-input-row { display:flex;gap:8px;margin-bottom:12px; }
|
||||||
|
.search-input-row input { flex:1;padding:10px 14px;border-radius:var(--radius-sm);border:1px solid var(--border);background:var(--bg-tertiary);color:#fff;font-size:13px; }
|
||||||
|
.search-result { padding:10px 12px;border-radius:var(--radius-sm);background:var(--bg-tertiary);margin-bottom:6px;cursor:pointer;font-size:12px; }
|
||||||
|
.search-result:hover { background:var(--bg-hover); }
|
||||||
|
.search-result .sr-type { font-size:10px;color:var(--accent-light);margin-bottom:4px; }
|
||||||
|
.search-result .sr-context { color:var(--text-muted);font-size:11px; }
|
||||||
|
.inspiration-capture textarea { width:100%;min-height:100px;padding:12px;border-radius:var(--radius-sm);border:1px solid var(--border);background:var(--bg-tertiary);color:#fff;font-size:13px;resize:vertical;font-family:var(--font-sans); }
|
||||||
|
|
||||||
/* ============ MODALS ============ */
|
/* ============ MODALS ============ */
|
||||||
.modal-overlay {
|
.modal-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -1764,15 +1788,17 @@
|
|||||||
<button onclick="showTemplateModal()" aria-label="模板快速新建"><i class="fa-solid fa-file-lines"></i> 模板新建</button>
|
<button onclick="showTemplateModal()" aria-label="模板快速新建"><i class="fa-solid fa-file-lines"></i> 模板新建</button>
|
||||||
<button onclick="addVolume()" aria-label="新建分卷"><i class="fa-solid fa-folder-plus"></i> 新卷</button>
|
<button onclick="addVolume()" aria-label="新建分卷"><i class="fa-solid fa-folder-plus"></i> 新卷</button>
|
||||||
<button onclick="showVersionModal()" aria-label="版本历史"><i class="fa-solid fa-clock-rotate-left"></i> 版本</button>
|
<button onclick="showVersionModal()" aria-label="版本历史"><i class="fa-solid fa-clock-rotate-left"></i> 版本</button>
|
||||||
|
<button onclick="showLandmarksModal()" aria-label="写作地标"><i class="fa-solid fa-map-pin"></i> 地标</button>
|
||||||
<button class="accent" onclick="enterReadingMode()" aria-label="阅读模式"><i class="fa-solid fa-book-open"></i> 阅读</button>
|
<button class="accent" onclick="enterReadingMode()" aria-label="阅读模式"><i class="fa-solid fa-book-open"></i> 阅读</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 大纲面板 -->
|
<!-- 大纲面板 -->
|
||||||
<div class="sidebar-panel" id="outline-panel" style="padding:4px;">
|
<div class="sidebar-panel" id="outline-panel" style="padding:4px;">
|
||||||
<div style="padding:4px 8px;display:flex;gap:4px;flex-shrink:0;">
|
<div style="padding:4px 8px;display:flex;gap:4px;flex-shrink:0;align-items:center;">
|
||||||
<button class="btn" style="font-size:10px;padding:3px 8px;background:var(--accent);color:#fff;">全部</button>
|
<button class="btn" style="font-size:10px;padding:3px 8px;background:var(--accent);color:#fff;">全部</button>
|
||||||
<button class="btn" style="font-size:10px;padding:3px 8px;">未覆盖</button>
|
<button class="btn" style="font-size:10px;padding:3px 8px;">未覆盖</button>
|
||||||
<button class="btn" style="font-size:10px;padding:3px 8px;">已覆盖</button>
|
<button class="btn" style="font-size:10px;padding:3px 8px;">已覆盖</button>
|
||||||
|
<button class="btn" style="font-size:10px;padding:3px 8px;margin-left:auto;" onclick="showToast('进入大纲-正文对照模式')" title="大纲-正文对照">⇔ 对照</button>
|
||||||
</div>
|
</div>
|
||||||
<div style="padding:4px 8px;font-size:10px;color:var(--text-muted);">大纲树(拖拽排序 · 点击关联章节)</div>
|
<div style="padding:4px 8px;font-size:10px;color:var(--text-muted);">大纲树(拖拽排序 · 点击关联章节)</div>
|
||||||
<div class="outline-item uncovered" title="预期 3000字 vs 实际 0字 — 偏差100%">⚠ 第一卷核心剧情线</div>
|
<div class="outline-item uncovered" title="预期 3000字 vs 实际 0字 — 偏差100%">⚠ 第一卷核心剧情线</div>
|
||||||
@@ -1838,6 +1864,7 @@
|
|||||||
<span class="sep"></span>
|
<span class="sep"></span>
|
||||||
<button class="tool-btn" id="btnSpellCheck" title="拼写检查" aria-label="拼写检查" onclick="toggleSpellCheck()"><i class="fa-solid fa-spell-check"></i></button>
|
<button class="tool-btn" id="btnSpellCheck" title="拼写检查" aria-label="拼写检查" onclick="toggleSpellCheck()"><i class="fa-solid fa-spell-check"></i></button>
|
||||||
<button class="tool-btn" id="btnTTS" title="朗读 Ctrl+Shift+R" aria-label="朗读" onclick="toggleTTS()"><i class="fa-solid fa-headphones"></i></button>
|
<button class="tool-btn" id="btnTTS" title="朗读 Ctrl+Shift+R" aria-label="朗读" onclick="toggleTTS()"><i class="fa-solid fa-headphones"></i></button>
|
||||||
|
<button class="tool-btn" id="btnReference" title="引用面板 Ctrl+Shift+P" aria-label="引用面板" onclick="toggleReferencePanel()"><i class="fa-solid fa-book-bookmark"></i></button>
|
||||||
<span class="sep"></span>
|
<span class="sep"></span>
|
||||||
<span class="ch-label">当前:第3章 · 入门考验</span>
|
<span class="ch-label">当前:第3章 · 入门考验</span>
|
||||||
<span class="word-count-display" id="wordCountDisplay" title="点击查看用词分析" onclick="showWordFreqInPanel()">2,847 字</span>
|
<span class="word-count-display" id="wordCountDisplay" title="点击查看用词分析" onclick="showWordFreqInPanel()">2,847 字</span>
|
||||||
@@ -1869,6 +1896,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- REFERENCE PANEL -->
|
||||||
|
<div id="reference-panel" aria-label="引用面板">
|
||||||
|
<div class="ref-tabs">
|
||||||
|
<button class="ref-tab active" onclick="switchRefTab('setting',this)">设定</button>
|
||||||
|
<button class="ref-tab" onclick="switchRefTab('outline',this)">大纲</button>
|
||||||
|
<button class="ref-tab" onclick="switchRefTab('inspiration',this)">灵感</button>
|
||||||
|
</div>
|
||||||
|
<div style="padding:6px 8px;border-bottom:1px solid var(--border);"><input type="text" placeholder="搜索引用..." style="width:100%;padding:6px 8px;font-size:11px;border-radius:4px;border:1px solid var(--border);background:var(--bg-tertiary);color:#fff;"></div>
|
||||||
|
<div class="ref-list" id="refList">
|
||||||
|
<div class="ref-item" draggable="true" onclick="showToast('展开:林远')">👤 林远 — 三纹灵根,主角</div>
|
||||||
|
<div class="ref-item" draggable="true" onclick="showToast('展开:测灵石')">📍 测灵石 — 青云宗测试法器</div>
|
||||||
|
<div class="ref-item" draggable="true" onclick="showToast('展开:入门考验大纲')">🗺 入门考验 — 预期3000字</div>
|
||||||
|
</div>
|
||||||
|
<div style="padding:8px;border-top:1px solid var(--border);font-size:10px;color:var(--text-muted);"><label><input type="checkbox"> 钉住当前条目</label></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- RIGHT PANEL -->
|
<!-- RIGHT PANEL -->
|
||||||
<div id="right-panel">
|
<div id="right-panel">
|
||||||
<div class="panel-tabs">
|
<div class="panel-tabs">
|
||||||
@@ -1879,6 +1922,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- AI Chat Panel -->
|
<!-- AI Chat Panel -->
|
||||||
<div class="panel-content active" id="ai-chat">
|
<div class="panel-content active" id="ai-chat">
|
||||||
|
<div class="ai-session-bar">
|
||||||
|
<select id="aiSessionSelect" aria-label="AI会话选择" onchange="showToast('切换会话:'+this.options[this.selectedIndex].text)">
|
||||||
|
<option>第3章续写讨论</option>
|
||||||
|
<option>角色命名 brainstorm</option>
|
||||||
|
<option>第二卷大纲规划</option>
|
||||||
|
</select>
|
||||||
|
<button class="btn" style="font-size:10px;padding:4px 8px;" onclick="showToast('新建AI会话')" aria-label="新建会话">+</button>
|
||||||
|
</div>
|
||||||
|
<div class="ai-mode-tabs" role="tablist">
|
||||||
|
<button class="ai-mode-tab active" onclick="setAiMode('chat',this)">对话</button>
|
||||||
|
<button class="ai-mode-tab" onclick="setAiMode('interactive',this)">交互写作</button>
|
||||||
|
<button class="ai-mode-tab" onclick="setAiMode('auto',this)">自动写作</button>
|
||||||
|
<button class="ai-mode-tab" onclick="setAiMode('wizard',this)">向导</button>
|
||||||
|
</div>
|
||||||
|
<div class="context-preview" onclick="showToast('展开上下文编辑器:第1-2章摘要、大纲3条、设定7个')" title="点击编辑上下文">📎 上下文:第1-2章摘要 · 大纲3条 · 设定7个 · <span style="color:var(--accent-light)">更新</span></div>
|
||||||
<div class="offline-banner" id="offlineBanner">⚠ 当前网络不可用,已切换为离线模式。请配置本地模型或连接网络。</div>
|
<div class="offline-banner" id="offlineBanner">⚠ 当前网络不可用,已切换为离线模式。请配置本地模型或连接网络。</div>
|
||||||
<div class="chat-messages" id="chatMessages">
|
<div class="chat-messages" id="chatMessages">
|
||||||
<div class="chat-msg ai">
|
<div class="chat-msg ai">
|
||||||
@@ -1983,10 +2041,20 @@
|
|||||||
<h2>⚙ 通用设置</h2>
|
<h2>⚙ 通用设置</h2>
|
||||||
<div class="setting-block">
|
<div class="setting-block">
|
||||||
<h4>语言与外观</h4>
|
<h4>语言与外观</h4>
|
||||||
<div class="setting-item"><span class="setting-label">界面语言</span><select><option>简体中文</option><option>繁體中文</option><option>English</option></select></div>
|
<div class="setting-item"><span class="setting-label">界面语言</span><select aria-label="界面语言"><option>简体中文</option><option>English</option></select></div>
|
||||||
<div class="setting-item"><span class="setting-label">主题</span><select id="themeSelect" onchange="switchTheme(this.value)" aria-label="主题选择"><option value="default">墨砚(默认)</option><option value="bamboo">竹语</option><option value="moonlit">月夜</option><option value="ricepaper">宣纸</option><option value="mist">晨雾</option><option value="teagarden">茶园</option><option value="twilight">暮光</option></select></div>
|
<div class="setting-item"><span class="setting-label">主题</span><select id="themeSelect" onchange="switchTheme(this.value)" aria-label="主题选择"><option value="default">墨砚(默认)</option><option value="bamboo">竹语</option><option value="moonlit">月夜</option><option value="ricepaper">宣纸</option><option value="mist">晨雾</option><option value="teagarden">茶园</option><option value="twilight">暮光</option></select></div>
|
||||||
<div class="setting-item"><span class="setting-label">字体大小</span><select><option>中</option><option>小</option><option>大</option></select></div>
|
<div class="setting-item"><span class="setting-label">字体大小</span><select><option>中</option><option>小</option><option>大</option></select></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="setting-block">
|
||||||
|
<h4>作者信息</h4>
|
||||||
|
<div class="setting-item"><span class="setting-label">笔名</span><input type="text" value="山海" placeholder="至少2个字符" style="width:160px;" aria-label="笔名"></div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-block">
|
||||||
|
<h4>写作目标</h4>
|
||||||
|
<div class="setting-item"><span class="setting-label">日更字数目标</span><input type="number" value="3000" min="0" step="500" aria-label="日更目标">字</div>
|
||||||
|
<div class="setting-item"><span class="setting-label">更新频率</span><select aria-label="更新频率"><option>日更</option><option>隔日更</option><option>周更</option><option>不设定</option></select></div>
|
||||||
|
<div class="setting-item"><span class="setting-label">存稿预警阈值</span><input type="number" value="3" min="1" max="30" aria-label="存稿阈值">章</div>
|
||||||
|
</div>
|
||||||
<div class="setting-block">
|
<div class="setting-block">
|
||||||
<h4>默认行为</h4>
|
<h4>默认行为</h4>
|
||||||
<div class="setting-item"><span class="setting-label">启动时打开上次书籍</span><select><option>是</option><option>否(显示主页)</option></select></div>
|
<div class="setting-item"><span class="setting-label">启动时打开上次书籍</span><select><option>是</option><option>否(显示主页)</option></select></div>
|
||||||
@@ -2001,6 +2069,7 @@
|
|||||||
<div class="setting-item"><span class="setting-label">默认字体</span><select><option>宋体</option><option>楷体</option><option>微软雅黑</option></select></div>
|
<div class="setting-item"><span class="setting-label">默认字体</span><select><option>宋体</option><option>楷体</option><option>微软雅黑</option></select></div>
|
||||||
<div class="setting-item"><span class="setting-label">行距</span><select><option>1.8</option><option>2.0</option><option>2.2</option></select></div>
|
<div class="setting-item"><span class="setting-label">行距</span><select><option>1.8</option><option>2.0</option><option>2.2</option></select></div>
|
||||||
<div class="setting-item"><span class="setting-label">专注模式背景</span><select><option>纯黑</option><option>深灰</option><option>渐变</option></select></div>
|
<div class="setting-item"><span class="setting-label">专注模式背景</span><select><option>纯黑</option><option>深灰</option><option>渐变</option></select></div>
|
||||||
|
<div class="setting-item"><span class="setting-label">合规检查(敏感词提示)</span><select><option>关闭</option><option>开启</option></select></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- AI设置 -->
|
<!-- AI设置 -->
|
||||||
@@ -2031,6 +2100,8 @@
|
|||||||
<div class="setting-item"><span class="setting-label">全局搜索</span><span class="shortcut-key" onclick="recordShortcut(this)">Ctrl+Shift+F</span></div>
|
<div class="setting-item"><span class="setting-label">全局搜索</span><span class="shortcut-key" onclick="recordShortcut(this)">Ctrl+Shift+F</span></div>
|
||||||
<div class="setting-item"><span class="setting-label">朗读 播放/暂停</span><span class="shortcut-key" onclick="recordShortcut(this)">Ctrl+Shift+R</span></div>
|
<div class="setting-item"><span class="setting-label">朗读 播放/暂停</span><span class="shortcut-key" onclick="recordShortcut(this)">Ctrl+Shift+R</span></div>
|
||||||
<div class="setting-item"><span class="setting-label">灵感快速捕获</span><span class="shortcut-key" onclick="recordShortcut(this)">Ctrl+Shift+I</span></div>
|
<div class="setting-item"><span class="setting-label">灵感快速捕获</span><span class="shortcut-key" onclick="recordShortcut(this)">Ctrl+Shift+I</span></div>
|
||||||
|
<div class="setting-item"><span class="setting-label">引用面板</span><span class="shortcut-key" onclick="recordShortcut(this)">Ctrl+Shift+P</span></div>
|
||||||
|
<div class="setting-item"><span class="setting-label">写作地标</span><span class="shortcut-key" onclick="recordShortcut(this)">Ctrl+Shift+M</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 备份与同步 -->
|
<!-- 备份与同步 -->
|
||||||
@@ -2171,6 +2242,11 @@
|
|||||||
<div style="margin-top:16px;"><h4 style="font-size:12px;color:var(--text-muted);margin-bottom:8px;">📅 写作热力图(近30天)</h4>
|
<div style="margin-top:16px;"><h4 style="font-size:12px;color:var(--text-muted);margin-bottom:8px;">📅 写作热力图(近30天)</h4>
|
||||||
<div style="display:flex;flex-wrap:wrap;gap:2px;" id="heatmapContainer"></div>
|
<div style="display:flex;flex-wrap:wrap;gap:2px;" id="heatmapContainer"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div style="margin-top:16px;display:flex;gap:8px;flex-wrap:wrap;">
|
||||||
|
<button class="btn primary" style="font-size:11px;" onclick="closeModal('dashboardModal');showToast('回到上次编辑位置')">↩ 回到上次编辑</button>
|
||||||
|
<button class="btn" style="font-size:11px;" onclick="closeModal('dashboardModal');showBridgeModal()">🔗 检查章间衔接</button>
|
||||||
|
<button class="btn" style="font-size:11px;" onclick="closeModal('dashboardModal');switchRightPanel('knowledge',document.querySelector('.panel-tab:nth-child(2)'))">⚠ 处理遗忘伏笔</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -2222,6 +2298,7 @@
|
|||||||
<div class="export-opt" onclick="showToast('导出为 .docx');closeModal('exportModal')"><span class="export-icon">📝</span><div class="export-info"><span class="export-name">Word 文档 .docx</span><span class="export-desc">保留格式,自动生成目录</span></div></div>
|
<div class="export-opt" onclick="showToast('导出为 .docx');closeModal('exportModal')"><span class="export-icon">📝</span><div class="export-info"><span class="export-name">Word 文档 .docx</span><span class="export-desc">保留格式,自动生成目录</span></div></div>
|
||||||
<div class="export-opt" onclick="showToast('导出为 .md');closeModal('exportModal')"><span class="export-icon">📋</span><div class="export-info"><span class="export-name">Markdown .md</span><span class="export-desc">适合发布到网络平台</span></div></div>
|
<div class="export-opt" onclick="showToast('导出为 .md');closeModal('exportModal')"><span class="export-icon">📋</span><div class="export-info"><span class="export-name">Markdown .md</span><span class="export-desc">适合发布到网络平台</span></div></div>
|
||||||
<div class="export-opt" onclick="showToast('导出为 PDF (A4模板)');closeModal('exportModal')"><span class="export-icon">📕</span><div class="export-info"><span class="export-name">PDF 文档 .pdf</span><span class="export-desc">A4/信纸排版,页眉含书名+章节名+页码</span></div></div>
|
<div class="export-opt" onclick="showToast('导出为 PDF (A4模板)');closeModal('exportModal')"><span class="export-icon">📕</span><div class="export-info"><span class="export-name">PDF 文档 .pdf</span><span class="export-desc">A4/信纸排版,页眉含书名+章节名+页码</span></div></div>
|
||||||
|
<div class="export-opt" onclick="showToast('已复制到剪贴板(网文投稿格式)');closeModal('exportModal')"><span class="export-icon">📋</span><div class="export-info"><span class="export-name">复制到剪贴板</span><span class="export-desc">网文投稿预设:段首空两格、含章节标题</span></div></div>
|
||||||
<div class="export-opt" onclick="showToast('打包为 .novel 项目包');closeModal('exportModal')"><span class="export-icon">📦</span><div class="export-info"><span class="export-name">笔临项目包 .novel</span><span class="export-desc">整书数据打包,含版本历史+设定+大纲</span></div></div>
|
<div class="export-opt" onclick="showToast('打包为 .novel 项目包');closeModal('exportModal')"><span class="export-icon">📦</span><div class="export-info"><span class="export-name">笔临项目包 .novel</span><span class="export-desc">整书数据打包,含版本历史+设定+大纲</span></div></div>
|
||||||
</div></div>
|
</div></div>
|
||||||
</div></div>
|
</div></div>
|
||||||
@@ -2244,6 +2321,83 @@
|
|||||||
<div class="modal-footer"><button class="btn" onclick="closeModal('templateModal')">取消</button><button class="btn primary" onclick="applyTemplate('generic');closeModal('templateModal')">创建章节</button></div>
|
<div class="modal-footer"><button class="btn" onclick="closeModal('templateModal')">取消</button><button class="btn primary" onclick="applyTemplate('generic');closeModal('templateModal')">创建章节</button></div>
|
||||||
</div></div>
|
</div></div>
|
||||||
|
|
||||||
|
<!-- Global Search -->
|
||||||
|
<div class="modal-overlay hidden" id="searchModal" role="dialog" aria-modal="true" aria-label="全局搜索">
|
||||||
|
<div class="modal" style="max-width:680px;">
|
||||||
|
<div class="modal-header"><h3>🔍 全局搜索</h3><button class="modal-close" onclick="closeModal('searchModal')" aria-label="关闭">✕</button></div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="search-input-row">
|
||||||
|
<input type="text" id="searchInput" placeholder="搜索章节、大纲、设定、灵感、地标..." autofocus>
|
||||||
|
<select style="padding:8px;font-size:11px;background:var(--bg-tertiary);color:#fff;border:1px solid var(--border);border-radius:4px;" aria-label="搜索范围"><option>全书</option><option>当前卷</option><option>当前章</option></select>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;gap:8px;margin-bottom:12px;font-size:11px;">
|
||||||
|
<label><input type="checkbox"> 正则</label><label><input type="checkbox"> 全词匹配</label><label><input type="checkbox"> 区分大小写</label>
|
||||||
|
</div>
|
||||||
|
<div id="searchResults">
|
||||||
|
<div class="search-result" onclick="showToast('跳转到第3章');closeModal('searchModal')"><div class="sr-type">章节 · 第3章 入门考验</div><div>...林远深吸一口气,推开木门走了出去。门外是一个宽阔的<b>演武场</b>...</div></div>
|
||||||
|
<div class="search-result" onclick="showToast('跳转到设定:测灵石');closeModal('searchModal')"><div class="sr-type">设定 · 测灵石</div><div class="sr-context">青云宗测试弟子灵根的法器,可显现一至五纹...</div></div>
|
||||||
|
<div class="search-result" onclick="showToast('跳转到地标');closeModal('searchModal')"><div class="sr-type">地标 · 第5章 TODO</div><div class="sr-context">[TODO: 补写林远与长老的私下对话]</div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer"><button class="btn" onclick="closeModal('searchModal')">关闭</button><button class="btn" onclick="showToast('展开替换预览')">替换...</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Import Book -->
|
||||||
|
<div class="modal-overlay hidden" id="importModal" role="dialog" aria-modal="true" aria-label="导入书籍">
|
||||||
|
<div class="modal" style="max-width:520px;">
|
||||||
|
<div class="modal-header"><h3>📥 导入书籍</h3><button class="modal-close" onclick="closeModal('importModal')" aria-label="关闭">✕</button></div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p style="color:var(--text-secondary);font-size:12px;margin-bottom:12px;">支持 .txt、.docx、.md 及笔临专用 .novel 项目包</p>
|
||||||
|
<div class="form-group"><label>选择文件</label><button class="btn primary" style="width:100%;padding:12px;" onclick="showToast('打开文件选择器...')"><i class="fa-solid fa-folder-open"></i> 浏览文件...</button></div>
|
||||||
|
<div class="form-group"><label>章节分隔规则</label><select style="width:100%;padding:8px;background:var(--bg-tertiary);color:#fff;border:1px solid var(--border);border-radius:4px;"><option>自动识别(# 第.*章)</option><option>双换行分段</option><option>自定义正则</option></select></div>
|
||||||
|
<p style="font-size:11px;color:var(--text-muted);margin-top:8px;">大于 20MB 的文件将提示确认,导入过程显示进度条。</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer"><button class="btn" onclick="closeModal('importModal')">取消</button><button class="btn primary" onclick="showToast('📥 开始导入...');closeModal('importModal')">开始导入</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Inspiration Capture -->
|
||||||
|
<div class="modal-overlay hidden" id="inspirationModal" role="dialog" aria-modal="true" aria-label="灵感快速捕获">
|
||||||
|
<div class="modal" style="max-width:480px;">
|
||||||
|
<div class="modal-header"><h3>💡 灵感快速捕获</h3><button class="modal-close" onclick="closeModal('inspirationModal')" aria-label="关闭">✕</button></div>
|
||||||
|
<div class="modal-body inspiration-capture">
|
||||||
|
<textarea id="inspirationInput" placeholder="记录一闪而过的想法..."></textarea>
|
||||||
|
<div class="form-group" style="margin-top:10px;"><label>标签(可选)</label><input type="text" placeholder="如:伏笔、第三卷、角色" style="width:100%;padding:8px;border-radius:4px;border:1px solid var(--border);background:var(--bg-tertiary);color:#fff;"></div>
|
||||||
|
<div class="form-group"><label>关联书籍</label><select style="width:100%;padding:8px;background:var(--bg-tertiary);color:#fff;border:1px solid var(--border);border-radius:4px;"><option>仙道长青(当前)</option><option>不关联书籍</option></select></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer"><button class="btn" onclick="showToast('🎤 语音输入...')"><i class="fa-solid fa-microphone"></i> 语音</button><button class="btn" onclick="closeModal('inspirationModal')">取消</button><button class="btn primary" onclick="saveInspiration()">保存</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Landmarks List -->
|
||||||
|
<div class="modal-overlay hidden" id="landmarksModal" role="dialog" aria-modal="true" aria-label="写作地标清单">
|
||||||
|
<div class="modal" style="max-width:560px;">
|
||||||
|
<div class="modal-header"><h3>📌 写作地标与待办</h3><button class="modal-close" onclick="closeModal('landmarksModal')" aria-label="关闭">✕</button></div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div style="display:flex;gap:6px;margin-bottom:12px;"><button class="btn" style="font-size:10px;background:var(--accent);color:#fff;">全部</button><button class="btn" style="font-size:10px;">待办</button><button class="btn" style="font-size:10px;">伏笔</button><button class="btn" style="font-size:10px;">待查证</button></div>
|
||||||
|
<div class="search-result" onclick="showToast('跳转到第5章地标');closeModal('landmarksModal')"><div class="sr-type">TODO · 第5章</div><div>补写林远与长老的私下对话</div></div>
|
||||||
|
<div class="search-result" onclick="showToast('跳转到第3章');closeModal('landmarksModal')"><div class="sr-type">伏笔提醒 · 第3章</div><div>测灵石异常发热 — 需在后续章节回收</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer"><button class="btn" onclick="closeModal('landmarksModal')">关闭</button><button class="btn primary" onclick="showToast('插入新地标');closeModal('landmarksModal')">+ 插入地标</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Chapter Bridge (衔接检查) -->
|
||||||
|
<div class="modal-overlay hidden" id="bridgeModal" role="dialog" aria-modal="true" aria-label="章间衔接检查">
|
||||||
|
<div class="modal" style="max-width:600px;">
|
||||||
|
<div class="modal-header"><h3>🔗 章间衔接 — 第2章 → 第3章</h3><button class="modal-close" onclick="closeModal('bridgeModal')" aria-label="关闭">✕</button></div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<h4 style="font-size:12px;color:var(--text-muted);margin-bottom:6px;">上一章末尾(第2章)</h4>
|
||||||
|
<div style="padding:12px;background:var(--bg-tertiary);border-radius:var(--radius-sm);font-size:12px;line-height:1.7;margin-bottom:12px;border-left:3px solid var(--accent);">...三纹灵根觉醒的异象尚未散去,林远站在原地,感受着体内翻涌的灵力。他知道,从这一刻起,一切都将不同。</div>
|
||||||
|
<h4 style="font-size:12px;color:var(--text-muted);margin-bottom:6px;">本章已写开头(第3章)</h4>
|
||||||
|
<div style="padding:12px;background:var(--bg-tertiary);border-radius:var(--radius-sm);font-size:12px;line-height:1.7;margin-bottom:12px;">晨光透过稀疏的竹帘,在青石地面上投下细碎的光斑。林远深吸一口气,推开木门走了出去...</div>
|
||||||
|
<div style="padding:10px;background:var(--accent-glow);border-radius:var(--radius-sm);font-size:11px;color:var(--text-secondary);">💡 AI 建议:上一章以「一切都将不同」收束,本章可从时间跳转(数日后抵达宗门)或接续同一日清晨出发,避免重复「深吸一口气」式开头。</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer"><label style="font-size:11px;color:var(--text-muted);margin-right:auto;"><input type="checkbox"> 本章不再提示</label><button class="btn primary" onclick="closeModal('bridgeModal')">开始写作</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Onboarding Wizard -->
|
<!-- Onboarding Wizard -->
|
||||||
<div class="modal-overlay hidden" id="onboardingModal"><div class="modal" style="max-width:560px;">
|
<div class="modal-overlay hidden" id="onboardingModal"><div class="modal" style="max-width:560px;">
|
||||||
<div class="modal-header"><h3>✒ 欢迎使用笔临</h3></div>
|
<div class="modal-header"><h3>✒ 欢迎使用笔临</h3></div>
|
||||||
@@ -2430,7 +2584,36 @@
|
|||||||
showToast('✅ 书籍已创建:' + name);
|
showToast('✅ 书籍已创建:' + name);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showImportModal() { showToast('📥 导入功能:支持.txt/.docx/.md及.novel项目包。大于20MB会提示确认。'); }
|
function showImportModal() { document.getElementById('importModal').classList.remove('hidden'); }
|
||||||
|
function showSearchModal() { document.getElementById('searchModal').classList.remove('hidden'); setTimeout(function() { var i = document.getElementById('searchInput'); if (i) i.focus(); }, 100); }
|
||||||
|
function showInspirationModal() { document.getElementById('inspirationModal').classList.remove('hidden'); setTimeout(function() { var i = document.getElementById('inspirationInput'); if (i) i.focus(); }, 100); }
|
||||||
|
function showLandmarksModal() { document.getElementById('landmarksModal').classList.remove('hidden'); }
|
||||||
|
function showBridgeModal() { document.getElementById('bridgeModal').classList.remove('hidden'); }
|
||||||
|
function saveInspiration() {
|
||||||
|
var t = document.getElementById('inspirationInput').value.trim();
|
||||||
|
if (!t) { showToast('请输入灵感内容'); return; }
|
||||||
|
closeModal('inspirationModal');
|
||||||
|
document.getElementById('inspirationInput').value = '';
|
||||||
|
showToast('💡 灵感已保存');
|
||||||
|
}
|
||||||
|
function toggleReferencePanel() {
|
||||||
|
var panel = document.getElementById('reference-panel');
|
||||||
|
var btn = document.getElementById('btnReference');
|
||||||
|
panel.classList.toggle('open');
|
||||||
|
if (btn) btn.classList.toggle('active', panel.classList.contains('open'));
|
||||||
|
showToast(panel.classList.contains('open') ? '📎 引用面板已打开' : '引用面板已关闭');
|
||||||
|
}
|
||||||
|
function switchRefTab(type, el) {
|
||||||
|
document.querySelectorAll('.ref-tab').forEach(function(t) { t.classList.remove('active'); });
|
||||||
|
el.classList.add('active');
|
||||||
|
showToast('引用:' + (type === 'setting' ? '设定' : type === 'outline' ? '大纲' : '灵感'));
|
||||||
|
}
|
||||||
|
function setAiMode(mode, el) {
|
||||||
|
document.querySelectorAll('.ai-mode-tab').forEach(function(t) { t.classList.remove('active'); });
|
||||||
|
el.classList.add('active');
|
||||||
|
var labels = { chat: '对话', interactive: '交互写作', auto: '自动写作', wizard: '向导模式' };
|
||||||
|
showToast('AI 模式:' + (labels[mode] || mode));
|
||||||
|
}
|
||||||
|
|
||||||
// ============ SIDEBAR ============
|
// ============ SIDEBAR ============
|
||||||
function switchSidebarPanel(panelId, btn) {
|
function switchSidebarPanel(panelId, btn) {
|
||||||
@@ -2604,7 +2787,11 @@
|
|||||||
if (e.ctrlKey && e.altKey && e.key === 'f') { e.preventDefault(); toggleFocusMode(); }
|
if (e.ctrlKey && e.altKey && e.key === 'f') { e.preventDefault(); toggleFocusMode(); }
|
||||||
if (e.ctrlKey && e.shiftKey && e.key === 'R') { e.preventDefault(); toggleTTS(); }
|
if (e.ctrlKey && e.shiftKey && e.key === 'R') { e.preventDefault(); toggleTTS(); }
|
||||||
if (e.ctrlKey && e.shiftKey && e.key === 'N') { e.preventDefault(); addChapter(); }
|
if (e.ctrlKey && e.shiftKey && e.key === 'N') { e.preventDefault(); addChapter(); }
|
||||||
if (e.ctrlKey && e.shiftKey && e.key === 'I') { e.preventDefault(); showToast('💡 灵感快速捕获窗已打开'); }
|
if (e.ctrlKey && e.shiftKey && e.key === 'F') { e.preventDefault(); showSearchModal(); }
|
||||||
|
if (e.ctrlKey && e.shiftKey && e.key === 'I') { e.preventDefault(); showInspirationModal(); }
|
||||||
|
if (e.ctrlKey && e.shiftKey && e.key === 'P') { e.preventDefault(); toggleReferencePanel(); }
|
||||||
|
if (e.ctrlKey && e.shiftKey && e.key === 'M') { e.preventDefault(); showLandmarksModal(); }
|
||||||
|
if (e.ctrlKey && e.shiftKey && e.key === 'L') { e.preventDefault(); showLandmarksModal(); }
|
||||||
});
|
});
|
||||||
|
|
||||||
// ============ V2.0 NEW FUNCTIONS ============
|
// ============ V2.0 NEW FUNCTIONS ============
|
||||||
|
|||||||
@@ -0,0 +1,550 @@
|
|||||||
|
# 笔临 P0/P1 设计规格
|
||||||
|
|
||||||
|
**版本**:1.0
|
||||||
|
**日期**:2026-07-05
|
||||||
|
**范围**:Electron + React 全量实现的第一阶段(地基 + 写作核心)
|
||||||
|
**参考**:`design/readme.md` v1.1、`design/ui_pc.html`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 背景与目标
|
||||||
|
|
||||||
|
### 1.1 产品定位
|
||||||
|
|
||||||
|
笔临是面向长篇连载作者的桌面写作平台,以「人本创作」为核心:AI 辅助可选,作者保留绝对控制权。本项目从设计文档与 UI 原型出发,实现完整桌面应用;交付采用分阶段策略,最终覆盖设计文档全部 20 章功能。
|
||||||
|
|
||||||
|
### 1.2 本规格范围(P0 + P1)
|
||||||
|
|
||||||
|
| 阶段 | 目标 | 预估周期 |
|
||||||
|
|------|------|----------|
|
||||||
|
| **P0 地基** | 可启动、可持久化设置、主题/i18n/Onboarding/快捷键 | ~1 周 |
|
||||||
|
| **P1 写作核心** | 多书管理、分卷章节、TipTap 编辑、自动保存、字数统计 | ~2 周 |
|
||||||
|
|
||||||
|
**P1 验收标准**:用户能完成「首次引导 → 创建书籍 → 分卷分章 → 富文本写作 → 自动保存 → 重启后内容恢复」。AI 与高级面板为 UI 占位,不实现业务逻辑。
|
||||||
|
|
||||||
|
### 1.3 已确认决策
|
||||||
|
|
||||||
|
| 维度 | 决策 |
|
||||||
|
|------|------|
|
||||||
|
| 总体目标 | 全量实现,分 P0–P7 阶段交付 |
|
||||||
|
| P1 演示重点 | 纯写作体验,AI/高级功能占位 |
|
||||||
|
| UI 策略 | **混合方案 C**:布局/主题/编辑器区沿用 `ui_pc.html` CSS;模态/表单等用 Radix UI |
|
||||||
|
| 目标平台 | **Windows 优先**(Win 10/11),macOS/Linux 在 P7 适配 |
|
||||||
|
| 状态管理 | **Zustand**(全局域)+ **Jotai**(编辑器高频局部状态) |
|
||||||
|
| 外围体验 | 与设计文档对齐:7 主题 + 双语 i18n + Onboarding + 可自定义快捷键 |
|
||||||
|
| 工程结构 | **electron-vite 单体仓库**(方案 A) |
|
||||||
|
|
||||||
|
### 1.4 后续阶段预览(本规格不实现)
|
||||||
|
|
||||||
|
| 阶段 | 范围 |
|
||||||
|
|------|------|
|
||||||
|
| P2 | 大纲/设定/灵感持久化、引用面板、全局搜索、版本历史 |
|
||||||
|
| P3 | AI 会话、对话模式、上下文注入、离线降级 |
|
||||||
|
| P4 | 交互/自动/向导写作、知识抽取、文风学习 |
|
||||||
|
| P5 | 驾驶舱、地标、章间衔接、更新计划、章节模板 |
|
||||||
|
| P6 | 关系图谱、时间线、用词分析、连续阅读 |
|
||||||
|
| P7 | 导入导出、云同步、自动更新、崩溃报告、跨平台打包 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 架构
|
||||||
|
|
||||||
|
### 2.1 进程模型
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ 主进程 (Node.js) │
|
||||||
|
│ · better-sqlite3(每书一个 .sqlite) │
|
||||||
|
│ · global_settings.json / books_registry.json │
|
||||||
|
│ · IPC Handlers(IpcResult 信封) │
|
||||||
|
│ · globalShortcut 快捷键 │
|
||||||
|
└───────────────────────────┬─────────────────────────────┘
|
||||||
|
│ contextBridge
|
||||||
|
┌───────────────────────────▼─────────────────────────────┐
|
||||||
|
│ 渲染进程 (React 18 + TypeScript) │
|
||||||
|
│ · Zustand Stores(书籍/Tab/设置) │
|
||||||
|
│ · Jotai Atoms(编辑器 dirty/字数/光标) │
|
||||||
|
│ · TipTap 编辑器 │
|
||||||
|
│ · i18next + 7 主题 CSS 变量 │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**约束**:
|
||||||
|
- 禁止 `nodeIntegration`;所有 DB/FS 操作经 IPC
|
||||||
|
- 所有 IPC 返回值使用 `IpcResult<T>`(见 §6)
|
||||||
|
- `better-sqlite3` 仅在主进程使用同步 API
|
||||||
|
|
||||||
|
### 2.2 技术栈
|
||||||
|
|
||||||
|
| 层次 | 技术 |
|
||||||
|
|------|------|
|
||||||
|
| 桌面框架 | Electron(electron-vite 脚手架) |
|
||||||
|
| 前端 | React 18、TypeScript strict |
|
||||||
|
| 编辑器 | TipTap (ProseMirror) |
|
||||||
|
| 数据库 | better-sqlite3 |
|
||||||
|
| 全局状态 | Zustand |
|
||||||
|
| 编辑器状态 | Jotai |
|
||||||
|
| 交互组件 | Radix UI(Dialog、Select、Tabs、Checkbox、Toast) |
|
||||||
|
| 国际化 | i18next + react-i18next |
|
||||||
|
| 单元/集成测试 | Vitest |
|
||||||
|
| E2E | Playwright + Electron |
|
||||||
|
| 图标 | Font Awesome 6(与原型一致) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 项目目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
bilin2/
|
||||||
|
├── design/ # 设计文档(只读参考)
|
||||||
|
├── docs/superpowers/
|
||||||
|
│ ├── specs/ # 本文件
|
||||||
|
│ └── plans/ # 实现计划(writing-plans 产出)
|
||||||
|
├── public/locales/
|
||||||
|
│ ├── zh-CN/translation.json
|
||||||
|
│ └── en/translation.json
|
||||||
|
├── src/
|
||||||
|
│ ├── main/
|
||||||
|
│ │ ├── index.ts
|
||||||
|
│ │ ├── db/
|
||||||
|
│ │ │ ├── connection.ts
|
||||||
|
│ │ │ ├── migrations/
|
||||||
|
│ │ │ ├── schema-v1.sql
|
||||||
|
│ │ │ └── repositories/
|
||||||
|
│ │ │ ├── volume.repo.ts
|
||||||
|
│ │ │ └── chapter.repo.ts
|
||||||
|
│ │ ├── ipc/
|
||||||
|
│ │ │ ├── register.ts
|
||||||
|
│ │ │ ├── result.ts
|
||||||
|
│ │ │ └── handlers/
|
||||||
|
│ │ ├── services/
|
||||||
|
│ │ │ ├── book-registry.ts
|
||||||
|
│ │ │ ├── global-settings.ts
|
||||||
|
│ │ │ └── word-count.ts
|
||||||
|
│ │ └── shortcuts/
|
||||||
|
│ │ └── manager.ts
|
||||||
|
│ ├── preload/
|
||||||
|
│ │ ├── index.ts
|
||||||
|
│ │ └── api.d.ts
|
||||||
|
│ └── renderer/
|
||||||
|
│ ├── styles/ # themes.css, layout.css(从 ui_pc.html 提取)
|
||||||
|
│ ├── components/
|
||||||
|
│ │ ├── layout/
|
||||||
|
│ │ ├── home/
|
||||||
|
│ │ ├── editor/
|
||||||
|
│ │ ├── settings/
|
||||||
|
│ │ ├── onboarding/
|
||||||
|
│ │ └── ui/ # Radix 封装
|
||||||
|
│ ├── stores/ # Zustand
|
||||||
|
│ ├── atoms/ # Jotai
|
||||||
|
│ ├── hooks/
|
||||||
|
│ ├── i18n/
|
||||||
|
│ └── lib/
|
||||||
|
├── electron.vite.config.ts
|
||||||
|
├── package.json
|
||||||
|
└── tsconfig.json
|
||||||
|
```
|
||||||
|
|
||||||
|
**分层约定**:Repository 负责 SQL;IPC Handler 负责校验 + 调用 Repository + 返回 `IpcResult`;渲染进程禁止直接访问数据库。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. P0 交付清单
|
||||||
|
|
||||||
|
| 模块 | 内容 | 设计文档对应 |
|
||||||
|
|------|------|--------------|
|
||||||
|
| 工程脚手架 | electron-vite + React + TS strict + ESLint | §1.4 |
|
||||||
|
| 自定义标题栏 | drag 区域、最小化/最大化/关闭 | ui_pc.html #topbar |
|
||||||
|
| 主题系统 | 7 套主题 CSS 变量,`data-theme` 切换,持久化 | §3.1 |
|
||||||
|
| i18n | zh-CN / en,所有 UI 文本走 `t()` | §3.2 |
|
||||||
|
| 全局设置 | `global_settings.json` 读写 | §19 |
|
||||||
|
| IPC 基础 | `IpcResult` 信封 + 通道注册 | §16 |
|
||||||
|
| 数据库 | Schema v1 迁移框架 | §12.1 |
|
||||||
|
| Onboarding | 5 步向导,笔名必填,可选示例书 | §3.3 |
|
||||||
|
| 快捷键 | 默认绑定 + 设置页录制 + 冲突提示 | §3.4 |
|
||||||
|
| 三视图骨架 | 主页 / 编辑器 / 设置页 + Tab 管理 | ui_pc.html |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. P1 交付清单
|
||||||
|
|
||||||
|
### 5.1 完整实现
|
||||||
|
|
||||||
|
| 模块 | 内容 |
|
||||||
|
|------|------|
|
||||||
|
| 主页 | 书籍网格、新建书籍(Radix Dialog)、最近编辑、连更徽章占位 |
|
||||||
|
| 多书 Tab | 打开/切换/关闭,Tab 标题为书名 |
|
||||||
|
| 分卷 | 创建、重命名、折叠/展开、删除(章节移入未分类或跟随删除) |
|
||||||
|
| 章节树 | 树状展示、选中、双击改标题、按当前卷新建章、状态徽章(草稿/定稿等) |
|
||||||
|
| TipTap 编辑器 | 加粗/斜体/下划线/分割线/缩进;默认 Ctrl+V 纯文本 |
|
||||||
|
| 自动保存 | 输入停顿 2 秒 debounce 写入 SQLite |
|
||||||
|
| 字数统计 | 本章/本卷/全书增量更新,状态栏展示 |
|
||||||
|
| 章节切换 | 切换前强制保存;加载 content + cursor_offset |
|
||||||
|
| Undo | 每章独立内存 undo 堆栈(TipTap 内置);**磁盘持久化放 P2** |
|
||||||
|
|
||||||
|
### 5.2 UI 占位(可见、不可用时 Toast)
|
||||||
|
|
||||||
|
| 功能 | 占位行为 |
|
||||||
|
|------|----------|
|
||||||
|
| 右栏 AI 助手 | 布局完整,输入框 disabled,文案 `t('feature.comingSoon')` |
|
||||||
|
| 右栏知识库/用词/书籍设置 | Tab 可见,点击内容区显示占位 |
|
||||||
|
| 引用面板 | 工具栏按钮可见,点击 Toast |
|
||||||
|
| 顶栏驾驶舱/图谱/时间线/专注/音效 | 按钮可见,点击 Toast 或空 modal 壳 |
|
||||||
|
| 侧栏大纲/设定/灵感 | Tab 可见,**mock 静态列表**,切换编辑器标签但不持久化 |
|
||||||
|
| 批量章节操作 | P2 |
|
||||||
|
| 导入导出 | P7 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. IPC 协议
|
||||||
|
|
||||||
|
### 6.1 IpcResult 信封
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type IpcResult<T> =
|
||||||
|
| { ok: true; data: T }
|
||||||
|
| { ok: false; error: IpcError };
|
||||||
|
|
||||||
|
interface IpcError {
|
||||||
|
code: ErrorCode;
|
||||||
|
message: string;
|
||||||
|
recoverable: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ErrorCode {
|
||||||
|
DB_READ_FAILED = 'DB_READ_FAILED',
|
||||||
|
DB_WRITE_FAILED = 'DB_WRITE_FAILED',
|
||||||
|
FILE_NOT_FOUND = 'FILE_NOT_FOUND',
|
||||||
|
IMPORT_INVALID = 'IMPORT_INVALID',
|
||||||
|
UNKNOWN = 'UNKNOWN',
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 通道列表(P0/P1)
|
||||||
|
|
||||||
|
**设置**
|
||||||
|
|
||||||
|
| 通道 | 参数 | 返回 |
|
||||||
|
|------|------|------|
|
||||||
|
| `settings:get` | — | `GlobalSettings` |
|
||||||
|
| `settings:update` | `Partial<GlobalSettings>` | `GlobalSettings` |
|
||||||
|
|
||||||
|
**书籍**
|
||||||
|
|
||||||
|
| 通道 | 参数 | 返回 |
|
||||||
|
|------|------|------|
|
||||||
|
| `book:list` | — | `BookMeta[]` |
|
||||||
|
| `book:create` | `CreateBookParams` | `BookMeta` |
|
||||||
|
| `book:delete` | `{ bookId }` | `void` |
|
||||||
|
| `book:open` | `{ bookId }` | `{ meta, volumes, chapters }` |
|
||||||
|
|
||||||
|
**分卷**
|
||||||
|
|
||||||
|
| 通道 | 参数 | 返回 |
|
||||||
|
|------|------|------|
|
||||||
|
| `volume:create` | `{ bookId, name }` | `Volume` |
|
||||||
|
| `volume:update` | `{ bookId, volumeId, ... }` | `Volume` |
|
||||||
|
| `volume:delete` | `{ bookId, volumeId, moveChaptersTo? }` | `void` |
|
||||||
|
| `volume:reorder` | `{ bookId, orderedIds }` | `void` |
|
||||||
|
|
||||||
|
**章节**
|
||||||
|
|
||||||
|
| 通道 | 参数 | 返回 |
|
||||||
|
|------|------|------|
|
||||||
|
| `chapter:create` | `{ bookId, volumeId, title }` | `Chapter` |
|
||||||
|
| `chapter:get` | `{ bookId, chapterId }` | `Chapter` |
|
||||||
|
| `chapter:update` | `{ bookId, chapterId, title?, content?, status?, cursorOffset?, wordCount? }` | `Chapter` |
|
||||||
|
| `chapter:delete` | `{ bookId, chapterId }` | `void` |
|
||||||
|
| `chapter:reorder` | `{ bookId, volumeId, orderedIds }` | `void` |
|
||||||
|
|
||||||
|
**快捷键**
|
||||||
|
|
||||||
|
| 通道 | 参数 | 返回 |
|
||||||
|
|------|------|------|
|
||||||
|
| `shortcut:getAll` | — | `Record<ActionId, string>` |
|
||||||
|
| `shortcut:register` | `{ action, accelerator }` | `void` |
|
||||||
|
|
||||||
|
**主→渲染事件**
|
||||||
|
|
||||||
|
| 事件 | 用途 |
|
||||||
|
|------|------|
|
||||||
|
| `shortcut:triggered` | `{ action: ActionId }` |
|
||||||
|
|
||||||
|
### 6.3 Preload API 形状
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface ElectronAPI {
|
||||||
|
settings: {
|
||||||
|
get(): Promise<IpcResult<GlobalSettings>>;
|
||||||
|
update(p: Partial<GlobalSettings>): Promise<IpcResult<GlobalSettings>>;
|
||||||
|
};
|
||||||
|
book: {
|
||||||
|
list(): Promise<IpcResult<BookMeta[]>>;
|
||||||
|
create(p: CreateBookParams): Promise<IpcResult<BookMeta>>;
|
||||||
|
delete(p: { bookId: string }): Promise<IpcResult<void>>;
|
||||||
|
open(p: { bookId: string }): Promise<IpcResult<BookOpenResult>>;
|
||||||
|
};
|
||||||
|
volume: { /* create, update, delete, reorder */ };
|
||||||
|
chapter: { /* create, get, update, delete, reorder */ };
|
||||||
|
shortcut: {
|
||||||
|
getAll(): Promise<IpcResult<Record<ActionId, string>>>;
|
||||||
|
register(p: { action: ActionId; accelerator: string }): Promise<IpcResult<void>>;
|
||||||
|
};
|
||||||
|
onShortcutTriggered(callback: (action: ActionId) => void): () => void;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 数据模型
|
||||||
|
|
||||||
|
### 7.1 文件布局
|
||||||
|
|
||||||
|
```
|
||||||
|
{userData}/
|
||||||
|
├── global_settings.json
|
||||||
|
├── books_registry.json
|
||||||
|
├── books/
|
||||||
|
│ └── {bookId}.sqlite
|
||||||
|
└── logs/bilin.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 global_settings.json
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface GlobalSettings {
|
||||||
|
penName: string;
|
||||||
|
onboardingCompleted: boolean;
|
||||||
|
theme: 'default' | 'bamboo' | 'moonlit' | 'ricepaper' | 'mist' | 'teagarden' | 'twilight';
|
||||||
|
language: 'zh-CN' | 'en';
|
||||||
|
dailyWordGoal: number;
|
||||||
|
shortcuts: Record<ActionId, string>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
P0/P1 不实现:`aiBackend`、`syncConfig`、`chapterTemplates`(留字段或 P2+ 扩展)。
|
||||||
|
|
||||||
|
### 7.3 books_registry.json
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface BookMeta {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
category: string;
|
||||||
|
targetWordCount: number | null;
|
||||||
|
dbPath: string;
|
||||||
|
status: 'draft' | 'ongoing' | 'done';
|
||||||
|
lastOpenedAt: string;
|
||||||
|
lastChapterId: string | null;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.4 SQLite Schema v1(每书)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE schema_version (
|
||||||
|
version INTEGER PRIMARY KEY,
|
||||||
|
applied_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
|
description TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE volumes (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
description TEXT DEFAULT '',
|
||||||
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||||
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE chapters (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
volume_id TEXT,
|
||||||
|
title TEXT NOT NULL DEFAULT '',
|
||||||
|
content TEXT NOT NULL DEFAULT '',
|
||||||
|
status TEXT NOT NULL DEFAULT 'draft',
|
||||||
|
word_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||||
|
cursor_offset INTEGER DEFAULT 0,
|
||||||
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
|
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
|
FOREIGN KEY (volume_id) REFERENCES volumes(id) ON DELETE SET NULL
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**P1 不含**:`outline_items`、`settings`、`inspiration`、`snapshots`、`undo_stack`、`bookmarks` 等(P2+ 通过迁移追加)。
|
||||||
|
|
||||||
|
### 7.5 创建书籍流程
|
||||||
|
|
||||||
|
1. 生成 UUID
|
||||||
|
2. 在 `books/` 下创建 `{id}.sqlite`,执行 schema v1 迁移
|
||||||
|
3. 写入 `books_registry.json`
|
||||||
|
4. 默认创建分卷「第一卷」
|
||||||
|
5. 若 Onboarding 勾选示例书,额外创建 1 章示例正文
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 状态管理
|
||||||
|
|
||||||
|
### 8.1 Zustand Stores
|
||||||
|
|
||||||
|
| Store | 职责 |
|
||||||
|
|-------|------|
|
||||||
|
| `useAppStore` | 当前视图(home/editor/settings)、侧栏激活 panel |
|
||||||
|
| `useTabStore` | openTabs、activeTabId |
|
||||||
|
| `useBookStore` | 书籍列表、当前书、分卷章节树、选中章节 ID |
|
||||||
|
| `useSettingsStore` | theme、language、penName、shortcuts |
|
||||||
|
| `useOnboardingStore` | 向导步骤、完成状态 |
|
||||||
|
|
||||||
|
写操作:调用 IPC → 成功后更新 Store。
|
||||||
|
|
||||||
|
### 8.2 Jotai Atoms(编辑器域)
|
||||||
|
|
||||||
|
| Atom | 用途 |
|
||||||
|
|------|------|
|
||||||
|
| `currentChapterIdAtom` | 当前编辑章节 |
|
||||||
|
| `editorContentAtom` | TipTap 文档内容 |
|
||||||
|
| `editorDirtyAtom` | 未保存标记 |
|
||||||
|
| `editorSavingAtom` | 保存中状态 |
|
||||||
|
| `wordCountAtom` | `{ chapter, volume, book }` |
|
||||||
|
| `cursorOffsetAtom` | 光标字符偏移 |
|
||||||
|
| `lastSavedAtAtom` | 上次保存时间 |
|
||||||
|
|
||||||
|
### 8.3 章节切换序列
|
||||||
|
|
||||||
|
1. 若 `editorDirtyAtom` → 立即 `chapter:update`
|
||||||
|
2. `chapter:get` 加载新章节
|
||||||
|
3. TipTap 设置 content,恢复 cursor_offset
|
||||||
|
4. 重置 dirty
|
||||||
|
|
||||||
|
### 8.4 自动保存
|
||||||
|
|
||||||
|
- 监听 TipTap `onUpdate`
|
||||||
|
- debounce **2000ms**
|
||||||
|
- 调用 `chapter:update`(content、cursorOffset、wordCount)
|
||||||
|
- 主进程 `word-count.ts` 增量更新章节字数;卷/全书字数由 chapters 聚合或缓存
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. UI 规范
|
||||||
|
|
||||||
|
### 9.1 混合方案落地
|
||||||
|
|
||||||
|
| 区域 | 实现 |
|
||||||
|
|------|------|
|
||||||
|
| 顶栏、侧栏、编辑器区、状态栏 | 从 `ui_pc.html` 提取 CSS(变量 + 布局类) |
|
||||||
|
| 模态框、Select、Checkbox、Toast | Radix UI 封装于 `components/ui/` |
|
||||||
|
| 主题 | `[data-theme="xxx"]` 覆盖 CSS 变量,与原型 7 主题 ID 一致 |
|
||||||
|
|
||||||
|
### 9.2 三视图
|
||||||
|
|
||||||
|
- **主页** `#home-page`:Logo、操作按钮、书籍网格、最近编辑
|
||||||
|
- **编辑器** `#editor-layout`:左栏四 Tab + 中央编辑器 + 右栏占位
|
||||||
|
- **设置** `#settings-page`:侧栏导航 + 通用/编辑器/AI(占位)/快捷键/备份(占位)/关于
|
||||||
|
|
||||||
|
### 9.3 默认快捷键(P0/P1 实现子集)
|
||||||
|
|
||||||
|
| 功能 | 默认 | P0/P1 |
|
||||||
|
|------|------|-------|
|
||||||
|
| 保存章节 | Ctrl+S | ✅ |
|
||||||
|
| 新建章节 | Ctrl+Shift+N | ✅ |
|
||||||
|
| 切换专注模式 | Ctrl+Alt+F | 占位 Toast |
|
||||||
|
| 全局搜索 | Ctrl+Shift+F | 占位 Toast |
|
||||||
|
| 关闭 Tab | Ctrl+W | ✅ |
|
||||||
|
| 打开设置 | — | 顶栏齿轮 |
|
||||||
|
|
||||||
|
其余快捷键在设置页展示,P2+ 绑定实际功能。
|
||||||
|
|
||||||
|
### 9.4 i18n 要求
|
||||||
|
|
||||||
|
- 所有用户可见字符串使用 `t()`
|
||||||
|
- zh-CN 与 en 同步维护,不得混用语言
|
||||||
|
- 数字/日期格式随 locale 变化
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 核心流程
|
||||||
|
|
||||||
|
### 10.1 启动
|
||||||
|
|
||||||
|
1. 主进程读取 `global_settings.json`,注册快捷键
|
||||||
|
2. 渲染进程 `settings:get` → 应用 theme + language
|
||||||
|
3. 若 `onboardingCompleted === false` → 显示 Onboarding
|
||||||
|
4. 否则 → 主页 + `book:list`
|
||||||
|
|
||||||
|
### 10.2 打开书籍
|
||||||
|
|
||||||
|
1. `book:open` → 填充 useBookStore
|
||||||
|
2. 新建 Tab,`lastChapterId` 或第一卷第一章
|
||||||
|
3. `chapter:get` → TipTap 加载
|
||||||
|
|
||||||
|
### 10.3 错误处理
|
||||||
|
|
||||||
|
| 场景 | code | recoverable | UI |
|
||||||
|
|------|------|-------------|-----|
|
||||||
|
| SQLite 写失败 | DB_WRITE_FAILED | true | Toast + 重试 |
|
||||||
|
| DB 文件丢失 | FILE_NOT_FOUND | false | 错误对话框 |
|
||||||
|
| IPC 异常 | UNKNOWN | true | Toast |
|
||||||
|
|
||||||
|
渲染进程统一 `ipcCall()` 封装处理。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 测试
|
||||||
|
|
||||||
|
### 11.1 工具
|
||||||
|
|
||||||
|
- **Vitest**:主进程 Repository、word-count、IPC handler(内存 SQLite)
|
||||||
|
- **Playwright + Electron**:E2E
|
||||||
|
|
||||||
|
### 11.2 P0/P1 必过用例
|
||||||
|
|
||||||
|
| ID | 场景 |
|
||||||
|
|----|------|
|
||||||
|
| E2E-THEME-01 | 依次切换 7 主题,UI 颜色正确 |
|
||||||
|
| E2E-I18N-03 | 中英文切换,无语言混杂 |
|
||||||
|
| E2E-ONBOARD-02 | 完成引导,笔名持久化 |
|
||||||
|
| E2E-KEY-02 | Ctrl+Shift+N 新建章节 |
|
||||||
|
| E2E-P1-SAVE | 写作 → 等 2s 自动保存 → 重启 → 内容完整 |
|
||||||
|
| E2E-P1-CURSOR | 切换章节再回来,光标偏移恢复 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. 非目标(P0/P1 明确排除)
|
||||||
|
|
||||||
|
- AI 任何后端集成
|
||||||
|
- 版本历史、undo 磁盘持久化
|
||||||
|
- 大纲/设定/灵感数据持久化
|
||||||
|
- 导入导出、云同步、PDF
|
||||||
|
- 驾驶舱、图谱、时间线业务逻辑
|
||||||
|
- macOS/Linux 打包与签名
|
||||||
|
- 无障碍完整 E2E(P7 补全)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. 风险与缓解
|
||||||
|
|
||||||
|
| 风险 | 缓解 |
|
||||||
|
|------|------|
|
||||||
|
| better-sqlite3 原生模块 Electron 编译 | electron-vite 官方模板 + rebuild 脚本 |
|
||||||
|
| TipTap 大文档性能 | P1 单章编辑;全书虚拟滚动在 P2 |
|
||||||
|
| ui_pc.html CSS 迁移工作量大 | 先迁移 layout + themes,组件级样式按需提取 |
|
||||||
|
| P1 范围含完整 i18n/主题/Onboarding 导致工期膨胀 | 严格区分 P1 完整 vs 占位清单(§5.2) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. 完成定义(Definition of Done)
|
||||||
|
|
||||||
|
P0/P1 合并发布为 **v0.1.0** 时:
|
||||||
|
|
||||||
|
- [ ] Windows 下 `npm run dev` 可启动,无控制台 error
|
||||||
|
- [ ] 新用户走完 Onboarding,可创建书籍并写作
|
||||||
|
- [ ] 7 主题、双语、快捷键自定义可用
|
||||||
|
- [ ] 自动保存与重启恢复通过 E2E
|
||||||
|
- [ ] §5.2 占位功能行为符合规范(可见 + Toast,无崩溃)
|
||||||
|
- [ ] Vitest 集成测试覆盖 chapter CRUD + settings 读写
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*本规格为笔临全量实现的 Phase 0/1 子项目。后续阶段各自独立 spec 或在本文档增量修订。*
|
||||||
Reference in New Issue
Block a user