Browse Source

Merge branch 'refs/heads/master_feat_statistics_20250521'

xdd 2 weeks ago
parent
commit
deff2b15b3
53 changed files with 2074 additions and 80 deletions
  1. 1 1
      fs-admin/src/main/java/com/fs/course/controller/FsCourseQuestionBankController.java
  2. 1 2
      fs-admin/src/main/java/com/fs/course/controller/FsUserCoursePeriodController.java
  3. 86 0
      fs-admin/src/main/java/com/fs/stats/SalesWatchStatisController.java
  4. 34 26
      fs-admin/src/test/java/com/fs/task/StoreTaskTest.java
  5. 6 6
      fs-company/src/main/java/com/fs/company/controller/CompanyRoleController.java
  6. 3 0
      fs-service-system/src/main/java/com/fs/company/mapper/CompanyMapper.java
  7. 13 0
      fs-service-system/src/main/java/com/fs/company/mapper/CompanyUserMapper.java
  8. 3 0
      fs-service-system/src/main/java/com/fs/company/service/ICompanyService.java
  9. 9 3
      fs-service-system/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  10. 27 0
      fs-service-system/src/main/java/com/fs/company/vo/CompanyDataVO.java
  11. 8 0
      fs-service-system/src/main/java/com/fs/course/cache/FsUserCourseCategoryCacheService.java
  12. 33 0
      fs-service-system/src/main/java/com/fs/course/cache/impl/FsUserCourseCategoryCacheServiceImpl.java
  13. 28 9
      fs-service-system/src/main/java/com/fs/course/domain/FsCourseQuestionBank.java
  14. 3 0
      fs-service-system/src/main/java/com/fs/course/dto/FsCourseQuestionBankImportDTO.java
  15. 4 0
      fs-service-system/src/main/java/com/fs/course/mapper/FsCourseQuestionBankMapper.java
  16. 12 1
      fs-service-system/src/main/java/com/fs/course/mapper/FsUserCourseCategoryMapper.java
  17. 6 0
      fs-service-system/src/main/java/com/fs/course/mapper/FsUserCoursePeriodMapper.java
  18. 4 0
      fs-service-system/src/main/java/com/fs/course/service/IFsCourseQuestionBankService.java
  19. 13 0
      fs-service-system/src/main/java/com/fs/course/service/cache/IFsCourseQuestionBankCacheService.java
  20. 36 0
      fs-service-system/src/main/java/com/fs/course/service/cache/impl/FsUserCourseQuestionBankCacheServiceImpl.java
  21. 94 5
      fs-service-system/src/main/java/com/fs/course/service/impl/FsCourseQuestionBankServiceImpl.java
  22. 5 0
      fs-service-system/src/main/java/com/fs/qw/mapper/QwUserMapper.java
  23. 23 11
      fs-service-system/src/main/java/com/fs/qw/service/impl/AsyncSopTestService.java
  24. 7 0
      fs-service-system/src/main/java/com/fs/sop/mapper/QwSopLogsMapper.java
  25. 1 0
      fs-service-system/src/main/java/com/fs/sop/mapper/QwSopMapper.java
  26. 2 0
      fs-service-system/src/main/java/com/fs/sop/mapper/SopUserLogsMapper.java
  27. 4 0
      fs-service-system/src/main/java/com/fs/sop/service/IQwSopService.java
  28. 1 0
      fs-service-system/src/main/java/com/fs/sop/service/impl/QwSopLogsServiceImpl.java
  29. 38 16
      fs-service-system/src/main/java/com/fs/sop/service/impl/QwSopServiceImpl.java
  30. 28 0
      fs-service-system/src/main/java/com/fs/sop/vo/QwSopTask.java
  31. 32 0
      fs-service-system/src/main/java/com/fs/sop/vo/QwSopTaskDetail.java
  32. 104 0
      fs-service-system/src/main/java/com/fs/statis/domain/FsStatisEveryDayWatch.java
  33. 107 0
      fs-service-system/src/main/java/com/fs/statis/domain/FsStatisPeriodWatch.java
  34. 112 0
      fs-service-system/src/main/java/com/fs/statis/domain/FsStatisSalerWatch.java
  35. 30 0
      fs-service-system/src/main/java/com/fs/statis/dto/StatsWatchLogPageListDTO.java
  36. 91 0
      fs-service-system/src/main/java/com/fs/statis/mapper/FsStatisEveryDayWatchMapper.java
  37. 99 0
      fs-service-system/src/main/java/com/fs/statis/mapper/FsStatisPeriodWatchMapper.java
  38. 86 0
      fs-service-system/src/main/java/com/fs/statis/mapper/FsStatisSalerWatchMapper.java
  39. 47 0
      fs-service-system/src/main/java/com/fs/statis/service/FsStatisEveryDayWatchService.java
  40. 56 0
      fs-service-system/src/main/java/com/fs/statis/service/FsStatisPeriodWatchService.java
  41. 59 0
      fs-service-system/src/main/java/com/fs/statis/service/FsStatisSalerWatchService.java
  42. 71 0
      fs-service-system/src/main/java/com/fs/statis/service/impl/FsStatisEveryDayWatchServiceImpl.java
  43. 82 0
      fs-service-system/src/main/java/com/fs/statis/service/impl/FsStatisPeriodWatchServiceImpl.java
  44. 175 0
      fs-service-system/src/main/java/com/fs/statis/service/impl/FsStatisSalerWatchServiceImpl.java
  45. 3 0
      fs-service-system/src/main/resources/mapper/company/CompanyMapper.xml
  46. 23 0
      fs-service-system/src/main/resources/mapper/company/CompanyUserMapper.xml
  47. 29 0
      fs-service-system/src/main/resources/mapper/course/FsCourseQuestionBankMapper.xml
  48. 17 0
      fs-service-system/src/main/resources/mapper/sop/QwSopLogsMapper.xml
  49. 3 0
      fs-service-system/src/main/resources/mapper/sop/QwSopMapper.xml
  50. 7 0
      fs-service-system/src/main/resources/mapper/sop/SopUserLogsMapper.xml
  51. 162 0
      fs-service-system/src/main/resources/mapper/statis/FsStatisEveryDayMapper.xml
  52. 60 0
      fs-service-system/src/main/resources/mapper/statis/FsStatisPeriodWatchMapper.xml
  53. 86 0
      fs-service-system/src/main/resources/mapper/statis/FsStatisSalerWatchMapper.xml

+ 1 - 1
fs-admin/src/main/java/com/fs/course/controller/FsCourseQuestionBankController.java

@@ -57,7 +57,7 @@ public class FsCourseQuestionBankController extends BaseController
     @GetMapping("/export")
     public AjaxResult export(FsCourseQuestionBank fsCourseQuestionBank)
     {
-        List<FsCourseQuestionBank> list = fsCourseQuestionBankService.selectFsCourseQuestionBankList(fsCourseQuestionBank);
+        List<FsCourseQuestionBank> list = fsCourseQuestionBankService.selectFsCourseQuestionBankListExport(fsCourseQuestionBank);
         ExcelUtil<FsCourseQuestionBank> util = new ExcelUtil<FsCourseQuestionBank>(FsCourseQuestionBank.class);
         return util.exportExcel(list, "题库数据");
     }

+ 1 - 2
fs-admin/src/main/java/com/fs/course/controller/FsUserCoursePeriodController.java

@@ -94,8 +94,7 @@ public class FsUserCoursePeriodController extends BaseController {
      */
     @PreAuthorize("@ss.hasPermi('course:period:query')")
     @GetMapping(value = "/{periodId}")
-    public AjaxResult getInfo(@PathVariable("periodId") Long periodId)
-    {
+    public AjaxResult getInfo(@PathVariable("periodId") Long periodId) {
         return AjaxResult.success(fsUserCoursePeriodService.selectFsUserCoursePeriodById(periodId));
     }
 

+ 86 - 0
fs-admin/src/main/java/com/fs/stats/SalesWatchStatisController.java

@@ -0,0 +1,86 @@
+package com.fs.stats;
+
+import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.company.mapper.CompanyMapper;
+import com.fs.company.service.ICompanyService;
+import com.fs.company.vo.CompanyDataVO;
+import com.fs.sop.service.IQwSopService;
+import com.fs.sop.vo.QwSopTask;
+import com.fs.statis.domain.FsStatisPeriodWatch;
+import com.fs.statis.domain.FsStatisSalerWatch;
+import com.fs.statis.dto.StatsWatchLogPageListDTO;
+import com.fs.statis.service.FsStatisEveryDayWatchService;
+import com.fs.statis.service.FsStatisPeriodWatchService;
+import com.fs.statis.service.FsStatisSalerWatchService;
+import lombok.AllArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 看课统计接口
+ */
+@RestController
+@RequestMapping("/stats")
+@AllArgsConstructor
+public class SalesWatchStatisController {
+
+    @Autowired
+    private FsStatisSalerWatchService fsStatisSalerWatchService;
+
+    @Autowired
+    private FsStatisPeriodWatchService fsStatisPeriodWatchService;
+
+    @Autowired
+    private FsStatisEveryDayWatchService fsStatisEveryDayWatchService;
+
+    @Autowired
+    private IQwSopService qwSopService;
+
+    /**
+     * 销售完播统计查询
+     * @param param param
+     * @return R
+     */
+    @PostMapping("/seller/pageList")
+    public R sellerQueryList(@RequestBody StatsWatchLogPageListDTO param){
+        List<FsStatisSalerWatch> list = fsStatisSalerWatchService.queryList(param);
+        return R.ok().put("data",list);
+    }
+
+    /**
+     * 训练营完播统计查询
+     * @param param param
+     * @return R
+     */
+    @PostMapping("/period/pageList")
+    public R periodQueryList(@RequestBody StatsWatchLogPageListDTO param){
+        List<FsStatisPeriodWatch> list = fsStatisPeriodWatchService.queryList(param);
+        return R.ok().put("data", list);
+    }
+
+    /**
+     * 每日完播统计查询
+     * @param param param
+     * @return R
+     */
+    @PostMapping("/everyDay/pageList")
+    public R everyDayQueryList(@RequestBody StatsWatchLogPageListDTO param){
+        List<FsStatisSalerWatch> list = fsStatisEveryDayWatchService.queryList(param);
+        return R.ok().put("data", list);
+    }
+
+    /**
+     * 获取SOP任务数据
+     * @return
+     */
+    @GetMapping("/sopTaskData")
+    public R getSOPTaskData(){
+        List<QwSopTask> qwSopTaskList = qwSopService.getQwSopTaskList();
+
+        return R.ok().put("data",qwSopTaskList);
+    }
+
+}

+ 34 - 26
fs-admin/src/test/java/com/fs/task/StoreTaskTest.java

@@ -1,26 +1,34 @@
-//package com.fs.task;
-//
-//
-//import com.fs.FSAdminApplication;
-//import com.fs.course.service.IFsCourseWatchLogService;
-//import org.junit.Test;
-//import org.junit.runner.RunWith;
-//import org.springframework.beans.factory.annotation.Autowired;
-//import org.springframework.boot.test.context.SpringBootTest;
-//
-//@RunWith(value = org.springframework.test.context.junit4.SpringRunner.class)
-//@SpringBootTest(classes = FSAdminApplication.class)
-//public class StoreTaskTest {
-//    @Autowired
-//    private IFsCourseWatchLogService fsCourseWatchLogService;
-//    @Autowired
-//    private FsCourseTask fsCourseTask;
-//    @Test
-//    public void addQwWatchLog() {
-//        fsCourseWatchLogService.addCourseWatchLogDayNew();
-//    }
-//    @Test
-//    public void test() throws Exception {
-//        fsCourseTask.hyWorkTask();
-//    }
-//}
+package com.fs.task;
+
+
+import com.fs.FSAdminApplication;
+import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.statis.service.FsStatisSalerWatchService;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@RunWith(value = org.springframework.test.context.junit4.SpringRunner.class)
+@SpringBootTest(classes = FSAdminApplication.class)
+public class StoreTaskTest {
+    @Autowired
+    private IFsCourseWatchLogService fsCourseWatchLogService;
+    @Autowired
+    private FsCourseTask fsCourseTask;
+    @Autowired
+    private FsStatisSalerWatchService fsStatisSalerWatchService;
+    @Test
+    public void addQwWatchLog() {
+        fsCourseWatchLogService.addCourseWatchLogDayNew();
+    }
+    @Test
+    public void test() throws Exception {
+        fsCourseTask.hyWorkTask();
+    }
+
+    @Test
+    public void testWriteData(){
+        fsStatisSalerWatchService.writeData();
+    }
+}

+ 6 - 6
fs-company/src/main/java/com/fs/company/controller/CompanyRoleController.java

@@ -33,8 +33,8 @@ import com.fs.common.utils.poi.ExcelUtil;
 
 /**
  * 角色信息
- * 
- 
+ *
+
  */
 @RestController
 @RequestMapping("/company/role")
@@ -45,10 +45,10 @@ public class CompanyRoleController extends BaseController
 
     @Autowired
     private TokenService tokenService;
-    
+
     @Autowired
     private CompanyPermissionService permissionService;
-    
+
     @Autowired
     private ICompanyUserService userService;
 
@@ -80,7 +80,7 @@ public class CompanyRoleController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('company:role:query')")
     @GetMapping(value = "/{roleId}")
-    public AjaxResult getInfo(@PathVariable Long roleId)
+    public AjaxResult getInfo(@PathVariable("roleId") Long roleId)
     {
         return AjaxResult.success(roleService.selectCompanyRoleById(roleId));
     }
@@ -128,7 +128,7 @@ public class CompanyRoleController extends BaseController
             return AjaxResult.error("修改角色'" + role.getRoleName() + "'失败,角色权限已存在");
         }
         role.setUpdateBy(SecurityUtils.getUsername());
-        
+
         if (roleService.updateRole(role) > 0)
         {
             // 更新缓存用户权限

+ 3 - 0
fs-service-system/src/main/java/com/fs/company/mapper/CompanyMapper.java

@@ -155,4 +155,7 @@ public interface CompanyMapper
      * @return list
      */
     List<OptionsVO> selectCompanyListByMap(@Param("params") Map<String, Object> params);
+
+    List<Company> selectCompanyAllList();
+
 }

+ 13 - 0
fs-service-system/src/main/java/com/fs/company/mapper/CompanyUserMapper.java

@@ -261,4 +261,17 @@ public interface CompanyUserMapper
 
     int setIsRegisterMember(@Param("status") boolean status, @Param("userIds")List<Long> userIds);
 
+    @Select("select user_id,dept_id,user_name,company_id from company_user where del_flag=0 and status=0")
+    List<CompanyUser> selectAllCompanyUserList();
+
+    /**
+     * 获取对应的销售观看记录数
+     *
+     * @param companyUserId
+     * @param periodId
+     * @return
+     */
+    Long queryCompanyUserWatchCount(@Param("companyUserId") Long companyUserId, @Param("periodId") Long periodId);
+
+    Long queryCompanyUserWatchCountCompleted(@Param("companyUserId") Long companyUserId,@Param("periodId") Long periodId);
 }

+ 3 - 0
fs-service-system/src/main/java/com/fs/company/service/ICompanyService.java

@@ -4,6 +4,7 @@ import com.fs.common.core.domain.R;
 import com.fs.company.domain.Company;
 import com.fs.company.param.CompanyParam;
 import com.fs.company.vo.CompanyCrmVO;
+import com.fs.company.vo.CompanyDataVO;
 import com.fs.company.vo.CompanyNameVO;
 import com.fs.company.vo.CompanyVO;
 import com.fs.his.vo.OptionsVO;
@@ -114,4 +115,6 @@ public interface ICompanyService
      * @return list
      */
     List<OptionsVO> selectCompanyListByMap(Map<String, Object> params);
+
+
 }

+ 9 - 3
fs-service-system/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java

@@ -8,6 +8,7 @@ import com.fs.company.mapper.*;
 import com.fs.company.param.CompanyParam;
 import com.fs.company.service.ICompanyService;
 import com.fs.company.vo.CompanyCrmVO;
+import com.fs.company.vo.CompanyDataVO;
 import com.fs.company.vo.CompanyNameVO;
 import com.fs.company.vo.CompanyVO;
 import com.fs.course.mapper.FsCourseRedPacketLogMapper;
@@ -25,9 +26,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 /**
  * 企业Service业务层处理
@@ -63,6 +62,10 @@ public class CompanyServiceImpl implements ICompanyService
     @Autowired
     private FsCourseRedPacketLogMapper courseRedPacketLogMapper;
 
+    @Autowired
+    private CompanyDeptMapper companyDeptMapper;
+
+
     @Override
     public List<OptionsVO> selectAllCompanyList() {
         return companyMapper.selectAllCompanyList();
@@ -459,4 +462,7 @@ public class CompanyServiceImpl implements ICompanyService
     public List<OptionsVO> selectCompanyListByMap(Map<String, Object> params) {
         return companyMapper.selectCompanyListByMap(params);
     }
+
+
+
 }

+ 27 - 0
fs-service-system/src/main/java/com/fs/company/vo/CompanyDataVO.java

@@ -0,0 +1,27 @@
+package com.fs.company.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 公司数据
+ */
+@Data
+public class CompanyDataVO implements Serializable {
+    /**
+     * 节点名称
+      */
+    private String name;
+    /**
+     * 父节点id
+     */
+    private Long parentId;
+
+    private Long Id;
+    /**
+     * 子节点
+     */
+    List<CompanyDataVO> childs;
+}

+ 8 - 0
fs-service-system/src/main/java/com/fs/course/cache/FsUserCourseCategoryCacheService.java

@@ -0,0 +1,8 @@
+package com.fs.course.cache;
+
+import com.fs.course.domain.FsUserCourseCategory;
+import org.apache.ibatis.annotations.Param;
+
+public interface FsUserCourseCategoryCacheService {
+    Long selectCateIdByName(@Param("name")String name);
+}

+ 33 - 0
fs-service-system/src/main/java/com/fs/course/cache/impl/FsUserCourseCategoryCacheServiceImpl.java

@@ -0,0 +1,33 @@
+package com.fs.course.cache.impl;
+
+import com.fs.course.cache.FsUserCourseCategoryCacheService;
+import com.fs.course.domain.FsUserCourseCategory;
+import com.fs.course.mapper.FsUserCourseCategoryMapper;
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.io.Serializable;
+import java.util.concurrent.TimeUnit;
+
+@Service
+public class FsUserCourseCategoryCacheServiceImpl implements FsUserCourseCategoryCacheService {
+    @Autowired
+    private FsUserCourseCategoryMapper fsUserCourseCategoryMapper;
+    private static final Cache<String, Long> CATEGORY_CACHE = Caffeine.newBuilder()
+            .maximumSize(5000)
+            .expireAfterWrite(12, TimeUnit.HOURS)
+            .build();
+
+    @Override
+    public Long selectCateIdByName(String name) {
+        return CATEGORY_CACHE.get(name,e->{
+            FsUserCourseCategory fsUserCourseCategory = fsUserCourseCategoryMapper.selectFsUserCourseCategoryByName(name);
+            if(fsUserCourseCategory == null) {
+                return null;
+            }
+            return fsUserCourseCategory.getCateId();
+        });
+    }
+}

+ 28 - 9
fs-service-system/src/main/java/com/fs/course/domain/FsCourseQuestionBank.java

@@ -19,28 +19,47 @@ public class FsCourseQuestionBank extends BaseEntity
     private Long id;
 
     /** 问题 */
-    @Excel(name = "题")
+    @Excel(name = "题")
     private String title;
 
-    /** 排序 */
-    @Excel(name = "排序")
-    private Long sort;
+    private Long questionType;
+    @Excel(name = "问题类别")
+    private String questionTypeStr;
 
     /** 类别1 单选 2 多选  */
-    @Excel(name = "类别1 单选 2 多选 ")
+
     private Long type;
+    @Excel(name = "题目类型: 单选 多选")
+    private String typeStr;
 
     /** 状态 */
-    @Excel(name = "状态")
+//    @Excel(name = "状态")
     private Long status;
 
     /** 选项 */
-    @Excel(name = "选项")
+
     private String question;
+    /**
+     * 多个用分隔符|
+     */
+    @Excel(name = "选项: 多个用 | 隔开")
+    private String questionOption;
 
     /** 答案 */
-    @Excel(name = "答案")
     private String answer;
-    private Long questionType;
+
+    /**
+     * 答案: 多个用 | 隔开
+     */
+    @Excel(name = "答案: 多个用 | 隔开")
+    private String answerOption;
+
     private Long questionSubType;
+
+    @Excel(name = "题目子类别")
+    private String questionSubTypeStr;
+
+    /** 排序 */
+    @Excel(name = "序号")
+    private Long sort;
 }

+ 3 - 0
fs-service-system/src/main/java/com/fs/course/dto/FsCourseQuestionBankImportDTO.java

@@ -12,6 +12,9 @@ public class FsCourseQuestionBankImportDTO {
     @Excel(name = "问题类别")
     private String questionType;
 
+    @Excel(name = "题目子类别")
+    private String questionSubTyp;
+
     @Excel(name = "题目类型: 单选 多选")
     private String type;
 

+ 4 - 0
fs-service-system/src/main/java/com/fs/course/mapper/FsCourseQuestionBankMapper.java

@@ -38,6 +38,8 @@ public interface FsCourseQuestionBankMapper
      */
     public int insertFsCourseQuestionBank(FsCourseQuestionBank fsCourseQuestionBank);
 
+    public int insertFsCourseQuestionBankBatch(@Param("list") List<FsCourseQuestionBank> fsCourseQuestionBank);
+
     /**
      * 修改题库
      *
@@ -73,4 +75,6 @@ public interface FsCourseQuestionBankMapper
     public List<FsCourseQuestionBank> selectFsCourseQuestionBankByIdVO(@Param("list") String[] questionBankId);
 
 
+    @Select("select cate_name from fs_user_course_category where cate_id = ${cateId} and is_del=0 limit 1")
+    String selectNameByCateId(@Param("cateId") Long cateId);
 }

+ 12 - 1
fs-service-system/src/main/java/com/fs/course/mapper/FsUserCourseCategoryMapper.java

@@ -1,8 +1,11 @@
 package com.fs.course.mapper;
 
 import java.util.List;
+import java.util.Map;
+
 import com.fs.course.domain.FsUserCourseCategory;
 import com.fs.his.vo.OptionsVO;
+import org.apache.ibatis.annotations.MapKey;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 
@@ -76,6 +79,14 @@ public interface FsUserCourseCategoryMapper
      * @param name  名称
      * @return  FsUserCourseCategory
      */
-    @Select("select * from fs_user_course_category where cate_name = #{name} limit 1")
+    @Select("select cate_id from fs_user_course_category where cate_name = #{name} and is_del=0 limit 1")
     FsUserCourseCategory selectFsUserCourseCategoryByName(@Param("name")String name);
+
+    /**
+     * 查询所有分类
+     * @return
+     */
+    @Select("select cate_id,cate_name from fs_user_course_category where is_del=0")
+    @MapKey("cateName")
+    Map<String,FsUserCourseCategory> queryAllCategoryData();
 }

+ 6 - 0
fs-service-system/src/main/java/com/fs/course/mapper/FsUserCoursePeriodMapper.java

@@ -4,6 +4,7 @@ import com.fs.course.domain.FsUserCoursePeriod;
 import com.fs.course.param.CompanyRedPacketParam;
 import com.fs.course.vo.FsUserCoursePeriodVO;
 import com.fs.course.vo.PeriodRedPacketVO;
+import org.apache.ibatis.annotations.MapKey;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
@@ -127,4 +128,9 @@ public interface FsUserCoursePeriodMapper
      */
     @Update("update fs_user_course_period set period_status = 3, update_time = now() where period_status = 2 and period_end_time < #{now}")
     void endPeriod(@Param("now") LocalDate now);
+
+
+    @Select("select * from fs_user_course_period where find_in_set(${companyId},company_id) and #{previousDay} >= period_starting_time and #{previousDay} <= period_end_time ")
+    List<Long> queryPeriod(@Param("companyId") Long companyId,
+                           @Param("previousDay") LocalDate previousDay);
 }

+ 4 - 0
fs-service-system/src/main/java/com/fs/course/service/IFsCourseQuestionBankService.java

@@ -90,4 +90,8 @@ public interface IFsCourseQuestionBankService
      * @return  list
      */
     List<FsCourseQuestionBank> selectFsCourseQuestionBankByIds(List<Long> ids);
+
+    List<FsCourseQuestionBank> selectFsCourseQuestionBankListExport(FsCourseQuestionBank fsCourseQuestionBank);
+
+    String selectNameByCateId(Long cateId);
 }

+ 13 - 0
fs-service-system/src/main/java/com/fs/course/service/cache/IFsCourseQuestionBankCacheService.java

@@ -0,0 +1,13 @@
+package com.fs.course.service.cache;
+
+import com.fs.course.domain.FsUserCourseCategory;
+
+public interface IFsCourseQuestionBankCacheService {
+    /**
+     * 查询课堂分类
+     *
+     * @param cateId 课堂分类主键
+     * @return 课堂分类
+     */
+    public String selectFsUserCourseCategoryByCateId(Long cateId);
+}

+ 36 - 0
fs-service-system/src/main/java/com/fs/course/service/cache/impl/FsUserCourseQuestionBankCacheServiceImpl.java

@@ -0,0 +1,36 @@
+package com.fs.course.service.cache.impl;
+
+import com.fs.course.domain.FsCourseQuestionBank;
+import com.fs.course.domain.FsUserCourseVideo;
+import com.fs.course.service.IFsCourseQuestionBankService;
+import com.fs.course.service.cache.IFsCourseQuestionBankCacheService;
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.concurrent.TimeUnit;
+
+@Service
+public class FsUserCourseQuestionBankCacheServiceImpl implements IFsCourseQuestionBankCacheService {
+
+    private static final Cache<Long, String> CATEGORY_CACHE = Caffeine.newBuilder()
+            .maximumSize(1000)
+            .expireAfterWrite(3, TimeUnit.HOURS)
+            .build();
+
+    @Autowired
+    private IFsCourseQuestionBankService fsCourseQuestionBankService;
+
+
+    @Override
+    public String selectFsUserCourseCategoryByCateId(Long cateId) {
+        return CATEGORY_CACHE.get(cateId,e->{
+            String cateName = fsCourseQuestionBankService.selectNameByCateId(cateId);
+            if(cateName == null) {
+                return "";
+            }
+            return cateName;
+        });
+    }
+}

+ 94 - 5
fs-service-system/src/main/java/com/fs/course/service/impl/FsCourseQuestionBankServiceImpl.java

@@ -3,21 +3,27 @@ package com.fs.course.service.impl;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.fs.common.core.domain.R;
 import com.fs.common.exception.ServiceException;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.StringUtils;
+import com.fs.course.cache.FsUserCourseCategoryCacheService;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.*;
 import com.fs.course.dto.FsCourseQuestionBankImportDTO;
 import com.fs.course.mapper.*;
 import com.fs.course.param.FsCourseQuestionAnswerUParam;
 import com.fs.course.service.IFsCourseQuestionBankService;
+import com.fs.course.service.cache.IFsCourseQuestionBankCacheService;
 import com.fs.store.domain.FsUser;
 import com.fs.store.mapper.FsUserMapper;
 import com.fs.store.service.IFsStorePaymentService;
 import com.fs.system.service.ISysConfigService;
+import com.google.gson.JsonParser;
 import com.hc.openapi.tool.fastjson.JSON;
+import jodd.util.StringUtil;
+import org.apache.commons.collections4.CollectionUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -56,6 +62,12 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
     @Autowired
     private FsUserCourseCategoryMapper courseCategoryMapper;
 
+    @Autowired
+    private IFsCourseQuestionBankCacheService fsCourseQuestionBankCacheService;
+
+    @Autowired
+    private FsUserCourseCategoryCacheService fsUserCourseCategoryCacheService;
+
     /**
      * 查询题库
      *
@@ -77,7 +89,8 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
     @Override
     public List<FsCourseQuestionBank> selectFsCourseQuestionBankList(FsCourseQuestionBank fsCourseQuestionBank)
     {
-        return fsCourseQuestionBankMapper.selectFsCourseQuestionBankList(fsCourseQuestionBank);
+        List<FsCourseQuestionBank> fsCourseQuestionBanks = fsCourseQuestionBankMapper.selectFsCourseQuestionBankList(fsCourseQuestionBank);
+        return fsCourseQuestionBanks;
     }
 
     /**
@@ -358,11 +371,17 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
         StringBuilder importErrorMsg = new StringBuilder();
         StringBuilder importMsg = new StringBuilder();
 
+        List<FsCourseQuestionBank> importData = new ArrayList<>();
+
+        Map<String,FsUserCourseCategory> categoryData = courseCategoryMapper.queryAllCategoryData();
+
         for (FsCourseQuestionBankImportDTO importDTO : list) {
             try {
                 String title = importDTO.getTitle();
                 String type = importDTO.getType();
                 String questionType = importDTO.getQuestionType();
+                String questionSubTyp = importDTO.getQuestionSubTyp();
+
                 String question = importDTO.getQuestion();
                 String answer = importDTO.getAnswer();
 
@@ -421,10 +440,17 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
                 }
 
                 // 分类
+                FsUserCourseCategory fsUserCourseCategory = categoryData.get(questionType);
                 Long questionTypeId = null;
-                FsUserCourseCategory category = courseCategoryMapper.selectFsUserCourseCategoryByName(questionType);
-                if (Objects.nonNull(category)) {
-                    questionTypeId = category.getCateId();
+                if(fsUserCourseCategory != null) {
+                    questionTypeId = fsUserCourseCategory.getCateId();
+                }
+
+                // 题目子分类
+                fsUserCourseCategory = categoryData.get(questionSubTyp);
+                Long questionSubTypeId = null;
+                if(fsUserCourseCategory != null) {
+                    questionSubTypeId = fsUserCourseCategory.getCateId();
                 }
 
                 FsCourseQuestionBank questionBank = new FsCourseQuestionBank();
@@ -437,7 +463,10 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
                 questionBank.setAnswer(answers.length > 1 ? JSON.toJSONString(answers) : answer);
                 questionBank.setCreateTime(new Date());
                 questionBank.setCreateBy(nickName);
-                fsCourseQuestionBankMapper.insertFsCourseQuestionBank(questionBank);
+                questionBank.setQuestionSubType(questionSubTypeId);
+
+                importData.add(questionBank);
+
 
                 importSuccessMsg.append("<br/>").append(successNum).append("、题目 ").append(title).append(" 导入成功");
                 successNum++;
@@ -448,6 +477,10 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
             }
         }
 
+        if(CollectionUtils.isNotEmpty(importData)) {
+            fsCourseQuestionBankMapper.insertFsCourseQuestionBankBatch(importData);
+        }
+
         // 在所有导入处理完成后,构建最终的导入结果消息
         importMsg.insert(0, "导入完成!成功" + successNum + " 条,失败" + failureNum + "条。");
         importMsg.append(importErrorMsg);
@@ -465,6 +498,62 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
         return fsCourseQuestionBankMapper.selectFsCourseQuestionBankByIds(ids);
     }
 
+    @Override
+    public List<FsCourseQuestionBank> selectFsCourseQuestionBankListExport(FsCourseQuestionBank fsCourseQuestionBank) {
+        List<FsCourseQuestionBank> fsCourseQuestionBanks = this.selectFsCourseQuestionBankList(fsCourseQuestionBank);
+
+        for (FsCourseQuestionBank courseQuestionBank : fsCourseQuestionBanks) {
+
+            if(StringUtil.isNotBlank(courseQuestionBank.getQuestion())) {
+                // 问题列表多个 以分隔符 | 结尾
+                JSONArray questionArr = com.alibaba.fastjson.JSON.parseArray(courseQuestionBank.getQuestion());
+                List<String> questionOpt = new ArrayList<>();
+                for(int i=0;i<questionArr.size();i++){
+                    JSONObject jsonObject = questionArr.getJSONObject(i);
+                    String name = jsonObject.getString("name");
+                    questionOpt.add(name);
+                }
+                courseQuestionBank.setQuestionOption(String.join("|",questionOpt));
+            }
+
+            if(StringUtil.isNotBlank(courseQuestionBank.getAnswer())) {
+                // 如果是json数组
+                if(JsonParser.parseString(courseQuestionBank.getAnswer()).isJsonArray()) {
+                    String answerOpt = String.join("|", JSONUtil.parseArray(courseQuestionBank.getAnswer()).toList(String.class));
+                    courseQuestionBank.setAnswerOption(answerOpt);
+                } else {
+                    courseQuestionBank.setAnswerOption(courseQuestionBank.getAnswer());
+                }
+            }
+
+            // 问题类别
+            if(ObjectUtils.isNotNull(courseQuestionBank.getQuestionType())) {
+                String questionType = fsCourseQuestionBankCacheService.selectFsUserCourseCategoryByCateId(courseQuestionBank.getQuestionType());
+
+                courseQuestionBank.setQuestionTypeStr(questionType);
+            }
+
+            // 题目类型
+            if(ObjectUtils.isNotNull(courseQuestionBank.getType())) {
+                String type = courseQuestionBank.getType() == 1 ? "单选" : "多选";
+                courseQuestionBank.setTypeStr(type);
+            }
+
+            // 题目子类别
+            if(ObjectUtils.isNotNull(courseQuestionBank.getQuestionSubType())) {
+                String questionType = fsCourseQuestionBankCacheService.selectFsUserCourseCategoryByCateId(courseQuestionBank.getQuestionSubType());
+                courseQuestionBank.setQuestionSubTypeStr(questionType);
+            }
+
+        }
+        return fsCourseQuestionBanks;
+    }
+
+    @Override
+    public String selectNameByCateId(Long cateId) {
+        return fsCourseQuestionBankMapper.selectNameByCateId(cateId);
+    }
+
 
     public static String[] convertStringToArray(String inputString) {
         String cleanString = inputString.replaceAll("[\\[\\]\"\\s]", "");

+ 5 - 0
fs-service-system/src/main/java/com/fs/qw/mapper/QwUserMapper.java

@@ -369,4 +369,9 @@ public interface QwUserMapper extends BaseMapper<QwUser>
      * @return  list
      */
     List<OptionVO> selectQwUserListByMap(@Param("params") Map<String, Object> params);
+
+
+    @Select("select qw_user_id from qw_user where company_user_id = ${companyUserId}")
+    List<String> findQwUserIdListByCompanyUserId(@Param("companyUserId") Long companyUserId);
+
 }

+ 23 - 11
fs-service-system/src/main/java/com/fs/qw/service/impl/AsyncSopTestService.java

@@ -27,6 +27,7 @@ import com.fs.wxUser.param.CompanyWxUserSopParam;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.rocketmq.spring.core.RocketMQTemplate;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
@@ -45,17 +46,28 @@ import java.util.stream.Collectors;
 public class AsyncSopTestService {
 
 
-    private final QwSopLogsMapper qwSopLogsMapper;
-    private final QwSopMapper qwSopMapper;
-    private final FsUserMapper fsUserMapper;
-    private final SopUserLogsInfoMapper sopUserLogsInfoMapper;
-    private final ISopUserLogsService sopUserLogsService;
-    private final QwExternalContactMapper qwExternalContactMapper;
-    private final QwSopTempMapper qwSopTempMapper;
-    private final RocketMQTemplate rocketMQTemplate;
-    private final SopUserLogsMapper sopUserLogsMapper;
-    private final FsCourseSopAppLinkMapper fsCourseSopAppLinkMapper;
-    private final uniPush2Service push2Service;
+    @Autowired
+    private QwSopLogsMapper qwSopLogsMapper;
+    @Autowired
+    private QwSopMapper qwSopMapper;
+    @Autowired
+    private FsUserMapper fsUserMapper;
+    @Autowired
+    private SopUserLogsInfoMapper sopUserLogsInfoMapper;
+    @Autowired
+    private ISopUserLogsService sopUserLogsService;
+    @Autowired
+    private QwExternalContactMapper qwExternalContactMapper;
+    @Autowired
+    private QwSopTempMapper qwSopTempMapper;
+    @Autowired
+    private RocketMQTemplate rocketMQTemplate;
+    @Autowired
+    private SopUserLogsMapper sopUserLogsMapper;
+    @Autowired
+    private FsCourseSopAppLinkMapper fsCourseSopAppLinkMapper;
+    @Autowired
+    private uniPush2Service push2Service;
 
     /**
      * 立即执行SOP任务

+ 7 - 0
fs-service-system/src/main/java/com/fs/sop/mapper/QwSopLogsMapper.java

@@ -13,6 +13,7 @@ import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.springframework.stereotype.Repository;
 
+import java.time.LocalDate;
 import java.util.List;
 
 /**
@@ -287,4 +288,10 @@ public interface QwSopLogsMapper extends BaseMapper<QwSopLogs> {
 
     @DataSource(DataSourceType.SOP)
     void batchUpdateQwSopLogsById(@Param("data") List<QwSopLogs> logs);
+
+    @DataSource(DataSourceType.SOP)
+    Long selectQwSopLogsCountByQwUserId(@Param("data") List<String> qwUserIdList,
+                                        @Param("periodId") Long periodId,
+                                        @Param("previousDay") LocalDate previousDay);
+
 }

+ 1 - 0
fs-service-system/src/main/java/com/fs/sop/mapper/QwSopMapper.java

@@ -372,5 +372,6 @@ public interface QwSopMapper extends BaseMapper<QwSop> {
     List<QwSop> selectQwSopByIsRatingNotNull();
 
 
+    List<QwSop> selectQwSopAllList();
 
 }

+ 2 - 0
fs-service-system/src/main/java/com/fs/sop/mapper/SopUserLogsMapper.java

@@ -170,4 +170,6 @@ public interface SopUserLogsMapper {
     void batchInsertSopUserLogs(@Param("list") List<SopUserLogs> list);
     @DataSource(DataSourceType.SOP)
     List<SopUserLogs> selectSopUserLogByChatIds(@Param("ids") List<String> ids);
+
+    List<SopUserLogs> queryExecuteLogBySopId(@Param("sopId") String id);
 }

+ 4 - 0
fs-service-system/src/main/java/com/fs/sop/service/IQwSopService.java

@@ -5,6 +5,7 @@ import com.fs.qw.domain.QwSopUpdateStatus;
 import com.fs.sop.domain.QwSop;
 import com.fs.sop.params.QwSopAutoTime;
 import com.fs.sop.params.QwSopEditQwUserParam;
+import com.fs.sop.vo.QwSopTask;
 import com.fs.sop.vo.SopVoiceListVo;
 
 import java.io.IOException;
@@ -18,6 +19,8 @@ import java.util.List;
  */
 public interface IQwSopService
 {
+
+    List<QwSopTask> getQwSopTaskList();
     /**
      * 查询企微sop
      *
@@ -26,6 +29,7 @@ public interface IQwSopService
      */
     public QwSop selectQwSopById(String id);
 
+
     /**
      * 查询企微sop列表
      *

+ 1 - 0
fs-service-system/src/main/java/com/fs/sop/service/impl/QwSopLogsServiceImpl.java

@@ -88,6 +88,7 @@ public class QwSopLogsServiceImpl implements IQwSopLogsService
     @Autowired
     private SopUserLogsInfoMapper sopUserLogsInfoMapper;
 
+    @Lazy
     @Autowired
     private AsyncSopTestService asyncSopTestService;
 

+ 38 - 16
fs-service-system/src/main/java/com/fs/sop/service/impl/QwSopServiceImpl.java

@@ -5,6 +5,7 @@ import com.fs.common.annotation.DataSource;
 import com.fs.common.core.domain.R;
 import com.fs.common.enums.DataSourceType;
 import com.fs.common.exception.base.BaseException;
+import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.StringUtils;
 import com.fs.company.mapper.CompanyUserMapper;
@@ -27,11 +28,14 @@ import com.fs.sop.domain.*;
 import com.fs.sop.mapper.*;
 import com.fs.sop.params.*;
 import com.fs.sop.service.*;
+import com.fs.sop.vo.QwSopTask;
+import com.fs.sop.vo.QwSopTaskDetail;
 import com.fs.sop.vo.SopVoiceListVo;
 import com.fs.sop.vo.VoiceVo;
 import com.fs.voice.utils.StringUtil;
 import com.fs.wxUser.mapper.CompanyWxUserMapper;
 import com.fs.wxUser.param.CompanyWxUserSopParam;
+import org.apache.commons.collections.ArrayStack;
 import org.apache.rocketmq.spring.core.RocketMQTemplate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -73,31 +77,17 @@ public class QwSopServiceImpl implements IQwSopService
     @Autowired
     private SopUserLogsInfoMapper sopUserLogsInfoMapper;
 
-    @Autowired
-    private IFsCourseLinkService iFsCourseLinkService;
-
-    @Autowired
-    private FsCourseWatchLogMapper fsCourseWatchLogMapper;
-
-    @Autowired
-    private CompanyWxUserMapper companyWxUserMapper;
-
     @Autowired
     private SopUserLogsMapper sopUserLogsMapper;
 
-    @Autowired
-    private AsyncSopService asyncSopService;
     @Autowired
     private AsyncSopTestService asyncSopTestService;
     @Autowired
     private AsyncWxSopService asyncWxSopService;
     @Autowired
     private AsyncChatSopService asyncChatSopService;
-
-
     @Autowired
     private QwApiService qwApiService;
-
     @Autowired
     private ISopUserLogsService sopUserLogsService;
     @Autowired
@@ -108,14 +98,46 @@ public class QwSopServiceImpl implements IQwSopService
     private IQwSopTempDayService qwSopTempDayService;
     @Autowired
     private IQwSopTempRulesService qwSopTempRulesService;
-    @Autowired
-    private IQwSopTempContentService qwSopTempContentService;
 
     @Autowired
     private RocketMQTemplate rocketMQTemplate;
     @Autowired
     private QwSopTempMapper qwSopTempMapper;
 
+
+    @Override
+    public List<QwSopTask> getQwSopTaskList() {
+        List<QwSop> qwSopList = qwSopMapper.selectQwSopAllList();
+        List<QwSopTask> qwSopTaskList = new ArrayList<>();
+
+        for (QwSop qwSop : qwSopList) {
+            QwSopTask qwSopTask = new QwSopTask();
+            qwSopTask.setTaskId(qwSop.getId());
+            qwSopTask.setTaskName(qwSop.getName());
+
+            List<QwSopTaskDetail> listData = new ArrayList<>();
+            // 获取对应SOP任务执行记录
+            List<SopUserLogs> sopUserLogs = sopUserLogsMapper.queryExecuteLogBySopId(qwSop.getId());
+            for (SopUserLogs sopUserLog : sopUserLogs) {
+                QwSopTaskDetail qwSopTaskDetail = new QwSopTaskDetail();
+                qwSopTaskDetail.setTaskId(sopUserLog.getId());
+                qwSopTaskDetail.setQwUserId(sopUserLog.getQwUserId());
+                qwSopTaskDetail.setTaskExecDate(sopUserLog.getStartTime());
+
+                long daysDifferenceFromNow = DateUtils.getDaysDifferenceFromNow(DateUtils.dateTime("yyyy-MM-dd", sopUserLog.getStartTime()));
+
+                qwSopTaskDetail.setDays(daysDifferenceFromNow);
+
+                listData.add(qwSopTaskDetail);
+            }
+            qwSopTask.setTaskDetailList(listData);
+
+            qwSopTaskList.add(qwSopTask);
+        }
+
+        return qwSopTaskList;
+    }
+
     /**
      * 查询企微sop
      *

+ 28 - 0
fs-service-system/src/main/java/com/fs/sop/vo/QwSopTask.java

@@ -0,0 +1,28 @@
+package com.fs.sop.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 企微sop任务
+ */
+@Data
+public class QwSopTask implements Serializable {
+
+    /**
+     * 任务id
+     */
+    private String taskId;
+
+    /**
+     * 任务名称
+     */
+    private String taskName;
+
+    /**
+     * 任务详情
+     */
+    private List<QwSopTaskDetail> taskDetailList;
+}

+ 32 - 0
fs-service-system/src/main/java/com/fs/sop/vo/QwSopTaskDetail.java

@@ -0,0 +1,32 @@
+package com.fs.sop.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 企微sop任务详情
+ */
+@Data
+public class QwSopTaskDetail implements Serializable {
+    /**
+     * 任务时间
+     */
+    private String taskId;
+    /**
+     * 企微员工账号
+     */
+    private String qwUserId;
+    /**
+     * 企微员工名称
+     */
+    private String qwUserName;
+    /**
+     * 任务执行时间
+     */
+    private String taskExecDate;
+    /**
+     * 天数
+     */
+    private Long days;
+}

+ 104 - 0
fs-service-system/src/main/java/com/fs/statis/domain/FsStatisEveryDayWatch.java

@@ -0,0 +1,104 @@
+package com.fs.statis.domain;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import java.time.LocalDateTime; // 使用 java.time 替代 java.util.Date
+
+/**
+ * 每日统计数据实体类
+ * 对应表 fs_statis_every_day_watch
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class FsStatisEveryDayWatch {
+
+    /**
+     * 主键ID
+     */
+    private Integer id;
+
+    /**
+     * 数据日期
+     */
+    private LocalDateTime dataDate;
+
+    /**
+     * 训练营id
+     */
+    private Integer periodId;
+
+    /**
+     * 训练营人数
+     */
+    private Integer periodNum;
+
+    /**
+     * 未报名人数
+     */
+    private Integer notRegNum;
+
+    /**
+     * 已报名人数
+     */
+    private Integer registeredNum;
+
+    /**
+     * 报名率
+     */
+    private Float regRate;
+
+    /**
+     * 完课率
+     */
+    private Float completedRate;
+
+    /**
+     * 未上线-总数
+     */
+    private Integer offlineTotal;
+
+    /**
+     * 未上线-未参与
+     */
+    private Integer offlineNotRegNum;
+
+    /**
+     * 未上线-未观看
+     */
+    private Integer offlineNotWatchNum;
+
+    /**
+     * 已上线-总数
+     */
+    private Integer onlineTotal;
+
+    /**
+     * 已上线-上线率
+     */
+    private Float onlineRate;
+
+    /**
+     * 已上线-完播率
+     */
+    private Float onlineCompletedRate;
+
+    /**
+     * 已上线-未完播
+     */
+    private Integer onlineNotCompRateNum;
+
+    /**
+     * 已上线-已完播
+     */
+    private Integer onlineCompletedNum;
+
+    /**
+     * 销售id
+     */
+    private Integer companyUserId;
+
+}

+ 107 - 0
fs-service-system/src/main/java/com/fs/statis/domain/FsStatisPeriodWatch.java

@@ -0,0 +1,107 @@
+package com.fs.statis.domain;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import java.util.Date; // 或者使用 java.time.LocalDateTime
+
+/**
+ * fs_statis_period_watch表 实体类
+ *
+ * @author AutoGenerator
+ * @since now
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class FsStatisPeriodWatch {
+
+    /**
+     * 主键ID (虽然注释中没有,但通常id为主键)
+     */
+    private Integer id;
+
+    /**
+     * 训练营id
+     */
+    private Integer periodId;
+
+    /**
+     * 训练营人数
+     */
+    private Integer periodNum;
+
+    /**
+     * 未报名人数
+     */
+    private Integer notRegNum;
+
+    /**
+     * 已报名人数
+     */
+    private Integer registeredNum;
+
+    /**
+     * 报名率
+     */
+    private Float regRate;
+
+    /**
+     * 完课率
+     */
+    private Float watchCompletedRate;
+
+    /**
+     * 未上线-总数
+     */
+    private Integer offlineTotal;
+
+    /**
+     * 未上线-未参与
+     */
+    private Integer offlineNotRegNum;
+
+    /**
+     * 未上线-未观看
+     */
+    private Integer offlineNotWatchNum;
+
+    /**
+     * 已上线-总数
+     */
+    private Integer onlineTotal;
+
+    /**
+     * 已上线-上线率
+     */
+    private Float onlineRate;
+
+    /**
+     * 已上线-上线完播率
+     */
+    private Float onlineWatchCompletedRate;
+
+    /**
+     * 已上线-未完播
+     */
+    private Integer onlineWatchNotCompleted;
+
+    /**
+     * 已上线-已完播
+     */
+    private Integer onlineWatchCompleted;
+
+    /**
+     * 数据日期
+     */
+    private Date dataDate;
+
+
+    /**
+     * 销售id
+     */
+    private Long companyUserId;
+
+}

+ 112 - 0
fs-service-system/src/main/java/com/fs/statis/domain/FsStatisSalerWatch.java

@@ -0,0 +1,112 @@
+package com.fs.statis.domain;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+
+import java.time.LocalDate;
+
+/**
+ * 销售观看统计实体类
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class FsStatisSalerWatch {
+
+    /**
+     * 主键ID
+     */
+    private Long id;
+
+    /**
+     * 部门
+     */
+    private Long deptId;
+
+    /**
+     * 销售id
+     */
+    private Long companyUserId;
+
+    /**
+     * 训练营人数
+     */
+    private Long trainCampNum;
+
+    /**
+     * 未报名人数
+     */
+    private Long notRegisteredNum;
+
+    /**
+     * 已报名人数
+     */
+    private Long registeredNum;
+    /**
+     * 完课人数
+     */
+    private Long completedNum;
+
+    /**
+     * 报名率
+     */
+    private Float regRate;
+
+    /**
+     * 完课率
+     */
+    private Float finishedRate;
+
+    /**
+     * 未上线-总数
+     */
+    private Long offlineTotal;
+
+    /**
+     * 未上线-未参与
+     */
+    private Long offlineNotPart;
+
+    /**
+     * 未上线-未观看 (原SQL注释为'为观看',推测应为'未观看')
+     */
+    private Long offlineNotWatched;
+
+    /**
+     * 已上线-总数
+     */
+    private Long onlineTotal;
+
+    /**
+     * 已上线-上线率
+     */
+    private Float onlineOnlineRate;
+
+    /**
+     * 已上线-完播率
+     */
+    private Float onlinePlaybackCompleRate;
+
+    /**
+     * 已上线-未完播
+     */
+    private Long onlineIncompletePlayback;
+
+    /**
+     * 已上线-已完播
+     */
+    private Long onlineCompletePlayback;
+
+    /**
+     * 训练营id
+     */
+    private Long periodId;
+
+    /**
+     * 数据日期
+     */
+    private LocalDate dataDate;
+}

+ 30 - 0
fs-service-system/src/main/java/com/fs/statis/dto/StatsWatchLogPageListDTO.java

@@ -0,0 +1,30 @@
+package com.fs.statis.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+@Data
+public class StatsWatchLogPageListDTO implements Serializable {
+
+    /**
+     * 员工列表
+     */
+    private List<String> userIds;
+
+    /**
+     * 开始时间
+     */
+    private String startDate;
+    /**
+     * 结束世界
+     */
+    private String endDate;
+
+    /**
+     * 营期列表
+     */
+    private List<String> periodList;
+
+}

+ 91 - 0
fs-service-system/src/main/java/com/fs/statis/mapper/FsStatisEveryDayWatchMapper.java

