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

1,461 阅读4分钟

效果截图

微信图片_20211207205701.png

1、需求背景

近期项目中要求插入富文本编辑器,并能实现插入图片和视频(包括本地上传和线上链接),第一个想到的就是tinymce富文本编辑器了,这篇文章也将记录我在使用过程中遇到的一些问题和解决的方法,小蔡第一次写文章,希望大家可以多提提建议。

2、项目环境

vue2.6.10、tinymce5.3.0、@tinymce/tinymce-vue3.2.8

3、安装

使用cnpm 或者 yarn 进行依赖包的安装 cnpm install tinymce@5.3.0 --save 或者 yarn add tinymce@5.3.0 --save,@tinymce/tinymce-vue的安装和tinymce一致。

4、如何使用

为了以后项目中或者其他的项目可以复用tinymce的功能,建议将tinymce封装成了一个组件

image.png

<template>
  <div id="tinymceEditorBox">
    <editor
      id='tinymce'
      v-model='tinymceHtml'
      :init='init'
    />
  </div>
</template>

其中init是初始化的配置项,tinymceHtml是富文本编辑器的实时内容(html)。

父子组件的传值

props: ["value", "disabled"]

其中value使用双向绑定原理进行父子组件间的内容同步,在watch中使用以下代码,实现子组件的tinymceHtml实时同步到父组件,以便于实现效果图中的右侧手机内容与编辑器内容同步更新。

value: {
    immediate: true,
    handler(newVal) {
        this.tinymceHtml = newVal;
    }
},
tinymceHtml(newVal) {
    this.$emit("input", this.tinymceHtml);
    this.$nextTick(()=>{
        this.$emit("change", this.tinymceHtml);
    });
}

disabled用于控制查看状态下,富文本编辑器的只读状态,由于在init中设置disabled属性无效(还没探究是什么原因,有遇到该问题的小伙伴们欢迎留言),我使用了比较笨的方法,控制编辑器的css中的point-events属性来实现只读与可编辑的状态切换。同样,以下代码也是写在watch中。

disabled: { // 控制是否只读
    immediate: true,
    handler(newVal) {
        this.$nextTick(() => {
            if (newVal) {
                document.getElementById("tinymceEditorBox").style.pointerEvents = "none";
            } else {
                document.getElementById("tinymceEditorBox").style.pointerEvents = "auto";
            }
        })
    }
}

init 配置项

init配置项用于初始化富文本编辑器,部分配置项需要引进tinymce对应的插件,本项目中我的初始化配置项如下,将此配置项放置于data中。在添加之前,需要将node_modulestinymce/skins文件夹拷贝到自己项目的静态目录下,一般是public文件夹,然后进行skin_urllanguage_url以及content_css等的配置。

init: {
    selector: "#tinymce",
    skin_url: BASE_URL + "tinymce/skins/ui/oxide",
    height: 628,
    language_url: BASE_URL + "tinymce/lang/zh_CN.js",
    language: 'zh_CN',
    icons: "custom",
    content_css: BASE_URL + "tinymce/skins/content/default/content.css",
    // 用数组把plugins和toolbar包起来 防止工具栏隐藏了
    plugins: ['link lists image media code table colorpicker textcolor wordcount contextmenu paste'],
    toolbar: ['bold italic underline strikethrough | fontsizeselect | forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist | outdent indent blockquote | undo redo | link unlink image media code | removeformat'],
    // toolbar_sticky: true,
    menubar: true,
    branding: false,
    browser_spellcheck: true,
    paste_data_images: true,
    resVideo: "",    //上传视频的url
    uploaded: false,//有没有上传完成
    file_picker_types: "media",
    images_upload_handler: (blobInfo, success, failure) => {
        this.handleImageAdded(blobInfo, success, failure);
    },
    file_picker_callback: (callback, value, meta) => {
        this.uploadMedia(callback, value, meta);
    }
}

需要引进的插件如下:

import tinymce from "tinymce/tinymce";
import "tinymce/themes/silver/theme";
import "tinymce/icons/default/icons"
import Editor from "@tinymce/tinymce-vue";
import 'tinymce/plugins/image' // 图片上传插件
import 'tinymce/plugins/media' // 视频插件
import 'tinymce/plugins/link' // 超链接插件
import 'tinymce/plugins/code' // 代码块插件
import 'tinymce/plugins/table' // 表格插件
import 'tinymce/plugins/lists' // 列表插件
import 'tinymce/plugins/contextmenu' // 右键菜单插件
import 'tinymce/plugins/wordcount' // 字数统计插件
import 'tinymce/plugins/colorpicker' // 颜色选择插件
import 'tinymce/plugins/textcolor' // 文本颜色插件
import 'tinymce/plugins/paste' // 粘贴图片插件
import "./mediaPlugin"; // 修改了一些源码,修复了上传视频后 不能预览的问题

