Files
Edu/apps/portal-shell/docs/needtodo/student-NeedTodo.md
SpecialX 062d9e9582 feat(portal-shell): 管理域 §9.4 B5 全量迁移(24 + 21 补充批次共 44 页 + 42 features)
按 ARCHITECTURE.md §9.4 规划口径 + admin-NeedTodo.md §四补充批次完成管理域全量页面迁移:

【§9.4 规划 24 页(B5)】
- users(2) + roles(1) + permissions(1) + audit-logs(4) + invitation-codes(1)
- school(6: redirect/schools/classes/departments/academic-year/grades)
- announcements(1) + files(1) + ai-settings(1) + system(1) + viewports(1)
- students(1) + teachers(1) + organization(1) + plugins(1, config-service)
- 仪表盘已存在(/shell/admin/page.tsx)

【§四补充批次 21 页】
- course-plans(4) + elective(4) + questions(1) + lesson-plans(2) + error-book(1)
- scheduling(3: auto/changes/rules) + attendance(1) + curriculum-map(1)
- announcements 详情/编辑(2) + roles/[id] 详情(1) + users/import(1)

【实现要点】
- 全部使用 ListPageShell + loading/error/empty 三态规范(§11.3 DoD)
- 走 lib/api hooks;未就绪契约走 MSW + @contract-pending 注释(§11.4)
- 文案走 useTranslations(zh-CN + en 两份同步更新)
- 42 个 features/<domain>/transformations.ts 纯函数 + 配套 vitest 单测
- catch 块统一 notify.error;无空 catch;lint:tokens 通过
- 路由全部登记到 route-permissions.ts(39 EXACT + 8 PREFIX)

【验收】
- tsc --noEmit: 0 errors
- ESLint src: 0 errors (4 generated-files warnings, pre-existing)
- lint:tokens: 0 errors
- vitest: 1639/1639 passed (含 23 admin 测试文件 671 用例)
- check:routes: PASS (143 routes, 4 ghost entries pre-existing)
- check:pages: PASS (146 pages)
- check:codegen: PASS
- arch:scan: 24 modules, 8262 symbols

关联:ARCHITECTURE.md §9.4 / §10 P5 / §11.3 DoD / §11.6
2026-07-24 23:07:20 +08:00

