Просмотр исходного кода

图片文本融合组件添加字幕、拼音、样式校对

natasha 6 дней назад
Родитель
Сommit
bd4da31a8e

+ 225 - 0
src/views/book/courseware/create/components/question/image_text/CheckArticle.vue

@@ -0,0 +1,225 @@
+<template>
+  <div class="check-article">
+    <div class="main">
+      <div class="main-top">
+        <b>校对</b>
+        <div class="btn-box">
+          <el-button @click="showPinyinFlag = true">校对拼音</el-button>
+          <el-button @click="showStyleFlag = true">校对样式</el-button>
+        </div>
+      </div>
+
+      <div class="sentence-box">
+        <div
+          v-for="(items, indexs) in data"
+          :key="indexs + 'words'"
+          class="sentence"
+          :style="{
+            fontFamily: items.fontFamily,
+          }"
+          v-html="items.wordsName ? items.wordsName : items.onebest"
+        ></div>
+      </div>
+    </div>
+
+    <el-dialog
+      v-if="showPinyinFlag"
+      :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"
+    >
+      <CheckPinyin :data="data" @savePinyin="savePinyin" />
+    </el-dialog>
+    <el-dialog
+      v-if="showStyleFlag"
+      :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"
+    >
+      <CheckStyle :data="data" @saveStyle="saveStyle" />
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import CheckPinyin from './CheckPinyin.vue';
+import CheckStyle from './CheckStyle.vue';
+import RichText from '@/components/RichText.vue';
+import { fileUpload } from '@/api/app';
+
+export default {
+  components: {
+    CheckPinyin,
+    CheckStyle,
+    RichText,
+  },
+  props: ['data'],
+  data() {
+    return {
+      // ArticelData: JSON.parse(JSON.stringify(this.data)),
+      showWordFlag: false,
+      showPinyinFlag: false,
+      showStyleFlag: false,
+      remarkVisible: false,
+      remark: null,
+      activeItem: null,
+    };
+  },
+  // 生命周期 - 创建完成(可以访问当前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(wordIndex, pinyin) {
+      this.$emit('savePinyin', wordIndex, pinyin);
+    },
+    saveStyle(wordIndex, fontFamily) {
+      this.$emit(
+        'saveStyle',
+
+        wordIndex,
+        fontFamily,
+      );
+    },
+  },
+};
+</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;
+      }
+    }
+
+    .sentence-box {
+      display: flex;
+      flex-flow: wrap;
+
+      .sentence {
+        margin-top: 8px;
+      }
+    }
+  }
+
+  .set-para {
+    margin-top: 8px;
+    text-align: left;
+  }
+}
+
+.remark-box {
+  display: flex;
+  gap: 8px;
+  align-items: center;
+  justify-content: center;
+  width: fit-content;
+  height: 24px;
+  padding: 2px 12px;
+  margin: 16px 0 0;
+  font-size: 12px;
+  font-weight: 400;
+  line-height: 20px; /* 166.667% */
+  color: #165dff;
+  cursor: pointer;
+  border: 1px solid #165dff;
+  border-radius: 2px;
+}
+
+.upload-resource {
+  text-align: left;
+}
+</style>

+ 476 - 0
src/views/book/courseware/create/components/question/image_text/CheckPinyin.vue

