Gqingci před 1 týdnem
rodič
revize
7ff3125ca9
29 změnil soubory, kde provedl 699 přidání a 5 odebrání
  1. 1 0
      .gitignore
  2. 38 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/MainExamEvaluationController.java
  3. 34 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/MainPostCandidateController.java
  4. 5 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainBackCandidate.java
  5. 0 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainExamAbilityAttempt.java
  6. 44 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainExamApply.java
  7. 0 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainExamAttemptQuestion.java
  8. 0 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainExamAttemptQuestionOption.java
  9. 16 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/MainExamEvaluationSyncBo.java
  10. 13 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/MainPostCandidateQueryBo.java
  11. 13 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/MainPostCandidateStatusBo.java
  12. 0 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/ExamViewDetailVo.java
  13. 29 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainExamApplyListVo.java
  14. 15 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainExamSyncEmployeeOptionVo.java
  15. 3 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainPostApplyVo.java
  16. 37 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainPostCandidateVo.java
  17. 7 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/MainBackCandidateMapper.java
  18. 0 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/MainExamAbilityAttemptMapper.java
  19. 18 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/MainExamApplyMapper.java
  20. 0 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/MainExamAttemptQuestionMapper.java
  21. 0 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/MainExamAttemptQuestionOptionMapper.java
  22. 14 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/MainExamEvaluationMapper.java
  23. 14 3
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainExamEvaluationService.java
  24. 14 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainPostCandidateService.java
  25. 170 1
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainExamEvaluationServiceImpl.java
  26. 48 1
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainPostApplyServiceImpl.java
  27. 45 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainPostCandidateServiceImpl.java
  28. 51 0
      ruoyi-modules/ruoyi-main/src/main/resources/mapper/MainBackCandidateMapper.xml
  29. 70 0
      ruoyi-modules/ruoyi-main/src/main/resources/mapper/MainExamEvaluationMapper.xml

+ 1 - 0
.gitignore

@@ -24,6 +24,7 @@ target/
 *.iws
 *.iml
 *.ipr
+.claude
 
 ### JRebel ###
 rebel.xml

+ 38 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/MainExamEvaluationController.java

@@ -17,12 +17,16 @@ import org.dromara.demo.domain.dto.KaoshixingExamListRequest;
 import org.dromara.demo.domain.dto.KaoshixingRequest;
 import org.dromara.demo.service.impl.KaoshixingService;
 import org.dromara.main.domain.bo.MainExamEvaluationBo;
+import org.dromara.main.domain.bo.MainExamEvaluationSyncBo;
+import org.dromara.main.domain.vo.MainExamApplyListVo;
+import org.dromara.main.domain.vo.MainExamSyncEmployeeOptionVo;
 import org.dromara.main.domain.vo.MainExamEvaluationVo;
 import org.dromara.main.service.IMainExamEvaluationService;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 
@@ -44,6 +48,40 @@ public class MainExamEvaluationController extends BaseController {
         return examEvaluationService.queryPageList(bo, pageQuery);
     }
 
+    @SaCheckPermission("main:examEvaluation:list")
+    @GetMapping("/stats/participant")
+    public R<Map<Long, Map<String, Long>>> participantStats(@RequestParam("ids") Collection<Long> ids) {
+        return R.ok(examEvaluationService.getParticipantStats(ids));
+    }
+
+    @SaCheckPermission("main:examEvaluation:list")
+    @GetMapping("/sync/employeeOptions")
+    public R<List<MainExamSyncEmployeeOptionVo>> syncEmployeeOptions() {
+        return R.ok(examEvaluationService.getSyncEmployeeOptions());
+    }
+
+    @SaCheckPermission("main:examEvaluation:sync")
+    @Log(title = "同步至员工", businessType = BusinessType.UPDATE)
+    @PostMapping("/sync/employees")
+    public R<Void> syncToEmployees(@Validated @RequestBody MainExamEvaluationSyncBo bo) {
+        return toAjax(examEvaluationService.syncToEmployees(bo));
+    }
+
+    @SaCheckPermission("main:examEvaluation:list")
+    @GetMapping("/applyList")
+    public TableDataInfo<MainExamApplyListVo> applyList(@RequestParam("evaluationId") Long evaluationId,
+                                                        @RequestParam(value = "keyword", required = false) String keyword,
+                                                        PageQuery pageQuery) {
+        return examEvaluationService.getApplyList(evaluationId, keyword, pageQuery);
+    }
+
+    @SaCheckPermission("main:examEvaluation:edit")
+    @Log(title = "移除测评报名", businessType = BusinessType.DELETE)
+    @DeleteMapping("/apply/{applyId}")
+    public R<Void> removeApply(@PathVariable("applyId") Long applyId) {
+        return toAjax(examEvaluationService.removeApply(applyId));
+    }
+
     /**
      * 获取测评详细信息
      */

+ 34 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/MainPostCandidateController.java

@@ -0,0 +1,34 @@
+package org.dromara.main.controller;
+
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.main.domain.bo.MainPostCandidateQueryBo;
+import org.dromara.main.domain.bo.MainPostCandidateStatusBo;
+import org.dromara.main.domain.vo.MainPostCandidateVo;
+import org.dromara.main.service.IMainPostCandidateService;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/main/postCandidate")
+public class MainPostCandidateController extends BaseController {
+
+    private final IMainPostCandidateService mainPostCandidateService;
+
+    @GetMapping("/list")
+    public TableDataInfo<MainPostCandidateVo> list(MainPostCandidateQueryBo bo, PageQuery pageQuery) {
+        return mainPostCandidateService.queryPageList(bo, pageQuery);
+    }
+
+    @PutMapping("/status")
+    public R<Void> updateStatus(@RequestBody MainPostCandidateStatusBo bo) {
+        return toAjax(mainPostCandidateService.updateStatus(bo));
+    }
+}

+ 5 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainBackCandidate.java

@@ -20,6 +20,9 @@ public class MainBackCandidate extends BaseEntity {
     /** 关联学员ID */
     private Long studentId;
 
+    /** 关联岗位ID */
+    private Long postId;
+
     /** 来源渠道 */
     private String source;
 
@@ -29,4 +32,6 @@ public class MainBackCandidate extends BaseEntity {
 
     /** 状态 */
     private String status;
+
+    private String remark;
 }

+ 0 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainExamAbilityAttempt.java


+ 44 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainExamApply.java

@@ -0,0 +1,44 @@
+package org.dromara.main.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+import java.util.Date;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("main_exam_apply")
+public class MainExamApply extends BaseEntity {
+
+    @TableId(value = "id")
+    private Long id;
+
+    private Long evaluationId;
+
+    private String tenantId;
+
+    private Long studentId;
+
+    private String applySource;
+
+    private String applyStatus;
+
+    private Date scheduleStartTime;
+
+    private Date deadlineTime;
+
+    private Integer maxAttemptCount;
+
+    private Date finishedTime;
+
+    private String finalResult;
+
+    private String remark;
+
+    @TableLogic
+    private String delFlag;
+}

+ 0 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainExamAttemptQuestion.java


+ 0 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainExamAttemptQuestionOption.java


+ 16 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/MainExamEvaluationSyncBo.java

@@ -0,0 +1,16 @@
+package org.dromara.main.domain.bo;
+
+import jakarta.validation.constraints.NotEmpty;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class MainExamEvaluationSyncBo {
+
+    @NotEmpty(message = "测评不能为空")
+    private List<Long> evaluationIds;
+
+    @NotEmpty(message = "员工不能为空")
+    private List<Long> studentIds;
+}

+ 13 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/MainPostCandidateQueryBo.java

@@ -0,0 +1,13 @@
+package org.dromara.main.domain.bo;
+
+import lombok.Data;
+
+@Data
+public class MainPostCandidateQueryBo {
+
+    private Long postId;
+
+    private String keyword;
+
+    private String status;
+}

+ 13 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/MainPostCandidateStatusBo.java

@@ -0,0 +1,13 @@
+package org.dromara.main.domain.bo;
+
+import lombok.Data;
+
+@Data
+public class MainPostCandidateStatusBo {
+
+    private Long id;
+
+    private String status;
+
+    private String remark;
+}

+ 0 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/ExamViewDetailVo.java


+ 29 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainExamApplyListVo.java

@@ -0,0 +1,29 @@
+package org.dromara.main.domain.vo;
+
+import lombok.Data;
+
+@Data
+public class MainExamApplyListVo {
+
+    private Long id;
+
+    private Long evaluationId;
+
+    private Long studentId;
+
+    private String name;
+
+    private String gender;
+
+    private String department;
+
+    private String phone;
+
+    private String statusText;
+
+    private String statusType;
+
+    private String evaluateTime;
+
+    private String actionType;
+}

+ 15 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainExamSyncEmployeeOptionVo.java

@@ -0,0 +1,15 @@
+package org.dromara.main.domain.vo;
+
+import lombok.Data;
+
+@Data
+public class MainExamSyncEmployeeOptionVo {
+
+    private Long id;
+
+    private String name;
+
+    private String mobile;
+
+    private String studentNo;
+}

+ 3 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainPostApplyVo.java

@@ -125,6 +125,9 @@ public class MainPostApplyVo implements Serializable {
     @ExcelProperty(value = "发布状态")
     private String status;
 
+    @ExcelProperty(value = "报名人数")
+    private Long applyCount;
+
     @ExcelProperty(value = "创建时间")
     private Date createTime;
 }

+ 37 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainPostCandidateVo.java

@@ -0,0 +1,37 @@
+package org.dromara.main.domain.vo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+@Data
+public class MainPostCandidateVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    private Long id;
+
+    private Long postId;
+
+    private Long studentId;
+
+    private String name;
+
+    private String gender;
+
+    private String phone;
+
+    private String status;
+
+    private String statusText;
+
+    private String statusType;
+
+    private String evaluateTime;
+
+    private Long resumeFile;
+
+    private String remark;
+}

+ 7 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/MainBackCandidateMapper.java

@@ -1,8 +1,15 @@
 package org.dromara.main.mapper;
 
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Param;
 import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
 import org.dromara.main.domain.MainBackCandidate;
+import org.dromara.main.domain.bo.MainPostCandidateQueryBo;
 import org.dromara.main.domain.vo.MainBackCandidateVo;
+import org.dromara.main.domain.vo.MainPostCandidateVo;
 
 public interface MainBackCandidateMapper extends BaseMapperPlus<MainBackCandidate, MainBackCandidateVo> {
+
+    IPage<MainPostCandidateVo> selectPostCandidatePage(Page<MainPostCandidateVo> page, @Param("bo") MainPostCandidateQueryBo bo);
 }

+ 0 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/MainExamAbilityAttemptMapper.java


+ 18 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/MainExamApplyMapper.java

@@ -0,0 +1,18 @@
+package org.dromara.main.mapper;
+
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.main.domain.MainExamApply;
+
+public interface MainExamApplyMapper extends BaseMapperPlus<MainExamApply, MainExamApply> {
+
+    @Select("SELECT * FROM main_exam_apply WHERE evaluation_id = #{evaluationId} AND tenant_id = #{tenantId} AND student_id = #{studentId} LIMIT 1")
+    MainExamApply selectAnyByUniqueKey(@Param("evaluationId") Long evaluationId,
+                                       @Param("tenantId") String tenantId,
+                                       @Param("studentId") Long studentId);
+
+    @Update("UPDATE main_exam_apply SET del_flag = '0', apply_source = '3', apply_status = '0', schedule_start_time = NOW(), deadline_time = NULL, max_attempt_count = 1, finished_time = NULL, final_result = NULL, update_time = NOW() WHERE id = #{id}")
+    int restoreDeletedApply(@Param("id") Long id);
+}

+ 0 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/MainExamAttemptQuestionMapper.java


+ 0 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/MainExamAttemptQuestionOptionMapper.java


+ 14 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/MainExamEvaluationMapper.java

@@ -6,8 +6,13 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import org.apache.ibatis.annotations.Param;
 import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
 import org.dromara.main.domain.MainExamEvaluation;
+import org.dromara.main.domain.vo.MainExamApplyListVo;
 import org.dromara.main.domain.vo.MainExamEvaluationVo;
 
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
 public interface MainExamEvaluationMapper extends BaseMapperPlus<MainExamEvaluation, MainExamEvaluationVo> {
 
     /**
@@ -19,4 +24,13 @@ public interface MainExamEvaluationMapper extends BaseMapperPlus<MainExamEvaluat
      * 分页查询测评列表(包含主图URL和能力配置)
      */
     Page<MainExamEvaluationVo> selectVoPageWithImages(Page<MainExamEvaluationVo> page, @Param(Constants.WRAPPER) Wrapper<MainExamEvaluation> queryWrapper);
+
+    /**
+     * 批量查询测评参与统计
+     */
+    List<Map<String, Object>> selectParticipantStatsByEvaluationIds(@Param("ids") Collection<Long> ids);
+
+    List<MainExamApplyListVo> selectApplyListByEvaluationId(@Param("evaluationId") Long evaluationId,
+                                                            @Param("tenantId") String tenantId,
+                                                            @Param("keyword") String keyword);
 }

