Files
NextEdu/docs/architecture/audit/dashboard-audit-report-v3.md
SpecialX 27db170c0a docs: update architecture docs, audit reports, and bug tracking
- Update architecture impact map, data, feature checklist, gap audit

- Add audit reports for dashboard, exam-homework, grades-diagnostic, settings-profile, textbooks

- Update bug reports (admin, teacher, lesson-preparation, others, shared)

- Update coding standards, DR plan, design docs, and README
2026-06-23 17:36:18 +08:00

14 KiB
Raw Permalink Blame History

Dashboard 模块 V3 审计报告

审计日期2026-06-22 审计范围src/modules/dashboard/ + 所有 dashboard 路由文件 前置审计v1P0 修复:跨模块 DB 查询、权限、i18n 容器组件、v210 个子组件 i18n、DashboardGreetingHeader 抽象、31 个纯函数单测、a11y 语义化标签)


概览

v1/v2 审计解决了表层问题。v3 审计发现了更深层次的问题涉及数据完整性、i18n 完整性、死代码、类型安全、流式架构和测试缺口。最严重的是 admin dashboard 中 ContentRow 标签与值完全错配的 P0 数据展示 bug

严重度 数量
P0 3
P1 10
P2 9

P0 问题(严重)

P0-1Admin Dashboard ContentRow 标签与值错配(数据完整性)

  • 文件src/modules/dashboard/components/admin-dashboard/admin-dashboard.tsx
  • 行号166-169Content 区块、180-181Homework Activity 区块)
  • 问题"Content" 区块显示教材/章节/题目/考试数量,但使用了用户/班级/待批改/已发布作业的标签。图标正确Library, BookOpen, FileText, ClipboardList但标签错误
    • 行 166label={t("stats.users")} + value={data.textbookCount} → 应为 t("stats.textbooks")
    • 行 167label={t("stats.classes")} + value={data.chapterCount} → 应为 t("stats.chapters")
    • 行 168label={t("stats.toGrade")} + value={data.questionCount} → 应为 t("stats.questions")
    • 行 169label={t("stats.homeworkPublished")} + value={data.examCount} → 应为 t("stats.exams")
    • 行 180label={t("stats.activeAssignments")} + value={data.homeworkAssignmentCount} → 标签说"active"但值是总数
    • 行 181label={t("stats.submissionRate")} + value={data.homeworkSubmissionCount} → 标签说"rate"(百分比)但值是原始计数
  • 修复:使用与值匹配的正确翻译键。新增缺失键(stats.textbooksstats.chaptersstats.questionsstats.examsstats.totalAssignmentsstats.totalSubmissions)到 messages/{zh-CN,en}/dashboard.json

P0-2admin/error.tsx 硬编码中文,无 i18n

  • 文件src/app/(dashboard)/admin/error.tsx
  • 行号12-14
  • 问题:此错误边界有硬编码中文字符串("页面加载失败""抱歉,页面加载时发生了意外错误。请稍后重试。""重试"),未导入或使用 useTranslations。英文用户会看到中文文本。v2 审计遗漏了此文件,因为只关注了 dashboard/ 模块而非 admin/ 路由错误边界。其他 dashboard error.tsxteacher、parent、root都正确使用了 useTranslations
  • 修复:导入 useTranslations,替换硬编码字符串为 t("error.loadFailed")t("error.loadFailedDesc")t("error.retry")

P0-3userGrowth 和 homeworkTrend 永远返回空数组

  • 文件src/modules/dashboard/data-access.ts
  • 行号46-47
  • 问题getAdminDashboardData 硬编码 userGrowth: []homeworkTrend: []UserGrowthChart 组件admin-dashboard.tsx 行 123、133渲染这些空数组产生永久空图表且无空状态。架构图行 973标注为"待后续接入真实统计",但至今未修复。用户看到两个空白图表区域,有标题但无数据也无说明。
  • 修复:为 UserGrowthChart 添加空状态(当 data.length === 0 时显示"暂无数据"),与其他图表组件的空状态保持一致。

