Ver código fonte

背景图优化

dsy 2 semanas atrás
pai
commit
4f90fb2d1a

+ 5 - 0
src/views/book/courseware/preview/CoursewarePreview.vue

@@ -433,6 +433,7 @@ export default {
         if (back.imageMode === 'fill') {
           canvasStyle['backgroundRepeat'] = 'repeat';
           canvasStyle['backgroundSize'] = '';
+          canvasStyle['backgroundPosition'] = '';
         } else {
           canvasStyle['backgroundRepeat'] = 'no-repeat';
         }
@@ -445,6 +446,10 @@ export default {
           canvasStyle['backgroundSize'] = 'contain';
         }
 
+        if (back.imageMode === 'auto') {
+          canvasStyle['backgroundPosition'] = `${pos.imgX}% ${pos.imgY}%`;
+        }
+
         if (back.mode === 'color') {
           canvasStyle['backgroundColor'] = back.color;
           canvasStyle['backgroundImage'] = '';

+ 2 - 1
src/views/book/courseware/preview/components/article/components/AudioLineSentence.vue

@@ -331,7 +331,8 @@ export default {
     align-items: center;
     width: 100%;
     height: 40px;
-    background: #fff;
+
+    // background: #fff;
     border-radius: 4px;
 
     &-green {

+ 2 - 1
src/views/book/courseware/preview/components/character_structure/CharacterStructurePreview.vue

@@ -448,7 +448,8 @@ export default {
 
     // padding: 5px;
     overflow: hidden;
-    background: #fff;
+
+    // background: #fff;
     background-size: cover;
     border: 2px solid #346cda;
     border-radius: 4px;

+ 3 - 2
src/views/book/courseware/preview/components/select/SelectPreview.vue

@@ -37,9 +37,10 @@
           </div>
         </li>
       </ul>
+
+      <PreviewOperation @showAnswerAnalysis="showAnswerAnalysis" @retry="retry" />
     </div>
 
-    <PreviewOperation @showAnswerAnalysis="showAnswerAnalysis" @retry="retry" />
     <AnswerCorrect
       :answer-correct="data?.answer_correct"
       :visible.sync="visibleAnswerCorrect"
@@ -170,7 +171,7 @@ export default {
       }
 
       // 判断是否是正确答案
-      if (isHas && this.isJudgingRightWrong) {
+      if (isHas && this.isJudgingRightWrong && type !== 'answer-analysis') {
         answerClass = isRight ? ['answer-right'] : ['wrong'];
       }
       return answerClass;

+ 143 - 90
src/views/book/courseware/preview/components/video/VideoPreview.vue

@@ -1,70 +1,29 @@
 <template>
   <div ref="videoArea" class="video-preview" :style="[getAreaStyle(), getComponentStyle()]">
     <SerialNumberPosition v-if="isEnable(data.property.sn_display_mode)" :property="data.property" />
-    <div ref="videoAreaBox" class="main">
-      <template v-if="isMore">
-        <ul v-if="'independent' === data.property.view_method" class="view-independent">
-          <li v-for="(file, i) in data.file_list" :key="i">
-            <VideoPlay
-              view-size="small"
-              view-method="independent"
-              :file-id="file.file_id"
-              :cur-video-index="curVideoIndex"
-              @changeFile="changeFile"
-            />
-          </li>
-        </ul>
-        <div v-else class="view-list">
-          <el-carousel
-            ref="video_carousel"
-            indicator-position="none"
-            direction="vertical"
-            :autoplay="false"
-            :interval="0"
-            :style="{ width: elementWidth - 248 - 32 + 'px', height: elementHeight <= 0 ? 139 : elementHeight + 'px' }"
-          >
-            <el-carousel-item v-for="(file, i) in data.file_list" :key="i">
-              <VideoPlay
-                view-size="big"
-                :file-id="file.file_id"
-                :cur-video-index="curVideoIndex"
-                @changeFile="changeFile"
-              />
-            </el-carousel-item>
-          </el-carousel>
-          <div class="container-box" :style="{ height: elementHeight <= 0 ? 139 : elementHeight + 'px' }">
-            <ul
-              ref="container"
-              class="view-list-bottom"
-              :style="{ height: elementHeight + 'px', transform: `translateY(${translateY}px)` }"
-            >
-              <li v-for="(file, i) in data.file_list" :key="i" @click="handleAudioClick(i)">
-                <VideoPlay
-                  view-size="small"
-                  :file-id="file.file_id"
-                  :audio-index="i"
-                  :cur-video-index="curVideoIndex"
-                />
-              </li>
-            </ul>
-            <button v-if="viewTopBottomBtn" class="arrow top" @click="scroll(1)">
-              <i class="el-icon-arrow-up"></i>
-            </button>
-            <button v-if="viewTopBottomBtn" class="arrow bottom" @click="scroll(-1)">
-              <i class="el-icon-arrow-down"></i>
-            </button>
-          </div>
-        </div>
-      </template>
-      <template v-else>
+
+    <div v-if="'independent' === data.property.view_method" ref="videoAreaBox" class="main">
+      <ul class="view-independent">
+        <li v-for="(file, i) in data.file_list" :key="i" :style="getVideoItemStyle()">
+          <VideoPlay
+            view-size="small"
+            view-method="independent"
+            :file-id="file.file_id"
+            :cur-video-index="curVideoIndex"
+            @changeFile="changeFile"
+          />
+        </li>
+      </ul>
+    </div>
+    <div v-else ref="videoAreaBox">
+      <div class="view-list">
         <el-carousel
           ref="video_carousel"
           indicator-position="none"
           direction="vertical"
           :autoplay="false"
           :interval="0"
-          class="alone-video-area"
-          :style="{ height: elementHeight <= 0 ? 139 : elementHeight + 'px' }"
+          :style="{ width: elementWidth - 248 - 32 + 'px', height: elementHeight <= 0 ? 139 : elementHeight + 'px' }"
         >
           <el-carousel-item v-for="(file, i) in data.file_list" :key="i">
             <VideoPlay
@@ -75,7 +34,24 @@
             />
           </el-carousel-item>
         </el-carousel>
-      </template>
+        <div class="container-box" :style="{ height: elementHeight <= 0 ? 139 : elementHeight + 'px' }">
+          <ul
+            ref="container"
+            class="view-list-bottom"
+            :style="{ height: elementHeight + 'px', transform: `translateY(${translateY}px)` }"
+          >
+            <li v-for="(file, i) in data.file_list" :key="i" @click="handleAudioClick(i)">
+              <VideoPlay view-size="small" :file-id="file.file_id" :audio-index="i" :cur-video-index="curVideoIndex" />
+            </li>
+          </ul>
+          <button v-if="viewTopBottomBtn" class="arrow top" @click="scroll(1)">
+            <i class="el-icon-arrow-up"></i>
+          </button>
+          <button v-if="viewTopBottomBtn" class="arrow bottom" @click="scroll(-1)">
+            <i class="el-icon-arrow-down"></i>
+          </button>
+        </div>
+      </div>
     </div>
   </div>
 </template>
@@ -102,17 +78,15 @@ export default {
       elementID: '',
       isResizing: false,
       resizeObserver: null,
-      isMore: false,
+      isFirstLoad: true,
     };
   },
   watch: {
     data: {
       handler(val) {
         this.fileLen = val.file_list.length;
-        if (this.fileLen > 1) {
-          this.isMore = true;
-        }
-        if ((this.fileLen > 0 && this.data.property.view_method === 'list') || this.fileLen === 1) {
+
+        if (this.fileLen > 0 && this.data.property.view_method === 'list') {
           const ele = this.$refs.videoAreaBox;
           const sn_position = this.data.property.sn_position;
           // 序号在左和右补齐序号高度,去掉padding(8*2)
@@ -138,6 +112,22 @@ export default {
             }
           });
         }
+
+        if (this.fileLen > 0 && this.data.property.view_method === 'independent') {
+          const ele = this.$refs.videoAreaBox;
+          const sn_position = this.data.property.sn_position;
+          if (sn_position.includes('left') || sn_position.includes('right')) {
+            this.elementWidth = ele.clientWidth - 16;
+            this.elementHeight = ele.clientHeight + 30;
+          } else {
+            this.elementWidth = ele.clientWidth;
+            this.elementHeight = ele.clientHeight;
+          }
+
+          if (ele.clientHeight <= 0) {
+            this.elementHeight = this.data.min_height;
+          }
+        }
       },
       deep: true,
     },
@@ -164,38 +154,41 @@ export default {
           this.elementHeight = this.data.min_height;
         }
         this.fileLen = this.data.file_list.length;
-        if (this.fileLen > 1) {
-          this.isMore = true;
-        }
+
         this.isViewTopBottomBtn();
         return;
       }
+
       this.resizeObserver = new ResizeObserver((entries) => {
-        if (!this.getDragStatus()) return;
+        if (!this.getDragStatus() && !this.isFirstLoad) return;
         this.isResizing = true; // 标记为调整中
-        for (let entry of entries) {
-          window.requestAnimationFrame(() => {
-            const sn_position = this.data.property.sn_position;
-            // 序号在上方和下方减去序号高度
-            let w = entry.contentRect.width - 16;
-            let h = entry.contentRect.height;
-            if (sn_position.includes('top') || sn_position.includes('bottom')) {
-              w = entry.contentRect.width;
-              h = entry.contentRect.height - 30;
-            }
-            if (this.elementWidth === w) {
-              this.elementHeight = h;
-            } else {
-              this.elementWidth = w;
-            }
-          });
-        }
-
+        const delay = this.isFirstLoad ? 1000 : 0;
+        setTimeout(() => {
+          for (let entry of entries) {
+            window.requestAnimationFrame(() => {
+              const sn_position = this.data.property.sn_position;
+              // 序号在上方和下方减去序号高度
+              let w = entry.contentRect.width - 16;
+              let h = entry.contentRect.height;
+              if (sn_position.includes('top') || sn_position.includes('bottom')) {
+                w = entry.contentRect.width;
+                h = entry.contentRect.height - 30;
+              }
+              if (this.elementWidth === w) {
+                this.elementHeight = h;
+              } else {
+                this.elementWidth = w;
+              }
+            });
+          }
+        }, delay);
+        this.isFirstLoad = false;
         // 防抖:100ms 后恢复监听
         setTimeout(() => {
           this.isResizing = false;
         }, 500);
       });
+
       this.resizeObserver.observe(this.$el);
     });
   },
@@ -205,6 +198,18 @@ export default {
     }
   },
   methods: {
+    getVideoItemStyle() {
+      if (this.data.property.view_method !== 'independent') {
+        return {};
+      }
+      const height = this.elementHeight > 0 ? this.elementHeight : Number(this.data.min_height);
+      const width = (height * 16) / 9;
+      return {
+        width: `${width}px`,
+        height: `${height}px`,
+        flexShrink: 0,
+      };
+    },
     handleAudioClick(index) {
       // 获取 Carousel 实例
       const carousel = this.$refs.video_carousel;
@@ -288,13 +293,61 @@ export default {
 
   .view-independent {
     display: flex;
-    flex-wrap: wrap;
     gap: 20px;
-    width: 100%;
+    width: fit-content;
+    padding: 0;
+    margin: 0;
 
     > li {
-      width: 248px;
-      height: 139px;
+      flex-shrink: 0;
+      list-style: none;
+
+      :deep(.video-play) {
+        width: 100%;
+        height: 100%;
+      }
+    }
+  }
+
+  .main {
+    position: relative;
+    overflow: auto hidden !important;
+
+    // Firefox 滚动条样式
+    scrollbar-width: none; // 默认隐藏
+
+    // 自定义滚动条样式 - 完全浮动
+    &::-webkit-scrollbar {
+      width: 0; // 默认宽度为 0
+      height: 0;
+      transition:
+        width 0.3s ease,
+        height 0.3s ease;
+    }
+
+    // 鼠标悬停时显示滚动条
+    &:hover::-webkit-scrollbar {
+      width: 8px;
+      height: 8px;
+    }
+
+    &::-webkit-scrollbar-thumb {
+      background-color: rgba(0, 0, 0, 50%);
+      border-radius: 4px;
+      transition: background-color 0.3s ease;
+
+      &:hover {
+        background-color: rgba(0, 0, 0, 70%);
+      }
+    }
+
+    &::-webkit-scrollbar-track {
+      background-color: transparent;
+    }
+
+    &:hover {
+      scrollbar-width: thin; // 鼠标悬停时显示
+      scrollbar-color: rgba(0, 0, 0, 50%) transparent;
     }
   }
 

+ 94 - 12
src/views/book/courseware/preview/components/video_interaction/VideoInteractionPreview.vue

@@ -3,7 +3,7 @@
   <div class="imageText-preview" :style="[getAreaStyle(), getComponentStyle()]">
     <SerialNumberPosition v-if="isEnable(data.property.sn_display_mode)" :property="data.property" />
     <div v-if="data.video_list.length > 0" class="interaction-box">
-      <video
+      <!-- <video
         id="interaction-preview-video"
         :src="data.video_list[0].file_url"
         width="100%"
@@ -11,6 +11,15 @@
         controls
         controlsList="nodownload"
         @timeupdate="handleTimeUpdate"
+      ></video> -->
+      <video
+        ref="videoPlayer"
+        id="interaction-preview-video"
+        :src="data.video_list[0].file_url"
+        width="100%"
+        height="400"
+        controls
+        class="video-js vjs-default-skin vjs-big-play-centered"
       ></video>
     </div>
     <!-- v-if="Object.keys(this.userAnswer).length === data.file_info_list.length" -->
@@ -21,8 +30,10 @@
           data.unified_attrib && data.unified_attrib.topic_color ? data.unified_attrib.topic_color : '#165dff',
         borderColor:
           data.unified_attrib && data.unified_attrib.topic_color ? data.unified_attrib.topic_color : '#165dff',
+        marginTop: '5px',
       }"
       @click="lookReport"
+      v-if="data.property.feed_back === 'total'"
       >{{ convertText('查看答题报告') }}</el-button
     >
     <AnswerCorrect
@@ -77,6 +88,10 @@ import Report from './Report.vue';
 import PreviewMixin from '../common/PreviewMixin';
 import { getVideoInteractionData } from '@/views/book/courseware/data/videoInteraction';
 import { getConfig } from '@/utils/auth';
+import 'videojs-markers/dist/videojs.markers.min.css';
+import videojs from 'video.js';
+import 'video.js/dist/video-js.css';
+import 'videojs-markers';
 export default {
   name: 'VideoInteractionPreview',
 
@@ -107,6 +122,7 @@ export default {
         error: 0,
         rightRate: 0,
       },
+      player: null,
     };
   },
   watch: {
@@ -125,18 +141,79 @@ export default {
       if (!this.isJudgingRightWrong) {
         this.answer.answer_list[0] = this.userAnswer;
       }
+      if (this.data.video_list.length > 0) {
+        let _this = this;
+        this.$nextTick(() => {
+          _this.handleCreatPlayer();
+        });
+      }
     },
-    handleTimeUpdate(event) {
-      let currentTime = event.target.currentTime;
-
+    // 初始化player
+    handleCreatPlayer() {
+      let _this = this;
+      let options = {
+        autoplay: false, //自动播放
+        height: 500,
+        width: 1000,
+        controls: true, //用户可以与之交互的控件
+        loop: true, //视频一结束就重新开始
+        muted: false, //默认情况下将使所有音频静音
+        playsinline: true,
+        webkitPlaysinline: true,
+        // aspectRatio:"16:9",//显示比率
+        playbackRates: [0.5, 1, 1.5, 2],
+        fullscreen: {
+          options: { navigationUI: 'hide' },
+        },
+        sources: [
+          {
+            src: this.data.video_list[0].file_url,
+            type: 'video/mp4',
+          },
+        ],
+      };
+      this.player = videojs(this.$refs.videoPlayer, options, function onPlayerReady() {
+        // console.log('onPlayerReady');
+      });
+      // 设置标点
+      _this.handleMarkers();
+      // 获取当前播放时间
+      this.player.on('timeupdate', function (event) {
+        _this.handleTimeUpdate(this.currentTime());
+        // console.log(this.currentTime());
+      });
+    },
+    // 设置标点
+    handleMarkers() {
+      let markers = [];
+      this.data.file_info_list.forEach((item) => {
+        markers.push({
+          time: item.currentTime,
+          text: item.file_name ?? item.name,
+        });
+      });
+      this.player.markers({
+        // 不显示鼠标悬浮标记提示文字
+        markerTip: {
+          display: true,
+        },
+        markerStyle: {
+          width: '4px',
+          'background-color': 'red',
+          'border-radius': '50%',
+        },
+        markers: markers,
+      });
+    },
+    handleTimeUpdate(currentTime) {
       this.data.file_info_list.forEach((item) => {
         if (
-          Number(item.currentTime) > Math.floor(currentTime) &&
-          Number(item.currentTime) < Math.floor(currentTime) + 1 &&
+          Number(item.currentTime) > Math.floor(currentTime) - 0.3 &&
+          Number(item.currentTime) < Math.floor(currentTime) + 0.3 &&
           item.id !== this.first
         ) {
           this.first = item.id;
-          document.getElementById('interaction-preview-video').pause();
+          this.player.pause();
           this.exercise_id = item.id;
           this.visible = true;
           // GetFileURLMap({ file_id_list: [item.file_id] }).then(({ url_map }) => {
@@ -148,22 +225,22 @@ export default {
     },
     handleClose() {
       this.reportFlag = false;
-      document.getElementById('interaction-preview-video').play();
+      this.player.play();
       // setTimeout(() => {
       //   this.first = '';
       // }, 1000);
     },
     submitAdd(id, answer, content) {
       this.visible = false;
-      document.getElementById('interaction-preview-video').play();
+      this.player.play();
       setTimeout(() => {
-        this.first = '';
-      }, 1000);
+        // this.first = '';
+      }, 2000);
       this.$set(this.userAnswer, id, answer);
       this.$set(this.exerciseList, id, content);
     },
     lookReport() {
-      document.getElementById('interaction-preview-video').pause();
+      this.player.pause();
       let num = 0;
       let total = this.data.file_info_list.length;
       Object.keys(this.userAnswer).forEach((key) => {
@@ -180,6 +257,11 @@ export default {
       this.reportFlag = true;
     },
   },
+  beforeDestroy() {
+    if (this.player) {
+      this.player.dispose();
+    }
+  },
 };
 </script>
 

+ 0 - 1
src/views/book/courseware/preview/components/voice_matrix/VoiceMatrixPreview.vue

@@ -666,7 +666,6 @@ $border-color: #e6e6e6;
     .voice-matrix-container {
       font-size: 16px;
       word-break: break-word;
-      background-color: #f5f5f5;
       border-right: 1px solid $border-color;
       border-bottom: 1px solid $border-color;
       border-left: 1px solid $border-color;

+ 2 - 1
src/views/book/courseware/preview/components/voice_matrix/components/AudioLine.vue

@@ -368,7 +368,8 @@ export default {
     align-items: center;
     width: 100%;
     height: 40px;
-    background: #fff;
+
+    // background: #fff;
     border-radius: 8px;
 
     .playBox {