Maison >interface Web >js tutoriel >vue encapsule un composant léger de téléchargement de fichiers

vue encapsule un composant léger de téléchargement de fichiers

php中世界最好的语言
php中世界最好的语言original
2018-06-14 13:54:502115parcourir

Cette fois, je vous propose un composant de téléchargement de fichier léger encapsulé dans vue. Quelles sont les précautions à prendre pour encapsuler un composant de téléchargement de fichier léger dans vue ? Ce qui suit est un cas pratique, jetons un coup d'oeil.

1. Quelques problèmes rencontrés auparavant

Il existe de nombreuses exigences pour le téléchargement de fichiers dans le projet, qui sont implémentées à l'aide du cadre d'interface utilisateur existant pendant le processus, il y aura toujours des bugs inexplicables pour une raison inconnue. Par exemple, lors de l'utilisation d'un certain composant de téléchargement, il est clairement marqué (:multiple="false"), mais en fait, il est toujours possible de sélectionner plusieurs fichiers, et plusieurs fichiers sont toujours envoyés lors du téléchargement, par exemple, à condition que ; (:file-list="fileList" est ajouté) "), lorsque j'espère contrôler manuellement la liste de téléchargement, l'événement de téléchargement this.refs.[upload (component ref)].submit() ne fonctionnera pas et ne pourra pas être transmis. Bref, je suis trop paresseux pour voir comment il est implémenté. J'utilise des fonctions et l'interface elle-même doit encore être réécrite. Si j'insiste pour l'utiliser, cela ajoutera beaucoup de logique et de code de style inutiles au projet. ...

Utilisé auparavant

Les frameworks de vue utilisés par Vue pour les projets incluent element-ui, zp-ui en complément au sein de l'équipe et iview. Le framework est facile à utiliser, mais vous ne pouvez souvent pas l'utiliser pour vos propres projets. En particulier, l'interface créée par notre designer est très différente du framework existant. Changer le code source est inefficace et peut facilement conduire à des bugs inconnus. donc je prends le temps de le faire moi-même. Encapsule ce composant de téléchargement.

2. Code et introduction

Composant parent

<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>

Le composant parent gère la logique métier. Le paramètre index est spécialement ajouté pour que l'interface puisse exploiter directement la valeur lors de l'affichage du résultat du téléchargement. Il n'est pas requis pour toutes les méthodes et peut être utilisé selon les besoins.

Sous-composant

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

Télécharger des fichiers, la partie html n'est qu'une paire de balises, je n'aime pas la complexité

<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>

Ici, nous définissons du composant parent au composant enfant La valeur d'attribut qui doit être transmise. Notez que la méthode est également transmise en tant qu'attribut ici, ce qui est acceptable.

Les composants que j'ai écrits ne sont pas aussi complets et complets que ceux publiés par les frameworks populaires. De plus, compte tenu du problème mentionné au début, la liste de fichiers liée ne peut pas être téléchargée (plus probablement à cause de ma posture). est faux), je veux également faire de mon mieux pour résoudre ce problème que j'ai rencontré, j'espère donc avoir un contrôle absolu sur la liste de fichiers. En plus de l'action, la liste de fichiers est également utilisée comme attribut qui doit être transmis. le composant parent. (Le nom d'attribut du composant parent est connecté à "-", correspondant au nom du cas chameau dans l'accessoire du composant enfant)

3. Fonction de téléchargement principale

methods: {
  addFile, remove, submit, checkIfCanUpload
}

Il existe un total de 4 méthodes dans les méthodes, notamment l'ajout de fichiers, la suppression de fichiers, la soumission et le test (test avant le téléchargement). Elles sont décrites ci-dessous :

1. files

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属性
 },

2. Supprimer des fichiers

C'est simple Parfois, lorsque le composant parent crée un fichier, transmettez simplement un index.

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

3. Soumettre le téléchargement

Deux méthodes sont utilisées ici, la récupération et la méthode native, car la récupération ne prend pas en charge l'obtention de la progression du téléchargement, si vous n'avez pas besoin de barre de progression ou de simulation. la progression vous-même ou Lorsque l'objet XMLHttpRequest n'existe pas, il sera plus simple d'utiliser fetch pour demander la logique de téléchargement

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

4 Sur la base des deux ensembles de logique de téléchargement, deux méthodes xhrSubmit et fetchSubmit sont. encapsulé ici

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);
},

Ceci est basé sur le code source de téléchargement d'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;
 }
}

Enfin , ajoutez la vérification avant la requête

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

Si le composant parent définit la méthode onBefore et renvoie false, ou si la liste des fichiers est vide, la requête ne sera pas envoyée.

Je pense que vous maîtrisez la méthode après avoir lu le cas dans cet article. Pour des informations plus intéressantes, veuillez prêter attention aux autres articles connexes sur le site Web chinois de php !

Lecture recommandée :

p5.js utilise la fonction draw() pour créer une spirale dorée

Comment l'utiliser dans les projets réels jquery filter()

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn