Browse Source

图片文本融合打点字幕

natasha 9 hours ago
parent
commit
893f2ad359

+ 1 - 1
src/api/article.js

@@ -27,5 +27,5 @@ export function analysSubmit(data) {
 
 // 音频转为base64流
 export function fileToBase64Text(data) {
-  return http.post(`/FileServer/SI?MethodName=file_store_manager-GetFileByteBase64Text`, data);
+  return http.post(`/FileServer/SI?MethodName=file_store_manager-GetFileByteBase64Text`, data, {}, true);
 }

+ 3 - 0
src/icons/svg/arrow-left-s-line.svg

@@ -0,0 +1,3 @@
+<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M6.31592 7.00041L9.2033 9.88773L8.37835 10.7127L4.66602 7.00041L8.37835 3.28809L9.2033 4.11304L6.31592 7.00041Z" fill="currentColor"/>
+</svg>

+ 3 - 0
src/icons/svg/arrow-right-s-line.svg

@@ -0,0 +1,3 @@
+<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M8.26624 7.00236L5.37891 4.11499L6.20386 3.29004L9.9162 7.00236L6.20386 10.7146L5.37891 9.88968L8.26624 7.00236Z" fill="currentColor"/>
+</svg>

+ 3 - 0
src/icons/svg/pause.svg

@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M8.75098 6C8.75098 5.58579 8.41519 5.25 8.00098 5.25C7.58676 5.25 7.25098 5.58579 7.25098 6V18C7.25098 18.4142 7.58676 18.75 8.00098 18.75C8.41519 18.75 8.75098 18.4142 8.75098 18V6ZM16.749 6C16.749 5.58579 16.4132 5.25 15.999 5.25C15.5848 5.25 15.249 5.58579 15.249 6V18C15.249 18.4142 15.5848 18.75 15.999 18.75C16.4132 18.75 16.749 18.4142 16.749 18V6Z" fill="currentColor"/>
+</svg>

+ 3 - 0
src/icons/svg/play.svg

@@ -0,0 +1,3 @@
+<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M11.125 9.28798C11.3571 9.15401 11.643 9.15401 11.875 9.28798L22.375 15.3502C22.6071 15.4842 22.75 15.7318 22.75 15.9997C22.75 16.2676 22.6071 16.5152 22.375 16.6492L11.875 22.7114C11.643 22.8454 11.3571 22.8454 11.125 22.7114C10.8929 22.5774 10.75 22.3299 10.75 22.0619V9.9375C10.75 9.66955 10.8929 9.42196 11.125 9.28798ZM12.25 11.2365V20.7629L20.5 15.9997L12.25 11.2365Z" fill="currentColor"/>
+</svg>

+ 3 - 3
src/views/book/courseware/create/components/question/image_text/ImageText.vue

@@ -237,9 +237,9 @@ export default {
     updateFileList({ file_list, file_id_list, file_info_list }) {
       this.data.image_list = file_list;
       this.data.image_id_list = file_id_list;
-      this.data.image_info_list = file_info_list;
-      this.data.file_id_list =
-        this.data.mp3_list.length > 0 ? file_id_list.push(this.data.mp3_list[0].file_id) : file_id_list;
+      // this.data.file_id_list =
+      //   this.data.mp3_list.length > 0 ? file_id_list.push(this.data.mp3_list[0].file_id) : file_id_list;
+      this.data.file_id_list = file_id_list;
     },
     uploadAudioSuccess(fileList) {
       if (fileList.length > 0) {

+ 63 - 11
src/views/book/courseware/preview/components/image_text/ImageTextPreview.vue

@@ -28,7 +28,7 @@
       <div
         v-for="(itemP, indexP) in data.text_list"
         :key="'text' + indexP"
-        :class="['position-item', mageazineDetailIndex === indexP ? 'active' : '']"
+        :class="['position-item', sentIndex === indexP ? 'active' : '']"
         :style="{
           width: itemP.width,
           height: itemP.height,
@@ -51,6 +51,25 @@
         <el-input v-model="answer.answer_list[indexP].text" type="textarea" style="height: 100%" placeholder="请输入" />
       </div>
     </div>
+    <el-dialog
+      :visible.sync="mageazineDetailShow"
+      :show-close="false"
+      :close-on-click-modal="false"
+      width="80%"
+      class="login-dialog magazine-detail-dialog"
+      :modal="false"
+      v-if="mageazineDetailShow"
+    >
+      <magazine-sentence
+        :fontSize="fontSize"
+        :sentenceTheme="sentenceTheme"
+        :data="data.word_time"
+        :activeIndex="mageazineDetailIndex"
+        @closeWord="closeMagazineSentence"
+        @changeTheme="changeTheme"
+        :mp3Url="mp3_url"
+      ></magazine-sentence>
+    </el-dialog>
   </div>
 </template>
 
@@ -59,10 +78,11 @@ import PreviewMixin from '../common/PreviewMixin';
 import AudioLine from '../voice_matrix/components/AudioLine.vue';
 import { getImageTextData } from '@/views/book/courseware/data/imageText';
 import { GetFileURLMap } from '@/api/app';
+import MagazineSentence from './components/MagazineSentence.vue';
 export default {
   name: 'ImageTextPreview',
 
-  components: { AudioLine },
+  components: { AudioLine, MagazineSentence },
   mixins: [PreviewMixin],
   data() {
     return {
@@ -77,6 +97,8 @@ export default {
       mageazineDetailIndex: null, // 当前高亮第几个
       mageazineDetailShow: false,
       inputIndex: null,
+      fontSize: 20,
+      sentenceTheme: 0,
     };
   },
   created() {
@@ -115,14 +137,14 @@ export default {
       this.getSentIndex(this.curTime);
     },
     getSentIndex(curTime) {
-      // for (let i = 0; i < this.curQue.wordTime.length; i++) {
-      //   let bg = this.curQue.wordTime[i].bg;
-      //   let ed = this.curQue.wordTime[i].ed;
-      //   if (curTime >= bg && curTime <= ed) {
-      //     this.sentIndex = i;
-      //     break;
-      //   }
-      // }
+      for (let i = 0; i < this.data.word_time.length; i++) {
+        let bg = this.data.word_time[i].bg;
+        let ed = this.data.word_time[i].ed;
+        if (curTime >= bg && curTime <= ed) {
+          this.sentIndex = i;
+          break;
+        }
+      }
     },
     emptyEd() {
       this.ed = undefined;
@@ -132,9 +154,18 @@ export default {
       if (this.$refs.audioLine.audio.playing) {
         this.$refs.audioLine.PlayAudio();
       }
-      this.mageazineDetailIndex = index;
+      this.sentIndex = index;
       this.mageazineDetailShow = true;
     },
+    // 关闭画刊卡片
+    closeMagazineSentence() {
+      this.mageazineDetailShow = false;
+    },
+    // 切换主题色和文字大小
+    changeTheme(theme, size) {
+      if (theme !== '') this.sentenceTheme = theme;
+      if (size) this.fontSize = size;
+    },
   },
 };
 </script>
@@ -175,3 +206,24 @@ export default {
   margin: 20px auto;
 }
 </style>
+<style lang="scss">
+.magazine-detail-dialog {
+  .el-dialog__header,
+  .el-dialog__body {
+    padding: 0;
+  }
+
+  .el-dialog {
+    position: absolute;
+    bottom: 50px;
+    left: 50%;
+    margin-left: -40%;
+    border: none;
+    border-radius: 16px;
+    box-shadow:
+      0 6px 30px 5px rgba(0, 0, 0, 5%),
+      0 16px 24px 2px rgba(0, 0, 0, 4%),
+      0 8px 10px -5px rgba(0, 0, 0, 8%);
+  }
+}
+</style>

+ 426 - 0
src/views/book/courseware/preview/components/image_text/components/MagazineSentence.vue

@@ -0,0 +1,426 @@
+<template>
+  <div class="sentence-box" :style="{ background: themeList[sentenceTheme].bg }">
+    <div class="sentence-top">
+      <a class="play-btn" @click="handlePlay" :style="{ background: themeList[sentenceTheme].playBtnBg }">
+        <svg-icon v-if="isPlay" icon-class="pause" size="24"></svg-icon>
+        <svg-icon v-else icon-class="play" size="24"></svg-icon>
+      </a>
+      <div class="sentence-right" :style="{ color: themeList[sentenceTheme].rightBtnColor }">
+        <a class="btn" @click="handlePage('-')"><svg-icon icon-class="arrow-left-s-line" size="24"></svg-icon></a>
+        <span>{{ sentenceActive + 1 + '/' + data.length }}</span>
+        <a class="btn" @click="handlePage('+')"><svg-icon icon-class="arrow-right-s-line" size="24"></svg-icon></a>
+        <i class="el-icon-close" @click="closeWord"></i>
+      </div>
+    </div>
+    <div class="content-inner" :style="{ background: themeList[sentenceTheme].contentBg }">
+      <template v-for="(itemC, indexC) in data[sentenceActive].wordsResultList">
+        <div
+          :key="indexC"
+          :class="[
+            'content-item',
+            (activeWordIndex === null &&
+              currentTime * 1000 <= data[sentenceActive].ed &&
+              currentTime * 1000 >= data[sentenceActive].wordsResultList[indexC].wordBg) ||
+            activeWordIndex === indexC
+              ? 'active'
+              : '',
+          ]"
+          :style="{
+            color:
+              (activeWordIndex === null &&
+                currentTime * 1000 <= data[sentenceActive].ed &&
+                currentTime * 1000 >= data[sentenceActive].wordsResultList[indexC].wordBg) ||
+              activeWordIndex === indexC
+                ? themeList[sentenceTheme].sentenceActiveColor
+                : themeList[sentenceTheme].sentenceColor,
+            fontSize: fontSize + 'px',
+            lineHeight: fontSize + 8 + 'px',
+          }"
+          @click="palyWord(indexC)"
+        >
+          {{ itemC.wordsName || itemC.onebest }}
+        </div>
+      </template>
+    </div>
+    <div class="sentence-bottom">
+      <div class="fontsize-box" :style="{ background: themeList[sentenceTheme].bottomBg }">
+        <span
+          :style="{
+            background: fontSize === 20 ? themeList[sentenceTheme].bottomBarActiveBtnBg : '',
+            color: fontSize === 20 ? themeList[sentenceTheme].bottomBarActive : '',
+          }"
+          @click="handleChangeBgColor(20, 'fontSize')"
+          >小</span
+        >
+        <div
+          class="border"
+          :style="{
+            background: fontSize === 28 ? themeList[sentenceTheme].bottomBarBorder : '',
+          }"
+        ></div>
+        <span
+          :style="{
+            background: fontSize === 24 ? themeList[sentenceTheme].bottomBarActiveBtnBg : '',
+            color: fontSize === 24 ? themeList[sentenceTheme].bottomBarActive : '',
+          }"
+          @click="handleChangeBgColor(24, 'fontSize')"
+          >中</span
+        >
+        <div
+          class="border"
+          :style="{
+            background: fontSize === 20 ? themeList[sentenceTheme].bottomBarBorder : '',
+          }"
+        ></div>
+        <span
+          :style="{
+            background: fontSize === 28 ? themeList[sentenceTheme].bottomBarActiveBtnBg : '',
+            color: fontSize === 28 ? themeList[sentenceTheme].bottomBarActive : '',
+          }"
+          @click="handleChangeBgColor(28, 'fontSize')"
+          >大</span
+        >
+      </div>
+      <ul class="article-color" :style="{ background: themeList[sentenceTheme].bottomBg }">
+        <li
+          :class="['color-item', sentenceTheme === indexC ? 'active' : '']"
+          v-for="(itemC, indexC) in themeList"
+          :key="indexC"
+          @click="handleChangeBgColor(indexC, 'theme')"
+          :style="{
+            borderColor: sentenceTheme === indexC ? itemC.boxBorder : '',
+          }"
+        >
+          <a
+            :style="{
+              background: itemC.themeBg,
+              borderColor: sentenceTheme === indexC ? itemC.themeActiveBorder : '',
+            }"
+          ></a>
+        </li>
+      </ul>
+    </div>
+  </div>
+</template>
+
+<script>
+//这里可以导入其它文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
+//例如:import 《组件名称》from ‘《组件路径》';
+
+export default {
+  //import引入的组件需要注入到对象中才能使用
+  components: {},
+  props: ['fontSize', 'sentenceTheme', 'data', 'activeIndex', 'mp3Url'],
+  data() {
+    //这里存放数据
+    return {
+      isPlay: false, // 音频是否在播放
+      sentenceActive: this.activeIndex,
+      themeList: [
+        {
+          type: 'white',
+          bg: '#E5E6EB',
+          playBtnBg: '#175DFF', // 播放按钮背景色
+          rightBtnColor: 'rgba(0, 0, 0, 0.96)', // 右侧按钮颜色
+          contentBg: '#F7F8FA',
+          sentenceColor: 'rgba(0, 0, 0, 0.96)',
+          sentenceActiveColor: '#175DFF',
+          bottomBg: '#F2F3F5',
+          bottomBarActiveBtnBg: '#FFFFFF',
+          bottomBarColor: '#4E5969',
+          bottomBarActive: '#165DFF',
+          bottomBarBorder: '#E5E6EB',
+          themeBg: '#FFFFFF',
+          themeActiveBorder: '#E5E6EB',
+          boxBorder: '#3459D2', // 选中时高亮的外圈边框
+        },
+        {
+          type: 'darkGreen',
+          bg: '#C2C9C6',
+          playBtnBg: '#236E55', // 播放按钮背景色
+          rightBtnColor: 'rgba(0, 0, 0, 0.96)', // 右侧按钮颜色
+          contentBg: '#DFE4E2',
+          sentenceColor: 'rgba(0, 0, 0, 0.96)',
+          sentenceActiveColor: '#236E55',
+          bottomBg: '#DFE4E2',
+          bottomBarActiveBtnBg: '#FFFFFF',
+          bottomBarColor: '#4E5969',
+          bottomBarActive: '#236E55',
+          bottomBarBorder: '#C2C9C6',
+          themeBg: '#5BB99A',
+          themeActiveBorder: '#5BB99A',
+          boxBorder: '#fff', // 选中时高亮的外圈边框
+        },
+        {
+          type: 'darkBlue',
+          bg: '#1C2129',
+          playBtnBg: '#5373E7', // 播放按钮背景色
+          rightBtnColor: '#fff', // 右侧按钮颜色
+          contentBg: '#2F3742',
+          sentenceColor: '#C1C5CD',
+          sentenceActiveColor: '#5373E7',
+          bottomBg: '#2F3742',
+          bottomBarActiveBtnBg: '#1C2129',
+          bottomBarColor: '#929CA8',
+          bottomBarActive: '#5373E7',
+          bottomBarBorder: '#1C2129',
+          themeBg: '#1F2C5C',
+          themeActiveBorder: '#1F2C5C',
+          boxBorder: '#fff', // 选中时高亮的外圈边框
+        },
+        {
+          type: 'armyGreen',
+          bg: '#2A2F2C',
+          playBtnBg: '#30A47D', // 播放按钮背景色
+          rightBtnColor: '#fff', // 右侧按钮颜色
+          contentBg: '#393F3C',
+          sentenceColor: '#C1C5CD',
+          sentenceActiveColor: '#30A47D',
+          bottomBg: '#393F3C',
+          bottomBarActiveBtnBg: '#2A2F2C',
+          bottomBarColor: '#C1C5CD',
+          bottomBarActive: '#30A47D',
+          bottomBarBorder: '#2A2F2C',
+          themeBg: '#13392E',
+          themeActiveBorder: '#13392E',
+          boxBorder: '#fff', // 选中时高亮的外圈边框
+        },
+      ],
+      currentTime: 0,
+      audio: new Audio(),
+      ed: null,
+      activeWordIndex: null,
+    };
+  },
+  //计算属性 类似于data概念
+  computed: {},
+  //监控data中数据变化
+  watch: {},
+  //方法集合
+  methods: {
+    // 播放、暂停
+    handlePlay() {
+      let _this = this;
+      _this.activeWordIndex = null;
+      _this.isPlay = !_this.isPlay;
+      if (!_this.isPlay) {
+        _this.audio.pause();
+        return;
+      }
+      _this.audio.pause();
+      _this.audio.load();
+      _this.audio.src = _this.mp3Url;
+      if (_this.currentTime <= _this.data[_this.sentenceActive].bg / 1000) {
+        _this.audio.currentTime = _this.data[_this.sentenceActive].bg / 1000;
+      } else {
+        _this.audio.currentTime = _this.currentTime;
+      }
+      _this.ed = _this.data[_this.sentenceActive].ed / 1000;
+      _this.audio.loop = false;
+      _this.audio.play();
+    },
+    palyWord(index) {
+      let _this = this;
+      _this.activeWordIndex = index;
+      _this.audio.pause();
+      _this.audio.load();
+      _this.audio.src = _this.mp3Url;
+      _this.audio.currentTime = _this.data[_this.sentenceActive].wordsResultList[index].wordBg / 1000;
+      _this.ed = _this.data[_this.sentenceActive].wordsResultList[index].wordEd / 1000;
+      _this.audio.loop = false;
+      _this.audio.play();
+    },
+    // 关闭
+    closeWord() {
+      this.$emit('closeWord');
+    },
+    handlePage(type) {
+      if (type === '-') {
+        if (this.sentenceActive > 0) {
+          this.audio.pause();
+          this.isPlay = false;
+          this.currentTime = 0;
+          this.sentenceActive--;
+        } else {
+          this.$message.warning('已经是第一句');
+        }
+      } else {
+        if (this.sentenceActive < this.data.length - 1) {
+          this.audio.pause();
+          this.isPlay = false;
+          this.currentTime = 0;
+          this.sentenceActive++;
+        } else {
+          this.$message.warning('已经是最后一句');
+        }
+      }
+    },
+    // 切换主题颜色
+    handleChangeBgColor(index, type) {
+      if (type === 'fontSize') {
+        this.$emit('changeTheme', '', index);
+      } else {
+        this.$emit('changeTheme', index);
+      }
+    },
+  },
+  //生命周期 - 创建完成(可以访问当前this实例)
+  created() {},
+  //生命周期 - 挂载完成(可以访问DOM元素)
+  mounted() {
+    let _this = this;
+    _this.audio.addEventListener('timeupdate', function () {
+      _this.currentTime = _this.audio.currentTime;
+      const currentTime = _this.audio.currentTime;
+      if (_this.ed && currentTime >= _this.ed) {
+        _this.audio.pause();
+        _this.isPlay = false;
+        _this.currentTime = 0;
+        _this.activeWordIndex = null;
+      }
+    });
+  },
+  //生命周期-创建之前
+  beforeCreated() {},
+  //生命周期-挂载之前
+  beforeMount() {},
+  //生命周期-更新之前
+  beforUpdate() {},
+  //生命周期-更新之后
+  updated() {},
+  //生命周期-销毁之前
+  beforeDestory() {},
+  //生命周期-销毁完成
+  destoryed() {},
+  //如果页面有keep-alive缓存功能,这个函数会触发
+  activated() {},
+};
+</script>
+<style lang="scss" scoped>
+/* @import url(); 引入css类 */
+.sentence-box {
+  padding: 16px;
+  border-radius: 16px;
+
+  .sentence-top {
+    display: flex;
+    justify-content: space-between;
+
+    .play-btn {
+      width: 40px;
+      height: 40px;
+      padding: 8px;
+      color: rgba(255, 255, 255, 96%);
+      background: #175dff;
+      border-radius: 20px;
+    }
+
+    .sentence-right {
+      display: flex;
+      align-items: center;
+      font-size: 12px;
+      font-weight: 700;
+      line-height: 20px;
+      color: rgba(0, 0, 0, 96%);
+
+      .btn {
+        width: 40px;
+        height: 40px;
+        padding: 8px;
+      }
+
+      span {
+        min-width: 40px;
+        text-align: center;
+      }
+
+      .el-icon-close {
+        font-size: 16px;
+        font-weight: 700;
+        cursor: pointer;
+      }
+    }
+
+    .svg-icon {
+      font-size: 24px;
+      cursor: pointer;
+    }
+  }
+
+  .content-inner {
+    display: flex;
+    flex-flow: wrap;
+    padding: 40px 45px;
+    margin: 16px 0;
+    border-radius: 8px;
+
+    .content-item {
+      // margin: 0 6px 0 0;
+      font-family: '楷体';
+      font-size: 20px;
+      line-height: 40px;
+      cursor: pointer;
+
+      // &.active {
+      //   font-weight: 700;
+      // }
+    }
+  }
+
+  .sentence-bottom {
+    display: flex;
+
+    .fontsize-box {
+      display: flex;
+      align-items: center;
+      padding: 3px;
+      border-radius: 20px;
+
+      span {
+        width: 38px;
+        padding: 2px 12px;
+        font-size: 14px;
+        font-weight: 400;
+        line-height: 22px;
+        cursor: pointer;
+        border-radius: 20px;
+
+        &.active {
+          font-weight: 500;
+        }
+      }
+
+      .border {
+        width: 1px;
+        height: 14px;
+      }
+    }
+
+    .article-color {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      width: 132px;
+      height: 32px;
+      padding: 4px;
+      margin-left: 12px;
+      border-radius: 40px;
+
+      .color-item {
+        padding: 2px;
+        border: 2px solid transparent;
+        border-radius: 50%;
+
+        a {
+          display: block;
+          width: 16px;
+          height: 16px;
+          padding: 0;
+          border: 1px solid transparent;
+          border-radius: 50%;
+        }
+      }
+    }
+  }
+}
+</style>