Vue3 零基础每日笔记(081):富文本编辑器集成 wangEditor 5——封装

一、前言

公告、文章、商品详情都需要富文本(加粗/图片/列表)。本篇集成国内最常用的 wangEditor 5:Vue3 官方适配层 + 图片上传配置 + 封装成组件。先说明:wangEditor 5 官方已停止维护但依然稳定可用;若求新可评估 Tiptap/Quill,本篇以企业存量最常见的 wangEditor 为准。

bash 复制代码
npm i @wangeditor/editor @wangeditor/editor-for-vue@next
# ★ Vue3 必须装 @next 标签版本(默认 latest 是 Vue2 组件层)

二、核心组件结构

wangEditor 的 Vue 封装分两块:Toolbar(工具栏)+ Editor(编辑区),各占一行布局:

vue 复制代码
<!-- src/components/RichEditor.vue -->
<template>
  <div class="rich-editor">
    <Toolbar class="toolbar" :editor="editorRef" :defaultConfig="toolbarConfig" mode="default" />
    <Editor
      class="editor"
      v-model="valueHtml"
      :defaultConfig="editorConfig"
      mode="default"
      style="height: 300px"
      @onCreated="handleCreated"
    />
  </div>
</template>

<script setup lang="ts">
import { ref, shallowRef, onBeforeUnmount } from 'vue'
import { Toolbar, Editor } from '@wangeditor/editor-for-vue'
import '@wangeditor/editor/dist/css/style.css'    // ★ 样式必须引入

// v-model:编辑内容(HTML 字符串)
const html = defineModel<string>({ default: '' })
const valueHtml = html                            // 语义别名,模板更直观

// ★ 编辑器实例用 shallowRef:实例内部结构复杂,不需要深度响应式(079 篇同款原则)
const editorRef = shallowRef()

const toolbarConfig = { excludeKeys: ['group-video'] }   // 排除视频组(按需)
const editorConfig = { placeholder: '请输入内容...' }

const handleCreated = (editor: any) => {
  editorRef.value = editor
}

// ★★★ 组件销毁前必须销毁编辑器实例,否则内存泄漏 + 切换路由报错
onBeforeUnmount(() => {
  editorRef.value?.destroy()
})
</script>

<style scoped>
.rich-editor { border: 1px solid var(--el-border-color); z-index: 1; }
.toolbar { border-bottom: 1px solid var(--el-border-color); }
</style>

三个关键点:

  1. CSS 必须引入 (@wangeditor/editor/dist/css/style.css),否则工具栏布局全乱
  2. 实例用 shallowRef(080 篇 ECharts 同款:重实例不做深度响应式)
  3. onBeforeUnmount 必调 destroy()------本篇头号大坑

三、使用:像普通表单一样绑 v-model

vue 复制代码
<!-- src/views/ArticleEditView.vue -->
<template>
  <el-input v-model="title" placeholder="文章标题" style="margin-bottom: 12px" />
  <RichEditor v-model="content" />
  <el-button type="primary" style="margin-top: 12px" @click="onSubmit">保存</el-button>

  <!-- 预览:富文本是 HTML,展示用 v-html -->
  <div class="preview" v-html="content"></div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import RichEditor from '@/components/RichEditor.vue'

const title = ref('')
const content = ref('<p>初始内容...</p>')

async function onSubmit() {
  // 真实项目:await createArticle({ title: title.value, html: content.value })
  console.log('提交 HTML:', content.value)
}
</script>

【截图位置:工具栏 + 编辑区输入"加粗文字和图片";下方预览区同步渲染】

四、图片上传配置

默认图片转 base64 塞进 HTML(文章一长提交体积爆炸),配置成上传服务器:

ts 复制代码
const editorConfig = {
  placeholder: '请输入内容...',
  MENU_CONF: {
    uploadImage: {
      // ★ 上传接口(063 篇的 /api 前缀方案)
      server: '/api/upload/image',
      fieldName: 'file',                     // 后端接收的文件字段名
      maxFileSize: 2 * 1024 * 1024,          // 2MB 限制
      // 后端返回格式约定:{ code: 0, data: { url: 'xxx' } } 时做映射
      customInsert(res: any, insertFn: (url: string, alt?: string) => void) {
        insertFn(res.data.url)               // ★ 把图片 url 插进编辑器
      }
    }
  }
}

后端约定:上传成功返回图片 URL,customInsert 负责把 URL 写进正文------对接任何后端格式都能适配。

五、踩坑记录

  1. 忘 destroy() :路由切换后控制台报错、内存泄漏------onBeforeUnmount 里 editorRef.value?.destroy(),本篇头号坑
  2. Vue3 装了 Vue2 的组件层 :@wangeditor/editor-for-vue 默认版本是 Vue2 组件------必须 @next
  3. 样式错乱/工具栏堆叠 :CSS 没引入------import '@wangeditor/editor/dist/css/style.css'
  4. 弹窗里 z-index 冲突 :工具栏下拉被 el-dialog 盖住------给编辑器容器加 z-index: 1(高于弹窗遮罩的层叠上下文),或弹窗 append-to-body
  5. 编辑器实例包 ref/reactive :深度代理内部复杂对象导致卡顿报错------shallowRef(080 篇同款)
  6. v-html 直接渲染用户内容:XSS 注入风险(085 篇 Checklist 有安全项)------展示端由后端过滤 XSS,或前端 DOMPurify 处理后再 v-html
  7. 回显内容丢失格式:后端存的 HTML 被转义过------确认存取都按原始 HTML 字符串,别擅自 encodeURIComponent

六、今日小结

  • 组成:Toolbar + Editor 两件套 + 必引 CSS ;Vue3 装 @next 版组件层
  • 数据流:v-model 绑 HTML 字符串,展示用 v-html(注意 XSS 过滤)
  • 实例管理:shallowRef 存实例 + onBeforeUnmount destroy()(重实例三连的第三连:080 dispose、081 destroy)
  • 图片上传:MENU_CONF.uploadImage + customInsert 适配任意后端

下篇预告

上传不止图片:头像、附件、大视频。下一篇 082 文件上传实战:el-upload 校验、自定义上传接 062 篇的 api,以及大文件分片上传的思路。

相关推荐
徐小夕1 分钟前
JitKnow知识库4.8发布:本体+知识图谱检索引擎,让AI回答不再“一本正经地胡说八道”
前端·vue.js·github
智迁1 分钟前
MySQL 到 Kingbase 实时同步:从 Docker 部署到增删改验证
前端·后端
熊猫钓鱼>_>17 分钟前
平行视界兼容模式与魔法变量配置避坑【鸿蒙心迹】
运维·服务器·开发语言·笔记·华为·harmonyos·openharmony
tryCbest29 分钟前
Vue3——第 11 篇:组合式 API(上)——setup 与响应式 API 深度解析
前端·javascript·vue.js
tryCbest1 小时前
Vue3——第 13 篇:计算属性与侦听器——computed 与 watch 的进化
前端·javascript·vue.js
怒放的生命19911 小时前
本地用 pnpm 省磁盘,团队继续用 npm:极简实践
前端·npm·node.js
IT19952 小时前
Docker 快速部署 DM8 达梦数据库|完整踩坑笔记
笔记·docker·容器
用户117953654782 小时前
企业团队编程软件怎么选:AI编程工具的协作与落地能力对比
javascript
東隅已逝,桑榆非晚2 小时前
算法2. 双指针法-复写零
c++·笔记·学习·算法
闲来没事抠鼻屎2 小时前
标签打印 8 个尺寸公式:小票、价签、吊牌、面单通用
vue.js