vue项目中使用富文本编辑器

22,461 阅读4分钟

前言

我们前端工程师对富文本编辑器都不陌生,甚至用的十分频繁,但是网络上有各种各样的编辑器,我们到底该做出怎样的选择,首先是要取决于我们项目的功能需求,其次再取决于是否开源并且容易上手。

公司的项目需求首先是最基本的文本编辑功能,其次就是可以上传本地图片,可以添加表格,可以支持计数,所以我在网上查了很多并进行了对比的编辑器:

  • 百度编辑器UEditor
  • 优点 缺点
    1、功能十分强大,其他没有的它基本都有;
    2、可以支持本地图片上传,媒体上传等;
    3、有全屏功能;
    1、一直没有人维护,有bug也没更新了;
    2、图片上传需要后端支持;
  • wangEditor
  • 优点 缺点
    1、可以支持本地图片上传;
    2、上传图片提供钩子函数进行自定义处理;
    1、没有提供全屏功能;
    2、从word复制粘贴的代码,会有很多坑;
    3、提供的功能都是最基本的功能;
  • TinyMCE
  • 优点 缺点
    1、功能十分强大,只有基础功能免费;
    2、上传图片并提供钩子函数进行处理;
    1、仅开源部分功能,大部分需要收费;
    2、只有英文文档,对英文一般的不太友好;
  • CKEditor
  • 优点 缺点
    1、可以上传本地图片;
    2、支持全屏功能;
    上传图片需要后台支持;

前3个编辑器我都用过,由于ueditor自己都放弃了它自己的项目,所以我们也应该放弃,接下我大概讲讲wangEditor和tinyMCE在vue项目中的用法:

wangEditor

安装依赖

npm install wangeditor --save
//cnpm i wangeditor --save

引入依赖

// 引入富文本编辑器
import WangEditor from 'wangeditor'

创建实例

data(){
    return {
        editorId:'',
        editor:null
    }
},
method:{
    //为了防止组件在同一个页面多次被调用,需要生成一个随机的不重复id,可以通过时间和随机数生成
    randomId(){
        let baseId = 'wang_editor';
        let now = Date.now();
        let num = Math.random().toFixed(4)*10000;
        return `${baseId}_${now}_${num}`;
    },
    //初始化编辑器
    initEditor(){
        let _this = this;
        _this.editorId = _this.randomId();//生成一个id
        this.$nextTick(()=>{
            //获取实例,wangEditor是被注册在window的
            // 如果不明白,可以let wangEditor = window.wangEditor
            let editor = new wangEditor('#'+_this.editorId);
            _this.editor = editor;//将实例保存待调用其他api
            editor.create();//开始创建编辑器;
        })
        
    }
}

配置编辑器

wangeditor给使用者提供了很多配置,可以配置编辑器使其更加满足我们的项目需求。

以下是常用的配置,还有其他需要的配置请去官网查看文档。

methods:{
    setConfig(){
        let setting = {
            uploadImgShowBase64:true,// 是否允许上传base64位图片
            pasteFilterStyle:true, // 是否过滤粘贴的样式
            zIndex:100,//设置层叠位置
            //菜单列表
            menus:[
                'head',  // 标题
                'bold',  // 粗体
                'fontSize',  // 字号
                'fontName',  // 字体
                'italic',  // 斜体
                'underline',  // 下划线
                'strikeThrough',  // 删除线
                'foreColor',  // 文字颜色
                'backColor',  // 背景颜色
                'link',  // 插入链接
                'list',  // 列表
                'justify',  // 对齐方式
                'quote',  // 引用
                'emoticon',  // 表情
                'image',  // 插入图片
                'table',  // 表格
                'video',  // 插入视频
                'code',  // 插入代码
                'undo',  // 撤销
                'redo'  // 恢复
            ],
            showLinkImg:false,//是否显示“网络图片”tab
            //监听用户输入后的change事件
            onchange:function(html){
                console.log(html);
            }
        }
        //配置给编辑器
        this.editor.customConfig = Object.assign(this.editor.customConfig,setting)
    }
}

配置完成后的效果:

仿word调整了下样式:

常用的API

  • 获取html代码/填充html代码
//类似jquery的用法
//如果是直接调用,不传递参数,就是获取
this.editor.txt.html();
//如果传递参数,就是讲html替换为参数内容
this.editor.txt.html('<p>这是我替换的内容</p>');
  • 获取除了标签的文本内容
