提交
This commit is contained in:
1 parent
e5113f1bec
commit
50ecada4d4
31 files changed
+9822
-35
No files matched your search
+161
@@ -29,6 +29,9 @@ import org.dromara.xg.mapper.XgStuColBatchClassMapper;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.core.utils.MapstructUtils;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.xg.service.impl.XgStudentManagerSourceServiceImpl;
|
||||
import org.dromara.xg.domain.vo.XgStudentManagerDeptTreeVo;
|
||||
import org.dromara.xg.domain.vo.XgStuColBatchVo;
|
||||
|
||||
/**
|
||||
* 学生信息收集批次班级权限
|
||||
@@ -44,6 +47,7 @@ public class XgStuColBatchClassController extends BaseController {
|
||||
|
||||
private final IXgStuColBatchClassService xgStuColBatchClassService;
|
||||
private final XgStuColBatchClassMapper xgStuColBatchClassMapper;
|
||||
private final XgStudentManagerSourceServiceImpl xgStudentManagerSourceService;
|
||||
|
||||
/**
|
||||
* 查询学生信息收集批次班级权限列表
|
||||
@@ -224,4 +228,161 @@ public class XgStuColBatchClassController extends BaseController {
|
||||
return R.fail("查询学生可参与批次信息失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前登录老师关联的批次信息
|
||||
*
|
||||
* @param batchName 批次名称(非必要参数)
|
||||
*/
|
||||
@SaCheckPermission("stuColBatchClass:stuColBatchClass:query")
|
||||
@GetMapping("/getTeacherRelatedBatches")
|
||||
public R<List<XgStuColBatchVo>> getTeacherRelatedBatches(
|
||||
@RequestParam(value = "batchName", required = false) String batchName) {
|
||||
try {
|
||||
// 第一步:获取当前用户所有有权限的部门信息
|
||||
List<XgStudentManagerDeptTreeVo> deptTree = xgStudentManagerSourceService.getCurrentUserAuthDeptTree();
|
||||
|
||||
// 第二步:递归提取所有部门ID
|
||||
List<Long> allDeptIds = new ArrayList<>();
|
||||
extractAllDeptIds(deptTree, allDeptIds);
|
||||
|
||||
if (allDeptIds.isEmpty()) {
|
||||
return R.ok(new ArrayList<>());
|
||||
}
|
||||
|
||||
// 第三步:根据部门ID查询关联的批次信息
|
||||
List<XgStuColBatchVo> batchList = xgStuColBatchClassMapper.selectTeacherRelatedBatches(allDeptIds,
|
||||
batchName);
|
||||
|
||||
return R.ok(batchList);
|
||||
} catch (Exception e) {
|
||||
return R.fail("查询老师关联批次信息失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户对应该批次的组织权限范围
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
*/
|
||||
@SaCheckPermission("stuColBatchClass:stuColBatchClass:query")
|
||||
@GetMapping("/getBatchAuthScope/{batchId}")
|
||||
public R<List<XgStudentManagerDeptTreeVo>> getBatchAuthScope(
|
||||
@NotNull(message = "批次ID不能为空") @PathVariable Long batchId) {
|
||||
try {
|
||||
// 第一步:获取当前用户所有的组织权限信息
|
||||
List<XgStudentManagerDeptTreeVo> userDeptTree = xgStudentManagerSourceService.getCurrentUserAuthDeptTree();
|
||||
if (userDeptTree == null || userDeptTree.isEmpty()) {
|
||||
return R.ok(new ArrayList<>());
|
||||
}
|
||||
|
||||
// 第二步:通过batchId在xg_stu_col_batch_class中获取class_id数据,过滤条件deleted=0
|
||||
List<Long> batchClassIds = xgStuColBatchClassMapper.selectClassIdsByBatchId(batchId);
|
||||
if (batchClassIds == null || batchClassIds.isEmpty()) {
|
||||
return R.ok(new ArrayList<>());
|
||||
}
|
||||
|
||||
// 第三步:过滤树形数据,只保留在第二步中存在的班级ID以及其上级节点
|
||||
List<XgStudentManagerDeptTreeVo> filteredTree = filterDeptTreeByClassIds(userDeptTree, batchClassIds);
|
||||
|
||||
return R.ok(filteredTree);
|
||||
} catch (Exception e) {
|
||||
return R.fail("获取批次组织权限范围失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤部门树,只保留指定班级ID及其所有上级节点
|
||||
*
|
||||
* @param deptTree 原始部门树
|
||||
* @param targetClassIds 目标班级ID列表
|
||||
* @return 过滤后的部门树
|
||||
*/
|
||||
private List<XgStudentManagerDeptTreeVo> filterDeptTreeByClassIds(List<XgStudentManagerDeptTreeVo> deptTree,
|
||||
List<Long> targetClassIds) {
|
||||
if (deptTree == null || deptTree.isEmpty() || targetClassIds == null || targetClassIds.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
List<XgStudentManagerDeptTreeVo> filteredList = new ArrayList<>();
|
||||
|
||||
for (XgStudentManagerDeptTreeVo dept : deptTree) {
|
||||
// 递归检查当前节点及其子节点是否包含目标班级ID
|
||||
XgStudentManagerDeptTreeVo filteredDept = filterSingleDeptNode(dept, targetClassIds);
|
||||
if (filteredDept != null) {
|
||||
filteredList.add(filteredDept);
|
||||
}
|
||||
}
|
||||
|
||||
return filteredList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤单个部门节点,如果该节点或其子节点包含目标班级ID,则保留该节点
|
||||
*
|
||||
* @param dept 部门节点
|
||||
* @param targetClassIds 目标班级ID列表
|
||||
* @return 过滤后的部门节点,如果不包含目标班级则返回null
|
||||
*/
|
||||
private XgStudentManagerDeptTreeVo filterSingleDeptNode(XgStudentManagerDeptTreeVo dept,
|
||||
List<Long> targetClassIds) {
|
||||
if (dept == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 创建新的部门节点副本
|
||||
XgStudentManagerDeptTreeVo filteredDept = new XgStudentManagerDeptTreeVo();
|
||||
filteredDept.setId(dept.getId());
|
||||
filteredDept.setParentId(dept.getParentId());
|
||||
filteredDept.setLabel(dept.getLabel());
|
||||
filteredDept.setWeight(dept.getWeight());
|
||||
filteredDept.setDeptCategory(dept.getDeptCategory());
|
||||
filteredDept.setDisabled(dept.getDisabled());
|
||||
|
||||
// 检查当前节点是否是目标班级
|
||||
boolean isTargetClass = targetClassIds.contains(dept.getId());
|
||||
|
||||
// 递归处理子节点
|
||||
List<XgStudentManagerDeptTreeVo> filteredChildren = new ArrayList<>();
|
||||
if (dept.getChildren() != null && !dept.getChildren().isEmpty()) {
|
||||
for (XgStudentManagerDeptTreeVo child : dept.getChildren()) {
|
||||
XgStudentManagerDeptTreeVo filteredChild = filterSingleDeptNode(child, targetClassIds);
|
||||
if (filteredChild != null) {
|
||||
filteredChildren.add(filteredChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果当前节点是目标班级,或者有过滤后的子节点,则保留该节点
|
||||
if (isTargetClass || !filteredChildren.isEmpty()) {
|
||||
filteredDept.setChildren(filteredChildren);
|
||||
return filteredDept;
|
||||
}
|
||||
|
||||
// 否则剔除该节点
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归提取部门树中的所有部门ID
|
||||
*
|
||||
* @param deptTree 部门树
|
||||
* @param deptIds 部门ID列表
|
||||
*/
|
||||
private void extractAllDeptIds(List<XgStudentManagerDeptTreeVo> deptTree, List<Long> deptIds) {
|
||||
if (deptTree == null || deptTree.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (XgStudentManagerDeptTreeVo dept : deptTree) {
|
||||
if (dept.getId() != null) {
|
||||
deptIds.add(dept.getId());
|
||||
}
|
||||
// 递归处理子部门
|
||||
if (dept.getChildren() != null && !dept.getChildren().isEmpty()) {
|
||||
extractAllDeptIds(dept.getChildren(), deptIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+99
@@ -113,6 +113,19 @@ public class XgStudentMsgBatchController extends BaseController {
|
||||
return R.ok(xgStudentMsgBatchService.getStudentMsgHistory(batchId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询学生信息完善的历史记录信息及最后一次提交的信息状态
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
@SaCheckPermission("studentMsgBatch:studentMsgBatch:query")
|
||||
@GetMapping("/history/{batchId}/{userId}")
|
||||
public R<Map<String, Object>> getStudentMsgHistory(@NotNull(message = "批次ID不能为空") @PathVariable Long batchId,
|
||||
@NotNull(message = "用户ID不能为空") @PathVariable Long userId) {
|
||||
return R.ok(xgStudentMsgBatchService.getStudentMsgHistory(batchId, userId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户该批次的历史已填写数据
|
||||
*
|
||||
@@ -124,6 +137,19 @@ public class XgStudentMsgBatchController extends BaseController {
|
||||
return R.ok(xgStudentMsgBatchService.getUserHistoryData(batchId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户该批次的历史已填写数据
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
@SaCheckPermission("studentMsgBatch:studentMsgBatch:query")
|
||||
@GetMapping("/userHistoryData/{batchId}/{userId}")
|
||||
public R<XgStudentMsgBatchVo> getUserHistoryData(@NotNull(message = "批次ID不能为空") @PathVariable Long batchId,
|
||||
@NotNull(message = "用户ID不能为空") @PathVariable Long userId) {
|
||||
return R.ok(xgStudentMsgBatchService.getUserHistoryData(batchId, userId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 学生提交信息完善数据
|
||||
*/
|
||||
@@ -135,4 +161,77 @@ public class XgStudentMsgBatchController extends BaseController {
|
||||
return toAjax(xgStudentMsgBatchService.submitStudentMsg(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据部门ID查询该组织下所有已完善信息的学生数据
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @param deptId 部门ID
|
||||
* @param name 学生姓名(模糊查询)
|
||||
* @param studentNo 学号(精确查询)
|
||||
* @param phone 手机号码(精确查询)
|
||||
* @param infoStatus 信息状态
|
||||
* @param pageQuery 分页查询参数
|
||||
*/
|
||||
@SaCheckPermission("studentMsgBatch:studentMsgBatch:query")
|
||||
@GetMapping("/listByDept")
|
||||
public TableDataInfo<XgStudentMsgBatchVo> listByDeptId(
|
||||
@RequestParam(required = true) Long batchId,
|
||||
@RequestParam(required = true) Long deptId,
|
||||
@RequestParam(required = false) String name,
|
||||
@RequestParam(required = false) String studentNo,
|
||||
@RequestParam(required = false) String phone,
|
||||
@RequestParam(required = false) String infoStatus,
|
||||
PageQuery pageQuery) {
|
||||
return xgStudentMsgBatchService.queryPageListByDeptId(batchId, deptId, name, studentNo, phone, infoStatus,
|
||||
pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 驳回学生信息提交
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @param userId 用户ID
|
||||
* @param remark 驳回原因
|
||||
*/
|
||||
@SaCheckPermission("studentMsgBatch:studentMsgBatch:edit")
|
||||
@Log(title = "驳回学生信息提交", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PostMapping("/rejectStudentMsg")
|
||||
public R<Void> rejectStudentMsg(@RequestParam @NotNull(message = "批次ID不能为空") Long batchId,
|
||||
@RequestParam @NotNull(message = "用户ID不能为空") Long userId,
|
||||
@RequestParam @NotBlank(message = "驳回原因不能为空") String remark) {
|
||||
return toAjax(xgStudentMsgBatchService.rejectStudentMsg(batchId, userId, remark));
|
||||
}
|
||||
|
||||
/**
|
||||
* 审核通过学生信息提交
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @param userId 用户ID
|
||||
* @param remark 审核备注
|
||||
*/
|
||||
@SaCheckPermission("studentMsgBatch:studentMsgBatch:edit")
|
||||
@Log(title = "审核通过学生信息提交", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PostMapping("/approveStudentMsg")
|
||||
public R<Void> approveStudentMsg(@RequestParam @NotNull(message = "批次ID不能为空") Long batchId,
|
||||
@RequestParam @NotNull(message = "用户ID不能为空") Long userId,
|
||||
@RequestParam(required = false) String remark) {
|
||||
return toAjax(xgStudentMsgBatchService.approveStudentMsg(batchId, userId, remark));
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较学生信息变更详情
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @param userId 用户ID
|
||||
* @return 变更详情信息
|
||||
*/
|
||||
@SaCheckPermission("studentMsgBatch:studentMsgBatch:query")
|
||||
@GetMapping("/compareStudentMsg")
|
||||
public R<Map<String, Object>> compareStudentMsg(@RequestParam @NotNull(message = "批次ID不能为空") Long batchId,
|
||||
@RequestParam @NotNull(message = "用户ID不能为空") Long userId) {
|
||||
return R.ok(xgStudentMsgBatchService.compareStudentMsg(batchId, userId));
|
||||
}
|
||||
|
||||
}
|
||||
+21
-2
@@ -14,8 +14,6 @@ import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 学生信息收集批次视图对象 xg_stu_col_batch
|
||||
*
|
||||
@@ -73,5 +71,26 @@ public class XgStuColBatchVo implements Serializable {
|
||||
@ExcelProperty(value = "收集类型 1 - 新学期收集 2 - 其他")
|
||||
private String collectionType;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
private Long createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
private Long updateBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
|
||||
}
|
||||
+12
@@ -4,6 +4,7 @@ import org.dromara.xg.domain.XgStuColBatchClass;
|
||||
import org.dromara.xg.domain.vo.XgStuColBatchClassVo;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.dromara.xg.domain.vo.XgStuColBatchVo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -57,4 +58,15 @@ public interface XgStuColBatchClassMapper extends BaseMapperPlus<XgStuColBatchCl
|
||||
*/
|
||||
List<Map<String, Object>> selectStudentAvailableBatches(@Param("studentId") Long studentId,
|
||||
@Param("batchName") String batchName);
|
||||
|
||||
/**
|
||||
* 查询老师关联的批次信息
|
||||
* 通过xg_stu_col_batch与xg_stu_col_batch_class关联查询
|
||||
*
|
||||
* @param deptIds 部门ID列表
|
||||
* @param batchName 批次名称(可选)
|
||||
* @return 批次信息列表
|
||||
*/
|
||||
List<XgStuColBatchVo> selectTeacherRelatedBatches(@Param("deptIds") List<Long> deptIds,
|
||||
@Param("batchName") String batchName);
|
||||
}
|
||||
+80
-21
@@ -4,6 +4,7 @@ import org.dromara.xg.domain.XgStudentMsgBatch;
|
||||
import org.dromara.xg.domain.vo.XgStudentMsgBatchVo;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -16,28 +17,86 @@ import java.util.Map;
|
||||
*/
|
||||
public interface XgStudentMsgBatchMapper extends BaseMapperPlus<XgStudentMsgBatch, XgStudentMsgBatchVo> {
|
||||
|
||||
/**
|
||||
* 查询学生信息完善的历史记录信息
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @return 历史记录列表
|
||||
*/
|
||||
List<Map<String, Object>> selectHistoryByBatchId(@Param("batchId") Long batchId, @Param("userId") Long userId);
|
||||
/**
|
||||
* 查询学生信息完善的历史记录信息
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @return 历史记录列表
|
||||
*/
|
||||
List<Map<String, Object>> selectHistoryByBatchId(@Param("batchId") Long batchId, @Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 查询最后一次提交的信息(delete = 0的数据)
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @return 最后一次提交的信息列表
|
||||
*/
|
||||
List<XgStudentMsgBatchVo> selectCurrentByBatchId(@Param("batchId") Long batchId, @Param("userId") Long userId);
|
||||
/**
|
||||
* 查询最后一次提交的信息(delete = 0的数据)
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @return 最后一次提交的信息列表
|
||||
*/
|
||||
List<XgStudentMsgBatchVo> selectCurrentByBatchId(@Param("batchId") Long batchId, @Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 逻辑删除当前登录人的历史数据(不更新update_by和update_time)
|
||||
*
|
||||
* @param userId 当前登录用户ID
|
||||
* @return 更新记录数
|
||||
*/
|
||||
int logicalDeleteByUserId(@Param("userId") Long userId);
|
||||
/**
|
||||
* 逻辑删除当前登录人的历史数据(不更新update_by和update_time)
|
||||
*
|
||||
* @param userId 当前登录用户ID
|
||||
* @return 更新记录数
|
||||
*/
|
||||
int logicalDeleteByUserId(@Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 根据部门ID查询所有子部门ID列表(包括自己)
|
||||
*
|
||||
* @param deptId 部门ID
|
||||
* @return 部门ID列表
|
||||
*/
|
||||
List<Long> selectChildDeptIds(@Param("deptId") Long deptId);
|
||||
|
||||
/**
|
||||
* 根据部门ID列表和条件分页查询学生信息批次数据
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param batchId 批次ID
|
||||
* @param deptIds 部门ID列表
|
||||
* @param name 学生姓名(模糊查询)
|
||||
* @param studentNo 学号(精确查询)
|
||||
* @param phone 手机号码(精确查询)
|
||||
* @param infoStatus 信息状态
|
||||
* @return 学生信息批次分页数据
|
||||
*/
|
||||
Page<XgStudentMsgBatchVo> selectPageByDeptIds(@Param("page") Page<XgStudentMsgBatchVo> page,
|
||||
@Param("batchId") Long batchId,
|
||||
@Param("deptIds") List<Long> deptIds,
|
||||
@Param("name") String name,
|
||||
@Param("studentNo") String studentNo,
|
||||
@Param("phone") String phone,
|
||||
@Param("infoStatus") String infoStatus);
|
||||
|
||||
/**
|
||||
* 驳回学生信息提交
|
||||
* 更新 xg_student_msg_batch 表中指定批次和用户的记录
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @param userId 用户ID
|
||||
* @param remark 驳回原因
|
||||
* @param currentUserId 当前登录人ID
|
||||
* @return 更新记录数
|
||||
*/
|
||||
int rejectStudentMsgByBatchIdAndUserId(@Param("batchId") Long batchId,
|
||||
@Param("userId") Long userId,
|
||||
@Param("remark") String remark,
|
||||
@Param("currentUserId") Long currentUserId);
|
||||
|
||||
/**
|
||||
* 审核通过学生信息提交
|
||||
* 更新 xg_student_msg_batch 表中指定批次和用户的记录
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @param userId 用户ID
|
||||
* @param remark 审核备注
|
||||
* @param currentUserId 当前登录人ID
|
||||
* @return 更新记录数
|
||||
*/
|
||||
int approveStudentMsgByBatchIdAndUserId(@Param("batchId") Long batchId,
|
||||
@Param("userId") Long userId,
|
||||
@Param("remark") String remark,
|
||||
@Param("currentUserId") Long currentUserId);
|
||||
|
||||
}
|
||||
+62
@@ -75,6 +75,15 @@ public interface IXgStudentMsgBatchService {
|
||||
*/
|
||||
Map<String, Object> getStudentMsgHistory(Long batchId);
|
||||
|
||||
/**
|
||||
* 查询学生信息完善的历史记录信息及最后一次提交的信息状态
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @param userId 用户ID
|
||||
* @return 包含历史记录和最后一次提交信息的Map
|
||||
*/
|
||||
Map<String, Object> getStudentMsgHistory(Long batchId, Long userId);
|
||||
|
||||
/**
|
||||
* 获取用户该批次的历史已填写数据
|
||||
*
|
||||
@@ -83,6 +92,15 @@ public interface IXgStudentMsgBatchService {
|
||||
*/
|
||||
XgStudentMsgBatchVo getUserHistoryData(Long batchId);
|
||||
|
||||
/**
|
||||
* 获取用户该批次的历史已填写数据
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @param userId 用户ID
|
||||
* @return 用户历史已填写数据
|
||||
*/
|
||||
XgStudentMsgBatchVo getUserHistoryData(Long batchId, Long userId);
|
||||
|
||||
/**
|
||||
* 学生提交信息完善数据
|
||||
* 第一步:逻辑删除当前登录人的历史数据
|
||||
@@ -92,4 +110,48 @@ public interface IXgStudentMsgBatchService {
|
||||
* @return 是否提交成功
|
||||
*/
|
||||
Boolean submitStudentMsg(XgStudentMsgBatchBo bo);
|
||||
|
||||
/**
|
||||
* 根据部门ID查询该组织下所有已完善信息的学生数据(分页查询)
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @param deptId 部门ID
|
||||
* @param name 学生姓名(模糊查询)
|
||||
* @param studentNo 学号(精确查询)
|
||||
* @param phone 手机号码(精确查询)
|
||||
* @param infoStatus 信息状态
|
||||
* @param pageQuery 分页查询参数
|
||||
* @return 学生信息批次收集分页列表
|
||||
*/
|
||||
TableDataInfo<XgStudentMsgBatchVo> queryPageListByDeptId(Long batchId, Long deptId, String name, String studentNo,
|
||||
String phone, String infoStatus, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 驳回学生信息提交
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @param userId 用户ID
|
||||
* @param remark 驳回原因
|
||||
* @return 是否驳回成功
|
||||
*/
|
||||
Boolean rejectStudentMsg(Long batchId, Long userId, String remark);
|
||||
|
||||
/**
|
||||
* 审核通过学生信息提交
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @param userId 用户ID
|
||||
* @param remark 审核备注
|
||||
* @return 是否审核通过成功
|
||||
*/
|
||||
Boolean approveStudentMsg(Long batchId, Long userId, String remark);
|
||||
|
||||
/**
|
||||
* 比较学生信息变更详情
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @param userId 用户ID
|
||||
* @return 包含字段变更详情的Map
|
||||
*/
|
||||
Map<String, Object> compareStudentMsg(Long batchId, Long userId);
|
||||
}
|
||||
+708
-1
@@ -19,6 +19,8 @@ import org.dromara.xg.domain.vo.XgStudentMsgVo;
|
||||
import org.dromara.xg.mapper.XgStudentMsgBatchMapper;
|
||||
import org.dromara.xg.mapper.XgStudentMsgMapper;
|
||||
import org.dromara.xg.service.IXgStudentMsgBatchService;
|
||||
import org.dromara.system.service.ISysUserService;
|
||||
import org.dromara.xg.service.IXgStudentMsgService;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -41,6 +43,8 @@ public class XgStudentMsgBatchServiceImpl implements IXgStudentMsgBatchService {
|
||||
|
||||
private final XgStudentMsgBatchMapper baseMapper;
|
||||
private final XgStudentMsgMapper xgStudentMsgMapper;
|
||||
private final ISysUserService sysUserService;
|
||||
private final IXgStudentMsgService xgStudentMsgService;
|
||||
|
||||
/**
|
||||
* 查询学生信息批次收集
|
||||
@@ -337,6 +341,7 @@ public class XgStudentMsgBatchServiceImpl implements IXgStudentMsgBatchService {
|
||||
}
|
||||
// 添加审核记录到处理后的列表
|
||||
processedHistoryMsg.add(auditRecord);
|
||||
record.put("infoStatus", "3");
|
||||
log.debug("为学号: {} 的记录添加审核记录,状态: {}",
|
||||
record.get("studentNo"), infoStatus);
|
||||
}
|
||||
@@ -346,7 +351,72 @@ public class XgStudentMsgBatchServiceImpl implements IXgStudentMsgBatchService {
|
||||
}
|
||||
|
||||
// 查询最后一次提交的信息(delete = 0的数据)
|
||||
List<XgStudentMsgBatchVo> currentMsg = baseMapper.selectCurrentByBatchId(batchId, LoginHelper.getUserId());
|
||||
List<XgStudentMsgBatchVo> currentMsg = baseMapper.selectCurrentByBatchId(batchId,
|
||||
LoginHelper.getUserId());
|
||||
|
||||
result.put("historyMsg", historyMsg);
|
||||
result.put("currentMsg", currentMsg);
|
||||
|
||||
log.info("查询批次ID: {} 的历史记录, 历史记录数量: {}, 当前记录数量: {}",
|
||||
batchId, historyMsg.size(), currentMsg.size());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询学生信息完善的历史记录信息及最后一次提交的信息状态
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @param userId 用户ID
|
||||
* @return 包含历史记录和最后一次提交信息的Map
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getStudentMsgHistory(Long batchId, Long userId) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
// 查询历史记录信息
|
||||
List<Map<String, Object>> historyMsg = baseMapper.selectHistoryByBatchId(batchId,
|
||||
userId);
|
||||
|
||||
// 处理历史记录,添加审核记录
|
||||
if (historyMsg != null && !historyMsg.isEmpty()) {
|
||||
List<Map<String, Object>> processedHistoryMsg = new ArrayList<>();
|
||||
for (Map<String, Object> record : historyMsg) {
|
||||
// 添加原记录
|
||||
processedHistoryMsg.add(record);
|
||||
|
||||
// 检查infoStatus,如果是4或5则添加审核记录
|
||||
Object infoStatusObj = record.get("infoStatus");
|
||||
if (infoStatusObj != null) {
|
||||
String infoStatus = infoStatusObj.toString();
|
||||
if ("4".equals(infoStatus) || "5".equals(infoStatus)) {
|
||||
// 创建审核记录
|
||||
Map<String, Object> auditRecord = new HashMap<>();
|
||||
auditRecord.put("infoStatus", infoStatus);
|
||||
auditRecord.put("createTime", record.get("updateTime"));
|
||||
auditRecord.put("createBy", record.get("updateBy"));
|
||||
auditRecord.put("createByName", record.get("updateByName"));
|
||||
auditRecord.put("remark", record.get("remark"));
|
||||
// 根据状态设置审核结果描述
|
||||
if ("4".equals(infoStatus)) {
|
||||
auditRecord.put("auditResult", "审核不通过");
|
||||
} else if ("5".equals(infoStatus)) {
|
||||
auditRecord.put("auditResult", "审核通过");
|
||||
}
|
||||
// 添加审核记录到处理后的列表
|
||||
processedHistoryMsg.add(auditRecord);
|
||||
record.put("infoStatus", "3");
|
||||
log.debug("为学号: {} 的记录添加审核记录,状态: {}",
|
||||
record.get("studentNo"), infoStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
historyMsg = processedHistoryMsg;
|
||||
}
|
||||
|
||||
// 查询最后一次提交的信息(delete = 0的数据)
|
||||
List<XgStudentMsgBatchVo> currentMsg = baseMapper.selectCurrentByBatchId(batchId,
|
||||
userId);
|
||||
|
||||
result.put("historyMsg", historyMsg);
|
||||
result.put("currentMsg", currentMsg);
|
||||
@@ -536,6 +606,186 @@ public class XgStudentMsgBatchServiceImpl implements IXgStudentMsgBatchService {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户该批次的历史已填写数据
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @param userId 用户ID
|
||||
* @return 用户历史已填写数据
|
||||
*/
|
||||
@Override
|
||||
public XgStudentMsgBatchVo getUserHistoryData(Long batchId, Long userId) {
|
||||
Long currentUserId = userId;
|
||||
|
||||
// 第一步:查询xg_student_msg_batch表
|
||||
// 条件:deleted = 0, batchId = 入参的batchId, create_by = 当前登录用户的id
|
||||
LambdaQueryWrapper<XgStudentMsgBatch> batchWrapper = Wrappers.lambdaQuery();
|
||||
batchWrapper.eq(XgStudentMsgBatch::getDeleted, 0)
|
||||
.eq(XgStudentMsgBatch::getBatchId, batchId)
|
||||
.eq(XgStudentMsgBatch::getId, currentUserId);
|
||||
|
||||
XgStudentMsgBatchVo batchData = baseMapper.selectVoOne(batchWrapper);
|
||||
|
||||
if (batchData != null) {
|
||||
log.info("找到用户ID: {} 在批次ID: {} 的数据", currentUserId, batchId);
|
||||
return batchData;
|
||||
}
|
||||
|
||||
// 第二步:如果第一步没有数据,则查询xg_student_msg表
|
||||
// 条件:deleted = 0, id = 当前登录用户的id
|
||||
LambdaQueryWrapper<XgStudentMsg> msgWrapper = Wrappers.lambdaQuery();
|
||||
msgWrapper.eq(XgStudentMsg::getDeleted, 0)
|
||||
.eq(XgStudentMsg::getId, currentUserId);
|
||||
|
||||
XgStudentMsgVo studentMsgData = xgStudentMsgMapper.selectVoOne(msgWrapper);
|
||||
|
||||
if (studentMsgData != null) {
|
||||
// 将XgStudentMsgVo转换为XgStudentMsgBatchVo - 使用手动转换避免MapstructUtils问题
|
||||
XgStudentMsgBatchVo result = new XgStudentMsgBatchVo();
|
||||
|
||||
// 复制基础信息
|
||||
result.setId(studentMsgData.getId());
|
||||
result.setDeleted(studentMsgData.getDeleted());
|
||||
result.setStudentNo(studentMsgData.getStudentNo());
|
||||
result.setIdCard(studentMsgData.getIdCard());
|
||||
result.setName(studentMsgData.getName());
|
||||
result.setNameEnglish(studentMsgData.getNameEnglish());
|
||||
result.setFormerName(studentMsgData.getFormerName());
|
||||
result.setGender(studentMsgData.getGender());
|
||||
result.setBirthDate(studentMsgData.getBirthDate());
|
||||
result.setNationality(studentMsgData.getNationality());
|
||||
result.setNativePlace(studentMsgData.getNativePlace());
|
||||
result.setPoliticalStatus(studentMsgData.getPoliticalStatus());
|
||||
result.setHukouLocation(studentMsgData.getHukouLocation());
|
||||
result.setHomeAddress(studentMsgData.getHomeAddress());
|
||||
result.setCurrentAddress(studentMsgData.getCurrentAddress());
|
||||
result.setPostalCode(studentMsgData.getPostalCode());
|
||||
result.setPhotoUrl(studentMsgData.getPhotoUrl());
|
||||
result.setPhotoPath(studentMsgData.getPhotoPath());
|
||||
result.setPhone(studentMsgData.getPhone());
|
||||
result.setIsBoarder(studentMsgData.getIsBoarder());
|
||||
result.setEducationLevel(studentMsgData.getEducationLevel());
|
||||
result.setHouseholdRegisterType(studentMsgData.getHouseholdRegisterType());
|
||||
result.setReligion(studentMsgData.getReligion());
|
||||
result.setSourceRegion(studentMsgData.getSourceRegion());
|
||||
result.setGraduateSchool(studentMsgData.getGraduateSchool());
|
||||
result.setEmail(studentMsgData.getEmail());
|
||||
result.setQq(studentMsgData.getQq());
|
||||
result.setWechat(studentMsgData.getWechat());
|
||||
result.setMaritalStatus(studentMsgData.getMaritalStatus());
|
||||
|
||||
// 联系人信息
|
||||
result.setEmergencyContactName(studentMsgData.getEmergencyContactName());
|
||||
result.setEmergencyContactPhone(studentMsgData.getEmergencyContactPhone());
|
||||
result.setEmergencyContactRelation(studentMsgData.getEmergencyContactRelation());
|
||||
|
||||
// 特殊情况标记
|
||||
result.setIsPoorStudent(studentMsgData.getIsPoorStudent());
|
||||
result.setIsMinority(studentMsgData.getIsMinority());
|
||||
result.setIsDisabled(studentMsgData.getIsDisabled());
|
||||
result.setIsOrphan(studentMsgData.getIsOrphan());
|
||||
result.setIsLeftBehindChild(studentMsgData.getIsLeftBehindChild());
|
||||
result.setIsSingleChild(studentMsgData.getIsSingleChild());
|
||||
result.setIsLowIncome(studentMsgData.getIsLowIncome());
|
||||
result.setHasLoan(studentMsgData.getHasLoan());
|
||||
result.setLoanAmount(studentMsgData.getLoanAmount());
|
||||
|
||||
// 个人特长和活动
|
||||
result.setSpecialties(studentMsgData.getSpecialties());
|
||||
result.setAwards(studentMsgData.getAwards());
|
||||
result.setViolations(studentMsgData.getViolations());
|
||||
result.setSocialActivities(studentMsgData.getSocialActivities());
|
||||
|
||||
// 家庭信息
|
||||
result.setFatherName(studentMsgData.getFatherName());
|
||||
result.setFatherPhone(studentMsgData.getFatherPhone());
|
||||
result.setFatherWorkUnit(studentMsgData.getFatherWorkUnit());
|
||||
result.setMotherName(studentMsgData.getMotherName());
|
||||
result.setMotherPhone(studentMsgData.getMotherPhone());
|
||||
result.setMotherWorkUnit(studentMsgData.getMotherWorkUnit());
|
||||
result.setGuardianName(studentMsgData.getGuardianName());
|
||||
result.setGuardianPhone(studentMsgData.getGuardianPhone());
|
||||
result.setGuardianRelation(studentMsgData.getGuardianRelation());
|
||||
result.setFamilyIncome(studentMsgData.getFamilyIncome());
|
||||
result.setPerCapitaIncome(studentMsgData.getPerCapitaIncome());
|
||||
result.setIncomeSource(studentMsgData.getIncomeSource());
|
||||
result.setFamilyEconomicStatus(studentMsgData.getFamilyEconomicStatus());
|
||||
result.setFamilyMembers(studentMsgData.getFamilyMembers());
|
||||
|
||||
// 身体健康信息
|
||||
result.setHeight(studentMsgData.getHeight());
|
||||
result.setWeight(studentMsgData.getWeight());
|
||||
result.setBloodType(studentMsgData.getBloodType());
|
||||
result.setHealthStatus(studentMsgData.getHealthStatus());
|
||||
result.setSpecialCondition(studentMsgData.getSpecialCondition());
|
||||
result.setMedicalHistory(studentMsgData.getMedicalHistory());
|
||||
result.setAllergies(studentMsgData.getAllergies());
|
||||
result.setDisabilityType(studentMsgData.getDisabilityType());
|
||||
result.setDisabilityLevel(studentMsgData.getDisabilityLevel());
|
||||
|
||||
// 学籍信息
|
||||
result.setAdmissionCategory(studentMsgData.getAdmissionCategory());
|
||||
result.setStudentStatus(studentMsgData.getStudentStatus());
|
||||
result.setEnrollmentYear(studentMsgData.getEnrollmentYear());
|
||||
result.setEnrollmentDate(studentMsgData.getEnrollmentDate());
|
||||
|
||||
// 银行信息
|
||||
result.setBankCardNo(studentMsgData.getBankCardNo());
|
||||
result.setBankName(studentMsgData.getBankName());
|
||||
result.setBankBranch(studentMsgData.getBankBranch());
|
||||
result.setCardHolderName(studentMsgData.getCardHolderName());
|
||||
|
||||
// 状态和数据来源
|
||||
result.setInfoStatus(studentMsgData.getInfoStatus());
|
||||
result.setDataSource(studentMsgData.getDataSource());
|
||||
result.setLastBatchId(studentMsgData.getLastBatchId());
|
||||
result.setLastAuditTime(studentMsgData.getLastAuditTime());
|
||||
result.setIsComplete(studentMsgData.getIsComplete());
|
||||
result.setCompletionRate(studentMsgData.getCompletionRate());
|
||||
|
||||
// 学校和班级信息
|
||||
result.setSchoolSystem(studentMsgData.getSchoolSystem());
|
||||
result.setAcademicLevel(studentMsgData.getAcademicLevel());
|
||||
result.setTrainingType(studentMsgData.getTrainingType());
|
||||
result.setMajorId(studentMsgData.getMajorId());
|
||||
result.setMajorName(studentMsgData.getMajorName());
|
||||
result.setClassId(studentMsgData.getClassId());
|
||||
result.setClassName(studentMsgData.getClassName());
|
||||
result.setGrade(studentMsgData.getGrade());
|
||||
result.setCollegeId(studentMsgData.getCollegeId());
|
||||
result.setCollegeName(studentMsgData.getCollegeName());
|
||||
result.setCounselorId(studentMsgData.getCounselorId());
|
||||
result.setCounselorName(studentMsgData.getCounselorName());
|
||||
result.setClassTeacherId(studentMsgData.getClassTeacherId());
|
||||
result.setClassTeacherName(studentMsgData.getClassTeacherName());
|
||||
result.setDormitoryBuilding(studentMsgData.getDormitoryBuilding());
|
||||
result.setDormitoryRoom(studentMsgData.getDormitoryRoom());
|
||||
|
||||
// 扩展字段
|
||||
result.setExtField1(studentMsgData.getExtField1());
|
||||
result.setExtField2(studentMsgData.getExtField2());
|
||||
result.setExtField3(studentMsgData.getExtField3());
|
||||
result.setExtField4(studentMsgData.getExtField4());
|
||||
result.setExtField5(studentMsgData.getExtField5());
|
||||
result.setRemark(studentMsgData.getRemark());
|
||||
|
||||
// 基础字段
|
||||
result.setCreateBy(studentMsgData.getCreateBy());
|
||||
result.setCreateTime(studentMsgData.getCreateTime());
|
||||
result.setUpdateBy(studentMsgData.getUpdateBy());
|
||||
result.setUpdateTime(studentMsgData.getUpdateTime());
|
||||
|
||||
// 设置批次ID
|
||||
result.setBatchId(batchId);
|
||||
|
||||
log.info("找到用户ID: {} 的基础学生信息数据", currentUserId);
|
||||
return result;
|
||||
}
|
||||
|
||||
log.info("用户ID: {} 在批次ID: {} 中没有找到任何数据", currentUserId, batchId);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 学生提交信息完善数据
|
||||
* 第一步:逻辑删除当前登录人的历史数据
|
||||
@@ -586,4 +836,461 @@ public class XgStudentMsgBatchServiceImpl implements IXgStudentMsgBatchService {
|
||||
throw new RuntimeException("提交学生信息完善数据失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据部门ID查询该组织下所有已完善信息的学生数据(分页查询)
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @param deptId 部门ID
|
||||
* @param name 学生姓名(模糊查询)
|
||||
* @param studentNo 学号(精确查询)
|
||||
* @param phone 手机号码(精确查询)
|
||||
* @param infoStatus 信息状态
|
||||
* @param pageQuery 分页查询参数
|
||||
* @return 学生信息批次收集分页列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<XgStudentMsgBatchVo> queryPageListByDeptId(Long batchId, Long deptId, String name,
|
||||
String studentNo, String phone, String infoStatus, PageQuery pageQuery) {
|
||||
// 第一步:根据deptId查询所有子部门ID(包括自己)
|
||||
List<Long> deptIds = baseMapper.selectChildDeptIds(deptId);
|
||||
|
||||
if (deptIds == null || deptIds.isEmpty()) {
|
||||
log.warn("部门ID: {} 没有找到任何子部门或自身不存在", deptId);
|
||||
// 返回空的分页结果
|
||||
return TableDataInfo.build(new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>());
|
||||
}
|
||||
|
||||
log.info("部门ID: {} 查询到的子部门列表: {}", deptId, deptIds);
|
||||
|
||||
// 第二步:根据部门ID列表和条件分页查询学生信息批次数据
|
||||
com.baomidou.mybatisplus.extension.plugins.pagination.Page<XgStudentMsgBatchVo> page = pageQuery
|
||||
.build();
|
||||
com.baomidou.mybatisplus.extension.plugins.pagination.Page<XgStudentMsgBatchVo> result = baseMapper
|
||||
.selectPageByDeptIds(page, batchId, deptIds, name, studentNo, phone, infoStatus);
|
||||
|
||||
log.info("根据部门ID: {} 和批次ID: {} 查询到 {} 条学生数据", deptId, batchId, result.getTotal());
|
||||
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 驳回学生信息提交
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @param userId 用户ID
|
||||
* @param remark 驳回原因
|
||||
* @return 是否驳回成功
|
||||
*/
|
||||
@Override
|
||||
public Boolean rejectStudentMsg(Long batchId, Long userId, String remark) {
|
||||
Long currentUserId = LoginHelper.getUserId();
|
||||
|
||||
try {
|
||||
// 更新学生信息批次数据:设置为驳回状态并添加驳回原因
|
||||
int updatedCount = baseMapper.rejectStudentMsgByBatchIdAndUserId(batchId, userId, remark,
|
||||
currentUserId);
|
||||
|
||||
if (updatedCount > 0) {
|
||||
log.info("成功驳回学生信息提交,批次ID: {}, 用户ID: {}, 操作人: {}, 驳回原因: {}",
|
||||
batchId, userId, currentUserId, remark);
|
||||
return true;
|
||||
} else {
|
||||
log.warn("驳回学生信息提交失败,未找到符合条件的记录,批次ID: {}, 用户ID: {}", batchId, userId);
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("驳回学生信息提交时发生异常,批次ID: {}, 用户ID: {}, 异常信息: {}",
|
||||
batchId, userId, e.getMessage(), e);
|
||||
throw new RuntimeException("驳回学生信息提交失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 审核通过学生信息提交
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @param userId 用户ID
|
||||
* @param remark 审核备注
|
||||
* @return 是否审核通过成功
|
||||
*/
|
||||
@Override
|
||||
@org.springframework.transaction.annotation.Transactional(rollbackFor = Exception.class)
|
||||
public Boolean approveStudentMsg(Long batchId, Long userId, String remark) {
|
||||
Long currentUserId = LoginHelper.getUserId();
|
||||
|
||||
try {
|
||||
// 第0步:判断 - 查询要审核的学生信息批次数据
|
||||
XgStudentMsgBatchVo batchVo = baseMapper.selectVoOne(
|
||||
Wrappers.<XgStudentMsgBatch>lambdaQuery()
|
||||
.eq(XgStudentMsgBatch::getBatchId, batchId)
|
||||
.eq(XgStudentMsgBatch::getId, userId)
|
||||
.eq(XgStudentMsgBatch::getDeleted, 0));
|
||||
|
||||
if (batchVo == null) {
|
||||
log.warn("审核通过失败,未找到符合条件的记录,批次ID: {}, 用户ID: {}", batchId, userId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 参考 XgStudentMsgController.edit 方法的校验逻辑
|
||||
// 验证学号作为用户名的唯一性
|
||||
if (StringUtils.isNotBlank(batchVo.getStudentNo())) {
|
||||
org.dromara.system.domain.bo.SysUserBo tempUser = new org.dromara.system.domain.bo.SysUserBo();
|
||||
tempUser.setUserName(batchVo.getStudentNo());
|
||||
tempUser.setUserId(userId);
|
||||
if (!sysUserService.checkUserNameUnique(tempUser)) {
|
||||
log.error("审核通过学生'{}'失败,学号'{}'已被使用作为登录账号", batchVo.getName(),
|
||||
batchVo.getStudentNo());
|
||||
throw new RuntimeException("审核通过学生'" + batchVo.getName() + "'失败,学号'"
|
||||
+ batchVo.getStudentNo() + "'已被使用作为登录账号");
|
||||
}
|
||||
}
|
||||
// 验证手机号唯一性(如果提供了手机号)
|
||||
if (StringUtils.isNotBlank(batchVo.getPhone())) {
|
||||
org.dromara.system.domain.bo.SysUserBo tempUser = new org.dromara.system.domain.bo.SysUserBo();
|
||||
tempUser.setPhonenumber(batchVo.getPhone());
|
||||
tempUser.setUserId(userId);
|
||||
if (!sysUserService.checkPhoneUnique(tempUser)) {
|
||||
log.error("审核通过学生'{}'失败,手机号码'{}'已存在", batchVo.getName(), batchVo.getPhone());
|
||||
throw new RuntimeException("审核通过学生'" + batchVo.getName() + "'失败,手机号码'"
|
||||
+ batchVo.getPhone() + "'已存在");
|
||||
}
|
||||
}
|
||||
// 验证邮箱唯一性(如果提供了邮箱)
|
||||
if (StringUtils.isNotBlank(batchVo.getEmail())) {
|
||||
org.dromara.system.domain.bo.SysUserBo tempUser = new org.dromara.system.domain.bo.SysUserBo();
|
||||
tempUser.setEmail(batchVo.getEmail());
|
||||
tempUser.setUserId(userId);
|
||||
if (!sysUserService.checkEmailUnique(tempUser)) {
|
||||
log.error("审核通过学生'{}'失败,邮箱账号'{}'已存在", batchVo.getName(), batchVo.getEmail());
|
||||
throw new RuntimeException("审核通过学生'" + batchVo.getName() + "'失败,邮箱账号'"
|
||||
+ batchVo.getEmail() + "'已存在");
|
||||
}
|
||||
}
|
||||
|
||||
// 第一步:更新 xg_student_msg_batch 表
|
||||
int updatedCount = baseMapper.approveStudentMsgByBatchIdAndUserId(batchId, userId, remark,
|
||||
currentUserId);
|
||||
|
||||
if (updatedCount <= 0) {
|
||||
log.warn("审核通过失败,未找到符合条件的记录进行更新,批次ID: {}, 用户ID: {}", batchId, userId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 第二步:将 xg_student_msg_batch 的数据更新到 xg_student_msg 表
|
||||
// 手动将 XgStudentMsgBatchVo 转换为 XgStudentMsgBo
|
||||
org.dromara.xg.domain.bo.XgStudentMsgBo studentMsgBo = new org.dromara.xg.domain.bo.XgStudentMsgBo();
|
||||
|
||||
// 复制基础信息
|
||||
studentMsgBo.setId(batchVo.getId());
|
||||
studentMsgBo.setDeleted(batchVo.getDeleted());
|
||||
studentMsgBo.setStudentNo(batchVo.getStudentNo());
|
||||
studentMsgBo.setIdCard(batchVo.getIdCard());
|
||||
studentMsgBo.setName(batchVo.getName());
|
||||
studentMsgBo.setNameEnglish(batchVo.getNameEnglish());
|
||||
studentMsgBo.setFormerName(batchVo.getFormerName());
|
||||
studentMsgBo.setGender(batchVo.getGender());
|
||||
studentMsgBo.setBirthDate(batchVo.getBirthDate());
|
||||
studentMsgBo.setNationality(batchVo.getNationality());
|
||||
studentMsgBo.setNativePlace(batchVo.getNativePlace());
|
||||
studentMsgBo.setPoliticalStatus(batchVo.getPoliticalStatus());
|
||||
studentMsgBo.setHukouLocation(batchVo.getHukouLocation());
|
||||
studentMsgBo.setHomeAddress(batchVo.getHomeAddress());
|
||||
studentMsgBo.setCurrentAddress(batchVo.getCurrentAddress());
|
||||
studentMsgBo.setPostalCode(batchVo.getPostalCode());
|
||||
studentMsgBo.setPhotoUrl(batchVo.getPhotoUrl());
|
||||
studentMsgBo.setPhotoPath(batchVo.getPhotoPath());
|
||||
studentMsgBo.setPhone(batchVo.getPhone());
|
||||
studentMsgBo.setIsBoarder(batchVo.getIsBoarder());
|
||||
studentMsgBo.setEducationLevel(batchVo.getEducationLevel());
|
||||
studentMsgBo.setHouseholdRegisterType(batchVo.getHouseholdRegisterType());
|
||||
studentMsgBo.setReligion(batchVo.getReligion());
|
||||
studentMsgBo.setSourceRegion(batchVo.getSourceRegion());
|
||||
studentMsgBo.setGraduateSchool(batchVo.getGraduateSchool());
|
||||
studentMsgBo.setEmail(batchVo.getEmail());
|
||||
studentMsgBo.setQq(batchVo.getQq());
|
||||
studentMsgBo.setWechat(batchVo.getWechat());
|
||||
studentMsgBo.setMaritalStatus(batchVo.getMaritalStatus());
|
||||
|
||||
// 联系人信息
|
||||
studentMsgBo.setEmergencyContactName(batchVo.getEmergencyContactName());
|
||||
studentMsgBo.setEmergencyContactPhone(batchVo.getEmergencyContactPhone());
|
||||
studentMsgBo.setEmergencyContactRelation(batchVo.getEmergencyContactRelation());
|
||||
|
||||
// 特殊情况标记
|
||||
studentMsgBo.setIsPoorStudent(batchVo.getIsPoorStudent());
|
||||
studentMsgBo.setIsMinority(batchVo.getIsMinority());
|
||||
studentMsgBo.setIsDisabled(batchVo.getIsDisabled());
|
||||
studentMsgBo.setIsOrphan(batchVo.getIsOrphan());
|
||||
studentMsgBo.setIsLeftBehindChild(batchVo.getIsLeftBehindChild());
|
||||
studentMsgBo.setIsSingleChild(batchVo.getIsSingleChild());
|
||||
studentMsgBo.setIsLowIncome(batchVo.getIsLowIncome());
|
||||
studentMsgBo.setHasLoan(batchVo.getHasLoan());
|
||||
studentMsgBo.setLoanAmount(batchVo.getLoanAmount());
|
||||
|
||||
// 个人特长和活动
|
||||
studentMsgBo.setSpecialties(batchVo.getSpecialties());
|
||||
studentMsgBo.setAwards(batchVo.getAwards());
|
||||
studentMsgBo.setViolations(batchVo.getViolations());
|
||||
studentMsgBo.setSocialActivities(batchVo.getSocialActivities());
|
||||
|
||||
// 家庭信息
|
||||
studentMsgBo.setFatherName(batchVo.getFatherName());
|
||||
studentMsgBo.setFatherPhone(batchVo.getFatherPhone());
|
||||
studentMsgBo.setFatherWorkUnit(batchVo.getFatherWorkUnit());
|
||||
studentMsgBo.setMotherName(batchVo.getMotherName());
|
||||
studentMsgBo.setMotherPhone(batchVo.getMotherPhone());
|
||||
studentMsgBo.setMotherWorkUnit(batchVo.getMotherWorkUnit());
|
||||
studentMsgBo.setGuardianName(batchVo.getGuardianName());
|
||||
studentMsgBo.setGuardianPhone(batchVo.getGuardianPhone());
|
||||
studentMsgBo.setGuardianRelation(batchVo.getGuardianRelation());
|
||||
studentMsgBo.setFamilyIncome(batchVo.getFamilyIncome());
|
||||
studentMsgBo.setPerCapitaIncome(batchVo.getPerCapitaIncome());
|
||||
studentMsgBo.setIncomeSource(batchVo.getIncomeSource());
|
||||
studentMsgBo.setFamilyEconomicStatus(batchVo.getFamilyEconomicStatus());
|
||||
studentMsgBo.setFamilyMembers(batchVo.getFamilyMembers());
|
||||
|
||||
// 身体健康信息
|
||||
studentMsgBo.setHeight(batchVo.getHeight());
|
||||
studentMsgBo.setWeight(batchVo.getWeight());
|
||||
studentMsgBo.setBloodType(batchVo.getBloodType());
|
||||
studentMsgBo.setHealthStatus(batchVo.getHealthStatus());
|
||||
studentMsgBo.setSpecialCondition(batchVo.getSpecialCondition());
|
||||
studentMsgBo.setMedicalHistory(batchVo.getMedicalHistory());
|
||||
studentMsgBo.setAllergies(batchVo.getAllergies());
|
||||
studentMsgBo.setDisabilityType(batchVo.getDisabilityType());
|
||||
studentMsgBo.setDisabilityLevel(batchVo.getDisabilityLevel());
|
||||
|
||||
// 学籍信息
|
||||
studentMsgBo.setAdmissionCategory(batchVo.getAdmissionCategory());
|
||||
studentMsgBo.setStudentStatus(batchVo.getStudentStatus());
|
||||
studentMsgBo.setEnrollmentYear(batchVo.getEnrollmentYear());
|
||||
studentMsgBo.setEnrollmentDate(batchVo.getEnrollmentDate());
|
||||
|
||||
// 银行信息
|
||||
studentMsgBo.setBankCardNo(batchVo.getBankCardNo());
|
||||
studentMsgBo.setBankName(batchVo.getBankName());
|
||||
studentMsgBo.setBankBranch(batchVo.getBankBranch());
|
||||
studentMsgBo.setCardHolderName(batchVo.getCardHolderName());
|
||||
|
||||
// 状态和数据来源
|
||||
studentMsgBo.setInfoStatus(batchVo.getInfoStatus());
|
||||
studentMsgBo.setDataSource(batchVo.getDataSource());
|
||||
studentMsgBo.setLastBatchId(batchVo.getBatchId());
|
||||
studentMsgBo.setLastAuditTime(new Date());
|
||||
studentMsgBo.setIsComplete(batchVo.getIsComplete());
|
||||
studentMsgBo.setCompletionRate(batchVo.getCompletionRate());
|
||||
|
||||
// 学校和班级信息
|
||||
studentMsgBo.setSchoolSystem(batchVo.getSchoolSystem());
|
||||
studentMsgBo.setAcademicLevel(batchVo.getAcademicLevel());
|
||||
studentMsgBo.setTrainingType(batchVo.getTrainingType());
|
||||
studentMsgBo.setMajorId(batchVo.getMajorId());
|
||||
studentMsgBo.setMajorName(batchVo.getMajorName());
|
||||
studentMsgBo.setClassId(batchVo.getClassId());
|
||||
studentMsgBo.setClassName(batchVo.getClassName());
|
||||
studentMsgBo.setGrade(batchVo.getGrade());
|
||||
studentMsgBo.setCollegeId(batchVo.getCollegeId());
|
||||
studentMsgBo.setCollegeName(batchVo.getCollegeName());
|
||||
studentMsgBo.setCounselorId(batchVo.getCounselorId());
|
||||
studentMsgBo.setCounselorName(batchVo.getCounselorName());
|
||||
studentMsgBo.setClassTeacherId(batchVo.getClassTeacherId());
|
||||
studentMsgBo.setClassTeacherName(batchVo.getClassTeacherName());
|
||||
studentMsgBo.setDormitoryBuilding(batchVo.getDormitoryBuilding());
|
||||
studentMsgBo.setDormitoryRoom(batchVo.getDormitoryRoom());
|
||||
|
||||
// 扩展字段
|
||||
studentMsgBo.setExtField1(batchVo.getExtField1());
|
||||
studentMsgBo.setExtField2(batchVo.getExtField2());
|
||||
studentMsgBo.setExtField3(batchVo.getExtField3());
|
||||
studentMsgBo.setExtField4(batchVo.getExtField4());
|
||||
studentMsgBo.setExtField5(batchVo.getExtField5());
|
||||
studentMsgBo.setRemark(batchVo.getRemark());
|
||||
|
||||
// 调用学生信息服务的更新方法(参考 XgStudentMsgController.edit 方法)
|
||||
boolean updateResult = xgStudentMsgService.updateByBo(studentMsgBo);
|
||||
|
||||
if (!updateResult) {
|
||||
log.error("审核通过失败,更新学生信息表失败,批次ID: {}, 用户ID: {}", batchId, userId);
|
||||
throw new RuntimeException("审核通过失败,更新学生信息表失败");
|
||||
}
|
||||
|
||||
log.info("成功审核通过学生信息提交,批次ID: {}, 用户ID: {}, 操作人: {}, 审核备注: {}",
|
||||
batchId, userId, currentUserId, remark);
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("审核通过学生信息提交时发生异常,批次ID: {}, 用户ID: {}, 异常信息: {}",
|
||||
batchId, userId, e.getMessage(), e);
|
||||
throw new RuntimeException("审核通过学生信息提交失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较学生信息变更详情
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @param userId 用户ID
|
||||
* @return 包含字段变更详情的Map
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> compareStudentMsg(Long batchId, Long userId) {
|
||||
try {
|
||||
// 查询原始数据(xg_student_msg表)
|
||||
LambdaQueryWrapper<XgStudentMsg> originalWrapper = Wrappers.lambdaQuery();
|
||||
originalWrapper.eq(XgStudentMsg::getId, userId)
|
||||
.eq(XgStudentMsg::getDeleted, 0);
|
||||
XgStudentMsgVo originalData = xgStudentMsgMapper.selectVoOne(originalWrapper);
|
||||
|
||||
// 查询修改后数据(xg_student_msg_batch表)
|
||||
LambdaQueryWrapper<XgStudentMsgBatch> batchWrapper = Wrappers.lambdaQuery();
|
||||
batchWrapper.eq(XgStudentMsgBatch::getId, userId) // batch表的id字段对应user_id
|
||||
.eq(XgStudentMsgBatch::getBatchId, batchId)
|
||||
.eq(XgStudentMsgBatch::getDeleted, 0);
|
||||
XgStudentMsgBatchVo batchData = baseMapper.selectVoOne(batchWrapper);
|
||||
|
||||
// 构建返回结果
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
List<Map<String, Object>> changedFields = new ArrayList<>();
|
||||
|
||||
if (originalData == null) {
|
||||
result.put("hasOriginalData", false);
|
||||
result.put("message", "未找到原始学生信息");
|
||||
result.put("changedFields", changedFields);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (batchData == null) {
|
||||
result.put("hasBatchData", false);
|
||||
result.put("message", "未找到批次提交信息");
|
||||
result.put("changedFields", changedFields);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 比较字段差异
|
||||
compareAndAddChange(changedFields, "姓名", "name", originalData.getName(), batchData.getName());
|
||||
compareAndAddChange(changedFields, "英文姓名", "nameEnglish", originalData.getNameEnglish(), batchData.getNameEnglish());
|
||||
compareAndAddChange(changedFields, "曾用名", "formerName", originalData.getFormerName(), batchData.getFormerName());
|
||||
compareAndAddChange(changedFields, "性别", "gender", originalData.getGender(), batchData.getGender());
|
||||
compareAndAddChange(changedFields, "出生日期", "birthDate", originalData.getBirthDate(), batchData.getBirthDate());
|
||||
compareAndAddChange(changedFields, "民族", "nationality", originalData.getNationality(), batchData.getNationality());
|
||||
compareAndAddChange(changedFields, "籍贯", "nativePlace", originalData.getNativePlace(), batchData.getNativePlace());
|
||||
compareAndAddChange(changedFields, "政治面貌", "politicalStatus", originalData.getPoliticalStatus(), batchData.getPoliticalStatus());
|
||||
compareAndAddChange(changedFields, "户籍所在地", "hukouLocation", originalData.getHukouLocation(), batchData.getHukouLocation());
|
||||
compareAndAddChange(changedFields, "家庭地址", "homeAddress", originalData.getHomeAddress(), batchData.getHomeAddress());
|
||||
compareAndAddChange(changedFields, "现住址", "currentAddress", originalData.getCurrentAddress(), batchData.getCurrentAddress());
|
||||
compareAndAddChange(changedFields, "邮政编码", "postalCode", originalData.getPostalCode(), batchData.getPostalCode());
|
||||
compareAndAddChange(changedFields, "手机号码", "phone", originalData.getPhone(), batchData.getPhone());
|
||||
compareAndAddChange(changedFields, "是否住校", "isBoarder", originalData.getIsBoarder(), batchData.getIsBoarder());
|
||||
compareAndAddChange(changedFields, "文化程度", "educationLevel", originalData.getEducationLevel(), batchData.getEducationLevel());
|
||||
compareAndAddChange(changedFields, "户口类型", "householdRegisterType", originalData.getHouseholdRegisterType(), batchData.getHouseholdRegisterType());
|
||||
compareAndAddChange(changedFields, "宗教信仰", "religion", originalData.getReligion(), batchData.getReligion());
|
||||
compareAndAddChange(changedFields, "生源地区", "sourceRegion", originalData.getSourceRegion(), batchData.getSourceRegion());
|
||||
compareAndAddChange(changedFields, "毕业院校", "graduateSchool", originalData.getGraduateSchool(), batchData.getGraduateSchool());
|
||||
compareAndAddChange(changedFields, "电子邮箱", "email", originalData.getEmail(), batchData.getEmail());
|
||||
compareAndAddChange(changedFields, "QQ号码", "qq", originalData.getQq(), batchData.getQq());
|
||||
compareAndAddChange(changedFields, "微信号", "wechat", originalData.getWechat(), batchData.getWechat());
|
||||
compareAndAddChange(changedFields, "婚姻状况", "maritalStatus", originalData.getMaritalStatus(), batchData.getMaritalStatus());
|
||||
compareAndAddChange(changedFields, "紧急联系人姓名", "emergencyContactName", originalData.getEmergencyContactName(), batchData.getEmergencyContactName());
|
||||
compareAndAddChange(changedFields, "紧急联系人电话", "emergencyContactPhone", originalData.getEmergencyContactPhone(), batchData.getEmergencyContactPhone());
|
||||
compareAndAddChange(changedFields, "紧急联系人关系", "emergencyContactRelation", originalData.getEmergencyContactRelation(), batchData.getEmergencyContactRelation());
|
||||
compareAndAddChange(changedFields, "是否贫困生", "isPoorStudent", originalData.getIsPoorStudent(), batchData.getIsPoorStudent());
|
||||
compareAndAddChange(changedFields, "是否少数民族", "isMinority", originalData.getIsMinority(), batchData.getIsMinority());
|
||||
compareAndAddChange(changedFields, "是否残疾", "isDisabled", originalData.getIsDisabled(), batchData.getIsDisabled());
|
||||
compareAndAddChange(changedFields, "是否孤儿", "isOrphan", originalData.getIsOrphan(), batchData.getIsOrphan());
|
||||
compareAndAddChange(changedFields, "是否留守儿童", "isLeftBehindChild", originalData.getIsLeftBehindChild(), batchData.getIsLeftBehindChild());
|
||||
compareAndAddChange(changedFields, "是否独生子女", "isSingleChild", originalData.getIsSingleChild(), batchData.getIsSingleChild());
|
||||
compareAndAddChange(changedFields, "是否低保", "isLowIncome", originalData.getIsLowIncome(), batchData.getIsLowIncome());
|
||||
compareAndAddChange(changedFields, "是否有助学贷款", "hasLoan", originalData.getHasLoan(), batchData.getHasLoan());
|
||||
compareAndAddChange(changedFields, "助学贷款金额", "loanAmount", originalData.getLoanAmount(), batchData.getLoanAmount());
|
||||
compareAndAddChange(changedFields, "特长爱好", "specialties", originalData.getSpecialties(), batchData.getSpecialties());
|
||||
compareAndAddChange(changedFields, "获奖情况", "awards", originalData.getAwards(), batchData.getAwards());
|
||||
compareAndAddChange(changedFields, "违纪记录", "violations", originalData.getViolations(), batchData.getViolations());
|
||||
compareAndAddChange(changedFields, "社会活动", "socialActivities", originalData.getSocialActivities(), batchData.getSocialActivities());
|
||||
compareAndAddChange(changedFields, "父亲姓名", "fatherName", originalData.getFatherName(), batchData.getFatherName());
|
||||
compareAndAddChange(changedFields, "父亲电话", "fatherPhone", originalData.getFatherPhone(), batchData.getFatherPhone());
|
||||
compareAndAddChange(changedFields, "父亲工作单位", "fatherWorkUnit", originalData.getFatherWorkUnit(), batchData.getFatherWorkUnit());
|
||||
compareAndAddChange(changedFields, "母亲姓名", "motherName", originalData.getMotherName(), batchData.getMotherName());
|
||||
compareAndAddChange(changedFields, "母亲电话", "motherPhone", originalData.getMotherPhone(), batchData.getMotherPhone());
|
||||
compareAndAddChange(changedFields, "母亲工作单位", "motherWorkUnit", originalData.getMotherWorkUnit(), batchData.getMotherWorkUnit());
|
||||
compareAndAddChange(changedFields, "监护人姓名", "guardianName", originalData.getGuardianName(), batchData.getGuardianName());
|
||||
compareAndAddChange(changedFields, "监护人电话", "guardianPhone", originalData.getGuardianPhone(), batchData.getGuardianPhone());
|
||||
compareAndAddChange(changedFields, "监护人关系", "guardianRelation", originalData.getGuardianRelation(), batchData.getGuardianRelation());
|
||||
compareAndAddChange(changedFields, "家庭年收入", "familyIncome", originalData.getFamilyIncome(), batchData.getFamilyIncome());
|
||||
compareAndAddChange(changedFields, "人均收入", "perCapitaIncome", originalData.getPerCapitaIncome(), batchData.getPerCapitaIncome());
|
||||
compareAndAddChange(changedFields, "收入来源", "incomeSource", originalData.getIncomeSource(), batchData.getIncomeSource());
|
||||
compareAndAddChange(changedFields, "家庭经济状况", "familyEconomicStatus", originalData.getFamilyEconomicStatus(), batchData.getFamilyEconomicStatus());
|
||||
compareAndAddChange(changedFields, "家庭成员数量", "familyMembers", originalData.getFamilyMembers(), batchData.getFamilyMembers());
|
||||
compareAndAddChange(changedFields, "身高", "height", originalData.getHeight(), batchData.getHeight());
|
||||
compareAndAddChange(changedFields, "体重", "weight", originalData.getWeight(), batchData.getWeight());
|
||||
compareAndAddChange(changedFields, "血型", "bloodType", originalData.getBloodType(), batchData.getBloodType());
|
||||
compareAndAddChange(changedFields, "健康状况", "healthStatus", originalData.getHealthStatus(), batchData.getHealthStatus());
|
||||
compareAndAddChange(changedFields, "特殊体质或疾病", "specialCondition", originalData.getSpecialCondition(), batchData.getSpecialCondition());
|
||||
compareAndAddChange(changedFields, "病史", "medicalHistory", originalData.getMedicalHistory(), batchData.getMedicalHistory());
|
||||
compareAndAddChange(changedFields, "过敏史", "allergies", originalData.getAllergies(), batchData.getAllergies());
|
||||
compareAndAddChange(changedFields, "残疾类型", "disabilityType", originalData.getDisabilityType(), batchData.getDisabilityType());
|
||||
compareAndAddChange(changedFields, "残疾等级", "disabilityLevel", originalData.getDisabilityLevel(), batchData.getDisabilityLevel());
|
||||
compareAndAddChange(changedFields, "录取类别", "admissionCategory", originalData.getAdmissionCategory(), batchData.getAdmissionCategory());
|
||||
compareAndAddChange(changedFields, "学籍状态", "studentStatus", originalData.getStudentStatus(), batchData.getStudentStatus());
|
||||
compareAndAddChange(changedFields, "入学年份", "enrollmentYear", originalData.getEnrollmentYear(), batchData.getEnrollmentYear());
|
||||
compareAndAddChange(changedFields, "入学日期", "enrollmentDate", originalData.getEnrollmentDate(), batchData.getEnrollmentDate());
|
||||
compareAndAddChange(changedFields, "银行卡号", "bankCardNo", originalData.getBankCardNo(), batchData.getBankCardNo());
|
||||
compareAndAddChange(changedFields, "开户银行", "bankName", originalData.getBankName(), batchData.getBankName());
|
||||
compareAndAddChange(changedFields, "开户支行", "bankBranch", originalData.getBankBranch(), batchData.getBankBranch());
|
||||
compareAndAddChange(changedFields, "持卡人姓名", "cardHolderName", originalData.getCardHolderName(), batchData.getCardHolderName());
|
||||
compareAndAddChange(changedFields, "扩展字段1", "extField1", originalData.getExtField1(), batchData.getExtField1());
|
||||
compareAndAddChange(changedFields, "扩展字段2", "extField2", originalData.getExtField2(), batchData.getExtField2());
|
||||
compareAndAddChange(changedFields, "扩展字段3", "extField3", originalData.getExtField3(), batchData.getExtField3());
|
||||
compareAndAddChange(changedFields, "扩展字段4", "extField4", originalData.getExtField4(), batchData.getExtField4());
|
||||
compareAndAddChange(changedFields, "扩展字段5", "extField5", originalData.getExtField5(), batchData.getExtField5());
|
||||
compareAndAddChange(changedFields, "备注信息", "remark", originalData.getRemark(), batchData.getRemark());
|
||||
|
||||
result.put("hasOriginalData", true);
|
||||
result.put("hasBatchData", true);
|
||||
result.put("changedFields", changedFields);
|
||||
result.put("totalChanges", changedFields.size());
|
||||
result.put("originalData", originalData);
|
||||
result.put("batchData", batchData);
|
||||
|
||||
log.info("成功比较学生信息变更,批次ID: {}, 用户ID: {}, 变更字段数量: {}",
|
||||
batchId, userId, changedFields.size());
|
||||
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.error("比较学生信息变更时发生异常,批次ID: {}, 用户ID: {}, 异常信息: {}",
|
||||
batchId, userId, e.getMessage(), e);
|
||||
throw new RuntimeException("比较学生信息变更失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较字段值并添加到变更列表
|
||||
*
|
||||
* @param changedFields 变更字段列表
|
||||
* @param fieldLabel 字段中文名称
|
||||
* @param fieldName 字段英文名称
|
||||
* @param originalValue 原始值
|
||||
* @param newValue 新值
|
||||
*/
|
||||
private void compareAndAddChange(List<Map<String, Object>> changedFields, String fieldLabel, String fieldName,
|
||||
Object originalValue, Object newValue) {
|
||||
// 处理null值比较
|
||||
boolean isChanged = false;
|
||||
if (originalValue == null && newValue != null) {
|
||||
isChanged = true;
|
||||
} else if (originalValue != null && newValue == null) {
|
||||
isChanged = true;
|
||||
} else if (originalValue != null && newValue != null && !originalValue.equals(newValue)) {
|
||||
isChanged = true;
|
||||
}
|
||||
|
||||
if (isChanged) {
|
||||
Map<String, Object> changeInfo = new HashMap<>();
|
||||
changeInfo.put("fieldLabel", fieldLabel);
|
||||
changeInfo.put("fieldName", fieldName);
|
||||
changeInfo.put("originalValue", originalValue);
|
||||
changeInfo.put("newValue", newValue);
|
||||
changedFields.add(changeInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
-7
@@ -23,14 +23,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
|
||||
<!-- 清理指定批次的历史权限数据 -->
|
||||
<update id="clearHistoryPermissions">
|
||||
UPDATE xg_stu_col_batch_class
|
||||
SET deleted = 1
|
||||
UPDATE xg_stu_col_batch_class
|
||||
SET deleted = 1
|
||||
WHERE batch_id = #{batchId}
|
||||
</update>
|
||||
|
||||
<!-- 根据部门ID列表查询班级信息 -->
|
||||
<select id="selectClassInfoByDeptIds" resultMap="XgStuColBatchClassVoResult">
|
||||
SELECT
|
||||
SELECT
|
||||
c.id,
|
||||
c.college_id,
|
||||
c.college_name,
|
||||
@@ -46,7 +46,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<foreach collection="deptIds" item="deptId" open="(" separator="," close=")">
|
||||
#{deptId}
|
||||
</foreach>
|
||||
AND c.deleted = 0
|
||||
AND c.deleted = 0
|
||||
AND c.class_status = '1'
|
||||
</select>
|
||||
|
||||
@@ -60,7 +60,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
|
||||
<!-- 根据批次ID查询权限范围详细信息 -->
|
||||
<select id="selectScopeDetailByBatchId" resultMap="XgStuColBatchClassVoResult">
|
||||
SELECT
|
||||
SELECT
|
||||
id,
|
||||
batch_id,
|
||||
class_id,
|
||||
@@ -83,7 +83,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
|
||||
<!-- 查询学生可参与的批次信息 -->
|
||||
<select id="selectStudentAvailableBatches" resultType="java.util.Map">
|
||||
SELECT
|
||||
SELECT
|
||||
b.id as batchId,
|
||||
b.batch_name as batchName,
|
||||
b.description,
|
||||
@@ -106,7 +106,36 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
AND b.batch_name LIKE CONCAT('%', #{batchName}, '%')
|
||||
</if>
|
||||
AND b.deleted = 0
|
||||
<!-- AND b.status = '0' -->
|
||||
<!-- AND b.status = '0' -->
|
||||
ORDER BY b.start_time DESC, b.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 查询老师关联的批次信息 -->
|
||||
<select id="selectTeacherRelatedBatches" resultType="org.dromara.xg.domain.vo.XgStuColBatchVo">
|
||||
SELECT DISTINCT
|
||||
b.id,
|
||||
b.batch_name as batchName,
|
||||
b.description,
|
||||
b.start_time as startTime,
|
||||
b.end_time as endTime,
|
||||
b.status,
|
||||
b.collection_type as collectionType,
|
||||
b.remark,
|
||||
b.create_by as createBy,
|
||||
b.create_time as createTime,
|
||||
b.update_by as updateBy,
|
||||
b.update_time as updateTime
|
||||
FROM xg_stu_col_batch b
|
||||
INNER JOIN xg_stu_col_batch_class bc ON b.id = bc.batch_id AND bc.deleted = 0
|
||||
WHERE bc.class_id IN
|
||||
<foreach collection="deptIds" item="deptId" open="(" separator="," close=")">
|
||||
#{deptId}
|
||||
</foreach>
|
||||
AND b.deleted = 0
|
||||
AND b.status = '0'
|
||||
<if test="batchName != null and batchName != ''">
|
||||
AND b.batch_name LIKE CONCAT('%', #{batchName}, '%')
|
||||
</if>
|
||||
ORDER BY b.start_time DESC, b.create_time DESC
|
||||
</select>
|
||||
|
||||
|
||||
+169
@@ -41,4 +41,173 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
WHERE id = #{userId} AND deleted = 0
|
||||
</update>
|
||||
|
||||
<!-- 根据部门ID查询所有子部门ID列表(包括自己) -->
|
||||
<select id="selectChildDeptIds" parameterType="java.lang.Long" resultType="java.lang.Long">
|
||||
SELECT dept_id
|
||||
FROM sys_dept
|
||||
WHERE (dept_id = #{deptId} OR FIND_IN_SET(#{deptId}, ancestors))
|
||||
AND status = '0'
|
||||
AND del_flag = '0'
|
||||
</select>
|
||||
|
||||
<!-- 根据部门ID列表和条件分页查询学生信息批次数据 -->
|
||||
<select id="selectPageByDeptIds" resultType="org.dromara.xg.domain.vo.XgStudentMsgBatchVo">
|
||||
SELECT
|
||||
xsmb.id,
|
||||
xsmb.deleted,
|
||||
xsmb.student_no,
|
||||
xsmb.id_card,
|
||||
xsmb.name,
|
||||
xsmb.name_english,
|
||||
xsmb.former_name,
|
||||
xsmb.gender,
|
||||
xsmb.birth_date,
|
||||
xsmb.nationality,
|
||||
xsmb.native_place,
|
||||
xsmb.political_status,
|
||||
xsmb.hukou_location,
|
||||
xsmb.home_address,
|
||||
xsmb.current_address,
|
||||
xsmb.postal_code,
|
||||
xsmb.photo_url,
|
||||
xsmb.photo_path,
|
||||
xsmb.phone,
|
||||
xsmb.is_boarder,
|
||||
xsmb.education_level,
|
||||
xsmb.household_register_type,
|
||||
xsmb.religion,
|
||||
xsmb.source_region,
|
||||
xsmb.graduate_school,
|
||||
xsmb.email,
|
||||
xsmb.qq,
|
||||
xsmb.wechat,
|
||||
xsmb.marital_status,
|
||||
xsmb.emergency_contact_name,
|
||||
xsmb.emergency_contact_phone,
|
||||
xsmb.emergency_contact_relation,
|
||||
xsmb.is_poor_student,
|
||||
xsmb.is_minority,
|
||||
xsmb.is_disabled,
|
||||
xsmb.is_orphan,
|
||||
xsmb.is_left_behind_child,
|
||||
xsmb.is_single_child,
|
||||
xsmb.is_low_income,
|
||||
xsmb.has_loan,
|
||||
xsmb.loan_amount,
|
||||
xsmb.specialties,
|
||||
xsmb.awards,
|
||||
xsmb.violations,
|
||||
xsmb.social_activities,
|
||||
xsmb.father_name,
|
||||
xsmb.father_phone,
|
||||
xsmb.father_work_unit,
|
||||
xsmb.mother_name,
|
||||
xsmb.mother_phone,
|
||||
xsmb.mother_work_unit,
|
||||
xsmb.guardian_name,
|
||||
xsmb.guardian_phone,
|
||||
xsmb.guardian_relation,
|
||||
xsmb.family_income,
|
||||
xsmb.per_capita_income,
|
||||
xsmb.income_source,
|
||||
xsmb.family_economic_status,
|
||||
xsmb.family_members,
|
||||
xsmb.height,
|
||||
xsmb.weight,
|
||||
xsmb.blood_type,
|
||||
xsmb.health_status,
|
||||
xsmb.special_condition,
|
||||
xsmb.medical_history,
|
||||
xsmb.allergies,
|
||||
xsmb.disability_type,
|
||||
xsmb.disability_level,
|
||||
xsmb.admission_category,
|
||||
xsmb.student_status,
|
||||
xsmb.enrollment_year,
|
||||
xsmb.enrollment_date,
|
||||
xsmb.bank_card_no,
|
||||
xsmb.bank_name,
|
||||
xsmb.bank_branch,
|
||||
xsmb.card_holder_name,
|
||||
xsmb.info_status,
|
||||
xsmb.data_source,
|
||||
xsmb.last_batch_id,
|
||||
xsmb.last_audit_time,
|
||||
xsmb.is_complete,
|
||||
xsmb.completion_rate,
|
||||
xsmb.school_system,
|
||||
xsmb.academic_level,
|
||||
xsmb.training_type,
|
||||
xsmb.major_id,
|
||||
xsmb.major_name,
|
||||
xsmb.class_id,
|
||||
xsmb.class_name,
|
||||
xsmb.grade,
|
||||
xsmb.college_id,
|
||||
xsmb.college_name,
|
||||
xsmb.counselor_id,
|
||||
xsmb.counselor_name,
|
||||
xsmb.class_teacher_id,
|
||||
xsmb.class_teacher_name,
|
||||
xsmb.dormitory_building,
|
||||
xsmb.dormitory_room,
|
||||
xsmb.ext_field1,
|
||||
xsmb.ext_field2,
|
||||
xsmb.ext_field3,
|
||||
xsmb.ext_field4,
|
||||
xsmb.ext_field5,
|
||||
xsmb.remark,
|
||||
xsmb.batch_id,
|
||||
xsmb.create_by,
|
||||
xsmb.create_time,
|
||||
xsmb.update_by,
|
||||
xsmb.update_time
|
||||
FROM xg_student_msg_batch xsmb
|
||||
WHERE xsmb.batch_id = #{batchId}
|
||||
AND xsmb.deleted = 0
|
||||
<if test="deptIds != null and deptIds.size() > 0">
|
||||
AND xsmb.class_id IN
|
||||
<foreach collection="deptIds" item="deptId" open="(" separator="," close=")">
|
||||
#{deptId}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="name != null and name != ''">
|
||||
AND xsmb.name LIKE CONCAT('%', #{name}, '%')
|
||||
</if>
|
||||
<if test="studentNo != null and studentNo != ''">
|
||||
AND xsmb.student_no LIKE CONCAT('%', #{studentNo} , '%')
|
||||
</if>
|
||||
<if test="phone != null and phone != ''">
|
||||
AND xsmb.phone LIKE CONCAT('%', #{phone}, '%')
|
||||
</if>
|
||||
<if test="infoStatus != null and infoStatus != ''">
|
||||
AND xsmb.info_status = #{infoStatus}
|
||||
</if>
|
||||
ORDER BY xsmb.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 驳回学生信息提交 -->
|
||||
<update id="rejectStudentMsgByBatchIdAndUserId">
|
||||
UPDATE xg_student_msg_batch
|
||||
SET remark = #{remark},
|
||||
info_status = '4',
|
||||
update_by = #{currentUserId},
|
||||
update_time = NOW()
|
||||
WHERE batch_id = #{batchId}
|
||||
AND id = #{userId}
|
||||
AND deleted = 0
|
||||
</update>
|
||||
|
||||
<!-- 审核通过学生信息提交 -->
|
||||
<update id="approveStudentMsgByBatchIdAndUserId">
|
||||
UPDATE xg_student_msg_batch
|
||||
SET remark = #{remark},
|
||||
info_status = '5',
|
||||
update_by = #{currentUserId},
|
||||
update_time = NOW()
|
||||
WHERE batch_id = #{batchId}
|
||||
AND id = #{userId}
|
||||
AND deleted = 0
|
||||
</update>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,198 @@
|
||||
# 学生信息审核通过API接口开发记录
|
||||
|
||||
## 变更基本信息
|
||||
- **变更时间**: 2025-01-27 20:15:30
|
||||
- **变更人员**: cursor
|
||||
- **变更类型**: 新增功能
|
||||
- **影响范围**: 后端API接口
|
||||
|
||||
## 需求描述
|
||||
在`XgStudentMsgBatchController.java`中增加一个API接口`/studentMsgBatch/studentMsgBatch/approveStudentMsg`,用于审核通过学生完善的个人信息。
|
||||
|
||||
## 接口设计
|
||||
|
||||
### API路径
|
||||
```
|
||||
POST /studentMsgBatch/studentMsgBatch/approveStudentMsg
|
||||
```
|
||||
|
||||
### 请求参数
|
||||
- `batchId` (Long): 批次ID,必填
|
||||
- `userId` (Long): 用户ID,必填
|
||||
- `remark` (String): 审核备注,可选
|
||||
|
||||
### 业务逻辑
|
||||
1. **第0步**: 查询并验证要审核的学生信息批次数据
|
||||
2. **校验逻辑**: 参考`XgStudentMsgController.edit`方法
|
||||
- 验证学号作为用户名的唯一性
|
||||
- 验证手机号唯一性(如果提供)
|
||||
- 验证邮箱唯一性(如果提供)
|
||||
3. **第一步**: 更新`xg_student_msg_batch`表
|
||||
- `remark` = 入参的remark
|
||||
- `info_status` = '5' (已审核通过)
|
||||
- `update_time` = 当前时间
|
||||
- `update_by` = 当前登录人ID
|
||||
4. **第二步**: 将`xg_student_msg_batch`的数据更新到`xg_student_msg`表
|
||||
5. **第三步**: 自动更新`sys_user`表(通过`xgStudentMsgService.updateByBo`方法)
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 1. Controller层修改
|
||||
**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgStudentMsgBatchController.java`
|
||||
|
||||
```java
|
||||
/**
|
||||
* 审核通过学生信息提交
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @param userId 用户ID
|
||||
* @param remark 审核备注
|
||||
*/
|
||||
@SaCheckPermission("studentMsgBatch:studentMsgBatch:edit")
|
||||
@Log(title = "审核通过学生信息提交", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PostMapping("/approveStudentMsg")
|
||||
public R<Void> approveStudentMsg(@RequestParam @NotNull(message = "批次ID不能为空") Long batchId,
|
||||
@RequestParam @NotNull(message = "用户ID不能为空") Long userId,
|
||||
@RequestParam(required = false) String remark) {
|
||||
return toAjax(xgStudentMsgBatchService.approveStudentMsg(batchId, userId, remark));
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Service接口层修改
|
||||
**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgStudentMsgBatchService.java`
|
||||
|
||||
```java
|
||||
/**
|
||||
* 审核通过学生信息提交
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @param userId 用户ID
|
||||
* @param remark 审核备注
|
||||
* @return 是否审核通过成功
|
||||
*/
|
||||
Boolean approveStudentMsg(Long batchId, Long userId, String remark);
|
||||
```
|
||||
|
||||
### 3. Service实现层修改
|
||||
**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentMsgBatchServiceImpl.java`
|
||||
|
||||
- 新增依赖注入: `ISysUserService`、`IXgStudentMsgService`
|
||||
- 实现完整的`approveStudentMsg`方法
|
||||
- 包含事务管理、数据校验、异常处理
|
||||
|
||||
### 4. Mapper层修改
|
||||
**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgStudentMsgBatchMapper.java`
|
||||
|
||||
```java
|
||||
/**
|
||||
* 审核通过学生信息提交
|
||||
* 更新 xg_student_msg_batch 表中指定批次和用户的记录
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @param userId 用户ID
|
||||
* @param remark 审核备注
|
||||
* @param currentUserId 当前登录人ID
|
||||
* @return 更新记录数
|
||||
*/
|
||||
int approveStudentMsgByBatchIdAndUserId(@Param("batchId") Long batchId,
|
||||
@Param("userId") Long userId,
|
||||
@Param("remark") String remark,
|
||||
@Param("currentUserId") Long currentUserId);
|
||||
```
|
||||
|
||||
### 5. SQL实现
|
||||
**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/studentMsgBatch/XgStudentMsgBatchMapper.xml`
|
||||
|
||||
```xml
|
||||
<!-- 审核通过学生信息提交 -->
|
||||
<update id="approveStudentMsgByBatchIdAndUserId">
|
||||
UPDATE xg_student_msg_batch
|
||||
SET remark = #{remark},
|
||||
info_status = '5',
|
||||
update_by = #{currentUserId},
|
||||
update_time = NOW()
|
||||
WHERE batch_id = #{batchId}
|
||||
AND id = #{userId}
|
||||
AND deleted = 0
|
||||
</update>
|
||||
```
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 安全性
|
||||
- **权限控制**: `@SaCheckPermission("studentMsgBatch:studentMsgBatch:edit")`
|
||||
- **防重复提交**: `@RepeatSubmit()`
|
||||
- **参数验证**: 必填参数验证
|
||||
|
||||
### 可靠性
|
||||
- **事务管理**: `@Transactional(rollbackFor = Exception.class)`
|
||||
- **数据完整性**: 查询验证 → 更新批次表 → 更新学生表 → 更新用户表
|
||||
- **异常处理**: 完整的try-catch和异常抛出
|
||||
|
||||
### 可维护性
|
||||
- **详细日志**: 记录操作过程和异常信息
|
||||
- **操作记录**: `@Log(title = "审核通过学生信息提交", businessType = BusinessType.UPDATE)`
|
||||
|
||||
## 数据库影响
|
||||
|
||||
### 表结构变更
|
||||
无新增表结构,使用现有表:
|
||||
- `xg_student_msg_batch`: 更新审核状态和备注
|
||||
- `xg_student_msg`: 同步学生信息数据
|
||||
- `sys_user`: 同步用户信息数据
|
||||
|
||||
### 数据状态变更
|
||||
- `info_status`: '3'(审核中) → '5'(已审核通过)
|
||||
|
||||
## 测试建议
|
||||
|
||||
### 功能测试
|
||||
1. **正常流程**: 提供有效的batchId和userId,验证审核通过成功
|
||||
2. **参数验证**: 测试必填参数为空的情况
|
||||
3. **数据验证**: 测试学号、手机号、邮箱重复的情况
|
||||
4. **权限验证**: 测试无权限用户访问的情况
|
||||
|
||||
### 异常测试
|
||||
1. **数据不存在**: 提供不存在的batchId或userId
|
||||
2. **并发测试**: 多用户同时审核同一条记录
|
||||
3. **事务回滚**: 模拟第二步或第三步失败的情况
|
||||
|
||||
## 相关文档
|
||||
|
||||
### API文档更新
|
||||
需要在以下文档中补充新接口说明:
|
||||
- `docs/开发文档/API/学生信息批次收集API.md`
|
||||
- `docs/开发文档/API/API文档总览.md`
|
||||
|
||||
### 相关接口
|
||||
- 驳回接口: `POST /studentMsgBatch/studentMsgBatch/rejectStudentMsg`
|
||||
- 查询接口: `GET /studentMsgBatch/studentMsgBatch/history/{batchId}/{userId}`
|
||||
|
||||
## 验收标准
|
||||
|
||||
### 接口调用成功标准
|
||||
1. 返回状态码200
|
||||
2. 返回数据格式: `{"code": 200, "msg": "操作成功"}`
|
||||
3. 数据库`xg_student_msg_batch`表`info_status`更新为'5'
|
||||
4. `xg_student_msg`表数据同步成功
|
||||
5. `sys_user`表数据同步成功
|
||||
|
||||
### 日志记录标准
|
||||
1. 成功日志: "成功审核通过学生信息提交,批次ID: {}, 用户ID: {}, 操作人: {}, 审核备注: {}"
|
||||
2. 异常日志: "审核通过学生信息提交时发生异常,批次ID: {}, 用户ID: {}, 异常信息: {}"
|
||||
|
||||
## 总结
|
||||
|
||||
成功实现了学生信息审核通过API接口,包含完整的业务逻辑、数据校验、事务管理和异常处理。该接口与现有的驳回接口形成完整的审核流程,为学生信息管理系统提供了完善的审核功能。
|
||||
|
||||
**关键改进点**:
|
||||
1. 参考现有edit方法的校验逻辑,确保数据一致性
|
||||
2. 使用事务管理,确保数据操作的原子性
|
||||
3. 完整的日志记录,便于问题排查和操作审计
|
||||
4. 统一的异常处理和返回格式
|
||||
|
||||
**下一步工作**:
|
||||
1. 补充API文档说明
|
||||
2. 编写接口测试用例
|
||||
3. 前端页面集成测试
|
||||
@@ -0,0 +1,137 @@
|
||||
# 代码调整记录 - 学生信息审核界面优化
|
||||
|
||||
**调整日期**: 2025-01-27 15:25:00
|
||||
**调整人员**: cursor
|
||||
**调整类型**: 功能优化
|
||||
|
||||
## 调整概述
|
||||
|
||||
优化学生信息审核界面,将原本简单的审核弹窗改为完整的学生信息详情弹窗,提供更丰富的审核功能。
|
||||
|
||||
## 具体调整内容
|
||||
|
||||
### 1. 文件调整
|
||||
|
||||
**调整文件**: `web/src/views/stuMsgAudit/auditDialog.vue`
|
||||
|
||||
### 2. 功能调整
|
||||
|
||||
#### 2.1 新增学生信息详情弹窗集成
|
||||
|
||||
1. **动态组件加载**
|
||||
- 使用 `defineAsyncComponent` 动态加载 `stuMsgDialog.vue` 组件
|
||||
- 避免静态导入可能出现的类型错误
|
||||
|
||||
2. **响应式数据新增**
|
||||
```typescript
|
||||
// 学生信息详情弹窗相关数据
|
||||
const stuMsgDialogVisible = ref(false);
|
||||
const stuMsgDialogTitle = ref('学生信息审核');
|
||||
const stuMsgBatchInfo = ref<any>(null);
|
||||
```
|
||||
|
||||
3. **审核按钮逻辑修改**
|
||||
- 原来:打开简单审核表单弹窗
|
||||
- 现在:打开完整学生信息详情弹窗
|
||||
|
||||
```typescript
|
||||
const handleAuditStudent = (row: StudentAuditInfo) => {
|
||||
// 打开学生信息详情弹窗进行审核
|
||||
stuMsgBatchInfo.value = {
|
||||
batchId: props.batchInfo?.id,
|
||||
userId: row.id,
|
||||
...props.batchInfo
|
||||
};
|
||||
stuMsgDialogTitle.value = `学生信息审核 - ${row.name}`;
|
||||
stuMsgDialogVisible.value = true;
|
||||
};
|
||||
```
|
||||
|
||||
4. **新增回调处理方法**
|
||||
```typescript
|
||||
const handleStuMsgConfirm = (data: any) => {
|
||||
console.log('学生信息已更新:', data);
|
||||
ElMessage.success('学生信息已更新');
|
||||
// 刷新学生列表
|
||||
getList();
|
||||
};
|
||||
```
|
||||
|
||||
#### 2.2 组件模板调整
|
||||
|
||||
1. **新增学生信息详情弹窗组件**
|
||||
```vue
|
||||
<!-- 学生信息详情弹窗 -->
|
||||
<component
|
||||
:is="StuMsgDialogComponent"
|
||||
:visible="stuMsgDialogVisible"
|
||||
:title="stuMsgDialogTitle"
|
||||
:batchInfo="stuMsgBatchInfo"
|
||||
@update:visible="stuMsgDialogVisible = $event"
|
||||
@confirm="handleStuMsgConfirm"
|
||||
v-if="StuMsgDialogComponent"
|
||||
/>
|
||||
```
|
||||
|
||||
### 3. 参数传递
|
||||
|
||||
#### 3.1 传入参数
|
||||
|
||||
- **batchId**: 从 `props.batchInfo?.id` 获取
|
||||
- **userId**: 从当前选中学生行 `row.id` 获取
|
||||
- **batchInfo**: 完整批次信息对象
|
||||
|
||||
#### 3.2 参数结构
|
||||
```typescript
|
||||
stuMsgBatchInfo.value = {
|
||||
batchId: props.batchInfo?.id,
|
||||
userId: row.id,
|
||||
...props.batchInfo
|
||||
};
|
||||
```
|
||||
|
||||
## 功能影响
|
||||
|
||||
### 3.1 用户体验优化
|
||||
|
||||
1. **审核功能增强**
|
||||
- 从简单的通过/不通过选择升级为完整学生信息查看和编辑
|
||||
- 提供历史记录查看功能
|
||||
- 支持多个信息tab页(基础信息、扩展信息、家庭信息等)
|
||||
|
||||
2. **操作流程改进**
|
||||
- 点击审核按钮 → 打开学生详情弹窗
|
||||
- 可查看和编辑完整学生信息
|
||||
- 确认后自动刷新学生列表
|
||||
|
||||
### 3.2 技术优化
|
||||
|
||||
1. **组件复用**
|
||||
- 复用现有的 `stuMsgDialog.vue` 组件
|
||||
- 避免重复开发相似功能
|
||||
|
||||
2. **错误处理**
|
||||
- 使用动态组件加载避免导入错误
|
||||
- 添加条件渲染确保组件正常加载
|
||||
|
||||
## 测试建议
|
||||
|
||||
1. **功能测试**
|
||||
- [ ] 点击审核按钮能正常打开学生信息详情弹窗
|
||||
- [ ] 弹窗中能正确显示学生信息和历史记录
|
||||
- [ ] 提交后能正确刷新学生列表
|
||||
|
||||
2. **参数传递测试**
|
||||
- [ ] batchId 正确传递
|
||||
- [ ] 学生信息正确显示
|
||||
- [ ] 批次信息完整传递
|
||||
|
||||
3. **异常处理测试**
|
||||
- [ ] 组件加载失败时的降级处理
|
||||
- [ ] 参数缺失时的错误处理
|
||||
|
||||
## 备注
|
||||
|
||||
- 保留了原有的简单审核弹窗代码(注释状态),必要时可以快速回退
|
||||
- 新功能完全兼容现有的审核流程
|
||||
- 建议在生产环境部署前进行充分测试
|
||||
@@ -0,0 +1,202 @@
|
||||
# 代码调整记录 - 2025-01-27 - audit-dialog
|
||||
|
||||
## 2025-01-27 19:00:00 - 调整审核对话框右侧表格高度和滚动条
|
||||
|
||||
### 变更内容
|
||||
在学生信息审核对话框(`web/src/views/stuMsgAudit/auditDialog.vue`)中调整右侧表格的高度,为其添加纵向滚动条,确保表格内容能够充分显示。
|
||||
|
||||
### 具体修改
|
||||
1. **右侧面板布局优化**:
|
||||
- 为右侧面板设置固定高度`600px`和`overflow: hidden`
|
||||
- 搜索表单区域设置为`flex-shrink: 0`,防止被压缩
|
||||
|
||||
2. **表格卡片容器调整**:
|
||||
- 设置表格卡片为`flex: 1`,自动占据剩余空间
|
||||
- 卡片内容区域使用flex布局:`display: flex; flex-direction: column`
|
||||
|
||||
3. **表格样式优化**:
|
||||
- 为表格添加类名`right-table`
|
||||
- 设置表格高度为`420px`
|
||||
- 表格体容器最大高度`350px`,超出显示垂直滚动条
|
||||
|
||||
4. **空间分配优化**:
|
||||
- 搜索表单区域:约100px
|
||||
- 表格区域:420px(含表头、表体和分页)
|
||||
- 分页组件:约50px
|
||||
- 间距和边距:约30px
|
||||
|
||||
### 变更文件
|
||||
- `web/src/views/stuMsgAudit/auditDialog.vue`(样式和模板部分)
|
||||
|
||||
### 效果说明
|
||||
- ✅ 右侧表格有固定高度和垂直滚动条
|
||||
- ✅ 表格充分利用右侧面板空间
|
||||
- ✅ 搜索表单不被压缩,保持正常显示
|
||||
- ✅ 分页组件正常显示在表格下方
|
||||
- ✅ 整体布局协调,左右两侧高度一致
|
||||
|
||||
### 测试验证
|
||||
- ✅ 表格数据多时显示垂直滚动条
|
||||
- ✅ 表格高度固定,不影响整体弹窗布局
|
||||
- ✅ 搜索功能正常工作
|
||||
- ✅ 分页功能正常工作
|
||||
|
||||
---
|
||||
|
||||
## 2025-01-27 18:45:00 - 固定审核对话框左侧部门树高度
|
||||
|
||||
### 变更内容
|
||||
在学生信息审核对话框(`web/src/views/stuMsgAudit/auditDialog.vue`)中将左侧部门树设置为固定高度,防止树展开收起时改变整个弹窗的高度。
|
||||
|
||||
### 具体修改
|
||||
1. **弹窗容器固定高度**:
|
||||
- 将弹窗高度从`90vh`改为固定`700px`
|
||||
- 设置弹窗内容区域高度为`calc(100% - 120px)`
|
||||
|
||||
2. **主布局容器固定**:
|
||||
- 将`.layout-container`高度从`100%`改为固定`600px`
|
||||
- 添加`overflow: hidden`防止内容溢出
|
||||
|
||||
3. **左侧面板固定尺寸**:
|
||||
- 卡片容器设置固定高度`600px`
|
||||
- 左侧面板设置`overflow: hidden`
|
||||
|
||||
4. **部门树固定高度**:
|
||||
- 将部门树高度设置为固定`520px`
|
||||
- 设置`overflow-y: auto`和`overflow-x: hidden`
|
||||
- 添加边框样式使其更清晰
|
||||
|
||||
5. **树节点样式优化**:
|
||||
- 移除强制宽度设置,使用`overflow: hidden`和`text-overflow: ellipsis`
|
||||
- 长文本使用省略号显示,避免横向溢出
|
||||
|
||||
6. **响应式适配调整**:
|
||||
- 小屏幕下左侧卡片高度`400px`,部门树高度`320px`
|
||||
|
||||
### 变更文件
|
||||
- `web/src/views/stuMsgAudit/auditDialog.vue`(样式部分)
|
||||
|
||||
### 效果说明
|
||||
- ✅ 弹窗整体高度固定,不会因为树展开收起而变化
|
||||
- ✅ 部门树有固定高度和垂直滚动条
|
||||
- ✅ 长文本使用省略号显示,避免布局错乱
|
||||
- ✅ 响应式设计保持正常
|
||||
|
||||
### 测试验证
|
||||
- ✅ 树展开收起时弹窗高度保持不变
|
||||
- ✅ 部门树显示垂直滚动条
|
||||
- ✅ 长文本正确显示省略号
|
||||
- ✅ 响应式布局正常
|
||||
|
||||
---
|
||||
|
||||
## 2025-01-27 18:30:00 - 调整审核对话框左侧部门树高度
|
||||
|
||||
### 变更内容
|
||||
在学生信息审核对话框(`web/src/views/stuMsgAudit/auditDialog.vue`)中调整左侧部门树的高度设置,使其充满整个左侧面板。
|
||||
|
||||
### 具体修改
|
||||
1. **左侧面板容器调整**:
|
||||
- 为`.left-panel`添加`height: 100%`属性
|
||||
- 为卡片容器设置`display: flex; flex-direction: column`布局
|
||||
|
||||
2. **卡片内容区域调整**:
|
||||
- 新增`.left-panel .el-card :deep(.el-card__body)`样式
|
||||
- 设置flex布局和间距:`flex: 1; display: flex; flex-direction: column; gap: 10px`
|
||||
|
||||
3. **部门树高度调整**:
|
||||
- 将原来的固定高度`height: 500px`改为`flex: 1`
|
||||
- 确保部门树自动占据剩余空间
|
||||
|
||||
4. **响应式适配**:
|
||||
- 小屏幕下保持相同的flex布局模式
|
||||
- 将`.left-panel`高度从`auto`改为固定`400px`
|
||||
|
||||
### 变更文件
|
||||
- `web/src/views/stuMsgAudit/auditDialog.vue`(样式部分)
|
||||
|
||||
### 效果说明
|
||||
- 部门树现在会自动占据左侧面板的全部可用高度
|
||||
- 与搜索框保持适当间距(10px gap)
|
||||
- 响应式设计在小屏幕下仍然有效
|
||||
- 解决了原来部门树高度偏小、离底部有距离的问题
|
||||
|
||||
### 测试验证
|
||||
- ✅ 部门树高度充满左侧面板
|
||||
- ✅ 搜索框与部门树间距合适
|
||||
- ✅ 滚动条正常工作
|
||||
- ✅ 响应式布局正常
|
||||
|
||||
---
|
||||
|
||||
**时间**: 2025-01-27 17:42:15
|
||||
**操作人**: cursor
|
||||
**功能模块**: 学生信息审核界面 - 部门树查询
|
||||
|
||||
## 调整内容
|
||||
|
||||
### 1. 修改 web/src/views/stuMsgAudit/auditDialog.vue
|
||||
|
||||
**修改目标**: 将 `getDeptTree` 方法中的API请求方式从 `api.XgDeptTreeSelect(101)` 替换为使用 `request` 方式调用后端 `XgStuColBatchClassController.getBatchAuthScope` 方法
|
||||
|
||||
**具体变更**:
|
||||
|
||||
1. **替换API调用方式**:
|
||||
- **修改前**: `const res = await api.XgDeptTreeSelect(101);`
|
||||
- **修改后**: `const res = await request({ url: '/stuColBatchClass/stuColBatchClass/getBatchAuthScope/${props.batchInfo.id}', method: 'get' });`
|
||||
|
||||
2. **新增批次ID检查**:
|
||||
```javascript
|
||||
if (!props.batchInfo?.id) {
|
||||
console.warn('批次ID不存在,无法获取部门树');
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
3. **优化数据处理逻辑**:
|
||||
- 检查 `res.code === 200` 确保接口调用成功
|
||||
- 简化错误处理,统一设置空数组 `deptOptions.value = []`
|
||||
|
||||
4. **移除无效注释代码**:
|
||||
- 删除了所有注释掉的 `api.deptTreeSelect()` 相关备用代码
|
||||
|
||||
**技术说明**:
|
||||
- 调用的后端接口: `/stuColBatchClass/stuColBatchClass/getBatchAuthScope/{batchId}`
|
||||
- 接口功能: 获取用户在指定批次的组织权限范围
|
||||
- 返回数据格式: `R<List<XgStudentManagerDeptTreeVo>>`
|
||||
- 参数来源: `props.batchInfo.id` (批次ID)
|
||||
|
||||
**解决的问题**:
|
||||
1. ✅ 修复了 linter 错误: `Cannot find name 'api'`
|
||||
2. ✅ 将部门树查询与具体批次关联,实现权限范围过滤
|
||||
3. ✅ 统一了前端请求方式,使用标准的 `request` 工具
|
||||
|
||||
## 影响范围
|
||||
|
||||
**前端**:
|
||||
- `web/src/views/stuMsgAudit/auditDialog.vue` - 部门树查询逻辑
|
||||
|
||||
**后端**:
|
||||
- 依赖 `XgStuColBatchClassController.getBatchAuthScope` 方法
|
||||
- 依赖 `xgStudentManagerSourceService.getCurrentUserAuthDeptTree()` 获取用户权限
|
||||
|
||||
## 测试建议
|
||||
|
||||
1. **功能测试**:
|
||||
- 打开学生信息审核界面
|
||||
- 验证部门树是否正确加载
|
||||
- 确认部门树数据与批次权限范围一致
|
||||
|
||||
2. **权限测试**:
|
||||
- 使用不同权限的用户登录
|
||||
- 验证部门树显示的数据符合用户权限范围
|
||||
|
||||
3. **异常测试**:
|
||||
- 测试无效批次ID的处理
|
||||
- 测试网络异常的错误处理
|
||||
|
||||
## 备注
|
||||
|
||||
- 此修改使部门树查询更加精确,只显示用户在当前批次有权限的组织范围
|
||||
- 提升了界面的安全性和用户体验
|
||||
- 符合项目的权限管理规范
|
||||
@@ -0,0 +1,93 @@
|
||||
# 代码调整记录 - 2025-01-27 - cursor
|
||||
|
||||
## 修复审核通过功能MapstructUtils转换异常 - 2025-01-27 01:15:00
|
||||
|
||||
### 问题描述
|
||||
学生信息批次管理审核通过功能出现异常:
|
||||
```
|
||||
Caused by: io.github.linpeilie.ConvertException: cannot find converter from XgStudentMsgBatchVo to XgStudentMsgBo
|
||||
```
|
||||
|
||||
### 问题原因
|
||||
在`XgStudentMsgBatchServiceImpl.approveStudentMsg`方法中,使用`MapstructUtils.convert`将`XgStudentMsgBatchVo`转换为`XgStudentMsgBo`时,系统找不到对应的转换器配置。
|
||||
|
||||
### 解决方案
|
||||
将MapstructUtils自动转换替换为手动字段转换:
|
||||
|
||||
**修改文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentMsgBatchServiceImpl.java`
|
||||
|
||||
**修改内容**:
|
||||
- 第980-983行:将`MapstructUtils.convert(batchVo, XgStudentMsgBo.class)`
|
||||
- 替换为:手动创建`XgStudentMsgBo`对象并逐个字段复制
|
||||
- 包含所有业务字段:基础信息、联系人信息、特殊情况标记、家庭信息、健康信息、学籍信息、银行信息、扩展字段等
|
||||
|
||||
### 修复方式
|
||||
参考代码中已有的`getUserHistoryData`方法中从`XgStudentMsgVo`手动转换为`XgStudentMsgBatchVo`的方式,实现反向转换。
|
||||
|
||||
### 验证结果
|
||||
修复后审核通过功能可以正常执行,不再出现类型转换异常。
|
||||
|
||||
### 影响范围
|
||||
- 学生信息批次管理 - 审核通过功能
|
||||
- 确保批次数据正确同步到主表
|
||||
|
||||
---
|
||||
|
||||
## 修改时间
|
||||
2025-01-27 16:45:30
|
||||
|
||||
## 修改内容
|
||||
|
||||
### 修改前端驳回学生信息API调用方式
|
||||
|
||||
**修改文件**: `web/src/views/stuMsgAudit/stuMsgDialog.vue`
|
||||
|
||||
**修改原因**:
|
||||
后端 `XgStudentMsgBatchController.java` 的 `rejectStudentMsg` 方法使用 `@RequestParam` 注解接收参数,需要前端以表单参数形式发送数据,而不是JSON请求体。
|
||||
|
||||
**具体变更**:
|
||||
|
||||
```javascript
|
||||
// 修改前 - 以JSON请求体形式发送
|
||||
const response = await request.post('/studentMsgBatch/studentMsgBatch/rejectStudentMsg', rejectData);
|
||||
|
||||
// 修改后 - 以表单参数形式发送
|
||||
const response = await request.post('/studentMsgBatch/studentMsgBatch/rejectStudentMsg', null, {
|
||||
params: {
|
||||
batchId: rejectData.batchId,
|
||||
userId: rejectData.userId,
|
||||
remark: rejectData.remark
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**后端方法签名**:
|
||||
```java
|
||||
@PostMapping("/rejectStudentMsg")
|
||||
public R<Void> rejectStudentMsg(@RequestParam @NotNull(message = "批次ID不能为空") Long batchId,
|
||||
@RequestParam @NotNull(message = "用户ID不能为空") Long userId,
|
||||
@RequestParam @NotBlank(message = "驳回原因不能为空") String remark)
|
||||
```
|
||||
|
||||
**影响范围**:
|
||||
- 学生信息审核页面的驳回功能
|
||||
- 确保前后端参数传递方式一致
|
||||
|
||||
**测试要点**:
|
||||
- 验证驳回功能能正常工作
|
||||
- 确认参数正确传递到后端
|
||||
- 检查错误处理和用户提示
|
||||
|
||||
## 技术说明
|
||||
|
||||
### @RequestParam vs @RequestBody 区别
|
||||
- `@RequestParam`: 接收表单参数或查询参数
|
||||
- `@RequestBody`: 接收JSON格式的请求体
|
||||
|
||||
### 前端发送方式对应
|
||||
- 表单参数: `{ params: { key: value } }`
|
||||
- JSON请求体: 直接传递对象作为第二个参数
|
||||
|
||||
## 相关文件
|
||||
- `web/src/views/stuMsgAudit/stuMsgDialog.vue` (前端调用)
|
||||
- `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgStudentMsgBatchController.java` (后端接口)
|
||||
@@ -0,0 +1,124 @@
|
||||
# 学生信息批次管理API - 新增按部门查询功能
|
||||
|
||||
## 🔧 变更信息
|
||||
- **变更日期**: 2025-01-27 17:30:00
|
||||
- **变更类型**: 功能新增
|
||||
- **变更范围**: 后端API接口
|
||||
- **影响模块**: 学工管理 - 学生信息批次收集
|
||||
|
||||
## 📋 变更内容
|
||||
|
||||
### 新增API接口
|
||||
- **接口路径**: `GET /studentMsgBatch/studentMsgBatch/listByDept`
|
||||
- **接口功能**: 根据部门ID查询该组织下所有已完善信息的学生数据
|
||||
- **权限要求**: `studentMsgBatch:studentMsgBatch:query`
|
||||
|
||||
### 接口参数
|
||||
#### 入参
|
||||
- `batchId` (Long, 必填): 批次ID
|
||||
- `deptId` (Long, 必填): 部门ID
|
||||
- `name` (String, 可选): 学生姓名(模糊查询)
|
||||
- `studentNo` (String, 可选): 学号(精确查询)
|
||||
- `phone` (String, 可选): 手机号码(精确查询)
|
||||
- `infoStatus` (String, 可选): 信息状态
|
||||
- `pageQuery` (PageQuery): 分页查询参数
|
||||
|
||||
#### 出参
|
||||
- `TableDataInfo<XgStudentMsgBatchVo>`: 分页的学生信息批次数据
|
||||
|
||||
### 业务逻辑
|
||||
1. 根据 `deptId` 在 `sys_dept` 表中查询该部门及其所有子部门的ID列表
|
||||
2. 在 `xg_student_msg_batch` 表中查询:
|
||||
- `batch_id = 入参的batchId`
|
||||
- `deleted = 0`
|
||||
- `class_id IN 部门ID列表`(因为班级ID等于部门ID)
|
||||
- 支持按姓名、学号、手机号、信息状态等条件过滤
|
||||
- 分页查询
|
||||
|
||||
## 🔨 技术实现
|
||||
|
||||
### 1. Controller层
|
||||
**文件**: `XgStudentMsgBatchController.java`
|
||||
```java
|
||||
@SaCheckPermission("studentMsgBatch:studentMsgBatch:query")
|
||||
@GetMapping("/listByDept")
|
||||
public TableDataInfo<XgStudentMsgBatchVo> listByDeptId(
|
||||
@RequestParam(required = true) Long batchId,
|
||||
@RequestParam(required = true) Long deptId,
|
||||
@RequestParam(required = false) String name,
|
||||
@RequestParam(required = false) String studentNo,
|
||||
@RequestParam(required = false) String phone,
|
||||
@RequestParam(required = false) String infoStatus,
|
||||
PageQuery pageQuery)
|
||||
```
|
||||
|
||||
### 2. Service层
|
||||
**文件**: `IXgStudentMsgBatchService.java` 和 `XgStudentMsgBatchServiceImpl.java`
|
||||
- 新增接口方法: `queryPageListByDeptId()`
|
||||
- 实现部门子节点查询和学生数据分页查询逻辑
|
||||
|
||||
### 3. Mapper层
|
||||
**文件**: `XgStudentMsgBatchMapper.java` 和 `XgStudentMsgBatchMapper.xml`
|
||||
|
||||
#### 新增Mapper方法
|
||||
1. `selectChildDeptIds(Long deptId)`: 查询部门及其所有子部门ID
|
||||
2. `selectPageByDeptIds()`: 根据部门ID列表分页查询学生数据
|
||||
|
||||
#### SQL实现
|
||||
```sql
|
||||
-- 查询部门及子部门
|
||||
SELECT dept_id
|
||||
FROM sys_dept
|
||||
WHERE (dept_id = #{deptId} OR FIND_IN_SET(#{deptId}, ancestors))
|
||||
AND status = '0' AND del_flag = '0'
|
||||
|
||||
-- 分页查询学生数据
|
||||
SELECT [完整字段列表]
|
||||
FROM xg_student_msg_batch xsmb
|
||||
WHERE xsmb.batch_id = #{batchId}
|
||||
AND xsmb.deleted = 0
|
||||
AND xsmb.class_id IN (部门ID列表)
|
||||
[可选条件过滤]
|
||||
ORDER BY xsmb.create_time DESC
|
||||
```
|
||||
|
||||
## 📊 数据流程
|
||||
```
|
||||
用户请求 → Controller接收参数 → Service查询部门子节点 → Mapper执行SQL → 返回分页数据
|
||||
```
|
||||
|
||||
## 🧪 测试说明
|
||||
|
||||
### 测试场景
|
||||
1. **正常查询**: 传入有效的批次ID和部门ID,验证能正确返回该部门及子部门下的学生数据
|
||||
2. **条件过滤**: 验证姓名模糊查询、学号精确查询等过滤条件
|
||||
3. **分页功能**: 验证分页参数的正确性
|
||||
4. **边界情况**: 传入不存在的部门ID,验证返回空结果
|
||||
5. **权限验证**: 验证无权限用户访问被拒绝
|
||||
|
||||
### 测试参数示例
|
||||
```
|
||||
GET /studentMsgBatch/studentMsgBatch/listByDept?batchId=1&deptId=101&name=张&pageNum=1&pageSize=10
|
||||
```
|
||||
|
||||
## 🔗 相关文档
|
||||
- **API文档**: `docs/开发文档/API/学生信息收集批次管理API.md`
|
||||
- **数据库设计**: `docs/设计文档/database/学工管理-学生信息管理/`
|
||||
- **业务需求**: `docs/需求文档/学工管理学生信息管理需求/`
|
||||
|
||||
## ⚠️ 注意事项
|
||||
1. 该接口依赖于班级与部门的关联关系(班级ID = 部门ID)
|
||||
2. 部门查询使用 `FIND_IN_SET` 函数查找祖先节点
|
||||
3. 分页查询支持多种条件组合过滤
|
||||
4. 返回数据包含学生的完整信息字段
|
||||
|
||||
## ✅ 测试状态
|
||||
- [ ] 单元测试
|
||||
- [ ] 集成测试
|
||||
- [ ] 功能测试
|
||||
- [ ] 性能测试
|
||||
|
||||
---
|
||||
**变更完成时间**: 2025-01-27 17:30:00
|
||||
**变更人员**: Cursor AI
|
||||
**审核状态**: 待测试验证
|
||||
@@ -0,0 +1,108 @@
|
||||
# 代码调整记录 - 2025-01-27 - 部门树API调用修复
|
||||
|
||||
**时间**: 2025-01-27 17:45:00
|
||||
**操作人**: cursor
|
||||
**功能模块**: 学生信息审核界面 - 部门树查询修复
|
||||
|
||||
## 调整背景
|
||||
|
||||
在学生信息审核弹窗 `auditDialog.vue` 中存在以下问题:
|
||||
1. Linter 错误:`Cannot find name 'api'`
|
||||
2. 导入模块错误:`Cannot find module '@/utils/request'`
|
||||
3. 部门树API调用方式需要统一
|
||||
|
||||
## 调整内容
|
||||
|
||||
### 修改文件:web/src/views/stuMsgAudit/auditDialog.vue
|
||||
|
||||
**修改目标**: 将 `getDeptTree` 方法中的API请求方式替换为标准的 `request` 方式,调用后端 `XgStuColBatchClassController.getBatchAuthScope` 方法
|
||||
|
||||
#### 1. API调用方式替换
|
||||
|
||||
**修改前**:
|
||||
```javascript
|
||||
const res = await api.XgDeptTreeSelect(101);
|
||||
```
|
||||
|
||||
**修改后**:
|
||||
```javascript
|
||||
const res = await request({
|
||||
url: `/stuColBatchClass/stuColBatchClass/getBatchAuthScope/${props.batchInfo.id}`,
|
||||
method: 'get'
|
||||
});
|
||||
```
|
||||
|
||||
#### 2. 新增批次ID验证
|
||||
|
||||
```javascript
|
||||
if (!props.batchInfo?.id) {
|
||||
console.warn('批次ID不存在,无法获取部门树');
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. 优化数据处理逻辑
|
||||
|
||||
- 检查 `res.code === 200` 确保接口调用成功
|
||||
- 简化错误处理,统一设置空数组 `deptOptions.value = []`
|
||||
- 移除所有注释掉的备用代码
|
||||
|
||||
## 技术说明
|
||||
|
||||
### 调用的后端接口
|
||||
- **接口路径**: `/stuColBatchClass/stuColBatchClass/getBatchAuthScope/{batchId}`
|
||||
- **请求方法**: GET
|
||||
- **参数**: batchId (路径参数,来源于 props.batchInfo.id)
|
||||
- **返回格式**: `R<List<XgStudentManagerDeptTreeVo>>`
|
||||
|
||||
### 接口功能
|
||||
- 获取用户在指定批次的组织权限范围
|
||||
- 基于当前用户权限和批次配置进行过滤
|
||||
- 返回树形结构的部门数据
|
||||
|
||||
## 解决的问题
|
||||
|
||||
1. ✅ **修复 Linter 错误**: 移除了对未定义的 `api` 对象的引用
|
||||
2. ✅ **统一请求方式**: 使用项目标准的 `request` 工具
|
||||
3. ✅ **权限范围精确化**: 部门树数据与具体批次权限关联
|
||||
4. ✅ **增强错误处理**: 添加批次ID检查和更好的异常处理
|
||||
|
||||
## 影响范围
|
||||
|
||||
### 前端影响
|
||||
- `web/src/views/stuMsgAudit/auditDialog.vue` - 部门树查询逻辑
|
||||
|
||||
### 后端依赖
|
||||
- `XgStuColBatchClassController.getBatchAuthScope()` 方法
|
||||
- `xgStudentManagerSourceService.getCurrentUserAuthDeptTree()` 权限服务
|
||||
- `xgStuColBatchClassMapper.selectClassIdsByBatchId()` 数据查询
|
||||
|
||||
## 测试验证
|
||||
|
||||
### 功能测试
|
||||
1. 打开学生信息审核界面,验证部门树正常加载
|
||||
2. 确认部门树数据与批次权限范围一致
|
||||
3. 测试部门树节点选择和数据筛选
|
||||
|
||||
### 权限测试
|
||||
1. 使用不同权限用户登录测试
|
||||
2. 验证部门树显示符合用户权限范围
|
||||
3. 确认跨批次权限隔离正确
|
||||
|
||||
### 异常测试
|
||||
1. 测试无效批次ID的处理
|
||||
2. 测试网络异常的错误处理
|
||||
3. 验证控制台日志输出
|
||||
|
||||
## 优化效果
|
||||
|
||||
1. **代码质量提升**: 消除了 Linter 错误和类型检查问题
|
||||
2. **安全性增强**: 部门树数据与用户权限和批次权限精确关联
|
||||
3. **用户体验改善**: 只显示有权限访问的组织范围
|
||||
4. **维护性提升**: 统一了项目的API调用方式
|
||||
|
||||
## 后续建议
|
||||
|
||||
1. 在其他类似组件中也统一使用 `request` 方式
|
||||
2. 考虑将部门树查询逻辑提取为可复用的 composable
|
||||
3. 添加部门树数据缓存机制提升性能
|
||||
@@ -0,0 +1,201 @@
|
||||
# 代码调整记录 - 学生信息审核对话框增加驳回按钮
|
||||
|
||||
**调整时间**: 2025-01-27 17:35:00
|
||||
**调整人员**: cursor
|
||||
**调整类型**: 功能增强
|
||||
|
||||
## 调整概述
|
||||
|
||||
在学生信息审核对话框(stuMsgDialog.vue)中,在确定按钮后新增了驳回按钮,为审核人员提供驳回学生信息的功能。
|
||||
|
||||
## 具体调整内容
|
||||
|
||||
### 1. 前端界面调整
|
||||
|
||||
**文件**: `web/src/views/stuMsgAudit/stuMsgDialog.vue`
|
||||
|
||||
#### 1.1 增加驳回按钮
|
||||
- **位置**: 对话框footer部分,确定按钮后
|
||||
- **样式**: danger类型的红色按钮
|
||||
- **文本**: "驳 回"
|
||||
- **加载状态**: 与确定按钮共享loading状态
|
||||
|
||||
#### 1.2 按钮布局调整
|
||||
```html
|
||||
<!-- 调整前 -->
|
||||
<el-button type="primary" @click="submitForm" :loading="loading">确 定</el-button>
|
||||
<el-button @click="handleClose">取 消</el-button>
|
||||
|
||||
<!-- 调整后 -->
|
||||
<el-button type="primary" @click="submitForm" :loading="loading">确 定</el-button>
|
||||
<el-button type="danger" @click="handleReject" :loading="loading">驳 回</el-button>
|
||||
<el-button @click="handleClose">取 消</el-button>
|
||||
```
|
||||
|
||||
### 2. 驳回功能逻辑
|
||||
|
||||
#### 2.1 新增 handleReject 方法
|
||||
- **功能**: 处理学生信息驳回操作
|
||||
- **确认机制**: 弹出确认对话框,防止误操作
|
||||
- **数据提交**: 调用后端API `/studentMsgBatch/studentMsgBatch/rejectStudentMsg`
|
||||
- **参数内容**:
|
||||
```javascript
|
||||
{
|
||||
batchId: props.batchInfo?.batchId || props.batchInfo?.id,
|
||||
userId: props.batchInfo?.userId,
|
||||
remark: '信息审核不通过,请重新填写'
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 错误处理机制
|
||||
- **用户取消**: 静默处理,不显示错误信息
|
||||
- **网络错误**: 显示具体错误信息或默认提示
|
||||
- **成功驳回**: 显示成功消息并关闭对话框
|
||||
|
||||
#### 2.3 交互流程
|
||||
1. 点击驳回按钮
|
||||
2. 弹出确认对话框:"确定要驳回这条学生信息吗?"
|
||||
3. 用户确认后显示loading状态
|
||||
4. 调用后端API处理驳回
|
||||
5. 成功后显示成功消息并关闭对话框
|
||||
6. 触发confirm事件通知父组件刷新数据
|
||||
|
||||
## 技术实现要点
|
||||
|
||||
### 1. 用户体验优化
|
||||
- 使用红色danger按钮明确标识驳回操作的危险性
|
||||
- 添加确认对话框防止误操作
|
||||
- 与确定按钮共享loading状态,确保操作互斥
|
||||
|
||||
### 2. 代码复用
|
||||
- 复用现有的loading状态管理
|
||||
- 复用现有的错误处理机制
|
||||
- 复用现有的对话框关闭逻辑
|
||||
|
||||
### 3. 数据流管理
|
||||
- 通过emit触发confirm事件通知父组件
|
||||
- 传递操作结果数据供父组件处理
|
||||
- 保持与现有确定操作的一致性
|
||||
|
||||
## 测试建议
|
||||
|
||||
### 1. 功能测试
|
||||
- [ ] 点击驳回按钮能正常弹出驳回原因输入框
|
||||
- [ ] 输入框显示默认驳回原因:"信息审核不通过,请重新填写"
|
||||
- [ ] 用户可以修改默认的驳回原因
|
||||
- [ ] 确认驳回后能正常调用后端API并传递用户输入的原因
|
||||
- [ ] 驳回成功后能正常关闭对话框
|
||||
- [ ] 驳回成功后父组件能正常刷新数据
|
||||
|
||||
### 2. 输入验证测试
|
||||
- [ ] 驳回原因为空时显示验证错误:"驳回原因不能为空"
|
||||
- [ ] 驳回原因超过200字符时显示验证错误
|
||||
- [ ] 验证错误时不能提交,必须修正后才能继续
|
||||
- [ ] 只输入空格时的验证处理
|
||||
|
||||
### 3. 异常测试
|
||||
- [ ] 用户取消驳回操作的处理
|
||||
- [ ] 网络异常时的错误提示
|
||||
- [ ] 后端返回错误时的处理
|
||||
|
||||
### 4. 界面测试
|
||||
- [ ] 驳回按钮的位置和样式正确
|
||||
- [ ] loading状态显示正常
|
||||
- [ ] 驳回原因输入框弹窗样式和文本正确
|
||||
- [ ] 输入框为多行文本框(textarea)类型
|
||||
- [ ] 弹窗标题显示为"驳回学生信息"
|
||||
- [ ] 确认按钮文本显示为"确定驳回"
|
||||
|
||||
## 影响范围
|
||||
|
||||
### 直接影响
|
||||
- `web/src/views/stuMsgAudit/stuMsgDialog.vue` - 增加驳回按钮和处理逻辑
|
||||
|
||||
### 间接影响
|
||||
- 需要后端提供对应的驳回API接口
|
||||
- 父组件可能需要处理驳回操作的回调
|
||||
|
||||
## 备注
|
||||
|
||||
1. 当前驳回备注为固定文本:"信息审核不通过,请重新填写"
|
||||
2. 后续可考虑支持自定义驳回理由
|
||||
3. 驳回按钮与确定按钮共享loading状态,确保操作互斥
|
||||
4. 按钮顺序:确定 → 驳回 → 取消,符合用户操作习惯
|
||||
|
||||
## 优化更新记录
|
||||
|
||||
### 🔄 2025-01-27 17:45:00 - 驳回原因输入框优化
|
||||
|
||||
#### 优化内容
|
||||
在驳回功能基础上,将简单的确认弹窗升级为可填写驳回原因的输入框弹窗。
|
||||
|
||||
#### 具体实现
|
||||
1. **弹窗类型变更**:
|
||||
- 从 `proxy.$confirm` 更改为 `proxy.$prompt`
|
||||
- 支持多行文本输入 (`inputType: 'textarea'`)
|
||||
|
||||
2. **默认值设置**:
|
||||
- 默认驳回原因:`"信息审核不通过,请重新填写"`
|
||||
- 用户可直接使用或修改该默认值
|
||||
|
||||
3. **输入验证机制**:
|
||||
```javascript
|
||||
inputValidator: (value: string) => {
|
||||
if (!value || value.trim() === '') {
|
||||
return '驳回原因不能为空';
|
||||
}
|
||||
if (value.trim().length > 200) {
|
||||
return '驳回原因不能超过200个字符';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
4. **界面优化**:
|
||||
- 弹窗标题:`"驳回学生信息"`
|
||||
- 提示文本:`"请填写驳回原因:"`
|
||||
- 按钮文本:`"确定驳回"` / `"取消"`
|
||||
- 占位符:`"请输入驳回原因"`
|
||||
|
||||
#### 用户体验提升
|
||||
- ✅ 支持自定义驳回原因,提高审核质量
|
||||
- ✅ 默认值减少输入工作量
|
||||
- ✅ 输入验证确保数据有效性
|
||||
- ✅ 多行文本框支持详细说明
|
||||
|
||||
### 🔧 2025-01-27 18:00:00 - 修复 proxy.$prompt 方法不存在问题
|
||||
|
||||
#### 问题描述
|
||||
在Vue 3 + Element Plus环境中,`proxy.$prompt` 方法不存在,导致点击驳回按钮时报错:`proxy.$prompt is not a function`
|
||||
|
||||
#### 解决方案
|
||||
1. **添加正确的导入**:
|
||||
```javascript
|
||||
import { ElMessageBox } from 'element-plus/es/components/message-box/index';
|
||||
```
|
||||
|
||||
2. **替换方法调用**:
|
||||
- 从 `proxy.$prompt` 更改为 `ElMessageBox.prompt`
|
||||
- 保持所有参数和配置不变
|
||||
|
||||
3. **优化错误处理**:
|
||||
```javascript
|
||||
// 优化取消操作的错误处理
|
||||
if (error === 'cancel' || error === 'close') {
|
||||
return; // 用户取消操作
|
||||
}
|
||||
```
|
||||
|
||||
#### 修复结果
|
||||
- ✅ 驳回按钮点击正常,弹出输入框
|
||||
- ✅ 输入验证功能正常工作
|
||||
- ✅ 取消操作正确处理
|
||||
- ✅ 驳回流程完整可用
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
1. ~~**自定义驳回理由**: 点击驳回时弹出输入框让用户填写具体的驳回理由~~ ✅ **已完成**
|
||||
2. **权限控制**: 根据用户角色显示或隐藏驳回按钮
|
||||
3. **操作日志**: 记录详细的驳回操作日志
|
||||
4. **消息通知**: 驳回后向学生发送消息通知
|
||||
5. **驳回原因模板**: 提供常用驳回原因的快捷选择
|
||||
@@ -0,0 +1,176 @@
|
||||
# 学生信息批次管理-驳回学生信息提交API新增记录
|
||||
|
||||
**变更时间**: 2025-01-27 17:15:00
|
||||
**操作人员**: cursor
|
||||
**变更类型**: 功能新增
|
||||
**变更模块**: 学工管理 - 学生信息批次收集
|
||||
|
||||
## 📋 变更概述
|
||||
|
||||
在学生信息批次收集模块中新增驳回学生信息提交的API接口,支持管理员驳回学生提交的信息完善数据,并记录驳回原因。
|
||||
|
||||
## 🎯 变更需求
|
||||
|
||||
**目标**: 在 XgStudentMsgBatchController 中增加一个API接口 `/studentMsgBatch/studentMsgBatch/rejectStudentMsg`,用于驳回学生信息的提交。
|
||||
|
||||
**API要求**:
|
||||
- 路径: `/studentMsgBatch/studentMsgBatch/rejectStudentMsg`
|
||||
- 方法: POST
|
||||
- 入参: batchId、userId、remark
|
||||
- 业务逻辑: 更新 xg_student_msg_batch 表,设置 info_status='4'(驳回状态)
|
||||
|
||||
## 📁 变更文件列表
|
||||
|
||||
### 1. Controller层
|
||||
**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgStudentMsgBatchController.java`
|
||||
- ✅ 新增 `rejectStudentMsg` API接口
|
||||
- ✅ 添加权限控制 `@SaCheckPermission("studentMsgBatch:studentMsgBatch:edit")`
|
||||
- ✅ 添加操作日志 `@Log(title = "驳回学生信息提交", businessType = BusinessType.UPDATE)`
|
||||
- ✅ 添加防重复提交 `@RepeatSubmit()`
|
||||
- ✅ 完善参数校验 `@NotNull`、`@NotBlank`
|
||||
|
||||
### 2. Service接口层
|
||||
**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgStudentMsgBatchService.java`
|
||||
- ✅ 新增 `rejectStudentMsg` 方法声明
|
||||
|
||||
### 3. Service实现层
|
||||
**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentMsgBatchServiceImpl.java`
|
||||
- ✅ 实现 `rejectStudentMsg` 业务逻辑
|
||||
- ✅ 添加完整的日志记录
|
||||
- ✅ 添加异常处理机制
|
||||
|
||||
### 4. Mapper接口层
|
||||
**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgStudentMsgBatchMapper.java`
|
||||
- ✅ 新增 `rejectStudentMsgByBatchIdAndUserId` 方法声明
|
||||
|
||||
### 5. SQL映射层
|
||||
**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/studentMsgBatch/XgStudentMsgBatchMapper.xml`
|
||||
- ✅ 新增驳回学生信息的SQL更新语句
|
||||
|
||||
## 🔧 核心实现代码
|
||||
|
||||
### API接口实现
|
||||
```java
|
||||
/**
|
||||
* 驳回学生信息提交
|
||||
*/
|
||||
@SaCheckPermission("studentMsgBatch:studentMsgBatch:edit")
|
||||
@Log(title = "驳回学生信息提交", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PostMapping("/rejectStudentMsg")
|
||||
public R<Void> rejectStudentMsg(@RequestParam @NotNull(message = "批次ID不能为空") Long batchId,
|
||||
@RequestParam @NotNull(message = "用户ID不能为空") Long userId,
|
||||
@RequestParam @NotBlank(message = "驳回原因不能为空") String remark) {
|
||||
return toAjax(xgStudentMsgBatchService.rejectStudentMsg(batchId, userId, remark));
|
||||
}
|
||||
```
|
||||
|
||||
### 业务逻辑实现
|
||||
```java
|
||||
@Override
|
||||
public Boolean rejectStudentMsg(Long batchId, Long userId, String remark) {
|
||||
Long currentUserId = LoginHelper.getUserId();
|
||||
|
||||
try {
|
||||
// 更新学生信息批次数据:设置为驳回状态并添加驳回原因
|
||||
int updatedCount = baseMapper.rejectStudentMsgByBatchIdAndUserId(batchId, userId, remark, currentUserId);
|
||||
|
||||
if (updatedCount > 0) {
|
||||
log.info("成功驳回学生信息提交,批次ID: {}, 用户ID: {}, 操作人: {}, 驳回原因: {}",
|
||||
batchId, userId, currentUserId, remark);
|
||||
return true;
|
||||
} else {
|
||||
log.warn("驳回学生信息提交失败,未找到符合条件的记录,批次ID: {}, 用户ID: {}", batchId, userId);
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("驳回学生信息提交时发生异常,批次ID: {}, 用户ID: {}, 异常信息: {}",
|
||||
batchId, userId, e.getMessage(), e);
|
||||
throw new RuntimeException("驳回学生信息提交失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### SQL实现
|
||||
```xml
|
||||
<!-- 驳回学生信息提交 -->
|
||||
<update id="rejectStudentMsgByBatchIdAndUserId">
|
||||
UPDATE xg_student_msg_batch
|
||||
SET remark = #{remark},
|
||||
info_status = '4',
|
||||
update_by = #{currentUserId},
|
||||
update_time = NOW()
|
||||
WHERE batch_id = #{batchId}
|
||||
AND id = #{userId}
|
||||
AND deleted = 0
|
||||
</update>
|
||||
```
|
||||
|
||||
## 🔍 业务逻辑详情
|
||||
|
||||
### 数据更新逻辑
|
||||
- **更新条件**: `batch_id = #{batchId} AND id = #{userId} AND deleted = 0`
|
||||
- **更新字段**:
|
||||
- `remark = #{remark}` (驳回原因)
|
||||
- `info_status = '4'` (驳回状态)
|
||||
- `update_by = #{currentUserId}` (当前登录人ID)
|
||||
- `update_time = NOW()` (当前时间)
|
||||
|
||||
### 权限控制
|
||||
- **权限注解**: `@SaCheckPermission("studentMsgBatch:studentMsgBatch:edit")`
|
||||
- **操作权限**: 需要具备学生信息批次编辑权限
|
||||
|
||||
### 参数校验
|
||||
- **batchId**: 不能为空,必须是有效的批次ID
|
||||
- **userId**: 不能为空,必须是有效的用户ID
|
||||
- **remark**: 不能为空或空白字符串,必须填写驳回原因
|
||||
|
||||
## ✅ 功能验证
|
||||
|
||||
### API调用示例
|
||||
```bash
|
||||
POST /studentMsgBatch/studentMsgBatch/rejectStudentMsg
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
batchId=123&userId=456&remark=信息填写不完整,请重新提交
|
||||
```
|
||||
|
||||
### 预期结果
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功"
|
||||
}
|
||||
```
|
||||
|
||||
## 🔒 安全考量
|
||||
|
||||
1. **权限验证**: 通过 `@SaCheckPermission` 确保只有具备编辑权限的用户才能执行驳回操作
|
||||
2. **参数校验**: 对所有输入参数进行非空和格式校验
|
||||
3. **防重复提交**: 通过 `@RepeatSubmit` 防止重复点击导致的多次提交
|
||||
4. **操作日志**: 记录所有驳回操作的详细日志,便于审计追踪
|
||||
|
||||
## 📊 影响范围
|
||||
|
||||
### 直接影响
|
||||
- ✅ 新增驳回学生信息提交的功能
|
||||
- ✅ 管理员可以对学生提交的信息进行驳回并添加原因
|
||||
|
||||
### 间接影响
|
||||
- ⭐ 完善了学生信息审核流程
|
||||
- ⭐ 提高了信息质量管控能力
|
||||
- ⭐ 为后续的审批流程优化奠定基础
|
||||
|
||||
## 🚀 后续计划
|
||||
|
||||
1. **前端集成**: 在学生信息管理页面添加驳回按钮
|
||||
2. **消息通知**: 驳回后向学生发送通知
|
||||
3. **审核流程**: 完善整个信息审核的工作流程
|
||||
4. **统计分析**: 添加驳回率统计和分析功能
|
||||
|
||||
## 📝 备注
|
||||
|
||||
- 本次变更严格遵循项目开发规范
|
||||
- 所有代码都包含完整的注释和日志
|
||||
- 数据库操作使用安全的参数化查询
|
||||
- 异常处理机制完善,确保系统稳定性
|
||||
@@ -0,0 +1,140 @@
|
||||
# 学生信息详情查看功能开发记录
|
||||
|
||||
**📅 变更日期**: 2025-01-27 21:30:00
|
||||
**👤 操作人员**: cursor
|
||||
**🎯 变更目标**: 实现学生信息审核页面中"查看详情"按钮功能
|
||||
|
||||
## 📋 变更内容
|
||||
|
||||
### 1. 功能实现概述
|
||||
- 在学生信息审核页面 (`auditDialog.vue`) 中实现"查看详情"按钮功能
|
||||
- 点击详情按钮弹出学生信息详情查看弹窗 (`stuMsgDetailDialog.vue`)
|
||||
- 传入正确的批次ID和用户ID参数
|
||||
|
||||
### 2. 修改文件清单
|
||||
|
||||
#### 📄 web/src/views/stuMsgAudit/auditDialog.vue
|
||||
**新增功能**:
|
||||
- 动态加载学生详情弹窗组件 `StuMsgDetailDialogComponent`
|
||||
- 添加详情弹窗状态管理变量
|
||||
- 实现 `handleViewDetail` 方法
|
||||
- 在模板中添加详情弹窗组件
|
||||
|
||||
**具体修改**:
|
||||
```javascript
|
||||
// 新增组件导入
|
||||
const StuMsgDetailDialogComponent = defineAsyncComponent(() => import('./stuMsgDetailDialog.vue'));
|
||||
|
||||
// 新增状态管理
|
||||
const stuMsgDetailDialogVisible = ref(false);
|
||||
const stuMsgDetailDialogTitle = ref('学生信息详情查看');
|
||||
const stuMsgDetailBatchInfo = ref<any>(null);
|
||||
|
||||
// 修改详情查看方法
|
||||
const handleViewDetail = (row: StudentAuditInfo) => {
|
||||
stuMsgDetailBatchInfo.value = {
|
||||
id: props.batchInfo?.id,
|
||||
batchId: props.batchInfo?.id,
|
||||
userId: row.id,
|
||||
studentId: row.id,
|
||||
batchName: props.batchInfo?.batchName,
|
||||
...props.batchInfo
|
||||
};
|
||||
stuMsgDetailDialogTitle.value = `学生信息详情 - ${row.name}`;
|
||||
stuMsgDetailDialogVisible.value = true;
|
||||
};
|
||||
```
|
||||
|
||||
#### 📄 web/src/views/stuMsgAudit/stuMsgDetailDialog.vue
|
||||
**优化功能**:
|
||||
- 修复 ElMessage 导入错误
|
||||
- 优化 `getUserHistoryData` 方法支持传入用户ID
|
||||
- 修改数据加载逻辑,正确传入用户ID参数
|
||||
|
||||
**具体修改**:
|
||||
```javascript
|
||||
// 修复导入错误
|
||||
import ElMessage from 'element-plus/es/components/message/index';
|
||||
|
||||
// 优化历史数据获取方法
|
||||
const getUserHistoryData = async (batchId: number, userId?: number) => {
|
||||
const url = userId
|
||||
? `/studentMsgBatch/studentMsgBatch/userHistoryData/${batchId}/${userId}`
|
||||
: `/studentMsgBatch/studentMsgBatch/userHistoryData/${batchId}`;
|
||||
const res = await request.get(url);
|
||||
// ...
|
||||
};
|
||||
|
||||
// 修改调用方式
|
||||
await getUserHistoryData(props.batchInfo.batchId, props.batchInfo.userId || props.batchInfo.studentId);
|
||||
```
|
||||
|
||||
### 3. 功能特性
|
||||
|
||||
#### ✅ 主要功能
|
||||
1. **详情查看**: 点击"查看详情"按钮弹出学生信息详情弹窗
|
||||
2. **数据传递**: 正确传入批次ID (`batch_id`) 和用户ID (`user_id`)
|
||||
3. **历史记录**: 在详情弹窗中显示学生信息收集历史记录
|
||||
4. **完整信息**: 展示学生的基础信息、扩展信息、家庭信息、健康信息、学籍信息、银行卡信息等所有维度
|
||||
5. **只读模式**: 详情查看为只读模式,不可编辑
|
||||
|
||||
#### 🔧 技术实现
|
||||
- **动态组件加载**: 使用 `defineAsyncComponent` 实现按需加载
|
||||
- **状态管理**: 独立的弹窗状态管理,避免与审核弹窗冲突
|
||||
- **参数传递**: 通过 `batchInfo` 对象传递所需的批次和用户参数
|
||||
- **API适配**: 支持传入用户ID的接口调用方式
|
||||
|
||||
### 4. 接口调用
|
||||
|
||||
#### 🔗 相关接口
|
||||
1. **历史记录接口**: `/studentMsgBatch/studentMsgBatch/history/{batchId}`
|
||||
2. **用户历史数据接口**: `/studentMsgBatch/studentMsgBatch/userHistoryData/{batchId}/{userId}`
|
||||
3. **行政区划接口**: `/system/SysRegion/tree`
|
||||
4. **宿舍楼栋接口**: `/xgss/dormBuildingTree/list`
|
||||
|
||||
### 5. 用户体验
|
||||
|
||||
#### 🎨 界面设计
|
||||
- 弹窗标题动态显示学生姓名
|
||||
- Tab页切换展示不同维度信息
|
||||
- 历史记录卡片式布局
|
||||
- 加载状态友好提示
|
||||
|
||||
#### 📱 响应式适配
|
||||
- 支持不同屏幕尺寸
|
||||
- 移动端友好的交互设计
|
||||
|
||||
## ✅ 测试验证
|
||||
|
||||
### 功能测试要点
|
||||
1. ✅ 点击"查看详情"按钮能正确弹出详情弹窗
|
||||
2. ✅ 弹窗标题显示正确的学生姓名
|
||||
3. ✅ 历史记录数据正确加载显示
|
||||
4. ✅ 学生信息各Tab页数据完整展示
|
||||
5. ✅ 弹窗关闭功能正常
|
||||
6. ✅ 多个学生详情查看互不影响
|
||||
|
||||
### API测试要点
|
||||
1. ✅ 历史记录接口调用正常
|
||||
2. ✅ 用户历史数据接口支持用户ID参数
|
||||
3. ✅ 基础配置数据接口正常
|
||||
|
||||
## 📝 注意事项
|
||||
|
||||
1. **权限控制**: 确保只有有权限的用户才能查看学生详情
|
||||
2. **数据安全**: 学生敏感信息的查看需要记录日志
|
||||
3. **性能优化**: 大量数据加载时的性能考虑
|
||||
4. **错误处理**: 网络异常时的友好提示
|
||||
|
||||
## 🔄 后续计划
|
||||
|
||||
1. **功能扩展**: 可考虑添加学生信息导出功能
|
||||
2. **权限细化**: 基于角色的字段级权限控制
|
||||
3. **审计日志**: 记录详情查看操作日志
|
||||
4. **缓存优化**: 对频繁查看的数据进行缓存
|
||||
|
||||
---
|
||||
|
||||
**✅ 状态**: 开发完成,功能测试通过
|
||||
**🎯 影响范围**: 学生信息审核模块
|
||||
**🔗 关联功能**: 学生信息收集、审核流程
|
||||
@@ -0,0 +1,164 @@
|
||||
# 代码调整记录 - 2025-01-27 - 学生信息变更比较接口开发
|
||||
|
||||
## 📋 基本信息
|
||||
- **调整人员**: Cursor AI Assistant
|
||||
- **调整时间**: 2025-01-27 21:50:00
|
||||
- **调整类型**: 新功能开发
|
||||
- **影响模块**: 学工管理-学生信息批次收集
|
||||
- **关联需求**: 学生信息变更比较功能
|
||||
|
||||
## 🎯 调整目标
|
||||
在`XgStudentMsgBatchController`中新增一个接口,用于比较学生提交信息修改了哪些字段,提供原始数据和修改后数据的对比功能。
|
||||
|
||||
## 🔧 具体调整内容
|
||||
|
||||
### 1. Controller层调整
|
||||
**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgStudentMsgBatchController.java`
|
||||
|
||||
**新增接口**:
|
||||
```java
|
||||
/**
|
||||
* 比较学生信息变更详情
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @param userId 用户ID
|
||||
* @return 变更详情信息
|
||||
*/
|
||||
@SaCheckPermission("studentMsgBatch:studentMsgBatch:query")
|
||||
@GetMapping("/compareStudentMsg")
|
||||
public R<Map<String, Object>> compareStudentMsg(@RequestParam @NotNull(message = "批次ID不能为空") Long batchId,
|
||||
@RequestParam @NotNull(message = "用户ID不能为空") Long userId) {
|
||||
return R.ok(xgStudentMsgBatchService.compareStudentMsg(batchId, userId));
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Service接口层调整
|
||||
**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgStudentMsgBatchService.java`
|
||||
|
||||
**新增方法签名**:
|
||||
```java
|
||||
/**
|
||||
* 比较学生信息变更详情
|
||||
*
|
||||
* @param batchId 批次ID
|
||||
* @param userId 用户ID
|
||||
* @return 包含字段变更详情的Map
|
||||
*/
|
||||
Map<String, Object> compareStudentMsg(Long batchId, Long userId);
|
||||
```
|
||||
|
||||
### 3. Service实现层调整
|
||||
**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentMsgBatchServiceImpl.java`
|
||||
|
||||
**新增方法实现**:
|
||||
- `compareStudentMsg()` - 主要比较逻辑方法
|
||||
- `compareAndAddChange()` - 字段比较辅助方法
|
||||
|
||||
**主要功能**:
|
||||
1. 查询原始数据(xg_student_msg表,条件:id = userId AND deleted = 0)
|
||||
2. 查询修改后数据(xg_student_msg_batch表,条件:id = userId AND batch_id = batchId AND deleted = 0)
|
||||
3. 逐字段比较所有业务字段差异
|
||||
4. 返回详细的变更信息
|
||||
|
||||
## 📊 接口设计
|
||||
|
||||
### 请求参数
|
||||
- **batchId** (Long): 批次ID,必填
|
||||
- **userId** (Long): 用户ID,必填
|
||||
|
||||
### 返回结果
|
||||
```json
|
||||
{
|
||||
"hasOriginalData": true,
|
||||
"hasBatchData": true,
|
||||
"changedFields": [
|
||||
{
|
||||
"fieldLabel": "姓名",
|
||||
"fieldName": "name",
|
||||
"originalValue": "张三",
|
||||
"newValue": "张三丰"
|
||||
},
|
||||
{
|
||||
"fieldLabel": "手机号码",
|
||||
"fieldName": "phone",
|
||||
"originalValue": "13800138000",
|
||||
"newValue": "13900139000"
|
||||
}
|
||||
],
|
||||
"totalChanges": 2,
|
||||
"originalData": {...},
|
||||
"batchData": {...}
|
||||
}
|
||||
```
|
||||
|
||||
### 返回字段说明
|
||||
- **hasOriginalData**: 是否有原始数据
|
||||
- **hasBatchData**: 是否有批次数据
|
||||
- **changedFields**: 变更字段列表
|
||||
- **fieldLabel**: 字段中文名称
|
||||
- **fieldName**: 字段英文名称
|
||||
- **originalValue**: 原始值
|
||||
- **newValue**: 修改后的值
|
||||
- **totalChanges**: 变更字段总数
|
||||
- **originalData**: 完整的原始数据
|
||||
- **batchData**: 完整的批次数据
|
||||
|
||||
## 🔍 比较字段覆盖范围
|
||||
涵盖学生信息的所有业务字段(共70+个字段):
|
||||
- 基本信息:姓名、性别、出生日期、民族等
|
||||
- 联系信息:手机号码、邮箱、地址等
|
||||
- 家庭信息:父母信息、监护人信息、家庭经济状况等
|
||||
- 健康信息:身高、体重、血型、健康状况等
|
||||
- 学籍信息:录取类别、学籍状态、入学信息等
|
||||
- 银行信息:银行卡号、开户银行等
|
||||
- 扩展字段:5个自定义扩展字段
|
||||
|
||||
## 🔒 权限设计
|
||||
- **权限标识**: `studentMsgBatch:studentMsgBatch:query`
|
||||
- **权限说明**: 复用现有的查询权限,无需新增权限配置
|
||||
|
||||
## 📝 技术要点
|
||||
|
||||
### 1. 数据查询逻辑
|
||||
- **原始数据**: 从`xg_student_msg`表查询,条件为`id = userId AND deleted = 0`
|
||||
- **批次数据**: 从`xg_student_msg_batch`表查询,条件为`id = userId AND batch_id = batchId AND deleted = 0`
|
||||
- **注意**: batch表的id字段对应user_id
|
||||
|
||||
### 2. 字段比较算法
|
||||
- 使用空值安全比较,正确处理null值情况
|
||||
- 使用equals方法进行对象比较,支持各种数据类型
|
||||
- 只记录有变更的字段,提高性能和可读性
|
||||
|
||||
### 3. 异常处理
|
||||
- 数据不存在时返回明确的状态信息
|
||||
- 查询异常时记录详细日志并抛出运行时异常
|
||||
- 提供友好的错误信息
|
||||
|
||||
## ✅ 验证要点
|
||||
1. **数据存在性验证**: 验证原始数据和批次数据是否存在
|
||||
2. **字段比较准确性**: 验证各种数据类型的比较逻辑
|
||||
3. **空值处理**: 验证null值比较的正确性
|
||||
4. **权限控制**: 验证接口权限控制有效性
|
||||
5. **错误处理**: 验证异常情况的处理
|
||||
|
||||
## 🔗 关联影响
|
||||
- **无数据库变更**: 纯查询功能,不涉及数据库结构变更
|
||||
- **无前端变更**: 仅提供后端API接口
|
||||
- **无权限变更**: 复用现有权限体系
|
||||
- **性能影响**: 查询两张表,性能影响较小
|
||||
|
||||
## 🎯 后续建议
|
||||
1. **前端集成**: 在审核界面集成此接口,展示变更详情
|
||||
2. **缓存优化**: 如果频繁调用,可考虑添加缓存
|
||||
3. **字段扩展**: 如有新字段,需要相应扩展比较逻辑
|
||||
4. **导出功能**: 可扩展为变更报告导出功能
|
||||
|
||||
## 📋 测试建议
|
||||
1. **正常流程**: 测试有变更数据的比较
|
||||
2. **边界情况**: 测试无原始数据、无批次数据的情况
|
||||
3. **数据类型**: 测试不同数据类型字段的比较
|
||||
4. **权限测试**: 测试权限控制的有效性
|
||||
|
||||
---
|
||||
**调整完成时间**: 2025-01-27 21:50:00
|
||||
**状态**: 开发完成,待测试验证
|
||||
@@ -8,6 +8,7 @@
|
||||
## 学工管理模块API
|
||||
|
||||
- [学生信息管理API](./学生信息管理API.md)
|
||||
- [学生信息收集批次管理API](./学生信息收集批次管理API.md)
|
||||
- [班级信息管理API](./班级信息管理API.md)
|
||||
- [宿舍管理API](./宿舍管理API.md)
|
||||
- [宿舍管理员信息API](./宿舍管理员信息API.md)
|
||||
@@ -116,3 +117,15 @@
|
||||
- **重要重构**: 将原本集中的大型API文档按业务模块拆分,提升维护效率
|
||||
- **新增**: API文档模板,用于新API文档的生成
|
||||
- **优化**: 目录结构,按功能模块分类管理
|
||||
|
||||
## 🔗 API文档导航
|
||||
|
||||
### 👥 学工管理模块
|
||||
|
||||
| API文档 | 描述 | 状态 | 更新时间 |
|
||||
|---------|------|------|----------|
|
||||
| [学生信息收集批次管理API](./学生信息收集批次管理API.md) | 学生信息收集批次班级权限管理 | ✅ 完成 | 2025-01-27 |
|
||||
| [学生信息批次收集API](./学生信息批次收集API.md) | 学生信息批次收集管理(新增) | ✅ 完成 | 2025-01-27 |
|
||||
| [班级信息管理API](./班级信息管理API.md) | 班级基础信息增删改查 | ✅ 完成 | 2025-01-27 |
|
||||
| [班干部管理API](./班干部管理API.md) | 班级干部职务管理 | ✅ 完成 | 2025-01-27 |
|
||||
| [班级教师关联管理API](./班级教师关联管理API.md) | 班主任辅导员关联关系 | ✅ 完成 | 2025-01-27 |
|
||||
@@ -0,0 +1,838 @@
|
||||
# 学生信息批次收集API文档
|
||||
|
||||
## 📋 文档信息
|
||||
|
||||
| 项目 | 信息 |
|
||||
|------|------|
|
||||
| **项目名称** | 校园管理系统 - 学生信息批次收集 |
|
||||
| **API模块** | 学生信息批次收集管理 |
|
||||
| **文档版本** | V1.0.0 |
|
||||
| **创建时间** | 2025-01-27 |
|
||||
| **基础路径** | `/studentMsgBatch/studentMsgBatch` |
|
||||
|
||||
## 🎯 API概述
|
||||
|
||||
### 模块说明
|
||||
学生信息批次收集系统提供学生信息收集、查询、历史记录管理等API接口,支持学生提交个人信息、管理员查询和审核学生信息等功能。
|
||||
|
||||
### 技术规范
|
||||
- **认证方式**: Bearer Token (Sa-Token)
|
||||
- **数据格式**: JSON
|
||||
- **响应格式**: 统一使用R类型或TableDataInfo分页格式
|
||||
- **权限控制**: 基于角色的权限管理(RBAC)
|
||||
|
||||
### 权限角色
|
||||
- **学生**: 查看和提交个人信息
|
||||
- **班主任**: 查看所带班级学生信息
|
||||
- **学工管理员**: 查看权限范围内的学生信息
|
||||
- **系统管理员**: 完整的学生信息管理权限
|
||||
|
||||
## 📊 数据模型
|
||||
|
||||
### 学生信息批次VO (XgStudentMsgBatchVo)
|
||||
|
||||
```java
|
||||
{
|
||||
"id": 1, // 主键ID(雪花ID)
|
||||
"deleted": 0, // 删除标志(0存在 1删除)
|
||||
"studentNo": "2025001", // 学号
|
||||
"idCard": "110101199901011234", // 身份证号
|
||||
"name": "张三", // 姓名
|
||||
"nameEnglish": "Zhang San", // 英文姓名
|
||||
"formerName": "", // 曾用名
|
||||
"gender": "0", // 性别(0-男,1-女)
|
||||
"birthDate": "1999-01-01", // 出生日期
|
||||
"nationality": "汉族", // 民族
|
||||
"nativePlace": "北京市", // 籍贯
|
||||
"politicalStatus": "团员", // 政治面貌
|
||||
"hukouLocation": "北京市海淀区", // 户籍所在地
|
||||
"homeAddress": "北京市海淀区中关村南大街1号", // 家庭地址
|
||||
"currentAddress": "北京市海淀区学院路8号", // 现住址
|
||||
"postalCode": "100081", // 邮政编码
|
||||
"photoUrl": "http://example.com/photo.jpg", // 照片URL
|
||||
"photoPath": "/upload/photo/2025/01/photo.jpg", // 照片存储路径
|
||||
"phone": "13800138000", // 手机号码
|
||||
"isBoarder": 1, // 是否住校(0-否,1-是)
|
||||
"educationLevel": "本科", // 文化程度
|
||||
"householdRegisterType": "城镇", // 户口类型
|
||||
"religion": "无", // 宗教信仰
|
||||
"sourceRegion": "北京市", // 生源地区
|
||||
"graduateSchool": "北京市第一中学", // 毕业院校
|
||||
"email": "zhangsan@example.com", // 电子邮箱
|
||||
"qq": "123456789", // QQ号码
|
||||
"wechat": "zhangsan_wx", // 微信号
|
||||
"maritalStatus": "1", // 婚姻状况(1未婚 2已婚 3离异 4丧偶)
|
||||
"emergencyContactName": "张父亲", // 紧急联系人姓名
|
||||
"emergencyContactPhone": "13900139000", // 紧急联系人电话
|
||||
"emergencyContactRelation": "父亲", // 紧急联系人关系
|
||||
"isPoorStudent": 0, // 是否贫困生(0-否,1-是)
|
||||
"isMinority": 0, // 是否少数民族(0-否,1-是)
|
||||
"isDisabled": 0, // 是否残疾(0-否,1-是)
|
||||
"isOrphan": 0, // 是否孤儿(0-否,1-是)
|
||||
"isLeftBehindChild": 0, // 是否留守儿童(0-否,1-是)
|
||||
"isSingleChild": 1, // 是否独生子女(0-否,1-是)
|
||||
"isLowIncome": 0, // 是否低保(0-否,1-是)
|
||||
"hasLoan": 0, // 是否有助学贷款(0-否,1-是)
|
||||
"loanAmount": 0.0, // 助学贷款金额
|
||||
"specialties": "篮球、编程", // 特长爱好
|
||||
"awards": "三好学生", // 获奖情况
|
||||
"violations": "", // 违纪记录
|
||||
"socialActivities": "志愿服务活动", // 社会活动
|
||||
"fatherName": "张父亲", // 父亲姓名
|
||||
"fatherPhone": "13900139001", // 父亲电话
|
||||
"fatherWorkUnit": "北京某公司", // 父亲工作单位
|
||||
"motherName": "张母亲", // 母亲姓名
|
||||
"motherPhone": "13900139002", // 母亲电话
|
||||
"motherWorkUnit": "北京某事业单位", // 母亲工作单位
|
||||
"guardianName": "张父亲", // 监护人姓名
|
||||
"guardianPhone": "13900139001", // 监护人电话
|
||||
"guardianRelation": "父亲", // 监护人关系
|
||||
"familyIncome": 100000.0, // 家庭年收入
|
||||
"perCapitaIncome": 33333.33, // 人均收入
|
||||
"incomeSource": "工资收入", // 收入来源
|
||||
"familyEconomicStatus": "一般", // 家庭经济状况
|
||||
"familyMembers": 3, // 家庭成员数量
|
||||
"height": 175.5, // 身高(cm)
|
||||
"weight": 65.0, // 体重(kg)
|
||||
"bloodType": "A", // 血型
|
||||
"healthStatus": "健康", // 健康状况
|
||||
"specialCondition": "", // 特殊体质或疾病
|
||||
"medicalHistory": "", // 病史
|
||||
"allergies": "", // 过敏史
|
||||
"disabilityType": "", // 残疾类型
|
||||
"disabilityLevel": "", // 残疾等级
|
||||
"admissionCategory": "统招", // 录取类别
|
||||
"studentStatus": "1", // 学籍状态(1在籍 2休学 3复学 4退学 5毕业)
|
||||
"enrollmentYear": 2025, // 入学年份
|
||||
"enrollmentDate": "2025-09-01", // 入学日期
|
||||
"bankCardNo": "6222081234567890123", // 银行卡号
|
||||
"bankName": "中国工商银行", // 开户银行
|
||||
"bankBranch": "北京中关村支行", // 开户支行
|
||||
"cardHolderName": "张三", // 持卡人姓名
|
||||
"infoStatus": "3", // 信息状态(1正常 2待审核 3审核中 4已驳回 5已锁定)
|
||||
"dataSource": "manual", // 数据来源(manual手工录入 import批量导入 sync系统同步)
|
||||
"lastBatchId": 1, // 最后修改批次ID
|
||||
"lastAuditTime": "2025-01-27 10:00:00", // 最后审核时间
|
||||
"isComplete": 1, // 信息是否完整(0不完整 1完整)
|
||||
"completionRate": 95.5, // 完整度百分比
|
||||
"schoolSystem": 4, // 学制(年)
|
||||
"academicLevel": "本科", // 学历层次
|
||||
"trainingType": "全日制", // 培养方式
|
||||
"majorId": "10001", // 专业ID
|
||||
"majorName": "计算机科学与技术", // 专业名称
|
||||
"classId": "20250101", // 班级ID
|
||||
"className": "计算机2025-1班", // 班级名称
|
||||
"grade": "2025", // 年级
|
||||
"collegeId": "1001", // 学院ID
|
||||
"collegeName": "计算机学院", // 学院名称
|
||||
"counselorId": 1001, // 辅导员ID
|
||||
"counselorName": "李辅导员", // 辅导员姓名
|
||||
"classTeacherId": 1002, // 班主任ID
|
||||
"classTeacherName": "王老师", // 班主任姓名
|
||||
"dormitoryBuilding": "1号楼", // 宿舍楼栋
|
||||
"dormitoryRoom": "101", // 宿舍房间号
|
||||
"extField1": "", // 扩展字段1
|
||||
"extField2": "", // 扩展字段2
|
||||
"extField3": "", // 扩展字段3
|
||||
"extField4": "", // 扩展字段4
|
||||
"extField5": "", // 扩展字段5
|
||||
"remark": "备注信息", // 备注信息
|
||||
"batchId": 1, // 正在进行中的批次ID
|
||||
"createBy": 1, // 创建者
|
||||
"createTime": "2025-01-27 10:00:00", // 创建时间
|
||||
"updateBy": 1, // 更新者
|
||||
"updateTime": "2025-01-27 10:00:00" // 更新时间
|
||||
}
|
||||
```
|
||||
|
||||
## 🔧 API接口列表
|
||||
|
||||
### 1. 分页查询学生信息批次收集列表
|
||||
|
||||
- **接口路径**: GET `/studentMsgBatch/studentMsgBatch/list`
|
||||
- **接口功能**: 分页查询学生信息批次收集列表
|
||||
- **权限要求**: `studentMsgBatch:studentMsgBatch:list`
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| pageNum | Integer | 否 | 页码,默认1 |
|
||||
| pageSize | Integer | 否 | 每页条数,默认10 |
|
||||
| batchId | Long | 否 | 批次ID |
|
||||
| studentNo | String | 否 | 学号 |
|
||||
| name | String | 否 | 姓名(模糊查询) |
|
||||
| phone | String | 否 | 手机号码 |
|
||||
| infoStatus | String | 否 | 信息状态 |
|
||||
| classId | String | 否 | 班级ID |
|
||||
|
||||
#### 响应数据
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"rows": [
|
||||
{
|
||||
"id": 1,
|
||||
"studentNo": "2025001",
|
||||
"name": "张三",
|
||||
"phone": "13800138000",
|
||||
"infoStatus": "3",
|
||||
"className": "计算机2025-1班",
|
||||
"createTime": "2025-01-27 10:00:00"
|
||||
}
|
||||
],
|
||||
"total": 100
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 导出学生信息批次收集列表
|
||||
|
||||
- **接口路径**: POST `/studentMsgBatch/studentMsgBatch/export`
|
||||
- **接口功能**: 导出学生信息批次收集列表到Excel
|
||||
- **权限要求**: `studentMsgBatch:studentMsgBatch:export`
|
||||
|
||||
#### 请求参数
|
||||
|
||||
同查询列表接口,用于筛选导出数据。
|
||||
|
||||
#### 响应数据
|
||||
|
||||
返回Excel文件流。
|
||||
|
||||
### 3. 获取学生信息批次收集详细信息
|
||||
|
||||
- **接口路径**: GET `/studentMsgBatch/studentMsgBatch/{id}`
|
||||
- **接口功能**: 根据主键ID获取学生信息详细信息
|
||||
- **权限要求**: `studentMsgBatch:studentMsgBatch:query`
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| id | Long | 是 | 主键ID |
|
||||
|
||||
#### 响应数据
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"studentNo": "2025001",
|
||||
"name": "张三",
|
||||
// ... 完整的学生信息字段
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 新增学生信息批次收集
|
||||
|
||||
- **接口路径**: POST `/studentMsgBatch/studentMsgBatch`
|
||||
- **接口功能**: 新增学生信息批次收集记录
|
||||
- **权限要求**: `studentMsgBatch:studentMsgBatch:add`
|
||||
|
||||
#### 请求参数
|
||||
|
||||
```json
|
||||
{
|
||||
"studentNo": "2025001",
|
||||
"name": "张三",
|
||||
"gender": "0",
|
||||
"phone": "13800138000",
|
||||
"batchId": 1
|
||||
// ... 其他学生信息字段
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应数据
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功"
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 修改学生信息批次收集
|
||||
|
||||
- **接口路径**: PUT `/studentMsgBatch/studentMsgBatch`
|
||||
- **接口功能**: 修改学生信息批次收集记录
|
||||
- **权限要求**: `studentMsgBatch:studentMsgBatch:edit`
|
||||
|
||||
#### 请求参数
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"studentNo": "2025001",
|
||||
"name": "张三",
|
||||
"gender": "0",
|
||||
"phone": "13800138000",
|
||||
"batchId": 1
|
||||
// ... 其他学生信息字段
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应数据
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功"
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 删除学生信息批次收集
|
||||
|
||||
- **接口路径**: DELETE `/studentMsgBatch/studentMsgBatch/{ids}`
|
||||
- **接口功能**: 批量删除学生信息批次收集记录
|
||||
- **权限要求**: `studentMsgBatch:studentMsgBatch:remove`
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| ids | Long[] | 是 | 主键ID数组 |
|
||||
|
||||
#### 响应数据
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功"
|
||||
}
|
||||
```
|
||||
|
||||
### 7. 查询学生信息完善历史记录
|
||||
|
||||
- **接口路径**: GET `/studentMsgBatch/studentMsgBatch/history/{batchId}`
|
||||
- **接口功能**: 查询学生信息完善的历史记录信息及最后一次提交的信息状态
|
||||
- **权限要求**: `studentMsgBatch:studentMsgBatch:query`
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| batchId | Long | 是 | 批次ID |
|
||||
|
||||
#### 响应数据
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": {
|
||||
"historyMsg": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "张三",
|
||||
"createTime": "2025-01-27 10:00:00",
|
||||
"createBy": 1,
|
||||
"createByName": "张三",
|
||||
"infoStatus": "3",
|
||||
"remark": "信息已提交"
|
||||
}
|
||||
],
|
||||
"currentMsg": [
|
||||
{
|
||||
"id": 1,
|
||||
"studentNo": "2025001",
|
||||
"name": "张三",
|
||||
// ... 完整的当前信息
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8. 获取用户该批次的历史已填写数据
|
||||
|
||||
- **接口路径**: GET `/studentMsgBatch/studentMsgBatch/userHistoryData/{batchId}`
|
||||
- **接口功能**: 获取当前用户在指定批次的历史已填写数据
|
||||
- **权限要求**: `studentMsgBatch:studentMsgBatch:query`
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| batchId | Long | 是 | 批次ID |
|
||||
|
||||
#### 响应数据
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"studentNo": "2025001",
|
||||
"name": "张三",
|
||||
// ... 完整的学生信息字段
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 9. 学生提交信息完善数据
|
||||
|
||||
- **接口路径**: POST `/studentMsgBatch/studentMsgBatch/submitStudentMsg`
|
||||
- **接口功能**: 学生提交信息完善数据
|
||||
- **权限要求**: `studentMsgBatch:studentMsgBatch:add`
|
||||
|
||||
#### 请求参数
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"studentNo": "2025001",
|
||||
"name": "张三",
|
||||
"gender": "0",
|
||||
"phone": "13800138000",
|
||||
"batchId": 1
|
||||
// ... 其他学生信息字段
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应数据
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功"
|
||||
}
|
||||
```
|
||||
|
||||
### 10. 驳回学生信息提交
|
||||
|
||||
- **接口路径**: POST `/studentMsgBatch/studentMsgBatch/rejectStudentMsg`
|
||||
- **接口功能**: 驳回学生信息提交,设置驳回状态并记录驳回原因
|
||||
- **权限要求**: `studentMsgBatch:studentMsgBatch:edit`
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| batchId | Long | 是 | 批次ID |
|
||||
| userId | Long | 是 | 用户ID(学生ID) |
|
||||
| remark | String | 是 | 驳回原因 |
|
||||
|
||||
#### 响应数据
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功"
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务逻辑说明
|
||||
|
||||
1. 更新 `xg_student_msg_batch` 表中符合条件的记录:
|
||||
- **更新条件**: `batch_id = #{batchId} AND id = #{userId} AND deleted = 0`
|
||||
- **更新字段**:
|
||||
- `remark = #{remark}` (驳回原因)
|
||||
- `info_status = '4'` (驳回状态)
|
||||
- `update_by = #{currentUserId}` (当前登录人ID)
|
||||
- `update_time = NOW()` (当前时间)
|
||||
|
||||
2. 记录操作日志,便于审计追踪
|
||||
|
||||
#### 使用示例
|
||||
|
||||
```javascript
|
||||
// 驳回学生信息提交
|
||||
fetch('/studentMsgBatch/studentMsgBatch/rejectStudentMsg', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: 'batchId=123&userId=456&remark=信息填写不完整,请重新提交'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.code === 200) {
|
||||
console.log('驳回成功');
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### 状态说明
|
||||
|
||||
| 状态值 | 状态名称 | 说明 |
|
||||
|--------|----------|------|
|
||||
| 1 | 待填写 | 学生尚未开始填写信息 |
|
||||
| 2 | 填写中 | 学生正在填写信息 |
|
||||
| 3 | 已提交 | 学生已提交信息,等待审核 |
|
||||
| 4 | 审核不通过 | 管理员驳回,学生需要重新填写 |
|
||||
| 5 | 审核通过 | 管理员审核通过 |
|
||||
|
||||
### 11. 根据部门ID查询学生数据(新增)
|
||||
|
||||
- **接口路径**: GET `/studentMsgBatch/studentMsgBatch/listByDept`
|
||||
- **接口功能**: 根据部门ID查询该组织下所有已完善信息的学生数据
|
||||
- **权限要求**: `studentMsgBatch:studentMsgBatch:query`
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| batchId | Long | 是 | 批次ID |
|
||||
| deptId | Long | 是 | 部门ID |
|
||||
| name | String | 否 | 学生姓名(模糊查询) |
|
||||
| studentNo | String | 否 | 学号(精确查询) |
|
||||
| phone | String | 否 | 手机号码(精确查询) |
|
||||
| infoStatus | String | 否 | 信息状态 |
|
||||
| pageNum | Integer | 否 | 页码,默认1 |
|
||||
| pageSize | Integer | 否 | 每页条数,默认10 |
|
||||
|
||||
#### 响应数据
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"rows": [
|
||||
{
|
||||
"id": 1,
|
||||
"studentNo": "2025001",
|
||||
"name": "张三",
|
||||
"phone": "13800138000",
|
||||
"infoStatus": "3",
|
||||
"className": "计算机2025-1班",
|
||||
"collegeName": "计算机学院",
|
||||
"createTime": "2025-01-27 10:00:00"
|
||||
}
|
||||
],
|
||||
"total": 50
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务逻辑说明
|
||||
|
||||
1. 根据 `deptId` 在 `sys_dept` 表中查询该部门及其所有子部门的ID列表
|
||||
2. 在 `xg_student_msg_batch` 表中查询:
|
||||
- `batch_id = 入参的batchId`
|
||||
- `deleted = 0`
|
||||
- `class_id IN 部门ID列表`(班级ID等于部门ID)
|
||||
- 支持按姓名、学号、手机号、信息状态等条件过滤
|
||||
- 分页查询
|
||||
|
||||
#### 使用示例
|
||||
|
||||
```javascript
|
||||
// 查询部门ID为101下的所有学生数据
|
||||
fetch('/studentMsgBatch/studentMsgBatch/listByDept?batchId=1&deptId=101&pageNum=1&pageSize=10')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('查询结果:', data);
|
||||
});
|
||||
```
|
||||
|
||||
### 12. 比较学生信息变更详情(新增)
|
||||
|
||||
- **接口路径**: GET `/studentMsgBatch/studentMsgBatch/compareStudentMsg`
|
||||
- **接口功能**: 比较学生提交信息与原始信息的字段变更详情
|
||||
- **权限要求**: `studentMsgBatch:studentMsgBatch:query`
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| batchId | Long | 是 | 批次ID |
|
||||
| userId | Long | 是 | 用户ID(学生ID) |
|
||||
|
||||
#### 响应数据
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": {
|
||||
"hasOriginalData": true,
|
||||
"hasBatchData": true,
|
||||
"changedFields": [
|
||||
{
|
||||
"fieldLabel": "姓名",
|
||||
"fieldName": "name",
|
||||
"originalValue": "张三",
|
||||
"newValue": "张三丰"
|
||||
},
|
||||
{
|
||||
"fieldLabel": "手机号码",
|
||||
"fieldName": "phone",
|
||||
"originalValue": "13800138000",
|
||||
"newValue": "13900139000"
|
||||
},
|
||||
{
|
||||
"fieldLabel": "家庭地址",
|
||||
"fieldName": "homeAddress",
|
||||
"originalValue": "北京市海淀区中关村南大街1号",
|
||||
"newValue": "北京市朝阳区建国门外大街1号"
|
||||
}
|
||||
],
|
||||
"totalChanges": 3,
|
||||
"originalData": {...},
|
||||
"batchData": {...}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 返回字段说明
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| hasOriginalData | Boolean | 是否找到原始学生信息 |
|
||||
| hasBatchData | Boolean | 是否找到批次提交信息 |
|
||||
| changedFields | Array | 变更字段列表 |
|
||||
| totalChanges | Integer | 变更字段总数 |
|
||||
| originalData | Object | 完整的原始数据 |
|
||||
| batchData | Object | 完整的批次数据 |
|
||||
|
||||
#### changedFields字段结构
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| fieldLabel | String | 字段中文名称 |
|
||||
| fieldName | String | 字段英文名称 |
|
||||
| originalValue | Object | 原始值(可能为null) |
|
||||
| newValue | Object | 修改后的值(可能为null) |
|
||||
|
||||
#### 业务逻辑说明
|
||||
|
||||
1. **数据查询**:
|
||||
- 原始数据:从 `xg_student_msg` 表查询,条件:`id = userId AND deleted = 0`
|
||||
- 批次数据:从 `xg_student_msg_batch` 表查询,条件:`id = userId AND batch_id = batchId AND deleted = 0`
|
||||
|
||||
2. **字段比较**:
|
||||
- 比较两个对象的所有业务字段(70+个字段)
|
||||
- 支持空值安全比较,正确处理null值情况
|
||||
- 只返回有变更的字段,提高性能和可读性
|
||||
|
||||
3. **异常处理**:
|
||||
- 当原始数据不存在时,返回 `hasOriginalData: false`
|
||||
- 当批次数据不存在时,返回 `hasBatchData: false`
|
||||
- 查询异常时返回友好的错误信息
|
||||
|
||||
#### 涵盖字段范围
|
||||
|
||||
- **基本信息**: 姓名、性别、出生日期、民族、籍贯、政治面貌等
|
||||
- **联系信息**: 手机号码、邮箱、家庭地址、现住址等
|
||||
- **家庭信息**: 父母信息、监护人信息、家庭经济状况等
|
||||
- **健康信息**: 身高、体重、血型、健康状况、过敏史等
|
||||
- **学籍信息**: 录取类别、学籍状态、入学信息等
|
||||
- **银行信息**: 银行卡号、开户银行、开户支行等
|
||||
- **扩展字段**: 5个自定义扩展字段
|
||||
|
||||
#### 使用场景
|
||||
|
||||
1. **审核前查看**: 管理员审核前查看学生修改了哪些信息
|
||||
2. **变更记录**: 记录学生信息的变更历史
|
||||
3. **数据核实**: 对比确认学生提交的修改内容
|
||||
4. **审计追踪**: 提供完整的信息变更审计轨迹
|
||||
|
||||
#### 使用示例
|
||||
|
||||
```javascript
|
||||
// 比较学生ID为1001在批次ID为1中的信息变更
|
||||
fetch('/studentMsgBatch/studentMsgBatch/compareStudentMsg?batchId=1&userId=1001')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('变更详情:', data);
|
||||
if (data.data.totalChanges > 0) {
|
||||
console.log('共有', data.data.totalChanges, '个字段发生变更');
|
||||
data.data.changedFields.forEach(change => {
|
||||
console.log(`${change.fieldLabel}: ${change.originalValue} → ${change.newValue}`);
|
||||
});
|
||||
} else {
|
||||
console.log('没有字段变更');
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## 📝 使用示例
|
||||
|
||||
### 前端调用示例
|
||||
|
||||
```typescript
|
||||
import { request } from '@/utils/request';
|
||||
|
||||
// 查询学生信息列表
|
||||
export function getStudentMsgBatchList(params: any) {
|
||||
return request({
|
||||
url: '/studentMsgBatch/studentMsgBatch/list',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
// 根据部门查询学生信息
|
||||
export function getStudentMsgBatchByDept(params: any) {
|
||||
return request({
|
||||
url: '/studentMsgBatch/studentMsgBatch/listByDept',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
// 获取学生详细信息
|
||||
export function getStudentMsgBatchDetail(id: number) {
|
||||
return request({
|
||||
url: `/studentMsgBatch/studentMsgBatch/${id}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
// 学生提交信息
|
||||
export function submitStudentMsg(data: any) {
|
||||
return request({
|
||||
url: '/studentMsgBatch/studentMsgBatch/submitStudentMsg',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 后端测试示例
|
||||
|
||||
```java
|
||||
@Test
|
||||
public void testQueryByDeptId() {
|
||||
// 测试根据部门ID查询学生数据
|
||||
PageQuery pageQuery = new PageQuery();
|
||||
pageQuery.setPageNum(1);
|
||||
pageQuery.setPageSize(10);
|
||||
|
||||
TableDataInfo<XgStudentMsgBatchVo> result =
|
||||
xgStudentMsgBatchService.queryPageListByDeptId(
|
||||
1L, // batchId
|
||||
101L, // deptId
|
||||
"张", // name
|
||||
null, // studentNo
|
||||
null, // phone
|
||||
"3", // infoStatus
|
||||
pageQuery // pageQuery
|
||||
);
|
||||
|
||||
Assert.assertNotNull(result);
|
||||
Assert.assertTrue(result.getTotal() >= 0);
|
||||
}
|
||||
```
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 数据安全
|
||||
1. **权限验证**: 所有接口都进行严格的权限验证
|
||||
2. **数据过滤**: 用户只能查看权限范围内的数据
|
||||
3. **SQL注入防护**: 使用参数绑定防止SQL注入
|
||||
|
||||
### 性能优化
|
||||
1. **查询优化**: 使用索引优化大数据量查询
|
||||
2. **部门树查询**: 使用FIND_IN_SET优化部门层级查询
|
||||
3. **分页查询**: 大列表数据支持分页
|
||||
|
||||
### 业务规则
|
||||
1. **数据关联**: 班级ID等于部门ID的设计规则
|
||||
2. **状态管理**: 信息状态流转(正常→待审核→审核中→已驳回/已锁定)
|
||||
3. **逻辑删除**: 支持数据的逻辑删除和恢复
|
||||
|
||||
### 错误处理
|
||||
1. **参数验证**: 对所有输入参数进行验证
|
||||
2. **异常捕获**: 完善的异常处理机制
|
||||
3. **友好提示**: 提供用户友好的错误信息
|
||||
|
||||
## 📈 版本更新记录
|
||||
|
||||
### V1.0.0 (2025-01-27)
|
||||
- 🆕 新增 `listByDept` 根据部门ID查询学生数据接口
|
||||
- 🔧 完善学生信息批次收集的完整API接口
|
||||
- 📝 完善API文档和使用示例
|
||||
- 🔐 完善权限控制策略
|
||||
- ✨ 支持部门层级查询和分页功能
|
||||
|
||||
### V1.1.0 (2025-01-27)
|
||||
- 🎯 **审核功能优化**: 审核按钮集成学生信息详情弹窗
|
||||
- 🔧 **前端集成**: 在学生信息审核界面(`auditDialog.vue`)中集成学生信息完善弹窗(`stuMsgDialog.vue`)
|
||||
- 📊 **参数传递优化**: 优化batchId和用户ID的传递机制
|
||||
- 🎨 **用户体验提升**: 从简单审核升级为完整信息查看和编辑
|
||||
|
||||
### V1.2.0 (2025-01-27)
|
||||
- 🆕 **新增比较接口**: `compareStudentMsg` 学生信息变更详情比较接口
|
||||
- 🔍 **字段对比功能**: 支持70+个字段的全面比较,包括基本信息、家庭信息、健康信息等
|
||||
- 📋 **变更记录**: 提供详细的字段变更记录,支持原值与新值对比
|
||||
- 🔒 **权限复用**: 复用现有查询权限,无需额外权限配置
|
||||
- 📝 **完善文档**: 更新API文档,添加接口使用示例和业务说明
|
||||
|
||||
## 🎯 前端审核功能集成
|
||||
|
||||
### 审核界面优化说明
|
||||
|
||||
**文件路径**: `web/src/views/stuMsgAudit/auditDialog.vue`
|
||||
|
||||
#### 功能升级
|
||||
- **原有功能**: 简单的通过/不通过审核弹窗
|
||||
- **升级后**: 完整的学生信息查看和编辑界面,包含历史记录、基础信息、扩展信息等
|
||||
|
||||
#### 技术实现
|
||||
```vue
|
||||
<!-- 审核按钮点击触发 -->
|
||||
<el-button link type="primary" icon="View" @click="handleAuditStudent(scope.row)">
|
||||
审核
|
||||
</el-button>
|
||||
|
||||
<!-- 学生信息详情弹窗 -->
|
||||
<component
|
||||
:is="StuMsgDialogComponent"
|
||||
:visible="stuMsgDialogVisible"
|
||||
:title="stuMsgDialogTitle"
|
||||
:batchInfo="stuMsgBatchInfo"
|
||||
@update:visible="stuMsgDialogVisible = $event"
|
||||
@confirm="handleStuMsgConfirm"
|
||||
v-if="StuMsgDialogComponent"
|
||||
/>
|
||||
```
|
||||
|
||||
#### 参数传递
|
||||
```typescript
|
||||
const handleAuditStudent = (row: StudentAuditInfo) => {
|
||||
// 构建传递给学生信息弹窗的批次信息
|
||||
stuMsgBatchInfo.value = {
|
||||
batchId: props.batchInfo?.id, // 批次ID
|
||||
userId: row.id, // 学生用户ID
|
||||
...props.batchInfo // 其他批次信息
|
||||
};
|
||||
stuMsgDialogTitle.value = `学生信息审核 - ${row.name}`;
|
||||
stuMsgDialogVisible.value = true;
|
||||
};
|
||||
```
|
||||
|
||||
#### 组件加载
|
||||
- 使用 `defineAsyncComponent` 动态加载组件
|
||||
- 避免静态导入可能的类型错误
|
||||
- 支持按需加载,优化性能
|
||||
|
||||
```typescript
|
||||
const StuMsgDialogComponent = defineAsyncComponent(() => import('./stuMsgDialog.vue'));
|
||||
```
|
||||
|
||||
### 集成效果
|
||||
|
||||
1. **操作流程**:
|
||||
- 点击学生列表中的"审核"按钮
|
||||
- 弹出完整的学生信息详情界面
|
||||
- 包含历史记录、基础信息、扩展信息、家庭信息等多个tab页
|
||||
- 支持信息查看和编辑
|
||||
- 确认后自动刷新学生列表
|
||||
|
||||
2. **数据传递**:
|
||||
- 自动传入当前批次的`batchId`
|
||||
- 传入当前学生的`userId`
|
||||
- 包含完整的批次配置信息
|
||||
|
||||
3. **用户体验**:
|
||||
- 无缝的界面切换
|
||||
- 丰富的学生信息展示
|
||||
- 完整的操作历史追踪
|
||||
|
||||
这次优化显著提升了审核功能的实用性和用户体验,从简单的审核操作升级为完整的学生信息管理功能。
|
||||
@@ -0,0 +1,608 @@
|
||||
# 学生信息收集批次管理API文档
|
||||
|
||||
## 📋 文档信息
|
||||
|
||||
| 项目 | 信息 |
|
||||
|------|------|
|
||||
| **项目名称** | 校园管理系统 - 学生信息收集批次管理 |
|
||||
| **API模块** | 学生信息收集批次班级权限管理 |
|
||||
| **文档版本** | V1.0.0 |
|
||||
| **创建时间** | 2025-01-27 |
|
||||
| **基础路径** | `/stuColBatchClass/stuColBatchClass` |
|
||||
|
||||
## 🎯 API概述
|
||||
|
||||
### 模块说明
|
||||
学生信息收集批次管理系统提供完整的批次权限配置、批次查询、权限范围管理等API接口,支持按班级权限控制学生信息收集范围。
|
||||
|
||||
### 技术规范
|
||||
- **认证方式**: Bearer Token (Sa-Token)
|
||||
- **数据格式**: JSON
|
||||
- **响应格式**: 统一使用R类型或TableDataInfo分页格式
|
||||
- **权限控制**: 基于角色的权限管理(RBAC)
|
||||
|
||||
### 权限角色
|
||||
- **学生管理员**: 查看权限范围内的批次信息
|
||||
- **班主任**: 查看所带班级的批次信息
|
||||
- **全校学生管理员**: 查看全校范围的批次信息
|
||||
- **系统管理员**: 完整的批次权限配置管理
|
||||
|
||||
## 📊 数据模型
|
||||
|
||||
### 批次信息VO (XgStuColBatchVo)
|
||||
|
||||
```java
|
||||
{
|
||||
"id": 1, // 批次ID
|
||||
"batchName": "2025年春季学期信息收集", // 批次名称
|
||||
"description": "收集学生基本信息", // 批次描述
|
||||
"startTime": "2025-02-01 00:00:00", // 开始时间
|
||||
"endTime": "2025-02-28 23:59:59", // 结束时间
|
||||
"status": "0", // 状态(0正常 1停用)
|
||||
"collectionType": "1", // 收集类型(1-新学期收集 2-其他)
|
||||
"remark": "备注信息", // 备注
|
||||
"createBy": 1, // 创建者
|
||||
"createTime": "2025-01-27 10:00:00", // 创建时间
|
||||
"updateBy": 1, // 更新者
|
||||
"updateTime": "2025-01-27 10:00:00" // 更新时间
|
||||
}
|
||||
```
|
||||
|
||||
### 批次班级权限VO (XgStuColBatchClassVo)
|
||||
|
||||
```java
|
||||
{
|
||||
"id": 1, // 主键ID
|
||||
"batchId": 1, // 批次ID
|
||||
"classId": 101, // 班级ID
|
||||
"className": "计算机2023-1", // 班级名称
|
||||
"majorId": 10, // 专业ID
|
||||
"majorName": "计算机科学与技术", // 专业名称
|
||||
"collegeId": 1, // 院系ID
|
||||
"collegeName": "计算机学院", // 院系名称
|
||||
"remark": "备注", // 备注
|
||||
"deleted": 0, // 删除标志
|
||||
"createBy": 1, // 创建者
|
||||
"createTime": "2025-01-27 10:00:00", // 创建时间
|
||||
"updateBy": 1, // 更新者
|
||||
"updateTime": "2025-01-27 10:00:00" // 更新时间
|
||||
}
|
||||
```
|
||||
|
||||
## 🔧 API接口列表
|
||||
|
||||
### 1. 基础管理接口
|
||||
|
||||
#### 1.1 查询批次班级权限列表
|
||||
- **接口路径**: GET `/stuColBatchClass/stuColBatchClass/list`
|
||||
- **权限标识**: `stuColBatchClass:stuColBatchClass:list`
|
||||
- **功能描述**: 分页查询学生信息收集批次班级权限列表
|
||||
|
||||
**请求参数**:
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| pageNum | integer | 否 | 页码,默认1 |
|
||||
| pageSize | integer | 否 | 每页大小,默认10 |
|
||||
| batchId | long | 否 | 批次ID |
|
||||
| classId | long | 否 | 班级ID |
|
||||
| className | string | 否 | 班级名称(模糊查询) |
|
||||
| majorName | string | 否 | 专业名称(模糊查询) |
|
||||
| collegeName | string | 否 | 院系名称(模糊查询) |
|
||||
|
||||
**响应数据**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "查询成功",
|
||||
"rows": [
|
||||
{
|
||||
"id": 1,
|
||||
"batchId": 1,
|
||||
"classId": 101,
|
||||
"className": "计算机2023-1",
|
||||
"majorId": 10,
|
||||
"majorName": "计算机科学与技术",
|
||||
"collegeId": 1,
|
||||
"collegeName": "计算机学院"
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.2 获取批次班级权限详细信息
|
||||
- **接口路径**: GET `/stuColBatchClass/stuColBatchClass/{id}`
|
||||
- **权限标识**: `stuColBatchClass:stuColBatchClass:query`
|
||||
- **功能描述**: 根据主键获取批次班级权限详细信息
|
||||
|
||||
**请求参数**:
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| id | long | 是 | 主键ID |
|
||||
|
||||
**响应数据**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "查询成功",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"batchId": 1,
|
||||
"classId": 101,
|
||||
"className": "计算机2023-1",
|
||||
"majorId": 10,
|
||||
"majorName": "计算机科学与技术",
|
||||
"collegeId": 1,
|
||||
"collegeName": "计算机学院",
|
||||
"remark": "备注",
|
||||
"deleted": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.3 新增批次班级权限
|
||||
- **接口路径**: POST `/stuColBatchClass/stuColBatchClass`
|
||||
- **权限标识**: `stuColBatchClass:stuColBatchClass:add`
|
||||
- **功能描述**: 新增学生信息收集批次班级权限
|
||||
|
||||
**请求参数**:
|
||||
```json
|
||||
{
|
||||
"batchId": 1,
|
||||
"classId": 101,
|
||||
"className": "计算机2023-1",
|
||||
"majorId": 10,
|
||||
"majorName": "计算机科学与技术",
|
||||
"collegeId": 1,
|
||||
"collegeName": "计算机学院",
|
||||
"remark": "备注"
|
||||
}
|
||||
```
|
||||
|
||||
**响应数据**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "新增成功"
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.4 修改批次班级权限
|
||||
- **接口路径**: PUT `/stuColBatchClass/stuColBatchClass`
|
||||
- **权限标识**: `stuColBatchClass:stuColBatchClass:edit`
|
||||
- **功能描述**: 修改学生信息收集批次班级权限
|
||||
|
||||
**请求参数**:
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"batchId": 1,
|
||||
"classId": 101,
|
||||
"className": "计算机2023-1",
|
||||
"majorId": 10,
|
||||
"majorName": "计算机科学与技术",
|
||||
"collegeId": 1,
|
||||
"collegeName": "计算机学院",
|
||||
"remark": "修改后的备注"
|
||||
}
|
||||
```
|
||||
|
||||
**响应数据**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "修改成功"
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.5 删除批次班级权限
|
||||
- **接口路径**: DELETE `/stuColBatchClass/stuColBatchClass/{ids}`
|
||||
- **权限标识**: `stuColBatchClass:stuColBatchClass:remove`
|
||||
- **功能描述**: 删除学生信息收集批次班级权限(支持批量删除)
|
||||
|
||||
**请求参数**:
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| ids | string | 是 | 主键ID,多个用逗号分隔 |
|
||||
|
||||
**响应数据**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "删除成功"
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.6 导出批次班级权限
|
||||
- **接口路径**: POST `/stuColBatchClass/stuColBatchClass/export`
|
||||
- **权限标识**: `stuColBatchClass:stuColBatchClass:export`
|
||||
- **功能描述**: 导出学生信息收集批次班级权限列表
|
||||
|
||||
**请求参数**:
|
||||
```json
|
||||
{
|
||||
"batchId": 1,
|
||||
"classId": 101,
|
||||
"className": "计算机",
|
||||
"majorName": "计算机科学",
|
||||
"collegeName": "计算机学院"
|
||||
}
|
||||
```
|
||||
|
||||
**响应数据**:
|
||||
- 直接返回Excel文件流
|
||||
|
||||
### 2. 权限范围管理接口
|
||||
|
||||
#### 2.1 获取批次收集范围
|
||||
- **接口路径**: GET `/stuColBatchClass/stuColBatchClass/getScope/{batchId}`
|
||||
- **权限标识**: `stuColBatchClass:stuColBatchClass:query`
|
||||
- **功能描述**: 获取指定批次配置的班级权限范围
|
||||
|
||||
**请求参数**:
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| batchId | long | 是 | 批次ID |
|
||||
|
||||
**响应数据**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "查询成功",
|
||||
"data": [101, 102, 103] // 班级ID列表
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 获取批次权限范围详情
|
||||
- **接口路径**: GET `/stuColBatchClass/stuColBatchClass/getScopeDetail/{batchId}`
|
||||
- **权限标识**: `stuColBatchClass:stuColBatchClass:query`
|
||||
- **功能描述**: 获取批次配置的权限范围详细信息
|
||||
|
||||
**请求参数**:
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| batchId | long | 是 | 批次ID |
|
||||
|
||||
**响应数据**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "查询成功",
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"batchId": 1,
|
||||
"classId": 101,
|
||||
"className": "计算机2023-1",
|
||||
"majorId": 10,
|
||||
"majorName": "计算机科学与技术",
|
||||
"collegeId": 1,
|
||||
"collegeName": "计算机学院"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 设置批次收集范围
|
||||
- **接口路径**: POST `/stuColBatchClass/stuColBatchClass/setScope`
|
||||
- **权限标识**: `stuColBatchClass:stuColBatchClass:setScope`
|
||||
- **功能描述**: 设置学生信息收集批次班级权限范围
|
||||
|
||||
**请求参数**:
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| batchId | long | 是 | 批次ID |
|
||||
| deptIds | array | 是 | 授权的班级ID列表 |
|
||||
|
||||
**响应数据**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "权限设置成功,共设置 3 个班级权限"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 用户相关接口
|
||||
|
||||
#### 3.1 查询学生可参与的批次
|
||||
- **接口路径**: GET `/stuColBatchClass/stuColBatchClass/getStudentAvailableBatches`
|
||||
- **权限标识**: 无需权限(基于当前登录用户)
|
||||
- **功能描述**: 查询当前登录学生可参与的批次信息
|
||||
|
||||
**请求参数**:
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| batchName | string | 否 | 批次名称(模糊查询) |
|
||||
|
||||
**响应数据**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "查询成功",
|
||||
"data": [
|
||||
{
|
||||
"batchId": 1,
|
||||
"batchName": "2025年春季学期信息收集",
|
||||
"description": "收集学生基本信息",
|
||||
"startTime": "2025-02-01 00:00:00",
|
||||
"endTime": "2025-02-28 23:59:59",
|
||||
"status": "0",
|
||||
"collectionType": "1",
|
||||
"remark": "备注信息",
|
||||
"classId": 101,
|
||||
"className": "计算机2023-1",
|
||||
"majorId": 10,
|
||||
"majorName": "计算机科学与技术",
|
||||
"collegeId": 1,
|
||||
"collegeName": "计算机学院"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2 查询老师关联的批次信息 🆕
|
||||
- **接口路径**: GET `/stuColBatchClass/stuColBatchClass/getTeacherRelatedBatches`
|
||||
- **权限标识**: `stuColBatchClass:stuColBatchClass:query`
|
||||
- **功能描述**: 查询当前登录老师有权限管理的批次信息
|
||||
|
||||
**请求参数**:
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| batchName | string | 否 | 批次名称(模糊查询) |
|
||||
|
||||
**业务逻辑**:
|
||||
1. 调用 `XgStudentManagerSourceServiceImpl.getCurrentUserAuthDeptTree()` 获取当前用户有权限的部门树
|
||||
2. 递归提取所有部门ID
|
||||
3. 根据部门ID查询关联的批次信息
|
||||
4. 支持全校学生管理员、普通管理员、班主任三种权限模式
|
||||
|
||||
**响应数据**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "查询成功",
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"batchName": "2025年春季学期信息收集",
|
||||
"description": "收集学生基本信息",
|
||||
"startTime": "2025-02-01 00:00:00",
|
||||
"endTime": "2025-02-28 23:59:59",
|
||||
"status": "0",
|
||||
"collectionType": "1",
|
||||
"remark": "备注信息",
|
||||
"createBy": 1,
|
||||
"createTime": "2025-01-27 10:00:00",
|
||||
"updateBy": 1,
|
||||
"updateTime": "2025-01-27 10:00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.3 获取批次组织权限范围 🆕
|
||||
- **接口路径**: GET `/stuColBatchClass/stuColBatchClass/getBatchAuthScope/{batchId}`
|
||||
- **权限标识**: `stuColBatchClass:stuColBatchClass:query`
|
||||
- **功能描述**: 获取用户对应该批次的组织权限范围,返回过滤后的组织树形结构
|
||||
|
||||
**请求参数**:
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| batchId | long | 是 | 批次ID |
|
||||
|
||||
**业务逻辑**:
|
||||
1. **获取用户权限**: 调用 `XgStudentManagerSourceServiceImpl.getCurrentUserAuthDeptTree()` 获取当前用户所有的组织权限信息
|
||||
2. **查询批次班级**: 通过batchId在 `xg_stu_col_batch_class` 中获取class_id数据(过滤条件:`deleted=0`)
|
||||
3. **过滤组织树**: 将第一步获取的树形数据中,非第二步中存在的班级节点都剔除,只保留在第二步中存在的班级ID以及其上级节点,直到树形数据的顶点
|
||||
|
||||
**核心算法**:
|
||||
- 使用递归算法实现树形结构过滤
|
||||
- 保留目标班级节点及其所有上级节点
|
||||
- 确保组织层级关系的完整性
|
||||
|
||||
**响应数据**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "查询成功",
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"label": "计算机学院",
|
||||
"parentId": 101,
|
||||
"deptCategory": "college",
|
||||
"weight": 1,
|
||||
"disabled": false,
|
||||
"children": [
|
||||
{
|
||||
"id": 10,
|
||||
"label": "计算机科学与技术",
|
||||
"parentId": 1,
|
||||
"deptCategory": "major",
|
||||
"weight": 1,
|
||||
"disabled": false,
|
||||
"children": [
|
||||
{
|
||||
"id": 101,
|
||||
"label": "计算机2023-1班",
|
||||
"parentId": 10,
|
||||
"deptCategory": "class",
|
||||
"weight": 1,
|
||||
"disabled": false,
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**异常情况**:
|
||||
- 当批次ID无效时返回空列表
|
||||
- 当用户无权限时返回空列表
|
||||
- 当批次未配置班级权限时返回空列表
|
||||
|
||||
**应用场景**:
|
||||
- 前端组织树选择器的数据源
|
||||
- 批次权限范围可视化显示
|
||||
- 权限验证和范围限制
|
||||
|
||||
## 🔐 权限设计
|
||||
|
||||
### 权限控制策略
|
||||
|
||||
#### 1. 全校学生管理员
|
||||
- **权限范围**: 可以查看和管理所有批次信息
|
||||
- **识别条件**: `xg_student_manager` 表中 `manager_type = '1'`
|
||||
- **部门权限**: 获取 `dept_id = 101` 下所有部门数据
|
||||
|
||||
#### 2. 普通管理员
|
||||
- **权限范围**: 基于 `xg_student_manager_source` 表中的授权范围
|
||||
- **识别条件**: `xg_student_manager` 表中 `manager_type != '1'`
|
||||
- **部门权限**: 根据授权的部门ID获取相关部门树
|
||||
|
||||
#### 3. 班主任
|
||||
- **权限范围**: 只能查看所带班级的批次信息
|
||||
- **识别条件**: `xg_class_msg` 表中 `class_teacher_id` 等于当前用户ID
|
||||
- **自动权限**: 系统自动将班主任权限合并到部门权限中
|
||||
|
||||
### 数据查询策略
|
||||
|
||||
#### 权限范围计算
|
||||
```sql
|
||||
-- 1. 查询用户授权的部门ID
|
||||
SELECT auth_source FROM xg_student_manager_source
|
||||
WHERE manager_id IN (
|
||||
SELECT id FROM xg_student_manager WHERE user_id = #{currentUserId}
|
||||
) AND status = '0' AND deleted = '0'
|
||||
|
||||
-- 2. 查询班主任权限
|
||||
SELECT id FROM xg_class_msg
|
||||
WHERE class_teacher_id = #{currentUserId} AND deleted = '0'
|
||||
|
||||
-- 3. 查询批次信息
|
||||
SELECT DISTINCT b.* FROM xg_stu_col_batch b
|
||||
INNER JOIN xg_stu_col_batch_class bc ON b.id = bc.batch_id
|
||||
WHERE bc.class_id IN (权限范围内的部门ID)
|
||||
AND b.deleted = 0 AND b.status = '0'
|
||||
```
|
||||
|
||||
## 📝 使用示例
|
||||
|
||||
### 前端调用示例
|
||||
|
||||
#### JavaScript/Vue示例
|
||||
```javascript
|
||||
// 查询老师关联的批次信息
|
||||
async function getTeacherBatches(batchName = '') {
|
||||
try {
|
||||
const response = await request({
|
||||
url: '/stuColBatchClass/stuColBatchClass/getTeacherRelatedBatches',
|
||||
method: 'get',
|
||||
params: {
|
||||
batchName: batchName
|
||||
}
|
||||
});
|
||||
|
||||
if (response.code === 200) {
|
||||
console.log('批次列表:', response.data);
|
||||
return response.data;
|
||||
} else {
|
||||
console.error('查询失败:', response.msg);
|
||||
return [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('请求异常:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// 设置批次权限范围
|
||||
async function setBatchScope(batchId, deptIds) {
|
||||
try {
|
||||
const response = await request({
|
||||
url: '/stuColBatchClass/stuColBatchClass/setScope',
|
||||
method: 'post',
|
||||
data: {
|
||||
batchId: batchId,
|
||||
deptIds: deptIds
|
||||
}
|
||||
});
|
||||
|
||||
if (response.code === 200) {
|
||||
console.log('权限设置成功:', response.msg);
|
||||
return true;
|
||||
} else {
|
||||
console.error('设置失败:', response.msg);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('请求异常:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 移动端调用示例
|
||||
|
||||
#### uni-app示例
|
||||
```javascript
|
||||
// 查询学生可参与的批次
|
||||
async function getStudentBatches() {
|
||||
try {
|
||||
const response = await uni.request({
|
||||
url: baseURL + '/stuColBatchClass/stuColBatchClass/getStudentAvailableBatches',
|
||||
method: 'GET',
|
||||
header: {
|
||||
'Authorization': 'Bearer ' + getToken(),
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (response.data.code === 200) {
|
||||
return response.data.data;
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: response.data.msg || '查询失败',
|
||||
icon: 'none'
|
||||
});
|
||||
return [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('请求异常:', error);
|
||||
uni.showToast({
|
||||
title: '网络异常',
|
||||
icon: 'none'
|
||||
});
|
||||
return [];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 数据安全
|
||||
1. **权限验证**: 所有接口都进行严格的权限验证
|
||||
2. **数据过滤**: 用户只能查看权限范围内的数据
|
||||
3. **SQL注入防护**: 使用参数绑定防止SQL注入
|
||||
|
||||
### 性能优化
|
||||
1. **查询优化**: 使用索引优化大数据量查询
|
||||
2. **缓存策略**: 部门权限树适合进行缓存
|
||||
3. **分页查询**: 大列表数据支持分页
|
||||
|
||||
### 错误处理
|
||||
1. **参数验证**: 对所有输入参数进行验证
|
||||
2. **异常捕获**: 完善的异常处理机制
|
||||
3. **友好提示**: 提供用户友好的错误信息
|
||||
|
||||
## 📈 版本更新记录
|
||||
|
||||
### V1.0.0 (2025-01-27)
|
||||
- 🆕 新增 `getTeacherRelatedBatches` 接口
|
||||
- 🔧 修复 `XgStuColBatchVo` 缺少基础字段的问题
|
||||
- 📝 完善API文档和使用示例
|
||||
- 🔐 完善权限控制策略
|
||||
- ✨ 支持全校管理员、普通管理员、班主任三种权限模式
|
||||
@@ -0,0 +1,242 @@
|
||||
# 批次组织权限范围API测试文档
|
||||
|
||||
## 📋 测试信息
|
||||
|
||||
| 项目 | 信息 |
|
||||
|------|------|
|
||||
| **测试接口** | `/stuColBatchClass/stuColBatchClass/getBatchAuthScope/{batchId}` |
|
||||
| **测试模块** | 学生信息收集批次管理 |
|
||||
| **测试时间** | 2025-01-27 |
|
||||
| **测试人员** | cursor |
|
||||
| **测试版本** | V1.0.0 |
|
||||
|
||||
## 🎯 测试目标
|
||||
|
||||
验证批次组织权限范围API接口的功能正确性,确保:
|
||||
1. 用户只能看到有权限访问的组织结构
|
||||
2. 组织树过滤逻辑正确
|
||||
3. 返回的树形结构保持层级关系完整
|
||||
4. 异常情况处理正确
|
||||
|
||||
## 🔧 测试环境
|
||||
|
||||
- **后端服务**: http://localhost:8080
|
||||
- **认证方式**: Bearer Token (Sa-Token)
|
||||
- **测试工具**: Postman / API测试脚本
|
||||
- **数据库**: MySQL 8.0
|
||||
|
||||
## 📊 测试用例
|
||||
|
||||
### 1. 正常功能测试
|
||||
|
||||
#### 1.1 全校学生管理员权限测试
|
||||
**测试步骤**:
|
||||
1. 使用全校学生管理员账号登录
|
||||
2. 调用接口 `GET /stuColBatchClass/stuColBatchClass/getBatchAuthScope/1`
|
||||
3. 验证返回结果
|
||||
|
||||
**预期结果**:
|
||||
- 返回完整的组织树结构
|
||||
- 只包含批次1配置的班级及其上级节点
|
||||
- 所有节点的disabled字段正确设置
|
||||
|
||||
**测试请求**:
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/stuColBatchClass/stuColBatchClass/getBatchAuthScope/1" \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H "clientid: e5cd7e4891bf95d1d19206ce24a7b32e" \
|
||||
-H "referer: http://localhost"
|
||||
```
|
||||
|
||||
#### 1.2 普通管理员权限测试
|
||||
**测试步骤**:
|
||||
1. 使用普通管理员账号登录
|
||||
2. 调用接口测试不同批次ID
|
||||
3. 验证权限过滤效果
|
||||
|
||||
**预期结果**:
|
||||
- 只返回用户有权限的组织部分
|
||||
- 权限范围外的节点被正确过滤
|
||||
- 保持父子节点关系完整
|
||||
|
||||
#### 1.3 班主任权限测试
|
||||
**测试步骤**:
|
||||
1. 使用班主任账号登录
|
||||
2. 调用接口测试
|
||||
3. 验证只返回所带班级的相关信息
|
||||
|
||||
**预期结果**:
|
||||
- 只返回班主任所带班级的组织树
|
||||
- 包含班级的完整上级层级
|
||||
- 其他班级信息被过滤
|
||||
|
||||
### 2. 边界情况测试
|
||||
|
||||
#### 2.1 无效批次ID测试
|
||||
**测试数据**:
|
||||
- 不存在的批次ID: 99999
|
||||
- 无效格式: "abc"
|
||||
- 空值: null
|
||||
|
||||
**预期结果**:
|
||||
- 返回空列表或适当的错误信息
|
||||
- 不会抛出异常
|
||||
|
||||
#### 2.2 批次无权限配置测试
|
||||
**测试条件**: 批次存在但未配置任何班级权限
|
||||
|
||||
**预期结果**:
|
||||
- 返回空列表
|
||||
- 响应码200,消息正常
|
||||
|
||||
#### 2.3 用户无权限测试
|
||||
**测试条件**: 用户对指定批次没有任何权限
|
||||
|
||||
**预期结果**:
|
||||
- 返回空列表
|
||||
- 不暴露其他用户的权限信息
|
||||
|
||||
### 3. 性能测试
|
||||
|
||||
#### 3.1 大数据量测试
|
||||
**测试条件**:
|
||||
- 批次配置包含大量班级(100+)
|
||||
- 用户权限范围较大
|
||||
|
||||
**性能指标**:
|
||||
- 响应时间 < 2秒
|
||||
- 内存使用合理
|
||||
- 无内存泄漏
|
||||
|
||||
#### 3.2 并发测试
|
||||
**测试条件**: 多用户同时调用接口
|
||||
|
||||
**预期结果**:
|
||||
- 并发处理正常
|
||||
- 数据隔离正确
|
||||
- 无数据混乱
|
||||
|
||||
### 4. 数据验证测试
|
||||
|
||||
#### 4.1 树形结构验证
|
||||
**验证项目**:
|
||||
- 父子节点关系正确
|
||||
- 节点ID唯一性
|
||||
- 层级深度合理
|
||||
- children数组结构正确
|
||||
|
||||
#### 4.2 字段完整性验证
|
||||
**验证字段**:
|
||||
- id: 非空,数字类型
|
||||
- label: 非空,字符串类型
|
||||
- parentId: 数字类型或null
|
||||
- deptCategory: 枚举值有效
|
||||
- weight: 数字类型
|
||||
- disabled: 布尔类型
|
||||
- children: 数组类型
|
||||
|
||||
## 📝 测试结果记录
|
||||
|
||||
### 测试执行记录
|
||||
|
||||
| 测试用例 | 执行状态 | 结果 | 备注 |
|
||||
|----------|----------|------|------|
|
||||
| 全校管理员权限 | ⭕ 待执行 | - | - |
|
||||
| 普通管理员权限 | ⭕ 待执行 | - | - |
|
||||
| 班主任权限 | ⭕ 待执行 | - | - |
|
||||
| 无效批次ID | ⭕ 待执行 | - | - |
|
||||
| 无权限配置 | ⭕ 待执行 | - | - |
|
||||
| 性能测试 | ⭕ 待执行 | - | - |
|
||||
|
||||
### 问题记录
|
||||
|
||||
| 问题ID | 问题描述 | 严重级别 | 状态 | 解决方案 |
|
||||
|--------|----------|----------|------|----------|
|
||||
| - | - | - | - | - |
|
||||
|
||||
## 🚀 自动化测试脚本
|
||||
|
||||
```python
|
||||
import requests
|
||||
import json
|
||||
|
||||
class BatchAuthScopeAPITest:
|
||||
def __init__(self, base_url, token):
|
||||
self.base_url = base_url
|
||||
self.headers = {
|
||||
'Authorization': f'Bearer {token}',
|
||||
'clientid': 'e5cd7e4891bf95d1d19206ce24a7b32e',
|
||||
'referer': 'http://localhost',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
def test_get_batch_auth_scope(self, batch_id):
|
||||
"""测试获取批次组织权限范围"""
|
||||
url = f"{self.base_url}/stuColBatchClass/stuColBatchClass/getBatchAuthScope/{batch_id}"
|
||||
|
||||
try:
|
||||
response = requests.get(url, headers=self.headers)
|
||||
print(f"Status Code: {response.status_code}")
|
||||
print(f"Response: {json.dumps(response.json(), indent=2, ensure_ascii=False)}")
|
||||
|
||||
# 验证响应结构
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
assert 'code' in data
|
||||
assert 'data' in data
|
||||
|
||||
# 验证树形结构
|
||||
if data['data']:
|
||||
self.validate_tree_structure(data['data'])
|
||||
|
||||
print("✅ 测试通过")
|
||||
else:
|
||||
print(f"❌ 测试失败: {response.status_code}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 测试异常: {str(e)}")
|
||||
|
||||
def validate_tree_structure(self, tree_data):
|
||||
"""验证树形结构"""
|
||||
for node in tree_data:
|
||||
assert 'id' in node
|
||||
assert 'label' in node
|
||||
assert 'children' in node
|
||||
assert isinstance(node['children'], list)
|
||||
|
||||
# 递归验证子节点
|
||||
if node['children']:
|
||||
self.validate_tree_structure(node['children'])
|
||||
|
||||
# 使用示例
|
||||
if __name__ == "__main__":
|
||||
# 替换为实际的token
|
||||
token = "your_actual_token_here"
|
||||
tester = BatchAuthScopeAPITest("http://localhost:8080", token)
|
||||
|
||||
# 测试不同批次ID
|
||||
test_batch_ids = [1, 2, 99999] # 包含有效和无效ID
|
||||
|
||||
for batch_id in test_batch_ids:
|
||||
print(f"\n🧪 测试批次ID: {batch_id}")
|
||||
tester.test_get_batch_auth_scope(batch_id)
|
||||
```
|
||||
|
||||
## 📈 测试报告
|
||||
|
||||
### 总体评估
|
||||
- **测试覆盖率**: 待完成
|
||||
- **功能完整性**: 待验证
|
||||
- **性能表现**: 待测试
|
||||
- **安全性**: 待评估
|
||||
|
||||
### 改进建议
|
||||
1. 增加缓存机制提升性能
|
||||
2. 完善错误信息的友好性
|
||||
3. 考虑添加批量查询接口
|
||||
4. 优化大数据量场景的处理
|
||||
|
||||
---
|
||||
|
||||
**文档更新时间**: 2025-01-27 22:15:00
|
||||
**下次更新计划**: 测试执行完成后更新结果
|
||||
@@ -0,0 +1,63 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { StuColBatchVO, StuColBatchForm, StuColBatchQuery } from '@/api/stuMsgAudit/types';
|
||||
|
||||
/**
|
||||
* 查询学生信息收集批次列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
|
||||
export const listStuColBatch = (query?: StuColBatchQuery): AxiosPromise<StuColBatchVO[]> => {
|
||||
return request({
|
||||
url: '/XgStuColBatch/stuColBatch/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询学生信息收集批次详细
|
||||
* @param id
|
||||
*/
|
||||
export const getStuColBatch = (id: string | number): AxiosPromise<StuColBatchVO> => {
|
||||
return request({
|
||||
url: '/XgStuColBatch/stuColBatch/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增学生信息收集批次
|
||||
* @param data
|
||||
*/
|
||||
export const addStuColBatch = (data: StuColBatchForm) => {
|
||||
return request({
|
||||
url: '/XgStuColBatch/stuColBatch',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改学生信息收集批次
|
||||
* @param data
|
||||
*/
|
||||
export const updateStuColBatch = (data: StuColBatchForm) => {
|
||||
return request({
|
||||
url: '/XgStuColBatch/stuColBatch',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除学生信息收集批次
|
||||
* @param id
|
||||
*/
|
||||
export const delStuColBatch = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/XgStuColBatch/stuColBatch/' + id,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,101 @@
|
||||
export interface StuColBatchVO {
|
||||
/**
|
||||
* 批次ID
|
||||
*/
|
||||
id: string | number;
|
||||
|
||||
/**
|
||||
* 批次名称
|
||||
*/
|
||||
batchName: string;
|
||||
|
||||
/**
|
||||
* 批次描述
|
||||
*/
|
||||
description: string;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
startTime: string;
|
||||
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
endTime: string;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
status: string;
|
||||
|
||||
/**
|
||||
* 收集类型 1 - 新学期收集 2 - 其他
|
||||
*/
|
||||
collectionType: string;
|
||||
|
||||
}
|
||||
|
||||
export interface StuColBatchForm extends BaseEntity {
|
||||
/**
|
||||
* 批次ID
|
||||
*/
|
||||
id?: string | number;
|
||||
|
||||
/**
|
||||
* 批次名称
|
||||
*/
|
||||
batchName?: string;
|
||||
|
||||
/**
|
||||
* 批次描述
|
||||
*/
|
||||
description?: string;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
startTime?: string;
|
||||
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
endTime?: string;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 收集类型 1 - 新学期收集 2 - 其他
|
||||
*/
|
||||
collectionType?: string;
|
||||
|
||||
}
|
||||
|
||||
export interface StuColBatchQuery extends PageQuery {
|
||||
|
||||
/**
|
||||
* 批次名称
|
||||
*/
|
||||
batchName?: string;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 收集类型 1 - 新学期收集 2 - 其他
|
||||
*/
|
||||
collectionType?: string;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,889 @@
|
||||
<template>
|
||||
<div class="audit-dialog">
|
||||
<el-dialog v-model="visible" :title="title" width="80%" append-to-body :before-close="handleClose" top="5vh" class="audit-dialog-container">
|
||||
<div class="layout-container">
|
||||
<!-- 部门树 -->
|
||||
<div class="left-panel">
|
||||
<el-card shadow="hover">
|
||||
<el-input v-model="deptName" placeholder="请输入院系名称" prefix-icon="Search" clearable />
|
||||
<el-tree
|
||||
ref="deptTreeRef"
|
||||
class="mt-2 dept-tree"
|
||||
node-key="id"
|
||||
:data="deptOptions"
|
||||
:props="{ label: 'label', children: 'children', disabled: 'disabled' }"
|
||||
:expand-on-click-node="false"
|
||||
:filter-node-method="filterNode"
|
||||
highlight-current
|
||||
default-expand-all
|
||||
@node-click="handleNodeClick"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 右侧内容区域 -->
|
||||
<div class="right-panel">
|
||||
<!-- 搜索表单 -->
|
||||
<div class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学生姓名" prop="name">
|
||||
<el-input v-model="queryParams.name" placeholder="请输入学生姓名" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学号" prop="studentNo">
|
||||
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="手机号码" prop="phone">
|
||||
<el-input v-model="queryParams.phone" placeholder="请输入手机号码" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="提交状态" prop="submitStatus">
|
||||
<el-select v-model="queryParams.submitStatus" placeholder="提交状态" clearable>
|
||||
<!-- 3-审核中,4-已驳回,5-已锁定 -->
|
||||
<el-option label="审核中" value="3" />
|
||||
<el-option label="已驳回" value="4" />
|
||||
<el-option label="已锁定" value="5" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24" class="search-btn-container">
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 学生信息表格 -->
|
||||
<el-card shadow="hover">
|
||||
<!-- <template #header>
|
||||
<div class="card-header">
|
||||
<span>学生信息审核 - {{ batchInfo?.batchName || '未知批次' }}</span>
|
||||
<div class="header-actions">
|
||||
<el-button type="success" icon="Check" :disabled="!hasSelected" @click="handleBatchApprove"> 批量通过 </el-button>
|
||||
<el-button type="danger" icon="Close" :disabled="!hasSelected" @click="handleBatchReject"> 批量拒绝 </el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template> -->
|
||||
|
||||
<el-table v-loading="loading" border :data="studentList" @selection-change="handleSelectionChange" class="right-table">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="学号" align="center" prop="studentNo" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="姓名" align="center" prop="name" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="院系" align="center" prop="collegeName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="专业" align="center" prop="majorName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="班级" align="center" prop="className" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="手机号码" align="center" prop="phone" width="120" />
|
||||
<el-table-column label="提交状态" align="center" width="120">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getSubmitStatusType(scope.row.infoStatus)">
|
||||
{{ getSubmitStatusLabel(scope.row.infoStatus) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="提交时间" align="center" prop="createTime" width="160">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="180" class-name="small-padding fixed-right" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="审核" placement="top">
|
||||
<el-button link type="primary" icon="View" @click="handleAuditStudent(scope.row)" :disabled="scope.row.submitStatus === '0'">
|
||||
审核
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="查看详情" placement="top">
|
||||
<el-button link type="info" icon="Document" @click="handleViewDetail(scope.row)"> 详情 </el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" :page="queryParams.pageNum" :limit="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">关 闭</el-button>
|
||||
</div>
|
||||
</template> -->
|
||||
</el-dialog>
|
||||
|
||||
<!-- 学生审核详情弹窗 -->
|
||||
<el-dialog v-model="auditDetailDialog.visible" :title="auditDetailDialog.title" width="800px" append-to-body>
|
||||
<div v-if="currentAuditStudent">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="学号">{{ currentAuditStudent.studentNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">{{ currentAuditStudent.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="院系">{{ currentAuditStudent.collegeName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业">{{ currentAuditStudent.majorName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="班级">{{ currentAuditStudent.className }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号码">{{ currentAuditStudent.phone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="提交状态" :span="2">
|
||||
<el-tag :type="getSubmitStatusType(currentAuditStudent.submitStatus)">
|
||||
{{ getSubmitStatusLabel(currentAuditStudent.submitStatus) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="提交时间" :span="2">
|
||||
{{ parseTime(currentAuditStudent.submitTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div class="mt-4">
|
||||
<el-form ref="auditFormRef" :model="auditForm" label-width="100px">
|
||||
<el-form-item label="审核结果" prop="auditResult">
|
||||
<el-radio-group v-model="auditForm.auditResult">
|
||||
<el-radio label="2">通过</el-radio>
|
||||
<el-radio label="3">不通过</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="审核意见" prop="auditRemark">
|
||||
<el-input v-model="auditForm.auditRemark" type="textarea" :rows="4" placeholder="请输入审核意见" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="auditLoading" type="primary" @click="submitAudit">确定审核</el-button>
|
||||
<el-button @click="auditDetailDialog.visible = false">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 学生信息详情弹窗 -->
|
||||
<component
|
||||
:is="StuMsgDialogComponent"
|
||||
:visible="stuMsgDialogVisible"
|
||||
:title="stuMsgDialogTitle"
|
||||
:batchInfo="stuMsgBatchInfo"
|
||||
@update:visible="stuMsgDialogVisible = $event"
|
||||
@confirm="handleStuMsgConfirm"
|
||||
v-if="StuMsgDialogComponent"
|
||||
/>
|
||||
|
||||
<!-- 学生详情查看弹窗 -->
|
||||
<component
|
||||
:is="StuMsgDetailDialogComponent"
|
||||
:visible="stuMsgDetailDialogVisible"
|
||||
:title="stuMsgDetailDialogTitle"
|
||||
:batchInfo="stuMsgDetailBatchInfo"
|
||||
@update:visible="stuMsgDetailDialogVisible = $event"
|
||||
v-if="StuMsgDetailDialogComponent"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getCurrentInstance, ComponentInternalInstance, ref, reactive, computed, watch, onMounted, nextTick, defineAsyncComponent } from 'vue';
|
||||
import ElMessage from 'element-plus/es/components/message/index';
|
||||
import ElMessageBox from 'element-plus/es/components/message-box/index';
|
||||
// @ts-ignore
|
||||
import request from '@/utils/request';
|
||||
|
||||
// 动态加载StuMsgDialog组件
|
||||
const StuMsgDialogComponent = defineAsyncComponent(() => import('./stuMsgDialog.vue'));
|
||||
// 动态加载学生详情弹窗组件
|
||||
const StuMsgDetailDialogComponent = defineAsyncComponent(() => import('./stuMsgDetailDialog.vue'));
|
||||
|
||||
interface StudentAuditInfo {
|
||||
id: string | number;
|
||||
studentNo: string;
|
||||
name: string;
|
||||
collegeName: string;
|
||||
majorName: string;
|
||||
className: string;
|
||||
phone: string;
|
||||
submitStatus: string;
|
||||
submitTime: string;
|
||||
auditResult?: string;
|
||||
auditRemark?: string;
|
||||
auditTime?: string;
|
||||
}
|
||||
|
||||
interface QueryParams {
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
name: string;
|
||||
studentNo: string;
|
||||
phone: string;
|
||||
submitStatus: string;
|
||||
deptId?: number;
|
||||
}
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
// Props
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
title: string;
|
||||
batchInfo: any;
|
||||
}>();
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
'update:visible': [value: boolean];
|
||||
confirm: [data: any];
|
||||
}>();
|
||||
|
||||
// 响应式数据
|
||||
const visible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value: boolean) => emit('update:visible', value)
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
const total = ref(0);
|
||||
const studentList = ref<StudentAuditInfo[]>([]);
|
||||
const selectedStudents = ref<StudentAuditInfo[]>([]);
|
||||
const deptName = ref('');
|
||||
const deptOptions = ref<any[]>([]);
|
||||
const currentSelectedNode = ref<any>(null);
|
||||
|
||||
// 计算属性
|
||||
const hasSelected = computed(() => selectedStudents.value.length > 0);
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive<QueryParams>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
name: '',
|
||||
studentNo: '',
|
||||
phone: '',
|
||||
submitStatus: '',
|
||||
deptId: undefined
|
||||
});
|
||||
|
||||
// 审核详情弹窗
|
||||
const auditDetailDialog = reactive({
|
||||
visible: false,
|
||||
title: '学生信息审核'
|
||||
});
|
||||
|
||||
const currentAuditStudent = ref<StudentAuditInfo | null>(null);
|
||||
const auditLoading = ref(false);
|
||||
|
||||
const auditForm = reactive({
|
||||
auditResult: '2',
|
||||
auditRemark: ''
|
||||
});
|
||||
|
||||
// Refs
|
||||
const deptTreeRef = ref();
|
||||
const queryFormRef = ref();
|
||||
const auditFormRef = ref();
|
||||
|
||||
// 学生信息详情弹窗相关数据
|
||||
const stuMsgDialogVisible = ref(false);
|
||||
const stuMsgDialogTitle = ref('学生信息审核');
|
||||
const stuMsgBatchInfo = ref<any>(null);
|
||||
|
||||
// 学生详情查看弹窗相关数据
|
||||
const stuMsgDetailDialogVisible = ref(false);
|
||||
const stuMsgDetailDialogTitle = ref('学生信息详情查看');
|
||||
const stuMsgDetailBatchInfo = ref<any>(null);
|
||||
|
||||
/** 通过条件过滤节点 */
|
||||
const filterNode = (value: string, data: any) => {
|
||||
if (!value) return true;
|
||||
return data.label.indexOf(value) !== -1;
|
||||
};
|
||||
|
||||
/** 获取提交状态标签 */
|
||||
const getSubmitStatusLabel = (status: string) => {
|
||||
// 3-审核中,4-已驳回,5-已锁定
|
||||
const statusMap: Record<string, string> = {
|
||||
'3': '审核中',
|
||||
'4': '已驳回',
|
||||
'5': '已锁定'
|
||||
};
|
||||
return statusMap[status] || status;
|
||||
};
|
||||
|
||||
/** 获取提交状态标签类型 */
|
||||
const getSubmitStatusType = (status: string): 'success' | 'info' | 'warning' | 'danger' => {
|
||||
const typeMap: Record<string, 'success' | 'info' | 'warning' | 'danger'> = {
|
||||
'3': 'info',
|
||||
'5': 'success',
|
||||
'4': 'danger'
|
||||
};
|
||||
return typeMap[status] || 'info';
|
||||
};
|
||||
|
||||
/** 格式化时间 */
|
||||
const parseTime = (time: any, pattern?: string): string => {
|
||||
if (!time) return '';
|
||||
|
||||
let date: Date;
|
||||
if (typeof time === 'object') {
|
||||
date = time;
|
||||
} else {
|
||||
if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
|
||||
time = parseInt(time);
|
||||
}
|
||||
if (typeof time === 'number' && time.toString().length === 10) {
|
||||
time = time * 1000;
|
||||
}
|
||||
date = new Date(time);
|
||||
}
|
||||
|
||||
const formatObj: Record<string, number> = {
|
||||
y: date.getFullYear(),
|
||||
m: date.getMonth() + 1,
|
||||
d: date.getDate(),
|
||||
h: date.getHours(),
|
||||
i: date.getMinutes(),
|
||||
s: date.getSeconds(),
|
||||
a: date.getDay()
|
||||
};
|
||||
|
||||
const time_str = pattern || '{y}-{m}-{d} {h}:{i}:{s}';
|
||||
return time_str.replace(/{([ymdhisa])+}/g, (result, key) => {
|
||||
const value = formatObj[key];
|
||||
if (key === 'a') {
|
||||
return ['日', '一', '二', '三', '四', '五', '六'][value];
|
||||
}
|
||||
return value.toString().padStart(2, '0');
|
||||
});
|
||||
};
|
||||
|
||||
/** 查询学生列表 */
|
||||
const getList = async () => {
|
||||
if (!props.batchInfo?.id) return;
|
||||
|
||||
// 如果没有选择部门,则不查询
|
||||
if (!queryParams.deptId) {
|
||||
ElMessage.warning('请先选择部门');
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const params = {
|
||||
batchId: props.batchInfo.id,
|
||||
deptId: queryParams.deptId,
|
||||
name: queryParams.name || undefined,
|
||||
studentNo: queryParams.studentNo || undefined,
|
||||
phone: queryParams.phone || undefined,
|
||||
infoStatus: queryParams.submitStatus || undefined,
|
||||
pageNum: queryParams.pageNum,
|
||||
pageSize: queryParams.pageSize
|
||||
};
|
||||
|
||||
const res = await request({
|
||||
url: '/studentMsgBatch/studentMsgBatch/listByDept',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
|
||||
if (res && res.code === 200) {
|
||||
studentList.value = res.rows || [];
|
||||
total.value = res.total || 0;
|
||||
} else {
|
||||
ElMessage.error(res?.msg || '获取学生列表失败');
|
||||
studentList.value = [];
|
||||
total.value = 0;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取学生列表失败:', error);
|
||||
ElMessage.error('获取学生列表失败');
|
||||
studentList.value = [];
|
||||
total.value = 0;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 查询部门下拉树结构 */
|
||||
const getDeptTree = async () => {
|
||||
try {
|
||||
if (!props.batchInfo?.id) {
|
||||
console.warn('批次ID不存在,无法获取部门树');
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await request({
|
||||
url: `/stuColBatchClass/stuColBatchClass/getBatchAuthScope/${props.batchInfo.id}`,
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
if (res && res.code === 200 && res.data && Array.isArray(res.data)) {
|
||||
deptOptions.value = res.data;
|
||||
// 设置根节点为默认选中节点
|
||||
setDefaultRootNode(res.data);
|
||||
} else {
|
||||
console.warn('部门树数据格式不符合预期:', res);
|
||||
deptOptions.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取部门树失败:', error);
|
||||
deptOptions.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
/** 设置默认根节点并触发查询 */
|
||||
const setDefaultRootNode = (treeData: any[]) => {
|
||||
if (treeData && treeData.length > 0) {
|
||||
// 找到第一个可用的根节点(不是disabled的节点)
|
||||
const findFirstAvailableNode = (nodes: any[]): any => {
|
||||
for (const node of nodes) {
|
||||
if (!node.disabled) {
|
||||
return node;
|
||||
}
|
||||
if (node.children && node.children.length > 0) {
|
||||
const childNode = findFirstAvailableNode(node.children);
|
||||
if (childNode) {
|
||||
return childNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const rootNode = findFirstAvailableNode(treeData);
|
||||
if (rootNode) {
|
||||
// 设置当前选中的节点
|
||||
currentSelectedNode.value = rootNode;
|
||||
queryParams.deptId = rootNode.id;
|
||||
|
||||
// 设置树形控件的当前选中项
|
||||
setTimeout(() => {
|
||||
deptTreeRef.value?.setCurrentKey(rootNode.id);
|
||||
}, 100);
|
||||
|
||||
// 触发数据查询
|
||||
getList();
|
||||
} else {
|
||||
// 如果没有找到可用节点,则直接查询全部数据
|
||||
getList();
|
||||
}
|
||||
} else {
|
||||
// 如果没有树数据,则直接查询全部数据
|
||||
getList();
|
||||
}
|
||||
};
|
||||
|
||||
/** 节点单击事件 */
|
||||
const handleNodeClick = (data: any) => {
|
||||
// alert(1);
|
||||
// console.log('handleNodeClick', data);
|
||||
// 只有班级节点才可以点击(disabled=false的节点)
|
||||
if (data.disabled) {
|
||||
ElMessage.warning('没有该节点权限,请联系管理员');
|
||||
return;
|
||||
}
|
||||
// 保存当前选中的节点信息
|
||||
currentSelectedNode.value = data;
|
||||
queryParams.deptId = data.id;
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
// /** 节点单击事件 */
|
||||
// const handleNodeClick = (data: any) => {
|
||||
// currentSelectedNode.value = data;
|
||||
// queryParams.deptId = data.id;
|
||||
// handleQuery();
|
||||
// };
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
queryParams.pageNum = 1;
|
||||
queryParams.name = '';
|
||||
queryParams.studentNo = '';
|
||||
queryParams.phone = '';
|
||||
queryParams.submitStatus = '';
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: StudentAuditInfo[]) => {
|
||||
selectedStudents.value = selection;
|
||||
};
|
||||
|
||||
/** 审核学生 */
|
||||
const handleAuditStudent = (row: StudentAuditInfo) => {
|
||||
// 打开学生信息详情弹窗进行审核
|
||||
stuMsgBatchInfo.value = {
|
||||
batchId: props.batchInfo?.id,
|
||||
userId: row.id,
|
||||
...props.batchInfo
|
||||
};
|
||||
stuMsgDialogTitle.value = `学生信息审核 - ${row.name}`;
|
||||
stuMsgDialogVisible.value = true;
|
||||
};
|
||||
|
||||
/** 查看学生详情 */
|
||||
const handleViewDetail = (row: StudentAuditInfo) => {
|
||||
// 设置详情弹窗数据
|
||||
stuMsgDetailBatchInfo.value = {
|
||||
id: props.batchInfo?.id,
|
||||
batchId: props.batchInfo?.id,
|
||||
userId: row.id,
|
||||
studentId: row.id,
|
||||
batchName: props.batchInfo?.batchName,
|
||||
...props.batchInfo
|
||||
};
|
||||
stuMsgDetailDialogTitle.value = `学生信息详情 - ${row.name}`;
|
||||
stuMsgDetailDialogVisible.value = true;
|
||||
};
|
||||
|
||||
/** 批量通过 */
|
||||
const handleBatchApprove = async () => {
|
||||
if (selectedStudents.value.length === 0) {
|
||||
ElMessage.warning('请选择要审核的学生');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm('确认批量通过选中的学生信息?', '提示');
|
||||
|
||||
loading.value = true;
|
||||
const studentIds = selectedStudents.value.map((item) => item.id);
|
||||
|
||||
const res = await request({
|
||||
url: '/stuColBatchClass/stuColBatchClass/batchAudit',
|
||||
method: 'post',
|
||||
data: {
|
||||
studentIds,
|
||||
auditResult: '2',
|
||||
auditRemark: '批量审核通过'
|
||||
}
|
||||
});
|
||||
|
||||
if (res && res.code === 200) {
|
||||
ElMessage.success('批量审核成功');
|
||||
await getList();
|
||||
} else {
|
||||
ElMessage.error(res?.msg || '批量审核失败');
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('批量审核失败:', error);
|
||||
ElMessage.error('批量审核失败');
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 批量拒绝 */
|
||||
const handleBatchReject = async () => {
|
||||
if (selectedStudents.value.length === 0) {
|
||||
ElMessage.warning('请选择要审核的学生');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { value: remark } = await ElMessageBox.prompt('请输入拒绝理由', '批量拒绝', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputPlaceholder: '请输入拒绝理由',
|
||||
inputValidator: (val) => {
|
||||
if (!val || val.trim() === '') {
|
||||
return '拒绝理由不能为空';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
loading.value = true;
|
||||
const studentIds = selectedStudents.value.map((item) => item.id);
|
||||
|
||||
const res = await request({
|
||||
url: '/stuColBatchClass/stuColBatchClass/batchAudit',
|
||||
method: 'post',
|
||||
data: {
|
||||
studentIds,
|
||||
auditResult: '3',
|
||||
auditRemark: remark
|
||||
}
|
||||
});
|
||||
|
||||
if (res && res.code === 200) {
|
||||
ElMessage.success('批量审核成功');
|
||||
await getList();
|
||||
} else {
|
||||
ElMessage.error(res?.msg || '批量审核失败');
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('批量审核失败:', error);
|
||||
ElMessage.error('批量审核失败');
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 提交审核 */
|
||||
const submitAudit = async () => {
|
||||
if (!currentAuditStudent.value) return;
|
||||
|
||||
auditFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
try {
|
||||
auditLoading.value = true;
|
||||
|
||||
const res = await request({
|
||||
url: '/stuColBatchClass/stuColBatchClass/auditStudent',
|
||||
method: 'post',
|
||||
data: {
|
||||
studentId: currentAuditStudent.value!.id,
|
||||
auditResult: auditForm.auditResult,
|
||||
auditRemark: auditForm.auditRemark
|
||||
}
|
||||
});
|
||||
|
||||
if (res && res.code === 200) {
|
||||
ElMessage.success('审核成功');
|
||||
auditDetailDialog.visible = false;
|
||||
await getList();
|
||||
} else {
|
||||
ElMessage.error(res?.msg || '审核失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('审核失败:', error);
|
||||
ElMessage.error('审核失败');
|
||||
} finally {
|
||||
auditLoading.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 关闭弹窗 */
|
||||
const handleClose = () => {
|
||||
visible.value = false;
|
||||
// 重置数据
|
||||
studentList.value = [];
|
||||
selectedStudents.value = [];
|
||||
queryParams.pageNum = 1;
|
||||
queryParams.name = '';
|
||||
queryParams.studentNo = '';
|
||||
queryParams.phone = '';
|
||||
queryParams.submitStatus = '';
|
||||
currentSelectedNode.value = null;
|
||||
};
|
||||
|
||||
// 监听弹窗显示状态
|
||||
watch(
|
||||
() => props.visible,
|
||||
(newVal) => {
|
||||
if (newVal && props.batchInfo?.id) {
|
||||
// 弹窗打开时初始化数据
|
||||
getDeptTree().then(() => {
|
||||
getList();
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 监听部门名称筛选
|
||||
watch(deptName, (val) => {
|
||||
deptTreeRef.value?.filter(val);
|
||||
});
|
||||
|
||||
/** 学生信息确认回调 */
|
||||
const handleStuMsgConfirm = (data: any) => {
|
||||
console.log('学生信息已更新:', data);
|
||||
ElMessage.success('学生信息已更新');
|
||||
// 刷新学生列表
|
||||
getList();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 审核弹窗样式 */
|
||||
.audit-dialog :deep(.el-dialog) {
|
||||
margin-top: 5vh !important;
|
||||
margin-bottom: 5vh !important;
|
||||
height: 700px;
|
||||
max-height: 700px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.audit-dialog :deep(.el-dialog__body) {
|
||||
flex: 1;
|
||||
padding: 15px 20px;
|
||||
overflow: hidden;
|
||||
height: calc(100% - 120px);
|
||||
}
|
||||
|
||||
/* 主布局容器 */
|
||||
.layout-container {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
height: 600px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 左侧面板 - 固定宽度300px */
|
||||
.left-panel {
|
||||
width: 350px;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 左侧面板内的卡片容器 */
|
||||
.left-panel .el-card {
|
||||
min-width: 330px;
|
||||
height: 600px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.left-panel .el-card :deep(.el-card__body) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
gap: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 部门树组件样式 */
|
||||
.dept-tree {
|
||||
height: 520px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
width: 100%;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 强制覆盖el-tree的默认样式 */
|
||||
.dept-tree :deep(.el-tree-node) {
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
.dept-tree :deep(.el-tree-node__content) {
|
||||
white-space: nowrap !important;
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis !important;
|
||||
padding-right: 10px !important;
|
||||
}
|
||||
|
||||
.dept-tree :deep(.el-tree-node__content:hover) {
|
||||
background-color: var(--el-tree-node-hover-bg-color, #f5f7fa) !important;
|
||||
}
|
||||
|
||||
.dept-tree :deep(.el-tree-node__label) {
|
||||
white-space: nowrap !important;
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis !important;
|
||||
flex: 1 !important;
|
||||
}
|
||||
|
||||
/* 右侧面板 - 占据剩余空间 */
|
||||
.right-panel {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 600px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 搜索按钮容器样式 */
|
||||
.search-btn-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.search-btn-container .el-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/* 右侧面板内容区域样式 */
|
||||
.right-panel .mb-\[10px\] {
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.right-panel .el-card:last-child {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.right-panel .el-card:last-child :deep(.el-card__body) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.right-panel .el-table {
|
||||
flex: 1;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* 右侧表格样式 */
|
||||
.right-table {
|
||||
height: 420px;
|
||||
}
|
||||
|
||||
.right-table :deep(.el-table__body-wrapper) {
|
||||
overflow-y: auto;
|
||||
max-height: 350px;
|
||||
}
|
||||
|
||||
/* 卡片头部样式 */
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* 响应式适配 */
|
||||
@media (max-width: 1200px) {
|
||||
.layout-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.left-panel {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.left-panel .el-card {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.dept-tree {
|
||||
height: 320px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,773 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
||||
<el-form-item label="批次名称" prop="batchName">
|
||||
<el-input v-model="queryParams.batchName" placeholder="请输入批次名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['XgStuColBatch:stuColBatch:add']">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['XgStuColBatch:stuColBatch:edit']"
|
||||
>修改</el-button
|
||||
>
|
||||
</el-col> -->
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="Delete"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete()"
|
||||
v-hasPermi="['XgStuColBatch:stuColBatch:remove']"
|
||||
>删除</el-button
|
||||
>
|
||||
</el-col> -->
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['XgStuColBatch:stuColBatch:export']">导出</el-button>
|
||||
</el-col> -->
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" border :data="stuColBatchList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="批次ID" align="center" prop="id" v-if="true" />
|
||||
<el-table-column label="批次名称" align="center" prop="batchName" />
|
||||
<el-table-column label="批次描述" align="center" prop="description" />
|
||||
<el-table-column label="开始时间" align="center" prop="startTime" width="200">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结束时间" align="center" prop="endTime" width="200">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">
|
||||
{{ scope.row.status === '0' ? '正常' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="收集类型" align="center" prop="collectionType">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.collectionType === '1' ? 'primary' : 'info'">
|
||||
{{ scope.row.collectionType === '1' ? '新学期收集' : '其他' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="200" class-name="small-padding fixed-right">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看详细" placement="top">
|
||||
<el-button link type="info" plain icon="View" @click="handleView(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="审核管理" placement="top">
|
||||
<el-button link type="warning" plain icon="Document" @click="handleAudit(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
<!-- <el-tooltip content="信息完善" placement="top" v-if="isBatchValid(scope.row)">
|
||||
<el-button link type="success" plain icon="Edit" @click="handleInfoComplete(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="查看提交历史" placement="top">
|
||||
<el-button link type="info" plain icon="Clock" @click="handleViewHistory(scope.row)"></el-button>
|
||||
</el-tooltip> -->
|
||||
|
||||
<!-- <el-tooltip content="修改" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
plain
|
||||
icon="Edit"
|
||||
@click="handleUpdate(scope.row)"
|
||||
v-hasPermi="['XgStuColBatch:stuColBatch:edit']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="收集范围" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="warning"
|
||||
plain
|
||||
icon="Setting"
|
||||
@click="handleScope(scope.row)"
|
||||
v-hasPermi="['XgStuColBatch:stuColBatch:scope']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button
|
||||
link
|
||||
plain
|
||||
type="danger"
|
||||
icon="Delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['XgStuColBatch:stuColBatch:remove']"
|
||||
></el-button>
|
||||
</el-tooltip> -->
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
<!-- 添加或修改学生信息收集批次对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
|
||||
<el-form ref="stuColBatchFormRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="批次名称" prop="batchName">
|
||||
<el-input v-model="form.batchName" placeholder="请输入批次名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="批次描述" prop="description">
|
||||
<el-input v-model="form.description" type="textarea" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开始时间" prop="startTime">
|
||||
<el-date-picker
|
||||
clearable
|
||||
style="width: 100%"
|
||||
v-model="form.startTime"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="请选择开始时间"
|
||||
>
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-date-picker
|
||||
clearable
|
||||
style="width: 100%"
|
||||
v-model="form.endTime"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="请选择结束时间"
|
||||
>
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="form.status" placeholder="请选择状态">
|
||||
<el-option label="正常" value="0" />
|
||||
<el-option label="停用" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="收集类型" prop="collectionType">
|
||||
<el-select v-model="form.collectionType" placeholder="请选择收集类型">
|
||||
<el-option label="新学期收集" value="1" />
|
||||
<el-option label="其他" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 收集范围设置对话框 -->
|
||||
<el-dialog title="设置收集范围" v-model="scopeDialog.visible" width="500px" append-to-body @close="handleScopeDialogClose">
|
||||
<div class="scope-content">
|
||||
<el-tree
|
||||
ref="scopeTreeRef"
|
||||
:data="deptTreeData"
|
||||
show-checkbox
|
||||
default-expand-all
|
||||
node-key="id"
|
||||
:props="{ children: 'children', label: 'label' }"
|
||||
:check-strictly="false"
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="scopeButtonLoading" type="primary" @click="submitScope">确 定</el-button>
|
||||
<el-button @click="cancelScope">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 查看详细对话框 -->
|
||||
<el-dialog title="批次详细信息" v-model="viewDialog.visible" width="800px" append-to-body>
|
||||
<el-tabs v-model="viewActiveTab" type="border-card">
|
||||
<el-tab-pane label="基本信息" name="basic">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="批次ID">{{ viewForm.id }}</el-descriptions-item>
|
||||
<el-descriptions-item label="批次名称">{{ viewForm.batchName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="批次描述" :span="2">{{ viewForm.description || '暂无描述' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="开始时间">{{ parseTime(viewForm.startTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
|
||||
<el-descriptions-item label="结束时间">{{ parseTime(viewForm.endTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="viewForm.status === '0' ? 'success' : 'danger'">
|
||||
{{ viewForm.status === '0' ? '正常' : '停用' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="收集类型">
|
||||
<el-tag :type="viewForm.collectionType === '1' ? 'primary' : 'info'">
|
||||
{{ viewForm.collectionType === '1' ? '新学期收集' : '其他' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ parseTime(viewForm.createTime || '', '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">{{ parseTime(viewForm.updateTime || '', '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-tab-pane>
|
||||
<!-- <el-tab-pane label="权限范围" name="scope">
|
||||
<div class="scope-detail-content">
|
||||
<div v-if="scopeDetailList.length === 0" class="no-data">
|
||||
<el-empty description="暂无权限范围配置" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-alert title="权限范围说明" type="info" :closable="false" show-icon class="mb-3">
|
||||
<template #default> 该批次配置了 {{ scopeDetailList.length }} 个班级的收集权限,具体如下: </template>
|
||||
</el-alert>
|
||||
<el-table :data="scopeDetailList" border stripe>
|
||||
<el-table-column label="序号" type="index" width="60" align="center" />
|
||||
<el-table-column label="学院名称" prop="collegeName" align="center" />
|
||||
<el-table-column label="专业名称" prop="majorName" align="center" />
|
||||
<el-table-column label="班级名称" prop="className" align="center" />
|
||||
<el-table-column label="配置时间" align="center" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane> -->
|
||||
</el-tabs>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancelView">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 学生信息完善弹窗 -->
|
||||
<StuMsgDialog
|
||||
v-model:visible="stuMsgDialog.visible"
|
||||
:title="stuMsgDialog.title"
|
||||
:batch-info="stuMsgDialog.batchInfo"
|
||||
@confirm="handleStuMsgConfirm"
|
||||
@update:visible="handleStuMsgClose"
|
||||
/>
|
||||
|
||||
<!-- 学生信息提交历史详情弹窗 -->
|
||||
<StuMsgDetailDialog
|
||||
v-model:visible="historyDialog.visible"
|
||||
:title="historyDialog.title"
|
||||
:batch-info="historyDialog.batchInfo"
|
||||
@update:visible="handleHistoryDialogClose"
|
||||
/>
|
||||
|
||||
<!-- 审核管理弹窗 -->
|
||||
<AuditDialog
|
||||
v-model:visible="auditDialog.visible"
|
||||
:title="auditDialog.title"
|
||||
:batch-info="auditDialog.batchInfo"
|
||||
@update:visible="handleAuditDialogClose"
|
||||
@confirm="handleAuditConfirm"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="StuColBatch" lang="ts">
|
||||
import { getCurrentInstance, ComponentInternalInstance, ref, reactive, toRefs, onMounted, nextTick } from 'vue';
|
||||
import { getStuColBatch, delStuColBatch, addStuColBatch, updateStuColBatch } from '@/api/stuMsgAudit';
|
||||
import { StuColBatchVO, StuColBatchQuery, StuColBatchForm } from '@/api/stuMsgAudit/types';
|
||||
import { setScope, getScope, getScopeDetail } from '@/api/stuColBatchClass/stuColBatchClass';
|
||||
import request from '@/utils/request';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import type { FormInstance as ElFormInstance } from 'element-plus';
|
||||
import StuMsgDialog from './stuMsgDialog.vue';
|
||||
import StuMsgDetailDialog from './stuMsgDetailDialog.vue';
|
||||
import AuditDialog from './auditDialog.vue';
|
||||
|
||||
interface DialogOption {
|
||||
visible: boolean;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface PageData<T, Q> {
|
||||
form: T;
|
||||
queryParams: Q;
|
||||
rules: Record<string, any[]>;
|
||||
}
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
const stuColBatchList = ref<StuColBatchVO[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const stuColBatchFormRef = ref<ElFormInstance>();
|
||||
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
// 收集范围弹窗相关数据
|
||||
const scopeDialog = reactive({
|
||||
visible: false
|
||||
});
|
||||
const scopeButtonLoading = ref(false);
|
||||
const currentBatch = ref<StuColBatchVO | null>(null);
|
||||
const deptTreeData = ref<any[]>([]);
|
||||
const checkedDeptIds = ref<number[]>([]);
|
||||
const scopeTreeRef = ref();
|
||||
|
||||
// 查看详细弹窗相关数据
|
||||
const viewDialog = reactive({
|
||||
visible: false
|
||||
});
|
||||
const viewForm = ref<StuColBatchVO>({} as StuColBatchVO);
|
||||
const viewActiveTab = ref('basic');
|
||||
const scopeDetailList = ref<any[]>([]);
|
||||
|
||||
// 学生信息完善弹窗相关数据
|
||||
const stuMsgDialog = reactive({
|
||||
visible: false,
|
||||
title: '学生信息完善',
|
||||
batchInfo: null as StuColBatchVO | null
|
||||
});
|
||||
|
||||
// 学生信息提交历史详情弹窗相关数据
|
||||
const historyDialog = reactive({
|
||||
visible: false,
|
||||
title: '学生信息提交历史',
|
||||
batchInfo: null as StuColBatchVO | null
|
||||
});
|
||||
|
||||
// 审核管理弹窗相关数据
|
||||
const auditDialog = reactive({
|
||||
visible: false,
|
||||
title: '审核管理',
|
||||
batchInfo: null as StuColBatchVO | null
|
||||
});
|
||||
|
||||
const initFormData: StuColBatchForm = {
|
||||
id: undefined,
|
||||
batchName: undefined,
|
||||
description: undefined,
|
||||
startTime: new Date()
|
||||
.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false
|
||||
})
|
||||
.replace(/\//g, '-'),
|
||||
endTime: undefined,
|
||||
status: '0',
|
||||
collectionType: '1'
|
||||
};
|
||||
const data = reactive<PageData<StuColBatchForm, StuColBatchQuery>>({
|
||||
form: { ...initFormData },
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
batchName: undefined,
|
||||
status: undefined,
|
||||
collectionType: undefined,
|
||||
params: {}
|
||||
},
|
||||
rules: {
|
||||
id: [{ required: true, message: '批次ID不能为空', trigger: 'blur' }],
|
||||
batchName: [{ required: true, message: '批次名称不能为空', trigger: 'blur' }],
|
||||
startTime: [{ required: true, message: '开始时间不能为空', trigger: 'blur' }],
|
||||
endTime: [{ required: true, message: '结束时间不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }],
|
||||
collectionType: [{ required: true, message: '收集类型 1 - 新学期收集 2 - 其他 不能为空', trigger: 'change' }]
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
/** 查询学生信息收集批次列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
console.log('queryParams.value.batchName', queryParams.value.batchName);
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/stuColBatchClass/stuColBatchClass/getTeacherRelatedBatches',
|
||||
method: 'get',
|
||||
params: {
|
||||
batchName: queryParams.value.batchName || ''
|
||||
}
|
||||
});
|
||||
|
||||
if (res && res.code === 200) {
|
||||
// 将返回的数据转换为表格需要的格式
|
||||
stuColBatchList.value = res.data || [];
|
||||
total.value = res.data ? res.data.length : 0;
|
||||
} else {
|
||||
stuColBatchList.value = [];
|
||||
total.value = 0;
|
||||
proxy?.$modal.msgError(res?.msg || '获取数据失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取学生可参与批次信息失败:', error);
|
||||
stuColBatchList.value = [];
|
||||
total.value = 0;
|
||||
proxy?.$modal.msgError('获取数据失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
reset();
|
||||
dialog.visible = false;
|
||||
};
|
||||
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = { ...initFormData };
|
||||
stuColBatchFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: StuColBatchVO[]) => {
|
||||
ids.value = selection.map((item) => item.id);
|
||||
single.value = selection.length != 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
reset();
|
||||
dialog.visible = true;
|
||||
dialog.title = '添加学生信息收集批次';
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: StuColBatchVO) => {
|
||||
reset();
|
||||
const _id = row?.id || ids.value[0];
|
||||
const res = await getStuColBatch(_id);
|
||||
Object.assign(form.value, res.data);
|
||||
dialog.visible = true;
|
||||
dialog.title = '修改学生信息收集批次';
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
stuColBatchFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
if (form.value.id) {
|
||||
await updateStuColBatch(form.value).finally(() => (buttonLoading.value = false));
|
||||
} else {
|
||||
await addStuColBatch(form.value).finally(() => (buttonLoading.value = false));
|
||||
}
|
||||
proxy?.$modal.msgSuccess('操作成功');
|
||||
dialog.visible = false;
|
||||
await getList();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: StuColBatchVO) => {
|
||||
const _ids = row?.id || ids.value;
|
||||
await proxy?.$modal.confirm('是否确认删除学生信息收集批次编号为"' + row?.batchName + '"的数据项?').finally(() => (loading.value = false));
|
||||
await delStuColBatch(_ids);
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
await getList();
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.download(
|
||||
'XgStuColBatch/stuColBatch/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`stuColBatch_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
|
||||
/** 收集范围按钮操作 */
|
||||
const handleScope = async (row: StuColBatchVO) => {
|
||||
currentBatch.value = row;
|
||||
// 先清空勾选状态
|
||||
checkedDeptIds.value = [];
|
||||
|
||||
await loadDeptTree();
|
||||
await loadCurrentScope(row.id);
|
||||
scopeDialog.visible = true;
|
||||
|
||||
// 使用nextTick确保DOM更新完成后再设置勾选状态
|
||||
await nextTick();
|
||||
if (scopeTreeRef.value && checkedDeptIds.value.length > 0) {
|
||||
scopeTreeRef.value.setCheckedKeys(checkedDeptIds.value);
|
||||
}
|
||||
};
|
||||
|
||||
/** 加载部门树数据 */
|
||||
const loadDeptTree = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const response = await request({
|
||||
url: '/system/user/deptTree',
|
||||
method: 'get',
|
||||
params: {
|
||||
belongDeptId: 101
|
||||
}
|
||||
});
|
||||
if (response && response.code === 200) {
|
||||
deptTreeData.value = response.data || [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取部门树数据失败:', error);
|
||||
ElMessage.error('获取范围选项失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 加载当前批次的收集范围 */
|
||||
const loadCurrentScope = async (batchId: string | number) => {
|
||||
try {
|
||||
checkedDeptIds.value = [];
|
||||
const response = await getScope(batchId);
|
||||
if (response && response.code === 200) {
|
||||
checkedDeptIds.value = response.data || [];
|
||||
} else {
|
||||
checkedDeptIds.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载收集范围失败:', error);
|
||||
checkedDeptIds.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
/** 构建树形数据 */
|
||||
const buildTree = (data: any[]) => {
|
||||
// 如果数据已经是树形结构,直接转换格式
|
||||
const convertToTreeNode = (item: any): any => {
|
||||
return {
|
||||
id: item.deptId,
|
||||
label: item.deptName,
|
||||
children: item.children ? item.children.map(convertToTreeNode) : []
|
||||
};
|
||||
};
|
||||
|
||||
return data.map(convertToTreeNode);
|
||||
};
|
||||
|
||||
/** 提交收集范围 */
|
||||
const submitScope = async () => {
|
||||
if (!currentBatch.value) return;
|
||||
|
||||
try {
|
||||
scopeButtonLoading.value = true;
|
||||
const checkedNodes = scopeTreeRef.value.getCheckedNodes();
|
||||
const checkedIds = checkedNodes.map((node: any) => node.id);
|
||||
|
||||
// 调用后端API设置收集范围
|
||||
const response = await setScope(currentBatch.value.id, checkedIds);
|
||||
|
||||
if (response && response.code === 200) {
|
||||
ElMessage.success(response.msg || '收集范围设置成功');
|
||||
scopeDialog.visible = false;
|
||||
// 提交成功后清空状态
|
||||
handleScopeDialogClose();
|
||||
} else {
|
||||
ElMessage.error(response?.msg || '设置收集范围失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('设置收集范围失败:', error);
|
||||
ElMessage.error('设置收集范围失败');
|
||||
} finally {
|
||||
scopeButtonLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 取消收集范围设置 */
|
||||
const cancelScope = () => {
|
||||
scopeDialog.visible = false;
|
||||
currentBatch.value = null;
|
||||
checkedDeptIds.value = [];
|
||||
// 清空树形组件的勾选状态
|
||||
if (scopeTreeRef.value) {
|
||||
scopeTreeRef.value.setCheckedKeys([]);
|
||||
}
|
||||
};
|
||||
|
||||
/** 处理收集范围弹窗关闭 */
|
||||
const handleScopeDialogClose = () => {
|
||||
// 清空相关状态
|
||||
currentBatch.value = null;
|
||||
checkedDeptIds.value = [];
|
||||
// 清空树形组件的勾选状态
|
||||
if (scopeTreeRef.value) {
|
||||
scopeTreeRef.value.setCheckedKeys([]);
|
||||
}
|
||||
};
|
||||
|
||||
/** 查看详细按钮操作 */
|
||||
const handleView = async (row: StuColBatchVO) => {
|
||||
try {
|
||||
// 获取批次基本信息
|
||||
const res = await getStuColBatch(row?.id);
|
||||
viewForm.value = res.data;
|
||||
|
||||
// 获取权限范围详细信息
|
||||
// await loadScopeDetail(row.id);
|
||||
|
||||
viewDialog.visible = true;
|
||||
viewActiveTab.value = 'basic';
|
||||
} catch (error) {
|
||||
console.error('获取批次详情失败:', error);
|
||||
proxy?.$modal.msgError('获取批次详情失败');
|
||||
}
|
||||
};
|
||||
|
||||
/** 加载权限范围详细信息 */
|
||||
const loadScopeDetail = async (batchId: string | number) => {
|
||||
try {
|
||||
const response = await getScopeDetail(batchId);
|
||||
if (response && response.code === 200) {
|
||||
scopeDetailList.value = response.data || [];
|
||||
} else {
|
||||
scopeDetailList.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载权限范围详情失败:', error);
|
||||
scopeDetailList.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
/** 取消查看详细 */
|
||||
const cancelView = () => {
|
||||
viewDialog.visible = false;
|
||||
viewForm.value = {} as StuColBatchVO;
|
||||
scopeDetailList.value = [];
|
||||
viewActiveTab.value = 'basic';
|
||||
};
|
||||
|
||||
/** 信息完善按钮操作 */
|
||||
const handleInfoComplete = (row: StuColBatchVO) => {
|
||||
console.log('信息完善按钮点击,批次ID:', row.id, '批次名称:', row.batchName);
|
||||
stuMsgDialog.batchInfo = row;
|
||||
stuMsgDialog.title = `信息完善 - ${row.batchName}`;
|
||||
stuMsgDialog.visible = true;
|
||||
};
|
||||
|
||||
/** 学生信息完善弹窗确认 */
|
||||
const handleStuMsgConfirm = (formData: any) => {
|
||||
console.log('学生信息完善提交:', formData);
|
||||
// TODO: 调用API保存学生信息
|
||||
ElMessage.success('学生信息完善成功');
|
||||
};
|
||||
|
||||
/** 关闭学生信息完善弹窗 */
|
||||
const handleStuMsgClose = () => {
|
||||
stuMsgDialog.visible = false;
|
||||
stuMsgDialog.batchInfo = null;
|
||||
};
|
||||
|
||||
/** 查看提交历史按钮操作 */
|
||||
const handleViewHistory = (row: StuColBatchVO) => {
|
||||
console.log('查看提交历史按钮点击,批次ID:', row.id, '批次名称:', row.batchName);
|
||||
|
||||
historyDialog.batchInfo = row;
|
||||
historyDialog.title = `学生信息提交历史 - ${row.batchName}`;
|
||||
historyDialog.visible = true;
|
||||
};
|
||||
|
||||
/** 关闭学生信息提交历史详情弹窗 */
|
||||
const handleHistoryDialogClose = () => {
|
||||
historyDialog.visible = false;
|
||||
historyDialog.batchInfo = null;
|
||||
};
|
||||
|
||||
/** 审核管理按钮操作 */
|
||||
const handleAudit = (row: StuColBatchVO) => {
|
||||
console.log('审核管理按钮点击,批次ID:', row.id, '批次名称:', row.batchName);
|
||||
|
||||
auditDialog.batchInfo = row;
|
||||
auditDialog.title = `审核管理 - ${row.batchName}`;
|
||||
auditDialog.visible = true;
|
||||
};
|
||||
|
||||
/** 审核弹窗确认 */
|
||||
const handleAuditConfirm = (data: any) => {
|
||||
console.log('审核弹窗确认:', data);
|
||||
ElMessage.success('审核操作成功');
|
||||
};
|
||||
|
||||
/** 关闭审核管理弹窗 */
|
||||
const handleAuditDialogClose = () => {
|
||||
auditDialog.visible = false;
|
||||
auditDialog.batchInfo = null;
|
||||
};
|
||||
|
||||
/** 检查批次是否有效(在有效时间内且状态正常) */
|
||||
const isBatchValid = (row: StuColBatchVO): boolean => {
|
||||
// 检查状态是否为正常
|
||||
if (row.status !== '0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否在有效时间内
|
||||
const now = new Date();
|
||||
const startTime = new Date(row.startTime);
|
||||
const endTime = new Date(row.endTime);
|
||||
|
||||
return now >= startTime && now <= endTime;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.scope-detail-content {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.mb-3 {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -20,7 +20,7 @@
|
||||
<div class="right-arrow">→</div>
|
||||
</div>
|
||||
|
||||
<div class="history-card">
|
||||
<div class="history-card" :class="{ 'rejected-card': record.infoStatus === '4', 'approved-card': record.infoStatus === '5' }">
|
||||
<div class="card-header">
|
||||
<span class="operator">{{ record.createByName || '系统' }}</span>
|
||||
<span class="operate-time">{{ formatDateTime(record.createTime) }}</span>
|
||||
@@ -719,7 +719,7 @@
|
||||
|
||||
<script setup lang="ts" name="StuMsgDetailDialog">
|
||||
import { ref, reactive, getCurrentInstance, nextTick, watch, toRefs } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import ElMessage from 'element-plus/es/components/message/index';
|
||||
// @ts-ignore
|
||||
import request from '@/utils/request';
|
||||
// 获取当前组件实例
|
||||
@@ -1305,6 +1305,28 @@ const handleClose = () => {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* 驳回状态卡片样式 */
|
||||
.rejected-card {
|
||||
background-color: #fef0f0 !important;
|
||||
border-color: #fbc4c4 !important;
|
||||
}
|
||||
|
||||
.rejected-card:hover {
|
||||
background-color: #fde2e2 !important;
|
||||
border-color: #f87171 !important;
|
||||
}
|
||||
|
||||
/* 通过状态卡片样式 */
|
||||
.approved-card {
|
||||
background-color: #f0fdf4 !important;
|
||||
border-color: #a7f3d0 !important;
|
||||
}
|
||||
|
||||
.approved-card:hover {
|
||||
background-color: #ecfdf5 !important;
|
||||
border-color: #34d399 !important;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<div class="right-arrow">→</div>
|
||||
</div>
|
||||
|
||||
<div class="history-card">
|
||||
<div class="history-card" :class="{ 'rejected-card': record.infoStatus === '4', 'approved-card': record.infoStatus === '5' }">
|
||||
<div class="card-header">
|
||||
<span class="operator">{{ record.createByName || '系统' }}</span>
|
||||
<span class="operate-time">{{ formatDateTime(record.createTime) }}</span>
|
||||
@@ -724,7 +724,7 @@
|
||||
|
||||
<script setup lang="ts" name="StuMsgDialog">
|
||||
import { ref, reactive, getCurrentInstance, nextTick, watch, toRefs } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import ElMessage from 'element-plus/es/components/message/index';
|
||||
import request from '@/utils/request';
|
||||
// 获取当前组件实例
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
@@ -1416,6 +1416,28 @@ const handleClose = () => {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* 驳回状态卡片样式 */
|
||||
.rejected-card {
|
||||
background-color: #fef0f0 !important;
|
||||
border-color: #fbc4c4 !important;
|
||||
}
|
||||
|
||||
.rejected-card:hover {
|
||||
background-color: #fde2e2 !important;
|
||||
border-color: #f87171 !important;
|
||||
}
|
||||
|
||||
/* 通过状态卡片样式 */
|
||||
.approved-card {
|
||||
background-color: #f0fdf4 !important;
|
||||
border-color: #a7f3d0 !important;
|
||||
}
|
||||
|
||||
.approved-card:hover {
|
||||
background-color: #ecfdf5 !important;
|
||||
border-color: #34d399 !important;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
Reference in new issue
Block a user