vue2使用wangeditor5及word导入解析的实现与问题

news2024/9/9 1:41:02

安装

wangeditor5

        官网:https://www.wangeditor.com/v5/

yarn add @wangeditor/editor
# 或者 npm install @wangeditor/editor --save

yarn add @wangeditor/editor-for-vue
# 或者 npm install @wangeditor/editor-for-vue --save

mammoth.js

        官网:https://github.com/mwilliamson/mammoth.js

npm install mammoth

        若出现依赖包下载失败的情况,可能是镜像问题,可选择使用国内镜像,参考文档:https://blog.csdn.net/hyk521/article/details/140706064

使用

        editor.vue:

<template>
  <div style="border: 1px solid #ccc;">
    <input type="file" id="weWordBtn" style="display:none;"
           accept="application/vnd.openxmlformats-officedocument.wordprocessingml.document"/>
    <Toolbar
      style="border-bottom: 1px solid #ccc"
      :editor="editor"
      :defaultConfig="toolbarConfig"
      :mode="mode"
    />
    <Editor
      :style="editorStyle"
      v-model="html"
      :defaultConfig="editorConfig"
      :mode="mode"
      @onCreated="onCreated"
      @onChange="onChange"
      @customPaste="customPaste"
    />
  </div>
</template>

<script>
  import Vue from 'vue';
  import {Boot, DomEditor} from '@wangeditor/editor';
  import {Editor, Toolbar} from '@wangeditor/editor-for-vue';
  import '@wangeditor/editor/dist/css/style.css';
  import {uploadPic} from "@/api/fileUpload/upload";
  import mammoth from "mammoth";
  import {Loading} from "element-ui";

  //自定义新菜单
  class wordImportMenu {
    constructor() {
      this.title = 'word导入';
      this.iconSvg = '<svg t="1721893685983" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12124" width="16" height="16"><path d="M563.2 1006.933333s-3.413333 0 0 0l-549.546667-102.4c-6.826667-3.413333-13.653333-10.24-13.653333-17.066666V170.666667c0-6.826667 6.826667-13.653333 13.653333-17.066667l546.133334-136.533333c3.413333 0 10.24 0 13.653333 3.413333s6.826667 6.826667 6.826667 13.653333v955.733334c0 3.413333-3.413333 10.24-6.826667 13.653333-3.413333 3.413333-6.826667 3.413333-10.24 3.413333zM34.133333 873.813333l512 95.573334V54.613333L34.133333 184.32v689.493333z" fill="" p-id="12125"></path><path d="M1006.933333 938.666667h-443.733333c-10.24 0-17.066667-6.826667-17.066667-17.066667s6.826667-17.066667 17.066667-17.066667H989.866667v-785.066666H563.2c-10.24 0-17.066667-6.826667-17.066667-17.066667s6.826667-17.066667 17.066667-17.066667h443.733333c10.24 0 17.066667 6.826667 17.066667 17.066667v819.2c0 10.24-6.826667 17.066667-17.066667 17.066667zM358.4 699.733333c-6.826667 0-13.653333-6.826667-17.066667-13.653333l-68.266666-249.173333-68.266667 249.173333c-3.413333 6.826667-6.826667 13.653333-17.066667 13.653333-6.826667 0-13.653333-3.413333-17.066666-10.24l-102.4-307.2c-3.413333-10.24 3.413333-17.066667 10.24-20.48 10.24-3.413333 17.066667 3.413333 20.48 10.24l85.333333 252.586667 71.68-252.586667c3.413333-13.653333 27.306667-13.653333 34.133333 0l71.68 252.586667 85.333334-252.586667c3.413333-10.24 13.653333-13.653333 20.48-10.24 10.24 3.413333 13.653333 13.653333 10.24 20.48l-102.4 307.2c-3.413333 6.826667-10.24 10.24-17.066667 10.24z" fill="" p-id="12126"></path><path d="M904.533333 256h-341.333333c-10.24 0-17.066667-6.826667-17.066667-17.066667s6.826667-17.066667 17.066667-17.066666h341.333333c10.24 0 17.066667 6.826667 17.066667 17.066666s-6.826667 17.066667-17.066667 17.066667zM904.533333 392.533333h-334.506666c-10.24 0-17.066667-6.826667-17.066667-17.066666s6.826667-17.066667 17.066667-17.066667h334.506666c10.24 0 17.066667 6.826667 17.066667 17.066667s-6.826667 17.066667-17.066667 17.066666zM904.533333 529.066667h-341.333333c-10.24 0-17.066667-6.826667-17.066667-17.066667s6.826667-17.066667 17.066667-17.066667h341.333333c10.24 0 17.066667 6.826667 17.066667 17.066667s-6.826667 17.066667-17.066667 17.066667zM904.533333 665.6h-341.333333c-10.24 0-17.066667-6.826667-17.066667-17.066667s6.826667-17.066667 17.066667-17.066666h341.333333c10.24 0 17.066667 6.826667 17.066667 17.066666s-6.826667 17.066667-17.066667 17.066667zM904.533333 802.133333H580.266667c-10.24 0-17.066667-6.826667-17.066667-17.066666s6.826667-17.066667 17.066667-17.066667h324.266666c10.24 0 17.066667 6.826667 17.066667 17.066667s-6.826667 17.066667-17.066667 17.066666z" fill="" p-id="12127"></path></svg>';
      this.tag = 'button';
    }

    //菜单是否需要激活(如选中加粗文本,“加粗”菜单会激活),用不到则返回 false
    isActive(editor) {
      return false;
    }

    //获取菜单执行时的 value,用不到则返回空字符串或 false
    getValue(editor) {
      return '';
    }

    //菜单是否需要禁用(如选中 H1 ,“引用”菜单被禁用),用不到则返回 false
    isDisabled(editor) {
      return false; // or true
    }

    //点击菜单时触发的函数
    exec(editor, value) {
      document.getElementById('weWordBtn').click();
    }
  }

  const wordImportConf = {
    key: 'wordImport',
    factory() {
      return new wordImportMenu();
    }
  };
  Boot.registerMenu(wordImportConf);

  export default Vue.extend({
    components: {Editor, Toolbar},
    props: {
      /* 编辑器的内容 */
      value: {
        type: String,
        default: "",
      },
      /* 高度 */
      height: {
        type: Number,
        default: 500,
      },
      /* 是否只读 */
      readOnly: {
        type: Boolean,
        default: false
      },
      /* 编辑器内提示语 */
      placeholder: {
        type: String,
        default: '请输入内容...'
      }
    },
    data() {
      return {
        editor: null,
        html: '',
        toolbarConfig: {
          modalAppendToBody: false,
          toolbarKeys: ['headerSelect', 'blockquote', '|', 'bold', 'underline', 'italic', 'through', 'code', 'sup', 'sub',
            'clearStyle', '|', 'color', 'bgColor', 'fontSize', 'lineHeight', '|', 'bulletedList', 'numberedList', 'todo',
            {
              'key': 'group-justify',
              'title': '对齐',
              'iconSvg': '<svg viewBox=\"0 0 1024 1024\"><path d=\"M768 793.6v102.4H51.2v-102.4h716.8z m204.8-230.4v102.4H51.2v-102.4h921.6z m-204.8-230.4v102.4H51.2v-102.4h716.8zM972.8 102.4v102.4H51.2V102.4h921.6z\"></path></svg>',
              'menuKeys': ['justifyLeft', 'justifyRight', 'justifyCenter', 'justifyJustify']
            },
            {
              'key': 'group-indent',
              'title': '缩进',
              'iconSvg': '<svg viewBox=\"0 0 1024 1024\"><path d=\"M0 64h1024v128H0z m384 192h640v128H384z m0 192h640v128H384z m0 192h640v128H384zM0 832h1024v128H0z m0-128V320l256 192z\"></path></svg>',
              'menuKeys': ['indent', 'delIndent']
            },
            '|', 'insertLink', 'uploadImage', 'insertTable', 'codeBlock', 'divider', '|', 'undo', 'redo', '|', '|', 'fullScreen'
          ],
          // excludeKeys: ['fontFamily', 'emotion', 'group-video']
          insertKeys: {
            index: 32,
            keys: ['wordImport']
          }
        },
        editorConfig: {
          placeholder: this.placeholder,
          readOnly: this.readOnly,
          autoFocus: true,
          MENU_CONF: {
            'uploadImage': {
              timeout: 300000,
              fieldName: 'files',
              maxNumberOfFiles: 10,
              allowedFileTypes: ['image/jpeg', 'image/png'],
              // allowedFileTypes: ['image/*'],
              maxFileSize: 1024 * 1024 * 5,
              server: process.env.VUE_APP_BASE_API + '/system/fileStorage/uploadPic',
              onError: (e, t, n) => {
                this.$message.error('图片上传失败:' + t);
              },
              onFailed: (e, t) => {
                this.$message.error('图片上传失败:未知错误');
              },
              onSuccess: (e, t) => {
                this.$message.success('图片上传成功');
              },
              customInsert(resp, insertFn) {
                insertFn(process.env.VUE_APP_BASE_API + resp.data.url, '', '');
              }
            }
          }
        },
        mode: 'default'
      }
    },
    computed: {
      editorStyle() {
        return 'overflow-y: hidden;height: ' + this.height + 'px;';
      }
    },
    watch: {
      value: {
        handler(val) {
          if (val !== this.html) {
            this.html = val === null ? "" : val;
          }
        },
        immediate: true,
      },
      readOnly: {
        handler(flag) {
          if (this.editor !== null) {
            if (flag) {
              this.editor.disable();
            } else {
              this.editor.enable();
            }
          }
        }
      }
    },
    methods: {
      onCreated(editor) {
        this.editor = Object.seal(editor);
        console.log('editor.getConfig()', editor.getConfig())
        console.log('editor.getAllMenuKeys()', editor.getAllMenuKeys())
        console.log('editor.getConfig().hoverbarKeys', editor.getConfig().hoverbarKeys)
        console.log('editor.getMenuConfig(uploadImage)', editor.getMenuConfig('uploadImage'))
      },
      onChange(editor) {
        console.log('toolbar.getConfig().toolbarKeys', DomEditor.getToolbar(editor).getConfig().toolbarKeys)
        console.log('editor.children ', editor.children)
        this.$emit('onChange', {editor: editor, html: editor.getHtml(), text: editor.getText()});
      },
      customPaste(editor, event, callback) {
        console.log('ClipboardEvent 粘贴事件对象', event)
        // const html = event.clipboardData.getData('text/html') // 获取粘贴的 html
        // const text = event.clipboardData.getData('text/plain') // 获取粘贴的纯文本
        // const rtf = event.clipboardData.getData('text/rtf') // 获取 rtf 数据(如从 word wsp 复制粘贴)

        // 自定义插入内容
        // editor.insertText('xxx')

        // 返回 false ,阻止默认粘贴行为
        // event.preventDefault()
        // callback(false) // 返回值(注意,vue 事件的返回值,不能用 return)

        // 返回 true ,继续默认的粘贴行为
        // callback(true)
      },
      base64ToBlob(imageType, imageBuffer) {
        let byteCharacters = atob(imageBuffer);
        let byteNumbers = new Array(byteCharacters.length);
        for (let i = 0; i < byteCharacters.length; i++) {
          byteNumbers[i] = byteCharacters.charCodeAt(i);
        }
        let byteArray = new Uint8Array(byteNumbers);
        let blob = new Blob([byteArray], {type: imageType});
        let imageName = 'e' + new Date().getTime();
        return new File([blob], imageName, {type: imageType});
      }
    },
    mounted() {
      document.getElementById("weWordBtn").addEventListener("change", (event) => {
        let requestLoading = Loading.service({
          fullscreen: true,
          text: 'word解析中......',
          spinner: 'el-icon-loading',
          background: 'rgba(217,217,217,0.2)'
        });

        let editorObj = this.editor;
        let _this = this;
        if (event.target.files && event.target.files.length > 0) {
          let file = event.target.files[0];
          mammoth.convertToHtml({arrayBuffer: file.arrayBuffer()}, {
            ignoreEmptyParagraphs: true,
            transformDocument: mammoth.transforms.paragraph((element) => {
              console.log('element', element)
              if (element.styleName === null) {
                if (element.children && element.children.length > 0) {
                  for (let i = 0; i < element.children.length; i++) {
                    let secondChild = element.children[i];
                    if (secondChild.type === 'hyperlink') {
                      secondChild.targetFrame = '_blank';
                    } else if (secondChild.type === 'run') {
                      if (secondChild.children && secondChild.children.length > 0) {
                        if (i === 0 && secondChild.children[0].type === 'text') {
                          let originVal = secondChild.children[0].value;
                          secondChild.children[0].value = '        ' + originVal;
                        }
                        if (secondChild.highlight !== null) {
                          secondChild.style = 'background-color: ' + secondChild.highlight + ';';
                          for (let j = 0; j < secondChild.children.length; j++) {
                            let thirdChild = secondChild.children[j];
                            thirdChild.style = 'background-color: ' + secondChild.highlight + ';';
                          }
                        }
                      }
                    } else {

                    }
                  }
                }
              }
              return element;
            }),
            styleMap: ["u => u"],
            convertImage: mammoth.images.imgElement(function (image) {
              return image.read('base64').then(async (imageBuffer) => {
                //本地图片上传至服务器
                let result = '';
                let imgFile = _this.base64ToBlob(image.contentType, imageBuffer);
                let formData = new FormData();
                formData.append('files', imgFile);
                await uploadPic(formData).then(resp => {
                  if (resp.code === '200') {
                    result = process.env.VUE_APP_BASE_API + resp.data.url;
                  }
                }).catch(e => {
                  console.error('uploadPic-error : ', e)
                });

                return {src: result}
              });
            })
          }).then(function (result) {
            console.log('result', result)
            if (result.messages.length > 0) {
              _this.$message.warning('发生错误:' + result.messages[0].message);
            } else {
              if (editorObj !== null) {
                editorObj.clear();
                editorObj.dangerouslyInsertHtml(result.value);
              }
            }
            requestLoading.close();
          }).catch(function (error) {
            console.error(error);
            requestLoading.close()
          });
        }
      });
    },
    beforeDestroy() {
      if (this.editor !== null) {
        this.editor.destroy();
      }
    }
  });
