Tinymce富文本编辑器封装
Tinymce版本:“tinymce”: “^8.0.2”,
官网文档:https://www.tiny.cloud/docs/tinymce
使用项目环境:vue3
封装内容:
1.图片上传
2.防止弹出框被遮挡
3.从网页复制选中内容,粘贴时内容保持样式不变。
tinymce组件:
<template><div :id="editorId"></div>
</template><script>
import { defineComponent, nextTick, onMounted, reactive, watch, ref, getCurrentInstance, onUnmounted } from 'vue';
import { globalHeaders } from '@/utils/request';export default defineComponent({props: {modelValue: {type: String,default: ''}},setup(props, { emit }) {// 生成唯一IDconst editorId = ref(`editor-${Math.random().toString(36).substr(2, 9)}`);const editorRef = ref(null);const { proxy } = getCurrentInstance();// 上传配置const upload = reactive({headers: globalHeaders(),url: import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload'});// 图片上传前验证const validateFile = (file) => {const type = ['image/jpeg', 'image/jpg', 'image/png', 'image/svg'];const isValidType = type.includes(file.type);if (!isValidType) {proxy?.$modal.msgError('图片格式错误!');return false;}const fileSize = 5; // MBconst isLt = file.size / 1024 / 1024 < fileSize;if (!isLt) {proxy?.$modal.msgError(`上传文件大小不能超过 ${fileSize} MB!`);return false;}return true;};// 页面加载时onMounted(() => {// 添加全局CSS样式确保TinyMCE弹出窗口在最上层const style = document.createElement('style');style.textContent = `.tox-dialog,.tox-dialog-wrap,.tox-dialog__backdrop,.tox-tinymce-aux,.tox-menu,.tox-collection,.tox-pop,.tox-silver-sink {z-index: 99999 !important;position: fixed !important;}.tox-toolbar-overlord {z-index: 99999 !important;position: static !important;max-width: 100% !important;overflow: hidden !important;}`;document.head.appendChild(style);// 富文本编辑器nextTick(() => {window.tinymce.init({selector: `#${editorId.value}`, // 选择器,指定哪个元素使用 TinyMCElicense_key: 'gpl', // 许可密钥,如果是 GPL 版本则不需要设置language: 'zh_CN', // 语言设置width: '100%', // 编辑器宽度height: 500, // 编辑器高度menubar: true, // 是否显示菜单栏statusbar: true, // 是否显示状态栏branding: false, // 去除底部的 TinyMCE 广告promotion: false, // 去除右上角的跳转链接// 设置更高的z-index基础值z_index: 99999,init_instance_callback: function(editor) {// 监听弹出窗口事件editor.on('OpenWindow', function(e) {setTimeout(() => {// 查找所有TinyMCE相关的弹出元素const elements = document.querySelectorAll('.tox-dialog, .tox-dialog-wrap, .tox-dialog__backdrop, ' +'.tox-tinymce-aux, .tox-menu, .tox-toolbar-overlord, ' +'.tox-collection, .tox-pop');elements.forEach(el => {el.style.zIndex = '99999';// 弹出框保持fixed定位,工具栏使用static定位if (el.classList.contains('tox-toolbar-overlord')) {el.style.position = 'static';el.style.maxWidth = '100%';el.style.overflow = 'hidden';} else {el.style.position = 'fixed';}});}, 10);});},plugins: ['advlist','autolink','lists','link','image','charmap','preview','anchor','searchreplace','visualblocks','code','fullscreen','insertdatetime','media','table','help','wordcount','emoticons','autosave','quickbars','codesample',], // 启用的插件列表toolbar: ['code formatselect fontselect fontsizeselect forecolor backcolor bold italic underline strikethrough link alignment outdent indent bullist numlist blockquote subscript superscript removeformat table image media importword charmap pagebreak formatpainter cut copy undo redo restoredraft searchreplace fullscreen'], // 工具栏按钮列表toolbar_sticky: true, // 工具栏固定在顶部content_css: '/path/to/content.css', // 自定义内容样式文件路径content_style: `h2 { position: relative; z-index: 99; }h2::before {content: "";display: block;width: 200px;height: 8px;position: absolute;bottom: 6px;left: -4px;z-index: -1;border-radius: 4px 0 0 4px;background: linear-gradient(90deg, #F6AFB0 0%, #FFFFFF 100%);}img {max-width: 100%;height: auto;}`, // 自定义编辑器内容的样式// 粘贴配置paste_retain_style_properties: 'text-align color background-color font-size font-weight font-style text-decoration',paste_merge_formats: true,paste_auto_cleanup_on_paste: false,paste_remove_styles_if_webkit: false,images_upload_handler: (blobInfo, progress) => new Promise((resolve, reject) => {// 创建文件对象进行验证const file = {type: blobInfo.blob().type,size: blobInfo.blob().size,name: blobInfo.filename()};// 验证文件if (!validateFile(file)) {reject('文件验证失败');return;}proxy?.$modal.loading('正在上传文件,请稍候...');const formData = new FormData();formData.append('file', blobInfo.blob(), blobInfo.filename());const xhr = new XMLHttpRequest();xhr.withCredentials = false;xhr.open('POST', upload.url);xhr.upload.onprogress = (e) => {progress(e.loaded / e.total * 100);};// 设置请求头Object.keys(upload.headers).forEach(key => {xhr.setRequestHeader(key, upload.headers[key]);});xhr.onload = () => {proxy?.$modal.closeLoading();if (xhr.status === 200) {try {const res = JSON.parse(xhr.responseText);console.log(44, res)if (res.code === 200) {resolve(res.data.url); // 上传成功,返回图片 URL} else {reject('上传失败: ' + (res.msg || '未知错误'));proxy?.$modal.msgError('图片上传失败'); }} catch (e) {reject('响应解析失败');proxy?.$modal.msgError('图片上传失败');}} else {reject('HTTP Error: ' + xhr.status);proxy?.$modal.msgError('图片上传失败');}};xhr.onerror = () => {proxy?.$modal.closeLoading();reject('网络错误');proxy?.$modal.msgError('图片上传失败');};xhr.send(formData);}),setup: (editor) => {// 保存编辑器实例的引用editorRef.value = editor;// 编辑器初始化完成后设置初始内容editor.on('init', () => {if (props.modelValue) {editor.setContent(props.modelValue);}});editor.on('change', () => {emit('update:modelValue', editor.getContent()); // 监听编辑器内容变化并更新表单内容});}});});});// 在setup中添加onUnmounted(() => {if (editorRef.value) {window.tinymce.remove(editorRef.value);editorRef.value = null;}});// 监听 modelValue 的变化,同步更新编辑器内容watch(() => props.modelValue,(newValue) => {if (editorRef.value && editorRef.value.getContent() !== newValue) {editorRef.value.setContent(newValue || '');}});return {editorId};}
});
</script><style>
/* 全局样式,确保TinyMCE弹出窗口在el-dialog之上 */
:global(.tox-dialog),
:global(.tox-dialog-wrap),
:global(.tox-dialog__backdrop),
:global(.tox-tinymce-aux),
:global(.tox-menu),
:global(.tox-collection),
:global(.tox-pop),
:global(.tox-silver-sink) {z-index: 99999 !important;position: fixed !important;
}:global(.tox-toolbar-overlord) {z-index: 99999 !important;position: static !important;max-width: 100% !important;overflow: hidden !important;
}/* 添加内联样式,确保在所有情况下都能覆盖 */
:deep(.tox-dialog),
:deep(.tox-dialog-wrap),
:deep(.tox-dialog__backdrop),
:deep(.tox-tinymce-aux),
:deep(.tox-menu),
:deep(.tox-collection),
:deep(.tox-pop),
:deep(.tox-silver-sink) {z-index: 99999 !important;position: fixed !important;
}:deep(.tox-toolbar-overlord) {z-index: 99999 !important;position: static !important;max-width: 100% !important;overflow: hidden !important;
}
</style>
扩展:
tinymce插件使用方法:
1.安装npm install tinymce@^8
2.把node_modules/tinymce文件夹复制到/public下,复制后就可以npm uninstall tinymce了
3.index.html引入
<script src="/tinymce/tinymce.min.js" referrerpolicy="origin" crossorigin="anonymous"></script>
4.使用tinymce组件
<tinymce v-model="form.content"/>
编译器显示英文问题:插件默认不带语言包,需要官网下载中文语言包放到tinymce/langs内。
运行效果: