🚧 Vue2 项目富文本替换实践:从 UEditor 到 WangEditor Next
📌 项目背景
项目原本使用的是 UEditor
,但遇到一些不可忽视的问题:
- ❌ 官方已停止维护,BUG 无人修复
- ❌ 体积庞大,功能臃肿,加载缓慢
- ❌ UI 过时,扩展性差
因此,我们决定迁移到更现代、更灵活的富文本编辑器 ------ WangEditor Next
。
💡 为什么选 WangEditor Next?
- ✅ 支持 Vue3 版本,也兼容 Vue2(通过组件封装)
- ✅ 中文社区活跃,文档完善,上手简单
- ✅ 支持模块化扩展,便于定制图片、视频上传等逻辑
⚠️ 遇到的实际问题 & 解决方向
1️⃣ 自定义上传图片、视频
WangEditor 默认使用 base64 插图或请求默认接口,实际项目需要:
- 上传图片到对象存储(如阿里 OSS、七牛云)
- 成功后插入返回的 URL 地址
📍可通过配置 customUpload
钩子,实现完全自定义上传逻辑。
2️⃣ Word 粘贴带有本地图片(file://
)
很多用户直接从 Word 中复制内容粘贴进编辑器,此时会出现类似:
ini
<img src="file://C:/Users/xxx/AppData/Local/...">
✔️ 解决思路:
- 拦截粘贴内容
- 查找
<img src^="file://">
标签并移除或提示用户重新插入图片
3️⃣ 拖拽外网图片插入
用户可能直接拖一张网页图片进编辑器,此时图片地址可能是外链,比如:
ini
<img src="https://xxx.com/image.jpg">
⚠️ 问题在于:
- 外链图片容易失效或跨域加载失败
- 项目需要统一上传图片到自有服务
✔️ 解决思路:
- 拦截
drop
事件
package.json 文件
js
"@wangeditor-next/editor": "^5.6.34",
"@wangeditor-next/editor-for-vue2": "^1.0.2",
Wandeditor 组件
js
<template>
<div style="border: 1px solid #ccc">
<Toolbar style="border-bottom: 1px solid #ccc" :editor="editor" :defaultConfig="toolbarConfig" :mode="mode" />
<Editor ref="editorRef" style="height: 500px; overflow-y: hidden" v-model="html" :defaultConfig="editorConfig" :mode="mode" @onCreated="onCreated" @onChange="onChange" />
<base-button @click="getHtml">获取HTML</base-button>
</div>
</template>
<script>
import { Editor, Toolbar } from '@wangeditor-next/editor-for-vue2'
// 上传文件Promise的方法, 成功后返回:文件名/系统文件地址/文件预览地址 {fileName,filePath,previewUrl}
import { uploadFile } from '@/components/common/common-upload/index.js'
export default {
components: { Editor, Toolbar },
data() {
return {
editor: null,
html: '',
// 禁用菜单功能
toolbarConfig: { excludeKeys: ['fontFamily', 'insertImage', 'insertVideo'] },
editorConfig: {
placeholder: '请输入内容...',
maxLength: 10000,
pasteIgnoreImg: true,
// 配置自定义上传图片和视频
MENU_CONF: {
uploadImage: {
customUpload: this.customUploadImage
},
uploadVideo: {
customUpload: this.customUploadVideo
}
},
// 可根据需求,配置图片和视频的操作功能
hoverbarKeys: {
image: { menuKeys: ['imageWidth30', 'imageWidth50', 'imageWidth100', 'editorImageSizeMenu', 'deleteImage'] },
video: { menuKeys: ['editVideoSize'] }
}
},
mode: 'default' // or 'simple'
}
},
created() {},
methods: {
onCreated(editor) {
this.editor = Object.seal(editor) // 一定要用 Object.seal() ,否则会报错
},
// 自定义-上传图片
customUploadImage(file, insertFn) {
uploadFile(file)
.then(fileData => {
const { fileName, previewUrl } = fileData
insertFn(previewUrl, fileName, previewUrl)
})
.catch(err => {
this.$message({
type: 'error',
message: this.$t('Upload_failed')
})
})
},
// 自定义-上传视频
customUploadVideo(file, insertFn) {
uploadFile(file)
.then(async fileData => {
const { fileName, previewUrl } = fileData
insertFn(previewUrl)
})
.catch(err => {
this.$message({
type: 'error',
message: this.$t('Upload_failed')
})
})
},
// 处理从外部复制/粘贴的图片地址问题
onChange(editor) {
let images = editor.getElemsByType('image')
let fileOsArray = []
images.forEach(item => {
// fileOsArray记录本地图片
if (item.src.startsWith('file://')) {
fileOsArray.push(item.src)
}
})
if (!fileOsArray.length) return
// 替换所有本地图片,提示错误图片信息
let html = editor.getHtml()
fileOsArray.forEach(fileOs => {
// 使用正则替换所有相同的 file:// 路径(可能重复)
const escaped = fileOs.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // 正则转义
html = html.replace(
new RegExp(escaped, 'g'),
// 这里可以改成你需要替换的图片地址或者图片base64
'data:image/png;base64,***'
)
})
editor.setHtml(html)
},
getHtml() {
const html = this.editor.getHtml()
console.log(html)
},
handleDrop(e) {
e.preventDefault()
e.stopPropagation()
}
},
mounted() {
// 禁止拖拽事件
const el = this.$refs.editorRef.$el
if (el) {
el.addEventListener('drop', this.handleDrop, true)
el.addEventListener('dragover', this.handleDrop, true)
}
setTimeout(() => {
this.html = '<p>模拟接口获取数据</p>'
}, 1000)
},
beforeDestroy() {
const el = this.$refs.editorRef.$el
if (el) {
el.removeEventListener('dragover', this.handleDrop, true)
el.removeEventListener('drop', this.handleDrop, true)
}
const editor = this.editor
if (editor == null) return
editor.destroy() // 组件销毁时,及时销毁编辑器
}
}
</script>
<style src="@wangeditor-next/editor/dist/css/style.css"></style>

