-
安装两个插件tinymce和 @tinymce/tinymce-vue
npm install tinymce@5.10.3 @tinymce/tinymce-vue@5.0.0 -S
注意:
- @tinymce/tinymce-vue 是对tinymce进行vue的包装,主要作用当作vue组件使用
- -S保存到package.json文件
2. 把node_modules/tinymce下的目录(icons、plugins、skins、themes)拷贝到public/tinymce里面
3. 汉化tinymce中的语言,下载 langs/zh_CN.js,[语言包下载地址](https://www.tiny.cloud/get-tiny/language-packages/)
选择
4.解压,将langs文件也拷贝到public/tinymce
5.引入tinymce编辑器
import TinymceRich from "@tinymce/tinymce-vue";
6.引入node_modules里的tinymce相关文件文件
import tinymce from "tinymce/tinymce";
7.使用TinymceRich组件
8.引入编辑器插件
import 'tinymce/themes/silver'
import 'tinymce/plugins/advlist' //高级列表
import 'tinymce/plugins/anchor' //锚点
import 'tinymce/plugins/autolink' //自动链接
import 'tinymce/plugins/autosave'//编辑器高度自适应,注:plugins里引入此插件时,Init里设置的height将失效
import 'tinymce/plugins/code'//编辑源码
import 'tinymce/plugins/codesample'//代码示例
import 'tinymce/plugins/directionality'//文字方向
import 'tinymce/plugins/fullscreen'全屏插件
import 'tinymce/plugins/hr'//水平分割线
import 'tinymce/plugins/insertdatetime'//插入日期时间
import 'tinymce/plugins/link'//链接插件
import 'tinymce/plugins/lists'列表插件
import 'tinymce/plugins/media' //媒体插件
import 'tinymce/plugins/nonbreaking'//插入不间断空格
import 'tinymce/plugins/noneditable'
import 'tinymce/plugins/pagebreak'//插入分页符
import 'tinymce/plugins/paste' //预览
import 'tinymce/plugins/print'//打印
import 'tinymce/plugins/save' //保存
import 'tinymce/plugins/searchreplace'//查找替换
// import 'tinymce/plugins/spellchecker' //拼写检查,未加入汉化,不建议使用
import 'tinymce/plugins/tabfocus'//切入切出,按tab键切出编辑器,切入页面其他输入框中
import 'tinymce/plugins/template'//内容模板
import 'tinymce/plugins/textpattern'//快速排版
import 'tinymce/plugins/visualblocks' //显示元素范围
import 'tinymce/plugins/visualchars' //显示不可见字符
import 'tinymce/plugins/wordcount'//字数统计
import 'tinymce/plugins/table'//表格插件
import "tinymce/plugins/textcolor"; //文字颜色
import "tinymce/plugins/toc"; //目录生成器
9.为RichText组件自定义属性(disabled 禁用状态,plugins 可用插件,toolbar 工具栏, modelValue 用于自定义RichText组件的v-model指令,名字是固定的)
<div>
<TinymceRich v-model="contentValue" :init="initOption" :disabled="disabled" />
</div>
10.初始化Editor组件,在data()选项中定义,并实现图片上传
let initOption = ref({
base_url: '/tinymce', // [必要参数] 指定public的目录
language_url: "/tinymce/langs/zh-Hans.js",//语言类型的路径
language: 'zh-Hans', // 语言类型(中文),默认会到tinymce/langs/zh_CN.js文件
skin_url: '/tinymce/skins/ui/oxide', // 皮肤,浅色
// skin_url: '/tinymce/skins/ui/oxide-dark', // 皮肤,深色
plugins: props.plugins, // 插件配置
toolbar: props.toolbar, // 工具栏配置,设置false则为隐藏
toolbar_mode: 'sliding', // 工具栏移除模式,floating / sliding / scrolling / wrap
// menubar: 'file edit', // 菜单栏配置,设置为false则隐藏,不配置默认显示全部菜单
fontsize_formats: '12px 14px 16px 18px 20px 22px 24px 28px 32px 36px 48px 56px 72px', // 字体大小
font_formats: '微软雅黑=Microsoft YaHei, Helvetica Neue, PingFang SC, sans-serif; 苹果苹方=PingFang SC, Microsoft YaHei, sans-serif; 宋体=simsun,serif; 仿宋体=FangSong,serif; Impact=impact,chicago', // 字体
lineheight_formats: '0.5 0.8 1 1.2 1.5 1.75 2.2.5 3 4 5', // 行高配置,也可配置成'12px 14px 16px 18px'这种形式
height: 400, // 注:引入autoresize插件时,此属性无效
placeholder: '在这里输入文字',
branding: false, // tiny技术支持信息是否显示
resize: false, // 编辑器宽高是否可变,false-否,true-高可变,'both'-宽高均可(注意引号)
// statusbar: false, // 最下方的元素路径和字数统计那一栏是否显示
elementpath: false, // 元素路径是否显示
content_style: 'img { max-width: 100%; }', // 自定义可编辑区的css样式
setup: function (editor: any) {
editor.ui.registry.addIcon(
"image",
`<svg t="1664002320321" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4420" width="24" height="24"><path d="M125.9 185h772.2v653.9H125.9z" fill="#1F53CC" p-id="4421"></path><path d="M164.7 217.2h694.6v516.7H164.7z" fill="#FECD44" p-id="4422"></path><path d="M458.9 734l-8.6-43.8-101.5-102.8-135 146.6z" fill="#FC355D" p-id="4423"></path><path d="M306.9 348.7m-66.7 0a66.7 66.7 0 1 0 133.4 0 66.7 66.7 0 1 0-133.4 0Z" fill="#FFFFFF" p-id="4424"></path><path d="M384.6 734h474.7V608.8L687.8 400.1z" fill="#FC355D" p-id="4425"></path><path d="M422.5 662l-37.9 72 52.1-57.5z" fill="#BF2847" p-id="4426"></path><path d="M302.5 778.9h418.9v16.7H302.5z" fill="#00F0D4" p-id="4427"></path></svg>`
);
editor.ui.registry.addIcon('preview', '<svg t="1664002320321" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4420" width="24" height="24"><path d="M125.9 185h772.2v653.9H125.9z" fill="#1F53CC" p-id="4421"></path><path d="M164.7 217.2h694.6v516.7H164.7z" fill="#FECD44" p-id="4422"></path><path d="M458.9 734l-8.6-43.8-101.5-102.8-135 146.6z" fill="#FC355D" p-id="4423"></path><path d="M306.9 348.7m-66.7 0a66.7 66.7 0 1 0 133.4 0 66.7 66.7 0 1 0-133.4 0Z" fill="#FFFFFF" p-id="4424"></path><path d="M384.6 734h474.7V608.8L687.8 400.1z" fill="#FC355D" p-id="4425"></path><path d="M422.5 662l-37.9 72 52.1-57.5z" fill="#BF2847" p-id="4426"></path><path d="M302.5 778.9h418.9v16.7H302.5z" fill="#00F0D4" p-id="4427"></path></svg>');
},
paste_data_images: true, // 图片是否可以粘贴
images_upload_handler: (blobInfo: any, success: any, failure: any) => {//自定义图片上传
// let img = 'data:image/jpeg;base64,' + blobInfo.base64();
let formData = new FormData();//创建一个表单对象
formData.append('file', blobInfo.blob());//通过formData对象封装图片二进制数据
upload(formData).then((res: any) => {//上传的图片路径
success(res.data.url);//返回图片的回调地址
}).catch((err: any) => {
failure(err.response.data.message);
})
}
});
11.自定义RichText组件v-mode,定义计算属性contentValue,使用get和set巧妙的完成RichText和Editor两个组件之间的数据联动
let props = defineProps({//自定义属性用于设置默认值
modelValue: {// 用于自定义v-model的value 父组件向子组件传递的值
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
},
plugins: { //插件
type: [String, Array],
default: 'print preview searchreplace autolink directionality visualblocks visualchars fullscreen image link media template code codesample table charmap nonbreaking anchor insertdatetime advlist lists wordcount autosave'
},
toolbar: { //工具栏
type: [String, Array],
default: 'fullscreen undo redo restoredraft | table image media charmap hrpagebreak insertdatetime print preview | cut copy paste pastetext | forcolor backcolor bold italic underline strikethrough link anchor | alignleft aligncenter alignright alignjustify outdent indent | styleselect formatselect fontselect fontsizeselect | bullist numlist | blockquote subscript superscript removeformat | code selectal searchreplace visualblocks indent2em lineheight formatpainter axupimgs'
}
});
// 计算属性
let contentValue = computed({
// 富文本框内容
get() { // 取值 父组件向子组件传值
return props.modelValue;
},
set(value: string) { // 赋值 子组件向父组件传值 ,吧value传递过去,
emit('update:modelValue', value)
}
})
整体代码
<template>
<div>
<TinymceRich v-model="contentValue" :init="initOption" :disabled="disabled" />
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import tinymce from "tinymce/tinymce";
import TinymceRich from "@tinymce/tinymce-vue";
import { upload } from "@/utils/http";
// 引入编辑器插件
import 'tinymce/themes/silver'
import 'tinymce/plugins/advlist' //高级列表
import 'tinymce/plugins/anchor' //锚点
import 'tinymce/plugins/autolink' //自动链接
import 'tinymce/plugins/autosave'//编辑器高度自适应,注:plugins里引入此插件时,Init里设置的height将失效
import 'tinymce/plugins/code'//编辑源码
import 'tinymce/plugins/codesample'//代码示例
import 'tinymce/plugins/directionality'//文字方向
import 'tinymce/plugins/fullscreen'全屏插件
import 'tinymce/plugins/hr'//水平分割线
import 'tinymce/plugins/insertdatetime'//插入日期时间
import 'tinymce/plugins/link'//链接插件
import 'tinymce/plugins/lists'列表插件
import 'tinymce/plugins/media' //媒体插件
import 'tinymce/plugins/nonbreaking'//插入不间断空格
import 'tinymce/plugins/noneditable'
import 'tinymce/plugins/pagebreak'//插入分页符
import 'tinymce/plugins/paste' //预览
import 'tinymce/plugins/print'//打印
import 'tinymce/plugins/save' //保存
import 'tinymce/plugins/searchreplace'//查找替换
// import 'tinymce/plugins/spellchecker' //拼写检查,未加入汉化,不建议使用
import 'tinymce/plugins/tabfocus'//切入切出,按tab键切出编辑器,切入页面其他输入框中
import 'tinymce/plugins/template'//内容模板
import 'tinymce/plugins/textpattern'//快速排版
import 'tinymce/plugins/visualblocks' //显示元素范围
import 'tinymce/plugins/visualchars' //显示不可见字符
import 'tinymce/plugins/wordcount'//字数统计
import 'tinymce/plugins/table'//表格插件
import "tinymce/plugins/textcolor"; //文字颜色
import "tinymce/plugins/toc"; //目录生成器
let props = defineProps({//自定义属性用于设置默认值
modelValue: {// 用于自定义v-model的value 父组件向子组件传递的值
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
},
plugins: { //插件
type: [String, Array],
default: 'print preview searchreplace autolink directionality visualblocks visualchars fullscreen image link media template code codesample table charmap nonbreaking anchor insertdatetime advlist lists wordcount autosave'
},
toolbar: { //工具栏
type: [String, Array],
default: 'fullscreen undo redo restoredraft | table image media charmap hrpagebreak insertdatetime print preview | cut copy paste pastetext | forcolor backcolor bold italic underline strikethrough link anchor | alignleft aligncenter alignright alignjustify outdent indent | styleselect formatselect fontselect fontsizeselect | bullist numlist | blockquote subscript superscript removeformat | code selectal searchreplace visualblocks indent2em lineheight formatpainter axupimgs'
}
});
let emit = defineEmits(['update:modelValue']);
let initOption = ref({
base_url: '/tinymce', // [必要参数] 指定public的目录
language_url: "/tinymce/langs/zh-Hans.js",//语言类型的路径
language: 'zh-Hans', // 语言类型(中文),默认会到tinymce/langs/zh_CN.js文件
skin_url: '/tinymce/skins/ui/oxide', // 皮肤,浅色
// skin_url: '/tinymce/skins/ui/oxide-dark', // 皮肤,深色
plugins: props.plugins, // 插件配置
toolbar: props.toolbar, // 工具栏配置,设置false则为隐藏
toolbar_mode: 'sliding', // 工具栏移除模式,floating / sliding / scrolling / wrap
// menubar: 'file edit', // 菜单栏配置,设置为false则隐藏,不配置默认显示全部菜单
fontsize_formats: '12px 14px 16px 18px 20px 22px 24px 28px 32px 36px 48px 56px 72px', // 字体大小
font_formats: '微软雅黑=Microsoft YaHei, Helvetica Neue, PingFang SC, sans-serif; 苹果苹方=PingFang SC, Microsoft YaHei, sans-serif; 宋体=simsun,serif; 仿宋体=FangSong,serif; Impact=impact,chicago', // 字体
lineheight_formats: '0.5 0.8 1 1.2 1.5 1.75 2.2.5 3 4 5', // 行高配置,也可配置成'12px 14px 16px 18px'这种形式
height: 400, // 注:引入autoresize插件时,此属性无效
placeholder: '在这里输入文字',
branding: false, // tiny技术支持信息是否显示
resize: false, // 编辑器宽高是否可变,false-否,true-高可变,'both'-宽高均可(注意引号)
// statusbar: false, // 最下方的元素路径和字数统计那一栏是否显示
elementpath: false, // 元素路径是否显示
content_style: 'img { max-width: 100%; }', // 自定义可编辑区的css样式
setup: function (editor: any) {
editor.ui.registry.addIcon(
"image",
`<svg t="1664002320321" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4420" width="24" height="24"><path d="M125.9 185h772.2v653.9H125.9z" fill="#1F53CC" p-id="4421"></path><path d="M164.7 217.2h694.6v516.7H164.7z" fill="#FECD44" p-id="4422"></path><path d="M458.9 734l-8.6-43.8-101.5-102.8-135 146.6z" fill="#FC355D" p-id="4423"></path><path d="M306.9 348.7m-66.7 0a66.7 66.7 0 1 0 133.4 0 66.7 66.7 0 1 0-133.4 0Z" fill="#FFFFFF" p-id="4424"></path><path d="M384.6 734h474.7V608.8L687.8 400.1z" fill="#FC355D" p-id="4425"></path><path d="M422.5 662l-37.9 72 52.1-57.5z" fill="#BF2847" p-id="4426"></path><path d="M302.5 778.9h418.9v16.7H302.5z" fill="#00F0D4" p-id="4427"></path></svg>`
);
editor.ui.registry.addIcon('preview', '<svg t="1664002320321" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4420" width="24" height="24"><path d="M125.9 185h772.2v653.9H125.9z" fill="#1F53CC" p-id="4421"></path><path d="M164.7 217.2h694.6v516.7H164.7z" fill="#FECD44" p-id="4422"></path><path d="M458.9 734l-8.6-43.8-101.5-102.8-135 146.6z" fill="#FC355D" p-id="4423"></path><path d="M306.9 348.7m-66.7 0a66.7 66.7 0 1 0 133.4 0 66.7 66.7 0 1 0-133.4 0Z" fill="#FFFFFF" p-id="4424"></path><path d="M384.6 734h474.7V608.8L687.8 400.1z" fill="#FC355D" p-id="4425"></path><path d="M422.5 662l-37.9 72 52.1-57.5z" fill="#BF2847" p-id="4426"></path><path d="M302.5 778.9h418.9v16.7H302.5z" fill="#00F0D4" p-id="4427"></path></svg>');
},
paste_data_images: true, // 图片是否可以粘贴
images_upload_handler: (blobInfo: any, success: any, failure: any) => {//自定义图片上传
// let img = 'data:image/jpeg;base64,' + blobInfo.base64();
let formData = new FormData();//创建一个表单对象
formData.append('file', blobInfo.blob());//通过formData对象封装图片二进制数据
upload(formData).then((res: any) => {//上传的图片路径
success(res.data.url);//返回图片的回调地址
}).catch((err: any) => {
failure(err.response.data.message);
})
}
});
// 计算属性
let contentValue = computed({
// 富文本框内容
get() { // 取值 父组件向子组件传值
return props.modelValue;
},
set(value: string) { // 赋值 子组件向父组件传值 ,吧value传递过去,
emit('update:modelValue', value)
}
})
</script>
效果图: