From be1192531cf62af215d83feee4996b5d4fdd7d57 Mon Sep 17 00:00:00 2001 From: SemmyWong <67748948+semmywong@users.noreply.github.com> Date: Sat, 22 Jan 2022 21:05:06 +0800 Subject: [PATCH] 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 --- packages/client/package.json | 1 + .../client/src/schema-component/antd/index.ts | 2 +- .../antd/upload/ReadPretty.tsx | 147 +++++++++++ .../schema-component/antd/upload/Upload.tsx | 207 ++++++++++++++++ .../antd/upload/demos/apiClient.ts | 43 ++++ .../antd/upload/demos/demo1.tsx | 51 ++++ .../antd/upload/demos/demo2.tsx | 115 +++++++++ .../src/schema-component/antd/upload/index.md | 10 + .../src/schema-component/antd/upload/index.ts | 1 + .../antd/upload/placeholder.ts | 62 +++++ .../schema-component/antd/upload/shared.ts | 228 ++++++++++++++++++ .../schema-component/antd/upload/style.less | 57 +++++ .../schema-component/antd/upload/type.d.ts | 26 ++ yarn.lock | 35 ++- 14 files changed, 983 insertions(+), 2 deletions(-) create mode 100644 packages/client/src/schema-component/antd/upload/ReadPretty.tsx create mode 100644 packages/client/src/schema-component/antd/upload/Upload.tsx create mode 100644 packages/client/src/schema-component/antd/upload/demos/apiClient.ts create mode 100644 packages/client/src/schema-component/antd/upload/demos/demo1.tsx create mode 100644 packages/client/src/schema-component/antd/upload/demos/demo2.tsx create mode 100644 packages/client/src/schema-component/antd/upload/index.ts create mode 100644 packages/client/src/schema-component/antd/upload/placeholder.ts create mode 100644 packages/client/src/schema-component/antd/upload/shared.ts create mode 100644 packages/client/src/schema-component/antd/upload/style.less create mode 100644 packages/client/src/schema-component/antd/upload/type.d.ts diff --git a/packages/client/package.json b/packages/client/package.json index cfe42da8d..a04531a1a 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -25,6 +25,7 @@ "@formily/react": "^2.0.7", "ahooks": "^3.0.5", "axios": "^0.24.0", + "file-saver": "^2.0.5", "i18next": "^21.6.0", "react-helmet": "^6.1.0", "react-i18next": "^11.15.1", diff --git a/packages/client/src/schema-component/antd/index.ts b/packages/client/src/schema-component/antd/index.ts index c14d01fcd..20c760cff 100644 --- a/packages/client/src/schema-component/antd/index.ts +++ b/packages/client/src/schema-component/antd/index.ts @@ -16,4 +16,4 @@ export * from './page'; export * from './password'; export * from './radio'; export * from './time-picker'; - +export * from './upload'; diff --git a/packages/client/src/schema-component/antd/upload/ReadPretty.tsx b/packages/client/src/schema-component/antd/upload/ReadPretty.tsx new file mode 100644 index 000000000..5fea7add1 --- /dev/null +++ b/packages/client/src/schema-component/antd/upload/ReadPretty.tsx @@ -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 & { + Upload?: React.FC; + Attachment?: React.FC; +}; + +export const ReadPretty: Composed = () => null; + +ReadPretty.Attachment = (props: UploadProps) => { + 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); + if (isImage(file.extname)) { + setVisible(true); + setPhotoIndex(index); + } else { + saveAs(file.url, `${file.title}${file.extname}`); + } + }; + 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={[ + , + ]} + /> + )} +
+ ); +}; + +ReadPretty.Upload = (props) => { + const field = useField(); + return (field.value || []).map((item) => ( +
+ {item.url ? ( + + {item.name} + + ) : ( + {item.name} + )} +
+ )); +}; diff --git a/packages/client/src/schema-component/antd/upload/Upload.tsx b/packages/client/src/schema-component/antd/upload/Upload.tsx new file mode 100644 index 000000000..4b2c0020b --- /dev/null +++ b/packages/client/src/schema-component/antd/upload/Upload.tsx @@ -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 ; + }, + 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 ( +
+
+
+ {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 ( +
+
+ + + +
+
+ ); + })} +
+ { + 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(ReadPretty.Attachment)); + +Upload.Dragger = connect( + (props: DraggerProps) => { + return ( +
+ +
+ ); + }, + mapProps({ + value: 'fileList', + }), +); + +export default Upload; diff --git a/packages/client/src/schema-component/antd/upload/demos/apiClient.ts b/packages/client/src/schema-component/antd/upload/demos/apiClient.ts new file mode 100644 index 000000000..9b564e20e --- /dev/null +++ b/packages/client/src/schema-component/antd/upload/demos/apiClient.ts @@ -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; diff --git a/packages/client/src/schema-component/antd/upload/demos/demo1.tsx b/packages/client/src/schema-component/antd/upload/demos/demo1.tsx new file mode 100644 index 000000000..3e841c45c --- /dev/null +++ b/packages/client/src/schema-component/antd/upload/demos/demo1.tsx @@ -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 ( + + + + + + ); +}; diff --git a/packages/client/src/schema-component/antd/upload/demos/demo2.tsx b/packages/client/src/schema-component/antd/upload/demos/demo2.tsx new file mode 100644 index 000000000..9cbd3f5bd --- /dev/null +++ b/packages/client/src/schema-component/antd/upload/demos/demo2.tsx @@ -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 ( + + + + + + ); +}; diff --git a/packages/client/src/schema-component/antd/upload/index.md b/packages/client/src/schema-component/antd/upload/index.md index 78a85f3f2..845666d0b 100644 --- a/packages/client/src/schema-component/antd/upload/index.md +++ b/packages/client/src/schema-component/antd/upload/index.md @@ -6,3 +6,13 @@ group: --- # Upload + +## Examples + +### Upload + + + +### Upload + + diff --git a/packages/client/src/schema-component/antd/upload/index.ts b/packages/client/src/schema-component/antd/upload/index.ts new file mode 100644 index 000000000..70aa20677 --- /dev/null +++ b/packages/client/src/schema-component/antd/upload/index.ts @@ -0,0 +1 @@ +export * from './Upload'; diff --git a/packages/client/src/schema-component/antd/upload/placeholder.ts b/packages/client/src/schema-component/antd/upload/placeholder.ts new file mode 100644 index 000000000..7be1a1ee2 --- /dev/null +++ b/packages/client/src/schema-component/antd/upload/placeholder.ts @@ -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', + }, +]; diff --git a/packages/client/src/schema-component/antd/upload/shared.ts b/packages/client/src/schema-component/antd/upload/shared.ts new file mode 100644 index 000000000..6df047490 --- /dev/null +++ b/packages/client/src/schema-component/antd/upload/shared.ts @@ -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(); + 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({ serviceErrorMessage, ...props }: T) { + useUploadValidator(serviceErrorMessage); + const onChange = (param: UploadChangeParam) => { + 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); +}; diff --git a/packages/client/src/schema-component/antd/upload/style.less b/packages/client/src/schema-component/antd/upload/style.less new file mode 100644 index 000000000..71039659c --- /dev/null +++ b/packages/client/src/schema-component/antd/upload/style.less @@ -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; + } +} diff --git a/packages/client/src/schema-component/antd/upload/type.d.ts b/packages/client/src/schema-component/antd/upload/type.d.ts new file mode 100644 index 000000000..150d0b550 --- /dev/null +++ b/packages/client/src/schema-component/antd/upload/type.d.ts @@ -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 & { + onChange?: (fileList: UploadFile[]) => void; + serviceErrorMessage?: string; + value?: any; + size?: string; +}; + +export type DraggerProps = Omit & { + onChange?: (fileList: UploadFile[]) => void; + serviceErrorMessage?: string; +}; + +export type ComposedUpload = React.FC & { + Dragger?: React.FC; + File?: React.FC; + Attachment?: React.FC; +}; + +export type IUploadProps = { + serviceErrorMessage?: string; + onChange?: (...args: any) => void; +}; diff --git a/yarn.lock b/yarn.lock index 90ab1bdd7..47141fd72 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6270,6 +6270,11 @@ execa@^5.0.0: signal-exit "^3.0.3" 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: version "0.1.2" resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -6443,6 +6448,11 @@ file-entry-cache@^6.0.1: dependencies: 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: version "3.9.0" 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-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: version "16.13.1" 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" 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: version "0.10.0" 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: makeerror "1.0.12" -warning@^4.0.1: +warning@^4.0.1, warning@^4.0.3: version "4.0.3" resolved "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==