@quansitech/antd-admin 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/components/Column/Cascader.tsx +78 -78
  2. package/components/Column/File.tsx +165 -167
  3. package/components/Column/Image.tsx +76 -76
  4. package/components/{Table/Option → Column/Readonly/Action}/Link.tsx +77 -67
  5. package/components/{Table/Option → Column/Readonly/Action}/types.d.ts +5 -4
  6. package/components/Column/Readonly/Action.tsx +80 -0
  7. package/components/Column/Readonly/Cascader.tsx +50 -50
  8. package/components/Column/Readonly/File.tsx +52 -53
  9. package/components/Column/Readonly/Image.tsx +38 -77
  10. package/components/Column/Readonly/Ueditor.tsx +18 -0
  11. package/components/Column/Readonly/types.d.ts +9 -8
  12. package/components/Column/Ueditor.tsx +313 -313
  13. package/components/Column/types.d.ts +29 -28
  14. package/components/Form/Action/Button.tsx +128 -124
  15. package/components/Form/Action/types.d.ts +5 -4
  16. package/components/Form/Actions.tsx +38 -34
  17. package/components/Form.tsx +179 -170
  18. package/components/FormContext.ts +8 -7
  19. package/components/Layout/New.tsx +252 -0
  20. package/components/Layout.tsx +52 -237
  21. package/components/LayoutContext.ts +25 -25
  22. package/components/ModalContext.ts +15 -15
  23. package/components/Table/Action/Button.tsx +88 -88
  24. package/components/Table/Action/StartEditable.tsx +58 -58
  25. package/components/Table/Action/types.d.ts +7 -6
  26. package/components/Table/ToolbarActions.tsx +43 -38
  27. package/components/Table.scss +4 -7
  28. package/components/Table.tsx +283 -279
  29. package/components/TableContext.ts +14 -13
  30. package/components/Tabs.tsx +71 -71
  31. package/lib/container.ts +83 -81
  32. package/lib/customRule.ts +9 -9
  33. package/lib/global.ts +10 -10
  34. package/lib/helpers.tsx +145 -149
  35. package/lib/http.ts +73 -73
  36. package/lib/schemaHandler.ts +121 -121
  37. package/lib/upload.ts +177 -177
  38. package/package.json +2 -6
  39. package/readme.md +128 -128
  40. package/components/Column/Readonly/Option.tsx +0 -58
@@ -1,79 +1,79 @@
1
- import {ColumnProps} from "./types";
2
- import {Cascader} from "antd";
3
- import {useEffect, useState} from "react";
4
- import http from "../../lib/http";
5
-
6
- export default function (props: ColumnProps) {
7
- const [options, setOptions] = useState<{ value: string, label: string }[]>();
8
- const [values, setValues] = useState<any>()
9
-
10
- useEffect(() => {
11
- const value = props.config.value
12
- setOptions(props.config.options as []);
13
-
14
- // 远程获取数据
15
- if (props.fieldProps.loadDataUrl) {
16
- http({
17
- url: props.fieldProps.loadDataUrl,
18
- method: 'get',
19
- params: {
20
- value,
21
- }
22
- }).then(res => {
23
- setOptions(res.data)
24
-
25
- if (value) {
26
- const findValue = (options: any[], value: any): any => {
27
- for (let i = 0; i < options.length; i++) {
28
- const option = options[i];
29
- if (option.value === value) {
30
- return [option.value]
31
- } else if (option.children) {
32
- return [option.value, ...findValue(option.children, value)]
33
- }
34
- }
35
- }
36
-
37
- setValues(findValue(res.data, value))
38
- }
39
-
40
- })
41
- }
42
-
43
- }, []);
44
-
45
- const loadData = async (selectedOptions: any) => {
46
- const targetOption = selectedOptions[selectedOptions.length - 1];
47
- if (targetOption.children) {
48
- return
49
- }
50
- targetOption.loading = true;
51
- const res = await http(props.fieldProps.loadDataUrl, {
52
- params: {
53
- selected: targetOption.value
54
- }
55
- })
56
- targetOption.loading = false;
57
- targetOption.children = res.data;
58
- setOptions([...options as []]);
59
- }
60
-
61
- const onChange = (values: any[]) => {
62
- setValues(values)
63
- if (!values?.length) {
64
- props.form.setFieldValue(props.dataIndex, undefined)
65
- return
66
- }
67
- const value = values[values.length - 1]
68
- props.form.setFieldValue(props.dataIndex, value)
69
- }
70
-
71
- return <div className={props.className}>
72
- <Cascader options={options}
73
- onChange={onChange}
74
- placeholder={'请选择'}
75
- value={values}
76
- loadData={props.fieldProps.loadDataUrl ? loadData : undefined}
77
- ></Cascader>
78
- </div>
1
+ import {ColumnProps} from "./types";
2
+ import {Cascader} from "antd";
3
+ import {useEffect, useState} from "react";
4
+ import http from "../../lib/http";
5
+
6
+ export default function (props: ColumnProps) {
7
+ const [options, setOptions] = useState<{ value: string, label: string }[]>();
8
+ const [values, setValues] = useState<any>()
9
+
10
+ useEffect(() => {
11
+ const value = props.config.value
12
+ setOptions(props.config.options as []);
13
+
14
+ // 远程获取数据
15
+ if (props.fieldProps.loadDataUrl) {
16
+ http({
17
+ url: props.fieldProps.loadDataUrl,
18
+ method: 'get',
19
+ params: {
20
+ value,
21
+ }
22
+ }).then(res => {
23
+ setOptions(res.data)
24
+
25
+ if (value) {
26
+ const findValue = (options: any[], value: any): any => {
27
+ for (let i = 0; i < options.length; i++) {
28
+ const option = options[i];
29
+ if (option.value === value) {
30
+ return [option.value]
31
+ } else if (option.children) {
32
+ return [option.value, ...findValue(option.children, value)]
33
+ }
34
+ }
35
+ }
36
+
37
+ setValues(findValue(res.data, value))
38
+ }
39
+
40
+ })
41
+ }
42
+
43
+ }, []);
44
+
45
+ const loadData = async (selectedOptions: any) => {
46
+ const targetOption = selectedOptions[selectedOptions.length - 1];
47
+ if (targetOption.children) {
48
+ return
49
+ }
50
+ targetOption.loading = true;
51
+ const res = await http(props.fieldProps.loadDataUrl, {
52
+ params: {
53
+ selected: targetOption.value
54
+ }
55
+ })
56
+ targetOption.loading = false;
57
+ targetOption.children = res.data;
58
+ setOptions([...options as []]);
59
+ }
60
+
61
+ const onChange = (values: any[]) => {
62
+ setValues(values)
63
+ if (!values?.length) {
64
+ props.form.setFieldValue(props.dataIndex, undefined)
65
+ return
66
+ }
67
+ const value = values[values.length - 1]
68
+ props.form.setFieldValue(props.dataIndex, value)
69
+ }
70
+
71
+ return <div className={props.className}>
72
+ <Cascader options={options}
73
+ onChange={onChange}
74
+ placeholder={'请选择'}
75
+ value={values}
76
+ loadData={props.fieldProps.loadDataUrl ? loadData : undefined}
77
+ ></Cascader>
78
+ </div>
79
79
  }
@@ -1,168 +1,166 @@
1
- import {Alert, Button, Spin, Tooltip, Upload, UploadFile, UploadProps} from "antd";
2
- import React, {ReactNode, useEffect, useState} from "react";
3
- import {beforeUpload, customRequest} from "../../lib/upload";
4
- import http from "../../lib/http";
5
- import {ColumnProps} from "./types";
6
- import {UploadListType} from "antd/es/upload/interface";
7
- import {DndContext, DragEndEvent, PointerSensor, useSensor} from '@dnd-kit/core';
8
- import {arrayMove, SortableContext, useSortable, verticalListSortingStrategy,} from '@dnd-kit/sortable';
9
- import {CSS} from '@dnd-kit/utilities';
10
- import {RcFile} from "antd/lib/upload";
11
-
12
-
13
- interface DraggableUploadListItemProps {
14
- originNode: React.ReactElement<any, string | React.JSXElementConstructor<any>>;
15
- file: UploadFile<any>;
16
- }
17
-
18
- const DraggableUploadListItem = ({originNode, file}: DraggableUploadListItemProps) => {
19
- const {attributes, listeners, setNodeRef, transform, transition, isDragging} = useSortable({
20
- id: file.uid,
21
- });
22
-
23
- const style: React.CSSProperties = {
24
- transform: CSS.Translate.toString(transform),
25
- transition,
26
- cursor: 'move',
27
- height: '100%',
28
- };
29
-
30
- return (
31
- <div
32
- ref={setNodeRef}
33
- style={style}
34
- // prevent preview event when drag end
35
- className={isDragging ? 'is-dragging' : ''}
36
- {...attributes}
37
- {...listeners}
38
- >
39
- {/* hide error tooltip when dragging */}
40
- {file.status === 'error' && isDragging ? originNode.props.children : originNode}
41
- </div>
42
- );
43
- };
44
-
45
-
46
- export default function (props: ColumnProps & {
47
- fieldProps?: {
48
- uploadRequest: {
49
- policyGetUrl: string,
50
- },
51
- maxCount?: number,
52
- loadUrl: string,
53
- }
54
-
55
- uploadButton?: (fileList: UploadFile[]) => ReactNode,
56
- listType?: UploadListType,
57
- onPreview?: (file: UploadFile) => Promise<void>,
58
- renderUploader?: (attr: {
59
- dom: JSX.Element,
60
- }) => ReactNode
61
- }) {
62
-
63
- const [loading, setLoading] = useState(true);
64
- const [fileList, setFileList] = useState<UploadFile[]>([]);
65
-
66
- const handlePreview = async (file: UploadFile) => {
67
- if (!file.url && !file.preview) {
68
- file.preview = URL.createObjectURL(file.originFileObj as RcFile);
69
- }
70
-
71
- window.open(file.url || (file.preview as string))
72
- };
73
-
74
- useEffect(() => {
75
- const value = props.value || props.config.value
76
- if (value && props.fieldProps.loadUrl) {
77
- http({
78
- url: props.fieldProps.loadUrl,
79
- params: {
80
- ids: value
81
- },
82
- method: 'get',
83
- }).then(res => {
84
- setFileList(res.data.map((item: any) => {
85
- return {
86
- uid: item.id,
87
- status: 'done',
88
- url: item.url,
89
- name: item.name,
90
- hash_id: item.hash_id,
91
- response: {
92
- file_id: item.id,
93
- url: item.url,
94
- }
95
- }
96
- }))
97
- setLoading(false)
98
- })
99
-
100
- } else {
101
- setLoading(false)
102
- }
103
- }, []);
104
-
105
- useEffect(() => {
106
- props.onChange && props.onChange(fileList.map(file => {
107
- if (file.status === 'done') {
108
- file.url = file.response.url || file.response.file_url
109
- }
110
- return file
111
- }))
112
- }, [fileList]);
113
-
114
- const uploadButton = (
115
- <Tooltip
116
- title={fileList.length >= (props.fieldProps?.maxCount || 1) ? '最多只能上传' + (props.fieldProps?.maxCount || 1) + '个文件' : ''}>
117
- <Button disabled={fileList.length >= (props.fieldProps?.maxCount || 1)}>上传文件</Button>
118
- </Tooltip>
119
- );
120
-
121
- const sensor = useSensor(PointerSensor, {
122
- activationConstraint: {distance: 10},
123
- });
124
-
125
- const onDragEnd = ({active, over}: DragEndEvent) => {
126
- if (active.id !== over?.id) {
127
- setFileList((prev) => {
128
- const activeIndex = prev.findIndex((i) => i.uid === active.id);
129
- const overIndex = prev.findIndex((i) => i.uid === over?.id);
130
- return arrayMove(prev, activeIndex, overIndex);
131
- });
132
- }
133
- };
134
-
135
- const uploader = (<Upload
136
- action={props.fieldProps.uploadRequest.policyGetUrl}
137
- listType={props.listType || 'text'}
138
- fileList={fileList}
139
- itemRender={(originNode, file) => (
140
- <DraggableUploadListItem originNode={originNode} file={file}/>
141
- )}
142
- onPreview={props.onPreview || handlePreview}
143
- onChange={({fileList}) => setFileList(fileList)}
144
- beforeUpload={(f, fl) => beforeUpload(f, fl, fileList)}
145
- customRequest={customRequest as UploadProps['customRequest']}
146
- >
147
- {props.uploadButton ? props.uploadButton(fileList) : uploadButton}
148
- </Upload>)
149
-
150
- return <>
151
- {props.fieldProps?.uploadRequest
152
- ? <>
153
- <Spin spinning={loading}>
154
- <DndContext sensors={[sensor]} onDragEnd={onDragEnd}>
155
- <SortableContext items={fileList.map((i) => i.uid)} strategy={verticalListSortingStrategy}>
156
- {props.renderUploader ? props.renderUploader({dom: uploader}) : uploader}
157
- </SortableContext>
158
- </DndContext>
159
- {<div style={{marginBottom: '16px'}}></div>}
160
- </Spin>
161
- </>
162
- : <>
163
- <Alert message={'缺少 uploadRequest 属性'}
164
- type={"error"}/>
165
- </>
166
- }
167
- </>
1
+ import {Alert, Button, Spin, Tooltip, Upload, UploadFile, UploadProps} from "antd";
2
+ import React, {ReactNode, useContext, useEffect, useState} from "react";
3
+ import {beforeUpload, customRequest} from "../../lib/upload";
4
+ import {ColumnProps} from "./types";
5
+ import {UploadListType} from "antd/es/upload/interface";
6
+ import {DndContext, DragEndEvent, PointerSensor, useSensor} from '@dnd-kit/core';
7
+ import {arrayMove, SortableContext, useSortable, verticalListSortingStrategy,} from '@dnd-kit/sortable';
8
+ import {CSS} from '@dnd-kit/utilities';
9
+ import {RcFile} from "antd/lib/upload";
10
+ import {FormContext} from "../FormContext";
11
+ import {TableContext} from "../TableContext";
12
+
13
+
14
+ interface DraggableUploadListItemProps {
15
+ originNode: React.ReactElement<any, string | React.JSXElementConstructor<any>>;
16
+ file: UploadFile<any>;
17
+ }
18
+
19
+ const DraggableUploadListItem = ({originNode, file}: DraggableUploadListItemProps) => {
20
+ const {attributes, listeners, setNodeRef, transform, transition, isDragging} = useSortable({
21
+ id: file.uid,
22
+ });
23
+
24
+ const style: React.CSSProperties = {
25
+ transform: CSS.Translate.toString(transform),
26
+ transition,
27
+ cursor: 'move',
28
+ height: '100%',
29
+ };
30
+
31
+ return (
32
+ <div
33
+ ref={setNodeRef}
34
+ style={style}
35
+ // prevent preview event when drag end
36
+ className={isDragging ? 'is-dragging' : ''}
37
+ {...attributes}
38
+ {...listeners}
39
+ >
40
+ {/* hide error tooltip when dragging */}
41
+ {file.status === 'error' && isDragging ? originNode.props.children : originNode}
42
+ </div>
43
+ );
44
+ };
45
+
46
+
47
+ export default function (props: ColumnProps & {
48
+ fieldProps?: {
49
+ uploadRequest: {
50
+ policyGetUrl: string,
51
+ },
52
+ maxCount?: number,
53
+ }
54
+
55
+ uploadButton?: (fileList: UploadFile[]) => ReactNode,
56
+ listType?: UploadListType,
57
+ onPreview?: (file: UploadFile) => Promise<void>,
58
+ renderUploader?: (attr: {
59
+ dom: JSX.Element,
60
+ }) => ReactNode
61
+ }) {
62
+
63
+ const [loading, setLoading] = useState(true);
64
+ const [fileList, setFileList] = useState<UploadFile[]>([]);
65
+ const formContext = useContext(FormContext);
66
+ const tableContext = useContext(TableContext);
67
+
68
+ const handlePreview = async (file: UploadFile) => {
69
+ if (!file.url && !file.preview) {
70
+ file.preview = URL.createObjectURL(file.originFileObj as RcFile);
71
+ }
72
+
73
+ window.open(file.url || (file.preview as string))
74
+ };
75
+
76
+ useEffect(() => {
77
+ let extraRenderValue = [];
78
+ if (formContext && formContext.extraRenderValues) {
79
+ extraRenderValue = formContext.extraRenderValues[props.dataIndex as string] ?? []
80
+ } else if (tableContext && tableContext.extraRenderValues) {
81
+ console.log(111)
82
+ extraRenderValue = tableContext.extraRenderValues[props.index as number]?.[props.schema.dataIndex as string] ?? []
83
+ }
84
+
85
+ setFileList(extraRenderValue.map((item: any) => {
86
+ return {
87
+ uid: item.id,
88
+ status: 'done',
89
+ url: item.url,
90
+ name: item.name,
91
+ hash_id: item.hash_id,
92
+ response: {
93
+ file_id: item.id,
94
+ url: item.url,
95
+ }
96
+ }
97
+ }))
98
+
99
+ setLoading(false)
100
+ }, []);
101
+
102
+ useEffect(() => {
103
+ props.form?.setFieldValue(props.dataIndex, fileList.map(file => {
104
+ if (file.status === 'done') {
105
+ file.url = file.response.url || file.response.file_url
106
+ }
107
+ return file
108
+ }))
109
+ }, [fileList]);
110
+
111
+ const uploadButton = (
112
+ <Tooltip
113
+ title={fileList.length >= (props.fieldProps?.maxCount || 1) ? '最多只能上传' + (props.fieldProps?.maxCount || 1) + '个文件' : ''}>
114
+ <Button disabled={fileList.length >= (props.fieldProps?.maxCount || 1)}>上传文件</Button>
115
+ </Tooltip>
116
+ );
117
+
118
+ const sensor = useSensor(PointerSensor, {
119
+ activationConstraint: {distance: 10},
120
+ });
121
+
122
+ const onDragEnd = ({active, over}: DragEndEvent) => {
123
+ if (active.id !== over?.id) {
124
+ setFileList((prev) => {
125
+ const activeIndex = prev.findIndex((i) => i.uid === active.id);
126
+ const overIndex = prev.findIndex((i) => i.uid === over?.id);
127
+ return arrayMove(prev, activeIndex, overIndex);
128
+ });
129
+ }
130
+ };
131
+
132
+ const uploader = (<Upload
133
+ {...props.fieldProps}
134
+ action={props.fieldProps.uploadRequest.policyGetUrl}
135
+ listType={props.listType || 'text'}
136
+ fileList={fileList}
137
+ itemRender={(originNode, file) => (
138
+ <DraggableUploadListItem originNode={originNode} file={file}/>
139
+ )}
140
+ onPreview={props.onPreview || handlePreview}
141
+ onChange={({fileList}) => setFileList(fileList)}
142
+ beforeUpload={(f, fl) => beforeUpload(f, fl, fileList)}
143
+ customRequest={customRequest as UploadProps['customRequest']}
144
+ >
145
+ {props.uploadButton ? props.uploadButton(fileList) : uploadButton}
146
+ </Upload>)
147
+
148
+ return <>
149
+ {props.fieldProps?.uploadRequest
150
+ ? <>
151
+ <Spin spinning={loading}>
152
+ <DndContext sensors={[sensor]} onDragEnd={onDragEnd}>
153
+ <SortableContext items={fileList.map((i) => i.uid)} strategy={verticalListSortingStrategy}>
154
+ {props.renderUploader ? props.renderUploader({dom: uploader}) : uploader}
155
+ </SortableContext>
156
+ </DndContext>
157
+ {<div style={{marginBottom: '16px'}}></div>}
158
+ </Spin>
159
+ </>
160
+ : <>
161
+ <Alert message={'缺少 uploadRequest 属性'}
162
+ type={"error"}/>
163
+ </>
164
+ }
165
+ </>
168
166
  }
@@ -1,77 +1,77 @@
1
- import {Col, Image, Row, UploadFile} from "antd";
2
- import React, {useState} from "react";
3
- import {PlusOutlined} from '@ant-design/icons';
4
- import {FileType, getBase64} from "../../lib/upload";
5
- import {ColumnProps} from "./types";
6
- import File from "./File";
7
- import ImgCrop from 'antd-img-crop';
8
-
9
- export default function (props: ColumnProps & {
10
- fieldProps?: {
11
- maxCount?: number,
12
- crop?: {
13
- ratio: string,
14
- }
15
- }
16
- }) {
17
-
18
- const [previewOpen, setPreviewOpen] = useState(false);
19
- const [previewImage, setPreviewImage] = useState('');
20
-
21
- const handlePreview = async (file: UploadFile) => {
22
- if (!file.url && !file.preview) {
23
- file.preview = await getBase64(file.originFileObj as FileType);
24
- }
25
-
26
- setPreviewImage(file.url || (file.preview as string));
27
- setPreviewOpen(true);
28
- };
29
-
30
- const uploadButton = (fileList: UploadFile[]) => (
31
- fileList.length < (props.fieldProps?.maxCount || 1) ?
32
- <Row justify={'center'}>
33
- <Col flex={1}>
34
- <PlusOutlined size={18}/>
35
- <div style={{marginTop: 8}}>上传</div>
36
- </Col>
37
- </Row> : null
38
- );
39
-
40
- const renderUploader = ({dom}: { dom: JSX.Element }) => {
41
- if (props.fieldProps?.crop) {
42
- const aspects = props.fieldProps.crop.ratio.split(/[\/:]/)
43
- let aspect = Number(aspects)
44
- if (aspects.length === 2) {
45
- aspect = parseInt(aspects[0]) / parseInt(aspects[1])
46
- }
47
- return <>
48
- <ImgCrop rotationSlider aspect={aspect}>
49
- {dom}
50
- </ImgCrop>
51
- </>
52
- } else {
53
- return dom
54
- }
55
- }
56
-
57
- return <>
58
- <File {...props}
59
-
60
- uploadButton={uploadButton}
61
- listType={'picture-card'}
62
- onPreview={handlePreview}
63
- renderUploader={renderUploader}
64
- ></File>
65
- {previewImage && (
66
- <Image
67
- wrapperStyle={{display: 'none'}}
68
- preview={{
69
- visible: previewOpen,
70
- onVisibleChange: (visible) => setPreviewOpen(visible),
71
- afterOpenChange: (visible) => !visible && setPreviewImage(''),
72
- }}
73
- src={previewImage}
74
- />
75
- )}
76
- </>
1
+ import {Col, Image, Row, UploadFile} from "antd";
2
+ import React, {useState} from "react";
3
+ import {PlusOutlined} from '@ant-design/icons';
4
+ import {FileType, getBase64} from "../../lib/upload";
5
+ import {ColumnProps} from "./types";
6
+ import File from "./File";
7
+ import ImgCrop from 'antd-img-crop';
8
+
9
+ export default function (props: ColumnProps & {
10
+ fieldProps?: {
11
+ maxCount?: number,
12
+ crop?: {
13
+ ratio: string,
14
+ }
15
+ }
16
+ }) {
17
+
18
+ const [previewOpen, setPreviewOpen] = useState(false);
19
+ const [previewImage, setPreviewImage] = useState('');
20
+
21
+ const handlePreview = async (file: UploadFile) => {
22
+ if (!file.url && !file.preview) {
23
+ file.preview = await getBase64(file.originFileObj as FileType);
24
+ }
25
+
26
+ setPreviewImage(file.url || (file.preview as string));
27
+ setPreviewOpen(true);
28
+ };
29
+
30
+ const uploadButton = (fileList: UploadFile[]) => (
31
+ fileList.length < (props.fieldProps?.maxCount || 1) ?
32
+ <Row justify={'center'}>
33
+ <Col flex={1}>
34
+ <PlusOutlined size={18}/>
35
+ <div style={{marginTop: 8}}>上传</div>
36
+ </Col>
37
+ </Row> : null
38
+ );
39
+
40
+ const renderUploader = ({dom}: { dom: JSX.Element }) => {
41
+ if (props.fieldProps?.crop) {
42
+ const aspects = props.fieldProps.crop.ratio.split(/[\/:]/)
43
+ let aspect = Number(aspects)
44
+ if (aspects.length === 2) {
45
+ aspect = parseInt(aspects[0]) / parseInt(aspects[1])
46
+ }
47
+ return <>
48
+ <ImgCrop rotationSlider aspect={aspect}>
49
+ {dom}
50
+ </ImgCrop>
51
+ </>
52
+ } else {
53
+ return dom
54
+ }
55
+ }
56
+
57
+ return <>
58
+ <File {...props}
59
+
60
+ uploadButton={uploadButton}
61
+ listType={'picture-card'}
62
+ onPreview={handlePreview}
63
+ renderUploader={renderUploader}
64
+ ></File>
65
+ {previewImage && (
66
+ <Image
67
+ wrapperStyle={{display: 'none'}}
68
+ preview={{
69
+ visible: previewOpen,
70
+ onVisibleChange: (visible) => setPreviewOpen(visible),
71
+ afterOpenChange: (visible) => !visible && setPreviewImage(''),
72
+ }}
73
+ src={previewImage}
74
+ />
75
+ )}
76
+ </>
77
77
  }