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

34 KiB
Raw Blame History

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-practicesweb-artifacts-builderweb-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-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-A02getDemoStudentUser 命名误导

  • 位置src/modules/homework/data-access.ts:475
  • 问题:函数名包含 "Demo",但实际执行真实 DB 查询JOIN users + usersToRoles + roles WHERE roles.name = "student"),并非演示用桩函数
  • 影响:开发者看到 "Demo" 会误以为是临时实现,不敢用于生产;或误以为返回固定演示数据
  • 改进建议:重命名为 getCurrentStudentUser()resolveCurrentStudent()

BUG-A03getDemoStudentUser 放置在错误的模块

  • 位置src/modules/homework/data-access.ts:475-490
  • 问题:学生身份查询属于 users 模块职责,却放在 homework 模块
  • 规范依据:项目规则「模块标准结构」+ 编码规范 2.2「模块封装」
  • 影响dashboardlearning/assignmentslearning/courseslearning/textbookslearning/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 中插入中文标签
    <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-116124-157
  • 问题:未答题区和已答题区的卡片 JSX 完全相同34 行 × 2 = 68 行重复),仅数据源不同
  • 规范依据编码规范「DRY 原则」+ 项目规则「单文件行数建议 ≤ 500 行」
  • 改进建议:抽取为 <AssignmentCard assignment={a} /> 组件,循环调用

BUG-L03JSX 语法格式错误

  • 位置src/app/(dashboard)/student/learning/assignments/page.tsx:162
  • 问题:行尾 })} 多了一个 )
    )})}
    
    应为:
    ))}
    
  • 影响:虽然能编译通过(因外层有 (但格式混乱IDE 自动格式化后会变化,造成 git diff 噪音
  • 改进建议:修正为 ))}

