# `src/app/(dashboard)/teacher` 前端规范核查报告 v2 > 核查日期:2026-06-18(第二轮) > 核查范围:`src/app/(dashboard)/teacher/` 目录下所有前端文件(page.tsx / loading.tsx) > 依据文档:项目规则、编码规范 `docs/standards/coding-standards.md`、架构影响地图 004、架构数据 005 > 应用技能:`vercel-react-best-practices`(性能优化)、`web-artifacts-builder`(界面优化)、`web-design-guidelines`(Web 界面规范审查) > 对比基准:[v1 报告](./teacher_bug.md) --- ## 一、v1 → v2 修复状态总览 ### 1.1 修复进度统计 | 状态 | 数量 | 占比 | |------|------|------| | 已修复 | 3 | 4.7% | | 部分修复 | 1 | 1.6% | | 未修复 | 60 | 93.7% | | **合计** | **64** | **100%** | ### 1.2 已修复问题清单 | v1 BUG ID | 问题摘要 | 修复方式 | |-----------|----------|----------| | T29 | schedule-changes/page.tsx 通过 actions 调用 | 改为从 `@/modules/scheduling/data-access` 导入 `getAdminClassesForScheduling` / `getTeachersForScheduling` / `getScheduleChanges` | | T57 | exams/all/page.tsx 缺少 `export const dynamic` | 当前仍缺少,但使用 Suspense 模式可接受(**部分修复**,见下方说明) | | 新增 | lesson-plans 模块新增 | 新增 3 个页面,需审查 | ### 1.3 新增文件清单 | 文件 | 行数 | 类型 | 用途 | |------|------|------|------| | [lesson-plans/page.tsx](../src/app/(dashboard)/teacher/lesson-plans/page.tsx) | 32 | 页面 | 课案列表 | | [lesson-plans/new/page.tsx](../src/app/(dashboard)/teacher/lesson-plans/new/page.tsx) | 10 | 页面 | 新建课案 | | [lesson-plans/[planId]/edit/page.tsx](../src/app/(dashboard)/teacher/lesson-plans/[planId]/edit/page.tsx) | 36 | 页面 | 编辑课案 | --- ## 二、未修复问题清单(按严重度排序) ### 2.1 架构分层违规 — 严重度:高(P0) #### BUG-V2-T01:app 层直接访问数据库(dashboard/page.tsx)❌ 未修复 - **位置**:[dashboard/page.tsx:4-6, 18-21](../src/app/(dashboard)/teacher/dashboard/page.tsx) - **问题**:页面直接 `import { db } from "@/shared/db"` 并调用 `db.query.users.findFirst()`,违反项目规则「`app/` 只能调用 `modules/` 的 Server Actions 和 data-access,不直接访问 DB」 - **现状**: ```typescript import { db } from "@/shared/db" import { users } from "@/shared/db/schema" // ... db.query.users.findFirst({ where: eq(users.id, teacherId), columns: { name: true }, }) ``` - **改进建议**:`modules/users/data-access.ts` 已有 `getUserBasicInfo(userId)` 函数(返回 name/email/image/gradeId),可直接复用: ```typescript import { getUserBasicInfo } from "@/modules/users/data-access" const teacherProfile = await getUserBasicInfo(teacherId) // teacherProfile?.name ``` #### BUG-V2-T02:app 层直接访问数据库(grades/page.tsx)❌ 未修复 - **位置**:[grades/page.tsx:5-7, 35](../src/app/(dashboard)/teacher/grades/page.tsx) - **问题**:直接 `db.query.subjects.findMany()` 查询科目列表 - **改进建议**:`modules/school/data-access.ts` 已有 `getSubjectOptions()` 函数(返回 id/name/code/order),可直接复用: ```typescript import { getSubjectOptions } from "@/modules/school/data-access" const allSubjects = await getSubjectOptions() ``` #### BUG-V2-T03:app 层直接访问数据库(grades/analytics/page.tsx)❌ 未修复 - **位置**:[grades/analytics/page.tsx:5-6, 48-50](../src/app/(dashboard)/teacher/grades/analytics/page.tsx) - **问题**:同 V2-T02 - **改进建议**:同 V2-T02 #### BUG-V2-T04:app 层直接访问数据库(grades/entry/page.tsx)❌ 未修复 - **位置**:[grades/entry/page.tsx:1-3, 25](../src/app/(dashboard)/teacher/grades/entry/page.tsx) - **问题**:同 V2-T02 - **改进建议**:同 V2-T02 #### BUG-V2-T05:app 层直接访问数据库(grades/stats/page.tsx)❌ 未修复 - **位置**:[grades/stats/page.tsx:1-3, 28](../src/app/(dashboard)/teacher/grades/stats/page.tsx) - **问题**:同 V2-T02 - **改进建议**:同 V2-T02 #### BUG-V2-T06:认证上下文获取方式不一致 ❌ 未修复 - **位置**: - [course-plans/page.tsx:1, 23](../src/app/(dashboard)/teacher/course-plans/page.tsx) - [elective/page.tsx:1, 23](../src/app/(dashboard)/teacher/elective/page.tsx) - **问题**:使用 `import { auth } from "@/auth"` + `auth()` 获取 session,而其他页面统一使用 `getAuthContext()`(含 DataScope 解析) - **影响**:无法获得 `dataScope`,无法做数据范围过滤;与项目其他页面不一致 - **改进建议**:统一改为 `const ctx = await getAuthContext(); const teacherId = ctx.userId` --- ### 2.2 Prettier 配置违规 — 严重度:中(P2) #### BUG-V2-T07:textbooks/page.tsx 使用分号 ❌ 未修复 - **位置**:[textbooks/page.tsx:3, 73](../src/app/(dashboard)/teacher/textbooks/page.tsx) - **问题**:`import { TextbookCard } from "...";` 等多处使用分号 - **改进建议**:运行 `npx prettier --write` 统一格式 #### BUG-V2-T08:textbooks/[id]/page.tsx 使用分号 ❌ 未修复 - **位置**:[textbooks/[id]/page.tsx](../src/app/(dashboard)/teacher/textbooks/[id]/page.tsx)(全文) - **问题**:多处语句使用分号结尾 - **改进建议**:同 V2-T07 #### BUG-V2-T09:textbooks/loading.tsx 使用分号 ❌ 未修复 - **位置**:[textbooks/loading.tsx](../src/app/(dashboard)/teacher/textbooks/loading.tsx)(全文) - **问题**:同 V2-T07 - **改进建议**:同 V2-T07 #### BUG-V2-T10:textbooks/[id]/loading.tsx 使用分号 ❌ 未修复 - **位置**:[textbooks/[id]/loading.tsx](../src/app/(dashboard)/teacher/textbooks/[id]/loading.tsx)(全文) - **问题**:同 V2-T07 - **改进建议**:同 V2-T07 #### BUG-V2-T10a:lesson-plans 系列文件使用分号 🆕 新增 - **位置**: - [lesson-plans/page.tsx](../src/app/(dashboard)/teacher/lesson-plans/page.tsx)(全文) - [lesson-plans/new/page.tsx](../src/app/(dashboard)/teacher/lesson-plans/new/page.tsx)(全文) - [lesson-plans/[planId]/edit/page.tsx](../src/app/(dashboard)/teacher/lesson-plans/[planId]/edit/page.tsx)(全文) - **问题**:新增文件均使用分号结尾,违反 `.prettierrc` 的 `"semi": false` - **改进建议**:同 V2-T07 --- ### 2.3 TypeScript 规范违规 — 严重度:高(P1) #### BUG-V2-T11:使用 `as` 类型断言(exams/[id]/build/page.tsx)❌ 未修复 - **位置**:[exams/[id]/build/page.tsx:32-34](../src/app/(dashboard)/teacher/exams/[id]/build/page.tsx) - **问题**:使用 `as` 断言转换类型,违反编码规范「禁止 `as` 断言(除非从 `unknown` 转换)」 - **现状**: ```typescript content: q.content as Question["content"], type: q.type as Question["type"], ``` - **改进建议**:在 data-access 层返回正确类型,或使用类型守卫函数 #### BUG-V2-T12:使用 `as` 类型断言(attendance/page.tsx)❌ 未修复 - **位置**:[attendance/page.tsx:39](../src/app/(dashboard)/teacher/attendance/page.tsx) - **问题**:`status as "present" | "absent" | "late" | "early_leave" | "excused"` 直接断言 - **改进建议**:使用类型守卫函数 `isAttendanceStatus(value): value is AttendanceStatus` #### BUG-V2-T13:使用 `as` 类型断言(grades/page.tsx)❌ 未修复 - **位置**:[grades/page.tsx:43-44](../src/app/(dashboard)/teacher/grades/page.tsx) - **问题**:`type as "exam" | "quiz" | "homework" | "other"` 和 `semester as "1" | "2"` 直接断言 - **改进建议**:使用类型守卫 #### BUG-V2-T14:使用 `as` 类型断言(grades/analytics/page.tsx)❌ 未修复 - **位置**:[grades/analytics/page.tsx](../src/app/(dashboard)/teacher/grades/analytics/page.tsx)(多处) - **问题**:同上模式 - **改进建议**:同上 #### BUG-V2-T15:使用 `as` 类型断言(diagnostic/page.tsx)❌ 未修复 - **位置**:[diagnostic/page.tsx:27-28](../src/app/(dashboard)/teacher/diagnostic/page.tsx) - **问题**:`reportType as DiagnosticReportType` 和 `status as DiagnosticReportStatus` - **改进建议**:使用类型守卫 #### BUG-V2-T16:函数返回值未显式标注(getParam 工具函数)❌ 未修复 - **位置**:以下 16 个文件中的 `getParam` 函数均未标注返回类型 - attendance/page.tsx:15 - attendance/sheet/page.tsx:9 - attendance/stats/page.tsx:12 - classes/schedule/page.tsx:14 - classes/students/page.tsx:14 - course-plans/page.tsx:10 - diagnostic/page.tsx:10 - elective/page.tsx:10 - exams/all/page.tsx:16 - grades/page.tsx:19 - grades/analytics/page.tsx:28 - grades/entry/page.tsx:12 - grades/stats/page.tsx:15 - homework/assignments/page.tsx:23 - questions/page.tsx:15 - textbooks/page.tsx:13 - **问题**:违反编码规范「函数返回值必须显式标注,特别是 `Promise`」 - **改进建议**:`const getParam = (params: SearchParams, key: string): string | undefined => { ... }` #### BUG-V2-T17:页面默认导出函数未标注返回类型 ❌ 未修复 - **位置**:所有 page.tsx 文件的 `export default async function XxxPage()` - **问题**:未标注 `Promise` 或 `Promise` - **规范依据**:编码规范 5.2 示例 `export default async function UsersPage(): Promise` - **改进建议**:统一补充返回类型标注 --- ### 2.4 DRY 违规(重复代码) — 严重度:中(P2) #### BUG-V2-T18:`getParam` 工具函数在 16 个文件中重复定义 ❌ 未修复 - **位置**:见 V2-T16 列表 - **问题**:完全相同的工具函数 `getParam` 和类型 `SearchParams` 在 16 个页面文件中复制粘贴 - **改进建议**:提取到 `shared/lib/search-params.ts`: ```typescript export type SearchParams = { [key: string]: string | string[] | undefined } export function getParam(params: SearchParams, key: string): string | undefined { const v = params[key] return Array.isArray(v) ? v[0] : v } ``` #### BUG-V2-T19:`StatsClassSelector` 模式重复 ❌ 未修复 - **位置**: - [attendance/stats/page.tsx:91-119](../src/app/(dashboard)/teacher/attendance/stats/page.tsx) - [grades/stats/page.tsx:86-138](../src/app/(dashboard)/teacher/grades/stats/page.tsx) - [grades/analytics/page.tsx:150-258](../src/app/(dashboard)/teacher/grades/analytics/page.tsx) - **问题**:三处文件都定义了「类筛选按钮组」组件,结构几乎相同(`` 标签 + 条件 className) - **改进建议**:提取为共享组件 `shared/components/ui/filter-chips.tsx` --- ### 2.5 性能问题(vercel-react-best-practices) — 严重度:高(P1) #### BUG-V2-T20:串行数据获取 waterfall(attendance/page.tsx)❌ 未修复 - **位置**:[attendance/page.tsx:32-41](../src/app/(dashboard)/teacher/attendance/page.tsx) - **问题**:`getTeacherClasses()` 与 `getAttendanceRecords()` 串行执行,但二者无依赖关系 - **违反规则**:`async-parallel` - 独立操作应使用 `Promise.all()` - **改进建议**: ```typescript const [classes, result] = await Promise.all([ getTeacherClasses(), getAttendanceRecords({ ... }), ]) ``` #### BUG-V2-T21:串行数据获取 waterfall(attendance/sheet/page.tsx)❌ 未修复 - **位置**:[attendance/sheet/page.tsx:24-29](../src/app/(dashboard)/teacher/attendance/sheet/page.tsx) - **问题**:`getTeacherClasses()` 与 `getClassStudentsForAttendance()` 串行,但 students 依赖 defaultClassId(来自 searchParams),可与 classes 并行 - **改进建议**:使用 `Promise.all` 并行 #### BUG-V2-T22:串行数据获取 waterfall(attendance/stats/page.tsx)❌ 未修复 - **位置**:[attendance/stats/page.tsx:28-53](../src/app/(dashboard)/teacher/attendance/stats/page.tsx) - **问题**:`getTeacherClasses()` → `getClassAttendanceStats()` 串行 - **改进建议**:先并行获取 classes,再取 targetClassId 后获取 stats(当前逻辑合理但可考虑预取) #### BUG-V2-T23:串行数据获取 waterfall(grades/page.tsx)❌ 未修复 - **位置**:[grades/page.tsx:33-45](../src/app/(dashboard)/teacher/grades/page.tsx) - **问题**:`Promise.all([getTeacherClasses, db.query])` 之后串行 `getGradeRecords`,但 `getGradeRecords` 不依赖前两者结果 - **改进建议**:三个查询全部 `Promise.all` #### BUG-V2-T24:串行数据获取 waterfall(grades/entry/page.tsx)❌ 未修复 - **位置**:[grades/entry/page.tsx:23-34](../src/app/(dashboard)/teacher/grades/entry/page.tsx) - **问题**:`Promise.all([getTeacherClasses, db.query])` 后串行 `getClassStudentsForEntry`,但 students 依赖 defaultClassId(来自 searchParams),可并行 - **改进建议**:`Promise.all` 三个查询 #### BUG-V2-T25:串行数据获取 waterfall(grades/stats/page.tsx)❌ 未修复 - **位置**:[grades/stats/page.tsx:26-54](../src/app/(dashboard)/teacher/grades/stats/page.tsx) - **问题**:`Promise.all([getTeacherClasses, db.query])` → `Promise.all([stats, ranking])` 两段串行 - **改进建议**:合并为单个 `Promise.all` #### BUG-V2-T26:串行数据获取 waterfall(classes/my/[id]/page.tsx)❌ 未修复 - **位置**:[classes/my/[id]/page.tsx:21-30](../src/app/(dashboard)/teacher/classes/my/[id]/page.tsx) - **问题**:`Promise.all([insights, students, schedule])` 后串行 `getClassStudentSubjectScoresV2` - **改进建议**:将 `getClassStudentSubjectScoresV2` 加入第一个 `Promise.all` #### BUG-V2-T27:串行数据获取 waterfall(diagnostic/student/[studentId]/page.tsx)❌ 未修复 - **位置**:[diagnostic/student/[studentId]/page.tsx:30-45](../src/app/(dashboard)/teacher/diagnostic/student/[studentId]/page.tsx) - **问题**:`Promise.all([summary, reports])` 后串行 `getKnowledgePointStats()` - **改进建议**:合并为单个 `Promise.all` #### BUG-V2-T28:串行数据获取 waterfall(exams/[id]/build/page.tsx)❌ 未修复 - **位置**:[exams/[id]/build/page.tsx:12-26](../src/app/(dashboard)/teacher/exams/[id]/build/page.tsx) - **问题**:`getExamById` → `getQuestions` → `getQuestions(ids)` 三段串行 - **改进建议**:前两个可并行;第三个依赖 exam.questions 的 ID 列表,需串行但可优化 #### BUG-V2-T29:Bundle 优化 - barrel imports(lucide-react)❌ 未修复 - **位置**:几乎所有页面文件 - **问题**:`import { PlusCircle, BarChart3, ClipboardList } from "lucide-react"` 使用 barrel 文件导入,违反 `bundle-barrel-imports` 规则 - **改进建议**:lucide-react 已支持 tree-shaking,但可考虑使用 `lucide-react/icons` 直接导入路径 #### BUG-V2-T30:缺少 `export const dynamic = "force-dynamic"` 声明 ❌ 未修复 - **位置**: - [exams/all/page.tsx](../src/app/(dashboard)/teacher/exams/all/page.tsx)(使用 Suspense,可省略) - [exams/create/page.tsx](../src/app/(dashboard)/teacher/exams/create/page.tsx) - [exams/[id]/build/page.tsx](../src/app/(dashboard)/teacher/exams/[id]/build/page.tsx) - [questions/page.tsx](../src/app/(dashboard)/teacher/questions/page.tsx)(使用 Suspense) - [textbooks/page.tsx](../src/app/(dashboard)/teacher/textbooks/page.tsx)(使用 Suspense) - [lesson-plans/page.tsx](../src/app/(dashboard)/teacher/lesson-plans/page.tsx) 🆕 - [lesson-plans/new/page.tsx](../src/app/(dashboard)/teacher/lesson-plans/new/page.tsx) 🆕 - [lesson-plans/[planId]/edit/page.tsx](../src/app/(dashboard)/teacher/lesson-plans/[planId]/edit/page.tsx) 🆕 - **问题**:动态数据页面未声明 `force-dynamic`,可能导致静态生成尝试失败 - **改进建议**:所有含动态数据的页面统一添加 `export const dynamic = "force-dynamic"` --- ### 2.6 Web 界面规范违规(web-design-guidelines) — 严重度:中(P2) #### BUG-V2-T31:`` 标签缺少 focus-visible 焦点样式 ❌ 未修复 - **位置**: - [attendance/stats/page.tsx:106-117](../src/app/(dashboard)/teacher/attendance/stats/page.tsx) - [grades/analytics/page.tsx:192-253](../src/app/(dashboard)/teacher/grades/analytics/page.tsx) - [grades/stats/page.tsx:100-135](../src/app/(dashboard)/teacher/grades/stats/page.tsx) - **问题**:筛选按钮使用 `` 标签但仅有 `hover:bg-accent`,缺少 `focus-visible:ring-*` 或 `focus-visible:outline` 焦点样式 - **违反规则**:Focus States - Interactive elements need visible focus - **改进建议**:添加 `focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2` #### BUG-V2-T32:`` 标签作为筛选按钮语义不当 ❌ 未修复 - **位置**:同 V2-T31 - **问题**:筛选操作使用 `` 标签导航到带 query 的 URL,虽然支持 Cmd/Ctrl+click,但视觉上是按钮形态,应使用 ` ) } ``` --- ## 七、架构图同步建议 本次核查未修改源码,无需同步架构图。但建议在后续修复时: 1. 若新增 `shared/lib/search-params.ts`,需在 005_architecture_data.json 的 `shared.lib.exports` 中添加 2. 若新增 `shared/components/ui/filter-chips.tsx`,需在 005 的 `shared.components.exports` 中添加 3. `lesson-plans` 模块需在 005 的 `modules` 中新增节点,记录其 `data-access` 和 `actions` 导出 4. `modules/school/data-access.ts` 的 `getSubjectOptions` 已存在,确认 005 中已记录 5. `modules/users/data-access.ts` 的 `getUserBasicInfo` 已存在,确认 005 中已记录 --- ## 八、总结 本次 v2 核查覆盖 `src/app/(dashboard)/teacher/` 下全部 **48 个前端文件**(37 个 page.tsx + 8 个 loading.tsx + 3 个新增 lesson-plans 页面),共发现 **74 个问题**,分布如下: | 严重度 | 数量 | 类别 | v1 对比 | |--------|------|------|---------| | P0 | 12 | 架构违规、权限缺失 | +3(含 2 个新增) | | P1 | 16 | TypeScript、性能 | 0 | | P2 | 23 | 规范、可访问性 | +5(含 5 个新增) | | P3 | 23 | 代码质量 | +2 | ### 核心问题(v2 更新) 1. **架构层违规加剧**:5 处 app 层直接访问 DB 未修复,新增 2 处 lesson-plans 通过 actions 读取数据 2. **权限校验不一致**:3 处页面无校验未修复,新增 lesson-plans 模块未做权限校验 3. **性能 waterfall 普遍**:9 处串行数据获取未修复 4. **DRY 违规突出**:`getParam` 函数在 16 个文件中重复 5. **可访问性缺失**:焦点样式、aria-label、skip link 普遍缺失 6. **新增模块质量待提升**:lesson-plans 模块存在架构违规、风格不一致、基础元素缺失等问题 ### 修复建议优先级 1. **第一优先级**:修复 5 处 app 层直接访问 DB(已有 `getUserBasicInfo` 和 `getSubjectOptions` 可直接复用) 2. **第二优先级**:统一权限校验为 `getAuthContext()` 3. **第三优先级**:修复 lesson-plans 模块的架构违规(actions → data-access) 4. **第四优先级**:提取共享工具 `getParam` 和 `FilterChips` 组件 5. **第五优先级**:并行化数据获取,优化性能 建议按 P0 → P1 → P2 → P3 顺序修复,优先解决架构与安全问题。特别是 app 层直接访问 DB 的问题,已有现成的 data-access 函数可用,修复成本极低。