提交代码

This commit is contained in:
zlwu committed 2025-07-21 20:15:31 +08:00
1 parent 225a268e32
commit 7ce20647f0
28 files changed
+1451 -420

No files matched your search

@@ -19,6 +19,7 @@ import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.xg.domain.vo.XgDormAreaVo;
import org.dromara.xg.domain.bo.XgDormAreaBo;
import org.dromara.xg.domain.vo.TreeSelectVo;
import org.dromara.xg.service.IXgDormAreaService;
import org.dromara.common.mybatis.core.page.TableDataInfo;
@@ -63,8 +64,7 @@ public class XgDormAreaController extends BaseController {
*/
@SaCheckPermission("ssqxx:dormArea:query")
@GetMapping("/{id}")
public R<XgDormAreaVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
public R<XgDormAreaVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
return R.ok(xgDormAreaService.queryById(id));
}
@@ -98,8 +98,17 @@ public class XgDormAreaController extends BaseController {
@SaCheckPermission("ssqxx:dormArea:remove")
@Log(title = "宿舍区信息", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
return toAjax(xgDormAreaService.deleteWithValidByIds(List.of(ids), true));
}
/**
* 获取宿舍区树形结构数据
* 以校区为根节点,宿舍区为子节点
*/
@SaCheckPermission("ssqxx:dormArea:query")
@GetMapping("/tree")
public R<List<TreeSelectVo>> getAreaTree() {
return R.ok(xgDormAreaService.getAreaTree());
}
}
@@ -19,6 +19,7 @@ import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.xg.domain.vo.XgDormBuildingVo;
import org.dromara.xg.domain.bo.XgDormBuildingBo;
import org.dromara.xg.domain.vo.BuildingTreeSelectVo;
import org.dromara.xg.service.IXgDormBuildingService;
import org.dromara.common.mybatis.core.page.TableDataInfo;
@@ -63,8 +64,7 @@ public class XgDormBuildingController extends BaseController {
*/
@SaCheckPermission("sslxx:dormBuilding:query")
@GetMapping("/{id}")
public R<XgDormBuildingVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
public R<XgDormBuildingVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
return R.ok(xgDormBuildingService.queryById(id));
}
@@ -98,8 +98,18 @@ public class XgDormBuildingController extends BaseController {
@SaCheckPermission("sslxx:dormBuilding:remove")
@Log(title = "宿舍楼信息", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
return toAjax(xgDormBuildingService.deleteWithValidByIds(List.of(ids), true));
}
/**
* 获取宿舍楼树形结构数据
* 校区-宿舍区-宿舍楼三级结构,校区和宿舍区不可选,只有宿舍楼可以选择
*/
@SaCheckPermission("sslxx:dormBuilding:list")
@GetMapping("/buildingTree")
public R<List<BuildingTreeSelectVo>> getBuildingTree() {
List<BuildingTreeSelectVo> treeData = xgDormBuildingService.getBuildingTreeData();
return R.ok(treeData);
}
}
@@ -114,7 +114,21 @@ public class XgDormManagerSourceController extends BaseController {
@PostMapping("/assignScope")
public R<Void> assignScope(@RequestParam @NotNull(message = "管理员ID不能为空") Long managerId,
@RequestParam @NotNull(message = "授权类型不能为空") Character authType,
@RequestBody @NotEmpty(message = "授权来源ID不能为空") List<Long> authSourceIds) {
@RequestBody List<Long> authSourceIds) {
return toAjax(xgDormManagerSourceService.assignScope(managerId, authType, authSourceIds));
}
/**
* 获取管理员授权范围名称
*
* @param managerId 管理员ID
* @param managerType 管理员类型(1全校级 2校区级 3宿舍区级 4楼栋级)
* @return 授权范围名称(逗号分隔字符串)
*/
@SaCheckPermission("glysqgx:dormManagerSource:query")
@GetMapping("/getScopeNames")
public R<String> getScopeNames(@RequestParam @NotNull(message = "管理员ID不能为空") Long managerId,
@RequestParam @NotNull(message = "管理员类型不能为空") Integer managerType) {
return R.ok(xgDormManagerSourceService.getScopeNames(managerId, managerType));
}
}
@@ -63,5 +63,4 @@ public class XgDormManagerSourceBo extends BaseEntity {
*/
private String remark;
}
@@ -0,0 +1,65 @@
package org.dromara.xg.domain.vo;
import java.io.Serializable;
import java.util.List;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 宿舍楼树形结构视图对象
*
* @author zlwu
* @date 2025-07-20
*/
@Data
@NoArgsConstructor
public class BuildingTreeSelectVo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 节点ID
*/
private Long id;
/**
* 节点名称
*/
private String label;
/**
* 节点类型(campus:校区 area:宿舍区 building:宿舍楼)
*/
private String nodeType;
/**
* 是否禁用
*/
private Boolean disabled;
/**
* 子节点
*/
private List<BuildingTreeSelectVo> children;
/**
* 创建者
*/
private Long createBy;
/**
* 创建时间
*/
private java.util.Date createTime;
/**
* 更新者
*/
private Long updateBy;
/**
* 更新时间
*/
private java.util.Date updateTime;
}
@@ -0,0 +1,63 @@
package org.dromara.xg.domain.vo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* 树形选择视图对象
*
* @author zlwu
* @date 2025-07-20
*/
@Data
@NoArgsConstructor
public class TreeSelectVo implements Serializable {
private static final long serialVersionUID = 1L;
/** 节点ID */
private Long id;
/** 节点名称 */
private String label;
/** 节点类型(campus:校区, area:宿舍区) */
private String type;
/** 是否可选择 */
private Boolean disabled;
/** 子节点 */
private List<TreeSelectVo> children = new ArrayList<>();
/**
* 构造树节点
*
* @param id 节点ID
* @param label 节点标签
* @param type 节点类型
* @param disabled 是否禁用
*/
public TreeSelectVo(Long id, String label, String type, Boolean disabled) {
this.id = id;
this.label = label;
this.type = type;
this.disabled = disabled;
}
/**
* 添加子节点
*
* @param child 子节点
*/
public void addChild(TreeSelectVo child) {
if (children == null) {
children = new ArrayList<>();
}
children.add(child);
}
}
@@ -12,8 +12,6 @@ import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 管理员授权关系视图对象 xg_dorm_manager_source
*
@@ -76,5 +74,4 @@ public class XgDormManagerSourceVo implements Serializable {
@ExcelProperty(value = "备注")
private String remark;
}
@@ -1,9 +1,12 @@
package org.dromara.xg.mapper;
import org.apache.ibatis.annotations.Param;
import org.dromara.xg.domain.XgDormManagerSource;
import org.dromara.xg.domain.vo.XgDormManagerSourceVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import java.util.List;
/**
* 管理员授权关系Mapper接口
*
@@ -12,4 +15,27 @@ import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
*/
public interface XgDormManagerSourceMapper extends BaseMapperPlus<XgDormManagerSource, XgDormManagerSourceVo> {
/**
* 根据ID列表获取校区名称(逗号分隔)
*
* @param sourceIds 校区ID列表
* @return 校区名称,多个以逗号分隔
*/
String getCampusNamesByIds(@Param("sourceIds") List<Long> sourceIds);
/**
* 根据ID列表获取宿舍区名称(逗号分隔)
*
* @param sourceIds 宿舍区ID列表
* @return 宿舍区名称,多个以逗号分隔
*/
String getAreaNamesByIds(@Param("sourceIds") List<Long> sourceIds);
/**
* 根据ID列表获取宿舍楼名称(逗号分隔)
*
* @param sourceIds 宿舍楼ID列表
* @return 宿舍楼名称,多个以逗号分隔
*/
String getBuildingNamesByIds(@Param("sourceIds") List<Long> sourceIds);
}
@@ -4,6 +4,7 @@ import org.dromara.xg.domain.vo.XgDormAreaVo;
import org.dromara.xg.domain.bo.XgDormAreaBo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.xg.domain.vo.TreeSelectVo;
import java.util.Collection;
import java.util.List;
@@ -65,4 +66,12 @@ public interface IXgDormAreaService {
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 获取宿舍区树形结构数据
* 以校区为根节点,宿舍区为子节点
*
* @return 宿舍区树形结构列表
*/
List<TreeSelectVo> getAreaTree();
}
@@ -2,6 +2,7 @@ package org.dromara.xg.service;
import org.dromara.xg.domain.vo.XgDormBuildingVo;
import org.dromara.xg.domain.bo.XgDormBuildingBo;
import org.dromara.xg.domain.vo.BuildingTreeSelectVo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
@@ -65,4 +66,12 @@ public interface IXgDormBuildingService {
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 获取宿舍楼树形结构数据
* 校区-宿舍区-宿舍楼三级结构,校区和宿舍区不可选,只有宿舍楼可以选择
*
* @return 树形结构数据
*/
List<BuildingTreeSelectVo> getBuildingTreeData();
}
@@ -18,51 +18,31 @@ public interface IXgDormManagerSourceService {
/**
* 查询管理员授权关系
*
* @param id 主键
* @return 管理员授权关系
*/
XgDormManagerSourceVo queryById(Long id);
/**
* 分页查询管理员授权关系列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 管理员授权关系分页列表
* 查询管理员授权关系列表
*/
TableDataInfo<XgDormManagerSourceVo> queryPageList(XgDormManagerSourceBo bo, PageQuery pageQuery);
/**
* 查询符合条件的管理员授权关系列表
*
* @param bo 查询条件
* @return 管理员授权关系列表
* 查询管理员授权关系列表
*/
List<XgDormManagerSourceVo> queryList(XgDormManagerSourceBo bo);
/**
* 新增管理员授权关系
*
* @param bo 管理员授权关系
* @return 是否新增成功
*/
Boolean insertByBo(XgDormManagerSourceBo bo);
/**
* 修改管理员授权关系
*
* @param bo 管理员授权关系
* @return 是否修改成功
*/
Boolean updateByBo(XgDormManagerSourceBo bo);
/**
* 校验并批量删除管理员授权关系信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
@@ -72,7 +52,16 @@ public interface IXgDormManagerSourceService {
* @param managerId 管理员ID
* @param authType 授权类型(1全校级 2校区级 3宿舍区级 4楼栋级)
* @param authSourceIds 授权来源ID列表
* @return 是否分配成功
* @return 结果
*/
Boolean assignScope(Long managerId, Character authType, List<Long> authSourceIds);
/**
* 获取管理员授权范围名称
*
* @param managerId 管理员ID
* @param managerType 管理员类型(1全校级 2校区级 3宿舍区级 4楼栋级)
* @return 授权范围名称(逗号分隔字符串)
*/
String getScopeNames(Long managerId, Integer managerType);
}
@@ -11,14 +11,20 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.dromara.xg.domain.bo.XgDormAreaBo;
import org.dromara.xg.domain.bo.XgDormCampusBo;
import org.dromara.xg.domain.vo.XgDormAreaVo;
import org.dromara.xg.domain.vo.XgDormCampusVo;
import org.dromara.xg.domain.XgDormArea;
import org.dromara.xg.domain.vo.TreeSelectVo;
import org.dromara.xg.mapper.XgDormAreaMapper;
import org.dromara.xg.service.IXgDormAreaService;
import org.dromara.xg.service.IXgDormCampusService;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Collection;
import java.util.stream.Collectors;
/**
* 宿舍区信息Service业务层处理
@@ -32,6 +38,7 @@ import java.util.Collection;
public class XgDormAreaServiceImpl implements IXgDormAreaService {
private final XgDormAreaMapper baseMapper;
private final IXgDormCampusService dormCampusService;
/**
* 查询宿舍区信息
@@ -131,4 +138,56 @@ public class XgDormAreaServiceImpl implements IXgDormAreaService {
}
return baseMapper.deleteByIds(ids) > 0;
}
/**
* 获取宿舍区树形结构数据
* 以校区为根节点,宿舍区为子节点
*
* @return 宿舍区树形结构列表
*/
@Override
public List<TreeSelectVo> getAreaTree() {
// 1. 获取所有校区数据
List<XgDormCampusVo> campusList = dormCampusService.queryList(new XgDormCampusBo());
// 2. 获取所有宿舍区数据
List<XgDormAreaVo> areaList = queryList(new XgDormAreaBo());
// 3. 构建树形结构
List<TreeSelectVo> treeList = new ArrayList<>();
// 4. 创建校区节点(校区节点不可选择)
for (XgDormCampusVo campus : campusList) {
// 校区节点不可选择
TreeSelectVo campusNode = new TreeSelectVo(
campus.getId(),
campus.getCampusName(),
"campus",
true // 禁用校区节点,不可选择
);
// 5. 添加对应校区的宿舍区节点
List<XgDormAreaVo> campusAreas = areaList.stream()
.filter(area -> campus.getId().equals(area.getCampusId()))
.collect(Collectors.toList());
for (XgDormAreaVo area : campusAreas) {
// 宿舍区节点可选择
TreeSelectVo areaNode = new TreeSelectVo(
area.getId(),
area.getAreaName(),
"area",
false // 不禁用宿舍区节点,可以选择
);
campusNode.addChild(areaNode);
}
// 只有包含子节点的校区才添加到树中
if (!campusNode.getChildren().isEmpty()) {
treeList.add(campusNode);
}
}
return treeList;
}
}
@@ -13,12 +13,19 @@ import org.springframework.stereotype.Service;
import org.dromara.xg.domain.bo.XgDormBuildingBo;
import org.dromara.xg.domain.vo.XgDormBuildingVo;
import org.dromara.xg.domain.XgDormBuilding;
import org.dromara.xg.domain.XgDormArea;
import org.dromara.xg.domain.XgDormCampus;
import org.dromara.xg.domain.vo.BuildingTreeSelectVo;
import org.dromara.xg.mapper.XgDormBuildingMapper;
import org.dromara.xg.mapper.XgDormAreaMapper;
import org.dromara.xg.mapper.XgDormCampusMapper;
import org.dromara.xg.service.IXgDormBuildingService;
import java.util.List;
import java.util.Map;
import java.util.Collection;
import java.util.ArrayList;
import java.util.stream.Collectors;
/**
* 宿舍楼信息Service业务层处理
@@ -32,6 +39,8 @@ import java.util.Collection;
public class XgDormBuildingServiceImpl implements IXgDormBuildingService {
private final XgDormBuildingMapper baseMapper;
private final XgDormAreaMapper areaMapper;
private final XgDormCampusMapper campusMapper;
/**
* 查询宿舍楼信息
@@ -133,4 +142,95 @@ public class XgDormBuildingServiceImpl implements IXgDormBuildingService {
}
return baseMapper.deleteByIds(ids) > 0;
}
/**
* 获取宿舍楼树形结构数据
* 校区-宿舍区-宿舍楼三级结构,校区和宿舍区不可选,只有宿舍楼可以选择
*
* @return 宿舍楼树形结构数据
*/
@Override
public List<BuildingTreeSelectVo> getBuildingTreeData() {
// 1. 获取所有校区数据
List<XgDormCampus> campusList = campusMapper.selectList(
Wrappers.<XgDormCampus>lambdaQuery()
.orderByAsc(XgDormCampus::getSortOrder, XgDormCampus::getId));
// 2. 获取所有宿舍区数据
List<XgDormArea> areaList = areaMapper.selectList(
Wrappers.<XgDormArea>lambdaQuery()
.orderByAsc(XgDormArea::getSortOrder, XgDormArea::getId));
// 3. 获取所有宿舍楼数据
List<XgDormBuilding> buildingList = baseMapper.selectList(
Wrappers.<XgDormBuilding>lambdaQuery()
.orderByAsc(XgDormBuilding::getSortOrder, XgDormBuilding::getId));
// 4. 构建树形结构
List<BuildingTreeSelectVo> treeList = new ArrayList<>();
// 5. 先构建校区节点
for (XgDormCampus campus : campusList) {
BuildingTreeSelectVo campusNode = new BuildingTreeSelectVo();
campusNode.setId(campus.getId());
campusNode.setLabel(campus.getCampusName());
campusNode.setNodeType("campus");
campusNode.setDisabled(true); // 校区不可选
campusNode.setChildren(new ArrayList<>());
// 设置创建者和更新者字段
campusNode.setCreateBy(campus.getCreateBy());
campusNode.setCreateTime(campus.getCreateTime());
campusNode.setUpdateBy(campus.getUpdateBy());
campusNode.setUpdateTime(campus.getUpdateTime());
// 添加该校区下的宿舍区
List<XgDormArea> campusAreas = areaList.stream()
.filter(area -> area.getCampusId().equals(campus.getId()))
.collect(Collectors.toList());
for (XgDormArea area : campusAreas) {
BuildingTreeSelectVo areaNode = new BuildingTreeSelectVo();
areaNode.setId(area.getId());
areaNode.setLabel(area.getAreaName());
areaNode.setNodeType("area");
areaNode.setDisabled(true); // 宿舍区不可选
areaNode.setChildren(new ArrayList<>());
// 设置创建者和更新者字段
areaNode.setCreateBy(area.getCreateBy());
areaNode.setCreateTime(area.getCreateTime());
areaNode.setUpdateBy(area.getUpdateBy());
areaNode.setUpdateTime(area.getUpdateTime());
// 添加该宿舍区下的宿舍楼
List<XgDormBuilding> areaBuildings = buildingList.stream()
.filter(building -> building.getAreaId().equals(area.getId()))
.collect(Collectors.toList());
for (XgDormBuilding building : areaBuildings) {
BuildingTreeSelectVo buildingNode = new BuildingTreeSelectVo();
buildingNode.setId(building.getId());
buildingNode.setLabel(building.getBuildingName());
buildingNode.setNodeType("building");
buildingNode.setDisabled(false); // 宿舍楼可选
buildingNode.setChildren(null); // 宿舍楼没有子节点
// 设置创建者和更新者字段
buildingNode.setCreateBy(building.getCreateBy());
buildingNode.setCreateTime(building.getCreateTime());
buildingNode.setUpdateBy(building.getUpdateBy());
buildingNode.setUpdateTime(building.getUpdateTime());
areaNode.getChildren().add(buildingNode);
}
campusNode.getChildren().add(areaNode);
}
treeList.add(campusNode);
}
return treeList;
}
}
@@ -22,6 +22,8 @@ import java.util.List;
import java.util.Map;
import java.util.Collection;
import java.util.ArrayList;
import java.util.Date;
import java.util.stream.Collectors;
/**
* 管理员授权关系Service业务层处理
@@ -136,7 +138,7 @@ public class XgDormManagerSourceServiceImpl implements IXgDormManagerSourceServi
if (isValid) {
// TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteByIds(ids) > 0;
return baseMapper.deleteBatchIds(ids) > 0;
}
/**
@@ -150,42 +152,102 @@ public class XgDormManagerSourceServiceImpl implements IXgDormManagerSourceServi
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean assignScope(Long managerId, Character authType, List<Long> authSourceIds) {
if (managerId == null || authType == null || authSourceIds == null || authSourceIds.isEmpty()) {
if (managerId == null || authType == null) {
return false;
}
try {
// 1. 先逻辑删除该管理员现有的对应类型的授权数据
// 1. 逻辑删除该管理员指定授权类型的现有数据
LambdaUpdateWrapper<XgDormManagerSource> updateWrapper = Wrappers.lambdaUpdate(XgDormManagerSource.class)
.set(XgDormManagerSource::getDeleted, "1")
.set(XgDormManagerSource::getDeleted, "1") // 标记为已删除
.eq(XgDormManagerSource::getManagerId, managerId)
.eq(XgDormManagerSource::getAuthType, authType.toString())
.eq(XgDormManagerSource::getDeleted, "0");
.eq(XgDormManagerSource::getDeleted, "0"); // 只更新未删除的记录
baseMapper.update(null, updateWrapper);
// 2. 批量插入新的授权数据
List<XgDormManagerSource> insertList = new ArrayList<>();
for (Long authSourceId : authSourceIds) {
XgDormManagerSource entity = new XgDormManagerSource();
entity.setManagerId(managerId);
entity.setAuthType(authType.toString());
entity.setAuthSource(authSourceId);
entity.setPermissionType("3"); // 默认为完全管理权限
entity.setStatus("0"); // 默认有效
entity.setDeleted("0"); // 默认未删除
insertList.add(entity);
}
// 循环插入,确保每条数据都有唯一ID
for (XgDormManagerSource entity : insertList) {
baseMapper.insert(entity);
// 2. 批量插入新的授权关系
if (authSourceIds != null && !authSourceIds.isEmpty()) {
for (Long authSourceId : authSourceIds) {
XgDormManagerSource entity = new XgDormManagerSource();
entity.setManagerId(managerId);
entity.setAuthType(authType.toString());
entity.setAuthSource(authSourceId);
entity.setPermissionType("3"); // 默认完全管理权限
entity.setStatus("0"); // 有效状态
baseMapper.insert(entity); // MyBatis-Plus会自动填充BaseEntity中定义的字段
}
}
return true;
} catch (Exception e) {
log.error("分配管理员数据范围失败", e);
log.error("批量分配管理员数据范围失败", e);
throw e;
}
}
/**
* 获取管理员授权范围名称
*
* @param managerId 管理员ID
* @param managerType 管理员类型(1全校级 2校区级 3宿舍区级 4楼栋级)
* @return 授权范围名称(逗号分隔字符串)
*/
@Override
public String getScopeNames(Long managerId, Integer managerType) {
if (managerId == null || managerType == null) {
return "";
}
try {
// 全校级管理员无需关联其他表,直接返回"全校"
if (managerType == 1) {
return "全校";
}
// 转换managerType为Character类型,与数据库authType匹配
String authType = String.valueOf(managerType);
// 查询该管理员的授权范围ID列表
LambdaQueryWrapper<XgDormManagerSource> queryWrapper = Wrappers.lambdaQuery(XgDormManagerSource.class)
.eq(XgDormManagerSource::getManagerId, managerId)
.eq(XgDormManagerSource::getAuthType, authType)
.eq(XgDormManagerSource::getDeleted, "0") // 未删除的记录
.eq(XgDormManagerSource::getStatus, "0"); // 状态为有效
List<XgDormManagerSource> sources = baseMapper.selectList(queryWrapper);
if (sources.isEmpty()) {
return "";
}
// 提取授权来源ID列表
List<Long> sourceIds = sources.stream()
.map(XgDormManagerSource::getAuthSource)
.collect(Collectors.toList());
// 根据不同管理员类型,关联不同表获取名称
String scopeNames = "";
switch (managerType) {
case 2:
// 校区级管理员,关联xg_dorm_campus表
scopeNames = baseMapper.getCampusNamesByIds(sourceIds);
break;
case 3:
// 宿舍区管理员,关联xg_dorm_area表
scopeNames = baseMapper.getAreaNamesByIds(sourceIds);
break;
case 4:
// 宿舍楼管理员,关联xg_dorm_building表
scopeNames = baseMapper.getBuildingNamesByIds(sourceIds);
break;
default:
return "";
}
return scopeNames;
} catch (Exception e) {
log.error("获取管理员授权范围名称失败", e);
return "";
}
}
}
@@ -4,4 +4,45 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.xg.mapper.XgDormManagerSourceMapper">
<!-- 根据ID列表获取校区名称(逗号分隔) -->
<select id="getCampusNamesByIds" resultType="String">
SELECT
GROUP_CONCAT(campus_name SEPARATOR ',')
FROM
xg_dorm_campus
WHERE
id IN
<foreach collection="sourceIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
AND deleted = '0'
</select>
<!-- 根据ID列表获取宿舍区名称(逗号分隔) -->
<select id="getAreaNamesByIds" resultType="String">
SELECT
GROUP_CONCAT(area_name SEPARATOR ',')
FROM
xg_dorm_area
WHERE
id IN
<foreach collection="sourceIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
AND deleted = '0'
</select>
<!-- 根据ID列表获取宿舍楼名称(逗号分隔) -->
<select id="getBuildingNamesByIds" resultType="String">
SELECT
GROUP_CONCAT(building_name SEPARATOR ',')
FROM
xg_dorm_building
WHERE
id IN
<foreach collection="sourceIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
AND deleted = '0'
</select>
</mapper>
@@ -0,0 +1,68 @@
# 宿舍楼管理API树形结构开发记录
**时间**: 2025-07-20 15:30:00
**开发人员**: zlwu
**变更模块**: 宿舍管理 - 宿舍楼树形结构API
## 变更内容
### 1. 新增类文件
1. 新增 `BuildingTreeSelectVo.java` 类用于宿舍楼树形结构展示
- 支持校区-宿舍区-宿舍楼三级结构
- 标识节点类型和可选状态
### 2. API接口开发
1. 在 `XgDormBuildingController` 中新增 `/buildingTree` 接口:
- 获取完整的宿舍楼树形结构
- 校区和宿舍区节点设置为不可选(disabled=true)
- 宿舍楼节点设置为可选(disabled=false)
2. 在 `IXgDormBuildingService` 接口中添加 `getBuildingTreeData()` 方法
3. 在 `XgDormBuildingServiceImpl` 中实现树形数据构建逻辑:
- 查询所有校区、宿舍区和宿舍楼数据
- 构建三级层次结构
- 确保树中校区和宿舍区节点被标记为不可选
- 只有宿舍楼节点可以被选择
### 3. 数据表关联关系
- `xg_dorm_building` 通过 `campus_id` 关联 `xg_dorm_campus`
- `xg_dorm_building` 通过 `area_id` 关联 `xg_dorm_area`
- `xg_dorm_area` 通过 `campus_id` 关联 `xg_dorm_campus`
### 4. 前端实现
1. 在`web/src/api/sslxx/dormBuilding/types.ts`中添加`BuildingTreeSelectVo`接口定义:
- 定义了树形节点的属性类型
- 包含id、label、nodeType、disabled等核心字段
- 支持children属性用于嵌套结构
2. 在`web/src/api/sslxx/dormBuilding/index.ts`中添加`getBuildingTree`方法:
- 调用后端API获取树形结构数据
- 方法返回类型为`AxiosPromise<BuildingTreeSelectVo[]>`
3. 修改`ScopeDialog.vue`组件:
- 为宿舍楼类型(type="building")添加独立的树形选择器
- 移除原有的模拟数据,改用API获取真实数据
- 添加宿舍楼数据回显和解析逻辑
- 保留对校区和宿舍区的原有支持
## 技术实现
树形结构数据构建采用三步查询 + 组装的方式:
1. 首先查询所有校区数据
2. 查询所有宿舍区数据
3. 查询所有宿舍楼数据
4. 通过Stream API过滤和关联数据,构建多层嵌套结构
## 使用说明
1. 校区管理员授权时,使用普通多选下拉框
2. 宿舍区管理员授权时,使用以校区为父节点的两级树形结构
3. 宿舍楼管理员授权时,使用新开发的三级树形结构(校区-宿舍区-宿舍楼)
- 校区和宿舍区节点自动禁用,不可选择
- 只有宿舍楼节点可以选择
- 支持多选和折叠标签
+29 -170
View File
@@ -1,189 +1,48 @@
# API 文档总览
# API文档总览
## 📋 文档说明
本文件作为API文档的统一导航入口,提供项目中所有API文档的快速访问链接。
本文档是校园管理系统 API 接口的总览和导航页面。为了提高文档的可维护性和可读性,API 文档已按业务模块进行拆分。
## 认证登录
**拆分完成时间**: 2025-07-01
**拆分目的**: 按业务模块管理,便于维护和查找,减少单个文件过大的问题
- [用户认证登录API](./用户认证登录API.md) - 用户登录、获取用户信息等接口
---
## 学工管理模块
- [班干部管理API](班干部管理API.md)
- [宿舍管理员授权API](宿舍管理员授权API.md)
- [宿舍楼树形结构API](宿舍楼树形结构API.md) - 校区-宿舍区-宿舍楼三级树形结构数据
## 📊 API 文档架构
## 后勤管理
### 🔗 分模块 API 文档
### 场地管理
| 文档名称 | 文件路径 | 主要内容 | 适用场景 |
| ------------------------- | ----------------------------------------------- | ---------------------------------------------- | -------------------- |
| **📋 API 文档基础规范** | [API 文档基础规范.md](./API文档基础规范.md) | 请求头规范、响应格式、状态码、错误码、数据字典 | **所有开发人员必读** |
| **🔐 用户认证登录 API** | [用户认证登录 API.md](./用户认证登录API.md) | 用户登录、退出、验证码、用户信息、租户管理 | 认证相关开发 |
| **🛠️ 物业报修管理 API** | [物业报修管理 API.md](./物业报修管理API.md) | 维修分类、报修工单、进度跟踪、服务评价 | 物业报修功能开发 |
| **📊 后勤满意度调查 API** | [后勤满意度调查 API.md](./后勤满意度调查API.md) | 问卷管理、答题、题目管理、答题记录 | 满意度调查功能开发 |
| **🏢 场地预约管理 API** | [场地预约管理 API.md](./场地预约管理API.md) | 场地信息管理、预约申请、审批流程、冲突检查 | 场地预约功能开发 |
| **🎭 报告厅预约审核 API** | [报告厅预约审核 API.md](./报告厅预约审核API.md) | 审核建议生成、时间冲突检查、批量审核处理 | 报告厅审核功能开发 |
- [场地预约管理API](./场地预约管理API.md) - 场地信息管理、预约申请管理接口
- [报告厅预约审核API](./报告厅预约审核API.md) - 报告厅信息管理、预约审核接口
### 📝 阅读建议
### 物业管理
1. **首次阅读**: 先阅读 [API 文档基础规范.md](./API文档基础规范.md) 了解通用规范
2. **业务开发**: 根据开发的功能模块选择对应的 API 文档
3. **交叉引用**: 各模块文档间有交叉引用,便于快速查找相关接口
- [物业报修管理API](./物业报修管理API.md) - 报修工单管理、维修处理接口
---
### 满意度调查
## 🎯 快速导航
- [后勤满意度调查API](./后勤满意度调查API.md) - 问卷设计、答题管理接口
### 🔍 按功能查找接口
## 工作流管理
#### 🔐 用户认证相关
- [请假流程API](./请假流程API.md) - 请假申请、审批管理接口
- [用户登录](./用户认证登录API.md#用户登录) - AES+RSA 加密登录
- [退出登录](./用户认证登录API.md#退出登录) - 清除会话
- [获取验证码](./用户认证登录API.md#获取验证码) - 图形验证码
- [获取用户信息](./用户认证登录API.md#获取当前用户信息) - 用户详情和权限
- [租户管理](./用户认证登录API.md#租户管理) - 多租户支持
## 系统管理
#### 🛠️ 物业报修相关
- [菜单权限API](./菜单权限API.md) - 菜单配置、权限管理接口
- [用户管理API](./用户管理API.md) - 系统用户、角色、部门管理接口
- [维修分类管理](./物业报修管理API.md#维修分类管理) - 分类 CRUD 操作
- [报修工单管理](./物业报修管理API.md#报修工单管理) - 工单生命周期
- [工单进度跟踪](./物业报修管理API.md#获取工单进度列表) - 进度记录
- [服务评价](./物业报修管理API.md#服务评价管理) - 评价系统
## API基础规范
#### 📊 满意度调查相关
- [API文档基础规范](./API文档基础规范.md) - API接口设计规范、响应格式规范
- [问卷管理](./后勤满意度调查API.md#问卷基础管理) - 问卷 CRUD 操作
- [答题功能](./后勤满意度调查API.md#答题API) - 答题提交和查询
- [题目管理](./后勤满意度调查API.md#题目管理API) - 题目和选项
- [答题记录](./后勤满意度调查API.md#答题记录管理API) - 记录查询
## 更新记录
#### 🏢 场地预约相关
- [场地信息管理](./场地预约管理API.md#场地信息管理api) - 场地 CRUD 操作
- [场地预约申请](./场地预约管理API.md#场地预约管理api) - 预约提交和修改
- [预约审批](./场地预约管理API.md#审批场地预约) - 审批流程管理
- [时间冲突检查](./场地预约管理API.md#检查时间冲突) - 预约冲突检测
#### 🎭 报告厅审核相关
- [报告厅预约查询](./报告厅预约审核API.md#报告厅预约查询-api) - 预约列表和详情查询
- [时间冲突检查](./报告厅预约审核API.md#时间冲突检查-api) - 智能冲突检测
- [批量审核处理](./报告厅预约审核API.md#报告厅预约审核-api) - 批准/拒绝预约申请
- [审核建议生成](./报告厅预约审核API.md#时间冲突检查-api) - 基于冲突检查的智能建议
### 🔍 按开发阶段查找
#### 🚀 项目初期
1. [API 文档基础规范.md](./API文档基础规范.md) - 了解通用规范
2. [用户认证登录 API.md](./用户认证登录API.md) - 实现用户认证
3. 选择业务模块文档进行开发
#### 🛠️ 功能开发
- 根据具体功能模块查找对应 API 文档
- 参考接口示例和错误码说明
- 使用数据字典了解枚举值含义
#### 🧪 接口测试
- 参考各文档中的请求示例
- 使用提供的测试脚本
- 查看错误码说明进行问题排查
---
## 📋 通用信息
### 🔧 基础配置
- **基础路径**: `/api`
- **响应格式**: JSON
- **字符编码**: UTF-8
- **权限认证**: Sa-Token + JWT
- **版本**: v1.0
- **🌐 在线 API 文档**: [http://localhost:8080/swagger-ui/index.html](http://localhost:8080/swagger-ui/index.html) ⭐
### 📊 API 文档查看方式
1. **🌟 Swagger UI 在线文档** (推荐)
- **访问地址**: http://localhost:8080/swagger-ui/index.html
- **特点**: 可视化界面,支持在线测试,按模块分组
- **使用场景**: 接口调试、快速查看、在线测试
2. **📁 项目内置文档** (详细)
- **位置**: `docs/开发文档/API/`
- **特点**: 中文详细说明,业务逻辑解释,完整示例
- **使用场景**: 深入了解业务逻辑、开发参考
### 🔐 认证要求
所有需要认证的接口都必须在请求头中携带:
```json
{
"Authorization": "Bearer {access_token}",
"clientid": "e5cd7e4891bf95d1d19206ce24a7b32e",
"Content-Type": "application/json;charset=utf-8"
}
```
详细说明请参考:[API 文档基础规范.md](./API文档基础规范.md#请求头规范强制性)
### 📊 响应格式
- **分页列表**: TableDataInfo 格式 (total, rows, code, msg)
- **非分页接口**: R 类型格式 (code, msg, data)
- **错误响应**: 统一错误格式
详细说明请参考:[API 文档基础规范.md](./API文档基础规范.md#统一响应格式强制性)
---
## 🔄 文档维护
### 📝 更新规则
1. **基础规范变更**: 更新 [API 文档基础规范.md](./API文档基础规范.md)
2. **认证相关变更**: 更新 [用户认证登录 API.md](./用户认证登录API.md)
3. **物业报修变更**: 更新 [物业报修管理 API.md](./物业报修管理API.md)
4. **满意度调查变更**: 更新 [后勤满意度调查 API.md](./后勤满意度调查API.md)
5. **总览更新**: 根据需要更新本文档
### 🚨 强制要求
- **同步更新**: 代码变更时必须同步更新对应的 API 文档
- **向下兼容**: 新版本 API 必须考虑向下兼容性
- **版本记录**: 重大变更需要在相应文档中记录版本信息
---
## 📞 联系方式
如有 API 文档相关问题,请联系开发团队或查看项目仓库的 Issues。
---
## 📈 版本历史
### v2.0.0 (2025-07-01) - API 文档拆分重构
- 🔄 **重大架构调整**: API 文档按业务模块拆分
- 📋 **新增基础规范文档**: 统一管理通用规范
- 🔐 **独立认证 API 文档**: 用户认证登录专门文档
- 🛠️ **物业报修 API 文档**: 报修管理专门文档
- 📊 **满意度调查 API 文档**: 问卷答题专门文档
- 📝 **中文文件命名**: 便于快速识别和查找
- 🔗 **交叉引用系统**: 各文档间建立引用关系
- 📚 **总览导航**: 提供统一的文档入口
### v1.x.x (2025-01-26 及之前)
参考各个拆分文档中的版本历史记录。
---
**📝 维护**: API 文档总览和导航
**🔄 更新**: 2025-07-01
**📁 架构**: 4 个专门 API 文档 + 1 个总览文档
**🎯 目标**: 提供清晰的 API 文档导航和管理体系
- 2025-07-20:新增宿舍楼树形结构API文档
- 2025-07-20:新增宿舍管理员授权API文档
- 2025-07-15:新增场地预约管理API文档
- 2025-07-10:新增物业报修管理API文档
- 2025-07-01:重组API文档结构,创建统一导航入口
@@ -0,0 +1,174 @@
# 宿舍楼树形结构API文档
## 接口说明
本文档描述了获取宿舍楼树形结构数据的API接口,该接口返回校区-宿舍区-宿舍楼的三级树形结构,其中校区和宿舍区节点不可选,只有宿舍楼节点可以选择。
## 接口定义
### 获取宿舍楼树形结构
**请求URL**:`/sslxx/dormBuilding/buildingTree`
**请求方式**:`GET`
**权限标识**:`sslxx:dormBuilding:list`
**请求参数**:无
**响应示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": [
{
"id": 1,
"label": "东校区",
"nodeType": "campus",
"disabled": true,
"children": [
{
"id": 101,
"label": "东区宿舍区",
"nodeType": "area",
"disabled": true,
"children": [
{
"id": 1001,
"label": "1号楼",
"nodeType": "building",
"disabled": false
},
{
"id": 1002,
"label": "2号楼",
"nodeType": "building",
"disabled": false
}
]
}
]
},
{
"id": 2,
"label": "西校区",
"nodeType": "campus",
"disabled": true,
"children": [
{
"id": 201,
"label": "西区宿舍区",
"nodeType": "area",
"disabled": true,
"children": [
{
"id": 2001,
"label": "3号楼",
"nodeType": "building",
"disabled": false
},
{
"id": 2002,
"label": "4号楼",
"nodeType": "building",
"disabled": false
}
]
}
]
}
]
}
```
## 数据结构
### BuildingTreeSelectVo
树形结构节点对象:
| 字段名 | 类型 | 描述 |
|------------|-------------------|----------------------------------------|
| id | Long | 节点ID |
| label | String | 节点名称 |
| nodeType | String | 节点类型:campus(校区)、area(宿舍区)、building(宿舍楼) |
| disabled | Boolean | 是否禁用选择 |
| children | List<BuildingTreeSelectVo> | 子节点列表 |
| createBy | Long | 创建者 |
| createTime | Date | 创建时间 |
| updateBy | Long | 更新者 |
| updateTime | Date | 更新时间 |
## 使用方法
### 前端使用示例
在Vue组件中使用该接口获取树形数据:
```typescript
import { getBuildingTree } from '@/api/sslxx/dormBuilding';
import type { BuildingTreeSelectVo } from '@/api/sslxx/dormBuilding/types';
// 在组件的setup函数中
const treeData = ref<BuildingTreeSelectVo[]>([]);
// 加载宿舍楼树形数据
const loadBuildingTree = async () => {
try {
const res = await getBuildingTree();
if (res && res.data) {
treeData.value = res.data;
}
} catch (error) {
console.error('加载宿舍楼树形数据失败:', error);
}
};
// 调用加载方法
loadBuildingTree();
```
### 使用el-tree-select组件展示
```html
<el-tree-select
v-model="selectedBuildings"
placeholder="请选择宿舍楼"
style="width: 100%"
clearable
filterable
multiple
collapse-tags
collapse-tags-tooltip
:data="treeData"
:props="{
children: 'children',
label: 'label',
disabled: 'disabled',
value: 'id'
}"
check-strictly
/>
```
## 特殊说明
1. 接口返回的树形结构中,校区和宿舍区节点已设置 `disabled=true`,前端无需额外处理即可实现只能选择宿舍楼节点的效果
2. 使用 `check-strictly=true` 可以确保树形选择器的选中状态不会级联影响父子节点
3. 该接口已支持管理员分配宿舍楼管理范围的业务需求
## 相关API接口
- [宿舍管理员授权API](./宿舍管理员授权API.md)
## 数据库关系
- `xg_dorm_building` 表通过 `campus_id` 字段关联 `xg_dorm_campus` 表
- `xg_dorm_building` 表通过 `area_id` 字段关联 `xg_dorm_area` 表
- `xg_dorm_area` 表通过 `campus_id` 字段关联 `xg_dorm_campus` 表
## 最后更新时间
2025-07-20
@@ -0,0 +1,112 @@
# 宿舍管理员授权API文档
## 1. 接口总览
| 接口名称 | 请求方式 | 接口地址 | 接口描述 |
| ------- | ------- | ------- | ------- |
| 获取宿舍区树形结构 | GET | /ssqxx/dormArea/tree | 获取以校区为根节点、宿舍区为子节点的树形结构数据 |
## 2. 权限要求
- 查询宿舍区树形结构:`ssqxx:dormArea:query`
## 3. 接口详情
### 3.1 获取宿舍区树形结构
#### 接口说明
该接口用于获取宿舍区的树形结构数据,以校区为根节点,宿舍区为子节点。前端可以使用该接口实现树形下拉选择,实现宿舍管理员授权范围选择功能。
#### 请求URL
```
GET /ssqxx/dormArea/tree
```
#### 请求参数
无
#### 响应参数
| 参数名 | 类型 | 说明 |
| ------ | ---- | ---- |
| code | Integer | 状态码,200表示成功 |
| msg | String | 提示信息 |
| data | Array | 数据内容 |
| data[i].id | Long | 节点ID |
| data[i].label | String | 节点名称 |
| data[i].type | String | 节点类型(campus:校区,area:宿舍区) |
| data[i].disabled | Boolean | 是否禁用选择(校区节点为true,宿舍区节点为false) |
| data[i].children | Array | 子节点,结构同父节点 |
#### 响应示例
```json
{
"code": 200,
"msg": "操作成功",
"data": [
{
"id": 1,
"label": "东校区",
"type": "campus",
"disabled": true,
"children": [
{
"id": 101,
"label": "1号宿舍区",
"type": "area",
"disabled": false,
"children": []
},
{
"id": 102,
"label": "2号宿舍区",
"type": "area",
"disabled": false,
"children": []
}
]
},
{
"id": 2,
"label": "西校区",
"type": "campus",
"disabled": true,
"children": [
{
"id": 201,
"label": "3号宿舍区",
"type": "area",
"disabled": false,
"children": []
}
]
}
]
}
```
## 4. 使用说明
### 前端使用方式
1. 调用树形结构接口获取宿舍区树形数据
2. 使用树形选择组件展示数据,设置不可选择校区节点(根据disabled属性)
3. 用户只能选择宿舍区节点,不能选择校区节点
4. 支持多选宿舍区节点
### 数据说明
- 树形结构中,校区为第一级节点,宿舍区为第二级节点
- 校区节点的disabled属性为true,表示不可选择
- 宿舍区节点的disabled属性为false,表示可以选择
- 通过type字段可以区分节点类型:campus为校区,area为宿舍区
## 5. 实现细节
- 宿舍区数据存放在xg_dorm_area表中,校区数据存放在xg_dorm_campus表中
- xg_dorm_area表中campus_id字段关联xg_dorm_campus表的id字段
- 树形数据的构建逻辑是:先查询所有校区,再查询所有宿舍区,然后根据关联关系构建树形结构
+34
View File
@@ -61,3 +61,37 @@ export const delDormManager = (id: string | number | Array<string | number>) =>
method: 'delete'
});
};
/**
* 分配管理员数据范围
* @param managerId 管理员ID
* @param authType 授权类型(1全校级 2校区级 3宿舍区级 4楼栋级)
* @param authSourceIds 授权来源ID列表
*/
export const assignScope = (managerId: string | number, authType: number, authSourceIds: (string | number)[]) => {
return request({
url: '/glysqgx/dormManagerSource/assignScope',
method: 'post',
params: {
managerId,
authType
},
data: authSourceIds
});
};
/**
* 获取管理员授权范围名称
* @param managerId 管理员ID
* @param managerType 管理员类型
*/
export const getManagerScopeNames = (managerId: string | number, managerType: number) => {
return request({
url: '/glysqgx/dormManagerSource/getScopeNames',
method: 'get',
params: {
managerId,
managerType
}
});
};
+5
View File
@@ -49,6 +49,11 @@ export interface DormManagerVO {
*/
remark: string;
/**
* 授权范围名称(逗号分隔的字符串)
*/
scopeNames?: string;
}
export interface DormManagerForm extends BaseEntity {
+12 -1
View File
@@ -1,6 +1,6 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { DormBuildingVO, DormBuildingForm, DormBuildingQuery } from '@/api/sslxx/dormBuilding/types';
import { DormBuildingVO, DormBuildingForm, DormBuildingQuery, BuildingTreeSelectVo } from '@/api/sslxx/dormBuilding/types';
/**
* 查询宿舍楼信息列表
@@ -83,3 +83,14 @@ export const listAreaTreeData = (campusId?: string | number): AxiosPromise<any>
params: { campusId }
});
};
/**
* 获取宿舍楼树形结构数据(校区-宿舍区-宿舍楼三级结构)
* 校区和宿舍区节点不可选,只有宿舍楼节点可以选择
*/
export const getBuildingTree = (): AxiosPromise<BuildingTreeSelectVo[]> => {
return request({
url: '/sslxx/dormBuilding/buildingTree',
method: 'get'
});
};
+24 -103
View File
@@ -1,125 +1,46 @@
export interface DormBuildingVO {
/**
* 主键ID
*/
id: string | number;
/**
* 宿舍楼编码
*/
buildingCode: string;
/**
* 宿舍楼名称
*/
buildingName: string;
/**
* 所属宿舍区ID
*/
areaId: string | number;
/**
* 所属校区ID
*/
campusId: string | number;
/**
* 宿舍楼类型(1男生楼 2女生楼 3混合楼)
*/
buildingType: string;
/**
* 宿舍楼描述
*/
buildingDescription: string;
/**
* 排序号
*/
sortOrder: number;
createTime: string;
createBy: string;
updateTime: string;
updateBy: string;
remark: string;
areaName?: string;
campusName?: string;
}
export interface DormBuildingForm extends BaseEntity {
/**
* 主键ID
*/
id?: string | number;
export interface DormBuildingForm extends Omit<DormBuildingVO, 'createTime' | 'createBy' | 'updateTime' | 'updateBy' | 'areaName' | 'campusName'> {
}
/**
* 宿舍楼编码
*/
export interface DormBuildingQuery {
buildingCode?: string;
/**
* 宿舍楼名称
*/
buildingName?: string;
/**
* 所属宿舍区ID
*/
areaId?: string | number;
/**
* 所属校区ID
*/
campusId?: string | number;
/**
* 宿舍楼类型(1男生楼 2女生楼 3混合楼)
*/
buildingType?: string;
/**
* 宿舍楼描述
*/
buildingDescription?: string;
/**
* 排序号
*/
sortOrder?: number;
params?: Record<string, any>;
}
export interface DormBuildingQuery extends PageQuery {
/**
* 宿舍楼编码
*/
buildingCode?: string;
/**
* 宿舍楼名称
*/
buildingName?: string;
/**
* 所属宿舍区ID
*/
areaId?: string | number;
/**
* 所属校区ID
*/
campusId?: string | number;
/**
* 宿舍楼类型(1男生楼 2女生楼 3混合楼)
*/
buildingType?: string;
/**
* 排序号
*/
sortOrder?: number;
/**
* 日期范围参数
*/
params?: any;
/**
* 宿舍楼树形结构节点
*/
export interface BuildingTreeSelectVo {
id: number;
label: string;
nodeType: 'campus' | 'area' | 'building';
disabled: boolean;
children?: BuildingTreeSelectVo[];
createBy?: number;
createTime?: string;
updateBy?: number;
updateTime?: string;
}
+12 -1
View File
@@ -1,6 +1,6 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { DormAreaVO, DormAreaForm, DormAreaQuery } from '@/api/ssqxx/dormArea/types';
import { DormAreaVO, DormAreaForm, DormAreaQuery, TreeSelectVo } from '@/api/ssqxx/dormArea/types';
/**
* 查询宿舍区信息列表
@@ -61,3 +61,14 @@ export const delDormArea = (id: string | number | Array<string | number>) => {
method: 'delete'
});
};
/**
* 获取宿舍区树形结构数据
* 以校区为根节点,宿舍区为子节点的树形结构
*/
export const getAreaTree = (): AxiosPromise<TreeSelectVo[]> => {
return request({
url: '/ssqxx/dormArea/tree',
method: 'get'
});
};
+16
View File
@@ -87,5 +87,21 @@ export interface DormAreaQuery extends PageQuery {
params?: any;
}
/**
* 树形选择数据类型
*/
export interface TreeSelectVo {
/** 节点ID */
id: number;
/** 节点名称 */
label: string;
/** 节点类型(campus:校区, area:宿舍区) */
type: string;
/** 是否禁用选择 */
disabled: boolean;
/** 子节点 */
children?: TreeSelectVo[];
}
+328 -41
View File
@@ -5,18 +5,70 @@
<span>{{ admin?.name }}</span>
</el-form-item>
<el-form-item label="管理范围" prop="scope">
<!-- 校区类型使用原有的多选下拉框 -->
<el-select
v-if="type === 'campus'"
v-model="form.scope"
placeholder="请选择管理范围"
style="width: 100%"
clearable
filterable
:multiple="isCampusType"
multiple
collapse-tags
default-expand-all
collapse-tags-tooltip
>
<el-option v-for="item in scopeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<!-- 宿舍区类型使用树形选择器 -->
<el-tree-select
v-else-if="type === 'area'"
v-model="form.scope"
placeholder="请选择宿舍区"
style="width: 100%"
clearable
filterable
multiple
collapse-tags
collapse-tags-tooltip
default-expand-all
:data="treeData"
:props="{
children: 'children',
label: 'label',
disabled: 'disabled',
value: 'id'
}"
check-strictly
/>
<!-- 宿舍楼类型使用新的树形选择器 -->
<el-tree-select
v-else-if="type === 'building'"
v-model="form.scope"
placeholder="请选择宿舍楼"
style="width: 100%"
clearable
filterable
multiple
collapse-tags
collapse-tags-tooltip
default-expand-all
:data="buildingTreeData"
:props="{
children: 'children',
label: 'label',
disabled: 'disabled',
value: 'id'
}"
check-strictly
/>
<!-- 其他类型保持原有的单选下拉框 -->
<el-select v-else v-model="form.scope" placeholder="请选择管理范围" style="width: 100%" clearable filterable>
<el-option v-for="item in scopeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
@@ -31,6 +83,10 @@
<script lang="ts">
import { defineComponent, ref, reactive, computed, watch, onMounted } from 'vue';
import { listDormCampus } from '@/api/xqxx/dormCampus';
import { getAreaTree } from '@/api/ssqxx/dormArea';
import { getBuildingTree } from '@/api/sslxx/dormBuilding';
import type { TreeSelectVo } from '@/api/ssqxx/dormArea/types';
import type { BuildingTreeSelectVo } from '@/api/sslxx/dormBuilding/types';
export default defineComponent({
name: 'ScopeDialog',
@@ -59,47 +115,231 @@ export default defineComponent({
// 判断是否为校区类型的计算属性
const isCampusType = computed(() => props.type === 'campus');
// 判断是否为宿舍区类型的计算属性
const isAreaType = computed(() => props.type === 'area');
// 判断是否为宿舍楼类型的计算属性
const isBuildingType = computed(() => props.type === 'building');
const formRef = ref();
const form = reactive({
scope: isCampusType.value ? [] : ''
scope: isCampusType.value || isAreaType.value || isBuildingType.value ? [] : ''
});
// 选项数据
// 下拉选项数据
const scopeOptions = ref<any[]>([]);
// 宿舍区树形数据
const treeData = ref<TreeSelectVo[]>([]);
// 宿舍楼树形数据
const buildingTreeData = ref<BuildingTreeSelectVo[]>([]);
// 标记是否完成数据加载的状态
const dataLoaded = ref(false);
// 处理scope字符串为数组的辅助函数(校区使用)
const parseScopeString = (scopeString: string) => {
if (!scopeString) return [];
// 尝试按逗号分隔并提取ID (假设格式为: "东校区,西校区")
return scopeString
.split(',')
.map((item) => {
// 在校区选项中查找匹配的校区名称
const option = scopeOptions.value.find((opt) => opt.label === item.trim());
return option ? option.value : null;
})
.filter((id) => id !== null); // 过滤掉未找到的项
};
// 处理宿舍区scope字符串为数组的辅助函数
const parseAreaScopeString = (scopeString: string) => {
if (!scopeString) return [];
// 尝试按逗号分隔宿舍区名称
const areaNames = scopeString.split(',').map((item) => item.trim());
// 在树形数据中查找匹配的宿舍区ID
const findAreaIds = (nodes: TreeSelectVo[]): number[] => {
if (!nodes || nodes.length === 0) return [];
let result: number[] = [];
for (const node of nodes) {
// 如果节点标签匹配任何一个宿舍区名称,添加该节点ID
if (areaNames.includes(node.label)) {
result.push(node.id);
}
// 如果有子节点,递归查找
if (node.children && node.children.length > 0) {
result = [...result, ...findAreaIds(node.children)];
}
}
return result;
};
return findAreaIds(treeData.value);
};
// 处理宿舍楼scope字符串为数组的辅助函数
const parseBuildingScopeString = (scopeString: string) => {
if (!scopeString) return [];
// 尝试按逗号分隔宿舍楼名称
const buildingNames = scopeString.split(',').map((item) => item.trim());
// 在树形数据中查找匹配的宿舍楼ID
const findBuildingIds = (nodes: BuildingTreeSelectVo[]): number[] => {
if (!nodes || nodes.length === 0) return [];
let result: number[] = [];
for (const node of nodes) {
// 如果是宿舍楼节点,且名称匹配,添加该节点ID
if (node.nodeType === 'building' && buildingNames.includes(node.label)) {
result.push(node.id);
}
// 如果有子节点,递归查找
if (node.children && node.children.length > 0) {
result = [...result, ...findBuildingIds(node.children)];
}
}
return result;
};
return findBuildingIds(buildingTreeData.value);
};
// 根据类型加载选项
const loadScopeOptions = async () => {
dataLoaded.value = false;
if (isCampusType.value) {
// 调用API获取校区列表
const res = await listDormCampus();
// 适配el-select格式
console.log(res);
scopeOptions.value = (res.rows || []).map((item: any) => ({
value: item.id,
label: item.campusName
}));
// 校区支持多选,初始化为数组
form.scope = Array.isArray(form.scope) ? form.scope : [];
} else if (props.type === 'area') {
scopeOptions.value = [
{ value: '1号宿舍区', label: '1号宿舍区' },
{ value: '2号宿舍区', label: '2号宿舍区' },
{ value: '3号宿舍区', label: '3号宿舍区' }
];
// 非校区为单选,初始化为字符串
form.scope = Array.isArray(form.scope) ? '' : form.scope;
try {
const res = await listDormCampus();
// 适配el-select格式
console.log('校区数据加载成功:', res);
scopeOptions.value = (res.rows || []).map((item: any) => ({
value: item.id,
label: item.campusName
}));
// 校区支持多选,初始化为数组
form.scope = Array.isArray(form.scope) ? form.scope : [];
dataLoaded.value = true;
// 数据加载完成后,处理当前管理员的scope
handleAdminScope();
} catch (error) {
console.error('加载校区数据失败:', error);
dataLoaded.value = true;
}
} else if (isAreaType.value) {
// 宿舍区类型时加载树形数据
try {
const res = await getAreaTree();
if (res && res.data) {
treeData.value = res.data;
console.log('宿舍区树形数据加载成功:', treeData.value);
}
// 宿舍区使用树形选择,支持多选
form.scope = Array.isArray(form.scope) ? form.scope : [];
dataLoaded.value = true;
// 数据加载完成后,处理当前管理员的scope
handleAdminScope();
} catch (error) {
console.error('加载树形数据失败:', error);
dataLoaded.value = true;
}
} else if (isBuildingType.value) {
// 宿舍楼类型时加载树形数据
try {
const res = await getBuildingTree();
if (res && res.data) {
buildingTreeData.value = res.data;
console.log('宿舍楼树形数据加载成功:', buildingTreeData.value);
}
// 宿舍楼使用树形选择,支持多选
form.scope = Array.isArray(form.scope) ? form.scope : [];
dataLoaded.value = true;
// 数据加载完成后,处理当前管理员的scope
handleAdminScope();
} catch (error) {
console.error('加载宿舍楼树形数据失败:', error);
dataLoaded.value = true;
}
} else if (props.type === 'building') {
scopeOptions.value = [
{ value: '1号楼', label: '1号楼' },
{ value: '2号楼', label: '2号楼' },
{ value: '3号楼', label: '3号楼' },
{ value: '4号楼', label: '4号楼' }
];
// 非校区为单选,初始化为字符串
form.scope = Array.isArray(form.scope) ? '' : form.scope;
// 这段代码已被上面的isBuildingType.value条件替代,保留此处以兼容旧代码
// 将在下一次重构时移除
dataLoaded.value = true;
// 数据加载完成后,处理当前管理员的scope
handleAdminScope();
} else {
scopeOptions.value = [];
dataLoaded.value = true;
// 数据加载完成后,处理当前管理员的scope
handleAdminScope();
}
};
// 创建单独的函数处理admin的scope,确保在数据加载完成后执行
const handleAdminScope = () => {
const admin = props.admin;
if (!admin || !dataLoaded.value) return;
// 根据类型决定如何设置scope值
if (isCampusType.value) {
// 校区类型 - 支持多选
if (admin.scope) {
// 判断scope的类型
if (Array.isArray(admin.scope)) {
// 如果已经是数组,直接使用
form.scope = admin.scope;
} else if (typeof admin.scope === 'string') {
// 如果是字符串,需要解析
form.scope = parseScopeString(admin.scope);
} else {
// 其他情况,初始化为空数组
form.scope = [];
}
console.log('校区管理员回显数据:', form.scope);
} else {
form.scope = [];
}
} else if (isAreaType.value) {
// 宿舍区类型 - 支持多选
if (admin.scope) {
if (Array.isArray(admin.scope)) {
// 如果已经是数组,直接使用
form.scope = admin.scope;
} else if (typeof admin.scope === 'string') {
// 如果是字符串,需要解析为树形选择器所需的ID数组
form.scope = parseAreaScopeString(admin.scope);
} else {
form.scope = [];
}
console.log('宿舍区管理员回显数据:', form.scope);
} else {
form.scope = [];
}
} else if (isBuildingType.value) {
// 宿舍楼类型 - 支持多选
if (admin.scope) {
if (Array.isArray(admin.scope)) {
// 如果已经是数组,直接使用
form.scope = admin.scope;
} else if (typeof admin.scope === 'string') {
// 如果是字符串,需要解析为树形选择器所需的ID数组
form.scope = parseBuildingScopeString(admin.scope);
} else {
form.scope = [];
}
console.log('宿舍楼管理员回显数据:', form.scope);
} else {
form.scope = [];
}
} else {
// 其他类型 - 单选
form.scope = admin.scope || '';
}
};
@@ -112,35 +352,82 @@ export default defineComponent({
{ immediate: true }
);
// 监听对话框可见性,确保每次打开时都重新加载数据
watch(
() => dialogVisible.value,
(newVal) => {
if (newVal) {
// 对话框打开时,确保重新加载数据
loadScopeOptions();
}
}
);
// 监听admin变化
watch(
() => props.admin,
(newVal) => {
if (newVal && newVal.scope) {
// 根据类型决定如何设置scope值
if (isCampusType.value) {
// 校区支持多选,确保scope是数组
form.scope = Array.isArray(newVal.scope) ? newVal.scope : [newVal.scope];
} else {
// 其他类型是单选
form.scope = newVal.scope;
}
() => {
// 如果数据已加载完成,则立即处理admin的scope
if (dataLoaded.value) {
handleAdminScope();
}
// 否则,会在loadScopeOptions完成后自动调用handleAdminScope
},
{ deep: true, immediate: true }
{ deep: true }
);
const submitForm = () => {
emit('submit', form.scope);
// 将form.scope转换为API所需的格式
let result;
if (Array.isArray(form.scope)) {
// 对于多选,发送选中的ID数组
result = {
sourceIds: form.scope
};
} else {
// 对于单选,将字符串ID包装在数组中
result = {
sourceIds: form.scope ? [form.scope] : []
};
}
emit('submit', result);
dialogVisible.value = false;
};
// 在对话框打开时,重置数据状态,防止旧数据影响新的显示
watch(
() => dialogVisible.value,
(newVal) => {
if (newVal) {
// 对话框打开时,先清空数据,确保重新加载
scopeOptions.value = [];
treeData.value = [];
buildingTreeData.value = [];
dataLoaded.value = false;
// 根据类型设置默认值
if (isCampusType.value || isAreaType.value || isBuildingType.value) {
form.scope = [];
} else {
form.scope = '';
}
// 然后加载新数据
loadScopeOptions();
}
}
);
return {
dialogVisible,
form,
formRef,
scopeOptions,
treeData,
buildingTreeData,
submitForm,
isCampusType
isCampusType,
type: computed(() => props.type)
};
}
});
+125 -44
View File
@@ -218,11 +218,17 @@
import { ref, reactive, onMounted } from 'vue';
import AdminDialog from './components/AdminDialog.vue';
import ScopeDialog from './components/ScopeDialog.vue';
import axios from 'axios';
import { listDormManager } from '@/api/ssglyxx/dormManager';
import { delDormManager } from '@/api/ssglyxx/dormManager';
import request from '@/utils/request';
import { ElMessage, ElMessageBox } from 'element-plus';
// API路径
const apiUrl = {
list: '/ssglyxx/dormManager/list',
delete: '/ssglyxx/dormManager/',
assignScope: '/glysqgx/dormManagerSource/assignScope',
getScopeNames: '/glysqgx/dormManagerSource/getScopeNames'
};
const activeTab = ref('school');
// 各tab独立检索表单
@@ -287,38 +293,80 @@ async function fetchAdmins(type: string) {
Object.keys(params).forEach((key) => {
if (params[key] === '' || params[key] === undefined) delete params[key];
});
// const res = await axios.get('/ssglyxx/dormManager/list', { params });
const res = await listDormManager(params);
// 兼容TableDataInfo和R结构
let data = [];
if (Array.isArray(res.data)) {
data = res.data;
} else if (res.rows) {
data = res?.rows;
}
// console.log(data);
const mappedData = data.map((item: any) => ({
id: item.id,
name: item.managerName, // 映射
phone: item.phone,
sex: typeof item.gender === 'number' ? item.gender : Number(item.gender), // gender转数字
remark: item.remark
// 其他字段按需添加
}));
// console.log(mappedData);
switch (type) {
case 'school':
schoolAdmins.value = mappedData;
break;
case 'campus':
campusAdmins.value = mappedData;
break;
case 'area':
areaAdmins.value = mappedData;
break;
case 'building':
buildingAdmins.value = mappedData;
break;
try {
// 获取管理员列表
const res = await request({
url: apiUrl.list,
method: 'get',
params
});
// 兼容TableDataInfo和R结构
let data = [];
if (Array.isArray(res.data)) {
data = res.data;
} else if (res.rows) {
data = res?.rows;
}
// 预处理管理员数据
const mappedData = await Promise.all(
data.map(async (item: any) => {
// 基础字段映射
const manager = {
id: item.id,
name: item.managerName, // 映射
phone: item.phone,
sex: typeof item.gender === 'number' ? item.gender : Number(item.gender), // gender转数字
remark: item.remark,
scope: '' // 初始化scope字段
};
// 非全校级管理员需要获取授权范围
if (managerType > 1) {
try {
// 获取授权范围名称
const scopeRes = await request({
url: apiUrl.getScopeNames,
method: 'get',
params: {
managerId: item.id,
managerType: managerType
}
});
console.log(scopeRes.msg);
if (scopeRes) {
manager.scope = scopeRes.msg;
}
} catch (error) {
console.error('获取管理员授权范围失败:', error);
}
}
return manager;
})
);
// 更新对应类型的管理员数据
switch (type) {
case 'school':
schoolAdmins.value = mappedData;
break;
case 'campus':
campusAdmins.value = mappedData;
break;
case 'area':
areaAdmins.value = mappedData;
break;
case 'building':
buildingAdmins.value = mappedData;
break;
}
} catch (error) {
console.error('获取管理员列表失败:', error);
ElMessage.error('获取管理员列表失败');
}
}
@@ -336,8 +384,6 @@ function onAdd(type: string) {
dialogVisible.value = true;
}
function onEdit(type: string, admin: any) {
console.log('onEdit=============');
console.log(admin);
dialogMode.value = 'edit';
currentAdmin.value = { ...admin };
activeTab.value = type; // 确保当前标签页与编辑的管理员类型一致
@@ -352,7 +398,10 @@ async function onDelete(type: string, admin: any) {
})
.then(async () => {
try {
await delDormManager(admin.id);
await request({
url: apiUrl.delete + admin.id,
method: 'delete'
});
ElMessage.success('删除成功');
fetchAdmins(type); // 删除后刷新
} catch (e) {
@@ -380,12 +429,44 @@ function onDialogSubmit(admin: any) {
}
dialogVisible.value = false;
}
function onScopeSubmit(scope: string) {
// TODO: 分配范围时调用API,成功后刷新
const list = getListByType(scopeType.value);
const idx = list.value.findIndex((a: any) => a.id === currentAdmin.value.id);
if (idx !== -1) list.value[idx].scope = scope;
scopeDialogVisible.value = false;
async function onScopeSubmit(scope: any) {
try {
// 将scope类型转换为数字
let authType = 1;
switch (scopeType.value) {
case 'school':
authType = 1;
break;
case 'campus':
authType = 2;
break;
case 'area':
authType = 3;
break;
case 'building':
authType = 4;
break;
}
// 调用API
await request({
url: apiUrl.assignScope,
method: 'post',
params: {
managerId: currentAdmin.value.id,
authType: authType
},
data: scope.sourceIds
});
// 成功后刷新数据并提示
ElMessage.success('管理范围分配成功');
fetchAdmins(scopeType.value);
scopeDialogVisible.value = false;
} catch (error) {
ElMessage.error('管理范围分配失败');
console.error(error);
}
}
function getListByType(type: string) {
switch (type) {
+2 -2
View File
@@ -232,7 +232,7 @@ cd backend
#### 3. 启动主应用服务
```bash
./hot-start.sh
./start-main.sh
```
**等待提示**: 看到 "主应用启动成功" 或访问 `http://localhost:8080` 正常返回
@@ -255,7 +255,7 @@ cd backend
cd backend
bash start-monitor.sh
bash start-job.sh
bash hot-start.sh
bash start-main.sh
# 或者直接运行 Java 命令
java -jar ruoyi-extend/ruoyi-monitor-admin/target/ruoyi-monitor-admin.jar