AgentCPM深度研报助手:课题分析的智能解决方案
UEditor百度富文本编辑器作为经典的富文本解决方案在企业级项目中仍有广泛应用但原生 UEditor 与 Vue3 TypeScript 结合时存在配置繁琐、图片上传逻辑不统
样式控制难等问题。
本文基于你提供的代码详细讲解如何封装一个高扩展性、易维护的 Vue3 TS 版 UEditor 组件重点解决单 / 多图上传统一处理、图片尺寸控制、内容双向绑定、生命周期管理等核心痛点。
组件核心功能概览该组件基于 Vue3script setup langts语法封装兼容 UEditor 原生功能的同时做了大量优化核心功能包括基础富文本编辑文本格式化、表格、列表等原生功能单图 / 多图上传统一处理自定义上传接口、文件大小 / 格式限制图片尺寸精细化控制禁用自动缩放、自定义固定宽度、移除干扰样式双向数据绑定v-model 适配 Vue3 语法防抖优化输入体验完善的事件监听上传成功 / 失败、内容变化、失焦等生命周期自动管理组件卸载时销毁实例避免内存泄漏TypeScript 类型兼容声明全局变量避免类型报错前置准备
环境依赖Vue3 TypeScript 项目Vite/CLI 均可UEditor 静态资源包放置在/public/UEditor目录包含ueditor.all.js、dialogs等文件夹后端上传接口需兼容 UEditor 上传参数规范默认字段名upfile
资源引入在项目入口文件如main.ts或 HTML 中引入 UEditor 核心脚本也可通过组件内动态加载!-- index.html 中引入 -- script src/public/UEditor/ueditor.config.js/script script src/public/UEditor/ueditor.all.min.js/script
接口配置确保代码中dataurl指向正确的后端域名且multiImageUploadUrl配置的上传接口符合 UEditor 响应规范// 上传成功响应示例 { state: SUCCESS, url: /upload/image/20260203/
png, title:
png, original: test.png } // 多图上传成功响应数组格式 [ {state:SUCCESS,url:/upload/
png}, {state:SUCCESS,url:/upload/
png} ]功能详解与
使用方法
组件基础使用父组件引入这是最核心的使用方式通过v-model绑定编辑器内容监听上传事件template div classeditor-wrapper stylewidth: 100%; !-- UEditor 富文本组件 -- UEditor v-modelcontent :img-fixed-width80 :img-resizablefalse :multi-image-max-count9 :multi-image-max-size5 * 1024 multi-image-upload-successhandleUploadSuccess multi-image-upload-errorhandleUploadError blurhandleEditorBlur / /div /template script setup langts import { ref } from vue; import UEditor from ./components/UEditor.vue; // 绑定编辑器内容 const content refstring(); // 多图上传成功回调 const handleUploadSuccess (images: any[]) { console.log(图片上传成功, images); // images 数组包含每张图片的 url/title/original 等信息 }; // 上传失败回调 const handleUploadError (error: { message: string; data?: any }) { console.error(图片上传失败, error.message); // 可在此处提示用户如 ElMessage.error(error.message) }; // 编辑器失焦回调 const handleEditorBlur () { console.log(编辑器失焦当前内容, content.value); }; /script
核心 Props 配置说明组件提供了丰富的可配置参数满足不同业务场景需求参数名类型默认值说明modelValueString双向绑定的编辑器内容v-model 绑定imgResizableBooleanfalse是否允许图片缩放禁用后无法拖动调整尺寸imgFixedWidthNumber/String100图片固定宽度数字 百分比字符串支持 px/%如 500pxmultiImageMaxCountNumber9多图上传最大数量multiImageMaxSizeNumber5*1024单张图片最大尺寸KB默认 5MBmultiImageAllowFilesArray[.png,.jpg,.jpeg,.gif,.bmp]允许上传的图片格式multiImageUploadUrlString/pc/common/ueditor图片上传接口地址multiImageFieldNameStringupfile上传文件的表单字段名
图片上传功能单图 / 多图统一处理组件重写了 UEditor 原生的图片插入逻辑解决了单图 / 多图上传行为不一致的问题
1 单图上传使用点击编辑器工具栏的「图片」按钮 → 选择「本地上传」选择单张图片点击「上传」组件会自动校验图片格式 / 大小超出限制触发multi-image-upload-error事件调用配置的上传接口上传成功后插入图片到光标位置移除图片内联 style 属性标记data-singletrue便于识别触发multi-image-upload-success事件返回图片信息
2 多图上传使用点击编辑器工具栏的「图片」按钮 → 选择「多图上传」选择多张图片最多multiImageMaxCount张点击「开始上传」组件会批量校验图片过滤不符合格式 / 大小的图片批量上传成功后将所有图片插入到光标位置图片间自动加空格分隔标记图片data-multitrue统一控制样式若部分图片上传失败会触发multi-image-upload-error事件返回失败详情
图片样式控制核心优化点组件通过「移除内联样式 CSS 变量 MutationObserver 监听」实现图片样式统一
1 固定图片宽度组件会自动将img-fixed-width转换为 CSS 变量--img-fixed-width你只需在全局样式中添加/* 全局样式非 scoped */ .ueditor-content img { width: var(--img-fixed-width) !important; height: auto !important; // 宽高比自适应 border: none !important; float: none !important; // 禁用浮动 }若img-fixed-width80则图片宽度为 80%若img-fixed-width500px则图片宽度为 500px禁用图片自动缩放imgResizablefalse后用户无法拖动调整图片尺寸。
2 自动修正图片样式组件通过MutationObserver监听编辑器内图片插入行为确保移除 UEditor 自动添加的style属性避免干扰自定义样式为所有图片标记data-single/data-multi属性便于区分来源多次延迟修正100ms/300ms/800ms覆盖 UEditor 后续的样式修改逻辑。
内容双向绑定防抖优化组件对contentChange事件做了防抖处理避免用户输入时频繁触发父组件更新用户打字 / 粘贴时200ms 无操作后才更新v-model非用户输入如代码调用setContent时立即更新监听父组件modelValue变化同步更新编辑器内容并保留光标位置。
高级用法调用组件暴露的方法组件通过defineExpose暴露了核心实例和方法支持外部手动控制template UEditor refeditorRef v-modelcontent / button clickfixImageSize强制修正图片尺寸/button button clickgetEditorContent获取编辑器内容/button /template script setup langts import { ref } from vue; import UEditor from ./components/UEditor.vue; const editorRef refany(null); const content ref(); // 强制修正所有图片尺寸如手动修改内容后调用 const fixImageSize () { editorRef.value.forceImageFixedSize(); }; // 直接获取 UEditor 实例调用原生方法 const getEditorContent () { // 获取纯文本内容原生方法 const plainText editorRef.value.ueditorInstance.getContentTxt(); console.log(纯文本内容, plainText); // 禁用编辑器原生方法 // editorRef.value.ueditorInstance.setDisabled(); }; /script
生命周期与资源清理组件内置了完善的生命周期管理无需手动处理onMounted等待 DOM 渲染完成后初始化 UEditor避免容器未挂载onBeforeUnmount销毁 UEditor 实例、断开 MutationObserver 监听、清空引用避免内存泄漏监听error事件捕获初始化 / 上传过程中的错误便于排查问题。
关键优化点解析
重写图片插入方法原生 UEditor 的insertImage方法对多图支持不佳组件重写后数组格式的 URL 视为多图逐个插入并添加分隔空格单图插入后保留光标在图片后方提升编辑体验为图片添加自定义属性data-single/data-multi便于后续样式控制。
防抖的内容更新逻辑通过isUserTyping标记用户输入状态结合 200ms 防抖避免用户每输入一个字符就触发一次v-model更新提升性能失焦后立即重置输入状态确保内容最终同步。
多重图片尺寸修正通过「初始化修正 插入后延迟修正 MutationObserver 监听修正」覆盖 UEditor 原生的样式自动添加逻辑确保所有图片包括初始化时的已有图片、上传后的新图片都符合样式规范。
总结该组件基于 Vue3 TS 封装兼容 UEditor 原生功能解决了图片上传、样式控制、双向绑定等核心痛点使用时只需通过 Props 配置上传参数、图片尺寸通过事件监听上传状态开箱即用内置完善的生命周期管理和异常处理保证了组件的稳定性和可维护性支持高级扩展如调用原生 UEditor 方法、自定义图片样式满足复杂业务场景需求。
源码template !-- 编辑器容器通过ref获取DOM元素供UEditor挂载 -- div refeditor/div /template script setup langts // 导入Vue核心API响应式引用、生命周期钩子、监听、DOM更新后回调、计算属性 import { ref, onMounted, onBeforeUnmount, watch, nextTick, computed } from vue; //导入域名 import { dataurl } from //api/url/index; // 声明UEditor全局变量避免TS类型报错实际由外部脚本引入 declare const UE: any; // 确保window上存在loader对象用于动态加载UEditor相关资源保持原有逻辑兼容 if (!window.loader) { window.loader { // loader.load方法加载指定URL资源成功后执行回调失败执行错误回调 load: (urls: string[], callback: () void, errorCallback: (err: any) void) { const filteredUrls urls; // 筛选后的资源URL数组此处未做筛选保留原逻辑 // 若没有需要加载的资源直接执行成功回调 if (filteredUrls.length
{ callback(); return; } // 单个脚本加载函数创建script标签并返回Promise const loadScript (url: string) { return new Promise((resolve, reject) { const script document.createElement(script); // 创建script元素 script.src url; // 设置脚本URL script.onload resolve; // 加载成功触发resolve script.onerror (err) { // 加载失败触发reject并打印警告 console.warn(资源加载失败: ${dataurl}, err); reject(err); }; document.head.appendChild(script); // 将script标签添加到页面头部 }); }; // 并行加载所有资源全部成功后执行回调任意失败触发错误回调 Promise.all(filteredUrls.map(loadScript)).then(callback).catch(errorCallback); } }; } // 定义组件Props外部传入的配置项和绑定值 const props defineProps({ // 双向绑定的编辑器内容默认空字符串 modelValue: { type: String, default: }, // 图片是否可缩放默认不可缩放 imgResizable: { type: Boolean, default: false }, // 图片固定宽度支持数字/字符串默认100数字将转为百分比 imgFixedWidth: { type: [Number, String], default: 100 }, // 多图上传最大数量默认9张 multiImageMaxCount: { type: Number, default: 9 }, // 单张图片最大尺寸默认5MB单位KB multiImageMaxSize: { type: Number, default: 5 * 1024 }, // 允许上传的图片格式默认常见图片格式数组 multiImageAllowFiles: { type: Array, default: () [.png, .jpg, .jpeg, .gif, .bmp] }, // 多图上传接口地址默认后端UEditor上传接口 multiImageUploadUrl: { type: String, default: 图片上传接口 }, // 上传文件的字段名默认upfile与后端接口约定 multiImageFieldName: { type: String, default: upfile }, }); // 定义组件事件向父组件传递状态和数据 const emit defineEmits([ update:modelValue, // 内容更新事件用于v-model双向绑定 blur, // 编辑器失焦事件 multi-image-upload-success, // 多图上传成功事件 multi-image-upload-error // 多图上传失败事件 ]); // 编辑器容器DOM引用用于挂载UEditor实例 const editor refHTMLElement | null(null); // UEditor实例对象存储初始化后的编辑器实例 let ueditorInstance: any null; // 图片尺寸修正锁防止重复执行修正逻辑 let isFixingImages false; // 图片插入监听者用于监听编辑器内图片插入操作 let imageInsertObserver: MutationObserver | null null; // 内容更新锁防止modelValue监听与编辑器内部更新冲突 let isContentUpdating false; // 计算属性处理图片固定宽度统一格式数字转为百分比字符串直接使用 const fixedWidth computed(() { if (typeof props.imgFixedWidth number) { return ${props.imgFixedWidth}%; // 数字类型默认按百分比处理如100 → 100% } // 字符串类型直接返回支持px/%等自定义单位如500px、80% return props.imgFixedWidth as string; }); // 动态更新CSS变量让全局样式能获取到最新的图片宽度配置 const updateCssVariable () { // 向根元素添加--img-fixed-width变量供全局样式使用 document.documentElement.style.setProperty(--img-fixed-width, fixedWidth.value); }; // 生命周期钩子组件挂载完成后执行 onMounted(() { updateCssVariable(); // 初始化CSS变量设置初始图片宽度 nextTick(() { // 等待DOM更新完成后初始化UEditor确保editor容器已渲染 initUEditor(); }); }); // 生命周期钩子组件卸载前执行清理资源 onBeforeUnmount(() { destroyUEditor(); // 销毁UEditor实例 if (imageInsertObserver) { imageInsertObserver.disconnect(); // 断开MutationObserver监听 } }); // 初始化UEditor编辑器 const initUEditor () { // 校验依赖编辑器容器不存在或UEditor未加载则终止 if (!editor.value || !UE) return; // 配置UEditor全局参数影响所有编辑器实例 if (UE.config) { UE.config.UEDITOR_HOME_URL /public/UEditor; // UEditor资源根路径需确保对应目录存在 UE.config.imageAutoSize false; // 禁用图片自动调整尺寸 UE.config.imageScale false; // 禁用图片缩放 UE.config.retainImageSize false; // 不保留图片原始尺寸 UE.config.initialFrameHeight 200; // 编辑器初始高度200px // 图片管理多图浏览相关配置 UE.config.imageManagerActionName listimage; // 图片管理接口动作名 UE.config.imageManagerListPath /upload/image/; // 图片存储路径 UE.config.imageManagerUrlPrefix ; // 图片URL前缀为空则使用相对路径 UE.config.imageManagerInsertAlign none; // 插入图片时不设置对齐方式 UE.config.imageManagerAllowFiles props.multiImageAllowFiles; // 图片管理允许的文件格式 UE.config.imageManagerPageSize 30; // 图片管理分页大小每页30张 // 核心配置统一单图/多图上传参数 UE.config.imageMultiUpload true; // 启用多图上传 UE.config.imageActionName uploadimage; // 图片上传接口动作名 UE.config.imageFieldName props.multiImageFieldName; // 上传文件字段名 UE.config.imageMaxSize props.multiImageMaxSize; // 单张图片最大尺寸 UE.config.imageAllowFiles props.multiImageAllowFiles; // 允许上传的图片格式 UE.config.imageCompressEnable true; // 启用图片压缩 UE.config.imageCompressBorder 1200; // 图片压缩最大边长超过则压缩 UE.config.imageCompressQuality
8; // 图片压缩质量
8为80% UE.config.imageInsertAlign none; // 插入图片时不设置对齐方式 UE.config.imageUrlPrefix ; // 图片URL前缀为空则使用相对路径 // 关键配置多图上传弹窗路径指定UEditor内置弹窗页面 UE.config.imagePopupUrl ${UE.config.UEDITOR_HOME_URL}/dialogs/image/image.html; UE.config.imageManagerPopupUrl ${UE.config.UEDITOR_HOME_URL}/dialogs/image/imageManager.html; // 上传接口地址统一使用多图上传接口避免重复配置 UE.config.serverUrl props.multiImageUploadUrl; // 关键修复禁用UEditor默认的图片自动尺寸和浮动功能 UE.config.imageEnableAutoSize false; UE.config.imageEnableFloat false; } // 创建UEditor实例绑定到editor容器传入实例专属配置 ueditorInstance UE.getEditor(editor.value, { initialFrameWidth: 100%, // 编辑器宽度100%自适应 initialFrameHeight: 200, // 编辑器初始高度200px autoHeightEnabled: false, // 禁用自动高度调整 serverUrl: props.multiImageUploadUrl, // 上传接口地址实例级覆盖 // 单图上传配置与多图统一确保参数一致 imageFieldName: props.multiImageFieldName, imageActionName: uploadimage, imageAllowFiles: props.multiImageAllowFiles, imageMaxSize: props.multiImageMaxSize, imageCompressEnable: true, imageCompressBorder: 1200, imageCompressQuality:
8, enableScaleImage: props.imgResizable, // 是否允许图片缩放由props控制 enableFloatImage: false, // 禁用图片浮动 retainImageSize: false, // 不保留图片原始尺寸 enableCodeHighlight: false, // 禁用代码高亮 enableAutoSave: false, // 禁用自动保存 imageEnableAutoSize: false, // 再次禁用自动尺寸确保生效 // 多图上传增强配置 imageMultiUpload: true, // 启用多图上传 imageManagerActionName: listimage, // 图片管理接口动作名 imageManagerListPath: /pc/common/ueditor, // 图片管理接口路径 imageManagerAllowFiles: props.multiImageAllowFiles, // 允许的图片格式 imageManagerMaxCount: props.multiImageMaxCount, // 多图上传最大数量 enableCORS: false, // 禁用跨域处理根据后端配置调整 timeout: 60000, // 上传超时时间60秒 }); // UEditor实例初始化完成后的回调确保编辑器已就绪 ueditorInstance.ready(() { // 初始化编辑器内容将props.modelValue传入编辑器 ueditorInstance.setContent(props.modelValue); // 内容变化监听逻辑优化用户输入体验 let contentChangeTimer: number | null null; // 内容变化防抖计时器 let isUserTyping false; // 是否正在用户输入 let lastUserActionTime 0; // 最后一次用户操作时间 // 监听键盘按下事件标记用户正在输入 ueditorInstance.addListener(keydown, () { isUserTyping true; lastUserActionTime Date.now(); }); // 监听键盘松开事件更新最后操作时间 ueditorInstance.addListener(keyup, () { isUserTyping true; lastUserActionTime Date.now(); }); // 监听鼠标松开事件更新最后操作时间如粘贴、点击等操作 ueditorInstance.addListener(mouseup, () { lastUserActionTime Date.now(); }); // 监听编辑器内容变化事件触发v-model更新 ueditorInstance.addListener(contentChange, () { if (isContentUpdating) return; // 内容更新中则跳过避免循环触发 // 若用户正在输入使用防抖延迟更新避免频繁触发父组件事件 if (isUserTyping) { if (contentChangeTimer) { window.clearTimeout(contentChangeTimer); // 清除之前的计时器 } // 200ms防抖用户停止输入200ms后再更新内容 contentChangeTimer window.setTimeout(() { emit(update:modelValue, ueditorInstance.getContent()); // 向父组件发送内容更新事件 // 300ms后检查是否仍无用户操作重置输入状态 setTimeout(() { if (Date.now() - lastUserActionTime
{ isUserTyping false; } },
; },
; } else { // 非用户输入如代码触发的内容变化立即更新 emit(update:modelValue, ueditorInstance.getContent()); } }); // 监听编辑器失焦事件向父组件发送blur事件 ueditorInstance.addListener(blur, () { isUserTyping false; // 失焦后重置输入状态 emit(blur); }); // 重写图片插入方法优化单图/多图插入逻辑 const originalInsertImage ueditorInstance.insertImage; // 保存原始插入图片方法 // 重写insertImage统一处理单图/多图插入保留宽高属性优化光标位置 ueditorInstance.insertImage function ( url: string | string[], alt , href , width , height , border 0, align ) { const currentRange this.selection.getRange(); // 获取当前光标选区 if (Array.isArray(url)) { // 多图插入逻辑 url.forEach((imgUrl, index) { const img this.document.createElement(img); // 创建img元素 img.src imgUrl; // 设置图片URL img.alt alt; // 设置图片alt属性 img.setAttribute(data-multi, true); // 标记为多图上传的图片 // 保留传入的宽高属性若有 if (width) img.width width; if (height) img.height height; currentRange.insertNode(img); // 将图片插入光标位置 // 图片之间插入空格最后一张图片后不插入 if (index url.length -
{ const space this.document.createTextNode( ); currentRange.insertNode(space); currentRange.setStartAfter(space); // 光标移到空格后 currentRange.setEndAfter(space); } }); // 光标移到最后一张图片后面 const lastImg currentRange.document.querySelector(img[data-multi]:last-of-type); if (lastImg) { currentRange.setStartAfter(lastImg); currentRange.setEndAfter(lastImg); } } else { // 单图插入逻辑 const img this.document.createElement(img); // 创建img元素 img.src url; // 设置图片URL img.alt alt; // 设置图片alt属性 img.setAttribute(data-single, true); // 标记为单图上传的图片 // 保留传入的宽高属性若有 if (width) img.width width; if (height) img.height height; currentRange.insertNode(img); // 将图片插入光标位置 } currentRange.collapse(false); // 光标折叠到插入内容后面 this.selection.setRange(currentRange); // 恢复光标位置 this.focus(); // 编辑器重新获取焦点 // 延迟触发内容变化事件确保DOM已更新 setTimeout(() { this.fireEvent(contentChange); },
; }; // 重写insertImages方法适配多图上传插件的调用逻辑 ueditorInstance.setOpt(insertImages, function (images: any[]) { const imageUrls images.map(img img.url || img.src); // 提取图片URL数组 return ueditorInstance.insertImage.call(this, imageUrls); // 调用重写后的insertImage }); // 监听图片插入使用MutationObserver确保所有图片都被处理 if (ueditorInstance.document ueditorInstance.document.body) { // 创建MutationObserver监听编辑器内容区的DOM变化 imageInsertObserver new MutationObserver((mutations) { let hasNewImage false; // 标记是否有新图片插入 mutations.forEach(mutation { // 遍历所有新增的节点 if (mutation.addedNodes.length
{ Array.from(mutation.addedNodes).forEach(node { const element node as HTMLElement; // 查找新增节点中的图片直接是img或包含img的元素 const imgs element.tagName IMG ? [element] : Array.from(element.querySelectorAll(img)); if (imgs.length
{ hasNewImage true; imgs.forEach(img { // 标记未分类的图片为单图 if (!img.hasAttribute(data-multi) !img.hasAttribute(data-single)) { img.setAttribute(data-single, true); } // 移除内联style属性避免UEditor默认样式干扰 if (img.hasAttribute(style)) { img.removeAttribute(style); } }); } }); } }); // 有新图片插入时执行图片尺寸修正多重延迟确保生效 if (hasNewImage) { safeImageSizeFix(true); // 立即修正 setTimeout(() safeImageSizeFix(true),
; // 300ms后再次修正 setTimeout(() safeImageSizeFix(true),
; // 800ms后第三次修正 } }); // 启动监听监听编辑器body的子节点变化、子树变化、属性变化 imageInsertObserver.observe(ueditorInstance.document.body, { childList: true, // 监听子节点增减 subtree: true, // 监听所有子树包括嵌套元素 attributes: true, // 监听元素属性变化 attributeFilter: [style, width, height], // 只监听关键属性优化性能 characterData: false // 不监听文本内容变化 }); } // 监听单图上传完成事件确保上传后图片尺寸正确 ueditorInstance.addListener(afterUpload, function (type: string, result: any) { // 仅处理图片类型上传且上传成功的情况 if (type image result result.state SUCCESS) { // 多重延迟修正覆盖UEditor上传后的后续处理 setTimeout(() safeImageSizeFix(true),
; setTimeout(() safeImageSizeFix(true),
; setTimeout(() safeImageSizeFix(true),
; } }); // 监听单图上传插入事件强化单图处理 ueditorInstance.addListener(afterInsertImage, function (tid: string, imgs: any[]) { // 延迟200ms处理确保图片已插入DOM setTimeout(() { if (imgs imgs.length
{ imgs.forEach((imgInfo: any) { const imgUrl imgInfo.src || imgInfo.url; // 提取图片URL if (imgUrl) { const doc ueditorInstance.document; // 同时查找编辑器主文档和iframe内的图片避免遗漏 const iframeDoc doc.querySelector(iframe)?.contentDocument || doc; const allImgs [...doc.querySelectorAll(img), ...iframeDoc.querySelectorAll(img)]; allImgs.forEach((img: HTMLImageElement) { // 匹配刚上传的图片通过URL模糊匹配 if ((img.src.includes(imgUrl) || img.src.includes(imgUrl.split(/).pop())) !img.hasAttribute(data-single)) { img.setAttribute(data-single, true); // 标记为单图 if (img.hasAttribute(style)) { img.removeAttribute(style); // 移除内联样式 } } }); } }); safeImageSizeFix(true); // 修正图片尺寸 } },
; // 800ms后再次修正确保覆盖UEditor的后续操作 setTimeout(() { if (imgs imgs.length
{ safeImageSizeFix(true); } },
; }); // 多图上传事件监听向父组件传递上传状态 ueditorInstance.addListener(uploadSuccess, function (type: string, response: any) { if (type ! image) return; // 仅处理图片上传 try { // 解析上传响应兼容字符串和对象类型 const result typeof response string ? JSON.parse(response) : response; if (Array.isArray(result)) { // 多图上传响应数组格式分离成功和失败的图片 const successImages result.filter((item: any) item.state SUCCESS); const errorImages result.filter((item: any) item.state ! SUCCESS); if (successImages.length
{ emit(multi-image-upload-success, successImages); // 发送成功事件 } if (errorImages.length
{ emit(multi-image-upload-error, { // 发送部分失败事件 message: 部分图片上传失败${errorImages.length}张, data: errorImages }); } } else if (result result.state SUCCESS) { // 单图上传成功对象格式 emit(multi-image-upload-success, [result]); } else { // 上传失败响应格式正确但状态错误 emit(multi-image-upload-error, { message: result?.state || 多图上传失败, data: result }); } } catch (error) { // 响应解析失败如JSON格式错误 emit(multi-image-upload-error, { message: 多图上传响应解析失败, error: error }); } }); // 监听多图上传失败事件 ueditorInstance.addListener(uploadError, function (type: string, xhr: XMLHttpRequest, error: any) { if (type ! image) return; // 仅处理图片上传 // 发送上传失败事件包含状态码、响应内容等信息 emit(multi-image-upload-error, { message: 上传失败状态码${xhr.status}, status: xhr.status, response: xhr.responseText, error: error }); }); // 初始化时修正已有图片确保页面加载的图片符合配置 setTimeout(() { safeImageSizeFix(true); },
; }); // 监听UEditor初始化错误事件 ueditorInstance.addListener(error, function (error: any) { console.error(UEditor初始化错误:, error); emit(multi-image-upload-error, { message: UEditor初始化失败, error: error }); }); }; /** * 安全的图片尺寸修正强化版 * 核心逻辑移除内联style属性保留width/height属性标记图片类型单图/多图 * param force 是否强制执行忽略isFixingImages锁 */ const safeImageSizeFix (force false) { // 校验依赖编辑器实例或文档不存在则终止 if (!ueditorInstance || !ueditorInstance.document) return; // 非强制模式下若正在修正则跳过避免重复执行 if (!force isFixingImages) return; isFixingImages true; // 开启修正锁 try { const doc ueditorInstance.document; // 同时获取主文档和iframe内的图片覆盖所有可能的图片容器 const iframeDoc doc.querySelector(iframe)?.contentDocument || doc; const bodyImg doc.body ? doc.body.querySelectorAll(img) : []; const iframeImg iframeDoc.body ? iframeDoc.body.querySelectorAll(img) : []; const imgElements [...bodyImg, ...iframeImg]; // 合并所有图片元素 imgElements.forEach((img: HTMLImageElement) { // 移除内联style属性避免UEditor默认样式或用户手动设置的样式干扰 if (img.hasAttribute(style)) { img.removeAttribute(style); } // 标记未分类的图片为单图确保所有图片都有类型标记 if (!img.hasAttribute(data-multi) !img.hasAttribute(data-single)) { img.setAttribute(data-single, true); } }); } catch (error) { console.warn(安全修正图片尺寸时发生错误:, error); } finally { // 释放修正锁强制模式延迟200ms释放避免短时间内重复触发 if (!force) { isFixingImages false; } else { setTimeout(() isFixingImages false,
; } } }; // 销毁UEditor实例组件卸载时清理资源 const destroyUEditor () { if (ueditorInstance) { try { UE.delEditor(editor.value); // 从UE全局移除编辑器 ueditorInstance.destroy(); // 销毁实例 ueditorInstance null; // 清空实例引用 } catch (error) { console.warn(销毁UEditor时发生错误:, error); } } if (imageInsertObserver) { imageInsertObserver.disconnect(); // 断开MutationObserver监听 imageInsertObserver null; // 清空监听者引用 } }; // 监听modelValue变化同步外部更新到编辑器 watch( () props.modelValue, // 监听props中的modelValue (newValue) { // 编辑器未初始化或正在更新内容则跳过 if (!ueditorInstance || isContentUpdating) return; const currentContent ueditorInstance.getContent(); // 获取编辑器当前内容 // 新值与当前内容不一致时才更新避免不必要的操作 if (newValue ! currentContent) { isContentUpdating true; // 开启内容更新锁 // 保存当前光标选区更新后恢复提升用户体验 const currentRange ueditorInstance.selection.getRange(); const isRangeValid currentRange !currentRange.collapsed; // 判断选区是否有效 ueditorInstance.setContent(newValue); // 将新值设置到编辑器 // 延迟恢复选区和修正图片确保DOM已更新 setTimeout(() { try { if (isRangeValid currentRange) { ueditorInstance.selection.setRange(currentRange); // 恢复光标选区 } ueditorInstance.focus(); // 编辑器获取焦点 safeImageSizeFix(true); // 修正图片尺寸确保新内容中的图片符合配置 } catch (e) { // 恢复选区失败时至少保持焦点并修正图片 ueditorInstance.focus(); safeImageSizeFix(true); } isContentUpdating false; // 释放内容更新锁 },
; } } ); // 暴露组件方法供外部调用 defineExpose({ ueditorInstance, // 暴露UEditor实例供外部直接操作 forceImageFixedSize: () { // 暴露强制修正图片尺寸的方法 safeImageSizeFix(true); } }); /script style scoped langscss /* 组件样式此处留空可根据需求添加编辑器容器样式 */ /style
少女自愈骑枕头视频高清-少女自愈骑枕头视频高清应用