P1 问题(高)

P1-1admin/dashboard 路由缺失 loading.tsx

  • 文件(缺失)src/app/(dashboard)/admin/dashboard/loading.tsx
  • 问题admin dashboard 路由无路由级 loading.tsx,回退到 admin/loading.tsx(通用骨架屏,不匹配 admin dashboard 布局。Teacher、student、parent 都有 dashboard 专属 loading.tsx
  • 修复:创建 admin/dashboard/loading.tsx,骨架屏匹配 AdminDashboardView 布局。

P1-2admin/dashboard 和 student/dashboard 路由缺失 error.tsx

  • 文件(缺失)src/app/(dashboard)/admin/dashboard/error.tsxsrc/app/(dashboard)/student/dashboard/error.tsx
  • 问题这些路由无路由级错误边界。Admin 回退到 admin/error.tsx(有硬编码中文 — 见 P0-2。Student 回退到 student/error.tsx。Teacher 和 parent 都有 dashboard 专属 error.tsx(含 i18n + 重试按钮)。
  • 修复:为两个路由创建 dashboard 专属 error.tsx,使用 useTranslationsreset()

P1-3UserGrowthChart 硬编码标签用于两个图表

  • 文件src/modules/dashboard/components/admin-dashboard/user-growth-chart.tsx
  • 行号44
  • 问题name 属性硬编码为 t("chart.newUsers")。此组件在 admin-dashboard.tsx 中被复用于用户增长(行 123和作业提交趋势行 133。作业趋势图错误地显示"新用户"作为图例/提示标签。
  • 修复:为 UserGrowthChart 添加 labelKeyname prop让调用方指定正确标签。

P1-4formatDate / formatLongDate 总是使用 zh-CN locale

  • 文件src/shared/lib/utils.ts(行 8、35及所有不传 locale 的 dashboard 组件

  • 问题formatDateformatLongDate 默认 locale = "zh-CN"。所有 dashboard 组件调用时未传用户 locale

    • dashboard-greeting-header.tsx 行 22
    • admin-dashboard.tsx 行 215
    • teacher-homework-card.tsx 行 69
    • recent-submissions.tsx 行 96
    • student-grades-card.tsx 行 23、105
    • student-upcoming-assignments-card.tsx 行 106

    英文用户看到中文格式日期(如"2026年6月22日 周一"而非"Monday, June 22, 2026")。

  • 修复:客户端组件用 useLocale()next-intl服务端组件用 getLocale()next-intl/server传入 formatDate/formatLongDate

P1-5死代码 — getCachedAdminDashboard 从未使用

  • 文件src/modules/dashboard/actions.ts
  • 行号146
  • 问题export const getCachedAdminDashboard = cache(getAdminDashboardAction) 定义但从未被导入或调用。data-access.ts 中的 getAdminDashboardData 已用 cache() 包裹。此外,用 React cache() 包裹调用 requirePermission() 的 Server Action 语义上不正确。
  • 修复:删除行 146 及未使用的 cache 导入。

P1-6死代码 — AvatarImage src={undefined}

  • 文件src/modules/dashboard/components/teacher-dashboard/recent-submissions.tsx
  • 行号76
  • 问题<AvatarImage src={undefined} alt={item.studentName} /> 总是传 undefined 作为 srcAvatarImage 永远不会渲染实际图片,总是回退到 AvatarFallback
  • 修复:移除 AvatarImage 行,仅保留 AvatarFallback

P1-7死 prop — TeacherStats isLoading 从未传入

  • 文件src/modules/dashboard/components/teacher-dashboard/teacher-stats.tsx
  • 行号10、18、32、41、50、59
  • 问题TeacherStats 接受 isLoading prop默认 false)并传给所有 4 个 StatCard。但 TeacherStats 仅在 DashboardSection 中渲染(teacher-dashboard-view.tsx 行 53未传 isLoading。prop 永远为 falseStudentStatsGrid 无此 prop造成不一致。
  • 修复:移除 TeacherStatsisLoading prop 及 StatCard 调用。

P1-8dashboard-utils.ts 中的 as 类型断言违反项目规则

  • 文件src/modules/dashboard/lib/dashboard-utils.ts

  • 行号114、145

  • 问题:项目规则明确"禁止 as 断言"(除 unknown 转换或测试外)。两处违规:

    • 行 114})) as StudentTodayScheduleItem[] | TeacherTodayScheduleItem[]
    • 行 145) as TeacherTodayScheduleItem[]

    根因是 filterTodaySchedule 重载服务于学生和教师课表,但返回类型是联合类型。

  • 修复:将 filterTodaySchedule 改为泛型函数,或拆分为两个函数。

P1-9辅助函数缺失显式返回类型

  • 文件
    • teacher-schedule.tsx 行 24const getStatus = (start: string, end: string) => {
    • student-upcoming-assignments-card.tsx 行 30const getDueUrgency = (dueAt: string | null) => {
  • 问题:项目规则要求"函数返回值必须显式标注"。
  • 修复:添加显式返回类型。

P1-10重复的 loading.tsx 和 error.tsx 文件

  • 文件
    • src/app/(dashboard)/dashboard/loading.tsxsrc/app/(dashboard)/teacher/dashboard/loading.tsx — 字节级完全相同
    • src/app/(dashboard)/dashboard/error.tsxteacher/dashboard/error.tsxparent/dashboard/error.tsx — 全部相同
  • 问题:这些文件是精确副本。任何修复必须应用到所有副本,容易产生漂移。
  • 修复:抽取共享 DashboardLoadingSkeletonDashboardErrorFallback 组件到 src/modules/dashboard/components/,每个路由的 loading.tsx/error.tsx 渲染共享组件。

P2 问题(中)

P2-1流式/Suspense 未生效 — 数据在页面级获取

  • 文件:所有 page.tsxadmin/teacher/student/parent dashboard
  • 问题:所有页面用 export const dynamic = "force-dynamic"await getDashboardAction() 在渲染任何子组件前获取所有数据。DashboardSection 包裹子组件于 <Suspense>,但数据已在页面级解析并作为 props 传入Suspense 永远不会在初始渲染时触发。
  • 修复:将数据获取移入各卡片组件(使其成为异步服务端组件自行获取数据),或传入未解析的 promise 并用 React use() hook。这是较大的架构变更。

P2-24 个组件不必要标记为 "use client"

  • 文件
    • dashboard-greeting-header.tsx — 仅用 useTranslationsformatLongDategetGreetingKey
    • teacher-quick-actions.tsx — 仅用 useTranslationsLinkButton
    • teacher-dashboard-header.tsx — 包裹上述两个
    • student-dashboard-header.tsx — 包裹 DashboardGreetingHeader
  • 问题:这些组件标记为 "use client" 但不含客户端 only hookuseStateuseEffect、事件处理器等)。useTranslations 在服务端组件中可用。转为服务端组件(用 getTranslations 替代 useTranslations)可减少客户端包大小。
  • 修复:移除 "use client",改 useTranslationsgetTranslationsasync组件改为 async function

P2-3UserGrowthChart 无空状态

  • 文件src/modules/dashboard/components/admin-dashboard/user-growth-chart.tsx
  • 问题:当 data 为空(当前永远如此 — 见 P0-3recharts 渲染空图表有坐标轴但无线条无说明。其他图表组件(TeacherGradeTrendsStudentGradesCard)使用 ChartCardShell 有正确空状态。
  • 修复:添加空状态检查:data.length === 0 时渲染 EmptyState

P2-4Student dashboard 空状态缺少 CTA与 teacher 不一致)

  • 文件
    • student-today-schedule-card.tsx 行 58-63EmptyStateaction
    • student-upcoming-assignments-card.tsx 行 59-64EmptyStateaction
  • 问题Teacher dashboard 空状态都含 CTA。Student dashboard 空状态无 CTA用户无明确下一步。
  • 修复:为 student 空状态添加 action prop。

P2-5StudentTodayScheduleCard 过时数据 — useMemo 不随时间更新

  • 文件src/modules/dashboard/components/student-dashboard/student-today-schedule-card.tsx
  • 行号25-43
  • 问题useMemo(() => { ... }, [items]) 基于 new Date() 计算 currentIdnextId。依赖数组是 [items],仅在 items 变化时重新计算。用户保持页面打开时,"进行中"和"下一个"徽章会过时。
  • 修复:添加基于时间的重渲染机制(如 useEffect + setInterval 每分钟更新 now state

P2-6仅图标按钮缺少 aria-label

  • 文件src/modules/dashboard/components/teacher-dashboard/teacher-homework-card.tsx
  • 行号22
  • 问题<Button asChild size="icon" variant="ghost" className="h-8 w-8" title={...}>title 作 tooltip 但无 aria-label。屏幕阅读器可能不播报按钮用途。
  • 修复:添加 aria-label={t("quickActions.createNewAssignment")}

P2-7无组件测试 — 仅有纯函数测试

  • 文件tests/integration/dashboard/dashboard-utils.test.ts408 行31 个测试覆盖 6 个纯函数)、tests/integration/dashboard/dashboard-routing.test.ts6 个测试覆盖重定向逻辑)
  • 问题v2 添加了纯函数单测,但零组件测试、零 Server Action 测试、零 data-access 测试、零错误边界测试。dashboard-routing.test.ts 在用户对象上 mock permissions(行 41但实际代码用 resolvePermissions(roles) — mock 的 permissions 字段被忽略,测试设置有误导性。
  • 修复添加组件测试RTL、Action 测试mock data-access验证权限调用、修复路由测试。

P2-8TeacherTodoCard 排序逻辑晦涩

  • 文件src/modules/dashboard/components/teacher-dashboard/teacher-todo-card.tsx
  • 行号52
  • 问题.sort((a, b) => (a.variant === "urgent" ? -1 : 1) - (b.variant === "urgent" ? -1 : 1)) 难以阅读。布尔转数字的算术不透明。
  • 修复:重写为更可读的比较函数。

P2-9TeacherSchedule 渲染两次(移动端 + 桌面端)— 重复服务端渲染

  • 文件src/modules/dashboard/components/teacher-dashboard/teacher-dashboard-view.tsx
  • 行号63-67移动端、85-89桌面端
  • 问题TeacherSchedule(异步服务端组件调用 getTranslations)在 React 树中渲染两次 — 一次在 lg:hidden div一次在 hidden lg:block div。两个实例都在服务端渲染并发送到客户端使此区块 HTML 负载翻倍。
  • 修复:渲染一次并用 CSS grid/flexbox 重排序实现响应式布局,或接受此重复为较小代价。

修复顺序

  1. P0-1ContentRow 标签)— 直接面向用户的数据 bug
  2. P0-2admin/error.tsx i18n— 直接 i18n 回归
  3. P0-3 + P1-3 + P2-3(空趋势数据 + 图表标签 + 空状态)— 一起修复
  4. P1-1、P1-2(缺失 loading.tsx/error.tsx— 一致性
  5. P1-4(日期 locale— 系统性 i18n 修复
  6. P1-5、P1-6、P1-7(死代码)— 快速清理
  7. P1-8、P1-9(类型安全)— 重构 filterTodaySchedule
  8. P1-10(重复文件)— 抽取共享组件
  9. P2-2、P2-4、P2-6、P2-8(增量改进)