前端自定义上传(ts语法)
导出 uploadFile
方法,外部直接使用该方法即可
分片上传api请求对象:multipartUploadApi
直传api请求对象:directUploadApi
分片上传方法:multipartUpload
,复制后根据自己api修改分片上传的参数即可
import { message } from 'ant-design-vue';
import axios from 'axios';
const apiUrl = ''; // 不分片上传url
const multipartBeforeUrl = ''; // 分片上传之前url传递
const multipartUploadUrl = ''; // 分片上传url
const multipartCompleteUrl = ''; // 分片上传完成
interface Options {
onProgress: any; // 进度回调
splitSize: number; // 切片大小
splitCount: number; // 一次性发送切片请求的个数
}
interface SplitInfo {
totalChunks: string | number; // 分片总数
fileName: string; // 文件名
fileSize: string | number; // 文件大小
partSize: string | number; // 文件类型
contentType?: string; // 文件类型
}
// 分片上传api
const multipartUploadApi = {
beforeUpload: async (params: SplitInfo, callback?: any) => {
const response = await axios.post<any, any>(multipartBeforeUrl, params);
if (response.success) {
return response.data;
} else {
message.error(response.msg || '上传失败!');
return null;
}
},
uploadFile: async (params: FormData, callback?: any) => {
const response = await axios.post<any, any>(multipartUploadUrl, params);
if (response.success) {
return response.data;
} else {
message.error(response.msg || '请求异常');
return response;
}
},
completeUpload: async (params: string, callback?: any) => {
const response = await axios.post<any, any>(multipartCompleteUrl);
if (response.success) {
return response;
} else {
message.error(response.msg);
return null;
}
},
};
// 不走分片api,直传
const directUploadApi = {
uploadFile: async (file: FormData) => {
const response = await axios.post<any, any>(apiUrl, file);
if (response.success) {
return response;
} else {
message.error(response.msg || '上传失败!');
return response;
}
},
};
// 根据splitSize 对文件进行切片
const splitFile = (file: File, splitSize = 5) => {
const _5M = 1024 * 1024 * splitSize; // 默认分割成5M
const { size, type } = file;
const tempFileList = []; // 盛放切割后的切片
for (let index = 0; index < size; index += _5M) {
const start = index; // 切片开始位置
const end = start + _5M < size ? index + _5M : size; // 切片结束位置
const item = file.slice(start, end, type); // 切割的文件
tempFileList.push(item);
}
return tempFileList;
};
// 将fileList 分组
const groupFileListByCount = (fileList: any[], count: number) => {
const group = Math.ceil(fileList.length / count); // 将 fileList 分为多少个组
const resultList = new Array(group); // 根据组数创建一个空的 list
fileList.map((item, index) => {
const i = Math.floor(index / count); // index / count 向下取整 为组数编号
if (Array.isArray(resultList[i])) resultList[i].push(item);
else resultList[i] = [item];
});
return resultList;
};
// 上传分组的切片文件
export const uploadGroupFileList = async (
list: any[] = [],
splitInfo: any,
onProgress: any, // 每一个切片上传成功之后 调一次上传回调 模拟一个进度条
) => {
/**
* 模拟一个进度条
* total+1 避免当所有的切片都上传完成后进度100% 。而还没有请求服务器合并切片
* 或者请求服务器合并分片失败 而上传进度为100% 对用户不友好
*/
const progress = {
total: splitInfo.totalChunks + 1,
loaded: 0,
};
for (let i = 0; i < list.length; i++) {
const itemList = Array.isArray(list[i]) ? list[i] : [];
const itemHttpUpload = itemList.map((item: any, index: number) => {
return new Promise((resolve, reject) => {
const chunkNumber = i * list[i].length + index + 1; // 当前是所有文件切片中的第几个
console.log(chunkNumber, 'chunkNumber');
const { multipartUploadTaskId } = splitInfo;
const uploadForm = new FormData();
// 自定义参数
uploadForm.append('file', item);
uploadForm.append('partNumber', chunkNumber + '');
uploadForm.append('multipartUploadTaskId', multipartUploadTaskId);
multipartUploadApi
.uploadFile(uploadForm)
.then(() => {
// 上传进度回调
progress.loaded++;
onProgress(progress);
resolve(chunkNumber);
})
.catch(() => {
reject(chunkNumber);
});
});
});
// Promise.allSettled 等待上一组 upload http请求所有都完成后 返回结果
const lastGroupUpload = await Promise.allSettled(itemHttpUpload)
.then(res => {
return Promise.resolve(res);
})
.catch(err => {
return Promise.resolve(err);
});
// 将上传失败的分片文件存储起来
const errorChunkList = lastGroupUpload
.filter((item: any) => item.status === 'rejected')
.map((item: any) => item.reason);
// 判断上一组的http 中是否有上传失败的分片 存在则不继续上传剩余的分片 返回 false
if (errorChunkList.length > 0) return false;
}
return true;
};
// 分片上传
const multipartUpload = async (file: File, { splitSize, splitCount, onProgress }: Options) => {
// 简单检查一下file 参数
if (!file || typeof file != 'object' || Array.isArray(file))
throw new Error(" a required parameter 'file' missing .");
// 简单判断一下 splitSize 必须是大于或等于 1 的 数字
if (typeof splitSize != 'number' || splitSize < 1)
throw new Error(" the type of 'splitSize' must be 'number' and greater than 1 .");
// 简单判断一下 splitCount 必须是大于或等于 1 的 数字
if (typeof splitCount !== 'number' || splitCount < 1)
throw new Error(" the type of 'splitCount' must be 'number' and greater than 1 .");
const _file = file;
const { size, name, type, lastModified } = _file; // 获取文件大小 和名称
const partSize = splitSize * 1024 * 1024; // 切片大小
const totalChunks = Math.ceil(size / partSize); // 切片总块数 文件大小/ 切片大小向上取整
const splitFileList = splitFile(_file, splitSize); // 返回切片数组
const groupFileList: any[] = groupFileListByCount(splitFileList, splitCount); // 将 切片数组根据count 分组
const splitInfo: SplitInfo = { totalChunks, fileName: name, partSize, fileSize: size }; // 文件切片信息
// 上传分片文件件 告诉服务器准备上传的切片文件信息
const beforeUploadFlag = await multipartUploadApi.beforeUpload(splitInfo);
if (!beforeUploadFlag) return false; // 当服务器没准备好切片上传
// 切片上传的结果
const uploadFlag = await uploadGroupFileList(
groupFileList,
{ ...splitInfo, multipartUploadTaskId: beforeUploadFlag.id },
onProgress,
);
if (!uploadFlag) return false;
// 请求服务器合并切片文件
const mergeResult = await multipartUploadApi.completeUpload(beforeUploadFlag.id);
if (!mergeResult) return false;
// 请求服务器合并切片成功后 调一次上传进度回调接口 让上传进度100%
onProgress({
total: totalChunks + 1,
loaded: totalChunks + 1,
});
return mergeResult;
};
// 文件上传
export const uploadFile = function (
file: File,
options: Options = {
splitSize: 3,
splitCount: 3,
onProgress: () => {},
},
) {
const _10M = 10 * 1024 * 1024;
const _MaxSize = 80 * 1024 * 1024;
const { size, type } = file;
if (size > _MaxSize) {
message.warn('上传文件超过50M, 请调整文件后上传!');
return;
}
// 判断当前文件是否分片上传
if (size > _10M) {
// 分片上传
return multipartUpload(file, options);
} else {
// 正常上传
const formData = new FormData();
formData.append('file', file);
return directUploadApi.uploadFile(formData);
}
};