Vue封裝一個簡單輕量的上傳檔案組件的樣本,vue上傳檔案

來源:互聯網
上載者:User

Vue封裝一個簡單輕量的上傳檔案組件的樣本,vue上傳檔案

一、之前遇到的一些問題

項目中多出有上傳檔案的需求,使用現有的UI架構實現的過程中,不知道什麼原因,總會有一些莫名其妙的bug。比如用某上傳組件,明明註明(:multiple="false"),可實際上還是能多選,上傳的時候依然發送了多個檔案;又比如只要加上了(:file-list="fileList")屬性,希望能手動控制上傳列表的時候,上傳事件this.refs.[upload(組件ref)].submit()就不起作用了,傳不了。總之,懶得再看它怎麼實現了,我用的是功能,介面本身還是要重寫的,如果堅持用也會使項目多很多不必要的邏輯、樣式代碼……

之前用Vue做項目用的視圖架構有element-ui,團隊內部作為補充的zp-ui,以及iview。架構是好用,但是針對自己的項目往往不能全部拿來用,尤其是我們的設計妹子出的介面與現有架構差異很大,改源碼效率低又容易導致未知的bug,於是自己就抽時間封裝了這個上傳組件。

二、代碼與介紹

父組件

<template> <div class="content"> <label for="my-upload">  <span>上傳</span> </label>  <my-upload   ref="myUpload"   :file-list="fileList"   action="/uploadPicture"   :data="param"   :on-change="onChange"   :on-progress="uploadProgress"   :on-success="uploadSuccess"   :on-failed="uploadFailed"   multiple   :limit="5"   :on-finished="onFinished">  </my-upload>  <button @click="upload" class="btn btn-xs btn-primary">Upload</button> </div></template><script>import myUpload from './components/my-upload'export default { name: 'test', data(){  return {  fileList: [],//上傳檔案清單,無論單選還是支援多選,檔案都以列表格式儲存  param: {param1: '', param2: '' },//攜帶參數列表  } }, methods: {  onChange(fileList){//監聽檔案變化,增減檔案時都會被子組件調用  this.fileList = [...fileList];  },  uploadSuccess(index, response){//某個檔案上傳成功都會執行該方法,index代表列表中第index個檔案  console.log(index, response);  },  upload(){//觸發子組件的上傳方法  this.$refs.myUpload.submit();  },  removeFile(index){//移除某檔案  this.$refs.myUpload.remove(index);  },  uploadProgress(index, progress){//上傳進度,上傳時會不斷被觸發,需要進度指示時會很有用  const{ percent } = progress;  console.log(index, percent);  },  uploadFailed(index, err){//某檔案上傳失敗會執行,index代表列表中第index個檔案  console.log(index, err);  },  onFinished(result){//所有檔案上傳完畢後(無論成敗)執行,result: { success: 成功數目, failed: 失敗數目 }  console.log(result);  } }, components: {  myUpload }}</script>

父組件處理與業務有關的邏輯,我特意加入索引參數,便於介面展示上傳結果的時候能夠直接操作第幾個值,並不是所有方法都必須的,視需求使用。

子組件

<template><div> <input style="display:none" @change="addFile" :multiple="multiple" type="file" :name="name" id="my-upload"/></div></template>

上傳檔案,html部分就這麼一對兒標籤,不喜歡複雜囉嗦

<script>export default { name: 'my-upload', props: { name: String, action: {  type: String,  required: true }, fileList: {  type: Array,  default: [] }, data: Object, multiple: Boolean, limit: Number, onChange: Function, onBefore: Function, onProgress: Function, onSuccess: Function, onFailed: Function, onFinished: Function }, methods: {}//下文主要是methods的介紹,此處先省略}</script>

這裡定義了父組件向子組件需要傳遞的屬性值,注意,這裡把方法也當做了屬性傳遞,都是可以的。

自己寫的組件,沒有像流行架構發布的那樣完備和全面,另外針對開頭提到的綁定file-list就不能上傳了的問題(更可能是我的姿勢不對),本人也想極力解決掉自身遇到的這個問題,所以希望能對檔案清單有絕對的控制權,除了action,把file-list也作為父組件必須要傳遞的屬性。(屬性名稱父組件使用“-”串連,對應子組件prop中的駝峰命名)

三、主要的上傳功能

methods: {  addFile, remove, submit, checkIfCanUpload}

methods內一共4個方法,添加檔案、移除檔案、提交、檢測(上傳之前的檢驗),下面一一講述:

1.添加檔案

addFile({target: {files}}){//input標籤觸發onchange事件時,將檔案加入待上傳列表 for(let i = 0, l = files.length; i < l; i++){ files[i].url = URL.createObjectURL(files[i]);//建立blob地址,不然圖片怎麼展示? files[i].status = 'ready';//開始想給檔案一個欄位表示上傳進行的步驟的,後面好像也沒去用...... } let fileList = [...this.fileList]; if(this.multiple){//多選時,檔案全部壓如列表末尾 fileList = [...fileList, ...files]; let l = fileList.length; let limit = this.limit; if(limit && typeof limit === "number" && Math.ceil(limit) > 0 && l > limit){//有數目限制時,取後面limit個檔案  limit = Math.ceil(limit);//  limit = limit > 10 ? 10 : limit;  fileList = fileList.slice(l - limit); } }else{//單選時,只取最後一個檔案。注意這裡沒寫成fileList = files;是因為files本身就有多個元素(比如選擇檔案時一下子框了一堆)時,也只要一個 fileList = [files[0]]; } this.onChange(fileList);//調用父組件方法,將列表緩衝到上一級data中的fileList屬性 },

2.移除檔案

這個簡單,有時候在父組件叉掉某檔案的時候,傳一個index即可。

remove(index){ let fileList = [...this.fileList]; if(fileList.length){ fileList.splice(index, 1); this.onChange(fileList); }},

3.提交上傳

這裡使用了兩種方式,fetch和原生方式,由於fetch不支援擷取上傳的進度,如果不需要進度條或者自己類比進度或者XMLHttpRequest對象不存在的時候,使用fetch請求上傳邏輯會更簡單一些

submit(){ if(this.checkIfCanUpload()){ if(this.onProgress && typeof XMLHttpRequest !== 'undefined')  this.xhrSubmit(); else  this.fetchSubmit(); }},

4.基於上傳的兩套邏輯,這裡封裝了兩個方法xhrSubmit和fetchSubmit

fetchSubmit

fetchSubmit(){ let keys = Object.keys(this.data), values = Object.values(this.data), action = this.action; const promises = this.fileList.map(each => { each.status = "uploading"; let data = new FormData(); data.append(this.name || 'file', each); keys.forEach((one, index) => data.append(one, values[index])); return fetch(action, {  method: 'POST',  headers: {   "Content-Type" : "application/x-www-form-urlencoded"  },  body: data }).then(res => res.text()).then(res => JSON.parse(res));//這裡res.text()是根據傳回值類型使用的,應該視情況而定 }); Promise.all(promises).then(resArray => {//多線程同時開始,如果並發數有限制,可以使用同步的方式一個一個傳,這裡不再贅述。 let success = 0, failed = 0; resArray.forEach((res, index) => {  if(res.code == 1){  success++;         //統計上傳成功的個數,由索引可以知道哪些成功了  this.onSuccess(index, res);  }else if(res.code == 520){   //約定失敗的傳回值是520  failed++;         //統計上傳失敗的個數,由索引可以知道哪些失敗了  this.onFailed(index, res);  } }); return { success, failed };   //上傳結束,將結果傳遞到下文 }).then(this.onFinished);      //把上傳總結果返回},

xhrSubmit

xhrSubmit(){  const _this = this; let options = this.fileList.map((rawFile, index) => ({ file: rawFile, data: _this.data,    filename: _this.name || "file",    action: _this.action,    onProgress(e){     _this.onProgress(index, e);//閉包,將index存住    },    onSuccess(res){     _this.onSuccess(index, res);    },    onError(err){     _this.onFailed(index, err);    }  })); let l = this.fileList.length; let send = async options => { for(let i = 0; i < l; i++){  await _this.sendRequest(options[i]);//這裡用了個非同步方法呼叫,按次序執行this.sendRequest方法,參數為檔案清單封裝的每個對象,this.sendRequest下面緊接著介紹 } }; send(options);},

這裡借鑒了element-ui的上傳源碼

sendRequest(option){ const _this = this;  upload(option); function getError(action, option, xhr) {  var msg = void 0;  if (xhr.response) {   msg = xhr.status + ' ' + (xhr.response.error || xhr.response);  } else if (xhr.responseText) {   msg = xhr.status + ' ' + xhr.responseText;  } else {   msg = 'fail to post ' + action + ' ' + xhr.status;  }  var err = new Error(msg);  err.status = xhr.status;  err.method = 'post';  err.url = action;  return err; } function getBody(xhr) {  var text = xhr.responseText || xhr.response;  if (!text) {   return text;  }  try {   return JSON.parse(text);  } catch (e) {   return text;  } } function upload(option) {  if (typeof XMLHttpRequest === 'undefined') {   return;  }  var xhr = new XMLHttpRequest();  var action = option.action;  if (xhr.upload) {   xhr.upload.onprogress = function progress(e) {    if (e.total > 0) {     e.percent = e.loaded / e.total * 100;    }    option.onProgress(e);   };  }  var formData = new FormData();  if (option.data) {   Object.keys(option.data).map(function (key) {    formData.append(key, option.data[key]);   });  }  formData.append(option.filename, option.file);  xhr.onerror = function error(e) {   option.onError(e);  };  xhr.onload = function onload() {   if (xhr.status < 200 || xhr.status >= 300) {    return option.onError(getError(action, option, xhr));   }   option.onSuccess(getBody(xhr));  };  xhr.open('post', action, true);  if (option.withCredentials && 'withCredentials' in xhr) {   xhr.withCredentials = true;  }  var headers = option.headers || {};  for (var item in headers) {   if (headers.hasOwnProperty(item) && headers[item] !== null) {    xhr.setRequestHeader(item, headers[item]);   }  }  xhr.send(formData);  return xhr; }}

最後把請求前的校正加上

checkIfCanUpload(){ return this.fileList.length ? (this.onBefore && this.onBefore() || !this.onBefore) : false;},

如果父組件定義了onBefore方法且返回了false,或者檔案清單為空白,請求就不會發送。

代碼部分完了,使用時只要有了on-progress屬性並且XMLHttpRequest對象可訪問,就會使用原生方式發送請求,否則就用fetch發送請求(不展示進度)。

以上就是本文的全部內容,希望對大家的學習有所協助,也希望大家多多支援幫客之家。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.