# 备课模块（lesson-preparation）设计文档

> 配套原型：`docs/feature/f_bk.md`
> 架构依据：`docs/architecture/004_architecture_impact_map.md`、`005_architecture_data.json`
> 编写日期：2026-06-18
> 范围：**P0 地基 + P1 联动**（P2 协作 / P3 AI 与学情回看留作后续 spec）

---

## 0. 关键决策摘要

| 决策项 | 最终选择 | 理由 |
|--------|----------|------|
| 本次 spec 范围 | P0 + P1 | 构成"备课→出题→下发"最小可用闭环，体量适中 |
| 编辑器形态 | Block 编辑器为主 | 与蓝图文字一致；设计稿的"课文+节点画布"作为 `text_study` block 的内部交互 |
| Block 存储模型 | 方案 A：JSON 文档 + 版本快照表 | 与现有 `questions.content` / `homeworkAssignments.structure` 的 JSON 模式一致；为 P2 批注 / P3 AI 重写预留稳定 blockId 锚点；零跨模块 DB 访问 |
| 知识点同步 | P1 仅"关联已有 + AI 推荐"，不回写教材树 | 避免 P1 引入审核流拖慢闭环；回写留作后续 spec |
| 作业发布闭环 | 复用 exam 中转 | 课案练习块 → 打包成 exam 草稿 → 调用现有 `createHomeworkAssignmentAction` 下发；零 schema 侵入、溯源清晰（作业→exam→课案） |

---

## 1. 模块定位与边界

### 1.1 模块名

`lesson-preparation`（目录 `src/modules/lesson-preparation/`），中文"备课"。

### 1.2 与 `course-plans` 的关系（互补，不合并）

| 模块 | 粒度 | 回答的问题 |
|------|------|-----------|
| `course-plans` | 学期/周宏观排课（totalHours/weeklyHours/week/topic） | "这学期每周教什么" |
| `lesson-preparation` | 具体一节课的教学设计（目标/重难点/导入/新授/练习/作业…） | "这节课怎么教" |

软关联：课案可记录来源 `coursePlanItemId`（可空，无强外键），便于"周计划→具体课案"下钻。

### 1.3 依赖关系（严格三层架构，零跨模块直查）

- 依赖 `shared/*`、`@/auth`
- 通过对方 data-access 通信（不直接查询对方表）：
  - `textbooks` — 只读章节树 / 知识点树
  - `questions` — 创建题目（含知识点关联）、查询题目
  - `exams` — 创建 exam 草稿（用于发布中转）
  - `homework` — 创建作业下发到班级
  - `classes` — 查询教师班级（用于下发目标选择）
  - `files` — 附件引用
- 被依赖：P0/P1 阶段无被依赖方

---

## 2. 数据模型（新增 3 张表）

### 2.1 `lesson_plans`（课案主表）

| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| id | id | PK | CUID2 |
| title | varchar(255) | notNull | 课案标题 |
| textbookId | varchar(128) | FK→textbooks, nullable | 教材（允许非教材备课） |
| chapterId | varchar(128) | FK→chapters, nullable | 章节 |
| coursePlanItemId | varchar(128) | nullable, 无 FK | 软关联课程计划项 |
| subjectId | varchar(128) | FK→subjects, nullable | 学科 |
| gradeId | varchar(128) | FK→grades, nullable | 年级 |
| templateId | varchar(128) | nullable | 使用的模板 ID |
| templateName | varchar(100) | nullable | 模板名快照（防模板改名） |
| content | json | notNull | block 文档 JSON（见 §3） |
| status | varchar(50) | default 'draft' | `draft`/`published`/`archived` |
| creatorId | varchar(128) | FK→users, notNull | 创建者 |
| lastSavedAt | timestamp | nullable | 最后自动保存时间 |
| createdAt | timestamp | defaultNow | |
| updatedAt | timestamp | defaultNow onUpdateNow | |

索引：`creatorIdx(creatorId)`、`statusIdx(status)`、`textbookChapterIdx(textbookId, chapterId)`、`subjectGradeIdx(subjectId, gradeId)`

### 2.2 `lesson_plan_versions`（版本快照表）

| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| id | id | PK | |
| planId | varchar(128) | FK→lesson_plans, onDelete cascade | |
| versionNo | int | notNull | 每 plan 内自增 |
| label | varchar(100) | nullable | 手动保存时的标签 |
| content | json | notNull | 该版本 content 快照 |
| isAuto | boolean | default false | true=自动保存触发 |
| creatorId | varchar(128) | FK→users, notNull | |
| createdAt | timestamp | defaultNow | |