this.editor.txt.text()
  • 使用js添加内容
this.editor.txt.append("<p>这是我用js代码添加的代码</p>")
  • 启用和禁用编辑器
this.editor.$textElem.attr('contenteditable',true/false)

完整代码

wang-editor.vue组件

<template>
  <div :id="editorId"></div>
</template>

<script>
// 引入富文本编辑器
import wangEditor from 'wangeditor';
export default {
  props:{
    value:{
      required:true
    },
    disabled:{
      type:Boolean,
      default:false
    }
  },
  data () {
    return {
      editor:'',
      editorId:'',
    };
  },
  watch:{
    value(newval){
      if(this.editor){
        this.editor.txt.html(newval)
      }
    }
  },
  methods: {
    //生成一个随机不重复id,可以通过时间和随机数生成
    randomId(){
        let baseId = 'wang_editor';
        let now = Date.now();
        let num = Math.random().toFixed(2)*100;
        return `${baseId}_${now}_${num}`
    },
    //初始化编辑器
    initEditor(){
        let _this = this;
        _this.editorId = _this.randomId();//生成一个id
        this.$nextTick(()=>{
          //获取实例,wangEditor是被注册在window的
          let editor = new wangEditor('#'+_this.editorId);
          _this.editor = editor;//将实例保存待调用其他api
          _this.setConfig();
          editor.create();//开始创建编辑器;
          _this.editor.txt.html(this.value)
          // 设置是否可编辑
          if(this.disabled!=='undefined'){
            this.editor.$textElem.attr('contenteditable',!this.disabled)
          }
        })
    },
    // 创建富文本编辑器
    setConfig(){
        var _this = this
        // 开始创建
        let setting = {
          uploadImgShowBase64:true,// 是否允许上传base64位图片
          pasteFilterStyle:true, // 是否过滤粘贴的样式
          zIndex:100,//设置层叠位置
          //菜单列表
          menus:[
              'head',  // 标题
              'bold',  // 粗体
              'fontSize',  // 字号
              'fontName',  // 字体
              'italic',  // 斜体
              'underline',  // 下划线
              'strikeThrough',  // 删除线
              'foreColor',  // 文字颜色
              'backColor',  // 背景颜色
              'link',  // 插入链接
              'list',  // 列表
              'justify',  // 对齐方式
              'quote',  // 引用
              'emoticon',  // 表情
              'image',  // 插入图片
              'table',  // 表格
              'video',  // 插入视频
              'code',  // 插入代码
              'undo',  // 撤销
              'redo'  // 恢复
          ],
          showLinkImg:false,//是否显示“网络图片”tab
          //监听用户输入后的change事件
          onchange:function(html){
            _this.$emit('input',html)
          }
        }
        //配置给编辑器
        _this.editor.customConfig = Object.assign(_this.editor.customConfig,setting)
    },
  },
  created(){
    // 创建editor实例
    this.initEditor();
  }
}

</script>

test.vue调用

<template>
<div>
  <wang-editor
        style='height:300px;'
        v-model='wangValue'
        :disabled='wangDisabled'
    ></wang-editor>
  <h5>双向绑定的源代码</h5>
  <div>{{wangValue}}</div>
  <h5>双向绑定的页面效果</h5>
  <div v-html='wangEditor'></div>
</div>
</template>

<script>
import wangEditor from './wang-editor';
export default {
  components:{wangEditor},
  data(){
    return {
      wangValue:'',
      wangDisabled:false
    }
  }
}
</script>

TinyMCE

本来是拒绝使用这个编辑器的,虽然这个编辑器的功能十分强大,但是大部分的功能都是要收费的,还有文档特别多,而且还全是英文的。可是我使用的wangEditor有一个很大的坑,就是从word复制粘贴的时候,会莫名的失去一些内容或者会自动替换一些内容,导致用户使用起来非常不方便,所以我被迫换掉了wangEditor。

安装依赖

cnpm i tinymce --save
cnpm i @tinymce/tinymce-vue --save

引入依赖

import tinymce from 'tinymce/tinymce';
import Editor from '@tinymce/tinymce-vue';

引入插件

