TinyMCE 是一款开源、可高度定制的富文本编辑器,广泛用于 Web 应用中(如 CMS 系统、博客平台、论坛、表单编辑器等),支持用户通过可视化界面编辑文本、插入图片、表格、链接、媒体等内容,最终输出标准化的 HTML 代码。
- 丰富的功能与扩展:内置超过 50 个插件(如表格、代码高亮、图像上传等),并开放 400 多个 API 接口,便于深度集成与功能定制。 13
- 现代兼容性:自 v5 版本起对 Web 标准支持优秀,界面设计符合现代审美,并提供浅色(oxide)和深色(oxide-dark)主题。
Vue3后台权限管理系统整合 tinymce。
首先看演示:https://www.eleadmin.cn
看完演示直接上代码
封装vue组件
<template>
<main>
<editor v-model="myValue" :init="init" :disabled="disabled" :id="tinymceId"></editor>
</main>
</template>
<script setup>
import { reactive, ref, onMounted, watch } from 'vue'
import axios from 'axios'
import { getToken } from '@/utils/auth'
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 'tinymce/icons/default/icons'
import 'tinymce/plugins/table'
import 'tinymce/plugins/lists'
import 'tinymce/plugins/image'
import 'tinymce/plugins/wordcount'
import 'tinymce/plugins/fullscreen'
import 'tinymce/plugins/preview'
import 'tinymce/plugins/searchreplace'
import 'tinymce/plugins/autolink'
import 'tinymce/plugins/advlist'
import 'tinymce/plugins/code'
import 'tinymce/plugins/anchor'
import 'tinymce/plugins/autoresize'
import 'tinymce/plugins/autosave'
import 'tinymce/plugins/charmap'
import 'tinymce/plugins/codesample'
import 'tinymce/plugins/directionality'
import 'tinymce/plugins/importcss'
import 'tinymce/plugins/insertdatetime'
import 'tinymce/plugins/link'
import 'tinymce/plugins/media'
import 'tinymce/plugins/nonbreaking'
import 'tinymce/plugins/pagebreak'
import 'tinymce/plugins/quickbars'
import 'tinymce/plugins/save'
import 'tinymce/plugins/visualblocks'
import 'tinymce/plugins/visualchars'
const emits = defineEmits(['update:modelValue', 'getContent'])
const props = defineProps({
modelValue: {
type: String,
default: () => {
return ''
}
},
value: {
type: String,
default: () => {
return ''
}
},
baseUrl: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
},
fileSize: {
type: Number,
default: 5 // 默认5MB
},
plugins: {
type: [String, Array],
default: `lists
table
image
wordcount
fullscreen
preview
searchreplace
autolink
advlist
anchor
autosave
charmap
codesample
directionality
importcss
insertdatetime
link
media
nonbreaking
pagebreak
quickbars
save
visualblocks
visualchars
code
`
}, //必填
toolbar: {
type: [String, Array],
default: `code codesample bold italic underline alignleft aligncenter alignright alignjustify |
undo redo |
fontfamily fontsize blocks |
formatselect |
forecolor backcolor |
bullist numlist outdent indent |
lists link unlink table code |
removeformat |
image media |
lineheight |
anchor blockquote orderedlist |
wordcount preview |
fullscreen
`
} //必填
})
//用于接收外部传递进来的富文本
const myValue = ref(props.modelValue || 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: 500, //编辑器高度
branding: false, //是否禁用"Powered by TinyMCE"
menubar: true, //顶部菜单栏显示
toolbar_mode: 'wrap', // 工具栏换行显示
image_dimensions: false, //去除宽高属性
image_title: true, //是否开启图片标题设置
automatic_uploads: true, //自动上传
images_upload_url: import.meta.env.VITE_APP_BASE_API + '/common/upload', //图片上传接口
images_upload_base_path: import.meta.env.VITE_APP_BASE_API, //图片上传基础路径
images_upload_credentials: true, //是否发送cookie
plugins: props.plugins, //这里的数据是在props里面就定义好了的
toolbar: props.toolbar, //这里的数据是在props里面就定义好了的
font_family_formats:
'Arial=arial,helvetica,sans-serif; 宋体=SimSun; 微软雅黑=Microsoft Yahei; Impact=impact,chicago;', //字体
font_size_formats: '11px 12px 14px 16px 18px 24px 36px 48px 64px 72px', //文字大小
// fontsize_formats: '8pt 10pt 12pt 14pt 18pt 24pt 36pt', // 第二步
// paste_convert_word_fake_lists: false, // 插入word文档需要该属性
paste_webkit_styles: 'all',
paste_merge_formats: true,
nonbreaking_force_tab: false,
file_picker_types: 'file',
promotion: false,
content_css: '/tinymce/skins/content/default/content.css', //以css文件方式自定义可编辑区域的css样式,css文件需自己创建并引入
//图片上传
images_upload_handler: (blobInfo, progress) =>
new Promise((resolve, reject) => {
// 检查文件大小
if (blobInfo.blob().size / 1024 / 1024 > props.fileSize) {
reject({ message: `上传失败,图片大小请控制在 ${props.fileSize}M 以内`, remove: true })
return
}
// 检查文件类型
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp']
if (!allowedTypes.includes(blobInfo.blob().type)) {
reject({ message: '上传失败,只支持 JPG、PNG、GIF、WebP 格式的图片', remove: true })
return
}
const formData = new FormData()
formData.append('file', blobInfo.blob(), blobInfo.filename())
// 获取认证token
const token = getToken()
const config = {
headers: {
'Content-Type': 'multipart/form-data',
Authorization: token ? `Bearer ${token}` : ''
}
}
// 使用项目的上传接口
const uploadUrl = '/common/upload'
axios
.post(uploadUrl, formData, config)
.then((res) => {
if (res.data.code === 200) {
// 返回完整的图片URL
const imageUrl = res.data.fileName
resolve(imageUrl)
} else {
reject('上传失败: ' + (res.data.msg || '未知错误'))
}
})
.catch((error) => {
console.error('图片上传失败:', error)
reject('上传失败: ' + (error.response?.data?.msg || error.message || '网络错误'))
})
}),
// 文件选择器回调
file_picker_callback: (callback, value, meta) => {
// 创建文件输入元素
const input = document.createElement('input')
input.setAttribute('type', 'file')
input.setAttribute('accept', 'image/*')
input.onchange = function () {
const file = this.files[0]
if (!file) return
// 检查文件大小
if (file.size / 1024 / 1024 > props.fileSize) {
alert(`图片大小不能超过 ${props.fileSize}MB`)
return
}
// 检查文件类型
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp']
if (!allowedTypes.includes(file.type)) {
alert('只支持 JPG、PNG、GIF、WebP 格式的图片')
return
}
const formData = new FormData()
formData.append('file', file)
// 获取认证token
const token = getToken()
const config = {
headers: {
'Content-Type': 'multipart/form-data',
Authorization: token ? `Bearer ${token}` : ''
}
}
// 上传文件
const uploadUrl = '/common/upload'
axios
.post(uploadUrl, formData, config)
.then((res) => {
if (res.data.code === 200) {
const imageUrl = import.meta.env.VITE_APP_BASE_API + res.data.fileName
callback(imageUrl, { alt: file.name })
} else {
alert('上传失败: ' + (res.data.msg || '未知错误'))
}
})
.catch((error) => {
console.error('文件上传失败:', error)
alert('上传失败: ' + (error.response?.data?.msg || error.message || '网络错误'))
})
}
input.click()
}
})
//监听外部传递进来的的数据变化
watch(
() => props.modelValue || props.value,
(newVal) => {
if (newVal !== myValue.value) {
myValue.value = newVal
emits('getContent', myValue.value)
emits('update:modelValue', myValue.value)
}
}
)
//监听富文本中的数据变化
watch(
() => myValue.value,
(newVal) => {
emits('getContent', newVal)
emits('update:modelValue', newVal)
}
)
</script>
页面引用组件
<TEditor v-model="form.content" :min-height="300" style="width: 100%"></TEditor>
大家看演示效果,请移步:https://www.eleadmin.cn
有问题可以私聊哦。