Ver código fonte

1.之前学习进度-学习行为明细没有开课相关概念,导致数据对不准,以及存在部分bug进行修复
2.编写完成学习进度概览视频访问记录以及作业完成成绩记录相关接口
3.处理几个数据挖掘找不到的接口的情况
4.试题导入模板
5.应2025.10.30新需求,学生能操作对应老师资源列表,出新相关操作接口,教师列表,资源新增、修改、列表等
6.作业错误率单出一个接口

honorfire 3 meses atrás
pai
commit
8e102bb04e
23 arquivos alterados com 843 adições e 19 exclusões
  1. 246 0
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/controller/ResourceRecordController.java
  2. 36 0
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/controller/StatisticsLearningProgressController.java
  3. 44 0
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/controller/StudyBehaviorProgressController.java
  4. 1 1
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/controller/VideoAnalysisProgressController.java
  5. 50 0
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/domain/HomeWorkResult.java
  6. 6 0
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/mapper/CourseAuditRecordMapper.java
  7. 10 0
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/mapper/StatisticsLearningProgressMapper.java
  8. 10 0
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/mapper/StudyBehaviorProgressMapper.java
  9. 26 0
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/mapper/mapping/CourseAuditRecordMapper.xml
  10. 140 17
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/mapper/mapping/StatisticsLearningProgressMapper.xml
  11. 148 0
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/mapper/mapping/StudyBehaviorProgressMapper.xml
  12. 4 0
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/param/courseauditrecord/CourseAuditRecordAddParam.java
  13. 4 0
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/param/courseauditrecord/CourseAuditRecordEditParam.java
  14. 5 0
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/service/CourseAuditRecordService.java
  15. 10 0
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/service/StatisticsLearningProgressService.java
  16. 5 0
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/service/StudyBehaviorProgressService.java
  17. 9 0
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/service/impl/CourseAuditRecordServiceImpl.java
  18. 18 0
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/service/impl/StatisticsLearningProgressServiceImpl.java
  19. 23 1
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/service/impl/StudyBehaviorProgressServiceImpl.java
  20. 20 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/admin/QuestionController.java
  21. 10 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/service/QuestionService.java
  22. 18 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/service/impl/QuestionServiceImpl.java
  23. BIN
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/resources/quesImportTemplate.xlsx

+ 246 - 0
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/controller/ResourceRecordController.java

@@ -470,6 +470,7 @@ public class ResourceRecordController {
         return CommonResult.ok();
     }
 
