# 恭学教育学生管理系统 P0 批次 — 设计规格

> 版本：v1.0  
> 日期：2026-07-05  
> 基于 PRD：`../../Downloads/PRD-恭学教育学生管理系统.md`（commit `59f75bb`）

## 目标

按 PRD 优先级分批实施，本批次覆盖 P0（阻塞上线）的全部 5 项 + 部分 P1（上线前完成），打通班级→排课→宿舍增强→操作日志→RBAC→考勤前端→数据面板的完整链路。

## 架构

Monorepo (Turborepo)，后端 NestJS 11 + TypeORM 0.3 + SQLite/MySQL，前端 React 19 + Vite + Ant Design 6 + ECharts。新增 `classes`、`schedules` 两个独立 NestJS 模块，在现有 `rooms`/`occupancies`/`bills` 模块上增量增强，考勤前端新建页面。遵循现有模块结构：每个业务模块独立目录，含 `entity`/`dto`/`service`/`controller`/`module`。

## 技术栈

| 层级 | 技术 |
|------|------|
| 架构 | Monorepo (Turborepo) |
| 前端 | React 19 + Vite + Ant Design 6 + ECharts |
| 后端 | NestJS 11 + TypeORM 0.3 + JWT + Passport |
| 数据库 | 开发 SQLite / 生产 MySQL 8 |
| 测试 | Jest (后端) + Playwright (前端 E2E) |

## 全局约束

- 所有涉及大量数据的列表/表单页面必须有详细的筛选方案
- 敏感信息（手机号、身份证）需脱敏展示，查看时二次确认+记录操作日志
- 遵循现有 NestJS 模块结构，每个业务模块独立目录
- 前端页面放在 `apps/admin/src/pages/` 下，每个模块独立目录
- 表名使用复数形式（与现有 `students`、`rooms`、`classrooms`、`bills` 一致）
- 冲突检测使用数据库唯一约束 + 应用层双重保障

---

## 模块 1：班级管理（Class）

### 实体

**`classes`**：班级主表，含 name/code/class_type/status/日期/教师/人数上限，通过 `department_id` 关联校区。

**`class_student`**：班级-学员关联，`UNIQUE(class_id, student_id)`，含 enroll_id/join_date/leave_date/status。

**`class_teacher`**：班级-教师关联，`UNIQUE(class_id, user_id, role_type)`，含 role_type（任课老师/班主任/生活老师/学服老师）+ subject。

Class 自身的 `head_teacher_id`/`life_teacher_id`/`academic_teacher_id` 与 `class_teacher` 表保持同步——创建/编辑班级时，若传了 head_teacher_id 等字段，同时写入 class_teacher 表对应记录。

### API

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/classes` | 班级列表（筛选：department_id/status/class_type/keyword） |
| POST | `/classes` | 创建班级（含初始教师/学员分配） |
| GET | `/classes/:id` | 班级详情（教师列表+学员统计） |
| PUT | `/classes/:id` | 编辑班级 |
| DELETE | `/classes/:id` | 删除班级（CASCADE 删除关联） |
| GET | `/classes/:id/students` | 班级学员列表 |
| POST | `/classes/:id/students` | 批量添加学员 `{ studentIds: number[] }` |
| DELETE | `/classes/:id/students/:studentId` | 移除学员 |
| GET | `/classes/:id/teachers` | 班级教师列表 |
| POST | `/classes/:id/teachers` | 添加教师 `{ userId, roleType, subject? }` |
| DELETE | `/classes/:id/teachers/:userId` | 移除教师 |

### 前端

**列表页 `Classes/index.tsx`**：筛选栏（校区下拉/班型下拉/状态下拉/搜索输入）+ 表格（名称/编码/校区/班型/日期/学员数/班主任/状态Tag/操作）+ 分页。

**详情页 `Classes/Detail.tsx`**：3 Tab — 基本信息（可编辑表单）、花名册（学员表格+批量添加/移除）、教师（教师表格+添加/移除）。

---

## 模块 2：排课管理（ClassSchedule）

### 实体

**`class_schedule`**：排课记录，含 class_id/classroom_id/week_day/start_time/end_time/start_date/end_date/subject/teacher_id/schedule_type（INTERNAL/RENTAL）/rental_id/status。

唯一约束：同教室 + 同 week_day + 时间段重叠 + status=active 拒绝写入（应用层检测 + 数据库层面依赖应用层保证，SQLite 不支持排他约束）。

### 核心规则

1. INTERNAL 排课：按班级维度，展示科目+教师，关联 class_id
2. RENTAL 排课：仅标记教室占用，关联 rental_id（classroom_rentals）
3. 冲突检测：新增/编辑时检查同教室同星期同时段是否已有 active 排课
4. 时段定义：预设 PRD 作息表（早自习 7:30-8:40、上午 9:00-12:00、下午 14:00-17:00、晚自习 18:30-21:00），管理员可在系统设置中调整

### API

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/class-schedules` | 排课列表（筛选：classroom_id/class_id/week_day/date_range） |
| POST | `/class-schedules` | 创建排课（含冲突检测） |
| PUT | `/class-schedules/:id` | 编辑排课（含冲突检测） |
| DELETE | `/class-schedules/:id` | 删除排课 |
| GET | `/class-schedules/weekly` | 周视图数据（按教室+week_day聚合） |
| GET | `/class-schedules/classroom/:id/occupancy` | 教室占用时间线（合并内部+外部） |

