Files
Edu/apps/portal-shell/docs/needtodo/student-NeedTodo.md
SpecialX 04b7a40bdc feat(portal-shell): 学生域全页面迁移与规范合规修复
- 学生域 32 页全量迁移(含作答/自动保存/提交/诊断)

- 补齐 4 个 MSW mock 缺口,修 diagnostic case 名

- 修 4 处 Tailwind 任意值;新增共享组件与路由
2026-08-31 11:25:21 +08:00

557 lines
36 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-08-04
> 任务范围:仅核查,不修改源代码
---
## 一、页面完成度总览
### 1.1 portal-shell 学生域现状2026-08-04 核查)
`apps/portal-shell/src/app/shell/student/` 下共有 **32 个 page.tsx** 文件全部为真实业务页Server Component 入口 + Client Component 业务实现)。
| 状态 | 数量 | 说明 |
| ---------------- | ------ | ----------------------------------------------------------------- |
| ✅ 完整实现 | 32 | page.tsx 引入 `@/features/student/*-client.tsx` 并渲染,三态完整 |
| 🟡 占位空态 | 0 | 无(原 2026-07-24 报告中的 34 个缺失页已全部补齐) |
| ❌ 缺失 | 0 | 无 |
| **本次核查总数** | **32** | 不含 announcements / messages / notifications / settings 共享路由 |
### 1.2 架构模式统一(与 2026-07-24 报告对比)
原报告指出仪表盘为 Client Component 硬编码中文、无权限校验。当前所有 32 页已统一为:
| 维度 | 当前实现 |
| -------- | --------------------------------------------------------------------- |
| 渲染模式 | Server Component 入口 + Client Component 业务(统一 Suspense 边界) |
| 数据获取 | `useWidgetQuery` / `useWidgetMutation` Hook走 Apollo Client + BFF |
| 国际化 | `next-intl` `useTranslations`(全部页面已接入 i18n key |
| 三态规范 | loadingSkeleton/ error局部降级/ emptyEmptyState均按 §11.3 |
| 加载骨架 | `ListPageSkeleton` / `DetailPageSkeleton` / `WorkbenchPageSkeleton` |
| 契约状态 | 全部 `@contract-pending`schema 根字段未定义MSW 兜底) |
### 1.3 文件统计
| 类别 | 数量 | 说明 |
| ---------------------------- | ----- | ----------------------------------------- |
| page.tsx | 32 | 全部存在,全部引入 client 组件 |
| features/student/*.tsx | 38 | 全部为真实实现115-698 行) |
| lib/api student 相关 hook | 40+ | student-portal.ts + student.ts + 各域文件 |
| graphql-data.ts mock handler | 28/32 | 4 个页面存在 mock 缺口(见 §七) |
---
## 二、按模块核查清单
### 2.1 dashboard 模块3 页,✅ 全部完整)
#### 2.1.1 ✅ `/shell/student/page.tsx`(仪表盘首页)
- **page.tsx**:引入 `StudentDashboardClient`Suspense + `DetailPageSkeleton`
- **client 组件**`features/student/dashboard/dashboard-client.tsx`632 行)
- **API hook**`useStudentDashboard()`lib/api/dashboard.ts
- **mock 数据**:✅ `case "GetStudentDashboard"`graphql-data.ts L5575
- **三态**loading/error/empty 完整
#### 2.1.2 ✅ `/shell/student/trend`(学习趋势详情页)
- **page.tsx**:引入 `StudentTrendClient`Suspense + `DetailPageSkeleton`
- **client 组件**`features/student/dashboard/trend-client.tsx`166 行)
- **API hook**`useStudentTrend()`lib/api/student-portal.ts
- **mock 数据**:✅ `case "GetStudentTrend"`graphql-data.ts L9633
- **三态**loading/error/empty 完整
#### 2.1.3 ✅ `/shell/student/weakness`(薄弱知识点详情页)
- **page.tsx**:引入 `StudentWeaknessClient`Suspense + `DetailPageSkeleton`
- **client 组件**`features/student/dashboard/weakness-client.tsx`194 行)
- **API hook**`useStudentPortalWeakness()`lib/api/student-portal.ts
- **mock 数据**:✅ `case "GetStudentPortalWeakness"`graphql-data.ts L9636
- **三态**loading/error/empty 完整
---
### 2.2 grades 模块2 页,✅ 全部完整)
#### 2.2.1 ✅ `/shell/student/grades`(成绩列表页)
- **page.tsx**:引入 `StudentGradesListClient`Suspense + `ListPageSkeleton`
- **client 组件**`features/student/grades/grades-list-client.tsx`516 行)
- **API hook**`useStudentGrades(subject, type, q)`lib/api/student-portal.ts
- **mock 数据**:✅ `case "GetStudentGrades"`graphql-data.ts L9586
- **三态**loading/error/empty 完整
#### 2.2.2 ✅ `/shell/student/grades/report-card`(成绩报告卡页)
- **page.tsx**:引入 `StudentReportCardClient`,导入 `report-card-print.css`Suspense + `DetailPageSkeleton`
- **client 组件**`features/student/grades/report-card-client.tsx`693 行)+ 子组件growth-archive-card / ranking-trend-card / score-distribution-card
- **API hook**`useStudentReportCard(academicYearId, semester)`lib/api/student-portal.ts
- **mock 数据**:✅ `case "GetStudentReportCard"`graphql-data.ts L9588
- **三态**loading/error/empty 完整
---
### 2.3 exams 模块3 页,✅ 全部完整)
#### 2.3.1 ✅ `/shell/student/exams`(考试列表页)
- **page.tsx**:引入 `StudentExamsListClient`Suspense + `ListPageSkeleton`
- **client 组件**`features/student/exams/exams-list-client.tsx`331 行)
- **API hook**`useStudentExams(status)`lib/api/student-portal.ts
- **mock 数据**:✅ `case "GetStudentExams"`graphql-data.ts L9591
- **三态**loading/error/empty 完整
#### 2.3.2 ✅ `/shell/student/exams/[id]/result`(考试结果页)
- **page.tsx**:引入 `StudentExamResultClient`Suspense + `DetailPageSkeleton`
- **client 组件**`features/student/exams/exam-result-client.tsx`252 行)
- **API hook**`useStudentExamResult(id)`lib/api/student-portal.ts
- **mock 数据**:✅ `case "GetStudentExamResult"`graphql-data.ts L9593
- **三态**loading/error/empty 完整
#### 2.3.3 ✅ `/shell/student/exams/[id]/take`(考试作答工作台页)
- **page.tsx**:引入 `StudentExamTakeClient`Suspense + `WorkbenchPageSkeleton`
- **client 组件**`features/student/exams/exam-take-client.tsx`665 行)
- **API hook**`useStudentExamTake(id)` + `useSubmitStudentExam()`lib/api/student-portal.ts
- **mock 数据**:✅ `case "GetStudentExamTake"` + `case "SubmitStudentExam"`L9595, L9669
- **三态**loading/error/empty 完整
---
### 2.4 homework 模块3 页,✅ 全部完整)
#### 2.4.1 ✅ `/shell/student/homework`(作业列表页)
- **page.tsx**:引入 `StudentHomeworkListClient`Suspense + `ListPageSkeleton`
- **client 组件**`features/student/homework/homework-list-client.tsx`547 行)
- **API hook**`useStudentHomework(status)`lib/api/student-portal.ts
- **mock 数据**:✅ `case "GetStudentHomework"`graphql-data.ts L9598
- **三态**loading/error/empty 完整
#### 2.4.2 ✅ `/shell/student/homework/[id]/submit`(作业作答工作台页)
- **page.tsx**:引入 `StudentHomeworkSubmitClient`Suspense + `WorkbenchPageSkeleton`
- **client 组件**`features/student/homework/homework-submit-client.tsx`698 行,学生域最大文件)
- **API hook**`useStudentHomeworkSubmit(id)` + `useSubmitStudentHomework()`lib/api/student-portal.ts
- **mock 数据**:✅ `case "GetStudentHomeworkSubmit"` + `case "SubmitStudentHomework"`L9600, L9671
- **三态**loading/error/empty 完整
#### 2.4.3 ✅ `/shell/student/homework/[id]/analysis`(作业分析页)
- **page.tsx**:引入 `StudentHomeworkAnalysisClient`Suspense + `DetailPageSkeleton`
- **client 组件**`features/student/homework/homework-analysis-client.tsx`139 行)
- **API hook**`useStudentHomeworkAnalysis(id)`lib/api/student-portal.ts
- **mock 数据**:✅ `case "GetStudentHomeworkAnalysis"`graphql-data.ts L9602
- **三态**loading/error/empty 完整
---
### 2.5 schedule 模块1 页,✅ 完整)
#### 2.5.1 ✅ `/shell/student/schedule`(课表页)
- **page.tsx**:引入 `StudentScheduleListClient`Suspense + `ListPageSkeleton`
- **client 组件**`features/student/schedule/schedule-list-client.tsx`244 行)
- **API hook**`useStudentSchedule()` + `useStudentClasses()`lib/api/student-portal.ts
- **mock 数据**:✅ `case "GetStudentSchedule"` + `case "GetStudentClasses"`L9605, L9609
- **三态**loading/error/empty 完整
---
### 2.6 attendance 模块1 页,✅ 完整)
#### 2.6.1 ✅ `/shell/student/attendance`(考勤页)
- **page.tsx**:引入 `StudentAttendanceClient`Suspense + `DetailPageSkeleton`
- **client 组件**`features/student/attendance/attendance-client.tsx`252 行)
- **API hook**`useStudentAttendance()`lib/api/student-portal.ts
- **mock 数据**:✅ `case "GetStudentAttendance"`graphql-data.ts L9607
- **三态**loading/error/empty 完整
---
### 2.7 classes 模块1 页,✅ 完整)
#### 2.7.1 ✅ `/shell/student/classes`(班级列表页)
- **page.tsx**:引入 `StudentClassesListClient`Suspense + `ListPageSkeleton`
- **client 组件**`features/student/classes/classes-list-client.tsx`134 行)
- **API hook**`useStudentClasses()`lib/api/student-portal.ts
- **mock 数据**:✅ `case "GetStudentClasses"`graphql-data.ts L9609
- **三态**loading/error/empty 完整
---
### 2.8 courses 模块2 页,✅ 全部完整)
#### 2.8.1 ✅ `/shell/student/courses`(课程列表页)
- **page.tsx**:引入 `StudentCoursesListClient`Suspense + `ListPageSkeleton`
- **client 组件**`features/student/courses/courses-list-client.tsx`473 行)
- **API hook**`useStudentCourses(q)` + `useJoinClassByInvitationCode()`lib/api/student-portal.ts
- **mock 数据**:✅ `case "GetStudentCourses"`graphql-data.ts L9611
- **三态**loading/error/empty 完整
#### 2.8.2 ✅ `/shell/student/courses/[id]`(课程详情页)
- **page.tsx**:引入 `StudentCourseDetailClient`Suspense + `DetailPageSkeleton`
- **client 组件**`features/student/courses/course-detail-client.tsx`248 行)
- **API hook**`useStudentCourseDetail(id)`lib/api/student-portal.ts
- **mock 数据**:✅ `case "GetStudentCourseDetail"`graphql-data.ts L9613
- **三态**loading/error/empty 完整
---
### 2.9 course-plans 模块2 页,✅ 全部完整)
#### 2.9.1 ✅ `/shell/student/course-plans`(课程计划列表页)
- **page.tsx**:引入 `StudentCoursePlansListClient`Suspense + `ListPageSkeleton`
- **client 组件**`features/student/course-plans/course-plan-list-client.tsx`340 行)
- **API hook**`useStudentCoursePlans()`lib/api/student-portal.ts
- **mock 数据**:✅ `case "GetStudentCoursePlans"`graphql-data.ts L9616
- **三态**loading/error/empty 完整
#### 2.9.2 ✅ `/shell/student/course-plans/[id]`(课程计划详情页)
- **page.tsx**:引入 `StudentCoursePlanDetailClient`Suspense + `DetailPageSkeleton`
- **client 组件**`features/student/course-plans/course-plan-detail-client.tsx`486 行)
- **API hook**`useStudentCoursePlanDetail(id)`lib/api/student-portal.ts
- **mock 数据**:✅ `case "GetStudentCoursePlanDetail"`graphql-data.ts L9618
- **三态**loading/error/empty 完整
---
### 2.10 lesson-plans 模块2 页,✅ 全部完整)
#### 2.10.1 ✅ `/shell/student/lesson-plans`(教案列表页)
- **page.tsx**:引入 `StudentLessonPlansListClient`Suspense + `ListPageSkeleton`
- **client 组件**`features/student/lesson-plans/lesson-plan-list-client.tsx`167 行)
- **API hook**`useStudentLessonPlans()` + `useSubjectOptions()`lib/api/student-portal.ts
- **mock 数据**:✅ `case "GetStudentLessonPlans"`graphql-data.ts L9620
- **三态**loading/error/empty 完整
#### 2.10.2 ✅ `/shell/student/lesson-plans/[planId]/view`(教案只读查看页)
- **page.tsx**:引入 `StudentLessonPlanViewClient`Suspense + `DetailPageSkeleton`
- **client 组件**`features/student/lesson-plans/lesson-plan-view-client.tsx`422 行)
- **API hook**`useStudentLessonPlanView(planId)`lib/api/student-portal.ts
- **mock 数据**:✅ `case "GetStudentLessonPlanView"`graphql-data.ts L9622
- **三态**loading/error/empty 完整
---
### 2.11 textbooks 模块2 页,✅ 全部完整)
#### 2.11.1 ✅ `/shell/student/textbooks`(教材列表页)
- **page.tsx**:引入 `StudentTextbooksListClient`Suspense + `ListPageSkeleton`
- **client 组件**`features/student/textbooks/textbooks-list-client.tsx`270 行)
- **API hook**`useStudentTextbooks(q, subject, grade)`lib/api/student-portal.ts
- **mock 数据**:✅ `case "GetStudentTextbooks"`graphql-data.ts L9624
- **三态**loading/error/empty 完整
#### 2.11.2 ✅ `/shell/student/textbooks/[id]/chapters`(教材章节阅读器页)
- **page.tsx**:引入 `StudentTextbookChaptersClient`Suspense + `DetailPageSkeleton`
- **client 组件**`features/student/textbooks/textbook-chapters-client.tsx`362 行)
- **API hook**`useStudentTextbookChapters(id)`lib/api/student-portal.ts
- **mock 数据**:✅ `case "GetStudentTextbookChapters"`graphql-data.ts L9626
- **三态**loading/error/empty 完整
---
### 2.12 error-book 模块1 页,✅ 完整)
#### 2.12.1 ✅ `/shell/student/error-book`(错题本页)
- **page.tsx**:引入 `StudentErrorBookListClient`Suspense + `ListPageSkeleton`
- **client 组件**`features/student/error-book/error-book-list-client.tsx`648 行)+ `error-book-detail-dialog.tsx`199 行,详情弹窗子组件)
- **API hook**`useStudentErrorBookV2(q, status, source, dueOnly)`lib/api/student-portal.ts
- **mock 数据**:✅ `case "GetStudentErrorBookV2"`graphql-data.ts L9629
- **三态**loading/error/empty 完整
---
### 2.13 learning 模块1 页,✅ 完整)
#### 2.13.1 ✅ `/shell/student/learning`(学习中心首页)
- **page.tsx**:引入 `StudentLearningCenterClient`Suspense + `ListPageSkeleton`
- **client 组件**`features/student/learning/learning-center-client.tsx`153 行)
- **API hook**`useStudentLearningCenter()`lib/api/student-portal.ts
- **mock 数据**:✅ `case "GetStudentLearningCenter"`graphql-data.ts L9631
- **三态**loading/error/empty 完整
---
### 2.14 learning-path 模块1 页,🟡 页面完整但缺 mock 数据)
#### 2.14.1 🟡 `/shell/student/learning-path`AI 学习路径页)
- **page.tsx**:引入 `StudentLearningPathClient`Suspense + `DetailPageSkeleton`
- **client 组件**`features/student/learning-path/learning-path-client.tsx`224 行)✅
- **API hook**`useLearningPath(subjectId)`lib/api/student.ts
- **GraphQL operation**`query MyLearningPath($subjectId: ID!)`student.graphql.ts L37
- **mock 数据**:❌ **缺失** — graphql-data.ts 中无 `case "MyLearningPath"` 处理器
- **运行时影响**MSW 未命中 → 返回 `{ data: null }` → 页面显示空态
- **需补全**:在 graphql-data.ts `graphqlResponse()` 中添加 `case "MyLearningPath"` 返回 `mockLearningPath` 数据
---
### 2.15 practice 模块2 页,✅ 全部完整)
#### 2.15.1 ✅ `/shell/student/practice`(自适应练习首页)
- **page.tsx**:引入 `StudentPracticeListClient`Suspense + `ListPageSkeleton`
- **client 组件**`features/student/practice/practice-list-client.tsx`507 行)
- **API hook**`useStudentPractice()` + `useStartPracticeSession()`lib/api/student-portal.ts
- **mock 数据**:✅ `case "GetStudentPractice"` + `case "StartPracticeSession"`L9639, L9673
- **三态**loading/error/empty 完整
#### 2.15.2 ✅ `/shell/student/practice/[sessionId]`(练习会话详情页)
- **page.tsx**:引入 `StudentPracticeSessionClient`Suspense + `DetailPageSkeleton`
- **client 组件**`features/student/practice/practice-session-client.tsx`191 行)
- **API hook**`useStudentPracticeSession(sessionId)` + `useSubmitPracticeAnswer()`lib/api/student-portal.ts
- **mock 数据**:✅ `case "GetStudentPracticeSession"` + `case "SubmitPracticeAnswer"`L9641, L9675
- **三态**loading/error/empty 完整
---
### 2.16 elective 模块2 页,🟡 列表页部分缺 mock / 详情页缺 mock
#### 2.16.1 🟡 `/shell/student/elective`(选课列表页)
- **page.tsx**:引入 `StudentElectiveListClient`Suspense + `ListPageSkeleton`
- **client 组件**`features/student/elective/elective-list-client.tsx`636 行)✅
- **API hook**`useElectiveCourses(termId)` + `useStudentSelections(termId)` + `useEnrollCourse()` + `useDropCourse()`lib/api/student.ts
- **GraphQL operations**`query ElectiveCourses` + `query StudentSelections`student.graphql.ts L53, L87
- **mock 数据**
-`case "ElectiveCourses"`graphql-data.ts L9681→ 可选课程列表有数据
-`case "EnrollCourse"` + `case "DropCourse"`L9683, L9685→ 选退课 mutation 有数据
-**缺失** `case "StudentSelections"` → 学生已选课程列表无数据
- **运行时影响**:可选课程正常展示,但"我的选课"区域为空
- **需补全**:在 graphql-data.ts 添加 `case "StudentSelections"` 返回学生已选课程 mock 数据
#### 2.16.2 🟡 `/shell/student/elective/[id]`(选课详情页)
- **page.tsx**:引入 `StudentElectiveDetailClient`Suspense + `DetailPageSkeleton`
- **client 组件**`features/student/elective/elective-detail-client.tsx`187 行)✅
- **API hook**`useStudentElectiveDetail(id)`lib/api/student-portal.ts
- **GraphQL operation**`query GetStudentElectiveDetail($id: ID!)`student.graphql.ts L439
- **mock 数据**:❌ **缺失** — graphql-data.ts 中无 `case "GetStudentElectiveDetail"` 处理器
- **运行时影响**MSW 未命中 → 返回 `{ data: null }` → 页面显示空态
- **需补全**:在 graphql-data.ts 添加 `case "GetStudentElectiveDetail"` 返回 `mockStudentElectiveDetail` 数据
---
### 2.17 diagnostic 模块1 页,🟡 页面完整但 mock handler 名不匹配)
#### 2.17.1 🟡 `/shell/student/diagnostic`(诊断报告页)
- **page.tsx**:引入 `StudentSelfDiagnosticClient`Suspense + `DetailPageSkeleton`
- **client 组件**`features/student/diagnostic/diagnostic-client.tsx`603 行)✅
- **API hook**`useStudentSelfDiagnostic()`lib/api/student-portal.ts
- **GraphQL operation**`query GetStudentSelfDiagnostic`student.graphql.ts L711
- **mock 数据**:❌ **名称不匹配** — graphql-data.ts 中有 `case "GetStudentDiagnostic"`L9804但 hook 发送的 operationName 是 `GetStudentSelfDiagnostic`
- **运行时影响**MSW case 不匹配 → 返回 `{ data: null }` → 页面显示空态
- **需补全**:在 graphql-data.ts 将 `case "GetStudentDiagnostic"` 改为 `case "GetStudentSelfDiagnostic"`(或新增同名 case
---
### 2.18 ai-tutor 模块1 页,✅ 完整)
#### 2.18.1 ✅ `/shell/student/ai-tutor`AI 辅导页)
- **page.tsx**:引入 `StudentAiTutorClient`Suspense + `DetailPageSkeleton`
- **client 组件**`features/student/ai-tutor/ai-tutor-client.tsx`241 行)
- **API hook**`useAiTutorSessions(limit)` + `useSendAiTutorMessage()`lib/api/student.ts
- **mock 数据**:✅ `case "AiTutorSessions"` + `case "SendAiTutorMessage"`graphql-data.ts L9687, L9689
- **三态**loading/error/empty 完整
---
### 2.19 leave 模块1 页,✅ 完整)
#### 2.19.1 ✅ `/shell/student/leave`(在线请假页)
- **page.tsx**:引入 `StudentLeaveClient`Suspense + `DetailPageSkeleton`
- **client 组件**`features/student/leave/leave-client.tsx`115 行)+ `leave-request-form.tsx`202 行)+ `leave-request-list.tsx`253 行)
- **API hook**`useStudentLeave(page, pageSize)` + `useSubmitLeaveRequest()` + `useStudentClasses()`lib/api/student-portal.ts
- **mock 数据**:✅ `case "GetStudentLeave"` + `case "SubmitLeaveRequest"`graphql-data.ts L9643, L9677
- **三态**loading/error/empty 完整
---
## 三、按模块汇总
| 模块 | 页数 | ✅ 完整 | 🟡 mock 缺口 | 备注 |
| ------------- | ------ | ------- | ------------ | -------------------------------------------------------------------------------- |
| dashboard | 3 | 3 | 0 | 仪表盘 + trend + weakness 全部完成 |
| grades | 2 | 2 | 0 | 列表 + 报告卡(含 3 个子组件 + 打印 CSS |
| exams | 3 | 3 | 0 | 列表 + result + take 工作台665 行最复杂之一) |
| homework | 3 | 3 | 0 | 列表 + submit 工作台698 行最大文件)+ analysis |
| schedule | 1 | 1 | 0 | |
| attendance | 1 | 1 | 0 | |
| classes | 1 | 1 | 0 | |
| courses | 2 | 2 | 0 | |
| course-plans | 2 | 2 | 0 | |
| lesson-plans | 2 | 2 | 0 | |
| textbooks | 2 | 2 | 0 | |
| error-book | 1 | 1 | 0 | 列表 + 详情弹窗 |
| learning | 1 | 1 | 0 | |
| learning-path | 1 | 0 | 1 | ❌ 缺 `MyLearningPath` mock handler |
| practice | 2 | 2 | 0 | |
| elective | 2 | 0 | 2 | ❌ 列表缺 `StudentSelections` mock详情缺 `GetStudentElectiveDetail` mock |
| ai-tutor | 1 | 1 | 0 | |
| leave | 1 | 1 | 0 | 表单 + 列表分页 |
| diagnostic | 1 | 0 | 1 | ❌ mock handler 名不匹配(`GetStudentDiagnostic` vs `GetStudentSelfDiagnostic` |
| **合计** | **32** | **28** | **4** | 28 页完全可用4 页存在 mock 缺口 |
> **结论**32 个页面在结构上全部完整page.tsx + client 组件 + API hook + 三态处理),其中 28 页 mock 数据齐全可完整运行4 页存在 mock 数据缺口需补齐。
---
## 四、关键技术实现差异 & 迁移完成情况
### 4.1 架构模式迁移(已全部完成)
| 维度 | CICD参考 | portal-shell当前 | 迁移状态 |
| -------- | -------------------------------------- | ----------------------------------------------------------------------- | --------- |
| 渲染模式 | Server Component 为主 | Server Component 入口 + Client Component 业务 | ✅ 已统一 |
| 数据获取 | Server Action + Drizzle 直查 DB | `useWidgetQuery` Hook → Apollo Client → BFF / 微服务 | ✅ 已迁移 |
| 国际化 | `next-intl` `getTranslations` | `next-intl` `useTranslations`(全部页面接入) | ✅ 已统一 |
| 三态规范 | 各页不一致 | 统一 loadingSkeleton/ error局部降级/ emptyEmptyState | ✅ 已统一 |
| 权限校验 | `requirePermission` + `getAuthContext` | 前端 `usePermission().hasPermission()`,后端 Gateway + BFF | ✅ 已迁移 |
| 共享 UI | `@/modules/<domain>/components/*` | `@/features/student/<module>/*-client.tsx` + `@/shared/components/ui/*` | ✅ 已迁移 |
### 4.2 路由路径对齐(已全部完成)
| CICD 路径 | 目标路由 | 迁移状态 |
| -------------------------------------------- | ------------------------- | -------- |
| `learning/assignments` | `homework` | ✅ |
| `learning/assignments/[assignmentId]` | `homework/[id]/submit` | ✅ |
| `learning/assignments/[assignmentId]/result` | `homework/[id]/analysis` | ✅ |
| `learning/courses` | `courses` | ✅ |
| `learning/courses/[classId]` | `courses/[id]` | ✅ |
| `learning/textbooks` | `textbooks` | ✅ |
| `learning/textbooks/[id]` | `textbooks/[id]/chapters` | ✅ |
| `learning/study-path` | `learning-path` | ✅ |
| `(无)` | `exams/*` | ✅ 全新 |
| `(无)` | `ai-tutor` | ✅ 全新 |
| `(无)` | `classes` | ✅ 全新 |
### 4.3 共享组件清单(已全部建立)
原报告列出的 40+ 共享组件已全部在 portal-shell 中以 client 组件形式实现:
- `ListPageShell` / `DetailPageShell` / `WorkbenchPageShell`page-templates
- `ListPageSkeleton` / `DetailPageSkeleton` / `WorkbenchPageSkeleton`(骨架)
- `EmptyState`(空态)
- `Button` / `Badge` / `Card` 等 UI 基础组件(`@/shared/components/ui/*`
- 各域专用组件(`@/features/student/<module>/*-client.tsx`
---
## 五、CICD 与 ARCHITECTURE.md §9.2 对照表2026-08-04 核查)
| ARCH §9.2 目标路由 | CICD 源页面 | portal-shell 实现状态 | mock 数据状态 | 备注 |
| ------------------------------------------- | ----------- | --------------------- | ------------- | ------------------ |
| `/shell/student` | ✅ | ✅ 完整 | ✅ | 仪表盘 |
| `/shell/student/trend` | ❌ | ✅ 完整 | ✅ | 全新设计 |
| `/shell/student/weakness` | ❌ | ✅ 完整 | ✅ | 全新设计 |
| `/shell/student/grades` | ✅ | ✅ 完整 | ✅ | |
| `/shell/student/grades/report-card` | ✅ | ✅ 完整 | ✅ | 含打印 CSS |
| `/shell/student/exams` | ❌ | ✅ 完整 | ✅ | 全新设计 |
| `/shell/student/exams/[id]/result` | ❌ | ✅ 完整 | ✅ | 全新设计 |
| `/shell/student/exams/[id]/take` | ❌ | ✅ 完整 | ✅ | 665 行,最复杂之一 |
| `/shell/student/homework` | ✅ | ✅ 完整 | ✅ | |
| `/shell/student/homework/[id]/submit` | ✅ | ✅ 完整 | ✅ | 698 行,最大文件 |
| `/shell/student/homework/[id]/analysis` | ✅ | ✅ 完整 | ✅ | |
| `/shell/student/schedule` | ✅ | ✅ 完整 | ✅ | |
| `/shell/student/attendance` | ✅ | ✅ 完整 | ✅ | |
| `/shell/student/classes` | ❌ | ✅ 完整 | ✅ | 全新设计 |
| `/shell/student/courses` | ✅ | ✅ 完整 | ✅ | |
| `/shell/student/courses/[id]` | ✅ | ✅ 完整 | ✅ | |
| `/shell/student/course-plans` | ✅ | ✅ 完整 | ✅ | |
| `/shell/student/course-plans/[id]` | ✅ | ✅ 完整 | ✅ | |
| `/shell/student/lesson-plans` | ✅ | ✅ 完整 | ✅ | |
| `/shell/student/lesson-plans/[planId]/view` | ✅ | ✅ 完整 | ✅ | |
| `/shell/student/textbooks` | ✅ | ✅ 完整 | ✅ | |
| `/shell/student/textbooks/[id]/chapters` | ✅ | ✅ 完整 | ✅ | |
| `/shell/student/error-book` | ✅ | ✅ 完整 | ✅ | |
| `/shell/student/learning` | ✅ | ✅ 完整 | ✅ | |
| `/shell/student/learning-path` | ✅ | ✅ 完整 | ❌ | **缺 mock** |
| `/shell/student/practice` | ✅ | ✅ 完整 | ✅ | |
| `/shell/student/practice/[sessionId]` | ✅ | ✅ 完整 | ✅ | |
| `/shell/student/elective` | ✅ | ✅ 完整 | 🟡 | **部分缺 mock** |
| `/shell/student/elective/[id]` | ✅ | ✅ 完整 | ❌ | **缺 mock** |
| `/shell/student/ai-tutor` | ❌ | ✅ 完整 | ✅ | 全新设计 |
| `/shell/student/leave` | ✅ | ✅ 完整 | ✅ | |
| `/shell/student/diagnostic`CICD 额外) | ✅ | ✅ 完整 | ❌ | **mock 名不匹配** |
---
## 六、风险与建议
### 6.1 当前风险点
1. **Mock 数据缺口4 页)**learning-path / elective/[id] / diagnostic / elective部分的 MSW handler 缺失或名称不匹配,开发环境下这 4 页将显示空态而非 mock 数据。详见 §七。
2. **契约全部 @contract-pending**32 页的 GraphQL 查询字段在 schema 中均未定义根字段,全部依赖 MSW 兜底。后端补齐 schema 后需逐页切换 fetcher。
3. **权限校验待确认**:前端 client 组件未见 `usePermission().hasPermission()` 调用,权限校验可能依赖 Gateway + BFF 层(需确认后端实现)。
### 6.2 建议
1. **优先补齐 mock 缺口**:按 §七 列出的 4 个 mock 缺口补齐 graphql-data.ts 中的 case handler使全部 32 页在开发环境可完整运行。
2. **契约补齐计划**:按 §9.2 优先级P0 exams/homework → P1 grades/schedule → P2 其余)逐步在 shared-proto / BFF schema 中补齐 `studentXxx` 根字段。
3. **diagnostic 决策**ARCHITECTURE.md §9.2 未将 diagnostic 列入 36 页清单,但页面已实现。需产品确认是否保留并补充到 §9.2。
4. **权限点映射**:确认前端是否需要在 client 组件中添加 `usePermission().hasPermission()` 调用,或完全依赖 Gateway 层鉴权。
---
## 七、Mock 数据缺口清单(需补齐)
以下 4 个页面的 page.tsx + client 组件 + API hook 均已完整实现,但 `src/mocks/graphql-data.ts` 中缺少对应的 MSW case handler导致开发环境下页面无法展示 mock 数据:
| # | 页面 | GraphQL operationName | student.graphql.ts 位置 | graphql-data.ts 状态 | 影响 |
| --- | --------------------------------- | -------------------------- | ----------------------- | ----------------------------------------------------- | -------------------------------- |
| 1 | `/shell/student/learning-path` | `MyLearningPath` | L37 | ❌ 无 case | 学习路径页全空 |
| 2 | `/shell/student/elective/[id]` | `GetStudentElectiveDetail` | L439 | ❌ 无 case | 选课详情页全空 |
| 3 | `/shell/student/diagnostic` | `GetStudentSelfDiagnostic` | L711 | ❌ case 名不匹配(现有 `GetStudentDiagnostic` L9804 | 诊断报告页全空 |
| 4 | `/shell/student/elective`(部分) | `StudentSelections` | L87 | ❌ 无 case | "我的选课"区域空(可选课程正常) |
### 补齐方案
1. **`MyLearningPath`**:在 graphql-data.ts 添加 `case "MyLearningPath"` + 新建 `mockMyLearningPath` 数据(含 nodes/edges 结构)
2. **`GetStudentElectiveDetail`**:在 graphql-data.ts 添加 `case "GetStudentElectiveDetail"` + 新建 `mockStudentElectiveDetail` 数据(含课程详情 + 选课状态)
3. **`GetStudentSelfDiagnostic`**:将 graphql-data.ts L9804 的 `case "GetStudentDiagnostic"` 改为 `case "GetStudentSelfDiagnostic"`(或新增同名 case 保留原 case
4. **`StudentSelections`**:在 graphql-data.ts 添加 `case "StudentSelections"` + 新建 `mockStudentSelections` 数据(含已选课程列表)
---
## 八、与 2026-07-24 报告对比(变更总结)
| 维度 | 2026-07-24 报告 | 2026-08-04 核查结果 |
| -------------- | -------------------------------- | ----------------------------------------------- |
| 完成页面数 | 1仅 dashboard 基础版) | 32全部页面结构完整 |
| 缺失页面数 | 34 | 0 |
| 部分完成页面数 | 1dashboard 缺 trend/weakness | 0trend/weakness 已实现) |
| 架构模式 | Client Component + 硬编码中文 | Server Component + Client Component + next-intl |
| 路由路径对齐 | 6 类路径需迁移 | 全部已对齐 |
| 共享组件 | 40+ 组件需建立 | 全部已建立 |
| Mock 数据覆盖 | 未评估 | 28/32 页完整4 页有缺口 |
| 最大风险 | 35 页未实现 | 4 页 mock 缺口(结构均完整) |
> **总结**:原报告识别的 35 个待完成页面已全部实现。当前仅剩 4 个页面的 MSW mock 数据需补齐,不影响页面结构完整性,仅影响开发环境的 mock 数据展示。
---
**核查完成。32 个页面全部为真实业务实现page.tsx + client 组件 + API hook + 三态处理),其中 28 页 mock 数据齐全可完整运行4 页存在 mock 数据缺口需补齐(详见 §七)。**