将教材模块图谱从基本无用状态升级为完整知识图谱可视化系统。 数据层:新增 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 一致性。
13 KiB
13 KiB
知识图谱重构设计文档
- 日期:2026-06-22
- 模块:textbooks
- 范围:知识图谱功能全面重构
- 状态:已批准,待实现
1. 背景与动机
1.1 当前问题
教材模块的知识图谱功能(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. 设计目标
- 跨章节全书视图:支持单章节和全书两种范围切换
- 前置依赖关系:新增数据模型,支持声明任意知识点间的前置依赖
- 掌握度可视化:学生看个人,教师看班级,红/黄/绿/灰着色
- 关联题目预览:节点显示关联题目数,详情面板可跳转题目库
- 缩放平移交互:React Flow 内置画布交互
- 师生双视角:同一组件,通过 prop 注入不同数据源
- 侧边栏详情面板:点击节点显示详情,不离开当前页面
3. 数据模型扩展
3.1 新增表:knowledge_point_prerequisites
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)
// 全书知识点 + 前置依赖 + 关联题目数,一次查询聚合
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 Actions(actions.ts)
// 图谱数据懒加载入口
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(教师看班级整体,不看个人)
- 学生角色:仅显示
- 章节筛选(多选下拉,默认全选)
- 关键词搜索(高亮匹配节点,非匹配节点降低透明度)
- 重置视图按钮
- 视图模式切换(structure / student-mastery / 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 新增键
{
"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)
以下功能不在本次范围内,后续迭代考虑:
- 节点位置持久化(拖拽后保存布局)
- 学习路径自动推荐
- 关联视频/课件资源(当前仅关联题目)
- 教材阅读进度跟踪
- 学生笔记/标注
- 教材导入/导出
- 多版本教材对比