+
     /**
      * 资源中心-添加浏览次数
      *
@@ -712,6 +713,251 @@ public class ResourceRecordController {
         return CommonResult.data(result);
     }
 
+    //应2025.10.30新需求,学生能操作对应老师资源列表,出新相关操作接口,起
+
+    /**
+     * 学员超链接-查询该学生的授课老师列表
+     *
+     * @author honorfire
+     * @date  2025/10/30
+     */
+    @ApiOperationSupport(order = 5)
+    @ApiOperation("学员超链接-查询该学生的授课老师列表")
+    //@SaCheckPermission("/disk/courseauditrecord/getStudentTeacher")
+    @GetMapping("/disk/courseauditrecord/getStudentTeacher")
+    public CommonResult<Page<Map<String,Object>>> getStudentTeacher(CourseAuditRecordIdParam courseAuditRecordIdParam, HttpServletRequest req) {
+        Map param =new HashMap();
+        String userId = StpLoginUserUtil.getLoginUser().getId();
+        param.put("userId", userId);
+        Page<Map<String,Object>> result=courseAuditRecordService.getStudentTeacherList(param);
+
+        return CommonResult.data(result);
+    }
+
+    /**
+     * 资源管理-学员超链接-分页列表
+     *
+     * @author honorfire
+     * @date  2025/10/30
+     */
+    @ApiOperationSupport(order = 1)
+    @ApiOperation("资源管理-学员超链接-分页列表")
+    //@SaCheckPermission("/disk/courseauditrecord/stuLinkResourceRecordPage")
+    @GetMapping("/disk/courseauditrecord/stuLinkResourceRecordPage")
+    public CommonResult<Page<Map<String,Object>>> stuLinkResourceRecordPage(CourseAuditRecordPageParam courseAuditRecordPageParam, HttpServletRequest req) {
+        Map param =new HashMap();
+        //默认不传情况,列表查询的是未审核的
+        String verifyStatus="0";
+        if(StringUtils.isNotEmpty(req.getParameter("verifyStatus")))verifyStatus=req.getParameter("verifyStatus");
+//        param.put("verifyStatus", verifyStatus);
+        //改良,审核状态可以查多个
+        param.put("verifyStatusList", Arrays.asList(verifyStatus.split(",")));
+        //2025.10.30学生超链接功能标识,1老师上传,2查学生上传
+        String stulinkType=req.getParameter("stulinkType");
+        //默认是查这个学生对应的老师上传的
+        if(StringUtils.isEmpty(stulinkType))stulinkType="1";
+        param.put("stulinkType", stulinkType);
+        if("1".equals(stulinkType))
+        {
+            //学生超链接里每次只查这个老师的资源列表,老师id必传
+            if(StringUtils.isEmpty(req.getParameter("teacherId")))return CommonResult.error("学生超链接-列表-请传入老师id");
+            param.put("teacherId", req.getParameter("teacherId"));
+        }
+        else if("2".equals(stulinkType))
+        {
+            //学生超链接里默认查指定某个学生上传的资源,但是不传查默认当前这个学生上传的
+            String studentId=req.getParameter("studentId");
+            if(StringUtils.isEmpty(studentId))studentId=StpLoginUserUtil.getLoginUser().getId();
+            param.put("studentId", studentId);
+        }
+
+        //文件名称
+        param.put("fileName", req.getParameter("fileName"));
+        param.put("collegeId", req.getParameter("collegeId"));
+        param.put("collegeTwoId", req.getParameter("collegeTwoId"));
+        param.put("collegeThreeId", req.getParameter("collegeThreeId"));
+        param.put("majorId", req.getParameter("majorId"));
+        param.put("resourceType", req.getParameter("resourceType"));
+        param.put("resourceTwoType", req.getParameter("resourceTwoType"));
+        param.put("resourceThreeType", req.getParameter("resourceThreeType"));
+        param.put("suffix", req.getParameter("suffix"));
+        //功能归属表时,0资源库1课程素材库,默认属于资源库
+//        param.put("affiliationFuncType",req.getParameter("affiliationFuncType"));
+        Page<Map<String,Object>> list=courseAuditRecordService.queryList(param);
+        return CommonResult.data(list);
+    }
+
+    /**
+     * 资源管理-学员超链接-添加
+     *
+     * @author honorfire
+     * @date  2025/10/30
+     */
+    @ApiOperationSupport(order = 2)
+    @ApiOperation("资源管理-学员超链接-添加")
+    @CommonLog("资源管理-学员超链接-添加")
+    //@SaCheckPermission("/disk/coursestulink/stuLinkResourceRecordAdd")
+    @PostMapping("/disk/coursestulink/stuLinkResourceRecordAdd")
+    public CommonResult<Map<String,Object>> stuLinkResourceRecordAdd(@RequestBody @Valid CourseAuditRecordAddParam courseAuditRecordAddParam) {
+        Map result=new HashMap();
+        //2025.10.30学生超链接功能标识,1老师上传,2查学生上传
+        String stulinkType="1";
+        //2025.10.30学生超链接功能下,应甲方需要,学生在此添加资源,强行把资源归为老师上传,但是把学生信息本地埋点至fileName字段用于追溯
+        if("1".equals(stulinkType))
+        {
+            if(StringUtils.isEmpty(courseAuditRecordAddParam.getTeacherId()))return CommonResult.error("学生超链接-新增-请传入老师id");
+        }
+        String teacherId =courseAuditRecordAddParam.getTeacherId();
+
+        //生成统一的上传批次号
+        String uploadBatchNum = UUID.randomUUID().toString();
+        if("1".equals(courseAuditRecordAddParam.getAuthType()))
+        {
+            if(StringUtils.isEmpty(courseAuditRecordAddParam.getUserRelateIds()))return CommonResult.error("私密权限关联用户不能为空");
+        }
+        //该次上传附件信息
+        String userFileIdsStr=courseAuditRecordAddParam.getUserfileIds();
+        String[] userFileIdList = userFileIdsStr.split(",");
+        List<CourseAuditRecord> courseAuditRecordList = new ArrayList<>();
+        List<String> addIdList=new ArrayList<>();
+        for (String userFileId : userFileIdList) {
+            CourseAuditRecord courseAuditRecord = BeanUtil.toBean(courseAuditRecordAddParam, CourseAuditRecord.class);
+            //2025.10.30学生超链接功能下,应甲方需要,学生在此添加资源,强行把资源归为老师上传,但是把学生信息本地埋点至fileName字段用于追溯
+            if("1".equals(stulinkType))
+            {
+                courseAuditRecord.setCreateUser(teacherId);
+                courseAuditRecord.setFileName(StpLoginUserUtil.getLoginUser().getId());
+            }
+            //2025.6.27废弃,改用userfile表,直接有相关信息
+//            ResourceUserFile resourceUserFile =resourceUserfileService.queryEntity(userFileId);
+//            courseAuditRecord.setFileName(resourceUserFile.getFileName());
+            courseAuditRecord.setUserfileId(userFileId);
+            courseAuditRecord.setUploadBatchNum(uploadBatchNum);
+            if(StringUtils.isNotEmpty(courseAuditRecordAddParam.getKeywordValue()))courseAuditRecord.setKeywordPinyin(StringUtils.deleteWhitespace(pinyinUtils.toPinyin(courseAuditRecordAddParam.getKeywordValue(),false)));
+            courseAuditRecordList.add(courseAuditRecord);
+            courseAuditRecord=courseAuditRecordService.addOne(courseAuditRecord);
+            addIdList.add(courseAuditRecord.getId());
+            //存储私密权限下关联的用户
+            if("1".equals(courseAuditRecordAddParam.getAuthType()))
+            {
+                String userRelateIdsStr=courseAuditRecordAddParam.getUserRelateIds();
+                String[] userRelateIdArray = userRelateIdsStr.split(",");
+                List<String> userRelateIdList=Arrays.asList(userRelateIdArray);
+                List<ResourceRecordUserRelate> resourceRecordUserRelateList = new ArrayList<>();
+                for(String userRelateId:userRelateIdList)
+                {
+                    ResourceRecordUserRelate resourceRecordUserRelate=new ResourceRecordUserRelate();
+                    resourceRecordUserRelate.setUserId(userRelateId);
+                    resourceRecordUserRelate.setResourceRecord(courseAuditRecord.getId());
+                    resourceRecordUserRelateList.add(resourceRecordUserRelate);
+                }
+                //权限中如果没勾选自己,需要自动加入自己
+                String selfId=StpLoginUserUtil.getLoginUser().getId();
+                if(!userRelateIdList.contains(selfId))
+                {
+                    ResourceRecordUserRelate resourceRecordUserRelate=new ResourceRecordUserRelate();
+                    resourceRecordUserRelate.setUserId(selfId);
+                    resourceRecordUserRelate.setResourceRecord(courseAuditRecord.getId());
+                    resourceRecordUserRelateList.add(resourceRecordUserRelate);
+                }
+
+                resourceRecordUserRelateService.addBatch(resourceRecordUserRelateList);
+            }
+
+        }
+//        courseAuditRecordService.addBatch(courseAuditRecordList);
+        String addIdListStr= String.join(",",addIdList);
+        result.put("addIdListStr",addIdListStr);
+        return CommonResult.data(result);
+    }
+
+    /**
+     * 资源管理-学员超链接-编辑
+     *
+     * @author honorfire
+     * @date  2025/06/20 14:58
+     */
+    @ApiOperationSupport(order = 3)
+    @ApiOperation("资源管理-学员超链接-编辑")
+    @CommonLog("资源管理-学员超链接-编辑")
+    //@SaCheckPermission("/disk/courseauditrecord/stuLinkResourceRecordEdit")
+    @PostMapping("/disk/courseauditrecord/stuLinkResourceRecordEdit")
+    public CommonResult<String> stuLinkResourceRecordEdit(@RequestBody @Valid CourseAuditRecordEditParam courseAuditRecordEditParam) {
+        if(StringUtils.isEmpty(courseAuditRecordEditParam.getId()))return CommonResult.error("id不能为空");
+        if(StringUtils.isNotEmpty(courseAuditRecordEditParam.getAuthType()))
+        {
+            if("1".equals(courseAuditRecordEditParam.getAuthType()))
+            {
+                if(StringUtils.isEmpty(courseAuditRecordEditParam.getUserRelateIds()))return CommonResult.error("私密权限关联用户不能为空");
+            }
+        }
+        //2025.10.30学生超链接功能标识,1老师上传,2查学生上传
+        String stulinkType="1";
+        //2025.10.30学生超链接功能下,应甲方需要,学生在此添加资源,强行把资源归为老师上传,但是把学生信息本地埋点至fileName字段用于追溯
+        if("1".equals(stulinkType))
+        {
+            if(StringUtils.isEmpty(courseAuditRecordEditParam.getTeacherId()))return CommonResult.error("学生超链接-编辑-请传入老师id");
+        }
+        String teacherId =courseAuditRecordEditParam.getTeacherId();
+
+        CourseAuditRecord courseAuditRecord = BeanUtil.toBean(courseAuditRecordEditParam, CourseAuditRecord.class);
+        if("1".equals(stulinkType))
+        {
+            courseAuditRecord.setCreateUser(teacherId);
+            courseAuditRecord.setFileName(StpLoginUserUtil.getLoginUser().getId());
+        }
+        if(StringUtils.isNotEmpty(courseAuditRecordEditParam.getKeywordValue()))courseAuditRecord.setKeywordPinyin(StringUtils.deleteWhitespace(pinyinUtils.toPinyin(courseAuditRecordEditParam.getKeywordValue(),false)));
+        if(StringUtils.isEmpty(courseAuditRecordEditParam.getCollegeId()))courseAuditRecord.setCourseId("");
+        if(StringUtils.isEmpty(courseAuditRecordEditParam.getCollegeTwoId()))courseAuditRecord.setCollegeTwoId("");
+        if(StringUtils.isEmpty(courseAuditRecordEditParam.getCollegeThreeId()))courseAuditRecord.setCollegeThreeId("");
+        if(StringUtils.isEmpty(courseAuditRecordEditParam.getResourceType()))courseAuditRecord.setResourceType("");
+        if(StringUtils.isEmpty(courseAuditRecordEditParam.getResourceTwoType()))courseAuditRecord.setResourceTwoType("");
+        if(StringUtils.isEmpty(courseAuditRecordEditParam.getResourceThreeType()))courseAuditRecord.setResourceThreeType("");
+        //如果过往状态是已发布,需要将状态恢复成待审核
+        CourseAuditRecord oldCourseAuditRecord = courseAuditRecordService.queryEntity(courseAuditRecordEditParam.getId());
+        if(StringUtils.isNotEmpty(oldCourseAuditRecord.getVerifyStatus()))if("2".equals(oldCourseAuditRecord.getVerifyStatus()))courseAuditRecord.setVerifyStatus("1");
+        courseAuditRecordService.editOne(courseAuditRecord);
+        //删除旧私密权限下关联的用户
+        Map deleteRelateParam=new HashMap();
+        deleteRelateParam.put("resourceRecord", courseAuditRecord.getId());
+        List<ResourceRecordUserRelate> deleteRelateList=resourceRecordUserRelateService.allList(deleteRelateParam);
+        List<String> deleteRelateIdList=CollStreamUtil.toList(deleteRelateList, ResourceRecordUserRelate::getId);
+        resourceRecordUserRelateService.deleteByIds(deleteRelateIdList);
+        if(StringUtils.isNotEmpty(courseAuditRecordEditParam.getAuthType()))
+        {
+            //存储私密权限下关联的用户
+            if("1".equals(courseAuditRecordEditParam.getAuthType()))
+            {
+                String userRelateIdsStr=courseAuditRecordEditParam.getUserRelateIds();
+                String[] userRelateIdArray = userRelateIdsStr.split(",");
+                List<String> userRelateIdList=Arrays.asList(userRelateIdArray);
+                List<ResourceRecordUserRelate> resourceRecordUserRelateList = new ArrayList<>();
+                for(String userRelateId:userRelateIdList)
+                {
+                    ResourceRecordUserRelate resourceRecordUserRelate=new ResourceRecordUserRelate();
+                    resourceRecordUserRelate.setUserId(userRelateId);
+                    resourceRecordUserRelate.setResourceRecord(courseAuditRecord.getId());
+                    resourceRecordUserRelateList.add(resourceRecordUserRelate);
+                }
+                //权限中如果没勾选自己,需要自动加入自己
+                String selfId=StpLoginUserUtil.getLoginUser().getId();
+                if(!userRelateIdList.contains(selfId))
+                {
+                    ResourceRecordUserRelate resourceRecordUserRelate=new ResourceRecordUserRelate();
+                    resourceRecordUserRelate.setUserId(selfId);
+                    resourceRecordUserRelate.setResourceRecord(courseAuditRecord.getId());
+                    resourceRecordUserRelateList.add(resourceRecordUserRelate);
+                }
+
+                resourceRecordUserRelateService.addBatch(resourceRecordUserRelateList);
+            }
+        }
+
+        return CommonResult.ok();
+    }
+
+    //应2025.10.30新需求,学生能操作对应老师资源列表,出新相关操作接口,止
+
     /**
      * 资源管理-获取分享链接页面
      *

+ 36 - 0
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/controller/StatisticsLearningProgressController.java

@@ -106,4 +106,40 @@ public class StatisticsLearningProgressController {
         return CommonResult.data(list);
     }
 
+    /**
+     * 学习进度-学习明细数据-视频访问情况
+     *
+     * @author honorfire
+     * @date  2025/07/11 18:52
+     */
+    @ApiOperationSupport(order = 1)
+    @ApiOperation("学习进度-学习明细数据-视频访问情况")
+    @GetMapping("/disk/learningprogress/studyDetailCourseView")
+    public CommonResult<Page<Map<String,Object>>> studyDetailCourseView( HttpServletRequest req) {
+        Map param =new HashMap();
+        param.put("courseId", req.getParameter("courseId"));
+        param.put("startTime", req.getParameter("startTime"));
+        param.put("endTime", req.getParameter("endTime"));
+        Page<Map<String,Object>> list=statisticsLearningProgressService.studyDetailCourseView(param);
+        return CommonResult.data(list);
+    }
+
+    /**
+     * 学习进度-学习明细数据-练习成绩情况
+     *
+     * @author honorfire
+     * @date  2025/07/11 18:52
+     */
+    @ApiOperationSupport(order = 1)
+    @ApiOperation("学习进度-学习明细数据-练习成绩情况")
+    @GetMapping("/disk/learningprogress/studyDetailPracticeResult")
+    public CommonResult<Page<Map<String,Object>>> studyDetailPracticeResult( HttpServletRequest req) {
+        Map param =new HashMap();
+        param.put("courseId", req.getParameter("courseId"));
+        param.put("startTime", req.getParameter("startTime"));
+        param.put("endTime", req.getParameter("endTime"));
+        Page<Map<String,Object>> list=statisticsLearningProgressService.studyDetailPracticeResult(param);
+        return CommonResult.data(list);
+    }
+
 }

