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

@@ -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<XgStudentMsg> 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;
@@ -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
**备注**:本文档将根据项目进展和需求变化进行持续更新和完善。
@@ -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;