索引：`planVersionIdx(planId, versionNo)`（唯一）、`planCreatedIdx(planId, createdAt desc)`

### 2.3 `lesson_plan_templates`（模板表）

| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| id | id | PK | |
| name | varchar(100) | notNull | 模板名 |
| type | varchar(50) | notNull | `system`/`personal` |
| scope | varchar(50) | notNull | `regular`/`review`/`experiment`/`inquiry`/`blank`/`custom` |
| blocks | json | notNull | 预置 block 骨架（blockType + 默认标题 + 提示语，无内容） |
| creatorId | varchar(128) | FK→users, nullable | personal 模板拥有者；system 为 null |
| createdAt | timestamp | defaultNow | |
| updatedAt | timestamp | defaultNow onUpdateNow | |

索引：`typeCreatorIdx(type, creatorId)`（personal 模板按创建者过滤）

> 系统预设 4+1 套模板由 seed 脚本写入（type=system）。教师"另存为我的模板"写入 type=personal。

---

## 3. Block 文档 JSON 结构

### 3.1 顶层结构

```json
{
  "version": 1,
  "blocks": [
    {
      "id": "blk_xxx",
      "type": "objective",
      "title": "教学目标",
      "data": { },
      "order": 0
    }
  ]
}
```

- `id`：客户端生成的稳定 ID（CUID2），是 P2 批注锚点、P3 AI 单 block 重写的定位依据
- `type`：见 §3.2 枚举
- `title`：环节名（可改，模板提供默认值）
- `data`：类型相关数据，见 §3.3
- `order`：排序索引（整数，编辑器拖拽后重排）

### 3.2 Block 类型枚举

| type | 用途 | 出现模板 |
|------|------|---------|
| `objective` | 教学目标 | 常规/复习/实验 |
| `key_point` | 教学重难点 | 常规 |
| `import` | 导入 | 常规 |
| `new_teaching` | 新授 | 常规 |
| `consolidation` | 巩固 | 常规 |
| `summary` | 小结 | 常规/复习/实验/探究 |
| `homework` | 作业布置（文字描述型） | 常规 |
| `blackboard` | 板书设计 | 常规 |
| `text_study` | 文本研习（设计稿画布形态） | 语文/英语精读课自定义添加 |
| `exercise` | 练习/作业块（P1 核心，关联题目） | 任意模板可添加 |
| `rich_text` | 通用富文本（自定义环节） | 复习/实验/探究的自定义环节 |
| `reflection` | 教学反思（P3 预留，P0/P1 不渲染特殊 UI） | 任意 |

### 3.3 各 block.data 结构

**富文本类**（`objective`/`key_point`/`import`/`new_teaching`/`consolidation`/`summary`/`homework`/`blackboard`/`rich_text`/`reflection`）：

```json
{
  "html": "<p>...</p>",
  "knowledgePointIds": ["kp_1", "kp_2"]
}
```

**`text_study`**（设计稿画布形态的 block 化）：

```json
{
  "sourceText": "天气凉了，树叶黄了...",
  "annotations": [
    {
      "id": "ann_xxx",
      "anchor": { "start": 12, "end": 20 },
      "nodeType": "language_feature",
      "title": "语言特色",
      "note": "关注'那么'的反复",
      "color": "yellow"
    }
  ],
  "knowledgePointIds": ["kp_1"]
}
```

**`exercise`**（P1 核心）：

```json
{
  "items": [
    {
      "questionId": "q_xxx",
      "source": "bank",
      "score": 5,
      "order": 0
    },
    {
      "questionId": "inline_draft_xxx",
      "source": "inline",
      "inlineContent": {
        "content": { },
        "type": "single_choice",
        "difficulty": 3,
        "knowledgePointIds": ["kp_1"]
      },
      "score": 10,
      "order": 1
    }
  ],
  "purpose": "class_practice",
  "knowledgePointIds": ["kp_1"]
}
```

