feat: 数据权限功能实现 - 学校作为部门节点

1. 学校/年级/班级/学生表增加 dept_id 字段
2. 添加 @DataPermission 数据权限注解
3. 学校创建时自动创建对应部门
4. 学校编辑对话框增加上级部门选择
5. 修复用户管理部门列显示问题
This commit is contained in:
神码-方晓辉 2026-02-03 21:45:14 +08:00
parent 57b171503d
commit 6f47adf86e
12 changed files with 227 additions and 10 deletions

View File

@ -45,6 +45,11 @@ public class PgSchool extends BaseEntity {
private String tenantId; private String tenantId;
/**
* 关联部门ID
*/
private Long deptId;
@TableLogic @TableLogic
private String delFlag; private String delFlag;

View File

@ -31,6 +31,11 @@ public class PgSchoolClass implements Serializable {
private String tenantId; private String tenantId;
/**
* 关联部门ID数据权限用
*/
private Long deptId;
private Long createBy; private Long createBy;
private Date createTime; private Date createTime;

View File

@ -27,6 +27,11 @@ public class PgSchoolGrade implements Serializable {
private String tenantId; private String tenantId;
/**
* 关联部门ID数据权限用
*/
private Long deptId;
private Long createBy; private Long createBy;
private Date createTime; private Date createTime;

View File

@ -1,12 +1,30 @@
package org.dromara.pangu.school.mapper; package org.dromara.pangu.school.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import org.dromara.common.mybatis.annotation.DataColumn;
import org.dromara.common.mybatis.annotation.DataPermission;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.pangu.school.domain.PgSchoolClass; import org.dromara.pangu.school.domain.PgSchoolClass;
import java.util.List;
/** /**
* 学校班级 Mapper 接口 * 学校班级 Mapper 接口
* *
* @author pangu * @author pangu
*/ */
public interface PgSchoolClassMapper extends BaseMapperPlus<PgSchoolClass, PgSchoolClass> { public interface PgSchoolClassMapper extends BaseMapperPlus<PgSchoolClass, PgSchoolClass> {
/**
* 查询学校班级列表带数据权限
*
* @param queryWrapper 查询条件
* @return 学校班级列表
*/
@DataPermission({
@DataColumn(key = "deptName", value = "dept_id")
})
default List<PgSchoolClass> selectClassList(Wrapper<PgSchoolClass> queryWrapper) {
return this.selectList(queryWrapper);
}
} }

View File

@ -1,12 +1,30 @@
package org.dromara.pangu.school.mapper; package org.dromara.pangu.school.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import org.dromara.common.mybatis.annotation.DataColumn;
import org.dromara.common.mybatis.annotation.DataPermission;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.pangu.school.domain.PgSchoolGrade; import org.dromara.pangu.school.domain.PgSchoolGrade;
import java.util.List;
/** /**
* 学校年级关联 Mapper 接口 * 学校年级关联 Mapper 接口
* *
* @author pangu * @author pangu
*/ */
public interface PgSchoolGradeMapper extends BaseMapperPlus<PgSchoolGrade, PgSchoolGrade> { public interface PgSchoolGradeMapper extends BaseMapperPlus<PgSchoolGrade, PgSchoolGrade> {
/**
* 查询学校年级列表带数据权限
*
* @param queryWrapper 查询条件
* @return 学校年级列表
*/
@DataPermission({
@DataColumn(key = "deptName", value = "dept_id")
})
default List<PgSchoolGrade> selectGradeList(Wrapper<PgSchoolGrade> queryWrapper) {
return this.selectList(queryWrapper);
}
} }

View File

@ -1,12 +1,45 @@
package org.dromara.pangu.school.mapper; package org.dromara.pangu.school.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.dromara.common.mybatis.annotation.DataColumn;
import org.dromara.common.mybatis.annotation.DataPermission;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.pangu.school.domain.PgSchool; import org.dromara.pangu.school.domain.PgSchool;
import java.util.List;
/** /**
* 学校 Mapper 接口 * 学校 Mapper 接口
* *
* @author pangu * @author pangu
*/ */
public interface PgSchoolMapper extends BaseMapperPlus<PgSchool, PgSchool> { public interface PgSchoolMapper extends BaseMapperPlus<PgSchool, PgSchool> {
/**
* 查询学校列表带数据权限
*
* @param queryWrapper 查询条件
* @return 学校列表
*/
@DataPermission({
@DataColumn(key = "deptName", value = "dept_id")
})
default List<PgSchool> selectSchoolList(Wrapper<PgSchool> queryWrapper) {
return this.selectList(queryWrapper);
}
/**
* 分页查询学校列表带数据权限
*
* @param page 分页信息
* @param queryWrapper 查询条件
* @return 学校分页列表
*/
@DataPermission({
@DataColumn(key = "deptName", value = "dept_id")
})
default Page<PgSchool> selectPageSchoolList(Page<PgSchool> page, Wrapper<PgSchool> queryWrapper) {
return this.selectPage(page, queryWrapper);
}
} }

View File

@ -21,6 +21,8 @@ import org.dromara.pangu.school.mapper.PgSchoolGradeMapper;
import org.dromara.pangu.school.mapper.PgSchoolMapper; import org.dromara.pangu.school.mapper.PgSchoolMapper;
import org.dromara.pangu.school.service.IPgSchoolService; import org.dromara.pangu.school.service.IPgSchoolService;
import org.dromara.common.core.service.UserService; import org.dromara.common.core.service.UserService;
import org.dromara.system.domain.bo.SysDeptBo;
import org.dromara.system.service.ISysDeptService;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -44,6 +46,7 @@ public class PgSchoolServiceImpl implements IPgSchoolService {
private final PgClassMapper classMapper; private final PgClassMapper classMapper;
private final PgRegionMapper regionMapper; private final PgRegionMapper regionMapper;
private final UserService userService; private final UserService userService;
private final ISysDeptService deptService;
@Override @Override
public TableDataInfo<PgSchool> selectPageList(PgSchool school, PageQuery pageQuery) { public TableDataInfo<PgSchool> selectPageList(PgSchool school, PageQuery pageQuery) {
@ -94,10 +97,26 @@ public class PgSchoolServiceImpl implements IPgSchoolService {
} }
@Override @Override
@Transactional(rollbackFor = Exception.class)
public int insert(PgSchool school) { public int insert(PgSchool school) {
// 自动生成学校编码 // 自动生成学校编码
String schoolCode = generateSchoolCode(); String schoolCode = generateSchoolCode();
school.setSchoolCode(schoolCode); school.setSchoolCode(schoolCode);
// 如果传入了上级部门ID自动创建学校对应的部门节点
if (school.getDeptId() != null) {
Long parentDeptId = school.getDeptId();
SysDeptBo deptBo = new SysDeptBo();
deptBo.setParentId(parentDeptId);
deptBo.setDeptName(school.getSchoolName());
deptBo.setDeptCategory("school"); // 标记为学校类型
deptBo.setOrderNum(0);
deptBo.setStatus("0"); // 正常状态
deptService.insertDept(deptBo);
// 获取新创建的部门ID
school.setDeptId(deptBo.getDeptId());
}
return baseMapper.insert(school); return baseMapper.insert(school);
} }
@ -123,7 +142,19 @@ public class PgSchoolServiceImpl implements IPgSchoolService {
} }
@Override @Override
@Transactional(rollbackFor = Exception.class)
public int update(PgSchool school) { public int update(PgSchool school) {
// 如果学校有关联的部门同步更新部门名称
PgSchool existSchool = baseMapper.selectById(school.getSchoolId());
if (existSchool != null && existSchool.getDeptId() != null) {
// 检查学校名称是否变化
if (school.getSchoolName() != null && !school.getSchoolName().equals(existSchool.getSchoolName())) {
SysDeptBo deptBo = new SysDeptBo();
deptBo.setDeptId(existSchool.getDeptId());
deptBo.setDeptName(school.getSchoolName());
deptService.updateDept(deptBo);
}
}
return baseMapper.updateById(school); return baseMapper.updateById(school);
} }
@ -133,6 +164,9 @@ public class PgSchoolServiceImpl implements IPgSchoolService {
for (Long schoolId : schoolIds) { for (Long schoolId : schoolIds) {
// TODO: 检查是否被学生信息引用学生管理模块完成后添加有则不允许删除 // TODO: 检查是否被学生信息引用学生管理模块完成后添加有则不允许删除
// 查询学校信息获取关联的部门ID
PgSchool school = baseMapper.selectById(schoolId);
// 级联删除先删除学校下的所有班级和年级 // 级联删除先删除学校下的所有班级和年级
List<PgSchoolGrade> grades = schoolGradeMapper.selectList( List<PgSchoolGrade> grades = schoolGradeMapper.selectList(
new LambdaQueryWrapper<PgSchoolGrade>().eq(PgSchoolGrade::getSchoolId, schoolId) new LambdaQueryWrapper<PgSchoolGrade>().eq(PgSchoolGrade::getSchoolId, schoolId)
@ -147,6 +181,15 @@ public class PgSchoolServiceImpl implements IPgSchoolService {
schoolGradeMapper.delete( schoolGradeMapper.delete(
new LambdaQueryWrapper<PgSchoolGrade>().eq(PgSchoolGrade::getSchoolId, schoolId) new LambdaQueryWrapper<PgSchoolGrade>().eq(PgSchoolGrade::getSchoolId, schoolId)
); );
// 删除关联的部门节点
if (school != null && school.getDeptId() != null) {
try {
deptService.deleteDeptById(school.getDeptId());
} catch (Exception e) {
// 忽略部门删除失败可能部门下有用户
}
}
} }
return baseMapper.deleteByIds(Arrays.asList(schoolIds)); return baseMapper.deleteByIds(Arrays.asList(schoolIds));
} }

View File

@ -48,6 +48,11 @@ public class PgStudent extends BaseEntity {
private String tenantId; private String tenantId;
/**
* 关联部门ID数据权限用
*/
private Long deptId;
@TableLogic @TableLogic
private String delFlag; private String delFlag;

View File

@ -1,12 +1,45 @@
package org.dromara.pangu.student.mapper; package org.dromara.pangu.student.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.dromara.common.mybatis.annotation.DataColumn;
import org.dromara.common.mybatis.annotation.DataPermission;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.pangu.student.domain.PgStudent; import org.dromara.pangu.student.domain.PgStudent;
import java.util.List;
/** /**
* 学生 Mapper 接口 * 学生 Mapper 接口
* *
* @author pangu * @author pangu
*/ */
public interface PgStudentMapper extends BaseMapperPlus<PgStudent, PgStudent> { public interface PgStudentMapper extends BaseMapperPlus<PgStudent, PgStudent> {
/**
* 查询学生列表带数据权限
*
* @param queryWrapper 查询条件
* @return 学生列表
*/
@DataPermission({
@DataColumn(key = "deptName", value = "dept_id")
})
default List<PgStudent> selectStudentList(Wrapper<PgStudent> queryWrapper) {
return this.selectList(queryWrapper);
}
/**
* 分页查询学生列表带数据权限
*
* @param page 分页信息
* @param queryWrapper 查询条件
* @return 学生分页列表
*/
@DataPermission({
@DataColumn(key = "deptName", value = "dept_id")
})
default Page<PgStudent> selectPageStudentList(Page<PgStudent> page, Wrapper<PgStudent> queryWrapper) {
return this.selectPage(page, queryWrapper);
}
} }

View File

@ -0,0 +1,16 @@
-- ============================================================
-- 脚本名称school_dept_permission.sql
-- 功能说明:学校数据权限支持 - 增加部门关联字段
-- 作 者pangu
-- 创建时间2026-02-03
-- ============================================================
-- 1. pg_school 表增加 dept_id 字段
ALTER TABLE pg_school ADD COLUMN dept_id BIGINT COMMENT '关联部门ID';
CREATE INDEX idx_school_dept ON pg_school(dept_id);
-- 2. 查看现有学校数据
SELECT school_id, school_name, school_code FROM pg_school;
-- 3. 查看现有部门数据
SELECT dept_id, parent_id, dept_name, ancestors FROM sys_dept;

View File

@ -27,6 +27,17 @@
<el-option label="完全中学" value="5" /> <el-option label="完全中学" value="5" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="上级部门" prop="deptId">
<el-tree-select
v-model="form.deptId"
:data="deptTree"
:props="{ value: 'id', label: 'label', children: 'children' }"
value-key="id"
placeholder="请选择上级部门(分公司)"
check-strictly
style="width: 100%"
/>
</el-form-item>
<el-form-item label="所属区域" prop="regionId"> <el-form-item label="所属区域" prop="regionId">
<el-cascader <el-cascader
v-model="form.regionIds" v-model="form.regionIds"
@ -66,8 +77,9 @@
* @author pangu * @author pangu
*/ */
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { computed, ref } from 'vue' import { computed, ref, onMounted } from 'vue'
import { addSchool, updateSchool } from '@/api/pangu/school' import { addSchool, updateSchool } from '@/api/pangu/school'
import { deptTreeSelect } from '@/api/system/user'
// //
const props = defineProps({ const props = defineProps({
@ -77,6 +89,9 @@ const props = defineProps({
} }
}) })
//
const deptTree = ref([])
const emit = defineEmits(['success']) const emit = defineEmits(['success'])
const dialogVisible = ref(false) const dialogVisible = ref(false)
@ -90,6 +105,7 @@ const form = ref({
schoolCode: '', schoolCode: '',
schoolName: '', schoolName: '',
schoolType: '', schoolType: '',
deptId: null,
regionId: null, regionId: null,
regionIds: [], regionIds: [],
regionName: '', regionName: '',
@ -107,6 +123,9 @@ const rules = {
schoolType: [ schoolType: [
{ required: true, message: '请选择学校类型', trigger: 'change' } { required: true, message: '请选择学校类型', trigger: 'change' }
], ],
deptId: [
{ required: true, message: '请选择上级部门', trigger: 'change' }
],
regionId: [ regionId: [
{ required: true, message: '请选择所属区域', trigger: 'change' } { required: true, message: '请选择所属区域', trigger: 'change' }
] ]
@ -140,13 +159,28 @@ const handleRegionChange = (value) => {
} }
} }
//
const loadDeptTree = async () => {
try {
const res = await deptTreeSelect()
if (res.code === 200) {
deptTree.value = res.data
}
} catch (error) {
console.error('加载部门树失败:', error)
}
}
// //
// row: null // row: null
// defaultRegionId: ID // defaultRegionId: ID
const open = (row, defaultRegionId = null) => { const open = async (row, defaultRegionId = null) => {
dialogVisible.value = true dialogVisible.value = true
isEdit.value = !!row isEdit.value = !!row
//
await loadDeptTree()
if (row) { if (row) {
// //
form.value = { form.value = {
@ -154,6 +188,7 @@ const open = (row, defaultRegionId = null) => {
schoolCode: row.schoolCode || '', schoolCode: row.schoolCode || '',
schoolName: row.schoolName, schoolName: row.schoolName,
schoolType: row.schoolType, schoolType: row.schoolType,
deptId: row.deptId,
regionId: row.regionId, regionId: row.regionId,
regionIds: getRegionIdPath(row.regionId), regionIds: getRegionIdPath(row.regionId),
regionName: row.regionName, regionName: row.regionName,
@ -167,6 +202,7 @@ const open = (row, defaultRegionId = null) => {
schoolCode: '', schoolCode: '',
schoolName: '', schoolName: '',
schoolType: '', schoolType: '',
deptId: null,
regionId: defaultRegionId, regionId: defaultRegionId,
regionIds: regionIds, regionIds: regionIds,
regionName: defaultRegionId ? getRegionPath(regionIds, props.regionTree) : '', regionName: defaultRegionId ? getRegionPath(regionIds, props.regionTree) : '',

View File

@ -61,7 +61,7 @@
<el-table-column label="用户编号" align="center" key="userId" prop="userId" v-if="columns.userId.visible" /> <el-table-column label="用户编号" align="center" key="userId" prop="userId" v-if="columns.userId.visible" />
<el-table-column label="用户名称" align="center" key="userName" prop="userName" v-if="columns.userName.visible" :show-overflow-tooltip="true" /> <el-table-column label="用户名称" align="center" key="userName" prop="userName" v-if="columns.userName.visible" :show-overflow-tooltip="true" />
<el-table-column label="用户昵称" align="center" key="nickName" prop="nickName" v-if="columns.nickName.visible" :show-overflow-tooltip="true" /> <el-table-column label="用户昵称" align="center" key="nickName" prop="nickName" v-if="columns.nickName.visible" :show-overflow-tooltip="true" />
<el-table-column label="部门" align="center" key="deptName" prop="dept.deptName" v-if="columns.deptName.visible" :show-overflow-tooltip="true" /> <el-table-column label="部门" align="center" key="deptName" prop="deptName" v-if="columns.deptName.visible" :show-overflow-tooltip="true" />
<el-table-column label="手机号码" align="center" key="phonenumber" prop="phonenumber" v-if="columns.phonenumber.visible" width="120" /> <el-table-column label="手机号码" align="center" key="phonenumber" prop="phonenumber" v-if="columns.phonenumber.visible" width="120" />
<el-table-column label="状态" align="center" key="status" v-if="columns.status.visible"> <el-table-column label="状态" align="center" key="status" v-if="columns.status.visible">
<template #default="scope"> <template #default="scope">
@ -504,8 +504,8 @@ function cancel() {
function handleAdd() { function handleAdd() {
reset() reset()
getUser().then(response => { getUser().then(response => {
postOptions.value = response.posts postOptions.value = response.data.posts
roleOptions.value = response.roles roleOptions.value = response.data.roles
open.value = true open.value = true
title.value = "添加用户" title.value = "添加用户"
form.value.password = initPassword.value form.value.password = initPassword.value
@ -517,11 +517,11 @@ function handleUpdate(row) {
reset() reset()
const userId = row.userId || ids.value const userId = row.userId || ids.value
getUser(userId).then(response => { getUser(userId).then(response => {
form.value = response.data form.value = response.data.user
postOptions.value = response.posts postOptions.value = response.data.posts
roleOptions.value = response.roles roleOptions.value = response.data.roles
form.value.postIds = response.postIds form.value.postIds = response.data.postIds
form.value.roleIds = response.roleIds form.value.roleIds = response.data.roleIds
open.value = true open.value = true
title.value = "修改用户" title.value = "修改用户"
form.value.password = "" form.value.password = ""