Vue 2.7 中使用 WangEditor 富文本编辑器的完整指南

304 阅读4分钟

引言

在前端开发中,富文本编辑器是一个常见的需求,特别是在需要用户输入大量文本内容的场景中,如博客、内容管理系统等。WangEditor 是一款轻量级、功能丰富的富文本编辑器,本文将详细介绍如何在 Vue 2.7 中使用 WangEditor 5。

安装依赖

首先,我们需要安装 WangEditor 相关的依赖包:

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

组件实现

下面是一个完整的 WangEditor 组件实现,支持图片上传、内容绑定和禁用状态:

<template>
  <div class="editor_container">
    <Toolbar
      :editor="editorRef"
      :defaultConfig="toolbarConfig"
      class="editor_toolbar"
    />
    <Editor
      v-model="contentHtml"
      :defaultConfig="editorConfig"
      :class="{ disabled: disabled, editor_content: true }"
      @onChange="doChange"
      @onCreated="doCreate"
    />
  </div>
</template>

<script>
  import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
  import { uploadFile } from '@/utils'
  import { baseURL } from '@/config'
  import store from '@/store'
  import { onBeforeUnmount, defineComponent, shallowRef, watch } from 'vue-demi'
  import { useRoute } from '@/hooks'

  const USER_TOKEN = store.getters['user/token']

  export default defineComponent({
    name: 'BaseEditor',
    components: { Editor, Toolbar },
    props: {
      value: {
        type: String,
        default: '',
      },
      disabled: {
        type: Boolean,
        default: false,
      },
    },
    setup(props, { emit }) {
      const route = useRoute()
      const editorRef = shallowRef(null)
      const contentHtml = shallowRef('')

      const toolbarConfig = {}
      const editorConfig = {
        placeholder: '请输入内容...',
        MENU_CONF: {
          uploadImage: {
            async customUpload(file, insertFn) {
              const { url, name } = await uploadFile(file, route.value.path)
              const imgUrl = `${baseURL}/tfle/v1/0/file-preview/by-url?url=${url}&access_token=bearer ${USER_TOKEN}&bucketName=tof`
              insertFn(imgUrl, name, imgUrl)
            },
          },
        },
      }

      watch(
        () => props.value,
        (value) => {
          if (!value) return
          contentHtml.value = replaceToken(decodeURIComponent(value))
        }
      )

      watch(() => props.disabled, doDisable, { immediate: true })

      const doCreate = (editor) => {
        editorRef.value = editor
        doDisable(props.disabled)
      }

      const doChange = (editor) => {
        const content = encodeURIComponent(editor.getHtml())
        emit('input', content)
        emit('change', content)
      }

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

      function doDisable(value) {
        if (value) {
          editorRef.value?.disable()
        }
      }

      function replaceToken(html) {
        let newHtml = ''
        const regex = /\bbearer \b(.*?)\b&bucketName\b/g
        const matches = Array.from(new Set(html.match(regex)))

        if (!matches.length) {
          return html
        }

        matches.forEach((match) => {
          newHtml = html.replaceAll(match, `bearer ${USER_TOKEN}&bucketName`)
        })

        return newHtml
      }
      return {
        contentHtml,
        editorConfig,
        editorRef,
        toolbarConfig,
        doChange,
        doCreate,
      }
    },
  })
</script>

<style src="@wangeditor/editor/dist/css/style.css"></style>
<style lang="scss" scoped>
  .editor_container {
    border: 1px solid #ccc;
    margin-top: 10px;
    height: 500px;
  }

  .editor_toolbar {
    border-bottom: 1px solid #ccc;
  }

  .editor_content {
    border: 1px solid #ccc;
    height: 80%;
  }
  .w-e-full-screen-container {
    z-index: 9999 !important;
  }
  ::v-deep {
    .disabled {
      .w-e-text-container {
        background-color: #f5f7fa;
        border-color: #dfe4ed;
        color: #333;
        cursor: not-allowed;
      }
    }
  }
</style>

核心功能解析

1. 组件结构

  • Toolbar:编辑器工具栏,包含各种编辑功能按钮
  • Editor:编辑器主体,用于输入和显示富文本内容
  • 容器:使用 editor_container 类包裹,设置边框和高度

2. 配置项

  • toolbarConfig:工具栏配置,默认为空对象,使用默认配置
  • editorConfig:编辑器配置,包含:
    • placeholder:占位符文本
    • MENU_CONF:菜单配置,这里主要配置了图片上传

3. 图片上传

使用 customUpload 方法自定义图片上传逻辑:

  1. 调用 uploadFile 函数上传文件,获取返回的 urlname
  2. 构建完整的图片访问 URL,包含 baseURL、文件路径、访问令牌和存储桶名称
  3. 调用 insertFn 方法将图片插入到编辑器中

4. 内容绑定

  • 使用 v-model 绑定 contentHtml,实现双向数据绑定
  • 监听 props.value 的变化,更新编辑器内容
  • 监听 doChange 事件,将编辑器内容通过 emit 传递给父组件

5. 禁用状态

  • 监听 props.disabled 的变化,调用 doDisable 方法
  • doDisable 方法中,调用编辑器的 disable 方法禁用编辑器
  • 通过 CSS 样式为禁用状态添加特殊样式

6. 组件销毁

onBeforeUnmount 生命周期钩子中,销毁编辑器实例,释放资源

7. Token 替换

  • replaceToken 函数用于替换 HTML 中的令牌,确保图片访问 URL 中的令牌是最新的
  • 使用正则表达式匹配并替换令牌

使用方法

基本使用

<template>
  <div>
    <h3>富文本编辑器示例</h3>
    <BaseEditor v-model="content" />
    <el-button type="primary" @click="submit">提交</el-button>
  </div>
</template>

<script>
import BaseEditor from '@/components/BaseEditor'

export default {
  components: {
    BaseEditor
  },
  data() {
    return {
      content: ''
    }
  },
  methods: {
    submit() {
      console.log('提交内容:', this.content)
      // 处理提交逻辑
    }
  }
}
</script>

禁用状态

<BaseEditor v-model="content" :disabled="true" />

注意事项

  1. 依赖版本:本文使用的是 @wangeditor/editor@5.0.1@wangeditor/editor-for-vue@1.0.1,不同版本可能有差异

  2. 图片上传:需要实现 uploadFile 函数来处理文件上传,返回 urlname

  3. 令牌管理:需要确保 USER_TOKEN 能够正确获取,并且在图片 URL 中正确使用

  4. 样式冲突:如果遇到样式冲突,可以通过调整 CSS 优先级或使用 ::v-deep 来解决

  5. 内存泄漏:确保在组件销毁时调用 editor.destroy() 来释放资源

  6. Vue 版本:本文适用于 Vue 2.7,使用了 vue-demi 来兼容 Vue 2 和 Vue 3 的 API

常见问题

1. 图片上传失败

原因uploadFile 函数实现不正确,或者后端接口返回格式不符合预期

解决方案:检查 uploadFile 函数的实现,确保它返回正确的 urlname

2. 编辑器高度调整

原因:默认高度可能不适合所有场景

解决方案:修改 .editor_container.editor_content 的高度样式

3. 工具栏按钮不显示

原因:可能是工具栏配置问题,或者样式冲突

解决方案:检查 toolbarConfig 配置,确保没有禁用必要的按钮;检查是否有样式冲突

image.png

总结

WangEditor 是一款功能强大、易于使用的富文本编辑器,在 Vue 2.7 中使用非常方便。通过本文的介绍,你可以快速实现一个支持图片上传、内容绑定和禁用状态的富文本编辑器组件。

主要功能包括:

  • 基本的富文本编辑功能
  • 自定义图片上传
  • 双向数据绑定
  • 禁用状态支持
  • 令牌自动替换

希望本文对你在 Vue 2.7 中使用 WangEditor 有所帮助!

技术栈

  • 前端框架:Vue 2.7
  • 富文本编辑器:WangEditor 5
  • 构建工具:Vue CLI / Vite
  • 样式:SCSS