- `source`：`bank`=从题库拉取（questionId 已存在于 questions 表）；`inline`=课案内新建（编辑期 questionId 为占位 `inline_draft_${cuid}`，发布时入库后回填真实 ID）
- `inlineContent`：仅 source=inline 时存在，结构与 `questions` 表字段对齐（content/type/difficulty/knowledgePointIds），发布时作为 `createQuestionWithRelations` 的入参
- `purpose`：`class_practice`=课堂练习；`after_class_homework`=课后作业（发布闭环仅处理此类型）

---

## 4. 模板系统

### 4.1 系统预设模板（4+1 套，由 seed 脚本写入）

| 模板 | scope | block 序列 |
|------|-------|-----------|
| 常规课 | `regular` | objective → key_point → import → new_teaching → consolidation → summary → homework → blackboard |
| 复习课 | `review` | objective → rich_text("知识网络梳理") → rich_text("典型例题精讲") → rich_text("变式训练") → exercise("当堂检测") → summary |
| 实验课 | `experiment` | objective → rich_text("器材准备") → rich_text("实验步骤") → rich_text("观察记录表") → rich_text("交流讨论") → summary |
| 探究课 | `inquiry` | rich_text("情境导入") → rich_text("问题驱动") → rich_text("小组探究") → rich_text("成果展示") → rich_text("归纳提升") |
| 空白 | `blank` | （无预置 block） |

模板 `blocks` JSON 仅定义骨架（type + 默认 title + 提示语），不含内容。教师选用后生成对应 block 序列，可自由增删、改序、改名、改内容。

### 4.2 自定义模板

- 教师在编辑器内"另存为我的模板"→ 写入 `lesson_plan_templates`（type=personal, creatorId=教师）
- personal 模板仅创建者可见、可编辑、可删除
- 创建课案时模板选择器并列展示 system + 我的 personal 模板

---

## 5. Block 编辑器与版本管理

### 5.1 编辑器交互

- 主体：可拖拽 block 列表（类 Notion/BlockNote 的块状编辑器）
- 每个 block：标题栏（可改名）+ 内容区（按 type 渲染不同编辑组件）+ 拖拽手柄 + 删除/上移/下移/复制
- block 增删：顶部"+"按钮选择 block 类型插入；可从模板侧栏拖入预置环节
- 自动保存：编辑器 debounce 3s 无操作后触发自动保存（写 `lesson_plans.content` + `lastSavedAt`，不生成版本）
- 手动保存：Ctrl+S 或按钮 → 生成新版本（写 `lesson_plan_versions`，isAuto=false）
- 版本历史：侧栏抽屉展示版本列表（versionNo + label + 时间 + isAuto 标记），点击预览该版本 content，"回退到此版本"= 用该版本 content 覆盖当前 + 生成新版本

### 5.2 版本策略

- 自动保存：只更新 `lesson_plans.content` 与 `lastSavedAt`，**不**写 versions 表（避免版本爆炸）
- 手动保存：写一条 versions 记录（isAuto=false）
- 定时自动版本：每 30 分钟若有过改动，自动写一条 versions 记录（isAuto=true），防止教师长时间未手动保存丢失历史
- 版本上限：每 plan 保留最近 50 条 versions，超出删除最旧的 isAuto=true 记录（手动版本永不被自动清理）

### 5.3 我的课案库

- 路由：`/teacher/lesson-plans`
- 列表展示：卡片网格，显示 title / 教材章节 / 学科年级 / 模板 / status / 最后保存时间
- 筛选：教材（级联章节）、学科、年级、状态、标签（标题关键词搜索）
- 操作：编辑、复制（生成副本，title 加" - 副本"）、删除（软删除：status=archived）、发布（status=published）

---

## 6. P1：知识点标注与关联

### 6.1 手动标注

- 在富文本类 block 内选中文本 → 弹出知识点选择器（从 `textbooks` 模块的章节-知识点树勾选）
- 选中的 knowledgePointId 写入该 block 的 `data.knowledgePointIds`
- block 渲染时在关联的知识点旁显示标签 chip

### 6.2 AI 推荐（轻量，P1 不做完整 AI 课案生成）

- 编辑器顶部"AI 推荐知识点"按钮 → 读取当前课案所有 block 的纯文本 → 调用 `shared/lib/ai.createAiChatCompletion` → 返回推荐 knowledgePointId 列表
- 教师在弹窗中勾选确认 → 合并到对应 block 的 `knowledgePointIds`
- AI 仅做"推荐候选"，不自动写入；知识点池来自教材已有知识点（不创建新知识点）

### 6.3 知识点-课案映射查询（data-access 暴露）

