vue-quill-editor 富文本编辑器封装,可上传图片,视频(附源码)

组件说明:
(1)支持图片上传到服务器,也可使用base64的方式
(2)支持视频上传,或者插入视频链接
【其实无论是上传图片到服务器还是上传视频到服务器,其实本质上都是上传文件然后后端返回一个地址插入到编辑器中】

  1. 安装
npm install vue-quill-editor --save

2.引入下面我封装的组件

<!--富文本编辑器-->
<template>
  <div class="RichTextEditor-Wrap" v-loading="loading">

    <quill-editor :content="content"
                  :options="editorOption"
                  class="ql-editor"
                  ref="myQuillEditor"
                  @change="onEditorChange($event)">
    </quill-editor>

    <!-- 图片上传组件辅助-->
    <el-upload
      v-show="false"
      :show-file-list="false"
      :name="uploadImgConfig.name"
      :multiple="false"
      :action="uploadImgConfig.uploadUrl"
      :before-upload="onBeforeUpload"
      :on-success="onSuccess"
      :on-error="onError"
      :file-list="fileList">
      <!--<i class="el-icon-upload"></i>-->
      <!--<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>-->
      <!--<div class="el-upload__tip" slot="tip">最多只能上传两个附件</div>-->
      <button ref="myinput">上传文件</button>
    </el-upload>

    <!--视频上传-->
    <div :visible.sync="videoDialog.show">
      <el-dialog
        :close-on-click-modal="false"
        width="50%"
        style="margin-top: 1px"
        title="视频上传"
        :visible.sync="videoDialog.show"
        append-to-body>
        <el-tabs v-model="videoDialog.activeName">
          <el-tab-pane label="添加视频链接" name="first">
            <el-input v-model="videoDialog.videoLink" placeholder="请输入视频链接" clearable></el-input>
            <el-button type="primary" size="small" style="margin: 20px 0px 0px 0px "
                       @click="addVideoLink(videoDialog.videoLink)">添加
            </el-button>
          </el-tab-pane>
          <el-tab-pane label="本地视频上传" name="second">
            <el-upload
              v-loading="loading"
              style="text-align: center;"
              drag
              :action="uploadVideoConfig.uploadUrl"
              accept="video/*"
              :name="uploadVideoConfig.name"
              :before-upload="onBeforeUploadVideo"
              :on-success="onSuccessVideo"
              :on-error="onErrorVideo"
              :multiple="false">
              <i class="el-icon-upload"></i>
              <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
              <div class="el-upload__tip" slot="tip">只能上传MP4文件,且不超过{{uploadVideoConfig.maxSize}}M</div>
            </el-upload>

          </el-tab-pane>
        </el-tabs>
      </el-dialog>
    </div>
  </div>