796 lines
49 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.
# 学生域Student待完成功能分析
> 参考项目:`e:\desktop\CICD\src\app\(dashboard)\student\`
> 当前项目:`e:\Desktop\Edu\apps\portal-shell\src\app\shell\student\`
> 规划依据:`apps/portal-shell/ARCHITECTURE.md` §9.236 页B3 批次)
> 分析日期2026-07-24
> 任务范围:仅分析,不写代码
---
## 一、页面完成度总览
### 1.1 portal-shell 学生域现状
当前 `apps/portal-shell/src/app/shell/student/` 下**仅有 1 个页面**
| 路径 | 状态 | 说明 |
| ------------------------- | --------- | ------------------------------------------------ |
| `/shell/student/page.tsx` | ✅ 已完成 | 学生仪表盘(仅基础版,缺 trend/weakness 详情页) |
### 1.2 完成度统计
| 状态 | 数量 | 说明 |
| ------------ | ------ | ---------------------------------------------------------- |
| ✅ 已完成 | 1 | `/shell/student/page.tsx`(仪表盘基础版,仍缺 2 个详情页) |
| 🟡 部分完成 | 1 | dashboard 模块基础页有trend/weakness 详情页缺) |
| ❌ 缺失 | 34 | 见下方模块清单 |
| **规划总数** | **36** | ARCHITECTURE.md §9.2 B3 批次 |
### 1.3 portal-shell 仪表盘与 CICD 仪表盘的技术差异(关键架构差异)
| 维度 | CICD 仪表盘 | portal-shell 仪表盘 |
| -------- | ---------------------------------------------------------------------- | --------------------------------------------------- |
| 渲染模式 | Server Component`async function` | Client Component`"use client"` |
| 数据获取 | Server Action `getStudentDashboardAction()` + Suspense 流式渲染 | `useStudentDashboard()` Hook`@/lib/api`,走 BFF |
| 国际化 | `next-intl` `getTranslations` | 无(硬编码中文) |
| 权限校验 | `requirePermission` + `getAuthContext` | 无 |
| 视图组件 | `StudentDashboard` 视图模块(拆分到 `@/modules/dashboard/components` | 直接内联在 page.tsx |
| 数据来源 | 直查 DBDrizzle | 走 data-ana 微服务聚合 |
> **重要提示**portal-shell 已转向微服务 + BFF 架构,迁移时**不能照搬 CICD 的 Server Action + Drizzle 直查模式**。学生页面应通过 `teacher-bff` / `data-ana` 等服务的 gRPC/HTTP API 获取数据,前端通过 hooks 消费。
---
## 二、缺失页面清单(按模块分组)
### 2.1 dashboard 模块3 页规划1 已完成2 缺失)
#### 2.1.1 ✅ `/shell/student/page.tsx`(已完成)
- **portal-shell 实现**:基础仪表盘,显示平均分 / 班级排名 / 待交作业 + 薄弱知识点 + 近期成绩趋势
- **技术栈**Client Component + `useStudentDashboard()` Hook + `DashboardShell` / `StatCard` 共享组件
- **CICD 参考实现**`e:\desktop\CICD\src\app\(dashboard)\student\dashboard\page.tsx`
- **CICD 技术栈**Server Component + `getStudentDashboardAction()` Server Action + `StudentDashboard` 视图组件 + Suspense 流式渲染
- **ARCHITECTURE.md 契约**:✅ `studentDashboard`
#### 2.1.2 ❌ `/shell/student/trend`(学习趋势详情页,缺失)
- **CICD 参考实现**CICD 无独立 `/trend` 页面(融合在 dashboard 中以 `recent_trends` 字段呈现)
- **功能描述**:学生学习趋势详情页,应展示更完整的历史成绩趋势、知识点掌握趋势、对比基线
- **技术栈建议**:复用 `learningTrend` 契约 + 趋势图组件Recharts/Echarts
- **ARCHITECTURE.md 契约**:✅ `learningTrend`
- **CICD 关键代码片段**dashboard 中仅以单条 `recent_trends: { date, score }` 呈现,需 portal-shell 自行扩展为详情页
#### 2.1.3 ❌ `/shell/student/weakness`(薄弱知识点详情页,缺失)
- **CICD 参考实现**CICD 无独立 `/weakness` 页面(融合在 dashboard 中以 `weak_points: { title, mastery, error_count }` 字段呈现)
- **功能描述**:学生薄弱知识点详情页,应展示完整的薄弱知识点列表、按学科分组、关联错题、推荐练习
- **技术栈建议**:复用 `studentWeakness` 契约 + 错题本 / 练习模块联动跳转
- **ARCHITECTURE.md 契约**:✅ `studentWeakness`
- **CICD 关键代码片段**dashboard 中仅以单条 `weak_points` 呈现,需 portal-shell 自行扩展
---
### 2.2 grades 模块2 页规划0 已完成2 缺失)
#### 2.2.1 ❌ `/shell/student/grades`(我的成绩列表,缺失)
- **CICD 参考实现**`e:\desktop\CICD\src\app\(dashboard)\student\grades\page.tsx`
- **功能描述**
- 学生本人成绩汇总(姓名 + 记录数)
- 学科 / 类型 / 学期 / 关键字 4 维过滤器
- **成绩趋势卡片**(含班级平均对比线)
- **排名趋势卡片**
- **班级分布直方图**(隐私保护视图,仅展示本人位置标注)
- **学生纵向成长档案**(跨学年/学期聚合)
- 成绩汇总明细表
- **技术栈**
- Server Component`async function`+ `force-dynamic`
- Server Actions`getStudentGradeSummary` / `getRankingTrend` / `getClassAverageTrend` / `getStudentPositionInClassDistribution` / `getStudentGrowthArchive` / `getSubjectOptions`
- `requirePermission(Permissions.GRADE_RECORD_READ)` + `ctx.dataScope`
- 组件:`StudentGradeSummary` / `StudentGradeFilters` / `GradeTrendCard` / `RankingTrendCard` / `GradeDistributionChart` / `GrowthArchiveChart` / `EmptyState`
- searchParams 过滤(`q` / `subject` / `type` / `semester`
- **ARCHITECTURE.md 契约**:❌(列表,未定义)
#### 2.2.2 ❌ `/shell/student/grades/report-card`(成绩报告卡,缺失)
- **CICD 参考实现**`e:\desktop\CICD\src\app\(dashboard)\student\grades\report-card\page.tsx`
- **功能描述**
- 学年 / 学期可切换的成绩报告卡
- 报告卡视图(学生本人维度聚合)
- **打印操作**`ReportCardPrintAction`
- 返回成绩列表的面包屑
- **技术栈**
- Server Component + `force-dynamic`
- Server Actions`getReportCardData(ctx.userId, ctx.dataScope, { academicYearId, semester })` + `getAcademicYears()`
- `requirePermission(Permissions.GRADE_RECORD_READ)`
- 组件:`ReportCardView` / `ReportCardPrintAction` / `Button` / `EmptyState`
- searchParams`academicYearId` / `semester`
- **ARCHITECTURE.md 契约**:❌(未单独定义)
---
### 2.3 exams 模块3 页规划0 已完成3 缺失)⚠️ 重点
> **CICD 参考项目无 exams 模块学生页面**。学生考试相关功能需 portal-shell 从零设计。
#### 2.3.1 ❌ `/shell/student/exams`(我的考试列表,缺失)
- **CICD 参考实现**:❌ CICD 无对应源页面
- **功能描述**:学生视角的考试列表,应展示:
- 即将到来的考试(含倒计时、考场、座位号)
- 已结束考试(含成绩、查看结果入口)
- 按学科 / 状态过滤
- **技术栈建议**:参考 CICD `learning/assignments/page.tsx` 的列表分组 + 状态徽章模式
- **ARCHITECTURE.md 契约**:🟡(部分,需补充)
#### 2.3.2 ❌ `/shell/student/exams/[id]/result`(考试结果页,缺失)
- **CICD 参考实现**:❌ CICD 无对应源页面(可参考 `learning/assignments/[assignmentId]/result/page.tsx` 的同构模式)
- **功能描述**
- 考试得分、排名、班级均分对比
- 题目作答详情(每题对错、参考答案、学生答案)
- 知识点掌握情况
- 错题一键加入错题本
- **技术栈建议**:参考 CICD `HomeworkSubmissionResult` 组件 + `getStudentSubmissionResult` 数据访问模式
- **ARCHITECTURE.md 契约**:🟡(部分,需补充)
#### 2.3.3 ❌ `/shell/student/exams/[id]/take`(考试作答工作台,缺失)⚠️ 关键
- **CICD 参考实现**:❌ CICD 无对应源页面(可参考 `learning/assignments/[assignmentId]/page.tsx``HomeworkTakeView` 同构模式)
- **功能描述**:考试作答工作台,应包含:
- 题目导航栏(题号、未答/已答/标记状态)
- 题目作答区(单选/多选/填空/简答)
- 倒计时(自动提交)
- 答案本地暂存(防刷新丢失)
- 提交确认 + 二次确认
- 中途断网恢复
- **技术栈建议**
- 客户端组件(`HomeworkTakeView` 是 client component
- 答案本地存储localStorage / IndexedDB
- 自动保存定时器
- 提交走 Server Action / BFF API
- **ARCHITECTURE.md 契约**:🟡(部分,需补充)
> ⚠️ **此页是学生域最复杂、最关键的页面**,需在 B3 中重点投入。CICD 的 `HomeworkTakeView` 是最近的同构参考,但其路由是 `learning/assignments/[assignmentId]` 而非 `exams/[id]/take`。
---
### 2.4 homework 模块3 页规划0 已完成3 缺失)
> **CICD 实际路由是 `learning/assignments/*`**ARCHITECTURE.md §9.2 计划将其提升到 `homework/*` 顶层。
#### 2.4.1 ❌ `/shell/student/homework`(我的作业列表,缺失)
- **CICD 参考实现**`e:\desktop\CICD\src\app\(dashboard)\student\learning\assignments\page.tsx`
- **功能描述**
- 作业列表,按**学科分组**展示
- 每组内按"未答 / 已答"二分桶PERF-05 单次遍历分桶)
- 每张卡片:标题、状态徽章、截止时间、剩余尝试次数、最新得分、逾期提示
- 学科 dot 颜色hash 稳定映射)
- 状态过滤all/pending/submitted/graded+ 关键字搜索
- `AssignmentCard``React.memo` 优化高频渲染
- **技术栈**
- Server Component + `force-dynamic`
- Server Action`getStudentHomeworkAssignments(student.id)`
- 组件:`AssignmentFilters` / `StatusBadge` / `Card` / `EmptyState` / `memo(AssignmentCard)`
- 工具:`formatDate` / `getParam` / `cn` / `STUDENT_HOMEWORK_PROGRESS_VARIANT`
- **ARCHITECTURE.md 契约**:🟡(部分)
#### 2.4.2 ❌ `/shell/student/homework/[id]/submit`(作业作答/提交,缺失)⚠️ 关键
- **CICD 参考实现**`e:\desktop\CICD\src\app\(dashboard)\student\learning\assignments\[assignmentId]\page.tsx`
- **功能描述**
- 根据 `submission.status` 分流:
- `graded` / `submitted` → 进入 `HomeworkReviewView`(查看提交后/批改后视图)
- `not_started` / `in_progress` → 进入 `HomeworkTakeView`(作答工作台)
- 顶部展示作业标题、截止时间、最大尝试次数
- **技术栈**
- Server Component外壳+ Client Component`HomeworkTakeView` / `HomeworkReviewView`
- Server Action`getStudentHomeworkTakeData(assignmentId, student.id)`
- 组件:`HomeworkTakeView` / `HomeworkReviewView`
- **ARCHITECTURE.md 契约**:🟡(部分)
#### 2.4.3 ❌ `/shell/student/homework/[id]/analysis`(作业分析/结果,缺失)
- **CICD 参考实现**`e:\desktop\CICD\src\app\(dashboard)\student\learning\assignments\[assignmentId]\result\page.tsx`
- **功能描述**
- 作业标题 + 提交结果详情
- 通过 `HomeworkSubmissionResult` 组件展示分析(含每题得分、参考答案等)
- **技术栈**
- Server Component + `force-dynamic`
- Server Actions`getHomeworkAssignmentById(assignmentId)` + `getStudentSubmissionResult(assignmentId, studentId)`
- 通过 `getSession()` 获取 studentId`requirePermission` 模式,需注意)
- 组件:`HomeworkSubmissionResult`
- **ARCHITECTURE.md 契约**:🟡(部分)
---
### 2.5 schedule 模块1 页规划0 已完成1 缺失)
#### 2.5.1 ❌ `/shell/student/schedule`(学生课表,缺失)
- **CICD 参考实现**`e:\desktop\CICD\src\app\(dashboard)\student\schedule\page.tsx`
- **功能描述**
- 学生个人课表(按班级聚合)
- 班级过滤器(`classId` searchParam默认 `all`
- 顶部标题 + 班级筛选器
- `StudentScheduleView` 渲染课表
- **技术栈**
- Server Component + `force-dynamic` + `generateMetadata`
- Server Actions`getStudentClasses(student.id)` + `getStudentSchedule(student.id)`
- `requirePermission(Permissions.CLASS_READ)` + `getCurrentStudentUser()`
- 组件:`StudentScheduleFilters` / `StudentScheduleView` / `EmptyState`
- searchParams`classId`
- **ARCHITECTURE.md 契约**:❌
---
### 2.6 attendance 模块1 页规划0 已完成1 缺失)
#### 2.6.1 ❌ `/shell/student/attendance`(学生考勤,缺失)
- **CICD 参考实现**`e:\desktop\CICD\src\app\(dashboard)\student\attendance\page.tsx`
- **功能描述**
- 学生本人考勤汇总(出勤率、迟到/早退/请假次数)
- 通过 `StudentAttendanceView` 渲染详细记录
- 无数据时显示 `EmptyState`
- **技术栈**
- Server Component + `force-dynamic`
- Server Action`getStudentAttendanceSummary(ctx.userId)`
- `requirePermission(Permissions.ATTENDANCE_READ)` + `getAuthContext()`
- 组件:`StudentAttendanceView` / `EmptyState`
- **ARCHITECTURE.md 契约**:❌
---
### 2.7 classes 模块1 页规划0 已完成1 缺失)
#### 2.7.1 ❌ `/shell/student/classes`(我的班级,缺失)
- **CICD 参考实现**:❌ CICD 学生端无独立 `/classes` 路由(班级信息融合在 `learning/courses/*` 中)
- **功能描述**
- 学生所在的班级列表(含班主任、教室、学校、年级、活跃状态)
- 区别于 courses课程classes 侧重"行政班/教学班"维度courses 侧重"学科课程"维度
- 可跳转到该班的课表、作业、同学(受权限限制)
- **技术栈建议**:参考 CICD `learning/courses/page.tsx` 的列表 + 过滤模式
- **ARCHITECTURE.md 契约**:❌ `myClasses`
---
### 2.8 courses 模块2 页规划0 已完成2 缺失)
#### 2.8.1 ❌ `/shell/student/courses`(课程列表,缺失)
- **CICD 参考实现**`e:\desktop\CICD\src\app\(dashboard)\student\learning\courses\page.tsx`
- **功能描述**
- 学生报名的课程(班级)列表
- 关键字搜索(名称 / 教师 / 学校 / 班主任)
- 通过 `StudentCoursesView` 渲染
- 仅有班级时才显示过滤器
- **技术栈**
- Server Component + `force-dynamic` + `generateMetadata`
- Server Action`getStudentClasses(student.id)`
- `requirePermission(Permissions.CLASS_READ)` + `getCurrentStudentUser()`
- 组件:`CourseFilters` / `StudentCoursesView` / `EmptyState`
- searchParams`q`
- **ARCHITECTURE.md 契约**:❌
#### 2.8.2 ❌ `/shell/student/courses/[id]`(课程详情,缺失)
- **CICD 参考实现**`e:\desktop\CICD\src\app\(dashboard)\student\learning\courses\[classId]\page.tsx`
- **功能描述**
- 班级(课程)详情页:名称、年级、班主任、教室、学校
- **三列卡片**:教师信息(姓名 + 邮箱 mailto/ 学校信息 / 教室信息
- **本班课表**:按 weekday 排序的列表
- 顶部按钮:跳转完整课表、跳转作业列表
- **技术栈**
- Server Component + `force-dynamic` + `generateMetadata`
- Server Actions`getStudentClassById(student.id, classId)` + `getStudentSchedule(student.id)`
- `requirePermission(Permissions.CLASS_READ)` + `getCurrentStudentUser()`
- 组件:`Card` / `Badge` / `Button` / `EmptyState`
- 路由参数:`classId`
- **ARCHITECTURE.md 契约**:❌
---
### 2.9 course-plans 模块2 页规划0 已完成2 缺失)
#### 2.9.1 ❌ `/shell/student/course-plans`(课程计划列表,缺失)
- **CICD 参考实现**`e:\desktop\CICD\src\app\(dashboard)\student\course-plans\page.tsx`
- **功能描述**
- 学生视角的课程计划列表(仅 `status: "active"`
- 通过 `class_members` scope 过滤可见的 classIds
- `CoursePlanList` 渲染detailBaseHref 指向 `/student/course-plans`
- **技术栈**
- Server Component + `force-dynamic`
- Server Action`getCoursePlans({ status: "active" }, { userId, isAdmin: false, classIds })`
- `requirePermission(Permissions.COURSE_PLAN_READ)`
- 组件:`CoursePlanList`
- **ARCHITECTURE.md 契约**:❌
#### 2.9.2 ❌ `/shell/student/course-plans/[id]`(课程计划详情,缺失)
- **CICD 参考实现**`e:\desktop\CICD\src\app\(dashboard)\student\course-plans\[id]\page.tsx`
- **功能描述**
- 课程计划详情视图(学生只读)
- 通过 class_members scope 过滤
- `CoursePlanDetail` 渲染,关联教材跳转 `/student/learning/textbooks`
- **技术栈**
- Server Component + `force-dynamic`
- Server Action`getCoursePlanById(id, { userId, isAdmin: false, classIds })`
- `requirePermission(Permissions.COURSE_PLAN_READ)`
- 组件:`CoursePlanDetail`
- 路由参数:`id`
- `notFound()` 兜底
- **ARCHITECTURE.md 契约**:❌
---
### 2.10 lesson-plans 模块2 页规划0 已完成2 缺失)
#### 2.10.1 ❌ `/shell/student/lesson-plans`(教案列表,缺失)
- **CICD 参考实现**`e:\desktop\CICD\src\app\(dashboard)\student\lesson-plans\page.tsx`
- **功能描述**
- 学生视角的教案列表(仅 `status: "published"`
- `LessonPlanList` 学生视图模式(`viewMode="student"`
- `LessonPlanProviderSetup` 注入学生角色配置(让筛选生效)
- **技术栈**
- Server Component + `force-dynamic`
- Server Actions`getLessonPlans({ status: "published" }, ctx.dataScope, ctx.userId)` + `getSubjectOptions()`
- `requirePermission(Permissions.LESSON_PLAN_READ)`
- 组件:`LessonPlanList` / `LessonPlanProviderSetup` / `STUDENT_ROLE_CONFIG` / `Skeleton` + `Suspense`
- **ARCHITECTURE.md 契约**:❌
#### 2.10.2 ❌ `/shell/student/lesson-plans/[planId]/view`(教案只读查看,缺失)
- **CICD 参考实现**`e:\desktop\CICD\src\app\(dashboard)\student\lesson-plans\[planId]\view\page.tsx`
- **功能描述**
- 教案只读视图
- **权限收敛**:学生仅可查看本年级已发布教案(`assertPlanInScope`),未发布显示提示
- 拉取教材标题 + 章节标题(并行 `Promise.all`
- `LessonPlanReadonlyView` 渲染文档内容
- **技术栈**
- Server Component + `force-dynamic`
- Server Actions`getLessonPlanById(planId, ctx.userId)` + `getTextbookById` + `getChaptersByTextbookId` + `findChapterById`
- `requirePermission(Permissions.LESSON_PLAN_READ)` + `assertPlanInScope(plan, ctx)`
- 组件:`LessonPlanReadonlyView` / `Skeleton` + `Suspense`
- 路由参数:`planId`
- **ARCHITECTURE.md 契约**:❌
---
### 2.11 textbooks 模块2 页规划0 已完成2 缺失)
#### 2.11.1 ❌ `/shell/student/textbooks`(教材列表,缺失)
- **CICD 参考实现**`e:\desktop\CICD\src\app\(dashboard)\student\learning\textbooks\page.tsx`
- **功能描述**
- 学生视角的教材列表
- **强制按学生所在年级过滤**`getGradeNameById` 解析 gradeId 为年级名称)
- 关键字 / 学科 / 年级 3 维过滤器
- `TextbookCard` 网格4 列响应式)
- 学生端 `hideActions`(无编辑权限)
- **技术栈**
- Server Component + `force-dynamic`
- Server Actions`getTextbooksWithScope(q, subject, grade, { grade: studentGradeName })` + `getGradeNameById`
- `requirePermission(Permissions.TEXTBOOK_READ)` + `getCurrentStudentUser()`
- 组件:`TextbookCard` / `TextbookFilters` / `EmptyState`
- searchParams`q` / `subject` / `grade`
- **ARCHITECTURE.md 契约**:🟡
#### 2.11.2 ❌ `/shell/student/textbooks/[id]/chapters`(教材章节阅读器,缺失)
- **CICD 参考实现**`e:\desktop\CICD\src\app\(dashboard)\student\learning\textbooks\[id]\page.tsx`
- **功能描述**
- 教材阅读器(`TextbookReader`
- 顶部固定标题栏:教材标题 + 学科 Badge + 年级 Badge
- 章节列表 + 阅读区
- **年级校验**:教材年级与学生年级不匹配则 `notFound()`(防越权)
- 学生端不传 `renderQuestionCreator`(无题目创建权限)
- 高度自适应(`h-[calc(100vh-4rem-3rem)]`
- **技术栈**
- Server Component + `force-dynamic`
- Server Actions`getTextbookById(id)` + `getChaptersByTextbookId(id)` + `getGradeNameById`
- `requirePermission(Permissions.TEXTBOOK_READ)` + `getCurrentStudentUser()`
- 组件:`TextbookReader` / `Badge` / `EmptyState`
- 路由参数:`id`
- **ARCHITECTURE.md 契约**:🟡
- **注**ARCHITECTURE.md §9.2 路径为 `/[id]/chapters`CICD 路径为 `/[id]`,迁移时需对齐
---
### 2.12 error-book 模块1 页规划0 已完成1 缺失)
#### 2.12.1 ❌ `/shell/student/error-book`(错题本,缺失)
- **CICD 参考实现**`e:\desktop\CICD\src\app\(dashboard)\student\error-book\page.tsx`
- **功能描述**
- **5 列统计卡片**:总数 / 新增 / 学习中 / 已掌握(含掌握率%/ 待复习(高亮 overdue
- **新增错题对话框**`AddErrorBookDialogWithQuestions`,带题目选择器)
- 筛选器:关键字 / 状态 / 来源 / 仅看待复习
- 错题列表(客户端组件 `StudentErrorBookListClient`
- **AI 上下文注入**`AiClientProvider` + `createCoreAiClientService`
- `WidgetBoundary` 包裹关键 widget降级容错
- `Suspense` 流式渲染过滤器 + 结果列表
- **技术栈**
- Server Component + `force-dynamic`
- Server Actions`getErrorBookItems({ studentId, q, status, sourceType, dueOnly, pageSize: 50 })` + `getErrorBookStats(ctx.userId)`
- `requirePermission(Permissions.ERROR_BOOK_READ)`
- 组件:`StatsGrid` / `AddErrorBookDialogWithQuestions` / `ErrorBookFilters` / `StudentErrorBookListClient` / `WidgetBoundary` / `AiClientProvider`
- 状态枚举守卫:`isErrorBookStatus` / `isErrorBookSource`(类型守卫)
- **ARCHITECTURE.md 契约**:✅
---
### 2.13 learning 模块2 页规划0 已完成2 缺失)
#### 2.13.1 ❌ `/shell/student/learning`(学习中心首页,缺失)
- **CICD 参考实现**`e:\desktop\CICD\src\app\(dashboard)\student\learning\page.tsx`
- **功能描述**
- 学习中心首页3 张导航卡片:
1. **课程**(已报名班级数)
2. **作业**(待交数 + 即将到期数7 天内)
3. **教材**(可用数)
- 每张卡片含图标 / 标题 / 描述 / 统计 / 箭头
- 学生身份校验(`getCurrentStudentUser`),无则显示 `EmptyState`
- **技术栈**
- Server Component + `force-dynamic` + `generateMetadata`
- Server Actions`getStudentClasses` + `getStudentHomeworkAssignments` + `getTextbooks`
- `requirePermission(Permissions.CLASS_READ)`
- 组件:`Card` / `EmptyState`
- 业务逻辑:`pendingCount`(未提交+未批改)/ `dueSoonCount`7 天内到期)计算
- **ARCHITECTURE.md 契约**:❌
#### 2.13.2 ❌ `/shell/student/learning-path`AI 学习路径,缺失)
- **CICD 参考实现**`e:\desktop\CICD\src\app\(dashboard)\student\learning\study-path\page.tsx`
- **功能描述**
- AI 生成的个性化学习路径
- `AiStudyPath` 客户端组件,由 dashboard layout 注入 `AiClientProvider`
- 学生身份校验
- **技术栈**
- Server Component + `force-dynamic` + `generateMetadata`
- `requirePermission(Permissions.AI_CHAT)`(与 AI_CHAT 权限点一致,防止学生角色被关闭后页面仍可访问)
- `getCurrentStudentUser()`
- 组件:`AiStudyPath`client component/ `EmptyState`
- **ARCHITECTURE.md 契约**:❌
- **注**ARCHITECTURE.md §9.2 路径为 `/learning-path`顶层CICD 路径为 `/learning/study-path`,迁移时需对齐
---
### 2.14 practice 模块2 页规划0 已完成2 缺失)
#### 2.14.1 ❌ `/shell/student/practice`(自适应练习首页,缺失)
- **CICD 参考实现**`e:\desktop\CICD\src\app\(dashboard)\student\practice\page.tsx`
- **功能描述**
- **4 列统计卡片**:总练习次数 / 已完成 / 总答题数 / 正确率
- 左侧:`PracticeStarterWithNav`(练习启动器 + 知识点选择 + 导航)
- 右侧:`PracticeHistory`(历史会话列表)
- `PracticeServiceProvider` 包裹(注入练习服务)
- **技术栈**
- Server Component + `force-dynamic`
- Server Actions`getPracticeStats(ctx.userId)` + `getPracticeSessions(ctx.userId, { pageSize: 20 })` + `getKnowledgePointOptions()`
- `requirePermission(Permissions.ADAPTIVE_PRACTICE_READ)`
- 组件:`StatsGrid` / `PracticeStarterWithNav` / `PracticeHistory` / `PracticeServiceProvider`
- **ARCHITECTURE.md 契约**:❌
#### 2.14.2 ❌ `/shell/student/practice/[sessionId]`(练习会话页,缺失)
- **CICD 参考实现**`e:\desktop\CICD\src\app\(dashboard)\student\practice\[sessionId]\page.tsx`
- **功能描述**
- 单个练习会话详情
- `PracticeSessionView` 渲染(题目作答 + 即时反馈 + 进度)
- 学生身份校验(防止跨用户访问)
- **技术栈**
- Server Component + `force-dynamic`
- Server Action`getPracticeSessionById(sessionId, ctx.userId)`
- `requirePermission(Permissions.ADAPTIVE_PRACTICE_READ)`
- 组件:`PracticeSessionView` / `PracticeServiceProvider`
- 路由参数:`sessionId`
- `notFound()` 兜底
- **ARCHITECTURE.md 契约**:❌
---
### 2.15 elective 模块2 页规划0 已完成2 缺失)
#### 2.15.1 ❌ `/shell/student/elective`(选课列表,缺失)
- **CICD 参考实现**`e:\desktop\CICD\src\app\(dashboard)\student\elective\page.tsx`
- **功能描述**
- 上下两段式布局:
1. **我的选课**`MySelectionsLoader`Suspense 流式加载)
2. **可选课程**`AvailableCoursesLoader`Suspense 流式加载 + searchParams 过滤)
- 学生身份校验
- **技术栈**
- Server Component + `force-dynamic`
- `requirePermission(Permissions.ELECTIVE_READ)`
- 子组件:`MySelectionsLoader` / `AvailableCoursesLoader` / `MySelectionsSkeleton` / `AvailableCoursesSkeleton`(均在 `_components/` 下)
- `Suspense` 双段流式渲染
- searchParams 透传到 `AvailableCoursesLoader`
- **ARCHITECTURE.md 契约**:❌
#### 2.15.2 ❌ `/shell/student/elective/[id]`(选课详情,缺失)
- **CICD 参考实现**`e:\desktop\CICD\src\app\(dashboard)\student\elective\[id]\page.tsx`
- **功能描述**
- 选修课程详情视图
- **学生视角隐私保护**:只展示课程信息,**不展示选课名单**`selections=[]``showEditButton=false`
- 通过 `ElectivePageLayout` + `ElectiveCourseDetail` 组合渲染
- **技术栈**
- Server Component + `force-dynamic`
- Server Action`getElectiveCourseById(id)`
- `requirePermission(Permissions.ELECTIVE_READ)`
- 组件:`ElectiveCourseDetail` / `ElectivePageLayout`
- 路由参数:`id`
- `notFound()` 兜底
- **ARCHITECTURE.md 契约**:❌
---
### 2.16 ai-tutor 模块1 页规划0 已完成1 缺失)
#### 2.16.1 ❌ `/shell/student/ai-tutor`AI 辅导,缺失)⚠️ B3 末
- **CICD 参考实现**:❌ CICD 无对应源页面
- **功能描述**
- 学生专属 AI 辅导对话(区别于通用 AI 聊天)
- 应基于学生学情(薄弱点、近期错题、当前进度)提供个性化辅导
- 可能的形态:聊天对话框 + 上下文卡片(学情摘要)
- **技术栈建议**:参考 CICD `learning/study-path/page.tsx``AiStudyPath` + `AiClientProvider` 模式 + `requirePermission(Permissions.AI_CHAT)`
- **ARCHITECTURE.md 契约**:❌
- **批次**B3 末
---
### 2.17 announcements 模块(共享 2 页规划0 已完成2 缺失)
#### 2.17.1 ❌ `/shell/announcements`(公告列表,缺失,共享)
- **CICD 参考实现**:❌ CICD `student/` 下无对应源页面(应在 `(dashboard)/` 顶层共享路由中,但本次未读取)
- **功能描述**:公告列表(学生可见范围)
- **ARCHITECTURE.md 契约**:❌(列表未定义)
- **批次**B3共享路由
#### 2.17.2 ❌ `/shell/announcements/[id]`(公告详情,缺失,共享)
- **CICD 参考实现**:❌ 同上
- **功能描述**:单条公告详情
- **ARCHITECTURE.md 契约**:❌
- **批次**B3共享路由
> **注**portal-shell 当前 `/shell/announcements/*` 路由不存在。需在共享层(非 student/ 下)创建。
---
### 2.18 messages 模块(共享 1 页规划0 已完成1 缺失)
#### 2.18.1 ❌ `/shell/messages`(消息中心,缺失,共享)⚠️ B3 末
- **CICD 参考实现**:❌ CICD `student/` 下无对应源页面
- **功能描述**:站内消息中心(学生收件箱)
- **ARCHITECTURE.md 契约**:❌
- **批次**B3 末,共享路由
> **注**portal-shell 当前 `/shell/messages` 路由不存在。
---
### 2.19 leave 模块1 页规划0 已完成1 缺失)
#### 2.19.1 ❌ `/shell/student/leave`(学生在线请假,缺失)
- **CICD 参考实现**`e:\desktop\CICD\src\app\(dashboard)\student\leave\page.tsx`
- **功能描述**
- 顶部:在线请假表单(`LeaveRequestForm``defaultStudentId` + `defaultClassId` 自动填入当前活跃班级)
- 底部:本人提交的请假申请列表(`LeaveRequestList`
- 返回仪表盘的面包屑
- 无活跃班级时显示提示卡片
- **技术栈**
- Server Component + `force-dynamic`
- Server Actions`getStudentActiveClass(ctx.userId)` + `getLeaveRequests({ scope, currentUserId, page: 1, pageSize: 50 })`
- `getAuthContext()`(注意:未用 `requirePermission`,直接取 ctx
- 组件:`LeaveRequestForm` / `LeaveRequestList` / `Card` / `Button`
- **scope 处理**:学生 scope 为 `class_members``buildScopeFilter` 返回 `1=0`,但通过 `currentUserId = requesterId` 过滤本人记录
- **ARCHITECTURE.md 契约**:❌
---
### 2.20 diagnostic 模块CICD 额外页面ARCHITECTURE.md 未规划)
#### 2.20.1 ⚠️ CICD `diagnostic/page.tsx`学生诊断报告ARCHITECTURE.md 未列入 36 页)
- **CICD 参考实现**`e:\desktop\CICD\src\app\(dashboard)\student\diagnostic\page.tsx`
- **功能描述**
- 学生诊断学情(`getStudentMasterySummary`+ 已发布诊断报告列表(`getDiagnosticReports`
- `StudentDiagnosticView` 渲染
- 学生仅可见 `status: "published"` 报告(防草稿泄露)
- **技术栈**Server Component + `requirePermission(Permissions.DIAGNOSTIC_READ)`
- **ARCHITECTURE.md 契约**:❌ 未列入 §9.2 36 页清单
- **建议**:若产品确认学生端诊断报告功能保留,需补充到 ARCHITECTURE.md §9.2;否则可在 dashboard/weakness 中替代承载
---
## 三、按模块汇总
| 模块 | 规划页数 | 已完成 | 缺失 | 备注 |
| --------------------- | -------- | ------ | ------ | -------------------------------------------------------------- |
| dashboard | 3 | 1 | 2 | 仪表盘基础页有trend/weakness 详情页缺 |
| grades | 2 | 0 | 2 | 列表 + 报告卡 |
| exams | 3 | 0 | 3 | ⚠️ CICD 无源页面take 工作台最关键 |
| homework | 3 | 0 | 3 | ⚠️ CICD 路由为 `learning/assignments/*`,需迁移到 `homework/*` |
| schedule | 1 | 0 | 1 | |
| attendance | 1 | 0 | 1 | |
| classes | 1 | 0 | 1 | CICD 无独立路由(融合在 courses |
| courses | 2 | 0 | 2 | CICD 路由为 `learning/courses/*` |
| course-plans | 2 | 0 | 2 | |
| lesson-plans | 2 | 0 | 2 | |
| textbooks | 2 | 0 | 2 | CICD 路由为 `learning/textbooks/*`,需对齐 `/[id]/chapters` |
| error-book | 1 | 0 | 1 | 含 AI 上下文 + 客户端列表 |
| learning | 2 | 0 | 2 | 学习中心首页 + AI 学习路径 |
| practice | 2 | 0 | 2 | 自适应练习 |
| elective | 2 | 0 | 2 | 含 Suspense 双段流式渲染 |
| ai-tutor | 1 | 0 | 1 | ⚠️ B3 末CICD 无源页面 |
| announcements共享 | 2 | 0 | 2 | 共享路由,非 student/ 下 |
| messages共享 | 1 | 0 | 1 | ⚠️ B3 末,共享路由 |
| leave | 1 | 0 | 1 | |
| **合计** | **34** | **1** | **33** | 不含 notifications/settings 共享路由 |
> **加上 diagnosticCICD 额外页)共 35**ARCHITECTURE.md §9.2 规划 36 页(含 notifications/settings 共享路由)。
---
## 四、关键技术差异 & 迁移注意事项
### 4.1 架构模式差异(最重要)
| 维度 | CICD参考 | portal-shell目标 |
| -------- | -------------------------------------- | ------------------------------------------------------------------- |
| 渲染模式 | Server Component 为主 | 当前仪表盘是 Client Component需确定后续页面模式 |
| 数据获取 | Server Action + Drizzle 直查 DB | **必须改为** BFF / 微服务 API`teacher-bff` / `data-ana` 等) |
| 权限校验 | `requirePermission` + `getAuthContext` | 需通过 Gateway + BFF 鉴权,前端用 `usePermission().hasPermission()` |
| 国际化 | `next-intl` `getTranslations` | portal-shell 当前仪表盘硬编码中文,需统一策略 |
| 共享 UI | `@/shared/components/ui/*` | `@/shared/components/ui/*`(已迁移部分) |
| 模块组件 | `@/modules/<domain>/components/*` | 需在 portal-shell 重建或下沉到 BFF |
### 4.2 路由路径对齐
| CICD 路径 | ARCHITECTURE.md §9.2 目标路径 | 备注 |
| -------------------------------------------- | ----------------------------- | --------------------------- |
| `learning/assignments` | `homework` | 提升到顶层 |
| `learning/assignments/[assignmentId]` | `homework/[id]/submit` | 路径参数改名 |
| `learning/assignments/[assignmentId]/result` | `homework/[id]/analysis` | 子路径改名 |
| `learning/courses` | `courses` | 提升到顶层 |
| `learning/courses/[classId]` | `courses/[id]` | 参数名 `classId``id` |
| `learning/textbooks` | `textbooks` | 提升到顶层 |
| `learning/textbooks/[id]` | `textbooks/[id]/chapters` | 增加子路径 `chapters` |
| `learning/study-path` | `learning-path` | 提升到顶层 |
| `(无)` | `exams/*` | 全新设计 |
| `(无)` | `ai-tutor` | 全新设计 |
| `(无)` | `classes` | 全新设计(与 courses 区分) |
### 4.3 权限点映射
CICD 中学生页面用到的权限点(迁移时需在 portal-shell `Permissions` 常量中对应):
- `GRADE_RECORD_READ`grades / report-card
- `CLASS_READ`schedule / courses
- `ATTENDANCE_READ`attendance
- `COURSE_PLAN_READ`course-plans
- `LESSON_PLAN_READ`lesson-plans
- `TEXTBOOK_READ`textbooks
- `ERROR_BOOK_READ`error-book
- `AI_CHAT`study-path / ai-tutor
- `ADAPTIVE_PRACTICE_READ`practice
- `ELECTIVE_READ`elective
- `DIAGNOSTIC_READ`diagnosticCICD 额外)
- leave 用 `getAuthContext` 而非 `requirePermission`(需统一)
### 4.4 共享组件清单(需在 portal-shell 补齐或确认已有)
- `DashboardShell` / `DashboardSection` / `StatCard`(仪表盘已有)
- `Card` / `CardContent` / `CardHeader` / `CardTitle`(基础)
- `Button` / `Badge` / `EmptyState` / `Skeleton`(基础)
- `StatsGrid`多列统计error-book/practice 用)
- `WidgetBoundary`widget 降级容错)
- `StatusBadge` + `variantMap`(作业状态徽章)
- `TextbookReader` / `TextbookCard` / `TextbookFilters`(教材相关)
- `LessonPlanReadonlyView` / `LessonPlanList`(教案相关)
- `CoursePlanList` / `CoursePlanDetail`(课程计划)
- `StudentScheduleView` / `StudentScheduleFilters`(课表)
- `StudentCoursesView` / `CourseFilters`(课程)
- `StudentAttendanceView`(考勤)
- `StudentGradeSummary` / `GradeTrendCard` / `RankingTrendCard` / `GradeDistributionChart` / `GrowthArchiveChart` / `ReportCardView` / `ReportCardPrintAction` / `StudentGradeFilters`(成绩,组件最多)
- `HomeworkTakeView` / `HomeworkReviewView` / `HomeworkSubmissionResult` / `AssignmentFilters`(作业,⚠️ take 是最复杂)
- `PracticeStarterWithNav` / `PracticeHistory` / `PracticeSessionView` / `PracticeServiceProvider`(练习)
- `ElectiveCourseDetail` / `ElectivePageLayout` / `MySelectionsLoader` / `AvailableCoursesLoader`(选课)
- `LeaveRequestForm` / `LeaveRequestList`(请假)
- `StudentErrorBookListClient` / `AddErrorBookDialogWithQuestions` / `ErrorBookFilters`(错题本)
- `AiStudyPath` / `AiClientProvider` / `createCoreAiClientService`AI
### 4.5 B3 实施优先级建议
按用户价值 + 实现复杂度排序:
1. **P0必做核心学习闭环**homework 列表 + submit 工作台 + analysis、exams 列表 + take 工作台 + result
2. **P1高频查看**grades 列表 + report-card、schedule、attendance、error-book
3. **P2学习资源**learning 首页、courses 列表 + 详情、textbooks 列表 + chapters、course-plans + 详情、lesson-plans + view
4. **P3辅助功能**classes、practice + session、elective + 详情、leave
5. **P4增强**dashboard trend/weakness 详情页、learning-path
6. **P5B3 末)**ai-tutor、messages
7. **共享**announcements 列表 + 详情
---
## 五、CICD 与 ARCHITECTURE.md §9.2 对照表
| ARCH §9.2 源路由 | ARCH §9.2 目标路由 | CICD 是否有源页面 | portal-shell 是否已有 | 契约状态 | 备注 |
| --------------------------- | ------------------------------------------- | -------------------------------------- | --------------------- | --------------------- | -------------------------------- |
| `dashboard` | `/shell/student` | ✅ | ✅ | ✅ `studentDashboard` | 已完成基础版 |
| `/trend` | `/shell/student/trend` | ❌(融合在 dashboard | ❌ | ✅ `learningTrend` | 需新建详情页 |
| `/weakness` | `/shell/student/weakness` | ❌(融合在 dashboard | ❌ | ✅ `studentWeakness` | 需新建详情页 |
| `my-grades` | `/shell/student/grades` | ✅ | ❌ | ❌ | |
| `/report-card` | `/shell/student/grades/report-card` | ✅ | ❌ | ❌ | |
| `my-exams` | `/shell/student/exams` | ❌ | ❌ | 🟡 | CICD 无,全新设计 |
| `/[id]/result` | `/shell/student/exams/[id]/result` | ❌ | ❌ | 🟡 | 参考作业 result |
| `/[id]/take` | `/shell/student/exams/[id]/take` | ❌ | ❌ | 🟡 | ⚠️ 最关键,参考 HomeworkTakeView |
| `my-homework` | `/shell/student/homework` | ✅learning/assignments | ❌ | 🟡 | 路径迁移 |
| `/[id]/submit` | `/shell/student/homework/[id]/submit` | ✅learning/assignments/[id] | ❌ | 🟡 | ⚠️ take 工作台 |
| `/[id]/analysis` | `/shell/student/homework/[id]/analysis` | ✅learning/assignments/[id]/result | ❌ | 🟡 | |
| `schedule` | `/shell/student/schedule` | ✅ | ❌ | ❌ | |
| `my-attendance` | `/shell/student/attendance` | ✅ | ❌ | ❌ | |
| `my-classes` | `/shell/student/classes` | ❌(融合在 courses | ❌ | ❌ `myClasses` | 全新设计 |
| `courses` | `/shell/student/courses` | ✅learning/courses | ❌ | ❌ | 路径迁移 |
| `/[id]` | `/shell/student/courses/[id]` | ✅learning/courses/[classId] | ❌ | ❌ | 参数名 classId→id |
| `course-plans` | `/shell/student/course-plans` | ✅ | ❌ | ❌ | |
| `/[id]` | `/shell/student/course-plans/[id]` | ✅ | ❌ | ❌ | |
| `lesson-plans` | `/shell/student/lesson-plans` | ✅ | ❌ | ❌ | |
| `/[id]/view` | `/shell/student/lesson-plans/[planId]/view` | ✅ | ❌ | ❌ | |
| `textbooks` | `/shell/student/textbooks` | ✅learning/textbooks | ❌ | 🟡 | 路径迁移 |
| `/[id]/chapters` | `/shell/student/textbooks/[id]/chapters` | ✅learning/textbooks/[id] | ❌ | 🟡 | 增加子路径 |
| `error-book` | `/shell/student/error-book` | ✅ | ❌ | ✅ | 含 AI 上下文 |
| `learning` | `/shell/student/learning` | ✅ | ❌ | ❌ | |
| `learning-path` | `/shell/student/learning-path` | ✅learning/study-path | ❌ | ❌ | 路径迁移 |
| `practice` | `/shell/student/practice` | ✅ | ❌ | ❌ | |
| `/[sessionId]` | `/shell/student/practice/[sessionId]` | ✅ | ❌ | ❌ | |
| `elective` | `/shell/student/elective` | ✅ | ❌ | ❌ | |
| `/[id]` | `/shell/student/elective/[id]` | ✅ | ❌ | ❌ | |
| `ai-tutor` | `/shell/student/ai-tutor` | ❌ | ❌ | ❌ | B3 末,全新设计 |
| `announcements` | `/shell/announcements`(共享) | ❌(不在 student/ 下) | ❌ | ❌ | 共享路由 |
| `/[id]` | `/shell/announcements/[id]`(共享) | ❌ | ❌ | ❌ | 共享路由 |
| `messages` | `/shell/messages`(共享) | ❌ | ❌ | ❌ | B3 末,共享路由 |
| `leave` | `/shell/student/leave` | ✅ | ❌ | ❌ | |
| `notifications``settings` | 共享路由 | — | ❌ | ✅ | B1/B3不在 36 页内 |
| `diagnostic`CICD 额外) | —(未列入 §9.2 | ✅ | ❌ | — | 需产品决策是否保留 |
---
## 六、风险与建议
### 6.1 高风险点
1. **exams/[id]/take 作答工作台**CICD 完全无源可参考(仅有 `HomeworkTakeView` 同构参考),需从零设计考试特性(倒计时、断网恢复、批量提交、防作弊等)
2. **路由路径大调整**6 类路径需迁移learning/* → 顶层),需同步更新导航、面包屑、内链
3. **架构模式转变**portal-shell 已转向 BFF + 微服务,**不能照搬 CICD 的 Server Action + Drizzle 直查模式**每个页面都需重新设计数据获取链路BFF → data-ana / core-edu 等服务)
4. **国际化缺失**当前仪表盘硬编码中文36 页全部完成后需补 i18n建议从一开始就用 `next-intl`
5. **权限校验缺失**:当前仪表盘无 `requirePermission`,需建立 portal-shell 学生域权限校验中间件/装饰器
### 6.2 建议
1. **先建契约**:开工前先在 `shared-proto` 中补齐 §9.2 标 ❌ / 🟡 的契约(特别是 exams 系列、my-classes、homework 等)
2. **统一渲染模式**:建议学生页面统一采用 Server Component + BFF 数据获取(与 CICD 一致),避免当前仪表盘的 Client Component 模式
3. **统一权限模型**:建立 portal-shell 学生域 `RequirePermission` 装饰器/中间件,覆盖所有页面
4. **共享组件下沉**CICD 中 `@/modules/<domain>/components/*` 的视图组件需评估是下沉到 BFF 返回结构化数据,还是在 portal-shell 重建
5. **CICD diagnostic 决策**:产品需确认是否保留学生端诊断报告功能,若保留需补充 ARCHITECTURE.md §9.2
6. **分批推进**:按本报告 §4.5 的 P0→P5 优先级分批实施,每批完成后跑 `pnpm run arch:scan` 同步 arch.db
---
**分析完成。共发现 35 个待完成页面(含 CICD 额外的 diagnostic 1 页),其中 33 个完全缺失、2 个为 dashboard 详情页缺失。最关键的 exams/[id]/take 作答工作台在 CICD 中无源页面,需重点设计。**