zlwu 1 سال پیش
والد
کامیت
0a96ebf80c
14فایلهای تغییر یافته به همراه1831 افزوده شده و 174 حذف شده
  1. 44 14
      backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/controller/XskhDisciplineRevokeController.java
  2. 19 4
      backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/XskhDisciplineRevoke.java
  3. 28 4
      backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/bo/XskhDisciplineRevokeBo.java
  4. 34 9
      backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhDisciplineRevokeVo.java
  5. 53 19
      backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/mapper/XskhDisciplineRevokeMapper.java
  6. 22 5
      backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/IXskhDisciplineRevokeService.java
  7. 79 17
      backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhDisciplineRevokeServiceImpl.java
  8. 113 9
      backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/XskhDisciplineRevokeMapper.xml
  9. 12 5
      docs/设计文档/database/学生考核管理/学生考核管理数据库模型.sql
  10. 41 3
      web/src/api/xskh/disciplineRevoke/disciplineRevoke.ts
  11. 77 16
      web/src/api/xskh/disciplineRevoke/types.ts
  12. 451 54
      web/src/views/xskh/classTeacherDiscipline/RevokeApplicationApproval.vue
  13. 790 0
      web/src/views/xskh/studentAffairsApproval/RevokeApplicationApproval.vue
  14. 68 15
      web/src/views/xskh/xsDisciplineRecord/RevokeApplicationView.vue

+ 44 - 14
backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/controller/XskhDisciplineRevokeController.java

@@ -134,17 +134,6 @@ public class XskhDisciplineRevokeController extends BaseController {
         return toAjax(xskhDisciplineRevokeService.applyRevoke(bo));
     }
 
-    /**
-     * 审核撤销申请
-     */
-    @SaCheckPermission("disciplineRevoke:disciplineRevoke:approve")
-    @Log(title = "审核撤销申请", businessType = BusinessType.UPDATE)
-    @RepeatSubmit()
-    @PutMapping("/approve")
-    public R<Void> approveRevoke(@Validated(EditGroup.class) @RequestBody XskhDisciplineRevokeBo bo) {
-        return toAjax(xskhDisciplineRevokeService.approveRevoke(bo));
-    }
-
     /**
      * 查询班主任班级的违纪撤销申请列表(班主任专用)
      */
@@ -155,15 +144,56 @@ public class XskhDisciplineRevokeController extends BaseController {
         return xskhDisciplineRevokeService.queryClassTeacherPageList(bo, pageQuery);
     }
 
+    /**
+     * 查询班主任已审核的违纪撤销申请列表(根据审核人查询历史记录)
+     */
+    @SaCheckPermission("disciplineRevoke:classTeacher:list")
+    @GetMapping("/classTeacherReviewedList")
+    public TableDataInfo<XskhDisciplineRevokeVo> getClassTeacherReviewedRevokeApplications(XskhDisciplineRevokeBo bo,
+            PageQuery pageQuery) {
+        return xskhDisciplineRevokeService.queryClassTeacherReviewedPageList(bo, pageQuery);
+    }
+
     /**
      * 班主任审核撤销申请
      */
     @SaCheckPermission("disciplineRevoke:classTeacher:approve")
     @Log(title = "班主任审核撤销申请", businessType = BusinessType.UPDATE)
     @RepeatSubmit()
