# `src/app/(dashboard)/teacher` 前端规范核查报告 > 核查日期: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 界面规范审查) --- ## 一、核查文件清单 | 文件 | 行数 | 类型 | 用途 | |------|------|------|------| | [dashboard/page.tsx](../src/app/(dashboard)/teacher/dashboard/page.tsx) | 37 | 页面 | 教师仪表盘 | | [attendance/page.tsx](../src/app/(dashboard)/teacher/attendance/page.tsx) | 83 | 页面 | 考勤记录列表 | | [attendance/sheet/page.tsx](../src/app/(dashboard)/teacher/attendance/sheet/page.tsx) | 49 | 页面 | 考勤登记 | | [attendance/stats/page.tsx](../src/app/(dashboard)/teacher/attendance/stats/page.tsx) | 120 | 页面 | 考勤统计 | | [classes/page.tsx](../src/app/(dashboard)/teacher/classes/page.tsx) | 5 | 页面 | 重定向到 my | | [classes/my/page.tsx](../src/app/(dashboard)/teacher/classes/my/page.tsx) | 18 | 页面 | 我的班级 | | [classes/my/[id]/page.tsx](../src/app/(dashboard)/teacher/classes/my/[id]/page.tsx) | 109 | 页面 | 班级详情 | | [classes/my/loading.tsx](../src/app/(dashboard)/teacher/classes/my/loading.tsx) | 31 | 加载态 | 班级列表骨架屏 | | [classes/schedule/page.tsx](../src/app/(dashboard)/teacher/classes/schedule/page.tsx) | 81 | 页面 | 班级课表 | | [classes/schedule/loading.tsx](../src/app/(dashboard)/teacher/classes/schedule/loading.tsx) | 28 | 加载态 | 课表骨架屏 | | [classes/students/page.tsx](../src/app/(dashboard)/teacher/classes/students/page.tsx) | 102 | 页面 | 学生列表 | | [classes/students/loading.tsx](../src/app/(dashboard)/teacher/classes/students/loading.tsx) | 20 | 加载态 | 学生列表骨架屏 | | [course-plans/page.tsx](../src/app/(dashboard)/teacher/course-plans/page.tsx) | 49 | 页面 | 课程计划列表 | | [course-plans/[id]/page.tsx](../src/app/(dashboard)/teacher/course-plans/[id]/page.tsx) | 26 | 页面 | 课程计划详情 | | [diagnostic/page.tsx](../src/app/(dashboard)/teacher/diagnostic/page.tsx) | 48 | 页面 | 学习诊断报告 | | [diagnostic/class/[classId]/page.tsx](../src/app/(dashboard)/teacher/diagnostic/class/[classId]/page.tsx) | 45 | 页面 | 班级诊断 | | [diagnostic/student/[studentId]/page.tsx](../src/app/(dashboard)/teacher/diagnostic/student/[studentId]/page.tsx) | 65 | 页面 | 学生诊断 | | [elective/page.tsx](../src/app/(dashboard)/teacher/elective/page.tsx) | 50 | 页面 | 选修课程 | | [exams/page.tsx](../src/app/(dashboard)/teacher/exams/page.tsx) | 5 | 页面 | 重定向到 all | | [exams/all/page.tsx](../src/app/(dashboard)/teacher/exams/all/page.tsx) | 148 | 页面 | 考试列表 | | [exams/all/loading.tsx](../src/app/(dashboard)/teacher/exams/all/loading.tsx) | 24 | 加载态 | 考试列表骨架屏 | | [exams/create/page.tsx](../src/app/(dashboard)/teacher/exams/create/page.tsx) | 10 | 页面 | 创建考试 | | [exams/create/loading.tsx](../src/app/(dashboard)/teacher/exams/create/loading.tsx) | 16 | 加载态 | 创建考试骨架屏 | | [exams/[id]/build/page.tsx](../src/app/(dashboard)/teacher/exams/[id]/build/page.tsx) | 120 | 页面 | 组卷 | | [exams/[id]/proctoring/page.tsx](../src/app/(dashboard)/teacher/exams/[id]/proctoring/page.tsx) | 55 | 页面 | 监考 | | [exams/grading/page.tsx](../src/app/(dashboard)/teacher/exams/grading/page.tsx) | 5 | 页面 | 重定向 | | [exams/grading/[submissionId]/page.tsx](../src/app/(dashboard)/teacher/exams/grading/[submissionId]/page.tsx) | 6 | 页面 | 重定向 | | [exams/grading/loading.tsx](../src/app/(dashboard)/teacher/exams/grading/loading.tsx) | 20 | 加载态 | 批改骨架屏 | | [grades/page.tsx](../src/app/(dashboard)/teacher/grades/page.tsx) | 101 | 页面 | 成绩管理 | | [grades/analytics/page.tsx](../src/app/(dashboard)/teacher/grades/analytics/page.tsx) | 259 | 页面 | 成绩分析 | | [grades/entry/page.tsx](../src/app/(dashboard)/teacher/grades/entry/page.tsx) | 52 | 页面 | 批量录入 | | [grades/stats/page.tsx](../src/app/(dashboard)/teacher/grades/stats/page.tsx) | 139 | 页面 | 成绩统计 | | [homework/page.tsx](../src/app/(dashboard)/teacher/homework/page.tsx) | 5 | 页面 | 重定向 | | [homework/assignments/page.tsx](../src/app/(dashboard)/teacher/homework/assignments/page.tsx) | 119 | 页面 | 作业列表 | | [homework/assignments/create/page.tsx](../src/app/(dashboard)/teacher/homework/assignments/create/page.tsx) | 43 | 页面 | 创建作业 | | [homework/assignments/[id]/page.tsx](../src/app/(dashboard)/teacher/homework/assignments/[id]/page.tsx) | 100 | 页面 | 作业详情 | | [homework/assignments/[id]/submissions/page.tsx](../src/app/(dashboard)/teacher/homework/assignments/[id]/submissions/page.tsx) | 86 | 页面 | 作业提交列表 | | [homework/submissions/page.tsx](../src/app/(dashboard)/teacher/homework/submissions/page.tsx) | 80 | 页面 | 提交审阅 | | [homework/submissions/[submissionId]/page.tsx](../src/app/(dashboard)/teacher/homework/submissions/[submissionId]/page.tsx) | 44 | 页面 | 批改详情 | | [questions/page.tsx](../src/app/(dashboard)/teacher/questions/page.tsx) | 120 | 页面 | 题库 | | [questions/loading.tsx](../src/app/(dashboard)/teacher/questions/loading.tsx) | 29 | 加载态 | 题库骨架屏 | | [schedule-changes/page.tsx](../src/app/(dashboard)/teacher/schedule-changes/page.tsx) | 69 | 页面 | 课表变更 | | [textbooks/page.tsx](../src/app/(dashboard)/teacher/textbooks/page.tsx) | 74 | 页面 | 教材列表 | | [textbooks/loading.tsx](../src/app/(dashboard)/teacher/textbooks/loading.tsx) | 48 | 加载态 | 教材骨架屏 | | [textbooks/[id]/page.tsx](../src/app/(dashboard)/teacher/textbooks/[id]/page.tsx) | 63 | 页面 | 教材详情 | | [textbooks/[id]/loading.tsx](../src/app/(dashboard)/teacher/textbooks/[id]/loading.tsx) | 66 | 加载态 | 教材详情骨架屏 | 共计 **45 个文件**(37 个 page.tsx + 8 个 loading.tsx)。 --- ## 二、违规问题清单 ### 2.1 架构分层违规 — 严重度:高 #### BUG-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` 暴露 `getUserNameById(id)` 函数调用 #### BUG-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` 或 `modules/grades/data-access.ts` 暴露 `getSubjects()` 函数 #### BUG-T03:app 层直接访问数据库(grades/analytics/page.tsx) - **位置**:[grades/analytics/page.tsx:5-6, 48-50](../src/app/(dashboard)/teacher/grades/analytics/page.tsx) - **问题**:同 BUG-T02,直接 `db.query.subjects.findMany()` - **改进建议**:同 BUG-T02 #### BUG-T04:app 层直接访问数据库(grades/entry/page.tsx) - **位置**:[grades/entry/page.tsx:1-3, 25](../src/app/(dashboard)/teacher/grades/entry/page.tsx) - **问题**:同 BUG-T02 - **改进建议**:同 BUG-T02 #### BUG-T05:app 层直接访问数据库(grades/stats/page.tsx) - **位置**:[grades/stats/page.tsx:1-3, 28](../src/app/(dashboard)/teacher/grades/stats/page.tsx) - **问题**:同 BUG-T02 - **改进建议**:同 BUG-T02 #### BUG-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 配置违规 — 严重度:中 项目 `.prettierrc` 配置 `"semi": false`,但以下文件使用分号结尾: #### BUG-T07:textbooks/page.tsx 使用分号 - **位置**:[textbooks/page.tsx:3, 73](../src/app/(dashboard)/teacher/textbooks/page.tsx) - **问题**:`import { TextbookCard } from "...";` 等多处使用分号 - **改进建议**:运行 `npx prettier --write` 统一格式 #### BUG-T08:textbooks/[id]/page.tsx 使用分号 - **位置**:[textbooks/[id]/page.tsx](../src/app/(dashboard)/teacher/textbooks/[id]/page.tsx)(全文) - **问题**:多处语句使用分号结尾 - **改进建议**:同 BUG-T07 #### BUG-T09:textbooks/loading.tsx 使用分号 - **位置**:[textbooks/loading.tsx](../src/app/(dashboard)/teacher/textbooks/loading.tsx)(全文) - **问题**:同 BUG-T07 - **改进建议**:同 BUG-T07 #### BUG-T10:textbooks/[id]/loading.tsx 使用分号 - **位置**:[textbooks/[id]/loading.tsx](../src/app/(dashboard)/teacher/textbooks/[id]/loading.tsx)(全文) - **问题**:同 BUG-T07 - **改进建议**:同 BUG-T07 --- ### 2.3 TypeScript 规范违规 — 严重度:高 #### BUG-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-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-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-T14:使用 `as` 类型断言(grades/analytics/page.tsx) - **位置**:[grades/analytics/page.tsx](../src/app/(dashboard)/teacher/grades/analytics/page.tsx)(多处) - **问题**:同上模式 - **改进建议**:同上 #### BUG-T15:使用 `as` 类型断言(diagnostic/page.tsx) - **位置**:[diagnostic/page.tsx:27-28](../src/app/(dashboard)/teacher/diagnostic/page.tsx) - **问题**:`reportType as DiagnosticReportType` 和 `status as DiagnosticReportStatus` - **改进建议**:使用类型守卫 #### BUG-T16:函数返回值未显式标注(getParam 工具函数) - **位置**:以下 15 个文件中的 `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) => { ... }` - **改进建议**:`const getParam = (params: SearchParams, key: string): string | undefined => { ... }` #### BUG-T17:页面默认导出函数未标注返回类型 - **位置**:所有 page.tsx 文件的 `export default async function XxxPage()` - **问题**:未标注 `Promise` 或 `Promise` - **规范依据**:编码规范 5.2 示例 `export default async function UsersPage(): Promise` - **改进建议**:统一补充返回类型标注 --- ### 2.4 DRY 违规(重复代码) — 严重度:中 #### BUG-T18:`getParam` 工具函数在 16 个文件中重复定义 - **位置**:见 BUG-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-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) — 严重度:高 #### BUG-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-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-T22:串行数据获取 waterfall(attendance/stats/page.tsx) - **位置**:[attendance/stats/page.tsx:28-53](../src/app/(dashboard)/teacher/attendance/stats/page.tsx) - **问题**:`getTeacherClasses()` → `getClassAttendanceStats()` 串行,但 stats 依赖 classId(可从 classes[0] 取默认),可优化 - **改进建议**:先并行获取 classes,再取 targetClassId 后获取 stats(当前逻辑合理但可考虑预取) #### BUG-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-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-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-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-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-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-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-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) - **问题**:动态数据页面未声明 `force-dynamic`,可能导致静态生成尝试失败 - **改进建议**:所有含动态数据的页面统一添加 `export const dynamic = "force-dynamic"` --- ### 2.6 Web 界面规范违规(web-design-guidelines) — 严重度:中 #### BUG-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-T32:`` 标签作为筛选按钮语义不当 - **位置**:同 BUG-T31 - **问题**:筛选操作使用 `` 标签导航到带 query 的 URL,虽然支持 Cmd/Ctrl+click,但视觉上是按钮形态,应使用 `