# 备课模块使用问题修复设计

> **日期**：2026-06-23
> **前置文档**：[2026-06-22-lesson-preparation-anchor-canvas-design.md](./2026-06-22-lesson-preparation-anchor-canvas-design.md)
> **审计来源**：深度分析备课模块现状后发现的使用问题

## 一、问题背景

备课模块 v3 锚点画布重构已完成，但实际使用中存在 15 个问题（3 个 P0 阻塞性 + 6 个 P1 严重 + 6 个 P2 一般），导致核心流程断裂、角色使用不完整。

### 核心闭环断裂

```
教师备课 → 发布课案 → 学生查看 → 家长查看
    ↑         ❌ P0-1     ❌ P0-2/3   ❌ P0-2/3
    无"发布"action   无权限/路由   无权限/路由
```

## 二、功能定位

### 备课模块核心闭环

1. **教师备课**：选定教材+章节 → 锚点画布编辑 → 保存版本 → **发布**
2. **学生查看**：按班级可见 published 课案，只读画布视图
3. **家长查看**：通过孩子关系自动可见 published 课案
4. **教研监督**：教研组长/年级主任查看本年级教师备课（只读）
5. **管理员管理**：全校课案列表 + 统计

## 三、角色使用矩阵

| 角色 | 权限 | 路由 | 视图 |
|------|------|------|------|
| 教师 | CREATE/READ/UPDATE/DELETE/PUBLISH | `/teacher/lesson-plans` | 完整编辑画布 + 发布按钮 |
| 学生 | READ（仅 published） | `/student/lesson-plans/[planId]/view` | 只读画布视图 |
| 家长 | READ（仅孩子的 published） | `/parent/lesson-plans/[planId]/view` | 只读画布视图 |
| 管理员 | 全部 | `/admin/lesson-plans` | 全校课案列表 + 统计 |
| 教研组长 | READ（本年级教师） | `/grade-head/lesson-plans` | 本年级教师课案列表（只读） |
| 年级主任 | READ（本年级教师） | `/teaching-head/lesson-plans` | 同上 |

### 学生查看视图设计

**只读画布视图**：复用 React Flow，配置：
- `nodesDraggable={false}`
- `nodesConnectable={false}`
- `elementsSelectable={true}`（可点击查看节点详情）
- `panOnDrag={true}`、`zoomOnScroll={true}`（可缩放平移）

保留正文+节点+锚点的完整视觉关系，学生可缩放平移但不可编辑。

### 发布机制

- 发布到班级所有学生，家长通过孩子关系自动可见
- 状态流转：`draft` → `published`（发布）→ `draft`（撤回）或 `archived`（归档）
- 发布后学生/家长立即可见，撤回后立即不可见

## 四、修复范围（P0+P1 优先）

### P0 阻塞性问题

| 编号 | 问题 | 修复方案 |
|------|------|----------|
| P0-1 | 无"发布课案"action | 新增 `publishLessonPlanAction` / `unpublishLessonPlanAction` |
| P0-2 | 学生/家长无权限 | 给 student/parent 添加 `LESSON_PLAN_READ` 权限 |
| P0-3 | 无学生/家长/管理员路由 | 新增路由 + 导航入口 |

### P1 严重问题

| 编号 | 问题 | 修复方案 |
|------|------|----------|
| P1-1 | 锚点节点选择器占位 | 完善 `AnchorNodeSelector`，接收节点列表 + 实现"锚定到新节点" |
| P1-2 | 锚点偏移计算不可靠 | 改用 `markdownToPlainText` 搜索定位，支持跨段落 |
| P1-3 | 边回写丢失 anchorId | anchorId 存入 `edge.data`，`fromRfEdges` 从 data 读取 |
| P1-4 | 锚点边颜色无区分 | 使用 `getNodeColor(anchor.nodeId)` |
| P1-5 | 教研组长无权限 | 给 grade_head/teaching_head 添加 `LESSON_PLAN_READ` 权限 |
| P1-6 | 个人模板不显示 | TemplatePicker 调用 `getLessonPlanTemplatesAction` 合并显示 |

### P2 一般问题（本次顺带修复）

| 编号 | 问题 | 修复方案 |
|------|------|----------|
| P2-1 | 正文节点侧边面板提示误导 | 改为"请在画布上直接操作正文"或显示锚点列表 |
| P2-5 | 默认骨架节点位置重叠 | 增大列间距，避免与正文节点重叠 |

## 五、实施阶段

### 阶段 1：权限与路由基础（P0-2、P0-3、P1-5）

**权限修改**（`shared/lib/permissions.ts`）：
- student 数组添加 `Permissions.LESSON_PLAN_READ`
- parent 数组添加 `Permissions.LESSON_PLAN_READ`
- grade_head 数组添加 `Permissions.LESSON_PLAN_READ`
- teaching_head 数组添加 `Permissions.LESSON_PLAN_READ`

