Rumah hujung hadapan web tutorial js vue封装轻量级文件上传组件

vue封装轻量级文件上传组件

Jun 14, 2018 pm 01:54 PM
vue vue.js

这次给大家带来vue封装轻量级文件上传组件,vue封装轻量级文件上传组件的注意事项有哪些,下面就是实战案例,一起来看一下。

一、之前遇到的一些问题

项目中多出有上传文件的需求,使用现有的UI框架实现的过程中,不知道什么原因,总会有一些莫名其妙的bug。比如用某上传组件,明明注明(:multiple="false"),可实际上还是能多选,上传的时候依然发送了多个文件;又比如只要加上了(:file-list="fileList")属性,希望能手动控制上传列表的时候,上传事件this.refs.[upload(组件ref)].submit()就不起作用了,传不了。总之,懒得再看它怎么实现了,我用的是功能,界面本身还是要重写的,如果坚持用也会使项目多很多不必要的逻辑、样式代码……

之前用Vue做项目用的视图框架有element-ui,团队内部作为补充的zp-ui,以及iview。框架是好用,但是针对自己的项目往往不能全部拿来用,尤其是我们的设计妹子出的界面与现有框架差异很大,改源码效率低又容易导致未知的bug,于是自己就抽时间封装了这个上传组件。

二、代码与介绍

父组件

<template>
 <p 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>
 </p>
</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>
Salin selepas log masuk

父组件处理与业务有关的逻辑,我特意加入索引参数,便于界面展示上传结果的时候能够直接操作第几个值,并不是所有方法都必须的,视需求使用。

子组件

<template>
<p>
 <input style="display:none" @change="addFile" :multiple="multiple" type="file" :name="name" id="my-upload"/>
</p>
</template>
Salin selepas log masuk

上传文件,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>
Salin selepas log masuk

这里定义了父组件向子组件需要传递的属性值,注意,这里把方法也当做了属性传递,都是可以的。

自己写的组件,没有像流行框架发布的那样完备和全面,另外针对开头提到的绑定file-list就不能上传了的问题(更可能是我的姿势不对),本人也想极力解决掉自身遇到的这个问题,所以希望能对文件列表有绝对的控制权,除了action,把file-list也作为父组件必须要传递的属性。(属性名父组件使用“-”连接,对应子组件prop中的驼峰命名)

三、主要的上传功能

methods: {
  addFile, remove, submit, checkIfCanUpload
}
Salin selepas log masuk

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 = &#39;ready&#39;;//开始想给文件一个字段表示上传进行的步骤的,后面好像也没去用......
 }
 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属性
 },
Salin selepas log masuk

2.移除文件

这个简单,有时候在父组件叉掉某文件的时候,传一个index即可。

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

3.提交上传

这里使用了两种方式,fetch和原生方式,由于fetch不支持获取上传的进度,如果不需要进度条或者自己模拟进度或者XMLHttpRequest对象不存在的时候,使用fetch请求上传逻辑会更简单一些

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

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);      //把上传总结果返回
},
Salin selepas log masuk

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);
},
Salin selepas log masuk

这里借鉴了element-ui的上传源码

sendRequest(option){
 const _this = this;
  upload(option);
 function getError(action, option, xhr) {
  var msg = void 0;
  if (xhr.response) {
   msg = xhr.status + &#39; &#39; + (xhr.response.error || xhr.response);
  } else if (xhr.responseText) {
   msg = xhr.status + &#39; &#39; + xhr.responseText;
  } else {
   msg = &#39;fail to post &#39; + action + &#39; &#39; + xhr.status;
  }
  var err = new Error(msg);
  err.status = xhr.status;
  err.method = &#39;post&#39;;
  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 === &#39;undefined&#39;) {
   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;
 }
}
Salin selepas log masuk

最后把请求前的校验加上

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

如果父组件定义了onBefore方法且返回了false,或者文件列表为空,请求就不会发送。

相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

推荐阅读:

p5.js使用draw()函数做出黄金螺旋

实战项目中如何使用jquery filter()

Atas ialah kandungan terperinci vue封装轻量级文件上传组件. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn

Alat AI Hot

Undresser.AI Undress

Undresser.AI Undress

Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover

AI Clothes Remover

Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool

Undress AI Tool

Gambar buka pakaian secara percuma

Clothoff.io

Clothoff.io

Penyingkiran pakaian AI

Video Face Swap

Video Face Swap

Tukar muka dalam mana-mana video dengan mudah menggunakan alat tukar muka AI percuma kami!

Alat panas

Notepad++7.3.1

Notepad++7.3.1

Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina

SublimeText3 versi Cina

Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1

Hantar Studio 13.0.1

Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6

Dreamweaver CS6

Alat pembangunan web visual

SublimeText3 versi Mac

SublimeText3 versi Mac

Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Vue.js vs React: Pertimbangan khusus projek Vue.js vs React: Pertimbangan khusus projek Apr 09, 2025 am 12:01 AM

Vue.js sesuai untuk projek kecil dan sederhana dan lelaran yang cepat, sementara React sesuai untuk aplikasi besar dan kompleks. 1) Vue.js mudah digunakan dan sesuai untuk situasi di mana pasukan tidak mencukupi atau skala projek kecil. 2) React mempunyai ekosistem yang lebih kaya dan sesuai untuk projek dengan prestasi tinggi dan keperluan fungsional yang kompleks.

Cara menggunakan bootstrap di vue Cara menggunakan bootstrap di vue Apr 07, 2025 pm 11:33 PM

Menggunakan bootstrap dalam vue.js dibahagikan kepada lima langkah: Pasang bootstrap. Import bootstrap di main.js. Gunakan komponen bootstrap secara langsung dalam templat. Pilihan: Gaya tersuai. Pilihan: Gunakan pemalam.

Cara menambah fungsi ke butang untuk vue Cara menambah fungsi ke butang untuk vue Apr 08, 2025 am 08:51 AM

Anda boleh menambah fungsi ke butang VUE dengan mengikat butang dalam templat HTML ke kaedah. Tentukan kaedah dan tulis logik fungsi dalam contoh Vue.

Cara Menggunakan Watch di Vue Cara Menggunakan Watch di Vue Apr 07, 2025 pm 11:36 PM

Pilihan Watch di Vue.js membolehkan pemaju mendengar perubahan dalam data tertentu. Apabila data berubah, tontonkan mencetuskan fungsi panggil balik untuk melakukan paparan kemas kini atau tugas lain. Pilihan konfigurasinya termasuk segera, yang menentukan sama ada untuk melaksanakan panggilan balik dengan serta -merta, dan mendalam, yang menentukan sama ada untuk mendengarkan secara rekursif terhadap objek atau tatasusunan.

Apakah yang dimaksudkan dengan pembangunan Vue Multi-Page? Apakah yang dimaksudkan dengan pembangunan Vue Multi-Page? Apr 07, 2025 pm 11:57 PM

Pembangunan pelbagai halaman Vue adalah cara untuk membina aplikasi menggunakan rangka kerja VUE.JS, di mana permohonan dibahagikan kepada halaman berasingan: Penyelenggaraan kod: Memisahkan aplikasi ke dalam beberapa halaman boleh menjadikan kod lebih mudah untuk dikendalikan dan diselenggarakan. Modularity: Setiap halaman boleh digunakan sebagai modul yang berasingan untuk penggunaan semula dan penggantian mudah. Routing mudah: Navigasi antara halaman boleh diuruskan melalui konfigurasi penghalaan mudah. Pengoptimuman SEO: Setiap halaman mempunyai URL sendiri, yang membantu SEO.

Cara kembali ke halaman sebelumnya oleh Vue Cara kembali ke halaman sebelumnya oleh Vue Apr 07, 2025 pm 11:30 PM

Vue.js mempunyai empat kaedah untuk kembali ke halaman sebelumnya: $ router.go (-1) $ router.back () menggunakan & lt; router-link to = & quot;/& quot; Komponen Window.History.Back (), dan pemilihan kaedah bergantung pada tempat kejadian.

Cara Menggunakan Vue Traversal Cara Menggunakan Vue Traversal Apr 07, 2025 pm 11:48 PM

Terdapat tiga kaedah umum untuk vue.js untuk melintasi tatasusunan dan objek: Arahan V-untuk digunakan untuk melintasi setiap elemen dan membuat templat; Arahan V-mengikat boleh digunakan dengan V-untuk menetapkan nilai atribut secara dinamik untuk setiap elemen; dan kaedah .map boleh menukar elemen array ke dalam tatasusunan baru.

Cara melompat tag ke vue Cara melompat tag ke vue Apr 08, 2025 am 09:24 AM

Kaedah untuk melaksanakan lompatan tag dalam Vue termasuk: menggunakan tag dalam templat HTML untuk menentukan atribut HREF. Gunakan komponen router-link routing VUE. Gunakan ini. $ Router.push () kaedah dalam JavaScript. Parameter boleh dilalui melalui parameter pertanyaan dan laluan dikonfigurasikan dalam pilihan penghala untuk lompatan dinamik.

See all articles