Bläddra i källkod

文本图片融合预览

natasha 1 månad sedan
förälder
incheckning
8c11fde08e

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

@@ -33,6 +33,10 @@
         </div>
         <SvgIcon icon-class="delete-black" size="12" @click="removeFile" />
       </div>
+      <el-radio-group v-model="isText" style="width: 100%; margin: 20px 0; text-align: center">
+        <el-radio-button :label="true">文字框</el-radio-button>
+        <el-radio-button :label="false">输入框</el-radio-button>
+      </el-radio-group>
       <div
         v-if="data.image_list[0]"
         id="selectableArea"
@@ -55,7 +59,7 @@
             left: `${startX}px`,
             width: `${endX - startX}px`,
             height: `${endY - startY}px`,
-            border: '2px solid #165DFF',
+            border: isText ? '2px solid #165DFF' : '2px solid #f90',
           }"
         ></div>
         <template v-for="(itemh, indexh) in data.text_list">
@@ -89,34 +93,94 @@
             >
           </div>
         </template>
-      </div>
-      <ul class="hotspots-list" v-if="data.image_list[0] && data.text_list">
-        <li v-for="(itemh, indexh) in data.text_list" :key="indexh">
-          <span>{{ indexh + 1 }}</span>
-          <div class="content">
-            <el-input
-              v-if="indexh === hotspotsActiveIndex"
-              type="textarea"
-              :rows="2"
-              placeholder="请输入"
-              v-model="itemh.text"
-              maxlength="500"
-              show-word-limit
+        <template v-for="(itemh, indexhs) in data.input_list">
+          <div
+            v-if="indexhs === inputActiveIndex"
+            :key="indexhs"
+            :style="{
+              position: 'absolute',
+              top: `${itemh.y}`,
+              left: `${itemh.x}`,
+              width: `${itemh.width}`,
+              height: `${itemh.height}`,
+              border: '2px solid #f90',
+            }"
+          >
+            <label
+              :style="{
+                position: 'absolute',
+                top: '-13px',
+                right: '-13px',
+                width: '26px',
+                height: '26px',
+                border: '2px solid #f90',
+                textAlign: 'center',
+                borderRadius: '50%',
+                boxShadow: ' 0px 5px 5px -3px #0000001A',
+                background: '#fff',
+                color: '#f90',
+              }"
+              >{{ indexhs + 1 }}</label
             >
-            </el-input>
-            <p v-else>{{ itemh.text }}</p>
           </div>
-          <el-button
-            v-if="hotspotsActiveIndex !== indexh"
-            type="primary"
-            size="small"
-            @click="hotspotsActiveIndex = indexh"
-            >编辑</el-button
-          >
-          <el-button v-else type="primary" size="small" @click="hotspotsActiveIndex = null">保存</el-button>
-          <el-button type="danger" size="small" @click="deletehotspots(indexh)">删除</el-button>
-        </li>
-      </ul>
+        </template>
+      </div>
+      <template v-if="data.image_list[0] && data.text_list.length > 0">
+        <h4>文本框内容</h4>
+        <ul class="hotspots-list">
+          <li v-for="(itemh, indexh) in data.text_list" :key="indexh">
+            <span>{{ indexh + 1 }}</span>
+            <div class="content">
+              <el-input
+                v-if="indexh === hotspotsActiveIndex"
+                type="textarea"
+                :rows="2"
+                placeholder="请输入"
+                v-model="itemh.text"
+                maxlength="500"
+                show-word-limit
+              >
+              </el-input>
+              <p v-else>{{ itemh.text }}</p>
+            </div>
+            <el-button
+              v-if="hotspotsActiveIndex !== indexh"
+              type="primary"
+              size="small"
+              @click="hotspotsActiveIndex = indexh"
+              >编辑</el-button
+            >
+            <el-button v-else type="primary" size="small" @click="hotspotsActiveIndex = null">保存</el-button>
+            <el-button type="danger" size="small" @click="deletehotspots(indexh)">删除</el-button>
+          </li>
+        </ul>
+      </template>
+      <template v-if="data.image_list[0] && data.input_list.length > 0">
+        <h4>输入框答案</h4>
+        <ul class="hotspots-list">
+          <li v-for="(itemh, indexh) in data.input_list" :key="indexh">
+            <span>{{ indexh + 1 }}</span>
+            <div class="content">
+              <el-input
+                v-if="indexh === inputActiveIndex"
+                type="textarea"
+                :rows="2"
+                placeholder="请输入"
+                v-model="itemh.text"
+                maxlength="500"
+                show-word-limit
+              >
+              </el-input>
+              <p v-else>{{ itemh.text }}</p>
+            </div>
+            <el-button v-if="inputActiveIndex !== indexh" type="primary" size="small" @click="inputActiveIndex = indexh"
+              >编辑</el-button
+            >
+            <el-button v-else type="primary" size="small" @click="inputActiveIndex = null">保存</el-button>
+            <el-button type="danger" size="small" @click="deletehotspotsInput(indexh)">删除</el-button>
+          </li>
+        </ul>
+      </template>
     </template>
   </ModuleBase>
 </template>
