From a1c283e10dd748ac44216e59fe3b11f89cd2c46c Mon Sep 17 00:00:00 2001 From: SpecialX <47072643+wangxiner55@users.noreply.github.com> Date: Sat, 4 Jul 2026 10:58:55 +0800 Subject: [PATCH] =?UTF-8?q?docs(lesson-preparation):=20=E5=A4=87=E8=AF=BE?= =?UTF-8?q?=E7=BC=96=E8=BE=91=E5=99=A8=E6=97=A0=E8=BE=B9=E8=AE=B0=E7=BA=B8?= =?UTF-8?q?=E6=84=9F=E9=87=8D=E6=9E=84=E8=AE=BE=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增设计文档,定义正文节点改造为真实 Tiptap 富文本编辑器、三栏布局(结构树 + 纸 + 详情面板)、节点展开到正文、师生交互节点、Tiptap Mark 锚点系统、v3→v4 数据迁移。 --- ...esson-preparation-paper-redesign-design.md | 472 ++++++++++++++++++ 1 file changed, 472 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-04-lesson-preparation-paper-redesign-design.md diff --git a/docs/superpowers/specs/2026-07-04-lesson-preparation-paper-redesign-design.md b/docs/superpowers/specs/2026-07-04-lesson-preparation-paper-redesign-design.md new file mode 100644 index 0000000..c0401e3 --- /dev/null +++ b/docs/superpowers/specs/2026-07-04-lesson-preparation-paper-redesign-design.md @@ -0,0 +1,472 @@ +# 备课编辑器 · 无边记纸感重构设计 + +**日期**: 2026-07-04 +**主题**: 备课模块正文节点改为真实富文本编辑器 + 三栏布局重构 + 节点展开到正文 + 师生交互节点 +**状态**: 已视觉确认,待规格审核 + +--- + +## 1. 背景与问题 + +### 1.1 当前问题 + +备课模块编辑器(`src/modules/lesson-preparation/components/lesson-plan-editor.tsx`)存在以下核心问题: + +1. **正文节点是"假"的富文本编辑器** + - `textbook-content-node.tsx`(画布中央节点)只是把 Markdown 字符串作为纯文本用 `whitespace-pre-wrap` 显示 + - 没有真实排版(加粗/标题/列表/图片都看不到)、没有光标、不可直接编辑 + - 与 `rich-text-block.tsx`(侧栏教学节点已用 Tiptap)形成不一致体验 + +2. **画布交互不适合长文档编辑** + - React Flow 画布上 520px 小框装不下教材正文 + - 频繁缩放/拖动画布打断写作流 + - 移动端几乎不可用 + +3. **锚点系统脆弱** + - `lib/anchor-injector.ts` 用字符串偏移(`plainToMd` Map)定位 + - 任何编辑都引起偏移漂移,要靠 `relocateAnchors` 修补 + - Markdown → 纯文本的偏移映射在复杂格式下不可靠 + +4. **配色不统一** + - `globals.css` 的 `--lesson-node-*` 用了 13 个 Material Design 鲜艳原色(#4caf50、#f44336、#9c27b0 等) + - 与 shadcn/ui 设计语言冲突 + +### 1.2 用户期望 + +- "像在无边记(Freeform)里做教案"——一张安静的白纸居中,便签轻贴其侧 +- 正文节点必须是真实富文本编辑器,可编辑(教材正文副本,不影响原课文) +- 节点能展开到正文流里,用字体差异区分(不框住) +- 师生交互环节设计(师问生答) +- 右键菜单含 AI 协助 +- 不用奇怪颜色和图标,符合系统设计语言 + +--- + +## 2. 视觉设计 + +### 2.1 视觉主张 + +一张安静的白纸居中,节点内容轻贴其侧——像在无边记里把教案摊开写,而不是在画布上摆积木。 + +### 2.2 字体双轨 + +| 用途 | 字体 | 字号 | 颜色 | +|------|------|------|------| +| 正文(纸上教材) | Fraunces 衬线 | 16px / 1.75 行高 | #1a1a1a | +| UI / 节点文本 / 详情 | Inter 无衬线 | 13px / 1.6 行高 | #44403c | +| 锚点标签 / 元数据 | JetBrains Mono | 9-11px | #a8a29e | + +### 2.3 配色(移除鲜艳 Material 色) + +- 全中性调:白/灰/近黑 +- 节点类型只用 6px 小色点区分: + - objective `#4b5563` / new_teaching `#1c1917` / interaction `#6366f1`(克制靛蓝) + - exercise `#6b7280` / summary `#525252` / textbook `#44403c` +- 锚点:区间高亮 `rgba(28,25,23,0.08)` + 底部 1.5px 黑线;点锚点黑色小圆圈 + +### 2.4 禁用元素 + +- 所有 emoji(📖🎯📚 等) +- 所有装饰性图标 +- 鲜艳色块 + +--- + +## 3. 布局架构 + +### 3.1 三栏布局(替换 React Flow 画布) + +``` +┌──────────────────────────────────────────────────────────────┐ +│ 顶部工具栏(标题 / 保存状态 / 撤销重做 / 发布 / AI) │ +├──────────┬────────────────────────────┬────────────────────┤ +│ │ │ │ +│ 左栏 │ 中栏(纸) │ 右栏(详情面板) │ +│ 260px │ 1fr │ 380px │ +│ │ │ │ +│ 结构树 │ 教材正文富文本编辑器 │ 选中节点的详情 │ +│ (折叠) │ 居中 max-w-720px │ (普通卡片样式) │ +│ │ 白底 shadow │ │ +└──────────┴────────────────────────────┴────────────────────┘ +``` + +### 3.2 左栏:结构树 + +- 所有节点(含正文)按 `order` 排序,纯列表 +- 每行:折叠箭头 + 6px 色点 + 标题 + 类型标签 + 「纸上」标记(如已展开) +- 师生交互节点可展开子节点(对话轮次) +- 底部"+ 添加节点" + +### 3.3 中栏:纸 + +- 教材正文的 Tiptap 富文本编辑器 +- `max-width: 720px`、`padding: 64px 72px`、`box-shadow`、白底 +- 聚焦时顶部出现毛玻璃浮动工具条(B / I / H1 / H2 / • / 1. / " / —) +- 锚点标记内嵌在文本流里 +- 展开的节点作为 `inline-node` 嵌入段落之间 + +### 3.4 右栏:详情面板 + +- **不再是便签样式**,改为普通详情卡片 +- 头部:色点 + 类型标签 + 标题输入框 + 展开切换按钮 + 更多按钮 +- 属性条:教学阶段 / 差异化 / 时长 / 锚点(可点击切换) +- 主体:节点类型的专属编辑器(复用现有 `BlockRenderer`) +- 底部 AI 区:4 个 AI 协助按钮(靛蓝 `#6366f1`) +- 元信息条:标签 + 更新时间 + +--- + +## 4. 节点展开/收起机制 + +### 4.1 统一展开(所有节点类型) + +所有教学节点(objective / new_teaching / exercise / interaction 等)都支持展开到正文流。 + +**展开态(inline-node 嵌入纸面)**: +- 左侧 2px 细线(`#d6d3d1`,hover 变 `#1c1917`) +- Inter 字体 13.5px / 暖灰 `#44403c` +- 头部:色点 + 类型标签 + 锚点引用 + 「收起 ▴」按钮 +- 标题:Inter 14px 加粗 `#1a1a1a` +- 主体:节点类型的渲染内容 +- 视觉上"从正文里长出来",不框住 + +**插入位置**:按节点 `order` 字段顺序,在教材正文的段落之间插入 inline-node。锚点(区间高亮/点圆圈)仍留在正文文本内,仅作为视觉标记;inline-node 的位置由 order 决定,不由锚点位置决定。这样保证展开顺序稳定,编辑正文不会引起 inline-node 位置漂移。 + +**收起态**: +- inline-node 从纸上消失 +- 节点数据仍在右栏详情和左栏结构树可见 + +### 4.2 三个展开入口 + +1. **左栏结构树**:点击「纸上」标记 → 切换展开/收起 +2. **右栏详情头部**:展开切换按钮 ▾/▴ +3. **右键菜单**:纸上右键节点 → "展开到正文" / "从正文收起" + +### 4.3 右键菜单(节点级) + +**触发对象**:纸上的 inline-node(已展开的节点)。右键教材正文文本触发的是"锚定"菜单(见 §6.3),不是节点菜单。右键左栏结构树节点不触发菜单(左栏用按钮操作)。 + +分组结构: +- **节点操作**:展开到正文 / 从正文收起 / 上移 / 下移 +- **AI 协助**:生成本节点内容 / 优化表达 / 差异化建议 / 生成分层提问 +- **其他**:复制节点 / 删除节点 + +--- + +## 5. 师生交互节点(新节点类型) + +### 5.1 类型定义 + +新增 `interaction` BlockType: + +```typescript +export type BlockType = + | "objective" | "key_point" | "import" + | "new_teaching" | "consolidation" | "summary" + | "homework" | "blackboard" | "text_study" + | "exercise" | "rich_text" | "reflection" + | "interaction"; // 新增 +``` + +### 5.2 数据结构 + +```typescript +export interface QATurn { + id: string; + role: "teacher" | "student"; + content: string; + /** 教师提问的预期答案 / 引导策略(可选)*/ + expectedAnswer?: string; + /** 这一轮的顺序 */ + order: number; +} + +export interface InteractionBlockData { + /** 设计意图 */ + designIntent: string; + /** 对话轮次 */ + turns: QATurn[]; + /** 关联知识点 */ + knowledgePointIds: string[]; +} +``` + +### 5.3 渲染样式 + +**纸上展开态(QA 对话体)**: +- 每轮对话:`师`/`生` 角色标签(JetBrains Mono 9px,黑/灰区分)+ 内容 +- 教师提问下方用 italic 灰色小字标注 `[预期:...]` +- 对话轮次之间用 dashed 线分隔 + +**右栏详情编辑器**: +- 每轮对话独立卡片:角色选择 + 内容文本框 + 预期答案文本框 +- 上下移 / 删除按钮 +- 底部"+ 添加一轮对话" + +### 5.4 左栏子节点 + +师生交互节点在左栏可展开显示对话轮次: +- 第 1 轮 · 提问(师) +- 第 2 轮 · 回答(生) +- 第 3 轮 · 追问(师) + +--- + +## 6. 锚点系统重构 + +### 6.1 从字符串偏移改为 Tiptap Mark + +**当前**:`NodeAnchor.start/end` 是基于 `markdownToPlainText` 的纯文本偏移,靠 `injectPlaceholders` 注入 `[[anchor:id]]` 标记,靠 `relocateAnchors` 修补漂移。 + +**改为**:Tiptap 自定义 Mark `AnchorMark`,存储 `anchorId` 和 `nodeId`。 + +```typescript +// Tiptap Mark 定义 +const AnchorMark = Mark.create({ + name: "anchor", + addAttributes() { + return { + anchorId: { default: null }, + nodeId: { default: null }, + type: { default: "range" }, // "range" | "point" + }; + }, + parseHTML() { return [{ tag: "span[data-anchor-id]" }]; }, + renderHTML({ HTMLAttributes }) { + return ["span", mergeAttributes(HTMLAttributes, { + "data-anchor-id": HTMLAttributes.anchorId, + "data-node-id": HTMLAttributes.nodeId, + "data-anchor-type": HTMLAttributes.type, + })]; + }, +}); +``` + +### 6.2 锚点数据结构变更 + +```typescript +export interface NodeAnchor { + id: string; + nodeId: string; + type: AnchorType; + // 删除 start/end/textPreview/invalid(改为 Mark 内嵌) + // 保留 id/nodeId/type 用于关联 +} +``` + +### 6.3 锚点交互 + +- **区间锚定**:在纸上选中文本 → 右键 → "锚定到节点" → 选择节点 → Tiptap `toggleMark` 包裹选中文本 +- **点锚定**:在纸上点击位置 → 右键 → "插入锚点" → 选择节点 → 插入 PointMark 节点(带圈数字) +- **编辑时自动跟随**:因为是 Mark,文本增删时 Mark 自动随文本移动,不再需要 `relocateAnchors` +- **删除锚点**:右栏详情面板的锚点列表 → 删除按钮 → Tiptap `unsetMark` + +### 6.4 锚点视觉 + +- 区间锚点:浅灰高亮 `rgba(28,25,23,0.08)` + 底部 1.5px 黑线 + 行内小标签 `A` `B` `C`(黑底白字 9px) +- 点锚点:16px 黑色圆圈带数字 ①②③ +- hover/active:高亮加深,圆点放大 + +--- + +## 7. 数据模型变更 + +### 7.1 文档版本升级到 v4 + +```typescript +export interface LessonPlanDocumentV4 { + version: 4; + textbookContentNodeId: string; + nodes: AnyLessonPlanNode[]; + edges: AnyLessonPlanEdge[]; // 保留但不再用于画布连线 + anchors: NodeAnchor[]; // 简化:只存 id 关联 + /** V4 新增:节点展开状态 */ + expandedNodeIds: string[]; +} +``` + +### 7.2 迁移 + +- `lib/document-migration.ts` 新增 `migrateV3ToV4` +- v3 的 `anchor.start/end/textPreview/invalid` 字段忽略(旧锚点在 v4 中失效,需重新锚定) +- v3 的 `position {x,y}` 字段保留但忽略(不再用于画布) +- v3 的 `edges` 保留但忽略(不再画连线) + +### 7.3 向后兼容 + +- 读取 v3 文档时自动迁移到 v4 +- v4 文档保存时 `version: 4` +- 旧版本无法读取 v4(v4 是新创建分支,不回写 v3) + +--- + +## 8. 组件架构 + +### 8.1 移除的组件 + +- `components/node-editor.tsx`(React Flow 画布) +- `components/nodes/lesson-node.tsx` +- `components/nodes/textbook-content-node.tsx` +- `components/nodes/textbook-segments.tsx` +- `components/nodes/anchor-node-selector.tsx` +- `lib/anchor-injector.ts`(字符串偏移系统) +- `lib/rf-mappers.ts`(React Flow 映射) +- `lib/auto-layout.ts` + +### 8.2 新增组件 + +``` +src/modules/lesson-preparation/components/ +├─ paper-editor/ +│ ├─ paper-editor.tsx # 中栏:纸区容器 +│ ├─ textbook-tiptap-editor.tsx # 正文 Tiptap 编辑器(含锚点 Mark) +│ ├─ inline-node.tsx # 展开节点的 inline 渲染 +│ ├─ inline-qa-dialog.tsx # 师生交互的对话体渲染 +│ ├─ anchor-mark.ts # Tiptap AnchorMark 定义 +│ ├─ paper-toolbar.tsx # 浮动工具条 +│ └─ paper-context-menu.tsx # 右键菜单 +├─ structure-tree/ +│ ├─ structure-tree.tsx # 左栏结构树 +│ └─ tree-node-row.tsx # 树节点行 +├─ detail-panel/ +│ ├─ detail-panel.tsx # 右栏详情面板容器 +│ ├─ detail-head.tsx # 头部(类型 + 标题 + 操作) +│ ├─ detail-props.tsx # 属性条 +│ └─ qa-editor.tsx # 师生交互编辑器 +└─ blocks/ + └─ interaction-block.tsx # 师生交互 block(详情编辑) +``` + +### 8.3 改造的组件 + +- `lesson-plan-editor.tsx`:移除 NodeEditor + NodeEditPanel,改为三栏布局(StructureTree + PaperEditor + DetailPanel) +- `node-edit-panel.tsx`:拆分到 `detail-panel/` 下,逻辑保留 +- `config/block-registry.tsx`:新增 `interaction` 类型注册 + +### 8.4 保留的组件 + +- `blocks/rich-text-block.tsx`(仍用于非正文节点的富文本编辑) +- `blocks/objective-block.tsx` 等其他 block 编辑器 +- `version-history-drawer.tsx` / `print-view.tsx` / `consistency-check-dialog.tsx` / `ai-feedback-dialog.tsx` / `ai-differentiation-dialog.tsx` +- 所有 `actions-*.ts` / `data-access-*.ts` + +--- + +## 9. 状态管理 + +### 9.1 新增 slice:`expanded-slice.ts` + +```typescript +interface ExpandedState { + expandedNodeIds: string[]; + toggleExpand: (nodeId: string) => void; + setExpanded: (nodeIds: string[]) => void; + isExpanded: (nodeId: string) => boolean; +} +``` + +### 9.2 现有 slice 调整 + +- `editor-slice.ts`:移除 `selectedNodeId` 改为 `activeNodeId`(详情面板显示的节点),新增 `anchorNodeForSelection`(待锚定的节点 ID,用于纸上选中文本时锚定) +- `selection-slice.ts`:移除画布选择相关,保留节点选择 +- `history-slice.ts`:保持不变(撤销/重做覆盖 v4 文档) + +--- + +## 10. 关键交互流 + +### 10.1 编辑教材正文 + +1. 中栏纸区聚焦 → Tiptap 编辑器激活 → 浮动工具条出现 +2. 输入 → `onUpdate` → `editor-slice.updateNode(textbookNodeId, { data: { content: html } })` +3. debounce 3s 自动保存(现有逻辑保留) + +### 10.2 锚定选中文本到节点 + +1. 在纸上选中文本 → Tiptap selection change +2. 右键 → 上下文菜单 → "锚定到节点" +3. 选择节点 → `toggleMark("anchor", { anchorId, nodeId, type: "range" })` +4. `expandedNodeIds` 不变,但右栏详情面板的"锚点"属性更新 + +### 10.3 展开节点到正文 + +1. 右栏详情头部点击 ▾ → `expanded-slice.toggleExpand(nodeId)` +2. `paper-editor` 监听 `expandedNodeIds` → 在教材正文对应锚点位置插入 `` +3. InlineNode 渲染节点内容(用 Inter 字体区分正文 Fraunces) + +### 10.4 师生交互对话编辑 + +1. 左栏添加 `interaction` 节点 → 默认 3 轮空对话 +2. 右栏详情显示 `qa-editor` → 编辑每轮角色/内容/预期 +3. 展开到正文 → `inline-qa-dialog` 用对话体渲染 +4. 编辑右栏 → 纸上展开态实时更新 + +--- + +## 11. 移除的功能 + +| 功能 | 原因 | +|------|------| +| React Flow 画布 | 不适合长文档编辑,纸感设计取代 | +| 节点拖拽 / 连线 | 树结构 + 展开流取代空间布局 | +| Minimap / Controls | 画布移除 | +| 字符串偏移锚点 | Tiptap Mark 取代 | +| `relocateAnchors` | Mark 自动跟随 | +| 13 个鲜艳 Material 色 | 统一到中性令牌 | +| `position {x,y}` 字段使用 | 保留字段但忽略(向后兼容) | + +--- + +## 12. i18n + +新增翻译键(zh-CN / en): + +- `lessonPreparation.interaction.*`:师生交互节点相关 +- `lessonPreparation.paper.*`:纸区相关 +- `lessonPreparation.contextMenu.*`:右键菜单 +- `lessonPreparation.detail.*`:详情面板 +- `lessonPreparation.tree.*`:结构树 + +--- + +## 13. 架构图同步 + +完成后更新: +- `docs/architecture/004_architecture_impact_map.md`:lesson-preparation 模块章节 +- `docs/architecture/005_architecture_data.json`: + - 新增 `interaction` 到 BlockType + - 新增 `QATurn` / `InteractionBlockData` 类型 + - 新增组件 exports + - 移除的组件 + - 数据结构版本 v4 + +--- + +## 14. 已知风险 + +1. **v3 锚点丢失**:v3 文档的字符串偏移锚点在迁移到 v4 后失效,需重新锚定。迁移后在编辑器顶部显示一条不可关闭的黄色 banner("此课案使用旧版锚点格式,部分锚点已失效,请重新锚定"),点击"知道了"后消失(记录到 localStorage 不再提示该 planId)。 +2. **Tiptap Mark 与 React 状态同步**:Tiptap 的 Mark 状态在编辑时是内部的,需要 `onSelectionUpdate` 同步到 React 状态以驱动右栏锚点列表。 +3. **展开节点的性能**:大量节点展开时,纸上 DOM 数量增加。需 `onlyRenderVisibleElements` 或虚拟化(暂不实现,超过 20 个展开节点时再说)。 +4. **打印视图**:`print-view.tsx` 需适配 v4 文档结构(移除画布依赖,按展开顺序打印)。 + +--- + +## 15. 实现范围 + +本设计涵盖: +- 三栏布局重构 +- 正文 Tiptap 富文本编辑器 +- 锚点 Mark 系统 +- 节点展开/收起 +- 师生交互节点 +- 右键菜单 + AI 协助入口 +- 配色统一 +- v3 → v4 数据迁移 +- i18n +- 架构图同步 + +**不涵盖**(YAGNI): +- 协同编辑(V5-11 R3 单独立项) +- 移动端独立优化(先桌面端可用) +- 展开节点的虚拟化(性能问题出现再处理) +- 自定义字体加载(先引 Google Fonts CDN)