提交代码

This commit is contained in:
zlwu committed 2025-08-13 15:02:38 +08:00
1 parent 1bf7f78c97
commit 719f69fe11
7 files changed
+579 -312

No files matched your search

+1
View File
@@ -91,6 +91,7 @@ export const getBedDetailsByBuildingId = (buildingId: string | number): AxiosPro
export const batchAllocateBedsToCollege = (data: {
bedIds: (string | number)[];
collegeIds: (string | number)[];
collegeStudentCounts?: number[];
buildingId?: string | number;
buildingType?: string;
}) => {
@@ -14,6 +14,10 @@
<el-tag v-if="buildingInfo" :type="buildingInfo.buildingType === '1' ? 'primary' : 'danger'" class="mr-2 mb-2">
{{ buildingInfo.buildingType === '1' ? '男生楼' : '女生楼' }}:{{ buildingInfo.label }}
</el-tag>
<br />
<el-tag type="success" class="mr-2 mb-2"> 可分配:{{ availableBedsCount }} 个 </el-tag>
<el-tag type="warning" class="mr-2 mb-2"> 已分配:{{ allocatedBedsCount }} 个 </el-tag>
<el-tag type="danger" class="mr-2 mb-2"> 维修中:{{ maintenanceBedsCount }} 个 </el-tag>
</div>
<!-- 床位树形结构 -->
@@ -29,12 +33,26 @@
<template #default="{ node, data }">
<div class="bed-tree-node">
<span :class="getBedNodeClass(data)">
<u-icon v-if="data.type === 'room'" name="home" size="16" class="mr-1" />
<u-icon v-else-if="data.type === 'bed'" name="bed" size="16" class="mr-1" />
<i v-if="data.type === 'room'" class="el-icon-house mr-1"></i>
<i v-else-if="data.type === 'bed'" class="el-icon-bed mr-1"></i>
{{ node.label }}
<span v-if="data.type === 'bed'" class="bed-status-indicator ml-1">
<el-tag v-if="data.bedData?.bedStatus === '0'" type="success" size="small">空闲</el-tag>
<el-tag v-else-if="data.bedData?.bedStatus === '1'" type="warning" size="small">已分配</el-tag>
<el-tag v-else-if="data.bedData?.bedStatus === '2'" type="danger" size="small">维修中</el-tag>
</span>
<!-- 显示已分配学院名称 -->
<span v-if="data.type === 'bed' && data.bedData?.bedStatus === '1' && data.bedData?.collegeName" class="college-name ml-2">
<el-tag size="small" type="info" class="college-tag">
<i class="el-icon-school mr-1"></i>
{{ data.bedData.collegeName }}
</el-tag>
</span>
</span>
<span v-if="data.type === 'room'" class="bed-count ml-2">
<el-tag size="small" :type="getBedCountTagType(data)" :class="getBedCountTagClass(data)"> {{ data.bedCount }}个床位 </el-tag>
<el-tag size="small" type="success" class="mr-1"> 可分配:{{ data.availableCount || 0 }} </el-tag>
<el-tag size="small" type="warning" class="mr-1"> 已分配:{{ data.allocatedCount || 0 }} </el-tag>
<el-tag size="small" type="danger"> 维修中:{{ data.maintenanceCount || 0 }} </el-tag>
</span>
</div>
</template>
@@ -137,8 +155,8 @@
{
type: 'number',
min: 0,
max: props.selectedBeds.length,
message: `床位数必须在1-${props.selectedBeds.length}之间`,
max: availableBedsCount,
message: `床位数必须在0-${availableBedsCount}之间`,
trigger: 'blur'
}
]"
@@ -146,7 +164,7 @@
<el-input-number
v-model="allocationForm[collegeId]"
:min="0"
:max="props.selectedBeds.length"
:max="availableBedsCount"
:precision="0"
placeholder="请输入床位数"
style="width: 200px"
@@ -234,14 +252,32 @@ const roomCount = computed(() => {
return roomNames.size;
});
// 计算已分配的床位数
// 计算各状态床位数量
const availableBedsCount = computed(() => {
return props.selectedBeds.filter((bed) => bed.bedStatus === '0').length;
});
const allocatedBedsCount = computed(() => {
return props.selectedBeds.filter((bed) => bed.bedStatus === '1').length;
});
const maintenanceBedsCount = computed(() => {
return props.selectedBeds.filter((bed) => bed.bedStatus === '2').length;
});
// 获取可分配的床位(只有空闲状态的床位)
const availableBeds = computed(() => {
return props.selectedBeds.filter((bed) => bed.bedStatus === '0');
});
// 计算已分配的床位数(学院分配表单中的)
const formAllocatedBedsCount = computed(() => {
return Object.values(allocationForm.value).reduce((sum, count) => sum + (count || 0), 0);
});
// 计算剩余床位数
// 计算剩余可分配床位数(基于空闲床位)
const remainingBedsCount = computed(() => {
return props.selectedBeds.length - allocatedBedsCount.value;
return availableBedsCount.value - formAllocatedBedsCount.value;
});
// 构建床位树形数据
@@ -263,11 +299,19 @@ const buildBedTreeData = () => {
// 构建树形结构
const treeData = [];
roomGroups.forEach((beds, roomName) => {
// 统计房间内各状态床位数量
const availableCount = beds.filter((bed) => bed.bedStatus === '0').length;
const allocatedCount = beds.filter((bed) => bed.bedStatus === '1').length;
const maintenanceCount = beds.filter((bed) => bed.bedStatus === '2').length;
const roomNode = {
id: `room-${roomName}`,
label: roomName,
type: 'room',
bedCount: beds.length,
availableCount,
allocatedCount,
maintenanceCount,
children: beds.map((bed) => ({
id: bed.id,
label: `${bed.bedName}`,
@@ -321,6 +365,17 @@ const getBedNodeClass = (data: any) => {
}
} else if (data.type === 'bed') {
classes.push('bed-node');
// 根据床位状态添加样式
if (data.bedData?.bedStatus === '0') {
classes.push('available-bed-node'); // 可分配床位
} else if (data.bedData?.bedStatus === '1') {
classes.push('allocated-bed-node'); // 已分配床位
} else if (data.bedData?.bedStatus === '2') {
classes.push('maintenance-bed-node'); // 维修中床位
}
// 根据宿舍楼类型添加颜色主题
if (props.buildingInfo?.buildingType === '1') {
classes.push('male-bed-node');
} else if (props.buildingInfo?.buildingType === '2') {
@@ -381,16 +436,19 @@ const handleCheckChange = (data: XgStudentTreeVO, checked: boolean) => {
return;
}
// 检查是否还有剩余床位可分配
if (checked && props.selectedBeds.length === 0) {
// 如果没有床位可分配,阻止勾选并给出提示
// 检查是否还有可分配的床位
if (checked && availableBedsCount.value === 0) {
// 如果没有可分配床位,阻止勾选并给出提示
nextTick(() => {
collegeTreeRef.value?.setChecked(data.id, false, false);
});
ElMessage.warning('当前没有床位可分配,请先选择床位');
ElMessage.warning('当前没有可分配的床位,只有空闲状态的床位才能分配');
return;
}
// 获取之前的选中学院列表
const previousCollegeIds = [...selectedCollegeIds.value];
// 更新选中的学院ID列表
const checkedKeys = collegeTreeRef.value?.getCheckedKeys() || [];
selectedCollegeIds.value = checkedKeys.filter((key: any) => {
@@ -398,11 +456,11 @@ const handleCheckChange = (data: XgStudentTreeVO, checked: boolean) => {
return node?.data?.type === 'college';
});
// 初始化人数分配表单
initAllocationForm();
// 只为新增的学院分配床位,保留已有学院的用户修改
updateAllocationFormForNewColleges(previousCollegeIds, selectedCollegeIds.value);
};
// 初始化人数分配表单
// 初始化人数分配表单(首次打开对话框时使用)
const initAllocationForm = () => {
allocationForm.value = {};
@@ -410,25 +468,25 @@ const initAllocationForm = () => {
return;
}
// 检查是否还有床位可分配
if (props.selectedBeds.length === 0) {
// 如果没有床位可分配,清空选中的学院并给出提示
// 检查是否还有可分配的床位
if (availableBedsCount.value === 0) {
// 如果没有可分配床位,清空选中的学院并给出提示
selectedCollegeIds.value = [];
nextTick(() => {
// 取消所有勾选状态
collegeTreeRef.value?.setCheckedKeys([]);
});
ElMessage.warning('当前没有床位可分配,请先选择床位');
ElMessage.warning('当前没有可分配的床位,只有空闲状态的床位才能分配');
return;
}
// 新的床位分配规则:尽量满足学院需求
// 新的床位分配规则:基于可分配床位(空闲状态)尽量满足学院需求
// 1. 按学院顺序逐个分配
// 2. 如果学院需求床位数 <= 剩余床位数,则满足学院需求
// 3. 如果学院需求床位数 > 剩余床位数,则分配所有剩余床位
// 2. 如果学院需求床位数 <= 剩余可分配床位数,则满足学院需求
// 3. 如果学院需求床位数 > 剩余可分配床位数,则分配所有剩余可分配床位
const totalBeds = props.selectedBeds.length;
let remainingBeds = totalBeds;
const totalAvailableBeds = availableBedsCount.value;
let remainingBeds = totalAvailableBeds;
// 获取学院的真实床位需求数(总需求数 - 已分配数)
const getCollegeDemand = (collegeId: string | number) => {
@@ -475,6 +533,85 @@ const initAllocationForm = () => {
});
};
// 更新学院分配表单(只为新增学院分配床位,保留已有学院的用户修改)
const updateAllocationFormForNewColleges = (previousCollegeIds: (string | number)[], currentCollegeIds: (string | number)[]) => {
// 检查是否还有可分配的床位
if (availableBedsCount.value === 0) {
// 如果没有可分配床位,清空选中的学院并给出提示
selectedCollegeIds.value = [];
allocationForm.value = {};
nextTick(() => {
// 取消所有勾选状态
collegeTreeRef.value?.setCheckedKeys([]);
});
ElMessage.warning('当前没有可分配的床位,只有空闲状态的床位才能分配');
return;
}
// 获取学院的真实床位需求数(总需求数 - 已分配数)
const getCollegeDemand = (collegeId: string | number) => {
// 从学院树数据中查找学院节点
const college = findCollegeById(collegeTreeData.value, collegeId);
if (!college) return 0;
// 根据宿舍楼类型确定性别
if (props.buildingInfo?.buildingType === '1') {
// 男生楼,计算男生床位需求
const maleDemand = (college.maleTotal || 0) - (college.maleAssigned || 0);
return Math.max(0, maleDemand); // 确保不为负数
} else if (props.buildingInfo?.buildingType === '2') {
// 女生楼,计算女生床位需求
const femaleDemand = (college.femaleTotal || 0) - (college.femaleAssigned || 0);
return Math.max(0, femaleDemand); // 确保不为负数
} else {
// 混合楼,计算总床位需求
const totalDemand = (college.maleTotal || 0) + (college.femaleTotal || 0) - (college.maleAssigned || 0) - (college.femaleAssigned || 0);
return Math.max(0, totalDemand); // 确保不为负数
}
};
// 找出新增的学院和取消勾选的学院
const addedColleges = currentCollegeIds.filter((id) => !previousCollegeIds.includes(id));
const removedColleges = previousCollegeIds.filter((id) => !currentCollegeIds.includes(id));
// 移除取消勾选的学院
removedColleges.forEach((collegeId) => {
delete allocationForm.value[collegeId];
});
// 如果有新增的学院,为其分配床位
if (addedColleges.length > 0) {
// 计算已分配的床位数(保留用户手动修改的数据)
const allocatedBeds = Object.keys(allocationForm.value)
.filter((collegeId) => currentCollegeIds.includes(collegeId))
.reduce((sum, collegeId) => sum + (allocationForm.value[collegeId] || 0), 0);
let remainingBeds = availableBedsCount.value - allocatedBeds;
// 为新增的学院分配床位
addedColleges.forEach((collegeId) => {
let bedCount = 0;
if (remainingBeds > 0) {
// 获取学院的真实床位需求数
const collegeDemand = getCollegeDemand(collegeId);
if (remainingBeds >= collegeDemand) {
// 如果剩余床位数 >= 学院需求,则满足学院需求
bedCount = collegeDemand;
remainingBeds -= collegeDemand;
} else {
// 如果剩余床位数 < 学院需求,则分配所有剩余床位
bedCount = remainingBeds;
remainingBeds = 0;
}
}
allocationForm.value[collegeId] = bedCount;
});
}
};
// 获取学院树数据
const getCollegeTreeData = async () => {
try {
@@ -554,12 +691,12 @@ const handleAllocationChange = () => {
// 验证床位分配是否超出总床位数
const totalAllocated = Object.values(allocationForm.value).reduce((sum, count) => sum + (count || 0), 0);
if (totalAllocated > props.selectedBeds.length) {
ElMessage.warning(`床位分配超出:已分配 ${totalAllocated} 个,但只有 ${props.selectedBeds.length} 个床位可用`);
if (totalAllocated > availableBedsCount.value) {
ElMessage.warning(`床位分配超出:已分配 ${totalAllocated} 个,但只有 ${availableBedsCount.value} 个可分配床位`);
}
console.log('学院人数分配变化:', allocationForm.value);
console.log('总分配床位数:', totalAllocated, '总可用床位数:', props.selectedBeds.length);
console.log('总分配床位数:', totalAllocated, '总可分配床位数:', availableBedsCount.value);
};
// 获取分配统计信息标题
@@ -576,11 +713,11 @@ const getAllocationStatsTitle = () => {
// 获取分配统计信息描述
const getAllocationStatsDescription = () => {
if (remainingBedsCount.value === 0) {
return `已分配 ${allocatedBedsCount.value} 个床位,分配完成!`;
return `已分配 ${formAllocatedBedsCount.value} 个床位,分配完成!`;
} else if (remainingBedsCount.value > 0) {
return `已分配 ${allocatedBedsCount.value} 个床位,还需分配 ${remainingBedsCount.value} 个床位`;
return `已分配 ${formAllocatedBedsCount.value} 个床位,还可以分配 ${remainingBedsCount.value} 个床位`;
} else {
return `已分配 ${allocatedBedsCount.value} 个床位,超出 ${Math.abs(remainingBedsCount.value)} 个床位`;
return `已分配 ${formAllocatedBedsCount.value} 个床位,超出 ${Math.abs(remainingBedsCount.value)} 个床位`;
}
};
@@ -618,15 +755,15 @@ const handleConfirm = async () => {
return;
}
if (props.selectedBeds.length === 0) {
ElMessage.warning('请选择要分配的床位');
if (availableBedsCount.value === 0) {
ElMessage.warning('请选择可分配的床位(只有空闲状态的床位才能分配)');
return;
}
// 验证学院人数分配
const totalAllocated = Object.values(allocationForm.value).reduce((sum, count) => sum + (count || 0), 0);
if (totalAllocated !== props.selectedBeds.length) {
ElMessage.warning(`床位分配不匹配:已分配 ${totalAllocated} 个,但选择了 ${props.selectedBeds.length} 个床位`);
if (totalAllocated > availableBedsCount.value) {
ElMessage.warning(`床位分配超出:已分配 ${totalAllocated} 个,但只有 ${availableBedsCount.value} 个可分配床位`);
return;
}
@@ -639,23 +776,50 @@ const handleConfirm = async () => {
// }
try {
await ElMessageBox.confirm(`确定要将 ${props.selectedBeds.length} 个床位分配给 ${selectedCollegeIds.value.length} 个学院吗?`, '确认分配', {
// 构建详细的分配信息提示
const allocationDetails = selectedCollegeIds.value
.map((collegeId) => {
const collegeName = getCollegeName(collegeId);
const bedCount = allocationForm.value[collegeId] || 0;
return ` • ${collegeName}:${bedCount} 个床位`;
})
.join('\n');
const confirmMessage = `确定要分配以下床位吗?\n\n总选择床位:${props.selectedBeds.length} 个\n可分配床位:${availableBedsCount.value} 个\n总分配数:${totalAllocated} 个\n\n分配详情:\n${allocationDetails}`;
await ElMessageBox.confirm(confirmMessage, '确认分配', {
confirmButtonText: '确定分配',
cancelButtonText: '取消',
type: 'warning'
type: 'warning',
dangerouslyUseHTMLString: false
});
confirmLoading.value = true;
// 构建分配参数,包含学院人数信息
// 获取真正的学院ID列表和对应的学生人数(从树节点数据中提取collegeId字段)
const realCollegeIds: number[] = [];
const collegeStudentCounts: number[] = [];
selectedCollegeIds.value.forEach((treeNodeId) => {
const college = findCollegeById(collegeTreeData.value, treeNodeId);
if (!college?.collegeId) {
throw new Error(`学院"${getCollegeName(treeNodeId)}"的ID信息不完整`);
}
realCollegeIds.push(college.collegeId);
collegeStudentCounts.push(allocationForm.value[treeNodeId] || 0);
});
// 构建分配参数,只使用可分配的床位(空闲状态)
const allocationData = {
bedIds: props.selectedBeds.map((bed) => bed.id),
collegeIds: selectedCollegeIds.value,
collegeStudentCounts: selectedCollegeIds.value.map((collegeId) => allocationForm.value[collegeId]),
bedIds: availableBeds.value.map((bed) => bed.id), // 只传递可分配的床位ID
collegeIds: realCollegeIds, // 使用真正的学院ID
collegeStudentCounts: collegeStudentCounts, // 与学院ID一一对应的学生人数
buildingId: props.buildingInfo?.id,
buildingType: props.buildingInfo?.buildingType
};
console.log('床位分配参数:', allocationData);
// 调用批量分配API
await batchAllocateBedsToCollege(allocationData);
@@ -711,6 +875,20 @@ watch(
});
ElMessage.info('床位已清空,已自动清空学院选择');
}
// 如果没有可分配床位,自动清空已选择的学院
const newAvailableCount = newBeds.filter((bed) => bed.bedStatus === '0').length;
const oldAvailableCount = oldBeds ? oldBeds.filter((bed) => bed.bedStatus === '0').length : 0;
if (newAvailableCount === 0 && oldAvailableCount > 0) {
selectedCollegeIds.value = [];
allocationForm.value = {};
nextTick(() => {
// 取消所有勾选状态
collegeTreeRef.value?.setCheckedKeys([]);
});
ElMessage.warning('没有可分配的床位,已自动清空学院选择');
}
},
{ deep: true }
);
@@ -788,12 +966,10 @@ watch(
background-color: rgba(64, 158, 255, 0.1);
padding: 2px 6px;
border-radius: 4px;
/* border-left: 3px solid var(--el-color-primary); */
}
.male-bed-node {
color: var(--el-color-primary);
background-color: rgba(64, 158, 255, 0.05);
padding: 1px 4px;
border-radius: 3px;
}
@@ -804,18 +980,66 @@ watch(
background-color: rgba(255, 105, 180, 0.1);
padding: 2px 6px;
border-radius: 4px;
/* border-left: 3px solid #ff69b4; */
}
.female-bed-node {
color: #ff69b4;
background-color: rgba(255, 105, 180, 0.05);
padding: 1px 4px;
border-radius: 3px;
}
/* 床位状态样式 */
.available-bed-node {
background-color: rgba(103, 194, 58, 0.1) !important;
color: var(--el-color-success) !important;
font-weight: 500;
/* border-left: 3px solid var(--el-color-success); */
padding-left: 8px !important;
}
.allocated-bed-node {
background-color: rgba(230, 162, 60, 0.1) !important;
color: var(--el-color-warning) !important;
opacity: 0.7;
}
.maintenance-bed-node {
background-color: rgba(245, 108, 108, 0.1) !important;
color: var(--el-color-danger) !important;
opacity: 0.7;
text-decoration: line-through;
}
.bed-count {
font-size: 12px;
display: flex;
gap: 4px;
align-items: center;
}
.bed-status-indicator {
display: inline-flex;
align-items: center;
}
/* 学院名称标签样式 */
.college-name {
display: inline-flex;
align-items: center;
}
.college-tag {
background-color: rgba(144, 147, 153, 0.1) !important;
border-color: rgba(144, 147, 153, 0.3) !important;
color: var(--el-text-color-regular) !important;
font-size: 11px;
padding: 2px 6px;
border-radius: 3px;
}
.college-tag .el-icon-school {
font-size: 10px;
margin-right: 2px;
}
/* 床位数量标签样式 */
+281 -100
View File
@@ -149,12 +149,18 @@
</span>
- 房间管理</span
>
<span class="text-blue-500 whitespace-nowrap">
已选择: {{ selectedRoomCount }}个房间 / {{ selectedBedCount }}个床位
<span v-if="selectedRoomCount > 0 || selectedBedCount > 0" class="text-gray-500 text-sm ml-2">
(房间选择时会自动包含其下所有空闲床位)
<div class="text-blue-500 flex flex-wrap items-center gap-2">
<span class="whitespace-nowrap"> 已选择: {{ selectedRoomCount }}个房间 / {{ selectedBedCount }}个床位 </span>
<span v-if="selectedBedCount > 0" class="whitespace-nowrap text-sm">
[可分配: <el-tag size="small" type="success">{{ selectedBedStats.available }}</el-tag
>个, 已分配: <el-tag size="small" type="warning">{{ selectedBedStats.allocated }}</el-tag
>个, 维修中: <el-tag size="small" type="danger">{{ selectedBedStats.maintenance }}</el-tag
>个]
</span>
</span>
<span v-if="selectedRoomCount > 0 || selectedBedCount > 0" class="text-gray-500 text-sm">
(房间选择时会自动包含其下所有床位)
</span>
</div>
</div>
</el-col>
</el-row>
@@ -174,9 +180,10 @@
table-layout="auto"
@selection-change="handleSelectionChange"
@select-all="handleSelectAll"
:row-class-name="getRowClassName"
>
<el-table-column type="selection" width="55" :selectable="checkSelectable" />
<el-table-column prop="roomName" label="房间/宿舍名称" min-width="120">
<el-table-column prop="roomName" label="房间/宿舍名称" min-width="160">
<template #default="scope">
<span v-if="!scope.row.isBed">{{ scope.row.roomName }} (房间)</span>
<span v-else>{{ scope.row.bedName }} (床位)</span>
@@ -201,12 +208,16 @@
</template>
</template>
</el-table-column>
<el-table-column label="状态" width="100" align="center">
<el-table-column label="状态" width="120" align="center">
<template #default="scope">
<template v-if="!scope.row.isBed">
<el-tag v-if="scope.row.roomStatus === '0'" type="success">正常</el-tag>
<el-tag v-else-if="scope.row.roomStatus === '1'" type="warning">维修中</el-tag>
<el-tag v-else type="danger">停用</el-tag>
<div class="flex flex-col items-center gap-1">
<el-tag v-if="scope.row.roomStatus === '0'" type="success">正常</el-tag>
<el-tag v-else-if="scope.row.roomStatus === '1'" type="warning">维修中</el-tag>
<el-tag v-else type="danger">停用</el-tag>
<!-- 房间床位占用状态 -->
<span v-if="scope.row.roomStatus === '0'" class="text-xs text-gray-500"> ({{ getRoomBedStatusText(scope.row) }}) </span>
</div>
</template>
<template v-else>
<el-tag v-if="scope.row.bedStatus === '0'" type="success">空闲</el-tag>
@@ -349,6 +360,12 @@ const expandedRowKeys = ref<string[]>([]);
const selectedRows = ref<any[]>([]);
const selectedRoomCount = ref(0);
const selectedBedCount = ref(0);
// 床位状态统计
const selectedBedStats = ref({
available: 0, // 可分配床位(空闲)
allocated: 0, // 已分配床位
maintenance: 0 // 维修中床位
});
const animate = reactive({
searchAnimate: {
@@ -367,6 +384,9 @@ const roomList = ref<any[]>([]);
// 添加请求取消控制器
let currentController: AbortController | null = null;
// 床位到房间的映射缓存,优化性能
const bedToRoomMap = ref<Map<string, any>>(new Map());
// 查询参数
const queryParams = reactive({
pageNum: 1,
@@ -567,16 +587,28 @@ const loadRoomData = async () => {
roomList.value = tempList;
// 重置选择状态
selectedRows.value = [];
selectedRoomCount.value = 0;
selectedBedCount.value = 0;
// 构建床位到房间的映射缓存
buildBedToRoomMap();
// 确保表格选择状态与数据一致
nextTick(() => {
// 清除所有选择,避免不可选行被选中
tableRef.value?.clearSelection();
});
// 清除可选行缓存,强制重新计算
selectableRowsCache.value = [];
// 重置选择状态
isUpdatingSelection.value = true;
try {
selectedRows.value = [];
selectedRoomCount.value = 0;
selectedBedCount.value = 0;
selectedBedStats.value = { available: 0, allocated: 0, maintenance: 0 };
// 确保表格选择状态与数据一致
nextTick(() => {
// 清除所有选择,避免不可选行被选中
tableRef.value?.clearSelection();
});
} finally {
isUpdatingSelection.value = false;
}
} catch (error: any) {
// 忽略请求被取消的错误
if (error.name !== 'AbortError') {
@@ -642,6 +674,8 @@ const handleDeleteRoom = (row: any) => {
/** 房间操作成功回调 */
const handleRoomSuccess = () => {
// 清除缓存,确保数据刷新
selectableRowsCache.value = [];
loadRoomData();
};
@@ -682,17 +716,27 @@ const handleDeleteBed = (row: any) => {
/** 床位操作成功回调 */
const handleBedSuccess = () => {
// 清除缓存,确保数据刷新
selectableRowsCache.value = [];
loadRoomData();
};
/** 学院分配成功回调 */
const handleCollegeAllocationSuccess = () => {
// 清除缓存,确保数据刷新
selectableRowsCache.value = [];
// 刷新房间和床位数据
loadRoomData();
// 清空选择状态
selectedRows.value = [];
selectedRoomCount.value = 0;
selectedBedCount.value = 0;
isUpdatingSelection.value = true;
try {
selectedRows.value = [];
selectedRoomCount.value = 0;
selectedBedCount.value = 0;
selectedBedStats.value = { available: 0, allocated: 0, maintenance: 0 };
} finally {
isUpdatingSelection.value = false;
}
};
/** 处理行的展开/收起 */
@@ -710,50 +754,145 @@ const handleExpandChange = (row: any, expanded: boolean) => {
}
};
/** 检查行是否可选中(只有正常状态的房间和空闲状态的床位可选) */
/** 检查行是否可选中(只有维修中和停用状态的房间及其床位不可选) */
const checkSelectable = (row: any) => {
if (row.isBed) {
// 床位:只有空闲状态(0)可选,且其父房间必须是正常状态
// 床位:只要其父房间不是维修中或停用状态就可选
const parentRoom = findParentRoom(row);
return row.bedStatus === '0' && parentRoom && parentRoom.roomStatus === '0';
return parentRoom && parentRoom.roomStatus !== '1' && parentRoom.roomStatus !== '2';
} else {
// 房间:只有正常状态(0)可选
return row.roomStatus === '0';
// 房间:只有维修中(1)和停用(2)不可选
return row.roomStatus !== '1' && row.roomStatus !== '2';
}
};
/** 查找床位的父房间 */
const findParentRoom = (bed: any) => {
return roomList.value.find((room) => room.children && room.children.some((child: any) => child.id === bed.id));
/** 构建床位到房间的映射缓存 */
const buildBedToRoomMap = () => {
const map = new Map();
roomList.value.forEach((room) => {
if (room.children && room.children.length > 0) {
room.children.forEach((bed: any) => {
map.set(bed.id, room);
});
}
});
bedToRoomMap.value = map;
};
/** 查找床位的父房间(优化版,使用缓存) */
const findParentRoom = (bed: any) => {
return bedToRoomMap.value.get(bed.id);
};
/** 获取房间床位占用状态文本 */
const getRoomBedStatusText = (room: any) => {
if (!room.children || room.children.length === 0) {
return '无床位';
}
const totalBeds = room.children.length;
const availableBeds = room.children.filter((bed: any) => bed.bedStatus === '0').length;
const allocatedBeds = room.children.filter((bed: any) => bed.bedStatus === '1').length;
const maintenanceBeds = room.children.filter((bed: any) => bed.bedStatus === '2').length;
if (availableBeds === totalBeds) {
return '空闲';
} else if (allocatedBeds === totalBeds) {
return '已满';
} else if (allocatedBeds > 0) {
// 修复:显示已分配的床位数量,而不是空闲的床位数量
return `部分占用(${allocatedBeds}/${totalBeds})`;
} else if (maintenanceBeds === totalBeds) {
return '全部维修';
} else {
return `混合状态(${allocatedBeds}/${totalBeds})`;
}
};
/** 获取行类名,用于区分房间和床位 */
const getRowClassName = ({ row }: { row: any }) => {
if (!row.isBed) {
return 'room-row'; // 房间行
}
return 'bed-row'; // 床位行
};
/** 获取所有可选择的行(包括房间和床位) - 优化版 */
const getAllSelectableRows = () => {
// 如果缓存存在且数据未变化,直接返回缓存
if (selectableRowsCache.value.length > 0) {
return selectableRowsCache.value;
}
const selectableRows: any[] = [];
roomList.value.forEach((room) => {
const isRoomSelectable = room.roomStatus !== '1' && room.roomStatus !== '2'; // 只有维修中和停用不可选
// 检查房间是否可选
if (isRoomSelectable) {
selectableRows.push(room);
}
// 检查房间下的床位是否可选
if (room.children && room.children.length > 0) {
room.children.forEach((bed: any) => {
// 床位:只要父房间可选就可选(不限制床位状态)
if (isRoomSelectable) {
selectableRows.push(bed);
}
});
}
});
// 缓存结果
selectableRowsCache.value = selectableRows;
return selectableRows;
};
// 添加可选行缓存
const selectableRowsCache = ref<any[]>([]);
/** 处理全选事件 */
const handleSelectAll = (selection: any[]) => {
// 全选时,只选择可用的行
const availableRows = roomList.value.filter((row) => checkSelectable(row));
// 避免在全选操作时触发联动逻辑
isUpdatingSelection.value = true;
console.log('全选处理:', {
总行数: roomList.value.length,
可选行数: availableRows.length,
不可选行数: roomList.value.length - availableRows.length,
不可选行详情: roomList.value
.filter((row) => !checkSelectable(row))
.map((row) => ({
id: row.id,
name: row.isBed ? row.bedName : row.roomName,
type: row.isBed ? '床位' : '房间',
status: row.isBed ? row.bedStatus : row.roomStatus,
parentStatus: row.isBed ? findParentRoom(row)?.roomStatus : null
}))
});
try {
// 判断是全选还是取消全选
const isSelectAll = selection.length > 0;
// 清空当前选择
tableRef.value?.clearSelection();
if (isSelectAll) {
console.log('================>全选0');
// 全选操作:选择所有可用的行(包括房间和床位)
const availableRows = getAllSelectableRows();
console.log('================>availableRows', availableRows);
// 清空当前选择
tableRef.value?.clearSelection();
// 只选择可用的行
availableRows.forEach((row) => {
tableRef.value?.toggleRowSelection(row, true);
});
console.log('================>全选1');
// 批量选择所有可用行
availableRows.forEach((row) => {
tableRef.value?.toggleRowSelection(row, true);
});
console.log('================>全选2');
// 直接更新选择状态,避免等待事件触发
selectedRows.value = [...availableRows];
updateSelectionCount(availableRows);
} else {
console.log('================>取消全选1');
// 取消全选操作:清空所有选择
tableRef.value?.clearSelection();
console.log('================>取消全选2');
// 直接更新选择状态
selectedRows.value = [];
selectedRoomCount.value = 0;
selectedBedCount.value = 0;
selectedBedStats.value = { available: 0, allocated: 0, maintenance: 0 };
}
} finally {
isUpdatingSelection.value = false;
}
};
/** 更新选择统计 */
@@ -763,10 +902,25 @@ const updateSelectionCount = (rows: any[]) => {
const uniqueRooms = new Set();
const uniqueBeds = new Set();
// 床位状态统计
const bedStats = {
available: 0, // 可分配床位(空闲)bedStatus = '0'
allocated: 0, // 已分配床位 bedStatus = '1'
maintenance: 0 // 维修中床位 bedStatus = '2'
};
rows.forEach((row) => {
if (row.isBed) {
// 床位:记录床位ID
// 床位:记录床位ID并统计状态
uniqueBeds.add(row.id);
// 统计床位状态
if (row.bedStatus === '0') {
bedStats.available++;
} else if (row.bedStatus === '1') {
bedStats.allocated++;
} else if (row.bedStatus === '2') {
bedStats.maintenance++;
}
} else {
// 房间:记录房间ID
uniqueRooms.add(row.id);
@@ -779,18 +933,21 @@ const updateSelectionCount = (rows: any[]) => {
// 统计选中的床位数(包括直接选中的床位和通过房间选择自动选中的床位)
selectedBedCount.value = uniqueBeds.size;
// 调试信息(开发时可以打开,生产环境建议注释掉)
console.log('选择统计更新:', {
总行数: rows.length,
房间数: uniqueRooms.size,
床位数: uniqueBeds.size,
房间IDs: Array.from(uniqueRooms),
床位IDs: Array.from(uniqueBeds)
});
// 更新床位状态统计
selectedBedStats.value = bedStats;
};
// 添加一个标志位来避免循环触发
const isUpdatingSelection = ref(false);
/** 处理选择变更 */
const handleSelectionChange = (rows: any[]) => {
// 避免在批量更新时重复处理
if (isUpdatingSelection.value) return;
// 处理房间选择联动逻辑
handleRoomBedSelection(rows);
// 更新选中行数组
selectedRows.value = rows;
@@ -798,52 +955,57 @@ const handleSelectionChange = (rows: any[]) => {
updateSelectionCount(rows);
};
/** 当选择房间时,自动选择其下所有空闲床位 */
const toggleRoomSelection = async (room: any, isSelected: boolean) => {
if (!room || !room.children || room.children.length === 0) return;
/** 处理房间和床位的联动选择逻辑 */
const handleRoomBedSelection = (currentSelection: any[]) => {
const previousSelection = selectedRows.value;
// 获取该房间下所有空闲状态的床位,且房间必须是正常状态
const availableBeds = room.children.filter((bed: any) => bed.bedStatus === '0' && room.roomStatus === '0');
// 找出新增和移除的选择项
const addedItems = currentSelection.filter((current) => !previousSelection.some((prev) => prev.id === current.id && prev.isBed === current.isBed));
const removedItems = previousSelection.filter(
(prev) => !currentSelection.some((current) => current.id === prev.id && current.isBed === prev.isBed)
);
// 根据房间选择状态,选中或取消选中所有符合条件的床位
// 如果没有变化,直接返回
if (addedItems.length === 0 && removedItems.length === 0) {
return;
}
// 批量处理联动选择
isUpdatingSelection.value = true;
try {
// 处理新增的房间选择 - 自动选择其下所有可用床位
const addedRooms = addedItems.filter((item) => !item.isBed);
addedRooms.forEach((room) => {
selectRoomBeds(room, true);
});
// 处理移除的房间选择 - 自动取消选择其下所有床位
const removedRooms = removedItems.filter((item) => !item.isBed);
removedRooms.forEach((room) => {
selectRoomBeds(room, false);
});
} finally {
isUpdatingSelection.value = false;
}
};
/** 选择或取消选择房间下的所有可用床位 */
const selectRoomBeds = (room: any, isSelected: boolean) => {
if (!room?.children?.length) return;
// 获取该房间下所有床位(不限制床位状态),只要房间可选
const availableBeds = room.children.filter((bed: any) => room.roomStatus !== '1' && room.roomStatus !== '2');
// 如果没有可用床位,直接返回
if (availableBeds.length === 0) return;
// 批量处理床位选择 - 避免在每次操作时触发事件
availableBeds.forEach((bed: any) => {
tableRef.value?.toggleRowSelection(bed, isSelected);
});
// 注意:这里不需要手动更新统计,因为 toggleRowSelection 会触发 handleSelectionChange
// 而 handleSelectionChange 会调用 updateSelectionCount 来更新统计
};
// 监听房间选择变化
watch(
selectedRows,
(newRows, oldRows) => {
// 找出新增的房间和移除的房间
const oldRooms = oldRows.filter((row) => !row.isBed);
const newRooms = newRows.filter((row) => !row.isBed);
// 找出新增选中的房间
const addedRooms = newRooms.filter((newRoom) => !oldRooms.some((oldRoom) => oldRoom.id === newRoom.id));
// 找出取消选中的房间
const removedRooms = oldRooms.filter((oldRoom) => !newRooms.some((newRoom) => newRoom.id === oldRoom.id));
// 为新增选中的房间选中所有空闲床位
addedRooms.forEach((room) => {
toggleRoomSelection(room, true);
});
// 为取消选中的房间取消选中所有床位
removedRooms.forEach((room) => {
toggleRoomSelection(room, false);
});
// 手动更新统计,确保统计准确
updateSelectionCount(newRows);
},
{ deep: true }
);
// 获取学生信息树数据
const getStudentTreeData = async () => {
try {
@@ -1103,4 +1265,23 @@ onMounted(() => {
min-width: max-content;
width: auto !important;
}
/* 房间行样式 - 使用更高优先级的选择器 */
.el-table :deep(.room-row) {
background-color: #eefeff !important; /* 浅蓝色背景,区分房间 */
}
/* 床位行样式 - 使用更高优先级的选择器 */
.el-table :deep(.bed-row) {
background-color: #ffffff !important; /* 浅灰色背景 */
}
/* 确保行背景色在hover状态下也能保持 */
.el-table :deep(.room-row:hover) {
background-color: #d1f0ff !important; /* 房间行hover时的深一点蓝色 */
}
.el-table :deep(.bed-row:hover) {
background-color: #e8f0f5 !important; /* 床位行hover时的深一点灰色 */
}
</style>