This commit is contained in:
zlwu committed 2025-12-24 13:19:04 +08:00
1 parent 86401c7753
commit 8783150883
13 files changed
+10336 -2

No files matched your search

@@ -0,0 +1,119 @@
package org.dromara.xg.controller;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.xg.domain.vo.XgStudentStatusChangeVo;
import org.dromara.xg.domain.bo.XgStudentStatusChangeBo;
import org.dromara.xg.service.IXgStudentStatusChangeService;
import org.dromara.common.mybatis.core.page.TableDataInfo;
/**
* 学生学籍异动
*
* @author zlwu
* @date 2025-07-23
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/studentMsg/studentStatusChange")
public class XgStudentStatusChangeController extends BaseController {
private final IXgStudentStatusChangeService xgStudentStatusChangeService;
/**
* 查询学生学籍异动列表
*/
// @SaCheckPermission("studentMsg:studentStatusChange:list")
@GetMapping("/list")
public TableDataInfo<XgStudentStatusChangeVo> list(XgStudentStatusChangeBo bo, PageQuery pageQuery) {
return xgStudentStatusChangeService.queryPageList(bo, pageQuery);
}
/**
* 导出学生学籍异动列表
*/
// @SaCheckPermission("studentMsg:studentStatusChange:export")
@Log(title = "学生学籍异动", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(XgStudentStatusChangeBo bo, HttpServletResponse response) {
List<XgStudentStatusChangeVo> list = xgStudentStatusChangeService.queryList(bo);
ExcelUtil.exportExcel(list, "学生学籍异动", XgStudentStatusChangeVo.class, response);
}
/**
* 获取学生学籍异动详细信息
*
* @param id 主键
*/
// @SaCheckPermission("studentMsg:studentStatusChange:query")
@GetMapping("/{id}")
public R<XgStudentStatusChangeVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
return R.ok(xgStudentStatusChangeService.queryById(id));
}
/**
* 新增学生学籍异动
*/
// @SaCheckPermission("studentMsg:studentStatusChange:add")
@Log(title = "学生学籍异动", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody XgStudentStatusChangeBo bo) {
return toAjax(xgStudentStatusChangeService.insertByBo(bo));
}
/**
* 批量新增学生学籍异动(用于前端提交多个学生的异动)
*/
// @SaCheckPermission("studentMsg:studentStatusChange:add")
@Log(title = "学生学籍异动", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping("/batch")
public R<Void> addBatch(@Validated(AddGroup.class) @RequestBody List<XgStudentStatusChangeBo> boList) {
boolean result = true;
for (XgStudentStatusChangeBo bo : boList) {
result &= xgStudentStatusChangeService.insertByBo(bo);
}
return toAjax(result);
}
/**
* 修改学生学籍异动
*/
// @SaCheckPermission("studentMsg:studentStatusChange:edit")
@Log(title = "学生学籍异动", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody XgStudentStatusChangeBo bo) {
return toAjax(xgStudentStatusChangeService.updateByBo(bo));
}
/**
* 删除学生学籍异动
*
* @param ids 主键串
*/
// @SaCheckPermission("studentMsg:studentStatusChange:remove")
@Log(title = "学生学籍异动", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
return toAjax(xgStudentStatusChangeService.deleteWithValidByIds(List.of(ids), true));
}
}
@@ -0,0 +1,86 @@
package org.dromara.xg.domain;
import org.dromara.common.tenant.core.TenantEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
import java.io.Serial;
/**
* 学生学籍异动对象 xg_student_status_change
*
* @author zlwu
* @date 2025-07-23
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("xg_student_status_change")
public class XgStudentStatusChange extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键,雪花ID
*/
@TableId(value = "id")
private Long id;
/**
* 删除标志(0代表存在 1代表删除)
*/
@TableLogic(value = "0", delval = "1")
private Long deleted;
/**
* 学生ID,关联学生主表
*/
private Long studentId;
/**
* 学号
*/
private String studentNo;
/**
* 异动类型: TUI_XUE(退学), XIU_XUE(休学), FU_XUE(复学), BI_YE(毕业)
*/
private String changeType;
/**
* 申请/登记日期
*/
private Date applyDate;
/**
* 生效日期(业务生效时间)
*/
private Date effectiveDate;
/**
* 异动原因
*/
private String reason;
/**
* 经办人ID(登记人)
*/
private Long operatorId;
/**
* 经办人姓名
*/
private String operatorName;
/**
* 来源: manual/import/workflow
*/
private String source;
/**
* 处理状态:0=未处理,1=已处理
*/
private Integer processStatus;
}
@@ -0,0 +1,96 @@
package org.dromara.xg.domain.bo;
import org.dromara.xg.domain.XgStudentStatusChange;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* 学生学籍异动业务对象 xg_student_status_change
*
* @author zlwu
* @date 2025-07-23
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = XgStudentStatusChange.class, reverseConvertGenerate = false)
public class XgStudentStatusChangeBo extends BaseEntity {
/**
* 主键,雪花ID
*/
@NotNull(message = "主键,雪花ID不能为空", groups = { EditGroup.class })
private Long id;
/**
* 学生ID,关联学生主表
*/
@NotNull(message = "学生ID,关联学生主表不能为空", groups = { AddGroup.class, EditGroup.class })
private Long studentId;
/**
* 学号
*/
private String studentNo;
/**
* 异动类型: TUI_XUE(退学), XIU_XUE(休学), FU_XUE(复学), BI_YE(毕业)
*/
@NotBlank(message = "异动类型: TUI_XUE(退学), XIU_XUE(休学), FU_XUE(复学), BI_YE(毕业)不能为空", groups = { AddGroup.class, EditGroup.class })
private String changeType;
/**
* 申请/登记日期
*/
@NotNull(message = "申请/登记日期不能为空", groups = { AddGroup.class, EditGroup.class })
private Date applyDate;
/**
* 生效日期(业务生效时间)
*/
@NotNull(message = "生效日期(业务生效时间)不能为空", groups = { AddGroup.class, EditGroup.class })
private Date effectiveDate;
/**
* 异动原因
*/
@NotBlank(message = "异动原因不能为空", groups = { AddGroup.class, EditGroup.class })
private String reason;
/**
* 经办人ID(登记人)
*/
private Long operatorId;
/**
* 经办人姓名
*/
private String operatorName;
/**
* 来源: manual/import/workflow
*/
private String source;
/**
* 处理状态:0=未处理,1=已处理
*/
private Integer processStatus;
/**
* 创建部门
*/
private Long createDept;
/**
* 备注信息
*/
private String remark;
}
@@ -0,0 +1,151 @@
package org.dromara.xg.domain.vo;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.dromara.xg.domain.XgStudentStatusChange;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 学生学籍异动视图对象 xg_student_status_change
*
* @author zlwu
* @date 2025-07-23
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = XgStudentStatusChange.class)
public class XgStudentStatusChangeVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键,雪花ID
*/
@ExcelProperty(value = "主键,雪花ID", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "雪=花ID")
private Long id;
/**
* 学生ID,关联学生主表
*/
@ExcelProperty(value = "学生ID,关联学生主表")
private Long studentId;
/**
* 学号
*/
@ExcelProperty(value = "学号")
private String studentNo;
/**
* 学生姓名
*/
@ExcelProperty(value = "学生姓名")
private String studentName;
/**
* 班级名称
*/
@ExcelProperty(value = "班级名称")
private String className;
/**
* 专业名称
*/
@ExcelProperty(value = "专业名称")
private String majorName;
/**
* 学院名称
*/
@ExcelProperty(value = "学院名称")
private String collegeName;
/**
* 异动类型: TUI_XUE(退学), XIU_XUE(休学), FU_XUE(复学), BI_YE(毕业)
*/
@ExcelProperty(value = "异动类型")
private String changeType;
/**
* 申请/登记日期
*/
@ExcelProperty(value = "申请/登记日期")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date applyDate;
/**
* 生效日期(业务生效时间)
*/
@ExcelProperty(value = "生效日期")
@JsonFormat(pattern = "yyyy-MM-dd")
private Date effectiveDate;
/**
* 异动原因
*/
@ExcelProperty(value = "异动原因")
private String reason;
/**
* 经办人ID(登记人)
*/
@ExcelProperty(value = "经办人ID")
private Long operatorId;
/**
* 经办人姓名
*/
@ExcelProperty(value = "经办人姓名")
private String operatorName;
/**
* 来源: manual/import/workflow
*/
@ExcelProperty(value = "来源")
private String source;
/**
* 处理状态:0=未处理,1=已处理
*/
@ExcelProperty(value = "处理状态")
private Integer processStatus;
/**
* 创建部门
*/
private Long createDept;
/**
* 创建者
*/
private Long createBy;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 更新者
*/
private Long updateBy;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}
@@ -0,0 +1,35 @@
package org.dromara.xg.mapper;
import org.dromara.xg.domain.XgStudentStatusChange;
import org.dromara.xg.domain.vo.XgStudentStatusChangeVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
/**
* 学生学籍异动Mapper接口
*
* @author zlwu
* @date 2025-07-23
*/
public interface XgStudentStatusChangeMapper extends BaseMapperPlus<XgStudentStatusChange, XgStudentStatusChangeVo> {
/**
* 分页查询学生学籍异动列表(包含学生信息)
*/
Page<XgStudentStatusChangeVo> selectVoPageWithStudentInfo(IPage<XgStudentStatusChange> page, LambdaQueryWrapper<XgStudentStatusChange> lqw);
/**
* 查询学生学籍异动列表(包含学生信息)
*/
List<XgStudentStatusChangeVo> selectVoListWithStudentInfo(LambdaQueryWrapper<XgStudentStatusChange> lqw);
/**
* 根据ID查询学生学籍异动(包含学生信息)
*/
XgStudentStatusChangeVo selectVoByIdWithStudentInfo(Long id);
}
@@ -0,0 +1,69 @@
package org.dromara.xg.service;
import org.dromara.xg.domain.vo.XgStudentStatusChangeVo;
import org.dromara.xg.domain.bo.XgStudentStatusChangeBo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import java.util.Collection;
import java.util.List;
/**
* 学生学籍异动Service接口
*
* @author zlwu
* @date 2025-07-23
*/
public interface IXgStudentStatusChangeService {
/**
* 查询学生学籍异动
*
* @param id 主键
* @return 学生学籍异动
*/
XgStudentStatusChangeVo queryById(Long id);
/**
* 分页查询学生学籍异动列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 学生学籍异动分页列表
*/
TableDataInfo<XgStudentStatusChangeVo> queryPageList(XgStudentStatusChangeBo bo, PageQuery pageQuery);
/**
* 查询符合条件的学生学籍异动列表
*
* @param bo 查询条件
* @return 学生学籍异动列表
*/
List<XgStudentStatusChangeVo> queryList(XgStudentStatusChangeBo bo);
/**
* 新增学生学籍异动
*
* @param bo 学生学籍异动
* @return 是否新增成功
*/
Boolean insertByBo(XgStudentStatusChangeBo bo);
/**
* 修改学生学籍异动
*
* @param bo 学生学籍异动
* @return 是否修改成功
*/
Boolean updateByBo(XgStudentStatusChangeBo bo);
/**
* 校验并批量删除学生学籍异动信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}
@@ -0,0 +1,146 @@
package org.dromara.xg.service.impl;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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.XgStudentStatusChangeBo;
import org.dromara.xg.domain.vo.XgStudentStatusChangeVo;
import org.dromara.xg.domain.XgStudentStatusChange;
import org.dromara.xg.mapper.XgStudentStatusChangeMapper;
import org.dromara.xg.service.IXgStudentStatusChangeService;
import java.util.List;
import java.util.Map;
import java.util.Collection;
/**
* 学生学籍异动Service业务层处理
*
* @author zlwu
* @date 2025-07-23
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class XgStudentStatusChangeServiceImpl implements IXgStudentStatusChangeService {
private final XgStudentStatusChangeMapper baseMapper;
/**
* 查询学生学籍异动
*
* @param id 主键
* @return 学生学籍异动
*/
@Override
public XgStudentStatusChangeVo queryById(Long id) {
return baseMapper.selectVoByIdWithStudentInfo(id);
}
/**
* 分页查询学生学籍异动列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 学生学籍异动分页列表
*/
@Override
public TableDataInfo<XgStudentStatusChangeVo> queryPageList(XgStudentStatusChangeBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<XgStudentStatusChange> lqw = buildQueryWrapper(bo);
Page<XgStudentStatusChange> page = pageQuery.build();
Page<XgStudentStatusChangeVo> result = baseMapper.selectVoPageWithStudentInfo(page, lqw);
return TableDataInfo.build(result);
}
/**
* 查询符合条件的学生学籍异动列表
*
* @param bo 查询条件
* @return 学生学籍异动列表
*/
@Override
public List<XgStudentStatusChangeVo> queryList(XgStudentStatusChangeBo bo) {
LambdaQueryWrapper<XgStudentStatusChange> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoListWithStudentInfo(lqw);
}
private LambdaQueryWrapper<XgStudentStatusChange> buildQueryWrapper(XgStudentStatusChangeBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<XgStudentStatusChange> lqw = Wrappers.lambdaQuery();
lqw.eq(bo.getId() != null, XgStudentStatusChange::getId, bo.getId());
lqw.eq(bo.getStudentId() != null, XgStudentStatusChange::getStudentId, bo.getStudentId());
lqw.like(StringUtils.isNotBlank(bo.getStudentNo()), XgStudentStatusChange::getStudentNo, bo.getStudentNo());
lqw.eq(StringUtils.isNotBlank(bo.getChangeType()), XgStudentStatusChange::getChangeType, bo.getChangeType());
lqw.eq(bo.getEffectiveDate() != null, XgStudentStatusChange::getEffectiveDate, bo.getEffectiveDate());
lqw.like(StringUtils.isNotBlank(bo.getReason()), XgStudentStatusChange::getReason, bo.getReason());
lqw.eq(bo.getOperatorId() != null, XgStudentStatusChange::getOperatorId, bo.getOperatorId());
lqw.like(StringUtils.isNotBlank(bo.getOperatorName()), XgStudentStatusChange::getOperatorName, bo.getOperatorName());
lqw.eq(StringUtils.isNotBlank(bo.getSource()), XgStudentStatusChange::getSource, bo.getSource());
lqw.eq(bo.getProcessStatus() != null, XgStudentStatusChange::getProcessStatus, bo.getProcessStatus());
return lqw;
}
/**
* 新增学生学籍异动
*
* @param bo 学生学籍异动
* @return 是否新增成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean insertByBo(XgStudentStatusChangeBo bo) {
XgStudentStatusChange add = MapstructUtils.convert(bo, XgStudentStatusChange.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
}
return flag;
}
/**
* 修改学生学籍异动
*
* @param bo 学生学籍异动
* @return 是否修改成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateByBo(XgStudentStatusChangeBo bo) {
XgStudentStatusChange update = MapstructUtils.convert(bo, XgStudentStatusChange.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(XgStudentStatusChange entity) {
// TODO 做一些数据校验,如唯一约束
}
/**
* 校验并批量删除学生学籍异动信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
// TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteByIds(ids) > 0;
}
}
@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.xg.mapper.XgStudentStatusChangeMapper">
<resultMap id="XgStudentStatusChangeResult" type="org.dromara.xg.domain.vo.XgStudentStatusChangeVo">
<id property="id" column="id"/>
<result property="studentId" column="student_id"/>
<result property="studentNo" column="student_no"/>
<result property="studentName" column="student_name"/>
<result property="className" column="class_name"/>
<result property="majorName" column="major_name"/>
<result property="collegeName" column="college_name"/>
<result property="changeType" column="change_type"/>
<result property="applyDate" column="apply_date"/>
<result property="effectiveDate" column="effective_date"/>
<result property="reason" column="reason"/>
<result property="operatorId" column="operator_id"/>
<result property="operatorName" column="operator_name"/>
<result property="source" column="source"/>
<result property="processStatus" column="process_status"/>
<result property="createDept" column="create_dept"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="remark" column="remark"/>
</resultMap>
<sql id="selectXgStudentStatusChangeVo">
select c.id, c.student_id, c.student_no,
s.name as student_name, s.class_name as class_name, s.major_name as major_name, s.college_name as college_name,
c.change_type, c.apply_date, c.effective_date,
c.reason, c.operator_id, c.operator_name, c.source, c.process_status,
c.create_dept, c.create_by, c.create_time, c.update_by, c.update_time
from xg_student_status_change c
left join xg_student_msg s on c.student_id = s.id and s.deleted = 0
where c.deleted = 0
</sql>
<select id="selectVoList" resultMap="XgStudentStatusChangeResult">
<include refid="selectXgStudentStatusChangeVo"/>
${ew.getCustomSqlSegment}
</select>
<select id="selectVoById" parameterType="Long" resultMap="XgStudentStatusChangeResult">
<include refid="selectXgStudentStatusChangeVo"/>
and c.id = #{id}
</select>
<select id="selectVoPage" resultMap="XgStudentStatusChangeResult">
<include refid="selectXgStudentStatusChangeVo"/>
${ew.getCustomSqlSegment}
</select>
<!-- 自定义查询方法,包含学生信息 -->
<select id="selectVoPageWithStudentInfo" resultMap="XgStudentStatusChangeResult">
<include refid="selectXgStudentStatusChangeVo"/>
<where>
<if test="lqw != null">
${lqw.getCustomSqlSegment}
</if>
</where>
order by c.create_time desc
</select>
<select id="selectVoListWithStudentInfo" resultMap="XgStudentStatusChangeResult">
<include refid="selectXgStudentStatusChangeVo"/>
<where>
<if test="lqw != null">
${lqw.getCustomSqlSegment}
</if>
</where>
order by c.create_time desc
</select>
<select id="selectVoByIdWithStudentInfo" parameterType="Long" resultMap="XgStudentStatusChangeResult">
<include refid="selectXgStudentStatusChangeVo"/>
and c.id = #{id}
</select>
</mapper>
@@ -0,0 +1,85 @@
/** 学生学籍异动查询参数 */
export interface XgStudentStatusChangeQuery {
/** 学号 */
studentNo?: string;
/** 异动类型 */
changeType?: string;
/** 处理状态 */
processStatus?: number | null;
/** 经办人姓名 */
operatorName?: string;
/** 分页大小 */
pageSize: number;
/** 当前页码 */
pageNum: number;
/** 其他参数 */
params: Record<string, any>;
}
/** 学生学籍异动视图对象 */
export interface XgStudentStatusChangeVo {
/** 主键,雪花ID */
id: string | number;
/** 学生ID,关联学生主表 */
studentId?: string | number;
/** 学号 */
studentNo?: string;
/** 学生姓名 */
studentName?: string;
/** 班级名称 */
className?: string;
/** 专业名称 */
majorName?: string;
/** 学院名称 */
collegeName?: string;
/** 异动类型: TUI_XUE(退学), XIU_XUE(休学), FU_XUE(复学), BI_YE(毕业) */
changeType?: string;
/** 申请/登记日期 */
applyDate?: string;
/** 生效日期(业务生效时间) */
effectiveDate?: string;
/** 异动原因 */
reason?: string;
/** 经办人ID(登记人) */
operatorId?: string | number;
/** 经办人姓名 */
operatorName?: string;
/** 来源: manual/import/workflow */
source?: string;
/** 处理状态:0=未处理,1=已处理 */
processStatus?: number;
/** 创建者 */
createBy?: string | number;
/** 创建时间 */
createTime?: string;
/** 更新者 */
updateBy?: string | number;
/** 更新时间 */
updateTime?: string;
}
/** 学生学籍异动业务对象 */
export interface XgStudentStatusChangeBo {
/** 主键,雪花ID */
id?: string | number;
/** 学生ID,关联学生主表 */
studentId?: string | number;
/** 学号 */
studentNo?: string;
/** 异动类型: TUI_XUE(退学), XIU_XUE(休学), FU_XUE(复学), BI_YE(毕业) */
changeType?: string;
/** 申请/登记日期 */
applyDate?: string;
/** 生效日期(业务生效时间) */
effectiveDate?: string;
/** 异动原因 */
reason?: string;
/** 经办人ID(登记人) */
operatorId?: string | number;
/** 经办人姓名 */
operatorName?: string;
/** 来源: manual/import/workflow */
source?: string;
/** 处理状态:0=未处理,1=已处理 */
processStatus?: number;
}
@@ -115,6 +115,18 @@
<!-- <el-col :span="1.5"> <!-- <el-col :span="1.5">
<el-button :disabled="multiple" icon="Delete" @click="handleDelete()"> 删除 </el-button> <el-button :disabled="multiple" icon="Delete" @click="handleDelete()"> 删除 </el-button>
</el-col> --> </el-col> -->
<el-col :span="1.2">
<el-button type="danger" :disabled="multiple" @click="openStatusChangeModal('TUI_XUE')">退学</el-button>
</el-col>
<el-col :span="1.2">
<el-button type="warning" :disabled="multiple" @click="openStatusChangeModal('XIU_XUE')">休学</el-button>
</el-col>
<el-col :span="1.2">
<el-button type="success" :disabled="multiple" @click="openStatusChangeModal('FU_XUE')">复学</el-button>
</el-col>
<el-col :span="1.2">
<el-button type="primary" :disabled="multiple" @click="openStatusChangeModal('BI_YE')">毕业</el-button>
</el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-dropdown class="mt-[1px]"> <el-dropdown class="mt-[1px]">
<el-button> <el-button>
@@ -1508,6 +1520,69 @@
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
<!-- 学籍异动弹窗 -->
<el-dialog v-model="statusChangeDialog.visible" :title="getStatusChangeDialogTitle()" width="900px" append-to-body>
<el-form ref="statusChangeFormRef" :model="statusChangeForm" :rules="statusChangeRules" label-width="120px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="异动类型" prop="changeType">
<el-select v-model="statusChangeForm.changeType" placeholder="请选择异动类型" style="width: 100%">
<el-option v-for="dict in xg_stu_status" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="生效日期" prop="effectiveDate">
<el-date-picker
v-model="statusChangeForm.effectiveDate"
type="date"
placeholder="请选择生效日期"
value-format="YYYY-MM-DD"
:disabled-date="disabledDate"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="异动原因" prop="reason">
<el-input v-model="statusChangeForm.reason" type="textarea" placeholder="请输入异动原因" :rows="3" maxlength="1000" show-word-limit />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="经办人ID" prop="operatorId">
<el-input v-model="statusChangeForm.operatorId" readonly />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="经办人姓名" prop="operatorName">
<el-input v-model="statusChangeForm.operatorName" readonly />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item :label="`选中的学生 (${statusChangeForm.selectedStudents.length})`">
<div class="selected-students-container">
<div v-for="student in statusChangeForm.selectedStudents" :key="student.id" class="student-item">
<el-tag type="info">{{ student.studentNo }} - {{ student.name }}</el-tag>
</div>
</div>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="statusChangeDialog.visible = false">取消</el-button>
<el-button type="primary" @click="submitStatusChange">确定</el-button>
</div>
</template>
</el-dialog>
</div> </div>
</template> </template>
@@ -1527,11 +1602,13 @@ import { globalHeaders } from '@/utils/request';
// import type { FormInstance, TreeInstance } from 'element-plus'; // import type { FormInstance, TreeInstance } from 'element-plus';
import { Plus, ArrowDown, InfoFilled, Loading } from '@element-plus/icons-vue'; import { Plus, ArrowDown, InfoFilled, Loading } from '@element-plus/icons-vue';
import service from '@/utils/request'; import service from '@/utils/request';
import { useUserStore } from '@/store/modules/user';
interface ComponentInternalInstance { interface ComponentInternalInstance {
proxy?: any; proxy?: any;
} }
const router = useRouter(); const router = useRouter();
const { proxy } = getCurrentInstance() as ComponentInternalInstance; const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const userStore = useUserStore();
const { const {
wenhuachengdu, wenhuachengdu,
xg_political_status, xg_political_status,
@@ -1551,7 +1628,8 @@ const {
xg_training_mode, xg_training_mode,
admission_category, admission_category,
student_status, student_status,
xg_class_status xg_class_status,
xg_stu_status
} = toRefs<any>( } = toRefs<any>(
proxy?.useDict( proxy?.useDict(
'wenhuachengdu', 'wenhuachengdu',
@@ -1572,7 +1650,8 @@ const {
'xg_training_mode', 'xg_training_mode',
'admission_category', 'admission_category',
'student_status', 'student_status',
'xg_class_status' 'xg_class_status',
'xg_stu_status'
) )
); );
@@ -1696,6 +1775,7 @@ const columns = ref([
const deptTreeRef = ref<any>(); const deptTreeRef = ref<any>();
const queryFormRef = ref<any>(); const queryFormRef = ref<any>();
const studentFormRef = ref<any>(); const studentFormRef = ref<any>();
const statusChangeFormRef = ref<any>();
const uploadRef = ref<any>(); const uploadRef = ref<any>();
const initPassword = ref('Hzdyjs123456!@#'); const initPassword = ref('Hzdyjs123456!@#');
@@ -1704,6 +1784,21 @@ const dialog = reactive({
title: '' title: ''
}); });
// 学籍异动弹窗
const statusChangeDialog = reactive({
visible: false
});
// 学籍异动表单
const statusChangeForm = reactive({
changeType: '',
effectiveDate: '',
reason: '',
operatorId: '',
operatorName: '',
selectedStudents: [] as StudentVO[]
});
const detailDialog = reactive({ const detailDialog = reactive({
visible: false, visible: false,
title: '学生详情' title: '学生详情'
@@ -1854,6 +1949,15 @@ const rules = reactive({
gender: [{ required: true, message: '性别不能为空', trigger: 'blur' }] gender: [{ required: true, message: '性别不能为空', trigger: 'blur' }]
}); });
// 学籍异动表单验证规则
const statusChangeRules = reactive({
changeType: [{ required: true, message: '异动类型不能为空', trigger: 'change' }],
effectiveDate: [{ required: true, message: '生效日期不能为空', trigger: 'blur' }],
reason: [{ required: true, message: '异动原因不能为空', trigger: 'blur' }]
});
// 异动类型选项通过字典表获取:xg_stu_status
const queryParams = reactive<StudentQuery>({ const queryParams = reactive<StudentQuery>({
pageNum: 1, pageNum: 1,
pageSize: 10, pageSize: 10,
@@ -2811,6 +2915,81 @@ const handleSelectionChange = (selection: StudentVO[]) => {
multiple.value = !selection.length; multiple.value = !selection.length;
}; };
/** 打开学籍异动弹窗 */
const openStatusChangeModal = (changeType?: string) => {
if (ids.value.length === 0) {
ElMessage.warning('请先选择要进行学籍异动的学生');
return;
}
// 获取选中的学生信息
const selectedStudents = studentList.value.filter((student) => ids.value.includes(student.id));
// 设置表单数据
statusChangeForm.changeType = changeType || '';
statusChangeForm.effectiveDate = proxy?.parseTime(new Date(), '{y}-{m}-{d}');
statusChangeForm.reason = '';
statusChangeForm.operatorId = userStore.userId || '';
statusChangeForm.operatorName = userStore.nickname || '';
statusChangeForm.selectedStudents = selectedStudents;
statusChangeDialog.visible = true;
};
/** 获取学籍异动弹窗标题 */
const getStatusChangeDialogTitle = () => {
const typeMap = {
'TUI_XUE': '退学',
'XIU_XUE': '休学',
'FU_XUE': '复学',
'BI_YE': '毕业'
};
const typeLabel = typeMap[statusChangeForm.changeType as keyof typeof typeMap] || '学籍异动';
return `${typeLabel}申请`;
};
/** 限制日期选择(不能选择过去的日期) */
const disabledDate = (time: Date) => {
return time.getTime() < Date.now() - 24 * 60 * 60 * 1000; // 不能选择昨天及以前的日期
};
/** 提交学籍异动 */
const submitStatusChange = async () => {
const formRef = statusChangeFormRef.value;
if (!formRef) return;
await formRef.validate(async (valid: boolean) => {
if (valid) {
try {
const submitData = statusChangeForm.selectedStudents.map((student) => ({
studentId: student.id,
studentNo: student.studentNo,
changeType: statusChangeForm.changeType,
applyDate: proxy?.parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}'),
effectiveDate: statusChangeForm.effectiveDate,
reason: statusChangeForm.reason,
operatorId: statusChangeForm.operatorId,
operatorName: statusChangeForm.operatorName,
source: 'manual',
processStatus: 0
}));
const res = await service.post('/studentMsg/studentStatusChange/batch', submitData);
if (res && res.code === 200) {
ElMessage.success('学籍异动申请提交成功');
statusChangeDialog.visible = false;
await getList(); // 刷新列表
} else {
ElMessage.error(res?.msg || '提交失败');
}
} catch (error) {
console.error('提交学籍异动失败:', error);
ElMessage.error('提交失败,请重试');
}
}
});
};
/** 导入按钮操作 */ /** 导入按钮操作 */
const handleImport = () => { const handleImport = () => {
if (!showAddButton.value) { if (!showAddButton.value) {
@@ -3580,4 +3759,25 @@ onMounted(() => {
:deep(.batch-cancel-bed-dialog .el-message-box__message strong) { :deep(.batch-cancel-bed-dialog .el-message-box__message strong) {
font-weight: 600; font-weight: 600;
} }
/* 选中学生列表样式 */
.selected-students-container {
max-height: 200px;
overflow-y: auto;
border: 1px solid #e4e7ed;
border-radius: 4px;
padding: 12px;
background-color: #fafafa;
}
.student-item {
margin-bottom: 8px;
display: inline-block;
margin-right: 12px;
}
.student-item:last-child {
margin-bottom: 0;
margin-right: 0;
}
</style> </style>
@@ -0,0 +1,359 @@
<template>
<div class="p-2 page-container">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="学号" prop="studentNo">
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="异动类型" prop="changeType">
<el-select v-model="queryParams.changeType" placeholder="请选择异动类型" clearable>
<el-option v-for="dict in xg_stu_status" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="处理状态" prop="processStatus">
<el-select v-model="queryParams.processStatus" placeholder="请选择处理状态" clearable>
<el-option label="未处理" :value="0" />
<el-option label="已处理" :value="1" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="经办人" prop="operatorName">
<el-input v-model="queryParams.operatorName" placeholder="请输入经办人姓名" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="申请日期">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24" class="search-btn-container">
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple || selectedRows.some(row => row.processStatus === 1)"
@click="handleDelete()"
v-hasPermi="['studentMsg:studentStatusChange:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['studentMsg:studentStatusChange:export']"
>导出</el-button
>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="statusChangeList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="主键ID" align="center" prop="id" v-if="false" />
<el-table-column label="学号" align="center" prop="studentNo" />
<el-table-column label="姓名" align="center" prop="studentName" />
<el-table-column label="班级" align="center" prop="className" />
<el-table-column label="专业" align="center" prop="majorName" />
<el-table-column label="学院" align="center" prop="collegeName" />
<el-table-column label="异动类型" align="center" prop="changeType">
<template #default="scope">
<dict-tag :options="xg_stu_status" :value="scope.row.changeType" />
</template>
</el-table-column>
<el-table-column label="申请日期" align="center" prop="applyDate" width="160">
<template #default="scope">
<span>{{ proxy?.parseTime(scope.row.applyDate, '{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<el-table-column label="生效日期" align="center" prop="effectiveDate" width="120">
<template #default="scope">
<span>{{ proxy?.parseTime(scope.row.effectiveDate, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="异动原因" align="center" prop="reason" width="200" :show-overflow-tooltip="true" />
<el-table-column label="经办人" align="center" prop="operatorName" />
<el-table-column label="来源" align="center" prop="source">
<template #default="scope">
<el-tag v-if="scope.row.source === 'manual'" type="primary">手动</el-tag>
<el-tag v-else-if="scope.row.source === 'import'" type="success">导入</el-tag>
<el-tag v-else-if="scope.row.source === 'workflow'" type="warning">工作流</el-tag>
<el-tag v-else type="info">{{ scope.row.source }}</el-tag>
</template>
</el-table-column>
<el-table-column label="处理状态" align="center" prop="processStatus">
<template #default="scope">
<el-tag v-if="scope.row.processStatus === 0" type="warning">未处理</el-tag>
<el-tag v-else-if="scope.row.processStatus === 1" type="success">已处理</el-tag>
<el-tag v-else type="info">未知</el-tag>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="160">
<template #default="scope">
<span>{{ proxy?.parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="100" fixed="right">
<template #default="scope">
<el-tooltip content="删除" placement="top">
<el-button
link
type="danger"
plain
icon="Delete"
@click="handleDelete(scope.row)"
v-hasPermi="['studentMsg:studentStatusChange:remove']"
:disabled="scope.row.processStatus === 1"
></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
</div>
</template>
<script setup name="StudentStatusChange" lang="ts">
import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue';
import type { FormInstance as ElFormInstance } from 'element-plus';
import { ElMessage, ElMessageBox } from 'element-plus';
import { to } from 'await-to-js';
import service from '@/utils/request';
import { XgStudentStatusChangeVo, XgStudentStatusChangeQuery } from '@/api/xg/xsjcxx/studentStatusChange/types';
interface PageData<T, Q> {
form: T;
queryParams: Q;
rules: Record<string, any[]>;
}
interface ComponentInternalInstance {
proxy?: any;
}
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
// 字典数据
const { xg_stu_status } = toRefs<any>(proxy?.useDict('xg_stu_status'));
// 表格数据
const statusChangeList = ref<XgStudentStatusChangeVo[]>([]);
const loading = ref(true);
const ids = ref<Array<number | string>>([]);
const selectedRows = ref<XgStudentStatusChangeVo[]>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const dateRange = ref<[string, string]>(['', '']);
const showSearch = ref(true);
// 分页参数
const queryParams = reactive<XgStudentStatusChangeQuery>({
pageNum: 1,
pageSize: 10,
studentNo: '',
changeType: '',
processStatus: null,
operatorName: '',
params: {}
});
// 表单引用
const queryFormRef = ref<ElFormInstance>();
/** 查询列表 */
const getList = async () => {
loading.value = true;
try {
// 构造请求参数
const requestBody: any = {
studentNo: queryParams.studentNo || '',
changeType: queryParams.changeType || '',
processStatus: queryParams.processStatus,
operatorName: queryParams.operatorName || ''
};
// 处理时间范围查询
if (dateRange.value && dateRange.value[0] && dateRange.value[1]) {
requestBody.beginTime = dateRange.value[0];
requestBody.endTime = dateRange.value[1];
}
// 构造分页参数
const pageQuery = {
pageNum: queryParams.pageNum,
pageSize: queryParams.pageSize
};
// 调用后端接口
const res = await service.get('/studentMsg/studentStatusChange/list', {
params: { ...requestBody, ...pageQuery }
});
loading.value = false;
if (res && res.code === 200) {
statusChangeList.value = res.rows || [];
total.value = res.total || 0;
} else {
ElMessage.error(res?.msg || '查询失败');
statusChangeList.value = [];
total.value = 0;
}
} catch (error) {
loading.value = false;
console.error('查询失败:', error);
ElMessage.error('查询失败,请重试');
statusChangeList.value = [];
total.value = 0;
}
};
/** 选择条数 */
const handleSelectionChange = (selection: XgStudentStatusChangeVo[]) => {
ids.value = selection.map((item) => item.id);
selectedRows.value = selection;
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
dateRange.value = ['', ''];
proxy?.resetForm(queryFormRef.value);
queryParams.pageNum = 1;
queryParams.studentNo = '';
queryParams.changeType = '';
queryParams.processStatus = null;
queryParams.operatorName = '';
handleQuery();
};
/** 删除按钮操作 */
const handleDelete = async (row?: XgStudentStatusChangeVo) => {
if (row && row.processStatus === 1) {
ElMessage.warning('已处理的记录不能删除');
return;
}
const statusChangeIds = row?.id || ids.value;
const [err] = await to(
ElMessageBox.confirm(`是否确认删除选中的${Array.isArray(statusChangeIds) ? statusChangeIds.length : 1}条数据项?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
);
if (!err) {
try {
const res = await service.delete(`/studentMsg/studentStatusChange/${statusChangeIds}`);
if (res && res.code === 200) {
await getList();
ElMessage.success('删除成功');
} else {
ElMessage.error(res?.msg || '删除失败');
}
} catch (error) {
console.error('删除失败:', error);
ElMessage.error('删除失败,请重试');
}
}
};
/** 导出按钮操作 */
const handleExport = () => {
ElMessage({
message: '正在导出数据,请耐心等待...',
type: 'info',
duration: 0,
zIndex: 4000
});
proxy
?.download(
'studentMsg/studentStatusChange/export',
{
...queryParams,
...proxy?.addDateRange(queryParams, dateRange.value)
},
`student_status_change_${new Date().getTime()}.xlsx`
)
.then(() => {
ElMessage.closeAll();
ElMessage.success('导出成功');
})
.catch((error) => {
ElMessage.closeAll();
if (error.message && error.message.includes('timeout')) {
ElMessage.error('导出超时,请尝试减少导出数据量或联系管理员');
} else {
ElMessage.error('导出失败,请稍后重试');
}
});
};
// 页面初始化
onMounted(() => {
getList();
});
</script>
<style scoped>
.page-container {
padding: 8px;
}
.search-btn-container {
display: flex;
justify-content: flex-end;
margin-top: 10px;
}
.search-btn-container .el-form-item {
margin-bottom: 0 !important;
}
</style>
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,31 @@
-- xg_student_status_change.sql
-- 学生学籍异动表 DDL(退学/休学/复学/毕业)
-- 生成说明:根据用户提供的表模型,已修正小幅语法问题(尾逗号)。如需保持原样请告知。
CREATE TABLE `xg_student_status_change` (
`id` bigint(20) NOT NULL COMMENT '主键,雪花ID(建议使用雪花算法生成)',
`student_id` bigint(20) NOT NULL COMMENT '学生ID,关联学生主表',
`student_no` varchar(100) DEFAULT NULL COMMENT '学号',
`change_type` varchar(20) NOT NULL COMMENT '异动类型: TUI_XUE(退学), XIU_XUE(休学), FU_XUE(复学), BI_YE(毕业)',
`apply_date` datetime NOT NULL COMMENT '申请/登记日期',
`effective_date` datetime DEFAULT NULL COMMENT '生效日期(业务生效时间)',
`reason` varchar(1000) DEFAULT NULL COMMENT '异动原因',
`operator_id` bigint(20) DEFAULT NULL COMMENT '经办人ID(登记人)',
`operator_name` varchar(64) DEFAULT NULL COMMENT '经办人姓名',
`source` varchar(50) DEFAULT 'manual' COMMENT '来源: manual/import/workflow',
`process_status` tinyint(2) NOT NULL DEFAULT 0 COMMENT '处理状态:0=未处理,1=已处理',
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户ID(多租户)',
`deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=正常,1=已删除',
PRIMARY KEY (`id`),
KEY `idx_student_id` (`student_id`),
KEY `idx_change_type` (`change_type`),
KEY `idx_process_status` (`process_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='学生学籍异动表';