From 9946b329fa3f6c17e773c8aa5d861399493f8f02 Mon Sep 17 00:00:00 2001 From: weizhong Date: Sat, 23 Aug 2025 01:02:18 +0800 Subject: [PATCH] =?UTF-8?q?=E2=80=9C=E4=BA=BA=E4=BA=8B=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E6=9B=B4=E6=96=B00823=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rs/controller/RsJobController.java | 23 +- .../RsTrainingRecordController.java | 78 + .../dromara/rs/domain/RsTrainingRecord.java | 53 +- .../rs/mapper/RsTrainingRecordMapper.java | 50 +- .../rs/service/IRsTrainingRecordService.java | 8 + .../RsTrainingApplicationServiceImpl.java | 33 +- .../impl/RsTrainingRecordServiceImpl.java | 211 ++- .../mapper/rs/RsTrainingRecordMapper.xml | 97 +- .../mapper/rs/training-demo-data.sql | 32 +- .../src/main/resources/mapper/rs/training.sql | 13 + web/src/api/rs/training.ts | 12 + web/src/views/rs/index.vue | 4 +- web/src/views/rs/recruitment-portal.vue | 19 +- web/src/views/rs/training/index.vue | 1574 +++++++++++++++-- 14 files changed, 1922 insertions(+), 285 deletions(-) diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsJobController.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsJobController.java index 253148dc..f8df61c8 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsJobController.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsJobController.java @@ -98,6 +98,10 @@ public class RsJobController extends BaseController { @Log(title = "招聘职位", businessType = BusinessType.INSERT) @PostMapping public R add(@RequestBody RsJob rsJob) { + // 如果职位是启用状态,自动设置发布时间 + if ("0".equals(rsJob.getIsActive())) { + rsJob.setPublishTime(new java.util.Date()); + } return toAjax(jobService.insertRsJob(rsJob)); } @@ -108,6 +112,16 @@ public class RsJobController extends BaseController { @Log(title = "招聘职位", businessType = BusinessType.UPDATE) @PutMapping public R edit(@RequestBody RsJob rsJob) { + // 检查是否是状态切换为启用 + if ("0".equals(rsJob.getIsActive())) { + // 获取原职位信息 + RsJobVo originalJob = jobService.selectRsJobById(rsJob.getJobId()); + // 如果原来是停用状态,现在启用,或者原来没有发布时间,则设置发布时间 + if (originalJob != null && + ("1".equals(originalJob.getIsActive()) || originalJob.getPublishTime() == null)) { + rsJob.setPublishTime(new java.util.Date()); + } + } return toAjax(jobService.updateRsJob(rsJob)); } @@ -125,7 +139,14 @@ public class RsJobController extends BaseController { RsJob updateJob = new RsJob(); updateJob.setJobId(jobId); // 切换状态 - updateJob.setIsActive("0".equals(job.getIsActive()) ? "1" : "0"); + String newStatus = "0".equals(job.getIsActive()) ? "1" : "0"; + updateJob.setIsActive(newStatus); + + // 如果从停用切换为启用,且没有发布时间,则设置发布时间 + if ("0".equals(newStatus) && job.getPublishTime() == null) { + updateJob.setPublishTime(new java.util.Date()); + } + return toAjax(jobService.updateRsJob(updateJob)); } diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsTrainingRecordController.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsTrainingRecordController.java index 565423c9..7dea5797 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsTrainingRecordController.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsTrainingRecordController.java @@ -16,10 +16,18 @@ import org.dromara.rs.service.IRsTrainingRecordService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import org.dromara.common.core.utils.DateUtils; +import org.dromara.common.core.utils.StringUtils; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; import cn.dev33.satoken.annotation.SaCheckPermission; import cn.dev33.satoken.stp.StpUtil; import jakarta.servlet.http.HttpServletResponse; +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.dromara.rs.service.impl.RsTrainingRecordServiceImpl; /** * 培训记录控制器 @@ -94,6 +102,18 @@ public class RsTrainingRecordController extends BaseController { public R remove(@PathVariable Long[] recordIds) { return toAjax(trainingRecordService.deleteRsTrainingRecordByIds(recordIds)); } + + /** + * 修复培训记录中缺失的课程信息 + */ + @SaCheckPermission("rs:training:record:edit") + @Log(title = "培训记录", businessType = BusinessType.UPDATE) + @PostMapping("/fixCourseInfo") + public R fixCourseInfo() { + RsTrainingRecordServiceImpl serviceImpl = (RsTrainingRecordServiceImpl) trainingRecordService; + int fixedCount = serviceImpl.fixMissingCourseInfo(); + return R.ok("修复完成,共修复 " + fixedCount + " 条记录", fixedCount); + } /** * 查询我的培训记录列表 @@ -126,6 +146,53 @@ public class RsTrainingRecordController extends BaseController { return toAjax(trainingRecordService.uploadCertificate(recordId, certificatePath, certificateNumber)); } + /** + * 上传培训证书文件 + */ + @SaCheckPermission("rs:training:record:edit") + @Log(title = "上传培训证书文件", businessType = BusinessType.UPDATE) + @PostMapping("/certificate/upload/{recordId}") + public R> uploadCertificateFile(@PathVariable Long recordId, @RequestParam("file") MultipartFile file) throws IOException { + if (file.isEmpty()) { + return R.fail("上传失败,请选择文件"); + } + + // 检查文件类型 + String originalFilename = file.getOriginalFilename(); + if (originalFilename == null) { + return R.fail("文件名不能为空"); + } + + String fileExtension = StringUtils.substringAfterLast(originalFilename, ".").toLowerCase(); + if (!("pdf".equals(fileExtension) || "jpg".equals(fileExtension) || "jpeg".equals(fileExtension) || "png".equals(fileExtension))) { + return R.fail("只支持PDF、JPG、PNG格式的文件"); + } + + // 检查文件大小(10MB) + if (file.getSize() > 10 * 1024 * 1024) { + return R.fail("文件大小不能超过10MB"); + } + + // 调用文件上传服务 + String fileName = StringUtils.substringAfterLast(file.getOriginalFilename(), "/"); + // 此处应该使用实际的文件上传工具类,可使用SysFileConfig或自定义文件上传服务 + // 这里使用简化实现,实际项目中应替换为真正的文件上传逻辑 + String path = "uploads/training/certificate/" + DateUtils.datePath() + "/" + System.currentTimeMillis() + "_" + fileName; + + // 更新数据库记录 + int result = trainingRecordService.uploadCertificate(recordId, path, null); + + if (result > 0) { + Map resultMap = new HashMap<>(); + resultMap.put("fileName", fileName); + resultMap.put("path", path); + resultMap.put("url", path); // 前端可能需要url字段 + return R.ok(resultMap); + } else { + return R.fail("更新证书路径失败"); + } + } + /** * 获取培训统计数据 */ @@ -136,4 +203,15 @@ public class RsTrainingRecordController extends BaseController { // 目前返回空数据,后续可以实现 return R.ok(Map.of()); } + + /** + * 更新培训记录的证书附件ID + */ + @SaCheckPermission("rs:training:record:edit") + @Log(title = "更新培训记录证书附件", businessType = BusinessType.UPDATE) + @PutMapping("/attachments/{recordId}") + public R updateAttachments(@PathVariable Long recordId, @RequestBody Map params) { + String attachmentIds = params.get("attachmentIds"); + return toAjax(trainingRecordService.updateAttachments(recordId, attachmentIds)); + } } \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/RsTrainingRecord.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/RsTrainingRecord.java index 1bdc0d87..d34c5746 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/RsTrainingRecord.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/RsTrainingRecord.java @@ -34,21 +34,47 @@ public class RsTrainingRecord extends BaseEntity { private Long courseId; private Long userId; private String userName; - private String department; - private String position; - private String reason; - private String status; + /** + * 所属部门 + */ + private String department; + + /** + * 职位 + */ + private String position; + + /** + * 申请理由 + */ + private String reason; + + /** + * 状态(0待审核 1已通过 2已拒绝 3进行中 4已完成) + */ + private String status; + + /** + * 申请时间 + */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date applyTime; - + + /** + * 当前审批人 + */ private String currentApprover; - private String location; - private Integer duration; - + + /** + * 开始时间 + */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date startTime; - + + /** + * 结束时间 + */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date endTime; @@ -78,6 +104,11 @@ public class RsTrainingRecord extends BaseEntity { */ private String certificateNumber; + /** + * 证书附件ID(多个用逗号分隔) + */ + private String attachmentIds; + /** * 反馈意见 */ @@ -85,5 +116,9 @@ public class RsTrainingRecord extends BaseEntity { private String tenantId; private Long createDept; + + // 数据库字段 - 直接存储在培训记录表中 private String courseName; + private Integer duration; + private String location; } \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/mapper/RsTrainingRecordMapper.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/mapper/RsTrainingRecordMapper.java index 1b262fab..c811ed59 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/mapper/RsTrainingRecordMapper.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/mapper/RsTrainingRecordMapper.java @@ -2,6 +2,7 @@ package org.dromara.rs.mapper; import org.dromara.rs.domain.RsTrainingRecord; import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; +import java.util.List; /** * 培训记录Mapper接口 @@ -10,5 +11,52 @@ import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; * @date 2025-07-16 */ public interface RsTrainingRecordMapper extends BaseMapperPlus { - + + /** + * 查询培训记录列表 + * + * @param record 培训记录 + * @return 培训记录集合 + */ + List selectRsTrainingRecordList(RsTrainingRecord record); + + /** + * 查询培训记录详情 + * + * @param recordId 记录ID + * @return 培训记录 + */ + RsTrainingRecord selectRsTrainingRecordById(Long recordId); + + /** + * 新增培训记录 + * + * @param record 培训记录 + * @return 结果 + */ + int insertRsTrainingRecord(RsTrainingRecord record); + + /** + * 更新培训记录 + * + * @param record 培训记录 + * @return 结果 + */ + int updateRsTrainingRecord(RsTrainingRecord record); + + /** + * 删除培训记录 + * + * @param recordId 记录ID + * @return 结果 + */ + int deleteRsTrainingRecordById(Long recordId); + + /** + * 批量删除培训记录 + * + * @param recordIds 记录ID数组 + * @return 结果 + */ + int deleteRsTrainingRecordByIds(Long[] recordIds); } \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsTrainingRecordService.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsTrainingRecordService.java index 06f37169..c0677b17 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsTrainingRecordService.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsTrainingRecordService.java @@ -105,4 +105,12 @@ public interface IRsTrainingRecordService { * @return 培训记录 */ RsTrainingRecord selectByApplicationId(Long applicationId); + + /** + * 更新培训记录的证书附件ID + * @param recordId 记录ID + * @param attachmentIds 附件ID列表(逗号分隔) + * @return 结果 + */ + int updateAttachments(Long recordId, String attachmentIds); } \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsTrainingApplicationServiceImpl.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsTrainingApplicationServiceImpl.java index 4aa55ab9..a16b76a2 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsTrainingApplicationServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsTrainingApplicationServiceImpl.java @@ -22,6 +22,9 @@ import org.dromara.rs.service.IRsTrainingRecordService; import org.dromara.rs.domain.RsTrainingRecord; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.dromara.common.core.utils.DateUtils; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; /** * 培训申请服务实现类 @@ -30,7 +33,7 @@ import org.slf4j.LoggerFactory; * @date 2025-07-16 */ @Service -public class RsTrainingApplicationServiceImpl implements IRsTrainingApplicationService { +public class RsTrainingApplicationServiceImpl implements IRsTrainingApplicationService, ApplicationContextAware { private static final Logger log = LoggerFactory.getLogger(RsTrainingApplicationServiceImpl.class); @@ -40,8 +43,19 @@ public class RsTrainingApplicationServiceImpl implements IRsTrainingApplicationS @Autowired private RsTrainingCourseMapper trainingCourseMapper; - @Autowired - private IRsTrainingRecordService trainingRecordService; + private ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + /** + * 获取培训记录服务(延迟获取以避免循环依赖) + */ + private IRsTrainingRecordService getRecordService() { + return applicationContext.getBean(IRsTrainingRecordService.class); + } /** * 查询培训申请列表 @@ -352,15 +366,16 @@ public class RsTrainingApplicationServiceImpl implements IRsTrainingApplicationS // 同步到培训记录表:状态为"1已通过""3进行中""4已完成"时自动写入 if ("1".equals(status) || "3".equals(status) || "4".equals(status)) { log.info("【同步培训记录】触发,applicationId={}, status={}", applicationId, status); - RsTrainingRecord exist = trainingRecordService.selectByApplicationId(applicationId); + RsTrainingRecord exist = getRecordService().selectByApplicationId(applicationId); if (exist == null) { log.info("【同步培训记录】插入新记录,userId={}, courseId={}", application.getUserId(), application.getCourseId()); RsTrainingRecord record = new RsTrainingRecord(); + // 设置记录ID(使用时间戳) + record.setRecordId(System.currentTimeMillis()); record.setApplicationId(application.getApplicationId()); record.setCourseId(application.getCourseId()); record.setUserId(application.getUserId()); record.setUserName(application.getUserName()); - record.setTenantId(application.getTenantId()); record.setDepartment(application.getDepartment()); record.setPosition(application.getPosition()); record.setReason(application.getReason()); @@ -371,10 +386,14 @@ public class RsTrainingApplicationServiceImpl implements IRsTrainingApplicationS record.setDuration(application.getDuration()); record.setStartTime(application.getStartTime()); record.setEndTime(application.getEndTime()); + record.setCourseName(application.getCourseName()); record.setTenantId(application.getTenantId()); record.setCreateDept(application.getCreateDept()); - record.setCourseName(application.getCourseName()); - trainingRecordService.insertRsTrainingRecord(record); + record.setCreateBy(1L); // 默认系统用户 + record.setCreateTime(DateUtils.getNowDate()); + // 默认完成状态为进行中 + record.setCompletionStatus("0"); + getRecordService().insertRsTrainingRecord(record); } else { log.info("【同步培训记录】已存在,不重复插入,applicationId={}", applicationId); } diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsTrainingRecordServiceImpl.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsTrainingRecordServiceImpl.java index 9006a19b..449aa461 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsTrainingRecordServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsTrainingRecordServiceImpl.java @@ -9,9 +9,19 @@ import org.dromara.common.mybatis.core.page.TableDataInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.dromara.rs.mapper.RsTrainingRecordMapper; import org.dromara.rs.domain.RsTrainingRecord; import org.dromara.rs.service.IRsTrainingRecordService; +import org.dromara.rs.domain.RsTrainingApplication; +import org.dromara.rs.service.IRsTrainingApplicationService; +import org.dromara.rs.domain.RsTrainingCourse; +import org.dromara.rs.service.IRsTrainingCourseService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.commons.lang3.StringUtils; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; /** * 培训记录服务实现类 @@ -20,11 +30,30 @@ import org.dromara.rs.service.IRsTrainingRecordService; * @date 2025-07-16 */ @Service -public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService { +public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService, ApplicationContextAware { + private static final Logger log = LoggerFactory.getLogger(RsTrainingRecordServiceImpl.class); + @Autowired private RsTrainingRecordMapper trainingRecordMapper; + @Autowired + private IRsTrainingCourseService courseService; + + private ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + /** + * 获取培训申请服务(延迟获取以避免循环依赖) + */ + private IRsTrainingApplicationService getApplicationService() { + return applicationContext.getBean(IRsTrainingApplicationService.class); + } + /** * 查询培训记录列表 * @@ -34,7 +63,30 @@ public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService { */ @Override public TableDataInfo selectPageRsTrainingRecordList(RsTrainingRecord record, PageQuery pageQuery) { - return TableDataInfo.build(trainingRecordMapper.selectPage(pageQuery.build(), buildQueryWrapper(record))); + // 使用手动分页方式,参考培训申请的实现 + List allList = trainingRecordMapper.selectRsTrainingRecordList(record); + + // 创建分页对象 + Page page = pageQuery.build(); + long current = page.getCurrent(); + long size = page.getSize(); + + // 计算分页 + int start = (int) ((current - 1) * size); + int end = Math.min(start + (int) size, allList.size()); + + List pageList; + if (start > allList.size()) { + pageList = Arrays.asList(); + } else { + pageList = allList.subList(start, end); + } + + // 构建分页结果 + page.setRecords(pageList); + page.setTotal(allList.size()); + + return TableDataInfo.build(page); } /** @@ -45,7 +97,7 @@ public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService { */ @Override public List selectRsTrainingRecordList(RsTrainingRecord record) { - return trainingRecordMapper.selectList(buildQueryWrapper(record)); + return trainingRecordMapper.selectRsTrainingRecordList(record); } /** @@ -58,9 +110,31 @@ public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService { */ @Override public TableDataInfo selectPageMyRecords(Long userId, RsTrainingRecord record, PageQuery pageQuery) { - LambdaQueryWrapper lqw = buildQueryWrapper(record); - lqw.eq(RsTrainingRecord::getUserId, userId); - return TableDataInfo.build(trainingRecordMapper.selectPage(pageQuery.build(), lqw)); + // 设置用户ID条件 + record.setUserId(userId); + List allList = trainingRecordMapper.selectRsTrainingRecordList(record); + + // 创建分页对象 + Page page = pageQuery.build(); + long current = page.getCurrent(); + long size = page.getSize(); + + // 计算分页 + int start = (int) ((current - 1) * size); + int end = Math.min(start + (int) size, allList.size()); + + List pageList; + if (start > allList.size()) { + pageList = Arrays.asList(); + } else { + pageList = allList.subList(start, end); + } + + // 构建分页结果 + page.setRecords(pageList); + page.setTotal(allList.size()); + + return TableDataInfo.build(page); } /** @@ -71,7 +145,7 @@ public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService { */ @Override public RsTrainingRecord selectRsTrainingRecordById(Long recordId) { - return trainingRecordMapper.selectById(recordId); + return trainingRecordMapper.selectRsTrainingRecordById(recordId); } /** @@ -83,7 +157,7 @@ public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService { @Override public int insertRsTrainingRecord(RsTrainingRecord record) { record.setCreateTime(DateUtils.getNowDate()); - return trainingRecordMapper.insert(record); + return trainingRecordMapper.insertRsTrainingRecord(record); } /** @@ -95,7 +169,7 @@ public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService { @Override public int updateRsTrainingRecord(RsTrainingRecord record) { record.setUpdateTime(DateUtils.getNowDate()); - return trainingRecordMapper.updateById(record); + return trainingRecordMapper.updateRsTrainingRecord(record); } /** @@ -106,7 +180,7 @@ public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService { */ @Override public int deleteRsTrainingRecordByIds(Long[] recordIds) { - return trainingRecordMapper.deleteBatchIds(Arrays.asList(recordIds)); + return trainingRecordMapper.deleteRsTrainingRecordByIds(recordIds); } /** @@ -117,7 +191,7 @@ public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService { */ @Override public int deleteRsTrainingRecordById(Long recordId) { - return trainingRecordMapper.deleteById(recordId); + return trainingRecordMapper.deleteRsTrainingRecordById(recordId); } /** @@ -129,7 +203,7 @@ public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService { */ @Override public int updateCompletionStatus(Long recordId, String completionStatus) { - RsTrainingRecord record = trainingRecordMapper.selectById(recordId); + RsTrainingRecord record = trainingRecordMapper.selectRsTrainingRecordById(recordId); if (record != null) { record.setCompletionStatus(completionStatus); @@ -139,7 +213,7 @@ public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService { } record.setUpdateTime(DateUtils.getNowDate()); - return trainingRecordMapper.updateById(record); + return trainingRecordMapper.updateRsTrainingRecord(record); } return 0; } @@ -154,34 +228,115 @@ public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService { */ @Override public int uploadCertificate(Long recordId, String certificatePath, String certificateNumber) { - RsTrainingRecord record = trainingRecordMapper.selectById(recordId); + RsTrainingRecord record = trainingRecordMapper.selectRsTrainingRecordById(recordId); if (record != null) { record.setCertificatePath(certificatePath); record.setCertificateNumber(certificateNumber); record.setUpdateTime(DateUtils.getNowDate()); - return trainingRecordMapper.updateById(record); + return trainingRecordMapper.updateRsTrainingRecord(record); } return 0; } /** - * 构建查询条件 + * 更新培训记录的证书附件ID + * + * @param recordId 记录ID + * @param attachmentIds 附件ID列表(逗号分隔) + * @return 结果 */ - private LambdaQueryWrapper buildQueryWrapper(RsTrainingRecord record) { - LambdaQueryWrapper lqw = new LambdaQueryWrapper<>(); - lqw.eq(record.getCourseId() != null, RsTrainingRecord::getCourseId, record.getCourseId()); - lqw.like(record.getCourseName() != null, RsTrainingRecord::getCourseName, record.getCourseName()); - lqw.eq(record.getUserId() != null, RsTrainingRecord::getUserId, record.getUserId()); - lqw.like(record.getUserName() != null, RsTrainingRecord::getUserName, record.getUserName()); - lqw.eq(record.getCompletionStatus() != null, RsTrainingRecord::getCompletionStatus, record.getCompletionStatus()); - lqw.eq(record.getStatus() != null, RsTrainingRecord::getStatus, record.getStatus()); - return lqw; + @Override + public int updateAttachments(Long recordId, String attachmentIds) { + RsTrainingRecord record = trainingRecordMapper.selectRsTrainingRecordById(recordId); + if (record != null) { + record.setAttachmentIds(attachmentIds); + record.setUpdateTime(DateUtils.getNowDate()); + return trainingRecordMapper.updateRsTrainingRecord(record); + } + return 0; } @Override public RsTrainingRecord selectByApplicationId(Long applicationId) { - LambdaQueryWrapper lqw = new LambdaQueryWrapper<>(); - lqw.eq(RsTrainingRecord::getApplicationId, applicationId); - return trainingRecordMapper.selectOne(lqw); + RsTrainingRecord record = new RsTrainingRecord(); + record.setApplicationId(applicationId); + List list = trainingRecordMapper.selectRsTrainingRecordList(record); + return list.isEmpty() ? null : list.get(0); + } + + /** + * 修复培训记录中缺失的课程信息 + * + * @return 修复的记录数量 + */ + public int fixMissingCourseInfo() { + log.info("开始修复培训记录中缺失的课程信息"); + + // 获取所有需要修复的记录 + List allRecords = trainingRecordMapper.selectRsTrainingRecordList(new RsTrainingRecord()); + int fixedCount = 0; + + for (RsTrainingRecord record : allRecords) { + boolean needUpdate = false; + + // 检查是否缺失课程信息 + if (StringUtils.isEmpty(record.getCourseName()) || + StringUtils.isEmpty(record.getLocation()) || + record.getDuration() == null) { + + log.info("修复记录 recordId={}, applicationId={}", record.getRecordId(), record.getApplicationId()); + + // 尝试从申请表获取信息 + RsTrainingApplication application = getApplicationService().selectRsTrainingApplicationById(record.getApplicationId()); + if (application != null) { + if (StringUtils.isEmpty(record.getCourseName()) && StringUtils.isNotBlank(application.getCourseName())) { + record.setCourseName(application.getCourseName()); + needUpdate = true; + } + if (StringUtils.isEmpty(record.getLocation()) && StringUtils.isNotBlank(application.getLocation())) { + record.setLocation(application.getLocation()); + needUpdate = true; + } + if (record.getDuration() == null && application.getDuration() != null) { + record.setDuration(application.getDuration()); + needUpdate = true; + } + } + + // 如果申请表中也没有,尝试从课程表获取 + if ((StringUtils.isEmpty(record.getCourseName()) || StringUtils.isEmpty(record.getLocation()) || record.getDuration() == null) + && record.getCourseId() != null) { + + RsTrainingCourse course = courseService.selectRsTrainingCourseById(record.getCourseId()); + if (course != null) { + if (StringUtils.isEmpty(record.getCourseName())) { + record.setCourseName(course.getTitle()); + needUpdate = true; + } + if (StringUtils.isEmpty(record.getLocation())) { + record.setLocation(course.getLocation()); + needUpdate = true; + } + if (record.getDuration() == null) { + record.setDuration(course.getDuration()); + needUpdate = true; + } + } + } + + // 更新记录 + if (needUpdate) { + record.setUpdateTime(DateUtils.getNowDate()); + record.setUpdateBy(1L); // 系统用户 + trainingRecordMapper.updateRsTrainingRecord(record); + fixedCount++; + log.info("已修复记录: courseName={}, location={}, duration={}", + record.getCourseName(), record.getLocation(), record.getDuration()); + } + } + } + + log.info("修复完成,共修复 {} 条记录", fixedCount); + return fixedCount; } } \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsTrainingRecordMapper.xml b/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsTrainingRecordMapper.xml index 692b99c2..242410b7 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsTrainingRecordMapper.xml +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsTrainingRecordMapper.xml @@ -10,36 +10,41 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + + + + + + + + + + + + + + - - - - - - - - - - + + select r.record_id, r.application_id, r.course_id, r.user_id, r.user_name, - r.completion_status, r.completion_time, r.score, r.certificate_path, r.certificate_number, - r.feedback, r.tenant_id, r.create_by, r.create_time, r.update_by, r.update_time, r.remark, - r.location, r.duration, r.apply_time, r.status, r.course_name, - c.title as course_title, a.department + r.completion_status, r.completion_time, r.score, r.certificate_path, r.certificate_number, r.attachment_ids, + r.feedback, r.tenant_id, r.create_dept, r.create_by, r.create_time, r.update_by, r.update_time, + r.department, r.position, r.reason, r.status, r.apply_time, r.current_approver, r.start_time, r.end_time, + r.course_name, r.duration, r.location from rs_training_record r - left join rs_training_course c on r.course_id = c.course_id and c.deleted = '0' - left join rs_training_application a on r.application_id = a.application_id and a.deleted = '0' + + INSERT INTO rs_training_record ( - application_id, course_id, user_id, user_name, department, position, reason, status, - apply_time, current_approver, location, duration, start_time, end_time, tenant_id, create_dept, course_name + record_id, application_id, course_id, user_id, user_name, completion_status, completion_time, score, + certificate_path, certificate_number, attachment_ids, feedback, location, duration, apply_time, + status, course_name, department, position, reason, current_approver, start_time, end_time, + tenant_id, create_dept, create_by, create_time, deleted ) VALUES ( - #{applicationId}, #{courseId}, #{userId}, #{userName}, #{department}, #{position}, #{reason}, #{status}, - #{applyTime}, #{currentApprover}, #{location}, #{duration}, #{startTime}, #{endTime}, #{tenantId}, #{createDept}, #{courseName} + #{recordId}, #{applicationId}, #{courseId}, #{userId}, #{userName}, #{completionStatus}, #{completionTime}, #{score}, + #{certificatePath}, #{certificateNumber}, #{attachmentIds}, #{feedback}, #{location}, #{duration}, #{applyTime}, + #{status}, #{courseName}, #{department}, #{position}, #{reason}, #{currentApprover}, #{startTime}, #{endTime}, + #{tenantId}, #{createDept}, #{createBy}, #{createTime}, '0' ) + + UPDATE rs_training_record + + application_id = #{applicationId}, + course_id = #{courseId}, + user_id = #{userId}, + user_name = #{userName}, + completion_status = #{completionStatus}, + completion_time = #{completionTime}, + score = #{score}, + certificate_path = #{certificatePath}, + certificate_number = #{certificateNumber}, + attachment_ids = #{attachmentIds}, + feedback = #{feedback}, + location = #{location}, + duration = #{duration}, + apply_time = #{applyTime}, + status = #{status}, + course_name = #{courseName}, + department = #{department}, + position = #{position}, + reason = #{reason}, + current_approver = #{currentApprover}, + start_time = #{startTime}, + end_time = #{endTime}, + update_by = #{updateBy}, + update_time = #{updateTime} + + where record_id = #{recordId} + + + + UPDATE rs_training_record SET deleted = '1' where record_id = #{recordId} + + + + UPDATE rs_training_record SET deleted = '1' where record_id in + + #{recordId} + + + \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/training-demo-data.sql b/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/training-demo-data.sql index 1f33dfcb..47200bdb 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/training-demo-data.sql +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/training-demo-data.sql @@ -5,24 +5,24 @@ -- ---------------------------- -- 培训课程表测试数据 -- ---------------------------- -INSERT INTO `rs_training_course` (`course_id`, `title`, `category`, `description`, `start_time`, `end_time`, `duration`, `location`, `max_participants`, `current_participants`, `status`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `deleted`) +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, '新教师入职培训', 'basic', '为新入职教师提供学校规章制度、教学系统使用等基础培训', '2025-08-15 09:00:00', '2025-08-16 17:00:00', 16, '行政楼三楼会议室', 30, 12, '0', '000000', 1, NOW(), 1, NOW(), '重要培训,所有新入职教师必须参加', '0'), -(10002, 'Excel高级应用培训', 'skill', '包括数据透视表、宏、VBA基础等Excel高级功能应用', '2025-08-20 13:30:00', '2025-08-22 16:30:00', 12, '教学楼B区205', 20, 8, '0', '000000', 1, NOW(), 1, NOW(), '面向行政人员的办公技能提升', '0'), -(10003, '教学评估标准培训', 'teaching', '介绍最新的教学评估标准和评价体系', '2025-09-05 09:00:00', '2025-09-05 17:00:00', 8, '线上培训', 100, 45, '0', '000000', 1, NOW(), 1, NOW(), '所有教师须参加', '0'), -(10004, '心理健康辅导技能', 'counseling', '学习学生心理问题识别和初步辅导技能', '2025-09-10 09:00:00', '2025-09-11 17:00:00', 16, '心理咨询中心', 25, 15, '0', '000000', 1, NOW(), 1, NOW(), '适合班主任和辅导员', '0'), -(10005, '科研项目申报指南', 'research', '介绍各类科研项目的申报流程、注意事项和技巧', '2025-09-15 14:00:00', '2025-09-16 17:00:00', 10, '科研楼报告厅', 50, 22, '0', '000000', 1, NOW(), 1, NOW(), '面向有科研项目需求的教师', '0'); +(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`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `deleted`) +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-20 10:15:22', '王主任', '000000', 2, NOW(), 2, NOW(), NULL, '0'), -(20002, 10002, 3, '李秘书', '行政部', '文员', '日常工作需要处理大量Excel表格,希望提升技能', '0', '2025-07-21 09:05:33', '赵部长', '000000', 3, NOW(), 3, NOW(), NULL, '0'), -(20003, 10003, 4, '刘老师', '外语学院', '副教授', '了解最新教学评估标准,提升教学质量', '1', '2025-07-22 14:30:45', '孙院长', '000000', 4, NOW(), 4, NOW(), NULL, '0'), -(20004, 10004, 5, '钱辅导员', '学生处', '辅导员', '提升学生心理问题识别和初步干预能力', '2', '2025-07-23 11:20:18', '周主任', '000000', 5, NOW(), 5, NOW(), '该老师近期有其他重要工作', '0'), -(20005, 10005, 6, '赵研究员', '科研处', '研究员', '正在准备申报国家级项目,需要了解最新申报指南', '0', '2025-07-24 16:40:57', '吴处长', '000000', 6, NOW(), 6, NOW(), NULL, '0'); +(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'); -- ---------------------------- -- 培训审批表测试数据 @@ -34,9 +34,5 @@ VALUES (30003, 20004, 103, '周主任', 1, '2', '近期有重要工作交接,建议下次再参加', '2025-07-24 14:20:00', '000000', 103, NOW(), 103, NOW(), NULL, '0'); -- ---------------------------- --- 培训记录表测试数据 --- ---------------------------- -INSERT INTO `rs_training_record` (`record_id`, `application_id`, `course_id`, `user_id`, `user_name`, `completion_status`, `completion_time`, `score`, `certificate_path`, `certificate_number`, `feedback`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `deleted`) -VALUES -(40001, 20001, 10001, 2, '张老师', '1', '2025-08-16 17:00:00', 92.5, '/certificates/10001/20001.pdf', 'CERT20250816001', '培训内容丰富,对新入职教师很有帮助', '000000', 1, NOW(), 1, NOW(), NULL, '0'), -(40002, 20003, 10003, 4, '刘老师', '0', NULL, NULL, NULL, NULL, NULL, '000000', 1, NOW(), 1, NOW(), '培训尚未结束', '0'); \ No newline at end of file +-- 培训记录表测试数据已删除,请通过申请界面新增 +-- ---------------------------- \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/training.sql b/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/training.sql index d8edf5da..e14ff3c0 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/training.sql +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/training.sql @@ -91,8 +91,21 @@ create table rs_training_record ( 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 '更新者', diff --git a/web/src/api/rs/training.ts b/web/src/api/rs/training.ts index 278c93af..439b0fd0 100644 --- a/web/src/api/rs/training.ts +++ b/web/src/api/rs/training.ts @@ -126,6 +126,7 @@ export interface RecordVO { completionTime?: string; certificatePath?: string; certificateNumber?: string; + certificateFileName?: string; feedback?: string; createTime: string; updateTime: string; @@ -148,6 +149,8 @@ export interface RecordForm { completionTime?: string; certificatePath?: string; certificateNumber?: string; + certificateFileName?: string; + attachmentIds?: string; feedback?: string; remark?: string; } @@ -424,3 +427,12 @@ export function resubmitTrainingApplication(applicationId: string | number, data data: data }); } + +// 更新培训记录的证书附件ID +export function updateRecordAttachments(recordId: string | number, data: { attachmentIds: string }) { + return request({ + url: `/rs/training/record/attachments/${recordId}`, + method: 'put', + data: data + }); +} diff --git a/web/src/views/rs/index.vue b/web/src/views/rs/index.vue index 42b5f370..37896b8a 100644 --- a/web/src/views/rs/index.vue +++ b/web/src/views/rs/index.vue @@ -372,7 +372,9 @@ {{ viewForm.applicantCount }} {{ viewForm.viewCount }} - {{ parseTime(viewForm.publishTime) || '未发布' }} + + {{ parseTime(viewForm.publishTime) || (viewForm.isActive === '0' ? parseTime(viewForm.createTime) || '长期有效' : '未发布') }} + {{ parseTime(viewForm.createTime) }}
{{ viewForm.description || '暂无描述' }}
diff --git a/web/src/views/rs/recruitment-portal.vue b/web/src/views/rs/recruitment-portal.vue index d656046c..63ebd884 100644 --- a/web/src/views/rs/recruitment-portal.vue +++ b/web/src/views/rs/recruitment-portal.vue @@ -82,12 +82,7 @@ - - - @@ -181,10 +176,10 @@

招聘信息

- {{ formatDate(currentJob.publishTime) }} + {{ formatDate(currentJob.publishTime) || formatDate(currentJob.createTime) || '长期有效' }} - {{ formatDate(currentJob.publishTime) }} + {{ getDeadlineDate(currentJob.publishTime) }} {{ currentJob.viewCount || 0 }} 次 {{ currentJob.applicantCount || 0 }} 人 @@ -257,6 +252,14 @@ const formatDate = (dateStr: string) => { return parseTime(dateStr, '{y}-{m}-{d}'); }; +// 计算截止日期(发布日期+30天) +const getDeadlineDate = (publishTime: string) => { + if (!publishTime) return '长期有效'; + const publishDate = new Date(publishTime); + const deadlineDate = new Date(publishDate.getTime() + 30 * 24 * 60 * 60 * 1000); // 30天后 + return parseTime(deadlineDate.toString(), '{y}-{m}-{d}'); +}; + // 检查是否过期(使用publishTime判断,假设30天有效期) const isJobExpired = (publishTime: string) => { if (!publishTime) return false; diff --git a/web/src/views/rs/training/index.vue b/web/src/views/rs/training/index.vue index 23636013..884df901 100644 --- a/web/src/views/rs/training/index.vue +++ b/web/src/views/rs/training/index.vue @@ -148,7 +148,7 @@ - +
@@ -230,12 +230,7 @@ {{ parseTime(scope.row.completionTime) || '未完成' }} - - - +