zlwu 1 år sedan
förälder
incheckning
aba267170a

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

@@ -22,6 +22,7 @@ import org.dromara.xg.domain.bo.XgDormBedStudentBo;
 import org.dromara.xg.domain.bo.XgDormBedBatchAllocateBo;
 import org.dromara.xg.domain.bo.XgDormBedBatchAllocateBo;
 import org.dromara.xg.service.IXgDormBedStudentService;
 import org.dromara.xg.service.IXgDormBedStudentService;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.xg.domain.bo.XgDormBedBatchRecallBo;
 
 
 /**
 /**
  * 床位学生分配关系
  * 床位学生分配关系
@@ -115,4 +116,14 @@ public class XgDormBedStudentController extends BaseController {
         return toAjax(xgDormBedStudentService.batchAllocateBedsToCollege(bo));
         return toAjax(xgDormBedStudentService.batchAllocateBedsToCollege(bo));
     }
     }
 
 
+    /**
+     * 批量收回宿舍床位
+     */
+    @SaCheckPermission("dormBedStudent:dormBedStudent:edit")
+    @Log(title = "批量收回宿舍床位", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PostMapping("/batchRecall")
+    public R<Void> batchRecallBeds(@Validated(AddGroup.class) @RequestBody XgDormBedBatchRecallBo bo) {
+        return toAjax(xgDormBedStudentService.batchRecallBeds(bo));
+    }
 }
 }

+ 1 - 1
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgStudentMsg.java

