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 / 文件清单)
This commit is contained in:
SpecialX
2026-06-22 15:50:56 +08:00
parent 2548f70f40
commit 868ac5f9cf
28 changed files with 1507 additions and 399 deletions

View File

@@ -866,30 +866,44 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
## 2.12 dashboard仪表盘模块
**职责**:管理员/教师/学生仪表盘数据聚合 + 管理员趋势图表
**职责**:管理员/教师/学生/家长仪表盘数据聚合 + 权限校验 + i18n + 纯逻辑工具函数
**导出函数**
- Data-access`getAdminDashboardData` / `getTeacherDashboardData` / `getStudentDashboardData`
- Components`AdminDashboardView` / `UserGrowthChart`recharts 折线图,复用于用户增长趋势与作业提交趋势
- Actions`getAdminDashboardAction` / `getTeacherDashboardAction` / `getStudentDashboardAction` / `getParentDashboardAction`(均调用 `requirePermission()` 校验对应 `DASHBOARD_*_READ` 权限)
- Data-access`getAdminDashboardData`(并行调用 6 个模块 stats 函数
- Lib 纯函数:`toWeekday` / `countStudentAssignments` / `sortUpcomingAssignments` / `filterTodaySchedule` / `computeTeacherMetrics` / `getGreetingKey`
- Components`AdminDashboardView` / `TeacherDashboardView` / `StudentDashboard` / `UserGrowthChart`(均接入 next-intl i18n
**依赖关系**
- 依赖:`shared/*``@/auth``classes`(通过 data-access,合理)、`homework`(通过 data-access,合理)、`grades`(合理)、`users`/`textbooks`/`questions`/`exams`(通过各模块 dashboard stats 函数P0-4 已修复)、`recharts`UserGrowthChart
- 依赖:`shared/*``@/auth``classes`(通过 data-access`homework`(通过 data-access`users`(通过 data-access`parent`(通过 data-access.getParentDashboardData`textbooks`/`questions`/`exams`(通过各模块 dashboard stats 函数)、`recharts``next-intl`
- 被依赖:无
**权限点**
- `DASHBOARD_ADMIN_READ`admin
- `DASHBOARD_TEACHER_READ`teacher
- `DASHBOARD_STUDENT_READ`student
- `DASHBOARD_PARENT_READ`parent
**已知问题**
- ✅ P0-4 已修复:`getAdminDashboardData` 改为并行调用各模块 dashboard stats 函数`getUsersDashboardStats`/`getClassesDashboardStats`/`getTextbooksDashboardStats`/`getQuestionsDashboardStats`/`getExamsDashboardStats`/`getHomeworkDashboardStats`,不再直查跨模块表
- ✅ P1-1 已修复~~教师仪表盘直查 `users` 表获取教师姓名~~ 改为通过 users data-access 获取
-学生/教师仪表盘正确通过各模块 data-access 获取数据
- V1 新增:`AdminDashboardData` 类型新增 `userGrowth`/`homeworkTrend` 字段(`Array<{ date: string; count: number }>``data-access.ts` 当前返回空数组占位,待后续接入真实统计
- ✅ P0-4 已修复:`getAdminDashboardData` 改为并行调用各模块 dashboard stats 函数,不再直查跨模块表
- ✅ P0 已修复2026-06-22所有仪表盘页面通过 `actions.ts` 调用 `requirePermission()` 进行权限校验,不再裸调 data-access
-P0 已修复2026-06-22根重定向页 `/dashboard` 改用 `resolvePermissions()` + 权限点判断,不再 `role === "xxx"` 硬编码
- ✅ P0 已修复2026-06-22所有仪表盘组件接入 next-intl`useTranslations` / `getTranslations`),翻译文件 `messages/{zh-CN,en}/dashboard.json`
- ✅ P1 已修复2026-06-22业务逻辑weekday 转换、作业统计、教师指标计算、问候语时段)抽取至 `lib/dashboard-utils.ts` 纯函数,与 UI 分离
- V1 新增:`AdminDashboardData` 类型含 `userGrowth`/`homeworkTrend` 字段,`data-access.ts` 当前返回空数组占位,待后续接入真实统计
- parent 仪表盘组件仍位于 `modules/parent/components/parent-dashboard.tsx`,通过 `dashboard/actions.getParentDashboardAction` 调用(架构决策:保留在 parent 模块以避免移动文件破坏其他 import
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `data-access.ts` | - | 仪表盘数据聚合P0-4 已修复,通过各模块 data-access 获取数据V1 新增 userGrowth/homeworkTrend 占位字段 |
| `types.ts` | - | 类型定义V1 新增 userGrowth/homeworkTrend 字段 |
| `components/admin-dashboard/admin-dashboard.tsx` | - | 管理员仪表盘视图V1 新增趋势图表区域:用户增长趋势 + 作业提交趋势 |
| `components/admin-dashboard/user-growth-chart.tsx` | - | recharts 折线图组件V1 新增,复用于两个趋势卡片) |
| `components/*` | 14 文件 | 三种角色仪表盘组件 |
| `actions.ts` | 120 | 4 个 Server Action编排层`requirePermission()` 权限校验 |
| `data-access.ts` | 49 | admin 仪表盘数据聚合(并行调用各模块 stats 函数 |
| `lib/dashboard-utils.ts` | 170 | 纯逻辑工具函数weekday / 统计 / 排序 / 指标计算 / 问候语 |
| `types.ts` | 74 | Admin / Teacher / Student 类型定义 |
| `components/admin-dashboard/admin-dashboard.tsx` | 267 | 管理员仪表盘视图i18n |
| `components/admin-dashboard/user-growth-chart.tsx` | 50 | recharts 折线图i18n |
| `components/teacher-dashboard/*.tsx` | 9 文件 | 教师仪表盘组件i18n |
| `components/student-dashboard/*.tsx` | 6 文件 | 学生仪表盘组件i18n |
---

View File

@@ -121,7 +121,11 @@
"LESSON_PLAN_READ": "lesson_plan:read",
"LESSON_PLAN_UPDATE": "lesson_plan:update",
"LESSON_PLAN_DELETE": "lesson_plan:delete",
"LESSON_PLAN_PUBLISH": "lesson_plan:publish"
"LESSON_PLAN_PUBLISH": "lesson_plan:publish",
"DASHBOARD_ADMIN_READ": "dashboard:admin_read",
"DASHBOARD_TEACHER_READ": "dashboard:teacher_read",
"DASHBOARD_STUDENT_READ": "dashboard:student_read",
"DASHBOARD_PARENT_READ": "dashboard:parent_read"
},
"rolePermissions": {
"admin": [
@@ -176,7 +180,8 @@
"ELECTIVE_MANAGE",
"ELECTIVE_READ",
"EXAM_PROCTOR",
"EXAM_PROCTOR_READ"
"EXAM_PROCTOR_READ",
"DASHBOARD_ADMIN_READ"
],
"teacher": [
"EXAM_CREATE",
@@ -216,7 +221,8 @@
"ELECTIVE_MANAGE",
"ELECTIVE_READ",
"EXAM_PROCTOR",
"EXAM_PROCTOR_READ"
"EXAM_PROCTOR_READ",
"DASHBOARD_TEACHER_READ"
],
"student": [
"EXAM_READ",
@@ -236,7 +242,8 @@
"MESSAGE_DELETE",
"DIAGNOSTIC_READ",
"ELECTIVE_SELECT",
"ELECTIVE_READ"
"ELECTIVE_READ",
"DASHBOARD_STUDENT_READ"
],
"parent": [
"EXAM_READ",
@@ -248,7 +255,8 @@
"ATTENDANCE_READ",
"MESSAGE_SEND",
"MESSAGE_READ",
"MESSAGE_DELETE"
"MESSAGE_DELETE",
"DASHBOARD_PARENT_READ"
],
"grade_head": [
"EXAM_CREATE",
@@ -5724,8 +5732,96 @@
},
"dashboard": {
"path": "src/modules/dashboard",
"description": "各角色仪表盘数据聚合与展示",
"description": "各角色仪表盘数据聚合与展示(含权限校验 + i18n + 纯逻辑工具函数)",
"exports": {
"actions": [
{
"name": "getAdminDashboardAction",
"signature": "() => Promise<AdminDashboardData>",
"deps": [
"shared/lib/auth-guard.requirePermission",
"dashboard/data-access.getAdminDashboardData",
"Permissions.DASHBOARD_ADMIN_READ"
],
"usedBy": [
"admin/dashboard/page.tsx"
]
},
{
"name": "getTeacherDashboardAction",
"signature": "() => Promise<TeacherDashboardData & { metrics: TeacherDashboardMetrics }>",
"deps": [
"shared/lib/auth-guard.requirePermission",
"classes/data-access.getTeacherClasses/getClassSchedule/getTeacherIdForMutations",
"homework/data-access.getHomeworkAssignments/getHomeworkSubmissions/getTeacherGradeTrends",
"users/data-access.getUserBasicInfo",
"dashboard/lib/dashboard-utils.computeTeacherMetrics",
"Permissions.DASHBOARD_TEACHER_READ"
],
"usedBy": [
"teacher/dashboard/page.tsx"
]
},
{
"name": "getStudentDashboardAction",
"signature": "() => Promise<{ student, dashboardProps }>",
"deps": [
"shared/lib/auth-guard.requirePermission",
"users/data-access.getCurrentStudentUser",
"classes/data-access.getStudentClasses/getStudentSchedule",
"homework/data-access.getStudentHomeworkAssignments/getStudentDashboardGrades",
"dashboard/lib/dashboard-utils.countStudentAssignments/sortUpcomingAssignments/toWeekday/filterTodaySchedule",
"Permissions.DASHBOARD_STUDENT_READ"
],
"usedBy": [
"student/dashboard/page.tsx"
]
},
{
"name": "getParentDashboardAction",
"signature": "() => Promise<{ data, hasChildren }>",
"deps": [
"shared/lib/auth-guard.requirePermission",
"parent/data-access.getParentDashboardData",
"Permissions.DASHBOARD_PARENT_READ"
],
"usedBy": [
"parent/dashboard/page.tsx"
]
}
],
"lib": [
{
"name": "toWeekday",
"signature": "(d: Date) => Weekday",
"purpose": "Date 转 1-7 周几(周一=1周日=7"
},
{
"name": "countStudentAssignments",
"signature": "(assignments, now, dueSoonWindowDays?) => StudentAssignmentStats",
"purpose": "单次遍历统计学生作业:即将到期/已逾期/已批改"
},
{
"name": "sortUpcomingAssignments",
"signature": "(assignments, limit?) => StudentHomeworkAssignmentListItem[]",
"purpose": "按截止日期升序排序取前 N 条"
},
{
"name": "filterTodaySchedule",
"signature": "(schedule, weekday, classNameById?) => StudentTodayScheduleItem[] | TeacherTodayScheduleItem[]",
"purpose": "筛选指定周几课表并按开始时间排序"
},
{
"name": "computeTeacherMetrics",
"signature": "(classes, schedule, assignments, submissions, gradeTrends, now) => TeacherDashboardMetrics",
"purpose": "计算教师仪表盘派生指标:待批改数/进行中作业/平均分/提交率/今日课表/待批改列表"
},
{
"name": "getGreetingKey",
"signature": "(now: Date) => 'morning' | 'afternoon' | 'evening'",
"purpose": "根据当前小时返回问候语时段 key"
}
],
"dataAccess": [
{
"name": "getAdminDashboardData",
@@ -5740,7 +5836,7 @@
"DataScope"
],
"usedBy": [
"admin/dashboard/page.tsx"
"dashboard/actions.getAdminDashboardAction"
]
}
],

View File

@@ -0,0 +1,320 @@
# 仪表盘模块审计报告
> 审查日期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": "重试"
}
}
```