js formdata 上传多文件,js文件上传fileupload
墨初 知识笔记 127阅读
上传功能需要的一些辅助库
1、使用第三方 vant 组件中的提示做 loading 来区分和提示。
2、大文件上传需要计算 MD5 值使用 spark-md5 库。
3、上传接口自定义的 axios接口使用 Apis[‘方法’] 的方式调用接口此处不是接口封装的重点
此处使用 vue 形式编写功能可以提出使用在js方法中注意去掉 ts 内容

<template><input refinputFile typefile hidden changecheckFiles /><van-button classupBut color#BF234D clickonClick>点击上传文件</van-button></template>
<script setup langts>import Apis from /utils/apis;import FileReaderUpoad from /utils/upoad;import { showLoadingToast, showSuccessToast, showFailToast } from vant;/** 创建上传组件实例 */const inputFile ref<HTMLFieldSetElement | null>(null);/** 创建公共字段集合 */const data reactive({ loading: false,});// 点击自定义上传按钮调用上传点击事件const onClick () > { // 调用 input file 的点击事件 inputFile.value && inputFile.value.click();};// input 获取文件成功后触发的change事件const checkFiles (e: Event) > {/** 判断拦截是否正在上传 */if (data.loading) return; data.loading true; /** 获取元素内容 */ let event: any e.target as EventTarget; /** 获取选中的文件 */ let file event.files[0]; /** 文件使用的验证ID */ let storageId ; let fileName , suffix ; if (file.name) { let arr file.name.split(.); fileName arr.filter((item: object, index: number) > arr.length - 1 ! index).join(.); suffix arr.filter((item: object, index: number) > arr.length - 1 index).join(.); }; /** 提示 */ showLoadingToast({ message: 正在解析文件, forbidClick: true, duration: 0, overlay: true, });/** 创建文件分片并发实例(方法参数作用请向下找到自定义封装的分片处理文件中查找) */ let upoad new FileReaderUpoad({ file, maxNum: 4, size: 5242880, byteLength: file.size, singleFileSize: 209715200, /** 单文件上传 */ once: () > { showLoadingToast({ message: 正在上传中, forbidClick: true, duration: 0, overlay: true, }); // 单文件上传 let obj: any {...}; Apis.upLoad(obj).then((res: any) > { if (res.code 200) { data.loading false; showSuccessToast(上传成功); }; }).catch((err) > { showFailToast(上传出现问题); data.loading false; }); }, /** 开始分片处理创建分片信息上传分片信息 */ start: (data: any, next: Function) > { /** 开始创建分片信息上传信息, 具体需要什么信息请联系自己的后端打印 data 数据查找对应字段 */ let obj: any { /** 计算好的 md5 值 */ fileMd5: data.md5, /** 文件总片数 */ sliceTotalChunks: data.total, /** 其他字段自己添加从 file 文件数据或者 data 中查找 */ ...}; /** 上传分片信息 */ Apis.start(obj).then((res: any) > { if (res.code 200) { /** 这里后台可能会返回一个验证 ID 要带在每个分片上传中不需要删除即可 */ storageId res.obj.storageId; /** 当前分片信息已经上传成功通过调用 next() 函数通知执行下一个逻辑 */ next(); }; }).catch(() > { showFailToast(创建上传信息失败); data.loading false; }); }, /** 单个分片上传方法 */ upload: (val: any, next: Function) > { /** 上传分片参数根据需要添加参数 **/ let obj: any { /** 文件名称 */ name: fileName, /** 当前是第几个分片 */ chunk: val.page, /** 一共多少分片 */ chunks: val.total, /** 总文件的md5哈希值 */ md5: val.totalMd5, /** 总文件的大小 */ size: val.file.size, /** 验证 ID 值 */ tenantId: storageId, /** 当前分片的 md5 哈希值 */ currentFileMd5: val.md5, /** 当前分片文件 */ file: val.blob }; /** 上传单片文件的接口 */ Apis.burst(obj).then((res: any) > { /** 上传文件成功直接通过 next() 执行下一步 */ if (res.code 200 && res.success) next(); /** 如果上传失败或者超时则将当前分片传入 next(val),让分片重新进入队列上传分片 */ else next(val); }).catch(() > {/** 如果上传失败或者超时则将当前分片传入 next(val),让分片重新进入队列上传分片 */ next(val); }); }, /** 分片上传加载进度返回 */ loading: (val: string) > { showLoadingToast({ message: 已上传${val}%, forbidClick: true, duration: 0, overlay: true, }); }, /** 分片上传结束使用当前函数通过接口通知后台上传结束 */ end: (res: any) > { /** 调用结束接口 */ Apis.end({ tenantId: storageId }).then((res: any) > { if (res.code 200) { showSuccessToast(上传成功); } }).catch(() > { showFailToast(上传失败); data.loading false; }) } }); /** 调用函数开始对文件进行分片处理 */ upoad.uploadStart();};</script>
以下是自定义库的内容可直接使用
import SparkMD5 from spark-md5;interface FileType { file: File; // 源文件 size: number; // 单片大小 once?: Function; // 单文件上传 maxNum?: number; // 设置最大并发数 start?: Function; // 创建开始上传初始化 end?: Function; // 分片派发完结束返回 upload?: Function; // 抛出所有拆分的分片 loading?: Function; // 加载进度 byteLength: number; // 文件总大小 singleFileSize: number; // 文件超出多大值做分片处理};export default class FileReaderUpoad { private option: any []; // 所有分片存储 private Setting: FileType; // 传进来的参数 private totalFilm!: number; // 总片数 private result: string ; // 二进制串 private current: number 0; // 当前是第几片 private totalMd5: string ; // 总文件的md5哈希值 private arrLength: number 0; // 总长度 constructor(Setting: FileType) { this.Setting Setting; }; // 开始执行 public uploadStart () > { let that this; // 判断文件是否需要分片 if (this.Setting.file.size > this.Setting.singleFileSize) { // 将文件转化为二进制串 let fileReader new FileReader(); fileReader.readAsBinaryString(this.Setting.file); fileReader.onload function () { // 设置并发数 that.Setting.maxNum that.Setting.maxNum || 1; // 获取二进制串 that.result fileReader.result as string; // 计算总片数 that.totalFilm Math.ceil(that.Setting.byteLength / that.Setting.size); // 计算md5值; that.getDataMd5(that.result).then((res: any) > { // 计算成功后将参数传出 start 方法调用上传创建信息 that.totalMd5 res; if (typeof (that.Setting.start) function) that.Setting.start({ md5: res, total: that.totalFilm }, () > { that.current 1; that.option []; that.burstParam(); }); }); }; } else { typeof this.Setting.once function && this.Setting.once(); }; }; // 单个分片解析计算以及参数补齐 private burstParam() { // 如果当前片数小于总片数 if (this.current < this.totalFilm) { // 计算当前片的起始位置和结束位置 let start (this.current - 1) * this.Setting.size; let end this.current * this.Setting.size; // 对当前片进行分片处理 this.fileSlice(start, end, (val: string) > { // 计算当前片的md5值 this.getDataMd5(val).then((res: unknown) > { // 将当前片的参数存入option数组中 this.option.push({ md5: res, chunkFile: val, size: val.length, page: this.current -1, total: this.totalFilm, totalMd5: this.totalMd5, file: this.Setting.file, blob: this.Setting.file.slice(start, end), }); // 当前片数加1 this.current; // 递归调用burstParam函数 this.burstParam(); }); }); } else { // 计算最后一片的起始位置和结束位置 let start (this.current - 1) * this.Setting.size; let end this.current * this.Setting.size; // 对最后一片进行分片处理 this.fileSlice(start, end, (val: string) > { // 计算最后一片的md5值 this.getDataMd5(val).then((res: unknown) > { // 将最后一片的参数存入option数组中 this.option.push({ md5: res, chunkFile: val, size: val.length, page: this.current -1, total: this.totalFilm, totalMd5: this.totalMd5, file: this.Setting.file, blob: this.Setting.file.slice(start, this.Setting.byteLength), }); // 记录option数组的长度 this.arrLength this.option.length; // 判断并发数量存不存在并且要大于 0 if (this.Setting.maxNum && this.Setting.maxNum > 0) { // 调用multiRequest函数传入option数组和最大并发数 this.multiRequest(this.option, this.Setting.maxNum).then((res: any) > { // 调用end函数传入结果 typeof this.Setting.end function && this.Setting.end(res); }); } else { // 并发数不能为零 new Error(并发数不能为零!); }; }); }); }; }; // 分片处理 private fileSlice (start: number, end: number, callback: Function) > { callback(this.result.slice(start, end)); }; // 计算md5值 private getDataMd5 (data: string) > { return new Promise((resolve, reject) > { if (data) resolve(new SparkMD5().appendBinary(data).end()); else reject(计算失败); }); }; /** * 多个请求并发 * param urls 请求地址数组 * param maxNum 最大并发数 * returns Promise */ private multiRequest (urls: any, maxNum: number) > { // 初始化队列 let queue: any []; // 克隆urls数组 let urlsClone [...urls]; // 初始化结果数组 let result new Array(urls.length); // 初始化请求是否完成数组 let isDoneArr new Array(urls.length).fill(0); // 用于判断请求是否全部完成 let queueLimit Math.min(maxNum, urls.length); // 初始化计数器 let index 0; // 请求返回 const request (queue: any, url: any) > { // 如果有上传函数, 调用 upload 函数传参 if (typeof this.Setting.upload function) this.Setting.upload(url, (val: object) > { // 保证result结果和 urls 顺序相同 let i urlsClone.indexOf(url); result[i] url; isDoneArr[i] 1; // 判断当返回值val有值时则接口请求失败再次添加到请求列表 if(val) { // 列表边长说明计算 百分比总数发生变化 this.arrLength; // 添加失败的的接口到请求列表 urls.push(val); // 添加到判断请求是否完毕的列表添加请求次数 isDoneArr.push(val); } // 执行完一个请求后执行出队操作 outLine(queue, url); }) }; // 进入 const EnterTheTeam (queue: any [], url: object) > { // 请求入队并触发数据请求返回队列长度 let len queue.push(url); if (len this.Setting.maxNum) { index; // 计算加载进度 let t parseInt(String(index / this.arrLength * 100)); // 存在加载进度函数传出当前进度 typeof this.Setting.loading function && this.Setting.loading(t); }; request(queue, url); return len; }; // 初始化队列请求在此处限制队列长度 while (EnterTheTeam(queue, urls.shift()) < queueLimit) {}; // 设置定义抛出内容 let promise: any { resolve: , reject: , }; // 出队 const outLine (queue: any [], url: object) > { // 一个请求完成出队下一个队列如果存在 queue.splice(queue.indexOf(url), 1); if (urls.length) EnterTheTeam(queue, urls.shift()); else { // 判断所有请求完成再 resolve if (isDoneArr.indexOf(0) -1) { promise.resolve(result); typeof this.Setting.loading function && this.Setting.loading(100); }; } }; // 返回所有内容 return new Promise((resolve, reject) > { return promise.resolve resolve; }); };};

标签: