【上传】分片上传+ts+上传进度

前端自定义上传(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);
  }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值