docs: update architecture docs, audit reports, and bug tracking
- Update architecture impact map, data, feature checklist, gap audit - Add audit reports for dashboard, exam-homework, grades-diagnostic, settings-profile, textbooks - Update bug reports (admin, teacher, lesson-preparation, others, shared) - Update coding standards, DR plan, design docs, and README
This commit is contained in:
296
bugs/lesson_preparation_bug_v3.md
Normal file
296
bugs/lesson_preparation_bug_v3.md
Normal file
@@ -0,0 +1,296 @@
|
||||
# 备课模块(lesson-preparation)审查报告 v3
|
||||
|
||||
> 审查日期:2026-06-22
|
||||
> 审查范围:`src/modules/lesson-preparation/` 全部 34 个文件 + 3 个路由页面
|
||||
> 审查方式:代码审查 + Playwright 运行时测试
|
||||
> 前置状态:v2 已完成节点图编辑器重构(React Flow)+ P1 问题修复
|
||||
|
||||
---
|
||||
|
||||
## 一、审查结论
|
||||
|
||||
| 维度 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| 编辑器可用性 | ✅ | 节点图渲染、选中、添加、编辑、保存均正常 |
|
||||
| 功能完整性 | ⚠️ | 存在 5 个 P1 功能缺陷 + 2 个 P2 规范问题 |
|
||||
| 代码质量 | ⚠️ | 存在 6 个 P2 代码规范违规 |
|
||||
| 用户体验 | ⚠️ | 存在 4 个 P3 改进项 |
|
||||
| 架构合规 | ✅ | 三层架构正确,权限校验完整 |
|
||||
| 运行时稳定性 | ✅ | Playwright 测试无控制台错误 |
|
||||
|
||||
---
|
||||
|
||||
## 二、运行时测试结果(Playwright)
|
||||
|
||||
| 测试项 | 结果 | 说明 |
|
||||
|--------|------|------|
|
||||
| 登录 | ✅ | 正常跳转 dashboard |
|
||||
| 新建课案 | ✅ | 模板选择 → 创建 → 跳转编辑页 |
|
||||
| 节点渲染 | ✅ | 8 节点 + 7 边正确渲染 |
|
||||
| 节点选中 | ✅ | 点击节点 → 侧边面板显示 |
|
||||
| 标题编辑 | ✅ | 侧边面板输入框可编辑 |
|
||||
| 添加节点 | ✅ | 8 → 9 节点 |
|
||||
| 连线 Handle | ✅ | 18 个 handle(9 节点 × 2) |
|
||||
| 版本抽屉 | ✅ | 打开/关闭正常,显示"暂无版本" |
|
||||
| 保存版本 | ✅ | 点击后无错误 |
|
||||
| 控制台错误 | ✅ | 无 error/warning |
|
||||
|
||||
---
|
||||
|
||||
## 三、P1 功能缺陷
|
||||
|
||||
### [P1-1] 节点拖拽位置不持久化(position 变化未触发自动保存)
|
||||
|
||||
**文件**:[node-editor.tsx:59-74](file:///e:/Desktop/CICD/src/modules/lesson-preparation/components/node-editor.tsx#L59-L74)
|
||||
|
||||
**现象**:拖拽节点改变位置后,3 秒自动保存未触发,刷新页面位置丢失。
|
||||
|
||||
**原因**:`onNodesChange` 中 `updateNodePosition` 调用了 `set({ isDirty: true })`,但 `lesson-plan-editor.tsx:71` 的自动保存 effect 依赖 `[editor.isDirty, editor.doc, planId]`。`editor.doc` 是 zustand 的订阅值,但 `updateNodePosition` 每次都创建新的 doc 对象,导致 effect 频繁触发。然而拖拽过程中会触发多次 position 变化,debounce 3s 应该能生效。
|
||||
|
||||
**实际根因**:React Flow 拖拽时 `change.position` 可能是中间状态(dragging: true),最终位置在 dragging: false 时才确定。当前代码未区分 dragging 状态,每次都写入 store,但最终位置是正确的。问题在于 `editor.doc` 引用变化太快,debounce timer 不断重置,如果用户持续拖拽超过 3s 仍未保存。
|
||||
|
||||
**修复建议**:在 `onNodesChange` 中检查 `change.dragging === false` 才写入最终位置,避免中间状态污染。
|
||||
|
||||
---
|
||||
|
||||
### [P1-2] 侧边面板关闭后无法重新打开
|
||||
|
||||
**文件**:[lesson-plan-editor.tsx:65-68](file:///e:/Desktop/CICD/src/modules/lesson-preparation/components/lesson-plan-editor.tsx#L65-L68)
|
||||
|
||||
**现象**:用户点击节点选中 → 侧边面板打开 → 点击面板关闭按钮 → 再次点击同一节点,面板不会重新打开。
|
||||
|
||||
**原因**:
|
||||
```tsx
|
||||
useEffect(() => {
|
||||
if (editor.selectedNodeId) setPanelOpen(true);
|
||||
}, [editor.selectedNodeId]);
|
||||
```
|
||||
点击关闭按钮调用 `selectNode(null)`,`selectedNodeId` 变为 null,`panelOpen` 仍为 true。再次点击同一节点时,`selectedNodeId` 从 null 变为该节点 id,effect 触发 `setPanelOpen(true)`,但 `panelOpen` 已经是 true,React 不会重新渲染。
|
||||
|
||||
实际问题是:关闭按钮只调用 `selectNode(null)` 但没有 `setPanelOpen(false)`,导致面板在 `selectedNodeId` 为 null 时仍然显示(因为 `panelOpen && selectedNodeId` 条件中 panelOpen 为 true 但 selectedNodeId 为 null,条件为 false,面板隐藏)。再次点击节点时 selectedNodeId 变化,effect 触发 setPanelOpen(true),但已经是 true。
|
||||
|
||||
**实际根因**:关闭面板后 `panelOpen` 仍为 true,但 `selectedNodeId` 为 null,条件 `panelOpen && selectedNodeId` 为 false。再次点击节点时 `selectedNodeId` 变化,effect 触发 `setPanelOpen(true)`(已是 true),面板应该显示。但 `NodeEditPanel` 内部 `node` 查找依赖 `selectedNodeId`,如果找到了节点应该显示。
|
||||
|
||||
**验证**:需要实际测试确认。如果确实无法重新打开,可能是 `panelOpen` 状态管理问题。
|
||||
|
||||
**修复建议**:移除 `panelOpen` 状态,直接用 `selectedNodeId !== null` 控制面板显示。
|
||||
|
||||
---
|
||||
|
||||
### [P1-3] inline-question-editor 知识点标注缺失(v2 遗留)
|
||||
|
||||
**文件**:[inline-question-editor.tsx:22](file:///e:/Desktop/CICD/src/modules/lesson-preparation/components/inline-question-editor.tsx#L22)
|
||||
|
||||
**现象**:课案内新建题目无法关联知识点。
|
||||
|
||||
**原因**:`kpIds` 被硬编码为常量空数组:
|
||||
```tsx
|
||||
const kpIds: string[] = [];
|
||||
```
|
||||
|
||||
**修复建议**:添加知识点选择器 UI,或复用 `KnowledgePointPicker`。
|
||||
|
||||
---
|
||||
|
||||
### [P1-4] exercise-block 用 index 作为 key(v2 遗留)
|
||||
|
||||
**文件**:[exercise-block.tsx:67](file:///e:/Desktop/CICD/src/modules/lesson-preparation/components/blocks/exercise-block.tsx#L67)
|
||||
|
||||
```tsx
|
||||
{data.items.map((item, idx) => (
|
||||
<div key={idx} ...>
|
||||
```
|
||||
|
||||
**问题**:删除/排序时可能导致 React 状态错乱。
|
||||
|
||||
**修复建议**:用 `item.questionId` 作为 key。
|
||||
|
||||
---
|
||||
|
||||
### [P1-5] lesson-plan-card 用 window.location.reload()(v2 遗留)
|
||||
|
||||
**文件**:[lesson-plan-card.tsx:39,50](file:///e:/Desktop/CICD/src/modules/lesson-preparation/components/lesson-plan-card.tsx#L39)
|
||||
|
||||
**问题**:不符合 SPA 模式,导致整个页面重新加载。
|
||||
|
||||
**修复建议**:用 `useRouter().refresh()`。
|
||||
|
||||
---
|
||||
|
||||
## 四、P2 代码规范问题
|
||||
|
||||
### [P2-1] node-editor 用 `as unknown as Record<string, unknown>` 类型断言
|
||||
|
||||
**文件**:[node-editor.tsx:42](file:///e:/Desktop/CICD/src/modules/lesson-preparation/components/node-editor.tsx#L42)
|
||||
|
||||
```tsx
|
||||
data: n as unknown as Record<string, unknown>,
|
||||
```
|
||||
|
||||
**问题**:双重断言绕过类型检查,违反"禁止 as 断言"规范。
|
||||
|
||||
**建议**:React Flow 的 `Node` 类型要求 `data` 为 `Record<string, unknown>`,可以构造一个符合类型的对象。
|
||||
|
||||
---
|
||||
|
||||
### [P2-2] node-editor 隐藏 span 传递 props(hack)
|
||||
|
||||
**文件**:[node-editor.tsx:152](file:///e:/Desktop/CICD/src/modules/lesson-preparation/components/node-editor.tsx#L152)
|
||||
|
||||
```tsx
|
||||
<span className="hidden" data-textbook={textbookId} data-chapter={chapterId} data-classes={classes?.length} />
|
||||
```
|
||||
|
||||
**问题**:用隐藏 DOM 元素避免 unused 警告,是 hack 做法。
|
||||
|
||||
**建议**:`textbookId`/`chapterId`/`classes` 是 NodeEditor 的 props 但未使用(实际由 NodeEditPanel 使用)。应移除这些 props,或让 NodeEditor 不接收它们。
|
||||
|
||||
---
|
||||
|
||||
### [P2-3] publish-service 用 JSON.parse(JSON.stringify()) 深拷贝(v2 遗留)
|
||||
|
||||
**文件**:[publish-service.ts:83-85](file:///e:/Desktop/CICD/src/modules/lesson-preparation/publish-service.ts#L83-L85)
|
||||
|
||||
**问题**:性能差,且不支持 Date 等特殊类型。
|
||||
|
||||
**建议**:用 `structuredClone()`。
|
||||
|
||||
---
|
||||
|
||||
### [P2-4] exercise-block 用 `as never` 类型断言(v2 遗留)
|
||||
|
||||
**文件**:[exercise-block.tsx:52](file:///e:/Desktop/CICD/src/modules/lesson-preparation/components/blocks/exercise-block.tsx#L52)
|
||||
|
||||
```tsx
|
||||
update({ purpose: e.target.value as never })
|
||||
```
|
||||
|
||||
**建议**:用 `as ExercisePurpose` 并添加类型守卫。
|
||||
|
||||
---
|
||||
|
||||
### [P2-5] 多个组件用 alert()/confirm()(v2 遗留)
|
||||
|
||||
**文件**:
|
||||
- [version-history-drawer.tsx:46](file:///e:/Desktop/CICD/src/modules/lesson-preparation/components/version-history-drawer.tsx#L46)
|
||||
- [lesson-plan-card.tsx:48](file:///e:/Desktop/CICD/src/modules/lesson-preparation/components/lesson-plan-card.tsx#L48)
|
||||
- [inline-question-editor.tsx:26](file:///e:/Desktop/CICD/src/modules/lesson-preparation/components/inline-question-editor.tsx#L26)
|
||||
- [text-study-block.tsx:42](file:///e:/Desktop/CICD/src/modules/lesson-preparation/components/blocks/text-study-block.tsx#L42)
|
||||
|
||||
**问题**:阻塞主线程,不符合现代 Web UI 规范。
|
||||
|
||||
**建议**:使用 `AlertDialog` 组件或 `sonner` toast。
|
||||
|
||||
---
|
||||
|
||||
### [P2-6] text-study-block 选区计算错误
|
||||
|
||||
**文件**:[text-study-block.tsx:29-37](file:///e:/Desktop/CICD/src/modules/lesson-preparation/components/blocks/text-study-block.tsx#L29-L37)
|
||||
|
||||
**问题**:`range.startOffset`/`range.endOffset` 是相对于当前 DOM 节点的偏移,不是相对于 `sourceText` 的字符偏移。如果 textarea 内有换行或子节点,偏移会不正确。
|
||||
|
||||
**建议**:用 `textarea.selectionStart`/`textarea.selectionEnd` 获取相对于文本的偏移。
|
||||
|
||||
---
|
||||
|
||||
## 五、P3 用户体验改进
|
||||
|
||||
### [P3-1] 节点画布无空状态提示
|
||||
|
||||
**问题**:空白课案(无节点)时画布只显示网格,无引导提示。
|
||||
|
||||
**建议**:当 `doc.nodes.length === 0` 时显示"点击左下角添加节点开始"提示。
|
||||
|
||||
---
|
||||
|
||||
### [P3-2] 版本抽屉无预览功能(v2 遗留)
|
||||
|
||||
**问题**:版本列表只显示版本号和标签,无法预览版本内容差异。
|
||||
|
||||
**建议**:点击版本时展开内容预览。
|
||||
|
||||
---
|
||||
|
||||
### [P3-3] 编辑器无 loading 骨架屏(v2 遗留)
|
||||
|
||||
**问题**:编辑器初始化时无加载状态。
|
||||
|
||||
**建议**:添加 Suspense fallback。
|
||||
|
||||
---
|
||||
|
||||
### [P3-4] 列表页英文标题与中文 UI 不一致
|
||||
|
||||
**文件**:[page.tsx:24-25](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/lesson-plans/page.tsx#L24-L25)
|
||||
|
||||
```tsx
|
||||
<h1>My Lesson Plans</h1>
|
||||
<p>Manage your lesson preparation and teaching plans.</p>
|
||||
```
|
||||
|
||||
**问题**:项目其他页面用中文,此处用英文。
|
||||
|
||||
**建议**:改为"我的备课"和"管理备课和教学计划"。
|
||||
|
||||
---
|
||||
|
||||
## 六、架构合规性检查
|
||||
|
||||
| 检查项 | 状态 | 说明 |
|
||||
|--------|------|------|
|
||||
| 三层架构(app→modules→shared) | ✅ | 路由层只调用 actions 和 data-access |
|
||||
| 模块间通过 data-access 通信 | ✅ | publish-service 通过 questions/exams/homework 的 data-access |
|
||||
| Server Action 权限校验 | ✅ | 所有 action 调用 requirePermission |
|
||||
| Zod 校验 | ✅ | actions 使用 schema 校验输入 |
|
||||
| ActionState 返回类型 | ✅ | 统一使用 ActionState<T> |
|
||||
| "server-only" 标注 | ✅ | 所有 data-access 文件有 "server-only" |
|
||||
| "use client" 标注 | ✅ | 所有客户端组件有 "use client" |
|
||||
| revalidatePath 精确刷新 | ✅ | 创建/删除/回退后调用 revalidatePath |
|
||||
| 架构图同步 | ✅ | 004/005 已同步 v2 节点图结构 |
|
||||
| 数据结构向后兼容 | ✅ | normalizeDocument 自动迁移 v1→v2 |
|
||||
|
||||
---
|
||||
|
||||
## 七、修复优先级
|
||||
|
||||
| 优先级 | 问题编号 | 描述 | 影响 |
|
||||
|--------|----------|------|------|
|
||||
| **P1** | P1-2 | 侧边面板关闭后无法重新打开 | UX 阻塞 |
|
||||
| **P1** | P1-1 | 节点拖拽位置可能不持久化 | 数据丢失风险 |
|
||||
| **P1** | P1-4 | exercise-block 用 index 作为 key | 列表状态错乱 |
|
||||
| **P1** | P1-5 | lesson-plan-card 用 window.location.reload | SPA 体验差 |
|
||||
| **P1** | P1-3 | inline 题目无知识点标注 | 功能缺失 |
|
||||
| **P2** | P2-2 | node-editor 隐藏 span hack | 代码质量 |
|
||||
| **P2** | P2-1 | node-editor 类型断言 | 代码规范 |
|
||||
| **P2** | P2-6 | text-study-block 选区计算错误 | 功能错误 |
|
||||
| **P2** | P2-3 | publish-service 深拷贝方式 | 性能 |
|
||||
| **P2** | P2-4 | exercise-block as never 断言 | 代码规范 |
|
||||
| **P2** | P2-5 | alert/confirm 使用 | UX 规范 |
|
||||
| **P3** | P3-4 | 列表页英文标题 | i18n 一致性 |
|
||||
| **P3** | P3-1 | 画布空状态提示 | UX 引导 |
|
||||
| **P3** | P3-2 | 版本预览 | UX 增强 |
|
||||
| **P3** | P3-3 | 编辑器骨架屏 | UX 优化 |
|
||||
|
||||
---
|
||||
|
||||
## 八、验证记录
|
||||
|
||||
| 验证项 | 命令 | 结果 |
|
||||
|--------|------|------|
|
||||
| TypeScript | `npx tsc --noEmit` | ✅ exit 0 |
|
||||
| ESLint | `npm run lint` | ✅ 备课模块零错误 |
|
||||
| Playwright 节点渲染 | 8 节点 + 7 边 | ✅ |
|
||||
| Playwright 节点选中 | 侧边面板显示 | ✅ |
|
||||
| Playwright 添加节点 | 8 → 9 节点 | ✅ |
|
||||
| Playwright 版本抽屉 | 打开/关闭 | ✅ |
|
||||
| Playwright 保存版本 | 无错误 | ✅ |
|
||||
| 控制台错误 | 无 error/warning | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 九、附录:测试截图
|
||||
|
||||
- `bugs/v3_01_initial.png` - 初始编辑页
|
||||
- `bugs/v3_02_selected.png` - 节点选中状态
|
||||
- `bugs/v3_03_versions.png` - 版本抽屉
|
||||
- `bugs/v3_04_final.png` - 最终状态
|
||||
Reference in New Issue
Block a user