提交班干部管理

This commit is contained in:
zlwu committed 2025-07-29 16:37:42 +08:00
1 parent 0837e6defb
commit 61f681c339
37 files changed
+2617 -235

No files matched your search

@@ -42,7 +42,7 @@ public class HqPmRepairAttachmentController extends BaseController {
/**
* 查询报修附件列表
*/
// @SaCheckPermission("pm:attachment:list")
@SaCheckPermission("pm:attachment:list")
@GetMapping("/list")
public TableDataInfo<HqPmRepairAttachmentVo> list(HqPmRepairAttachmentBo bo, PageQuery pageQuery) {
return attachmentService.queryPageList(bo, pageQuery);
@@ -51,7 +51,7 @@ public class HqPmRepairAttachmentController extends BaseController {
/**
* 根据工单ID获取附件列表
*/
// @SaCheckPermission("pm:attachment:list")
@SaCheckPermission("pm:attachment:list")
@GetMapping("/order/{orderId}")
public R<List<HqPmRepairAttachmentVo>> getByOrderId(@PathVariable("orderId") Long orderId) {
List<HqPmRepairAttachmentVo> list = attachmentService.queryByOrderId(orderId);
@@ -61,7 +61,7 @@ public class HqPmRepairAttachmentController extends BaseController {
/**
* 获取报修附件详细信息
*/
// @SaCheckPermission("pm:attachment:query")
@SaCheckPermission("pm:attachment:query")
@GetMapping("/{id}")
public R<HqPmRepairAttachmentVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
return R.ok(attachmentService.queryById(id));
@@ -70,7 +70,7 @@ public class HqPmRepairAttachmentController extends BaseController {
/**
* 新增报修附件
*/
// @SaCheckPermission("pm:attachment:add")
@SaCheckPermission("pm:attachment:add")
@Log(title = "报修附件", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping
@@ -81,7 +81,7 @@ public class HqPmRepairAttachmentController extends BaseController {
/**
* 修改报修附件
*/
// @SaCheckPermission("pm:attachment:edit")
@SaCheckPermission("pm:attachment:edit")
@Log(title = "报修附件", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping
@@ -93,7 +93,7 @@ public class HqPmRepairAttachmentController extends BaseController {
* 删除报修附件
* //
*/
// @SaCheckPermission("pm:attachment:remove")
@SaCheckPermission("pm:attachment:remove")
@Log(title = "报修附件", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
@@ -103,7 +103,7 @@ public class HqPmRepairAttachmentController extends BaseController {
/**
* 上传单个附件文件
*/
// @SaCheckPermission("pm:attachment:upload")
@SaCheckPermission("pm:attachment:upload")
@Log(title = "上传报修附件", businessType = BusinessType.INSERT)
@PostMapping("/upload")
public R<HqPmRepairAttachmentVo> uploadFile(
@@ -127,7 +127,7 @@ public class HqPmRepairAttachmentController extends BaseController {
/**
* 批量上传附件文件
*/
// @SaCheckPermission("pm:attachment:upload")
@SaCheckPermission("pm:attachment:upload")
@Log(title = "批量上传报修附件", businessType = BusinessType.INSERT)
@PostMapping("/upload/batch")
public R<List<HqPmRepairAttachmentVo>> uploadFiles(
@@ -152,7 +152,7 @@ public class HqPmRepairAttachmentController extends BaseController {
/**
* 删除附件文件(包括OSS文件和数据库记录)
*/
// @SaCheckPermission("pm:attachment:remove")
@SaCheckPermission("pm:attachment:remove")
@Log(title = "删除报修附件文件", businessType = BusinessType.DELETE)
@DeleteMapping("/file/{id}")
public R<Void> deleteAttachment(@PathVariable("id") Long id) {
@@ -162,7 +162,7 @@ public class HqPmRepairAttachmentController extends BaseController {
/**
* 下载附件文件
*/
// @SaCheckPermission("pm:attachment:download")
@SaCheckPermission("pm:attachment:download")
@GetMapping("/download/{id}")
public void downloadFile(@PathVariable("id") Long id, HttpServletResponse response) {
HqPmRepairAttachmentVo attachment = attachmentService.queryById(id);
@@ -186,7 +186,7 @@ public class HqPmRepairAttachmentController extends BaseController {
/**
* 更新附件的工单ID(用于新增工单时关联已上传的附件)
*/
// @SaCheckPermission("pm:attachment:edit")
@SaCheckPermission("pm:attachment:edit")
@Log(title = "更新附件工单ID", businessType = BusinessType.UPDATE)
@PutMapping("/{attachmentId}/order/{orderId}")
public R<Void> updateAttachmentOrderId(@PathVariable("attachmentId") Long attachmentId,
@@ -197,7 +197,7 @@ public class HqPmRepairAttachmentController extends BaseController {
/**
* 批量更新附件的工单ID(用于新增工单时关联已上传的附件)
*/
// @SaCheckPermission("pm:attachment:edit")
@SaCheckPermission("pm:attachment:edit")
@Log(title = "批量更新附件工单ID", businessType = BusinessType.UPDATE)
@PutMapping("/batch-update-order")
public R<Void> batchUpdateAttachmentOrderId(@RequestBody BatchUpdateOrderIdRequest request) {
@@ -135,7 +135,7 @@ public class HqSatSurveyController extends BaseController {
*
* @param surveyId 问卷ID
*/
// @SaCheckPermission("hq:satSurvey:question")
@SaCheckPermission("hq:satSurvey:question")
@GetMapping("/{surveyId}/questions")
public R<List<HqSatSurveyQuestionVo>> getSurveyQuestions(
@NotNull(message = "问卷ID不能为空") @PathVariable Long surveyId) {
@@ -104,7 +104,7 @@ public class SysRegionController extends BaseController {
/**
* 获取省市区树形数据
*/
// @SaCheckPermission("system:SysRegion:list")
@SaCheckPermission("system:SysRegion:list")
@GetMapping("/tree")
public R<List<SysRegionVo>> getRegionTree() {
List<SysRegionVo> tree = sysRegionService.getRegionTree();
@@ -63,8 +63,7 @@ public class XgClassPosDictController extends BaseController {
*/
@SaCheckPermission("xgClassPosDict:xgClassPosDict:query")
@GetMapping("/{id}")
public R<XgClassPosDictVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
public R<XgClassPosDictVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
return R.ok(xgClassPosDictService.queryById(id));
}
@@ -98,8 +97,24 @@ public class XgClassPosDictController extends BaseController {
@SaCheckPermission("xgClassPosDict:xgClassPosDict:remove")
@Log(title = "班级干部职务配置", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
return toAjax(xgClassPosDictService.deleteWithValidByIds(List.of(ids), true));
}
/**
* 获取有效的职务信息列表
*
* @param positionName 职务名称(非必填,支持模糊查询)
* @return 有效的职务信息列表
*/
@SaCheckPermission("xgClassPosDict:xgClassPosDict:list")
@GetMapping("/validPositions")
public R<List<XgClassPosDictVo>> getValidPositions(@RequestParam(required = false) String positionName) {
XgClassPosDictBo bo = new XgClassPosDictBo();
bo.setPositionName(positionName);
bo.setStatus("0"); // 启用状态
bo.setDeleted(0L); // 未删除
List<XgClassPosDictVo> list = xgClassPosDictService.queryValidPositions(bo);
return R.ok(list);
}
}
@@ -22,6 +22,8 @@ import org.dromara.xg.domain.bo.XgClassPosMsgBo;
import org.dromara.xg.service.IXgClassPosMsgService;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import java.util.Date;
/**
* 班级干部信息
*
@@ -38,11 +40,13 @@ public class XgClassPosMsgController extends BaseController {
/**
* 查询班级干部信息列表
* 增加一个参数 deptId
*/
@SaCheckPermission("xgClassPosMsg:xgClassPosMsg:list")
@GetMapping("/list")
public TableDataInfo<XgClassPosMsgVo> list(XgClassPosMsgBo bo, PageQuery pageQuery) {
return xgClassPosMsgService.queryPageList(bo, pageQuery);
public TableDataInfo<XgClassPosMsgVo> list(XgClassPosMsgBo bo, PageQuery pageQuery,
@RequestParam(required = false) Long deptId) {
return xgClassPosMsgService.queryPageList(bo, pageQuery, deptId);
}
/**
@@ -63,8 +67,7 @@ public class XgClassPosMsgController extends BaseController {
*/
@SaCheckPermission("xgClassPosMsg:xgClassPosMsg:query")
@GetMapping("/{id}")
public R<XgClassPosMsgVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
public R<XgClassPosMsgVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
return R.ok(xgClassPosMsgService.queryById(id));
}
@@ -98,8 +101,27 @@ public class XgClassPosMsgController extends BaseController {
@SaCheckPermission("xgClassPosMsg:xgClassPosMsg:remove")
@Log(title = "班级干部信息", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
return toAjax(xgClassPosMsgService.deleteWithValidByIds(List.of(ids), true));
}
/**
* 学生干部离任
*
* @param id 班级干部信息ID
* @param termEndDate 任期结束日期
* @param cadreStatus 学生职务任职状态
* @param dismissalReason 离任原因
* @return 操作结果
*/
@SaCheckPermission("xgClassPosMsg:xgClassPosMsg:edit")
@Log(title = "学生干部离任", businessType = BusinessType.UPDATE)
@PostMapping("/dismiss/{id}")
public R<Void> dismissCadre(
@NotNull(message = "班级干部信息ID不能为空") @PathVariable Long id,
@NotNull(message = "任期结束日期不能为空") @RequestParam Date termEndDate,
@NotBlank(message = "学生职务任职状态不能为空") @RequestParam String cadreStatus,
@NotBlank(message = "离任原因不能为空") @RequestParam String dismissalReason) {
return toAjax(xgClassPosMsgService.dismissCadre(id, termEndDate, cadreStatus, dismissalReason));
}
}
@@ -30,7 +30,7 @@ public class XgDormBuildingTreeController extends BaseController {
* 获取宿舍楼树形结构数据
* 校区-宿舍区-宿舍楼三级结构,校区和宿舍区不可选,只有宿舍楼可以选择
*/
// @SaCheckPermission("xgss:dormBuildingTree:list")
@SaCheckPermission("xgss:dormBuildingTree:list")
@GetMapping("/list")
public R<List<DormBuildingTreeVo>> getDormBuildingTree(DormBuildingTreeBo bo) {
List<DormBuildingTreeVo> treeData = xgDormBuildingTreeService.getDormBuildingTree(bo);
@@ -209,7 +209,7 @@ public class XgStuColBatchClassController extends BaseController {
* 查询学生可参与的批次信息
* 通过xg_student_msg、xg_stu_col_batch_class、xg_stu_col_batch三张表关联查询
*/
// @SaCheckPermission("stuColBatchClass:stuColBatchClass:query")
@SaCheckPermission("stuColBatchClass:stuColBatchClass:query")
@GetMapping("/getStudentAvailableBatches")
public R<List<Map<String, Object>>> getStudentAvailableBatches(
@RequestParam(value = "batchName", required = false) String batchName) {
@@ -107,7 +107,7 @@ public class XgStudentManagerController extends BaseController {
*
* @param managerType 管理员类型(1 全校管理员 2 自定义管理员 3 班主任)
*/
// @SaCheckPermission("xgStudentManager:studentManager:list")
@SaCheckPermission("xgStudentManager:studentManager:list")
@GetMapping("/listAvailableTeachers")
public R<List<XgStudentManagerVo>> listAvailableTeachers(@RequestParam("managerType") String managerType) {
List<XgStudentManagerVo> teachers = xgStudentManagerService.queryAvailableTeachers(managerType);
@@ -224,4 +224,20 @@ public class XgStudentMsgController extends BaseController {
}
return R.ok(personalInfo);
}
/**
* 查询班级中所有学生信息
*
* @param classId 班级ID
* @param name 学生姓名(模糊查询)
* @return 学生信息列表
*/
@SaCheckPermission("studentMsg:studentMsg:query")
@GetMapping("/listByClassId")
public R<List<XgStudentMsgVo>> getStudentsByClassId(
@RequestParam(required = true) String classId,
@RequestParam(required = false) String name) {
List<XgStudentMsgVo> students = xgStudentMsgService.queryStudentsByClassId(classId, name);
return R.ok(students);
}
}
@@ -55,6 +55,11 @@ public class XgClassPosMsg extends TenantEntity {
*/
private String studentName;
/**
* 职务编码id,关联xg_class_pos_dict.id
*/
private Long positionId;
/**
* 职务编码,关联xg_class_pos_dict.position_code
*/
@@ -57,6 +57,12 @@ public class XgClassPosMsgBo extends BaseEntity {
@NotBlank(message = "学生姓名(冗余字段)不能为空", groups = { AddGroup.class, EditGroup.class })
private String studentName;
/**
* 职务编码,关联xg_class_pos_dict.id
*/
@NotNull(message = "职务编码id,关联xg_class_pos_dict.id不能为空", groups = { AddGroup.class, EditGroup.class })
private Long positionId;
/**
* 职务编码,关联xg_class_pos_dict.position_code
*/
@@ -120,5 +126,4 @@ public class XgClassPosMsgBo extends BaseEntity {
*/
private String remark;
}
@@ -14,8 +14,6 @@ import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 班级干部信息视图对象 xg_class_pos_msg
*
@@ -50,6 +48,12 @@ public class XgClassPosMsgVo implements Serializable {
@ExcelProperty(value = "班级ID,关联xg_class_msg.id")
private Long classId;
/**
* 班级名称(联表查询获取)
*/
@ExcelProperty(value = "班级名称")
private String className;
/**
* 学生ID,关联xg_student_msg.id
*/
@@ -69,12 +73,24 @@ public class XgClassPosMsgVo implements Serializable {
@ExcelDictFormat(readConverterExp = "冗=余字段")
private String studentName;
/**
* 职务编码id,关联xg_class_pos_dict.id
*/
@ExcelProperty(value = "职务编码id,关联xg_class_pos_dict.id")
private Long positionId;
/**
* 职务编码,关联xg_class_pos_dict.position_code
*/
@ExcelProperty(value = "职务编码,关联xg_class_pos_dict.position_code")
private String positionCode;
/**
* 职务名称(联表查询获取)
*/
@ExcelProperty(value = "职务名称")
private String positionName;
/**
* 任职日期
*/
@@ -145,5 +161,30 @@ public class XgClassPosMsgVo implements Serializable {
@ExcelProperty(value = "备注信息")
private String remark;
/**
* 创建者
*/
@ExcelProperty(value = "创建者")
private Long createBy;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ExcelProperty(value = "创建时间")
private Date createTime;
/**
* 更新者
*/
@ExcelProperty(value = "更新者")
private Long updateBy;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ExcelProperty(value = "更新时间")
private Date updateTime;
}
@@ -3,6 +3,10 @@ package org.dromara.xg.mapper;
import org.dromara.xg.domain.XgClassPosMsg;
import org.dromara.xg.domain.vo.XgClassPosMsgVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
/**
* 班级干部信息Mapper接口
@@ -12,4 +16,31 @@ import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
*/
public interface XgClassPosMsgMapper extends BaseMapperPlus<XgClassPosMsg, XgClassPosMsgVo> {
/**
* 分页查询班级干部信息(使用XML联表查询)
*
* @param page 分页参数
* @param queryWrapper 查询条件
* @return 分页结果
*/
Page<XgClassPosMsgVo> selectVoPageWithJoin(Page<XgClassPosMsgVo> page,
@org.apache.ibatis.annotations.Param("ew") Wrapper<XgClassPosMsg> queryWrapper);
/**
* 查询班级干部信息列表(使用XML联表查询)
*
* @param queryWrapper 查询条件
* @return 列表结果
*/
List<XgClassPosMsgVo> selectVoListWithJoin(
@org.apache.ibatis.annotations.Param("ew") Wrapper<XgClassPosMsg> queryWrapper);
/**
* 根据ID查询班级干部信息(使用XML联表查询)
*
* @param id 主键
* @return 班级干部信息
*/
XgClassPosMsgVo selectVoByIdWithJoin(Long id);
}
@@ -29,4 +29,13 @@ public interface XgStudentMsgMapper extends BaseMapperPlus<XgStudentMsg, XgStude
@Param("ew") LambdaQueryWrapper<XgStudentMsg> queryWrapper,
@Param("deptIds") List<Long> deptIds);
/**
* 根据班级ID和姓名查询学生信息列表
*
* @param classId 班级ID
* @param name 学生姓名(模糊查询)
* @return 学生信息列表
*/
List<XgStudentMsgVo> selectStudentsByClassId(@Param("classId") String classId, @Param("name") String name);
}
@@ -65,4 +65,12 @@ public interface IXgClassPosDictService {
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 查询有效的职务信息列表
*
* @param bo 查询条件
* @return 有效的职务信息列表
*/
List<XgClassPosDictVo> queryValidPositions(XgClassPosDictBo bo);
}
@@ -29,9 +29,10 @@ public interface IXgClassPosMsgService {
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @param deptId 部门ID
* @return 班级干部信息分页列表
*/
TableDataInfo<XgClassPosMsgVo> queryPageList(XgClassPosMsgBo bo, PageQuery pageQuery);
TableDataInfo<XgClassPosMsgVo> queryPageList(XgClassPosMsgBo bo, PageQuery pageQuery, Long deptId);
/**
* 查询符合条件的班级干部信息列表
@@ -65,4 +66,15 @@ public interface IXgClassPosMsgService {
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 学生干部离任
*
* @param id 班级干部信息ID
* @param termEndDate 任期结束日期
* @param cadreStatus 学生职务任职状态
* @param dismissalReason 离任原因
* @return 是否离任成功
*/
Boolean dismissCadre(Long id, java.util.Date termEndDate, String cadreStatus, String dismissalReason);
}
@@ -85,4 +85,13 @@ public interface IXgStudentMsgService {
* @return 学生信息
*/
XgStudentMsgVo queryPersonalInfo(Long userId);
/**
* 根据班级ID和姓名查询学生信息列表
*
* @param classId 班级ID
* @param name 学生姓名(模糊查询)
* @return 学生信息列表
*/
List<XgStudentMsgVo> queryStudentsByClassId(String classId, String name);
}
@@ -337,4 +337,27 @@ public class XgClassPosDictServiceImpl implements IXgClassPosDictService {
}
}
}
/**
* 查询有效的职务信息列表
*
* @param bo 查询条件
* @return 有效的职务信息列表
*/
@Override
public List<XgClassPosDictVo> queryValidPositions(XgClassPosDictBo bo) {
LambdaQueryWrapper<XgClassPosDict> lqw = Wrappers.lambdaQuery();
// 设置基础过滤条件:启用状态且未删除
lqw.eq(XgClassPosDict::getStatus, "0"); // 启用状态
lqw.eq(XgClassPosDict::getDeleted, 0L); // 未删除
// 根据职务名称进行模糊查询(如果提供)
lqw.like(StringUtils.isNotBlank(bo.getPositionName()), XgClassPosDict::getPositionName, bo.getPositionName());
// 按创建时间升序排序
lqw.orderByAsc(XgClassPosDict::getCreateTime);
return baseMapper.selectVoList(lqw);
}
}
@@ -10,15 +10,25 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.dromara.xg.domain.bo.XgClassPosMsgBo;
import org.dromara.xg.domain.vo.XgClassPosMsgVo;
import org.dromara.xg.domain.XgClassPosMsg;
import org.dromara.xg.mapper.XgClassPosMsgMapper;
import org.dromara.xg.service.IXgClassPosMsgService;
import cn.hutool.core.util.ObjectUtil;
import org.dromara.system.domain.SysDept;
import org.dromara.system.domain.SysUserRole;
import org.dromara.system.mapper.SysDeptMapper;
import org.dromara.system.mapper.SysUserRoleMapper;
import java.util.stream.Collectors;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Calendar;
import java.util.Date;
/**
* 班级干部信息Service业务层处理
@@ -32,6 +42,8 @@ import java.util.Collection;
public class XgClassPosMsgServiceImpl implements IXgClassPosMsgService {
private final XgClassPosMsgMapper baseMapper;
private final SysDeptMapper sysDeptMapper;
private final SysUserRoleMapper sysUserRoleMapper;
/**
* 查询班级干部信息
@@ -40,8 +52,8 @@ public class XgClassPosMsgServiceImpl implements IXgClassPosMsgService {
* @return 班级干部信息
*/
@Override
public XgClassPosMsgVo queryById(Long id){
return baseMapper.selectVoById(id);
public XgClassPosMsgVo queryById(Long id) {
return baseMapper.selectVoByIdWithJoin(id);
}
/**
@@ -49,12 +61,35 @@ public class XgClassPosMsgServiceImpl implements IXgClassPosMsgService {
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @param deptId 部门ID
* @return 班级干部信息分页列表
*/
@Override
public TableDataInfo<XgClassPosMsgVo> queryPageList(XgClassPosMsgBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<XgClassPosMsg> lqw = buildQueryWrapper(bo);
Page<XgClassPosMsgVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
public TableDataInfo<XgClassPosMsgVo> queryPageList(XgClassPosMsgBo bo, PageQuery pageQuery, Long deptId) {
// 第一步:获取dept_id并查找所有子组织
List<Long> deptIds = new ArrayList<>();
if (ObjectUtil.isNotNull(deptId)) {
deptIds.add(deptId); // 包含自己
// 查找所有子组织
List<SysDept> childDepts = sysDeptMapper.selectListByParentId(deptId);
if (!childDepts.isEmpty()) {
List<Long> childDeptIds = childDepts.stream()
.map(SysDept::getDeptId)
.collect(Collectors.toList());
deptIds.addAll(childDeptIds);
}
log.info("组织ID {} 及其子组织共 {} 个: {}", deptId, deptIds.size(), deptIds);
}
// 构建查询条件
LambdaQueryWrapper<XgClassPosMsg> lqw = buildQueryWrapper(bo, deptIds);
// 使用XML配置的联表查询方法,确保能获取到班级名称和职务名称
Page<XgClassPosMsgVo> result = baseMapper.selectVoPageWithJoin(pageQuery.build(), lqw);
log.info("查询到班级干部信息 {} 条,使用XML配置的联表查询", result.getTotal());
return TableDataInfo.build(result);
}
@@ -66,19 +101,29 @@ public class XgClassPosMsgServiceImpl implements IXgClassPosMsgService {
*/
@Override
public List<XgClassPosMsgVo> queryList(XgClassPosMsgBo bo) {
LambdaQueryWrapper<XgClassPosMsg> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
LambdaQueryWrapper<XgClassPosMsg> lqw = buildQueryWrapper(bo, null);
return baseMapper.selectVoListWithJoin(lqw);
}
private LambdaQueryWrapper<XgClassPosMsg> buildQueryWrapper(XgClassPosMsgBo bo) {
private LambdaQueryWrapper<XgClassPosMsg> buildQueryWrapper(XgClassPosMsgBo bo, List<Long> deptIds) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<XgClassPosMsg> lqw = Wrappers.lambdaQuery();
lqw.orderByAsc(XgClassPosMsg::getId);
lqw.eq(bo.getDeleted() != null, XgClassPosMsg::getDeleted, bo.getDeleted());
lqw.eq(bo.getClassId() != null, XgClassPosMsg::getClassId, bo.getClassId());
lqw.eq(bo.getStudentId() != null, XgClassPosMsg::getStudentId, bo.getStudentId());
lqw.eq(StringUtils.isNotBlank(bo.getStudentNo()), XgClassPosMsg::getStudentNo, bo.getStudentNo());
lqw.like(StringUtils.isNotBlank(bo.getStudentName()), XgClassPosMsg::getStudentName, bo.getStudentName());
// 确保逻辑删除条件始终存在,如果bo.getDeleted()为null,则默认设置为0
// 使用apply方法添加带别名的字段条件
lqw.apply("cpm.deleted = {0}", bo.getDeleted() != null ? bo.getDeleted() : 0L);
// deptIds 不为空,则查询deptIds中的所有班级
if (deptIds != null && !deptIds.isEmpty()) {
lqw.in(XgClassPosMsg::getClassId, deptIds);
} else {
// 如果deptIds为空,则使用班级ID查询
lqw.eq(bo.getClassId() != null, XgClassPosMsg::getClassId, bo.getClassId());
}
lqw.apply(bo.getStudentId() != null, "cpm.student_id = {0}", bo.getStudentId());
lqw.apply(StringUtils.isNotBlank(bo.getStudentNo()), "cpm.student_no LIKE CONCAT('%', {0}, '%')",
bo.getStudentNo());
lqw.apply(StringUtils.isNotBlank(bo.getStudentName()), "cpm.student_name LIKE CONCAT('%', {0}, '%')",
bo.getStudentName());
lqw.eq(StringUtils.isNotBlank(bo.getPositionCode()), XgClassPosMsg::getPositionCode, bo.getPositionCode());
lqw.eq(bo.getAppointmentDate() != null, XgClassPosMsg::getAppointmentDate, bo.getAppointmentDate());
lqw.eq(bo.getTermStartDate() != null, XgClassPosMsg::getTermStartDate, bo.getTermStartDate());
@@ -86,10 +131,15 @@ public class XgClassPosMsgServiceImpl implements IXgClassPosMsgService {
lqw.eq(bo.getTermDuration() != null, XgClassPosMsg::getTermDuration, bo.getTermDuration());
lqw.eq(bo.getIsCurrent() != null, XgClassPosMsg::getIsCurrent, bo.getIsCurrent());
lqw.eq(StringUtils.isNotBlank(bo.getCadreStatus()), XgClassPosMsg::getCadreStatus, bo.getCadreStatus());
lqw.eq(StringUtils.isNotBlank(bo.getAppointmentReason()), XgClassPosMsg::getAppointmentReason, bo.getAppointmentReason());
lqw.eq(StringUtils.isNotBlank(bo.getAppointmentMethod()), XgClassPosMsg::getAppointmentMethod, bo.getAppointmentMethod());
lqw.eq(StringUtils.isNotBlank(bo.getAppointmentReason()), XgClassPosMsg::getAppointmentReason,
bo.getAppointmentReason());
lqw.eq(StringUtils.isNotBlank(bo.getAppointmentMethod()), XgClassPosMsg::getAppointmentMethod,
bo.getAppointmentMethod());
lqw.eq(bo.getDismissalDate() != null, XgClassPosMsg::getDismissalDate, bo.getDismissalDate());
lqw.eq(StringUtils.isNotBlank(bo.getDismissalReason()), XgClassPosMsg::getDismissalReason, bo.getDismissalReason());
lqw.eq(StringUtils.isNotBlank(bo.getDismissalReason()), XgClassPosMsg::getDismissalReason,
bo.getDismissalReason());
// 按照 创建时间 降序
lqw.orderByDesc(XgClassPosMsg::getCreateTime);
return lqw;
}
@@ -100,12 +150,32 @@ public class XgClassPosMsgServiceImpl implements IXgClassPosMsgService {
* @return 是否新增成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean insertByBo(XgClassPosMsgBo bo) {
XgClassPosMsg add = MapstructUtils.convert(bo, XgClassPosMsg.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
// 新增班级干部信息成功后,向sys_user_role表插入一条数据
// user_id = 当前记录的studentId, role_id = 当前记录的id
try {
SysUserRole userRole = new SysUserRole();
userRole.setUserId(add.getStudentId());
userRole.setRoleId(add.getPositionId());
boolean userRoleFlag = sysUserRoleMapper.insert(userRole) > 0;
if (userRoleFlag) {
log.info("成功为用户ID: {} 分配角色ID: {}", add.getStudentId(), add.getId());
} else {
log.warn("为用户ID: {} 分配角色ID: {} 失败", add.getStudentId(), add.getId());
}
} catch (Exception e) {
log.error("向sys_user_role表插入数据时发生异常,用户ID: {}, 角色ID: {}, 异常信息: {}",
add.getStudentId(), add.getId(), e.getMessage(), e);
// 不抛出异常,避免影响主业务流程
}
}
return flag;
}
@@ -126,8 +196,8 @@ public class XgClassPosMsgServiceImpl implements IXgClassPosMsgService {
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(XgClassPosMsg entity){
//TODO 做一些数据校验,如唯一约束
private void validEntityBeforeSave(XgClassPosMsg entity) {
// TODO 做一些数据校验,如唯一约束
}
/**
@@ -138,10 +208,124 @@ public class XgClassPosMsgServiceImpl implements IXgClassPosMsgService {
* @return 是否删除成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
if (isValid) {
// TODO 做一些业务上的校验,判断是否需要校验
}
// 删除班级干部信息前,先删除对应的用户角色关联
try {
// 先查询班级干部信息,获取student_id
for (Long id : ids) {
XgClassPosMsg classPosMsg = baseMapper.selectById(id);
if (classPosMsg != null && classPosMsg.getStudentId() != null) {
// 删除sys_user_role表中role_id=id且user_id=student_id的记录
sysUserRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>()
.eq(SysUserRole::getRoleId, classPosMsg.getPositionId())
.eq(SysUserRole::getUserId, classPosMsg.getStudentId()));
log.info("删除班级干部信息ID: {} 对应的用户角色关联,用户ID: {}", id, classPosMsg.getStudentId());
} else {
log.warn("班级干部信息ID: {} 不存在或student_id为空,跳过用户角色关联删除", id);
}
}
} catch (Exception e) {
log.error("删除用户角色关联时发生异常,异常信息: {}", e.getMessage(), e);
// 不抛出异常,避免影响主业务流程
}
return baseMapper.deleteByIds(ids) > 0;
}
/**
* 学生干部离任
*
* @param id 班级干部信息ID
* @param termEndDate 任期结束日期
* @param cadreStatus 学生职务任职状态
* @param dismissalReason 离任原因
* @return 是否离任成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean dismissCadre(Long id, Date termEndDate, String cadreStatus, String dismissalReason) {
// 1. 查询班级干部信息
XgClassPosMsg classPosMsg = baseMapper.selectById(id);
if (classPosMsg == null) {
throw new RuntimeException("班级干部信息不存在");
}
// 2. 计算任期时长(月)
Long termDuration = calculateTermDuration(classPosMsg.getTermStartDate(), termEndDate);
// 3. 更新班级干部信息
XgClassPosMsg updateEntity = new XgClassPosMsg();
updateEntity.setId(id);
updateEntity.setTermEndDate(termEndDate);
updateEntity.setTermDuration(termDuration);
updateEntity.setCadreStatus(cadreStatus);
updateEntity.setDismissalReason(dismissalReason);
updateEntity.setDismissalDate(termEndDate); // 离任日期设置为任期结束日期
updateEntity.setIsCurrent(0L); // 设置为非现任
boolean updateSuccess = baseMapper.updateById(updateEntity) > 0;
if (!updateSuccess) {
throw new RuntimeException("更新班级干部信息失败");
}
// 4. 删除sys_user_role表中的关联数据
try {
int deleteCount = sysUserRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>()
.eq(SysUserRole::getRoleId, classPosMsg.getPositionId())
.eq(SysUserRole::getUserId, classPosMsg.getStudentId()));
if (deleteCount > 0) {
log.info("成功删除班级干部信息ID: {} 对应的用户角色关联,用户ID: {}", id, classPosMsg.getStudentId());
} else {
log.warn("未找到班级干部信息ID: {} 对应的用户角色关联,用户ID: {}", id, classPosMsg.getStudentId());
}
} catch (Exception e) {
log.error("删除用户角色关联时发生异常,班级干部信息ID: {}, 用户ID: {}, 异常信息: {}",
id, classPosMsg.getStudentId(), e.getMessage(), e);
throw new RuntimeException("删除用户角色关联失败: " + e.getMessage());
}
log.info("学生干部离任成功,ID: {}, 学生: {}, 任期时长: {} 月",
id, classPosMsg.getStudentName(), termDuration);
return true;
}
/**
* 计算任期时长(月)
*
* @param startDate 开始日期
* @param endDate 结束日期
* @return 任期时长(月)
*/
private Long calculateTermDuration(Date startDate, Date endDate) {
if (startDate == null || endDate == null) {
return 0L;
}
Calendar startCal = Calendar.getInstance();
startCal.setTime(startDate);
Calendar endCal = Calendar.getInstance();
endCal.setTime(endDate);
int yearDiff = endCal.get(Calendar.YEAR) - startCal.get(Calendar.YEAR);
int monthDiff = endCal.get(Calendar.MONTH) - startCal.get(Calendar.MONTH);
// 计算总月数
long totalMonths = yearDiff * 12L + monthDiff;
// 如果结束日期的天数小于开始日期的天数,且不是整月,则减去1个月
if (endCal.get(Calendar.DAY_OF_MONTH) < startCal.get(Calendar.DAY_OF_MONTH)) {
totalMonths--;
}
// 确保至少为0个月
return Math.max(0L, totalMonths);
}
}
@@ -464,4 +464,17 @@ public class XgStudentMsgServiceImpl implements IXgStudentMsgService {
.eq(XgStudentMsg::getDeleted, 0)
.eq(XgStudentMsg::getId, userId));
}
/**
* 根据班级ID和姓名查询学生信息列表
*
* @param classId 班级ID
* @param name 学生姓名(模糊查询)
* @return 学生信息列表
*/
@Override
public List<XgStudentMsgVo> queryStudentsByClassId(String classId, String name) {
log.info("查询班级学生信息 - 班级ID: {}, 姓名: {}", classId, name);
return baseMapper.selectStudentsByClassId(classId, name);
}
}
@@ -37,4 +37,38 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
ORDER BY create_time DESC
</select>
<!-- 根据班级ID和姓名查询学生信息列表 -->
<select id="selectStudentsByClassId" resultType="org.dromara.xg.domain.vo.XgStudentMsgVo">
SELECT
id, deleted, student_no, id_card, name, name_english, former_name, gender, birth_date,
nationality, native_place, political_status, hukou_location, home_address, current_address,
postal_code, photo_url, photo_path, phone, is_boarder, education_level, household_register_type,
religion, source_region, graduate_school, email, qq, wechat, marital_status, emergency_contact_name,
emergency_contact_phone, emergency_contact_relation, is_poor_student, is_minority, is_disabled,
is_orphan, is_left_behind_child, is_single_child, is_low_income, has_loan, loan_amount,
specialties, awards, violations, social_activities, father_name, father_phone, father_work_unit,
mother_name, mother_phone, mother_work_unit, guardian_name, guardian_phone, guardian_relation,
family_income, per_capita_income, income_source, family_economic_status, family_members,
height, weight, blood_type, health_status, special_condition, medical_history, allergies,
disability_type, disability_level, admission_category, student_status, enrollment_year,
enrollment_date, bank_card_no, bank_name, bank_branch, card_holder_name, info_status,
data_source, last_batch_id, last_audit_time, is_complete, completion_rate, school_system,
academic_level, training_type, major_id, major_name, class_id, class_name, grade,
college_id, college_name, counselor_id, counselor_name, class_teacher_id, class_teacher_name,
dormitory_building, dormitory_room, ext_field1, ext_field2, ext_field3, ext_field4, ext_field5,
remark, create_by, create_time, update_by, update_time, tenant_id
FROM xg_student_msg
<where>
deleted = 0
AND student_status = '1'
<if test="classId != null and classId != ''">
AND class_id = #{classId}
</if>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
</where>
ORDER BY create_time DESC
</select>
</mapper>
@@ -4,4 +4,58 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.xg.mapper.XgClassPosMsgMapper">
<resultMap id="XgClassPosMsgResult" type="org.dromara.xg.domain.vo.XgClassPosMsgVo">
<id property="id" column="id"/>
<result property="deleted" column="deleted"/>
<result property="classId" column="class_id"/>
<result property="className" column="class_name"/>
<result property="studentId" column="student_id"/>
<result property="studentNo" column="student_no"/>
<result property="studentName" column="student_name"/>
<result property="positionCode" column="position_code"/>
<result property="positionName" column="position_name"/>
<result property="appointmentDate" column="appointment_date"/>
<result property="termStartDate" column="term_start_date"/>
<result property="termEndDate" column="term_end_date"/>
<result property="termDuration" column="term_duration"/>
<result property="isCurrent" column="is_current"/>
<result property="cadreStatus" column="cadre_status"/>
<result property="appointmentReason" column="appointment_reason"/>
<result property="appointmentMethod" column="appointment_method"/>
<result property="dismissalDate" column="dismissal_date"/>
<result property="dismissalReason" column="dismissal_reason"/>
<result property="remark" column="remark"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
</resultMap>
<sql id="selectXgClassPosMsgVo">
select cpm.id, cpm.deleted, cpm.class_id, cm.class_name, cpm.student_id, cpm.student_no, cpm.student_name,
cpm.position_code, cpd.position_name, cpm.appointment_date, cpm.term_start_date,
cpm.term_end_date, cpm.term_duration, cpm.is_current, cpm.cadre_status,
cpm.appointment_reason, cpm.appointment_method, cpm.dismissal_date, cpm.dismissal_reason,
cpm.remark, cpm.create_by, cpm.create_time, cpm.update_by, cpm.update_time,
cpm.position_id
from xg_class_pos_msg cpm
left join xg_class_msg cm on cpm.class_id = cm.id and cm.deleted = 0
left join xg_class_pos_dict cpd on cpm.position_code = cpd.position_code and cpd.deleted = 0
</sql>
<select id="selectVoListWithJoin" resultMap="XgClassPosMsgResult">
<include refid="selectXgClassPosMsgVo"/>
${ew.getCustomSqlSegment}
</select>
<select id="selectVoByIdWithJoin" parameterType="Long" resultMap="XgClassPosMsgResult">
<include refid="selectXgClassPosMsgVo"/>
where cpm.deleted = 0 and cpm.id = #{id}
</select>
<select id="selectVoPageWithJoin" resultMap="XgClassPosMsgResult">
<include refid="selectXgClassPosMsgVo"/>
${ew.getCustomSqlSegment}
</select>
</mapper>
@@ -36,9 +36,10 @@ public class ZsjyEnrollmentStatisticsController extends BaseController {
/**
* 获取报名录取信息统计数据
*/
// @SaCheckPermission("zsjy:enrollment:statistics:list")
@SaCheckPermission("zsjy:enrollment:statistics:list")
@GetMapping("/list")
public TableDataInfo<ZsjyEnrollmentStatisticsResultVo> getStatistics(ZsjyEnrollmentStatisticsQueryVo queryVo, PageQuery pageQuery) {
public TableDataInfo<ZsjyEnrollmentStatisticsResultVo> getStatistics(ZsjyEnrollmentStatisticsQueryVo queryVo,
PageQuery pageQuery) {
log.info("接收到获取报名录取信息统计数据请求,查询条件: {}", queryVo);
List<ZsjyEnrollmentStatisticsResultVo> list = enrollmentStatisticsService.getEnrollmentStatistics(queryVo);
log.info("报名录取信息统计数据响应: {}", list != null ? "数据获取成功" : "数据为空");
@@ -48,7 +49,7 @@ public class ZsjyEnrollmentStatisticsController extends BaseController {
/**
* 获取报名录取信息汇总统计
*/
// @SaCheckPermission("zsjy:enrollment:statistics:summary")
@SaCheckPermission("zsjy:enrollment:statistics:summary")
@GetMapping("/summary")
public R<Map<String, Object>> getSummary(ZsjyEnrollmentStatisticsQueryVo queryVo) {
log.info("接收到获取报名录取信息汇总统计请求,查询条件: {}", queryVo);
@@ -60,7 +61,7 @@ public class ZsjyEnrollmentStatisticsController extends BaseController {
/**
* 获取统计维度选项数据
*/
// @SaCheckPermission("zsjy:enrollment:statistics:dimensions")
@SaCheckPermission("zsjy:enrollment:statistics:dimensions")
@GetMapping("/dimensions")
public R<Map<String, List<Map<String, Object>>>> getDimensions() {
log.info("接收到获取统计维度选项数据请求");
@@ -73,7 +74,7 @@ public class ZsjyEnrollmentStatisticsController extends BaseController {
* 导出统计数据
*/
@Log(title = "导出报名录取统计数据", businessType = BusinessType.EXPORT)
// @SaCheckPermission("zsjy:enrollment:statistics:export")
@SaCheckPermission("zsjy:enrollment:statistics:export")
@PostMapping("/export")
public void export(@RequestBody ZsjyEnrollmentStatisticsQueryVo queryVo) {
log.info("接收到导出报名录取统计数据请求,查询条件: {}", queryVo);
@@ -32,7 +32,7 @@ public class ZsjyStatisticsController extends BaseController {
/**
* 获取统计概览数据
*/
// @SaCheckPermission("zsjy:statistics:view")
@SaCheckPermission("zsjy:statistics:view")
@GetMapping("/overview")
public R<ZsjyStatisticsOverviewVo> getOverview() {
log.info("接收到获取统计概览数据请求");
@@ -44,7 +44,7 @@ public class ZsjyStatisticsController extends BaseController {
/**
* 获取专业分布统计
*/
// @SaCheckPermission("zsjy:statistics:major:view")
@SaCheckPermission("zsjy:statistics:major:view")
@GetMapping("/major")
public R<Map<String, Object>> getMajorDistribution() {
log.info("接收到获取专业分布统计请求");
@@ -56,7 +56,7 @@ public class ZsjyStatisticsController extends BaseController {
/**
* 获取地区分布统计
*/
// @SaCheckPermission("zsjy:statistics:region:view")
@SaCheckPermission("zsjy:statistics:region:view")
@GetMapping("/region")
public R<Map<String, Object>> getRegionDistribution() {
log.info("接收到获取地区分布统计请求");
@@ -68,7 +68,7 @@ public class ZsjyStatisticsController extends BaseController {
/**
* 获取性别比例统计
*/
// @SaCheckPermission("zsjy:statistics:view")
@SaCheckPermission("zsjy:statistics:view")
@GetMapping("/gender")
public R<Map<String, Object>> getGenderRatio() {
log.info("接收到获取性别比例统计请求");
@@ -80,7 +80,7 @@ public class ZsjyStatisticsController extends BaseController {
/**
* 获取报名趋势统计
*/
// @SaCheckPermission("zsjy:statistics:view")
@SaCheckPermission("zsjy:statistics:view")
@GetMapping("/trend")
public R<Map<String, Object>> getEnrollmentTrend(@RequestParam(required = false) Integer days) {
log.info("接收到获取报名趋势统计请求, days={}", days);
@@ -92,7 +92,7 @@ public class ZsjyStatisticsController extends BaseController {
/**
* 获取状态分布统计
*/
// @SaCheckPermission("zsjy:statistics:view")
@SaCheckPermission("zsjy:statistics:view")
@GetMapping("/status")
public R<Map<String, Object>> getStatusDistribution() {
log.info("接收到获取状态分布统计请求");
@@ -0,0 +1,171 @@
# 班级干部信息管理页面学生选择功能优化
**变更时间**: 2025-01-27 15:30:00
**变更人员**: AI Assistant
**变更类型**: 功能优化
**影响范围**: 前端页面
## 变更概述
优化班级干部信息管理页面的学生选择功能,将原来的手动输入学生ID改为下拉选择模式,支持按姓名模糊搜索,提升用户体验。
## 具体变更内容
### 1. 前端页面修改 (`web/src/views/xgClassPosMsg/xgClassPosMsg/index.vue`)
#### 1.1 表单字段调整
- **学生ID字段**: 从输入框改为下拉选择框
- 支持远程搜索(按姓名模糊查询)
- 显示格式:姓名(学号)
- 支持清空选择
- 加载状态显示
#### 1.2 学号和姓名字段
- 改为只读模式,自动填充
- 提示文字:请先选择学生
#### 1.3 新增功能方法
- `handleStudentSearch`: 学生搜索方法
- 调用API: `/studentMsg/studentMsg/listByClassId`
- 参数:classId(班级ID)、name(姓名模糊查询)
- 使用request方法进行API调用
- 支持错误处理和加载状态
- `handleStudentChange`: 学生选择变化处理
- 自动填充学号和姓名
- 清空选择时重置相关字段
#### 1.4 数据变量
- `studentOptions`: 学生选项列表
- `studentSearchLoading`: 搜索加载状态
#### 1.5 表单验证调整
- 学生ID验证:改为change触发,提示信息优化
#### 1.6 编辑功能优化
- 编辑时自动加载当前学生信息到选项列表
- 重置时清空学生选择相关数据
### 2. API接口使用
#### 2.1 接口信息
- **接口路径**: `/studentMsg/studentMsg/listByClassId`
- **请求方法**: GET
- **参数**:
- `classId`: 班级ID(必填)
- `name`: 学生姓名(可选,模糊查询)
#### 2.2 响应格式
```json
{
"code": 200,
"msg": "操作成功",
"data": [
{
"id": 1,
"name": "张三",
"studentNo": "2021001"
}
]
}
```
## 技术实现细节
### 1. 下拉选择组件配置
```vue
<el-select
v-model="form.studentId"
placeholder="请选择学生"
filterable
remote
:remote-method="handleStudentSearch"
:loading="studentSearchLoading"
clearable
style="width: 100%"
@change="handleStudentChange"
>
```
### 2. 选项显示格式
```vue
<el-option
v-for="item in studentOptions"
:key="item.id"
:label="`${item.name}(${item.studentNo})`"
:value="item.id"
>
<span>{{ item.name }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.studentNo }}</span>
</el-option>
```
### 3. 搜索方法实现
```javascript
const response = await request({
url: '/studentMsg/studentMsg/listByClassId',
method: 'get',
params: {
classId: currentSelectedNode.value.id,
name: query
}
});
```
- 使用request方法调用API
- 支持错误处理和用户提示
- 自动管理加载状态
## 用户体验改进
### 1. 操作流程优化
- 原来:手动输入学生ID → 容易出错
- 现在:下拉搜索选择 → 直观便捷
### 2. 数据展示优化
- 显示格式:姓名(学号)
- 支持模糊搜索
- 实时加载状态提示
### 3. 错误处理
- API调用失败时显示友好提示
- 网络异常时给出明确错误信息
## 测试要点
### 1. 功能测试
- [ ] 新增班级干部时学生选择功能正常
- [ ] 编辑班级干部时学生信息正确显示
- [ ] 学生搜索功能正常(按姓名模糊查询)
- [ ] 选择学生后学号和姓名自动填充
- [ ] 清空选择后相关字段正确重置
### 2. 边界测试
- [ ] 无班级节点选择时的处理
- [ ] 搜索无结果时的显示
- [ ] 网络异常时的错误处理
- [ ] 大量数据时的性能表现
### 3. 兼容性测试
- [ ] 不同浏览器的兼容性
- [ ] 移动端显示效果
- [ ] 不同屏幕尺寸的适配
## 注意事项
1. **权限控制**: 确保只有班级节点下才能进行学生选择
2. **数据一致性**: 选择学生后自动填充的字段需要与后端数据保持一致
3. **性能优化**: 搜索时添加适当的防抖处理,避免频繁API调用
4. **错误处理**: 完善的错误提示和异常处理机制
## 后续优化建议
1. **缓存机制**: 可以考虑添加学生数据的本地缓存
2. **分页加载**: 如果学生数据量很大,可以考虑分页加载
3. **键盘导航**: 支持键盘上下键选择学生
4. **历史记录**: 记录最近选择的学生,方便快速选择
---
**变更状态**: ✅ 已完成
**测试状态**: ⏳ 待测试
**部署状态**: ⏳ 待部署
@@ -0,0 +1,107 @@
# 学生信息管理API - 新增班级学生查询功能
## 🔧 变更信息
- **变更日期**: 2025-01-27 18:00:00
- **变更类型**: 功能新增
- **变更范围**: 后端API接口
- **影响模块**: 学工管理 - 学生信息管理
## 📋 变更内容
### 新增API接口
- **接口路径**: `GET /studentMsg/studentMsg/listByClassId`
- **接口功能**: 查询班级中所有学生信息
- **权限要求**: `studentMsg:studentMsg:query`
### 接口参数
#### 入参
- `classId` (String, 必填): 班级ID
- `name` (String, 可选): 学生姓名(模糊查询)
#### 出参
- `R<List<XgStudentMsgVo>>`: 学生信息列表
### 业务逻辑
1. 在 `xg_student_msg` 表中查询:
- `deleted = 0` (未删除)
- `student_status = '1'` (在籍状态)
- `class_id = 入参的classId` (班级ID)
- `name LIKE '%入参的name%'` (姓名模糊查询,可选)
- 按创建时间倒序排列
## 🔨 技术实现
### 1. Controller层
**文件**: `XgStudentMsgController.java`
```java
@SaCheckPermission("studentMsg:studentMsg:query")
@GetMapping("/listByClassId")
public R<List<XgStudentMsgVo>> getStudentsByClassId(
@RequestParam(required = true) String classId,
@RequestParam(required = false) String name)
```
### 2. Service层
**文件**: `IXgStudentMsgService.java` 和 `XgStudentMsgServiceImpl.java`
- 新增接口方法: `queryStudentsByClassId()`
- 实现班级学生查询逻辑
### 3. Mapper层
**文件**: `XgStudentMsgMapper.java` 和 `XgStudentMsgMapper.xml`
#### 新增Mapper方法
`selectStudentsByClassId(String classId, String name)`: 根据班级ID和姓名查询学生数据
#### SQL实现
```sql
SELECT [完整字段列表]
FROM xg_student_msg
WHERE deleted = 0
AND student_status = '1'
AND class_id = #{classId}
AND name LIKE CONCAT('%', #{name}, '%')
ORDER BY create_time DESC
```
## 📊 数据流程
```
用户请求 → Controller接收参数 → Service调用Mapper → 执行SQL查询 → 返回学生列表
```
## 🧪 测试说明
### 测试场景
1. **正常查询**: 传入有效的班级ID,验证能正确返回该班级下的所有在籍学生
2. **姓名过滤**: 传入班级ID和姓名,验证能正确过滤出匹配的学生
3. **边界情况**: 传入不存在的班级ID,验证返回空列表
4. **权限验证**: 验证无权限用户访问被拒绝
### 测试参数示例
```
GET /studentMsg/studentMsg/listByClassId?classId=101&name=张
```
## 🔗 相关文档
- **API文档**: `docs/开发文档/API/学生信息管理API.md`
- **数据库设计**: `docs/设计文档/database/学工管理-学生信息管理/`
- **业务需求**: `docs/需求文档/学工管理学生信息管理需求/`
## ⚠️ 注意事项
1. 该接口只返回在籍状态(student_status = '1')的学生
2. 该接口只返回未删除(deleted = 0)的学生
3. 姓名参数为可选,不传时返回班级下所有学生
4. 返回结果按创建时间倒序排列
## 📝 变更文件清单
1. `XgStudentMsgController.java` - 新增Controller方法
2. `IXgStudentMsgService.java` - 新增Service接口方法
3. `XgStudentMsgServiceImpl.java` - 新增Service实现方法
4. `XgStudentMsgMapper.java` - 新增Mapper接口方法
5. `XgStudentMsgMapper.xml` - 新增SQL查询语句
## ✅ 验证清单
- [x] 接口参数验证
- [x] 权限控制验证
- [x] SQL查询逻辑验证
- [x] 返回数据格式验证
- [x] 异常处理验证
@@ -1,93 +1,136 @@
# 代码调整记录 - 2025-01-27 - cursor
## 修复审核通过功能MapstructUtils转换异常 - 2025-01-27 01:15:00
## 📝 变更记录
### 问题描述
学生信息批次管理审核通过功能出现异常:
```
Caused by: io.github.linpeilie.ConvertException: cannot find converter from XgStudentMsgBatchVo to XgStudentMsgBo
### 2025-01-27 17:00:00 - 班级干部信息管理页面添加离任功能
**变更内容**: 在班级干部信息管理页面的表格操作栏中新增离任按钮和离任对话框功能
**涉及文件**:
- `web/src/views/xgClassPosMsg/xgClassPosMsg/index.vue`
- `web/src/api/xgClassPosMsg/xgClassPosMsg/index.ts`
**具体修改**:
#### 1. 前端页面修改
- **文件**: `web/src/views/xgClassPosMsg/xgClassPosMsg/index.vue`
- **修改**: 在表格操作栏中添加离任按钮,并创建离任对话框
- **功能特性**:
- 离任按钮只有在任状态(cadreStatus为'1')时才可点击
- 离任对话框显示当前学生的基本信息(只读)
- 可填写离任相关信息:任期结束日期、任职状态、离任原因
- 支持的状态选项:2-到期、3-辞职、4-免职、5-调任、6-休学
#### 2. API接口扩展
- **文件**: `web/src/api/xgClassPosMsg/xgClassPosMsg/index.ts`
- **修改**: 添加离任API接口(暂时使用updateXgClassPosMsg)
- **接口定义**:
```typescript
export const dismissXgClassPosMsg = (data: XgClassPosMsgForm) => {
return request({
url: '/xgClassPosMsg/xgClassPosMsg/dismiss',
method: 'put',
data: data
});
};
```
### 问题原因
在`XgStudentMsgBatchServiceImpl.approveStudentMsg`方法中,使用`MapstructUtils.convert`将`XgStudentMsgBatchVo`转换为`XgStudentMsgBo`时,系统找不到对应的转换器配置。
#### 3. 离任功能实现细节
- **按钮控制**: 通过 `:disabled="scope.row.cadreStatus !== '1'"` 控制按钮可用性
- **表单验证**: 对任期结束日期、任职状态、离任原因进行必填验证
- **数据更新**: 离任时自动设置 `isCurrent: 0`(非现任)和 `dismissalDate`
- **用户体验**: 提供加载状态、成功/失败提示、表单重置等功能
### 解决方案
将MapstructUtils自动转换替换为手动字段转换:
#### 4. 离任对话框字段说明
- **只读字段**: 学号、学生姓名、职务名称、任职开始日期
- **可编辑字段**: 任期结束日期、学生职务任职状态、离任原因
- **默认值**: 任期结束日期默认为当前日期
**修改文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentMsgBatchServiceImpl.java`
### 2025-01-27 16:30:00 - 班级干部信息编辑时显示问题修复
**修改内容**:
- 第980-983行:将`MapstructUtils.convert(batchVo, XgStudentMsgBo.class)`
- 替换为:手动创建`XgStudentMsgBo`对象并逐个字段复制
- 包含所有业务字段:基础信息、联系人信息、特殊情况标记、家庭信息、健康信息、学籍信息、银行信息、扩展字段等
**变更内容**: 修复编辑班级干部信息时班级名称显示为空、职务名称显示职务编码的问题
### 修复方式
参考代码中已有的`getUserHistoryData`方法中从`XgStudentMsgVo`手动转换为`XgStudentMsgBatchVo`的方式,实现反向转换。
**涉及文件**:
- `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgClassPosMsgVo.java`
- `backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/xgClassPosMsg/XgClassPosMsgMapper.xml`
- `web/src/api/xgClassPosMsg/xgClassPosMsg/types.ts`
- `web/src/views/xgClassPosMsg/xgClassPosMsg/index.vue`
### 验证结果
修复后审核通过功能可以正常执行,不再出现类型转换异常。
**具体修改**:
### 影响范围
- 学生信息批次管理 - 审核通过功能
- 确保批次数据正确同步到主表
---
## 修改时间
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
}
});
```
**后端方法签名**:
#### 1. 后端VO类修改
- **文件**: `XgClassPosMsgVo.java`
- **修改**: 添加 `className` 字段,用于存储班级的中文名称
- **字段定义**:
```java
@PostMapping("/rejectStudentMsg")
public R<Void> rejectStudentMsg(@RequestParam @NotNull(message = "批次ID不能为空") Long batchId,
@RequestParam @NotNull(message = "用户ID不能为空") Long userId,
@RequestParam @NotBlank(message = "驳回原因不能为空") String remark)
/**
* 班级名称(联表查询获取)
*/
@ExcelProperty(value = "班级名称")
private String className;
```
**影响范围**:
- 学生信息审核页面的驳回功能
- 确保前后端参数传递方式一致
#### 2. Mapper.xml联表查询优化
- **文件**: `XgClassPosMsgMapper.xml`
- **修改**: 添加班级表的联表查询,获取班级名称
- **核心SQL**:
```xml
<sql id="selectXgClassPosMsgVo">
select cpm.id, cpm.deleted, cpm.class_id, cm.class_name, cpm.student_id, cpm.student_no, cpm.student_name,
cpm.position_code, cpd.position_name, cpm.appointment_date, cpm.term_start_date,
cpm.term_end_date, cpm.term_duration, cpm.is_current, cpm.cadre_status,
cpm.appointment_reason, cpm.appointment_method, cpm.dismissal_date, cpm.dismissal_reason,
cpm.remark, cpm.create_by, cpm.create_time, cpm.update_by, cpm.update_time
from xg_class_pos_msg cpm
left join xg_class_msg cm on cpm.class_id = cm.id and cm.deleted = 0
left join xg_class_pos_dict cpd on cpm.position_code = cpd.position_code and cpd.deleted = 0
where cpm.deleted = 0
</sql>
```
**测试要点**:
- 验证驳回功能能正常工作
- 确认参数正确传递到后端
- 检查错误处理和用户提示
#### 3. 前端类型定义修改
- **文件**: `types.ts`
- **修改**: 在 `XgClassPosMsgVO` 和 `XgClassPosMsgForm` 接口中添加 `className` 和 `positionName` 字段
- **字段定义**:
```typescript
/**
* 班级名称(联表查询获取)
*/
className: string;
## 技术说明
/**
* 职务名称(联表查询获取)
*/
positionName: string;
```
### @RequestParam vs @RequestBody 区别
- `@RequestParam`: 接收表单参数或查询参数
- `@RequestBody`: 接收JSON格式的请求体
#### 4. 前端页面显示优化
- **文件**: `index.vue`
- **修改**: 在编辑对话框中正确显示班级名称和职务名称
- **实现方式**: 优先使用后端返回的数据,如果没有则从当前选中的节点获取
### 前端发送方式对应
- 表单参数: `{ params: { key: value } }`
- JSON请求体: 直接传递对象作为第二个参数
## 🎯 功能验证
## 相关文件
- `web/src/views/stuMsgAudit/stuMsgDialog.vue` (前端调用)
- `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgStudentMsgBatchController.java` (后端接口)
### 离任功能验证要点
1. **按钮状态**: 只有在任状态的记录才显示可点击的离任按钮
2. **表单验证**: 必填字段验证正常工作
3. **数据更新**: 离任后状态正确更新为对应的离任状态
4. **用户体验**: 操作流程顺畅,提示信息准确
### 编辑功能验证要点
1. **班级名称显示**: 编辑时正确显示班级的中文名称
2. **职务名称显示**: 编辑时正确显示职务的中文名称
3. **数据完整性**: 所有字段都能正确显示和编辑
## 📋 后续优化建议
### 离任功能优化
1. **后端API**: 实现专门的离任API接口,处理离任相关的业务逻辑
2. **权限控制**: 添加离任操作的权限控制
3. **日志记录**: 记录离任操作的详细日志
4. **通知功能**: 离任后自动通知相关人员
### 编辑功能优化
1. **数据缓存**: 优化班级和职务数据的缓存机制
2. **性能优化**: 优化联表查询的性能
3. **错误处理**: 完善异常情况的处理机制
@@ -0,0 +1,201 @@
# 班级干部离任API接口实现变更记录
**变更日期**: 2025-01-28
**变更人员**: Cursor AI
**变更类型**: 功能新增
**影响范围**: 后端API、前端调用、数据库操作
## 📋 变更概述
在 `XgClassPosMsgController.java` 中新增学生干部离任接口,实现以下功能:
1. 通过任期开始日期和任期结束日期计算任期时长(月)
2. 更新班级干部信息到数据库
3. 删除 `sys_user_role` 表中的关联数据
## 🔧 具体变更内容
### 1. 后端Service接口扩展
**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgClassPosMsgService.java`
**新增方法**:
```java
/**
* 学生干部离任
*
* @param id 班级干部信息ID
* @param termEndDate 任期结束日期
* @param cadreStatus 学生职务任职状态
* @param dismissalReason 离任原因
* @return 是否离任成功
*/
Boolean dismissCadre(Long id, java.util.Date termEndDate, String cadreStatus, String dismissalReason);
```
### 2. 后端Service实现
**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgClassPosMsgServiceImpl.java`
**新增功能**:
- `dismissCadre()` 方法实现
- `calculateTermDuration()` 私有方法实现任期时长计算
- 事务管理确保数据一致性
- 删除 `sys_user_role` 关联数据
**核心逻辑**:
```java
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean dismissCadre(Long id, Date termEndDate, String cadreStatus, String dismissalReason) {
// 1. 查询班级干部信息
// 2. 计算任期时长(月)
// 3. 更新班级干部信息
// 4. 删除sys_user_role表中的关联数据
}
```
**任期时长计算逻辑**:
```java
private Long calculateTermDuration(Date startDate, Date endDate) {
// 使用Calendar计算年差和月差
// 考虑天数差异进行精确计算
// 返回总月数
}
```
### 3. 后端Controller接口
**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgClassPosMsgController.java`
**新增接口**:
```java
/**
* 学生干部离任
*/
@SaCheckPermission("xgClassPosMsg:xgClassPosMsg:edit")
@Log(title = "学生干部离任", businessType = BusinessType.UPDATE)
@PostMapping("/dismiss/{id}")
public R<Void> dismissCadre(
@NotNull(message = "班级干部信息ID不能为空") @PathVariable Long id,
@NotNull(message = "任期结束日期不能为空") @RequestParam Date termEndDate,
@NotBlank(message = "学生职务任职状态不能为空") @RequestParam String cadreStatus,
@NotBlank(message = "离任原因不能为空") @RequestParam String dismissalReason)
```
**接口特点**:
- 使用 `@PathVariable` 接收ID参数
- 使用 `@RequestParam` 接收其他参数
- 包含完整的参数验证
- 记录操作日志
### 4. 前端API调用
**文件**: `web/src/api/xgClassPosMsg/xgClassPosMsg/index.ts`
**新增方法**:
```typescript
/**
* 学生干部离任
*/
export const dismissCadre = (id: string | number, termEndDate: string, cadreStatus: string, dismissalReason: string) => {
return request({
url: '/xgClassPosMsg/xgClassPosMsg/dismiss/' + id,
method: 'post',
params: {
termEndDate,
cadreStatus,
dismissalReason
}
});
};
```
### 5. 前端组件更新
**文件**: `web/src/views/xgClassPosMsg/xgClassPosMsg/index.vue`
**更新内容**:
- 导入新的 `dismissCadre` API方法
- 更新 `submitDismissalForm()` 方法使用新接口
- 移除原有的 `updateXgClassPosMsg` 调用
## 🗄️ 数据库操作
### 更新操作
- **表**: `xg_class_pos_msg`
- **更新字段**:
- `term_end_date`: 任期结束日期
- `term_duration`: 任期时长(月)
- `cadre_status`: 学生职务任职状态
- `dismissal_reason`: 离任原因
- `dismissal_date`: 离任日期
- `is_current`: 设置为0(非现任)
### 删除操作
- **表**: `sys_user_role`
- **删除条件**: `role_id = 班级干部ID AND user_id = 学生ID`
## 🔍 业务逻辑
### 任期时长计算规则
1. 计算年差和月差
2. 转换为总月数
3. 考虑天数差异进行精确计算
4. 如果结束日期的天数小于开始日期的天数,且不是整月,则减去1个月
5. 确保至少为0个月
### 状态变更规则
- `is_current`: 1 → 0(现任 → 非现任)
- `cadre_status`: 1(在任)→ 2/3/4/5/6(其他状态)
## 🧪 测试验证
### API接口测试
- **URL**: `POST /xgClassPosMsg/xgClassPosMsg/dismiss/{id}`
- **参数验证**: 所有必填参数都有验证
- **权限验证**: 需要 `xgClassPosMsg:xgClassPosMsg:edit` 权限
- **事务验证**: 确保数据一致性
### 测试用例
1. **正常离任**: 任期届满,状态改为"到期"
2. **辞职离任**: 个人原因,状态改为"辞职"
3. **免职离任**: 违纪原因,状态改为"免职"
4. **异常处理**: 无效ID、参数缺失等
## 📝 注意事项
1. **事务管理**: 使用 `@Transactional` 确保数据一致性
2. **异常处理**: 完善的异常捕获和日志记录
3. **权限控制**: 需要相应的编辑权限
4. **数据验证**: 前端和后端都有参数验证
5. **日志记录**: 记录所有关键操作
## 🔄 后续优化建议
1. **批量离任**: 支持批量处理多个干部的离任
2. **离任审批**: 增加离任审批流程
3. **历史记录**: 保存离任历史记录
4. **通知机制**: 离任时发送通知给相关人员
5. **数据统计**: 增加离任原因统计分析
## ✅ 验证清单
- [x] 后端Service接口定义
- [x] 后端Service实现逻辑
- [x] 后端Controller接口
- [x] 前端API调用方法
- [x] 前端组件更新
- [x] 参数验证
- [x] 权限控制
- [x] 事务管理
- [x] 异常处理
- [x] 日志记录
- [x] 数据库操作
- [x] 任期时长计算
---
**变更完成时间**: 2025-01-28
**变更状态**: ✅ 已完成
**测试状态**: 🔄 待测试
**部署状态**: 🔄 待部署
@@ -0,0 +1,74 @@
# 班级干部信息页面字典显示问题修复记录
## 修改时间
2025-07-22 16:30:00
## 变更内容
修复了班级干部信息管理页面中"是否现任"字段显示数字1而不是汉字"是"的问题。
## 变更原因
通过分析发现,数据库中 `xg_class_pos_msg` 表的 `is_current` 字段是 `tinyint(1)` 类型,存储的是数字值(0-否,1-是),但前端使用的是 `sys_yes_no` 字典,该字典的值是字符串类型(`Y`-是,`N`-否),导致类型不匹配。
## 问题分析
1. **数据库字段类型**:`is_current` 字段为 `tinyint(1)`,存储数字值(0-否,1-是)
2. **字典配置不匹配**:
- `sys_yes_no` 字典:字符串值(`Y`-是,`N`-否)
- `sys_yesno` 字典:数字值(`0`-否,`1`-是)
3. **显示问题**:前端使用 `sys_yes_no` 字典显示数字值,导致显示为数字1而不是汉字"是"
## 解决方案
将前端代码中的字典从 `sys_yes_no` 改为 `sys_yesno`,因为 `sys_yesno` 字典使用的是数字值,与数据库字段类型匹配。
## 变更详情
### 1. 修改字典引用
**文件**:`web/src/views/xgClassPosMsg/xgClassPosMsg/index.vue`
**修改前**:
```javascript
const { stu_appointment_method, sys_yes_no, stu_cadre_status } = toRefs<any>(
proxy?.useDict('stu_appointment_method', 'sys_yes_no', 'stu_cadre_status')
);
```
**修改后**:
```javascript
const { stu_appointment_method, sys_yesno, stu_cadre_status } = toRefs<any>(
proxy?.useDict('stu_appointment_method', 'sys_yesno', 'stu_cadre_status')
);
```
### 2. 修改表格列显示
**修改前**:
```vue
<el-table-column label="是否现任" align="center" prop="isCurrent">
<template #default="scope">
<dict-tag :options="sys_yes_no" :value="scope.row.isCurrent" />
</template>
</el-table-column>
```
**修改后**:
```vue
<el-table-column label="是否现任" align="center" prop="isCurrent">
<template #default="scope">
<dict-tag :options="sys_yesno" :value="scope.row.isCurrent" />
</template>
</el-table-column>
```
## 影响范围
1. 班级干部信息管理页面的"是否现任"字段显示
2. 字典缓存数据使用
## 测试验证
1. 访问班级干部信息管理页面
2. 检查"是否现任"列是否正确显示为"是"/"否"而不是数字
3. 验证新增和编辑功能中相关字段的显示
## 相关文档
- 数据库设计文档:`docs/设计文档/database/学工管理-学生信息管理新版/22-class-cadre-dict-tables 班级职务表.sql`
- 前端页面:`web/src/views/xgClassPosMsg/xgClassPosMsg/index.vue`
## 备注
此修复确保了前端显示与数据库存储的数据类型一致,提高了用户体验。建议在后续开发中注意字典类型与数据库字段类型的匹配问题。
@@ -1,70 +1,37 @@
# 代码调整记录 - 2025-07-29
**调整时间**: 2025-07-29 10:30:00
**调整人员**: wuzilong
**调整原因**: 修复班级干部职务配置创建系统角色时的空指针异常
## 问题描述
在新增班级干部职务配置时,系统会自动创建对应的系统角色,但在调用 `sysRoleService.insertRole(roleBo)` 时出现空指针异常:
## 2025-07-29 16:30:00 - 修复XgClassPosMsgBo中positionId字段验证注解错误
### 问题描述
在新增班级干部信息时,出现验证异常:
```
java.lang.NullPointerException: Cannot read the array length because "<local4>" is null
at org.dromara.system.service.impl.SysRoleServiceImpl.insertRoleMenu(SysRoleServiceImpl.java:356)
jakarta.validation.UnexpectedTypeException: HV000030: No validator could be found for constraint 'jakarta.validation.constraints.NotBlank' validating type 'java.lang.Long'. Check configuration for 'positionId'
```
## 问题分析
1. **根本原因**: 在 `XgClassPosDictServiceImpl.createSystemRoleForPosition` 方法中,创建 `SysRoleBo` 对象时没有设置 `menuIds` 和 `deptIds` 字段
2. **触发位置**: `SysRoleServiceImpl.insertRoleMenu` 方法第356行 `for (Long menuId : role.getMenuIds())` 时,`role.getMenuIds()` 返回 `null`
3. **影响范围**: 班级干部职务配置的新增功能无法正常使用
## 解决方案
### 问题原因
在 `XgClassPosMsgBo` 类中,`positionId` 字段(类型为 `Long`)错误地使用了 `@NotBlank` 注解。`@NotBlank` 注解只能用于字符串类型,对于 `Long` 类型应该使用 `@NotNull` 注解。
### 修复内容
**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgClassPosMsgBo.java`
1. **修复文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgClassPosDictServiceImpl.java`
**修改前**:
```java
@NotBlank(message = "职务编码id,关联xg_class_pos_dict.id不能为空", groups = { AddGroup.class, EditGroup.class })
private Long positionId;
```
2. **修复方法**:
- 在 `createSystemRoleForPosition` 方法中,为 `SysRoleBo` 对象设置空的菜单ID和部门ID数组
- 在 `updateSystemRoleForPosition` 方法中,同样设置空的数组避免类似问题
**修改后**:
```java
@NotNull(message = "职务编码id,关联xg_class_pos_dict.id不能为空", groups = { AddGroup.class, EditGroup.class })
private Long positionId;
```
3. **具体修改**:
```java
// 设置空的菜单ID数组,避免空指针异常
roleBo.setMenuIds(new Long[0]);
// 设置空的部门ID数组,避免空指针异常
roleBo.setDeptIds(new Long[0]);
```
### 修复结果
- ✅ 修复了验证注解类型不匹配的问题
- ✅ 新增班级干部信息功能可以正常工作
- ✅ 保持了字段的必填验证功能
### 技术细节
- **数据类型**: `SysRoleBo` 类中的 `menuIds` 和 `deptIds` 字段为 `Long[]` 类型,不是 `List<Long>`
- **空值处理**: 使用 `new Long[0]` 创建空数组,避免 `null` 值导致的空指针异常
- **异常处理**: 保持原有的 try-catch 异常处理机制,确保主业务流程不受影响
## 验证结果
- ✅ 修复后班级干部职务配置新增功能正常工作
- ✅ 系统角色创建成功,无空指针异常
- ✅ 数据库记录正确插入(职务配置表和角色表)
- ✅ 更新功能同样正常工作
## 相关文档
- **API文档**: `docs/开发文档/API/班干部管理API.md`
- **业务模块**: `docs/业务模块/学工管理-学生信息管理/`
- **数据库设计**: `docs/设计文档/database/学工管理-学生信息管理/`
## 注意事项
1. **向后兼容**: 此修复不影响现有功能,只是修复了空指针异常
2. **权限管理**: 创建的班级干部角色默认不分配任何菜单权限,需要后续手动配置
3. **数据一致性**: 职务配置与系统角色的关联关系通过职务编码(`positionCode`)和角色标识(`roleKey`)保持一致
---
**记录人**: wuzilong
**审核人**: -
**状态**: 已完成
### 技术要点
- `@NotBlank`: 用于字符串类型,验证字符串不为空且不只包含空白字符
- `@NotNull`: 用于任何类型,验证字段不为null
- 对于 `Long` 类型的必填字段,应该使用 `@NotNull` 而不是 `@NotBlank`
+14 -1
View File
@@ -10,6 +10,7 @@
- [学生信息管理API](./学生信息管理API.md)
- [学生信息收集批次管理API](./学生信息收集批次管理API.md)
- [班级信息管理API](./班级信息管理API.md)
- [班级干部信息管理API](./班级干部信息管理API.md)
- [宿舍管理API](./宿舍管理API.md)
- [宿舍管理员信息API](./宿舍管理员信息API.md)
- [宿舍管理员授权API](./宿舍管理员授权API.md)
@@ -99,7 +100,18 @@
---
## 📅 最新更新日志
## �� 最新更新日志
### 2025-01-27 - 新增班级学生查询接口
- **新增功能**: 新增 `GET /studentMsg/studentMsg/listByClassId` 接口
- **功能描述**: 查询指定班级中所有在籍学生信息
- **过滤条件**:
- `deleted = 0` (未删除)
- `student_status = '1'` (在籍状态)
- `class_id = 入参的classId` (班级ID)
- `name LIKE '%入参的name%'` (姓名模糊查询,可选)
- **排序方式**: 按创建时间倒序排列
- **权限要求**: `studentMsg:studentMsg:query`
### 2025-07-24 - 学生信息管理界面重构
- **界面重构**: 学生信息管理新增弹窗调整为多tab页形式
@@ -127,5 +139,6 @@
| [学生信息收集批次管理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 |
| [班级教师关联管理API](./班级教师关联管理API.md) | 班主任辅导员关联关系 | ✅ 完成 | 2025-01-27 |
@@ -0,0 +1,419 @@
# 学生信息管理API文档
## 1. 文档说明
### 1.1 API基础信息
- **模块名称**: 学工管理 - 学生信息管理
- **模块代码**: ruoyi-xg
- **基础路径**: `/studentMsg/studentMsg`
- **权限前缀**: `studentMsg:studentMsg`
- **版本**: 1.0.0
- **更新时间**: 2025-01-27(新增班级学生查询接口)
### 1.2 通用规范
- **请求方式**: 支持GET、POST、PUT、DELETE
- **数据格式**: JSON
- **字符编码**: UTF-8
- **认证方式**: Bearer Token
- **权限控制**: Sa-Token + 数据权限
### 1.3 响应格式
```json
{
"code": 200,
"msg": "操作成功",
"data": {},
"timestamp": "2025-01-27T18:00:00"
}
```
## 2. 学生信息管理接口
### 2.1 查询学生信息列表
- **接口路径**: GET `/studentMsg/studentMsg/list`
- **权限标识**: `studentMsg:studentMsg:list`
- **功能描述**: 分页查询学生信息列表,支持多条件筛选
#### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| pageNum | integer | 否 | 页码,默认1 |
| pageSize | integer | 否 | 每页大小,默认10 |
| studentNo | string | 否 | 学号 |
| name | string | 否 | 姓名(模糊查询) |
| idCard | string | 否 | 身份证号 |
| gender | string | 否 | 性别:0-男,1-女 |
| studentStatus | string | 否 | 学籍状态 |
| phone | string | 否 | 联系电话(模糊查询) |
| email | string | 否 | 邮箱 |
| classId | string | 否 | 班级ID |
| className | string | 否 | 班级名称 |
| majorId | string | 否 | 专业ID |
| majorName | string | 否 | 专业名称 |
| collegeId | string | 否 | 学院ID |
| collegeName | string | 否 | 学院名称 |
#### 响应数据
```json
{
"code": 200,
"msg": "查询成功",
"rows": [
{
"id": 1,
"studentNo": "2023001001",
"name": "张三",
"gender": "0",
"birthDate": "2000-01-01",
"idCard": "110101199901010001",
"phone": "13800138000",
"email": "zhangsan@example.com",
"classId": "101",
"className": "计算机2023-1班",
"majorId": "CS001",
"majorName": "计算机科学与技术",
"collegeId": "CS",
"collegeName": "计算机学院",
"studentStatus": "1",
"createTime": "2025-01-27 10:00:00",
"updateTime": "2025-01-27 10:00:00"
}
],
"total": 1
}
```
### 2.2 根据组织ID查询学生信息列表
- **接口路径**: POST `/studentMsg/studentMsg/listByDeptId`
- **权限标识**: `studentMsg:studentMsg:list`
- **功能描述**: 根据部门ID查询该组织下所有学生信息
#### 请求参数
```json
{
"dept_id": 101,
"name": "张",
"studentNo": "2023001",
"phone": "138",
"studentStatus": "1",
"beginTime": "2025-01-01",
"endTime": "2025-01-27"
}
```
#### 响应数据
```json
{
"code": 200,
"msg": "查询成功",
"rows": [...],
"total": 10
}
```
### 2.3 查询班级中所有学生信息 ⭐ 新增
- **接口路径**: GET `/studentMsg/studentMsg/listByClassId`
- **权限标识**: `studentMsg:studentMsg:query`
- **功能描述**: 查询指定班级中所有在籍学生信息
#### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| classId | string | 是 | 班级ID |
| name | string | 否 | 学生姓名(模糊查询) |
#### 响应数据
```json
{
"code": 200,
"msg": "查询成功",
"data": [
{
"id": 1,
"studentNo": "2023001001",
"name": "张三",
"gender": "0",
"birthDate": "2000-01-01",
"idCard": "110101199901010001",
"phone": "13800138000",
"email": "zhangsan@example.com",
"classId": "101",
"className": "计算机2023-1班",
"majorId": "CS001",
"majorName": "计算机科学与技术",
"collegeId": "CS",
"collegeName": "计算机学院",
"studentStatus": "1",
"createTime": "2025-01-27 10:00:00",
"updateTime": "2025-01-27 10:00:00"
}
]
}
```
#### 业务逻辑
1. 在 `xg_student_msg` 表中查询:
- `deleted = 0` (未删除)
- `student_status = '1'` (在籍状态)
- `class_id = 入参的classId` (班级ID)
- `name LIKE '%入参的name%'` (姓名模糊查询,可选)
- 按创建时间倒序排列
### 2.4 获取学生信息详情
- **接口路径**: GET `/studentMsg/studentMsg/{id}`
- **权限标识**: `studentMsg:studentMsg:query`
- **功能描述**: 根据ID获取学生详细信息
#### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | long | 是 | 学生ID |
#### 响应数据
```json
{
"code": 200,
"msg": "查询成功",
"data": {
"id": 1,
"studentNo": "2023001001",
"name": "张三",
"gender": "0",
"birthDate": "2000-01-01",
"idCard": "110101199901010001",
"phone": "13800138000",
"email": "zhangsan@example.com",
"classId": "101",
"className": "计算机2023-1班",
"majorId": "CS001",
"majorName": "计算机科学与技术",
"collegeId": "CS",
"collegeName": "计算机学院",
"studentStatus": "1",
"createTime": "2025-01-27 10:00:00",
"updateTime": "2025-01-27 10:00:00"
}
}
```
### 2.5 新增学生信息
- **接口路径**: POST `/studentMsg/studentMsg`
- **权限标识**: `studentMsg:studentMsg:add`
- **功能描述**: 新增学生信息
#### 请求参数
```json
{
"studentNo": "2023001001",
"name": "张三",
"gender": "0",
"birthDate": "2000-01-01",
"idCard": "110101199901010001",
"phone": "13800138000",
"email": "zhangsan@example.com",
"classId": "101",
"className": "计算机2023-1班",
"majorId": "CS001",
"majorName": "计算机科学与技术",
"collegeId": "CS",
"collegeName": "计算机学院",
"studentStatus": "1"
}
```
#### 响应数据
```json
{
"code": 200,
"msg": "新增成功"
}
```
### 2.6 修改学生信息
- **接口路径**: PUT `/studentMsg/studentMsg`
- **权限标识**: `studentMsg:studentMsg:edit`
- **功能描述**: 修改学生信息
#### 请求参数
```json
{
"id": 1,
"studentNo": "2023001001",
"name": "张三",
"gender": "0",
"birthDate": "2000-01-01",
"idCard": "110101199901010001",
"phone": "13800138000",
"email": "zhangsan@example.com",
"classId": "101",
"className": "计算机2023-1班",
"majorId": "CS001",
"majorName": "计算机科学与技术",
"collegeId": "CS",
"collegeName": "计算机学院",
"studentStatus": "1"
}
```
#### 响应数据
```json
{
"code": 200,
"msg": "修改成功"
}
```
### 2.7 删除学生信息
- **接口路径**: DELETE `/studentMsg/studentMsg/{ids}`
- **权限标识**: `studentMsg:studentMsg:remove`
- **功能描述**: 批量删除学生信息
#### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| ids | string | 是 | 学生ID列表,多个用逗号分隔 |
#### 响应数据
```json
{
"code": 200,
"msg": "删除成功"
}
```
### 2.8 导出学生信息
- **接口路径**: POST `/studentMsg/studentMsg/export`
- **权限标识**: `studentMsg:studentMsg:export`
- **功能描述**: 导出学生信息列表
#### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| studentNo | string | 否 | 学号 |
| name | string | 否 | 姓名 |
| studentStatus | string | 否 | 学籍状态 |
#### 响应数据
返回Excel文件流
### 2.9 查询学生个人信息
- **接口路径**: GET `/studentMsg/studentMsg/personalInfo`
- **权限标识**: `studentMsg:studentMsg:query`
- **功能描述**: 查询当前登录学生的个人信息
#### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| userId | long | 否 | 用户ID,为空时使用当前登录用户ID |
#### 响应数据
```json
{
"code": 200,
"msg": "查询成功",
"data": {
"id": 1,
"studentNo": "2023001001",
"name": "张三",
"gender": "0",
"birthDate": "2000-01-01",
"idCard": "110101199901010001",
"phone": "13800138000",
"email": "zhangsan@example.com",
"classId": "101",
"className": "计算机2023-1班",
"majorId": "CS001",
"majorName": "计算机科学与技术",
"collegeId": "CS",
"collegeName": "计算机学院",
"studentStatus": "1",
"createTime": "2025-01-27 10:00:00",
"updateTime": "2025-01-27 10:00:00"
}
}
```
## 3. 数据字典
### 3.1 性别字典
| 字典值 | 字典标签 |
|--------|----------|
| 0 | 男 |
| 1 | 女 |
### 3.2 学籍状态字典
| 字典值 | 字典标签 |
|--------|----------|
| 1 | 在籍 |
| 2 | 休学 |
| 3 | 复学 |
| 4 | 退学 |
| 5 | 毕业 |
### 3.3 信息状态字典
| 字典值 | 字典标签 |
|--------|----------|
| 1 | 正常 |
| 2 | 待审核 |
| 3 | 审核中 |
| 4 | 已驳回 |
| 5 | 已锁定 |
## 4. 错误码说明
| 错误码 | 说明 |
|--------|------|
| 200 | 操作成功 |
| 400 | 请求参数错误 |
| 401 | 未授权 |
| 403 | 无权限 |
| 404 | 资源不存在 |
| 500 | 服务器内部错误 |
## 5. 权限说明
### 5.1 数据权限控制
- **超级管理员**: 可查看所有学生信息
- **学工管理员**: 可查看所有学生信息
- **二级学院管理员**: 只能查看本学院学生信息
- **辅导员**: 只能查看负责班级的学生信息
- **班主任**: 只能查看负责班级的学生信息
- **学生**: 只能查看自己的信息
### 5.2 操作权限控制
- **新增**: 学工管理员、二级学院管理员
- **修改**: 学工管理员、二级学院管理员、辅导员、班主任(限制范围)
- **删除**: 学工管理员、二级学院管理员
- **导入**: 学工管理员、二级学院管理员
- **导出**: 学工管理员、二级学院管理员、辅导员、班主任
- **查询**: 所有有权限的用户
## 6. 注意事项
1. 所有接口都需要Bearer Token认证
2. 删除操作为逻辑删除,不会物理删除数据
3. 学号和身份证号必须唯一
4. 新增学生时会自动创建对应的系统用户账号
5. 支持多租户数据隔离
6. 所有时间字段使用yyyy-MM-dd HH:mm:ss格式
7. 数据权限按照用户角色自动过滤
8. 班级学生查询接口只返回在籍状态的学生
## 7. 更新日志
### 2025-01-27 - 新增班级学生查询接口
- **新增功能**: 新增 `GET /studentMsg/studentMsg/listByClassId` 接口
- **功能描述**: 查询指定班级中所有在籍学生信息
- **过滤条件**:
- `deleted = 0` (未删除)
- `student_status = '1'` (在籍状态)
- `class_id = 入参的classId` (班级ID)
- `name LIKE '%入参的name%'` (姓名模糊查询,可选)
- **排序方式**: 按创建时间倒序排列
---
**文档更新时间**: 2025-01-27
**文档版本**: 1.0.0
**维护人员**: Cursor AI
**更新说明**: 新增班级学生查询接口,完善学生信息管理API文档
@@ -0,0 +1,415 @@
# 班级干部信息管理API文档
## 📋 文档信息
| 项目 | 信息 |
|------|------|
| **模块名称** | 班级干部信息管理 |
| **模块代码** | ruoyi-xg |
| **基础路径** | `/xgClassPosMsg/xgClassPosMsg` |
| **权限前缀** | `xgClassPosMsg:xgClassPosMsg` |
| **版本** | V1.0.0 |
| **创建时间** | 2025-01-27 |
| **更新时间** | 2025-01-27 |
## 🎯 API概述
### 模块说明
班级干部信息管理模块提供完整的班级干部信息管理功能,包括班级干部的任命、任期管理、状态跟踪等全生命周期管理,以及用户角色关联功能。
### 技术规范
- **认证方式**: Bearer Token (Sa-Token)
- **数据格式**: JSON
- **响应格式**: 统一使用R类型或TableDataInfo分页格式
- **权限控制**: 基于角色的权限管理(RBAC)
### 核心特性
- **基础管理**: 班级干部信息的增删改查、导入导出
- **任期管理**: 任职日期、任期时长、到期提醒
- **状态跟踪**: 在任、到期、辞职、免职、调任、休学等状态
- **用户角色关联**: 自动建立学生与角色的关联关系
- **数据权限**: 支持按部门查询班级干部信息
## 📊 数据模型
### 班级干部信息VO (XgClassPosMsgVo)
```java
{
"id": 1, // 主键ID(雪花ID)
"deleted": 0, // 删除标志(0代表存在 1代表删除)
"classId": 101, // 班级ID,关联xg_class_msg.id
"className": "计算机2023-1班", // 班级名称(联表查询获取)
"studentId": 1001, // 学生ID,关联xg_student_msg.id
"studentNo": "2023001", // 学号
"studentName": "张三", // 学生姓名(冗余字段)
"positionCode": "monitor", // 职务编码,关联xg_class_pos_dict.position_code
"positionName": "班长", // 职务名称(联表查询获取)
"appointmentDate": "2024-09-01", // 任职日期
"termStartDate": "2024-09-01", // 任期开始日期
"termEndDate": "2025-07-01", // 任期结束日期
"termDuration": 10, // 任期时长(月)
"isCurrent": 1, // 是否现任:0-否,1-是
"cadreStatus": "1", // 学生职务任职状态:1-在任,2-到期,3-辞职,4-免职,5-调任,6-休学
"appointmentReason": "综合表现优秀", // 任命原因
"appointmentMethod": "appointment", // 学生职务任命方式:appointment-任命,election-选举,recommendation-推荐
"dismissalDate": null, // 离任日期
"dismissalReason": null, // 离任原因
"remark": "备注信息", // 备注信息
"createBy": 1, // 创建者
"createTime": "2024-09-01 10:00:00", // 创建时间
"updateBy": 1, // 更新者
"updateTime": "2024-09-01 10:00:00" // 更新时间
}
```
## 🔧 API接口列表
### 1. 基础管理接口
#### 1.1 查询班级干部信息列表
- **接口路径**: GET `/xgClassPosMsg/xgClassPosMsg/list`
- **权限标识**: `xgClassPosMsg:xgClassPosMsg:list`
- **功能描述**: 分页查询班级干部信息列表,支持按部门查询
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| pageNum | integer | 否 | 页码,默认1 |
| pageSize | integer | 否 | 每页大小,默认10 |
| deptId | long | 否 | 部门ID,用于按部门查询班级干部信息 |
| classId | long | 否 | 班级ID |
| studentId | long | 否 | 学生ID |
| studentNo | string | 否 | 学号(模糊查询) |
| studentName | string | 否 | 学生姓名(模糊查询) |
| positionCode | string | 否 | 职务编码 |
| appointmentDate | string | 否 | 任职日期 |
| termStartDate | string | 否 | 任期开始日期 |
| termEndDate | string | 否 | 任期结束日期 |
| termDuration | integer | 否 | 任期时长(月) |
| isCurrent | integer | 否 | 是否现任:0-否,1-是 |
| cadreStatus | string | 否 | 学生职务任职状态 |
| appointmentReason | string | 否 | 任命原因 |
| appointmentMethod | string | 否 | 学生职务任命方式 |
| dismissalDate | string | 否 | 离任日期 |
| dismissalReason | string | 否 | 离任原因 |
**响应数据**:
```json
{
"code": 200,
"msg": "查询成功",
"rows": [
{
"id": 1,
"classId": 101,
"className": "计算机2023-1班",
"studentId": 1001,
"studentNo": "2023001",
"studentName": "张三",
"positionCode": "monitor",
"positionName": "班长",
"appointmentDate": "2024-09-01",
"termStartDate": "2024-09-01",
"termEndDate": "2025-07-01",
"termDuration": 10,
"isCurrent": 1,
"cadreStatus": "1",
"appointmentReason": "综合表现优秀",
"appointmentMethod": "appointment",
"createTime": "2024-09-01 10:00:00"
}
],
"total": 1
}
```
#### 1.2 获取班级干部信息详情
- **接口路径**: GET `/xgClassPosMsg/xgClassPosMsg/{id}`
- **权限标识**: `xgClassPosMsg:xgClassPosMsg:query`
- **功能描述**: 根据主键获取班级干部信息详细信息
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | long | 是 | 主键ID |
**响应数据**:
```json
{
"code": 200,
"msg": "查询成功",
"data": {
"id": 1,
"classId": 101,
"className": "计算机2023-1班",
"studentId": 1001,
"studentNo": "2023001",
"studentName": "张三",
"positionCode": "monitor",
"positionName": "班长",
"appointmentDate": "2024-09-01",
"termStartDate": "2024-09-01",
"termEndDate": "2025-07-01",
"termDuration": 10,
"isCurrent": 1,
"cadreStatus": "1",
"appointmentReason": "综合表现优秀",
"appointmentMethod": "appointment",
"dismissalDate": null,
"dismissalReason": null,
"remark": "备注信息",
"createTime": "2024-09-01 10:00:00",
"updateTime": "2024-09-01 10:00:00"
}
}
```
#### 1.3 新增班级干部信息
- **接口路径**: POST `/xgClassPosMsg/xgClassPosMsg`
- **权限标识**: `xgClassPosMsg:xgClassPosMsg:add`
- **功能描述**: 新增班级干部信息,同时自动建立用户角色关联
**请求参数**:
```json
{
"classId": 101,
"studentId": 1001,
"studentNo": "2023001",
"studentName": "张三",
"positionCode": "monitor",
"appointmentDate": "2024-09-01",
"termStartDate": "2024-09-01",
"termEndDate": "2025-07-01",
"termDuration": 10,
"isCurrent": 1,
"cadreStatus": "1",
"appointmentReason": "综合表现优秀",
"appointmentMethod": "appointment",
"remark": "备注信息"
}
```
**响应数据**:
```json
{
"code": 200,
"msg": "新增成功"
}
```
**重要说明**:
- 新增班级干部信息时,系统会自动向 `sys_user_role` 表插入一条数据
- `user_id` = 当前记录的 `studentId`
- `role_id` = 当前记录的 `id`(班级干部信息ID)
- 此操作在事务中进行,确保数据一致性
#### 1.4 修改班级干部信息
- **接口路径**: PUT `/xgClassPosMsg/xgClassPosMsg`
- **权限标识**: `xgClassPosMsg:xgClassPosMsg:edit`
- **功能描述**: 修改班级干部信息
**请求参数**:
```json
{
"id": 1,
"classId": 101,
"studentId": 1001,
"studentNo": "2023001",
"studentName": "张三",
"positionCode": "monitor",
"appointmentDate": "2024-09-01",
"termStartDate": "2024-09-01",
"termEndDate": "2025-07-01",
"termDuration": 10,
"isCurrent": 1,
"cadreStatus": "1",
"appointmentReason": "综合表现优秀",
"appointmentMethod": "appointment",
"remark": "修改后的备注信息"
}
```
**响应数据**:
```json
{
"code": 200,
"msg": "修改成功"
}
```
#### 1.5 删除班级干部信息
- **接口路径**: DELETE `/xgClassPosMsg/xgClassPosMsg/{ids}`
- **权限标识**: `xgClassPosMsg:xgClassPosMsg:remove`
- **功能描述**: 删除班级干部信息(支持批量删除),同时删除对应的用户角色关联
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| ids | string | 是 | 主键ID,多个用逗号分隔 |
**响应数据**:
```json
{
"code": 200,
"msg": "删除成功"
}
```
**重要说明**:
- 删除班级干部信息时,系统会自动删除 `sys_user_role` 表中对应的角色关联
- 删除操作会先清理关联数据,再删除主数据
- 此操作在事务中进行,确保数据一致性
#### 1.6 导出班级干部信息
- **接口路径**: POST `/xgClassPosMsg/xgClassPosMsg/export`
- **权限标识**: `xgClassPosMsg:xgClassPosMsg:export`
- **功能描述**: 导出班级干部信息列表
**请求参数**: 同列表查询参数
**响应数据**: Excel文件下载
## 🔐 用户角色关联功能
### 功能说明
班级干部信息管理模块集成了用户角色关联功能,确保班级干部信息与系统权限管理的一致性。
### 关联规则
1. **新增关联**: 新增班级干部信息时,自动建立用户角色关联
- `sys_user_role.user_id` = `xg_class_pos_msg.student_id`
- `sys_user_role.role_id` = `xg_class_pos_msg.id`
2. **删除关联**: 删除班级干部信息时,自动删除对应的用户角色关联
- 根据 `role_id` 删除 `sys_user_role` 表中的记录
### 事务处理
- 所有用户角色关联操作都在事务中进行
- 关联操作失败不会影响主业务流程,但会记录警告日志
- 使用 `@Transactional(rollbackFor = Exception.class)` 确保数据一致性
### 异常处理
- 用户角色关联操作异常会被捕获并记录日志
- 不会抛出异常影响主业务流程
- 通过日志记录操作结果和异常信息
## 📝 数据字典
### 学生职务任职状态 (cadre_status)
| 编码 | 名称 | 说明 |
|------|------|------|
| 1 | 在任 | 正常任职状态 |
| 2 | 到期 | 任期已结束 |
| 3 | 辞职 | 主动辞职 |
| 4 | 免职 | 被免职 |
| 5 | 调任 | 调任其他职务 |
| 6 | 休学 | 因休学暂停职务 |
### 学生职务任命方式 (appointment_method)
| 编码 | 名称 | 说明 |
|------|------|------|
| appointment | 任命 | 直接任命 |
| election | 选举 | 通过选举产生 |
| recommendation | 推荐 | 通过推荐产生 |
### 是否现任 (is_current)
| 编码 | 名称 | 说明 |
|------|------|------|
| 0 | 否 | 非现任职务 |
| 1 | 是 | 现任职务 |
## 🔒 权限说明
### 权限标识列表
| 权限标识 | 权限名称 | 说明 |
|----------|----------|------|
| xgClassPosMsg:xgClassPosMsg:list | 班级干部信息列表 | 查看班级干部信息列表 |
| xgClassPosMsg:xgClassPosMsg:query | 班级干部信息查询 | 查询班级干部信息详情 |
| xgClassPosMsg:xgClassPosMsg:add | 班级干部信息新增 | 新增班级干部信息 |
| xgClassPosMsg:xgClassPosMsg:edit | 班级干部信息修改 | 修改班级干部信息 |
| xgClassPosMsg:xgClassPosMsg:remove | 班级干部信息删除 | 删除班级干部信息 |
| xgClassPosMsg:xgClassPosMsg:export | 班级干部信息导出 | 导出班级干部信息 |
### 数据权限
- 支持按部门查询班级干部信息
- 通过 `deptId` 参数控制数据权限范围
- 自动包含子部门的数据
## 🚨 错误码说明
| 错误码 | 错误信息 | 说明 |
|--------|----------|------|
| 40001 | 学生不存在 | 指定的学生ID不存在 |
| 40002 | 班级不存在 | 指定的班级ID不存在 |
| 40003 | 职务不存在 | 指定的职务编码不存在 |
| 40004 | 学生已担任该职务 | 学生已经担任该职务 |
| 40005 | 班级干部记录不存在 | 指定的班级干部记录不存在 |
| 40006 | 操作权限不足 | 当前用户没有操作权限 |
| 40007 | 数据校验失败 | 提交的数据不符合规则 |
| 40008 | 用户角色关联失败 | 用户角色关联操作失败 |
## 📋 使用示例
### 1. 查询班级干部信息
```javascript
// 查询指定部门的班级干部信息
const response = await api.get('/xgClassPosMsg/xgClassPosMsg/list', {
params: {
deptId: 101,
pageNum: 1,
pageSize: 10
}
});
console.log(response.data);
```
### 2. 新增班级干部
```javascript
// 新增班级干部信息
const addData = {
classId: 101,
studentId: 1001,
studentNo: "2023001",
studentName: "张三",
positionCode: "monitor",
appointmentDate: "2024-09-01",
termStartDate: "2024-09-01",
termEndDate: "2025-07-01",
termDuration: 10,
isCurrent: 1,
cadreStatus: "1",
appointmentReason: "综合表现优秀",
appointmentMethod: "appointment"
};
const response = await api.post('/xgClassPosMsg/xgClassPosMsg', addData);
```
### 3. 删除班级干部
```javascript
// 删除班级干部信息
const response = await api.delete('/xgClassPosMsg/xgClassPosMsg/1,2,3');
```
## ⚠️ 注意事项
1. **数据一致性**: 班级干部信息与用户角色关联保持一致
2. **权限控制**: 严格按照角色权限进行操作控制
3. **事务管理**: 所有关联操作都在事务中进行
4. **任期管理**: 注意任期时间的合理性和连续性
5. **状态管理**: 正确维护班级干部的任职状态
6. **并发控制**: 防止同时操作导致的数据冲突
7. **日志记录**: 重要操作都会记录详细日志
## 📈 更新日志
| 版本 | 日期 | 更新内容 |
|------|------|----------|
| V1.0.0 | 2025-01-27 | 初始版本,完整的班级干部信息管理API |
| V1.0.1 | 2025-01-27 | 新增用户角色关联功能 |
---
**文档维护**: Cursor AI 辅助
**最后更新**: 2025-01-27
**文档状态**: 已完成
@@ -51,7 +51,7 @@ CREATE TABLE `xg_class_pos_dict` (
-- ===========================================
-- 2. 班级干部信息表 (xg_class_cadre_info)
-- ===========================================
DROP TABLE IF EXISTS `xg_class_pos_smg`;
DROP TABLE IF EXISTS `xg_class_pos_msg`;
CREATE TABLE `xg_class_pos_msg` (
-- 标准基础字段(必须)
`id` bigint(20) NOT NULL COMMENT '主键ID(雪花ID)',
@@ -68,6 +68,7 @@ CREATE TABLE `xg_class_pos_msg` (
`student_no` varchar(32) NOT NULL COMMENT '学号',
`student_name` varchar(50) NOT NULL COMMENT '学生姓名(冗余字段)',
-- 职务信息
`position_id` bigint(20) NOT NULL COMMENT '职务编码,关联xg_class_pos_dict.id',
`position_code` varchar(20) NOT NULL COMMENT '职务编码,关联xg_class_pos_dict.position_code',
-- 任期信息
`appointment_date` date NOT NULL COMMENT '任职日期',
@@ -61,3 +61,22 @@ export const delXgClassPosMsg = (id: string | number | Array<string | number>) =
method: 'delete'
});
};
/**
* 学生干部离任
* @param id 班级干部信息ID
* @param termEndDate 任期结束日期
* @param cadreStatus 学生职务任职状态
* @param dismissalReason 离任原因
*/
export const dismissCadre = (id: string | number, termEndDate: string, cadreStatus: string, dismissalReason: string) => {
return request({
url: '/xgClassPosMsg/xgClassPosMsg/dismiss/' + id,
method: 'post',
params: {
termEndDate,
cadreStatus,
dismissalReason
}
});
};
@@ -14,6 +14,11 @@ export interface XgClassPosMsgVO {
*/
classId: string | number;
/**
* 班级名称(联表查询获取)
*/
className: string;
/**
* 学生ID,关联xg_student_msg.id
*/
@@ -34,6 +39,11 @@ export interface XgClassPosMsgVO {
*/
positionCode: string;
/**
* 职务名称(联表查询获取)
*/
positionName: string;
/**
* 任职日期
*/
@@ -107,6 +117,11 @@ export interface XgClassPosMsgForm extends BaseEntity {
*/
classId?: string | number;
/**
* 班级名称(显示用,非数据库字段)
*/
className?: string;
/**
* 学生ID,关联xg_student_msg.id
*/
@@ -122,11 +137,21 @@ export interface XgClassPosMsgForm extends BaseEntity {
*/
studentName?: string;
/**
* 职务编码id,关联xg_class_pos_dict.id
*/
positionId?: string | number;
/**
* 职务编码,关联xg_class_pos_dict.position_code
*/
positionCode?: string;
/**
* 职务名称(显示用,非数据库字段)
*/
positionName?: string;
/**
* 任职日期
*/
@@ -266,6 +291,11 @@ export interface XgClassPosMsgQuery extends PageQuery {
*/
dismissalReason?: string;
/**
* 部门ID,用于按部门查询班级干部信息
*/
deptId?: string | number;
/**
* 日期范围参数
*/
@@ -101,7 +101,11 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['xgClassPosMsg:xgClassPosMsg:add']">新增</el-button>
<el-tooltip content="只有在班级节点下才能新增班级干部信息" placement="top" :disabled="canAdd">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['xgClassPosMsg:xgClassPosMsg:add']" :disabled="!canAdd"
>新增</el-button
>
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-button
@@ -142,12 +146,12 @@
<!-- <el-table-column label="学生ID,关联xg_student_msg.id" align="center" prop="studentId" /> -->
<el-table-column label="学号" align="center" prop="studentNo" />
<el-table-column label="学生姓名" align="center" prop="studentName" />
<el-table-column label="职务编码" align="center" prop="positionCode" />
<el-table-column label="任职日期" align="center" prop="appointmentDate" width="180">
<el-table-column label="职务名称" align="center" prop="positionName" />
<!-- <el-table-column label="任职日期" align="center" prop="appointmentDate" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.appointmentDate, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
</el-table-column> -->
<el-table-column label="任期开始日期" align="center" prop="termStartDate" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.termStartDate, '{y}-{m}-{d}') }}</span>
@@ -161,7 +165,7 @@
<!-- <el-table-column label="任期时长" align="center" prop="termDuration" /> -->
<el-table-column label="是否现任" align="center" prop="isCurrent">
<template #default="scope">
<dict-tag :options="sys_yes_no" :value="scope.row.isCurrent" />
<dict-tag :options="sys_yesno" :value="scope.row.isCurrent" />
</template>
</el-table-column>
<el-table-column label="学生职务任职状态" align="center" prop="cadreStatus">
@@ -175,14 +179,14 @@
<dict-tag :options="stu_appointment_method" :value="scope.row.appointmentMethod" />
</template>
</el-table-column>
<el-table-column label="离任日期" align="center" prop="dismissalDate" width="180">
<!-- <el-table-column label="离任日期" align="center" prop="dismissalDate" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.dismissalDate, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
</el-table-column> -->
<!-- <el-table-column label="离任原因" align="center" prop="dismissalReason" /> -->
<el-table-column label="备注信息" align="center" prop="remark" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<el-table-column label="操作" align="center" width="220" class-name="small-padding fixed-width" fixed="right">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button
@@ -193,10 +197,20 @@
v-hasPermi="['xgClassPosMsg:xgClassPosMsg:edit']"
></el-button>
</el-tooltip>
<el-tooltip content="离任" placement="top">
<el-button
link
type="warning"
icon="ArrowDown"
@click="handleDismissal(scope.row)"
v-hasPermi="['xgClassPosMsg:xgClassPosMsg:edit']"
:disabled="scope.row.cadreStatus !== '1'"
></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button
link
type="primary"
type="danger"
icon="Delete"
@click="handleDelete(scope.row)"
v-hasPermi="['xgClassPosMsg:xgClassPosMsg:remove']"
@@ -218,31 +232,83 @@
</div>
<!-- 添加或修改班级干部信息对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="xgClassPosMsgFormRef" :model="form" :rules="rules" label-width="80px">
<el-form ref="xgClassPosMsgFormRef" :model="form" :rules="rules" label-width="140px">
<!-- <el-form-item label="删除标志" prop="deleted">
<el-input v-model="form.deleted" placeholder="请输入删除标志" />
</el-form-item> -->
<!-- <el-form-item label="班级ID,关联xg_class_msg.id" prop="classId">
<el-input v-model="form.classId" placeholder="请输入班级ID,关联xg_class_msg.id" />
</el-form-item> -->
<el-form-item label="学生ID" prop="studentId">
<el-input v-model="form.studentId" placeholder="请输入学生ID" />
<!-- 增加一个班级名称字段 班级名称字段不可编辑,默认左侧树选择的节点名称 -->
<el-form-item label="班级名称" prop="className">
<el-input v-model="form.className" placeholder="请输入班级名称" disabled />
</el-form-item>
<el-form-item label="学生" prop="studentId">
<el-select
v-model="form.studentId"
placeholder="请选择学生"
filterable
remote
:remote-method="handleStudentSearch"
:loading="studentSearchLoading"
clearable
style="width: 100%"
@change="handleStudentChange"
@focus="handleStudentFocus"
:disabled="!!form.id"
>
<el-option v-for="item in studentOptions" :key="item.id" :label="`${item.name}(${item.studentNo})`" :value="item.id">
<span>{{ item.name }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.studentNo }}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学号" prop="studentNo">
<el-input v-model="form.studentNo" placeholder="请输入学号" />
<el-input v-model="form.studentNo" placeholder="请先选择学生" disabled />
</el-form-item>
<el-form-item label="学生姓名" prop="studentName">
<el-input v-model="form.studentName" placeholder="请输入学生姓名" />
<el-input v-model="form.studentName" placeholder="请先选择学生" disabled />
</el-form-item>
<el-form-item label="职务编码" prop="positionCode">
<el-input v-model="form.positionCode" placeholder="请输入职务编码" />
<el-form-item label="职务名称" prop="positionCode">
<el-select
v-model="form.positionCode"
placeholder="请选择职务名称"
filterable
remote
:remote-method="handlePositionSearch"
:loading="positionSearchLoading"
clearable
style="width: 100%"
@change="handlePositionChange"
@focus="handlePositionFocus"
:disabled="!!form.id"
>
<el-option v-for="item in positionOptions" :key="item.positionCode" :label="item.positionName" :value="item.positionCode">
<span>{{ item.positionName }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.positionCode }}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="任职日期" prop="appointmentDate">
<el-date-picker clearable v-model="form.appointmentDate" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择任职日期">
<!-- <el-form-item label="任职日期" prop="appointmentDate">
<el-date-picker
clearable
v-model="form.appointmentDate"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择任职日期"
style="width: 100%"
>
</el-date-picker>
</el-form-item>
</el-form-item> -->
<el-form-item label="任期开始日期" prop="termStartDate">
<el-date-picker clearable v-model="form.termStartDate" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择任期开始日期">
<el-date-picker
clearable
v-model="form.termStartDate"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择任期开始日期"
style="width: 100%"
>
</el-date-picker>
</el-form-item>
<!-- <el-form-item label="任期结束日期" prop="termEndDate">
@@ -258,7 +324,7 @@
</el-radio-group>
</el-form-item> -->
<el-form-item label="学生职务任职状态" prop="cadreStatus">
<el-select v-model="form.cadreStatus" placeholder="请选择学生职务任职状态">
<el-select v-model="form.cadreStatus" placeholder="请选择学生职务任职状态" :disabled="!!form.id">
<el-option v-for="dict in stu_cadre_status" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
@@ -270,13 +336,20 @@
<el-option v-for="dict in stu_appointment_method" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="离任日期" prop="dismissalDate">
<el-date-picker clearable v-model="form.dismissalDate" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择离任日期">
<!-- <el-form-item label="离任日期" prop="dismissalDate">
<el-date-picker
clearable
v-model="form.dismissalDate"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择离任日期"
style="width: 100%"
>
</el-date-picker>
</el-form-item>
<el-form-item label="离任原因" prop="dismissalReason">
</el-form-item> -->
<!-- <el-form-item label="离任原因" prop="dismissalReason">
<el-input v-model="form.dismissalReason" placeholder="请输入离任原因" />
</el-form-item>
</el-form-item> -->
<el-form-item label="备注信息" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
@@ -288,17 +361,77 @@
</div>
</template>
</el-dialog>
<!-- 离任对话框 -->
<el-dialog title="班级干部离任" v-model="dismissalDialog.visible" width="500px" append-to-body>
<el-form ref="dismissalFormRef" :model="dismissalForm" :rules="dismissalRules" label-width="140px">
<!-- 当前信息(只读) -->
<el-form-item label="班级名称" prop="className">
<el-input v-model="dismissalForm.className" disabled />
</el-form-item>
<el-form-item label="学号" prop="studentNo">
<el-input v-model="dismissalForm.studentNo" disabled />
</el-form-item>
<el-form-item label="学生姓名" prop="studentName">
<el-input v-model="dismissalForm.studentName" disabled />
</el-form-item>
<el-form-item label="职务名称" prop="positionName">
<el-input v-model="dismissalForm.positionName" disabled />
</el-form-item>
<el-form-item label="任职开始日期" prop="termStartDate">
<el-input v-model="dismissalForm.termStartDate" disabled />
</el-form-item>
<!-- 离任信息(可填写) -->
<el-form-item label="任期结束日期" prop="termEndDate">
<el-date-picker
clearable
v-model="dismissalForm.termEndDate"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择任期结束日期"
style="width: 100%"
>
</el-date-picker>
</el-form-item>
<el-form-item label="学生职务任职状态" prop="cadreStatus">
<el-select v-model="dismissalForm.cadreStatus" placeholder="请选择学生职务任职状态">
<el-option
v-for="dict in stu_cadre_status.filter((item) => item.value !== '1')"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="离任原因" prop="dismissalReason">
<el-input v-model="dismissalForm.dismissalReason" type="textarea" placeholder="请输入离任原因" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="dismissalButtonLoading" type="primary" @click="submitDismissalForm">确 定</el-button>
<el-button @click="cancelDismissal">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="XgClassPosMsg" lang="ts">
import { listXgClassPosMsg, getXgClassPosMsg, delXgClassPosMsg, addXgClassPosMsg, updateXgClassPosMsg } from '@/api/xgClassPosMsg/xgClassPosMsg';
import {
listXgClassPosMsg,
getXgClassPosMsg,
delXgClassPosMsg,
addXgClassPosMsg,
updateXgClassPosMsg,
dismissCadre
} from '@/api/xgClassPosMsg/xgClassPosMsg';
import { XgClassPosMsgVO, XgClassPosMsgQuery, XgClassPosMsgForm } from '@/api/xgClassPosMsg/xgClassPosMsg/types';
import { getCurrentInstance, ref, reactive, toRefs, watchEffect, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { getCurrentInstance, ref, reactive, toRefs, watchEffect, onMounted, computed } from 'vue';
import { ElMessage, ElMessageBox, type FormInstance } from 'element-plus';
import { XgDeptTreeSelect, deptTreeSelect } from '@/api/studentMsg/index';
import type { FormInstance } from 'element-plus';
import request from '@/utils/request';
interface ComponentInternalInstance {
proxy?: any;
}
@@ -315,8 +448,8 @@ interface PageData<T, Q> {
}
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { stu_appointment_method, sys_yes_no, stu_cadre_status } = toRefs<any>(
proxy?.useDict('stu_appointment_method', 'sys_yes_no', 'stu_cadre_status')
const { stu_appointment_method, sys_yesno, stu_cadre_status } = toRefs<any>(
proxy?.useDict('stu_appointment_method', 'sys_yesno', 'stu_cadre_status')
);
const xgClassPosMsgList = ref<XgClassPosMsgVO[]>([]);
@@ -334,6 +467,14 @@ const deptOptions = ref<any[]>([]);
const deptName = ref('');
const currentSelectedNode = ref<any>(null);
// 学生选择相关变量
const studentOptions = ref<any[]>([]);
const studentSearchLoading = ref(false);
// 职务名称选择相关变量
const positionOptions = ref<any[]>([]);
const positionSearchLoading = ref(false);
const queryFormRef = ref<FormInstance>();
const xgClassPosMsgFormRef = ref<FormInstance>();
@@ -342,22 +483,73 @@ const dialog = reactive<DialogOption>({
title: ''
});
// 离任对话框相关变量
const dismissalDialog = reactive<DialogOption>({
visible: false,
title: '班级干部离任'
});
const dismissalFormRef = ref<FormInstance>();
const dismissalButtonLoading = ref(false);
// 离任表单数据
const dismissalForm = reactive({
id: undefined,
className: '',
studentNo: '',
studentName: '',
positionName: '',
termStartDate: '',
termEndDate: '',
cadreStatus: '',
dismissalReason: '',
dismissalDate: ''
});
// 离任表单验证规则
const dismissalRules = {
termEndDate: [{ required: true, message: '任期结束日期不能为空', trigger: 'blur' }],
cadreStatus: [{ required: true, message: '请选择学生职务任职状态', trigger: 'change' }],
dismissalReason: [{ required: true, message: '离任原因不能为空', trigger: 'blur' }]
};
// 获取当前日期字符串(YYYY-MM-DD格式)
const getCurrentDate = () => {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0');
const day = String(today.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
// 格式化日期为YYYY-MM-DD格式
const formatDate = (date: string | Date | undefined) => {
if (!date) return '';
const d = new Date(date);
if (isNaN(d.getTime())) return '';
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
const initFormData: XgClassPosMsgForm = {
id: undefined,
deleted: undefined,
classId: undefined,
className: undefined,
studentId: undefined,
studentNo: undefined,
studentName: undefined,
positionCode: undefined,
appointmentDate: undefined,
termStartDate: undefined,
appointmentDate: getCurrentDate(), // 默认值为当前日期
termStartDate: getCurrentDate(), // 默认值为当前日期
termEndDate: undefined,
termDuration: undefined,
isCurrent: undefined,
cadreStatus: undefined,
cadreStatus: '1', // 默认值:1-在任
appointmentReason: undefined,
appointmentMethod: undefined,
appointmentMethod: 'appointment', // 默认值:appointment-任命
dismissalDate: undefined,
dismissalReason: undefined,
remark: undefined
@@ -389,10 +581,10 @@ const data = reactive<PageData<XgClassPosMsgForm, XgClassPosMsgQuery>>({
rules: {
id: [{ required: true, message: '主键ID不能为空', trigger: 'blur' }],
classId: [{ required: true, message: '班级ID不能为空', trigger: 'blur' }],
studentId: [{ required: true, message: '学生ID不能为空', trigger: 'blur' }],
studentId: [{ required: true, message: '请选择学生', trigger: 'change' }],
studentNo: [{ required: true, message: '学号不能为空', trigger: 'blur' }],
studentName: [{ required: true, message: '学生姓名不能为空', trigger: 'blur' }],
positionCode: [{ required: true, message: '职务编码不能为空', trigger: 'blur' }],
positionCode: [{ required: true, message: '请选择职务名称', trigger: 'change' }],
appointmentDate: [{ required: true, message: '任职日期不能为空', trigger: 'blur' }],
termStartDate: [{ required: true, message: '任期开始日期不能为空', trigger: 'blur' }]
}
@@ -400,6 +592,20 @@ const data = reactive<PageData<XgClassPosMsgForm, XgClassPosMsgQuery>>({
const { queryParams, form, rules } = toRefs(data);
/** 计算新增按钮是否可用 */
const canAdd = computed(() => {
if (!currentSelectedNode.value) {
return false;
}
// 检查当前选中节点的deptCategory是否以bj-开头(支持大小写组合)
const deptCategory = currentSelectedNode.value.deptCategory;
if (!deptCategory) {
return false;
}
// 转换为小写进行匹配,支持 bj-、BJ-、Bj-、bJ- 等大小写组合
return deptCategory.toLowerCase().startsWith('bj-');
});
/** 根据名称筛选部门树 */
watchEffect(
() => {
@@ -490,9 +696,16 @@ const handleNodeClick = (data: any) => {
ElMessage.warning('没有该节点权限,请联系管理员');
return;
}
// 保存当前选中的节点信息
currentSelectedNode.value = data;
queryParams.value.deptId = data.id;
// 如果选中的不是班级节点,给出提示
if (!canAdd.value) {
ElMessage.info('当前节点不是班级节点,无法新增班级干部信息');
}
handleQuery();
};
@@ -515,6 +728,10 @@ const cancel = () => {
const reset = () => {
form.value = { ...initFormData };
xgClassPosMsgFormRef.value?.resetFields();
// 清空学生选择相关数据
studentOptions.value = [];
// 清空职务名称选择相关数据
positionOptions.value = [];
};
/** 搜索按钮操作 */
@@ -550,7 +767,20 @@ const handleSelectionChange = (selection: XgClassPosMsgVO[]) => {
/** 新增按钮操作 */
const handleAdd = () => {
// 再次验证是否可以新增
if (!canAdd.value) {
ElMessage.warning('只有在班级节点下才能新增班级干部信息');
return;
}
reset();
// 设置班级名称和班级ID
if (currentSelectedNode.value) {
form.value.className = currentSelectedNode.value.label;
form.value.classId = currentSelectedNode.value.id;
}
dialog.visible = true;
dialog.title = '添加班级干部信息';
};
@@ -561,6 +791,34 @@ const handleUpdate = async (row?: XgClassPosMsgVO) => {
const _id = row?.id || ids.value[0];
const res = await getXgClassPosMsg(_id);
Object.assign(form.value, res.data);
// 如果是编辑模式,需要将当前学生信息添加到选项中
if (form.value.studentId && form.value.studentName && form.value.studentNo) {
studentOptions.value = [
{
id: form.value.studentId,
name: form.value.studentName,
studentNo: form.value.studentNo
}
];
}
console.log(form.value);
// 如果是编辑模式,需要将当前职务信息添加到选项中
if (form.value.positionCode && form.value.positionName) {
positionOptions.value = [
{
positionCode: form.value.positionCode,
positionName: form.value.positionName
}
];
}
// 设置班级名称 - 优先使用后端返回的数据,如果没有则从当前选中的节点获取
if (!form.value.className && currentSelectedNode.value) {
form.value.className = currentSelectedNode.value.label;
}
dialog.visible = true;
dialog.title = '修改班级干部信息';
};
@@ -602,6 +860,188 @@ const handleExport = () => {
);
};
/** 学生搜索方法 */
const handleStudentSearch = async (query: string) => {
if (!currentSelectedNode.value || !canAdd.value) {
studentOptions.value = [];
return;
}
studentSearchLoading.value = true;
try {
const response = await request({
url: '/studentMsg/studentMsg/listByClassId',
method: 'get',
params: {
classId: currentSelectedNode.value.id,
name: query || '' // 允许空字符串查询所有学生
}
});
if (response.code === 200) {
studentOptions.value = response.data || [];
} else {
studentOptions.value = [];
ElMessage.error(response.msg || '获取学生列表失败');
}
} catch (error) {
console.error('获取学生列表失败:', error);
studentOptions.value = [];
ElMessage.error('获取学生列表失败');
} finally {
studentSearchLoading.value = false;
}
};
/** 学生选择变化处理 */
const handleStudentChange = (studentId: string | number) => {
if (studentId) {
const selectedStudent = studentOptions.value.find((item) => item.id === studentId);
if (selectedStudent) {
form.value.studentNo = selectedStudent.studentNo;
form.value.studentName = selectedStudent.name;
}
} else {
// 清空选择时,清空相关字段
form.value.studentNo = '';
form.value.studentName = '';
}
};
/** 学生选择框获得焦点时加载学生列表 */
const handleStudentFocus = async () => {
// 如果当前没有学生选项数据,则加载默认的学生列表
if (studentOptions.value.length === 0) {
await handleStudentSearch('');
}
};
/** 职务名称搜索方法 */
const handlePositionSearch = async (query: string) => {
positionSearchLoading.value = true;
try {
const response = await request({
url: '/xgClassPosDict/xgClassPosDict/validPositions',
method: 'get',
params: {
positionName: query || '' // 允许空字符串查询所有职务
}
});
if (response.code === 200) {
positionOptions.value = response.data || [];
} else {
positionOptions.value = [];
ElMessage.error(response.msg || '获取职务列表失败');
}
} catch (error) {
console.error('获取职务列表失败:', error);
positionOptions.value = [];
ElMessage.error('获取职务列表失败');
} finally {
positionSearchLoading.value = false;
}
};
/** 职务名称选择变化处理 */
const handlePositionChange = (positionCode: string) => {
if (positionCode) {
const selectedPosition = positionOptions.value.find((item) => item.positionCode === positionCode);
console.log(selectedPosition);
if (selectedPosition) {
form.value.positionCode = selectedPosition.positionCode;
form.value.positionId = selectedPosition.id;
// 注意:这里不需要设置positionName,因为表单中只有positionCode字段
}
} else {
// 清空选择时,清空相关字段
form.value.positionCode = '';
form.value.positionId = '';
}
};
/** 职务名称选择框获得焦点时加载职务列表 */
const handlePositionFocus = async () => {
// 如果当前没有职务选项数据,则加载默认的职务列表
if (positionOptions.value.length === 0) {
await handlePositionSearch('');
}
};
/** 离任按钮操作 */
const handleDismissal = async (row?: XgClassPosMsgVO) => {
if (!row) {
return;
}
// 清空表单数据
Object.assign(dismissalForm, {
id: undefined,
className: '',
studentNo: '',
studentName: '',
positionName: '',
termStartDate: '',
termEndDate: '',
cadreStatus: '',
dismissalReason: '',
dismissalDate: ''
});
// 设置当前行数据
dismissalForm.id = row.id;
dismissalForm.className = row.className || currentSelectedNode.value?.label || '';
dismissalForm.studentNo = row.studentNo;
dismissalForm.studentName = row.studentName;
dismissalForm.positionName = row.positionName;
dismissalForm.termStartDate = formatDate(row.termStartDate);
// 设置默认的任期结束日期为当前日期
dismissalForm.termEndDate = getCurrentDate();
dismissalDialog.visible = true;
};
/** 离任表单提交 */
const submitDismissalForm = () => {
dismissalFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
dismissalButtonLoading.value = true;
try {
// 使用新的离任接口
await dismissCadre(dismissalForm.id, dismissalForm.termEndDate, dismissalForm.cadreStatus, dismissalForm.dismissalReason);
proxy?.$modal.msgSuccess('离任成功');
dismissalDialog.visible = false;
await getList();
} catch (error) {
console.error('离任失败:', error);
proxy?.$modal.msgError('离任失败');
} finally {
dismissalButtonLoading.value = false;
}
}
});
};
/** 离任对话框取消 */
const cancelDismissal = () => {
dismissalDialog.visible = false;
// 重置表单数据
Object.assign(dismissalForm, {
id: undefined,
className: '',
studentNo: '',
studentName: '',
positionName: '',
termStartDate: '',
termEndDate: '',
cadreStatus: '',
dismissalReason: '',
dismissalDate: ''
});
dismissalFormRef.value?.resetFields();
};
onMounted(() => {
getDeptTree(); // 初始化部门数据(会在完成后自动调用getList)
});