Bladeren bron

提交房间分配给二级学院的业务代码

zlwu 1 jaar geleden
bovenliggende
commit
b5e6ca3cce
27 gewijzigde bestanden met toevoegingen van 2727 en 55 verwijderingen
  1. 105 0
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormBedController.java
  2. 66 0
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgDormBed.java
  3. 68 0
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormBedBo.java
  4. 5 0
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/DormBuildingInfoVo.java
  5. 5 0
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/DormBuildingTreeVo.java
  6. 80 0
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormBedVo.java
  7. 15 0
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgDormBedMapper.java
  8. 68 0
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormBedService.java
  9. 137 0
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedServiceImpl.java
  10. 1 0
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBuildingTreeServiceImpl.java
  11. 7 6
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormRoomServiceImpl.java
  12. 7 0
      backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/dormbed/XgDormBedMapper.xml
  13. 6 0
      backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/xg/XgDormBuildingTreeMapper.xml
  14. 68 0
      docs/变更管理/code-adjust-record-2025-07-22-wuzilong.md
  15. 150 4
      docs/开发文档/API/宿舍房间管理API.md
  16. 1 0
      docs/设计文档/database/学工管理-宿舍管理/宿舍管理数据库建表脚本.sql
  17. 63 0
      web/src/api/xgss/dormBed/index.ts
  18. 66 0
      web/src/api/xgss/dormBed/types.ts
  19. 63 0
      web/src/api/xgss/dormRoom/index.ts
  20. 176 0
      web/src/api/xgss/dormRoom/types.ts
  21. 8 4
      web/src/api/xgss/xsssfjgl/index.ts
  22. 5 1
      web/src/api/xgss/xsssfjgl/types.ts
  23. 42 12
      web/src/views/xgss/xsssfjgl/components/BedDialog.vue
  24. 205 28
      web/src/views/xgss/xsssfjgl/index.vue
  25. 150 0
      web/src/views/xgss/xsssfpejxy/components/BedDialog.vue
  26. 171 0
      web/src/views/xgss/xsssfpejxy/components/RoomDialog.vue
  27. 989 0
      web/src/views/xgss/xsssfpejxy/index.vue

+ 105 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormBedController.java

@@ -0,0 +1,105 @@
+package org.dromara.xg.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.xg.domain.vo.XgDormBedVo;
+import org.dromara.xg.domain.bo.XgDormBedBo;
+import org.dromara.xg.service.IXgDormBedService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 宿舍床位信息
+ *
+ * @author zlwu
+ * @date 2025-07-22
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/dormbed/dormBed")
+public class XgDormBedController extends BaseController {
+
+    private final IXgDormBedService xgDormBedService;
+
+    /**
+     * 查询宿舍床位信息列表
+     */
+    @SaCheckPermission("dormbed:dormBed:list")
+    @GetMapping("/list")
+    public TableDataInfo<XgDormBedVo> list(XgDormBedBo bo, PageQuery pageQuery) {
+        return xgDormBedService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出宿舍床位信息列表
+     */
+    @SaCheckPermission("dormbed:dormBed:export")
+    @Log(title = "宿舍床位信息", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(XgDormBedBo bo, HttpServletResponse response) {
+        List<XgDormBedVo> list = xgDormBedService.queryList(bo);
+        ExcelUtil.exportExcel(list, "宿舍床位信息", XgDormBedVo.class, response);
+    }
+
+    /**
+     * 获取宿舍床位信息详细信息
+     *
+     * @param id 主键
+     */
+    @SaCheckPermission("dormbed:dormBed:query")
+    @GetMapping("/{id}")
+    public R<XgDormBedVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable Long id) {
+        return R.ok(xgDormBedService.queryById(id));
+    }
+
+    /**
+     * 新增宿舍床位信息
+     */
+    @SaCheckPermission("dormbed:dormBed:add")
+    @Log(title = "宿舍床位信息", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody XgDormBedBo bo) {
+        return toAjax(xgDormBedService.insertByBo(bo));
+    }
+
+    /**
+     * 修改宿舍床位信息
+     */
+    @SaCheckPermission("dormbed:dormBed:edit")
+    @Log(title = "宿舍床位信息", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody XgDormBedBo bo) {
+        return toAjax(xgDormBedService.updateByBo(bo));
+    }
+
+    /**
+     * 删除宿舍床位信息
+     *
+     * @param ids 主键串
+     */
+    @SaCheckPermission("dormbed:dormBed:remove")
+    @Log(title = "宿舍床位信息", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable Long[] ids) {
+        return toAjax(xgDormBedService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 66 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgDormBed.java

@@ -0,0 +1,66 @@
+package org.dromara.xg.domain;
+
+import org.dromara.common.tenant.core.TenantEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+
+/**
+ * 宿舍床位信息对象 xg_dorm_bed
+ *
+ * @author zlwu
+ * @date 2025-07-22
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("xg_dorm_bed")
+public class XgDormBed extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 床位编号
+     */
+    private String bedCode;
+
+    /**
+     * 床位名称
+     */
+    private String bedName;
+
+    /**
+     * 所属房间ID
+     */
+    private Long roomId;
+
+    /**
+     * 床位类型(1下铺 2上铺 3中铺 4独立床)
+     */
+    private String bedType;
+
+    /**
+     * 床位状态(0空闲 1已分配 2维修中)
+     */
+    private String bedStatus;
+
+    /**
+     * 删除标志(0代表存在 1代表删除)
+     */
+    @TableLogic(value = "0", delval = "1")
+    private String deleted;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+}

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

@@ -0,0 +1,68 @@
+package org.dromara.xg.domain.bo;
+
+import org.dromara.xg.domain.XgDormBed;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+
+/**
+ * 宿舍床位信息业务对象 xg_dorm_bed
+ *
+ * @author zlwu
+ * @date 2025-07-22
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = XgDormBed.class, reverseConvertGenerate = false)
+public class XgDormBedBo extends BaseEntity {
+
+    /**
+     * 主键ID
+     */
+    @NotNull(message = "主键ID不能为空", groups = { EditGroup.class })
+    private Long id;
+
+    /**
+     * 床位编号
+     */
+    @NotBlank(message = "床位编号不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String bedCode;
+
+    /**
+     * 床位名称
+     */
+    @NotBlank(message = "床位名称不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String bedName;
+
+    /**
+     * 所属房间ID
+     */
+    @NotNull(message = "所属房间ID不能为空", groups = { AddGroup.class, EditGroup.class })
+    private Long roomId;
+
+    /**
+     * 床位类型(1下铺 2上铺 3中铺 4独立床)
+     */
+    private String bedType;
+
+    /**
+     * 床位状态(0空闲 1已分配 2维修中)
+     */
+    private String bedStatus;
+
+    /**
+     * 删除标志(0代表存在 1代表删除)
+     */
+    private String deleted;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+
+}

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

@@ -102,4 +102,9 @@ public class DormBuildingInfoVo implements Serializable {
      */
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date updateTime;
+
+    /**
+     * 宿舍楼类型(1男生楼 2女生楼 3混合楼)(仅宿舍楼节点有此字段)
+     */
+    private String buildingType;
 }

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

@@ -41,6 +41,11 @@ public class DormBuildingTreeVo implements Serializable {
      */
     private Integer buildingFloors;
 
+    /**
+     * 宿舍楼类型(1男生楼 2女生楼 3混合楼)(仅宿舍楼节点有此字段)
+     */
+    private String buildingType;
+
     /**
      * 子节点
      */

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

@@ -0,0 +1,80 @@
+package org.dromara.xg.domain.vo;
+
+import org.dromara.xg.domain.XgDormBed;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+
+/**
+ * 宿舍床位信息视图对象 xg_dorm_bed
+ *
+ * @author zlwu
+ * @date 2025-07-22
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = XgDormBed.class)
+public class XgDormBedVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @ExcelProperty(value = "主键ID")
+    private Long id;
+
+    /**
+     * 床位编号
+     */
+    @ExcelProperty(value = "床位编号")
+    private String bedCode;
+
+    /**
+     * 床位名称
+     */
+    @ExcelProperty(value = "床位名称")
+    private String bedName;
+
+    /**
+     * 所属房间ID
+     */
+    @ExcelProperty(value = "所属房间ID")
+    private Long roomId;
+
+    /**
+     * 床位类型(1下铺 2上铺 3中铺 4独立床)
+     */
+    @ExcelProperty(value = "床位类型(1下铺 2上铺 3中铺 4独立床)")
+    private String bedType;
+
+    /**
+     * 床位状态(0空闲 1已分配 2维修中)
+     */
+    @ExcelProperty(value = "床位状态(0空闲 1已分配 2维修中)")
+    private String bedStatus;
+
+    /**
+     * 删除标志(0代表存在 1代表删除)
+     */
+    @ExcelProperty(value = "删除标志(0代表存在 1代表删除)")
+    private String deleted;
+
+    /**
+     * 备注
+     */
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+
+}

+ 15 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgDormBedMapper.java

@@ -0,0 +1,15 @@
+package org.dromara.xg.mapper;
+
+import org.dromara.xg.domain.XgDormBed;
+import org.dromara.xg.domain.vo.XgDormBedVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 宿舍床位信息Mapper接口
+ *
+ * @author zlwu
+ * @date 2025-07-22
+ */
+public interface XgDormBedMapper extends BaseMapperPlus<XgDormBed, XgDormBedVo> {
+
+}

+ 68 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormBedService.java

@@ -0,0 +1,68 @@
+package org.dromara.xg.service;
+
+import org.dromara.xg.domain.vo.XgDormBedVo;
+import org.dromara.xg.domain.bo.XgDormBedBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 宿舍床位信息Service接口
+ *
+ * @author zlwu
+ * @date 2025-07-22
+ */
+public interface IXgDormBedService {
+
+    /**
+     * 查询宿舍床位信息
+     *
+     * @param id 主键
+     * @return 宿舍床位信息
+     */
+    XgDormBedVo queryById(Long id);
+
+    /**
+     * 分页查询宿舍床位信息列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 宿舍床位信息分页列表
+     */
+    TableDataInfo<XgDormBedVo> queryPageList(XgDormBedBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询符合条件的宿舍床位信息列表
+     *
+     * @param bo 查询条件
+     * @return 宿舍床位信息列表
+     */
+    List<XgDormBedVo> queryList(XgDormBedBo bo);
+
+    /**
+     * 新增宿舍床位信息
+     *
+     * @param bo 宿舍床位信息
+     * @return 是否新增成功
+     */
+    Boolean insertByBo(XgDormBedBo bo);
+
+    /**
+     * 修改宿舍床位信息
+     *
+     * @param bo 宿舍床位信息
+     * @return 是否修改成功
+     */
+    Boolean updateByBo(XgDormBedBo bo);
+
+    /**
+     * 校验并批量删除宿舍床位信息信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}

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

@@ -0,0 +1,137 @@
+package org.dromara.xg.service.impl;
+
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.dromara.xg.domain.bo.XgDormBedBo;
+import org.dromara.xg.domain.vo.XgDormBedVo;
+import org.dromara.xg.domain.XgDormBed;
+import org.dromara.xg.mapper.XgDormBedMapper;
+import org.dromara.xg.service.IXgDormBedService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * 宿舍床位信息Service业务层处理
+ *
+ * @author zlwu
+ * @date 2025-07-22
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class XgDormBedServiceImpl implements IXgDormBedService {
+
+    private final XgDormBedMapper baseMapper;
+
+    /**
+     * 查询宿舍床位信息
+     *
+     * @param id 主键
+     * @return 宿舍床位信息
+     */
+    @Override
+    public XgDormBedVo queryById(Long id){
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 分页查询宿舍床位信息列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 宿舍床位信息分页列表
+     */
+    @Override
+    public TableDataInfo<XgDormBedVo> queryPageList(XgDormBedBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<XgDormBed> lqw = buildQueryWrapper(bo);
+        Page<XgDormBedVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询符合条件的宿舍床位信息列表
+     *
+     * @param bo 查询条件
+     * @return 宿舍床位信息列表
+     */
+    @Override
+    public List<XgDormBedVo> queryList(XgDormBedBo bo) {
+        LambdaQueryWrapper<XgDormBed> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<XgDormBed> buildQueryWrapper(XgDormBedBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<XgDormBed> lqw = Wrappers.lambdaQuery();
+        lqw.orderByAsc(XgDormBed::getId);
+        lqw.like(StringUtils.isNotBlank(bo.getBedCode()), XgDormBed::getBedCode, bo.getBedCode());
+        lqw.like(StringUtils.isNotBlank(bo.getBedName()), XgDormBed::getBedName, bo.getBedName());
+        lqw.eq(bo.getRoomId() != null, XgDormBed::getRoomId, bo.getRoomId());
+        lqw.eq(StringUtils.isNotBlank(bo.getBedType()), XgDormBed::getBedType, bo.getBedType());
+        lqw.eq(StringUtils.isNotBlank(bo.getBedStatus()), XgDormBed::getBedStatus, bo.getBedStatus());
+        lqw.eq(StringUtils.isNotBlank(bo.getDeleted()), XgDormBed::getDeleted, bo.getDeleted());
+        return lqw;
+    }
+
+    /**
+     * 新增宿舍床位信息
+     *
+     * @param bo 宿舍床位信息
+     * @return 是否新增成功
+     */
+    @Override
+    public Boolean insertByBo(XgDormBedBo bo) {
+        XgDormBed add = MapstructUtils.convert(bo, XgDormBed.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 修改宿舍床位信息
+     *
+     * @param bo 宿舍床位信息
+     * @return 是否修改成功
+     */
+    @Override
+    public Boolean updateByBo(XgDormBedBo bo) {
+        XgDormBed update = MapstructUtils.convert(bo, XgDormBed.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(XgDormBed entity){
+        //TODO 做一些数据校验,如唯一约束
+    }
+
+    /**
+     * 校验并批量删除宿舍床位信息信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if(isValid){
+            //TODO 做一些业务上的校验,判断是否需要校验
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}

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

@@ -141,6 +141,7 @@ public class XgDormBuildingTreeServiceImpl implements IXgDormBuildingTreeService
             buildingNode.setType("building");
             buildingNode.setDisabled(false); // 宿舍楼节点可点击
             buildingNode.setBuildingFloors(building.getFloors());
+            buildingNode.setBuildingType(building.getBuildingType());
 
             // 将宿舍楼节点添加到对应的宿舍区节点下
             DormBuildingTreeVo areaNode = areaMap.get(building.getAreaId());

+ 7 - 6
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormRoomServiceImpl.java

@@ -40,7 +40,7 @@ public class XgDormRoomServiceImpl implements IXgDormRoomService {
      * @return 宿舍房间信息
      */
     @Override
-    public XgDormRoomVo queryById(Long id){
+    public XgDormRoomVo queryById(Long id) {
         return baseMapper.selectVoById(id);
     }
 
@@ -73,7 +73,8 @@ public class XgDormRoomServiceImpl implements IXgDormRoomService {
     private LambdaQueryWrapper<XgDormRoom> buildQueryWrapper(XgDormRoomBo bo) {
         Map<String, Object> params = bo.getParams();
         LambdaQueryWrapper<XgDormRoom> lqw = Wrappers.lambdaQuery();
-        lqw.orderByAsc(XgDormRoom::getId);
+        lqw.orderByAsc(XgDormRoom::getFloorNum); // 优先按楼层排序
+        lqw.orderByAsc(XgDormRoom::getRoomName); // 其次按房间名称排序
         lqw.like(StringUtils.isNotBlank(bo.getRoomCode()), XgDormRoom::getRoomCode, bo.getRoomCode());
         lqw.like(StringUtils.isNotBlank(bo.getRoomName()), XgDormRoom::getRoomName, bo.getRoomName());
         lqw.eq(bo.getBuildingId() != null, XgDormRoom::getBuildingId, bo.getBuildingId());
@@ -119,8 +120,8 @@ public class XgDormRoomServiceImpl implements IXgDormRoomService {
     /**
      * 保存前的数据校验
      */
-    private void validEntityBeforeSave(XgDormRoom entity){
-        //TODO 做一些数据校验,如唯一约束
+    private void validEntityBeforeSave(XgDormRoom entity) {
+        // TODO 做一些数据校验,如唯一约束
     }
 
     /**
@@ -132,8 +133,8 @@ public class XgDormRoomServiceImpl implements IXgDormRoomService {
      */
     @Override
     public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
-        if(isValid){
-            //TODO 做一些业务上的校验,判断是否需要校验
+        if (isValid) {
+            // TODO 做一些业务上的校验,判断是否需要校验
         }
         return baseMapper.deleteByIds(ids) > 0;
     }

+ 7 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/dormbed/XgDormBedMapper.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.xg.mapper.XgDormBedMapper">
+
+</mapper>

+ 6 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/xg/XgDormBuildingTreeMapper.xml

@@ -8,6 +8,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <id property="id" column="id"/>
         <result property="buildingName" column="building_name"/>
         <result property="buildingCode" column="building_code"/>
+        <result property="buildingType" column="building_type"/>
         <result property="campusId" column="campus_id"/>
         <result property="campusName" column="campus_name"/>
         <result property="areaId" column="area_id"/>
@@ -55,6 +56,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             b.building_name,
             b.building_code,
             b.campus_id,
+            b.building_type,
             c.campus_name,
             b.area_id,
             a.area_name,
@@ -103,6 +105,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             b.id,
             b.building_name,
             b.building_code,
+            b.building_type,
             b.campus_id,
             c.campus_name,
             b.area_id,
@@ -148,6 +151,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             b.building_name,
             b.building_code,
             b.campus_id,
+            b.building_type,
             c.campus_name,
             b.area_id,
             a.area_name,
@@ -201,6 +205,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             b.campus_id,
             c.campus_name,
             b.area_id,
+            b.building_type,
             a.area_name,
             b.floors,
             b.remark,
@@ -249,6 +254,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             b.id,
             b.building_name,
             b.building_code,
+            b.building_type,
             b.campus_id,
             c.campus_name,
             b.area_id,

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

@@ -0,0 +1,68 @@
+# 代码调整记录 - 2025-07-22
+
+## 宿舍房间查询排序优化
+
+### 变更时间
+2025-07-22 10:35:26
+
+### 变更类型
+接口优化 - 数据排序调整
+
+### 变更描述
+修改了宿舍房间列表查询接口的排序逻辑,调整为优先按楼层(floorNum)排序,然后按房间名称(roomName)排序,确保宿舍房间按照楼层顺序输出。
+
+### 变更文件
+- `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormRoomServiceImpl.java`
+
+### 变更详情
+原先的排序逻辑:
+```java
+lqw.orderByAsc(XgDormRoom::getId);
+lqw.orderByAsc(XgDormRoom::getFloorNum); // 按楼层排序
+```
+
+修改后的排序逻辑:
+```java
+lqw.orderByAsc(XgDormRoom::getFloorNum); // 优先按楼层排序
+lqw.orderByAsc(XgDormRoom::getRoomName); // 其次按房间名称排序
+```
+
+### 原因
+前端展示宿舍房间列表时,需要按照楼层顺序进行排列,但原有排序逻辑中先按ID排序,导致楼层排序不能正确生效。
+
+### 影响范围
+- 宿舍分配二级学院页面(xsssfpejxy)中的房间列表显示顺序
+- 其他调用宿舍房间查询接口的功能
+
+### 测试验证
+- 验证宿舍楼下的房间是否按楼层升序排列
+- 验证相同楼层的房间是否按房间名称排序 
+
+# 宿舍管理功能变更记录
+
+## 宿舍树性别标识功能添加
+
+**时间**:2025-07-22 15:30:00
+
+**变更内容**:
+1. 在宿舍树显示中,为宿舍楼名称前添加性别标识
+   - 男生楼:添加"(男)"前缀,显示蓝色
+   - 女生楼:添加"(女)"前缀,显示粉色
+2. 修改前端数据模型
+   - 在`DormTreeNode`接口中添加`gender`字段,用于标识宿舍楼性别(1-男生楼,2-女生楼)
+3. 修改模拟数据
+   - 为模拟数据中的宿舍楼添加性别标识
+4. 适配逻辑处理
+   - 对于后端返回的没有性别标识的数据,前端会进行随机分配(临时措施)
+
+**相关文件**:
+- `/web/src/views/xgss/xsssfjgl/index.vue`:修改宿舍树节点显示,添加性别标识和样式
+- `/web/src/api/xgss/xsssfjgl/types.ts`:在`DormTreeNode`接口中添加性别字段
+- `/web/src/api/xgss/xsssfjgl/index.ts`:修改模拟数据,添加性别标识
+
+**后续优化建议**:
+1. 后端需要确保返回的宿舍楼数据中包含性别信息
+2. 宿舍楼管理功能中应添加性别设置选项
+3. 考虑在宿舍树过滤功能中添加按性别筛选功能
+
+**相关SQL变更**:无(本次只涉及前端显示变更) 

+ 150 - 4
docs/开发文档/API/宿舍房间管理API.md

@@ -6,6 +6,8 @@
   - [1.1 获取宿舍楼树形结构](#11-获取宿舍楼树形结构)
 - [2. 宿舍楼信息管理](#2-宿舍楼信息管理)
   - [2.1 获取宿舍楼列表](#21-获取宿舍楼列表)
+- [3. 宿舍房间管理](#3-宿舍房间管理)
+  - [3.1 获取宿舍房间列表](#31-获取宿舍房间列表)
 
 ## 1. 宿舍楼树形结构
 
@@ -60,14 +62,16 @@ xgss:dormBuildingTree:list
               "label": "1号楼",
               "type": "building",
               "disabled": false,
-              "buildingFloors": 6
+              "buildingFloors": 6,
+              "gender": "male"
             },
             {
               "id": 1002,
               "label": "2号楼",
               "type": "building",
               "disabled": false,
-              "buildingFloors": 5
+              "buildingFloors": 5,
+              "gender": "female"
             }
           ]
         },
@@ -97,10 +101,11 @@ xgss:dormBuildingTree:list
 | 参数名         | 类型    | 描述                                             |
 | -------------- | ------- | ------------------------------------------------ |
 | id             | Long    | 节点ID                                           |
-| label          | String  | 节点名称                                         |
-| type           | String  | 节点类型(campus:校区,area:宿舍区,building:宿舍楼) |
+| label          | String  | 节点显示名称                                     |
+| type           | String  | 节点类型(campus:校区、area:宿舍区、building:宿舍楼) |
 | disabled       | Boolean | 是否禁用点击(校区和宿舍区为true,宿舍楼为false)     |
 | buildingFloors | Integer | 宿舍楼层数(只有宿舍楼节点有此字段)             |
+| gender         | String  | 宿舍楼性别(只有宿舍楼节点有此字段)             |
 | children       | Array   | 子节点列表                                       |
 
 #### 数据权限说明
@@ -260,4 +265,145 @@ async function getDormBuildingList(params) {
   }
   return { total: 0, rows: [] };
 }
+``` 
+
+## 3. 宿舍房间管理
+
+### 3.1 获取宿舍房间列表
+
+#### 接口说明
+
+该接口用于获取宿舍房间信息的列表,包含房间基本信息。列表按照楼层升序、房间名称升序排列。
+
+#### 请求方式
+
+```
+GET /xgss/dormRoom/list
+```
+
+#### 权限要求
+
+```
+xgss:dormRoom:list
+```
+
+#### 请求参数
+
+| 参数名     | 类型    | 必须 | 描述                   |
+| ---------- | ------- | ---- | ---------------------- |
+| pageNum    | Integer | 是   | 当前页码,从1开始      |
+| pageSize   | Integer | 是   | 每页记录数             |
+| buildingId | Long    | 否   | 宿舍楼ID,精确查询     |
+| roomName   | String  | 否   | 房间名称,模糊查询     |
+| floorNum   | Integer | 否   | 楼层号,精确查询       |
+| roomType   | String  | 否   | 房间类型(1标准间 2单人间 3套间) |
+| roomStatus | String  | 否   | 房间状态(0正常 1维修中 2停用) |
+
+#### 返回结果
+
+```json
+{
+  "total": 2,
+  "rows": [
+    {
+      "id": "1947459199879495682",
+      "roomCode": "room-101",
+      "roomName": "101",
+      "buildingId": "1947459199879495681",
+      "floorNum": 1,
+      "roomType": "1",
+      "standardBeds": 4,
+      "actualBeds": 4,
+      "roomArea": 20.5,
+      "roomStatus": "0",
+      "remark": "标准四人间",
+      "createBy": 1,
+      "createTime": "2025-07-22 09:15:30",
+      "updateBy": 1,
+      "updateTime": "2025-07-22 09:15:30"
+    },
+    {
+      "id": "1947459199879495683",
+      "roomCode": "room-201",
+      "roomName": "201",
+      "buildingId": "1947459199879495681",
+      "floorNum": 2,
+      "roomType": "1",
+      "standardBeds": 4,
+      "actualBeds": 4,
+      "roomArea": 20.5,
+      "roomStatus": "0",
+      "remark": "标准四人间",
+      "createBy": 1,
+      "createTime": "2025-07-22 09:15:30",
+      "updateBy": 1,
+      "updateTime": "2025-07-22 09:15:30"
+    }
+  ],
+  "code": 200,
+  "msg": "查询成功"
+}
+```
+
+#### 返回参数说明
+
+| 参数名       | 类型    | 描述                                    |
+| ------------ | ------- | --------------------------------------- |
+| id           | Long    | 房间主键ID                              |
+| roomCode     | String  | 房间编码                                |
+| roomName     | String  | 房间名称                                |
+| buildingId   | Long    | 所属宿舍楼ID                            |
+| floorNum     | Integer | 楼层号                                  |
+| roomType     | String  | 房间类型(1标准间 2单人间 3套间)          |
+| standardBeds | Integer | 标准床位数                              |
+| actualBeds   | Integer | 实际床位数                              |
+| roomArea     | Double  | 房间面积(平方米)                        |
+| roomStatus   | String  | 房间状态(0正常 1维修中 2停用)            |
+| remark       | String  | 备注                                    |
+| createBy     | Long    | 创建者ID                                |
+| createTime   | Date    | 创建时间                                |
+| updateBy     | Long    | 更新者ID                                |
+| updateTime   | Date    | 更新时间                                |
+
+#### 排序规则
+
+房间列表按照以下顺序排序:
+1. 优先按楼层号(floorNum)升序排列
+2. 其次按房间名称(roomName)升序排列
+
+这确保了在宿舍管理系统中房间按照从低楼层到高楼层的顺序展示,相同楼层的房间按照名称顺序排列,提供更直观的浏览体验。
+
+#### 错误码
+
+| 错误码 | 描述                 |
+| ------ | -------------------- |
+| 401    | 未授权,需要登录     |
+| 403    | 无权限访问该接口     |
+| 500    | 服务器内部错误       |
+
+#### 使用示例
+
+```javascript
+// 前端代码示例(使用axios)
+import axios from 'axios';
+
+// 获取宿舍房间列表
+async function getDormRoomList(params) {
+  try {
+    const response = await axios.get('/xgss/dormRoom/list', { 
+      params: { 
+        pageNum: 1, 
+        pageSize: 100,
+        buildingId: buildingId,
+        ...params 
+      } 
+    });
+    if (response.data.code === 200) {
+      return response.data;
+    }
+  } catch (error) {
+    console.error('获取宿舍房间列表失败:', error);
+  }
+  return { total: 0, rows: [] };
+}
 ``` 

+ 1 - 0
docs/设计文档/database/学工管理-宿舍管理/宿舍管理数据库建表脚本.sql

@@ -109,6 +109,7 @@ create table `xg_dorm_bed` (
   `bed_status` char(1) default '0' comment '床位状态(0空闲 1已分配 2维修中)',
   `deleted` char(1) default '0' comment '删除标志(0代表存在 1代表删除)',
   `tenant_id` bigint(20) default '0' comment '租户编号',
+  `create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
   `create_by` bigint(20) default null comment '创建者',
   `create_time` datetime default null comment '创建时间',
   `update_by` bigint(20) default null comment '更新者',

+ 63 - 0
web/src/api/xgss/dormBed/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { DormBedVO, DormBedForm, DormBedQuery } from '@/api/xgss/dormBed/types';
+
+/**
+ * 查询宿舍床位信息列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listDormBed = (query?: DormBedQuery): AxiosPromise<DormBedVO[]> => {
+  return request({
+    url: '/dormbed/dormBed/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询宿舍床位信息详细
+ * @param id
+ */
+export const getDormBed = (id: string | number): AxiosPromise<DormBedVO> => {
+  return request({
+    url: '/dormbed/dormBed/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增宿舍床位信息
+ * @param data
+ */
+export const addDormBed = (data: DormBedForm) => {
+  return request({
+    url: '/dormbed/dormBed',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改宿舍床位信息
+ * @param data
+ */
+export const updateDormBed = (data: DormBedForm) => {
+  return request({
+    url: '/dormbed/dormBed',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除宿舍床位信息
+ * @param id
+ */
+export const delDormBed = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/dormbed/dormBed/' + id,
+    method: 'delete'
+  });
+};

+ 66 - 0
web/src/api/xgss/dormBed/types.ts

@@ -0,0 +1,66 @@
+/**
+ * 宿舍床位信息查询参数
+ */
+export interface DormBedQuery {
+  /** 床位编号 */
+  bedCode?: string;
+  /** 床位名称 */
+  bedName?: string;
+  /** 所属房间ID */
+  roomId?: string | number;
+  /** 床位状态(0=空闲,1=已分配,2=维修中) */
+  bedStatus?: string;
+  /** 床位类型(1=下铺,2=上铺,3=中铺,4=独立床) */
+  bedType?: string;
+}
+
+/**
+ * 宿舍床位信息对象
+ */
+export interface DormBedVO {
+  /** 床位ID */
+  id: string | number;
+  /** 床位编号 */
+  bedCode: string;
+  /** 床位名称 */
+  bedName: string;
+  /** 所属房间ID */
+  roomId: string | number;
+  /** 床位状态(0=空闲,1=已分配,2=维修中) */
+  bedStatus: string;
+  /** 床位类型(1=下铺,2=上铺,3=中铺,4=独立床) */
+  bedType: string;
+  /** 备注 */
+  remark?: string;
+  /** 创建者 */
+  createBy: number;
+  /** 创建时间 */
+  createTime: Date;
+  /** 更新者 */
+  updateBy: number;
+  /** 更新时间 */
+  updateTime: Date;
+}
+
+/**
+ * 宿舍床位信息表单对象
+ */
+export interface DormBedForm {
+  /** 床位ID */
+  id: string | number;
+  /** 床位编号 */
+  bedCode: string;
+  /** 床位名称 */
+  bedName: string;
+  /** 所属房间ID */
+  roomId: string | number;
+  /** 床位状态(0=空闲,1=已分配,2=维修中) */
+  bedStatus: string;
+  /** 床位类型(1=下铺,2=上铺,3=中铺,4=独立床) */
+  bedType: string;
+  /** 备注 */
+  remark?: string;
+}
+
+
+

+ 63 - 0
web/src/api/xgss/dormRoom/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { DormRoomVO, DormRoomForm, DormRoomQuery } from '@/api/xgss/dormRoom/types';
+
+/**
+ * 查询宿舍房间信息列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listDormRoom = (query?: DormRoomQuery): AxiosPromise<DormRoomVO[]> => {
+  return request({
+    url: '/xgss/dormRoom/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询宿舍房间信息详细
+ * @param id
+ */
+export const getDormRoom = (id: string | number): AxiosPromise<DormRoomVO> => {
+  return request({
+    url: '/xgss/dormRoom/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增宿舍房间信息
+ * @param data
+ */
+export const addDormRoom = (data: DormRoomForm) => {
+  return request({
+    url: '/xgss/dormRoom',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改宿舍房间信息
+ * @param data
+ */
+export const updateDormRoom = (data: DormRoomForm) => {
+  return request({
+    url: '/xgss/dormRoom',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除宿舍房间信息
+ * @param id
+ */
+export const delDormRoom = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/xgss/dormRoom/' + id,
+    method: 'delete'
+  });
+};

+ 176 - 0
web/src/api/xgss/dormRoom/types.ts

@@ -0,0 +1,176 @@
+export interface DormRoomVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 房间编号
+   */
+  roomCode: string;
+
+  /**
+   * 房间名称
+   */
+  roomName: string;
+
+  /**
+   * 所属宿舍楼ID
+   */
+  buildingId: string | number;
+
+  /**
+   * 楼层
+   */
+  floorNum: number;
+
+  /**
+   * 房间类型(1标准间 2 单人间 3 套间)
+   */
+  roomType: string;
+
+  /**
+   * 实际床位数
+   */
+  actualBeds: number;
+
+  /**
+   * 当前床位数
+   */
+  currentBeds?: number;
+
+  /**
+   * 房间面积(平方米)
+   */
+  roomArea: number;
+
+  /**
+   * 房间状态(0可用 1维修中 2停用)
+   */
+  roomStatus: string;
+
+  /**
+   * 删除标志(0代表存在 1代表删除)
+   */
+  deleted: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+}
+
+export interface DormRoomForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 房间编号
+   */
+  roomCode?: string;
+
+  /**
+   * 房间名称
+   */
+  roomName?: string;
+
+  /**
+   * 所属宿舍楼ID
+   */
+  buildingId?: string | number;
+
+  /**
+   * 楼层
+   */
+  floorNum?: number;
+
+  /**
+   * 房间类型(1标准间 2 单人间 3 套间)
+   */
+  roomType?: string;
+
+  /**
+   * 实际床位数
+   */
+  actualBeds?: number;
+
+  /**
+   * 房间面积(平方米)
+   */
+  roomArea?: number;
+
+  /**
+   * 房间状态(0可用 1维修中 2停用)
+   */
+  roomStatus?: string;
+
+  /**
+   * 删除标志(0代表存在 1代表删除)
+   */
+  deleted?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+}
+
+export interface DormRoomQuery extends PageQuery {
+
+  /**
+   * 房间编号
+   */
+  roomCode?: string;
+
+  /**
+   * 房间名称
+   */
+  roomName?: string;
+
+  /**
+   * 所属宿舍楼ID
+   */
+  buildingId?: string | number;
+
+  /**
+   * 楼层
+   */
+  floorNum?: number;
+
+  /**
+   * 房间类型(1标准间 2 单人间 3 套间)
+   */
+  roomType?: string;
+
+  /**
+   * 实际床位数
+   */
+  actualBeds?: number;
+
+  /**
+   * 房间面积(平方米)
+   */
+  roomArea?: number;
+
+  /**
+   * 房间状态(0可用 1维修中 2停用)
+   */
+  roomStatus?: string;
+
+  /**
+   * 删除标志(0代表存在 1代表删除)
+   */
+  deleted?: string;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 8 - 4
web/src/api/xgss/xsssfjgl/index.ts

@@ -148,13 +148,15 @@ export function getMockDormTree(): { data: DormTreeNode[] } {
               id: 111,
               label: '东区1号楼',
               type: 'building',
-              buildingFloors: 6
+              buildingFloors: 6,
+              gender: '1' // 男生楼
             },
             {
               id: 112,
               label: '东区2号楼',
               type: 'building',
-              buildingFloors: 8
+              buildingFloors: 8,
+              gender: '2' // 女生楼
             }
           ]
         }
@@ -176,13 +178,15 @@ export function getMockDormTree(): { data: DormTreeNode[] } {
               id: 211,
               label: '西区1号楼',
               type: 'building',
-              buildingFloors: 7
+              buildingFloors: 7,
+              gender: '2' // 女生楼
             },
             {
               id: 212,
               label: '西区2号楼',
               type: 'building',
-              buildingFloors: 5
+              buildingFloors: 5,
+              gender: '1' // 男生楼
             }
           ]
         }

+ 5 - 1
web/src/api/xgss/xsssfjgl/types.ts

@@ -20,6 +20,7 @@ export interface DormTreeNode {
   type: 'campus' | 'area' | 'building'; // 节点类型:校区、宿舍区、宿舍楼
   disabled?: boolean; // 是否禁用选择
   buildingFloors?: number; // 宿舍楼层数
+  gender?: string; // 宿舍楼性别: 1-男生楼, 2-女生楼
 }
 
 // 房间信息
@@ -72,9 +73,12 @@ export interface RoomForm {
 
 // 床位表单
 export interface BedForm {
+  id?: string | number;
   bedId?: string | number;
+  bedCode: string;
   bedName: string;
   roomId: string | number;
-  status: string;
+  bedType: string;
+  bedStatus: string;
   remark?: string;
 }

+ 42 - 12
web/src/views/xgss/xsssfjgl/components/BedDialog.vue

@@ -1,16 +1,27 @@
 <template>
   <el-dialog v-model="visible" :title="title" width="500px" append-to-body @close="closeDialog">
     <el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
+      <el-form-item label="床位编号" prop="bedCode">
+        <el-input v-model="form.bedCode" placeholder="请输入床位编号" maxlength="20" />
+      </el-form-item>
       <el-form-item label="床位名称" prop="bedName">
         <el-input v-model="form.bedName" placeholder="请输入床位名称" maxlength="20" />
       </el-form-item>
-      <el-form-item label="床位状态" prop="status">
-        <el-radio-group v-model="form.status">
+      <el-form-item label="床位状态" prop="bedStatus">
+        <el-radio-group v-model="form.bedStatus">
           <el-radio label="0">空闲</el-radio>
-          <el-radio label="1">已分配</el-radio>
+          <el-radio label="1" disabled>已分配</el-radio>
           <el-radio label="2">维修中</el-radio>
         </el-radio-group>
       </el-form-item>
+      <el-form-item label="床位类型" prop="bedType">
+        <el-radio-group v-model="form.bedType">
+          <el-radio label="1">下铺</el-radio>
+          <el-radio label="2">上铺</el-radio>
+          <el-radio label="3">中铺</el-radio>
+          <el-radio label="4">独立床</el-radio>
+        </el-radio-group>
+      </el-form-item>
       <el-form-item label="备注" prop="remark">
         <el-input v-model="form.remark" type="textarea" placeholder="请输入备注内容" />
       </el-form-item>
@@ -27,8 +38,8 @@
 <script setup lang="ts">
 import { ref, reactive, watch } from 'vue';
 import type { FormInstance, FormRules } from 'element-plus';
-import { BedForm } from '@/api/xgss/xsssfjgl/types';
-import { addBed, updateBed } from '@/api/xgss/xsssfjgl';
+import { DormBedForm } from '@/api/xgss/dormBed/types';
+import { addDormBed, updateDormBed, getDormBed } from '@/api/xgss/dormBed';
 
 const props = defineProps<{
   modelValue: boolean;
@@ -43,19 +54,24 @@ const formRef = ref<FormInstance>();
 const visible = ref(false);
 
 // 表单数据
-const initFormData: BedForm = {
+const initFormData: DormBedForm = {
+  id: '',
+  bedCode: '',
   bedName: '',
   roomId: '',
-  status: '0',
+  bedStatus: '0',
+  bedType: '4',
   remark: ''
 };
 
-const form = reactive<BedForm>({ ...initFormData });
+const form = reactive<DormBedForm>({ ...initFormData });
 
 // 表单校验规则
 const rules = reactive<FormRules>({
+  bedCode: [{ required: true, message: '床位编号不能为空', trigger: 'blur' }],
   bedName: [{ required: true, message: '床位名称不能为空', trigger: 'blur' }],
-  status: [{ required: true, message: '床位状态不能为空', trigger: 'change' }]
+  bedStatus: [{ required: true, message: '床位状态不能为空', trigger: 'change' }],
+  bedType: [{ required: true, message: '床位类型不能为空', trigger: 'change' }]
 });
 
 // 监听visible属性变化
@@ -67,6 +83,20 @@ watch(
       resetForm();
       // 设置表单初始值
       form.roomId = props.roomId;
+      // 编辑模式,获取床位详情
+      if (props.bedId) {
+        console.log('开始获取床位详情, bedId:', props.bedId);
+        getDormBed(props.bedId)
+          .then((res) => {
+            console.log('获取床位详情成功:', res.data);
+            if (res.data) {
+              Object.assign(form, res.data);
+            }
+          })
+          .catch((error) => {
+            console.error('获取床位详情失败', error);
+          });
+      }
     }
   }
 );
@@ -98,11 +128,11 @@ const submitForm = () => {
       try {
         if (props.bedId) {
           // 修改
-          form.bedId = props.bedId;
-          await updateBed(form);
+          form.id = props.bedId;
+          await updateDormBed(form);
         } else {
           // 新增
-          await addBed(form);
+          await addDormBed(form);
         }
         closeDialog();
         emit('success');

+ 205 - 28
web/src/views/xgss/xsssfjgl/index.vue

@@ -13,18 +13,34 @@
             @keyup.enter="watchBuildingName"
           >
           </el-input>
-          <el-tree
-            ref="dormTreeRef"
-            class="mt-2"
-            node-key="id"
-            :data="dormOptions"
-            :props="{ label: 'label', children: 'children', disabled: 'disabled' }"
-            :expand-on-click-node="false"
-            :filter-node-method="filterNode"
-            highlight-current
-            default-expand-all
-            @node-click="handleNodeClick"
-          />
+          <div class="tree-container">
+            <el-tree
+              ref="dormTreeRef"
+              class="mt-2"
+              node-key="id"
+              :data="dormOptions"
+              :props="{ label: 'label', children: 'children', disabled: 'disabled' }"
+              :expand-on-click-node="false"
+              :filter-node-method="filterNode"
+              highlight-current
+              default-expand-all
+              @node-click="handleNodeClick"
+            >
+              <template #default="{ node, data }">
+                <div class="custom-tree-node">
+                  <el-tooltip v-if="data.disabled" effect="dark" content="此节点不可选择,请选择具体的宿舍楼" placement="right">
+                    <span :class="{ 'disabled-node': data.disabled }">{{ node.label }}</span>
+                  </el-tooltip>
+                  <span v-else>
+                    <span v-if="data.type === 'building'" :class="data.buildingType === '1' ? 'male-building' : 'female-building'">
+                      {{ data.buildingType === '1' ? '(男)' : '(女)' }}
+                    </span>
+                    {{ node.label }}
+                  </span>
+                </div>
+              </template>
+            </el-tree>
+          </div>
         </el-card>
       </div>
 
@@ -106,7 +122,7 @@
                       <div class="mb-2">
                         <span class="text-gray-500">类型:</span>{{ room.roomType === '1' ? '标准间' : room.roomType === '2' ? '单人间' : '套间' }}
                       </div>
-                      <div class="mb-2"><span class="text-gray-500">床位:</span>0/{{ room.actualBeds }}</div>
+                      <div class="mb-2"><span class="text-gray-500">床位:</span>{{ room.currentBeds || 0 }}/{{ room.actualBeds }}</div>
                       <div class="flex justify-between mt-4">
                         <div>
                           <el-button type="primary" link size="small" @click="handleManageBeds(room)">床位管理</el-button>
@@ -140,18 +156,30 @@
     <!-- 床位管理对话框 -->
     <el-dialog v-model="bedManageDialog.visible" :title="`${bedManageDialog.roomName} - 床位管理`" width="800px">
       <div class="mb-4 text-right">
-        <el-button type="primary" icon="Plus" @click="handleAddBed">添加床位</el-button>
+        <el-button v-if="!isBedLimitReached" type="primary" icon="Plus" size="small" @click="handleAddBed">添加床位</el-button>
+        <el-tag v-else type="info">已达到床位上限 ({{ bedList.length }}/{{ bedManageDialog.maxBeds }})</el-tag>
+        <!-- <el-button type="success" icon="Check" size="small" @click="handleSaveBeds">保存</el-button> -->
       </div>
       <el-table v-loading="bedLoading" :data="bedList" border>
+        <el-table-column label="床位编号" prop="bedCode" />
         <el-table-column label="床位名称" prop="bedName" />
         <el-table-column label="状态" align="center">
           <template #default="scope">
-            <el-tag v-if="scope.row.status === '0'" type="success">空闲</el-tag>
-            <el-tag v-else-if="scope.row.status === '1'" type="warning">已分配</el-tag>
+            <el-tag v-if="scope.row.bedStatus === '0'" type="success">空闲</el-tag>
+            <el-tag v-else-if="scope.row.bedStatus === '1'" type="warning">已分配</el-tag>
             <el-tag v-else type="danger">维修中</el-tag>
           </template>
         </el-table-column>
-        <el-table-column label="学生" prop="studentName" />
+        <!-- 在这里增加床位类型-->
+        <el-table-column label="床位类型" prop="bedType">
+          <template #default="scope">
+            <el-tag v-if="scope.row.bedType === '1'" type="success">下铺</el-tag>
+            <el-tag v-else-if="scope.row.bedType === '2'" type="warning">上铺</el-tag>
+            <el-tag v-else-if="scope.row.bedType === '3'" type="danger">中铺</el-tag>
+            <el-tag v-else type="info">独立床</el-tag>
+          </template>
+        </el-table-column>
+        <!-- <el-table-column label="学生" prop="studentName" /> -->
         <el-table-column label="备注" prop="remark" :show-overflow-tooltip="true" />
         <el-table-column label="操作" width="180" align="center">
           <template #default="scope">
@@ -179,7 +207,9 @@ import { ElMessage, ElMessageBox } from 'element-plus';
 import { InfoFilled, Search } from '@element-plus/icons-vue';
 import { DormTreeNode, BedInfo, DormQuery } from '@/api/xgss/xsssfjgl/types';
 import { DormRoomVO, DormRoomQuery } from '@/api/xgss/dormRoom/types';
-import { getDormTree, listBeds, delBed } from '@/api/xgss/xsssfjgl';
+import { getDormTree } from '@/api/xgss/xsssfjgl';
+import { listDormBed, delDormBed } from '@/api/xgss/dormBed';
+import type { DormBedQuery } from '@/api/xgss/dormBed/types';
 import { listDormRoom, delDormRoom } from '@/api/xgss/dormRoom';
 
 // 引入子组件
@@ -198,6 +228,11 @@ const animate = reactive({
   }
 });
 
+// 判断床位是否已达到上限的计算属性
+const isBedLimitReached = computed(() => {
+  return bedList.value.length >= bedManageDialog.maxBeds;
+});
+
 // 宿舍树数据
 const dormOptions = ref<DormTreeNode[]>([]);
 const selectedBuilding = ref<DormTreeNode>();
@@ -232,7 +267,8 @@ const roomDialog = reactive({
 const bedManageDialog = reactive({
   visible: false,
   roomId: '' as string | number,
-  roomName: ''
+  roomName: '',
+  maxBeds: 0 // 预设的最大床位数
 });
 
 // 床位表单对话框
@@ -267,7 +303,29 @@ watch(buildingName, () => {
 const getDormTreeData = async () => {
   try {
     const res = await getDormTree();
-    dormOptions.value = res.data;
+    // 处理树节点,标记非宿舍楼节点为禁用
+    const processTreeNodes = (nodes: DormTreeNode[]) => {
+      return nodes.map((node) => {
+        // 如果不是宿舍楼类型,则禁用该节点
+        if (node.type !== 'building') {
+          node.disabled = true;
+        }
+
+        // 如果是宿舍楼但没有性别信息,随机分配性别(实际项目中应该由后端提供)
+        if (node.type === 'building' && !node.gender) {
+          // 这里使用随机分配,实际项目中应该从后端获取
+          node.gender = Math.random() > 0.5 ? '1' : '2';
+        }
+
+        // 递归处理子节点
+        if (node.children && node.children.length > 0) {
+          node.children = processTreeNodes(node.children);
+        }
+        return node;
+      });
+    };
+
+    dormOptions.value = processTreeNodes(res.data);
   } catch (error) {
     console.error('获取宿舍树失败', error);
   }
@@ -292,6 +350,20 @@ const loadRoomData = async () => {
   try {
     const res = await listDormRoom(queryParams);
     roomList.value = res?.rows;
+
+    // 加载每个房间的当前床位数量
+    await Promise.all(
+      roomList.value.map(async (room) => {
+        try {
+          const bedQuery: DormBedQuery = { roomId: room.id };
+          const bedRes = await listDormBed(bedQuery);
+          room.currentBeds = bedRes?.rows?.length || 0;
+        } catch (error) {
+          console.error('获取床位数量失败', error);
+          room.currentBeds = 0;
+        }
+      })
+    );
   } catch (error) {
     console.error('获取房间列表失败', error);
   }
@@ -373,6 +445,7 @@ const handleRoomSuccess = () => {
 const handleManageBeds = async (room: DormRoomVO) => {
   bedManageDialog.roomId = room.id;
   bedManageDialog.roomName = room.roomName;
+  bedManageDialog.maxBeds = room.actualBeds || 0; // 保存预设的床位数
   bedManageDialog.visible = true;
 
   await loadBedData(room.id);
@@ -382,8 +455,13 @@ const handleManageBeds = async (room: DormRoomVO) => {
 const loadBedData = async (roomId: string | number) => {
   bedLoading.value = true;
   try {
-    const res = await listBeds(roomId);
-    bedList.value = res.data;
+    const query: DormBedQuery = {
+      roomId,
+      pageNum: 1,
+      pageSize: 100
+    };
+    const res = await listDormBed(query);
+    bedList.value = res?.rows || [];
   } catch (error) {
     console.error('获取床位列表失败', error);
   } finally {
@@ -402,7 +480,7 @@ const handleAddBed = () => {
 /** 编辑床位 */
 const handleEditBed = (bed: BedInfo) => {
   bedDialog.title = '编辑床位';
-  bedDialog.bedId = bed.bedId;
+  bedDialog.bedId = bed.id;
   bedDialog.roomId = bed.roomId;
   bedDialog.visible = true;
 };
@@ -416,10 +494,13 @@ const handleDeleteBed = (row: BedInfo) => {
   })
     .then(async () => {
       try {
-        // await delBed(row.bedId);
-        // 模拟删除成功
+        await delDormBed(row.id);
         ElMessage.success('删除成功');
-        loadBedData(bedManageDialog.roomId);
+        await loadBedData(bedManageDialog.roomId);
+        // 更新房间的当前床位数
+        if (selectedBuilding.value) {
+          loadRoomData();
+        }
       } catch (error) {
         console.error('删除床位失败', error);
       }
@@ -428,8 +509,23 @@ const handleDeleteBed = (row: BedInfo) => {
 };
 
 /** 床位操作成功回调 */
-const handleBedSuccess = () => {
-  loadBedData(bedManageDialog.roomId);
+const handleBedSuccess = async () => {
+  await loadBedData(bedManageDialog.roomId);
+  // 更新房间的当前床位数
+  if (selectedBuilding.value) {
+    loadRoomData();
+  }
+};
+
+/** 保存床位信息 */
+const handleSaveBeds = async () => {
+  try {
+    ElMessage.success('床位信息保存成功');
+    await loadBedData(bedManageDialog.roomId);
+  } catch (error) {
+    console.error('保存床位信息失败', error);
+    ElMessage.error('保存失败,请重试');
+  }
 };
 
 onMounted(() => {
@@ -460,6 +556,23 @@ watch(
   min-width: 320px;
   max-width: 320px;
   flex-shrink: 0;
+  display: flex;
+  flex-direction: column;
+  height: calc(100vh - 120px); /* 调整高度以适应页面 */
+}
+
+.tree-sidebar .el-card {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+}
+
+.tree-sidebar .el-card__body {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  padding: 15px;
+  overflow: hidden; /* 防止卡片内容溢出 */
 }
 
 .content-area {
@@ -498,4 +611,68 @@ watch(
 .empty-tip {
   min-height: 400px;
 }
+
+.custom-tree-node {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  padding-right: 8px;
+  white-space: nowrap; /* 防止节点内容换行 */
+  overflow: visible; /* 确保长内容可见 */
+  min-width: 200px; /* 设置最小宽度 */
+}
+
+.disabled-node {
+  cursor: not-allowed;
+}
+
+.male-building {
+  color: var(--el-color-primary);
+  font-weight: bold;
+}
+
+.female-building {
+  color: #ff69b4; /* 粉色 */
+  font-weight: bold;
+}
+
+.tree-container {
+  overflow-y: auto; /* 纵向滚动条 */
+  overflow-x: auto; /* 横向滚动条 */
+  max-height: unset; /* 取消最大高度限制 */
+  min-height: 0; /* 允许容器收缩 */
+  height: 100%; /* 填满父容器 */
+  flex: 1; /* 占用剩余空间 */
+  border: 1px solid #ebeef5;
+  border-radius: 4px;
+  padding: 5px;
+  margin-top: 10px;
+}
+
+.tree-container::-webkit-scrollbar {
+  width: 6px;
+  height: 6px;
+}
+
+.tree-container::-webkit-scrollbar-thumb {
+  background-color: #c0c4cc;
+  border-radius: 3px;
+}
+
+.tree-container::-webkit-scrollbar-track {
+  background-color: #f5f7fa;
+}
+
+/* 当宿舍树节点太长时,确保有足够空间显示完整内容 */
+.tree-container .el-tree-node__content {
+  min-width: fit-content;
+  height: auto;
+  min-height: 26px;
+  padding: 4px 0;
+}
+
+.tree-container .el-tree {
+  min-width: 100%; /* 确保树至少占满容器宽度 */
+  width: max-content; /* 允许内容扩展 */
+}
 </style>

+ 150 - 0
web/src/views/xgss/xsssfpejxy/components/BedDialog.vue

@@ -0,0 +1,150 @@
+<template>
+  <el-dialog v-model="visible" :title="title" width="500px" append-to-body @close="closeDialog">
+    <el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
+      <el-form-item label="床位编号" prop="bedCode">
+        <el-input v-model="form.bedCode" placeholder="请输入床位编号" maxlength="20" />
+      </el-form-item>
+      <el-form-item label="床位名称" prop="bedName">
+        <el-input v-model="form.bedName" placeholder="请输入床位名称" maxlength="20" />
+      </el-form-item>
+      <el-form-item label="床位状态" prop="bedStatus">
+        <el-radio-group v-model="form.bedStatus">
+          <el-radio label="0">空闲</el-radio>
+          <el-radio label="1" disabled>已分配</el-radio>
+          <el-radio label="2">维修中</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="床位类型" prop="bedType">
+        <el-radio-group v-model="form.bedType">
+          <el-radio label="1">下铺</el-radio>
+          <el-radio label="2">上铺</el-radio>
+          <el-radio label="3">中铺</el-radio>
+          <el-radio label="4">独立床</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="备注" prop="remark">
+        <el-input v-model="form.remark" type="textarea" placeholder="请输入备注内容" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="visible = false">取 消</el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, watch } from 'vue';
+import type { FormInstance, FormRules } from 'element-plus';
+import { DormBedForm } from '@/api/xgss/dormBed/types';
+import { addDormBed, updateDormBed, getDormBed } from '@/api/xgss/dormBed';
+
+const props = defineProps<{
+  modelValue: boolean;
+  title: string;
+  bedId?: string | number;
+  roomId: string | number;
+}>();
+
+const emit = defineEmits(['update:modelValue', 'success']);
+
+const formRef = ref<FormInstance>();
+const visible = ref(false);
+
+// 表单数据
+const initFormData: DormBedForm = {
+  id: '',
+  bedCode: '',
+  bedName: '',
+  roomId: '',
+  bedStatus: '0',
+  bedType: '4',
+  remark: ''
+};
+
+const form = reactive<DormBedForm>({ ...initFormData });
+
+// 表单校验规则
+const rules = reactive<FormRules>({
+  bedCode: [{ required: true, message: '床位编号不能为空', trigger: 'blur' }],
+  bedName: [{ required: true, message: '床位名称不能为空', trigger: 'blur' }],
+  bedStatus: [{ required: true, message: '床位状态不能为空', trigger: 'change' }],
+  bedType: [{ required: true, message: '床位类型不能为空', trigger: 'change' }]
+});
+
+// 监听visible属性变化
+watch(
+  () => props.modelValue,
+  (val) => {
+    visible.value = val;
+    if (val) {
+      resetForm();
+      // 设置表单初始值
+      form.roomId = props.roomId;
+      // 编辑模式,获取床位详情
+      if (props.bedId) {
+        console.log('开始获取床位详情, bedId:', props.bedId);
+        getDormBed(props.bedId)
+          .then((res) => {
+            console.log('获取床位详情成功:', res.data);
+            if (res.data) {
+              Object.assign(form, res.data);
+            }
+          })
+          .catch((error) => {
+            console.error('获取床位详情失败', error);
+          });
+      }
+    }
+  }
+);
+
+// 监听visible变化,同步回父组件
+watch(
+  () => visible.value,
+  (val) => {
+    emit('update:modelValue', val);
+  }
+);
+
+// 关闭对话框
+const closeDialog = () => {
+  visible.value = false;
+  resetForm();
+};
+
+// 重置表单
+const resetForm = () => {
+  Object.assign(form, { ...initFormData });
+  formRef.value?.resetFields();
+};
+
+// 提交表单
+const submitForm = () => {
+  formRef.value?.validate(async (valid) => {
+    if (valid) {
+      try {
+        if (props.bedId) {
+          // 修改
+          form.id = props.bedId;
+          await updateDormBed(form);
+        } else {
+          // 新增
+          await addDormBed(form);
+        }
+        closeDialog();
+        emit('success');
+      } catch (error) {
+        console.error('操作失败', error);
+      }
+    }
+  });
+};
+
+// 添加默认导出以支持导入
+defineOptions({
+  name: 'BedDialog'
+});
+</script>

+ 171 - 0
web/src/views/xgss/xsssfpejxy/components/RoomDialog.vue

@@ -0,0 +1,171 @@
+<template>
+  <el-dialog v-model="visible" :title="title" width="500px" append-to-body @close="closeDialog">
+    <el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
+      <el-form-item label="楼层" prop="floorNum">
+        <el-input v-model="form.floorNum" placeholder="请输入楼层" :disabled="true" />
+      </el-form-item>
+      <el-form-item label="房间编号" prop="roomCode">
+        <el-input v-model="form.roomCode" placeholder="请输入房间编号" maxlength="20" />
+      </el-form-item>
+      <el-form-item label="房间名称" prop="roomName">
+        <el-input v-model="form.roomName" placeholder="请输入房间名称" maxlength="20" />
+      </el-form-item>
+      <el-form-item label="房间类型" prop="roomType">
+        <el-select v-model="form.roomType" placeholder="请选择房间类型" clearable style="width: 100%">
+          <el-option label="标准间" value="1" />
+          <el-option label="单人间" value="2" />
+          <el-option label="套间" value="3" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="床位数量" prop="actualBeds">
+        <el-input-number v-model="form.actualBeds" :min="1" :max="8" controls-position="right" style="width: 100%" />
+      </el-form-item>
+      <el-form-item label="房间面积" prop="roomArea">
+        <el-input v-model="form.roomArea" placeholder="请输入房间面积" maxlength="20">
+          <template #append>
+            <span style="color: #000; background: transparent">平方米</span>
+          </template>
+        </el-input>
+      </el-form-item>
+      <el-form-item label="房间状态" prop="roomStatus">
+        <el-radio-group v-model="form.roomStatus">
+          <el-radio label="0">正常</el-radio>
+          <el-radio label="1">维修中</el-radio>
+          <el-radio label="2">停用</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="备注" prop="remark">
+        <el-input v-model="form.remark" type="textarea" placeholder="请输入备注内容" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="visible = false">取 消</el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, watch } from 'vue';
+import type { FormInstance, FormRules } from 'element-plus';
+import { DormRoomForm, DormRoomVO } from '@/api/xgss/dormRoom/types';
+import { addDormRoom, updateDormRoom } from '@/api/xgss/dormRoom';
+
+const props = defineProps<{
+  modelValue: boolean;
+  title: string;
+  roomId?: string | number;
+  floorNum: number;
+  buildingId: string | number;
+  room?: DormRoomVO;
+}>();
+
+const emit = defineEmits(['update:modelValue', 'success']);
+
+const formRef = ref<FormInstance>();
+const visible = ref(false);
+
+// 表单数据
+const initFormData: DormRoomForm = {
+  id: undefined,
+  roomCode: '',
+  roomName: '',
+  floorNum: 1,
+  buildingId: '',
+  actualBeds: 4,
+  roomArea: 0,
+  roomType: '1',
+  roomStatus: '0',
+  remark: ''
+};
+
+const form = reactive<DormRoomForm>({ ...initFormData });
+
+// 表单校验规则
+const rules = reactive<FormRules>({
+  roomCode: [{ required: true, message: '房间编号不能为空', trigger: 'blur' }],
+  roomName: [{ required: true, message: '房间名称不能为空', trigger: 'blur' }],
+  floorNum: [{ required: true, message: '楼层不能为空', trigger: 'blur' }],
+  actualBeds: [{ required: true, message: '床位数量不能为空', trigger: 'change' }],
+  roomType: [{ required: true, message: '房间类型不能为空', trigger: 'change' }],
+  roomStatus: [{ required: true, message: '房间状态不能为空', trigger: 'change' }]
+});
+
+// 监听visible属性变化
+watch(
+  () => props.modelValue,
+  (val) => {
+    visible.value = val;
+    if (val) {
+      // 完全重置表单,确保没有残留数据
+      resetForm();
+
+      // 重新设置基本参数
+      form.floorNum = props.floorNum;
+      form.buildingId = props.buildingId;
+
+      // 如果是编辑模式且有room数据,填充表单数据
+      if (props.room) {
+        Object.keys(form).forEach((key) => {
+          if (key in props.room) {
+            form[key] = props.room[key];
+          }
+        });
+      }
+    }
+  }
+);
+
+// 监听visible变化,同步回父组件
+watch(
+  () => visible.value,
+  (val) => {
+    emit('update:modelValue', val);
+  }
+);
+
+// 关闭对话框
+const closeDialog = () => {
+  visible.value = false;
+  resetForm();
+};
+
+// 重置表单
+const resetForm = () => {
+  // 使用深拷贝确保完全重置
+  Object.keys(form).forEach((key) => {
+    form[key] = initFormData[key];
+  });
+  formRef.value?.resetFields();
+};
+
+// 提交表单
+const submitForm = () => {
+  formRef.value?.validate(async (valid) => {
+    if (valid) {
+      try {
+        console.log(props.roomId);
+        if (props.roomId) {
+          // 修改
+          form.id = props.roomId;
+          await updateDormRoom(form);
+        } else {
+          // 新增
+          await addDormRoom(form);
+        }
+        closeDialog();
+        emit('success');
+      } catch (error) {
+        console.error('操作失败', error);
+      }
+    }
+  });
+};
+
+// 添加默认导出以支持导入
+defineOptions({
+  name: 'RoomDialog'
+});
+</script>

+ 989 - 0
web/src/views/xgss/xsssfpejxy/index.vue

@@ -0,0 +1,989 @@
+<template>
+  <div class="p-2">
+    <div class="flex-layout">
+      <!-- 宿舍树 -->
+      <div class="tree-sidebar">
+        <!-- 宿舍楼树 -->
+        <el-card shadow="hover" class="tree-card">
+          <div class="card-header-title">宿舍楼</div>
+          <el-input
+            v-model="buildingName"
+            prefix-icon="Search"
+            placeholder="请输入宿舍楼名称"
+            clearable
+            @clear="watchBuildingName"
+            @keyup.enter="watchBuildingName"
+          >
+          </el-input>
+          <el-tree
+            ref="dormTreeRef"
+            class="mt-2"
+            node-key="id"
+            :data="dormOptions"
+            :props="{ label: 'label', children: 'children', disabled: 'disabled' }"
+            :expand-on-click-node="false"
+            :filter-node-method="filterNode"
+            highlight-current
+            default-expand-all
+            @node-click="handleNodeClick"
+          >
+            <template #default="{ node, data }">
+              <div class="custom-tree-node">
+                <el-tooltip v-if="data.disabled" effect="dark" content="此节点不可选择,请选择具体的宿舍楼" placement="right">
+                  <span :class="{ 'disabled-node': data.disabled }">{{ node.label }}</span>
+                </el-tooltip>
+                <span v-else>
+                  <span v-if="data.type === 'building'" :class="data.buildingType === '1' ? 'male-building' : 'female-building'">
+                    {{ data.buildingType === '1' ? '(男)' : '(女)' }}
+                  </span>
+                  {{ node.label }}
+                </span>
+                <span v-if="data.type === 'college' || data.type === 'class'" class="bed-count">
+                  <el-tag size="small" type="success" class="ml-2">男:{{ data.maleAssigned }}/{{ data.maleTotal }}</el-tag>
+                  <el-tag size="small" type="danger" class="ml-2">女:{{ data.femaleAssigned }}/{{ data.femaleTotal }}</el-tag>
+                </span>
+              </div>
+            </template>
+          </el-tree>
+        </el-card>
+
+        <!-- 学生树 -->
+        <el-card shadow="hover" class="tree-card mt-4">
+          <div class="card-header-title">学生信息</div>
+          <el-input
+            v-model="studentSearchKey"
+            prefix-icon="Search"
+            placeholder="请输入关键词搜索"
+            clearable
+            @clear="watchStudentSearch"
+            @keyup.enter="watchStudentSearch"
+          >
+          </el-input>
+          <el-tree
+            ref="studentTreeRef"
+            class="mt-2"
+            node-key="id"
+            :data="studentTreeData"
+            :props="{ label: (data) => data.label, children: 'children' }"
+            :expand-on-click-node="false"
+            :filter-node-method="filterStudentNode"
+            highlight-current
+            default-expand-all
+            @node-click="handleStudentNodeClick"
+          >
+            <template #default="{ node, data }">
+              <span class="custom-tree-node">
+                <span>{{ node.label }}</span>
+                <span v-if="data.type === 'college' || data.type === 'class'" class="bed-count">
+                  <el-tag size="small" type="success" class="ml-2">男:{{ data.maleAssigned }}/{{ data.maleTotal }}</el-tag>
+                  <el-tag size="small" type="danger" class="ml-2">女:{{ data.femaleAssigned }}/{{ data.femaleTotal }}</el-tag>
+                </span>
+              </span>
+            </template>
+          </el-tree>
+        </el-card>
+      </div>
+
+      <div class="content-area">
+        <transition :enter-active-class="animate.searchAnimate.enter" :leave-active-class="animate.searchAnimate.leave">
+          <div v-show="showSearch" class="mb-[10px]">
+            <el-card shadow="hover">
+              <el-form ref="queryFormRef" :model="queryParams" inline>
+                <el-form-item label="房间号" prop="roomName">
+                  <el-input v-model="queryParams.roomName" placeholder="请输入房间号" clearable @keyup.enter="handleQuery" />
+                </el-form-item>
+                <el-form-item label="状态" prop="roomStatus">
+                  <el-select v-model="queryParams.roomStatus" placeholder="房间状态" clearable>
+                    <el-option label="正常" value="0" />
+                    <el-option label="维修中" value="1" />
+                    <el-option label="停用" value="2" />
+                  </el-select>
+                </el-form-item>
+                <el-form-item>
+                  <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+                  <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+                </el-form-item>
+              </el-form>
+            </el-card>
+          </div>
+        </transition>
+
+        <el-card v-if="!selectedBuilding" shadow="hover" class="empty-tip">
+          <div class="flex flex-col items-center justify-center h-[400px]">
+            <el-icon class="text-gray-400 text-5xl mb-4"><InfoFilled /></el-icon>
+            <p class="text-gray-500 text-lg">请在左侧选择宿舍楼</p>
+          </div>
+        </el-card>
+
+        <template v-else>
+          <el-card shadow="hover" class="mb-4">
+            <template #header>
+              <el-row>
+                <el-col :span="12">
+                  <el-button type="primary" icon="check" size="small" @click="handleAddRoom">分配给二级学院</el-button>
+                </el-col>
+              </el-row>
+            </template>
+
+            <!-- 操作按钮 -->
+            <div class="mb-4">
+              <el-row>
+                <el-col :span="12">
+                  <span class="font-bold">{{ selectedBuilding?.label }} - 房间管理</span>
+                  <span class="ml-4 text-blue-500">已选择: {{ selectedRoomCount }}个房间 / {{ selectedBedCount }}个床位</span>
+                </el-col>
+              </el-row>
+              <!-- <el-button type="primary" icon="Plus" @click="handleAddRoom">添加房间</el-button> -->
+            </div>
+
+            <!-- 房间及床位树形表格 -->
+            <el-table
+              ref="tableRef"
+              v-loading="loading"
+              :data="roomList"
+              row-key="id"
+              border
+              :expand-row-keys="expandedRowKeys"
+              @expand-change="handleExpandChange"
+              :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
+              style="width: 100%"
+              @selection-change="handleSelectionChange"
+            >
+              <el-table-column type="selection" width="55" :selectable="checkSelectable" />
+              <el-table-column prop="roomName" label="房间/宿舍名称" min-width="180">
+                <template #default="scope">
+                  <span v-if="!scope.row.isBed">{{ scope.row.roomName }} (房间)</span>
+                  <span v-else>{{ scope.row.bedName }} (床位)</span>
+                </template>
+              </el-table-column>
+              <el-table-column prop="floorNum" label="楼层" width="80" align="center">
+                <template #default="scope">
+                  <span v-if="!scope.row.isBed">{{ scope.row.floorNum }}</span>
+                  <span v-else>-</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="类型" width="120" align="center">
+                <template #default="scope">
+                  <template v-if="!scope.row.isBed">
+                    <span>{{ scope.row.roomType === '1' ? '标准间' : scope.row.roomType === '2' ? '单人间' : '套间' }}</span>
+                  </template>
+                  <template v-else>
+                    <el-tag v-if="scope.row.bedType === '1'" type="success" size="small">下铺</el-tag>
+                    <el-tag v-else-if="scope.row.bedType === '2'" type="warning" size="small">上铺</el-tag>
+                    <el-tag v-else-if="scope.row.bedType === '3'" type="danger" size="small">中铺</el-tag>
+                    <el-tag v-else type="info" size="small">独立床</el-tag>
+                  </template>
+                </template>
+              </el-table-column>
+              <el-table-column label="状态" width="100" align="center">
+                <template #default="scope">
+                  <template v-if="!scope.row.isBed">
+                    <el-tag v-if="scope.row.roomStatus === '0'" type="success">正常</el-tag>
+                    <el-tag v-else-if="scope.row.roomStatus === '1'" type="warning">维修中</el-tag>
+                    <el-tag v-else type="danger">停用</el-tag>
+                  </template>
+                  <template v-else>
+                    <el-tag v-if="scope.row.bedStatus === '0'" type="success">空闲</el-tag>
+                    <el-tag v-else-if="scope.row.bedStatus === '1'" type="warning">已分配</el-tag>
+                    <el-tag v-else type="danger">维修中</el-tag>
+                  </template>
+                </template>
+              </el-table-column>
+              <el-table-column prop="remark" label="备注" min-width="150" :show-overflow-tooltip="true">
+                <template #default="scope">
+                  {{ scope.row.remark }}
+                </template>
+              </el-table-column>
+              <el-table-column label="操作" width="180" align="center">
+                <template #default="scope">
+                  <template v-if="!scope.row.isBed">
+                    <!-- <el-button type="primary" link icon="Plus" @click="handleAddBed(scope.row)" /> -->
+                    <el-button type="primary" link icon="Edit" @click="handleEditRoom(scope.row)" />
+                    <el-button type="danger" link icon="Delete" @click="handleDeleteRoom(scope.row)" />
+                  </template>
+                  <template v-else>
+                    <el-button type="primary" link icon="Edit" @click="handleEditBed(scope.row)" />
+                    <el-button type="danger" link icon="Delete" @click="handleDeleteBed(scope.row)" />
+                  </template>
+                </template>
+              </el-table-column>
+            </el-table>
+          </el-card>
+        </template>
+      </div>
+    </div>
+
+    <!-- 房间管理对话框 -->
+    <room-dialog
+      v-model="roomDialog.visible"
+      :title="roomDialog.title"
+      :room-id="roomDialog.roomId"
+      :floor-num="roomDialog.floorNum"
+      :building-id="roomDialog.buildingId"
+      :room="roomDialog.room"
+      @success="handleRoomSuccess"
+    />
+
+    <!-- 床位表单对话框 -->
+    <bed-dialog
+      v-model="bedDialog.visible"
+      :title="bedDialog.title"
+      :bed-id="bedDialog.bedId"
+      :room-id="bedDialog.roomId"
+      @success="handleBedSuccess"
+    />
+  </div>
+</template>
+
+<script setup name="DormRoomTreeManagement" lang="ts">
+import { ref, computed, reactive, onMounted, nextTick, watch, getCurrentInstance } from 'vue';
+import { InfoFilled } from '@element-plus/icons-vue';
+import type { DormTreeNode, BedInfo } from '@/api/xgss/xsssfjgl/types';
+import type { DormRoomVO } from '@/api/xgss/dormRoom/types';
+import { getDormTree } from '@/api/xgss/xsssfjgl';
+import { listDormBed, delDormBed, getDormBed } from '@/api/xgss/dormBed';
+import { listDormRoom, delDormRoom } from '@/api/xgss/dormRoom';
+
+// 引入子组件
+import RoomDialog from './components/RoomDialog.vue';
+import BedDialog from './components/BedDialog.vue';
+
+const { proxy } = getCurrentInstance();
+
+// 组件状态
+const dormTreeRef = ref();
+const queryFormRef = ref();
+const tableRef = ref();
+const buildingName = ref('');
+const showSearch = ref(true);
+const loading = ref(false);
+const expandedRowKeys = ref<string[]>([]);
+
+// 选择状态
+const selectedRows = ref<any[]>([]);
+const selectedRoomCount = ref(0);
+const selectedBedCount = ref(0);
+
+const animate = reactive({
+  searchAnimate: {
+    enter: 'animate__animated animate__fadeInDown',
+    leave: 'animate__animated animate__fadeOutUp'
+  }
+});
+
+// 宿舍树数据
+const dormOptions = ref<DormTreeNode[]>([]);
+const selectedBuilding = ref<DormTreeNode>();
+
+// 房间和床位数据(合并展示)
+const roomList = ref<any[]>([]);
+
+// 查询参数
+const queryParams = reactive({
+  pageNum: 1,
+  pageSize: 10000,
+  buildingId: undefined as string | number | undefined,
+  roomName: '',
+  roomStatus: undefined as string | undefined
+});
+
+// 房间对话框
+const roomDialog = reactive({
+  visible: false,
+  title: '',
+  roomId: undefined as string | number | undefined,
+  floorNum: 1,
+  buildingId: '' as string | number,
+  room: undefined as DormRoomVO | undefined
+});
+
+// 床位表单对话框
+const bedDialog = reactive({
+  visible: false,
+  title: '',
+  bedId: undefined as string | number | undefined,
+  roomId: '' as string | number
+});
+
+// 学生树相关数据
+const studentTreeRef = ref();
+const studentSearchKey = ref('');
+const studentTreeData = ref<any[]>([
+  {
+    id: 'grade-2025',
+    label: '2025级',
+    type: 'grade',
+    children: [
+      {
+        id: 'college-cs-2025',
+        label: '计算机学院',
+        type: 'college',
+        maleAssigned: 45,
+        maleTotal: 120,
+        femaleAssigned: 25,
+        femaleTotal: 60,
+        children: [
+          {
+            id: 'class-cs-1-2025',
+            label: '计算机科学与技术1班',
+            type: 'class',
+            maleAssigned: 20,
+            maleTotal: 40,
+            femaleAssigned: 10,
+            femaleTotal: 15
+          },
+          {
+            id: 'class-cs-2-2025',
+            label: '计算机科学与技术2班',
+            type: 'class',
+            maleAssigned: 25,
+            maleTotal: 40,
+            femaleAssigned: 15,
+            femaleTotal: 20
+          }
+        ]
+      },
+      {
+        id: 'college-ee-2025',
+        label: '电子工程学院',
+        type: 'college',
+        maleAssigned: 55,
+        maleTotal: 100,
+        femaleAssigned: 35,
+        femaleTotal: 50,
+        children: [
+          {
+            id: 'class-ee-1-2025',
+            label: '电子信息工程1班',
+            type: 'class',
+            maleAssigned: 30,
+            maleTotal: 50,
+            femaleAssigned: 20,
+            femaleTotal: 25
+          },
+          {
+            id: 'class-ee-2-2025',
+            label: '电子信息工程2班',
+            type: 'class',
+            maleAssigned: 25,
+            maleTotal: 50,
+            femaleAssigned: 15,
+            femaleTotal: 25
+          }
+        ]
+      }
+    ]
+  },
+  {
+    id: 'grade-2024',
+    label: '2024级',
+    type: 'grade',
+    children: [
+      {
+        id: 'college-cs-2024',
+        label: '计算机学院',
+        type: 'college',
+        maleAssigned: 110,
+        maleTotal: 120,
+        femaleAssigned: 58,
+        femaleTotal: 60,
+        children: [
+          {
+            id: 'class-cs-1-2024',
+            label: '计算机科学与技术1班',
+            type: 'class',
+            maleAssigned: 38,
+            maleTotal: 40,
+            femaleAssigned: 14,
+            femaleTotal: 15
+          },
+          {
+            id: 'class-cs-2-2024',
+            label: '计算机科学与技术2班',
+            type: 'class',
+            maleAssigned: 37,
+            maleTotal: 40,
+            femaleAssigned: 18,
+            femaleTotal: 20
+          }
+        ]
+      }
+    ]
+  },
+  {
+    id: 'grade-2024',
+    label: '2024级',
+    type: 'grade',
+    children: [
+      {
+        id: 'college-cs-2024',
+        label: '计算机学院',
+        type: 'college',
+        maleAssigned: 110,
+        maleTotal: 120,
+        femaleAssigned: 58,
+        femaleTotal: 60,
+        children: [
+          {
+            id: 'class-cs-1-2024',
+            label: '计算机科学与技术1班',
+            type: 'class',
+            maleAssigned: 38,
+            maleTotal: 40,
+            femaleAssigned: 14,
+            femaleTotal: 15
+          },
+          {
+            id: 'class-cs-2-2024',
+            label: '计算机科学与技术2班',
+            type: 'class',
+            maleAssigned: 37,
+            maleTotal: 40,
+            femaleAssigned: 18,
+            femaleTotal: 20
+          }
+        ]
+      }
+    ]
+  },
+  {
+    id: 'grade-2024',
+    label: '2024级',
+    type: 'grade',
+    children: [
+      {
+        id: 'college-cs-2024',
+        label: '计算机学院',
+        type: 'college',
+        maleAssigned: 110,
+        maleTotal: 120,
+        femaleAssigned: 58,
+        femaleTotal: 60,
+        children: [
+          {
+            id: 'class-cs-1-2024',
+            label: '计算机科学与技术1班',
+            type: 'class',
+            maleAssigned: 38,
+            maleTotal: 40,
+            femaleAssigned: 14,
+            femaleTotal: 15
+          },
+          {
+            id: 'class-cs-2-2024',
+            label: '计算机科学与技术2班',
+            type: 'class',
+            maleAssigned: 37,
+            maleTotal: 40,
+            femaleAssigned: 18,
+            femaleTotal: 20
+          }
+        ]
+      }
+    ]
+  }
+]);
+
+/** 通过条件过滤节点 */
+const filterNode = (value: string, data: DormTreeNode) => {
+  if (!value) return true;
+  return data.label.toLowerCase().indexOf(value.toLowerCase()) !== -1;
+};
+
+/** 通过条件过滤学生节点 */
+const filterStudentNode = (value: string, data: any) => {
+  if (!value) return true;
+  return data.label.toLowerCase().indexOf(value.toLowerCase()) !== -1;
+};
+
+/** 监听宿舍楼名称变化,筛选树 */
+const watchBuildingName = () => {
+  dormTreeRef.value?.filter(buildingName.value);
+};
+
+/** 监听学生树搜索输入变化 */
+const watchStudentSearch = () => {
+  studentTreeRef.value?.filter(studentSearchKey.value);
+};
+
+// 监听buildingName变化,实时筛选宿舍楼
+watch(buildingName, () => {
+  watchBuildingName();
+});
+
+/** 获取宿舍树数据 */
+const getDormTreeData = async () => {
+  try {
+    const res = await getDormTree();
+
+    // 处理树节点,标记非宿舍楼节点为禁用
+    const processTreeNodes = (nodes: DormTreeNode[]) => {
+      return nodes.map((node) => {
+        // 如果不是宿舍楼类型,则禁用该节点
+        if (node.type !== 'building') {
+          node.disabled = true;
+        }
+
+        // 如果是宿舍楼但没有性别信息,添加宿舍楼性别类型
+        if (node.type === 'building' && !node.buildingType) {
+          // 实际项目中应该从后端获取,这里先用随机分配,后续可以修改为从后端获取
+          node.buildingType = Math.random() > 0.5 ? '1' : '2';
+        }
+
+        // 递归处理子节点
+        if (node.children && node.children.length > 0) {
+          node.children = processTreeNodes(node.children);
+        }
+        return node;
+      });
+    };
+
+    dormOptions.value = processTreeNodes(res.data);
+  } catch (error) {
+    console.error('获取宿舍树失败', error);
+  }
+};
+
+/** 处理节点单击事件 */
+const handleNodeClick = (data: DormTreeNode) => {
+  if (data.type !== 'building') {
+    // 如果不是宿舍楼,不进行操作
+    return;
+  }
+
+  // 设置选中的宿舍楼
+  selectedBuilding.value = data;
+  // 更新查询参数
+  queryParams.buildingId = data.id;
+  // 加载房间数据
+  handleQuery();
+};
+
+/** 学生树节点点击处理 */
+const handleStudentNodeClick = (data: any) => {
+  console.log('学生树节点点击:', data);
+  // 这里可以实现学生节点点击后的相关功能
+};
+
+/** 查询房间数据 */
+const handleQuery = () => {
+  loadRoomData();
+};
+
+/** 重置查询条件 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  if (selectedBuilding.value) {
+    queryParams.buildingId = selectedBuilding.value.id;
+  }
+  handleQuery();
+};
+
+/** 加载房间数据 */
+const loadRoomData = async () => {
+  if (!queryParams.buildingId) return;
+
+  loading.value = true;
+  try {
+    // 获取房间列表
+    const res = await listDormRoom(queryParams);
+    const rooms = res.rows || [];
+
+    // 存储临时结果
+    const tempList: any[] = [];
+
+    // 处理每个房间
+    for (const room of rooms) {
+      // 添加房间记录
+      const roomRecord = {
+        ...room,
+        isBed: false,
+        children: []
+      };
+
+      // 获取房间的床位信息
+      try {
+        const bedRes = await listDormBed({ roomId: room.id });
+        const beds = bedRes.rows || [];
+
+        // 将床位信息添加为房间的子项
+        if (beds.length > 0) {
+          roomRecord.children = beds.map((bed: any) => ({
+            ...bed,
+            isBed: true,
+            roomName: room.roomName // 保留房间名称,方便展示
+          }));
+        }
+      } catch (error) {
+        console.error(`获取房间 ${room.id} 的床位信息失败`, error);
+      }
+
+      tempList.push(roomRecord);
+    }
+
+    roomList.value = tempList;
+
+    // 重置选择状态
+    selectedRows.value = [];
+    selectedRoomCount.value = 0;
+    selectedBedCount.value = 0;
+  } catch (error) {
+    console.error('加载房间数据失败', error);
+  } finally {
+    loading.value = false;
+  }
+};
+
+/** 添加房间 */
+const handleAddRoom = () => {
+  if (!selectedBuilding.value) {
+    // 使用全局自动导入的 ElMessage
+    proxy.$modal.msgWarning('请先选择宿舍楼');
+    return;
+  }
+  roomDialog.title = '添加房间';
+  roomDialog.roomId = undefined;
+  roomDialog.floorNum = 1;
+  roomDialog.buildingId = selectedBuilding.value.id;
+  roomDialog.room = undefined;
+  roomDialog.visible = true;
+};
+
+/** 编辑房间 */
+const handleEditRoom = (row: any) => {
+  roomDialog.title = '编辑房间';
+  roomDialog.roomId = row.id;
+  roomDialog.floorNum = row.floorNum;
+  roomDialog.buildingId = row.buildingId;
+  roomDialog.room = { ...row };
+  roomDialog.visible = true;
+};
+
+/** 删除房间 */
+const handleDeleteRoom = (row: any) => {
+  proxy.$modal
+    .confirm(`确定删除房间 ${row.roomName} 吗?`, '警告', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning'
+    })
+    .then(async () => {
+      try {
+        await delDormRoom(row.id);
+        proxy.$modal.msgSuccess('删除成功');
+        loadRoomData(); // 刷新数据
+      } catch (error) {
+        console.error('删除房间失败', error);
+      }
+    })
+    .catch(() => {});
+};
+
+/** 房间操作成功回调 */
+const handleRoomSuccess = () => {
+  loadRoomData();
+};
+
+/** 添加床位 */
+const handleAddBed = (row: any) => {
+  bedDialog.title = '添加床位';
+  bedDialog.bedId = undefined;
+  bedDialog.roomId = row.roomId;
+  bedDialog.visible = true;
+};
+
+/** 编辑床位 */
+const handleEditBed = (row: any) => {
+  bedDialog.title = '编辑床位';
+  bedDialog.bedId = row.id;
+  bedDialog.roomId = row.roomId;
+  bedDialog.visible = true;
+};
+
+/** 删除床位 */
+const handleDeleteBed = (row: any) => {
+  proxy.$modal
+    .confirm(`确定删除床位 ${row.bedName} 吗?`, '警告', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning'
+    })
+    .then(async () => {
+      try {
+        await delDormBed(row.id);
+        proxy.$modal.msgSuccess('删除成功');
+        loadRoomData(); // 刷新数据
+      } catch (error) {
+        console.error('删除床位失败', error);
+      }
+    })
+    .catch(() => {});
+};
+
+/** 床位操作成功回调 */
+const handleBedSuccess = () => {
+  loadRoomData();
+};
+
+/** 处理行的展开/收起 */
+const handleExpandChange = (row: any, expanded: boolean) => {
+  const rowId = String(row.roomId);
+  if (expanded) {
+    // 添加到已展开行数组
+    expandedRowKeys.value.push(rowId);
+  } else {
+    // 从已展开行数组中移除
+    const index = expandedRowKeys.value.findIndex((key) => key === rowId);
+    if (index !== -1) {
+      expandedRowKeys.value.splice(index, 1);
+    }
+  }
+};
+
+/** 检查行是否可选中(只有正常状态的房间和空闲状态的床位可选) */
+const checkSelectable = (row: any) => {
+  if (row.isBed) {
+    // 床位:只有空闲状态(0)可选
+    return row.bedStatus === '0';
+  } else {
+    // 房间:只有正常状态(0)可选
+    return row.roomStatus === '0';
+  }
+};
+
+/** 处理选择变更 */
+const handleSelectionChange = (rows: any[]) => {
+  // 更新选中行数组
+  selectedRows.value = rows;
+
+  // 计算选中的房间和床位数量
+  const rooms = rows.filter((row) => !row.isBed);
+  const beds = rows.filter((row) => row.isBed);
+
+  selectedRoomCount.value = rooms.length;
+  selectedBedCount.value = beds.length;
+};
+
+/** 当选择房间时,自动选择其下所有空闲床位 */
+const toggleRoomSelection = async (room: any, isSelected: boolean) => {
+  if (!room || !room.children || room.children.length === 0) return;
+
+  // 获取该房间下所有空闲状态的床位
+  const availableBeds = room.children.filter((bed: any) => bed.bedStatus === '0');
+
+  // 根据房间选择状态,选中或取消选中所有空闲床位
+  availableBeds.forEach((bed: any) => {
+    tableRef.value?.toggleRowSelection(bed, isSelected);
+  });
+};
+
+// 监听房间选择变化
+watch(
+  selectedRows,
+  (newRows, oldRows) => {
+    // 找出新增的房间和移除的房间
+    const oldRooms = oldRows.filter((row) => !row.isBed);
+    const newRooms = newRows.filter((row) => !row.isBed);
+
+    // 找出新增选中的房间
+    const addedRooms = newRooms.filter((newRoom) => !oldRooms.some((oldRoom) => oldRoom.id === newRoom.id));
+
+    // 找出取消选中的房间
+    const removedRooms = oldRooms.filter((oldRoom) => !newRooms.some((newRoom) => newRoom.id === oldRoom.id));
+
+    // 为新增选中的房间选中所有空闲床位
+    addedRooms.forEach((room) => {
+      toggleRoomSelection(room, true);
+    });
+
+    // 为取消选中的房间取消选中所有床位
+    removedRooms.forEach((room) => {
+      toggleRoomSelection(room, false);
+    });
+  },
+  { deep: true }
+);
+
+// 组件挂载时获取宿舍树数据
+onMounted(() => {
+  getDormTreeData();
+});
+</script>
+
+<style scoped>
+.flex-layout {
+  display: flex;
+  gap: 16px;
+}
+
+.tree-sidebar {
+  width: 300px;
+  flex-shrink: 0;
+  display: flex;
+  flex-direction: column;
+}
+
+.tree-card {
+  flex: 1;
+  max-height: calc(50vh - 24px);
+  display: flex;
+  flex-direction: column;
+}
+
+.tree-card :deep(.el-card__body) {
+  flex: 1;
+  overflow-y: auto;
+  overflow-x: auto; /* 添加横向滚动 */
+  padding-top: 10px;
+}
+
+.content-area {
+  flex-grow: 1;
+  overflow: hidden; /* 防止溢出 */
+}
+
+.room-container {
+  margin-top: 16px;
+}
+
+.empty-tip {
+  height: 100%;
+}
+
+/* 添加表格横向滚动样式 */
+.el-card :deep(.el-table) {
+  width: 100%;
+  overflow-x: auto;
+}
+
+.el-card :deep(.el-table__body-wrapper),
+.el-card :deep(.el-table__header-wrapper) {
+  overflow-x: auto !important;
+}
+
+.el-card :deep(.el-table__body),
+.el-card :deep(.el-table__header) {
+  min-width: 100%;
+}
+
+/* 学生树自定义样式 */
+.card-header-title {
+  font-weight: bold;
+  margin-bottom: 10px;
+  font-size: 16px;
+  color: var(--el-color-primary);
+}
+
+.custom-tree-node {
+  display: flex;
+  justify-content: space-between;
+  width: 100%;
+  align-items: center;
+  min-width: max-content; /* 确保内容不被压缩 */
+}
+
+.bed-count {
+  font-size: 12px;
+  white-space: nowrap;
+}
+
+/* 学生树横向和纵向滚动样式 - 修复版 */
+.tree-card.mt-4 {
+  position: relative;
+}
+
+/* 学生树卡片内容区域的滚动行为 - 使用嵌套滚动容器解决滚动条问题 */
+.tree-card.mt-4 :deep(.el-card__body) {
+  position: relative;
+  display: flex;
+  flex-direction: column;
+  padding: 10px !important;
+  max-height: calc(50vh - 24px); /* 设置最大高度 */
+  overflow: hidden; /* 避免双重滚动条 */
+}
+
+/* 创建一个包装容器进行纵向滚动 */
+.tree-card.mt-4 :deep(.el-input) {
+  flex-shrink: 0; /* 防止搜索框被压缩 */
+  margin-bottom: 10px;
+}
+
+/* 添加自定义滚动容器 */
+.tree-card.mt-4 :deep(.el-tree) {
+  overflow-y: auto; /* 纵向滚动 */
+  overflow-x: auto; /* 横向滚动 */
+  max-width: 100%; /* 确保不超出父容器 */
+  min-width: 100%; /* 确保至少占满容器宽度 */
+  padding-bottom: 10px; /* 为横向滚动条留出空间 */
+  display: inline-block; /* 使容器能够根据内容扩展 */
+  width: max-content; /* 允许根据内容扩展宽度 */
+}
+
+/* 确保学生树节点内容完整显示 */
+.tree-card.mt-4 :deep(.el-tree-node) {
+  min-width: max-content;
+  white-space: nowrap;
+}
+
+.tree-card.mt-4 :deep(.el-tree-node__content) {
+  min-width: max-content;
+  width: auto !important;
+}
+
+.disabled-node {
+  cursor: not-allowed;
+}
+
+.male-building {
+  color: var(--el-color-primary);
+  font-weight: bold;
+}
+
+.female-building {
+  color: #ff69b4; /* 粉色 */
+  font-weight: bold;
+}
+
+/* 宿舍树横向和纵向滚动样式 - 修复版 */
+.tree-card:first-child {
+  position: relative;
+}
+
+/* 宿舍树卡片内容区域的滚动行为 - 使用嵌套滚动容器解决滚动条问题 */
+.tree-card:first-child :deep(.el-card__body) {
+  position: relative;
+  display: flex;
+  flex-direction: column;
+  padding: 10px !important;
+  max-height: calc(50vh - 24px); /* 设置最大高度 */
+  overflow: hidden; /* 避免双重滚动条 */
+}
+
+/* 创建一个包装容器进行纵向滚动 */
+.tree-card:first-child :deep(.el-input) {
+  flex-shrink: 0; /* 防止搜索框被压缩 */
+  margin-bottom: 10px;
+}
+
+/* 添加自定义滚动容器 */
+.tree-card:first-child :deep(.el-tree) {
+  overflow-y: auto; /* 纵向滚动 */
+  overflow-x: auto; /* 横向滚动 */
+  max-width: 100%; /* 确保不超出父容器 */
+  min-width: 100%; /* 确保至少占满容器宽度 */
+  padding-bottom: 10px; /* 为横向滚动条留出空间 */
+  display: inline-block; /* 使容器能够根据内容扩展 */
+  width: max-content; /* 允许根据内容扩展宽度 */
+}
+
+/* 确保宿舍树节点内容完整显示 */
+.tree-card:first-child :deep(.el-tree-node) {
+  min-width: max-content;
+  white-space: nowrap;
+}
+
+.tree-card:first-child :deep(.el-tree-node__content) {
+  min-width: max-content;
+  width: auto !important;
+}
+</style>