21 KiB
21 KiB
学校管理模块 - 开发任务清单
| 文档信息 | 内容 |
|---|---|
| 文档版本 | V1.0 |
| 模块名称 | 学校管理模块 |
| 编写团队 | pangu |
| 创建日期 | 2026-01-31 |
任务概览
| 类别 | 任务数 | 预估工时 | 状态 |
|---|---|---|---|
| 后端开发 | 14 | 25h | 待开始 |
| 前端开发 | 10 | 23h | 待开始 |
| 测试 | 3 | 8h | 待开始 |
| 合计 | 27 | 56h | - |
一、后端开发任务
1.1 实体层开发
| 任务编号 | 任务名称 | 描述 | 优先级 | 工时 | 状态 |
|---|---|---|---|---|---|
| BE-SCH-01 | 创建实体类 | 创建 School、SchoolGrade、SchoolClass 实体类 | P0 | 1h | ☐ |
BE-SCH-01 详细说明
创建以下文件:
domain/entity/
├── School.java # 学校实体
├── SchoolGrade.java # 学校年级关联实体
└── SchoolClass.java # 学校班级关联实体
domain/dto/
├── SchoolQueryDTO.java # 学校查询DTO
├── SchoolCreateDTO.java # 学校新增DTO
├── BindGradesDTO.java # 年级挂载DTO
└── BindClassesDTO.java # 班级挂载DTO
domain/vo/
├── SchoolVO.java # 学校VO
└── SchoolTreeVO.java # 学校树VO
验收标准
- 所有实体类包含表中所有字段
- DTO包含必要的验证注解(@NotNull, @NotBlank等)
- VO符合接口响应规范
1.2 Mapper层开发
| 任务编号 | 任务名称 | 描述 | 优先级 | 工时 | 状态 |
|---|---|---|---|---|---|
| BE-SCH-02 | SchoolMapper开发 | 创建学校Mapper接口及XML | P0 | 2h | ☐ |
| BE-SCH-03 | SchoolGradeMapper开发 | 创建学校年级Mapper接口及XML | P0 | 1h | ☐ |
| BE-SCH-04 | SchoolClassMapper开发 | 创建学校班级Mapper接口及XML | P0 | 1h | ☐ |
BE-SCH-02 详细说明 - SchoolMapper
public interface SchoolMapper extends BaseMapper<School> {
/**
* 按区域查询学校列表
*/
List<School> selectSchoolsByRegionId(@Param("regionId") Long regionId);
/**
* 查询学校列表(带数据权限)
*/
List<SchoolVO> selectSchoolList(SchoolQueryDTO query);
/**
* 查询当前年份最大编码
*/
String selectMaxCode(@Param("prefix") String prefix);
/**
* 统计学校数量(按区域)
*/
int countByRegionId(@Param("regionId") Long regionId);
}
BE-SCH-03 详细说明 - SchoolGradeMapper
public interface SchoolGradeMapper extends BaseMapper<SchoolGrade> {
/**
* 按学校ID批量查询年级
*/
List<SchoolGrade> selectBySchoolIds(@Param("schoolIds") List<Long> schoolIds);
/**
* 检查是否已存在
*/
boolean exists(@Param("schoolId") Long schoolId, @Param("gradeId") Long gradeId);
/**
* 统计学校下的年级数量
*/
int countBySchoolId(@Param("schoolId") Long schoolId);
}
BE-SCH-04 详细说明 - SchoolClassMapper
public interface SchoolClassMapper extends BaseMapper<SchoolClass> {
/**
* 按学校年级ID批量查询班级
*/
List<SchoolClass> selectBySchoolGradeIds(@Param("schoolGradeIds") List<Long> schoolGradeIds);
/**
* 检查是否已存在
*/
boolean exists(@Param("schoolGradeId") Long schoolGradeId, @Param("classId") Long classId);
/**
* 统计年级下的班级数量
*/
int countBySchoolGradeId(@Param("schoolGradeId") Long schoolGradeId);
}
验收标准
- 所有SQL语句已测试通过
- 软删除条件已正确添加(del_flag = '0')
- 数据权限注解已添加
1.3 Service层开发
| 任务编号 | 任务名称 | 描述 | 优先级 | 工时 | 状态 |
|---|---|---|---|---|---|
| BE-SCH-05 | 创建Service接口 | 创建 ISchoolService 接口 | P0 | 1h | ☐ |
| BE-SCH-06 | 实现学校CRUD | 实现学校增删改查方法 | P0 | 3h | ☐ |
| BE-SCH-07 | 实现年级/班级挂载 | 实现 bindGrades、bindClasses 方法 | P0 | 2h | ☐ |
| BE-SCH-08 | 实现学校树查询 | 实现 selectSchoolTree 方法 | P0 | 2h | ☐ |
| BE-SCH-09 | 实现删除校验 | 实现删除前关联检查逻辑 | P0 | 2h | ☐ |
| BE-SCH-10 | 实现编码生成 | 实现学校编码自动生成逻辑 | P0 | 1h | ☐ |
BE-SCH-06 详细说明 - 学校CRUD
// 查询方法
List<SchoolVO> selectSchoolList(SchoolQueryDTO query);
SchoolVO selectSchoolById(Long schoolId);
// 新增方法
int insertSchool(SchoolCreateDTO dto);
// 要点:
// 1. 调用 generateSchoolCode() 生成编码
// 2. 查询并设置 regionPath
// 3. 设置 createBy、createTime
// 修改方法
int updateSchool(School school);
// 要点:
// 1. 如果修改了regionId,需要更新regionPath
// 2. 设置 updateBy、updateTime
// 删除方法
int deleteSchool(Long schoolId);
// 要点:
// 1. 调用 deleteSchool 校验逻辑
// 2. 执行软删除(设置 del_flag = '1')
BE-SCH-08 详细说明 - 学校树查询
/**
* 查询学校树形结构
*
* 实现步骤:
* 1. 根据 regionId 查询学校列表
* 2. 提取所有 schoolId,批量查询学校年级关联
* 3. 提取所有 schoolGradeId,批量查询学校班级关联
* 4. 内存中组装三级树形结构
*
* 性能优化:
* - 使用批量查询减少数据库交互
* - 使用 Map 进行分组,避免循环嵌套查询
*/
List<SchoolTreeVO> selectSchoolTree(Long regionId);
BE-SCH-09 详细说明 - 删除校验
/**
* 删除学校前校验
*
* 校验规则:
* 1. 检查是否有年级/班级子节点
* - 调用 schoolGradeMapper.countBySchoolId(schoolId)
* - 如果 > 0,抛出异常:"该学校下存在年级数据,请先删除年级"
*
* 2. 检查是否被学生信息引用
* - 调用 studentMapper.countBySchoolId(schoolId)
* - 如果 > 0,抛出异常:"该学校已被学生信息引用,无法删除"
*/
/**
* 删除年级前校验
*
* 校验规则:
* 1. 检查是否有班级子节点
* 2. 检查是否被学生信息引用(school_grade_id)
*/
/**
* 删除班级前校验
*
* 校验规则:
* 1. 检查是否被学生信息引用(school_class_id)
*/
BE-SCH-10 详细说明 - 编码生成
/**
* 生成学校编码
*
* 格式:SCH + 年份(4位) + 序号(4位)
* 示例:SCH20260001, SCH20260002
*
* 实现步骤:
* 1. 获取当前年份
* 2. 拼接前缀 "SCH" + year
* 3. 查询数据库中该前缀的最大编码
* 4. 提取序号部分 +1
* 5. 格式化为4位数字(不足补0)
*
* 并发处理:
* - 使用数据库唯一索引保证唯一性
* - 冲突时重试生成
*/
private String generateSchoolCode() {
String year = String.valueOf(LocalDate.now().getYear());
String prefix = "SCH" + year;
String maxCode = schoolMapper.selectMaxCode(prefix);
int seq = 1;
if (maxCode != null) {
seq = Integer.parseInt(maxCode.substring(7)) + 1;
}
return prefix + String.format("%04d", seq);
}
验收标准
- 所有Service方法已实现并通过单元测试
- 事务注解已正确添加
- 异常信息清晰易懂
1.4 Controller层开发
| 任务编号 | 任务名称 | 描述 | 优先级 | 工时 | 状态 |
|---|---|---|---|---|---|
| BE-SCH-11 | 创建Controller | 创建 SchoolController 控制器 | P0 | 2h | ☐ |
BE-SCH-11 详细说明
@RestController
@RequestMapping("/api/school")
public class SchoolController {
// 接口列表
GET /tree # 获取学校树
GET /list # 获取学校列表(分页)
GET /{schoolId} # 获取学校详情
POST / # 新增学校
PUT / # 修改学校
DELETE /{schoolId} # 删除学校
POST /bindGrades # 挂载年级
POST /bindClasses # 挂载班级
DELETE /grade/{schoolGradeId} # 删除学校年级
DELETE /class/{schoolClassId} # 删除学校班级
}
验收标准
- 所有接口遵循RESTful规范
- 参数校验注解已添加
- 操作日志注解已添加(@Log)
- 权限注解已添加(@PreAuthorize)
1.5 权限与测试
| 任务编号 | 任务名称 | 描述 | 优先级 | 工时 | 状态 |
|---|---|---|---|---|---|
| BE-SCH-12 | 数据权限控制 | 实现分公司用户数据隔离 | P1 | 2h | ☐ |
| BE-SCH-13 | 单元测试 | 编写Service层单元测试 | P1 | 3h | ☐ |
| BE-SCH-14 | 接口联调 | 与前端联调并修复Bug | P1 | 2h | ☐ |
BE-SCH-12 详细说明 - 数据权限
/**
* 数据权限实现
*
* 超级管理员:查看全部学校
* 分公司用户:只能查看所属区域的学校
*
* 实现方式:
* 1. 在 selectSchoolList 方法上添加 @DataScope 注解
* 2. SQL中关联区域表和部门表
* 3. MyBatis拦截器自动拼接权限条件
*/
// Mapper.xml 示例
<select id="selectSchoolList" resultType="SchoolVO">
SELECT s.*, r.region_name
FROM pg_school s
LEFT JOIN pg_region r ON s.region_id = r.region_id
LEFT JOIN sys_dept d ON s.region_id = d.dept_id
WHERE s.del_flag = '0'
<if test="schoolName != null and schoolName != ''">
AND s.school_name LIKE CONCAT('%', #{schoolName}, '%')
</if>
<if test="status != null and status != ''">
AND s.status = #{status}
</if>
<!-- 数据权限会自动拼接在这里 -->
${params.dataScope}
</select>
BE-SCH-13 详细说明 - 单元测试
// 测试用例清单
@Test void testInsertSchool() // 新增学校
@Test void testUpdateSchool() // 修改学校
@Test void testDeleteSchool_Success() // 删除学校成功
@Test void testDeleteSchool_HasGrades() // 删除学校失败-有年级
@Test void testDeleteSchool_HasStudents() // 删除学校失败-有学生
@Test void testBindGrades() // 挂载年级
@Test void testBindGrades_Duplicate() // 重复挂载年级
@Test void testBindClasses() // 挂载班级
@Test void testDeleteSchoolGrade_Success() // 删除年级成功
@Test void testDeleteSchoolGrade_HasClasses() // 删除年级失败-有班级
@Test void testDeleteSchoolClass_Success() // 删除班级成功
@Test void testDeleteSchoolClass_HasStudents() // 删除班级失败-有学生
@Test void testSelectSchoolTree() // 查询学校树
@Test void testGenerateSchoolCode() // 生成学校编码
二、前端开发任务
2.1 页面框架开发
| 任务编号 | 任务名称 | 描述 | 优先级 | 工时 | 状态 |
|---|---|---|---|---|---|
| FE-SCH-01 | 创建主页面框架 | 创建 school/index.vue 主页面 | P0 | 2h | ☐ |
FE-SCH-01 详细说明
<!-- 页面结构 -->
<div class="school-container">
<!-- 左侧区域树:宽度250px,固定 -->
<div class="region-tree-wrapper">
<RegionTree />
</div>
<!-- 右侧内容区 -->
<div class="main-content">
<!-- 搜索栏 -->
<el-form />
<!-- 工具栏 -->
<div class="toolbar">
<el-button>新增学校</el-button>
</div>
<!-- 学校树表格 -->
<SchoolTree />
</div>
</div>
<!-- 样式 -->
.school-container {
display: flex;
height: calc(100vh - 84px);
}
.region-tree-wrapper {
width: 250px;
border-right: 1px solid #e6e6e6;
padding: 20px;
overflow-y: auto;
}
.main-content {
flex: 1;
padding: 20px;
overflow-y: auto;
}
验收标准
- 左右分栏布局正确
- 响应式滚动正常
- 加载状态显示
2.2 组件开发
| 任务编号 | 任务名称 | 描述 | 优先级 | 工时 | 状态 |
|---|---|---|---|---|---|
| FE-SCH-02 | RegionTree 组件 | 开发左侧区域树组件 | P0 | 2h | ☐ |
| FE-SCH-03 | SchoolTree 组件 | 开发右侧学校树表格组件 | P0 | 4h | ☐ |
| FE-SCH-04 | SchoolDialog 组件 | 开发新增/编辑学校弹窗 | P0 | 3h | ☐ |
| FE-SCH-05 | GradeSelectDialog 组件 | 开发年级选择弹窗 | P0 | 2h | ☐ |
| FE-SCH-06 | ClassSelectDialog 组件 | 开发班级选择弹窗 | P0 | 2h | ☐ |
FE-SCH-02 详细说明 - RegionTree
<!-- 组件功能 -->
1. 加载并展示区域树(省-市-区)
2. 默认展开第一级
3. 点击节点触发 node-click 事件
4. 高亮当前选中节点
<!-- 核心代码 -->
<el-tree
:data="regionTree"
:props="{ label: 'regionName', children: 'children' }"
:default-expand-all="false"
:expand-on-click-node="false"
highlight-current
node-key="regionId"
@node-click="handleNodeClick"
/>
FE-SCH-03 详细说明 - SchoolTree
<!-- 组件功能 -->
1. 使用 el-table 的 row-key 和 tree-props 实现树形展示
2. 根据节点类型(school/grade/class)显示不同操作按钮
3. 支持展开/收起
4. 加载状态显示
<!-- 核心代码 -->
<el-table
:data="schoolTreeData"
row-key="id"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
default-expand-all
>
<el-table-column prop="name" label="名称" />
<el-table-column prop="code" label="编码" width="150">
<template #default="{ row }">
{{ row.type === 'school' ? row.code : '-' }}
</template>
</el-table-column>
<!-- ... -->
<el-table-column label="操作" width="250">
<template #default="{ row }">
<!-- 学校操作 -->
<template v-if="row.type === 'school'">
<el-button link @click="handleEdit(row)">编辑</el-button>
<el-button link @click="handleAddGrade(row)">新增年级</el-button>
<el-popconfirm @confirm="handleDelete(row)">
<template #reference>
<el-button link type="danger">删除</el-button>
</template>
</el-popconfirm>
</template>
<!-- 年级操作 -->
<template v-else-if="row.type === 'grade'">
<el-button link @click="handleAddClass(row)">新增班级</el-button>
<el-popconfirm @confirm="handleDeleteGrade(row)">
<template #reference>
<el-button link type="danger">删除</el-button>
</template>
</el-popconfirm>
</template>
<!-- 班级操作 -->
<template v-else>
<el-popconfirm @confirm="handleDeleteClass(row)">
<template #reference>
<el-button link type="danger">删除</el-button>
</template>
</el-popconfirm>
</template>
</template>
</el-table-column>
</el-table>
FE-SCH-04 详细说明 - SchoolDialog
<!-- 组件功能 -->
1. 支持新增和编辑两种模式
2. 新增时学校编码不显示(后端自动生成)
3. 所属区域使用级联选择器
4. 表单验证
<!-- 表单字段 -->
| 字段 | 组件 | 验证规则 |
|-----|------|---------|
| 学校名称 | el-input | 必填,最大100字符 |
| 学校编码 | el-input | 编辑时只读显示 |
| 学校类型 | el-select | 必填 |
| 所属区域 | el-cascader | 必填 |
| 详细地址 | el-input | 选填 |
| 联系人 | el-input | 选填 |
| 联系电话 | el-input | 选填,手机号格式 |
| 状态 | el-switch | 默认启用 |
<!-- 核心方法 -->
// 暴露给父组件的方法
defineExpose({
open, // 打开新增弹窗
openEdit // 打开编辑弹窗
})
FE-SCH-05 详细说明 - GradeSelectDialog
<!-- 组件功能 -->
1. 加载年级字典列表
2. 已挂载的年级禁用(disabled)
3. 支持多选(el-checkbox-group)
4. 确认后调用 bindGrades 接口
<!-- 核心代码 -->
<el-checkbox-group v-model="selectedGrades">
<el-checkbox
v-for="item in gradeList"
:key="item.gradeId"
:label="item.gradeId"
:disabled="item.disabled"
>
{{ item.gradeName }}
<el-tag v-if="item.disabled" size="small" type="info">已挂载</el-tag>
</el-checkbox>
</el-checkbox-group>
FE-SCH-06 详细说明 - ClassSelectDialog
与 GradeSelectDialog 类似,调用 bindClasses 接口。
验收标准
- 组件功能完整
- Props/Events 定义清晰
- 加载状态处理
- 错误提示友好
2.3 接口与联调
| 任务编号 | 任务名称 | 描述 | 优先级 | 工时 | 状态 |
|---|---|---|---|---|---|
| FE-SCH-07 | 封装API接口 | 创建 api/school.js | P0 | 1h | ☐ |
| FE-SCH-08 | 主页面逻辑实现 | 实现主页面交互逻辑并联调 | P0 | 3h | ☐ |
FE-SCH-07 详细说明 - API封装
// api/school.js
// 获取学校树
export function getSchoolTree(regionId)
// 获取学校列表
export function getSchoolList(query)
// 获取学校详情
export function getSchool(schoolId)
// 新增学校
export function addSchool(data)
// 修改学校
export function updateSchool(data)
// 删除学校
export function deleteSchool(schoolId)
// 挂载年级
export function bindGrades(data)
// 挂载班级
export function bindClasses(data)
// 删除学校年级
export function deleteSchoolGrade(schoolGradeId)
// 删除学校班级
export function deleteSchoolClass(schoolClassId)
FE-SCH-08 详细说明 - 主页面逻辑
// 主要逻辑点
1. 初始化加载区域树和学校树
2. 区域树节点点击 -> 刷新学校树
3. 搜索栏查询 -> 刷新学校树
4. 新增/编辑学校 -> 打开弹窗 -> 提交 -> 刷新列表
5. 删除学校 -> 确认 -> 调用接口 -> 刷新列表
6. 新增年级 -> 打开选择弹窗 -> 确认 -> 刷新列表
7. 新增班级 -> 打开选择弹窗 -> 确认 -> 刷新列表
2.4 优化与测试
| 任务编号 | 任务名称 | 描述 | 优先级 | 工时 | 状态 |
|---|---|---|---|---|---|
| FE-SCH-09 | 样式优化 | 优化页面样式和交互体验 | P1 | 2h | ☐ |
| FE-SCH-10 | Bug修复 | 修复测试发现的问题 | P1 | 2h | ☐ |
FE-SCH-09 详细说明 - 样式优化
优化项:
1. 区域树节点悬停效果
2. 表格行悬停高亮
3. 操作按钮间距调整
4. 弹窗宽度适配
5. 加载骨架屏
6. 空状态提示
三、测试任务
| 任务编号 | 任务名称 | 描述 | 优先级 | 工时 | 状态 |
|---|---|---|---|---|---|
| TEST-01 | 接口测试 | 使用Postman测试所有接口 | P0 | 3h | ☐ |
| TEST-02 | 功能测试 | UI功能测试 | P0 | 3h | ☐ |
| TEST-03 | 集成测试 | 前后端联调测试 | P1 | 2h | ☐ |
TEST-01 详细说明 - 接口测试用例
| 用例编号 | 测试场景 | 预期结果 |
|---|---|---|
| API-01 | 获取学校树(有区域ID) | 返回该区域下学校树 |
| API-02 | 获取学校树(无区域ID) | 返回全部学校树 |
| API-03 | 新增学校(正常) | 成功,学校编码自动生成 |
| API-04 | 新增学校(名称为空) | 失败,返回验证错误 |
| API-05 | 编辑学校 | 成功 |
| API-06 | 删除学校(无子级) | 成功 |
| API-07 | 删除学校(有年级) | 失败,提示有年级 |
| API-08 | 删除学校(有学生) | 失败,提示被引用 |
| API-09 | 挂载年级 | 成功 |
| API-10 | 重复挂载年级 | 成功(忽略重复) |
| API-11 | 挂载班级 | 成功 |
| API-12 | 删除年级(有班级) | 失败,提示有班级 |
| API-13 | 删除班级(有学生) | 失败,提示被引用 |
TEST-02 详细说明 - 功能测试用例
| 用例编号 | 测试场景 | 测试步骤 | 预期结果 |
|---|---|---|---|
| UI-01 | 区域树展示 | 进入页面 | 左侧显示区域树 |
| UI-02 | 区域筛选 | 点击区域节点 | 右侧学校列表刷新 |
| UI-03 | 学校树展开 | 点击展开图标 | 显示年级和班级 |
| UI-04 | 新增学校 | 点击新增,填写表单,提交 | 成功,列表刷新 |
| UI-05 | 编辑学校 | 点击编辑,修改,提交 | 成功,数据更新 |
| UI-06 | 删除学校 | 点击删除,确认 | 成功/失败提示 |
| UI-07 | 新增年级 | 点击新增年级,选择,确认 | 成功,树刷新 |
| UI-08 | 新增班级 | 点击新增班级,选择,确认 | 成功,树刷新 |
| UI-09 | 表单验证 | 必填项留空提交 | 显示验证错误 |
| UI-10 | 搜索功能 | 输入关键词,点击搜索 | 列表过滤 |
四、开发进度跟踪
4.1 每日站会记录
| 日期 | 昨日完成 | 今日计划 | 阻塞问题 |
|---|---|---|---|
4.2 里程碑
| 里程碑 | 目标日期 | 完成日期 | 状态 |
|---|---|---|---|
| 后端实体层完成 | ☐ | ||
| 后端Service层完成 | ☐ | ||
| 后端Controller完成 | ☐ | ||
| 前端页面框架完成 | ☐ | ||
| 前端组件开发完成 | ☐ | ||
| 前后端联调完成 | ☐ | ||
| 测试验收完成 | ☐ |
五、问题跟踪
| 问题编号 | 问题描述 | 发现日期 | 负责人 | 状态 | 解决方案 |
|---|---|---|---|---|---|
文档结束