wangEditor4.x和5.x使用及总结

1,589 阅读3分钟

最近有需求需要用到富文本编辑器,于是调研了一下,发现wangEditor的呼声很高,于是就开始了探索之路,文章会分别使用4.x和5.x版本进行实践,最后会总结4.x和5.x在使用上的一些区别。

4.x

安装

yarn add wangeditor

使用

安装后几行代码即可创建一个编辑器:

import E from "wangeditor"
const editor = new E("#div1")
editor.create()

常用方法

  • editor.txt.getJSON() 获取JSON内容
  • editor.txt.setJSON(xx) 设置JSON内容
  • editor.txt.html() 获取html
  • editor.txt.html(xx) html回显示
  • editor.config.uploadImgHooks上传图片的回调函数

实践

下面demo中使用wangEditor创建富文本编辑器,编辑器里会有默认值(表格)

<template>
  <div>
    <div ref="editor" class="editor-wrapper"></div>
    <button v-on:click="getContent" class="see-content">查看内容</button>

    <!-- 动态插入表格 -->
    <table
      border="0"
      width="100%"
      cellpadding="0"
      cellspacing="0"
      ref="tableBox"
    >
      <tbody>
        <tr>
          <th v-for="(item, index) in tableHeader" :key="index">{{ item }}</th>
        </tr>
        <tr v-for="item in tableData" :key="item.id">
          <td>{{ item.id }}</td>
          <td>{{ item.eventName }}</td>
          <td>{{ item.eventNumber }}</td>
          <td>{{ item.eventType }}</td>
          <td>{{ item.eventLevel }}</td>
          <td>{{ item.alarmNum }}</td>
          <td>{{ item.attackNum }}</td>
          <td>{{ item.victim }}</td>
          <td>{{ item.isSendReport }}</td>
        </tr>
      </tbody>
    </table>

    <div>
      <el-table :data="tableData" border style="width: 100%">
        <el-table-column prop="id" label="序号"></el-table-column>
        <el-table-column prop="eventNumber" label="编号"></el-table-column>
        <el-table-column prop="eventName" label="名称"></el-table-column>
        <el-table-column prop="eventType" label="分类"></el-table-column>
        <el-table-column prop="eventLevel" label="级别"></el-table-column>
        <el-table-column prop="alarmNum" label="数量"></el-table-column>
        <el-table-column prop="attackNum" label="攻击IP数"></el-table-column>
        <el-table-column prop="victim" label="受害IP数"></el-table-column>
    </div>
  </div>
</template>