+ 14 - 3
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainExamEvaluationService.java

@@ -1,13 +1,14 @@
 package org.dromara.main.service;
 
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import org.dromara.main.domain.bo.MainExamEvaluationBo;
+import org.dromara.main.domain.bo.MainExamEvaluationSyncBo;
+import org.dromara.main.domain.vo.MainExamApplyListVo;
+import org.dromara.main.domain.vo.MainExamSyncEmployeeOptionVo;
 import org.dromara.main.domain.vo.MainExamEvaluationVo;
 
 import java.util.Collection;
 import java.util.List;
-
-
+import java.util.Map;
 
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
@@ -59,4 +60,14 @@ public interface IMainExamEvaluationService {
      * 同步第三方数据
      */
     Boolean syncThirdPartyData();
+
+    Map<Long, Map<String, Long>> getParticipantStats(Collection<Long> ids);
+
+    List<MainExamSyncEmployeeOptionVo> getSyncEmployeeOptions();
+
+    Boolean syncToEmployees(MainExamEvaluationSyncBo bo);
+
+    TableDataInfo<MainExamApplyListVo> getApplyList(Long evaluationId, String keyword, PageQuery pageQuery);
+
+    Boolean removeApply(Long applyId);
 }

+ 14 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainPostCandidateService.java

@@ -0,0 +1,14 @@
+package org.dromara.main.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.main.domain.bo.MainPostCandidateQueryBo;
+import org.dromara.main.domain.bo.MainPostCandidateStatusBo;
+import org.dromara.main.domain.vo.MainPostCandidateVo;
+
+public interface IMainPostCandidateService {
+
+    TableDataInfo<MainPostCandidateVo> queryPageList(MainPostCandidateQueryBo bo, PageQuery pageQuery);
+
+    Boolean updateStatus(MainPostCandidateStatusBo bo);
+}

+ 170 - 1
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainExamEvaluationServiceImpl.java

@@ -1,30 +1,49 @@
 package org.dromara.main.service.impl;
 
 import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 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.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
+
 import org.dromara.main.domain.MainAbilityConfig;
+import org.dromara.main.domain.MainExamApply;
 import org.dromara.main.domain.MainExamEvaluation;
+import org.dromara.main.domain.MainStudent;
 import org.dromara.main.domain.bo.MainExamEvaluationBo;
+import org.dromara.main.domain.bo.MainExamEvaluationSyncBo;
+import org.dromara.main.domain.vo.MainExamApplyListVo;
+import org.dromara.main.domain.vo.MainExamSyncEmployeeOptionVo;
 import org.dromara.main.domain.vo.MainExamEvaluationVo;
 import org.dromara.main.mapper.MainAbilityConfigMapper;
+import org.dromara.main.mapper.MainExamApplyMapper;
 import org.dromara.main.mapper.MainExamEvaluationMapper;
+import org.dromara.main.mapper.MainStudentMapper;
 import org.dromara.main.service.IMainExamEvaluationService;
 import org.springframework.stereotype.Service;
+import org.springframework.dao.DuplicateKeyException;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.Base64;
 import java.util.Collection;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 @Slf4j
 @RequiredArgsConstructor
@@ -33,6 +52,8 @@ public class MainExamEvaluationServiceImpl implements IMainExamEvaluationService
 
     private final MainExamEvaluationMapper baseMapper;
     private final MainAbilityConfigMapper mainAbilityConfigMapper;
+    private final MainStudentMapper mainStudentMapper;
+    private final MainExamApplyMapper mainExamApplyMapper;
 
     @Override
     public MainExamEvaluationVo queryById(Long id) {
@@ -158,6 +179,154 @@ public class MainExamEvaluationServiceImpl implements IMainExamEvaluationService
         return true;
     }
 
+    @Override
+    public TableDataInfo<MainExamApplyListVo> getApplyList(Long evaluationId, String keyword, PageQuery pageQuery) {
+        if (evaluationId == null) {
+            throw new ServiceException("测评ID不能为空");
+        }
+        String tenantId = getCurrentTenantId();
+        List<MainExamApplyListVo> list = baseMapper.selectApplyListByEvaluationId(evaluationId, tenantId, keyword);
+        return TableDataInfo.build(list, pageQuery.build());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean removeApply(Long applyId) {
+        if (applyId == null) {
+            throw new ServiceException("报名记录不能为空");
+        }
+        MainExamApply apply = mainExamApplyMapper.selectById(applyId);
+        if (apply == null || !StringUtils.equals(apply.getTenantId(), getCurrentTenantId())) {
+            throw new ServiceException("报名记录不存在");
+        }
+        return mainExamApplyMapper.deleteById(applyId) > 0;
+    }
+
+    @Override
+    public Map<Long, Map<String, Long>> getParticipantStats(Collection<Long> ids) {
+        Map<Long, Map<String, Long>> result = new HashMap<>();
+        if (CollectionUtils.isEmpty(ids)) {
+            return result;
+        }
+        List<Map<String, Object>> stats = baseMapper.selectParticipantStatsByEvaluationIds(ids);
+        for (Map<String, Object> item : stats) {
+            if (item == null || item.get("evaluationId") == null) {
+                continue;
+            }
+            Long evaluationId = ((Number) item.get("evaluationId")).longValue();
+            long participantCount = item.get("participantCount") == null ? 0L : ((Number) item.get("participantCount")).longValue();
+            long totalCount = item.get("totalCount") == null ? 0L : ((Number) item.get("totalCount")).longValue();
+            Map<String, Long> countMap = new HashMap<>();
+            countMap.put("participantCount", participantCount);
+            countMap.put("totalCount", totalCount);
+            result.put(evaluationId, countMap);
+        }
+        return result;
+    }
+
+    @Override
+    public List<MainExamSyncEmployeeOptionVo> getSyncEmployeeOptions() {
+        String tenantId = getCurrentTenantId();
+        List<MainStudent> studentList = mainStudentMapper.selectList(
+            Wrappers.<MainStudent>lambdaQuery()
+                .eq(MainStudent::getTenantId, tenantId)
+                .eq(MainStudent::getStatus, "0")
+                .orderByDesc(MainStudent::getCreateTime)
+        );
+        if (CollectionUtils.isEmpty(studentList)) {
+            return new ArrayList<>();
+        }
+        List<MainExamSyncEmployeeOptionVo> result = new ArrayList<>();
+        for (MainStudent student : studentList) {
+            MainExamSyncEmployeeOptionVo option = new MainExamSyncEmployeeOptionVo();
+            option.setId(student.getId());
+            option.setName(student.getName());
+            option.setMobile(student.getMobile());
+            option.setStudentNo(student.getStudentNo());
+            result.add(option);
+        }
+        return result.stream()
+            .collect(Collectors.toMap(MainExamSyncEmployeeOptionVo::getId, item -> item, (left, right) -> left))
+            .values()
+            .stream()
+            .sorted(Comparator.comparing(MainExamSyncEmployeeOptionVo::getId).reversed())
+            .toList();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean syncToEmployees(MainExamEvaluationSyncBo bo) {
+        if (bo == null || CollectionUtils.isEmpty(bo.getEvaluationIds()) || CollectionUtils.isEmpty(bo.getStudentIds())) {
+            throw new ServiceException("同步参数不能为空");
+        }
+        String tenantId = getCurrentTenantId();
+        Set<Long> evaluationIds = new HashSet<>(bo.getEvaluationIds());
+        Set<Long> studentIds = new HashSet<>(bo.getStudentIds());
+        List<MainStudent> validStudents = mainStudentMapper.selectList(
+            Wrappers.<MainStudent>lambdaQuery()
+                .eq(MainStudent::getTenantId, tenantId)
+                .eq(MainStudent::getStatus, "0")
+                .in(MainStudent::getId, studentIds)
+        );
+        Set<Long> validStudentIds = validStudents.stream()
+            .map(MainStudent::getId)
+            .filter(ObjectUtil::isNotNull)
+            .collect(Collectors.toSet());
+        if (CollectionUtils.isEmpty(validStudentIds)) {
+            throw new ServiceException("未找到可同步的员工");
+        }
+        List<MainExamApply> exists = mainExamApplyMapper.selectList(
+            Wrappers.<MainExamApply>lambdaQuery()
+                .eq(MainExamApply::getTenantId, tenantId)
+                .in(MainExamApply::getEvaluationId, evaluationIds)
+                .in(MainExamApply::getStudentId, validStudentIds)
+        );
+        Set<String> existingKeys = exists.stream()
+            .map(item -> item.getEvaluationId() + "_" + item.getStudentId())
+            .collect(Collectors.toSet());
+        Date now = new Date();
+        List<MainExamApply> addList = new ArrayList<>();
+        for (Long evaluationId : evaluationIds) {
+            for (Long studentId : validStudentIds) {
+                String key = evaluationId + "_" + studentId;
+                if (existingKeys.contains(key)) {
+                    continue;
+                }
+                MainExamApply apply = new MainExamApply();
+                apply.setEvaluationId(evaluationId);
+                apply.setTenantId(tenantId);
+                apply.setStudentId(studentId);
+                apply.setApplySource("3");
+                apply.setApplyStatus("0");
+                apply.setMaxAttemptCount(1);
+                apply.setScheduleStartTime(now);
+                apply.setDelFlag("0");
+                addList.add(apply);
+            }
+        }
+        for (MainExamApply apply : addList) {
+            try {
+                mainExamApplyMapper.insert(apply);
+            } catch (DuplicateKeyException e) {
+                MainExamApply existApply = mainExamApplyMapper.selectAnyByUniqueKey(apply.getEvaluationId(), apply.getTenantId(), apply.getStudentId());
+                if (existApply != null && StringUtils.equals(existApply.getDelFlag(), "1")) {
+                    mainExamApplyMapper.restoreDeletedApply(existApply.getId());
+                    continue;
+                }
+                log.warn("测评报名已存在,跳过本条: evaluationId={}, studentId={}, tenantId={}", apply.getEvaluationId(), apply.getStudentId(), apply.getTenantId());
+            }
+        }
+        return true;
+    }
+
+    private String getCurrentTenantId() {
+        String tenantId = LoginHelper.getTenantId();
+        if (StringUtils.isBlank(tenantId)) {
+            throw new ServiceException("未登录或token已失效");
+        }
+        return tenantId;
+    }
+
     private void decodeDetailFieldInBo(MainExamEvaluationBo bo) {
         if (StringUtils.isNotBlank(bo.getDetail())) {
             try {

+ 48 - 1
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainPostApplyServiceImpl.java

@@ -1,6 +1,7 @@
 package org.dromara.main.service.impl;
 
 import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -9,34 +10,46 @@ import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.main.domain.MainBackCandidate;
 import org.dromara.main.domain.MainPostApply;
 import org.dromara.main.domain.bo.MainPostApplyBo;
 import org.dromara.main.domain.vo.MainPostApplyVo;
+import org.dromara.main.mapper.MainBackCandidateMapper;
 import org.dromara.main.mapper.MainPostApplyMapper;
 import org.dromara.main.service.IMainPostApplyService;
 import org.springframework.stereotype.Service;
 
 import java.math.BigDecimal;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 @RequiredArgsConstructor
 @Service
 public class MainPostApplyServiceImpl implements IMainPostApplyService {
 
     private final MainPostApplyMapper baseMapper;
+    private final MainBackCandidateMapper mainBackCandidateMapper;
 
     @Override
     public TableDataInfo<MainPostApplyVo> queryPageList(MainPostApplyBo bo, PageQuery pageQuery) {
         LambdaQueryWrapper<MainPostApply> lqw = buildQueryWrapper(bo);
         Page<MainPostApplyVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        fillApplyCount(result.getRecords());
         return TableDataInfo.build(result);
     }
 
     @Override
     public List<MainPostApplyVo> queryList(MainPostApplyBo bo) {
         LambdaQueryWrapper<MainPostApply> lqw = buildQueryWrapper(bo);
-        return baseMapper.selectVoList(lqw);
+        List<MainPostApplyVo> list = baseMapper.selectVoList(lqw);
+        fillApplyCount(list);
+        return list;
     }
 
     @Override
@@ -124,4 +137,38 @@ public class MainPostApplyServiceImpl implements IMainPostApplyService {
             throw new ServiceException("能力C及格分必须在0-100之间");
         }
     }
+
+    private void fillApplyCount(List<MainPostApplyVo> postList) {
+        if (postList == null || postList.isEmpty()) {
+            return;
+        }
+
+        Set<Long> postIds = postList.stream()
+            .map(MainPostApplyVo::getPostId)
+            .filter(Objects::nonNull)
+            .collect(Collectors.toSet());
+
+        if (postIds.isEmpty()) {
+            postList.forEach(item -> item.setApplyCount(0L));
+            return;
+        }
+
+        QueryWrapper<MainBackCandidate> queryWrapper = new QueryWrapper<>();
+        queryWrapper.select("post_id", "COUNT(1) AS applyCount");
+        queryWrapper.in("post_id", postIds);
+        queryWrapper.eq("del_flag", "0");
+        queryWrapper.groupBy("post_id");
+
+        List<Map<String, Object>> countRows = mainBackCandidateMapper.selectMaps(queryWrapper);
+        Map<Long, Long> countMap = countRows == null ? Collections.emptyMap() : countRows.stream()
+            .filter(Objects::nonNull)
+            .collect(Collectors.toMap(
+                row -> Long.valueOf(String.valueOf(row.get("post_id"))),
+                row -> Long.valueOf(String.valueOf(row.get("applyCount"))),
+                (left, right) -> left,
+                HashMap::new
+            ));
+
+        postList.forEach(item -> item.setApplyCount(countMap.getOrDefault(item.getPostId(), 0L)));
+    }
 }

+ 45 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainPostCandidateServiceImpl.java

@@ -0,0 +1,45 @@
+package org.dromara.main.service.impl;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.main.domain.MainBackCandidate;
+import org.dromara.main.domain.bo.MainPostCandidateQueryBo;
+import org.dromara.main.domain.bo.MainPostCandidateStatusBo;
+import org.dromara.main.domain.vo.MainPostCandidateVo;
+import org.dromara.main.mapper.MainBackCandidateMapper;
+import org.dromara.main.service.IMainPostCandidateService;
+import org.springframework.stereotype.Service;
+
+@RequiredArgsConstructor
+@Service
+public class MainPostCandidateServiceImpl implements IMainPostCandidateService {
+
+    private final MainBackCandidateMapper mainBackCandidateMapper;
+
+    @Override
+    public TableDataInfo<MainPostCandidateVo> queryPageList(MainPostCandidateQueryBo bo, PageQuery pageQuery) {
+        if (bo.getPostId() == null) {
+            throw new ServiceException("岗位ID不能为空");
+        }
+        IPage<MainPostCandidateVo> page = mainBackCandidateMapper.selectPostCandidatePage(pageQuery.build(), bo);
+        return TableDataInfo.build(page);
+    }
+
+    @Override
+    public Boolean updateStatus(MainPostCandidateStatusBo bo) {
+        if (bo.getId() == null) {
+            throw new ServiceException("报名记录ID不能为空");
+        }
+        if (bo.getStatus() == null || bo.getStatus().isBlank()) {
+            throw new ServiceException("状态不能为空");
+        }
+        MainBackCandidate entity = new MainBackCandidate();
+        entity.setId(bo.getId());
+        entity.setStatus(bo.getStatus());
+        entity.setRemark(bo.getRemark());
+        return mainBackCandidateMapper.updateById(entity) > 0;
+    }
+}

+ 51 - 0
ruoyi-modules/ruoyi-main/src/main/resources/mapper/MainBackCandidateMapper.xml

@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.main.mapper.MainBackCandidateMapper">
+
+    <select id="selectPostCandidatePage" resultType="org.dromara.main.domain.vo.MainPostCandidateVo">
+        SELECT
+            c.id,
+            c.post_id AS postId,
+            c.student_id AS studentId,
+            s.name AS name,
+            CASE s.gender
+                WHEN '0' THEN '男'
+                WHEN '1' THEN '女'
+                ELSE '--'
+            END AS gender,
+            s.mobile AS phone,
+            COALESCE(NULLIF(c.status, ''), 'pending') AS status,
+            CASE COALESCE(NULLIF(c.status, ''), 'pending')
+                WHEN 'adopted' THEN '录用'
+                WHEN 'rejected' THEN '不录用'
+                WHEN 'closed' THEN '学员已拒绝'
+                ELSE '待回复'
+            END AS statusText,
+            CASE COALESCE(NULLIF(c.status, ''), 'pending')
+                WHEN 'adopted' THEN 'success'
+                WHEN 'rejected' THEN 'danger'
+                WHEN 'closed' THEN 'danger'
+                ELSE 'warning'
+            END AS statusType,
+            DATE_FORMAT(COALESCE(c.update_time, c.create_time), '%Y-%m-%d %H:%i:%s') AS evaluateTime,
+            s.resume_file AS resumeFile,
+            c.remark
+        FROM main_back_candidate c
+        LEFT JOIN main_student s ON c.student_id = s.id AND IFNULL(s.del_flag, '0') = '0'
+        WHERE IFNULL(c.del_flag, '0') = '0'
+          AND c.post_id = #{bo.postId}
+        <if test="bo.keyword != null and bo.keyword != ''">
+            AND (
+                s.name LIKE CONCAT('%', #{bo.keyword}, '%')
+                OR s.mobile LIKE CONCAT('%', #{bo.keyword}, '%')
+            )
+        </if>
+        <if test="bo.status != null and bo.status != ''">
+            AND COALESCE(NULLIF(c.status, ''), 'pending') = #{bo.status}
+        </if>
+        ORDER BY c.create_time DESC, c.id DESC
+    </select>
+
+</mapper>

+ 70 - 0
ruoyi-modules/ruoyi-main/src/main/resources/mapper/MainExamEvaluationMapper.xml

@@ -58,4 +58,74 @@
         ${ew.customSqlSegment}
     </select>
 
+    <select id="selectParticipantStatsByEvaluationIds" resultType="java.util.HashMap">
+        SELECT
+            a.evaluation_id AS evaluationId,
+            COUNT(DISTINCT a.id) AS totalCount,
+            COUNT(DISTINCT CASE
+                WHEN a.final_result IN ('1', '2')
+                 AND EXISTS (
+                    SELECT 1
+                    FROM main_exam_ability_attempt t
+                    WHERE t.apply_id = a.id
+                      AND t.del_flag = '0'
+                      AND t.total_score IS NOT NULL
+                 )
+                THEN a.id
+            END) AS participantCount
+        FROM main_exam_apply a
+        WHERE IFNULL(a.del_flag, '0') = '0'
+          AND a.evaluation_id IN
+          <foreach collection="ids" item="id" open="(" separator="," close=")">
+              #{id}
+          </foreach>
+        GROUP BY a.evaluation_id
+    </select>
+
+    <select id="selectApplyListByEvaluationId" resultType="org.dromara.main.domain.vo.MainExamApplyListVo">
+        SELECT
+            a.id,
+            a.evaluation_id AS evaluationId,
+            a.student_id AS studentId,
+            s.name AS name,
+            CASE s.gender
+                WHEN '0' THEN '男'
+                WHEN '1' THEN '女'
+                ELSE '--'
+            END AS gender,
+            '--' AS department,
+            s.mobile AS phone,
+            CASE
+                WHEN a.final_result = '1' THEN '通过'
+                WHEN a.final_result = '2' THEN '未通过'
+                WHEN a.apply_status = '2' THEN '待评分'
+                WHEN a.apply_status = '1' THEN '测评中'
+                ELSE '待测评'
+            END AS statusText,
+            CASE
+                WHEN a.final_result = '1' THEN 'success'
+                WHEN a.final_result = '2' THEN 'danger'
+                WHEN a.apply_status IN ('1', '2') THEN 'warning'
+                ELSE 'info'
+            END AS statusType,
+            DATE_FORMAT(COALESCE(a.finished_time, a.update_time, a.create_time), '%Y-%m-%d %H:%i:%s') AS evaluateTime,
+            CASE
+                WHEN a.final_result IN ('1', '2') OR a.apply_status IN ('1', '2') THEN 'detail'
+                ELSE 'remove'
+            END AS actionType
+        FROM main_exam_apply a
+        LEFT JOIN main_student s ON a.student_id = s.id AND IFNULL(s.del_flag, '0') = '0'
+        WHERE IFNULL(a.del_flag, '0') = '0'
+          AND a.evaluation_id = #{evaluationId}
+          AND a.tenant_id = #{tenantId}
+          <if test="keyword != null and keyword != ''">
+            AND (
+                s.name LIKE CONCAT('%', #{keyword}, '%')
+                OR s.mobile LIKE CONCAT('%', #{keyword}, '%')
+                OR a.final_result LIKE CONCAT('%', #{keyword}, '%')
+            )
+          </if>
+        ORDER BY a.create_time DESC, a.id DESC
+    </select>
+
 </mapper>