feat: 重构学校管理模块 - 按需求文档实现树形展示
根据需求文档完全重构学校管理页面: - 按区域树形展示学校、年级、班级结构 - 新增后端接口 /business/school/tree 返回树形数据 - 新增后端接口删除年级/班级 - 前端使用树形表格展示层级结构 - 弹窗标题按需求文档:编辑-学校、新增-年级、新增-班级 - 操作按钮:学校(编辑/新增年级/删除)、年级(新增班级/删除)、班级(删除) - 删除时提示是否有子级
This commit is contained in:
parent
bb14acd923
commit
b42aab4a8f
|
|
@ -11,6 +11,7 @@ import org.dromara.common.web.core.BaseController;
|
||||||
import org.dromara.pangu.school.domain.PgSchool;
|
import org.dromara.pangu.school.domain.PgSchool;
|
||||||
import org.dromara.pangu.school.domain.PgSchoolClass;
|
import org.dromara.pangu.school.domain.PgSchoolClass;
|
||||||
import org.dromara.pangu.school.domain.PgSchoolGrade;
|
import org.dromara.pangu.school.domain.PgSchoolGrade;
|
||||||
|
import org.dromara.pangu.school.domain.vo.SchoolTreeNode;
|
||||||
import org.dromara.pangu.school.service.IPgSchoolService;
|
import org.dromara.pangu.school.service.IPgSchoolService;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
@ -42,6 +43,15 @@ public class PgSchoolController extends BaseController {
|
||||||
return R.ok(schoolService.selectList(school));
|
return R.ok(schoolService.selectList(school));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取学校树形结构(包含年级和班级)
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("business:school:list")
|
||||||
|
@GetMapping("/tree")
|
||||||
|
public R<List<SchoolTreeNode>> tree(PgSchool school) {
|
||||||
|
return R.ok(schoolService.selectSchoolTree(school));
|
||||||
|
}
|
||||||
|
|
||||||
@SaCheckPermission("business:school:query")
|
@SaCheckPermission("business:school:query")
|
||||||
@GetMapping("/{schoolId}")
|
@GetMapping("/{schoolId}")
|
||||||
public R<PgSchool> getInfo(@PathVariable Long schoolId) {
|
public R<PgSchool> getInfo(@PathVariable Long schoolId) {
|
||||||
|
|
@ -103,4 +113,24 @@ public class PgSchoolController extends BaseController {
|
||||||
List<Long> classIds = (List<Long>) params.get("classIds");
|
List<Long> classIds = (List<Long>) params.get("classIds");
|
||||||
return toAjax(schoolService.addGradeClasses(schoolId, schoolGradeId, classIds));
|
return toAjax(schoolService.addGradeClasses(schoolId, schoolGradeId, classIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除学校下的年级
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("business:school:edit")
|
||||||
|
@Log(title = "学校年级管理", businessType = BusinessType.DELETE)
|
||||||
|
@DeleteMapping("/grade/{schoolGradeId}")
|
||||||
|
public R<Void> removeSchoolGrade(@PathVariable Long schoolGradeId) {
|
||||||
|
return toAjax(schoolService.removeSchoolGrade(schoolGradeId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除年级下的班级
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("business:school:edit")
|
||||||
|
@Log(title = "学校班级管理", businessType = BusinessType.DELETE)
|
||||||
|
@DeleteMapping("/class/{schoolClassId}")
|
||||||
|
public R<Void> removeGradeClass(@PathVariable Long schoolClassId) {
|
||||||
|
return toAjax(schoolService.removeGradeClass(schoolClassId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
package org.dromara.pangu.school.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校树节点(学校/年级/班级统一结构)
|
||||||
|
*
|
||||||
|
* @author pangu
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SchoolTreeNode {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点ID
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点名称
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点类型:school-学校, grade-年级, class-班级
|
||||||
|
*/
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校编码(仅学校节点有值)
|
||||||
|
*/
|
||||||
|
private String schoolCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校类型(仅学校节点有值)
|
||||||
|
*/
|
||||||
|
private String schoolType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 区域名称(仅学校节点有值)
|
||||||
|
*/
|
||||||
|
private String regionName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 区域ID(仅学校节点有值)
|
||||||
|
*/
|
||||||
|
private Long regionId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态(0正常 1停用)
|
||||||
|
*/
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private String createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建人
|
||||||
|
*/
|
||||||
|
private String createBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 子节点
|
||||||
|
*/
|
||||||
|
private List<SchoolTreeNode> children;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 父节点ID(用于前端判断层级)
|
||||||
|
*/
|
||||||
|
private Long parentId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校ID(年级和班级节点需要)
|
||||||
|
*/
|
||||||
|
private Long schoolId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校年级关联ID(班级节点需要)
|
||||||
|
*/
|
||||||
|
private Long schoolGradeId;
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||||
import org.dromara.pangu.school.domain.PgSchool;
|
import org.dromara.pangu.school.domain.PgSchool;
|
||||||
import org.dromara.pangu.school.domain.PgSchoolGrade;
|
import org.dromara.pangu.school.domain.PgSchoolGrade;
|
||||||
|
import org.dromara.pangu.school.domain.vo.SchoolTreeNode;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
@ -34,4 +35,19 @@ public interface IPgSchoolService {
|
||||||
* 为年级添加班级(批量挂载)
|
* 为年级添加班级(批量挂载)
|
||||||
*/
|
*/
|
||||||
int addGradeClasses(Long schoolId, Long schoolGradeId, List<Long> classIds);
|
int addGradeClasses(Long schoolId, Long schoolGradeId, List<Long> classIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取学校树形结构(包含年级和班级)
|
||||||
|
*/
|
||||||
|
List<SchoolTreeNode> selectSchoolTree(PgSchool school);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除学校下的年级(同时删除该年级下的班级)
|
||||||
|
*/
|
||||||
|
int removeSchoolGrade(Long schoolGradeId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除年级下的班级
|
||||||
|
*/
|
||||||
|
int removeGradeClass(Long schoolClassId);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,16 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||||
|
import org.dromara.pangu.base.domain.PgClass;
|
||||||
import org.dromara.pangu.base.domain.PgGrade;
|
import org.dromara.pangu.base.domain.PgGrade;
|
||||||
import org.dromara.pangu.base.domain.PgRegion;
|
import org.dromara.pangu.base.domain.PgRegion;
|
||||||
|
import org.dromara.pangu.base.mapper.PgClassMapper;
|
||||||
import org.dromara.pangu.base.mapper.PgGradeMapper;
|
import org.dromara.pangu.base.mapper.PgGradeMapper;
|
||||||
import org.dromara.pangu.base.mapper.PgRegionMapper;
|
import org.dromara.pangu.base.mapper.PgRegionMapper;
|
||||||
import org.dromara.pangu.school.domain.PgSchool;
|
import org.dromara.pangu.school.domain.PgSchool;
|
||||||
import org.dromara.pangu.school.domain.PgSchoolClass;
|
import org.dromara.pangu.school.domain.PgSchoolClass;
|
||||||
import org.dromara.pangu.school.domain.PgSchoolGrade;
|
import org.dromara.pangu.school.domain.PgSchoolGrade;
|
||||||
|
import org.dromara.pangu.school.domain.vo.SchoolTreeNode;
|
||||||
import org.dromara.pangu.school.mapper.PgSchoolClassMapper;
|
import org.dromara.pangu.school.mapper.PgSchoolClassMapper;
|
||||||
import org.dromara.pangu.school.mapper.PgSchoolGradeMapper;
|
import org.dromara.pangu.school.mapper.PgSchoolGradeMapper;
|
||||||
import org.dromara.pangu.school.mapper.PgSchoolMapper;
|
import org.dromara.pangu.school.mapper.PgSchoolMapper;
|
||||||
|
|
@ -20,10 +23,8 @@ import org.dromara.pangu.school.service.IPgSchoolService;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -39,6 +40,7 @@ public class PgSchoolServiceImpl implements IPgSchoolService {
|
||||||
private final PgSchoolGradeMapper schoolGradeMapper;
|
private final PgSchoolGradeMapper schoolGradeMapper;
|
||||||
private final PgSchoolClassMapper schoolClassMapper;
|
private final PgSchoolClassMapper schoolClassMapper;
|
||||||
private final PgGradeMapper gradeMapper;
|
private final PgGradeMapper gradeMapper;
|
||||||
|
private final PgClassMapper classMapper;
|
||||||
private final PgRegionMapper regionMapper;
|
private final PgRegionMapper regionMapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -188,6 +190,23 @@ public class PgSchoolServiceImpl implements IPgSchoolService {
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public int removeSchoolGrade(Long schoolGradeId) {
|
||||||
|
// 先删除该年级下的所有班级
|
||||||
|
schoolClassMapper.delete(
|
||||||
|
new LambdaQueryWrapper<PgSchoolClass>()
|
||||||
|
.eq(PgSchoolClass::getSchoolGradeId, schoolGradeId)
|
||||||
|
);
|
||||||
|
// 再删除年级
|
||||||
|
return schoolGradeMapper.deleteById(schoolGradeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int removeGradeClass(Long schoolClassId) {
|
||||||
|
return schoolClassMapper.deleteById(schoolClassId);
|
||||||
|
}
|
||||||
|
|
||||||
private LambdaQueryWrapper<PgSchool> buildQueryWrapper(PgSchool school) {
|
private LambdaQueryWrapper<PgSchool> buildQueryWrapper(PgSchool school) {
|
||||||
LambdaQueryWrapper<PgSchool> lqw = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<PgSchool> lqw = new LambdaQueryWrapper<>();
|
||||||
lqw.like(StrUtil.isNotBlank(school.getSchoolName()), PgSchool::getSchoolName, school.getSchoolName());
|
lqw.like(StrUtil.isNotBlank(school.getSchoolName()), PgSchool::getSchoolName, school.getSchoolName());
|
||||||
|
|
@ -196,4 +215,110 @@ public class PgSchoolServiceImpl implements IPgSchoolService {
|
||||||
lqw.eq(StrUtil.isNotBlank(school.getStatus()), PgSchool::getStatus, school.getStatus());
|
lqw.eq(StrUtil.isNotBlank(school.getStatus()), PgSchool::getStatus, school.getStatus());
|
||||||
return lqw;
|
return lqw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SchoolTreeNode> selectSchoolTree(PgSchool school) {
|
||||||
|
// 查询学校列表
|
||||||
|
List<PgSchool> schools = baseMapper.selectList(buildQueryWrapper(school));
|
||||||
|
fillRegionName(schools);
|
||||||
|
|
||||||
|
if (schools.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收集所有学校ID
|
||||||
|
List<Long> schoolIds = schools.stream().map(PgSchool::getSchoolId).toList();
|
||||||
|
|
||||||
|
// 查询所有学校的年级
|
||||||
|
List<PgSchoolGrade> allSchoolGrades = schoolGradeMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<PgSchoolGrade>().in(PgSchoolGrade::getSchoolId, schoolIds)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 查询年级名称
|
||||||
|
Set<Long> gradeIds = allSchoolGrades.stream().map(PgSchoolGrade::getGradeId).collect(java.util.stream.Collectors.toSet());
|
||||||
|
Map<Long, String> gradeNameMap = new HashMap<>();
|
||||||
|
if (!gradeIds.isEmpty()) {
|
||||||
|
List<PgGrade> grades = gradeMapper.selectBatchIds(gradeIds);
|
||||||
|
gradeNameMap = grades.stream().collect(java.util.stream.Collectors.toMap(PgGrade::getGradeId, PgGrade::getGradeName));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收集所有学校年级关联ID
|
||||||
|
List<Long> schoolGradeIds = allSchoolGrades.stream().map(PgSchoolGrade::getId).toList();
|
||||||
|
|
||||||
|
// 查询所有班级
|
||||||
|
final List<PgSchoolClass> allSchoolClasses;
|
||||||
|
if (!schoolGradeIds.isEmpty()) {
|
||||||
|
allSchoolClasses = schoolClassMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<PgSchoolClass>().in(PgSchoolClass::getSchoolGradeId, schoolGradeIds)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
allSchoolClasses = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询班级名称
|
||||||
|
Set<Long> classIds = allSchoolClasses.stream().map(PgSchoolClass::getClassId).collect(java.util.stream.Collectors.toSet());
|
||||||
|
Map<Long, String> classNameMap = new HashMap<>();
|
||||||
|
if (!classIds.isEmpty()) {
|
||||||
|
List<PgClass> classes = classMapper.selectBatchIds(classIds);
|
||||||
|
classNameMap = classes.stream().collect(java.util.stream.Collectors.toMap(PgClass::getClassId, PgClass::getClassName));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建树形结构
|
||||||
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
List<SchoolTreeNode> result = new ArrayList<>();
|
||||||
|
|
||||||
|
for (PgSchool s : schools) {
|
||||||
|
SchoolTreeNode schoolNode = new SchoolTreeNode();
|
||||||
|
schoolNode.setId(s.getSchoolId());
|
||||||
|
schoolNode.setName(s.getSchoolName());
|
||||||
|
schoolNode.setType("school");
|
||||||
|
schoolNode.setSchoolCode(s.getSchoolCode());
|
||||||
|
schoolNode.setSchoolType(s.getSchoolType());
|
||||||
|
schoolNode.setRegionName(s.getRegionName());
|
||||||
|
schoolNode.setRegionId(s.getRegionId());
|
||||||
|
schoolNode.setStatus(s.getStatus());
|
||||||
|
schoolNode.setCreateTime(s.getCreateTime() != null ? sdf.format(s.getCreateTime()) : null);
|
||||||
|
schoolNode.setCreateBy(s.getCreateBy() != null ? s.getCreateBy().toString() : null);
|
||||||
|
schoolNode.setSchoolId(s.getSchoolId());
|
||||||
|
|
||||||
|
// 查找该学校的年级
|
||||||
|
Map<Long, String> finalGradeNameMap = gradeNameMap;
|
||||||
|
Map<Long, String> finalClassNameMap = classNameMap;
|
||||||
|
List<SchoolTreeNode> gradeNodes = allSchoolGrades.stream()
|
||||||
|
.filter(sg -> sg.getSchoolId().equals(s.getSchoolId()))
|
||||||
|
.map(sg -> {
|
||||||
|
SchoolTreeNode gradeNode = new SchoolTreeNode();
|
||||||
|
gradeNode.setId(sg.getId()); // 使用学校年级关联表ID
|
||||||
|
gradeNode.setName(finalGradeNameMap.getOrDefault(sg.getGradeId(), ""));
|
||||||
|
gradeNode.setType("grade");
|
||||||
|
gradeNode.setSchoolId(sg.getSchoolId());
|
||||||
|
gradeNode.setParentId(s.getSchoolId());
|
||||||
|
gradeNode.setSchoolGradeId(sg.getId());
|
||||||
|
|
||||||
|
// 查找该年级的班级
|
||||||
|
List<SchoolTreeNode> classNodes = allSchoolClasses.stream()
|
||||||
|
.filter(sc -> sc.getSchoolGradeId().equals(sg.getId()))
|
||||||
|
.map(sc -> {
|
||||||
|
SchoolTreeNode classNode = new SchoolTreeNode();
|
||||||
|
classNode.setId(sc.getId()); // 使用学校班级关联表ID
|
||||||
|
classNode.setName(finalClassNameMap.getOrDefault(sc.getClassId(), ""));
|
||||||
|
classNode.setType("class");
|
||||||
|
classNode.setSchoolId(sc.getSchoolId());
|
||||||
|
classNode.setSchoolGradeId(sc.getSchoolGradeId());
|
||||||
|
classNode.setParentId(sg.getId());
|
||||||
|
return classNode;
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
gradeNode.setChildren(classNodes.isEmpty() ? null : new ArrayList<>(classNodes));
|
||||||
|
return gradeNode;
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
schoolNode.setChildren(gradeNodes.isEmpty() ? null : new ArrayList<>(gradeNodes));
|
||||||
|
result.add(schoolNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
title="新增班级"
|
title="新增-班级"
|
||||||
width="500px"
|
width="500px"
|
||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
>
|
>
|
||||||
<div style="margin-bottom: 16px;">
|
<div style="margin-bottom: 16px;">
|
||||||
<span style="font-weight: bold;">学校:</span>
|
<div><span style="font-weight: bold;">学校:</span><span>{{ currentSchool?.schoolName }}</span></div>
|
||||||
<span>{{ currentSchool?.schoolName }}</span>
|
<div><span style="font-weight: bold;">年级:</span><span>{{ currentSchool?.gradeName }}</span></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-form ref="formRef" :model="form" label-width="80px">
|
<el-form ref="formRef" :model="form" label-width="80px">
|
||||||
<el-form-item label="选择年级" prop="schoolGradeId">
|
|
||||||
<el-select v-model="form.schoolGradeId" placeholder="请先选择年级" style="width: 100%">
|
|
||||||
<el-option
|
|
||||||
v-for="item in schoolGradeOptions"
|
|
||||||
:key="item.id"
|
|
||||||
:label="item.gradeName"
|
|
||||||
:value="item.id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="选择班级" prop="classIds">
|
<el-form-item label="选择班级" prop="classIds">
|
||||||
<el-checkbox-group v-model="form.classIds">
|
<el-checkbox-group v-model="form.classIds">
|
||||||
<el-row :gutter="10">
|
<el-row :gutter="10">
|
||||||
|
|
@ -45,13 +34,12 @@
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
/**
|
/**
|
||||||
* 新增班级弹窗
|
* 新增班级弹窗(为年级挂载班级)
|
||||||
* @author pangu
|
* @author pangu
|
||||||
*/
|
*/
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import request from '@/utils/request'
|
import { addGradeClass, getClassOptions } from '@/api/pangu/school'
|
||||||
import { addGradeClass, getClassOptions, getSchoolGradeTree } from '@/api/pangu/school'
|
|
||||||
|
|
||||||
const emit = defineEmits(['success'])
|
const emit = defineEmits(['success'])
|
||||||
|
|
||||||
|
|
@ -59,7 +47,6 @@ const dialogVisible = ref(false)
|
||||||
const submitLoading = ref(false)
|
const submitLoading = ref(false)
|
||||||
const formRef = ref(null)
|
const formRef = ref(null)
|
||||||
const currentSchool = ref(null)
|
const currentSchool = ref(null)
|
||||||
const schoolGradeOptions = ref([])
|
|
||||||
const classOptions = ref([])
|
const classOptions = ref([])
|
||||||
|
|
||||||
// 表单数据
|
// 表单数据
|
||||||
|
|
@ -69,19 +56,6 @@ const form = ref({
|
||||||
classIds: []
|
classIds: []
|
||||||
})
|
})
|
||||||
|
|
||||||
// 获取学校已挂载的年级
|
|
||||||
const fetchSchoolGrades = async (schoolId) => {
|
|
||||||
try {
|
|
||||||
const res = await getSchoolGradeTree(schoolId)
|
|
||||||
if (res.code === 200) {
|
|
||||||
// res.data 是 PgSchoolGrade 列表,包含 id(学校年级关联ID)、gradeId、gradeName
|
|
||||||
schoolGradeOptions.value = res.data || []
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取学校年级失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取班级选项
|
// 获取班级选项
|
||||||
const fetchClassOptions = async () => {
|
const fetchClassOptions = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -100,24 +74,19 @@ const fetchClassOptions = async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 打开弹窗
|
// 打开弹窗
|
||||||
const open = (school) => {
|
// schoolData: { schoolId, schoolName, schoolGradeId, gradeName }
|
||||||
|
const open = (schoolData) => {
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
currentSchool.value = school
|
currentSchool.value = schoolData
|
||||||
form.value = {
|
form.value = {
|
||||||
schoolId: school.schoolId,
|
schoolId: schoolData.schoolId,
|
||||||
schoolGradeId: null,
|
schoolGradeId: schoolData.schoolGradeId,
|
||||||
classIds: []
|
classIds: []
|
||||||
}
|
}
|
||||||
// 获取该学校已挂载的年级
|
|
||||||
fetchSchoolGrades(school.schoolId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提交表单
|
// 提交表单
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!form.value.schoolGradeId) {
|
|
||||||
ElMessage.warning('请选择年级')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (form.value.classIds.length === 0) {
|
if (form.value.classIds.length === 0) {
|
||||||
ElMessage.warning('请至少选择一个班级')
|
ElMessage.warning('请至少选择一个班级')
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
title="新增年级"
|
title="新增-年级"
|
||||||
width="500px"
|
width="500px"
|
||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
|
|
|
||||||
|
|
@ -96,8 +96,8 @@ const form = ref({
|
||||||
status: '0'
|
status: '0'
|
||||||
})
|
})
|
||||||
|
|
||||||
// 弹窗标题
|
// 弹窗标题 - 根据需求文档:编辑-学校
|
||||||
const dialogTitle = computed(() => isEdit.value ? '编辑学校' : '新增学校')
|
const dialogTitle = computed(() => isEdit.value ? '编辑-学校' : '新增学校')
|
||||||
|
|
||||||
// 表单验证规则
|
// 表单验证规则
|
||||||
const rules = {
|
const rules = {
|
||||||
|
|
@ -141,7 +141,9 @@ const handleRegionChange = (value) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 打开弹窗
|
// 打开弹窗
|
||||||
const open = (row) => {
|
// row: 编辑时传入学校数据,新增时为null
|
||||||
|
// defaultRegionId: 新增时默认选中的区域ID(从列表页带入)
|
||||||
|
const open = (row, defaultRegionId = null) => {
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
isEdit.value = !!row
|
isEdit.value = !!row
|
||||||
|
|
||||||
|
|
@ -158,15 +160,16 @@ const open = (row) => {
|
||||||
status: row.status
|
status: row.status
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 新增模式:重置表单
|
// 新增模式:重置表单,如果有默认区域则带入
|
||||||
|
const regionIds = defaultRegionId ? getRegionIdPath(defaultRegionId) : []
|
||||||
form.value = {
|
form.value = {
|
||||||
schoolId: null,
|
schoolId: null,
|
||||||
schoolCode: '',
|
schoolCode: '',
|
||||||
schoolName: '',
|
schoolName: '',
|
||||||
schoolType: '',
|
schoolType: '',
|
||||||
regionId: null,
|
regionId: defaultRegionId,
|
||||||
regionIds: [],
|
regionIds: regionIds,
|
||||||
regionName: '',
|
regionName: defaultRegionId ? getRegionPath(regionIds, props.regionTree) : '',
|
||||||
status: '0'
|
status: '0'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
:props="{ label: 'regionName', children: 'children' }"
|
:props="{ label: 'regionName', children: 'children' }"
|
||||||
node-key="regionId"
|
node-key="regionId"
|
||||||
highlight-current
|
highlight-current
|
||||||
|
default-expand-all
|
||||||
:filter-node-method="filterNode"
|
:filter-node-method="filterNode"
|
||||||
@node-click="handleNodeClick"
|
@node-click="handleNodeClick"
|
||||||
/>
|
/>
|
||||||
|
|
@ -51,50 +52,61 @@
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-table v-loading="loading" :data="tableData" border stripe :header-cell-style="{ background: '#f5f7fa', color: '#606266' }" style="width: 100%">
|
<!-- 树形表格展示学校/年级/班级 -->
|
||||||
|
<el-table
|
||||||
|
v-loading="loading"
|
||||||
|
:data="treeData"
|
||||||
|
row-key="id"
|
||||||
|
border
|
||||||
|
default-expand-all
|
||||||
|
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||||
|
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-table-column prop="name" label="学校名称" min-width="200" show-overflow-tooltip />
|
||||||
<el-table-column prop="schoolCode" label="学校编码" width="130" />
|
<el-table-column prop="schoolCode" label="学校编码" width="130" />
|
||||||
<el-table-column prop="schoolName" label="学校名称" min-width="150" show-overflow-tooltip />
|
|
||||||
<el-table-column prop="schoolType" label="学校类型" width="100" align="center">
|
<el-table-column prop="schoolType" label="学校类型" width="100" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
{{ schoolTypeMap[row.schoolType] || row.schoolType }}
|
<span v-if="row.type === 'school'">{{ schoolTypeMap[row.schoolType] || '-' }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="regionName" label="所属区域" min-width="150" show-overflow-tooltip />
|
<el-table-column prop="regionName" label="所属区域" min-width="120" show-overflow-tooltip />
|
||||||
<el-table-column prop="status" label="状态" width="80" align="center">
|
<el-table-column prop="status" label="状态" width="80" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag :type="row.status === '0' ? 'success' : 'danger'">
|
<template v-if="row.type === 'school'">
|
||||||
{{ row.status === '0' ? '正常' : '停用' }}
|
<el-tag :type="row.status === '0' ? 'success' : 'danger'" size="small">
|
||||||
</el-tag>
|
{{ row.status === '0' ? '正常' : '停用' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="createTime" label="创建时间" width="160" />
|
<el-table-column prop="createTime" label="创建时间" width="160" />
|
||||||
<el-table-column prop="createBy" label="创建人" width="100" />
|
<el-table-column prop="createBy" label="创建人" width="100" />
|
||||||
<el-table-column label="操作" width="280" fixed="right" align="center">
|
<el-table-column label="操作" width="220" fixed="right" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button type="primary" link :icon="Edit" @click="handleEdit(row)">编辑</el-button>
|
<!-- 学校操作:编辑、新增年级、删除 -->
|
||||||
<el-button type="primary" link :icon="Collection" @click="handleAddGrade(row)">新增年级</el-button>
|
<template v-if="row.type === 'school'">
|
||||||
<el-button type="primary" link :icon="Files" @click="handleAddClass(row)">新增班级</el-button>
|
<el-button type="primary" link size="small" @click="handleEdit(row)">编辑</el-button>
|
||||||
<el-button type="danger" link :icon="Delete" @click="handleDelete(row)">删除</el-button>
|
<el-button type="primary" link size="small" @click="handleAddGrade(row)">新增年级</el-button>
|
||||||
|
<el-button type="danger" link size="small" @click="handleDelete(row, 'school')">删除</el-button>
|
||||||
|
</template>
|
||||||
|
<!-- 年级操作:新增班级、删除 -->
|
||||||
|
<template v-else-if="row.type === 'grade'">
|
||||||
|
<el-button type="primary" link size="small" @click="handleAddClass(row)">新增班级</el-button>
|
||||||
|
<el-button type="danger" link size="small" @click="handleDelete(row, 'grade')">删除</el-button>
|
||||||
|
</template>
|
||||||
|
<!-- 班级操作:删除 -->
|
||||||
|
<template v-else>
|
||||||
|
<el-button type="danger" link size="small" @click="handleDelete(row, 'class')">删除</el-button>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<!-- 分页 -->
|
|
||||||
<el-pagination
|
|
||||||
v-model:current-page="queryParams.pageNum"
|
|
||||||
v-model:page-size="queryParams.pageSize"
|
|
||||||
:page-sizes="[10, 20, 50, 100]"
|
|
||||||
:total="total"
|
|
||||||
layout="total, sizes, prev, pager, next, jumper"
|
|
||||||
style="margin-top: 16px; justify-content: flex-end"
|
|
||||||
@size-change="handleQuery"
|
|
||||||
@current-change="handleQuery"
|
|
||||||
/>
|
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<!-- 新增/编辑弹窗 -->
|
<!-- 编辑学校弹窗 -->
|
||||||
<SchoolDialog ref="schoolDialogRef" :region-tree="regionTree" @success="handleQuery" />
|
<SchoolDialog ref="schoolDialogRef" :region-tree="regionTree" @success="handleQuery" />
|
||||||
|
|
||||||
<!-- 新增年级弹窗 -->
|
<!-- 新增年级弹窗 -->
|
||||||
|
|
@ -108,9 +120,10 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
/**
|
/**
|
||||||
* 学校管理页面
|
* 学校管理页面
|
||||||
|
* 按区域树形展示学校、年级、班级结构
|
||||||
* @author pangu
|
* @author pangu
|
||||||
*/
|
*/
|
||||||
import { Collection, Delete, Edit, Files, Plus, Refresh, Search } from '@element-plus/icons-vue'
|
import { Plus, Refresh, Search } from '@element-plus/icons-vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { onMounted, ref, watch } from 'vue'
|
import { onMounted, ref, watch } from 'vue'
|
||||||
import request from '@/utils/request'
|
import request from '@/utils/request'
|
||||||
|
|
@ -135,13 +148,10 @@ const selectedRegionId = ref(null)
|
||||||
|
|
||||||
// 表格相关
|
// 表格相关
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const tableData = ref([])
|
const treeData = ref([])
|
||||||
const total = ref(0)
|
|
||||||
|
|
||||||
// 查询参数
|
// 查询参数
|
||||||
const queryParams = ref({
|
const queryParams = ref({
|
||||||
pageNum: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
schoolName: '',
|
schoolName: '',
|
||||||
status: '',
|
status: '',
|
||||||
regionId: ''
|
regionId: ''
|
||||||
|
|
@ -171,86 +181,125 @@ const getRegionTree = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取学校列表
|
// 获取学校树形数据(包含年级和班级)
|
||||||
const getList = async () => {
|
const getSchoolTree = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await request.get('/business/school/list', { params: queryParams.value })
|
const res = await request.get('/business/school/tree', { params: queryParams.value })
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
tableData.value = res.rows
|
treeData.value = res.data
|
||||||
total.value = res.total
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 树节点点击
|
// 区域树节点点击 - 基于选择的区域筛选学校
|
||||||
const handleNodeClick = (data) => {
|
const handleNodeClick = (data) => {
|
||||||
selectedRegionId.value = data.regionId
|
selectedRegionId.value = data.regionId
|
||||||
queryParams.value.regionId = data.regionId
|
queryParams.value.regionId = data.regionId
|
||||||
queryParams.value.pageNum = 1
|
getSchoolTree()
|
||||||
getList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 搜索
|
// 搜索
|
||||||
const handleQuery = () => {
|
const handleQuery = () => {
|
||||||
queryParams.value.pageNum = 1
|
getSchoolTree()
|
||||||
getList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置
|
// 重置
|
||||||
const resetQuery = () => {
|
const resetQuery = () => {
|
||||||
queryParams.value = {
|
queryParams.value = {
|
||||||
pageNum: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
schoolName: '',
|
schoolName: '',
|
||||||
status: '',
|
status: '',
|
||||||
regionId: ''
|
regionId: ''
|
||||||
}
|
}
|
||||||
selectedRegionId.value = null
|
selectedRegionId.value = null
|
||||||
treeRef.value?.setCurrentKey(null)
|
treeRef.value?.setCurrentKey(null)
|
||||||
getList()
|
getSchoolTree()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增
|
// 新增学校 - 基于选择的区域新增学校信息
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
schoolDialogRef.value?.open()
|
schoolDialogRef.value?.open(null, selectedRegionId.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 编辑
|
// 编辑学校
|
||||||
const handleEdit = (row) => {
|
const handleEdit = (row) => {
|
||||||
schoolDialogRef.value?.open(row)
|
const schoolData = {
|
||||||
|
schoolId: row.id,
|
||||||
|
schoolCode: row.schoolCode,
|
||||||
|
schoolName: row.name,
|
||||||
|
schoolType: row.schoolType,
|
||||||
|
regionId: row.regionId,
|
||||||
|
regionName: row.regionName,
|
||||||
|
status: row.status
|
||||||
|
}
|
||||||
|
schoolDialogRef.value?.open(schoolData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除
|
// 新增年级 - 学校下新增年级,选择对应的年级挂载
|
||||||
const handleDelete = (row) => {
|
const handleAddGrade = (row) => {
|
||||||
ElMessageBox.confirm(`确定要删除学校"${row.schoolName}"吗?`, '提示', {
|
const schoolData = {
|
||||||
|
schoolId: row.id,
|
||||||
|
schoolName: row.name
|
||||||
|
}
|
||||||
|
gradeDialogRef.value?.open(schoolData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增班级 - 年级下新增班级,选择对应的班级挂载
|
||||||
|
const handleAddClass = (row) => {
|
||||||
|
// 从 treeData 中查找学校名称
|
||||||
|
const school = treeData.value.find(s => s.id === row.schoolId)
|
||||||
|
const schoolData = {
|
||||||
|
schoolId: row.schoolId,
|
||||||
|
schoolName: school ? school.name : '',
|
||||||
|
schoolGradeId: row.id,
|
||||||
|
gradeName: row.name
|
||||||
|
}
|
||||||
|
classDialogRef.value?.open(schoolData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除操作 - 删除时提示是否有子级
|
||||||
|
const handleDelete = (row, type) => {
|
||||||
|
let message = ''
|
||||||
|
let url = ''
|
||||||
|
|
||||||
|
if (type === 'school') {
|
||||||
|
// 检查是否有子级
|
||||||
|
if (row.children && row.children.length > 0) {
|
||||||
|
message = `学校"${row.name}"下有年级/班级,确定要删除吗?删除后其下的年级和班级也将被删除。`
|
||||||
|
} else {
|
||||||
|
message = `确定要删除学校"${row.name}"吗?`
|
||||||
|
}
|
||||||
|
url = `/business/school/${row.id}`
|
||||||
|
} else if (type === 'grade') {
|
||||||
|
if (row.children && row.children.length > 0) {
|
||||||
|
message = `年级"${row.name}"下有班级,确定要删除吗?其下的班级也将被删除。`
|
||||||
|
} else {
|
||||||
|
message = `确定要删除年级"${row.name}"吗?`
|
||||||
|
}
|
||||||
|
url = `/business/school/grade/${row.id}`
|
||||||
|
} else {
|
||||||
|
message = `确定要删除班级"${row.name}"吗?`
|
||||||
|
url = `/business/school/class/${row.id}`
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessageBox.confirm(message, '提示', {
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
type: 'warning'
|
type: 'warning'
|
||||||
}).then(async () => {
|
}).then(async () => {
|
||||||
const res = await request.delete(`/business/school/${row.schoolId}`)
|
const res = await request.delete(url)
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
ElMessage.success('删除成功')
|
ElMessage.success('删除成功')
|
||||||
getList()
|
getSchoolTree()
|
||||||
}
|
}
|
||||||
}).catch(() => {})
|
}).catch(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增年级
|
|
||||||
const handleAddGrade = (row) => {
|
|
||||||
gradeDialogRef.value?.open(row)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增班级
|
|
||||||
const handleAddClass = (row) => {
|
|
||||||
classDialogRef.value?.open(row)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getRegionTree()
|
getRegionTree()
|
||||||
getList()
|
getSchoolTree()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue