|
@@ -0,0 +1,400 @@
|
|
|
+<template>
|
|
|
+ <ModuleBase :type="data.type">
|
|
|
+ <template #content>
|
|
|
+ <UploadFile
|
|
|
+ key="upload_image"
|
|
|
+ :courseware-id="courseware_id"
|
|
|
+ :component-id="id"
|
|
|
+ :type="data.type"
|
|
|
+ :total-size="data.total_size"
|
|
|
+ :file-list="data.image_list"
|
|
|
+ :file-id-list="data.image_id_list"
|
|
|
+ :file-info-list="data.image_info_list"
|
|
|
+ :label-text="labelText"
|
|
|
+ :accept-file-type="acceptFileType"
|
|
|
+ :icon-class="iconClass"
|
|
|
+ :limit="1"
|
|
|
+ :singleSize="500"
|
|
|
+ @updateFileList="updateFileList"
|
|
|
+ />
|
|
|
+ <div class="image-size">
|
|
|
+ <span>图片大小</span
|
|
|
+ ><el-input-number v-model="data.image_width" :step="100" :min="200" :max="800"></el-input-number
|
|
|
+ ><el-input-number v-model="data.image_height" :step="100" :min="200" :max="800"></el-input-number>
|
|
|
+ </div>
|
|
|
+ <SelectUpload label="音频" type="audio" width="500px" @uploadSuccess="uploadAudioSuccess" />
|
|
|
+ <div v-if="data.mp3_list.length > 0" class="upload-file">
|
|
|
+ <div class="file-name">
|
|
|
+ <span>
|
|
|
+ <SvgIcon icon-class="mp3" size="12" />
|
|
|
+ <span>{{ data.mp3_list[0].name }}</span>
|
|
|
+ </span>
|
|
|
+ <span> 完成 </span>
|
|
|
+ </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"
|
|
|
+ @mousedown="startSelection"
|
|
|
+ @mousemove="updateSelection"
|
|
|
+ @mouseup="endSelection"
|
|
|
+ @mouseleave="endSelection"
|
|
|
+ :style="{
|
|
|
+ width: data.image_width + 'px',
|
|
|
+ height: data.image_height + 'px',
|
|
|
+ background: 'url(' + data.image_list[0].url + ') center / contain no-repeat',
|
|
|
+ position: 'relative',
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ v-if="isSelecting"
|
|
|
+ :style="{
|
|
|
+ position: 'absolute',
|
|
|
+ top: `${startY}px`,
|
|
|
+ left: `${startX}px`,
|
|
|
+ width: `${endX - startX}px`,
|
|
|
+ height: `${endY - startY}px`,
|
|
|
+ border: isText ? '2px solid #165DFF' : '2px solid #f90',
|
|
|
+ }"
|
|
|
+ ></div>
|
|
|
+ <template v-for="(itemh, indexh) in data.text_list">
|
|
|
+ <div
|
|
|
+ v-if="indexh === hotspotsActiveIndex"
|
|
|
+ :key="indexh"
|
|
|
+ :style="{
|
|
|
+ position: 'absolute',
|
|
|
+ top: `${itemh.y}`,
|
|
|
+ left: `${itemh.x}`,
|
|
|
+ width: `${itemh.width}`,
|
|
|
+ height: `${itemh.height}`,
|
|
|
+ border: '2px solid #165DFF',
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <label
|
|
|
+ :style="{
|
|
|
+ position: 'absolute',
|
|
|
+ top: '-13px',
|
|
|
+ right: '-13px',
|
|
|
+ width: '26px',
|
|
|
+ height: '26px',
|
|
|
+ border: '2px solid #165DFF',
|
|
|
+ textAlign: 'center',
|
|
|
+ borderRadius: '50%',
|
|
|
+ boxShadow: ' 0px 5px 5px -3px #0000001A',
|
|
|
+ background: '#fff',
|
|
|
+ color: '#165DFF',
|
|
|
+ }"
|
|
|
+ >{{ indexh + 1 }}</label
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <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
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </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>
|
|
|
+
|
|
|
+<script>
|
|
|
+import ModuleMixin from '../../common/ModuleMixin';
|
|
|
+import UploadFile from '../../base/common/UploadFile.vue';
|
|
|
+import { getImageTextData } from '@/views/book/courseware/data/imageText';
|
|
|
+import SelectUpload from '@/views/book/courseware/create/components/common/SelectUpload.vue';
|
|
|
+import { GetFileURLMap } from '@/api/app';
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'ImageTextPage',
|
|
|
+ components: { UploadFile, SelectUpload },
|
|
|
+ mixins: [ModuleMixin],
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ data: getImageTextData(),
|
|
|
+ labelText: '背景图',
|
|
|
+ acceptFileType: '.jpg,.png,.jpeg',
|
|
|
+ iconClass: 'picture',
|
|
|
+ isSelecting: false,
|
|
|
+ startX: 0,
|
|
|
+ startY: 0,
|
|
|
+ endX: 0,
|
|
|
+ endY: 0,
|
|
|
+ hotspotsActiveIndex: null, // 当前编辑文本框热区索引
|
|
|
+ genloading: false, // 字幕节点loading
|
|
|
+ isText: true, // 框选是文本还是输入框
|
|
|
+ inputActiveIndex: null, // 当前编辑输入框热区索引
|
|
|
+ };
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ 'data.image_list': {
|
|
|
+ handler(val) {
|
|
|
+ if (val.length > 0) {
|
|
|
+ this.handleData();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ immediate: true,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ 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;
|
|
|
+ },
|
|
|
+ uploadAudioSuccess(fileList) {
|
|
|
+ if (fileList.length > 0) {
|
|
|
+ const { file_name: name, file_url: temporary_url, file_id, media_duration } = fileList[0];
|
|
|
+ this.data.mp3_list = [
|
|
|
+ {
|
|
|
+ name,
|
|
|
+ media_duration,
|
|
|
+ temporary_url,
|
|
|
+ url: file_id,
|
|
|
+ file_id,
|
|
|
+ },
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ },
|
|
|
+ removeFile() {
|
|
|
+ this.data.mp3_list = [];
|
|
|
+ },
|
|
|
+ startSelection(event) {
|
|
|
+ this.isSelecting = true;
|
|
|
+ let clientRect = document.getElementById('selectableArea').getBoundingClientRect();
|
|
|
+
|
|
|
+ this.startX = event.clientX - clientRect.left;
|
|
|
+ this.startY = event.clientY - clientRect.top;
|
|
|
+ },
|
|
|
+ updateSelection(event) {
|
|
|
+ if (!this.isSelecting) return;
|
|
|
+ let clientRect = document.getElementById('selectableArea').getBoundingClientRect();
|
|
|
+
|
|
|
+ this.endX = event.clientX - clientRect.left;
|
|
|
+ this.endY = event.clientY - clientRect.top;
|
|
|
+ },
|
|
|
+ endSelection() {
|
|
|
+ this.isSelecting = false;
|
|
|
+ const width = Math.abs(this.endX - this.startX);
|
|
|
+ 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';
|
|
|
+ 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() {
|
|
|
+ this.data.image_list.forEach((item) => {
|
|
|
+ GetFileURLMap({ file_id_list: [item.file_id] }).then(({ url_map }) => {
|
|
|
+ this.$set(item, 'url', url_map[item.file_id]);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ },
|
|
|
+ // 删除文本框热区
|
|
|
+ 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>
|
|
|
+<style lang="scss" scoped>
|
|
|
+.upload-file {
|
|
|
+ display: flex;
|
|
|
+ column-gap: 12px;
|
|
|
+ align-items: center;
|
|
|
+ margin: 8px 0;
|
|
|
+
|
|
|
+ .file-name {
|
|
|
+ display: flex;
|
|
|
+ column-gap: 14px;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ max-width: 360px;
|
|
|
+ padding: 8px 12px;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #1d2129;
|
|
|
+ background-color: #f7f8fa;
|
|
|
+
|
|
|
+ span {
|
|
|
+ display: flex;
|
|
|
+ column-gap: 14px;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .svg-icon {
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.image-size {
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+ align-items: center;
|
|
|
+ padding: 20px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.hotspots-list {
|
|
|
+ padding: 0;
|
|
|
+ margin: 0;
|
|
|
+ list-style: none;
|
|
|
+
|
|
|
+ li {
|
|
|
+ display: flex;
|
|
|
+ column-gap: 8px;
|
|
|
+ width: 100%;
|
|
|
+ padding: 16px;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ border: 1px solid rgba(0, 0, 0, 8%);
|
|
|
+ }
|
|
|
+
|
|
|
+ span {
|
|
|
+ display: block;
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ line-height: 24px;
|
|
|
+ color: #fff;
|
|
|
+ text-align: center;
|
|
|
+ background: #004eff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-button--danger,
|
|
|
+ .el-button--primary {
|
|
|
+ height: 32px;
|
|
|
+ margin-left: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .content {
|
|
|
+ flex: 1;
|
|
|
+
|
|
|
+ .el-textarea {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ p {
|
|
|
+ margin: 0;
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 400;
|
|
|
+ line-height: 24px;
|
|
|
+ color: #000;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|