Files
NextEdu/docs/architecture/audit/dashboard-audit-report.md
SpecialX 868ac5f9cf feat(dashboard): 仪表盘模块审计重构 — 权限校验 + i18n + 逻辑抽离
基于 dashboard-audit-report.md 审计结论,对仪表盘模块进行 P0/P1 级修复:

- 新增 4 个 dashboard 权限点(DASHBOARD_ADMIN/TEACHER/STUDENT/PARENT_READ),补充到 permissions.ts 和角色-权限映射

- 新建 actions.ts:4 个 Server Action 均调用 requirePermission() 校验权限,消除 admin 页面零鉴权、teacher/student/parent 仅 requireAuth 的安全隐患

- 根重定向页 /dashboard 改用 resolvePermissions() + 权限点判断,不再 role === xxx 硬编码

- 新建 lib/dashboard-utils.ts:抽取 toWeekday / countStudentAssignments / sortUpcomingAssignments / filterTodaySchedule / computeTeacherMetrics / getGreetingKey 纯函数,与 UI 分离,便于单测

- 新建 messages/{zh-CN,en}/dashboard.json 翻译文件,i18n request.ts 加载 dashboard 命名空间;所有视图组件接入 useTranslations / getTranslations,消除中英混杂硬编码

- 重构 4 个角色 page.tsx:通过 actions 获取数据,generateMetadata 使用 i18n

- 同步更新架构图 004 / 005 文档(dashboard exports / permissions / 文件清单)
2026-06-22 15:50:56 +08:00

