效果截图
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封装成了一个组件
<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_modules中tinymce/skins文件夹拷贝到自己项目的静态目录下,一般是public文件夹,然后进行skin_url、language_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({})。
插件列表可参考下图:
图片上传的配置
图片上传需要在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中配置resVideo、uploaded、file_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进行存放video的src:
第二处修改
判断video标签 获取src值赋值给videoSource。将第一张图的代码注释,并在注释后面的位置新增后面的代码:
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));
}
第三处修改
进行到这一步,源码就已经修改好了。接着进行该文件引进,放到插件引进之后再进行
import。
在项目中使用
import textEditor from "@/components/text-editor";
components: {
textEditor
},
<text-editor
:disabled="type === 'check'"
v-model="content"
/>
写在最后
至此,tinymce富文本组件已经封装完成,支持基本文本操作以及本地图片与视屏的上传,这是小蔡的第一篇文章,可能还有很多可以提高的地方,希望大家能多多指教,也希望这篇文章可以对小伙伴们工作上遇到的类似问题能有所帮助。如果小伙伴们有不理解的地方或者建议,可以在评论区进行交流哦~