基于 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 / 文件清单)
18 KiB
仪表盘模块审计报告
审查日期: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 | 直接调用 getAdminDashboardData(),无任何 auth/permission 校验 |
"所有 Server Action 必须调用 requirePermission() 进行权限校验" |
| teacher/dashboard/page.tsx | 仅调用 getAuthContext(),未校验任何权限点 |
同上 |
| student/dashboard/page.tsx | 无任何 auth 调用,完全依赖 layout 守卫 | 同上 |
| parent/dashboard/page.tsx | 调用 requireAuth(),未校验具体权限 |
同上 |
| permissions.ts | 无 dashboard 相关权限点定义 | 权限体系不完整 |
后果:admin 仪表盘数据(含全校用户数、活跃会话数、最近注册用户列表)可被任意已登录用户访问,属于严重越权。即使 layout 层有路由组守卫,data-access 层仍缺乏二次校验,不符合"Server Action 二次校验"要求。
2.2 架构分层:页面层越权编排 + 模块归属错位(P0)
| 位置 | 问题 | 违反规则 |
|---|---|---|
| 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 L36-86 | 页面层包含 weekday 转换、作业状态统计、排序切片等 80 行业务逻辑 | "Server Actions / Data Access 模块"应承担编排职责;纯逻辑应抽为 hooks/纯函数 |
| parent-dashboard.tsx | 家长仪表盘组件位于 modules/parent 而非 modules/dashboard |
仪表盘模块不完整,四角色仪表盘分散在两个模块 |
dashboard 模块无 actions.ts |
缺失编排层 | "模块标准结构"要求 actions.ts(编排层) |
后果:页面层臃肿、逻辑不可复用、不可测试;新增角色需复制粘贴整页编排逻辑。
2.3 角色硬编码(P0)
| 位置 | 代码 | 违反规则 |
|---|---|---|
| dashboard/page.tsx L12-15 | roles.includes("admin") / roles.includes("student") / roles.includes("parent") |
"前端权限判断统一使用 usePermission().hasPermission(),严禁出现 role === 'xxx' 硬编码" |
| auth-guard.ts L69/L74/L86/L118/L131 | roleNames.includes("admin"/"teacher"/"student"/"parent") |
同上(dataScope 解析也基于角色硬编码) |
后果:新增角色(如 grade_head 已存在但未处理仪表盘重定向)无法正确路由;权限策略变更需改多处代码。
2.4 国际化:零覆盖 + 中英混杂(P0)
| 位置 | 问题 |
|---|---|
| admin-dashboard.tsx | L34 "Dashboard"、L63 "Users" 为英文;L74 "批量导入用户"、L113 "用户增长趋势(近30天)" 为中文 — 同一文件中英混杂 |
| teacher-dashboard-header.tsx L13-16 | greeting = "早上好"/"下午好"/"晚上好" 硬编码 |
| teacher-dashboard-view.tsx L53-55 | "待批改作业" / "今日待考勤" / "进行中作业" 硬编码 |
| student-stats-grid.tsx | "Enrolled Classes" / "Average Score" 等全英文硬编码 |
| parent-dashboard.tsx L28-31 | "Good morning" / "Good afternoon" 硬编码 |
| 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 | 6 张 Card + 1 张表格,任一数据源异常导致整页崩溃 |
| teacher-dashboard-view.tsx | 7 个子区块,无独立 Suspense 包裹 |
| error.tsx 文案 | "页面加载失败" 硬编码中文,未 i18n |
违反规则:"每个独立的数据区块必须用 React Error Boundary 包裹"、"异步数据使用 React Suspense + 骨架屏"。
后果:单个 Widget 故障导致整页不可用;无法流式渲染,首屏白屏时间长。
2.6 可测试性:业务逻辑与 UI 耦合(P1)
| 位置 | 耦合的逻辑 |
|---|---|
| teacher-dashboard-view.tsx L18-56 | toWeekday、todayScheduleItems 过滤排序、toGradeCount/submissionRate 计算、todoItems 聚合 — 全部内联在组件中 |
| student/dashboard/page.tsx L13-86 | toWeekday、dueSoonCount/overdueCount/gradedCount 单次遍历统计、upcomingAssignments 排序切片 — 80 行纯逻辑在 Server Component 中 |
| 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.4 调用链路:新增 teacher / student / parent 仪表盘调用链路(当前仅记录 admin)
- dashboard 模块章节:补充
actions.ts(新增)、hooks/(新增)、lib/(新增)描述 - parent 模块章节:标注 parent-dashboard 组件的归属决策
5.2 005_architecture_data.json 需修改
modules.dashboard节点:- 新增
exports:getAdminDashboardData、getTeacherDashboardData(新增)、getStudentDashboardData(新增)、getParentDashboardData(迁移或代理) - 新增
actions:getAdminDashboardAction等 - 新增
hooks:useTeacherDashboardMetrics、useStudentDashboardMetrics
- 新增
permissions节点:新增DASHBOARD_*_READ四个权限点routes节点:补充 teacher/student/parent dashboard 调用链dependencyMatrix:更新 dashboard → classes/homework/users 的依赖关系(通过 actions 层而非页面层)
5.3 翻译文件结构示例
src/shared/i18n/messages/
├─ zh-CN/
│ └─ dashboard.json # 新增
└─ en/
└─ dashboard.json # 新增
dashboard.json 结构示例(zh-CN):
{
"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": "重试"
}
}