zlwu il y a 1 an
Parent
commit
7ce20647f0
28 fichiers modifiés avec 1451 ajouts et 420 suppressions
  1. 13 4
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormAreaController.java
  2. 14 4
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormBuildingController.java
  3. 15 1
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormManagerSourceController.java
  4. 0 1
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormManagerSourceBo.java
  5. 65 0
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/BuildingTreeSelectVo.java
  6. 63 0
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/TreeSelectVo.java
  7. 0 3
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormManagerSourceVo.java
  8. 26 0
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgDormManagerSourceMapper.java
  9. 9 0
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormAreaService.java
  10. 9 0
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormBuildingService.java
  11. 12 23
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormManagerSourceService.java
  12. 59 0
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormAreaServiceImpl.java
  13. 100 0
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBuildingServiceImpl.java
  14. 84 22
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormManagerSourceServiceImpl.java
  15. 41 0
      backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/glysqgx/XgDormManagerSourceMapper.xml
  16. 68 0
      docs/变更管理/code-adjust-record-2025-07-20-zlwu.md
  17. 29 170
      docs/开发文档/API/API文档总览.md
  18. 174 0
      docs/开发文档/API/宿舍楼树形结构API.md
  19. 112 0
      docs/开发文档/API/宿舍管理员授权API.md
  20. 34 0
      web/src/api/ssglyxx/dormManager/index.ts
  21. 5 0
      web/src/api/ssglyxx/dormManager/types.ts
  22. 12 1
      web/src/api/sslxx/dormBuilding/index.ts
  23. 24 103
      web/src/api/sslxx/dormBuilding/types.ts
  24. 12 1
      web/src/api/ssqxx/dormArea/index.ts
  25. 16 0
      web/src/api/ssqxx/dormArea/types.ts
  26. 328 41
      web/src/views/xsssgly/components/ScopeDialog.vue
  27. 125 44
      web/src/views/xsssgly/index.vue
  28. 2 2
      工程本地启动指导.md

+ 13 - 4
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormAreaController.java

@@ -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());
+    }
 }

+ 14 - 4
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormBuildingController.java

@@ -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);
+    }
 }

+ 15 - 1
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormManagerSourceController.java

@@ -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));
+    }
 }

+ 0 - 1
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormManagerSourceBo.java

@@ -63,5 +63,4 @@ public class XgDormManagerSourceBo extends BaseEntity {
      */
     private String remark;
 
-
 }

+ 65 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/BuildingTreeSelectVo.java

@@ -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;
+}

+ 63 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/TreeSelectVo.java

@@ -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);
+    }
+}

+ 0 - 3
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormManagerSourceVo.java

@@ -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;
 
-
 }

+ 26 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgDormManagerSourceMapper.java

@@ -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);
 }

+ 9 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormAreaService.java

@@ -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();
 }

+ 9 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormBuildingService.java

@@ -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();
 }

+ 12 - 23
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormManagerSourceService.java

@@ -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);
 }

+ 59 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormAreaServiceImpl.java

@@ -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;
+    }
 }

+ 100 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBuildingServiceImpl.java

@@ -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;
+    }
 }

+ 84 - 22
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormManagerSourceServiceImpl.java

@@ -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 "";
+        }
+    }
 }

+ 41 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/glysqgx/XgDormManagerSourceMapper.xml

@@ -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>

+ 68 - 0
docs/变更管理/code-adjust-record-2025-07-20-zlwu.md

@@ -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
docs/开发文档/API/API文档总览.md

@@ -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文档结构,创建统一导航入口

+ 174 - 0
docs/开发文档/API/宿舍楼树形结构API.md

@@ -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 

+ 112 - 0
docs/开发文档/API/宿舍管理员授权API.md

@@ -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 - 0
web/src/api/ssglyxx/dormManager/index.ts

@@ -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 - 0
web/src/api/ssglyxx/dormManager/types.ts

@@ -49,6 +49,11 @@ export interface DormManagerVO {
    */
   remark: string;
 
+  /**
+   * 授权范围名称(逗号分隔的字符串)
+   */
+  scopeNames?: string;
+
 }
 
 export interface DormManagerForm extends BaseEntity {

+ 12 - 1
web/src/api/sslxx/dormBuilding/index.ts

@@ -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
web/src/api/sslxx/dormBuilding/types.ts

@@ -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;
-
-  /**
-   * 宿舍楼编码
-   */
-  buildingCode?: string;
-
-  /**
-   * 宿舍楼名称
-   */
-  buildingName?: string;
-
-  /**
-   * 所属宿舍区ID
-   */
-  areaId?: string | number;
-
-  /**
-   * 所属校区ID
-   */
-  campusId?: string | number;
-
-  /**
-   * 宿舍楼类型(1男生楼 2女生楼 3混合楼)
-   */
-  buildingType?: string;
-
-  /**
-   * 宿舍楼描述
-   */
-  buildingDescription?: string;
-
-  /**
-   * 排序号
-   */
-  sortOrder?: number;
-
+export interface DormBuildingForm extends Omit<DormBuildingVO, 'createTime' | 'createBy' | 'updateTime' | 'updateBy' | 'areaName' | 'campusName'> {
 }
 
-export interface DormBuildingQuery extends PageQuery {
-
-  /**
-   * 宿舍楼编码
-   */
+export interface DormBuildingQuery {
   buildingCode?: string;
-
-  /**
-   * 宿舍楼名称
-   */
   buildingName?: string;
-
-  /**
-   * 所属宿舍区ID
-   */
   areaId?: string | number;
-
-  /**
-   * 所属校区ID
-   */
   campusId?: string | number;
-
-  /**
-   * 宿舍楼类型(1男生楼 2女生楼 3混合楼)
-   */
   buildingType?: string;
+  params?: Record<string, any>;
+}
 
-  /**
-   * 排序号
-   */
-  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
web/src/api/ssqxx/dormArea/index.ts

@@ -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 - 0
web/src/api/ssqxx/dormArea/types.ts

@@ -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
web/src/views/xsssgly/components/ScopeDialog.vue

@@ -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(
-      () => props.admin,
+      () => dialogVisible.value,
       (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;
-          }
+        if (newVal) {
+          // 对话框打开时,确保重新加载数据
+          loadScopeOptions();
         }
+      }
+    );
+
+    // 监听admin变化
+    watch(
+      () => props.admin,
+      () => {
+        // 如果数据已加载完成,则立即处理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
web/src/views/xsssgly/index.vue

@@ -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
工程本地启动指导.md

@@ -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