当前位置: 首页 > web >正文

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内。

运行效果:
在这里插入图片描述

http://www.xdnf.cn/news/19209.html

相关文章:

  • 云手机技术中都有着哪些局限性?
  • mysql中cross join于普通join的区别
  • 无懈可击的 TCP AIMD
  • 网络请求优化:用 Retrofit 拦截器玩转日志、重试与缓存,OkHttp 和 Volley 谁更香?
  • STM32 USBx Device MSC standalone 移植示例 LAT1488
  • Product Hunt 每日热榜 | 2025-08-29
  • typescript postgres@3.x jsonb数据插入绕过类型检测错误问题
  • SwiGLU激活函数的原理
  • TensorFlow 面试题及详细答案 120道(51-60)-- 模型保存、加载与部署
  • 微软正在公开测试其首个完全自主训练的大语言模型——MAI-1-preview
  • python 日常学习记录
  • Java全栈开发工程师面试实录:从基础到微服务的深度技术解析
  • 【python】相机输出图片时保留时间戳数据
  • Blender模拟结构光3D Scanner(三)获取相机观测点云的真值
  • 信息系统生命周期
  • 小程序版碰一碰发视频:源码搭建与定制化开发的源头技术解析
  • CSS scale函数详解
  • nginx 怎么将 https 请求转为 http
  • Docker 实战 -- EMQX
  • 第22章笔记|把“可传参脚本”打磨成“高级好用的工具”
  • 链表(LinkedList)
  • docker compose设置命令别名的方法
  • Swift 解法详解:LeetCode 366《寻找二叉树的叶子节点》
  • 贪心算法面试常见问题分类解析
  • 微服务入门指南(一):从单体架构到服务注册发现
  • PPT处理控件Aspose.Slides教程:使用 C# 编程将 PPTX 转换为 XML
  • Pytorch超分辨率模型实现与详细解释
  • CRYPT32!CryptMsgUpdate函数分析和asn.1 editor nt5inf.cat 的总览信息
  • 机器学习回顾——逻辑回归
  • Consul 操作命令汇总 - Prometheus服务注册