4

vue使用quill上传图片的基本配置我在前面文字中详细介绍过(https://segmentfault.com/a/11...)这里主要介绍上传图片,拖拽上传,截图粘贴,调整图片大小的使用。

一、下载安装

cnpm i vue-quill-editor -S
// 可拖拽图片
cnpm i quill-image-drop-module -S
// 这两个是改变图片大小的
cnpm i quill-image-resize-module -S
// 粘贴图片上传
cnpm i quill-image-paste-module -S

二、这些插件需要配置webpack支持,webpack.dev.conf.js/webpack.prod.conf.js

plugins: [
    ...
    new webpack.ProvidePlugin({
      'window.Quill': 'quill/dist/quill.js',
      'Quill': 'quill/dist/quill.js'
    })
    ...

三、配置组件,最好将富文本编辑器封装成公共组件使用

// 富文本编辑器 quill editor 样式
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
import * as Quill from 'quill'
import { quillEditor } from 'vue-quill-editor'
// 拖拽上传
import { ImageDrop } from 'quill-image-drop-module'
// 调整上传图片大小
import ImageResize from 'quill-image-resize-module'
// 粘贴图片上传
import {ImageExtend} from 'quill-image-paste-module'
// 注册事件~~~~
Quill.register('modules/imageDrop', ImageDrop)
Quill.register('modules/imageResize', ImageResize)
Quill.register('modules/ImageExtend', ImageExtend)
// html文件
<template>
  <div id='quillEditorQiniu'>
    <el-row v-loading="loadingImg">
      <quill-editor
        :style="{width: width ? width: '100%',  background: '#fff'}"
        v-model="content"
        ref="myQuillEditor"
        :options="editorOption"
        @change="onEditorChange($event)"
      >
      </quill-editor>
    </el-row>
    <Upload
      class="avatar-uploader"
      :accept="'image/*'"
      :action="'https://upload.qiniup.com'"
      name='file'
      :data="updateParams"
      :show-upload-list="false"
      :on-success="uploadSuccess"
      :on-error="uploadError"
      :before-upload="beforeUpload"
    >
    </Upload>
  </div>
</template>
// 配置参数
editorOption: {
        placeholder: this.placeholder ? this.placeholder : '请输入',
        theme: 'snow',
        modules: {
          toolbar: {
            container: toolbarOptions
          },
          // 拖拽上传
          imageDrop: true,
          // 调整图片大小
          imageResize: {
            displayStyles: {
              backgroundColor: 'black',
              border: 'none',
              color: 'white'
            },
            modules: [ 'Resize', 'DisplaySize', 'Toolbar' ]
          },
          // 截屏上传
          ImageExtend: {
            loading: true,
            name: 'file',
            // 设置上传参数,因为我们要上传七牛,所以需要token
            change: (xhr, FormData) => {
              FormData.append('token', this.$store.state.upload_token)
            },
            action: 'https://upload.qiniup.com',
            response: (res) => {
              console.log(res, 'response')
              // 这里有必要说一下,由于七牛上传成功以后返回的是 {hash: 'xxxx', key: 'xxx'},其中key就是七牛生成的文件名称,所以我们拼接上自己的服务器地址,就是图片保存的地址,return
              的结果会座位图片的src
              return this.$store.getters.upload_url + res.key
            }
          }
        }
      },

插件的具体配置可以查看:https://github.com/binperson/...

四、配置参数说明

modules: {
    ImageExtend: { // 如果不作设置,即{}  则依然开启复制粘贴功能且以base64插入
    name: 'img', // 图片参数名
    size: 3, // 可选参数 图片大小,单位为M,1M = 1024kb
    action: updateUrl, // 服务器地址, 如果action为空,则采用base64插入图片
    // response 为一个函数用来获取服务器返回的具体图片地址
    // 例如服务器返回{code: 200; data:{ url: 'baidu.com'}}
    // 则 return res.data.url
    response: (res) => {
        return res.info
    },
    headers: (xhr) => {
        // xhr.setRequestHeader('myHeader','myValue')
    }, // 可选参数 设置请求头部
    sizeError: () => {}, // 图片超过大小的回调
    start: () => {}, // 可选参数 自定义开始上传触发事件
    end: () => {}, // 可选参数 自定义上传结束触发的事件,无论成功或者失败
    error: () => {}, // 可选参数 上传失败触发的事件
    success: () => {}, // 可选参数 上传成功触发的事件
    change: (xhr, formData) => {
        // xhr.setRequestHeader('myHeader','myValue')
        // formData.append('token', 'myToken')
    } // 可选参数 每次选择图片触发,也可用来设置头部,但比headers多了一个参数,可设置formData
    },
    toolbar: { // 如果不上传图片到服务器,此处不必配置
        container: container, // container为工具栏,此次引入了全部工具栏,也可自行配置
        handlers: {
            'image': function () { // 劫持原来的图片点击按钮事件
            QuillWatch.emit(this.quill.id)
        }
    }
}

五、组件代码

<template>
  <div id='quillEditorQiniu'>
    <el-row v-loading="loadingImg">
      <quill-editor
        :style="{width: width ? width: '100%',  background: '#fff'}"
        v-model="content"
        ref="myQuillEditor"
        :options="editorOption"
        @change="onEditorChange($event)"
      >
      </quill-editor>
    </el-row>
    <Upload
      class="avatar-uploader"
      :accept="'image/*'"
      :action="'https://upload.qiniup.com'"
      name='file'
      :data="updateParams"
      :show-upload-list="false"
      :on-success="uploadSuccess"
      :on-error="uploadError"
      :before-upload="beforeUpload"
    >
    </Upload>
  </div>
</template>
<script>
import * as Quill from 'quill'
import { quillEditor } from 'vue-quill-editor'
import { ImageDrop } from 'quill-image-drop-module'
import ImageResize from 'quill-image-resize-module'
import {ImageExtend} from 'quill-image-paste-module'
let fontSizeStyle = Quill.import('attributors/style/size')
fontSizeStyle.whitelist = ['12px', '14px', '16px', '20px', '24px', '36px']
Quill.register(fontSizeStyle, true)
Quill.register('modules/imageDrop', ImageDrop)
Quill.register('modules/imageResize', ImageResize)
Quill.register('modules/ImageExtend', ImageExtend)
const toolbarOptions = [
  ['bold', 'italic', 'underline', 'strike'],
  [{'color': ['#000000', '#e60000', '#ff9900', '#ffff00', '#008a00', '#0066cc', '#9933ff', '#ffffff', '#facccc', '#ffebcc', '#ffffcc', '#cce8cc', '#cce0f5', '#ebd6ff', '#bbbbbb', '#f06666', '#ffc266', '#ffff66', '#66b966', '#66a3e0', '#c285ff', '#888888', '#a10000', '#b26b00', '#b2b200', '#006100', '#0047b2', '#6b24b2', '#444444', '#5c0000', '#663d00', '#666600', '#003700', '#002966', '#3d1466']}, { 'size': fontSizeStyle.whitelist }],
  [{ list: 'ordered' }, { list: 'bullet' }],
  [{ indent: '-1' }, { indent: '+1' }],
  ['link', 'image']
]
// 自定义编辑器的工作条
export default {
  name: 'quill-editor-qiniu',
  components: {
    quillEditor,
    ImageResize
  },
  props: ['initValue', 'width', 'placeholder'],
  created () {
    // 获取初始化回显内容
    this.content = this.initValue
    // 获取上传token
    this.$store.dispatch('uploadToken')
  },
  mounted () {
    // 工具栏中的图片图标被单击的时候调用这个方法
    let imgHandler = (state) => {
      if (state) {
        document.querySelector('.avatar-uploader input').click()
      }
    }
    // 当工具栏中的图片图标被单击的时候
    this.$refs.myQuillEditor.quill.getModule('toolbar').addHandler('image', imgHandler)
  },
  data () {
    return {
      content: '',
      editorOption: {
        placeholder: this.placeholder ? this.placeholder : '请输入',
        theme: 'snow',
        modules: {
          toolbar: {
            container: toolbarOptions
          },
          // 拖拽上传和调整图片大小
          imageDrop: true,
          imageResize: {
            displayStyles: {
              backgroundColor: 'black',
              border: 'none',
              color: 'white'
            },
            modules: [ 'Resize', 'DisplaySize', 'Toolbar' ]
          },
          // 截屏上传
          ImageExtend: {
            loading: true,
            name: 'file',
            change: (xhr, FormData) => {
              FormData.append('token', this.$store.state.upload_token)
            },
            action: 'https://upload.qiniup.com',
            response: (res) => {
              console.log(res, 'response')
              return this.$store.getters.upload_url + res.key
            }
          }
        }
      },
      updateParams: {},
      loadingImg: false
    }
  },
  watch: {
    initValue: function (newVal, oldVal) {
      this.content = newVal
    }
  },
  methods: {
    onEditorChange (event) {
      console.log(event, 'change')
      this.$emit('getEditorInfo', event)
    },
    beforeUpload (request, file) {
      // 设置上传参数
      this.updateParams.token = this.$store.state.upload_token
      this.updateParams.key = request.name
      this.loadingImg = true
    },
    // 上传图片成功
    uploadSuccess (res, file) {
      // file 返回的文件信息,也可以在这里调用七牛上传。
      console.log(res, file, this.$store.getters.upload_url + res.key, 'success')
      // 上传完成以后修改图片地址,回显到quill编辑器中
      let quill = this.$refs.myQuillEditor.quill
      let length = quill.getSelection() ? quill.getSelection().index : 0
      // 插入图片  res.info为服务器返回的图片地址
      console.log(this.$store.getters.upload_url + file.name)
      // quill.insertEmbed(length, 'image', this.$store.getters.upload_url + file.name)
      quill.insertEmbed(length, 'image', this.$store.getters.upload_url + res.key)
      // 调整光标到最后
      quill.setSelection(length + 1)
      this.loadingImg = false
    },
    // 上传图片失败
    uploadError (error, file, list) {
      console.log(error, file, list, 'error')
      this.loadingImg = false
      if (file.error === 'file exists') {
        this.$message({type: 'error', message: list.name + ' 已存在,请重新选择!'})
      } else {
        this.$message({type: 'error', message: list.name + ' 上传出错,请重新上传!'})
      }
    }
  }
}
</script>
<style scoped>
</style>

六、清除复制粘贴样式

1.modules中配置 clipboard选项
editorOption: {
        ...
        
        modules: {
          toolbar: {
            container: toolbarOptions
          },
          clipboard: {
            // 粘贴过滤
            matchers: [[Node.ELEMENT_NODE, this.HandleCustomMatcher]]
          }
          ...
2.实现HandleCustomMatcher方法,过滤复制粘贴数据
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
    }
3.这样存在一个问题,如果每次打开编辑框,编辑之前编辑过的内容,就会调clipboard里面的配置,从而调用HandleCustomMatcher方法,quill里面显示的就是去掉样式的内容,所以我们需要添加一个变量,如果是第一次打开编辑器我们不能调用配置文件,清空样式,只有内容发生改变了,是进行了复制粘贴操作,我们才调用配置过滤样式。
data () {
    ...
    once: true // 配置
    ...
    
    内容改变调用方法
    
    onEditorChange (event) {
        this.once = false
        
    复制粘贴配置调用方法
    HandleCustomMatcher (node, Delta) {
      if (this.once === false) {
        // 文字、图片等,从别处复制而来,清除自带样式,转为纯文本
        let ops = []
        Delta.ops.forEach(op => {
          console.log(node, op, '富文本编辑器中的内容')
          if (op.insert && (typeof op.insert === 'string' || op.insert.image)) {
            ops.push({
              insert: op.insert
            })
          }
        })
        Delta.ops = ops
        return Delta
      } else {
        return Delta
      }
    }
  }

全部代码,封装上传七牛

<!--
  *  initValue 默认值 不设置为空
  *  width 宽度 不设置为100%
  *  placeholder 提示文字 默认为空
  *  special 是否开启 输入特殊符号触发弹窗 默认不开启
  *  specialText 特殊符号 @、#、¥默认为@
  *  replaceInfo 替换的内容
  *  getEditorInfo() 返回数据给父组件
-->
<template>
  <div id='quillEditorQiniu'>
    <el-row v-loading="loadingImg">
      <quill-editor
        :style="{width: width ? width: '100%', background: '#fff'}"
        v-model="content"
        ref="myQuillEditor"
        :options="editorOption"
        @change="onEditorChange($event)"
        @focus="focusEvent()"
        @blur="blurEvent()"
      >
      </quill-editor>
    </el-row>
    <div v-if="special && showSelectList" class="otherInfoList" :style="{left: offsetLeft, top: offsetTop}">
      <slot name="changeList"></slot>
    </div>
    <Upload
      :class="classNm"
      :accept="'image/*'"
      :action="'https://upload.qiniup.com'"
      name='file'
      :data="updateParams"
      :show-upload-list="false"
      :on-success="uploadSuccess"
      :on-error="uploadError"
      :before-upload="beforeUpload"
      style="display: none;"
    >
    </Upload>
  </div>
</template>
<script>
import * as Quill from 'quill'
import { quillEditor } from 'vue-quill-editor'
import { ImageDrop } from 'quill-image-drop-module'
import ImageResize from 'quill-image-resize-module'
import {ImageExtend} from 'quill-image-paste-module'
let fontSizeStyle = Quill.import('attributors/style/size')
fontSizeStyle.whitelist = ['12px', '14px', '16px', '20px', '24px', '36px']
Quill.register(fontSizeStyle, true)
Quill.register('modules/imageDrop', ImageDrop)
Quill.register('modules/imageResize', ImageResize)
Quill.register('modules/ImageExtend', ImageExtend)
const toolbarOptions = [
  ['bold', 'italic', 'underline', 'strike'],
  [{'color': ['#000000', '#e60000', '#ff9900', '#ffff00', '#008a00', '#0066cc', '#9933ff', '#ffffff', '#facccc', '#ffebcc', '#ffffcc', '#cce8cc', '#cce0f5', '#ebd6ff', '#bbbbbb', '#f06666', '#ffc266', '#ffff66', '#66b966', '#66a3e0', '#c285ff', '#888888', '#a10000', '#b26b00', '#b2b200', '#006100', '#0047b2', '#6b24b2', '#444444', '#5c0000', '#663d00', '#666600', '#003700', '#002966', '#3d1466']}, { 'size': fontSizeStyle.whitelist }],
  [{ list: 'ordered' }, { list: 'bullet' }],
  [{ indent: '-1' }, { indent: '+1' }],
  ['link', 'image']
]
// 自定义编辑器的工作条
export default {
  name: 'quill-editor-qiniu',
  components: {
    quillEditor,
    ImageResize
  },
  props: ['initValue', 'width', 'placeholder', 'special', 'replaceInfo'],
  created () {
    // 获取初始化回显内容
    this.content = this.initValue
    // 获取上传token
    this.$store.dispatch('uploadToken')
  },
  mounted () {
    this.classNm = 'avatar-uploader' + new Date().getTime()
    // 工具栏中的图片图标被单击的时候调用这个方法
    let imgHandler = (state) => {
      if (state) {
        document.querySelector(`.${this.classNm} input`).click()
      }
    }
    // 当工具栏中的图片图标被单击的时候
    this.$refs.myQuillEditor.quill.getModule('toolbar').addHandler('image', imgHandler)
  },
  data () {
    return {
      classNm: '',
      content: '',
      once: true,
      showSelectList: false,
      // 编辑器配置选项
      editorOption: {
        placeholder: this.placeholder ? this.placeholder : '请输入',
        theme: 'snow',
        modules: {
          toolbar: {
            container: toolbarOptions
          },
          clipboard: {
            // 粘贴过滤
            matchers: [[Node.ELEMENT_NODE, this.HandleCustomMatcher]]
          },
          // 拖拽上传和调整图片大小
          imageDrop: true,
          imageResize: {
            displayStyles: {
              backgroundColor: 'black',
              border: 'none',
              color: 'white'
            },
            modules: [ 'Resize', 'DisplaySize', 'Toolbar' ]
          },
          // 截屏上传
          ImageExtend: {
            loading: true,
            name: 'file',
            change: (xhr, FormData) => {
              FormData.append('token', this.$store.state.upload_token)
            },
            action: 'https://upload.qiniup.com',
            response: (res) => {
              console.log(res, 'response')
              return this.$store.getters.upload_url + res.key
            }
          }
        }
      },
      updateParams: {},
      loadingImg: false,
      offsetLeft: 0,
      offsetTop: 0
    }
  },
  watch: {
    initValue (newVal, oldVal) {
      this.content = newVal
    },
    replaceInfo (newVal, oldVal) {
      if (this.special) {
        this.content = this._.replace(this.content, '@', newVal)
      }
    }
  },
  methods: {
    onEditorChange (event) {
      this.once = false
      console.log(event, 'change', this.content)
      // 如果为true标识开启@功能
      if (this.special) {
        // 计算输入特殊字符显示浮动框的位置
        // 获取光标index
        let pindex = event.quill.selection.savedRange.index
        console.log(pindex)
        let arr = event.text.split('\n')
        let str = ''
        let haveMark = false
        for (let index = 0; index < arr.length; index++) {
          let value = ' ' + arr[index]
          str += value
          if (pindex <= str.length) {
            console.log(pindex, '循环index的值')
            this.offsetTop = (index + 1) * 20 + 'px'
            this.offsetLeft = arr[index].lastIndexOf('@') * 14 + 'px'
            let newArr = arr[index].split('@')
            if (arr[index].lastIndexOf('@') !== -1 && newArr[newArr.length - 1] === '') {
              haveMark = true
            }
            break
          } else {
            this.offsetTop = 0
            this.offsetLeft = 0
          }
        }
        console.log(str.length, '长度', pindex)
        console.log(this.offsetTop, this.offsetLeft)
        if (pindex && haveMark) {
          this.showSelectList = true
        } else {
          this.showSelectList = false
        }
      }
      this.$emit('getEditorInfo', event)
    },
    // 选择@的信息
    selectItemInfo () {
      this.content = 'gaibaile'
    },
    beforeUpload (request, file) {
      // 设置上传参数
      this.updateParams.token = this.$store.state.upload_token
      this.updateParams.key = request.name
      this.loadingImg = true
    },
    // 上传图片成功
    uploadSuccess (res, file) {
      // file 返回的文件信息,也可以在这里调用七牛上传。
      console.log(res, file, this.$store.getters.upload_url + res.key, 'success')
      // 上传完成以后修改图片地址,回显到quill编辑器中
      let quill = this.$refs.myQuillEditor.quill
      let length = quill.getSelection() ? quill.getSelection().index : 0
      // 插入图片  res.info为服务器返回的图片地址
      console.log(this.$store.getters.upload_url + file.name)
      // quill.insertEmbed(length, 'image', this.$store.getters.upload_url + file.name)
      quill.insertEmbed(length, 'image', this.$store.getters.upload_url + res.key)
      // 调整光标到最后
      quill.setSelection(length + 1)
      this.loadingImg = false
    },
    // 上传图片失败
    uploadError (error, file, list) {
      console.log(error, file, list, 'error')
      this.loadingImg = false
      if (file.error === 'file exists') {
        this.$message({type: 'error', message: list.name + ' 已存在,请重新选择!'})
      } else {
        this.$message({type: 'error', message: list.name + ' 上传出错,请重新上传!'})
      }
    },
    focusEvent () {
      this.$emit('on-focus')
    },
    blurEvent () {
      this.$emit('on-blur')
    },
    HandleCustomMatcher (node, Delta) {
      if (this.once === false) {
        // 文字、图片等,从别处复制而来,清除自带样式,转为纯文本
        let ops = []
        Delta.ops.forEach(op => {
          console.log(node, op, '富文本编辑器中的内容')
          if (op.insert && (typeof op.insert === 'string' || op.insert.image)) {
            ops.push({
              insert: op.insert
            })
          }
        })
        Delta.ops = ops
        return Delta
      } else {
        return Delta
      }
    }
  }
}
</script>
<style lang="scss" scoped>
  #quillEditorQiniu {
    position: relative;
    .otherInfoList {
      position: absolute;
      top: 0px;
      left: 0px;
      background: #fff;
      border: 1px solid #eee;
      padding: 10px;
      border-radius: 5px;
    }
  }
</style>


张旭超
1.4k 声望222 粉丝

精通 html+div+css jquery, vue, angularjs, angular2, angular4, ionic, ionic2