-    @PutMapping("/classTeacherApprove")
-    public R<Void> classTeacherApproveRevoke(@Validated(EditGroup.class) @RequestBody XskhDisciplineRevokeBo bo) {
-        return toAjax(xskhDisciplineRevokeService.classTeacherApproveRevoke(bo));
+    @PutMapping("/teacherApprove")
+    public R<Void> teacherApproveRevoke(@Validated(EditGroup.class) @RequestBody XskhDisciplineRevokeBo bo) {
+        return toAjax(xskhDisciplineRevokeService.teacherApproveRevoke(bo));
+    }
+
+    /**
+     * 查询学工处待审核的撤销申请列表(学工处专用)
+     */
+    @SaCheckPermission("disciplineRevoke:studentAffairs:list")
+    @GetMapping("/studentAffairsList")
+    public TableDataInfo<XskhDisciplineRevokeVo> getStudentAffairsRevokeApplications(XskhDisciplineRevokeBo bo,
+            PageQuery pageQuery) {
+        return xskhDisciplineRevokeService.queryStudentAffairsPageList(bo, pageQuery);
+    }
+
+    /**
+     * 学工处审核撤销申请
+     */
+    @SaCheckPermission("disciplineRevoke:studentAffairs:approve")
+    @Log(title = "学工处审核撤销申请", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping("/studentAffairsApprove")
+    public R<Void> studentAffairsApproveRevoke(@Validated(EditGroup.class) @RequestBody XskhDisciplineRevokeBo bo) {
+        return toAjax(xskhDisciplineRevokeService.studentAffairsApproveRevoke(bo));
+    }
+
+    /**
+     * 查询学工处已审核的撤销申请列表(学工处专用)
+     */
+    @SaCheckPermission("disciplineRevoke:studentAffairs:list")
+    @GetMapping("/studentAffairsReviewedList")
+    public TableDataInfo<XskhDisciplineRevokeVo> getStudentAffairsReviewedRevokeApplications(XskhDisciplineRevokeBo bo,
+            PageQuery pageQuery) {
+        return xskhDisciplineRevokeService.queryStudentAffairsReviewedPageList(bo, pageQuery);
     }
 
 }

+ 19 - 4
backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/XskhDisciplineRevoke.java

@@ -59,22 +59,37 @@ public class XskhDisciplineRevoke extends TenantEntity {
     private Date applyTime;
 
     /**
-     * 状态(1-待审核 2-审核通过 3-审核拒绝)
+     * 状态(1-待班主任审核 2-待学工处审核 3-审核通过 4-班主任审核拒绝 5-学工处审核拒绝)
      */
     private String status;
 
     /**
-     * 审核人ID
+     * 班主任审核人ID
+     */
+    private Long teacherApprovalBy;
+
+    /**
+     * 班主任审核时间
+     */
+    private Date teacherApprovalTime;
+
+    /**
+     * 班主任审核备注
+     */
+    private String teacherApprovalRemark;
+
+    /**
+     * 学工处审核人ID
      */
     private Long approvalBy;
 
     /**
-     * 审核时间
+     * 学工处审核时间
      */
     private Date approvalTime;
 
     /**
-     * 审核备注
+     * 学工处审核备注
      */
     private String approvalRemark;
 

+ 28 - 4
backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/bo/XskhDisciplineRevokeBo.java

@@ -68,24 +68,48 @@ public class XskhDisciplineRevokeBo extends BaseEntity {
     private Date applyTime;
 
     /**
-     * 状态(1-待审核 2-审核通过 3-审核拒绝)
+     * 状态(1-待班主任审核 2-待学工处审核 3-审核通过 4-班主任审核拒绝 5-学工处审核拒绝)
      */
     private String status;
 
     /**
-     * 审核人ID
+     * 班主任审核人ID
+     */
+    private Long teacherApprovalBy;
+
+    /**
+     * 班主任审核时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date teacherApprovalTime;
+
+    /**
+     * 班主任审核备注
+     */
+    private String teacherApprovalRemark;
+
+    /**
+     * 学工处审核人ID
      */
     private Long approvalBy;
 
     /**
-     * 审核时间
+     * 学工处审核时间
      */
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date approvalTime;
 
     /**
-     * 审核备注
+     * 学工处审核备注
      */
     private String approvalRemark;
 
+    // VO类基础字段规范(避免MyBatis映射错误)
+    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; // 更新时间
+
 }

+ 34 - 9
backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhDisciplineRevokeVo.java

@@ -90,35 +90,60 @@ public class XskhDisciplineRevokeVo implements Serializable {
     private Date applyTime;
 
     /**
-     * 状态(1-待审核 2-审核通过 3-审核拒绝)
+     * 状态(1-待班主任审核 2-待学工处审核 3-审核通过 4-班主任审核拒绝 5-学工处审核拒绝)
      */
     @ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
     @ExcelDictFormat(dictType = "student_revoke_status")
     private String status;
 
     /**
-     * 审核人ID
+     * 班主任审核人ID
      */
-    @ExcelProperty(value = "审核人ID")
+    @ExcelProperty(value = "班主任审核人ID")
+    private Long teacherApprovalBy;
+
+    /**
+     * 班主任审核人姓名(关联查询)
+     */
+    @ExcelProperty(value = "班主任审核人姓名")
+    private String teacherApprovalByName;
+
+    /**
+     * 班主任审核时间
+     */
+    @ExcelProperty(value = "班主任审核时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date teacherApprovalTime;
+
+    /**
+     * 班主任审核备注
+     */
+    @ExcelProperty(value = "班主任审核备注")
+    private String teacherApprovalRemark;
+
+    /**
+     * 学工处审核人ID
+     */
+    @ExcelProperty(value = "学工处审核人ID")
     private Long approvalBy;
 
     /**
-     * 审核人姓名(关联查询)
+     * 学工处审核人姓名(关联查询)
      */
-    @ExcelProperty(value = "审核人姓名")
+    @ExcelProperty(value = "学工处审核人姓名")
     private String approvalByName;
 
     /**
-     * 审核时间
+     * 学工处审核时间
      */
-    @ExcelProperty(value = "审核时间")
+    @ExcelProperty(value = "学工处审核时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date approvalTime;
 
     /**
-     * 审核备注
+     * 学工处审核备注
      */
-    @ExcelProperty(value = "审核备注")
+    @ExcelProperty(value = "学工处审核备注")
     private String approvalRemark;
 
     /**

+ 53 - 19
backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/mapper/XskhDisciplineRevokeMapper.java

@@ -15,24 +15,58 @@ import org.dromara.xskh.domain.vo.XskhDisciplineRevokeVo;
  */
 public interface XskhDisciplineRevokeMapper extends BaseMapperPlus<XskhDisciplineRevoke, XskhDisciplineRevokeVo> {
 
-    /**
-     * 根据违纪记录ID查询撤销申请记录
-     *
-     * @param disciplineRecordId 违纪记录ID
-     * @return 撤销申请记录
-     */
-    XskhDisciplineRevokeVo selectByDisciplineRecordId(Long disciplineRecordId);
-
-    /**
-     * 查询班主任班级的违纪撤销申请列表(班主任专用)
-     *
-     * @param page           分页参数
-     * @param bo             查询条件
-     * @param classTeacherId 班主任ID
-     * @return 分页查询结果
-     */
-    Page<XskhDisciplineRevokeVo> selectClassTeacherVoPage(@Param("page") Page<XskhDisciplineRevokeVo> page,
-            @Param("bo") XskhDisciplineRevokeBo bo,
-            @Param("classTeacherId") Long classTeacherId);
+        /**
+         * 根据违纪记录ID查询撤销申请记录
+         *
+         * @param disciplineRecordId 违纪记录ID
+         * @return 撤销申请记录
+         */
+        XskhDisciplineRevokeVo selectByDisciplineRecordId(Long disciplineRecordId);
+
+        /**
+         * 查询班主任班级的违纪撤销申请列表(班主任专用)
+         *
+         * @param page           分页参数
+         * @param bo             查询条件
+         * @param classTeacherId 班主任ID
+         * @return 分页查询结果
+         */
+        Page<XskhDisciplineRevokeVo> selectClassTeacherVoPage(@Param("page") Page<XskhDisciplineRevokeVo> page,
+                        @Param("bo") XskhDisciplineRevokeBo bo,
+                        @Param("classTeacherId") Long classTeacherId);
+
+        /**
+         * 查询学工处待审核的违纪撤销申请列表(学工处专用)
+         *
+         * @param page 分页参数
+         * @param bo   查询条件
+         * @return 分页查询结果
+         */
+        Page<XskhDisciplineRevokeVo> selectStudentAffairsVoPage(@Param("page") Page<XskhDisciplineRevokeVo> page,
+                        @Param("bo") XskhDisciplineRevokeBo bo);
+
+        /**
+         * 查询学工处已审核的违纪撤销申请列表(学工处专用)
+         *
+         * @param page 分页参数
+         * @param bo   查询条件
+         * @return 分页查询结果
+         */
+        Page<XskhDisciplineRevokeVo> selectStudentAffairsReviewedVoPage(
+                        @Param("page") Page<XskhDisciplineRevokeVo> page,
+                        @Param("bo") XskhDisciplineRevokeBo bo);
+
+        /**
+         * 查询班主任已审核的违纪撤销申请列表(根据审核人查询历史记录)
+         *
+         * @param page           分页参数
+         * @param bo             查询条件
+         * @param classTeacherId 班主任ID
+         * @return 分页查询结果
+         */
+        Page<XskhDisciplineRevokeVo> selectClassTeacherReviewedVoPage(
+                        @Param("page") Page<XskhDisciplineRevokeVo> page,
+                        @Param("bo") XskhDisciplineRevokeBo bo,
+                        @Param("classTeacherId") Long classTeacherId);
 
 }

+ 22 - 5
backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/IXskhDisciplineRevokeService.java

@@ -62,18 +62,35 @@ public interface IXskhDisciplineRevokeService {
     Boolean applyRevoke(XskhDisciplineRevokeBo bo);
 
     /**
-     * 审核撤销申请
+     * 查询班主任班级的违纪撤销申请列表(班主任专用)
      */
-    Boolean approveRevoke(XskhDisciplineRevokeBo bo);
+    TableDataInfo<XskhDisciplineRevokeVo> queryClassTeacherPageList(XskhDisciplineRevokeBo bo, PageQuery pageQuery);
 
     /**
-     * 查询班主任班级的违纪撤销申请列表(班主任专用)
+     * 查询班主任已审核的违纪撤销申请列表(根据审核人查询历史记录)
      */
-    TableDataInfo<XskhDisciplineRevokeVo> queryClassTeacherPageList(XskhDisciplineRevokeBo bo, PageQuery pageQuery);
+    TableDataInfo<XskhDisciplineRevokeVo> queryClassTeacherReviewedPageList(XskhDisciplineRevokeBo bo,
+            PageQuery pageQuery);
 
     /**
      * 班主任审核撤销申请
      */
-    Boolean classTeacherApproveRevoke(XskhDisciplineRevokeBo bo);
+    Boolean teacherApproveRevoke(XskhDisciplineRevokeBo bo);
+
+    /**
+     * 查询学工处待审核的撤销申请列表(学工处专用)
+     */
+    TableDataInfo<XskhDisciplineRevokeVo> queryStudentAffairsPageList(XskhDisciplineRevokeBo bo, PageQuery pageQuery);
+
+    /**
+     * 学工处审核撤销申请
+     */
+    Boolean studentAffairsApproveRevoke(XskhDisciplineRevokeBo bo);
+
+    /**
+     * 查询学工处已审核的撤销申请列表(学工处专用)
+     */
+    TableDataInfo<XskhDisciplineRevokeVo> queryStudentAffairsReviewedPageList(XskhDisciplineRevokeBo bo,
+            PageQuery pageQuery);
 
 }

+ 79 - 17
backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhDisciplineRevokeServiceImpl.java

@@ -21,6 +21,7 @@ import org.springframework.stereotype.Service;
 
 import java.util.Collection;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -197,46 +198,53 @@ public class XskhDisciplineRevokeServiceImpl implements IXskhDisciplineRevokeSer
         bo.setStudentId(currentUserId);
         bo.setStudentName(disciplineRecord.getStudentName());
         bo.setDisciplineRecordId(bo.getDisciplineRecordId());
-        bo.setStatus("1"); // 待审核
+        bo.setStatus("1"); // 待班主任审核
         bo.setApplyTime(new Date());
 
         return insertByBo(bo);
     }
 
     /**
-     * 审核撤销申请
+     * 班主任审核撤销申请
      */
     @Override
-    public Boolean approveRevoke(XskhDisciplineRevokeBo bo) {
-        // 获取当前登录用户ID作为审核人
+    public Boolean teacherApproveRevoke(XskhDisciplineRevokeBo bo) {
+        // 获取当前登录用户ID作为班主任审核人
         Long currentUserId = LoginHelper.getUserId();
 
-        bo.setApprovalBy(currentUserId);
-        bo.setApprovalTime(new Date());
+        bo.setTeacherApprovalBy(currentUserId);
+        bo.setTeacherApprovalTime(new Date());
+
+        // 根据审核结果更新状态
+        if ("2".equals(bo.getStatus())) {
+            // 班主任审核通过,状态改为待学工处审核
+        } else if ("4".equals(bo.getStatus())) {
+            // 班主任审核拒绝,流程结束
+        }
 
         return updateByBo(bo);
     }
 
     /**
-     * 查询班主任班级的违纪撤销申请列表(班主任专用)
+     * 查询学工处待审核的撤销申请列表(学工处专用)
      */
     @Override
-    public TableDataInfo<XskhDisciplineRevokeVo> queryClassTeacherPageList(XskhDisciplineRevokeBo bo,
+    public TableDataInfo<XskhDisciplineRevokeVo> queryStudentAffairsPageList(XskhDisciplineRevokeBo bo,
             PageQuery pageQuery) {
-        // 获取当前登录用户ID(班主任ID)
-        Long currentUserId = LoginHelper.getUserId();
+        // 只查询状态为2(待学工处审核)的申请
+        bo.setStatus("2");
 
-        // 调用mapper中的专用查询方法
-        Page<XskhDisciplineRevokeVo> result = baseMapper.selectClassTeacherVoPage(pageQuery.build(), bo, currentUserId);
+        // 调用mapper中的学工处专用查询方法
+        Page<XskhDisciplineRevokeVo> result = baseMapper.selectStudentAffairsVoPage(pageQuery.build(), bo);
         return TableDataInfo.build(result);
     }
 
     /**
-     * 班主任审核撤销申请
+     * 学工处审核撤销申请
      */
     @Override
-    public Boolean classTeacherApproveRevoke(XskhDisciplineRevokeBo bo) {
-        // 获取当前登录用户ID作为审核人
+    public Boolean studentAffairsApproveRevoke(XskhDisciplineRevokeBo bo) {
+        // 获取当前登录用户ID作为学工处审核人
         Long currentUserId = LoginHelper.getUserId();
 
         bo.setApprovalBy(currentUserId);
@@ -245,8 +253,8 @@ public class XskhDisciplineRevokeServiceImpl implements IXskhDisciplineRevokeSer
         // 更新撤销申请状态
         Boolean updateResult = updateByBo(bo);
 
-        // 如果撤销申请审批通过(状态为2),则同步更新违纪记录状态为已撤销
-        if (updateResult && "2".equals(bo.getStatus())) {
+        // 如果学工处审核通过(状态为3),则同步更新违纪记录状态为已撤销
+        if (updateResult && "3".equals(bo.getStatus())) {
             // 根据撤销申请ID查询完整信息以获取违纪记录ID
             XskhDisciplineRevokeVo revokeInfo = queryById(bo.getId());
             if (revokeInfo != null && revokeInfo.getDisciplineRecordId() != null) {
@@ -258,4 +266,58 @@ public class XskhDisciplineRevokeServiceImpl implements IXskhDisciplineRevokeSer
         return updateResult;
     }
 
+    /**
+     * 查询班主任班级的违纪撤销申请列表(班主任专用)
+     */
+    @Override
+    public TableDataInfo<XskhDisciplineRevokeVo> queryClassTeacherPageList(XskhDisciplineRevokeBo bo,
+            PageQuery pageQuery) {
+        // 获取当前登录用户ID(班主任ID)
+        Long currentUserId = LoginHelper.getUserId();
+
+        // 只查询状态为1(待班主任审核)的申请
+        bo.setStatus("1");
+
+        // 调用mapper中的专用查询方法
+        Page<XskhDisciplineRevokeVo> result = baseMapper.selectClassTeacherVoPage(pageQuery.build(), bo, currentUserId);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询班主任已审核的违纪撤销申请列表(根据审核人查询历史记录)
+     */
+    @Override
+    public TableDataInfo<XskhDisciplineRevokeVo> queryClassTeacherReviewedPageList(XskhDisciplineRevokeBo bo,
+            PageQuery pageQuery) {
+        // 获取当前登录用户ID(班主任ID)
+        Long currentUserId = LoginHelper.getUserId();
+
+        // 调用mapper中的专用查询方法,通过teacherApprovalBy字段查询历史记录
+        Page<XskhDisciplineRevokeVo> result = baseMapper.selectClassTeacherReviewedVoPage(pageQuery.build(), bo,
+                currentUserId);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询学工处已审核的撤销申请列表(学工处专用)
+     */
+    @Override
+    public TableDataInfo<XskhDisciplineRevokeVo> queryStudentAffairsReviewedPageList(XskhDisciplineRevokeBo bo,
+            PageQuery pageQuery) {
+        // 如果没有指定状态,则查询所有已审核状态(3-审核通过,5-审核拒绝)
+        if (StringUtils.isBlank(bo.getStatus())) {
+            // 通过params传递状态列表
+            if (bo.getParams() == null) {
+                bo.setParams(new HashMap<>());
+            }
+            if (!bo.getParams().containsKey("statusList")) {
+                bo.getParams().put("statusList", "3,5");
+            }
+        }
+
+        // 调用mapper中的学工处已审核查询方法
+        Page<XskhDisciplineRevokeVo> result = baseMapper.selectStudentAffairsReviewedVoPage(pageQuery.build(), bo);
+        return TableDataInfo.build(result);
+    }
+
 }

+ 113 - 9
backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/XskhDisciplineRevokeMapper.xml

@@ -13,6 +13,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="revokeEvidence" column="revoke_evidence" />
         <result property="applyTime" column="apply_time" />
         <result property="status" column="status" />
+        <result property="teacherApprovalBy" column="teacher_approval_by" />
+        <result property="teacherApprovalTime" column="teacher_approval_time" />
+        <result property="teacherApprovalRemark" column="teacher_approval_remark" />
         <result property="approvalBy" column="approval_by" />
         <result property="approvalTime" column="approval_time" />
         <result property="approvalRemark" column="approval_remark" />
@@ -26,17 +29,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </resultMap>
 
     <sql id="selectXskhDisciplineRevokeVo">
-        select id, discipline_record_id, student_id, student_name, revoke_reason, revoke_evidence, apply_time, status, approval_by, approval_time, approval_remark, create_by, create_time, create_dept, update_by, update_time, deleted, tenant_id from xskh_discipline_revoke
+        select id, discipline_record_id, student_id, student_name, revoke_reason, revoke_evidence, apply_time, status,
+               teacher_approval_by, teacher_approval_time, teacher_approval_remark,
+               approval_by, approval_time, approval_remark,
+               create_by, create_time, create_dept, update_by, update_time, deleted, tenant_id
+        from xskh_discipline_revoke
     </sql>
 
     <select id="selectByDisciplineRecordId" parameterType="Long" resultType="XskhDisciplineRevokeVo">
         select dr.id, dr.discipline_record_id, dr.student_id, dr.student_name, dr.revoke_reason,
-               dr.revoke_evidence, dr.apply_time, dr.status, dr.approval_by, dr.approval_time,
-               dr.approval_remark, dr.create_by, dr.create_time, dr.create_dept,
+               dr.revoke_evidence, dr.apply_time, dr.status,
+               dr.teacher_approval_by, dr.teacher_approval_time, dr.teacher_approval_remark,
+               dr.approval_by, dr.approval_time, dr.approval_remark,
+               dr.create_by, dr.create_time, dr.create_dept,
                dr.update_by, dr.update_time, dr.deleted, dr.tenant_id,
-               u.nick_name as approval_by_name
+               tu.nick_name as teacher_approval_by_name,
+               au.nick_name as approval_by_name
         from xskh_discipline_revoke dr
-        left join sys_user u on dr.approval_by = u.user_id and u.del_flag = '0' and u.tenant_id = '000000'
+        left join sys_user tu on dr.teacher_approval_by = tu.user_id and tu.del_flag = '0' and tu.tenant_id = '000000'
+        left join sys_user au on dr.approval_by = au.user_id and au.del_flag = '0' and au.tenant_id = '000000'
         where dr.discipline_record_id = #{disciplineRecordId}
         and dr.deleted = 0
         limit 1
@@ -44,14 +55,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
     <select id="selectClassTeacherVoPage" resultType="XskhDisciplineRevokeVo">
         select dr.id, dr.discipline_record_id, dr.student_id, dr.student_name, dr.revoke_reason,
-               dr.revoke_evidence, dr.apply_time, dr.status, dr.approval_by, dr.approval_time,
-               dr.approval_remark, dr.create_by, dr.create_time, dr.create_dept,
+               dr.revoke_evidence, dr.apply_time, dr.status,
+               dr.teacher_approval_by, dr.teacher_approval_time, dr.teacher_approval_remark,
+               dr.approval_by, dr.approval_time, dr.approval_remark,
+               dr.create_by, dr.create_time, dr.create_dept,
                dr.update_by, dr.update_time, dr.deleted, dr.tenant_id,
                xdr.student_no, xdr.discipline_type_name, xdr.discipline_date,
-               u.nick_name as approval_by_name
+               tu.nick_name as teacher_approval_by_name,
+               au.nick_name as approval_by_name
         from xskh_discipline_revoke dr
         inner join xskh_discipline_record xdr on dr.discipline_record_id = xdr.id
-        left join sys_user u on dr.approval_by = u.user_id and u.del_flag = '0' and u.tenant_id = '000000'
+        left join sys_user tu on dr.teacher_approval_by = tu.user_id and tu.del_flag = '0' and tu.tenant_id = '000000'
+        left join sys_user au on dr.approval_by = au.user_id and au.del_flag = '0' and au.tenant_id = '000000'
         where dr.deleted = 0
         and dr.student_id in (
             select sm.id
@@ -75,4 +90,93 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         order by dr.apply_time desc
     </select>
 
+    <select id="selectStudentAffairsVoPage" resultType="XskhDisciplineRevokeVo">
+        select dr.id, dr.discipline_record_id, dr.student_id, dr.student_name, dr.revoke_reason,
+               dr.revoke_evidence, dr.apply_time, dr.status,
+               dr.teacher_approval_by, dr.teacher_approval_time, dr.teacher_approval_remark,
+               dr.approval_by, dr.approval_time, dr.approval_remark,
+               dr.create_by, dr.create_time, dr.create_dept,
+               dr.update_by, dr.update_time, dr.deleted, dr.tenant_id,
+               xdr.student_no, xdr.discipline_type_name, xdr.discipline_date,
+               tu.nick_name as teacher_approval_by_name,
+               au.nick_name as approval_by_name
+        from xskh_discipline_revoke dr
+        inner join xskh_discipline_record xdr on dr.discipline_record_id = xdr.id
+        left join sys_user tu on dr.teacher_approval_by = tu.user_id and tu.del_flag = '0' and tu.tenant_id = '000000'
+        left join sys_user au on dr.approval_by = au.user_id and au.del_flag = '0' and au.tenant_id = '000000'
+        where dr.deleted = 0
+        <if test="bo.studentNo != null and bo.studentNo != ''">
+            and xdr.student_no like concat('%', #{bo.studentNo}, '%')
+        </if>
+        <if test="bo.studentName != null and bo.studentName != ''">
+            and dr.student_name like concat('%', #{bo.studentName}, '%')
+        </if>
+        <if test="bo.status != null and bo.status != ''">
+            and dr.status = #{bo.status}
+        </if>
+        order by dr.apply_time desc
+    </select>
+
+    <select id="selectStudentAffairsReviewedVoPage" resultType="XskhDisciplineRevokeVo">
+        select dr.id, dr.discipline_record_id, dr.student_id, dr.student_name, dr.revoke_reason,
+               dr.revoke_evidence, dr.apply_time, dr.status,
+               dr.teacher_approval_by, dr.teacher_approval_time, dr.teacher_approval_remark,
+               dr.approval_by, dr.approval_time, dr.approval_remark,
+               dr.create_by, dr.create_time, dr.create_dept,
+               dr.update_by, dr.update_time, dr.deleted, dr.tenant_id,
+               xdr.student_no, xdr.discipline_type_name, xdr.discipline_date,
+               tu.nick_name as teacher_approval_by_name,
+               au.nick_name as approval_by_name
+        from xskh_discipline_revoke dr
+        inner join xskh_discipline_record xdr on dr.discipline_record_id = xdr.id
+        left join sys_user tu on dr.teacher_approval_by = tu.user_id and tu.del_flag = '0' and tu.tenant_id = '000000'
+        left join sys_user au on dr.approval_by = au.user_id and au.del_flag = '0' and au.tenant_id = '000000'
+        where dr.deleted = 0
+        <if test="bo.studentNo != null and bo.studentNo != ''">
+            and xdr.student_no like concat('%', #{bo.studentNo}, '%')
+        </if>
+        <if test="bo.studentName != null and bo.studentName != ''">
+            and dr.student_name like concat('%', #{bo.studentName}, '%')
+        </if>
+        <if test="bo.status != null and bo.status != ''">
+            and dr.status = #{bo.status}
+        </if>
+        <if test="bo.params.statusList != null and bo.params.statusList != ''">
+            and dr.status in
+            <foreach collection="bo.params.statusList.split(',')" item="status" open="(" close=")" separator=",">
+                #{status}
+            </foreach>
+        </if>
+        order by dr.approval_time desc, dr.apply_time desc
+    </select>
+
+    <select id="selectClassTeacherReviewedVoPage" resultType="XskhDisciplineRevokeVo">
+        select dr.id, dr.discipline_record_id, dr.student_id, dr.student_name, dr.revoke_reason,
+               dr.revoke_evidence, dr.apply_time, dr.status,
+               dr.teacher_approval_by, dr.teacher_approval_time, dr.teacher_approval_remark,
+               dr.approval_by, dr.approval_time, dr.approval_remark,
+               dr.create_by, dr.create_time, dr.create_dept,
+               dr.update_by, dr.update_time, dr.deleted, dr.tenant_id,
+               xdr.student_no, xdr.discipline_type_name, xdr.discipline_date,
+               tu.nick_name as teacher_approval_by_name,
+               au.nick_name as approval_by_name
+        from xskh_discipline_revoke dr
+        inner join xskh_discipline_record xdr on dr.discipline_record_id = xdr.id
+        left join sys_user tu on dr.teacher_approval_by = tu.user_id and tu.del_flag = '0' and tu.tenant_id = '000000'
+        left join sys_user au on dr.approval_by = au.user_id and au.del_flag = '0' and au.tenant_id = '000000'
+        where dr.deleted = 0
+        and dr.teacher_approval_by = #{classTeacherId}
+        and dr.teacher_approval_time is not null
+        <if test="bo.studentNo != null and bo.studentNo != ''">
+            and xdr.student_no like concat('%', #{bo.studentNo}, '%')
+        </if>
+        <if test="bo.studentName != null and bo.studentName != ''">
+            and dr.student_name like concat('%', #{bo.studentName}, '%')
+        </if>
+        <if test="bo.status != null and bo.status != ''">
+            and dr.status = #{bo.status}
+        </if>
+        order by dr.teacher_approval_time desc, dr.apply_time desc
+    </select>
+
 </mapper>

+ 12 - 5
docs/设计文档/database/学生考核管理/学生考核管理数据库模型.sql

@@ -112,10 +112,15 @@ CREATE TABLE `xskh_discipline_revoke` (
   `revoke_reason` varchar(500) NOT NULL COMMENT '撤销申请原因',
   `revoke_evidence` varchar(500) DEFAULT NULL COMMENT '撤销证明材料,附件的oss_id,多个用逗号分隔',
   `apply_time` datetime NOT NULL COMMENT '申请时间',
-  `status` char(1) DEFAULT '1' COMMENT '状态(1-待审核 2-审核通过 3-审核拒绝)',
-  `approval_by` bigint(20) DEFAULT NULL COMMENT '审核人ID',
-  `approval_time` datetime DEFAULT NULL COMMENT '审核时间',
-  `approval_remark` varchar(500) DEFAULT NULL COMMENT '审核备注',
+  `status` char(1) DEFAULT '1' COMMENT '状态(1-待班主任审核 2-待学工处审核 3-审核通过 4-班主任审核拒绝 5-学工处审核拒绝)',
+  -- 班主任审核信息
+  `teacher_approval_by` bigint(20) DEFAULT NULL COMMENT '班主任审核人ID',
+  `teacher_approval_time` datetime DEFAULT NULL COMMENT '班主任审核时间',
+  `teacher_approval_remark` varchar(500) DEFAULT NULL COMMENT '班主任审核备注',
+  -- 学工处审核信息(保留原有字段名,语义调整为学工处审核)
+  `approval_by` bigint(20) DEFAULT NULL COMMENT '学工处审核人ID',
+  `approval_time` datetime DEFAULT NULL COMMENT '学工处审核时间',
+  `approval_remark` varchar(500) DEFAULT NULL COMMENT '学工处审核备注',
   `create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
   `create_time` datetime DEFAULT NULL COMMENT '创建时间',
   `create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
@@ -126,7 +131,9 @@ CREATE TABLE `xskh_discipline_revoke` (
   PRIMARY KEY (`id`),
   KEY `idx_discipline_record_id` (`discipline_record_id`),
   KEY `idx_student_id` (`student_id`),
-  KEY `idx_status` (`status`)
+  KEY `idx_status` (`status`),
+  KEY `idx_teacher_approval_by` (`teacher_approval_by`),
+  KEY `idx_approval_by` (`approval_by`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='违纪处分撤销申请审批表';
 
 -- 4. 奖励类型表

+ 41 - 3
web/src/api/xskh/disciplineRevoke/disciplineRevoke.ts

@@ -98,12 +98,50 @@ export const applyDisciplineRevoke = (data: DisciplineRevokeForm) => {
 };
 
 /**
- * 审核撤销申请
+ * 查询班主任待审核的撤销申请列表
+ * @param query
+ * @returns {*}
+ */
+export const listClassTeacherDisciplineRevoke = (query?: DisciplineRevokeQuery): AxiosPromise<DisciplineRevokeVO[]> => {
+  return request({
+    url: '/disciplineRevoke/disciplineRevoke/classTeacherList',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 班主任审核撤销申请
+ * @param data
+ */
+export const teacherApproveDisciplineRevoke = (data: DisciplineRevokeForm) => {
+  return request({
+    url: '/disciplineRevoke/disciplineRevoke/teacherApprove',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 查询学工处待审核的撤销申请列表
+ * @param query
+ * @returns {*}
+ */
+export const listStudentAffairsDisciplineRevoke = (query?: DisciplineRevokeQuery): AxiosPromise<DisciplineRevokeVO[]> => {
+  return request({
+    url: '/disciplineRevoke/disciplineRevoke/studentAffairsList',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 学工处审核撤销申请
  * @param data
  */
-export const approveDisciplineRevoke = (data: DisciplineRevokeForm) => {
+export const studentAffairsApproveDisciplineRevoke = (data: DisciplineRevokeForm) => {
   return request({
-    url: '/disciplineRevoke/disciplineRevoke/approve',
+    url: '/disciplineRevoke/disciplineRevoke/studentAffairsApprove',
     method: 'put',
     data: data
   });

+ 77 - 16
web/src/api/xskh/disciplineRevoke/types.ts

@@ -37,27 +37,47 @@ export interface DisciplineRevokeVO {
   applyTime: string;
 
   /**
-   * 状态(1-待审核 2-审核通过 3-审核拒绝)
+   * 状态(1-待班主任审核 2-待学工处审核 3-审核通过 4-班主任审核拒绝 5-学工处审核拒绝)
    */
   status: string;
 
   /**
-   * 审核人ID
+   * 班主任审核人ID
+   */
+  teacherApprovalBy: string | number;
+
+  /**
+   * 班主任审核人姓名
+   */
+  teacherApprovalByName: string;
+
+  /**
+   * 班主任审核时间
+   */
+  teacherApprovalTime: string;
+
+  /**
+   * 班主任审核备注
+   */
+  teacherApprovalRemark: string;
+
+  /**
+   * 学工处审核人ID
    */
   approvalBy: string | number;
 
   /**
-   * 审核人姓名
+   * 学工处审核人姓名
    */
   approvalByName: string;
 
   /**
-   * 审核时间
+   * 学工处审核时间
    */
   approvalTime: string;
 
   /**
-   * 审核备注
+   * 学工处审核备注
    */
   approvalRemark: string;
 
@@ -80,6 +100,22 @@ export interface DisciplineRevokeVO {
    * 更新时间
    */
   updateTime: string;
+
+  // 扩展字段(用于关联查询显示)
+  /**
+   * 学号
+   */
+  studentNo?: string;
+
+  /**
+   * 违纪类型名称
+   */
+  disciplineTypeName?: string;
+
+  /**
+   * 违纪日期
+   */
+  disciplineDate?: string;
 }
 
 export interface DisciplineRevokeForm {
@@ -119,22 +155,37 @@ export interface DisciplineRevokeForm {
   applyTime?: string;
 
   /**
-   * 状态(1-待审核 2-审核通过 3-审核拒绝)
+   * 状态(1-待班主任审核 2-待学工处审核 3-审核通过 4-班主任审核拒绝 5-学工处审核拒绝)
    */
   status?: string;
 
   /**
-   * 审核人ID
+   * 班主任审核人ID
+   */
+  teacherApprovalBy?: string | number;
+
+  /**
+   * 班主任审核时间
+   */
+  teacherApprovalTime?: string;
+
+  /**
+   * 班主任审核备注
+   */
+  teacherApprovalRemark?: string;
+
+  /**
+   * 学工处审核人ID
    */
   approvalBy?: string | number;
 
   /**
-   * 审核时间
+   * 学工处审核时间
    */
   approvalTime?: string;
 
   /**
-   * 审核备注
+   * 学工处审核备注
    */
   approvalRemark?: string;
 }
@@ -155,6 +206,11 @@ export interface DisciplineRevokeQuery extends PageQuery {
    */
   studentName?: string;
 
+  /**
+   * 学号
+   */
+  studentNo?: string;
+
   /**
    * 撤销申请原因
    */
@@ -166,24 +222,29 @@ export interface DisciplineRevokeQuery extends PageQuery {
   applyTime?: string;
 
   /**
-   * 状态(1-待审核 2-审核通过 3-审核拒绝)
+   * 状态(1-待班主任审核 2-待学工处审核 3-审核通过 4-班主任审核拒绝 5-学工处审核拒绝)
    */
   status?: string;
 
   /**
-   * 审核人ID
+   * 班主任审核人ID
    */
-  approvalBy?: string | number;
+  teacherApprovalBy?: string | number;
 
   /**
-   * 审核时间
+   * 班主任审核时间
    */
-  approvalTime?: string;
+  teacherApprovalTime?: string;
 
   /**
-   * 审核备注
+   * 学工处审核人ID
    */
-  approvalRemark?: string;
+  approvalBy?: string | number;
+
+  /**
+   * 学工处审核时间
+   */
+  approvalTime?: string;
 
   /**
    * 其他参数

+ 451 - 54
web/src/views/xskh/classTeacherDiscipline/RevokeApplicationApproval.vue

@@ -33,56 +33,94 @@
       <template #header>
         <el-row :gutter="10" class="mb8">
           <el-col :span="1.5">
-            <span class="text-lg font-medium">违纪撤销申请审核列表</span>
+            <span class="text-lg font-medium">违纪撤销申请管理</span>
           </el-col>
           <right-toolbar :showSearch="showSearch" @update:showSearch="showSearch = $event" @queryTable="getList"></right-toolbar>
         </el-row>
       </template>
 
-      <el-table v-loading="loading" border :data="revokeApplicationList" class="full-width-table">
-        <el-table-column label="学号" align="center" prop="studentNo" min-width="120" />
-        <el-table-column label="学生姓名" align="center" prop="studentName" min-width="100" />
-        <el-table-column label="违纪类型" align="center" prop="disciplineTypeName" min-width="140" show-overflow-tooltip />
-        <el-table-column label="违纪日期" align="center" prop="disciplineDate" min-width="130" />
-        <el-table-column label="撤销原因" align="center" prop="revokeReason" min-width="200" show-overflow-tooltip />
-        <el-table-column label="申请时间" align="center" prop="applyTime" min-width="160" />
-        <el-table-column label="状态" align="center" prop="status" min-width="100">
-          <template #default="scope">
-            <dict-tag :options="student_revoke_status" :value="scope.row.status" />
-          </template>
-        </el-table-column>
-        <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
-          <template #default="scope">
-            <el-tooltip content="查看" placement="top">
-              <el-button
-                link
-                type="primary"
-                icon="View"
-                @click="handleView(scope.row)"
-                v-hasPermi="['disciplineRevoke:classTeacher:query']"
-              ></el-button>
-            </el-tooltip>
-            <el-tooltip content="审核通过" placement="top" v-if="scope.row.status === '1'">
-              <el-button
-                link
-                type="success"
-                icon="CircleCheck"
-                @click="handleApprove(scope.row, '2')"
-                v-hasPermi="['disciplineRevoke:classTeacher:approve']"
-              ></el-button>
-            </el-tooltip>
-            <el-tooltip content="审核驳回" placement="top" v-if="scope.row.status === '1'">
-              <el-button
-                link
-                type="danger"
-                icon="CircleClose"
-                @click="handleApprove(scope.row, '3')"
-                v-hasPermi="['disciplineRevoke:classTeacher:approve']"
-              ></el-button>
-            </el-tooltip>
-          </template>
-        </el-table-column>
-      </el-table>
+      <!-- Tab标签页 -->
+      <el-tabs v-model="activeTab" @tab-change="handleTabChange" class="demo-tabs">
+        <!-- 待审核Tab -->
+        <el-tab-pane label="待审核申请" name="pending">
+          <el-table v-loading="loading" border :data="revokeApplicationList" class="full-width-table">
+            <el-table-column label="学号" align="center" prop="studentNo" min-width="120" />
+            <el-table-column label="学生姓名" align="center" prop="studentName" min-width="100" />
+            <el-table-column label="违纪类型" align="center" prop="disciplineTypeName" min-width="140" show-overflow-tooltip />
+            <el-table-column label="违纪日期" align="center" prop="disciplineDate" min-width="130" />
+            <el-table-column label="撤销原因" align="center" prop="revokeReason" min-width="200" show-overflow-tooltip />
+            <el-table-column label="申请时间" align="center" prop="applyTime" min-width="160" />
+            <el-table-column label="状态" align="center" prop="status" min-width="100">
+              <template #default="scope">
+                <dict-tag :options="student_revoke_status" :value="scope.row.status" />
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
+              <template #default="scope">
+                <el-tooltip content="查看" placement="top">
+                  <el-button
+                    link
+                    type="primary"
+                    icon="View"
+                    @click="handleView(scope.row)"
+                    v-hasPermi="['disciplineRevoke:classTeacher:query']"
+                  ></el-button>
+                </el-tooltip>
+                <el-tooltip content="审核通过" placement="top">
+                  <el-button
+                    link
+                    type="success"
+                    icon="CircleCheck"
+                    @click="handleApprove(scope.row, '2')"
+                    v-hasPermi="['disciplineRevoke:classTeacher:approve']"
+                  ></el-button>
+                </el-tooltip>
+                <el-tooltip content="审核驳回" placement="top">
+                  <el-button
+                    link
+                    type="danger"
+                    icon="CircleClose"
+                    @click="handleApprove(scope.row, '4')"
+                    v-hasPermi="['disciplineRevoke:classTeacher:approve']"
+                  ></el-button>
+                </el-tooltip>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-tab-pane>
+
+        <!-- 已审核Tab -->
+        <el-tab-pane label="已审核记录" name="reviewed">
+          <el-table v-loading="loading" border :data="revokeApplicationList" class="full-width-table">
+            <el-table-column label="学号" align="center" prop="studentNo" min-width="120" />
+            <el-table-column label="学生姓名" align="center" prop="studentName" min-width="100" />
+            <el-table-column label="违纪类型" align="center" prop="disciplineTypeName" min-width="140" show-overflow-tooltip />
+            <el-table-column label="违纪日期" align="center" prop="disciplineDate" min-width="130" />
+            <el-table-column label="撤销原因" align="center" prop="revokeReason" min-width="200" show-overflow-tooltip />
+            <el-table-column label="申请时间" align="center" prop="applyTime" min-width="160" />
+            <el-table-column label="审核时间" align="center" prop="teacherApprovalTime" min-width="160" />
+            <el-table-column label="班主任审核备注" align="center" prop="teacherApprovalRemark" min-width="200" show-overflow-tooltip />
+            <el-table-column label="状态" align="center" prop="status" min-width="100">
+              <template #default="scope">
+                <dict-tag :options="student_revoke_status" :value="scope.row.status" />
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="120">
+              <template #default="scope">
+                <el-tooltip content="查看" placement="top">
+                  <el-button
+                    link
+                    type="primary"
+                    icon="View"
+                    @click="handleView(scope.row)"
+                    v-hasPermi="['disciplineRevoke:classTeacher:query']"
+                  ></el-button>
+                </el-tooltip>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-tab-pane>
+      </el-tabs>
 
       <pagination v-show="total > 0" :total="total" :page="queryParams.pageNum" :limit="queryParams.pageSize" @pagination="getList" />
     </el-card>
@@ -105,6 +143,32 @@
         <el-form-item label="撤销申请原因">
           <el-input v-model="approvalForm.revokeReason" type="textarea" :rows="3" disabled />
         </el-form-item>
+
+        <!-- 撤销证明材料展示 -->
+        <el-form-item label="撤销证明材料" v-if="revokeAttachmentList && revokeAttachmentList.length > 0">
+          <div class="attachment-view-container">
+            <div class="attachment-view-grid">
+              <div v-for="file in revokeAttachmentList" :key="file.uid" class="upload-file-card">
+                <div class="upload-file-thumb">
+                  <image-preview v-if="isImageFile(file) && file.url" :src="getFileOssId(file)" class="upload-thumb-image" :isOssId="true" />
+                  <div v-else class="upload-file-placeholder">
+                    <el-icon><Document /></el-icon>
+                    <span class="file-ext">{{ getFileExt(file.name) }}</span>
+                  </div>
+                </div>
+                <div class="upload-file-name" :title="file.name">
+                  {{ getShortFileName(file.name) }}
+                </div>
+                <div class="upload-file-card-actions">
+                  <el-button type="success" size="small" @click.stop="handleDownloadAttachment(file)" title="下载">
+                    <el-icon><Download /></el-icon>
+                  </el-button>
+                </div>
+              </div>
+            </div>
+          </div>
+        </el-form-item>
+
         <el-form-item label="审核备注" prop="approvalRemark">
           <el-input v-model="approvalForm.approvalRemark" type="textarea" placeholder="请输入审核备注" :rows="4" />
         </el-form-item>
@@ -120,11 +184,15 @@
 </template>
 
 <script setup name="RevokeApplicationApproval" lang="ts">
-import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue';
+import { ref, reactive, toRefs, onMounted, getCurrentInstance, ComponentInternalInstance } from 'vue';
+import { Document, Download } from '@element-plus/icons-vue';
+import ElMessage from 'element-plus/es/components/message/index';
+import { ElLoading } from 'element-plus/es/components/loading/index';
+import ImagePreview from '@/components/ImagePreview/index.vue';
 import request from '@/utils/request';
 import RevokeApplicationView from '../xsDisciplineRecord/RevokeApplicationView.vue';
 
-const { proxy } = getCurrentInstance();
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const { student_revoke_status } = toRefs<any>(proxy?.useDict('student_revoke_status'));
 
 const revokeApplicationList = ref<any[]>([]);
@@ -132,16 +200,21 @@ const loading = ref(true);
 const showSearch = ref(true);
 const total = ref(0);
 const buttonLoading = ref(false);
+const activeTab = ref('pending'); // 默认显示待审核Tab
 
 const queryFormRef = ref();
 const revokeApplicationViewRef = ref();
 const approvalFormRef = ref();
 
+// 撤销申请附件数据
+const revokeAttachmentList = ref<any[]>([]);
+
 interface QueryParams {
   pageNum: number;
   pageSize: number;
   studentNo?: string;
   studentName?: string;
+  status?: string;
   params: Record<string, any>;
 }
 
@@ -155,6 +228,7 @@ const data = reactive<{
     pageSize: 10,
     studentNo: undefined,
     studentName: undefined,
+    status: '1', // 默认查询待审核状态
     params: {}
   }
 });
@@ -186,9 +260,15 @@ const approvalRules = reactive({
 const getList = async () => {
   loading.value = true;
   try {
-    // 后续需要调用班主任专用的API
+    // 根据当前Tab决定使用哪个API
+    let apiUrl = '/disciplineRevoke/disciplineRevoke/classTeacherList';
+    if (activeTab.value === 'reviewed') {
+      // 已审核Tab使用新的API,通过审核人查询历史记录
+      apiUrl = '/disciplineRevoke/disciplineRevoke/classTeacherReviewedList';
+    }
+
     const res = await request({
-      url: '/disciplineRevoke/disciplineRevoke/classTeacherList',
+      url: apiUrl,
       method: 'get',
       params: queryParams.value
     });
@@ -215,27 +295,209 @@ const resetQuery = () => {
   handleQuery();
 };
 
+/** Tab切换操作 */
+const handleTabChange = (tabName: string) => {
+  // 重置页码
+  queryParams.value.pageNum = 1;
+
+  // 根据Tab设置状态筛选条件
+  if (tabName === 'pending') {
+    // 待审核:状态为'1'
+    queryParams.value.status = '1';
+  } else if (tabName === 'reviewed') {
+    // 已审核:清除状态筛选,通过API切换查询已审核记录
+    queryParams.value.status = undefined;
+  }
+
+  // 重新查询数据
+  getList();
+};
+
 /** 查看按钮操作 */
 const handleView = (row: any) => {
   revokeApplicationViewRef.value?.openDialog(row.disciplineRecordId, '查看');
 };
 
 /** 审核按钮操作 */
-const handleApprove = (row: any, status: string) => {
+const handleApprove = async (row: any, status: string) => {
   approvalForm.value = {
     id: row.id,
     disciplineRecordId: row.disciplineRecordId, // 添加违纪记录ID
     studentName: row.studentName,
     status: status,
-    statusText: status === '2' ? '审核通过' : '审核驳回',
+    statusText: status === '2' ? '班主任审核通过' : '班主任审核拒绝',
     approvalTime: new Date().toLocaleString(),
     revokeReason: row.revokeReason || '', // 添加撤销申请原因
     approvalRemark: ''
   };
-  approvalDialog.title = status === '2' ? '审核通过' : '审核驳回';
+  approvalDialog.title = status === '2' ? '班主任审核通过' : '班主任审核拒绝';
+
+  // 加载撤销申请附件数据
+  await loadRevokeAttachments(row.id);
+
   approvalDialog.visible = true;
 };
 
+/** 加载撤销申请附件数据 */
+const loadRevokeAttachments = async (revokeId: string | number) => {
+  try {
+    // 获取撤销申请详情
+    const response = await request({
+      url: `/disciplineRevoke/disciplineRevoke/${revokeId}`,
+      method: 'get'
+    });
+
+    if (response.code === 200 && response.data && response.data.revokeEvidence) {
+      await handleRevokeAttachmentData(response.data.revokeEvidence);
+    } else {
+      revokeAttachmentList.value = [];
+    }
+  } catch (error) {
+    console.error('加载撤销申请附件失败:', error);
+    revokeAttachmentList.value = [];
+  }
+};
+
+/** 处理撤销申请附件数据 */
+const handleRevokeAttachmentData = async (revokeEvidence: string) => {
+  try {
+    if (!revokeEvidence || !revokeEvidence.toString().trim()) {
+      revokeAttachmentList.value = [];
+      return;
+    }
+
+    const ossIdsArray = revokeEvidence
+      .toString()
+      .split(',')
+      .map((id: string) => id.trim())
+      .filter((id: string) => id);
+
+    if (ossIdsArray.length === 0) {
+      revokeAttachmentList.value = [];
+      return;
+    }
+
+    const response = await request({
+      url: `/resource/oss/listByIds/${ossIdsArray.join(',')}`,
+      method: 'get'
+    });
+
+    if (response.code === 200 && response.data) {
+      const attachmentFiles = response.data.map((item: any, index: number) => ({
+        uid: `revoke_${index}_${Date.now()}`,
+        name: item.originalName || `撤销证明${index + 1}`,
+        url: item.url,
+        ossId: item.ossId,
+        status: 'success'
+      }));
+
+      revokeAttachmentList.value = attachmentFiles;
+    } else {
+      revokeAttachmentList.value = [];
+    }
+  } catch (error) {
+    console.error('处理撤销申请附件数据失败:', error);
+    revokeAttachmentList.value = [];
+  }
+};
+
+// 文件处理方法
+const isImageFile = (file: any) => {
+  const imageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/webp'];
+  return imageTypes.includes(file.raw?.type) || (file.url && /\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file.name));
+};
+
+const getFileOssId = (file: any) => {
+  return file.ossId || '';
+};
+
+const getFileExt = (fileName: string) => {
+  const ext = fileName.split('.').pop()?.toUpperCase();
+  return ext || 'FILE';
+};
+
+const getShortFileName = (fileName: string) => {
+  if (!fileName) return '';
+  const lastDotIndex = fileName.lastIndexOf('.');
+  if (lastDotIndex === -1) {
+    return fileName.length > 12 ? fileName.substring(0, 12) + '...' : fileName;
+  }
+  const name = fileName.substring(0, lastDotIndex);
+  const ext = fileName.substring(lastDotIndex);
+  if (name.length <= 8) {
+    return fileName;
+  }
+  return name.substring(0, 8) + '...' + ext;
+};
+
+const handleDownloadAttachment = async (file: any) => {
+  let downloadUrl = '';
+  let fileName = file.name || 'attachment';
+
+  if (file.response && file.response.data && file.response.data.filePath) {
+    downloadUrl = file.response.data.filePath;
+    fileName = file.response.data.fileName || fileName;
+  } else if (file.ossId) {
+    downloadUrl = file.ossId;
+  } else if (file.raw) {
+    const link = document.createElement('a');
+    link.href = URL.createObjectURL(file.raw);
+    link.download = fileName;
+    link.style.display = 'none';
+    document.body.appendChild(link);
+    link.click();
+    document.body.removeChild(link);
+    URL.revokeObjectURL(link.href);
+    return;
+  }
+
+  if (!downloadUrl) {
+    ElMessage.error('无法获取文件下载地址');
+    return;
+  }
+
+  try {
+    const loading = ElLoading.service({
+      text: '正在下载文件...',
+      target: document.body
+    });
+
+    const response = await request({
+      url: '/resource/oss/download/' + downloadUrl,
+      method: 'get',
+      responseType: 'blob',
+      timeout: 300000
+    });
+
+    if (!response) {
+      throw new Error('响应数据为空');
+    }
+
+    let blobData: Blob;
+    if (response instanceof Blob) {
+      blobData = response;
+    } else if (response.data instanceof Blob) {
+      blobData = response.data;
+    } else {
+      throw new Error('无效的响应数据格式');
+    }
+
+    const url = window.URL.createObjectURL(blobData);
+    const link = document.createElement('a');
+    link.href = url;
+    link.download = fileName;
+    link.style.display = 'none';
+    document.body.appendChild(link);
+    link.click();
+    document.body.removeChild(link);
+    window.URL.revokeObjectURL(url);
+    loading?.close();
+  } catch (error) {
+    console.error('下载文件失败:', error);
+    ElMessage.error('下载文件失败,请稍后重试');
+  }
+};
+
 /** 提交审核 */
 const submitApproval = () => {
   approvalFormRef.value?.validate(async (valid: boolean) => {
@@ -243,14 +505,14 @@ const submitApproval = () => {
       buttonLoading.value = true;
       try {
         await request({
-          url: '/disciplineRevoke/disciplineRevoke/classTeacherApprove',
+          url: '/disciplineRevoke/disciplineRevoke/teacherApprove',
           method: 'put',
           data: {
             id: approvalForm.value.id,
             disciplineRecordId: approvalForm.value.disciplineRecordId, // 添加违纪记录ID
             status: approvalForm.value.status,
             revokeReason: approvalForm.value.revokeReason, // 添加撤销申请原因
-            approvalRemark: approvalForm.value.approvalRemark
+            teacherApprovalRemark: approvalForm.value.approvalRemark // 使用班主任审核备注字段
           }
         });
         proxy?.$modal.msgSuccess('审核成功');
@@ -279,6 +541,8 @@ const cancelApproval = () => {
     revokeReason: '', // 添加撤销申请原因字段
     approvalRemark: ''
   };
+  // 重置附件数据
+  revokeAttachmentList.value = [];
 };
 
 onMounted(() => {
@@ -312,4 +576,137 @@ onMounted(() => {
 :deep(.el-table__body-wrapper) {
   width: 100%;
 }
+
+/* 文件卡片样式 */
+.upload-file-card {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  border-radius: 6px;
+  overflow: hidden;
+  background: #fff;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+  transition: all 0.3s;
+}
+
+.upload-file-card:hover {
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
+}
+
+.upload-file-thumb {
+  position: relative;
+  width: 100%;
+  height: 110px;
+  background: #f5f5f5;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  overflow: hidden;
+}
+
+.upload-thumb-image {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+
+.upload-file-placeholder {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  color: #909399;
+  font-size: 14px;
+}
+
+.upload-file-placeholder .el-icon {
+  font-size: 28px;
+  margin-bottom: 4px;
+}
+
+.file-ext {
+  font-size: 12px;
+  text-transform: uppercase;
+  text-align: center;
+}
+
+.upload-file-name {
+  padding: 6px 8px;
+  font-size: 11px;
+  color: #303133;
+  text-align: center;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  background: #fafafa;
+  border-top: 1px solid #f0f0f0;
+  line-height: 1.2;
+  height: 32px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  word-break: break-all;
+  max-width: 100%;
+}
+
+.upload-file-card-actions {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  padding: 6px 8px;
+  background: #fafafa;
+  border-top: 1px solid #f0f0f0;
+  transition: all 0.3s;
+  height: 38px;
+}
+
+.upload-file-card-actions .el-button {
+  padding: 4px 8px;
+  border: 1px solid #e0e0e0;
+  background: #fff;
+  font-size: 12px;
+  border-radius: 3px;
+  transition: all 0.3s;
+}
+
+.upload-file-card-actions .el-button:hover {
+  background: #f5f5f5;
+  border-color: #409eff;
+}
+
+/* 查看模式下的附件网格展示 */
+.attachment-view-container {
+  width: 100%;
+  max-height: 300px;
+  min-height: 150px;
+  overflow-y: auto;
+  padding-right: 8px;
+  border: 1px solid #e4e7ed;
+  border-radius: 6px;
+  background: #fafafa;
+}
+
+.attachment-view-grid {
+  display: grid;
+  grid-template-columns: repeat(4, 1fr);
+  gap: 9px;
+  margin: 12px;
+  padding-bottom: 16px;
+}
+
+/* Tab标签页样式 */
+.demo-tabs {
+  margin-bottom: 20px;
+}
+
+.demo-tabs .el-tabs__header {
+  margin-bottom: 20px;
+}
+
+/* Tab内容区域样式 */
+.el-tab-pane {
+  margin-top: 10px;
+}
 </style>

+ 790 - 0
web/src/views/xskh/studentAffairsApproval/RevokeApplicationApproval.vue

@@ -0,0 +1,790 @@
+<template>
+  <div class="revoke-application-approval">
+    <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">
+            <el-row :gutter="20">
+              <el-col :span="8">
+                <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="8">
+                <el-form-item label="学生姓名" prop="studentName">
+                  <el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable @keyup.enter="handleQuery" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="8" v-if="activeTab === 'reviewed'">
+                <el-form-item label="审核结果" prop="status">
+                  <el-select v-model="queryParams.status" placeholder="请选择审核结果" clearable>
+                    <el-option label="审核通过" value="3" />
+                    <el-option label="审核拒绝" value="5" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="8" v-else>
+                <el-form-item label="状态" prop="status">
+                  <el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
+                    <el-option v-for="dict in student_revoke_status" :key="dict.value" :label="dict.label" :value="dict.value" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <el-row>
+              <el-col :span="24" class="search-btn-container">
+                <el-form-item>
+                  <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+                  <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+                </el-form-item>
+              </el-col>
+            </el-row>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <el-card shadow="never">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <span class="text-lg font-medium">学工处违纪撤销申请审核管理</span>
+          </el-col>
+          <right-toolbar :showSearch="showSearch" @update:showSearch="showSearch = $event" @queryTable="getList"></right-toolbar>
+        </el-row>
+      </template>
+
+      <el-tabs v-model="activeTab" @tab-change="handleTabChange" class="tab-container">
+        <!-- 待审核申请 Tab -->
+        <el-tab-pane label="待审核申请" name="pending">
+          <el-table v-loading="loading" border :data="revokeApplicationList" class="full-width-table">
+            <el-table-column label="学号" align="center" prop="studentNo" min-width="120" />
+            <el-table-column label="学生姓名" align="center" prop="studentName" min-width="100" />
+            <el-table-column label="违纪类型" align="center" prop="disciplineTypeName" min-width="140" show-overflow-tooltip />
+            <el-table-column label="违纪日期" align="center" prop="disciplineDate" min-width="130" />
+            <el-table-column label="撤销原因" align="center" prop="revokeReason" min-width="200" show-overflow-tooltip />
+            <el-table-column label="申请时间" align="center" prop="applyTime" min-width="160" />
+            <el-table-column label="班主任审核" align="center" min-width="120">
+              <template #default="scope">
+                <div v-if="scope.row.teacherApprovalTime">
+                  <div>{{ scope.row.teacherApprovalByName }}</div>
+                  <div class="text-xs text-gray-500">{{ scope.row.teacherApprovalTime }}</div>
+                </div>
+                <span v-else class="text-gray-400">-</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="状态" align="center" prop="status" min-width="100">
+              <template #default="scope">
+                <dict-tag :options="student_revoke_status" :value="scope.row.status" />
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
+              <template #default="scope">
+                <el-tooltip content="查看" placement="top">
+                  <el-button
+                    link
+                    type="primary"
+                    icon="View"
+                    @click="handleView(scope.row)"
+                    v-hasPermi="['disciplineRevoke:studentAffairs:query']"
+                  ></el-button>
+                </el-tooltip>
+                <el-tooltip content="审核通过" placement="top" v-if="scope.row.status === '2'">
+                  <el-button
+                    link
+                    type="success"
+                    icon="CircleCheck"
+                    @click="handleApprove(scope.row, '3')"
+                    v-hasPermi="['disciplineRevoke:studentAffairs:approve']"
+                  ></el-button>
+                </el-tooltip>
+                <el-tooltip content="审核驳回" placement="top" v-if="scope.row.status === '2'">
+                  <el-button
+                    link
+                    type="danger"
+                    icon="CircleClose"
+                    @click="handleApprove(scope.row, '5')"
+                    v-hasPermi="['disciplineRevoke:studentAffairs:approve']"
+                  ></el-button>
+                </el-tooltip>
+              </template>
+            </el-table-column>
+          </el-table>
+
+          <pagination v-show="total > 0" :total="total" :page="queryParams.pageNum" :limit="queryParams.pageSize" @pagination="getList" />
+        </el-tab-pane>
+
+        <!-- 已审核记录 Tab -->
+        <el-tab-pane label="已审核记录" name="reviewed">
+          <el-table v-loading="loading" border :data="reviewedApplicationList" class="full-width-table">
+            <el-table-column label="学号" align="center" prop="studentNo" min-width="120" />
+            <el-table-column label="学生姓名" align="center" prop="studentName" min-width="100" />
+            <el-table-column label="违纪类型" align="center" prop="disciplineTypeName" min-width="140" show-overflow-tooltip />
+            <el-table-column label="违纪日期" align="center" prop="disciplineDate" min-width="130" />
+            <el-table-column label="撤销原因" align="center" prop="revokeReason" min-width="200" show-overflow-tooltip />
+            <el-table-column label="申请时间" align="center" prop="applyTime" min-width="160" />
+            <el-table-column label="班主任审核" align="center" min-width="120">
+              <template #default="scope">
+                <div v-if="scope.row.teacherApprovalTime">
+                  <div>{{ scope.row.teacherApprovalByName }}</div>
+                  <div class="text-xs text-gray-500">{{ scope.row.teacherApprovalTime }}</div>
+                </div>
+                <span v-else class="text-gray-400">-</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="学工处审核" align="center" min-width="150">
+              <template #default="scope">
+                <div v-if="scope.row.approvalTime">
+                  <div>{{ scope.row.approvalByName || '学工处' }}</div>
+                  <div class="text-xs text-gray-500">{{ scope.row.approvalTime }}</div>
+                </div>
+                <span v-else class="text-gray-400">-</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="审核结果" align="center" prop="status" min-width="100">
+              <template #default="scope">
+                <el-tag :type="scope.row.status === '3' ? 'success' : 'danger'">
+                  {{ scope.row.status === '3' ? '审核通过' : '审核拒绝' }}
+                </el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column label="审核备注" align="center" prop="approvalRemark" min-width="200" show-overflow-tooltip />
+            <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="120">
+              <template #default="scope">
+                <el-tooltip content="查看" placement="top">
+                  <el-button
+                    link
+                    type="primary"
+                    icon="View"
+                    @click="handleView(scope.row)"
+                    v-hasPermi="['disciplineRevoke:studentAffairs:query']"
+                  ></el-button>
+                </el-tooltip>
+              </template>
+            </el-table-column>
+          </el-table>
+
+          <pagination
+            v-show="reviewedTotal > 0"
+            :total="reviewedTotal"
+            :page="queryParams.pageNum"
+            :limit="queryParams.pageSize"
+            @pagination="getList"
+          />
+        </el-tab-pane>
+      </el-tabs>
+    </el-card>
+
+    <!-- 查看撤销申请对话框 -->
+    <RevokeApplicationView ref="revokeApplicationViewRef" />
+
+    <!-- 审核对话框 -->
+    <el-dialog :title="approvalDialog.title" v-model="approvalDialog.visible" width="600px" append-to-body destroy-on-close>
+      <el-form ref="approvalFormRef" :model="approvalForm" :rules="approvalRules" label-width="120px">
+        <el-form-item label="学生姓名">
+          <el-input v-model="approvalForm.studentName" disabled />
+        </el-form-item>
+        <el-form-item label="审核结果">
+          <el-input v-model="approvalForm.statusText" disabled />
+        </el-form-item>
+        <el-form-item label="审核时间">
+          <el-input v-model="approvalForm.approvalTime" disabled />
+        </el-form-item>
+        <el-form-item label="撤销申请原因">
+          <el-input v-model="approvalForm.revokeReason" type="textarea" :rows="3" disabled />
+        </el-form-item>
+        <!-- 撤销证明材料展示(只读) -->
+        <el-form-item label="撤销证明材料" v-if="revokeAttachmentList && revokeAttachmentList.length > 0">
+          <div class="attachment-view-container">
+            <div class="attachment-view-grid">
+              <div v-for="file in revokeAttachmentList" :key="file.uid" class="upload-file-card">
+                <div class="upload-file-thumb">
+                  <image-preview v-if="isImageFile(file) && file.url" :src="getFileOssId(file)" class="upload-thumb-image" :isOssId="true" />
+                  <div v-else class="upload-file-placeholder">
+                    <el-icon><Document /></el-icon>
+                    <span class="file-ext">{{ getFileExt(file.name) }}</span>
+                  </div>
+                </div>
+                <div class="upload-file-name" :title="file.name">
+                  {{ getShortFileName(file.name) }}
+                </div>
+                <div class="upload-file-card-actions">
+                  <el-button type="success" size="small" @click.stop="handleDownloadAttachment(file)" title="下载">
+                    <el-icon><Download /></el-icon>
+                  </el-button>
+                </div>
+              </div>
+            </div>
+          </div>
+        </el-form-item>
+        <el-form-item label="班主任审核意见" v-if="approvalForm.teacherApprovalRemark">
+          <el-input v-model="approvalForm.teacherApprovalRemark" type="textarea" :rows="2" disabled />
+        </el-form-item>
+        <el-form-item label="学工处审核备注" prop="approvalRemark">
+          <el-input v-model="approvalForm.approvalRemark" type="textarea" placeholder="请输入学工处审核备注" :rows="4" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitApproval">确 定</el-button>
+          <el-button @click="cancelApproval">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="StudentAffairsRevokeApproval" lang="ts">
+import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue';
+import { Document, Download } from '@element-plus/icons-vue';
+import { ElMessage, ElLoading } from 'element-plus';
+import ImagePreview from '@/components/ImagePreview/index.vue';
+import request from '@/utils/request';
+import RevokeApplicationView from '../xsDisciplineRecord/RevokeApplicationView.vue';
+
+const { proxy } = getCurrentInstance();
+const { student_revoke_status } = toRefs<any>(proxy?.useDict('student_revoke_status'));
+
+const revokeApplicationList = ref<any[]>([]);
+const reviewedApplicationList = ref<any[]>([]);
+const loading = ref(true);
+const showSearch = ref(true);
+const total = ref(0);
+const reviewedTotal = ref(0);
+const buttonLoading = ref(false);
+const activeTab = ref('pending');
+
+// 撤销申请附件数据
+const revokeAttachmentList = ref<any[]>([]);
+
+const queryFormRef = ref();
+const revokeApplicationViewRef = ref();
+const approvalFormRef = ref();
+
+interface QueryParams {
+  pageNum: number;
+  pageSize: number;
+  studentNo?: string;
+  studentName?: string;
+  status?: string;
+  params: Record<string, any>;
+}
+
+const data = reactive<{
+  form: Record<string, any>;
+  queryParams: QueryParams;
+}>({
+  form: {},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    studentNo: undefined,
+    studentName: undefined,
+    status: undefined,
+    params: {}
+  }
+});
+
+const { queryParams } = toRefs(data);
+
+// 审核对话框
+const approvalDialog = reactive({
+  visible: false,
+  title: ''
+});
+
+const approvalForm = ref({
+  id: undefined,
+  disciplineRecordId: undefined,
+  studentName: '',
+  status: '',
+  statusText: '',
+  approvalTime: '',
+  revokeReason: '',
+  teacherApprovalRemark: '',
+  approvalRemark: ''
+});
+
+const approvalRules = reactive({
+  approvalRemark: [{ required: true, message: '审核备注不能为空', trigger: 'blur' }]
+});
+
+/** 查询学工处待审核的撤销申请列表 */
+const getList = async () => {
+  if (activeTab.value === 'pending') {
+    await getPendingList();
+  } else {
+    await getReviewedList();
+  }
+};
+
+/** 获取待审核申请列表 */
+const getPendingList = async () => {
+  loading.value = true;
+  try {
+    // 待审核状态,只查询班主任已审核通过的记录(状态为2)
+    const params = { ...queryParams.value };
+    if (!params.status) {
+      params.status = '2'; // 默认查询待学工处审核的记录
+    }
+
+    const res = await request({
+      url: '/disciplineRevoke/disciplineRevoke/studentAffairsList',
+      method: 'get',
+      params: params
+    });
+    revokeApplicationList.value = res.rows || [];
+    total.value = res.total || 0;
+  } catch (error) {
+    console.error('获取学工处待审核撤销申请列表失败:', error);
+    revokeApplicationList.value = [];
+    total.value = 0;
+  } finally {
+    loading.value = false;
+  }
+};
+
+/** 获取已审核记录列表 */
+const getReviewedList = async () => {
+  loading.value = true;
+  try {
+    // 已审核状态,查询状态为3(通过)或5(拒绝)的记录
+    const params = { ...queryParams.value };
+    if (!params.status) {
+      // 如果没有指定状态,查询所有已审核的记录
+      params.params.statusList = '3,5';
+    }
+
+    const res = await request({
+      url: '/disciplineRevoke/disciplineRevoke/studentAffairsReviewedList',
+      method: 'get',
+      params: params
+    });
+    reviewedApplicationList.value = res.rows || [];
+    reviewedTotal.value = res.total || 0;
+  } catch (error) {
+    console.error('获取学工处已审核撤销申请列表失败:', error);
+    reviewedApplicationList.value = [];
+    reviewedTotal.value = 0;
+  } finally {
+    loading.value = false;
+  }
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+};
+
+/** Tab切换处理 */
+const handleTabChange = (tabName: string) => {
+  activeTab.value = tabName;
+  // 重置查询参数
+  queryParams.value.pageNum = 1;
+  queryParams.value.status = undefined;
+  // 重新加载数据
+  getList();
+};
+
+/** 查看按钮操作 */
+const handleView = (row: any) => {
+  revokeApplicationViewRef.value?.openDialog(row.disciplineRecordId, '查看');
+};
+
+/** 审核按钮操作 */
+const handleApprove = async (row: any, status: string) => {
+  approvalForm.value = {
+    id: row.id,
+    disciplineRecordId: row.disciplineRecordId,
+    studentName: row.studentName,
+    status: status,
+    statusText: status === '3' ? '学工处审核通过' : '学工处审核拒绝',
+    approvalTime: new Date().toLocaleString(),
+    revokeReason: row.revokeReason || '',
+    teacherApprovalRemark: row.teacherApprovalRemark || '',
+    approvalRemark: ''
+  };
+
+  // 加载撤销申请的证明材料
+  if (row.revokeEvidence) {
+    await handleRevokeAttachmentData(row.revokeEvidence);
+  } else {
+    revokeAttachmentList.value = [];
+  }
+
+  approvalDialog.title = status === '3' ? '学工处审核通过' : '学工处审核拒绝';
+  approvalDialog.visible = true;
+};
+
+/** 提交审核 */
+const submitApproval = () => {
+  approvalFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      try {
+        await request({
+          url: '/disciplineRevoke/disciplineRevoke/studentAffairsApprove',
+          method: 'put',
+          data: {
+            id: approvalForm.value.id,
+            disciplineRecordId: approvalForm.value.disciplineRecordId,
+            status: approvalForm.value.status,
+            revokeReason: approvalForm.value.revokeReason,
+            approvalRemark: approvalForm.value.approvalRemark
+          }
+        });
+        proxy?.$modal.msgSuccess('审核成功');
+        approvalDialog.visible = false;
+        getList();
+      } catch (error) {
+        console.error('审核失败:', error);
+        proxy?.$modal.msgError('审核失败');
+      } finally {
+        buttonLoading.value = false;
+      }
+    }
+  });
+};
+
+/** 取消审核 */
+const cancelApproval = () => {
+  approvalDialog.visible = false;
+  approvalForm.value = {
+    id: undefined,
+    disciplineRecordId: undefined,
+    studentName: '',
+    status: '',
+    statusText: '',
+    approvalTime: '',
+    revokeReason: '',
+    teacherApprovalRemark: '',
+    approvalRemark: ''
+  };
+  // 重置附件数据
+  revokeAttachmentList.value = [];
+};
+
+// 文件处理方法
+const isImageFile = (file: any) => {
+  const imageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/webp'];
+  return imageTypes.includes(file.raw?.type) || (file.url && /\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file.name));
+};
+
+const getFileOssId = (file: any) => {
+  return file.ossId || '';
+};
+
+const getFileExt = (fileName: string) => {
+  const ext = fileName.split('.').pop()?.toUpperCase();
+  return ext || 'FILE';
+};
+
+const getShortFileName = (fileName: string) => {
+  if (!fileName) return '';
+  const lastDotIndex = fileName.lastIndexOf('.');
+  if (lastDotIndex === -1) {
+    return fileName.length > 12 ? fileName.substring(0, 12) + '...' : fileName;
+  }
+  const name = fileName.substring(0, lastDotIndex);
+  const ext = fileName.substring(lastDotIndex);
+  if (name.length <= 8) {
+    return fileName;
+  }
+  return name.substring(0, 8) + '...' + ext;
+};
+
+const handleDownloadAttachment = async (file: any) => {
+  let downloadUrl = '';
+  let fileName = file.name || 'attachment';
+
+  if (file.response && file.response.data && file.response.data.filePath) {
+    downloadUrl = file.response.data.filePath;
+    fileName = file.response.data.fileName || fileName;
+  } else if (file.ossId) {
+    downloadUrl = file.ossId;
+  } else if (file.raw) {
+    const link = document.createElement('a');
+    link.href = URL.createObjectURL(file.raw);
+    link.download = fileName;
+    link.style.display = 'none';
+    document.body.appendChild(link);
+    link.click();
+    document.body.removeChild(link);
+    URL.revokeObjectURL(link.href);
+    return;
+  }
+
+  if (!downloadUrl) {
+    ElMessage.error('无法获取文件下载地址');
+    return;
+  }
+
+  try {
+    const loading = ElLoading.service({
+      text: '正在下载文件...',
+      target: document.body
+    });
+
+    const response = await request({
+      url: '/resource/oss/download/' + downloadUrl,
+      method: 'get',
+      responseType: 'blob',
+      timeout: 300000
+    });
+
+    if (!response) {
+      throw new Error('响应数据为空');
+    }
+
+    let blobData: Blob;
+    if (response instanceof Blob) {
+      blobData = response;
+    } else if (response.data instanceof Blob) {
+      blobData = response.data;
+    } else {
+      throw new Error('无效的响应数据格式');
+    }
+
+    const url = window.URL.createObjectURL(blobData);
+    const link = document.createElement('a');
+    link.href = url;
+    link.download = fileName;
+    link.style.display = 'none';
+    document.body.appendChild(link);
+    link.click();
+    document.body.removeChild(link);
+    window.URL.revokeObjectURL(url);
+    loading?.close();
+  } catch (error) {
+    console.error('下载文件失败:', error);
+    ElMessage.error('下载文件失败,请稍后重试');
+  }
+};
+
+const handleRevokeAttachmentData = async (revokeEvidence: string) => {
+  try {
+    if (!revokeEvidence || !revokeEvidence.toString().trim()) {
+      revokeAttachmentList.value = [];
+      return;
+    }
+
+    const ossIdsArray = revokeEvidence
+      .toString()
+      .split(',')
+      .map((id: string) => id.trim())
+      .filter((id: string) => id);
+
+    if (ossIdsArray.length === 0) {
+      revokeAttachmentList.value = [];
+      return;
+    }
+
+    const response = await request({
+      url: `/resource/oss/listByIds/${ossIdsArray.join(',')}`,
+      method: 'get'
+    });
+
+    if (response.code === 200 && response.data) {
+      const attachmentFiles = response.data.map((item: any, index: number) => ({
+        uid: `revoke_${index}_${Date.now()}`,
+        name: item.originalName || `撤销证明${index + 1}`,
+        url: item.url,
+        ossId: item.ossId,
+        status: 'success'
+      }));
+
+      revokeAttachmentList.value = attachmentFiles;
+    } else {
+      revokeAttachmentList.value = [];
+    }
+  } catch (error) {
+    console.error('处理撤销申请附件数据失败:', error);
+    revokeAttachmentList.value = [];
+  }
+};
+
+onMounted(() => {
+  getList();
+});
+</script>
+
+<style scoped>
+/* 搜索按钮容器样式 */
+.search-btn-container {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 10px;
+}
+
+.search-btn-container .el-form-item {
+  margin-bottom: 0 !important;
+}
+
+/* 表格全宽样式 */
+.full-width-table {
+  width: 100%;
+}
+
+/* 确保表格容器充满宽度 */
+:deep(.el-table) {
+  width: 100%;
+}
+
+/* 表格内容区域充满宽度 */
+:deep(.el-table__body-wrapper) {
+  width: 100%;
+}
+
+/* 文件卡片样式 */
+.upload-file-card {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  border-radius: 6px;
+  overflow: hidden;
+  background: #fff;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+  transition: all 0.3s;
+}
+
+.upload-file-card:hover {
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
+}
+
+.upload-file-thumb {
+  position: relative;
+  width: 100%;
+  height: 110px;
+  background: #f5f5f5;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  overflow: hidden;
+}
+
+.upload-thumb-image {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+
+.upload-file-placeholder {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  color: #909399;
+  font-size: 14px;
+}
+
+.upload-file-placeholder .el-icon {
+  font-size: 28px;
+  margin-bottom: 4px;
+}
+
+.file-ext {
+  font-size: 12px;
+  text-transform: uppercase;
+  text-align: center;
+}
+
+.upload-file-name {
+  padding: 6px 8px;
+  font-size: 11px;
+  color: #303133;
+  text-align: center;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  background: #fafafa;
+  border-top: 1px solid #f0f0f0;
+  line-height: 1.2;
+  height: 32px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  word-break: break-all;
+  max-width: 100%;
+}
+
+.upload-file-card-actions {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  padding: 6px 8px;
+  background: #fafafa;
+  border-top: 1px solid #f0f0f0;
+  transition: all 0.3s;
+  height: 38px;
+}
+
+.upload-file-card-actions .el-button {
+  padding: 4px 8px;
+  border: 1px solid #e0e0e0;
+  background: #fff;
+  font-size: 12px;
+  border-radius: 3px;
+  transition: all 0.3s;
+}
+
+.upload-file-card-actions .el-button:hover {
+  background: #f5f5f5;
+  border-color: #409eff;
+}
+
+/* 查看模式下的附件网格展示 */
+.attachment-view-container {
+  width: 100%;
+  max-height: 300px;
+  min-height: 150px;
+  overflow-y: auto;
+  padding-right: 8px;
+  border: 1px solid #e4e7ed;
+  border-radius: 6px;
+  background: #fafafa;
+}
+
+.attachment-view-grid {
+  display: grid;
+  grid-template-columns: repeat(4, 1fr);
+  gap: 9px;
+  margin: 12px;
+  padding-bottom: 16px;
+}
+
+/* Tab 容器样式 */
+.tab-container {
+  margin-top: 16px;
+}
+
+.tab-container :deep(.el-tabs__header) {
+  margin-bottom: 20px;
+}
+
+.tab-container :deep(.el-tabs__nav-wrap) {
+  padding-left: 0;
+}
+
+.tab-container :deep(.el-tabs__item) {
+  font-size: 14px;
+  font-weight: 500;
+  padding: 0 20px;
+}
+
+.tab-container :deep(.el-tabs__item.is-active) {
+  /* color: #409eff; */
+}
+
+.tab-container :deep(.el-tabs__content) {
+  padding-top: 0;
+}
+</style>

+ 68 - 15
web/src/views/xskh/xsDisciplineRecord/RevokeApplicationView.vue

@@ -111,14 +111,9 @@
             </el-form-item>
           </el-col>
 
-          <!-- 右列:审批信息 -->
+          <!-- 右列:预留空间 -->
           <el-col :span="12">
-            <el-form-item label="审核时间" v-if="revokeData?.approvalTime">
-              <div class="form-field-value">{{ revokeData.approvalTime }}</div>
-            </el-form-item>
-            <el-form-item label="审核人" v-if="revokeData?.approvalByName">
-              <div class="form-field-value">{{ revokeData.approvalByName }}</div>
-            </el-form-item>
+            <!-- 占位 -->
           </el-col>
         </el-row>
 
@@ -130,14 +125,6 @@
           </el-col>
         </el-row>
 
-        <el-row v-if="revokeData?.approvalRemark">
-          <el-col :span="24">
-            <el-form-item label="审核备注">
-              <div class="form-field-value textarea-value">{{ revokeData.approvalRemark }}</div>
-            </el-form-item>
-          </el-col>
-        </el-row>
-
         <!-- 撤销证明材料展示(只读) -->
         <el-row v-if="revokeAttachmentList && revokeAttachmentList.length > 0">
           <el-col :span="24">
@@ -166,6 +153,57 @@
             </el-form-item>
           </el-col>
         </el-row>
+
+        <!-- 班主任审核信息 -->
+        <div
+          v-if="revokeData?.teacherApprovalTime || (revokeData?.status && ['2', '3', '4', '5'].includes(revokeData.status))"
+          class="approval-section"
+        >
+          <h4 class="section-subtitle">班主任审核</h4>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="班主任审核时间" v-if="revokeData?.teacherApprovalTime">
+                <div class="form-field-value">{{ revokeData.teacherApprovalTime }}</div>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="班主任审核人" v-if="revokeData?.teacherApprovalByName">
+                <div class="form-field-value">{{ revokeData.teacherApprovalByName }}</div>
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row v-if="revokeData?.teacherApprovalRemark">
+            <el-col :span="24">
+              <el-form-item label="班主任审核备注">
+                <div class="form-field-value textarea-value">{{ revokeData.teacherApprovalRemark }}</div>
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </div>
+
+        <!-- 学工处审核信息 -->
+        <div v-if="revokeData?.approvalTime || (revokeData?.status && ['3', '5'].includes(revokeData.status))" class="approval-section">
+          <h4 class="section-subtitle">学工处审核</h4>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="学工处审核时间" v-if="revokeData?.approvalTime">
+                <div class="form-field-value">{{ revokeData.approvalTime }}</div>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="学工处审核人" v-if="revokeData?.approvalByName">
+                <div class="form-field-value">{{ revokeData.approvalByName }}</div>
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row v-if="revokeData?.approvalRemark">
+            <el-col :span="24">
+              <el-form-item label="学工处审核备注">
+                <div class="form-field-value textarea-value">{{ revokeData.approvalRemark }}</div>
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </div>
       </el-form>
     </div>
 
@@ -446,6 +484,21 @@ defineExpose({
   border-bottom: 1px solid #ebeef5;
 }
 
+.section-subtitle {
+  font-size: 14px;
+  font-weight: 600;
+  color: #606266;
+  margin-bottom: 10px;
+  padding-bottom: 5px;
+  border-bottom: 1px dashed #ebeef5;
+}
+
+.approval-section {
+  margin-top: 20px;
+  padding-top: 15px;
+  border-top: 1px dashed #ebeef5;
+}
+
 .discipline-record-section {
   margin-bottom: 20px;
 }