項目使用的是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.all
。axios.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…
skecozo做者的laravel-demo項目裏的部分代碼