vue-quill-editor –save 实现自定义图片/视频上传

踩在这个作者的肩膀上实现了自定义图片/视频上传 https://sanshui.blog.csdn.net/article/details/81464100,原文章有些地方有点没说清楚的地方,稍作了些修改。也解决了文中未提及的bug。
复制以下代码即可使用

一,安装 vue-quill-editor

npm install vue-quill-editor --save 

二,
创建QuillEditor组件

<template>
  <div>
    <quill-editor
      ref="myTextEditor"
      v-model="contentValue"
      :options="editorOption"
      @blur="onEditorBlur($event)"
      @focus="onEditorFocus($event)"
      @ready="onEditorReady($event)"
      @change="onEditorChange($event)"
      class="cfpa-quill-editor"
      :style="{ height: quillEditorHeight +  px  }"
    >
      <div id="toolbar" slot="toolbar">
        <!-- Add a bold button -->
        <button class="ql-bold" title="加粗">Bold</button>
        <button class="ql-italic" title="斜体">Italic</button>
        <button class="ql-underline" title="下划线">underline</button>
        <button class="ql-strike" title="删除线">strike</button>
        <button class="ql-blockquote" title="引用"></button>
        <button class="ql-code-block" title="代码"></button>
        <button class="ql-header" value="1" title="标题1"></button>
        <button class="ql-header" value="2" title="标题2"></button>
        <!--Add list -->
        <button class="ql-list" value="ordered" title="有序列表"></button>
        <button class="ql-list" value="bullet" title="无序列表"></button>
        <!-- Add font size dropdown -->
        <select class="ql-header" title="段落格式">
          <option selected>段落</option>
          <option value="1">标题1</option>
          <option value="2">标题2</option>
          <option value="3">标题3</option>
          <option value="4">标题4</option>
          <option value="5">标题5</option>
          <option value="6">标题6</option>
        </select>
        <select class="ql-size" title="字体大小">
          <option value="10px">10px</option>
          <option value="12px">12px</option>
          <option value="14px">14px</option>
          <option value="16px" selected>16px</option>
          <option value="18px">18px</option>
          <option value="20px">20px</option>
        </select>
        <select class="ql-font" title="字体">
          <option value="SimSun" selected="selected"></option>
          <option value="SimHei"></option>
          <option value="Microsoft-YaHei"></option>
          <option value="KaiTi"></option>
          <option value="FangSong"></option>
          <option value="Arial"></option>
          <!-- <option value="Times-New-Roman"></option>
          <option value="sans-serif"></option> -->
        </select>

        <!-- Add subscript and superscript buttons -->
        <select class="ql-color" value="color" title="字体颜色"></select>
        <select
          class="ql-background"
          value="background"
          title="背景颜色"
        ></select>
        <select class="ql-align" value="align" title="对齐"></select>
        <button class="ql-clean" title="还原"></button>
        <!-- <button class="ql-link" title="超链接"></button> -->
        <!-- You can also add your own -->
        <button
          id="custom-button"
          @click.prevent="fnOpenUploadImage"
          title="图片"
        >
          <i class="iconfont el-icon-picture"></i>
        </button>
        <button
          id="custom-button"
          @click.prevent="fnOpenUploadVideo"
          title="视频"
        >
          <i class="iconfont el-icon-video-play"></i>
        </button>
      </div>
    </quill-editor>
    <div :style="wordCount" v-if="wordCount" class="cfpa-quill-wordCount">
      <div class="cfpa-quill-wordCount-text">
        当前已经输入<span style="color: red">{{ contentLength }}</span
        >个字符
      </div>
    </div>
    <el-dialog
      :title="title"
      width="30%"
      :visible.sync="dialogFnOpenUpload"
      :close-on-click-modal="false"
    >
      <file-upload
        :data_extra="data_extra"
        @fnUploadSucess="fnUploadSucess"
        @fnCloseDialog="dialogFnOpenUpload = false"
        ref="fileUpload"
        :types="
          uploadType ===  video 
            ? [ video/mp4 ]
            : [ image/jpeg ,  image/png ,  image/jpg ,  image/bmp ]
        "
      ></file-upload>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogFnOpenUpload = false">取 消</el-button>
        <el-button type="primary" @click="fnOpenUploadSubmit">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>
<script>
import  quill/dist/quill.core.css 
import  quill/dist/quill.snow.css 
import  quill/dist/quill.bubble.css 
import fileUpload from  ./upload 
import { Quill, quillEditor } from  vue-quill-editor 

// 这里引入修改过的video模块并注册
import Video from  ./upload/video 
Quill.register(Video, true)

// 图片可收缩
import { ImageDrop } from  quill-image-drop-module 
import ImageResize from  quill-image-resize-module 
Quill.register( modules/imageDrop , ImageDrop)
Quill.register( modules/imageResize , ImageResize)

// 自定义字体大小
let Size = Quill.import( attributors/style/size )
Size.whitelist = [ 10px ,  12px ,  14px ,  16px ,  18px ,  20px ]
Quill.register(Size, true)

// 自定义字体类型
var fonts = [ SimSun ,  SimHei ,  Microsoft-YaHei ,  KaiTi ,  FangSong ,  Arial ,  Times-New-Roman ,  sans-serif ]
var Font = Quill.import( formats/font )
Font.whitelist = fonts // 将字体加入到白名单
Quill.register(Font, true)

export default {
  name:  editor ,
  components: {
    quillEditor,
    fileUpload
  },
  props: {
    value: {
      type: String,
      default:   
    },
    editorHeight: {
      type: Number,
      default: 355
    },
    editorWordCount: {
      type: Number,
      default: 0
    }
  },
  data () {
    return {
      contentValue:   ,
      preContent:   ,
      dialogFnOpenUpload: false,
      uploadType:  image ,
      editorOption: {
        modules: {
          toolbar:  #toolbar ,
          history: {
            delay: 1000,
            maxStack: 50,
            userOnly: false
          },
          imageDrop: true,
          imageResize: {
            displayStyles: {
              backgroundColor:  black ,
              border:  none ,
              color:  white 
            },
            modules: [ Resize ,  DisplaySize ,  Toolbar ]
          }
        },
        placeholder:  请编写内容... 
      },
      data_extra: {
        parentId: 0,
        fileName:   
      },
      contentLength: 0,
      wordCount:   ,
      title:  添加图片 ,
      quillEditorHeight: 300
    }
  },
  computed: {
    editor () {
      return this.$refs.myTextEditor.quill
    }
  },
  methods: {
    /**
     * @description [onEditorBlur 失去焦点]
     * @author   zoumiao
     * @param {Object} editor 返回的quill对象
     * @return   {null}   [没有返回]
     */
    onEditorBlur (editor) {
      this.$emit( editorBlur )
    },
    /**
     * @description [onEditorFocus 获取焦点]
     * @author   zoumiao
     * @param {Object} editor 返回的quill对象
     * @return   {null}   [没有返回]
     */
    onEditorFocus (editor) {
      this.$emit( editorFocus )
    },
    /**
     * @description [onEditorReady 可以输入]
     * @author   zoumiao
     * @param {Object} editor 返回的quill对象
     * @return   {null}   [没有返回]
     */
    onEditorReady (editor) {
    },
    /**
     * @description [onEditorChange 输入文本改变事件]
     * @author   zoumiao
     * @param {Object} editor 返回的编辑对象{html, text, quill}
     * @return   {null}   [没有返回]
     */
    onEditorChange (editor) {
      console.log(editor);
      let html = editor.html
      this.preContent = html
      this.$emit( input , html)
      this.contentLength = editor.text.trim().length
    },
    /**
     * @description [fnOpenUploadImage 上传图片]
     * @author   zoumiao
     * @return   {null}   [没有返回]
     */
    fnOpenUploadImage () {
      this.uploadType =  image 
      this.title =  添加图片 
      this.dialogFnOpenUpload = true
    },
    /**
     * @description [fnOpenUploadVideo 上传视频]
     * @author   zoumiao
     * @return   {null}   [没有返回]
     */
    fnOpenUploadVideo () {
      this.uploadType =  video 
      this.title =  添加视频 
      this.dialogFnOpenUpload = true
    },
    /**
     * [fnOpenUploadSubmit 提交上传文件]
     * @author   zoumiao
     * @return   {null}   [没有返回]
     */
    async fnOpenUploadSubmit () {
      await this.$refs.fileUpload.$refs.upload.submit()
    },
    /**
     * [fnUploadSucess 上传文件成功]
     * @author   zoumiao
     * @param {Array} uploadFileUrlList [上传文件返回的url]
     * @return   {null}   [没有返回]
     */
    fnUploadSucess (uploadFileUrlList) {
      this.editor.focus()
      this.editor.insertEmbed(this.editor.getSelection().index, this.uploadType, uploadFileUrlList)
      this.dialogFnOpenUpload = false
    }
  },
  created () {
    this.quillEditorHeight = document.body.clientHeight - this.editorHeight
    this.contentValue = this.value
    this.contentLength = this.editorWordCount || 0
  },
  mounted () {
    let toolbar = document.querySelector( div.ql-toolbar.ql-snow )
    if (toolbar) {
      let toolbarHeight = toolbar.offsetHeight
      this.wordCount = {
         top : `${toolbarHeight}px`
      }
      return
    }
    this.wordCount = {
       top :  42px 
    }
  },
  watch: {
    // Watch content change
    value (newVal, oldVal) {
      if (newVal && newVal !== this.preContent) {
        this.preContent = newVal
        this.contentValue = newVal
      } else if (!newVal) {
        this.contentValue =   
      }
    }
  }
}
</script>
<style lang="scss">
.cfpa-quill-editor {
  line-height: 24px;
  .ql-snow {
    background-color: #ffffff;
  }
}
.cfpa-quill-wordCount {
  background-color: #ffffff;
  position: relative;
  border-left: 1px solid #ccc;
  border-right: 1px solid #ccc;
  border-bottom: 1px solid #ccc;
  border-bottom-left-radius: 3px;
  border-bottom-right-radius: 3px;
  line-height: 20px;
  font-size: 12px;
  .cfpa-quill-wordCount-text {
    text-align: right;
    margin-right: 10px;
    color: #aaa;
  }
}
</style>

创建上传的图片和视频的组件,基于element ui和oss,可根据自己的需求修改
./upload/index.vue

<template>
  <div id="fileUpload">
    <el-upload
      :disabled="loading && showLoading"
      :action="action"
      :data="dataObj"
      :before-upload="beforeUpload"
      :on-success="fnUploadSucess"
      :on-error="handlerError"
      :on-preview="handlePreview"
      :show-file-list="false"
      :on-remove="handleRemove"
      :loading="loading"
      :file-list="fileList"
    >
      <i class="el-icon-upload"></i>
      <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
    </el-upload>
  </div>
</template>

<script>
export default {
  name:  FileUpload ,
  data () {
    return {
      action:  api/third/oss/upload ,
      dataObj: {},
      fileList: [],
      loading: false,
    }
  },
  props: {
    types: {
      type: Array
    },
    showLoading: {
      type: Boolean,
      default: true
    },
    limitSize: {
      type: Boolean,
      default: false
    }
  },
  created () {
  },
  methods: {
    handleRemove (file, fileList) {
      this.$emit( removeFile , [file, fileList])
    },
    handlePreview (file, fileList) {
      this.$emit( preview , [file, fileList])
    },
    beforeUpload (file) {
      let canUpload = false
      // 若文件限制格式不为空但是在可上传类型内 或 未指定上传文件 则让他上传
      if (this.types && this.types.length !== 0) {
        for (let i = 0; i < this.types.length; i++) {
          if (file.type === this.types[i]) {
            canUpload = true
          }
        }
      } else {
        canUpload = true
      }
      if (!canUpload) {
        this.$message.error( 上传仅支持:  + this.types +  文件 )
        return false
      }
      if (this.limitSize) {
        //限制大小
        const isLt2M = file.size / 1024 / 1024 < 2;
        if (!isLt2M) {
          this.$message.error( 上传图片大小不能超过 2MB! );
          return false
        }
      }
      this.loading = true
      //获取时长
      this.getVideoDuration(file)

    },
    getVideoDuration (file) {
      var url = URL.createObjectURL(file);
      var audioElement = new Audio(url);
      var duration;
      audioElement.addEventListener("loadedmetadata", () => {
        duration = audioElement.duration; //时长为秒,小数,182.36
        this.$emit( getDuration , duration)
      });
    },

    fnUploadSucess (res, file) {
      this.$emit( fnUploadSucess , file.response.data)
      this.loading = false
      this.fileList = []
    },
    handlerError () {
      this.loading = false
      this.$message.error( 上传失败 )
      this.$emit( error )
    }
  }
}
</script>

