|
|
@@ -0,0 +1,202 @@
|
|
|
+import React, { useEffect } from 'react';
|
|
|
+import type { Field } from '@formily/core';
|
|
|
+import { connect, mapProps, useField } from '@formily/react';
|
|
|
+import { Upload as AntdUpload, Button } from 'antd';
|
|
|
+import type {
|
|
|
+ UploadChangeParam,
|
|
|
+ UploadProps as AntdUploadProps,
|
|
|
+ DraggerProps as AntdDraggerProps,
|
|
|
+} from 'antd/lib/upload';
|
|
|
+import { InboxOutlined, UploadOutlined } from '@ant-design/icons';
|
|
|
+import { reaction } from '@formily/reactive';
|
|
|
+import type { UploadFile } from 'antd/lib/upload/interface';
|
|
|
+import { isArr, toArr } from '@formily/shared';
|
|
|
+import { UPLOAD_PLACEHOLDER } from './placeholder';
|
|
|
+import { usePrefixCls } from '@formily/antd/lib/__builtins__';
|
|
|
+
|
|
|
+type UploadProps = Omit<AntdUploadProps, 'onChange'> & {
|
|
|
+ textContent?: React.ReactNode;
|
|
|
+ onChange?: (fileList: UploadFile[]) => void;
|
|
|
+ serviceErrorMessage?: string;
|
|
|
+};
|
|
|
+
|
|
|
+type DraggerProps = Omit<AntdDraggerProps, 'onChange'> & {
|
|
|
+ textContent?: React.ReactNode;
|
|
|
+ onChange?: (fileList: UploadFile[]) => void;
|
|
|
+ serviceErrorMessage?: string;
|
|
|
+};
|
|
|
+
|
|
|
+type ComposedUpload = React.FC<UploadProps> & {
|
|
|
+ Dragger?: React.FC<DraggerProps>;
|
|
|
+};
|
|
|
+
|
|
|
+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 += 1) {
|
|
|
+ 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?.['result'] || target?.['url'] || target?.['downloadURL'] || target?.['imgURL'];
|
|
|
+};
|
|
|
+const getThumbURL = (target: any) => {
|
|
|
+ return (
|
|
|
+ target?.['result'] ||
|
|
|
+ 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<Field>();
|
|
|
+ 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') => {
|
|
|
+ // eslint-disable-next-line consistent-return
|
|
|
+ useValidator((value) => {
|
|
|
+ const list = toArr(value);
|
|
|
+ for (let i = 0; i < list.length; i += 1) {
|
|
|
+ if (list[i]?.status === 'error') {
|
|
|
+ return (
|
|
|
+ getErrorMessage(list[i]?.response) || getErrorMessage(list[i]) || serviceErrorMessage
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+function useUploadProps<T extends IUploadProps = UploadProps>({
|
|
|
+ serviceErrorMessage,
|
|
|
+ ...props
|
|
|
+}: T) {
|
|
|
+ useUploadValidator(serviceErrorMessage);
|
|
|
+ const onChange = (param: UploadChangeParam<UploadFile>) => {
|
|
|
+ props.onChange?.(normalizeFileList([...param.fileList]));
|
|
|
+ };
|
|
|
+ return {
|
|
|
+ ...props,
|
|
|
+ onChange,
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+const getPlaceholder = (props: UploadProps) => {
|
|
|
+ if (props.listType !== 'picture-card') {
|
|
|
+ return (
|
|
|
+ <Button>
|
|
|
+ <UploadOutlined />
|
|
|
+ {props.textContent}
|
|
|
+ </Button>
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return <UploadOutlined style={{ fontSize: 20 }} />;
|
|
|
+};
|
|
|
+
|
|
|
+export const Upload: ComposedUpload = connect(
|
|
|
+ (props: React.PropsWithChildren<UploadProps>) => {
|
|
|
+ return (
|
|
|
+ <AntdUpload {...useUploadProps(props)}>{props.children || getPlaceholder(props)}</AntdUpload>
|
|
|
+ );
|
|
|
+ },
|
|
|
+ mapProps({
|
|
|
+ value: 'fileList',
|
|
|
+ }),
|
|
|
+);
|
|
|
+
|
|
|
+const Dragger = connect(
|
|
|
+ (props: React.PropsWithChildren<DraggerProps>) => {
|
|
|
+ return (
|
|
|
+ <div className={usePrefixCls('upload-dragger')}>
|
|
|
+ <AntdUpload.Dragger {...useUploadProps(props)}>
|
|
|
+ {props.children || (
|
|
|
+ <React.Fragment>
|
|
|
+ <p className="ant-upload-drag-icon">
|
|
|
+ <InboxOutlined />
|
|
|
+ </p>
|
|
|
+ {props.textContent && <p className="ant-upload-text">{props.textContent}</p>}
|
|
|
+ </React.Fragment>
|
|
|
+ )}
|
|
|
+ </AntdUpload.Dragger>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ },
|
|
|
+ mapProps({
|
|
|
+ value: 'fileList',
|
|
|
+ }),
|
|
|
+);
|
|
|
+
|
|
|
+Upload.Dragger = Dragger;
|
|
|
+
|
|
|
+export default Upload;
|