@@ -0,0 +1,476 @@
+<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 v-for="(items, indexs) in data" :key="indexs + 'words'" class="sentence-box">
+          <div
+            class="sentence"
+            :style="{
+              marginRight: items.marginRight ? '8px' : '',
+              color: activeIndex === indexs ? '#F2555A' : '',
+            }"
+            @click="selectItem(items, indexs)"
+          >
+            <span class="pinyin">{{ items.pinyin }}</span>
+            <span
+              class="words"
+              :style="{
+                fontFamily: items.fontFamily,
+              }"
+            >
+              {{ items.wordsName ? items.wordsName : items.onebest }}
+            </span>
+          </div>
+        </div>
+      </div>
+    </div>
+    <el-dialog
+      v-if="dialogFlag"
+      :visible.sync="dialogFlag"
+      :show-close="false"
+      :close-on-click-modal="false"
+      :modal-append-to-body="false"
+      :modal="false"
+      width="250px"
+      class="login-dialog"
+    >
+      <div class="check-box">
+        <div class="content">
+          <div class="words-box">
+            <span class="pinyin">
+              {{ itemActive.pinyin }}
+            </span>
+            <span class="words">
+              {{ itemActive.wordsName ? itemActive.wordsName : itemActive.onebest }}
+            </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 {
+  components: {},
+  props: ['data'],
+  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'],
+        ['n', 'ń', 'ň', 'ǹ', 'n'],
+        ['m̄', 'ḿ', 'm', 'm̀', 'm'],
+      ],
+      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_first_sentence_first_hz_pinyin_first_char_upper_case === 'true' && indexss === 0
+                  ? itemss.pinyin_up
+                  : itemss.pinyin
+                : itemss.pinyin_tone;
+            let obj = {
+              text: itemss.chs,
+              pinyin,
+              paraIndex: item.paraIndex,
+              sentenceIndex: indexs,
+              wordIndex: indexss,
+              marginRight: true,
+              saveIndex,
+            };
+            arr[index].push(obj);
+            let saveObj = {
+              word: itemss.chs,
+              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;
+      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[Number(item)],
+                },
+              ]);
+            } 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.activeIndex].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,
+              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', 'n', 'm'];
+      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.activeIndex, 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 {
+      display: flex;
+      flex-flow: wrap;
+      padding: 8px;
+      background: #fcfcfc;
+      border: 1px solid rgba(0, 0, 0, 8%);
+      border-radius: 2px;
+    }
+
+    .sentence-box {
+      .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>

+ 501 - 0
src/views/book/courseware/create/components/question/image_text/CheckStyle.vue

@@ -0,0 +1,501 @@
+<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 v-for="(items, indexs) in data" :key="indexs + 'words'" class="sentence-box">
+          <div
+            class="sentence"
+            :style="{
+              marginRight: '8px',
+              color: activeIndex === indexs ? '#F2555A' : '',
+            }"
+            @click="selectItem(items, indexs)"
+          >
+            <span
+              class="words"
+              :style="{
+                fontFamily: items.fontFamily,
+              }"
+            >
+              {{ items.wordsName ? items.wordsName : items.onebest }}
+            </span>
+          </div>
+        </div>
+      </div>
+    </div>
+    <el-dialog
+      v-if="dialogFlag"
+      :visible.sync="dialogFlag"
+      :show-close="false"
+      :close-on-click-modal="false"
+      :modal-append-to-body="false"
+      :modal="false"
+      width="400px"
+      class="login-dialog"
+    >
+      <div class="check-box">
+        <div class="content">
+          <div class="words-box">
+            <span
+              class="words"
+              :style="{
+                fontFamily: itemActive.fontFamily,
+              }"
+            >
+              {{ itemActive.wordsName ? itemActive.wordsName : itemActive.onebest }}
+            </span>
+          </div>
+        </div>
+        <div ref="toolbarMenu" class="toolbar">
+          <el-select v-model="itemActive.fontFamily" placeholder="请选择">
+            <el-option v-for="item in fontFamilyList" :key="item.value" :label="item.name" :value="item.value" />
+          </el-select>
+        </div>
+
+        <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 { fileUpload } from '@/api/app';
+
+export default {
+  components: {},
+  props: ['data'],
+  data() {
+    return {
+      loading: false,
+      id: '',
+      ArticelData: null,
+      indexArr: [], // 索引数组
+      activeIndex: null,
+      itemActive: null,
+      dialogFlag: false,
+      checkPinyinInput: '',
+      oldInput: '',
+      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: {
+    onBlur(item, field) {
+      item[field] = item[field] ? item[field].trim() : '';
+    },
+    // 获取分析结果
+    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,
+              fontFamily: itemss.fontFamily,
+              textDecoration: itemss.textDecoration,
+              fontWeight: itemss.fontWeight,
+              border: itemss.border,
+              color: itemss.color,
+              matchWords: itemss.matchWords,
+              matchNotes: itemss.matchNotes,
+              img: itemss.img,
+              imgPosition: itemss.imgPosition,
+            };
+            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;
+      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.activeIndex,
+        this.itemActive.fontFamily,
+      );
+      this.$message.success('保存成功');
+      this.activeIndex = null;
+      // this.itemActive = null;
+      this.dialogFlag = false;
+    },
+    // 设置样式
+    setActiveTextStyle(type) {
+      let style = this.itemActive;
+      if (type === 'line-through') {
+        style.textDecoration = style.textDecoration === 'line-through' ? '' : 'line-through';
+        this.$set(this.itemActive, 'textDecoration', style.textDecoration);
+      } else if (type === 'bold') {
+        style.fontWeight = style.fontWeight === 'bold' ? '' : 'bold';
+        this.$set(this.itemActive, 'fontWeight', style.fontWeight);
+      } else if (type === 'underline') {
+        style.textDecoration = style.textDecoration === 'underline' ? '' : 'underline';
+        this.$set(this.itemActive, 'textDecoration', style.textDecoration);
+      } else if (type === 'border') {
+        style.border = style.border === 'dotted' ? '' : 'dotted';
+        this.$set(this.itemActive, 'border', style.border);
+      }
+    },
+    // 处理超出图片个数操作
+    handleExceed(files, fileList) {
+      this.$message.warning(
+        `当前限制选择 1 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`,
+      );
+    },
+    // 图片上传前处理
+    handleBeforeImage(file) {
+      // 判断文件是否为图片
+      if (!file.type.includes('image')) {
+        this.$message.error('请选择图片文件');
+        return false;
+      }
+    },
+    // 图片上传
+    handleImage(file) {
+      fileUpload('Mid', file, { isGlobalprogress: true }).then(({ file_info_list }) => {
+        if (file_info_list.length > 0) {
+          this.itemActive.img = file_info_list;
+          this.itemActive.img[0].name = file_info_list[0].file_name;
+        }
+      });
+    },
+  },
+};
+</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 {
+      display: flex;
+      flex-flow: wrap;
+      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;
+    }
+  }
+}
+
+.toolbar {
+  display: flex;
+  column-gap: 10px;
+  align-items: center;
+  justify-content: center;
+  margin: 10px 0;
+  cursor: pointer;
+
+  .picker-area {
+    position: relative;
+
+    :deep .el-color-picker {
+      position: absolute;
+
+      &__trigger {
+        border: none;
+      }
+
+      &__color {
+        border: none !important;
+
+        &-inner {
+          background-color: rgba(0, 0, 0, 0%) !important;
+        }
+      }
+
+      &__icon {
+        display: none !important;
+      }
+    }
+  }
+
+  .tool-item {
+    padding: 4px 6px;
+    border-radius: 8px;
+
+    &.active {
+      background: #a6ccf7;
+    }
+  }
+}
+
+.match-info {
+  display: flex;
+  align-items: center;
+  margin: 10px 0;
+
+  label {
+    width: 100px;
+  }
+}
+
+.remark-box {
+  display: flex;
+  gap: 8px;
+  align-items: center;
+  justify-content: center;
+  width: fit-content;
+  height: 24px;
+  padding: 2px 12px;
+  font-size: 12px;
+  font-weight: 400;
+  line-height: 20px; /* 166.667% */
+  color: #165dff;
+  cursor: pointer;
+  border: 1px solid #165dff;
+  border-radius: 2px;
+}
+</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;
+    }
+  }
+
+  .el-color-picker__empty {
+    display: none;
+  }
+}
+</style>

+ 78 - 2
src/views/book/courseware/create/components/question/image_text/ImageText.vue

@@ -36,8 +36,12 @@
         <SvgIcon icon-class="delete-black" size="12" @click="removeFile" />
       </div>
       <el-button v-if="data.mp3_list.length > 0" type="primary" size="small" @click="handleTimes">{{
-        data.word_time.length === 0 ? '自动生成字幕节点' : '重新生成字幕节点'
+        data.word_time.length === 0 ? '自动生成字幕时间' : '重新生成字幕时间'
       }}</el-button>
+      <template v-if="data.word_time && data.word_time.length > 0">
+        <el-button size="medium" @click="compareTime('句子')">校对句子字幕时间</el-button>
+        <el-button size="medium" @click="compareTime('文字')">校对文字字幕时间</el-button></template
+      >
 
       <el-radio-group v-model="isText" style="width: 100%; margin: 20px 0; text-align: center">
         <el-radio-button :label="true">文字框</el-radio-button>
@@ -157,6 +161,13 @@
               >编辑</el-button
             >
             <el-button v-else type="primary" size="small" @click="hotspotsActiveIndex = null">保存</el-button>
+            <el-button
+              v-if="data.word_time && data.word_time[indexh]"
+              type="primary"
+              size="small"
+              @click="handleCheckStyle(indexh)"
+              >校对</el-button
+            >
             <el-button type="danger" size="small" @click="deletehotspots(indexh)">删除</el-button>
           </li>
         </ul>
@@ -198,6 +209,26 @@
         :translations="data.multilingual"
         @SubmitTranslation="handleMultilingualTranslation"
       />
+      <el-dialog title="校对字幕时间" :visible.sync="compareShow" width="50%" :before-close="handleClose" top="0">
+        <CompareTime :data="compareData" :type="compareType" :changewords-result-list="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
+        v-if="showArticleFlag"
+        :visible.sync="showArticleFlag"
+        :show-close="true"
+        :close-on-click-modal="true"
+        :modal-append-to-body="true"
+        :append-to-body="true"
+        :lock-scroll="true"
+        width="60%"
+        class="practiceBox"
+      >
+        <CheckArticle :data="styleData" @savePinyin="savePinyin" @saveStyle="saveStyle" />
+      </el-dialog>
     </template>
   </ModuleBase>
 </template>
@@ -207,6 +238,8 @@ import ModuleMixin from '../../common/ModuleMixin';
 import UploadFile from '../../base/common/UploadFile.vue';
 import { getImageTextData, isEnable } from '@/views/book/courseware/data/imageText';
 import SelectUpload from '@/views/book/courseware/create/components/common/SelectUpload.vue';
