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 @@ 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 @@ - + + + + + +

请在上方签字板中手写签名
+