文章目录
  1. 1. 图片上传控件的制作
    1. 1.1. type=”file”的input
    2. 1.2. change事件
    3. 1.3. FileReader
    4. 1.4. 添加校验
    5. 1.5. 处理错误信息
    6. 1.6. 添加设置说明
    7. 1.7. 使用[(ngModel)]绑定
  2. 2. 结束语

因为项目原因又玩上了Angular2(v4.0+),《玩转Angular2》系列用于探索一些灵活或者新的用法。
本文紧跟上节radio和checkbox表单控件的创建,讲述图片上传表单控件的创建过程。

图片上传控件的制作


type=”file”的input

要讲图片上传,事情总要从下面的代码开始:

1
<input type="file" accept="image/*" multiple="multiple">

这里我们允许上传多张,这样的话我们就需要通过数组的方式双向绑定传值。

跟之前的checkbox-group一样,我们需要自定义ngModel的绑定过程,这样我们的原型便是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import {Component, Input, OnInit, ElementRef} from '@angular/core';
import {customInputAccessor} from '../../class/custom-input.class';
@Component({
selector: 'upload-image',
templateUrl: './upload-image.component.html',
providers: [customInputAccessor(UploadImageComponent)]
})
export class UploadImageComponent {
@Input() disabled: boolean = false;
@Input() required: boolean = false;
private model: string[] = []; // 控件的值
private onChange: (_: any) => void;
private onTouched: () => void;
onBlur() {
this.onTouched();
}
writeValue(value: string[]): void {
if (value && value.length) {
this.model = value;
}
}
registerOnChange(fn: (_: any) => {}): void {
this.onChange = fn;
}
registerOnTouched(fn: () => {}): void {
this.onTouched = fn;
}
}

如果说这里看不懂,那麻烦大家补一下前两节的内容:
《玩转Angular2(7)–创建动态表单》
《玩转Angular2(8)–表单的radio和checkbox》

change事件

这里我们首先要把原本丑丑的file input藏起来,这个通过样式调整就可以啦:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.btn-file {
position: relative;
overflow: hidden;
}
.btn-file input[type="file"] {
position: absolute;
top: 0;
right: 0;
min-width: 100%;
min-height: 100%;
font-size: 100px;
text-align: right;
filter: alpha(opacity=0);
opacity: 0;
outline: none;
background: #fff;
cursor: inherit;
display: block;
}

然后我们的html调整一下:

1
2
3
4
5
<span class="btn btn-info btn-file">
<i class="fa fa-upload fa-fw"></i>
{{btnName}}
<input type="file" accept="image/*" multiple="multiple" (change)="upLoad()">
</span>

是的,我们添加了按钮的名字,这里默认设置为”上传图片就好了”,后面我们再一起看逻辑代码。

这样我们点击呈现的按钮的同时,也就是点击了<input type="file" />,当我们选择了不同的内容之后,change事件便会触发。

这里我们可以猜想upload()回调会执行些什么:

  1. 多个图片,遍历数组。
  2. 获取每个图片信息,包括预览url、名字、大小等等。
1
2
3
4
5
6
7
8
9
10
// 拿到input,然后获取选中文件
const input = $(this.el.nativeElement).find('input')[0];
const files = input.files;
if (files) {
// 遍历文件
Object.keys(files).forEach(index => {
const file = files[index];
// 获取每个图片信息
});
}

FileReader

好久没处理上传事件了,我们先来回顾一下FileReader

FileReader对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用FileBlob对象指定要读取的文件或数据。

其中File对象可以是来自用户在一个<input>元素上选择文件后返回的FileList对象,也可以来自拖放操作生成的DataTransfer对象,还可以是来自在一个HTMLCanvasElement上执行mozGetAsFile()方法后返回结果。

  • 方法

    • abort(): 中止该读取操作
    • readAsArrayBuffer(in Blob blob)/readAsBinaryString(in Blob blob)/readAsDataURL(in Blob blob)/readAsText
      • 开始读取指定的Blob对象或File对象中的内容。当读取操作完成时,readyState属性的值会成为DONE,如果设置了onloadend事件处理程序,则调用
      • 四者区别在于返回的result
      • readAsDataURL()result属性中将包含一个data: URL格式的字符串以表示所读取文件的内容
  • 事件处理程序

    • onabort: 当读取操作被中止时调用
    • onerror: 当读取操作发生错误时调用
    • onload: 当读取操作成功完成时调用
    • onloadend: 当读取操作完成时调用,不管是成功还是失败。在onload或者onerror之后调用
    • onloadstart: 当读取操作将要开始之前调用
    • onprogress: 在读取数据过程中周期性调用

说这么多,其实我们只需要它的一个onload事件回调,以及一个readAsDataURL()方法:

1
2
3
4
5
6
7
const reader: FileReader = new FileReader();
// 设置文件读取完毕事件
reader.onload = (e: ProgressEvent) => {
const url = reader.result; // 获取url
const name = file.name; // 获取文件名字
};
reader.readAsDataURL(file); // 获取图片的data: URL

