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

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

  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>
      <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

如果对你有帮助,点个赞支持下吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容