zlwu 1 rok temu
rodzic
commit
4075b903cf

+ 51 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormBedStudentController.java

@@ -270,4 +270,55 @@ public class XgDormBedStudentController extends BaseController {
         return toAjax(xgDormBedStudentService.classTeacherStayawaySelectedBeds(bedIds, classId));
         return toAjax(xgDormBedStudentService.classTeacherStayawaySelectedBeds(bedIds, classId));
     }
     }
 
 
+    /**
+     * 班主任床位互换
+     * 将两个有学生的床位进行互换
+     */
+    @SaCheckPermission("dormBedStudent:dormBedStudent:edit")
+    @Log(title = "班主任床位互换", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PostMapping("/classTeacherExchangeBeds")
+    public R<Void> classTeacherExchangeBeds(@RequestParam Long sourceBedId,
+            @RequestParam Long targetBedId,
+            @RequestParam(required = false) Long classId) {
+        return toAjax(xgDormBedStudentService.classTeacherExchangeBeds(sourceBedId, targetBedId, classId));
+    }
+
+    /**
+     * 班主任床位移动
+     * 将学生从当前床位移动到指定的空闲床位
+     */
+    @SaCheckPermission("dormBedStudent:dormBedStudent:edit")
+    @Log(title = "班主任床位移动", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PostMapping("/classTeacherMoveBed")
+    public R<Void> classTeacherMoveBed(@RequestParam Long sourceBedId,
+            @RequestParam Long targetBedId,
+            @RequestParam(required = false) Long classId) {
+        return toAjax(xgDormBedStudentService.classTeacherMoveBed(sourceBedId, targetBedId, classId));
+    }
+
+    /**
+     * 获取班级所有已入住宿舍的学生床位信息
+     * 包含床位信息(校区名称、宿舍区名称、楼栋名称、房间名称、床位名称)
+     * 和学生信息(学生姓名、学号、性别)
+     */
+    @SaCheckPermission("dormBedStudent:dormBedStudent:list")
+    @GetMapping("/getClassOccupiedBedDetails")
+    public R<List<Object>> getClassOccupiedBedDetails(@RequestParam(required = false) Long classId) {
+        List<Object> occupiedBedDetails = xgDormBedStudentService.getClassOccupiedBedDetails(classId);
+        return R.ok(occupiedBedDetails);
+    }
+
+    /**
+     * 获取班级所有未安排学生的床位信息
+     * 包含床位信息(校区名称、宿舍区名称、楼栋名称、房间名称、床位名称)
+     */
+    @SaCheckPermission("dormBedStudent:dormBedStudent:list")
+    @GetMapping("/getClassUnoccupiedBedDetails")
+    public R<List<Object>> getClassUnoccupiedBedDetails(@RequestParam(required = false) Long classId) {
+        List<Object> unoccupiedBedDetails = xgDormBedStudentService.getClassUnoccupiedBedDetails(classId);
+        return R.ok(unoccupiedBedDetails);
+    }
+
 }
 }

+ 41 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormBedStudentService.java

@@ -192,4 +192,45 @@ public interface IXgDormBedStudentService {
      * @return 是否退宿成功
      * @return 是否退宿成功
      */
      */
     Boolean classTeacherStayawaySelectedBeds(List<Long> bedIds, Long classId);
     Boolean classTeacherStayawaySelectedBeds(List<Long> bedIds, Long classId);
+
+    /**
+     * 班主任床位互换
+     * 将两个有学生的床位进行互换
+     *
+     * @param sourceBedId 源床位ID
+     * @param targetBedId 目标床位ID
+     * @param classId     班级ID
+     * @return 是否互换成功
+     */
+    Boolean classTeacherExchangeBeds(Long sourceBedId, Long targetBedId, Long classId);
+
+    /**
+     * 班主任床位移动
+     * 将学生从当前床位移动到指定的空闲床位
+     *
+     * @param sourceBedId 源床位ID
+     * @param targetBedId 目标床位ID
+     * @param classId     班级ID
+     * @return 是否移动成功
+     */
+    Boolean classTeacherMoveBed(Long sourceBedId, Long targetBedId, Long classId);
+
+    /**
+     * 获取班级所有已入住宿舍的学生床位信息
+     * 包含床位信息(校区名称、宿舍区名称、楼栋名称、房间名称、床位名称)
+     * 和学生信息(学生姓名、学号、性别)
+     *
+     * @param classId 班级ID,可为空
+     * @return 已入住床位详情列表
+     */
+    List<Object> getClassOccupiedBedDetails(Long classId);
+
+    /**
+     * 获取班级所有未安排学生的床位信息
+     * 包含床位信息(校区名称、宿舍区名称、楼栋名称、房间名称、床位名称)
+     *
+     * @param classId 班级ID,可为空
+     * @return 未安排学生的床位详情列表
+     */
+    List<Object> getClassUnoccupiedBedDetails(Long classId);
 }
 }

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

@@ -1873,4 +1873,323 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService {
         }
         }
     }
     }
 
 
+    /**
+     * 班主任床位互换
+     * 将两个有学生的床位进行互换
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean classTeacherExchangeBeds(Long sourceBedId, Long targetBedId, Long classId) {
+        try {
+            log.info("班主任床位互换开始,源床位ID: {}, 目标床位ID: {}, 班级ID: {}", sourceBedId, targetBedId, classId);
+
+            // 1. 校验参数
+            if (sourceBedId == null || targetBedId == null) {
+                throw new RuntimeException("床位ID不能为空");
+            }
+            if (sourceBedId.equals(targetBedId)) {
+                throw new RuntimeException("不能选择相同的床位进行互换");
+            }
+
+            // 2. 获取源床位和目标床位的分配信息
+            LambdaQueryWrapper<XgDormBedStudent> queryWrapper = Wrappers.lambdaQuery();
+            queryWrapper.in(XgDormBedStudent::getBedId, sourceBedId, targetBedId)
+                    .eq(XgDormBedStudent::getDeleted, "0");
+            List<XgDormBedStudent> bedStudents = baseMapper.selectList(queryWrapper);
+
+            if (bedStudents.size() != 2) {
+                throw new RuntimeException("互换床位必须都已分配学生");
+            }
+
+            // 3. 找到对应的分配记录
+            XgDormBedStudent sourceBedStudent = bedStudents.stream()
+                    .filter(bs -> bs.getBedId().equals(sourceBedId))
+                    .findFirst()
+                    .orElseThrow(() -> new RuntimeException("未找到源床位的分配记录"));
+
+            XgDormBedStudent targetBedStudent = bedStudents.stream()
+                    .filter(bs -> bs.getBedId().equals(targetBedId))
+                    .findFirst()
+                    .orElseThrow(() -> new RuntimeException("未找到目标床位的分配记录"));
+
+            // 4. 验证班级权限(如果指定了班级ID)
+            if (classId != null) {
+                if (!classId.equals(sourceBedStudent.getClassId()) && !classId.equals(targetBedStudent.getClassId())) {
+                    throw new RuntimeException("没有权限操作这些床位");
+                }
+            }
+
+            // 5. 执行互换:交换两个记录的床位ID
+            Long tempStudentId = sourceBedStudent.getStudentId();
+            Long tempClassId = sourceBedStudent.getClassId();
+
+            // 更新源床位分配记录
+            sourceBedStudent.setStudentId(targetBedStudent.getStudentId());
+            sourceBedStudent.setClassId(targetBedStudent.getClassId());
+            sourceBedStudent.setUpdateTime(new Date());
+            sourceBedStudent.setUpdateBy(LoginHelper.getUserId());
+
+            // 更新目标床位分配记录
+            targetBedStudent.setStudentId(tempStudentId);
+            targetBedStudent.setClassId(tempClassId);
+            targetBedStudent.setUpdateTime(new Date());
+            targetBedStudent.setUpdateBy(LoginHelper.getUserId());
+
+            // 6. 更新数据库
+            baseMapper.updateById(sourceBedStudent);
+            baseMapper.updateById(targetBedStudent);
+
+            log.info("班主任床位互换成功");
+            return true;
+
+        } catch (Exception e) {
+            log.error("班主任床位互换失败", e);
+            throw new RuntimeException("床位互换失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 班主任床位移动
+     * 将学生从当前床位移动到指定的空闲床位
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean classTeacherMoveBed(Long sourceBedId, Long targetBedId, Long classId) {
+        try {
+            log.info("班主任床位移动开始,源床位ID: {}, 目标床位ID: {}, 班级ID: {}", sourceBedId, targetBedId, classId);
+
+            // 1. 校验参数
+            if (sourceBedId == null || targetBedId == null) {
+                throw new RuntimeException("床位ID不能为空");
+            }
+            if (sourceBedId.equals(targetBedId)) {
+                throw new RuntimeException("不能选择相同的床位进行移动");
+            }
+
+            // 2. 检查源床位是否有学生分配
+            LambdaQueryWrapper<XgDormBedStudent> sourceQueryWrapper = Wrappers.lambdaQuery();
+            sourceQueryWrapper.eq(XgDormBedStudent::getBedId, sourceBedId)
+                    .eq(XgDormBedStudent::getDeleted, "0");
+            XgDormBedStudent sourceBedStudent = baseMapper.selectOne(sourceQueryWrapper);
+
+            if (sourceBedStudent == null) {
+                throw new RuntimeException("源床位未分配学生");
+            }
+
+            // 3. 验证班级权限(如果指定了班级ID)
+            if (classId != null && !classId.equals(sourceBedStudent.getClassId())) {
+                throw new RuntimeException("没有权限操作此床位");
+            }
+
+            // 4. 检查目标床位是否空闲
+            LambdaQueryWrapper<XgDormBedStudent> targetQueryWrapper = Wrappers.lambdaQuery();
+            targetQueryWrapper.eq(XgDormBedStudent::getBedId, targetBedId)
+                    .eq(XgDormBedStudent::getDeleted, "0");
+            XgDormBedStudent targetBedStudent = baseMapper.selectOne(targetQueryWrapper);
+
+            if (targetBedStudent != null) {
+                throw new RuntimeException("目标床位已被占用");
+            }
+
+            // 5. 检查目标床位是否存在且可用
+            XgDormBed targetBed = xgDormBedMapper.selectById(targetBedId);
+            if (targetBed == null || !"0".equals(targetBed.getDeleted())) {
+                throw new RuntimeException("目标床位不存在");
+            }
+            if (!"0".equals(targetBed.getBedStatus())) {
+                throw new RuntimeException("目标床位不可用(维修中或其他状态)");
+            }
+
+            // 6. 创建目标床位的新分配记录
+            XgDormBedStudent newBedStudent = new XgDormBedStudent();
+            newBedStudent.setBedId(targetBedId);
+            newBedStudent.setStudentId(sourceBedStudent.getStudentId());
+            newBedStudent.setClassId(sourceBedStudent.getClassId());
+            newBedStudent.setCollegeId(sourceBedStudent.getCollegeId());
+            newBedStudent.setCreateTime(new Date());
+            newBedStudent.setCreateBy(LoginHelper.getUserId());
+            newBedStudent.setDeleted("0");
+
+            // 7. 插入新记录
+            baseMapper.insert(newBedStudent);
+
+            // 8. 删除源床位的分配记录
+            sourceBedStudent.setDeleted("1");
+            sourceBedStudent.setUpdateTime(new Date());
+            sourceBedStudent.setUpdateBy(LoginHelper.getUserId());
+            baseMapper.updateById(sourceBedStudent);
+
+            log.info("班主任床位移动成功");
+            return true;
+
+        } catch (Exception e) {
+            log.error("班主任床位移动失败", e);
+            throw new RuntimeException("床位移动失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取班级所有已入住宿舍的学生床位信息
+     * 包含床位信息(校区名称、宿舍区名称、楼栋名称、房间名称、床位名称)
+     * 和学生信息(学生姓名、学号、性别)
+     */
+    @Override
+    public List<Object> getClassOccupiedBedDetails(Long classId) {
+        try {
+            log.info("开始获取班级已入住床位详情,班级ID: {}", classId);
+
+            // 查询已分配学生的床位信息
+            List<Map<String, Object>> bedDetails = baseMapper.selectMaps(
+                    Wrappers.lambdaQuery(XgDormBedStudent.class)
+                            .select(XgDormBedStudent::getBedId, XgDormBedStudent::getStudentId,
+                                    XgDormBedStudent::getClassId, XgDormBedStudent::getCollegeId)
+                            .eq(XgDormBedStudent::getClassId, classId)
+                            .isNotNull(XgDormBedStudent::getStudentId)
+                            .eq(XgDormBedStudent::getStatus, "0")
+                            .eq(XgDormBedStudent::getDeleted, "0"));
+
+            List<Object> result = new ArrayList<>();
+
+            for (Map<String, Object> bedStudent : bedDetails) {
+                Long bedId = (Long) bedStudent.get("bed_id");
+                Long studentId = (Long) bedStudent.get("student_id");
+
+                // 查询床位详细信息
+                XgDormBed bed = xgDormBedMapper.selectById(bedId);
+                if (bed == null)
+                    continue;
+
+                XgDormRoom room = xgDormRoomMapper.selectById(bed.getRoomId());
+                if (room == null)
+                    continue;
+
+                XgDormBuilding building = xgDormBuildingMapper.selectById(room.getBuildingId());
+                if (building == null)
+                    continue;
+
+                XgDormArea area = xgDormAreaMapper.selectById(building.getAreaId());
+                if (area == null)
+                    continue;
+
+                XgDormCampus campus = xgDormCampusMapper.selectById(area.getCampusId());
+                if (campus == null)
+                    continue;
+
+                // 查询学生信息
+                XgStudentMsg student = xgStudentMsgMapper.selectById(studentId);
+                if (student == null)
+                    continue;
+
+                // 组装结果数据
+                Map<String, Object> detailInfo = new LinkedHashMap<>();
+
+                // 床位信息
+                detailInfo.put("bedId", bedId);
+                detailInfo.put("campusName", campus.getCampusName());
+                detailInfo.put("areaName", area.getAreaName());
+                detailInfo.put("buildingName", building.getBuildingName());
+                detailInfo.put("roomName", room.getRoomName());
+                detailInfo.put("bedName", bed.getBedName());
+                detailInfo.put("bedType", bed.getBedType());
+
+                // 床位性别类型(基于楼栋类型)
+                String bedGenderType = building.getBuildingType(); // 1男生楼 2女生楼 3混合楼
+                detailInfo.put("bedGenderType", bedGenderType);
+
+                // 学生信息
+                detailInfo.put("studentId", studentId);
+                detailInfo.put("studentName", student.getName());
+                detailInfo.put("studentNo", student.getStudentNo());
+                detailInfo.put("gender", student.getGender());
+
+                result.add(detailInfo);
+            }
+
+            log.info("获取班级已入住床位详情成功,共{}条记录", result.size());
+            return result;
+
+        } catch (Exception e) {
+            log.error("获取班级已入住床位详情失败", e);
+            return Collections.emptyList();
+        }
+    }
+
+    /**
+     * 获取班级所有未安排学生的床位信息
+     * 包含床位信息(校区名称、宿舍区名称、楼栋名称、房间名称、床位名称)
+     */
+    @Override
+    public List<Object> getClassUnoccupiedBedDetails(Long classId) {
+        try {
+            log.info("开始获取班级未分配学生床位详情,班级ID: {}", classId);
+
+            // 查询分配给班级但未分配学生的床位信息
+            List<Map<String, Object>> bedDetails = baseMapper.selectMaps(
+                    Wrappers.lambdaQuery(XgDormBedStudent.class)
+                            .select(XgDormBedStudent::getBedId, XgDormBedStudent::getClassId,
+                                    XgDormBedStudent::getCollegeId)
+                            .eq(XgDormBedStudent::getClassId, classId)
+                            .isNull(XgDormBedStudent::getStudentId)
+                            .eq(XgDormBedStudent::getStatus, "0")
+                            .eq(XgDormBedStudent::getDeleted, "0"));
+
+            List<Object> result = new ArrayList<>();
+
+            for (Map<String, Object> bedStudent : bedDetails) {
+                Long bedId = (Long) bedStudent.get("bed_id");
+
+                // 查询床位详细信息
+                XgDormBed bed = xgDormBedMapper.selectById(bedId);
+                if (bed == null)
+                    continue;
+
+                // 确保床位可用
+                if (!"1".equals(bed.getBedStatus()) || !"0".equals(bed.getDeleted())) {
+                    continue;
+                }
+
+                XgDormRoom room = xgDormRoomMapper.selectById(bed.getRoomId());
+                if (room == null)
+                    continue;
+
+                XgDormBuilding building = xgDormBuildingMapper.selectById(room.getBuildingId());
+                if (building == null)
+                    continue;
+
+                XgDormArea area = xgDormAreaMapper.selectById(building.getAreaId());
+                if (area == null)
+                    continue;
+
+                XgDormCampus campus = xgDormCampusMapper.selectById(area.getCampusId());
+                if (campus == null)
+                    continue;
+
+                // 组装结果数据
+                Map<String, Object> detailInfo = new LinkedHashMap<>();
+
+                // 床位信息
+                detailInfo.put("bedId", bedId);
+                detailInfo.put("campusName", campus.getCampusName());
+                detailInfo.put("areaName", area.getAreaName());
+                detailInfo.put("buildingName", building.getBuildingName());
+                detailInfo.put("roomName", room.getRoomName());
+                detailInfo.put("bedName", bed.getBedName());
+                detailInfo.put("bedType", bed.getBedType());
+
+                // 床位性别类型(基于楼栋类型)
+                String bedGenderType = building.getBuildingType(); // 1男生楼 2女生楼 3混合楼
+                detailInfo.put("bedGenderType", bedGenderType);
+
+                result.add(detailInfo);
+            }
+
+            log.info("获取班级未分配学生床位详情成功,共{}条记录", result.size());
+            return result;
+
+        } catch (Exception e) {
+            log.error("获取班级未分配学生床位详情失败", e);
+            return Collections.emptyList();
+        }
+    }
+
 }
 }

