Files
NextEdu/docs/superpowers/specs/2026-07-04-lesson-preparation-paper-redesign-design.md
SpecialX a1c283e10d docs(lesson-preparation): 备课编辑器无边记纸感重构设计
新增设计文档,定义正文节点改造为真实 Tiptap 富文本编辑器、三栏布局(结构树 + 纸 + 详情面板)、节点展开到正文、师生交互节点、Tiptap Mark 锚点系统、v3→v4 数据迁移。
2026-07-04 10:58:55 +08:00

17 KiB
Raw Blame History

备课编辑器 · 无边记纸感重构设计

日期: 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: 720pxpadding: 64px 72pxbox-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 细线(#d6d3d1hover 变 #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

export type BlockType =
  | "objective" | "key_point" | "import"
  | "new_teaching" | "consolidation" | "summary"
  | "homework" | "blackboard" | "text_study"
  | "exercise" | "rich_text" | "reflection"
  | "interaction";  // 新增

5.2 数据结构

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,存储 anchorIdnodeId

// 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 锚点数据结构变更

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

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
  • 旧版本无法读取 v4v4 是新创建分支,不回写 v3

8. 组件架构

8.1 移除的组件

  • components/node-editor.tsxReact 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.tsReact 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 新增 sliceexpanded-slice.ts

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. 输入 → onUpdateeditor-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 → 在教材正文对应锚点位置插入 <InlineNode>
  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.mdlesson-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