docs(textbooks): 新增教材模块审计报告并同步架构图

- 新增 docs/architecture/audit/textbooks-audit-report.md,覆盖三层架构、权限、i18n、类型安全、错误边界、组件复用、a11y、可测试性、性能、安全等维度的审计,并给出 P0/P1/P2 改进优先级与重构方案要点

- 同步 004 架构影响地图 §2.5:修正 actions/data-access 行数与导出函数名(移除不存在的读 Action,补充 reorderChaptersAction),补充跨模块 UI 依赖、已知问题清单

- 同步 005 架构数据 JSON:补充 getKnowledgePointOptions 跨模块接口、uiDeps、knownIssues、auditReport 字段,修正 getTextbooks/getTextbookById 的 usedBy 以包含学生端页面
This commit is contained in:
SpecialX
2026-06-22 15:38:26 +08:00
parent 30f4983d49
commit 2548f70f40
3 changed files with 575 additions and 12 deletions

View File

@@ -625,32 +625,44 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
--- ---
## 2.5 textbooks教材模块— 标杆模块 ## 2.5 textbooks教材模块— 标杆模块data-access 层)
**职责**:教材与知识体系管理(教材/章节树形结构、知识点 CRUD、Markdown 内容编辑、知识图谱)。 **职责**:教材与知识体系管理(教材/章节树形结构、知识点 CRUD、Markdown 内容编辑、知识图谱)。
**导出函数** **导出函数**
- Actions`getTextbooksAction` / `getTextbookByIdAction` / `createTextbookAction` / `updateTextbookAction` / `deleteTextbookAction` / `getChaptersAction` / `createChapterAction` / `updateChapterAction` / `deleteChapterAction` / `getKnowledgePointsAction` / `createKnowledgePointAction` / `updateKnowledgePointAction` / `deleteKnowledgePointAction` - Actions10 个,均为写操作;读操作由 RSC 页面直接调用 data-access`createTextbookAction` / `updateTextbookAction` / `deleteTextbookAction` / `createChapterAction` / `updateChapterContentAction` / `deleteChapterAction` / `reorderChaptersAction` / `createKnowledgePointAction` / `updateKnowledgePointAction` / `deleteKnowledgePointAction`
- Data-access与 actions 一一对应的 data-access 函数 - Data-access`getTextbooks` / `getTextbookById` / `getChaptersByTextbookId` / `getKnowledgePointsByChapterId` / `getKnowledgePointsByTextbookId` / `createTextbook` / `updateTextbook` / `deleteTextbook` / `createChapter` / `updateChapterContent` / `deleteChapter` / `createKnowledgePoint` / `updateKnowledgePoint` / `deleteKnowledgePoint` / `reorderChapters` / `getTextbooksDashboardStats` / `getKnowledgePointOptions`(跨模块接口,供 questions 调用)
**依赖关系** **依赖关系**
- 依赖:`shared/*``@/auth` - 依赖:`shared/*``@/auth`
- 被依赖:`questions`(✅ P1-1 已修复:通过 textbooks data-access`exams`(通过类型)、`dashboard`(通过 data-accessP0-4 已修复) - 被依赖:`questions`(✅ P1-1 已修复:通过 textbooks data-access`exams`(通过类型)、`dashboard`(通过 data-accessP0-4 已修复)
- ⚠️ UI 层跨模块依赖:`textbooks/components/knowledge-point-dialogs.tsx` 直接 import `questions/components/create-question-dialog`P0 待解耦,详见 [textbooks-audit-report.md](audit/textbooks-audit-report.md)
**已知问题** **已知问题**
- ✅ 无跨模块 DB 访问 - ✅ 无跨模块 DB 访问data-access 层)
- ✅ actions 层编排模式标杆(权限校验 → 调用 data-access → revalidatePath - ✅ actions 层编排模式标杆(权限校验 → 调用 data-access → revalidatePath
- ✅ data-access 层职责单一 - ✅ data-access 层职责单一
- ✅ P2 已修复:`data-access.ts``byId.get(pid)!.children.push` 非空断言清理为安全守卫;`or(...)!` 非空断言清理为条件 push - ✅ P2 已修复:`data-access.ts``byId.get(pid)!.children.push` 非空断言清理为安全守卫;`or(...)!` 非空断言清理为条件 push
- ⚠️ P0 跨模块 UI 依赖:`knowledge-point-dialogs.tsx` 直接 import questions 模块组件
- ⚠️ P0 前端权限硬编码:`canEdit={true}` 按路由写死,未用 `usePermission().hasPermission()`
- ⚠️ P0 全模块零 i18n中英文文案硬编码未接入 next-intl
- ⚠️ P1 Server Action 未校验资源归属chapterId 是否属于 textbookId
- ⚠️ P1 data-access 缺数据范围过滤(学生端未按年级过滤)
- ⚠️ P1 缺 Error Boundary无 error.tsx
- ⚠️ P1 知识点列表/弹窗存在重复实现knowledge-point-panel.tsx 无调用方)
- ⚠️ P1 学科/年级选项硬编码三处且彼此不一致
- ⚠️ P1 纯逻辑未导出,零单测
**文件清单** **文件清单**
| 文件 | 行数 | 职责 | | 文件 | 行数 | 职责 |
|------|------|------| |------|------|------|
| `actions.ts` | 276 | 13 个 Server Action标杆 | | `actions.ts` | 317 | 10 个 Server Action写操作 |
| `data-access.ts` | 428 | 教材/章节/知识点 CRUD | | `data-access.ts` | 514 | 教材/章节/知识点 CRUD + 跨模块查询接口 |
| `types.ts` | 79 | 类型定义 | | `types.ts` | 45 | 类型定义 |
| `schema.ts` | 64 | Zod 校验 |
| `hooks/use-knowledge-point-actions.ts` | 121 | 知识点操作 Hook | | `hooks/use-knowledge-point-actions.ts` | 121 | 知识点操作 Hook |
| `components/*` | 12 文件 | 教材编辑/知识图谱组件 | | `hooks/use-text-selection.ts` | 57 | 文本选区捕获 Hook |
| `components/*` | 11 文件 | 教材编辑/知识图谱组件 |
--- ---
@@ -1068,11 +1080,13 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
**导出函数** **导出函数**
- Data-access`getChildren` / `getChildBasicInfo` / `getChildDashboardData` / `getParentDashboardData` / `verifyParentChildRelation` / `getChildNameList`(✅ v4 新增:用于详情页头部多子女切换器,一次批量查询避免 N+1 - Data-access`getChildren` / `getChildBasicInfo` / `getChildDashboardData` / `getParentDashboardData` / `verifyParentChildRelation` / `getChildNameList`(✅ v4 新增:用于详情页头部多子女切换器,一次批量查询避免 N+1
- Components`ParentDashboard` / `ChildCard` / `ChildDetailHeader` / `ChildDetailPanel` / `SiblingSwitcher` / `ChildHomeworkSummary` / `ChildGradeSummary` / `ChildScheduleCard` / `ParentChildrenDataPage` / `ParentNoChildrenPage` / `ParentAttentionBanner`v4 新增)/ `ParentAttendanceWarning`v4 新增)/ `ParentExportButton`v4 新增) - Components`ParentDashboard` / `ChildCard` / `ChildDetailHeader` / `ChildDetailPanel` / `SiblingSwitcher` / `ChildHomeworkSummary` / `ChildHomeworkDetail`v4 新增)/ `ChildGradeSummary` / `ChildGradeDetail`v4 新增)/ `ChildScheduleCard` / `ParentChildrenDataPage` / `ParentNoChildrenPage` / `ParentAttentionBanner`v4 新增)/ `ParentAttendanceWarning`v4 新增)/ `ParentAttendanceRateCard`v4 新增)/ `ParentAttendanceCalendar`v4 新增)/ `ParentExportButton`v4 新增)
**v4 修复(产品/UX 维度)** **v4 修复(产品/UX 维度)**
- ✅ FEAT-G01新增 `/parent/leave` 请假申请占位页(含 loading.tsx - ✅ FEAT-G01新增 `/parent/leave` 请假申请占位页(含 loading.tsx
- ✅ FEAT-G02详情页 Schedule Tab 支持完整周课表(新增 `weeklySchedule` 字段 + `ChildWeeklyScheduleItem` 类型 + `buildWeeklySchedule` 函数) - ✅ FEAT-G02详情页 Schedule Tab 支持完整周课表(新增 `weeklySchedule` 字段 + `ChildWeeklyScheduleItem` 类型 + `buildWeeklySchedule` 函数)
- ✅ FEAT-G03详情页 Grades Tab 新增 `ChildGradeDetail` 按科目分组展示(平均分、趋势、最近成绩)
- ✅ FEAT-G04详情页 Homework Tab 新增 `ChildHomeworkDetail` 展示完整作业信息(状态、截止、提交时间、尝试次数)
- ✅ FEAT-G05考勤页新增 `ParentAttendanceWarning` 异常预警横幅(聚合缺勤/迟到/低出勤率) - ✅ FEAT-G05考勤页新增 `ParentAttendanceWarning` 异常预警横幅(聚合缺勤/迟到/低出勤率)
- ✅ FEAT-G06详情页底部新增"Contact Teacher"快捷入口 - ✅ FEAT-G06详情页底部新增"Contact Teacher"快捷入口
- ✅ FEAT-G07详情页头部新增 `SiblingSwitcher` 多子女切换器 - ✅ FEAT-G07详情页头部新增 `SiblingSwitcher` 多子女切换器
@@ -1083,12 +1097,18 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
- ✅ LAYOUT-P05详情页新增面包屑导航 - ✅ LAYOUT-P05详情页新增面包屑导航
- ✅ LAYOUT-P07成绩趋势图 X 轴改用序号,避免日期重叠 - ✅ LAYOUT-P07成绩趋势图 X 轴改用序号,避免日期重叠
- ✅ LAYOUT-P08成绩页新增 `ParentExportButton` 导出按钮(占位) - ✅ LAYOUT-P08成绩页新增 `ParentExportButton` 导出按钮(占位)
- ✅ LAYOUT-P09考勤页新增 `ParentAttendanceCalendar` 月历视图(按状态着色,支持按月切换)
- ✅ LAYOUT-P10考勤异常高亮与 FEAT-G05 同步实现)
- ✅ NAV-P02Grades/Attendance 页面描述明确职责(多子女对比 vs 单子女详情)
- ✅ NAV-P03详情页实现 `?tab=` 参数支持 - ✅ NAV-P03详情页实现 `?tab=` 参数支持
- ✅ NAV-P04所有 parent 路由新增 `loading.tsx` 骨架屏 + `error.tsx` 错误边界 - ✅ NAV-P04所有 parent 路由新增 `loading.tsx` 骨架屏 + `error.tsx` 错误边界
- ✅ DATA-P02成绩卡片新增 TrendIcon 进步/退步/持平标识 - ✅ DATA-P02成绩卡片新增 TrendIcon 进步/退步/持平标识
- ✅ DATA-P03排名展示新增"Top X%"百分比 - ✅ DATA-P03排名展示新增"Top X%"百分比
- ✅ DATA-P04作业列表新增科目标识 Badge - ✅ DATA-P04作业列表新增科目标识 Badge
- ✅ DATA-P05作业分数显示新增"pts"单位
- ✅ DATA-P06考勤页新增 `ParentAttendanceRateCard` 出勤率汇总卡片
- ✅ HABIT-P01仪表盘"一眼定位异常"能力AttentionBanner 聚合) - ✅ HABIT-P01仪表盘"一眼定位异常"能力AttentionBanner 聚合)
- ✅ HABIT-P02待办横幅作业项直接跳转详情页 homework tab1 次点击到达)
- ✅ HABIT-P03多子女切换无需返回仪表盘 - ✅ HABIT-P03多子女切换无需返回仪表盘
- ✅ HABIT-P06仪表盘展示未读/待办数量 - ✅ HABIT-P06仪表盘展示未读/待办数量
- ✅ A11Y-P02Overdue 状态增加 AlertTriangle 图标辅助 - ✅ A11Y-P02Overdue 状态增加 AlertTriangle 图标辅助
@@ -1096,6 +1116,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
- ✅ PERF-P01/P02骨架屏 + 错误边界 - ✅ PERF-P01/P02骨架屏 + 错误边界
- ✅ PERF-P03空状态新增"Contact support"引导按钮 - ✅ PERF-P03空状态新增"Contact support"引导按钮
- ✅ PERF-P04`ChildCard` Link 添加 `prefetch` - ✅ PERF-P04`ChildCard` Link 添加 `prefetch`
- ✅ MOBILE-P03移动端子女卡片改为水平滑动 Carouselsnap-x
- ✅ MOBILE-P04作业/成绩列表项 `min-h-[44px]` 触摸区域 - ✅ MOBILE-P04作业/成绩列表项 `min-h-[44px]` 触摸区域
**依赖关系** **依赖关系**

