diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApplicationController.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApplicationController.java
index c4e1c4aa..93e4606f 100644
--- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApplicationController.java
+++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApplicationController.java
@@ -330,7 +330,7 @@ public class RsOverseasApplicationController extends BaseController {
return toAjax(applicationService.approveApplication(
request.getApplicationId(), approverId, approverName,
request.getApprovalStatus(), request.getApprovalOpinion(),
- request.getTaskId()));
+ request.getApprovalSignature(), request.getTaskId()));
}
/**
@@ -376,6 +376,7 @@ public class RsOverseasApplicationController extends BaseController {
private Long applicationId;
private String approvalStatus;
private String approvalOpinion;
+ private String approvalSignature; // 审批签字(base64图片)
private String taskId; // 🔴 新增:工作流任务ID,支持标准工作流模式
public Long getApplicationId() {
@@ -402,6 +403,14 @@ public class RsOverseasApplicationController extends BaseController {
this.approvalOpinion = approvalOpinion;
}
+ public String getApprovalSignature() {
+ return approvalSignature;
+ }
+
+ public void setApprovalSignature(String approvalSignature) {
+ this.approvalSignature = approvalSignature;
+ }
+
public String getTaskId() {
return taskId;
}
diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApprovalMonitorController.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApprovalMonitorController.java
index 4b4b637a..e91aac59 100644
--- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApprovalMonitorController.java
+++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApprovalMonitorController.java
@@ -6,20 +6,14 @@ import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
-import org.dromara.common.log.annotation.Log;
-import org.dromara.common.log.enums.BusinessType;
-import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.rs.domain.vo.RsOverseasApprovalMonitorVo;
-import org.dromara.rs.domain.vo.RsOverseasApprovalVo;
import org.dromara.rs.domain.query.RsOverseasApprovalMonitorQuery;
import org.dromara.rs.service.IRsOverseasApprovalMonitorService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
-import java.util.List;
-
/**
* 出国申请审批监控Controller
*
@@ -40,53 +34,40 @@ public class RsOverseasApprovalMonitorController extends BaseController {
private final IRsOverseasApprovalMonitorService monitorService;
/**
- * 查询审批中的申请列表(实时监控)
+ * 查询待审批的申请列表(出国申请进度查询)
*
- * 显示所有状态为"待审核"的申请,包含:
- * - 申请人信息
- * - 出国目的地和时间
- * - 当前审批环节(部门主管/分管领导/校领导)
- * - 当前审批人姓名
- * - 审批开始时间
+ * 🔴 功能说明:查询所有待审批申请(不限制申请人)
+ * - 拥有权限的用户可以查看所有待审批申请
+ * - 显示当前审批环节和审批人
+ * - 可以查看审批历史记录
*
* @param query 查询条件
- * @return 审批中的申请列表
+ * @return 所有待审批申请列表
*/
- // 🔴 标准化改造:删除自定义待审批查询接口
- // 原因:出国申请工作流应使用标准工作流模式
- // 用户应通过工作流API pageByTaskWait() 获取待办任务
- // 而不是调用自定义的业务API
- //
- // 标准工作流模式:
- // 1. 用户访问标准待办页面:/workflow/task/taskWaiting.vue
- // 2. 调用工作流API:pageByTaskWait() 获取待办任务
- // 3. 点击"办理"跳转:/rs/overseas/application/edit?taskId=xxx
- // 4. 在业务表单页面进行审批操作
-
@SaCheckPermission("rs:overseas:list")
@GetMapping("/pending/list")
- public R pendingList(RsOverseasApprovalMonitorQuery query) {
- // 提示用户使用标准工作流API
- return R.fail("请使用标准工作流待办页面查看审批任务。访问:工作流 > 我的任务 > 待办任务");
+ public TableDataInfo pendingList(RsOverseasApprovalMonitorQuery query) {
+ log.info("查询所有待审批申请列表");
+ // 只查询待审核状态(status = 0)
+ query.setStatus("0");
+ return monitorService.queryPendingList(query);
}
/**
* 查询已完成审批的申请列表
*
- * 显示所有审批流程完成的申请(已通过),包含:
- * - 申请人信息
- * - 出国目的地和时间
- * - 最终审批结果
- * - 完成时间
- * - 所有审批环节的历史记录
+ * 🔴 功能说明:查询所有已完成审批申请(不限制申请人)
+ * - 拥有权限的用户可以查看所有已完成审批申请
+ * - 显示最终审批结果和完成时间
+ * - 可以查看所有审批环节的历史记录
*
* @param query 查询条件
- * @return 已完成审批的申请列表
+ * @return 所有已完成审批的申请列表
*/
@SaCheckPermission("rs:overseas:list")
@GetMapping("/completed/list")
public TableDataInfo completedList(RsOverseasApprovalMonitorQuery query) {
- log.info("查询已完成审批的申请列表,查询条件:{}", query);
+ log.info("查询所有已完成审批的申请列表,查询条件:{}", query);
// 只查询已通过状态(status = 1)
query.setStatus("1");
return monitorService.queryCompletedList(query);
@@ -128,4 +109,21 @@ public class RsOverseasApprovalMonitorController extends BaseController {
log.info("查询审批进度统计");
return R.ok(monitorService.getApprovalStatistics());
}
+
+ /**
+ * 下载出国申请审批表PDF
+ *
+ * 生成并下载"杭州第一技师学院登记报备人员因私事出国(境)审批表"PDF文件
+ *
+ * @param applicationId 申请ID
+ * @param response HTTP响应对象
+ */
+ @SaCheckPermission("rs:overseas:query")
+ @GetMapping("/downloadApprovalPdf/{applicationId}")
+ public void downloadApprovalPdf(
+ @PathVariable @NotNull(message = "申请ID不能为空") Long applicationId,
+ HttpServletResponse response) {
+ log.info("下载出国申请审批表PDF,申请ID:{}", applicationId);
+ monitorService.downloadApprovalPdf(applicationId, response);
+ }
}
diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/RsOverseasApproval.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/RsOverseasApproval.java
index 707c6544..1a99cde2 100644
--- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/RsOverseasApproval.java
+++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/RsOverseasApproval.java
@@ -54,7 +54,7 @@ public class RsOverseasApproval extends BaseEntity {
private String approverName;
/**
- * 审批级别:1-部门主管,2-分管领导,3-校领导
+ * 审批级别:1-部门负责人,2-人事处,3-纪委,4-学院分管领导,5-学院党委书记
*/
@ExcelProperty(value = "审批级别")
private String approvalLevel;
@@ -71,6 +71,12 @@ public class RsOverseasApproval extends BaseEntity {
@ExcelProperty(value = "审批意见")
private String approvalOpinion;
+ /**
+ * 审批签字(base64图片数据)
+ */
+ @ExcelIgnore
+ private String approvalSignature;
+
/**
* 审批时间
*/
@@ -118,5 +124,4 @@ public class RsOverseasApproval extends BaseEntity {
@ExcelIgnore
private Date updateTime;
-
-}
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/bo/RsOverseasApprovalBo.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/bo/RsOverseasApprovalBo.java
index f0a42fc5..8d176366 100644
--- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/bo/RsOverseasApprovalBo.java
+++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/bo/RsOverseasApprovalBo.java
@@ -72,4 +72,9 @@ public class RsOverseasApprovalBo extends BaseEntity {
* 审批意见
*/
private String approvalOpinion;
-}
\ No newline at end of file
+
+ /**
+ * 审批签字(base64图片数据)
+ */
+ private String approvalSignature;
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsOverseasApprovalVo.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsOverseasApprovalVo.java
index a9040758..486b8519 100644
--- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsOverseasApprovalVo.java
+++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsOverseasApprovalVo.java
@@ -67,6 +67,11 @@ public class RsOverseasApprovalVo implements Serializable {
*/
private String approvalOpinion;
+ /**
+ * 审批签字(base64图片数据)
+ */
+ private String approvalSignature;
+
/**
* 审批时间
*/
@@ -74,49 +79,49 @@ public class RsOverseasApprovalVo implements Serializable {
private Date approvalTime;
// ==================== 申请信息相关字段 ====================
-
+
/**
* 申请人ID
*/
private Long applicantId;
-
+
/**
* 申请人姓名
*/
private String applicantName;
-
+
/**
* 所属部门
*/
private String department;
-
+
/**
* 出国目的
*/
private String purpose;
-
+
/**
* 出国目的文本
*/
private String purposeText;
-
+
/**
* 目的地
*/
private String destination;
-
+
/**
* 出国开始日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date startDate;
-
+
/**
* 出国结束日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date endDate;
-
+
/**
* 出国天数
*/
@@ -158,4 +163,4 @@ public class RsOverseasApprovalVo implements Serializable {
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
-}
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsOverseasApplicationService.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsOverseasApplicationService.java
index 8f267922..605890ee 100644
--- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsOverseasApplicationService.java
+++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsOverseasApplicationService.java
@@ -132,21 +132,22 @@ public interface IRsOverseasApplicationService {
* @return 是否成功
*/
Boolean approveApplication(Long applicationId, Long approverId, String approverName,
- String approvalStatus, String approvalOpinion);
+ String approvalStatus, String approvalOpinion, String approvalSignature);
/**
* 审批出国申请(支持标准工作流模式)
*
- * @param applicationId 申请ID
- * @param approverId 审批人ID
- * @param approverName 审批人姓名
- * @param approvalStatus 审批结果:1-通过,2-拒绝
- * @param approvalOpinion 审批意见
- * @param taskId 工作流任务ID(可选,用于标准工作流模式)
+ * @param applicationId 申请ID
+ * @param approverId 审批人ID
+ * @param approverName 审批人姓名
+ * @param approvalStatus 审批结果:1-通过,2-拒绝
+ * @param approvalOpinion 审批意见
+ * @param approvalSignature 审批签字(base64图片)
+ * @param taskId 工作流任务ID(可选,用于标准工作流模式)
* @return 是否成功
*/
Boolean approveApplication(Long applicationId, Long approverId, String approverName,
- String approvalStatus, String approvalOpinion, String taskId);
+ String approvalStatus, String approvalOpinion, String approvalSignature, String taskId);
/**
* 校验删除数据
diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsOverseasApprovalMonitorService.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsOverseasApprovalMonitorService.java
index 8db60212..8659fe33 100644
--- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsOverseasApprovalMonitorService.java
+++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsOverseasApprovalMonitorService.java
@@ -1,5 +1,6 @@
package org.dromara.rs.service;
+import jakarta.servlet.http.HttpServletResponse;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.rs.domain.vo.RsOverseasApprovalMonitorVo;
import org.dromara.rs.domain.query.RsOverseasApprovalMonitorQuery;
@@ -44,4 +45,12 @@ public interface IRsOverseasApprovalMonitorService {
* @return 统计数据
*/
Map getApprovalStatistics();
+
+ /**
+ * 下载出国申请审批表PDF
+ *
+ * @param applicationId 申请ID
+ * @param response HTTP响应对象
+ */
+ void downloadApprovalPdf(Long applicationId, HttpServletResponse response);
}
diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApplicationServiceImpl.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApplicationServiceImpl.java
index ed3bf9b6..6e885517 100644
--- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApplicationServiceImpl.java
+++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApplicationServiceImpl.java
@@ -558,7 +558,7 @@ public class RsOverseasApplicationServiceImpl extends ServiceImpl queryPendingList(RsOverseasApprovalMonitorQuery query) {
@@ -54,6 +77,7 @@ public class RsOverseasApprovalMonitorServiceImpl implements IRsOverseasApproval
// 构建查询条件
QueryWrapper wrapper = buildQueryWrapper(query);
wrapper.eq("status", "0"); // 只查询待审核状态
+ // 🔴 不再限制申请人,拥有权限的用户可以看到所有数据
// 分页查询
Page page = applicationMapper.selectPage(
@@ -70,11 +94,13 @@ public class RsOverseasApprovalMonitorServiceImpl implements IRsOverseasApproval
resultPage.setRecords(voList);
resultPage.setTotal(page.getTotal());
+ log.info("✅ 查询到 {} 条待审批申请", voList.size());
return TableDataInfo.build(resultPage);
}
/**
* 查询已完成审批的申请列表(已通过状态)
+ * 🔴 查询所有已完成审批申请(不限制申请人)
*/
@Override
public TableDataInfo queryCompletedList(RsOverseasApprovalMonitorQuery query) {
@@ -83,6 +109,7 @@ public class RsOverseasApprovalMonitorServiceImpl implements IRsOverseasApproval
// 构建查询条件
QueryWrapper wrapper = buildQueryWrapper(query);
wrapper.eq("status", "1"); // 只查询已通过状态
+ // 🔴 不再限制申请人,拥有权限的用户可以看到所有数据
// 分页查询
Page page = applicationMapper.selectPage(
@@ -99,6 +126,7 @@ public class RsOverseasApprovalMonitorServiceImpl implements IRsOverseasApproval
resultPage.setRecords(voList);
resultPage.setTotal(page.getTotal());
+ log.info("✅ 查询到 {} 条已完成审批申请", voList.size());
return TableDataInfo.build(resultPage);
}
@@ -143,11 +171,13 @@ public class RsOverseasApprovalMonitorServiceImpl implements IRsOverseasApproval
.eq("tenant_id", tenantId));
statistics.put("pendingCount", pendingCount);
- // 2. 各审批级别的待审批数量
+ // 2. 各审批级别的待审批数量(五级审批制度)
Map pendingByLevel = new HashMap<>();
- pendingByLevel.put("level1", countByStatusAndLevel("0", "1", tenantId)); // 部门主管
- pendingByLevel.put("level2", countByStatusAndLevel("0", "2", tenantId)); // 分管领导
- pendingByLevel.put("level3", countByStatusAndLevel("0", "3", tenantId)); // 校领导
+ pendingByLevel.put("level1", countByStatusAndLevel("0", "1", tenantId)); // 部门负责人
+ pendingByLevel.put("level2", countByStatusAndLevel("0", "2", tenantId)); // 人事处
+ pendingByLevel.put("level3", countByStatusAndLevel("0", "3", tenantId)); // 纪委
+ pendingByLevel.put("level4", countByStatusAndLevel("0", "4", tenantId)); // 学院分管领导
+ pendingByLevel.put("level5", countByStatusAndLevel("0", "5", tenantId)); // 学院党委书记
statistics.put("pendingByLevel", pendingByLevel);
// 3. 已完成审批总数
@@ -285,9 +315,20 @@ public class RsOverseasApprovalMonitorServiceImpl implements IRsOverseasApproval
// 审批状态信息
vo.setStatus(application.getStatus());
vo.setStatusText(getStatusText(application.getStatus()));
- vo.setCurrentApprover(application.getCurrentApprover());
- vo.setApprovalLevel(application.getApprovalLevel());
- vo.setApprovalLevelText(getApprovalLevelText(application.getApprovalLevel()));
+
+ // 根据申请状态设置当前审批人和审批级别
+ if ("1".equals(application.getStatus()) || "2".equals(application.getStatus())) {
+ // 审批已完成(通过或拒绝)
+ vo.setCurrentApprover("审批已完成");
+ vo.setApprovalLevel(application.getApprovalLevel());
+ vo.setApprovalLevelText("审批已完成");
+ } else {
+ // 待审核状态,显示当前审批信息
+ vo.setCurrentApprover(application.getCurrentApprover());
+ vo.setApprovalLevel(application.getApprovalLevel());
+ vo.setApprovalLevelText(getApprovalLevelText(application.getApprovalLevel()));
+ }
+
vo.setApplyTime(application.getApplyTime());
vo.setApprovalTime(application.getApprovalTime());
@@ -336,8 +377,8 @@ public class RsOverseasApprovalMonitorServiceImpl implements IRsOverseasApproval
.count();
vo.setApprovedLevels((int) approvedCount);
- // 总审批级别数(假设最多3级:部门主管、分管领导、校领导)
- vo.setTotalLevels(3);
+ // 总审批级别数(五级审批制度:部门负责人、人事处、纪委、学院分管领导、学院党委书记)
+ vo.setTotalLevels(5);
// 审批进度文本
vo.setApprovalProgress(approvedCount + "/" + vo.getTotalLevels());
@@ -350,8 +391,8 @@ public class RsOverseasApprovalMonitorServiceImpl implements IRsOverseasApproval
} else {
// 未开始审批
vo.setApprovedLevels(0);
- vo.setTotalLevels(3);
- vo.setApprovalProgress("0/3");
+ vo.setTotalLevels(5);
+ vo.setApprovalProgress("0/5");
}
}
@@ -380,6 +421,7 @@ public class RsOverseasApprovalMonitorServiceImpl implements IRsOverseasApproval
/**
* 获取审批级别文本
+ * 五级审批制度:1-部门负责人,2-人事处,3-纪委,4-学院分管领导,5-学院党委书记
*/
private String getApprovalLevelText(String level) {
if (level == null) {
@@ -387,11 +429,15 @@ public class RsOverseasApprovalMonitorServiceImpl implements IRsOverseasApproval
}
switch (level) {
case "1":
- return "部门主管审批";
+ return "部门负责人审批";
case "2":
- return "分管领导审批";
+ return "人事处审批";
case "3":
- return "校领导审批";
+ return "纪委审批";
+ case "4":
+ return "学院分管领导审批";
+ case "5":
+ return "学院党委书记审批";
default:
return "未知";
}
@@ -495,4 +541,560 @@ public class RsOverseasApprovalMonitorServiceImpl implements IRsOverseasApproval
return politicalStatus;
}
}
+
+ /**
+ * 下载出国申请审批表PDF
+ */
+ @Override
+ public void downloadApprovalPdf(Long applicationId, HttpServletResponse response) {
+ log.info("开始生成出国申请审批表PDF,申请ID:{}", applicationId);
+
+ try {
+ // 查询申请信息
+ RsOverseasApplication application = applicationMapper.selectById(applicationId);
+ if (application == null) {
+ throw new ServiceException("未找到申请信息");
+ }
+
+ // 查询审批历史
+ List approvalHistory = approvalService.queryByApplicationId(applicationId);
+
+ // 调试日志:输出审批历史数据
+ log.info("📋 查询到审批历史记录数量: {}", approvalHistory != null ? approvalHistory.size() : 0);
+ if (approvalHistory != null && !approvalHistory.isEmpty()) {
+ for (RsOverseasApprovalVo approval : approvalHistory) {
+ log.info(" - 级别{}: 审批人={}, 意见={}, 签字数据长度={}",
+ approval.getApprovalLevel(),
+ approval.getApproverName(),
+ approval.getApprovalOpinion(),
+ approval.getApprovalSignature() != null ? approval.getApprovalSignature().length() : 0);
+ }
+ }
+
+ // 设置响应头
+ String fileName = "出国申请审批表_" + application.getApplicantName() + "_"
+ + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".pdf";
+ response.setContentType("application/pdf");
+ response.setHeader("Content-Disposition",
+ "attachment; filename*=UTF-8''" + URLEncoder.encode(fileName, StandardCharsets.UTF_8));
+
+ // 生成PDF
+ generateApprovalPdf(application, approvalHistory, response.getOutputStream());
+
+ log.info("✅ 出国申请审批表PDF生成成功");
+ } catch (Exception e) {
+ log.error("❌ 生成出国申请审批表PDF失败:{}", e.getMessage(), e);
+ throw new ServiceException("生成PDF失败:" + e.getMessage());
+ }
+ }
+
+ /**
+ * 生成审批表PDF文档
+ */
+ private void generateApprovalPdf(RsOverseasApplication application,
+ List approvalHistory, OutputStream outputStream)
+ throws DocumentException, IOException {
+
+ Document document = new Document(PageSize.A4, 40, 40, 40, 40);
+ PdfWriter.getInstance(document, outputStream);
+ document.open();
+
+ try {
+ // 创建中文字体
+ BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
+ Font titleFont = new Font(baseFont, 16, Font.BOLD);
+ Font headerFont = new Font(baseFont, 14, Font.BOLD);
+ Font normalFont = new Font(baseFont, 10, Font.NORMAL);
+ Font smallFont = new Font(baseFont, 9, Font.NORMAL);
+
+ // 1. 标题
+ Paragraph title1 = new Paragraph("杭州第一技师学院", titleFont);
+ title1.setAlignment(Element.ALIGN_CENTER);
+ title1.setSpacingAfter(5);
+ document.add(title1);
+
+ Paragraph title2 = new Paragraph("登记报备人员因私事出国(境)审批表", headerFont);
+ title2.setAlignment(Element.ALIGN_CENTER);
+ title2.setSpacingAfter(15);
+ document.add(title2);
+
+ // 2. 个人信息表格(优化列宽,使垂直线对齐)
+ PdfPTable infoTable = new PdfPTable(12); // 12列
+ infoTable.setWidthPercentage(100);
+ // 列宽:使关键分界线对齐
+ infoTable.setWidths(new float[] { 6f, 9f, 5f, 7f, 8f, 9f, 8f, 9f, 6f, 9f, 8f, 10f });
+
+ // 第一行:姓名、性别、出生年月、政治面貌
+ addCell(infoTable, "姓名", normalFont, true, 1); // 列1: 6f
+ addCell(infoTable, application.getApplicantName(), normalFont, false, 2); // 列2-3: 9f+5f=14f
+ addCell(infoTable, "性别", normalFont, true, 1); // 列4: 7f
+ addCell(infoTable, translateGender(application.getGender()), normalFont, false, 1); // 列5: 8f
+ addCell(infoTable, "出生年月", normalFont, true, 1); // 列6: 9f
+ addCell(infoTable, formatBirthDate(application.getBirthDate()), normalFont, false, 2); // 列7-8: 8f+9f=17f
+ addCell(infoTable, "政治面貌", normalFont, true, 1); // 列9: 6f
+ addCell(infoTable, translatePoliticalStatus(application.getPoliticalStatus()), normalFont, false, 3); // 列10-12:
+ // 9f+8f+10f=27f
+
+ // 第二行:现工作部门、职称、职务
+ addCell(infoTable, "现工作部门\n(教学系)", normalFont, true, 3); // 列1-3: 6f+9f+5f=20f
+ addCell(infoTable, application.getDepartment(), normalFont, false, 2); // 列4-5: 7f+8f=15f
+ addCell(infoTable, "职称", normalFont, true, 1); // 列6: 9f
+ addCell(infoTable, application.getProfessionalTitle(), normalFont, false, 2); // 列7-8: 8f+9f=17f
+ addCell(infoTable, "职务", normalFont, true, 1); // 列9: 6f
+ addCell(infoTable, application.getPosition(), normalFont, false, 3); // 列10-12: 9f+8f+10f=27f
+
+ // 第三行:申领证件名称、证件号码
+ addCell(infoTable, "申领证件名称", normalFont, true, 1); // 列1: 6f
+ addCell(infoTable, application.getDocumentName(), normalFont, false, 2); // 列2-3: 9f+5f=14f
+ addCell(infoTable, "证件号码", normalFont, true, 2); // 列4-5: 7f+8f=15f
+ addCell(infoTable, application.getDocumentNumber(), normalFont, false, 7); // 列6-12:
+ // 9f+8f+9f+6f+9f+8f+10f=59f
+
+ // 第四行:前往国家或地区(按照附件2标准格式)
+ addCell(infoTable, "前往国家\n或地区", normalFont, true, 1); // 列1: 6f
+
+ PdfPCell destinationCell = new PdfPCell();
+ destinationCell.setColspan(11); // 列2-12: 占据剩余11列
+ destinationCell.setMinimumHeight(60);
+ destinationCell.setVerticalAlignment(Element.ALIGN_TOP);
+ destinationCell.setPadding(0);
+
+ // 创建嵌套表格:5列布局(国家名称 | 出境事由 | 旅游/探亲 | 探望对象关系标签 | 探望对象关系内容)
+ // 列宽精确对齐外层表格的垂直线位置
+ PdfPTable innerTable = new PdfPTable(5);
+ innerTable.setWidthPercentage(100);
+ // 对齐到外层列3|列5|列8|列10|列12的位置: 14f, 15f, 26f, 15f, 18f (总计88f)
+ innerTable.setWidths(new float[] { 14f, 15f, 26f, 15f, 18f });
+
+ // 第一列:国家名称(合并3行)
+ String destination = StringUtils.isNotBlank(application.getDestinationCountry())
+ ? application.getDestinationCountry()
+ : "";
+ PdfPCell countryCell = new PdfPCell(new Phrase(destination, smallFont));
+ countryCell.setRowspan(3);
+ countryCell.setMinimumHeight(60);
+ countryCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
+ countryCell.setHorizontalAlignment(Element.ALIGN_CENTER);
+ countryCell.setPadding(3);
+ innerTable.addCell(countryCell);
+
+ // 第一行:出境事由(合并2行)| 旅游选项(占据右侧3列)
+ // 出境事由标签(合并2行)
+ PdfPCell reasonLabelCell = new PdfPCell(new Phrase("出境事由", smallFont));
+ reasonLabelCell.setRowspan(2);
+ reasonLabelCell.setMinimumHeight(40);
+ reasonLabelCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
+ reasonLabelCell.setHorizontalAlignment(Element.ALIGN_CENTER);
+ innerTable.addCell(reasonLabelCell);
+
+ // 旅游选项框(占据3列)
+ String travelMark = "旅游".equals(application.getExitReason()) ? "√" : "";
+ PdfPCell travelCell = new PdfPCell(new Phrase("旅游( " + travelMark + " )", smallFont));
+ travelCell.setColspan(3);
+ travelCell.setMinimumHeight(20);
+ travelCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
+ travelCell.setHorizontalAlignment(Element.ALIGN_CENTER);
+ innerTable.addCell(travelCell);
+
+ // 第二行:探亲选项 | 探望对象关系标签 | 探望对象关系内容
+ // 探亲选项框
+ String visitMark = "探亲".equals(application.getExitReason()) ? "√" : "";
+ PdfPCell visitCell = new PdfPCell(new Phrase("探亲( " + visitMark + " )", smallFont));
+ visitCell.setMinimumHeight(20);
+ visitCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
+ visitCell.setHorizontalAlignment(Element.ALIGN_CENTER);
+ innerTable.addCell(visitCell);
+
+ // 探望对象关系标签(独立单元格)
+ PdfPCell relationLabelCell = new PdfPCell(new Phrase("探望对象姓名及\n与申请者关系", smallFont));
+ relationLabelCell.setMinimumHeight(20);
+ relationLabelCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
+ relationLabelCell.setHorizontalAlignment(Element.ALIGN_CENTER);
+ relationLabelCell.setPadding(3);
+ innerTable.addCell(relationLabelCell);
+
+ // 探望对象关系内容(独立单元格)
+ String relationship = StringUtils.isNotBlank(application.getVisitRelationship())
+ ? application.getVisitRelationship()
+ : "";
+ PdfPCell relationContentCell = new PdfPCell(new Phrase(relationship, smallFont));
+ relationContentCell.setMinimumHeight(20);
+ relationContentCell.setVerticalAlignment(Element.ALIGN_TOP);
+ relationContentCell.setPadding(3);
+ innerTable.addCell(relationContentCell);
+
+ // 第三行:备注 | 备注内容(国家名称已合并,从第2列开始,备注内容占据3列)
+ PdfPCell remarkLabelCell = new PdfPCell(new Phrase("备注", smallFont));
+ remarkLabelCell.setMinimumHeight(20);
+ remarkLabelCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
+ remarkLabelCell.setHorizontalAlignment(Element.ALIGN_CENTER);
+ remarkLabelCell.setPadding(3);
+ innerTable.addCell(remarkLabelCell);
+
+ String remarks = StringUtils.isNotBlank(application.getRemarks())
+ ? application.getRemarks()
+ : "";
+ PdfPCell remarkValueCell = new PdfPCell(new Phrase(remarks, smallFont));
+ remarkValueCell.setColspan(3); // 占据第3、4、5列
+ remarkValueCell.setMinimumHeight(20);
+ remarkValueCell.setVerticalAlignment(Element.ALIGN_TOP);
+ remarkValueCell.setPadding(3);
+ innerTable.addCell(remarkValueCell);
+
+ destinationCell.addElement(innerTable);
+ infoTable.addCell(destinationCell);
+
+ // 第五行:出国时间
+ addCell(infoTable, "出过(境)时间", normalFont, true, 2); // 列1-2
+ PdfPCell timeCell = new PdfPCell();
+ timeCell.setColspan(10); // 列3-12: 占据剩余10列
+ timeCell.setMinimumHeight(30);
+ timeCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
+
+ String timeText = String.format("拟于 %s 至 %s 出国(境),为期 %d 天。",
+ formatDateChinese(application.getStartDate()),
+ formatDateChinese(application.getEndDate()),
+ application.getDuration() != null ? application.getDuration() : 0);
+ Paragraph timePara = new Paragraph(timeText, smallFont);
+ timeCell.addElement(timePara);
+ infoTable.addCell(timeCell);
+
+ // 第六行:承诺声明(修复断层问题,使用单一段落)
+ PdfPCell promiseCell = new PdfPCell();
+ promiseCell.setColspan(12); // 占据全部12列
+ promiseCell.setMinimumHeight(70);
+ promiseCell.setVerticalAlignment(Element.ALIGN_TOP);
+ promiseCell.setPadding(5);
+
+ // 使用单一段落避免断层,通过Chunk控制对齐
+ Paragraph promisePara = new Paragraph();
+ promisePara.setSpacingAfter(0);
+ promisePara.setSpacingBefore(0);
+
+ // 承诺内容 - 居中部分
+ Chunk promiseText1 = new Chunk("本人承诺,在出国(境)期间遵守当地法律法规和中国的法律法规,结束后如期回国并到学校", smallFont);
+ Chunk promiseText2 = new Chunk("\n报到,如逾期不归,同意作自动离职处理。\n", smallFont);
+ promisePara.add(promiseText1);
+ promisePara.add(promiseText2);
+ promisePara.setAlignment(Element.ALIGN_CENTER);
+ promiseCell.addElement(promisePara);
+
+ // 联系信息和签字 - 左对齐部分
+ Paragraph contactPara = new Paragraph();
+ contactPara.setSpacingAfter(0);
+ contactPara.setSpacingBefore(2);
+ contactPara.setAlignment(Element.ALIGN_LEFT);
+ contactPara.setIndentationLeft(10);
+ contactPara.add(new Chunk("手写签字:", smallFont));
+
+ // 如果有签字,插入签字图片
+ if (StringUtils.isNotBlank(application.getSignature())) {
+ try {
+ Image signatureImage = getImageFromBase64(application.getSignature());
+ if (signatureImage != null) {
+ signatureImage.scaleToFit(60, 20);
+ contactPara.add(new Chunk(signatureImage, 0, -5));
+ }
+ } catch (Exception e) {
+ log.warn("插入申请人签字图片失败: {}", e.getMessage());
+ }
+ }
+
+ contactPara.add(new Chunk(" 联系电话:", smallFont));
+ if (StringUtils.isNotBlank(application.getMobilePhone())) {
+ contactPara.add(new Chunk(application.getMobilePhone(), smallFont));
+ } else {
+ contactPara.add(new Chunk("13333333333", smallFont));
+ }
+ contactPara.add(new Chunk(" 应急联系人及联系电话:", smallFont));
+ if (StringUtils.isNotBlank(application.getEmergencyContact())) {
+ contactPara.add(new Chunk(application.getEmergencyContact(), smallFont));
+ } else {
+ contactPara.add(new Chunk("13333333333", smallFont));
+ }
+ promiseCell.addElement(contactPara);
+
+ // 日期 - 靠右对齐部分(使用申请日期)
+ Paragraph datePara = new Paragraph();
+ datePara.setSpacingAfter(0);
+ datePara.setSpacingBefore(2);
+ datePara.setAlignment(Element.ALIGN_RIGHT);
+ datePara.setIndentationRight(20);
+ // 使用申请时间作为申请人签名日期(优先使用applyTime,其次使用createTime)
+ Date applicationDateTime = application.getApplyTime() != null
+ ? application.getApplyTime()
+ : application.getCreateTime();
+ String applicationDate = applicationDateTime != null
+ ? formatDateChinese(applicationDateTime)
+ : "年 月 日";
+ datePara.add(new Chunk(applicationDate, smallFont));
+ promiseCell.addElement(datePara);
+
+ infoTable.addCell(promiseCell);
+
+ document.add(infoTable);
+
+ // 3. 审批意见表格(紧贴个人信息表格,无间隔)
+ PdfPTable approvalTable = new PdfPTable(2);
+ approvalTable.setWidthPercentage(100);
+ approvalTable.setWidths(new float[] { 20f, 80f });
+ approvalTable.setSpacingBefore(0); // 设置为0,与上方表格紧密连接
+
+ // 部门(教学系)意见
+ addApprovalRowWithSignature(approvalTable, "部门\n(教学\n系)\n意见",
+ approvalHistory, "1", normalFont, smallFont);
+
+ // 人事(组织)处审核意见
+ addApprovalRowWithSignature(approvalTable, "人事\n(组\n织)处\n审核\n意见",
+ approvalHistory, "2", normalFont, smallFont);
+
+ // 纪委意见
+ addApprovalRowWithSignature(approvalTable, "纪委\n意见",
+ approvalHistory, "3", normalFont, smallFont);
+
+ // 学院分管领导意见(独立行)
+ addApprovalRowWithLeaderSignature(approvalTable, "学院分\n管领导\n意见",
+ approvalHistory, "4", normalFont, smallFont);
+
+ // 学院党委书记意见(独立行)
+ addApprovalRowWithSignature(approvalTable, "学院党\n委书记\n意见",
+ approvalHistory, "5", normalFont, smallFont);
+
+ document.add(approvalTable);
+
+ // 4. 备注说明(移除空段落,直接连接)
+ Paragraph note = new Paragraph();
+ note.setSpacingBefore(5); // 只留很小的间距
+ note.add(new Chunk("备注:1. 因私出境时间,除特殊原因外,原则上应为假期;\n", smallFont));
+ note.add(new Chunk(" 2. 新申办证件名,本表证件号码一栏不填。", smallFont));
+ document.add(note);
+
+ } finally {
+ document.close();
+ }
+ }
+
+ /**
+ * 添加表格单元格
+ */
+ private void addCell(PdfPTable table, String text, Font font, boolean isHeader, int colspan) {
+ PdfPCell cell = new PdfPCell(new Phrase(text, font));
+ cell.setColspan(colspan);
+ cell.setMinimumHeight(25);
+ cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
+ cell.setHorizontalAlignment(Element.ALIGN_CENTER);
+ // 移除灰色背景,所有单元格统一白色背景
+ // if (isHeader) {
+ // cell.setBackgroundColor(BaseColor.LIGHT_GRAY);
+ // }
+ table.addCell(cell);
+ }
+
+ /**
+ * 添加审批意见行(带签字显示)
+ */
+ private void addApprovalRowWithSignature(PdfPTable table, String label,
+ List approvalHistory, String level, Font labelFont, Font contentFont) {
+ PdfPCell labelCell = new PdfPCell(new Phrase(label, labelFont));
+ labelCell.setMinimumHeight(50);
+ labelCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
+ labelCell.setHorizontalAlignment(Element.ALIGN_CENTER);
+ table.addCell(labelCell);
+
+ PdfPCell opinionCell = new PdfPCell();
+ opinionCell.setMinimumHeight(50);
+ opinionCell.setVerticalAlignment(Element.ALIGN_TOP);
+ opinionCell.setPadding(5);
+
+ Paragraph para = new Paragraph();
+ String opinion = getApprovalOpinion(approvalHistory, level);
+ para.add(new Chunk(opinion, contentFont));
+ para.add(new Chunk("\n\n负责人签字:", contentFont));
+
+ // 添加签字图片
+ String signature = getApprovalSignature(approvalHistory, level);
+ if (StringUtils.isNotBlank(signature)) {
+ try {
+ Image signImage = getImageFromBase64(signature);
+ if (signImage != null) {
+ signImage.scaleToFit(60, 20);
+ para.add(new Chunk(signImage, 0, -5));
+ }
+ } catch (Exception e) {
+ log.warn("插入审批人签字图片失败: {}", e.getMessage());
+ }
+ }
+
+ // 日期靠右(使用审批日期)
+ opinionCell.addElement(para);
+ Paragraph datePara = new Paragraph();
+ datePara.setAlignment(Element.ALIGN_RIGHT);
+ datePara.setIndentationRight(10);
+ // 获取审批日期
+ Date approvalDate = getApprovalDate(approvalHistory, level);
+ String dateText = approvalDate != null ? formatDateChinese(approvalDate) : "年 月 日";
+ datePara.add(new Chunk(dateText, contentFont));
+ opinionCell.addElement(datePara);
+
+ table.addCell(opinionCell);
+ }
+
+ /**
+ * 添加学院分管领导审批行(使用"领导签字"并包含"学院"文字)
+ */
+ private void addApprovalRowWithLeaderSignature(PdfPTable table, String label,
+ List approvalHistory, String level, Font labelFont, Font contentFont) {
+ PdfPCell labelCell = new PdfPCell(new Phrase(label, labelFont));
+ labelCell.setMinimumHeight(50);
+ labelCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
+ labelCell.setHorizontalAlignment(Element.ALIGN_CENTER);
+ table.addCell(labelCell);
+
+ PdfPCell opinionCell = new PdfPCell();
+ opinionCell.setMinimumHeight(50);
+ opinionCell.setVerticalAlignment(Element.ALIGN_TOP);
+ opinionCell.setPadding(5);
+
+ Paragraph para = new Paragraph();
+ String opinion = getApprovalOpinion(approvalHistory, level);
+ para.add(new Chunk(opinion, contentFont));
+ para.add(new Chunk("\n\n领导签字:", contentFont));
+
+ // 添加签字图片
+ String signature = getApprovalSignature(approvalHistory, level);
+ if (StringUtils.isNotBlank(signature)) {
+ try {
+ Image signImage = getImageFromBase64(signature);
+ if (signImage != null) {
+ signImage.scaleToFit(60, 20);
+ para.add(new Chunk(signImage, 0, -5));
+ }
+ } catch (Exception e) {
+ log.warn("插入分管领导签字图片失败: {}", e.getMessage());
+ }
+ }
+
+ // 日期靠右(使用审批日期)
+ opinionCell.addElement(para);
+ Paragraph datePara = new Paragraph();
+ datePara.setAlignment(Element.ALIGN_RIGHT);
+ datePara.setIndentationRight(10);
+ // 获取审批日期
+ Date approvalDate = getApprovalDate(approvalHistory, level);
+ String dateText = approvalDate != null ? formatDateChinese(approvalDate) : "年 月 日";
+ datePara.add(new Chunk(dateText, contentFont));
+ opinionCell.addElement(datePara);
+
+ table.addCell(opinionCell);
+ }
+
+ /**
+ * 获取指定级别的审批意见
+ */
+ private String getApprovalOpinion(List approvalHistory, String level) {
+ if (approvalHistory == null || approvalHistory.isEmpty()) {
+ return "";
+ }
+
+ return approvalHistory.stream()
+ .filter(a -> level.equals(a.getApprovalLevel()))
+ .findFirst()
+ .map(a -> {
+ String opinion = StringUtils.isNotBlank(a.getApprovalOpinion())
+ ? a.getApprovalOpinion()
+ : ("1".equals(a.getApprovalStatus()) ? "同意" : "");
+ return opinion;
+ })
+ .orElse("");
+ }
+
+ /**
+ * 格式化日期
+ */
+ private String formatDate(Date date) {
+ if (date == null) {
+ return "";
+ }
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+ return sdf.format(date);
+ }
+
+ /**
+ * 格式化日期为中文格式(yyyy 年 MM 月 dd 日)
+ */
+ private String formatDateChinese(Date date) {
+ if (date == null) {
+ return "";
+ }
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy 年 MM 月 dd 日");
+ return sdf.format(date);
+ }
+
+ /**
+ * 格式化出生日期(只显示年月)
+ */
+ private String formatBirthDate(Date date) {
+ if (date == null) {
+ return "";
+ }
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月");
+ return sdf.format(date);
+ }
+
+ /**
+ * 获取指定级别的审批签字
+ */
+ private String getApprovalSignature(List approvalHistory, String level) {
+ if (approvalHistory == null || approvalHistory.isEmpty()) {
+ return null;
+ }
+
+ return approvalHistory.stream()
+ .filter(a -> level.equals(a.getApprovalLevel()))
+ .findFirst()
+ .map(RsOverseasApprovalVo::getApprovalSignature)
+ .orElse(null);
+ }
+
+ /**
+ * 获取指定级别的审批日期
+ */
+ private Date getApprovalDate(List approvalHistory, String level) {
+ if (approvalHistory == null || approvalHistory.isEmpty()) {
+ return null;
+ }
+
+ return approvalHistory.stream()
+ .filter(a -> level.equals(a.getApprovalLevel()))
+ .findFirst()
+ .map(RsOverseasApprovalVo::getApprovalTime)
+ .orElse(null);
+ }
+
+ /**
+ * 从Base64字符串创建Image对象
+ */
+ private Image getImageFromBase64(String base64String) {
+ if (StringUtils.isBlank(base64String)) {
+ return null;
+ }
+
+ try {
+ // 处理data URL格式:data:image/png;base64,xxxxx
+ String base64Data = base64String;
+ if (base64String.contains(",")) {
+ base64Data = base64String.substring(base64String.indexOf(",") + 1);
+ }
+
+ // Base64解码
+ byte[] imageBytes = java.util.Base64.getDecoder().decode(base64Data);
+
+ // 创建Image对象
+ Image image = Image.getInstance(imageBytes);
+ return image;
+ } catch (Exception e) {
+ log.error("从Base64创建图片失败: {}", e.getMessage());
+ return null;
+ }
+ }
}
diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasApplicationMapper.xml b/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasApplicationMapper.xml
index 223cc07d..fe8e0e65 100644
--- a/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasApplicationMapper.xml
+++ b/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasApplicationMapper.xml
@@ -121,9 +121,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
ELSE oa.purpose
END as purpose_text,
CASE oa.approval_level
- WHEN '1' THEN '部门主管'
- WHEN '2' THEN '分管领导'
- WHEN '3' THEN '校领导'
+ WHEN '1' THEN '部门负责人'
+ WHEN '2' THEN '人事处'
+ WHEN '3' THEN '纪委'
+ WHEN '4' THEN '学院分管领导'
+ WHEN '5' THEN '学院党委书记'
ELSE '未知级别'
END as approval_level_text,
CASE oa.gender
diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasApprovalMapper.xml b/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasApprovalMapper.xml
index 6fec3346..3613cf7c 100644
--- a/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasApprovalMapper.xml
+++ b/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasApprovalMapper.xml
@@ -12,6 +12,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+
@@ -39,16 +40,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
select oa.approval_id, oa.application_id, oa.approver_id, oa.approver_name, oa.approval_level,
- oa.approval_status, oa.approval_opinion, oa.approval_time, oa.tenant_id,
+ oa.approval_status, oa.approval_opinion, oa.approval_signature, oa.approval_time, oa.tenant_id,
oa.create_by, oa.create_time, oa.update_by, oa.update_time, oa.deleted,
app.applicant_id, app.applicant_name, app.department, app.purpose, app.destination,
app.start_date, app.end_date, app.duration,
CASE oa.approval_level
- WHEN '1' THEN '部门主管'
- WHEN '2' THEN '分管领导'
- WHEN '3' THEN '校领导'
+ WHEN '1' THEN '部门负责人'
+ WHEN '2' THEN '人事处'
+ WHEN '3' THEN '纪委'
+ WHEN '4' THEN '学院分管领导'
+ WHEN '5' THEN '学院党委书记'
ELSE '未知级别'
END as approval_level_text,
CASE oa.approval_status
@@ -169,9 +172,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
app.duration,
-- 文本转换
CASE oa.approval_level
- WHEN '1' THEN '部门主管'
- WHEN '2' THEN '分管领导'
- WHEN '3' THEN '校领导'
+ WHEN '1' THEN '部门负责人'
+ WHEN '2' THEN '人事处'
+ WHEN '3' THEN '纪委'
+ WHEN '4' THEN '学院分管领导'
+ WHEN '5' THEN '学院党委书记'
ELSE '未知级别'
END as approval_level_text,
CASE oa.approval_status
diff --git a/web/src/api/rs/overseas.ts b/web/src/api/rs/overseas.ts
index 52d718ce..f6f2be5d 100644
--- a/web/src/api/rs/overseas.ts
+++ b/web/src/api/rs/overseas.ts
@@ -157,6 +157,7 @@ export interface ApprovalRequest {
applicationId: number | string;
approvalStatus: string;
approvalOpinion?: string;
+ approvalSignature?: string; // 审批签字(base64图片)
taskId?: string; // 🔴 新增:工作流任务ID,支持标准工作流模式
}
@@ -885,6 +886,7 @@ export interface ItineraryChangeApprovalRequest {
changeId: number | string;
approved: boolean; // true=通过,false=拒绝
opinion?: string; // 审批意见
+ signature?: string; // 审批签字(base64图片)
}
// 查询行程变更列表
@@ -1030,3 +1032,54 @@ export function getHrApprovers(applicantId: string | number): Promise<{ data: nu
method: 'get'
});
}
+
+// ==================== 出国申请进度查询(监控)相关API ====================
+
+// 审批监控查询参数类型
+export interface OverseasMonitorQuery {
+ pageNum: number;
+ pageSize: number;
+ applicantName?: string;
+ department?: string;
+ destination?: string;
+ approvalLevel?: string;
+ currentApprover?: string;
+ status?: string;
+ applyTimeStart?: string;
+ applyTimeEnd?: string;
+}
+
+// 查询待审批列表(监控)
+export function getPendingList(query: OverseasMonitorQuery) {
+ return request({
+ url: '/rs/overseas/monitor/pending/list',
+ method: 'get',
+ params: query
+ });
+}
+
+// 查询已完成审批列表(监控)
+export function getCompletedList(query: OverseasMonitorQuery) {
+ return request({
+ url: '/rs/overseas/monitor/completed/list',
+ method: 'get',
+ params: query
+ });
+}
+
+// 查询申请详情(监控)
+export function getDetail(applicationId: string | number) {
+ return request({
+ url: `/rs/overseas/monitor/detail/${applicationId}`,
+ method: 'get'
+ });
+}
+
+// 下载审批表PDF
+export function downloadApprovalPdf(applicationId: string | number) {
+ return request({
+ url: `/rs/overseas/monitor/downloadApprovalPdf/${applicationId}`,
+ method: 'get',
+ responseType: 'blob'
+ });
+}
diff --git a/web/src/components/SignaturePad/index.vue b/web/src/components/SignaturePad/index.vue
index fd468367..febc93f5 100644
--- a/web/src/components/SignaturePad/index.vue
+++ b/web/src/components/SignaturePad/index.vue
@@ -1,278 +1,247 @@
-
-
-
+
+
-
请在此处签名
+ :width="width"
+ :height="height"
+ @mousedown="startDrawing"
+ @mousemove="draw"
+ @mouseup="stopDrawing"
+ @mouseleave="stopDrawing"
+ @touchstart="handleTouch"
+ @touchmove="handleTouch"
+ @touchend="stopDrawing"
+ />
+
+
+ 清除
+ 保存
diff --git a/web/src/views/rs/overseas/approval.vue b/web/src/views/rs/overseas/approval.vue
index 0b0cccdb..0d13724a 100644
--- a/web/src/views/rs/overseas/approval.vue
+++ b/web/src/views/rs/overseas/approval.vue
@@ -325,11 +325,23 @@
-
+
+
+
+
+
+ 请在上方签字板中手写签名
+
@@ -360,6 +372,7 @@ import { ElMessage, ElMessageBox } from 'element-plus';
import { Document, Warning, Paperclip, View, Download } from '@element-plus/icons-vue';
import animate from '@/animate';
import request from '@/utils/request';
+import SignaturePad from '@/components/SignaturePad/index.vue';
import { getToken } from '@/utils/auth';
// 响应式数据
@@ -400,7 +413,8 @@ const state = reactive({
approvalForm: {
applicationId: '',
approvalStatus: '1',
- approvalOpinion: ''
+ approvalOpinion: '',
+ approvalSignature: '' // 审批签字
} as ApprovalRequest,
approvalRules: {
approvalOpinion: [{ required: true, message: '请输入审批意见', trigger: 'blur' }]
@@ -550,6 +564,7 @@ const handleApprovalPass = (row: OverseasApplicationVO) => {
state.approvalForm.applicationId = row.applicationId;
state.approvalForm.approvalStatus = '1';
state.approvalForm.approvalOpinion = '';
+ state.approvalForm.approvalSignature = ''; // 清空签字
state.approvalDialog.title = '通过';
state.approvalDialog.visible = true;
};
@@ -559,6 +574,7 @@ const handleApprovalReject = (row: OverseasApplicationVO) => {
state.approvalForm.applicationId = row.applicationId;
state.approvalForm.approvalStatus = '2';
state.approvalForm.approvalOpinion = '';
+ state.approvalForm.approvalSignature = ''; // 清空签字
state.approvalDialog.title = '拒绝';
state.approvalDialog.visible = true;
};
@@ -586,7 +602,8 @@ const cancelApproval = () => {
state.approvalForm = {
applicationId: '',
approvalStatus: '1',
- approvalOpinion: ''
+ approvalOpinion: '',
+ approvalSignature: ''
};
};
@@ -913,4 +930,12 @@ const handlePreviewStampedFile = async (data: OverseasApplicationVO) => {
font-size: 12px;
}
}
+
+/* 签字样式 */
+.signature-tip {
+ margin-top: 8px;
+ font-size: 12px;
+ color: #909399;
+ text-align: center;
+}
diff --git a/web/src/views/rs/overseas/itinerary-change-approval.vue b/web/src/views/rs/overseas/itinerary-change-approval.vue
index 4aab2bc1..2d8403ea 100644
--- a/web/src/views/rs/overseas/itinerary-change-approval.vue
+++ b/web/src/views/rs/overseas/itinerary-change-approval.vue
@@ -257,7 +257,7 @@
-
+
@@ -268,6 +268,18 @@
+
+
+
+
+ 请在上方签字板中手写签名
+
@@ -295,6 +307,7 @@ import {
type ItineraryChangeVO,
type ItineraryChangeApprovalRequest
} from '@/api/rs/overseas';
+import SignaturePad from '@/components/SignaturePad/index.vue';
// ==================== 响应式数据 ====================
@@ -346,7 +359,8 @@ const state = reactive({
approvalForm: {
changeId: 0,
approved: true, // true=通过,false=拒绝
- opinion: ''
+ opinion: '',
+ signature: '' // 审批签字
} as ItineraryChangeApprovalRequest,
approvalLoading: false,
@@ -549,4 +563,12 @@ const getStatusTagType = (status: string): 'primary' | 'success' | 'info' | 'war
.tab-content {
min-height: 400px;
}
+
+/* 签字样式 */
+.signature-tip {
+ margin-top: 8px;
+ font-size: 12px;
+ color: #909399;
+ text-align: center;
+}