diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgClassMsgServiceImpl.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgClassMsgServiceImpl.java index e75c9233..6a960f00 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgClassMsgServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgClassMsgServiceImpl.java @@ -16,8 +16,10 @@ import org.springframework.transaction.annotation.Transactional; import org.dromara.xg.domain.bo.XgClassMsgBo; import org.dromara.xg.domain.vo.XgClassMsgVo; import org.dromara.xg.domain.XgClassMsg; +import org.dromara.xg.domain.XgStudentMsg; import org.dromara.xg.domain.XgSysDept; import org.dromara.xg.mapper.XgClassMsgMapper; +import org.dromara.xg.mapper.XgStudentMsgMapper; import org.dromara.xg.mapper.XgSysDeptMapper; import org.dromara.xg.service.IXgClassMsgService; @@ -38,6 +40,7 @@ public class XgClassMsgServiceImpl implements IXgClassMsgService { private final XgClassMsgMapper baseMapper; private final XgSysDeptMapper deptMapper; + private final XgStudentMsgMapper studentMsgMapper; /** * 查询班级信息-新版 @@ -233,6 +236,13 @@ public class XgClassMsgServiceImpl implements IXgClassMsgService { deptMapper.insert(dept); log.info("班级对应的部门信息不存在,已创建部门信息,班级ID:{},部门ID:{}", update.getId(), dept.getDeptId()); } + // 同步更新班级内学生的学制和学历层次 + LambdaUpdateWrapper studentUpdateWrapper = Wrappers.lambdaUpdate(); + studentUpdateWrapper.eq(XgStudentMsg::getClassId, String.valueOf(update.getId())) + .set(XgStudentMsg::getSchoolSystem, update.getSchoolSystem()) + .set(XgStudentMsg::getAcademicLevel, update.getClassType()); + int affected = studentMsgMapper.update(null, studentUpdateWrapper); + log.info("同步更新班级内学生学制/学历字段,班级ID:{},受影响学生数:{}", update.getId(), affected); } return result; diff --git a/docs/需求文档/学工管理学生信息管理需求/学生信息管理需求分析.md b/docs/需求文档/学工管理学生信息管理需求/学生信息管理需求分析.md deleted file mode 100644 index 87e6cdfa..00000000 --- a/docs/需求文档/学工管理学生信息管理需求/学生信息管理需求分析.md +++ /dev/null @@ -1,430 +0,0 @@ -# 学生信息管理需求分析书 - -## 📋 文档信息 - -| 项目 | 信息 | -|------|------| -| **项目名称** | 校园管理系统 - 学工管理模块 | -| **子模块** | 学生信息管理 | -| **文档版本** | V1.0.0 | -| **创建时间** | 2025-01-14 | -| **创建人员** | 产品团队 | -| **审核状态** | 待审核 | - -## 🎯 需求概述 - -### 业务背景 -解决对所有在籍在校学生基本信息的收集,通过已掌握的学生基本信息可进一步统计出有效的数据,作为学校领导开展全校策略、规划工作提供的数据依据等。可解决对于学生基本信息收集流程及审核步骤的信息化服务应用。 - -### 核心目标 -- 实现学生信息收集、维护、审核全流程信息化 -- 建立统一的学生信息数据库,支持多维度统计分析 -- 规范学生信息采集、审核、管理流程 -- 实现分层级、分角色的权限管理体系 -- 为学校管理层提供数据决策支持 - -## 👥 用户角色分析 - -### 1. 学生用户 -**角色描述**:在校学生,信息的主要提供者和维护者 -**主要职责**: -- 填写和维护个人基本信息 -- 在开放的业务期内自行核对和编辑个人信息 -- 提交信息后跟踪查询审核进度 -- 查看个人信息的审核状态和反馈意见 - -**权限范围**: -- 查看:个人信息、审核进度、审核意见 -- 编辑:个人基本信息(在批次开放期内) -- 操作:信息提交、进度查询 - -### 2. 辅导员 -**角色描述**:学生日常管理的直接负责人 -**主要职责**: -- 对所负责学生的信息进行初步审核 -- 指导学生完善信息填写 -- 统计所负责学生的信息完成情况 -- 处理学生信息相关问题 - -**权限范围**: -- 查看:所负责学生的完整信息 -- 编辑:学生信息的审核状态和意见 -- 操作:审核通过/驳回、批量审核、数据导出 - -### 3. 班主任 -**角色描述**:班级管理的直接责任人 -**主要职责**: -- 对班级学生信息进行审核确认 -- 维护班级基础信息和班干部信息 -- 协助辅导员进行学生信息管理 -- 处理班级内学生信息问题 - -**权限范围**: -- 查看:所负责班级学生的完整信息 -- 编辑:班级信息、学生信息审核 -- 操作:审核操作、班级统计、信息导出 - -### 4. 二级学院管理员 -**角色描述**:学院层面的学生信息管理人员 -**主要职责**: -- 对学院范围内学生信息进行复审 -- 统计分析学院学生信息数据 -- 协调处理学院内信息管理问题 -- 向学生处报告信息收集情况 - -**权限范围**: -- 查看:学院内所有学生信息 -- 编辑:学生信息复审、学院统计数据 -- 操作:批量审核、数据分析、报表生成 - -### 5. 学生处管理员 -**角色描述**:全校学生信息管理的最高权限用户 -**主要职责**: -- 创建和管理信息采集批次 -- 配置各用户组的权限设置 -- 进行全校范围的数据统计分析 -- 系统管理和维护 - -**权限范围**: -- 查看:全校所有学生信息 -- 编辑:系统配置、批次管理、权限设置 -- 操作:系统管理、全局统计、数据维护 - -## 📊 功能需求分析 - -### 1. 学生基本信息管理 - -#### 1.1 信息录入与维护 -**需求描述**:提供完整的学生信息录入和维护功能 - -**功能要求**: -- 支持学生基本信息录入(姓名、性别、出生日期、身份证号等) -- 支持学籍信息管理(学号、专业、班级、入学时间、学制等) -- 支持联系信息管理(手机、邮箱、家庭地址、紧急联系人等) -- 支持家庭信息管理(父母信息、家庭收入、家庭成员等) -- 支持体征信息管理(身高、体重、血型、健康状况等) -- **必须包含银行卡信息**(银行卡号、开户银行、持卡人姓名等) - -**技术要求**: -- 银行卡等敏感信息需加密存储 -- 支持必填字段验证 -- 支持数据格式校验 -- 支持信息修改历史记录 - -#### 1.2 信息展示方式 -**需求描述**:提供多种信息展示方式 - -**功能要求**: -- **表格展示**:传统列表形式展示学生信息 -- **卡片展示**:以学生照片、学号、性别、班级等信息进行直观展现 -- **详情展示**:完整信息的详细展示页面 -- 支持字段显示/隐藏配置 -- 支持排序和筛选功能 - -### 2. 班级信息管理 - -#### 2.1 班级基础信息 -**需求描述**:提供班级基础信息的维护和管理功能 - -**功能要求**: -- 班级基本信息维护(班级代码、名称、专业、学院等) -- 班级规模统计(总人数、男女比例等) -- 班级状态管理(正常、毕业、解散等) - -#### 2.2 人员配置管理 -**需求描述**:支持维护辅导员、班主任的聘任信息及班干部的任命信息 - -**功能要求**: -- 辅导员信息配置和管理 -- 班主任信息配置和管理 -- 班干部任命信息管理(班长、副班长、各委员等) -- 人员变更历史记录 -- 任职期限管理 - -### 3. 信息采集批次管理 - -#### 3.1 批次创建与配置 -**需求描述**:提供单独的基本信息批次管理功能,与校每学年的学生信息采集工作开展相对应 - -**功能要求**: -- 批次基本信息配置(名称、时间范围、描述等) -- 批次状态管理(草稿、开放、关闭、归档等) -- 批次数据统计和进度跟踪 -- 批次结果汇总和报告 - -#### 3.2 权限配置 -**需求描述**:根据辅导员、班主任、二级学院、学生处等用户组,批量进行不同的权限配置 - -**功能要求**: -- 基于角色的权限配置 -- 数据访问范围控制 -- 操作权限细粒度管理 -- 权限继承和覆盖机制 - -### 4. 多级审核流程管理 - -#### 4.1 审核流程设计 -**需求描述**:班主任或辅导员、二级学院、学生处可以对于学生填写基本信息的采集进行审核 - -**审核层级**: -1. **学生提交**:学生完成信息填写后提交 -2. **辅导员/班主任初审**:对学生信息进行初步审核 -3. **二级学院复审**:对学院内学生信息进行复审 -4. **学生处终审**:最终审核确认 - -#### 4.2 审核功能要求 -**功能要求**: -- 将必填字段重点罗列显示 -- 将修改或变化的内容以不同的颜色标识出来 -- 支持审核意见录入 -- 支持审核状态跟踪 -- 支持审核历史记录查看 -- 支持批量审核操作 - -#### 4.3 进度跟踪 -**需求描述**:提交后需要支持在线跟踪查询审核进度 - -**功能要求**: -- 实时审核状态显示 -- 审核进度时间线展示 -- 审核意见和反馈查看 -- 审核结果通知机制 - -### 5. 高级搜索与查询 - -#### 5.1 基础搜索功能 -**需求描述**:提供高级搜索、多字段组合查询来快速筛选出符合条件的学生 - -**功能要求**: -- 单字段快速搜索(姓名、学号、身份证等) -- 多字段组合搜索 -- 模糊搜索和精确搜索 -- 搜索历史记录 -- 搜索结果保存和分享 - -#### 5.2 综合条件筛选 -**需求描述**:支持各种综合信息筛选查询,如按成绩条件、违纪处分条件、困难生条件、奖助条件作为查询条件查询 - -**功能要求**: -- 跨业务域的综合条件筛选 -- 多维度条件组合查询 -- 动态筛选条件配置 -- 筛选结果统计分析 -- 筛选条件模板保存 - -### 6. 数据统计分析 - -#### 6.1 基础统计维度 -**需求描述**:提供对在校学生进行查询与统计,包括多个统计维度 - -**统计维度**: -- 二级学院统计 -- 年龄统计 -- 专业统计 -- 政治面貌统计 -- 民族统计 -- 生源地统计 -- 性别统计 -- 血型统计 -- 宗教信仰统计 -- 学籍状态统计 -- 培养方式统计 -- 体重统计 -- 身高统计 - -#### 6.2 统计功能要求 -**功能要求**: -- 支持多维度、多条件的组合查询 -- 相关统计结果能够实现在线详情查看和导出 -- 支持汇总表、统计表、统计图及统计数据透视 -- 支持统计结果的图表展示 -- 支持统计报表的定时生成和推送 - -### 7. 批量数据操作 - -#### 7.1 数据导入功能 -**需求描述**:管理老师可进行单个学生信息的新增和编辑,也支持学生信息批量导入导出操作 - -**功能要求**: -- 支持Excel模板批量导入 -- 数据格式校验和错误提示 -- 导入进度显示和结果反馈 -- 导入数据预览和确认 -- 导入失败数据的错误报告 - -#### 7.2 数据导出功能 -**功能要求**: -- 支持按条件批量导出学生信息 -- 支持多种导出格式(Excel、PDF等) -- 支持导出字段自定义选择 -- 支持导出模板配置 -- 导出操作日志记录 - -#### 7.3 数据集成支持 -**需求描述**:需要支持通过数据集成方式,来获取相应业务数据 - -**功能要求**: -- 支持与教务系统的数据同步 -- 支持与其他业务系统的数据交换 -- 数据同步规则配置 -- 数据冲突处理机制 -- 同步日志和监控 - -### 8. 系统管理功能 - -#### 8.1 权限管理 -**功能要求**: -- 基于角色的权限控制 -- 数据访问权限细粒度管理 -- 权限变更审批流程 -- 权限使用情况监控 - -#### 8.2 系统配置 -**功能要求**: -- 字典数据管理 -- 系统参数配置 -- 业务规则配置 -- 界面个性化设置 - -## 📋 非功能性需求 - -### 1. 性能要求 -- 系统响应时间:页面加载时间不超过3秒 -- 并发用户数:支持1000个并发用户同时访问 -- 数据处理能力:支持10万条学生数据的快速查询和统计 -- 批量操作:支持5000条数据的批量导入导出 - -### 2. 安全要求 -- 数据加密:银行卡等敏感信息必须加密存储 -- 访问控制:基于角色的权限控制,确保数据安全 -- 审计日志:完整的操作日志记录和审计追踪 -- 数据备份:定期数据备份和恢复机制 - -### 3. 可用性要求 -- 系统可用性:99.5%以上的系统可用率 -- 界面友好:简洁直观的用户界面设计 -- 操作便捷:符合用户习惯的操作流程 -- 帮助支持:完善的帮助文档和在线支持 - -### 4. 兼容性要求 -- 浏览器兼容:支持主流浏览器(Chrome、Firefox、Safari、Edge) -- 移动端支持:支持移动端访问和操作 -- 系统集成:支持与其他校园系统的集成 -- 数据格式:支持主流数据格式的导入导出 - -## 🔄 业务流程设计 - -### 1. 信息采集流程 -``` -批次创建 → 权限配置 → 学生填写 → 信息提交 → 多级审核 → 审核完成 → 数据归档 -``` - -### 2. 审核流程 -``` -学生提交 → 辅导员初审 → 二级学院复审 → 学生处终审 → 审核完成 - ↓ ↓ ↓ - 驳回修改 驳回修改 驳回修改 -``` - -### 3. 数据统计流程 -``` -条件设置 → 数据查询 → 统计分析 → 结果展示 → 报表导出 -``` - -## 📊 数据需求分析 - -### 1. 核心数据实体 -- **学生信息**:学生基本信息、学籍信息、联系信息、家庭信息等 -- **班级信息**:班级基础信息、人员配置、统计数据等 -- **批次信息**:采集批次、配置信息、进度数据等 -- **审核信息**:审核记录、审核意见、状态变更等 - -### 2. 数据关系设计 -- 学生与班级:多对一关系 -- 学生与批次:多对多关系 -- 审核与学生:一对多关系 -- 用户与权限:多对多关系 - -### 3. 数据安全要求 -- 敏感信息加密存储 -- 数据访问权限控制 -- 数据变更审计日志 -- 数据备份和恢复 - -## 🎯 验收标准 - -### 1. 功能验收 -- 所有功能模块按需求正常运行 -- 用户角色权限控制正确 -- 数据统计分析准确 -- 批量操作功能稳定 - -### 2. 性能验收 -- 系统响应时间符合要求 -- 并发用户数达到指标 -- 数据处理能力满足需求 -- 系统稳定性良好 - -### 3. 安全验收 -- 数据加密存储正确 -- 权限控制有效 -- 审计日志完整 -- 数据备份可靠 - -### 4. 用户验收 -- 界面友好易用 -- 操作流程顺畅 -- 帮助文档完善 -- 用户培训到位 - -## 📅 项目计划 - -### 第一阶段:基础功能开发(4周) -- 学生基本信息管理 -- 班级信息管理 -- 基础的增删改查功能 - -### 第二阶段:业务流程开发(4周) -- 信息采集批次管理 -- 多级审核流程管理 -- 高级搜索与查询 - -### 第三阶段:数据分析与展示(3周) -- 数据统计分析 -- 批量数据操作 -- 多视图展示与权限管理 - -### 第四阶段:测试与部署(2周) -- 功能测试 -- 性能测试 -- 用户验收测试 -- 系统部署上线 - -## 📝 风险评估 - -### 1. 技术风险 -- 数据量大导致的性能问题 -- 复杂权限控制的实现难度 -- 多系统集成的技术挑战 - -### 2. 业务风险 -- 需求变更导致的开发延期 -- 用户培训不足影响使用效果 -- 数据迁移可能出现的问题 - -### 3. 风险应对措施 -- 提前进行性能测试和优化 -- 采用成熟的权限管理框架 -- 制定详细的集成测试方案 -- 建立需求变更管理流程 - ---- - -## 📞 联系信息 - -**项目负责人**:产品团队 -**技术负责人**:开发团队 -**创建时间**:2025-01-14 -**文档版本**:V1.0.0 - -**备注**:本文档将根据项目进展和需求变化进行持续更新和完善。 \ No newline at end of file diff --git a/web/src/views/xg/xsjcxx/studentMsg/studentMsg/index.vue b/web/src/views/xg/xsjcxx/studentMsg/studentMsg/index.vue index 7588831d..4ddf7b0f 100644 --- a/web/src/views/xg/xsjcxx/studentMsg/studentMsg/index.vue +++ b/web/src/views/xg/xsjcxx/studentMsg/studentMsg/index.vue @@ -5,6 +5,13 @@
+
+ 班级状态: + + 全部 + {{ normalClassStatusLabel }} + +
+ > + +
@@ -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( 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([]); const majorOptions = ref([]); const classOptions = ref([]); const regionOptions = ref([]); // 行政区划选项 @@ -1621,6 +1662,10 @@ const dormBuildingOptions = ref([]); // 宿舍楼栋选项 const currentSelectedNode = ref(null); // 当前选中的部门节点 const roomCache = ref>(new Map()); // 房间名称缓存 const bedCache = ref>(new Map()); // 床位名称缓存 +const classStatusFilter = ref<'all' | 'normal'>('all'); +const classStatusMap = new Map(); +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 => { 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;