安装命令:
这里版本一定要跟我一样,不然会出现不可思议的bug,切记。
npm install tinymce@6.0.3
npm install @tinymce/tinymce-vue@5.0.0
步骤:
1. 在public下创建 tinymce 文件夹。
2. 将 node_modules 中的 tinymce 里面的 skins 复制到 public 下的 tinymce 文件中。
3. 在 public 下的 tinymce 创建 langs 文件夹
4. 将 https://gitee.com/shuiche/tinymce-vue3/blob/master/langs/zh_CN.js#
里面的语言包下载后放进 langs 文件夹中,如下图:
5. 封装富文本组件,在 components 下创建 TEditor,代码如下:
<template>
<main>
<editor
v-model="myValue"
:init="init"
:disabled="disabled"
:id="tinymceId"
/>
</main>
</template>
<script setup>
import { reactive, ref, onMounted, watch } from "vue";
import tinymce from "tinymce/tinymce";
import "tinymce/skins/content/default/content.css";
import Editor from "@tinymce/tinymce-vue";
import "tinymce/themes/silver";
import "tinymce/themes/silver/theme";
import "tinymce/icons/default";
import "tinymce/models/dom";
import { min } from "lodash";
// 按需导入
import "tinymce/icons/default/icons";
import "tinymce/plugins/table";
import "tinymce/plugins/lists";
import "tinymce/plugins/wordcount";
import "tinymce/plugins/code";
//
import "tinymce/plugins/fullscreen";
import "tinymce/plugins/image";
const emits = defineEmits(["getContent"]);
const props = defineProps({
value: {
type: String,
default: () => {
return "";
},
},
baseUrl: {
type: String,
default: "",
},
disabled: {
type: Boolean,
default: false,
},
plugins: {
type: [String, Array],
default: "lists fullscreen code image",
}, //必填
toolbar: {
type: [String, Array],
default:
"fullscreen | code | codesample bold italic underline alignleft aligncenter alignright alignjustify | undo redo | formatselect | fontselect | fontsizeselect | forecolor backcolor | bullist numlist outdent indent | lists link table code | removeformat | undo redo | bold italic underline | image",
}, //必填
});
//用于接收外部传递进来的富文本
const myValue = ref(props.value);
const tinymceId = ref(
"vue-tinymce-" + +new Date() + ((Math.random() * 1000).toFixed(0) + "")
);
//定义一个对象 init初始化
const init = reactive({
selector: "#" + tinymceId.value, //富文本编辑器的id,
language_url: "/tinymce/langs/zh-cn.js", // 语言包的路径,具体路径看自己的项目,文档后面附上中文js文件
language: "zh_CN", //语言
skin_url: "/tinymce/skins/ui/oxide", // skin路径,具体路径看自己的项目
height: 1000, //编辑器高度
branding: false, //是否禁用“Powered by TinyMCE”
menubar: true, //顶部菜单栏显示
image_dimensions: false, //去除宽高属性
plugins: props.plugins, //这里的数据是在props里面就定义好了的
toolbar: props.toolbar, //这里的数据是在props里面就定义好了的
font_formats:
"Arial=arial,helvetica,sans-serif; 宋体=SimSun; 微软雅黑=Microsoft Yahei; Impact=impact,chicago;", //字体
fontsize_formats: "11px 12px 14px 16px 18px 24px 36px 48px 64px 72px", //文字大小
// paste_convert_word_fake_lists: false, // 插入word文档需要该属性
paste_webkit_styles: "all",
paste_merge_formats: true,
nonbreaking_force_tab: false,
paste_auto_cleanup_on_paste: false,
file_picker_types: "file",
content_css: "/tinymce/skins/content/default/content.min.css", //以css文件方式自定义可编辑区域的css样式,css文件需自己创建并引入
//图片上传
images_upload_handler: (blobInfo, progress) =>
new Promise((resolve, reject) => {
if (blobInfo.blob().size / 1024 / 1024 > 2) {
reject({ message: "上传失败,图片大小请控制在 2M 以内", remove: true });
return;
} else {
const ph =
import.meta.env.VITE_BASE_PATH +
":" +
import.meta.env.VITE_SERVER_PORT +
"/";
let params = new FormData();
params.append("file", blobInfo.blob());
let config = {
headers: {
"Content-Type": "multipart/form-data",
},
};
axios
.post("xxxx", params, config)
.then((res) => {
if (res.data.code == 200) {
resolve(ph + res.data.msg); //上传成功,在成功函数里填入图片路径
} else {
reject("HTTP Error: 上传失败" + res.data.code);
return;
}
})
.catch(() => {
reject("上传出错,服务器开小差了呢");
return;
});
}
}),
// 文件上传
file_picker_callback: (callback, value, meta) => {
// Provide file and text for the link dialog
if (meta.filetype == "file") {
callback("mypage.html", { text: "My text" });
}
// Provide image and alt text for the image dialog
if (meta.filetype == "image") {
callback("myimage.jpg", { alt: "My alt text" });
}
// Provide alternative source and posted for the media dialog
if (meta.filetype == "media") {
callback("movie.mp4", { source2: "alt.ogg", poster: "image.jpg" });
}
},
});
//监听外部传递进来的的数据变化
watch(
() => props.value,
() => {
console.log("部传递进来的的数据变化", props.value);
myValue.value = props.value;
emits("getContent", myValue.value);
}
);
//监听富文本中的数据变化
watch(
() => myValue.value,
() => {
console.log("监听富文本中的数据变化", myValue.value);
emits("getContent", myValue.value);
}
);
watchEffect(() => {
console.log("???????????", props);
});
//在onMounted中初始化编辑器
onMounted(() => {
console.log("props.value", props.value);
tinymce.init({});
});
</script>
<style lang="scss" scoped>
main {
width: 100%;
textarea {
display: none;
}
}
</style>
<style>
/* 在el-dialog中tinymce z-index 被太小而被遮挡时要加这两句 */
.tox-tinymce-aux {
z-index: 99999 !important;
}
.tinymce.ui.FloatPanel {
z-index: 99;
}
.tox-promotion {
display: none;
}
.tox-tinymce {
height: 100% !important;
min-height: 236px;
}
.tox-textarea-wrap {
height: 100% !important;
}
textarea {
height: 100% !important;
cursor: default !important;
}
</style>
6. 页面中使用组件, 代码如下:
<template>
<div>
<TEditor
ref="editor"
v-model:value="formState.contents"
:disabled="disabled"
@getContent="getContent"
/>
</div>
</template>
<script setup lang="ts">
import TEditor from "@/components/TEditor/index.vue";
const formState = reactive({ contents: "" });
const getContent = (v: string) => {
formState.contents = v;
};
</script>
<style lang="scss" scoped></style>
问题:
样式需要选择 content.min.css,假如content.css会有问题,样式不准确。
按需引入教程在官网有: importcss 引入css | TinyMCE中文文档中文手册
示例:
假如需要进入图片插件,步骤:
官网图:
自定义工具栏的按钮:
setup: function (editor) {
// 在编辑器初始化时注册自定义按钮
editor.ui.registry.addButton("customButton", {
text: "image",
icon: "image", // 自定义按钮的图标名称
onAction: function () {
appStore.changeShow(true);
},
});
效果如下图: