瀏覽代碼

其他词汇

natasha 1 年之前
父節點
當前提交
ab5ab011bc

+ 307 - 0
src/views/book/courseware/create/components/question/other_word/OtherWord.vue

@@ -0,0 +1,307 @@
+<template>
+  <ModuleBase :type="data.type">
+    <template #content>
+      <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">
+            <RichText
+              v-model="scope.row.new_word"
+              :inline="true"
+              toolbar="fontselect fontsizeselect forecolor backcolor | underline | bold italic strikethrough alignleft aligncenter alignright"
+            />
+          </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">
+            <RichText
+              v-model="scope.row.pinyin"
+              :inline="true"
+              toolbar="fontselect fontsizeselect forecolor backcolor | underline | bold italic strikethrough alignleft aligncenter alignright"
+            />
+          </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 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 { getOtherWordData, getOption } from '@/views/book/courseware/data/otherWord';
+import SelectUpload from '@/views/book/courseware/create/components/common/SelectUpload.vue';
+import { GetStaticResources } from '@/api/app';
+
+export default {
+  name: 'OtherWordPage',
+  components: {
+    SelectUpload,
+    SoundRecord,
+    UploadAudio,
+  },
+  mixins: [ModuleMixin],
+  data() {
+    return {
+      data: getOtherWordData(),
+    };
+  },
+  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 }) => {
+        this.data.lrc_arr = lrc_list;
+        this.distribution();
+        loading.close();
+      });
+    },
+    /**
+     * 分配标记
+     */
+    distribution() {
+      const lrcArr = this.data.lrc_arr;
+      if (lrcArr.length === 0) {
+        return this.$message.warning('没有标记可分配');
+      }
+      let curIndex = 0;
+      this.data.option_list.forEach((row) => {
+        row.forEach((item, i) => {
+          const lrcData = lrcArr[curIndex];
+          if (lrcData) {
+            row[i].lrc_data = lrcData;
+            curIndex += 1;
+          }
+        });
+      });
+    },
+    uploadLrcSuccess(fileList) {
+      if (fileList.length > 0) {
+        const { file_name: name, file_url: url, file_id } = fileList[0];
+        this.data.lrc_data = {
+          name,
+          url,
+          id: `[FID##${file_id}##FID]`,
+          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: `[FID##${file_id}##FID]`,
+          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: '',
+        };
+      }
+    },
+    uploads(file_id, index) {
+      this.data.option[index].mp3_list = file_id;
+    },
+    deleteFiles(file_id, index) {
+      this.data.option[index].mp3_list = '';
+    },
+    // 自动生成音频
+    handleMatic(index) {
+      GetStaticResources('tool-TextToVoiceFile', {
+        text: this.data.content.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());
+    },
+  },
+};
+</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>

+ 51 - 0
src/views/book/courseware/create/components/question/other_word/OtherWordSetting.vue

@@ -0,0 +1,51 @@
+<template>
+  <div>
+    <el-form :model="property" label-width="72px" label-position="left">
+      <SerailNumber :property="property" />
+      <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 {
+  getOtherWordProperty,
+  audioGenerationMethodList,
+  pinyinPositionList,
+} from '@/views/book/courseware/data/otherWord';
+
+export default {
+  name: 'OtherWordSetting',
+  mixins: [SettingMixin],
+  data() {
+    return {
+      property: getOtherWordProperty(),
+      audioGenerationMethodList,
+      pinyinPositionList,
+    };
+  },
+  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

@@ -42,6 +42,8 @@ import NewWord from '../create/components/question/new_word/NewWord.vue';
 import NewWordSetting from '../create/components/question/new_word/NewWordSetting.vue';
 import Notes from '../create/components/question/notes/Notes.vue';
 import NotesSetting from '../create/components/question/notes/NotesSetting.vue';
+import OtherWord from '../create/components/question/other_word/OtherWord.vue';
+import OtherWordSetting from '../create/components/question/other_word/OtherWordSetting.vue';
 
 import AudioPreview from '@/views/book/courseware/preview/components/audio/AudioPreview.vue';
 import DividerPreview from '@/views/book/courseware/preview/components/divider/DividerPreview.vue';
@@ -65,6 +67,7 @@ import CharacterPreview from '../preview/components/character/CharacterPreview.v
 import WritePreview from '../preview/components/write/WritePreview.vue';
 import NewWordPreview from '../preview/components/new_word/NewWordPreview.vue';
 import NotesPreview from '../preview/components/notes/NotesPreview.vue';
+import OtherWordPreview from '../preview/components/other_word/OtherWordPreview.vue';
 
 export const bookTypeOption = [
   {
@@ -239,6 +242,14 @@ export const bookTypeOption = [
         preview: NotesPreview,
       },
       {
+        value: 'other_word',
+        label: '其他词汇',
+        icon: '',
+        component: OtherWord,
+        set: OtherWordSetting,
+        preview: OtherWordPreview,
+      },
+      {
         value: 'character',
         label: '汉字组件',
         icon: '',

+ 84 - 0
src/views/book/courseware/data/otherWord.js

@@ -0,0 +1,84 @@
+import {
+  displayList,
+  serialNumberTypeList,
+  serialNumberPositionList,
+  arrangeTypeList,
+  switchOption,
+  isEnable,
+} from '@/views/book/courseware/data/common';
+
+export { arrangeTypeList, switchOption, isEnable };
+  
+// 拼音位置
+export const pinyinPositionList = [
+  { value: 'front', label: '前面' },
+  { value: 'back', label: '后面' },
+  { value: 'top', label: '上面' },
+  { value: 'bottom', label: '下面' },
+];
+  
+// 读音生成方式
+export const audioGenerationMethodList = [
+  {
+    value: 'upload',
+    label: '上传',
+  },
+  {
+    value: 'auto',
+    label: '自动生成',
+  },
+  {
+    value: 'record',
+    label: '录音',
+  },
+];
+
+export function getOption() {
+  return {
+    number:'',
+    new_word: "",
+    cixing: "", //词性
+    definition_list: "", //需要增加词性
+    pinyin: "",
+    mp3_list: '',
+    collocation: '', // 搭配
+    liju_list: '', // 例句
+  }
+}
+export function getOtherWordProperty() {
+  return {
+    serial_number: 1,
+    sn_type: serialNumberTypeList[0].value,
+    sn_position: serialNumberPositionList[0].value,
+    sn_display_mode: displayList[0].value,
+    audio_generation_method: audioGenerationMethodList[0].value,
+    pinyin_position: pinyinPositionList[0].value
+  };
+}
+
+export function getOtherWordData() {
+  return {
+    type: 'other_word',
+    title: '其他词汇',
+    property: getOtherWordProperty(),
+    option: [
+      getOption()
+    ],
+    lrc_arr: [], // lrc 文件解析后的数据
+    // lrc 文件数据
+    lrc_data: {
+      name: '',
+      url: '',
+      id: '',
+      file_id: '',
+    },
+    // 音频文件数据
+    audio_data: {
+      name: '',
+      media_duration: 0,
+      temporary_url: '',
+      url: '',
+      file_id: '',
+    },
+  };
+}

+ 109 - 0
src/views/book/courseware/preview/components/other_word/OtherWordPreview.vue

@@ -0,0 +1,109 @@
+<!-- eslint-disable vue/no-v-html -->
+<template>
+  <div class="select-preview" :style="getAreaStyle()">
+    <SerialNumberPosition v-if="isEnable(data.property.sn_display_mode)" :property="data.property" />
+
+    <div class="main">预览开发中</div>
+  </div>
+</template>
+
+<script>
+import { getOtherWordData } from '@/views/book/courseware/data/otherWord';
+
+import PreviewMixin from '../common/PreviewMixin';
+import AudioFill from '../fill/components/AudioFillPlay.vue';
+import SoundRecord from '../../common/SoundRecord.vue';
+
+export default {
+  name: 'OtherWordPreview',
+  components: {
+    AudioFill,
+    SoundRecord,
+  },
+  mixins: [PreviewMixin],
+  data() {
+    return {
+      data: getOtherWordData(),
+    };
+  },
+  computed: {},
+  created() {
+    // this.answer.answer_list = this.data.model_essay
+    //   .map((item) => {
+    //     return item
+    //       .map(({ type, content, mark }) => {
+    //         if (type === 'input') {
+    //           return {
+    //             value: content,
+    //             mark,
+    //           };
+    //         }
+    //       })
+    //       .filter((item) => item);
+    //   })
+    //   .flat();
+  },
+  methods: {},
+};
+</script>
+
+<style lang="scss" scoped>
+@use '@/styles/mixin.scss' as *;
+
+.select-preview {
+  @include preview-base;
+
+  .main {
+    display: grid;
+    align-items: center;
+  }
+
+  .fill-wrapper {
+    grid-area: fill;
+    font-size: 16pt;
+
+    p {
+      margin: 0;
+    }
+
+    .el-input {
+      display: inline-flex;
+      align-items: center;
+      width: 120px;
+      margin: 0 2px;
+
+      &.pinyin :deep input.el-input__inner {
+        font-family: 'PINYIN-B', sans-serif;
+      }
+
+      &.chinese :deep input.el-input__inner {
+        font-family: 'arial', sans-serif;
+      }
+
+      &.english :deep input.el-input__inner {
+        font-family: 'arial', sans-serif;
+      }
+
+      :deep input.el-input__inner {
+        padding: 0;
+        font-size: 16pt;
+        color: $font-color;
+        text-align: center;
+        background-color: #fff;
+        border-width: 0;
+        border-bottom: 1px solid $font-color;
+        border-radius: 0;
+      }
+    }
+  }
+
+  .record-box {
+    padding: 6px 12px;
+    background-color: $fill-color;
+
+    :deep .record-time {
+      width: 100px;
+    }
+  }
+}
+</style>