</template>
<script>
  // require styles
  import 'quill/dist/quill.core.css'
  import 'quill/dist/quill.snow.css'
  import 'quill/dist/quill.bubble.css'
  import {quillEditor} from 'vue-quill-editor'
  // 设置title
  import {addQuillTitle} from './quill-title.js'

  // 工具栏
  const toolbarOptions = [
    ['bold', 'italic', 'underline', 'strike'], // toggled buttons
    ['blockquote', 'code-block'],
    [{'header': 1}, {'header': 2}],
    [{'list': 'ordered'}, {'list': 'bullet'}],
    [{'script': 'sub'}, {'script': 'super'}], // superscript/subscript
    [{'indent': '-1'}, {'indent': '+1'}], // outdent/indent
    [{'direction': 'rtl'}],
    [{'size': ['small', false, 'large', 'huge']}],
    [{'header': [1, 2, 3, 4, 5, 6, false]}],
    [{'font': []}],
    [{'color': []}, {'background': []}], // dropdown with defaults from theme
    [{'align': []}],
    [{'clean': '清除'}], // remove formatting button
    // ['link', 'image', 'video']
    ['image', 'video']
  ]
  export default {
    name: 'RichTextEditor',
    model: {
      prop: 'content',
      event: 'change'
    },
    components: {
      quillEditor
    },
    props: {
      content: { // 返回的html片段
        type: String,
        default: ''
      },
      uploadImgConfig: { // 图片上传配置 - 若不配置则使用quillEditor默认方式,即base64方式
        type: Object,
        default(){
          return {
            uploadUrl: '', // 图片上传地址
            maxSize: 2, // 图片上传大小限制,默认不超过2M
            name: 'Filedata' // 图片上传字段
          }
        }
      },
      uploadVideoConfig: { // 视频上传配置
        type: Object,
        default(){
          return {
            uploadUrl: '', // 上传地址
            maxSize: 10, // 图片上传大小限制,默认不超过2M
            name: 'Filedata' // 图片上传字段
          }
        }
      }
    },
    data() {
      let _self = this;
      return {
        loading: false, // 加载loading
        editorOption: {
          placeholder: '',
          theme: 'snow', // or 'bubble'
          modules: {
            toolbar: {
              container: toolbarOptions, // 工具栏
              handlers: {
                'video': function (value) {
                  _self.videoDialog.show = true;
                }
              }
            }
          }
        },

        // 图片上传变量
        fileList: [],

        // 视频上传变量
        videoDialog: {
          show: false,
          videoLink: '',
          activeName: 'first'
        }
      }
    },
    mounted () {
      // 初始给编辑器设置title
      addQuillTitle()

      let toolbar = this.$refs['myQuillEditor'].quill.getModule('toolbar');
      // 是否开启图片上传到服务器功能
      if (this.uploadImgConfig.uploadUrl) {
        toolbar.addHandler('image', this.addImageHandler);
      }

    },
    methods: {
      // 文本编辑
      onEditorChange ({quill, html, text}) {
        // console.log('editor change!', quill, html, text)
        // console.log(html.replace(/<[^>]*>|/g, ''), 33333333)
        this.$emit('update:content', html)
        this.$emit('change', html)
      },
      hideLoading(){
        this.loading = false
      },
      // --------- 图片上传相关 start ---------

      addImageHandler(value){
        if (value) {
          // 触发input框选择图片文件
          this.$refs['myinput'].click();
        } else {
          this.quill.format('image', false)
        }
      },
      // 把已经上传的图片显示回富文本编辑框中
      uploadSuccess (imgurl) {
        let quill = this.$refs['myQuillEditor'].quill
        let range = quill.getSelection()
        let index = 0;
        if (range == null) {
          index = 0;
        } else {
          index = range.index; // 获取光标所在位置
        }
        // 插入
        quill.insertEmbed(index, 'image', imgurl) // imgurl是服务器返回的图片链接地址
        // 调整光标到最后
        quill.setSelection(index + 1)
      },
      // el-文件上传组件
      onBeforeUpload (file) {
        this.loading = true
        let acceptArr = ['image/jpg', 'image/jpeg', 'image/gif', 'image/png']
        const isIMAGE = acceptArr.includes(file.type)
        const isLt1M = file.size / 1024 / 1024 < this.uploadImgConfig.maxSize
        if (!isIMAGE) {
          this.hideLoading()
          this.$message.error('只能插入图片格式!')
        }
        if (!isLt1M) {
          this.hideLoading()
          this.$message.error(`上传文件大小不能超过 ${this.uploadImgConfig.maxSize}MB!`)
        }
        return isLt1M && isIMAGE
      },
      // 文件上传成功时的钩子
      onSuccess (response, file, fileList) { // ---- 注意这部分需要改为对应的返回格式
        this.hideLoading()
        if (response.retCode === '00') {
          this.uploadSuccess(response.url)
        } else {
          this.$message.error('上传失败')
        }
      },
      // 文件上传失败时的钩子
      onError (file, fileList) {
        this.hideLoading()
        this.$message.error('上传失败')
      },

      // --------- 图片上传相关 end ---------

      // --------- 视频上传相关 start ---------

      addVideoLink(videoLink) {
        if (!videoLink) return this.$message.error('请输入视频地址')
        this.videoDialog.show = false
        let quill = this.$refs['myQuillEditor'].quill
        let range = quill.getSelection()
        let index = 0;
        if (range == null) {
          index = 0;
        } else {
          index = range.index;
        }
        // 插入
        quill.insertEmbed(index, 'video', videoLink)
        // 调整光标到最后
        quill.setSelection(index + 1)
      },

      // el-文件上传组件
      onBeforeUploadVideo (file) {
        this.loading = true
        let acceptArr = ['video/mp4']
        const isVideo = acceptArr.includes(file.type)
        const isLt1M = file.size / 1024 / 1024 < this.uploadVideoConfig.maxSize
        if (!isVideo) {
          this.hideLoading()
          this.$message.error('只能上传mp4格式文件!')
        }
        if (!isLt1M) {
          this.hideLoading()
          this.$message.error(`上传文件大小不能超过 ${this.uploadVideoConfig.maxSize}MB!`)
        }
        return isLt1M && isVideo
      },
      // 文件上传成功时的钩子
      onSuccessVideo (response, file, fileList) { // ---- 注意这部分需要改为对应的返回格式
        this.hideLoading()
        if (response.retCode === '00') {
          this.addVideoLink(response.url)
        } else {
          this.$message.error('上传失败')
        }
      },
      // 文件上传失败时的钩子
      onErrorVideo (file, fileList) {
        this.hideLoading()
        this.$message.error('上传失败')
      },

      // --------- 视频上传相关 end ---------
    }
  }
</script>
<style>
  .RichTextEditor-Wrap .ql-container {
    height: 300px;
  }

  .RichTextEditor-Wrap .ql-editor {
    padding: 0;
  }

  .RichTextEditor-Wrap .ql-tooltip {
    left: 5px !important;
  }
</style>


quill-title.js这个文件是拿来给工具栏设置title的,代码如下:

const titleConfig = {
  'ql-bold': '加粗',
  'ql-font': '字体',
  'ql-code': '插入代码',
  'ql-italic': '斜体',
  'ql-link': '添加链接',
  'ql-color': '字体颜色',
  'ql-background': '背景颜色',
  'ql-size': '字体大小',
  'ql-strike': '删除线',
  'ql-script': '上标/下标',
  'ql-underline': '下划线',
  'ql-blockquote': '引用',
  'ql-header': '标题',
  'ql-indent': '缩进',
  'ql-list': '列表',
  'ql-align': '文本对齐',
  'ql-direction': '文本方向',
  'ql-code-block': '代码块',
  'ql-formula': '公式',
  'ql-image': '图片',
  'ql-video': '视频',
  'ql-clean': '清除字体样式'
}

export function addQuillTitle () {
  const oToolBar = document.querySelector('.ql-toolbar')
  const aButton = oToolBar.querySelectorAll('button')
  const aSelect = oToolBar.querySelectorAll('select')
  aButton.forEach(function (item) {
    if (item.className === 'ql-script') {
      item.value === 'sub' ? item.title = '下标' : item.title = '上标'
    } else if (item.className === 'ql-indent') {
      item.value === '+1' ? item.title = '向右缩进' : item.title = '向左缩进'
    } else {
      item.title = titleConfig[item.className]
    }
  })
  // 字体颜色和字体背景特殊处理,两个在相同的盒子
  aSelect.forEach(function (item) {
    if (item.className.indexOf('ql-background') > -1) {
      item.previousSibling.title = titleConfig['ql-background']
    } else if (item.className.indexOf('ql-color') > -1) {
      item.previousSibling.title = titleConfig['ql-color']
    } else {
      item.parentNode.title = titleConfig[item.className]
    }
  })
}

title.gif

组件使用: 注意props,其中 uploadImgConfig 是针对上传图片的配置,如果不传则默认使用原始的base64方式,视频上传必须要有上传服务器的地址

image.png

组件复制过去后,还需更改下下面这部分代码,根据自己的接口返回值类型更改:
image.png

组件调用方式
支持v-model、.sync方式。

上传图片效果图:

image.png

上传视频效果图:

image.png

image.png

最后: 对于组件中上传图片、视频的格式判断可自行更改,也可提取到props中,整个组件代码逻辑比较简单,大家自由发挥吧。

组件中视频上传封装参考了这篇文章:
https://zhuanlan.zhihu.com/p/108705388

本文章由javascript技术分享原创和收集

发表评论 (审核通过后显示评论):