wuzhen update

This commit is contained in:
wuzhen committed 2025-08-02 20:54:37 +08:00
commit 9ea52837de
34 files changed
+6949 -1393

No files matched your search

@@ -0,0 +1,150 @@
# 代码调整记录 - 2025-01-07 - assistant
## 高级检索界面参数传递功能实现
**时间**: 2025-01-07 22:30:00
**修改人**: assistant
**影响模块**: 学工管理 - 学生信息管理 - 学生花名册
### 修改内容
#### 1. 主页面功能增强
**文件**: `web/src/views/xg/xsjcxx/stuRoster/index.vue`
**修改点1**: 高级检索按钮事件处理
- 位置: 第66行
- 修改前: `@click="showAdvancedSearch = true"`
- 修改后: `@click="handleShowAdvancedSearch"`
- 说明: 将直接显示弹窗改为调用处理方法,以便在显示前同步参数
**修改点2**: 高级查询参数定义增强
- 位置: 第293-322行
- 新增字段:
```javascript
studentNo: '', // 学号
name: '', // 学生姓名
phone: '', // 手机号码
studentStatus: [], // 学籍状态
```
- 说明: 在 advancedQueryParams 中添加基础检索栏对应的字段
**修改点3**: 新增参数传递处理方法
- 位置: 第481-501行
- 新增方法: `handleShowAdvancedSearch`
- 功能描述:
```javascript
const handleShowAdvancedSearch = () => {
// 将基础检索栏的参数同步到高级检索参数中
if (queryParams.studentNo) {
advancedQueryParams.studentNo = queryParams.studentNo;
}
if (queryParams.name) {
advancedQueryParams.name = queryParams.name;
}
if (queryParams.phone) {
advancedQueryParams.phone = queryParams.phone;
}
// 状态对应学籍状况
if (queryParams.studentStatus && queryParams.studentStatus.length > 0) {
advancedQueryParams.studentStatus = [...queryParams.studentStatus];
}
// 显示高级检索弹窗
showAdvancedSearch.value = true;
};
```
### 功能说明
#### 实现效果
1. **参数自动传递**: 用户在基础检索栏输入学号、姓名、手机号码、选择状态后,点击"高级检索"按钮时,这些参数会自动传递到高级检索界面
2. **状态字段映射**: 基础检索栏的"状态"字段对应高级检索界面的"学籍状态"字段
3. **数组类型处理**: 正确处理多选状态字段的数组格式传递
4. **条件判断**: 只有当基础检索栏有值时才进行传递,避免空值覆盖
#### 用户体验提升
- 避免用户在高级检索中重复输入已在基础检索中输入的内容
- 保持检索条件的连续性和一致性
- 提高检索操作的便利性
### 技术要点
1. **响应式数据同步**: 使用 reactive 对象的属性直接赋值实现参数同步
2. **数组深拷贝**: 对于数组类型的 studentStatus 使用展开运算符进行深拷贝
3. **条件判断**: 通过 if 判断确保只有有效值才进行传递
4. **组件通信**: 利用高级检索组件的 watch 监听机制实现参数自动同步到表单
### 相关文件
- 主页面: `web/src/views/xg/xsjcxx/stuRoster/index.vue`
- 高级检索组件: `web/src/views/xg/xsjcxx/stuRoster/components/AdvancedSearchDialog.vue`
### 测试建议
1. 在基础检索栏输入学号、姓名、手机号码,选择状态
2. 点击"高级检索"按钮
3. 验证高级检索界面中对应字段是否已自动填充
4. 验证状态字段是否正确映射到学籍状态
---
# 校园管理系统代码调整记录
**日期**: 2025-01-07
**操作人**: assistant
**说明**: 记录2025年1月7日的代码调整和修改内容
---
## 2025-01-07 20:15:00 - 优化学生花名册主界面查询逻辑
### 🔧 修改内容
**文件**: `web/src/views/xg/xsjcxx/stuRoster/index.vue`
**修改说明**:
- 优化 `handleQuery` 方法,确保基本搜索时只保留检索栏的4个条件和部门树选择
- 清除高级查询产生的额外参数,避免参数污染
**具体变更**:
```javascript
// 修改前
const handleQuery = () => {
queryParams.pageNum = 1;
getList();
};
// 修改后
const handleQuery = () => {
// 保留基本检索条件和部门选择
const basicSearchParams = {
pageNum: 1,
pageSize: queryParams.pageSize,
studentNo: queryParams.studentNo || '',
name: queryParams.name || '',
phone: queryParams.phone || '',
studentStatus: queryParams.studentStatus || [],
deptIds: queryParams.deptIds || []
};
// 清空 queryParams 对象的所有属性
Object.keys(queryParams).forEach((key) => {
delete queryParams[key];
});
// 重新设置基本检索参数
Object.assign(queryParams, basicSearchParams);
getList();
};
```
**影响范围**:
- ✅ 基本搜索功能:只使用检索栏的4个条件进行查询
- ✅ 部门树选择:保留左侧部门树的选择状态
- ✅ 高级查询功能:不影响高级查询的独立功能
- ✅ 分页功能:保持正常的分页逻辑
**测试验证**:
- [x] 基本搜索按钮点击后只传递基本参数
- [x] 部门树选择状态正常保留
- [x] 高级查询后再使用基本搜索,额外参数被正确清除
---
@@ -0,0 +1,626 @@
# 学生信息高级检索功能开发记录
**变更时间**: 2025-01-27 15:30:00
**变更人员**: zlwu
**变更类型**: 功能新增
**影响范围**: 学生信息管理模块
## 📋 变更概述
为前端高级检索界面完善API功能,新增学生信息高级检索功能,支持多维度、多条件的复杂查询。
## 🎯 功能需求
### 主要功能
1. **高级检索BO类**: 创建专门的高级检索业务对象,包含所有检索字段
2. **权限控制**: 支持部门树节点权限过滤
3. **多维度检索**: 支持基础信息、学籍信息、学院专业班级、家庭信息、特殊身份、身体状况、住宿信息等多个维度
4. **灵活查询**: 文本类参数支持模糊查询,数值类参数支持范围查询,时间类参数支持时间范围查询
### 检索维度
- **基础信息检索**: 学号、姓名、身份证号、手机号码、邮箱、性别、民族、政治面貌、出生日期
- **学籍信息检索**: 学籍状态、入学年份、入学日期、录取类别
- **学院专业班级检索**: 学院名称、专业名称、班级名称、年级、辅导员姓名、班主任姓名
- **家庭信息检索**: 父亲姓名、母亲姓名、监护人姓名、家庭年收入、人均收入、家庭经济状况、家庭成员数量
- **特殊身份检索**: 贫困生、少数民族、残疾、孤儿、留守儿童、独生子女、低保、助学贷款
- **身体状况检索**: 身高、体重、血型、健康状况、残疾类型、残疾等级
- **住宿信息检索**: 是否住校、宿舍楼栋、宿舍房间号
- **其他信息检索**: 籍贯、生源地区、毕业院校、户口类型、文化程度、培养方式、学历层次、学制
- **时间范围检索**: 创建时间、更新时间
## 🔧 技术实现
### 1. 新增文件
#### 1.1 高级检索BO类
**文件路径**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgStudentMsgAdvancedSearchBo.java`
**主要特性**:
- 包含所有检索维度的字段定义
- 支持文本模糊查询、数值范围查询、时间范围查询
- 包含部门树节点ID列表
- 包含分页参数
**关键字段**:
```java
// 基础信息检索
private String studentNo; // 学号(模糊查询)
private String name; // 姓名(模糊查询)
private String idCard; // 身份证号(模糊查询)
private String phone; // 手机号码(模糊查询)
private String email; // 邮箱(模糊查询)
private String gender; // 性别:0-男,1-女
private String nationality; // 民族
private String politicalStatus; // 政治面貌
private Date birthDateStart; // 出生日期范围 - 开始
private Date birthDateEnd; // 出生日期范围 - 结束
// 学籍信息检索
private String studentStatus; // 学籍状态
private Long enrollmentYearStart; // 入学年份范围 - 开始
private Long enrollmentYearEnd; // 入学年份范围 - 结束
private Date enrollmentDateStart; // 入学日期范围 - 开始
private Date enrollmentDateEnd; // 入学日期范围 - 结束
private String admissionCategory; // 录取类别
// 学院专业班级检索
private String collegeName; // 学院名称(模糊查询)
private String majorName; // 专业名称(模糊查询)
private String className; // 班级名称(模糊查询)
private String grade; // 年级
private String counselorName; // 辅导员姓名(模糊查询)
private String classTeacherName; // 班主任姓名(模糊查询)
// 家庭信息检索
private String fatherName; // 父亲姓名(模糊查询)
private String motherName; // 母亲姓名(模糊查询)
private String guardianName; // 监护人姓名(模糊查询)
private Double familyIncomeMin; // 家庭年收入范围 - 最小值
private Double familyIncomeMax; // 家庭年收入范围 - 最大值
private Double perCapitaIncomeMin; // 人均收入范围 - 最小值
private Double perCapitaIncomeMax; // 人均收入范围 - 最大值
private String familyEconomicStatus; // 家庭经济状况
private Long familyMembersMin; // 家庭成员数量范围 - 最小值
private Long familyMembersMax; // 家庭成员数量范围 - 最大值
// 特殊身份检索
private Long isPoorStudent; // 是否贫困生:0-否,1-是
private Long isMinority; // 是否少数民族:0-否,1-是
private Long isDisabled; // 是否残疾:0-否,1-是
private Long isOrphan; // 是否孤儿:0-否,1-是
private Long isLeftBehindChild; // 是否留守儿童:0-否,1-是
private Long isSingleChild; // 是否独生子女:0-否,1-是
private Long isLowIncome; // 是否低保:0-否,1-是
private Long hasLoan; // 是否有助学贷款:0-否,1-是
private Double loanAmountMin; // 助学贷款金额范围 - 最小值
private Double loanAmountMax; // 助学贷款金额范围 - 最大值
// 身体状况检索
private Double heightMin; // 身高范围 - 最小值(cm)
private Double heightMax; // 身高范围 - 最大值(cm)
private Double weightMin; // 体重范围 - 最小值(kg)
private Double weightMax; // 体重范围 - 最大值(kg)
private String bloodType; // 血型
private String healthStatus; // 健康状况
private String disabilityType; // 残疾类型
private String disabilityLevel; // 残疾等级
// 住宿信息检索
private Long isBoarder; // 是否住校:0-否,1-是
private String dormitoryBuilding; // 宿舍楼栋(模糊查询)
private String dormitoryRoom; // 宿舍房间号(模糊查询)
// 其他信息检索
private String nativePlace; // 籍贯(模糊查询)
private String sourceRegion; // 生源地区(省市县)
private String graduateSchool; // 毕业院校(模糊查询)
private String householdRegisterType; // 户口类型:农业、非农业
private String educationLevel; // 文化程度
private String trainingType; // 培养方式:全日制、在职、函授、夜大等
private String academicLevel; // 学历层次
private Long schoolSystemMin; // 学制范围 - 最小值(年)
private Long schoolSystemMax; // 学制范围 - 最大值(年)
private String infoStatus; // 信息状态:1-正常,2-待审核,3-审核中,4-已驳回,5-已锁定
private String dataSource; // 数据来源:manual-手工录入,import-批量导入,sync-系统同步
private Long isComplete; // 信息是否完整:0-不完整,1-完整
private Double completionRateMin; // 完整度百分比范围 - 最小值
private Double completionRateMax; // 完整度百分比范围 - 最大值
// 时间范围检索
private Date createTimeStart; // 创建时间范围 - 开始
private Date createTimeEnd; // 创建时间范围 - 结束
private Date updateTimeStart; // 更新时间范围 - 开始
private Date updateTimeEnd; // 更新时间范围 - 结束
// 部门树节点
private List<Long> deptIds; // 部门树勾选的节点ID列表
// 分页参数
private Integer pageNum = 1; // 分页参数
private Integer pageSize = 10; // 分页大小
```
### 2. 修改文件
#### 2.1 Service接口扩展
**文件路径**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgStudentMsgService.java`
**新增方法**:
```java
/**
* 高级检索学生信息
*
* @param bo 高级检索条件
* @return 学生信息分页列表
*/
TableDataInfo<XgStudentMsgVo> advancedSearch(XgStudentMsgAdvancedSearchBo bo);
```
#### 2.2 Service实现类扩展
**文件路径**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentMsgServiceImpl.java`
**新增方法**:
```java
/**
* 高级检索学生信息
*/
@Override
public TableDataInfo<XgStudentMsgVo> advancedSearch(XgStudentMsgAdvancedSearchBo bo) {
// 第一步:权限过滤,获取过滤后的deptIds
List<Long> filteredDeptIds = filterDeptIdsForAdvancedSearch(bo.getDeptIds());
// 第二步:构建高级检索查询条件
LambdaQueryWrapper<XgStudentMsg> lqw = buildAdvancedSearchQueryWrapper(bo);
// 第三步:分页查询学生信息
Page<XgStudentMsgVo> result = baseMapper.selectPageByDeptIds(
new Page<>(bo.getPageNum(), bo.getPageSize()), lqw, filteredDeptIds);
return TableDataInfo.build(result);
}
/**
* 权限过滤方法 - 高级检索专用
*/
private List<Long> filterDeptIdsForAdvancedSearch(List<Long> deptIds) {
// 获取当前登录用户ID
Long currentUserId = LoginHelper.getUserId();
// 判断当前用户是否为全校管理员
boolean isSchoolManager = checkIsSchoolManager(currentUserId);
if (isSchoolManager) {
return getDeptIdsWithChildrenForAdvancedSearch(deptIds);
}
// 获取用户的总权限集合
Set<Long> userAuthDeptIds = getUserAuthDeptIds(currentUserId);
// 过滤deptIds,只保留在权限范围内的
List<Long> filteredDeptIds = new ArrayList<>();
if (deptIds != null && !deptIds.isEmpty()) {
for (Long deptId : deptIds) {
List<Long> deptIdsWithChildren = getDeptIdsWithChildren(deptId);
for (Long deptIdWithChild : deptIdsWithChildren) {
if (userAuthDeptIds.contains(deptIdWithChild)) {
filteredDeptIds.add(deptIdWithChild);
}
}
}
} else {
filteredDeptIds.addAll(userAuthDeptIds);
}
// 对过滤后的deptIds,查找所有子组织包括他们自己
Set<Long> finalDeptIds = new HashSet<>();
for (Long filteredDeptId : filteredDeptIds) {
List<Long> deptIdsWithChildren = getDeptIdsWithChildren(filteredDeptId);
finalDeptIds.addAll(deptIdsWithChildren);
}
return new ArrayList<>(finalDeptIds);
}
/**
* 构建高级检索查询条件包装器
*/
private LambdaQueryWrapper<XgStudentMsg> buildAdvancedSearchQueryWrapper(XgStudentMsgAdvancedSearchBo bo) {
LambdaQueryWrapper<XgStudentMsg> lqw = Wrappers.lambdaQuery();
// 基础查询条件
lqw.eq(XgStudentMsg::getDeleted, 0); // 未删除
// ==================== 基础信息检索 ====================
// 文本类参数使用like查询
lqw.like(StringUtils.isNotBlank(bo.getStudentNo()), XgStudentMsg::getStudentNo, bo.getStudentNo());
lqw.like(StringUtils.isNotBlank(bo.getName()), XgStudentMsg::getName, bo.getName());
lqw.like(StringUtils.isNotBlank(bo.getIdCard()), XgStudentMsg::getIdCard, bo.getIdCard());
lqw.like(StringUtils.isNotBlank(bo.getPhone()), XgStudentMsg::getPhone, bo.getPhone());
lqw.like(StringUtils.isNotBlank(bo.getEmail()), XgStudentMsg::getEmail, bo.getEmail());
// 精确匹配参数
lqw.eq(StringUtils.isNotBlank(bo.getGender()), XgStudentMsg::getGender, bo.getGender());
lqw.eq(StringUtils.isNotBlank(bo.getNationality()), XgStudentMsg::getNationality, bo.getNationality());
lqw.eq(StringUtils.isNotBlank(bo.getPoliticalStatus()), XgStudentMsg::getPoliticalStatus, bo.getPoliticalStatus());
// 时间范围查询
if (bo.getBirthDateStart() != null && bo.getBirthDateEnd() != null) {
lqw.between(XgStudentMsg::getBirthDate, bo.getBirthDateStart(), bo.getBirthDateEnd());
}
// ==================== 学籍信息检索 ====================
lqw.eq(StringUtils.isNotBlank(bo.getStudentStatus()), XgStudentMsg::getStudentStatus, bo.getStudentStatus());
lqw.eq(StringUtils.isNotBlank(bo.getAdmissionCategory()), XgStudentMsg::getAdmissionCategory, bo.getAdmissionCategory());
// 数值范围查询
if (bo.getEnrollmentYearStart() != null && bo.getEnrollmentYearEnd() != null) {
lqw.between(XgStudentMsg::getEnrollmentYear, bo.getEnrollmentYearStart(), bo.getEnrollmentYearEnd());
}
// 时间范围查询
if (bo.getEnrollmentDateStart() != null && bo.getEnrollmentDateEnd() != null) {
lqw.between(XgStudentMsg::getEnrollmentDate, bo.getEnrollmentDateStart(), bo.getEnrollmentDateEnd());
}
// ==================== 学院专业班级检索 ====================
lqw.like(StringUtils.isNotBlank(bo.getCollegeName()), XgStudentMsg::getCollegeName, bo.getCollegeName());
lqw.like(StringUtils.isNotBlank(bo.getMajorName()), XgStudentMsg::getMajorName, bo.getMajorName());
lqw.like(StringUtils.isNotBlank(bo.getClassName()), XgStudentMsg::getClassName, bo.getClassName());
lqw.eq(StringUtils.isNotBlank(bo.getGrade()), XgStudentMsg::getGrade, bo.getGrade());
lqw.like(StringUtils.isNotBlank(bo.getCounselorName()), XgStudentMsg::getCounselorName, bo.getCounselorName());
lqw.like(StringUtils.isNotBlank(bo.getClassTeacherName()), XgStudentMsg::getClassTeacherName, bo.getClassTeacherName());
// ==================== 家庭信息检索 ====================
lqw.like(StringUtils.isNotBlank(bo.getFatherName()), XgStudentMsg::getFatherName, bo.getFatherName());
lqw.like(StringUtils.isNotBlank(bo.getMotherName()), XgStudentMsg::getMotherName, bo.getMotherName());
lqw.like(StringUtils.isNotBlank(bo.getGuardianName()), XgStudentMsg::getGuardianName, bo.getGuardianName());
// 数值范围查询
if (bo.getFamilyIncomeMin() != null && bo.getFamilyIncomeMax() != null) {
lqw.between(XgStudentMsg::getFamilyIncome, bo.getFamilyIncomeMin(), bo.getFamilyIncomeMax());
}
if (bo.getPerCapitaIncomeMin() != null && bo.getPerCapitaIncomeMax() != null) {
lqw.between(XgStudentMsg::getPerCapitaIncome, bo.getPerCapitaIncomeMin(), bo.getPerCapitaIncomeMax());
}
lqw.eq(StringUtils.isNotBlank(bo.getFamilyEconomicStatus()), XgStudentMsg::getFamilyEconomicStatus, bo.getFamilyEconomicStatus());
if (bo.getFamilyMembersMin() != null && bo.getFamilyMembersMax() != null) {
lqw.between(XgStudentMsg::getFamilyMembers, bo.getFamilyMembersMin(), bo.getFamilyMembersMax());
}
// ==================== 特殊身份检索 ====================
lqw.eq(bo.getIsPoorStudent() != null, XgStudentMsg::getIsPoorStudent, bo.getIsPoorStudent());
lqw.eq(bo.getIsMinority() != null, XgStudentMsg::getIsMinority, bo.getIsMinority());
lqw.eq(bo.getIsDisabled() != null, XgStudentMsg::getIsDisabled, bo.getIsDisabled());
lqw.eq(bo.getIsOrphan() != null, XgStudentMsg::getIsOrphan, bo.getIsOrphan());
lqw.eq(bo.getIsLeftBehindChild() != null, XgStudentMsg::getIsLeftBehindChild, bo.getIsLeftBehindChild());
lqw.eq(bo.getIsSingleChild() != null, XgStudentMsg::getIsSingleChild, bo.getIsSingleChild());
lqw.eq(bo.getIsLowIncome() != null, XgStudentMsg::getIsLowIncome, bo.getIsLowIncome());
lqw.eq(bo.getHasLoan() != null, XgStudentMsg::getHasLoan, bo.getHasLoan());
if (bo.getLoanAmountMin() != null && bo.getLoanAmountMax() != null) {
lqw.between(XgStudentMsg::getLoanAmount, bo.getLoanAmountMin(), bo.getLoanAmountMax());
}
// ==================== 身体状况检索 ====================
if (bo.getHeightMin() != null && bo.getHeightMax() != null) {
lqw.between(XgStudentMsg::getHeight, bo.getHeightMin(), bo.getHeightMax());
}
if (bo.getWeightMin() != null && bo.getWeightMax() != null) {
lqw.between(XgStudentMsg::getWeight, bo.getWeightMin(), bo.getWeightMax());
}
lqw.eq(StringUtils.isNotBlank(bo.getBloodType()), XgStudentMsg::getBloodType, bo.getBloodType());
lqw.eq(StringUtils.isNotBlank(bo.getHealthStatus()), XgStudentMsg::getHealthStatus, bo.getHealthStatus());
lqw.eq(StringUtils.isNotBlank(bo.getDisabilityType()), XgStudentMsg::getDisabilityType, bo.getDisabilityType());
lqw.eq(StringUtils.isNotBlank(bo.getDisabilityLevel()), XgStudentMsg::getDisabilityLevel, bo.getDisabilityLevel());
// ==================== 住宿信息检索 ====================
lqw.eq(bo.getIsBoarder() != null, XgStudentMsg::getIsBoarder, bo.getIsBoarder());
lqw.like(StringUtils.isNotBlank(bo.getDormitoryBuilding()), XgStudentMsg::getDormitoryBuilding, bo.getDormitoryBuilding());
lqw.like(StringUtils.isNotBlank(bo.getDormitoryRoom()), XgStudentMsg::getDormitoryRoom, bo.getDormitoryRoom());
// ==================== 其他信息检索 ====================
lqw.like(StringUtils.isNotBlank(bo.getNativePlace()), XgStudentMsg::getNativePlace, bo.getNativePlace());
lqw.eq(StringUtils.isNotBlank(bo.getSourceRegion()), XgStudentMsg::getSourceRegion, bo.getSourceRegion());
lqw.like(StringUtils.isNotBlank(bo.getGraduateSchool()), XgStudentMsg::getGraduateSchool, bo.getGraduateSchool());
lqw.eq(StringUtils.isNotBlank(bo.getHouseholdRegisterType()), XgStudentMsg::getHouseholdRegisterType, bo.getHouseholdRegisterType());
lqw.eq(StringUtils.isNotBlank(bo.getEducationLevel()), XgStudentMsg::getEducationLevel, bo.getEducationLevel());
lqw.eq(StringUtils.isNotBlank(bo.getTrainingType()), XgStudentMsg::getTrainingType, bo.getTrainingType());
lqw.eq(StringUtils.isNotBlank(bo.getAcademicLevel()), XgStudentMsg::getAcademicLevel, bo.getAcademicLevel());
if (bo.getSchoolSystemMin() != null && bo.getSchoolSystemMax() != null) {
lqw.between(XgStudentMsg::getSchoolSystem, bo.getSchoolSystemMin(), bo.getSchoolSystemMax());
}
lqw.eq(StringUtils.isNotBlank(bo.getInfoStatus()), XgStudentMsg::getInfoStatus, bo.getInfoStatus());
lqw.eq(StringUtils.isNotBlank(bo.getDataSource()), XgStudentMsg::getDataSource, bo.getDataSource());
lqw.eq(bo.getIsComplete() != null, XgStudentMsg::getIsComplete, bo.getIsComplete());
if (bo.getCompletionRateMin() != null && bo.getCompletionRateMax() != null) {
lqw.between(XgStudentMsg::getCompletionRate, bo.getCompletionRateMin(), bo.getCompletionRateMax());
}
// ==================== 时间范围检索 ====================
if (bo.getCreateTimeStart() != null && bo.getCreateTimeEnd() != null) {
lqw.between(XgStudentMsg::getCreateTime, bo.getCreateTimeStart(), bo.getCreateTimeEnd());
}
if (bo.getUpdateTimeStart() != null && bo.getUpdateTimeEnd() != null) {
lqw.between(XgStudentMsg::getUpdateTime, bo.getUpdateTimeStart(), bo.getUpdateTimeEnd());
}
// 排序:按创建时间倒序
lqw.orderByDesc(XgStudentMsg::getCreateTime);
return lqw;
}
```
#### 2.3 Controller新增API
**文件路径**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgStudentMsgController.java`
**新增API方法**:
```java
/**
* 高级检索学生信息
*
* @param bo 高级检索条件
* @return 学生信息分页列表
*/
@SaCheckPermission("studentMsg:studentMsg:list")
@PostMapping("/advancedSearch")
public TableDataInfo<XgStudentMsgVo> advancedSearch(@RequestBody XgStudentMsgAdvancedSearchBo bo) {
log.info("接收到高级检索请求,检索条件: {}", bo);
return xgStudentMsgService.advancedSearch(bo);
}
```
## 🔍 查询逻辑说明
### 1. 查询类型分类
#### 1.1 文本类参数 - 模糊查询
使用 `LIKE` 操作符进行模糊匹配:
- 学号、姓名、身份证号、手机号码、邮箱
- 学院名称、专业名称、班级名称、辅导员姓名、班主任姓名
- 父亲姓名、母亲姓名、监护人姓名
- 宿舍楼栋、宿舍房间号
- 籍贯、毕业院校
#### 1.2 数值类参数 - 范围查询
使用 `BETWEEN` 操作符进行范围查询:
- 入学年份:`enrollmentYearStart` 到 `enrollmentYearEnd`
- 家庭年收入:`familyIncomeMin` 到 `familyIncomeMax`
- 人均收入:`perCapitaIncomeMin` 到 `perCapitaIncomeMax`
- 家庭成员数量:`familyMembersMin` 到 `familyMembersMax`
- 助学贷款金额:`loanAmountMin` 到 `loanAmountMax`
- 身高:`heightMin` 到 `heightMax`
- 体重:`weightMin` 到 `weightMax`
- 学制:`schoolSystemMin` 到 `schoolSystemMax`
- 完整度百分比:`completionRateMin` 到 `completionRateMax`
#### 1.3 时间类参数 - 时间范围查询
使用 `BETWEEN` 操作符进行时间范围查询:
- 出生日期:`birthDateStart` 到 `birthDateEnd`
- 入学日期:`enrollmentDateStart` 到 `enrollmentDateEnd`
- 创建时间:`createTimeStart` 到 `createTimeEnd`
- 更新时间:`updateTimeStart` 到 `updateTimeEnd`
#### 1.4 枚举类参数 - 精确匹配
使用 `=` 操作符进行精确匹配:
- 性别、民族、政治面貌、学籍状态、录取类别
- 年级、家庭经济状况、血型、健康状况
- 残疾类型、残疾等级、户口类型、文化程度
- 培养方式、学历层次、信息状态、数据来源
- 各种是否标识(贫困生、少数民族、残疾等)
### 2. 权限控制逻辑
#### 2.1 全校管理员权限
- 不进行权限过滤
- 可以查看所有部门的学生信息
- 返回包含入参deptIds及其子组织的列表
#### 2.2 普通用户权限
- 获取用户的总权限集合
- 过滤deptIds,只保留在权限范围内的
- 如果入参deptIds为空,直接使用用户权限范围内的所有部门
- 对过滤后的deptIds,查找所有子组织包括他们自己
### 3. 查询优化
#### 3.1 条件构建优化
- 使用 `StringUtils.isNotBlank()` 进行空值检查
- 只有非空参数才会添加到查询条件中
- 避免无效的查询条件影响性能
#### 3.2 排序优化
- 默认按创建时间倒序排列
- 确保查询结果的一致性和可预测性
## 📊 API接口规范
### 请求信息
- **接口路径**: `POST /studentMsg/studentMsg/advancedSearch`
- **权限要求**: `studentMsg:studentMsg:list`
- **请求方式**: POST
- **Content-Type**: application/json
### 请求参数
```json
{
"studentNo": "2021001",
"name": "张三",
"gender": "0",
"studentStatus": "1",
"collegeName": "计算机学院",
"isPoorStudent": 1,
"heightMin": 160,
"heightMax": 180,
"deptIds": [1001, 1002, 1003],
"pageNum": 1,
"pageSize": 10
}
```
### 响应格式
```json
{
"code": 200,
"msg": "操作成功",
"rows": [
{
"id": 1234567890,
"studentNo": "2021001",
"name": "张三",
"gender": "0",
"phone": "13800138000",
"email": "zhangsan@example.com",
"studentStatus": "1",
"collegeName": "计算机学院",
"majorName": "软件工程",
"className": "软件2101班",
"isPoorStudent": 1,
"height": 175.0,
"weight": 65.0,
"createTime": "2025-01-27 15:30:00",
"updateTime": "2025-01-27 15:30:00"
}
],
"total": 1
}
```
## ✅ 测试验证
### 1. 功能测试
- [x] 基础信息检索测试
- [x] 学籍信息检索测试
- [x] 学院专业班级检索测试
- [x] 家庭信息检索测试
- [x] 特殊身份检索测试
- [x] 身体状况检索测试
- [x] 住宿信息检索测试
- [x] 其他信息检索测试
- [x] 时间范围检索测试
### 2. 权限测试
- [x] 全校管理员权限测试
- [x] 普通用户权限测试
- [x] 部门树节点权限过滤测试
### 3. 性能测试
- [x] 大数据量查询性能测试
- [x] 复杂条件组合查询测试
- [x] 分页查询性能测试
## 🚀 部署说明
### 1. 编译部署
```bash
# 编译项目
mvn clean compile
# 打包项目
mvn clean package
# 启动服务
java -jar ruoyi-admin.jar
```
### 2. 数据库验证
- 确保 `xg_student_msg` 表结构完整
- 确保相关索引已创建
- 确保权限相关表数据正确
### 3. 接口验证
```bash
# 测试高级检索接口
curl -X POST http://localhost:8080/studentMsg/studentMsg/advancedSearch \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"name": "张三",
"pageNum": 1,
"pageSize": 10
}'
```
## 📝 注意事项
### 1. 性能考虑
- 大量数据查询时建议使用分页
- 复杂条件组合可能影响查询性能
- 建议在常用查询字段上创建索引
### 2. 权限安全
- 严格遵循权限控制逻辑
- 确保用户只能查看有权限的数据
- 定期检查权限配置的正确性
### 3. 数据一致性
- 确保查询条件的数据类型匹配
- 注意时间格式的统一性
- 验证枚举值的有效性
## 🔄 后续优化
### 1. 功能扩展
- 支持更多检索维度
- 增加检索条件保存功能
- 支持检索结果导出
### 2. 性能优化
- 添加查询缓存机制
- 优化数据库索引
- 实现异步查询处理
### 3. 用户体验
- 提供检索条件模板
- 增加检索历史记录
- 优化前端交互体验
---
**变更完成时间**: 2025-01-27 17:00:00
**变更状态**: ✅ 已完成
**测试状态**: ✅ 已通过
**部署状态**: ⏳ 待部署
## 📝 后续完善记录
### 2025-01-27 16:30:00 - 完善检索条件配置
#### 完善内容
1. **补充BO类缺失字段**:
- 英文姓名、曾用名
- 户籍所在地、家庭地址、现住址
- QQ号码、微信号、婚姻状况
- 宗教信仰
- 父亲电话、父亲工作单位
- 母亲电话、母亲工作单位
- 监护人电话、监护人关系
- 收入来源
- 紧急联系人姓名、电话、关系
- 特长爱好、获奖情况、违纪记录、社会活动
- 特殊体质或疾病、病史、过敏史
2. **完善查询条件构建方法**:
- 按照前端界面顺序重新组织查询条件
- 基础信息 → 学籍信息 → 家庭信息 → 扩展信息 → 健康信息 → 时间范围
- 修正查询类型:文本类使用like,数值类使用between,枚举类使用eq
- 添加详细的注释说明每个检索条件的作用
#### 技术改进
- 修复了BO类中重复字段定义的问题
- 优化了查询条件的组织结构,提高代码可读性
- 确保所有前端界面的检索条件都有对应的后端处理逻辑
### 2025-01-27 17:00:00 - 支持下拉框多选功能
#### 完善内容
1. **修改BO类字段类型**:
- 将多选下拉框字段从String改为List<String>或List<Long>
- 涉及字段:民族、政治面貌、户口类型、文化程度、婚姻状况、录取类别、学历层次、培养方式、学籍状态、是否住校、各种特殊身份标识、血型、残疾类型
2. **修改查询条件构建方法**:
- 将多选字段的查询方式从eq改为in
- 使用`bo.getField() != null && !bo.getField().isEmpty()`进行空值检查
- 支持多选值的OR查询逻辑
#### 技术改进
- 支持前端多选下拉框的数据传递
- 优化了多选字段的查询性能
- 确保查询条件的准确性和完整性
@@ -1728,6 +1728,7 @@ data() {
---
<<<<<<< HEAD
## 🔧 审批操作优化和意见填写强化 (2025-01-27 17:30:00)
### 问题描述
@@ -1958,10 +1959,220 @@ loadAvailableApprovers(applicationId) {
}).catch(error => {
console.error('获取审批人列表失败:', error);
this.approverList = [];
=======
本次修改遵循了项目的前端开发规范,使用了Element Plus的ElMessage组件实现loading效果,保持了代码风格的一致性。
# 学生花名册页面左侧树加载API实现 - 变更记录
**变更时间**: 2025-01-27 15:30:00
**变更人员**: Cursor AI
**变更类型**: 功能实现
## 📋 变更概述
为学生花名册页面(`web/src/views/xg/xsjcxx/stuRoster/index.vue`)的左侧部门树实现API加载功能,与学生信息管理页面的getDeptTree API保持一致。
## 🔧 具体变更内容
### 1. 创建API文件
- **新增文件**: `web/src/api/xg/xsjcxx/stuRoster/index.ts`
- 实现 `XgDeptTreeSelect` 方法,调用 `/xgStudentManagerSource/studentManagerSource/currentUserAuthTree` 接口
- 实现 `deptTreeSelect` 方法,调用 `/system/dept/treeselect` 接口作为备选
- 实现 `listStudentRoster` 方法,调用 `/studentMsg/studentMsg/list` 接口
- 实现 `exportStudentRoster` 方法,调用 `/studentMsg/studentMsg/export` 接口
### 2. 创建类型定义文件
- **新增文件**: `web/src/api/xg/xsjcxx/stuRoster/types.ts`
- 定义 `XgDeptTreeVO` 部门树节点类型
- 定义 `R` 通用响应类型
- 定义 `TableDataInfo` 分页数据信息类型
- 定义 `StudentRosterQuery` 查询参数类型
- 定义 `StudentRosterVO` 学生花名册数据类型
### 3. 修改学生花名册页面
- **文件**: `web/src/views/xg/xsjcxx/stuRoster/index.vue`
- 添加API导入: `import api from '@/api/xg/xsjcxx/stuRoster/index'`
- 实现 `getDeptTree` 方法,与学生信息管理页面保持一致
- 修改 `getList` 方法,使用真实API替代模拟数据
- 修改 `handleExport` 方法,使用真实导出API
- 删除静态部门树数据,改为动态加载
- 删除模拟数据生成代码
- 在 `onMounted` 中先调用 `getDeptTree()` 再调用 `getList()`
## 🔄 API实现细节
### getDeptTree方法实现
```typescript
const getDeptTree = async () => {
try {
const res = await api.XgDeptTreeSelect(101);
if (res.data && Array.isArray(res.data)) {
deptOptions.value = res.data;
} else {
console.warn('部门树数据格式不符合预期');
const backup = await api.deptTreeSelect();
deptOptions.value = backup.data.data;
}
} catch (error) {
console.error('获取部门树失败:', error);
try {
const backup = await api.deptTreeSelect();
deptOptions.value = backup.data.data;
} catch (backupError) {
console.error('备选API也失败:', backupError);
ElMessage.error('获取部门树失败,请重试');
}
}
};
```
### API接口映射
- **主要接口**: `/xgStudentManagerSource/studentManagerSource/currentUserAuthTree`
- **备选接口**: `/system/dept/treeselect`
- **学生列表接口**: `/studentMsg/studentMsg/list`
- **导出接口**: `/studentMsg/studentMsg/export`
## ✅ 变更验证
### 功能验证
- [x] 部门树能够从API动态加载
- [x] 学生列表能够从API获取数据
- [x] 导出功能能够正常工作
- [x] 错误处理机制完善
### 代码质量
- [x] API接口与学生信息管理页面保持一致
- [x] 类型定义完整
- [x] 错误处理完善
- [x] 代码结构清晰
## 📝 注意事项
1. **API一致性**: 学生花名册页面的getDeptTree方法与学生信息管理页面完全一致
2. **错误处理**: 实现了主API失败时的备选API机制
3. **数据格式**: 支持两种不同的API返回格式
4. **性能优化**: 删除了大量静态数据,改为动态加载
## 🔗 相关文件
- `web/src/views/xg/xsjcxx/stuRoster/index.vue` - 学生花名册页面
- `web/src/api/xg/xsjcxx/stuRoster/index.ts` - API接口文件
- `web/src/api/xg/xsjcxx/stuRoster/types.ts` - 类型定义文件
- `web/src/views/xg/xsjcxx/studentMsg/studentMsg/index.vue` - 参考的学生信息管理页面
---
**变更完成时间**: 2025-01-27 15:30:00
**状态**: ✅ 已完成
# 代码调整记录 - 2025-01-27
## 修改概述
在高级检索界面中为籍贯和生源地区字段添加API数据获取功能,替换原有的字典数据源。
## 修改文件
- `web/src/views/xg/xsjcxx/stuRoster/components/AdvancedSearchDialog.vue`
## 具体修改内容
### 1. 导入依赖
- 添加 `onMounted` 生命周期钩子导入
- 添加 `ElMessage` 消息提示组件导入
- 添加 `request` HTTP请求工具导入
### 2. 响应式数据
- 新增 `regionOptions` 响应式数组,用于存储行政区划数据
- 移除 `xg_source_region` 字典数据依赖
### 3. 模板修改
- 将籍贯字段的数据源从 `xg_native_place` 字典改为 `regionOptions` API数据
- 将生源地区字段的数据源从 `xg_source_region` 字典改为 `regionOptions` API数据
- 修改选项的key、label、value属性映射:
- `key`: `item.id`
- `label`: `item.name`
- `value`: `item.id`
### 4. 方法实现
- 新增 `getRegionTree` 异步方法:
- 调用 `/system/SysRegion/tree` API接口
- 获取行政区划树形数据
- 错误处理和用户提示
### 5. 生命周期
- 在 `onMounted` 钩子中调用 `getRegionTree` 方法
- 确保组件挂载时自动获取行政区划数据
## 技术实现要点
### API接口
- **接口地址**: `/system/SysRegion/tree`
- **请求方式**: GET
- **返回格式**: 树形结构的行政区划数据
### 数据结构
```typescript
interface RegionItem {
id: string | number; // 区域ID
name: string; // 区域名称
// 其他树形结构字段...
}
```
### 错误处理
- 使用 try-catch 捕获API请求异常
- 控制台输出详细错误信息
- 用户友好的错误提示消息
## 影响范围
- 高级检索对话框中的籍贯选择功能
- 高级检索对话框中的生源地区选择功能
- 用户可以选择真实的行政区划数据而不是字典数据
- 提升了数据的准确性和实用性
## 测试建议
1. 打开高级检索对话框
2. 验证籍贯下拉选项是否正确加载
3. 测试多选功能是否正常
4. 检查网络异常时的错误提示
## 相关文档
- 参考实现:`web/src/views/xg/xsjcxx/stuPersonalMsg/index.vue` 中的 `getRegionTree` 方法
- API文档:行政区划管理相关接口
---
**修改时间**: 2025-01-27
**修改人**: Cursor AI
**状态**: 已完成
# 代码调整记录 - 2025-01-27
## 修改概述
将学生花名册API从基础的list接口改为使用高级搜索接口,以支持更丰富的查询功能。
## 修改详情
### 1. 前端API接口修改
**文件**: `web/src/api/xg/xsjcxx/stuRoster/index.ts`
**修改内容**:
- 将 `listStudentRoster` 方法的请求方式从 `GET` 改为 `POST`
- 将请求URL从 `/studentMsg/studentMsg/list` 改为 `/studentMsg/studentMsg/advancedSearch`
- 将请求参数从 `params` 改为 `data`(POST请求体)
- 添加了新的类型导入 `XgStudentMsgAdvancedSearchQuery`
**修改前**:
```typescript
export function listStudentRoster(query?: any): AxiosPromise<R<TableDataInfo<any[]>>> {
return request({
url: '/studentMsg/studentMsg/list',
method: 'get',
params: query
>>>>>>> 2b5db607f6268a7631c2075e81359ee1ca827e07
});
}
```
<<<<<<< HEAD
#### 模板绑定
```html
<!-- 下一审批人选择框 -->
@@ -2575,4 +2786,82 @@ API提交到后端 (包含所有分年度预算字段)
---
// ... existing code ...
// ... existing code ...
=======
**修改后**:
```typescript
export function listStudentRoster(query?: XgStudentMsgAdvancedSearchQuery): AxiosPromise<R<TableDataInfo<any[]>>> {
return request({
url: '/studentMsg/studentMsg/advancedSearch',
method: 'post',
data: query
});
}
```
### 2. 类型定义扩展
**文件**: `web/src/api/xg/xsjcxx/stuRoster/types.ts`
**新增内容**:
- 添加了 `XgStudentMsgAdvancedSearchQuery` 接口类型定义
- 包含了完整的 advancedSearch 接口支持的所有查询参数
- 涵盖了基础信息、学籍信息、学院专业班级、家庭信息、特殊身份、身体状况、住宿信息等多个维度的检索条件
**新增类型结构**:
```typescript
export interface XgStudentMsgAdvancedSearchQuery {
// 基础信息检索
studentNo?: string;
name?: string;
idCard?: string;
phone?: string;
email?: string;
gender?: string[];
// ... 更多字段
// 分页参数
pageNum?: number;
pageSize?: number;
}
```
## 技术要点
### 1. 接口兼容性
- 保持了原有的函数签名,只是改变了内部实现
- 新的查询参数类型是扩展的,向后兼容原有的简单查询参数
### 2. 请求方式变更
- 从 GET 请求改为 POST 请求,支持更复杂的查询条件
- 参数传递方式从 URL 参数改为请求体,避免 URL 过长的问题
### 3. 类型安全
- 添加了完整的 TypeScript 类型定义
- 提供了更好的开发体验和代码提示
## 影响范围
### 1. 前端调用方
- 使用 `listStudentRoster` 方法的组件需要确保传递的参数符合新的类型定义
- 原有的简单查询参数仍然可以正常工作
### 2. 后端接口
- 后端 `XgStudentMsgController.advancedSearch` 方法已经实现
- 支持完整的查询条件处理
## 验证建议
1. **功能测试**: 验证学生花名册列表页面是否正常加载
2. **查询测试**: 测试各种查询条件组合是否正常工作
3. **类型检查**: 确保 TypeScript 编译无错误
4. **向后兼容**: 验证原有的简单查询是否仍然有效
## 相关文档
- 后端接口文档: `docs/开发文档/API/学生信息管理API.md`
- 高级搜索功能说明: 参考 `XgStudentMsgAdvancedSearchBo` 类的完整字段说明
---
**修改时间**: 2025-01-27 14:30:00
**修改人**: Cursor AI
**修改原因**: 提升学生信息查询功能,支持更丰富的高级搜索条件
>>>>>>> 2b5db607f6268a7631c2075e81359ee1ca827e07
@@ -0,0 +1,184 @@
# 学生详情弹窗集成修改记录
**修改时间**: 2025-01-27 16:00:00
**修改人员**: AI Assistant
**修改类型**: 功能增强
## 修改概述
将学生详情弹窗(`StudentDetailDialog.vue`)从独立页面改造为可复用的弹窗组件,支持从学生花名册页面的列表视图和卡片视图中传递学生ID并显示详细信息。
## 修改内容
### 1. 学生详情弹窗组件改造
**文件**: `web/src/views/xg/xsjcxx/stuRoster/components/StudentDetailDialog.vue`
#### 1.1 模板结构修改
- 将原有的独立页面结构包装在`el-dialog`组件中
- 添加弹窗标题、宽度、关闭行为等配置
- 保持原有的Tab页结构和表单布局
#### 1.2 Props和Emits定义
```typescript
// 定义props
interface Props {
visible: boolean;
studentId?: number | null;
}
const props = withDefaults(defineProps<Props>(), {
visible: false,
studentId: null
});
// 定义emits
const emit = defineEmits<{
'update:visible': [value: boolean];
}>();
// 计算属性:控制弹窗显示
const dialogVisible = computed({
get: () => props.visible,
set: (value) => emit('update:visible', value)
});
```
#### 1.3 API调用修改
- 修改`getPersonalInfoData`方法,支持传入学生ID参数
- 使用正确的API路径:`/studentMsg/studentMsg/personalInfo`
- 通过查询参数`userId`传递学生ID
```typescript
/** 获取学生个人信息 */
const getPersonalInfoData = async (studentId?: number) => {
if (!studentId) {
ElMessage.warning('学生ID不能为空');
return;
}
try {
loading.value = true;
const response = await request({
url: '/studentMsg/studentMsg/personalInfo',
method: 'get',
params: { userId: studentId }
});
// ... 处理响应数据
} catch (error) {
console.error('获取个人信息失败:', error);
ElMessage.error('获取个人信息失败,请稍后重试');
} finally {
loading.value = false;
}
};
```
#### 1.4 数据监听机制
- 添加`watch`监听器,监听`props.visible`和`props.studentId`的变化
- 当弹窗打开且有学生ID时,自动获取学生信息
- 在获取新数据前重置表单数据
```typescript
// 监听学生ID变化,当弹窗打开且有学生ID时获取数据
watch(
() => [props.visible, props.studentId],
async ([visible, studentId]) => {
if (visible && studentId && typeof studentId === 'number') {
// 重置表单数据
Object.keys(form).forEach((key) => {
const value = form[key];
if (typeof value === 'string') {
(form as any)[key] = '';
} else if (typeof value === 'number') {
(form as any)[key] = undefined;
} else if (typeof value === 'boolean') {
(form as any)[key] = undefined;
}
});
// 获取学生个人信息
await getPersonalInfoData(studentId);
}
},
{ immediate: true }
);
```
### 2. 主页面集成
**文件**: `web/src/views/xg/xsjcxx/stuRoster/index.vue`
#### 2.1 详情弹窗状态管理
```typescript
// 详情弹窗
const detailDialog = reactive({
visible: false,
studentId: null as number | null
});
```
#### 2.2 查看详情方法
```typescript
const handleDetail = (row: any) => {
detailDialog.studentId = row.id;
detailDialog.visible = true;
};
```
#### 2.3 组件使用
```vue
<!-- 学生详情弹窗 -->
<student-detail-dialog
:visible="detailDialog.visible"
:student-id="detailDialog.studentId"
@update:visible="(val) => (detailDialog.visible = val)"
/>
```
### 3. 触发方式
#### 3.1 列表视图
- 在操作列中添加"查看详情"按钮
- 点击按钮调用`handleDetail(row)`方法
#### 3.2 卡片视图
- 卡片支持双击事件
- 双击卡片调用`handleDetail(item)`方法
- 添加视觉提示:"双击查看详情"
## 功能特点
1. **响应式设计**: 弹窗支持不同屏幕尺寸的响应式布局
2. **数据隔离**: 每次打开弹窗都会重置表单数据,避免数据混淆
3. **错误处理**: 完善的错误提示和异常处理机制
4. **用户体验**: 加载状态提示,操作反馈及时
5. **代码复用**: 组件化设计,可在其他页面复用
## 技术要点
1. **Vue 3 Composition API**: 使用`defineProps`、`defineEmits`、`computed`等现代Vue 3特性
2. **TypeScript支持**: 完整的类型定义和类型安全
3. **响应式数据**: 使用`reactive`和`ref`管理组件状态
4. **生命周期管理**: 合理使用`onMounted`和`watch`监听器
5. **API集成**: 正确调用后端API并处理响应数据
## 测试验证
1. **列表视图测试**: 点击"查看详情"按钮,弹窗正常打开并显示学生信息
2. **卡片视图测试**: 双击学生卡片,弹窗正常打开并显示学生信息
3. **数据验证**: 确认显示的学生信息与列表/卡片中的基本信息一致
4. **错误处理**: 测试无效学生ID的情况,确认错误提示正常
5. **响应式测试**: 在不同屏幕尺寸下测试弹窗显示效果
## 注意事项
1. **API路径**: 确保后端API路径`/studentMsg/studentMsg/personalInfo`正确配置
2. **权限控制**: 确认用户有访问学生个人信息的权限
3. **数据格式**: 注意API返回的数据格式与表单字段的映射关系
4. **性能优化**: 避免频繁的API调用,合理使用缓存机制
5. **用户体验**: 提供清晰的加载状态和错误提示
---
**修改完成**: 学生详情弹窗已成功集成到学生花名册页面,支持从列表视图和卡片视图查看学生详细信息。
@@ -1,74 +1,172 @@
# 代码调整记录 - 2025-01-27
## 修改时间
2025-01-27 14:30:00
## 修复内容
## 修改人员
wuzilong
## 修改内容
### 高级检索弹出框学籍信息优化
### 高级检索界面多选下拉框参数传递格式修复
**文件**: `web/src/views/xg/xsjcxx/stuRoster/components/AdvancedSearchDialog.vue`
**问题描述**:
- 高级检索界面中,检索按钮调用后台API时,对于下拉框的多选传的参数是 `dormitoryBuilding[0]`、`dormitoryBuilding[1]` 等索引格式
- 需要调整为传递 `dormitoryBuilding: list<String>` 的数组格式
- 其他所有下拉框都需要做同样的调整
**修复方案**:
1. 在 `handleSearch` 方法中新增多选字段处理逻辑
2. 定义所有多选下拉框字段列表:
```javascript
const multiSelectFields = [
'gender', 'nationality', 'nativePlace', 'politicalStatus', 'educationLevel',
'admissionCategory', 'academicLevel', 'trainingType', 'householdRegisterType',
'studentStatus', 'isBoarder', 'dormitoryBuilding', 'familyEconomicStatus',
'isPoorStudent', 'isMinority', 'isDisabled', 'isOrphan', 'isLeftBehindChild',
'isSingleChild', 'isLowIncome', 'hasLoan', 'sourceRegion', 'bloodType',
'disabilityType'
];
```
1. **学制字段改为输入框**
- 将学制从下拉选择框改为文本输入框
- 移除 `school_system` 字典数据依赖
- 用户可以直接输入学制信息
3. 统一处理多选字段:
- 过滤掉空值,确保只传递有效的数组
- 如果数组为空则删除该字段
- 保持数组格式传递给后端
2. **新增学籍状态检索条件**
- 在学籍信息部分新增"学籍状态"字段
- 添加 `student_status` 字典数据,包含:在读、休学、退学、毕业、结业、肄业
- 位置:学籍信息部分的最后一行
**修复效果**:
- ✅ 所有多选下拉框现在都传递正确的数组格式
- ✅ 避免了 `dormitoryBuilding[0]`、`dormitoryBuilding[1]` 等索引格式问题
- ✅ 后端可以正确接收 `dormitoryBuilding: list<String>` 格式的参数
- ✅ 提高了代码的可维护性和一致性
3. **新增是否住校检索条件**
- 在学籍信息部分新增"是否住校"字段
- 使用现有的 `yes_no` 字典数据
- 位置:学籍信息部分的最后一行
**涉及的多选字段**:
- 基础信息:性别、民族、籍贯、政治面貌、文化程度
- 学籍信息:录取类别、学历层次、培养方式、户口类型、学籍状态、是否住校、宿舍楼栋
- 家庭信息:家庭经济状况
- 扩展信息:是否贫困生、是否少数民族、是否残疾、是否孤儿、是否留守儿童、是否独生子女、是否低保、是否有助学贷款、生源地区
- 健康信息:血型、残疾类型
4. **新增宿舍楼栋检索条件**
- 在学籍信息部分新增"宿舍楼栋"字段
- 使用文本输入框,支持模糊检索
- 位置:学籍信息部分的最后一行
**测试建议**:
1. 测试各个多选下拉框的选择功能
2. 验证传递给后端的参数格式是否正确
3. 确认后端能正确接收和处理数组格式的参数
5. **户口类型字段位置调整**
- 将户口类型从学籍信息部分移动到基础信息部分
- 位置:基础信息中户籍地址的前一行
- 保持字段功能和字典数据不变
---
**记录时间**: 2025-01-27 14:30:00
**记录人**: wuzilong
**变更类型**: 功能修复
6. **修复重复字段问题**
- 移除特殊标识部分重复的"是否住校"字段
- 修复 `familyMembers` 字段类型错误(从string改为number)
# 角色管理页面新增按钮isSystemRole问题修复
7. **家庭收入字段优化**
- 家庭年收入标签添加单位"(元)"
- 人均收入标签添加单位"(元)"
- 将两个字段的输入框从数字输入框改为普通文本输入框
- 保持范围查询功能(最小值-最大值)
- 数据类型从number数组改为string数组
- 输入框宽度优化:从45%调整为40%,分隔符间距从5px调整为3px
**日期**: 2025-01-27 15:30:00
**修改人**: wuzilong
**文件**: `web/src/views/system/role/index.vue`
#### 技术细节
## 问题描述
- 新增字段在 `formData` 中正确定义
- 字典数据 `student_status` 已添加到组件中
- 表单布局保持一致性,每行4个字段
- 所有新增字段都支持清空操作
点击角色管理页面的新增按钮时,`isSystemRole` 计算属性应该为 `false`,允许用户填写表单。但实际显示为 `true`,导致表单字段被禁用。
#### 影响范围
## 问题分析
- 学生花名册高级检索功能
- 检索条件更加完善,支持按学籍状态、住宿情况进行筛选
- 用户体验提升,检索条件更加直观
1. **根本原因**: `isSystemRole` 计算属性检查 `form.value.roleKey` 是否以 `'XT-BJZW-'` 开头
2. **初始化问题**: 虽然 `initForm` 中 `roleKey` 初始化为空字符串,但可能存在重置不完整的情况
3. **逻辑问题**: 原计算属性没有正确处理空字符串的情况
## 验证状态
- [x] 代码语法检查通过
- [x] 字段类型正确
- [x] 字典数据完整
- [x] 布局合理
## 修复方案
### 1. 优化 `isSystemRole` 计算属性
```typescript
// 修复前
const isSystemRole = computed(() => {
return form.value.roleKey && form.value.roleKey.startsWith('XT-BJZW-');
});
// 修复后
const isSystemRole = computed(() => {
return form.value.roleKey && form.value.roleKey.trim() !== '' && form.value.roleKey.startsWith('XT-BJZW-');
});
```
### 2. 优化 `isSystemRoleInTable` 函数
```typescript
// 修复前
const isSystemRoleInTable = (row: RoleVO) => {
return row.roleKey && row.roleKey.startsWith('XT-BJZW-');
};
// 修复后
const isSystemRoleInTable = (row: RoleVO) => {
return row.roleKey && row.roleKey.trim() !== '' && row.roleKey.startsWith('XT-BJZW-');
};
```
### 3. 增强 `reset` 函数
```typescript
const reset = () => {
menuRef.value?.setCheckedKeys([]);
menuExpand.value = false;
menuNodeAll.value = false;
deptExpand.value = true;
deptNodeAll.value = false;
form.value = { ...initForm };
// 确保roleKey被明确重置为空字符串
form.value.roleKey = '';
roleFormRef.value?.resetFields();
};
```
### 4. 增强 `handleAdd` 函数
```typescript
const handleAdd = () => {
reset();
// 确保roleKey被正确重置为空字符串
form.value.roleKey = '';
console.log('新增角色 - roleKey:', `"${form.value.roleKey}"`, 'isSystemRole:', isSystemRole.value);
getMenuTreeselect();
dialog.visible = true;
dialog.title = '添加角色';
};
```
## 修复效果
1. ✅ **新增角色**: 点击新增按钮时,`isSystemRole` 正确显示为 `false`
2. ✅ **表单可用**: 角色名称、权限字符等字段可以正常填写
3. ✅ **系统角色保护**: 对于真正的系统角色(`XT-BJZW-` 开头),仍然正确识别并禁用编辑
4. ✅ **调试信息**: 添加了控制台日志,便于调试和验证
## 验证结果
### ✅ 修复成功验证
**测试日志输出**:
```
新增角色 - roleKey: "" isSystemRole: false
```
**验证结果**:
- ✅ `roleKey` 正确显示为空字符串 `""`
- ✅ `isSystemRole` 正确显示为 `false`
- ✅ 表单字段未被禁用,可以正常填写
- ✅ 系统角色保护机制仍然正常工作
## 测试验证
1. **新增角色测试**:
- 点击"新增"按钮
- 验证表单字段未被禁用
- 验证控制台显示 `roleKey: ""` 和 `isSystemRole: false`
2. **系统角色测试**:
- 编辑系统内置角色(`XT-BJZW-` 开头)
- 验证表单字段被正确禁用
- 验证系统角色提示信息显示
## 相关文件
- `web/src/views/system/role/index.vue` - 角色管理页面
- `docs/变更管理/code-adjust-record-2025-01-27-wuzilong.md` - 本变更记录
## 备注
本次修改提升了学生信息检索的灵活性和准确性,特别是针对学籍状态和住宿情况的检索需求。
此修复确保了角色管理页面的新增功能正常工作,同时保持了对系统内置角色的保护机制。修复后的代码更加健壮,能够正确处理各种边界情况。
**修复状态**: ✅ 完全成功
**验证时间**: 2025-01-27 15:35:00
@@ -0,0 +1,165 @@
# 学生花名册详情弹窗API调用修复记录
**修改时间**: 2025-07-17 16:30:00
**修改人员**: AI Assistant
**修改类型**: 功能修复
## 问题描述
学生花名册主界面(`web/src/views/xg/xsjcxx/stuRoster/index.vue`)中的学生详情弹窗(`StudentDetailDialog.vue`)没有调用API获取学生数据,导致详情弹窗显示空白。
## 问题分析
1. **组件导入错误**: StudentDetailDialog和AdvancedSearchDialog组件使用`<script setup>`语法,但没有默认导出,导致主页面导入失败
2. **API缺失**: stuRoster的API文件中缺少获取学生个人信息的API方法
3. **API调用错误**: StudentDetailDialog组件中直接使用request调用API,而不是使用统一的API模块
## 修复内容
### 1. 修复组件导出问题
**文件**: `web/src/views/xg/xsjcxx/stuRoster/components/AdvancedSearchDialog.vue`
```typescript
// 添加默认导出
defineOptions({
name: 'AdvancedSearchDialog'
});
```
**文件**: `web/src/views/xg/xsjcxx/stuRoster/components/StudentDetailDialog.vue`
```typescript
// 添加默认导出
defineOptions({
name: 'StudentDetailDialog'
});
```
### 2. 添加学生个人信息API
**文件**: `web/src/api/xg/xsjcxx/stuRoster/index.ts`
```typescript
/**
* 获取学生个人信息
* @param userId 用户ID,可为空,为空时使用当前登录用户ID
*/
export function getStudentPersonalInfo(userId?: number): AxiosPromise<any> {
return request({
url: '/studentMsg/studentMsg/personalInfo',
method: 'get',
params: { userId }
});
}
export default {
XgDeptTreeSelect,
deptTreeSelect,
listStudentRoster,
exportStudentRoster,
getStudentPersonalInfo // 新增
};
```
### 3. 修复StudentDetailDialog组件API调用
**文件**: `web/src/views/xg/xsjcxx/stuRoster/components/StudentDetailDialog.vue`
#### 3.1 导入API模块
```typescript
import api from '@/api/xg/xsjcxx/stuRoster/index';
```
#### 3.2 修改API调用方法
```typescript
/** 获取学生个人信息 */
const getPersonalInfoData = async (studentId?: number) => {
if (!studentId) {
ElMessage.warning('学生ID不能为空');
return;
}
try {
loading.value = true;
// 使用统一的API模块
const response = await api.getStudentPersonalInfo(studentId);
if (response.code === 200) {
const data = response.data;
// 将是否字段的数值转换为字符串
const booleanFields = [
'isPoorStudent',
'isMinority',
'isOrphan',
'isLeftBehindChild',
'isSingleChild',
'isLowIncome',
'hasLoan',
'isDisabled',
'isBoarder'
];
booleanFields.forEach((field) => {
if (data[field] !== undefined && data[field] !== null) {
data[field] = String(data[field]);
}
});
// 将API返回的数据映射到表单
Object.assign(form, data);
} else {
ElMessage.error(response.msg || '获取个人信息失败');
}
} catch (error) {
console.error('获取个人信息失败:', error);
ElMessage.error('获取个人信息失败,请稍后重试');
} finally {
loading.value = false;
}
};
```
### 4. 修复主页面API响应处理
**文件**: `web/src/views/xg/xsjcxx/stuRoster/index.vue`
```typescript
// 修复API响应数据结构处理
api
.listStudentRoster(queryParams)
.then((response: any) => {
if (response.code === 200) {
studentList.value = response.data.rows || [];
total.value = response.data.total || 0;
} else {
ElMessage.error(response.msg || '查询失败');
studentList.value = [];
total.value = 0;
}
})
```
## 修复效果
1. **组件导入正常**: StudentDetailDialog和AdvancedSearchDialog组件可以正常导入和使用
2. **API调用成功**: 学生详情弹窗可以正确调用后端API获取学生个人信息
3. **数据展示完整**: 详情弹窗可以显示学生的完整个人信息,包括基础信息、扩展信息、家庭信息、健康信息、学籍信息、银行卡信息等
4. **错误处理完善**: 添加了完善的错误处理和用户提示
## 技术要点
1. **Vue 3 Composition API**: 使用`<script setup>`语法和`defineOptions`进行组件定义
2. **API模块化**: 统一使用API模块进行接口调用,便于维护和管理
3. **类型安全**: 使用TypeScript进行类型定义,提高代码质量
4. **错误处理**: 完善的错误处理和用户友好的提示信息
## 测试验证
1. 打开学生花名册页面
2. 点击学生列表中的"查看详情"按钮
3. 验证详情弹窗是否正确显示学生个人信息
4. 验证各个Tab页(基础信息、扩展信息、家庭信息、健康信息、学籍信息、银行卡信息)是否正常显示
5. 验证API调用是否成功,网络请求是否正常
## 相关文件
- `web/src/views/xg/xsjcxx/stuRoster/index.vue` - 主页面
- `web/src/views/xg/xsjcxx/stuRoster/components/StudentDetailDialog.vue` - 学生详情弹窗
- `web/src/views/xg/xsjcxx/stuRoster/components/AdvancedSearchDialog.vue` - 高级搜索弹窗
- `web/src/api/xg/xsjcxx/stuRoster/index.ts` - API接口定义
- `web/src/api/xg/xsjcxx/stuRoster/types.ts` - 类型定义
+82 -3
View File
@@ -296,6 +296,82 @@
#### 响应数据
返回Excel文件流
### 2.8.1 高级检索导出学生信息
- **接口路径**: POST `/studentMsg/studentMsg/advancedSearchExport`
- **权限标识**: `studentMsg:studentMsg:export`
- **功能描述**: 根据高级检索条件导出学生信息列表,支持多维度复合查询条件
- **版本**: 1.2.1 (2025-01-07修复MapStruct转换错误)
- **修复**: 解决 `XgStudentMsgVo` 到 `XgStudentMsgExportVo` 转换器缺失问题
#### 请求参数
支持以下高级检索条件:
**基础信息检索**
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| studentNo | string | 否 | 学号(模糊查询) |
| name | string | 否 | 姓名(模糊查询) |
| idCard | string | 否 | 身份证号(模糊查询) |
| phone | string | 否 | 手机号码(模糊查询) |
| email | string | 否 | 邮箱(模糊查询) |
| gender | array | 否 | 性别数组:0-男,1-女 |
| nationality | array | 否 | 民族数组 |
| politicalStatus | array | 否 | 政治面貌数组 |
| birthDateStart | string | 否 | 出生日期开始:yyyy-MM-dd |
| birthDateEnd | string | 否 | 出生日期结束:yyyy-MM-dd |
**学籍信息检索**
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| studentStatus | array | 否 | 学籍状态数组:1-在籍,2-休学,3-复学,4-退学,5-毕业 |
| enrollmentYearStart | integer | 否 | 入学年份开始 |
| enrollmentYearEnd | integer | 否 | 入学年份结束 |
| admissionCategory | array | 否 | 录取类别数组 |
**学院专业班级检索**
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| collegeName | string | 否 | 学院名称(模糊查询) |
| majorName | string | 否 | 专业名称(模糊查询) |
| className | string | 否 | 班级名称(模糊查询) |
| grade | array | 否 | 年级数组 |
| counselorName | string | 否 | 辅导员姓名(模糊查询) |
| classTeacherName | string | 否 | 班主任姓名(模糊查询) |
**家庭信息检索**
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| fatherName | string | 否 | 父亲姓名(模糊查询) |
| motherName | string | 否 | 母亲姓名(模糊查询) |
| familyEconomicStatus | array | 否 | 家庭经济状况数组 |
| familyIncomeMin | number | 否 | 家庭收入最小值 |
| familyIncomeMax | number | 否 | 家庭收入最大值 |
**特殊身份检索**
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| isPoorStudent | array | 否 | 是否贫困生:0-否,1-是 |
| isMinority | array | 否 | 是否少数民族:0-否,1-是 |
| isDisabled | array | 否 | 是否残疾:0-否,1-是 |
| isOrphan | array | 否 | 是否孤儿:0-否,1-是 |
**权限范围**
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| deptIds | array | 否 | 部门ID数组,用于权限控制 |
#### 响应数据
返回Excel文件流,包含完整的学生信息数据
#### 修复记录
- **2025-01-07 v1.2.1**: 修复MapStruct转换错误,解决 `XgStudentMsgVo` 到 `XgStudentMsgExportVo` 转换器缺失问题
- **问题**: `MapstructUtils.convert()` 无法找到两个VO类之间的转换器,导致导出功能报错
- **解决**: 在Controller中添加手动转换方法 `convertToExportVo()` 和 `convertToExportVoList()`
- **影响**: 导出功能正常工作,所有字段完整映射
- **2025-01-07 v1.2.0**: 修复分页限制问题,导出时pageSize设置为50000,确保能导出所有符合条件的记录
- **问题**: 之前由于BO类默认pageSize=10,导致只能导出10条记录
- **解决**: 取消注释Controller中的分页参数设置代码
### 2.9 查询学生个人信息
- **接口路径**: GET `/studentMsg/studentMsg/personalInfo`
- **权限标识**: `studentMsg:studentMsg:query`
@@ -540,7 +616,10 @@ updateSupport: true
---
**文档更新时间**: 2025-01-27
**文档版本**: 1.1.0
**文档更新时间**: 2025-01-07
**文档版本**: 1.2.0
**维护人员**: Cursor AI
**更新说明**: 新增学生信息导入功能,完善学生信息管理API文档
**更新说明**:
- 新增高级检索导出接口文档
- 修复高级检索导出分页限制问题
- 之前版本:新增学生信息导入功能,完善学生信息管理API文档