+import CompareTime from '../article/CompareTime.vue';
+import CheckArticle from './CheckArticle.vue';
 
 import { GetFileURLMap } from '@/api/app';
 import { fileToBase64Text, prepareTranscribe, getWordTime, getWordTimes } from '@/api/article';
@@ -214,7 +247,7 @@ import cnchar from 'cnchar';
 
 export default {
   name: 'ImageTextPage',
-  components: { UploadFile, SelectUpload },
+  components: { UploadFile, SelectUpload, CompareTime, CheckArticle },
   mixins: [ModuleMixin],
   data() {
     return {
@@ -233,6 +266,13 @@ export default {
       inputActiveIndex: null, // 当前编辑输入框热区索引
       loading: false,
       multilingualText: '',
+      compareType: '', // 校对类型
+      compareShow: false,
+      compareData: null,
+      compareloading: false,
+      showArticleFlag: false, // 校对
+      showStyleFlag: false,
+      styleData: null,
     };
   },
   watch: {
@@ -475,6 +515,42 @@ export default {
         });
       }
     },
+    // 校对时间
+    compareTime(type) {
+      this.compareType = type;
+      this.compareData = JSON.parse(JSON.stringify(this.data.word_time));
+      this.compareShow = true;
+    },
+    handleClose() {
+      this.compareShow = false;
+      this.compareData = null;
+      this.compareType = '';
+    },
+    // 校对每个字的时间
+    changewordsResultList(index, item) {
+      this.data.word_time[index].wordsResultList = JSON.parse(JSON.stringify(item));
+    },
+    // 保存校对
+    saveCompare() {
+      this.compareloading = false;
+      this.data.word_time = this.compareData;
+      this.handleClose();
+    },
+    saveStyle(wordIndex, fontFamily) {
+      this.styleData[wordIndex].fontFamily = fontFamily;
+    },
+    // 保存拼音
+    savePinyin(wordIndex, pinyin) {
+      if (this.data.property.is_first_sentence_first_hz_pinyin_first_char_upper_case === 'true') {
+        this.styleData[wordIndex].pinyin_up = pinyin;
+      } else {
+        this.styleData[wordIndex].pinyin = pinyin;
+      }
+    },
+    handleCheckStyle(index) {
+      this.showArticleFlag = true;
+      this.styleData = this.data.word_time[index].wordsResultList;
+    },
   },
 };
 </script>

+ 28 - 8
src/views/book/courseware/preview/components/image_text/components/MagazineSentence.vue

@@ -61,7 +61,12 @@
                 >
                 <span class="NNPE-chs">
                   <template>
-                    <span>{{ itemC.wordsName }}</span>
+                    <span
+                      :style="{
+                        fontFamily: itemC.fontFamily,
+                      }"
+                      >{{ itemC.wordsName }}</span
+                    >
                   </template>
                 </span>
                 <span
@@ -90,9 +95,14 @@
                       : data[sentenceActive].wordsResultList[indexC + 1].pinyin
                   }}</span
                 >
-                <span class="NNPE-chs" style="text-align: left">{{
-                  data[sentenceActive].wordsResultList[indexC + 1].wordsName
-                }}</span>
+                <span
+                  class="NNPE-chs"
+                  style="text-align: left"
+                  :style="{
+                    fontFamily: data[sentenceActive].wordsResultList[indexC + 1].fontFamily,
+                  }"
+                  >{{ data[sentenceActive].wordsResultList[indexC + 1].wordsName }}</span
+                >
                 <span
                   v-if="property.pinyin_position == 'bottom' && property.view_pinyin === 'true'"
                   :class="[
@@ -130,9 +140,14 @@
                       : data[sentenceActive].wordsResultList[indexC + 2].pinyin
                   }}</span
                 >
-                <span class="NNPE-chs" style="text-align: left">{{
-                  data[sentenceActive].wordsResultList[indexC + 2].wordsName
-                }}</span>
+                <span
+                  class="NNPE-chs"
+                  style="text-align: left"
+                  :style="{
+                    fontFamily: data[sentenceActive].wordsResultList[indexC + 2].fontFamily,
+                  }"
+                  >{{ data[sentenceActive].wordsResultList[indexC + 2].wordsName }}</span
+                >
                 <span
                   v-if="property.pinyin_position == 'bottom' && property.view_pinyin === 'true'"
                   :class="[
@@ -167,7 +182,12 @@
                 >
                 <span class="NNPE-chs">
                   <template>
-                    <span>{{ itemC.wordsName }}</span>
+                    <span
+                      :style="{
+                        fontFamily: itemC.fontFamily,
+                      }"
+                      >{{ itemC.wordsName }}</span
+                    >
                   </template>
                 </span>
                 <span