</script>

<style scoped>
</style>

        Test.vue:

<template>
  <div>
    <h1 style="text-align: center">editor测试</h1>
    <div style="width: 80%;margin: 0 auto;">
      <editor :value="editorHtml" :height="450" :readOnly="readOnly" @onChange="onChange"/>
      <div class="test_count">
        <span>{{editorCount}}&nbsp;字</span>
      </div>
    </div>
    <div style="text-align: center;margin-top: 25px;">
      <el-button type="primary" @click="control">{{controlText}}</el-button>
      <el-button type="primary" @click="submit">提交</el-button>
    </div>
  </div>
</template>

<script>
  import Editor from './editor';

  export default {
    name: "Test",
    components: {Editor},
    data() {
      return {
        readOnly: false,
        controlText: '禁用',
        editorHtml: '',
        editorText: ''
      }
    },
    computed: {
      editorCount() {
        return this.editorText.replace(/\s*/g, "").replace(/\n/g, "").length;
      }
    },
    mounted() {
    },
    methods: {
      onChange(data) {
        if (data.html !== this.editorHtml) {
          this.editorHtml = data.html;
          this.editorText = data.text;
        }
      },
      control() {
        this.readOnly = !this.readOnly;
        if (this.readOnly) {
          this.controlText = '启用';
        } else {
          this.controlText = '禁用';
        }
      },
      submit() {
        console.log('editorHtml', this.editorHtml)
        console.log('editorText', this.editorText)
      }
    },
  };
