Antd中Upload组件封装及使用:

1.Upload上传组件功能:

文件校验 :

文件格式校验/文件大小校验/上传文件总个数校验

相关功能 :

拖拽功能/上传到远程(七牛)/文件删除及下载

2.组件效果展示:

3.疑难点及解决方案:

Promise.all多文件并行上传到远程(七牛云):

(1)在beforeUpload钩子函数中获取token

(2)循环fileList文件列表,使用fetch将所有文件上传到七牛,并将结果包装成Promise return出去

(3)待所有文件上传成功后,通过Promise.all获取并存储结果,并通过useEffect及时将七牛返回的结果添加到fileList文件列表中。

(4)注:由于一次上传多个文件时,beforeUpload钩子函数会执行多次,需要使用debounce进行防抖。

复制代码
const [promiseAllResult, setPromiseAllResult] = useState<UploadFile[]>([]);

const beforeUpload: UploadProps["beforeUpload"] = (file, fileList) => {
		debouncedBeforeUpload(fileList);
		return false;
};

const debouncedBeforeUpload = debounce(async fileList => {
		...
		const res = await getQiniuTokenApi();
		const uploadPromises = fileList.map(async (file: any) => {
				return new Promise((resolve, reject) => {
					const formData = new FormData();
					formData.append("file", file);
					formData.append("token", res.data?.upToken || "");

					fetch("https://upload.qiniup.com/", {
						method: "POST",
						body: formData
					})
						.then(response => {
							if (response.status === 200) {
								return response
									.json()
									.then(data => {
										// 返回包含文件信息和响应数据的对象
										resolve({
											uid: file.uid,
											url: "https://" + res.data?.domain + "/" + data.key + "?attname=" + file.name,
											filePreviewUrl: "https://" + res.data?.domain + "/" + data.key
										});
									})
									.catch(() => {
										reject(new Error("Upload failed"));
									});
							} else {
								reject(new Error("Upload failed"));
							}
						})
						.catch(error => reject(error));
				});
		});
		Promise.all(uploadPromises)
				.then(res => {
					setPromiseAllResult(res);
					message.success("文件上传成功");
				})
				.catch(() => message.error("文件上传失败"));
});

useEffect(() => {
		if (promiseAllResult && promiseAllResult.length > 0 && fileList && fileList.length > 0) {
				fileList.forEach(item => {
					const findResult = promiseAllResult.find(file => file.uid === item.uid);
					if (findResult) {
						// @ts-ignore
						item.filePreviewUrl = findResult.filePreviewUrl;
						item.url = findResult.url;
					}
				});
		}
}, [promiseAllResult]);

4.完整代码:

封装文件上传组件:

src/component/Upload/index.tsx:

