Vue で軽量アップロード ファイル コンポーネントをカプセル化する手順の詳細な説明

php中世界最好的语言
リリース: 2018-05-09 10:20:11
オリジナル
2153 人が閲覧しました

今回は、Vue で軽量アップロード ファイル コンポーネントをカプセル化する手順について詳しく説明します。Vue で軽量アップロード ファイル コンポーネントをカプセル化する際の 注意事項 は何ですか。実際のケースを見てみましょう。

1. 以前に遭遇したいくつかの問題

既存の UI フレームワークを使用して実装するプロセスでは、何らかの理由で説明できないバグが常に発生します。たとえば、特定のアップロード コンポーネントを使用する場合、明確に (:multiple="false") とマークされていますが、実際には、複数のファイルを選択することが可能であり、たとえば次の場合に限り、アップロード時に複数のファイルが送信されます。 (:file-list="fileList" が追加されています) ") 属性でアップロード リストを手動で制御したい場合、アップロード イベント this.refs.[upload (component ref)].submit() が機能せず、実行できません。伝わった。要するに、私はそれを実装する方法を理解するのが面倒なので、関数を使用していますが、それを使用することに固執すると、プロジェクトに多くの不要なロジックとスタイルコードが追加されます。 ...

私は以前プロジェクトに Vue を使用していました。ビュー フレームワークには、チーム内の補足として element-ui、zp-ui、および iview が含まれています。フレームワークは使いやすいですが、自分のプロジェクトでは使用できないことがよくあります。特に、デザイン担当者が作成したインターフェイスは、既存のフレームワークとは大きく異なり、ソースコードを変更すると非効率で、未知のバグが発生しやすいです。そのため、時間をかけてこのアップロード コンポーネントをカプセル化します。

2. コードと紹介

親コンポーネント

<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>
ログイン後にコピー
親コンポーネントは、アップロード結果を表示するときにインターフェイスが値を直接操作できるようにするために、特別にインデックスパラメータを追加しました。すべてのメソッドは必須であり、必要に応じて使用する必要があります。

サブコンポーネント

<template>
<p>
 <input style="display:none" @change="addFile" :multiple="multiple" type="file" :name="name" id="my-upload"/>
</p>
</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>
ログイン後にコピー
これは、親コンポーネントから子コンポーネントに渡す必要がある属性値を定義します。ここではメソッドも属性として渡されることに注意してください。すべてが可能です。

私が書いたコンポーネントは、一般的なフレームワークによってリリースされたものほど完全かつ包括的ではありません。また、冒頭で述べた、バインドされたファイルリストをアップロードできないという問題を解決するために最善を尽くしたいと考えています。私の姿勢が間違っています) 私が遭遇したこの問題を取り除くために、アクションに加えて、ファイルリストを親によって渡される必要がある属性としても使用したいと考えています。成分。 (親コンポーネントの属性名は「-」で接続されており、サブコンポーネントpropのキャメルケース命名に対応しています)

3. メインアップロード関数

methods: {
  addFile, remove, submit, checkIfCanUpload
}
ログイン後にコピー
には合計4つのメソッドがありますメソッド、ファイルの追加、ファイルの削除、送信、検出 (アップロード前の検査) について、以下で 1 つずつ説明します。

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属性
 },
ログイン後にコピー
2. ファイルの削除

これは、親コンポーネントの場合に簡単です。ファイルをフォークする場合は、インデックスを渡すだけです。

remove(index){
 let fileList = [...this.fileList];
 if(fileList.length){
 fileList.splice(index, 1);
 this.onChange(fileList);
 }
},
ログイン後にコピー
3. アップロードを送信します

ここでは、フェッチとネイティブ メソッドの 2 つのメソッドが使用されます。フェッチはアップロードの進行状況の取得をサポートしていないため、

進行状況バーを必要としない場合、または XMLHttpRequest オブジェクトが必要ない場合は、フェッチ リクエストのアップロード ロジックがよりシンプルになります

submit(){
 if(this.checkIfCanUpload()){
 if(this.onProgress && typeof XMLHttpRequest !== 'undefined')
  this.xhrSubmit();
 else
  this.fetchSubmit();
 }
},
ログイン後にコピー
4. 2 つのアップロード ロジックに基づいて、2 つのメソッド 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 + &#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;
 }
}
ログイン後にコピー
最後にリクエストの前に検証を追加します

checkIfCanUpload(){
 return this.fileList.length ? (this.onBefore && this.onBefore() || !this.onBefore) : false;
},
ログイン後にコピー
親コンポーネントがonBeforeメソッドを定義していてfalseを返した場合、またはファイルリストが空の場合、リクエストは送信されません。

これを使用する場合、on-progress 属性が存在し、XMLHttpRequest オブジェクトにアクセスできる限り、リクエストはネイティブ メソッドを使用して送信されます。それ以外の場合、リクエストはフェッチを使用して送信されます。進行状況は表示されません)。

この記事の事例を読んだ後は、この方法を習得したと思います。さらに興味深い情報については、php 中国語 Web サイトの他の関連記事に注目してください。

推奨読書:

jqueryのfilter()メソッドの使用例の説明

ファジーボックスでWebUploaderを使用する手順の詳細な説明

以上がVue で軽量アップロード ファイル コンポーネントをカプセル化する手順の詳細な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート