import { Button, Modal, Upload, message } from "antd"; import type { UploadRequestOption as RcCustomRequestOptions } from "rc-upload/lib/interface"; import { CloudUploadOutlined, PlusOutlined, UploadOutlined, } from "@ant-design/icons"; import { updateFileList } from "antd/es/upload/utils"; import { FC, useContext, useEffect, useMemo, useRef, useState } from "react"; import { requestByPost, getBaseURL } from "@dage/service"; import { RcFile, UploadFile, UploadProps } from "antd/es/upload"; import { DageFileAPIResponseType, DageUploadProps, DageUploadType, } from "./types"; import { AntdDragger, AntdDraggerText, AntdUpload, UploadContainer, UploadTips, } from "./style"; import { DageUploadContext } from "./context"; import { DageUploadItemActions } from "./ItemActions"; import { getImageSize } from "./utils"; const getBase64 = (file: RcFile): Promise => new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => resolve(reader.result as string); reader.onerror = (error) => reject(error); }); export const DageUpload: FC = ({ action, value, dType = DageUploadType.IMG, maxCount = 9, maxSize = 5, tips, disabled, name = "file", downloadErrorMessage = "下载失败", onChange, ...rest }) => { const context = useContext(DageUploadContext); // 内部维护 mergeFileList // 修复 reat18 受控模式下批量更新导致状态异常 const _mergedFileList = useRef< (UploadFile | Readonly>)[] >([]); const [previewOpen, setPreviewOpen] = useState(false); const [previewImage, setPreviewImage] = useState(""); const [previewTitle, setPreviewTitle] = useState(""); const uploadListType = useMemo(() => { switch (dType) { case DageUploadType.IMG: return "picture-card"; default: return "text"; } }, [dType]); const isPictureCard = uploadListType === "picture-card"; // 可选文件类型 const accept = useMemo(() => { switch (dType) { case DageUploadType.IMG: return ".jpg,.jpeg,.png,.gif"; case DageUploadType.MOBILE_MODEL: return ".zip"; case DageUploadType.MODEL: return ".4dage"; case DageUploadType.VIDEO: return ".mp4"; case DageUploadType.AUDIO: return ".mp3"; default: return "*"; } }, [dType]); useEffect(() => { if (!value) return; _mergedFileList.current = value; }, [value]); const beforeUpload = (file: RcFile) => { let passFileType = false; // 校验文件类型 const fileName = file.name.toLowerCase(); const fileExtension = fileName.substring(fileName.lastIndexOf(".")); passFileType = accept.split(",").includes(fileExtension); if (!passFileType) { message.error(tips || "选择的文件类型不正确!"); return Upload.LIST_IGNORE; } // 校验文件大小 const isLtM = file.size / 1024 / 1024 < maxSize; if (!isLtM) { message.error(`最大支持 ${maxSize}M!`); return Upload.LIST_IGNORE; } context?.handleUploadingFileNum("add"); return true; }; const onUpload = (option: RcCustomRequestOptions) => { const formData = new FormData(); const headers = option.headers || {}; formData.append("type", dType); if (option.file instanceof Blob) { formData.append(name, option.file, (option.file as any).name); } else { formData.append(name, option.file); } option.data && Object.keys(option.data).forEach((key) => { const value = option.data?.[key]; if (Array.isArray(value)) { value.forEach((item) => { formData.append(`${key}[]`, item); }); return; } formData.append(key, value as string | Blob); }); requestByPost(action, formData, { headers, }) .then((data) => { option.onSuccess?.(data); }) .catch((err) => { context?.handleUploadingFileNum("uploaded"); option.onError?.(err); }); }; const handleChange: UploadProps["onChange"] = async ({ fileList: newFileList, file, }) => { if (file.status === "done") { if (typeof file.response !== "object" || !file.response.fileName) { // 上传失败 file.status = "error"; } else if (isPictureCard && file.originFileObj) { // 如果是图片获取图片宽高 try { // @ts-ignore file.imgAttrs = await getImageSize(file.originFileObj); // todo: 缩略图并发上传有时候会丢失 file.thumbUrl = getBaseURL() + file.response.filePath; } catch (err) { // 图片加载失败 file.status = "error"; } } context?.handleUploadingFileNum("uploaded"); } if (_mergedFileList.current.length > newFileList.length) { _mergedFileList.current = newFileList; } else { _mergedFileList.current = updateFileList(file, _mergedFileList.current); } onChange?.( _mergedFileList.current.map((i) => ({ ...i, dType, })), { ...file, dType, } ); }; const handleCancel = () => setPreviewOpen(false); const handlePreview = async (file: UploadFile) => { if (!isPictureCard) return; if (!file.url && !file.preview) { file.preview = await getBase64(file.originFileObj as RcFile); } setPreviewImage(file.url || (file.preview as string)); setPreviewOpen(true); setPreviewTitle( file.name || file.url!.substring(file.url!.lastIndexOf("/") + 1) ); }; const props: UploadProps = { fileList: value, withCredentials: true, customRequest: onUpload, listType: uploadListType, maxCount, accept, disabled, itemRender: (node, file, fileList, actions) => ( {node} ), showUploadList: { showPreviewIcon: false, showDownloadIcon: false, showRemoveIcon: false, }, multiple: maxCount > 1, onPreview: handlePreview, beforeUpload: beforeUpload, onChange: handleChange, ...rest, }; return ( {[DageUploadType.MODEL, DageUploadType.MOBILE_MODEL].includes(dType) ? ( 将文件拖到此处,或点击上传 {tips} ) : ( <> {isPictureCard ? ( !disabled && (!value || value.length < maxCount) && ) : ( )} {!!tips && ( {tips} )} )} example ); }; export * from "./types"; export * from "./context";