add upload component into schema components (#165)
* feat: add Upload component into schema components * docx file * upload with customRequest * action * optimize: remove downloadFile instead of saveAs * fix: direct download of non-image files Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
		
							parent
							
								
									5e7e3c2c46
								
							
						
					
					
						commit
						be1192531c
					
				| @ -25,6 +25,7 @@ | |||||||
|     "@formily/react": "^2.0.7", |     "@formily/react": "^2.0.7", | ||||||
|     "ahooks": "^3.0.5", |     "ahooks": "^3.0.5", | ||||||
|     "axios": "^0.24.0", |     "axios": "^0.24.0", | ||||||
|  |     "file-saver": "^2.0.5", | ||||||
|     "i18next": "^21.6.0", |     "i18next": "^21.6.0", | ||||||
|     "react-helmet": "^6.1.0", |     "react-helmet": "^6.1.0", | ||||||
|     "react-i18next": "^11.15.1", |     "react-i18next": "^11.15.1", | ||||||
|  | |||||||
| @ -16,4 +16,4 @@ export * from './page'; | |||||||
| export * from './password'; | export * from './password'; | ||||||
| export * from './radio'; | export * from './radio'; | ||||||
| export * from './time-picker'; | export * from './time-picker'; | ||||||
| 
 | export * from './upload'; | ||||||
|  | |||||||
							
								
								
									
										147
									
								
								packages/client/src/schema-component/antd/upload/ReadPretty.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								packages/client/src/schema-component/antd/upload/ReadPretty.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,147 @@ | |||||||
|  | import DownloadOutlined from '@ant-design/icons/DownloadOutlined'; | ||||||
|  | import { Field } from '@formily/core'; | ||||||
|  | import { useField } from '@formily/react'; | ||||||
|  | import { Button, Space } from 'antd'; | ||||||
|  | import cls from 'classnames'; | ||||||
|  | import { saveAs } from 'file-saver'; | ||||||
|  | import React, { useState } from 'react'; | ||||||
|  | import Lightbox from 'react-image-lightbox'; | ||||||
|  | import { isImage, toArr, toImages } from './shared'; | ||||||
|  | import type { UploadProps } from './type'; | ||||||
|  | 
 | ||||||
|  | type Composed = React.FC<UploadProps> & { | ||||||
|  |   Upload?: React.FC<UploadProps>; | ||||||
|  |   Attachment?: React.FC<UploadProps>; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const ReadPretty: Composed = () => null; | ||||||
|  | 
 | ||||||
|  | ReadPretty.Attachment = (props: UploadProps) => { | ||||||
|  |   const field = useField<Field>(); | ||||||
|  |   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 ( | ||||||
|  |     <div> | ||||||
|  |       <div className={cls('ant-upload-picture-card-wrapper nb-upload', size ? `nb-upload-${size}` : null)}> | ||||||
|  |         <div className={'ant-upload-list ant-upload-list-picture-card'}> | ||||||
|  |           {images.map((file) => { | ||||||
|  |             const handleClick = (e) => { | ||||||
|  |               e.preventDefault(); | ||||||
|  |               e.stopPropagation(); | ||||||
|  |               const index = images.indexOf(file); | ||||||
|  |               if (isImage(file.extname)) { | ||||||
|  |                 setVisible(true); | ||||||
|  |                 setPhotoIndex(index); | ||||||
|  |               } else { | ||||||
|  |                 saveAs(file.url, `${file.title}${file.extname}`); | ||||||
|  |               } | ||||||
|  |             }; | ||||||
|  |             return ( | ||||||
|  |               <div className={'ant-upload-list-picture-card-container'}> | ||||||
|  |                 <div className="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-picture-card"> | ||||||
|  |                   <div className={'ant-upload-list-item-info'}> | ||||||
|  |                     <span className="ant-upload-span"> | ||||||
|  |                       <a | ||||||
|  |                         className="ant-upload-list-item-thumbnail" | ||||||
|  |                         href={file.url} | ||||||
|  |                         target="_blank" | ||||||
|  |                         rel="noopener noreferrer" | ||||||
|  |                         onClick={handleClick} | ||||||
|  |                       > | ||||||
|  |                         {file.imageUrl && ( | ||||||
|  |                           <img | ||||||
|  |                             src={`${file.imageUrl}?x-oss-process=style/thumbnail`} | ||||||
|  |                             alt={file.title} | ||||||
|  |                             className="ant-upload-list-item-image" | ||||||
|  |                           /> | ||||||
|  |                         )} | ||||||
|  |                       </a> | ||||||
|  |                       <a | ||||||
|  |                         target="_blank" | ||||||
|  |                         rel="noopener noreferrer" | ||||||
|  |                         className="ant-upload-list-item-name" | ||||||
|  |                         title={file.title} | ||||||
|  |                         href={file.url} | ||||||
|  |                         onClick={handleClick} | ||||||
|  |                       > | ||||||
|  |                         {file.title} | ||||||
|  |                       </a> | ||||||
|  |                     </span> | ||||||
|  |                   </div> | ||||||
|  |                   {size !== 'small' && ( | ||||||
|  |                     <span className={'ant-upload-list-item-actions'}> | ||||||
|  |                       <Space size={3}> | ||||||
|  |                         <Button | ||||||
|  |                           size={'small'} | ||||||
|  |                           type={'text'} | ||||||
|  |                           icon={<DownloadOutlined />} | ||||||
|  |                           onClick={(e) => { | ||||||
|  |                             e.preventDefault(); | ||||||
|  |                             e.stopPropagation(); | ||||||
|  |                             saveAs(file.url, `${file.title}${file.extname}`); | ||||||
|  |                           }} | ||||||
|  |                         /> | ||||||
|  |                       </Space> | ||||||
|  |                     </span> | ||||||
|  |                   )} | ||||||
|  |                 </div> | ||||||
|  |               </div> | ||||||
|  |             ); | ||||||
|  |           })} | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       {visible && ( | ||||||
|  |         <Lightbox | ||||||
|  |           // discourageDownloads={true}
 | ||||||
|  |           mainSrc={images[photoIndex]?.imageUrl} | ||||||
|  |           nextSrc={images[(photoIndex + 1) % images.length]?.imageUrl} | ||||||
|  |           prevSrc={images[(photoIndex + images.length - 1) % images.length]?.imageUrl} | ||||||
|  |           // @ts-ignore
 | ||||||
|  |           onCloseRequest={(e) => { | ||||||
|  |             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={[ | ||||||
|  |             <button | ||||||
|  |               style={{ fontSize: 22, background: 'none', lineHeight: 1 }} | ||||||
|  |               type="button" | ||||||
|  |               aria-label="Zoom in" | ||||||
|  |               title="Zoom in" | ||||||
|  |               className="ril-zoom-in ril__toolbarItemChild ril__builtinButton" | ||||||
|  |               onClick={(e) => { | ||||||
|  |                 e.preventDefault(); | ||||||
|  |                 e.stopPropagation(); | ||||||
|  |                 const file = images[photoIndex]; | ||||||
|  |                 saveAs(file.url, `${file.title}${file.extname}`); | ||||||
|  |               }} | ||||||
|  |             > | ||||||
|  |               <DownloadOutlined /> | ||||||
|  |             </button>, | ||||||
|  |           ]} | ||||||
|  |         /> | ||||||
|  |       )} | ||||||
|  |     </div> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | ReadPretty.Upload = (props) => { | ||||||
|  |   const field = useField<Field>(); | ||||||
|  |   return (field.value || []).map((item) => ( | ||||||
|  |     <div> | ||||||
|  |       {item.url ? ( | ||||||
|  |         <a target={'_blank'} href={item.url}> | ||||||
|  |           {item.name} | ||||||
|  |         </a> | ||||||
|  |       ) : ( | ||||||
|  |         <span>{item.name}</span> | ||||||
|  |       )} | ||||||
|  |     </div> | ||||||
|  |   )); | ||||||
|  | }; | ||||||
							
								
								
									
										207
									
								
								packages/client/src/schema-component/antd/upload/Upload.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										207
									
								
								packages/client/src/schema-component/antd/upload/Upload.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,207 @@ | |||||||
|  | import DeleteOutlined from '@ant-design/icons/DeleteOutlined'; | ||||||
|  | import DownloadOutlined from '@ant-design/icons/DownloadOutlined'; | ||||||
|  | import PlusOutlined from '@ant-design/icons/PlusOutlined'; | ||||||
|  | import { usePrefixCls } from '@formily/antd/esm/__builtins__'; | ||||||
|  | import { connect, mapProps, mapReadPretty } from '@formily/react'; | ||||||
|  | import { Button, Progress, Space, Upload as AntdUpload } from 'antd'; | ||||||
|  | import cls from 'classnames'; | ||||||
|  | import { saveAs } from 'file-saver'; | ||||||
|  | import React, { useEffect, useState } from 'react'; | ||||||
|  | import { useTranslation } from 'react-i18next'; | ||||||
|  | import Lightbox from 'react-image-lightbox'; | ||||||
|  | import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
 | ||||||
|  | import { ReadPretty } from './ReadPretty'; | ||||||
|  | import { isImage, toArr, toFileList, toItem, toValue, useUploadProps } from './shared'; | ||||||
|  | import './style.less'; | ||||||
|  | import type { ComposedUpload, DraggerProps, UploadProps } from './type'; | ||||||
|  | 
 | ||||||
|  | export const Upload: ComposedUpload = connect( | ||||||
|  |   (props: UploadProps) => { | ||||||
|  |     return <AntdUpload {...useUploadProps(props)} />; | ||||||
|  |   }, | ||||||
|  |   mapProps({ | ||||||
|  |     value: 'fileList', | ||||||
|  |   }), | ||||||
|  |   mapReadPretty(ReadPretty.Upload), | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | 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 ( | ||||||
|  |     <div> | ||||||
|  |       <div className={cls('ant-upload-picture-card-wrapper nb-upload')}> | ||||||
|  |         <div className={'ant-upload-list ant-upload-list-picture-card'}> | ||||||
|  |           {fileList.map((file) => { | ||||||
|  |             const handleClick = (e) => { | ||||||
|  |               e.preventDefault(); | ||||||
|  |               e.stopPropagation(); | ||||||
|  |               const index = fileList.indexOf(file); | ||||||
|  |               if (isImage(file.extname)) { | ||||||
|  |                 setVisible(true); | ||||||
|  |                 setPhotoIndex(index); | ||||||
|  |               } else { | ||||||
|  |                 saveAs(file.url, `${file.title}${file.extname}`); | ||||||
|  |               } | ||||||
|  |             }; | ||||||
|  |             return ( | ||||||
|  |               <div className={'ant-upload-list-picture-card-container'}> | ||||||
|  |                 <div className="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-picture-card"> | ||||||
|  |                   <div className={'ant-upload-list-item-info'}> | ||||||
|  |                     <span className="ant-upload-span"> | ||||||
|  |                       <a | ||||||
|  |                         className="ant-upload-list-item-thumbnail" | ||||||
|  |                         href={file.url} | ||||||
|  |                         target="_blank" | ||||||
|  |                         rel="noopener noreferrer" | ||||||
|  |                         onClick={handleClick} | ||||||
|  |                       > | ||||||
|  |                         {file.imageUrl && ( | ||||||
|  |                           <img src={file.imageUrl} alt={file.title} className="ant-upload-list-item-image" /> | ||||||
|  |                         )} | ||||||
|  |                       </a> | ||||||
|  |                       <a | ||||||
|  |                         target="_blank" | ||||||
|  |                         rel="noopener noreferrer" | ||||||
|  |                         className="ant-upload-list-item-name" | ||||||
|  |                         title={file.title} | ||||||
|  |                         href={file.url} | ||||||
|  |                         onClick={handleClick} | ||||||
|  |                       > | ||||||
|  |                         {file.status === 'uploading' ? t('Uploading') : file.title} | ||||||
|  |                       </a> | ||||||
|  |                     </span> | ||||||
|  |                   </div> | ||||||
|  |                   <span className={'ant-upload-list-item-actions'}> | ||||||
|  |                     <Space size={3}> | ||||||
|  |                       <Button | ||||||
|  |                         size={'small'} | ||||||
|  |                         type={'text'} | ||||||
|  |                         icon={<DownloadOutlined />} | ||||||
|  |                         onClick={() => { | ||||||
|  |                           saveAs(file.url, `${file.title}${file.extname}`); | ||||||
|  |                         }} | ||||||
|  |                       /> | ||||||
|  |                       {!disabled && ( | ||||||
|  |                         <Button | ||||||
|  |                           size={'small'} | ||||||
|  |                           type={'text'} | ||||||
|  |                           icon={<DeleteOutlined />} | ||||||
|  |                           onClick={() => { | ||||||
|  |                             setSync(false); | ||||||
|  |                             setFileList((prevFileList) => { | ||||||
|  |                               if (!multiple) { | ||||||
|  |                                 onChange(null); | ||||||
|  |                                 return []; | ||||||
|  |                               } | ||||||
|  |                               const index = prevFileList.indexOf(file); | ||||||
|  |                               prevFileList.splice(index, 1); | ||||||
|  |                               onChange(toValue([...prevFileList])); | ||||||
|  |                               return [...prevFileList]; | ||||||
|  |                             }); | ||||||
|  |                           }} | ||||||
|  |                         /> | ||||||
|  |                       )} | ||||||
|  |                     </Space> | ||||||
|  |                   </span> | ||||||
|  |                   {file.status === 'uploading' && ( | ||||||
|  |                     <div className={'ant-upload-list-item-progress'}> | ||||||
|  |                       <Progress strokeWidth={2} type={'line'} showInfo={false} percent={file.percent} /> | ||||||
|  |                     </div> | ||||||
|  |                   )} | ||||||
|  |                 </div> | ||||||
|  |               </div> | ||||||
|  |             ); | ||||||
|  |           })} | ||||||
|  |           <div className={'ant-upload-list-picture-card-container'}> | ||||||
|  |             <AntdUpload | ||||||
|  |               {...useUploadProps({ ...props })} | ||||||
|  |               disabled={disabled} | ||||||
|  |               multiple={multiple} | ||||||
|  |               listType={'picture-card'} | ||||||
|  |               fileList={fileList} | ||||||
|  |               onChange={(info) => { | ||||||
|  |                 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) && ( | ||||||
|  |                 <span> | ||||||
|  |                   <PlusOutlined /> | ||||||
|  |                   <br /> {t('Upload')} | ||||||
|  |                 </span> | ||||||
|  |               )} | ||||||
|  |             </AntdUpload> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       {visible && ( | ||||||
|  |         <Lightbox | ||||||
|  |           // discourageDownloads={true}
 | ||||||
|  |           mainSrc={images[photoIndex]?.imageUrl} | ||||||
|  |           nextSrc={images[(photoIndex + 1) % images.length]?.imageUrl} | ||||||
|  |           prevSrc={images[(photoIndex + images.length - 1) % images.length]?.imageUrl} | ||||||
|  |           onCloseRequest={() => setVisible(false)} | ||||||
|  |           onMovePrevRequest={() => setPhotoIndex((photoIndex + images.length - 1) % images.length)} | ||||||
|  |           onMoveNextRequest={() => setPhotoIndex((photoIndex + 1) % images.length)} | ||||||
|  |           imageTitle={images[photoIndex]?.title} | ||||||
|  |           toolbarButtons={[ | ||||||
|  |             <button | ||||||
|  |               style={{ fontSize: 22, background: 'none', lineHeight: 1 }} | ||||||
|  |               type="button" | ||||||
|  |               aria-label="Zoom in" | ||||||
|  |               title="Zoom in" | ||||||
|  |               className="ril-zoom-in ril__toolbarItemChild ril__builtinButton" | ||||||
|  |               onClick={(e) => { | ||||||
|  |                 e.preventDefault(); | ||||||
|  |                 const file = images[photoIndex]; | ||||||
|  |                 saveAs(file.url, `${file.title}${file.extname}`); | ||||||
|  |               }} | ||||||
|  |             > | ||||||
|  |               <DownloadOutlined /> | ||||||
|  |             </button>, | ||||||
|  |           ]} | ||||||
|  |         /> | ||||||
|  |       )} | ||||||
|  |     </div> | ||||||
|  |   ); | ||||||
|  | }, mapReadPretty(ReadPretty.Attachment)); | ||||||
|  | 
 | ||||||
|  | Upload.Dragger = connect( | ||||||
|  |   (props: DraggerProps) => { | ||||||
|  |     return ( | ||||||
|  |       <div className={usePrefixCls('upload-dragger')}> | ||||||
|  |         <AntdUpload.Dragger {...useUploadProps(props)} /> | ||||||
|  |       </div> | ||||||
|  |     ); | ||||||
|  |   }, | ||||||
|  |   mapProps({ | ||||||
|  |     value: 'fileList', | ||||||
|  |   }), | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | export default Upload; | ||||||
| @ -0,0 +1,43 @@ | |||||||
|  | import { APIClient } from '@nocobase/client'; | ||||||
|  | import MockAdapter from 'axios-mock-adapter'; | ||||||
|  | 
 | ||||||
|  | export const apiClient = new APIClient({ | ||||||
|  |   baseURL: 'http://localhost:8001/api/', | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const mock = new MockAdapter(apiClient.axios); | ||||||
|  | 
 | ||||||
|  | const sleep = (value: number) => new Promise((resolve) => setTimeout(resolve, value)); | ||||||
|  | 
 | ||||||
|  | mock.onPost('/attachments:upload').reply(async (config) => { | ||||||
|  |   const total = 1024; // mocked file size
 | ||||||
|  |   for (const progress of [0, 0.2, 0.4, 0.6, 0.8, 1]) { | ||||||
|  |     await sleep(500); | ||||||
|  |     if (config.onUploadProgress) { | ||||||
|  |       config.onUploadProgress({ loaded: total * progress, total }); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return [ | ||||||
|  |     200, | ||||||
|  |     { | ||||||
|  |       data: { | ||||||
|  |         id: 2, | ||||||
|  |         title: 'd9f6ad6669902a9a8a1229d9f362235a (6)', | ||||||
|  |         filename: '7edb55e4e3145e5ac59ea3a44ca840e9.docx', | ||||||
|  |         extname: '.docx', | ||||||
|  |         path: '', | ||||||
|  |         size: null, | ||||||
|  |         url: 'https://nocobase.oss-cn-beijing.aliyuncs.com/7edb55e4e3145e5ac59ea3a44ca840e9.docx', | ||||||
|  |         mimetype: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', | ||||||
|  |         meta: {}, | ||||||
|  |         storage_id: 2, | ||||||
|  |         updated_at: '2022-01-21T07:21:21.084Z', | ||||||
|  |         created_at: '2022-01-21T07:21:21.084Z', | ||||||
|  |         created_by_id: null, | ||||||
|  |         updated_by_id: null, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | export default apiClient; | ||||||
| @ -0,0 +1,51 @@ | |||||||
|  | /** | ||||||
|  |  * title: Upload | ||||||
|  |  */ | ||||||
|  | import { FormItem } from '@formily/antd'; | ||||||
|  | import { APIClientProvider, SchemaComponent, SchemaComponentProvider, Upload } from '@nocobase/client'; | ||||||
|  | import React from 'react'; | ||||||
|  | import apiClient from './apiClient'; | ||||||
|  | 
 | ||||||
|  | const schema = { | ||||||
|  |   type: 'object', | ||||||
|  |   properties: { | ||||||
|  |     input: { | ||||||
|  |       type: 'string', | ||||||
|  |       title: `编辑模式`, | ||||||
|  |       'x-decorator': 'FormItem', | ||||||
|  |       'x-component': 'Upload.Attachment', | ||||||
|  |       'x-component-props': { | ||||||
|  |         action: 'attachments:upload', | ||||||
|  |         // multiple: true,
 | ||||||
|  |       }, | ||||||
|  |       'x-reactions': { | ||||||
|  |         target: 'read', | ||||||
|  |         fulfill: { | ||||||
|  |           state: { | ||||||
|  |             value: '{{$self.value}}', | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     read: { | ||||||
|  |       type: 'string', | ||||||
|  |       title: `阅读模式`, | ||||||
|  |       'x-read-pretty': true, | ||||||
|  |       'x-decorator': 'FormItem', | ||||||
|  |       'x-component': 'Upload.Attachment', | ||||||
|  |       'x-component-props': { | ||||||
|  |         // multiple: true,
 | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default () => { | ||||||
|  |   return ( | ||||||
|  |     <APIClientProvider apiClient={apiClient}> | ||||||
|  |       <SchemaComponentProvider components={{ Upload, FormItem }}> | ||||||
|  |         <SchemaComponent schema={schema} /> | ||||||
|  |       </SchemaComponentProvider> | ||||||
|  |     </APIClientProvider> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
							
								
								
									
										115
									
								
								packages/client/src/schema-component/antd/upload/demos/demo2.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								packages/client/src/schema-component/antd/upload/demos/demo2.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,115 @@ | |||||||
|  | /** | ||||||
|  |  * title: Upload | ||||||
|  |  */ | ||||||
|  | import { FormItem } from '@formily/antd'; | ||||||
|  | import { APIClientProvider, SchemaComponent, SchemaComponentProvider, Upload } from '@nocobase/client'; | ||||||
|  | import React from 'react'; | ||||||
|  | import apiClient from './apiClient'; | ||||||
|  | 
 | ||||||
|  | const schema = { | ||||||
|  |   type: 'object', | ||||||
|  |   properties: { | ||||||
|  |     input: { | ||||||
|  |       type: 'object', | ||||||
|  |       title: `编辑模式`, | ||||||
|  |       default: [ | ||||||
|  |         { | ||||||
|  |           id: 45, | ||||||
|  |           title: 's33766399', | ||||||
|  |           name: 's33766399', | ||||||
|  |           filename: 'cd48dc833ab01aa3959ac39309fc39de.jpg', | ||||||
|  |           extname: '.jpg', | ||||||
|  |           size: null, | ||||||
|  |           mimetype: 'image/jpeg', | ||||||
|  |           path: '', | ||||||
|  |           meta: {}, | ||||||
|  |           status: 'uploading', | ||||||
|  |           percent: 60, | ||||||
|  |           url: 'https://nocobase.oss-cn-beijing.aliyuncs.com/cd48dc833ab01aa3959ac39309fc39de.jpg', | ||||||
|  |           created_at: '2021-08-13T15:00:17.423Z', | ||||||
|  |           updated_at: '2021-08-13T15:00:17.423Z', | ||||||
|  |           created_by_id: null, | ||||||
|  |           updated_by_id: null, | ||||||
|  |           storage_id: 2, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           id: 7, | ||||||
|  |           title: '简历', | ||||||
|  |           filename: 'd9f6ad6669902a9a8a1229d9f362235a.docx', | ||||||
|  |           extname: '.docx', | ||||||
|  |           size: null, | ||||||
|  |           mimetype: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', | ||||||
|  |           path: '', | ||||||
|  |           meta: {}, | ||||||
|  |           url: 'https://nocobase.oss-cn-beijing.aliyuncs.com/d9f6ad6669902a9a8a1229d9f362235a.docx', | ||||||
|  |           created_at: '2021-09-12T01:22:06.229Z', | ||||||
|  |           updated_at: '2021-09-12T01:22:06.229Z', | ||||||
|  |           created_by_id: null, | ||||||
|  |           updated_by_id: 1, | ||||||
|  |           storage_id: 2, | ||||||
|  |           t_jh7a28dsfzi: { | ||||||
|  |             createdAt: '2021-09-12T01:22:07.886Z', | ||||||
|  |             updatedAt: '2021-09-12T01:22:07.886Z', | ||||||
|  |             f_xg3mysbjfra: 1, | ||||||
|  |             f_gc7ppj0b7n1: 7, | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |       ], | ||||||
|  |       'x-decorator': 'FormItem', | ||||||
|  |       'x-component': 'Upload.Attachment', | ||||||
|  |       'x-component-props': { | ||||||
|  |         action: 'attachments:upload', | ||||||
|  |         multiple: true, | ||||||
|  |       }, | ||||||
|  |       'x-reactions': [ | ||||||
|  |         { | ||||||
|  |           target: 'read', | ||||||
|  |           fulfill: { | ||||||
|  |             state: { | ||||||
|  |               value: '{{$self.value}}', | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           target: 'read2', | ||||||
|  |           fulfill: { | ||||||
|  |             state: { | ||||||
|  |               value: '{{$self.value}}', | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |       ], | ||||||
|  |     }, | ||||||
|  |     read: { | ||||||
|  |       type: 'object', | ||||||
|  |       title: `阅读模式`, | ||||||
|  |       'x-read-pretty': true, | ||||||
|  |       'x-decorator': 'FormItem', | ||||||
|  |       'x-component': 'Upload.Attachment', | ||||||
|  |       'x-component-props': { | ||||||
|  |         multiple: true, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     read2: { | ||||||
|  |       type: 'object', | ||||||
|  |       title: `小图预览`, | ||||||
|  |       'x-read-pretty': true, | ||||||
|  |       'x-decorator': 'FormItem', | ||||||
|  |       'x-component': 'Upload.Attachment', | ||||||
|  |       'x-component-props': { | ||||||
|  |         multiple: true, | ||||||
|  |         size: 'small', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default () => { | ||||||
|  |   return ( | ||||||
|  |     <APIClientProvider apiClient={apiClient}> | ||||||
|  |       <SchemaComponentProvider components={{ Upload, FormItem }}> | ||||||
|  |         <SchemaComponent schema={schema} /> | ||||||
|  |       </SchemaComponentProvider> | ||||||
|  |     </APIClientProvider> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
| @ -6,3 +6,13 @@ group: | |||||||
| --- | --- | ||||||
| 
 | 
 | ||||||
| # Upload | # Upload | ||||||
|  | 
 | ||||||
|  | ## Examples | ||||||
|  | 
 | ||||||
|  | ### Upload | ||||||
|  | 
 | ||||||
|  | <code src="./demos/demo1.tsx" /> | ||||||
|  | 
 | ||||||
|  | ### Upload | ||||||
|  | 
 | ||||||
|  | <code src="./demos/demo2.tsx" /> | ||||||
|  | |||||||
| @ -0,0 +1 @@ | |||||||
|  | export * from './Upload'; | ||||||
| @ -0,0 +1,62 @@ | |||||||
|  | export const UPLOAD_PLACEHOLDER = [ | ||||||
|  |   { | ||||||
|  |     ext: /\.docx?$/i, | ||||||
|  |     icon: '//img.alicdn.com/tfs/TB1n8jfr1uSBuNjy1XcXXcYjFXa-200-200.png', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     ext: /\.pptx?$/i, | ||||||
|  |     icon: '//img.alicdn.com/tfs/TB1ItgWr_tYBeNjy1XdXXXXyVXa-200-200.png', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     ext: /\.jpe?g$/i, | ||||||
|  |     icon: '//img.alicdn.com/tfs/TB1wrT5r9BYBeNjy0FeXXbnmFXa-200-200.png', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     ext: /\.pdf$/i, | ||||||
|  |     icon: '//img.alicdn.com/tfs/TB1GwD8r9BYBeNjy0FeXXbnmFXa-200-200.png', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     ext: /\.png$/i, | ||||||
|  |     icon: '//img.alicdn.com/tfs/TB1BHT5r9BYBeNjy0FeXXbnmFXa-200-200.png', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     ext: /\.eps$/i, | ||||||
|  |     icon: '//img.alicdn.com/tfs/TB1G_iGrVOWBuNjy0FiXXXFxVXa-200-200.png', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     ext: /\.ai$/i, | ||||||
|  |     icon: '//img.alicdn.com/tfs/TB1B2cVr_tYBeNjy1XdXXXXyVXa-200-200.png', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     ext: /\.gif$/i, | ||||||
|  |     icon: '//img.alicdn.com/tfs/TB1DTiGrVOWBuNjy0FiXXXFxVXa-200-200.png', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     ext: /\.svg$/i, | ||||||
|  |     icon: '//img.alicdn.com/tfs/TB1uUm9rY9YBuNjy0FgXXcxcXXa-200-200.png', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     ext: /\.xlsx?$/i, | ||||||
|  |     icon: '//img.alicdn.com/tfs/TB1any1r1OSBuNjy0FdXXbDnVXa-200-200.png', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     ext: /\.psd?$/i, | ||||||
|  |     icon: '//img.alicdn.com/tfs/TB1_nu1r1OSBuNjy0FdXXbDnVXa-200-200.png', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     ext: /\.(wav|aif|aiff|au|mp1|mp2|mp3|ra|rm|ram|mid|rmi)$/i, | ||||||
|  |     icon: '//img.alicdn.com/tfs/TB1jPvwr49YBuNjy0FfXXXIsVXa-200-200.png', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     ext: /\.(avi|wmv|mpg|mpeg|vob|dat|3gp|mp4|mkv|rm|rmvb|mov|flv)$/i, | ||||||
|  |     icon: '//img.alicdn.com/tfs/TB1FrT5r9BYBeNjy0FeXXbnmFXa-200-200.png', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     ext: /\.(zip|rar|arj|z|gz|iso|jar|ace|tar|uue|dmg|pkg|lzh|cab)$/i, | ||||||
|  |     icon: '//img.alicdn.com/tfs/TB10jmfr29TBuNjy0FcXXbeiFXa-200-200.png', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     ext: /\.[^.]+$/i, | ||||||
|  |     icon: '//img.alicdn.com/tfs/TB10.R4r3mTBuNjy1XbXXaMrVXa-200-200.png', | ||||||
|  |   }, | ||||||
|  | ]; | ||||||
							
								
								
									
										228
									
								
								packages/client/src/schema-component/antd/upload/shared.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								packages/client/src/schema-component/antd/upload/shared.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,228 @@ | |||||||
|  | import { Field } from '@formily/core'; | ||||||
|  | import { useField } from '@formily/react'; | ||||||
|  | import { reaction } from '@formily/reactive'; | ||||||
|  | import { isArr, isValid, toArr as toArray } from '@formily/shared'; | ||||||
|  | import { useAPIClient } from '@nocobase/client'; | ||||||
|  | import { UploadChangeParam } from 'antd/lib/upload'; | ||||||
|  | import { UploadFile } from 'antd/lib/upload/interface'; | ||||||
|  | import { useEffect } from 'react'; | ||||||
|  | import { UPLOAD_PLACEHOLDER } from './placeholder'; | ||||||
|  | import type { IUploadProps, UploadProps } from './type'; | ||||||
|  | 
 | ||||||
|  | export const isImage = (extName: string) => { | ||||||
|  |   var reg = /\.(png|jpg|gif|jpeg|webp)$/; | ||||||
|  |   return reg.test(extName); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const 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)]; | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export 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'], | ||||||
|  |       }), | ||||||
|  |     }; | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const toArr = (value) => { | ||||||
|  |   if (!isValid(value)) { | ||||||
|  |     return []; | ||||||
|  |   } | ||||||
|  |   if (Object.keys(value).length === 0) { | ||||||
|  |     return []; | ||||||
|  |   } | ||||||
|  |   return toArray(value); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export 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; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export 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; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const getURL = (target: any) => { | ||||||
|  |   return target?.['url'] || target?.['downloadURL'] || target?.['imgURL']; | ||||||
|  | }; | ||||||
|  | export const getThumbURL = (target: any) => { | ||||||
|  |   return target?.['thumbUrl'] || target?.['url'] || target?.['downloadURL'] || target?.['imgURL']; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const getErrorMessage = (target: any) => { | ||||||
|  |   return target?.errorMessage || | ||||||
|  |     target?.errMsg || | ||||||
|  |     target?.errorMsg || | ||||||
|  |     target?.message || | ||||||
|  |     typeof target?.error === 'string' | ||||||
|  |     ? target.error | ||||||
|  |     : ''; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export 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; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export 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 []; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export 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(); | ||||||
|  |     }; | ||||||
|  |   }, []); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export 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; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function useUploadProps<T extends IUploadProps = UploadProps>({ serviceErrorMessage, ...props }: T) { | ||||||
|  |   useUploadValidator(serviceErrorMessage); | ||||||
|  |   const onChange = (param: UploadChangeParam<UploadFile>) => { | ||||||
|  |     props.onChange?.(normalizeFileList([...param.fileList])); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const api = useAPIClient(); | ||||||
|  | 
 | ||||||
|  |   return { | ||||||
|  |     ...props, | ||||||
|  |     customRequest({ action, data, file, filename, headers, onError, onProgress, onSuccess, withCredentials }) { | ||||||
|  |       const formData = new FormData(); | ||||||
|  |       if (data) { | ||||||
|  |         Object.keys(data).forEach((key) => { | ||||||
|  |           formData.append(key, data[key]); | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |       formData.append(filename, file); | ||||||
|  |       api.axios | ||||||
|  |         .post(action, formData, { | ||||||
|  |           withCredentials, | ||||||
|  |           headers, | ||||||
|  |           onUploadProgress: ({ total, loaded }) => { | ||||||
|  |             onProgress({ percent: Math.round((loaded / total) * 100).toFixed(2) }, file); | ||||||
|  |           }, | ||||||
|  |         }) | ||||||
|  |         .then(({ data }) => { | ||||||
|  |           onSuccess(data, file); | ||||||
|  |         }) | ||||||
|  |         .catch(onError); | ||||||
|  | 
 | ||||||
|  |       return { | ||||||
|  |         abort() { | ||||||
|  |           console.log('upload progress is aborted.'); | ||||||
|  |         }, | ||||||
|  |       }; | ||||||
|  |     }, | ||||||
|  |     onChange, | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const 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'], | ||||||
|  |     }), | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const toFileList = (fileList: any) => { | ||||||
|  |   return toArr(fileList).map(toItem); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const toValue = (fileList: any) => { | ||||||
|  |   return toArr(fileList) | ||||||
|  |     .filter((file) => !file.response || file.status === 'done') | ||||||
|  |     .map((file) => file?.response?.data || file); | ||||||
|  | }; | ||||||
							
								
								
									
										57
									
								
								packages/client/src/schema-component/antd/upload/style.less
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								packages/client/src/schema-component/antd/upload/style.less
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,57 @@ | |||||||
|  | .nb-file-list { | ||||||
|  |   display: flex; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .nb-upload { | ||||||
|  |   .ant-upload-list-item.ant-upload-list-item-list-type-picture-card { | ||||||
|  |     padding: 3px; | ||||||
|  |   } | ||||||
|  |   .ant-upload-list-item-thumbnail { | ||||||
|  |     img { | ||||||
|  |       object-fit: cover; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   .ant-upload-list-item-actions { | ||||||
|  |     left: auto; | ||||||
|  |     right: 4px; | ||||||
|  |     top: 4px; | ||||||
|  |     transform: none; | ||||||
|  |   } | ||||||
|  |   .ant-upload-list-picture-card .ant-upload-list-item-info { | ||||||
|  |     overflow: inherit; | ||||||
|  |   } | ||||||
|  |   .ant-upload-list-picture-card .ant-upload-list-item-name { | ||||||
|  |     display: block; | ||||||
|  |     margin-top: 10px; | ||||||
|  |     font-size: 13px; | ||||||
|  |     color: #636363; | ||||||
|  |   } | ||||||
|  |   .ant-upload-list-picture-card .ant-upload-list-item:hover .ant-upload-list-item-info::before { | ||||||
|  |     display: none; | ||||||
|  |   } | ||||||
|  |   .ant-upload-list-picture-card .ant-upload-list-item-progress { | ||||||
|  |     bottom: calc(50% - 11px); | ||||||
|  |     pointer-events: none; | ||||||
|  |   } | ||||||
|  |   .ant-btn { | ||||||
|  |     background: rgba(0, 0, 0, 0.5); | ||||||
|  |   } | ||||||
|  |   .ant-upload-list-picture-card-container { | ||||||
|  |     margin-bottom: 28px; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .nb-upload-small { | ||||||
|  |   .ant-upload-list-picture-card-container { | ||||||
|  |     margin: 0 3px 3px 0; | ||||||
|  |     height: 32px; | ||||||
|  |     width: 32px; | ||||||
|  |   } | ||||||
|  |   .ant-upload-list-picture-card .ant-upload-list-item-name { | ||||||
|  |     display: none; | ||||||
|  |   } | ||||||
|  |   .ant-upload-list-picture .ant-upload-list-item, | ||||||
|  |   .ant-upload-list-picture-card .ant-upload-list-item { | ||||||
|  |     padding: 1px; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										26
									
								
								packages/client/src/schema-component/antd/upload/type.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								packages/client/src/schema-component/antd/upload/type.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | |||||||
|  | import type { DraggerProps as AntdDraggerProps, UploadProps as AntdUploadProps } from 'antd/lib/upload'; | ||||||
|  | import { UploadFile } from 'antd/lib/upload/interface'; | ||||||
|  | import React from 'react'; | ||||||
|  | 
 | ||||||
|  | export type UploadProps = Omit<AntdUploadProps, 'onChange'> & { | ||||||
|  |   onChange?: (fileList: UploadFile[]) => void; | ||||||
|  |   serviceErrorMessage?: string; | ||||||
|  |   value?: any; | ||||||
|  |   size?: string; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export type DraggerProps = Omit<AntdDraggerProps, 'onChange'> & { | ||||||
|  |   onChange?: (fileList: UploadFile[]) => void; | ||||||
|  |   serviceErrorMessage?: string; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export type ComposedUpload = React.FC<UploadProps> & { | ||||||
|  |   Dragger?: React.FC<DraggerProps>; | ||||||
|  |   File?: React.FC<UploadProps>; | ||||||
|  |   Attachment?: React.FC<UploadProps>; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export type IUploadProps = { | ||||||
|  |   serviceErrorMessage?: string; | ||||||
|  |   onChange?: (...args: any) => void; | ||||||
|  | }; | ||||||
							
								
								
									
										35
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								yarn.lock
									
									
									
									
									
								
							| @ -6270,6 +6270,11 @@ execa@^5.0.0: | |||||||
|     signal-exit "^3.0.3" |     signal-exit "^3.0.3" | ||||||
|     strip-final-newline "^2.0.0" |     strip-final-newline "^2.0.0" | ||||||
| 
 | 
 | ||||||
|  | exenv@^1.2.0: | ||||||
|  |   version "1.2.2" | ||||||
|  |   resolved "https://registry.npmmirror.com/exenv/download/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" | ||||||
|  |   integrity sha1-KueOhdmJQVhnCwPUe+wfA72Ru50= | ||||||
|  | 
 | ||||||
| exit@^0.1.2: | exit@^0.1.2: | ||||||
|   version "0.1.2" |   version "0.1.2" | ||||||
|   resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" |   resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" | ||||||
| @ -6443,6 +6448,11 @@ file-entry-cache@^6.0.1: | |||||||
|   dependencies: |   dependencies: | ||||||
|     flat-cache "^3.0.4" |     flat-cache "^3.0.4" | ||||||
| 
 | 
 | ||||||
|  | file-saver@^2.0.5: | ||||||
|  |   version "2.0.5" | ||||||
|  |   resolved "https://registry.npmmirror.com/file-saver/download/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38" | ||||||
|  |   integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA== | ||||||
|  | 
 | ||||||
| file-type@^3.3.0: | file-type@^3.3.0: | ||||||
|   version "3.9.0" |   version "3.9.0" | ||||||
|   resolved "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9" |   resolved "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9" | ||||||
| @ -12343,6 +12353,14 @@ react-i18next@^11.15.1: | |||||||
|     html-escaper "^2.0.2" |     html-escaper "^2.0.2" | ||||||
|     html-parse-stringify "^3.0.1" |     html-parse-stringify "^3.0.1" | ||||||
| 
 | 
 | ||||||
|  | react-image-lightbox@^5.1.4: | ||||||
|  |   version "5.1.4" | ||||||
|  |   resolved "https://registry.nlark.com/react-image-lightbox/download/react-image-lightbox-5.1.4.tgz#5b847dcb79e9efdf9d7cd5621a92e0f156d2cf30" | ||||||
|  |   integrity sha1-W4R9y3np79+dfNViGpLg8VbSzzA= | ||||||
|  |   dependencies: | ||||||
|  |     prop-types "^15.7.2" | ||||||
|  |     react-modal "^3.11.1" | ||||||
|  | 
 | ||||||
| react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4: | react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4: | ||||||
|   version "16.13.1" |   version "16.13.1" | ||||||
|   resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" |   resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" | ||||||
| @ -12353,6 +12371,21 @@ react-is@^17.0.1: | |||||||
|   resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" |   resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" | ||||||
|   integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== |   integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== | ||||||
| 
 | 
 | ||||||
|  | react-lifecycles-compat@^3.0.0: | ||||||
|  |   version "3.0.4" | ||||||
|  |   resolved "https://registry.npmmirror.com/react-lifecycles-compat/download/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" | ||||||
|  |   integrity sha1-TxonOv38jzSIqMUWv9p4+HI1I2I= | ||||||
|  | 
 | ||||||
|  | react-modal@^3.11.1: | ||||||
|  |   version "3.14.4" | ||||||
|  |   resolved "https://registry.npmmirror.com/react-modal/download/react-modal-3.14.4.tgz#2ca7e8e9a180955e5c9508c228b73167c1e6f6a3" | ||||||
|  |   integrity sha512-8surmulejafYCH9wfUmFyj4UfbSJwjcgbS9gf3oOItu4Hwd6ivJyVBETI0yHRhpJKCLZMUtnhzk76wXTsNL6Qg== | ||||||
|  |   dependencies: | ||||||
|  |     exenv "^1.2.0" | ||||||
|  |     prop-types "^15.7.2" | ||||||
|  |     react-lifecycles-compat "^3.0.0" | ||||||
|  |     warning "^4.0.3" | ||||||
|  | 
 | ||||||
| react-refresh@0.10.0: | react-refresh@0.10.0: | ||||||
|   version "0.10.0" |   version "0.10.0" | ||||||
|   resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.10.0.tgz#2f536c9660c0b9b1d500684d9e52a65e7404f7e3" |   resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.10.0.tgz#2f536c9660c0b9b1d500684d9e52a65e7404f7e3" | ||||||
| @ -14934,7 +14967,7 @@ walker@^1.0.7, walker@~1.0.5: | |||||||
|   dependencies: |   dependencies: | ||||||
|     makeerror "1.0.12" |     makeerror "1.0.12" | ||||||
| 
 | 
 | ||||||
| warning@^4.0.1: | warning@^4.0.1, warning@^4.0.3: | ||||||
|   version "4.0.3" |   version "4.0.3" | ||||||
|   resolved "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" |   resolved "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" | ||||||
|   integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== |   integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user