2026-01-31 17:55:58 +08:00
|
|
|
|
# 盘古用户平台 - 基础数据模块技术方案
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
| 文档信息 | 内容 |
|
|
|
|
|
|
| -------- | --------------------------------- |
|
|
|
|
|
|
| **文档版本** | V1.0 |
|
|
|
|
|
|
| **项目名称** | 盘古用户平台(Pangu User Platform) |
|
|
|
|
|
|
| **模块名称** | 基础数据管理(年级、班级、学科、区域) |
|
|
|
|
|
|
| **编写团队** | pangu |
|
|
|
|
|
|
| **创建日期** | 2026-01-31 |
|
|
|
|
|
|
| **审核状态** | 待评审 |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 修订记录
|
|
|
|
|
|
|
|
|
|
|
|
| 版本 | 日期 | 修订人 | 修订内容 |
|
|
|
|
|
|
| ---- | ---------- | ----- | ---- |
|
|
|
|
|
|
| V1.0 | 2026-01-31 | pangu | 初稿 |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 目录
|
|
|
|
|
|
|
|
|
|
|
|
1. [模块概述](#1-模块概述)
|
|
|
|
|
|
2. [需求分析](#2-需求分析)
|
|
|
|
|
|
3. [系统设计](#3-系统设计)
|
|
|
|
|
|
4. [前端技术方案](#4-前端技术方案)
|
|
|
|
|
|
5. [后端技术方案](#5-后端技术方案)
|
|
|
|
|
|
6. [数据库设计](#6-数据库设计)
|
|
|
|
|
|
7. [接口设计](#7-接口设计)
|
|
|
|
|
|
8. [开发计划](#8-开发计划)
|
|
|
|
|
|
9. [测试方案](#9-测试方案)
|
|
|
|
|
|
10. [风险与应对](#10-风险与应对)
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 1. 模块概述
|
|
|
|
|
|
|
|
|
|
|
|
### 1.1 模块定位
|
|
|
|
|
|
|
|
|
|
|
|
基础数据模块是盘古用户平台的核心支撑模块,负责管理系统运行所需的基础字典数据。这些数据被学校管理、学生管理等业务模块广泛引用,是整个系统数据完整性的基础保障。
|
|
|
|
|
|
|
|
|
|
|
|
### 1.2 功能范围
|
|
|
|
|
|
|
|
|
|
|
|
| 子模块 | 功能描述 | 数据特性 |
|
|
|
|
|
|
| ----- | ----------------- | -------- |
|
|
|
|
|
|
| 年级管理 | 管理年级字典数据(一年级~高三等) | 字典型、被学校引用 |
|
|
|
|
|
|
| 班级管理 | 管理班级字典数据(1班~10班等) | 字典型、被学校引用 |
|
|
|
|
|
|
| 学科管理 | 管理学科字典数据(语文、数学等) | 字典型、被学生引用 |
|
|
|
|
|
|
| 区域管理 | 管理省-市-区三级区域树 | 树形、被学校引用 |
|
|
|
|
|
|
|
|
|
|
|
|
> **说明**:区域管理的核心逻辑是管理每个区域下的学校信息,区域只被学校表直接引用,会员和学生通过学校间接关联区域。
|
|
|
|
|
|
|
|
|
|
|
|
### 1.3 关联关系
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
基础数据模块关联图:
|
|
|
|
|
|
|
|
|
|
|
|
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
|
|
|
|
|
│ 年级管理 │────▶│ 学校-年级 │────▶│ 学校管理 │
|
|
|
|
|
|
│ (pg_grade) │ │ 关联表 │ │ (pg_school) │
|
|
|
|
|
|
└─────────────┘ └─────────────┘ └─────────────┘
|
|
|
|
|
|
│
|
|
|
|
|
|
▼
|
|
|
|
|
|
┌─────────────┐ ┌─────────────┐
|
|
|
|
|
|
│ 班级管理 │────▶│ 学校-班级 │
|
|
|
|
|
|
│ (pg_class) │ │ 关联表 │
|
|
|
|
|
|
└─────────────┘ └─────────────┘
|
|
|
|
|
|
│
|
|
|
|
|
|
▼
|
|
|
|
|
|
┌─────────────┐ ┌─────────────┐
|
|
|
|
|
|
│ 学科管理 │────▶│ 学生管理 │
|
|
|
|
|
|
│ (pg_subject)│ │ (pg_student)│
|
|
|
|
|
|
└─────────────┘ └─────────────┘
|
|
|
|
|
|
|
|
|
|
|
|
┌─────────────┐
|
|
|
|
|
|
│ 区域管理 │────▶ 学校(区域ID)
|
|
|
|
|
|
│ (pg_region) │
|
|
|
|
|
|
└─────────────┘
|
|
|
|
|
|
|
|
|
|
|
|
> 区域通过学校间接关联会员和学生,删除区域时只需检查学校引用
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 2. 需求分析
|
|
|
|
|
|
|
|
|
|
|
|
### 2.1 功能需求
|
|
|
|
|
|
|
|
|
|
|
|
#### 2.1.1 年级管理
|
|
|
|
|
|
|
|
|
|
|
|
| 功能编号 | 功能名称 | 功能描述 | 优先级 |
|
|
|
|
|
|
| ------- | ------ | --------------- | :-: |
|
|
|
|
|
|
| GRD-001 | 年级列表查询 | 按名称、编码、状态筛选分页查询 | P0 |
|
|
|
|
|
|
| GRD-002 | 新增年级 | 创建年级,编码自动生成 | P0 |
|
|
|
|
|
|
| GRD-003 | 编辑年级 | 修改年级名称、排序、状态 | P0 |
|
|
|
|
|
|
| GRD-004 | 删除年级 | 软删除(需检查学校引用) | P1 |
|
|
|
|
|
|
| GRD-005 | 年级选项 | 供下拉选择使用的选项列表 | P0 |
|
|
|
|
|
|
|
|
|
|
|
|
**业务规则:**
|
|
|
|
|
|
- 年级编码自动生成,格式:GRD + 3位序号(如GRD001)
|
|
|
|
|
|
- 删除前检查是否被学校-年级关联表引用,有则不允许删除
|
|
|
|
|
|
- 所有删除为软删除(del_flag = 1)
|
|
|
|
|
|
|
|
|
|
|
|
#### 2.1.2 班级管理
|
|
|
|
|
|
|
|
|
|
|
|
| 功能编号 | 功能名称 | 功能描述 | 优先级 |
|
|
|
|
|
|
| ------- | ------ | --------------- | :-: |
|
|
|
|
|
|
| CLS-001 | 班级列表查询 | 按名称、编码、状态筛选分页查询 | P0 |
|
|
|
|
|
|
| CLS-002 | 新增班级 | 创建班级,编码自动生成 | P0 |
|
|
|
|
|
|
| CLS-003 | 编辑班级 | 修改班级名称、排序、状态 | P0 |
|
|
|
|
|
|
| CLS-004 | 删除班级 | 软删除(需检查学校引用) | P1 |
|
|
|
|
|
|
| CLS-005 | 班级选项 | 供下拉选择使用的选项列表 | P0 |
|
|
|
|
|
|
|
|
|
|
|
|
**业务规则:**
|
|
|
|
|
|
- 班级编码自动生成,格式:CLS + 3位序号(如CLS001)
|
|
|
|
|
|
- 删除前检查是否被学校-班级关联表引用,有则不允许删除
|
|
|
|
|
|
|
|
|
|
|
|
#### 2.1.3 学科管理
|
|
|
|
|
|
|
|
|
|
|
|
| 功能编号 | 功能名称 | 功能描述 | 优先级 |
|
|
|
|
|
|
| ------- | ------ | --------------- | :-: |
|
|
|
|
|
|
| SUB-001 | 学科列表查询 | 按名称、编码、状态筛选分页查询 | P0 |
|
|
|
|
|
|
| SUB-002 | 新增学科 | 创建学科信息 | P0 |
|
|
|
|
|
|
| SUB-003 | 编辑学科 | 修改学科名称、排序、状态 | P0 |
|
|
|
|
|
|
| SUB-004 | 删除学科 | 软删除(需检查学生引用) | P1 |
|
|
|
|
|
|
| SUB-005 | 学科选项 | 供下拉选择使用的选项列表 | P0 |
|
|
|
|
|
|
|
|
|
|
|
|
**业务规则:**
|
|
|
|
|
|
- 学科编码自动生成,格式:SUB + 3位序号(如SUB001)
|
|
|
|
|
|
- 删除前检查是否被学生表引用,有则不允许删除
|
|
|
|
|
|
|
|
|
|
|
|
#### 2.1.4 区域管理
|
|
|
|
|
|
|
|
|
|
|
|
| 功能编号 | 功能名称 | 功能描述 | 优先级 |
|
|
|
|
|
|
| ------- | ----- | ------------------- | :-: |
|
|
|
|
|
|
| REG-001 | 区域树查询 | 树形展示省-市-区层级结构 | P0 |
|
|
|
|
|
|
| REG-002 | 新增区域 | 在指定节点下新增区域 | P0 |
|
|
|
|
|
|
| REG-003 | 编辑区域 | 修改区域名称 | P0 |
|
|
|
|
|
|
| REG-004 | 删除区域 | 软删除(需检查子级和学校/会员引用) | P1 |
|
|
|
|
|
|
| REG-005 | 新增下级 | 在当前区域下新增子区域(自动带入父级) | P0 |
|
|
|
|
|
|
|
|
|
|
|
|
**业务规则:**
|
|
|
|
|
|
- 区域编码自动生成,格式:区分层级
|
|
|
|
|
|
- 省级:REG + 2位序号(如REG01)
|
|
|
|
|
|
- 市级:父编码 + 2位序号(如REG0101)
|
|
|
|
|
|
- 区级:父编码 + 2位序号(如REG010101)
|
|
|
|
|
|
- 新增下级时自动带入父级区域信息
|
|
|
|
|
|
- 删除前检查:
|
|
|
|
|
|
- 是否有子级区域,有则不允许删除
|
|
|
|
|
|
- 是否被学校引用,有则不允许删除
|
|
|
|
|
|
|
|
|
|
|
|
### 2.2 角色权限
|
|
|
|
|
|
|
|
|
|
|
|
| 功能 | 超级管理员 | 分公司用户 | 学校用户 |
|
|
|
|
|
|
| ------ | :---: | :---: | :--: |
|
|
|
|
|
|
| 年级管理 | ✓ | ✓ | ✗ |
|
|
|
|
|
|
| 班级管理 | ✓ | ✓ | ✗ |
|
|
|
|
|
|
| 学科管理 | ✓ | ✓ | ✗ |
|
|
|
|
|
|
| 区域管理 | ✓ | ✗ | ✗ |
|
|
|
|
|
|
|
|
|
|
|
|
### 2.3 非功能需求
|
|
|
|
|
|
|
|
|
|
|
|
| 需求类型 | 需求描述 |
|
|
|
|
|
|
| ------ | --------------- |
|
|
|
|
|
|
| 性能需求 | 列表查询响应时间 ≤ 200ms |
|
|
|
|
|
|
| 数据完整性 | 删除前必须检查引用关系 |
|
|
|
|
|
|
| 缓存需求 | 区域树数据需缓存,减少查询压力 |
|
|
|
|
|
|
| 日志需求 | 增删改操作需记录操作日志 |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 3. 系统设计
|
|
|
|
|
|
|
|
|
|
|
|
### 3.1 模块架构
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
基础数据模块架构:
|
|
|
|
|
|
|
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ 前端层 (Vue 3) │
|
|
|
|
|
|
├────────────────┬────────────────┬───────────────┬───────────────┤
|
|
|
|
|
|
│ GradeView │ ClassView │ SubjectView │ RegionView │
|
|
|
|
|
|
│ 年级管理页面 │ 班级管理页面 │ 学科管理页面 │ 区域管理页面 │
|
|
|
|
|
|
└───────┬────────┴───────┬────────┴───────┬───────┴───────┬───────┘
|
|
|
|
|
|
│ │ │ │
|
|
|
|
|
|
▼ ▼ ▼ ▼
|
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ API层 (Axios) │
|
|
|
|
|
|
│ grade.js class.js subject.js region.js │
|
|
|
|
|
|
└───────────────────────────────┬─────────────────────────────────┘
|
|
|
|
|
|
│
|
|
|
|
|
|
▼
|
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ 后端层 (Spring Boot) │
|
|
|
|
|
|
├────────────────┬────────────────┬───────────────┬───────────────┤
|
|
|
|
|
|
│ GradeController│ ClassController│SubjectController│RegionController│
|
|
|
|
|
|
└───────┬────────┴───────┬────────┴───────┬───────┴───────┬───────┘
|
|
|
|
|
|
│ │ │ │
|
|
|
|
|
|
▼ ▼ ▼ ▼
|
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ Service层 │
|
|
|
|
|
|
│ GradeService ClassService SubjectService RegionService │
|
|
|
|
|
|
└───────────────────────────────┬─────────────────────────────────┘
|
|
|
|
|
|
│
|
|
|
|
|
|
▼
|
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ Mapper层 (MyBatis Plus) │
|
|
|
|
|
|
│ GradeMapper ClassMapper SubjectMapper RegionMapper │
|
|
|
|
|
|
└───────────────────────────────┬─────────────────────────────────┘
|
|
|
|
|
|
│
|
|
|
|
|
|
▼
|
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ MySQL 8.0 │
|
|
|
|
|
|
│ pg_grade pg_class pg_subject pg_region │
|
|
|
|
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3.2 技术选型
|
|
|
|
|
|
|
|
|
|
|
|
| 层次 | 技术 | 版本 | 说明 |
|
|
|
|
|
|
| --- | ------------ | ----- | ------ |
|
|
|
|
|
|
| 前端 | Vue | 3.5.x | 视图框架 |
|
|
|
|
|
|
| 前端 | Element Plus | 2.x | UI组件库 |
|
|
|
|
|
|
| 前端 | Pinia | 3.x | 状态管理 |
|
|
|
|
|
|
| 后端 | Spring Boot | 3.3.x | 应用框架 |
|
|
|
|
|
|
| 后端 | MyBatis Plus | 3.5.x | ORM框架 |
|
|
|
|
|
|
| 数据库 | MySQL | 8.0 | 关系数据库 |
|
|
|
|
|
|
| 缓存 | Redis | 7.x | 区域树缓存 |
|
|
|
|
|
|
|
|
|
|
|
|
### 3.3 目录结构
|
|
|
|
|
|
|
|
|
|
|
|
**前端目录结构:**
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
pangu-ui/src/
|
|
|
|
|
|
├── api/
|
|
|
|
|
|
│ ├── grade.js # 年级API
|
|
|
|
|
|
│ ├── class.js # 班级API
|
|
|
|
|
|
│ ├── subject.js # 学科API
|
|
|
|
|
|
│ └── region.js # 区域API
|
|
|
|
|
|
├── mock/
|
|
|
|
|
|
│ ├── grade.js # 年级Mock
|
|
|
|
|
|
│ ├── class.js # 班级Mock
|
|
|
|
|
|
│ ├── subject.js # 学科Mock
|
|
|
|
|
|
│ └── region.js # 区域Mock
|
|
|
|
|
|
└── views/base/
|
|
|
|
|
|
├── grade/
|
|
|
|
|
|
│ └── index.vue # 年级管理页面
|
|
|
|
|
|
├── class/
|
|
|
|
|
|
│ └── index.vue # 班级管理页面
|
|
|
|
|
|
├── subject/
|
|
|
|
|
|
│ └── index.vue # 学科管理页面
|
|
|
|
|
|
└── region/
|
|
|
|
|
|
└── index.vue # 区域管理页面
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**后端目录结构:**
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
pangu-admin/src/main/java/com/pangu/
|
|
|
|
|
|
├── base/
|
|
|
|
|
|
│ ├── controller/
|
|
|
|
|
|
│ │ ├── GradeController.java
|
|
|
|
|
|
│ │ ├── ClassController.java
|
|
|
|
|
|
│ │ ├── SubjectController.java
|
|
|
|
|
|
│ │ └── RegionController.java
|
|
|
|
|
|
│ ├── service/
|
|
|
|
|
|
│ │ ├── IGradeService.java
|
|
|
|
|
|
│ │ ├── IClassService.java
|
|
|
|
|
|
│ │ ├── ISubjectService.java
|
|
|
|
|
|
│ │ ├── IRegionService.java
|
|
|
|
|
|
│ │ └── impl/
|
|
|
|
|
|
│ │ ├── GradeServiceImpl.java
|
|
|
|
|
|
│ │ ├── ClassServiceImpl.java
|
|
|
|
|
|
│ │ ├── SubjectServiceImpl.java
|
|
|
|
|
|
│ │ └── RegionServiceImpl.java
|
|
|
|
|
|
│ ├── mapper/
|
|
|
|
|
|
│ │ ├── GradeMapper.java
|
|
|
|
|
|
│ │ ├── ClassMapper.java
|
|
|
|
|
|
│ │ ├── SubjectMapper.java
|
|
|
|
|
|
│ │ └── RegionMapper.java
|
|
|
|
|
|
│ └── domain/
|
|
|
|
|
|
│ ├── Grade.java
|
|
|
|
|
|
│ ├── PgClass.java
|
|
|
|
|
|
│ ├── Subject.java
|
|
|
|
|
|
│ └── Region.java
|
|
|
|
|
|
└── resources/mapper/base/
|
|
|
|
|
|
├── GradeMapper.xml
|
|
|
|
|
|
├── ClassMapper.xml
|
|
|
|
|
|
├── SubjectMapper.xml
|
|
|
|
|
|
└── RegionMapper.xml
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 4. 前端技术方案
|
|
|
|
|
|
|
|
|
|
|
|
### 4.1 页面设计
|
|
|
|
|
|
|
|
|
|
|
|
#### 4.1.1 年级/班级/学科管理(列表页通用模式)
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ 搜索区域 │
|
|
|
|
|
|
├─────────────────────────────────────────────────────────────────┤
|
|
|
|
|
|
│ [名称输入框] [状态下拉] [搜索按钮] [重置按钮] │
|
|
|
|
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
|
|
|
|
|
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ 操作区域 │
|
|
|
|
|
|
├─────────────────────────────────────────────────────────────────┤
|
|
|
|
|
|
│ [新增按钮] │
|
|
|
|
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
|
|
|
|
|
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ 数据表格 │
|
|
|
|
|
|
├─────────┬─────────┬──────┬──────┬────────────┬─────────────────┤
|
|
|
|
|
|
│ 名称 │ 编码 │ 排序 │ 状态 │ 创建时间 │ 操作 │
|
|
|
|
|
|
├─────────┼─────────┼──────┼──────┼────────────┼─────────────────┤
|
|
|
|
|
|
│ 一年级 │ GRD001 │ 1 │ 正常 │ 2026-01-01 │ [编辑] [删除] │
|
|
|
|
|
|
│ 二年级 │ GRD002 │ 2 │ 正常 │ 2026-01-01 │ [编辑] [删除] │
|
|
|
|
|
|
└─────────┴─────────┴──────┴──────┴────────────┴─────────────────┘
|
|
|
|
|
|
|
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ 分页区域 │
|
|
|
|
|
|
│ 共 12 条 < 1 2 > 10条/页 │
|
|
|
|
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 4.1.2 区域管理(树形表格模式)
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ 操作区域 │
|
|
|
|
|
|
├─────────────────────────────────────────────────────────────────┤
|
|
|
|
|
|
│ [新增按钮] [展开全部] [折叠全部] │
|
|
|
|
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
|
|
|
|
|
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ 树形表格 │
|
|
|
|
|
|
├────────────────────┬─────────┬────────────┬────────────────────┤
|
|
|
|
|
|
│ 区域名称 │ 区域编码 │ 创建时间 │ 操作 │
|
|
|
|
|
|
├────────────────────┼─────────┼────────────┼────────────────────┤
|
|
|
|
|
|
│ ▼ 湖北 │ REG01 │ 2026-01-01 │ [新增下级][编辑][删除]│
|
|
|
|
|
|
│ ▼ 武汉 │ REG0101 │ 2026-01-01 │ [新增下级][编辑][删除]│
|
|
|
|
|
|
│ ├ 武昌区 │ REG010101│2026-01-01 │ [编辑][删除] │
|
|
|
|
|
|
│ ├ 汉口区 │ REG010102│2026-01-01 │ [编辑][删除] │
|
|
|
|
|
|
│ └ 汉阳区 │ REG010103│2026-01-01 │ [编辑][删除] │
|
|
|
|
|
|
│ └ 黄冈 │ REG0102 │ 2026-01-01 │ [新增下级][编辑][删除]│
|
|
|
|
|
|
│ ► 北京 │ REG02 │ 2026-01-01 │ [新增下级][编辑][删除]│
|
|
|
|
|
|
└────────────────────┴─────────┴────────────┴────────────────────┘
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 4.2 组件设计
|
|
|
|
|
|
|
|
|
|
|
|
#### 4.2.1 通用组件结构
|
|
|
|
|
|
|
|
|
|
|
|
```vue
|
|
|
|
|
|
<!-- views/base/grade/index.vue 示例结构 -->
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<div class="app-container">
|
|
|
|
|
|
<!-- 搜索区域 -->
|
|
|
|
|
|
<el-card shadow="never" class="search-wrapper">
|
|
|
|
|
|
<el-form :model="queryParams" :inline="true">
|
|
|
|
|
|
<!-- 搜索表单项 -->
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 表格区域 -->
|
|
|
|
|
|
<el-card shadow="never" style="margin-top: 12px">
|
|
|
|
|
|
<!-- 操作按钮 -->
|
|
|
|
|
|
<el-row :gutter="10" style="margin-bottom: 12px">
|
|
|
|
|
|
<el-col :span="1.5">
|
|
|
|
|
|
<el-button type="primary" :icon="Plus" @click="handleAdd">新增</el-button>
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
</el-row>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 数据表格 -->
|
|
|
|
|
|
<el-table v-loading="loading" :data="tableData" border stripe>
|
|
|
|
|
|
<!-- 表格列定义 -->
|
|
|
|
|
|
</el-table>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 分页组件 -->
|
|
|
|
|
|
<el-pagination />
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 新增/编辑弹窗 -->
|
|
|
|
|
|
<el-dialog v-model="dialogVisible" :title="dialogTitle">
|
|
|
|
|
|
<el-form ref="formRef" :model="form" :rules="rules">
|
|
|
|
|
|
<!-- 表单项 -->
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 4.2.2 年级管理组件
|
|
|
|
|
|
|
|
|
|
|
|
**核心功能实现:**
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 年级管理核心逻辑
|
|
|
|
|
|
const queryParams = ref({
|
|
|
|
|
|
pageNum: 1,
|
|
|
|
|
|
pageSize: 10,
|
|
|
|
|
|
gradeName: '',
|
|
|
|
|
|
status: ''
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const form = ref({
|
|
|
|
|
|
gradeId: null,
|
|
|
|
|
|
gradeName: '',
|
|
|
|
|
|
gradeCode: '',
|
|
|
|
|
|
orderNum: 0,
|
|
|
|
|
|
status: '0'
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const rules = {
|
|
|
|
|
|
gradeName: [
|
|
|
|
|
|
{ required: true, message: '请输入年级名称', trigger: 'blur' },
|
|
|
|
|
|
{ max: 50, message: '年级名称长度不能超过50个字符', trigger: 'blur' }
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 新增时自动生成编码
|
|
|
|
|
|
const handleAdd = () => {
|
|
|
|
|
|
form.value = {
|
|
|
|
|
|
gradeId: null,
|
|
|
|
|
|
gradeName: '',
|
|
|
|
|
|
gradeCode: '', // 由后端生成
|
|
|
|
|
|
orderNum: tableData.value.length + 1,
|
|
|
|
|
|
status: '0'
|
|
|
|
|
|
}
|
|
|
|
|
|
dialogVisible.value = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 删除前检查引用
|
|
|
|
|
|
const handleDelete = async (row) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await ElMessageBox.confirm(
|
|
|
|
|
|
`确定要删除年级"${row.gradeName}"吗?删除后不可恢复!`,
|
|
|
|
|
|
'删除确认',
|
|
|
|
|
|
{ type: 'warning' }
|
|
|
|
|
|
)
|
|
|
|
|
|
const res = await deleteGrade(row.gradeId)
|
|
|
|
|
|
if (res.code === 200) {
|
|
|
|
|
|
ElMessage.success('删除成功')
|
|
|
|
|
|
getList()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 被引用时后端返回错误信息
|
|
|
|
|
|
ElMessage.error(res.msg || '删除失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
// 用户取消
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 4.2.3 区域管理组件
|
|
|
|
|
|
|
|
|
|
|
|
**树形表格核心逻辑:**
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 区域管理核心逻辑
|
|
|
|
|
|
import { ref, onMounted } from 'vue'
|
|
|
|
|
|
import { getRegionTree, addRegion, updateRegion, deleteRegion } from '@/api/region'
|
|
|
|
|
|
|
|
|
|
|
|
const treeData = ref([])
|
|
|
|
|
|
const expandedRowKeys = ref([])
|
|
|
|
|
|
|
|
|
|
|
|
// 获取区域树
|
|
|
|
|
|
const getTree = async () => {
|
|
|
|
|
|
const res = await getRegionTree()
|
|
|
|
|
|
if (res.code === 200) {
|
|
|
|
|
|
treeData.value = res.data
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 新增下级
|
|
|
|
|
|
const handleAddChild = (row) => {
|
|
|
|
|
|
dialogTitle.value = '新增下级区域'
|
|
|
|
|
|
form.value = {
|
|
|
|
|
|
regionId: null,
|
|
|
|
|
|
parentId: row.regionId,
|
|
|
|
|
|
regionName: '',
|
|
|
|
|
|
level: row.level + 1,
|
|
|
|
|
|
parentName: row.regionName // 显示用
|
|
|
|
|
|
}
|
|
|
|
|
|
dialogVisible.value = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 展开/折叠全部
|
|
|
|
|
|
const toggleExpandAll = (expand) => {
|
|
|
|
|
|
if (expand) {
|
|
|
|
|
|
// 收集所有非叶子节点的ID
|
|
|
|
|
|
const getAllKeys = (nodes) => {
|
|
|
|
|
|
let keys = []
|
|
|
|
|
|
nodes.forEach(node => {
|
|
|
|
|
|
if (node.children && node.children.length > 0) {
|
|
|
|
|
|
keys.push(node.regionId)
|
|
|
|
|
|
keys = keys.concat(getAllKeys(node.children))
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
return keys
|
|
|
|
|
|
}
|
|
|
|
|
|
expandedRowKeys.value = getAllKeys(treeData.value)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
expandedRowKeys.value = []
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 删除区域(检查子级)
|
|
|
|
|
|
const handleDelete = async (row) => {
|
|
|
|
|
|
// 检查是否有子级
|
|
|
|
|
|
if (row.children && row.children.length > 0) {
|
|
|
|
|
|
ElMessage.warning('该区域下有子区域,不能删除')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
// 执行删除(后端还会检查是否被学校引用)
|
|
|
|
|
|
// ...
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 4.3 API接口封装
|
|
|
|
|
|
|
|
|
|
|
|
#### 4.3.1 年级管理API
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// api/grade.js
|
|
|
|
|
|
import request from '@/utils/request'
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取年级分页列表
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function getGradeList(params) {
|
|
|
|
|
|
return request({
|
|
|
|
|
|
url: '/api/grade/list',
|
|
|
|
|
|
method: 'get',
|
|
|
|
|
|
params
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取年级选项列表(下拉用)
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function getGradeOptions() {
|
|
|
|
|
|
return request({
|
|
|
|
|
|
url: '/api/grade/options',
|
|
|
|
|
|
method: 'get'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取年级详情
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function getGradeDetail(gradeId) {
|
|
|
|
|
|
return request({
|
|
|
|
|
|
url: `/api/grade/${gradeId}`,
|
|
|
|
|
|
method: 'get'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 新增年级
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function addGrade(data) {
|
|
|
|
|
|
return request({
|
|
|
|
|
|
url: '/api/grade',
|
|
|
|
|
|
method: 'post',
|
|
|
|
|
|
data
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 修改年级
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function updateGrade(data) {
|
|
|
|
|
|
return request({
|
|
|
|
|
|
url: '/api/grade',
|
|
|
|
|
|
method: 'put',
|
|
|
|
|
|
data
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 删除年级
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function deleteGrade(gradeId) {
|
|
|
|
|
|
return request({
|
|
|
|
|
|
url: `/api/grade/${gradeId}`,
|
|
|
|
|
|
method: 'delete'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 4.3.2 区域管理API
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// api/region.js
|
|
|
|
|
|
import request from '@/utils/request'
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取区域树
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function getRegionTree() {
|
|
|
|
|
|
return request({
|
|
|
|
|
|
url: '/api/region/tree',
|
|
|
|
|
|
method: 'get'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取区域详情
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function getRegionDetail(regionId) {
|
|
|
|
|
|
return request({
|
|
|
|
|
|
url: `/api/region/${regionId}`,
|
|
|
|
|
|
method: 'get'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 新增区域
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function addRegion(data) {
|
|
|
|
|
|
return request({
|
|
|
|
|
|
url: '/api/region',
|
|
|
|
|
|
method: 'post',
|
|
|
|
|
|
data
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 修改区域
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function updateRegion(data) {
|
|
|
|
|
|
return request({
|
|
|
|
|
|
url: '/api/region',
|
|
|
|
|
|
method: 'put',
|
|
|
|
|
|
data
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 删除区域
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function deleteRegion(regionId) {
|
|
|
|
|
|
return request({
|
|
|
|
|
|
url: `/api/region/${regionId}`,
|
|
|
|
|
|
method: 'delete'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 4.4 Mock数据
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// mock/grade.js 示例
|
|
|
|
|
|
import Mock from 'mockjs'
|
|
|
|
|
|
|
|
|
|
|
|
const gradeData = [
|
|
|
|
|
|
{ gradeId: 1, gradeName: '一年级', gradeCode: 'GRD001', orderNum: 1, status: '0', createTime: '2026-01-01 10:00:00' },
|
|
|
|
|
|
{ gradeId: 2, gradeName: '二年级', gradeCode: 'GRD002', orderNum: 2, status: '0', createTime: '2026-01-01 10:00:00' },
|
|
|
|
|
|
// ...更多数据
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
// 获取年级列表
|
|
|
|
|
|
Mock.mock(/\/api\/grade\/list/, 'get', (options) => {
|
|
|
|
|
|
const url = new URL('http://localhost' + options.url)
|
|
|
|
|
|
const gradeName = url.searchParams.get('gradeName') || ''
|
|
|
|
|
|
const status = url.searchParams.get('status')
|
|
|
|
|
|
const pageNum = parseInt(url.searchParams.get('pageNum')) || 1
|
|
|
|
|
|
const pageSize = parseInt(url.searchParams.get('pageSize')) || 10
|
|
|
|
|
|
|
|
|
|
|
|
let list = gradeData.filter(item => {
|
|
|
|
|
|
let match = true
|
|
|
|
|
|
if (gradeName) {
|
|
|
|
|
|
match = match && item.gradeName.includes(gradeName)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (status !== null && status !== '') {
|
|
|
|
|
|
match = match && item.status === status
|
|
|
|
|
|
}
|
|
|
|
|
|
return match
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const total = list.length
|
|
|
|
|
|
const start = (pageNum - 1) * pageSize
|
|
|
|
|
|
const rows = list.slice(start, start + pageSize)
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
code: 200,
|
|
|
|
|
|
msg: '查询成功',
|
|
|
|
|
|
total,
|
|
|
|
|
|
rows
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 4.5 路由配置
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// router/index.js
|
|
|
|
|
|
{
|
|
|
|
|
|
path: '/base',
|
|
|
|
|
|
component: Layout,
|
|
|
|
|
|
redirect: '/base/grade',
|
|
|
|
|
|
name: 'Base',
|
|
|
|
|
|
meta: { title: '基础数据', icon: 'Setting' },
|
|
|
|
|
|
children: [
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'grade',
|
|
|
|
|
|
name: 'Grade',
|
|
|
|
|
|
component: () => import('@/views/base/grade/index.vue'),
|
|
|
|
|
|
meta: { title: '年级管理' }
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'class',
|
|
|
|
|
|
name: 'Class',
|
|
|
|
|
|
component: () => import('@/views/base/class/index.vue'),
|
|
|
|
|
|
meta: { title: '班级管理' }
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'subject',
|
|
|
|
|
|
name: 'Subject',
|
|
|
|
|
|
component: () => import('@/views/base/subject/index.vue'),
|
|
|
|
|
|
meta: { title: '学科管理' }
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
path: 'region',
|
|
|
|
|
|
name: 'Region',
|
|
|
|
|
|
component: () => import('@/views/base/region/index.vue'),
|
|
|
|
|
|
meta: { title: '区域管理' }
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 5. 后端技术方案
|
|
|
|
|
|
|
|
|
|
|
|
### 5.1 Controller层设计
|
|
|
|
|
|
|
|
|
|
|
|
#### 5.1.1 年级管理Controller
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 年级管理Controller
|
2026-01-31 23:14:11 +08:00
|
|
|
|
* @author pangu
|
2026-01-31 17:55:58 +08:00
|
|
|
|
*/
|
|
|
|
|
|
@RestController
|
|
|
|
|
|
@RequestMapping("/api/grade")
|
|
|
|
|
|
public class GradeController extends BaseController {
|
|
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
|
private IGradeService gradeService;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取年级分页列表
|
|
|
|
|
|
*/
|
|
|
|
|
|
@GetMapping("/list")
|
|
|
|
|
|
public TableDataInfo list(Grade grade) {
|
|
|
|
|
|
startPage();
|
|
|
|
|
|
List<Grade> list = gradeService.selectGradeList(grade);
|
|
|
|
|
|
return getDataTable(list);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取年级选项列表(下拉用)
|
|
|
|
|
|
*/
|
|
|
|
|
|
@GetMapping("/options")
|
|
|
|
|
|
public AjaxResult options() {
|
|
|
|
|
|
List<Grade> list = gradeService.selectGradeOptions();
|
|
|
|
|
|
return success(list);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取年级详情
|
|
|
|
|
|
*/
|
|
|
|
|
|
@GetMapping("/{gradeId}")
|
|
|
|
|
|
public AjaxResult getInfo(@PathVariable Long gradeId) {
|
|
|
|
|
|
return success(gradeService.selectGradeById(gradeId));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 新增年级
|
|
|
|
|
|
*/
|
|
|
|
|
|
@PostMapping
|
|
|
|
|
|
@Log(title = "年级管理", businessType = BusinessType.INSERT)
|
|
|
|
|
|
public AjaxResult add(@Validated @RequestBody Grade grade) {
|
|
|
|
|
|
// 检查名称是否重复
|
|
|
|
|
|
if (!gradeService.checkGradeNameUnique(grade)) {
|
|
|
|
|
|
return error("年级名称已存在");
|
|
|
|
|
|
}
|
|
|
|
|
|
grade.setCreateBy(getUsername());
|
|
|
|
|
|
return toAjax(gradeService.insertGrade(grade));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 修改年级
|
|
|
|
|
|
*/
|
|
|
|
|
|
@PutMapping
|
|
|
|
|
|
@Log(title = "年级管理", businessType = BusinessType.UPDATE)
|
|
|
|
|
|
public AjaxResult edit(@Validated @RequestBody Grade grade) {
|
|
|
|
|
|
if (!gradeService.checkGradeNameUnique(grade)) {
|
|
|
|
|
|
return error("年级名称已存在");
|
|
|
|
|
|
}
|
|
|
|
|
|
grade.setUpdateBy(getUsername());
|
|
|
|
|
|
return toAjax(gradeService.updateGrade(grade));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 删除年级
|
|
|
|
|
|
*/
|
|
|
|
|
|
@DeleteMapping("/{gradeId}")
|
|
|
|
|
|
@Log(title = "年级管理", businessType = BusinessType.DELETE)
|
|
|
|
|
|
public AjaxResult remove(@PathVariable Long gradeId) {
|
|
|
|
|
|
// 检查是否被学校引用
|
|
|
|
|
|
if (gradeService.checkGradeExistSchool(gradeId)) {
|
|
|
|
|
|
return error("该年级已被学校使用,不能删除");
|
|
|
|
|
|
}
|
|
|
|
|
|
return toAjax(gradeService.deleteGradeById(gradeId));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 5.1.2 区域管理Controller
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 区域管理Controller
|
2026-01-31 23:14:11 +08:00
|
|
|
|
* @author pangu
|
2026-01-31 17:55:58 +08:00
|
|
|
|
*/
|
|
|
|
|
|
@RestController
|
|
|
|
|
|
@RequestMapping("/api/region")
|
|
|
|
|
|
public class RegionController extends BaseController {
|
|
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
|
private IRegionService regionService;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取区域树
|
|
|
|
|
|
*/
|
|
|
|
|
|
@GetMapping("/tree")
|
|
|
|
|
|
public AjaxResult tree() {
|
|
|
|
|
|
List<Region> list = regionService.selectRegionTree();
|
|
|
|
|
|
return success(list);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取区域详情
|
|
|
|
|
|
*/
|
|
|
|
|
|
@GetMapping("/{regionId}")
|
|
|
|
|
|
public AjaxResult getInfo(@PathVariable Long regionId) {
|
|
|
|
|
|
return success(regionService.selectRegionById(regionId));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 新增区域
|
|
|
|
|
|
*/
|
|
|
|
|
|
@PostMapping
|
|
|
|
|
|
@Log(title = "区域管理", businessType = BusinessType.INSERT)
|
|
|
|
|
|
public AjaxResult add(@Validated @RequestBody Region region) {
|
|
|
|
|
|
region.setCreateBy(getUsername());
|
|
|
|
|
|
return toAjax(regionService.insertRegion(region));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 修改区域
|
|
|
|
|
|
*/
|
|
|
|
|
|
@PutMapping
|
|
|
|
|
|
@Log(title = "区域管理", businessType = BusinessType.UPDATE)
|
|
|
|
|
|
public AjaxResult edit(@Validated @RequestBody Region region) {
|
|
|
|
|
|
// 父级不能设为自己或自己的子级
|
|
|
|
|
|
if (region.getParentId().equals(region.getRegionId())) {
|
|
|
|
|
|
return error("父级区域不能选择自己");
|
|
|
|
|
|
}
|
|
|
|
|
|
region.setUpdateBy(getUsername());
|
|
|
|
|
|
return toAjax(regionService.updateRegion(region));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 删除区域
|
|
|
|
|
|
*/
|
|
|
|
|
|
@DeleteMapping("/{regionId}")
|
|
|
|
|
|
@Log(title = "区域管理", businessType = BusinessType.DELETE)
|
|
|
|
|
|
public AjaxResult remove(@PathVariable Long regionId) {
|
|
|
|
|
|
// 检查是否有子区域
|
|
|
|
|
|
if (regionService.hasChildRegion(regionId)) {
|
|
|
|
|
|
return error("存在下级区域,不能删除");
|
|
|
|
|
|
}
|
|
|
|
|
|
// 检查是否被学校/会员引用
|
|
|
|
|
|
if (regionService.checkRegionExistSchool(regionId)) {
|
|
|
|
|
|
return error("该区域已被学校使用,不能删除");
|
|
|
|
|
|
}
|
|
|
|
|
|
if (regionService.checkRegionExistMember(regionId)) {
|
|
|
|
|
|
return error("该区域已被会员使用,不能删除");
|
|
|
|
|
|
}
|
|
|
|
|
|
return toAjax(regionService.deleteRegionById(regionId));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 5.2 Service层设计
|
|
|
|
|
|
|
|
|
|
|
|
#### 5.2.1 年级管理Service
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 年级管理Service接口
|
2026-01-31 23:14:11 +08:00
|
|
|
|
* @author pangu
|
2026-01-31 17:55:58 +08:00
|
|
|
|
*/
|
|
|
|
|
|
public interface IGradeService {
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 查询年级分页列表
|
|
|
|
|
|
*/
|
|
|
|
|
|
List<Grade> selectGradeList(Grade grade);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 查询年级选项列表
|
|
|
|
|
|
*/
|
|
|
|
|
|
List<Grade> selectGradeOptions();
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 查询年级详情
|
|
|
|
|
|
*/
|
|
|
|
|
|
Grade selectGradeById(Long gradeId);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 新增年级
|
|
|
|
|
|
*/
|
|
|
|
|
|
int insertGrade(Grade grade);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 修改年级
|
|
|
|
|
|
*/
|
|
|
|
|
|
int updateGrade(Grade grade);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 删除年级
|
|
|
|
|
|
*/
|
|
|
|
|
|
int deleteGradeById(Long gradeId);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 校验年级名称是否唯一
|
|
|
|
|
|
*/
|
|
|
|
|
|
boolean checkGradeNameUnique(Grade grade);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 检查年级是否被学校使用
|
|
|
|
|
|
*/
|
|
|
|
|
|
boolean checkGradeExistSchool(Long gradeId);
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 年级管理Service实现
|
2026-01-31 23:14:11 +08:00
|
|
|
|
* @author pangu
|
2026-01-31 17:55:58 +08:00
|
|
|
|
*/
|
|
|
|
|
|
@Service
|
|
|
|
|
|
public class GradeServiceImpl implements IGradeService {
|
|
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
|
private GradeMapper gradeMapper;
|
|
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
|
private SchoolGradeMapper schoolGradeMapper;
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public List<Grade> selectGradeList(Grade grade) {
|
|
|
|
|
|
return gradeMapper.selectGradeList(grade);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public List<Grade> selectGradeOptions() {
|
|
|
|
|
|
Grade grade = new Grade();
|
|
|
|
|
|
grade.setStatus("0"); // 只查启用的
|
|
|
|
|
|
return gradeMapper.selectGradeList(grade);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public Grade selectGradeById(Long gradeId) {
|
|
|
|
|
|
return gradeMapper.selectGradeById(gradeId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public int insertGrade(Grade grade) {
|
|
|
|
|
|
// 生成年级编码
|
|
|
|
|
|
grade.setGradeCode(generateGradeCode());
|
|
|
|
|
|
grade.setCreateTime(DateUtils.getNowDate());
|
|
|
|
|
|
return gradeMapper.insertGrade(grade);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成年级编码:GRD + 3位序号
|
|
|
|
|
|
*/
|
|
|
|
|
|
private String generateGradeCode() {
|
|
|
|
|
|
String maxCode = gradeMapper.selectMaxGradeCode();
|
|
|
|
|
|
if (StringUtils.isEmpty(maxCode)) {
|
|
|
|
|
|
return "GRD001";
|
|
|
|
|
|
}
|
|
|
|
|
|
int num = Integer.parseInt(maxCode.substring(3)) + 1;
|
|
|
|
|
|
return String.format("GRD%03d", num);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public int updateGrade(Grade grade) {
|
|
|
|
|
|
grade.setUpdateTime(DateUtils.getNowDate());
|
|
|
|
|
|
return gradeMapper.updateGrade(grade);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public int deleteGradeById(Long gradeId) {
|
|
|
|
|
|
return gradeMapper.deleteGradeById(gradeId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public boolean checkGradeNameUnique(Grade grade) {
|
|
|
|
|
|
Long gradeId = grade.getGradeId() == null ? -1L : grade.getGradeId();
|
|
|
|
|
|
Grade info = gradeMapper.checkGradeNameUnique(grade.getGradeName());
|
|
|
|
|
|
return info == null || info.getGradeId().equals(gradeId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public boolean checkGradeExistSchool(Long gradeId) {
|
|
|
|
|
|
int count = schoolGradeMapper.countByGradeId(gradeId);
|
|
|
|
|
|
return count > 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 5.2.2 区域管理Service
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 区域管理Service接口
|
2026-01-31 23:14:11 +08:00
|
|
|
|
* @author pangu
|
2026-01-31 17:55:58 +08:00
|
|
|
|
*/
|
|
|
|
|
|
public interface IRegionService {
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 查询区域树
|
|
|
|
|
|
*/
|
|
|
|
|
|
List<Region> selectRegionTree();
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 查询区域详情
|
|
|
|
|
|
*/
|
|
|
|
|
|
Region selectRegionById(Long regionId);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 新增区域
|
|
|
|
|
|
*/
|
|
|
|
|
|
int insertRegion(Region region);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 修改区域
|
|
|
|
|
|
*/
|
|
|
|
|
|
int updateRegion(Region region);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 删除区域
|
|
|
|
|
|
*/
|
|
|
|
|
|
int deleteRegionById(Long regionId);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 是否存在子区域
|
|
|
|
|
|
*/
|
|
|
|
|
|
boolean hasChildRegion(Long regionId);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 检查区域是否被学校使用
|
|
|
|
|
|
*/
|
|
|
|
|
|
boolean checkRegionExistSchool(Long regionId);
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 区域管理Service实现
|
2026-01-31 23:14:11 +08:00
|
|
|
|
* @author pangu
|
2026-01-31 17:55:58 +08:00
|
|
|
|
*/
|
|
|
|
|
|
@Service
|
|
|
|
|
|
public class RegionServiceImpl implements IRegionService {
|
|
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
|
private RegionMapper regionMapper;
|
|
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
|
private SchoolMapper schoolMapper;
|
|
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
|
private RedisCache redisCache;
|
|
|
|
|
|
|
|
|
|
|
|
private static final String REGION_TREE_KEY = "region_tree";
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public List<Region> selectRegionTree() {
|
|
|
|
|
|
// 优先从缓存获取
|
|
|
|
|
|
List<Region> cacheList = redisCache.getCacheObject(REGION_TREE_KEY);
|
|
|
|
|
|
if (cacheList != null) {
|
|
|
|
|
|
return cacheList;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查询所有区域
|
|
|
|
|
|
List<Region> regionList = regionMapper.selectRegionList(new Region());
|
|
|
|
|
|
// 构建树形结构
|
|
|
|
|
|
List<Region> tree = buildRegionTree(regionList);
|
|
|
|
|
|
|
|
|
|
|
|
// 放入缓存(24小时)
|
|
|
|
|
|
redisCache.setCacheObject(REGION_TREE_KEY, tree, 24, TimeUnit.HOURS);
|
|
|
|
|
|
|
|
|
|
|
|
return tree;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 构建区域树
|
|
|
|
|
|
*/
|
|
|
|
|
|
private List<Region> buildRegionTree(List<Region> regionList) {
|
|
|
|
|
|
List<Region> rootList = new ArrayList<>();
|
|
|
|
|
|
Map<Long, Region> regionMap = regionList.stream()
|
|
|
|
|
|
.collect(Collectors.toMap(Region::getRegionId, r -> r));
|
|
|
|
|
|
|
|
|
|
|
|
for (Region region : regionList) {
|
|
|
|
|
|
if (region.getParentId() == 0) {
|
|
|
|
|
|
rootList.add(region);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Region parent = regionMap.get(region.getParentId());
|
|
|
|
|
|
if (parent != null) {
|
|
|
|
|
|
if (parent.getChildren() == null) {
|
|
|
|
|
|
parent.setChildren(new ArrayList<>());
|
|
|
|
|
|
}
|
|
|
|
|
|
parent.getChildren().add(region);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return rootList;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public int insertRegion(Region region) {
|
|
|
|
|
|
// 设置层级和祖级列表
|
|
|
|
|
|
if (region.getParentId() == 0) {
|
|
|
|
|
|
region.setLevel(1);
|
|
|
|
|
|
region.setAncestors("0");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Region parent = regionMapper.selectRegionById(region.getParentId());
|
|
|
|
|
|
region.setLevel(parent.getLevel() + 1);
|
|
|
|
|
|
region.setAncestors(parent.getAncestors() + "," + parent.getRegionId());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成区域编码
|
|
|
|
|
|
region.setRegionCode(generateRegionCode(region));
|
|
|
|
|
|
region.setCreateTime(DateUtils.getNowDate());
|
|
|
|
|
|
|
|
|
|
|
|
int rows = regionMapper.insertRegion(region);
|
|
|
|
|
|
|
|
|
|
|
|
// 清除缓存
|
|
|
|
|
|
if (rows > 0) {
|
|
|
|
|
|
redisCache.deleteObject(REGION_TREE_KEY);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return rows;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成区域编码
|
|
|
|
|
|
*/
|
|
|
|
|
|
private String generateRegionCode(Region region) {
|
|
|
|
|
|
if (region.getParentId() == 0) {
|
|
|
|
|
|
// 省级:REG + 2位序号
|
|
|
|
|
|
String maxCode = regionMapper.selectMaxCodeByLevel(1);
|
|
|
|
|
|
if (StringUtils.isEmpty(maxCode)) {
|
|
|
|
|
|
return "REG01";
|
|
|
|
|
|
}
|
|
|
|
|
|
int num = Integer.parseInt(maxCode.substring(3)) + 1;
|
|
|
|
|
|
return String.format("REG%02d", num);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 市/区级:父编码 + 2位序号
|
|
|
|
|
|
Region parent = regionMapper.selectRegionById(region.getParentId());
|
|
|
|
|
|
String maxCode = regionMapper.selectMaxCodeByParent(region.getParentId());
|
|
|
|
|
|
if (StringUtils.isEmpty(maxCode)) {
|
|
|
|
|
|
return parent.getRegionCode() + "01";
|
|
|
|
|
|
}
|
|
|
|
|
|
String suffix = maxCode.substring(parent.getRegionCode().length());
|
|
|
|
|
|
int num = Integer.parseInt(suffix) + 1;
|
|
|
|
|
|
return parent.getRegionCode() + String.format("%02d", num);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public int updateRegion(Region region) {
|
|
|
|
|
|
region.setUpdateTime(DateUtils.getNowDate());
|
|
|
|
|
|
int rows = regionMapper.updateRegion(region);
|
|
|
|
|
|
|
|
|
|
|
|
// 清除缓存
|
|
|
|
|
|
if (rows > 0) {
|
|
|
|
|
|
redisCache.deleteObject(REGION_TREE_KEY);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return rows;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public int deleteRegionById(Long regionId) {
|
|
|
|
|
|
int rows = regionMapper.deleteRegionById(regionId);
|
|
|
|
|
|
|
|
|
|
|
|
// 清除缓存
|
|
|
|
|
|
if (rows > 0) {
|
|
|
|
|
|
redisCache.deleteObject(REGION_TREE_KEY);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return rows;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public boolean hasChildRegion(Long regionId) {
|
|
|
|
|
|
int count = regionMapper.countChildByParentId(regionId);
|
|
|
|
|
|
return count > 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public boolean checkRegionExistSchool(Long regionId) {
|
|
|
|
|
|
int count = schoolMapper.countByRegionId(regionId);
|
|
|
|
|
|
return count > 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 5.3 Mapper层设计
|
|
|
|
|
|
|
|
|
|
|
|
#### 5.3.1 年级Mapper
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 年级Mapper接口
|
2026-01-31 23:14:11 +08:00
|
|
|
|
* @author pangu
|
2026-01-31 17:55:58 +08:00
|
|
|
|
*/
|
|
|
|
|
|
public interface GradeMapper {
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 查询年级列表
|
|
|
|
|
|
*/
|
|
|
|
|
|
List<Grade> selectGradeList(Grade grade);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 查询年级详情
|
|
|
|
|
|
*/
|
|
|
|
|
|
Grade selectGradeById(Long gradeId);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 新增年级
|
|
|
|
|
|
*/
|
|
|
|
|
|
int insertGrade(Grade grade);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 修改年级
|
|
|
|
|
|
*/
|
|
|
|
|
|
int updateGrade(Grade grade);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 删除年级(软删除)
|
|
|
|
|
|
*/
|
|
|
|
|
|
int deleteGradeById(Long gradeId);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 校验年级名称唯一
|
|
|
|
|
|
*/
|
|
|
|
|
|
Grade checkGradeNameUnique(String gradeName);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 查询最大编码
|
|
|
|
|
|
*/
|
|
|
|
|
|
String selectMaxGradeCode();
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```xml
|
|
|
|
|
|
<!-- GradeMapper.xml -->
|
|
|
|
|
|
<?xml version="1.0" encoding="UTF-8" ?>
|
|
|
|
|
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|
|
|
|
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
|
|
|
|
|
<mapper namespace="com.pangu.base.mapper.GradeMapper">
|
|
|
|
|
|
|
|
|
|
|
|
<resultMap id="GradeResult" type="Grade">
|
|
|
|
|
|
<id property="gradeId" column="grade_id"/>
|
|
|
|
|
|
<result property="gradeCode" column="grade_code"/>
|
|
|
|
|
|
<result property="gradeName" column="grade_name"/>
|
|
|
|
|
|
<result property="orderNum" column="order_num"/>
|
|
|
|
|
|
<result property="status" column="status"/>
|
|
|
|
|
|
<result property="createBy" column="create_by"/>
|
|
|
|
|
|
<result property="createTime" column="create_time"/>
|
|
|
|
|
|
<result property="updateBy" column="update_by"/>
|
|
|
|
|
|
<result property="updateTime" column="update_time"/>
|
|
|
|
|
|
</resultMap>
|
|
|
|
|
|
|
|
|
|
|
|
<sql id="selectGradeVo">
|
|
|
|
|
|
select grade_id, grade_code, grade_name, order_num, status,
|
|
|
|
|
|
create_by, create_time, update_by, update_time
|
|
|
|
|
|
from pg_grade
|
|
|
|
|
|
where del_flag = '0'
|
|
|
|
|
|
</sql>
|
|
|
|
|
|
|
|
|
|
|
|
<select id="selectGradeList" parameterType="Grade" resultMap="GradeResult">
|
|
|
|
|
|
<include refid="selectGradeVo"/>
|
|
|
|
|
|
<if test="gradeName != null and gradeName != ''">
|
|
|
|
|
|
AND grade_name like concat('%', #{gradeName}, '%')
|
|
|
|
|
|
</if>
|
|
|
|
|
|
<if test="status != null and status != ''">
|
|
|
|
|
|
AND status = #{status}
|
|
|
|
|
|
</if>
|
|
|
|
|
|
order by order_num
|
|
|
|
|
|
</select>
|
|
|
|
|
|
|
|
|
|
|
|
<select id="selectGradeById" parameterType="Long" resultMap="GradeResult">
|
|
|
|
|
|
<include refid="selectGradeVo"/>
|
|
|
|
|
|
AND grade_id = #{gradeId}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
|
|
|
|
|
|
<insert id="insertGrade" parameterType="Grade" useGeneratedKeys="true" keyProperty="gradeId">
|
|
|
|
|
|
insert into pg_grade (
|
|
|
|
|
|
grade_code, grade_name, order_num, status,
|
|
|
|
|
|
create_by, create_time, del_flag
|
|
|
|
|
|
) values (
|
|
|
|
|
|
#{gradeCode}, #{gradeName}, #{orderNum}, #{status},
|
|
|
|
|
|
#{createBy}, #{createTime}, '0'
|
|
|
|
|
|
)
|
|
|
|
|
|
</insert>
|
|
|
|
|
|
|
|
|
|
|
|
<update id="updateGrade" parameterType="Grade">
|
|
|
|
|
|
update pg_grade
|
|
|
|
|
|
<set>
|
|
|
|
|
|
<if test="gradeName != null and gradeName != ''">grade_name = #{gradeName},</if>
|
|
|
|
|
|
<if test="orderNum != null">order_num = #{orderNum},</if>
|
|
|
|
|
|
<if test="status != null">status = #{status},</if>
|
|
|
|
|
|
update_by = #{updateBy},
|
|
|
|
|
|
update_time = #{updateTime}
|
|
|
|
|
|
</set>
|
|
|
|
|
|
where grade_id = #{gradeId}
|
|
|
|
|
|
</update>
|
|
|
|
|
|
|
|
|
|
|
|
<update id="deleteGradeById" parameterType="Long">
|
|
|
|
|
|
update pg_grade set del_flag = '1' where grade_id = #{gradeId}
|
|
|
|
|
|
</update>
|
|
|
|
|
|
|
|
|
|
|
|
<select id="checkGradeNameUnique" parameterType="String" resultMap="GradeResult">
|
|
|
|
|
|
<include refid="selectGradeVo"/>
|
|
|
|
|
|
AND grade_name = #{gradeName}
|
|
|
|
|
|
limit 1
|
|
|
|
|
|
</select>
|
|
|
|
|
|
|
|
|
|
|
|
<select id="selectMaxGradeCode" resultType="String">
|
|
|
|
|
|
select max(grade_code) from pg_grade where del_flag = '0'
|
|
|
|
|
|
</select>
|
|
|
|
|
|
|
|
|
|
|
|
</mapper>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 5.4 实体类设计
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 年级实体类
|
2026-01-31 23:14:11 +08:00
|
|
|
|
* @author pangu
|
2026-01-31 17:55:58 +08:00
|
|
|
|
*/
|
|
|
|
|
|
@Data
|
|
|
|
|
|
@TableName("pg_grade")
|
|
|
|
|
|
public class Grade extends BaseEntity {
|
|
|
|
|
|
|
|
|
|
|
|
/** 年级ID */
|
|
|
|
|
|
@TableId(type = IdType.AUTO)
|
|
|
|
|
|
private Long gradeId;
|
|
|
|
|
|
|
|
|
|
|
|
/** 年级编码 */
|
|
|
|
|
|
@NotBlank(message = "年级编码不能为空")
|
|
|
|
|
|
private String gradeCode;
|
|
|
|
|
|
|
|
|
|
|
|
/** 年级名称 */
|
|
|
|
|
|
@NotBlank(message = "年级名称不能为空")
|
|
|
|
|
|
@Size(max = 50, message = "年级名称长度不能超过50个字符")
|
|
|
|
|
|
private String gradeName;
|
|
|
|
|
|
|
|
|
|
|
|
/** 显示顺序 */
|
|
|
|
|
|
private Integer orderNum;
|
|
|
|
|
|
|
|
|
|
|
|
/** 状态(0正常 1停用)*/
|
|
|
|
|
|
private String status;
|
|
|
|
|
|
|
|
|
|
|
|
/** 删除标志(0存在 1删除)*/
|
|
|
|
|
|
@TableLogic
|
|
|
|
|
|
private String delFlag;
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 区域实体类
|
2026-01-31 23:14:11 +08:00
|
|
|
|
* @author pangu
|
2026-01-31 17:55:58 +08:00
|
|
|
|
*/
|
|
|
|
|
|
@Data
|
|
|
|
|
|
@TableName("pg_region")
|
|
|
|
|
|
public class Region extends BaseEntity {
|
|
|
|
|
|
|
|
|
|
|
|
/** 区域ID */
|
|
|
|
|
|
@TableId(type = IdType.AUTO)
|
|
|
|
|
|
private Long regionId;
|
|
|
|
|
|
|
|
|
|
|
|
/** 父区域ID */
|
|
|
|
|
|
private Long parentId;
|
|
|
|
|
|
|
|
|
|
|
|
/** 区域名称 */
|
|
|
|
|
|
@NotBlank(message = "区域名称不能为空")
|
|
|
|
|
|
@Size(max = 100, message = "区域名称长度不能超过100个字符")
|
|
|
|
|
|
private String regionName;
|
|
|
|
|
|
|
|
|
|
|
|
/** 区域编码 */
|
|
|
|
|
|
private String regionCode;
|
|
|
|
|
|
|
|
|
|
|
|
/** 层级(1省 2市 3区)*/
|
|
|
|
|
|
private Integer level;
|
|
|
|
|
|
|
|
|
|
|
|
/** 祖级列表 */
|
|
|
|
|
|
private String ancestors;
|
|
|
|
|
|
|
|
|
|
|
|
/** 显示顺序 */
|
|
|
|
|
|
private Integer orderNum;
|
|
|
|
|
|
|
|
|
|
|
|
/** 状态(0正常 1停用)*/
|
|
|
|
|
|
private String status;
|
|
|
|
|
|
|
|
|
|
|
|
/** 删除标志(0存在 1删除)*/
|
|
|
|
|
|
@TableLogic
|
|
|
|
|
|
private String delFlag;
|
|
|
|
|
|
|
|
|
|
|
|
/** 子区域 */
|
|
|
|
|
|
@TableField(exist = false)
|
|
|
|
|
|
private List<Region> children;
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 6. 数据库设计
|
|
|
|
|
|
|
|
|
|
|
|
### 6.1 表结构
|
|
|
|
|
|
|
|
|
|
|
|
参考《数据库设计文档_v1.0.md》中的:
|
|
|
|
|
|
- `pg_grade` - 年级字典表
|
|
|
|
|
|
- `pg_class` - 班级字典表
|
|
|
|
|
|
- `pg_subject` - 学科表
|
|
|
|
|
|
- `pg_region` - 区域表
|
|
|
|
|
|
|
|
|
|
|
|
### 6.2 初始化数据
|
|
|
|
|
|
|
|
|
|
|
|
#### 6.2.1 年级数据
|
|
|
|
|
|
|
|
|
|
|
|
```sql
|
|
|
|
|
|
INSERT INTO pg_grade (grade_id, grade_code, grade_name, order_num, status, create_time, del_flag) VALUES
|
|
|
|
|
|
(1, 'GRD001', '一年级', 1, '0', NOW(), '0'),
|
|
|
|
|
|
(2, 'GRD002', '二年级', 2, '0', NOW(), '0'),
|
|
|
|
|
|
(3, 'GRD003', '三年级', 3, '0', NOW(), '0'),
|
|
|
|
|
|
(4, 'GRD004', '四年级', 4, '0', NOW(), '0'),
|
|
|
|
|
|
(5, 'GRD005', '五年级', 5, '0', NOW(), '0'),
|
|
|
|
|
|
(6, 'GRD006', '六年级', 6, '0', NOW(), '0'),
|
|
|
|
|
|
(7, 'GRD007', '七年级', 7, '0', NOW(), '0'),
|
|
|
|
|
|
(8, 'GRD008', '八年级', 8, '0', NOW(), '0'),
|
|
|
|
|
|
(9, 'GRD009', '九年级', 9, '0', NOW(), '0'),
|
|
|
|
|
|
(10, 'GRD010', '高一', 10, '0', NOW(), '0'),
|
|
|
|
|
|
(11, 'GRD011', '高二', 11, '0', NOW(), '0'),
|
|
|
|
|
|
(12, 'GRD012', '高三', 12, '0', NOW(), '0');
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 6.2.2 班级数据
|
|
|
|
|
|
|
|
|
|
|
|
```sql
|
|
|
|
|
|
INSERT INTO pg_class (class_id, class_code, class_name, order_num, status, create_time, del_flag) VALUES
|
|
|
|
|
|
(1, 'CLS001', '1班', 1, '0', NOW(), '0'),
|
|
|
|
|
|
(2, 'CLS002', '2班', 2, '0', NOW(), '0'),
|
|
|
|
|
|
(3, 'CLS003', '3班', 3, '0', NOW(), '0'),
|
|
|
|
|
|
(4, 'CLS004', '4班', 4, '0', NOW(), '0'),
|
|
|
|
|
|
(5, 'CLS005', '5班', 5, '0', NOW(), '0'),
|
|
|
|
|
|
(6, 'CLS006', '6班', 6, '0', NOW(), '0'),
|
|
|
|
|
|
(7, 'CLS007', '7班', 7, '0', NOW(), '0'),
|
|
|
|
|
|
(8, 'CLS008', '8班', 8, '0', NOW(), '0'),
|
|
|
|
|
|
(9, 'CLS009', '9班', 9, '0', NOW(), '0'),
|
|
|
|
|
|
(10, 'CLS010', '10班', 10, '0', NOW(), '0');
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 6.2.3 学科数据
|
|
|
|
|
|
|
|
|
|
|
|
```sql
|
|
|
|
|
|
INSERT INTO pg_subject (subject_id, subject_code, subject_name, order_num, status, create_time, del_flag) VALUES
|
|
|
|
|
|
(1, 'SUB001', '语文', 1, '0', NOW(), '0'),
|
|
|
|
|
|
(2, 'SUB002', '数学', 2, '0', NOW(), '0'),
|
|
|
|
|
|
(3, 'SUB003', '英语', 3, '0', NOW(), '0'),
|
|
|
|
|
|
(4, 'SUB004', '物理', 4, '0', NOW(), '0'),
|
|
|
|
|
|
(5, 'SUB005', '化学', 5, '0', NOW(), '0'),
|
|
|
|
|
|
(6, 'SUB006', '生物', 6, '0', NOW(), '0'),
|
|
|
|
|
|
(7, 'SUB007', '历史', 7, '0', NOW(), '0'),
|
|
|
|
|
|
(8, 'SUB008', '地理', 8, '0', NOW(), '0'),
|
|
|
|
|
|
(9, 'SUB009', '政治', 9, '0', NOW(), '0'),
|
|
|
|
|
|
(10, 'SUB010', '体育', 10, '0', NOW(), '0'),
|
|
|
|
|
|
(11, 'SUB011', '音乐', 11, '0', NOW(), '0'),
|
|
|
|
|
|
(12, 'SUB012', '美术', 12, '0', NOW(), '0');
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 6.2.4 区域数据
|
|
|
|
|
|
|
|
|
|
|
|
```sql
|
|
|
|
|
|
-- 省级
|
|
|
|
|
|
INSERT INTO pg_region (region_id, parent_id, region_name, region_code, level, ancestors, order_num, status, create_time, del_flag) VALUES
|
|
|
|
|
|
(1, 0, '湖北', 'REG01', 1, '0', 1, '0', NOW(), '0'),
|
|
|
|
|
|
(2, 0, '北京', 'REG02', 1, '0', 2, '0', NOW(), '0'),
|
|
|
|
|
|
(3, 0, '香港', 'REG03', 1, '0', 3, '0', NOW(), '0'),
|
|
|
|
|
|
(4, 0, '吉宁', 'REG04', 1, '0', 4, '0', NOW(), '0');
|
|
|
|
|
|
|
|
|
|
|
|
-- 市级(湖北)
|
|
|
|
|
|
INSERT INTO pg_region (region_id, parent_id, region_name, region_code, level, ancestors, order_num, status, create_time, del_flag) VALUES
|
|
|
|
|
|
(11, 1, '武汉', 'REG0101', 2, '0,1', 1, '0', NOW(), '0'),
|
|
|
|
|
|
(12, 1, '黄冈', 'REG0102', 2, '0,1', 2, '0', NOW(), '0');
|
|
|
|
|
|
|
|
|
|
|
|
-- 区级(武汉)
|
|
|
|
|
|
INSERT INTO pg_region (region_id, parent_id, region_name, region_code, level, ancestors, order_num, status, create_time, del_flag) VALUES
|
|
|
|
|
|
(111, 11, '武昌区', 'REG010101', 3, '0,1,11', 1, '0', NOW(), '0'),
|
|
|
|
|
|
(112, 11, '汉口区', 'REG010102', 3, '0,1,11', 2, '0', NOW(), '0'),
|
|
|
|
|
|
(113, 11, '汉阳区', 'REG010103', 3, '0,1,11', 3, '0', NOW(), '0'),
|
|
|
|
|
|
(114, 11, '江夏区', 'REG010104', 3, '0,1,11', 4, '0', NOW(), '0'),
|
|
|
|
|
|
(115, 11, '新洲区', 'REG010105', 3, '0,1,11', 5, '0', NOW(), '0'),
|
|
|
|
|
|
(116, 11, '黄陂区', 'REG010106', 3, '0,1,11', 6, '0', NOW(), '0');
|
|
|
|
|
|
|
|
|
|
|
|
-- 区级(黄冈)
|
|
|
|
|
|
INSERT INTO pg_region (region_id, parent_id, region_name, region_code, level, ancestors, order_num, status, create_time, del_flag) VALUES
|
|
|
|
|
|
(121, 12, '黄州区', 'REG010201', 3, '0,1,12', 1, '0', NOW(), '0'),
|
|
|
|
|
|
(122, 12, '红安县', 'REG010202', 3, '0,1,12', 2, '0', NOW(), '0'),
|
|
|
|
|
|
(123, 12, '麻城市', 'REG010203', 3, '0,1,12', 3, '0', NOW(), '0');
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 7. 接口设计
|
|
|
|
|
|
|
|
|
|
|
|
### 7.1 接口清单
|
|
|
|
|
|
|
|
|
|
|
|
#### 7.1.1 年级管理接口
|
|
|
|
|
|
|
|
|
|
|
|
| 接口地址 | 方法 | 说明 |
|
|
|
|
|
|
| -------------------- | ------ | -------- |
|
|
|
|
|
|
| GET /api/grade/list | GET | 获取年级分页列表 |
|
|
|
|
|
|
| GET /api/grade/options | GET | 获取年级选项列表 |
|
|
|
|
|
|
| GET /api/grade/{id} | GET | 获取年级详情 |
|
|
|
|
|
|
| POST /api/grade | POST | 新增年级 |
|
|
|
|
|
|
| PUT /api/grade | PUT | 修改年级 |
|
|
|
|
|
|
| DELETE /api/grade/{id} | DELETE | 删除年级 |
|
|
|
|
|
|
|
|
|
|
|
|
#### 7.1.2 班级管理接口
|
|
|
|
|
|
|
|
|
|
|
|
| 接口地址 | 方法 | 说明 |
|
|
|
|
|
|
| -------------------- | ------ | -------- |
|
|
|
|
|
|
| GET /api/class/list | GET | 获取班级分页列表 |
|
|
|
|
|
|
| GET /api/class/options | GET | 获取班级选项列表 |
|
|
|
|
|
|
| GET /api/class/{id} | GET | 获取班级详情 |
|
|
|
|
|
|
| POST /api/class | POST | 新增班级 |
|
|
|
|
|
|
| PUT /api/class | PUT | 修改班级 |
|
|
|
|
|
|
| DELETE /api/class/{id} | DELETE | 删除班级 |
|
|
|
|
|
|
|
|
|
|
|
|
#### 7.1.3 学科管理接口
|
|
|
|
|
|
|
|
|
|
|
|
| 接口地址 | 方法 | 说明 |
|
|
|
|
|
|
| ---------------------- | ------ | -------- |
|
|
|
|
|
|
| GET /api/subject/list | GET | 获取学科分页列表 |
|
|
|
|
|
|
| GET /api/subject/options | GET | 获取学科选项列表 |
|
|
|
|
|
|
| GET /api/subject/{id} | GET | 获取学科详情 |
|
|
|
|
|
|
| POST /api/subject | POST | 新增学科 |
|
|
|
|
|
|
| PUT /api/subject | PUT | 修改学科 |
|
|
|
|
|
|
| DELETE /api/subject/{id} | DELETE | 删除学科 |
|
|
|
|
|
|
|
|
|
|
|
|
#### 7.1.4 区域管理接口
|
|
|
|
|
|
|
|
|
|
|
|
| 接口地址 | 方法 | 说明 |
|
|
|
|
|
|
| --------------------- | ------ | ------ |
|
|
|
|
|
|
| GET /api/region/tree | GET | 获取区域树 |
|
|
|
|
|
|
| GET /api/region/{id} | GET | 获取区域详情 |
|
|
|
|
|
|
| POST /api/region | POST | 新增区域 |
|
|
|
|
|
|
| PUT /api/region | PUT | 修改区域 |
|
|
|
|
|
|
| DELETE /api/region/{id} | DELETE | 删除区域 |
|
|
|
|
|
|
|
|
|
|
|
|
### 7.2 接口详情
|
|
|
|
|
|
|
|
|
|
|
|
#### 7.2.1 获取年级分页列表
|
|
|
|
|
|
|
|
|
|
|
|
**请求**
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
GET /api/grade/list?gradeName=年级&status=0&pageNum=1&pageSize=10
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**响应**
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"code": 200,
|
|
|
|
|
|
"msg": "查询成功",
|
|
|
|
|
|
"total": 12,
|
|
|
|
|
|
"rows": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"gradeId": 1,
|
|
|
|
|
|
"gradeCode": "GRD001",
|
|
|
|
|
|
"gradeName": "一年级",
|
|
|
|
|
|
"orderNum": 1,
|
|
|
|
|
|
"status": "0",
|
|
|
|
|
|
"createTime": "2026-01-01 10:00:00"
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 7.2.2 新增年级
|
|
|
|
|
|
|
|
|
|
|
|
**请求**
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
POST /api/grade
|
|
|
|
|
|
Content-Type: application/json
|
|
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
"gradeName": "学前班",
|
|
|
|
|
|
"orderNum": 0,
|
|
|
|
|
|
"status": "0"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**响应**
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"code": 200,
|
|
|
|
|
|
"msg": "新增成功"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 7.2.3 删除年级(被引用时)
|
|
|
|
|
|
|
|
|
|
|
|
**请求**
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
DELETE /api/grade/1
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**响应**
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"code": 500,
|
|
|
|
|
|
"msg": "该年级已被学校使用,不能删除"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 7.2.4 获取区域树
|
|
|
|
|
|
|
|
|
|
|
|
**请求**
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
GET /api/region/tree
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**响应**
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"code": 200,
|
|
|
|
|
|
"msg": "查询成功",
|
|
|
|
|
|
"data": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"regionId": 1,
|
|
|
|
|
|
"parentId": 0,
|
|
|
|
|
|
"regionName": "湖北",
|
|
|
|
|
|
"regionCode": "REG01",
|
|
|
|
|
|
"level": 1,
|
|
|
|
|
|
"children": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"regionId": 11,
|
|
|
|
|
|
"parentId": 1,
|
|
|
|
|
|
"regionName": "武汉",
|
|
|
|
|
|
"regionCode": "REG0101",
|
|
|
|
|
|
"level": 2,
|
|
|
|
|
|
"children": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"regionId": 111,
|
|
|
|
|
|
"parentId": 11,
|
|
|
|
|
|
"regionName": "武昌区",
|
|
|
|
|
|
"regionCode": "REG010101",
|
|
|
|
|
|
"level": 3,
|
|
|
|
|
|
"children": null
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 8. 开发计划
|
|
|
|
|
|
|
|
|
|
|
|
### 8.1 阶段划分
|
|
|
|
|
|
|
|
|
|
|
|
| 阶段 | 任务 | 工时 | 交付物 |
|
|
|
|
|
|
| ---- | -------------------- | ----- | ------------------ |
|
|
|
|
|
|
| 阶段一 | 数据库表创建、初始化数据 | 0.5天 | SQL脚本 |
|
|
|
|
|
|
| 阶段二 | 后端接口开发(年级、班级、学科) | 1天 | Controller/Service |
|
|
|
|
|
|
| 阶段三 | 后端接口开发(区域管理) | 0.5天 | Controller/Service |
|
|
|
|
|
|
| 阶段四 | 前端页面开发(年级、班级、学科) | 1天 | Vue页面 |
|
|
|
|
|
|
| 阶段五 | 前端页面开发(区域管理-树形表格) | 0.5天 | Vue页面 |
|
|
|
|
|
|
| 阶段六 | 联调测试、Bug修复 | 0.5天 | 测试报告 |
|
|
|
|
|
|
| **合计** | | **4天** | |
|
|
|
|
|
|
|
|
|
|
|
|
### 8.2 里程碑
|
|
|
|
|
|
|
|
|
|
|
|
| 里程碑 | 时间节点 | 交付内容 |
|
|
|
|
|
|
| ---- | ---------- | ---------------- |
|
|
|
|
|
|
| M1 | 第1天结束 | 数据库就绪、后端接口完成 |
|
|
|
|
|
|
| M2 | 第3天结束 | 前端页面完成 |
|
|
|
|
|
|
| M3 | 第4天结束 | 联调测试完成,功能上线 |
|
|
|
|
|
|
|
|
|
|
|
|
### 8.3 任务分解
|
|
|
|
|
|
|
|
|
|
|
|
#### 阶段一:数据库(0.5天)
|
|
|
|
|
|
|
|
|
|
|
|
- [x] 创建 pg_grade 表
|
|
|
|
|
|
- [x] 创建 pg_class 表
|
|
|
|
|
|
- [x] 创建 pg_subject 表
|
|
|
|
|
|
- [x] 创建 pg_region 表
|
|
|
|
|
|
- [x] 初始化年级数据(12条)
|
|
|
|
|
|
- [x] 初始化班级数据(10条)
|
|
|
|
|
|
- [x] 初始化学科数据(12条)
|
|
|
|
|
|
- [x] 初始化区域数据(湖北省数据)
|
|
|
|
|
|
|
|
|
|
|
|
#### 阶段二:后端-年级/班级/学科(1天)
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] Grade 实体类
|
|
|
|
|
|
- [ ] GradeMapper + XML
|
|
|
|
|
|
- [ ] GradeService 接口及实现
|
|
|
|
|
|
- [ ] GradeController(含权限注解)
|
|
|
|
|
|
- [ ] Class 实体类
|
|
|
|
|
|
- [ ] ClassMapper + XML
|
|
|
|
|
|
- [ ] ClassService 接口及实现
|
|
|
|
|
|
- [ ] ClassController
|
|
|
|
|
|
- [ ] Subject 实体类
|
|
|
|
|
|
- [ ] SubjectMapper + XML
|
|
|
|
|
|
- [ ] SubjectService 接口及实现
|
|
|
|
|
|
- [ ] SubjectController
|
|
|
|
|
|
- [ ] 单元测试
|
|
|
|
|
|
|
|
|
|
|
|
#### 阶段三:后端-区域管理(0.5天)
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] Region 实体类
|
|
|
|
|
|
- [ ] RegionMapper + XML
|
|
|
|
|
|
- [ ] RegionService 接口及实现(含缓存)
|
|
|
|
|
|
- [ ] RegionController
|
|
|
|
|
|
- [ ] 单元测试
|
|
|
|
|
|
|
|
|
|
|
|
#### 阶段四:前端-年级/班级/学科(1天)
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] grade API 封装
|
|
|
|
|
|
- [ ] grade Mock 数据
|
|
|
|
|
|
- [ ] grade/index.vue 页面
|
|
|
|
|
|
- [ ] class API 封装
|
|
|
|
|
|
- [ ] class Mock 数据
|
|
|
|
|
|
- [ ] class/index.vue 页面
|
|
|
|
|
|
- [ ] subject API 封装
|
|
|
|
|
|
- [ ] subject Mock 数据
|
|
|
|
|
|
- [ ] subject/index.vue 页面
|
|
|
|
|
|
- [ ] 路由配置
|
|
|
|
|
|
|
|
|
|
|
|
#### 阶段五:前端-区域管理(0.5天)
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] region API 封装
|
|
|
|
|
|
- [ ] region Mock 数据
|
|
|
|
|
|
- [ ] region/index.vue 页面(树形表格)
|
|
|
|
|
|
- [ ] 展开/折叠全部功能
|
|
|
|
|
|
- [ ] 新增下级功能
|
|
|
|
|
|
|
|
|
|
|
|
#### 阶段六:联调测试(0.5天)
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] 前后端联调
|
|
|
|
|
|
- [ ] 功能测试
|
|
|
|
|
|
- [ ] Bug修复
|
|
|
|
|
|
- [ ] 代码Review
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 9. 测试方案
|
|
|
|
|
|
|
|
|
|
|
|
### 9.1 测试范围
|
|
|
|
|
|
|
|
|
|
|
|
| 测试类型 | 测试内容 |
|
|
|
|
|
|
| ----- | ------------------ |
|
|
|
|
|
|
| 单元测试 | Service层核心方法 |
|
|
|
|
|
|
| 接口测试 | 所有API接口 |
|
|
|
|
|
|
| 功能测试 | 前端页面操作 |
|
|
|
|
|
|
| 边界测试 | 删除被引用数据、名称重复等 |
|
|
|
|
|
|
| 性能测试 | 区域树查询响应时间 |
|
|
|
|
|
|
|
|
|
|
|
|
### 9.2 测试用例
|
|
|
|
|
|
|
|
|
|
|
|
#### 9.2.1 年级管理测试用例
|
|
|
|
|
|
|
|
|
|
|
|
| 用例编号 | 测试场景 | 前置条件 | 测试步骤 | 预期结果 |
|
|
|
|
|
|
| ---------- | ------------ | -------- | ----------------- | ----------------- |
|
|
|
|
|
|
| TC-GRD-001 | 新增年级-正常 | 登录后台 | 输入年级名称,点击确定 | 新增成功,列表显示新数据 |
|
|
|
|
|
|
| TC-GRD-002 | 新增年级-名称重复 | 已存在"一年级" | 新增"一年级" | 提示"年级名称已存在" |
|
|
|
|
|
|
| TC-GRD-003 | 编辑年级-正常 | 存在年级数据 | 修改名称,点击确定 | 修改成功 |
|
|
|
|
|
|
| TC-GRD-004 | 删除年级-未被引用 | 年级未被学校使用 | 点击删除,确认 | 删除成功 |
|
|
|
|
|
|
| TC-GRD-005 | 删除年级-被引用 | 年级已被学校使用 | 点击删除,确认 | 提示"该年级已被学校使用,不能删除" |
|
|
|
|
|
|
| TC-GRD-006 | 搜索年级-按名称 | 存在多条数据 | 输入关键字搜索 | 返回匹配数据 |
|
|
|
|
|
|
| TC-GRD-007 | 搜索年级-按状态 | 存在启用/禁用 | 选择状态筛选 | 返回对应状态数据 |
|
|
|
|
|
|
|
|
|
|
|
|
#### 9.2.2 区域管理测试用例
|
|
|
|
|
|
|
|
|
|
|
|
| 用例编号 | 测试场景 | 前置条件 | 测试步骤 | 预期结果 |
|
|
|
|
|
|
| ---------- | ---------- | -------- | ------------- | ----------------- |
|
|
|
|
|
|
| TC-REG-001 | 查看区域树 | 存在区域数据 | 进入区域管理页面 | 显示树形结构 |
|
|
|
|
|
|
| TC-REG-002 | 展开/折叠全部 | 存在多级区域 | 点击展开全部 | 全部节点展开 |
|
|
|
|
|
|
| TC-REG-003 | 新增省级区域 | 登录后台 | 点击新增,输入名称 | 新增成功 |
|
|
|
|
|
|
| TC-REG-004 | 新增下级区域 | 存在父区域 | 点击新增下级 | 弹窗自动带入父级 |
|
|
|
|
|
|
| TC-REG-005 | 删除区域-有子级 | 区域有子节点 | 点击删除 | 提示"存在下级区域,不能删除" |
|
|
|
|
|
|
| TC-REG-006 | 删除区域-被学校引用 | 区域被学校使用 | 点击删除 | 提示"该区域已被学校使用,不能删除" |
|
|
|
|
|
|
| TC-REG-007 | 删除区域-正常 | 区域无子级无引用 | 点击删除,确认 | 删除成功 |
|
|
|
|
|
|
|
|
|
|
|
|
### 9.3 性能测试
|
|
|
|
|
|
|
|
|
|
|
|
| 测试项 | 测试方法 | 指标要求 |
|
|
|
|
|
|
| --------- | ------------------- | ---------- |
|
|
|
|
|
|
| 区域树查询 | 模拟100条区域数据 | 响应时间 ≤ 200ms |
|
|
|
|
|
|
| 区域树缓存命中 | 连续请求区域树 | 第二次 ≤ 50ms |
|
|
|
|
|
|
| 年级列表分页查询 | 模拟1000条数据 | 响应时间 ≤ 200ms |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 10. 风险与应对
|
|
|
|
|
|
|
|
|
|
|
|
### 10.1 技术风险
|
|
|
|
|
|
|
|
|
|
|
|
| 风险点 | 影响程度 | 应对措施 |
|
|
|
|
|
|
| ------- | ---- | -------------------- |
|
|
|
|
|
|
| 区域树性能问题 | 中 | 使用Redis缓存,设置合理过期时间 |
|
|
|
|
|
|
| 数据引用关系复杂 | 中 | 删除前严格检查,提供友好的错误提示 |
|
|
|
|
|
|
| 编码生成冲突 | 低 | 使用数据库max函数 + 锁机制保证唯一 |
|
|
|
|
|
|
|
|
|
|
|
|
### 10.2 业务风险
|
|
|
|
|
|
|
|
|
|
|
|
| 风险点 | 影响程度 | 应对措施 |
|
|
|
|
|
|
| -------- | ---- | --------------- |
|
|
|
|
|
|
| 初始数据不完整 | 中 | 与业务方确认初始数据范围 |
|
|
|
|
|
|
| 权限控制遗漏 | 高 | 严格按照权限矩阵实现,测试验证 |
|
|
|
|
|
|
|
|
|
|
|
|
### 10.3 进度风险
|
|
|
|
|
|
|
|
|
|
|
|
| 风险点 | 影响程度 | 应对措施 |
|
|
|
|
|
|
| ------- | ---- | ------------- |
|
|
|
|
|
|
| 需求变更 | 中 | 预留缓冲时间 |
|
|
|
|
|
|
| 联调问题多 | 中 | 尽早进行前后端联调 |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 审核意见
|
|
|
|
|
|
|
|
|
|
|
|
| 评审角色 | 评审人 | 评审日期 | 评审意见 | 签字 |
|
|
|
|
|
|
| ---- | --- | ---- | ---- | -- |
|
|
|
|
|
|
| 技术负责人 | | | | |
|
|
|
|
|
|
| 前端负责人 | | | | |
|
|
|
|
|
|
| 后端负责人 | | | | |
|
|
|
|
|
|
| 测试负责人 | | | | |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
*文档结束*
|