瀏覽代碼

图片文本融合

natasha 1 月之前
父節點
當前提交
a23b56ee84

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

@@ -0,0 +1,348 @@
+<template>
+  <ModuleBase :type="data.type">
+    <template #content>
+      <div style="text-align: left">
+        <label>标题:</label>
+        <RichText
+          v-model="data.title_con"
+          :inline="true"
+          :placeholder="'输入标题'"
+          toolbar="fontselect fontsizeselect forecolor backcolor | underline | bold italic strikethrough alignleft aligncenter alignright"
+        />
+      </div>
+      <el-table :data="data.option" border style="width: 100%">
+        <el-table-column fixed prop="number" label="序号" width="70">
+          <template slot-scope="scope">
+            <el-input v-model="scope.row.number"></el-input>
+          </template>
+        </el-table-column>
+        <el-table-column fixed prop="new_word" label="生词/短语" width="110">
+          <template slot-scope="scope">
+            <el-input v-model="scope.row.new_word" @blur="handleBlurCon(scope.row)"></el-input>
+          </template>
+        </el-table-column>
+        <el-table-column prop="mp3_list" label="读音" width="200">
+          <template slot-scope="scope">
+            <div v-if="scope.row.mp3_list">
+              <SoundRecord :wav-blob.sync="scope.row.mp3_list" />
+            </div>
+            <template v-else>
+              <div :class="['upload-audio-play']">
+                <UploadAudio
+                  v-if="data.property.audio_generation_method === 'upload'"
+                  :file-id="scope.row.mp3_list"
+                  :item-index="scope.$index"
+                  :show-upload="!scope.row.mp3_list"
+                  @upload="uploads"
+                  @deleteFile="deleteFiles"
+                />
+                <div
+                  v-else-if="data.property.audio_generation_method === 'auto'"
+                  class="auto-matic"
+                  @click="handleMatic(scope.$index)"
+                >
+                  <SvgIcon icon-class="voiceprint-line" class="record" />
+                  <span class="auto-btn">{{ scope.row.mp3_list ? '已生成' : '生成音频' }}</span
+                  >{{ scope.row.mp3_list ? '成功' : '' }}
+                </div>
+                <SoundRecord v-else :wav-blob.sync="scope.row.mp3_list" />
+              </div>
+            </template>
+          </template>
+        </el-table-column>
+        <el-table-column prop="pinyin" label="拼音" width="110">
+          <template slot-scope="scope">
+            <el-input v-model="scope.row.pinyin"></el-input>
+          </template>
+        </el-table-column>
+        <el-table-column prop="cixing" label="词性" width="110">
+          <template slot-scope="scope">
+            <RichText
+              v-model="scope.row.cixing"
+              :inline="true"
+              toolbar="fontselect fontsizeselect forecolor backcolor | underline | bold italic strikethrough alignleft aligncenter alignright"
+            />
+          </template>
+        </el-table-column>
+        <el-table-column prop="definition_list" label="释义" width="200">
+          <template slot-scope="scope">
+            <RichText
+              v-model="scope.row.definition_list"
+              :inline="true"
+              :placeholder="'多个释义用;隔开'"
+              toolbar="fontselect fontsizeselect forecolor backcolor | underline | bold italic strikethrough alignleft aligncenter alignright"
+            />
+          </template>
+        </el-table-column>
+        <el-table-column prop="collocation" label="搭配" width="200">
+          <template slot-scope="scope">
+            <RichText
+              v-model="scope.row.collocation"
+              :inline="true"
+              toolbar="fontselect fontsizeselect forecolor backcolor | underline | bold italic strikethrough alignleft aligncenter alignright"
+            />
+          </template>
+        </el-table-column>
+        <el-table-column prop="liju_list" label="例句" width="300">
+          <template slot-scope="scope">
+            <RichText
+              v-model="scope.row.liju_list"
+              :inline="true"
+              :placeholder="'多条例句用回车'"
+              toolbar="fontselect fontsizeselect forecolor backcolor | underline | bold italic strikethrough alignleft aligncenter alignright"
+            />
+          </template>
+        </el-table-column>
+        <el-table-column prop="header_con" label="页眉" width="100">
+          <template slot-scope="scope">
+            <el-input v-model="scope.row.header_con"></el-input>
+          </template>
+        </el-table-column>
+        <el-table-column prop="label" label="标签" width="100">
+          <template slot-scope="scope">
+            <el-input v-model="scope.row.label"></el-input>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="150">
+          <template slot-scope="scope">
+            <el-button size="mini" type="text" @click="handleDelete(scope.$index)">删除</el-button>
+            <el-button size="mini" type="text" @click="moveElement(scope.row, scope.$index, 'up')">上移</el-button>
+            <el-button size="mini" type="text" @click="moveElement(scope.row, scope.$index, 'down')">下移</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <el-button icon="el-icon-plus" style="margin: 24px 0" @click="addElement">增加一个</el-button>
+      <SelectUpload label="生词音频" type="audio" width="500px" @uploadSuccess="uploadAudioSuccess" />
+      <div v-if="data.audio_data.url.length > 0" class="upload-file">
+        <div class="file-name">
+          <span>
+            <SvgIcon icon-class="note" size="12" />
+            <span>{{ data.audio_data.name }}</span>
+          </span>
+        </div>
+        <SvgIcon icon-class="delete-black" size="12" @click="removeFile('audio')" />
+      </div>
+      <SelectUpload label="lrc 文件" :limit="1" type="lrc" width="500px" @uploadSuccess="uploadLrcSuccess" />
+      <div v-if="data.lrc_data.url.length > 0" class="upload-file">
+        <div class="file-name">
+          <span>
+            <SvgIcon icon-class="note" size="12" />
+            <span>{{ data.lrc_data.name }}</span>
+          </span>
+        </div>
+        <SvgIcon icon-class="delete-black" size="12" @click="removeFile('lrc')" />
+      </div>
+    </template>
+  </ModuleBase>
+</template>
+
+<script>
+import ModuleMixin from '../../common/ModuleMixin';
+import SoundRecord from '@/views/book/courseware/create/components/question/fill/components/SoundRecord.vue';
+import UploadAudio from '@/views/book/courseware/create/components/question/fill/components/UploadAudio.vue';
+
+import { getNewWordData, getOption } from '@/views/book/courseware/data/newWord';
+import SelectUpload from '@/views/book/courseware/create/components/common/SelectUpload.vue';
+import { GetStaticResources } from '@/api/app';
+import cnchar from 'cnchar';
+
+export default {
+  name: 'NewWordPage',
+  components: {
+    SelectUpload,
+    SoundRecord,
+    UploadAudio,
+  },
+  mixins: [ModuleMixin],
+  data() {
+    return {
+      data: getNewWordData(),
+    };
+  },
+  methods: {
+    /**
+     * 解析lrc文件
+     */
+    parseLrcFile() {
+      if (this.data.lrc_data.file_id.length === 0) {
+        return this.$message.warning('请先上传lrc文件');
+      }
+      const loading = this.$loading({ text: '解析lrc文件中' });
+      GetStaticResources('tool-ParseLRCFile', {
+        content_type: 'FILE',
+        file_id: this.data.lrc_data.file_id,
+      }).then(({ lrc_list }) => {
+        let lrc_list_res = [];
+        lrc_list.forEach((item) => {
+          let obj = {
+            bg: item.begin_time,
+            ed: item.end_time,
+          };
+          lrc_list_res.push(obj);
+        });
+        this.data.lrc_arr = lrc_list_res;
+        loading.close();
+      });
+    },
+    uploadLrcSuccess(fileList) {
+      if (fileList.length > 0) {
+        const { file_name: name, file_url: url, file_id } = fileList[0];
+        this.data.lrc_data = {
+          name,
+          url,
+          id: file_id,
+          file_id,
+        };
+        this.parseLrcFile();
+      }
+    },
+    uploadAudioSuccess(fileList) {
+      if (fileList.length > 0) {
+        const { file_name: name, file_url: temporary_url, file_id, media_duration } = fileList[0];
+        this.data.audio_data = {
+          name,
+          media_duration,
+          temporary_url,
+          url: file_id,
+          file_id,
+        };
+      }
+    },
+    /**
+     * 删除文件
+     * @param {'audio' | 'lrc'} type
+     */
+    removeFile(type) {
+      if (type === 'audio') {
+        this.data.audio_data = {
+          name: '',
+          media_duration: 0,
+          temporary_url: '',
+          url: '',
+          file_id: '',
+        };
+      } else if (type === 'lrc') {
+        this.data.lrc_data = {
+          name: '',
+          url: '',
+          id: '',
+          file_id: '',
+        };
+      }
+      this.data.lrc_arr = [];
+    },
+    uploads(file_id, index) {
+      this.data.option[index].mp3_list = file_id;
+    },
+    deleteFiles(file_id, index) {
+      this.data.option[index].mp3_list = '';
+    },
+    uploadPic(file_id, index) {
+      this.data.option[index].file_list[0] = file_id;
+    },
+    deletePic(file_id, index) {
+      this.data.option[index].file_list[0] = '';
+    },
+    // 自动生成音频
+    handleMatic(index) {
+      GetStaticResources('tool-TextToVoiceFile', {
+        text: this.data.option[index].new_word.replace(/<[^>]+>/g, ''),
+      })
+        .then(({ status, file_id }) => {
+          if (status === 1) {
+            this.data.option[index].mp3_list = file_id;
+          }
+        })
+        .catch(() => {});
+    },
+    // 删除行
+    handleDelete(index) {
+      this.data.option.splice(index, 1);
+    },
+    // 上移下移
+    moveElement(dItem, index, type) {
+      let obj = JSON.parse(JSON.stringify(dItem));
+      if (type == 'up' && index > 0) {
+        this.data.option.splice(index - 1, 0, obj);
+        this.data.option.splice(index + 1, 1);
+      }
+      if (type == 'down' && index < this.data.option.length - 1) {
+        this.data.option[index] = this.data.option.splice(index + 1, 1, this.data.option[index])[0];
+      }
+    },
+    // 增加
+    addElement() {
+      this.data.option.push(getOption());
+    },
+    // 获取数据
+    handleBlurCon(row) {
+      let cons = row.new_word.trim();
+      let MethodName = 'hz_resource_manager-GetMultHZStrokesContent';
+      let data = {
+        hz_str: cons,
+      };
+      GetStaticResources(MethodName, data)
+        .then((res) => {
+          for (let key in res) {
+            if (key != 'status' && key != ',' && res[key]) {
+              res[key] = JSON.parse(res[key]);
+            }
+          }
+          let hzDetailList = res;
+          let hz_list = [];
+          cons.split('').forEach((items) => {
+            let res = JSON.parse(JSON.stringify(hzDetailList[items]));
+            let obj = {
+              con: items,
+              hzDetail: {
+                hz_json: res,
+              },
+            };
+            hz_list.push(obj);
+          });
+          row.hz_info = hz_list;
+        })
+        .catch(() => {
+          this.loading = false;
+        });
+
+      row.pinyin = cnchar.spell(cons, 'array', 'low', 'tone').join(' ');
+    },
+  },
+};
+</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;
+  }
+}
+</style>
+<style lang="scss">
+.tox .tox-editor-header {
+  z-index: 3;
+}
+</style>