+ 57 - 0
web/src/api/xg/xgss/dormBedStudent/dormBedStudent/index.ts

@@ -215,3 +215,60 @@ export const classTeacherStayawaySelectedBeds = (bedIds: number[], classId?: str
     params: { classId }
     params: { classId }
   });
   });
 };
 };
+
+/**
+ * 班主任床位互换
+ * 将两个有学生的床位进行互换
+ * @param sourceBedId 源床位ID
+ * @param targetBedId 目标床位ID
+ * @param classId 班级ID,可为空
+ */
+export const classTeacherExchangeBeds = (sourceBedId: number, targetBedId: number, classId?: string | number) => {
+  return request({
+    url: '/dormBedStudent/dormBedStudent/classTeacherExchangeBeds',
+    method: 'post',
+    params: { sourceBedId, targetBedId, classId }
+  });
+};
+
+/**
+ * 班主任床位移动
+ * 将学生从当前床位移动到指定的空闲床位
+ * @param sourceBedId 源床位ID
+ * @param targetBedId 目标床位ID
+ * @param classId 班级ID,可为空
+ */
+export const classTeacherMoveBed = (sourceBedId: number, targetBedId: number, classId?: string | number) => {
+  return request({
+    url: '/dormBedStudent/dormBedStudent/classTeacherMoveBed',
+    method: 'post',
+    params: { sourceBedId, targetBedId, classId }
+  });
+};
+
+/**
+ * 获取班级所有已入住宿舍的学生床位信息
+ * 包含床位信息(校区名称、宿舍区名称、楼栋名称、房间名称、床位名称)
+ * 和学生信息(学生姓名、学号、性别)
+ * @param classId 班级ID,可为空
+ */
+export const getClassOccupiedBedDetails = (classId?: string | number) => {
+  return request({
+    url: '/dormBedStudent/dormBedStudent/getClassOccupiedBedDetails',
+    method: 'get',
+    params: { classId }
+  });
+};
+
+/**
+ * 获取班级所有未安排学生的床位信息
+ * 包含床位信息(校区名称、宿舍区名称、楼栋名称、房间名称、床位名称)
+ * @param classId 班级ID,可为空
+ */
+export const getClassUnoccupiedBedDetails = (classId?: string | number) => {
+  return request({
+    url: '/dormBedStudent/dormBedStudent/getClassUnoccupiedBedDetails',
+    method: 'get',
+    params: { classId }
+  });
+};