@@ -0,0 +1,91 @@
+package com.fs.statis.mapper;
+
+import com.fs.statis.domain.FsStatisEveryDayWatch;
+import com.fs.statis.domain.FsStatisSalerWatch;
+import com.fs.statis.dto.StatsWatchLogPageListDTO;
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Options;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
+import java.util.List;
+
+/**
+ * 每日统计数据Mapper接口
+ */
+@Mapper
+public interface FsStatisEveryDayWatchMapper {
+
+    /**
+     * 根据主键ID查询每日统计数据
+     * @param id 主键ID
+     * @return 每日统计数据对象,如果不存在则返回null
+     */
+    @Select("SELECT id, data_date AS dataDate, period_id AS periodId, period_num AS periodNum, " +
+            "not_reg_num AS notRegNum, registered_num AS registeredNum, reg_rate AS regRate, " +
+            "completed_rate AS completedRate, offline_total AS offlineTotal, " +
+            "offline_not_reg_num AS offlineNotRegNum, offline_not_watch_num AS offlineNotWatchNum, " +
+            "online_total AS onlineTotal, online_rate AS onlineRate, " +
+            "online_completed_rate AS onlineCompletedRate, online_not_comp_rate_num AS onlineNotCompRateNum, " +
+            "online_completed_num AS onlineCompletedNum, company_user_id AS companyUserId " +
+            "FROM fs_statis_every_day_watch WHERE id = #{id}")
+    FsStatisEveryDayWatch findById(@Param("id") Integer id);
+
+    /**
+     * 新增每日统计数据
+     * @param fsStatisEveryDayWatch 待插入的每日统计数据对象
+     * @return 影响的行数
+     */
+    @Insert("INSERT INTO fs_statis_every_day_watch (id, data_date, period_id, period_num, not_reg_num, " +
+            "registered_num, reg_rate, completed_rate, offline_total, offline_not_reg_num, " +
+            "offline_not_watch_num, online_total, online_rate, online_completed_rate, " +
+            "online_not_comp_rate_num, online_completed_num, company_user_id) " +
+            "VALUES (#{id}, #{dataDate}, #{periodId}, #{periodNum}, #{notRegNum}, #{registeredNum}, " +
+            "#{regRate}, #{completedRate}, #{offlineTotal}, #{offlineNotRegNum}, #{offlineNotWatchNum}, " +
+            "#{onlineTotal}, #{onlineRate}, #{onlineCompletedRate}, #{onlineNotCompRateNum}, " +
+            "#{onlineCompletedNum}, #{companyUserId})")
+    int insert(FsStatisEveryDayWatch fsStatisEveryDayWatch);
+
+    /**
+     * 更新每日统计数据
+     * @param fsStatisEveryDayWatch 待更新的每日统计数据对象 (必须包含ID)
+     * @return 影响的行数
+     */
+    @Update("UPDATE fs_statis_every_day_watch SET " +
+            "data_date = #{dataDate}, " +
+            "period_id = #{periodId}, " +
+            "period_num = #{periodNum}, " +
+            "not_reg_num = #{notRegNum}, " +
+            "registered_num = #{registeredNum}, " +
+            "reg_rate = #{regRate}, " +
+            "completed_rate = #{completedRate}, " +
+            "offline_total = #{offlineTotal}, " +
+            "offline_not_reg_num = #{offlineNotRegNum}, " +
+            "offline_not_watch_num = #{offlineNotWatchNum}, " +
+            "online_total = #{onlineTotal}, " +
+            "online_rate = #{onlineRate}, " +
+            "online_completed_rate = #{onlineCompletedRate}, " +
+            "online_not_comp_rate_num = #{onlineNotCompRateNum}, " +
+            "online_completed_num = #{onlineCompletedNum}, " +
+            "company_user_id = #{companyUserId} " +
+            "WHERE id = #{id}")
+    int update(FsStatisEveryDayWatch fsStatisEveryDayWatch);
+
+    /**
+     * 查询所有每日统计数据 (示例,根据需要添加更多查询方法)
+     * @return 每日统计数据列表
+     */
+    @Select("SELECT id, data_date AS dataDate, period_id AS periodId, period_num AS periodNum, " +
+            "not_reg_num AS notRegNum, registered_num AS registeredNum, reg_rate AS regRate, " +
+            "completed_rate AS completedRate, offline_total AS offlineTotal, " +
+            "offline_not_reg_num AS offlineNotRegNum, offline_not_watch_num AS offlineNotWatchNum, " +
+            "online_total AS onlineTotal, online_rate AS onlineRate, " +
+            "online_completed_rate AS onlineCompletedRate, online_not_comp_rate_num AS onlineNotCompRateNum, " +
+            "online_completed_num AS onlineCompletedNum, company_user_id AS companyUserId " +
+            "FROM fs_statis_every_day_watch")
+    List<FsStatisEveryDayWatch> findAll();
+
+    List<FsStatisSalerWatch> queryList(StatsWatchLogPageListDTO param);
+
+}

+ 99 - 0
fs-service-system/src/main/java/com/fs/statis/mapper/FsStatisPeriodWatchMapper.java

@@ -0,0 +1,99 @@
+package com.fs.statis.mapper;
+
+import com.fs.statis.domain.FsStatisPeriodWatch;
+import com.fs.statis.dto.StatsWatchLogPageListDTO;
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
+import org.apache.ibatis.annotations.Delete;
+
+import java.util.List;
+
+/**
+ * fs_statis_period_watch表 Mapper 接口
+ *
+ * @author AutoGenerator
+ * @since now
+ */
+@Mapper
+public interface FsStatisPeriodWatchMapper {
+
+    /**
+     * 根据主键查询数据
+     *
+     * @param id 主键
+     * @return 实体对象
+     */
+    @Select("SELECT id, period_id, period_num, not_reg_num, registered_num, reg_rate, " +
+            "watch_completed_rate, offline_total, offline_not_reg_num, offline_not_watch_num, " +
+            "online_total, online_rate, online_watch_completed_rate, online_watch_not_completed, " +
+            "online_watch_completed, data_date " +
+            "FROM fs_statis_period_watch WHERE id = #{id}")
+    FsStatisPeriodWatch selectById(@Param("id") Integer id);
+
+    /**
+     * 新增数据
+     *
+     * @param fsStatisPeriodWatch 实体对象
+     * @return 影响行数
+     */
+    @Insert("INSERT INTO fs_statis_period_watch (id, period_id, period_num, not_reg_num, registered_num, " +
+            "reg_rate, watch_completed_rate, offline_total, offline_not_reg_num, offline_not_watch_num, " +
+            "online_total, online_rate, online_watch_completed_rate, online_watch_not_completed, " +
+            "online_watch_completed, data_date) " +
+            "VALUES (#{id}, #{periodId}, #{periodNum}, #{notRegNum}, #{registeredNum}, #{regRate}, " +
+            "#{watchCompletedRate}, #{offlineTotal}, #{offlineNotRegNum}, #{offlineNotWatchNum}, " +
+            "#{onlineTotal}, #{onlineRate}, #{onlineWatchCompletedRate}, #{onlineWatchNotCompleted}, " +
+            "#{onlineWatchCompleted}, #{dataDate})")
+    // 如果id是自增的,可以加上 @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn="id")
+    int insert(FsStatisPeriodWatch fsStatisPeriodWatch);
+
+    /**
+     * 根据主键更新数据
+     *
+     * @param fsStatisPeriodWatch 实体对象
+     * @return 影响行数
+     */
+    @Update("<script>" +
+            "UPDATE fs_statis_period_watch " +
+            "<set>" +
+            "  <if test='periodId != null'>period_id = #{periodId},</if>" +
+            "  <if test='periodNum != null'>period_num = #{periodNum},</if>" +
+            "  <if test='notRegNum != null'>not_reg_num = #{notRegNum},</if>" +
+            "  <if test='registeredNum != null'>registered_num = #{registeredNum},</if>" +
+            "  <if test='regRate != null'>reg_rate = #{regRate},</if>" +
+            "  <if test='watchCompletedRate != null'>watch_completed_rate = #{watchCompletedRate},</if>" +
+            "  <if test='offlineTotal != null'>offline_total = #{offlineTotal},</if>" +
+            "  <if test='offlineNotRegNum != null'>offline_not_reg_num = #{offlineNotRegNum},</if>" +
+            "  <if test='offlineNotWatchNum != null'>offline_not_watch_num = #{offlineNotWatchNum},</if>" +
+            "  <if test='onlineTotal != null'>online_total = #{onlineTotal},</if>" +
+            "  <if test='onlineRate != null'>online_rate = #{onlineRate},</if>" +
+            "  <if test='onlineWatchCompletedRate != null'>online_watch_completed_rate = #{onlineWatchCompletedRate},</if>" +
+            "  <if test='onlineWatchNotCompleted != null'>online_watch_not_completed = #{onlineWatchNotCompleted},</if>" +
+            "  <if test='onlineWatchCompleted != null'>online_watch_completed = #{onlineWatchCompleted},</if>" +
+            "  <if test='dataDate != null'>data_date = #{dataDate},</if>" +
+            "</set>" +
+            "WHERE id = #{id}" +
+            "</script>")
+    int updateById(FsStatisPeriodWatch fsStatisPeriodWatch);
+
+    /**
+     * 根据主键删除数据
+     *
+     * @param id 主键
+     * @return 影响行数
+     */
+    @Delete("DELETE FROM fs_statis_period_watch WHERE id = #{id}")
+    int deleteById(@Param("id") Integer id);
+
+    /**
+     * 查询所有数据
+     *
+     * @return 实体对象列表
+     */
+    List<FsStatisPeriodWatch> selectAll();
+
+    List<FsStatisPeriodWatch> queryList(StatsWatchLogPageListDTO param);
+}

+ 86 - 0
fs-service-system/src/main/java/com/fs/statis/mapper/FsStatisSalerWatchMapper.java

@@ -0,0 +1,86 @@
+package com.fs.statis.mapper;
+
+import com.fs.statis.domain.FsStatisSalerWatch;
+import com.fs.statis.dto.StatsWatchLogPageListDTO;
+import org.apache.ibatis.annotations.Delete;
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Options;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
+
+import java.util.List;
+
+/**
+ * 销售观看统计Mapper接口
+ */
+@Mapper
+public interface FsStatisSalerWatchMapper {
+
+    /**
+     * 根据ID查询销售观看统计
+     * @param id 主键ID
+     * @return 销售观看统计对象
+     */
+    @Select("SELECT * FROM fs_statis_saler_watch WHERE id = #{id}")
+    FsStatisSalerWatch findById(@Param("id") Integer id);
+
+    /**
+     * 查询所有销售观看统计
+     * @return 销售观看统计列表
+     */
+    @Select("SELECT * FROM fs_statis_saler_watch")
+    List<FsStatisSalerWatch> findAll();
+
+    /**
+     * 新增销售观看统计
+     * @param fsStatisSalerWatch 销售观看统计对象
+     * @return 受影响的行数
+     */
+    @Insert("INSERT INTO fs_statis_saler_watch (dept_id, company_user_id, train_camp_num, not_registered_num, " +
+            "registered_num, reg_rate, finished_rate, offline_total, offline_not_part, offline_not_watched, " +
+            "online_total, online_online_rate, online_playback_comple_rate, online_incomplete_playback, " +
+            "online_complete_playback) " +
+            "VALUES (#{deptId}, #{companyUserId}, #{trainCampNum}, #{notRegisteredNum}, #{registeredNum}, " +
+            "#{regRate}, #{finishedRate}, #{offlineTotal}, #{offlineNotPart}, #{offlineNotWatched}, #{onlineTotal}, " +
+            "#{onlineOnlineRate}, #{onlinePlaybackCompleRate}, #{onlineIncompletePlayback}, #{onlineCompletePlayback})")
+    @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
+    int insert(FsStatisSalerWatch fsStatisSalerWatch);
+
+    /**
+     * 更新销售观看统计
+     * @param fsStatisSalerWatch 销售观看统计对象
+     * @return 受影响的行数
+     */
+    @Update("UPDATE fs_statis_saler_watch SET " +
+            "dept_id = #{deptId}, " +
+            "company_user_id = #{companyUserId}, " +
+            "train_camp_num = #{trainCampNum}, " +
+            "not_registered_num = #{notRegisteredNum}, " +
+            "registered_num = #{registeredNum}, " +
+            "reg_rate = #{regRate}, " +
+            "finished_rate = #{finishedRate}, " +
+            "offline_total = #{offlineTotal}, " +
+            "offline_not_part = #{offlineNotPart}, " +
+            "offline_not_watched = #{offlineNotWatched}, " +
+            "online_total = #{onlineTotal}, " +
+            "online_online_rate = #{onlineOnlineRate}, " +
+            "online_playback_comple_rate = #{onlinePlaybackCompleRate}, " +
+            "online_incomplete_playback = #{onlineIncompletePlayback}, " +
+            "online_complete_playback = #{onlineCompletePlayback} " +
+            "WHERE id = #{id}")
+    int update(FsStatisSalerWatch fsStatisSalerWatch);
+
+    /**
+     * 根据ID删除销售观看统计
+     * @param id 主键ID
+     * @return 受影响的行数
+     */
+    @Delete("DELETE FROM fs_statis_saler_watch WHERE id = #{id}")
+    int deleteById(@Param("id") Integer id);
+
+    List<FsStatisSalerWatch> queryList(StatsWatchLogPageListDTO param);
+
+    void batchSave(@Param("list") List<FsStatisSalerWatch> writeData);
+}

+ 47 - 0
fs-service-system/src/main/java/com/fs/statis/service/FsStatisEveryDayWatchService.java

@@ -0,0 +1,47 @@
+package com.fs.statis.service;
+
+import com.fs.statis.domain.FsStatisEveryDayWatch;
+import com.fs.statis.domain.FsStatisSalerWatch;
+import com.fs.statis.dto.StatsWatchLogPageListDTO;
+
+import java.util.List;
+
+/**
+ * 每日统计数据服务接口
+ */
+public interface FsStatisEveryDayWatchService {
+
+    /**
+     * 根据主键ID查询每日统计数据
+     *
+     * @param id 主键ID
+     * @return 每日统计数据对象,如果不存在则返回null
+     */
+    FsStatisEveryDayWatch findById(Integer id);
+
+    /**
+     * 新增每日统计数据
+     *
+     * @param fsStatisEveryDayWatch 待插入的每日统计数据对象
+     * @return 影响的行数,通常是1表示成功
+     */
+    int create(FsStatisEveryDayWatch fsStatisEveryDayWatch);
+
+    /**
+     * 更新每日统计数据
+     *
+     * @param fsStatisEveryDayWatch 待更新的每日统计数据对象 (必须包含ID)
+     * @return 影响的行数,通常是1表示成功,0表示未找到对应记录
+     */
+    int update(FsStatisEveryDayWatch fsStatisEveryDayWatch);
+
+    /**
+     * 查询所有每日统计数据
+     *
+     * @return 每日统计数据列表
+     */
+    List<FsStatisEveryDayWatch> findAll();
+
+    List<FsStatisSalerWatch> queryList(StatsWatchLogPageListDTO param);
+
+}

+ 56 - 0
fs-service-system/src/main/java/com/fs/statis/service/FsStatisPeriodWatchService.java

@@ -0,0 +1,56 @@
+package com.fs.statis.service; // 假设Service接口放在此包下
+
+import com.fs.statis.domain.FsStatisPeriodWatch;
+import com.fs.statis.dto.StatsWatchLogPageListDTO;
+
+import java.util.List;
+
+/**
+ * 训练营周期统计数据服务接口
+ * 对应表 fs_statis_period_watch
+ */
+public interface FsStatisPeriodWatchService {
+
+    /**
+     * 根据主键ID查询训练营周期统计数据
+     *
+     * @param id 主键ID
+     * @return 训练营周期统计数据对象,如果不存在则返回null
+     */
+    FsStatisPeriodWatch findById(Integer id);
+
+    /**
+     * 新增训练营周期统计数据
+     *
+     * @param fsStatisPeriodWatch 待插入的训练营周期统计数据对象
+     * @return 影响的行数,通常是1表示成功
+     */
+    int create(FsStatisPeriodWatch fsStatisPeriodWatch);
+
+    /**
+     * 根据主键更新训练营周期统计数据
+     * (只会更新实体中非null的字段)
+     *
+     * @param fsStatisPeriodWatch 待更新的训练营周期统计数据对象 (必须包含ID)
+     * @return 影响的行数,通常是1表示成功,0表示未找到对应记录或未更新任何字段
+     */
+    int updateById(FsStatisPeriodWatch fsStatisPeriodWatch);
+
+    /**
+     * 根据主键ID删除训练营周期统计数据
+     *
+     * @param id 主键ID
+     * @return 影响的行数
+     */
+    int deleteById(Integer id);
+
+    /**
+     * 查询所有训练营周期统计数据
+     *
+     * @return 训练营周期统计数据列表
+     */
+    List<FsStatisPeriodWatch> findAll();
+
+    List<FsStatisPeriodWatch> queryList(StatsWatchLogPageListDTO param);
+
+}

+ 59 - 0
fs-service-system/src/main/java/com/fs/statis/service/FsStatisSalerWatchService.java

@@ -0,0 +1,59 @@
+package com.fs.statis.service;
+
+import com.fs.statis.domain.FsStatisSalerWatch;
+import com.fs.statis.dto.StatsWatchLogPageListDTO;
+
+import java.util.List;
+
+/**
+ * 销售观看统计服务接口
+ */
+public interface FsStatisSalerWatchService {
+
+    /**
+     * 根据ID查询销售观看统计
+     *
+     * @param id 主键ID
+     * @return 销售观看统计对象,如果不存在则返回null
+     */
+    FsStatisSalerWatch getById(Integer id);
+
+    /**
+     * 查询所有销售观看统计
+     *
+     * @return 销售观看统计列表
+     */
+    List<FsStatisSalerWatch> getAll();
+
+    /**
+     * 新增销售观看统计
+     *
+     * @param fsStatisSalerWatch 待新增的销售观看统计对象
+     * @return 返回带有生成ID的销售观看统计对象
+     */
+    FsStatisSalerWatch create(FsStatisSalerWatch fsStatisSalerWatch);
+
+    /**
+     * 更新销售观看统计
+     *
+     * @param fsStatisSalerWatch 待更新的销售观看统计对象 (ID必须存在)
+     * @return 返回更新后的销售观看统计对象,如果更新失败或对象不存在则可能返回null或抛出异常
+     */
+    FsStatisSalerWatch update(FsStatisSalerWatch fsStatisSalerWatch);
+
+    /**
+     * 根据ID删除销售观看统计
+     *
+     * @param id 主键ID
+     * @return 删除成功返回true,否则返回false
+     */
+    boolean deleteById(Integer id);
+
+    List<FsStatisSalerWatch> queryList(StatsWatchLogPageListDTO param);
+
+
+    /**
+     * 写入数据 写入前一天的数据
+     */
+    void writeData();
+}

+ 71 - 0
fs-service-system/src/main/java/com/fs/statis/service/impl/FsStatisEveryDayWatchServiceImpl.java

