diff --git a/docs/architecture/004_architecture_impact_map.md b/docs/architecture/004_architecture_impact_map.md index a738dda..29e4ab9 100644 --- a/docs/architecture/004_architecture_impact_map.md +++ b/docs/architecture/004_architecture_impact_map.md @@ -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) | --- diff --git a/docs/architecture/005_architecture_data.json b/docs/architecture/005_architecture_data.json index d90fcca..6d15dba 100644 --- a/docs/architecture/005_architecture_data.json +++ b/docs/architecture/005_architecture_data.json @@ -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", + "deps": [ + "shared/lib/auth-guard.requirePermission", + "dashboard/data-access.getAdminDashboardData", + "Permissions.DASHBOARD_ADMIN_READ" + ], + "usedBy": [ + "admin/dashboard/page.tsx" + ] + }, + { + "name": "getTeacherDashboardAction", + "signature": "() => Promise", + "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" ] } ], diff --git a/docs/architecture/audit/dashboard-audit-report.md b/docs/architecture/audit/dashboard-audit-report.md new file mode 100644 index 0000000..9270827 --- /dev/null +++ b/docs/architecture/audit/dashboard-audit-report.md @@ -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 各写一套 `
`,无统一 `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` 等全部数据就绪后才渲染 | +| 无 `` 包裹 | 无法流式渲染,首屏 TTFB 到 FCP 全部阻塞 | + +**违反规则**:"优先使用 React Server Components 获取初始数据;客户端组件仅负责交互;支持流式渲染"。 + +### 2.9 可访问性(P2) + +| 位置 | 问题 | +|------|------| +| admin-dashboard.tsx | 表格无 `caption`,快捷操作 Card 作为链接无 `aria-label` | +| teacher-dashboard-view.tsx | 布局 div 无语义化标签(`
` / `