添加校验

一般来说,我们常用的校验有:大小、宽高、类型。
这里我们使用limit输入:@Input() limit: ILimit = {};

而我们的limit输入为以下的样子:

1
2
3
4
5
6
interface ILimit {
width?: number; // 宽
height?: number; // 高
size?: number; // 大小
type?: string; // 类型
}

其中我们的大小和类型都可以通过FileReader获取,那我们的宽高呢?
我们将使用new Image()来获取。

1
2
3
4
5
6
7
8
const image = new Image();
const url = reader.result;
// 添加load事件
image.onload = ev => {
// image.width
// image.height
}
image.src = url; // 添加图片地址

处理错误信息

我们的图片校验成功,则进行预览。而校验不通过的话,好的交互则需要我们返回详细的错误信息。
这里我们可以将错误信息收集起来打印:

1
2
3
4
5
6
<div *ngIf="checkErrArr.length" style="color: red;">
<div *ngFor="let errArr of checkErrArr">
<p><strong>{{errArr.name}}</strong></p>
<p *ngFor="let err of errArr.checkErr">{{err}}</p>
</div>
</div>

大家可以看到checkErrArr将以数组方式存放错误信息。我们添加校验后,FileReader的load事件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const regMap = { // 图片类型校验
jpg: /\.(jpe?g)$/i,
jpeg: /\.(jpe?g)$/i,
png: /\.(png)$/i,
gif: /\.(gif)$/i
};
reader.onload = (e: ProgressEvent) => {
const image = new Image();
const url = reader.result;
const name = file.name;
const checkErr = [];
image.onload = ev => {
// 图片大小校验
if (this.limit.size && file.size > this.limit.size * 1024) {
checkErr.push('图片大小已超过 ' + this.limit.size + ' K限制');
}
// 图片尺寸校验
if ((this.limit.width && image.width > this.limit.width) ||
(this.limit.height && image.height > this.limit.height)) {
checkErr.push('图片尺寸不满足 ' + (this.limit.width ? 'w: ' + this.limit.width + ' 以内' : '') +
(this.limit.height ? ' h: ' + this.limit.height + ' 以内' : ''));
}
// 图片类型校验
if (this.limit.type && regMap[this.limit.type] && !regMap[this.limit.type].test(file.name)) {
checkErr.push('图片类型不符合要求,需要上传' + this.limit.type);
}
if (!checkErr.length) {
// 若校验通过,添加进预览列表
this.imagesArr.push({name, url});
this.model.push(url);
this.onChange(this.model);
} else {
// 校验不通过,则收入错误信息
this.checkErrArr.push({name, checkErr});
}
};
image.src = url;
};

添加设置说明

这里我们还需要给我们的设定添加说明,像该处做了哪些限制,需要展示出来给用户。
我们通过一个help字段保存这些协助信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
help: string = '';
ngOnInit() {
if (this.required) {
this.help += '必填;';
}
if (this.limit) {
if (this.limit.width && this.limit.height) {
this.help += '图片尺寸:' + this.limit.width + '*' + this.limit.height + '以内';
}
if (this.limit.size) {
this.help = this.help ? this.help + ',' + this.limit.size + 'k以内' : this.limit.size + 'k以内';
}
if (this.limit.type) {
this.help = this.help ? this.help + ',图片类型:' + this.limit.type : ',图片类型:' + this.limit.type;
}
}
}

然后我们在按钮下面进行辅助说明:

1
<p *ngIf="help" class="help-block">{{help}}</p>

使用[(ngModel)]绑定

经过前面的处理,我们的图片上传控件基本完成了。使用方式也很简单,绑定[(ngModel)]就好了:

1
<upload-image [limit]="{width:750,height:422,size:30,type:'jpg'}" [(ngModel)]="imageArr" [required]="true"></upload-image>

这里本骚年还传入了些设定,最终效果图如下:

image

结束语


上传图片控件和前面动态表单的结合大家可以自行实践,本骚年也将实现代码放到项目里了。
不知道是不是FileReader的原因,上传文件变得好慢,或许是本骚年的使用方式不对。不知道大家有啥建议没呢?
此处查看项目代码
此处查看页面效果

查看Github有更多内容噢:https://github.com/godbasin
更欢迎来被删的前端游乐场边撸猫边学前端噢

码生艰难,写文不易,给我家猪囤点猫粮了喵~

作者:被删

出处:https://godbasin.github.io

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

文章目录
  1. 1. 图片上传控件的制作
    1. 1.1. type=”file”的input
    2. 1.2. change事件
    3. 1.3. FileReader
    4. 1.4. 添加校验
    5. 1.5. 处理错误信息
    6. 1.6. 添加设置说明
    7. 1.7. 使用[(ngModel)]绑定
  2. 2. 结束语