From 7b53c004abc28c5589fc4bdc77dcaef9610321f8 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Thu, 14 Aug 2025 00:32:15 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../XgDormBedStudentController.java | 15 + .../bo/XgDormBedBatchAllocateToClassBo.java | 49 ++ .../dromara/xg/mapper/XgStudentMsgMapper.java | 128 ++-- .../xg/service/IXgDormBedStudentService.java | 9 + .../impl/XgDormBedStudentServiceImpl.java | 305 ++++++++ .../service/impl/XgStudentMsgServiceImpl.java | 152 +++- .../mapper/studentMsg/XgStudentMsgMapper.xml | 50 ++ web/src/api/xg/xgss/dormBed/index.ts | 18 + ...onDialog.vue => ClassAllocationDialog.vue} | 669 ++++++++++++------ web/src/views/xg/xgss/ejxyfp/ejxyfp.vue | 24 +- 10 files changed, 1123 insertions(+), 296 deletions(-) create mode 100644 backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormBedBatchAllocateToClassBo.java rename web/src/views/xg/xgss/ejxyfp/components/{CollegeAllocationDialog.vue => ClassAllocationDialog.vue} (56%) diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormBedStudentController.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormBedStudentController.java index c0bcd59c..f8fced54 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormBedStudentController.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormBedStudentController.java @@ -20,6 +20,7 @@ import org.dromara.common.excel.utils.ExcelUtil; import org.dromara.xg.domain.vo.XgDormBedStudentVo; import org.dromara.xg.domain.bo.XgDormBedStudentBo; import org.dromara.xg.domain.bo.XgDormBedBatchAllocateBo; +import org.dromara.xg.domain.bo.XgDormBedBatchAllocateToClassBo; import org.dromara.xg.service.IXgDormBedStudentService; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.xg.domain.bo.XgDormBedBatchRecallBo; @@ -117,6 +118,20 @@ public class XgDormBedStudentController extends BaseController { return toAjax(xgDormBedStudentService.batchAllocateBedsToCollege(bo)); } + /** + * 批量分配床位给班级 + * + * @param bo 批量分配给班级参数 + */ + @SaCheckPermission("dormBedStudent:dormBedStudent:add") + @Log(title = "批量分配床位给班级", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/batchAllocateToClass") + public R batchAllocateBedsToClass( + @Validated(AddGroup.class) @RequestBody XgDormBedBatchAllocateToClassBo bo) { + return toAjax(xgDormBedStudentService.batchAllocateBedsToClass(bo)); + } + /** * 批量收回宿舍床位 */ diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormBedBatchAllocateToClassBo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormBedBatchAllocateToClassBo.java new file mode 100644 index 00000000..e2ccf8fc --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormBedBatchAllocateToClassBo.java @@ -0,0 +1,49 @@ +package org.dromara.xg.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.mybatis.core.domain.BaseEntity; + +import java.util.List; + +/** + * 床位批量分配给班级业务对象 xg_dorm_bed_batch_allocate_to_class + * + * @author zlwu + * @date 2025-01-27 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class XgDormBedBatchAllocateToClassBo extends BaseEntity { + + /** + * 床位ID列表 + */ + @NotEmpty(message = "床位ID列表不能为空", groups = { AddGroup.class }) + private List bedIds; + + /** + * 班级ID列表 + */ + @NotEmpty(message = "班级ID列表不能为空", groups = { AddGroup.class }) + private List classIds; + + /** + * 班级人数列表(与classIds一一对应) + */ + private List classStudentCounts; + + /** + * 楼栋ID + */ + private Long buildingId; + + /** + * 楼栋类型(1-男生楼,2-女生楼) + */ + private String buildingType; + +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgStudentMsgMapper.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgStudentMsgMapper.java index b2bb03e4..3f7f938c 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgStudentMsgMapper.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgStudentMsgMapper.java @@ -19,71 +19,79 @@ import java.util.Map; */ public interface XgStudentMsgMapper extends BaseMapperPlus { - /** - * 根据组织ID列表分页查询学生信息 用于导出 - * - * @param page 分页参数 - * @param queryWrapper 查询条件 - * @param deptIds 组织ID列表 - * @return 学生信息分页结果 - */ - List queryPageListExportByDeptId( - @Param("ew") LambdaQueryWrapper queryWrapper, - @Param("deptIds") List deptIds); + /** + * 根据组织ID列表分页查询学生信息 用于导出 + * + * @param page 分页参数 + * @param queryWrapper 查询条件 + * @param deptIds 组织ID列表 + * @return 学生信息分页结果 + */ + List queryPageListExportByDeptId( + @Param("ew") LambdaQueryWrapper queryWrapper, + @Param("deptIds") List deptIds); - /** - * 根据组织ID列表分页查询学生信息 - * - * @param page 分页参数 - * @param queryWrapper 查询条件 - * @param deptIds 组织ID列表 - * @return 学生信息分页结果 - */ - Page selectPageByDeptIds(@Param("page") Page page, - @Param("ew") LambdaQueryWrapper queryWrapper, - @Param("deptIds") List deptIds); + /** + * 根据组织ID列表分页查询学生信息 + * + * @param page 分页参数 + * @param queryWrapper 查询条件 + * @param deptIds 组织ID列表 + * @return 学生信息分页结果 + */ + Page selectPageByDeptIds(@Param("page") Page page, + @Param("ew") LambdaQueryWrapper queryWrapper, + @Param("deptIds") List deptIds); - /** - * 根据班级ID和姓名查询学生信息列表 - * - * @param classId 班级ID - * @param name 学生姓名(模糊查询) - * @return 学生信息列表 - */ - List selectStudentsByClassId(@Param("classId") String classId, @Param("name") String name); + /** + * 根据班级ID和姓名查询学生信息列表 + * + * @param classId 班级ID + * @param name 学生姓名(模糊查询) + * @return 学生信息列表 + */ + List selectStudentsByClassId(@Param("classId") String classId, @Param("name") String name); - /** - * 根据学生ID查询班主任ID - * - * @param studentId 学生ID - * @return 班主任ID - */ - Long selectClassTeacherIdByStudentId(@Param("studentId") Long studentId); + /** + * 根据学生ID查询班主任ID + * + * @param studentId 学生ID + * @return 班主任ID + */ + Long selectClassTeacherIdByStudentId(@Param("studentId") Long studentId); - /** - * 根据学生ID查询学院ID - * - * @param studentId 学生ID - * @return 学院ID - */ - Long selectCollegeIdByStudentId(@Param("studentId") Long studentId); + /** + * 根据学生ID查询学院ID + * + * @param studentId 学生ID + * @return 学院ID + */ + Long selectCollegeIdByStudentId(@Param("studentId") Long studentId); - /** - * 根据学院ID和角色Key查询具备该角色的用户列表 - * - * @param collegeId 学院ID - * @param roleKey 角色Key - * @return 用户ID列表 - */ - List selectUserIdsByCollegeIdAndRoleKey(@Param("collegeId") Long collegeId, - @Param("roleKey") String roleKey); + /** + * 根据学院ID和角色Key查询具备该角色的用户列表 + * + * @param collegeId 学院ID + * @param roleKey 角色Key + * @return 用户ID列表 + */ + List selectUserIdsByCollegeIdAndRoleKey(@Param("collegeId") Long collegeId, + @Param("roleKey") String roleKey); - /** - * 查询二级学院下的学院、专业、班级学生统计信息 - * - * @param secondaryCollegeId 二级学院ID - * @return 学生统计信息列表 - */ - List> getCollegeStudentStats(@Param("secondaryCollegeId") Long secondaryCollegeId); + /** + * 查询二级学院下的学院、专业、班级学生统计信息 + * + * @param secondaryCollegeId 二级学院ID + * @return 学生统计信息列表 + */ + List> getCollegeStudentStats(@Param("secondaryCollegeId") Long secondaryCollegeId); + + /** + * 查询二级学院下的学院、专业、班级学生统计信息(按床位分配统计) + * + * @param secondaryCollegeId 二级学院ID + * @return 学生统计信息列表 + */ + List> getCollegeStudentStatsByClass(@Param("secondaryCollegeId") Long secondaryCollegeId); } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormBedStudentService.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormBedStudentService.java index 7c31b8a6..ee168606 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormBedStudentService.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormBedStudentService.java @@ -3,6 +3,7 @@ package org.dromara.xg.service; import org.dromara.xg.domain.vo.XgDormBedStudentVo; import org.dromara.xg.domain.bo.XgDormBedStudentBo; import org.dromara.xg.domain.bo.XgDormBedBatchAllocateBo; +import org.dromara.xg.domain.bo.XgDormBedBatchAllocateToClassBo; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.mybatis.core.page.PageQuery; @@ -79,6 +80,14 @@ public interface IXgDormBedStudentService { Boolean batchAllocateBedsToCollege( XgDormBedBatchAllocateBo bo); + /** + * 批量分配床位给班级 + * + * @param bo 批量分配给班级请求参数 + * @return 是否分配成功 + */ + Boolean batchAllocateBedsToClass(XgDormBedBatchAllocateToClassBo bo); + /** * 批量收回宿舍床位 * diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedStudentServiceImpl.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedStudentServiceImpl.java index 8dd3d955..c33d897f 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedStudentServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedStudentServiceImpl.java @@ -45,6 +45,9 @@ import org.dromara.xg.mapper.XgSysDeptMapper; import org.dromara.xg.domain.XgSysDept; import java.util.stream.Collectors; import java.util.LinkedHashMap; +import org.dromara.xg.domain.bo.XgDormBedBatchAllocateToClassBo; +import org.dromara.xg.mapper.XgClassMsgMapper; +import org.dromara.xg.domain.XgClassMsg; /** * 床位学生分配关系Service业务层处理 @@ -65,6 +68,7 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService { private final XgDormCampusMapper xgDormCampusMapper; private final XgDormAreaMapper xgDormAreaMapper; private final XgSysDeptMapper xgSysDeptMapper; + private final XgClassMsgMapper xgClassMsgMapper; /** * 查询床位学生分配关系 @@ -504,6 +508,307 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService { return true; } + /** + * 批量分配床位给班级 + * + * @param bo 批量分配给班级请求参数 + * @return 是否分配成功 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean batchAllocateBedsToClass(XgDormBedBatchAllocateToClassBo bo) { + try { + // 1. 获取床位信息 + List beds = xgDormBedMapper.selectBatchIds(bo.getBedIds()); + + // 2. 校验床位是否已分配给班级(检查xg_dorm_bed_student表中的classId) + LambdaQueryWrapper classCheckWrapper = new LambdaQueryWrapper<>(); + classCheckWrapper.in(XgDormBedStudent::getBedId, bo.getBedIds()) + .isNotNull(XgDormBedStudent::getClassId) + .eq(XgDormBedStudent::getStatus, "0"); // 正常状态,未退宿 + + List existingClassAllocations = baseMapper.selectList(classCheckWrapper); + + if (!existingClassAllocations.isEmpty()) { + // 找出已分配的床位名称 + Set allocatedBedIds = existingClassAllocations.stream() + .map(XgDormBedStudent::getBedId) + .collect(java.util.stream.Collectors.toSet()); + + List allocatedBedNames = beds.stream() + .filter(bed -> allocatedBedIds.contains(bed.getId())) + .map(XgDormBed::getBedName) + .collect(java.util.stream.Collectors.toList()); + + throw new RuntimeException("以下床位已分配给班级,无法重复分配:" + String.join("、", allocatedBedNames)); + } + + // 3. 检查床位是否已分配给学院(必须先分配给学院才能分配给班级) + LambdaQueryWrapper collegeCheckWrapper = new LambdaQueryWrapper<>(); + collegeCheckWrapper.in(XgDormBedStudent::getBedId, bo.getBedIds()) + .isNotNull(XgDormBedStudent::getCollegeId) + .isNull(XgDormBedStudent::getClassId) // 只分配了学院,还没分配班级 + .eq(XgDormBedStudent::getStatus, "0"); // 正常状态,未退宿 + + List existingCollegeAllocations = baseMapper.selectList(collegeCheckWrapper); + + if (existingCollegeAllocations.isEmpty()) { + throw new RuntimeException("选择的床位尚未分配给学院,请先分配给学院后再分配给班级"); + } + + // 4. 验证所有床位都已分配给学院 + Set collegeAllocatedBedIds = existingCollegeAllocations.stream() + .map(XgDormBedStudent::getBedId) + .collect(java.util.stream.Collectors.toSet()); + + List unallocatedBedIds = bo.getBedIds().stream() + .filter(bedId -> !collegeAllocatedBedIds.contains(bedId)) + .collect(java.util.stream.Collectors.toList()); + + if (!unallocatedBedIds.isEmpty()) { + List unallocatedBedNames = beds.stream() + .filter(bed -> unallocatedBedIds.contains(bed.getId())) + .map(XgDormBed::getBedName) + .collect(java.util.stream.Collectors.toList()); + + throw new RuntimeException("以下床位尚未分配给学院,无法分配给班级:" + String.join("、", unallocatedBedNames)); + } + + // 5. 获取楼栋信息,用于获取校区ID和宿舍区ID + XgDormBuilding building = xgDormBuildingMapper.selectById(bo.getBuildingId()); + if (building == null) { + throw new RuntimeException("楼栋信息不存在,buildingId: " + bo.getBuildingId()); + } + + // 6. 查询班级信息,获取专业ID和学院ID + List classes = xgClassMsgMapper.selectBatchIds(bo.getClassIds()); + if (classes.size() != bo.getClassIds().size()) { + throw new RuntimeException("部分班级信息不存在,请检查班级ID"); + } + + // 建立班级ID到班级信息的映射 + Map classMap = classes.stream() + .collect(java.util.stream.Collectors.toMap(XgClassMsg::getId, cls -> cls)); + + // 7. 计算每个班级分配的床位数量 + Date now = new Date(); + Long loginUserId = LoginHelper.getUserId(); + int totalAvailableBeds = collegeAllocatedBedIds.size(); + + // 如果没有提供班级人数信息,则平均分配 + List classCounts = bo.getClassStudentCounts(); + if (classCounts == null || classCounts.isEmpty()) { + // 平均分配:每个班级分配的床位数 + int bedsPerClass = totalAvailableBeds / bo.getClassIds().size(); + int remainingBeds = totalAvailableBeds % bo.getClassIds().size(); + + classCounts = new ArrayList<>(); + for (int i = 0; i < bo.getClassIds().size(); i++) { + int count = bedsPerClass + (i < remainingBeds ? 1 : 0); + classCounts.add(count); + } + } + + // 验证班级人数总和不能超过可用床位总数 + int totalStudents = classCounts.stream().mapToInt(Integer::intValue).sum(); + if (totalStudents > totalAvailableBeds) { + throw new RuntimeException( + "班级人数总和(" + totalStudents + ")超过了可用床位总数(" + totalAvailableBeds + "),请调整分配数量"); + } + + log.info("床位分配验证通过:可用床位数{},分配学生数{},剩余床位数{}", + totalAvailableBeds, totalStudents, totalAvailableBeds - totalStudents); + + // 8. 获取房间信息,建立roomId到roomCode的映射 + Set roomIds = beds.stream().map(XgDormBed::getRoomId).collect(java.util.stream.Collectors.toSet()); + List rooms = xgDormRoomMapper.selectBatchIds(roomIds); + Map roomCodeMap = rooms.stream() + .collect(java.util.stream.Collectors.toMap(XgDormRoom::getId, XgDormRoom::getRoomCode)); + + // 9. 将床位按房间顺序排列,实现房间优先分配 + // 获取已分配给学院的床位详细信息 + List availableBeds = beds.stream() + .filter(bed -> collegeAllocatedBedIds.contains(bed.getId())) + .collect(java.util.stream.Collectors.toList()); + + // 按房间编码排序床位,确保同一房间的床位连续 + List sortedBeds = availableBeds.stream() + .sorted((b1, b2) -> { + // 首先按房间编码排序 + String roomCode1 = roomCodeMap.get(b1.getRoomId()); + String roomCode2 = roomCodeMap.get(b2.getRoomId()); + int roomCompare = roomCode1.compareTo(roomCode2); + if (roomCompare != 0) { + return roomCompare; + } + // 同一房间内按床位编码排序 + return b1.getBedCode().compareTo(b2.getBedCode()); + }) + .collect(java.util.stream.Collectors.toList()); + + // 10. 按房间分组床位,实现房间优先分配策略 + Map> bedsByRoom = sortedBeds.stream() + .collect(java.util.stream.Collectors.groupingBy(XgDormBed::getRoomId)); + + // 获取排序后的房间ID列表 + List sortedRoomIds = new ArrayList<>(bedsByRoom.keySet()); + sortedRoomIds.sort((r1, r2) -> roomCodeMap.get(r1).compareTo(roomCodeMap.get(r2))); + + // 11. 房间优先分配算法(四轮分配策略) + // 房间使用状态跟踪 + int[] roomUsage = new int[sortedRoomIds.size()]; // 记录每个房间已使用床位数 + Map roomClassMap = new java.util.HashMap<>(); // 记录房间主要分配给哪个班级 + + // 按班级顺序,采用房间优先分配策略 + for (int classIndex = 0; classIndex < bo.getClassIds().size(); classIndex++) { + Long classId = bo.getClassIds().get(classIndex); + XgClassMsg classInfo = classMap.get(classId); + int remainingStudents = classCounts.get(classIndex); + + log.info("开始为班级{}分配{}个床位,采用房间优先策略", classInfo.getClassName(), remainingStudents); + + // 第一轮:优先分配完整房间 + for (int roomIndex = 0; roomIndex < sortedRoomIds.size() && remainingStudents > 0; roomIndex++) { + Long roomId = sortedRoomIds.get(roomIndex); + List roomBeds = bedsByRoom.get(roomId); + int roomCapacity = roomBeds.size(); + + // 如果房间空闲且学生数足够分配整个房间 + if (roomUsage[roomIndex] == 0 && remainingStudents >= roomCapacity) { + // 分配整个房间给当前班级 + roomUsage[roomIndex] = roomCapacity; + roomClassMap.put(roomIndex, classId); + remainingStudents -= roomCapacity; + + log.info("为班级{}分配完整房间{},房间编码{},床位数{}", + classInfo.getClassName(), roomId, roomCodeMap.get(roomId), roomCapacity); + } + } + + // 第二轮:处理剩余学生(优先分配到已有该班级学生的房间) + for (int roomIndex = 0; roomIndex < sortedRoomIds.size() && remainingStudents > 0; roomIndex++) { + Long roomId = sortedRoomIds.get(roomIndex); + List roomBeds = bedsByRoom.get(roomId); + int availableBedsCount = roomBeds.size() - roomUsage[roomIndex]; + + // 优先分配到已有该班级学生的房间 + if (availableBedsCount > 0 && classId.equals(roomClassMap.get(roomIndex))) { + int bedsToAllocate = Math.min(remainingStudents, availableBedsCount); + roomUsage[roomIndex] += bedsToAllocate; + remainingStudents -= bedsToAllocate; + + log.info("为班级{}在已分配房间{}补充{}个床位", + classInfo.getClassName(), roomId, bedsToAllocate); + } + } + + // 第三轮:如果还有剩余学生,分配到空闲房间(避免混住) + for (int roomIndex = 0; roomIndex < sortedRoomIds.size() && remainingStudents > 0; roomIndex++) { + Long roomId = sortedRoomIds.get(roomIndex); + List roomBeds = bedsByRoom.get(roomId); + int availableBedsCount = roomBeds.size() - roomUsage[roomIndex]; + + // 分配到完全空闲的房间 + if (availableBedsCount > 0 && roomClassMap.get(roomIndex) == null) { + int bedsToAllocate = Math.min(remainingStudents, availableBedsCount); + roomUsage[roomIndex] += bedsToAllocate; + roomClassMap.put(roomIndex, classId); + remainingStudents -= bedsToAllocate; + + log.info("为班级{}分配空闲房间{}的{}个床位", + classInfo.getClassName(), roomId, bedsToAllocate); + } + } + + // 第四轮:确保所有学生都能分配到床位(允许混住) + for (int roomIndex = 0; roomIndex < sortedRoomIds.size() && remainingStudents > 0; roomIndex++) { + Long roomId = sortedRoomIds.get(roomIndex); + List roomBeds = bedsByRoom.get(roomId); + int availableBedsCount = roomBeds.size() - roomUsage[roomIndex]; + + // 分配到任何有空闲床位的房间,即使会产生混住 + if (availableBedsCount > 0) { + int bedsToAllocate = Math.min(remainingStudents, availableBedsCount); + roomUsage[roomIndex] += bedsToAllocate; + remainingStudents -= bedsToAllocate; + + Long existingClassId = roomClassMap.get(roomIndex); + if (existingClassId != null && !existingClassId.equals(classId)) { + XgClassMsg existingClass = classMap.get(existingClassId); + log.warn("房间{}产生混住:班级{}和班级{}", + roomCodeMap.get(roomId), existingClass.getClassName(), classInfo.getClassName()); + } + + log.info("为班级{}分配房间{}的{}个床位(可能混住)", + classInfo.getClassName(), roomId, bedsToAllocate); + } + } + + // 检查是否还有未分配的学生 + if (remainingStudents > 0) { + throw new RuntimeException( + "班级" + classInfo.getClassName() + "还有" + remainingStudents + "个学生未能分配床位,床位资源不足"); + } + + log.info("班级{}床位分配完成", classInfo.getClassName()); + } + + // 12. 批量更新床位学生记录,补充班级和专业信息 + for (int i = 0; i < bo.getClassIds().size(); i++) { + Long classId = bo.getClassIds().get(i); + XgClassMsg classInfo = classMap.get(classId); + + // 计算应该分配给该班级的床位数量 + int classStudentCount = classCounts.get(i); + + // 获取该班级应该分配的床位ID列表(按分配算法确定的顺序) + List classBedIds = new ArrayList<>(); + int allocatedCount = 0; + + // 按房间分配顺序找出分配给该班级的床位 + for (int roomIndex = 0; roomIndex < sortedRoomIds.size() + && allocatedCount < classStudentCount; roomIndex++) { + if (classId.equals(roomClassMap.get(roomIndex))) { + Long roomId = sortedRoomIds.get(roomIndex); + List roomBeds = bedsByRoom.get(roomId); + + // 计算这个房间分配给该班级的床位数 + int roomBedsForThisClass = Math.min(classStudentCount - allocatedCount, roomBeds.size()); + + for (int j = 0; j < roomBedsForThisClass; j++) { + classBedIds.add(roomBeds.get(j).getId()); + allocatedCount++; + } + } + } + + if (!classBedIds.isEmpty()) { + // 批量更新该班级分配的床位记录 + LambdaUpdateWrapper updateWrapper = Wrappers.lambdaUpdate(); + updateWrapper.in(XgDormBedStudent::getBedId, classBedIds) + .eq(XgDormBedStudent::getStatus, "0") // 正常状态 + .isNull(XgDormBedStudent::getClassId) // 只更新还没分配班级的记录 + .isNotNull(XgDormBedStudent::getCollegeId) // 已分配给学院 + .set(XgDormBedStudent::getMajorId, classInfo.getMajorId()) + .set(XgDormBedStudent::getClassId, classInfo.getId()) + .set(XgDormBedStudent::getUpdateTime, now) + .set(XgDormBedStudent::getUpdateBy, loginUserId); + + int updateCount = baseMapper.update(null, updateWrapper); + log.info("为班级{}({})更新了{}条床位分配记录", classInfo.getClassName(), classId, updateCount); + } + } + + log.info("成功更新{}个床位分配给{}个班级,总可用床位:{}", + bo.getBedIds().size(), bo.getClassIds().size(), totalAvailableBeds); + return true; + + } catch (Exception e) { + log.error("批量分配床位给班级失败", e); + throw new RuntimeException("床位分配失败:" + e.getMessage()); + } + } + /** * 获取当前用户二级学院的宿舍楼树形结构 * 校区-宿舍区-宿舍楼三级结构,校区和宿舍区不可选,只有宿舍楼可以选择 diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentMsgServiceImpl.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentMsgServiceImpl.java index d513f82d..74c3d32a 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentMsgServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentMsgServiceImpl.java @@ -1321,6 +1321,154 @@ public class XgStudentMsgServiceImpl implements IXgStudentMsgService { return result; } + /** + * 获取学生信息树(二级学院下的学院、专业、班级统计信息)- 按班级床位分配统计 + * + * @param secondaryCollegeId 二级学院ID,默认101 + * @return 学生信息树 + */ + public List getStudentTreeByClass(Long secondaryCollegeId) { + log.info("开始构建学生信息树(按床位分配统计),二级学院ID: {}", secondaryCollegeId); + + List result = new ArrayList<>(); + + try { + // 1. 查询二级学院下的所有学院信息,并统计男女生人数(使用新的按班级床位分配统计方法) + List> collegeStats = baseMapper.getCollegeStudentStatsByClass(secondaryCollegeId); + log.info("查询到 {} 个学院", collegeStats.size()); + + // 按学院分组 + Map>> collegeMap = collegeStats.stream() + .collect(Collectors.groupingBy(item -> { + Object collegeIdObj = item.get("college_id"); + Long collegeId = collegeIdObj instanceof Long ? (Long) collegeIdObj + : Long.valueOf(collegeIdObj.toString()); + String collegeName = (String) item.get("college_name"); + return collegeId + "_" + collegeName; + })); + + // 2. 构建学院节点 + for (Map.Entry>> collegeEntry : collegeMap.entrySet()) { + String collegeKey = collegeEntry.getKey(); + List> collegeMajors = collegeEntry.getValue(); + + String[] keyParts = collegeKey.split("_"); + Long collegeId = Long.valueOf(keyParts[0]); + String collegeName = keyParts[1]; + + // 计算学院级别的统计信息 + int collegeMaleTotal = 0; + int collegeFemaleTotal = 0; + int collegeMaleAssigned = 0; + int collegeFemaleAssigned = 0; + + for (Map item : collegeMajors) { + collegeMaleTotal += ((Number) item.get("male_total")).intValue(); + collegeFemaleTotal += ((Number) item.get("female_total")).intValue(); + collegeMaleAssigned += ((Number) item.get("male_assigned")).intValue(); + collegeFemaleAssigned += ((Number) item.get("female_assigned")).intValue(); + } + + XgStudentTreeVo collegeNode = new XgStudentTreeVo(); + collegeNode.setId(collegeId.toString()); + collegeNode.setLabel(collegeName); + collegeNode.setType("college"); + collegeNode.setCollegeId(collegeId); + collegeNode.setCollegeName(collegeName); + collegeNode.setMaleTotal(collegeMaleTotal); + collegeNode.setFemaleTotal(collegeFemaleTotal); + collegeNode.setMaleAssigned(collegeMaleAssigned); + collegeNode.setFemaleAssigned(collegeFemaleAssigned); + collegeNode.setChildren(new ArrayList<>()); + + // 按专业分组 + Map>> majorMap = collegeMajors.stream() + .collect(Collectors.groupingBy(item -> { + Object majorIdObj = item.get("major_id"); + Long majorId = majorIdObj instanceof Long ? (Long) majorIdObj + : Long.valueOf(majorIdObj.toString()); + String majorName = (String) item.get("major_name"); + return majorId + "_" + majorName; + })); + + // 3. 构建专业节点 + for (Map.Entry>> majorEntry : majorMap.entrySet()) { + String majorKey = majorEntry.getKey(); + List> majorClasses = majorEntry.getValue(); + + String[] majorKeyParts = majorKey.split("_"); + Long majorId = Long.valueOf(majorKeyParts[0]); + String majorName = majorKeyParts[1]; + + // 计算专业级别的统计信息 + int majorMaleTotal = 0; + int majorFemaleTotal = 0; + int majorMaleAssigned = 0; + int majorFemaleAssigned = 0; + + for (Map item : majorClasses) { + majorMaleTotal += ((Number) item.get("male_total")).intValue(); + majorFemaleTotal += ((Number) item.get("female_total")).intValue(); + majorMaleAssigned += ((Number) item.get("male_assigned")).intValue(); + majorFemaleAssigned += ((Number) item.get("female_assigned")).intValue(); + } + + XgStudentTreeVo majorNode = new XgStudentTreeVo(); + majorNode.setId(majorId.toString()); + majorNode.setLabel(majorName); + majorNode.setType("major"); + majorNode.setMajorId(majorId); + majorNode.setMajorName(majorName); + majorNode.setMaleTotal(majorMaleTotal); + majorNode.setFemaleTotal(majorFemaleTotal); + majorNode.setMaleAssigned(majorMaleAssigned); + majorNode.setFemaleAssigned(majorFemaleAssigned); + majorNode.setChildren(new ArrayList<>()); + + // 4. 构建班级节点 + for (Map classData : majorClasses) { + Object classIdObj = classData.get("class_id"); + Long classId = classIdObj instanceof Long ? (Long) classIdObj + : Long.valueOf(classIdObj.toString()); + String className = (String) classData.get("class_name"); + String grade = (String) classData.get("grade"); + + int classMaleTotal = ((Number) classData.get("male_total")).intValue(); + int classFemaleTotal = ((Number) classData.get("female_total")).intValue(); + int classMaleAssigned = ((Number) classData.get("male_assigned")).intValue(); + int classFemaleAssigned = ((Number) classData.get("female_assigned")).intValue(); + + XgStudentTreeVo classNode = new XgStudentTreeVo(); + classNode.setId(classId.toString()); + classNode.setLabel(className); + classNode.setType("class"); + classNode.setGrade(grade); + classNode.setClassId(classId); + classNode.setClassName(className); + classNode.setMaleTotal(classMaleTotal); + classNode.setFemaleTotal(classFemaleTotal); + classNode.setMaleAssigned(classMaleAssigned); + classNode.setFemaleAssigned(classFemaleAssigned); + classNode.setChildren(new ArrayList<>()); + + majorNode.getChildren().add(classNode); + } + + collegeNode.getChildren().add(majorNode); + } + + result.add(collegeNode); + } + + log.info("成功构建学生信息树,共 {} 个学院", result.size()); + + } catch (Exception e) { + log.error("构建学生信息树失败", e); + } + + return result; + } + /** * 获取当前用户所在学院的学生信息树 * @@ -1338,8 +1486,8 @@ public class XgStudentMsgServiceImpl implements IXgStudentMsgService { log.info("获取到当前用户的学院ID: {}", collegeId); - // 2. 调用现有的getStudentTree方法 - return getStudentTree(collegeId); + // 2. 调用按床位分配统计的方法 + return getStudentTreeByClass(collegeId); } catch (Exception e) { log.error("获取当前用户学院的学生信息树失败", e); diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/studentMsg/XgStudentMsgMapper.xml b/backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/studentMsg/XgStudentMsgMapper.xml index dae23ebf..a8671ab3 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/studentMsg/XgStudentMsgMapper.xml +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/studentMsg/XgStudentMsgMapper.xml @@ -200,4 +200,54 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" ORDER BY c.grade DESC, sm.college_name, sm.major_name, sm.class_name + + + diff --git a/web/src/api/xg/xgss/dormBed/index.ts b/web/src/api/xg/xgss/dormBed/index.ts index 118ff0cb..2e4cf3f8 100644 --- a/web/src/api/xg/xgss/dormBed/index.ts +++ b/web/src/api/xg/xgss/dormBed/index.ts @@ -111,3 +111,21 @@ export const batchAllocateBedsToCollege = (data: { data: data }); }; + +/** + * 批量分配床位给班级 + * @param data 批量分配给班级参数 + */ +export const batchAllocateBedsToClass = (data: { + bedIds: (string | number)[]; + classIds: (string | number)[]; + classStudentCounts?: number[]; + buildingId?: string | number; + buildingType?: string; +}) => { + return request({ + url: '/dormBedStudent/dormBedStudent/batchAllocateToClass', + method: 'post', + data: data + }); +}; diff --git a/web/src/views/xg/xgss/ejxyfp/components/CollegeAllocationDialog.vue b/web/src/views/xg/xgss/ejxyfp/components/ClassAllocationDialog.vue similarity index 56% rename from web/src/views/xg/xgss/ejxyfp/components/CollegeAllocationDialog.vue rename to web/src/views/xg/xgss/ejxyfp/components/ClassAllocationDialog.vue index 78570767..0a1e3668 100644 --- a/web/src/views/xg/xgss/ejxyfp/components/CollegeAllocationDialog.vue +++ b/web/src/views/xg/xgss/ejxyfp/components/ClassAllocationDialog.vue @@ -37,15 +37,20 @@ {{ node.label }} - 空闲 - 已分配 + 可分配 + 已分配 维修中 - - - + + + - {{ data.bedData.collegeName }} + {{ data.bedData.majorName }} + - + {{ data.bedData.className }} @@ -60,12 +65,12 @@ - - + + @@ -73,7 +78,7 @@
- +
- + - -
-
- 学院床位分配 - (为每个学院指定分配的床位数) + +
+
+ 班级床位分配 + (为每个班级指定分配的床位数) + + + +
-