基於vue-simplemde實現圖片拖拽、粘貼功能的一些思考

前言

項目使用的是vue框架,須要一個markdown的編輯框,就在npm上找了一下,發現simplemde挺不錯的,因爲我比較懶,就順便在npm又搜了一下,找到了vue-simplemde這個package,那就開始使用它吧。javascript

可是這個vue-simplemde不支持圖片拖拽上傳、粘貼上傳,也不能說是由於這個vue-simplemde,由於vue-simplemde只是對simplemde的基礎上封裝成一個Vue插件。因此最後仍是因爲simplemde沒有提供相關的功能,可是爲了用戶體驗考慮,這個功能時必要的,除非不使用markdown編輯器。而去使用富文本編輯器,那樣的話,項目不少的代碼都要進行更改。因此就在網上查了文章,及在github上查了一些代碼。下面將進行分析vue

拖拽

拖拽的API核心是drop這個事件,就是當咱們從桌面拖動一個文件到瀏覽器裏時,鬆開的時候,而觸發的事件名。java

咱們都知道,你隨便拖動一個圖片到瀏覽器裏,會直接打開這個圖片,這是由於瀏覽器默認你拖動文件到瀏覽器裏時,將打開這個文件,因此,咱們須要阻止原生的操做。ios

咱們如今先寫一段代碼,讓其屏蔽掉默認事件laravel

window.addEventListener("drop", e => {
  e = e || event
  if (e.target.className === 'CodeMirror-scroll') { // 若是進入到編輯器的話,將阻止默認事件
    e.preventDefault()
  }
}, false)
複製代碼

CodeMirror-scroll這個Class就是simplemde編輯框的Class名稱。git

如今咱們拖拽文件到這個編輯框,而後鬆掉,不會出現任何反應。若是在編輯框以外的地方,仍是會繼續觸發默認事件。github

下面就是獲取simplemde方法,給他drop事件處理方法。npm

// 假設頁面一共有三個編輯窗口,因此須要循環監聽事件
[ this.$refs.simplemde1,
  this.$refs.simplemde2,
  this.$refs.simplemde3
].map(({simplemde}) => {
  simplemde.codemirror.on('drop', (editor, e) => {
    if (!(e.dataTransfer && e.dataTransfer.files)) {
      // 彈窗說明,此瀏覽器不支持此操做
      return
    }

    let dataList = e.dataTransfer.files
    let imageFiles = [] // 要上傳的文件實例數組

    // 循環,是由於可能會同時拖動幾個圖片文件
    for (let i = 0; i < dataList.length; i++) {
    // 若是不是圖片,則彈窗警告 僅支持拖拽圖片文件
      if (dataList[i].type.indexOf('image') === -1) {
        // 下面的continue,做用是,若是用戶同時拖動2個圖片和一個文檔,那麼文檔不給於上傳,圖片照常上傳。
        continue
      }
      imageFiles.push(dataList[i])  // 先把當前的文件push進數組裏,等for循環結束以後,統一上傳。
    }
    // uploadImagesFile方法是上傳圖片的方法
    // simplemde.codemirror的做用是用於區分當前的圖片上傳是處於哪一個編輯框
    this.uploadImagesFile(simplemde.codemirror, imageFiles)
    // 由於已經有了下面這段代碼,因此上面的屏蔽默認事件代碼就不用寫了
    e.preventDefault()
  })
})
複製代碼

詐一看,代碼好像有點多,那是由於註釋的緣由,下面是沒有註釋的代碼。你能夠根據下面的代碼,有本身的看法和理解:axios

[ this.$refs.simplemde1,
  this.$refs.simplemde2,
  this.$refs.simplemde3
].map(({simplemde}) => {
  simplemde.codemirror.on('drop', (editor, e) => {
    if (!(e.dataTransfer && e.dataTransfer.files)) {
      return
    }
    let dataList = e.dataTransfer.files
    let imageFiles = []
    for (let i = 0; i < dataList.length; i++) {
      if (dataList[i].type.indexOf('image') === -1) {
        continue
      }
      imageFiles.push(dataList[i])
    }
    this.uploadImagesFile(simplemde.codemirror, imageFiles)
    e.preventDefault()
  })
})
複製代碼

粘貼

粘貼的API是paste方法,這個不像上面同樣,粘貼不須要禁止默認事件,由於咱們能夠看到,你複製一個圖片,到瀏覽器裏按下ctrl+v的時候,是不會發生任何變化的,因此沒用必要禁止默認事件。數組

