zy-react-library/components/Upload/index.js

263 lines
6.6 KiB
JavaScript
Raw Normal View History

import { PlusOutlined, UploadOutlined, VideoCameraAddOutlined } from "@ant-design/icons";
2025-10-22 14:43:42 +08:00
import { Upload as AntUpload, Button, message, Modal } from "antd";
import { useState } from "react";
/**
* 文件上传组件
*/
const Upload = (props) => {
const {
value = [],
onChange,
onPreview,
maxCount: externalMaxCount,
listType: externalListType,
accept: externalAccept,
2025-10-22 14:43:42 +08:00
ratio = "",
showTip = true,
multiple = true,
size: externalSize,
2025-10-22 14:43:42 +08:00
tipContent,
uploadButtonText: externalUploadButtonText,
fileType: externalFileType,
2025-10-22 14:43:42 +08:00
...restProps
} = props;
const [previewVisible, setPreviewVisible] = useState(false);
const [previewImage, setPreviewImage] = useState("");
// 预设的文件格式
const imageAccept = ".jpg,.jpeg,.png";
const documentAccept = ".pdf,.doc,.docx";
const videoAccept = ".mp4";
// 根据accept自动判断文件类型
const getAutoFileType = () => {
if (externalAccept) {
if (externalAccept === "*")
return "document";
const acceptList = externalAccept.split(",");
if (acceptList.some(format => videoAccept.split(",").includes(format)))
return "video";
if (acceptList.some(format => documentAccept.split(",").includes(format)))
return "document";
if (acceptList.some(format => imageAccept.split(",").includes(format)))
return "image";
return "document";
}
return "image";
};
const fileType = externalFileType || getAutoFileType();
// 文件类型判断
const isImageType = fileType === "image";
const isVideoType = fileType === "video";
const isDocumentType = fileType === "document";
// 获取listType
const getListType = () => {
if (externalListType)
return externalListType;
if (externalAccept === "*")
return "text";
if (fileType === "image")
return "picture-card";
return "text";
};
const listType = getListType();
// 获取文件格式
const getAccept = () => {
if (externalAccept)
return externalAccept === "*" ? "" : externalAccept;
if (isImageType)
return imageAccept;
if (isVideoType)
return videoAccept;
if (isDocumentType)
return documentAccept;
return imageAccept;
};
const accept = getAccept();
// 获取默认上传数量
const getMaxCount = () => {
if (externalMaxCount)
return externalMaxCount;
if (isVideoType)
return 1;
if (isImageType)
return 4;
if (isDocumentType)
return 4;
return 1;
};
const maxCount = getMaxCount();
// 获取默认文件大小
const getSize = () => {
if (externalSize)
return externalSize;
if (isVideoType)
return 100;
return 0;
};
const size = getSize();
// 上传按钮文字
const uploadButtonText = externalUploadButtonText || (isVideoType ? "上传视频" : "上传附件");
// 文件格式提示
const acceptTip = accept.replace(/\./g, "").split(",").join("、");
2025-10-22 14:43:42 +08:00
// 生成提示信息
const getTipText = () => {
if (tipContent)
return tipContent;
const tips = [
`最多上传${maxCount}个文件`,
accept
? `并且只能上传${acceptTip}格式的文件`
2025-10-22 14:43:42 +08:00
: "可以上传任意格式的文件",
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(`只能上传${acceptTip}格式的文件`);
2025-10-22 14:43:42 +08:00
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 (isImageType) {
2025-10-22 14:43:42 +08:00
setPreviewImage(file.url || file.thumbUrl);
setPreviewVisible(true);
}
onPreview?.(file);
};
// 关闭预览
const handleCancel = () => {
setPreviewVisible(false);
};
// 上传按钮
const uploadButton
= isImageType
2025-10-22 14:43:42 +08:00
? (
<div>
<PlusOutlined style={{ fontSize: 32 }} />
</div>
)
: (
<Button
type="primary"
icon={isVideoType ? <VideoCameraAddOutlined /> : <UploadOutlined />}
>
{uploadButtonText}
</Button>
2025-10-22 14:43:42 +08:00
);
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
? (getTipText()) && (
2025-10-22 14:43:42 +08:00
<div style={{ marginTop: 10, color: "#ff4d4f" }}>
{getTipText()}
2025-10-22 14:43:42 +08:00
</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;