vue-quill-editor --save 實現(xiàn)自定義圖片/視頻上傳

踩在這個作者的肩膀上實現(xiàn)了自定義圖片/視頻上傳 https://sanshui.blog.csdn.net/article/details/81464100状植,原文章有些地方有點沒說清楚的地方怎棱,稍作了些修改窖贤。也解決了文中未提及的bug火邓。
復(fù)制以下代碼即可使用

一堆巧,安裝 vue-quill-editor

npm install vue-quill-editor --save 

二勤揩,
創(chuàng)建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="標(biāo)題1"></button>
        <button class="ql-header" value="2" title="標(biāo)題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">標(biāo)題1</option>
          <option value="2">標(biāo)題2</option>
          <option value="3">標(biāo)題3</option>
          <option value="4">標(biāo)題4</option>
          <option value="5">標(biāo)題5</option>
          <option value="6">標(biāo)題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">
        當(dāng)前已經(jīng)輸入<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: '請編寫內(nèi)容...'
      },
      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>

創(chuàng)建上傳的圖片和視頻的組件寞奸,基于element ui和oss,可根據(jù)自己的需求修改
./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
      // 若文件限制格式不為空但是在可上傳類型內(nèi) 或 未指定上傳文件 則讓他上傳
      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; //時長為秒,小數(shù)决侈,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內(nèi)置的video blot螺垢,用video標(biāo)簽替換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標(biāo)簽所需的屬性
    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標(biāo)簽替換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

如果在引進(jìn)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);
    },
}

清空編輯區(qū)內(nèi)容使用:

this.$refs.quill.$refs.myTextEditor.quill.setText('\n')
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末展父,一起剝皮案震驚了整個濱河市墨林,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌犯祠,老刑警劉巖旭等,帶你破解...
    沈念sama閱讀 223,002評論 6 519
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異衡载,居然都是意外死亡搔耕,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,357評論 3 400
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來弃榨,“玉大人菩收,你說我怎么就攤上這事【ňΓ” “怎么了娜饵?”我有些...
    開封第一講書人閱讀 169,787評論 0 365
  • 文/不壞的土叔 我叫張陵,是天一觀的道長官辈。 經(jīng)常有香客問我箱舞,道長,這世上最難降的妖魔是什么拳亿? 我笑而不...
    開封第一講書人閱讀 60,237評論 1 300
  • 正文 為了忘掉前任晴股,我火速辦了婚禮,結(jié)果婚禮上肺魁,老公的妹妹穿的比我還像新娘电湘。我一直安慰自己,他們只是感情好鹅经,可當(dāng)我...
    茶點故事閱讀 69,237評論 6 398
  • 文/花漫 我一把揭開白布寂呛。 她就那樣靜靜地躺著,像睡著了一般瘾晃。 火紅的嫁衣襯著肌膚如雪昧谊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,821評論 1 314
  • 那天酗捌,我揣著相機(jī)與錄音呢诬,去河邊找鬼。 笑死胖缤,一個胖子當(dāng)著我的面吹牛尚镰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播哪廓,決...
    沈念sama閱讀 41,236評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼狗唉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了涡真?” 一聲冷哼從身側(cè)響起分俯,我...
    開封第一講書人閱讀 40,196評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎哆料,沒想到半個月后缸剪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,716評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡东亦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,794評論 3 343
  • 正文 我和宋清朗相戀三年杏节,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,928評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡奋渔,死狀恐怖镊逝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情嫉鲸,我是刑警寧澤撑蒜,帶...
    沈念sama閱讀 36,583評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站玄渗,受9級特大地震影響座菠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜捻爷,卻給世界環(huán)境...
    茶點故事閱讀 42,264評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望份企。 院中可真熱鬧也榄,春花似錦、人聲如沸司志。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,755評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽骂远。三九已至囚霸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間激才,已是汗流浹背拓型。 一陣腳步聲響...
    開封第一講書人閱讀 33,869評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留瘸恼,地道東北人劣挫。 一個月前我還...
    沈念sama閱讀 49,378評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像东帅,于是被迫代替她去往敵國和親压固。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,937評論 2 361