# 教师仪表盘实现与 Hydration 修复记录 **日期**: 2025-12-23 **作者**: 资深前端工程师 (Senior Frontend Engineer) **状态**: 已实现 ## 1. 概述 本文档详细说明了教师仪表盘 (Teacher Dashboard) 的实现细节,该实现严格遵循 Next_Edu 设计系统 v1.3.0。文档还记录了开发过程中遇到的 Hydration 错误及其解决方案。 ## 2. 组件架构 仪表盘采用垂直切片架构 (Vertical Slice Architecture),代码位于 `src/modules/dashboard`。 ### 2.1 文件结构 ``` src/modules/dashboard/ └── components/ ├── teacher-stats.tsx # 核心指标 (学生数, 课程数, 待批改作业) ├── teacher-schedule.tsx # 今日日程列表 ├── recent-submissions.tsx # 最近的学生提交记录 └── teacher-quick-actions.tsx # 常用操作 (创建作业等) ``` ### 2.2 设计系统集成 所有组件严格遵循 v1.3.0 规范: - **排版 (Typography)**: 使用 `Geist Sans`,数据展示开启 `tabular-nums`。 - **色彩 (Colors)**: 使用语义化 HSL 变量 (`muted`, `primary`, `destructive`)。 - **图标 (Icons)**: 使用 `lucide-react` (如 `Users`, `BookOpen`, `Inbox`)。 - **状态 (States)**: - **Loading**: 使用自定义骨架屏 (Skeleton),拒绝全屏 Spinner。 - **Empty**: 使用 `EmptyState` 组件处理无数据场景。 ## 3. 组件详情 ### 3.1 TeacherStats (教师统计) - **用途**: 展示教师当前状态的高层概览。 - **特性**: - 在响应式网格中展示 4 个关键指标。 - 支持 `isLoading` 属性以渲染骨架屏。 - 使用 `Card` 组件作为容器。 ### 3.2 TeacherSchedule (教师日程) - **用途**: 展示今日课程安排。 - **特性**: - 列出课程时间及地点。 - 使用 Badge 区分 "Lecture" (讲座) 和 "Workshop" (研讨会)。 - **空状态**: 当无日程时显示 "No Classes Today"。 ### 3.3 RecentSubmissions (最近提交) - **用途**: 追踪最新的学生活动。 - **特性**: - 展示学生头像、姓名、作业名称及时间。 - "Late" (迟交) 状态指示器。 - **空状态**: 当列表为空时显示 "No New Submissions"。 ### 3.4 EmptyState Component (空状态组件) - **位置**: `src/shared/components/ui/empty-state.tsx` - **规范**: - 虚线边框容器。 - 居中图标 (Muted 背景)。 - 清晰的标题和描述。 - 可选的操作按钮插槽。 ## 4. Hydration 错误修复 ### 4.1 问题描述 开发过程中观察到 "Hydration failed" 错误,原因是 HTML 嵌套无效。具体来说,是 `p` 标签内包含了块级元素(或 React 在 hydration 检查期间视为块级的元素)。 ### 4.2 根本原因分析 React 的 hydration 过程对 HTML 有效性要求极高。将 `div` 放入 `p` 标签中违反了 HTML5 标准,但浏览器通常会自动修正 DOM 结构,导致实际 DOM 与 React 基于虚拟 DOM 预期的结构不一致。 ### 4.3 实施的修复 将所有仪表盘组件中存在风险的 `p` 标签替换为 `div` 标签,以确保嵌套结构的健壮性。 **示例 (RecentSubmissions):** *修改前 (有风险):* ```tsx

{item.studentName}

