提交
This commit is contained in:
1 parent
7d4bfaecb3
commit
f4a1eac9ed
3 files changed
+320
-449
No files matched your search
+10
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user