☰
wangEditor图片视频上传配置实践与常见坑点全解析
2026/10/15 5:26:09 网站建设 项目流程

wangEditor 是我在项目里用得比较多的开源富文本编辑器,轻量、API 直观,和 Vue、React 都能很快接上。但“轻量”的反面就是很多功能要自己动手配,尤其是图片和视频上传,默认行为基本等于不可用状态。最近帮一个内部后台系统做升级,又通了一遍从配置到踩坑的完整流程,顺手把其中最关键的部分拆出来,给正打算接这个编辑器的朋友一点参考。

先说结论:wangEditor 本身不存文件,也不替你找图床,它只负责把你丢给它的文件转成一段可用的 URL 或 base64,再写进编辑区。所以你要解决的核心问题只有一个——把本地文件变成可访问的资源地址。搞清楚这一点,剩下的事情都是围绕它展开。

1. 先想明白 wangEditor 上传背后的数据链路

接 wangEditor 的第一件事,不是急着装包,而是先理解它处理图片和视频的两种路径——默认的 base64 方案和自定义上传方案。前者适合体验,后者适合生产,两个方案的切换成本很低,但理解不到位会埋一堆雷。

1.1 默认的 base64 模式为什么只适合临时用

wangEditor 开箱即用的时候,你在工具栏点“上传图片”,它会直接把图片转成 base64 字符串塞进 HTML 的 img 标签里。效果就是图片确实“上传”了,但也仅限于编辑器内部能看到,刷新页面、发给别人、提交到数据库……这些场景一律崩掉。一是因为 base64 会让内容体积膨胀 30% 以上,二是因为它压根没有落盘,别人拿到的是一堆数据字符,不是可访问的链接。

这种模式适合什么场景?内部测试、原型演示、或者你确认内容只在当前页面停留。但凡涉及真实业务,比如公告发布、商品详情、工单系统,就必须走自定义上传——把文件丢给后端,拿到真实 URL,再把 URL 插入编辑区。

1.2 上传路径不同,但触发点都在“菜单配置”

wangEditor v5 的上传能力挂在MENU_CONF这个配置项上。你可以在初始化编辑器的时候,手动指定图片和视频各自上传菜单的行为。命令大致是两条:uploadImage控制图片相关,uploadVideo控制视频相关。每条命令下都支持自定义方法customUpload、请求地址server、以及各种参数。

需要注意:v4 和 v5 的写法差异很大,网上很多旧教程是 v4 的“挂载到editor.config”思路,直接搬到 v5 会静默失效。如果你在调试中发现配置项“好像不生效”,先确认版本。

import { createEditor, createToolbar } from '@wangeditor/editor' const editorConfig = { placeholder: '请输入内容...', MENU_CONF: { uploadImage: { // 这里放图片上传配置 }, uploadVideo: { // 这里放视频上传配置 } } } const editor = createEditor({ selector: '#editor-container', html: '', config: editorConfig, mode: 'default' })

我习惯在 Vite + Vue3 项目里用这种显式初始化方式,比依赖组件库封装的默认行为更可控。

1.3 上传的字段名和返回格式,前后端必须先对齐

这一步很关键,直接决定联调顺不顺。wangEditor 官方约定的返回 JSON 结构是:

{ "errno": 0, "data": { "url": "https://example.com/uploads/2024/01/01/abc.jpg" } }

errno必须是数字 0,data.url必须是可直接访问的完整地址。后端如果返回{ "code": 0 }或者{ "status": 200 },编辑器是认不出来的,到时候你会发现上传接口明明通了、文件也存了,可编辑器里死活不出现图片。

所以我在做前后端分工的时候,会直接把这段格式写进接口文档,甚至贴给后端同事看,省得他们自由发挥。这条约定同样适用于视频——视频插进编辑器后,其实也是一段<video>标签加 src,格式完全一样。

2. 图片上传:从零到一配置出可用的方案

图片上传看着简单,实际配置时牵扯的点很多:定制请求头、动态加参、文件类型限制、多图支持、还有上传中的状态反馈。一个一个说。

2.1 基础版:直接让编辑器帮你发请求

如果后端已经有现成的图片上传接口,你只想最快跑通,可以用server配置。wangEditor 会自动构造表单请求发过去,你只需要告诉它接口地址和表单字段名。

MENU_CONF: { uploadImage: { server: '/api/upload', // 你的上传接口 fieldName: 'file', // 后端接收文件的字段名 maxFileSize: 10 * 1024 * 1024, // 限制10MB maxNumberOfFiles: 5, // 一次最多传5张 allowedFileTypes: ['image/*'], metaWithUrl: true, // 上传时把meta拼成query参数还是form字段 meta: { token: localStorage.getItem('token') } } }

这种方案有个小坑:如果后端要求动态 token,而你的 token 是登录后临时拿到的,配置meta时不能直接取localStorage,因为编辑器初始化早于登录状态的情况很常见。我一般会在初始化前先确保 token 存在,或者让后端把 token 校验的优先级调低,待上传时再校验。

另外,metaWithUrl: true表示 meta 里的字段会以查询参数形式拼到 URL 上。如果 URL 已经带了一些查询参数,而你又开了这个开关,可能出现参数冲突。我在自己项目里统一用 false,让 meta 走 form 字段,避免各种拼接问题。

2.2 升级版:用 customUpload 接管整条链路

server方案虽然省事,但不够灵活。比如你想给图片做前端压缩、想先传到一个私有 OSS 再拿签名 URL、或者想统计上传进度、发错误监控,这些行为都插不进默认流程。这时候用customUpload,它把“选择文件后的处理权”完全交给你。

MENU_CONF: { uploadImage: { customUpload: async (file, insertFn) => { // file 是编辑器内部传来的原始文件对象 // insertFn 是编辑器提供的回调,参数是图片最终 URL const formData = new FormData() formData.append('file', file) const res = await fetch('/api/upload/image', { method: 'POST', headers: { 'Authorization': 'Bearer ' + getToken() }, body: formData }) const json = await res.json() if (json.errno === 0) { insertFn(json.data.url, json.data.alt || '', json.data.href || '') } else { // 这里要拦截错误,因为不回调的话编辑器会一直处于上传等待状态 throw new Error(json.message || '上传失败') } } } }

关键点是insertFn这个回调。它接收的参数分别是 URL、alt 文本、超链接地址。没调用insertFn的后果非常隐蔽:编辑器不会报错,但图片就是不出现,而且上传按钮会一直转圈,仿佛文件丢了。我在项目里排查过一次,前后查了半小时才发现是insertFn没被调用。

所以写customUpload的时候,我给自己立了个规矩:无论上传成功、失败、异常,最终都要走insertFn或者显式抛错。前者让编辑器插入内容,后者让编辑器停止 loading 状态。两头至少占一个。

2.3 多图上传、截图粘贴和拖拽上传的处理差异

wangEditor 对三种图片来源的处理逻辑不同,如果你只配置了工具栏按钮上传,其他入口可能还是默认行为:

  • 工具栏选择文件:走uploadImage配置
  • 从剪贴板粘贴图片(比如截图工具直接 Ctrl+V):同样走uploadImage配置,默认会把图片放进编辑区
  • 拖拽文件到编辑区:默认行为是插入 base64 或提示不支持,你也可以改

我建议统一改掉菜单配置下的所有行为,别让用户从不同入口传图得到不同结果。v5 里图片菜单有个base64LimitSize的配置,默认 2M,意思是小于 2M 的图片粘贴时直接转 base64,大于 2M 才走上传逻辑。这个阈值要结合你的服务器承受能力设,如果图片经常超过 2M,就要把 maxFileSize 和服务器限制一起调大,否则中间有个槛会卡住。

多图上传时,如果后端接口一次性只能收一个文件,不要依赖 wangEditor 帮你拆。我碰到过后端一次只能收单图的情况,就在customUpload里做循环串行上传,每次请求插一个insertFn,视觉上图片一张一张出现,体验反而更清晰。还要注意并发数,浏览器对同一域名的并发连接数有限制,批量传 20 张图时并发开太大,会直接拖垮接口响应。

2.4 文件类型校验别只依赖 allowedFileTypes

allowedFileTypes在编辑器端做了第一层过滤,但这只是“选择框”层面的限制,不阻挡程序化设置的文件。真正需要的校验应该在customUpload里做二次确认:读取file.type、检查后缀名、限制文件大小,甚至用canvas去读取图片宽高做异常检测。

我记得有一次,用户从微信里存的图片直接拖进编辑器,后缀名是.jpeg,但实际内容是 HEIF 格式,部分浏览器预览不出来,后端也没拦。后来在customUpload里加了一个“已知图片类型白名单”的判断,非白名单直接弹提示,出了问题也能第一时间定位。

3. 视频上传:和图片逻辑相同,坑却完全不同

视频上传的配置入口和图片基本平行,都是在MENU_CONF下,但真实项目里视频的坑主要集中在三个地方:大小、格式、播放兼容性。

3.1 基本配置与校验项

MENU_CONF: { uploadVideo: { maxFileSize: 50 * 1024 * 1024, // 50MB,按需调整 allowedFileTypes: ['video/*'], customUpload: async (file, insertFn) => { const formData = new FormData() formData.append('file', file) const res = await fetch('/api/upload/video', { method: 'POST', headers: { 'Authorization': 'Bearer ' + getToken() }, body: formData }) const json = await res.json() if (json.errno === 0) { insertFn(json.data.url) } } } }

视频的insertFn参数只有一个 URL,没有 alt 和 href。这和图片不同,写错了也不会立刻报错,但插入的内容会出现异常结构。我这边的经验是:视频插入后的 HTML 是<video src="..."></video>,wangEditor 会帮你在编辑区内嵌播放器,但前端展示时得确认你有自己的视频样式或者播放控制逻辑,别指望一个裸 video 标签在所有浏览器里都可用。

3.2 大视频的切片与临时凭证思路

文件一大,直接上传的风险就上来了:网络闪断要整个重来、服务器超时要重新排队、甚至浏览器内存直接爆掉。如果你的业务允许用户上传 1GB 的视频,就要考虑切片上传。

wangEditor 的customUpload里拿到的file对象,你可以直接拿去切片,不需要借助编辑器内部逻辑。我常用方案是先按 5MB 一片切分,逐片上传,后端记录分片索引,传完再合并。进度条自己实现,用它驱动一个状态展示在编辑区上方,用户体验好很多。

不过这里有个协调成本:编辑器本身的上传进度 UI 不会接管你的自定义逻辑,所以要么自己做进度反馈,要么尽可能把视频大小限制在 100MB 以内,走单请求直传,让上传逻辑保持简单。大多数后台系统的业务场景,50MB 到 100MB 已经够用。

3.3 视频上传后的缩略图与封面

另一个容易被忽略的点:用户传完视频,内容卡片在列表页显示什么。很多系统只显示一个视频文件图标,观感很粗糙。解决办法是上传视频时额外生成或让用户自定义封面图。这个不属于 wangEditor 的范畴,但属于“视频上传”整个需求的自然延伸。我在项目里会让前端上传视频后把返回的首帧图 URL 存下来,渲染列表时盖在视频上,比默认控件好看得多。wangEditor 的视频菜单也有一个customInsert回调,可以在视频插入时带上 poster 属性,这一步可以直接写进 HTML 结构,减少后处理成本。

4. 我在真实项目中踩过的三个隐蔽坑

4.1 上传成功后编辑器里不显示图片

现象:接口返回 200,文件存到了服务器,返回格式严格按照{errno: 0, data: {url: '...'}},但图片就是不出现在光标位置。

排查过程:先打开浏览器 Network 面板,确认请求确实返回了正确数据。接着在customUpload里加 console.log,确认回调有没有走。最后发现问题出在insertFn必须是同步调用吗?不是,我的调用时机太晚——我在 fetch 之后又做了一次异步的图片压缩,压缩耗时较长,编辑器认为上传超时,自动重置了状态。此时再调用insertFn,内容插入位置错乱。

解决:把压缩逻辑前置,在文件进入customUpload之前就处理好,保证insertFn在编辑器等待的时间内被调用。如果压缩确实耗时,可以先把一个 loading 占位图插入编辑区,再用真实的 URL 替换。不过替换逻辑比直接等待复杂,我建议能前置就前置。

另外还要检查一件事:你的 HTML 结构里是否已经存在一个设置了contenteditable="false"的元素把光标挡住了。有些后端模板会输出异常标签,导致输入区光标失效,insertFn虽然被调用了,但落点不对。碰到这种情况,把编辑器重新初始化或者清空异常节点能解决。

4.2 视频能上传,却播放不了

现象:视频文件成功插入编辑器,src地址在浏览器里手动访问也能打开,但编辑器内视频控件无法播放,点击播放按钮没反应。

排查过程:看浏览器控制台报错,发现是跨域问题。上传接口返回的 URL 用的是另一个域名,而页面主域名和视频文件域名不一致,且视频响应头里没有Access-Control-Allow-Origin。浏览器阻止了跨域媒体加载。听起来离谱,但在本地开发环境特别容易触发——本地是 localhost,上传走的是测试服域名。

解决:后端需要给视频文件所在域名配置 CORS 头,或者在开发环境让上传接口返回的 URL 跟当前页面同域——用一个反向代理把上传文件和页面放在同一个源下。这也是我在项目里推荐把静态资源和页面放在同一个网关后面的原因,减少很多跨域边缘问题。

需要注意:跨域问题视频比图片更敏感,图片跨域最多是显示不了,但通过 canvas 读取像素数据时也会被拦;视频跨域直接播放键失效,很容易误判为文件损坏。

4.3 工具栏里有视频按钮,却找不到上传入口

现象:配置完MENU_CONF后,上传视频按钮倒是出现了,但点开只有“插入网络视频”的输入框,没有“上传视频”选项。

原因:wangEditor 的工具栏入口分得很细。上传视频是一个独立菜单项,和“网络视频”是分开的两个按钮。你的 toolBarKeys 配置里很可能只加入了group-video,没把uploadVideo这个具体菜单加进去。

解决:在toolbarKeys里显式添加uploadVideo和insertVideo:

const toolbarConfig = { toolbarKeys: [ 'group-image', 'group-video', 'uploadVideo', 'insertVideo', // 其他菜单... ] }

如果不放心,可以直接用editor.getConfig().toolbarKeys打印出当前实际生效的键值,对着官方文档逐一核对。这种“按钮在但功能不全”的坑,源头都是配置项没有完整展开,不是编辑器 bug。

5. 生产环境落地前,再做一遍加固

5.1 统一封装上传函数,别在页面里散落 fetch

我的做法是把上传逻辑抽成一个服务文件,比如services/upload.js,编辑器配置里只引用它。好处是接口地址统一管理、错误监控集中处理,后续切换 OSS、换上传供应商时,只改一个文件。

这个封装里要额外处理两件事:token 过期时自动触发重新登录流程;上传失败超过三次后给用户明确提示。wangEditor 内部不会替你重试,多传失败时需要手动刷新页面再操作。

5.2 内容安全比上传本身更重要

编辑器只是录入工具,真正的内容清洗在保存环节。图片和视频 URL 需要做合法性校验,防注入、防外链恶意资源。用户上传的 HTML 里可能夹带脚本,wangEditor v5 默认做了一些过滤,但服务端必须再做一次白名单过滤。

我在后端清洗逻辑里,对img标签只放行受信任的域名规则,对video标签强制controls属性和poster域名校验,其余一律丢弃。前端看着是正常内容,提交到服务端时已经被剥了一层壳,这样就算用户故意构造消息体,也攻不进存储层。

5.3 优化界面反馈:上传成功、失败、删除时的状态同步

编辑器上传图片成功后,编辑区会出现预览图,但如果用户后悔了,直接删除图片,服务器上的文件并不会被清理,会长年累月堆积垃圾数据。我在项目里做了个“删除联动”的简单方案:监听编辑器的 change 事件,定期比对内容里的图片/视频地址和服务器上实际创建的临时文件列表,超过一定时间仍没有出现在内容中的文件,就走一遍清理接口。

这个方案不用做得很复杂,一个定时任务就够。粗粒度的清理远比不做强,生产环境跑起来后,存储成本肉眼可见地降下来。

最后再分享一个小技巧:如果你在 Vue 或 React 里用 wangEditor,记得在组件销毁时调用editor.destroy(),不然快速切换路由时很容易看到内存泄漏的警告。编辑器实例的配置不再生效的报错,十有八九是忘了销毁重建。上传这件事说到底不算复杂,但得把每个环节都想到,尤其是数据格式和生命周期,这两块稳了,基本上不会出大问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询