Vue3 + wangeditor 5.x 企业级封装实战:打造高可用的富文本上传组件

在后台管理系统开发中,富文本编辑器几乎是标配功能。但市面上大多数教程只停留在基础API调用层面,对于实际企业开发中遇到的样式污染、上传接口对接、组件销毁等痛点问题往往避而不谈。本文将基于Vue3的组合式API,带你从零封装一个生产环境可用的wangeditor组件,重点解决以下问题:

  1. 如何实现图片/视频的自定义上传逻辑
  2. 组件样式隔离的最佳实践方案
  3. 内存泄漏防范与编辑器实例销毁
  4. 支持双向绑定的v-model集成
  5. 上传进度反馈与错误处理机制

1. 环境准备与基础集成

1.1 初始化项目与安装依赖

首先确保你的项目已经配置好Vue3环境。推荐使用Vite作为构建工具:

npm create vite@latest vue3-editor-demo --template vue-ts

安装wangeditor相关依赖:

npm install @wangeditor/editor @wangeditor/editor-for-vue

1.2 基础组件结构

创建 src/components/RichEditor.vue 文件,搭建基础骨架:

<template>
  <div class="editor-container">
    <Toolbar 
      :editor="editorRef"
      :defaultConfig="toolbarConfig"
      mode="default"
    />
    <Editor
      v-model="modelValue"
      :defaultConfig="editorConfig"
      mode="default"
      @onCreated="handleCreated"
    />
  </div>
</template>

<script setup lang="ts">
import '@wangeditor/editor/dist/css/style.css'
import { onBeforeUnmount, shallowRef } from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'

const editorRef = shallowRef()
const modelValue = defineModel<string>({ required: true })

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

关键点说明:使用 shallowRef 存储编辑器实例可以避免不必要的响应式开销,这对富文本这种复杂对象尤为重要。

2. 深度配置编辑器功能

2.1 工具栏定制化

wangeditor支持高度自定义的工具栏配置。以下是企业应用中常见的配置方案:

const toolbarConfig = {
  excludeKeys: [
    'group-video', // 初始隐藏视频功能
    'fullScreen',
    'insertLink',
    'codeBlock'
  ],
  insertKeys: {
    index: 5,
    keys: ['uploadImage', 'uploadVideo']
  }
}

2.2 编辑器核心配置

针对不同场景,我们需要灵活调整编辑器的行为:

const editorConfig = {
  placeholder: '请输入内容...',
  autoFocus: false,
  scroll: true,
  maxLength: 50000,
  MENU_CONF: {}
}

3. 实现文件上传功能

3.1 图片上传方案

企业级应用通常需要对接自己的文件服务,下面是一个完整的图片上传实现:

editorConfig.MENU_CONF['uploadImage'] = {
  fieldName: 'file',
  server: '/api/upload/image',
  maxFileSize: 5 * 1024 * 1024, // 5MB
  allowedFileTypes: ['image/*'],
  
  customUpload: async (file: File, insertFn: Function) => {
    try {
      const formData = new FormData()
      formData.append('file', file)
      
      const { data } = await axios.post('/api/upload', formData, {
        headers: { 'Content-Type': 'multipart/form-data' },
        onUploadProgress: (progressEvent) => {
          const percent = Math.round(
            (progressEvent.loaded * 100) / progressEvent.total
          )
          // 可以在这里添加进度显示逻辑
        }
      })
      
      insertFn(data.url, '', '')
    } catch (error) {
      console.error('上传失败:', error)
      // 添加错误提示逻辑
    }
  }
}

3.2 视频上传的特殊处理

视频上传需要额外考虑封面和预览问题:

editorConfig.MENU_CONF['uploadVideo'] = {
  customUpload: async (file: File, insertFn: Function) => {
    // 先上传视频文件
    const videoRes = await uploadFile(file)
    
    // 生成视频封面缩略图
    const poster = await generateVideoPoster(videoRes.url)
    
    insertFn(videoRes.url, poster)
  }
}

4. 生产环境优化策略

4.1 样式隔离方案

避免编辑器样式影响全局样式:

.editor-container {
  :deep(.w-e-bar) {
    background-color: #f8f8f8;
    border: 1px solid #ddd;
  }
  
  :deep(.w-e-text-container) {
    border: 1px solid #ddd !important;
    border-top: none !important;
  }
}

4.2 内存管理与性能优化

正确处理组件销毁:

onBeforeUnmount(() => {
  const editor = editorRef.value
  if (!editor) return
  
  editor.destroy()
  editorRef.value = null
})

4.3 扩展功能集成

实现字数统计功能示例:

const handleChange = (editor: IDomEditor) => {
  const text = editor.getText()
  const html = editor.getHtml()
  
  // 触发字数变化事件
  emit('count-change', {
    textLength: text.length,
    htmlLength: html?.length || 0
  })
}

5. 完整组件代码与使用示例

5.1 最终组件实现

<template>
  <div class="rich-editor">
    <Toolbar
      :editor="editorRef"
      :defaultConfig="toolbarConfig"
    />
    <Editor
      v-model="modelValue"
      :defaultConfig="editorConfig"
      @onCreated="handleCreated"
      @onChange="handleChange"
    />
    
    <div v-if="showCount" class="editor-count">
      字数: {{ count }} / {{ maxLength }}
    </div>
  </div>
</template>

<script setup lang="ts">
// 完整导入和配置见上文实现
</script>

<style scoped lang="scss">
// 完整样式见上文实现
</style>

5.2 在父组件中使用

<template>
  <RichEditor 
    v-model="content"
    :maxLength="10000"
    @count-change="handleCountChange"
  />
</template>

<script setup>
const content = ref('<p>初始内容</p>')

const handleCountChange = ({ textLength }) => {
  console.log('当前字数:', textLength)
}
</script>

在实际项目中,这个组件已经处理了文件上传、样式隔离、内存管理等企业级关注点,可以直接集成到你的后台系统中。根据具体需求,你还可以进一步扩展如粘贴图片自动上传、自定义表情面板等功能。

更多推荐