当前位置: 首页 > news >正文

[React]Antd Upload组件上传多个文件

前言

实现需求:上传多个文件。其实就是获取多个文件的绝对路径交给后端接口去处理。

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 (<Uploadname={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);}));
};
http://www.xdnf.cn/news/1362403.html

相关文章:

  • 微服务商城构筑其一
  • VIVO/OPPO手机,显示5G开关
  • 【Wrangler(Cloudflare 的官方 CLI)和 npm/npx 的区别一次讲清】
  • 大模型面试题剖析:Pre-Norm与Post-Norm的对比及当代大模型选择Pre-Norm的原因
  • openharmony之DRM开发:数字知识产权保护揭秘
  • ESP8266学习
  • 迁移面试题
  • 将跨平台框架或游戏引擎开发的 macOS 应用上架 Mac App Store
  • Docker基本使用方法和常用命令
  • 8851定期复盘代码实现设计模式的于芬应用
  • 从2D序列帧到3D体积感:我用AE+UE5 Niagara构建次世代风格化VFX工作流
  • TDengine IDMP 应用场景:IT 系统监控
  • Ubuntu 14.10 i386桌面版安装教程(U盘启动详细步骤-附安装包下载)​
  • 800G时代!全场景光模块矩阵解锁数据中心超高速未来
  • 5分钟发布技术博客:cpolar简化Docsify远程协作流程
  • Zabbix企业级监控运维实践为主(新)
  • ╳╳╳╳╳╳╳╳╳╳头像商店╳╳╳╳╳╳╳╳╳╳
  • 独立显卡接口操作指南
  • blazor 学习笔记--vscode debug
  • 探索汽车材料新纪元:AUTO TECH 2025广州先进汽车材料展即将震撼来袭
  • Vim 的 :term命令:终端集成的终极指南
  • 服务器Docker 安装和常用命令总结
  • 零售收银选乐檬,高市占率背后的全链路价值赋能
  • 【SQL】深入理解MySQL存储过程:从入门到实战
  • Linux / 宝塔面板下 PHP OPcache 完整实践指南
  • 当模型学会集思广益:集成学习的核心原理与多样化协作模式解析
  • 【Hadoop】HDFS 分布式存储系统
  • 数据结构:单链表(详解)
  • Linux-Redis的安装
  • 【Linux】开发工具命令指南:深度解析Vim的使用操作