西格玛许 5 godzin temu
rodzic
commit
a6c7e374a6
25 zmienionych plików z 697 dodań i 35 usunięć
  1. 3 1
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/CsSessionController.java
  2. 8 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/MainOrderController.java
  3. 80 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/MainStudentController.java
  4. 37 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/MainTrainingLearnRecordController.java
  5. 3 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/CsSession.java
  6. 76 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainTrainingLearnRecord.java
  7. 2 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/CsSessionBo.java
  8. 11 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/MainExamEvaluationBo.java
  9. 0 2
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/MainPositionBo.java
  10. 3 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/MainStudentBo.java
  11. 3 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/CsSessionVo.java
  12. 3 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainExamEvaluationVo.java
  13. 178 1
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainStudentVo.java
  14. 41 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/MainTrainingLearnRecordMapper.java
  15. 2 1
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/ICsSessionService.java
  16. 5 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainOrderService.java
  17. 2 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainPositionService.java
  18. 20 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainTrainingLearnRecordService.java
  19. 23 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/CsMessageServiceImpl.java
  20. 13 3
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/CsSessionServiceImpl.java
  21. 52 16
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainExamEvaluationServiceImpl.java
  22. 12 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainOrderServiceImpl.java
  23. 9 3
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainPositionServiceImpl.java
  24. 85 8
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainStudentServiceImpl.java
  25. 26 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainTrainingLearnRecordServiceImpl.java

+ 3 - 1
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/CsSessionController.java

@@ -34,9 +34,11 @@ public class CsSessionController extends BaseController {
         Long fromUserId = Long.valueOf(params.get("fromUserId").toString());
         String fromUserName = (String) params.get("fromUserName");
         String fromUserAvatar = (String) params.get("fromUserAvatar");
+        String sourceId = (String) params.get("sourceId");
 
         CsSessionVo session = sessionService.createOrGetSession(
-            sessionType, fromUserId, fromUserName, fromUserAvatar
+            sessionType, fromUserId, fromUserName, fromUserAvatar,
+            sourceId
         );
         return R.ok(session);
     }

+ 8 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/MainOrderController.java

@@ -47,6 +47,14 @@ public class MainOrderController extends BaseController {
         return iMainOrderService.queryPageList(order, pageQuery);
     }
 
+    /**
+     * 获取订单状态统计
+     */
+    @GetMapping("/statistics")
+    public R<java.util.Map<String, Long>> statistics() {
+        return R.ok(iMainOrderService.getStatusCount());
+    }
+
     /**
      * 获取订单详细信息
      *

+ 80 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/MainStudentController.java

@@ -6,6 +6,7 @@ import jakarta.validation.constraints.NotEmpty;
 import jakarta.validation.constraints.NotNull;
 import lombok.RequiredArgsConstructor;
 import org.dromara.common.core.domain.R;
+import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.core.validate.AddGroup;
 import org.dromara.common.core.validate.EditGroup;
 import org.dromara.common.excel.utils.ExcelUtil;
@@ -18,10 +19,24 @@ import org.dromara.common.web.core.BaseController;
 import org.dromara.main.domain.bo.MainStudentBo;
 import org.dromara.main.domain.vo.MainStudentVo;
 import org.dromara.main.service.IMainStudentService;
+import org.dromara.system.service.ISysOssService;
 import org.springframework.validation.annotation.Validated;
+import org.dromara.common.oss.core.OssClient;
+import org.dromara.common.oss.factory.OssFactory;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.main.domain.vo.MainStudentAppendixVo;
+import org.dromara.system.domain.vo.SysOssVo;
+import org.springframework.http.MediaType;
+import org.dromara.common.core.utils.file.FileUtils;
+import cn.hutool.core.util.ObjectUtil;
 import org.springframework.web.bind.annotation.*;
 
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
 import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
 
 @Validated
 @RequiredArgsConstructor
@@ -30,6 +45,7 @@ import java.util.List;
 public class MainStudentController extends BaseController {
 
     private final IMainStudentService mainStudentService;
+    private final ISysOssService sysOssService;
 
     /**
      * 查询学员列表
@@ -116,4 +132,68 @@ public class MainStudentController extends BaseController {
     public R<Void> updateUserType(@RequestBody MainStudentBo bo) {
         return toAjax(mainStudentService.updateUserType(bo.getId(), bo.getUserType()));
     }
+
+
+
+    /**
+     * 导出单人在线简历 (Excel)
+     */
+    @Log(title = "学员管理", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportResume/{id}")
+    public void exportResume(@PathVariable Long id, HttpServletResponse response) {
+        MainStudentVo vo = mainStudentService.queryById(id);
+        if (vo == null) {
+            throw new ServiceException("学员不存在");
+        }
+        // 包装成单行列表进行导出
+        List<MainStudentVo> list = List.of(vo);
+        ExcelUtil.exportExcel(list, vo.getName() + "_在线简历", MainStudentVo.class, response);
+    }
+
+
+    @Log(title = "学员管理", businessType = BusinessType.EXPORT)
+    @GetMapping("/downloadResume/{studentId}")
+    public void downloadResume(@PathVariable Long studentId, HttpServletResponse response) throws java.io.IOException {
+        MainStudentVo vo = mainStudentService.queryById(studentId);
+        if (vo == null || (vo.getResumeFile() == null && ObjectUtil.isEmpty(vo.getAppendixList()))) {
+            throw new ServiceException("该学员未上传简历附件");
+        }
+
+        List<MainStudentAppendixVo> appendixList = vo.getAppendixList();
+        if (ObjectUtil.isEmpty(appendixList)) {
+            // 兜底逻辑:如果 appendixList 为空但 resumeFile 有值
+            sysOssService.download(vo.getResumeFile(), response);
+            return;
+        }
+
+        if (appendixList.size() == 1) {
+            // 只有一个附件,直接下载原始文件
+            sysOssService.download(appendixList.get(0).getOssId(), response);
+        } else {
+            // 多个附件,打包下载
+            String zipName = vo.getName() + "_简历附件.zip";
+            FileUtils.setAttachmentResponseHeader(response, zipName);
+            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");
+
+            try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
+                for (MainStudentAppendixVo appendix : appendixList) {
+                    SysOssVo sysOss = sysOssService.getById(appendix.getOssId());
+                    if (sysOss != null) {
+                        ZipEntry entry = new ZipEntry(sysOss.getFileName());
+                        zos.putNextEntry(entry);
+                        OssClient storage = OssFactory.instance(sysOss.getService());
+                        // 使用 FilterOutputStream 包装,防止 storage.download 关闭 zos
+                        storage.download(sysOss.getFileName(), new FilterOutputStream(zos) {
+                            @Override
+                            public void close() throws IOException {
+                                // 忽略 close 调用,由外部 ZipOutputStream 控制
+                            }
+                        }, null);
+                        zos.closeEntry();
+                    }
+                }
+                zos.finish();
+            }
+        }
+    }
 }

+ 37 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/MainTrainingLearnRecordController.java

@@ -0,0 +1,37 @@
+package org.dromara.main.controller;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.main.domain.vo.MainTrainingLearnRecordVo;
+import org.dromara.main.service.IMainTrainingLearnRecordService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 培训学习记录控制器
+ *
+ * @author ruoyi
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/main/training-learn-record")
+public class MainTrainingLearnRecordController extends BaseController {
+
+    private final IMainTrainingLearnRecordService learnRecordService;
+
+    /**
+     * 查询学员培训学习记录列表
+     */
+    @GetMapping("/list")
+    public R<List<MainTrainingLearnRecordVo>> list(@NotNull(message = "学员ID不能为空") @RequestParam Long studentId) {
+        return R.ok(learnRecordService.queryRecordListByStudentId(studentId));
+    }
+}

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

@@ -83,4 +83,7 @@ public class CsSession {
      * 会话结束时间
      */
     private LocalDateTime endTime;
+
+
+    private String sourceId;
 }

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

@@ -0,0 +1,76 @@
+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;
+
+/**
+ * 培训学习记录对象 main_training_learn_record
+ *
+ * @author ruoyi
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("main_training_learn_record")
+public class MainTrainingLearnRecord extends BaseEntity {
+
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 关联培训ID(main_training.id)
+     */
+    private Long trainingId;
+
+    /**
+     * 学员ID(关联 main_student.id)
+     */
+    private Long studentId;
+
+    /**
+     * 已学习时长(分钟)
+     */
+    private Integer learnedTime;
+
+    /**
+     * 剩余时长(分钟)
+     */
+    private Integer remainingTime;
+
+    /**
+     * 学习进度(0-100)
+     */
+    private Integer progress;
+
+    /**
+     * 学习状态: 0=进行中, 1=已完成
+     */
+    private Integer learnStatus;
+
+    /**
+     * 完成时间
+     */
+    private Date finishTime;
+
+    /**
+     * 上次学习时间
+     */
+    private Date lastLearnTime;
+
+    /**
+     * 租户编号
+     */
+    private String tenantId;
+
+    /**
+     * 删除标志(0代表存在 1代表删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+}

+ 2 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/CsSessionBo.java

@@ -31,6 +31,8 @@ public class CsSessionBo extends BaseEntity {
      */
     private Long waiterId;
 
+    private String sourceId;
+
 
 
 }

+ 11 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/MainExamEvaluationBo.java

@@ -44,6 +44,17 @@ public class MainExamEvaluationBo extends BaseEntity {
 
     private String status;
 
+
+    /**
+     * 最低价格
+     */
+    private BigDecimal minPrice;
+
+    /**
+     * 最高价格
+     */
+    private BigDecimal maxPrice;
+
     /**
      * 能力配置列表
      */

+ 0 - 2
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/MainPositionBo.java

@@ -57,8 +57,6 @@ public class MainPositionBo implements Serializable {
     private BigDecimal gradeB;
     private BigDecimal gradeC;
 
-
-
     private String keyword;
 
     private Long studentId;

+ 3 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/MainStudentBo.java

@@ -129,4 +129,7 @@ public class MainStudentBo extends BaseEntity {
      */
     private String remark;
 
+
+    private String isBlacklist;
+
 }

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

@@ -104,4 +104,7 @@ public class CsSessionVo implements Serializable {
      */
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private LocalDateTime createTime;
+
+
+    private String sourceId;
 }

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

@@ -55,6 +55,9 @@ public class MainExamEvaluationVo implements Serializable {
     @ExcelProperty(value = "商品相册")
     private String imageAlbum;
 
+    @Translation(type = TransConstant.OSS_ID_TO_URL, mapper = "imageAlbum")
+    private String imageAlbumUrls;
+
     @ExcelProperty(value = "详情")
     private String detail;
 

+ 178 - 1
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/MainStudentVo.java

@@ -1,162 +1,339 @@
 package org.dromara.main.domain.vo;
 
+
+
 import io.github.linpeilie.annotations.AutoMapper;
+
 import lombok.Data;
+
 import org.dromara.common.translation.annotation.Translation;
+
 import org.dromara.common.translation.constant.TransConstant;
+
+import org.dromara.main.domain.MainOrder;
 import org.dromara.main.domain.MainStudent;
+
 import java.io.Serial;
+
 import java.io.Serializable;
+
 import java.math.BigDecimal;
+
 import java.util.Date;
+
 import java.util.List;
 
+
+
 @Data
+
 @AutoMapper(target = MainStudent.class)
+
 public class MainStudentVo implements Serializable {
 
+
+
     @Serial
+
     private static final long serialVersionUID = 1L;
 
+
+
     /**
+
      * 学员ID
+
      */
+
     private Long id;
 
+
+
     /**
+
      * 学员展示编号(如U2023071567)
+
      */
+
     private String studentNo;
 
+
+
     /**
+
      * 学员姓名
+
      */
+
     private String name;
 
+
+
     /**
+
      * 联络手机号
+
      */
+
     private String mobile;
 
+
+
     /**
+
      * 电子邮箱
+
      */
+
     private String email;
 
+
+
     /**
+
      * 证件号码
+
      */
+
     private String idCardNumber;
 
+
+
     /**
+
      * 性别(0男 1女 2未知)
+
      */
+
     private String gender;
 
+    @Translation(type = TransConstant.DICT_TYPE_TO_LABEL, mapper = "gender", other = "sys_user_sex")
+    private String genderLabel;
+
     /**
+
      * 头像ID(关联oss表)
+
      */
+
     private Long avatar;
 
     @Translation(type=TransConstant.OSS_ID_TO_URL,mapper="avatar")
+
     private String avatarUrl;
 
     /**
-     * 用户类型(1付费用户 2普通用户 3黑名单)
+
+     * 用户类型(1付费用户 2普通用户 3黑名单)
+
      */
+
     private String userType;
 
     @Translation(type = TransConstant.DICT_TYPE_TO_LABEL, mapper = "userType", other = "main_student_type")
     private String userTypeLabel;
 
     /**
+
      * 历史累计消费金额
+
      */
+
     private BigDecimal totalAmount;
 
     /**
+
      * 求职状态/到岗时间(如:一周内到岗)
+
      */
+
     private String availability;
 
+    @Translation(type = TransConstant.DICT_TYPE_TO_LABEL, mapper = "availability", other = "main_arrivail_time")
+    private String availabilityLabel;
+
     /**
+
      * 求职意向(岗位ID或名称,逗号分隔)
+
      */
+
     private String jobIntention;
 
     /**
+
      * 意向公司列表(公司名称,逗号分隔)
+
      */
+
     private String intentionCompanies;
 
     /**
+
      * 建议类型(1全职 2实习 3兼职)
+
      */
+
     private String jobType;
 
+    @Translation(type = TransConstant.DICT_TYPE_TO_LABEL, mapper = "jobType", other = "main_position_type")
+    private String jobTypeLabel;
+
     /**
+
      * 毕业/就读院校
+
      */
+
     private String schoolName;
 
     /**
+
      * 学历要求(字典:main_education)
+
      */
+
     private String education;
 
+    @Translation(type = TransConstant.DICT_TYPE_TO_LABEL, mapper = "education", other = "main_education")
+    private String educationLabel;
+
     /**
+
      * 当前年级(字典:main_experience)
+
      */
+
     private String grade;
 
+    @Translation(type = TransConstant.DICT_TYPE_TO_LABEL, mapper = "grade", other = "main_experience")
+    private String gradeLabel;
+
     /**
+
      * 实习时长(字典:main_internship_duration)
+
      */
+
     private String internshipDuration;
 
+    @Translation(type = TransConstant.DICT_TYPE_TO_LABEL, mapper = "internshipDuration", other = "main_internship_duration")
+    private String internshipDurationLabel;
+
     /**
+
      * 个人简历附件ID(关联oss表)
+
      */
+
     private Long resumeFile;
 
+
+
     /**
+
      * 账号状态(0正常 1停用)
+
      */
+
     private String status;
 
+
+
     /**
+
      * 最后登录时间
+
      */
+
     private Date loginDate;
 
+
+
     /**
+
      * 创建日期
+
      */
+
     private Date createTime;
 
+
+
     /**
+
      * 最后信息更新时间
+
      */
+
     private Date updateTime;
 
+
+
     /**
+
      * 备注信息
+
      */
+
     private String remark;
 
+
+
     /**
+
      * 教育经历列表
+
      */
+
     private List<MainStudentEducationVo> educationList;
 
+
+
     /**
+
      * 工作经历列表
+
      */
+
     private List<MainStudentExperienceVo> experienceList;
 
+
+
     /**
+
      * 项目经历列表
+
      */
+
     private List<MainStudentProjectVo> projectList;
 
+    /**
+     * 咨询数
+     */
+    private Integer consultationCount;
+
+    /**
+     * 测评数
+     */
+    private Integer evaluationCount;
+
+    /**
+     * 是否入职 (0否 1是)
+     */
+    private String isEmployed;
+    /**
+     * 最近订单列表
+     */
+    private List<MainOrder> orderList;
+
+    /**
+     * 简历附件列表
+     */
+    private List<MainStudentAppendixVo> appendixList;
+
+
+
+
 }
+

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

@@ -0,0 +1,41 @@
+package org.dromara.main.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.main.domain.MainTrainingLearnRecord;
+import org.dromara.main.domain.vo.MainTrainingLearnRecordVo;
+
+import java.util.List;
+
+/**
+ * 培训学习记录Mapper接口
+ *
+ * @author ruoyi
+ */
+@Mapper
+public interface MainTrainingLearnRecordMapper extends BaseMapperPlus<MainTrainingLearnRecord, MainTrainingLearnRecordVo> {
+
+    /**
+     * 查询学员视频培训学习记录 (从学员角度查看培训列表)
+     * 这里将培训名称映射到 VO 的 name 字段,以便前端统一显示
+     */
+    @Select("""
+        SELECT r.id,
+               r.training_id AS trainingId,
+               t.name AS name,
+               r.student_id AS studentId,
+               r.learned_time AS learnedTime,
+               r.remaining_time AS remainingTime,
+               r.progress AS progress,
+               r.finish_time AS finishTime,
+               r.last_learn_time AS lastLearnTime
+          FROM main_training_learn_record r
+          LEFT JOIN main_training t ON t.id = r.training_id AND t.del_flag = '0'
+         WHERE r.student_id = #{studentId}
+           AND r.del_flag = '0'
+         ORDER BY r.last_learn_time DESC
+        """)
+    List<MainTrainingLearnRecordVo> selectRecordListByStudentId(@Param("studentId") Long studentId);
+}

+ 2 - 1
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/ICsSessionService.java

@@ -13,7 +13,8 @@ public interface ICsSessionService {
      * 创建或获取会话
      */
     CsSessionVo createOrGetSession(Integer sessionType, Long fromUserId,
-                                   String fromUserName, String fromUserAvatar);
+                                   String fromUserName, String fromUserAvatar,
+                                   String sourceId);
 
     /**
      * 查询会话列表

+ 5 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainOrderService.java

@@ -44,6 +44,11 @@ public interface IMainOrderService {
      */
     Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
 
+    /**
+     * 获取订单状态统计
+     */
+    java.util.Map<String, Long> getStatusCount();
+
 //    WxPayMpOrderResult jsapi(Long orderId) throws WxPayException;
 
 }

+ 2 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainPositionService.java

@@ -1,5 +1,6 @@
 package org.dromara.main.service;
 
+import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.IService;
 import org.dromara.common.mybatis.core.page.PageQuery;
@@ -12,6 +13,7 @@ public interface IMainPositionService extends IService<MainPosition> {
     /**
      * 查询岗位分页列表
      */
+    @InterceptorIgnore(tenantLine = "true")
     Page<MainPositionVo> queryPageList(MainPositionBo bo, PageQuery pageQuery);
 
     /**

+ 20 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainTrainingLearnRecordService.java

@@ -0,0 +1,20 @@
+package org.dromara.main.service;
+
+import org.dromara.main.domain.vo.MainTrainingLearnRecordVo;
+
+import java.util.List;
+
+/**
+ * 培训学习记录Service接口
+ *
+ * @author ruoyi
+ */
+public interface IMainTrainingLearnRecordService {
+
+    /**
+     * 查询学员培训学习记录列表
+     * @param studentId 学员ID
+     * @return 学习记录列表
+     */
+    List<MainTrainingLearnRecordVo> queryRecordListByStudentId(Long studentId);
+}

+ 23 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/CsMessageServiceImpl.java

@@ -94,6 +94,8 @@ public class CsMessageServiceImpl implements ICsMessageService {
 
         CsMessageVo vo = converter.convert(message, CsMessageVo.class);
         messagingTemplate.convertAndSend("/topic/session/" + bo.getSessionId(), vo);
+        notifyWaiter(vo);
+
         return vo;
     }
 
@@ -133,6 +135,8 @@ public class CsMessageServiceImpl implements ICsMessageService {
 
             CsMessageVo vo = converter.convert(message, CsMessageVo.class);
             messagingTemplate.convertAndSend("/topic/session/" + sessionId, vo);
+            notifyWaiter(vo);
+
             return vo;
         } catch (Exception e) {
             throw new RuntimeException("上传图片失败: " + e.getMessage(), e);
@@ -175,6 +179,8 @@ public class CsMessageServiceImpl implements ICsMessageService {
 
             CsMessageVo vo = converter.convert(message, CsMessageVo.class);
             messagingTemplate.convertAndSend("/topic/session/" + sessionId, vo);
+            notifyWaiter(vo);
+
             return vo;
         } catch (Exception e) {
             throw new RuntimeException("上传文件失败: " + e.getMessage(), e);
@@ -201,6 +207,8 @@ public class CsMessageServiceImpl implements ICsMessageService {
 
         CsMessageVo vo = converter.convert(message, CsMessageVo.class);
         messagingTemplate.convertAndSend("/topic/session/" + bo.getSessionId(), vo);
+        notifyWaiter(vo);
+
         return vo;
     }
 
@@ -316,4 +324,19 @@ public class CsMessageServiceImpl implements ICsMessageService {
         return sysUserMapper.selectBatchIds(waiterIds).stream()
             .collect(Collectors.toMap(SysUser::getUserId, Function.identity()));
     }
+
+    private void notifyWaiter(CsMessageVo vo) {
+        if (vo == null || vo.getSessionId() == null) {
+            return;
+        }
+        CsSession session = sessionMapper.selectById(vo.getSessionId());
+        if (session != null && session.getWaiterId() != null) {
+            // 推送到客服个人的实时通知队列
+            messagingTemplate.convertAndSendToUser(
+                session.getWaiterId().toString(),
+                "/queue/notify",
+                vo
+            );
+        }
+    }
 }

+ 13 - 3
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/CsSessionServiceImpl.java

@@ -48,12 +48,21 @@ public class CsSessionServiceImpl implements ICsSessionService {
 
     @Override
     public CsSessionVo createOrGetSession(Integer sessionType, Long fromUserId,
-                                          String fromUserName, String fromUserAvatar) {
+                                          String fromUserName, String fromUserAvatar,
+                                          String sourceId) {
         LambdaQueryWrapper<CsSession> lqw = Wrappers.lambdaQuery();
         lqw.eq(CsSession::getSessionType, sessionType)
             .eq(CsSession::getFromUserId, fromUserId)
-            .eq(CsSession::getStatus, 1)
-            .orderByDesc(CsSession::getCreateTime)
+            .eq(CsSession::getStatus, 1);
+
+        if (StringUtils.isNotBlank(sourceId)) {
+            lqw.eq(CsSession::getSourceId, sourceId);
+        } else {
+            // 如果没传 sourceId(比如从个人中心进),优先找 sourceId 为空的通用会话
+            lqw.isNull(CsSession::getSourceId);
+        }
+
+        lqw.orderByDesc(CsSession::getCreateTime)
             .last("LIMIT 1");
 
         CsSession existSession = baseMapper.selectOne(lqw);
@@ -68,6 +77,7 @@ public class CsSessionServiceImpl implements ICsSessionService {
         newSession.setFromUserName(fromUserName);
         newSession.setFromUserAvatar(fromUserAvatar);
         newSession.setStatus(1);
+        newSession.setSourceId(sourceId);
         newSession.setUnreadCount(0);
         newSession.setCreateTime(LocalDateTime.now());
         assignSeatAndWaiter(newSession);

+ 52 - 16
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainExamEvaluationServiceImpl.java

@@ -83,11 +83,49 @@ public class MainExamEvaluationServiceImpl implements IMainExamEvaluationService
 
     private LambdaQueryWrapper<MainExamEvaluation> buildQueryWrapper(MainExamEvaluationBo bo) {
         LambdaQueryWrapper<MainExamEvaluation> lqw = Wrappers.lambdaQuery();
-        lqw.like(StringUtils.isNotBlank(bo.getEvaluationName()), MainExamEvaluation::getEvaluationName, bo.getEvaluationName());
+
+        // 1. 测评名称搜索(移除 ID 匹配)
+        lqw.like(StringUtils.isNotBlank(bo.getEvaluationName()),
+            MainExamEvaluation::getEvaluationName, bo.getEvaluationName());
+
         lqw.eq(StringUtils.isNotBlank(bo.getGrade()), MainExamEvaluation::getGrade, bo.getGrade());
         lqw.eq(StringUtils.isNotBlank(bo.getPosition()), MainExamEvaluation::getPosition, bo.getPosition());
         lqw.eq(StringUtils.isNotBlank(bo.getPositionType()), MainExamEvaluation::getPositionType, bo.getPositionType());
-        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), MainExamEvaluation::getStatus, bo.getStatus());
+
+        // 2. 业务状态搜索逻辑
+        if (StringUtils.isNotBlank(bo.getStatus())) {
+            Date now = new Date();
+            switch (bo.getStatus()) {
+                case "0": // 草稿
+                    lqw.eq(MainExamEvaluation::getStatus, "0");
+                    break;
+                case "1": // 在售 (状态为发布 且 在上架时间内)
+                    lqw.eq(MainExamEvaluation::getStatus, "1")
+                        .and(w -> w.isNull(MainExamEvaluation::getOnTime).or().le(MainExamEvaluation::getOnTime, now))
+                        .and(w -> w.isNull(MainExamEvaluation::getDownTime).or().ge(MainExamEvaluation::getDownTime, now));
+                    break;
+                case "2": // 已下架 (手动下架 或 已过下架时间)
+                    lqw.and(w -> w.eq(MainExamEvaluation::getStatus, "2")
+                        .or(w2 -> w2.eq(MainExamEvaluation::getStatus, "1")
+                            .isNotNull(MainExamEvaluation::getDownTime)
+                            .lt(MainExamEvaluation::getDownTime, now)));
+                    break;
+            }
+        }
+
+        // 3. 自定义价格区间搜索
+        lqw.ge(bo.getMinPrice() != null, MainExamEvaluation::getPrice, bo.getMinPrice());
+        lqw.le(bo.getMaxPrice() != null, MainExamEvaluation::getPrice, bo.getMaxPrice());
+
+        // 4. 创建时间范围搜索
+        Map<String, Object> params = bo.getParams();
+        if (params != null) {
+            Object beginTime = params.get("beginTime");
+            Object endTime = params.get("endTime");
+            lqw.between(beginTime != null && endTime != null,
+                MainExamEvaluation::getCreateTime, beginTime, endTime);
+        }
+
         lqw.orderByDesc(MainExamEvaluation::getCreateTime);
         return lqw;
     }
@@ -124,22 +162,18 @@ public class MainExamEvaluationServiceImpl implements IMainExamEvaluationService
 
     private void saveMainAbilityConfigs(Long evaluationId, List<MainAbilityConfig> configs) {
         if (!CollectionUtils.isEmpty(configs)) {
-            Map<String, MainAbilityConfig> distinctConfigMap = new LinkedHashMap<>();
-            for (MainAbilityConfig config : configs) {
-                if (config == null || StringUtils.isBlank(config.getAbilityName()) || config.getThirdExamInfoId() == null) {
-                    continue;
-                }
-                String distinctKey = config.getAbilityName().trim() + "_" + config.getThirdExamInfoId();
-                distinctConfigMap.putIfAbsent(distinctKey, config);
-            }
-            List<MainAbilityConfig> distinctConfigs = distinctConfigMap.values().stream().toList();
-            for (int i = 0; i < distinctConfigs.size(); i++) {
-                MainAbilityConfig config = distinctConfigs.get(i);
+            // 过滤掉完全没填名称的项,但不强制要求必须关联试卷(允许后续编辑)
+            List<MainAbilityConfig> validConfigs = configs.stream()
+                .filter(c -> c != null && StringUtils.isNotBlank(c.getAbilityName()))
+                .collect(Collectors.toList());
+
+            for (int i = 0; i < validConfigs.size(); i++) {
+                MainAbilityConfig config = validConfigs.get(i);
                 config.setEvaluationId(evaluationId);
                 config.setSortOrder(i);
             }
-            if (!distinctConfigs.isEmpty()) {
-                mainAbilityConfigMapper.insertBatch(distinctConfigs);
+            if (!validConfigs.isEmpty()) {
+                mainAbilityConfigMapper.insertBatch(validConfigs);
             }
         }
     }
@@ -402,10 +436,12 @@ public class MainExamEvaluationServiceImpl implements IMainExamEvaluationService
     private void decodeDetailFieldInBo(MainExamEvaluationBo bo) {
         if (StringUtils.isNotBlank(bo.getDetail())) {
             try {
+                // 现在的逻辑:前端强制 Base64 编码,后端强制解码,不再猜测
                 String decoded = new String(Base64.getDecoder().decode(bo.getDetail()), StandardCharsets.UTF_8);
                 bo.setDetail(decoded);
             } catch (Exception e) {
-                log.warn("详情字段Base64解码失败,使用原始值", e);
+                // 如果解码失败,说明传来的不是 Base64(可能是旧数据或异常),记录日志并保持原样
+                log.error("详情字段解码异常,内容可能是原始 HTML。内容长度: {}", bo.getDetail().length());
             }
         }
     }

+ 12 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainOrderServiceImpl.java

@@ -163,6 +163,18 @@ public class MainOrderServiceImpl implements IMainOrderService {
         return baseMapper.deleteByIds(ids) > 0;
     }
 
+    @Override
+    public Map<String, Long> getStatusCount() {
+        Map<String, Long> map = new java.util.HashMap<>();
+        // 0:待付款, 1:已完成, 2:已关闭, 3:售后中
+        map.put("all", baseMapper.selectCount(Wrappers.lambdaQuery()));
+        map.put("0", baseMapper.selectCount(Wrappers.<MainOrder>lambdaQuery().eq(MainOrder::getOrderStatus, 0)));
+        map.put("1", baseMapper.selectCount(Wrappers.<MainOrder>lambdaQuery().eq(MainOrder::getOrderStatus, 1)));
+        map.put("2", baseMapper.selectCount(Wrappers.<MainOrder>lambdaQuery().eq(MainOrder::getOrderStatus, 2)));
+        map.put("3", baseMapper.selectCount(Wrappers.<MainOrder>lambdaQuery().eq(MainOrder::getOrderStatus, 3)));
+        return map;
+    }
+
 //    @Override
 //    public WxPayMpOrderResult jsapi(Long orderId) throws WxPayException {
 //        MainOrder order = orderMapper.selectById(orderId);

+ 9 - 3
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainPositionServiceImpl.java

@@ -15,6 +15,7 @@ import org.dromara.main.domain.vo.MainPositionVo;
 import org.dromara.main.mapper.MainPositionMapper;
 import org.dromara.main.service.IMainPositionService;
 import org.dromara.main.service.IMainStudentDislikeService;
+import org.dromara.system.domain.SysTenant;
 import org.dromara.system.domain.vo.SysTenantVo;
 import org.dromara.system.mapper.SysTenantMapper;
 import org.springframework.stereotype.Service;
@@ -56,7 +57,7 @@ public class MainPositionServiceImpl extends ServiceImpl<MainPositionMapper, Mai
         // 批量补全企业名称(根据 tenantId 查询 sys_tenant)
         if (result.getTotal() > 0) {
             result.getRecords().forEach(vo -> {
-                SysTenantVo tenant = tenantMapper.selectVoOne(new LambdaQueryWrapper<org.dromara.system.domain.SysTenant>().eq(org.dromara.system.domain.SysTenant::getTenantId, vo.getTenantId()));
+                SysTenantVo tenant = tenantMapper.selectVoOne(new LambdaQueryWrapper<SysTenant>().eq(SysTenant::getTenantId, vo.getTenantId()));
                 if (tenant != null) {
                     vo.setCompanyName(tenant.getCompanyName());
                 } else if ("000000".equals(vo.getTenantId())) {
@@ -123,14 +124,19 @@ public class MainPositionServiceImpl extends ServiceImpl<MainPositionMapper, Mai
         lqw.eq(StringUtils.isNotBlank(bo.getSchoolRequirement()),MainPosition::getSchoolRequirement,bo.getSchoolRequirement());
 //        经验要求筛选
         lqw.eq(StringUtils.isNotBlank(bo.getGradeRequirement()),MainPosition::getGradeRequirement,bo.getGradeRequirement());
-//
+//        薪资要求查询
+        lqw.ge(bo.getMinSalary() != null, MainPosition::getMinSalary, bo.getMinSalary());
+        // le: 岗位最大薪资 <= 用户要求的最大薪资
+        lqw.le(bo.getMaxSalary() != null, MainPosition::getMaxSalary, bo.getMaxSalary());
+
+
         lqw.eq(StringUtils.isNotBlank(bo.getWorkProvince()), MainPosition::getWorkProvince, bo.getWorkProvince());
         lqw.eq(StringUtils.isNotBlank(bo.getWorkCity()), MainPosition::getWorkCity, bo.getWorkCity());
         lqw.eq(StringUtils.isNotBlank(bo.getPostLevel()), MainPosition::getPostLevel, bo.getPostLevel());
         lqw.eq(bo.getIsUrgent() != null, MainPosition::getIsUrgent, bo.getIsUrgent());
 
         // 排序规则
-        lqw.orderByDesc(MainPosition::getCreateTime);
+//        lqw.orderByDesc(MainPosition::getCreateTime);
         return lqw;
     }
 

+ 85 - 8
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainStudentServiceImpl.java

@@ -16,18 +16,13 @@ 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.MainStudent;
-import org.dromara.main.domain.MainStudentEducation;
-import org.dromara.main.domain.MainStudentExperience;
-import org.dromara.main.domain.MainStudentProject;
+import org.dromara.main.domain.*;
 import org.dromara.main.domain.bo.MainStudentBo;
+import org.dromara.main.domain.vo.MainStudentAppendixVo;
 import org.dromara.main.domain.vo.MainStudentVo;
 
 import org.dromara.main.domain.vo.MiniappLoginVo;
-import org.dromara.main.mapper.MainStudentEducationMapper;
-import org.dromara.main.mapper.MainStudentExperienceMapper;
-import org.dromara.main.mapper.MainStudentMapper;
-import org.dromara.main.mapper.MainStudentProjectMapper;
+import org.dromara.main.mapper.*;
 import org.dromara.main.service.IMainStudentService;
 import org.dromara.main.utils.WechatMiniAppUtil;
 import org.springframework.stereotype.Service;
@@ -46,17 +41,74 @@ public class MainStudentServiceImpl implements IMainStudentService {
     private final MainStudentEducationMapper educationMapper;
     private final MainStudentProjectMapper projectMapper;
     private final MainStudentExperienceMapper experienceMapper;
+    private final MainStudentAppendixMapper appendixMapper;
+    private final CsSessionMapper sessionMapper;
+    private final MainOrderMapper orderMapper;
+    private final MainExamApplyMapper examApplyMapper;
+    private final MainBackCandidateMapper candidateMapper;
 
     @Override
     public MainStudentVo queryById(Long id){
         MainStudentVo vo = baseMapper.selectVoById(id);
         if(vo != null){
+            // 数据标准化:确保性别等字段与字典对应
+            if ("F".equalsIgnoreCase(vo.getGender())) {
+                vo.setGender("1"); // 字典中通常 1 为女
+            } else if ("M".equalsIgnoreCase(vo.getGender())) {
+                vo.setGender("0"); // 字典中通常 0 为男
+            }
+            
+            // 确保所有字典字段为字符串,匹配字典 key
+            if (vo.getUserType() != null) vo.setUserType(String.valueOf(vo.getUserType()));
+            if (vo.getEducation() != null) vo.setEducation(String.valueOf(vo.getEducation()));
+            if (vo.getGrade() != null) vo.setGrade(String.valueOf(vo.getGrade()));
+            if (vo.getJobType() != null) vo.setJobType(String.valueOf(vo.getJobType()));
+            if (vo.getInternshipDuration() != null) vo.setInternshipDuration(String.valueOf(vo.getInternshipDuration()));
+            if (vo.getAvailability() != null) vo.setAvailability(String.valueOf(vo.getAvailability()));
+
             vo.setEducationList(educationMapper.selectVoList(new LambdaQueryWrapper<MainStudentEducation>()
                     .eq(MainStudentEducation::getStudentId, id)));
             vo.setExperienceList(experienceMapper.selectVoList(new LambdaQueryWrapper<MainStudentExperience>()
                     .eq(MainStudentExperience::getStudentId, id)));
             vo.setProjectList(projectMapper.selectVoList(new LambdaQueryWrapper<MainStudentProject>()
                     .eq(MainStudentProject::getStudentId, id)));
+
+            // 查询所有简历附件
+            List<MainStudentAppendixVo> appendixList = appendixMapper.selectVoList(new LambdaQueryWrapper<MainStudentAppendix>()
+                .eq(MainStudentAppendix::getStudentId, id)
+                .orderByDesc(MainStudentAppendix::getCreateTime));
+            vo.setAppendixList(appendixList);
+
+            // 为兼容旧逻辑,设置最新的附件 ID 到 resumeFile
+            if (ObjectUtil.isNotEmpty(appendixList)) {
+                vo.setResumeFile(appendixList.get(0).getOssId());
+            }
+
+            // 2. 统计咨询数 (从 cs_session 表查询)
+            Long consultationCount = sessionMapper.selectCount(new LambdaQueryWrapper<CsSession>()
+                .eq(CsSession::getFromUserId, id)
+                .eq(CsSession::getSessionType, 1));
+            vo.setConsultationCount(consultationCount.intValue());
+
+            // 3. 统计测评数 (从 main_exam_apply 表查询)
+            Long evaluationCount = examApplyMapper.selectCount(new LambdaQueryWrapper<MainExamApply>()
+                .eq(MainExamApply::getStudentId, id));
+            vo.setEvaluationCount(evaluationCount.intValue());
+
+            // 4. 查询是否入职 (从 main_back_candidate 表查询:双方确认状态即为入职)
+            Long employedCount = candidateMapper.selectCount(new LambdaQueryWrapper<MainBackCandidate>()
+                .eq(MainBackCandidate::getStudentId, id)
+                .eq(MainBackCandidate::getEnterpriseStatus, "adopted") // 企业录用
+                .eq(MainBackCandidate::getStudentStatus, "accepted")); // 学员接受
+            vo.setIsEmployed(employedCount > 0 ? "1" : "0");
+
+            // 5. 查询最近3条订单 (直接使用 MainOrder 实体类)
+            List<MainOrder> orders = orderMapper.selectList(new LambdaQueryWrapper<MainOrder>()
+                .eq(MainOrder::getBuyerId, id)
+                .eq(MainOrder::getBuyerType, 2) // 2表示学员
+                .orderByDesc(MainOrder::getCreateTime)
+                .last("LIMIT 3"));
+            vo.setOrderList(orders);
         }
         return vo;
     }
@@ -65,6 +117,19 @@ public class MainStudentServiceImpl implements IMainStudentService {
     public TableDataInfo<MainStudentVo> queryPageList(MainStudentBo bo, PageQuery pageQuery) {
         LambdaQueryWrapper<MainStudent> lqw = buildQueryWrapper(bo);
         Page<MainStudentVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+
+        List<MainStudentVo> records = result.getRecords();
+        if (ObjectUtil.isNotEmpty(records)) {
+            List<Long> studentIds = records.stream().map(MainStudentVo::getId).toList();
+            // 批量查询测评数
+            // 注意:这里需要根据您的业务逻辑,通过 examApplyMapper 或对应的 SQL 统计出每个学员的测评数并填充
+            // 简单示例如下:
+            records.forEach(vo -> {
+                Long count = examApplyMapper.selectCount(new LambdaQueryWrapper<MainExamApply>()
+                    .eq(MainExamApply::getStudentId, vo.getId()));
+                vo.setEvaluationCount(count.intValue());
+            });
+        }
         return TableDataInfo.build(result);
     }
 
@@ -88,6 +153,14 @@ public class MainStudentServiceImpl implements IMainStudentService {
         lqw.like(ObjectUtil.isNotNull(bo.getSchoolName()), MainStudent::getSchoolName, bo.getSchoolName());
         lqw.eq(ObjectUtil.isNotNull(bo.getEducation()), MainStudent::getEducation, bo.getEducation());
         lqw.eq(ObjectUtil.isNotNull(bo.getStatus()), MainStudent::getStatus, bo.getStatus());
+        // 新增:处理黑名单筛选逻辑
+        if (StringUtils.isNotBlank(bo.getIsBlacklist())) {
+            if ("Y".equals(bo.getIsBlacklist())) {
+                lqw.eq(MainStudent::getUserType, "3"); // 3 为黑名单用户类型
+            } else if ("N".equals(bo.getIsBlacklist())) {
+                lqw.ne(MainStudent::getUserType, "3"); // 排除黑名单用户
+            }
+        }
         return lqw;
     }
 
@@ -214,6 +287,10 @@ public class MainStudentServiceImpl implements IMainStudentService {
             .setExtra(LoginHelper.USER_KEY, student.getId())
             .setExtra(LoginHelper.USER_NAME_KEY, student.getName());
         StpUtil.login(student.getId(), loginModel);
+        MainStudent loginUpdate = new MainStudent();
+        loginUpdate.setId(student.getId());
+        loginUpdate.setLoginDate(new java.util.Date()); // 设置当前时间为最近登录时间
+        baseMapper.updateById(loginUpdate);
         String token = StpUtil.getTokenValue();
         // 5. 组合组装返回结果
         MiniappLoginVo result = new MiniappLoginVo();

+ 26 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainTrainingLearnRecordServiceImpl.java

@@ -0,0 +1,26 @@
+package org.dromara.main.service.impl;
+
+import lombok.RequiredArgsConstructor;
+import org.dromara.main.domain.vo.MainTrainingLearnRecordVo;
+import org.dromara.main.mapper.MainTrainingLearnRecordMapper;
+import org.dromara.main.service.IMainTrainingLearnRecordService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 培训学习记录Service业务层处理
+ *
+ * @author ruoyi
+ */
+@RequiredArgsConstructor
+@Service
+public class MainTrainingLearnRecordServiceImpl implements IMainTrainingLearnRecordService {
+
+    private final MainTrainingLearnRecordMapper baseMapper;
+
+    @Override
+    public List<MainTrainingLearnRecordVo> queryRecordListByStudentId(Long studentId) {
+        return baseMapper.selectRecordListByStudentId(studentId);
+    }
+}