dedsudiyu 1 day ago
parent
commit
61b22df587

+ 2 - 2
src/views/safetyEducationExaminationNew/components/coursewareComponent.vue

@@ -141,8 +141,8 @@
         font-size:16px;
       }
       .video-box{
-        width:1460px;
-        height:759px;
+        width:1458px;
+        height:797px;
       }
       .rich-text-box {
         padding:20px 100px;

+ 4 - 0
src/views/safetyEducationExaminationNew/courseManagement/courseManagement/addPage.vue

@@ -74,6 +74,8 @@
             shareStatus:response.data.shareStatus,
             courseName:response.data.courseName,
             coverAttachmentPath:response.data.coverAttachmentPath,
+            popupVerifyEnabled:response.data.popupVerifyEnabled?response.data.popupVerifyEnabled:0,
+            popupIntervalMinutes:response.data.popupIntervalMinutes?response.data.popupIntervalMinutes:0,
           })
           let list = [];
           for(let i=0;i<response.data.coursewares.length;i++){
@@ -169,6 +171,8 @@
             shareStatus:basicData.shareStatus,
             courseName:basicData.courseName,
             coverAttachmentPath:basicData.coverAttachmentPath,
+            popupVerifyEnabled:basicData.popupVerifyEnabled,
+            popupIntervalMinutes:basicData.popupVerifyEnabled == 1?basicData.popupIntervalMinutes:0,
             assessmentStatus:0,
             assessmentTotalScore:0,
             assessmentPassScore:0,

+ 50 - 0
src/views/safetyEducationExaminationNew/courseManagement/courseManagement/basicSettings.vue

@@ -42,6 +42,31 @@
             </el-form-item>
           </div>
           <div class="form-box">
+            <el-form-item label="图形弹窗校验" prop="popupVerifyEnabled">
+              <div class="switch-row">
+                <el-switch v-model="newData.popupVerifyEnabled" :active-value="1" :inactive-value="0"
+                           active-color="#0183FB" inactive-color="#AAAAAA"
+                />
+                <template v-if="newData.popupVerifyEnabled==1">
+                  <p class="tip-text" style="margin-left:12px;">开启后,每隔</p>
+                  <div class="number-stepper" style="margin-left:8px;">
+                    <el-input-number
+                      class="form-input-number"
+                      style="width:160px;"
+                      :min="0"
+                      :max="9999"
+                      :step="1" :precision="0" step-strictly
+                      v-model="newData.popupIntervalMinutes"
+                    >
+                    </el-input-number>
+                  </div>
+                  <span class="tip-text" style="margin-left:8px;">分钟,提醒弹窗验证</span>
+                </template>
+                <span class="tip-text" style="margin-left:12px;" v-else>开启后,每隔 N 分钟,提醒弹窗验证</span>
+              </div>
+            </el-form-item>
+          </div>
+          <div class="form-box">
             <div class="form-flex-box">
               <el-form-item label="课程封面" prop="coverAttachmentPath">
                 <el-upload
@@ -80,6 +105,13 @@
       propsData:{},
     },
     data(){
+      const validateData5 = (rule, value, callback) => {
+        if (this.newData.popupVerifyEnabled == 1 && this.newData.popupIntervalMinutes == 0) {
+          callback(new Error('图形弹窗校验间隔不能为0'))
+        } else {
+          callback()
+        }
+      }
       return{
         //上传相关
         uploadImgUrl: this.uploadUrl(), // 上传的图片服务器地址
@@ -93,6 +125,8 @@
           shareStatus:1,
           courseName:'',
           coverAttachmentPath:'',
+          popupVerifyEnabled: 0,
+          popupIntervalMinutes: 0,
         },
         rules:{
           disciplineTypeId: [
@@ -107,6 +141,10 @@
             { required: true, message: "请上传课程封面", trigger: "blur" },
             { required: true, message: "请上传课程封面", validator: this.spaceJudgment, trigger: "blur" }
           ],
+          popupVerifyEnabled: [
+            { required: true, message: '请输入人图形弹窗校验间隔', trigger: 'change' },
+            { validator: validateData5, trigger: 'blur' }
+          ],
         }
       }
     },
@@ -200,6 +238,18 @@
             margin-left:20px;
             border-radius:6px;
           }
+          .switch-row {
+            display: flex;
+            /*align-items: center;*/
+            /*flex-wrap: wrap;*/
+            .el-switch {
+              margin-top: 10px;
+            }
+
+            .tip-text {
+              margin-top: 2px;
+            }
+          }
         }
         .form-flex-box{
           display: flex;

+ 98 - 26
src/views/safetyEducationExaminationNew/courseManagement/courseManagement/studyCourses.vue

@@ -180,7 +180,7 @@
         this.$refs.drawer.closeDrawer();
       },
       //弹窗确定
-      drawerConfirmButton(data){
+      drawerConfirmButton(data) {
         let self = this;
         // --- 核心修改:计算当前 dataList 下所有 minList 的总数 ---
         let currentTotalMin = 0;
@@ -204,36 +204,107 @@
           self.msgError(`最多只能添加 100 个子项!当前已有 ${currentTotalMin} 个,本次尝试添加 ${addMinCount} 个。`);
           return; // 超过限制,直接终止后续操作
         }
-        if (!data.knowledgePointId){
-          //大项添加
-          let num = 0;
-          for(let i=0;i<self.dataList.length;i++){
-            if(self.dataList[i].knowledgePointId == data.data.knowledgePointId){
-              //旧大项
-              num++
-              self.dataList[i].minList = [...self.dataList[i].minList, ...data.data.minList]
-              let numA = 0;
-              let numB = 0;
-              let numC = 0;
-              for(let o=0;o<self.dataList[i].minList.length;o++){
-                numA++
-                numB = self.addAndFormat(numB,self.dataList[i].minList[o].minData4)
-                numC = self.addAndFormat(numC,self.dataList[i].minList[o].minData5)
+
+        // 用于存储最终要显示的提示信息
+        let toastMessage = '';
+
+        if (!data.knowledgePointId) {
+          // --- 大项添加 ---
+          let existingItemIndex = self.dataList.findIndex(item => item.knowledgePointId === data.data.knowledgePointId);
+
+          if (existingItemIndex > -1) {
+            // 1. 如果大项已存在,则合并其 minList
+            let existingMinList = self.dataList[existingItemIndex].minList || [];
+            let newMinList = data.data.minList || [];
+
+            // 统计新增和重复的数量
+            let addedCount = 0;
+            let duplicateCount = 0;
+
+            let uniqueNewMinList = newMinList.filter(newItem => {
+              const isDuplicate = existingMinList.some(existingItem => existingItem.id === newItem.id);
+              if (isDuplicate) {
+                duplicateCount++;
+              } else {
+                addedCount++;
               }
-              self.dataList[i].data2 = numA
-              self.dataList[i].data3 = numB
-              self.dataList[i].data4 = numC
+              return !isDuplicate;
+            });
+
+            // 合并并更新
+            self.dataList[existingItemIndex].minList = [...existingMinList, ...uniqueNewMinList];
+
+            // 重新计算统计数据
+            let numA = 0;
+            let numB = 0;
+            let numC = 0;
+            for (let o = 0; o < self.dataList[existingItemIndex].minList.length; o++) {
+              numA++;
+              numB = self.addAndFormat(numB, self.dataList[existingItemIndex].minList[o].minData4);
+              numC = self.addAndFormat(numC, self.dataList[existingItemIndex].minList[o].minData5);
+            }
+            self.dataList[existingItemIndex].data2 = numA;
+            self.dataList[existingItemIndex].data3 = numB;
+            self.dataList[existingItemIndex].data4 = numC;
+
+            // 生成提示信息
+            if (addedCount > 0 && duplicateCount > 0) {
+              toastMessage = `${addedCount}个课件已添加,${duplicateCount}个课件重复已过滤`;
+            } else if (addedCount > 0) {
+              toastMessage = `${addedCount}个课件已添加`;
+            } else if (duplicateCount > 0) {
+              toastMessage = `${duplicateCount}个课件重复已过滤`;
+            }
+
+          } else {
+            // 2. 如果大项不存在,则直接添加
+            self.dataList.push(data.data);
+            // 直接添加,所有都是新增的
+            const addedCount = data.data.minList ? data.data.minList.length : 0;
+            if (addedCount > 0) {
+              toastMessage = `${addedCount}个课件已添加`;
             }
           }
-          if(num == 0){
-            //新大项
-            this.dataList.push(data.data)
-          }else{
+
+        } else {
+          // --- 子项添加 ---
+          let targetMinList = self.dataList[data.checkIndex].minList || [];
+          let newMinList = data.list || [];
+
+          // 统计新增和重复的数量
+          let addedCount = 0;
+          let duplicateCount = 0;
+
+          let uniqueNewMinList = newMinList.filter(newItem => {
+            const isDuplicate = targetMinList.some(existingItem => existingItem.id === newItem.id);
+            if (isDuplicate) {
+              duplicateCount++;
+            } else {
+              addedCount++;
+            }
+            return !isDuplicate;
+          });
+
+          // 合并并更新
+          self.dataList[data.checkIndex].minList = [...targetMinList, ...uniqueNewMinList];
+
+          // 生成提示信息
+          if (addedCount > 0 && duplicateCount > 0) {
+            toastMessage = `${addedCount}个课件已添加,${duplicateCount}个课件重复已过滤`;
+          } else if (addedCount > 0) {
+            toastMessage = `${addedCount}个课件已添加`;
+          } else if (duplicateCount > 0) {
+            toastMessage = `${duplicateCount}个课件重复已过滤`;
           }
-        }else{
-          //子项添加
-          self.dataList[data.checkIndex].minList = [...self.dataList[data.checkIndex].minList, ...data.list]
         }
+
+        // 如果有提示信息,则显示
+        if (toastMessage) {
+          // 假设你有一个通用的消息提示方法,比如 this.$message.success 或 this.msgSuccess
+          // 请根据你项目中实际的消息提示组件进行调整
+          self.msgSuccess(toastMessage);
+        }
+
         this.statisticalCalculation();
         this.$refs.drawer.closeDrawer();
       },
@@ -378,6 +449,7 @@
         let self = this;
         if(!this.dataList[0]){
           this.msgError('请添加课件')
+          return
         }
         for (let i=0;i<self.dataList.length;i++) {
           if(!self.dataList[i].minList[0]){

+ 42 - 42
src/views/safetyEducationExaminationNew/examManagement/examinationArrange/addPage.vue

@@ -347,30 +347,30 @@
             <!-- ==================== 防作弊 ==================== -->
             <div v-show="activeTab === 'antiCheat'">
               <div class="section-block">
-                <el-form-item label="图形弹窗校验" prop="popupVerifyEnabled">
-                  <div class="switch-row">
-                    <el-switch :disabled="showType" v-model="newData.popupVerifyEnabled" :active-value="1" :inactive-value="0"
-                               active-color="#0183FB" inactive-color="#AAAAAA"
-                    />
-                    <template v-if="newData.popupVerifyEnabled==1">
-                      <p class="tip-text" style="margin-left:12px;">开启后,每隔</p>
-                      <div class="number-stepper" style="margin-left:8px;">
-                        <el-input-number
-                          :disabled="showType"
-                          class="form-input-number"
-                          style="width:160px;"
-                          :min="0"
-                          :max="9999"
-                          :step="1" :precision="0" step-strictly
-                          v-model="newData.popupIntervalMinutes"
-                        >
-                        </el-input-number>
-                      </div>
-                      <span class="tip-text" style="margin-left:8px;">分钟,提醒弹窗验证</span>
-                    </template>
-                    <span class="tip-text" style="margin-left:12px;" v-else>开启后,每隔 N 分钟,提醒弹窗验证</span>
-                  </div>
-                </el-form-item>
+                <!--<el-form-item label="图形弹窗校验" prop="popupVerifyEnabled">-->
+                  <!--<div class="switch-row">-->
+                    <!--<el-switch :disabled="showType" v-model="newData.popupVerifyEnabled" :active-value="1" :inactive-value="0"-->
+                               <!--active-color="#0183FB" inactive-color="#AAAAAA"-->
+                    <!--/>-->
+                    <!--<template v-if="newData.popupVerifyEnabled==1">-->
+                      <!--<p class="tip-text" style="margin-left:12px;">开启后,每隔</p>-->
+                      <!--<div class="number-stepper" style="margin-left:8px;">-->
+                        <!--<el-input-number-->
+                          <!--:disabled="showType"-->
+                          <!--class="form-input-number"-->
+                          <!--style="width:160px;"-->
+                          <!--:min="0"-->
+                          <!--:max="9999"-->
+                          <!--:step="1" :precision="0" step-strictly-->
+                          <!--v-model="newData.popupIntervalMinutes"-->
+                        <!--&gt;-->
+                        <!--</el-input-number>-->
+                      <!--</div>-->
+                      <!--<span class="tip-text" style="margin-left:8px;">分钟,提醒弹窗验证</span>-->
+                    <!--</template>-->
+                    <!--<span class="tip-text" style="margin-left:12px;" v-else>开启后,每隔 N 分钟,提醒弹窗验证</span>-->
+                  <!--</div>-->
+                <!--</el-form-item>-->
 
                 <el-form-item label="人脸识别校验">
                   <div class="anti-cheat-group">
@@ -559,13 +559,13 @@
           callback()
         }
       }
-      const validateData5 = (rule, value, callback) => {
-        if (this.newData.popupVerifyEnabled == 1 && this.newData.popupIntervalMinutes == 0) {
-          callback(new Error('图形弹窗校验间隔不能为0'))
-        } else {
-          callback()
-        }
-      }
+      // const validateData5 = (rule, value, callback) => {
+      //   if (this.newData.popupVerifyEnabled == 1 && this.newData.popupIntervalMinutes == 0) {
+      //     callback(new Error('图形弹窗校验间隔不能为0'))
+      //   } else {
+      //     callback()
+      //   }
+      // }
       const validateData6 = (rule, value, callback) => {
         if (this.newData.certificateEnabled == 1 && !this.newData.certificateTemplateId) {
           callback(new Error('请选择证书'))
@@ -647,8 +647,8 @@
           commitmentEnabled: 0,
           commitmentPrintEnabled: 0,
           commitmentContent: '',
-          popupVerifyEnabled: 0,
-          popupIntervalMinutes: 0,
+          // popupVerifyEnabled: 0,
+          // popupIntervalMinutes: 0,
           preFaceVerifyEnabled: 0,
           postFaceVerifyEnabled: 0,
           cameraCaptureEnabled: 0,
@@ -705,10 +705,10 @@
             { required: true, message: '请输入考试承诺书内容', trigger: 'change' },
             { validator: validateData4, trigger: 'blur' }
           ],
-          popupVerifyEnabled: [
-            { required: true, message: '请输入人图形弹窗校验间隔', trigger: 'change' },
-            { validator: validateData5, trigger: 'blur' }
-          ],
+          // popupVerifyEnabled: [
+          //   { required: true, message: '请输入人图形弹窗校验间隔', trigger: 'change' },
+          //   { validator: validateData5, trigger: 'blur' }
+          // ],
           certificateEnabled: [
             { required: true, message: '请选择证书', trigger: 'change' },
             { validator: validateData6, trigger: 'blur' }
@@ -790,8 +790,8 @@
               commitmentEnabled: response.data.commitmentEnabled,
               commitmentPrintEnabled: response.data.commitmentPrintEnabled,
               commitmentContent: response.data.commitmentEnabled==1?response.data.commitmentContent:'',
-              popupVerifyEnabled: response.data.popupVerifyEnabled,
-              popupIntervalMinutes: response.data.popupIntervalMinutes,
+              // popupVerifyEnabled: response.data.popupVerifyEnabled,
+              // popupIntervalMinutes: response.data.popupIntervalMinutes,
               preFaceVerifyEnabled: response.data.preFaceVerifyEnabled,
               postFaceVerifyEnabled: response.data.postFaceVerifyEnabled,
               cameraCaptureEnabled: response.data.cameraCaptureEnabled,
@@ -823,7 +823,7 @@
             const tabFieldMap = [
               { tab: 'basic', list: ['examTypeId','riskLevelId','examName','attemptLimit','paperId','enableStatus','answerDuration','scopeType','examTimeRange'] },
               { tab: 'preExam', list: ['studyHoursRequirementEnabled','preCourseEnabled','mockEnabled','commitmentEnabled'] },
-              { tab: 'antiCheat', list: ['popupVerifyEnabled'] },
+              // { tab: 'antiCheat', list: ['popupVerifyEnabled'] },
               { tab: 'certificate', list: ['certificateEnabled','certificateValidMonths','certificateReviewMonths'] }
             ]
             const matchedTab = tabFieldMap.find(item => item.list.includes(firstErrorField))
@@ -868,8 +868,8 @@
           commitmentEnabled:this.newData.commitmentEnabled,
           commitmentPrintEnabled:this.newData.commitmentPrintEnabled,
           commitmentContent:this.newData.commitmentEnabled == 1 ? this.newData.commitmentContent:'',
-          popupVerifyEnabled:this.newData.popupVerifyEnabled,
-          popupIntervalMinutes:this.newData.popupVerifyEnabled == 1?this.newData.popupIntervalMinutes:0,
+          // popupVerifyEnabled:this.newData.popupVerifyEnabled,
+          // popupIntervalMinutes:this.newData.popupVerifyEnabled == 1?this.newData.popupIntervalMinutes:0,
           preFaceVerifyEnabled:this.newData.preFaceVerifyEnabled,
           postFaceVerifyEnabled:this.newData.postFaceVerifyEnabled,
           cameraCaptureEnabled:this.newData.cameraCaptureEnabled,

+ 0 - 1
src/views/safetyEducationExaminationNew/examManagement/testPaperManagement/index.vue

@@ -100,7 +100,6 @@
           <el-table-column label="来源" prop="deptName" width="160" show-overflow-tooltip/>
           <el-table-column label="总分" prop="totalScore" width="100" show-overflow-tooltip/>
           <el-table-column label="及格分" prop="passScore" width="100" show-overflow-tooltip/>
-          <el-table-column label="时长" prop="answerDuration" width="100" show-overflow-tooltip/>
           <el-table-column label="更新时间" prop="createTime" width="180" show-overflow-tooltip>
             <template slot-scope="scope">
               <span>{{ scope.row.updateTime?parseTime(scope.row.updateTime,"{y}-{m}-{d} {h}:{i}"):parseTime(scope.row.createTime,"{y}-{m}-{d} {h}:{i}") }}</span>

+ 173 - 107
src/views/safetyEducationExaminationNew/featurePage/learningCourse/index.vue

@@ -10,26 +10,26 @@
         <coursewareComponent ref="coursewareComponent"></coursewareComponent>
       </div>
       <div class="right-max-big-box">
-        <p class="title-p">{{learningData.data1}}</p>
-        <div class="text-p" style="color:#666;">{{learningData.data2}}分钟丨{{learningData.data3}}学时丨{{learningData.data4}}积分</div>
-        <div class="text-p">已学 {{learningData.data5}} / 总 {{learningData.data6}} 课件</div>
+        <p class="title-p">{{newData.courseName}}</p>
+        <div class="text-p" style="color:#666;">{{secondsToMinutes(newData.totalDuration)}}分钟丨{{newData.totalCreditHours}}学时丨{{newData.totalPoints}}积分</div>
+        <div class="text-p">已学 {{newData.learnedCoursewareCount}} / 总 {{newData.totalCoursewareCount}} 课件</div>
         <div class="progress-box">
           <p class="progress-title-box">学习进度</p>
           <div class="progress-min-box">
-            <el-progress :stroke-width="12" :percentage="learningData.data7"></el-progress>
+            <el-progress :stroke-width="12" :percentage="newData.coursewares[0].progress?newData.coursewares[0].progress:0"></el-progress>
           </div>
         </div>
         <div class="learning-for-max-big-box scrollbar-box">
           <div class="for-max-big-box"
-               v-for="(item,index) in learningData.dataList" :key="index">
+               v-for="(item,index) in newData.coursewares" :key="index">
             <div class="for-big-box" @click="checkLearningButton(index)"
                  :class="checkLearningIndex == index ?'check-for-box':''">
-              <p>{{item.listData1}}</p>
-              <p>已学习:{{formatTime(item.listData3)}} / {{formatTime(item.listData4)}}</p>
-              <p :class="item.listData5 ==1 ?'colorA':''">{{item.listData5==1?'已学完':'未学完'}}</p>
+              <p>{{item.coursewareName}}</p>
+              <p>已学习:{{formatTime(item.learnedDuration)}} / {{formatTime(item.minLearningDuration)}}</p>
+              <p :class="item.learnStatus ==2 ?'colorA':''">{{item.learnStatus==2?'已学完':'未学完'}}</p>
             </div>
             <p class="for-button" @click="finishStudying(index)"
-               v-if="item.listData5 == 0 && item.listData3 == item.listData4 && checkLearningIndex == index">
+               v-if="item.learnStatus != 2 && item.learnedDuration == item.minLearningDuration && checkLearningIndex == index">
               若已完成学习,请点此确认
             </p>
           </div>
@@ -59,6 +59,10 @@
   import CountdownTimer from '@/utils/timerUtils/CountdownTimer';
   import { VerifySlider } from 'vue-verify-slider';
   import coursewareComponent from "@/views/safetyEducationExaminationNew/components/coursewareComponent.vue";
+  import {
+    examUserCourseLearningCoursewareList,examUserCoursewareLearningReportProgress,
+    examUserCoursewareLearningConfirmCompletion,examUserCoursewareLearningRestart
+  } from "@/api/safetyEducationExaminationNew/index";
   export default {
     name: 'index',
     components: {
@@ -67,78 +71,80 @@
     },
     data() {
       return {
-        learningData:{
-          data1:"课程名称",
-          data2:"20",
-          data3:"1",
-          data4:"3",
-          data5:"0",
-          data6:"2",
-          data7:20,
-          dataList:[
+        examId:null,
+        courseId:null,
+        newData:{
+          courseName:"",
+          totalDuration:0,
+          totalCreditHours:0,
+          totalPoints:0,
+          learnedCoursewareCount:0,
+          totalCoursewareCount:0,
+          progress:0,
+          coursewares:[
             {
-              listData1:'视频课件名称',
+              coursewareName:'视频课件名称',
               listData2:1,
-              listData3:590,
-              listData4:600,
-              listData5:0,
-              listData6:'http://192.168.1.8/statics/bigFile/20260801/0731dbcb-622b-4468-ade2-e20820b122eb.mp4',
-              listData7:'mp4',
+              learnedDuration:590,
+              minLearningDuration:600,
+              learnStatus:2,
+              attachmentPath:'http://192.168.1.8/statics/bigFile/20260801/0731dbcb-622b-4468-ade2-e20820b122eb.mp4',
+              attachmentType:'mp4',
               materialType:'1',
               articleContent:'',
             },
             {
-              listData1:'docx课件名称',
+              coursewareName:'docx课件名称',
               listData2:1,
-              listData3:600,
-              listData4:600,
-              listData5:0,
-              listData6:'http://192.168.1.8/statics/2026/08/01/b68f1b1d-99ea-44e0-94b5-4bad4ac3ff1f.docx',
-              listData7:'.docx',
+              learnedDuration:600,
+              minLearningDuration:600,
+              learnStatus:0,
+              attachmentPath:'http://192.168.1.8/statics/2026/08/01/b68f1b1d-99ea-44e0-94b5-4bad4ac3ff1f.docx',
+              attachmentType:'.docx',
               materialType:'3',
               articleContent:'',
             },
             {
-              listData1:'xlsx课件名称',
+              coursewareName:'xlsx课件名称',
               listData2:1,
-              listData3:600,
-              listData4:600,
-              listData5:1,
-              listData6:'http://192.168.1.8/statics/2026/08/01/270eb79e-627b-4fdf-9f1a-397bd9784d39.xlsx',
-              listData7:'.xlsx',
+              learnedDuration:600,
+              minLearningDuration:600,
+              learnStatus:1,
+              attachmentPath:'http://192.168.1.8/statics/2026/08/01/270eb79e-627b-4fdf-9f1a-397bd9784d39.xlsx',
+              attachmentType:'.xlsx',
               materialType:'3',
               articleContent:'',
             },
             {
-              listData1:'pdf课件名称',
+              coursewareName:'pdf课件名称',
               listData2:1,
-              listData3:600,
-              listData4:600,
-              listData5:1,
-              listData6:'http://192.168.1.8/statics/2026/08/01/b251bd69-120c-48d5-b662-c28e308cd61b.pdf',
-              listData7:'.pdf',
+              learnedDuration:600,
+              minLearningDuration:600,
+              learnStatus:1,
+              attachmentPath:'http://192.168.1.8/statics/2026/08/01/b251bd69-120c-48d5-b662-c28e308cd61b.pdf',
+              attachmentType:'.pdf',
               materialType:'3',
               articleContent:'',
             },
             {
-              listData1:'富文本课件名称',
+              coursewareName:'富文本课件名称',
               listData2:1,
-              listData3:600,
-              listData4:600,
-              listData5:1,
-              listData6:'',
-              listData7:'',
+              learnedDuration:600,
+              minLearningDuration:600,
+              learnStatus:1,
+              attachmentPath:'',
+              attachmentType:'',
               materialType:'4',
               articleContent:"<p><img style=\"max-width: 100%;\" src=\"../statics/2026/08/01/e2788e82-745c-4b49-82e0-ebb6f2a30713.jpeg\" alt=\"1.jpeg\">&nbsp;</p>"
             },
             {
-              listData1:'xlsx课件名称',
+              coursewareName:'xlsx课件名称',
               listData2:1,
-              listData3:600,
-              listData4:600,
-              listData5:1,
-              listData6:'http://192.168.1.8/statics/2026/08/01/270eb79e-627b-4fdf-9f1a-397bd9784d39.xlsx',
-              listData7:'.xlsx',
+              learnedDuration:600,
+              minLearningDuration:600,
+              learnStatus:1,
+              attachmentPath:'http://192.168.1.8/statics/2026/08/01/270eb79e-627b-4fdf-9f1a-397bd9784d39.xlsx',
+              attachmentType:'.xlsx',
               materialType:'3',
               articleContent:'',
             },
@@ -153,7 +159,7 @@
         //验证器弹窗状态
         dialogVisible:false,
         //验证器弹窗间隔时间
-        verificationTime:120,
+        verificationTime:0,
         //心跳回报时间
         heartbeatValue:30,
       }
@@ -165,22 +171,29 @@
       this.initialization();
     },
     methods: {
-      /*
-      this.$router.push({
-        name: 'Detail',
-        params: {
-          id: 1,
-          title: '消息标题'
-        }
-      })
-      this.$route.params.title
-      */
       //初始化
       initialization(){
-
-        this.initTimer();
-        this.startCountdown();
-        this.courseRendering(0);
+        this.$set(this,'courseId',this.$route.params.courseId)
+        this.$set(this,'examId',this.$route.params.examId)
+        this.$nextTick(()=>{
+          this.examUserCourseLearningCoursewareList();
+        })
+      },
+      //获取课程下的课件列表
+      examUserCourseLearningCoursewareList(){
+        examUserCourseLearningCoursewareList({ courseId:this.courseId,examId:this.examId }).then(response => {
+          this.$set(this, 'newData', response.data)
+          if(response.data.popupVerifyEnabled == 1){
+            if (this.countdown) {
+              this.countdown.clear();
+              this.countdown = null;
+            }
+            this.$set(this,'verificationTime',this.accMul(response.data.popupIntervalMinutes,60));
+            this.startCountdown();//验证码倒计时
+          }
+          this.initTimer();//学习计时器
+          this.courseRendering(this.checkLearningIndex);//课件渲染
+        })
       },
       //返回
       backPage() {
@@ -195,58 +208,93 @@
           type: "warning"
         }).then(function() {
         }).then(() => {
-
+          self.examUserCoursewareLearningConfirmCompletion();
         }).catch(() => {});
       },
       //切换课件
       checkLearningButton(index){
         let self = this;
-        self.handleToggle();
-        self.verificationHandleToggle();
-        if(this.checkLearningIndex != index){
-          this.$confirm('确认切换课件?', "警告", {
-            confirmButtonText: "确认",
-            cancelButtonText: "取消",
-            type: "warning"
-          }).then(function() {
-          }).then(() => {
-            self.$set(self,'checkLearningIndex',index);
-            self.initTimer();
-            self.verificationHandleToggle();
-            self.courseRendering(index);
-          }).catch(() => {
-            self.handleToggle();
-            self.verificationHandleToggle();
-          });
+        if(this.newData.coursewares[this.checkLearningIndex].learnStatus == 2){
+          self.$set(self,'checkLearningIndex',index);
+          self.initTimer();
+          self.courseRendering(index);
+        }else{
+          self.handleToggle();
+          self.verificationHandleToggle();
+          if(this.checkLearningIndex != index){
+            this.$confirm('确认切换课件?', "警告", {
+              confirmButtonText: "确认",
+              cancelButtonText: "取消",
+              type: "warning"
+            }).then(function() {
+            }).then(() => {
+              self.$set(self,'checkLearningIndex',index);
+              self.initTimer();
+              self.verificationHandleToggle();
+              self.courseRendering(index);
+            }).catch(() => {
+              self.handleToggle();
+              self.verificationHandleToggle();
+            });
+          }
         }
       },
       //课件渲染
       courseRendering(index){
-        let name = this.learningData.dataList[index].listData1;
+        let name = this.newData.coursewares[index].coursewareName;
         let url = '';
         let type = '';
-        if(this.learningData.dataList[index].materialType == 1){
+        if(this.newData.coursewares[index].materialType == 1){
           //视频
           type = 'video'
-          url = this.learningData.dataList[index].listData6;
-        }else if(this.learningData.dataList[index].materialType == 3){
+          url = this.newData.coursewares[index].attachmentPath;
+        }else if(this.newData.coursewares[index].materialType == 3){
           //文档
-          if(this.learningData.dataList[index].listData7 == '.docx'){
+          if(this.newData.coursewares[index].attachmentType == '.docx'){
             type = 'docx'
-          }else if(this.learningData.dataList[index].listData7 == '.xlsx'){
+          }else if(this.newData.coursewares[index].attachmentType == '.xlsx'){
             type = 'excel'
-          }else if(this.learningData.dataList[index].listData7 == '.pdf'){
+          }else if(this.newData.coursewares[index].attachmentType == '.pdf'){
             type = 'pdf'
           }
-          url = this.learningData.dataList[index].listData6;
-        }else if(this.learningData.dataList[index].materialType == 4){
+          url = this.newData.coursewares[index].attachmentPath;
+        }else if(this.newData.coursewares[index].materialType == 4){
           //文章
           type = 'text'
-          url = this.learningData.dataList[index].articleContent;
+          url = this.newData.coursewares[index].articleContent;
         }
         this.$refs['coursewareComponent'].initialize(name,url,type);
       },
-      //时间换算
+      //学习完成
+      examUserCoursewareLearningConfirmCompletion(){
+        let self = this;
+        let obj = {
+          coursewareId:this.newData.coursewares[this.checkLearningIndex].coursewareId,
+          courseId:this.courseId,
+          examId:this.examId,
+        }
+        examUserCoursewareLearningConfirmCompletion(obj).then(response => {
+          for(let i=0;i<self.newData.coursewares.length;i++){
+            if(self.newData.coursewares[i].learnStatus!=2 && i!=this.checkLearningIndex){
+              this.$set(this,'checkLearningIndex',i);
+              break
+            }
+          }
+          this.examUserCourseLearningCoursewareList();
+          this.msgSuccess(response.message)
+        })
+      },
+      //心跳接口
+      examUserCoursewareLearningReportProgress(){
+        let obj = {
+          coursewareId:this.newData.coursewares[this.checkLearningIndex].coursewareId,
+          courseId:this.courseId,
+          examId:this.examId,
+          duration:this.heartbeatValue,
+        }
+        examUserCoursewareLearningReportProgress(obj).then(response => {})
+      },
+      //倒计时时间换算
       formatTime(totalSeconds) {
         if (typeof totalSeconds !== 'number' || totalSeconds <= 0) {
           return '0秒';
@@ -261,6 +309,13 @@
           return `${seconds}秒`;
         }
       },
+      //展示时间换算
+      secondsToMinutes(totalSeconds) {
+        if (typeof totalSeconds !== 'number' || totalSeconds <= 0) {
+          return 1;
+        }
+        return Math.ceil(totalSeconds / 60);
+      },
       /*=================================== 验证器 ===================================*/
       onVerifySuccess() {
         this.$message.success('验证成功');
@@ -268,7 +323,7 @@
         this.handleToggle();
         this.startCountdown();
         //如果是视频课件 则继续播放视频
-        if(this.learningData.dataList[this.checkLearningIndex].materialType == 1){
+        if(this.newData.coursewares[this.checkLearningIndex].materialType == 1){
           this.$refs['coursewareComponent'].resumeVideo();
         }
       },
@@ -276,26 +331,26 @@
       // 初始化定时器
       initTimer() {
         let self = this;
-        if(this.learningData.dataList[this.checkLearningIndex].listData3 == this.learningData.dataList[this.checkLearningIndex].listData4){
+        if(this.newData.coursewares[this.checkLearningIndex].learnedDuration == this.newData.coursewares[this.checkLearningIndex].minLearningDuration){
           return
         }
         this.studyTimer = new StudyTimer({
-          initialSeconds: self.learningData.dataList[self.checkLearningIndex].listData3,
-          maxSeconds: self.learningData.dataList[self.checkLearningIndex].listData4,
+          initialSeconds: self.newData.coursewares[self.checkLearningIndex].learnedDuration,
+          maxSeconds: self.newData.coursewares[self.checkLearningIndex].minLearningDuration,
           onTick: (elapsedSeconds) => {
             //每次计时
-            this.$set(self.learningData.dataList[self.checkLearningIndex],'listData3',elapsedSeconds);
+            this.$set(self.newData.coursewares[self.checkLearningIndex],'learnedDuration',elapsedSeconds);
             //心跳判断
             if (elapsedSeconds % this.heartbeatValue === 0) {
               //调用心跳方法
-
+              this.examUserCoursewareLearningReportProgress();
             }
           },
           onMaxTimeReached: (elapsedSeconds) => {
             //达到最大值
-            this.$set(self.learningData.dataList[self.checkLearningIndex],'listData3',elapsedSeconds);
+            this.$set(self.newData.coursewares[self.checkLearningIndex],'learnedDuration',elapsedSeconds);
             //调用心跳方法
-
+            this.examUserCoursewareLearningReportProgress();
           }
         });
         this.studyTimer.resume();
@@ -331,7 +386,7 @@
             this.verificationHandleToggle();
             this.$set(this,'dialogVisible',true);
             //如果是视频课件 则暂停播放视频
-            if(this.learningData.dataList[this.checkLearningIndex].materialType == 1){
+            if(this.newData.coursewares[this.checkLearningIndex].materialType == 1){
               this.$refs['coursewareComponent'].pauseVideo();
             }
           }
@@ -352,6 +407,13 @@
           this.countdown = null;
         }
       },
+      //乘法
+      accMul(arg1,arg2){
+        var m=0,s1=arg1.toString(),s2=arg2.toString();
+        try{m+=s1.split(".")[1].length}catch(e){}
+        try{m+=s2.split(".")[1].length}catch(e){}
+        return Number(s1.replace(".",""))*Number(s2.replace(".",""))/Math.pow(10,m)
+      },
     },
     beforeDestroy() {
       if (this.studyTimer) {
@@ -376,6 +438,10 @@
     padding:0;
     box-shadow: 0 0 8px 1px rgba(0, 0, 0, 0.1);
     margin:10px;
+    ::v-deep .el-progress .el-progress__text{
+      margin-left:10px;
+      margin-top:1px;
+    }
     .content-box{
       flex: 1;
       display: flex;

+ 9 - 53
src/views/safetyEducationExaminationNew/safetyEducation/learningTasks/index.vue

@@ -45,7 +45,7 @@
       </div>
       <div class="page-content-box">
         <div class="for-max-big-box scrollbar-box">
-          <div class="for-big-box" v-for="(item,index) in dataList" :key="index">
+          <div class="for-big-box" v-for="(item,index) in dataList" :key="index" @click="tableButton(item)">
             <img :src="item.coverAttachmentPath">
             <div class="bottom-big-box">
               <p class="name-p">{{item.courseName}}</p>
@@ -141,58 +141,14 @@
         })
       },
       //操作按钮
-      tableButton(type, row) {
-        let self = this
-        if (type == 1) {
-          //新增
-          this.$set(this, 'pageType', 2)
-          this.$set(this, 'propsData', {})
-        } else if (type == 2) {
-          //详情
-          this.$set(this, 'pageType', 2)
-          let obj = JSON.parse(JSON.stringify(row))
-          obj.showType = true
-          this.$set(this, 'propsData', obj)
-        } else if (type == 3) {
-          //编辑
-          this.$set(this, 'pageType', 2)
-          let obj = JSON.parse(JSON.stringify(row))
-          obj.showType = false
-          this.$set(this, 'propsData', obj)
-        } else if (type == 4) {
-          //删除
-          this.$confirm('是否确认删除?', '警告', {
-            confirmButtonText: '确定',
-            cancelButtonText: '取消',
-            type: 'warning'
-          }).then(function() {
-          }).then(() => {
-            deleteFunction({ id: row.id }).then(response => {
-              self.msgSuccess(response.message)
-              self.getList()
-            })
-          }).catch(() => {
-          })
-        } else if (type == 5) {
-          //启用&停用
-          let text = row.state ? '停用' : '启用'
-          this.$confirm('是否确认' + text + '?', '警告', {
-            confirmButtonText: '确定',
-            cancelButtonText: '取消',
-            type: 'warning'
-          }).then(function() {
-          }).then(() => {
-            stateFunction({ id: row.id, state: !row.state }).then(response => {
-              self.msgSuccess(response.message)
-              self.getList()
-            })
-          }).catch(() => {
-          })
-        } else if (type == 6) {
-          //返回并刷新
-          this.$set(this, 'pageType', 1)
-          this.getList()
-        }
+      tableButton(item) {
+        this.$router.push({
+          name: 'learningCourse',
+          params: {
+            courseId: item.courseId,
+            examId: item.examId,
+          }
+        })
       },
       examElKnowledgePointTreeList(){
         examElKnowledgePointTreeList({}).then(response => {