Bläddra i källkod

预览多语言放到右侧边栏

选词翻译
zq 11 timmar sedan
förälder
incheckning
dd77160c99

+ 8 - 0
src/api/book.js

@@ -8,6 +8,14 @@ export function CrateParsedTextInfo_Pinyin(data) {
 }
 
 /**
+ * @description 文本翻译
+ * @param {object} data
+ */
+export function Texttrans(data) {
+  return http.post(`${process.env.VUE_APP_EepServer}?MethodName=tool-Texttrans`, data);
+}
+
+/**
  * @description 得到教材章节结构
  * @param {object} data
  */

+ 201 - 0
src/components/TranslateDialog.vue

@@ -0,0 +1,201 @@
+<template>
+  <el-dialog :title="titleText" :visible.sync="visible" width="600px" :close-on-click-modal="false"
+    @close="dialogClose()">
+    <div class="translate-container">
+      <div class="translate-section">
+        <div class="section-label">原文:</div>
+        <div class="text-content original-text">{{ initText }}</div>
+      </div>
+
+      <div class="translate-section lang-selector">
+        <span class="section-label">目标语种:</span>
+        <el-select v-model="lang" placeholder="请选择语言" size="mini" class="lang-select" @change="handleLangChange">
+          <el-option v-for="item in langList" :key="item.type" :label="item.name" :value="item.type" />
+        </el-select>
+      </div>
+
+      <div class="translate-section">
+        <div class="section-label">译文:</div>
+        <div class="text-content translated-text" :style="{ lineHeight: lang === 'ZH' ? '1' : '1.5' }">
+          {{ translateResultText }}
+        </div>
+      </div>
+    </div>
+
+    <div slot="footer" class="dialog-footer">
+      <el-button @click="dialogClose">关闭</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { GetLanguageTypeList, Texttrans } from '@/api/book';
+
+export default {
+  name: 'TranslateDialog',
+  components: {},
+  props: {
+    open: {
+      type: Boolean,
+      default: false,
+      required: true,
+    },
+    initText: {
+      type: String,
+      default: '',
+    },
+    titleText: {
+      type: String,
+      default: '翻译',
+    },
+    bookId: {
+      type: String,
+      default: '',
+    },
+  },
+  data() {
+    return {
+      visible: false,
+      lang: 'ZH',
+      langList: [],
+      translateResultList: [],
+    };
+  },
+  computed: {
+    // 计算最终显示的译文字符串
+    translateResultText() {
+      if (!this.translateResultList || this.translateResultList.length === 0) {
+        return '';
+      }
+      let resultText = this.translateResultList.map((item) => item.dst);
+      return resultText.join('\n');
+    },
+  },
+  watch: {
+    open(newVal) {
+      this.visible = newVal;
+      if (newVal) {
+        this.getLangList();
+        this.fetchTranslation();
+      }
+    },
+    visible(newVal) {
+      if (!newVal) {
+        this.$emit('update:open', false);
+        this.translateResultList = [];
+        this.lang = 'ZH';
+      }
+    },
+  },
+  methods: {
+    dialogClose() {
+      this.visible = false;
+    },
+
+    /**
+     * 获取语言列表
+     */
+    getLangList() {
+      if (!this.bookId) return;
+      GetLanguageTypeList({ book_id: this.bookId, is_contain_zh: 'true' }).then(({ language_type_list }) => {
+        this.langList = language_type_list;
+      });
+    },
+
+    /**
+     * 语种改变时触发
+     */
+    handleLangChange(val) {
+      if (!this.initText) {
+        this.$message.warning('暂无可翻译内容');
+        return;
+      }
+      this.fetchTranslation();
+    },
+
+    /**
+     * 调用翻译接口
+     */
+    fetchTranslation() {
+      this.translateResultList = [];
+      const params = {
+        text: this.initText,
+        from: 'zh',
+        to: this.lang,
+      };
+      Texttrans(params)
+        .then((res) => {
+          if (res.status === 1) {
+            this.translateResultList = res.trans_list || [];
+          }
+        })
+        .catch(() => {
+          this.$message.error('翻译失败');
+        });
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.translate-container {
+  padding: 10px;
+
+  .translate-section {
+    margin-bottom: 15px;
+
+    .section-label {
+      display: block;
+      margin-bottom: 8px;
+      font-size: 14px;
+      font-weight: bold;
+      color: #606266;
+    }
+
+    // 核心样式:保留换行符,自动换行
+    .text-content {
+      min-height: 40px;
+      max-height: 300px;
+      padding: 10px;
+      overflow-y: auto;
+      font-size: 14px;
+      line-height: 1;
+      color: #303133;
+      word-break: break-all;
+      white-space: pre-wrap;
+
+      /* 关键:保留空格和换行符 */
+      background-color: #f5f7fa;
+      border: 1px solid #e4e7ed;
+      border-radius: 4px;
+    }
+
+    .translated-text {
+      line-height: 1.5;
+    }
+
+    &.lang-selector {
+      display: flex;
+      align-items: center;
+
+      .section-label {
+        margin-right: 10px;
+        margin-bottom: 0;
+      }
+    }
+  }
+}
+
+.el-dialog {
+  :deep &__body {
+    display: flex;
+    flex-direction: column;
+    row-gap: 8px;
+    padding: 10px;
+
+    .el-textarea__inner {
+      height: 108px;
+    }
+  }
+}
+</style>

+ 10 - 0
src/courseware_preview/index.vue

@@ -54,6 +54,7 @@
             @computeScroll="computeScroll"
             @editNote="handEditNote"
             @saveCollect="saveCollect"
+            @getTranslate="getTranslate"
           />
           <div class="preview-right"></div>
         </main>
@@ -89,6 +90,7 @@
       @confirm="saveNote"
       @cancel="delNote"
     />
+    <TranslateDialog :open.sync="showTranslate" :init-text="translateText" :book-id="projectId" title-text="翻译" />
   </div>
 </template>
 
@@ -97,6 +99,7 @@ import CoursewarePreview from '@/views/book/courseware/preview/CoursewarePreview
 import { isTrue } from '@/utils/validate';
 import MindMap from '@/components/MindMap.vue';
 import ExplanatoryNoteDialog from '@/components/ExplanatoryNoteDialog.vue';
+import TranslateDialog from '@/components/TranslateDialog.vue';
 import VisNetwork from '@/components/VisNetwork.vue';
 import * as OpenCC from 'opencc-js';
 
@@ -129,6 +132,7 @@ export default {
     MindMap,
     ExplanatoryNoteDialog,
     VisNetwork,
+    TranslateDialog,
   },
   provide() {
     return {
@@ -245,6 +249,8 @@ export default {
       },
       allNoteList: [],
       editDialogOpen: false,
+      showTranslate: false,
+      translateText: '',
       oldRichData: {},
       newSelectedInfo: null,
       allCottectList: [],
@@ -1027,6 +1033,10 @@ export default {
         })
         .catch(() => {});
     },
+    async getTranslate(info) {
+      this.showTranslate = true;
+      this.translateText = info.text;
+    },
     getSearch(params) {
       if (params && params.type) this.drawerType = Number(params.type);
     },

+ 13 - 0
src/views/book/courseware/preview/CoursewarePreview.vue

@@ -73,6 +73,8 @@
       <span class="button" @click="setNote"><SvgIcon icon-class="sidebar-text" size="14" /> 笔记</span>
       <span class="line"></span>
       <span class="button" @click="setCollect"><SvgIcon icon-class="sidebar-collect" size="14" /> 收藏 </span>
+      <span class="line"></span>
+        <span class="button" @click="setTranslate"><img style="width: 14px; height: 14px;" :src="require('@/assets/icon/sidebar-translate.png')" /> 翻译 </span>
     </div>
   </div>
 </template>
@@ -601,6 +603,17 @@ export default {
       this.$emit('saveCollect', info);
       this.selectedInfo = null;
     },
+    // 翻译
+    setTranslate() {
+      this.showToolbar = false;
+
+      let info = this.selectHandleInfo;
+      if (!info) return;
+      info.coursewareId = this.courseware_id;
+
+      this.$emit('getTranslate', info);
+      this.selectedInfo = null;
+    },
     // 定位
     handleLocation(item) {
       this.scrollToDataId(item.blockId);

+ 20 - 3
src/web_preview/index.vue

@@ -315,6 +315,15 @@
               </li>
             </ul>
           </div>
+          <div v-if="curToolbarIcon === 'translate'" class="resource_box">
+            <h5>{{ drawerTitle }}</h5>
+            <div style="height: 40px"></div>
+            <div style="padding: 10px">
+              <el-select v-model="lang" placeholder="请选择语言" size="mini" class="lang-select">
+                <el-option v-for="item in langList" :key="item.type" :label="item.name" :value="item.type" />
+              </el-select>
+            </div>
+          </div>
           <template v-if="curToolbarIcon === 'audit'">
             <AuditRemark :remark-list="remark_list" :is-audit="isShowAudit" @deleteRemarks="deleteRemarks" />
           </template>
@@ -444,7 +453,7 @@ export default {
       },
       { icon: 'collect', title: '收藏', handle: 'getCollect', param: { type: '11' } },
       { icon: 'note', title: '笔记', handle: 'getNote', param: { type: '12' } },
-      { icon: 'translate', title: '翻译', handle: 'openTranslate', param: {} },
+      { icon: 'translate', title: '翻译', handle: 'openTranslate', param: { type: '21' } },
       { icon: 'setting', title: '设置', handle: '', param: {} },
     ];
 
@@ -530,6 +539,8 @@ export default {
       },
       allNoteList: [],
       editDialogOpen: false,
+      showTranslate: false,
+      translateText: '',
       oldRichData: {},
       newSelectedInfo: null,
       allCottectList: [],
@@ -567,6 +578,7 @@ export default {
         11: '收藏列表',
         12: '笔记列表',
         13: '搜索结果',
+        21: '多语言',
       };
       return titleMap[this.drawerType] || '资源列表';
     },
@@ -862,8 +874,9 @@ export default {
     /**
      * 打开选择语言弹窗
      */
-    openTranslate() {
-      this.visibleTranslate = true;
+    openTranslate(params) {
+      // this.visibleTranslate = true;
+      if (params && params.type) this.drawerType = Number(params.type);
     },
     // 计算抽屉滑出位置
     calcDrawerPosition() {
@@ -1333,6 +1346,10 @@ export default {
         })
         .catch(() => {});
     },
+    async getTranslate(info) {
+      this.showTranslate = true;
+      this.translateText = info.text;
+    },
     getSearch(params) {
       if (params && params.type) this.drawerType = Number(params.type);
     },