<style scoped lang="scss">
#fileUpload {
  .fileInput {
    display: none;
  }
}
</style>

修改Quill内置的video blot,用video标签替换iframe
./upload/video.js

import { Quill } from "vue-quill-editor";

// 源码中是import直接倒入,这里要用Quill.import引入
const BlockEmbed = Quill.import("blots/block/embed");
const Link = Quill.import("formats/link");

const ATTRIBUTES = ["height", "width"];

class Video extends BlockEmbed {
  static create(value) {
    const node = super.create(value);
    // 添加video标签所需的属性
    node.setAttribute("controls", "controls");
    node.setAttribute("type", "video/mp4");
    node.setAttribute("src", this.sanitize(value));
    return node;
  }

  static formats(domNode) {
    return ATTRIBUTES.reduce((formats, attribute) => {
      if (domNode.hasAttribute(attribute)) {
        formats[attribute] = domNode.getAttribute(attribute);
      }
      return formats;
    }, {});
  }

  static sanitize(url) {
    return Link.sanitize(url); // eslint-disable-line import/no-named-as-default-member
  }

  static value(domNode) {
    return domNode.getAttribute("src");
  }

  format(name, value) {
    if (ATTRIBUTES.indexOf(name) > -1) {
      if (value) {
        this.domNode.setAttribute(name, value);
      } else {
        this.domNode.removeAttribute(name);
      }
    } else {
      super.format(name, value);
    }
  }

  html() {
    const { video } = this.value();
    return `<a href="${video}">${video}</a>`;
  }
}
Video.blotName = "video"; // 这里不用改,楼主不用iframe,直接替换掉原来,如果需要也可以保留原来的,这里用个新的blot
Video.className = "ql-video";
Video.tagName = "video"; // 用video标签替换iframe

export default Video;

如果报错说没有下面这些依赖,请依次安装

npm install quill-image-extend-module --save-dev
npm install quill-image-drop-module --save-dev 
npm install quill-image-resize-module --save-dev

如果在引进quill-image-resize-module 报错TypeError: Cannot read property imports of undefined,
在vue.config.js中:(没有就在在根目录下新建vue.config.js 文件 (不是在src 下))(配置后需要重新运行一下)

var webpack = require( webpack );
module.exports = {
  // 在vue.config.js中configureWebpack中配置
// 要引入webpack

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

最后在页面中使用:


<template>
<div>
 <quill-editor @input="changeContent" ref="quill"></quill-editor>
</div>
</template>
         

import QuillEditor from  @/components/QuillEditor 
//多余代码就不写了
  components: {
  QuillEditor
  },
data(){
return {
content :""
}
}
methods:{
    // 获取html
    changeContent (val) {
      this.content = val
      console.log(val);
    },
}

清空编辑区内容使用:

this.$refs.quill.$refs.myTextEditor.quill.setText( 
 )

© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
none
暂无评论...