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);
相关推荐
西瓜太郎12341 天前
外层模型卡片与分组详情成功率不一致,应该怎么排查?
前端·typescript·go·软件工程·react
星辰徐哥1 天前
本地视频预览别只自己看:把Remotion动效项目发给客户远程验收
docker·ai·node.js·html·音视频·react·remotion
知兀3 天前
【前端】受控和非受控组件
前端·javascript·react
醉颜凉4 天前
React触摸事件全解析:掌握移动端交互开发的核心秘籍
react·前端开发·触摸事件·移动端交互·react事件
递归尽头是星辰4 天前
Java 智能体工程化:从 Spring AI 出发读懂 AgentScope
react·智能体·springai·javaai·agentscope
Richown7 天前
React 高级模式:并发渲染下的状态机驱动架构——从 Finite State Machine 到生产级实现
区块链·react
LayZhangStrive8 天前
Agent开发 - 实现人类与Manus智能体的终端窗口命令交互
ai·交互·agent·react·终端·manus
名字还没想好☜9 天前
React 实现暗黑模式切换:localStorage 持久化、SSR 首屏闪烁与跟随系统主题
前端·javascript·react.js·ecmascript·react·next.js
ZGG00314 天前
真题拆解 05:ReAct 还是 Plan-and-Execute——规划范式怎么选
python·agent·react
eokred16 天前
图片预览组件的另一种设计:Headless、可组合、开箱即用
前端·react