添加之后,在components中引进Editor组件,然后在mounted中进行tinymce富文本编辑器的初始化:tinymce.init({})。 插件列表可参考下图:

image.png

图片上传的配置

图片上传需要在init中配置images_upload_handler方法,请参考上面示例代码。其中blobInfo对应文件信息,success上传成功回调函数,返回文件链接地址,failure上传失败回调函数,返回错误信息。

async handleImageAdded(blobInfo, success, failure) {
    let file = blobInfo.blob();

    if (file.size / 1024 / 1024 > 3) {
        this.$message.error("图片大小不能超过3MB!");
        return;
    }

    let res = await putFiles({
        file: file
    });

    if (res.data.success) {
        success(res.data.data.link);
        this.$message.success("上传图片成功");
    } else {
        failure(res.data.msg);
        this.$message.error(res.data.msg);
    }
}

视频上传的配置

原先视频上传是只能使用线上链接,没有本地上传按钮,那么如何上传本地视频呢?首先我们需要在init中配置resVideouploadedfile_picker_types以及file_picker_callback。详情请参考上面示例代码。选择本地视频后,点击上传执行uploadMedia方法。

uploadMedia(callback, value, meta) {
    console.log(meta);
    if (meta.filetype === "media") {
        let input = document.createElement("input");
        let self = this;

        input.setAttribute("type", "file");
        input.setAttribute("accept", "video/*");
        input.onchange = function () {
            let file = this.files[0]; // 选第一个文件

            self.uploadMediaCallback(file, "media", callback);
        };

        input.click();
    }
},
async uploadMediaCallback(file, type, call) {
    let res = await putFiles({
        file: file
    });

    if (res.data.success) {
        this.init.resVideo = res.data.data.link;
        this.init.uploaded = true;

        console.log(res.data.data.link);
        call(res.data.data.link);
        this.$message.success("上传视频成功");
    }
}

至此,已经可以成功上传本地视频了,但是细心的小伙伴们会发现,无法进行视频的预览,这时候,需要我们进行部分源码的修改。在这里,我是将需要修改的源码文件,复制一份放到组件文件夹下面,这样就方便了其他同事进行tinymce依赖下载后,不用进行同样的源码修改重复操作。我们需要复制tinymce/plugins文件夹下media/plugins.js文件,修改后使用import引进文件:import "./mediaPlugin"

第一处修改

增加videoSource进行存放videosrc

image.png

第二处修改

判断video标签 获取src值赋值给videoSource。将第一张图的代码注释,并在注释后面的位置新增后面的代码:

image.png

if(node.name === 'video' && Settings.hasLiveEmbeds(editor) && global$8.ceFalse){
  console.log('videoSource===', videoSource)
  videoSource = ''
  if(node.attributes['map'] && node.attributes['map'].src){
    videoSource = node.attributes['map'].src
  }else{
    for(var ii=0;ii<node.attributes.length;ii++){
      if(node.attributes[ii].name == "src"){
        videoSource = node.map.node.attributes[ii].value
      }
    }
  }
  if(node.firstChild && node.firstChild.value){
    var elel=node.firstChild && node.firstChild.value
    var objE = document.createElement("div");
    objE.innerHTML = elel;
    var dom = objE.getElementsByTagName('source')[0]
    videoSource = dom.getAttribute('src')
  }
  node.replace(createPreviewIframeNode(editor, node));
}

第三处修改

image.png 进行到这一步,源码就已经修改好了。接着进行该文件引进,放到插件引进之后再进行import

在项目中使用

import textEditor from "@/components/text-editor";
components: {
    textEditor
},
<text-editor
  :disabled="type === 'check'"
  v-model="content"
/>

写在最后

至此,tinymce富文本组件已经封装完成,支持基本文本操作以及本地图片与视屏的上传,这是小蔡的第一篇文章,可能还有很多可以提高的地方,希望大家能多多指教,也希望这篇文章可以对小伙伴们工作上遇到的类似问题能有所帮助。如果小伙伴们有不理解的地方或者建议,可以在评论区进行交流哦~