### 前端

**周视图 `Schedules/index.tsx`**：顶部周切换 + 教室/班级筛选，主体 7列×N行矩阵（列=周一~周日，行=教室），每格显示科目/教师/时间。点击格子弹出排课表单 Modal（班级/科目/教师/教室/星期/时段/日期范围）。冲突时 Modal 内红色提示。

---

## 模块 3：宿舍/入住/账单增强

### 数据变更

**rooms**：新增 `rental_category`（VARCHAR(10)，long/short）、`monthly_rate`（DECIMAL(10,2)）。

**occupancies**：新增 `rental_type`（VARCHAR(10)，long/short）、`tenant_id`（FK → tenants.id）。

**students**：新增 `tenant_id`（FK → tenants.id），替代原 `organization` 文本字段。迁移时根据 `organization` 匹配 `tenants.name`。

### 账单逻辑

`POST /bills/generate` 内部：遍历宿舍的 occupancy 记录，`rental_type === 'long'` 的长租学生走固定月费独立生成（不参与分摊），`short` 学生走原人天数加权分摊。

### 前端

Rooms 表单增加 `rental_category` Select + `monthly_rate` InputNumber。Occupancies 表单增加 `rental_type` Select + `tenant_id` Select。

---

## 模块 4：操作日志全量接入

### 覆盖范围

在以下 controller 的每个写操作中统一调用 `OperationLogsService.log()`：

| 模块 | 审计操作 |
|------|---------|
| Classes | 创建/编辑/删除班级、添加/移除学员、添加/移除教师 |
| ClassSchedules | 创建/编辑/删除排课 |
| Students | 新增/编辑/删除学生、导入/导出、查看敏感信息 |
| Occupancies | 新增/编辑/退住 |
| Expenses | 新增/编辑/删除费用 |
| Bills | 生成账单、确认账单、标记已付、导出 |
| Deposits | 收取/退还押金 |
| Attendance | 补录/编辑考勤、匹配钉钉数据 |
| RBAC | 角色变更、权限变更 |

### 调用模式

```typescript
this.operationLogsService.log({
  userId: req.user.id,
  username: req.user.username,
  module: 'CLASS',
  action: 'CREATE',
  targetId: result.id,
  targetType: 'class',
  detail: { name: result.name },
  ipAddress: req.ip,
  status: 'success',
});
```

---

## 模块 5：RBAC 权限扩展

`permission.json` 新增以下节点：

| 权限节点 | super_admin | staff | class_teacher | student |
|----------|:---:|:---:|:---:|:---:|
| CLASS:READ | ✅ | ✅ | ✅ | - |
| CLASS:ADD | ✅ | ✅ | - | - |
| CLASS:UPDATE | ✅ | ✅ | - | - |
| CLASS:DELETE | ✅ | - | - | - |
| SCHEDULE:READ | ✅ | ✅ | ✅ | - |
| SCHEDULE:ADD | ✅ | ✅ | - | - |
| SCHEDULE:UPDATE | ✅ | ✅ | - | - |
| SCHEDULE:DELETE | ✅ | - | - | - |
| ATTENDANCE:READ | ✅ | ✅ | ✅ | ✅ |
| ATTENDANCE:ADD | ✅ | ✅ | - | - |
| ATTENDANCE:UPDATE | ✅ | ✅ | - | - |

---

## 模块 6：考勤管理前端

### 页面

**列表页 `Attendance/index.tsx`**：筛选栏（班级/日期范围/时段/状态/来源）+ 表格（姓名/班级/日期/时段/状态Tag/来源/打卡时间/备注/操作）+ 批量补录按钮 + 切换到日历视图按钮。

**日历视图**：切换模式，行=学生、列=日期+时段，格=状态色块（绿=出勤、黄=迟到、红=缺勤、蓝=请假）。

### API

增强现有 `/attendance-records` 筛选参数，新增：

| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/attendance-records/batch` | 批量补录 |
| GET | `/attendance-records/summary` | 考勤汇总统计（按班级/日期范围） |
| GET | `/attendance-records/calendar` | 日历视图数据 |
| GET | `/ding-attendance-raw` | 钉钉原始数据列表（筛选match_status） |
| POST | `/ding-attendance-raw/:id/match` | 手动匹配钉钉数据到学生 |

---

## 模块 7：数据面板增强

在现有 Dashboard 的基础上：

- **第二行指标卡**：教室总数/占用率、今日出勤率、本月收入总额、教室占用率
- **新增图表**：考勤趋势折线图（近30天）、近6月收入趋势图
- **权限控制**：不同角色看到不同指标——班主任只看本班考勤，超管看全局

### API

`GET /dashboard` 返回数据增加字段：`classroomCount`/`classroomOccupancyRate`/`todayAttendanceRate`/`monthlyIncome`/`attendanceTrend`/`incomeTrend`。

---

## 数据库迁移

### 新增表

`classes`、`class_student`、`class_teacher`、`class_schedule` — TypeORM `synchronize: true` 自动建表。

### 现有表变更

```sql
ALTER TABLE rooms ADD COLUMN rental_category VARCHAR(10) DEFAULT 'short';
ALTER TABLE rooms ADD COLUMN monthly_rate DECIMAL(10,2) DEFAULT 0;
ALTER TABLE occupancies ADD COLUMN rental_type VARCHAR(10) DEFAULT 'short';
ALTER TABLE occupancies ADD COLUMN tenant_id INTEGER REFERENCES tenants(id);
ALTER TABLE students ADD COLUMN tenant_id INTEGER REFERENCES tenants(id);
```

### 数据迁移

`students.organization` → `students.tenant_id`：遍历 students 表，根据 `organization` 文本匹配 `tenants.name`，匹配到的写入 tenant_id，未匹配的置 NULL。原 `organization` 列保留不删，待后续迭代清理。

---

## 文件结构

```
apps/server/src/
├── entities/
│   ├── class.entity.ts          🆕
│   ├── class-student.entity.ts  🆕
│   ├── class-teacher.entity.ts  🆕
│   ├── class-schedule.entity.ts 🆕
│   ├── room.entity.ts           ✏️
│   ├── occupancy.entity.ts      ✏️
│   ├── student.entity.ts        ✏️
│   └── index.ts                 ✏️
├── classes/                     🆕
│   ├── classes.module.ts
│   ├── classes.controller.ts
│   ├── classes.service.ts
│   └── dto/
├── schedules/                   🆕
│   ├── schedules.module.ts
│   ├── schedules.controller.ts
│   ├── schedules.service.ts
│   └── dto/
├── rooms/                       ✏️ dto
├── occupancies/                 ✏️ dto + service
├── bills/                       ✏️ service
├── operation-logs/              ✏️ 注入各模块
├── rbac/                        ✏️ permission.json
├── dashboard/                   ✏️ service + controller
└── app.module.ts                ✏️ 注册新模块

apps/admin/src/pages/
├── Classes/                     🆕
│   ├── index.tsx
│   └── Detail.tsx
├── Schedules/                   🆕
│   └── index.tsx
├── Attendance/                  🆕
│   └── index.tsx
├── Dashboard/                   ✏️ index.tsx
├── Rooms/                       ✏️ index.tsx
└── Occupancies/                 ✏️ index.tsx
```

---

## 开发顺序

```
Phase 1  班级管理（无上游依赖）
  │
Phase 2  排课管理（依赖班级+教室）
  │
Phase 3  宿舍/入住/账单增强（独立，可与1/2并行）
  │
Phase 4  操作日志全量接入（依赖1/2/3的controller就绪）
  │
Phase 5  RBAC权限扩展（依赖1/2模块存在）
  │
Phase 6  考勤管理前端（后端API已有，独立开发）
  │
Phase 7  数据面板增强（依赖各方面数据）
```

Phase 1-3 可部分并行，Phase 4-5 需等 1-3 完成，Phase 6-7 可在 1-5 完成后并行。
