Bug fixes (from bugs/ directory): - Fix cross-module DB queries in 9 modules (homework, grades, parent, diagnostic, elective, proctoring, notifications, scheduling, classes) by routing through data-access functions - Fix shared/lib <-> auth circular dependency via new session.ts module - Fix divide-by-zero guard in grades data-access - Fix audit export data truncation (paginated fetch for full datasets) - Fix missing transactions in homework grading and elective lottery - Fix missing revalidatePath in course-plans actions - Fix frontend permission checks using requirePermission instead of requireAuth - Fix dashboard role routing using session.user.roles - Fix student auth pattern (migrate getDemoStudentUser to users module) - Fix ActionState return type handling in components Code quality fixes: - Remove 60+ as type assertions (replace with type guards) - Remove non-null assertions (use optional chaining or explicit checks) - Convert dynamic imports to static imports (grades, diagnostic) - Add React.cache() wrapping for read functions - Parallelize independent queries with Promise.all - Add explicit return types to 30+ arrow functions - Replace any with unknown + type guards - Fix import type for type-only imports - Add Zod validation schemas for classes and diagnostic modules - Extract duplicate code (normalizeRoleName, normalizeBcryptHash, logger IP extraction) - Add console.error to silent catch blocks - Fix permission naming consistency (exam:proctor_read -> exam:proctor:read) Architecture doc sync: - Update 004_architecture_impact_map.md and 005_architecture_data.json - Update management-modules-audit.md for P0-7 cross-module fix Moved deleted proctoring event route to deletes/ folder.
742 lines
39 KiB
Markdown
742 lines
39 KiB
Markdown
# `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<T>`」
|
||
- **现状**:`const getParam = (params: SearchParams, key: string) => { ... }`
|
||
- **改进建议**:`const getParam = (params: SearchParams, key: string): string | undefined => { ... }`
|
||
|
||
#### BUG-T17:页面默认导出函数未标注返回类型
|
||
- **位置**:所有 page.tsx 文件的 `export default async function XxxPage()`
|
||
- **问题**:未标注 `Promise<JSX.Element>` 或 `Promise<React.ReactNode>`
|
||
- **规范依据**:编码规范 5.2 示例 `export default async function UsersPage(): Promise<JSX.Element>`
|
||
- **改进建议**:统一补充返回类型标注
|
||
|
||
---
|
||
|
||
### 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)
|
||
- **问题**:三处文件都定义了「类筛选按钮组」组件,结构几乎相同(`<a>` 标签 + 条件 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:`<a>` 标签缺少 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)
|
||
- **问题**:筛选按钮使用 `<a>` 标签但仅有 `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:`<a>` 标签作为筛选按钮语义不当
|
||
- **位置**:同 BUG-T31
|
||
- **问题**:筛选操作使用 `<a>` 标签导航到带 query 的 URL,虽然支持 Cmd/Ctrl+click,但视觉上是按钮形态,应使用 `<button>` 或添加 `role="button"`
|
||
- **违反规则**:`<button>` for actions, `<a>`/`<Link>` for navigation
|
||
- **改进建议**:使用 Next.js `<Link>` 并补充焦点样式,或改为 `<button>` + `useRouter` + `useSearchParams`
|
||
|
||
#### BUG-T33:标题层级缺失(exams/[id]/build/page.tsx)
|
||
- **位置**:[exams/[id]/build/page.tsx:104-118](../src/app/(dashboard)/teacher/exams/[id]/build/page.tsx)
|
||
- **问题**:页面无 `<h1>` 标题,直接渲染 `<ExamAssembly>` 组件,违反「Headings hierarchical `<h1>`–`<h6>`」
|
||
- **改进建议**:在页面顶部添加 `<h1>` 标题(如「Build Exam」)
|
||
|
||
#### BUG-T34:标题层级缺失(exams/[id]/proctoring/page.tsx)
|
||
- **位置**:[exams/[id]/proctoring/page.tsx:50-54](../src/app/(dashboard)/teacher/exams/[id]/proctoring/page.tsx)
|
||
- **问题**:同 BUG-T33,无 `<h1>`
|
||
- **改进建议**:同 BUG-T33
|
||
|
||
#### BUG-T35:标题层级缺失(classes/my/[id]/page.tsx)
|
||
- **位置**:[classes/my/[id]/page.tsx:65-108](../src/app/(dashboard)/teacher/classes/my/[id]/page.tsx)
|
||
- **问题**:页面无 `<h1>`,依赖 `<ClassHeader>` 组件渲染标题,需确认组件内是否有 h1
|
||
- **改进建议**:确认 `ClassHeader` 包含 `<h1>`
|
||
|
||
#### BUG-T36:长文本未截断(homework/assignments/page.tsx)
|
||
- **位置**:[homework/assignments/page.tsx:99-101](../src/app/(dashboard)/teacher/homework/assignments/page.tsx)
|
||
- **问题**:作业标题 `<Link>{a.title}</Link>` 未限制长度,长标题会破坏表格布局
|
||
- **违反规则**:Content Handling - Text containers handle long content
|
||
- **改进建议**:添加 `line-clamp-2` 或 `truncate max-w-[200px]`
|
||
|
||
#### BUG-T37:长文本未截断(homework/submissions/page.tsx)
|
||
- **位置**:[homework/submissions/page.tsx:58-60](../src/app/(dashboard)/teacher/homework/submissions/page.tsx)
|
||
- **问题**:同 BUG-T36
|
||
- **改进建议**:同 BUG-T36
|
||
|
||
#### BUG-T38:长文本未截断(homework/assignments/[id]/submissions/page.tsx)
|
||
- **位置**:[homework/assignments/[id]/submissions/page.tsx:65](../src/app/(dashboard)/teacher/homework/assignments/[id]/submissions/page.tsx)
|
||
- **问题**:学生姓名单元格未限制长度
|
||
- **改进建议**:添加 `truncate max-w-[160px]`
|
||
|
||
#### BUG-T39:Flex 子元素缺少 `min-w-0`
|
||
- **位置**:
|
||
- [homework/assignments/[id]/page.tsx:26-42](../src/app/(dashboard)/teacher/homework/assignments/[id]/page.tsx)
|
||
- [classes/my/[id]/page.tsx:86-104](../src/app/(dashboard)/teacher/classes/my/[id]/page.tsx)
|
||
- **问题**:flex 容器内的文本子元素未设置 `min-w-0`,长内容无法正确截断
|
||
- **违反规则**:Flex children need `min-w-0` to allow text truncation
|
||
- **改进建议**:在 flex 子元素添加 `min-w-0`
|
||
|
||
#### BUG-T40:使用 `transition: all` 或 `transition-colors` 未列明属性
|
||
- **位置**:
|
||
- [attendance/stats/page.tsx:109](../src/app/(dashboard)/teacher/attendance/stats/page.tsx) - `transition-colors`(可接受)
|
||
- [grades/analytics/page.tsx:195](../src/app/(dashboard)/teacher/grades/analytics/page.tsx) - `transition-colors`(可接受)
|
||
- **问题**:`transition-colors` 实际上列明了属性,符合规范;但需检查是否有 `transition: all` 使用
|
||
- **现状**:未发现 `transition: all`,此项通过
|
||
|
||
#### BUG-T41:硬编码日期/数字格式
|
||
- **位置**:所有使用 `formatDate` 的文件
|
||
- **问题**:需确认 `formatDate` 内部是否使用 `Intl.DateTimeFormat`,若使用硬编码格式则违规
|
||
- **违反规则**:Locale & i18n - Dates/times: use `Intl.DateTimeFormat`
|
||
- **改进建议**:检查 `shared/lib/utils.ts` 的 `formatDate` 实现
|
||
|
||
#### BUG-T42:数字列未使用 `tabular-nums`
|
||
- **位置**:
|
||
- [exams/all/page.tsx:54-60](../src/app/(dashboard)/teacher/exams/all/page.tsx) - 考试计数
|
||
- [homework/submissions/page.tsx:69-71](../src/app/(dashboard)/teacher/homework/submissions/page.tsx) - 计数列
|
||
- [homework/assignments/[id]/submissions/page.tsx:73](../src/app/(dashboard)/teacher/homework/assignments/[id]/submissions/page.tsx) - 分数
|
||
- **问题**:数字列未使用 `font-variant-numeric: tabular-nums`,对齐不整齐
|
||
- **违反规则**:Typography - `font-variant-numeric: tabular-nums` for number columns
|
||
- **改进建议**:数字单元格添加 `tabular-nums` 类
|
||
|
||
#### BUG-T43:大列表未虚拟化
|
||
- **位置**:
|
||
- [questions/page.tsx:42](../src/app/(dashboard)/teacher/questions/page.tsx) - `pageSize: 200`
|
||
- [exams/all/page.tsx](../src/app/(dashboard)/teacher/exams/all/page.tsx) - ExamDataTable
|
||
- **问题**:题库页面一次加载 200 条题目,若渲染全部 DOM 节点会卡顿
|
||
- **违反规则**:Performance - Large lists (>50 items): virtualize
|
||
- **改进建议**:使用 `virtua` 或 `content-visibility: auto` 虚拟化长列表
|
||
|
||
---
|
||
|
||
### 2.7 组件规范违规 — 严重度:中
|
||
|
||
#### BUG-T44:不必要的包装组件(classes/my/page.tsx)
|
||
- **位置**:[classes/my/page.tsx:6-17](../src/app/(dashboard)/teacher/classes/my/page.tsx)
|
||
- **问题**:默认导出 `MyClassesPage` 仅调用 `MyClassesPageImpl`,多此一举
|
||
- **现状**:
|
||
```typescript
|
||
export default function MyClassesPage() {
|
||
return <MyClassesPageImpl />
|
||
}
|
||
|
||
async function MyClassesPageImpl() {
|
||
// ...
|
||
}
|
||
```
|
||
- **改进建议**:直接默认导出 async 函数:
|
||
```typescript
|
||
export default async function MyClassesPage() {
|
||
const [classes, subjectOptions] = await Promise.all([...])
|
||
return <MyClassesGrid classes={classes} subjectOptions={subjectOptions} />
|
||
}
|
||
```
|
||
|
||
#### BUG-T45:非导出组件定义在 page.tsx 中
|
||
- **位置**:
|
||
- [attendance/stats/page.tsx:91-119](../src/app/(dashboard)/teacher/attendance/stats/page.tsx) - `StatsClassSelector`
|
||
- [grades/analytics/page.tsx:150-258](../src/app/(dashboard)/teacher/grades/analytics/page.tsx) - `AnalyticsFilters`
|
||
- [grades/stats/page.tsx:86-138](../src/app/(dashboard)/teacher/grades/stats/page.tsx) - `StatsClassSelector`
|
||
- [classes/schedule/page.tsx:45-63](../src/app/(dashboard)/teacher/classes/schedule/page.tsx) - `ScheduleResultsFallback`
|
||
- [classes/students/page.tsx:68-81](../src/app/(dashboard)/teacher/classes/students/page.tsx) - `StudentsResultsFallback`
|
||
- [exams/all/page.tsx:101-128](../src/app/(dashboard)/teacher/exams/all/page.tsx) - `ExamsResultsFallback`
|
||
- [questions/page.tsx:75-88](../src/app/(dashboard)/teacher/questions/page.tsx) - `QuestionBankResultsFallback`
|
||
- **问题**:辅助组件定义在 page.tsx 中,违反「其余所有组件使用具名导出」规范,且无法复用
|
||
- **改进建议**:提取到 `components/` 目录或 `shared/components/ui/`
|
||
|
||
#### BUG-T46:exams/create/page.tsx 顶部多余空行
|
||
- **位置**:[exams/create/page.tsx:5](../src/app/(dashboard)/teacher/exams/create/page.tsx)
|
||
- **问题**:JSX 开始标签前有多余空行
|
||
- **现状**:
|
||
```typescript
|
||
return (
|
||
|
||
<div className="...">
|
||
```
|
||
- **改进建议**:删除空行
|
||
|
||
---
|
||
|
||
### 2.8 安全与权限违规 — 严重度:高
|
||
|
||
#### BUG-T47:缺少权限校验(course-plans/page.tsx)
|
||
- **位置**:[course-plans/page.tsx](../src/app/(dashboard)/teacher/course-plans/page.tsx)
|
||
- **问题**:仅通过 `auth()` 获取 session,未调用 `requirePermission()` 或 `getAuthContext()` 进行权限校验
|
||
- **改进建议**:使用 `getAuthContext()` 替代 `auth()`,并在 data-access 层做 DataScope 过滤
|
||
|
||
#### BUG-T48:缺少权限校验(elective/page.tsx)
|
||
- **位置**:[elective/page.tsx](../src/app/(dashboard)/teacher/elective/page.tsx)
|
||
- **问题**:同 BUG-T47
|
||
- **改进建议**:同 BUG-T47
|
||
|
||
#### BUG-T49:缺少权限校验(dashboard/page.tsx)
|
||
- **位置**:[dashboard/page.tsx](../src/app/(dashboard)/teacher/dashboard/page.tsx)
|
||
- **问题**:依赖路由层代理(proxy.ts)做角色路由,但页面本身未做二次权限校验
|
||
- **改进建议**:添加 `getAuthContext()` 确认教师身份
|
||
|
||
#### BUG-T50:权限校验方式不一致
|
||
- **位置**:
|
||
- [exams/[id]/proctoring/page.tsx:21](../src/app/(dashboard)/teacher/exams/[id]/proctoring/page.tsx) - 使用 `requirePermission(Permissions.EXAM_PROCTOR)`
|
||
- [diagnostic/class/[classId]/page.tsx:15-23](../src/app/(dashboard)/teacher/diagnostic/class/[classId]/page.tsx) - 使用 `getAuthContext()` + DataScope 校验
|
||
- [grades/page.tsx:26](../src/app/(dashboard)/teacher/grades/page.tsx) - 使用 `getAuthContext()`
|
||
- **问题**:权限校验方式不统一,部分用 `requirePermission`,部分用 `getAuthContext`,部分无校验
|
||
- **改进建议**:统一权限校验策略,页面入口用 `getAuthContext()`,写操作用 `requirePermission()`
|
||
|
||
---
|
||
|
||
### 2.9 加载态缺失 — 严重度:低
|
||
|
||
#### BUG-T51:缺少 loading.tsx 的目录
|
||
- **位置**:
|
||
- `attendance/`(含 sheet/、stats/)
|
||
- `course-plans/`(含 [id]/)
|
||
- `diagnostic/`(含 class/、student/)
|
||
- `elective/`
|
||
- `exams/[id]/`(含 build/、proctoring/)
|
||
- `grades/`(含 analytics/、entry/、stats/)
|
||
- `homework/`(含 assignments/、submissions/)
|
||
- `schedule-changes/`
|
||
- **问题**:以上目录无 `loading.tsx`,导航时无骨架屏反馈
|
||
- **改进建议**:为每个动态页面目录添加 `loading.tsx`,参考 `classes/my/loading.tsx` 模式
|
||
|
||
#### BUG-T52:exams/grading/loading.tsx 实际无用
|
||
- **位置**:[exams/grading/loading.tsx](../src/app/(dashboard)/teacher/exams/grading/loading.tsx)
|
||
- **问题**:`exams/grading/page.tsx` 仅做 `redirect()`,loading.tsx 永远不会显示
|
||
- **改进建议**:删除该 loading.tsx
|
||
|
||
---
|
||
|
||
### 2.10 逻辑与代码质量问题 — 严重度:中
|
||
|
||
#### BUG-T53:homework/assignments/page.tsx 条件取数逻辑反直觉
|
||
- **位置**:[homework/assignments/page.tsx:33-36](../src/app/(dashboard)/teacher/homework/assignments/page.tsx)
|
||
- **问题**:`classId && classId !== "all" ? getTeacherClasses() : Promise.resolve([])` 仅在有 classId 时才获取班级列表,逻辑反直觉(通常应始终获取班级列表用于筛选下拉)
|
||
- **现状**:classes 仅用于查找 className 显示,逻辑正确但可读性差
|
||
- **改进建议**:始终获取 classes,或添加注释说明「仅在过滤时需要 className」
|
||
|
||
#### BUG-T54:exams/[id]/build/page.tsx `normalizeStructure` 函数过长
|
||
- **位置**:[exams/[id]/build/page.tsx:52-91](../src/app/(dashboard)/teacher/exams/[id]/build/page.tsx)
|
||
- **问题**:40 行的 `normalizeStructure` 函数定义在组件内部,包含嵌套递归逻辑,可读性差
|
||
- **改进建议**:提取到 `modules/exams/utils/normalize-structure.ts`,并添加单元测试
|
||
|
||
#### BUG-T55:exams/[id]/build/page.tsx 使用 `satisfies` 但混合 `as`
|
||
- **位置**:[exams/[id]/build/page.tsx:74, 84, 86](../src/app/(dashboard)/teacher/exams/[id]/build/page.tsx)
|
||
- **问题**:同时使用 `satisfies ExamNode`(好)和 `as ExamNode[]`(违规),类型处理不一致
|
||
- **改进建议**:移除 `as ExamNode[]`,改用类型守卫或 `Array.from()` 配合 filter
|
||
|
||
#### BUG-T56:grades/analytics/page.tsx 文件过长
|
||
- **位置**:[grades/analytics/page.tsx](../src/app/(dashboard)/teacher/grades/analytics/page.tsx) - 259 行
|
||
- **问题**:单文件 259 行,接近 React 组件 500 行建议上限的 50%,包含页面 + `AnalyticsFilters` 组件
|
||
- **改进建议**:将 `AnalyticsFilters` 提取到 `modules/grades/components/analytics-filters.tsx`
|
||
|
||
#### BUG-T57:exams/all/page.tsx 缺少 `export const dynamic`
|
||
- **位置**:[exams/all/page.tsx](../src/app/(dashboard)/teacher/exams/all/page.tsx)
|
||
- **问题**:使用 Suspense 但未声明 `force-dynamic`,可能导致构建时尝试静态生成
|
||
- **改进建议**:添加 `export const dynamic = "force-dynamic"`
|
||
|
||
---
|
||
|
||
### 2.11 可访问性问题 — 严重度:中
|
||
|
||
#### BUG-T58:图标按钮缺少 aria-label
|
||
- **位置**:
|
||
- [textbooks/[id]/page.tsx:33-36](../src/app/(dashboard)/teacher/textbooks/[id]/page.tsx) - 返回按钮
|
||
- [homework/assignments/[id]/page.tsx:28-31](../src/app/(dashboard)/teacher/homework/assignments/[id]/page.tsx) - 面包屑链接(有文本,OK)
|
||
- **问题**:`textbooks/[id]/page.tsx` 的返回按钮仅含图标,无 `aria-label`
|
||
- **违反规则**:Accessibility - Icon-only buttons need `aria-label`
|
||
- **改进建议**:添加 `aria-label="Back to textbooks"`
|
||
|
||
#### BUG-T59:装饰性图标未标记 aria-hidden
|
||
- **位置**:几乎所有页面中的 lucide 图标
|
||
- **问题**:如 `<BarChart3 className="mr-2 h-4 w-4" />` 等装饰性图标未添加 `aria-hidden="true"`
|
||
- **违反规则**:Accessibility - Decorative icons need `aria-hidden="true"`
|
||
- **改进建议**:装饰性图标添加 `aria-hidden="true"`
|
||
|
||
#### BUG-T60:缺少 skip link
|
||
- **位置**:所有页面
|
||
- **问题**:页面无「跳到主内容」的 skip link,键盘用户需 Tab 遍历整个侧边栏
|
||
- **违反规则**:Accessibility - include skip link for main content
|
||
- **改进建议**:在 dashboard layout 添加 skip link(应在 layout 层处理)
|
||
|
||
---
|
||
|
||
### 2.12 其他问题
|
||
|
||
#### BUG-T61:homework/assignments/[id]/page.tsx 使用 h1 但其他页面用 h2
|
||
- **位置**:
|
||
- [homework/assignments/[id]/page.tsx:36](../src/app/(dashboard)/teacher/homework/assignments/[id]/page.tsx) - `<h1>`
|
||
- [attendance/page.tsx:47](../src/app/(dashboard)/teacher/attendance/page.tsx) - `<h2>`
|
||
- [grades/page.tsx:54](../src/app/(dashboard)/teacher/grades/page.tsx) - `<h2>`
|
||
- **问题**:页面主标题层级不统一,部分用 h1,部分用 h2
|
||
- **改进建议**:统一使用 h1 作为页面主标题(layout 可能已有 h1,需确认)
|
||
|
||
#### BUG-T62:textbooks/page.tsx 使用 h1,其他页面用 h2
|
||
- **位置**:
|
||
- [textbooks/page.tsx:57](../src/app/(dashboard)/teacher/textbooks/page.tsx) - `<h1>`
|
||
- [textbooks/[id]/page.tsx:45](../src/app/(dashboard)/teacher/textbooks/[id]/page.tsx) - `<h1>`
|
||
- **问题**:同 BUG-T61,标题层级不统一
|
||
- **改进建议**:统一标题层级策略
|
||
|
||
#### BUG-T63:exams/create/page.tsx 缺少页面标题
|
||
- **位置**:[exams/create/page.tsx:3-9](../src/app/(dashboard)/teacher/exams/create/page.tsx)
|
||
- **问题**:页面无任何标题,直接渲染表单
|
||
- **改进建议**:添加 `<h1>Create Exam</h1>`
|
||
|
||
#### BUG-T64:loading.tsx 文件命名风格不一致
|
||
- **位置**:
|
||
- [textbooks/loading.tsx](../src/app/(dashboard)/teacher/textbooks/loading.tsx) - 使用 Card 组件
|
||
- [classes/my/loading.tsx](../src/app/(dashboard)/teacher/classes/my/loading.tsx) - 使用纯 div
|
||
- **问题**:骨架屏风格不统一,部分用 Card 组件,部分用纯 div
|
||
- **改进建议**:统一骨架屏风格,提取共享骨架屏组件
|
||
|
||
---
|
||
|
||
## 三、改进优先级汇总
|
||
|
||
### P0 - 立即修复(架构与安全)
|
||
|
||
| BUG ID | 问题 | 影响 |
|
||
|--------|------|------|
|
||
| T01-T05 | app 层直接访问 DB | 破坏三层架构,模块封装失效 |
|
||
| T06 | 认证方式不一致 | 数据范围过滤缺失 |
|
||
| T47-T50 | 权限校验缺失/不一致 | 越权访问风险 |
|
||
|
||
### P1 - 高优先级(TypeScript 与性能)
|
||
|
||
| BUG ID | 问题 | 影响 |
|
||
|--------|------|------|
|
||
| T11-T15 | 使用 `as` 类型断言 | 类型安全受损 |
|
||
| T16-T17 | 函数返回值未标注 | 类型推导不显式 |
|
||
| T20-T28 | 串行数据获取 waterfall | 页面加载性能差 |
|
||
| T43 | 大列表未虚拟化 | 题库页面卡顿 |
|
||
|
||
### P2 - 中优先级(规范与可访问性)
|
||
|
||
| BUG ID | 问题 | 影响 |
|
||
|--------|------|------|
|
||
| T07-T10 | Prettier 分号违规 | 代码风格不一致 |
|
||
| T18-T19 | DRY 违规 | 维护成本高 |
|
||
| T31-T32 | 筛选按钮焦点样式/语义 | 键盘可访问性差 |
|
||
| T36-T39 | 长文本未截断 | 布局破坏风险 |
|
||
| T42 | 数字列未用 tabular-nums | 数字对齐不整齐 |
|
||
| T58-T60 | 可访问性缺失 | 屏幕阅读器体验差 |
|
||
|
||
### P3 - 低优先级(代码质量)
|
||
|
||
| BUG ID | 问题 | 影响 |
|
||
|--------|------|------|
|
||
| T44-T46 | 组件定义问题 | 可读性差 |
|
||
| T51-T52 | loading.tsx 缺失/冗余 | 用户体验不一致 |
|
||
| T53-T57 | 逻辑与长度问题 | 可维护性 |
|
||
| T61-T64 | 标题层级与风格 | 一致性 |
|
||
|
||
---
|
||
|
||
## 四、推荐改进方案
|
||
|
||
### 4.1 提取共享工具(解决 T16, T18)
|
||
|
||
新建 `src/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
|
||
}
|
||
```
|
||
|
||
所有页面统一 `import { getParam, type SearchParams } from "@/shared/lib/search-params"`。
|
||
|
||
### 4.2 提取共享筛选组件(解决 T19, T31, T32)
|
||
|
||
新建 `src/shared/components/ui/filter-chips.tsx`:
|
||
|
||
```tsx
|
||
import Link from "next/link"
|
||
import { cn } from "@/shared/lib/utils"
|
||
|
||
interface FilterChip {
|
||
id: string
|
||
label: string
|
||
href: string
|
||
active: boolean
|
||
}
|
||
|
||
export function FilterChips({ chips }: { chips: FilterChip[] }) {
|
||
return (
|
||
<div className="flex flex-wrap gap-2">
|
||
{chips.map((c) => (
|
||
<Link
|
||
key={c.id}
|
||
href={c.href}
|
||
className={cn(
|
||
"rounded-md border px-3 py-1.5 text-sm transition-colors",
|
||
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||
c.active
|
||
? "border-primary bg-primary text-primary-foreground"
|
||
: "bg-card hover:bg-accent"
|
||
)}
|
||
>
|
||
{c.label}
|
||
</Link>
|
||
))}
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
### 4.3 统一权限校验模式(解决 T47-T50)
|
||
|
||
所有教师页面入口统一:
|
||
|
||
```typescript
|
||
import { getAuthContext } from "@/shared/lib/auth-guard"
|
||
|
||
export default async function XxxPage() {
|
||
const ctx = await getAuthContext()
|
||
// 使用 ctx.userId、ctx.dataScope 进行数据过滤
|
||
}
|
||
```
|
||
|
||
### 4.4 并行数据获取优化(解决 T20-T28)
|
||
|
||
将串行 `await` 改为 `Promise.all`:
|
||
|
||
```typescript
|
||
// 优化前
|
||
const classes = await getTeacherClasses()
|
||
const records = await getGradeRecords({ ... })
|
||
|
||
// 优化后
|
||
const [classes, records] = await Promise.all([
|
||
getTeacherClasses(),
|
||
getGradeRecords({ ... }),
|
||
])
|
||
```
|
||
|
||
### 4.5 DB 访问下沉到 data-access(解决 T01-T05)
|
||
|
||
在 `modules/school/data-access.ts` 添加:
|
||
|
||
```typescript
|
||
import "server-only"
|
||
import { db } from "@/shared/db"
|
||
import { subjects } from "@/shared/db/schema"
|
||
import { asc } from "drizzle-orm"
|
||
|
||
export async function getSubjectsOrdered(): Promise<Subject[]> {
|
||
return db.query.subjects.findMany({
|
||
orderBy: [asc(subjects.order), asc(subjects.name)],
|
||
})
|
||
}
|
||
```
|
||
|
||
页面改为 `import { getSubjectsOrdered } from "@/modules/school/data-access"`。
|
||
|
||
---
|
||
|
||
## 五、架构图同步建议
|
||
|
||
本次核查未修改源码,无需同步架构图。但建议在后续修复时:
|
||
|
||
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. 若 `modules/school/data-access.ts` 新增 `getSubjectsOrdered`,需在 005 的 `modules.school.dataAccess` 中添加
|
||
|
||
---
|
||
|
||
## 六、总结
|
||
|
||
本次核查覆盖 `src/app/(dashboard)/teacher/` 下全部 45 个前端文件,共发现 **64 个问题**,分布如下:
|
||
|
||
| 严重度 | 数量 | 类别 |
|
||
|--------|------|------|
|
||
| P0 | 9 | 架构违规、权限缺失 |
|
||
| P1 | 16 | TypeScript、性能 |
|
||
| P2 | 18 | 规范、可访问性 |
|
||
| P3 | 21 | 代码质量 |
|
||
|
||
**核心问题**:
|
||
1. **架构层违规严重**:5 处 app 层直接访问 DB,破坏三层架构
|
||
2. **权限校验不一致**:部分页面无校验,部分用 `auth()`,部分用 `getAuthContext()`
|
||
3. **性能 waterfall 普遍**:9 处串行数据获取,应改为并行
|
||
4. **DRY 违规突出**:`getParam` 函数在 16 个文件中重复
|
||
5. **可访问性缺失**:焦点样式、aria-label、skip link 普遍缺失
|
||
|
||
建议按 P0 → P1 → P2 → P3 顺序修复,优先解决架构与安全问题。
|