View File

@@ -4076,14 +4076,16 @@
"name": "getTextbooks", "name": "getTextbooks",
"signature": "(query?, subject?, grade?) => Promise<Textbook[]>", "signature": "(query?, subject?, grade?) => Promise<Textbook[]>",
"usedBy": [ "usedBy": [
"teacher/textbooks/page.tsx" "teacher/textbooks/page.tsx",
"student/learning/textbooks/page.tsx"
] ]
}, },
{ {
"name": "getTextbookById", "name": "getTextbookById",
"signature": "(id) => Promise<Textbook | undefined>", "signature": "(id) => Promise<Textbook | undefined>",
"usedBy": [ "usedBy": [
"teacher/textbooks/[id]/page.tsx" "teacher/textbooks/[id]/page.tsx",
"student/learning/textbooks/[id]/page.tsx"
] ]
}, },
{ {
@@ -4194,6 +4196,14 @@
"usedBy": [ "usedBy": [
"dashboard/data-access.getAdminDashboardData" "dashboard/data-access.getAdminDashboardData"
] ]
},
{
"name": "getKnowledgePointOptions",
"signature": "() => Promise<KnowledgePointOption[]>",
"purpose": "跨模块接口:获取所有知识点选项(含章节/教材信息),供 questions 模块调用",
"usedBy": [
"questions/data-access.getKnowledgePointOptions"
]
} }
], ],
"hooks": [ "hooks": [
@@ -4350,7 +4360,29 @@
"name": "TextbookSettingsDialog", "name": "TextbookSettingsDialog",
"purpose": "教材设置对话框" "purpose": "教材设置对话框"
} }
] ],
"uiDeps": [
{
"from": "textbooks/components/knowledge-point-dialogs.tsx",
"to": "questions/components/create-question-dialog",
"status": "P0 待解耦(直接 import 跨模块业务组件,应改为 props/Context 注入)",
"auditRef": "audit/textbooks-audit-report.md §2.1.1"
}
],
"knownIssues": [
"P0 跨模块 UI 依赖knowledge-point-dialogs.tsx 直接 import questions 模块 CreateQuestionDialog",
"P0 前端权限硬编码canEdit={true} 按路由写死,未用 usePermission().hasPermission()",
"P0 全模块零 i18n中英文文案硬编码未接入 next-intl",
"P1 Server Action 未校验资源归属chapterId 是否属于 textbookId",
"P1 data-access 缺数据范围过滤(学生端未按年级过滤)",
"P1 缺 Error Boundary无 error.tsx",
"P1 知识点列表/弹窗重复实现knowledge-point-panel.tsx 无调用方)",
"P1 学科/年级选项硬编码三处且彼此不一致",
"P1 纯逻辑未导出,零单测",
"P2 类型断言chapter-sidebar-list.tsx 用 ! 、knowledge-graph.tsx 用 as+!",
"P2 删除确认不一致textbook-settings-dialog 用 confirm(),其余用 AlertDialog"
],
"auditReport": "audit/textbooks-audit-report.md"
} }
}, },
"classes": { "classes": {

View File

@@ -0,0 +1,510 @@
# 教材Textbooks模块审计报告
> 审计日期2026-06-22
> 审计范围:`src/modules/textbooks/**`、`src/app/(dashboard)/teacher/textbooks/**`、`src/app/(dashboard)/student/learning/textbooks/**`
> 参照规则:`docs/architecture/004_architecture_impact_map.md`、`docs/architecture/005_architecture_data.json`、`.trae/rules/project_rules.md`
---
## 一、现有实现概要
### 1.1 文件分布
教材模块作为 K12 系统的"标杆模块"(架构图原文),文件分布如下:
| 层 | 文件 | 行数 | 职责 |
|------|------|------|------|
| 数据访问 | [data-access.ts](file:///e:/Desktop/CICD/src/modules/textbooks/data-access.ts) | 514 | 教材/章节/知识点 CRUD + 跨模块查询接口 |
| Server Actions | [actions.ts](file:///e:/Desktop/CICD/src/modules/textbooks/actions.ts) | 317 | 13 个 Server Action含权限校验 |
| 类型 | [types.ts](file:///e:/Desktop/CICD/src/modules/textbooks/types.ts) | 45 | Textbook / Chapter / KnowledgePoint 类型 |
| 校验 | [schema.ts](file:///e:/Desktop/CICD/src/modules/textbooks/schema.ts) | 64 | Zod 校验 schema |
| Hook | [hooks/use-knowledge-point-actions.ts](file:///e:/Desktop/CICD/src/modules/textbooks/hooks/use-knowledge-point-actions.ts) | 121 | 知识点增删改状态机 |
| Hook | [hooks/use-text-selection.ts](file:///e:/Desktop/CICD/src/modules/textbooks/hooks/use-text-selection.ts) | 57 | 文本选区捕获 |
| 组件 | [components/textbook-reader.tsx](file:///e:/Desktop/CICD/src/modules/textbooks/components/textbook-reader.tsx) | 319 | 阅读器主壳Tabs目录/知识点/图谱) |
| 组件 | [components/textbook-content-panel.tsx](file:///e:/Desktop/CICD/src/modules/textbooks/components/textbook-content-panel.tsx) | 170 | Markdown 渲染 + 编辑切换 |
| 组件 | [components/chapter-sidebar-list.tsx](file:///e:/Desktop/CICD/src/modules/textbooks/components/chapter-sidebar-list.tsx) | 348 | 递归章节树 + 拖拽排序 |
| 组件 | [components/knowledge-point-list.tsx](file:///e:/Desktop/CICD/src/modules/textbooks/components/knowledge-point-list.tsx) | 107 | 知识点列表 |
| 组件 | [components/knowledge-graph.tsx](file:///e:/Desktop/CICD/src/modules/textbooks/components/knowledge-graph.tsx) | 181 | 知识图谱 SVG 可视化 |
| 组件 | [components/knowledge-point-panel.tsx](file:///e:/Desktop/CICD/src/modules/textbooks/components/knowledge-point-panel.tsx) | 157 | 知识点面板(旧版,与 list 重叠) |
| 组件 | [components/knowledge-point-dialogs.tsx](file:///e:/Desktop/CICD/src/modules/textbooks/components/knowledge-point-dialogs.tsx) | 148 | 创建/编辑知识点弹窗集合 |
| 组件 | [components/textbook-card.tsx](file:///e:/Desktop/CICD/src/modules/textbooks/components/textbook-card.tsx) | 121 | 教材卡片 |
| 组件 | [components/textbook-filters.tsx](file:///e:/Desktop/CICD/src/modules/textbooks/components/textbook-filters.tsx) | 71 | 筛选栏 |
| 组件 | [components/textbook-form-dialog.tsx](file:///e:/Desktop/CICD/src/modules/textbooks/components/textbook-form-dialog.tsx) | 134 | 新建教材弹窗 |
| 组件 | [components/textbook-settings-dialog.tsx](file:///e:/Desktop/CICD/src/modules/textbooks/components/textbook-settings-dialog.tsx) | 160 | 教材设置/删除弹窗 |
| 组件 | [components/create-chapter-dialog.tsx](file:///e:/Desktop/CICD/src/modules/textbooks/components/create-chapter-dialog.tsx) | 95 | 新建章节弹窗 |
| 组件 | [components/create-knowledge-point-dialog.tsx](file:///e:/Desktop/CICD/src/modules/textbooks/components/create-knowledge-point-dialog.tsx) | 95 | 新建知识点弹窗(旧版) |
| 页面 | [teacher/textbooks/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/textbooks/page.tsx) | 68 | 教师端列表页RSC |
| 页面 | [teacher/textbooks/[id]/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/textbooks/[id]/page.tsx) | 65 | 教师端详情页RSC |
| 页面 | [student/learning/textbooks/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/student/learning/textbooks/page.tsx) | 66 | 学生端列表页RSC |
| 页面 | [student/learning/textbooks/[id]/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/student/learning/textbooks/[id]/page.tsx) | 64 | 学生端详情页RSC |
| 骨架屏 | 4 个 `loading.tsx` | — | 列表/详情骨架屏 |
### 1.2 数据流
```
page.tsx (RSC)
└─ getTextbooks / getTextbookById / getChaptersByTextbookId / getKnowledgePointsByTextbookId (data-access)
└─ db (drizzle) → textbooks / chapters / knowledgePoints 表
└─ <TextbookReader> (client)
├─ <ChapterSidebarList> → deleteChapterAction / reorderChaptersAction
├─ <TextbookContentPanel> → updateChapterContentAction
├─ <KnowledgePointList> → useKnowledgePointActions → create/update/deleteKnowledgePointAction
└─ <KnowledgePointDialogs> → ⚠️ 直接 import @/modules/questions/components/create-question-dialog
```
### 1.3 架构图记录完整性
经核对 [004_architecture_impact_map.md](file:///e:/Desktop/CICD/docs/architecture/004_architecture_impact_map.md) §2.5 与 [005_architecture_data.json](file:///e:/Desktop/CICD/docs/architecture/005_architecture_data.json),架构图对教材模块的记录**存在以下偏差**(详见第五节):
- 行数统计过期:图记 `actions.ts 276 行 / data-access.ts 428 行`,实际为 `317 / 514`
- 导出函数名错误:图记 `getTextbooksAction / getTextbookByIdAction / getChaptersAction / getKnowledgePointsAction` 等"读 Action",实际不存在——读操作直接走 data-accessRSC未包装成 Action。
- 组件文件数:图记"12 文件",实际 11 个组件文件。
- 未记录跨模块 UI 依赖:`knowledge-point-dialogs.tsx` 直接 import questions 模块的 `CreateQuestionDialog`,图未标注。
---
## 二、现存问题与原因分析
### 2.1 架构解耦
#### 问题 2.1.1 跨模块直接 import 业务组件P0
- **位置**[knowledge-point-dialogs.tsx#L16](file:///e:/Desktop/CICD/src/modules/textbooks/components/knowledge-point-dialogs.tsx#L16)
- **现象**`import { CreateQuestionDialog } from "@/modules/questions/components/create-question-dialog"`
- **违反规则**:项目规则"该模块必须作为独立功能单元……模块内部组件绝不直接 import 其他业务模块的 actions 或 data-access只能通过注入的接口调用"以及"模块间只能通过对方 data-access 通信"。
- **原因**:教材知识点页希望"一键创建相关题目",直接耦合了 questions 模块的弹窗组件,而非通过接口注入或事件回调。
- **后果**questions 模块任何对 `CreateQuestionDialog` props/位置的变更都会破坏教材模块编译;无法独立测试、独立部署教材模块;新增 admin/parent 角色时无法替换该弹窗实现。
#### 问题 2.1.2 前端权限硬编码 `canEdit`P0
- **位置**
- [teacher/textbooks/[id]/page.tsx#L60](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/textbooks/[id]/page.tsx#L60)`canEdit={true}`
- [student/learning/textbooks/[id]/page.tsx#L58](file:///e:/Desktop/CICD/src/app/(dashboard)/student/learning/textbooks/[id]/page.tsx#L58):未传 `canEdit`(默认 `false`
- **违反规则**:项目规则"前端权限判断统一使用 `usePermission().hasPermission()`,严禁出现 `role === "xxx"` 硬编码"。此处虽未出现 `role ===`,但用"路由前缀"teacher/student隐式决定编辑权本质等价于角色硬编码。
- **原因**:图省事直接按路由写死布尔值,未接入权限上下文。
- **后果**:一旦 admin 也需编辑教材、或 teacher 在某些场景被回收 `TEXTBOOK_UPDATE`,前端仍会展示编辑按钮,造成"按钮可见但点击 403"的体验;权限策略变更需改多处代码。
#### 问题 2.1.3 data-access 缺少数据范围过滤P1
- **位置**[data-access.ts#L75](file:///e:/Desktop/CICD/src/modules/textbooks/data-access.ts#L75) `getTextbooks`、[#L125](file:///e:/Desktop/CICD/src/modules/textbooks/data-access.ts#L125) `getTextbookById`
- **现象**:查询未结合当前用户身份(年级、班级、学科权限)做过滤,任何能进入路由的用户都能读到全量教材。
- **违反规则**:项目规则"所有敏感数据查询必须在 data-access 层结合当前用户权限过滤"。
- **原因**:学生端页面虽调用 `getCurrentStudentUser()`,但拿到的 student 信息并未用于过滤教材(如按学生年级筛选)。
- **后果**:跨年级学生可看到非本年级教材;多租户场景下数据越权。
### 2.2 国际化i18n
#### 问题 2.2.1 全模块零 i18n 覆盖P0
- **位置**:模块全部 19 个源文件
- **现象**:项目已接入 next-intl见 [i18n/request.ts](file:///e:/Desktop/CICD/src/i18n/request.ts)),但教材模块**没有任何一处**使用 `useTranslations` / `getTranslations`,所有文案硬编码,且中英文混杂:
- 中文硬编码:`"章节目录"``"知识点"``"图谱"``"请选择一个章节查看知识点。"``"该章节暂无知识点。"``"添加知识点"``"取消"``"删除"``"保存"``"确认删除"``"确定要删除这个知识点吗?此操作无法撤销。"``"创建中..."``"保存中..."``"知识点已创建"``"发生错误"``"删除失败"``"更新失败"``"返回教材列表"` 等([textbook-reader.tsx](file:///e:/Desktop/CICD/src/modules/textbooks/components/textbook-reader.tsx)、[knowledge-point-list.tsx](file:///e:/Desktop/CICD/src/modules/textbooks/components/knowledge-point-list.tsx)、[knowledge-point-dialogs.tsx](file:///e:/Desktop/CICD/src/modules/textbooks/components/knowledge-point-dialogs.tsx)、[use-knowledge-point-actions.ts](file:///e:/Desktop/CICD/src/modules/textbooks/hooks/use-knowledge-point-actions.ts)、[teacher/textbooks/[id]/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/textbooks/[id]/page.tsx)
- 英文硬编码:`"Textbooks"``"Manage your digital curriculum resources and chapters."``"Add Textbook"``"Add New Textbook"``"Create a new digital textbook."``"Save changes"``"Search by title, publisher..."``"All Subjects"``"All Grades"``"Subject"``"Grade"``"Publisher"``"Title"``"Chapters"``"Updated"``"Edit Content"``"Delete"``"Settings"``"Textbook Settings"``"Delete Textbook"``"Add Chapter"``"Add Knowledge Point"``"Knowledge Points"``"No points yet"``"Select a chapter to manage knowledge points"` 等([textbook-filters.tsx](file:///e:/Desktop/CICD/src/modules/textbooks/components/textbook-filters.tsx)、[textbook-form-dialog.tsx](file:///e:/Desktop/CICD/src/modules/textbooks/components/textbook-form-dialog.tsx)、[textbook-settings-dialog.tsx](file:///e:/Desktop/CICD/src/modules/textbooks/components/textbook-settings-dialog.tsx)、[textbook-card.tsx](file:///e:/Desktop/CICD/src/modules/textbooks/components/textbook-card.tsx)、[knowledge-point-panel.tsx](file:///e:/Desktop/CICD/src/modules/textbooks/components/knowledge-point-panel.tsx)
- **违反规则**:项目规则"所有用户可见文本必须适配 i18n使用 next-intl提取翻译键"。
- **原因**:模块开发时未跟进 i18n 改造,文案随写随定。
- **后果**:无法切换语言;同一界面中英混杂,专业度差;后续做国际化需返工全部组件。
### 2.3 类型安全
#### 问题 2.3.1 非空断言与 `as` 断言P1
- **位置**
- [chapter-sidebar-list.tsx#L141](file:///e:/Desktop/CICD/src/modules/textbooks/components/chapter-sidebar-list.tsx#L141)`items={chapter.children!}` —— 已在 `hasChildren` 守卫后仍用 `!`,应改用 narrowing。
- [knowledge-graph.tsx#L105](file:///e:/Desktop/CICD/src/modules/textbooks/components/knowledge-graph.tsx#L105)`positions.get(kp.parentId as string)!` —— `as string` + `!` 双重断言。
- [knowledge-graph.tsx#L106](file:///e:/Desktop/CICD/src/modules/textbooks/components/knowledge-graph.tsx#L106)`positions.get(kp.id)!`
- **违反规则**:项目规则"禁止 `as` 断言(除非从 `unknown` 转换)"、"可选链后禁止跟非空断言 `!`"。
- **后果**:运行时若数据不一致(如 parentId 指向已删除节点),直接抛错而非优雅降级。
#### 问题 2.3.2 `data-access.ts` 使用 `select()` 无类型投影P2
- **位置**[data-access.ts#L413](file:///e:/Desktop/CICD/src/modules/textbooks/data-access.ts#L413)`db.select().from(chapters)`
- **现象**`select()` 不传参数返回整行,类型推断为全表 schema与模块对外 `Chapter` 类型不完全一致(如 `content` 可空性)。
- **后果**:类型边界模糊,后续 schema 变更可能静默破坏调用方。
### 2.4 错误与边界处理
#### 问题 2.4.1 缺少 React Error BoundaryP1
- **位置**`src/app/(dashboard)/teacher/textbooks/**``src/app/(dashboard)/student/learning/textbooks/**` 均无 `error.tsx`
- **现象**:详情页 `getTextbookById` 返回 `undefined` 时走 `notFound()`,但章节/知识点查询失败、Server Action 抛错时整页崩溃,无降级 UI。
- **违反规则**:项目规则"每个独立的数据区块必须用 React Error Boundary 包裹"。
- **后果**:一次 DB 抖动导致整个阅读器白屏,无法隔离故障域。
#### 问题 2.4.2 删除确认交互不一致P2
- **位置**[textbook-settings-dialog.tsx#L52](file:///e:/Desktop/CICD/src/modules/textbooks/components/textbook-settings-dialog.tsx#L52)`if (!confirm("Are you sure..."))` 使用浏览器原生 `confirm`
- **现象**:模块内其他删除(章节、知识点)均用 `AlertDialog`,唯独教材删除用 `confirm()`
- **违反规则**:项目规则"组合优先"与 UI 一致性;`confirm()` 阻塞主线程且不可定制样式。
- **后果**:交互体验割裂;移动端 `confirm` 表现不一。
#### 问题 2.4.3 空状态文案与组件不统一P2
- **位置**
- [textbook-reader.tsx#L222](file:///e:/Desktop/CICD/src/modules/textbooks/components/textbook-reader.tsx#L222):内联 `<div>请选择一个章节查看知识点。</div>`
- [knowledge-point-list.tsx#L32](file:///e:/Desktop/CICD/src/modules/textbooks/components/knowledge-point-list.tsx#L32):内联 `<div>该章节暂无知识点。</div>`
- [textbook-content-panel.tsx#L67](file:///e:/Desktop/CICD/src/modules/textbooks/components/textbook-content-panel.tsx#L67):内联 `<div>请选择一个章节开始阅读。</div>`
- 列表页则用 `EmptyState` 组件
- **后果**同一模块内空状态有三种写法维护成本高a11y 属性缺失。
### 2.5 组件复用与组合
#### 问题 2.5.1 知识点列表/面板存在重复实现P1
- **位置**
- [knowledge-point-list.tsx](file:///e:/Desktop/CICD/src/modules/textbooks/components/knowledge-point-list.tsx)107 行,被 `TextbookReader` 使用)
- [knowledge-point-panel.tsx](file:///e:/Desktop/CICD/src/modules/textbooks/components/knowledge-point-panel.tsx)157 行,未被任何页面引用,疑似旧版遗留)
- **现象**:两个组件职责几乎相同(展示章节知识点 + 删除),`KnowledgePointPanel` 还自带 `router.refresh()`,但实际无调用方。
- **违反规则**:项目规则"最大化复用"。
- **后果**:死代码增加认知负担;修改知识点展示逻辑需同步两处。
#### 问题 2.5.2 创建知识点弹窗存在两套实现P1
- **位置**
- [create-knowledge-point-dialog.tsx](file:///e:/Desktop/CICD/src/modules/textbooks/components/create-knowledge-point-dialog.tsx)(独立弹窗,被 `KnowledgePointPanel` 引用,但 `KnowledgePointPanel` 本身无调用方)
- [knowledge-point-dialogs.tsx#L56-L85](file:///e:/Desktop/CICD/src/modules/textbooks/components/knowledge-point-dialogs.tsx#L56)(内嵌创建弹窗,被 `TextbookReader` 使用)
- **现象**:两套创建知识点弹窗,文案一中一英,字段一致但实现独立。
- **后果**:同上,双份维护。
#### 问题 2.5.3 学科/年级选项硬编码三处P1
- **位置**
- [textbook-filters.tsx#L43-L66](file:///e:/Desktop/CICD/src/modules/textbooks/components/textbook-filters.tsx#L43)Select 选项
- [textbook-form-dialog.tsx#L89-L113](file:///e:/Desktop/CICD/src/modules/textbooks/components/textbook-form-dialog.tsx#L89)Select 选项(且 form 与 settings 的学科列表不一致form 含 Biology/Geographysettings 缺这两项)
- [textbook-settings-dialog.tsx#L106-L112](file:///e:/Desktop/CICD/src/modules/textbooks/components/textbook-settings-dialog.tsx#L106)Select 选项
- [textbook-card.tsx#L26-L34](file:///e:/Desktop/CICD/src/modules/textbooks/components/textbook-card.tsx#L26)`subjectColorMap` 学科颜色映射
- **现象**:学科、年级枚举在 4 个文件里各写一份,且**彼此不一致**settings 弹窗的学科列表少了 Biology 和 Geography
- **违反规则**:项目规则"最大化复用……抽象为泛型组件和 hooks"、"配置驱动设计"。
- **后果**:新增学科需改 4 处;当前已出现数据不一致——用户在 form 里能选 Biology但 settings 里看不到,编辑时学科被覆盖。
### 2.6 可访问性a11y
#### 问题 2.6.1 知识图谱 SVG 缺少无障碍属性P1
- **位置**[knowledge-graph.tsx#L142-L158](file:///e:/Desktop/CICD/src/modules/textbooks/components/knowledge-graph.tsx#L142)
- **现象**`<svg>``role="img"`、无 `aria-label`、无 `<title>`;节点用 `<button>` 但无 `aria-label` 描述跳转目标。
- **违反规则**:项目规则"可访问性a11y语义化标签、ARIA 属性、键盘导航"。
- **后果**:屏幕阅读器用户无法理解图谱内容。
#### 问题 2.6.2 图谱节点不支持键盘导航P2
- **位置**[knowledge-graph.tsx#L159](file:///e:/Desktop/CICD/src/modules/textbooks/components/knowledge-graph.tsx#L159)
- **现象**:节点用绝对定位 `<button>`,但无 `tabIndex` 管理、无方向键导航Tab 顺序混乱。
- **后果**:键盘用户难以在图谱中移动焦点。
### 2.7 可测试性
#### 问题 2.7.1 纯逻辑未导出无法单测P1
- **位置**
- [data-access.ts#L29-L73](file:///e:/Desktop/CICD/src/modules/textbooks/data-access.ts#L29) `sortChapters` / `buildChapterTree`(模块内未导出)
- [knowledge-graph.tsx#L29-L117](file:///e:/Desktop/CICD/src/modules/textbooks/components/knowledge-graph.tsx#L29) `computeGraphLayout`(模块内未导出)
- [textbook-reader.tsx#L32-L44](file:///e:/Desktop/CICD/src/modules/textbooks/components/textbook-reader.tsx#L32) `buildChapterIndex`
- **现象**:这些纯函数(树构建、图布局、索引构建)是核心逻辑,但未导出,无法写单测;模块目录下无任何 `__tests__``*.test.ts`
- **违反规则**:项目规则"数据获取、计算、格式化等纯逻辑全部放入纯函数或 hooks与 UI 分离;导出清晰的接口类型以便 mock"。
- **后果**:章节树构建、图谱布局这类容易出 bug 的算法无回归保护。
#### 问题 2.7.2 零测试覆盖P1
- **位置**:整个模块
- **现象**:无单元测试、无集成测试、无 e2e 测试。
- **后果**:重构高风险。
### 2.8 性能
#### 问题 2.8.1 知识点高亮用正则全局替换存在性能与正确性风险P2
- **位置**[textbook-reader.tsx#L153-L165](file:///e:/Desktop/CICD/src/modules/textbooks/components/textbook-reader.tsx#L153)
- **现象**`processedContent` 对每个知识点名做 `new RegExp(..., "gi")` 全局替换O(n×m) 复杂度;且未处理知识点名互为子串的情况(已按长度降序缓解,但仍可能误伤)。
- **后果**:章节内容长、知识点多时主线程卡顿;高亮可能跨标签边界破坏 Markdown。
#### 问题 2.8.2 `getKnowledgePointsByTextbookId` 一次性拉全量P2
- **位置**[data-access.ts#L357](file:///e:/Desktop/CICD/src/modules/textbooks/data-access.ts#L357)
- **现象**:详情页一次性加载整本教材所有章节的知识点,无分页/懒加载。
- **后果**:大体量教材首屏慢。
### 2.9 安全性
#### 问题 2.9.1 Server Action 未校验资源归属P1
- **位置**[actions.ts](file:///e:/Desktop/CICD/src/modules/textbooks/actions.ts) 全部 Action
- **现象**`updateChapterContentAction(chapterId, content, textbookId)` 仅校验 `TEXTBOOK_UPDATE` 权限,未校验 `chapterId` 是否属于当前用户有权访问的教材。
- **违反规则**:项目规则"Server Action 二次校验"。
- **后果**:教师 A 可通过改 chapterId 篡改教师 B 的章节内容(越权写)。
#### 问题 2.9.2 Markdown 渲染虽用 sanitize但编辑端无 XSS 过滤P2
- **位置**[textbook-content-panel.tsx#L118](file:///e:/Desktop/CICD/src/modules/textbooks/components/textbook-content-panel.tsx#L118) 用了 `rehype-sanitize`(✅),但 [RichTextEditor](file:///e:/Desktop/CICD/src/shared/components/ui/rich-text-editor.tsx) 输出未在保存前清洗。
- **后果**:依赖前端 sanitize一旦渲染端配置变更可能被绕过。
---
## 三、行业差距对比
对标国内外主流 K12 教育平台如人教数字教材、ClassIn、Seewo、Khan Academy、好未来"学而思"教材体系)在教材模块的设计,本模块存在以下差距:
### 3.1 内容呈现层
| 行业优秀实践 | 本模块现状 | 影响 |
|---|---|---|
| 支持富媒体嵌入(图片/音频/视频/公式/交互式 3D 模型) | 仅 Markdown 文本 + `RichTextEditor` | 理科教材无法呈现实验视频、几何图形、化学方程式K12 教学场景严重受限 |
| 公式编辑LaTeX / MathML | 无 | 数学/物理教材无法正确呈现公式 |
| 页面翻阅式阅读(带页码、书签、进度记忆) | 仅滚动 + URL `chapterId` | 学生阅读进度无持久化,无法"续读" |
| 朗读 / TTS 朗读 | 无 | 低年级学生、视障学生体验差 |
| 笔记/划线/高亮/书签 | 仅有"选区创建知识点" | 学生无法在教材上做个人笔记,教师无法布置"精读"任务 |
### 3.2 知识体系层
| 行业优秀实践 | 本模块现状 | 影响 |
|---|---|---|
| 知识图谱支持缩放/拖拽/力导向布局/关联题目预览 | 静态 SVG 树状布局,无交互(无缩放、无拖拽、无关联题目) | 图谱仅"能看",不能"用",无法支撑知识图谱驱动的个性化学习 |
| 知识点与题目/作业/考试双向关联,支持"知识点掌握度"雷达 | 仅单向"知识点→创建题目"入口 | 无法做学情诊断、薄弱知识点推送 |
| 知识点支持多级层级、跨章节关联、前置/后置依赖 | 仅 `parentId` 树 + `chapterId` 归属 | 无法表达"学习路径",无法做前置知识校验 |
### 3.3 多角色协作层
| 行业优秀实践 | 本模块现状 | 影响 |
|---|---|---|
| admin统一教材库 + 多教师协作编辑 + 版本历史 | 仅 teacher 单人编辑,无版本管理 | 多教师同改一本教材会互相覆盖,无回滚能力 |
| parent查看孩子教材进度、笔记 | 完全缺失 parent 角色 | parent 无法了解孩子学习内容 |
| student教材 + 笔记 + 作业联动 | 仅只读阅读 | 学生无法在教材上做标记、无法跳转到对应作业 |
| 教研组:教材模板复用、章节共享 | 无模板/共享机制 | 同学科同年级教材重复建设 |
### 3.4 交互体验层
| 行业优秀实践 | 本模块现状 | 影响 |
|---|---|---|
| 章节拖拽支持跨级移动 | `reorderChapters` 仅支持同级排序,跨级需先删后建 | 教材结构调整效率低 |
| 全文搜索(章节标题 + 正文 + 知识点) | 仅列表页按 title/subject/grade/publisher 模糊搜索 | 学生无法"在教材里搜概念" |
| 离线下载 / 移动端适配 | 阅读器布局在窄屏下三栏堆叠,未做移动端阅读优化 | 移动端体验差K12 学生主要用平板/手机 |
| 阅读进度条 / 章节完成度 | 无 | 无法量化学习进度 |
### 3.5 数据分析层
| 行业优秀实践 | 本模块现状 | 影响 |
|---|---|---|
| 教材使用统计(阅读时长、热门章节、知识点停留) | 无埋点 | 无法为教研提供数据支撑 |
| 知识点难度标注 / 教师标注重点 | 仅有 `level` 字段但无 UI 录入 | 无法做分层教学 |
---
## 四、改进优先级建议
### P0紧急阻塞多角色上线
1. **解耦跨模块 UI 依赖**:将 `KnowledgePointDialogs` 中对 `CreateQuestionDialog` 的直接 import 改为通过 props 注入render prop 或 children由页面层决定渲染哪个题目创建组件或定义 `QuestionCreator` 接口,由 questions 模块实现并通过 Context 注入。
2. **接入前端权限 Hook**:删除 `canEdit={true}` 硬编码,在 `TextbookReader` 内部调用 `usePermission().hasPermission(Permissions.TEXTBOOK_UPDATE)` 决定编辑按钮可见性;列表页"新增教材"按钮同理用 `TEXTBOOK_CREATE` 控制。
3. **全模块 i18n 改造**:新增 `shared/i18n/messages/{en,zh-CN}/textbooks.json` 命名空间提取所有硬编码文案Server Component 用 `getTranslations`Client Component 用 `useTranslations`;统一中英文混杂问题。
4. **Server Action 资源归属校验**:在 `updateChapterContentAction` / `deleteChapterAction` / `createKnowledgePointAction` 等 Action 内,先校验 `chapterId` 所属 `textbookId` 与传入 `textbookId` 一致,并结合当前用户身份做二次校验。
### P1重要影响正确性与可维护性
1. **data-access 加数据范围过滤**`getTextbooks` 接受 `scope` 参数(年级/班级/学科),学生端按学生年级过滤;`getTextbookById` 校验访问权。
2. **补齐 Error Boundary**:在 `teacher/textbooks/[id]``student/learning/textbooks/[id]` 下新增 `error.tsx``TextbookReader` 内对章节区、知识点区、图谱区分别用 Error Boundary 包裹。
3. **消除重复组件**:删除未使用的 `knowledge-point-panel.tsx``create-knowledge-point-dialog.tsx`;统一知识点列表与创建弹窗为单一实现。
4. **抽取学科/年级配置**:新建 `src/modules/textbooks/constants.ts`,集中导出 `SUBJECTS``GRADES``SUBJECT_COLORS`,供 filters/form/settings/card 复用,消除不一致。
5. **导出纯函数并补单测**:导出 `buildChapterTree` / `sortChapters` / `computeGraphLayout` / `buildChapterIndex`,补 Vitest 单测覆盖空数组、单节点、深层嵌套、循环引用等边界。
6. **修复类型断言**:用类型守卫替换 `!``as`,例如 `chapter.children!` 改为 `hasChildren ? <RecursiveSortableList items={chapter.children} /> : null`
7. **图谱 a11y**svg 加 `role="img"` + `aria-label`;节点加 `aria-label={node.name}`;支持方向键导航。
8. **统一删除确认**`textbook-settings-dialog.tsx``confirm()` 改为 `AlertDialog`,与模块其他删除一致。
### P2优化提升体验与专业度
1. **统一空状态**:内联空状态全部改用 `EmptyState` 组件,补 a11y。
2. **知识点高亮性能优化**:改用一次 AST 遍历(基于 remark 插件)替换正则全局替换,避免跨标签误伤。
3. **知识点懒加载**:详情页仅加载当前章节知识点,切换章节时按需加载。
4. **移动端阅读优化**:窄屏下三栏改为抽屉式(章节侧栏可滑出)。
5. **补全架构图同步**(见第五节)。
6. **埋点接口预留**:在 `data-access``actions` 中预留 `onTextbookView` / `onChapterRead` 钩子,供后续接入监控。
---
## 五、架构图同步说明
本次审计发现 [004_architecture_impact_map.md](file:///e:/Desktop/CICD/docs/architecture/004_architecture_impact_map.md) §2.5 与 [005_architecture_data.json](file:///e:/Desktop/CICD/docs/architecture/005_architecture_data.json) 中教材模块节点存在以下偏差,需同步修正:
### 5.1 行数统计过期
| 文件 | 图记行数 | 实际行数 |
|------|---------|---------|
| `actions.ts` | 276 | 317 |
| `data-access.ts` | 428 | 514 |
| `types.ts` | 79 | 45 |
| `hooks/use-knowledge-point-actions.ts` | 121 | 121一致 |
| 组件文件数 | 12 | 11 |
### 5.2 导出函数名错误
架构图 §2.5 记录的 Actions 列表含 `getTextbooksAction` / `getTextbookByIdAction` / `getChaptersAction` / `getKnowledgePointsAction`**实际不存在**。读操作直接由 RSC 页面调用 data-access`getTextbooks` / `getTextbookById` / `getChaptersByTextbookId` / `getKnowledgePointsByTextbookId` / `getKnowledgePointsByChapterId`),未包装成 Server Action。实际 Actions 为:
```
createTextbookAction / updateTextbookAction / deleteTextbookAction
createChapterAction / updateChapterContentAction / deleteChapterAction / reorderChaptersAction
createKnowledgePointAction / updateKnowledgePointAction / deleteKnowledgePointAction
```
### 5.3 未记录的跨模块 UI 依赖
架构图标注教材为"标杆模块(无跨模块 DB 访问)",这一结论对 data-access 层成立,但**组件层存在跨模块 UI 依赖**未记录:
- `textbooks/components/knowledge-point-dialogs.tsx``questions/components/create-question-dialog`
应在 004 的依赖关系图与 005 的 `dependencyMatrix` 中补充该 UI 层依赖,并标注为"待解耦P0"。
### 5.4 未记录的跨模块 data-access 调用方
`getKnowledgePointOptions`data-access 导出)被 questions 模块调用架构图已记录§2.4 questions 依赖 textbooks data-access但 005 JSON 中 textbooks 节点的 `exports` 字段未列出该函数。建议补充。
### 5.5 建议的 JSON 节点更新
`005_architecture_data.json``modules.textbooks` 节点建议补充/修正:
```jsonc
{
"textbooks": {
"exports": {
"actions": [
"createTextbookAction", "updateTextbookAction", "deleteTextbookAction",
"createChapterAction", "updateChapterContentAction", "deleteChapterAction",
"reorderChaptersAction",
"createKnowledgePointAction", "updateKnowledgePointAction", "deleteKnowledgePointAction"
],
"dataAccess": [
"getTextbooks", "getTextbookById", "getChaptersByTextbookId",
"getKnowledgePointsByChapterId", "getKnowledgePointsByTextbookId",
"createTextbook", "updateTextbook", "deleteTextbook",
"createChapter", "updateChapterContent", "deleteChapter",
"createKnowledgePoint", "updateKnowledgePoint", "deleteKnowledgePoint",
"reorderChapters", "getTextbooksDashboardStats",
"getKnowledgePointOptions" // 跨模块接口,供 questions 使用
]
},
"uiDeps": [
"questions/components/create-question-dialog // P0 待解耦"
],
"files": {
"actions.ts": 317,
"data-access.ts": 514,
"types.ts": 45,
"schema.ts": 64,
"components": 11
},
"knownIssues": [
"跨模块 UI 依赖 CreateQuestionDialogP0",
"前端权限硬编码 canEditP0",
"全模块零 i18nP0",
"Server Action 未校验资源归属P1",
"data-access 缺数据范围过滤P1",
"缺 Error BoundaryP1",
"知识点列表/弹窗重复实现P1",
"学科/年级选项硬编码且不一致P1",
"纯逻辑未导出零单测P1"
]
}
}
```
---
## 附:重构方案设计要点(不写实现代码)
为满足"完全解耦 / 组合优先 / 国际化就绪 / 最大化复用 / 错误与边界处理 / 可测试性 / 可扩展性 / 企业级补充"八项原则,建议按以下方向重构(详细实现留待后续任务):
### A. 数据服务接口抽象
```ts
// textbooks/services/types.ts
export interface TextbookDataService {
listTextbooks(query?: TextbookQuery): Promise<Textbook[]>
getTextbook(id: string): Promise<Textbook | null>
listChapters(textbookId: string): Promise<Chapter[]>
listKnowledgePoints(textbookId: string): Promise<KnowledgePoint[]>
}
export interface TextbookMutationService {
createTextbook(input: CreateTextbookInput): Promise<ActionState>
updateTextbook(id: string, input: UpdateTextbookInput): Promise<ActionState>
deleteTextbook(id: string): Promise<ActionState>
// ...chapter / knowledgePoint mutations
}
```
通过 `TextbookDataProvider`React Context注入不同角色实现teacher 实现 = 全量 + 可写student 实现 = 按年级过滤 + 只读admin 实现 = 全量 + 可写 + 可分配。
### B. 配置驱动角色渲染
```ts
// textbooks/config/role-config.ts
export const TEXTBOOK_ROLE_CONFIG: Record<Role, TextbookRoleConfig> = {
teacher: { canEdit: true, showStats: true, widgets: ['chapters','knowledge','graph','settings'] },
student: { canEdit: false, showProgress: true, widgets: ['chapters','knowledge','graph','notes'] },
admin: { canEdit: true, showStats: true, showAudit: true, widgets: ['chapters','knowledge','graph','settings','audit'] },
parent: { canEdit: false, showChildProgress: true, widgets: ['chapters','progress'] },
}
```
`TextbookReader` 根据 `useRoleConfig()` 决定渲染哪些 Widget新增角色只改配置。
### C. 组合式 UI
- `TextbookReader` 改为 `children`-based 组合:`<TextbookReader><ChapterSidebar /><ContentPanel /><KnowledgePanel /></TextbookReader>`
- 跨模块的"创建题目"入口改为 render prop`<KnowledgePointList onCreateQuestion={renderQuestionCreator} />`,由页面层注入 questions 模块组件,模块内部不 import questions。
### D. i18n 翻译文件结构示例
```
shared/i18n/messages/
├─ en/textbooks.json
└─ zh-CN/textbooks.json
```
```jsonc
// zh-CN/textbooks.json
{
"list": {
"title": "教材",
"subtitle": "管理数字课程资源与章节",
"add": "新建教材",
"empty": { "withFilters": "没有匹配的教材", "withoutFilters": "暂无教材" }
},
"reader": {
"tabs": { "chapters": "章节目录", "knowledge": "知识点", "graph": "图谱" },
"selectChapter": "请选择一个章节开始阅读",
"emptyKnowledge": "该章节暂无知识点"
},
"dialog": {
"create": { "title": "新建教材", "submit": "保存" },
"settings": { "title": "教材设置", "delete": "删除教材" },
"knowledge": { "create": "添加知识点", "edit": "编辑知识点" }
},
"field": {
"title": "标题", "subject": "学科", "grade": "年级", "publisher": "出版社"
},
"subject": { "Mathematics": "数学", "Physics": "物理", /* ... */ },
"grade": { "Grade 7": "七年级", /* ... */ }
}
```
### E. 错误边界与骨架屏
- 每个独立数据区块(章节树、内容区、知识点区、图谱区)用 `<ErrorBoundary fallback={<ErrorState />}>` 包裹
- 异步加载用 `<Suspense fallback={<TextbookReaderSkeleton />}>`
- 空状态、无权限、网络异常统一用 `EmptyState` / `ForbiddenState` / `ErrorState` 三套标准组件
### F. 可测试性
- 纯逻辑(`buildChapterTree` / `computeGraphLayout` / `sortChapters` / `buildChapterIndex` / `processedContent` 生成器)抽到 `textbooks/utils/` 并导出
- 数据服务接口便于 mock组件测试时注入 stub service
- 补 Vitest 单测 + Playwright e2e列表筛选、章节拖拽、知识点创建三条核心路径
### G. 监控埋点接口
```ts
export interface TextbookAnalytics {
onTextbookOpen(textbookId: string): void
onChapterRead(textbookId: string, chapterId: string, durationMs: number): void
onKnowledgePointClick(kpId: string): void
}
```
通过 Context 注入,默认 no-op后续接入真实监控 SDK。