- Update architecture impact map, data, feature checklist, gap audit - Add audit reports for dashboard, exam-homework, grades-diagnostic, settings-profile, textbooks - Update bug reports (admin, teacher, lesson-preparation, others, shared) - Update coding standards, DR plan, design docs, and README
544 lines
20 KiB
Markdown
544 lines
20 KiB
Markdown
# 备课模块重构设计 — 课文锚点画布
|
||
|
||
**日期**:2026-06-22
|
||
**状态**:已确认,待实现
|
||
**作者**:brainstorming session
|
||
|
||
## 背景与目标
|
||
|
||
当前备课模块基于 React Flow 节点图编辑器(v2 nodes+edges),支持 12 种 Block 类型、版本管理、自动保存、模板系统。但存在以下问题:
|
||
|
||
1. **创建课案时无法选择教材/章节**(UI 缺失),所有课案 `textbookId/chapterId` 都是 null
|
||
2. **TextStudyBlock 与教材课文完全脱节**,教师手动粘贴纯文本到 textarea
|
||
3. **编辑器内无法切换关联的教材/章节**
|
||
4. **节点与课文无关联**,无法体现教学流程的时间线
|
||
|
||
**本次重构目标**:以课文正文为核心主体,教学节点围绕课文组织,通过锚点机制建立节点与课文位置的关联,形成教学流程时间线。
|
||
|
||
## 核心设计决策
|
||
|
||
### 决策 1:1 课案 = 1 课文
|
||
|
||
一个课案对应一篇课文(如《秋天》第一课时)。课文正文在画布中央作为核心主体,教学目标/重难点/导入/新授等节点围绕课文组织。
|
||
|
||
### 决策 2:画布式锚点布局
|
||
|
||
保留 React Flow 画布交互(缩放/平移/节点拖动/连线),课文作为特殊节点类型 `textbook_content` 嵌在画布中央,`draggable: false`(不可移动)但可缩放。
|
||
|
||
### 决策 3:两种锚定方式
|
||
|
||
- **范围锚定(range)**:选中一段文字 → 关联节点。文本背景色 = 节点颜色,默认 `opacity: 0`(完全透明),选中时 `opacity: 0.3`
|
||
- **点锚定(point)**:点击文本某位置 → 插入占位符标记(①②③)。默认 `opacity: 0.3`(半透明),选中时 `opacity: 1`(不透明)
|
||
|
||
### 决策 4:连线透明度策略
|
||
|
||
- 锚点连线(anchor):默认 10%,选中节点时 100%
|
||
- 流程连线(flow):节点间教学流程连线,正常显示
|
||
|
||
### 决策 5:每个节点类型完全定制字段
|
||
|
||
每个节点类型有独特的字段和交互,不是统一富文本。详见第 2 节。
|
||
|
||
### 决策 6:默认骨架 10 节点
|
||
|
||
创建课案时强制选择教材/章节,自动生成 10 个默认教学节点 + 1 个正文节点。
|
||
|
||
### 决策 7:实时拖动
|
||
|
||
节点拖动改为实时更新位置(`onNodeDrag`),而非当前的 `onNodeDragStop` 才更新。
|
||
|
||
### 决策 8:颜色保持现有方案
|
||
|
||
节点颜色复用 `lib/node-summary.ts` 的 `getNodeColor`,不改变现有配色。
|
||
|
||
## 第 1 节:整体架构与数据模型
|
||
|
||
### 1.1 整体布局
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 顶部工具栏:标题 | 教材/章节 | 保存状态 | 版本 | 保存按钮 │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌─────────┐ ┌──────────────┐ ┌─────────┐ │
|
||
│ │ 导入 │───→│ 课文正文 │←───│ 新授 │ │
|
||
│ │ 节点 │ │ (固定中央) │ │ 节点 │ │
|
||
│ └─────────┘ │ │ └─────────┘ │
|
||
│ │ 天气凉了① │ │
|
||
│ ┌─────────┐ │ 天空那么蓝②│ ┌─────────┐ │
|
||
│ │ 文本研习│───→│ ... │←───│ 练习 │ │
|
||
│ │ 节点 │ │ │ └─────────┘ │
|
||
│ └─────────┘ └──────────────┘ │
|
||
│ │
|
||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||
│ │ 教学目标│ │ 重难点 │ │ 作业 │ (未锚定节点) │
|
||
│ └─────────┘ └─────────┘ └─────────┘ │
|
||
│ │
|
||
│ [+ 添加节点] [+] [-] [⌖] │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 1.2 数据模型升级(v2 → v3)
|
||
|
||
```typescript
|
||
// 新增:正文节点类型
|
||
interface TextbookContentNodeData {
|
||
chapterId: string;
|
||
content: string; // Markdown 正文(缓存)
|
||
zoom: number; // 缩放比例 0.5-2.0
|
||
}
|
||
|
||
// 新增:正文节点(继承 LessonPlanNode)
|
||
interface TextbookContentNode extends LessonPlanNode {
|
||
type: "textbook_content";
|
||
data: TextbookContentNodeData;
|
||
draggable: false; // 不可拖动
|
||
}
|
||
|
||
// 新增:锚点类型
|
||
type AnchorType = "range" | "point";
|
||
|
||
interface NodeAnchor {
|
||
id: string;
|
||
nodeId: string; // 关联的教学节点 ID
|
||
type: AnchorType;
|
||
start: number; // 正文纯文本偏移量
|
||
end?: number; // range 锚定的结束偏移
|
||
textPreview?: string; // range 锚定的文字预览
|
||
}
|
||
|
||
// 新增:边类型
|
||
type EdgeType = "anchor" | "flow";
|
||
|
||
interface AnchorEdge extends LessonPlanEdge {
|
||
type: "anchor";
|
||
source: string; // 教学节点 ID
|
||
target: string; // 正文节点 ID
|
||
anchorId: string; // 关联的 NodeAnchor ID
|
||
}
|
||
|
||
interface FlowEdge extends LessonPlanEdge {
|
||
type: "flow"; // 教学流程连线(如 导入→新授)
|
||
}
|
||
|
||
// 升级:LessonPlanDocument v3
|
||
interface LessonPlanDocument {
|
||
version: 3;
|
||
textbookContentNodeId: string; // 正文节点 ID(唯一)
|
||
nodes: (LessonPlanNode | TextbookContentNode)[];
|
||
edges: (AnchorEdge | FlowEdge)[];
|
||
anchors: NodeAnchor[]; // 新增:锚点数组
|
||
}
|
||
```
|
||
|
||
### 1.3 迁移策略
|
||
|
||
- `migrateV2ToV3(doc, chapterId?, chapterContent?)`:将 v2 文档升级为 v3
|
||
- 如果有关联的 chapterId,创建 `TextbookContentNode` 注入正文
|
||
- 现有节点保留,`edges` 保留为 `flow` 类型
|
||
- `anchors` 初始化为空数组
|
||
- `normalizeDocument` 优先识别 v3,v2 自动迁移
|
||
|
||
## 第 2 节:节点类型与定制字段
|
||
|
||
### 2.1 节点类型清单(11 种 + 1 正文节点)
|
||
|
||
| 类型 | BlockType | 定制字段 | 输入 | 输出 |
|
||
|------|-----------|---------|------|------|
|
||
| 教学目标 | `objective` | `objectives: { dimension, text }[]` | 三维目标列表 | 结构化目标 |
|
||
| 重难点 | `key_point` | `keyPoints: { type: "key"\|"difficult", text }[]` | 重点/难点分组 | 结构化重难点 |
|
||
| 导入 | `import` | `method, prompt, durationMin` | 导入方式+提问+时长 | 导入脚本 |
|
||
| 新授 | `new_teaching` | `teachingPoints: { knowledgePointIds, outline, boardNotes }[]` | 知识点+讲解要点+板书 | 教学步骤 |
|
||
| 练习 | `exercise` | `items: ExerciseItem[]; purpose` | 题目列表+用途 | 可发布作业 |
|
||
| 小结 | `summary` | `summaryPoints: string[]; homeworkPreview` | 要点列表+作业预告 | 总结文本 |
|
||
| 作业 | `homework` | `assignments: { type, refId?, description }[]` | 作业项列表 | 作业清单 |
|
||
| 板书设计 | `blackboard` | `layout, content, knowledgePointIds` | 布局+内容 | 板书图 |
|
||
| 教学反思 | `reflection` | `reflection: { aspect, text }[]` | 反维度反思 | 反思记录 |
|
||
| 文本研习 | `text_study` | `annotations: TextStudyAnnotation[]` | 课文批注(与正文联动) | 批注列表 |
|
||
| 富文本 | `rich_text` | `html, knowledgePointIds` | 自由富文本 | HTML |
|
||
| **正文** | `textbook_content` | `chapterId, content, zoom` | 教材章节 Markdown | 只读正文 |
|
||
|
||
### 2.2 数据类型定义
|
||
|
||
```typescript
|
||
// 教学目标
|
||
interface ObjectiveBlockData {
|
||
objectives: {
|
||
dimension: "knowledge" | "process" | "emotion";
|
||
text: string;
|
||
}[];
|
||
}
|
||
|
||
// 重难点
|
||
interface KeyPointBlockData {
|
||
keyPoints: {
|
||
type: "key" | "difficult";
|
||
text: string;
|
||
}[];
|
||
}
|
||
|
||
// 导入
|
||
interface ImportBlockData {
|
||
method: "question" | "situation" | "review" | "other";
|
||
prompt: string;
|
||
durationMin: number;
|
||
}
|
||
|
||
// 新授
|
||
interface NewTeachingBlockData {
|
||
teachingPoints: {
|
||
knowledgePointIds: string[];
|
||
outline: string;
|
||
boardNotes: string;
|
||
}[];
|
||
}
|
||
|
||
// 小结
|
||
interface SummaryBlockData {
|
||
summaryPoints: string[];
|
||
homeworkPreview: string;
|
||
}
|
||
|
||
// 作业
|
||
interface HomeworkBlockData {
|
||
assignments: {
|
||
type: "exercise" | "reading" | "writing";
|
||
refId?: string;
|
||
description: string;
|
||
}[];
|
||
}
|
||
|
||
// 板书设计
|
||
interface BlackboardBlockData {
|
||
layout: "structure" | "mindmap" | "text";
|
||
content: string;
|
||
knowledgePointIds: string[];
|
||
}
|
||
|
||
// 教学反思
|
||
interface ReflectionBlockData {
|
||
reflection: {
|
||
aspect: "effectiveness" | "problems" | "improvements";
|
||
text: string;
|
||
}[];
|
||
}
|
||
|
||
// BlockData 联合类型扩展
|
||
type BlockData =
|
||
| RichTextBlockData
|
||
| TextStudyBlockData
|
||
| ExerciseBlockData
|
||
| ObjectiveBlockData
|
||
| KeyPointBlockData
|
||
| ImportBlockData
|
||
| NewTeachingBlockData
|
||
| SummaryBlockData
|
||
| HomeworkBlockData
|
||
| BlackboardBlockData
|
||
| ReflectionBlockData
|
||
| TextbookContentNodeData;
|
||
```
|
||
|
||
### 2.3 BlockRegistry 配置驱动
|
||
|
||
每个节点类型在 `block-registry.tsx` 注册:
|
||
- `component`: 对应的编辑组件
|
||
- `icon`: 节点图标
|
||
- `defaultTitle`: 默认标题(i18n 键)
|
||
- `defaultData`: 初始数据
|
||
- `summaryExtractor`: 节点卡片摘要函数
|
||
- `color`: 节点颜色(复用现有 `getNodeColor`)
|
||
|
||
### 2.4 默认骨架(10 节点)
|
||
|
||
创建课案时自动生成:
|
||
1. 教学目标(未锚定,全局)
|
||
2. 重难点(未锚定,全局)
|
||
3. 导入(锚定到正文开头)
|
||
4. 文本研习(锚定到正文,范围锚定)
|
||
5. 新授(锚定到正文中部)
|
||
6. 练习(锚定到正文,点锚定)
|
||
7. 小结(锚定到正文结尾)
|
||
8. 作业(未锚定,课后)
|
||
9. 板书设计(未锚定,全局)
|
||
10. 教学反思(未锚定,课后)
|
||
|
||
## 第 3 节:正文节点与锚点交互
|
||
|
||
### 3.1 正文节点组件(TextbookContentNode)
|
||
|
||
```typescript
|
||
// components/nodes/textbook-content-node.tsx
|
||
interface Props {
|
||
data: TextbookContentNodeData;
|
||
selectedNodeId: string | null;
|
||
anchors: NodeAnchor[];
|
||
onAddAnchor: (anchor: NodeAnchor) => void;
|
||
onRemoveAnchor: (anchorId: string) => void;
|
||
onSelectNode: (nodeId: string | null) => void;
|
||
}
|
||
```
|
||
|
||
**渲染流程**:
|
||
1. `ReactMarkdown` 渲染 `data.content`(复用教材模块的 `remarkGfm + remarkBreaks + rehypeSanitize`)
|
||
2. 渲染前调用 `injectPlaceholders(content, anchors)` 在对应偏移位置插入占位符标记
|
||
3. 范围锚定的文字用 `<span class="range-anchor">` 包裹,背景色 = 节点颜色
|
||
4. 点锚定的位置插入 `<span class="point-anchor">①</span>` 标记
|
||
5. 缩放通过 `transform: scale(data.zoom)` 实现
|
||
|
||
### 3.2 占位符注入算法
|
||
|
||
```typescript
|
||
// lib/anchor-injector.ts
|
||
|
||
// 将 Markdown 渲染为纯文本,记录偏移映射
|
||
function buildOffsetMap(markdown: string): {
|
||
plainText: string;
|
||
mdToPlain: Map<number, number>;
|
||
}
|
||
|
||
// 在纯文本中注入占位符标记
|
||
function injectPlaceholders(
|
||
markdown: string,
|
||
anchors: NodeAnchor[]
|
||
): string {
|
||
// 1. buildOffsetMap 得到 plainText + 映射
|
||
// 2. 按 start 排序 anchors(倒序,避免偏移变化)
|
||
// 3. 对 range 锚定:在 [start, end] 范围包裹 <span class="range-anchor">
|
||
// 4. 对 point 锚定:在 start 位置插入 <span class="point-anchor">①</span>
|
||
// 5. 返回注入标记后的 HTML(供 ReactMarkdown 的 components 自定义渲染)
|
||
}
|
||
```
|
||
|
||
### 3.3 CSS 透明度规则
|
||
|
||
```css
|
||
/* 范围锚定:文本背景色 = 节点颜色 */
|
||
.range-anchor {
|
||
background-color: var(--node-color);
|
||
border-radius: 2px;
|
||
opacity: 0;
|
||
transition: opacity 0.2s;
|
||
}
|
||
.range-anchor.active {
|
||
opacity: 0.3;
|
||
}
|
||
|
||
/* 点锚定:占位符标记 */
|
||
.point-anchor {
|
||
display: inline-block;
|
||
background-color: var(--node-color);
|
||
color: #fff;
|
||
border-radius: 3px;
|
||
padding: 0 4px;
|
||
font-size: 0.75em;
|
||
font-weight: bold;
|
||
opacity: 0.3;
|
||
transition: opacity 0.2s;
|
||
cursor: pointer;
|
||
}
|
||
.point-anchor.active {
|
||
opacity: 1;
|
||
}
|
||
.point-anchor:hover {
|
||
opacity: 0.6;
|
||
}
|
||
|
||
/* 连线默认 10% */
|
||
.react-flow__edge.anchor {
|
||
opacity: 0.1;
|
||
}
|
||
.react-flow__edge.anchor.active {
|
||
opacity: 1;
|
||
}
|
||
```
|
||
|
||
### 3.4 两种锚定交互流程
|
||
|
||
**范围锚定(选文本 → 关联节点)**:
|
||
1. 教师在正文选中一段文字
|
||
2. 选中后浮动菜单出现:"关联节点 →"
|
||
3. 下拉列表显示所有未锚定的教学节点 + "新建节点"
|
||
4. 选择后创建 `NodeAnchor { type: "range", start, end, textPreview }`
|
||
5. 创建 `AnchorEdge { source: nodeId, target: textbookContentNodeId, anchorId }`
|
||
6. 正文对应文字被 `<span class="range-anchor">` 包裹
|
||
|
||
**点锚定(点击位置 → 插入占位符)**:
|
||
1. 教师在正文某位置点击(光标位置或点击空白处)
|
||
2. 弹出菜单:"在此处插入节点 →"
|
||
3. 下拉列表显示所有未锚定的教学节点 + "新建节点"
|
||
4. 选择后创建 `NodeAnchor { type: "point", start }`
|
||
5. 创建 `AnchorEdge`
|
||
6. 正文对应位置插入 `<span class="point-anchor">①</span>`
|
||
|
||
### 3.5 选中节点的视觉反馈
|
||
|
||
```typescript
|
||
function getActiveAnchorIds(anchors: NodeAnchor[], selectedNodeId: string | null): Set<string> {
|
||
if (!selectedNodeId) return new Set();
|
||
return new Set(anchors.filter(a => a.nodeId === selectedNodeId).map(a => a.id));
|
||
}
|
||
|
||
const activeAnchorIds = getActiveAnchorIds(anchors, selectedNodeId);
|
||
// 对每个占位符:activeAnchorIds.has(anchor.id) ? "active" : ""
|
||
```
|
||
|
||
### 3.6 正文内容变更处理
|
||
|
||
- 正文来自教材模块的 `chapter.content`,教师不可编辑正文本身
|
||
- 如果教材章节内容更新,课案中的正文缓存需要同步
|
||
- 提供"同步正文"按钮,调用 `getChapterContentAction(chapterId)` 刷新
|
||
- 同步后锚点偏移量可能失效,用 `textPreview` 做模糊匹配尝试重新定位
|
||
- 无法定位的锚点标记为"失效",提示教师重新锚定
|
||
|
||
## 第 4 节:创建课案流程
|
||
|
||
### 4.1 入口 1:从备课模块新建
|
||
|
||
`template-picker.tsx` 改造为强制选择教材/章节:
|
||
1. 选择学科/年级
|
||
2. 选择教材(从 `getTextbooksAction` 获取)
|
||
3. 选择章节(从 `getChaptersByTextbookIdAction` 获取章节树)
|
||
4. 输入课案标题
|
||
5. 点击创建 → 自动拉取章节正文 + 生成默认骨架
|
||
|
||
### 4.2 入口 2:从教材阅读器进入
|
||
|
||
在 `textbook-reader.tsx` 的章节内容面板添加"为此课文备课"按钮:
|
||
- 仅教师角色可见
|
||
- 校验教师教授科目与教材学科匹配
|
||
- 点击后跳转到 `/teacher/lesson-plans/new?textbookId=xxx&chapterId=xxx`
|
||
- `template-picker.tsx` 读取 URL 参数自动预选
|
||
|
||
### 4.3 默认骨架生成
|
||
|
||
```typescript
|
||
function buildDefaultSkeleton(chapterId: string, chapterContent: string): LessonPlanDocument {
|
||
const textbookContentNodeId = createId();
|
||
const textbookNode: TextbookContentNode = {
|
||
id: textbookContentNodeId,
|
||
type: "textbook_content",
|
||
position: { x: 400, y: 200 }, // 画布中央
|
||
draggable: false,
|
||
data: { chapterId, content: chapterContent, zoom: 1 },
|
||
};
|
||
|
||
const defaultNodes = [
|
||
{ type: "objective", position: { x: 80, y: 80 }, anchor: null },
|
||
{ type: "key_point", position: { x: 80, y: 180 }, anchor: null },
|
||
{ type: "import", position: { x: 80, y: 280 }, anchor: { type: "point", start: 0 } },
|
||
{ type: "text_study", position: { x: 80, y: 380 }, anchor: { type: "range", start: 0, end: 10 } },
|
||
{ type: "new_teaching", position: { x: 720, y: 80 }, anchor: { type: "range", start: 50, end: 60 } },
|
||
{ type: "exercise", position: { x: 720, y: 180 }, anchor: { type: "point", start: 100 } },
|
||
{ type: "summary", position: { x: 720, y: 280 }, anchor: { type: "point", start: 200 } },
|
||
{ type: "homework", position: { x: 80, y: 480 }, anchor: null },
|
||
{ type: "blackboard", position: { x: 720, y: 380 }, anchor: null },
|
||
{ type: "reflection", position: { x: 720, y: 480 }, anchor: null },
|
||
];
|
||
|
||
// 生成 nodes + anchors + edges
|
||
return { version: 3, textbookContentNodeId, nodes, edges, anchors };
|
||
}
|
||
```
|
||
|
||
## 第 5 节:编辑器交互改进
|
||
|
||
### 5.1 实时拖动
|
||
|
||
修改 `use-lesson-plan-editor.ts`:
|
||
- 当前:`onNodeDragStop` 才调用 `updateNodePosition`
|
||
- 改为:`onNodeDrag` 实时调用 `updateNodePosition`(每次拖动事件都更新)
|
||
|
||
### 5.2 顶部工具栏增加教材/章节切换
|
||
|
||
- 显示当前教材/章节名称
|
||
- 点击可切换教材/章节
|
||
- 切换后重新拉取正文内容,更新 `TextbookContentNode.data`
|
||
- 锚点可能失效,提示教师
|
||
|
||
### 5.3 添加节点菜单
|
||
|
||
- 左下角"+ 添加节点"按钮
|
||
- 弹出 11 种节点类型菜单(不含 textbook_content)
|
||
- 选择后在画布空闲位置创建节点
|
||
|
||
### 5.4 节点编辑面板
|
||
|
||
- 点击节点 → 右侧 `NodeEditPanel` 显示对应编辑组件
|
||
- `BlockRenderer` 配置驱动渲染
|
||
- 正文节点不可编辑内容,但可缩放(zoom 控件)
|
||
|
||
## 第 6 节:错误处理与边界情况
|
||
|
||
### 6.1 正文内容为空
|
||
|
||
- 如果章节无 `content`,正文节点显示"暂无课文内容,请在教材模块编辑"
|
||
- 锚点功能禁用
|
||
|
||
### 6.2 锚点失效
|
||
|
||
- 正文同步后,用 `textPreview` 模糊匹配重新定位
|
||
- 无法定位的锚点标记 `invalid: true`
|
||
- UI 显示"锚点已失效,请重新选择"
|
||
- 教师可删除失效锚点或重新锚定
|
||
|
||
### 6.3 数据迁移失败
|
||
|
||
- v2 文档无 chapterId:创建空正文节点,提示"请选择教材/章节"
|
||
- 迁移过程异常:保留 v2 原始数据,记录错误日志
|
||
|
||
## 第 7 节:测试策略
|
||
|
||
### 7.1 单元测试
|
||
|
||
- `lib/anchor-injector.ts`:占位符注入算法
|
||
- `lib/document-migration.ts`:v2 → v3 迁移
|
||
- `lib/node-summary.ts`:新节点类型的摘要提取
|
||
|
||
### 7.2 集成测试
|
||
|
||
- 创建课案 → 选择教材/章节 → 验证默认骨架生成
|
||
- 选中正文文字 → 关联节点 → 验证锚点创建
|
||
- 点击正文位置 → 插入占位符 → 验证点锚定
|
||
- 选中节点 → 验证透明度变化
|
||
- 正文同步 → 验证锚点重定位
|
||
|
||
### 7.3 E2E 测试
|
||
|
||
- 完整备课流程:创建 → 编辑 → 锚定 → 保存 → 版本回退
|
||
|
||
## 实现范围
|
||
|
||
本次重构涉及以下文件(预估):
|
||
|
||
**新增**:
|
||
- `src/modules/lesson-preparation/components/nodes/textbook-content-node.tsx`
|
||
- `src/modules/lesson-preparation/components/anchor-context-menu.tsx`
|
||
- `src/modules/lesson-preparation/lib/anchor-injector.ts`
|
||
- `src/modules/lesson-preparation/components/blocks/objective-block.tsx`
|
||
- `src/modules/lesson-preparation/components/blocks/key-point-block.tsx`
|
||
- `src/modules/lesson-preparation/components/blocks/import-block.tsx`
|
||
- `src/modules/lesson-preparation/components/blocks/new-teaching-block.tsx`
|
||
- `src/modules/lesson-preparation/components/blocks/summary-block.tsx`
|
||
- `src/modules/lesson-preparation/components/blocks/homework-block.tsx`
|
||
- `src/modules/lesson-preparation/components/blocks/blackboard-block.tsx`
|
||
- `src/modules/lesson-preparation/components/blocks/reflection-block.tsx`(重构)
|
||
|
||
**修改**:
|
||
- `src/modules/lesson-preparation/types.ts`(v3 数据模型)
|
||
- `src/modules/lesson-preparation/constants.ts`(BlockType 枚举)
|
||
- `src/modules/lesson-preparation/config/block-registry.tsx`(注册新节点)
|
||
- `src/modules/lesson-preparation/lib/document-migration.ts`(v2→v3)
|
||
- `src/modules/lesson-preparation/lib/node-summary.ts`(新节点摘要)
|
||
- `src/modules/lesson-preparation/hooks/use-lesson-plan-editor.ts`(实时拖动 + 锚点操作)
|
||
- `src/modules/lesson-preparation/components/node-editor.tsx`(正文节点 + 连线透明度)
|
||
- `src/modules/lesson-preparation/components/lesson-plan-editor.tsx`(教材/章节切换)
|
||
- `src/modules/lesson-preparation/components/template-picker.tsx`(强制选教材)
|
||
- `src/modules/lesson-preparation/components/blocks/text-study-block.tsx`(与正文联动)
|
||
- `src/modules/lesson-preparation/data-access.ts`(buildDefaultSkeleton)
|
||
- `src/modules/lesson-preparation/actions.ts`(创建课案传入 chapterId)
|
||
- `src/modules/textbooks/components/textbook-reader.tsx`("为此课文备课"按钮)
|
||
- `src/shared/i18n/messages/zh-CN/lesson-preparation.json`(新 i18n 键)
|
||
- `src/shared/i18n/messages/en/lesson-preparation.json`(新 i18n 键)
|
||
- `src/app/globals.css`(锚点 CSS)
|