+ 46 - 0
web/src/views/xg/xgss/bzrfp/bzrfp.vue

@@ -100,6 +100,7 @@
                   <el-button type="info" icon="Sort" size="small" @click="handleStudentNoAllocation">按学号分配</el-button> -->
                   <el-button type="info" icon="Sort" size="small" @click="handleStudentNoAllocation">按学号分配</el-button> -->
                   <el-button type="warning" icon="RefreshLeft" size="small" @click="handleRecallBeds">收回选中床位</el-button>
                   <el-button type="warning" icon="RefreshLeft" size="small" @click="handleRecallBeds">收回选中床位</el-button>
                   <el-button type="danger" icon="House" size="small" @click="handleStayawayBeds">退宿选中床位</el-button>
                   <el-button type="danger" icon="House" size="small" @click="handleStayawayBeds">退宿选中床位</el-button>
+                  <el-button type="info" icon="Switch" size="small" @click="handleBedTransfer">调宿</el-button>
                 </el-col>
                 </el-col>
                 <el-col :span="12" style="text-align: right">
                 <el-col :span="12" style="text-align: right">
                   <el-button
                   <el-button
@@ -294,6 +295,17 @@
       :parent-class-info="currentClassInfo"
       :parent-class-info="currentClassInfo"
       @success="handleAllocationSuccess"
       @success="handleAllocationSuccess"
     />
     />
+
+    <!-- 调宿管理对话框 -->
+    <bed-transfer-dialog
+      v-model="bedTransferDialog.visible"
+      :title="bedTransferDialog.title"
+      :selected-bed="bedTransferDialog.selectedBed"
+      :building-info="bedTransferDialog.buildingInfo"
+      :class-id="activeClassId"
+      :parent-class-info="currentClassInfo"
+      @success="handleAllocationSuccess"
+    />
   </div>
   </div>
 </template>
 </template>
 
 
@@ -325,6 +337,7 @@ import ElMessageBox from 'element-plus/es/components/message-box/index';
 
 
 // 引入子组件
 // 引入子组件
 import SelectiveAllocationDialog from './components/SelectiveAllocationDialog.vue';
 import SelectiveAllocationDialog from './components/SelectiveAllocationDialog.vue';
+import BedTransferDialog from './components/BedTransferDialog.vue';
 
 
 // 组件状态
 // 组件状态
 const dormTreeRef = ref();
 const dormTreeRef = ref();
@@ -411,6 +424,14 @@ const selectiveAllocationDialog = reactive({
   buildingInfo: undefined as DormBuildingTreeVO | undefined
   buildingInfo: undefined as DormBuildingTreeVO | undefined
 });
 });
 
 
