Maison  >  Article  >  interface Web  >  Vue encapsule un exemple de composant de fichier de téléchargement simple et léger

Vue encapsule un exemple de composant de fichier de téléchargement simple et léger

亚连
亚连original
2018-05-26 16:46:171418parcourir

Cet article présente principalement un exemple de Vue encapsulant un composant de fichier de téléchargement simple et léger. L'éditeur pense que c'est plutôt bon, je vais donc le partager avec vous maintenant et le donner comme référence. Suivons l'éditeur pour y jeter un œil

1. Quelques problèmes rencontrés auparavant

Il existe de nombreuses exigences pour le téléchargement de fichiers dans les projets. processus de mise en œuvre utilisant le cadre d'interface utilisateur existant, 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é avant

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 &#39;./components/my-upload&#39;
export default {
 name: &#39;test&#39;,
 data(){
  return {
  fileList: [],//上传文件列表,无论单选还是支持多选,文件都以列表格式保存
  param: {param1: &#39;&#39;, param2: &#39;&#39; },//携带参数列表
  }
 },
 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. J'ai spécialement ajouté des paramètres d'index afin que l'interface puisse directement exploiter la valeur lors de l'affichage des résultats du téléchargement. Toutes les méthodes ne sont pas obligatoires et peuvent être utilisées 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échargez le fichier, la partie html n'est qu'une paire de balises, je n'aime pas la complexité

<script>
export default {
 name: &#39;my-upload&#39;,
 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>

Cela définit les valeurs d'attribut qui doivent être transmises du composant parent au composant enfant. Notez que les méthodes sont également transmises en tant qu'attributs ici. , ce qui est acceptable.

Les composants que j'ai écrits ne sont pas aussi complets et exhaustifs que ceux publiés par les frameworks populaires. De plus, compte tenu du problème selon lequel la liste de fichiers liée mentionnée au début 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), qui sont décrits un par un ci-dessous :

1. Ajouter des fichiers

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 la progression téléchargée. , si vous n'avez pas besoin de barre de progression ou simulez vous-même la progression ou que l'objet XMLHttpRequest n'existe pas, il sera plus simple d'utiliser fetch pour demander la logique d'upload

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

4. Basé sur les deux ensembles de logique de téléchargement, deux méthodes xhrSubmit et 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 || &#39;file&#39;, each);
 keys.forEach((one, index) => data.append(one, values[index]));
 return fetch(action, {
  method: &#39;POST&#39;,
  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);
},

Ici, nous empruntons 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(&#39;post&#39;, action, true);

  if (option.withCredentials && &#39;withCredentials&#39; 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, mettez la requête La vérification précédente plus

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 le la liste des fichiers est vide, la demande ne sera pas envoyée.


La partie code est terminée Lors de son utilisation, tant que l'attribut on-progress est présent et que l'objet XMLHttpRequest est accessible, la requête sera envoyée de manière native. sera envoyé en utilisant fetch (la progression ne sera pas affichée).

J'ai compilé ce qui précède pour vous, j'espère que cela vous sera utile à l'avenir.

Articles connexes :

Exemple de code de téléchargement de fichiers asynchrones de formulaire Ajax

Fonctionnement en cascade du menu déroulant

Ajax réalise une cascade à trois niveaux de provinces et de municipalités

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