React Antd Upload组件上传多个文件实现方式
作者:水果摊见
为实现多文件上传,需使用beforeUpload和customRequest替代onChange以避免多次调用问题,并处理文件路径以兼容Electron不同平台
前言
实现需求:
上传多个文件。其实就是获取多个文件的绝对路径交给后端接口去处理。
Upload组件
首先,这里不能调用Upload的onChange函数,因为上传中、完成、失败都会调用这个函数,在多个文件的情况下会出现多次调用问题。
改用beforeUpload和customRequest。
import React, { useRef, useState, useEffect } from 'react'; import { UploadOutlined } from '@ant-design/icons'; import type { UploadFile, UploadProps } from 'antd'; import { Button, Upload } from 'antd'; import { useTranslation } from 'react-i18next'; import Console from '../../../../util/Console'; import { UploadChangeParam } from 'antd/es/upload'; import { sendMsgToMain } from 'electron-prokit'; type Props = { name?: string; onChangeFilePath: (path: string | null | unknown, info?: UploadChangeParam<UploadFile<any>>) => void; accept: Array<string>; headers?: any; progress?: any; buttonTitle?: string; rest?: UploadProps; action?: string | ((file: any) => Promise<string>); }; const FileMultiSelectButton: React.FC<Props> = ({ name, onChangeFilePath, accept, headers, progress = null, buttonTitle, action, ...rest }) => { const { t } = useTranslation(); const fileState: any = useRef(); const [uploadFiles, setUploadFiles] = useState<UploadFile[]>([]); const updateFiles = (function () { let fileList: UploadFile[] | null = null; return function (list: UploadFile[], setState: React.Dispatch<React.SetStateAction<UploadFile[]>>) { if (!fileList) { fileList = list; setState && setState(list); } return { fileList, reset() { fileList = null; } }; }; })(); const beforeUpload = (_: any, fileList: UploadFile[]) => { fileState.current = updateFiles(fileList, setUploadFiles); return false; } const customRequest = () => { if (uploadFiles.length > 0) { const path = uploadFiles.map(item => (item as any).path); // 这是electron的ipc处理函数,在node中复制文件 sendMsgToMain({ key: 'fileMultiSelectPath', data: path }).then(filePath => { if (typeof onChangeFilePath === 'function') { onChangeFilePath(filePath); } else { Console.handleErrorMsg('onChangeFilePath is not a function'); } }); } else { Console.handleWarningMsg('no file uploaded'); } } useEffect(() => { if (uploadFiles.length > 0) { customRequest(); fileState.current.reset(); } }, [uploadFiles]); return ( <Upload name={name || 'file'} accept={accept.join(',')} progress={progress} showUploadList={false} headers={headers} action={action} multiple={true} beforeUpload={beforeUpload} customRequest={customRequest} {...rest} > <Button icon={<UploadOutlined />}> {buttonTitle || t('Select')} </Button> </Upload> ); }; export default FileMultiSelectButton;
在node中处理(不必须),这里主要因为electron需要兼容不同的平台,需要处理文件路径
function generateRandomString(length: number) { let result = ''; const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const charactersLength = characters.length; for (let i = 0; i < length; i++) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); } return result; } const fileSelectPath = (sourcePath: string) => { return new Promise((resolve, reject) => { const userDataPath = app.getPath('userData'); // 获取用户数据目录,并在该目录下创建文件夹,用于保存用户数据,需要判断是否存在,如果存在则直接使用,否则创建,并返回该目录,用于后续操作,并需要兼容跨平台,比如windows和mac // 拼接出 files 目录的完整路径 const filesDirPath = path.join(userDataPath, 'files'); fs.mkdir(filesDirPath, { recursive: true }, err => { if (err) { // 如果目录创建失败,返回错误信息 // 如果目录创建失败,处理错误 console.error('Failed to create files directory:', err); } // 生成一个随机数 const randomBytes = generateRandomString(8); // 生成8字节的随机数 // 获取当前日期和时间 const date = new Date(); const formattedDate = date.toISOString().split('T')[0].replace(/-/g, ''); // 格式化为不含破折号的日期 const formattedTime = date.toTimeString().split(' ')[0].replace(/:/g, ''); // 格式化为不含冒号的时间 // 提取源文件名和扩展名 const { name, ext } = path.parse(sourcePath); // 根据日期、时间和随机数生成一个新的文件名 const newName = `${name}_${formattedDate}_${formattedTime}_${randomBytes}${ext}`; // 构造目标路径 const destinationPath = path.join(filesDirPath, newName); // 使用fs模块的copy方法复制文件 fs.copyFile(sourcePath, destinationPath, err => { if (err) { // 如果文件已存在或复制失败,返回错误信息 reject(err); } else { // 复制成功,返回新的文件路径 resolve(destinationPath); } }); }); }); }; export const fileMultiSelectPath = (sourcePath: Array<string>) => { return Promise.all(sourcePath.map(async (sourcePath) => { return await fileSelectPath(sourcePath); })); };
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。