177 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
			
		
		
	
	
			177 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
| 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 = "点击选择文件上传",
 | ||
|     formValues,
 | ||
|     ...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/")) {
 | ||
|       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;
 | ||
|       };
 | ||
| 
 | ||
|       // 如果有现成的URL则直接使用,否则使用FileReader读取本地文件
 | ||
|       if (file.url) {
 | ||
|         validateImageResolution(file.url);
 | ||
|       }
 | ||
|       else {
 | ||
|         const reader = new FileReader();
 | ||
|         reader.onload = (e) => {
 | ||
|           validateImageResolution(e.target.result);
 | ||
|         };
 | ||
|         reader.readAsDataURL(file);
 | ||
|       }
 | ||
|     }
 | ||
|     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;
 |