</script>
<style scoped>
  .test_count {
    height: 40px;
    line-height: 40px;
    text-align: right;
    padding-right: 20px;
    border: 1px solid #ccc;
    border-top: none;
  }
</style>

        页面效果:

word导入问题与解决方案

        问题:

                mammoth 仅支持简单的样式,对于背景色、颜色字体等高级样式无法支持。

        解决方案:

                1、修改 mammoth.js 的源码,参考文档:https://blog.csdn.net/Jioho_chen/article/details/124699665

                2、前端加一个按钮或触发器,后端 Java 使用 poi 解析 word 内容,具体参考:https://www.cnblogs.com/ismallboy/p/12584761.html

        若有其他方法,欢迎留言探讨。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1959322.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue2 vue3 props 的处理机制

在 Vue 2 中&#xff0c;props 是单向数据流&#xff0c;父组件向子组件传递的 props 默认情况下是不具有响应式特性的。这意味着当父组件的数据发生变化时&#xff0c;如果传递给子组件的 props 发生变化&#xff0c;子组件不会自动更新视图。 具体来说&#xff0c;在 Vue 2 …

Linux安装青龙面板并将本地服务映射至公网实现远程访问

文章目录 前言一、前期准备本教程环境为&#xff1a;Centos7&#xff0c;可以跑Docker的系统都可以使用。本教程使用Docker部署青龙&#xff0c;如何安装Docker详见&#xff1a; 二、安装青龙面板三、映射本地部署的青龙面板至公网四、使用固定公网地址访问本地部署的青龙面板 …

【Java】韩顺平Java学习笔记 第19章 IO流

文章目录 文件概述常用的文件操作创建文件获取文件信息目录的操作和文件删除流的分类各抽象类常用子类对象FileInputStreamFileOutputStreamFileReaderFileWriter 节点流和处理流概念BufferedReaderBufferedWriterBufferedInputStream & BufferedOutputStream 对象流&#…

代发考生战报:7月22号,广州,HCIP-Cloud Service SA云服务 H13-821考试通过

代发考生战报&#xff1a;7月22号&#xff0c;广州&#xff0c;HCIP-Cloud Service SA云服务 H13-821考试通过&#xff0c;遇到7-10个新题&#xff0c;剩下都是题库里的原题&#xff0c;记住了考试要带两个证件才行&#xff0c;我用的是身份证和驾照&#xff0c;想考的抓紧考吧…

php实现动态登录

简介&#xff1a; 效果&#xff1a;通过前端页面的注册&#xff0c;通过MD5将密码加密&#xff0c;发送到数据库&#xff0c;通过验证数据库的内容实现登录&#xff0c;以及各种保证安全的措施 实验环境&#xff1a;phphtmlcssmysql数据表&#xff0c;使用html css设计注册&a…

do语句——输入两个值求和

代码如下&#xff1a; #include<stdio.h> int main() {int sum0;int a,b;puts("请输入两个整数");printf("整数a:"); scanf("%d",&a);printf("整数b:"); scanf("%d",&b);int t(a>b)?b:a;do{sumsumt;tt1;…

