diff --git a/docs/architecture/004_architecture_impact_map.md b/docs/architecture/004_architecture_impact_map.md index 2cf8c49..dd62db8 100644 --- a/docs/architecture/004_architecture_impact_map.md +++ b/docs/architecture/004_architecture_impact_map.md @@ -2544,44 +2544,47 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions" ## 2.27 lesson-preparation(备课模块) -**职责**:教师备课,基于教材章节创建课案(**节点图编辑器 React Flow**),支持模板、版本管理、知识点标注、题目创建/拉取、作业发布。 +**职责**:教师备课,基于教材章节创建课案(**V4 纸感重构:三栏布局 StructureTree + PaperEditor + DetailPanel**),正文采用 Tiptap 富文本编辑器(AnchorMark/AnchorPoint 内联锚点),支持模板、版本管理、知识点标注、题目创建/拉取、作业发布、撤销/重做、AI 反馈/差异化、一致性校验、课时安排。 > 架构变更(2026-06-21):编辑器从列表式(BlockRenderer + @dnd-kit)升级为节点图式(NodeEditor + @xyflow/react)。数据结构从 v1(blocks 数组)升级到 v2(nodes + edges 节点图),旧数据通过 `migrateV1ToV2()` 自动迁移。 > 架构变更(2026-06-23):数据结构从 v2 升级到 v3(课文锚点画布格式)。正文节点(textbook_content)作为画布中央核心,教学节点围绕正文组织。新增锚点系统(range/point 两种锚定方式)、占位符注入算法、11 种定制节点类型。旧数据通过 `migrateV2ToV3()` 自动迁移。 +> 架构变更(2026-07-04,V4 纸感重构):抛弃 React Flow 画布,改用三栏布局(260px 结构树 + 1fr 纸感编辑器 + 380px 详情面板)。正文采用 Tiptap 富文本编辑器,锚点从字符串偏移升级为 Tiptap Mark(AnchorMark 区间锚/AnchorPoint 点锚),v3 旧锚点通过 AnchorMigrationBanner 提示失效。doc.version=4,editor-slice 移除 updateNodePosition/connect/setEdges/autoLayout。新增 interaction block(师生交互对话回合)。结构树支持节点展开到正文流(expanded-slice)。PrintView 改为按 nodes.order 排序渲染。删除 8 个 React Flow 画布和字符串锚点相关文件(node-editor/nodes/*/anchor-injector/rf-mappers/auto-layout)。 + **数据结构**: - v1(已废弃,仅向后兼容读取):`{ version: 1, blocks: Block[] }` - v2(已废弃,仅向后兼容读取):`{ version: 2, nodes: LessonPlanNode[]; edges: LessonPlanEdge[] }` -- v3(当前):`{ version: 3, textbookContentNodeId: string; nodes: AnyLessonPlanNode[]; edges: AnyLessonPlanEdge[]; anchors: NodeAnchor[] }` - - `TextbookContentNode`:正文节点(`draggable: false`,画布中央,可缩放) - - `LessonPlanNode`:教学节点(`Block` + `position`,可拖动可连线) - - `NodeAnchor`:锚点(`{ id, nodeId, type: "range"|"point", start, end?, textPreview?, invalid? }`) - - `AnchorEdge`:锚点连线(教学节点 → 正文节点,默认 10% 透明度) - - `FlowEdge`:流程连线(教学节点 → 教学节点) +- v3(已废弃,仅向后兼容读取):`{ version: 3, textbookContentNodeId: string; nodes: AnyLessonPlanNode[]; edges: AnyLessonPlanEdge[]; anchors: NodeAnchor[] }` + - `NodeAnchor`:v3 字符串偏移锚点(`{ id, nodeId, type: "range"|"point", start, end?, textPreview?, invalid? }`),V4 中通过 `AnchorMigrationBanner` 提示失效 +- v4(当前):`{ version: 4, textbookContentNodeId: string; nodes: AnyLessonPlanNode[]; edges: AnyLessonPlanEdge[]; anchors: NodeAnchor[] }` + - 锚点改为 Tiptap Mark:`AnchorMark`(区间锚)+ `AnchorPoint`(点锚),存储在正文富文本 doc 中 + - `nodes` 新增 `order` 字段,按顺序渲染(替代画布 position) + - `InteractionBlockData`:师生交互对话回合(`{ designIntent, turns: QATurn[], expectedAnswer? }`) **导出函数**: -- Data-access(`data-access.ts`):`getLessonPlans`(V4:查询后按 textbookId+chapterId+creatorId 聚合版本,返回代表项 + versionCount + versions 摘要数组)/ `getLessonPlanById` / `createLessonPlan` / `updateLessonPlanContent` / `softDeleteLessonPlan` / `duplicateLessonPlan` / `getTemplateById` / `buildInitialContent` / `migrateV1ToV2` / `normalizeDocument`(v3 规范化,兼容 v1/v2 旧数据)/ `buildDefaultSkeleton`(v3 默认 10 节点骨架)/ `getTextbooksForPicker` / `getChaptersForPicker` / `findChapterById` / `publishLessonPlan`(V3 新增,设置 status=published)/ `unpublishLessonPlan`(V3 新增,设置 status=draft,仅 published 课案) +- Data-access(`data-access.ts`):`getLessonPlans`(V4:查询后按 textbookId+chapterId+creatorId 聚合版本,返回代表项 + versionCount + versions 摘要数组)/ `getLessonPlanById` / `createLessonPlan` / `updateLessonPlanContent` / `softDeleteLessonPlan` / `duplicateLessonPlan` / `getTemplateById` / `buildInitialContent` / `migrateV1ToV2` / `normalizeDocument`(v4 规范化,兼容 v1/v2/v3 旧数据)/ `buildDefaultSkeleton`(默认骨架)/ `getTextbooksForPicker` / `getChaptersForPicker` / `findChapterById` / `publishLessonPlan` / `unpublishLessonPlan` - Lib(`lib/document-migration.ts`):`defaultDataForType` / `migrateV1ToV2` / `migrateV2ToV3` / `normalizeDocument` / `buildInitialContent` / `buildDefaultSkeleton` / `isTextbookContentNode` / `isAnchorEdge` / `getAnchorsForNode` / `getActiveAnchorIds` / `getAnchorEdges` -- Lib(`lib/anchor-injector.ts`):`markdownToPlainText` / `injectPlaceholders` / `parseAnchoredText` / `toCircledNumber` / `getNextPointIndex` / `relocateAnchors` / `getAnchorColor` +- Lib(`lib/anchor-mark.ts`,V4 新增):Tiptap `AnchorMark` 扩展(区间锚)+ `AnchorPoint` 扩展(点锚),替代 v3 字符串偏移锚点 - Lib(`lib/node-summary.ts`):`getNodeSummary` / `getTextbookContentSummary` / `getNodeColor` / `NODE_COLORS` -- Lib(`lib/rf-mappers.ts`):`toRfNodes`(支持 textbook_content 节点;V3:ctx 新增 `anchorableNodes` 和 `onCreateNewNode` 字段)/ `toRfEdges`(区分 anchor/flow 边透明度;V3:锚点边颜色使用 `getNodeColor(anchor.nodeId)` 替代硬编码,anchorId 存入 edge.data)/ `fromRfEdges`(V3:从 `e.data.anchorId` 读取 anchorId,回退到 className 判断) +- Lib(`lib/export.ts`,V5-4 新增 + V4 增强):`flattenLessonPlanForPrint` / `flattenBlockData`(V4:新增 `interaction` case + `flattenInteraction` 函数)/ `PrintableLessonPlan` 类型 - Data-access-versions(`data-access-versions.ts`):`getLessonPlanVersions` / `createLessonPlanVersion` / `getVersionContent` / `revertToVersion` / `pruneAutoVersions` - Data-access-templates(`data-access-templates.ts`):`getLessonPlanTemplates` / `saveAsTemplate` / `deletePersonalTemplate` -- Data-access-knowledge(`data-access-knowledge.ts`):`getLessonPlansByKnowledgePoint`(V3:增加 userId 参数,按 creatorId 过滤 + 排除 archived)/ `getLessonPlansByQuestion`(V3:增加 userId 参数,按 creatorId 过滤 + 排除 archived) +- Data-access-knowledge(`data-access-knowledge.ts`):`getLessonPlansByKnowledgePoint` / `getLessonPlansByQuestion` - Publish-service(`publish-service.ts`):`publishLessonPlanHomework` - AI-suggest(`ai-suggest.ts`):`suggestKnowledgePoints` -- Actions:`getLessonPlansAction` / `getLessonPlanByIdAction` / `createLessonPlanAction` / `updateLessonPlanAction` / `saveLessonPlanVersionAction` / `getLessonPlanVersionsAction` / `revertLessonPlanVersionAction` / `deleteLessonPlanAction` / `duplicateLessonPlanAction` / `getLessonPlanTemplatesAction` / `saveAsTemplateAction` / `deleteTemplateAction` / `suggestKnowledgePointsAction` / `publishLessonPlanHomeworkAction` / `getKnowledgePointOptionsAction` / `getTextbooksForPickerAction` / `getChaptersForPickerAction` / `publishLessonPlanAction`(V3 新增,requirePermission(LESSON_PLAN_PUBLISH))/ `unpublishLessonPlanAction`(V3 新增,requirePermission(LESSON_PLAN_PUBLISH)) +- Actions:`getLessonPlansAction` / `getLessonPlanByIdAction` / `createLessonPlanAction` / `updateLessonPlanAction` / `saveLessonPlanVersionAction` / `getLessonPlanVersionsAction` / `revertLessonPlanVersionAction` / `deleteLessonPlanAction` / `duplicateLessonPlanAction` / `getLessonPlanTemplatesAction` / `saveAsTemplateAction` / `deleteTemplateAction` / `suggestKnowledgePointsAction` / `publishLessonPlanHomeworkAction` / `getKnowledgePointOptionsAction` / `getTextbooksForPickerAction` / `getChaptersForPickerAction` / `publishLessonPlanAction` / `unpublishLessonPlanAction` **依赖关系**: -- 依赖:`shared/*`、`@/auth`、`shared/lib/ai`、`@xyflow/react`(节点图编辑器)、`textbooks`(只读章节/知识点树)、`questions`(创建/查询题目)、`exams`(创建 exam 草稿)、`homework`(创建作业下发)、`classes`(查询教师班级)、`files`(附件) +- 依赖:`shared/*`、`@/auth`、`shared/lib/ai`、`@tiptap/react`、`@tiptap/starter-kit`、`@tiptap/pm`、`@tiptap/extension-image`(V4:Tiptap 富文本编辑器)、`textbooks`(只读章节/知识点树)、`questions`(创建/查询题目)、`exams`(创建 exam 草稿)、`homework`(创建作业下发)、`classes`(查询教师班级)、`files`(附件) - 被依赖:无 **已知问题**: - ✅ 通过对方 data-access 调用跨模块数据,无直查跨模块表 - ✅ data-access 按职责拆分为 4 个文件(data-access/data-access-versions/data-access-templates/data-access-knowledge) - ✅ actions 按职责拆分为 4 个文件(actions/actions-publish/actions-ai/actions-kp) -- ✅ 编辑器架构升级:NodeEditor(React Flow 画布)+ NodeEditPanel(侧边内容编辑面板)+ LessonNode(自定义节点组件),支持节点拖拽、连线、画布缩放 +- ✅ V4 纸感重构:三栏布局 StructureTree + PaperEditor + DetailPanel,正文采用 Tiptap 富文本编辑器,锚点升级为 Tiptap Mark(AnchorMark/AnchorPoint) +- ✅ V4:删除 8 个 React Flow 画布和字符串锚点相关文件(node-editor/nodes/*/anchor-injector/rf-mappers/auto-layout),无残留引用 - ✅ V4:`block-renderer.tsx`(@deprecated)已删除,无残留引用 > 架构变更(2026-06-22,本次审计修复): @@ -2671,10 +2674,10 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions" | `schema.ts` | Zod 验证(V3:错误消息改为 i18n 键,如 `error.titleRequired`) | | `lib/type-guards.ts` | **集中类型守卫(V3 新增)**:11 种 BlockData 类型守卫(isRichTextBlockData/isTextStudyBlockData/isExerciseBlockData/isObjectiveBlockData/isKeyPointBlockData/isImportBlockData/isNewTeachingBlockData/isSummaryBlockData/isHomeworkBlockData/isBlackboardBlockData/isReflectionBlockData)+ 节点类型守卫(isTextbookContentNode/isLessonPlanNode)+ 题目类型守卫(isValidQuestionType + **V4 导出 `VALID_QUESTION_TYPES` 常量 + `ValidQuestionType` 类型**)+ 基础类型守卫(isLessonPlanStatus/isTemplateType/isTemplateScope/isBlockType)+ **Block 字段值类型守卫(V3 续新增)**:isBlackboardLayout/isImportMethod/isExercisePurpose/isObjectiveDimension/isKeyPointType/isHomeworkType/isReflectionAspect(用于 select onChange 替代 `as` 断言)+ **normalizeTemplateBlocks 规范化函数(V3 续审计新增)**:从 DB `unknown` 安全转换为 `TemplateBlockSkeleton[]`,替代 `as LessonPlanTemplate["blocks"]` 断言 | | `lib/i18n-errors.ts` | **Zod 错误 i18n 翻译辅助(V3 新增)**:`translateFieldErrors`(将 Zod fieldErrors 中的 i18n 键翻译为实际消息)/ `safeParseWithI18n`(安全解析 Zod 结果并返回带翻译的 ActionState 错误格式) | -| `lib/document-migration.ts` | **纯函数**:v1→v2(migrateV1ToV2)/ v2→v3(migrateV2ToV3)/ 规范化(normalizeDocument,兼容 v1/v2/v3)/ 初始内容(buildInitialContent)/ 默认骨架(buildDefaultSkeleton,10 节点 + 正文节点)/ defaultDataForType / 工具函数(isTextbookContentNode/isAnchorEdge/getAnchorsForNode/getActiveAnchorIds/getAnchorEdges) | -| `lib/anchor-injector.ts` | **纯函数**:锚点注入算法(markdownToPlainText/injectPlaceholders/parseAnchoredText/toCircledNumber/getNextPointIndex/relocateAnchors/getAnchorColor;**V4:`markdownToPlainText` 引用去除 regex 与 `skipPatterns` 一致化为 `/^\s*>\s+/gm`,5 个 skipPatterns 统一添加 `g` 标志**) | -| `lib/node-summary.ts` | **纯函数**:getNodeSummary(支持 11 种节点类型)+ getTextbookContentSummary + NODE_COLORS + getNodeColor(V3 续审计:移除 `as { html?: string; ... }` 断言,改用 isRecord/getArrayLength/getString/getNumber 安全字段提取从 `unknown` 收窄) | -| `lib/rf-mappers.ts` | **纯函数**:toRfNodes(V3:移除冗余 `as` 断言,TypeScript 判别联合自动收窄)/ toRfEdges / fromRfEdges | +| `lib/document-migration.ts` | **纯函数**:v1→v2(migrateV1ToV2)/ v2→v3(migrateV2ToV3)/ 规范化(normalizeDocument,兼容 v1/v2/v3/v4)/ 初始内容(buildInitialContent)/ 默认骨架(buildDefaultSkeleton)/ defaultDataForType / 工具函数(isTextbookContentNode/isAnchorEdge/getAnchorsForNode/getActiveAnchorIds/getAnchorEdges) | +| `lib/anchor-mark.ts` | **V4 新增**:Tiptap `AnchorMark` 扩展(区间锚)+ `AnchorPoint` 扩展(点锚),替代 v3 字符串偏移锚点;存储在正文富文本 doc 中 | +| `lib/export.ts` | **V5-4 新增 + V4 增强**:`flattenLessonPlanForPrint`(按 `nodes.order` 排序扁平化为 `PrintableLessonPlan`)+ `flattenBlockData`(V4:新增 `interaction` case + `flattenInteraction` 函数)+ `PrintableLessonPlan` 类型;支持 detailed/concise 两种 variant | +| `lib/node-summary.ts` | **纯函数**:getNodeSummary(支持 12 种节点类型,含 V4 interaction)+ getTextbookContentSummary + NODE_COLORS + getNodeColor(V3 续审计:移除 `as { html?: string; ... }` 断言,改用 isRecord/getArrayLength/getString/getNumber 安全字段提取从 `unknown` 收窄) | | `config/block-registry.tsx` | **配置驱动**:BLOCK_REGISTRY 注册表 + BlockRenderer(V3:使用 `lib/type-guards.ts` 类型守卫替代所有 `as XxxBlockData` 断言) | | `providers/lesson-plan-provider.tsx` | **Provider + Context**:LessonPlanProvider 注入数据服务/角色配置/埋点;定义 LessonPlanDataService 接口(V3:扩展 5 个方法 getLessonPlanById/updateLessonPlan/saveLessonPlanVersion/publishLessonPlan/unpublishLessonPlan;**V3 续扩展 7 个方法 createLessonPlan/getTextbooksForPicker/getChaptersForPicker/getLessonPlanTemplates/getKnowledgePointOptions/publishLessonPlanHomework/getQuestions + 6 个导出类型**)、4 个角色配置、ROLE_CONFIGS 注册表、LessonPlanTracker 接口 + noopTracker;hooks:useLessonPlanContextSafe/useLessonPlanContext/useRoleConfig/useLessonPlanService/useLessonPlanTracker/useLessonPlanTrackerSafe | | `providers/lesson-plan-provider-setup.tsx` | **页面层 Provider 设置包装组件(V3 续新增)**:LessonPlanProviderSetup 自动注入默认数据服务(createDefaultDataService)和角色配置(TEACHER_ROLE_CONFIG),3 个 teacher 页面用此组件包裹 | @@ -2691,20 +2694,39 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions" | `ai-suggest.ts` | AI 知识点建议服务(V3 续审计:移除 `as Record` 断言改用 `isRecord` 类型守卫;AI prompt 提取为 `AI_SUGGEST_PROMPT_TEMPLATE` 模块常量) | | `seed-templates.ts` | 模板种子数据 | | `hooks/use-lesson-plan-editor.ts` | **课案编辑器 Zustand store 主文件(V4:从 303 行单体 store 重写为 25 行薄层,组合 3 个 slice 并导出 `EditorState` 类型)**(V3:`updateNode` patch 类型改为 `Omit, "type">` 防止类型变更) | -| `hooks/editor-slice.ts` | **编辑器 slice(V4 P2-6 新增)**:Zustand `StateCreator`,包含 planId/title/doc 状态及所有文档操作方法(addNode/updateNode/updateNodePosition/removeNode/updateTextbookContent/getTextbookContentNode/addAnchor/removeAnchor/updateAnchor/connect/disconnect/setEdges/setTitle/setPlanId) | +| `hooks/editor-slice.ts` | **编辑器 slice(V4 P2-6 新增 + V4 纸感重构)**:Zustand `StateCreator`,包含 planId/title/doc 状态及所有文档操作方法(addNode/updateNode/removeNode/updateTextbookContent/getTextbookContentNode/addAnchor/removeAnchor/updateAnchor/setTitle/setPlanId);**V4 移除**:updateNodePosition/connect/disconnect/setEdges/autoLayout(画布相关方法) | | `hooks/selection-slice.ts` | **选中状态 slice(V4 P2-6 新增)**:Zustand `StateCreator`,包含 selectedNodeId 状态 + selectNode 方法 | -| `hooks/version-slice.ts` | **版本与保存状态 slice(V4 P2-6 新增)**:Zustand `StateCreator`,包含 isDirty/isSaving/lastSavedAt 状态 + hydrate/markSaved/setSaving/replaceDoc 方法 | +| `hooks/version-slice.ts` | **版本与保存状态 slice(V4 P2-6 新增)**:Zustand `StateCreator`,包含 isDirty/isSaving/lastSavedAt/saveError/isOnline 状态 + hydrate/markSaved/setSaving/replaceDoc/setSaveError/setOnline 方法 | +| `hooks/history-slice.ts` | **撤销/重做 slice(V5-2 新增)**:Zustand `StateCreator`,past/future 栈(50 步上限)+ canUndo/canRedo/pushHistory/undo/redo/clearHistory 方法 | +| `hooks/expanded-slice.ts` | **V4 纸感重构新增**:Zustand `StateCreator`,节点展开到正文流状态(expandedNodeIds/toggleExpand/setExpanded) | | `components/lesson-plan-list.tsx` | 课案列表(V3:完全通过 service 调用,移除直接 import `getLessonPlansAction`;V3 续审计:新增 `isLoading` 状态,加载时显示加载文本,传递给 `LessonPlanFilters`) | | `components/lesson-plan-card.tsx` | 课案卡片(V3:完全通过 service 调用,移除直接 import actions;V3 续审计:4 个异步处理函数用 `useCallback` 包裹性能优化) | | `components/lesson-plan-filters.tsx` | 课案筛选器(V3 续审计:新增 `isLoading` prop,加载时禁用输入并设置 `aria-busy`) | -| `components/lesson-plan-editor.tsx` | 课案编辑器(V3:完全通过 service 调用,移除直接 import 5 个 actions;所有操作改为 `service.updateLessonPlan`/`service.saveLessonPlanVersion`/`service.getLessonPlanById`/`service.publishLessonPlan`/`service.unpublishLessonPlan`) | -| `components/lesson-plan-readonly-view.tsx` | **只读画布组件**(V3:MiniMap 使用 `getNodeColor` 替代硬编码颜色) | -| `components/node-editor.tsx` | **节点图画布**(V3:MiniMap `nodeColor` 使用类型守卫替代 `as { node?: AnyLessonPlanNode }` 断言) | -| `components/node-edit-panel.tsx` | **侧边内容编辑面板**(V3:移除冗余 `as LessonPlanNode` 断言,TypeScript 判别联合自动收窄) | -| `components/nodes/lesson-node.tsx` | **自定义教学节点组件**(V3 续审计:使用 `isLessonNodeData` 类型守卫替代 `as { node: LessonPlanNode }` 断言 + 空状态回退) | -| `components/nodes/textbook-content-node.tsx` | **正文节点组件**(V3 续:拆分为 3 文件,主文件 471 行;`as unknown as` 替换为 `isTextbookContentNodePropsData` 类型守卫) | -| `components/nodes/anchor-node-selector.tsx` | **锚点节点选择器(V3 续新增,从 textbook-content-node.tsx 抽取)**:渲染可锚定教学节点列表 + 关联到选中节点 + 创建新节点选项 | -| `components/nodes/textbook-segments.tsx` | **锚点段落渲染函数(V3 续新增,从 textbook-content-node.tsx 抽取)**:renderSegments 遍历 segments 数组渲染文本/区间锚点/点锚点 | +| `components/lesson-plan-editor.tsx` | 课案编辑器(V3:完全通过 service 调用;**V4 纸感重构**:移除 NodeEditor/NodeEditPanel,改用三栏布局 StructureTree + PaperEditor + DetailPanel,保留顶部工具栏 + 5 个对话框) | +| `components/lesson-plan-readonly-view.tsx` | **只读视图**(V3:MiniMap 使用 `getNodeColor` 替代硬编码颜色;**V4**:移除 React Flow 依赖,委托 `LessonPlanMobileView` 线性卡片视图渲染所有视口) | +| `components/anchor-migration-banner.tsx` | **V4 新增**:v3 锚点失效提示 banner,检测 `doc.anchors` 中 `invalid:true` 或含 `start` 字段的旧锚点;通过 `key={planId}` 重新挂载 + lazy initial state 读取 localStorage 避免 setState in effect | +| `components/print-view.tsx` | **V5-4 新增 + V4 适配**:打印视图组件,按 `nodes.order` 排序渲染,详细版/简洁版切换 + `window.print()` | +| `components/schedule-dialog.tsx` | **V5-7 新增**:课案绑定课时对话框 | +| `components/consistency-check-dialog.tsx` | **V5-19 新增**:目标-评价一致性校验对话框 | +| `components/ai-feedback-dialog.tsx` | **V5-17 新增**:AI 反馈闭环对话框(4 维度 strengths/improvements/alignment/differentiation) | +| `components/ai-differentiation-dialog.tsx` | **V5-21 新增**:AI 差异化教学对话框(3 Tab:差异化建议/课标核对/可解释评估) | +| `components/curriculum-heatmap.tsx` | **V5-20 新增**:课标覆盖热力图组件 | +| `components/version-diff-view.tsx` | **V5-16 新增**:版本对比视图组件 | +| `components/lesson-plan-mobile-view.tsx` | **V5-13 新增**:移动端只读视图(按教学阶段分组线性卡片视图);**V4**:被 `lesson-plan-readonly-view.tsx` 委托渲染所有视口 | +| `components/attachment-picker.tsx` | **V5-5 新增**:素材库 picker(复用 use-file-upload + M6 附件表 data-access) | +| `components/paper-editor/paper-editor.tsx` | **V4 新增**:纸感编辑器容器(中栏),承载 Tiptap 富文本编辑器 | +| `components/paper-editor/paper-toolbar.tsx` | **V4 新增**:纸感编辑器工具栏 | +| `components/paper-editor/paper-context-menu.tsx` | **V4 新增**:纸感编辑器右键菜单 | +| `components/paper-editor/textbook-tiptap-editor.tsx` | **V4 新增**:教材正文 Tiptap 编辑器(集成 AnchorMark/AnchorPoint 扩展) | +| `components/paper-editor/inline-node.tsx` | **V4 新增**:内联节点渲染组件 | +| `components/paper-editor/inline-qa-dialog.tsx` | **V4 新增**:内联问答对话框 | +| `components/structure-tree/structure-tree.tsx` | **V4 新增**:结构树容器(左栏 260px),支持节点展开到正文流 | +| `components/structure-tree/tree-node-row.tsx` | **V4 新增**:结构树节点行组件 | +| `components/detail-panel/detail-panel.tsx` | **V4 新增**:详情面板容器(右栏 380px) | +| `components/detail-panel/detail-head.tsx` | **V4 新增**:详情面板头部 | +| `components/detail-panel/detail-props.tsx` | **V4 新增**:详情面板属性编辑 | +| `components/detail-panel/qa-editor.tsx` | **V4 新增**:师生问答编辑器(interaction block 配套) | +| `components/blocks/interaction-block.tsx` | **V4 新增**:师生交互 Block(designIntent + turns[] + expectedAnswer) | | `components/lesson-plan-error-boundary.tsx` | **错误边界**(V3 续:重写为包装组件模式——内部类组件接受 `errorText`/`retryText` props,外部函数组件通过 `useTranslations` 注入 i18n 文案) | | `components/lesson-plan-skeleton.tsx` | **骨架屏**:VersionListSkeleton/QuestionBankSkeleton/KnowledgePointSkeleton/LessonPlanListSkeleton | | `components/template-picker.tsx` | 模板选择器(V3 续:完全通过 service 调用,移除直接 import 4 个 actions;V3 续审计:通过 `ChapterPickerOption.children` 递归类型消除 `as ChapterPickerOption[]` 断言) | @@ -2788,6 +2810,17 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions" > - **i18n 扩展**:zh-CN/en 同步新增 editor.stage*/editor.differentiation*/editor.consistency*/consistency.*/diff.*/library.*/feedback.*/heatmap.*/aiDifferentiation.*/blackboard.editMode/previewMode/editHint/previewEmpty/untitled > - **V5-11 R3 协同编辑**:需引入新依赖(Yjs/Liveblocks)或采用轻量协作方案(评论+@提醒+版本对比),暂标记为待规划,待用户决策 +> 架构变更(2026-07-04,V4 纸感重构 — 抛弃 React Flow 画布,改用三栏布局 + Tiptap 富文本编辑器): +> - **H1 interaction block 注册**:`config/block-registry.tsx` 新增 `interaction` case,导入 `InteractionBlock` 组件 + `isInteractionBlockData` 类型守卫;BLOCK_REGISTRY 支持 12 种 Block 类型(含 V4 interaction) +> - **H2 三栏布局重构**:`lesson-plan-editor.tsx` 移除 `NodeEditor`/`NodeEditPanel` 导入与 JSX,改用 `StructureTree + PaperEditor + DetailPanel` 三栏布局(`grid-template-columns: 260px 1fr 380px`),保留顶部工具栏(标题/保存/撤销重做/发布/导出/安排课时/一致性校验/AI 反馈/AI 差异化)+ 5 个对话框(VersionHistoryDrawer/ConsistencyCheckDialog/AiFeedbackDialog/AiDifferentiationDialog/PrintView/ScheduleDialog);删除 `components/node-edit-panel.tsx` + `app/(dashboard)/teacher/lesson-plans/[planId]/edit/ai-content-generator-slot.tsx`(依赖 NodeEditPanel 类型) +> - **I1 v3 锚点失效 banner**:新增 `components/anchor-migration-banner.tsx`,检测 `doc.anchors` 中 `invalid:true` 或含 `start` 字段的旧锚点并提示用户;通过 `key={planId}` 实现 planId 变化时重新挂载内部组件,使用 lazy initial state 读取 localStorage(dismiss 状态按 planId 隔离),避免 useEffect 中 setState(符合 V4 `react-hooks/set-state-in-effect` lint 规则) +> - **J1 删除废弃文件**:删除 8 个 React Flow 画布和字符串锚点相关文件——`components/node-editor.tsx`、`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`、`lib/auto-layout.ts`;`lesson-plan-readonly-view.tsx` 重写为委托 `LessonPlanMobileView`(线性卡片视图)渲染所有视口,移除 React Flow 依赖 +> - **J2 PrintView 适配 V4**:`lib/export.ts` 的 `flattenBlockData` switch 新增 `interaction` case + `flattenInteraction` 函数(输出设计意图 + 师生对话回合 + 预期回答);export.ts 已使用 `nodes.sort((a, b) => a.order - b.order)` 按顺序扁平化,无画布依赖;docstring 更新为 12 种 Block(含 V4 interaction) +> - **J3 lint + tsc 零错误验证**:tsc --noEmit 0 错误;lint 中 V4 引入的 `react-hooks/set-state-in-effect` 错误已通过 `anchor-migration-banner.tsx` 的 key-based remount + lazy initial state 模式修复;剩余 6 个 lint 错误为预存无关模块问题(exams/use-exam-preview-tasks react-hooks/refs 3 个、template-picker V5-9 set-state-in-effect 1 个),按计划要求记录不修复 +> - **J4 架构图同步**:004_architecture_impact_map.md 新增 V4 纸感重构章节;005_architecture_data.json 更新 lesson_preparation 节点——description 改为 V4 描述、dependencies 移除 `@xyflow/react` 改为 `@tiptap/*` 包、files 数组移除 9 个废弃文件并新增 V4 组件、auditFixes 新增 V4-PAPER-1~8 + V4-PAPER-DOC 条目 +> - **J5 known-issues V4 规则**:`docs/troubleshooting/known-issues.md` 新增 V4 纸感重构规则表,记录 8 条规则——React Flow 弃用、Tiptap Mark 锚点替代字符串偏移、setState in effect 禁用、doc.version=4、editor-slice 移除画布相关方法、interaction block 注册、PrintView 按 order 排序、AnchorMigrationBanner 失效提示 +> - **数据模型 v4**:`types.ts` `LessonPlanDocument.version = 4`;`lib/document-migration.ts` `normalizeDocument` 兼容 v1/v2/v3/v4 链式迁移;`editor-slice.ts` 移除 `updateNodePosition`/`connect`/`setEdges`/`autoLayout` 画布相关方法;`hooks/expanded-slice.ts` 新增 `expandedNodeIds`/`toggleExpand`/`setExpanded` 状态(节点展开到正文流);`lib/anchor-mark.ts` 新增 Tiptap `AnchorMark` + `AnchorPoint` 扩展(替代 v3 字符串偏移锚点) + --- ## 2.27b standards(课标模块,2026-07-01 新增) diff --git a/docs/architecture/005_architecture_data.json b/docs/architecture/005_architecture_data.json index 6d826fd..90ab252 100644 --- a/docs/architecture/005_architecture_data.json +++ b/docs/architecture/005_architecture_data.json @@ -20330,7 +20330,7 @@ }, "lesson_preparation": { "path": "src/modules/lesson-preparation", - "description": "教师备课模块(v3 锚点画布):以课文正文为核心主体(textbook_content 节点置于画布中央,draggable=false 可缩放),教学目标/重难点等 11 种定制节点围绕正文组织。两种锚定机制:range(选择一段文本关联节点,未选中 opacity:0)+ point(插入带圈数字占位符,未选中 opacity:0.3)。锚点连线默认 10% 透明度,选中时完整显示。数据模型 v3:LessonPlanDocument 含 textbookContentNodeId、nodes、edges、anchors 数组。anchor-injector 实现 Markdown 纯文本偏移映射与占位符注入。实时拖动(onNodeDrag 替代 onNodeDragStop)。v1→v2→v3 链式迁移。强制选定教材+章节才能备课(template-picker 四步流程)。保留模板、版本管理、知识点标注、题目创建/拉取、作业发布能力", + "description": "教师备课模块(V4 纸感重构):抛弃 React Flow 画布,改用三栏布局(260px 结构树 + 1fr 纸感编辑器 + 380px 详情面板)。正文采用 Tiptap 富文本编辑器,锚点从字符串偏移升级为 Tiptap Mark(AnchorMark 区间锚/AnchorPoint 点锚),v3 旧锚点通过 AnchorMigrationBanner 提示失效。doc.version=4,editor-slice 移除 updateNodePosition/connect/setEdges/autoLayout。新增 interaction block(师生交互对话回合,含 designIntent/turns[]/expectedAnswer)。结构树支持节点展开到正文流(expanded-slice)。PrintView 改为按 nodes.order 排序渲染,不再依赖画布。v1→v2→v3→v4 链式迁移。保留模板、版本管理、知识点标注、题目创建/拉取、作业发布、撤销/重做、AI 反馈/差异化、一致性校验、课时安排等能力", "exports": { "dataAccess": [ { @@ -20862,7 +20862,10 @@ "classes", "files", "shared/lib/ai", - "@xyflow/react" + "@tiptap/react", + "@tiptap/starter-kit", + "@tiptap/pm", + "@tiptap/extension-image" ], "files": [ "types.ts", @@ -20871,13 +20874,17 @@ "lib/type-guards.ts", "lib/i18n-errors.ts", "lib/document-migration.ts", - "lib/anchor-injector.ts", "lib/node-summary.ts", - "lib/rf-mappers.ts", + "lib/anchor-mark.ts", + "lib/export.ts", + "lib/scope-check.ts", + "lib/document-diff.ts", "config/block-registry.tsx", "providers/lesson-plan-provider.tsx", "providers/lesson-plan-provider-setup.tsx", "services/default-data-service.ts", + "services/default-question-service.ts", + "services/external-questions-bridge.ts", "data-access.ts", "data-access-versions.ts", "data-access-templates.ts", @@ -20886,6 +20893,7 @@ "actions-publish.ts", "actions-ai.ts", "actions-kp.ts", + "actions-questions.ts", "publish-service.ts", "ai-suggest.ts", "seed-templates.ts", @@ -20893,17 +20901,13 @@ "hooks/editor-slice.ts", "hooks/selection-slice.ts", "hooks/version-slice.ts", + "hooks/history-slice.ts", + "hooks/expanded-slice.ts", "components/lesson-plan-list.tsx", "components/lesson-plan-card.tsx", "components/lesson-plan-filters.tsx", "components/lesson-plan-editor.tsx", "components/lesson-plan-readonly-view.tsx", - "components/node-editor.tsx", - "components/node-edit-panel.tsx", - "components/nodes/lesson-node.tsx", - "components/nodes/textbook-content-node.tsx", - "components/nodes/anchor-node-selector.tsx", - "components/nodes/textbook-segments.tsx", "components/lesson-plan-error-boundary.tsx", "components/lesson-plan-skeleton.tsx", "components/template-picker.tsx", @@ -20912,6 +20916,29 @@ "components/question-bank-picker.tsx", "components/inline-question-editor.tsx", "components/publish-homework-dialog.tsx", + "components/anchor-migration-banner.tsx", + "components/print-view.tsx", + "components/schedule-dialog.tsx", + "components/consistency-check-dialog.tsx", + "components/ai-feedback-dialog.tsx", + "components/ai-differentiation-dialog.tsx", + "components/curriculum-heatmap.tsx", + "components/version-diff-view.tsx", + "components/version-diff-viewer.tsx", + "components/lesson-plan-mobile-view.tsx", + "components/attachment-picker.tsx", + "components/paper-editor/paper-editor.tsx", + "components/paper-editor/paper-toolbar.tsx", + "components/paper-editor/paper-context-menu.tsx", + "components/paper-editor/textbook-tiptap-editor.tsx", + "components/paper-editor/inline-node.tsx", + "components/paper-editor/inline-qa-dialog.tsx", + "components/structure-tree/structure-tree.tsx", + "components/structure-tree/tree-node-row.tsx", + "components/detail-panel/detail-panel.tsx", + "components/detail-panel/detail-head.tsx", + "components/detail-panel/detail-props.tsx", + "components/detail-panel/qa-editor.tsx", "components/blocks/rich-text-block.tsx", "components/blocks/text-study-block.tsx", "components/blocks/exercise-block.tsx", @@ -20923,6 +20950,7 @@ "components/blocks/summary-block.tsx", "components/blocks/homework-block.tsx", "components/blocks/blackboard-block.tsx", + "components/blocks/interaction-block.tsx", "components/calendar-view.tsx", "components/curriculum-map-view.tsx", "data-access-calendar.ts", @@ -20933,6 +20961,7 @@ "data-access-substitutes.ts", "data-access-ai-evaluation.ts", "data-access-analytics.ts", + "data-access-schedules.ts", "actions-calendar.ts", "actions-review.ts", "actions-comments.ts", @@ -20940,7 +20969,8 @@ "actions-attachments.ts", "actions-substitutes.ts", "actions-ai-evaluation.ts", - "actions-analytics.ts" + "actions-analytics.ts", + "actions-schedules.ts" ], "i18n": { "namespace": "lessonPreparation", @@ -21020,7 +21050,16 @@ "V5-2": "撤销/重做:新增 hooks/history-slice.ts(past/future 栈,50 步上限,canUndo/canRedo/pushHistory/undo/redo/clearHistory 方法);editor-slice.ts 所有 mutation 方法(setTitle/addNode/updateNode/removeNode/updateTextbookContent/addAnchor/removeAnchor/updateAnchor/connect/disconnect/setEdges)包装 pushHistory,updateNodePosition 除外(拖拽过程中由调用方在 onDragStart 时推入);version-slice hydrate/replaceDoc 调用 clearHistory;lesson-plan-editor.tsx 顶部新增 Undo/Redo 按钮 + Ctrl/Cmd+Z 撤销 + Ctrl/Cmd+Shift+Z 或 Ctrl/Cmd+Y 重做快捷键", "V5-3": "发布前作业单预览:publish-homework-dialog.tsx 改为 3 步流程(step=select 选班级+时间 → step=preview 预览题目+总分+班级数 → step=confirm 确认发布),第二步显示题目列表(题型/来源/题干预览/分值)+汇总信息(班级数/题目数/总分),第三步显示最终确认信息+警告文案;exercise-block.tsx 调用 PublishHomeworkDialog 时传入 items={data.items}", "V5-4": "导出/打印:新增 lib/export.ts(flattenLessonPlanForPrint 函数,将画布式 LessonPlanDocument 扁平化为 PrintableLessonPlan 线性结构,支持 detailed/concise 两种 variant,concise 仅含 objective/new_teaching/exercise/homework)+ components/print-view.tsx(打印视图组件,详细版/简洁版切换 + 调用 window.print() 通过浏览器原生能力保存为 PDF + print: CSS 媒体查询隐藏工具栏 + 页眉含教材/教师/班级/教学时长/最后保存时间 + break-inside-avoid 防止段落跨页);lesson-plan-editor.tsx 新增 printablePlan useMemo 构造 + 导出按钮 + PrintView 渲染", - "V5-5": "多媒体嵌入 + 我的素材库:types.ts RichTextBlockData 新增 attachments?: RichTextAttachment[] 字段 + RichTextAttachment 类型(attachmentId/fileId/displayName/kind: image|audio|video|file/url/mimeType?);rich-text-block.tsx 集成 @tiptap/extension-image(Image.configure inline:false allowBase64:false)+ 新增附件入口按钮 + 已嵌入附件列表渲染(非图片附件列表 + 图片附件缩略图,含删除按钮)+ 图片附件通过 editor.commands.setImage 直接插入富文本;新增 components/attachment-picker.tsx(素材库 picker,复用 use-file-upload hook 调用 /api/upload + 调用 service.createLessonPlanAttachment 落库到 M6 lessonPlanAttachments 表 + 列出 service.getLessonPlanAttachments + 删除 service.deleteLessonPlanAttachment + inferAttachmentKind 工具函数);LessonPlanDataService 接口新增 getLessonPlanAttachments/createLessonPlanAttachment/deleteLessonPlanAttachment 三个方法 + LessonPlanAttachmentOption 导出类型;default-data-service.ts 实现三个方法(DB 层 Date 转 ISO string 以匹配接口类型);BlockRenderProps 新增 planId? 字段;NodeEditPanel 新增 planId prop 透传;lesson-plan-editor.tsx NodeEditPanel 调用传入 planId;新增依赖 @tiptap/extension-image;files/types.ts FileTargetType 新增 'lesson_plan' 类型" + "V5-5": "多媒体嵌入 + 我的素材库:types.ts RichTextBlockData 新增 attachments?: RichTextAttachment[] 字段 + RichTextAttachment 类型(attachmentId/fileId/displayName/kind: image|audio|video|file/url/mimeType?);rich-text-block.tsx 集成 @tiptap/extension-image(Image.configure inline:false allowBase64:false)+ 新增附件入口按钮 + 已嵌入附件列表渲染(非图片附件列表 + 图片附件缩略图,含删除按钮)+ 图片附件通过 editor.commands.setImage 直接插入富文本;新增 components/attachment-picker.tsx(素材库 picker,复用 use-file-upload hook 调用 /api/upload + 调用 service.createLessonPlanAttachment 落库到 M6 lessonPlanAttachments 表 + 列出 service.getLessonPlanAttachments + 删除 service.deleteLessonPlanAttachment + inferAttachmentKind 工具函数);LessonPlanDataService 接口新增 getLessonPlanAttachments/createLessonPlanAttachment/deleteLessonPlanAttachment 三个方法 + LessonPlanAttachmentOption 导出类型;default-data-service.ts 实现三个方法(DB 层 Date 转 ISO string 以匹配接口类型);BlockRenderProps 新增 planId? 字段;NodeEditPanel 新增 planId prop 透传;lesson-plan-editor.tsx NodeEditPanel 调用传入 planId;新增依赖 @tiptap/extension-image;files/types.ts FileTargetType 新增 'lesson_plan' 类型", + "V4-PAPER-1": "H1 注册 interaction block:config/block-registry.tsx 新增 interaction case,导入 InteractionBlock 组件 + isInteractionBlockData 类型守卫;BLOCK_REGISTRY 支持 12 种 Block 类型(含 V4 interaction)", + "V4-PAPER-2": "H2 三栏布局重构:lesson-plan-editor.tsx 移除 NodeEditor/NodeEditPanel 导入与 JSX,改用 StructureTree + PaperEditor + DetailPanel 三栏布局(grid-template-columns: 260px 1fr 380px),保留顶部工具栏(标题/保存/撤销重做/发布/导出/安排课时/一致性校验/AI 反馈/AI 差异化)+ 5 个对话框(VersionHistoryDrawer/ConsistencyCheckDialog/AiFeedbackDialog/AiDifferentiationDialog/PrintView/ScheduleDialog);删除 components/node-edit-panel.tsx + app/(dashboard)/teacher/lesson-plans/[planId]/edit/ai-content-generator-slot.tsx(依赖 NodeEditPanel 类型)", + "V4-PAPER-3": "I1 v3 锚点失效 banner:新增 components/anchor-migration-banner.tsx,检测 doc.anchors 中 invalid:true 或含 start 字段的旧锚点并提示用户;通过 key={planId} 实现 planId 变化时重新挂载内部组件,使用 lazy initial state 读取 localStorage(dismiss 状态按 planId 隔离),避免 useEffect 中 setState(符合 V4 react-hooks/set-state-in-effect lint 规则)", + "V4-PAPER-4": "J1 删除废弃文件:删除 8 个 React Flow 画布和字符串锚点相关文件——components/node-editor.tsx、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、lib/auto-layout.ts;lesson-plan-readonly-view.tsx 重写为委托 LessonPlanMobileView(线性卡片视图)渲染所有视口,移除 React Flow 依赖", + "V4-PAPER-5": "J2 PrintView 适配 V4:lib/export.ts 的 flattenBlockData switch 新增 interaction case + flattenInteraction 函数(输出设计意图 + 师生对话回合 + 预期回答);export.ts 已使用 nodes.sort((a, b) => a.order - b.order) 按顺序扁平化,无画布依赖;docstring 更新为 12 种 Block(含 V4 interaction)", + "V4-PAPER-6": "J3 lint + tsc 零错误验证:tsc --noEmit 0 错误;lint 中 V4 引入的 react-hooks/set-state-in-effect 错误已通过 anchor-migration-banner.tsx 的 key-based remount + lazy initial state 模式修复;剩余 6 个 lint 错误为预存无关模块问题(exams/use-exam-preview-tasks react-hooks/refs 3 个、template-picker V5-9 set-state-in-effect 1 个),按计划要求记录不修复", + "V4-PAPER-7": "J4 架构图同步:004_architecture_impact_map.md 新增 V4 纸感重构章节;005_architecture_data.json 更新 lesson_preparation 节点——description 改为 V4 描述、dependencies 移除 @xyflow/react 改为 @tiptap/* 包、files 数组移除 9 个废弃文件并新增 V4 组件(paper-editor/structure-tree/detail-panel/interaction-block/anchor-migration-banner/print-view/schedule-dialog/consistency-check-dialog/ai-feedback-dialog/ai-differentiation-dialog/expanded-slice/history-slice/anchor-mark/export/scope-check/document-diff 等)、auditFixes 新增 V4-PAPER-1~7 条目", + "V4-PAPER-8": "J5 known-issues V4 规则:docs/troubleshooting/known-issues.md 新增 V4 纸感重构规则表,记录 8 条规则——React Flow 弃用、Tiptap Mark 锚点替代字符串偏移、setState in effect 禁用(用 key-based remount + lazy initial state 替代)、doc.version=4、editor-slice 移除画布相关方法、interaction block 注册、PrintView 按 order 排序、AnchorMigrationBanner 失效提示", + "V4-PAPER-DOC": "documentVersion 升级为 4:types.ts LessonPlanDocument.version = 4;lib/document-migration.ts normalizeDocument 兼容 v1/v2/v3/v4 链式迁移;editor-slice.ts 移除 updateNodePosition/connect/setEdges/autoLayout 画布相关方法;hooks/expanded-slice.ts 新增 expandedNodeIds/toggleExpand/setExpanded 状态(节点展开到正文流);lib/anchor-mark.ts 新增 Tiptap AnchorMark + AnchorPoint 扩展(替代 v3 字符串偏移锚点)" } }, "error-book": {