# `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 路由组 | 无统一布局,每个页面重复写 `
` 容器 |
| `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
未答题
...
已答题
```
- **规范依据**:项目为 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 行」
- **改进建议**:抽取为 `
` 组件,循环调用
#### BUG-L03:JSX 语法格式错误
- **位置**:`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
()` 类型不优雅
- **位置**:`src/app/(dashboard)/student/learning/assignments/page.tsx:63`
- **问题**:使用 `typeof assignments` 作为 Map 值类型,将类型绑定到变量,可读性差
- **改进建议**:导入显式类型 `new Map()`
---
### 2.3 `learning/textbooks/page.tsx` — 严重度:中
#### BUG-T01:存在注释掉的代码
- **位置**:`src/app/(dashboard)/student/learning/textbooks/page.tsx:42-50`
- **问题**:
```tsx
{/*
Textbooks
Browse your course textbooks.
*/}
```
- **规范依据**:编码规范禁止提交注释掉的代码(应删除或用版本管理)
- **改进建议**:删除注释块
#### BUG-T02:缺少页面标题
- **位置**:`src/app/(dashboard)/student/learning/textbooks/page.tsx`
- **问题**:其他 student 页面都有 `` 标题,本页面因注释掉了标题块,直接渲染 `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:装饰性 `` 缺少 `aria-hidden`
- **位置**:`src/app/(dashboard)/student/learning/textbooks/[id]/page.tsx:49`
- **问题**:
```tsx
```
纯装饰性分隔线,屏幕阅读器会朗读空内容
- **规范依据**: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 页面都有 `` 容器,本页面直接返回 ``,无外层 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-C01:catch 块吞掉错误
- **位置**:`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`(用于 `