提交代码
This commit is contained in:
1 parent
4e34c7eb36
commit
375a2c8df7
54 files changed
+4631
-4352
No files matched your search
@@ -1,2 +0,0 @@
|
||||
# 人事管理模块 - 控制器层
|
||||
# 此目录用于存放REST API控制器类
|
||||
+41
-27
@@ -24,6 +24,7 @@ import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.validate.AddGroup;
|
||||
import org.dromara.common.core.validate.EditGroup;
|
||||
import org.dromara.common.excel.utils.ExcelUtil;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.annotation.SaMode;
|
||||
@@ -97,6 +98,8 @@ public class RsOverseasApplicationController extends BaseController {
|
||||
|
||||
/**
|
||||
* 查询当前用户的待审批申请列表(五级审批模式)
|
||||
*
|
||||
* @deprecated 已废弃,请使用 pendingPageList 方法(支持分页和搜索)
|
||||
*/
|
||||
@SaCheckPermission("rs:overseasapproval:list")
|
||||
@GetMapping("/pending/list")
|
||||
@@ -105,6 +108,30 @@ public class RsOverseasApplicationController extends BaseController {
|
||||
return R.ok(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前用户的待审批申请分页列表(根据角色过滤,支持分页和搜索)
|
||||
*
|
||||
* 🔴 核心逻辑:
|
||||
* 1. 根据用户角色确定审批级别列表
|
||||
* 2. 部门负责人(rs_bmfzr)只能看到本部门的申请
|
||||
* 3. 其他角色可以看到全校的申请
|
||||
* 4. 支持分页和搜索(前往国家、出境事由)
|
||||
* 5. 只返回status='0'(待审核)的申请
|
||||
* 6. 按创建时间倒序排列
|
||||
*
|
||||
* 角色映射:
|
||||
* - rs_bmfzr → approval_level='1' + 部门过滤
|
||||
* - hr_dept_role → approval_level='2'
|
||||
* - discipline_committee_role → approval_level='3'
|
||||
* - college_leader_role → approval_level='4'
|
||||
* - party_secretary_role → approval_level='5'
|
||||
*/
|
||||
@SaCheckPermission("rs:overseasapproval:list")
|
||||
@GetMapping("/pending/page")
|
||||
public TableDataInfo<RsOverseasApplicationVo> pendingPageList(RsOverseasApplicationQuery query) {
|
||||
return applicationService.queryPendingPageListByRole(query);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 五级审批模式说明
|
||||
//
|
||||
@@ -321,7 +348,7 @@ public class RsOverseasApplicationController extends BaseController {
|
||||
@PostMapping("/approve")
|
||||
public R<Void> approve(@RequestBody ApprovalRequest request) {
|
||||
Long approverId = LoginHelper.getUserId();
|
||||
String approverName = LoginHelper.getUsername();
|
||||
String approverName = StringUtils.defaultIfBlank(LoginHelper.getNickname(), LoginHelper.getUsername());
|
||||
|
||||
// 🔴 标准工作流模式:支持任务ID参数
|
||||
// 如果传入了taskId,将通过工作流引擎进行审批
|
||||
@@ -488,12 +515,6 @@ public class RsOverseasApplicationController extends BaseController {
|
||||
public R<Map<String, Object>> uploadStampedFile(@RequestParam("file") MultipartFile file,
|
||||
@RequestParam("applicationId") Long applicationId) {
|
||||
try {
|
||||
// 验证文件格式
|
||||
String contentType = file.getContentType();
|
||||
if (!isValidFileType(contentType)) {
|
||||
return R.fail("不支持的文件格式,请上传PDF、JPG或PNG文件");
|
||||
}
|
||||
|
||||
// 验证文件大小(10MB)
|
||||
long maxSize = 10 * 1024 * 1024; // 10MB
|
||||
if (file.getSize() > maxSize) {
|
||||
@@ -561,12 +582,6 @@ public class RsOverseasApplicationController extends BaseController {
|
||||
public R<Map<String, Object>> uploadPartyRetentionFile(@RequestParam("file") MultipartFile file,
|
||||
@RequestParam("applicationId") Long applicationId) {
|
||||
try {
|
||||
// 验证文件格式
|
||||
String contentType = file.getContentType();
|
||||
if (!isValidFileType(contentType)) {
|
||||
return R.fail("不支持的文件格式,请上传PDF、JPG或PNG文件");
|
||||
}
|
||||
|
||||
// 验证文件大小(10MB)
|
||||
long maxSize = 10 * 1024 * 1024; // 10MB
|
||||
if (file.getSize() > maxSize) {
|
||||
@@ -583,7 +598,7 @@ public class RsOverseasApplicationController extends BaseController {
|
||||
ossResult.getOriginalName(), // 原始文件名
|
||||
new Date(), // 上传时间
|
||||
"1", // 上传状态:已上传
|
||||
ossResult.getOssId() // OSS文件ID
|
||||
String.valueOf(ossResult.getOssId()) // OSS文件ID - 转换为字符串
|
||||
);
|
||||
|
||||
// 返回结果
|
||||
@@ -616,25 +631,24 @@ public class RsOverseasApplicationController extends BaseController {
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理多个OSS ID的情况(逗号分隔),取第一个ID进行下载
|
||||
String ossIdStr = application.getPartyRetentionFileOssId();
|
||||
Long ossId;
|
||||
if (ossIdStr.contains(",")) {
|
||||
// 如果包含逗号,取第一个ID
|
||||
ossId = Long.parseLong(ossIdStr.split(",")[0].trim());
|
||||
} else {
|
||||
// 单个ID直接解析
|
||||
ossId = Long.parseLong(ossIdStr);
|
||||
}
|
||||
|
||||
// 使用系统OSS服务进行安全下载
|
||||
ossService.secureDownload(application.getPartyRetentionFileOssId(), response);
|
||||
ossService.secureDownload(ossId, response);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证文件类型
|
||||
*/
|
||||
private boolean isValidFileType(String contentType) {
|
||||
if (contentType == null)
|
||||
return false;
|
||||
return contentType.equals("application/pdf") ||
|
||||
contentType.equals("image/jpeg") ||
|
||||
contentType.equals("image/jpg") ||
|
||||
contentType.equals("image/png");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件扩展名
|
||||
*/
|
||||
|
||||
+6
-10
@@ -62,17 +62,13 @@ public class RsOverseasItineraryChangeController extends BaseController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔴 标准化改造:删除自定义待审批查询接口
|
||||
*
|
||||
* 原因:出国行程变更工作流应使用标准工作流模式
|
||||
* 用户应通过工作流API pageByTaskWait() 获取待办任务
|
||||
* 而不是调用自定义的业务API
|
||||
* 查询待审批的行程变更申请列表(status=0)
|
||||
*/
|
||||
@SaCheckPermission("rs:itineraryChange:approve")
|
||||
@GetMapping("/pending/list")
|
||||
public R<String> pendingList(RsOverseasItineraryChangeQuery query) {
|
||||
// 提示用户使用标准工作流API
|
||||
return R.fail("请使用标准工作流待办页面查看审批任务。访问:工作流 > 我的任务 > 待办任务");
|
||||
public TableDataInfo<RsOverseasItineraryChangeVo> pendingList(RsOverseasItineraryChangeQuery query) {
|
||||
query.setTenantId(LoginHelper.getTenantId());
|
||||
return changeService.queryPendingList(query);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,7 +163,7 @@ public class RsOverseasItineraryChangeController extends BaseController {
|
||||
/**
|
||||
* 导出行程变更申请列表
|
||||
*/
|
||||
@SaCheckPermission("rs:itineraryChange:export")
|
||||
// @SaCheckPermission("rs:itineraryChange:export")
|
||||
@Log(title = "行程变更申请", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(RsOverseasItineraryChangeQuery query, HttpServletResponse response) {
|
||||
@@ -179,7 +175,7 @@ public class RsOverseasItineraryChangeController extends BaseController {
|
||||
/**
|
||||
* 查询某个出国记录的所有变更历史
|
||||
*/
|
||||
@SaCheckPermission("rs:itineraryChange:query")
|
||||
// @SaCheckPermission("rs:itineraryChange:query")
|
||||
@GetMapping("/record/{recordId}/history")
|
||||
public R<List<RsOverseasItineraryChangeVo>> getChangeHistory(@PathVariable Long recordId) {
|
||||
return R.ok(changeService.queryByRecordId(recordId));
|
||||
|
||||
+29
-37
@@ -32,7 +32,7 @@ import org.dromara.rs.service.IRsOverseasRegistrationService;
|
||||
|
||||
/**
|
||||
* 出国登记Controller
|
||||
*
|
||||
*
|
||||
* @author weizzzz
|
||||
* @date 2025-09-30
|
||||
*/
|
||||
@@ -47,7 +47,7 @@ public class RsOverseasRegistrationController extends BaseController {
|
||||
/**
|
||||
* 查询出国登记列表
|
||||
*/
|
||||
@SaCheckPermission("rs:overseas:registration:list")
|
||||
// @SaCheckPermission("rs:overseasregistration:list")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<RsOverseasRegistrationVo> list(RsOverseasRegistrationQuery query) {
|
||||
return registrationService.queryPageList(query);
|
||||
@@ -56,7 +56,7 @@ public class RsOverseasRegistrationController extends BaseController {
|
||||
/**
|
||||
* 查询我的出国登记列表
|
||||
*/
|
||||
@SaCheckPermission("rs:overseas:registration:list")
|
||||
// @SaCheckPermission("rs:overseasregistration:list")
|
||||
@GetMapping("/my/list")
|
||||
public TableDataInfo<RsOverseasRegistrationVo> myList(RsOverseasRegistrationQuery query) {
|
||||
return registrationService.queryMyRegistrationList(query);
|
||||
@@ -65,7 +65,7 @@ public class RsOverseasRegistrationController extends BaseController {
|
||||
/**
|
||||
* 导出出国登记列表
|
||||
*/
|
||||
@SaCheckPermission("rs:overseas:registration:export")
|
||||
// @SaCheckPermission("rs:overseasregistration:export")
|
||||
@Log(title = "出国登记", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(RsOverseasRegistrationQuery query, HttpServletResponse response) {
|
||||
@@ -76,27 +76,26 @@ public class RsOverseasRegistrationController extends BaseController {
|
||||
/**
|
||||
* 获取出国登记详细信息
|
||||
*/
|
||||
@SaCheckPermission("rs:overseas:registration:query")
|
||||
// @SaCheckPermission("rs:overseasregistration:query")
|
||||
@GetMapping("/{registrationId}")
|
||||
public R<RsOverseasRegistrationVo> getInfo(@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long registrationId) {
|
||||
public R<RsOverseasRegistrationVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long registrationId) {
|
||||
return R.ok(registrationService.queryById(registrationId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据申请ID获取出国登记信息
|
||||
*/
|
||||
@SaCheckPermission("rs:overseas:registration:query")
|
||||
// @SaCheckPermission("rs:overseasregistration:query")
|
||||
@GetMapping("/application/{applicationId}")
|
||||
public R<RsOverseasRegistrationVo> getByApplicationId(@NotNull(message = "申请ID不能为空")
|
||||
@PathVariable Long applicationId) {
|
||||
public R<RsOverseasRegistrationVo> getByApplicationId(
|
||||
@NotNull(message = "申请ID不能为空") @PathVariable Long applicationId) {
|
||||
return R.ok(registrationService.queryByApplicationId(applicationId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增出国登记
|
||||
*/
|
||||
@SaCheckPermission("rs:overseas:registration:add")
|
||||
// @SaCheckPermission("rs:overseasregistration:add")
|
||||
@Log(title = "出国登记", businessType = BusinessType.INSERT)
|
||||
@PostMapping()
|
||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody RsOverseasRegistrationBo bo) {
|
||||
@@ -106,7 +105,7 @@ public class RsOverseasRegistrationController extends BaseController {
|
||||
/**
|
||||
* 新增并提交出国登记
|
||||
*/
|
||||
@SaCheckPermission("rs:overseas:registration:add")
|
||||
// @SaCheckPermission("rs:overseasregistration:add")
|
||||
@Log(title = "出国登记", businessType = BusinessType.INSERT)
|
||||
@PostMapping("/addAndSubmit")
|
||||
public R<Void> addAndSubmit(@Validated(AddGroup.class) @RequestBody RsOverseasRegistrationBo bo) {
|
||||
@@ -116,7 +115,7 @@ public class RsOverseasRegistrationController extends BaseController {
|
||||
/**
|
||||
* 修改出国登记
|
||||
*/
|
||||
@SaCheckPermission("rs:overseas:registration:edit")
|
||||
// @SaCheckPermission("rs:overseasregistration:edit")
|
||||
@Log(title = "出国登记", businessType = BusinessType.UPDATE)
|
||||
@PutMapping()
|
||||
public R<Void> edit(@Validated(EditGroup.class) @RequestBody RsOverseasRegistrationBo bo) {
|
||||
@@ -126,67 +125,61 @@ public class RsOverseasRegistrationController extends BaseController {
|
||||
/**
|
||||
* 删除出国登记
|
||||
*/
|
||||
@SaCheckPermission("rs:overseas:registration:remove")
|
||||
// @SaCheckPermission("rs:overseasregistration:remove")
|
||||
@Log(title = "出国登记", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{registrationIds}")
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空")
|
||||
@PathVariable Long[] registrationIds) {
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] registrationIds) {
|
||||
return toAjax(registrationService.deleteWithValidByIds(List.of(registrationIds), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交出国登记
|
||||
*/
|
||||
@SaCheckPermission("rs:overseas:registration:submit")
|
||||
// @SaCheckPermission("rs:overseasregistration:submit")
|
||||
@Log(title = "出国登记", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/submit/{registrationId}")
|
||||
public R<Void> submit(@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long registrationId) {
|
||||
public R<Void> submit(@NotNull(message = "主键不能为空") @PathVariable Long registrationId) {
|
||||
return toAjax(registrationService.submitRegistration(registrationId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认出国登记
|
||||
*/
|
||||
@SaCheckPermission("rs:overseas:registration:confirm")
|
||||
// @SaCheckPermission("rs:overseasregistration:confirm")
|
||||
@Log(title = "出国登记", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/confirm/{registrationId}")
|
||||
public R<Void> confirm(@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long registrationId) {
|
||||
public R<Void> confirm(@NotNull(message = "主键不能为空") @PathVariable Long registrationId) {
|
||||
return toAjax(registrationService.confirmRegistration(registrationId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤回出国登记
|
||||
*/
|
||||
@SaCheckPermission("rs:overseas:registration:withdraw")
|
||||
// @SaCheckPermission("rs:overseasregistration:withdraw")
|
||||
@Log(title = "出国登记", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/withdraw/{registrationId}")
|
||||
public R<Void> withdraw(@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long registrationId) {
|
||||
public R<Void> withdraw(@NotNull(message = "主键不能为空") @PathVariable Long registrationId) {
|
||||
return toAjax(registrationService.withdrawRegistration(registrationId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于出国申请创建登记记录
|
||||
*/
|
||||
@SaCheckPermission("rs:overseas:registration:create")
|
||||
// @SaCheckPermission("rs:overseasregistration:create")
|
||||
@Log(title = "出国登记", businessType = BusinessType.INSERT)
|
||||
@PostMapping("/create/{applicationId}")
|
||||
public R<Void> createFromApplication(@NotNull(message = "申请ID不能为空")
|
||||
@PathVariable Long applicationId) {
|
||||
public R<Void> createFromApplication(@NotNull(message = "申请ID不能为空") @PathVariable Long applicationId) {
|
||||
return toAjax(registrationService.createFromApplication(applicationId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批准出国登记
|
||||
*/
|
||||
@SaCheckPermission("rs:registration:approve")
|
||||
// @SaCheckPermission("rs:registration:approve")
|
||||
@Log(title = "出国登记", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/approve/{registrationId}")
|
||||
public R<Void> approve(@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long registrationId,
|
||||
@RequestBody(required = false) RsOverseasRegistrationBo bo) {
|
||||
public R<Void> approve(@NotNull(message = "主键不能为空") @PathVariable Long registrationId,
|
||||
@RequestBody(required = false) RsOverseasRegistrationBo bo) {
|
||||
String approveNotes = (bo != null && bo.getRemarks() != null) ? bo.getRemarks() : "同意";
|
||||
return toAjax(registrationService.approveRegistration(registrationId, approveNotes));
|
||||
}
|
||||
@@ -194,13 +187,12 @@ public class RsOverseasRegistrationController extends BaseController {
|
||||
/**
|
||||
* 拒绝出国登记
|
||||
*/
|
||||
@SaCheckPermission("rs:registration:reject")
|
||||
// @SaCheckPermission("rs:registration:reject")
|
||||
@Log(title = "出国登记", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/reject/{registrationId}")
|
||||
public R<Void> reject(@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long registrationId,
|
||||
@RequestBody(required = false) RsOverseasRegistrationBo bo) {
|
||||
public R<Void> reject(@NotNull(message = "主键不能为空") @PathVariable Long registrationId,
|
||||
@RequestBody(required = false) RsOverseasRegistrationBo bo) {
|
||||
String rejectReason = (bo != null && bo.getRemarks() != null) ? bo.getRemarks() : "不同意";
|
||||
return toAjax(registrationService.rejectRegistration(registrationId, rejectReason));
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -384,10 +384,10 @@ public class RsOverseasApplication extends BaseEntity {
|
||||
private String partyRetentionUploadStatus;
|
||||
|
||||
/**
|
||||
* 党籍保留附件OSS ID(用于安全下载)
|
||||
* 党籍保留附件OSS ID(用于安全下载,支持多个ID用逗号分隔)
|
||||
*/
|
||||
@ExcelIgnore
|
||||
private Long partyRetentionFileOssId;
|
||||
private String partyRetentionFileOssId;
|
||||
|
||||
/**
|
||||
* 租户编号
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ public class RsOverseasRegistration extends BaseEntity {
|
||||
/**
|
||||
* 登记ID
|
||||
*/
|
||||
@TableId(value = "registration_id", type = IdType.AUTO)
|
||||
@TableId(value = "registration_id", type = IdType.ASSIGN_ID)
|
||||
@ExcelProperty(value = "登记ID")
|
||||
private Long registrationId;
|
||||
|
||||
|
||||
+26
@@ -317,6 +317,32 @@ public class RsOverseasApplicationBo extends BaseEntity {
|
||||
*/
|
||||
private String stampedFileUploadStatus;
|
||||
|
||||
/**
|
||||
* 党籍保留附件文件路径
|
||||
*/
|
||||
private String partyRetentionFilePath;
|
||||
|
||||
/**
|
||||
* 党籍保留附件文件名
|
||||
*/
|
||||
private String partyRetentionFileName;
|
||||
|
||||
/**
|
||||
* 党籍保留附件上传时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date partyRetentionUploadTime;
|
||||
|
||||
/**
|
||||
* 党籍保留附件上传状态:0-未上传,1-已上传
|
||||
*/
|
||||
private String partyRetentionUploadStatus;
|
||||
|
||||
/**
|
||||
* 党籍保留附件OSS ID(支持多个ID用逗号分隔)
|
||||
*/
|
||||
private String partyRetentionFileOssId;
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
|
||||
+2
-2
@@ -365,9 +365,9 @@ public class RsOverseasApplicationVo implements Serializable {
|
||||
private String partyRetentionUploadStatus;
|
||||
|
||||
/**
|
||||
* 党籍保留附件OSS ID(用于安全下载)
|
||||
* 党籍保留附件OSS ID(用于安全下载,支持多个ID用逗号分隔)
|
||||
*/
|
||||
private Long partyRetentionFileOssId;
|
||||
private String partyRetentionFileOssId;
|
||||
|
||||
/**
|
||||
* 租户编号
|
||||
|
||||
+5
@@ -196,6 +196,11 @@ public class RsOverseasApprovalMonitorVo implements Serializable {
|
||||
@ExcelProperty(value = "应急联系人及电话")
|
||||
private String emergencyContact;
|
||||
|
||||
/**
|
||||
* 申请人手写签名(base64)
|
||||
*/
|
||||
private String signature;
|
||||
|
||||
// ==================== 审批状态信息 ====================
|
||||
|
||||
/**
|
||||
|
||||
+19
@@ -144,4 +144,23 @@ public interface RsOverseasApplicationMapper extends BaseMapperPlus<RsOverseasAp
|
||||
* @return 待审批申请列表
|
||||
*/
|
||||
List<RsOverseasApplication> selectPendingApplications(List<String> approvalLevels, Long deptId);
|
||||
|
||||
/**
|
||||
* 🔴 新增:根据角色查询待审批申请列表(支持分页和搜索)
|
||||
*
|
||||
* 查询逻辑:
|
||||
* 1. 根据用户角色确定审批级别列表
|
||||
* 2. 关联 sys_user 表获取申请人的部门信息
|
||||
* 3. 按审批级别过滤(approvalLevel IN (...))
|
||||
* 4. 按状态过滤(status = '0' 待审核)
|
||||
* 5. 按部门过滤(部门负责人:u.dept_id = 当前用户部门ID)
|
||||
* 6. 按搜索条件过滤(前往国家、出境事由)
|
||||
* 7. 按创建时间倒序排列
|
||||
*
|
||||
* @param query 查询条件(包含分页、搜索条件)
|
||||
* @param approvalLevels 审批级别列表,例如:["1", "2"]
|
||||
* @param deptId 部门ID(部门负责人需要),为null则不过滤部门
|
||||
* @return 待审批申请分页列表
|
||||
*/
|
||||
List<RsOverseasApplicationVo> selectPendingApplicationsByRole(RsOverseasApplicationQuery query, List<String> approvalLevels, Long deptId);
|
||||
}
|
||||
+9
-1
@@ -48,6 +48,14 @@ public interface IRsOverseasApplicationService {
|
||||
*/
|
||||
List<RsOverseasApplicationVo> queryPendingByCurrentUser();
|
||||
|
||||
/**
|
||||
* 查询当前用户的待审批申请分页列表(根据角色过滤)
|
||||
*
|
||||
* @param query 查询条件(包含分页、搜索条件)
|
||||
* @return 待审批申请分页集合
|
||||
*/
|
||||
TableDataInfo<RsOverseasApplicationVo> queryPendingPageListByRole(RsOverseasApplicationQuery query);
|
||||
|
||||
/**
|
||||
* 查询出国申请详细信息
|
||||
*
|
||||
@@ -207,7 +215,7 @@ public interface IRsOverseasApplicationService {
|
||||
* @return 是否成功
|
||||
*/
|
||||
Boolean updatePartyRetentionFileInfo(Long applicationId, String filePath, String fileName,
|
||||
java.util.Date uploadTime, String uploadStatus, Long ossId);
|
||||
java.util.Date uploadTime, String uploadStatus, String ossId);
|
||||
|
||||
/**
|
||||
* 校验出国登记前置条件
|
||||
|
||||
+195
-19
@@ -51,7 +51,7 @@ import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
|
||||
/**
|
||||
* 出国申请Service业务层处理
|
||||
*
|
||||
*
|
||||
* @author weizzzz
|
||||
* @date 2025-07-28
|
||||
*/
|
||||
@@ -232,14 +232,14 @@ public class RsOverseasApplicationServiceImpl extends ServiceImpl<RsOverseasAppl
|
||||
|
||||
/**
|
||||
* 查询当前用户的待审批申请列表(动态权限方案 + 部门过滤)
|
||||
*
|
||||
*
|
||||
* 🆕 核心改进:
|
||||
* 1. 不再依赖 current_approver_id 字段(该字段仅作为冗余展示用)
|
||||
* 2. 动态查询用户的审批权限,转换为审批级别
|
||||
* 3. 根据审批级别和状态查询待审批申请
|
||||
* 4. 🔴 部门负责人只能看到本部门的申请
|
||||
* 5. 支持人员岗位变动:权限调整后立即生效
|
||||
*
|
||||
*
|
||||
* 查询逻辑:
|
||||
* - 用户有 rs:overseas:approve1 权限(部门负责人)→ 查询本部门员工的、approval_level='1' 且
|
||||
* status='0' 的申请
|
||||
@@ -307,6 +307,156 @@ public class RsOverseasApplicationServiceImpl extends ServiceImpl<RsOverseasAppl
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前用户的待审批申请分页列表(根据角色过滤)
|
||||
*
|
||||
* 🔴 核心逻辑:
|
||||
* 1. 根据用户角色确定审批级别列表
|
||||
* 2. 部门负责人(rs_bmfzr)只能看到本部门的申请
|
||||
* 3. 其他角色可以看到全校的申请
|
||||
* 4. 支持分页和搜索(前往国家、出境事由)
|
||||
* 5. 按创建时间倒序排列
|
||||
* 6. 🔴 修复:当用户同时拥有多个角色时,分别查询后合并去重
|
||||
*
|
||||
* 角色映射:
|
||||
* - rs_bmfzr → approval_level='1' + 部门过滤
|
||||
* - hr_dept_role → approval_level='2'
|
||||
* - discipline_committee_role → approval_level='3'
|
||||
* - college_leader_role → approval_level='4'
|
||||
* - party_secretary_role → approval_level='5'
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<RsOverseasApplicationVo> queryPendingPageListByRole(RsOverseasApplicationQuery query) {
|
||||
Long currentUserId = LoginHelper.getUserId();
|
||||
String currentUserName = LoginHelper.getUsername();
|
||||
String tenantId = LoginHelper.getTenantId();
|
||||
|
||||
log.info("🔍 查询用户待审批申请分页列表 - 用户ID: {}, 用户名: {}", currentUserId, currentUserName);
|
||||
|
||||
// 1. 获取当前用户的角色列表
|
||||
List<SysRoleVo> userRoles = roleService.selectRolesByUserId(currentUserId);
|
||||
if (userRoles == null || userRoles.isEmpty()) {
|
||||
log.warn("❌ 用户 {} 没有任何角色,返回空列表", currentUserName);
|
||||
return TableDataInfo.build();
|
||||
}
|
||||
|
||||
// 2. 根据角色编码确定审批级别列表
|
||||
List<String> deptHeadLevels = new ArrayList<>(); // 部门负责人审批级别
|
||||
List<String> otherLevels = new ArrayList<>(); // 其他审批级别
|
||||
Long deptIdForFilter = null;
|
||||
boolean hasDeptHeadRole = false;
|
||||
boolean hasOtherRole = false;
|
||||
|
||||
for (SysRoleVo role : userRoles) {
|
||||
String roleKey = role.getRoleKey();
|
||||
if (roleKey == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 角色编码映射到审批级别
|
||||
if ("rs_bmfzr".equals(roleKey)) {
|
||||
deptHeadLevels.add("1");
|
||||
hasDeptHeadRole = true;
|
||||
} else if ("hr_dept_role".equals(roleKey)) {
|
||||
otherLevels.add("2");
|
||||
hasOtherRole = true;
|
||||
} else if ("discipline_committee_role".equals(roleKey)) {
|
||||
otherLevels.add("3");
|
||||
hasOtherRole = true;
|
||||
} else if ("college_leader_role".equals(roleKey)) {
|
||||
otherLevels.add("4");
|
||||
hasOtherRole = true;
|
||||
} else if ("party_secretary_role".equals(roleKey)) {
|
||||
otherLevels.add("5");
|
||||
hasOtherRole = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasDeptHeadRole && !hasOtherRole) {
|
||||
log.warn("❌ 用户 {} 没有任何出国审批角色,返回空列表", currentUserName);
|
||||
return TableDataInfo.build();
|
||||
}
|
||||
|
||||
log.info("✅ 用户 {} 拥有审批级别 - 部门负责人: {}, 其他角色: {}",
|
||||
currentUserName, deptHeadLevels, otherLevels);
|
||||
|
||||
// 3. 如果是部门负责人,获取部门ID进行过滤
|
||||
if (hasDeptHeadRole) {
|
||||
SysUserVo currentUser = userService.selectUserById(currentUserId);
|
||||
if (currentUser != null && currentUser.getDeptId() != null) {
|
||||
deptIdForFilter = currentUser.getDeptId();
|
||||
log.info("🔒 [部门权限过滤] 部门负责人,用户: {}, 部门ID: {}, 只查看本部门待审批",
|
||||
currentUserName, deptIdForFilter);
|
||||
} else {
|
||||
log.warn("⚠️ 用户 {} 是部门负责人但没有部门信息,无法过滤", currentUserName);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 设置租户ID
|
||||
if (StringUtils.isNotBlank(tenantId)) {
|
||||
query.setTenantId(tenantId);
|
||||
}
|
||||
|
||||
// 5. 🔴 修复:分别查询不同角色的数据,然后合并去重
|
||||
List<RsOverseasApplicationVo> allList = new ArrayList<>();
|
||||
Map<Long, RsOverseasApplicationVo> applicationMap = new HashMap<>(); // 用于去重,key=applicationId
|
||||
|
||||
// 5.1 查询部门负责人的数据(本部门,审批级别='1')
|
||||
if (hasDeptHeadRole && !deptHeadLevels.isEmpty() && deptIdForFilter != null) {
|
||||
List<RsOverseasApplicationVo> deptList = baseMapper.selectPendingApplicationsByRole(
|
||||
query, deptHeadLevels, deptIdForFilter);
|
||||
log.info("📋 [部门负责人] 查询到 {} 条本部门待审批申请", deptList.size());
|
||||
for (RsOverseasApplicationVo vo : deptList) {
|
||||
applicationMap.put(vo.getApplicationId(), vo);
|
||||
}
|
||||
}
|
||||
|
||||
// 5.2 查询其他角色的数据(全校,审批级别为其他级别)
|
||||
if (hasOtherRole && !otherLevels.isEmpty()) {
|
||||
List<RsOverseasApplicationVo> otherList = baseMapper.selectPendingApplicationsByRole(
|
||||
query, otherLevels, null); // deptIdForFilter=null,不限制部门
|
||||
log.info("📋 [其他角色] 查询到 {} 条全校待审批申请", otherList.size());
|
||||
for (RsOverseasApplicationVo vo : otherList) {
|
||||
applicationMap.put(vo.getApplicationId(), vo);
|
||||
}
|
||||
}
|
||||
|
||||
// 5.3 合并去重后的结果,按创建时间倒序排列
|
||||
allList = new ArrayList<>(applicationMap.values());
|
||||
allList.sort((a, b) -> {
|
||||
if (a.getCreateTime() != null && b.getCreateTime() != null) {
|
||||
return b.getCreateTime().compareTo(a.getCreateTime());
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
log.info("✅ [合并结果] 去重后共 {} 条待审批申请", allList.size());
|
||||
|
||||
// 6. 手动分页处理
|
||||
int pageNum = query.getPageNum();
|
||||
int pageSize = query.getPageSize();
|
||||
int total = allList.size();
|
||||
int start = (pageNum - 1) * pageSize;
|
||||
int end = Math.min(start + pageSize, total);
|
||||
|
||||
List<RsOverseasApplicationVo> pageList;
|
||||
if (start >= total) {
|
||||
pageList = new ArrayList<>();
|
||||
} else {
|
||||
pageList = allList.subList(start, end);
|
||||
}
|
||||
|
||||
// 7. 构建分页对象
|
||||
Page<RsOverseasApplicationVo> page = new Page<>(pageNum, pageSize);
|
||||
page.setRecords(pageList);
|
||||
page.setTotal(total);
|
||||
|
||||
log.info("📋 用户 {} 查询到 {} 条待审批申请,分页显示第 {} 页,共 {} 条",
|
||||
currentUserName, total, pageNum, pageList.size());
|
||||
|
||||
return TableDataInfo.build(page);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换实体为VO对象
|
||||
* 🔴 修复:使用Mapper查询完整的VO对象,包含字典翻译等扩展信息
|
||||
@@ -514,7 +664,7 @@ public class RsOverseasApplicationServiceImpl extends ServiceImpl<RsOverseasAppl
|
||||
|
||||
/**
|
||||
* 提交出国申请(动态审批人方案)
|
||||
*
|
||||
*
|
||||
* 🆕 核心改进:
|
||||
* 1. 不再设置固定的 current_approver_id(该字段改为冗余展示字段)
|
||||
* 2. 仅设置 approval_level='1',审批人根据权限动态查询
|
||||
@@ -599,7 +749,7 @@ public class RsOverseasApplicationServiceImpl extends ServiceImpl<RsOverseasAppl
|
||||
RsOverseasApproval approval = new RsOverseasApproval();
|
||||
approval.setApplicationId(applicationId);
|
||||
approval.setApproverId(approverId);
|
||||
approval.setApproverName(approverName);
|
||||
approval.setApproverName(resolveApproverDisplayName(approverId, approverName));
|
||||
approval.setApprovalLevel(application.getApprovalLevel());
|
||||
approval.setApprovalStatus(approvalStatus);
|
||||
approval.setApprovalOpinion(approvalOpinion);
|
||||
@@ -721,7 +871,7 @@ public class RsOverseasApplicationServiceImpl extends ServiceImpl<RsOverseasAppl
|
||||
|
||||
/**
|
||||
* 修复已审批通过但未创建记录的申请
|
||||
*
|
||||
*
|
||||
* @return 修复的记录数量
|
||||
*/
|
||||
@Override
|
||||
@@ -950,6 +1100,16 @@ public class RsOverseasApplicationServiceImpl extends ServiceImpl<RsOverseasAppl
|
||||
entity.setStampedFileUploadTime(bo.getStampedFileUploadTime());
|
||||
entity.setStampedFileUploadStatus(bo.getStampedFileUploadStatus());
|
||||
|
||||
// ==================== 党籍保留附件相关字段 ====================
|
||||
entity.setPartyRetentionFilePath(bo.getPartyRetentionFilePath());
|
||||
entity.setPartyRetentionFileName(bo.getPartyRetentionFileName());
|
||||
entity.setPartyRetentionUploadTime(bo.getPartyRetentionUploadTime());
|
||||
entity.setPartyRetentionUploadStatus(bo.getPartyRetentionUploadStatus());
|
||||
entity.setPartyRetentionFileOssId(bo.getPartyRetentionFileOssId());
|
||||
|
||||
// ==================== 附件ID列表 ====================
|
||||
entity.setAttachmentIds(bo.getAttachmentIds());
|
||||
|
||||
// ==================== 租户信息 ====================
|
||||
// 🔴 关键修复:设置租户ID(如果Bo中有值则使用Bo的值,否则依赖MyBatis-Plus自动填充)
|
||||
if (bo.getTenantId() != null) {
|
||||
@@ -975,6 +1135,22 @@ public class RsOverseasApplicationServiceImpl extends ServiceImpl<RsOverseasAppl
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析审批人的展示名称:优先使用用户昵称,缺省时回退账号
|
||||
*/
|
||||
private String resolveApproverDisplayName(Long approverId, String fallbackName) {
|
||||
String displayName = fallbackName;
|
||||
try {
|
||||
SysUserVo approver = userService.selectUserById(approverId);
|
||||
if (approver != null && StringUtils.isNotBlank(approver.getNickName())) {
|
||||
displayName = approver.getNickName();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("⚠️ 获取审批人昵称失败, 使用账号作为展示名称. approverId={}, error={}", approverId, e.getMessage());
|
||||
}
|
||||
return displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除出国申请
|
||||
*/
|
||||
@@ -1008,7 +1184,7 @@ public class RsOverseasApplicationServiceImpl extends ServiceImpl<RsOverseasAppl
|
||||
@Override
|
||||
@Transactional
|
||||
public Boolean updatePartyRetentionFileInfo(Long applicationId, String filePath, String fileName,
|
||||
java.util.Date uploadTime, String uploadStatus, Long ossId) {
|
||||
java.util.Date uploadTime, String uploadStatus, String ossId) {
|
||||
return baseMapper.update(null,
|
||||
new LambdaUpdateWrapper<RsOverseasApplication>()
|
||||
.eq(RsOverseasApplication::getApplicationId, applicationId)
|
||||
@@ -1087,7 +1263,7 @@ public class RsOverseasApplicationServiceImpl extends ServiceImpl<RsOverseasAppl
|
||||
|
||||
/**
|
||||
* 根据申请人ID获取部门审批人ID
|
||||
*
|
||||
*
|
||||
* @param applicantId 申请人ID
|
||||
* @return 部门审批人ID
|
||||
*/
|
||||
@@ -1134,7 +1310,7 @@ public class RsOverseasApplicationServiceImpl extends ServiceImpl<RsOverseasAppl
|
||||
|
||||
/**
|
||||
* 根据申请人ID获取人事处审批人列表
|
||||
*
|
||||
*
|
||||
* @param applicantId 申请人ID
|
||||
* @return 人事处审批人ID列表
|
||||
*/
|
||||
@@ -1176,7 +1352,7 @@ public class RsOverseasApplicationServiceImpl extends ServiceImpl<RsOverseasAppl
|
||||
|
||||
/**
|
||||
* 根据申请人查找对应的部门负责人审批人ID
|
||||
*
|
||||
*
|
||||
* @param applicantId 申请人ID
|
||||
* @return 部门负责人审批人ID
|
||||
*/
|
||||
@@ -1203,7 +1379,7 @@ public class RsOverseasApplicationServiceImpl extends ServiceImpl<RsOverseasAppl
|
||||
|
||||
/**
|
||||
* 根据部门ID查找对应的部门负责人审批人
|
||||
*
|
||||
*
|
||||
* @param deptId 部门ID
|
||||
* @return 部门负责人审批人ID
|
||||
*/
|
||||
@@ -1251,7 +1427,7 @@ public class RsOverseasApplicationServiceImpl extends ServiceImpl<RsOverseasAppl
|
||||
|
||||
/**
|
||||
* 查找父部门的负责人审批人
|
||||
*
|
||||
*
|
||||
* @param deptId 当前部门ID
|
||||
* @param approvers 所有部门负责人审批人列表
|
||||
* @return 父部门负责人审批人ID
|
||||
@@ -1288,7 +1464,7 @@ public class RsOverseasApplicationServiceImpl extends ServiceImpl<RsOverseasAppl
|
||||
|
||||
/**
|
||||
* 根据审批级别获取下一级审批人ID
|
||||
*
|
||||
*
|
||||
* @param approvalLevel 审批级别
|
||||
* @return 下一级审批人ID
|
||||
*/
|
||||
@@ -1298,7 +1474,7 @@ public class RsOverseasApplicationServiceImpl extends ServiceImpl<RsOverseasAppl
|
||||
|
||||
/**
|
||||
* 根据审批级别获取审批环节描述
|
||||
*
|
||||
*
|
||||
* @param approvalLevel 审批级别
|
||||
* @return 审批环节描述
|
||||
*/
|
||||
@@ -1321,14 +1497,14 @@ public class RsOverseasApplicationServiceImpl extends ServiceImpl<RsOverseasAppl
|
||||
|
||||
/**
|
||||
* 🆕 根据当前登录用户的审批权限,获取其可审批的级别列表
|
||||
*
|
||||
*
|
||||
* 权限到级别的映射:
|
||||
* - rs:overseas:approve1 → 审批级别 "1" (部门负责人)
|
||||
* - rs:overseas:approve2 → 审批级别 "2" (人事处)
|
||||
* - rs:overseas:approve3 → 审批级别 "3" (纪委)
|
||||
* - rs:overseas:approve4 → 审批级别 "4" (学院分管领导)
|
||||
* - rs:overseas:approve5 → 审批级别 "5" (学院党委书记)
|
||||
*
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 可审批的级别列表,例如:["2", "3"]
|
||||
*/
|
||||
@@ -1371,7 +1547,7 @@ public class RsOverseasApplicationServiceImpl extends ServiceImpl<RsOverseasAppl
|
||||
|
||||
/**
|
||||
* 🆕 根据审批级别获取对应的权限标识
|
||||
*
|
||||
*
|
||||
* @param approvalLevel 审批级别(2-5)
|
||||
* @return 权限标识,例如:"rs:overseas:approve2"
|
||||
*/
|
||||
@@ -1396,7 +1572,7 @@ public class RsOverseasApplicationServiceImpl extends ServiceImpl<RsOverseasAppl
|
||||
|
||||
/**
|
||||
* 🔴 新增方法:验证审批人是否有指定级别的审批权限
|
||||
*
|
||||
*
|
||||
* @param approverId 审批人ID
|
||||
* @param approvalLevel 需要的审批级别(1-5)
|
||||
* @return true-有权限,false-无权限
|
||||
@@ -1423,4 +1599,4 @@ public class RsOverseasApplicationServiceImpl extends ServiceImpl<RsOverseasAppl
|
||||
return hasPermission;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+1
@@ -311,6 +311,7 @@ public class RsOverseasApprovalMonitorServiceImpl implements IRsOverseasApproval
|
||||
vo.setMobilePhone(application.getMobilePhone());
|
||||
vo.setContactPhone(application.getContactPhone());
|
||||
vo.setEmergencyContact(application.getEmergencyContact());
|
||||
vo.setSignature(application.getSignature());
|
||||
|
||||
// 审批状态信息
|
||||
vo.setStatus(application.getStatus());
|
||||
|
||||
+190
-18
@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.rs.domain.RsOverseasItineraryChange;
|
||||
@@ -13,14 +14,23 @@ import org.dromara.rs.domain.bo.RsOverseasItineraryChangeBo;
|
||||
import org.dromara.rs.domain.query.RsOverseasItineraryChangeQuery;
|
||||
import org.dromara.rs.domain.vo.RsOverseasItineraryChangeVo;
|
||||
import org.dromara.rs.mapper.RsOverseasItineraryChangeMapper;
|
||||
import org.dromara.rs.mapper.RsOverseasApplicationMapper;
|
||||
import org.dromara.rs.mapper.RsOverseasRecordMapper;
|
||||
import org.dromara.rs.domain.RsOverseasApplication;
|
||||
import org.dromara.rs.domain.RsOverseasRecord;
|
||||
import org.dromara.rs.domain.vo.RsOverseasRecordVo;
|
||||
import org.dromara.rs.service.IRsOverseasItineraryChangeService;
|
||||
import org.dromara.rs.service.IRsOverseasApplicationService;
|
||||
import org.dromara.rs.service.IRsOverseasRecordService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
|
||||
/**
|
||||
* 出国行程变更申请Service业务层处理
|
||||
@@ -35,14 +45,38 @@ public class RsOverseasItineraryChangeServiceImpl implements IRsOverseasItinerar
|
||||
|
||||
private final RsOverseasItineraryChangeMapper changeMapper;
|
||||
private final IRsOverseasApplicationService applicationService;
|
||||
private final RsOverseasApplicationMapper applicationMapper;
|
||||
private final IRsOverseasRecordService recordService;
|
||||
private final RsOverseasRecordMapper recordMapper;
|
||||
|
||||
/**
|
||||
* 查询出国行程变更申请分页列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<RsOverseasItineraryChangeVo> queryPageList(RsOverseasItineraryChangeQuery query) {
|
||||
// 查询所有符合条件的数据(不分页)
|
||||
List<RsOverseasItineraryChangeVo> list = changeMapper.selectChangeList(query);
|
||||
return TableDataInfo.build(list);
|
||||
|
||||
// 手动分页处理
|
||||
int pageNum = query.getPageNum();
|
||||
int pageSize = query.getPageSize();
|
||||
int total = list.size();
|
||||
int start = (pageNum - 1) * pageSize;
|
||||
int end = Math.min(start + pageSize, total);
|
||||
|
||||
List<RsOverseasItineraryChangeVo> pageList;
|
||||
if (start >= total) {
|
||||
pageList = new ArrayList<>();
|
||||
} else {
|
||||
pageList = list.subList(start, end);
|
||||
}
|
||||
|
||||
// 构建分页对象
|
||||
Page<RsOverseasItineraryChangeVo> page = new Page<>(pageNum, pageSize);
|
||||
page.setRecords(pageList);
|
||||
page.setTotal(total);
|
||||
|
||||
return TableDataInfo.build(page);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,11 +123,12 @@ public class RsOverseasItineraryChangeServiceImpl implements IRsOverseasItinerar
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean insertByBo(RsOverseasItineraryChangeBo bo) {
|
||||
// 查询出国申请信息
|
||||
org.dromara.rs.domain.vo.RsOverseasApplicationVo application = applicationService.queryById(bo.getApplicationId());
|
||||
org.dromara.rs.domain.vo.RsOverseasApplicationVo application = applicationService
|
||||
.queryById(bo.getApplicationId());
|
||||
if (application == null) {
|
||||
throw new ServiceException("出国申请不存在");
|
||||
}
|
||||
|
||||
|
||||
// 检查申请状态是否为已通过
|
||||
if (!"1".equals(application.getStatus())) {
|
||||
throw new ServiceException("只能为已通过审批的出国申请提交变更申请");
|
||||
@@ -102,8 +137,8 @@ public class RsOverseasItineraryChangeServiceImpl implements IRsOverseasItinerar
|
||||
// 检查是否有未完成的变更申请
|
||||
LambdaQueryWrapper<RsOverseasItineraryChange> wrapper = Wrappers.lambdaQuery();
|
||||
wrapper.eq(RsOverseasItineraryChange::getApplicationId, bo.getApplicationId())
|
||||
.in(RsOverseasItineraryChange::getStatus, Arrays.asList("0")) // 待审核
|
||||
.eq(RsOverseasItineraryChange::getDeleted, 0);
|
||||
.in(RsOverseasItineraryChange::getStatus, Arrays.asList("0")) // 待审核
|
||||
.eq(RsOverseasItineraryChange::getDeleted, 0);
|
||||
Long count = changeMapper.selectCount(wrapper);
|
||||
if (count > 0) {
|
||||
throw new ServiceException("该出国申请已有待审核的变更申请,请先处理");
|
||||
@@ -117,11 +152,12 @@ public class RsOverseasItineraryChangeServiceImpl implements IRsOverseasItinerar
|
||||
entity.setApplicantId(userId);
|
||||
entity.setApplicantName(LoginHelper.getUsername());
|
||||
entity.setApplicationId(bo.getApplicationId());
|
||||
|
||||
|
||||
// 从出国申请中复制原始信息
|
||||
entity.setOriginalDestination(application.getDestination());
|
||||
entity.setOriginalDestinationCity(application.getDestinationCity());
|
||||
entity.setOriginalExitReason(application.getExitReason() != null ? application.getExitReason() : application.getPurposeDetail());
|
||||
entity.setOriginalExitReason(
|
||||
application.getExitReason() != null ? application.getExitReason() : application.getPurposeDetail());
|
||||
entity.setOriginalStartDate(application.getStartDate());
|
||||
entity.setOriginalEndDate(application.getEndDate());
|
||||
entity.setOriginalDuration(application.getDuration());
|
||||
@@ -148,9 +184,10 @@ public class RsOverseasItineraryChangeServiceImpl implements IRsOverseasItinerar
|
||||
throw new ServiceException("变更申请不存在");
|
||||
}
|
||||
|
||||
// 只有待审核或已拒绝的申请可以修改
|
||||
if (!"0".equals(original.getStatus()) && !"2".equals(original.getStatus())) {
|
||||
throw new ServiceException("只有待审核或已拒绝的变更申请可以修改");
|
||||
// 只有待审核、已拒绝或已撤回的申请可以修改
|
||||
if (!"0".equals(original.getStatus()) && !"2".equals(original.getStatus())
|
||||
&& !"3".equals(original.getStatus())) {
|
||||
throw new ServiceException("只有待审核、已拒绝或已撤回的变更申请可以修改");
|
||||
}
|
||||
|
||||
// 检查权限:只能修改自己的申请
|
||||
@@ -161,7 +198,7 @@ public class RsOverseasItineraryChangeServiceImpl implements IRsOverseasItinerar
|
||||
|
||||
// 转换BO到Entity
|
||||
RsOverseasItineraryChange entity = BeanUtil.toBean(bo, RsOverseasItineraryChange.class);
|
||||
|
||||
|
||||
// 重新提交时,状态改为待审核
|
||||
entity.setStatus("0");
|
||||
entity.setApprovalOpinion(null);
|
||||
@@ -179,24 +216,24 @@ public class RsOverseasItineraryChangeServiceImpl implements IRsOverseasItinerar
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean deleteByIds(List<Long> changeIds) {
|
||||
Long userId = LoginHelper.getUserId();
|
||||
|
||||
|
||||
for (Long changeId : changeIds) {
|
||||
RsOverseasItineraryChange change = changeMapper.selectById(changeId);
|
||||
if (change == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// 检查权限:只能删除自己的申请
|
||||
if (!userId.equals(change.getApplicantId())) {
|
||||
throw new ServiceException("只能删除自己的变更申请");
|
||||
}
|
||||
|
||||
|
||||
// 只有待审核或已拒绝的申请可以删除
|
||||
if (!"0".equals(change.getStatus()) && !"2".equals(change.getStatus())) {
|
||||
throw new ServiceException("只有待审核或已拒绝的变更申请可以删除");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return changeMapper.deleteBatchIds(changeIds) > 0;
|
||||
}
|
||||
|
||||
@@ -229,6 +266,12 @@ public class RsOverseasItineraryChangeServiceImpl implements IRsOverseasItinerar
|
||||
|
||||
/**
|
||||
* 审批变更申请
|
||||
*
|
||||
* 🔴 审批通过后,需要更新原申请单和记录的数据:
|
||||
* 1. 前往国家 → 更新到 rs_overseas_application 和 rs_overseas_record
|
||||
* 2. 实际出行开始时间、结束时间 → 更新到 rs_overseas_application 和 rs_overseas_record
|
||||
* 3. 行程安排 → 更新到 rs_overseas_application 和 rs_overseas_record
|
||||
* 4. 出境事由 → 追加到 rs_overseas_application 的备注字段
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@@ -249,9 +292,139 @@ public class RsOverseasItineraryChangeServiceImpl implements IRsOverseasItinerar
|
||||
change.setApprovalOpinion(opinion);
|
||||
change.setApprovalTime(new Date());
|
||||
change.setApproverId(userId);
|
||||
change.setApproverName(LoginHelper.getUsername());
|
||||
String displayName = StringUtils.defaultIfBlank(LoginHelper.getNickname(), LoginHelper.getUsername());
|
||||
change.setApproverName(displayName);
|
||||
|
||||
return changeMapper.updateById(change) > 0;
|
||||
// 更新变更申请状态
|
||||
boolean updateChangeResult = changeMapper.updateById(change) > 0;
|
||||
if (!updateChangeResult) {
|
||||
throw new ServiceException("更新变更申请状态失败");
|
||||
}
|
||||
|
||||
// 🔴 如果审批通过,需要更新原申请单和记录的数据
|
||||
if (approved && change.getApplicationId() != null) {
|
||||
log.info("🔧 [审批通过] 开始更新原申请单和记录数据,变更ID: {}, 申请ID: {}", changeId, change.getApplicationId());
|
||||
|
||||
try {
|
||||
// 1. 更新 rs_overseas_application 表
|
||||
RsOverseasApplication application = applicationMapper.selectById(change.getApplicationId());
|
||||
if (application != null) {
|
||||
LambdaUpdateWrapper<RsOverseasApplication> appUpdateWrapper = new LambdaUpdateWrapper<>();
|
||||
appUpdateWrapper.eq(RsOverseasApplication::getApplicationId, change.getApplicationId());
|
||||
|
||||
// 更新前往国家(destination 和 destinationCountry 同步更新)
|
||||
if (change.getNewDestination() != null && !change.getNewDestination().trim().isEmpty()) {
|
||||
appUpdateWrapper.set(RsOverseasApplication::getDestination, change.getNewDestination());
|
||||
appUpdateWrapper.set(RsOverseasApplication::getDestinationCountry, change.getNewDestination());
|
||||
log.info("✅ [更新申请单] 前往国家: {} (同步更新 destination 和 destinationCountry)",
|
||||
change.getNewDestination());
|
||||
}
|
||||
|
||||
// 更新出发日期和返回日期
|
||||
if (change.getNewStartDate() != null) {
|
||||
appUpdateWrapper.set(RsOverseasApplication::getStartDate, change.getNewStartDate());
|
||||
log.info("✅ [更新申请单] 出发日期: {}", change.getNewStartDate());
|
||||
}
|
||||
if (change.getNewEndDate() != null) {
|
||||
appUpdateWrapper.set(RsOverseasApplication::getEndDate, change.getNewEndDate());
|
||||
log.info("✅ [更新申请单] 返回日期: {}", change.getNewEndDate());
|
||||
|
||||
// 重新计算出国天数
|
||||
if (change.getNewStartDate() != null && change.getNewEndDate() != null) {
|
||||
long diffInMillies = change.getNewEndDate().getTime() - change.getNewStartDate().getTime();
|
||||
int days = (int) (diffInMillies / (1000 * 60 * 60 * 24)) + 1;
|
||||
appUpdateWrapper.set(RsOverseasApplication::getDuration, days);
|
||||
log.info("✅ [更新申请单] 出国天数: {} 天", days);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新行程安排
|
||||
if (change.getNewItinerary() != null && !change.getNewItinerary().trim().isEmpty()) {
|
||||
appUpdateWrapper.set(RsOverseasApplication::getItinerary, change.getNewItinerary());
|
||||
log.info("✅ [更新申请单] 行程安排已更新");
|
||||
}
|
||||
|
||||
// 追加出境事由到备注字段
|
||||
if (change.getNewExitReason() != null && !change.getNewExitReason().trim().isEmpty()) {
|
||||
String currentRemarks = application.getRemarks() != null ? application.getRemarks() : "";
|
||||
String newRemarks = currentRemarks.isEmpty()
|
||||
? "行程变更-出境事由: " + change.getNewExitReason()
|
||||
: currentRemarks + "\n行程变更-出境事由: " + change.getNewExitReason();
|
||||
appUpdateWrapper.set(RsOverseasApplication::getRemarks, newRemarks);
|
||||
log.info("✅ [更新申请单] 备注已追加出境事由: {}", change.getNewExitReason());
|
||||
}
|
||||
|
||||
boolean appUpdateResult = applicationMapper.update(null, appUpdateWrapper) > 0;
|
||||
if (!appUpdateResult) {
|
||||
log.warn("⚠️ [更新申请单] 更新失败,申请ID: {}", change.getApplicationId());
|
||||
} else {
|
||||
log.info("✅ [更新申请单] 更新成功,申请ID: {}", change.getApplicationId());
|
||||
}
|
||||
} else {
|
||||
log.warn("⚠️ [更新申请单] 申请单不存在,申请ID: {}", change.getApplicationId());
|
||||
}
|
||||
|
||||
// 2. 更新 rs_overseas_record 表
|
||||
RsOverseasRecordVo recordVo = recordMapper
|
||||
.selectOverseasRecordByApplicationId(change.getApplicationId());
|
||||
if (recordVo != null) {
|
||||
RsOverseasRecord record = recordMapper.selectById(recordVo.getRecordId());
|
||||
if (record != null) {
|
||||
LambdaUpdateWrapper<RsOverseasRecord> recordUpdateWrapper = new LambdaUpdateWrapper<>();
|
||||
recordUpdateWrapper.eq(RsOverseasRecord::getRecordId, record.getRecordId());
|
||||
|
||||
// 更新前往国家
|
||||
if (change.getNewDestination() != null && !change.getNewDestination().trim().isEmpty()) {
|
||||
recordUpdateWrapper.set(RsOverseasRecord::getDestination, change.getNewDestination());
|
||||
log.info("✅ [更新记录] 前往国家: {}", change.getNewDestination());
|
||||
}
|
||||
|
||||
// 更新实际开始日期和结束日期
|
||||
if (change.getNewStartDate() != null) {
|
||||
recordUpdateWrapper.set(RsOverseasRecord::getActualStartDate, change.getNewStartDate());
|
||||
log.info("✅ [更新记录] 实际开始日期: {}", change.getNewStartDate());
|
||||
}
|
||||
if (change.getNewEndDate() != null) {
|
||||
recordUpdateWrapper.set(RsOverseasRecord::getActualEndDate, change.getNewEndDate());
|
||||
log.info("✅ [更新记录] 实际结束日期: {}", change.getNewEndDate());
|
||||
|
||||
// 重新计算实际出国天数
|
||||
if (change.getNewStartDate() != null && change.getNewEndDate() != null) {
|
||||
long diffInMillies = change.getNewEndDate().getTime()
|
||||
- change.getNewStartDate().getTime();
|
||||
int days = (int) (diffInMillies / (1000 * 60 * 60 * 24)) + 1;
|
||||
recordUpdateWrapper.set(RsOverseasRecord::getActualDuration, days);
|
||||
log.info("✅ [更新记录] 实际出国天数: {} 天", days);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新行程安排
|
||||
if (change.getNewItinerary() != null && !change.getNewItinerary().trim().isEmpty()) {
|
||||
recordUpdateWrapper.set(RsOverseasRecord::getItinerary, change.getNewItinerary());
|
||||
log.info("✅ [更新记录] 行程安排已更新");
|
||||
}
|
||||
|
||||
boolean recordUpdateResult = recordMapper.update(null, recordUpdateWrapper) > 0;
|
||||
if (!recordUpdateResult) {
|
||||
log.warn("⚠️ [更新记录] 更新失败,记录ID: {}", record.getRecordId());
|
||||
} else {
|
||||
log.info("✅ [更新记录] 更新成功,记录ID: {}", record.getRecordId());
|
||||
}
|
||||
} else {
|
||||
log.warn("⚠️ [更新记录] 记录不存在,记录ID: {}", recordVo.getRecordId());
|
||||
}
|
||||
} else {
|
||||
log.warn("⚠️ [更新记录] 未找到关联记录,申请ID: {}", change.getApplicationId());
|
||||
}
|
||||
|
||||
log.info("✅ [审批通过] 数据更新完成,变更ID: {}", changeId);
|
||||
} catch (Exception e) {
|
||||
log.error("❌ [审批通过] 更新原申请单和记录数据失败,变更ID: {}, 错误: {}", changeId, e.getMessage(), e);
|
||||
throw new ServiceException("审批通过后更新原申请单和记录数据失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -262,4 +435,3 @@ public class RsOverseasItineraryChangeServiceImpl implements IRsOverseasItinerar
|
||||
return changeMapper.selectChangeListByRecordId(recordId);
|
||||
}
|
||||
}
|
||||
|
||||
+37
@@ -617,5 +617,42 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</if>
|
||||
ORDER BY a.apply_time DESC, a.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 🔴 新增:根据角色查询待审批申请列表(支持分页和搜索) -->
|
||||
<select id="selectPendingApplicationsByRole" resultMap="RsOverseasApplicationVoResult">
|
||||
<include refid="selectOverseasApplicationVo"/>
|
||||
<where>
|
||||
oa.deleted = '0'
|
||||
AND oa.status = '0'
|
||||
<!-- 审批级别过滤 -->
|
||||
<if test="approvalLevels != null and approvalLevels.size() > 0">
|
||||
AND oa.approval_level IN
|
||||
<foreach collection="approvalLevels" item="level" open="(" separator="," close=")">
|
||||
#{level}
|
||||
</foreach>
|
||||
</if>
|
||||
<!-- 部门过滤(仅对部门负责人生效) -->
|
||||
<if test="deptId != null">
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM sys_user u
|
||||
WHERE u.user_id = oa.applicant_id
|
||||
AND u.dept_id = #{deptId}
|
||||
)
|
||||
</if>
|
||||
<!-- 搜索条件:前往国家 -->
|
||||
<if test="query != null and query.destination != null and query.destination != ''">
|
||||
AND oa.destination LIKE CONCAT('%', #{query.destination}, '%')
|
||||
</if>
|
||||
<!-- 搜索条件:出境事由 -->
|
||||
<if test="query != null and query.exitReason != null and query.exitReason != ''">
|
||||
AND oa.exit_reason = #{query.exitReason}
|
||||
</if>
|
||||
<!-- 租户过滤 -->
|
||||
<if test="query != null and query.tenantId != null and query.tenantId != ''">
|
||||
AND oa.tenant_id = #{query.tenantId}
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY oa.create_time DESC
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
+5
-8
@@ -74,8 +74,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<sql id="selectOverseasRecordDetailVo">
|
||||
select
|
||||
ors.record_id, ors.application_id, ors.applicant_id, ors.applicant_name, ors.department,
|
||||
ors.position,
|
||||
COALESCE(sp.post_name, ors.position) as position_text,
|
||||
oa.position,
|
||||
oa.position as position_text,
|
||||
ors.contact_phone, ors.contact_email, ors.purpose, ors.purpose_detail, ors.destination, ors.destination_city,
|
||||
ors.actual_start_date, ors.actual_end_date, ors.actual_duration, ors.actual_budget,
|
||||
ors.invitation, ors.itinerary, ors.accommodation_plan, ors.transportation_plan,
|
||||
@@ -134,7 +134,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
'record' as data_source
|
||||
from rs_overseas_record ors
|
||||
left join rs_overseas_application oa on ors.application_id = oa.application_id and oa.deleted = '0'
|
||||
left join sys_post sp on (ors.position = CAST(sp.post_id AS CHAR) or ors.position = sp.post_code) and sp.status = '0'
|
||||
</sql>
|
||||
|
||||
<!-- 联合查询:出国记录 + 已完成审批的申请 -->
|
||||
@@ -142,8 +141,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
-- 查询已创建的出国记录(关联申请表获取扩展字段)
|
||||
select
|
||||
ors.record_id, ors.application_id, ors.applicant_id, ors.applicant_name, ors.department,
|
||||
ors.position,
|
||||
COALESCE(sp1.post_name, ors.position) as position_text,
|
||||
oa.position,
|
||||
oa.position as position_text,
|
||||
ors.contact_phone, ors.contact_email, ors.purpose, ors.purpose_detail, ors.destination, ors.destination_city,
|
||||
ors.actual_start_date as actual_start_date, ors.actual_end_date as actual_end_date, ors.actual_duration as actual_duration,
|
||||
ors.actual_budget as actual_budget,
|
||||
@@ -212,7 +211,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
'record' as data_source
|
||||
from rs_overseas_record ors
|
||||
left join rs_overseas_application oa on ors.application_id = oa.application_id and oa.deleted = '0'
|
||||
left join sys_post sp1 on (ors.position = CAST(sp1.post_id AS CHAR) or ors.position = sp1.post_code) and sp1.status = '0'
|
||||
where ors.deleted = '0'
|
||||
|
||||
UNION ALL
|
||||
@@ -222,7 +220,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
NULL as record_id,
|
||||
oa.application_id, oa.applicant_id, oa.applicant_name, oa.department,
|
||||
oa.position,
|
||||
COALESCE(sp2.post_name, oa.position) as position_text,
|
||||
oa.position as position_text,
|
||||
oa.contact_phone, oa.contact_email, oa.purpose, oa.purpose_detail, oa.destination, oa.destination_city,
|
||||
oa.start_date as actual_start_date, oa.end_date as actual_end_date, oa.duration as actual_duration,
|
||||
NULL as actual_budget,
|
||||
@@ -284,7 +282,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
'进行中' as completion_status_text,
|
||||
'approved_application' as data_source
|
||||
from rs_overseas_application oa
|
||||
left join sys_post sp2 on (oa.position = CAST(sp2.post_id AS CHAR) or oa.position = sp2.post_code) and sp2.status = '0'
|
||||
where oa.deleted = '0'
|
||||
and oa.status = '1'
|
||||
and not exists (
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
-- 招聘模块表结构
|
||||
-- 创建于 2025-07-02
|
||||
|
||||
-- 职位表
|
||||
DROP TABLE IF EXISTS `rs_job`;
|
||||
CREATE TABLE `rs_job` (
|
||||
`job_id` bigint(20) NOT NULL COMMENT '职位ID',
|
||||
`title` varchar(100) NOT NULL COMMENT '职位名称',
|
||||
`department` varchar(50) NOT NULL COMMENT '部门',
|
||||
`salary_range` varchar(50) DEFAULT NULL COMMENT '薪资范围',
|
||||
`location` varchar(100) DEFAULT NULL COMMENT '工作地点',
|
||||
`description` text COMMENT '职位描述',
|
||||
`requirements` text COMMENT '任职要求',
|
||||
`is_active` char(1) DEFAULT '0' COMMENT '是否在招(0是 1否)',
|
||||
`publish_time` datetime DEFAULT NULL COMMENT '发布时间',
|
||||
`view_count` int(11) DEFAULT '0' COMMENT '浏览次数',
|
||||
`tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号',
|
||||
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
||||
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
|
||||
`deleted` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)',
|
||||
PRIMARY KEY (`job_id`),
|
||||
KEY `idx_department` (`department`),
|
||||
KEY `idx_is_active` (`is_active`)
|
||||
) ENGINE=InnoDB COMMENT='招聘职位表';
|
||||
|
||||
-- 应聘者表
|
||||
DROP TABLE IF EXISTS `rs_applicant`;
|
||||
CREATE TABLE `rs_applicant` (
|
||||
`applicant_id` bigint(20) NOT NULL COMMENT '应聘者ID',
|
||||
`user_id` bigint(20) DEFAULT NULL COMMENT '关联用户ID',
|
||||
`name` varchar(50) NOT NULL COMMENT '姓名',
|
||||
`phone` varchar(20) NOT NULL COMMENT '手机号码',
|
||||
`email` varchar(100) NOT NULL COMMENT '电子邮箱',
|
||||
`resume_file_path` varchar(255) DEFAULT NULL COMMENT '简历文件路径',
|
||||
`self_intro` text COMMENT '自我介绍',
|
||||
`current_status` varchar(20) DEFAULT 'active' COMMENT '当前状态(active-可申请 hired-已录用 rejected-已拒绝)',
|
||||
`tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号',
|
||||
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
|
||||
`deleted` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)',
|
||||
PRIMARY KEY (`applicant_id`),
|
||||
KEY `idx_user_id` (`user_id`),
|
||||
KEY `idx_phone` (`phone`)
|
||||
) ENGINE=InnoDB COMMENT='应聘者信息表';
|
||||
|
||||
-- 教育经历表
|
||||
DROP TABLE IF EXISTS `rs_education`;
|
||||
CREATE TABLE `rs_education` (
|
||||
`edu_id` bigint(20) NOT NULL COMMENT '教育经历ID',
|
||||
`applicant_id` bigint(20) NOT NULL COMMENT '应聘者ID',
|
||||
`school_name` varchar(100) NOT NULL COMMENT '学校名称',
|
||||
`major` varchar(100) DEFAULT NULL COMMENT '专业',
|
||||
`degree` varchar(50) DEFAULT NULL COMMENT '学位',
|
||||
`start_date` date DEFAULT NULL COMMENT '开始日期',
|
||||
`end_date` date DEFAULT NULL COMMENT '结束日期',
|
||||
`gpa` varchar(20) DEFAULT NULL COMMENT '绩点',
|
||||
`description` text COMMENT '描述',
|
||||
`tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号',
|
||||
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`deleted` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)',
|
||||
PRIMARY KEY (`edu_id`),
|
||||
KEY `idx_applicant_id` (`applicant_id`)
|
||||
) ENGINE=InnoDB COMMENT='教育经历表';
|
||||
|
||||
-- 工作经历表
|
||||
DROP TABLE IF EXISTS `rs_work_experience`;
|
||||
CREATE TABLE `rs_work_experience` (
|
||||
`exp_id` bigint(20) NOT NULL COMMENT '工作经历ID',
|
||||
`applicant_id` bigint(20) NOT NULL COMMENT '应聘者ID',
|
||||
`company_name` varchar(100) NOT NULL COMMENT '公司名称',
|
||||
`position` varchar(100) NOT NULL COMMENT '职位',
|
||||
`start_date` date DEFAULT NULL COMMENT '开始日期',
|
||||
`end_date` date DEFAULT NULL COMMENT '结束日期',
|
||||
`is_current` char(1) DEFAULT '0' COMMENT '是否当前工作(0是 1否)',
|
||||
`description` text COMMENT '工作描述',
|
||||
`tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号',
|
||||
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`deleted` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)',
|
||||
PRIMARY KEY (`exp_id`),
|
||||
KEY `idx_applicant_id` (`applicant_id`)
|
||||
) ENGINE=InnoDB COMMENT='工作经历表';
|
||||
|
||||
-- 申请记录表
|
||||
DROP TABLE IF EXISTS `rs_application`;
|
||||
CREATE TABLE `rs_application` (
|
||||
`application_id` bigint(20) NOT NULL COMMENT '申请ID',
|
||||
`job_id` bigint(20) NOT NULL COMMENT '职位ID',
|
||||
`applicant_id` bigint(20) NOT NULL COMMENT '应聘者ID',
|
||||
`apply_time` datetime NOT NULL COMMENT '申请时间',
|
||||
`status` varchar(20) DEFAULT 'pending' COMMENT '状态(pending-待处理 interviewed-已面试 offered-已录用 rejected-已拒绝)',
|
||||
`hr_notes` text COMMENT 'HR备注',
|
||||
`tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号',
|
||||
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
|
||||
`deleted` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)',
|
||||
PRIMARY KEY (`application_id`),
|
||||
KEY `idx_job_id` (`job_id`),
|
||||
KEY `idx_applicant_id` (`applicant_id`),
|
||||
KEY `idx_status` (`status`)
|
||||
) ENGINE=InnoDB COMMENT='申请记录表';
|
||||
|
||||
-- 附件表
|
||||
DROP TABLE IF EXISTS `rs_attachment`;
|
||||
CREATE TABLE `rs_attachment` (
|
||||
`attachment_id` bigint(20) NOT NULL COMMENT '附件ID',
|
||||
`application_id` bigint(20) NOT NULL COMMENT '申请ID',
|
||||
`file_name` varchar(100) NOT NULL COMMENT '文件名',
|
||||
`file_path` varchar(255) NOT NULL COMMENT '文件路径',
|
||||
`file_type` varchar(50) DEFAULT NULL COMMENT '文件类型',
|
||||
`file_size` bigint(20) DEFAULT NULL COMMENT '文件大小(字节)',
|
||||
`tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号',
|
||||
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`deleted` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)',
|
||||
PRIMARY KEY (`attachment_id`),
|
||||
KEY `idx_application_id` (`application_id`)
|
||||
) ENGINE=InnoDB COMMENT='申请附件表';
|
||||
|
||||
-- 插入初始测试数据
|
||||
INSERT INTO `rs_job` (`job_id`, `title`, `department`, `salary_range`, `location`, `description`, `requirements`, `is_active`, `create_time`, `create_by`, `update_time`, `update_by`, `remark`, `deleted`, `tenant_id`)
|
||||
VALUES
|
||||
(1, '高级Java开发工程师', '教学', '15k-25k', '杭州', '负责校园教学管理系统的开发和维护,参与核心模块设计与实现,优化系统性能,解决技术难题。\n作为团队核心成员,参与技术方案评审,指导初级开发人员,确保项目按时高质量交付。', '计算机相关专业本科及以上学历,3年以上Java开发经验\n精通Java核心技术,熟悉Spring Boot、MyBatis等主流框架\n熟练掌握MySQL数据库设计与优化\n有教育行业相关系统开发经验者优先\n良好的沟通能力和团队协作精神', '0', NOW(), 1, NOW(), 1, '示例职位', '0', '000000'),
|
||||
(2, '前端工程师', '教学', '12k-18k', '杭州', '负责校园教学管理系统前端开发,使用Vue3+TypeScript技术栈,参与UI设计与实现', '计算机相关专业本科及以上学历,2年以上前端开发经验\n精通HTML5/CSS3/JavaScript,熟悉Vue3/React等主流框架\n熟练使用TypeScript进行开发\n有教育行业相关系统开发经验者优先', '0', NOW(), 1, NOW(), 1, '示例职位', '0', '000000'),
|
||||
(3, '行政助理', '行政', '5k-8k', '杭州', '协助部门主管处理日常行政事务,包括文件整理、会议安排、接待来访等工作', '大专及以上学历,行政管理相关专业优先\n熟悉办公软件,有较强的文字处理能力\n工作认真负责,有较强的组织协调能力\n有相关工作经验者优先', '0', NOW(), 1, NOW(), 1, '示例职位', '0', '000000');
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
-- -----------------------------
|
||||
-- 删除培训申请表中的提交次数字段
|
||||
-- -----------------------------
|
||||
|
||||
-- 删除 rs_training_application 表中的 submit_count 字段
|
||||
-- 注意:MySQL 5.7及以下版本不支持 DROP COLUMN IF EXISTS 语法
|
||||
-- 需要先检查字段是否存在,然后删除
|
||||
ALTER TABLE rs_training_application DROP COLUMN submit_count;
|
||||
|
||||
-- 验证字段已删除
|
||||
DESCRIBE rs_training_application;
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
-- -----------------------------
|
||||
-- 培训审批数据一致性检查和修复
|
||||
-- -----------------------------
|
||||
|
||||
-- 检查审批记录表是否为空
|
||||
SELECT COUNT(*) AS approval_count FROM rs_training_approval WHERE deleted = '0';
|
||||
|
||||
-- 检查审批记录与申请记录是否一致
|
||||
SELECT
|
||||
COUNT(*) AS inconsistent_count
|
||||
FROM
|
||||
rs_training_approval a
|
||||
LEFT JOIN
|
||||
rs_training_application app ON a.application_id = app.application_id
|
||||
WHERE
|
||||
a.deleted = '0'
|
||||
AND (app.application_id IS NULL OR app.deleted = '1');
|
||||
|
||||
-- 插入测试数据(如果没有审批记录)
|
||||
INSERT INTO rs_training_approval (
|
||||
approval_id, application_id, approver_id, approver_name,
|
||||
approval_level, status, opinion, approval_time, tenant_id,
|
||||
create_by, create_time, deleted
|
||||
)
|
||||
SELECT
|
||||
FLOOR(RAND() * 1000000) + 100000, -- 随机审批ID
|
||||
app.application_id, -- 使用真实申请ID
|
||||
app.create_by, -- 使用申请的创建者ID作为审批人ID
|
||||
(SELECT user_name FROM sys_user WHERE user_id = app.create_by LIMIT 1), -- 获取用户名
|
||||
1, -- 审批级别
|
||||
app.status, -- 使用申请状态
|
||||
'系统自动生成的测试审批记录', -- 审批意见
|
||||
app.update_time, -- 使用申请更新时间作为审批时间
|
||||
app.tenant_id, -- 租户ID
|
||||
app.create_by, -- 创建者
|
||||
NOW(), -- 创建时间
|
||||
'0' -- 未删除
|
||||
FROM
|
||||
rs_training_application app
|
||||
WHERE
|
||||
app.deleted = '0'
|
||||
AND app.status IN ('1', '2') -- 已通过或已拒绝
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM rs_training_approval a
|
||||
WHERE a.application_id = app.application_id AND a.deleted = '0'
|
||||
)
|
||||
LIMIT 5; -- 限制生成5条测试数据
|
||||
|
||||
-- 查询生成的测试数据
|
||||
SELECT
|
||||
a.approval_id, a.application_id, a.approver_name, a.status, a.opinion, a.approval_time,
|
||||
app.user_name AS applicant_name, app.reason
|
||||
FROM
|
||||
rs_training_approval a
|
||||
JOIN
|
||||
rs_training_application app ON a.application_id = app.application_id
|
||||
WHERE
|
||||
a.deleted = '0'
|
||||
AND a.create_time > DATE_SUB(NOW(), INTERVAL 1 HOUR) -- 最近一小时创建的记录
|
||||
ORDER BY
|
||||
a.create_time DESC;
|
||||
@@ -1,38 +0,0 @@
|
||||
-- ----------------------------
|
||||
-- 培训管理模块测试数据
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- 培训课程表测试数据
|
||||
-- ----------------------------
|
||||
INSERT INTO `rs_training_course` (`course_id`, `title`, `description`, `instructor`, `duration`, `location`, `capacity`, `status`, `start_time`, `end_time`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `deleted`)
|
||||
VALUES
|
||||
(10001, '新入职教师培训', '针对新入职教师的岗前培训,包括教学理念、师德师风、教学方法等内容', '张教授', 16, '学术会议厅', 50, '1', '2025-08-01 09:00:00', '2025-08-03 17:00:00', '000000', 1, NOW(), 1, NOW(), '重要培训', '0'),
|
||||
(10002, '教育技术与信息化', '介绍现代教育技术在教学中的应用,包括多媒体教学、在线教育平台等', '李博士', 12, '多媒体教室', 30, '1', '2025-08-10 09:00:00', '2025-08-12 17:00:00', '000000', 1, NOW(), 1, NOW(), '技术培训', '0'),
|
||||
(10003, '教学方法与技能培训', '提升教师教学技能,包括课堂管理、学生互动、教学评估等', '王老师', 8, '实训楼', 40, '1', '2025-08-05 14:00:00', '2025-08-05 18:00:00', '000000', 1, NOW(), 1, NOW(), '实用培训', '0'),
|
||||
(10004, '学生管理与心理健康', '帮助教师了解学生心理,掌握学生管理技巧和心理疏导方法', '陈心理师', 6, '学术报告厅', 60, '1', '2025-08-15 14:00:00', '2025-08-15 18:00:00', '000000', 1, NOW(), 1, NOW(), '心理培训', '0'),
|
||||
(10005, '科研方法与学术写作', '指导教师进行科学研究,提升学术写作和论文发表能力', '赵教授', 20, '图书馆报告厅', 25, '1', '2025-08-25 09:00:00', '2025-08-30 17:00:00', '000000', 1, NOW(), 1, NOW(), '科研培训', '0');
|
||||
|
||||
-- ----------------------------
|
||||
-- 培训申请表测试数据
|
||||
-- ----------------------------
|
||||
INSERT INTO `rs_training_application` (`application_id`, `course_id`, `user_id`, `user_name`, `department`, `position`, `reason`, `status`, `apply_time`, `current_approver`, `process_instance_id`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `deleted`)
|
||||
VALUES
|
||||
(20001, 10001, 2, '张老师', '教务处', '教师', '新入职需要培训', '1', '2025-07-25 12:03:49', 'admin', NULL, '000000', 1, NOW(), 1, NOW(), NULL, '0'),
|
||||
(20002, 10002, 3, '王老师', '信息技术中心', '工程师', '学习新技术', '1', '2025-08-01 12:37:17', 'admin', NULL, '000000', 1, NOW(), 1, NOW(), '未完成', '0'),
|
||||
(20003, 10003, 4, '刘老师', '计算机学院', '讲师', '提升教学技能', '1', '2025-07-25 12:04:06', 'admin', NULL, '000000', 1, NOW(), 1, NOW(), '培训尚未结束', '0'),
|
||||
(20004, 10004, 5, '李老师', '学生处', '辅导员', '提升学生管理能力', '1', '2025-08-06 14:24:37', 'admin', NULL, '000000', 1, NOW(), 1, NOW(), '未完成', '0'),
|
||||
(20005, 10005, 6, '陈老师', '科研处', '副教授', '提升科研能力', '1', '2025-08-22 15:25:51', 'admin', NULL, '000000', 1, NOW(), 1, NOW(), '未完成', '0');
|
||||
|
||||
-- ----------------------------
|
||||
-- 培训审批表测试数据
|
||||
-- ----------------------------
|
||||
INSERT INTO `rs_training_approval` (`approval_id`, `application_id`, `approver_id`, `approver_name`, `approval_level`, `status`, `opinion`, `approval_time`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `deleted`)
|
||||
VALUES
|
||||
(30001, 20001, 101, '王主任', 1, '1', '同意参加,安排好工作交接', '2025-07-21 09:30:00', '000000', 101, NOW(), 101, NOW(), NULL, '0'),
|
||||
(30002, 20003, 102, '孙院长', 1, '1', '批准参加,对提升教学质量有帮助', '2025-07-23 10:15:00', '000000', 102, NOW(), 102, NOW(), NULL, '0'),
|
||||
(30003, 20004, 103, '周主任', 1, '2', '近期有重要工作交接,建议下次再参加', '2025-07-24 14:20:00', '000000', 103, NOW(), 103, NOW(), NULL, '0');
|
||||
|
||||
-- ----------------------------
|
||||
-- 培训记录表测试数据已删除,请通过申请界面新增
|
||||
-- ----------------------------
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
-- -----------------------------
|
||||
-- 培训审批数据修复脚本
|
||||
-- -----------------------------
|
||||
|
||||
-- 检查comment列是否存在,如果存在则迁移数据到opinion列
|
||||
SET @columnExists = 0;
|
||||
SELECT COUNT(*) INTO @columnExists
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'rs_training_approval'
|
||||
AND COLUMN_NAME = 'comment';
|
||||
|
||||
-- 如果comment列存在,将数据从comment迁移到opinion
|
||||
SET @sqlStmt = IF(@columnExists > 0,
|
||||
'UPDATE rs_training_approval SET opinion = comment WHERE opinion IS NULL AND comment IS NOT NULL',
|
||||
'SELECT "comment列不存在,无需迁移" AS message');
|
||||
|
||||
PREPARE stmt FROM @sqlStmt;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 添加测试数据(如果表为空)
|
||||
INSERT INTO rs_training_approval (
|
||||
approval_id, application_id, approver_id, approver_name,
|
||||
approval_level, status, opinion, approval_time, tenant_id,
|
||||
create_by, create_time, deleted
|
||||
)
|
||||
SELECT
|
||||
FLOOR(RAND() * 1000000) + 100000, -- 随机ID
|
||||
app.application_id, -- 关联到真实的申请ID
|
||||
1, -- 管理员ID
|
||||
'系统管理员', -- 管理员名称
|
||||
1, -- 第一级审批
|
||||
'1', -- 已通过状态
|
||||
'系统自动生成的测试审批记录', -- 审批意见
|
||||
NOW(), -- 当前时间
|
||||
app.tenant_id, -- 使用申请的租户ID
|
||||
app.create_by, -- 使用申请的创建者
|
||||
NOW(), -- 当前时间
|
||||
'0' -- 未删除
|
||||
FROM rs_training_application app
|
||||
WHERE NOT EXISTS (SELECT 1 FROM rs_training_approval)
|
||||
LIMIT 1;
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
-- -----------------------------
|
||||
-- 培训审批历史记录完整性更新脚本
|
||||
-- -----------------------------
|
||||
|
||||
-- 1. 在培训申请表中添加提交次数字段
|
||||
ALTER TABLE rs_training_application ADD COLUMN IF NOT EXISTS submit_count int DEFAULT 1 COMMENT '提交次数';
|
||||
|
||||
-- 2. 检查并移除审批表可能存在的唯一约束(允许一个申请有多条审批记录)
|
||||
ALTER TABLE rs_training_approval DROP INDEX IF EXISTS uk_application_approver;
|
||||
ALTER TABLE rs_training_approval DROP INDEX IF EXISTS uk_application_level;
|
||||
|
||||
-- 3. 确保审批表中有足够的索引来高效查询
|
||||
CREATE INDEX IF NOT EXISTS idx_training_approval_application ON rs_training_approval(application_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_training_approval_status ON rs_training_approval(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_training_approval_time ON rs_training_approval(approval_time);
|
||||
|
||||
-- 4. 添加审批记录表注释说明
|
||||
ALTER TABLE rs_training_approval COMMENT = '培训审批表(记录所有审批动作,包括通过和拒绝记录)';
|
||||
|
||||
-- 5. 更新已有数据的提交次数为1(如果有)
|
||||
UPDATE rs_training_application SET submit_count = 1 WHERE submit_count IS NULL;
|
||||
|
||||
-- 6. 检查并修复可能的数据不一致问题
|
||||
-- 查找有申请状态为已拒绝但没有对应拒绝记录的数据
|
||||
SELECT
|
||||
app.application_id,
|
||||
app.course_id,
|
||||
app.user_name,
|
||||
app.status,
|
||||
COUNT(appr.approval_id) as approval_count
|
||||
FROM
|
||||
rs_training_application app
|
||||
LEFT JOIN
|
||||
rs_training_approval appr ON app.application_id = appr.application_id
|
||||
WHERE
|
||||
app.status = '2' AND app.deleted = '0'
|
||||
GROUP BY
|
||||
app.application_id
|
||||
HAVING
|
||||
approval_count = 0;
|
||||
|
||||
-- 查找有申请状态为已通过但没有对应通过记录的数据
|
||||
SELECT
|
||||
app.application_id,
|
||||
app.course_id,
|
||||
app.user_name,
|
||||
app.status,
|
||||
COUNT(appr.approval_id) as approval_count
|
||||
FROM
|
||||
rs_training_application app
|
||||
LEFT JOIN
|
||||
rs_training_approval appr ON app.application_id = appr.application_id
|
||||
WHERE
|
||||
app.status = '1' AND app.deleted = '0'
|
||||
GROUP BY
|
||||
app.application_id
|
||||
HAVING
|
||||
approval_count = 0;
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
-- -----------------------------
|
||||
-- 培训审批历史记录更新
|
||||
-- -----------------------------
|
||||
|
||||
-- 确保审批表中没有唯一约束,允许一个申请有多条审批记录
|
||||
ALTER TABLE rs_training_approval DROP INDEX IF EXISTS uk_application_approver;
|
||||
|
||||
-- 添加审批意见列(如果不存在)
|
||||
ALTER TABLE rs_training_approval ADD COLUMN IF NOT EXISTS opinion varchar(500) COMMENT '审批意见' AFTER status;
|
||||
|
||||
-- 添加comment列的别名以保持向后兼容(如果存在)
|
||||
ALTER TABLE rs_training_approval CHANGE COLUMN comment opinion varchar(500) COMMENT '审批意见';
|
||||
|
||||
-- 更新审批记录表注释
|
||||
ALTER TABLE rs_training_approval COMMENT = '培训审批表(记录所有审批动作,包括拒绝记录)';
|
||||
|
||||
-- 添加相关索引以提高查询效率
|
||||
CREATE INDEX IF NOT EXISTS idx_training_approval_application ON rs_training_approval(application_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_training_approval_status ON rs_training_approval(status);
|
||||
@@ -1,17 +0,0 @@
|
||||
-- 更新培训课程表,添加create_dept字段
|
||||
ALTER TABLE rs_training_course ADD COLUMN create_dept bigint(20) COMMENT '创建部门' AFTER tenant_id;
|
||||
|
||||
-- 更新培训申请表,添加create_dept字段
|
||||
ALTER TABLE rs_training_application ADD COLUMN create_dept bigint(20) COMMENT '创建部门' AFTER tenant_id;
|
||||
|
||||
-- 更新培训申请表,添加location、duration、startTime和endTime字段
|
||||
ALTER TABLE rs_training_application ADD COLUMN location varchar(200) COMMENT '培训地点' AFTER current_approver;
|
||||
ALTER TABLE rs_training_application ADD COLUMN duration int(11) COMMENT '学时' AFTER location;
|
||||
ALTER TABLE rs_training_application ADD COLUMN start_time datetime COMMENT '开始时间' AFTER duration;
|
||||
ALTER TABLE rs_training_application ADD COLUMN end_time datetime COMMENT '结束时间' AFTER start_time;
|
||||
|
||||
-- 更新培训审批表,添加create_dept字段
|
||||
ALTER TABLE rs_training_approval ADD COLUMN create_dept bigint(20) COMMENT '创建部门' AFTER tenant_id;
|
||||
|
||||
-- 更新培训记录表,添加create_dept字段
|
||||
ALTER TABLE rs_training_record ADD COLUMN create_dept bigint(20) COMMENT '创建部门' AFTER tenant_id;
|
||||
@@ -1,122 +0,0 @@
|
||||
-- ----------------------------
|
||||
-- 培训管理模块数据库表
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- 培训课程表
|
||||
-- ----------------------------
|
||||
drop table if exists rs_training_course;
|
||||
create table rs_training_course (
|
||||
course_id bigint(20) not null comment '课程ID',
|
||||
title varchar(100) not null comment '课程名称',
|
||||
category varchar(50) not null comment '课程类别',
|
||||
description text comment '课程描述',
|
||||
start_time datetime not null comment '开始时间',
|
||||
end_time datetime not null comment '结束时间',
|
||||
duration int(11) not null comment '学时数',
|
||||
location varchar(200) comment '培训地点',
|
||||
max_participants int(11) default 0 comment '最大参与人数',
|
||||
current_participants int(11) default 0 comment '当前报名人数',
|
||||
status char(1) default '0' comment '状态(0正常 1停用)',
|
||||
tenant_id varchar(20) default '000000' comment '租户编号',
|
||||
create_by bigint(20) comment '创建者',
|
||||
create_time datetime comment '创建时间',
|
||||
update_by bigint(20) comment '更新者',
|
||||
update_time datetime comment '更新时间',
|
||||
remark varchar(500) comment '备注',
|
||||
deleted char(1) default '0' comment '删除标志',
|
||||
primary key (course_id)
|
||||
) engine=innodb comment = '培训课程表';
|
||||
|
||||
-- ----------------------------
|
||||
-- 培训申请表
|
||||
-- ----------------------------
|
||||
drop table if exists rs_training_application;
|
||||
create table rs_training_application (
|
||||
application_id bigint(20) not null comment '申请ID',
|
||||
course_name varchar(100) not null comment '培训名称',
|
||||
user_id bigint(20) not null comment '申请人ID',
|
||||
user_name varchar(50) not null comment '申请人姓名',
|
||||
department varchar(50) not null comment '所属部门',
|
||||
position varchar(50) comment '职位',
|
||||
reason text comment '申请理由',
|
||||
status char(1) default '0' comment '状态(0待审核 1已通过 2已拒绝 3进行中 4已完成)',
|
||||
apply_time datetime comment '申请时间',
|
||||
current_approver varchar(50) comment '当前审批人',
|
||||
location varchar(200) comment '培训地点',
|
||||
duration int(11) comment '学时',
|
||||
start_time datetime comment '开始时间',
|
||||
end_time datetime comment '结束时间',
|
||||
tenant_id varchar(20) default '000000' comment '租户编号',
|
||||
create_dept bigint(20) comment '创建部门',
|
||||
reject_time datetime comment '拒绝时间',
|
||||
create_by bigint(20) comment '创建者',
|
||||
create_time datetime comment '创建时间',
|
||||
update_by bigint(20) comment '更新者',
|
||||
update_time datetime comment '更新时间',
|
||||
remark varchar(500) comment '备注',
|
||||
deleted char(1) default '0' comment '删除标志',
|
||||
primary key (application_id)
|
||||
) engine=innodb comment = '培训申请表';
|
||||
|
||||
-- ----------------------------
|
||||
-- 培训审批表
|
||||
-- ----------------------------
|
||||
drop table if exists rs_training_approval;
|
||||
create table rs_training_approval (
|
||||
approval_id bigint(20) not null comment '审批ID',
|
||||
application_id bigint(20) not null comment '申请ID',
|
||||
approver_id bigint(20) not null comment '审批人ID',
|
||||
approver_name varchar(50) not null comment '审批人姓名',
|
||||
approval_level int(11) not null comment '审批级别(1部门主管 2分管领导 3校领导)',
|
||||
status char(1) default '0' comment '状态(0待审核 1已通过 2已拒绝)',
|
||||
opinion varchar(500) comment '审批意见',
|
||||
approval_time datetime comment '审批时间',
|
||||
tenant_id varchar(20) default '000000' comment '租户编号',
|
||||
create_by bigint(20) comment '创建者',
|
||||
create_time datetime comment '创建时间',
|
||||
update_by bigint(20) comment '更新者',
|
||||
update_time datetime comment '更新时间',
|
||||
remark varchar(500) comment '备注',
|
||||
deleted char(1) default '0' comment '删除标志',
|
||||
primary key (approval_id)
|
||||
) engine=innodb comment = '培训审批表';
|
||||
|
||||
-- ----------------------------
|
||||
-- 培训记录表
|
||||
-- ----------------------------
|
||||
drop table if exists rs_training_record;
|
||||
create table rs_training_record (
|
||||
record_id bigint(20) not null comment '记录ID',
|
||||
application_id bigint(20) not null comment '申请ID',
|
||||
course_id bigint(20) not null comment '课程ID',
|
||||
user_id bigint(20) not null comment '用户ID',
|
||||
user_name varchar(50) not null comment '用户姓名',
|
||||
completion_status char(1) default '0' comment '完成状态(0进行中 1已完成 2未完成)',
|
||||
completion_time datetime comment '完成时间',
|
||||
score decimal(5,2) comment '成绩',
|
||||
certificate_path varchar(255) comment '证书路径',
|
||||
certificate_number varchar(50) comment '证书编号',
|
||||
attachment_ids varchar(255) comment '证书附件ID(多个用逗号分隔)',
|
||||
feedback text comment '培训反馈',
|
||||
location varchar(255) comment '培训地点',
|
||||
duration int(11) comment '学时',
|
||||
apply_time datetime comment '申请时间',
|
||||
status varchar(10) comment '申请状态',
|
||||
course_name varchar(255) comment '课程名称',
|
||||
department varchar(255) comment '所属部门',
|
||||
position varchar(255) comment '职位',
|
||||
reason varchar(255) comment '申请理由',
|
||||
current_approver varchar(255) comment '当前审批人',
|
||||
start_time datetime comment '开始时间',
|
||||
end_time datetime comment '结束时间',
|
||||
tenant_id varchar(20) default '000000' comment '租户编号',
|
||||
create_dept bigint(20) comment '创建部门',
|
||||
create_by bigint(20) comment '创建者',
|
||||
create_time datetime comment '创建时间',
|
||||
update_by bigint(20) comment '更新者',
|
||||
update_time datetime comment '更新时间',
|
||||
remark varchar(500) comment '备注',
|
||||
deleted char(1) default '0' comment '删除标志',
|
||||
primary key (record_id)
|
||||
) engine=innodb comment = '培训记录表';
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
-- 添加自定义课程名称字段
|
||||
-- 创建于 2025-07-24
|
||||
|
||||
-- 在rs_training_application表中添加course_name列,用于存储自定义培训名称
|
||||
ALTER TABLE `rs_training_application` ADD COLUMN `course_name` varchar(100) DEFAULT NULL COMMENT '自定义课程名称' AFTER `course_id`;
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
-- 为rs_application表添加附件相关字段
|
||||
ALTER TABLE `rs_application`
|
||||
ADD COLUMN `attachment_path` varchar(255) DEFAULT NULL COMMENT '附件路径' AFTER `self_description`,
|
||||
ADD COLUMN `attachment_name` varchar(255) DEFAULT NULL COMMENT '附件名称' AFTER `attachment_path`;
|
||||
|
||||
-- 更新表注释
|
||||
ALTER TABLE `rs_application` COMMENT '应聘申请记录表';
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
-- =============================================
|
||||
-- 修改 rs_overseas_application 表结构
|
||||
-- 将 party_retention_file_oss_id 字段从 bigint 改为 varchar(500)
|
||||
-- 以支持存储多个附件OSS ID(逗号分隔)
|
||||
-- 创建时间: 2025-11-27
|
||||
-- =============================================
|
||||
|
||||
-- 修改字段类型(MySQL/MariaDB)
|
||||
ALTER TABLE rs_overseas_application
|
||||
MODIFY COLUMN party_retention_file_oss_id VARCHAR(500) COMMENT '党籍保留附件OSS ID(多个ID用逗号分隔)';
|
||||
|
||||
-- 注意:
|
||||
-- 1. 此脚本会保留现有数据,bigint值会自动转换为字符串
|
||||
-- 2. 建议在执行前备份数据库
|
||||
-- 3. 如果字段当前有NOT NULL约束,会被保留
|
||||
@@ -316,7 +316,7 @@ interface OverseasApprovalVO {
|
||||
approvalId: number | string; // 审批ID
|
||||
applicationId: number; // 关联申请ID
|
||||
approverId: number; // 审批人ID
|
||||
approverName: string; // 审批人姓名
|
||||
approverName: string; // 审批人姓名(优先返回昵称,缺省时回退账号)
|
||||
approvalLevel: string; // 审批级别编码
|
||||
approvalStatus: string; // 审批状态编码
|
||||
approvalOpinion: string; // 审批意见
|
||||
@@ -329,6 +329,8 @@ interface OverseasApprovalVO {
|
||||
}
|
||||
```
|
||||
|
||||
> 🆕 **2025-11-28 更新**:`approverName` 字段统一返回审批人的昵称,若用户未设置昵称则自动回退到账号,确保审批记录界面展示真实姓名。
|
||||
|
||||
---
|
||||
|
||||
## 🔧 核心接口详细说明
|
||||
@@ -1048,6 +1050,7 @@ PDF模板包含以下空白填写区域(完全手工填写):
|
||||
- 申请表单:党员且出国天数>30天时自动显示
|
||||
- 申请详情:符合条件时在弹窗底部显示绿色按钮
|
||||
- **前端实现**:必须使用 `request` 工具而不是 `fetch` API,以确保包含所有必要的认证请求头
|
||||
- **附件格式**:自 2025-11-27 起,党籍保留附件支持任意文件类型上传,仅保留 10MB 单文件大小限制
|
||||
|
||||
#### 错误处理
|
||||
|
||||
@@ -1242,4 +1245,20 @@ import { getCurrentApproversForTask } from '@/api/workflow/common';
|
||||
|
||||
**更新时间**:2025-10-28
|
||||
**维护人员**:开发团队
|
||||
**变更记录**:新增工作流动态审批人方案说明
|
||||
**变更记录**:新增工作流动态审批人方案说明
|
||||
|
||||
---
|
||||
|
||||
### 2025-11-28 更新:出国记录回执下载
|
||||
|
||||
- **前端**:`web/src/views/rs/overseas/index.vue` 中的 “下载因私出国(境)情况回执” 按钮改为直接基于 Word 模板生成文件,摆脱后端 PDF 下载接口依赖。
|
||||
- **模板**:`web/public/chuguoqingkuanghuizhi.docx`,占位符 `{{applicantName}}`、`{{startYear}}`、`{{startMonth}}`、`{{startDay}}`、`{{endYear}}`、`{{endMonth}}`、`{{endDay}}`、`{{destinationText}}`、`{{exitReasonText}}`、`{{durationDays}}`、`{{expenseType}}`。
|
||||
- **数据映射**:优先使用记录中的实际出行信息(`actualStartDate`、`actualEndDate`、`destinationCountry`、`exitReason` 等),缺省时回落到申请单数据,行程天数自动根据起止日期计算,默认费用说明为“费用自理”。
|
||||
- **交互提示**:增加起止日期缺失时的前端校验,模板生成失败会提示用户检查模板或稍后再试。
|
||||
|
||||
### 2025-11-28 更新:审批监控页面审批表导出
|
||||
|
||||
- **前端**:`web/src/views/rs/overseas/monitor/index.vue` 的 “下载审批表” 按钮从 PDF 调整为 Word 导出,保持统一的 docxtemplater + image module 生成逻辑。
|
||||
- **模板**:`web/public/chuguojingshenpibiao.docx`,覆盖个人信息、行程信息、申请人签名及五级审批节点(部门负责人、人事处、纪委、学院分管领导、学院党委书记)的签名、意见、日期占位符,支持 `{{%signature}}` 形式的图片变量。
|
||||
- **数据来源**:优先取 `currentDetail` 详情接口返回值;审批节点数据由 `approvalHistory` 根据 `approvalLevel` 映射,若无签名/日期则留空。详情接口新增 `signature` 字段(申请人手写签名 base64),供模板直接使用。
|
||||
- **交互提示**:仅在审批完成(状态为 `1`)时显示导出按钮,生成失败时给出友好提示并引导检查模板。
|
||||
@@ -144,6 +144,10 @@
|
||||
}
|
||||
```
|
||||
|
||||
#### 字段补充说明(2025-11-28)
|
||||
|
||||
- `approverName`:后台统一返回审批人的 **昵称**,若用户未设置昵称则自动回退到账号;前端无需再做用户名到昵称的手动映射。
|
||||
|
||||
---
|
||||
|
||||
### 3. 查询待审批列表
|
||||
|
||||
+5
-1
@@ -34,9 +34,12 @@
|
||||
"await-to-js": "3.0.0",
|
||||
"axios": "1.8.4",
|
||||
"crypto-js": "4.2.0",
|
||||
"docx": "^9.5.1",
|
||||
"docxtemplater": "^3.67.5",
|
||||
"docxtemplater-image-module-free": "^1.1.1",
|
||||
"echarts": "^5.6.0",
|
||||
"element-plus": "2.9.8",
|
||||
"file-saver": "2.0.5",
|
||||
"file-saver": "^2.0.5",
|
||||
"highlight.js": "11.9.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"image-conversion": "2.1.1",
|
||||
@@ -46,6 +49,7 @@
|
||||
"jspdf-font": "^1.0.7",
|
||||
"nprogress": "0.2.0",
|
||||
"pinia": "3.0.2",
|
||||
"pizzip": "^3.2.0",
|
||||
"qrcode": "^1.5.4",
|
||||
"screenfull": "6.0.2",
|
||||
"vue": "3.5.13",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -70,6 +70,7 @@ export interface OverseasRegistrationForm {
|
||||
officePhone?: string; // 办公电话
|
||||
mobilePhone?: string; // 移动电话
|
||||
remarks?: string; // 备注
|
||||
status?: string; // 登记状态:0-草稿,1-待审核,2-已通过,3-已拒绝
|
||||
}
|
||||
|
||||
// 登记历史查询参数类型
|
||||
@@ -142,6 +143,17 @@ export function getOverseasRegistrationList(query: OverseasRegistrationQuery) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询我的出国登记列表(只查询当前登录人创建的记录)
|
||||
*/
|
||||
export function getMyOverseasRegistrationList(query: OverseasRegistrationQuery) {
|
||||
return request({
|
||||
url: '/rs/overseas/registration/my/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询出国登记详细
|
||||
*/
|
||||
@@ -433,4 +445,4 @@ export function batchRejectOverseasRegistration(registrationIds: Array<number |
|
||||
...data
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@ export interface OverseasApplicationVO {
|
||||
partyRetentionFileName?: string; // 党籍保留附件文件名
|
||||
partyRetentionUploadTime?: string; // 党籍保留附件上传时间
|
||||
partyRetentionUploadStatus?: string; // 党籍保留附件上传状态
|
||||
partyRetentionFileOssId?: number; // 党籍保留附件OSS ID
|
||||
partyRetentionFileOssId?: string; // 党籍保留附件OSS ID(支持多个ID用逗号分隔)
|
||||
purpose: string;
|
||||
purposeDetail: string;
|
||||
destination: string;
|
||||
@@ -137,6 +137,7 @@ export interface OverseasApplicationForm {
|
||||
partyRetentionFileName?: string; // 党籍保留附件文件名
|
||||
partyRetentionUploadTime?: string; // 党籍保留附件上传时间
|
||||
partyRetentionUploadStatus?: string; // 党籍保留附件上传状态:0-未上传,1-已上传
|
||||
partyRetentionFileOssId?: string; // 党籍保留附件OSS ID(支持多个ID用逗号分隔)
|
||||
purpose: string;
|
||||
purposeDetail: string;
|
||||
destination: string;
|
||||
@@ -341,6 +342,15 @@ export function listPendingOverseasApplication() {
|
||||
});
|
||||
}
|
||||
|
||||
// 查询当前用户的待审批申请分页列表(根据角色过滤,支持分页和搜索)
|
||||
export function listPendingOverseasApplicationPage(query: OverseasApplicationQuery) {
|
||||
return request({
|
||||
url: '/rs/overseas/application/pending/page',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 🚨 标准化改造:删除自定义待审批查询API
|
||||
//
|
||||
@@ -805,6 +815,7 @@ export interface ItineraryChangeQuery {
|
||||
applicantName?: string;
|
||||
destination?: string;
|
||||
status?: string;
|
||||
applicantId?: number;
|
||||
params?: Record<string, any>;
|
||||
}
|
||||
|
||||
@@ -842,8 +853,8 @@ export interface ItineraryChangeVO {
|
||||
status: string;
|
||||
statusText: string;
|
||||
approvalOpinion?: string;
|
||||
approvalBy?: number;
|
||||
approvalByName?: string;
|
||||
approverId?: number;
|
||||
approverName?: string;
|
||||
approvalTime?: string;
|
||||
createTime: string;
|
||||
updateTime?: string;
|
||||
|
||||
@@ -34,7 +34,7 @@ router.beforeEach(async (to, from, next) => {
|
||||
const [err] = await tos(useUserStore().getInfo());
|
||||
if (err) {
|
||||
await useUserStore().logout();
|
||||
ElMessage.error(err);
|
||||
// ElMessage.error(err.message || '获取用户信息失败');
|
||||
next({ path: '/' });
|
||||
} else {
|
||||
isRelogin.show = false;
|
||||
|
||||
@@ -204,8 +204,14 @@ service.interceptors.response.use(
|
||||
err.response = res;
|
||||
return Promise.reject(err);
|
||||
} else if (code !== HttpStatus.SUCCESS) {
|
||||
ElNotification.error({ title: msg });
|
||||
const err = new Error(msg) as any;
|
||||
// 确保错误消息不为空,避免只显示图标没有文字
|
||||
const errorMsg = msg || errorCode['default'] || '操作失败,请稍后重试';
|
||||
ElNotification.error({
|
||||
title: '错误提示',
|
||||
message: errorMsg,
|
||||
duration: 5000
|
||||
});
|
||||
const err = new Error(errorMsg) as any;
|
||||
err.response = res;
|
||||
return Promise.reject(err);
|
||||
} else {
|
||||
|
||||
+22
-6
@@ -192,13 +192,29 @@ const getLoginData = () => {
|
||||
* 获取租户列表
|
||||
*/
|
||||
const initTenantList = async () => {
|
||||
const { data } = await getTenantList(false);
|
||||
tenantEnabled.value = data.tenantEnabled === undefined ? true : data.tenantEnabled;
|
||||
if (tenantEnabled.value) {
|
||||
tenantList.value = data.voList;
|
||||
if (tenantList.value != null && tenantList.value.length !== 0) {
|
||||
loginForm.value.tenantId = tenantList.value[0].tenantId;
|
||||
try {
|
||||
const [err, res] = await to(getTenantList(false));
|
||||
if (err) {
|
||||
// 静默处理租户列表获取失败,不影响登录功能
|
||||
console.warn('获取租户列表失败:', err);
|
||||
// 设置默认值,确保登录功能正常
|
||||
tenantEnabled.value = true;
|
||||
return;
|
||||
}
|
||||
if (res && res.data) {
|
||||
const { data } = res;
|
||||
tenantEnabled.value = data.tenantEnabled === undefined ? true : data.tenantEnabled;
|
||||
if (tenantEnabled.value) {
|
||||
tenantList.value = data.voList;
|
||||
if (tenantList.value != null && tenantList.value.length !== 0) {
|
||||
loginForm.value.tenantId = tenantList.value[0].tenantId;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 捕获其他可能的错误,静默处理
|
||||
console.warn('初始化租户列表时发生错误:', error);
|
||||
tenantEnabled.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+1142
-573
File diff suppressed because it is too large.
Load diff
@@ -1,30 +1,6 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-card class="box-card">
|
||||
<!-- 🔴 标准化改造:提示用户使用标准工作流页面 -->
|
||||
<el-alert
|
||||
title="功能迁移通知"
|
||||
type="info"
|
||||
description="出国行程变更审批功能已标准化,请使用系统统一的工作流页面查看和处理任务"
|
||||
show-icon
|
||||
:closable="false"
|
||||
class="mb-4"
|
||||
>
|
||||
<template #default>
|
||||
<div class="mt-2">
|
||||
<p><strong>推荐操作:</strong></p>
|
||||
<ul class="ml-4">
|
||||
<li>访问 <strong>工作流 > 我的任务 > 待办任务</strong> 页面</li>
|
||||
<li>在统一的待办列表中查看"出国行程变更审批"任务</li>
|
||||
<li>使用工作流的筛选功能按业务类型查看</li>
|
||||
</ul>
|
||||
<div class="mt-3">
|
||||
<el-button type="primary" @click="navigateToTaskWaiting"> 前往待办任务页面 </el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
<!-- 审批管理选项卡 -->
|
||||
<el-tabs v-model="activeTab" type="border-card" @tab-click="handleTabClick">
|
||||
<!-- 待审批 -->
|
||||
@@ -69,13 +45,8 @@
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<!-- 🔴 标准化改造:显示迁移提示 -->
|
||||
<el-empty description="请使用标准工作流待办页面查看审批任务" :image-size="120">
|
||||
<el-button type="primary" @click="navigateToTaskWaiting">前往待办页面</el-button>
|
||||
</el-empty>
|
||||
|
||||
<!-- 保留原表格结构用于兼容(隐藏显示) -->
|
||||
<el-table v-loading="pendingLoading" border :data="pendingList" @row-dblclick="handleView" style="display: none">
|
||||
<!-- 待审批变更列表 -->
|
||||
<el-table v-loading="pendingLoading" border :data="pendingList" @row-dblclick="handleView">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="申请人" align="center" prop="applicantName" min-width="100" />
|
||||
<el-table-column label="部门" align="center" prop="department" min-width="120" />
|
||||
@@ -190,7 +161,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审批时间" align="center" prop="approvalTime" min-width="160" />
|
||||
<el-table-column label="审批人" align="center" prop="approvalByName" min-width="100" />
|
||||
<el-table-column label="审批人" align="center" prop="approverName" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column label="操作" align="center" width="80" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看详情" placement="top">
|
||||
@@ -243,7 +214,7 @@
|
||||
<el-tag :type="getStatusTagType(viewData.status)">{{ viewData.statusText }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="申请时间">{{ viewData.createTime || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="审批人" v-if="viewData.approvalByName">{{ viewData.approvalByName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="审批人" v-if="viewData.approverName">{{ viewData.approverName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="审批时间" v-if="viewData.approvalTime">{{ viewData.approvalTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="审批意见" :span="2" v-if="viewData.approvalOpinion">{{ viewData.approvalOpinion }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
@@ -267,16 +238,10 @@
|
||||
<el-form-item label="审批意见" prop="opinion">
|
||||
<el-input v-model="approvalForm.opinion" type="textarea" :rows="4" placeholder="请输入审批意见" maxlength="500" show-word-limit />
|
||||
</el-form-item>
|
||||
|
||||
|
||||
<!-- 手写签字 -->
|
||||
<el-form-item label="审批签字">
|
||||
<SignaturePad
|
||||
v-model="approvalForm.signature"
|
||||
:width="450"
|
||||
:height="120"
|
||||
:auto-save="true"
|
||||
:show-save-button="false"
|
||||
/>
|
||||
<SignaturePad v-model="approvalForm.signature" :width="450" :height="120" :auto-save="true" :show-save-button="false" />
|
||||
<div class="signature-tip">请在上方签字板中手写签名</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -413,17 +378,24 @@ const handleTabClick = (tab: any) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 🔴 标准化改造:简化待审批列表获取(仅用于兼容模式)
|
||||
// 获取待审批变更列表(status=0)
|
||||
const getPendingList = async () => {
|
||||
state.pendingLoading = true;
|
||||
|
||||
// 提示用户使用标准工作流页面
|
||||
ElMessage.info('请使用标准工作流待办页面查看审批任务');
|
||||
|
||||
// 清空数据,不再调用自定义API
|
||||
state.pendingList = [];
|
||||
state.pendingTotal = 0;
|
||||
state.pendingLoading = false;
|
||||
try {
|
||||
// 查询待审核状态的变更申请(status='0')
|
||||
const params = { ...state.pendingQueryParams };
|
||||
params.status = '0'; // 只查询待审核的
|
||||
const response = await listPendingItineraryChange(params);
|
||||
state.pendingList = response.rows || [];
|
||||
state.pendingTotal = response.total || 0;
|
||||
} catch (error) {
|
||||
console.error('获取待审批列表失败:', error);
|
||||
ElMessage.error('获取待审批列表失败');
|
||||
state.pendingList = [];
|
||||
state.pendingTotal = 0;
|
||||
} finally {
|
||||
state.pendingLoading = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 查询审批记录列表
|
||||
@@ -446,20 +418,22 @@ const getRecordList = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 🔴 标准化改造:导航到标准工作流待办页面
|
||||
const navigateToTaskWaiting = () => {
|
||||
// 跳转到标准工作流待办页面
|
||||
router.push('/workflow/task/taskWaiting');
|
||||
};
|
||||
|
||||
// 搜索查询 - 待审批列表
|
||||
const handlePendingQuery = () => {
|
||||
ElMessage.info('请使用标准工作流待办页面查看审批任务');
|
||||
state.pendingQueryParams.pageNum = 1;
|
||||
getPendingList();
|
||||
};
|
||||
|
||||
// 重置搜索 - 待审批列表
|
||||
const resetPendingQuery = () => {
|
||||
ElMessage.info('请使用标准工作流待办页面查看审批任务');
|
||||
pendingQueryFormRef.value?.resetFields();
|
||||
Object.assign(state.pendingQueryParams, {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
applicantName: '',
|
||||
destination: ''
|
||||
});
|
||||
getPendingList();
|
||||
};
|
||||
|
||||
// 搜索查询 - 审批记录
|
||||
@@ -574,4 +548,7 @@ const getStatusTagType = (status: string): 'primary' | 'success' | 'info' | 'war
|
||||
color: #909399;
|
||||
text-align: center;
|
||||
}
|
||||
:global(.app-container) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
</style>
|
||||
@@ -3,7 +3,6 @@
|
||||
<el-card class="box-card">
|
||||
<!-- Tab切换:变更申请 / 变更记录 -->
|
||||
<el-tabs v-model="activeTab" type="border-card" @tab-click="handleTabClick">
|
||||
|
||||
<!-- Tab 1: 变更申请(待审核) -->
|
||||
<el-tab-pane label="变更申请(待审核)" name="all">
|
||||
<div class="tab-content">
|
||||
@@ -23,15 +22,6 @@
|
||||
<el-input v-model="queryParams.destination" placeholder="请输入目的地国家" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="变更状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择变更状态" clearable style="width: 100%">
|
||||
<el-option label="待审核" value="0" />
|
||||
<el-option label="已通过" value="1" />
|
||||
<el-option label="已拒绝" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
@@ -52,9 +42,7 @@
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['rs:itineraryChange:add']">
|
||||
新增变更申请
|
||||
</el-button>
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['rs:itineraryChange:add']"> 新增变更申请 </el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
@@ -62,45 +50,65 @@
|
||||
|
||||
<!-- 变更行程列表 -->
|
||||
<el-table v-loading="loading" border :data="changeList" @row-dblclick="handleView">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="申请人" align="center" prop="applicantName" min-width="100" />
|
||||
<el-table-column label="原目的地" align="center" prop="originalDestination" min-width="120" />
|
||||
<el-table-column label="新目的地" align="center" prop="newDestination" min-width="120" />
|
||||
<el-table-column label="原出国时间" align="center" min-width="200">
|
||||
<template #default="scope">
|
||||
{{ formatDate(scope.row.originalStartDate) }} 至 {{ formatDate(scope.row.originalEndDate) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="新出国时间" align="center" min-width="200">
|
||||
<template #default="scope">
|
||||
{{ formatDate(scope.row.newStartDate) }} 至 {{ formatDate(scope.row.newEndDate) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="变更状态" align="center" prop="statusText" min-width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusTagType(scope.row.status)">{{ scope.row.statusText }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="申请时间" align="center" prop="createTime" min-width="160" />
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="申请人" align="center" prop="applicantName" min-width="100" />
|
||||
<el-table-column label="原目的地" align="center" prop="originalDestination" min-width="120" />
|
||||
<el-table-column label="新目的地" align="center" prop="newDestination" min-width="120" />
|
||||
<el-table-column label="原出国时间" align="center" min-width="200">
|
||||
<template #default="scope"> {{ formatDate(scope.row.originalStartDate) }} 至 {{ formatDate(scope.row.originalEndDate) }} </template>
|
||||
</el-table-column>
|
||||
<el-table-column label="新出国时间" align="center" min-width="200">
|
||||
<template #default="scope"> {{ formatDate(scope.row.newStartDate) }} 至 {{ formatDate(scope.row.newEndDate) }} </template>
|
||||
</el-table-column>
|
||||
<el-table-column label="变更状态" align="center" prop="statusText" min-width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusTagType(scope.row.status)">{{ scope.row.statusText }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="申请时间" align="center" prop="createTime" min-width="160" />
|
||||
<el-table-column label="操作" align="center" width="250" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看详情" placement="top">
|
||||
<el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['rs:itineraryChange:query']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="变更历史" placement="top">
|
||||
<el-button link type="info" icon="List" @click="handleViewHistory(scope.row)" v-hasPermi="['rs:itineraryChange:query']"></el-button>
|
||||
<el-button
|
||||
link
|
||||
type="info"
|
||||
icon="List"
|
||||
@click="handleViewHistory(scope.row)"
|
||||
v-hasPermi="['rs:itineraryChange:query']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<!-- 待审核状态:显示撤回按钮 -->
|
||||
<el-tooltip content="撤回申请" placement="top" v-if="scope.row.status === '0'">
|
||||
<el-button link type="warning" icon="RefreshLeft" @click="handleCancel(scope.row)" v-hasPermi="['rs:itineraryChange:cancel']"></el-button>
|
||||
<el-button
|
||||
link
|
||||
type="warning"
|
||||
icon="RefreshLeft"
|
||||
@click="handleCancel(scope.row)"
|
||||
v-hasPermi="['rs:itineraryChange:cancel']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<!-- 已拒绝状态:显示重新提交按钮 -->
|
||||
<el-tooltip content="重新提交" placement="top" v-if="scope.row.status === '2'">
|
||||
<el-button link type="success" icon="RefreshRight" @click="handleResubmit(scope.row)" v-hasPermi="['rs:itineraryChange:edit']"></el-button>
|
||||
<!-- 已拒绝或已撤回状态:显示重新提交按钮 -->
|
||||
<el-tooltip content="重新提交" placement="top" v-if="scope.row.status === '2' || scope.row.status === '3'">
|
||||
<el-button
|
||||
link
|
||||
type="success"
|
||||
icon="RefreshRight"
|
||||
@click="handleResubmit(scope.row)"
|
||||
v-hasPermi="['rs:itineraryChange:edit']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<!-- 删除按钮 -->
|
||||
<el-tooltip content="删除" placement="top" v-if="scope.row.status !== '1'">
|
||||
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['rs:itineraryChange:remove']"></el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
icon="Delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['rs:itineraryChange:remove']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -131,15 +139,6 @@
|
||||
<el-input v-model="myQueryParams.destination" placeholder="请输入目的地国家" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="变更状态" prop="status">
|
||||
<el-select v-model="myQueryParams.status" placeholder="请选择变更状态" clearable style="width: 100%">
|
||||
<el-option label="待审核" value="0" />
|
||||
<el-option label="已通过" value="1" />
|
||||
<el-option label="已拒绝" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
@@ -169,14 +168,10 @@
|
||||
<el-table-column label="原目的地" align="center" prop="originalDestination" min-width="120" />
|
||||
<el-table-column label="新目的地" align="center" prop="newDestination" min-width="120" />
|
||||
<el-table-column label="原出国时间" align="center" min-width="200">
|
||||
<template #default="scope">
|
||||
{{ formatDate(scope.row.originalStartDate) }} 至 {{ formatDate(scope.row.originalEndDate) }}
|
||||
</template>
|
||||
<template #default="scope"> {{ formatDate(scope.row.originalStartDate) }} 至 {{ formatDate(scope.row.originalEndDate) }} </template>
|
||||
</el-table-column>
|
||||
<el-table-column label="新出国时间" align="center" min-width="200">
|
||||
<template #default="scope">
|
||||
{{ formatDate(scope.row.newStartDate) }} 至 {{ formatDate(scope.row.newEndDate) }}
|
||||
</template>
|
||||
<template #default="scope"> {{ formatDate(scope.row.newStartDate) }} 至 {{ formatDate(scope.row.newEndDate) }} </template>
|
||||
</el-table-column>
|
||||
<el-table-column label="变更状态" align="center" prop="statusText" min-width="100">
|
||||
<template #default="scope">
|
||||
@@ -190,19 +185,43 @@
|
||||
<el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['rs:itineraryChange:query']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="变更历史" placement="top">
|
||||
<el-button link type="info" icon="List" @click="handleViewHistory(scope.row)" v-hasPermi="['rs:itineraryChange:query']"></el-button>
|
||||
<el-button
|
||||
link
|
||||
type="info"
|
||||
icon="List"
|
||||
@click="handleViewHistory(scope.row)"
|
||||
v-hasPermi="['rs:itineraryChange:query']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<!-- 待审核状态:显示撤回按钮 -->
|
||||
<el-tooltip content="撤回申请" placement="top" v-if="scope.row.status === '0'">
|
||||
<el-button link type="warning" icon="RefreshLeft" @click="handleCancel(scope.row)" v-hasPermi="['rs:itineraryChange:cancel']"></el-button>
|
||||
<el-button
|
||||
link
|
||||
type="warning"
|
||||
icon="RefreshLeft"
|
||||
@click="handleCancel(scope.row)"
|
||||
v-hasPermi="['rs:itineraryChange:cancel']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<!-- 已拒绝状态:显示重新提交按钮 -->
|
||||
<el-tooltip content="重新提交" placement="top" v-if="scope.row.status === '2'">
|
||||
<el-button link type="success" icon="RefreshRight" @click="handleResubmit(scope.row)" v-hasPermi="['rs:itineraryChange:edit']"></el-button>
|
||||
<!-- 已拒绝或已撤回状态:显示重新提交按钮 -->
|
||||
<el-tooltip content="重新提交" placement="top" v-if="scope.row.status === '2' || scope.row.status === '3'">
|
||||
<el-button
|
||||
link
|
||||
type="success"
|
||||
icon="RefreshRight"
|
||||
@click="handleResubmit(scope.row)"
|
||||
v-hasPermi="['rs:itineraryChange:edit']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<!-- 删除按钮 -->
|
||||
<el-tooltip content="删除" placement="top" v-if="scope.row.status !== '1'">
|
||||
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['rs:itineraryChange:remove']"></el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
icon="Delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['rs:itineraryChange:remove']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -223,24 +242,23 @@
|
||||
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="900px" append-to-body :close-on-click-modal="false" destroy-on-close>
|
||||
<div style="max-height: 70vh; overflow-y: auto; padding-right: 10px;">
|
||||
<div style="max-height: 70vh; overflow-y: auto; padding-right: 10px">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="140px">
|
||||
|
||||
<!-- 选择原出国申请 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="选择原出国申请" prop="applicationId">
|
||||
<el-select
|
||||
v-model="form.applicationId"
|
||||
placeholder="请选择已审批通过的出国申请"
|
||||
<el-select
|
||||
v-model="form.applicationId"
|
||||
placeholder="请选择已审批通过的出国申请"
|
||||
style="width: 100%"
|
||||
@change="handleApplicationChange"
|
||||
:disabled="!!form.changeId"
|
||||
filterable
|
||||
>
|
||||
<el-option
|
||||
v-for="application in availableApplicationList"
|
||||
:key="application.applicationId"
|
||||
<el-option
|
||||
v-for="application in availableApplicationList"
|
||||
:key="application.applicationId"
|
||||
:label="`${application.applicantName} - ${application.destination} (${formatDate(application.startDate)}至${formatDate(application.endDate)})`"
|
||||
:value="application.applicationId"
|
||||
/>
|
||||
@@ -336,13 +354,7 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="行程安排">
|
||||
<el-input
|
||||
v-model="form.originalItinerary"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
disabled
|
||||
placeholder="原行程安排"
|
||||
/>
|
||||
<el-input v-model="form.originalItinerary" type="textarea" :rows="3" disabled placeholder="原行程安排" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -384,14 +396,7 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="行程安排" prop="newItinerary">
|
||||
<el-input
|
||||
v-model="form.newItinerary"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入实际行程安排"
|
||||
maxlength="1000"
|
||||
show-word-limit
|
||||
/>
|
||||
<el-input v-model="form.newItinerary" type="textarea" :rows="3" placeholder="请输入实际行程安排" maxlength="1000" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -401,10 +406,10 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item prop="changeReason">
|
||||
<el-input
|
||||
v-model="form.changeReason"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
<el-input
|
||||
v-model="form.changeReason"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请详细说明行程变更的原因(相关证明材料可附后)"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
@@ -418,21 +423,13 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item>
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请填写有关认识和态度"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
/>
|
||||
<div style="margin-top: 12px; color: #303133; font-size: 14px; font-weight: bold;">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请填写有关认识和态度" maxlength="200" show-word-limit />
|
||||
<div style="margin-top: 12px; color: #303133; font-size: 14px; font-weight: bold">
|
||||
我承诺,以上情况说明完全属实。如有不实行为,愿意接受组织处理。
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
@@ -450,14 +447,14 @@
|
||||
<!-- 基本信息 -->
|
||||
<el-descriptions-item label="申请人">{{ viewData.applicantName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="部门">{{ viewData.department || '-' }}</el-descriptions-item>
|
||||
|
||||
|
||||
<!-- 原申请信息 -->
|
||||
<el-descriptions-item label="原目的地">{{ viewData.originalDestination || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="原出境事由">{{ viewData.originalExitReason || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="原出发日期">{{ formatDate(viewData.originalStartDate) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="原返回日期">{{ formatDate(viewData.originalEndDate) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="原出国天数" :span="2">{{ viewData.originalDuration || 0 }}天</el-descriptions-item>
|
||||
|
||||
|
||||
<!-- 变更信息 -->
|
||||
<el-descriptions-item label="变更类型" :span="2">{{ viewData.changeType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="新目的地" v-if="viewData.newDestination">{{ viewData.newDestination }}</el-descriptions-item>
|
||||
@@ -468,7 +465,7 @@
|
||||
<el-descriptions-item label="新行程安排" :span="2" v-if="viewData.newItinerary">{{ viewData.newItinerary }}</el-descriptions-item>
|
||||
<el-descriptions-item label="变更原因" :span="2">{{ viewData.changeReason || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="2" v-if="viewData.remark">{{ viewData.remark }}</el-descriptions-item>
|
||||
|
||||
|
||||
<!-- 审批信息 -->
|
||||
<el-descriptions-item label="变更状态">
|
||||
<el-tag :type="getStatusTagType(viewData.status)">{{ viewData.statusText }}</el-tag>
|
||||
@@ -513,7 +510,6 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -522,7 +518,8 @@ import { ref, reactive, toRefs, onMounted, computed, watch } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import animate from '@/animate';
|
||||
import { parseTime } from '@/utils/ruoyi';
|
||||
import {
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import {
|
||||
listItineraryChange,
|
||||
listMyItineraryChange,
|
||||
getItineraryChange,
|
||||
@@ -535,6 +532,7 @@ import {
|
||||
getOverseasApplication,
|
||||
getItineraryChangeHistory,
|
||||
getOverseasRecordByApplication,
|
||||
listOverseasApplication,
|
||||
type ItineraryChangeQuery,
|
||||
type ItineraryChangeVO,
|
||||
type ItineraryChangeForm,
|
||||
@@ -553,7 +551,7 @@ const formRef = ref();
|
||||
const state = reactive({
|
||||
// Tab切换
|
||||
activeTab: 'all',
|
||||
|
||||
|
||||
// 全部变更列表
|
||||
loading: false,
|
||||
showSearch: true,
|
||||
@@ -565,9 +563,10 @@ const state = reactive({
|
||||
pageSize: 10,
|
||||
applicantName: '',
|
||||
destination: '',
|
||||
status: ''
|
||||
status: '',
|
||||
applicantId: undefined as number | undefined
|
||||
} as ItineraryChangeQuery,
|
||||
|
||||
|
||||
// 我的变更列表
|
||||
myLoading: false,
|
||||
showMySearch: true,
|
||||
@@ -577,9 +576,10 @@ const state = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
destination: '',
|
||||
status: ''
|
||||
status: '',
|
||||
applicantId: undefined as number | undefined
|
||||
} as ItineraryChangeQuery,
|
||||
|
||||
|
||||
// 对话框
|
||||
dialog: {
|
||||
visible: false,
|
||||
@@ -591,21 +591,21 @@ const state = reactive({
|
||||
historyDialog: {
|
||||
visible: false
|
||||
},
|
||||
|
||||
|
||||
// 表单数据
|
||||
form: {} as ItineraryChangeForm,
|
||||
viewData: null as ItineraryChangeVO | null,
|
||||
|
||||
|
||||
// 变更历史
|
||||
historyLoading: false,
|
||||
historyList: [] as ItineraryChangeVO[],
|
||||
|
||||
|
||||
// 可用的出国申请列表(已审批通过)
|
||||
availableApplicationList: [] as OverseasApplicationVO[],
|
||||
|
||||
|
||||
// 变更类型(多选)
|
||||
changeTypes: [] as string[],
|
||||
|
||||
|
||||
// 表单验证规则
|
||||
rules: {
|
||||
applicationId: [{ required: true, message: '请选择原出国申请', trigger: 'change' }],
|
||||
@@ -690,7 +690,7 @@ onMounted(() => {
|
||||
const handleTabClick = (tab: any) => {
|
||||
const tabName = tab.props?.name || tab.paneName;
|
||||
state.activeTab = tabName;
|
||||
|
||||
|
||||
if (tabName === 'all') {
|
||||
getList();
|
||||
} else if (tabName === 'my') {
|
||||
@@ -698,18 +698,36 @@ const handleTabClick = (tab: any) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 查询变更申请列表(只显示待审核的数据)
|
||||
// 查询变更申请列表(只显示当前登录人的待审核和已撤回的数据)
|
||||
const getList = async () => {
|
||||
state.loading = true;
|
||||
try {
|
||||
// 如果用户没有手动选择状态,默认只显示待审核的申请
|
||||
const params = { ...state.queryParams };
|
||||
if (!params.status) {
|
||||
params.status = '0'; // 默认只显示待审核状态
|
||||
}
|
||||
const userStore = useUserStore();
|
||||
// 先获取所有当前登录人的数据(不分页),然后在前端过滤和分页
|
||||
const params = {
|
||||
pageNum: 1,
|
||||
pageSize: 10000, // 设置一个较大的值,获取所有数据
|
||||
applicantId: Number(userStore.userId),
|
||||
applicantName: state.queryParams.applicantName,
|
||||
destination: state.queryParams.destination
|
||||
};
|
||||
|
||||
const response = await listItineraryChange(params);
|
||||
state.changeList = response.rows;
|
||||
state.total = response.total;
|
||||
|
||||
// 前端过滤:只显示状态为 0(待审核)和 3(已撤回)的数据
|
||||
const filteredRows = response.rows.filter((item: ItineraryChangeVO) => {
|
||||
return item.status === '0' || item.status === '3';
|
||||
});
|
||||
|
||||
// 前端分页
|
||||
const pageNum = state.queryParams.pageNum;
|
||||
const pageSize = state.queryParams.pageSize;
|
||||
const total = filteredRows.length;
|
||||
const start = (pageNum - 1) * pageSize;
|
||||
const end = Math.min(start + pageSize, total);
|
||||
|
||||
state.changeList = filteredRows.slice(start, end);
|
||||
state.total = total;
|
||||
} catch (error) {
|
||||
console.error('获取变更行程列表失败:', error);
|
||||
} finally {
|
||||
@@ -717,13 +735,35 @@ const getList = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 查询我的变更记录列表(只显示已通过的变更申请)
|
||||
// 查询我的变更记录列表(只显示当前登录人的已通过和已拒绝的数据)
|
||||
const getMyList = async () => {
|
||||
state.myLoading = true;
|
||||
try {
|
||||
const response = await listMyItineraryChange(state.myQueryParams);
|
||||
state.myChangeList = response.rows;
|
||||
state.myTotal = response.total;
|
||||
const userStore = useUserStore();
|
||||
// 先获取所有当前登录人的数据(不分页),然后在前端过滤和分页
|
||||
const params = {
|
||||
pageNum: 1,
|
||||
pageSize: 10000, // 设置一个较大的值,获取所有数据
|
||||
applicantId: Number(userStore.userId),
|
||||
destination: state.myQueryParams.destination
|
||||
};
|
||||
|
||||
const response = await listItineraryChange(params);
|
||||
|
||||
// 前端过滤:只显示状态为 1(已通过)和 2(已拒绝)的数据
|
||||
const filteredRows = response.rows.filter((item: ItineraryChangeVO) => {
|
||||
return item.status === '1' || item.status === '2';
|
||||
});
|
||||
|
||||
// 前端分页
|
||||
const pageNum = state.myQueryParams.pageNum;
|
||||
const pageSize = state.myQueryParams.pageSize;
|
||||
const total = filteredRows.length;
|
||||
const start = (pageNum - 1) * pageSize;
|
||||
const end = Math.min(start + pageSize, total);
|
||||
|
||||
state.myChangeList = filteredRows.slice(start, end);
|
||||
state.myTotal = total;
|
||||
} catch (error) {
|
||||
console.error('获取我的变更记录失败:', error);
|
||||
} finally {
|
||||
@@ -731,13 +771,25 @@ const getMyList = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 获取已审批通过的出国申请列表
|
||||
// 获取已审批通过的出国申请列表(只显示当前登录人的)
|
||||
const getApprovedApplicationList = async () => {
|
||||
try {
|
||||
const response = await getApprovedOverseasApplications();
|
||||
state.availableApplicationList = response.data;
|
||||
const userStore = useUserStore();
|
||||
// 使用listOverseasApplication查询当前登录人的已通过申请
|
||||
const response = await listOverseasApplication({
|
||||
pageNum: 1,
|
||||
pageSize: 1000, // 设置一个较大的值,获取所有数据
|
||||
applicantId: Number(userStore.userId), // 只查询当前登录人的申请
|
||||
status: '1' // 只查询已通过的状态
|
||||
});
|
||||
|
||||
// 确保只返回已通过审批的申请
|
||||
state.availableApplicationList = response.rows.filter((item: OverseasApplicationVO) => {
|
||||
return item.status === '1';
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取已审批通过的出国申请列表失败:', error);
|
||||
ElMessage.error('获取已审批通过的出国申请列表失败');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -798,30 +850,30 @@ const handleApplicationChange = async (applicationId: number) => {
|
||||
// 1. 获取出国申请详情
|
||||
const response = await getOverseasApplication(applicationId);
|
||||
const selectedApplication = response.data;
|
||||
|
||||
|
||||
// 2. 根据申请ID获取出国记录(包含recordId)
|
||||
const recordResponse = await getOverseasRecordByApplication(applicationId);
|
||||
const recordData = recordResponse.data;
|
||||
|
||||
|
||||
if (selectedApplication && recordData) {
|
||||
// 设置recordId(关键字段,必填)
|
||||
state.form.recordId = recordData.recordId;
|
||||
|
||||
|
||||
// 设置申请人ID(关键字段,必填)
|
||||
state.form.applicantId = selectedApplication.applicantId;
|
||||
|
||||
|
||||
// 填充本人基本情况
|
||||
state.form.applicantName = selectedApplication.applicantName;
|
||||
state.form.gender = selectedApplication.gender;
|
||||
state.form.birthDate = selectedApplication.birthDate;
|
||||
// 政治面貌:将字典值转换为文本标签
|
||||
state.form.politicalStatus = getPoliticalStatusLabel(selectedApplication.politicalStatus);
|
||||
state.form.politicalStatus = selectedApplication.politicalStatus ? getPoliticalStatusLabel(selectedApplication.politicalStatus) : '';
|
||||
state.form.professionalTitle = selectedApplication.professionalTitle;
|
||||
state.form.position = selectedApplication.position;
|
||||
state.form.documentName = selectedApplication.documentName;
|
||||
state.form.documentNumber = selectedApplication.documentNumber;
|
||||
state.form.department = selectedApplication.department;
|
||||
|
||||
|
||||
// 填充申请行程情况
|
||||
// 前往国家:优先使用destinationCountry,其次使用destination
|
||||
state.form.originalDestination = selectedApplication.destinationCountry || selectedApplication.destination;
|
||||
@@ -833,7 +885,7 @@ const handleApplicationChange = async (applicationId: number) => {
|
||||
state.form.originalStartDate = selectedApplication.startDate;
|
||||
state.form.originalEndDate = selectedApplication.endDate;
|
||||
state.form.originalDuration = selectedApplication.duration;
|
||||
|
||||
|
||||
// 默认将原信息复制到新信息
|
||||
state.form.newDestination = selectedApplication.destinationCountry || selectedApplication.destination;
|
||||
state.form.newDestinationCity = selectedApplication.destinationCity;
|
||||
@@ -867,22 +919,24 @@ const handleCancel = (row: ItineraryChangeVO) => {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputPlaceholder: '请输入撤回原因'
|
||||
}).then(async ({ value }) => {
|
||||
try {
|
||||
await cancelItineraryChange(row.changeId, value);
|
||||
ElMessage.success('撤回成功');
|
||||
|
||||
// 根据当前Tab刷新列表
|
||||
if (state.activeTab === 'all') {
|
||||
getList();
|
||||
} else if (state.activeTab === 'my') {
|
||||
getMyList();
|
||||
})
|
||||
.then(async ({ value }) => {
|
||||
try {
|
||||
await cancelItineraryChange(row.changeId, value);
|
||||
ElMessage.success('撤回成功');
|
||||
|
||||
// 根据当前Tab刷新列表
|
||||
if (state.activeTab === 'all') {
|
||||
getList();
|
||||
} else if (state.activeTab === 'my') {
|
||||
getMyList();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('撤回失败:', error);
|
||||
ElMessage.error('撤回失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('撤回失败:', error);
|
||||
ElMessage.error('撤回失败');
|
||||
}
|
||||
}).catch(() => {});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
// 重新提交
|
||||
@@ -890,12 +944,12 @@ const handleResubmit = async (row: ItineraryChangeVO) => {
|
||||
try {
|
||||
const response = await getItineraryChange(row.changeId);
|
||||
Object.assign(state.form, response.data);
|
||||
|
||||
|
||||
// 解析变更类型
|
||||
if (response.data.changeType) {
|
||||
state.changeTypes = response.data.changeType.split(',');
|
||||
}
|
||||
|
||||
|
||||
state.dialog.title = '重新提交行程变更申请';
|
||||
state.dialog.visible = true;
|
||||
} catch (error) {
|
||||
@@ -910,30 +964,32 @@ const handleDelete = (row: ItineraryChangeVO) => {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
try {
|
||||
await delItineraryChange(row.changeId);
|
||||
ElMessage.success('删除成功');
|
||||
|
||||
// 根据当前Tab刷新列表(如果删除的是当前页最后一条且不是第1页,返回上一页)
|
||||
if (state.activeTab === 'all') {
|
||||
// 如果删除的是当前页最后一条且不是第1页,返回上一页
|
||||
if (state.changeList.length === 1 && state.queryParams.pageNum > 1) {
|
||||
state.queryParams.pageNum--;
|
||||
})
|
||||
.then(async () => {
|
||||
try {
|
||||
await delItineraryChange(row.changeId);
|
||||
ElMessage.success('删除成功');
|
||||
|
||||
// 根据当前Tab刷新列表(如果删除的是当前页最后一条且不是第1页,返回上一页)
|
||||
if (state.activeTab === 'all') {
|
||||
// 如果删除的是当前页最后一条且不是第1页,返回上一页
|
||||
if (state.changeList.length === 1 && state.queryParams.pageNum > 1) {
|
||||
state.queryParams.pageNum--;
|
||||
}
|
||||
getList();
|
||||
} else if (state.activeTab === 'my') {
|
||||
// 如果删除的是当前页最后一条且不是第1页,返回上一页
|
||||
if (state.myChangeList.length === 1 && state.myQueryParams.pageNum > 1) {
|
||||
state.myQueryParams.pageNum--;
|
||||
}
|
||||
getMyList();
|
||||
}
|
||||
getList();
|
||||
} else if (state.activeTab === 'my') {
|
||||
// 如果删除的是当前页最后一条且不是第1页,返回上一页
|
||||
if (state.myChangeList.length === 1 && state.myQueryParams.pageNum > 1) {
|
||||
state.myQueryParams.pageNum--;
|
||||
}
|
||||
getMyList();
|
||||
} catch (error) {
|
||||
console.error('删除失败:', error);
|
||||
ElMessage.error('删除失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除失败:', error);
|
||||
ElMessage.error('删除失败');
|
||||
}
|
||||
}).catch(() => {});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
// 查看变更历史
|
||||
@@ -963,15 +1019,14 @@ const submitForm = () => {
|
||||
if (state.form.newExitReason !== state.form.originalExitReason) {
|
||||
changeTypeList.push('出境事由变更');
|
||||
}
|
||||
if (state.form.newStartDate !== state.form.originalStartDate ||
|
||||
state.form.newEndDate !== state.form.originalEndDate) {
|
||||
if (state.form.newStartDate !== state.form.originalStartDate || state.form.newEndDate !== state.form.originalEndDate) {
|
||||
changeTypeList.push('时间变更');
|
||||
}
|
||||
if (state.form.newItinerary && state.form.newItinerary !== state.form.originalItinerary) {
|
||||
changeTypeList.push('行程变更');
|
||||
}
|
||||
state.form.changeType = changeTypeList.length > 0 ? changeTypeList.join(',') : '其他变更';
|
||||
|
||||
|
||||
state.submitLoading = true;
|
||||
try {
|
||||
if (state.form.changeId) {
|
||||
@@ -982,7 +1037,7 @@ const submitForm = () => {
|
||||
ElMessage.success('提交成功!变更申请已进入审批流程。');
|
||||
}
|
||||
state.dialog.visible = false;
|
||||
|
||||
|
||||
// 根据当前Tab刷新列表(重置到第1页)
|
||||
if (state.activeTab === 'all') {
|
||||
state.queryParams.pageNum = 1;
|
||||
@@ -1056,11 +1111,11 @@ const getStatusTagType = (status: string): 'primary' | 'success' | 'info' | 'war
|
||||
const typeMap: Record<string, 'primary' | 'success' | 'info' | 'warning' | 'danger'> = {
|
||||
'0': 'warning', // 待审核
|
||||
'1': 'success', // 已通过
|
||||
'2': 'danger' // 已拒绝
|
||||
'2': 'danger', // 已拒绝
|
||||
'3': 'info' // 已撤回
|
||||
};
|
||||
return typeMap[status] || 'info';
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -1081,6 +1136,8 @@ const getStatusTagType = (status: string): 'primary' | 'success' | 'info' | 'war
|
||||
.table-card {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
:global(.app-container) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -49,24 +49,24 @@
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮区域 -->
|
||||
<el-card shadow="never" class="button-card table-card">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Refresh" @click="handleRefreshPending">刷新</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<div class="info-text">
|
||||
<el-icon><InfoFilled /></el-icon>
|
||||
<span>这里显示所有待审批申请</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<!-- 操作按钮区域 -->
|
||||
<el-card shadow="never" class="button-card table-card">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Refresh" @click="handleRefreshPending">刷新</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<div class="info-text">
|
||||
<el-icon><InfoFilled /></el-icon>
|
||||
<span>这里显示所有待审批申请</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<!-- 表格 -->
|
||||
<el-table v-loading="pendingLoading" :data="pendingList" border>
|
||||
<!-- 表格 -->
|
||||
<el-table v-loading="pendingLoading" :data="pendingList" border>
|
||||
<el-table-column label="申请人" prop="applicantName" min-width="100" align="center" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="所属部门" prop="department" min-width="140" align="center" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="前往国家" prop="destinationCountry" min-width="110" align="center" :show-overflow-tooltip="true" />
|
||||
@@ -350,8 +350,16 @@
|
||||
<template #footer>
|
||||
<div style="display: flex; justify-content: space-between; width: 100%">
|
||||
<div>
|
||||
<!-- 只有审批完成的申请才显示下载PDF按钮 -->
|
||||
<el-button v-if="currentDetail && currentDetail.status === '1'" type="primary" icon="Download" @click="handleDownloadPdf" v-hasPermi="['rs:overseas:query']">下载审批表PDF</el-button>
|
||||
<!-- 只有审批完成的申请才显示下载Word按钮 -->
|
||||
<el-button
|
||||
v-if="currentDetail && currentDetail.status === '1'"
|
||||
type="primary"
|
||||
icon="Download"
|
||||
@click="handleDownloadApprovalWord"
|
||||
v-hasPermi="['rs:overseas:query']"
|
||||
>
|
||||
下载审批表Word
|
||||
</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<el-button @click="detailDialogVisible = false">关闭</el-button>
|
||||
@@ -362,14 +370,15 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="OverseasApprovalMonitor">
|
||||
<script setup lang="ts" name="OverseasApprovalMonitor">
|
||||
import { ref, reactive, onMounted, watch } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { Clock, Search, Refresh, View, EditPen, InfoFilled, Download } from '@element-plus/icons-vue';
|
||||
|
||||
// API请求
|
||||
import { getPendingList as apiGetPendingList, getCompletedList as apiGetCompletedList, getDetail, downloadApprovalPdf } from '@/api/rs/overseas';
|
||||
import { getPendingList as apiGetPendingList, getCompletedList as apiGetCompletedList, getDetail } from '@/api/rs/overseas';
|
||||
|
||||
// 导入变更历史API接口
|
||||
import { getItineraryChangeHistory } from '@/api/rs/overseas';
|
||||
@@ -405,6 +414,8 @@ const completedQueryParams = reactive({
|
||||
department: undefined,
|
||||
destination: undefined,
|
||||
applyTimeRange: undefined,
|
||||
applyTimeStart: undefined as string | undefined,
|
||||
applyTimeEnd: undefined as string | undefined,
|
||||
status: '1'
|
||||
});
|
||||
|
||||
@@ -691,48 +702,180 @@ const getChangeStatusTagType = (status) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 下载审批表PDF
|
||||
* 下载审批表Word
|
||||
*/
|
||||
const handleDownloadPdf = async () => {
|
||||
if (!currentDetail.value || !currentDetail.value.applicationId) {
|
||||
const handleDownloadApprovalWord = async () => {
|
||||
const detail = currentDetail.value;
|
||||
if (!detail || !detail.applicationId) {
|
||||
ElMessage.error('无法获取申请ID');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ElMessage.info('正在生成PDF,请稍候...');
|
||||
|
||||
// 调用API获取blob数据
|
||||
const response = await downloadApprovalPdf(currentDetail.value.applicationId);
|
||||
|
||||
// 创建带MIME类型的Blob对象
|
||||
const blob = response instanceof Blob
|
||||
? new Blob([response], { type: 'application/pdf' })
|
||||
: new Blob([response.data], { type: 'application/pdf' });
|
||||
|
||||
// 创建下载链接
|
||||
ElMessage.info('正在生成审批表,请稍候...');
|
||||
|
||||
const templateData = buildApprovalTemplateData(detail);
|
||||
const [Docxtemplater, PizZip, ImageModule] = await Promise.all([
|
||||
import('docxtemplater'),
|
||||
import('pizzip'),
|
||||
import('docxtemplater-image-module-free')
|
||||
]);
|
||||
const templateResponse = await fetch('/chuguojingshenpibiao.docx', { cache: 'no-store' });
|
||||
|
||||
if (!templateResponse.ok) {
|
||||
throw new Error('模板文件加载失败,请联系管理员');
|
||||
}
|
||||
|
||||
const arrayBuffer = await templateResponse.arrayBuffer();
|
||||
const zip = new PizZip.default(arrayBuffer);
|
||||
const imageOpts = {
|
||||
centered: false,
|
||||
getImage: (base64: string) => {
|
||||
if (!base64) {
|
||||
return null;
|
||||
}
|
||||
const pure = base64.split(',')[1] || base64;
|
||||
const bytes = Uint8Array.from(atob(pure), (char) => char.charCodeAt(0));
|
||||
return bytes.buffer;
|
||||
},
|
||||
getSize: () => [60, 20]
|
||||
};
|
||||
|
||||
const doc = new Docxtemplater.default(zip, {
|
||||
modules: [new ImageModule.default(imageOpts)],
|
||||
paragraphLoop: true,
|
||||
linebreaks: true,
|
||||
delimiters: { start: '{{', end: '}}' },
|
||||
parser: (tag: string) => {
|
||||
return {
|
||||
get: (scope: Record<string, any>) => {
|
||||
const cleanTag = tag.trim().replace(/^%+/, '');
|
||||
return scope[cleanTag];
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
doc.render(templateData);
|
||||
|
||||
const blob = doc.getZip().generate({
|
||||
type: 'blob',
|
||||
mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||
});
|
||||
|
||||
const fileName = `出国申请审批表-${detail.applicantName || dayjs().format('YYYYMMDDHHmmss')}.docx`;
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
|
||||
// 设置下载文件名(使用申请人姓名和时间戳)
|
||||
const fileName = `出国申请审批表_${currentDetail.value.applicantName}_${currentDetail.value.applicationId}.pdf`;
|
||||
link.download = fileName;
|
||||
|
||||
// 触发下载
|
||||
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
// 清理资源
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
|
||||
ElMessage.success('PDF下载成功');
|
||||
} catch (error) {
|
||||
console.error('下载PDF失败', error);
|
||||
ElMessage.error('下载PDF失败,请稍后重试');
|
||||
|
||||
ElMessage.success('审批表下载成功');
|
||||
} catch (error: any) {
|
||||
console.error('下载审批表失败', error);
|
||||
ElMessage.error(error?.message || '下载审批表失败,请稍后重试');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 构建审批表模板数据
|
||||
*/
|
||||
const buildApprovalTemplateData = (detail) => {
|
||||
const startParts = getDateParts(detail.startDate);
|
||||
const endParts = getDateParts(detail.endDate);
|
||||
const applyParts = getDateParts(detail.applyTime || detail.createTime);
|
||||
const durationDays = detail.duration || calculateDuration(detail.startDate, detail.endDate);
|
||||
const templateData: Record<string, any> = {
|
||||
applicantName: detail.applicantName || '',
|
||||
genderText: detail.genderText || '',
|
||||
birthYear: getDateParts(detail.birthDate).year,
|
||||
birthMonth: getDateParts(detail.birthDate).month,
|
||||
birthDay: getDateParts(detail.birthDate).day,
|
||||
politicalStatusText: detail.politicalStatusText || '-',
|
||||
department: detail.department || '-',
|
||||
professionalTitle: detail.professionalTitle || '-',
|
||||
position: detail.position || '-',
|
||||
documentName: detail.documentName || '-',
|
||||
documentNumber: detail.documentNumber || '-',
|
||||
destinationCountry: detail.destinationCountry || detail.destination || '-',
|
||||
isTourismChecked: detail.exitReason === '旅游' ? '√' : '',
|
||||
isVisitChecked: detail.exitReason === '探亲' ? '√' : '',
|
||||
visitRelationship: detail.visitRelationship || '',
|
||||
remarks: detail.remarks || '',
|
||||
startYear: startParts.year,
|
||||
startMonth: startParts.month,
|
||||
startDay: startParts.day,
|
||||
endYear: endParts.year,
|
||||
endMonth: endParts.month,
|
||||
endDay: endParts.day,
|
||||
durationDays: durationDays || '',
|
||||
applicantSignature: detail.signature || '',
|
||||
mobilePhone: detail.mobilePhone || detail.contactPhone || '',
|
||||
emergencyContact: detail.emergencyContact || '',
|
||||
applicantSignYear: applyParts.year,
|
||||
applicantSignMonth: applyParts.month,
|
||||
applicantSignDay: applyParts.day
|
||||
};
|
||||
|
||||
fillApprovalNodeData(templateData, detail.approvalHistory || []);
|
||||
return templateData;
|
||||
};
|
||||
|
||||
/**
|
||||
* 计算出国天数
|
||||
*/
|
||||
const calculateDuration = (startDate, endDate) => {
|
||||
if (!startDate || !endDate) return '';
|
||||
const start = dayjs(startDate);
|
||||
const end = dayjs(endDate);
|
||||
if (!start.isValid() || !end.isValid()) return '';
|
||||
return Math.max(end.diff(start, 'day') + 1, 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取日期拆分
|
||||
*/
|
||||
const getDateParts = (date) => {
|
||||
if (!date) {
|
||||
return { year: '', month: '', day: '' };
|
||||
}
|
||||
const parsed = dayjs(date);
|
||||
if (!parsed.isValid()) {
|
||||
return { year: '', month: '', day: '' };
|
||||
}
|
||||
return {
|
||||
year: parsed.format('YYYY'),
|
||||
month: parsed.format('MM'),
|
||||
day: parsed.format('DD')
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 填充审批节点数据
|
||||
*/
|
||||
const fillApprovalNodeData = (templateData, approvalHistory) => {
|
||||
const approvalNodes = [
|
||||
{ level: '1', prefix: 'deptLeader' },
|
||||
{ level: '2', prefix: 'hr' },
|
||||
{ level: '3', prefix: 'discipline' },
|
||||
{ level: '4', prefix: 'viceDean' },
|
||||
{ level: '5', prefix: 'secretary' }
|
||||
];
|
||||
|
||||
approvalNodes.forEach((node) => {
|
||||
const record = approvalHistory.find((item) => item.approvalLevel === node.level);
|
||||
const dateParts = getDateParts(record?.approvalTime);
|
||||
templateData[`${node.prefix}Signature`] = record?.approvalSignature || record?.signature || '';
|
||||
templateData[`${node.prefix}Opinion`] = record?.approvalOpinion || '';
|
||||
templateData[`${node.prefix}Year`] = dateParts.year;
|
||||
templateData[`${node.prefix}Month`] = dateParts.month;
|
||||
templateData[`${node.prefix}Day`] = dateParts.day;
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
# 出国申请进度查询功能修改总结 - 快速参考
|
||||
|
||||
## ✅ 修改完成
|
||||
|
||||
**修改时间**:2025-10-29
|
||||
**修改范围**:仅修改monitor相关文件,未影响其他模块
|
||||
|
||||
---
|
||||
|
||||
## 📋 核心修改
|
||||
|
||||
### 1. 功能变化
|
||||
|
||||
#### 修改前
|
||||
- ❌ 显示工作流迁移提示
|
||||
- ❌ 无实际查询功能
|
||||
- ❌ 表格被隐藏
|
||||
|
||||
#### 修改后
|
||||
- ✅ 显示当前用户提交的待审批申请
|
||||
- ✅ 显示当前用户提交的已完成审批申请(抄送)
|
||||
- ✅ 支持搜索和查看审批历史
|
||||
|
||||
---
|
||||
|
||||
## 🎯 核心逻辑
|
||||
|
||||
### 数据过滤
|
||||
```java
|
||||
// 后端Service层添加过滤条件
|
||||
Long currentUserId = LoginHelper.getUserId();
|
||||
wrapper.eq("applicant_id", currentUserId); // 🔴 只查询当前用户提交的申请
|
||||
```
|
||||
|
||||
### 功能说明
|
||||
- **待审批中**:用户提交申请后,在这里查看审批进度
|
||||
- **审批完成**:审批流程完成后,抄送一份到这里
|
||||
|
||||
---
|
||||
|
||||
## 📄 修改文件
|
||||
|
||||
| 文件 | 类型 | 修改内容 |
|
||||
|------|------|---------|
|
||||
| `RsOverseasApprovalMonitorServiceImpl.java` | 后端 | 添加当前用户过滤 |
|
||||
| `RsOverseasApprovalMonitorController.java` | 后端 | 恢复pending接口 |
|
||||
| `monitor/index.vue` | 前端 | 删除提示,恢复表格 |
|
||||
|
||||
---
|
||||
|
||||
## 🧪 快速测试
|
||||
|
||||
### 测试步骤
|
||||
1. 登录系统
|
||||
2. 访问"出国申请进度查询"
|
||||
3. 查看"待审批中"和"审批完成"选项卡
|
||||
|
||||
### 预期结果
|
||||
- ✅ 只显示当前用户提交的申请
|
||||
- ✅ 不显示其他用户的申请
|
||||
- ✅ 可以查看审批历史
|
||||
|
||||
---
|
||||
|
||||
## 🚀 部署命令
|
||||
|
||||
```bash
|
||||
# 后端
|
||||
cd backend
|
||||
mvn clean package -pl ruoyi-admin -am -DskipTests
|
||||
./stop-main.sh && ./start-main.sh
|
||||
|
||||
# 前端(如需要)
|
||||
cd web
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 详细文档
|
||||
|
||||
完整修改说明请参考:`出国申请进度查询功能修改说明.md`
|
||||
|
||||
---
|
||||
|
||||
**状态**:✅ 已完成
|
||||
**版本**:v1.0
|
||||
|
||||
@@ -1,365 +0,0 @@
|
||||
# 出国申请进度查询功能优化说明
|
||||
|
||||
**日期**: 2025-01-29
|
||||
**模块**: 出国申请进度查询 (`/rs/overseas/monitor`)
|
||||
|
||||
## 📋 功能需求
|
||||
|
||||
### 1. 变更历史记录条件显示
|
||||
- **需求**: 变更历史记录字段只在有变更记录时显示,没有变更记录时不显示该区域
|
||||
- **实现位置**: `web/src/views/rs/overseas/monitor/index.vue`
|
||||
|
||||
### 2. PDF下载功能
|
||||
- **需求**: 在审批完成选项卡的详情弹窗中新增PDF下载按钮
|
||||
- **PDF模板**: 杭州第一技师学院登记报备人员因私事出国(境)审批表
|
||||
- **显示条件**: 仅当申请状态为"已通过"(status='1')时显示下载按钮
|
||||
|
||||
## 🎯 实现内容
|
||||
|
||||
### 一、前端修改
|
||||
|
||||
#### 1. 变更历史记录条件显示
|
||||
**文件**: `web/src/views/rs/overseas/monitor/index.vue`
|
||||
|
||||
**修改内容**:
|
||||
- 原条件: `v-if="currentDetail.recordId"`
|
||||
- 新条件: `v-if="currentDetail.recordId && changeHistoryList.length > 0"`
|
||||
- 移除空状态提示组件 `<el-empty>`
|
||||
|
||||
**代码位置**: 第287行
|
||||
|
||||
```vue
|
||||
<!-- 变更历史记录(仅已完成且有变更记录时显示) -->
|
||||
<div v-if="currentDetail.recordId && changeHistoryList.length > 0" style="margin-top: 20px">
|
||||
<el-divider content-position="left">
|
||||
<span style="font-size: 16px; font-weight: bold; color: #409eff">
|
||||
<el-icon style="vertical-align: middle; margin-right: 5px"><EditPen /></el-icon>
|
||||
变更历史记录
|
||||
</span>
|
||||
</el-divider>
|
||||
|
||||
<!-- 变更历史列表 -->
|
||||
<div v-loading="changeHistoryLoading">
|
||||
<el-table :data="changeHistoryList" border style="width: 100%" max-height="400px">
|
||||
<!-- 表格列定义 -->
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### 2. PDF下载按钮
|
||||
**文件**: `web/src/views/rs/overseas/monitor/index.vue`
|
||||
|
||||
**新增内容**:
|
||||
1. **导入图标**: 添加 `Download` 图标
|
||||
2. **导入API**: 添加 `downloadApprovalPdf` 方法
|
||||
3. **弹窗footer布局**: 改为左右布局,左侧显示下载按钮,右侧显示关闭按钮
|
||||
4. **下载方法**: 添加 `handleDownloadPdf` 方法
|
||||
|
||||
**代码位置**: 第350-360行
|
||||
|
||||
```vue
|
||||
<template #footer>
|
||||
<div style="display: flex; justify-content: space-between; width: 100%">
|
||||
<div>
|
||||
<!-- 只有审批完成的申请才显示下载PDF按钮 -->
|
||||
<el-button
|
||||
v-if="currentDetail && currentDetail.status === '1'"
|
||||
type="primary"
|
||||
icon="Download"
|
||||
@click="handleDownloadPdf"
|
||||
v-hasPermi="['rs:overseas:query']">
|
||||
下载审批表PDF
|
||||
</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<el-button @click="detailDialogVisible = false">关闭</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
**JavaScript方法** (第693-710行):
|
||||
```javascript
|
||||
/**
|
||||
* 下载审批表PDF
|
||||
*/
|
||||
const handleDownloadPdf = async () => {
|
||||
if (!currentDetail.value || !currentDetail.value.applicationId) {
|
||||
ElMessage.error('无法获取申请ID');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ElMessage.info('正在生成PDF,请稍候...');
|
||||
await downloadApprovalPdf(currentDetail.value.applicationId);
|
||||
ElMessage.success('PDF下载成功');
|
||||
} catch (error) {
|
||||
console.error('下载PDF失败', error);
|
||||
ElMessage.error('下载PDF失败,请稍后重试');
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### 3. API接口定义
|
||||
**文件**: `web/src/api/rs/overseas.ts`
|
||||
|
||||
**新增内容**: Monitor相关API接口(第1036-1085行)
|
||||
|
||||
```typescript
|
||||
// ==================== 出国申请进度查询(监控)相关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'
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 二、后端修改
|
||||
|
||||
#### 1. Service接口
|
||||
**文件**: `backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsOverseasApprovalMonitorService.java`
|
||||
|
||||
**新增方法**:
|
||||
```java
|
||||
/**
|
||||
* 下载出国申请审批表PDF
|
||||
*
|
||||
* @param applicationId 申请ID
|
||||
* @param response HTTP响应对象
|
||||
*/
|
||||
void downloadApprovalPdf(Long applicationId, HttpServletResponse response);
|
||||
```
|
||||
|
||||
#### 2. Controller接口
|
||||
**文件**: `backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApprovalMonitorController.java`
|
||||
|
||||
**新增接口** (第119-135行):
|
||||
```java
|
||||
/**
|
||||
* 下载出国申请审批表PDF
|
||||
* <p>
|
||||
* 生成并下载"杭州第一技师学院登记报备人员因私事出国(境)审批表"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);
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. Service实现
|
||||
**文件**: `backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalMonitorServiceImpl.java`
|
||||
|
||||
**新增依赖导入**:
|
||||
```java
|
||||
import com.itextpdf.text.*;
|
||||
import com.itextpdf.text.pdf.*;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
```
|
||||
|
||||
**新增方法** (第521-808行):
|
||||
|
||||
1. **`downloadApprovalPdf`**: 主方法,负责查询数据和设置响应头
|
||||
2. **`generateApprovalPdf`**: PDF生成核心方法
|
||||
3. **`addCell`**: 添加表格单元格辅助方法
|
||||
4. **`addApprovalRow`**: 添加审批意见行辅助方法
|
||||
5. **`getApprovalOpinion`**: 获取指定级别审批意见
|
||||
6. **`formatDate`**: 格式化日期(yyyy-MM-dd)
|
||||
7. **`formatBirthDate`**: 格式化出生日期(yyyy年MM月)
|
||||
|
||||
**PDF文档结构**:
|
||||
1. 标题:
|
||||
- "杭州第一技师学院"
|
||||
- "登记报备人员因私事出国(境)审批表"
|
||||
|
||||
2. 个人信息表格(8列):
|
||||
- 姓名、性别、出生年月、政治面貌
|
||||
- 现工作部门(教学系)、职称、职务
|
||||
- 申领证件名称、证件号码
|
||||
- 前往国家或地区、出境事由(旅游/探亲)
|
||||
- 探望对象姓名及与申请者关系
|
||||
- 出国时间、备注
|
||||
- 承诺声明、联系电话、应急联系人
|
||||
|
||||
3. 审批意见表格(2列):
|
||||
- 部门(教学系)意见
|
||||
- 人事(组织)处审核意见
|
||||
- 纪委意见
|
||||
- 学院分管领导意见
|
||||
- 学院党委书记意见
|
||||
|
||||
4. 备注说明:
|
||||
- 因私出境时间说明
|
||||
- 证件号码填写说明
|
||||
|
||||
## 🔧 技术实现细节
|
||||
|
||||
### PDF生成技术
|
||||
- **库**: iText 5.5.13.3
|
||||
- **中文字体**: STSong-Light (UniGB-UCS2-H)
|
||||
- **页面尺寸**: A4
|
||||
- **页边距**: 40pt (上下左右)
|
||||
|
||||
### 字体大小
|
||||
- **标题字体**: 16pt, 加粗
|
||||
- **副标题字体**: 14pt, 加粗
|
||||
- **正文字体**: 10pt, 常规
|
||||
- **小字体**: 9pt, 常规
|
||||
|
||||
### 表格设计
|
||||
- **个人信息表**: 8列,列宽比例 [8:12:6:10:10:12:16:6]
|
||||
- **审批意见表**: 2列,列宽比例 [20:80]
|
||||
- **单元格最小高度**: 25-50pt
|
||||
- **表头背景**: 浅灰色 (BaseColor.LIGHT_GRAY)
|
||||
|
||||
### 数据处理
|
||||
- **性别翻译**: 0→男, 1→女
|
||||
- **政治面貌翻译**: 支持多种编码格式(01-13, 1-13, 英文代码)
|
||||
- **出境事由**: 旅游/探亲,用√标记
|
||||
- **日期格式化**: yyyy-MM-dd / yyyy年MM月
|
||||
- **审批意见**: 优先显示审批意见,无意见时通过状态显示"同意"
|
||||
|
||||
## 📝 权限要求
|
||||
|
||||
- **查看权限**: `rs:overseas:query`
|
||||
- **列表权限**: `rs:overseas:list`
|
||||
|
||||
## 🎨 UI/UX优化
|
||||
|
||||
1. **按钮位置**: 弹窗footer左右布局,下载按钮在左侧,关闭按钮在右侧
|
||||
2. **按钮样式**: 主色调按钮(type="primary")+ 下载图标
|
||||
3. **显示条件**: 仅审批完成状态显示下载按钮
|
||||
4. **用户提示**:
|
||||
- 开始下载:"正在生成PDF,请稍候..."
|
||||
- 下载成功:"PDF下载成功"
|
||||
- 下载失败:"下载PDF失败,请稍后重试"
|
||||
|
||||
## ✅ 测试要点
|
||||
|
||||
### 前端测试
|
||||
1. ✅ 有变更记录时,变更历史区域正常显示
|
||||
2. ✅ 无变更记录时,变更历史区域不显示
|
||||
3. ✅ 审批完成状态显示下载PDF按钮
|
||||
4. ✅ 待审批状态不显示下载PDF按钮
|
||||
5. ✅ 点击下载按钮能触发PDF下载
|
||||
6. ✅ 下载过程中显示加载提示
|
||||
|
||||
### 后端测试
|
||||
1. ✅ PDF文件名包含申请人姓名和时间戳
|
||||
2. ✅ PDF包含完整的个人信息
|
||||
3. ✅ PDF包含所有审批历史记录
|
||||
4. ✅ 中文字体正确显示
|
||||
5. ✅ 表格格式符合模板要求
|
||||
6. ✅ 审批意见正确填充
|
||||
7. ✅ 日期格式化正确
|
||||
|
||||
## 📊 文件清单
|
||||
|
||||
### 前端文件
|
||||
- ✅ `web/src/views/rs/overseas/monitor/index.vue` (修改)
|
||||
- ✅ `web/src/api/rs/overseas.ts` (修改,新增monitor相关API)
|
||||
|
||||
### 后端文件
|
||||
- ✅ `backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsOverseasApprovalMonitorService.java` (修改)
|
||||
- ✅ `backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApprovalMonitorController.java` (修改)
|
||||
- ✅ `backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalMonitorServiceImpl.java` (修改)
|
||||
|
||||
### 文档文件
|
||||
- ✅ `web/src/views/rs/overseas/monitor/出国申请进度查询功能优化说明.md` (新增)
|
||||
|
||||
## 🚀 部署说明
|
||||
|
||||
### 前端部署
|
||||
```bash
|
||||
cd web
|
||||
npm run build
|
||||
# 部署dist目录到Nginx
|
||||
```
|
||||
|
||||
### 后端部署
|
||||
```bash
|
||||
cd backend
|
||||
mvn clean package -DskipTests
|
||||
# 重启ruoyi-admin服务
|
||||
```
|
||||
|
||||
### 依赖检查
|
||||
后端已包含iText PDF生成库依赖:
|
||||
- `com.itextpdf:itextpdf:5.5.13.3`
|
||||
- `com.itextpdf:itext-asian:5.2.0` (中文字体支持)
|
||||
|
||||
## 📖 相关文档
|
||||
|
||||
- **项目规范**: `.cursor/cursorrules.md`
|
||||
- **开发文档**: `docs/开发文档/API/`
|
||||
- **出国申请API**: `docs/开发文档/API/出国申请管理API.md`
|
||||
|
||||
## 🔗 相关模块
|
||||
|
||||
- **出国申请管理**: `/rs/overseas/application`
|
||||
- **出国记录管理**: `/rs/overseas/record`
|
||||
- **行程变更管理**: `/rs/itinerary/change`
|
||||
|
||||
---
|
||||
|
||||
**实施人**: Cursor AI
|
||||
**审核状态**: ✅ 已完成
|
||||
**更新日期**: 2025-01-29
|
||||
|
||||
@@ -1,394 +0,0 @@
|
||||
# 出国申请进度查询功能修改说明
|
||||
|
||||
## 📋 修改概述
|
||||
|
||||
**修改时间**:2025-10-29
|
||||
**修改目的**:按照审批流程要求,修改出国申请进度查询功能,实现申请人查看自己提交的申请进度
|
||||
|
||||
---
|
||||
|
||||
## 🎯 功能需求
|
||||
|
||||
### 1. 待审批选项卡
|
||||
- **功能**:用户提交申请后,在这里查看自己的待审批记录
|
||||
- **显示内容**:
|
||||
- 当前用户提交的所有待审批申请
|
||||
- 当前审批环节和审批人
|
||||
- 审批历史记录
|
||||
|
||||
### 2. 审批完成选项卡
|
||||
- **功能**:审批完成后,抄送一份到这里
|
||||
- **显示内容**:
|
||||
- 当前用户提交的所有已完成审批申请
|
||||
- 完整的审批历史记录
|
||||
- 关联的出国记录信息
|
||||
|
||||
---
|
||||
|
||||
## 📝 修改内容
|
||||
|
||||
### 1. 后端Service修改
|
||||
|
||||
**文件**:`backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalMonitorServiceImpl.java`
|
||||
|
||||
#### 修改点1:待审批列表查询
|
||||
```java
|
||||
/**
|
||||
* 查询审批中的申请列表(待审核状态)
|
||||
* 🔴 只查询当前用户提交的待审批申请
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<RsOverseasApprovalMonitorVo> queryPendingList(RsOverseasApprovalMonitorQuery query) {
|
||||
// 🔴 获取当前登录用户ID
|
||||
Long currentUserId = LoginHelper.getUserId();
|
||||
|
||||
// 构建查询条件
|
||||
QueryWrapper<RsOverseasApplication> wrapper = buildQueryWrapper(query);
|
||||
wrapper.eq("status", "0"); // 只查询待审核状态
|
||||
wrapper.eq("applicant_id", currentUserId); // 🔴 只查询当前用户提交的申请
|
||||
|
||||
// ... 分页查询和数据处理
|
||||
}
|
||||
```
|
||||
|
||||
**修改说明**:
|
||||
- ✅ 添加当前用户ID过滤条件
|
||||
- ✅ 只查询申请人ID = 当前用户ID的记录
|
||||
- ✅ 添加日志记录便于排查问题
|
||||
|
||||
#### 修改点2:已完成列表查询
|
||||
```java
|
||||
/**
|
||||
* 查询已完成审批的申请列表(已通过状态)
|
||||
* 🔴 只查询当前用户提交的已完成审批申请(抄送功能)
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<RsOverseasApprovalMonitorVo> queryCompletedList(RsOverseasApprovalMonitorQuery query) {
|
||||
// 🔴 获取当前登录用户ID
|
||||
Long currentUserId = LoginHelper.getUserId();
|
||||
|
||||
// 构建查询条件
|
||||
QueryWrapper<RsOverseasApplication> wrapper = buildQueryWrapper(query);
|
||||
wrapper.eq("status", "1"); // 只查询已通过状态
|
||||
wrapper.eq("applicant_id", currentUserId); // 🔴 只查询当前用户提交的申请
|
||||
|
||||
// ... 分页查询和数据处理
|
||||
}
|
||||
```
|
||||
|
||||
**修改说明**:
|
||||
- ✅ 实现抄送功能:审批完成后,申请人可以在这里查看
|
||||
- ✅ 添加当前用户ID过滤条件
|
||||
- ✅ 只显示当前用户提交且已完成审批的申请
|
||||
|
||||
---
|
||||
|
||||
### 2. 后端Controller修改
|
||||
|
||||
**文件**:`backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApprovalMonitorController.java`
|
||||
|
||||
#### 修改点:恢复pending接口功能
|
||||
```java
|
||||
/**
|
||||
* 查询待审批的申请列表(出国申请进度查询)
|
||||
* 🔴 功能说明:查询当前用户提交的待审批申请
|
||||
*/
|
||||
@SaCheckPermission("rs:overseas:list")
|
||||
@GetMapping("/pending/list")
|
||||
public TableDataInfo<RsOverseasApprovalMonitorVo> pendingList(RsOverseasApprovalMonitorQuery query) {
|
||||
log.info("查询当前用户的待审批申请列表");
|
||||
query.setStatus("0");
|
||||
return monitorService.queryPendingList(query);
|
||||
}
|
||||
```
|
||||
|
||||
**修改说明**:
|
||||
- ❌ 删除了工作流迁移提示
|
||||
- ✅ 恢复正常的查询功能
|
||||
- ✅ 返回实际数据而不是错误提示
|
||||
|
||||
---
|
||||
|
||||
### 3. 前端页面修改
|
||||
|
||||
**文件**:`web/src/views/rs/overseas/monitor/index.vue`
|
||||
|
||||
#### 修改点1:删除工作流迁移提示
|
||||
```vue
|
||||
<!-- 🔴 删除的内容 -->
|
||||
<el-alert
|
||||
title="功能迁移通知"
|
||||
description="出国申请监控功能已标准化..."
|
||||
...
|
||||
>
|
||||
<el-button @click="navigateToTaskWaiting">前往待办任务页面</el-button>
|
||||
</el-alert>
|
||||
```
|
||||
|
||||
**修改说明**:
|
||||
- ❌ 删除整个Alert提示组件
|
||||
- ❌ 删除跳转到工作流页面的按钮
|
||||
|
||||
#### 修改点2:恢复待审批表格显示
|
||||
```vue
|
||||
<!-- 表格 -->
|
||||
<el-table v-loading="pendingLoading" :data="pendingList" border>
|
||||
<!-- 之前是 style="display: none" 隐藏的,现在恢复显示 -->
|
||||
<el-table-column label="申请人" prop="applicantName" ... />
|
||||
<el-table-column label="所属部门" prop="department" ... />
|
||||
<!-- ... 其他列 -->
|
||||
</el-table>
|
||||
```
|
||||
|
||||
**修改说明**:
|
||||
- ✅ 恢复表格正常显示
|
||||
- ✅ 删除 `style="display: none"`
|
||||
- ✅ 显示待审批申请数据
|
||||
|
||||
#### 修改点3:恢复查询功能
|
||||
```javascript
|
||||
// 🔴 修改前:显示提示信息
|
||||
const getPendingList = async () => {
|
||||
ElMessage.info('请使用标准工作流待办页面查看审批任务');
|
||||
pendingList.value = [];
|
||||
};
|
||||
|
||||
// ✅ 修改后:正常查询数据
|
||||
const getPendingList = async () => {
|
||||
pendingLoading.value = true;
|
||||
try {
|
||||
const response = await apiGetPendingList(pendingQueryParams);
|
||||
if (response.code === 200) {
|
||||
pendingList.value = response.rows || [];
|
||||
pendingTotal.value = response.total || 0;
|
||||
} else {
|
||||
ElMessage.error(response.msg || '获取数据失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取待审批列表失败', error);
|
||||
ElMessage.error('获取数据失败,请稍后重试');
|
||||
} finally {
|
||||
pendingLoading.value = false;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**修改说明**:
|
||||
- ✅ 调用真实API接口
|
||||
- ✅ 处理loading状态
|
||||
- ✅ 显示查询结果或错误提示
|
||||
|
||||
#### 修改点4:恢复查询和重置按钮功能
|
||||
```javascript
|
||||
// 查询按钮
|
||||
const handlePendingQuery = () => {
|
||||
pendingQueryParams.pageNum = 1;
|
||||
getPendingList();
|
||||
};
|
||||
|
||||
// 重置按钮
|
||||
const resetPendingQuery = () => {
|
||||
pendingQueryParams.pageNum = 1;
|
||||
pendingQueryParams.applicantName = undefined;
|
||||
pendingQueryParams.department = undefined;
|
||||
pendingQueryParams.destination = undefined;
|
||||
pendingQueryParams.approvalLevel = undefined;
|
||||
getPendingList();
|
||||
};
|
||||
```
|
||||
|
||||
**修改说明**:
|
||||
- ✅ 恢复正常的查询功能
|
||||
- ✅ 恢复正常的重置功能
|
||||
- ❌ 删除工作流跳转提示
|
||||
|
||||
#### 修改点5:添加功能说明
|
||||
```vue
|
||||
<el-col :span="1.5">
|
||||
<div class="info-text">
|
||||
<el-icon><InfoFilled /></el-icon>
|
||||
<span>这里显示您提交的待审批申请</span>
|
||||
</div>
|
||||
</el-col>
|
||||
```
|
||||
|
||||
**修改说明**:
|
||||
- ✅ 在操作按钮区域添加功能说明
|
||||
- ✅ 明确告知用户这里只显示自己提交的申请
|
||||
- ✅ 添加对应的CSS样式
|
||||
|
||||
---
|
||||
|
||||
## 🎨 UI变化
|
||||
|
||||
### 修改前
|
||||
```
|
||||
[Alert提示框:功能迁移通知]
|
||||
[前往待办任务页面按钮]
|
||||
[Empty: 请使用标准工作流待办页面]
|
||||
```
|
||||
|
||||
### 修改后
|
||||
```
|
||||
[搜索条件]
|
||||
[刷新按钮] [ℹ️ 这里显示您提交的待审批申请]
|
||||
[数据表格:显示待审批申请列表]
|
||||
[分页组件]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 数据过滤逻辑
|
||||
|
||||
### 待审批选项卡
|
||||
```sql
|
||||
SELECT * FROM rs_overseas_application
|
||||
WHERE status = '0' -- 待审核状态
|
||||
AND applicant_id = {当前用户ID} -- 🔴 只查询当前用户提交的
|
||||
AND deleted = '0'
|
||||
AND tenant_id = {租户ID}
|
||||
ORDER BY apply_time DESC
|
||||
```
|
||||
|
||||
### 审批完成选项卡
|
||||
```sql
|
||||
SELECT * FROM rs_overseas_application
|
||||
WHERE status = '1' -- 已通过状态
|
||||
AND applicant_id = {当前用户ID} -- 🔴 只查询当前用户提交的
|
||||
AND deleted = '0'
|
||||
AND tenant_id = {租户ID}
|
||||
ORDER BY apply_time DESC
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试验证
|
||||
|
||||
### 测试场景1:待审批选项卡
|
||||
|
||||
**测试步骤**:
|
||||
1. 使用用户A登录系统
|
||||
2. 提交一个出国申请
|
||||
3. 访问"出国申请进度查询"菜单
|
||||
4. 切换到"待审批中"选项卡
|
||||
|
||||
**预期结果**:
|
||||
- ✅ 能看到刚才提交的申请记录
|
||||
- ✅ 显示当前审批环节和审批人
|
||||
- ✅ 点击"查看详情"能查看审批历史
|
||||
|
||||
**测试步骤2**:
|
||||
1. 使用用户B登录系统
|
||||
2. 访问"出国申请进度查询"菜单
|
||||
3. 切换到"待审批中"选项卡
|
||||
|
||||
**预期结果**:
|
||||
- ✅ 只能看到用户B提交的申请
|
||||
- ❌ 看不到用户A提交的申请
|
||||
|
||||
### 测试场景2:审批完成选项卡
|
||||
|
||||
**测试步骤**:
|
||||
1. 使用用户A登录系统
|
||||
2. 提交一个出国申请
|
||||
3. 完成全部审批流程
|
||||
4. 访问"出国申请进度查询"菜单
|
||||
5. 切换到"审批完成"选项卡
|
||||
|
||||
**预期结果**:
|
||||
- ✅ 能看到刚才审批完成的申请(抄送)
|
||||
- ✅ 显示完整的审批历史记录
|
||||
- ✅ 能查看关联的出国记录信息
|
||||
|
||||
### 测试场景3:搜索功能
|
||||
|
||||
**测试步骤**:
|
||||
1. 在"待审批中"选项卡输入搜索条件
|
||||
2. 点击"搜索"按钮
|
||||
|
||||
**预期结果**:
|
||||
- ✅ 能正常搜索并返回结果
|
||||
- ✅ 结果仍然只包含当前用户提交的申请
|
||||
|
||||
---
|
||||
|
||||
## 📊 修改文件清单
|
||||
|
||||
| 序号 | 文件类型 | 文件路径 | 修改内容 | 状态 |
|
||||
|------|---------|---------|---------|------|
|
||||
| 1 | 后端Service | `RsOverseasApprovalMonitorServiceImpl.java` | 添加当前用户过滤条件 | ✅ |
|
||||
| 2 | 后端Controller | `RsOverseasApprovalMonitorController.java` | 恢复pending接口功能 | ✅ |
|
||||
| 3 | 前端页面 | `monitor/index.vue` | 删除工作流提示,恢复表格显示 | ✅ |
|
||||
| 4 | 前端脚本 | `monitor/index.vue` | 恢复查询、重置功能 | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 1. 权限要求
|
||||
- 用户需要有 `rs:overseas:list` 权限才能访问此菜单
|
||||
- 用户需要有 `rs:overseas:query` 权限才能查看详情
|
||||
|
||||
### 2. 数据范围
|
||||
- **待审批选项卡**:只显示当前用户提交的待审核申请
|
||||
- **审批完成选项卡**:只显示当前用户提交的已完成申请
|
||||
- 不会显示其他用户提交的申请
|
||||
|
||||
### 3. 与其他模块的关系
|
||||
- **出国申请**:用户在这里提交申请
|
||||
- **出国审批**:审批人在这里审批申请
|
||||
- **出国记录**:审批完成后,申请人在这里查看出国记录
|
||||
- **出国申请进度查询**:申请人在这里查看申请进度(当前模块)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 部署说明
|
||||
|
||||
### 后端部署
|
||||
```bash
|
||||
cd backend
|
||||
mvn clean package -pl ruoyi-admin -am -DskipTests
|
||||
./stop-main.sh
|
||||
./start-main.sh
|
||||
```
|
||||
|
||||
### 前端部署
|
||||
```bash
|
||||
cd web
|
||||
npm run build
|
||||
# 将dist目录部署到nginx
|
||||
```
|
||||
|
||||
### 验证步骤
|
||||
1. 清除浏览器缓存
|
||||
2. 重新登录系统
|
||||
3. 访问"出国申请进度查询"菜单
|
||||
4. 确认功能正常
|
||||
|
||||
---
|
||||
|
||||
## ✅ 验收标准
|
||||
|
||||
- [ ] 待审批选项卡只显示当前用户提交的待审批申请
|
||||
- [ ] 审批完成选项卡只显示当前用户提交的已完成审批申请
|
||||
- [ ] 搜索功能正常工作
|
||||
- [ ] 重置功能正常工作
|
||||
- [ ] 查看详情能显示审批历史
|
||||
- [ ] 不会显示其他用户的申请
|
||||
- [ ] UI显示正常,无工作流迁移提示
|
||||
|
||||
---
|
||||
|
||||
## 📞 问题反馈
|
||||
|
||||
如有问题,请检查:
|
||||
1. 后端日志:`backend/logs/sys-info.log`
|
||||
2. 浏览器控制台:F12查看网络请求和错误
|
||||
3. 确认用户权限配置正确
|
||||
|
||||
---
|
||||
|
||||
**文档版本**:v1.0
|
||||
**最后更新**:2025-10-29
|
||||
**修改人员**:Cursor AI
|
||||
|
||||
@@ -1,411 +0,0 @@
|
||||
# 出国申请审批环节命名标准分析报告
|
||||
|
||||
## 📋 问题发现
|
||||
|
||||
在分析代码时,发现系统中**存在两套不同的审批级别命名标准**:
|
||||
|
||||
1. **出国申请进度查询模块**:使用**3级审批**命名
|
||||
2. **出国申请审批模块**:使用**5级审批**命名
|
||||
|
||||
这导致**命名不一致**的问题。
|
||||
|
||||
---
|
||||
|
||||
## 🔍 详细分析
|
||||
|
||||
### 一、出国申请进度查询模块(monitor)
|
||||
|
||||
**文件位置**:`RsOverseasApprovalMonitorServiceImpl.java`
|
||||
|
||||
**当前定义**(第390-404行):
|
||||
```java
|
||||
private String getApprovalLevelText(String level) {
|
||||
if (level == null) {
|
||||
return "未开始";
|
||||
}
|
||||
switch (level) {
|
||||
case "1":
|
||||
return "部门主管审批";
|
||||
case "2":
|
||||
return "分管领导审批";
|
||||
case "3":
|
||||
return "校领导审批";
|
||||
default:
|
||||
return "未知";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**问题**:
|
||||
- ❌ 只定义了3个级别
|
||||
- ❌ Level 4 和 Level 5 会显示为"未知"
|
||||
- ❌ 命名与实际审批流程不符
|
||||
|
||||
---
|
||||
|
||||
### 二、出国申请审批模块(application)
|
||||
|
||||
**文件位置**:`RsOverseasApplicationServiceImpl.java`
|
||||
|
||||
**当前定义**(第1269-1283行):
|
||||
```java
|
||||
private String getApprovalLevelDescription(String approvalLevel) {
|
||||
switch (approvalLevel) {
|
||||
case "1":
|
||||
return "待部门负责人审批";
|
||||
case "2":
|
||||
return "待人事处审批";
|
||||
case "3":
|
||||
return "待纪委审批";
|
||||
case "4":
|
||||
return "待学院分管领导审批";
|
||||
case "5":
|
||||
return "待学院党委书记审批";
|
||||
default:
|
||||
return "待审批";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**流转逻辑**(第683-698行):
|
||||
```java
|
||||
/**
|
||||
* 获取下一级审批级别
|
||||
* 五级审批制度:1-部门负责人,2-人事处,3-纪委,4-学院分管领导,5-学院党委书记
|
||||
*/
|
||||
private String getNextApprovalLevel(String currentLevel) {
|
||||
switch (currentLevel) {
|
||||
case "1":
|
||||
return "2"; // 部门负责人 → 人事处
|
||||
case "2":
|
||||
return "3"; // 人事处 → 纪委
|
||||
case "3":
|
||||
return "4"; // 纪委 → 学院分管领导
|
||||
case "4":
|
||||
return "5"; // 学院分管领导 → 学院党委书记
|
||||
case "5":
|
||||
return null; // 学院党委书记 → 审批完成
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 标准审批级别定义
|
||||
|
||||
### 完整的五级审批制度
|
||||
|
||||
| 审批级别 | 数据库值 | 权限标识 | 审批环节名称 | 当前审批人显示 | 说明 |
|
||||
|---------|---------|---------|------------|--------------|------|
|
||||
| **Level 1** | `"1"` | `rs:overseas:approve1` | **部门负责人审批** | 待部门负责人审批 | 只能看到本部门的申请 |
|
||||
| **Level 2** | `"2"` | `rs:overseas:approve2` | **人事处审批** | 待人事处审批 | 可以看到所有部门的申请 |
|
||||
| **Level 3** | `"3"` | `rs:overseas:approve3` | **纪委审批** | 待纪委审批 | 可以看到所有部门的申请 |
|
||||
| **Level 4** | `"4"` | `rs:overseas:approve4` | **学院分管领导审批** | 待学院分管领导审批 | 可以看到所有部门的申请 |
|
||||
| **Level 5** | `"5"` | `rs:overseas:approve5` | **学院党委书记审批** | 待学院党委书记审批 | 可以看到所有部门的申请 |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 审批流程完整流转
|
||||
|
||||
### 流程图
|
||||
```
|
||||
申请提交
|
||||
↓
|
||||
┌─────────────────────────────────┐
|
||||
│ Level 1: 部门负责人审批 │
|
||||
│ - 审批人:各部门负责人 │
|
||||
│ - 权限:rs:overseas:approve1 │
|
||||
│ - 范围:仅本部门申请 │
|
||||
└─────────────────────────────────┘
|
||||
↓ (审批通过)
|
||||
┌─────────────────────────────────┐
|
||||
│ Level 2: 人事处审批 │
|
||||
│ - 审批人:人事处工作人员 │
|
||||
│ - 权限:rs:overseas:approve2 │
|
||||
│ - 范围:所有部门申请 │
|
||||
└─────────────────────────────────┘
|
||||
↓ (审批通过)
|
||||
┌─────────────────────────────────┐
|
||||
│ Level 3: 纪委审批 │
|
||||
│ - 审批人:纪委工作人员 │
|
||||
│ - 权限:rs:overseas:approve3 │
|
||||
│ - 范围:所有部门申请 │
|
||||
└─────────────────────────────────┘
|
||||
↓ (审批通过)
|
||||
┌─────────────────────────────────┐
|
||||
│ Level 4: 学院分管领导审批 │
|
||||
│ - 审批人:学院分管领导 │
|
||||
│ - 权限:rs:overseas:approve4 │
|
||||
│ - 范围:所有部门申请 │
|
||||
└─────────────────────────────────┘
|
||||
↓ (审批通过)
|
||||
┌─────────────────────────────────┐
|
||||
│ Level 5: 学院党委书记审批 │
|
||||
│ - 审批人:学院党委书记 │
|
||||
│ - 权限:rs:overseas:approve5 │
|
||||
│ - 范围:所有部门申请 │
|
||||
└─────────────────────────────────┘
|
||||
↓ (审批通过)
|
||||
申请完成 → 生成出国记录
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 各字段的具体命名标准
|
||||
|
||||
### 1. **当前审批环节**(approval_level字段)
|
||||
|
||||
**数据库字段**:`approval_level VARCHAR(10)`
|
||||
|
||||
**标准命名**(用于显示):
|
||||
```java
|
||||
switch (approvalLevel) {
|
||||
case "1": return "部门负责人审批";
|
||||
case "2": return "人事处审批";
|
||||
case "3": return "纪委审批";
|
||||
case "4": return "学院分管领导审批";
|
||||
case "5": return "学院党委书记审批";
|
||||
default: return "未知";
|
||||
}
|
||||
```
|
||||
|
||||
### 2. **当前审批人**(current_approver字段)
|
||||
|
||||
**数据库字段**:`current_approver VARCHAR(100)`
|
||||
|
||||
**标准命名格式**:
|
||||
```java
|
||||
switch (approvalLevel) {
|
||||
case "1": return "待部门负责人审批";
|
||||
case "2": return "待人事处审批";
|
||||
case "3": return "待纪委审批";
|
||||
case "4": return "待学院分管领导审批";
|
||||
case "5": return "待学院党委书记审批";
|
||||
default: return "待审批";
|
||||
}
|
||||
```
|
||||
|
||||
**特殊情况**:
|
||||
- 如果是部门负责人级别,可能显示:`"待指定部门负责人"` 或 `"待{具体审批人姓名}审批"`
|
||||
- 其他级别通常显示:`"待{审批环节}审批"`
|
||||
|
||||
### 3. **审批进度**(用于进度查询)
|
||||
|
||||
**显示格式**:`"已审批级别数/总级别数"`
|
||||
|
||||
**示例**:
|
||||
- `"0/5"` - 未开始审批
|
||||
- `"1/5"` - 部门负责人已审批
|
||||
- `"2/5"` - 人事处已审批
|
||||
- `"3/5"` - 纪委已审批
|
||||
- `"4/5"` - 学院分管领导已审批
|
||||
- `"5/5"` - 全部审批完成
|
||||
|
||||
---
|
||||
|
||||
## 🔧 数据库定义
|
||||
|
||||
### rs_overseas_application表字段
|
||||
|
||||
```sql
|
||||
-- 审批级别字段
|
||||
`approval_level` varchar(10) DEFAULT '1'
|
||||
COMMENT '当前审批级别:1-部门负责人,2-人事处,3-纪委,4-学院分管领导,5-学院党委书记'
|
||||
|
||||
-- 当前审批人字段
|
||||
`current_approver` varchar(100) DEFAULT NULL
|
||||
COMMENT '当前审批人姓名或环节描述'
|
||||
|
||||
-- 当前审批人ID字段(冗余字段,仅供参考)
|
||||
`current_approver_id` bigint DEFAULT NULL
|
||||
COMMENT '当前审批人ID(冗余字段)'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 前端显示示例
|
||||
|
||||
### 待审批列表显示
|
||||
|
||||
| 申请人 | 所属部门 | 当前审批环节 | 当前审批人 | 审批进度 |
|
||||
|--------|---------|-------------|-----------|---------|
|
||||
| 张三 | 教务处 | 部门负责人审批 | 待部门负责人审批 | 0% (0/5) |
|
||||
| 李四 | 人事处 | 人事处审批 | 待人事处审批 | 20% (1/5) |
|
||||
| 王五 | 学工处 | 纪委审批 | 待纪委审批 | 40% (2/5) |
|
||||
| 赵六 | 后勤处 | 学院分管领导审批 | 待学院分管领导审批 | 60% (3/5) |
|
||||
| 钱七 | 财务处 | 学院党委书记审批 | 待学院党委书记审批 | 80% (4/5) |
|
||||
|
||||
### 审批历史记录显示
|
||||
|
||||
```
|
||||
📋 审批历史
|
||||
|
||||
✅ 第1级:部门负责人审批
|
||||
审批人:张主任
|
||||
审批时间:2025-10-27 10:30:25
|
||||
审批结果:通过
|
||||
审批意见:同意出国交流
|
||||
|
||||
✅ 第2级:人事处审批
|
||||
审批人:李处长
|
||||
审批时间:2025-10-27 14:20:15
|
||||
审批结果:通过
|
||||
审批意见:人事档案无问题,同意
|
||||
|
||||
⏳ 第3级:纪委审批
|
||||
状态:待审批
|
||||
|
||||
⏸️ 第4级:学院分管领导审批
|
||||
状态:未到审批阶段
|
||||
|
||||
⏸️ 第5级:学院党委书记审批
|
||||
状态:未到审批阶段
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 当前存在的问题
|
||||
|
||||
### 问题1:命名不统一
|
||||
|
||||
**问题描述**:
|
||||
- `RsOverseasApprovalMonitorServiceImpl.java` 中只定义了3个级别
|
||||
- 与实际的5级审批流程不符
|
||||
|
||||
**影响范围**:
|
||||
- 出国申请进度查询菜单的"当前审批环节"显示
|
||||
- Level 4 和 Level 5 会显示为"未知"
|
||||
|
||||
**解决方案**:
|
||||
需要修改 `getApprovalLevelText()` 方法,添加完整的5级定义
|
||||
|
||||
### 问题2:总审批级别数设置错误
|
||||
|
||||
**问题位置**:`RsOverseasApprovalMonitorServiceImpl.java` 第345-346行
|
||||
|
||||
```java
|
||||
// 总审批级别数(假设最多3级:部门主管、分管领导、校领导)
|
||||
vo.setTotalLevels(3);
|
||||
```
|
||||
|
||||
**问题**:
|
||||
- 实际是5级审批,但这里设置为3级
|
||||
- 导致审批进度显示错误(如:2/3 而不是 2/5)
|
||||
|
||||
**影响**:
|
||||
- 审批进度百分比计算错误
|
||||
- 进度条显示不准确
|
||||
|
||||
---
|
||||
|
||||
## ✅ 建议的标准命名
|
||||
|
||||
### 统一的命名标准
|
||||
|
||||
| 级别 | 简称 | 完整名称 | 待审批显示 | 已审批显示 |
|
||||
|------|------|---------|-----------|-----------|
|
||||
| 1 | 部门负责人 | 部门负责人审批 | 待部门负责人审批 | 部门负责人已审批 |
|
||||
| 2 | 人事处 | 人事处审批 | 待人事处审批 | 人事处已审批 |
|
||||
| 3 | 纪委 | 纪委审批 | 待纪委审批 | 纪委已审批 |
|
||||
| 4 | 学院分管领导 | 学院分管领导审批 | 待学院分管领导审批 | 学院分管领导已审批 |
|
||||
| 5 | 学院党委书记 | 学院党委书记审批 | 待学院党委书记审批 | 学院党委书记已审批 |
|
||||
|
||||
### 代码实现建议
|
||||
|
||||
```java
|
||||
/**
|
||||
* 获取审批级别完整名称(用于"当前审批环节"列)
|
||||
*/
|
||||
private String getApprovalLevelText(String level) {
|
||||
if (level == null) {
|
||||
return "未开始";
|
||||
}
|
||||
switch (level) {
|
||||
case "1":
|
||||
return "部门负责人审批";
|
||||
case "2":
|
||||
return "人事处审批";
|
||||
case "3":
|
||||
return "纪委审批";
|
||||
case "4":
|
||||
return "学院分管领导审批";
|
||||
case "5":
|
||||
return "学院党委书记审批";
|
||||
default:
|
||||
return "未知";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前审批人显示文本(用于"当前审批人"列)
|
||||
*/
|
||||
private String getCurrentApproverText(String level) {
|
||||
if (level == null) {
|
||||
return "未指定";
|
||||
}
|
||||
switch (level) {
|
||||
case "1":
|
||||
return "待部门负责人审批";
|
||||
case "2":
|
||||
return "待人事处审批";
|
||||
case "3":
|
||||
return "待纪委审批";
|
||||
case "4":
|
||||
return "待学院分管领导审批";
|
||||
case "5":
|
||||
return "待学院党委书记审批";
|
||||
default:
|
||||
return "待审批";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 总结
|
||||
|
||||
### 当前审批级别定义
|
||||
|
||||
**完整的五级审批体系**:
|
||||
|
||||
1. **Level 1 - 部门负责人审批**
|
||||
- 权限:`rs:overseas:approve1`
|
||||
- 范围:仅本部门申请
|
||||
- 显示:部门负责人审批
|
||||
|
||||
2. **Level 2 - 人事处审批**
|
||||
- 权限:`rs:overseas:approve2`
|
||||
- 范围:所有部门申请
|
||||
- 显示:人事处审批
|
||||
|
||||
3. **Level 3 - 纪委审批**
|
||||
- 权限:`rs:overseas:approve3`
|
||||
- 范围:所有部门申请
|
||||
- 显示:纪委审批
|
||||
|
||||
4. **Level 4 - 学院分管领导审批**
|
||||
- 权限:`rs:overseas:approve4`
|
||||
- 范围:所有部门申请
|
||||
- 显示:学院分管领导审批
|
||||
|
||||
5. **Level 5 - 学院党委书记审批**
|
||||
- 权限:`rs:overseas:approve5`
|
||||
- 范围:所有部门申请
|
||||
- 显示:学院党委书记审批
|
||||
|
||||
### 关键注意事项
|
||||
|
||||
- ✅ 系统使用**5级审批制度**
|
||||
- ✅ 只有**部门负责人(Level 1)**需要部门过滤
|
||||
- ✅ **Level 2-5** 可以看到所有部门的申请
|
||||
- ⚠️ 出国申请进度查询模块存在命名不一致问题
|
||||
- ⚠️ 总审批级别数应该是5而不是3
|
||||
|
||||
---
|
||||
|
||||
**分析完成时间**:2025-10-29
|
||||
**分析人员**:Cursor AI
|
||||
**文档版本**:v1.0
|
||||
|
||||
@@ -1,286 +0,0 @@
|
||||
# 出国申请进度查询 - 审批级别修复说明
|
||||
|
||||
## 🔧 修复时间
|
||||
2025-10-29
|
||||
|
||||
## 🎯 修复目标
|
||||
将出国申请进度查询模块从3级审批改为完整的5级审批,与实际审批流程保持一致。
|
||||
|
||||
---
|
||||
|
||||
## 📝 修复内容
|
||||
|
||||
### 修复1:更新审批级别文本定义
|
||||
|
||||
**文件**:`RsOverseasApprovalMonitorServiceImpl.java`
|
||||
**方法**:`getApprovalLevelText(String level)`
|
||||
|
||||
#### 修复前
|
||||
```java
|
||||
switch (level) {
|
||||
case "1":
|
||||
return "部门主管审批";
|
||||
case "2":
|
||||
return "分管领导审批";
|
||||
case "3":
|
||||
return "校领导审批";
|
||||
default:
|
||||
return "未知";
|
||||
}
|
||||
```
|
||||
|
||||
**问题**:
|
||||
- ❌ Level 4 和 Level 5 显示为"未知"
|
||||
- ❌ 命名与实际流程不符
|
||||
|
||||
#### 修复后
|
||||
```java
|
||||
/**
|
||||
* 获取审批级别文本
|
||||
* 五级审批制度:1-部门负责人,2-人事处,3-纪委,4-学院分管领导,5-学院党委书记
|
||||
*/
|
||||
switch (level) {
|
||||
case "1":
|
||||
return "部门负责人审批";
|
||||
case "2":
|
||||
return "人事处审批";
|
||||
case "3":
|
||||
return "纪委审批";
|
||||
case "4":
|
||||
return "学院分管领导审批";
|
||||
case "5":
|
||||
return "学院党委书记审批";
|
||||
default:
|
||||
return "未知";
|
||||
}
|
||||
```
|
||||
|
||||
**改进**:
|
||||
- ✅ 支持完整的5级审批
|
||||
- ✅ 命名与实际流程一致
|
||||
- ✅ 添加了详细的注释说明
|
||||
|
||||
---
|
||||
|
||||
### 修复2:更新总审批级别数
|
||||
|
||||
**文件**:`RsOverseasApprovalMonitorServiceImpl.java`
|
||||
**方法**:`fillApprovalProgress()`
|
||||
|
||||
#### 修复前
|
||||
```java
|
||||
// 总审批级别数(假设最多3级:部门主管、分管领导、校领导)
|
||||
vo.setTotalLevels(3);
|
||||
|
||||
// 未开始审批
|
||||
vo.setApprovedLevels(0);
|
||||
vo.setTotalLevels(3);
|
||||
vo.setApprovalProgress("0/3");
|
||||
```
|
||||
|
||||
**问题**:
|
||||
- ❌ 总级别数设置为3
|
||||
- ❌ 审批进度显示错误(如:2/3 而不是 2/5)
|
||||
- ❌ 进度百分比计算错误
|
||||
|
||||
#### 修复后
|
||||
```java
|
||||
// 总审批级别数(五级审批制度:部门负责人、人事处、纪委、学院分管领导、学院党委书记)
|
||||
vo.setTotalLevels(5);
|
||||
|
||||
// 未开始审批
|
||||
vo.setApprovedLevels(0);
|
||||
vo.setTotalLevels(5);
|
||||
vo.setApprovalProgress("0/5");
|
||||
```
|
||||
|
||||
**改进**:
|
||||
- ✅ 总级别数正确设置为5
|
||||
- ✅ 审批进度显示正确
|
||||
- ✅ 进度百分比计算准确
|
||||
|
||||
---
|
||||
|
||||
### 修复3:更新统计信息定义
|
||||
|
||||
**文件**:`RsOverseasApprovalMonitorServiceImpl.java`
|
||||
**方法**:`getApprovalStatistics()`
|
||||
|
||||
#### 修复前
|
||||
```java
|
||||
// 2. 各审批级别的待审批数量
|
||||
Map<String, Long> pendingByLevel = new HashMap<>();
|
||||
pendingByLevel.put("level1", countByStatusAndLevel("0", "1", tenantId)); // 部门主管
|
||||
pendingByLevel.put("level2", countByStatusAndLevel("0", "2", tenantId)); // 分管领导
|
||||
pendingByLevel.put("level3", countByStatusAndLevel("0", "3", tenantId)); // 校领导
|
||||
statistics.put("pendingByLevel", pendingByLevel);
|
||||
```
|
||||
|
||||
**问题**:
|
||||
- ❌ 只统计3个级别
|
||||
- ❌ Level 4 和 Level 5 的统计缺失
|
||||
|
||||
#### 修复后
|
||||
```java
|
||||
// 2. 各审批级别的待审批数量(五级审批制度)
|
||||
Map<String, Long> 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("level4", countByStatusAndLevel("0", "4", tenantId)); // 学院分管领导
|
||||
pendingByLevel.put("level5", countByStatusAndLevel("0", "5", tenantId)); // 学院党委书记
|
||||
statistics.put("pendingByLevel", pendingByLevel);
|
||||
```
|
||||
|
||||
**改进**:
|
||||
- ✅ 统计所有5个级别
|
||||
- ✅ 统计数据完整准确
|
||||
- ✅ 命名规范一致
|
||||
|
||||
---
|
||||
|
||||
## 📊 修复效果对比
|
||||
|
||||
### 1. 当前审批环节显示
|
||||
|
||||
| 审批级别 | 修复前 | 修复后 | 状态 |
|
||||
|---------|--------|--------|------|
|
||||
| Level 1 | 部门主管审批 | 部门负责人审批 | ✅ 已修正 |
|
||||
| Level 2 | 分管领导审批 | 人事处审批 | ✅ 已修正 |
|
||||
| Level 3 | 校领导审批 | 纪委审批 | ✅ 已修正 |
|
||||
| Level 4 | **未知** ❌ | 学院分管领导审批 | ✅ 已修复 |
|
||||
| Level 5 | **未知** ❌ | 学院党委书记审批 | ✅ 已修复 |
|
||||
|
||||
### 2. 审批进度显示
|
||||
|
||||
| 已审批级别 | 修复前 | 修复后 | 进度百分比(修复前) | 进度百分比(修复后) |
|
||||
|-----------|--------|--------|-------------------|-------------------|
|
||||
| 0级 | 0/3 | 0/5 | 0% | 0% |
|
||||
| 1级 | 1/3 | 1/5 | 33% ❌ | 20% ✅ |
|
||||
| 2级 | 2/3 | 2/5 | 67% ❌ | 40% ✅ |
|
||||
| 3级 | 3/3 | 3/5 | 100% ❌ | 60% ✅ |
|
||||
| 4级 | - | 4/5 | - | 80% ✅ |
|
||||
| 5级 | - | 5/5 | - | 100% ✅ |
|
||||
|
||||
### 3. 统计信息
|
||||
|
||||
| 统计项 | 修复前 | 修复后 |
|
||||
|--------|--------|--------|
|
||||
| 统计级别数 | 3个 | 5个 ✅ |
|
||||
| Level 4统计 | ❌ 缺失 | ✅ 包含 |
|
||||
| Level 5统计 | ❌ 缺失 | ✅ 包含 |
|
||||
|
||||
---
|
||||
|
||||
## 🎨 前端显示效果
|
||||
|
||||
### 待审批列表示例
|
||||
|
||||
#### 修复前
|
||||
| 申请人 | 当前审批环节 | 审批进度 |
|
||||
|--------|-------------|---------|
|
||||
| 张三 | 部门主管审批 | 0/3 (0%) |
|
||||
| 李四 | 分管领导审批 | 1/3 (33%) |
|
||||
| 王五 | 校领导审批 | 2/3 (67%) |
|
||||
| 赵六 | **未知** ❌ | 3/3 (100%) ❌ |
|
||||
| 钱七 | **未知** ❌ | - |
|
||||
|
||||
#### 修复后
|
||||
| 申请人 | 当前审批环节 | 审批进度 |
|
||||
|--------|-------------|---------|
|
||||
| 张三 | 部门负责人审批 | 0/5 (0%) |
|
||||
| 李四 | 人事处审批 | 1/5 (20%) |
|
||||
| 王五 | 纪委审批 | 2/5 (40%) |
|
||||
| 赵六 | 学院分管领导审批 ✅ | 3/5 (60%) ✅ |
|
||||
| 钱七 | 学院党委书记审批 ✅ | 4/5 (80%) ✅ |
|
||||
|
||||
---
|
||||
|
||||
## ✅ 修复验证
|
||||
|
||||
### 验证步骤
|
||||
|
||||
1. **重新编译后端**
|
||||
```bash
|
||||
cd backend
|
||||
mvn clean package -pl ruoyi-admin -am -DskipTests
|
||||
```
|
||||
|
||||
2. **重启后端服务**
|
||||
```bash
|
||||
./stop-main.sh
|
||||
./start-main.sh
|
||||
```
|
||||
|
||||
3. **登录系统测试**
|
||||
- 访问"出国申请进度查询"菜单
|
||||
- 查看待审批中和审批完成选项卡
|
||||
- 验证以下内容:
|
||||
|
||||
#### 验证项1:当前审批环节显示
|
||||
- [ ] Level 1 显示为"部门负责人审批"
|
||||
- [ ] Level 2 显示为"人事处审批"
|
||||
- [ ] Level 3 显示为"纪委审批"
|
||||
- [ ] Level 4 显示为"学院分管领导审批"(不再是"未知")
|
||||
- [ ] Level 5 显示为"学院党委书记审批"(不再是"未知")
|
||||
|
||||
#### 验证项2:审批进度显示
|
||||
- [ ] 未审批显示:0/5 (0%)
|
||||
- [ ] 部门负责人已审批:1/5 (20%)
|
||||
- [ ] 人事处已审批:2/5 (40%)
|
||||
- [ ] 纪委已审批:3/5 (60%)
|
||||
- [ ] 学院分管领导已审批:4/5 (80%)
|
||||
- [ ] 学院党委书记已审批:5/5 (100%)
|
||||
|
||||
#### 验证项3:统计信息
|
||||
- [ ] 统计信息包含所有5个级别
|
||||
- [ ] Level 4 和 Level 5 的统计数据正常显示
|
||||
|
||||
---
|
||||
|
||||
## 📋 修改文件清单
|
||||
|
||||
| 序号 | 文件 | 修改内容 | 行数 | 状态 |
|
||||
|------|------|---------|------|------|
|
||||
| 1 | `RsOverseasApprovalMonitorServiceImpl.java` | 更新审批级别文本定义 | 387-404 | ✅ |
|
||||
| 2 | `RsOverseasApprovalMonitorServiceImpl.java` | 更新总审批级别数(已审批) | 345-346 | ✅ |
|
||||
| 3 | `RsOverseasApprovalMonitorServiceImpl.java` | 更新总审批级别数(未审批) | 358-360 | ✅ |
|
||||
| 4 | `RsOverseasApprovalMonitorServiceImpl.java` | 更新统计信息定义 | 152-157 | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 修复总结
|
||||
|
||||
### 修复前的问题
|
||||
- ❌ 只支持3级审批,Level 4和5显示"未知"
|
||||
- ❌ 总审批级别数错误(3而不是5)
|
||||
- ❌ 审批进度百分比计算错误
|
||||
- ❌ 统计信息不完整
|
||||
|
||||
### 修复后的改进
|
||||
- ✅ 完整支持5级审批流程
|
||||
- ✅ 所有审批级别正确显示
|
||||
- ✅ 审批进度计算准确
|
||||
- ✅ 统计信息完整
|
||||
- ✅ 与实际业务流程一致
|
||||
|
||||
### 影响范围
|
||||
- ✅ 仅修改出国申请进度查询模块
|
||||
- ✅ 不影响其他模块
|
||||
- ✅ 向后兼容
|
||||
|
||||
---
|
||||
|
||||
## 📞 相关文档
|
||||
|
||||
- 详细分析报告:`审批环节命名标准分析报告.md`
|
||||
- 功能修改说明:`出国申请进度查询功能修改说明.md`
|
||||
- 权限修改说明:`权限修改说明.md`
|
||||
|
||||
---
|
||||
|
||||
**修复状态**:✅ 已完成
|
||||
**需要操作**:重新编译和重启后端服务
|
||||
**版本**:v1.2
|
||||
**最后更新**:2025-10-29
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
# 出国申请进度查询权限修改说明
|
||||
|
||||
## 📋 修改时间
|
||||
2025-10-29
|
||||
|
||||
## 🎯 修改目的
|
||||
将"出国申请进度查询"从"只能查看自己提交的申请"改为"拥有权限的用户可以查看所有申请"
|
||||
|
||||
---
|
||||
|
||||
## 📝 修改内容
|
||||
|
||||
### 1. 后端Service层修改
|
||||
|
||||
**文件**:`RsOverseasApprovalMonitorServiceImpl.java`
|
||||
|
||||
#### 修改前
|
||||
```java
|
||||
// 只查询当前用户提交的申请
|
||||
Long currentUserId = LoginHelper.getUserId();
|
||||
wrapper.eq("applicant_id", currentUserId);
|
||||
```
|
||||
|
||||
#### 修改后
|
||||
```java
|
||||
// 不再限制申请人,拥有权限的用户可以看到所有数据
|
||||
// 已删除 applicant_id 过滤条件
|
||||
```
|
||||
|
||||
**影响方法**:
|
||||
- ✅ `queryPendingList()` - 待审批列表
|
||||
- ✅ `queryCompletedList()` - 已完成列表
|
||||
|
||||
---
|
||||
|
||||
### 2. 后端Controller层修改
|
||||
|
||||
**文件**:`RsOverseasApprovalMonitorController.java`
|
||||
|
||||
#### 修改点
|
||||
- 更新注释说明:从"当前用户提交的"改为"所有"
|
||||
- 更新日志输出:从"当前用户的"改为"所有"
|
||||
|
||||
---
|
||||
|
||||
### 3. 前端页面修改
|
||||
|
||||
**文件**:`monitor/index.vue`
|
||||
|
||||
#### 修改点
|
||||
- 提示文字:从"这里显示您提交的"改为"这里显示所有"
|
||||
|
||||
---
|
||||
|
||||
## 🔍 权限控制
|
||||
|
||||
### 访问控制
|
||||
- **权限标识**:`rs:overseas:list`
|
||||
- **控制级别**:拥有此权限的用户可以查看所有数据
|
||||
- **数据范围**:不再限制申请人,显示所有待审批和已完成的申请
|
||||
|
||||
### 查询逻辑
|
||||
```sql
|
||||
-- 待审批列表
|
||||
SELECT * FROM rs_overseas_application
|
||||
WHERE status = '0' -- 待审核状态
|
||||
AND deleted = '0'
|
||||
AND tenant_id = {租户ID}
|
||||
ORDER BY apply_time DESC
|
||||
|
||||
-- 已完成列表
|
||||
SELECT * FROM rs_overseas_application
|
||||
WHERE status = '1' -- 已通过状态
|
||||
AND deleted = '0'
|
||||
AND tenant_id = {租户ID}
|
||||
ORDER BY apply_time DESC
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 修改对比
|
||||
|
||||
| 项目 | 修改前 | 修改后 |
|
||||
|------|--------|--------|
|
||||
| 数据范围 | 当前用户提交的申请 | 所有申请 |
|
||||
| 权限要求 | rs:overseas:list | rs:overseas:list(不变) |
|
||||
| 查询条件 | applicant_id = 当前用户ID | 无申请人限制 |
|
||||
| 用户体验 | 只能看到自己的 | 可以看到所有 |
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试验证
|
||||
|
||||
### 测试场景1:查看待审批列表
|
||||
|
||||
**测试步骤**:
|
||||
1. 用户A登录系统(拥有 rs:overseas:list 权限)
|
||||
2. 访问"出国申请进度查询"菜单
|
||||
3. 切换到"待审批中"选项卡
|
||||
|
||||
**预期结果**:
|
||||
- ✅ 能看到所有用户提交的待审批申请
|
||||
- ✅ 包括用户A自己提交的和其他用户提交的
|
||||
|
||||
### 测试场景2:查看已完成列表
|
||||
|
||||
**测试步骤**:
|
||||
1. 用户A登录系统(拥有 rs:overseas:list 权限)
|
||||
2. 访问"出国申请进度查询"菜单
|
||||
3. 切换到"审批完成"选项卡
|
||||
|
||||
**预期结果**:
|
||||
- ✅ 能看到所有已完成审批的申请
|
||||
- ✅ 不限制申请人
|
||||
|
||||
### 测试场景3:无权限用户
|
||||
|
||||
**测试步骤**:
|
||||
1. 用户B登录系统(没有 rs:overseas:list 权限)
|
||||
2. 尝试访问"出国申请进度查询"菜单
|
||||
|
||||
**预期结果**:
|
||||
- ❌ 无法访问该菜单(权限控制在菜单级别)
|
||||
|
||||
---
|
||||
|
||||
## 📊 修改文件清单
|
||||
|
||||
| 序号 | 文件类型 | 文件路径 | 修改内容 | 状态 |
|
||||
|------|---------|---------|---------|------|
|
||||
| 1 | 后端Service | `RsOverseasApprovalMonitorServiceImpl.java` | 删除申请人ID过滤 | ✅ |
|
||||
| 2 | 后端Controller | `RsOverseasApprovalMonitorController.java` | 更新注释和日志 | ✅ |
|
||||
| 3 | 前端页面 | `monitor/index.vue` | 更新提示文字 | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 1. 权限配置
|
||||
- 确保只有需要查看所有数据的用户才有 `rs:overseas:list` 权限
|
||||
- 如果需要区分不同权限级别,建议创建新的权限标识
|
||||
|
||||
### 2. 数据安全
|
||||
- 所有查询仍然受租户ID限制
|
||||
- 不会跨租户显示数据
|
||||
|
||||
### 3. 性能考虑
|
||||
- 查询所有数据时,建议使用搜索条件过滤
|
||||
- 分页显示可以减轻服务器压力
|
||||
|
||||
---
|
||||
|
||||
## 🚀 部署说明
|
||||
|
||||
### 后端部署
|
||||
```bash
|
||||
cd backend
|
||||
mvn clean package -pl ruoyi-admin -am -DskipTests
|
||||
./stop-main.sh
|
||||
./start-main.sh
|
||||
```
|
||||
|
||||
### 前端(如已运行无需重启)
|
||||
```bash
|
||||
# 前端代码修改后会自动热更新
|
||||
# 如需打包部署:
|
||||
cd web
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📞 相关文档
|
||||
|
||||
- 详细修改说明:`出国申请进度查询功能修改说明.md`
|
||||
- 快速参考:`修改总结-快速参考.md`
|
||||
|
||||
---
|
||||
|
||||
**修改状态**:✅ 已完成
|
||||
**生效方式**:需要重启后端服务
|
||||
**版本**:v1.1
|
||||
|
||||
Reference in new issue
Block a user