Vue 使用 TinyMCE 富文本编辑器
前言
说到富文本编辑器,Quill
是 Vue 开发者绕不开的,但遗憾的是,Quill
的 Vue
扩展 vue-quill-editor
在 5 年前停止更新了。
斯人已逝,我们迎来了新的富文本编辑器,TinyMCE
!
环境
Vue: v2.6.10
tinymce: v5.4.2
@tinymce/tinymce-vue: v3.2.3
copy-webpack-plugin: v6.1.1
实现
安装 tinymce
和 @tinymce/tinymce-vue
shell
yarn add tinymce
yarn add @tinymce/tinymce-vue
创建通用富文本组件
这种复用度高的组件,自然是要抽象为公共组件。
和很多组件包一样,TinyMCE
也是通过一个基础组件和配置参数对象来实现功能的。
js
// 引入tinyMCE对象
import tinymce from "tinymce/tinymce";
// 引入编辑器组件
import Editor from "@tinymce/tinymce-vue";
// 引入主题文件
import "tinymce/themes/silver/theme";
// 引入皮肤样式
import "tinymce/skins/ui/oxide/skin.css";
在默认配置下的 TinyMCE
显得过于简陋了,所以我们要自己引入需要的功能插件包。
需要注意的是,引入和使用在 TinyMCE
中是不同的操作。引入后,不更改工具栏配置,也是使用不了对应功能插件的。
js
// 编辑器插件plugins
// 更多插件参考:https://www.tiny.cloud/docs/plugins/
import "tinymce/plugins/image"; // 插入上传图片插件
import "tinymce/plugins/table"; // 插入表格插件
import "tinymce/plugins/lists"; // 列表插件
import "tinymce/plugins/wordcount"; // 字数统计插件
import 'tinymce/plugins/media'; // 插入上传视频插件
import 'tinymce/plugins/autosave'; // 自动保存插件
import 'tinymce/plugins/preview'; // 预览插件
import 'tinymce/plugins/hr'; // 预览插件
import 'tinymce/plugins/code'; // 源码插件
const config = {
.....
// 需要配置 toolbar,使功能按钮显示出来,除了一些特殊插件,如字数统计插件等。
// 当 toolbar 为 String 类型时,所有组件都会放置在同一行,直到溢出换行。
// 当 toolbar 为 Array 类型时,每个下标为一行。
toolbar: [
"undo redo | cut copy paste pastetext | forecolor backcolor bold italic underline hr link anchor | alignleft aligncenter alignright alignjustify outdent indent",
"styleselect formatselect fontsizeselect",
"bullist numlist | blockquote subscript superscript removeformat | table image charmap emoticons pagebreak insertdatetime print preview | bdmap indent2em lineheight formatpainter axupimgs",
]
}
然后在 template
使用 Editor
组件,组件内的富文本值可以直接通过 v-model
获取,init
属性传初始配置对象,onClick
接收点击事件。
在 mounted
生命周期方法内调用 tinymce
对象的 init
方法,完成初始化。
html
<template>
<div class="tinymce-editor">
<editor
v-model="myValue"
:init="init"
:disabled="disabled"
@onClick="onClick"
></editor>
</div>
</template>
<script>
export default {
mounted() {
tinymce.init({});
},
};
</script>
这样就可以了,在想要使用的地方直接引入。
如果想了解最新的功能,建议直接看 TinyMCE 官方文档。
如果想了解 API
,建议看 TinyMCE 中文文档,但是只能用来参考。因为这个网站已经很久没更新了,而且他的用法主要围绕 <script />
标签直接引入写的,并不怎么适合 Vue
。
问题
编辑器样式消失
因为我的编辑器获取样式是相对路径,所以需要使用 copy-webpack-plugin
插件包,将静态文件不打包直接放到指定目录,建议是配置一个绝对路径。
js
module.exports = {
......
plugins: [
......
new CopyPlugin({//复制不需要参加打包的文件到指定目录
patterns: [
{ from: resolve(__dirname, './src/static'), to: 'static' }
]
}),
]
}
代码
html
<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";
import "tinymce/skins/ui/oxide/skin.css";
// 编辑器插件plugins
// 更多插件参考:https://www.tiny.cloud/docs/plugins/
import "tinymce/plugins/image"; // 插入上传图片插件
import "tinymce/plugins/table"; // 插入表格插件
import "tinymce/plugins/lists"; // 列表插件
import "tinymce/plugins/wordcount"; // 字数统计插件
import "tinymce/plugins/media"; // 插入上传视频插件
import "tinymce/plugins/autosave"; // 自动保存插件
import "tinymce/plugins/preview"; // 预览插件
import "tinymce/plugins/hr"; // 预览插件
import "tinymce/plugins/code"; // 源码插件
export default {
components: {
Editor,
},
props: {
disabled: {
type: Boolean,
default: false,
},
plugins: {
type: [String, Array],
default: "lists image table wordcount autosave preview hr",
},
toolbar: {
type: [String, Array],
default: () => {
return [
"undo redo | cut copy paste pastetext | forecolor backcolor bold italic underline hr link anchor | alignleft aligncenter alignright alignjustify outdent indent",
"styleselect formatselect fontsizeselect",
"bullist numlist | blockquote subscript superscript removeformat | table image charmap emoticons pagebreak insertdatetime print preview | bdmap indent2em lineheight formatpainter axupimgs",
];
},
},
value: {
type: String,
default: "",
},
},
data() {
return {
init: {
selector: "textarea",
language_url: "./static/tinymce/langs/zh_CN.js",
language: "zh_CN",
convert_urls: false,
skin_url: "./static/tinymce/skins/ui/oxide",
content_css: "./static/tinymce/skins/content/default/content.css",
icons: "default",
icons_url: "./static/tinymce/icons/icons.js",
content_style: "img {max-width: 100%} ",
height: "80vh",
plugins: this.plugins,
toolbar: this.toolbar,
branding: false,
// menubar: 'file edit',
// 如需ajax上传可参考https://www.tiny.cloud/docs/configure/file-image-upload/#images_upload_handler
images_upload_handler: (blobInfo, success, failure) => {
var xhr, formData;
xhr = new XMLHttpRequest();
xhr.withCredentials = false;
xhr.open("POST", "./file/upload");
xhr.onload = function () {
var json;
if (xhr.status != 200) {
failure("HTTP Error: " + xhr.status);
return;
}
json = JSON.parse(xhr.responseText);
// this.imgsUrl[this.imgsUrl.length - 1] = json["data"];
success("./files/" + json["path"]);
};
formData = new FormData();
formData.append("file", blobInfo.blob(), blobInfo.filename());
formData.append("dir", "jfzsy");
xhr.send(formData);
},
},
myValue: this.value,
};
},
mounted() {
tinymce.init({});
},
methods: {
// 添加相关的事件,可用的事件参照文档=> https://github.com/tinymce/tinymce-vue => All available events
// 需要什么事件可以自己增加
onClick(e) {
this.$emit("onClick", e, tinymce);
},
// 可以添加一些自己的自定义事件,如清空内容
clear() {
this.myValue = "";
},
},
watch: {
value(newValue) {
this.myValue = newValue;
},
myValue(newValue) {
this.$emit("input", newValue);
},
},
};
</script>