dedsudiyu 1 dzień temu
rodzic
commit
8ccd21fa80

+ 6 - 2
demo.md

@@ -1,2 +1,6 @@
-1. 根据当前UI图片在E:\git\2026项目\safetyEducationExamination\src\views\safetyEducationExaminationNew\examManagement\testPaperManagement\addPage.vue内生成页面
-2. 所有表单内容生成在.content-box内
+帮我在 E:\git\2026项目\safetyEducationExamination\src\views\safetyEducationExaminationNew\components\videoComponent.vue 写一个video组件 我会传入2个参数 
+用户ID userId
+视频链接 url
+需要记录当前用户在播放该视频时的观看时间点 用于下次打开该组件时自动跳转到该时间点上
+每个用户与视频单独记录
+同时注意防抖节流 不要对浏览器造成压力

+ 1 - 1
src/layout/components/AppMain.vue

@@ -16,7 +16,7 @@ export default {
       return this.$store.state.tagsView.cachedViews
     },
     key() {
-      this.$store.dispatch("getMessageNum", {}).then((res) => {}).catch(() => {});
+      // this.$store.dispatch("getMessageNum", {}).then((res) => {}).catch(() => {});
       return this.$route.path
     }
   },

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

@@ -21,8 +21,9 @@
           @rendered="renderedHandler"
           @error="errorHandler"/>
         <p v-if="officeNullType" class="office-null-text">加载失败...</p>
-        <video v-if="officeType === 'video'" ref="videoPlayer"
-               class="video-box" :src="lookUrl" autoplay  controls muted></video>
+        <video-component :userId="userId" :url="lookUrl" />
+        <!--<video v-if="officeType === 'video'" ref="videoPlayer"-->
+               <!--class="video-box" :src="lookUrl" autoplay  controls muted></video>-->
         <div  v-if="officeType === 'text'" class="rich-text-box" v-html="lookUrl"></div>
       </transition>
     </div>
@@ -38,18 +39,21 @@
   import '@vue-office/excel/lib/index.css'
   //引入VueOfficePdf组件
   import VueOfficePdf from '@vue-office/pdf'
+  import videoComponent from "@/views/safetyEducationExaminationNew/components/videoComponent.vue";
   export default {
     name: 'addPage',
     components: {
       VueOfficeDocx,
       VueOfficeExcel,
       VueOfficePdf,
+      videoComponent,
     },
     props: {
       coursewareComponentPropsData: {}
     },
     data() {
       return {
+        userId:localStorage.getItem('userId'),
         showType:false,
         officeNullType:false,
         //文档类型

+ 117 - 0
src/views/safetyEducationExaminationNew/components/videoComponent.vue

@@ -0,0 +1,117 @@
+<template>
+  <div class="video-component">
+    <video
+      ref="videoRef"
+      :src="url"
+      autoplay  controls muted
+      preload="metadata"
+      class="video-player"
+      @timeupdate="onTimeUpdate"
+      @loadedmetadata="onLoadedMetadata"
+      @pause="saveProgress"
+      @ended="onEnded"
+    ></video>
+  </div>
+</template>
+
+<script>
+const STORAGE_KEY_PREFIX = 'video_progress_'
+
+export default {
+  name: 'VideoComponent',
+  props: {
+    userId: {
+      type: [String, Number],
+      required: true,
+    },
+    url: {
+      type: String,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      // 节流定时器:每5秒最多保存一次
+      _saveTimer: null,
+    }
+  },
+  computed: {
+    // 每个用户+每个视频单独一个key
+    storageKey() {
+      const safeUrl = this.url.replace(/[^a-zA-Z0-9]/g, '_').slice(-60)
+      return `${STORAGE_KEY_PREFIX}${this.userId}_${safeUrl}`
+    },
+  },
+  methods: {
+    // 读取上次保存的时间点
+    getSavedTime() {
+      try {
+        const val = localStorage.getItem(this.storageKey)
+        return val ? parseFloat(val) : 0
+      } catch {
+        return 0
+      }
+    },
+
+    // 视频元数据加载完成后跳转到上次位置
+    onLoadedMetadata() {
+      const saved = this.getSavedTime()
+      const video = this.$refs.videoRef
+      if (saved > 0 && video && saved < video.duration - 1) {
+        video.currentTime = saved
+      }
+    },
+
+    // timeupdate 触发频繁(约250ms一次),用节流降低保存频率到每5秒一次
+    onTimeUpdate() {
+      if (this._saveTimer) return
+      this._saveTimer = setTimeout(() => {
+        this._saveTimer = null
+        this.saveProgress()
+      }, 5000)
+    },
+
+    // 实际写入 localStorage
+    saveProgress() {
+      const video = this.$refs.videoRef
+      if (!video) return
+      try {
+        localStorage.setItem(this.storageKey, String(video.currentTime))
+      } catch {
+        // localStorage 不可用时静默失败
+      }
+    },
+
+    // 播放结束时清除记录,下次从头开始
+    onEnded() {
+      try {
+        localStorage.removeItem(this.storageKey)
+      } catch {
+        // ignore
+      }
+    },
+  },
+  beforeDestroy() {
+    // 组件销毁前立即保存一次,清除定时器
+    if (this._saveTimer) {
+      clearTimeout(this._saveTimer)
+      this._saveTimer = null
+    }
+    this.saveProgress()
+  },
+}
+</script>
+
+<style scoped lang="scss">
+.video-component {
+  width: 100%;
+
+  .video-player {
+    width:1458px;
+    height:797px;
+    display: block;
+    background: #000;
+    border-radius: 6px;
+  }
+}
+</style>

+ 78 - 69
src/views/safetyEducationExaminationNew/featurePage/learningCourse/index.vue

@@ -16,7 +16,7 @@
         <div class="progress-box">
           <p class="progress-title-box">学习进度</p>
           <div class="progress-min-box">
-            <el-progress :stroke-width="12" :percentage="newData.coursewares[0].progress?newData.coursewares[0].progress:0"></el-progress>
+            <el-progress :stroke-width="12" :percentage="newData.progress?newData.progress:0"></el-progress>
           </div>
         </div>
         <div class="learning-for-max-big-box scrollbar-box">
@@ -26,7 +26,7 @@
                  :class="checkLearningIndex == index ?'check-for-box':''">
               <p>{{item.coursewareName}}</p>
               <p>已学习:{{formatTime(item.learnedDuration)}} / {{formatTime(item.minLearningDuration)}}</p>
-              <p :class="item.learnStatus ==2 ?'colorA':''">{{item.learnStatus==2?'已学完':'未学完'}}</p>
+              <p :class="item.learnStatus ==2 ?'colorA':(item.learnStatus ==1 ?'colorB':'')">{{item.learnStatus==2?'已学完':(item.learnStatus==1?'学习中':'未学完')}}</p>
             </div>
             <p class="for-button" @click="finishStudying(index)"
                v-if="item.learnStatus != 2 && item.learnedDuration == item.minLearningDuration && checkLearningIndex == index">
@@ -82,72 +82,72 @@
           totalCoursewareCount:0,
           progress:0,
           coursewares:[
-            {
-              coursewareName:'视频课件名称',
-              listData2:1,
-              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:'',
-            },
-            {
-              coursewareName:'docx课件名称',
-              listData2:1,
-              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:'',
-            },
-            {
-              coursewareName:'xlsx课件名称',
-              listData2:1,
-              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:'',
-            },
-            {
-              coursewareName:'pdf课件名称',
-              listData2:1,
-              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:'',
-            },
-            {
-              coursewareName:'富文本课件名称',
-              listData2:1,
-              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>"
-            },
-            {
-              coursewareName:'xlsx课件名称',
-              listData2:1,
-              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:'',
-            },
+            // {
+            //   coursewareName:'视频课件名称',
+            //   listData2:1,
+            //   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:'',
+            // },
+            // {
+            //   coursewareName:'docx课件名称',
+            //   listData2:1,
+            //   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:'',
+            // },
+            // {
+            //   coursewareName:'xlsx课件名称',
+            //   listData2:1,
+            //   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:'',
+            // },
+            // {
+            //   coursewareName:'pdf课件名称',
+            //   listData2:1,
+            //   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:'',
+            // },
+            // {
+            //   coursewareName:'富文本课件名称',
+            //   listData2:1,
+            //   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>"
+            // },
+            // {
+            //   coursewareName:'xlsx课件名称',
+            //   listData2:1,
+            //   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:'',
+            // },
           ],
         },
         //当前选中
@@ -264,6 +264,9 @@
           url = this.newData.coursewares[index].articleContent;
         }
         this.$refs['coursewareComponent'].initialize(name,url,type);
+        if(this.newData.coursewares[this.checkLearningIndex].learnStatus == 0){
+          this.$set(this.newData.coursewares[this.checkLearningIndex],'learnStatus',1);
+        }
       },
       //学习完成
       examUserCoursewareLearningConfirmCompletion(){
@@ -527,7 +530,7 @@
                 position: absolute;
                 bottom: 16px;
                 right: 16px;
-                color:#fff!important;
+                color:#fff;
                 background-color: #999;
                 border-radius:4px;
                 width:60px;
@@ -536,6 +539,12 @@
               }
               .colorA{
                 background-color: #0183fa!important;
+                color:#fff!important;
+              }
+              .colorB{
+                background-color: #fff!important;
+                border:1px solid #0183fa!important;
+                color:#0183fa!important;
               }
             }
             .for-button{