321 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 仪表盘模块审计报告
> 审查日期2026-06-22
> 审查范围:`src/modules/dashboard/**`、`src/app/(dashboard)/*/dashboard/**`、`src/modules/parent/components/parent-dashboard.tsx`(家长端仪表盘)
> 架构图参考:`docs/architecture/004_architecture_impact_map.md` §1.4.3、`docs/architecture/005_architecture_data.json`
---
## 一、现有实现概要
### 1.1 文件分布
| 层 | 路径 | 文件数 | 说明 |
|----|------|--------|------|
| 路由层 | `src/app/(dashboard)/{admin,teacher,student,parent}/dashboard/` | 4 个 `page.tsx` + 3 个 `error.tsx` + 3 个 `loading.tsx` | 各角色独立路由,另有根 `/dashboard/page.tsx` 做角色重定向 |
| 模块层 - admin | `src/modules/dashboard/components/admin-dashboard/` | 2 个(`admin-dashboard.tsx` 263 行、`user-growth-chart.tsx` 46 行) | |
| 模块层 - teacher | `src/modules/dashboard/components/teacher-dashboard/` | 9 个组件 | `teacher-dashboard-view.tsx` 为容器,含业务计算逻辑 |
| 模块层 - student | `src/modules/dashboard/components/student-dashboard/` | 6 个组件 | `student-dashboard-view.tsx` 为容器 |
| 模块层 - parent | `src/modules/parent/components/parent-dashboard.tsx` | 1 个108 行) | **不在 dashboard 模块内**,位于 parent 模块 |
| 数据层 | `src/modules/dashboard/data-access.ts` | 1 个49 行) | 仅 `getAdminDashboardData`,并行调用 6 个模块的 stats 函数 |
| 类型层 | `src/modules/dashboard/types.ts` | 1 个74 行) | Admin / Teacher / Student 类型定义 |
| Actions 层 | **缺失** | 0 | 无 `actions.ts`,页面直接调用 data-access |
### 1.2 数据流
```
[Route] /admin/dashboard/page.tsx
└─▶ dashboard/data-access.getAdminDashboardData()
└─▶ Promise.all(users/classes/textbooks/questions/exams/homework stats)
[Route] /teacher/dashboard/page.tsx
├─▶ classes/data-access.getTeacherClasses / getClassSchedule
├─▶ homework/data-access.getHomeworkAssignments / getHomeworkSubmissions / getTeacherGradeTrends
└─▶ users/data-access.getUserBasicInfo
(页面层直接编排 3 个模块的 data-access
[Route] /student/dashboard/page.tsx
├─▶ users/data-access.getCurrentStudentUser
├─▶ classes/data-access.getStudentClasses / getStudentSchedule
└─▶ homework/data-access.getStudentHomeworkAssignments / getStudentDashboardGrades
(页面层直接编排 3 个模块的 data-access + 业务计算)
[Route] /parent/dashboard/page.tsx
└─▶ parent/data-access.getParentDashboardData
```
### 1.3 架构图记录情况
`004_architecture_impact_map.md` §1.4.3 记录了 admin 仪表盘聚合链路P0-4 已修复跨模块直查),但存在遗漏:
- **未记录 teacher / student / parent 仪表盘的调用链路**
- **未记录 dashboard 模块的 exports 清单**005 JSON 中 dashboard 节点缺失 `exports` 字段)
- **未记录 parent 仪表盘组件位于 parent 模块这一结构异常**
---
## 二、现存问题与原因分析
### 2.1 安全性权限校验完全缺失P0
| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [admin/dashboard/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/dashboard/page.tsx) | 直接调用 `getAdminDashboardData()`**无任何 auth/permission 校验** | "所有 Server Action 必须调用 `requirePermission()` 进行权限校验" |
| [teacher/dashboard/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/dashboard/page.tsx) | 仅调用 `getAuthContext()`,未校验任何权限点 | 同上 |
| [student/dashboard/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/student/dashboard/page.tsx) | **无任何 auth 调用**,完全依赖 layout 守卫 | 同上 |
| [parent/dashboard/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/parent/dashboard/page.tsx) | 调用 `requireAuth()`,未校验具体权限 | 同上 |
| [permissions.ts](file:///e:/Desktop/CICD/src/shared/types/permissions.ts) | **无 dashboard 相关权限点定义** | 权限体系不完整 |
**后果**admin 仪表盘数据(含全校用户数、活跃会话数、最近注册用户列表)可被任意已登录用户访问,属于严重越权。即使 layout 层有路由组守卫data-access 层仍缺乏二次校验,不符合"Server Action 二次校验"要求。
### 2.2 架构分层:页面层越权编排 + 模块归属错位P0
| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [teacher/dashboard/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/dashboard/page.tsx) L16-23 | 页面层直接 `Promise.all` 调用 classes/homework/users 三个模块的 data-access | "app/ 只能调用 modules/ 的 Server Actions 和 data-access" — 虽然语法允许,但编排逻辑应在 dashboard 模块的 actions/data-access 层完成 |
| [student/dashboard/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/student/dashboard/page.tsx) L36-86 | 页面层包含 weekday 转换、作业状态统计、排序切片等 **80 行业务逻辑** | "Server Actions / Data Access 模块"应承担编排职责;纯逻辑应抽为 hooks/纯函数 |
| [parent-dashboard.tsx](file:///e:/Desktop/CICD/src/modules/parent/components/parent-dashboard.tsx) | 家长仪表盘组件位于 `modules/parent` 而非 `modules/dashboard` | 仪表盘模块不完整,四角色仪表盘分散在两个模块 |
| dashboard 模块无 `actions.ts` | 缺失编排层 | "模块标准结构"要求 `actions.ts`(编排层) |
**后果**:页面层臃肿、逻辑不可复用、不可测试;新增角色需复制粘贴整页编排逻辑。
### 2.3 角色硬编码P0
| 位置 | 代码 | 违反规则 |
|------|------|----------|
| [dashboard/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/dashboard/page.tsx) L12-15 | `roles.includes("admin")` / `roles.includes("student")` / `roles.includes("parent")` | "前端权限判断统一使用 `usePermission().hasPermission()`,严禁出现 `role === 'xxx'` 硬编码" |
| [auth-guard.ts](file:///e:/Desktop/CICD/src/shared/lib/auth-guard.ts) L69/L74/L86/L118/L131 | `roleNames.includes("admin"/"teacher"/"student"/"parent")` | 同上dataScope 解析也基于角色硬编码) |
**后果**:新增角色(如 grade_head 已存在但未处理仪表盘重定向)无法正确路由;权限策略变更需改多处代码。
### 2.4 国际化:零覆盖 + 中英混杂P0
| 位置 | 问题 |
|------|------|
| [admin-dashboard.tsx](file:///e:/Desktop/CICD/src/modules/dashboard/components/admin-dashboard/admin-dashboard.tsx) | L34 `"Dashboard"`、L63 `"Users"` 为英文L74 `"批量导入用户"`、L113 `"用户增长趋势近30天"` 为中文 — **同一文件中英混杂** |
| [teacher-dashboard-header.tsx](file:///e:/Desktop/CICD/src/modules/dashboard/components/teacher-dashboard/teacher-dashboard-header.tsx) L13-16 | `greeting = "早上好"/"下午好"/"晚上好"` 硬编码 |
| [teacher-dashboard-view.tsx](file:///e:/Desktop/CICD/src/modules/dashboard/components/teacher-dashboard/teacher-dashboard-view.tsx) L53-55 | `"待批改作业"` / `"今日待考勤"` / `"进行中作业"` 硬编码 |
| [student-stats-grid.tsx](file:///e:/Desktop/CICD/src/modules/dashboard/components/student-dashboard/student-stats-grid.tsx) | `"Enrolled Classes"` / `"Average Score"` 等全英文硬编码 |
| [parent-dashboard.tsx](file:///e:/Desktop/CICD/src/modules/parent/components/parent-dashboard.tsx) L28-31 | `"Good morning"` / `"Good afternoon"` 硬编码 |
| [user-growth-chart.tsx](file:///e:/Desktop/CICD/src/modules/dashboard/components/admin-dashboard/user-growth-chart.tsx) L42 | `name="新增用户"` 硬编码 |
| `messages/` 目录 | **无 `dashboard.json`**,仅 onboarding/classes/auth/errors/common 有翻译文件 |
**违反规则**"所有用户可见文本必须适配 i18n使用 next-intl提取翻译键"。
**后果**:无法切换语言;维护时需逐文件改字符串;中英混杂给用户造成混乱。
### 2.5 错误与边界处理仅路由级P1
| 位置 | 问题 |
|------|------|
| `error.tsx` / `loading.tsx` | 仅存在于路由级(`app/(dashboard)/*/dashboard/`**无按数据区块的 Error Boundary** |
| [admin-dashboard.tsx](file:///e:/Desktop/CICD/src/modules/dashboard/components/admin-dashboard/admin-dashboard.tsx) | 6 张 Card + 1 张表格,任一数据源异常导致整页崩溃 |
| [teacher-dashboard-view.tsx](file:///e:/Desktop/CICD/src/modules/dashboard/components/teacher-dashboard/teacher-dashboard-view.tsx) | 7 个子区块,无独立 Suspense 包裹 |
| error.tsx 文案 | `"页面加载失败"` 硬编码中文,未 i18n |
**违反规则**"每个独立的数据区块必须用 React Error Boundary 包裹"、"异步数据使用 React Suspense + 骨架屏"。
**后果**:单个 Widget 故障导致整页不可用;无法流式渲染,首屏白屏时间长。
### 2.6 可测试性:业务逻辑与 UI 耦合P1
| 位置 | 耦合的逻辑 |
|------|-----------|
| [teacher-dashboard-view.tsx](file:///e:/Desktop/CICD/src/modules/dashboard/components/teacher-dashboard/teacher-dashboard-view.tsx) L18-56 | `toWeekday``todayScheduleItems` 过滤排序、`toGradeCount`/`submissionRate` 计算、`todoItems` 聚合 — 全部内联在组件中 |
| [student/dashboard/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/student/dashboard/page.tsx) L13-86 | `toWeekday``dueSoonCount`/`overdueCount`/`gradedCount` 单次遍历统计、`upcomingAssignments` 排序切片 — 80 行纯逻辑在 Server Component 中 |
| [parent-dashboard.tsx](file:///e:/Desktop/CICD/src/modules/parent/components/parent-dashboard.tsx) L27-31 | greeting 时段判断内联在组件中 |
**违反规则**"数据获取、计算、格式化等纯逻辑全部放入纯函数或 hooks与 UI 分离"。
**后果**:无法对统计逻辑做单元测试;逻辑变更需改组件代码;复用需复制粘贴。
### 2.7 可复用性四角色零共享抽象P1
| 维度 | 现状 |
|------|------|
| 布局容器 | admin/teacher/student/parent 各写一套 `<div className="space-y-*">`,无统一 `DashboardLayout` |
| 统计卡片 | 已复用 `shared/components/ui/stat-card.tsx`(✅ 良好) |
| 快捷操作 | admin 的 `QuickActionCard`内联、parent 的 `QUICK_ENTRIES`内联、teacher 的 `TeacherQuickActions` — 三套独立实现,无统一 `QuickActions` 组件 |
| 待办/任务 | 仅 teacher 有 `TeacherTodoCard`student/admin/parent 无类似组件 |
| 问候语 | teacher/parent 各写一套 `hour < 12 ? "早上好" : ...`,无统一 `useGreeting` hook |
| Widget 配置 | 无配置驱动设计,新增角色需新建整套组件 |
**违反规则**"最大化复用:识别四个角色共用的 UI 块和业务逻辑块,抽象为泛型组件和 hooks"、"采用配置驱动设计"。
### 2.8 性能:全量 force-dynamic 无流式渲染P2
| 位置 | 问题 |
|------|------|
| 所有 `page.tsx` | `export const dynamic = "force-dynamic"``Promise.all` 等全部数据就绪后才渲染 |
| 无 `<Suspense>` 包裹 | 无法流式渲染,首屏 TTFB 到 FCP 全部阻塞 |
**违反规则**"优先使用 React Server Components 获取初始数据;客户端组件仅负责交互;支持流式渲染"。
### 2.9 可访问性P2
| 位置 | 问题 |
|------|------|
| admin-dashboard.tsx | 表格无 `caption`,快捷操作 Card 作为链接无 `aria-label` |
| teacher-dashboard-view.tsx | 布局 div 无语义化标签(`<section>` / `<aside>` |
| student-dashboard-view.tsx | 同上 |
**违反规则**"语义化标签、ARIA 属性、键盘导航"。
---
## 三、行业差距对比
### 3.1 K12 仪表盘主流设计模式
| 模式 | 行业实践 | 本项目现状 | 差距影响 |
|------|----------|------------|----------|
| **Widget 网格系统** | 可拖拽、可配置的 Widget 卡片(如 PowerSchool、Veracross | 四角色各自硬编码布局 | 无法个性化,新增角色需重写 |
| **跨角色数据联动** | 家长端预览孩子仪表盘、教师端查看学生上下文 | 四角色完全隔离 | 家长需跳转多个页面才能了解孩子情况 |
| **可操作洞察** | "3 名学生成绩下滑"、"2 份作业待批改超 3 天" 等智能提醒 | 仅展示静态数字 | 管理者/教师需手动分析,效率低 |
| **通知中心集成** | 仪表盘首屏显示未读通知摘要 | 无通知集成 | 用户需进入消息模块查看 |
| **统一日历** | 跨模块日历视图(作业/考试/考勤/请假) | 无 | 师生需在多个模块间切换查看日程 |
| **学习进度可视化** | 学生学习路径、知识点掌握雷达图 | student 仅有成绩卡片 | 学生无法直观了解学习状态 |
| **空状态引导** | 无数据时提供 CTA"创建第一个作业" | admin 有部分 EmptyState其他角色缺失 | 新用户不知下一步操作 |
| **实时更新** | 活跃会话数、待批改数 WebSocket 推送 | 全静态 | 数据滞后,需手动刷新 |
| **响应式适配** | 移动端优先布局 | parent 有移动端横向滑动,其他角色仅 `md:` 断点 | 移动端体验差 |
### 3.2 各角色差距详述
**Admin**
- 缺少学校运营关键指标(出勤率、作业完成率趋势)
- 用户增长趋势图为空(`userGrowth: []` 硬编码在 data-access L46
- 无系统健康监控DB 连接数、API 延迟等)
**Teacher**
- 缺少班级对比视图(哪个班表现最好/最差)
- 缺少学生预警列表(成绩下滑/未提交作业的学生)
- 课表仅显示今日,无本周概览
**Student**
- 缺少学习目标/进度跟踪
- 缺少同学协作入口(小组作业、学习伙伴)
- 成绩仅显示排名,无知识点维度分析
**Parent**
- 缺少多孩子对比视图
- 缺少与教师沟通快捷入口
- 缺少孩子出勤/成绩异常告警
---
## 四、改进优先级建议
### P0紧急 — 安全与合规)
| # | 问题 | 改进方向 |
|---|------|----------|
| P0-1 | 权限校验完全缺失 | 新增 `DASHBOARD_ADMIN_READ` / `DASHBOARD_TEACHER_READ` / `DASHBOARD_STUDENT_READ` / `DASHBOARD_PARENT_READ` 权限点;创建 `actions.ts`,每个 Action 调用 `requirePermission()` |
| P0-2 | 根重定向页角色硬编码 | 改用 `hasPermission(DASHBOARD_*_READ)` 决定重定向目标 |
| P0-3 | i18n 零覆盖 | 创建 `messages/{zh-CN,en}/dashboard.json`;所有组件接入 `useTranslations` / `getTranslations` |
| P0-4 | 页面层越权编排 | 将 teacher/student/parent 的数据编排下沉到 `dashboard/actions.ts``data-access.ts` |
### P1较严重 — 架构与质量)
| # | 问题 | 改进方向 |
|---|------|----------|
| P1-1 | 业务逻辑耦合 UI | 抽取 `hooks/use-teacher-dashboard-metrics.ts``hooks/use-student-dashboard-metrics.ts``lib/weekday.ts`(纯函数) |
| P1-2 | 四角色零共享 | 抽象 `DashboardLayout``QuickActions``GreetingHeader``WidgetBoundary`Error Boundary + Suspense 组合) |
| P1-3 | 仅路由级错误边界 | 每个数据区块用 `<WidgetBoundary>` 包裹,支持独立 fallback |
| P1-4 | parent 仪表盘归属错位 | 将 `parent-dashboard.tsx` 迁移至 `modules/dashboard/components/parent-dashboard/`,或保留在 parent 模块但在架构图中明确标注 |
| P1-5 | 无流式渲染 | 用 `<Suspense>` 包裹各 Widget数据获取改为独立 async 组件 |
### P2优化 — 体验与扩展)
| # | 问题 | 改进方向 |
|---|------|----------|
| P2-1 | 无 Widget 配置系统 | 设计 `DashboardWidgetConfig` 类型,按角色配置渲染哪些 Widget |
| P2-2 | a11y 不足 | 补充语义化标签、ARIA 属性、表格 caption |
| P2-3 | 无单测 | 为抽取的纯函数/hooks 添加单测 |
| P2-4 | 行业功能差距 | 逐步补齐通知集成、统一日历、学生预警等(按角色优先级迭代) |
---
## 五、架构图同步说明
本次审计发现架构图存在以下遗漏,需在实现后同步更新:
### 5.1 `004_architecture_impact_map.md` 需补充
1. **§1.4 调用链路**:新增 teacher / student / parent 仪表盘调用链路(当前仅记录 admin
2. **dashboard 模块章节**:补充 `actions.ts`(新增)、`hooks/`(新增)、`lib/`(新增)描述
3. **parent 模块章节**:标注 parent-dashboard 组件的归属决策
### 5.2 `005_architecture_data.json` 需修改
1. `modules.dashboard` 节点:
- 新增 `exports``getAdminDashboardData``getTeacherDashboardData`(新增)、`getStudentDashboardData`(新增)、`getParentDashboardData`(迁移或代理)
- 新增 `actions``getAdminDashboardAction`
- 新增 `hooks``useTeacherDashboardMetrics``useStudentDashboardMetrics`
2. `permissions` 节点:新增 `DASHBOARD_*_READ` 四个权限点
3. `routes` 节点:补充 teacher/student/parent dashboard 调用链
4. `dependencyMatrix`:更新 dashboard → classes/homework/users 的依赖关系(通过 actions 层而非页面层)
### 5.3 翻译文件结构示例
```
src/shared/i18n/messages/
├─ zh-CN/
│ └─ dashboard.json # 新增
└─ en/
└─ dashboard.json # 新增
```
`dashboard.json` 结构示例zh-CN
```json
{
"title": {
"admin": "管理控制台",
"teacher": "教师工作台",
"student": "学生中心",
"parent": "家长中心"
},
"greeting": {
"morning": "早上好",
"afternoon": "下午好",
"evening": "晚上好",
"welcome": "欢迎回来"
},
"stats": {
"users": "用户总数",
"classes": "班级数",
"activeSessions": "活跃会话",
"toGrade": "待批改",
"enrolledClasses": "已选课程",
"averageScore": "平均分",
"classRank": "班级排名",
"graded": "已批改",
"dueSoon": "即将到期",
"overdue": "已逾期"
},
"quickActions": {
"importUsers": "批量导入用户",
"newAnnouncement": "发布公告",
"approveSchedule": "审批课表变更",
"autoSchedule": "自动排课",
"fileManagement": "文件管理",
"attendanceOverview": "考勤总览"
},
"todo": {
"title": "今日待办",
"toGrade": "待批改作业",
"todayAttendance": "今日待考勤",
"activeAssignments": "进行中作业",
"empty": "今日无待办事项"
},
"empty": {
"noUsers": "暂无用户",
"noChildren": "未绑定孩子",
"allGraded": "全部批改完成!"
},
"error": {
"loadFailed": "页面加载失败",
"retry": "重试"
}
}
```