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.
34 KiB
34 KiB
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 | 88 | Server Component | 学生仪表盘 |
| dashboard/loading.tsx | 60 | Loading UI | 仪表盘骨架屏 |
| attendance/page.tsx | 40 | Server Component | 学生考勤 |
| diagnostic/page.tsx | 31 | Server Component | 学情诊断 |
| elective/page.tsx | 49 | Server Component | 选课中心 |
| grades/page.tsx | 40 | Server Component | 我的成绩 |
| learning/assignments/page.tsx | 167 | Server Component | 作业列表 |
| learning/assignments/[assignmentId]/page.tsx | 53 | Server Component | 作业作答/复习 |
| learning/courses/page.tsx | 39 | Server Component | 课程列表 |
| learning/courses/loading.tsx | 28 | Loading UI | 课程骨架屏 |
| learning/textbooks/page.tsx | 71 | Server Component | 教材列表 |
| learning/textbooks/[id]/page.tsx | 76 | Server Component | 教材阅读 |
| schedule/page.tsx | 54 | Server Component | 课表 |
| schedule/loading.tsx | 31 | Loading UI | 课表骨架屏 |
1.2 关联模块组件(3 个)
| 文件 | 行数 | 类型 | 用途 |
|---|---|---|---|
| student-courses-view.tsx | 156 | Client Component | 课程视图 + 加入班级表单 |
| student-schedule-filters.tsx | 32 | Client Component | 课表筛选器 |
| 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-guardattendance / diagnostic / grades ✅ 推荐 auth()from@/authelective ⚠️ 直接调用 auth(),绕过 auth-guard 抽象 getDemoStudentUser()from@/modules/homework/data-accessdashboard / learning/* / schedule ⚠️ 依赖 homework 模块获取用户身份 -
规范依据:编码规范 8.3「统一通过
getAuthContext()/requirePermission()获取会话」 -
影响:
elective/page.tsx直接import { auth } from "@/auth",违反分层规则(app 层不应直接依赖根模块@/auth,应通过shared/lib/auth-guard)getDemoStudentUser()名为 "Demo" 实为真实查询,命名误导getDemoStudentUser()定义在homework/data-access.ts,导致 6 个 student 页面为获取用户身份而依赖 homework 模块,违反模块封装
-
改进建议:
- 将
getDemoStudentUser迁移到users/data-access.ts并重命名为getCurrentStudentUser()或getStudentSession() - 所有 student 页面统一使用
getAuthContext()获取ctx.userId,再按需查询学生信息 - 移除
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]、schedule6 个页面为获取用户身份而importhomework 模块,造成虚假依赖 - 改进建议:迁移到
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 中插入中文标签
<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-L03:JSX 语法格式错误
- 位置:
src/app/(dashboard)/student/learning/assignments/page.tsx:162 - 问题:行尾
})}多了一个)应为:)})}))} - 影响:虽然能编译通过(因外层有
(),但格式混乱,IDE 自动格式化后会变化,造成 git diff 噪音 - 改进建议:修正为
))}
BUG-L04:getStatusVariant 对 submitted 和 in_progress 返回相同值
- 位置:
src/app/(dashboard)/student/learning/assignments/page.tsx:13-18 - 问题:
两种状态视觉上无法区分,学生无法快速辨别「已提交待批改」和「进行中」
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 - 问题:
{/* <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 - 问题:
纯装饰性分隔线,屏幕阅读器会朗读空内容
<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 - 问题:
return (day === 0 ? 7 : day) as 1 | 2 | 3 | 4 | 5 | 6 | 7 - 规范依据:编码规范 4.2.3「禁止
as断言(除非从unknown转换)」 - 改进建议:使用类型守卫或重写为:
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 - 问题:
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 - 问题:
import { auth } from "@/auth" ... const session = await auth() const studentId = String(session?.user?.id ?? "") - 规范依据:项目规则「app 层不应直接依赖
@/auth」+ 编码规范 8.3 - 改进建议:改用
getAuthContext():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 - 问题:
错误被吞掉,无
} catch { toast.error("Failed to join class") }console.error记录,调试困难 - 规范依据:编码规范 9.2「错误必须可观测」
- 改进建议:
} 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 - 问题:
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:flexattendance / diagnostic / grades / learning/textbooks / learning/textbooks/[id] flex h-full flex-col space-y-8 p-8elective / learning/courses / schedule flex h-full flex-col space-y-4 p-6learning/assignments/[assignmentId] h-full flex-1 flex-col space-y-8 p-8 md:flexlearning/assignments 无容器 dashboard -
改进建议:抽取到
student/layout.tsx的<main>中,统一 padding 和布局
BUG-X02:"No user" 处理方式不一致
- 位置:多个页面
- 问题:
dashboard、learning/courses、learning/textbooks、learning/textbooks/[id]、schedule、learning/assignments、elective:返回EmptyStatelearning/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:Inboxattendance:CalendarCheckgrades:GraduationCapelective: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 - 改进建议:
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 - 问题:
3 次遍历同一数组,O(3n)
const dueSoonCount = assignments.filter(...).length const overdueCount = assignments.filter(...).length const gradedCount = assignments.filter(...).length - 违反规则:
js-combine-iterations - 改进建议:单次遍历统计:
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 - 问题:
2 次遍历同一数组
const answeredItems = items.filter((a) => isAnswered(a.progressStatus)) const unansweredItems = items.filter((a) => !isAnswered(a.progressStatus)) - 违反规则:
js-combine-iterations - 改进建议:单次遍历分桶:
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 - 问题:每次渲染都重建
itemsByDayMap 并排序 - 现状:作为 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中提供"重试"按钮:"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-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(立即修复 — 影响正确性和架构合规性)
- BUG-A01 + BUG-A02 + BUG-A03:将
getDemoStudentUser迁移到users/data-access.ts并重命名为getCurrentStudentUser(),所有 student 页面统一使用getAuthContext()或新函数 - BUG-E01:
elective/page.tsx改用getAuthContext(),移除import { auth } from "@/auth" - BUG-L01:
learning/assignments/page.tsx中英文混排修正 - BUG-L03:
learning/assignments/page.tsx:162JSX 语法格式错误修正 - BUG-T01:
learning/textbooks/page.tsx删除注释代码
P1(本迭代修复 — 影响可维护性和一致性)
- BUG-X01:创建
student/layout.tsx统一页面容器 - UI-01:为 8 个缺失路由添加
loading.tsx - UI-02:创建
student/error.tsx错误边界 - BUG-L02:抽取
AssignmentCard组件消除重复 - BUG-D01:移除
as类型断言 - BUG-L04:
getStatusVariant区分 submitted / in_progress - BUG-X02 + BUG-X03:统一 "No user" 处理和图标选择
- PERF-01 + PERF-02:
student-courses-view.tsx使用useTransition+ memoize 卡片 - PERF-04 + PERF-05:合并重复
filter遍历
P2(下迭代修复 — 增强健壮性)
- BUG-L05 + BUG-L06:类型精确化
- BUG-T02 + BUG-D02:补全页面标题
- BUG-TD01 + BUG-TD03:清理未使用变量 +
aria-hidden - UI-03:所有装饰性
<span>添加aria-hidden - UI-05:表单客户端校验
- BUG-C01 + BUG-C02 + BUG-C03:
student-courses-view.tsx错误处理 + 表单优化 - BUG-S01 + BUG-S02 + BUG-SV01:小重构
P3(文档同步)
- DOC-01 ~ DOC-07:同步 004 和 005 架构文档
八、student/layout.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 重复)
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>
)
}
十、验证命令
修复完成后应运行以下命令确保零错误:
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(界面规范审查)