前言
上传功能在任何一个网站中的地位都是举足轻重的,这篇文章主要扯下如何实现一个上传组件
效果图
所具有的功能
- 支持的图片格式(不传参则使用默认参数)
- 支持的图片大小
- 图片上传之前会被压缩(前端) -- 异步加载进来
- 上传过程会显示loading(loading组件)--就一些css3样式
- 支持组件高度设置,宽度自适应
- 支持标题设置
代码实现
代码如下,相关逻辑请看注释。
mit-upload.module.ts
-- 上传模块
// 这三个就不用再解释了 import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; // 服务 import { MitImageUploadLoaderService } from './services/mit-image-upload.loader.service'; // 异步加载JS import { MitImageUploadService } from './services/mit-image-upload.service'; const service = [ MitImageUploadLoaderService, MitImageUploadService ]; // 页面 import { MitUploadComponent } from './mit-upload.component'; // 组件 -- loading import { MitLoadingModule } from '../mit-loading/mit-loading.module'; const component = [ MitUploadComponent ]; @NgModule({ imports: [ FormsModule, CommonModule, MitLoadingModule ], declarations: [ ...component ], exports: [ ...component ], providers: [ ...service ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }) export class MitUploadModule { }
mit-upload.component.html
--- 组件的html结构
<div class="image-upload" [style.height]="height + 'px'" [ngClass]="{'upload-fail': uploadStatus}"> <div class="upload-area"> <input type="file" class="upload-input" (change)="selected($event)"> <div class="icon-wrap"> <i class="fpd fpd-upload"></i> </div> <p class="upload-tips">{{uploadDesrc}}</p> <div class="img-preview" *ngIf="preview"> <img [src]="preview" class="res-img" alt="" onError="this.src='assets/images/default_img/img_error_load.png';"> </div> <app-mit-loading [option]="'load1'" style="position:absolute;top:0;left:0;width:100%;height:100%;background: rgba(72, 72, 72, 0.65);" *ngIf="loadingStatus"></app-mit-loading> </div> <div class="upload-footer" *ngIf="uploadTitleName"> <span>{{uploadTitleName}}</span><a href="javascript:;" (click)="delete($event)" *ngIf="preview">删除</a> </div> </div>
mit-upload.component.scss
-- 组件样式(scss)
@charset 'UTF-8'; // 自定义的一些mixin什么的。。 @import '../../../assets/scss_styles/custom_scss/_custom-export.scss'; $iu-border:#e7e7e7 !default; // 边框颜色 $iu-icon-color:#d5d4d4 !default; .upload-fail { .upload-area { border: 2px solid #d9534f !important; .icon-wrap { i { color: #d9534f !important; } } .upload-tips { color: #d9534f; } } } .image-upload { width: 100%; height: 100%; position: relative; .upload-area { position: relative; width: 100%; height: 100%; border: 2px solid $iu-border; @include flex(center, center); // flex的mixin,就是水平垂直居中 flex-wrap: wrap; .icon-wrap { width: 100%; @include flex(center, center); i { font-size: 64px; color: $iu-icon-color; } } p { margin: 0; padding: 0.5rem; font-size: 14px; color: #808080; } .upload-input { cursor:pointer; position: absolute; left: 0; top: 0; opacity: 0; height: 100%; width: 100%; background: transparent; z-index: $zindex-xs; } .img-preview { position: absolute; left: 0; top: 0; width: 100%; height: 100%; z-index: $zindex-md; img { width: 100%; height: 100%; } } } .upload-footer { padding: 0.5rem; @include flex(space-between, center); // 两边对齐,垂直剧中 span { font-size: 14px; color: #313131; } a { font-size: 14px; color: #37c2dd; } } } .res-img { width: 100%; }
mit-image-upload.loader.service.ts
-- 异步加载前端图片压缩的脚本
用到的是一个github上库:localResizeIMG; 我这里下载了放在cdn上。。
import { Injectable } from '@angular/core'; @Injectable() export class MitImageUploadLoaderService { constructor() { } // 使用promise异步获取回调后动态插入该脚本 load(): Promise<any> { const LRZ_URL = 'http://xxxxxxx.bkt.clouddn.com/lrz.all.bundle.js';// xxxx是我随意打,我放在七牛上了 const p = new Promise((resolve, reject) => { const script = document.createElement('script'); script.type = 'text/javascript'; script.setAttribute('src', LRZ_URL); script.onload = resolve; script.async = true; document.head.appendChild(script); }); return p; } }
mit-image-upload.service.ts
-- 处理图片上传(接口)
import { Injectable } from '@angular/core'; // 核心库-注入服务 import { AuthService } from '../../../services/auth.service'; // 鉴权 import { environment } from '../../../../environments/environment'; // 环境变量 // 图片上传接口 import { IMitImageUpload } from '../interface/mit-image-upload.model'; @Injectable() export class MitImageUploadService { constructor(private authHttp: AuthService) { } resize(e): Promise<any> { const lrz = (<any>window)['lrz']; const p = new Promise((resolve, reject) => { lrz(e) .then(function (rst) { resolve(rst); }) .catch(function (err) { // 处理失败会执行 reject(err); }) .always(function () { // 不管是成功失败,都会执行 }); }); return p; } uploadImg(iMitImageUploadParam: IMitImageUpload) { // authHttp已经做了一些鉴权的封装(对内置的http模块) return this.authHttp.upload(environment.baseUrl + 'FileUpload/ImgUpload', iMitImageUploadParam); } }
environment.ts
-- 存放限制规格参数的。。
我们这里是考虑environment
这个来存放各种配置相关的信息,所以就独立出来了,正常逻辑是封装到组件内的。
// 图片上传参数 export const uploadImgParam = { 'fileType': ['image/png', 'image/jpeg', 'image/jpg'], // 图片上传格式 'fileSize': 3, // 图片上传大小限制(MB) };
mit-upload.component.ts
--- 上传逻辑的实现
import { Component, OnInit, Input, Output, EventEmitter, ElementRef, HostListener, AfterViewInit } from '@angular/core'; import { MitImageUploadLoaderService } from './services/mit-image-upload.loader.service'; import { MitImageUploadService } from './services/mit-image-upload.service'; import { uploadImgParam } from '../../../environments/environment'; // 上传配置 @Component({ selector: 'app-mit-upload', templateUrl: './mit-upload.component.html', styleUrls: ['./mit-upload.component.scss'] }) export class MitUploadComponent implements OnInit, AfterViewInit { @Input() uploadTitleName: string; // 上传标题字 @Input() height: any; // 定制高度 @Input() valiScope?: any; // 上传限制条件 @Input() uploadType: string; // 限定上类型 @Output() uploadEvt = new EventEmitter(); @Input() preview: any; // 图片预览 public uploadDesrc = '请点击上传'; // 点击上传文字替换 public loadingStatus = false; // loading public uploadStatus = false; // 上传状态样式高亮 constructor( private loader: MitImageUploadLoaderService, private mitImageUploadService: MitImageUploadService ) { } ngOnInit() { // console.log( this.uploadTitleName ); } ngAfterViewInit() { const lrz = (<any>window)['lrz']; if (!lrz) { this.loader.load().then(() => { // console.log( 'lrz异步加载成功' ); // console.log(( <any>window )[ 'lrz' ] ); }).catch(() => { }); } else { // console.log( 'lrz无需异步加载' ); } } onerror(e) { // console.log( e ); } // 选择文件 selected(e) { this.fileValidator(e.target.files[0], this.uploadType); } // 上传格式限制判断 fileValidator(e, uploadType?) { // console.log( e ); // [valiScope]="{'imageType':['image/png','image/jpeg','image/jpg'],'imageSize':3}" const scope = { type: [], size: 3 }; const filename = e.name; const filetype = e.type; const filesize = e.size; if (this.valiScope) { scope.type = this.valiScope.Type; // 限制的文件格式 scope.size = this.valiScope.Size * 1024 * 1024; // 限制的大小 } else { scope.type = uploadImgParam.fileType; scope.size = uploadImgParam.fileSize * 1024 * 1024; } // console.log( scope.type ); if (e && scope.type.indexOf(filetype) === -1) { this.uploadDesrc = '图片格式不匹配'; this.uploadStatus = true; } else if (e && scope.size < filesize) { this.uploadDesrc = '图片大小不匹配'; this.uploadStatus = true; } else { this.uploadStatus = false; this.loadingStatus = true; this.resize(e); } return null; } // reize resize(e) { if (e) { this.mitImageUploadService.resize(e).then((res) => { this.uploadIMG(res.formData); // 调用上传接口 }); } } // 上传图片 uploadIMG(data) { this.mitImageUploadService.uploadImg(data).subscribe((res) => { console.log(res); this.loadingStatus = false; if (res.State) { this.preview = res.Data; // 回调成功后渲染图片 this.uploadEvt.emit(res.Data); } else { this.uploadStatus = true; this.uploadDesrc = res.Message; } }, (error) => { this.loadingStatus = false; this.uploadStatus = true; this.uploadDesrc = '上传失败请重试'; }); } // 删除图片预览 delete(e) { // console.log(e); this.preview = null; } }