等下,用户说文章模式是问答模式,标题是提问,内容是针对主标题的问题回答,然后有副标题用H标签,内容不要以H开头,引用权威相关内容,不要标注,调整逻辑,要原创,无AI痕迹,日常用语,不少于1400
Vue3官方文档里有没有推荐的集成方案,TinyMCE官方文档应该有tinymce-vue 5对应Vue3,对吧?先确认版本对应很重要,这个是第一个核心细节开头铺垫真实开发中踩过的版本不对应的坑,比如之前有人用tinymce-vue 4在Vue3报错挂载不上,对吧?然后问题展开,第一个小问题?不对,主标题是这个大问题,然后副标题用H2 H3那种? 好的,先理框架:提问):Vue3项目中怎么完美集成并优化TinyMCE富文本编辑器? 开头先别H,先讲日常做后台、博客后台,最烦的就是选富文本编辑器对吧?试过CKEditor太老套配置难?试过Quill扩展功能麻烦?试过Vue自带的或者element-plus的Wangeditor?哦Wangeditor也不错但TinyMCE插件多功能全,像公众号排版、粘贴Word、多语言这些官方就有,这个场景引入,然后踩过的第一个坑直接抛,然后一步步讲怎么解决这些问题,就是怎么“完美”,完美不是功能全就行,还要好用(配置顺手)、加载快(首屏优化CDN还是按需npm?)、不报错(版本对应)、能满足基本需求(图片上传、粘贴格式化、多语言切换)、有常见坑的避坑指南。Vue3项目中怎么完美集成并优化TinyMCE富文本编辑器?
这段时间一直在做公司的内容管理系统,后台富文本这块纠结了快一周——试过Quill但扩展多图上传、表格编辑要自己写大半插件;Wangeditor倒是轻量,但官方适配公众号排版的模板要开付费版;最后还是绕回了TinyMCE,毕竟功能全到离谱,像公众号常用的一键美化、Word无格式粘贴、多语言、代码高亮这些,官方插件库直接拿,省了好多开发时间,不过刚上手时踩的坑可不少,比如刚打开就白屏、粘贴进来的Word文档像乱码、图片上传半天传不上去,最后翻遍了各种社区讨论、官方示例,再结合项目实际调整,才算把它“驯服”得服服帖帖,今天就把整个集成、避坑、优化的过程说清楚,应该能帮到同样选了TinyMCE的朋友。
第一步先搞懂版本对应,别一开始就白屏
好多人一开始就踩这个雷,不管Vue版本乱装tinymce-vue,结果组件一挂载就报一堆看不懂的错误,甚至直接整个编辑器区域空着。 首先要明确TinyMCE核心版本和Vue绑定库版本的对应关系:
- 如果是Vue2项目,直接用tinymce-vue@4;
- 如果是Vue3项目(不管是Composition API还是Options API,Vue3.0到最新的Vue3.4+都能用),必须装tinymce-vue@5;
- 另外TinyMCE核心和tinymce-vue@5也不是随便搭的,202X年X月,这里可以自己更,但大概是)官方推荐tinymce核心至少是6.0以上,因为5.x核心对Vue3.3+的新API支持有小bug。 核心版本和绑定库版本的对应关系非常重要,不然整个集成过程都是白搭,之前我就是图省事装了个tinymce-vue@3,结果怎么调配置都不对,后来卸载重装了5.x版本,再配合6.8核心,白屏问题一下就解决了。
第二步选好加载方式,npm还是CDN?各有各的好
版本确定后,接下来就是选加载方式了,主流的有两种:npm本地安装+按需引入插件,或者CDN远程加载核心和插件,这里给大家分析下两种方式的优缺点,方便结合自己的项目情况选。
npm本地安装:适合内网项目,或者对首屏加载速度要求不算特别极致的
首先说npm本地安装的步骤,这个步骤官方给的示例里也有,但有几个容易漏的细节:
- 先装依赖:打开终端,cd到你的Vue3项目根目录,然后执行两条命令,第一条是装绑定库,第二条是装核心:
npm install @tinymce/tinymce-vue@5 npm install tinymce
- 然后创建一个封装组件,别直接在页面里写死配置,不然以后换页面复用配置太麻烦,封装成.vue组件的话,哪里要用哪里引,还能传props动态调整,比如不同的上传接口、不同的工具栏配置,封装的时候,要注意引入核心和需要的插件、皮肤、语言包,不能只引入绑定库,不然编辑器还是会白屏或者找不到资源:
- 核心的引入位置一般是在script setup的最上面,直接import 'tinymce/tinymce'就行;
- 皮肤的话,TinyMCE 6.x默认用oxide皮肤,直接import 'tinymce/skins/ui/oxide/skin.min.css'和import 'tinymce/skins/content/default/content.min.css'就行,content.min.css是控制编辑器内容区域样式的,这个很重要,不然编辑器里写的东西看起来很奇怪;
- 语言包的话,官方默认只有英文,中文(简体)的要自己去TinyMCE官方网站下载,下载后放到项目的public目录下的tinymce-langs文件夹里,然后在配置项里设置language_url为'/tinymce-langs/zh_CN.js',注意路径别写错,public目录下的资源访问的时候直接根目录就行;
- 插件的话,按需引入就行,比如常用的advlist(高级列表)、anchor(锚点)、autolink(自动链接)、autoresize(自动调整高度)、codesample(代码高亮)、image(图片上传)、link(链接)、lists(列表)、media(媒体上传)、paste(粘贴格式化)、table(表格)、wordcount(字数统计)这些,直接import 'tinymce/plugins/插件名'就行,import几个就用几个,别全引,全引会让打包体积变大。
CDN远程加载:适合对首屏加载速度要求极致的,或者不想让打包体积变大的
CDN远程加载的优点是核心和插件都不用打包进项目,打包后的app.js会小很多,首屏加载速度也会快一些,另外更新核心和插件版本也很方便,直接改CDN链接就行;缺点是如果是内网项目,外网访问不了CDN的话就用不了,还有就是CDN有时候会抽风,加载速度变慢甚至加载失败,这时候可以用国内的镜像CDN。 CDN远程加载的步骤和npm本地安装的步骤差不多,区别主要是不用npm装核心和插件,直接在index.html的head标签里引入CDN链接就行:
- 核心CDN链接可以用TinyMCE官方的Cloud CDN,这个链接是免费的,但是有次数限制,一天免费加载次数大概是1000次?不过一般个人项目或者小公司项目够用了,要是不够用的话可以买Cloud版,或者找国内的镜像CDN;
- 要是用官方的Cloud CDN的话,配置项里要设置api_key,api_key可以去TinyMCE官方网站免费注册一个账号,然后在账号后台生成一个;
- 要是不用Cloud CDN,用国内镜像的话,就不用设置api_key,直接引入核心和插件的CDN链接就行,皮肤和语言包的话,国内镜像一般也会有,或者自己放到public目录下。
第三步封装一个好用的组件,这里给大家一个基础但够用的模板
刚才说了封装组件很重要,现在给大家一个我自己封装的、踩过坑调整过的基础模板,这个模板用的是npm本地安装+按需引入插件的方式,包含了常用的工具栏、图片上传、粘贴格式化、自动调整高度、多语言(简体中文)、字数统计这些功能,大家可以直接复制过去用,然后根据自己的项目情况调整。 封装好的组件大概长这样:
<template>
<div class="tinymce-container">
<Editor
v-model="myValue"
:init="init"
:disabled="disabled"
@onClick="onClick"
@onChange="onChange"
/>
</div>
</template>
<script setup>
import { ref, watch, defineProps, defineEmits } from 'vue'
import Editor from '@tinymce/tinymce-vue'
// 引入核心
import 'tinymce/tinymce'
// 引入皮肤
import 'tinymce/skins/ui/oxide/skin.min.css'
import 'tinymce/skins/content/default/content.min.css'
// 引入常用插件
import 'tinymce/plugins/advlist'
import 'tinymce/plugins/anchor'
import 'tinymce/plugins/autolink'
import 'tinymce/plugins/autoresize'
import 'tinymce/plugins/codesample'
import 'tinymce/plugins/image'
import 'tinymce/plugins/link'
import 'tinymce/plugins/lists'
import 'tinymce/plugins/media'
import 'tinymce/plugins/paste'
import 'tinymce/plugins/table'
import 'tinymce/plugins/wordcount'
// 定义props
const props = defineProps({
modelValue: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
},
// 可以传图片上传接口
uploadUrl: {
type: String,
default: '/api/upload/image'
},
// 可以传工具栏配置
toolbar: {
type: Array,
default: () => [
'undo redo | blocks fontfamily fontsize | bold italic underline strikethrough | forecolor backcolor | alignleft aligncenter alignright alignjustify | outdent indent | numlist bullist advlist | link image media codesample table | removeformat | wordcount'
]
}
})
// 定义emits
const emits = defineEmits(['update:modelValue', 'onClick', 'onChange'])
// 双向绑定的值
const myValue = ref(props.modelValue)
// 监听modelValue的变化
watch(() => props.modelValue, (newVal) => {
if (newVal !== myValue.value) {
myValue.value = newVal
}
})
// 监听myValue的变化
watch(myValue, (newVal) => {
emits('update:modelValue', newVal)
})
// 初始化配置
const init = ref({
// 语言设置
language: 'zh_CN',
language_url: '/tinymce-langs/zh_CN.js',
// 自动调整高度,最小高度300px,最大高度800px
autoresize_bottom_margin: 50,
autoresize_min_height: 300,
autoresize_max_height: 800,
// 自动链接
link_default_target: '_blank',
// 粘贴格式化,只保留段落、加粗、斜体、下划线、列表这些基本格式,去掉Word里的乱七八糟的格式
paste_data_images: false, // 禁止直接粘贴图片,强制用户上传到服务器,不然图片是base64的,会让数据库变得很大
paste_postprocess: function(plugin, args) {
// 这里可以再自定义粘贴后的处理逻辑,比如去掉所有的style属性
args.node.querySelectorAll('[style]').forEach(el => {
el.removeAttribute('style')
})
},
// 图片上传
images_upload_handler: function(blobInfo, success, failure) {
const formData = new FormData()
formData.append('file', blobInfo.blob(), blobInfo.filename())
// 这里用axios或者fetch都可以,我这里用的是fetch
fetch(props.uploadUrl, {
method: 'POST',
body: formData,
headers: {
// 这里可以加token之类的请求头
'Authorization': 'Bearer ' + localStorage.getItem('token')
}
})
.then(response => {
if (!response.ok) {
throw new Error('网络错误')
}
return response.json()
})
.then(data => {
// 这里根据后端返回的格式调整,一般后端返回的是一个包含图片url的对象, code: 200, data: { url: 'https://example.com/image.jpg' } }
if (data.code === 200) {
success(data.data.url)
} else {
failure(data.message || '上传失败')
}
})
.catch(error => {
failure(error.message || '上传失败')
})
},
// 图片上传后的预览
images_reuse_filename: true,
// 代码高亮的主题
codesample_languages: [
{ text: 'HTML/XML', value: 'markup' },
{ text: 'JavaScript', value: 'javascript' },
{ text: 'CSS', value: 'css' },
{ text: 'Python', value: 'python' },
{ text: 'Java', value: 'java' },
{ text: 'C++', value: 'cpp' },
{ text: 'C#', value: 'csharp' },
{ text: 'SQL', value: 'sql' }
],
codesample_theme: 'monokai',
// 工具栏配置
toolbar: props.toolbar,
// 状态栏,默认不显示品牌信息
branding: false,
// 菜单栏
menubar: 'file edit view insert format tools table help',
// 菜单栏配置
menu: {
file: { title: '文件', items: 'newdocument undo redo | preview | print' },
edit: { title: '编辑', items: 'undo redo | cut copy paste pastetext | selectall | findandreplace' },
view: { title: '视图', items: 'visualaid | spellchecker | fullscreen' },
insert: { title: '插入', items: 'link image media codesample table | anchor' },
format: { title: '格式', items: 'blocks | bold italic underline strikethrough | forecolor backcolor | alignleft aligncenter alignright alignjustify | outdent indent | removeformat' },
tools: { title: '工具', items: 'wordcount' },
table: { title: '表格', items: 'inserttable | cell row column | advtablesort | tableprops deletetable' },
help: { title: '帮助', items: 'help' }
}
})
// 点击事件
const onClick = (e) => {
emits('onClick', e)
}
// 改变事件
const onChange = (e) => {
emits('onChange', e)
}
</script>
<style scoped>
.tinymce-container {
width: 100%;
}
</style>
这个模板里有几个我踩过坑的细节,再强调一下:
- paste_data_images设置为false:这个真的很重要,要是设置为true的话,用户直接粘贴Word里的图片或者截图的话,图片会变成base64编码的字符串,这个字符串会很长很长,要是用户粘贴很多图片的话,整个文章的内容会变得非常大,存到数据库里会占用很多空间,加载的时候也会非常慢,所以一定要设置为false,强制用户上传到服务器;
- paste_postprocess自定义处理:Word里的格式真的是太乱了,有很多没用的style属性、span标签之类的,要是不处理的话,粘贴进来的文章看起来非常奇怪,而且存到数据库里也会占用很多空间,所以一定要自定义处理一下,比如去掉所有的style属性,或者只保留有用的属性;
- 双向绑定的监听:tinymce-vue的v-model有时候会有小问题,比如父组件传过来的modelValue变化了,编辑器里的内容没变化,或者编辑器里的内容变化了,父组件的modelValue没变化,所以一定要加两个watch,分别监听modelValue和myValue的变化;
- 图片上传的headers:要是你的后端接口需要token之类的请求头的话,一定要在fetch或者axios里加上,不然会报401未授权的错误;
- 图片上传后的success和failure回调:一定要记得调用这两个回调函数,不然图片上传成功后编辑器里不会显示图片,上传失败后也不会提示用户。
第四步优化,让编辑器更好用、更快
刚才的基础模板已经够用了,但要是想让编辑器更好用、更快,还可以做一些优化:
- **首屏加载优化:如果是npm本地安装的话,可以用Webpack或者Vite的代码分割,把TinyMCE的核心和插件单独打包成一个chunk,只有当用户点击打开包含编辑器的页面时才加载这个chunk,这样可以大大减少首屏加载的时间;如果是CDN远程加载的话,可以用prefetch或者preload,让浏览器在空闲的时候提前加载TinyMCE的核心和插件;
- **图片上传优化:可以加一个图片压缩的功能,在上传之前先把图片压缩一下,这样可以减少上传的时间和占用服务器的空间,图片压缩可以用tinify或者compressorjs这些库;
- **编辑器主题优化:可以自己修改TinyMCE的皮肤,让编辑器的样式和自己的项目的样式保持一致;
- **常用功能快捷键优化:可以给常用的功能加一些自定义的快捷键,比如Ctrl+S保存,Ctrl+B加粗之类的;
- **编辑器内容回显优化:要是编辑器里的内容包含视频的话,回显的时候可能会有问题,比如视频的尺寸不对,或者视频播放不了,这时候可以在content.min.css里加一些自定义的样式,或者在回显的时候对内容做一些处理。
常见的坑和解决方法
最后再给大家说几个我自己踩过的、还有在社区里看到别人踩过的常见的坑和解决方法:
- **编辑器内容区域高度不显示或者显示不全:这个一般是因为没有引入content.min.css,或者引入的路径不对,一定要检查一下;
- **图片上传后编辑器里显示的是图片地址,不是图片:这个一般是因为没有调用success回调函数,或者调用success回调函数的时候传的参数不对,一定要检查一下;
- **粘贴进来的Word文档里的图片显示不出来:这个就是刚才说的paste_data_images设置为false的问题,要是一定要显示粘贴的图片的话,可以把paste_data_images设置为true,但是不推荐这样做;
- **编辑器里的中文显示乱码:这个一般是因为没有引入中文语言包,或者引入的路径不对,一定要检查一下;
- **编辑器在弹窗里显示不出来:这个一般是因为弹窗是用v-if控制的,当v-if为true的时候,TinyMCE的核心还没有加载完,这时候可以用v-show代替v-if,或者在弹窗打开后再初始化TinyMCE的核心。
TinyMCE是一个功能非常强大的富文本编辑器,只要掌握了正确的集成、避坑、优化的方法,就可以在Vue3项目中完美使用它,希望今天的分享能帮到大家,如果大家还有什么问题,可以在评论区留言,我会尽量回答。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网



