feat(textbooks): 知识图谱功能全面重构 — 前置依赖 + dagre 布局 + React Flow 可视化 + 师生双视角

将教材模块图谱从基本无用状态升级为完整知识图谱可视化系统。

数据层:新增 knowledgePointPrerequisites 表(复合主键+双外键 cascade);新增 data-access-graph.ts(server-only)知识点关联聚合、学生/班级掌握度查询;utils.ts 新增 hasCycleAfterAddingEdge(DFS 循环依赖检测)。

业务层:3 个新 Server Action(getKnowledgeGraphDataAction 三视图模式、createPrerequisiteAction 含循环检测、deletePrerequisiteAction);graph-layout.ts 重写为 dagre 分层有向图布局。

视图层:knowledge-graph.tsx 重写为 React Flow 主组件(全书视图+搜索高亮+关联节点高亮+章节着色);4 个新组件(graph-kp-node/graph-prerequisite-edge/graph-toolbar/graph-node-detail-panel);use-graph-data.ts 派生值模式避免 effect 中 setState。

架构:严格三层架构,客户端通过 Server Action 间接访问 server-only 数据层;权限校验+ i18n 全覆盖;架构文档 004/005 同步。

测试:utils.test.ts 新增 5 个循环检测测试,graph-layout.test.ts 重写 5 个 dagre 布局测试,全部 30 个教材模块单元测试通过。

附带提交 drizzle/0005 error-book 迁移文件以保持 journal 一致性。
This commit is contained in:
SpecialX
2026-06-23 00:13:03 +08:00
parent 15aa84b72c
commit 58656da983
28 changed files with 21377 additions and 575 deletions

View File

@@ -0,0 +1,338 @@
# 知识图谱重构设计文档
- **日期**2026-06-22
- **模块**textbooks
- **范围**:知识图谱功能全面重构
- **状态**:已批准,待实现
## 1. 背景与动机
### 1.1 当前问题
教材模块的知识图谱功能([knowledge-graph.tsx](file:///e:/Desktop/CICD/src/modules/textbooks/components/knowledge-graph.tsx))处于"基本无用"状态:
- **静态 SVG 树状布局**:仅 `parentId` 父子关系,无前置依赖
- **仅显示当前章节**:无法跨章节/全书查看知识体系
- **交互单一**:点击节点仅高亮正文,无缩放/平移/拖拽/键盘导航
- **信息密度低**:节点只有名称,无关联题目数、无掌握度
- **无师生区分**:教师和学生看到相同的图,无学情数据
### 1.2 同类平台调研
| 平台 | 核心设计 | 可借鉴点 |
|------|---------|---------|
| 人教数字教材 | 按学科/章节聚合的层级图,节点关联资源 | 跨章节聚合 + 资源关联 |
| Khan Academy | 力导向图,前置依赖边,节点显示掌握度 | 前置依赖 + 掌握度 + 跳转练习 |
| 学而思/猿辅导 | 学习路径图,红/黄/绿表示掌握度 | 掌握度色彩 + 路径推荐 |
| ClassIn/Seewo | 师生双视角,教师看班级整体 | 师生双视角 |
| 洋葱学院 | 章节→知识点→题目三级下钻 | 下钻交互 + 缩放 |
**共性特征**:① 跨章节/全书视图 ② 前置依赖关系 ③ 掌握度可视化 ④ 关联题目/资源 ⑤ 缩放平移 ⑥ 师生双视角
### 1.3 已有可复用数据
- `questionsToKnowledgePoints` 关联表(题目↔知识点多对多)
- `knowledgePointMastery` 表(学生掌握度,已被 `diagnostic` 模块填充)
- `questions` 模块已支持按 `knowledgePointId` 筛选
- `@xyflow/react`React Flow 12已在 `lesson-preparation` 模块使用
## 2. 设计目标
1. **跨章节全书视图**:支持单章节和全书两种范围切换
2. **前置依赖关系**:新增数据模型,支持声明任意知识点间的前置依赖
3. **掌握度可视化**:学生看个人,教师看班级,红/黄/绿/灰着色
4. **关联题目预览**:节点显示关联题目数,详情面板可跳转题目库
5. **缩放平移交互**React Flow 内置画布交互
6. **师生双视角**:同一组件,通过 prop 注入不同数据源
7. **侧边栏详情面板**:点击节点显示详情,不离开当前页面
## 3. 数据模型扩展
### 3.1 新增表knowledge_point_prerequisites
```typescript
export const knowledgePointPrerequisites = mysqlTable("knowledge_point_prerequisites", {
id: id("id").primaryKey(),
knowledgePointId: varchar("knowledge_point_id", { length: 128 }).notNull()
.references(() => knowledgePoints.id, { onDelete: "cascade" }),
prerequisiteKpId: varchar("prerequisite_kp_id", { length: 128 }).notNull()
.references(() => knowledgePoints.id, { onDelete: "cascade" }),
createdAt: timestamp("created_at").defaultNow().notNull(),
}, (table) => ({
kpPairPk: primaryKey({ columns: [table.knowledgePointId, table.prerequisiteKpId] }),
kpIdx: index("kp_prereq_kp_idx").on(table.knowledgePointId),
prereqIdx: index("kp_prereq_prereq_idx").on(table.prerequisiteKpId),
}))
```
**设计说明**
- 多对多自关联表,表达"学习 KP_B 前应先掌握 KP_A"
- `knowledgePointId` = 目标知识点,`prerequisiteKpId` = 前置知识点
- 联合主键防止重复声明
- 级联删除:知识点删除时自动清理关联
- 循环依赖检测由 Server Action 层 DFS 校验,拒绝形成环的声明
### 3.2 不修改的表
- `knowledgePoints`:已有 `parentId`(树归属)和 `chapterId`(章节归属),不变
- `knowledgePointMastery`:已有 `masteryLevel`/`totalQuestions`/`correctQuestions`,不变
- `questionsToKnowledgePoints`:不变
## 4. 架构与模块结构
### 4.1 新增文件清单
```
src/modules/textbooks/
├─ data-access-graph.ts # 新增:图谱专用数据访问
├─ components/
│ ├─ knowledge-graph.tsx # 重写React Flow 渲染器
│ ├─ graph-node-detail-panel.tsx # 新增:节点详情侧边栏
│ ├─ graph-kp-node.tsx # 新增React Flow 自定义节点
│ ├─ graph-prerequisite-edge.tsx # 新增React Flow 自定义边
│ └─ graph-toolbar.tsx # 新增:视图切换/筛选/搜索工具栏
└─ hooks/
└─ use-graph-data.ts # 新增:图谱数据加载与缓存 Hook
```
### 4.2 修改的文件
| 文件 | 修改内容 |
|------|---------|
| `src/shared/db/schema.ts` | 新增 `knowledgePointPrerequisites` 表定义 |
| `data-access.ts` | 新增 prerequisite CRUD 函数 |
| `actions.ts` | 新增 3 个 Server Action |
| `schema.ts` | 新增 prerequisite 声明的 Zod 校验 |
| `types.ts` | 新增 `GraphNodeData` / `GraphViewMode` / `KpWithRelations` 等类型 |
| `graph-layout.ts` | 重写:调用 dagre保留纯函数签名 |
| `components/textbook-reader.tsx` | 图谱 Tab 接入新组件 |
| `i18n/messages/zh-CN/textbooks.json` | 新增 graph.* 翻译键 |
| `i18n/messages/en/textbooks.json` | 新增 graph.* 翻译键 |
### 4.3 数据访问层data-access-graph.ts
```typescript
// 全书知识点 + 前置依赖 + 关联题目数,一次查询聚合
export async function getKnowledgePointsWithRelations(
textbookId: string
): Promise<KpWithRelations[]>
// 学生个人掌握度(按教材范围)
export async function getStudentKpMastery(
studentId: string,
textbookId: string
): Promise<Map<string, MasteryInfo>>
// 班级平均掌握度(教师视角)
export async function getClassKpMastery(
teacherId: string,
textbookId: string
): Promise<Map<string, MasteryInfo>>
// 单个知识点的前置列表
export async function getPrerequisitesForKp(
kpId: string
): Promise<KnowledgePoint[]>
```
**性能考量**
- 全书知识点通常 50-300 个,一次查询无压力
- 关联题目数用子查询 `COUNT` 聚合,避免 N+1
- 掌握度查询走索引(`mastery_kp_idx` + `mastery_student_idx`
### 4.4 Server Actionsactions.ts
```typescript
// 图谱数据懒加载入口
export async function getKnowledgeGraphDataAction(
textbookId: string,
viewMode: GraphViewMode
): Promise<ActionState<KnowledgeGraphData>>
// 声明前置依赖(含循环检测)
export async function createPrerequisiteAction(
input: CreatePrerequisiteInput
): Promise<ActionState<void>>
// 删除前置依赖
export async function deletePrerequisiteAction(
input: DeletePrerequisiteInput
): Promise<ActionState<void>>
```
**权限**
- `getKnowledgeGraphDataAction``requirePermission(Permissions.TEXTBOOK_READ)`,掌握度数据按当前用户角色过滤
- `createPrerequisiteAction` / `deletePrerequisiteAction``requirePermission(Permissions.TEXTBOOK_UPDATE)`
**循环检测**`createPrerequisiteAction` 中做 DFS若声明 `A→B` 后从 B 可达 A 则拒绝。
## 5. 图谱视图与交互
### 5.1 视图模式
| 模式 | 数据源 | 节点着色 | 适用角色 |
|------|--------|---------|---------|
| `structure` | 全书知识点 + parentId + prerequisites | 按章节分色 | 教师/学生 |
| `student-mastery` | + 学生个人掌握度 | 红(<60%)/黄(60-85%)/绿(>85%)/灰(未测) | 学生 |
| `class-mastery` | + 班级平均掌握度 | 同上但聚合班级数据 | 教师 |
### 5.2 节点设计graph-kp-node.tsx
- 矩形卡片,宽度 180px高度自适应
- 内容:知识点名称 + 关联题目数徽章 + 掌握度进度条mastery 模式下)
- 双击节点 → 打开右侧详情面板
- 单击节点 → 高亮关联节点(前置+后置),其余节点降低透明度
- 节点支持拖拽(位置不持久化,切换章节后重新布局)
### 5.3 边设计
- `parentId` 关系:实线,无箭头(树归属)
- `prerequisite` 关系:虚线 + 箭头(依赖方向)
- 选中节点时:关联边高亮,其余边降低透明度
### 5.4 画布交互
- React Flow 内置缩放滚轮、平移拖拽空白、小地图、键盘导航Tab/方向键)
- 工具栏(`graph-toolbar.tsx`
- 视图模式切换structure / student-mastery / class-mastery
- 学生角色:仅显示 `structure` + `student-mastery`
- 教师角色:显示 `structure` + `class-mastery`(教师看班级整体,不看个人)
- 章节筛选(多选下拉,默认全选)
- 关键词搜索(高亮匹配节点,非匹配节点降低透明度)
- 重置视图按钮
### 5.5 详情面板graph-node-detail-panel.tsx
- 知识点描述
- 掌握度详情(个人/班级,含答题数/正确率)
- 关联题目列表(前 5 条 + "查看全部"跳转题目库,带 `?kp=<id>` 查询参数)
- 前置知识点列表(可点击跳转)
- 后置知识点列表(可点击跳转)
- 教师/有权限者:编辑前置依赖入口(添加/删除前置)
## 6. 数据流与性能
### 6.1 数据加载策略
- 图谱数据按教材维度一次性加载(全书知识点 + 依赖 + 题目数聚合)
- 掌握度数据按 viewMode 懒加载:切换到 mastery 模式时才请求
- 使用 Server Action `getKnowledgeGraphDataAction(textbookId, viewMode)` 统一入口
- 客户端缓存:`use-graph-data.ts``useState` + `useRef` 防重复请求(复用 P2-3 模式)
### 6.2 布局计算
- dagre 布局在客户端 `useMemo` 中执行
- 知识点量级50-300下 dagre 计算时间 <10ms
- 布局参数:`rankdir=TB`(从上到下)、`nodesep=40``ranksep=90`
### 6.3 错误处理
- 图谱数据加载失败 → 复用 `TextbookSectionErrorBoundary`
- 掌握度数据缺失 → 节点显示"未测评"灰色状态,不阻断图谱渲染
- 前置依赖循环检测 → Server Action 返回结构化错误,前端 toast 提示
## 7. 权限与 i18n
### 7.1 权限
- `TEXTBOOK_READ` — 查看图谱
- `TEXTBOOK_UPDATE` — 编辑前置依赖
- 掌握度查看:学生只能看自己,教师看班级(由 data-access 层按 `getCurrentStudentUser`/`getCurrentTeacherUser` 过滤)
### 7.2 i18n 新增键
```json
{
"graph": {
"viewMode": {
"structure": "结构图",
"studentMastery": "个人掌握度",
"classMastery": "班级掌握度"
},
"node": {
"questions": "题目",
"mastery": "掌握度",
"prerequisite": "前置",
"successor": "后置"
},
"detail": {
"title": "知识点详情",
"noDescription": "暂无描述",
"viewAllQuestions": "查看全部题目",
"editPrerequisite": "编辑前置依赖",
"addPrerequisite": "添加前置",
"removePrerequisite": "移除",
"noPrerequisites": "暂无前置知识点",
"noSuccessors": "暂无后置知识点",
"masteryNotAssessed": "未测评",
"correctRate": "正确率"
},
"toolbar": {
"search": "搜索知识点",
"filterByChapter": "按章节筛选",
"resetView": "重置视图"
},
"empty": {
"noPrerequisites": "暂无前置依赖关系",
"noData": "暂无图谱数据"
},
"error": {
"cyclicDependency": "不能添加循环依赖",
"loadFailed": "图谱加载失败"
}
}
}
```
## 8. 测试策略
### 8.1 单元测试
- `graph-layout.ts`dagre 集成后布局正确性、空数据、循环容错
- `data-access-graph.ts`:聚合查询正确性、权限过滤
- 循环依赖检测DFS 算法单测
### 8.2 组件测试
- `graph-kp-node.tsx`:节点渲染、掌握度进度条、徽章
- `graph-node-detail-panel.tsx`:详情展示、前置/后置列表、跳转链接
- `graph-toolbar.tsx`:视图切换、搜索、筛选
### 8.3 集成测试
- 图谱数据加载 → 渲染 → 节点点击 → 详情面板
- 视图模式切换 → 掌握度数据懒加载
- 前置依赖 CRUD → 图谱边更新
## 9. 依赖变更
### 9.1 新增依赖
- `@dagrejs/dagre` — 分层有向图布局算法(~50KB gzip
### 9.2 复用依赖
- `@xyflow/react`(已在项目中使用)
## 10. 架构图同步
实现完成后需同步以下架构文档:
- `docs/architecture/004_architecture_impact_map.md` — §2.5 教材模块章节
- 更新文件清单(新增 6 个文件)
- 更新导出函数(新增 4 个 data-access + 3 个 actions
- 更新 knownIssues移除"P2 图谱方向键导航未实现"
- `docs/architecture/005_architecture_data.json` — modules.textbooks 节点
- 更新 exports、dbTables新增 knowledge_point_prerequisites、dependencyMatrix
## 11. 非目标YAGNI
以下功能不在本次范围内,后续迭代考虑:
- 节点位置持久化(拖拽后保存布局)
- 学习路径自动推荐
- 关联视频/课件资源(当前仅关联题目)
- 教材阅读进度跟踪
- 学生笔记/标注
- 教材导入/导出
- 多版本教材对比