- `getLessonPlansByKnowledgePoint(knowledgePointId)`：反查哪些课案重点讲解了某知识点（供后续学情分析/教材知识点树反查使用，P1 仅实现 data-access 函数，不做 UI）

---

## 7. P1：题目创建 / 拉取 / 同步题库

### 7.1 从题库拉取（source=bank）

- exercise block 侧栏：题库搜索器（按知识点 / 题型 / 难度筛选，调用 `questions/data-access.getQuestions`）
- 选中题目 → 插入 exercise.items（source=bank, questionId=真实 ID）
- 仅引用，不复制题目内容；渲染时按 questionId 查询展示

### 7.2 课案内新建题目（source=inline）

- exercise block 内"新建题目"按钮 → 弹出题目编辑器（复用 `questions/components/create-question-dialog` 的表单逻辑）
- 编辑期：题目暂存为 inline draft，完整内容存入 `exercise.items[].inlineContent`（结构与 questions 表字段对齐），`questionId` 为占位 `inline_draft_${cuid}`
- 保存课案时：inline 题目**不立即入库**，保持 draft 状态（inlineContent 随课案 content 一起持久化）
- 发布作业时（见 §8）：inline 题目先入库（调用 `questions/data-access.createQuestionWithRelations`，入参取自 inlineContent），用真实 questionId 替换占位 ID，回写到课案 content

### 7.3 题目-课案关联查询

- `getLessonPlansByQuestion(questionId)`：反查某题在哪些课案的哪个 exercise block 被使用（data-access 函数，P1 仅实现，不做 UI）

---

## 8. P1：作业 / 考试发布打通（复用 exam 中转）

### 8.1 发布流程

```
教师点击 exercise block（purpose=after_class_homework）的"发布作业"按钮
  │
  ▼
[Action] publishLessonPlanHomeworkAction
  │
  ├─ requirePermission(LESSON_PLAN_PUBLISH)
  │
  ├─ 1. inline 题目入库
  │     └─ 遍历 exercise.items，对 source=inline 的调用
  │        questions/data-access.createQuestionWithRelations
  │        （authorId=教师，关联 knowledgePointIds）
  │     └─ 用真实 questionId 替换课案 content 中的占位 ID
  │     └─ 更新 lesson_plans.content
  │
  ├─ 2. 打包成 exam 草稿
  │     └─ 调用 exams/data-access.persistExamDraft
  │        （title=课案标题+" - 作业"，creatorId=教师，
  │         sourceLessonPlanId=课案ID，关联 textbookId/chapterId/subjectId/gradeId，
  │         examQuestions = exercise.items 映射）
  │     └─ 得到 examId
  │
  ├─ 3. 下发作业
  │     └─ 调用 homework/data-access-write.createHomeworkAssignment
  │        （sourceExamId=examId, title, targets=班级学生列表,
  │         availableAt, dueAt）
  │     └─ 得到 assignmentId
  │
  ├─ 4. 记录溯源
  │     └─ 在课案 content 的 exercise block.data 写入
  │        publishedAssignmentId + publishedExamId + publishedAt
  │
  └─ revalidatePath("/teacher/lesson-plans") + revalidatePath("/teacher/homework")
```

### 8.2 溯源标记

- 课案 exercise block 渲染时：若 `data.publishedAssignmentId` 存在，显示"已发布为作业"徽章 + 跳转链接
- 作业侧（P1 不改 homework 模块）：作业的 sourceExamId → exam → 可查到 sourceLessonPlanId（exam 草稿创建时记录），实现"作业→课案"反查链路
- 学情报告（P3）：通过 assignmentId → exam → lessonPlanId 回链到课案

### 8.3 发布前置校验

- exercise block 至少有 1 道题
- inline 题目必须填写完整（content/type/difficulty/knowledgePointIds）
- 教师必须对目标班级有 `class_taught` DataScope 权限
- 同一 exercise block 不可重复发布（已有 publishedAssignmentId 则禁用发布按钮，提供"重新发布为新作业"选项）

---

## 9. 模块文件结构

