natasha преди 1 месец
родител
ревизия
578fe8e9b0

+ 440 - 83
src/views/book/courseware/create/components/question/article/Article.vue

@@ -2,147 +2,504 @@
   <ModuleBase :type="data.type">
     <template #content>
       <!-- eslint-disable max-len -->
-      <div class="fill-wrapper">
-        <el-input v-model="data.content" placeholder="输入" type="textarea" @change="handleChangeContent" />
+      <div class="article-wrapper" v-loading="loading">
+        <el-input v-model="data.content" placeholder="输入" type="textarea" @change="handleChangeContent"></el-input>
+        <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="note" size="12" />
+              <span>{{ data.mp3_list[0].name }}</span>
+            </span>
+          </div>
+          <SvgIcon icon-class="delete-black" size="12" @click="removeFile" />
+        </div>
+        <div class="btn-box" v-if="data.content">
+          <a @click="checkArticle">文章校对</a>
+          <a @click="editWordsFlag = true">编辑生词短语注释</a>
+          <template v-if="data.wordTime && data.wordTime.length > 0">
+            <!-- <span>已有字幕时间节点</span> -->
+            <a type="text" @click="againWordTime">重新生成字幕时间</a>
+            <a @click="compareTime('句子')" size="medium">校对句子字幕时间</a>
+            <a @click="compareTime('文字')" size="medium">校对文字字幕时间</a>
+          </template>
+          <template v-else>
+            <a v-if="!isWordTime" size="medium" @click="createWordTime">自动生成字幕节点</a>
+            <p v-else>字幕节点生成中...请等待</p>
+          </template>
+        </div>
       </div>
+      <el-dialog
+        :visible.sync="showArticleFlag"
+        :show-close="true"
+        :close-on-click-modal="true"
+        :modal-append-to-body="true"
+        :append-to-body="true"
+        :lock-scroll="true"
+        width="80%"
+        class="practiceBox"
+        v-if="showArticleFlag"
+      >
+        <CheckArticle :data="data" @saveWord="saveWord" @savePinyin="savePinyin" @saveStyle="saveStyle"></CheckArticle>
+      </el-dialog>
+      <el-dialog title="校对字幕时间" :visible.sync="compareShow" width="50%" :before-close="handleClose" top="0">
+        <CompareTime :data="compareData" :type="compareType" :changewordsResultList="changewordsResultList" />
+        <span slot="footer" class="dialog-footer">
+          <el-button @click="handleClose">取 消</el-button>
+          <el-button :loading="compareloading" type="primary" @click="saveCompare">确 定</el-button>
+        </span>
+      </el-dialog>
+      <el-dialog title="" :visible.sync="editWordsFlag" width="80%" :close-on-click-modal="true" top="0">
+        <div class="tabs-box">
+          <a :class="[editWordIndex === 0 ? 'active' : '']" @click="editWordIndex = 0">生词</a>
+          <a :class="[editWordIndex === 1 ? 'active' : '']" @click="editWordIndex = 1">注释</a>
+          <a :class="[editWordIndex === 2 ? 'active' : '']" @click="editWordIndex = 2">其他词汇</a>
+        </div>
+        <NewWord
+          v-if="editWordIndex === 0"
+          :dataNewWord="data.new_word_list"
+          key="new_word"
+          @sureNewWords="sureNewWords"
+        ></NewWord>
+
+        <Notes v-if="editWordIndex === 1" :dataNotes="data.notes_list" key="notes" @sureNotes="sureNotes"></Notes>
+        <NewWord
+          v-if="editWordIndex === 2"
+          :dataNewWord="data.other_word_list"
+          @sureNewWords="sureOtherNewWords"
+        ></NewWord>
+      </el-dialog>
     </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 SelectUpload from '@/views/book/courseware/create/components/common/SelectUpload.vue';
+import CheckArticle from './CheckArticle.vue';
+import CompareTime from './CompareTime.vue';
+import NewWord from './NewWord.vue';
+import Notes from './Notes.vue';
 
 import { getArticleData } from '@/views/book/courseware/data/article';
-import { GetStaticResources } from '@/api/app';
+import { segSentences, BatchSegContent, GetStaticResources, getWordTime, prepareTranscribe } from '@/api/app';
+const Base64 = require('js-base64').Base64;
 import cnchar from 'cnchar';
 
 export default {
   name: 'ArticlePage',
   components: {
-    SoundRecord,
-    UploadAudio,
+    SelectUpload,
+    CheckArticle,
+    CompareTime,
+    NewWord,
+    Notes,
   },
   mixins: [ModuleMixin],
   data() {
     return {
       data: getArticleData(),
+      showArticleFlag: false, // 校对文章
+      toneList: [' ', 'ˉ', 'ˊ', 'ˇ', 'ˋ'],
+      loading: false,
+      isWordTime: false,
+      compareType: '', //校对类型
+      compareShow: false,
+      compareData: null,
+      compareloading: false,
+      editWordsFlag: false,
+      editWordIndex: 0,
     };
   },
-  created() {
-    // console.log(this.data);
-  },
+  created() {},
   methods: {
     // 解析输入内容
     handleChangeContent() {
+      this.loading = true;
+      this.data.detail = [];
       if (this.data.content.trim()) {
         let contentArr = this.data.content.split('\n');
-        let contentList = [];
+        let textList = [];
+        let detailItem = {
+          paraIndex: 0,
+          para: '',
+          sentences: [],
+          segList: [],
+          seg_words: [],
+          wordsList: [],
+          timeList: [],
+          isTitle: false,
+          sentencesEn: [],
+        };
+        // 分段
         contentArr.forEach((item, index) => {
           if (item.trim()) {
-            contentList.push({
-              con: item.trim(),
-              pinyin: cnchar.spell(item.trim(), 'array', 'low', 'tone').join(' '),
-              audio_file_id: '',
+            detailItem.para = item;
+            detailItem.paraIndex = index;
+            this.data.detail.push(JSON.parse(JSON.stringify(detailItem)));
+
+            let str = Base64.encode(item);
+            textList.push(str);
+          }
+        });
+        // 分句
+        let sentenceList = []; // 句子按段数组
+        let data = {
+          textList: textList,
+        };
+        segSentences(data)
+          .then((res) => {
+            let result = res.data.result;
+            sentenceList = JSON.parse(JSON.stringify(res.data.result));
+            result.forEach((item, index) => {
+              let sentenceListBase64 = [];
+              this.data.detail[index].sentences = item;
+              item.forEach((items) => {
+                sentenceListBase64.push(Base64.encode(items));
+              });
+              // 分词
+              BatchSegContent({ textList: sentenceListBase64 })
+                .then((res) => {
+                  let list = res.data.result.list;
+                  this.data.detail[index].segList = list;
+                  this.setWordsList(list, index);
+                  if (index === result.length - 1) {
+                    this.loading = false;
+                  }
+                })
+                .catch(() => {
+                  this.loading = false;
+                });
             });
-            this.handleMatic(item.trim(), contentList.length - 1);
+          })
+          .catch(() => {
+            this.loading = false;
+          });
+      }
+      console.log(this.data);
+    },
+    // 处理句子和词的关系
+    handleSenWord() {
+      this.data.sentence_list_mp = [];
+      this.data.detail.forEach((item) => {
+        item.sentences.forEach((items, indexs) => {
+          let word_list = [];
+
+          item.segList[indexs].forEach((itemw) => {
+            word_list.push({
+              word: itemw,
+            });
+          });
+          let obj = {
+            sentence: items,
+            word_list: word_list,
+          };
+          this.data.sentence_list_mp.push(obj);
+        });
+      });
+      console.log(this.data.sentence_list_mp);
+    },
+    setWordsList(list, paraIndex) {
+      let wordsList = [];
+      list.forEach((item, index) => {
+        let sentArr = [];
+
+        item.map((sItem) => {
+          let toneStr = [];
+          for (let i = 0; i < sItem.length; i++) {
+            if (cnchar.isCnChar(sItem[i])) {
+              toneStr.push(this.toneList[cnchar.spellInfo(cnchar.spell(sItem[i], 'low', 'tone')).tone]);
+            }
           }
+
+          let obj = {
+            chs: sItem,
+            pinyin: cnchar.spell(sItem, 'low', 'tone'),
+            pinyin_up:
+              cnchar.spell(sItem, 'low', 'tone').charAt(0).toUpperCase() + cnchar.spell(sItem, 'low', 'tone').slice(1),
+            pinyin_tone: toneStr.join(' '),
+            fontFamily: '楷体',
+          };
+          sentArr.push(obj);
+        });
+        wordsList.push(sentArr);
+      });
+      this.data.detail[paraIndex].wordsList = wordsList;
+    },
+    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 = [];
+    },
+    // 校对文章
+    checkArticle() {
+      this.showArticleFlag = true;
+    },
+    saveWord(saveArr) {
+      saveArr.forEach((item, index) => {
+        let para = '';
+        let sentenceStr = [];
+        let sentences = [];
+        let wordsList = [];
+        item.forEach((items) => {
+          para += items.join('');
+          sentenceStr.push(items.join('&nbsp;&nbsp;'));
+          sentences.push(items.join(''));
+          let sentArr = [];
+          items.forEach((sItem) => {
+            let toneStr = [];
+            for (let i = 0; i < sItem.length; i++) {
+              if (cnchar.isCnChar(sItem[i])) {
+                toneStr.push(this.toneList[cnchar.spellInfo(cnchar.spell(sItem[i], 'low', 'tone')).tone]);
+              }
+            }
+
+            let obj = {
+              chs: sItem,
+              pinyin: cnchar.spell(sItem, 'low', 'tone'),
+              pinyin_up:
+                cnchar.spell(sItem, 'low', 'tone').charAt(0).toUpperCase() +
+                cnchar.spell(sItem, 'low', 'tone').slice(1),
+              pinyin_tone: toneStr.join(' '),
+              fontFamily: '楷体',
+            };
+            sentArr.push(obj);
+          });
+          wordsList.push(sentArr);
         });
-        this.data.content_list = contentList;
+        if (this.data.detail[index]) {
+          this.data.detail[index].segList = item;
+          this.data.detail[index].para = para;
+          this.data.detail[index].sentenceStr = sentenceStr;
+          this.data.detail[index].sentences = sentences;
+          this.data.detail[index].wordsList = wordsList;
+        } else {
+          let obj = {
+            paraIndex: index,
+            para: para,
+            sentences: sentences,
+            segList: item,
+            seg_words: [],
+            wordsList: wordsList,
+            timeList: [],
+            isTitle: false,
+            sentencesEn: [],
+            sentenceStr: sentenceStr,
+          };
+          this.data.detail.push(obj);
+        }
+      });
+      this.$message.success('保存成功,请校对拼音');
+      this.handleSenWord();
+    },
+    // 保存拼音
+    savePinyin(paraIndex, sentenceIndex, wordIndex, pinyin) {
+      if (this.data.pinyin_type === 'tone') {
+        this.data.detail[paraIndex].wordsList[sentenceIndex][wordIndex].pinyin_tone = pinyin;
       } else {
-        this.data.content_list = [];
+        if (wordIndex === 0) {
+          this.data.detail[paraIndex].wordsList[sentenceIndex][wordIndex].pinyin_up = pinyin;
+        } else {
+          this.data.detail[paraIndex].wordsList[sentenceIndex][wordIndex].pinyin = pinyin;
+        }
       }
     },
-    // 自动生成音频
-    handleMatic(con, index) {
-      GetStaticResources('tool-TextToVoiceFile', {
-        text: con.replace(/<[^>]+>/g, ''),
-      })
-        .then(({ status, file_id }) => {
-          if (status === 1) {
-            this.data.content_list[index].audio_file_id = file_id;
-          }
+    saveStyle(paraIndex, sentenceIndex, wordIndex, style) {
+      this.data.detail[paraIndex].wordsList[sentenceIndex][wordIndex].fontFamily = style;
+    },
+    // 保存校对
+    saveCompare() {
+      if (this.compareType == '文字') {
+        this.compareloading = false;
+        return;
+      }
+      this.compareloading = true;
+      compareSenTenceTime({ matchList: JSON.stringify(this.compareData) })
+        .then((res) => {
+          console.log(res);
+          this.compareloading = false;
+          this.data.wordTime = res.data.result;
         })
-        .catch(() => {});
+        .catch(() => {
+          this.compareloading = false;
+        });
+    },
+    // 校对时间
+    compareTime(type) {
+      this.compareType = type;
+      this.compareData = JSON.parse(JSON.stringify(this.data.wordTime));
+      this.compareShow = true;
+    },
+    handleClose() {
+      this.compareShow = false;
+      this.compareData = null;
+      this.compareType = '';
+    },
+    // 校对每个字的时间
+    changewordsResultList(index, item) {
+      this.data.wordTime[index].wordsResultList = JSON.parse(JSON.stringify(item));
+    },
+    againWordTime() {
+      this.isWordTime = false;
+      this.data.wordTime = [];
+    },
+    createWordTime() {
+      this.getfillLiu().then(() => {
+        if (this.data.taskId) {
+          let verseList = [];
+          this.data.detail.forEach((item) => {
+            verseList = verseList.concat(item.sentences);
+          });
+          if (verseList.length > 0) {
+            this.isWordTime = true;
+            let data = {
+              taskId: this.data.taskId,
+              verseList: JSON.stringify(verseList),
+              matchType: 'chinese',
+              language: 'ch',
+            };
+            getWordTime(data).then((res) => {
+              this.data.wordTime = res.data.result;
+              this.isWordTime = false;
+            });
+          }
+        } else {
+          this.$message.warning('请先上传音频');
+          this.loading = false;
+        }
+      });
+    },
+    // 得到文件流
+    getfillLiu() {
+      this.loading = true;
+      let _this = this;
+      return new Promise(function (resolve, reject) {
+        if (_this.data.mp3_list && _this.data.mp3_list.length > 0 && _this.data.mp3_list[0].file_id) {
+          let Mname = 'file_store_manager-GetFileByteBase64Text';
+          let id = _this.data.mp3_list[0].file_id.replace('[FID##', '').replace('##FID]', '');
+          let data = {
+            file_id: id,
+          };
+          GetStaticResources(Mname, data).then((res) => {
+            let taskIddata = {
+              fileName: _this.data.mp3_list[0].name,
+              speechBase64: res.base64_text,
+              language: 'ch',
+            };
+            prepareTranscribe(taskIddata)
+              .then((ress) => {
+                _this.$set(_this.data, 'taskId', ress.data.taskId);
+                _this.loading = false;
+                resolve();
+              })
+              .catch(() => {
+                _this.loading = false;
+              });
+          });
+        } else {
+          _this.$message.warning('请先上传音频');
+          _this.loading = false;
+        }
+      });
+    },
+    sureNewWords(data) {
+      this.data.new_word_list = data;
+    },
+    sureOtherNewWords(data) {
+      this.data.other_word_list = data;
+    },
+    sureNotes(data) {
+      this.data.notes_list = data;
     },
   },
 };
 </script>
 
 <style lang="scss" scoped>
-.fill-wrapper {
+.article-wrapper {
   display: flex;
   flex-direction: column;
   row-gap: 16px;
   align-items: flex-start;
+}
 
-  :deep .rich-wrapper {
-    width: 100%;
-  }
+.upload-file {
+  display: flex;
+  column-gap: 12px;
+  align-items: center;
+  margin: 8px 0;
 
-  .tips {
-    font-size: 12px;
-    color: #999;
-  }
+  .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;
 
-  .auto-matic,
-  .upload-audio-play {
-    :deep .upload-wrapper {
-      margin-top: 0;
+    span {
+      display: flex;
+      column-gap: 14px;
+      align-items: center;
     }
+  }
 
-    .audio-wrapper {
-      :deep .audio-play {
-        width: 16px;
-        height: 16px;
-        color: #000;
-        background-color: initial;
-      }
-
-      :deep .audio-play.not-url {
-        color: #a1a1a1;
-      }
-
-      :deep .voice-play {
-        width: 16px;
-        height: 16px;
-      }
-    }
+  .svg-icon {
+    cursor: pointer;
   }
+}
 
-  .auto-matic {
-    display: flex;
-    flex-shrink: 0;
-    column-gap: 12px;
-    align-items: center;
-    width: 200px;
-    padding: 5px 12px;
-    background-color: $fill-color;
+.btn-box {
+  display: flex;
+  column-gap: 16px;
+
+  a {
+    padding: 5px 16px;
+    font-size: 14px;
+    line-height: 22px;
+    color: #4e5969;
+    cursor: pointer;
+    background: #f2f3f5;
     border-radius: 2px;
+  }
 
-    .auto-btn {
-      font-size: 16px;
-      font-weight: 400;
-      line-height: 22px;
-      color: #1d2129;
-      cursor: pointer;
-    }
+  p {
+    margin: 0;
   }
+}
 
-  .correct-answer {
-    display: flex;
-    flex-wrap: wrap;
-    gap: 8px;
+.tabs-box {
+  display: flex;
+  column-gap: 12px;
+  margin-bottom: 12px;
 
-    .el-input {
-      width: 180px;
+  a {
+    padding: 5px 16px;
+    font-size: 14px;
+    font-weight: 400;
+    line-height: 22px; /* 157.143% */
+    color: #4e5969;
+    cursor: pointer;
+    border-radius: 100px;
 
-      :deep &__prefix {
-        display: flex;
-        align-items: center;
-        color: $text-color;
-      }
+    &.active {
+      color: #165dff;
+      background: #f2f3f5;
     }
   }
 }

+ 233 - 0
src/views/book/courseware/create/components/question/article/CheckArticle.vue

@@ -0,0 +1,233 @@
+<template>
+  <div class="check-article">
+    <div class="main">
+      <div class="main-top">
+        <b>校对</b>
+        <div class="btn-box">
+          <el-button @click="showWordFlag = true">校对分词</el-button>
+          <el-button @click="showPinyinFlag = true">校对拼音</el-button>
+          <el-button @click="showStyleFlag = true">校对样式</el-button>
+        </div>
+      </div>
+      <div class="paragraph" v-for="(item, index) in data.detail" :key="index + 'paragraph'">
+        <label>段 {{ index + 1 }}</label>
+        <div class="sentence-box" v-for="(items, indexs) in item.sentenceStr" :key="indexs + 'words'">
+          <div class="sentence">
+            <b>{{ indexs + 1 }}.</b>
+            <div class="sentence" v-html="items"></div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <el-dialog
+      :visible.sync="showWordFlag"
+      :show-close="true"
+      :close-on-click-modal="true"
+      :modal-append-to-body="true"
+      :append-to-body="true"
+      :lock-scroll="true"
+      width="80%"
+      class="practiceBox"
+      v-if="showWordFlag"
+    >
+      <CheckWord :data="data" @saveWord="saveWord"></CheckWord>
+    </el-dialog>
+    <el-dialog
+      :visible.sync="showPinyinFlag"
+      :show-close="true"
+      :close-on-click-modal="true"
+      :modal-append-to-body="true"
+      :append-to-body="true"
+      :lock-scroll="true"
+      width="80%"
+      class="practiceBox"
+      v-if="showPinyinFlag"
+    >
+      <CheckPinyin :data="data" @savePinyin="savePinyin"></CheckPinyin>
+    </el-dialog>
+    <el-dialog
+      :visible.sync="showStyleFlag"
+      :show-close="true"
+      :close-on-click-modal="true"
+      :modal-append-to-body="true"
+      :append-to-body="true"
+      :lock-scroll="true"
+      width="80%"
+      class="practiceBox"
+      v-if="showStyleFlag"
+    >
+      <CheckStyle :data="data" @saveStyle="saveStyle"></CheckStyle>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import CheckWord from './CheckWord.vue';
+import CheckPinyin from './CheckPinyin.vue';
+import CheckStyle from './CheckStyle.vue';
+export default {
+  props: ['data'],
+  components: {
+    CheckWord,
+    CheckPinyin,
+    CheckStyle,
+  },
+  data() {
+    return {
+      // ArticelData: JSON.parse(JSON.stringify(this.data)),
+      showWordFlag: false,
+      showPinyinFlag: false,
+      showStyleFlag: false,
+    };
+  },
+  // 生命周期 - 创建完成(可以访问当前this实例)
+  created() {
+    this.getArticleData();
+  },
+  methods: {
+    // 获取分析结果
+    getArticleData() {
+      this.data.detail.forEach((item) => {
+        if (item.segList && item.segList.length > 0) {
+          item.sentenceStr = [];
+          item.segList.forEach((items, indexs) => {
+            let str = '';
+            items.forEach((itemss, indexss) => {
+              str += itemss;
+
+              if (indexss !== items.length - 1) str += '&nbsp;&nbsp;';
+            });
+            item.sentenceStr.push(str);
+          });
+        }
+      });
+    },
+    // 保存分词分段
+    saveWord(saveArr) {
+      this.$emit('saveWord', saveArr);
+    },
+    savePinyin(paraIndex, sentenceIndex, wordIndex, pinyin) {
+      this.$emit('savePinyin', paraIndex, sentenceIndex, wordIndex, pinyin);
+    },
+    saveStyle(paraIndex, sentenceIndex, wordIndex, style) {
+      this.$emit('saveStyle', paraIndex, sentenceIndex, wordIndex, style);
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.check-article {
+  min-height: 100%;
+  background: #f6f6f6;
+
+  .wheader {
+    background: #fff;
+  }
+
+  .main {
+    background: #fff;
+
+    &-top {
+      position: relative;
+      display: flex;
+      align-items: center;
+      justify-content: flex-end;
+      margin-bottom: 24px;
+
+      b {
+        position: absolute;
+        top: 0;
+        left: 50%;
+        width: 50px;
+        margin-left: -25px;
+        font-size: 24px;
+        font-weight: 500;
+        line-height: 34px;
+        color: #000;
+        text-align: center;
+      }
+
+      .el-button {
+        padding: 5px 16px;
+        font-size: 14px;
+        font-weight: 400;
+        line-height: 22px;
+        color: #165dff;
+        border: 1px solid #165dff;
+        border-radius: 2px;
+
+        &.el-button--primary {
+          color: #fff;
+          background: #165dff;
+        }
+      }
+    }
+
+    .go-back {
+      display: flex;
+      align-items: center;
+      width: 60px;
+      padding: 5px 8px;
+      font-size: 14px;
+      font-weight: 400;
+      line-height: 22px;
+      color: #333;
+      cursor: pointer;
+      background: #fff;
+      border: 1px solid #d9d9d9;
+      border-radius: 4px;
+      box-shadow: 0 2px 0 0 rgba(0, 0, 0, 2%);
+
+      .el-icon-arrow-left {
+        margin-right: 8px;
+        font-size: 16px;
+      }
+    }
+
+    .paragraph {
+      margin-bottom: 8px;
+      text-align: center;
+
+      > label {
+        padding: 1px 8px;
+        font-size: 14px;
+        font-weight: 400;
+        line-height: 22px;
+        color: var(--blue-05, #175dff);
+        background: #e7eeff;
+        border: 1px solid #175dff;
+        border-radius: 2px;
+      }
+
+      .sentence-box {
+        .sentence {
+          display: flex;
+          margin-top: 8px;
+
+          b {
+            flex-shrink: 0;
+            width: 32px;
+            margin-top: 16px;
+            font-size: 16px;
+            font-weight: 400;
+            line-height: 24px;
+            color: #000;
+          }
+
+          .sentence {
+            flex: 1;
+            padding: 8px;
+            font-size: 16px;
+            font-weight: 400;
+            line-height: 24px;
+            color: #000;
+            text-align: left;
+            background: #f7f7f7;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 500 - 0
src/views/book/courseware/create/components/question/article/CheckPinyin.vue

@@ -0,0 +1,500 @@
+<template>
+  <div class="check-article">
+    <div class="main">
+      <div class="main-top">
+        <div style="display: flex">
+          <b>校对拼音</b>
+        </div>
+        <!-- <div class="btn-box">
+          <el-button type="primary" @click="savePinyin">保存</el-button>
+        </div> -->
+      </div>
+      <div class="article">
+        <el-form>
+          <el-form-item label="标注方式">
+            <el-radio-group v-model="data.pinyin_type" @change="getArticleData">
+              <el-radio label="pinyin">拼音</el-radio>
+              <el-radio label="tone">声调</el-radio>
+            </el-radio-group>
+          </el-form-item>
+        </el-form>
+        <div class="paragraph" v-for="(item, index) in indexArr" :key="index + 'paragraph'">
+          <div class="sentence-box" v-for="(items, indexs) in item" :key="indexs + 'words'">
+            <div
+              class="sentence"
+              @click="selectItem(items, index)"
+              :style="{
+                marginRight: items.marginRight ? '8px' : '',
+                color: activeIndex === index + '_' + items.sentenceIndex + '_' + items.wordIndex ? '#F2555A' : '',
+              }"
+            >
+              <span class="pinyin">{{ items.pinyin }}</span>
+              <span
+                class="words"
+                :class="[/^[0-9]*$/.test(items.text)] ? (/^[\u4e00-\u9fa5]/.test(items.text) ? 'hanzi' : 'en') : ''"
+              >
+                {{ items.text }}
+              </span>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <el-dialog
+      :visible.sync="dialogFlag"
+      :show-close="false"
+      :close-on-click-modal="false"
+      :modal-append-to-body="false"
+      :modal="false"
+      width="250px"
+      class="login-dialog"
+      v-if="dialogFlag"
+    >
+      <div class="check-box">
+        <div class="content">
+          <div class="words-box">
+            <span class="pinyin">
+              {{ itemActive.pinyin }}
+            </span>
+            <span class="words">
+              {{ itemActive.text }}
+            </span>
+          </div>
+        </div>
+        <el-input v-model="checkPinyinInput" type="text" class="checkPinyinInput" />
+        <p class="tips">
+          一到四声分别用数字1-4表示。拼音间用空格隔开,儿化音用_代替空格,如“骨朵儿”输入“gu1 duo3_er”。
+        </p>
+        <div class="btn-box">
+          <el-button type="info" size="small" @click="cancleDialog">取消</el-button>
+          <el-button type="primary" size="small" @click="surePinyin">保存</el-button>
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+// import { publicMethods, reparse } from '@/api/api';
+import th from 'element-ui/lib/locale/lang/th';
+
+export default {
+  props: ['data'],
+  components: {},
+  data() {
+    return {
+      loading: false,
+      id: '',
+      ArticelData: null,
+      indexArr: [], // 索引数组
+      activeIndex: null,
+      itemActive: null,
+      dialogFlag: false,
+      checkPinyinInput: '',
+      oldInput: '',
+      tableData: [
+        ['ā', 'á', 'ǎ', 'à', 'a'],
+        ['ō', 'ó', 'ǒ', 'ò', 'o'],
+        ['ē', 'é', 'ě', 'è', 'e'],
+        ['ī', 'í', 'ǐ', 'ì', 'i'],
+        ['ū', 'ú', 'ǔ', 'ù', 'u'],
+        ['ǖ', 'ǘ', 'ǚ', 'ǜ', 'ü'],
+        ['Ā', 'Á', 'Â', 'À', 'A'],
+        ['Ō', 'Ó', 'Ô', 'Ò', 'O'],
+        ['Ē', 'É', 'Ê', 'È', 'E'],
+        ['Ī', 'Í', 'Î', 'Ì', 'I'],
+        ['Ū', 'Ú', 'Û', 'Ù', 'U'],
+      ],
+      toneList: [' ', 'ˉ', 'ˊ', 'ˇ', 'ˋ'],
+    };
+  },
+  // 生命周期 - 创建完成(可以访问当前this实例)
+  created() {
+    this.getArticleData();
+  },
+  methods: {
+    // 获取分析结果
+    getArticleData() {
+      this.loading = true;
+      let newdata = [];
+      this.data.detail.forEach((item) => {
+        if (item.wordsList && item.wordsList.length !== 0) {
+          newdata.push(item);
+        }
+      });
+      // this.ArticelData = JSON.parse(JSON.stringify(newdata));
+      let saveArr = [];
+      let arr = [];
+      let saveIndex = 0;
+      // 添加索引
+      newdata.forEach((item, index) => {
+        arr.push([]);
+        item.wordsList.forEach((items, indexs) => {
+          items.forEach((itemss, indexss) => {
+            let pinyin =
+              this.data.pinyin_type === 'pinyin'
+                ? this.data.property.is_enable_sentence_case && indexss === 0
+                  ? itemss.pinyin_up
+                  : itemss.pinyin
+                : itemss.pinyin_tone;
+            let obj = {
+              text: itemss.chs,
+              pinyin: pinyin,
+              paraIndex: item.paraIndex,
+              sentenceIndex: indexs,
+              wordIndex: indexss,
+              marginRight: true,
+              saveIndex: saveIndex,
+            };
+            arr[index].push(obj);
+            let saveObj = {
+              word: itemss.chs,
+              pinyin: pinyin,
+              // pinyin_lt: itemss.pinyin_lt?itemss.pinyin_lt:''
+            };
+            saveArr.push(saveObj);
+            saveIndex++;
+          });
+        });
+      });
+      this.indexArr = arr;
+      this.ArticelData = saveArr;
+      this.loading = false;
+    },
+    selectItem(item, index) {
+      this.activeIndex = index + '_' + item.sentenceIndex + '_' + item.wordIndex;
+      this.itemActive = item;
+      this.dialogFlag = true;
+      this.checkPinyinInput = '';
+    },
+    cancleDialog() {
+      this.activeIndex = null;
+      this.itemActive = null;
+      this.checkPinyinInput = '';
+      this.oldInput = '';
+      this.dialogFlag = false;
+    },
+    surePinyin() {
+      this.oldInput = JSON.parse(JSON.stringify(this.checkPinyinInput.trim()));
+      this.handleReplaceTone(this.checkPinyinInput.trim());
+    },
+    handleReplaceTone(e) {
+      let _this = this;
+      _this.$nextTick(() => {
+        let value = e;
+        _this.resArr = [];
+        if (value) {
+          let valueArr = [];
+          value.replace(/\s+/g, ' ');
+          valueArr = value.split(' ');
+          //   let reg = /\s+/g;
+          //   valueArr = value.split(reg);
+
+          valueArr.forEach((item, index) => {
+            if (this.data.pinyin_type === 'tone') {
+              this.resArr.push([
+                {
+                  con: this.toneList[item * 1],
+                },
+              ]);
+            } else {
+              this.handleValue(item);
+            }
+          });
+          let str = '';
+          setTimeout(() => {
+            _this.resArr.forEach((item) => {
+              str += ' ';
+              item.forEach((sItem) => {
+                if (sItem.number && sItem.con) {
+                  let number = Number(sItem.number);
+                  let con = sItem.con;
+                  let word = _this.addTone(number, con);
+                  str += word;
+                } else {
+                  if (sItem.number) {
+                    str += sItem.number;
+                  } else if (sItem.con) {
+                    str += ' ' + sItem.con + ' ';
+                  }
+                }
+              });
+            });
+            this.checkPinyinInput = str.trim();
+            this.ArticelData[this.itemActive.saveIndex].pinyin = this.checkPinyinInput
+              .replace(/\s+/g, ' ')
+              .split(/\s+/)
+              .join(',');
+            this.itemActive.pinyin = this.checkPinyinInput.replace(/\s+/g, '');
+            this.savePinyin();
+          }, 10);
+        }
+      });
+    },
+    handleValue(valItem) {
+      let reg = /\d/;
+      let reg2 = /[A-Za-z]+\d/g;
+      let numList = [];
+      let valArr = valItem.split('');
+      if (reg2.test(valItem)) {
+        for (let i = 0; i < valArr.length; i++) {
+          let item = valItem[i];
+          if (reg.test(item)) {
+            let numIndex = numList.length == 0 ? 0 : numList[numList.length - 1].index;
+            let con = valItem.substring(numIndex, i);
+            con = con.replace(/\d/g, '');
+            let obj = {
+              index: i,
+              number: item,
+              con: con,
+              isTran: true,
+            };
+            numList.push(obj);
+          }
+        }
+      } else {
+        numList = [];
+      }
+      if (numList.length == 0) {
+        this.resArr.push([{ con: valItem }]);
+      } else {
+        this.resArr.push(numList);
+      }
+    },
+    addTone(number, con) {
+      let _this = this;
+      let zmList = ['a', 'o', 'e', 'i', 'u', 'v', 'A', 'O', 'E', 'I', 'U'];
+      if (number) {
+        for (let i = 0; i < zmList.length; i++) {
+          let zm = zmList[i];
+          if (con.indexOf(zm) > -1) {
+            let zm2 = _this.tableData[i][number - 1];
+            if (con.indexOf('iu') > -1) {
+              zm2 = _this.tableData[4][number - 1];
+              con = con.replace('u', zm2);
+            } else if (con.indexOf('ui') > -1) {
+              zm2 = _this.tableData[3][number - 1];
+              con = con.replace('i', zm2);
+            } else if (
+              con.indexOf('yv') > -1 ||
+              con.indexOf('jv') > -1 ||
+              con.indexOf('qv') > -1 ||
+              con.indexOf('xv') > -1
+            ) {
+              zm2 = _this.tableData[4][number - 1];
+              con = con.replace('v', zm2);
+            } else {
+              con = con.replace(zm, zm2);
+            }
+
+            break;
+          }
+        }
+      }
+      return con;
+    },
+    savePinyin() {
+      this.$emit(
+        'savePinyin',
+        this.itemActive.paraIndex,
+        this.itemActive.sentenceIndex,
+        this.itemActive.wordIndex,
+        this.itemActive.pinyin,
+      );
+      this.$message.success('保存成功');
+      this.activeIndex = null;
+      // this.itemActive = null;
+      this.checkPinyinInput = '';
+      this.oldInput = '';
+      this.dialogFlag = false;
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.check-article {
+  min-height: 100%;
+  background: #f6f6f6;
+
+  .wheader {
+    background: #fff;
+  }
+
+  .el-button {
+    padding: 5px 16px;
+    font-size: 14px;
+    font-weight: 400;
+    line-height: 22px;
+    color: #4e5969;
+    background: #f2f3f5;
+    border: 1px solid #f2f3f5;
+    border-radius: 2px;
+
+    &.el-button--primary {
+      color: #fff;
+      background: #165dff;
+      border: 1px solid #165dff;
+    }
+  }
+
+  .main {
+    background: #fff;
+
+    &-top {
+      position: relative;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      margin-bottom: 24px;
+
+      b {
+        margin-left: 16px;
+        font-size: 24px;
+        font-weight: 500;
+        line-height: 34px;
+        color: #000;
+      }
+    }
+
+    .go-back {
+      display: flex;
+      align-items: center;
+      width: 60px;
+      padding: 5px 8px;
+      font-size: 14px;
+      font-weight: 400;
+      line-height: 22px;
+      color: #333;
+      cursor: pointer;
+      background: #fff;
+      border: 1px solid #d9d9d9;
+      border-radius: 4px;
+      box-shadow: 0 2px 0 0 rgba(0, 0, 0, 2%);
+
+      .el-icon-arrow-left {
+        margin-right: 8px;
+        font-size: 16px;
+      }
+    }
+
+    .article {
+      padding: 8px;
+      background: #fcfcfc;
+      border: 1px solid rgba(0, 0, 0, 8%);
+      border-radius: 2px;
+    }
+
+    .paragraph {
+      display: flex;
+      flex-flow: wrap;
+      width: 100%;
+      margin-bottom: 24px;
+      text-align: center;
+
+      .sentence-box {
+        display: flex;
+        flex-flow: wrap;
+        float: left;
+
+        .sentence {
+          margin-top: 8px;
+          color: #000;
+          text-align: center;
+          cursor: pointer;
+
+          .words {
+            display: block;
+            font-size: 20px;
+            font-weight: 400;
+            line-height: 28px;
+          }
+
+          .pinyin {
+            display: block;
+            height: 14px;
+            font-family: 'League';
+            font-size: 14px;
+            font-weight: 400;
+            line-height: 1;
+          }
+        }
+      }
+    }
+  }
+}
+
+.check-box {
+  padding: 24px;
+  background: #fff;
+  border-radius: 4px;
+  box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 25%);
+
+  .content {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+
+    .words-box {
+      color: #000;
+      text-align: center;
+
+      .words {
+        display: block;
+        font-size: 28px;
+        font-weight: 400;
+        line-height: 40px;
+      }
+
+      .pinyin {
+        display: block;
+        height: 20px;
+        font-family: 'League';
+        font-size: 20px;
+        font-weight: 400;
+        line-height: 1;
+      }
+    }
+  }
+
+  .checkPinyinInput {
+    margin: 16px 0 8px;
+  }
+
+  .tips {
+    margin: 0 0 24px;
+    font-size: 12px;
+    font-weight: 400;
+    line-height: 18px;
+    color: #a0a0a0;
+  }
+
+  .btn-box {
+    text-align: right;
+
+    .el-button {
+      padding: 2px 12px;
+      font-size: 12px;
+      line-height: 20px;
+    }
+  }
+}
+</style>
+<style lang="scss">
+.check-article {
+  .login-dialog {
+    .el-dialog__header {
+      padding: 0;
+    }
+
+    .el-dialog__body {
+      padding: 0;
+    }
+
+    .el-input__inner {
+      text-align: center;
+      background: #f3f3f3;
+    }
+  }
+}
+</style>

+ 387 - 0
src/views/book/courseware/create/components/question/article/CheckStyle.vue

@@ -0,0 +1,387 @@
+<template>
+  <div class="check-article">
+    <div class="main">
+      <div class="main-top">
+        <div style="display: flex">
+          <b>校对样式</b>
+        </div>
+        <!-- <div class="btn-box">
+          <el-button type="primary" @click="savePinyin">保存</el-button>
+        </div> -->
+      </div>
+      <div class="article">
+        <div class="paragraph" v-for="(item, index) in indexArr" :key="index + 'paragraph'">
+          <div class="sentence-box" v-for="(items, indexs) in item" :key="indexs + 'words'">
+            <div
+              class="sentence"
+              @click="selectItem(items, index)"
+              :style="{
+                marginRight: items.marginRight ? '8px' : '',
+                color: activeIndex === index + '_' + items.sentenceIndex + '_' + items.wordIndex ? '#F2555A' : '',
+              }"
+            >
+              <span
+                class="words"
+                :class="[/^[0-9]*$/.test(items.text)] ? (/^[\u4e00-\u9fa5]/.test(items.text) ? 'hanzi' : 'en') : ''"
+              >
+                {{ items.text }}
+              </span>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <el-dialog
+      :visible.sync="dialogFlag"
+      :show-close="false"
+      :close-on-click-modal="false"
+      :modal-append-to-body="false"
+      :modal="false"
+      width="250px"
+      class="login-dialog"
+      v-if="dialogFlag"
+    >
+      <div class="check-box">
+        <div class="content">
+          <div class="words-box">
+            <span class="words">
+              {{ itemActive.text }}
+            </span>
+          </div>
+        </div>
+        <el-form>
+          <el-form-item label="字体">
+            <el-radio-group v-model="itemActive.fontFamily">
+              <el-radio v-for="(item, index) in fontFamilyList" :key="'fontFamilyList' + index" :label="item.value">
+                <span>{{ item.name }}</span>
+              </el-radio>
+            </el-radio-group>
+          </el-form-item>
+        </el-form>
+        <div class="btn-box">
+          <el-button type="info" size="small" @click="cancleDialog">取消</el-button>
+          <el-button type="primary" size="small" @click="surePinyin">保存</el-button>
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+// import { publicMethods, reparse } from '@/api/api';
+import th from 'element-ui/lib/locale/lang/th';
+
+export default {
+  props: ['data'],
+  components: {},
+  data() {
+    return {
+      loading: false,
+      id: '',
+      ArticelData: null,
+      indexArr: [], // 索引数组
+      activeIndex: null,
+      itemActive: null,
+      dialogFlag: false,
+      checkPinyinInput: '',
+      oldInput: '',
+      tableData: [
+        ['ā', 'á', 'ǎ', 'à', 'a'],
+        ['ō', 'ó', 'ǒ', 'ò', 'o'],
+        ['ē', 'é', 'ě', 'è', 'e'],
+        ['ī', 'í', 'ǐ', 'ì', 'i'],
+        ['ū', 'ú', 'ǔ', 'ù', 'u'],
+        ['ǖ', 'ǘ', 'ǚ', 'ǜ', 'ü'],
+        ['Ā', 'Á', 'Â', 'À', 'A'],
+        ['Ō', 'Ó', 'Ô', 'Ò', 'O'],
+        ['Ē', 'É', 'Ê', 'È', 'E'],
+        ['Ī', 'Í', 'Î', 'Ì', 'I'],
+        ['Ū', 'Ú', 'Û', 'Ù', 'U'],
+      ],
+      toneList: [' ', 'ˉ', 'ˊ', 'ˇ', 'ˋ'],
+      fontFamilyList: [
+        {
+          value: '楷体',
+          name: '中文',
+        },
+        {
+          value: 'Sans-GBNPC',
+          name: '拼音',
+        },
+        {
+          value: 'robot',
+          name: '英文',
+        },
+        {
+          value: 'Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, sans-serif, alabo',
+          name: '系统字体',
+        },
+      ],
+    };
+  },
+  // 生命周期 - 创建完成(可以访问当前this实例)
+  created() {
+    this.getArticleData();
+  },
+  methods: {
+    // 获取分析结果
+    getArticleData() {
+      this.loading = true;
+      let newdata = [];
+      this.data.detail.forEach((item) => {
+        if (item.wordsList.length !== 0) {
+          newdata.push(item);
+        }
+      });
+      // this.ArticelData = JSON.parse(JSON.stringify(newdata));
+      let saveArr = [];
+      let arr = [];
+      let saveIndex = 0;
+      // 添加索引
+      newdata.forEach((item, index) => {
+        arr.push([]);
+        item.wordsList.forEach((items, indexs) => {
+          items.forEach((itemss, indexss) => {
+            let obj = {
+              text: itemss.chs,
+              paraIndex: index,
+              sentenceIndex: indexs,
+              wordIndex: indexss,
+              marginRight: true,
+              saveIndex: saveIndex,
+              fontFamily: itemss.fontFamily,
+            };
+            arr[index].push(obj);
+            let saveObj = {
+              word: itemss.chs,
+              // pinyin_lt: itemss.pinyin_lt?itemss.pinyin_lt:''
+            };
+            saveArr.push(saveObj);
+            saveIndex++;
+          });
+        });
+      });
+      this.indexArr = arr;
+      this.ArticelData = saveArr;
+      this.loading = false;
+    },
+    selectItem(item, index) {
+      this.activeIndex = index + '_' + item.sentenceIndex + '_' + item.wordIndex;
+      this.itemActive = item;
+      this.dialogFlag = true;
+      this.checkPinyinInput = '';
+    },
+    cancleDialog() {
+      this.activeIndex = null;
+      this.itemActive = null;
+      this.checkPinyinInput = '';
+      this.oldInput = '';
+      this.dialogFlag = false;
+    },
+    surePinyin() {
+      this.saveStyle();
+      this.dialogFlag = false;
+    },
+    saveStyle() {
+      this.$emit(
+        'saveStyle',
+        this.itemActive.paraIndex,
+        this.itemActive.sentenceIndex,
+        this.itemActive.wordIndex,
+        this.itemActive.fontFamily,
+      );
+      this.$message.success('保存成功');
+      this.activeIndex = null;
+      // this.itemActive = null;
+      this.dialogFlag = false;
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.check-article {
+  min-height: 100%;
+  background: #f6f6f6;
+
+  .wheader {
+    background: #fff;
+  }
+
+  .el-button {
+    padding: 5px 16px;
+    font-size: 14px;
+    font-weight: 400;
+    line-height: 22px;
+    color: #4e5969;
+    background: #f2f3f5;
+    border: 1px solid #f2f3f5;
+    border-radius: 2px;
+
+    &.el-button--primary {
+      color: #fff;
+      background: #165dff;
+      border: 1px solid #165dff;
+    }
+  }
+
+  .main {
+    background: #fff;
+
+    &-top {
+      position: relative;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      margin-bottom: 24px;
+
+      b {
+        margin-left: 16px;
+        font-size: 24px;
+        font-weight: 500;
+        line-height: 34px;
+        color: #000;
+      }
+    }
+
+    .go-back {
+      display: flex;
+      align-items: center;
+      width: 60px;
+      padding: 5px 8px;
+      font-size: 14px;
+      font-weight: 400;
+      line-height: 22px;
+      color: #333;
+      cursor: pointer;
+      background: #fff;
+      border: 1px solid #d9d9d9;
+      border-radius: 4px;
+      box-shadow: 0 2px 0 0 rgba(0, 0, 0, 2%);
+
+      .el-icon-arrow-left {
+        margin-right: 8px;
+        font-size: 16px;
+      }
+    }
+
+    .article {
+      padding: 8px;
+      background: #fcfcfc;
+      border: 1px solid rgba(0, 0, 0, 8%);
+      border-radius: 2px;
+    }
+
+    .paragraph {
+      display: flex;
+      flex-flow: wrap;
+      width: 100%;
+      margin-bottom: 24px;
+      text-align: center;
+
+      .sentence-box {
+        display: flex;
+        flex-flow: wrap;
+        float: left;
+
+        .sentence {
+          margin-top: 8px;
+          color: #000;
+          text-align: center;
+          cursor: pointer;
+
+          .words {
+            display: block;
+            font-size: 20px;
+            font-weight: 400;
+            line-height: 28px;
+          }
+
+          .pinyin {
+            display: block;
+            height: 14px;
+            font-family: 'League';
+            font-size: 14px;
+            font-weight: 400;
+            line-height: 1;
+          }
+        }
+      }
+    }
+  }
+}
+
+.check-box {
+  padding: 24px;
+  background: #fff;
+  border-radius: 4px;
+  box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 25%);
+
+  .content {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+
+    .words-box {
+      color: #000;
+      text-align: center;
+
+      .words {
+        display: block;
+        font-size: 28px;
+        font-weight: 400;
+        line-height: 40px;
+      }
+
+      .pinyin {
+        display: block;
+        height: 20px;
+        font-family: 'League';
+        font-size: 20px;
+        font-weight: 400;
+        line-height: 1;
+      }
+    }
+  }
+
+  .checkPinyinInput {
+    margin: 16px 0 8px;
+  }
+
+  .tips {
+    margin: 0 0 24px;
+    font-size: 12px;
+    font-weight: 400;
+    line-height: 18px;
+    color: #a0a0a0;
+  }
+
+  .btn-box {
+    text-align: right;
+
+    .el-button {
+      padding: 2px 12px;
+      font-size: 12px;
+      line-height: 20px;
+    }
+  }
+}
+</style>
+<style lang="scss">
+.check-article {
+  .login-dialog {
+    .el-dialog__header {
+      padding: 0;
+    }
+
+    .el-dialog__body {
+      padding: 0;
+    }
+
+    .el-input__inner {
+      text-align: center;
+      background: #f3f3f3;
+    }
+  }
+}
+</style>

+ 301 - 0
src/views/book/courseware/create/components/question/article/CheckWord.vue

@@ -0,0 +1,301 @@
+<template>
+  <div class="check-article">
+    <div class="main">
+      <div class="main-top">
+        <div style="display: flex; align-items: end">
+          <b>校对分词</b>
+          <p class="tips">
+            分词:在需要分开的内容中间插入两个空格;分句:将句子另起一行;分段:两段中间加入空行。校对分词后请校对拼音。
+          </p>
+        </div>
+        <div class="btn-box">
+          <!-- <el-switch
+                class="show-pos"
+                v-model="showPos"
+                active-text="显示词性"
+                inactive-text="">
+            </el-switch> -->
+          <el-button type="primary" @click="saveWord">保存</el-button>
+        </div>
+      </div>
+      <el-input class="no-pos-content" v-model="noPosContent" type="textarea" :autosize="{ minRows: 27 }" />
+    </div>
+  </div>
+</template>
+
+<script>
+// import { publicMethods, reparse } from '@/api/api';
+
+export default {
+  props: ['data'],
+  components: {},
+  data() {
+    return {
+      noPosContent: '',
+    };
+  },
+  // 生命周期 - 创建完成(可以访问当前this实例)
+  created() {
+    this.getArticleData();
+  },
+  methods: {
+    // 获取分析结果
+    getArticleData() {
+      let str = '';
+
+      this.data.detail.forEach((item, index) => {
+        if (item.segList && item.segList.length > 0) {
+          item.segList.forEach((items, indexs) => {
+            items.forEach((itemss, indexss) => {
+              str += itemss + (indexss !== items.length - 1 ? '  ' : '');
+            });
+            str += '\n';
+          });
+        }
+
+        if (index !== this.data.detail.length - 1) {
+          str += '\n';
+        }
+      });
+      this.noPosContent = str;
+    },
+    saveWord() {
+      let saveArr = [];
+      let arr = this.noPosContent.split('\n');
+      let indexP = 0; // 段落索引
+      let indexS = 0; // 句子索引
+      let flag = true;
+      arr.forEach((item) => {
+        if (item === '') {
+          saveArr.push([]);
+        }
+      });
+      arr.forEach((item, index) => {
+        if (item === '') {
+          indexP++;
+          indexS = 0;
+        } else {
+          let arrs = item.trim().split('  ');
+          let saveItem = [];
+          arrs.forEach((items, indexs) => {
+            saveItem.push(items);
+          });
+          saveArr[indexP].push(saveItem);
+          indexS++;
+        }
+      });
+      this.$emit('saveWord', saveArr);
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.check-article {
+  min-height: 100%;
+  background: #f6f6f6;
+
+  .wheader {
+    background: #fff;
+  }
+
+  .el-button {
+    padding: 5px 16px;
+    font-size: 14px;
+    font-weight: 400;
+    line-height: 22px;
+    color: #4e5969;
+    background: #f2f3f5;
+    border: 1px solid #f2f3f5;
+    border-radius: 2px;
+
+    &.el-button--primary {
+      color: #fff;
+      background: #165dff;
+      border: 1px solid #165dff;
+    }
+  }
+
+  .main {
+    background: #fff;
+
+    &-top {
+      position: relative;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      margin-bottom: 24px;
+
+      b {
+        margin-left: 16px;
+        font-size: 24px;
+        font-weight: 500;
+        line-height: 34px;
+        color: #000;
+      }
+
+      .tips {
+        margin-left: 12px;
+        font-size: 12px;
+        font-weight: 400;
+        line-height: 20px;
+        color: #979797;
+      }
+
+      .show-pos {
+        margin-right: 24px;
+
+        &.is-checked {
+          .el-switch__label.is-active {
+            color: rgba(13, 13, 13, 100%);
+          }
+        }
+      }
+    }
+
+    .go-back {
+      display: flex;
+      align-items: center;
+      width: 60px;
+      padding: 5px 8px;
+      font-size: 14px;
+      font-weight: 400;
+      line-height: 22px;
+      color: #333;
+      cursor: pointer;
+      background: #fff;
+      border: 1px solid #d9d9d9;
+      border-radius: 4px;
+      box-shadow: 0 2px 0 0 rgba(0, 0, 0, 2%);
+
+      .el-icon-arrow-left {
+        margin-right: 8px;
+        font-size: 16px;
+      }
+    }
+
+    .pos-box {
+      padding: 16px;
+      margin-bottom: 16px;
+      background: #f7f7f7;
+      border-radius: 4px;
+
+      h4 {
+        margin: 0;
+        font-size: 14px;
+        font-weight: 600;
+        line-height: 22px;
+        color: #000;
+      }
+
+      ul {
+        display: flex;
+        flex-flow: wrap;
+        padding: 0;
+        margin: 0;
+        list-style: none;
+
+        li {
+          padding: 4px 16px;
+          margin: 8px 8px 0 0;
+          background: #fff;
+          border-radius: 4px;
+
+          label {
+            margin-right: 8px;
+            font-size: 14px;
+            font-weight: 600;
+            line-height: 22px;
+            color: #007eff;
+          }
+
+          span {
+            font-size: 14px;
+            font-weight: 400;
+            line-height: 22px;
+            color: #000;
+          }
+        }
+      }
+    }
+  }
+}
+
+.check-box {
+  padding: 24px;
+  background: #fff;
+  border-radius: 4px;
+  box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 25%);
+
+  .content {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+
+    .words-box {
+      color: #000;
+      text-align: center;
+
+      .words {
+        display: block;
+        font-size: 28px;
+        font-weight: 400;
+        line-height: 40px;
+      }
+
+      .pinyin {
+        display: block;
+        height: 20px;
+        font-family: 'League';
+        font-size: 20px;
+        font-weight: 400;
+        line-height: 1;
+      }
+    }
+  }
+
+  .checkPinyinInput {
+    margin: 16px 0 8px;
+  }
+
+  .tips {
+    margin: 0 0 24px;
+    font-size: 12px;
+    font-weight: 400;
+    line-height: 18px;
+    color: #a0a0a0;
+  }
+
+  .btn-box {
+    text-align: right;
+
+    .el-button {
+      padding: 2px 12px;
+      font-size: 12px;
+      line-height: 20px;
+    }
+  }
+}
+</style>
+<style lang="scss">
+.check-article {
+  .show-pos {
+    .el-switch__label.is-active {
+      color: rgba(13, 13, 13, 100%);
+    }
+
+    &.el-switch.is-checked .el-switch__core {
+      background-color: #165dff;
+      border-color: #165dff;
+    }
+  }
+
+  .no-pos-content {
+    .el-textarea__inner {
+      font-family: 'Helvetica', '楷体';
+      font-size: 16px;
+      color: rgba(0, 0, 0, 88%);
+    }
+  }
+}
+</style>

+ 207 - 0
src/views/book/courseware/create/components/question/article/CompareTime.vue

@@ -0,0 +1,207 @@
+<template>
+  <div class="CompareTime">
+    <template v-if="type == '句子'">
+      <table>
+        <tr>
+          <th style="width: 280px">句子</th>
+          <th style="width: 200px">开始时间</th>
+          <th style="width: 200px">结束时间</th>
+        </tr>
+        <tr v-for="(item, index) in data" :key="index + 'sen'">
+          <td>{{ item.onebest }}</td>
+          <td>
+            <input
+              v-model="item.bg"
+              @change="changeTime(item, 'bg')"
+              type="text"
+              maxlength="200"
+              show-word-limit
+            />
+          </td>
+          <td>
+            <input
+              v-model="item.ed"
+              @change="changeTime(item, 'ed')"
+              type="text"
+              maxlength="200"
+              show-word-limit
+            />
+          </td>
+        </tr>
+      </table>
+    </template>
+    <template v-else>
+      <table>
+        <tr>
+          <th style="width: 280px">句子</th>
+          <th style="width: 200px">操作</th>
+        </tr>
+        <tr v-for="(item, index) in data" :key="index + 'sen'">
+          <td>{{ item.onebest }}</td>
+          <td>
+            <el-button type="primary" @click="compareOneHZ(index)" size="medium"
+              >校对文字字幕时间</el-button
+            >
+          </td>
+        </tr>
+      </table>
+    </template>
+    <template v-if="wordData">
+      <el-dialog
+        title="校对字母时间"
+        :visible.sync="oneHZshow"
+        width="60%"
+        :before-close="handleClose"
+        :modal="false"
+        top="0"
+      >
+        <table>
+          <tr>
+            <th style="width: 250px">文字</th>
+            <th style="width: 200px">开始时间</th>
+            <th style="width: 200px">结束时间</th>
+          </tr>
+          <tr v-for="(item, index) in wordData" :key="index + 'wordsResul'">
+            <td>{{ item.wordsName ? item.wordsName : item.onebest }}</td>
+            <td>
+              <input
+                v-model="item.wordBg"
+                type="text"
+                @change="changeTime(item, 'wordBg')"
+                maxlength="200"
+                show-word-limit
+              />
+            </td>
+            <td>
+              <input
+                v-model="item.wordEd"
+                type="text"
+                @change="changeTime(item, 'wordEd')"
+                maxlength="200"
+                show-word-limit
+              />
+            </td>
+          </tr>
+        </table>
+        <span slot="footer" class="dialog-footer">
+          <el-button @click="handleClose">取 消</el-button>
+          <el-button :loading="compareloading" type="primary" @click="savehz"
+            >确 定</el-button
+          >
+        </span>
+      </el-dialog>
+    </template>
+  </div>
+</template>
+
+<script>
+//这里可以导入其它文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
+//例如:import 《组件名称》from ‘《组件路径》';
+export default {
+  //import引入的组件需要注入到对象中才能使用
+  components: {},
+  props: ["data", "type", "changewordsResultList"],
+  data() {
+    //这里存放数据
+    return {
+      oneHZshow: false,
+      compareloading: false,
+      wordData: null,
+      index: null,
+    };
+  },
+  //计算属性 类似于data概念
+  computed: {},
+  //监控data中数据变化
+  watch: {},
+  //方法集合
+  methods: {
+    changeTime(item, type) {
+      item[type] = item[type] * 1;
+      if (type == "bg" || type == "en") {
+        item.adjust = "1";
+      }
+    },
+    compareOneHZ(index) {
+      this.index = index;
+      this.wordData = JSON.parse(
+        JSON.stringify(this.data[index].wordsResultList)
+      );
+      this.oneHZshow = true;
+    },
+    savehz() {
+      this.data[this.index].wordsResultList = JSON.parse(
+        JSON.stringify(this.wordData)
+      );
+      this.changewordsResultList(this.index, this.wordData);
+      this.$message.success("保存成功");
+    },
+    handleClose() {
+      this.index = null;
+      this.wordData = null;
+      this.oneHZshow = false;
+    },
+  },
+  //生命周期 - 创建完成(可以访问当前this实例)
+  created() {},
+  //生命周期 - 挂载完成(可以访问DOM元素)
+  mounted() {},
+  //生命周期-创建之前
+  beforeCreated() {},
+  //生命周期-挂载之前
+  beforeMount() {},
+  //生命周期-更新之前
+  beforUpdate() {},
+  //生命周期-更新之后
+  updated() {},
+  //生命周期-销毁之前
+  beforeDestory() {},
+  //生命周期-销毁完成
+  destoryed() {},
+  //如果页面有keep-alive缓存功能,这个函数会触发
+  activated() {},
+};
+</script>
+<style lang="scss" scoped>
+/* @import url(); 引入css类 */
+.CompareTime {
+  .sentence {
+    // display: flex;
+    // align-items: center;
+    margin-top: 15px;
+
+    .time {
+      margin-top: 15px;
+    }
+  }
+
+  table {
+    border-collapse: collapse;
+
+    tr {
+      > :nth-child(2) {
+        text-align: center;
+      }
+
+      > :nth-child(3) {
+        text-align: center;
+      }
+
+      td {
+        height: 50px;
+        border: 1px solid black;
+
+        input {
+          border: none;
+          outline: none;
+        }
+      }
+
+      th {
+        height: 50px;
+        border: 1px solid black;
+      }
+    }
+  }
+}
+</style>

+ 297 - 0
src/views/book/courseware/create/components/question/article/NewWord.vue

@@ -0,0 +1,297 @@
+<template>
+  <div>
+    <label>标题:</label>
+    <RichText
+      v-model="data.title_con"
+      :inline="true"
+      :placeholder="'输入标题'"
+      toolbar="fontselect fontsizeselect forecolor backcolor | underline | bold italic strikethrough alignleft aligncenter alignright"
+    />
+    <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"></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']">
+              <div 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>
+            </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 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>
+    <div class="btn-box" style="text-align: right">
+      <el-button @click="sureNewWords">保存</el-button>
+    </div>
+  </div>
+</template>
+
+<script>
+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 SelectUpload from '@/views/book/courseware/create/components/common/SelectUpload.vue';
+import { GetStaticResources } from '@/api/app';
+import RichText from '@/components/RichText.vue';
+
+export default {
+  name: 'NewWordPage',
+  props: ['dataNewWord'],
+  components: {
+    SelectUpload,
+    SoundRecord,
+    UploadAudio,
+    RichText,
+  },
+  data() {
+    return {
+      data: JSON.parse(JSON.stringify(this.dataNewWord)),
+    };
+  },
+  methods: {
+    getOption() {
+      return {
+        number: '',
+        new_word: '',
+        cixing: '', // 词性
+        definition_list: '', // 需要增加词性
+        pinyin: '',
+        mp3_list: '',
+        collocation: '', // 搭配
+        liju_list: '', // 例句
+      };
+    },
+    /**
+     * 解析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 = '';
+    },
+    // 自动生成音频
+    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(this.getOption());
+    },
+    sureNewWords() {
+      this.$emit('sureNewWords', this.data);
+    },
+  },
+};
+</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>

+ 134 - 0
src/views/book/courseware/create/components/question/article/Notes.vue

@@ -0,0 +1,134 @@
+<template>
+  <div>
+    <label>标题:</label>
+    <RichText
+      v-model="data.title_con"
+      :inline="true"
+      :placeholder="'输入标题'"
+      toolbar="fontselect fontsizeselect forecolor backcolor | underline | bold italic strikethrough alignleft aligncenter alignright"
+    />
+    <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="con" label="内容" width="200">
+        <template slot-scope="scope">
+          <RichText
+            v-model="scope.row.con"
+            :inline="true"
+            toolbar="fontselect fontsizeselect forecolor backcolor | underline | bold italic strikethrough alignleft aligncenter alignright"
+          />
+        </template>
+      </el-table-column>
+      <el-table-column prop="interpret" label="翻译" width="200">
+        <template slot-scope="scope">
+          <RichText
+            v-model="scope.row.interpret"
+            :inline="true"
+            toolbar="fontselect fontsizeselect forecolor backcolor | underline | bold italic strikethrough alignleft aligncenter alignright"
+          />
+        </template>
+      </el-table-column>
+      <el-table-column prop="note" label="注释">
+        <template slot-scope="scope">
+          <RichText
+            v-model="scope.row.note"
+            :inline="true"
+            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>
+  </div>
+</template>
+
+<script>
+import RichText from '@/components/RichText.vue';
+export default {
+  name: 'NotesPage',
+  props: ['dataNotes'],
+  components: {
+    RichText,
+  },
+  data() {
+    return {
+      data: JSON.parse(JSON.stringify(this.dataNotes)),
+    };
+  },
+  methods: {
+    // 删除行
+    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(this.getOption());
+    },
+    getOption() {
+      return {
+        number: '',
+        con: '',
+        interpret: '', //翻译
+        note: '', //注释
+        img_list: [],
+      };
+    },
+  },
+};
+</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 !important;
+}
+</style>