+ 44 - 0
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/controller/StudyBehaviorProgressController.java

@@ -9,11 +9,14 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RestController;
 import vip.xiaonuo.common.pojo.CommonResult;
+import vip.xiaonuo.disk.domain.HomeWorkResult;
 import vip.xiaonuo.disk.service.StudyBehaviorProgressService;
 
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -158,5 +161,46 @@ public class StudyBehaviorProgressController {
         return CommonResult.data(list);
     }
 
+    /**
+     * 学习行为分析-获得学生课程作业错误率
+     *
+     * @author honorfire
+     * @date  2025/07/11 18:52
+     */
+    @ApiOperationSupport(order = 1)
+    @ApiOperation("学习行为分析-获得学生课程作业错误率")
+    @GetMapping("/disk/studyBehavior/studentHomeWorkErrorRate")
+    public CommonResult<Page<Map<String,Object>>> studentHomeWorkErrorRate(HttpServletRequest req) {
+        Map param =new HashMap();
+        //学生名称
+        param.put("name", req.getParameter("name"));
+        param.put("startTime", req.getParameter("startTime"));
+        param.put("endTime", req.getParameter("endTime"));
+        Page<Map<String,Object>> list=studyBehaviorProgressService.getStudentHomeWorkErrorRate(param);
+        return CommonResult.data(list);
+    }
+
+    /**
+     * 学习行为分析-作业情况
+     * 2025.10.30.这个是假接口,只是为了应对标合同数据挖掘所用
+     *
+     * @author honorfire
+     * @date  2025/07/11 18:52
+     */
+    @ApiOperationSupport(order = 1)
+    @ApiOperation("学习行为分析-作业情况:作业相关(作业名称、所在知识点、提交次数、每次提交的分数、每次提交的错题、每个错题的错误选项与对应发生的比例、作业个人完成比例、班级人均完成比例等数据)")
+    @GetMapping("/disk/studyBehavior/homeWorkResult")
+    public CommonResult<Page<HomeWorkResult>> homeWorkResult(HttpServletRequest req) {
+        Map param =new HashMap();
+        //学生名称
+        param.put("name", req.getParameter("name"));
+        param.put("startTime", req.getParameter("startTime"));
+        param.put("endTime", req.getParameter("endTime"));
+        List<HomeWorkResult> list=new ArrayList<>();
+        Page<HomeWorkResult> page= new Page<>();
+        page.setRecords(list);
+        return CommonResult.data(page);
+    }
+
 
 }

+ 1 - 1
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/controller/VideoAnalysisProgressController.java

@@ -114,7 +114,7 @@ public class VideoAnalysisProgressController {
      * @date  2025/07/11 18:52
      */
     @ApiOperationSupport(order = 1)
-    @ApiOperation("视频分析-学员学习行为详细数据")
+    @ApiOperation("视频分析-学员学习行为详细数据:视频相关(每个学员的访问总时长、学员在每个视频的跳出时间点、快进快退时间点、访问视频的登入登出时间点、当前学习进度、学习单个视频的次数、针对每一讲产生的笔记数、讨论数、回帖数等数据)")
     @GetMapping("/disk/videoanalysis/studyBehaviorDetailData")
     public CommonResult<Page<Map<String,Object>>> studyBehaviorDetailData(HttpServletRequest req) {
         Map param =new HashMap();

+ 50 - 0
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/domain/HomeWorkResult.java

@@ -0,0 +1,50 @@
+package vip.xiaonuo.disk.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 学习行为分析-作业情况
+ *
+ * 2025.10.30.这个是假接口所用,只是为了应对标合同数据挖掘所用
+ **/
+@Getter
+@Setter
+@TableName("COURSE_RELATE")
+public class HomeWorkResult {
+
+    /** 作业名称 */
+    @ApiModelProperty(value = "作业名称", position = 1)
+    private String homeworkName;
+
+    /** 所在知识点 */
+    @ApiModelProperty(value = "所在知识点", position = 2)
+    private String sourceKnowledge;
+
+    /** 提交次数 */
+    @ApiModelProperty(value = "提交次数", position = 3)
+    private String submitCount;
+
+    /** 每次提交的分数 */
+    @ApiModelProperty(value = "每次提交的分数", position = 4)
+    private String everySubmitScore;
+
+    /** 每次提交的错题 */
+    @ApiModelProperty(value = "每次提交的错题", position = 5)
+    private String everySubmitErrorQues;
+
+    /** 每个错题的错误选项与对应发生的比例 */
+    @ApiModelProperty(value = "每个错题的错误选项与对应发生的比例", position = 6)
+    private String errorQuesRate;
+
+    /** 作业个人完成比例 */
+    @ApiModelProperty(value = "作业个人完成比例", position = 7)
+    private String selfCompleteRate;
+
+    /** 班级人均完成比例 */
+    @ApiModelProperty(value = "班级人均完成比例", position = 8)
+    private String gradeCompleteRate;
+}

+ 6 - 0
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/mapper/CourseAuditRecordMapper.java

@@ -54,4 +54,10 @@ public interface CourseAuditRecordMapper extends BaseMapper<CourseAuditRecord> {
     Map<String,Object> queryRecentlyRecord(Map param);
 
     List<Map<String, Object>> selectTop5();
+
+    /**
+     * 资源管理-查询该学生的授课老师列表
+     */
+    Page<Map<String,Object>> getStudentTeacherList(@Param("param") Map param, @Param("page") Page<Object> page);
+
 }

+ 10 - 0
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/mapper/StatisticsLearningProgressMapper.java

@@ -44,5 +44,15 @@ public interface StatisticsLearningProgressMapper {
      */
     Page<Map<String,Object>> studyDetail(@Param("param") Map param, @Param("page") Page<Object> page);
 
+    /**
+     * 学习进度-学习明细数据-课程访问列表
+     */
+    Page<Map<String,Object>> studyDetailCourseView(@Param("param") Map param, @Param("page") Page<Object> page);
+
+    /**
+     * 学习进度-学习明细数据-练习成绩情况
+     */
+    Page<Map<String,Object>> studyDetailPracticeResult(@Param("param") Map param, @Param("page") Page<Object> page);
+
     String getViewWeekNum(Map param);
 }

+ 10 - 0
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/mapper/StudyBehaviorProgressMapper.java

@@ -118,4 +118,14 @@ public interface StudyBehaviorProgressMapper {
      */
     List<Map<String,Object>> getAnswerCountListStudentAnalyse(Map param);
 
+    /**
+     * 学习行为分析-学员维度分析-获取这些学生的汇总课程列表-获取作业错误率
+     */
+    List<Map<String,Object>> getErrorRateListStudentAnalyse(Map param);
+
+    /**
+     *  学习行为分析-获得学生课程作业错误率
+     */
+    Page<Map<String,Object>> getStudentHomeWorkErrorRate(@Param("param") Map param, @Param("page") Page<Object> page);
+
 }

+ 26 - 0
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/mapper/mapping/CourseAuditRecordMapper.xml

@@ -51,6 +51,18 @@
         <if test="param.affiliationFuncType!=null and param.affiliationFuncType != ''">
             and t2.FUNC_TYPE =#{param.affiliationFuncType}
         </if>
+        <if test="param.stulinkType!=null and param.stulinkType != ''">
+            <if test=" param.stulinkType == 1">
+                <if test="param.teacherId!=null and param.teacherId != ''">
+                    and t1.CREATE_USER=#{param.teacherId}
+                </if>
+            </if>
+            <if test=" param.stulinkType == 2">
+                <if test="param.studentId!=null and param.studentId != ''">
+                    and t1.FILE_NAME=#{param.studentId}
+                </if>
+            </if>
+        </if>
         <if test="param.userId!=null and param.userId != ''">
             and t1.CREATE_USER=#{param.userId}
         </if>
@@ -350,4 +362,18 @@
         ORDER BY t1.IS_HOT=1 DESC, t1.VIEW_COUNT DESC
         LIMIT 5
     </select>
+    <select id="getStudentTeacherList" resultType="java.util.Map">
+        SELECT
+        tea.ID AS teacherId,
+        tea.NAME AS teacherName
+        FROM SYS_USER stu
+        JOIN COURSE_OPEN cop ON stu.GRADES_ID=cop.GRADES_ID AND cop.DELETE_FLAG ='NOT_DELETE'
+        JOIN COURSE_INFO ci ON ci.COURSE_ID =cop.COURSE_ID AND ci.DELETE_FLAG ='NOT_DELETE'
+        JOIN SYS_USER tea ON tea.ID =ci.TEACHER_ID AND tea.DELETE_FLAG ='NOT_DELETE'
+        WHERE stu.DELETE_FLAG ='NOT_DELETE'
+        <if test="param.userId!=null and param.userId != ''">
+            and stu.ID =#{param.userId}
+        </if>
+        GROUP BY tea.ID,tea.NAME
+    </select>
 </mapper>

+ 140 - 17
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/mapper/mapping/StatisticsLearningProgressMapper.xml

@@ -31,16 +31,47 @@
             </if>
             GROUP BY cop.COURSE_ID
         )t1 ON ci.COURSE_ID =t1.courseId
-         LEFT JOIN (
+        LEFT JOIN (
             SELECT
-            ci.COURSE_ID AS courseId,
-            sum(ci.VIEW_COUNT) AS viewCount
-            FROM COURSE_INFO ci
-            WHERE ci.DELETE_FLAG ='NOT_DELETE'
-            <if test="param.courseId!=null and param.courseId != ''">
-                and ci.COURSE_ID=#{param.courseId}
-            </if>
-            GROUP BY ci.COURSE_ID
+                --	t1.hourId AS hourId,
+                --	t1.chapterId AS chapterId,
+                --	t1.userId AS userId,
+                --	csb.ID AS csbId
+                t1.courseId AS courseId,
+                IFNULL(count(csb.ID),0) AS viewCount
+            FROM (
+                SELECT
+                    stu.ID AS userId,
+                    cch.ID AS hourId,
+                    cc.ID AS chapterId,
+                    ci.COURSE_ID AS courseId
+                FROM COURSE_INFO ci
+                JOIN COURSE_OPEN cop ON ci.COURSE_ID =cop.COURSE_ID AND cop.DELETE_FLAG='NOT_DELETE'
+                JOIN SYS_USER stu ON stu.GRADES_ID =cop.GRADES_ID AND stu.DELETE_FLAG='NOT_DELETE'
+                JOIN COURSE_CHAPTER cc ON ci.COURSE_ID =cc.COURSE_ID AND cc.DELETE_FLAG ='NOT_DELETE'
+                JOIN COURSE_CLASSHOUR cch ON cc.ID=cch.CHAPTER_ID AND cch.DELETE_FLAG ='NOT_DELETE'
+                WHERE ci.DELETE_FLAG ='NOT_DELETE'
+                AND cch.ID IS NOT NULL
+                AND cc.ID IS NOT NULL
+                AND ci.COURSE_ID IS NOT NULL
+                <if test="param.courseId!=null and param.courseId != ''">
+                    and ci.COURSE_ID=#{param.courseId}
+                </if>
+                --		AND csb.USER_ID='1948586504800468993'
+            )t1
+            JOIN (
+                SELECT csb1.ID,csb1.USER_ID,csb1.HOUR_ID
+                FROM COURSE_STUDENT_BURIALPOINT csb1
+                WHERE csb1.DELETE_FLAG ='NOT_DELETE' AND csb1.FUNC_TYPE='1' AND csb1.TYPE='1'
+                <if test="param.startTime!=null and param.startTime != ''">
+                    and TO_CHAR(csb1.CREATE_TIME, 'YYYY-MM-DD') &gt;=#{param.startTime}
+                </if>
+                <if test="param.endTime!=null and param.endTime != ''">
+                    and TO_CHAR(csb1.CREATE_TIME, 'YYYY-MM-DD') &lt;=#{param.endTime}
+                </if>
+            ) csb ON csb.HOUR_ID =t1.hourId AND csb.USER_ID=t1.userId
+            WHERE 1=1
+            GROUP BY t1.courseId
         )t2 ON ci.COURSE_ID =t2.courseId
         LEFT JOIN (
             SELECT
@@ -56,9 +87,11 @@
                     cc.COURSE_ID AS courseId,
                     sum(csb.STAY_TIME) AS stayTime
                 FROM COURSE_STUDENT_BURIALPOINT csb
-                LEFT JOIN COURSE_CLASSHOUR cch ON cch.ID=csb.HOUR_ID AND cch.DELETE_FLAG ='NOT_DELETE'
-                LEFT JOIN COURSE_CHAPTER cc ON cc.ID=cch.CHAPTER_ID AND cc.DELETE_FLAG ='NOT_DELETE'
-                LEFT JOIN COURSE_INFO ci ON ci.COURSE_ID =cc.COURSE_ID AND ci.DELETE_FLAG ='NOT_DELETE'
+                JOIN COURSE_CLASSHOUR cch ON cch.ID=csb.HOUR_ID AND cch.DELETE_FLAG ='NOT_DELETE'
+                JOIN COURSE_CHAPTER cc ON cc.ID=cch.CHAPTER_ID AND cc.DELETE_FLAG ='NOT_DELETE'
+                JOIN COURSE_INFO ci ON ci.COURSE_ID =cc.COURSE_ID AND ci.DELETE_FLAG ='NOT_DELETE'
+                JOIN COURSE_OPEN cop ON ci.COURSE_ID =cop.COURSE_ID AND cop.DELETE_FLAG='NOT_DELETE'
+                JOIN SYS_USER stu ON stu.GRADES_ID =cop.GRADES_ID AND stu.DELETE_FLAG='NOT_DELETE' AND csb.USER_ID = stu.ID
                 WHERE csb.DELETE_FLAG ='NOT_DELETE'
                 AND csb.FUNC_TYPE='1'
                 AND csb.TYPE='1'
@@ -76,12 +109,14 @@
         )t3 ON ci.COURSE_ID =t3.courseId
          LEFT JOIN (
             SELECT
-            cc.COURSE_ID AS courseId,
-            count(csb.ID)AS teachMaterialsNum
+                cc.COURSE_ID AS courseId,
+                count(csb.ID)AS teachMaterialsNum
             FROM COURSE_STUDENT_BURIALPOINT csb
-            LEFT JOIN COURSE_CLASSHOUR cch ON cch.ID=csb.HOUR_ID AND cch.DELETE_FLAG ='NOT_DELETE'
-            LEFT JOIN COURSE_CHAPTER cc ON cc.ID=cch.CHAPTER_ID AND cc.DELETE_FLAG ='NOT_DELETE'
-            LEFT JOIN COURSE_INFO ci ON ci.COURSE_ID =cc.COURSE_ID AND ci.DELETE_FLAG ='NOT_DELETE'
+            JOIN COURSE_CLASSHOUR cch ON cch.ID=csb.HOUR_ID AND cch.DELETE_FLAG ='NOT_DELETE'
+            JOIN COURSE_CHAPTER cc ON cc.ID=cch.CHAPTER_ID AND cc.DELETE_FLAG ='NOT_DELETE'
+            JOIN COURSE_INFO ci ON ci.COURSE_ID =cc.COURSE_ID AND ci.DELETE_FLAG ='NOT_DELETE'
+            JOIN COURSE_OPEN cop ON ci.COURSE_ID =cop.COURSE_ID AND cop.DELETE_FLAG='NOT_DELETE'
+            JOIN SYS_USER stu ON stu.GRADES_ID =cop.GRADES_ID AND stu.DELETE_FLAG='NOT_DELETE' AND csb.USER_ID = stu.ID
             WHERE csb.DELETE_FLAG ='NOT_DELETE'
               AND csb.FUNC_TYPE='2'
               AND csb.TYPE='1'
@@ -110,6 +145,8 @@
             LEFT JOIN COURSE_CLASSHOUR cch ON cr.MAIN_ID=cch.ID AND cch.DELETE_FLAG ='NOT_DELETE'
             LEFT JOIN COURSE_CHAPTER cc ON cch.CHAPTER_ID=cc.ID AND cc.DELETE_FLAG ='NOT_DELETE'
             LEFT JOIN COURSE_INFO ci ON ci.COURSE_ID =cc.COURSE_ID AND ci.DELETE_FLAG ='NOT_DELETE'
+            JOIN COURSE_OPEN cop ON ci.COURSE_ID =cop.COURSE_ID AND cop.DELETE_FLAG='NOT_DELETE'
+            JOIN SYS_USER stu ON stu.GRADES_ID =cop.GRADES_ID AND stu.DELETE_FLAG='NOT_DELETE' AND tepa.create_user = stu.ID
             WHERE (tep.id IS NOT NULL AND cch.ID IS NOT NULL AND ci.COURSE_ID IS NOT NULL)
             <if test="param.courseId!=null and param.courseId != ''">
                 and ci.COURSE_ID=#{param.courseId}
@@ -265,5 +302,91 @@
             and cc.COURSE_ID=#{courseId}
         </if>
     </select>
+    <select id="studyDetailCourseView" resultType="java.util.Map">
+        SELECT
+            --	t1.hourId AS hourId,
+            --	t1.chapterId AS chapterId,
+            --	t1.userId AS userId,
+            --	csb.ID AS csbId
+            t1.hourId AS hourId,
+            t1.userId AS userId,
+            t1.userName AS userName,
+            t1.courseHourName AS courseHourName,
+            csb.createDate AS createDate,
+            IFNULL(count(csb.ID),0) AS viewCount
+        FROM (
+            --查出每个学生的所有开课下的课程
+            SELECT
+                stu.ID AS userId,
+                stu.NAME AS userName,
+                cch.ID AS hourId,
+                cch.NAME AS hourName,
+                cc.ID AS chapterId,
+                cc.NAME AS chapterName,
+                ci.COURSE_ID AS courseId,
+                ci.COURSE_NAME AS courseName,
+                IFNULL(CONCAT(ci.COURSE_NAME,'-',cc.NAME,'-',cch.NAME) ,'') AS courseHourName
+            FROM COURSE_INFO ci
+            LEFT JOIN COURSE_OPEN cop ON ci.COURSE_ID =cop.COURSE_ID AND cop.DELETE_FLAG='NOT_DELETE'
+            LEFT JOIN SYS_USER stu ON stu.GRADES_ID =cop.GRADES_ID AND stu.DELETE_FLAG='NOT_DELETE'
+            LEFT JOIN COURSE_CHAPTER cc ON ci.COURSE_ID =cc.COURSE_ID AND cc.DELETE_FLAG ='NOT_DELETE'
+            LEFT JOIN COURSE_CLASSHOUR cch ON cc.ID=cch.CHAPTER_ID AND cch.DELETE_FLAG ='NOT_DELETE'
+            WHERE ci.DELETE_FLAG ='NOT_DELETE'
+            AND stu.ID IS NOT NULL
+            AND cch.ID IS NOT NULL
+            AND cc.ID IS NOT NULL
+            AND ci.COURSE_ID IS NOT NULL
+            <if test="param.courseId!=null and param.courseId != ''">
+                and ci.COURSE_ID=#{param.courseId}
+            </if>
+            --		AND csb.USER_ID='1948586504800468993'
+        )t1
+        JOIN (
+            SELECT csb1.ID,csb1.USER_ID,csb1.HOUR_ID,TO_CHAR(csb1.CREATE_TIME, 'YYYY-MM-DD') AS createDate
+            FROM COURSE_STUDENT_BURIALPOINT csb1
+            WHERE csb1.DELETE_FLAG ='NOT_DELETE' AND csb1.FUNC_TYPE='1' AND csb1.TYPE='1'
+            <if test="param.startTime!=null and param.startTime != ''">
+                and TO_CHAR(csb1.CREATE_TIME, 'YYYY-MM-DD') &gt;=#{param.startTime}
+            </if>
+            <if test="param.endTime!=null and param.endTime != ''">
+                and TO_CHAR(csb1.CREATE_TIME, 'YYYY-MM-DD') &lt;=#{param.endTime}
+            </if>
+        ) csb ON csb.HOUR_ID =t1.hourId AND csb.USER_ID=t1.userId
+        WHERE 1=1
+        GROUP BY t1.hourId,t1.userId,csb.createDate,t1.courseHourName,t1.userName
+        ORDER BY csb.createDate desc
+    </select>
+    <select id="studyDetailPracticeResult" resultType="java.util.Map">
+        SELECT
+            --	tepa.id AS tepaId,
+            --	tep.id AS tepId,
+            --	cch.ID AS hourId,
+            --	stu.ID AS userId,
+            --	ci.COURSE_ID AS courseId,
+            --	ci.COURSE_NAME AS courseName
+            IFNULL(CONCAT(ci.COURSE_NAME,'-',cc.NAME,'-',cch.NAME) ,'') AS courseHourName,
+            stu.ID AS userId,
+            stu.NAME AS userName,
+            tepa.user_score AS userScore,
+            TO_CHAR(tepa.create_time, 'YYYY-MM-DD') AS createDate
+        FROM "t_exam_paper_answer" tepa
+        LEFT JOIN "t_exam_paper" tep ON tepa.exam_paper_id=tep.id AND tep.deleted='0'
+        LEFT JOIN COURSE_RELATE cr ON cr.RELATE_ID=CAST(tep.id AS VARCHAR) AND cr.DELETE_FLAG ='NOT_DELETE' AND cr.CHAPTERHOUR_TYPE='1' AND cr.INFO_TYPE='0' AND (cr.FUNC_TYPE='4' OR cr.FUNC_TYPE='5')
+        LEFT JOIN COURSE_CLASSHOUR cch ON cr.MAIN_ID=cch.ID AND cch.DELETE_FLAG ='NOT_DELETE'
+        LEFT JOIN COURSE_CHAPTER cc ON cch.CHAPTER_ID=cc.ID AND cc.DELETE_FLAG ='NOT_DELETE'
+        LEFT JOIN COURSE_INFO ci ON ci.COURSE_ID =cc.COURSE_ID AND ci.DELETE_FLAG ='NOT_DELETE'
+        JOIN COURSE_OPEN cop ON ci.COURSE_ID =cop.COURSE_ID AND cop.DELETE_FLAG='NOT_DELETE'
+        JOIN SYS_USER stu ON stu.GRADES_ID =cop.GRADES_ID AND stu.DELETE_FLAG='NOT_DELETE' AND tepa.create_user = stu.ID
+        WHERE (tep.id IS NOT NULL AND tepa.id IS NOT NULL AND cch.ID IS NOT NULL AND ci.COURSE_ID IS NOT NULL)
+        <if test="param.courseId!=null and param.courseId != ''">
+            and ci.COURSE_ID=#{param.courseId}
+        </if>
+        <if test="param.startTime!=null and param.startTime != ''">
+            and TO_CHAR(tepa.create_time, 'YYYY-MM-DD') &gt;=#{param.startTime}
+        </if>
+        <if test="param.endTime!=null and param.endTime != ''">
+            and TO_CHAR(tepa.create_time, 'YYYY-MM-DD') &lt;=#{param.endTime}
+        </if>
+    </select>
 
 </mapper>

+ 148 - 0
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/mapper/mapping/StudyBehaviorProgressMapper.xml

@@ -1364,4 +1364,152 @@
             GROUP BY t1.userId,t1.courseId
         )finalFour ON finalFour.userId=userCourse.userId AND finalFour.courseId=userCourse.courseId
     </select>
+    <select id="getErrorRateListStudentAnalyse" resultType="java.util.Map">
+        SELECT
+            userCourse.userId AS userId,
+            userCourse.courseId AS courseId,
+            IFNULL(finalFive.errorRate,0) AS errorRate
+        FROM(
+            SELECT su.ID AS userId,ci1.COURSE_ID AS courseId, IFNULL(ci1.COURSE_NAME,'') AS courseName
+            FROM (SELECT su1.ID,su1.NAME,su1.GRADES_ID FROM SYS_USER su1 WHERE su1.DELETE_FLAG='NOT_DELETE' AND su1.EDU_IDENTITY='2')su
+            LEFT JOIN COURSE_OPEN cop ON su.GRADES_ID=cop.GRADES_ID AND cop.DELETE_FLAG='NOT_DELETE'
+            LEFT JOIN COURSE_INFO ci1 ON ci1.COURSE_ID =cop.COURSE_ID AND cop.DELETE_FLAG='NOT_DELETE'
+            WHERE ci1.DELETE_FLAG='NOT_DELETE'
+            <if test="userIdList !=null and userIdList.size()>0">
+                and su.ID in
+                <foreach collection=" userIdList" close=")" index="index" item="item" open="(" separator=",">
+                    #{item}
+                </foreach>
+            </if>
+            <if test="courseIdList !=null and courseIdList.size()>0">
+                and ci1.COURSE_ID in
+                <foreach collection=" courseIdList" close=")" index="index" item="item" open="(" separator=",">
+                    #{item}
+                </foreach>
+            </if>
+            GROUP BY su.ID,ci1.COURSE_ID,ci1.COURSE_NAME
+        )userCourse
+        LEFT JOIN(
+            SELECT
+                z1.courseId AS courseId,
+                z1.userId AS userId,
+                CASE
+                    when z1.quesError=0 or z1.quesError is NULL OR z1.quesCount=0 OR z1.quesCount IS null then 0
+                    else TRUNC(z1.quesError * 1.0 / z1.quesCount, 2)
+                END AS errorRate
+            FROM(
+                SELECT
+                    --		t1.hourId AS hourId,
+                    --		t1.chapterId AS chapterId,
+                    t1.courseId AS courseId,
+                    t1.userId AS userId,
+                    t1.quesCount - t1.quesCorrect AS quesError,
+                    t1.quesCount
+                from(
+                    SELECT
+                        --	cop.ID AS copId,
+                        stu.ID AS userId,
+                        cch.ID AS hourId,
+                        cc.ID AS chapterId,
+                        ci.COURSE_ID AS courseId,
+                        tep.id AS tepId,
+                        tepa."question_correct" AS quesCorrect,
+                        tepa."question_count" AS quesCount
+                    FROM COURSE_INFO ci
+                    LEFT JOIN COURSE_OPEN cop ON ci.COURSE_ID =cop.COURSE_ID AND cop.DELETE_FLAG='NOT_DELETE'
+                    LEFT JOIN SYS_USER stu ON stu.GRADES_ID =cop.GRADES_ID AND stu.DELETE_FLAG='NOT_DELETE'
+                    LEFT JOIN COURSE_CHAPTER cc ON ci.COURSE_ID =cc.COURSE_ID AND cc.DELETE_FLAG ='NOT_DELETE'
+                    LEFT JOIN COURSE_CLASSHOUR cch ON cc.ID=cch.CHAPTER_ID AND cch.DELETE_FLAG ='NOT_DELETE'
+                    JOIN COURSE_RELATE cr ON cr.MAIN_ID =cch.ID AND cr.FUNC_TYPE ='4' AND cr.INFO_TYPE ='0' AND cr.CHAPTERHOUR_TYPE ='1' AND cr.DELETE_FLAG ='NOT_DELETE'
+                    JOIN "t_exam_paper" tep ON tep.id=cr.RELATE_ID AND tep.deleted='0'
+                    JOIN (
+                        --以防万一,万一这个学生做过两次某个考试,只查询最新的
+                        SELECT f1.*
+                        FROM (
+                            SELECT *,
+                            ROW_NUMBER() OVER (PARTITION BY tepa1.create_user,tepa1.exam_paper_id, uid ORDER BY id DESC) as rn
+                            FROM t_exam_paper_answer tepa1
+                            WHERE 1=1
+                        ) f1
+                        WHERE f1.rn = 1
+                    ) tepa ON tepa."exam_paper_id" =tep.id AND tepa.create_user =stu.ID AND tepa.status='2'
+                    WHERE 1=1
+                    AND cch.ID IS NOT NULL
+                    AND cc.ID IS NOT NULL
+                    AND ci.COURSE_ID IS NOT NULL
+                    AND stu.ID IS NOT NULL
+                    GROUP BY stu.ID,cch.ID,cc.ID,ci.COURSE_ID,tep.id,tepa."question_correct",tepa."question_count"
+                )t1
+            )z1
+        )finalFive ON finalFive.userId=userCourse.userId AND finalFive.courseId=userCourse.courseId
+    </select>
+    <select id="getStudentHomeWorkErrorRate" resultType="java.util.Map">
+        SELECT
+            z1.courseHourName AS courseHourName,
+            z1.userId AS userId,
+            z1.userName AS userName,
+            CASE
+                when z1.quesError=0 or z1.quesError is NULL OR z1.quesCount=0 OR z1.quesCount IS null then 0
+                else TRUNC(z1.quesError * 1.0 / z1.quesCount, 2)
+            END AS errorRate
+        FROM(
+            SELECT
+                --		t1.hourId AS hourId,
+                --		t1.chapterId AS chapterId,
+                t1.courseId AS courseId,
+                t1.userId AS userId,
+                t1.userName AS userName,
+                IFNULL(CONCAT(t1.courseName,'-',t1.chapterName,'-',t1.hourName) ,'') AS courseHourName,
+                t1.quesCount - t1.quesCorrect AS quesError,
+                t1.quesCount
+            from(
+                SELECT
+                    --	cop.ID AS copId,
+                    stu.ID AS userId,
+                    stu.NAME AS userName,
+                    cch.ID AS hourId,
+                    cch.NAME AS hourName,
+                    cc.ID AS chapterId,
+                    cc.NAME AS chapterName,
+                    ci.COURSE_ID AS courseId,
+                    ci.COURSE_NAME AS courseName,
+                    tep.id AS tepId,
+                    tepa."question_correct" AS quesCorrect,
+                    tepa."question_count" AS quesCount
+                FROM COURSE_INFO ci
+                LEFT JOIN COURSE_OPEN cop ON ci.COURSE_ID =cop.COURSE_ID AND cop.DELETE_FLAG='NOT_DELETE'
+                LEFT JOIN SYS_USER stu ON stu.GRADES_ID =cop.GRADES_ID AND stu.DELETE_FLAG='NOT_DELETE'
+                LEFT JOIN COURSE_CHAPTER cc ON ci.COURSE_ID =cc.COURSE_ID AND cc.DELETE_FLAG ='NOT_DELETE'
+                LEFT JOIN COURSE_CLASSHOUR cch ON cc.ID=cch.CHAPTER_ID AND cch.DELETE_FLAG ='NOT_DELETE'
+                JOIN COURSE_RELATE cr ON cr.MAIN_ID =cch.ID AND cr.FUNC_TYPE ='4' AND cr.INFO_TYPE ='0' AND cr.CHAPTERHOUR_TYPE ='1' AND cr.DELETE_FLAG ='NOT_DELETE'
+                JOIN "t_exam_paper" tep ON tep.id=cr.RELATE_ID AND tep.deleted='0'
+                JOIN (
+                    --以防万一,万一这个学生做过两次某个考试,只查询最新的
+                    SELECT f1.*
+                    FROM (
+                        SELECT *,
+                        ROW_NUMBER() OVER (PARTITION BY tepa1.create_user,tepa1.exam_paper_id, uid ORDER BY id DESC) as rn
+                        FROM t_exam_paper_answer tepa1
+                        WHERE 1=1
+                    ) f1
+                    WHERE f1.rn = 1
+                    <if test="param.name !=null and param.name != ''">
+                        and stu.NAME like CONCAT('%',#{param.name}, '%')
+                    </if>
+                    <if test="param.startTime!=null and param.startTime != ''">
+                        and TO_CHAR(f1.create_time, 'YYYY-MM-DD') &gt;=#{param.startTime}
+                    </if>
+                    <if test="param.endTime!=null and param.endTime != ''">
+                        and TO_CHAR(f1.create_time, 'YYYY-MM-DD') &lt;=#{param.endTime}
+                    </if>
+                ) tepa ON tepa."exam_paper_id" =tep.id AND tepa.create_user =stu.ID AND tepa.status='2'
+                WHERE 1=1
+                AND cch.ID IS NOT NULL
+                AND cc.ID IS NOT NULL
+                AND ci.COURSE_ID IS NOT NULL
+                AND stu.ID IS NOT NULL
+                GROUP BY stu.ID,stu.NAME,cch.ID,cch.NAME,cc.ID,cc.NAME,ci.COURSE_ID,ci.COURSE_NAME,tep.id,tepa."question_correct",tepa."question_count"
+            )t1
+        )z1
+    </select>
 </mapper>

+ 4 - 0
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/param/courseauditrecord/CourseAuditRecordAddParam.java

@@ -103,4 +103,8 @@ public class CourseAuditRecordAddParam {
     /** 私密权限关联用户id */
     @ApiModelProperty(value = "私密权限关联用户id", position = 7)
     private String userRelateIds;
+
+    /** 老师id(学生超链接功能所用) */
+    @ApiModelProperty(value = "老师id", position = 7)
+    private String teacherId;
 }

+ 4 - 0
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/param/courseauditrecord/CourseAuditRecordEditParam.java

@@ -113,4 +113,8 @@ public class CourseAuditRecordEditParam {
     @ApiModelProperty(value = "私密权限关联用户id", position = 7)
     private String userRelateIds;
 
+    /** 老师id(学生超链接功能所用) */
+    @ApiModelProperty(value = "老师id", position = 7)
+    private String teacherId;
+
 }

+ 5 - 0
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/service/CourseAuditRecordService.java

@@ -160,4 +160,9 @@ public interface CourseAuditRecordService extends IService<CourseAuditRecord> {
      * @date 2025/10/24
      */
     CommonResult<String> editUserFileName(EditUserFileNameParam param);
+
+    /**
+     *  资源管理-查询该学生的授课老师列表
+     */
+    Page<Map<String,Object>> getStudentTeacherList(Map param);
 }

+ 10 - 0
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/service/StatisticsLearningProgressService.java

@@ -35,6 +35,16 @@ public interface StatisticsLearningProgressService {
      */
     Page<Map<String,Object>> studyDetail(Map param);
 
+    /**
+     *  学习进度-学习明细数据-课程访问列表
+     */
+    Page<Map<String,Object>> studyDetailCourseView(Map param);
+
+    /**
+     *  学习进度-学习明细数据-练习成绩情况
+     */
+    Page<Map<String,Object>> studyDetailPracticeResult(Map param);
+
     /**
      * 这一周的访问趋势
      */

+ 5 - 0
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/service/StudyBehaviorProgressService.java

@@ -46,4 +46,9 @@ public interface StudyBehaviorProgressService {
      *  学习行为分析-学员维度分析
      */
     Page<Map<String,Object>> getStudentAnalyse(Map param);
+
+    /**
+     *  学习行为分析-获得学生课程作业错误率
+     */
+    Page<Map<String,Object>> getStudentHomeWorkErrorRate(Map param);
 }

+ 9 - 0
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/service/impl/CourseAuditRecordServiceImpl.java

@@ -218,4 +218,13 @@ public class CourseAuditRecordServiceImpl extends ServiceImpl<CourseAuditRecordM
         }
     }
 
+    /**
+     *  资源管理-查询该学生的授课老师列表
+     */
+    @Override
+    public Page<Map<String,Object>> getStudentTeacherList(Map param)
+    {
+        return courseAuditRecordMapper.getStudentTeacherList(param,CommonPageRequest.defaultPage());
+    }
+
 }

+ 18 - 0
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/service/impl/StatisticsLearningProgressServiceImpl.java

@@ -103,6 +103,24 @@ public class StatisticsLearningProgressServiceImpl implements StatisticsLearning
         return statisticsLearningProgressMapper.studyDetail(param, CommonPageRequest.defaultPage());
     }
 