**路由新增**：
- `app/(dashboard)/student/lesson-plans/page.tsx` — 课案列表（按班级 published）
- `app/(dashboard)/student/lesson-plans/[planId]/view/page.tsx` — 只读画布
- `app/(dashboard)/parent/lesson-plans/page.tsx` — 课案列表（按孩子 published）
- `app/(dashboard)/parent/lesson-plans/[planId]/view/page.tsx` — 只读画布
- `app/(dashboard)/admin/lesson-plans/page.tsx` — 全校课案列表 + 统计
- `app/(dashboard)/admin/lesson-plans/[planId]/view/page.tsx` — 查看任意课案

**导航入口**（`shared/lib/navigation.ts` 或 `modules/layout/config/navigation.ts`）：
- student 导航添加"我的课案"
- parent 导航添加"孩子课案"
- admin 导航添加"课案管理"

**只读画布组件**：
- 新建 `components/lesson-plan-readonly-view.tsx`
- 复用 React Flow，配置只读模式
- 复用 `toRfNodes` / `toRfEdges` 渲染

### 阶段 2：发布机制（P0-1）

**data-access 新增**（`data-access.ts`）：
```typescript
export async function publishLessonPlan(planId: string, userId: string): Promise<void>
export async function unpublishLessonPlan(planId: string, userId: string): Promise<void>
```

**actions 新增**（`actions.ts`）：
```typescript
export async function publishLessonPlanAction(planId: string): Promise<ActionState<string>>
export async function unpublishLessonPlanAction(planId: string): Promise<ActionState<string>>
```

**UI 修改**：
- `LessonPlanEditor` 工具栏添加"发布"/"撤回发布"按钮
- `LessonPlanCard` 添加"发布"/"撤回"操作菜单项
- 发布时显示确认对话框

### 阶段 3：锚点交互完善（P1-1、P1-2、P1-3、P1-4）

**P1-1 完善 AnchorNodeSelector**：
- 接收 `doc.nodes` 列表（过滤掉 textbook_content）
- 渲染节点下拉选择器（显示节点标题 + 颜色标识）
- 实现"锚定到新节点"：弹出节点类型选择 → 创建节点 → 自动锚定

**P1-2 修复锚点偏移计算**：
- `handleMouseUp` 改用 `Range.toString()` 获取选中文本
- 在 `markdownToPlainText(content)` 中搜索定位 start/end 偏移
- 支持跨段落、跨行内元素的选择

**P1-3 修复 fromRfEdges anchorId 丢失**：
- `toRfEdges` 时将 `anchorId` 存入 `edge.data.anchorId`
- `fromRfEdges` 从 `e.data?.anchorId` 读取

**P1-4 修复锚点边颜色**：
- `rf-mappers.ts` 导入 `getNodeColor`
- `toRfEdges` 中 `stroke: getNodeColor(anchor.nodeId)`

### 阶段 4：模板与体验（P1-6 + P2）

**P1-6 TemplatePicker 显示个人模板**：
- 初始化时调用 `getLessonPlanTemplatesAction()`
- 合并系统模板和个人模板展示
- 个人模板标记"个人"徽章

**P2-1 修复正文节点侧边面板**：
- `NodeEditPanel` 选中 textbook_content 时显示锚点列表
- 或显示提示"请在画布上直接操作正文，点击正文选中文本可创建锚点"

**P2-5 优化默认骨架节点位置**：
- 增大列间距，左列 x=80，右列 x=900
- 正文节点居中 (500, 250)

## 六、数据模型变更

无数据模型变更。发布机制仅修改 `lessonPlans.status` 字段（已存在）。

## 七、i18n 键新增

### zh-CN / en grades 命名空间（lesson-preparation.json）

- `action.publish` / `action.unpublish`
- `action.publishConfirm` / `action.unpublishConfirm`
- `action.publishSuccess` / `action.unpublishSuccess`
- `status.published` / `status.draft` / `status.archived`
- `readonly.title` / `readonly.hint`
- `admin.title` / `admin.stats` / `admin.totalPlans` / `admin.publishedPlans`
- `gradeHead.title`
- `anchor.selectNode` / `anchor.createNode` / `anchor.nodeTypePrompt`

## 八、架构文档同步

- `004_architecture_impact_map.md`：补充路由、权限、新组件
- `005_architecture_data.json`：补充 routes、permissions、exports

## 九、验证标准

- `npx tsc --noEmit` 零错误
- `npm run lint` 零错误
- 教师可发布课案，学生/家长可查看 published 课案
- 教研组长可查看本年级教师备课
- 锚点交互完整可用（选中文本 → 选择节点 → 创建锚点）
- 锚点边颜色与关联节点一致
