好久沒登錄博客園了,今天來一發分享。 最近項目里有個需求,上傳文件(好吧,這種需求很常見,這也不是第一次遇到了)。當時第一想法就是直接用form表單提交(原諒我以前就是這麼乾的),不過表單里不僅有文件還有別的信息需要交互,跟後端商量後決定文件單獨上傳,獲取到伺服器端返回的文件地址在和表單一起提...
好久沒登錄博客園了,今天來一發分享。
最近項目里有個需求,上傳文件(好吧,這種需求很常見,這也不是第一次遇到了)。當時第一想法就是直接用form表單提交(原諒我以前就是這麼乾的),不過表單里不僅有文件還有別的信息需要交互,跟後端商量後決定文件單獨上傳,獲取到伺服器端返回的文件地址在和表單一起提交。這裡就需要非同步上傳文件。
在網上扒了扒相關的內容,發現還真不少,阮一峰老師的這篇文章(文件上傳的漸進式增強)就介紹的很具體,下麵就談談自己在實戰中遇到的一些問題的感受吧。
先看看效果,實現了哪些功能
(好吧,就一個按鈕而已,搞得神神秘秘,嘿嘿)
<button type="button" class="btn" @click="upload">點擊上傳文件</button>
給按鈕綁定了一個點擊事件,下麵看看點擊事件方法里做了什麼
methods: { upload: function(){ myUpload({ url: window.location.protocol + '//' + window.location.host + '/crm/upload', maxSize: 10, beforeSend: function(file){ }, callback: function(res){ var data = JSON.parse(res); pageCont.attachmentUrl = data.url; }, uploading: function(pre){ pageCont.uploadCont.display = 'block'; pageCont.uploadStyle.width = pre * 2 + 'px'; pageCont.pre = pre; } }); } }
按鈕綁定的點擊事件執行了upload方法,在upload方法里調用了一下myUpload方法,並傳遞了一些配置信息進去,稍後說下這些配置信息。先看看myUpload的具體實現:
初始化了一個FormData對象和一個XMHttpResquest對象,創建一個type為file的input,並觸發一次該input的click,如下
var fd = new FormData(), xhr = new XMLHttpRequest(), input; input = document.createElement('input'); input.setAttribute('id', 'myUploadInput'); input.setAttribute('type', 'file'); input.setAttribute('name', 'file'); document.body.appendChild(input); input.style.display = 'none'; input.click();
監聽剛纔創建的input的change事件,並作在裡面做相應處理
input.onchange = function(){ if(!input.value){return;} if(option.maxSize && input.files[0].size > option.maxSize * 1024 * 1024){ dialog({ title: '提示', content: '請上傳小於'+option.maxSize+'M的文件', okValue: '確定', ok: function () {} }).showModal(); return; } if(option.beforeSend instanceof Function){ if(option.beforeSend(input) === false){ return false; } } fd.append('file', input.files[0]); xhr.open('post', option.url); xhr.onreadystatechange = function(){ if(xhr.status == 200){ if(xhr.readyState == 4){ if(option.callback instanceof Function){ option.callback(xhr.responseText); } } }else{ if(!(dialog.get('uploadfail'))){ dialog({ id: 'uploadfail', title: '提示', content: '上傳失敗', okValue: '確定', ok: function () {} }).showModal(); } } } xhr.upload.onprogress = function(event){ var pre = Math.floor(100 * event.loaded / event.total); if(option.uploading instanceof Function){ option.uploading(pre); } } xhr.send(fd); }
解釋下上面的代碼。input的change事件觸發後,首先判斷了下當前是否選擇了文件
if(!input.value){return;}
已開始我是沒做這個判斷的,在後來的測試過程中發現,當上傳一次文件後,再次點擊按鈕上傳,打開文件選擇框,然後不選擇文件,而是點擊取消按鈕,change事件也觸發了,導致後面的代碼也會執行,顯然這不合理,故加了這個判斷。
然後限制了下上傳文件的大小(這樣的事能夠前端處理就不要交給服務端來驗證了),當文件大小超過最大限制,就會彈框提示
if(option.maxSize && input.files[0].size > option.maxSize * 1024 * 1024){ dialog({ title: '提示', content: '請上傳小於'+option.maxSize+'M的文件', okValue: '確定', ok: function () {} }).showModal(); return; }
然後加了一個文件上傳前的操作,可以在文件上傳前做一些處理,如進度條的顯示,圖片預覽等等
if(option.beforeSend instanceof Function){ if(option.beforeSend(input) === false){ return false; } }
接下來將文件append到formData對象里,使用欄位名‘file’,該欄位名是服務端接收文件時使用的欄位名
fd.append('file', input.files[0]);
然後就是使用XMLHttpRequest對象向服務端發送數據了
xhr.open('post', option.url); xhr.onreadystatechange = function(){ if(xhr.status == 200){ if(xhr.readyState == 4){ if(option.callback instanceof Function){ option.callback(xhr.responseText); } } }else{ if(!(dialog.get('uploadfail'))){ dialog({ id: 'uploadfail', title: '提示', content: '上傳失敗', okValue: '確定', ok: function () {} }).showModal(); } } } xhr.upload.onprogress = function(event){ var pre = Math.floor(100 * event.loaded / event.total); if(option.uploading instanceof Function){ option.uploading(pre); } } xhr.send(fd);
在向服務端發送數據時,使用了監聽了一下progress事件,主要是為了進行上傳進度的顯示,上述代碼中,
var pre = Math.floor(100 * event.loaded / event.total);
獲取上傳的百分比,能夠拿到這個值,頁面上就可以展示各種各樣的上傳進度效果了。
差不多介紹完了,下麵補充一下使用中遇到的問題:
問題一:文件在上傳的過程中,使用JSON.parse()序列化服務端返回的json字元串報錯(傻啊,文件還在上傳,服務端怎麼會返回數據啊)。
事情是這樣的,一開始,我在readystatechange里只監聽了狀態碼是否是200,如果是就說明通了,然後執行回調,在回調里處理服務端返回的數據,但是通了不一定代表服務端已經返回了數據,所以就出現了上面的錯誤,所以後來在判斷了status是否為200後,還判斷了readyState,以確保服務端已處理完畢並返回數據在執行回調
if(xhr.status == 200){ if(option.callback instanceof Function){ option.callback(xhr.responseText); } }
問題二:重覆創建input。每次點擊按鈕上傳文件後,頁面都會多一個type=file的input感覺不是很好(個人癖好吧),所以對最開始的初始化代碼做了下優化,判斷當前頁面是否存在剛纔創建的input,存在就直接使用,不存在就創建,如下
if(document.getElementById('myUploadInput')){ input = document.getElementById('myUploadInput'); }else{ input = document.createElement('input'); input.setAttribute('id', 'myUploadInput'); input.setAttribute('type', 'file'); input.setAttribute('name', 'file'); document.body.appendChild(input); input.style.display = 'none'; }
好了,就這麼多了。看看效果
因個人知識面有限,如有錯誤,還請指正。轉載請註明出處,謝謝!