Files
CICD/docs/design/007_school_module_implementation.md
SpecialX 57807def37
Some checks failed
CI / build-and-test (push) Failing after 3m50s
CI / deploy (push) Has been skipped
完整性更新
现在已经实现了大部分基础功能
2026-01-08 11:14:03 +08:00

7.6 KiB
Raw Permalink Blame History

学校基础数据模块School实现文档与更新记录

日期: 2026-01-07
作者: Frontend Team
状态: 已实现

1. 范围

本文档覆盖管理端「School」域的基础数据维护页面Schools / Departments / Academic Year / Grades并记录相关实现约束与关键更新遵循 003_frontend_engineering_standards.md 的工程规范Vertical Slice、Server/Client 边界、质量门禁)。

2. 路由入口Admin

School 域路由位于 src/app/(dashboard)/admin/school/*,均显式声明 export const dynamic = "force-dynamic" 以避免构建期预渲染触发数据库访问。

  • /admin/school:入口重定向到 Classes当前落点不在 school 模块内)
    实现:page.tsx
  • /admin/school/schools:学校维护(增删改)
    实现:page.tsx
  • /admin/school/departments:部门维护(增删改)
    实现:page.tsx
  • /admin/school/academic-year:学年维护(增删改 + 设为当前学年)
    实现:page.tsx
  • /admin/school/grades:年级维护(增删改 + 指派年级组长/教研组长)
    实现:page.tsx
  • /admin/school/grades/insights:年级维度作业统计(跨班级聚合)
    实现:page.tsx

3. 模块结构Vertical Slice

School 模块位于 src/modules/school

src/modules/school/
├── components/
│   ├── schools-view.tsx
│   ├── departments-view.tsx
│   ├── academic-year-view.tsx
│   └── grades-view.tsx
├── actions.ts
├── data-access.ts
├── schema.ts
└── types.ts

边界约束:

  • data-access.ts 包含 import "server-only",仅用于服务端查询与 DTO 组装。
  • actions.ts 包含 "use server",写操作统一通过 Server Actions 并 revalidatePath
  • components/* 为 Client 交互层表单、Dialog、筛选、行级操作调用 Server Actions 并用 sonner toast 反馈。

4. 数据访问data-access.ts

实现:data-access.ts

  • getSchools(): Promise<SchoolListItem[]>
  • getDepartments(): Promise<DepartmentListItem[]>
  • getAcademicYears(): Promise<AcademicYearListItem[]>
  • getGrades(): Promise<GradeListItem[]>
    • join schools 获取 school.name
    • 收集 gradeHeadId/teachingHeadId 并批量查询 users 以组装 StaffOption
  • getStaffOptions(): Promise<StaffOption[]>
    • 角色过滤 teacher/admin
    • 排序 name/email,用于 Select 列表可用性
  • getGradesForStaff(staffId: string): Promise<GradeListItem[]>
    • 用于按负责人(年级组长/教研组长)反查关联年级

返回 DTO 类型定义位于:types.ts

5. 写操作actions.ts

实现:actions.ts

通用约束:

  • 输入校验:统一使用 schema.ts 的 Zod schema
  • 返回结构:统一使用 ActionState
  • 刷新策略:对目标页面路径执行 revalidatePath

Departments

  • createDepartmentAction(formData)
  • updateDepartmentAction(departmentId, formData)
  • deleteDepartmentAction(departmentId)

Academic Year

  • createAcademicYearAction(formData)
  • updateAcademicYearAction(academicYearId, formData)
  • deleteAcademicYearAction(academicYearId)
    • isActive=true 时,通过事务把其它学年置为非激活,保证唯一激活学年

Schools

  • createSchoolAction(formData)
  • updateSchoolAction(schoolId, formData)
  • deleteSchoolAction(schoolId)
    • 删除后会同时刷新 /admin/school/schools/admin/school/grades

Grades

  • createGradeAction(formData)
  • updateGradeAction(gradeId, formData)
  • deleteGradeAction(gradeId)

6. UI 组件components/*

Schools

  • 实现:schools-view.tsx
  • 交互:列表 + Dialog 表单(新增/编辑)+ 删除确认AlertDialog

Departments

  • 实现:departments-view.tsx
  • 交互:列表 + Dialog 表单(新增/编辑)+ 删除确认AlertDialog

Academic Year

  • 实现:academic-year-view.tsx
  • 交互:列表 + Dialog 表单(新增/编辑)+ 删除确认AlertDialog+ 设为当前学年isActive

Grades

  • 实现:grades-view.tsx
  • 交互:列表展示 + URL 驱动筛选(搜索/学校/负责人/排序)+ Dialog 表单(新增/编辑)+ 删除确认AlertDialog
  • 负责人指派:
    • 年级组长gradeHeadId
    • 教研组长teachingHeadId

7. 关键交互与规则Grades

页面入口RSC 组装)在服务端并发拉取三类数据:

  • 年级列表:getGrades()
  • 学校选项:getSchools()
  • 负责人候选:getStaffOptions()

实现:page.tsx

7.1 URL State筛选/排序)

Grades 列表页的筛选状态 URL 化(nuqs

  • q:关键字(匹配 grade/school
  • school:学校过滤(all 或具体 schoolId
  • head:负责人过滤(全部 / 两者缺失 / 缺年级组长 / 缺教研组长)
  • sort:排序(默认/名称/更新时间等)

实现:grades-view.tsx

7.2 表单校验

Grades 的新增/编辑表单在客户端做轻量校验:

  • 必填:schoolIdname
  • order:非负整数
  • 去重:同一学校下年级名称不允许重复(忽略大小写 + 规范化空格)

说明:

  • 服务端写入前仍会经过 UpsertGradeSchema 校验schema.ts避免仅依赖客户端校验。

7.3 负责人选择Radix Select

Radix Select 约束:SelectItemvalue 不能为 ""(空字符串),否则会触发运行时错误。

当前实现策略:

  • UI 中 “未设置” 选项使用占位值 __none__
  • onValueChange 中将 __none__ 映射回 "" 存入本地表单 state
  • 提交时依旧传递空字符串,由 UpsertGradeSchema 将其归一为 null

实现:grades-view.tsx

2. 更新记录2026-01-07

  • 修复 Add Grades 弹窗报错:将 4 处 <SelectItem value=\"\"> 替换为占位值 __none__,并在 onValueChange 中映射回 \"\",保持“可清空选择/显示 placeholder”的行为不变。
  • 修复新建年级按钮不可用:创建/编辑表单在状态变化时触发实时校验更新,避免校验状态滞后导致提交被禁用。
  • 质量门禁:本地通过 npm run lintnpm run typecheck