# `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`(用于 `
`)或 `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` 的 `
` 中,统一 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` - **改进建议**:抽取 `` 组件并用 `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 ( ) } ``` ### UI-03:装饰性元素缺少 `aria-hidden` - **位置**: - `learning/textbooks/[id]/page.tsx:49` — 分隔线 `` - `learning/assignments/page.tsx:98, 139` — `` 分隔点 - `learning/assignments/[assignmentId]/page.tsx:45` — `` - `student-courses-view.tsx:63` — `` - **问题**:屏幕阅读器会朗读"•"或空内容 - **违反规则**:Web Interface Guidelines — Accessibility「Decorative elements need `aria-hidden`」 - **改进建议**:所有装饰性 `` 添加 `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` - **问题**:`` 依赖 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` - **问题**:`` 默认 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-01:005 JSON 中 `/student/grades` 的 dataAccess 记录错误 - **位置**:`docs/architecture/005_architecture_data.json:12047` - **问题**:记录为 `"grades/actions.getStudentGradeSummaryAction"`,实际代码调用 `grades/data-access.getStudentGradeSummary` - **改进建议**:修正为 `"grades/data-access.getStudentGradeSummary"` ### DOC-02:005 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-03:005 JSON 中 `/student/diagnostic` 的 type 错误 - **位置**:`docs/architecture/005_architecture_data.json:12063` - **问题**:记录 `"type": "client"`,实际 `page.tsx` 是 Server Component - **改进建议**:改为 `"type": "server"`,并注明内部 `StudentDiagnosticView` 组件为 client ### DOC-04:005 JSON 中 `/student/dashboard` 缺少 `getStudentSchedule` 记录 - **位置**:`docs/architecture/005_architecture_data.json:11978-11982` - **问题**:实际代码调用了 `getStudentSchedule(student.id)`,但 dataAccess 数组未记录 - **改进建议**:补充 `"classes/data-access.getStudentSchedule"` ### DOC-05:005 JSON 中 `/student/learning/assignments` 缺少 `getDemoStudentUser` 记录 - **位置**:`docs/architecture/005_architecture_data.json:11989-11991` - **问题**:实际代码调用了 `getDemoStudentUser()`,但 dataAccess 数组未记录 - **改进建议**:补充 `"homework/data-access.getDemoStudentUser"`(或迁移后更新为 `users/data-access.getCurrentStudentUser`) ### DOC-06:004 文档 2.26 节未记录 `loading.tsx` 文件 - **位置**:`docs/architecture/004_architecture_impact_map.md:1109-1114` - **问题**:文件清单仅列出 3 个组件,未记录 `app/(dashboard)/student/*/loading.tsx` - **改进建议**:补充 loading.tsx 文件清单 ### DOC-07:004 文档未记录 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**:所有装饰性 `` 添加 `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 (
{children}
) } ``` > 注:`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 (
{a.title} {getStatusLabel(a.progressStatus)}
Due {a.dueAt ? formatDate(a.dueAt) : "-"} Attempts {a.attemptsUsed}/{a.maxAttempts}
Score
{a.latestScore ?? "-"}
) } ``` --- ## 十、验证命令 修复完成后应运行以下命令确保零错误: ```bash npm run lint npx tsc --noEmit npm run test:unit -- student ``` --- > 报告生成人:AI Agent(GLM-5.2) > 核查方法:人工逐行审查 + 架构图比对 + 技能规则匹配 > 应用技能:`vercel-react-best-practices`(性能优化)、`web-artifacts-builder`(界面构建参考)、`web-design-guidelines`(界面规范审查)