import React, { useState, useCallback, useRef, CSSProperties, useEffect, forwardRef, useImperativeHandle, HTMLAttributes, ReactNode } from 'react' import cns from 'classnames' import { usePrefix } from '../ConfigProvider' import { concat, getId } from '../utils' import { UploadData, UploadFileData, UploadFunction, UploadStatus } from './types' import { FileItem } from './FileItem' import { useForceUpdate, useUnstableEvent } from '../utils/hooks' import { isUploadRealFile } from './common' import { WithRef } from '../utils/type' export interface BaseUploadProps { /** * 文件被上传时调用的方法 * @param file 需要上传的文件的数据 * @param callbacks 文件上传过程中的回调,onProgress用来更新文件上传进度,onError用来告诉组件上传失败和失败的原因,onComplete用来告诉组件上传成功 * @returns 停止文件上传的方法,可以不传,不传时组件上没有停止上传的按钮 */ onUpload: UploadFunction /** * 展示文件列表 * @default true */ showFileList?: boolean /** * 文件列表,可以通过 getUploadValue 快速构造列表数据 */ value: UploadData[] /** * 文件列表或文件状态发生变更时的回调 */ onChange: (files: UploadData[]) => void /** * 可以上传的文件类型,参考:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept * @default '' */ accept?: string /** * 持否支持多选文件 * @default false */ multiple?: boolean /** 自定义样式名 */ className?: string /** 自定义样式 */ style?: CSSProperties /** * 禁用组件 * @default false */ disabled?: boolean /** 根元素的 html 属性 */ rootHtmlProps?: WithRef, HTMLDivElement> /** 展示文件的 html 属性 */ fileHtmlProps?: WithRef, HTMLDivElement> children?: ReactNode } export type BaseUploadRef = { handleClick: () => void uploadFiles: (files: File[] | null) => Promise } // eslint-disable-next-line react/display-name export const BaseUpload = forwardRef((props, ref) => { const { onUpload, accept = '', children, rootHtmlProps, className, style, disabled = false, multiple = false, value: uploadList, onChange, showFileList = true, fileHtmlProps } = props const uploadHandler = useUnstableEvent(onUpload) const uploadRef = useRef(null) const batchStatusRef = useRef(new Map()) const prefix = usePrefix('upload') const listPrefix = concat(prefix, 'list') const classname = cns(prefix, className, rootHtmlProps?.className, disabled && concat(prefix, 'disabled')) const setUploadList = useUnstableEvent(onChange) const forceUpdate = useForceUpdate() const uploadFiles = useCallback(async (files: File[] | null) => { if (!files || files.length === 0) return const list: UploadFileData[] = files.map(file => ({ id: getId(), file, name: file.name, status: 'ready' })) const innerList = multiple ? list : list.slice(0, 1) if (multiple) { setUploadList([...innerList, ...uploadList]) } else { setUploadList(innerList) } }, [multiple, setUploadList, uploadList]) const [inputKey, setInputKey] = useState(getId) const onSelectFiles = useCallback((e: React.ChangeEvent) => { const files = e.target.files uploadFiles(files ? Array.from(files) : null) setInputKey(getId()) }, [uploadFiles]) const handleClick = useCallback(() => { if (disabled) return uploadRef.current?.click() }, [disabled]) const onDeleteFile = useCallback((id: string) => { setUploadList(uploadList.filter(file => file.id !== id)) }, [setUploadList, uploadList]) const handleFileStatusChange = useUnstableEvent( (id: string, status: UploadStatus) => { const index = uploadList.findIndex(f => f.id === id) if (index !== -1) { if (batchStatusRef.current.size === 0) forceUpdate() const data = uploadList[index] batchStatusRef.current.set(index, { ...data, status }) } } ) useImperativeHandle(ref, () => ({ handleClick, uploadFiles })) useEffect(() => { if (batchStatusRef.current.size > 0) { const newList = [...uploadList] for (const [index, data] of batchStatusRef.current) { newList.splice(index, 1, data) } setUploadList(newList) batchStatusRef.current.clear() } }) return (
{/* uploadButton */} {children} {/* FileList */} {uploadList.length > 0 && (
{uploadList .map(item => )}
)}
) })