@@ -143,8 +207,10 @@ export default {
       startY: 0,
       endX: 0,
       endY: 0,
-      hotspotsActiveIndex: null, // 当前编辑热区索引
+      hotspotsActiveIndex: null, // 当前编辑文本框热区索引
       genloading: false, // 字幕节点loading
+      isText: true, // 框选是文本还是输入框
+      inputActiveIndex: null, // 当前编辑输入框热区索引
     };
   },
   watch: {
@@ -200,20 +266,26 @@ export default {
       const height = Math.abs(this.endY - this.startY);
       const x = this.endX > this.startX ? this.startX + 'px' : this.endX + 'px';
       const y = this.endY > this.startY ? this.startY + 'px' : this.endY + 'px';
-      if (width && height) {
-        this.data.text_list.push({
-          id: Math.random().toString(36).substring(2, 10),
-          width: width + 'px',
-          height: height + 'px',
-          x: x,
-          y: y,
-          text: '',
-        });
-        this.startX = 0;
-        this.endX = 0;
-        this.startY = 0;
-        this.endY = 0;
+      let obj = {
+        id: Math.random().toString(36).substring(2, 10),
+        width: width + 'px',
+        height: height + 'px',
+        x: x,
+        y: y,
+        text: '',
+      };
+      this.startX = 0;
+      this.endX = 0;
+      this.startY = 0;
+      this.endY = 0;
+      if (width && height && this.isText) {
+        this.data.text_list.push(obj);
+
         this.hotspotsActiveIndex = this.data.text_list.length - 1;
+      } else if (width && height && !this.isText) {
+        this.data.input_list.push(obj);
+
+        this.inputActiveIndex = this.data.input_list.length - 1;
       }
     },
     handleData() {
@@ -223,13 +295,20 @@ export default {
         });
       });
     },
-    // 删除热区
+    // 删除文本框热区
     deletehotspots(index) {
       this.data.text_list.splice(index, 1);
       if (this.hotspotsActiveIndex === index) {
         this.hotspotsActiveIndex = null;
       }
     },
+    // 删除输入框热区
+    deletehotspotsInput(index) {
+      this.data.input_list.splice(index, 1);
+      if (this.inputActiveIndex === index) {
+        this.inputActiveIndex = null;
+      }
+    },
   },
 };
 </script>

+ 1 - 1
src/views/book/courseware/data/bookType.js

@@ -88,7 +88,7 @@ import InputPreview from '../preview/components/input/InputPreview.vue';
 
 import JudgePreview from '../preview/components/judge/JudgePreview.vue';
 import WriteBasePreview from '../preview/components/write_base/WriteBasePreview.vue';
