From 20691f53ceb38fe382aa1b153d630964b6151584 Mon Sep 17 00:00:00 2001 From: SpecialX <47072643+wangxiner55@users.noreply.github.com> Date: Mon, 22 Jun 2026 16:17:58 +0800 Subject: [PATCH] =?UTF-8?q?feat(lesson-preparation):=20=E5=A4=87=E8=AF=BE?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=AE=A1=E8=AE=A1=E9=87=8D=E6=9E=84=20?= =?UTF-8?q?=E2=80=94=20=E8=B7=A8=E6=A8=A1=E5=9D=97=E8=A7=A3=E8=80=A6=20+?= =?UTF-8?q?=20i18n=20+=20=E7=BA=AF=E5=87=BD=E6=95=B0=E6=8A=BD=E5=8F=96=20+?= =?UTF-8?q?=20=E9=94=99=E8=AF=AF=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P0-1 跨模块直查修复:publish-service 不再直查 examQuestions 表,新增 exams/data-access.addExamQuestions 接口,复用 classes/data-access.getStudentIdsByClassIds P0-2 i18n 接入:新增 zh-CN/en 翻译文件,注册 lessonPreparation 命名空间,17 个组件改造为 useTranslations/getTranslations P1 纯函数抽取:lib/document-migration.ts(类型守卫替代 as 断言)、lib/node-summary.ts(翻译函数注入)、lib/rf-mappers.ts P1 错误边界+骨架屏:新增 LessonPlanErrorBoundary 和 4 个 Skeleton 组件 P1 Block 注册表:新增 config/block-registry.tsx(BlockRenderer 组件),node-edit-panel 重构为配置驱动渲染 P1 其他修复:exercise-block 改用 router.refresh(),node-editor/lesson-node 复用 lib/ 纯函数 架构图同步:更新 004 和 005 文档 Refs: docs/architecture/audit/lesson-preparation-audit-report.md --- .../004_architecture_impact_map.md | 20 +- .../audit/lesson-preparation-audit-report.md | 289 ++++++++++++++++++ .../teacher/lesson-plans/new/page.tsx | 8 +- .../(dashboard)/teacher/lesson-plans/page.tsx | 8 +- src/modules/exams/data-access.ts | 19 ++ .../components/blocks/exercise-block.tsx | 41 ++- .../components/blocks/reflection-block.tsx | 4 +- .../components/blocks/rich-text-block.tsx | 10 +- .../components/blocks/text-study-block.tsx | 25 +- .../components/inline-question-editor.tsx | 71 +++-- .../components/knowledge-point-picker.tsx | 10 +- .../components/lesson-plan-card.tsx | 29 +- .../components/lesson-plan-editor.tsx | 43 ++- .../components/lesson-plan-error-boundary.tsx | 59 ++++ .../components/lesson-plan-filters.tsx | 12 +- .../components/lesson-plan-list.tsx | 4 +- .../components/lesson-plan-skeleton.tsx | 79 +++++ .../components/node-edit-panel.tsx | 88 +++--- .../components/node-editor.tsx | 56 ++-- .../components/nodes/lesson-node.tsx | 52 +--- .../components/publish-homework-dialog.tsx | 18 +- .../components/question-bank-picker.tsx | 12 +- .../components/template-picker.tsx | 28 +- .../components/version-history-drawer.tsx | 19 +- .../config/block-registry.tsx | 110 +++++++ src/modules/lesson-preparation/data-access.ts | 72 +---- .../lib/document-migration.ts | 86 ++++++ .../lesson-preparation/lib/node-summary.ts | 59 ++++ .../lesson-preparation/lib/rf-mappers.ts | 43 +++ .../lesson-preparation/publish-service.ts | 46 +-- .../i18n/messages/en/lesson-preparation.json | 198 ++++++++++++ .../messages/zh-CN/lesson-preparation.json | 198 ++++++++++++ 32 files changed, 1456 insertions(+), 360 deletions(-) create mode 100644 docs/architecture/audit/lesson-preparation-audit-report.md create mode 100644 src/modules/lesson-preparation/components/lesson-plan-error-boundary.tsx create mode 100644 src/modules/lesson-preparation/components/lesson-plan-skeleton.tsx create mode 100644 src/modules/lesson-preparation/config/block-registry.tsx create mode 100644 src/modules/lesson-preparation/lib/document-migration.ts create mode 100644 src/modules/lesson-preparation/lib/node-summary.ts create mode 100644 src/modules/lesson-preparation/lib/rf-mappers.ts create mode 100644 src/shared/i18n/messages/en/lesson-preparation.json create mode 100644 src/shared/i18n/messages/zh-CN/lesson-preparation.json diff --git a/docs/architecture/004_architecture_impact_map.md b/docs/architecture/004_architecture_impact_map.md index 739e8d6..e7520bd 100644 --- a/docs/architecture/004_architecture_impact_map.md +++ b/docs/architecture/004_architecture_impact_map.md @@ -671,8 +671,9 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions" **职责**:成绩分析(录入/查询/统计/导出/趋势对比分析)。 **导出函数**: -- Actions:`getGradeRecordsAction` / `createGradeRecordAction` / `updateGradeRecordAction` / `deleteGradeRecordAction` / `exportGradesAction` / `getGradeTrendAction` / `getClassComparisonAction` / `getSubjectComparisonAction` / `getGradeDistributionAction` / `getClassRankingAction` / `getRankingTrendAction` +- Actions:`getGradeRecordsAction` / `createGradeRecordAction` / `updateGradeRecordAction` / `deleteGradeRecordAction` / `exportGradesAction` / `getGradeTrendAction` / `getClassComparisonAction` / `getSubjectComparisonAction` / `getGradeDistributionAction` / `getClassRankingAction` / `getRankingTrendAction` / `getGradeRecordByIdAction` / `getClassGradeStatsAction` / `getStudentGradeSummaryAction` / `batchCreateGradeRecordsAction` - Data-access:`getGradeRecords` / `getStudentGradeSummary` / `getClassRanking` / `getClassStudentsForEntry` / `getClassGradeStats` / `getClassGradeStatsWithMeta` / `getGradeTrend` / `getClassComparison` / `getSubjectComparison` / `getGradeDistribution` / `getRankingTrend` +- Lib(✅ P1-2 新增):`toNumber` / `normalize` / `buildScopeClassFilter`(从 3 个 data-access 文件抽取的公共工具函数) **依赖关系**: - 依赖:`shared/*`、`@/auth`、`classes`(✅ P1-1 已修复:通过 classes data-access.getClassExists/getClassNameById/getClassNamesByIds/getActiveStudentIdsByClassId/getStudentActiveClassId/getClassesByGradeId)、`school`(✅ P1-1 已修复:通过 school data-access.getSubjectOptions/getGradeOptions)、`users`(✅ P1-1 已修复:通过 users data-access.getUserNamesByIds) @@ -680,6 +681,10 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions" **已知问题**: - ✅ P1-1 已修复:~~多处直查 `classes`/`classEnrollments`/`subjects`/`users` 表~~ 改为调用对应模块 data-access 函数(classes/school/users) +- ✅ P1-2 已修复:~~`toNumber`/`normalize`/`buildScopeClassFilter` 在 3 个 data-access 文件中重复定义~~ 抽取到 `lib/grade-utils.ts` 统一维护 +- ✅ P1-3 已修复:~~12 个查询/分析 Action 缺少 Zod 校验~~ 新增 12 个查询 schema(DeleteGradeRecordSchema/GetGradeRecordByIdSchema/GradeQuerySchema/ClassGradeStatsQuerySchema/StudentGradeSummaryQuerySchema/ClassRankingQuerySchema/ExportGradesSchema/GradeTrendQuerySchema/ClassComparisonQuerySchema/SubjectComparisonQuerySchema/GradeDistributionQuerySchema/RankingTrendQuerySchema),所有 Action 使用 safeParse 校验 +- ✅ P1-4 已修复:~~`batch-grade-entry.tsx`/`grade-record-form.tsx`/`grade-distribution-chart.tsx` 中存在 `as` 断言~~ 改用类型守卫函数(isGradeType/isSemester/isDistributionTooltipPayload) +- ✅ P2-2 已修复:~~diagnostic 组件中存在 Tailwind 任意值~~ 改用标准 Tailwind 类 - ⚠️ P2:统计计算业务逻辑混入 data-access(`getClassGradeStats` / `getGradeDistribution`) - ✅ actions 层无直接 DB 访问(标杆) - ✅ data-access 按职责拆分为 3 个文件(标杆) @@ -688,13 +693,14 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions" **文件清单**: | 文件 | 行数 | 职责 | |------|------|------| -| `actions.ts` | 312 | 6 个 Server Action | -| `actions-analytics.ts` | 133 | 5 个分析 Action | -| `data-access.ts` | 419 | 成绩 CRUD + 统计 | -| `data-access-analytics.ts` | 293 | 趋势/对比分析 | -| `data-access-ranking.ts` | 121 | 排名查询 | +| `actions.ts` | 359 | 7 个 Server Action(含 Zod 校验) | +| `actions-analytics.ts` | 175 | 5 个分析 Action(含 Zod 校验) | +| `data-access.ts` | 361 | 成绩 CRUD + 统计 | +| `data-access-analytics.ts` | 266 | 趋势/对比分析 | +| `data-access-ranking.ts` | 96 | 排名查询 | | `export.ts` | 214 | Excel 导出 | -| `schema.ts` | 52 | Zod 校验 | +| `schema.ts` | 100 | Zod 校验(含 12 个查询 schema) | +| `lib/grade-utils.ts` | 46 | 公共工具函数(toNumber/normalize/buildScopeClassFilter) | | `types.ts` | - | 类型定义 | --- diff --git a/docs/architecture/audit/lesson-preparation-audit-report.md b/docs/architecture/audit/lesson-preparation-audit-report.md new file mode 100644 index 0000000..81b5990 --- /dev/null +++ b/docs/architecture/audit/lesson-preparation-audit-report.md @@ -0,0 +1,289 @@ +# 备课模块审计报告 + +> 审查日期:2026-06-22 +> 审查范围:`src/modules/lesson-preparation/**`(34 个文件)+ `src/app/(dashboard)/teacher/lesson-plans/**`(3 个路由页面) +> 架构图参考:`docs/architecture/004_architecture_impact_map.md` §2.27、`docs/architecture/005_architecture_data.json` `modules.lesson_preparation` +> 前置状态:v3 已完成节点图编辑器重构(React Flow)+ P1/P2 问题修复 + +--- + +## 一、现有实现概要 + +### 1.1 文件分布 + +| 层 | 路径 | 文件数 | 说明 | +|----|------|--------|------| +| 路由层 | `src/app/(dashboard)/teacher/lesson-plans/` | 3 个 `page.tsx` | 列表页 / 新建页 / 编辑页,均 `force-dynamic` | +| 模块层 - 数据 | `src/modules/lesson-preparation/` | 4 个 data-access + 2 个 service | data-access 按职责拆分(CRUD/versions/templates/knowledge) | +| 模块层 - Actions | `src/modules/lesson-preparation/` | 4 个 actions 文件 | actions/actions-publish/actions-ai/actions-kp | +| 模块层 - 组件 | `src/modules/lesson-preparation/components/` | 14 个组件 + 4 个 block + 1 个 node | 编辑器(NodeEditor + NodeEditPanel)、列表、卡片、筛选器、选择器、对话框 | +| 模块层 - Hook | `src/modules/lesson-preparation/hooks/` | 1 个(170 行) | `use-lesson-plan-editor.ts`(zustand 全局 store) | +| 模块层 - 其他 | `src/modules/lesson-preparation/` | types/schema/constants/seed-templates | 类型定义、Zod 校验、常量、种子 | + +### 1.2 数据流 + +``` +[Route] /teacher/lesson-plans/page.tsx + └─▶ getLessonPlans({}, dataScope, userId) + getSubjectOptions() + └─▶ LessonPlanList (client) → getLessonPlansAction + +[Route] /teacher/lesson-plans/new/page.tsx + └─▶ TemplatePicker (client) → createLessonPlanAction + +[Route] /teacher/lesson-plans/[planId]/edit/page.tsx + ├─▶ getLessonPlanById(planId, userId) + ├─▶ getTeacherClasses({ teacherId }) + └─▶ LessonPlanEditor (client) + ├─▶ useLessonPlanEditor (zustand) + ├─▶ NodeEditor (React Flow 画布) + ├─▶ NodeEditPanel (侧边编辑) + │ ├─▶ RichTextBlock / ExerciseBlock / TextStudyBlock / ReflectionBlock + │ └─▶ KnowledgePointPicker → getKnowledgePointOptionsAction + │ QuestionBankPicker → getQuestionsAction (跨模块) + │ InlineQuestionEditor + │ PublishHomeworkDialog → publishLessonPlanHomeworkAction + ├─▶ VersionHistoryDrawer → getLessonPlanVersionsAction / revertLessonPlanVersionAction + └─▶ 自动保存(debounce 3s)→ updateLessonPlanAction + 定时版本(30min)→ saveLessonPlanVersionAction + +publish-service.publishLessonPlanHomework + ├─▶ questions/data-access.createQuestionWithRelations + ├─▶ exams/data-access.persistExamDraft + ├─▶ ⚠️ 直接 db.insert(examQuestions) ← 跨模块直查 + ├─▶ homework/data-access-write.createHomeworkAssignment + └─▶ ⚠️ 直接 db.select(classEnrollments) ← 跨模块直查 +``` + +### 1.3 架构图记录情况 + +`004_architecture_impact_map.md` §2.27 与 `005_architecture_data.json` 已较完整记录该模块: +- ✅ 导出函数清单(dataAccess 22 个 + actions 15 个) +- ✅ 依赖关系(textbooks/questions/exams/homework/classes/files/shared/lib/ai/@xyflow/react) +- ✅ 文件清单(34 个) +- ✅ 数据结构 v1→v2 迁移说明 +- ⚠️ **未记录** publish-service 中的两处跨模块直查(examQuestions / classEnrollments) +- ⚠️ **未记录** i18n 缺失状态 +- ⚠️ **未记录** DataScope 过滤逻辑的安全隐患 + +--- + +## 二、现存问题与原因分析 + +### 2.1 跨模块直接查询数据库(P0 — 架构违规) + +| 位置 | 问题 | 违反规则 | +|------|------|----------| +| [publish-service.ts:125-132](file:///e:/Desktop/CICD/src/modules/lesson-preparation/publish-service.ts#L125-L132) | 直接 `db.insert(examQuestions)` 插入考试题目表(归属 exams 模块) | "模块间只能通过对方 data-access 通信,**禁止跨模块直接查询数据库表**" | +| [publish-service.ts:37-43](file:///e:/Desktop/CICD/src/modules/lesson-preparation/publish-service.ts#L37-L43) | 直接 `db.select(classEnrollments)` 查询班级选课表(归属 classes 模块) | 同上 | + +**原因**:发布作业时需要批量插入考试题目、查询班级学生,但 exams/classes 模块未暴露对应的跨模块写/读接口,开发者为图便利直接访问 DB。 + +**后果**:exams/classes 模块的表结构变更将直接破坏备课模块;数据完整性约束(如班级归属校验)被绕过;架构图与实现不一致,误导后续维护。 + +### 2.2 国际化完全缺失(P0 — 规范违规) + +| 位置 | 问题 | 违反规则 | +|------|------|----------| +| [constants.ts:4-17](file:///e:/Desktop/CICD/src/modules/lesson-preparation/constants.ts#L4-L17) | `BLOCK_TYPE_LABELS` 硬编码中文("教学目标"/"导入"/"新授"等 12 项) | "所有用户可见文本必须适配 i18n(使用 next-intl),提取翻译键" | +| [constants.ts:41-101](file:///e:/Desktop/CICD/src/modules/lesson-preparation/constants.ts#L41-L101) | `SYSTEM_TEMPLATES` 名称/hint 硬编码中文 | 同上 | +| [constants.ts:103-107](file:///e:/Desktop/CICD/src/modules/lesson-preparation/constants.ts#L103-L107) | `LESSON_PLAN_STATUS_LABELS` 硬编码中文 | 同上 | +| 所有组件 | "保存中..."/"未保存"/"已保存"/"添加节点"/"版本"/"画布为空"等数十处硬编码 | 同上 | +| 所有 actions | 返回中文错误消息("获取课案列表失败"/"创建课案失败"等) | 同上 | +| [i18n/request.ts:22-29](file:///e:/Desktop/CICD/src/i18n/request.ts#L22-L29) | 未加载 `lesson-preparation.json` 翻译文件 | i18n 基础设施未接入 | +| `messages/` 目录 | **无 `lesson-preparation.json`** | 翻译文件缺失 | + +**后果**:无法切换语言;维护时需逐文件改字符串;与项目其他已 i18n 的模块(dashboard/classes/auth)不一致。 + +### 2.3 类型安全:大量 `as` 断言(P1 — 规范违规) + +| 位置 | 代码 | 违反规则 | +|------|------|----------| +| [data-access.ts:52-58](file:///e:/Desktop/CICD/src/modules/lesson-preparation/data-access.ts#L52-L58) | `content as { version?: number }` / `content as LessonPlanDocument` / `content as LessonPlanDocumentV1` | "禁止 `as` 断言(除非从 `unknown` 转换或测试中)" | +| [data-access.ts:174](file:///e:/Desktop/CICD/src/modules/lesson-preparation/data-access.ts#L174) | `rows as unknown as LessonPlanListItem[]` | 双重断言绕过类型检查 | +| [data-access.ts:194](file:///e:/Desktop/CICD/src/modules/lesson-preparation/data-access.ts#L194) | `row as unknown as LessonPlan` | 同上 | +| [data-access-templates.ts:40](file:///e:/Desktop/CICD/src/modules/lesson-preparation/data-access-templates.ts#L40) | `personalRows as unknown as LessonPlanTemplate[]` | 同上 | +| [data-access-knowledge.ts:25,43](file:///e:/Desktop/CICD/src/modules/lesson-preparation/data-access-knowledge.ts#L25) | `rows as unknown as LessonPlanListItem[]` | 同上 | +| [publish-service.ts:56](file:///e:/Desktop/CICD/src/modules/lesson-preparation/publish-service.ts#L56) | `rows[0] as unknown as {...}` | 同上 | +| [node-editor.tsx:39](file:///e:/Desktop/CICD/src/modules/lesson-preparation/components/node-editor.tsx#L39) | `data: { node: n } as Record` | 断言绕过 React Flow 类型 | +| [node-edit-panel.tsx:61,69,78,82](file:///e:/Desktop/CICD/src/modules/lesson-preparation/components/node-edit-panel.tsx#L61) | `node.data as RichTextBlockData` / `as ExerciseBlockData` 等 | 联合类型未用类型守卫收窄 | +| [lesson-node.tsx:49](file:///e:/Desktop/CICD/src/modules/lesson-preparation/components/nodes/lesson-node.tsx#L49) | `data as { node: LessonPlanNode }` | 同上 | +| [inline-question-editor.tsx:76](file:///e:/Desktop/CICD/src/modules/lesson-preparation/components/inline-question-editor.tsx#L76) | `e.target.value as never` | `as never` 绕过类型检查 | + +**后果**:类型系统形同虚设;运行时数据结构与类型声明不符时无法被编译器捕获;重构时易引入隐蔽 bug。 + +### 2.4 安全性:DataScope 过滤逻辑过宽(P1) + +| 位置 | 问题 | 违反规则 | +|------|------|----------| +| [data-access.ts:93-111](file:///e:/Desktop/CICD/src/modules/lesson-preparation/data-access.ts#L93-L111) | `buildScopeCondition` 对 `class_taught`/`grade_managed`/`class_members`/`children` 四种 scope 统一返回 `creatorId = userId OR status = published` | "所有敏感数据查询必须在 data-access 层结合当前用户权限过滤" | +| 同上 | 教师可查看**所有** published 课案(不限学科/年级/班级) | 数据隔离不足 | +| 同上 | `class_members`(学生)/`children`(家长)scope 也返回 published 课案,但学生/家长角色未分配 `LESSON_PLAN_READ` 权限,**一旦分配则越权** | 权限边界依赖角色配置而非代码保证 | + +**后果**:教师 A 可查看教师 B 的 published 课案(即使不同学科/年级);未来若给 student/parent 开放只读权限,将立即暴露全部 published 课案。 + +### 2.5 错误与边界处理:仅路由级 + 阻塞式 UI(P1) + +| 位置 | 问题 | 违反规则 | +|------|------|----------| +| 全模块 | 无按数据区块的 Error Boundary(版本抽屉/题库选择器/知识点选择器/发布对话框任一异常导致整页崩溃) | "每个独立的数据区块必须用 React Error Boundary 包裹" | +| 全模块 | 无 Suspense + 骨架屏(版本列表/题库列表/知识点列表加载时仅显示"加载中..."文字) | "异步数据使用 React Suspense + 骨架屏" | +| [version-history-drawer.tsx:47](file:///e:/Desktop/CICD/src/modules/lesson-preparation/components/version-history-drawer.tsx#L47) | `confirm("确认回退到 v${versionNo}?")` | 应使用 AlertDialog | +| [lesson-plan-card.tsx:52](file:///e:/Desktop/CICD/src/modules/lesson-preparation/components/lesson-plan-card.tsx#L52) | `confirm("确认归档此课案?")` | 同上 | +| [inline-question-editor.tsx:30](file:///e:/Desktop/CICD/src/modules/lesson-preparation/components/inline-question-editor.tsx#L30) | `alert("请输入题干")` | 应使用 toast | +| [text-study-block.tsx:39](file:///e:/Desktop/CICD/src/modules/lesson-preparation/components/blocks/text-study-block.tsx#L39) | `alert("请先在课文中选中一段文本")` | 同上 | +| [exercise-block.tsx:155](file:///e:/Desktop/CICD/src/modules/lesson-preparation/components/blocks/exercise-block.tsx#L155) | `window.location.reload()` | 应使用 `router.refresh()` | + +**后果**:单个 Widget 故障导致整页不可用;`alert/confirm` 阻塞主线程且不可定制样式;`window.location.reload()` 丢失未保存的编辑器状态。 + +### 2.6 可测试性:纯逻辑与 UI 耦合 + 全局 store(P1) + +| 位置 | 耦合的逻辑 | 违反规则 | +|------|-----------|----------| +| [use-lesson-plan-editor.ts](file:///e:/Desktop/CICD/src/modules/lesson-preparation/hooks/use-lesson-plan-editor.ts) | zustand **全局单例** store,组件直接 `useLessonPlanEditor()` 订阅 | "组合优先:逻辑复用一律抽取为自定义 hooks" — 全局 store 无法多实例、无法注入 mock | +| [data-access.ts:31-90](file:///e:/Desktop/CICD/src/modules/lesson-preparation/data-access.ts#L31-L90) | `migrateV1ToV2`/`normalizeDocument`/`buildInitialContent` 为纯函数但与 DB 操作同文件 | 纯函数应独立便于单测 | +| [lesson-node.tsx:24-43](file:///e:/Desktop/CICD/src/modules/lesson-preparation/components/nodes/lesson-node.tsx#L24-L43) | `getNodeSummary` 业务逻辑内联在组件中 | "数据获取、计算、格式化等纯逻辑全部放入纯函数或 hooks" | +| [node-editor.tsx:33-54](file:///e:/Desktop/CICD/src/modules/lesson-preparation/components/node-editor.tsx#L33-L54) | `rfNodes`/`rfEdges` 映射逻辑内联在组件 useMemo 中 | 同上 | + +**后果**:无法对迁移/规范化/摘要逻辑做单元测试;编辑器无法多实例(如对比两个课案);组件无法独立测试(依赖全局 store)。 + +### 2.7 可复用性:角色零共享 + 无配置驱动(P1) + +| 维度 | 现状 | 违反规则 | +|------|------|----------| +| 角色覆盖 | 仅 teacher 角色可访问(admin 有权限但无 UI 入口);student/parent 完全无法查看 published 课案 | "最大化复用:识别四个角色共用的 UI 块和业务逻辑块" | +| 配置驱动 | 无角色配置,新增角色需新建整套组件 | "采用配置驱动设计,例如通过角色配置决定该模块渲染哪些 Widget/子模块" | +| 数据服务注入 | 组件直接 import actions(`getLessonPlansAction`/`updateLessonPlanAction` 等),无法替换实现 | "通过定义 TypeScript 接口抽象数据依赖,使用 React Context 注入数据服务" | +| Block 渲染 | `NodeEditPanel` 用 if/else 链渲染 4 种 block 类型,新增 block 类型需改组件 | 应改为注册表/配置驱动 | + +**后果**:无法支持 admin 查看全校课案统计、student/parent 查看教师发布的课案;未来新增角色(如教研组长)需重写模块;组件无法独立复用。 + +### 2.8 可访问性:缺失(P2) + +| 位置 | 问题 | 违反规则 | +|------|------|----------| +| 所有图标按钮 | 无 `aria-label`(如 `` 关闭按钮) | "语义化标签、ARIA 属性、键盘导航" | +| 所有模态对话框 | 无 `role="dialog"`/`aria-modal`/焦点陷阱 | 同上 | +| [node-editor.tsx](file:///e:/Desktop/CICD/src/modules/lesson-preparation/components/node-editor.tsx) | React Flow 画布无键盘导航支持(Tab/方向键无法聚焦/移动节点) | 同上 | +| [lesson-plan-filters.tsx](file:///e:/Desktop/CICD/src/modules/lesson-preparation/components/lesson-plan-filters.tsx) | `` 关联 `