1.导入 tinymce 依赖
npm i tinymce
2.show me the code
MyEditor.vue
<template>
<!-- 添加visibility: hidden防止初始化之前显示出来 -->
<textarea :id="editorId" style="visibility: hidden" />
</template>
<script>
import tinymce from "tinymce/tinymce";
// 把tinymce插件引入抽取为一个文件
import { contentStyle } from "./plugins";
import { uploadFile } from "~/utils";
export default {
name: "MyEditor",
props: {
// 富文本内容
value: {
type: String,
default: "",
},
// 自定义选项
opt: {
type: Object,
default: () => {},
},
// 控制禁用状态
disabled: {
type: Boolean,
default: false,
},
},
data() {
return {
// 生成随机id,用uuid可能会更好
editorId: `editor-${Math.floor(Math.random() * 1000000)}`,
// 富文本实例
editor: null,
};
},
watch: {
// 实现v-model更新内容
value(val, oldVal) {
if (this.editor && val !== oldVal && val !== this.editor.getContent()) {
this.editor.setContent(val);
}
},
// 控制禁用
disabled(val) {
if (this.editor !== null) {
this.editor.setMode(val ? "readonly" : "design");
}
},
},
mounted() {
// 初始化富文本
this.init();
},
destroyed() {
// 销毁实例
if (this.editor) {
this.editor.destroy();
this.editor = null;
}
},
methods: {
init() {
// 默认配置
const opt = {
// selector dom 来初始化
selector: "#" + this.editorId,
// menubar 不想顶栏太高所以隐藏掉了
menubar: false,
// 指定使用的插件
plugins: [
"autolink lists link image",
"searchreplace charmap emoticons",
"media help paste wordcount codesample hr preview",
],
// 选择显示在 toolbar 上的功能
toolbar:
"bold italic underline strikethrough | forecolor backcolor removeformat | " +
"fontselect fontsizeselect formatselect | alignleft aligncenter alignright alignjustify | " +
"bullist numlist | outdent indent | subscript superscript | hr | " +
"charmap emoticons | link image media codesample | preview help",
// 指定使用的字体,默认没有中文字体
font_formats:
"Arial=arial,helvetica,sans-serif; Courier New=courier new,courier,monospace; AkrutiKndPadmini=Akpdmi-n;宋体=宋体;黑体=黑体;仿宋=仿宋;微软雅黑=Microsoft YaHei;楷体-GB2312=楷体-GB2312",
// 引入语言包,也要引入下载好的js文件,下载地址(https://www.tiny.cloud/get-tiny/language-packages/)
language: "zh_CN",
height: 400,
// 工具栏显示模式,默认只显示一行
toolbar_mode: "wrap",
// 如果不用cdn,要手动引入皮肤样式
skin: false,
content_css: false,
content_style: contentStyle,
// 图片上传
automatic_uploads: true,
file_picker_types: "image",
images_upload_handler: (blobInfo, success, failure) => {
// 调用了一个公共函数,会返回上传好的图片地址
uploadFile({ filename: blobInfo.filename(), blob: blobInfo.blob() })
.then((res) => {
success(res);
})
.catch((err) => {
console.debug(err);
failure(err);
});
},
// 合并配置
...this.opt,
// 富文本实例化之前执行的回调
setup: (editor) => {
this.editor = editor;
// 注册初始化的事件
editor.on("init", (e) => this.initSetup(e));
},
};
// 参数
tinymce.init(opt);
},
initSetup() {
if (!this.editor) return;
this.editor.setContent(this.value);
// 注册富文本更新的事件,监听更新实现v-model
this.editor.on("change keyup undo redo", () => {
const content = this.editor.getContent();
this.$emit("input", content);
});
},
},
};
</script>
<style lang="scss" scope>
// 引入富文本皮肤
@import "~tinymce/skins/ui/oxide/skin.css";
</style>
plugins.js
import "tinymce/themes/silver";
// 富文本语言包
import "./zh_CN";
import "tinymce/icons/default/icons";
import "tinymce/plugins/autolink";
import "tinymce/plugins/codesample";
import "tinymce/plugins/hr";
import "tinymce/plugins/link";
import "tinymce/plugins/lists";
import "tinymce/plugins/image";
import "tinymce/plugins/media";
import "tinymce/plugins/paste";
import "tinymce/plugins/preview";
import "tinymce/plugins/searchreplace";
import "tinymce/plugins/wordcount";
import "tinymce/plugins/charmap";
import "tinymce/plugins/emoticons";
import "tinymce/plugins/emoticons/js/emojis";
import "tinymce/plugins/help";
import "tinymce/plugins/codesample";
// 富文本 iframe 的样式,这里只引入了图片缩放的样式
const contentStyle =
".mce-content-body audio[data-mce-selected],.mce-content-body embed[data-mce-selected],.mce-content-body img[data-mce-selected],.mce-content-body object[data-mce-selected],.mce-content-body video[data-mce-selected]{outline:3px solid #b4d7ff}" +
".mce-content-body img::selection{background:0 0}" +
".mce-content-body div.mce-resizehandle{background-color:#4099ff;border-color:#4099ff;border-style:solid;border-width:1px;box-sizing:border-box;height:10px;position:absolute;width:10px;z-index:1298}.mce-content-body div.mce-resizehandle:hover{background-color:#4099ff}.mce-content-body div.mce-resizehandle:nth-of-type(1){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(2){cursor:nesw-resize}.mce-content-body div.mce-resizehandle:nth-of-type(3){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(4){cursor:nesw-resize}.mce-content-body .mce-resize-backdrop{z-index:10000}.mce-content-body .mce-clonedresizable{cursor:default;opacity:.5;outline:1px dashed #000;position:absolute;z-index:10001}.mce-content-body .mce-clonedresizable.mce-resizetable-columns td,.mce-content-body .mce-clonedresizable.mce-resizetable-columns th{border:0}.mce-content-body .mce-resize-helper{background:#555;background:rgba(0,0,0,.75);border:1px;border-radius:3px;color:#fff;display:none;font-family:sans-serif;font-size:12px;line-height:14px;margin:5px 10px;padding:5px;position:absolute;white-space:nowrap;z-index:10002}";
export { contentStyle };
example.vue
<template>
<DydEditor v-model="content" :disabled="isDisabled" />
</template>