提交
This commit is contained in:
1 parent
6ca85a1b15
commit
7d4bfaecb3
45 files changed
+7015
-1762
No files matched your search
@@ -37,6 +37,24 @@ export function updateClassMsg(data: ClassMsgForm) {
|
||||
});
|
||||
}
|
||||
|
||||
// 仅更新班级状态
|
||||
export function updateClassStatus(data: { id: string | number; classStatus: string | number }) {
|
||||
return request({
|
||||
url: '/classMsg/classMsg/status',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
// 批量仅更新班级状态
|
||||
export function updateClassStatusBatch(data: { ids: Array<string | number>; classStatus: string | number }) {
|
||||
return request({
|
||||
url: '/classMsg/classMsg/status/batch',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
// 删除班级信息-新版
|
||||
export function delClassMsg(id: string | number) {
|
||||
return request({
|
||||
|
||||
@@ -8,6 +8,7 @@ export interface XgDeptTreeVO extends BaseEntity {
|
||||
weight: number;
|
||||
children: XgDeptTreeVO[];
|
||||
disabled: boolean;
|
||||
deptCategory?: string; // 部门类别编码(如 'bj-xxx' 表示班级)
|
||||
}
|
||||
|
||||
// 学生查询参数
|
||||
|
||||
@@ -93,3 +93,15 @@ export const listActiveClassTeachers = (params: { phone?: string; name?: string
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取全部班主任信息(按班级状态排序)
|
||||
* @param params 包含 phone 和 name 字段
|
||||
*/
|
||||
export const listAllClassTeachers = (params: { phone?: string; name?: string }) => {
|
||||
return request({
|
||||
url: '/xgStudentManager/studentManager/listAllClassTeachers',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
};
|
||||
@@ -59,6 +59,27 @@
|
||||
>删除</el-button
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
icon="CircleCheck"
|
||||
:disabled="multiple"
|
||||
@click="handleBatchGraduate"
|
||||
v-hasPermi="['classMsg:classMsg:edit']"
|
||||
>毕业</el-button
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="VideoPause" :disabled="multiple" @click="handleBatchPause" v-hasPermi="['classMsg:classMsg:edit']"
|
||||
>暂停</el-button
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="CloseBold" :disabled="multiple" @click="handleBatchDissolve" v-hasPermi="['classMsg:classMsg:edit']"
|
||||
>解散</el-button
|
||||
>
|
||||
</el-col>
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['classMsg:classMsg:export']">导出</el-button>
|
||||
</el-col> -->
|
||||
@@ -286,7 +307,7 @@
|
||||
<el-form-item label="班级规章制度" prop="classRules" v-if="false">
|
||||
<el-input v-model="form.classRules" type="textarea" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
<el-form-item label="班级状态" prop="classStatus" v-if="false">
|
||||
<el-form-item label="班级状态" prop="classStatus">
|
||||
<el-select v-model="form.classStatus" placeholder="请选择班级状态" style="width: 100%">
|
||||
<!-- <el-option label="正常" :value="1" />
|
||||
<el-option label="暂停" :value="2" />
|
||||
@@ -413,7 +434,15 @@
|
||||
</template>
|
||||
|
||||
<script setup name="ClassMsg" lang="ts">
|
||||
import { listClassMsg, getClassMsg, delClassMsg, addClassMsg, updateClassMsg } from '@/api/xg/xsjcxx/classmsg/classMsg';
|
||||
import {
|
||||
listClassMsg,
|
||||
getClassMsg,
|
||||
delClassMsg,
|
||||
addClassMsg,
|
||||
updateClassMsg,
|
||||
updateClassStatus,
|
||||
updateClassStatusBatch
|
||||
} from '@/api/xg/xsjcxx/classmsg/classMsg';
|
||||
import { listColleges, collegeTree, listMajorsByCollegeId } from '@/api/xg/xsjcxx/classmsg/college';
|
||||
import { ClassMsgVO, ClassMsgQuery, ClassMsgForm, SysDeptVo } from '@/api/xg/xsjcxx/classmsg/types';
|
||||
import { ComponentInternalInstance, ref, reactive, toRefs, onMounted, getCurrentInstance, watch } from 'vue';
|
||||
@@ -528,7 +557,7 @@ const initFormData: ClassMsgForm = {
|
||||
classMonitorName: undefined,
|
||||
deputyMonitorId: undefined,
|
||||
deputyMonitorName: undefined,
|
||||
classStatus: 1,
|
||||
classStatus: '1',
|
||||
isGraduation: '0',
|
||||
graduationDate: undefined,
|
||||
plannedCapacity: undefined,
|
||||
@@ -575,6 +604,7 @@ const data = reactive<PageData<ClassMsgForm, ClassMsgQuery>>({
|
||||
enrollmentYear: [{ required: true, message: '入学年份不能为空', trigger: 'blur' }],
|
||||
majorId: [{ required: true, message: '专业不能为空', trigger: 'blur' }],
|
||||
classType: [{ required: true, message: '学历层级不能为空', trigger: 'blur' }],
|
||||
classStatus: [{ required: true, message: '班级状态不能为空', trigger: 'change' }],
|
||||
schoolSystem: [{ required: true, message: '学制(年)不能为空', trigger: 'blur' }],
|
||||
graduationYear: [{ required: true, message: '毕业年份不能为空', trigger: 'blur' }],
|
||||
collegeId: [{ required: true, message: '学院不能为空', trigger: 'blur' }]
|
||||
@@ -587,7 +617,7 @@ const { queryParams, form, rules } = toRefs(data);
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
const res = await listClassMsg(queryParams.value);
|
||||
classMsgList.value = res.rows;
|
||||
classMsgList.value = res.rows || [];
|
||||
total.value = res.total;
|
||||
loading.value = false;
|
||||
};
|
||||
@@ -722,14 +752,25 @@ const handleUpdate = async (row?: ClassMsgVO) => {
|
||||
console.log(res);
|
||||
Object.assign(form.value, res.data);
|
||||
|
||||
// 确保classType是数字类型
|
||||
if (form.value.classType !== undefined) {
|
||||
// 确保classType是字符串类型,null时设置为undefined
|
||||
if (form.value.classType !== undefined && form.value.classType !== null) {
|
||||
form.value.classType = String(form.value.classType);
|
||||
} else {
|
||||
form.value.classType = undefined;
|
||||
}
|
||||
|
||||
// 确保isGraduation是字符串类型
|
||||
if (form.value.isGraduation !== undefined) {
|
||||
// 确保isGraduation是字符串类型,null时设置为undefined
|
||||
if (form.value.isGraduation !== undefined && form.value.isGraduation !== null) {
|
||||
form.value.isGraduation = String(form.value.isGraduation);
|
||||
} else {
|
||||
form.value.isGraduation = undefined;
|
||||
}
|
||||
|
||||
// 确保classStatus是字符串类型,null时设置为undefined
|
||||
if (form.value.classStatus !== undefined && form.value.classStatus !== null) {
|
||||
form.value.classStatus = String(form.value.classStatus);
|
||||
} else {
|
||||
form.value.classStatus = undefined;
|
||||
}
|
||||
|
||||
// 获取该学院下的专业列表
|
||||
@@ -780,6 +821,55 @@ const handleDelete = async (row?: ClassMsgVO) => {
|
||||
await getList();
|
||||
};
|
||||
|
||||
/** 批量状态更新通用方法 */
|
||||
const handleBatchChangeStatus = async (status: '2' | '3' | '4', actionLabel: string) => {
|
||||
// 选中名称用于确认提示
|
||||
const classNames =
|
||||
classMsgList.value
|
||||
.filter((item) => ids.value.includes(item.id))
|
||||
.map((item) => item.className)
|
||||
.join('、') || '';
|
||||
|
||||
await proxy?.$modal.confirm(`是否将班级「${classNames}」批量设置为【${actionLabel}】?`).finally(() => (loading.value = false));
|
||||
|
||||
buttonLoading.value = true;
|
||||
try {
|
||||
// 批量更新所选班级状态(后端单次处理)
|
||||
await updateClassStatusBatch({ ids: ids.value, classStatus: status });
|
||||
proxy?.$modal.msgSuccess(`${actionLabel}操作成功`);
|
||||
await getList();
|
||||
} finally {
|
||||
buttonLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 批量设置为毕业 */
|
||||
const handleBatchGraduate = async () => {
|
||||
if (!ids.value || ids.value.length === 0) {
|
||||
proxy?.$modal.msgWarning('请先勾选需要操作的班级');
|
||||
return;
|
||||
}
|
||||
await handleBatchChangeStatus('3', '毕业');
|
||||
};
|
||||
|
||||
/** 批量设置为暂停 */
|
||||
const handleBatchPause = async () => {
|
||||
if (!ids.value || ids.value.length === 0) {
|
||||
proxy?.$modal.msgWarning('请先勾选需要操作的班级');
|
||||
return;
|
||||
}
|
||||
await handleBatchChangeStatus('2', '暂停');
|
||||
};
|
||||
|
||||
/** 批量设置为解散 */
|
||||
const handleBatchDissolve = async () => {
|
||||
if (!ids.value || ids.value.length === 0) {
|
||||
proxy?.$modal.msgWarning('请先勾选需要操作的班级');
|
||||
return;
|
||||
}
|
||||
await handleBatchChangeStatus('4', '解散');
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.download(
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
<template>
|
||||
<el-dialog :title="getScopeDialogTitle(type)" v-model="visible" width="600px" append-to-body destroy-on-close>
|
||||
<el-form :model="form" label-width="100px">
|
||||
<el-form-item :label="getScopeName(type)">
|
||||
<el-form-item :label="getScopeName(type)" class="scope-form-item">
|
||||
<div v-if="loading" class="loading-container">
|
||||
<el-icon class="is-loading"><Loading /></el-icon>
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
<div v-else-if="type === 'custom'">
|
||||
<!-- 过滤按钮区域 -->
|
||||
<div class="filter-buttons">
|
||||
<el-radio-group v-model="classStatusFilter" size="small" @change="handleFilterChange">
|
||||
<el-radio-button label="all">全部</el-radio-button>
|
||||
<el-radio-button label="normal">正常</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<el-tree
|
||||
:data="scopeOptions.custom"
|
||||
:data="filteredTreeData"
|
||||
show-checkbox
|
||||
node-key="id"
|
||||
:default-checked-keys="form.selectedScopes"
|
||||
@@ -25,12 +32,36 @@
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
"
|
||||
/>
|
||||
>
|
||||
<template #default="{ data }">
|
||||
<span class="scope-node">
|
||||
<span class="scope-label">{{ data.label }}</span>
|
||||
<dict-tag
|
||||
v-if="isClassNode(data) && data.classStatus !== undefined && data.classStatus !== null && data.classStatus !== ''"
|
||||
:options="classStatusOptions"
|
||||
:value="data.classStatus"
|
||||
class="scope-tag"
|
||||
/>
|
||||
<!-- 调试信息(开发时可见,生产环境可删除) -->
|
||||
<!-- <span v-if="isClassNode(data)" style="font-size: 10px; color: #999; margin-left: 4px;">
|
||||
[{{ data.deptCategory }}|{{ data.classStatus || '无状态' }}]
|
||||
</span> -->
|
||||
</span>
|
||||
</template>
|
||||
</el-tree>
|
||||
</div>
|
||||
<div v-else-if="type === 'teacher'">
|
||||
<el-checkbox-group v-model="form.selectedScopes">
|
||||
<el-checkbox v-for="item in scopeOptions.teacher" :key="item.id" :label="item.id">
|
||||
{{ item.label }}
|
||||
<span class="scope-checkbox-label">
|
||||
<span>{{ item.label }}</span>
|
||||
<dict-tag
|
||||
v-if="isClassNode(item) && item.classStatus !== undefined && item.classStatus !== null"
|
||||
:options="classStatusOptions"
|
||||
:value="item.classStatus"
|
||||
class="scope-tag"
|
||||
/>
|
||||
</span>
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
@@ -46,11 +77,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch, onMounted } from 'vue';
|
||||
import { ref, reactive, computed, watch, getCurrentInstance, toRefs } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { Loading } from '@element-plus/icons-vue';
|
||||
import axios from 'axios';
|
||||
import request from '@/utils/request';
|
||||
import { listClassMsg } from '@/api/xg/xsjcxx/classMsg/classMsg';
|
||||
import type { ClassMsgQuery, ClassMsgVO } from '@/api/xg/xsjcxx/classMsg/types';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
@@ -69,6 +101,22 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'submit']);
|
||||
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
const { xg_class_status } = toRefs<any>(proxy?.useDict('xg_class_status') || {});
|
||||
const classStatusOptions = computed(() => {
|
||||
const options = xg_class_status?.value || [];
|
||||
// 调试日志:检查字典数据是否加载
|
||||
if (options.length === 0) {
|
||||
console.warn('[班级状态] 字典数据未加载,xg_class_status 为空');
|
||||
} else {
|
||||
console.log(`[班级状态] 字典数据已加载,共 ${options.length} 个选项`);
|
||||
}
|
||||
return options;
|
||||
});
|
||||
|
||||
const classStatusCache = new Map<string, string>();
|
||||
let classStatusLoaded = false;
|
||||
|
||||
// 计算属性:对话框可见性
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
@@ -81,6 +129,12 @@ const scopeOptions = reactive({
|
||||
teacher: [] as any[]
|
||||
});
|
||||
|
||||
// 原始树数据(未过滤)
|
||||
const originalTreeData = ref<any[]>([]);
|
||||
|
||||
// 班级状态过滤:'all' 全部, 'normal' 正常
|
||||
const classStatusFilter = ref<'all' | 'normal'>('all');
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(false);
|
||||
|
||||
@@ -101,12 +155,127 @@ async function fetchDeptTree() {
|
||||
}
|
||||
});
|
||||
if (response && response.code === 200) {
|
||||
// custom类型直接使用树形结构
|
||||
const rawData = response.data || [];
|
||||
|
||||
// 调试:递归收集所有叶子节点(可能是班级节点)
|
||||
function collectLeafNodes(nodes: any[], path = '', depth = 0): any[] {
|
||||
const results: any[] = [];
|
||||
nodes.forEach((node) => {
|
||||
const currentPath = path ? `${path} > ${node.label}` : node.label;
|
||||
const isLeaf = !node.children || node.children.length === 0;
|
||||
|
||||
if (isLeaf) {
|
||||
// 收集所有字段,看看deptCategory在哪里
|
||||
results.push({
|
||||
path: currentPath,
|
||||
depth: depth,
|
||||
id: node.id,
|
||||
label: node.label,
|
||||
// 检查所有可能的字段位置
|
||||
deptCategory: node.deptCategory,
|
||||
dept_category: node.dept_category,
|
||||
params: node.params,
|
||||
// 打印节点的所有键,看看还有什么字段
|
||||
allKeys: Object.keys(node),
|
||||
// 打印完整节点(限制深度避免过多输出)
|
||||
fullNode: depth >= 3 ? node : undefined
|
||||
});
|
||||
}
|
||||
|
||||
if (node.children && node.children.length > 0) {
|
||||
results.push(...collectLeafNodes(node.children, currentPath, depth + 1));
|
||||
}
|
||||
});
|
||||
return results;
|
||||
}
|
||||
|
||||
const leafNodes = collectLeafNodes(rawData);
|
||||
console.log(`[叶子节点统计] 找到 ${leafNodes.length} 个叶子节点(可能是班级)`);
|
||||
console.log('[叶子节点示例] 前10个叶子节点的完整结构:', leafNodes.slice(0, 10));
|
||||
|
||||
// 调试:查找所有可能的班级节点(检查所有字段和位置)
|
||||
function findClassNodes(nodes: any[], path = ''): any[] {
|
||||
const results: any[] = [];
|
||||
nodes.forEach((node) => {
|
||||
const currentPath = path ? `${path} > ${node.label}` : node.label;
|
||||
|
||||
// 检查所有可能的字段位置
|
||||
const category =
|
||||
node.deptCategory ?? node.dept_category ?? node.params?.deptCategory ?? node.data?.deptCategory ?? node.data?.dept_category;
|
||||
|
||||
// 检查是否是班级节点(多种可能的格式)
|
||||
const isClass =
|
||||
category &&
|
||||
typeof category === 'string' &&
|
||||
(category.toLowerCase().startsWith('bj-') || category.toLowerCase().includes('class') || category.toLowerCase().includes('班级'));
|
||||
|
||||
if (isClass) {
|
||||
results.push({
|
||||
path: currentPath,
|
||||
id: node.id,
|
||||
label: node.label,
|
||||
category: category,
|
||||
deptCategory: node.deptCategory,
|
||||
dept_category: node.dept_category,
|
||||
params: node.params,
|
||||
data: node.data
|
||||
});
|
||||
}
|
||||
|
||||
if (node.children && node.children.length > 0) {
|
||||
results.push(...findClassNodes(node.children, currentPath));
|
||||
}
|
||||
});
|
||||
return results;
|
||||
}
|
||||
const foundClassNodes = findClassNodes(rawData);
|
||||
console.log(`[班级节点查找] 找到 ${foundClassNodes.length} 个可能的班级节点:`, foundClassNodes.slice(0, 10));
|
||||
|
||||
// 如果没找到,尝试通过节点名称模式识别(如"西餐运营251"这种格式)
|
||||
if (foundClassNodes.length === 0) {
|
||||
console.log('[调试] 未通过deptCategory找到班级节点,尝试通过节点名称模式识别...');
|
||||
const namePatternNodes = leafNodes.filter((node) => {
|
||||
// 匹配类似"西餐运营251"、"中烹252"这种格式(名称+数字)
|
||||
const nameMatch = /^.+?\d{3}$/.test(node.label);
|
||||
return nameMatch;
|
||||
});
|
||||
console.log(`[名称模式匹配] 找到 ${namePatternNodes.length} 个符合"名称+3位数字"格式的节点:`, namePatternNodes.slice(0, 10));
|
||||
}
|
||||
|
||||
// 先加载班级状态缓存
|
||||
await ensureClassStatusCache();
|
||||
// 增强节点数据(添加班级状态)
|
||||
const enhancedTree = enhanceDeptNodes(rawData);
|
||||
|
||||
// 调试:统计增强后的班级节点数量
|
||||
let classNodeCount = 0;
|
||||
let classNodeWithStatusCount = 0;
|
||||
function countClassNodes(nodes: any[]) {
|
||||
nodes.forEach((node) => {
|
||||
// 使用 isClassNode 函数判断(支持两种方式:deptCategory字段 或 名称模式)
|
||||
if (isClassNode(node)) {
|
||||
classNodeCount++;
|
||||
if (node.classStatus) {
|
||||
classNodeWithStatusCount++;
|
||||
}
|
||||
}
|
||||
if (node.children && node.children.length > 0) {
|
||||
countClassNodes(node.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
countClassNodes(enhancedTree);
|
||||
console.log(`[班级状态统计] 总班级节点: ${classNodeCount}, 有状态的节点: ${classNodeWithStatusCount}`);
|
||||
|
||||
// 保存原始树数据
|
||||
originalTreeData.value = enhancedTree;
|
||||
|
||||
if (props.type === 'custom') {
|
||||
scopeOptions.custom = response.data || [];
|
||||
scopeOptions.custom = enhancedTree;
|
||||
} else if (props.type === 'teacher') {
|
||||
// teacher类型仍可用扁平化
|
||||
scopeOptions.teacher = flattenTreeData(response.data || []);
|
||||
scopeOptions.teacher = flattenTreeData(enhancedTree);
|
||||
} else {
|
||||
scopeOptions.custom = enhancedTree;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -125,7 +294,9 @@ function flattenTreeData(treeData: any[]): any[] {
|
||||
nodes.forEach((node) => {
|
||||
result.push({
|
||||
id: node.id,
|
||||
label: node.label
|
||||
label: node.label,
|
||||
deptCategory: node.deptCategory,
|
||||
classStatus: node.classStatus
|
||||
});
|
||||
|
||||
if (node.children && node.children.length > 0) {
|
||||
@@ -138,6 +309,130 @@ function flattenTreeData(treeData: any[]): any[] {
|
||||
return result;
|
||||
}
|
||||
|
||||
async function ensureClassStatusCache() {
|
||||
if (classStatusLoaded) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const params: ClassMsgQuery = {
|
||||
pageNum: 1,
|
||||
pageSize: 9999,
|
||||
params: {}
|
||||
};
|
||||
const res = await listClassMsg(params);
|
||||
const rows: ClassMsgVO[] = res?.rows || [];
|
||||
|
||||
// 建立缓存映射:sys_dept.dept_id = xg_class_msg.id -> class_status
|
||||
rows.forEach((item) => {
|
||||
const status = item?.classStatus;
|
||||
if (status === null || status === undefined || status === '') {
|
||||
return;
|
||||
}
|
||||
const statusStr = String(status);
|
||||
// 关联关系:sys_dept.dept_id = xg_class_msg.id
|
||||
// 所以使用 xg_class_msg.id 作为 key,对应 sys_dept.dept_id
|
||||
if (item.id !== undefined && item.id !== null) {
|
||||
classStatusCache.set(`deptId:${String(item.id)}`, statusStr);
|
||||
}
|
||||
});
|
||||
classStatusLoaded = true;
|
||||
console.log(`[班级状态缓存] 加载完成,共 ${classStatusCache.size} 条记录`);
|
||||
// 调试:打印前5条缓存记录
|
||||
if (classStatusCache.size > 0) {
|
||||
const sampleKeys = Array.from(classStatusCache.keys()).slice(0, 5);
|
||||
console.log(
|
||||
'[班级状态缓存] 示例记录:',
|
||||
sampleKeys.map((key) => ({ key, status: classStatusCache.get(key) }))
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载班级状态失败:', error);
|
||||
ElMessage.warning('加载班级状态失败,班级状态标签可能无法显示');
|
||||
}
|
||||
}
|
||||
|
||||
function enhanceDeptNodes(nodes: any[]): any[] {
|
||||
return (nodes || []).map((node) => {
|
||||
const enhanced = { ...node };
|
||||
const category = enhanced.deptCategory ?? enhanced.dept_category ?? enhanced.params?.deptCategory;
|
||||
if (category) {
|
||||
enhanced.deptCategory = category;
|
||||
}
|
||||
|
||||
// 判断是否是班级节点(支持两种方式:deptCategory字段 或 名称模式)
|
||||
const isClass = isClassNode(enhanced);
|
||||
const status = resolveClassStatus(enhanced);
|
||||
|
||||
if (status) {
|
||||
enhanced.classStatus = status;
|
||||
// 调试日志:记录成功添加状态的节点
|
||||
console.log(`[班级状态] 为节点添加状态: id=${enhanced.id}, label=${enhanced.label}, category=${category || '名称模式'}, status=${status}`);
|
||||
} else if (isClass) {
|
||||
// 调试日志:记录是班级但没有状态的节点
|
||||
console.warn(
|
||||
`[班级状态] 班级节点未找到状态: id=${enhanced.id}, label=${enhanced.label}, category=${category || '名称模式'}, deptId=${enhanced.id}`
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(enhanced.children) && enhanced.children.length > 0) {
|
||||
enhanced.children = enhanceDeptNodes(enhanced.children);
|
||||
}
|
||||
return enhanced;
|
||||
});
|
||||
}
|
||||
|
||||
function resolveClassStatus(node: any): string | undefined {
|
||||
// 判断是否是班级节点
|
||||
// 方法1:通过 deptCategory 字段判断(优先)
|
||||
const category = node.deptCategory ?? node.dept_category ?? node.params?.deptCategory;
|
||||
const isClassByCategory = category && typeof category === 'string' && category.toLowerCase().startsWith('bj-');
|
||||
|
||||
// 方法2:通过名称模式判断(备用方案)
|
||||
// 如果节点是叶子节点(没有children或children为空),且名称符合"名称+3位数字"格式,则认为是班级节点
|
||||
const isLeaf = !node?.children || node.children.length === 0;
|
||||
const isClassByName = isLeaf && node?.label && typeof node.label === 'string' && /^.+?\d{3}$/.test(node.label);
|
||||
|
||||
// 如果不是班级节点,直接返回
|
||||
if (!isClassByCategory && !isClassByName) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 通过 sys_dept.dept_id 关联 xg_class_msg.id 获取 class_status
|
||||
// 关联关系:sys_dept.dept_id = xg_class_msg.id
|
||||
const deptId = node.id ?? node.deptId;
|
||||
if (deptId === undefined || deptId === null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const key = `deptId:${String(deptId)}`;
|
||||
if (classStatusCache.has(key)) {
|
||||
return classStatusCache.get(key);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function isClassNode(data: any) {
|
||||
// 方法1:通过 deptCategory 字段判断(优先)
|
||||
const category = data?.deptCategory ?? data?.dept_category ?? data?.params?.deptCategory;
|
||||
if (category && typeof category === 'string' && category.toLowerCase().startsWith('bj-')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 方法2:通过名称模式判断(备用方案)
|
||||
// 如果节点是叶子节点(没有children或children为空),且名称符合"名称+3位数字"格式,则认为是班级节点
|
||||
const isLeaf = !data?.children || data.children.length === 0;
|
||||
if (isLeaf && data?.label && typeof data.label === 'string') {
|
||||
// 匹配类似"西餐运营251"、"中烹252"这种格式(名称+3位数字结尾)
|
||||
const nameMatch = /^.+?\d{3}$/.test(data.label);
|
||||
if (nameMatch) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 新增:获取历史范围数据
|
||||
async function fetchSelectedScopes(managerId: number | string) {
|
||||
if (!managerId) return;
|
||||
@@ -160,6 +455,8 @@ async function fetchSelectedScopes(managerId: number | string) {
|
||||
watch(visible, async (newVal) => {
|
||||
if (newVal) {
|
||||
const managerId = props.admin?.managerId || props.admin?.id;
|
||||
// 重置过滤状态为"全部"
|
||||
classStatusFilter.value = 'all';
|
||||
await fetchDeptTree();
|
||||
await fetchSelectedScopes(managerId);
|
||||
} else {
|
||||
@@ -236,6 +533,63 @@ function cancel() {
|
||||
// 从props获取类型的响应式引用
|
||||
const type = computed(() => props.type);
|
||||
|
||||
// 过滤后的树数据(计算属性)
|
||||
const filteredTreeData = computed(() => {
|
||||
if (classStatusFilter.value === 'all') {
|
||||
return scopeOptions.custom;
|
||||
} else if (classStatusFilter.value === 'normal') {
|
||||
// 只显示状态为"1"(正常)的班级节点及其父节点
|
||||
return filterTreeByClassStatus(scopeOptions.custom, '1');
|
||||
}
|
||||
return scopeOptions.custom;
|
||||
});
|
||||
|
||||
// 根据班级状态过滤树数据
|
||||
function filterTreeByClassStatus(nodes: any[], targetStatus: string): any[] {
|
||||
if (!nodes || nodes.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return nodes
|
||||
.map((node) => {
|
||||
const isClass = isClassNode(node);
|
||||
|
||||
// 如果是班级节点,检查状态
|
||||
if (isClass) {
|
||||
const nodeStatus = node.classStatus;
|
||||
// 如果状态匹配,保留该节点
|
||||
if (nodeStatus === targetStatus) {
|
||||
return { ...node };
|
||||
}
|
||||
// 状态不匹配,返回null(将被过滤掉)
|
||||
return null;
|
||||
}
|
||||
|
||||
// 非班级节点,递归处理子节点
|
||||
const filteredChildren = node.children && node.children.length > 0
|
||||
? filterTreeByClassStatus(node.children, targetStatus)
|
||||
: [];
|
||||
|
||||
// 如果有子节点被保留,则保留当前节点
|
||||
if (filteredChildren.length > 0) {
|
||||
return {
|
||||
...node,
|
||||
children: filteredChildren
|
||||
};
|
||||
}
|
||||
|
||||
// 没有符合条件的子节点,过滤掉当前节点
|
||||
return null;
|
||||
})
|
||||
.filter((node) => node !== null);
|
||||
}
|
||||
|
||||
// 处理过滤条件变化
|
||||
function handleFilterChange() {
|
||||
// 过滤逻辑由计算属性 filteredTreeData 自动处理
|
||||
// 这里可以添加额外的逻辑,比如重置展开状态等
|
||||
}
|
||||
|
||||
// 暴露组件以修复导出问题
|
||||
defineExpose({
|
||||
submitForm,
|
||||
@@ -245,6 +599,19 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.scope-node,
|
||||
.scope-checkbox-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.scope-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
.scope-tag :deep(.el-tag) {
|
||||
margin-left: 0;
|
||||
}
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
@@ -268,4 +635,21 @@ defineExpose({
|
||||
margin-right: 8px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.scope-form-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.scope-form-item :deep(.el-form-item__label) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 8px; /* 与 filter-buttons 的 padding-top 对齐 */
|
||||
margin-top: 0;
|
||||
height: auto;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.scope-form-item :deep(.el-form-item__content) {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
@@ -154,6 +154,11 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="className" label="所管理班级" align="center" />
|
||||
<el-table-column prop="classStatus" label="班级状态" align="center">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="xg_class_status" :value="scope.row.classStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" align="center" />
|
||||
<!-- <el-table-column label="操作" align="center" width="200" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
@@ -179,11 +184,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, watch } from 'vue';
|
||||
import { ref, reactive, onMounted, watch, toRefs, getCurrentInstance } from 'vue';
|
||||
import type { ComponentInternalInstance } from 'vue';
|
||||
import AdminDialog from './components/AdminDialog.vue';
|
||||
import ScopeDialog from './components/ScopeDialog.vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { listStudentManager, delStudentManager, listActiveClassTeachers } from '@/api/xg/xsjcxx/xgStudentManager/studentManager';
|
||||
import { listStudentManager, delStudentManager, listAllClassTeachers } from '@/api/xg/xsjcxx/xgStudentManager/studentManager';
|
||||
import type { StudentManagerQuery } from '@/api/xg/xsjcxx/xgStudentManager/studentManager/types';
|
||||
import service from '@/utils/request';
|
||||
|
||||
@@ -220,6 +226,9 @@ const currentAdmin = ref<any>(null);
|
||||
const scopeDialogVisible = ref(false);
|
||||
const scopeType = ref('');
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { xg_class_status } = toRefs<any>(proxy?.useDict('xg_class_status') || {});
|
||||
|
||||
// 班主任模拟数据 (预留,后续完善)
|
||||
const mockTeacherData = [
|
||||
{ id: 5, name: '刘老师', phone: '13800138005', sex: 0, scope: '高一(1)班', remark: '数学教师' },
|
||||
@@ -250,7 +259,7 @@ async function fetchAdmins(type: string) {
|
||||
phone: searchForm.phone,
|
||||
name: searchForm.managerName // teacherSearchForm 实际只有 managerName 字段
|
||||
};
|
||||
const res = await listActiveClassTeachers(params);
|
||||
const res = await listAllClassTeachers(params);
|
||||
teacherAdmins.value = res.data || [];
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -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">
|
||||
<el-radio-button label="all">全部</el-radio-button>
|
||||
<el-radio-button label="normal">正常</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<el-tree
|
||||
ref="deptTreeRef"
|
||||
class="mt-2 dept-tree"
|
||||
@@ -16,7 +23,19 @@
|
||||
highlight-current
|
||||
default-expand-all
|
||||
@node-click="handleNodeClick"
|
||||
/>
|
||||
>
|
||||
<template #default="{ data }">
|
||||
<span class="dept-tree-node">
|
||||
<span class="dept-tree-node__label">{{ data.label }}</span>
|
||||
<dict-tag
|
||||
v-if="isClassNode(data) && hasClassStatus(data)"
|
||||
:options="xg_class_status || []"
|
||||
:value="data.classStatus"
|
||||
class="dept-tree-node__tag"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
</el-tree>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
@@ -431,10 +450,12 @@ import {
|
||||
dismissCadre
|
||||
} from '@/api/xg/xsjcxx/xgClassPosMsg/xgClassPosMsg';
|
||||
import { XgClassPosMsgVO, XgClassPosMsgQuery, XgClassPosMsgForm } from '@/api/xg/xsjcxx/xgClassPosMsg/xgClassPosMsg/types';
|
||||
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, type FormInstance } from 'element-plus';
|
||||
import { XgDeptTreeSelect, deptTreeSelect } from '@/api/xg/xsjcxx/studentMsg/index';
|
||||
import request from '@/utils/request';
|
||||
import { listClassMsg } from '@/api/xg/xsjcxx/classMsg/classMsg';
|
||||
import type { ClassMsgQuery, ClassMsgVO } from '@/api/xg/xsjcxx/classMsg/types';
|
||||
interface ComponentInternalInstance {
|
||||
proxy?: any;
|
||||
}
|
||||
@@ -451,8 +472,8 @@ interface PageData<T, Q> {
|
||||
}
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { stu_appointment_method, sys_yesno, stu_cadre_status } = toRefs<any>(
|
||||
proxy?.useDict('stu_appointment_method', 'sys_yesno', 'stu_cadre_status')
|
||||
const { stu_appointment_method, sys_yesno, stu_cadre_status, xg_class_status } = toRefs<any>(
|
||||
proxy?.useDict('stu_appointment_method', 'sys_yesno', 'stu_cadre_status', 'xg_class_status') || {}
|
||||
);
|
||||
|
||||
const xgClassPosMsgList = ref<XgClassPosMsgVO[]>([]);
|
||||
@@ -467,8 +488,10 @@ const total = ref(0);
|
||||
// 部门树相关变量
|
||||
const deptTreeRef = ref();
|
||||
const deptOptions = ref<any[]>([]);
|
||||
const rawDeptTree = ref<any[]>([]);
|
||||
const deptName = ref('');
|
||||
const currentSelectedNode = ref<any>(null);
|
||||
const classStatusFilter = ref<'all' | 'normal'>('all');
|
||||
|
||||
// 学生选择相关变量
|
||||
const studentOptions = ref<any[]>([]);
|
||||
@@ -478,6 +501,35 @@ const studentSearchLoading = ref(false);
|
||||
const positionOptions = ref<any[]>([]);
|
||||
const positionSearchLoading = ref(false);
|
||||
|
||||
const classStatusMap = new Map<string, string>();
|
||||
let classStatusLoaded = false;
|
||||
let classStatusLoading = false;
|
||||
|
||||
const classStatusOrderMap = computed(() => {
|
||||
const map = new Map<string, number>();
|
||||
const options = xg_class_status?.value || [];
|
||||
options.forEach((item: any, index: number) => {
|
||||
if (item?.value !== undefined && item?.value !== null) {
|
||||
map.set(String(item.value), index);
|
||||
}
|
||||
});
|
||||
return map;
|
||||
});
|
||||
|
||||
const compareLabels = (a: string | undefined, b: string | undefined) => {
|
||||
const labelA = a || '';
|
||||
const labelB = b || '';
|
||||
return labelA.localeCompare(labelB, 'zh-Hans-CN-u-co-pinyin');
|
||||
};
|
||||
|
||||
const getClassStatusOrder = (status: string | undefined) => {
|
||||
if (status === undefined || status === null || status === '') {
|
||||
return Number.MAX_SAFE_INTEGER;
|
||||
}
|
||||
const order = classStatusOrderMap.value.get(String(status));
|
||||
return order !== undefined ? order : Number.MAX_SAFE_INTEGER;
|
||||
};
|
||||
|
||||
const queryFormRef = ref<FormInstance>();
|
||||
const xgClassPosMsgFormRef = ref<FormInstance>();
|
||||
|
||||
@@ -617,6 +669,152 @@ watchEffect(
|
||||
{ flush: 'post' }
|
||||
);
|
||||
|
||||
/** 班级节点是否有状态 */
|
||||
const hasClassStatus = (node: any) => {
|
||||
const status = node?.classStatus ?? node?.class_status;
|
||||
return status !== undefined && status !== null && status !== '';
|
||||
};
|
||||
|
||||
/** 获取节点的deptCategory */
|
||||
const getDeptCategory = (node: any): string | undefined => {
|
||||
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 sortDeptTreeNodes = (nodes: any[]): any[] => {
|
||||
if (!Array.isArray(nodes)) {
|
||||
return [];
|
||||
}
|
||||
const clonedNodes = nodes.map((node) => {
|
||||
const cloned: any = { ...node };
|
||||
if (Array.isArray(cloned.children)) {
|
||||
cloned.children = sortDeptTreeNodes(cloned.children);
|
||||
}
|
||||
return cloned;
|
||||
});
|
||||
|
||||
clonedNodes.sort((a, b) => {
|
||||
const aIsClass = isClassNode(a);
|
||||
const bIsClass = isClassNode(b);
|
||||
|
||||
if (aIsClass && bIsClass) {
|
||||
const statusCompare = getClassStatusOrder(a.classStatus ?? resolveClassStatus(a)) - getClassStatusOrder(b.classStatus ?? resolveClassStatus(b));
|
||||
if (statusCompare !== 0) {
|
||||
return statusCompare;
|
||||
}
|
||||
}
|
||||
|
||||
return compareLabels(a.label, b.label);
|
||||
});
|
||||
|
||||
return clonedNodes;
|
||||
};
|
||||
|
||||
/** 增强部门树节点,补充班级状态 */
|
||||
const enhanceDeptTreeNodes = (nodes: any[]): any[] => {
|
||||
return (nodes || []).map((node) => {
|
||||
const enhanced: any = { ...node };
|
||||
const category = getDeptCategory(enhanced);
|
||||
if (category && !enhanced.deptCategory) {
|
||||
enhanced.deptCategory = category;
|
||||
}
|
||||
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 extractDeptTree = (payload: any): any[] => {
|
||||
if (Array.isArray(payload)) {
|
||||
return payload;
|
||||
}
|
||||
if (payload?.data && Array.isArray(payload.data)) {
|
||||
return payload.data;
|
||||
}
|
||||
if (payload?.data?.data && Array.isArray(payload.data.data)) {
|
||||
return payload.data.data;
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
/** 处理并设置部门树 */
|
||||
const processAndSetDeptTree = async (treeData: any[]) => {
|
||||
try {
|
||||
await ensureClassStatusMap();
|
||||
} catch (error) {
|
||||
console.warn('加载班级状态时发生异常,继续渲染部门树', error);
|
||||
}
|
||||
const processedTree = enhanceDeptTreeNodes(treeData);
|
||||
const sortedTree = sortDeptTreeNodes(processedTree);
|
||||
rawDeptTree.value = sortedTree;
|
||||
applyDeptFilter(true);
|
||||
};
|
||||
|
||||
/** 筛选节点方法 */
|
||||
const filterNode = (value: string, data: any) => {
|
||||
if (!value) return true;
|
||||
@@ -627,29 +825,125 @@ const filterNode = (value: string, data: any) => {
|
||||
const getDeptTree = async () => {
|
||||
try {
|
||||
const res = await XgDeptTreeSelect(101);
|
||||
// XgDeptTreeSelect返回的数据结构与deptTreeSelect不同,需要进行适当处理
|
||||
if (res.data && Array.isArray(res.data)) {
|
||||
deptOptions.value = res.data;
|
||||
// 设置根节点为默认选中节点
|
||||
setDefaultRootNode(res.data);
|
||||
const treeData = extractDeptTree(res);
|
||||
if (treeData.length > 0) {
|
||||
await processAndSetDeptTree(treeData);
|
||||
} else {
|
||||
console.warn('部门树数据格式不符合预期');
|
||||
// 回退到默认处理
|
||||
const backup = await deptTreeSelect();
|
||||
deptOptions.value = backup.data.data;
|
||||
// 设置根节点为默认选中节点
|
||||
setDefaultRootNode(backup.data.data);
|
||||
const backupTree = extractDeptTree(backup);
|
||||
await processAndSetDeptTree(backupTree);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取部门树失败:', error);
|
||||
// 如果API调用失败,可以使用默认的deptTreeSelect作为备选
|
||||
const backup = await deptTreeSelect();
|
||||
deptOptions.value = backup.data.data;
|
||||
// 设置根节点为默认选中节点
|
||||
setDefaultRootNode(backup.data.data);
|
||||
const backupTree = extractDeptTree(backup);
|
||||
await processAndSetDeptTree(backupTree);
|
||||
}
|
||||
};
|
||||
|
||||
const findNodeById = (nodes: any[], id: number | string): any | null => {
|
||||
if (!Array.isArray(nodes)) {
|
||||
return null;
|
||||
}
|
||||
for (const node of nodes) {
|
||||
if (node?.id === id) {
|
||||
return node;
|
||||
}
|
||||
if (Array.isArray(node?.children)) {
|
||||
const child = findNodeById(node.children, id);
|
||||
if (child) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const filterTreeByClassStatus = (nodes: any[], targetStatus: string | number | undefined): any[] => {
|
||||
if (!Array.isArray(nodes) || targetStatus === undefined) {
|
||||
return nodes ? JSON.parse(JSON.stringify(nodes)) : [];
|
||||
}
|
||||
const result: any[] = [];
|
||||
nodes.forEach((node) => {
|
||||
const clonedNode = { ...node };
|
||||
if (Array.isArray(clonedNode.children)) {
|
||||
clonedNode.children = filterTreeByClassStatus(clonedNode.children, targetStatus);
|
||||
}
|
||||
if (isClassNode(clonedNode)) {
|
||||
const status = resolveClassStatus(clonedNode);
|
||||
if (status === String(targetStatus)) {
|
||||
result.push(clonedNode);
|
||||
}
|
||||
} else if (Array.isArray(clonedNode.children) && clonedNode.children.length > 0) {
|
||||
result.push(clonedNode);
|
||||
}
|
||||
});
|
||||
return sortDeptTreeNodes(result);
|
||||
};
|
||||
|
||||
const normalClassStatusValue = computed(() => {
|
||||
const options = xg_class_status?.value || [];
|
||||
const normalOption =
|
||||
options.find((item: any) => item?.label === '正常') ||
|
||||
options.find((item: any) => String(item?.value).toLowerCase() === 'normal') ||
|
||||
options.find((item: any) => String(item?.value) === '1');
|
||||
return normalOption ? normalOption.value : options[0]?.value;
|
||||
});
|
||||
|
||||
const reselectOrResetNode = () => {
|
||||
const currentId = currentSelectedNode.value?.id;
|
||||
if (!currentId) {
|
||||
return;
|
||||
}
|
||||
const matched = findNodeById(deptOptions.value, currentId);
|
||||
if (matched) {
|
||||
currentSelectedNode.value = matched;
|
||||
queryParams.value.deptId = matched.id;
|
||||
nextTick(() => {
|
||||
deptTreeRef.value?.setCurrentKey(matched.id);
|
||||
});
|
||||
} else {
|
||||
currentSelectedNode.value = null;
|
||||
queryParams.value.deptId = undefined;
|
||||
setDefaultRootNode(deptOptions.value);
|
||||
}
|
||||
};
|
||||
|
||||
const applyDeptFilter = (resetSelection = false) => {
|
||||
const sourceTree = rawDeptTree.value || [];
|
||||
if (classStatusFilter.value === 'normal') {
|
||||
const targetStatus = normalClassStatusValue.value;
|
||||
if (targetStatus === undefined) {
|
||||
deptOptions.value = JSON.parse(JSON.stringify(sourceTree));
|
||||
} else {
|
||||
deptOptions.value = filterTreeByClassStatus(sourceTree, targetStatus);
|
||||
}
|
||||
} else {
|
||||
deptOptions.value = JSON.parse(JSON.stringify(sourceTree));
|
||||
}
|
||||
|
||||
if (resetSelection) {
|
||||
setDefaultRootNode(deptOptions.value);
|
||||
} else {
|
||||
reselectOrResetNode();
|
||||
}
|
||||
};
|
||||
|
||||
watch(classStatusFilter, () => {
|
||||
applyDeptFilter(false);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => xg_class_status?.value,
|
||||
() => {
|
||||
if (classStatusFilter.value === 'normal') {
|
||||
applyDeptFilter(false);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/** 设置默认根节点并触发查询 */
|
||||
const setDefaultRootNode = (treeData: any[]) => {
|
||||
if (treeData && treeData.length > 0) {
|
||||
@@ -1074,11 +1368,24 @@ onMounted(() => {
|
||||
.dept-tree {
|
||||
height: 600px;
|
||||
overflow-y: auto;
|
||||
overflow-x: auto;
|
||||
overflow-x: hidden;
|
||||
width: 330px; /* 固定宽度,超出时显示滚动条 */
|
||||
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;
|
||||
}
|
||||
|
||||
/* 强制覆盖el-tree的默认样式 */
|
||||
.dept-tree :deep(.el-tree-node) {
|
||||
white-space: nowrap !important;
|
||||
@@ -1124,6 +1431,16 @@ onMounted(() => {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.dept-tree-node {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.dept-tree-node__tag :deep(.el-tag) {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
/* 右侧面板 - 占据剩余空间 */
|
||||
.right-panel {
|
||||
flex: 1;
|
||||
|
||||
Reference in new issue
Block a user