# 学生域(Student)待完成功能分析 > 参考项目:`e:\desktop\CICD\src\app\(dashboard)\student\` > 当前项目:`e:\Desktop\Edu\apps\portal-shell\src\app\shell\student\` > 规划依据:`apps/portal-shell/ARCHITECTURE.md` §9.2(36 页,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 | | 数据来源 | 直查 DB(Drizzle) | 走 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 共享路由 | > **加上 diagnostic(CICD 额外页)共 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//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`(diagnostic,CICD 额外) - 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. **P5(B3 末)**: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//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 中无源页面,需重点设计。**