@@ -0,0 +1,71 @@
+package com.fs.statis.service.impl;
+
+import com.fs.statis.domain.FsStatisEveryDayWatch;
+import com.fs.statis.domain.FsStatisSalerWatch;
+import com.fs.statis.dto.StatsWatchLogPageListDTO;
+import com.fs.statis.mapper.FsStatisEveryDayWatchMapper;
+import com.fs.statis.service.FsStatisEveryDayWatchService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional; // 引入事务注解
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 每日统计数据服务实现类
+ */
+@Service
+public class FsStatisEveryDayWatchServiceImpl implements FsStatisEveryDayWatchService {
+
+    private final FsStatisEveryDayWatchMapper fsStatisEveryDayWatchMapper;
+
+    /**
+     * 通过构造函数注入Mapper
+     * @param fsStatisEveryDayWatchMapper 每日统计数据Mapper
+     */
+    @Autowired
+    public FsStatisEveryDayWatchServiceImpl(FsStatisEveryDayWatchMapper fsStatisEveryDayWatchMapper) {
+        this.fsStatisEveryDayWatchMapper = fsStatisEveryDayWatchMapper;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FsStatisEveryDayWatch findById(Integer id) {
+        return fsStatisEveryDayWatchMapper.findById(id);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @Transactional
+    public int create(FsStatisEveryDayWatch fsStatisEveryDayWatch) {
+        return fsStatisEveryDayWatchMapper.insert(fsStatisEveryDayWatch);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @Transactional
+    public int update(FsStatisEveryDayWatch fsStatisEveryDayWatch) {
+        return fsStatisEveryDayWatchMapper.update(fsStatisEveryDayWatch);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<FsStatisEveryDayWatch> findAll() {
+        return fsStatisEveryDayWatchMapper.findAll();
+    }
+
+    @Override
+    public List<FsStatisSalerWatch> queryList(StatsWatchLogPageListDTO param) {
+        return fsStatisEveryDayWatchMapper.queryList(param);
+    }
+
+}

+ 82 - 0
fs-service-system/src/main/java/com/fs/statis/service/impl/FsStatisPeriodWatchServiceImpl.java

@@ -0,0 +1,82 @@
+package com.fs.statis.service.impl; // 假设Service实现类放在此包下
+
+import com.fs.statis.domain.FsStatisPeriodWatch;
+import com.fs.statis.dto.StatsWatchLogPageListDTO;
+import com.fs.statis.mapper.FsStatisPeriodWatchMapper;
+import com.fs.statis.service.FsStatisPeriodWatchService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 训练营周期统计数据服务实现类
+ */
+@Service
+public class FsStatisPeriodWatchServiceImpl implements FsStatisPeriodWatchService {
+
+    private final FsStatisPeriodWatchMapper fsStatisPeriodWatchMapper;
+
+    /**
+     * 通过构造函数注入Mapper
+     * @param fsStatisPeriodWatchMapper 训练营周期统计数据Mapper
+     */
+    @Autowired
+    public FsStatisPeriodWatchServiceImpl(FsStatisPeriodWatchMapper fsStatisPeriodWatchMapper) {
+        this.fsStatisPeriodWatchMapper = fsStatisPeriodWatchMapper;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FsStatisPeriodWatch findById(Integer id) {
+        return fsStatisPeriodWatchMapper.selectById(id);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @Transactional // 标记此方法需要事务管理
+    public int create(FsStatisPeriodWatch fsStatisPeriodWatch) {
+        // 在这里可以添加业务逻辑,例如参数校验等
+        return fsStatisPeriodWatchMapper.insert(fsStatisPeriodWatch);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @Transactional // 标记此方法需要事务管理
+    public int updateById(FsStatisPeriodWatch fsStatisPeriodWatch) {
+        // 在这里可以添加业务逻辑,例如检查记录是否存在,或在更新前进行特定校验
+        // 注意:Mapper中的updateById是动态SQL,只更新非null字段
+        return fsStatisPeriodWatchMapper.updateById(fsStatisPeriodWatch);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @Transactional // 标记此方法需要事务管理
+    public int deleteById(Integer id) {
+        // 在这里可以添加业务逻辑,例如检查关联数据等
+        return fsStatisPeriodWatchMapper.deleteById(id);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<FsStatisPeriodWatch> findAll() {
+        return fsStatisPeriodWatchMapper.selectAll();
+    }
+
+    @Override
+    public List<FsStatisPeriodWatch> queryList(StatsWatchLogPageListDTO param) {
+        return fsStatisPeriodWatchMapper.queryList(param);
+    }
+}

+ 175 - 0
fs-service-system/src/main/java/com/fs/statis/service/impl/FsStatisSalerWatchServiceImpl.java

@@ -0,0 +1,175 @@
+package com.fs.statis.service.impl;
+
+import com.fs.company.domain.CompanyUser;
+import com.fs.company.mapper.CompanyUserMapper;
+import com.fs.course.domain.FsUserCoursePeriod;
+import com.fs.course.mapper.FsUserCoursePeriodMapper;
+import com.fs.qw.mapper.QwUserMapper;
+import com.fs.sop.mapper.QwSopLogsMapper;
+import com.fs.statis.domain.FsStatisSalerWatch;
+import com.fs.statis.dto.StatsWatchLogPageListDTO;
+import com.fs.statis.mapper.FsStatisSalerWatchMapper;
+import com.fs.statis.service.FsStatisSalerWatchService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 销售观看统计服务实现类
+ */
+@Service("fsStatisSalerWatchService")
+@Slf4j
+public class FsStatisSalerWatchServiceImpl implements FsStatisSalerWatchService {
+
+    private final FsStatisSalerWatchMapper fsStatisSalerWatchMapper;
+
+    private final CompanyUserMapper companyUserMapper;
+
+    private final QwUserMapper qwUserMapper;
+
+    private final QwSopLogsMapper qwSopLogsMapper;
+
+    private final FsUserCoursePeriodMapper fsUserCoursePeriodMapper;
+
+    @Autowired
+    public FsStatisSalerWatchServiceImpl(FsStatisSalerWatchMapper fsStatisSalerWatchMapper, CompanyUserMapper companyUserMapper, QwUserMapper qwUserMapper, QwSopLogsMapper qwSopLogsMapper, FsUserCoursePeriodMapper fsUserCoursePeriodMapper) {
+        this.fsStatisSalerWatchMapper = fsStatisSalerWatchMapper;
+        this.companyUserMapper = companyUserMapper;
+        this.qwUserMapper = qwUserMapper;
+        this.qwSopLogsMapper = qwSopLogsMapper;
+        this.fsUserCoursePeriodMapper = fsUserCoursePeriodMapper;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FsStatisSalerWatch getById(Integer id) {
+        if (id == null) {
+            return null;
+        }
+        return fsStatisSalerWatchMapper.findById(id);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<FsStatisSalerWatch> getAll() {
+        return fsStatisSalerWatchMapper.findAll();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FsStatisSalerWatch create(FsStatisSalerWatch fsStatisSalerWatch) {
+        if (fsStatisSalerWatch == null) {
+            return null;
+        }
+        fsStatisSalerWatch.setId(null);
+        fsStatisSalerWatchMapper.insert(fsStatisSalerWatch);
+        return fsStatisSalerWatch;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FsStatisSalerWatch update(FsStatisSalerWatch fsStatisSalerWatch) {
+        if (fsStatisSalerWatch == null || fsStatisSalerWatch.getId() == null) {
+            return null;
+        }
+        int affectedRows = fsStatisSalerWatchMapper.update(fsStatisSalerWatch);
+        if (affectedRows > 0) {
+            return fsStatisSalerWatch;
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean deleteById(Integer id) {
+        if (id == null) {
+            return false;
+        }
+        int affectedRows = fsStatisSalerWatchMapper.deleteById(id);
+        return affectedRows > 0;
+    }
+
+    @Override
+    public List<FsStatisSalerWatch> queryList(StatsWatchLogPageListDTO param) {
+        return fsStatisSalerWatchMapper.queryList(param);
+    }
+
+    @Override
+    public void writeData() {
+        // 统计销售看课情况
+        // 获取前一天的时间
+        List<CompanyUser> companyUserList = this.companyUserMapper.selectAllCompanyUserList();
+
+
+        List<FsStatisSalerWatch> writeData = new ArrayList<>();
+
+        LocalDate previousDay = LocalDate.now().minusDays(1);
+        for (CompanyUser companyUser : companyUserList) {
+
+            if(companyUser.getCompanyId() == null) {
+                log.info("销售{} 对应公司id {} 为空!",companyUser.getUserId(),companyUser.getCompanyId());
+                continue;
+            }
+            // 确定当前销售对应的营期
+            List<Long> periodList = fsUserCoursePeriodMapper.queryPeriod(companyUser.getCompanyId(),previousDay);
+
+            // 找到销售关联的企微账号
+            List<String> qwUserIdList = qwUserMapper.findQwUserIdListByCompanyUserId(companyUser.getUserId());
+
+            for (Long periodId : periodList) {
+                // 去sop记录表找对应的SOP发送记录,记录数作为营期人数
+                Long periodCount = qwSopLogsMapper.selectQwSopLogsCountByQwUserId(qwUserIdList,periodId,previousDay);
+                // 再去course_watch_log找对应的销售观看记录作为已报名数
+                Long registerCount = companyUserMapper.queryCompanyUserWatchCount(companyUser.getUserId(),periodId);
+                Long completedCount = companyUserMapper.queryCompanyUserWatchCountCompleted(companyUser.getUserId(),periodId);
+
+
+                FsStatisSalerWatch fsStatisSalerWatch = new FsStatisSalerWatch();
+                fsStatisSalerWatch.setDeptId(companyUser.getDeptId());
+                fsStatisSalerWatch.setCompanyUserId(companyUser.getUserId());
+                fsStatisSalerWatch.setTrainCampNum(periodCount);
+                fsStatisSalerWatch.setNotRegisteredNum(periodCount - registerCount);
+                fsStatisSalerWatch.setRegisteredNum(registerCount);
+                fsStatisSalerWatch.setCompletedNum(completedCount);
+
+                float regRate = 0.0f;
+                if(periodCount != 0) {
+                    regRate = registerCount / periodCount;
+                }
+                float finishedRate = 0.0f;
+                if(registerCount != 0) {
+                    finishedRate = completedCount / registerCount;
+                }
+
+
+                fsStatisSalerWatch.setRegRate(regRate);
+                fsStatisSalerWatch.setFinishedRate(finishedRate);
+                fsStatisSalerWatch.setDataDate(previousDay);
+
+                fsStatisSalerWatch.setPeriodId(periodId);
+
+                writeData.add(fsStatisSalerWatch);
+            }
+        }
+
+        if(CollectionUtils.isNotEmpty(writeData)){
+            fsStatisSalerWatchMapper.batchSave(writeData);
+        }
+    }
+}

+ 3 - 0
fs-service-system/src/main/resources/mapper/company/CompanyMapper.xml

@@ -62,6 +62,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             </if>
         </where>
     </select>
+    <select id="selectCompanyAllList" resultType="com.fs.company.domain.Company">
+        select company_id,company_name where is_del=0
+    </select>
 
     <insert id="insertCompany" parameterType="Company" useGeneratedKeys="true" keyProperty="companyId">
         insert into company

+ 23 - 0
fs-service-system/src/main/resources/mapper/company/CompanyUserMapper.xml

@@ -502,6 +502,29 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <!-- 数据范围过滤 -->
         ${params.dataScope}
     </select>
+    <select id="queryCompanyUserWatchCount" resultType="java.lang.Long">
+        select count(1) from fs_course_watch_log
+        <where>
+            <if test="companyUserId != null">
+                AND company_user_id = #{companyUserId}
+            </if>
+            <if test="periodId != null">
+                AND period_id = #{periodId}
+            </if>
+        </where>
+    </select>
+    <select id="queryCompanyUserWatchCountCompleted" resultType="java.lang.Long">
+        select count(1) from fs_course_watch_log
+        <where>
+            AND log_type = 2
+            <if test="companyUserId != null">
+                AND company_user_id = #{companyUserId}
+            </if>
+            <if test="periodId != null">
+                AND period_id = #{periodId}
+            </if>
+        </where>
+    </select>
 
     <update id="setIsRegisterMember" parameterType="Long">
         update company_user

+ 29 - 0
fs-service-system/src/main/resources/mapper/course/FsCourseQuestionBankMapper.xml

@@ -69,6 +69,35 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="questionSubType != null">#{questionSubType},</if>
          </trim>
     </insert>
+    <insert id="insertFsCourseQuestionBankBatch">
+        INSERT INTO fs_course_question_bank (
+        title,
+        sort,
+        type,
+        status,
+        question,
+        create_time,
+        answer,
+        create_by,
+        question_type,
+        question_sub_type
+        )
+        VALUES
+        <foreach collection="list" item="item" separator=",">
+            (
+            #{item.title, jdbcType=VARCHAR},
+            #{item.sort, jdbcType=INTEGER},
+            #{item.type, jdbcType=VARCHAR},
+            #{item.status, jdbcType=INTEGER},
+            #{item.question, jdbcType=CLOB},
+            #{item.createTime, jdbcType=TIMESTAMP},
+            #{item.answer, jdbcType=CLOB},
+            #{item.createBy, jdbcType=VARCHAR},
+            #{item.questionType, jdbcType=VARCHAR},
+            #{item.questionSubType, jdbcType=VARCHAR}
+            )
+        </foreach>
+    </insert>
 
     <update id="updateFsCourseQuestionBank" parameterType="FsCourseQuestionBank">
         update fs_course_question_bank

+ 17 - 0
fs-service-system/src/main/resources/mapper/sop/QwSopLogsMapper.xml

@@ -552,6 +552,23 @@
         </where>
         ORDER BY send_time desc
     </select>
+    <select id="selectQwSopLogsCountByQwUserId" resultType="java.lang.Long">
+        select count(1) from qw_sop_logs
+        <where>
+            AND type = '0'
+            AND send_status = '1'
+            AND DATE(real_send_time) = #{previousDay}
+            AND user_logs_id = #{periodId}
+            <if test="data != null and data.size() > 0">
+                AND qw_userid in
+                <foreach collection="data" item="item" open="(" close=")" separator=",">
+                    #{item}
+                </foreach>
+            </if>
+
+        </where>
+
+    </select>
 
 
     <!-- 批量更新 QwSopLogs -->

+ 3 - 0
fs-service-system/src/main/resources/mapper/sop/QwSopMapper.xml

@@ -414,6 +414,9 @@
             #{id}
         </foreach>
     </select>
+    <select id="selectQwSopAllList" resultType="com.fs.sop.domain.QwSop">
+        select id,name from qw_sop where type=2
+    </select>
     <update id="updateQwSop" parameterType="QwSop" useGeneratedKeys="false" keyProperty="id" >
         UPDATE  qw_sop
         <trim prefix="SET" suffixOverrides=",">

+ 7 - 0
fs-service-system/src/main/resources/mapper/sop/SopUserLogsMapper.xml

@@ -285,5 +285,12 @@
     <select id="selectSopUserLogByChatIds" resultType="com.fs.sop.domain.SopUserLogs">
         select * from sop_user_logs where chat_id in <foreach collection="ids" open="(" separator="," close=")" item="item">#{item}</foreach>
     </select>
+    <select id="queryExecuteLogBySopId" resultType="com.fs.sop.domain.SopUserLogs">
+        select *
+            from
+         sop_user_logs
+
+         where sop_id=#{sopId}
+    </select>
 
 </mapper>

+ 162 - 0
fs-service-system/src/main/resources/mapper/statis/FsStatisEveryDayMapper.xml

@@ -0,0 +1,162 @@
+<?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="com.fs.statis.mapper.FsStatisEveryDayWatchMapper">
+
+    <!-- 结果集映射 -->
+    <resultMap id="BaseResultMap" type="com.fs.statis.domain.FsStatisEveryDayWatch">
+        <id column="id" jdbcType="INTEGER" property="id" />
+        <result column="data_date" jdbcType="TIMESTAMP" property="dataDate" />
+        <result column="period_id" jdbcType="INTEGER" property="periodId" />
+        <result column="period_num" jdbcType="INTEGER" property="periodNum" />
+        <result column="not_reg_num" jdbcType="INTEGER" property="notRegNum" />
+        <result column="registered_num" jdbcType="INTEGER" property="registeredNum" />
+        <result column="reg_rate" jdbcType="REAL" property="regRate" /> <!-- Use REAL or FLOAT for float type -->
+        <result column="completed_rate" jdbcType="REAL" property="completedRate" />
+        <result column="offline_total" jdbcType="INTEGER" property="offlineTotal" />
+        <result column="offline_not_reg_num" jdbcType="INTEGER" property="offlineNotRegNum" />
+        <result column="offline_not_watch_num" jdbcType="INTEGER" property="offlineNotWatchNum" />
+        <result column="online_total" jdbcType="INTEGER" property="onlineTotal" />
+        <result column="online_rate" jdbcType="REAL" property="onlineRate" />
+        <result column="online_completed_rate" jdbcType="REAL" property="onlineCompletedRate" />
+        <result column="online_not_comp_rate_num" jdbcType="INTEGER" property="onlineNotCompRateNum" />
+        <result column="online_completed_num" jdbcType="INTEGER" property="onlineCompletedNum" />
+        <result column="company_user_id" jdbcType="INTEGER" property="companyUserId" />
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        id, data_date, period_id, period_num, not_reg_num, registered_num, reg_rate,
+    completed_rate, offline_total, offline_not_reg_num, offline_not_watch_num,
+    online_total, online_rate, online_completed_rate, online_not_comp_rate_num,
+    online_completed_num, company_user_id
+    </sql>
+
+    <!-- 根据主键查询 -->
+    <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
+        select
+        <include refid="Base_Column_List" />
+        from fs_statis_every_day_watch
+        where id = #{id,jdbcType=INTEGER}
+    </select>
+
+    <!-- 根据主键删除 -->
+    <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
+        delete from fs_statis_every_day_watch
+        where id = #{id,jdbcType=INTEGER}
+    </delete>
+
+    <!-- 选择性插入记录 (只插入非空字段) -->
+    <insert id="insertSelective" parameterType="com.fs.statis.domain.FsStatisEveryDayWatch" useGeneratedKeys="true" keyProperty="id">
+        insert into fs_statis_every_day_watch
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="dataDate != null">data_date,</if>
+            <if test="periodId != null">period_id,</if>
+            <if test="periodNum != null">period_num,</if>
+            <if test="notRegNum != null">not_reg_num,</if>
+            <if test="registeredNum != null">registered_num,</if>
+            <if test="regRate != null">reg_rate,</if>
+            <if test="completedRate != null">completed_rate,</if>
+            <if test="offlineTotal != null">offline_total,</if>
+            <if test="offlineNotRegNum != null">offline_not_reg_num,</if>
+            <if test="offlineNotWatchNum != null">offline_not_watch_num,</if>
+            <if test="onlineTotal != null">online_total,</if>
+            <if test="onlineRate != null">online_rate,</if>
+            <if test="onlineCompletedRate != null">online_completed_rate,</if>
+            <if test="onlineNotCompRateNum != null">online_not_comp_rate_num,</if>
+            <if test="onlineCompletedNum != null">online_completed_num,</if>
+            <if test="companyUserId != null">company_user_id,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="dataDate != null">#{dataDate,jdbcType=TIMESTAMP},</if>
+            <if test="periodId != null">#{periodId,jdbcType=INTEGER},</if>
+            <if test="periodNum != null">#{periodNum,jdbcType=INTEGER},</if>
+            <if test="notRegNum != null">#{notRegNum,jdbcType=INTEGER},</if>
+            <if test="registeredNum != null">#{registeredNum,jdbcType=INTEGER},</if>
+            <if test="regRate != null">#{regRate,jdbcType=REAL},</if>
+            <if test="completedRate != null">#{completedRate,jdbcType=REAL},</if>
+            <if test="offlineTotal != null">#{offlineTotal,jdbcType=INTEGER},</if>
+            <if test="offlineNotRegNum != null">#{offlineNotRegNum,jdbcType=INTEGER},</if>
+            <if test="offlineNotWatchNum != null">#{offlineNotWatchNum,jdbcType=INTEGER},</if>
+            <if test="onlineTotal != null">#{onlineTotal,jdbcType=INTEGER},</if>
+            <if test="onlineRate != null">#{onlineRate,jdbcType=REAL},</if>
+            <if test="onlineCompletedRate != null">#{onlineCompletedRate,jdbcType=REAL},</if>
+            <if test="onlineNotCompRateNum != null">#{onlineNotCompRateNum,jdbcType=INTEGER},</if>
+            <if test="onlineCompletedNum != null">#{onlineCompletedNum,jdbcType=INTEGER},</if>
+            <if test="companyUserId != null">#{companyUserId,jdbcType=INTEGER},</if>
+        </trim>
+    </insert>
+
+    <!-- 根据主键选择性更新 (只更新非空字段) -->
+    <update id="updateByPrimaryKeySelective" parameterType="com.fs.statis.domain.FsStatisEveryDayWatch">
+        update fs_statis_every_day_watch
+        <set>
+            <if test="dataDate != null">data_date = #{dataDate,jdbcType=TIMESTAMP},</if>
+            <if test="periodId != null">period_id = #{periodId,jdbcType=INTEGER},</if>
+            <if test="periodNum != null">period_num = #{periodNum,jdbcType=INTEGER},</if>
+            <if test="notRegNum != null">not_reg_num = #{notRegNum,jdbcType=INTEGER},</if>
+            <if test="registeredNum != null">registered_num = #{registeredNum,jdbcType=INTEGER},</if>
+            <if test="regRate != null">reg_rate = #{regRate,jdbcType=REAL},</if>
+            <if test="completedRate != null">completed_rate = #{completedRate,jdbcType=REAL},</if>
+            <if test="offlineTotal != null">offline_total = #{offlineTotal,jdbcType=INTEGER},</if>
+            <if test="offlineNotRegNum != null">offline_not_reg_num = #{offlineNotRegNum,jdbcType=INTEGER},</if>
+            <if test="offlineNotWatchNum != null">offline_not_watch_num = #{offlineNotWatchNum,jdbcType=INTEGER},</if>
+            <if test="onlineTotal != null">online_total = #{onlineTotal,jdbcType=INTEGER},</if>
+            <if test="onlineRate != null">online_rate = #{onlineRate,jdbcType=REAL},</if>
+            <if test="onlineCompletedRate != null">online_completed_rate = #{onlineCompletedRate,jdbcType=REAL},</if>
+            <if test="onlineNotCompRateNum != null">online_not_comp_rate_num = #{onlineNotCompRateNum,jdbcType=INTEGER},</if>
+            <if test="onlineCompletedNum != null">online_completed_num = #{onlineCompletedNum,jdbcType=INTEGER},</if>
+            <if test="companyUserId != null">company_user_id = #{companyUserId,jdbcType=INTEGER},</if>
+        </set>
+        where id = #{id,jdbcType=INTEGER}
+    </update>
+
+    <!-- 根据主键更新 (所有字段) -->
+    <update id="updateByPrimaryKey" parameterType="com.fs.statis.domain.FsStatisEveryDayWatch">
+        update fs_statis_every_day_watch
+        set data_date = #{dataDate,jdbcType=TIMESTAMP},
+            period_id = #{periodId,jdbcType=INTEGER},
+            period_num = #{periodNum,jdbcType=INTEGER},
+            not_reg_num = #{notRegNum,jdbcType=INTEGER},
+            registered_num = #{registeredNum,jdbcType=INTEGER},
+            reg_rate = #{regRate,jdbcType=REAL},
+            completed_rate = #{completedRate,jdbcType=REAL},
+            offline_total = #{offlineTotal,jdbcType=INTEGER},
+            offline_not_reg_num = #{offlineNotRegNum,jdbcType=INTEGER},
+            offline_not_watch_num = #{offlineNotWatchNum,jdbcType=INTEGER},
+            online_total = #{onlineTotal,jdbcType=INTEGER},
+            online_rate = #{onlineRate,jdbcType=REAL},
+            online_completed_rate = #{onlineCompletedRate,jdbcType=REAL},
+            online_not_comp_rate_num = #{onlineNotCompRateNum,jdbcType=INTEGER},
+            online_completed_num = #{onlineCompletedNum,jdbcType=INTEGER},
+            company_user_id = #{companyUserId,jdbcType=INTEGER}
+        where id = #{id,jdbcType=INTEGER}
+    </update>
+
+    <!-- 查询所有记录 -->
+    <select id="selectAll" resultMap="BaseResultMap">
+        select
+        <include refid="Base_Column_List" />
+        from fs_statis_every_day_watch
+    </select>
+    <select id="queryList" resultType="com.fs.statis.domain.FsStatisSalerWatch">
+        select * from fs_statis_every_day_watch
+        <where>
+            <if test="userIds != null and userIds.length > 0">
+                AND company_user_id IN
+                <foreach collection="userIds" open="(" close=")" separator="," item="item">
+                    ${item}
+                </foreach>
+            </if>
+            <if test="periodList != null and periodList.length > 0">
+                AND period_id IN
+                <foreach collection="periodList" open="(" close=")" separator="," item="item">
+                    ${item}
+                </foreach>
+            </if>
+            <if test="startDate != null and endDate != null">
+                AND data_date BETWEEN #{startDate} AND #{endDate}
+            </if>
+        </where>
+    </select>
+
+</mapper>

+ 60 - 0
fs-service-system/src/main/resources/mapper/statis/FsStatisPeriodWatchMapper.xml

@@ -0,0 +1,60 @@
+<?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="com.fs.statis.mapper.FsStatisPeriodWatchMapper">
+
+    <!-- 通用查询结果映射 -->
+    <resultMap id="BaseResultMap" type="com.fs.statis.domain.FsStatisPeriodWatch">
+        <id column="id" property="id" jdbcType="INTEGER" />
+        <result column="period_id" property="periodId" jdbcType="INTEGER" />
+        <result column="period_num" property="periodNum" jdbcType="INTEGER" />
+        <result column="not_reg_num" property="notRegNum" jdbcType="INTEGER" />
+        <result column="registered_num" property="registeredNum" jdbcType="INTEGER" />
+        <result column="reg_rate" property="regRate" jdbcType="DECIMAL" />
+        <result column="watch_completed_rate" property="watchCompletedRate" jdbcType="DECIMAL" />
+        <result column="offline_total" property="offlineTotal" jdbcType="INTEGER" />
+        <result column="offline_not_reg_num" property="offlineNotRegNum" jdbcType="INTEGER" />
+        <result column="offline_not_watch_num" property="offlineNotWatchNum" jdbcType="INTEGER" />
+        <result column="online_total" property="onlineTotal" jdbcType="INTEGER" />
+        <result column="online_rate" property="onlineRate" jdbcType="DECIMAL" />
+        <result column="online_watch_completed_rate" property="onlineWatchCompletedRate" jdbcType="DECIMAL" />
+        <result column="online_watch_not_completed" property="onlineWatchNotCompleted" jdbcType="INTEGER" />
+        <result column="online_watch_completed" property="onlineWatchCompleted" jdbcType="INTEGER" />
+        <result column="data_date" property="dataDate" jdbcType="DATE" />
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        id, period_id, period_num, not_reg_num, registered_num, reg_rate,
+        watch_completed_rate, offline_total, offline_not_reg_num, offline_not_watch_num,
+        online_total, online_rate, online_watch_completed_rate, online_watch_not_completed,
+        online_watch_completed, data_date
+    </sql>
+
+    <!-- 查询所有数据 -->
+    <select id="selectAll" resultMap="BaseResultMap">
+        SELECT
+        <include refid="Base_Column_List" />
+        FROM fs_statis_period_watch
+    </select>
+    <select id="queryList" resultType="com.fs.statis.domain.FsStatisPeriodWatch">
+        select * from fs_statis_period_watch
+        <where>
+            <if test="userIds != null and userIds.length > 0">
+                AND company_user_id IN
+                <foreach collection="userIds" open="(" close=")" separator="," item="item">
+                    ${item}
+                </foreach>
+            </if>
+            <if test="periodList != null and periodList.length > 0">
+                AND period_id IN
+                <foreach collection="periodList" open="(" close=")" separator="," item="item">
+                    ${item}
+                </foreach>
+            </if>
+            <if test="startDate != null and endDate != null">
+                AND data_date BETWEEN #{startDate} AND #{endDate}
+            </if>
+        </where>
+    </select>
+
+</mapper>

+ 86 - 0
fs-service-system/src/main/resources/mapper/statis/FsStatisSalerWatchMapper.xml

@@ -0,0 +1,86 @@
+<?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="com.fs.statis.mapper.FsStatisSalerWatchMapper"> <!-- 请替换为你的Mapper接口的完整路径 -->
+
+    <!-- 结果映射 -->
+    <resultMap id="BaseResultMap" type="com.fs.statis.domain.FsStatisSalerWatch"> <!-- 请替换为你的Entity的完整路径 -->
+        <id column="id" property="id" jdbcType="INTEGER"/>
+        <result column="dept_id" property="deptId" jdbcType="INTEGER"/>
+        <result column="company_user_id" property="companyUserId" jdbcType="INTEGER"/>
+        <result column="train_camp_num" property="trainCampNum" jdbcType="INTEGER"/>
+        <result column="not_registered_num" property="notRegisteredNum" jdbcType="INTEGER"/>
+        <result column="registered_num" property="registeredNum" jdbcType="INTEGER"/>
+        <result column="reg_rate" property="regRate" jdbcType="FLOAT"/>
+        <result column="finished_rate" property="finishedRate" jdbcType="FLOAT"/>
+        <result column="offline_total" property="offlineTotal" jdbcType="INTEGER"/>
+        <result column="offline_not_part" property="offlineNotPart" jdbcType="INTEGER"/> <!-- 对应修正后的 offlineNotPart -->
+        <result column="offline_not_watched" property="offlineNotWatched" jdbcType="INTEGER"/>
+        <result column="online_total" property="onlineTotal" jdbcType="INTEGER"/>
+        <result column="online_online_rate" property="onlineOnlineRate" jdbcType="FLOAT"/>
+        <result column="online_playback_comple_rate" property="onlinePlaybackCompleRate" jdbcType="FLOAT"/>
+        <result column="online_incomplete_playback" property="onlineIncompletePlayback" jdbcType="INTEGER"/>
+        <result column="online_complete_playback" property="onlineCompletePlayback" jdbcType="INTEGER"/>
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        id, dept_id, company_user_id, train_camp_num, not_registered_num, registered_num,
+        reg_rate, finished_rate, offline_total, offline_not_part, offline_not_watched,
+        online_total, online_online_rate, online_playback_comple_rate,
+        online_incomplete_playback, online_complete_playback
+    </sql>
+
+    <select id="queryList" resultType="com.fs.statis.domain.FsStatisSalerWatch">
+        select * from fs_statis_saler_watch
+        <where>
+            <if test="userIds != null and userIds.length > 0">
+                AND company_user_id IN
+                 <foreach collection="userIds" open="(" close=")" separator="," item="item">
+                     ${item}
+                </foreach>
+            </if>
+            <if test="periodList != null and periodList.length > 0">
+                AND period_id IN
+                <foreach collection="periodList" open="(" close=")" separator="," item="item">
+                    ${item}
+                </foreach>
+            </if>
+            <if test="startDate != null and endDate != null">
+                AND data_date BETWEEN #{startDate} AND #{endDate}
+            </if>
+        </where>
+    </select>
+
+    <insert id="batchSave">
+        INSERT INTO fs_statis_saler_watch (
+            dept_id, company_user_id, train_camp_num,
+            not_registered_num, registered_num, reg_rate,
+            finished_rate, offline_total, offline_not_part,
+            offline_not_watched, online_total, online_online_rate,
+            online_playback_comple_rate, online_incomplete_playback,
+            online_complete_playback,period_id,data_date
+        ) VALUES
+        <foreach collection="list" item="item" separator=",">
+            (
+                #{item.deptId,jdbcType=INTEGER},
+                #{item.companyUserId,jdbcType=INTEGER},
+                #{item.trainCampNum,jdbcType=INTEGER},
+                #{item.notRegisteredNum,jdbcType=INTEGER},
+                #{item.registeredNum,jdbcType=INTEGER},
+                #{item.regRate,jdbcType=FLOAT},
+                #{item.finishedRate,jdbcType=FLOAT},
+                #{item.offlineTotal,jdbcType=INTEGER},
+                #{item.offlineNotPart,jdbcType=INTEGER},
+                #{item.offlineNotWatched,jdbcType=INTEGER},
+                #{item.onlineTotal,jdbcType=INTEGER},
+                #{item.onlineOnlineRate,jdbcType=FLOAT},
+                #{item.onlinePlaybackCompleRate,jdbcType=FLOAT},
+                #{item.onlineIncompletePlayback,jdbcType=INTEGER},
+                #{item.onlineCompletePlayback,jdbcType=INTEGER},
+                #{item.periodId,jdbcType=INTEGER},
+                #{item.dataDate,jdbcType=VARCHAR}
+            )
+        </foreach>
+    </insert>
+
+</mapper>