```
src/modules/lesson-preparation/
├─ actions.ts                    # Server Actions（编排层）
├─ data-access.ts                # 课案 CRUD + 版本查询
├─ data-access-versions.ts       # 版本快照写入 + 查询 + 回退
├─ data-access-templates.ts      # 模板 CRUD（system + personal）
├─ data-access-knowledge.ts      # 知识点-课案映射查询（P1）
├─ publish-service.ts            # 发布编排（inline 入库 → exam 草稿 → 作业下发）
├─ ai-suggest.ts                 # AI 知识点推荐（P1 轻量）
├─ schema.ts                     # Zod 验证
├─ types.ts                      # 类型定义（含 Block 类型联合）
├─ constants.ts                  # 模板预设、block 类型枚举、状态常量
├─ seed.ts                       # 系统预设模板 seed 脚本
├─ hooks/
│  └─ use-lesson-plan-editor.ts  # 编辑器状态管理（自动保存/版本/拖拽）
└─ components/
   ├─ lesson-plan-list.tsx              # 我的课案库列表
   ├─ lesson-plan-card.tsx              # 课案卡片
   ├─ lesson-plan-filters.tsx           # 筛选器
   ├─ lesson-plan-editor.tsx            # 编辑器主壳（block 列表容器）
   ├─ block-renderer.tsx                # block 分发渲染
   ├─ blocks/
   │  ├─ rich-text-block.tsx            # 富文本类 block 编辑器
   │  ├─ text-study-block.tsx           # 文本研习画布 block
   │  ├─ exercise-block.tsx             # 练习/作业 block
   │  └─ reflection-block.tsx           # 教学反思（P3 预留，P1 简单渲染）
   ├─ template-picker.tsx               # 模板选择器
   ├─ version-history-drawer.tsx        # 版本历史抽屉
   ├─ knowledge-point-picker.tsx        # 知识点选择器（复用 textbooks 组件）
   ├─ question-bank-picker.tsx          # 题库拉取侧栏（复用 questions 组件）
   ├─ inline-question-editor.tsx        # 课案内新建题目（复用 questions 表单）
   └─ publish-homework-dialog.tsx       # 发布作业弹窗（选班级/时间）
```

---

## 10. Server Actions 清单

所有 Action 遵循项目规范：`requirePermission()` → Zod 校验 → 调用 data-access → `revalidatePath` → 返回 `ActionState<T>`。

| Action | 权限点 | 用途 |
|--------|--------|------|
| `getLessonPlansAction` | LESSON_PLAN_READ | 我的课案库列表（含筛选） |
| `getLessonPlanByIdAction` | LESSON_PLAN_READ | 获取单个课案（含权限校验：creator 或 published） |
| `createLessonPlanAction` | LESSON_PLAN_CREATE | 创建课案（选模板 → 生成初始 content） |
| `updateLessonPlanAction` | LESSON_PLAN_UPDATE | 更新课案（自动保存，不生成版本） |
| `saveLessonPlanVersionAction` | LESSON_PLAN_UPDATE | 手动保存生成版本 |
| `revertLessonPlanVersionAction` | LESSON_PLAN_UPDATE | 回退到指定版本（生成新版本） |
| `getLessonPlanVersionsAction` | LESSON_PLAN_READ | 获取版本列表 |
| `deleteLessonPlanAction` | LESSON_PLAN_DELETE | 删除课案（软删除：status=archived） |
| `duplicateLessonPlanAction` | LESSON_PLAN_CREATE | 复制课案 |
| `getLessonPlanTemplatesAction` | LESSON_PLAN_READ | 获取模板列表（system + 我的 personal） |
| `saveAsTemplateAction` | LESSON_PLAN_CREATE | 另存为我的模板 |
| `deleteTemplateAction` | LESSON_PLAN_DELETE | 删除 personal 模板 |
| `suggestKnowledgePointsAction` | LESSON_PLAN_READ + AI_CHAT | AI 推荐知识点（只读返回候选，不写入课案；教师确认后另调 updateLessonPlanAction） |
| `publishLessonPlanHomeworkAction` | LESSON_PLAN_PUBLISH + HOMEWORK_CREATE | 发布作业（§8 编排） |

---

## 11. data-access 清单