BUG-L04getStatusVariant 对 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-L06new 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-T03student 变量未真正使用

  • 位置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-TD01student 变量未使用

  • 位置src/app/(dashboard)/student/learning/textbooks/[id]/page.tsx:18-31
  • 问题:同 BUG-T03student 仅用于 "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-D01as 类型断言违规

  • 位置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-D03EmptyStateicon 使用 Inbox 不合适

  • 位置src/app/(dashboard)/student/dashboard/page.tsx:5, 22
  • 问题:用户未找到时显示 Inbox 图标,语义不匹配
  • 改进建议:使用 UserXUserMinus 图标

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-S02searchParams 类型未共享

  • 位置src/app/(dashboard)/student/schedule/page.tsx:11learning/textbooks/page.tsx:11
  • 问题:两处定义相同的 type SearchParams = { [key: string]: string | string[] | undefined }
  • 改进建议:抽取到 shared/typesshared/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-E02String(... ?? "") 模式冗余

  • 位置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
  • 问题
    } 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-transitionsrendering-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-SV01for...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: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" 处理方式不一致

  • 位置:多个页面
  • 问题
    • dashboardlearning/courseslearning/textbookslearning/textbooks/[id]schedulelearning/assignmentselective:返回 EmptyState
    • learning/assignments/[assignmentId]:调用 notFound()
    • attendancegrades:返回 EmptyState(但检查的是 summary 而非 student
  • 改进建议:未认证场景应由 proxy.ts 拦截,页面内统一用 notFound() 或统一 EmptyState

BUG-X03图标选择不一致

  • 位置:多个页面
  • 问题:同样 "No user found" 场景使用不同图标:
    • dashboardlearning/courseslearning/textbooksschedulelearning/assignmentsInbox
    • attendanceCalendarCheck
    • gradesGraduationCap
    • electiveInbox
  • 改进建议:未认证场景统一使用 UserX;空数据场景使用业务相关图标

三、React 性能优化(应用 vercel-react-best-practices 技能)

PERF-01student-courses-view.tsx 未使用 useTransition

  • 位置src/modules/student/components/student-courses-view.tsx:28-44
  • 问题Server Action joinClassByInvitationCodeActionuseState 管理 loading阻塞 UI 线程
  • 违反规则rerender-transitionsrendering-usetransition-loading
  • 改进建议
    const [isPending, startTransition] = useTransition()
    const handleJoin = async (formData: FormData) => {
      startTransition(async () => {
        const res = await joinClassByInvitationCodeAction(null, formData)
        ...
      })
    }
    

PERF-02student-courses-view.tsx 卡片列表未 memoize

  • 位置src/modules/student/components/student-courses-view.tsx:50-108
  • 问题classes.map(...) 每次渲染都重新创建 6+ 个 Card 元素,当 code 输入变化时所有卡片重渲染
  • 违反规则rerender-memorerender-no-inline-components
  • 改进建议:抽取 <ClassCard class={c} /> 组件并用 React.memo 包裹

PERF-03student-schedule-filters.tsxoptions 依赖 classes 引用

  • 位置src/modules/student/components/student-schedule-filters.tsx:12
  • 问题useMemo(() => [...classes], [classes]) — 父组件每次传入新 classes 数组引用时都重新计算
  • 违反规则rerender-dependencies
  • 现状:已用 useMemo,但依赖项是父组件传入的 prop若父组件不 memoize 则失效
  • 改进建议:可接受,但建议父组件 schedule/page.tsxReact.cache 或在 Server Component 层面稳定引用

PERF-04dashboard/page.tsx 多次 filter 遍历同一数组

  • 位置src/app/(dashboard)/student/dashboard/page.tsx:40-52
  • 问题
    const dueSoonCount = assignments.filter(...).length
    const overdueCount = assignments.filter(...).length
    const gradedCount = assignments.filter(...).length
    
    3 次遍历同一数组O(3n)
  • 违反规则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-05learning/assignments/page.tsx 重复 filter 调用

  • 位置src/app/(dashboard)/student/learning/assignments/page.tsx:73-74
  • 问题
    const answeredItems = items.filter((a) => isAnswered(a.progressStatus))
    const unansweredItems = items.filter((a) => !isAnswered(a.progressStatus))
    
    2 次遍历同一数组
  • 违反规则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-06student-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-07dashboard/page.tsx 已正确使用 Promise.all 并行获取

  • 位置src/app/(dashboard)/student/dashboard/page.tsx:29-34
  • 现状 符合 async-parallel 规则
  • 说明4 个独立查询并行执行,无需优化

PERF-08diagnostic/page.tsx 已正确使用 Promise.all

  • 位置src/app/(dashboard)/student/diagnostic/page.tsx:12-15
  • 现状 符合 async-parallel 规则

PERF-09schedule/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-hiddenaria-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, 151student-courses-view.tsx:94, 100
  • 问题<Link href="/student/learning/assignments/..."> 默认 prefetch但作业详情页数据量大可能浪费带宽
  • 违反规则bundle-preload(轻度)
  • 改进建议:对详情页链接添加 prefetch={false} 或在 hover 时预加载

UI-07hover:shadow-md 可能导致性能问题

  • 位置learning/assignments/page.tsx:84, 125student-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-09tabular-nums 使用正确

  • 位置learning/assignments/page.tsx:107, 148student-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-E01elective/page.tsx 改用 getAuthContext(),移除 import { auth } from "@/auth"
  3. BUG-L01learning/assignments/page.tsx 中英文混排修正
  4. BUG-L03learning/assignments/page.tsx:162 JSX 语法格式错误修正
  5. BUG-T01learning/textbooks/page.tsx 删除注释代码

P1本迭代修复 — 影响可维护性和一致性)

  1. BUG-X01:创建 student/layout.tsx 统一页面容器
  2. UI-01:为 8 个缺失路由添加 loading.tsx
  3. UI-02:创建 student/error.tsx 错误边界
  4. BUG-L02:抽取 AssignmentCard 组件消除重复
  5. BUG-D01:移除 as 类型断言
  6. BUG-L04getStatusVariant 区分 submitted / in_progress
  7. BUG-X02 + BUG-X03:统一 "No user" 处理和图标选择
  8. PERF-01 + PERF-02student-courses-view.tsx 使用 useTransition + memoize 卡片
  9. PERF-04 + PERF-05:合并重复 filter 遍历

P2下迭代修复 — 增强健壮性)

  1. BUG-L05 + BUG-L06:类型精确化
  2. BUG-T02 + BUG-D02:补全页面标题
  3. BUG-TD01 + BUG-TD03:清理未使用变量 + aria-hidden
  4. UI-03:所有装饰性 <span> 添加 aria-hidden
  5. UI-05:表单客户端校验
  6. BUG-C01 + BUG-C02 + BUG-C03student-courses-view.tsx 错误处理 + 表单优化
  7. BUG-S01 + BUG-S02 + BUG-SV01:小重构

P3文档同步

  1. 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.tsxlearning/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 AgentGLM-5.2 核查方法:人工逐行审查 + 架构图比对 + 技能规则匹配 应用技能:vercel-react-best-practices(性能优化)、web-artifacts-builder(界面构建参考)、web-design-guidelines(界面规范审查)