在项目中有一些大文件需要做分断上传并显示进度条,最近我在做项目的时候也遇到了这种情况,在这里我记录下分断上传如何实现的。
分断上传实际上就是将原本是一个文件一个请求改为一个文件多个请求上传
首先,写一个分断上传的类,这里直接上代码
// 封装请求的的api
import ApiService from "../core/services/ApiService";
/**
* 上传文件
* */
/** 上传的类 */
export default class UploadFile {
// 当前的类
private static uploadFile: UploadFile;
// 分断上传 每一片的大小
private chunkSize: number = 5242880;
// 上传的文件列表
private fileArray: Array<any> = [];
// 当前是否是多文件上传
private multiple: boolean = false;
// 上传的格式类型 - 如果是空数组就代表全部 - 如果想一个都不允许给数组随便设置值 比如: [null]
private typeArray: Array<string> = [];
// 构造函数
constructor() {
// 这里不允许new对象 - 抛出异常
if(UploadFile.uploadFile) {
throw new Error("");
}
};
/** 获取当前对象 */
public static newInstance() {
if(!UploadFile.uploadFile) {
UploadFile.uploadFile = new UploadFile();
}
return UploadFile.uploadFile;
}
/**
* 初始化数据
* config - 所有配置项
* success - 更新文件的回调或上传成功的回调 - 可选
* error - 上传失败的回调 - 可选
* */
public handleInitData = (configData: any, success?: Function, error?: Function) => {
const { multiple } = configData;
this.fileArray = [];
if(multiple) {
this.multiple = multiple;
}
if(success) {
this.updateFileCallBack = success;
}
if(error) {
this.uploadErrorCallBack = error;
}
}
/** 上传方法 */
public handleUpload(fileArray: Array<any>) {
// 上传之前的判断
if(!this.handleBeforeUpload(fileArray)) {
return;
}
// 开始上传
this.handleMergeFileRequest(fileArray);
}
/** 上传之前 */
private handleBeforeUpload = (fileArray: Array<any>): Boolean => {
// 这里做上传之前的格式校验判断...
return true;
}
/** 处理文件断点续传的请求参数 - 参数-单个文件 */
private handleUploadFragmentParams(file): Array<any> {
// 总的上传次数
const chunkTotal = Math.ceil(file.size / this.chunkSize);
// 当前的上传次数
let chunkIndex = 0;
// 记录当前文件上传的请求参数列表
let tempArray: Array<any> = [];
// 循环整理当前文件每一次上传的参数
for(let i = 0; i < chunkTotal; i++) {
let params = {
name: file.name,
size: file.size,
type: file.type,
chunk: chunkIndex,
chunks: chunkTotal,
file: i + 1 === chunkTotal ? new File([file.slice(chunkIndex)], file.name) : new File([file.slice(chunkIndex * this.chunkSize, (chunkIndex + 1) * this.chunkSize)], file.name),
};
chunkIndex++;
tempArray.push(params);
}
// 将处理好的参数列表返回出去
return tempArray;
}
/** 处理文件上传请求 */
private handleUploadFileRequest(params): Promise<any> {
return new Promise((resolve, reject) => {
// 将file文件设置成form数据
const fileForm = new FormData();
for(const key in params) {
fileForm.append(key, params[key]);
}
ApiService._post("/breakpoint-upload", fileForm).then(response => {
resolve(response);
}).catch(() => {
reject(null);
});
});
}
/** 处理上传成功后获取文件数据 - 最后一次上传成功后获取刚刚上传的文件信息 - 我这里后端是需要发送这个请求的,具体看后端怎么说 */
private handleGetFileData = (name: any): Promise<any> => {
return new Promise(resolve => {
// 将file文件设置成form数据
const fileForm = new FormData();
//
fileForm.append("compress", "否");
fileForm.append("fileName", name);
ApiService._post("/attachments/add", fileForm).then(res => {
resolve(res.data.data);
}).catch(() => {
resolve(null);
});
});
}
/** 处理整合上传文件请求 */
private handleMergeFileRequest(fileArray: Array<any>) {
// 记录本次上传的起始下标
const uploadStartIndex = this.fileArray.length;
// 处理循环执行请求
const handleLoopExecutionRequest = (array: Array<any>, index: number, chunkIndex: number = 0) => {
// 发送请求开始上传文件
this.handleUploadFileRequest(array[index][chunkIndex]).then(() => {
// 不是最后一次上传
if(chunkIndex + 1 !== array[index].length) {
if(this.multiple) {
// 刷新当前上传的文件进度
const tempFileArray = this.fileArray;
tempFileArray[uploadStartIndex + index] = { ...tempFileArray[uploadStartIndex + index], type: 2 };
this.updateFileCallBack(tempFileArray);
}
// 再次调下一个接口继续上传
handleLoopExecutionRequest(array, index, chunkIndex + 1);
}
// 如果是最后一次上传的话
else {
// 获取上传的文件数据
this.handleGetFileData(array[index][chunkIndex].name).then(data => {
// 上传成功
if(data) {
if(!this.multiple) {
this.updateFileCallBack(data);
}
else {
// 刷新文件上传进度 - 这里显示进度百分百
const tempFileArray = this.fileArray;
tempFileArray[uploadStartIndex + index] = { ...tempFileArray[uploadStartIndex + index], type: 2 };
this.updateFileCallBack(tempFileArray);
// 再次刷新 显示上传完成 - 这里显示完成
const timeout = setTimeout(() => {
tempFileArray[uploadStartIndex + index] = { ...data, ...tempFileArray[uploadStartIndex + index], type: 1 };
this.updateFileCallBack(tempFileArray);
clearTimeout(timeout);
}, 500);
}
}
// 上传失败
else {
const tempFileArray = this.fileArray;
tempFileArray[uploadStartIndex + index] = { ...tempFileArray[uploadStartIndex + index], type: 0, error: '上传失败', name: array[index][chunkIndex].name, size: array[index][chunkIndex].size };
this.updateFileCallBack(tempFileArray);
}
});
}
}).catch(() => {
handleLoopExecutionRequest(array, index, chunkIndex);
});
}
let tempArray = [] as any;
for(let i = 0; i < fileArray.length; i++) {
tempArray[i] = this.handleUploadFragmentParams(fileArray[i]);
if(this.multiple) {
// 这边更新下上传文件的进度 - 这里我定义的是 type = 0 上传失败 type = 1 上传成功 type = 2 上传中
// 可以根据type在页面展示不同的效果
const tempFileArray = this.fileArray;
tempFileArray.push({ type: 2, size: 0, totalSize: tempArray[i][0].size, name: tempArray[i][0].name });
this.updateFileCallBack(tempFileArray);
}
handleLoopExecutionRequest(tempArray, i, 0);
}
}
/** 更新文件列表的回调 - 更新进度条或者每一次上传成功后 */
private updateFileCallBack: Function = () => {};
/** 上传错误的回调 */
private uploadErrorCallBack: Function = () => {};
}
使用:
<template>
<div>
<input @change="handleChange" multiple type="file" />
</div>
</template>
<script lang="ts">
import {defineComponent, reactive, toRefs } from 'vue';
import UploadFile from '@/class/UploadFile';
export default defineComponent({
setup() {
const state = reactive({});
const handleChange = (event) => {
// 获取上传对象
const uploadFile = UploadFile.newInstance();
// 初始化一些配置
uploadFile.handleInitData({ multiple: true }, value => {
// 这里是上传成功的回调
console.log(value)
});
// 触发上传方法
uploadFile.handleUpload(event.target.files);
}
return {
...toRefs(state),
handleChange,
}
}
});
</script>