react antd upload custom request处理多个文件上传
react antd upload custom request处理多个文件上传的问题
背景:第一次请求需要请求后端返回aws 一个link,再往link push文件,再调用另一个接口告诉后端已经上传成功,拿到返回值。 再把返回值传给业务api... 多文件上传一直是循环触发custom request,并且文件上传完之后,需要利用websocket实时更改页面文件的状态
// Uploadinterface BotFile {botFileId: string;url: string;botFileKey: string;openaiFileId: string | null;type: number;fileUId: string;}const [defaultFileList, setDefaultFileList] = useState([]);// current uploaded file (antd Upload OnChange)const [currentUploadFileList, setCurrentUploadFileList] = useState<RcFile[]>([]);// aws completed upload filesconst [uploadedFileList, setUploadedFileList] = useState<BotFile[]>([]);const uploadFilesProps = (type: any) => {const allowedFileTypes = ['.txt','.docx','.pdf','.md','.csv','.json','.xlsx','.xls','.jpg','.jpeg','.png',];const maxTotalSize = 500 * 1024 * 1024;return {name: 'file',multiple: true,listType: 'picture',directory: type,showUploadList: false,action: '/api/sapien-storage/v1/file/frontEndUploads',data: { tenantId: currentUser?.tenantId, type: 'LOCAL_FILE' },headers: {Authorization: `Bearer ${getToken()}`,'api-pass-key': getRasKey(),},fileList: defaultFileList,accept: allowedFileTypes.join(','),beforeUpload(file: RcFile, fileList2: RcFile[]) {setShowCard(true);const ext = file.name.slice(((file.name.lastIndexOf('.') - 1) >>> 0) + 1).toLowerCase();if (!allowedFileTypes.includes(ext)) {message.error(file.name + ' Unsupported file format.');return Upload.LIST_IGNORE;}if (file.size === 0) {updateUploadStatus(file, 'error', 'File cannot be empty');return Upload.LIST_IGNORE;}if (ext === '.xlsx' && file.size > 3145728) {updateUploadStatus(file, 'error', 'Max 3MB');return Upload.LIST_IGNORE;} else if ((ext === '.jpg' || ext === '.jpeg' || ext === '.png') &&file.size > 10 * 1024 * 1024) {updateUploadStatus(file, 'error', 'Max 10MB');return Upload.LIST_IGNORE;} else if (file.size > 52428800) {updateUploadStatus(file, 'error', 'Max 50MB');return Upload.LIST_IGNORE;}let currentTotalSize = 0;let currentUploadFileSize = 0;fileList2.forEach((item) => {currentUploadFileSize += item.size || 0;});defaultFileList.forEach((item) => {currentTotalSize += item.size || 0;});if (currentTotalSize + currentUploadFileSize > maxTotalSize) {if (limitMsg) {updateUploadStatus(file, 'error', 'Max 500MB');setLimitMsg(false);setTimeout(() => {setLimitMsg(true);}, 1000);}return Upload.LIST_IGNORE;}},onProgress: (progressEvent: any, file: any) => {const percent = Math.floor((progressEvent.loaded / progressEvent.total) * 100);setUploadStatus((prevStatus) => {const newStatus: any = [...prevStatus];const fileIndex = newStatus.findIndex((item: any) => item.uid === file.uid);if (fileIndex !== -1) {newStatus[fileIndex].status = 'uploading';newStatus[fileIndex].percent = percent;} else {newStatus.push({uid: file.uid,name: file.name,status: 'uploading',percent: percent,size: file.size,});}return newStatus;});},onChange(info: any) {if (info.file.status === 'done') {if (info.file.response.code === '1001') {setPricingPlanContent(info.file.response.message);setPricingPlanModalVisible(true);setUploadStatus(info.fileList.filter((item: any) => item.uid !== info.file.uid));}setCurrentUploadFileList((prevList) => [...prevList, info.file]);}setDefaultFileList(info.fileList);if (fileListContainerRef.current) {fileListContainerRef.current.scrollTop = fileListContainerRef.current.scrollHeight;}},customRequest(options: any) {const { file, onSuccess, onError, onProgress } = options;const formData = {fileInfos: [{fileName: file.name,contentType: file.type,length: file.size.toString(),},],fileConstants: 'LOCAL_FILE',};const xhr = new XMLHttpRequest();setUploadRequests((prevRequests) => {const newRequests = new Map(prevRequests);newRequests.set(file.uid, xhr);return newRequests;});xhr.upload.onprogress = (event) => {const percent = Math.floor((event.loaded / event.total) * 100);onProgress({ percent }, file);};xhr.onload = () => {if (xhr.status < 200 || xhr.status >= 300) {onError(new Error('Upload error'));setUploadStatus((prevStatus) => {const newStatus = prevStatus.map((item: any) => {if (item.uid === file.uid) {return { ...item, status: 'error' };}return item;});return newStatus;});return;}const response = JSON.parse(xhr.responseText);const uploadResData = response.data[0];const awsUrl = uploadResData.link;const awsXhr = new XMLHttpRequest();setUploadRequests((prevRequests) => {const newRequests = new Map(prevRequests);newRequests.set(file.uid, awsXhr);return newRequests;});awsXhr.upload.onprogress = (event) => {const percent = Math.floor((event.loaded / event.total) * 100);onProgress({ percent }, file);};awsXhr.onload = () => {if (awsXhr.status < 200 || awsXhr.status >= 300) {onError(new Error('AWS Upload error'));setUploadStatus((prevStatus) => {const newStatus = prevStatus.map((item: any) => {if (item.uid === file.uid) {return { ...item, status: 'error' };}return item;});return newStatus;});return;}if (awsXhr.status === 200) {const awsCompletesFileUploadXhr = new XMLHttpRequest();setUploadRequests((prevRequests) => {const newRequests = new Map(prevRequests);newRequests.set(file.uid, awsCompletesFileUploadXhr);return newRequests;});const awsCompletesFileUploadFormData = {fileInfos: response.data,fileConstants: 'LOCAL_FILE',};awsCompletesFileUploadXhr.upload.onprogress = (event) => {const percent = Math.floor((event.loaded / event.total) * 100);onProgress({ percent }, file);};awsCompletesFileUploadXhr.open('POST','/api/sapien-storage/v1/file/awsCompletesFileUpload',true,);awsCompletesFileUploadXhr.onload = () => {if (xhr.status < 200 || xhr.status >= 300) {onError(new Error('Upload error'));setUploadStatus((prevStatus) => {const newStatus = prevStatus.map((item: any) => {if (item.uid === file.uid) {return { ...item, status: 'error' };}return item;});return newStatus;});return;}const awsXhrResponse: API.awsCompletedResponse = JSON.parse(awsCompletesFileUploadXhr.responseText,);if (awsXhrResponse && awsXhrResponse.success) {const awsResData = awsXhrResponse.data[0];const paramData = {botFileId: awsResData.id,url: awsResData.link,botFileKey: awsResData.name,openaiFileId: awsResData.openaiFileId,type: 1,fileUId: file.uid,};setUploadedFileList((prevList) => [...prevList, paramData]);setUploadStatus((prevStatus) => {const newStatus = prevStatus.map((item: any) => {if (item.uid === file.uid) {return { ...item, status: 'success', percent: 100 };}return item;});return newStatus;});}};awsCompletesFileUploadXhr.onerror = () => {onError(new Error('AWS Completion error'));setUploadStatus((prevStatus) => {const newStatus = prevStatus.map((item: any) => {if (item.uid === file.uid) {return { ...item, status: 'error' };}return item;});return newStatus;});};awsCompletesFileUploadXhr.setRequestHeader('Content-Type', 'application/json');awsCompletesFileUploadXhr.setRequestHeader('Authorization', `Bearer ${getToken()}`);const rasKey = getRasKey();if (typeof rasKey === 'string') {awsCompletesFileUploadXhr.setRequestHeader('api-pass-key', rasKey);}awsCompletesFileUploadXhr.send(JSON.stringify(awsCompletesFileUploadFormData));}onSuccess(response);};awsXhr.onerror = () => {onError(new Error('AWS Upload error'));setUploadStatus((prevStatus) => {const newStatus = prevStatus.map((item: any) => {if (item.uid === file.uid) {return { ...item, status: 'error' };}return item;});return newStatus;});};awsXhr.open('PUT', awsUrl, true);awsXhr.setRequestHeader('Content-Type', file.type);awsXhr.send(file);};xhr.onerror = () => {onError(new Error('Upload error'));setUploadStatus((prevStatus) => {const newStatus = prevStatus.map((item: any) => {if (item.uid === file.uid) {return { ...item, status: 'error' };}return item;});return newStatus;});};xhr.open('POST', '/api/sapien-storage/v1/file/frontEndUploads', true);xhr.setRequestHeader('Content-Type', 'application/json');xhr.setRequestHeader('Authorization', `Bearer ${getToken()}`);const rasKey = getRasKey();if (typeof rasKey === 'string') {xhr.setRequestHeader('api-pass-key', rasKey);}xhr.send(JSON.stringify(formData));},};};useEffect(() => {if (currentUploadFileList.length === uploadedFileList.length) {updateFileList(uploadedFileList);}}, [uploadedFileList]);// upload closeconst interruptUpload = (file: any) => {const xhr = uploadRequests.get(file.uid);if (xhr) {xhr.abort();setUploadStatus((prevStatus) => {const newStatus: any = [...prevStatus];const fileIndex = newStatus.findIndex((item: any) => item.uid === file.uid);if (fileIndex !== -1) {newStatus[fileIndex].status = 'suspend';newStatus[fileIndex].percent = 0;}return newStatus;});setUploadRequests((prevRequests) => {const newRequests = new Map(prevRequests);newRequests.delete(file.uid);return newRequests;});setDefaultFileList((prevFileList: any) => {const newFileList = prevFileList.map((item: any) => {if (item.uid === file.uid) {return { ...item, status: 'error' };}return item;});return newFileList;});const newFileList = defaultFileList.map((item: any) => {if (item.uid === file.uid) {return { ...item, status: 'error' };}return item;});updateFileList(newFileList);}};const deleteUpload = (file: any) => {const index = uploadStatus.findIndex((item) => item === file);if (index > -1) {const newUploadStatus = [...uploadStatus];newUploadStatus.splice(index, 1);setUploadStatus(newUploadStatus);}};// Upload End
onChange 里面setDefaultFileList就是把上传的文件列表放里面,方便之后对比文件上传的数量
setUploadRequests 方法是我用来close 上传用的,用不到请忽略
setUploadStatus 方法是我用来展示上传的状态用的,用不到可以忽略
下面这个代码就是我组装已经上传完的数据,之后用来作对比的
const paramData = {botFileId: awsResData.id,url: awsResData.link,botFileKey: awsResData.name,openaiFileId: awsResData.openaiFileId,type: 1,fileUId: file.uid,};setUploadedFileList((prevList) => [...prevList, paramData]);
比较长度,如果一致,那就走上传的逻辑
useEffect(() => {if (currentUploadFileList.length === uploadedFileList.length) {updateFileList(uploadedFileList);}}, [uploadedFileList]);
七八个前端经手了,到我这里我也不知道该咋样了,反正最后是实现了