// 更多插件参考:https://www.tiny.cloud/docs/plugins/
import 'tinymce/plugins/image';// 插入上传图片插件
import 'tinymce/plugins/media';// 插入视频插件
import 'tinymce/plugins/table';// 插入表格插件
import 'tinymce/plugins/lists';// 列表插件
import 'tinymce/plugins/wordcount';// 字数统计插件
import 'tinymce/plugins/paste';//复制粘贴图片
import 'tinymce/plugins/fullscreen';//全屏插件

复制皮肤包

注意:这个步骤必须做,否则编辑器是显示不出来的,就相当于一个人,不然别人穿衣服出去裸奔。

从node_modules中找到tinymce下的skins文件夹,复制到static目录下。

中文包

这个看个人需求,如果需要的话就去官网进行下载,下载地址

源代码

废话不多说,直接上源代码吧,基本上都添加了注释。

<template>
  <div class="tinymce-editor">
    <editor 
      v-model="myValue"
      :init="init"
      :disabled="disabled"
      @onClick="onClick">
    </editor>
  </div>
</template>
<script>
  import tinymce from 'tinymce/tinymce'
  import Editor from '@tinymce/tinymce-vue'
  import 'tinymce/themes/silver/theme'
  // 更多插件参考:https://www.tiny.cloud/docs/plugins/
  import 'tinymce/plugins/image'// 插入上传图片插件
  // import 'tinymce/plugins/media'// 插入视频插件
  import 'tinymce/plugins/table'// 插入表格插件
  // import 'tinymce/plugins/lists'// 列表插件
  import 'tinymce/plugins/wordcount'// 字数统计插件
  import 'tinymce/plugins/paste';//复制粘贴图片
  import 'tinymce/plugins/fullscreen';//全屏插件
  export default {
    components: {
      Editor
    },
    props: {
      //传入的默认值
      value: {
        type: String,
        default: ''
      },
      //是否禁用
      disabled: {
        type: Boolean,
        default: false
      },
      //插件
      plugins: {
        type: [String, Array],
        default: 'image table wordcount paste fullscreen'
      },
      //菜单
      toolbar: {
        type: [String, Array],
        //如果显示的内容和配置的不符,表明插件未引入。需要去引入插件;
        default: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image | print preview media fullpage | forecolor backcolor emoticons | help'
      },
      //编辑器高度
      height:{
        default:300
      }
    },
    data () {
      let _this = this;
      return {
        init: {
          language_url: '/static/tinymce/lang/zh_CN.js',  //中文包路径地址
          language: 'zh_CN',
          skin_url: '/static/tinymce/skins/ui/oxide', //皮肤包路径地址
          height: this.height,//编辑器高度
          max_height:570,//resize为true时,控制编辑器的最大高度
          plugins: this.disabled?'':this.plugins,  // 插件
          toolbar: this.disabled?'':this.toolbar,  // 工具栏
          images_upload_url: '', //上传路径
          elementpath:false,//标签路径是否显示
          menubar:false,//顶部菜单
          resize:this.disabled?false:true,//页面大小拖动
          paste_data_images: true,//可以粘贴图片,需要引入paste插件
          content_style:'div,p{margin:5px 0;}'+'img{max-width:100%;}'+'*::-webkit-scrollbar{width:6px;height:6px;background:transparent;}'+'*::-webkit-scrollbar-thumb{border-radius: 3px;background: #bac3d9;}',
          // 此处为图片上传处理函数,这个直接用了base64的图片形式上传图片
         // 直接使用base64处理,最好还是上传到服务器,因为base64存储到服务器数据库太庞大了
          images_upload_handler: (blobInfo, success, failure) => {
            const img = 'data:image/jpeg;base64,' + blobInfo.base64()
            success(img)
          },
          init_instance_callback : function(editor) {
            _this.editorId = editor.id;
          },
          branding:false
        },
        myValue: this.value,
        editorId:null
      }
    },
    mounted () {
      tinymce.init({})
    },
    methods: {
      onClick (e) {
        this.$emit('onClick', e, tinymce)
      }
    },
    watch: {
      value (newValue) {
        this.myValue = newValue
      },
      myValue (newValue) {
        this.$emit('input', newValue)
      }
    }
  }

</script>

tinymce这个编辑器虽然很多功能是要收费的,但是他提供的基本功能都能满足大部分用户的需求,而且UI设计上也很美观,有需要的您可以试一试。