PHP基于微信小程序的打车平台-计算机毕业设计源码78689

摘 要 本文介绍的是基于PHP开发的打车平台小程序。该系统旨在为用户提供一个便捷、高效的平台&#xff0c;以实现网约车的打车功能。随着社交媒体和互联网的普及&#xff0c;网约车已成为日常交通中常见的形式。然而&#xff0c;传统的打车方式存在不方便、不及时等问题。 微信…

【Kafka】对 kafka 消费程序客户端进行监控采集

前言 对于 Kafka 组件而言&#xff0c;我们通常会对 kafka 服务端添加一些监控&#xff0c;来确保服务的稳定性&#xff0c;虽然有 kafka-exporter 来对消费者进行监控&#xff0c;但是指标很少&#xff0c;对于生产者和消费者更细粒度的监控就无法做到了。只能将监控部署在客…

DDPM 核心代码解析(1)

所有代码 已上传至GitHub - duhanyue349/diffusion_model_learned_ddpm_main: 扩散模型基础框架源代码 目录结构如下 在train_cifar.py 中展示了扩散模型训练的所有代码 如果没有安装wandb 可以在create_argparser()设置 log_to_wandbFalse 一、加载模型参数 args 这里用了一…

语音转文字在线免费有什么工具?这4款工具让记录更高效

