Files
NextEdu/bugs/student_bug.md
SpecialX 49291fcc31 refactor: fix all P0/P1/P2 bugs and architecture issues
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.
2026-06-19 05:13:34 +08:00

712 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# `src/app/(dashboard)/student` 前端规范核查报告
> 核查日期2026-06-18
> 核查范围:`src/app/(dashboard)/student/` 目录下所有前端文件(含 `loading.tsx`+ 关联模块组件 `src/modules/student/components/*`
> 依据文档:项目规则、编码规范 `docs/standards/coding-standards.md`、架构影响地图 004、架构数据 005
> 应用技能:`vercel-react-best-practices`、`web-artifacts-builder`、`web-design-guidelines`
---
## 一、核查文件清单
### 1.1 路由页面文件11 个)
| 文件 | 行数 | 类型 | 用途 |
|------|------|------|------|
| [dashboard/page.tsx](../src/app/(dashboard)/student/dashboard/page.tsx) | 88 | Server Component | 学生仪表盘 |
| [dashboard/loading.tsx](../src/app/(dashboard)/student/dashboard/loading.tsx) | 60 | Loading UI | 仪表盘骨架屏 |
| [attendance/page.tsx](../src/app/(dashboard)/student/attendance/page.tsx) | 40 | Server Component | 学生考勤 |
| [diagnostic/page.tsx](../src/app/(dashboard)/student/diagnostic/page.tsx) | 31 | Server Component | 学情诊断 |
| [elective/page.tsx](../src/app/(dashboard)/student/elective/page.tsx) | 49 | Server Component | 选课中心 |
| [grades/page.tsx](../src/app/(dashboard)/student/grades/page.tsx) | 40 | Server Component | 我的成绩 |
| [learning/assignments/page.tsx](../src/app/(dashboard)/student/learning/assignments/page.tsx) | 167 | Server Component | 作业列表 |
| [learning/assignments/[assignmentId]/page.tsx](../src/app/(dashboard)/student/learning/assignments/[assignmentId]/page.tsx) | 53 | Server Component | 作业作答/复习 |
| [learning/courses/page.tsx](../src/app/(dashboard)/student/learning/courses/page.tsx) | 39 | Server Component | 课程列表 |
| [learning/courses/loading.tsx](../src/app/(dashboard)/student/learning/courses/loading.tsx) | 28 | Loading UI | 课程骨架屏 |
| [learning/textbooks/page.tsx](../src/app/(dashboard)/student/learning/textbooks/page.tsx) | 71 | Server Component | 教材列表 |
| [learning/textbooks/[id]/page.tsx](../src/app/(dashboard)/student/learning/textbooks/[id]/page.tsx) | 76 | Server Component | 教材阅读 |
| [schedule/page.tsx](../src/app/(dashboard)/student/schedule/page.tsx) | 54 | Server Component | 课表 |
| [schedule/loading.tsx](../src/app/(dashboard)/student/schedule/loading.tsx) | 31 | Loading UI | 课表骨架屏 |
### 1.2 关联模块组件3 个)
| 文件 | 行数 | 类型 | 用途 |
|------|------|------|------|
| [student-courses-view.tsx](../src/modules/student/components/student-courses-view.tsx) | 156 | Client Component | 课程视图 + 加入班级表单 |
| [student-schedule-filters.tsx](../src/modules/student/components/student-schedule-filters.tsx) | 32 | Client Component | 课表筛选器 |
| [student-schedule-view.tsx](../src/modules/student/components/student-schedule-view.tsx) | 88 | Server Component | 课表视图 |
### 1.3 缺失文件
| 缺失类型 | 影响范围 | 说明 |
|---------|---------|------|
| `layout.tsx` | 整个 student 路由组 | 无统一布局,每个页面重复写 `<div className="... p-8">` 容器 |
| `error.tsx` | 整个 student 路由组 | 无错误边界Server Component 抛错时显示 Next.js 默认错误页 |
| `loading.tsx` | attendance / diagnostic / elective / grades / learning/assignments / learning/assignments/[assignmentId] / learning/textbooks / learning/textbooks/[id] | 8 个路由无骨架屏,跳转时白屏 |
| `not-found.tsx` | 整个 student 路由组 | `notFound()` 调用时显示 Next.js 默认 404 |
---
## 二、违规问题清单
### 2.1 认证模式严重不一致 — 严重度:高
#### BUG-A01三种认证模式混用
- **位置**:整个 student 目录
- **问题**同一角色student的页面使用了 3 种不同的认证方式:
| 模式 | 使用页面 | 问题 |
|------|---------|------|
| `getAuthContext()` from `@/shared/lib/auth-guard` | attendance / diagnostic / grades | ✅ 推荐 |
| `auth()` from `@/auth` | elective | ⚠️ 直接调用 auth(),绕过 auth-guard 抽象 |
| `getDemoStudentUser()` from `@/modules/homework/data-access` | dashboard / learning/* / schedule | ⚠️ 依赖 homework 模块获取用户身份 |
- **规范依据**:编码规范 8.3「统一通过 `getAuthContext()` / `requirePermission()` 获取会话」
- **影响**
1. `elective/page.tsx` 直接 `import { auth } from "@/auth"`违反分层规则app 层不应直接依赖根模块 `@/auth`,应通过 `shared/lib/auth-guard`
2. `getDemoStudentUser()` 名为 "Demo" 实为真实查询,命名误导
3. `getDemoStudentUser()` 定义在 `homework/data-access.ts`,导致 6 个 student 页面为获取用户身份而依赖 homework 模块,违反模块封装
- **改进建议**
1.`getDemoStudentUser` 迁移到 `users/data-access.ts` 并重命名为 `getCurrentStudentUser()``getStudentSession()`
2. 所有 student 页面统一使用 `getAuthContext()` 获取 `ctx.userId`,再按需查询学生信息
3. 移除 `elective/page.tsx` 中的 `import { auth } from "@/auth"`
#### BUG-A02`getDemoStudentUser` 命名误导
- **位置**`src/modules/homework/data-access.ts:475`
- **问题**:函数名包含 "Demo",但实际执行真实 DB 查询JOIN users + usersToRoles + roles WHERE roles.name = "student"),并非演示用桩函数
- **影响**:开发者看到 "Demo" 会误以为是临时实现,不敢用于生产;或误以为返回固定演示数据
- **改进建议**:重命名为 `getCurrentStudentUser()``resolveCurrentStudent()`
#### BUG-A03`getDemoStudentUser` 放置在错误的模块
- **位置**`src/modules/homework/data-access.ts:475-490`
- **问题**:学生身份查询属于 `users` 模块职责,却放在 `homework` 模块
- **规范依据**:项目规则「模块标准结构」+ 编码规范 2.2「模块封装」
- **影响**`dashboard``learning/assignments``learning/courses``learning/textbooks``learning/textbooks/[id]``schedule` 6 个页面为获取用户身份而 `import` homework 模块,造成虚假依赖
- **改进建议**:迁移到 `src/modules/users/data-access.ts`,导出为 `getCurrentStudentUser()`
---
### 2.2 `learning/assignments/page.tsx` — 严重度:高
#### BUG-L01中英文混排
- **位置**`src/app/(dashboard)/student/learning/assignments/page.tsx:81, 122`
- **问题**:在英文 UI 中插入中文标签
```tsx
<div className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">未答题</div>
...
<div className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">已答题</div>
```
- **规范依据**:项目为 K12 中文教务系统,但本页面其余文案均为英文("Due"、"Attempts"、"Score"、"Start"、"Continue"),中英文混排显得不专业
- **改进建议**:统一为英文 "Pending" / "Completed",或整页改为中文
#### BUG-L02卡片渲染逻辑重复
- **位置**`src/app/(dashboard)/student/learning/assignments/page.tsx:83-116` 与 `124-157`
- **问题**:未答题区和已答题区的卡片 JSX 完全相同34 行 × 2 = 68 行重复),仅数据源不同
- **规范依据**编码规范「DRY 原则」+ 项目规则「单文件行数建议 ≤ 500 行」
- **改进建议**:抽取为 `<AssignmentCard assignment={a} />` 组件,循环调用
#### BUG-L03JSX 语法格式错误
- **位置**`src/app/(dashboard)/student/learning/assignments/page.tsx:162`
- **问题**:行尾 `})}` 多了一个 `)`
```tsx
)})}
```
应为:
```tsx
))}
```
- **影响**:虽然能编译通过(因外层有 `(`但格式混乱IDE 自动格式化后会变化,造成 git diff 噪音
- **改进建议**:修正为 `))}`
#### BUG-L04`getStatusVariant` 对 submitted 和 in_progress 返回相同值
- **位置**`src/app/(dashboard)/student/learning/assignments/page.tsx:13-18`
- **问题**
```tsx
if (status === "submitted") return "secondary"
if (status === "in_progress") return "secondary"
```
两种状态视觉上无法区分,学生无法快速辨别「已提交待批改」和「进行中」
- **改进建议**`in_progress` 改为 `"outline"` 或新增 `"in_progress"` 专属样式
#### BUG-L05状态判断函数参数类型过宽
- **位置**`src/app/(dashboard)/student/learning/assignments/page.tsx:13, 20, 27, 34, 39`
- **问题**`getStatusVariant(status: string)` 等函数参数为 `string`,但 `a.progressStatus` 实际类型是 `StudentHomeworkProgressStatus` 联合类型
- **规范依据**:编码规范 4.2「优先使用精确类型」
- **改进建议**:参数类型改为 `StudentHomeworkProgressStatus`,并在 switch 中穷举
#### BUG-L06`new Map<string, typeof assignments>()` 类型不优雅
- **位置**`src/app/(dashboard)/student/learning/assignments/page.tsx:63`
- **问题**:使用 `typeof assignments` 作为 Map 值类型,将类型绑定到变量,可读性差
- **改进建议**:导入显式类型 `new Map<string, StudentHomeworkAssignment[]>()`
---
### 2.3 `learning/textbooks/page.tsx` — 严重度:中
#### BUG-T01存在注释掉的代码
- **位置**`src/app/(dashboard)/student/learning/textbooks/page.tsx:42-50`
- **问题**
```tsx
{/* <div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div>
<h2 className="text-2xl font-bold tracking-tight">Textbooks</h2>
<p className="text-muted-foreground">Browse your course textbooks.</p>
</div>
<Button asChild variant="outline">
<Link href="/student/dashboard">Back</Link>
</Button>
</div> */}
```
- **规范依据**:编码规范禁止提交注释掉的代码(应删除或用版本管理)
- **改进建议**:删除注释块
#### BUG-T02缺少页面标题
- **位置**`src/app/(dashboard)/student/learning/textbooks/page.tsx`
- **问题**:其他 student 页面都有 `<h2 className="text-2xl font-bold tracking-tight">` 标题,本页面因注释掉了标题块,直接渲染 `TextbookFilters`,与其他页面不一致
- **改进建议**:恢复标题(不带"Back"按钮),保持视觉一致性
#### BUG-T03`student` 变量未真正使用
- **位置**`src/app/(dashboard)/student/learning/textbooks/page.tsx:23-31`
- **问题**`student` 仅用于 "No user" 检查,`getTextbooks(q, subject, grade)` 不依赖学生身份
- **影响**:任何登录学生都能浏览所有教材,无年级/科目过滤;如设计如此,则 `student` 检查可简化为 `getAuthContext()` 仅做认证
- **改进建议**:若教材对所有学生开放,改用 `getAuthContext()` 仅做认证;若应按年级过滤,则 `getTextbooks` 增加 `grade` 参数
---
### 2.4 `learning/textbooks/[id]/page.tsx` — 严重度:中
#### BUG-TD01`student` 变量未使用
- **位置**`src/app/(dashboard)/student/learning/textbooks/[id]/page.tsx:18-31`
- **问题**:同 BUG-T03`student` 仅用于 "No user" 检查,教材内容查询不依赖学生身份
- **改进建议**:同 BUG-T03
#### BUG-TD02错误处理不一致
- **位置**`src/app/(dashboard)/student/learning/textbooks/[id]/page.tsx:19, 41`
- **问题**
- `if (!student)` 返回完整 `EmptyState` 页面
- `if (!textbook) notFound()` 抛出 404
同一文件内两种错误处理模式
- **改进建议**:统一为 `notFound()` 或都返回 `EmptyState`
#### BUG-TD03装饰性 `<span>` 缺少 `aria-hidden`
- **位置**`src/app/(dashboard)/student/learning/textbooks/[id]/page.tsx:49`
- **问题**
```tsx
<span className="hidden sm:inline-block w-px h-4 bg-border" />
```
纯装饰性分隔线,屏幕阅读器会朗读空内容
- **规范依据**Web Interface Guidelines — Accessibility「装饰性元素应 `aria-hidden`」
- **改进建议**:添加 `aria-hidden="true"`
---
### 2.5 `dashboard/page.tsx` — 严重度:中
#### BUG-D01`as` 类型断言违规
- **位置**`src/app/(dashboard)/student/dashboard/page.tsx:11`
- **问题**
```tsx
return (day === 0 ? 7 : day) as 1 | 2 | 3 | 4 | 5 | 6 | 7
```
- **规范依据**:编码规范 4.2.3「禁止 `as` 断言(除非从 `unknown` 转换)」
- **改进建议**:使用类型守卫或重写为:
```tsx
const toWeekday = (d: Date): 1 | 2 | 3 | 4 | 5 | 6 | 7 => {
const day = d.getDay()
const weekday = day === 0 ? 7 : day
if (weekday < 1 || weekday > 7) throw new Error(`Invalid weekday: ${weekday}`)
return weekday
}
```
#### BUG-D02缺少页面标题容器
- **位置**`src/app/(dashboard)/student/dashboard/page.tsx:76-87`
- **问题**:其他 student 页面都有 `<div className="... p-8"><div><h2>...</h2><p>...</p></div>...</div>` 容器,本页面直接返回 `<StudentDashboard />`,无外层 padding 和标题
- **影响**:视觉上与其他页面不一致(无 padding无标题
- **改进建议**:添加统一的页面容器和标题,或将容器抽到 `layout.tsx`
#### BUG-D03`EmptyState` 的 `icon` 使用 `Inbox` 不合适
- **位置**`src/app/(dashboard)/student/dashboard/page.tsx:5, 22`
- **问题**:用户未找到时显示 `Inbox` 图标,语义不匹配
- **改进建议**:使用 `UserX` 或 `UserMinus` 图标
---
### 2.6 `schedule/page.tsx` — 严重度:低
#### BUG-S01嵌套三元表达式可读性差
- **位置**`src/app/(dashboard)/student/schedule/page.tsx:38`
- **问题**
```tsx
const classId = typeof classIdParam === "string" ? classIdParam : Array.isArray(classIdParam) ? classIdParam[0] : "all"
```
- **规范依据**:编码规范「避免嵌套三元」
- **改进建议**:抽为独立函数或使用 if 语句
#### BUG-S02`searchParams` 类型未共享
- **位置**`src/app/(dashboard)/student/schedule/page.tsx:11` 和 `learning/textbooks/page.tsx:11`
- **问题**:两处定义相同的 `type SearchParams = { [key: string]: string | string[] | undefined }`
- **改进建议**:抽取到 `shared/types` 或 `shared/lib/utils`
---
### 2.7 `elective/page.tsx` — 严重度:中
#### BUG-E01直接调用 `auth()` 绕过 auth-guard
- **位置**`src/app/(dashboard)/student/elective/page.tsx:1, 11`
- **问题**
```tsx
import { auth } from "@/auth"
...
const session = await auth()
const studentId = String(session?.user?.id ?? "")
```
- **规范依据**项目规则「app 层不应直接依赖 `@/auth`」+ 编码规范 8.3
- **改进建议**:改用 `getAuthContext()`
```tsx
const ctx = await getAuthContext()
const studentId = ctx.userId
```
#### BUG-E02`String(... ?? "")` 模式冗余
- **位置**`src/app/(dashboard)/student/elective/page.tsx:12`
- **问题**`String(session?.user?.id ?? "")` — `session.user.id` 已是 string 类型,`String()` 包裹多余
- **改进建议**:使用 `getAuthContext()` 后直接用 `ctx.userId`
---
### 2.8 `student-courses-view.tsx` — 严重度:中
#### BUG-C01catch 块吞掉错误
- **位置**`src/modules/student/components/student-courses-view.tsx:39-41`
- **问题**
```tsx
} catch {
toast.error("Failed to join class")
}
```
错误被吞掉,无 `console.error` 记录,调试困难
- **规范依据**:编码规范 9.2「错误必须可观测」
- **改进建议**
```tsx
} catch (err) {
console.error("[joinClass] failed:", err)
toast.error("Failed to join class")
}
```
#### BUG-C02未使用 `useFormStatus` / `useTransition`
- **位置**`src/modules/student/components/student-courses-view.tsx:26-44`
- **问题**:使用本地 `useState` + `setIsWorking` 管理 loading 状态,但 Next.js 16 推荐 `useFormStatus`(用于 `<form action>`)或 `useTransition`
- **违反规则**`rerender-transitions`、`rendering-usetransition-loading`
- **改进建议**:使用 `useTransition` 包裹 Server Action 调用
#### BUG-C03表单缺少客户端格式校验
- **位置**`src/modules/student/components/student-courses-view.tsx:136-148`
- **问题**`maxLength={6}` 和 `inputMode="numeric"` 不阻止字母输入,用户可提交 "abcdef"
- **改进建议**:添加 `pattern="\d{6}"` 或 `onChange` 过滤非数字字符
---
### 2.9 `student-schedule-view.tsx` — 严重度:低
#### BUG-SV01`for...of` 修改 Map 后再次 set
- **位置**`src/modules/student/components/student-schedule-view.tsx:36-39`
- **问题**
```tsx
for (const [day, list] of itemsByDay) {
list.sort((a, b) => a.startTime.localeCompare(b.startTime))
itemsByDay.set(day, list) // 多余sort 已 in-place
}
```
`list.sort()` 已原地排序,`itemsByDay.set(day, list)` 多余
- **改进建议**:删除 `itemsByDay.set(day, list)` 行
---
### 2.10 跨文件一致性 — 严重度:中
#### BUG-X01页面容器 className 不统一
- **位置**:多个页面
- **问题**:存在 4 种不同的容器写法:
| className | 使用页面 |
|-----------|---------|
| `h-full flex-1 flex-col space-y-8 p-8 md:flex` | attendance / diagnostic / grades / learning/textbooks / learning/textbooks/[id] |
| `flex h-full flex-col space-y-8 p-8` | elective / learning/courses / schedule |
| `flex h-full flex-col space-y-4 p-6` | learning/assignments/[assignmentId] |
| `h-full flex-1 flex-col space-y-8 p-8 md:flex` | learning/assignments |
| 无容器 | dashboard |
- **改进建议**:抽取到 `student/layout.tsx` 的 `<main>` 中,统一 padding 和布局
#### BUG-X02"No user" 处理方式不一致
- **位置**:多个页面
- **问题**
- `dashboard`、`learning/courses`、`learning/textbooks`、`learning/textbooks/[id]`、`schedule`、`learning/assignments`、`elective`:返回 `EmptyState`
- `learning/assignments/[assignmentId]`:调用 `notFound()`
- `attendance`、`grades`:返回 `EmptyState`(但检查的是 `summary` 而非 `student`
- **改进建议**:未认证场景应由 `proxy.ts` 拦截,页面内统一用 `notFound()` 或统一 `EmptyState`
#### BUG-X03图标选择不一致
- **位置**:多个页面
- **问题**:同样 "No user found" 场景使用不同图标:
- `dashboard`、`learning/courses`、`learning/textbooks`、`schedule`、`learning/assignments``Inbox`
- `attendance``CalendarCheck`
- `grades``GraduationCap`
- `elective``Inbox`
- **改进建议**:未认证场景统一使用 `UserX`;空数据场景使用业务相关图标
---
## 三、React 性能优化(应用 `vercel-react-best-practices` 技能)
### PERF-01`student-courses-view.tsx` 未使用 `useTransition`
- **位置**`src/modules/student/components/student-courses-view.tsx:28-44`
- **问题**Server Action `joinClassByInvitationCodeAction` 用 `useState` 管理 loading阻塞 UI 线程
- **违反规则**`rerender-transitions`、`rendering-usetransition-loading`
- **改进建议**
```tsx
const [isPending, startTransition] = useTransition()
const handleJoin = async (formData: FormData) => {
startTransition(async () => {
const res = await joinClassByInvitationCodeAction(null, formData)
...
})
}
```
### PERF-02`student-courses-view.tsx` 卡片列表未 memoize
- **位置**`src/modules/student/components/student-courses-view.tsx:50-108`
- **问题**`classes.map(...)` 每次渲染都重新创建 6+ 个 Card 元素,当 `code` 输入变化时所有卡片重渲染
- **违反规则**`rerender-memo`、`rerender-no-inline-components`
- **改进建议**:抽取 `<ClassCard class={c} />` 组件并用 `React.memo` 包裹
### PERF-03`student-schedule-filters.tsx` 的 `options` 依赖 `classes` 引用
- **位置**`src/modules/student/components/student-schedule-filters.tsx:12`
- **问题**`useMemo(() => [...classes], [classes])` — 父组件每次传入新 `classes` 数组引用时都重新计算
- **违反规则**`rerender-dependencies`
- **现状**:已用 `useMemo`,但依赖项是父组件传入的 prop若父组件不 memoize 则失效
- **改进建议**:可接受,但建议父组件 `schedule/page.tsx` 用 `React.cache` 或在 Server Component 层面稳定引用
### PERF-04`dashboard/page.tsx` 多次 `filter` 遍历同一数组
- **位置**`src/app/(dashboard)/student/dashboard/page.tsx:40-52`
- **问题**
```tsx
const dueSoonCount = assignments.filter(...).length
const overdueCount = assignments.filter(...).length
const gradedCount = assignments.filter(...).length
```
3 次遍历同一数组O(3n)
- **违反规则**`js-combine-iterations`
- **改进建议**:单次遍历统计:
```tsx
let dueSoonCount = 0, overdueCount = 0, gradedCount = 0
for (const a of assignments) {
if (a.progressStatus === "graded") { gradedCount++; continue }
if (!a.dueAt) continue
const due = new Date(a.dueAt)
if (due >= now && due <= in7Days) dueSoonCount++
else if (due < now) overdueCount++
}
```
### PERF-05`learning/assignments/page.tsx` 重复 `filter` 调用
- **位置**`src/app/(dashboard)/student/learning/assignments/page.tsx:73-74`
- **问题**
```tsx
const answeredItems = items.filter((a) => isAnswered(a.progressStatus))
const unansweredItems = items.filter((a) => !isAnswered(a.progressStatus))
```
2 次遍历同一数组
- **违反规则**`js-combine-iterations`
- **改进建议**:单次遍历分桶:
```tsx
const { answered, unanswered } = items.reduce(
(acc, a) => {
isAnswered(a.progressStatus) ? acc.answered.push(a) : acc.unanswered.push(a)
return acc
},
{ answered: [], unanswered: [] }
)
```
### PERF-06`student-schedule-view.tsx` 在渲染期构建 Map
- **位置**`src/modules/student/components/student-schedule-view.tsx:30-39`
- **问题**:每次渲染都重建 `itemsByDay` Map 并排序
- **现状**:作为 Server Component 每次请求只渲染一次,影响较小
- **违反规则**`js-cache-function-results`(轻度)
- **改进建议**:可接受,若未来改为 Client Component 则需 `useMemo`
### PERF-07`dashboard/page.tsx` 已正确使用 `Promise.all` 并行获取
- **位置**`src/app/(dashboard)/student/dashboard/page.tsx:29-34`
- **现状**:✅ 符合 `async-parallel` 规则
- **说明**4 个独立查询并行执行,无需优化
### PERF-08`diagnostic/page.tsx` 已正确使用 `Promise.all`
- **位置**`src/app/(dashboard)/student/diagnostic/page.tsx:12-15`
- **现状**:✅ 符合 `async-parallel` 规则
### PERF-09`schedule/page.tsx` 已正确使用 `Promise.all`
- **位置**`src/app/(dashboard)/student/schedule/page.tsx:31-35`
- **现状**:✅ 符合 `async-parallel` 规则
---
## 四、Web 界面规范审查(应用 `web-design-guidelines` 技能)
### UI-01缺少 `loading.tsx` 导致白屏
- **位置**attendance / diagnostic / elective / grades / learning/assignments / learning/assignments/[assignmentId] / learning/textbooks / learning/textbooks/[id]
- **问题**8 个路由无骨架屏,跳转时显示白屏,违反 "Perceived Performance" 原则
- **违反规则**Web Interface Guidelines — Perceived Performance「Always show loading state」
- **改进建议**:为每个缺失的路由添加 `loading.tsx`,参考已有的 `dashboard/loading.tsx` 模式
### UI-02缺少 `error.tsx` 错误边界
- **位置**:整个 student 路由组
- **问题**Server Component 抛错时显示 Next.js 默认错误页,无法"恢复"
- **违反规则**Web Interface Guidelines — Error Handling「Provide recoverable error states」
- **改进建议**:在 `student/error.tsx` 中提供"重试"按钮:
```tsx
"use client"
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return (
<EmptyState
title="Something went wrong"
description={error.message}
action={{ label: "Try again", onClick: reset }}
/>
)
}
```
### UI-03装饰性元素缺少 `aria-hidden`
- **位置**
- `learning/textbooks/[id]/page.tsx:49` — 分隔线 `<span>`
- `learning/assignments/page.tsx:98, 139` — `<span className="px-2">•</span>` 分隔点
- `learning/assignments/[assignmentId]/page.tsx:45` — `<span className="mx-2">•</span>`
- `student-courses-view.tsx:63` — `<span>•</span>`
- **问题**:屏幕阅读器会朗读"•"或空内容
- **违反规则**Web Interface Guidelines — Accessibility「Decorative elements need `aria-hidden`」
- **改进建议**:所有装饰性 `<span>` 添加 `aria-hidden="true"`
### UI-04图标缺少 `aria-hidden` 或 `aria-label`
- **位置**:所有页面使用的 lucide-react 图标
- **问题**lucide 图标默认 `aria-hidden="true"`,但部分图标作为唯一内容(如按钮内仅图标)时需 `aria-label`
- **现状**:本目录内图标均伴随文字,符合规范 ✅
### UI-05表单缺少 `noValidate` 和客户端校验
- **位置**`student-courses-view.tsx:133`
- **问题**`<form action={handleJoin}>` 依赖 HTML5 原生校验(`required`),但未添加数字格式校验
- **违反规则**Web Interface Guidelines — Forms「Validate on client before submit」
- **改进建议**:添加 `pattern="\d{6}"` 或自定义校验
### UI-06链接缺少 `prefetch` 控制
- **位置**`learning/assignments/page.tsx:88, 110, 129, 151`、`student-courses-view.tsx:94, 100`
- **问题**`<Link href="/student/learning/assignments/...">` 默认 prefetch但作业详情页数据量大可能浪费带宽
- **违反规则**`bundle-preload`(轻度)
- **改进建议**:对详情页链接添加 `prefetch={false}` 或在 hover 时预加载
### UI-07`hover:shadow-md` 可能导致性能问题
- **位置**`learning/assignments/page.tsx:84, 125`、`student-courses-view.tsx:51`
- **问题**:大量卡片同时绑定 `hover:shadow-md`,低端设备滚动时可能卡顿
- **违反规则**`rendering-content-visibility`(轻度)
- **改进建议**:长列表添加 `content-visibility: auto` 或使用 `will-change: shadow`
### UI-08颜色对比度待验证
- **位置**`text-muted-foreground` 在多处用于次要信息
- **问题**`text-muted-foreground` 在浅色模式下对比度可能不足 WCAG AA 标准4.5:1
- **违反规则**Web Interface Guidelines — Accessibility「Color contrast WCAG AA」
- **改进建议**:使用工具验证 `--muted-foreground` 与 `--background` 的对比度
### UI-09`tabular-nums` 使用正确 ✅
- **位置**`learning/assignments/page.tsx:107, 148`、`student-schedule-view.tsx:64`
- **现状**:数字列使用 `tabular-nums`,对齐良好 ✅
### UI-10响应式断点一致 ✅
- **位置**:所有页面
- **现状**:统一使用 `md:`、`lg:`、`xl:` 断点,符合规范 ✅
---
## 五、架构文档同步问题
### DOC-01005 JSON 中 `/student/grades` 的 dataAccess 记录错误
- **位置**`docs/architecture/005_architecture_data.json:12047`
- **问题**:记录为 `"grades/actions.getStudentGradeSummaryAction"`,实际代码调用 `grades/data-access.getStudentGradeSummary`
- **改进建议**:修正为 `"grades/data-access.getStudentGradeSummary"`
### DOC-02005 JSON 中 `/student/learning/textbooks/[id]` 的 type 错误
- **位置**`docs/architecture/005_architecture_data.json:12024`
- **问题**:记录 `"type": "client"`,实际 `page.tsx` 是 Server Component无 `"use client"`
- **改进建议**:改为 `"type": "server"`,并注明内部 `TextbookReader` 组件为 client
### DOC-03005 JSON 中 `/student/diagnostic` 的 type 错误
- **位置**`docs/architecture/005_architecture_data.json:12063`
- **问题**:记录 `"type": "client"`,实际 `page.tsx` 是 Server Component
- **改进建议**:改为 `"type": "server"`,并注明内部 `StudentDiagnosticView` 组件为 client
### DOC-04005 JSON 中 `/student/dashboard` 缺少 `getStudentSchedule` 记录
- **位置**`docs/architecture/005_architecture_data.json:11978-11982`
- **问题**:实际代码调用了 `getStudentSchedule(student.id)`,但 dataAccess 数组未记录
- **改进建议**:补充 `"classes/data-access.getStudentSchedule"`
### DOC-05005 JSON 中 `/student/learning/assignments` 缺少 `getDemoStudentUser` 记录
- **位置**`docs/architecture/005_architecture_data.json:11989-11991`
- **问题**:实际代码调用了 `getDemoStudentUser()`,但 dataAccess 数组未记录
- **改进建议**:补充 `"homework/data-access.getDemoStudentUser"`(或迁移后更新为 `users/data-access.getCurrentStudentUser`
### DOC-06004 文档 2.26 节未记录 `loading.tsx` 文件
- **位置**`docs/architecture/004_architecture_impact_map.md:1109-1114`
- **问题**:文件清单仅列出 3 个组件,未记录 `app/(dashboard)/student/*/loading.tsx`
- **改进建议**:补充 loading.tsx 文件清单
### DOC-07004 文档未记录 student 路由的认证模式不一致问题
- **位置**`docs/architecture/004_architecture_impact_map.md:1095-1115`
- **问题**2.26 节"已知问题"未提及三种认证模式混用
- **改进建议**:在"已知问题"中补充 P1 项「认证模式不一致」
---
## 六、问题汇总统计
| 严重度 | 数量 | 问题编号 |
|--------|------|----------|
| 高 | 4 | BUG-A01, BUG-A02, BUG-A03, BUG-L01 |
| 中 | 10 | BUG-L02, BUG-L04, BUG-L05, BUG-T01, BUG-T02, BUG-T03, BUG-TD01, BUG-TD02, BUG-D01, BUG-D02, BUG-E01, BUG-C01, BUG-X01, BUG-X02, BUG-X03 |
| 低 | 6 | BUG-L03, BUG-L06, BUG-D03, BUG-S01, BUG-S02, BUG-E02, BUG-C02, BUG-C03, BUG-SV01 |
| 性能 | 6 | PERF-01, PERF-02, PERF-03, PERF-04, PERF-05, PERF-06 |
| 界面 | 8 | UI-01, UI-02, UI-03, UI-05, UI-06, UI-07, UI-08, UI-09 |
| 文档 | 7 | DOC-01 ~ DOC-07 |
| **合计** | **41** | |
---
## 七、修复优先级建议
### P0立即修复 — 影响正确性和架构合规性)
1. **BUG-A01 + BUG-A02 + BUG-A03**:将 `getDemoStudentUser` 迁移到 `users/data-access.ts` 并重命名为 `getCurrentStudentUser()`,所有 student 页面统一使用 `getAuthContext()` 或新函数
2. **BUG-E01**`elective/page.tsx` 改用 `getAuthContext()`,移除 `import { auth } from "@/auth"`
3. **BUG-L01**`learning/assignments/page.tsx` 中英文混排修正
4. **BUG-L03**`learning/assignments/page.tsx:162` JSX 语法格式错误修正
5. **BUG-T01**`learning/textbooks/page.tsx` 删除注释代码
### P1本迭代修复 — 影响可维护性和一致性)
6. **BUG-X01**:创建 `student/layout.tsx` 统一页面容器
7. **UI-01**:为 8 个缺失路由添加 `loading.tsx`
8. **UI-02**:创建 `student/error.tsx` 错误边界
9. **BUG-L02**:抽取 `AssignmentCard` 组件消除重复
10. **BUG-D01**:移除 `as` 类型断言
11. **BUG-L04**`getStatusVariant` 区分 submitted / in_progress
12. **BUG-X02 + BUG-X03**:统一 "No user" 处理和图标选择
13. **PERF-01 + PERF-02**`student-courses-view.tsx` 使用 `useTransition` + memoize 卡片
14. **PERF-04 + PERF-05**:合并重复 `filter` 遍历
### P2下迭代修复 — 增强健壮性)
15. **BUG-L05 + BUG-L06**:类型精确化
16. **BUG-T02 + BUG-D02**:补全页面标题
17. **BUG-TD01 + BUG-TD03**:清理未使用变量 + `aria-hidden`
18. **UI-03**:所有装饰性 `<span>` 添加 `aria-hidden`
19. **UI-05**:表单客户端校验
20. **BUG-C01 + BUG-C02 + BUG-C03**`student-courses-view.tsx` 错误处理 + 表单优化
21. **BUG-S01 + BUG-S02 + BUG-SV01**:小重构
### P3文档同步
22. **DOC-01 ~ DOC-07**:同步 004 和 005 架构文档
---
## 八、`student/layout.tsx` 推荐实现
```tsx
import type { ReactNode } from "react"
export default function StudentLayout({ children }: { children: ReactNode }) {
return (
<div className="h-full flex-1 flex-col space-y-8 p-8 md:flex">
{children}
</div>
)
}
```
> 注:`dashboard/page.tsx` 和 `learning/assignments/[assignmentId]/page.tsx` 若需不同 padding可在页面内覆盖。
---
## 九、`AssignmentCard` 组件推荐实现(消除 BUG-L02 重复)
```tsx
import Link from "next/link"
import { Badge } from "@/shared/components/ui/badge"
import { Button } from "@/shared/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card"
import { formatDate } from "@/shared/lib/utils"
import type { StudentHomeworkAssignment } from "@/modules/homework/types"
const getStatusVariant = (status: StudentHomeworkProgressStatus): "default" | "secondary" | "outline" => {
switch (status) {
case "graded": return "default"
case "submitted": return "secondary"
case "in_progress": return "outline" // BUG-L04 修复:区分 in_progress
default: return "outline"
}
}
export function AssignmentCard({ assignment: a }: { assignment: StudentHomeworkAssignment }) {
return (
<Card className="flex h-full flex-col overflow-hidden transition-all hover:shadow-md">
<CardHeader className="gap-2 pb-3">
<div className="flex items-start justify-between gap-3">
<CardTitle className="text-base">
<Link href={`/student/learning/assignments/${a.id}`} className="hover:underline">
{a.title}
</Link>
</CardTitle>
<Badge variant={getStatusVariant(a.progressStatus)} className="capitalize">
{getStatusLabel(a.progressStatus)}
</Badge>
</div>
<div className="text-xs text-muted-foreground">
<span>Due {a.dueAt ? formatDate(a.dueAt) : "-"}</span>
<span className="px-2" aria-hidden="true">•</span>
<span>Attempts {a.attemptsUsed}/{a.maxAttempts}</span>
</div>
</CardHeader>
<CardContent className="mt-auto flex items-center justify-between">
<div className="text-sm">
<div className="text-muted-foreground">Score</div>
<div className="font-medium tabular-nums">{a.latestScore ?? "-"}</div>
</div>
<Button asChild size="sm" variant={getActionVariant(a.progressStatus)}>
<Link href={`/student/learning/assignments/${a.id}`}>
{getActionLabel(a.progressStatus)}
</Link>
</Button>
</CardContent>
</Card>
)
}
```
---
## 十、验证命令
修复完成后应运行以下命令确保零错误:
```bash
npm run lint
npx tsc --noEmit
npm run test:unit -- student
```
---
> 报告生成人AI AgentGLM-5.2
> 核查方法:人工逐行审查 + 架构图比对 + 技能规则匹配
> 应用技能:`vercel-react-best-practices`(性能优化)、`web-artifacts-builder`(界面构建参考)、`web-design-guidelines`(界面规范审查)