diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/mapper/RsOverseasApplicationMapper.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/mapper/RsOverseasApplicationMapper.java index 9a1fb7aa..d371ea95 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/mapper/RsOverseasApplicationMapper.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/mapper/RsOverseasApplicationMapper.java @@ -101,4 +101,47 @@ public interface RsOverseasApplicationMapper extends BaseMapperPlus selectUserIdsByPermission(String permissionCode); + + /** + * 根据用户ID查询其拥有的出国审批权限列表 + * + * 查询逻辑: + * 1. 通过 sys_user_role 查找用户的角色 + * 2. 通过 sys_role_menu 查找角色的菜单 + * 3. 通过 sys_menu 查找菜单的权限标识 + * 4. 过滤出出国审批相关的权限(rs:overseas:approve%) + * + * @param userId 用户ID + * @return 审批权限标识列表(如:["rs:overseas:approve2", "rs:overseas:approve3"]) + */ + List selectApprovalPermissionsByUserId(Long userId); + + /** + * 🔴 新增:查询待审批申请列表(支持部门过滤) + * + * 查询逻辑: + * 1. 关联 sys_user 表获取申请人的部门信息 + * 2. 按审批级别过滤(approvalLevel IN (...) ) + * 3. 按状态过滤(status = '0' 待审核) + * 4. 按部门过滤(deptId != null 时,u.dept_id = #{deptId}) + * + * @param approvalLevels 审批级别列表,例如:["1", "2"] + * @param deptId 部门ID(部门负责人需要),为null则不过滤部门 + * @return 待审批申请列表 + */ + List selectPendingApplications(List approvalLevels, Long deptId); } \ No newline at end of file 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 5c8d43ed..ed3bf9b6 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 @@ -87,29 +87,40 @@ public class RsOverseasApplicationServiceImpl extends ServiceImpl queryPageList(RsOverseasApplicationQuery query) { - // 🔴 新增:部门负责人权限过滤逻辑 + // 🔴 新增:多级审批权限过滤逻辑 Long currentUserId = LoginHelper.getUserId(); // 1. 判断是否是rs-admin管理员(管理员可以看所有数据) boolean isAdmin = cn.dev33.satoken.stp.StpUtil.hasRole("rs-admin"); if (!isAdmin) { - // 2. 判断是否是部门负责人(拥有rs:overseas:approve1权限) - boolean isDeptHead = cn.dev33.satoken.stp.StpUtil.hasPermission("rs:overseas:approve1"); + // 2. 判断是否有高级别审批权限(人事处及以上:approve2/3/4/5) + boolean hasHigherApprovalPermission = cn.dev33.satoken.stp.StpUtil.hasPermission("rs:overseas:approve2") || // 人事处 + cn.dev33.satoken.stp.StpUtil.hasPermission("rs:overseas:approve3") || // 纪委 + cn.dev33.satoken.stp.StpUtil.hasPermission("rs:overseas:approve4") || // 学院分管领导 + cn.dev33.satoken.stp.StpUtil.hasPermission("rs:overseas:approve5"); // 学院党委书记 - if (isDeptHead) { - // 🔴 部门负责人:只能看到本部门员工提交的申请 - SysUserVo currentUser = userService.selectUserById(currentUserId); - if (currentUser != null && currentUser.getDeptId() != null) { - query.setApplicantDeptId(currentUser.getDeptId()); - log.info("🔒 [部门权限过滤] 用户: {}, 部门ID: {}, 只能查看本部门申请", - currentUser.getUserName(), currentUser.getDeptId()); - } + if (hasHigherApprovalPermission) { + // 🔴 人事处及以上:可以看到所有申请(不按部门过滤) + log.info("✅ [高级审批权限] 用户ID: {}, 可以查看所有申请", currentUserId); } else { - // 3. 普通用户:只能看到自己提交的申请 - if (query.getApplicantId() == null) { - query.setApplicantId(currentUserId); - log.info("🔒 [个人权限过滤] 用户ID: {}, 只能查看自己的申请", currentUserId); + // 3. 判断是否是部门负责人(只有 rs:overseas:approve1 权限) + boolean isDeptHead = cn.dev33.satoken.stp.StpUtil.hasPermission("rs:overseas:approve1"); + + if (isDeptHead) { + // 🔴 纯部门负责人:只能看到本部门员工提交的申请 + SysUserVo currentUser = userService.selectUserById(currentUserId); + if (currentUser != null && currentUser.getDeptId() != null) { + query.setApplicantDeptId(currentUser.getDeptId()); + log.info("🔒 [部门权限过滤] 用户: {}, 部门ID: {}, 只能查看本部门申请", + currentUser.getUserName(), currentUser.getDeptId()); + } + } else { + // 4. 普通用户:只能看到自己提交的申请 + if (query.getApplicantId() == null) { + query.setApplicantId(currentUserId); + log.info("🔒 [个人权限过滤] 用户ID: {}, 只能查看自己的申请", currentUserId); + } } } } else { @@ -220,46 +231,92 @@ public class RsOverseasApplicationServiceImpl extends ServiceImpl queryPendingByCurrentUser() { Long currentUserId = LoginHelper.getUserId(); - log.info("查询当前用户待审批申请,用户ID: {}", currentUserId); - - // 获取当前用户信息 String currentUserName = LoginHelper.getUsername(); - log.info("当前用户名: {}", currentUserName); - List applications = baseMapper.selectList( - new LambdaQueryWrapper() - .eq(RsOverseasApplication::getStatus, "0") // 待审核状态 - .eq(RsOverseasApplication::getCurrentApprover, currentUserName) // 使用当前审批人姓名 - .orderByDesc(RsOverseasApplication::getApplyTime)); + log.info("🔍 查询用户待审批申请 - 用户ID: {}, 用户名: {}", currentUserId, currentUserName); - // 转换为VO对象 - return applications.stream() - .map(this::convertToVo) - .collect(Collectors.toList()); + // 1. 获取当前用户的审批级别列表(基于权限动态查询) + List approvalLevels = getApprovalLevelsByUser(currentUserId); + + if (approvalLevels == null || approvalLevels.isEmpty()) { + log.warn("❌ 用户 {} 没有任何出国审批权限,返回空列表", currentUserName); + return new ArrayList<>(); + } + + log.info("✅ 用户 {} 拥有审批级别: {}", currentUserName, approvalLevels); + + // 2. 🔴 新逻辑:对每个级别单独查询并合并(方案1) + List result = new ArrayList<>(); + + // 遍历每个审批级别 + for (String level : approvalLevels) { + Long deptIdForFilter = null; + + // 🔴 关键:只对 level=1 进行部门过滤 + if ("1".equals(level)) { + // 获取当前用户的部门ID + SysUserVo currentUser = userService.selectUserById(currentUserId); + if (currentUser != null && currentUser.getDeptId() != null) { + deptIdForFilter = currentUser.getDeptId(); + log.info("🔒 [部门权限过滤] 级别1,用户: {}, 部门ID: {}, 只查看本部门待审批", + currentUserName, deptIdForFilter); + } else { + log.warn("⚠️ 用户 {} 是部门负责人但没有部门信息,无法过滤", currentUserName); + } + } else { + // level=2, 3, 4, 5 不进行部门过滤 + log.info("✅ [全校权限] 级别{}, 用户 {}, 查看全校待审批", + level, currentUserName); + } + + // 3. 查询该级别的待审批申请(单个级别) + List levelApplications = baseMapper.selectPendingApplications( + Arrays.asList(level), // 🔴 关键:只查询单个级别 + deptIdForFilter // level=1时有值,其他为null + ); + + log.info("📋 级别{} 查询到 {} 条待审批申请", level, levelApplications.size()); + + // 4. 转换为VO对象并添加到结果列表 + result.addAll(levelApplications.stream() + .map(this::convertToVo) + .collect(Collectors.toList())); + } + + log.info("📋 用户 {} 总共查询到 {} 条待审批申请", currentUserName, result.size()); + + // 5. 返回合并后的结果 + return result; } /** * 转换实体为VO对象 + * 🔴 修复:使用Mapper查询完整的VO对象,包含字典翻译等扩展信息 */ private RsOverseasApplicationVo convertToVo(RsOverseasApplication entity) { if (entity == null) { return null; } - RsOverseasApplicationVo vo = new RsOverseasApplicationVo(); - // 使用BeanUtils复制属性(如果有的话)或者手动复制关键属性 - vo.setApplicationId(entity.getApplicationId()); - vo.setSerialNumber(entity.getSerialNumber()); - vo.setApplicantId(entity.getApplicantId()); - vo.setApplicantName(entity.getApplicantName()); - vo.setStatus(entity.getStatus()); - vo.setApplyTime(entity.getApplyTime()); - // 复制其他必要的字段... - return vo; + // 🔴 使用Mapper的详细查询方法,获取完整的VO对象(包含字典翻译) + return baseMapper.selectOverseasApplicationById(entity.getApplicationId()); } /** @@ -437,7 +494,13 @@ public class RsOverseasApplicationServiceImpl extends ServiceImpl 0; - log.info("✅ [出国申请提交] 提交成功, applicationId: {}, 申请人: {}", + log.info("✅ [出国申请提交] 提交成功 - applicationId: {}, 申请人: {}, 审批级别: 1", applicationId, application.getApplicantName()); return updated; @@ -530,19 +596,18 @@ public class RsOverseasApplicationServiceImpl extends ServiceImpl 0; } @@ -1181,58 +1247,9 @@ public class RsOverseasApplicationServiceImpl extends ServiceImpl roles = roleService.selectRoleList(roleQuery); - if (roles.isEmpty()) { - log.error("未找到审批角色: {}", roleKey); - return null; - } - SysRoleVo role = roles.get(0); - - // 获取角色下的用户(取第一个作为审批人) - SysUserBo userQuery = new SysUserBo(); - userQuery.setRoleId(role.getRoleId()); - PageQuery pageQuery2 = new PageQuery(); - pageQuery2.setPageSize(1000); // 设置较大的页面大小以获取所有用户 - List users = userService.selectAllocatedList(userQuery, pageQuery2).getRows(); - if (users.isEmpty()) { - log.warn("审批角色 {} 下没有用户", roleKey); - return null; - } - - // 返回第一个用户作为审批人 - SysUserVo approver = users.get(0); - log.info("获取到第{}级审批人: {} ({})", approvalLevel, approver.getUserName(), approver.getNickName()); - return approver.getUserId(); - - } catch (Exception e) { - log.error("获取第{}级审批人失败", approvalLevel, e); - return null; - } - } + // 🗑️ getNextApprover() 方法已删除 + // 原因:采用动态审批人方案后,不再需要预先指定固定的审批人ID + // 新方案:待审批列表通过 getApprovalLevelsByUser() 动态查询 /** * 根据审批级别获取审批环节描述 @@ -1257,6 +1274,69 @@ public class RsOverseasApplicationServiceImpl extends ServiceImpl getApprovalLevelsByUser(Long userId) { + try { + List approvalLevels = new ArrayList<>(); + + // 🔴 修复:直接使用Sa-Token API检查权限,避免数据库查询问题 + // 检查5个审批级别的权限(1-5级) + String[] levels = { "1", "2", "3", "4", "5" }; + for (String level : levels) { + String permission = "rs:overseas:approve" + level; + if (StpUtil.hasPermission(permission)) { + approvalLevels.add(level); + log.info("✅ 用户 {} 拥有第{}级审批权限: {}", userId, level, permission); + } + } + + // 检查通用审批权限(超级管理员) + if (StpUtil.hasPermission("rs:overseas:approve")) { + // 通用审批权限可以审批所有级别 + log.info("✅ 用户 {} 拥有通用审批权限,可审批所有级别", userId); + approvalLevels.clear(); + approvalLevels.addAll(Arrays.asList(levels)); + } + + if (approvalLevels.isEmpty()) { + log.warn("❌ 用户 {} 没有任何出国审批权限", userId); + } else { + log.info("📋 用户 {} 可审批的级别: {}", userId, approvalLevels); + } + + return approvalLevels; + + } catch (Exception e) { + log.error("❌ 获取用户 {} 的审批级别失败", userId, e); + return new ArrayList<>(); + } + } + + /** + * 🆕 根据审批级别获取对应的权限标识 + * + * @param approvalLevel 审批级别(2-5) + * @return 权限标识,例如:"rs:overseas:approve2" + */ + private String getPermissionByLevel(String approvalLevel) { + if (approvalLevel == null || approvalLevel.isEmpty()) { + return null; + } + return "rs:overseas:approve" + approvalLevel; + } + // 工作流监听器已移至独立的 OverseasApplicationWorkflowListener 类中 // 状态更新方法已移至独立的 OverseasApplicationWorkflowListener 类中 diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalServiceImpl.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalServiceImpl.java index 0ba62d26..0742d3b3 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalServiceImpl.java @@ -246,19 +246,7 @@ public class RsOverseasApprovalServiceImpl implements IRsOverseasApprovalService return; } - // 🔧 优先检查部门负责人权限(避免被rs-admin角色覆盖) - boolean isDeptApprover = isDeptApprover(currentUserId); - log.info("✓ 部门负责人检查结果: {}", isDeptApprover); - - if (isDeptApprover) { - // 部门负责人只能看到本部门的所有申请(不限制审批人) - query.setDeptId(currentDeptId); - log.info("→ 权限结果:部门负责人,设置部门过滤 deptId={}", currentDeptId); - log.info("==================== 权限过滤结束 ===================="); - return; - } - - // 人事管理员可以看到所有申请(仅当不是部门负责人时) + // 人事管理员可以看到所有申请 boolean hasAdminRole = StpUtil.hasRole("rs-admin"); log.info("✓ 角色检查 [rs-admin]: {}", hasAdminRole); if (hasAdminRole) { @@ -267,7 +255,28 @@ public class RsOverseasApprovalServiceImpl implements IRsOverseasApprovalService return; // 全局权限 } - // 其他角色只能看到分配给自己的审批 + // 🔧 检查部门负责人权限(但不能与其他审批权限冲突) + boolean isDeptApprover = isDeptApprover(currentUserId); + log.info("✓ 部门负责人检查结果: {}", isDeptApprover); + + // 检查是否有其他审批权限(人事处、纪委等) + boolean hasOtherApprovePermission = StpUtil.hasPermission("rs:overseas:approve2") || // 人事处 + StpUtil.hasPermission("rs:overseas:approve3") || // 纪委 + StpUtil.hasPermission("rs:overseas:approve4") || // 学院分管领导 + StpUtil.hasPermission("rs:overseas:approve5"); // 学院党委书记 + log.info("✓ 其他审批权限检查: {}", hasOtherApprovePermission); + + if (isDeptApprover && !hasOtherApprovePermission) { + // 纯部门负责人:只能看到本部门且分配给自己的申请 + query.setDeptId(currentDeptId); + query.setApproverId(currentUserId); + log.info("→ 权限结果:部门负责人,设置部门过滤 deptId={} 和审批人过滤 approverId={}", + currentDeptId, currentUserId); + log.info("==================== 权限过滤结束 ===================="); + return; + } + + // 其他审批人(包括有其他审批权限的用户)只能看到分配给自己的审批 query.setApproverId(currentUserId); log.info("→ 权限结果:普通审批人,设置审批人过滤 approverId={}", currentUserId); log.info("==================== 权限过滤结束 ===================="); @@ -502,23 +511,33 @@ public class RsOverseasApprovalServiceImpl implements IRsOverseasApprovalService return; } - // 🔧 优先检查部门负责人权限(避免被rs-admin角色覆盖) - boolean isDeptApprover = isDeptApprover(currentUserId); - log.info("用户 {} 部门负责人检查结果: {}", currentUsername, isDeptApprover); - - if (isDeptApprover) { - log.info("用户 {} 是部门负责人,设置部门过滤: {}", currentUsername, currentDeptId); - query.setDeptId(currentDeptId); - return; - } - - // 人事管理员可以看到所有申请(仅当不是部门负责人时) + // 人事管理员可以看到所有申请 if (StpUtil.hasRole("rs-admin")) { log.info("用户 {} 具有rs-admin角色,不设置过滤条件", currentUsername); return; } - // 其他角色只能看到分配给自己的审批 + // 🔧 检查部门负责人权限(但不能与其他审批权限冲突) + boolean isDeptApprover = isDeptApprover(currentUserId); + log.info("用户 {} 部门负责人检查结果: {}", currentUsername, isDeptApprover); + + // 检查是否有其他审批权限(人事处、纪委等) + boolean hasOtherApprovePermission = StpUtil.hasPermission("rs:overseas:approve2") || // 人事处 + StpUtil.hasPermission("rs:overseas:approve3") || // 纪委 + StpUtil.hasPermission("rs:overseas:approve4") || // 学院分管领导 + StpUtil.hasPermission("rs:overseas:approve5"); // 学院党委书记 + log.info("用户 {} 其他审批权限检查: {}", currentUsername, hasOtherApprovePermission); + + if (isDeptApprover && !hasOtherApprovePermission) { + // 纯部门负责人:只能看到本部门且分配给自己的申请 + log.info("用户 {} 是纯部门负责人,设置部门过滤: {} 和审批人过滤: {}", + currentUsername, currentDeptId, currentUserId); + query.setDeptId(currentDeptId); // 只显示本部门员工的申请 + query.setApproverId(currentUserId); // 只显示当前审批人是自己的申请(待审批) + return; + } + + // 其他审批人(包括有其他审批权限的用户)只能看到分配给自己的审批 log.info("用户 {} 是普通审批人,设置审批人过滤: {}", currentUsername, currentUserId); query.setApproverId(currentUserId); } 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 a341f396..223cc07d 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 @@ -551,4 +551,55 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND u.deleted = '0' + + + + + + + + + \ No newline at end of file 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 3d7a5148..6fec3346 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 @@ -232,22 +232,22 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" UNION ALL - -- 查询待审批的申请(有审批记录且状态为待审核的) + -- 查询待审批的申请(使用current_approver_id查询当前审批人的待办) SELECT - oa.approval_id, + NULL as approval_id, -- 待审批申请没有审批记录ID app.application_id, - oa.approver_id, - oa.approver_name, - oa.approval_level, - oa.approval_status, - oa.approval_opinion, - oa.approval_time, - oa.tenant_id, - oa.create_by, - oa.create_time, - oa.update_by, - oa.update_time, - oa.deleted, + app.current_approver_id as approver_id, -- 🔴 修复:使用申请表的当前审批人ID + app.current_approver as approver_name, -- 🔴 修复:使用申请表的当前审批人名称 + app.approval_level, -- 🔴 修复:使用申请表的审批级别 + '0' as approval_status, -- 待审核状态 + NULL as approval_opinion, + NULL as approval_time, + app.tenant_id, + app.create_by, + app.create_time, + app.update_by, + app.update_time, + app.deleted, -- 申请信息字段 app.applicant_id, app.applicant_name, @@ -258,10 +258,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" app.end_date, app.duration, -- 文本转换 - CASE oa.approval_level - WHEN '1' THEN '部门主管' - WHEN '2' THEN '分管领导' - WHEN '3' THEN '校领导' + CASE app.approval_level + WHEN '1' THEN '部门负责人' + WHEN '2' THEN '人事处' + WHEN '3' THEN '纪委' + WHEN '4' THEN '学院分管领导' + WHEN '5' THEN '学院党委书记' ELSE '未知级别' END as approval_level_text, '待审核' as approval_status_text, @@ -279,12 +281,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" ELSE '未知目的' END as purpose_text, 'pending' as record_type - FROM rs_overseas_approval oa - INNER JOIN rs_overseas_application app ON oa.application_id = app.application_id - WHERE oa.deleted = '0' - AND app.deleted = '0' + FROM rs_overseas_application app + WHERE app.deleted = '0' AND app.status = '0' -- 申请状态:待审核 - AND oa.approval_status = '0' -- 审批状态:待审核 AND app.application_id = #{applicationId} @@ -292,16 +291,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND app.applicant_name like concat('%', #{applicantName}, '%') - AND oa.approval_level = #{approvalLevel} + AND app.approval_level = #{approvalLevel} + - AND oa.approver_id = #{approverId} + AND app.current_approver_id = #{approverId} - AND oa.approver_name like concat('%', #{approverName}, '%') + AND app.current_approver like concat('%', #{approverName}, '%') - AND oa.tenant_id = #{tenantId} + AND app.tenant_id = #{tenantId} diff --git a/docs/开发文档/API/出国申请管理API.md b/docs/开发文档/API/出国申请管理API.md index 2951ea89..9dc134e8 100644 --- a/docs/开发文档/API/出国申请管理API.md +++ b/docs/开发文档/API/出国申请管理API.md @@ -1061,4 +1061,184 @@ PDF模板包含以下空白填写区域(完全手工填写): **🆕 更新日志**: - 2025-10-17 新增党籍保留PDF空白模板下载功能(线下手工填写) - 接口路径调整:从 `/application/downloadPartyRetentionTemplate/{id}` 改为 `/record/downloadPartyRetention` -- 模板类型变更:从预填充模板改为完全空白模板 \ No newline at end of file +- 模板类型变更:从预填充模板改为完全空白模板 + +--- + +## 附录:工作流集成说明 + +### 动态审批人方案 + +#### 背景 + +出国申请业务集成了Warm-Flow工作流引擎,审批流程为:**发起人 → 部门领导 → 校领导**。审批人根据申请人所属部门动态确定,系统需要实时查询当前待审批人信息。 + +#### 方案设计 + +##### 1. 数据库字段说明 + +`rs_overseas_application` 表中的 `current_approver_id` 字段: + +- **字段类型**:`bigint(20)` +- **字段作用**:**冗余展示字段**(非核心业务字段) +- **维护方式**:不再由后端业务代码维护 +- **数据来源**:前端通过工作流API实时查询获取 +- **设计原因**: + - 审批人由工作流引擎动态确定,工作流表 `flow_node_user` 是唯一数据源 + - 避免数据冗余和不一致问题 + - 简化后端业务逻辑,降低维护成本 + +##### 2. 后端实现 + +**核心原则**:后端不维护 `current_approver_id` 字段 + +```java +// OverseasApplicationServiceImpl.java + +// ✅ 新增申请:不设置 current_approver_id +public Long addOverseasApplication(OverseasApplicationBo bo) { + // ... 业务逻辑 + // 不再包含:application.setCurrentApproverId(xxx); +} + +// ✅ 提交审批:不设置 current_approver_id +public R submitApplication(Long id) { + // ... 提交工作流 + // 不再包含:application.setCurrentApproverId(xxx); +} + +// ✅ 审批通过:不设置 current_approver_id +public R approve(Long id, String remark) { + // ... 审批逻辑 + // 不再包含:application.setCurrentApproverId(xxx); +} +``` + +**代码清理**: + +- 所有 `setCurrentApproverId()` 调用已移除 +- 相关查询逻辑已删除 +- 字段仅作为数据库表结构保留(历史兼容) + +##### 3. 前端实现 + +**核心方案**:通过工作流API实时查询当前审批人 + +```typescript +// overseasApplication/index.vue + +// 方案一:列表查询时批量获取审批人(推荐) +const getList = async () => { + const response = await listOverseasApplication(queryParams.value); + dataList.value = response.rows; + total.value = response.total; + + // 批量查询审批人信息 + await batchGetCurrentApproverNames(); +}; + +// 批量获取审批人姓名 +const batchGetCurrentApproverNames = async () => { + const promises = dataList.value + .filter(item => item.status === 'processing') + .map(async item => { + try { + // 调用工作流API查询当前审批人 + const approvers = await getCurrentApproversForTask(item.flowProcessId); + item.currentApproverName = approvers.map(u => u.name).join('、'); + } catch (error) { + item.currentApproverName = '-'; + } + }); + + await Promise.all(promises); +}; + +// 方案二:详情查询时单独获取审批人 +const handleViewDetail = async (row: OverseasApplicationVo) => { + currentRow.value = row; + + // 如果流程进行中,查询当前审批人 + if (row.flowProcessId && row.status === 'processing') { + try { + const approvers = await getCurrentApproversForTask(row.flowProcessId); + currentRow.value.currentApproverName = approvers.map(u => u.name).join('、'); + } catch (error) { + currentRow.value.currentApproverName = '查询失败'; + } + } + + detailDialogVisible.value = true; +}; +``` + +**工作流API调用**: + +```typescript +// 查询当前审批人(从工作流引擎获取) +import { getCurrentApproversForTask } from '@/api/workflow/common'; + +// API 路径:/api/workflow/instance/{processId}/current-approvers +// 返回格式: +{ + "code": 200, + "msg": "操作成功", + "data": [ + { + "userId": 1001, + "userName": "张三", + "nodeId": 2, + "nodeName": "部门领导审批" + } + ] +} +``` + +##### 4. 接口改造 + +相关API接口响应中的 `current_approver_id` 字段说明: + +| 接口 | 字段说明 | 使用建议 | +|------|---------|---------| +| 查询列表 | 返回null或历史值 | 忽略,通过工作流API获取 | +| 查询详情 | 返回null或历史值 | 忽略,通过工作流API获取 | +| 新增/修改 | 不处理该字段 | 前端不传递 | +| 提交审批 | 不处理该字段 | 前端不传递 | + +##### 5. 优势分析 + +**✅ 数据一致性** +- 工作流表是唯一数据源,避免双写不一致 +- 审批人变更(如转办、委派)自动生效 + +**✅ 降低维护成本** +- 后端无需在多个业务方法中同步更新 +- 减少代码复杂度和潜在Bug + +**✅ 实时性** +- 前端每次查询都获取最新审批人 +- 支持工作流引擎的高级功能(并行审批、会签等) + +**✅ 扩展性** +- 审批流程调整时,前端无需修改 +- 支持更复杂的审批场景(多人会签、或签等) + +#### 实施步骤 + +1. ✅ **后端改造**:移除所有 `current_approver_id` 字段的赋值代码 +2. ✅ **前端改造**:实现基于工作流API的动态查询 +3. ⏳ **测试验证**:确保列表、详情页面正确显示审批人 +4. ⏳ **文档更新**:同步更新API文档和技术文档 + +#### 注意事项 + +- 历史数据中的 `current_approver_id` 字段可能存在脏数据,前端应忽略 +- 工作流API调用失败时,前端应显示友好提示(如 "-" 或 "查询失败") +- 性能优化:列表页建议使用批量查询,避免N+1查询问题 +- 缓存策略:同一个流程实例的审批人信息可短时间缓存(如30秒) + +--- + +**更新时间**:2025-10-28 +**维护人员**:开发团队 +**变更记录**:新增工作流动态审批人方案说明 \ No newline at end of file diff --git a/web/src/views/rs/overseas/approval.vue b/web/src/views/rs/overseas/approval.vue index 3e67c481..0b0cccdb 100644 --- a/web/src/views/rs/overseas/approval.vue +++ b/web/src/views/rs/overseas/approval.vue @@ -132,7 +132,7 @@ :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" - @pagination="getList" + @pagination="applyFilterAndPagination" /> @@ -344,10 +344,10 @@