在当今职场、学术界和内容创作领域&#xff0c;人们越来越需要一种高效的方式来整理会议记录、讲座内容或采访对话。 幸运的是&#xff0c;除了传统的手动记笔记方式&#xff0c;我们还可以通过录音转文字软件来实现这一目标。这些软件能够直接将音频资料转写为文本&#xff0…

4nm点状激光模组的应用让未来科技走向潮流

在科技发展时代&#xff0c;激光技术以其高精度、高效率的特性&#xff0c;正逐步成为众多行业不可或缺的核心技术之一。其中&#xff0c;4nm点状激光模组作为激光技术领域的佼佼者&#xff0c;凭借其卓越的性能和广泛的应用前景&#xff0c;正引领着科技发展的新潮流。接下来我…

ubuntu20.04.6 安装Skywalking 10.0.1

1.前置准备 1.1. **jdk17&#xff08;Skywalking10 jdk22不兼容&#xff0c;用17版本即可&#xff09;**安装&#xff1a; https://blog.csdn.net/CsethCRM/article/details/140768670 1.2. elasticsearch安装&#xff1a; https://blog.csdn.net/CsethCRM/article/details…

Apollo:源码分析之cyber/mainboard启动入口介绍-my write, test ok

软件结构图 cyber入口 cyber的入口在"cyber/mainboard"目录中: ├── mainboard.cc // 主函数 ├── module_argument.cc // 模块输入参数 ├── module_argument.h ├── module_controller.cc // 模块加载,卸载 └── module_controller.…

Feature Corrective Transfer Learning (2024CVPR)

Feature Corrective Transfer learning Framework &#xff08;特征矫正迁移学习框架&#xff09; 旨在引导非理想图像上的模型训练与理想图像上训练的模型的特中层更紧密地对齐 Model Selection and Training on Ideal Images 首先在理想图像上训练&#xff0c;得到理想参数…

NV170D语音芯片:为洗地扫地一体机带来新体验!

随着物联网、人工智能技术的飞速发展&#xff0c;家用电器的智能化转型已成为不可逆转的趋势。在这一背景下&#xff0c;洗地扫地一体机&#xff0c;作为家务自动化的先锋&#xff0c;融合了高效清洁与便捷操作的双重优势&#xff0c;而语音芯片的应用&#xff0c;更是为其增添…

使用 nvm在linux上安装多个版本的node

使用 nvm&#xff08;Node Version Manager&#xff09;: nvm 是一个流行的 Node.js 版本管理工具&#xff0c;允许你安装和使用多个版本的 Node.js。 1、安装nvm wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash source ~/.nvm/nvm.sh2、…

javaEE(3)

目录 一. 前端浏览器保存用户信息 二. 前端路由导航守卫 三. 路由嵌套 四. web会话跟踪 1. web会话跟踪原理 2. JWT 2.1 传统的session认证 2.2 基于token的鉴权机制 2.3 jwt的构成 2.4 jwt搭建 五. 前端发送请求携带token 5.1 请求拦截器 六. 后端过滤器验证toke…

springboot合肥师范学院实习实训管理系统-计算机毕业设计源码31290

摘要 随着社会对高校毕业生的职业素养和实践能力要求不断提高&#xff0c;高校实习实训教育愈发受到重视。信息化技术的快速发展也为高校教学管理带来了新的机遇。合肥师范学院实习实训管理系统的研究就是源自当前高等教育对学生实习实训管理的需求。 实习实训管理系统充分利用…

手机录屏直播,教你3个方法,秒变录屏高手

在移动互联网飞速发展的今天&#xff0c;手机录屏直播已成为越来越多用户分享内容、交流心得的重要方式。无论是游戏高手展示高超技艺&#xff0c;还是教育从业者进行远程授课&#xff0c;手机录屏直播都能提供极大的便利。 在手机录屏的世界里&#xff0c;安卓手机和苹果手机…

深入分析 Android ContentProvider (九)

文章目录 深入分析 Android ContentProvider (九)ContentProvider 的高级使用及最佳实践&#xff08;续&#xff09;1. 复杂查询与联合查询复杂查询示例 2. 数据同步与一致性示例&#xff1a;使用事务确保数据一致性 3. 数据分页加载示例&#xff1a;分页加载数据 4. 内容提供者…