import React, { useEffect } from 'react'; import { connect, mapProps, mapReadPretty, observer, useField, } from '@formily/react'; import { Button, Progress, Space, Upload as AntdUpload } from 'antd'; import { UploadChangeParam, UploadProps as AntdUploadProps, DraggerProps as AntdDraggerProps, } from 'antd/lib/upload'; import { reaction } from '@formily/reactive'; import { UploadFile } from 'antd/lib/upload/interface'; import { isArr, toArr as toArray, isValid } from '@formily/shared'; import { UPLOAD_PLACEHOLDER } from './placeholder'; import { usePrefixCls } from '@formily/antd/esm/__builtins__'; import { useState } from 'react'; import Lightbox from 'react-image-lightbox'; import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app import { useMap } from 'ahooks'; import DeleteOutlined from '@ant-design/icons/DeleteOutlined'; import UploadOutlined from '@ant-design/icons/UploadOutlined'; import PlusOutlined from '@ant-design/icons/PlusOutlined'; import DownloadOutlined from '@ant-design/icons/DownloadOutlined'; import './style.less'; import cls from 'classnames'; import { saveAs } from 'file-saver'; import { useTranslation } from 'react-i18next'; const toArr = (value) => { if (!isValid(value)) { return []; } if (Object.keys(value).length === 0) { return []; } return toArray(value); }; type UploadProps = Omit & { onChange?: (fileList: UploadFile[]) => void; serviceErrorMessage?: string; value?: any; }; type DraggerProps = Omit & { onChange?: (fileList: UploadFile[]) => void; serviceErrorMessage?: string; }; type ComposedUpload = React.FC & { Dragger?: React.FC; File?: React.FC; Attachment?: React.FC; }; type IUploadProps = { serviceErrorMessage?: string; onChange?: (...args: any) => void; }; const testOpts = ( ext: RegExp, options: { exclude?: string[]; include?: string[] }, ) => { if (options && isArr(options.include)) { return options.include.some((url) => ext.test(url)); } if (options && isArr(options.exclude)) { return !options.exclude.some((url) => ext.test(url)); } return true; }; const getImageByUrl = (url: string, options: any) => { for (let i = 0; i < UPLOAD_PLACEHOLDER.length; i++) { if ( UPLOAD_PLACEHOLDER[i].ext.test(url) && testOpts(UPLOAD_PLACEHOLDER[i].ext, options) ) { return UPLOAD_PLACEHOLDER[i].icon || url; } } return url; }; const getURL = (target: any) => { return target?.['url'] || target?.['downloadURL'] || target?.['imgURL']; }; const getThumbURL = (target: any) => { return ( target?.['thumbUrl'] || target?.['url'] || target?.['downloadURL'] || target?.['imgURL'] ); }; const getErrorMessage = (target: any) => { return target?.errorMessage || target?.errMsg || target?.errorMsg || target?.message || typeof target?.error === 'string' ? target.error : ''; }; const getState = (target: any) => { if (target?.success === false) return 'error'; if (target?.failed === true) return 'error'; if (target?.error) return 'error'; return target?.state || target?.status; }; const normalizeFileList = (fileList: UploadFile[]) => { if (fileList && fileList.length) { return fileList.map((file, index) => { return { ...file, uid: file.uid || `${index}`, status: getState(file.response) || getState(file), url: getURL(file) || getURL(file?.response), thumbUrl: getImageByUrl( getThumbURL(file) || getThumbURL(file?.response), { exclude: ['.png', '.jpg', '.jpeg', '.gif'], }, ), }; }); } return []; }; const useValidator = (validator: (value: any) => string) => { const field = useField(); useEffect(() => { const dispose = reaction( () => field.value, (value) => { const message = validator(value); field.setFeedback({ type: 'error', code: 'UploadError', messages: message ? [message] : [], }); }, ); return () => { dispose(); }; }, []); }; const useUploadValidator = (serviceErrorMessage = 'Upload Service Error') => { useValidator((value) => { const list = toArr(value); for (let i = 0; i < list.length; i++) { if (list[i]?.status === 'error') { return ( getErrorMessage(list[i]?.response) || getErrorMessage(list[i]) || serviceErrorMessage ); } } }); }; function useUploadProps({ serviceErrorMessage, ...props }: T) { useUploadValidator(serviceErrorMessage); const onChange = (param: UploadChangeParam) => { props.onChange?.(normalizeFileList([...param.fileList])); }; return { ...props, onChange, }; } export const Upload: ComposedUpload = connect( (props: UploadProps) => { return ; }, mapProps({ value: 'fileList', }), mapReadPretty((props) => { const field = useField(); console.log('field.value', field.value); return (field.value || []).map((item) => (
{item.url ? ( {item.name} ) : ( {item.name} )}
)); }), ); const Dragger = connect( (props: DraggerProps) => { return (
); }, mapProps({ value: 'fileList', }), ); function toItem(file) { if (file?.response?.data) { file = file.response.data; } return { ...file, id: file.id || file.uid, title: file.title || file.name, imageUrl: getImageByUrl(file.url, { exclude: ['.png', '.jpg', '.jpeg', '.gif'], }), }; } function toFileList(fileList: any) { return toArr(fileList).map(toItem); } function toValue(fileList: any) { return toArr(fileList) .filter((file) => !file.response || file.status === 'done') .map((file) => file?.response?.data || file); } function toMap(fileList: any) { if (!fileList) { return []; } if (typeof fileList !== 'object') { return []; } let list = fileList; if (!Array.isArray(fileList)) { if (Object.keys({ ...fileList }).length === 0) { return []; } list = [fileList]; } console.log({ list, fileList }); return list.map((item) => { return [item.id || item.uid, toItem(item)]; }); } const toImages = (fileList) => { if (!fileList) { return []; } if (typeof fileList !== 'object') { return []; } if (Object.keys(fileList).length === 0) { return []; } let list = fileList; if (!Array.isArray(fileList) && typeof fileList === 'object') { list = [fileList]; } return list.map((item) => { return { ...item, title: item.title || item.name, imageUrl: getImageByUrl(item.url, { exclude: ['.png', '.jpg', '.jpeg', '.gif'], }), }; }); }; Upload.Attachment = connect( (props: UploadProps) => { const { disabled, multiple, value, onChange } = props; const [fileList, setFileList] = useState([]); const [sync, setSync] = useState(true); const images = fileList; const [photoIndex, setPhotoIndex] = useState(0); const [visible, setVisible] = useState(false); const { t } = useTranslation(); useEffect(() => { if (sync) { setFileList(toFileList(value)); } }, [value, sync]); return (
{fileList.map((file) => { const handleClick = (e) => { e.preventDefault(); const index = fileList.indexOf(file); setVisible(true); setPhotoIndex(index); }; return (
); })}
{ setSync(false); if (multiple) { if (info.file.status === 'done') { onChange(toValue(info.fileList)); } setFileList(info.fileList.map(toItem)); } else { if (info.file.status === 'done') { console.log('field.value', info.file?.response?.data); // TODO(BUG): object 的联动有问题,不响应,折中的办法先置空再赋值 onChange(null); onChange(info.file?.response?.data); } setFileList([toItem(info.file)]); } }} showUploadList={false} > {!disabled && (multiple || toArr(value).length < 1) && (
{t('Upload')}
)}
{visible && ( setVisible(false)} onMovePrevRequest={() => setPhotoIndex((photoIndex + images.length - 1) % images.length) } onMoveNextRequest={() => setPhotoIndex((photoIndex + 1) % images.length) } imageTitle={images[photoIndex]?.title} toolbarButtons={[ , ]} /> )}
); }, mapReadPretty((props) => { const field = useField(); const images = toImages(toArr(field.value)); const [photoIndex, setPhotoIndex] = useState(0); const [visible, setVisible] = useState(false); const { size } = props; console.log('field.value', field.value, images); return (
{images.map((file) => { const handleClick = (e) => { e.preventDefault(); e.stopPropagation(); const index = images.indexOf(file); setVisible(true); setPhotoIndex(index); }; return (
{size !== 'small' && (
); })}
{visible && ( { e.preventDefault(); e.stopPropagation(); setVisible(false); }} onMovePrevRequest={() => setPhotoIndex((photoIndex + images.length - 1) % images.length) } onMoveNextRequest={() => setPhotoIndex((photoIndex + 1) % images.length) } imageTitle={images[photoIndex]?.title} toolbarButtons={[ , ]} /> )}
); }), ); Upload.Dragger = Dragger; export default Upload;