| 函数 | 用途 |
|------|------|
| `getLessonPlans(params & scope)` | 课案列表（DataScope 过滤） |
| `getLessonPlanById(id, scope)` | 单课案详情 |
| `createLessonPlan(input)` | 创建（含模板初始化 content） |
| `updateLessonPlanContent(id, content, userId)` | 更新 content + lastSavedAt（自动保存） |
| `softDeleteLessonPlan(id, userId)` | status=archived |
| `duplicateLessonPlan(id, userId)` | 复制 |
| `getLessonPlanVersions(planId)` | 版本列表 |
| `createLessonPlanVersion(planId, content, userId, isAuto, label?)` | 写版本快照 |
| `revertToVersion(planId, versionNo, userId)` | 用版本 content 覆盖当前 + 生成新版本 |
| `pruneAutoVersions(planId, keep=50)` | 清理超出上限的自动版本 |
| `getLessonPlanTemplates(userId)` | system + 该用户 personal |
| `createPersonalTemplate(input, userId)` | 创建 personal 模板 |
| `deletePersonalTemplate(id, userId)` | 删除（仅 owner） |
| `getLessonPlansByKnowledgePoint(kpId)` | 知识点反查课案（P1 data-access only） |
| `getLessonPlansByQuestion(questionId)` | 题目反查课案（P1 data-access only） |

---

## 12. 权限点（新增 5 个）

在 `src/shared/types/permissions.ts` 新增：

```typescript
// Lesson Plan (备课)
LESSON_PLAN_CREATE: "lesson_plan:create",
LESSON_PLAN_READ: "lesson_plan:read",
LESSON_PLAN_UPDATE: "lesson_plan:update",
LESSON_PLAN_DELETE: "lesson_plan:delete",
LESSON_PLAN_PUBLISH: "lesson_plan:publish",
```

在 `src/shared/lib/permissions.ts` 的 `ROLE_PERMISSIONS` 映射中：
- `teacher`：全部 5 个
- `admin`：全部 5 个
- `student`/`parent`/其他：无

---

## 13. 路由

| 路由 | 页面 | 权限 |
|------|------|------|
| `/teacher/lesson-plans` | 我的课案库列表 | LESSON_PLAN_READ |
| `/teacher/lesson-plans/new` | 新建课案（选模板） | LESSON_PLAN_CREATE |
| `/teacher/lesson-plans/[planId]/edit` | 课案编辑器 | LESSON_PLAN_UPDATE（creator）或 LESSON_PLAN_READ（published 只读） |

侧边栏导航：在 `layout/config/navigation.ts` 的 teacher 角色菜单新增"备课"项。

---

## 14. DataScope 接入

- `getLessonPlans` 接受 `scope` 参数：
  - `teacher`/`admin`（type=all 或 class_taught）：返回自己创建的 + 公开 published 的
  - 其他角色：仅 published 的
- `getLessonPlanById`：creator 可看自己的 draft；非 creator 仅当 status=published 可看
- 写操作（update/delete/publish）：仅 creator（DataScope 不适用，直接校验 `creatorId === userId`）

---

## 15. 架构图同步计划

按项目规则"改码必同步图"，实现完成后需更新：

### 15.1 `docs/architecture/004_architecture_impact_map.md`

- §1.1 分层架构图：modules 行新增 `lesson-preparation`
- §1.2 模块依赖关系图：新增 `lesson-preparation` 节点，标注对 textbooks/questions/exams/homework/classes/files 的合理依赖（───▶ data-access）
- 第二部分新增 §2.27 lesson-preparation 模块清单（职责/导出函数/依赖/文件清单）
- 附录 A 依赖矩阵新增一行一列

### 15.2 `docs/architecture/005_architecture_data.json`

- `modules.lesson_preparation`：完整模块节点
- `dbTables`：新增 `lesson_plans` / `lesson_plan_versions` / `lesson_plan_templates`
- `permissions`：新增 5 个权限点
- `routes`：新增 3 个路由
- `dependencyMatrix`：新增依赖关系

### 15.3 `src/shared/db/schema.ts`

新增 3 张表定义（按现有分节风格，加在合适 section）。schema.ts 当前 1111 行已超 1000 硬上限（P0 已知问题），新增 3 表会加剧；建议本次新增时一并按业务域拆分 schema.ts（但拆分属独立任务，不在本 spec 范围，仅在备注中提示）。

---

## 16. 实施分阶段计划

### P0 地基（先做）

1. 新增 3 张表 schema + 迁移
2. 新增 5 个权限点 + 角色映射
3. seed 系统预设模板（4+1 套）
4. data-access + data-access-versions + data-access-templates
5. 基础 CRUD Actions（create/get/update/delete/duplicate）
6. 版本管理 Actions（save version / revert / list）
7. 模板 Actions（list / save as / delete）
8. 我的课案库列表页 + 筛选
9. Block 编辑器主壳 + 富文本类 block + 拖拽排序 + 自动保存
10. 版本历史抽屉
11. 模板选择器（新建课案入口）
12. 路由 + 侧边栏导航
13. 同步架构图 004/005