<script>
import E from "wangeditor";
import { tableData, tableHeader } from "./config.js";
export default {
  name: "editor",
  data() {
    return {
      editorContent: "",
      tableData: tableData,
      tableHeader: tableHeader,
      unableUpload: false
    };
  },
  methods: {
    getContent: function() {
      console.log(this.editorContent);
    },
    // 获取自定义图片上传接口返回的uuidName集合
    getImgUidFromContent(content, imageList = []) {
      if (!content.length) return;
      content.map(item => {
        if (item.tag === "img") {
          item.attrs?.map(item => {
            if (item.name === "src") {
              const uuid = item.value?.split("uuidName=")[1];
              imageList.push(uuid);
            }
          });
        } else {
          if (item.children && item.children?.length) {
            this.getImgUidFromContent(item.children, imageList);
          }
        }
      });
    }
  },
  beforeDestroy() {
    // 销毁编辑器
    this.editor.destroy();
    this.editor = null;
  },
  mounted() {
    this.$nextTick(() => {
      var editor = new E(this.$refs.editor);
      this.editor = editor; // 方便销毁,注意:editor不用在data中定义,否则会有问题,即输入框无法正常输入
      // console.log(editor.config, "editor.customConfig--");
      // 编辑器配置
      editor.config.height = 500;
      editor.config.zIndex = 500;
      editor.config.placeholder = "";
      editor.config.showFullScreen = false;
      editor.config.showLinkImg = false;
      // 关于图片上传 (wangeditor默认图片上传大小限制5M)
      // editor.config.uploadImgAccept = ["png", "jpg", "jpeg", "tif"];
      // editor.config.uploadImgMaxLength = 1;
      // editor.config.uploadImgHeaders = { token: xxx };
      // editor.config.uploadImgParams = { serviceName: xxx };
      // editor.config.uploadImgServer = "url";
      // editor.config.uploadFileName = "file"; // 为固定值,否则上传失败

      // 已上传图片插入到编辑器中
      editor.config.uploadImgHooks = {
        before: () => {
          // config中已对类型、大小做出限制,此处限制数量10个
          if (this.unableUpload) {
            return { prevent: true, msg: "上传图片数量不能超过10个" };
          }
        },
        customInsert: (insertImgFn, result = {}) => {
          const { data = {} } = result; // data为上传接口返回的数据结构
          const downloadUrl = "xxx";
          insertImgFn(downloadUrl, data.uuidName);
        }
      };

      // 覆盖wangEditor默认原生alert
      editor.config.customAlert = (s, t) => {
        const { $message } = this;
        switch (t) {
          case "success":
            $message.success(s);
            break;
          case "info":
            $message.info(s);
            break;
          case "warning":
            $message.warning(s);
            break;
          case "error":
            $message.error(s);
            break;
          default:
            $message.info(s);
            break;
        }
      };

      editor.config.onchange = html => {
        // 编辑器内容变化时触发
        this.editorContent = html;
      };
      editor.config.menus = [
        "head", // 标题
        "bold", // 粗体
        "fontSize", // 字号
        "fontName", // 字体
        "italic", // 斜体
        "underline", // 下划线
        "strikeThrough", // 删除线
        "foreColor", // 文字颜色
        "backColor", // 背景颜色
        "link", // 插入链接
        "list", // 列表
        "justify", // 对齐方式
        "quote", // 引用
        "emoticon", // 表情
        // 'image',  // 插入图片
        "table", // 表格
        // 'video',  // 插入视频
        // 'code',  // 插入代码
        "undo", // 撤销
        "redo" // 恢复
      ];
      editor.create();

      // 数据回显
      // editor.txt.html(this.content);

      // 自定义onChange钩子触发的延迟时长
      editor.config.onchangeTimeout = 300;

      // 编辑器内容change事件
      editor.config.onchange = async newHtml => {
        const content = editor.txt.getJSON();
        const imageList = [];
        await this.getImgUidFromContent(content, imageList); // 获取图片上传后id
        this.unableUpload = imageList.length >= 10;
        this.$emit("change", { content: newHtml, imageList });
      };

      // 动态插入表格
      const editDiv = document.getElementsByClassName("w-e-text")[0]; // 可编辑div
      console.log(editDiv, Array.isArray(editDiv), "editDiv--");
      const tableBox = this.$refs.tableBox;
      editDiv.appendChild(tableBox);
      console.log(this.editorContent, "获取表格元素----");
    });
  }
};
</script>
<style lang="less">
.editor-wrapper {
  margin-top: 40px;
}
.see-content {
  margin: 20px 0;
}
</style>

结果

image.png

5.x

安装

yarn add @wangeditor/editor

使用

// 引入css文件
import '@wangeditor/editor/dist/css/style.css'

// template
<div id="toolbar-container"></div>
<div id="editor-container"></div>

// script
import { createEditor, createToolbar } from '@wangeditor/editor'

// 编辑器
const editorConfig = {};
const toolbarConfig = {};
const editor = createEditor({
  selector: '#editor-container',
  config: editorConfig,
  mode: 'default'
})
// 创建工具栏
const toolbar = createToolbar({
  editor,
  selector: '#toolbar-container',
  config: toolbarConfig,
  mode: 'default'
})
这样就创建了一个简单的编辑器

常用方法

  • editor.children 获取JSON内容
  • editor.children = xxx 设置JSON内容
  • editor.getHtml() 获取HTML内容
  • editor.setHtml()设置html内容
  • editorConfig.MENU_CONF['uploadImage']图片上传回调

实践

// template
<div ref="toolbarEditor" class="toolbar-container" />
<div ref="contentEditor" class="editor-container" />
    
// script
import { createEditor, createToolbar } from '@wangeditor/editor';

data() {
  return {
    content: '',
    unableUpload: false,
  };
},

/**
 * @desc 递归获取富文本编辑器内容中图片的uuidName
 * @param {*} content 富文本编辑器对象格式文本内容
 */
getImgUidFromContent(content, imageList = []){
  if (!content.length) return;
  content.map(item => {
    if (item.type === 'image') {
      const uuid = item.src?.split('uuidName=')[1];
      imageList.push(uuid);
    } else {
      if (item.children && item.children?.length) {
        getImgUidFromContent(item.children, imageList);
      }
    }
  });
}

// 富文本编辑器初始化
initEditor() {
  const self = this;
  // 编辑器配置
  const editorConfig = {
    placeholder: '',
    MENU_CONF: {}
  };
  editorConfig.MENU_CONF['uploadImage'] = {
    fieldName: 'file',
    server: '服务端图片上传接口地址',
    maxFileSize: 10 * 1024 * 1024,
    maxNumberOfFiles: 10,
    allowedFileTypes: ['image/png', 'image/jpg', 'image/jpeg', 'image/tiff'],
    headers: {
      'Token': '业务token'
    },
    meta: {
      serviceName: 'xxx'
    },
    timeout: 5 * 1000,
    // 上传之前触发
    onBeforeUpload() {
      if (self.unableUpload) {
        self.$message.warning('上传图片数量不能超过10个');
        return false;
      }
    },
    // 图片上传并返回了结果,想要自己把图片插入到编辑器中
    customInsert: (res = {}, insertFn)=> {
      const { data = {} } = res;
      const downloadUrl = 'xxx';
      insertFn(downloadUrl);
    },
    // 上传错误,或者触发timeout超时
    onError(file) {
      const size = file.size;
      if (size / 1024 / 1024 > 10) self.$message.warning('上传图片大小最大为10M!');
    },
    // 覆盖wangEditor默认原生alert
    customAlert: (s, t)=> {
      const { $message } = self;
      switch (t) {
        case 'success':
          $message.success(s);
          break;
        case 'info':
          $message.info(s);
          break;
        case 'warning':
          $message.warning(s);
          break;
        case 'error':
          $message.error(s);
          break;
        default:
          $message.info(s);
          break;
      }
    }
  };

  // 工具栏配置
  const toolbarConfig = {
    toolbarKeys: [
      'headerSelect',
      'blockquote',
      'bold',
      'underline',
      'italic',
      'color',
      'bgColor',
      'fontSize',
      'bulletedList',
      'numberedList',
      {
        key: 'group-justify',
        title: '对齐',
        menuKeys: ['justifyLeft', 'justifyRight', 'justifyCenter', 'justifyJustify'],
        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>'
      },
      {
        key: 'group-image',
        title: '图片',
        menuKeys: ['uploadImage'],
        iconSvg: '<svg viewBox="0 0 1024 1024"><path d="M959.877 128l0.123 0.123v767.775l-0.123 0.122H64.102l-0.122-0.122V128.123l0.122-0.123h895.775zM960 64H64C28.795 64 0 92.795 0 128v768c0 35.205 28.795 64 64 64h896c35.205 0 64-28.795 64-64V128c0-35.205-28.795-64-64-64zM832 288.01c0 53.023-42.988 96.01-96.01 96.01s-96.01-42.987-96.01-96.01S682.967 192 735.99 192 832 234.988 832 288.01zM896 832H128V704l224.01-384 256 320h64l224.01-192z"></path></svg>'
      },
      {
        key: 'group-indent',
        title: '缩进',
        menuKeys: ['indent', 'delIndent'],
        iconSvg: '<svg viewBox="0 0 1024 1024"><path d="M0 64h1024v128H0z m384 192h640v128H384z m0 192h640v128H384z m0 192h640v128H384zM0 832h1024v128H0z m0-128V320l256 192z"></path></svg>'
      }
      // 'insertTable'
    ],
  };

  // 创建编辑器
  const editorEle = this.$refs.contentEditor;
  const editor = createEditor({
    selector: editorEle,
    config: {
      ...editorConfig,
      // 编辑器内容change事件
      async onChange(editor) {
        const content = editor.children;
        const imageList = [];
        await getImgUidFromContent(content, imageList);
        self.unableUpload = imageList.length >= 10;
        self.$emit('change', { content: editor.getHtml(), imageList });
      },
    },
    mode: 'default'
  });
  editor.setHtml(this.content);
  this.editor = editor;

  // 创建工具栏
  const toolbarEle = this.$refs.toolbarEditor;
  createToolbar({
    editor,
    selector: toolbarEle,
    config: toolbarConfig,
    mode: 'default'
  });
 }

结果

image.png

注意事项

  • editor实例上的属性为只读属性,不能直接修改某个属性,如果需要修改可以通过深拷贝间接处理
  • editor可以不用在data中直接定义
  • 生成多个编辑器实例时,可以将编辑器封装成一个公共组件,组件中进行编辑器创建、销毁,其他用的地方直接用,简单方便

总结

  • 4.x在拖动图片时直接是复制,不支持拖拽
  • 5.x在表格的支持上更加友好,支持图片拖拽
  • 5.x在对图片的处理上更加友好,如果业务中用到的富文本编辑器对图片有很多操作,可以考虑使用wangEditor5版本
  • 5.x在大文件内容复制粘贴时,会有卡顿问题,这是因为内部使用了虚拟滚动,这个问题官方暂时没有解决方案,github上已有人提issue
  • 5.x版本做了架构上的改变(JSON数据格式,工具栏与编辑器分离,核心模块与扩展模块分离等)

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