2025-10-22 14:43:42 +08:00
|
|
|
|
import { PlusOutlined } from "@ant-design/icons";
|
|
|
|
|
|
import { Upload as AntUpload, Button, message, Modal } from "antd";
|
|
|
|
|
|
import { useState } from "react";
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 文件上传组件
|
|
|
|
|
|
*/
|
|
|
|
|
|
const Upload = (props) => {
|
|
|
|
|
|
const {
|
|
|
|
|
|
value = [],
|
|
|
|
|
|
onChange,
|
|
|
|
|
|
onPreview,
|
|
|
|
|
|
maxCount = 1,
|
|
|
|
|
|
listType = "text",
|
|
|
|
|
|
accept = "",
|
|
|
|
|
|
ratio = "",
|
|
|
|
|
|
showTip = true,
|
|
|
|
|
|
multiple = true,
|
|
|
|
|
|
size = 0,
|
|
|
|
|
|
tipContent,
|
|
|
|
|
|
uploadButtonText = "点击选择文件上传",
|
2025-10-29 16:26:08 +08:00
|
|
|
|
formValues,
|
2025-10-22 14:43:42 +08:00
|
|
|
|
...restProps
|
|
|
|
|
|
} = props;
|
|
|
|
|
|
|
|
|
|
|
|
const [previewVisible, setPreviewVisible] = useState(false);
|
|
|
|
|
|
const [previewImage, setPreviewImage] = useState("");
|
|
|
|
|
|
|
|
|
|
|
|
// 生成提示信息
|
|
|
|
|
|
const getTipText = () => {
|
|
|
|
|
|
if (tipContent)
|
|
|
|
|
|
return tipContent;
|
|
|
|
|
|
|
|
|
|
|
|
const tips = [
|
|
|
|
|
|
`最多上传${maxCount}个文件`,
|
|
|
|
|
|
accept
|
|
|
|
|
|
? `并且只能上传${accept
|
|
|
|
|
|
.replace(/\./g, "")
|
|
|
|
|
|
.split(",")
|
|
|
|
|
|
.join("、")}格式的文件`
|
|
|
|
|
|
: "可以上传任意格式的文件",
|
|
|
|
|
|
size ? `文件大小不能超过${size}M` : "",
|
|
|
|
|
|
ratio ? `只能上传${ratio}分辨率的图片` : "",
|
|
|
|
|
|
].filter(Boolean);
|
|
|
|
|
|
|
|
|
|
|
|
return `${tips.join(",")}。`;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleBeforeUpload = () => {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 文件状态改变
|
|
|
|
|
|
const handleChange = ({ file, fileList }) => {
|
|
|
|
|
|
const acceptList = accept ? accept.split(",") : [];
|
|
|
|
|
|
const ratioArr = ratio ? ratio.split("*") : [];
|
|
|
|
|
|
const suffix = file.name.substring(
|
|
|
|
|
|
file.name.lastIndexOf("."),
|
|
|
|
|
|
file.name.length,
|
|
|
|
|
|
);
|
|
|
|
|
|
const maxSize = size * 1024 * 1024;
|
|
|
|
|
|
|
|
|
|
|
|
// 验证文件格式
|
|
|
|
|
|
if (acceptList.length > 0 && !acceptList.includes(suffix)) {
|
|
|
|
|
|
message.warning(`只能上传${accept}格式的文件`);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证文件大小
|
|
|
|
|
|
if (maxSize && file.size > maxSize) {
|
|
|
|
|
|
message.warning(`文件大小不能超过${size}M`);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证图片分辨率
|
|
|
|
|
|
if (ratioArr.length === 2 && file.type?.startsWith("image/")) {
|
2025-10-28 14:08:33 +08:00
|
|
|
|
const validateImageResolution = (imageUrl) => {
|
|
|
|
|
|
const img = new Image();
|
|
|
|
|
|
img.onload = () => {
|
|
|
|
|
|
if (img.width !== +ratioArr[0] || img.height !== +ratioArr[1]) {
|
|
|
|
|
|
message.warning(`只能上传${ratio}分辨率的图片`);
|
|
|
|
|
|
const filtered = fileList.filter(item => item.uid !== file.uid);
|
|
|
|
|
|
onChange?.(filtered);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
onChange?.(fileList);
|
|
|
|
|
|
};
|
|
|
|
|
|
img.src = imageUrl;
|
2025-10-22 14:43:42 +08:00
|
|
|
|
};
|
2025-10-28 14:08:33 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果有现成的URL则直接使用,否则使用FileReader读取本地文件
|
|
|
|
|
|
if (file.url) {
|
|
|
|
|
|
validateImageResolution(file.url);
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
const reader = new FileReader();
|
|
|
|
|
|
reader.onload = (e) => {
|
|
|
|
|
|
validateImageResolution(e.target.result);
|
|
|
|
|
|
};
|
|
|
|
|
|
reader.readAsDataURL(file);
|
|
|
|
|
|
}
|
2025-10-22 14:43:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
onChange?.(fileList);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 预览文件
|
|
|
|
|
|
const handlePreview = (file) => {
|
|
|
|
|
|
if (["picture-card", "picture-circle", "picture"].includes(listType)) {
|
|
|
|
|
|
setPreviewImage(file.url || file.thumbUrl);
|
|
|
|
|
|
setPreviewVisible(true);
|
|
|
|
|
|
}
|
|
|
|
|
|
onPreview?.(file);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 关闭预览
|
|
|
|
|
|
const handleCancel = () => {
|
|
|
|
|
|
setPreviewVisible(false);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 上传按钮
|
|
|
|
|
|
const uploadButton
|
|
|
|
|
|
= ["picture-card", "picture-circle", "picture"].includes(listType)
|
|
|
|
|
|
? (
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<PlusOutlined style={{ fontSize: 32 }} />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
: (
|
|
|
|
|
|
<Button type="primary">{uploadButtonText}</Button>
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<AntUpload
|
|
|
|
|
|
fileList={value}
|
|
|
|
|
|
multiple={multiple}
|
|
|
|
|
|
maxCount={maxCount}
|
|
|
|
|
|
listType={listType}
|
|
|
|
|
|
accept={accept}
|
|
|
|
|
|
onChange={handleChange}
|
|
|
|
|
|
onPreview={handlePreview}
|
|
|
|
|
|
beforeUpload={handleBeforeUpload}
|
|
|
|
|
|
{...restProps}
|
|
|
|
|
|
>
|
|
|
|
|
|
{value.length >= maxCount ? null : uploadButton}
|
|
|
|
|
|
</AntUpload>
|
|
|
|
|
|
{
|
|
|
|
|
|
showTip
|
|
|
|
|
|
? (tipContent || getTipText()) && (
|
|
|
|
|
|
<div style={{ marginTop: 10, color: "#ff4d4f" }}>
|
|
|
|
|
|
{tipContent || getTipText()}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
: null
|
|
|
|
|
|
}
|
|
|
|
|
|
<Modal
|
|
|
|
|
|
open={previewVisible}
|
|
|
|
|
|
title="查看图片"
|
|
|
|
|
|
footer={null}
|
|
|
|
|
|
onCancel={handleCancel}
|
|
|
|
|
|
>
|
|
|
|
|
|
<img
|
|
|
|
|
|
alt="preview"
|
|
|
|
|
|
style={{ width: "100%", objectFit: "scale-down" }}
|
|
|
|
|
|
src={previewImage}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Modal>
|
|
|
|
|
|
</>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Upload.displayName = "Upload";
|
|
|
|
|
|
|
|
|
|
|
|
export default Upload;
|