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