項目使用的是vue框架,需要一個markdown的編輯框,就在npm上找了一下,發(fā)現(xiàn)simplemde挺不錯的,由于我比較懶,就順便在npm又搜了一下,找到了vue-simplemde
這個 package ,那就開始使用它吧。
創(chuàng)新互聯(lián)是專業(yè)的貴溪網(wǎng)站建設(shè)公司,貴溪接單;提供成都網(wǎng)站設(shè)計、成都做網(wǎng)站,網(wǎng)頁設(shè)計,網(wǎng)站設(shè)計,建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進行貴溪網(wǎng)站開發(fā)網(wǎng)頁制作和功能擴展;專業(yè)做搜索引擎喜愛的網(wǎng)站,專業(yè)的做網(wǎng)站團隊,希望更多企業(yè)前來合作!
但是這個 vue-simplemde
不支持圖片拖拽上傳、粘貼上傳,也不能說是因為這個 vue-simplemde ,因為 vue-simplemde 只是對 simplemde 的基礎(chǔ)上封裝成一個Vue插件。所以最后還是由于 simplemde 沒有提供相關(guān)的功能,但是為了用戶體驗考慮,這個功能時必要的,除非不使用markdown編輯器。而去使用富文本編輯器,那樣的話,項目很多的代碼都要進行更改。所以就在網(wǎng)上查了文章,及在github上查了一些代碼。下面將進行分析
拖拽
拖拽的API核心是 drop 這個事件,就是當我們從桌面拖動一個文件到瀏覽器里時,松開的時候,而觸發(fā)的事件名。
我們都知道,你隨便拖動一個圖片到瀏覽器里,會直接打開這個圖片,這是因為瀏覽器默認你拖動文件到瀏覽器里時,將打開這個文件,所以,我們需要阻止原生的操作。
我們現(xiàn)在先寫一段代碼,讓其屏蔽掉默認事件
window.addEventListener("drop", e => { e = e || event if (e.target.className === 'CodeMirror-scroll') { // 如果進入到編輯器的話,將阻止默認事件 e.preventDefault() } }, false)
CodeMirror-scroll 這個Class就是 simplemde 編輯框的Class名稱。
現(xiàn)在我們拖拽文件到這個編輯框,然后松掉,不會出現(xiàn)任何反應(yīng)。如果在編輯框之外的地方,還是會繼續(xù)觸發(fā)默認事件。
下面就是獲取 simplemde 方法,給他 drop 事件處理方法。
// 假設(shè)頁面一共有三個編輯窗口,所以需要循環(huán)監(jiān)聽事件 [ 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 = [] // 要上傳的文件實例數(shù)組 // 循環(huán),是因為可能會同時拖動幾個圖片文件 for (let i = 0; i < dataList.length; i++) { // 如果不是圖片,則彈窗警告 僅支持拖拽圖片文件 if (dataList[i].type.indexOf('image') === -1) { // 下面的continue,作用是,如果用戶同時拖動2個圖片和一個文檔,那么文檔不給于上傳,圖片照常上傳。 continue } imageFiles.push(dataList[i]) // 先把當前的文件push進數(shù)組里,等for循環(huán)結(jié)束之后,統(tǒng)一上傳。 } // uploadImagesFile方法是上傳圖片的方法 // simplemde.codemirror的作用是用于區(qū)分當前的圖片上傳是處于哪個編輯框 this.uploadImagesFile(simplemde.codemirror, imageFiles) // 因為已經(jīng)有了下面這段代碼,所以上面的屏蔽默認事件代碼就不用寫了 e.preventDefault() }) })
詐一看,代碼好像有點多,那是因為注釋的原因,下面是沒有注釋的代碼。你可以根據(jù)下面的代碼,有自己的見解和理解:
[ 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 的時候,是不會發(fā)生任何變化的,所以沒用必要禁止默認事件。
下面是代碼:
simplemde.codemirror.on('paste', (editor, e) => { // 粘貼圖片的觸發(fā)函數(shù) 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循環(huán)里,使用 dataList[0].kind 。也就是 e.clipboardData.items[0].kind 。當 item 為空時,還去訪問一個不存的 kind 屬性時,就會報錯了。所以這里需要使用 try...catch 方法進行判斷。
dataList[0].getAsFile().type.indexOf('image') !== -1
這個句話是判斷,粘貼的東西確認是圖片,而不是其他東西。
if 里的上傳圖片,不一樣的地方是 [dataList[0].getAsFile()] ,因為為了統(tǒng)一格式,方便 uploadImagesFile 函數(shù)進行處理,我加上了 [] ,使之成為數(shù)組。 dataList[0].getAsFile() 就是獲取文件實例了。
上傳
上傳就有一點麻煩了:
uploadImagesFile (simplemde, files) { // 把每個文件實例使用FormData進行包裝一下,然后返回一個數(shù)組 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) } } // 服務(wù)端返回的格式是{state: Boolean, data: String} // state為false時,data就是返回的錯誤信息 // state為true時,data是圖片上傳后url地址,這個地址是針對網(wǎng)站的絕對路徑。如下: // /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') // 和編輯框之前的內(nèi)容進行拼接 } })) }
因為我是把 axiox 封裝成vue插件來使用,這樣會導致, this.$http 是實例化后的,而不是他本身。 axios 維護者說的解決方案是,重新引入 axios 包,來使用。但是我覺得沒有必要。 axios.all 內(nèi)部是 Promise.all 。 axios.spread 實現(xiàn)代碼比較少,就直接拿過來,重新賦值給 axios 就好了
所以上面有段代碼是
Promise.all(requests) .then(this.$http.spread((...resps) => { // code })
把這段代碼翻譯一下就是
axios.all(requests) .then(axios.spread((...resps) => { // code })
關(guān)于這個問題,請看下官方的解釋:axios-all-is-not-a-function-inside-vue-component 。也可以看下 axios 的代碼: axios.js#L45-L48
這個問題,暫時就不深究了,我們回到剛剛的話題上。
上面我說到當state為true時,data是文件相對于網(wǎng)站的絕對路徑,如: /static/upload/2cfd6a50-3d30-11e8-b351-0d25ce9162a3.png
如果我們需要進行拼接一下,所以就有了 ![](${location.origin + data})
這段代碼進行拼接。最后的兩行是獲取指的獲取之前的內(nèi)容,然后在追加url地址。