@@ -516,7 +516,7 @@ public class XgStudentMsg extends TenantEntity {
     private String dormitoryRoom;
     private String dormitoryRoom;
 
 
     /**
     /**
-     * 扩展字段1
+     * 扩展字段1 - 床位id
      */
      */
     private String extField1;
     private String extField1;
 
 

+ 1 - 1
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgStudentMsgBatch.java

@@ -516,7 +516,7 @@ public class XgStudentMsgBatch extends TenantEntity {
     private String dormitoryRoom;
     private String dormitoryRoom;
 
 
     /**
     /**
-     * 扩展字段1
+     * 扩展字段1 - 床位id
      */
      */
     private String extField1;
     private String extField1;
 
 

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

@@ -0,0 +1,23 @@
+package org.dromara.xg.domain.bo;
+
+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;
+
+/**
+ * 床位批量收回业务对象
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class XgDormBedBatchRecallBo extends BaseEntity {
+
+    /**
+     * 床位ID列表
+     */
+    @NotEmpty(message = "床位ID列表不能为空", groups = { AddGroup.class })
+    private List<Long> bedIds;
+}

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

@@ -517,7 +517,7 @@ public class XgStudentMsgBatchBo extends BaseEntity {
     private String dormitoryRoom;
     private String dormitoryRoom;
 
 
     /**
     /**
-     * 扩展字段1
+     * 扩展字段1 扩展字段1 - 床位id
      */
      */
     private String extField1;
     private String extField1;
 
 

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

@@ -517,7 +517,7 @@ public class XgStudentMsgBo extends BaseEntity {
     private String dormitoryRoom;
     private String dormitoryRoom;
 
 
     /**
     /**
-     * 扩展字段1
+     * 扩展字段1 扩展字段1 - 床位id
      */
      */
     private String extField1;
     private String extField1;
 
 

+ 1 - 1
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgStudentMsgBatchVo.java

@@ -623,7 +623,7 @@ public class XgStudentMsgBatchVo implements Serializable {
     private String dormitoryRoom;
     private String dormitoryRoom;
 
 
     /**
     /**
-     * 扩展字段1
+     * 扩展字段1 扩展字段1 - 床位id
      */
      */
     @ExcelProperty(value = "扩展字段1")
     @ExcelProperty(value = "扩展字段1")
     private String extField1;
     private String extField1;

+ 1 - 1
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgStudentMsgVo.java

@@ -623,7 +623,7 @@ public class XgStudentMsgVo implements Serializable {
     private String dormitoryRoom;
     private String dormitoryRoom;
 
 
     /**
     /**
-     * 扩展字段1
+     * 扩展字段1 扩展字段1 - 床位id
      */
      */
     @ExcelProperty(value = "扩展字段1")
     @ExcelProperty(value = "扩展字段1")
     private String extField1;
     private String extField1;

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

@@ -9,6 +9,8 @@ import org.dromara.common.mybatis.core.page.PageQuery;
 import java.util.Collection;
 import java.util.Collection;
 import java.util.List;
 import java.util.List;
 
 
+import org.dromara.xg.domain.bo.XgDormBedBatchRecallBo;
+
 /**
 /**
  * 床位学生分配关系Service接口
  * 床位学生分配关系Service接口
  *
  *
@@ -75,4 +77,12 @@ public interface IXgDormBedStudentService {
      */
      */
     Boolean batchAllocateBedsToCollege(
     Boolean batchAllocateBedsToCollege(
             XgDormBedBatchAllocateBo bo);
             XgDormBedBatchAllocateBo bo);
+
+    /**
+     * 批量收回宿舍床位
+     *
+     * @param bo 批量收回请求参数
+     * @return 是否成功
+     */
+    Boolean batchRecallBeds(XgDormBedBatchRecallBo bo);
 }
 }

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

@@ -31,6 +31,12 @@ import java.util.Collection;
 import java.util.Date;
 import java.util.Date;
 import java.util.ArrayList;
 import java.util.ArrayList;
 
 
+import org.dromara.xg.domain.bo.XgDormBedBatchRecallBo;
+import org.dromara.xg.mapper.XgStudentMsgMapper;
+import org.dromara.xg.domain.XgStudentMsg;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import org.dromara.common.satoken.utils.LoginHelper;
+
 /**
 /**
  * 床位学生分配关系Service业务层处理
  * 床位学生分配关系Service业务层处理
  *
  *
@@ -46,6 +52,7 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService {
     private final XgDormBedMapper xgDormBedMapper;
     private final XgDormBedMapper xgDormBedMapper;
     private final XgDormBuildingMapper xgDormBuildingMapper;
     private final XgDormBuildingMapper xgDormBuildingMapper;
     private final XgDormRoomMapper xgDormRoomMapper;
     private final XgDormRoomMapper xgDormRoomMapper;
+    private final XgStudentMsgMapper xgStudentMsgMapper;
 
 
     /**
     /**
      * 查询床位学生分配关系
      * 查询床位学生分配关系
@@ -421,4 +428,67 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService {
             allocationRecords.add(allocation);
             allocationRecords.add(allocation);
         }
         }
     }
     }
+
+    /**
+     * 批量收回宿舍床位
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean batchRecallBeds(XgDormBedBatchRecallBo bo) {
+        Date now = new Date();
+        Long loginUserId = LoginHelper.getUserId();
+
+        // 1) 查出这些床位当前有效的分配关系(status=0, deleted=0)
+        LambdaQueryWrapper<XgDormBedStudent> activeAssignQuery = Wrappers.lambdaQuery();
+        activeAssignQuery.in(XgDormBedStudent::getBedId, bo.getBedIds())
+                .eq(XgDormBedStudent::getStatus, "0")
+                .eq(XgDormBedStudent::getDeleted, "0");
+        List<XgDormBedStudent> activeAssignments = baseMapper.selectList(activeAssignQuery);
+
+        if (activeAssignments.isEmpty()) {
+            return true; // 没有需要处理的数据
+        }
+
+        // 2) 更新分配关系:status=1(已退宿), checkout_time=now, update_time=now,
+        // update_by=loginUserId
+        LambdaUpdateWrapper<XgDormBedStudent> updateAssign = Wrappers.lambdaUpdate();
+        updateAssign.in(XgDormBedStudent::getBedId, bo.getBedIds())
+                .eq(XgDormBedStudent::getStatus, "0")
+                .eq(XgDormBedStudent::getDeleted, "0")
+                .set(XgDormBedStudent::getStatus, "1")
+                .set(XgDormBedStudent::getCheckoutTime, now)
+                .set(XgDormBedStudent::getUpdateTime, now)
+                .set(XgDormBedStudent::getUpdateBy, loginUserId);
+        baseMapper.update(null, updateAssign);
+
+        // 3) 更新床位:bed_status=0(空闲), update_time=now, update_by=loginUserId
+        LambdaUpdateWrapper<XgDormBed> updateBed = Wrappers.lambdaUpdate();
+        updateBed.in(XgDormBed::getId, bo.getBedIds())
+                .eq(XgDormBed::getDeleted, "0")
+                .set(XgDormBed::getBedStatus, "0")
+                .set(XgDormBed::getUpdateTime, now)
+                .set(XgDormBed::getUpdateBy, loginUserId);
+        xgDormBedMapper.update(null, updateBed);
+
+        // 4) 更新学生信息:找到受影响的studentId,设置 student_status=1,清空
+        // ext_field1/dormitory_room/dormitory_building
+        Set<Long> affectedStudentIds = activeAssignments.stream()
+                .map(XgDormBedStudent::getStudentId)
+                .filter(java.util.Objects::nonNull)
+                .collect(java.util.stream.Collectors.toSet());
+        if (!affectedStudentIds.isEmpty()) {
+            LambdaUpdateWrapper<XgStudentMsg> updateStudent = Wrappers.lambdaUpdate();
+            updateStudent.in(XgStudentMsg::getId, affectedStudentIds)
+                    .eq(XgStudentMsg::getDeleted, 0L)
+                    .set(XgStudentMsg::getStudentStatus, "1")
+                    .set(XgStudentMsg::getExtField1, null)
+                    .set(XgStudentMsg::getDormitoryRoom, null)
+                    .set(XgStudentMsg::getDormitoryBuilding, null)
+                    .set(XgStudentMsg::getUpdateTime, now)
+                    .set(XgStudentMsg::getUpdateBy, loginUserId);
+            xgStudentMsgMapper.update(null, updateStudent);
+        }
+
+        return true;
+    }
 }
 }

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

@@ -11,6 +11,7 @@
 - [4. 宿舍床位管理](#4-宿舍床位管理)
 - [4. 宿舍床位管理](#4-宿舍床位管理)
   - [4.1 获取房间床位详细信息列表](#41-获取房间床位详细信息列表)
   - [4.1 获取房间床位详细信息列表](#41-获取房间床位详细信息列表)
   - [4.2 获取宿舍楼床位详细信息列表](#42-获取宿舍楼床位详细信息列表)
   - [4.2 获取宿舍楼床位详细信息列表](#42-获取宿舍楼床位详细信息列表)
+  - [4.3 批量收回宿舍床位](#43-批量收回宿舍床位)
 
 
 ## 1. 宿舍楼树形结构
 ## 1. 宿舍楼树形结构
 
 
@@ -678,4 +679,42 @@ async function loadBedDetailsByBuilding(buildingId) {
 1. **宿舍管理**:管理员查看房间或宿舍楼的床位分配情况
 1. **宿舍管理**:管理员查看房间或宿舍楼的床位分配情况
 2. **学生信息核查**:快速查看入住学生的基本信息
 2. **学生信息核查**:快速查看入住学生的基本信息
 3. **统计分析**:进行床位利用率、学院分布等数据分析
 3. **统计分析**:进行床位利用率、学院分布等数据分析
-4. **报表生成**:生成宿舍入住情况报表 
+4. **报表生成**:生成宿舍入住情况报表 
+
+### 4.3 批量收回宿舍床位
+
+#### 接口说明
+将选中的床位从当前分配状态恢复为空闲状态,并记录退宿时间等信息。
+
+#### 请求方式
+```
+POST /dormBedStudent/dormBedStudent/batchRecall
+```
+
+#### 权限要求
+```
+dormBedStudent:dormBedStudent:edit
+```
+
+#### 请求参数
+| 参数名 | 类型 | 必须 | 说明 |
+|--------|------|------|------|
+| bedIds | Long[] | 是 | 要收回的床位ID列表 |
+
+示例:
+```json
+{ "bedIds": [1001, 1002, 1003] }
+```
+
+#### 处理逻辑
+- 更新 `xg_dorm_bed_student`:按 `bed_id`、`status=0`、`deleted=0` 命中记录,设置 `status=1(已退宿)`、`checkout_time=当前时间`、`update_time=当前时间`、`update_by=当前用户`。
+- 更新 `xg_dorm_bed`:按 `id in bedIds` 且 `deleted=0`,设置 `bed_status=0(空闲)`、`update_time=当前时间`、`update_by=当前用户`。
+- 更新 `xg_student_msg`:根据上述分配记录中的 `student_id` 汇总(过滤空),且 `deleted=0`,设置 `student_status=1`,并将 `ext_field1`、`dormitory_room`、`dormitory_building` 置空,同时更新 `update_time`、`update_by`。
+
+#### 返回结果
+```json
+{
+  "code": 200,
+  "msg": "操作成功"
+}
+``` 

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

@@ -61,3 +61,14 @@ export const delDormBedStudent = (id: string | number | Array<string | number>)
     method: 'delete'
     method: 'delete'
   });
   });
 };
 };
+
+/**
+ * 批量收回宿舍床位
+ */
+export const batchRecallDormBeds = (data: { bedIds: Array<string | number> }) => {
+  return request({
+    url: '/dormBedStudent/dormBedStudent/batchRecall',
+    method: 'post',
+    data
+  });
+};

+ 98 - 5
web/src/views/xg/xgss/xsssfpejxy/index.vue

@@ -133,6 +133,27 @@
               <el-row>
               <el-row>
                 <el-col :span="12">
                 <el-col :span="12">
                   <el-button type="primary" icon="check" size="small" @click="handleAddRoom">分配给二级学院</el-button>
                   <el-button type="primary" icon="check" size="small" @click="handleAddRoom">分配给二级学院</el-button>
+                  <el-button type="warning" icon="RefreshLeft" size="small" @click="handleBatchRecall">批量收回宿舍</el-button>
+                </el-col>
+                <el-col :span="12" style="text-align: right">
+                  <el-button
+                    link
+                    type="primary"
+                    size="small"
+                    :loading="expandAllLoading"
+                    :disabled="expandAllLoading || selectionLoading || loading"
+                    @click="expandAll"
+                    >全部展开</el-button
+                  >
+                  <el-button
+                    link
+                    type="primary"
+                    size="small"
+                    :loading="collapseAllLoading"
+                    :disabled="collapseAllLoading || selectionLoading || loading"
+                    @click="collapseAll"
+                    >全部收起</el-button
+                  >
                 </el-col>
                 </el-col>
               </el-row>
               </el-row>
             </template>
             </template>
@@ -170,7 +191,8 @@
             <!-- 房间及床位树形表格 -->
             <!-- 房间及床位树形表格 -->
             <el-table
             <el-table
               ref="tableRef"
               ref="tableRef"
-              v-loading="loading"
+              v-loading="loading || selectionLoading || expandAllLoading || collapseAllLoading"
+              element-loading-text="处理中..."
               :data="roomList"
               :data="roomList"
               row-key="id"
               row-key="id"
               border
               border
@@ -344,6 +366,7 @@ import ElMessageBox from 'element-plus/es/components/message-box/index';
 import RoomDialog from './components/RoomDialog.vue';
 import RoomDialog from './components/RoomDialog.vue';
 import BedDialog from './components/BedDialog.vue';
 import BedDialog from './components/BedDialog.vue';
 import CollegeAllocationDialog from './components/CollegeAllocationDialog.vue';
 import CollegeAllocationDialog from './components/CollegeAllocationDialog.vue';
+import { batchRecallDormBeds } from '@/api/xg/xgss/dormBedStudent/dormBedStudent';
 
 
 // const { proxy } = getCurrentInstance();
 // const { proxy } = getCurrentInstance();
 
 
@@ -356,6 +379,14 @@ const showSearch = ref(true);
 const loading = ref(false);
 const loading = ref(false);
 const expandedRowKeys = ref<string[]>([]);
 const expandedRowKeys = ref<string[]>([]);
 
 
+// 加载状态:展开/收起/选择
+const expandAllLoading = ref(false);
+const collapseAllLoading = ref(false);
+const selectionLoading = ref(false);
+
+// 小延迟,确保加载动画先渲染
+const delay = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
+
 // 选择状态
 // 选择状态
 const selectedRows = ref<any[]>([]);
 const selectedRows = ref<any[]>([]);
 const selectedRoomCount = ref(0);
 const selectedRoomCount = ref(0);
@@ -643,6 +674,61 @@ const handleAddRoom = () => {
   collegeAllocationDialog.visible = true;
   collegeAllocationDialog.visible = true;
 };
 };
 
 
+/** 批量收回宿舍 */
+const handleBatchRecall = async () => {
+  if (!selectedBuilding.value) {
+    ElMessage.warning('请先选择宿舍楼');
+    return;
+  }
+  const selectedBeds = selectedRows.value.filter((row) => row.isBed).map((row) => row.id);
+  if (selectedBeds.length === 0) {
+    ElMessage.warning('请先选择要收回的床位');
+    return;
+  }
+  try {
+    const content = `确认收回选中的 ${selectedBeds.length} 个床位吗?\n\n重要提示:回收后,学生已分配的宿舍都会回收,无法恢复`;
+    await ElMessageBox.confirm(content, '提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning'
+    });
+  } catch {
+    return;
+  }
+  try {
+    await batchRecallDormBeds({ bedIds: selectedBeds });
+    ElMessage.success('批量收回成功');
+    // 刷新数据
+    selectableRowsCache.value = [];
+    loadRoomData();
+  } catch (e) {
+    ElMessage.error('批量收回失败');
+    console.error(e);
+  }
+};
+
+/** 全部展开 */
+const expandAll = async () => {
+  if (expandAllLoading.value || selectionLoading.value || !roomList.value?.length) return;
+  expandAllLoading.value = true;
+  await nextTick();
+  await delay(16);
+  expandedRowKeys.value = roomList.value.map((room) => String(room.id));
+  await nextTick();
+  expandAllLoading.value = false;
+};
+
+/** 全部收起 */
+const collapseAll = async () => {
+  if (collapseAllLoading.value || selectionLoading.value) return;
+  collapseAllLoading.value = true;
+  await nextTick();
+  await delay(16);
+  expandedRowKeys.value = [];
+  await nextTick();
+  collapseAllLoading.value = false;
+};
+
 /** 编辑房间 */
 /** 编辑房间 */
 const handleEditRoom = (row: any) => {
 const handleEditRoom = (row: any) => {
   roomDialog.title = '编辑房间';
   roomDialog.title = '编辑房间';
@@ -741,13 +827,13 @@ const handleCollegeAllocationSuccess = () => {
 
 
 /** 处理行的展开/收起 */
 /** 处理行的展开/收起 */
 const handleExpandChange = (row: any, expanded: boolean) => {
 const handleExpandChange = (row: any, expanded: boolean) => {
-  const rowId = String(row.roomId);
+  const expandKey = String(row.id);
   if (expanded) {
   if (expanded) {
     // 添加到已展开行数组
     // 添加到已展开行数组
-    expandedRowKeys.value.push(rowId);
+    expandedRowKeys.value.push(expandKey);
   } else {
   } else {
     // 从已展开行数组中移除
     // 从已展开行数组中移除
-    const index = expandedRowKeys.value.findIndex((key) => key === rowId);
+    const index = expandedRowKeys.value.findIndex((key) => key === expandKey);
     if (index !== -1) {
     if (index !== -1) {
       expandedRowKeys.value.splice(index, 1);
       expandedRowKeys.value.splice(index, 1);
     }
     }
@@ -854,7 +940,10 @@ const getAllSelectableRows = () => {
 const selectableRowsCache = ref<any[]>([]);
 const selectableRowsCache = ref<any[]>([]);
 
 
 /** 处理全选事件 */
 /** 处理全选事件 */
-const handleSelectAll = (selection: any[]) => {
+const handleSelectAll = async (selection: any[]) => {
+  if (selectionLoading.value) return;
+
+  selectionLoading.value = true;
   // 避免在全选操作时触发联动逻辑
   // 避免在全选操作时触发联动逻辑
   isUpdatingSelection.value = true;
   isUpdatingSelection.value = true;
 
 
@@ -862,6 +951,9 @@ const handleSelectAll = (selection: any[]) => {
     // 判断是全选还是取消全选
     // 判断是全选还是取消全选
     const isSelectAll = selection.length > 0;
     const isSelectAll = selection.length > 0;
 
 
+    await nextTick();
+    await delay(16);
+
     if (isSelectAll) {
     if (isSelectAll) {
       console.log('================>全选0');
       console.log('================>全选0');
       // 全选操作:选择所有可用的行(包括房间和床位)
       // 全选操作:选择所有可用的行(包括房间和床位)
@@ -892,6 +984,7 @@ const handleSelectAll = (selection: any[]) => {
     }
     }
   } finally {
   } finally {
     isUpdatingSelection.value = false;
     isUpdatingSelection.value = false;
+    selectionLoading.value = false;
   }
   }
 };
 };