+ 70 - 0
src/views/book/courseware/create/components/question/image_text/ImageTextSetting.vue

@@ -0,0 +1,70 @@
+<template>
+  <div>
+    <el-form :model="property" label-width="72px" label-position="left">
+      <SerailNumber :property="property" />
+      <el-form-item label="详细信息">
+        <el-radio-group v-model="property.is_has_infor">
+          <el-radio v-for="{ value, label } in inforList" :key="value" :label="value">
+            {{ label }}
+          </el-radio>
+        </el-radio-group>
+      </el-form-item>
+
+      <el-form-item label="预览展开">
+        <el-radio-group v-model="property.is_word_show">
+          <el-radio v-for="{ value, label } in wordShowList" :key="value" :label="value">
+            {{ label }}
+          </el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="读音">
+        <el-select v-model="property.audio_generation_method" placeholder="请选择">
+          <el-option v-for="{ value, label } in audioGenerationMethodList" :key="value" :label="label" :value="value" />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="拼音位置">
+        <el-radio-group v-model="property.pinyin_position">
+          <el-radio v-for="{ value, label } in pinyinPositionList" :key="value" :label="value">
+            {{ label }}
+          </el-radio>
+        </el-radio-group>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script>
+import SettingMixin from '@/views/book/courseware/create/components/common/SettingMixin';
+
+import {
+  getNewWordProperty,
+  audioGenerationMethodList,
+  pinyinPositionList,
+  wordShowList,
+  inforList,
+} from '@/views/book/courseware/data/newWord';
+
+export default {
+  name: 'NewWordSetting',
+  mixins: [SettingMixin],
+  data() {
+    return {
+      property: getNewWordProperty(),
+      audioGenerationMethodList,
+      pinyinPositionList,
+      wordShowList,
+      inforList,
+    };
+  },
+  methods: {},
+};
+</script>
+
+<style lang="scss" scoped>
+@use '@/styles/mixin.scss' as *;
+
+.el-form {
+  @include setting-base;
+}
+</style>

+ 11 - 0
src/views/book/courseware/data/bookType.js

@@ -55,6 +55,8 @@ import Judge from '../create/components/question/judge/Judge.vue';
 import JudgeSetting from '../create/components/question/judge/JudgeSetting.vue';
 import WriteBase from '../create/components/base/write_base/WriteBase.vue';
 import WriteBaseSetting from '../create/components/base/write_base/WriteBaseSetting.vue';
+import ImageText from '../create/components/question/image_text/ImageText.vue';
+import ImageTextSetting from '../create/components/question/image_text/ImageTextSetting.vue';
 
 // 预览组件页面列表
 import AudioPreview from '@/views/book/courseware/preview/components/audio/AudioPreview.vue';
@@ -86,6 +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';
 
 export const bookTypeOption = [
   {
@@ -323,6 +326,14 @@ export const bookTypeOption = [
         set: JudgeSetting,
         preview: JudgePreview,
       },
+      {
+        value: 'image_text',
+        label: '图片文本融合',
+        icon: '',
+        component: ImageText,
+        set: ImageTextSetting,
+        preview: ImageTextPreview,
+      },
     ],
   },
 ];

+ 68 - 0
src/views/book/courseware/data/imageText.js

@@ -0,0 +1,68 @@
+import {
+  displayList,
+  serialNumberTypeList,
+  serialNumberPositionList,
+  arrangeTypeList,
+  switchOption,
+  isEnable,
+} from '@/views/book/courseware/data/common';
+
+export { arrangeTypeList, switchOption, isEnable };
+
+
+
+// 显示
+export const showList = [
+  {
+    value: 'true',
+    label: '显示',
+  },
+  {
+    value: 'false',
+    label: '不显示',
+  },
+];
+
+// 汉字框
+export const frameList = [
+  {
+    value: 'tian',
+    label: '田字格',
+  },
+  {
+    value: 'fang',
+    label: '方框',
+  },
+  {
+    value: 'none',
+    label: '无',
+  },
+];
+
+export function getCharacterProperty() {
+  return {
+    serial_number: 1,
+    sn_type: serialNumberTypeList[0].value,
+    sn_position: serialNumberPositionList[0].value,
+    sn_display_mode: displayList[0].value,
+
+    is_enable_pinyin: showList[0].value,
+    frame_type: 'tian',
+    frame_color: '#F13232',
+    is_enable_stroke: showList[0].value,
+    is_enable_voice: showList[0].value,
+  };
+}
+
+export function getCharacterData() {
+  return {
+    type: 'character',
+    title: '汉字',
+    property: getCharacterProperty(),
+    content: '',
+    content_list: [],
+    answer: {
+      answer_list: [],
+    },
+  };
+}

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

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

+ 0 - 1
src/views/book/courseware/preview/components/notes/NotesPreview.vue

@@ -80,7 +80,6 @@ export default {
   @include preview-base;
 
   .NPC-zhedie {
-    width: 780px;
     margin-bottom: 24px;
 
     .topTitle {

+ 1 - 2
src/views/book/courseware/preview/components/pinyin_base/PinyinBasePreview.vue

@@ -211,7 +211,7 @@ export default {
     };
   },
   created() {
-    console.log(this.data);
+    // console.log(this.data);
   },
   methods: {
     chooseTone(item, value) {
@@ -325,7 +325,6 @@ export default {
         this.con_preview.push(obj);
         // });
       }
-      console.log(this.con_preview);
       this.show_preview = true;
     },
     handleReplaceTone(e, arr, index, resArr) {