在 Vue 3 中集成 WangEditor 富文本编辑器:从基础到实战

依赖包@wangeditor/editor-for-vue@5.1.12

富文本编辑器是现代 Web 应用中不可或缺的组件,尤其在 CMS、博客、后台管理系统等场景中广泛使用。本文将带你一步步在 Vue 3 项目中集成 WangEditor (国产开源富文本编辑器),并通过一个完整的 index.vue 组件示例,详细解析其配置项、事件处理、图片上传、表格支持及只读模式等高级功能。


一、为什么选择 WangEditor?

WangEditor 是一款轻量、易用、中文文档完善的国产富文本编辑器,具有以下优势:

  • 原生支持 Vue/React
  • 插件化架构,可按需扩展
  • 支持 Markdown、表格、代码块、公式等
  • 图片/视频上传配置灵活
  • 社区活跃,中文支持好

本文基于 @wangeditor/editor-for-vue(v5+),适用于 Vue 3 Composition API。


二、安装依赖

复制代码
npm install @wangeditor/editor-for-vue @wangeditor/editor

注意:editor-for-vue 是 Vue 封装层,底层仍依赖 @wangeditor/editor

同时引入 CSS 样式:

复制代码
import '@wangeditor/editor/dist/css/style.css'

三、完整组件代码解析(index.vue

以下是我们在项目中封装的富文本组件,支持内容传入、高度控制、只读模式、图片上传、表格操作等。

1. 模板结构

复制代码
<template>
  <div style="border: 1px solid #ccc">
    <!-- 工具栏 -->
    <Toolbar
      v-if="showToolbarFlag"
      :editor="editorRef"
      :defaultConfig="toolbarConfig"
      :mode="mode"
      style="border-bottom: 1px solid #ccc"
    />
    <!-- 编辑器主体 -->
    <Editor
      v-model="valueHtml"
      :defaultConfig="editorConfig"
      :mode="mode"
      @onCreated="handleCreated"
      @onChange="handleChange"
      :style="{ height: editorHeight, overflowY: 'hidden' }"
      :readOnly="readOnlyFlag"
    />
  </div>
</template>
  • 使用 <Toolbar><Editor> 两个组件。
  • 通过 v-model 绑定 HTML 内容(实际通过 valueHtml 手动同步)。
  • readOnly 控制是否可编辑(但更推荐使用 editor.disable(),见下文)。

2. 脚本逻辑(Composition API + TypeScript)

(1)引入与 Props 定义
复制代码
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import '@wangeditor/editor/dist/css/style.css'
import base from "@/utils/base.js"

const props = defineProps({
  content: { type: String, default: '' },
  showToolbarFlag: { type: Boolean, default: true },
  editorHeight: { type: String, default: '500px' },
  readOnlyFlag: { type: Boolean, default: false }
})

const emit = defineEmits(['update'])
  • content:父组件传入的 HTML 字符串。
  • update 事件:将编辑后的内容回传给父组件,实现"双向绑定"。
(2)响应式变量
复制代码
const mode = ref('default')
const editorRef = shallowRef() // 必须用 shallowRef,避免 Vue 深度追踪
const valueHtml = ref('')

⚠️ 注意:editorRef 必须使用 shallowRef,因为编辑器实例包含大量非响应式属性,深度响应会导致性能问题甚至报错。

(3)编辑器配置 editorConfig
复制代码
const editorConfig = {
  placeholder: '请输入内容,单个文件的最大10MB...',
  MENU_CONF: {
    // 表格配置
    insertTable: {
      withBorder: true,
      maxRow: 10,
      maxCol: 6,
      onInserted(tableNode) {
        console.log('插入的表格:', tableNode)
      }
    },
    // 表格悬停工具栏自定义
    hoverbarKeys: {
      table: {
        menuKeys: [
          'tableHeader',
          'insertTableRow', 'deleteTableRow',
          'insertTableCol', 'deleteTableCol',
          'deleteTable'
        ]
      }
    }
  }
}
表格功能说明:
  • withBorder: true:默认带边框。
  • maxRow/maxCol:限制行列数,防止页面卡顿。
  • hoverbarKeys:自定义表格悬停时显示的操作菜单。
(4)图片上传配置
复制代码
editorConfig.MENU_CONF['uploadImage'] = {
  fieldName: 'file', // 后端接收的字段名
  server: `${base.baseUrl}${base.project}/admin/oss/upload`,
  maxFileSize: 10 * 1024 * 1024, // 10MB
  maxNumberOfFiles: 10,
  allowedFileTypes: ['image/*'],
  timeout: 10000,
  meta: {
    token: sessionStorage.getItem("token") // 携带认证信息
  },
  // 自定义插入逻辑
  customInsert(res, insertFn, file) {
    // 假设后端返回 { url: 'https://xxx.jpg' }
    insertFn(res.url, res.url, res.url)
  },
  onFailed(file, res) {
    console.log(`${file.name} 上传失败`, res)
  }
}

✅ 关键点:

  • customInsert 决定了如何将上传结果插入编辑器。
  • meta 可携带 token、用户 ID 等,用于后端鉴权。
  • fieldName 必须与后端接口参数名一致。
(5)生命周期与事件处理
初始化编辑器
复制代码
const handleCreated = (editor) => {
  editorRef.value = editor
  if (props.readOnlyFlag) {
    editor.disable() // 真正的只读(比 :readOnly 更可靠)
  } else {
    editor.enable()
  }
}

📌 推荐使用 editor.disable() 而非 :readOnly 属性,后者在某些版本中可能失效。

内容变化监听
复制代码
const handleChange = () => {
  valueHtml.value = editorRef.value.getHtml()
  emit('update', valueHtml.value)
}
监听父组件 content 更新
复制代码
watch(() => props.content, (newVal) => {
  nextTick(() => {
    if (editorRef.value) {
      editorRef.value.setHtml(newVal)
      valueHTML.value = newVal
    }
  })
})

💡 使用 nextTick 确保 DOM 更新后再操作编辑器。

延迟初始化(防错处理)
复制代码
onMounted(async () => {
  await nextTick()
  setTimeout(() => {
    if (props.content && editorRef.value) {
      try {
        editorRef.value.setHtml(props.content)
      } catch (error) {
        // 防止非法 HTML(如未闭合标签)导致崩溃
        const cleanHtml = props.content.replace(/<table[^>]*>.*?<\/table>/gis, '')
        editorRef.value.setHtml(cleanHtml || '<p></p>')
      }
    }
  }, 100)
})

✅ 加入 try-catch 和表格清理,提升鲁棒性。

销毁编辑器(内存泄漏防护)
复制代码
onBeforeUnmount(() => {
  editorRef.value?.destroy()
})

3. 样式定制(表格美化)

复制代码
<style scoped>
.w-e-table {
  border-collapse: collapse;
  width: 100%;
  margin: 10px 0;
}
.w-e-table td, .w-e-table th {
  border: 1px solid #ddd;
  padding: 8px;
  min-width: 50px;
  height: 20px;
  vertical-align: top;
}
.w-e-table th {
  background-color: #f2f2f2;
  text-align: left;
}
</style>

WangEditor 的表格默认样式较简陋,可通过覆盖 .w-e-table 类进行美化。


四、父组件使用示例

复制代码
<template>
  <RichTextEditor 
    :content="article.content" 
    @update="handleContentUpdate"
    :editorHeight="'400px'"
    :readOnlyFlag="isPreviewMode"
  />
</template>

<script setup>
const article = reactive({ content: '<p>初始内容</p>' })
const handleContentUpdate = (html) => {
  article.content = html
}
const isPreviewMode = false
</script>

五、常见问题与解决方案

问题 解决方案
编辑器内容不更新 确保使用 editor.setHtml() 而非直接赋值 v-model
图片上传 401 检查 meta 是否携带有效 token
表格样式错乱 覆盖 .w-e-table 样式,设置 border-collapse
初始化空白 使用 setTimeout 延迟 100ms 设置内容
内存泄漏 务必在 onBeforeUnmount 中调用 destroy()

六、总结

通过本文的 index.vue 组件,我们实现了:

  • ✅ 双向绑定内容
  • ✅ 自定义图片上传(带 token)
  • ✅ 表格增强(行列操作、样式美化)
  • ✅ 只读/编辑模式切换
  • ✅ 安全初始化与销毁

WangEditor 在 Vue 3 中的集成非常友好,配合 TypeScript 和 Composition API,能构建出稳定、可维护的富文本解决方案。

🔗 官方文档:https://www.wangeditor.com/


欢迎点赞、收藏、评论交流!如有疑问,可在评论区留言~

相关推荐
No Silver Bullet2 小时前
HarmonyOS NEXT开发进阶(二十三):多端原生App中通过WebView嵌套Web应用实现机制
前端·华为·harmonyos
光影少年2 小时前
react和vue中的优点和缺点都有哪些
前端·vue.js·react.js
web_Hsir2 小时前
uniapp + vue2 + pfdjs + web-view 实现安卓、iOS App PDF预览
android·前端·uni-app
css趣多多2 小时前
画系统的整体思路
vue.js
EndingCoder2 小时前
Node.js 与 TypeScript:服务器端开发
前端·javascript·typescript·node.js
打小就很皮...2 小时前
基于 React 实现 Vditor 的可复用 Markdown 渲染组件
前端·react.js·markdown·vditor
EndingCoder2 小时前
React 与 TypeScript:组件类型化
前端·javascript·react.js·typescript·前端框架
沛沛老爹2 小时前
Web开发者实战:多模态Agent技能开发——语音交互与合成技能集成指南
java·开发语言·前端·人工智能·交互·skills
皮卡穆2 小时前
Vue3 + Swiper.js 实现无缝轮播图组件
前端·javascript·vue