-import ImageTextPreview from '../preview/components/image_text/ImageText.vue';
+import ImageTextPreview from '../preview/components/image_text/ImageTextPreview.vue';
 
 export const bookTypeOption = [
   {

+ 0 - 23
src/views/book/courseware/preview/components/image_text/ImageText.vue

@@ -1,23 +0,0 @@
-<!-- eslint-disable vue/no-v-html -->
-<template>
-  <div class="newWord-preview" :style="getAreaStyle()"></div>
-</template>
-
-<script>
-import PreviewMixin from '../common/PreviewMixin';
-export default {
-  name: 'ImageTextPreview',
-
-  components: {},
-  mixins: [PreviewMixin],
-  data() {
-    return {};
-  },
-  watch: {},
-  methods: {},
-};
-</script>
-
-<style lang="scss" scoped>
-@use '@/styles/mixin.scss' as *;
-</style>

+ 181 - 0
src/views/book/courseware/preview/components/image_text/ImageTextPreview.vue

@@ -0,0 +1,181 @@
+<!-- eslint-disable vue/no-v-html -->
+<template>
+  <div class="imageText-preview" :style="getAreaStyle()">
+    <template v-if="data.mp3_list && data.mp3_list.length > 0 && mp3_url">
+      <AudioLine
+        audioId="Audio"
+        :mp3="mp3_url"
+        :getCurTime="getCurTime"
+        :duration="data.mp3_list[0].media_duration"
+        :mp3Source="data.mp3_list[0].source"
+        :width="audio_width"
+        :ed="ed"
+        type="audioLine"
+        ref="audioLine"
+        @emptyEd="emptyEd"
+      />
+    </template>
+    <div
+      class="img-box"
+      :style="{
+        background: 'url(' + image_url + ') center / contain no-repeat',
+        width: data.image_width + 'px',
+        height: data.image_height + 'px',
+      }"
+      v-if="image_url"
+    >
+      <div
+        v-for="(itemP, indexP) in data.text_list"
+        :key="indexP"
+        :class="['position-item', mageazineDetailIndex === indexP ? 'active' : '']"
+        :style="{
+          width: itemP.width,
+          height: itemP.height,
+          left: itemP.x,
+          top: itemP.y,
+        }"
+        @click="handleChangePosition(indexP)"
+      ></div>
+      <div
+        v-for="(itemP, indexP) in data.input_list"
+        :key="indexP"
+        :class="['position-item position-item-input', 'active']"
+        :style="{
+          width: itemP.width,
+          height: itemP.height,
+          left: itemP.x,
+          top: itemP.y,
+        }"
+      >
+        <el-input
+          type="textarea"
+          v-model="answer.answer_list[indexP].text"
+          style="height: 100%"
+          placeholder="请输入"
+        ></el-input>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+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';
+export default {
+  name: 'ImageTextPreview',
+
+  components: { AudioLine },
+  mixins: [PreviewMixin],
+  data() {
+    return {
+      data: getImageTextData(),
+      curTime: 0,
+      paraIndex: -1, //段落索引
+      sentIndex: -1, // 句子索引
+      ed: undefined,
+      mp3_url: '',
+      image_url: '',
+      audio_width: 0,
+      mageazineDetailIndex: null, // 当前高亮第几个
+      mageazineDetailShow: false,
+      inputIndex: null,
+    };
+  },
+  methods: {
+    initData() {
+      if (!this.isJudgingRightWrong) {
+        this.answer.answer_list = [];
+        this.data.input_list.forEach((item, index) => {
+          let obj = {
+            text: '',
+            answer: item.text,
+            id: item.id,
+          };
+          this.answer.answer_list.push(obj);
+        });
+      }
+
+      this.data.image_list.forEach((item) => {
+        GetFileURLMap({ file_id_list: [item.file_id] }).then(({ url_map }) => {
+          this.image_url = url_map[item.file_id];
+        });
+      });
+      this.data.mp3_list.forEach((item) => {
+        GetFileURLMap({ file_id_list: [item.file_id] }).then(({ url_map }) => {
+          this.mp3_url = url_map[item.file_id];
+        });
+      });
+    },
+    getCurTime(curTime) {
+      this.curTime = curTime * 1000;
+      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;
+      //   }
+      // }
+    },
+    emptyEd() {
+      this.ed = undefined;
+    },
+    // 切换画刊里面的卡片
+    handleChangePosition(index) {
+      if (this.$refs.audioLine.audio.playing) {
+        this.$refs.audioLine.PlayAudio();
+      }
+      this.mageazineDetailIndex = index;
+      this.mageazineDetailShow = true;
+    },
+  },
+  created() {
+    this.initData();
+  },
+  mounted() {
+    this.audio_width = document.getElementsByClassName('imageText-preview')[0].clientWidth - 150;
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+@use '@/styles/mixin.scss' as *;
+
+.position-item {
+  position: absolute;
+  z-index: 1;
+  cursor: pointer;
+  border: 3px solid transparent;
+
+  &.active {
+    border-color: #ff1616;
+  }
+
+  &:hover {
+    border-color: #ff1616;
+  }
+
+  &.position-item-input {
+    border-color: #f90;
+  }
+
+  :deep .el-textarea__inner {
+    height: 100%;
+    font-family: 'League', '楷体';
+    text-align: center;
+    resize: none;
+    background: transparent;
+    border: none;
+  }
+}
+
+.img-box {
+  position: relative;
+  margin: 20px auto;
+}
+</style>

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

@@ -445,7 +445,7 @@ export default {
     top: 12px;
     width: 8px;
     height: 8px;
-    background: var(--slider-color);
+    background: #165dff;
     border: none;
   }
 
@@ -463,7 +463,7 @@ export default {
 
   .el-slider__bar {
     height: 2px;
-    background: var(--slider-color);
+    background: #165dff;
   }
 }
 </style>