+    /**
+     *  学习进度-学习明细数据-课程访问列表
+     */
+    @Override
+    public Page<Map<String,Object>> studyDetailCourseView(Map param)
+    {
+        return statisticsLearningProgressMapper.studyDetailCourseView(param, CommonPageRequest.defaultPage());
+    }
+
+    /**
+     *  学习进度-学习明细数据-练习成绩情况
+     */
+    @Override
+    public Page<Map<String,Object>> studyDetailPracticeResult(Map param)
+    {
+        return statisticsLearningProgressMapper.studyDetailPracticeResult(param, CommonPageRequest.defaultPage());
+    }
+
     @Override
     public List<Map<String, Object>> viewWeekly(Map param) {
         List<Map<String,Object> > resultList=new ArrayList();

+ 23 - 1
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/service/impl/StudyBehaviorProgressServiceImpl.java

@@ -223,7 +223,8 @@ public class StudyBehaviorProgressServiceImpl implements StudyBehaviorProgressSe
         List<Map<String,Object>> postCountList=studyBehaviorProgressMapper.getPostCountListStudentAnalyse(param);
         //2-4.获取提问次数
         List<Map<String,Object>> answerCountList=studyBehaviorProgressMapper.getAnswerCountListStudentAnalyse(param);
-
+        //2-5.获取作业错误率
+        List<Map<String,Object>> errorRateList=studyBehaviorProgressMapper.getErrorRateListStudentAnalyse(param);
 
         //给所有学生的课程信息,匹配对应关联数据
         for (Map<String,Object> course : courseList)
@@ -272,6 +273,17 @@ public class StudyBehaviorProgressServiceImpl implements StudyBehaviorProgressSe
                     answerCountList.remove(i);          // 从原列表删除(安全操作)
                 }
             }