+// 调宿对话框
+const bedTransferDialog = reactive({
+  visible: false,
+  title: '调宿管理',
+  selectedBed: undefined as any | undefined,
+  buildingInfo: undefined as DormBuildingTreeVO | undefined
+});
+
 // 表格最大高度计算
 // 表格最大高度计算
 const tableMaxHeight = computed(() => {
 const tableMaxHeight = computed(() => {
   // 根据不同屏幕尺寸动态调整预留空间
   // 根据不同屏幕尺寸动态调整预留空间
@@ -814,6 +835,31 @@ const handleStayawayBeds = async () => {
   }
   }
 };
 };
 
 
+/** 调宿管理 */
+const handleBedTransfer = () => {
+  if (!selectedBuilding.value) {
+    ElMessage.warning('请先选择宿舍楼');
+    return;
+  }
+
+  // 获取选中的已分配床位
+  const selectedAllocatedBeds = selectedRows.value.filter((row) => row.isBed && row.studentId);
+
+  if (selectedAllocatedBeds.length === 0) {
+    ElMessage.warning('请先勾选要调宿的床位(必须是已分配学生的床位)!');
+    return;
+  }
+
+  if (selectedAllocatedBeds.length > 1) {
+    ElMessage.warning('调宿操作一次只能选择一个床位!');
+    return;
+  }
+
+  bedTransferDialog.selectedBed = selectedAllocatedBeds[0];
+  bedTransferDialog.buildingInfo = selectedBuilding.value;
+  bedTransferDialog.visible = true;
+};
+
 /** 分配成功回调 */
 /** 分配成功回调 */
 const handleAllocationSuccess = () => {
 const handleAllocationSuccess = () => {
   // 清除缓存,确保数据刷新
   // 清除缓存,确保数据刷新

+ 702 - 0
web/src/views/xg/xgss/bzrfp/components/BedTransferDialog.vue

@@ -0,0 +1,702 @@
+<template>
+  <el-dialog v-model="dialogVisible" :title="title" width="1000px" :before-close="handleClose" destroy-on-close class="bed-transfer-dialog">
+    <div class="transfer-container">
+      <!-- 当前选中床位信息 -->
+      <div class="current-bed-info">
+        <div class="info-header">
+          <h3 class="info-title">
+            <el-icon><Avatar /></el-icon>
+            当前选中床位
+          </h3>
+        </div>
+
+        <el-card v-if="props.selectedBed" shadow="hover" class="bed-card">
+          <div class="bed-info-grid">
+            <div class="info-item">
+              <span class="label">学生姓名:</span>
+              <span class="value">{{ props.selectedBed.studentName }}</span>
+            </div>
+            <div class="info-item">
+              <span class="label">学号:</span>
+              <span class="value">{{ props.selectedBed.studentNo }}</span>
+            </div>
+            <div class="info-item">
+              <span class="label">性别:</span>
+              <el-tag v-if="props.selectedBed.studentGender === '0'" type="primary" size="small">男</el-tag>
+              <el-tag v-else-if="props.selectedBed.studentGender === '1'" class="female-tag" size="small">女</el-tag>
+            </div>
+            <!-- <div class="info-item">
+              <span class="label">校区:</span>
+              <span class="value">{{ props.selectedBed.campusName || '未知' }}</span>
+            </div>
+            <div class="info-item">
+              <span class="label">区域:</span>
+              <span class="value">{{ props.selectedBed.areaName || '未知' }}</span>
+            </div>
+            <div class="info-item">
+              <span class="label">楼栋:</span>
+              <span class="value">{{ props.selectedBed.buildingName || '未知' }}</span>
+            </div> -->
+            <div class="info-item">
+              <span class="label">房间号:</span>
+              <span class="value">{{ props.selectedBed.roomName }}</span>
+            </div>
+            <div class="info-item">
+              <span class="label">床位号:</span>
+              <span class="value">{{ props.selectedBed.bedName }}</span>
+            </div>
+            <div class="info-item">
+              <span class="label">床位类型:</span>
+              <el-tag v-if="props.selectedBed.bedType === '1'" type="success" size="small">下铺</el-tag>
+              <el-tag v-else-if="props.selectedBed.bedType === '2'" type="warning" size="small">上铺</el-tag>
+              <el-tag v-else-if="props.selectedBed.bedType === '3'" type="danger" size="small">中铺</el-tag>
+              <el-tag v-else type="info" size="small">独立床</el-tag>
+            </div>
+          </div>
+        </el-card>
+      </div>
+
+      <!-- 调宿模式选择 -->
+      <div class="transfer-mode">
+        <div class="mode-header">
+          <h3 class="mode-title">
+            <el-icon><Switch /></el-icon>
+            调宿模式
+          </h3>
+        </div>
+
+        <el-radio-group v-model="transferMode" @change="handleModeChange">
+          <el-radio value="exchange">
+            <el-icon><Refresh /></el-icon>
+            床位互换
+            <span class="mode-desc">与另一个有学生的床位互换</span>
+          </el-radio>
+          <el-radio value="move">
+            <el-icon><Right /></el-icon>
+            移动床位
+            <span class="mode-desc">移动到一个空闲的床位</span>
+          </el-radio>
+        </el-radio-group>
+      </div>
+
+      <!-- 目标床位选择区域 -->
+      <div class="target-bed-area">
+        <div class="area-header">
+          <h3 class="area-title">
+            <el-icon><House /></el-icon>
+            目标床位选择
+            <el-tag v-if="transferMode === 'exchange'" type="warning" size="small">请选择有学生的床位</el-tag>
+            <el-tag v-else type="success" size="small">请选择空闲床位</el-tag>
+            <el-tag v-if="props.selectedBed?.studentGender" type="info" size="small">
+              已过滤:仅显示{{ props.selectedBed.studentGender === '0' ? '男生' : '女生' }}可住床位
+            </el-tag>
+          </h3>
+          <div class="stats">
+            <span v-if="selectedTargetBed"> 已选择目标床位:{{ selectedTargetBed.roomName }} - {{ selectedTargetBed.bedName }} </span>
+            <span v-else class="text-gray-500">请选择目标床位</span>
+          </div>
+        </div>
+
+        <div class="search-bar">
+          <el-input v-model="bedSearchKey" placeholder="搜索房间号、床位号" prefix-icon="Search" clearable @input="handleBedSearch" />
+        </div>
+
+        <div class="bed-list" v-loading="bedLoading">
+          <el-table
+            ref="bedTableRef"
+            :data="filteredBedList"
+            row-key="id"
+            height="300"
+            max-height="300"
+            :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
+            :expand-row-keys="expandRowKeys"
+            @expand-change="handleExpandChange"
+            @current-change="handleTargetBedSelection"
+            highlight-current-row
+          >
+            <el-table-column prop="roomName" label="房间/床位" min-width="180">
+              <template #default="scope">
+                <span v-if="!scope.row.isBed" class="inline-flex items-center gap-1 whitespace-nowrap">
+                  <el-icon><House /></el-icon>
+                  {{ scope.row.roomName }}
+                </span>
+                <span v-else class="inline-flex items-center gap-1 whitespace-nowrap">
+                  <el-icon><Avatar /></el-icon>
+                  {{ scope.row.bedName }}
+                </span>
+              </template>
+            </el-table-column>
+            <el-table-column label="校区" width="100" align="center">
+              <template #default="scope">
+                <span v-if="scope.row.campusName" class="text-gray-700">{{ scope.row.campusName }}</span>
+                <span v-else class="text-gray-400">—</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="楼栋" width="120" align="center">
+              <template #default="scope">
+                <span v-if="scope.row.buildingName" class="text-gray-700">{{ scope.row.buildingName }}</span>
+                <span v-else class="text-gray-400">—</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="类型" width="80" align="center">
+              <template #default="scope">
+                <template v-if="scope.row.isBed">
+                  <el-tag v-if="scope.row.bedType === '1'" type="success" size="small">下铺</el-tag>
+                  <el-tag v-if="scope.row.bedType === '2'" type="warning" size="small">上铺</el-tag>
+                  <el-tag v-if="scope.row.bedType === '3'" type="danger" size="small">中铺</el-tag>
+                  <el-tag v-if="!scope.row.bedType || scope.row.bedType === '4'" type="info" size="small">独立床</el-tag>
+                </template>
+                <span v-else class="text-gray-400">
+                  <span>{{ scope.row.roomType === '1' ? '标准间' : scope.row.roomType === '2' ? '单人间' : '套间' }}</span>
+                </span>
+              </template>
+            </el-table-column>
+            <el-table-column label="性别" width="80" align="center">
+              <template #default="scope">
+                <template v-if="scope.row.isBed">
+                  <el-tag v-if="scope.row.bedGenderType === '1'" type="primary" size="small">男生</el-tag>
+                  <el-tag v-if="scope.row.bedGenderType === '2'" type="danger" size="small">女生</el-tag>
+                  <el-tag v-if="scope.row.bedGenderType === '3'" type="info" size="small">混合</el-tag>
+                </template>
+                <template v-else>
+                  <el-tag v-if="scope.row.buildingType === '1'" type="primary" size="small">男生楼</el-tag>
+                  <el-tag v-if="scope.row.buildingType === '2'" type="danger" size="small">女生楼</el-tag>
+                  <el-tag v-if="scope.row.buildingType === '3'" type="info" size="small">混合楼</el-tag>
+                </template>
+              </template>
+            </el-table-column>
+            <el-table-column label="状态" width="80" align="center">
+              <template #default="scope">
+                <template v-if="scope.row.isBed">
+                  <el-tag v-if="scope.row.studentId" type="warning" size="small">已分配</el-tag>
+                  <el-tag v-else type="success" size="small">空闲</el-tag>
+                </template>
+                <span v-else class="text-gray-400">—</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="学生" width="100" align="center">
+              <template #default="scope">
+                <span v-if="scope.row.isBed && scope.row.studentName">{{ scope.row.studentName }}</span>
+                <span v-else-if="scope.row.isBed" class="text-gray-400">—</span>
+                <span v-else class="text-gray-400">—</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="学号" width="100" align="center">
+              <template #default="scope">
+                <span v-if="scope.row.isBed && scope.row.studentNo">{{ scope.row.studentNo }}</span>
+                <span v-else-if="scope.row.isBed" class="text-gray-400">—</span>
+                <span v-else class="text-gray-400">—</span>
+              </template>
+            </el-table-column>
+            <!-- <el-table-column label="性别" width="60" align="center">
+              <template #default="scope">
+                <template v-if="scope.row.isBed && scope.row.studentGender !== undefined">
+                  <el-tag v-if="scope.row.studentGender === '0'" type="primary" size="small">男</el-tag>
+                  <el-tag v-else-if="scope.row.studentGender === '1'" class="female-tag" size="small">女</el-tag>
+                </template>
+                <span v-else-if="scope.row.isBed" class="text-gray-400">—</span>
+                <span v-else class="text-gray-400">—</span>
+              </template>
+            </el-table-column> -->
+          </el-table>
+        </div>
+      </div>
+    </div>
+
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="handleClose">取消</el-button>
+        <el-button type="primary" @click="handleConfirm" :disabled="!selectedTargetBed" :loading="confirmLoading">
+          确认{{ transferMode === 'exchange' ? '互换' : '移动' }}
+        </el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, computed, watch, nextTick } from 'vue';
+import { Avatar, House, Switch, Refresh, Right } from '@element-plus/icons-vue';
+import ElMessage from 'element-plus/es/components/message/index';
+import ElMessageBox from 'element-plus/es/components/message-box/index';
+// 导入调宿相关的API方法
+import { getClassTeacherBedDetails, classTeacherExchangeBeds, classTeacherMoveBed } from '@/api/xg/xgss/dormBedStudent/dormBedStudent';
+// @ts-expect-error 新添加的API方法,类型定义可能尚未更新
+import { getClassOccupiedBedDetails, getClassUnoccupiedBedDetails } from '@/api/xg/xgss/dormBedStudent/dormBedStudent';
+
+// 定义 props
+const props = defineProps<{
+  title?: string;
+  selectedBed?: any;
+  buildingInfo?: any;
+  classId?: string;
+  parentClassInfo?: any;
+}>();
+
+// 定义 emits
+const emit = defineEmits<{
+  'update:modelValue': [value: boolean];
+  success: [];
+}>();
+
+// 定义 model
+const dialogVisible = defineModel<boolean>({ default: false });
+
+// 状态变量
+const transferMode = ref<'exchange' | 'move'>('exchange'); // 调宿模式
+const selectedTargetBed = ref<any>(null); // 选中的目标床位
+const bedSearchKey = ref(''); // 床位搜索关键字
+const bedLoading = ref(false); // 床位数据加载状态
+const confirmLoading = ref(false); // 确认按钮加载状态
+const expandRowKeys = ref<string[]>([]); // 展开的房间行
+const bedTableRef = ref(); // 床位表格引用
+
+// 床位数据
+const bedList = ref<any[]>([]);
+
+// 过滤后的床位列表
+const filteredBedList = computed(() => {
+  if (!bedSearchKey.value) {
+    return bedList.value;
+  }
+
+  const keyword = bedSearchKey.value.toLowerCase();
+  return bedList.value.filter((room) => {
+    if (room.roomName.toLowerCase().includes(keyword)) {
+      return true;
+    }
+    if (room.children) {
+      const hasMatchBed = room.children.some((bed: any) => bed.bedName.toLowerCase().includes(keyword));
+      if (hasMatchBed) {
+        return true;
+      }
+    }
+    return false;
+  });
+});
+
+// 监听对话框显示状态
+watch(dialogVisible, (newVal) => {
+  if (newVal) {
+    loadBedData();
+  } else {
+    resetDialog();
+  }
+});
+
+// 监听调宿模式变化
+const handleModeChange = () => {
+  selectedTargetBed.value = null;
+  bedTableRef.value?.setCurrentRow(null);
+
+  // 根据新的调宿模式重新加载床位数据
+  loadBedData();
+};
+
+// 加载床位数据
+const loadBedData = async () => {
+  if (!props.classId) return;
+
+  bedLoading.value = true;
+  try {
+    let response;
+    if (transferMode.value === 'exchange') {
+      // 互换模式:获取已分配学生的床位信息
+      response = await getClassOccupiedBedDetails(props.classId);
+    } else {
+      // 移动模式:获取未分配学生的床位信息
+      response = await getClassUnoccupiedBedDetails(props.classId);
+    }
+
+    let rawData = response.data || [];
+
+    // 在互换模式下,过滤掉当前选中的床位
+    if (transferMode.value === 'exchange' && props.selectedBed?.id) {
+      const originalCount = rawData.length;
+      rawData = rawData.filter((bed: any) => bed.bedId !== props.selectedBed.id);
+      const filteredCount = rawData.length;
+      console.log(`互换模式过滤当前床位:原始床位 ${originalCount} 个,过滤后 ${filteredCount} 个`);
+    }
+
+    // 根据当前学生性别过滤床位数据
+    const filteredData = filterBedsByGender(rawData);
+
+    // 转换数据格式为树形结构(按房间分组)
+    const processedData = convertToBedTreeData(filteredData);
+    bedList.value = processedData;
+
+    // 自动展开所有房间
+    await nextTick();
+    expandRowKeys.value = processedData.map((room: any) => String(room.id));
+  } catch (error) {
+    console.error('加载床位数据失败', error);
+    ElMessage.error('加载床位数据失败');
+  } finally {
+    bedLoading.value = false;
+  }
+};
+
+// 根据当前学生性别过滤床位数据
+const filterBedsByGender = (bedDetails: any[]) => {
+  if (!props.selectedBed?.studentGender) {
+    console.warn('当前学生性别信息缺失,跳过性别过滤');
+    return bedDetails; // 如果没有学生性别信息,不进行过滤
+  }
+
+  const currentStudentGender = props.selectedBed.studentGender; // '0'男 '1'女
+  const originalCount = bedDetails.length;
+
+  const filteredBeds = bedDetails.filter((bed: any) => {
+    const bedGenderType = bed.bedGenderType; // '1'男生楼 '2'女生楼 '3'混合楼
+
+    // 混合楼的床位可以分配给任何性别的学生
+    if (bedGenderType === '3') {
+      return true;
+    }
+
+    // 男学生只能分配到男生楼的床位
+    if (currentStudentGender === '0' && bedGenderType === '1') {
+      return true;
+    }
+
+    // 女学生只能分配到女生楼的床位
+    if (currentStudentGender === '1' && bedGenderType === '2') {
+      return true;
+    }
+
+    return false;
+  });
+
+  const filteredCount = filteredBeds.length;
+  console.log(`性别过滤结果:原始床位 ${originalCount} 个,过滤后 ${filteredCount} 个(${currentStudentGender === '0' ? '男学生' : '女学生'})`);
+
+  return filteredBeds;
+};
+
+// 将床位详情数据转换为树形结构
+const convertToBedTreeData = (bedDetails: any[]) => {
+  const roomMap = new Map();
+
+  bedDetails.forEach((bed: any) => {
+    const roomKey = `${bed.buildingName}-${bed.roomName}`;
+
+    if (!roomMap.has(roomKey)) {
+      roomMap.set(roomKey, {
+        id: `room_${bed.bedId}_${Math.random()}`, // 使用随机ID避免冲突
+        roomName: bed.roomName,
+        buildingName: bed.buildingName,
+        areaName: bed.areaName,
+        campusName: bed.campusName,
+        roomType: '1', // 默认标准间
+        buildingType: bed.bedGenderType, // 楼栋性别类型
+        isBed: false,
+        children: []
+      });
+    }
+
+    const room = roomMap.get(roomKey);
+    room.children.push({
+      id: bed.bedId,
+      bedName: bed.bedName,
+      bedType: bed.bedType || '0',
+      bedGenderType: bed.bedGenderType, // 床位性别类型:1男生楼 2女生楼 3混合楼
+      studentId: bed.studentId || null,
+      studentName: bed.studentName || null,
+      studentNo: bed.studentNo || null,
+      studentGender: bed.gender || null,
+      roomName: bed.roomName,
+      buildingName: bed.buildingName,
+      areaName: bed.areaName,
+      campusName: bed.campusName,
+      isBed: true
+    });
+  });
+
+  return Array.from(roomMap.values());
+};
+
+// 处理床位搜索
+const handleBedSearch = () => {
+  // 搜索后自动展开所有房间
+  nextTick(() => {
+    if (filteredBedList.value.length > 0) {
+      expandRowKeys.value = filteredBedList.value.map((room) => String(room.id));
+    }
+  });
+};
+
+// 处理行展开
+const handleExpandChange = (row: any, expanded: boolean) => {
+  const expandKey = String(row.id);
+  if (expanded) {
+    if (!expandRowKeys.value.includes(expandKey)) {
+      expandRowKeys.value.push(expandKey);
+    }
+  } else {
+    const index = expandRowKeys.value.indexOf(expandKey);
+    if (index !== -1) {
+      expandRowKeys.value.splice(index, 1);
+    }
+  }
+};
+
+// 处理目标床位选择
+const handleTargetBedSelection = (currentRow: any) => {
+  if (!currentRow || !currentRow.isBed) {
+    selectedTargetBed.value = null;
+    return;
+  }
+
+  // 不能选择当前床位自己
+  if (currentRow.id === props.selectedBed?.id) {
+    ElMessage.warning('不能选择当前床位自己');
+    bedTableRef.value?.setCurrentRow(null);
+    selectedTargetBed.value = null;
+    return;
+  }
+
+  // 根据模式验证选择
+  if (transferMode.value === 'exchange') {
+    // 互换模式:必须选择有学生的床位
+    if (!currentRow.studentId) {
+      ElMessage.warning('互换模式下,请选择有学生的床位');
+      bedTableRef.value?.setCurrentRow(null);
+      selectedTargetBed.value = null;
+      return;
+    }
+
+    // 互换模式:验证两个学生的性别是否适合各自的目标床位
+    const currentStudentGender = props.selectedBed?.studentGender;
+    const targetStudentGender = currentRow.studentGender;
+    const currentBedGenderType = props.selectedBed?.bedGenderType;
+    const targetBedGenderType = currentRow.bedGenderType;
+
+    if (currentStudentGender && targetStudentGender && currentBedGenderType && targetBedGenderType) {
+      // 检查当前学生是否适合目标床位
+      const currentStudentCanUseTargetBed =
+        targetBedGenderType === '3' || // 混合楼
+        (currentStudentGender === '0' && targetBedGenderType === '1') || // 男学生到男生楼
+        (currentStudentGender === '1' && targetBedGenderType === '2'); // 女学生到女生楼
+
+      // 检查目标学生是否适合当前床位
+      const targetStudentCanUseCurrentBed =
+        currentBedGenderType === '3' || // 混合楼
+        (targetStudentGender === '0' && currentBedGenderType === '1') || // 男学生到男生楼
+        (targetStudentGender === '1' && currentBedGenderType === '2'); // 女学生到女生楼
+
+      if (!currentStudentCanUseTargetBed || !targetStudentCanUseCurrentBed) {
+        const currentGenderText = currentStudentGender === '0' ? '男生' : '女生';
+        const targetGenderText = targetStudentGender === '0' ? '男生' : '女生';
+        ElMessage.warning(`性别不匹配:${currentGenderText}和${targetGenderText}无法在这两个床位间互换`);
+        bedTableRef.value?.setCurrentRow(null);
+        selectedTargetBed.value = null;
+        return;
+      }
+    }
+  } else {
+    // 移动模式:必须选择空闲床位
+    if (currentRow.studentId) {
+      ElMessage.warning('移动模式下,请选择空闲床位');
+      bedTableRef.value?.setCurrentRow(null);
+      selectedTargetBed.value = null;
+      return;
+    }
+  }
+
+  selectedTargetBed.value = currentRow;
+};
+
+// 确认操作
+const handleConfirm = async () => {
+  if (!selectedTargetBed.value) {
+    ElMessage.warning('请先选择目标床位');
+    return;
+  }
+
+  confirmLoading.value = true;
+  try {
+    const confirmMessage =
+      transferMode.value === 'exchange'
+        ? `确认将 ${props.selectedBed.studentName}(${props.selectedBed.roomName}-${props.selectedBed.bedName})与 ${selectedTargetBed.value.studentName}(${selectedTargetBed.value.roomName}-${selectedTargetBed.value.bedName})进行床位互换吗?`
+        : `确认将 ${props.selectedBed.studentName}(${props.selectedBed.roomName}-${props.selectedBed.bedName})移动到 ${selectedTargetBed.value.roomName}-${selectedTargetBed.value.bedName} 吗?`;
+
+    await ElMessageBox.confirm(confirmMessage, '确认调宿', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning'
+    });
+
+    // 调用API
+    if (transferMode.value === 'exchange') {
+      await classTeacherExchangeBeds(props.selectedBed.id, selectedTargetBed.value.id, props.classId);
+    } else {
+      await classTeacherMoveBed(props.selectedBed.id, selectedTargetBed.value.id, props.classId);
+    }
+
+    ElMessage.success(`${transferMode.value === 'exchange' ? '互换' : '移动'}成功`);
+    emit('success');
+    handleClose();
+  } catch (error: any) {
+    if (error !== 'cancel') {
+      console.error('调宿操作失败', error);
+      ElMessage.error('调宿操作失败');
+    }
+  } finally {
+    confirmLoading.value = false;
+  }
+};
+
+// 关闭对话框
+const handleClose = () => {
+  dialogVisible.value = false;
+};
+
+// 重置对话框
+const resetDialog = () => {
+  transferMode.value = 'exchange';
+  selectedTargetBed.value = null;
+  bedSearchKey.value = '';
+  bedList.value = [];
+  expandRowKeys.value = [];
+  confirmLoading.value = false;
+};
+</script>
+
+<style scoped>
+.bed-transfer-dialog {
+  min-height: 600px;
+}
+
+.transfer-container {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+}
+
+.current-bed-info .info-header,
+.transfer-mode .mode-header,
+.target-bed-area .area-header {
+  margin-bottom: 12px;
+}
+
+.info-title,
+.mode-title,
+.area-title {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 16px;
+  font-weight: bold;
+  color: var(--el-color-primary);
+  margin: 0;
+}
+
+.bed-card {
+  margin-bottom: 0;
+}
+
+.bed-info-grid {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 12px;
+}
+
+.info-item {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.info-item .label {
+  font-weight: 500;
+  color: #666;
+  white-space: nowrap;
+}
+
+.info-item .value {
+  font-weight: 600;
+  color: #333;
+}
+
+.transfer-mode {
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+  padding: 16px;
+  background-color: #fafafa;
+}
+
+.transfer-mode :deep(.el-radio) {
+  display: flex;
+  align-items: center;
+  margin-bottom: 12px;
+  padding: 8px;
+  border-radius: 4px;
+  transition: all 0.3s;
+}
+
+.transfer-mode :deep(.el-radio:hover) {
+  background-color: #f0f9ff;
+}
+
+.transfer-mode :deep(.el-radio.is-checked) {
+  background-color: #e1f5fe;
+}
+
+.mode-desc {
+  color: #999;
+  font-size: 13px;
+  margin-left: 8px;
+}
+
+.target-bed-area .area-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.stats {
+  font-size: 14px;
+  color: #666;
+}
+
+.search-bar {
+  margin-bottom: 12px;
+}
+
+.bed-name {
+  font-weight: 500;
+}
+
+.female-tag {
+  background-color: rgba(255, 105, 180, 0.1) !important;
+  color: #ff69b4 !important;
+  border-color: rgba(255, 105, 180, 0.1) !important;
+}
+
+.dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+}
+
+/* 表格样式 */
+:deep(.el-table) {
+  border-radius: 4px;
+}
+
+:deep(.el-table .el-table__row) {
+  cursor: pointer;
+}
+
+:deep(.el-table .el-table__row:hover) {
+  background-color: #f5f7fa;
+}
+
+:deep(.el-table .current-row) {
+  background-color: #e6f7ff !important;
+}
+
+:deep(.el-table .current-row:hover) {
+  background-color: #d1eeff !important;
+}
+</style>