复制代码
import { forwardRef, useImperativeHandle, useState, useEffect } from "react";
import { Upload, message } from "antd";
import { InboxOutlined } from "@ant-design/icons";
import type { UploadFile, UploadProps } from "antd";
import { getQiniuTokenApi } from "@/api/modules/assetManagement";
import { debounce } from "lodash";
const { Dragger } = Upload;
interface UploadComType {
	maxCount?: number;
	accept?: string[];
	size?: number;
	multiple?: boolean;
}
const UploadCom = forwardRef(
	(
		{
			maxCount = 3,
			accept = [
				".doc",
				".docx",
				".xml",
				"application/msword",
				"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
				"application/pdf",
				"image/png",
				"image/jpeg"
			],
			size = 2,
			multiple = true
		}: UploadComType,
		ref: any
	) => {
		const [fileList, setFileList] = useState<UploadFile[]>([]);
		const [promiseAllResult, setPromiseAllResult] = useState<UploadFile[]>([]);
		useEffect(() => {
			if (promiseAllResult && promiseAllResult.length > 0 && fileList && fileList.length > 0) {
				fileList.forEach(item => {
					const findResult = promiseAllResult.find(file => file.uid === item.uid);
					if (findResult) {
						// @ts-ignore
						item.filePreviewUrl = findResult.filePreviewUrl;
						item.url = findResult.url;
					}
				});
			}
		}, [promiseAllResult]);
		useImperativeHandle(ref, () => ({
			getFileList: () => {
				return fileList;
			},
			parentSetList: (list: UploadFile[]) => {
				setFileList(list);
			}
		}));
		const onRemove = (file: UploadFile) => {
			const list = fileList.filter(item => item.uid !== file.uid);
			setFileList(list);
		};
		const beforeUpload: UploadProps["beforeUpload"] = (file, fileList) => {
			debouncedBeforeUpload(fileList);
			return false;
		};
		const debouncedBeforeUpload = debounce(async fileList => {
			let newFileList = fileList.filter((file: any) => {
				// 上传中的文件不进行校验
				if (file.status === "uploading") return true;

				// 校验文件类型
				const isFileTypeValid = accept.includes(file.type || "");
				if (!isFileTypeValid) {
					message.error(`${file.name} 不是允许的文件类型`);
					return false;
				}
				// 校验文件大小
				const isFileSizeValid = (file.size || 0) <= size * 1024 * 1024;
				if (!isFileSizeValid) {
					message.error(`${file.name} 超过最大文件大小限制 (${size}MB)`);
					return false;
				}
				return true;
			});
			if (newFileList.length === 0) {
				message.warning("没有符合要求的文件可上传");
				return false;
			}

			const res = await getQiniuTokenApi();
			const uploadPromises = newFileList.map(async (file: any) => {
				return new Promise((resolve, reject) => {
					const formData = new FormData();
					formData.append("file", file);
					formData.append("token", res.data?.upToken || "");

					fetch("https://upload.qiniup.com/", {
						method: "POST",
						body: formData
					})
						.then(response => {
							if (response.status === 200) {
								return response
									.json()
									.then(data => {
										// 返回包含文件信息和响应数据的对象
										resolve({
											name: file.name,
											size: file.size,
											uid: file.uid,
											type: file.type,
											status: file.status,
											url: "https://" + res.data?.domain + "/" + data.key + "?attname=" + file.name,
											filePreviewUrl: "https://" + res.data?.domain + "/" + data.key
										});
									})
									.catch(() => {
										reject(new Error("Upload failed"));
									});
							} else {
								reject(new Error("Upload failed"));
							}
						})
						.catch(error => reject(error));
				});
			});
			Promise.all(uploadPromises)
				.then(res => {
					setPromiseAllResult(res);
					message.success("文件上传成功");
				})
				.catch(() => message.error("文件上传失败"));
		});
		const handleChange: UploadProps["onChange"] = ({ fileList }) => {
			let newFileList = fileList;
			if (newFileList.length > maxCount) {
				message.warning(`最多可上传${maxCount}个文件`);
				newFileList = newFileList.slice(-maxCount);
			}
			setFileList(newFileList);
		};

		const uploadProps: UploadProps = {
			name: "file",
			onRemove,
			beforeUpload: beforeUpload,
			multiple: multiple,
			onChange: handleChange,
			accept: accept.join(",")
		};

		return (
			<div>
				<Dragger {...uploadProps} fileList={fileList}>
					<p className="ant-upload-drag-icon">
						<InboxOutlined />
					</p>
					<p className="ant-upload-text">Click or drag file to this area to upload</p>
					<p className="ant-upload-hint">
						Support for a single or bulk upload. Strictly prohibited from uploading company data or other banned files.
					</p>
				</Dragger>
			</div>
		);
	}
);
export default UploadCom;
使用文件上传组件:
复制代码
import UploadCom from "@/components/Upload/index";

const uploadComRef = useRef<any>(null);

<UploadCom ref={uploadComRef} />

//获取组件中的文件
const file = uploadComRef.current?.getFileList();

//给组件中的文件赋初始值
uploadComRef.current?.parentSetList(files);
相关推荐
全栈前端老曹18 小时前
【前端权限】 权限变更热更新
前端·javascript·vue·react·ui框架·权限系统·前端权限
Dragon Wu19 小时前
TanStack Query(React Query) 使用总结
前端·react.js·前端框架·react
Hao_Harrision3 天前
50天50个小项目 (React19 + Tailwindcss V4) ✨| RandomChoicePicker(标签生成)
前端·typescript·react·vite7·tailwildcss
Hao_Harrision3 天前
50天50个小项目 (React19 + Tailwindcss V4) ✨| FAQ Collapse(问题解答折叠面板)
前端·typescript·react·vite7·tailwildcss
前端不太难5 天前
RN 版本升级、第三方库兼容、Android/iOS 崩溃(实战博文 — 从 0.63 升到 0.72)
android·ios·react
前端无涯6 天前
TypeScript 完整学习指南:从基础到工程化实践
typescript·vue·react
至善迎风7 天前
React2Shell(CVE-2025-55182)漏洞服务器排查完整指南
网络安全·react·数据安全·漏洞·next·rsc·cve-2025-55182
打小就很皮...7 天前
前端 Word 导出:自定义页眉表格的实现方案
前端·word·react·页眉设置
相逢一笑与君行8 天前
css使用grid布局实现网格(表格),动态调整行高,列宽,整体缩放,插入行,列,删除行,列
前端·css·react
purpleseashell_Lili8 天前
如何学习 AG-UI 和 CopilotKit
javascript·typescript·react