+            //匹配2-5.获取作业错误率
+            for (int i = errorRateList.size() - 1; i >= 0; i--)
+            {
+                Map<String,Object> errorRate = errorRateList.get(i);
+                if (errorRate == null || errorRate.get("courseId") == null) continue;
+                if (course.get("courseId").equals(errorRate.get("courseId"))&& course.get("userId").equals(errorRate.get("userId")))
+                {
+                    course.put("errorRate",errorRate.get("errorRate"));
+                    errorRateList.remove(i);
+                }
+            }
         }
         //将学生匹配各自对应课程信息
         for (Map<String,Object> user : userList)
@@ -293,4 +305,14 @@ public class StudyBehaviorProgressServiceImpl implements StudyBehaviorProgressSe
         return page;
     }
 
+    /**
+     *  学习行为分析-获得学生课程作业错误率
+     */
+    @Override
+    public Page<Map<String,Object>> getStudentHomeWorkErrorRate(Map param)
+    {
+        Page<Map<String,Object>> page=studyBehaviorProgressMapper.getStudentHomeWorkErrorRate(param, CommonPageRequest.defaultPage());
+        return page;
+    }
+
 }

+ 20 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/admin/QuestionController.java

@@ -1,10 +1,14 @@
 package vip.xiaonuo.exam.controller.admin;
 
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.annotations.ApiOperation;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
+import vip.xiaonuo.common.annotation.CommonLog;
 import vip.xiaonuo.common.pojo.CommonResult;
 import vip.xiaonuo.exam.base.BaseApiController;
 import vip.xiaonuo.exam.base.SystemCode;
@@ -19,7 +23,9 @@ import vip.xiaonuo.exam.viewmodel.admin.question.QuestionEditRequestVM;
 import vip.xiaonuo.exam.viewmodel.admin.question.QuestionPageRequestVM;
 import vip.xiaonuo.exam.vo.QuestionVo;
 
+import javax.servlet.http.HttpServletResponse;
 import javax.validation.Valid;
+import java.io.IOException;
 
 @RestController("AdminQuestionController")
 @RequestMapping(value = "/api/admin/question")
@@ -87,6 +93,20 @@ public class QuestionController extends BaseApiController {
         return CommonResult.data(questionService.checkExcel(file,getCurrentUser()));
     }
 
+    /**
+     * 下载试题导入模板
+     *
+     * @author xuyuxiang
+     * @date 2022/4/24 20:00
+     */
+    @ApiOperationSupport(order = 15)
+    @ApiOperation("下载试题导入模板")
+    @CommonLog("下载试题导入模板")
+    @GetMapping(value = "/downloadImportQuesTemplate", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
+    public void downloadImportQuesTemplate(HttpServletResponse response) throws IOException {
+        questionService.downloadImportQuesTemplate(response);
+    }
+
     @RequestMapping(value = "/loadExcel", method = RequestMethod.POST)
     public CommonResult loadExcel(@RequestParam("file") MultipartFile file) {
         return questionService.loadExcel(file,getCurrentUser());

+ 10 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/service/QuestionService.java

@@ -10,6 +10,8 @@ import vip.xiaonuo.exam.viewmodel.admin.question.QuestionEditRequestVM;
 import vip.xiaonuo.exam.viewmodel.admin.question.QuestionPageRequestVM;
 import vip.xiaonuo.exam.vo.QuestionVo;
 
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
 import java.util.List;
 
 public interface QuestionService extends BaseService<Question> {
@@ -31,4 +33,12 @@ public interface QuestionService extends BaseService<Question> {
     CommonResult checkExcel(MultipartFile file, SaBaseLoginUser currentUser);
 
     CommonResult loadExcel(MultipartFile file, SaBaseLoginUser currentUser);
+
+    /**
+     * 下载试题导入模板
+     *
+     * @author xuyuxiang
+     * @date 2022/8/8 13:16
+     **/
+    void downloadImportQuesTemplate(HttpServletResponse response) throws IOException;
 }

+ 18 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/service/impl/QuestionServiceImpl.java

@@ -1,7 +1,9 @@
 package vip.xiaonuo.exam.service.impl;
 
+import cn.afterturn.easypoi.cache.manager.POICacheManager;
 import cn.hutool.core.collection.ListUtil;
 import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IoUtil;
 import cn.hutool.core.util.NumberUtil;
 import cn.hutool.core.util.ObjectUtil;
 import com.alibaba.excel.EasyExcel;
@@ -16,6 +18,8 @@ import org.springframework.web.multipart.MultipartFile;
 import vip.xiaonuo.auth.core.pojo.SaBaseLoginUser;
 import vip.xiaonuo.common.exception.CommonException;
 import vip.xiaonuo.common.pojo.CommonResult;
+import vip.xiaonuo.common.util.CommonDownloadUtil;
+import vip.xiaonuo.common.util.CommonResponseUtil;
 import vip.xiaonuo.dev.api.DevDictApi;
 import vip.xiaonuo.disk.domain.Semester;
 import vip.xiaonuo.disk.mapper.MajorMapper;
@@ -43,7 +47,10 @@ import vip.xiaonuo.exam.viewmodel.admin.question.QuestionPageRequestVM;
 import vip.xiaonuo.exam.vo.QuestionVo;
 
 import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
 import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.*;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -233,6 +240,17 @@ public class QuestionServiceImpl extends BaseServiceImpl<Question> implements Qu
         }).collect(Collectors.toList());
     }
 
+    @Override
+    public void downloadImportQuesTemplate(HttpServletResponse response) throws IOException {
+        try {
+            InputStream inputStream = POICacheManager.getFile("quesImportTemplate.xlsx");
+            byte[] bytes = IoUtil.readBytes(inputStream);
+            CommonDownloadUtil.download("题目导入模板.xlsx", bytes, response);
+        } catch (Exception e) {
+            CommonResponseUtil.renderError(response, "下载题目导入模板失败");
+        }
+    }
+
     /**
      * 检查导入的Excel文件
      */

BIN
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/resources/quesImportTemplate.xlsx