feat: 新增备课模块并修复全模块 P0/P1/P2 缺陷
Some checks failed
Security / deep-security-scan (push) Failing after 20m5s
DR Drill / dr-drill (push) Failing after 1m31s
CI / scheduled-backup (push) Failing after 1m31s
CI / backup-verify (push) Has been skipped
CI / weekly-dr-drill (push) Failing after 0s
CI / build-deploy (push) Has been cancelled
CI / security-scan (push) Has been cancelled
Some checks failed
Security / deep-security-scan (push) Failing after 20m5s
DR Drill / dr-drill (push) Failing after 1m31s
CI / scheduled-backup (push) Failing after 1m31s
CI / backup-verify (push) Has been skipped
CI / weekly-dr-drill (push) Failing after 0s
CI / build-deploy (push) Has been cancelled
CI / security-scan (push) Has been cancelled
主要变更: - 新增 lesson-preparation 模块: 备课编辑器、节点编辑、AI 建议、知识点选择、版本历史、作业发布 - 新增 shared 通用组件: charts/question-bank-filters/schedule-list/ui (chip-nav/filter-bar/page-header/stat-card/stat-item) - 新增 student/admin 端 loading.tsx 与 error.tsx, 优化加载与错误态体验 - 新增 teacher/lesson-plans 页面 (列表/新建/编辑) - 新增 drizzle 迁移 0002_tiny_lionheart 及 snapshot - 新增 textbooks/schema.ts 与 exams/utils/normalize-structure.ts - 修复 Tiptap v3 SSR hydration 崩溃 (rich-text-block immediatelyRender: false) - 重构多模块 data-access/actions/组件, 修复权限校验与类型规范 - 同步架构文档 004/005 反映新增模块、导出、依赖关系 - 归档 bugs/* 测试报告与 e2e 测试脚本 (admin/parent/student/teacher web_test)
This commit is contained in:
608
docs/feature/f_bk_design.md
Normal file
608
docs/feature/f_bk_design.md
Normal file
@@ -0,0 +1,608 @@
|
||||
# 备课模块(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 生成详细实施计划。
|
||||
Reference in New Issue
Block a user