React Antd Upload组件上传多个文件实现方式

 更新时间:2025年08月30日 10:55:48   作者:水果摊见  
为实现多文件上传,需使用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);
    }));
};

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • React-Hook中使用useEffect清除定时器的实现方法

    React-Hook中使用useEffect清除定时器的实现方法

    这篇文章主要介绍了React-Hook中useEffect详解(使用useEffect清除定时器),主要介绍了useEffect的功能以及使用方法,还有如何使用他清除定时器,需要的朋友可以参考下
    2022-11-11
  • React createElement方法使用原理分析介绍

    React createElement方法使用原理分析介绍

    这篇文章主要为大家介绍了React的createElement方法源码解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • React中的路由嵌套和手动实现路由跳转的方式详解

    React中的路由嵌套和手动实现路由跳转的方式详解

    这篇文章主要介绍了React中的路由嵌套和手动实现路由跳转的方式,手动路由的跳转,主要是通过Link或者NavLink进行跳转的,实际上我们也可以通JavaScript代码进行跳转,需要的朋友可以参考下
    2022-11-11
  • React中不适当的Hooks使用问题及解决方案

    React中不适当的Hooks使用问题及解决方案

    在 React 开发中,Hooks 提供了一种强大的方式来管理组件的状态和生命周期,然而,不恰当的 Hooks 使用可能会导致组件行为异常、性能问题或难以调试的错误,本文将探讨 React 中常见的不适当 Hooks 使用问题,并提供解决方案,需要的朋友可以参考下
    2025-03-03
  • React Draggable插件如何实现拖拽功能

    React Draggable插件如何实现拖拽功能

    这篇文章主要介绍了React Draggable插件如何实现拖拽功能问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-07-07
  • 详解React路由传参方法汇总记录

    详解React路由传参方法汇总记录

    这篇文章主要介绍了详解React路由传参方法汇总记录,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-11-11
  • ReactNative之FlatList的具体使用方法

    ReactNative之FlatList的具体使用方法

    本篇文章主要介绍了ReactNative之FlatList的具体使用方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-11-11
  • 一文带你搞懂React中的useReducer

    一文带你搞懂React中的useReducer

    useReducer 是除useState之外另一个与状态管理相关的 hook,这篇文章主要为大家介绍了useReducer应用的相关知识,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-06-06
  • React Hooks: useEffect()调用了两次问题分析

    React Hooks: useEffect()调用了两次问题分析

    这篇文章主要为大家介绍了React Hooks: useEffect()调用了两次问题分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-11-11
  • React中react-redux和路由详解

    React中react-redux和路由详解

    这篇文章主要介绍了React中react-redux和路由详解,redux早期被设计成可以在各个框架中使用,因此在不同的框架中使用的时候,要引入相应的插件,感兴趣的朋友可以继续学习下面文章
    2022-08-08

最新评论