下面是代碼:

simplemde.codemirror.on('paste', (editor, e) => { // 粘貼圖片的觸發函數
  if (!(e.clipboardData && e.clipboardData.items)) {
    // 彈窗說明,此瀏覽器不支持此操做
    return
  }
  try {
    let dataList = e.clipboardData.items
    if (dataList[0].kind === 'file' && dataList[0].getAsFile().type.indexOf('image') !== -1) {
      this.uploadImagesFile(simplemde.codemirror, [dataList[0].getAsFile()])
    }
  } catch (e) {
    // 彈窗說明,只能粘貼圖片
  }
})
複製代碼

之因此這裏寫上try...catch方法,是由於若是你粘貼的時候,若是是一個文件,items將是空的,而在下面的if循環裏,使用dataList[0].kind。也就是e.clipboardData.items[0].kind。當item爲空時,還去訪問一個不存的kind屬性時,就會報錯了。因此這裏須要使用try...catch方法進行判斷。

dataList[0].getAsFile().type.indexOf('image') !== -1這個句話是判斷,粘貼的東西確認是圖片,而不是其餘東西。

if裏的上傳圖片,不同的地方是[dataList[0].getAsFile()],由於爲了統一格式,方便uploadImagesFile函數進行處理,我加上了[],使之成爲數組。dataList[0].getAsFile()就是獲取文件實例了。

上傳

上傳就有一點麻煩了:

uploadImagesFile (simplemde, files) {
  // 把每一個文件實例使用FormData進行包裝一下,而後返回一個數組
  let params = files.map(file => {
    let param = new FormData()
    param.append('file', file, file.name)
    return param
  })

  let makeRequest = params => {
    return this.$http.post('/Api/upload', params)
  }
  let requests = params.map(makeRequest)

  this.$http.spread = callback => {
    return arr => {
      return callback.apply(null, arr)
    }
  }

  // 服務端返回的格式是{state: Boolean, data: String}
  // state爲false時,data就是返回的錯誤信息
  // state爲true時,data是圖片上傳後url地址,這個地址是針對網站的絕對路徑。以下:
  // /static/upload/2cfd6a50-3d30-11e8-b351-0d25ce9162a3.png
  Promise.all(requests)
    .then(this.$http.spread((...resps) => {
      for (let i = 0; i < resps.length; i++) {
        let {state, data} = resps[i].data
        if (!state) {
          // 彈窗顯示data的錯誤信息
          continue
        }
        let url = `![](${location.origin + data})`  // 拼接成markdown語法
        let content = simplemde.getValue()
        simplemde.setValue(content + url + '\n')  // 和編輯框以前的內容進行拼接
      }
    }))
}
複製代碼

由於我是把axiox封裝成vue插件來使用,這樣會致使,this.$http是實例化後的,而不是他自己。 axios維護者說的解決方案是,從新引入axios包,來使用。可是我以爲沒有必要。axios.all內部是Promise.allaxios.spread實現代碼比較少,就直接拿過來,從新賦值給axios就行了

因此上面有段代碼是

Promise.all(requests)
  .then(this.$http.spread((...resps) => {
    // code
  })
複製代碼

把這段代碼翻譯一下就是

axios.all(requests)
  .then(axios.spread((...resps) => {
    // code
  })
複製代碼

關於這個問題,請看下官方的解釋:axios-all-is-not-a-function-inside-vue-component。也能夠看下axios的代碼:axios.js#L45-L48

這個問題,暫時就不深究了,咱們回到剛剛的話題上。

上面我說到當state爲true時,data是文件相對於網站的絕對路徑,如: /static/upload/2cfd6a50-3d30-11e8-b351-0d25ce9162a3.png

若是咱們須要進行拼接一下,因此就有了![](${location.origin + data})這段代碼進行拼接。最後的兩行是獲取指的獲取以前的內容,而後在追加url地址。

結尾

下面是最終的效果圖,由於掘金沒法上傳gif圖片。因此就直接附上動態圖鏈接: 7xppwd.com1.z0.glb.clouddn.com/2b971f90-3d…

完整代碼:Subject.vue#L378-L465

參考 && 感謝

skecozo做者的laravel-demo項目裏的部分代碼

Lemon做者的《simplemde 實現拖拽、粘貼圖片上傳》文章

f-loat做者的vue-simplemde項目

wescossick做者的simplemde項目

相關文章
相關標籤/搜索