Browse Source

多语言译文弹框

dsy 1 week ago
parent
commit
080eb70920

+ 247 - 0
src/views/book/components/MultilingualFill.vue

@@ -0,0 +1,247 @@
+<template>
+  <el-dialog
+    :visible="visible"
+    width="800px"
+    custom-class="multilingual-fill-dialog"
+    :close-on-click-modal="false"
+    @close="closeDialog"
+  >
+    <div class="multilingual-fill">
+      <div class="left-menu">
+        <span class="title">多语言</span>
+        <ul class="lang-list">
+          <li
+            v-for="{ code } in selectedLangList"
+            :key="code"
+            :class="['lang-item', { active: curLang === code }]"
+            @click="curLang = code"
+          >
+            {{ langList.find((item) => item.code === code).name }}
+          </li>
+          <li class="lang-item" @click="showAddLang">
+            <i class="el-icon-plus"></i>
+            <span>增加语种</span>
+          </li>
+        </ul>
+      </div>
+
+      <div class="right-content">
+        <div class="operator">
+          <div class="operator-left">
+            <span class="btn" @click="isShowOriginal = !isShowOriginal">
+              <i v-show="!isShowOriginal" class="el-icon-view"></i>
+              <SvgIcon v-show="isShowOriginal" :size="12" icon-class="eye-invisible" />
+              <span>{{ isShowOriginal ? '隐藏原文' : '显示原文' }}</span>
+            </span>
+            <span class="btn primary" @click="submitTranslation">
+              <span>提交译文</span>
+            </span>
+          </div>
+          <i class="el-icon-close" @click="closeDialog"></i>
+        </div>
+        <div class="content">
+          <el-input v-show="isShowOriginal" :value="text" type="textarea" :rows="27" resize="none" :readonly="true" />
+          <el-input
+            v-for="lang in selectedLangList"
+            v-show="curLang === lang.code"
+            :key="lang.code"
+            v-model="lang.translation"
+            type="textarea"
+            :rows="27"
+            resize="none"
+            placeholder="输入译文"
+          />
+        </div>
+      </div>
+    </div>
+
+    <UpdateLang :visible.sync="langVisible" :selected-langs="selectedLangList" @update-langs="handleUpdateLangs" />
+  </el-dialog>
+</template>
+
+<script>
+import UpdateLang from './UpdateLang.vue';
+
+import { langList } from '@/views/book/courseware/data/common';
+
+export default {
+  name: 'MultilingualFill',
+  components: {
+    UpdateLang,
+  },
+  props: {
+    visible: {
+      type: Boolean,
+      required: true,
+    },
+    text: {
+      type: String,
+      default: '',
+    },
+    translations: {
+      type: Array,
+      default: () => [],
+    },
+  },
+  data() {
+    return {
+      langList,
+      selectedLangList: [
+        { code: 'en', translation: '' },
+        { code: 'fr', translation: '' },
+        { code: 'de', translation: '' },
+        { code: 'es', translation: '' },
+        { code: 'it', translation: '' },
+        { code: 'pt', translation: '' },
+        { code: 'ko', translation: '' },
+        { code: 'ja', translation: '' },
+      ],
+      noSelectedLangList: ['ru', 'ar', 'tr', 'nl', 'pl', 'sv', 'el'],
+      curLang: 'en',
+      isShowOriginal: true, // 是否显示原文
+      langVisible: false,
+    };
+  },
+  watch: {
+    translations: {
+      handler(newVal) {
+        if (!newVal || !Array.isArray(newVal) || newVal.length === 0) return;
+        this.selectedLangList = newVal.map(({ code, translation }) => ({
+          code,
+          translation,
+        }));
+      },
+      immediate: true,
+    },
+  },
+  methods: {
+    closeDialog() {
+      this.$emit('update:visible', false);
+    },
+    showAddLang() {
+      this.langVisible = true;
+    },
+    /**
+     * 处理语言更新
+     * @param {Array} langs
+     */
+    handleUpdateLangs(langs) {
+      const newLangs = langs.filter((item) => !this.selectedLangList.map((i) => i.code).includes(item));
+      const removedLangs = this.selectedLangList.map((i) => i.code).filter((item) => !langs.includes(item));
+
+      this.selectedLangList = [
+        ...this.selectedLangList.filter((item) => !removedLangs.includes(item.code)),
+        ...newLangs.map((item) => ({ code: item, translation: '' })),
+      ];
+    },
+    submitTranslation() {
+      this.$emit('submit-translation', this.selectedLangList);
+      this.closeDialog();
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+@use 'sass:color';
+
+.el-dialog__wrapper {
+  :deep .multilingual-fill-dialog {
+    > .el-dialog__header {
+      display: none;
+    }
+
+    > .el-dialog__body {
+      height: 650px;
+      padding: 12px 16px;
+    }
+  }
+}
+
+.multilingual-fill {
+  display: flex;
+  column-gap: 8px;
+
+  .left-menu {
+    width: 90px;
+
+    .title {
+      font-size: 16px;
+      font-weight: bold;
+      color: #333;
+    }
+
+    .lang-list {
+      display: flex;
+      flex-direction: column;
+      row-gap: 8px;
+      height: 585px;
+      margin-top: 12px;
+      overflow: auto;
+
+      .lang-item {
+        padding: 4px 8px;
+        text-align: center;
+        cursor: pointer;
+        background-color: #f5f5f5;
+        border-radius: 4px;
+
+        &:hover {
+          background-color: #e0e0e0;
+        }
+
+        &.active {
+          color: #fff;
+          background-color: $main-color;
+        }
+      }
+    }
+  }
+
+  .right-content {
+    flex: 1;
+
+    .operator {
+      display: flex;
+      justify-content: space-between;
+      margin-bottom: 8px;
+
+      &-left {
+        display: flex;
+        column-gap: 8px;
+        align-items: center;
+
+        .btn {
+          display: flex;
+          column-gap: 4px;
+          align-items: center;
+          padding: 6px 12px;
+          font-size: 12px;
+          cursor: pointer;
+          background-color: #f5f5f5;
+          border-radius: 4px;
+
+          &.primary {
+            color: #fff;
+            background-color: $main-color;
+
+            &:hover {
+              background-color: color.adjust($main-color, $lightness: -5%);
+            }
+          }
+        }
+      }
+
+      i {
+        font-weight: bold;
+        cursor: pointer;
+      }
+    }
+
+    .content {
+      display: flex;
+      column-gap: 8px;
+    }
+  }
+}
+</style>

+ 67 - 0
src/views/book/components/UpdateLang.vue

@@ -0,0 +1,67 @@
+<template>
+  <el-dialog
+    :visible="visible"
+    width="610px"
+    :close-on-click-modal="false"
+    :append-to-body="true"
+    title="增加语言"
+    @close="dialogLangClose"
+  >
+    <el-transfer
+      v-model="langs"
+      :titles="['可选', '已选择']"
+      :props="{ key: 'code', label: 'name' }"
+      :data="langList"
+    />
+
+    <div slot="footer">
+      <el-button @click="dialogLangClose">取消</el-button>
+      <el-button type="primary" @click="updateLangs">确定</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { langList } from '@/views/book/courseware/data/common';
+
+export default {
+  name: 'UpdateLang',
+  props: {
+    visible: {
+      type: Boolean,
+      required: true,
+    },
+    selectedLangs: {
+      type: Array,
+      default: () => [],
+    },
+  },
+  data() {
+    return {
+      langList: langList.map((item) => ({ ...item, disabled: false })),
+      langs: [],
+    };
+  },
+  watch: {
+    selectedLangs: {
+      handler(val) {
+        if (!val || !Array.isArray(val)) return;
+
+        this.langs = val.map((item) => item.code);
+      },
+      immediate: true,
+    },
+  },
+  methods: {
+    dialogLangClose() {
+      this.$emit('update:visible', false);
+    },
+    updateLangs() {
+      this.$emit('update-langs', this.langs);
+      this.dialogLangClose();
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped></style>

+ 11 - 1
src/views/book/courseware/create/components/question/fill/Fill.vue

@@ -39,7 +39,12 @@
             <SoundRecord v-else :wav-blob.sync="data.audio_file_id" />
           </div>
         </template>
-        <el-button @click="identifyText">识别</el-button>
+
+        <div>
+          <el-button @click="identifyText">识别</el-button>
+          <el-button @click="multilingualVisible = true">多语言</el-button>
+        </div>
+
         <div class="correct-answer">
           <el-input
             v-for="(item, i) in data.answer.answer_list.filter(({ type }) => type === 'any_one')"
@@ -51,6 +56,8 @@
           </el-input>
         </div>
       </div>
+
+      <MultilingualFill :visible.sync="multilingualVisible" :text="data.content" />
     </template>
   </ModuleBase>
 </template>
@@ -59,6 +66,7 @@
 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 MultilingualFill from '@/views/book/components/MultilingualFill.vue';
 
 import { getFillData, arrangeTypeList, fillFontList, fillTypeList } from '@/views/book/courseware/data/fill';
 import { addTone, handleToneValue } from '@/views/book/courseware/data/common';
@@ -70,12 +78,14 @@ export default {
   components: {
     SoundRecord,
     UploadAudio,
+    MultilingualFill,
   },
   mixins: [ModuleMixin],
   data() {
     return {
       data: getFillData(),
       fillTypeList,
+      multilingualVisible: false,
     };
   },
   watch: {

+ 18 - 0
src/views/book/courseware/data/common.js

@@ -184,3 +184,21 @@ export const reversedComputeOptionMethods = {
   [serialNumberTypeList[2].value]: (i) => i.charCodeAt(0) - 97 + 1, // 小写
   [serialNumberTypeList[3].value]: (i) => i.charCodeAt(0) - 65 + 1,
 };
+
+export const langList = [
+  { name: '英语', code: 'en' },
+  { name: '法语', code: 'fr' },
+  { name: '德语', code: 'de' },
+  { name: '意大利语', code: 'it' },
+  { name: '西班牙语', code: 'es' },
+  { name: '葡萄牙语', code: 'pt' },
+  { name: '韩语', code: 'ko' },
+  { name: '日语', code: 'ja' },
+  { name: '俄语', code: 'ru' },
+  { name: '阿拉伯语', code: 'ar' },
+  { name: '土耳其语', code: 'tr' },
+  { name: '荷兰语', code: 'nl' },
+  { name: '波兰语', code: 'pl' },
+  { name: '瑞典语', code: 'sv' },
+  { name: '希腊语', code: 'el' },
+];

+ 12 - 8
src/views/book/courseware/preview/components/newWord_template/components/EditDiv.vue

@@ -284,28 +284,32 @@ export default {
 <style lang="scss" rel="stylesheet/scss">
 .edit-div {
   width: 100%;
+
   // height: 100%; 不可加
   overflow: auto;
+  text-align: left;
   word-break: break-all;
-  outline: none;
-  user-select: text;
   white-space: pre-wrap;
-  text-align: left;
+  user-select: text;
+  outline: none;
+
   &[contenteditable='true'] {
     user-modify: read-write-plaintext-only;
-    &:empty:before {
-      content: attr(placeholder);
+
+    &:empty::before {
       display: block;
       color: #ccc;
+      content: attr(placeholder);
     }
   }
 }
+
 .limit {
   position: absolute;
-  right: 0px;
-  bottom: 0px;
+  right: 0;
+  bottom: 0;
   font-size: 16px;
   line-height: 24px;
-  color: rgba(0, 0, 0, 0.45);
+  color: rgba(0, 0, 0, 45%);
 }
 </style>