``` *修改后 (安全):* ```tsx
{item.studentName}
``` **受影响的组件:** 1. `recent-submissions.tsx` 2. `teacher-stats.tsx` 3. `teacher-schedule.tsx` ## 5. 更新记录(2026-01-04) - 教师仪表盘从 Mock Data 切换为真实数据查询:`/teacher/dashboard` 组合 `getTeacherClasses`、`getClassSchedule`、`getHomeworkSubmissions({ creatorId })` 渲染 KPI / 今日课表 / 最近提交。 - Quick Actions 落地为真实路由跳转(创建作业、查看列表等)。 - Schedule / Submissions 增加 “View All” 跳转到对应列表页(并携带筛选参数)。 --- ## 6. 教师端班级管理模块(真实数据接入记录) **日期**: 2025-12-31 **范围**: 教师端「我的班级 / 学生 / 课表」页面与 MySQL(Drizzle) 真数据对接 ### 6.1 页面入口与路由 班级管理相关页面位于: - `src/app/(dashboard)/teacher/classes/my/page.tsx` - `src/app/(dashboard)/teacher/classes/students/page.tsx` - `src/app/(dashboard)/teacher/classes/schedule/page.tsx` 为避免构建期/预渲染阶段访问数据库导致失败,以上页面显式启用动态渲染: - `export const dynamic = "force-dynamic"` ### 6.2 模块结构(Vertical Slice) 班级模块采用垂直切片架构,代码位于 `src/modules/classes/`: ``` src/modules/classes/ ├── components/ │ ├── my-classes-grid.tsx │ ├── students-filters.tsx │ ├── students-table.tsx │ ├── schedule-filters.tsx │ └── schedule-view.tsx ├── data-access.ts └── types.ts ``` 其中 `data-access.ts` 负责班级、学生、课表三类查询的服务端数据读取,并作为页面层唯一的数据入口。 ### 6.3 数据库表与迁移 新增班级领域表: - `classes` - `class_enrollments` - `class_schedule` 对应 Drizzle Schema: - `src/shared/db/schema.ts` - `src/shared/db/relations.ts` 对应迁移文件: - `drizzle/0003_petite_newton_destine.sql` 外键关系(核心): - `classes.teacher_id` -> `users.id` - `class_enrollments.class_id` -> `classes.id` - `class_enrollments.student_id` -> `users.id` - `class_schedule.class_id` -> `classes.id` 索引(核心): - `classes_teacher_idx`, `classes_grade_idx` - `class_enrollments_class_idx`, `class_enrollments_student_idx` - `class_schedule_class_idx`, `class_schedule_class_day_idx` ### 6.4 Seed 数据 Seed 脚本已覆盖班级相关数据,以便在开发环境快速验证页面渲染与关联关系: - `scripts/seed.ts` - 运行命令:`npm run db:seed` ### 6.5 开发过程中的问题与处理 - 端口占用(EADDRINUSE):开发服务器端口被占用时,通过更换端口启动规避(例如 `next dev -p `)。 - Next dev 锁文件:出现 `.next/dev/lock` 无法获取锁时,需要确保只有一个 dev 实例在运行,并清理残留 lock。 - 头像资源 404:移除 Header 中硬编码的本地头像资源引用,避免 `public/avatars/...` 不存在导致的 404 噪音(见 `src/modules/layout/components/site-header.tsx`)。 - 班级人数统计查询失败:`class_enrollments` 表实际列名为 `class_enrollment_status`,修复查询中引用的列名以恢复教师端班级列表渲染。 ### 6.6 班级详情页(聚合视图 + Schedule Builder + Homework 统计) **日期**: 2026-01-04 **入口**: `src/app/(dashboard)/teacher/classes/my/[id]/page.tsx` 聚合数据在单次 RSC 请求内并发获取: - 学生:`getClassStudents({ classId })` - 课表:`getClassSchedule({ classId })` - 作业统计:`getClassHomeworkInsights({ classId, limit })`(包含 latest、历史列表、overallScores、以及每次作业的 scoreStats:avg/median) 页面呈现: - 顶部 KPI 卡片:学生数、课表条目数、作业数、整体 avg/median - Latest homework:目标人数、提交数、批改数、avg/median,直达作业与提交列表 - Students / Schedule 预览:提供 View all 跳转到完整列表页 - Homework history 表格:支持通过 URL query `?hw=all|active|overdue` 过滤作业记录,并展示每条作业的 avg/median 课表编辑能力复用既有 Builder: - 组件:`src/modules/classes/components/schedule-view.tsx`(新增/编辑/删除课表项) - 数据变更:`src/modules/classes/actions.ts` ### 6.7 班级邀请码(6 位码)加入与管理 **日期**: 2026-01-08 **范围**: 为班级新增 6 位邀请码,支持学生通过输入邀请码加入班级;教师可查看与刷新邀请码 #### 6.7.1 数据结构 - 表:`classes` - 字段:`invitation_code`(varchar(6),unique,可为空) - 迁移:`drizzle/0007_add_class_invitation_code.sql` #### 6.7.2 教师端能力 - 在「我的班级」卡片中展示邀请码。 - 提供“刷新邀请码”操作:生成新的 6 位码并写入数据库(确保唯一性)。 #### 6.7.3 学生端能力 - 提供“通过邀请码加入班级”的入口,输入 6 位码后完成报名。 - 写库操作设计为幂等:重复提交同一个邀请码不会生成重复报名记录,已有记录会被更新为有效状态。 #### 6.7.4 Seed 支持 - `scripts/seed.ts` 为示例班级补充 `invitationCode`,便于在开发环境直接验证加入流程。