### P1 联动（P0 完成后）

14. `text_study` block（设计稿画布形态）
15. `exercise` block + 题库拉取侧栏（source=bank）
16. `exercise` block + 课案内新建题目（source=inline，draft 暂存）
17. 知识点选择器 + block 内 knowledgePointIds 标注
18. AI 知识点推荐 Action + 编辑器入口
19. publish-service（inline 入库 → exam 草稿 → 作业下发）
20. 发布作业弹窗（选班级/时间）
21. 溯源标记渲染（已发布徽章 + 跳转）
22. data-access-knowledge（反查函数，无 UI）
23. 同步架构图（若 P1 新增了导出函数）

---

## 17. 验收标准

### P0 验收

- [ ] 教师可创建课案（选教材/章节/模板）
- [ ] 5 套系统预设模板可选，选用后生成对应 block 骨架
- [ ] Block 编辑器：增删改 block、拖拽排序、富文本编辑
- [ ] 自动保存（3s debounce）+ 手动保存生成版本
- [ ] 版本历史：列表、预览、回退
- [ ] 我的课案库：列表、筛选、复制、删除
- [ ] 权限校验：非 creator 无法编辑 draft
- [ ] `npm run lint` + `npx tsc --noEmit` 零错误
- [ ] 架构图 004/005 已同步

### P1 验收

- [ ] exercise block 可从题库拉取题目并展示
- [ ] exercise block 可课案内新建题目（draft 暂存）
- [ ] 富文本 block 可标注知识点（选择器 + chip 展示）
- [ ] AI 推荐知识点按钮可用，推荐结果可勾选确认
- [ ] exercise block（purpose=after_class_homework）可发布为作业
- [ ] 发布后 inline 题目已入库，课案 content 中占位 ID 已替换
- [ ] 发布后作业可通过 sourceExamId → exam → sourceLessonPlanId 反查课案
- [ ] 已发布 exercise block 显示溯源徽章
- [ ] 重复发布被拦截（提供"重新发布为新作业"）
- [ ] `npm run lint` + `npx tsc --noEmit` 零错误
- [ ] 架构图已同步

---

## 18. 未覆盖范围（P2/P3 预告，本次不实现）

以下功能在蓝图中提及，但**不在本 spec 范围**，留作后续独立 spec：

### P2 协作
- 分享链接（密码/有效期）
- block 级批注线程（依赖本 spec 的稳定 blockId）
- 采纳建议生成新版本

### P3 智能与回看
- AI 课案初稿生成（按模板结构填充）
- 环节级 AI 重写（选中 block → 指令 → 替换该 block）
- 一致性检查（目标-活动-评价对齐）
- 系统内资源推荐（微课/课件/实验视频）
- 外部资源对接（国家中小学智慧教育平台 API）
- AI 生成资源草稿（课件大纲/微课脚本/学案）
- 教学反思 block 完整 UI
- 学情回看（班级知识点掌握率/高频错题内嵌）
- AI 补救教学建议
- 知识点回写教材树（含审核流）

---

## 19. 风险与备注

| 风险 | 影响 | 缓解 |
|------|------|------|
| `schema.ts` 已 1111 行超 1000 硬上限，新增 3 表加剧 | 违反编码规范 | 本次新增时备注提示；schema.ts 按业务域拆分作为独立任务跟进 |
| Block 编辑器复杂度高 | P0 工期风险 | 优先用成熟库（如 BlockNote/Plate）二次封装，不自研底层 |
| inline 题目发布时入库失败 | 数据不一致 | publish-service 用事务包裹；失败则回滚 exam 草稿创建，课案 content 不替换占位 ID |
| 自动保存频率高导致 versions 表膨胀 | 存储压力 | 自动保存不写 versions；定时自动版本 30min 一次；pruneAutoVersions 保留上限 50 |
| AI 推荐知识点依赖 AI Provider 配置 | 功能可用性 | AI 不可用时按钮置灰 + 提示"未配置 AI Provider"；不阻塞主流程 |

---

> 本 spec 完成后，下一步进入 `writing-plans` skill 生成详细实施计划。
