This commit is contained in:
zlwu committed 2025-11-13 20:42:34 +08:00
1 parent 7d4bfaecb3
commit f4a1eac9ed
3 files changed
+320 -449

No files matched your search

@@ -5,6 +5,13 @@
<div class="left-panel">
<el-card shadow="hover">
<el-input v-model="deptName" placeholder="请输入院系名称" prefix-icon="Search" clearable />
<div class="class-status-filter">
<span class="class-status-filter__label">班级状态:</span>
<el-radio-group v-model="classStatusFilter" size="small" @change="handleClassStatusFilterChange">
<el-radio-button label="all">全部</el-radio-button>
<el-radio-button label="normal">{{ normalClassStatusLabel }}</el-radio-button>
</el-radio-group>
</div>
<el-tree
ref="deptTreeRef"
class="mt-2 dept-tree"
@@ -16,7 +23,16 @@
highlight-current
default-expand-all
@node-click="handleNodeClick"
/>
>
<template #default="{ data }">
<div class="dept-tree-node">
<span class="dept-tree-node__label">{{ data.label }}</span>
<el-tag v-if="isClassNode(data) && data.classStatus" size="small" effect="light" :type="getClassStatusTagType(data.classStatus)">
{{ getClassStatusLabel(data.classStatus) }}
</el-tag>
</div>
</template>
</el-tree>
</el-card>
</div>
@@ -1497,8 +1513,10 @@ import api from '@/api/xg/xsjcxx/studentMsg/index';
import { downloadImportTemplate } from '@/api/xg/xsjcxx/studentMsg/studentMsg/index';
import { StudentForm, StudentQuery, StudentVO } from '@/api/xg/xsjcxx/studentMsg/types';
import { StudentMsgForm } from '@/api/xg/xsjcxx/studentMsg/studentMsg/types';
import { listClassMsg } from '@/api/xg/xsjcxx/classMsg/classMsg';
import type { ClassMsgQuery, ClassMsgVO } from '@/api/xg/xsjcxx/classMsg/classMsg/types';
import { useRouter } from 'vue-router';
import { getCurrentInstance, ref, reactive, toRefs, watchEffect, onMounted, computed } from 'vue';
import { getCurrentInstance, ref, reactive, toRefs, watchEffect, onMounted, computed, watch, nextTick } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { to } from 'await-to-js';
import { globalHeaders } from '@/utils/request';
@@ -1529,7 +1547,8 @@ const {
zsjy_education_level,
xg_training_mode,
admission_category,
student_status
student_status,
xg_class_status
} = toRefs<any>(
proxy?.useDict(
'wenhuachengdu',
@@ -1549,7 +1568,8 @@ const {
'zsjy_education_level',
'xg_training_mode',
'admission_category',
'student_status'
'student_status',
'xg_class_status'
)
);
@@ -1595,6 +1615,26 @@ const getAdmissionCategoryLabel = (value: string | number) => {
return dict ? dict.label : value || '未知';
};
/** 班级状态码表映射 */
const getClassStatusLabel = (value: string | number) => {
const options = xg_class_status?.value || [];
const dict = options.find((item: any) => item.value === String(value));
return dict ? dict.label : value || '未知';
};
/** 班级状态标签类型 */
const getClassStatusTagType = (value: string | number): 'success' | 'info' | 'warning' | 'danger' | 'primary' => {
const options = xg_class_status?.value || [];
const dict = options.find((item: any) => item.value === String(value));
const tagType = dict?.elTagType || dict?.listClass;
if (tagType && ['primary', 'success', 'info', 'warning', 'danger'].includes(tagType)) {
return tagType;
}
return 'info';
};
const normalClassStatusLabel = computed(() => getClassStatusLabel('1'));
/** 学籍状态码表映射 */
const getStudentStatusLabel = (value: string | number) => {
const dict = student_status.value.find((item: any) => item.value === String(value));
@@ -1614,6 +1654,7 @@ const total = ref(0);
const dateRange = ref<[string, string]>(['', '']);
const deptName = ref('');
const deptOptions = ref([]);
const rawDeptOptions = ref<any[]>([]);
const majorOptions = ref([]);
const classOptions = ref([]);
const regionOptions = ref([]); // 行政区划选项
@@ -1621,6 +1662,10 @@ const dormBuildingOptions = ref([]); // 宿舍楼栋选项
const currentSelectedNode = ref<any>(null); // 当前选中的部门节点
const roomCache = ref<Map<string | number, string>>(new Map()); // 房间名称缓存
const bedCache = ref<Map<string | number, string>>(new Map()); // 床位名称缓存
const classStatusFilter = ref<'all' | 'normal'>('all');
const classStatusMap = new Map<string | number, string>();
let classStatusLoaded = false;
let classStatusLoading = false;
// 计算属性:判断是否显示新增按钮(只有班级节点才显示)
const showAddButton = computed(() => {
@@ -1893,6 +1938,221 @@ const getDormBuildingName = (id: string | number) => {
return findBuildingName(dormBuildingOptions.value, id) || String(id);
};
/** 获取节点deptCategory */
const getDeptCategory = (node: any) => {
if (!node) {
return undefined;
}
const category = node.deptCategory ?? node.dept_category ?? node.params?.deptCategory;
if (category && !node.deptCategory) {
node.deptCategory = category;
}
return category;
};
/** 是否班级节点 */
const isClassNode = (node: any) => {
const category = getDeptCategory(node);
return typeof category === 'string' && category.toLowerCase().startsWith('bj-');
};
/** 加载班级状态映射 */
const ensureClassStatusMap = async () => {
if (classStatusLoaded || classStatusLoading) {
return;
}
classStatusLoading = true;
try {
const params: ClassMsgQuery = {
pageNum: 1,
pageSize: 9999,
params: {}
};
const res = await listClassMsg(params);
const rows: ClassMsgVO[] = (res && (res.rows || res.data?.rows)) || [];
rows.forEach((item) => {
if (item?.id === undefined || item?.id === null) {
return;
}
const status = item?.classStatus;
if (status === undefined || status === null || status === '') {
return;
}
classStatusMap.set(String(item.id), String(status));
});
classStatusLoaded = true;
} catch (error) {
console.error('加载班级状态失败:', error);
} finally {
classStatusLoading = false;
}
};
/** 解析节点班级状态 */
const resolveClassStatus = (node: any): string | undefined => {
const existing = node?.classStatus ?? node?.class_status;
if (existing !== undefined && existing !== null && existing !== '') {
return String(existing);
}
const deptId = node?.id ?? node?.deptId;
if (deptId === undefined || deptId === null) {
return undefined;
}
return classStatusMap.get(String(deptId));
};
/** 深拷贝部门树节点 */
const cloneDeptTreeNodes = (nodes: any[]): any[] => {
return (nodes || []).map((node) => {
const cloned: any = { ...node };
if (Array.isArray(node.children) && node.children.length > 0) {
cloned.children = cloneDeptTreeNodes(node.children);
} else {
cloned.children = [];
}
return cloned;
});
};
/** 增强树节点(补充班级状态) */
const enhanceDeptTreeNodes = (nodes: any[]): any[] => {
return (nodes || []).map((node) => {
const enhanced: any = { ...node };
if (Array.isArray(enhanced.children) && enhanced.children.length > 0) {
enhanced.children = enhanceDeptTreeNodes(enhanced.children);
}
if (isClassNode(enhanced)) {
const status = resolveClassStatus(enhanced);
if (status !== undefined) {
enhanced.classStatus = status;
}
}
return enhanced;
});
};
/** 根据状态过滤班级节点 */
const filterDeptTreeByStatus = (nodes: any[], status?: string): any[] => {
const targetStatus = status ?? '';
return (nodes || [])
.map((node) => {
const cloned: any = { ...node };
if (Array.isArray(node.children) && node.children.length > 0) {
cloned.children = filterDeptTreeByStatus(node.children, status);
} else {
cloned.children = [];
}
if (targetStatus && isClassNode(cloned)) {
if (cloned.classStatus !== targetStatus) {
return null;
}
}
return cloned;
})
.filter((item) => item !== null) as any[];
};
/** 根据id查找节点 */
const findDeptNodeById = (nodes: any[], id: string | number): any | undefined => {
for (const node of nodes || []) {
if (node?.id === id) {
return node;
}
if (Array.isArray(node.children) && node.children.length > 0) {
const found = findDeptNodeById(node.children, id);
if (found) {
return found;
}
}
}
return undefined;
};
/** 应用班级状态过滤 */
const applyClassStatusFilter = (preserveSelection = true) => {
const baseTree = rawDeptOptions.value || [];
let filteredTree: any[] = [];
if (classStatusFilter.value === 'normal') {
filteredTree = filterDeptTreeByStatus(baseTree, '1');
} else {
filteredTree = cloneDeptTreeNodes(baseTree);
}
deptOptions.value = filteredTree;
if (preserveSelection) {
nextTick(() => {
const selectedId = currentSelectedNode.value?.id;
if (!selectedId) {
return;
}
const exists = findDeptNodeById(filteredTree, selectedId);
if (exists) {
deptTreeRef.value?.setCurrentKey(selectedId);
currentSelectedNode.value = exists;
} else {
currentSelectedNode.value = null;
queryParams.deptId = undefined;
deptTreeRef.value?.setCurrentKey(undefined);
}
});
}
nextTick(() => {
if (deptTreeRef.value && deptName.value) {
deptTreeRef.value.filter(deptName.value);
}
});
};
/** 处理状态过滤变更 */
const handleClassStatusFilterChange = async () => {
await ensureClassStatusMap();
const previousSelectedId = currentSelectedNode.value?.id;
applyClassStatusFilter();
nextTick(() => {
const currentSelectedId = currentSelectedNode.value?.id;
if (currentSelectedId) {
if (currentSelectedId !== previousSelectedId) {
handleQuery();
}
return;
}
if (deptOptions.value && deptOptions.value.length > 0) {
setDefaultRootNode(deptOptions.value);
} else {
queryParams.deptId = undefined;
getList();
}
});
};
/** 提取接口返回的树形数据 */
const extractDeptTreeData = (payload: any): any[] => {
if (Array.isArray(payload)) {
return payload;
}
if (Array.isArray(payload?.data)) {
return payload.data;
}
if (Array.isArray(payload?.data?.data)) {
return payload.data.data;
}
return [];
};
/** 构建部门树 */
const rebuildDeptTree = async (treeData: any[], resetSelection = true) => {
await ensureClassStatusMap();
const enhanced = enhanceDeptTreeNodes(treeData);
rawDeptOptions.value = enhanced;
applyClassStatusFilter(!resetSelection);
if (resetSelection) {
nextTick(() => {
setDefaultRootNode(deptOptions.value);
});
}
};
/** 根据房间ID获取房间名称 */
const getRoomName = async (roomId: string | number): Promise<string> => {
if (!roomId) return '';
@@ -2129,26 +2389,25 @@ const getList = async () => {
const getDeptTree = async () => {
try {
const res = await api.XgDeptTreeSelect(101);
// XgDeptTreeSelect返回的数据结构与deptTreeSelect不同,需要进行适当处理
if (res.data && Array.isArray(res.data)) {
deptOptions.value = res.data;
// 设置根节点为默认选中节点
setDefaultRootNode(res.data);
} else {
console.warn('部门树数据格式不符合预期');
// 回退到默认处理
const backup = await api.deptTreeSelect();
deptOptions.value = backup.data.data;
// 设置根节点为默认选中节点
setDefaultRootNode(backup.data.data);
const treeData = extractDeptTreeData(res);
if (treeData.length > 0) {
await rebuildDeptTree(treeData, true);
return;
}
console.warn('部门树数据格式不符合预期,尝试使用备选接口');
const backup = await api.deptTreeSelect();
const backupTree = extractDeptTreeData(backup);
if (backupTree.length > 0) {
await rebuildDeptTree(backupTree, true);
}
} catch (error) {
console.error('获取部门树失败:', error);
// 如果API调用失败,可以使用默认的deptTreeSelect作为备选
const backup = await api.deptTreeSelect();
deptOptions.value = backup.data.data;
// 设置根节点为默认选中节点
setDefaultRootNode(backup.data.data);
const backupTree = extractDeptTreeData(backup);
if (backupTree.length > 0) {
await rebuildDeptTree(backupTree, true);
}
}
};
@@ -2270,6 +2529,10 @@ const resetQuery = () => {
queryParams.studentNo = '';
queryParams.phone = '';
queryParams.studentStatus = '';
if (classStatusFilter.value !== 'all') {
classStatusFilter.value = 'all';
applyClassStatusFilter(false);
}
// 重置到默认根节点
if (deptOptions.value && deptOptions.value.length > 0) {
@@ -2959,6 +3222,23 @@ onMounted(() => {
min-width: 330px;
}
.class-status-filter {
display: flex;
align-items: center;
gap: 8px;
margin-top: 10px;
}
.class-status-filter__label {
color: #606266;
font-size: 13px;
flex-shrink: 0;
}
.left-panel .w-full {
width: 100%;
}
/* 强制覆盖el-tree的默认样式 */
.dept-tree :deep(.el-tree-node) {
white-space: nowrap !important;
@@ -3004,6 +3284,17 @@ onMounted(() => {
width: 100% !important;
}
.dept-tree-node {
display: flex;
align-items: center;
gap: 6px;
}
.dept-tree-node__label {
flex: 1;
min-width: 0;
}
/* 右侧面板 - 占据剩余空间 */
.right-panel {
flex: 1;