vue使用富文本编辑器:vue-quill-editor粘贴图片+图片上传服务器+预览图片

引入vue-quill-editor
  1. 初始化vue-quill-editor
npm install vue-quill-editor --save
  1. 部分页面引入组件
  import 'quill/dist/quill.core.css'
  import 'quill/dist/quill.snow.css'
  import 'quill/dist/quill.bubble.css'
  import {quillEditor} from 'vue-quill-editor'

全局引入组件

  • main.js文件中
import  VueQuillEditor from 'vue-quill-editor'
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
Vue.use(VueQuillEditor)
根据需求自定义富文本
  • 创建QuillEditorForm.vue和toolbarOptions.js,抽象成一个新的组件
  • 需求:图片通过右键粘贴、点击上方图片上传按钮进行上传。由于组件默认将图片转为base64再放入内容中,如果图片比较大的话,富文本的内容就会很大,即使图片不大,只要图片较为多,篇幅较长,富文本的内容也变大影响数据传递。所以只能传递到服务器上,然后将url赋值给img的src。监听粘贴事件自定义上传(这个解决方案想了很久)
  • show me the code
<template>
  <!--富文本编辑器-->
  <div class="app-container">
    <div class="avatar">
      <!-- 图片上传组件辅助-->
      <el-upload
        id="quill-upload"
        action="上传的路径"
        ref="annexUpload"
        name="content"
        :limit="1"
        list-type="picture"
        :on-exceed="handleExceed"
        :on-error="handleError"
        :on-success="uploadAttachment"
        :file-list="uploadList"
        :with-credentials="true"
        :auto-upload="true">
      </el-upload>
    </div>
    <el-row v-loading="quillUpdateImg">
      <div class="edit_container">
        <quill-editor
          :disabled="disableFlag"
          v-model="detailContent"
          ref="myQuillEditor"
          :options="editorOption"
          @change="onEditorChange($event)">
        </quill-editor>
      </div>
    </el-row>
  </div>

</template>
<script>
  import 'quill/dist/quill.core.css'
  import 'quill/dist/quill.snow.css'
  import 'quill/dist/quill.bubble.css'
  import {quillEditor} from 'vue-quill-editor'
  import toolbarOptions from './toolbarOptions'

  export default {
    name: 'QuillEditorForm',
    components: {
      quillEditor
    },
    props: ['entity', 'disableFlag', 'problemDescribe'],
    data() {
      return {
        quillUpdateImg: false, // 根据图片上传状态来确定是否显示loading动画,刚开始是false,不显示
        uploadList: [],
        detailContent: '', // 富文本内容
        // 富文本编辑器配置
        editorOption: {
          placeholder: '',
          theme: 'snow',  // or 'bubble'
          modules: {
            toolbar: {
              container: toolbarOptions,  // 工具栏
              handlers: {
                'image': function(value) {
                  if (value) {
                  // 绑定上传图片按钮
                    document.querySelector('#quill-upload input').click()
                  } else {
                    this.quill.format('image', false)
                  }
                }
              }
            }
          }
        }
      }
    },
    mounted() {
      //  自定义粘贴图片功能
      let quill = this.$refs.myQuillEditor.quill
      if (!this.disableFlag) {
        toolbarOptions[0] = ['image']
      }
      this.$forceUpdate()
      quill.root.addEventListener('paste', evt => {
        if (evt.clipboardData && evt.clipboardData.files && evt.clipboardData.files.length) {
          evt.preventDefault();
          [].forEach.call(evt.clipboardData.files, file => {
            if (!file.type.match(/^image\/(gif|jpe?g|a?png|bmp)/i)) {
              return
            }
            this.uploadToServer(file, (res) => {
              // 获取光标内容
              var range = quill.getSelection()
              if (range) {
                this.uploadAttachment(res, file, null)
                //  将光标移动到图片后面
                this.$refs.myQuillEditor.quill.setSelection(range.index + 1)
              }
            })
          })
        }
      }, false)
    },
    methods: {
      uploadToServer(file, callback) {
        var xhr = new XMLHttpRequest()
        var formData = new FormData()
        formData.append('content', file)
        xhr.open('post', '服务器路径')
        xhr.withCredentials = true
        xhr.responseType = 'json'
        xhr.send(formData)
        xhr.onreadystatechange = () => {
          if (xhr.readyState === 4 && xhr.status === 200) {
            callback(xhr.response)
          }
        }
      },
      handleExceed(files, fileList) {
        this.$message.warning(`当前限制一次性上传最多 1 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`)
      },
      handleError(err, file, fileList) {
        this.$message.warning(`文件上传失败,请尝试重新上传,文件失败原因为: ${err}`)
      },
      uploadAttachment(response, file, fileList) {
        // 保存文件信息
        if (response.status.code === 0) {
          // 获取富文本组件实例
          let quill = this.$refs.myQuillEditor.quill
          // 获取光标所在位置
          let length = quill.getSelection().index
          // 插入图片  res.info为服务器返回的图片地址
          quill.insertEmbed(length, 'image', response.url)
          quill.setSelection(length + 1)
          let fileType = null
          if (file.raw && file.raw.type) {
            fileType = file.raw.type
          } else {
            fileType = file.type
          }
          let params = {}
          params = {
           // 保存文件上传的参数
          }
          workService.create(params).then((res) => {
            this.$message.success('上传成功')
          }).catch((err) => {
            this.$message.error(err)
          })
        } else {
          this.$message.error(response.status.message)
        }
        // 清空文件列表
        this.uploadList = []
      },
      onEditorChange(e) {
        this.$emit('change', e.html)
      }
    }
  }
</script>

<style scoped>

  .quill-editor {
    min-width: 600px;
    max-width: 700px;
    max-height: 250px;
    overflow: auto;
  }

  .avatar {
    display: none;
  }

  /deep/ .ql-disabled {
    background-color: #F5F7FA;
    border-color: #E4E7ED;
    color: #C0C4CC;
    cursor: not-allowed;
  }

  /*.edit_container {*/
  /*  position: relative;*/
  /*}*/
</style>

toolbarOptions.js

const toolbarOptions = [
  // ['bold', 'italic', 'underline', 'strike'],
  ['blockquote', 'code-block'],
  [{'header': 1}, {'header': 2}],
  // [{'list': 'ordered'}, {'list': 'bullet'}],
  [{'script': 'sub'}, {'script': 'super'}],
  [{'indent': '-1'}, {'indent': '+1'}],
  [{'direction': 'rtl'}],
  [{'size': ['small', false, 'large', 'huge']}],
  [{'header': [1, 2, 3, 4, 5, 6, false]}],
  // [{'color': []}, {'background': []}],
  // [{'font': []}],
  // [{'align': []}],
  // ['link', 'image', 'video'],
  ['clean']
]
export default toolbarOptions
// 只能粘贴原文本
      handleCustomMatcher(node, Delta) {
        let ops = []
        Delta.ops.forEach(op => {
          if (op.insert && typeof op.insert === 'string') {
            // 如果粘贴了图片,这里会是一个对象,所以可以这样处理
            ops.push({
              insert: op.insert
            })
          }
        })
        Delta.ops = ops
        return Delta
      },
拓展
npm install quill-image-resize-module quill-image-drop-module --save
// 引入
import { ImageDrop } from 'quill-image-drop-module'
import ImageResize from 'quill-image-resize-module'
Quill.register('modules/imageDrop', ImageDrop)
Quill.register('modules/imageResize', ImageResize)
// build/webpack.base.conf.js文件
const webpack = require('webpack')
plugins: [
   new webpack.ProvidePlugin({
       'window.Quill': 'quill/dist/quill.js',
       'Quill': 'quill/dist/quill.js'
 })
]
// 放在editorOption里面
 history: {
              delay: 1000,
              maxStack: 50,
              userOnly: false
            },
            imageDrop: true,
            imageResize: {
              displayStyles: {
                backgroundColor: 'black',
                border: 'none',
                color: 'white'
              },
              modules: [ 'Resize', 'DisplaySize', 'Toolbar' ]
            }
兼容性问题

兼容IE10以上

20211012更新富文本框

新增功能:点击预览图片

npm install v-viewer --save
<template>
  <!--富文本编辑器-->
  <div class="app-container">
    <div class="avatar">
      <!-- 图片上传组件辅助-->
      <el-upload
        id="quill-upload"
        class="avatar-uploader"
        action="/v1/file/upload"
        ref="annexUpload"
        name="content"
        :limit="1"
        list-type="picture"
        :on-exceed="handleExceed"
        :on-error="handleError"
        :on-success="uploadAttachment"
        :file-list="uploadList"
        :with-credentials="true"
        :auto-upload="true">
        <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
      </el-upload>
    </div>
    <el-row v-loading="quillUpdateImg">
      <div v-if="!disableFlag">
        <quill-editor
          v-if="loaded"
          :disabled="disableFlag"
          v-model="detailContent"
          ref="myQuillEditor"
          :options="editorOption"
          @focus="onEditorFocus($event)"
          @change="onEditorChange($event)">
        </quill-editor>
      </div>
      <div v-else>
        <div style="display: flex;">
          <span @click="showViewer = !showViewer" style="float: right;cursor: pointer;margin-right: 20px;">
            <i :class="!showViewer ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"   class="arrow"></i>
          </span>
        </div>
        <viewer v-if="showViewer" :images="[]" class="viewer" ref="viewer">
          <div v-html="detailContent"></div>
        </viewer>
      </div>
    </el-row>
  </div>

</template>
<script>
  import 'viewerjs/dist/viewer.css'
  import Viewer from 'v-viewer/src/component'
  import 'quill/dist/quill.core.css'
  import 'quill/dist/quill.snow.css'
  import 'quill/dist/quill.bubble.css'
  import Quill from 'quill'
  import {quillEditor} from 'vue-quill-editor'
  import { ImageDrop } from './ImageDrop'
  import ImageResize from 'quill-image-resize-module'
  Quill.register('modules/imageDrop', ImageDrop)
  Quill.register('modules/imageResize', ImageResize)
  export default {
    name: 'QuillEditorForm',
    components: {
      quillEditor, Viewer
    },
    props: ['entity', 'disableFlag', 'value'],
    data() {
      return {
        quillUpdateImg: false, // 根据图片上传状态来确定是否显示loading动画,刚开始是false,不显示
        serverUrl: '',  // 这里写你要上传的图片服务器地址
        header: {token: sessionStorage.token},  // 有的图片服务器要求请求头需要有token之类的参数,写在这里
        uploadList: [],
        activeNames: ['1'],
        detailContent: '', // 富文本内容
        initFlag: true,
        // 富文本编辑器配置
        editorOption: {
          placeholder: '',
          theme: 'snow',  // or 'bubble'
          modules: {
            imageDrop: true,
            imageResize: {
              displayStyles: {
                backgroundColor: 'black',
                border: 'none',
                color: 'white'
              },
              modules: [ 'Resize', 'DisplaySize', 'Toolbar' ]
            },
            toolbar: {
              container: [
                [], // 预留图片上传
                ['blockquote', 'code-block'], // 引用,代码块
                [{'list': 'ordered'}, { 'list': 'bullet' }],  // 列表
                [{'header': [1, 2, 3, false]}], // 几级标题(1~6)
                // [{'header': 1}, {'header': 2}], // 标题,键值对的形式;1、2表示字体大小
                [{'size': ['small', false, 'large']}], // 字体大小('small', false, 'large', 'huge')
                [{ 'color': [] }, { 'background': [] }],
                ['bold', 'italic', 'underline'], // 加粗,斜体,下划线,删除线('strike')
                // [{'script': 'sub'}, {'script': 'super'}],  // 上下标
                [{'indent': '-1'}, {'indent': '+1'}], // 缩进
                // [{'direction': 'rtl'}], // 文本方向
                // [{ 'font': [] }], // 字体
                // [{ 'align': [] }], // 对齐方式
                ['clean'] // 清除字体样式
              ],  // 工具栏
              handlers: {
                image: (value) => {
                  if (value) {
                    this.$refs.annexUpload.$el.querySelector('#quill-upload input').click()
                  } else {
                    this.$refs.myQuillEditor.quill.format('image', false)
                  }
                }
              }
            }
          }
        },
        quillInstance: null,
        loaded: true,
        showViewer: true
      }
    },
    watch: {
      value: {
        handler(val) {
          this.detailContent = val
          this.$forceUpdate()
        },
        deep: true,
        immediate: true
      },
      detailContent: {
        handler(val) {
          if (val && this.initFlag && this.$refs.myQuillEditor && this.$refs.myQuillEditor.quill) {
            // 初始化页面,取消focus状态
            this.$refs.myQuillEditor.quill.enable(false)
            this.detailContent = val
            this.$emit('input', val)
            setTimeout(() => {
              this.$refs.myQuillEditor.quill.enable(true)
            })
            this.initFlag = false
          } else {
            this.detailContent = val
            this.$emit('input', val)
          }
        },
        deep: true,
        immediate: true
      },
      disableFlag: {
        handler(val) {
          if (!val) {
            this.editorOption.modules.toolbar.container[0] = ['image']
          }
          this.loaded = false
          this.$nextTick(() => {
            this.loaded = true
            this.$nextTick(() => {
              // console.log('update', this, val, this.$refs.myQuillEditor)
              if (!this.$refs.myQuillEditor) {
                return
              }
              //  自定义粘贴图片功能
              this.quillInstance = this.$refs.myQuillEditor.quill
              this.quillInstance.root.removeEventListener('paste', this.handlePaste)
              this.quillInstance.root.addEventListener('paste', this.handlePaste, false)
            })
          })
        },
        immediate: true
      }
    },
    methods: {
      onEditorFocus(e) { // 用户自动获取焦点,不设置focus状态
        this.initFlag = false
      },
      handlePaste(evt) {
        if (evt.clipboardData && evt.clipboardData.files && evt.clipboardData.files.length) {
          evt.preventDefault();
          [].forEach.call(evt.clipboardData.files, file => {
            if (!file.type.match(/^image\/(gif|jpe?g|a?png|bmp)/i)) {
              return
            }
            this.uploadToServer(file, (res) => {
              // 获取光标内容
              var range = this.quillInstance.getSelection()
              if (range) {
                this.uploadAttachment(res, file, null)
                //  在当前光标位置插入图片
                // this.$refs.myQuillEditor.quill.insertEmbed(range.index, 'image', res.url)
                //  将光标移动到图片后面
                this.$refs.myQuillEditor.quill.setSelection(range.index + 1)
              }
            })
          })
        }
      },
      uploadToServer(file, callback) {
        var xhr = new XMLHttpRequest()
        var formData = new FormData()
        formData.append('content', file)
        xhr.open('post', '上传路径')
        xhr.withCredentials = true
        xhr.responseType = 'json'
        xhr.send(formData)
        xhr.onreadystatechange = () => {
          if (xhr.readyState === 4 && xhr.status === 200) {
            callback(xhr.response)
          }
        }
      },
      handleExceed(files, fileList) {
        this.$message.warning(`当前限制一次性上传最多 1 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`)
      },
      handleError(err, file, fileList) {
        this.$message.warning(`文件上传失败,请尝试重新上传,文件失败原因为: ${err}`)
      },
      uploadAttachment(response, file, fileList) {
        // 保存文件信息
        if (response.status.code === 0) {
          // 获取富文本组件实例
          let quill = this.$refs.myQuillEditor.quill
          // 获取光标所在位置
          let length = quill.getSelection().index
          // 插入图片  res.info为服务器返回的图片地址
          quill.insertEmbed(length, 'image', response.url)
          quill.setSelection(length + 1)
          let fileType = null
          if (file.raw && file.raw.type) {
            fileType = file.raw.type
          } else {
            fileType = file.type
          }
          let params = {}
          params = {
            // 保存文件上传的参数
          }
          service.createWorkOrderFile(params).then((res) => {
            this.$message.success('上传成功')
          }).catch((err) => {
            this.$message.error(err)
          })
        } else {
          this.$message.error(response.status.message)
        }
        // 清空文件列表
        this.uploadList = []
      },
      onEditorChange(e) {
        this.$emit('change', e.html)
      }
    }
  }
</script>

<style scoped>
  @import "../../assets/quill.css";
  .quill-editor {
    min-width: 657px;
    overflow: auto;
  }

  .avatar {
    display: none;
  }

  /deep/ .ql-disabled {
    background-color: #F5F7FA;
    border-color: #E4E7ED;
    color: #7f8185;
    cursor: not-allowed;
  }
  .viewer {
    border: 1px solid #ebeef5;
    padding: 12px 15px;
    min-width: 657px;
    max-width: 700px;
    max-height: 600px;
    overflow: auto;
  }
  .arrow {
    margin: 0 8px 0 auto;
    transition: transform .3s;
    font-weight: 300;
  }
</style>


  • 8
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值