Permalink
Browse files

feat(module:upload): add directory support (#2164)

close #2167 
close #2154
  • Loading branch information...
cipchk authored and vthinkxie committed Sep 21, 2018
1 parent a00f004 commit 3ef8bcf2403a7484847a6c862edfa96bdbe65520
@@ -0,0 +1,14 @@
---
order: 10
title:
zh-CN: 自定义上传
en-US: Custom request
---
## zh-CN
使用 `nzCustomRequest` 改变上传行为。
## en-US
Use `nzCustomRequest` override for the default xhr behavior.
@@ -0,0 +1,80 @@
import { Component } from '@angular/core';
import { HttpRequest, HttpClient, HttpEventType, HttpEvent, HttpResponse } from '@angular/common/http';
import { NzMessageService, UploadXHRArgs } from 'ng-zorro-antd';
import { forkJoin } from 'rxjs';
@Component({
selector: 'nz-demo-upload-custom-request',
template: `
<nz-upload
nzAction="https://jsonplaceholder.typicode.com/posts/"
[nzCustomRequest]="customReq">
<button nz-button>
<i class="anticon anticon-upload"></i><span>Click to Upload</span>
</button>
</nz-upload>
`
})
export class NzDemoUploadCustomRequestComponent {
constructor(private http: HttpClient, private msg: NzMessageService) {}
customReq = (item: UploadXHRArgs) => {
// 构建一个 FormData 对象,用于存储文件或其他参数
const formData = new FormData();
// tslint:disable-next-line:no-any
formData.append('file', item.file as any);
formData.append('id', '1000');
const req = new HttpRequest('POST', item.action, formData, {
reportProgress : true,
withCredentials: true
});
// 始终返回一个 `Subscription` 对象,nz-upload 会在适当时机自动取消订阅
return this.http.request(req).subscribe((event: HttpEvent<{}>) => {
if (event.type === HttpEventType.UploadProgress) {
if (event.total > 0) {
// tslint:disable-next-line:no-any
(event as any).percent = event.loaded / event.total * 100;
}
// 处理上传进度条,必须指定 `percent` 属性来表示进度
item.onProgress(event, item.file);
} else if (event instanceof HttpResponse) {
// 处理成功
item.onSuccess(event.body, item.file, event);
}
}, (err) => {
// 处理失败
item.onError(err, item.file);
});
}
// 一个简单的分片上传
customBigReq = (item: UploadXHRArgs) => {
const size = item.file.size;
const chunkSize = parseInt((size / 3) + '', 10);
const maxChunk = Math.ceil(size / chunkSize);
const reqs = Array(maxChunk).fill(0).map((v: {}, index: number) => {
const start = index * chunkSize;
let end = start + chunkSize;
if (size - end < 0) {
end = size;
}
const formData = new FormData();
formData.append('file', item.file.slice(start, end));
formData.append('start', start.toString());
formData.append('end', end.toString());
formData.append('index', index.toString());
const req = new HttpRequest('POST', item.action, formData, {
withCredentials: true
});
return this.http.request(req);
});
return forkJoin(...reqs).subscribe(resules => {
// 处理成功
item.onSuccess({}, item.file, event);
}, (err) => {
// 处理失败
item.onError(err, item.file);
});
}
}
@@ -0,0 +1,14 @@
---
order: 6
title:
zh-CN: 文件夹上传
en-US: Upload directory
---
## zh-CN
支持上传一个文件夹里的所有文件。
## en-US
You can select and upload a whole directory.
@@ -0,0 +1,15 @@
import { Component } from '@angular/core';
@Component({
selector: 'nz-demo-upload-directory',
template: `
<nz-upload
nzAction="https://jsonplaceholder.typicode.com/posts/"
nzDirectory>
<button nz-button>
<i class="anticon anticon-upload"></i> Upload Directory
</button>
</nz-upload>
`
})
export class NzDemoUploadDirectoryComponent {}
@@ -1,5 +1,5 @@
---
order: 6
order: 8
title:
zh-CN: 图片列表样式
en-US: Pictures with list style
@@ -24,6 +24,7 @@ Uploading is the process of publishing information (web pages, text, pictures, v
| --- | --- | --- | --- |
| `[nzAccept]` | File types that can be accepted. See [input accept Attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-accept) | string | - |
| `[nzAction]` | Required. Uploading URL | string | - |
| `[nzDirectory]` | support upload whole directory ([caniuse](https://caniuse.com/#feat=input-file-directory)) | boolean | false |
| `[nzBeforeUpload]` | Hook function which will be executed before uploading. Uploading will be stopped with `false` or a Observable. **Warning:this function is not supported in IE9**. NOTICE: Muse be use `=>` to define the method. | (file, fileList) => `boolean|Observable` | - |
| `[nzCustomRequest]` | override for the default xhr behavior allowing for additional customization and ability to implement your own XMLHttpRequest. NOTICE: Muse be use `=>` to define the method. | `(item) => Subscription` | - |
| `[nzData]` | Uploading params or function which can return uploading params. NOTICE: Muse be use `=>` to define the method. | `Object|((file: UploadFile) => Object)` | - |
@@ -25,6 +25,7 @@ title: Upload
| --- | --- | --- | --- |
| `[nzAccept]` | 接受上传的文件类型, 详见 [input accept Attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-accept) | string | - |
| `[nzAction]` | 必选参数, 上传的地址 | string | - |
| `[nzDirectory]` | 支持上传文件夹([caniuse](https://caniuse.com/#feat=input-file-directory)) | boolean | false |
| `[nzBeforeUpload]` | 上传文件之前的钩子,参数为上传的文件,若返回 `false` 则停止上传。注意:**IE9** 不支持该方法;注意:务必使用 `=>` 定义处理方法。 | (file, fileList) => `boolean|Observable` | - |
| `[nzCustomRequest]` | 通过覆盖默认的上传行为,可以自定义自己的上传实现;注意:务必使用 `=>` 定义处理方法。 | `(item) => Subscription` | - |
| `[nzData]` | 上传所需参数或返回上传参数的方法;注意:务必使用 `=>` 定义处理方法。 | `Object|((file: UploadFile) => Object)` | - |
@@ -48,6 +48,7 @@ export interface ZipButtonOptions {
disabled?: boolean;
accept?: string | string[];
action?: string;
directory?: boolean;
beforeUpload?: (file: UploadFile, fileList: UploadFile[]) => boolean | Observable<any>;
customRequest?: (item: any) => Subscription;
data?: {} | ((file: UploadFile) => {});
@@ -1,3 +1,6 @@
<input type="file" #file (change)="onChange($event)"
[attr.accept]="options.accept" [multiple]="options.multiple" style="display: none;">
[attr.accept]="options.accept"
[attr.directory]="options.directory ? 'directory': null"
[attr.webkitdirectory]="options.directory ? 'webkitdirectory': null"
[multiple]="options.multiple" style="display: none;">
<ng-content></ng-content>
@@ -66,11 +66,15 @@ export class NzUploadBtnComponent implements OnInit, OnChanges, OnDestroy {
e.preventDefault();
return;
}
const files: File[] = Array.prototype.slice.call(e.dataTransfer.files).filter(
(file: File) => this.attrAccept(file, this.options.accept)
);
if (files.length) {
this.uploadFiles(files);
if (this.options.directory) {
this.traverseFileTree(e.dataTransfer.items);
} else {
const files: File[] = Array.prototype.slice.call(e.dataTransfer.files).filter(
(file: File) => this.attrAccept(file, this.options.accept)
);
if (files.length) {
this.uploadFiles(files);
}
}
e.preventDefault();
@@ -85,6 +89,31 @@ export class NzUploadBtnComponent implements OnInit, OnChanges, OnDestroy {
hie.value = '';
}
// tslint:disable-next-line:no-any
private traverseFileTree(files: any): void {
// tslint:disable-next-line:no-any
const _traverseFileTree = (item: any, path: string) => {
if (item.isFile) {
item.file((file) => {
if (this.attrAccept(file, this.options.accept)) {
this.uploadFiles([file]);
}
});
} else if (item.isDirectory) {
const dirReader = item.createReader();
dirReader.readEntries((entries) => {
for (const entrieItem of entries) {
_traverseFileTree(entrieItem, `${path}${item.name}/`);
}
});
}
};
for (const file of files) {
_traverseFileTree(file.webkitGetAsEntry(), '');
}
}
private attrAccept(file: File, acceptedFiles: string | string[]): boolean {
if (file && acceptedFiles) {
const acceptedFilesArray = Array.isArray(acceptedFiles) ? acceptedFiles : acceptedFiles.split(',');
@@ -133,7 +162,7 @@ export class NzUploadBtnComponent implements OnInit, OnChanges, OnDestroy {
if (processedFileType === '[object File]' || processedFileType === '[object Blob]') {
this.attachUid(processedFile);
this.post(processedFile);
} else {
} else if (typeof processedFile === 'boolean' && processedFile !== false) {
this.post(file);
}
});
@@ -16,7 +16,7 @@ import {
import { of, Observable, Subscription } from 'rxjs';
import { filter } from 'rxjs/operators';
import { toBoolean, toNumber } from '../core/util/convert';
import { toBoolean, toNumber, InputBoolean } from '../core/util/convert';
import { NzI18nService } from '../i18n/nz-i18n.service';
import {
@@ -39,8 +39,6 @@ import { NzUploadBtnComponent } from './nz-upload-btn.component';
export class NzUploadComponent implements OnInit, OnChanges, OnDestroy {
private i18n$: Subscription;
locale: any = {};
private inited = false;
private progressTimer: any;
@ViewChild('upload') upload: NzUploadBtnComponent;
// region: fields
@@ -70,6 +68,7 @@ export class NzUploadComponent implements OnInit, OnChanges, OnDestroy {
@Input() nzFileType: string;
@Input() nzAccept: string | string[];
@Input() nzAction: string;
@Input() @InputBoolean() nzDirectory: boolean = false;
@Input() nzBeforeUpload: (file: UploadFile, fileList: UploadFile[]) => boolean | Observable<any>;
@Input() nzCustomRequest: (item: any) => Subscription;
@Input() nzData: {} | ((file: UploadFile) => {});
@@ -176,6 +175,7 @@ export class NzUploadComponent implements OnInit, OnChanges, OnDestroy {
disabled : this.nzDisabled,
accept : this.nzAccept,
action : this.nzAction,
directory : this.nzDirectory,
beforeUpload : this.nzBeforeUpload,
customRequest : this.nzCustomRequest,
data : this.nzData,
@@ -240,7 +240,7 @@ export class NzUploadComponent implements OnInit, OnChanges, OnDestroy {
file.thumbUrl = '';
const reader = new FileReader();
reader.onloadend = () => file.thumbUrl = reader.result;
reader.onloadend = () => file.thumbUrl = reader.result as string;
reader.readAsDataURL(file.originFileObj);
}
@@ -361,7 +361,6 @@ export class NzUploadComponent implements OnInit, OnChanges, OnDestroy {
// endregion
ngOnInit(): void {
this.inited = true;
this.i18n$ = this.i18n.localeChange.subscribe(() => {
this.locale = this.i18n.getLocaleData('Upload');
this.cd.detectChanges();
Oops, something went wrong.

0 comments on commit 3ef8bcf

Please sign in to comment.