@quansitech/antd-admin 1.0.0

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 (38) hide show
  1. package/components/Column/Cascader.tsx +79 -0
  2. package/components/Column/File.tsx +168 -0
  3. package/components/Column/Image.tsx +77 -0
  4. package/components/Column/Readonly/Cascader.tsx +51 -0
  5. package/components/Column/Readonly/File.tsx +54 -0
  6. package/components/Column/Readonly/Image.tsx +78 -0
  7. package/components/Column/Readonly/Option.tsx +58 -0
  8. package/components/Column/Readonly/types.d.ts +9 -0
  9. package/components/Column/Ueditor.tsx +314 -0
  10. package/components/Column/types.d.ts +29 -0
  11. package/components/Form/Action/Button.tsx +125 -0
  12. package/components/Form/Action/types.d.ts +5 -0
  13. package/components/Form/Actions.tsx +35 -0
  14. package/components/Form.tsx +171 -0
  15. package/components/FormContext.ts +8 -0
  16. package/components/Layout.tsx +237 -0
  17. package/components/LayoutContext.ts +26 -0
  18. package/components/ModalContext.ts +16 -0
  19. package/components/Table/Action/Button.tsx +89 -0
  20. package/components/Table/Action/StartEditable.tsx +59 -0
  21. package/components/Table/Action/types.d.ts +7 -0
  22. package/components/Table/Option/Link.tsx +68 -0
  23. package/components/Table/Option/types.d.ts +5 -0
  24. package/components/Table/ToolbarActions.tsx +39 -0
  25. package/components/Table.scss +7 -0
  26. package/components/Table.tsx +279 -0
  27. package/components/TableContext.ts +14 -0
  28. package/components/Tabs.tsx +72 -0
  29. package/components/types.d.ts +0 -0
  30. package/lib/container.ts +82 -0
  31. package/lib/customRule.ts +10 -0
  32. package/lib/global.ts +11 -0
  33. package/lib/helpers.tsx +150 -0
  34. package/lib/http.ts +74 -0
  35. package/lib/schemaHandler.ts +122 -0
  36. package/lib/upload.ts +177 -0
  37. package/package.json +35 -0
  38. package/readme.md +128 -0
@@ -0,0 +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>
79
+ }
@@ -0,0 +1,168 @@
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
+ </>
168
+ }
@@ -0,0 +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
+ </>
77
+ }
@@ -0,0 +1,51 @@
1
+ import {ColumnReadonlyProps} from "./types";
2
+ import {ReactNode, useEffect, useState} from "react";
3
+ import http from "../../../lib/http";
4
+
5
+ export default function (props: ColumnReadonlyProps & {
6
+ schema: {
7
+ fieldProps: {
8
+ loadDataUrl: string
9
+ }
10
+ }
11
+ }) {
12
+ const [text, setText] = useState<ReactNode>('-');
13
+
14
+ useEffect(() => {
15
+ setText(props.dom)
16
+ const value = props.entity.value
17
+
18
+ // 远程获取数据
19
+ if (props.schema.fieldProps?.loadDataUrl) {
20
+ http({
21
+ url: props.schema.fieldProps.loadDataUrl,
22
+ method: 'get',
23
+ params: {
24
+ value,
25
+ }
26
+ }).then(res => {
27
+ if (!value) {
28
+ return
29
+ }
30
+ const findValue = (options: any[], value: any): any => {
31
+ for (let i = 0; i < options.length; i++) {
32
+ const option = options[i];
33
+ if (option.value === value) {
34
+ return [option.label]
35
+ } else if (option.children) {
36
+ return [option.label, ...findValue(option.children, value)]
37
+ }
38
+ }
39
+ }
40
+
41
+ setText(findValue(res.data, value).join(' / '))
42
+
43
+ })
44
+ }
45
+
46
+ }, []);
47
+
48
+ return <div className={props.entity.className}>
49
+ {text}
50
+ </div>
51
+ }
@@ -0,0 +1,54 @@
1
+ import {ColumnReadonlyProps} from "./types";
2
+ import {Spin, Upload, UploadFile} from "antd";
3
+ import React, {useEffect, useState} from "react";
4
+ import http from "../../../lib/http";
5
+
6
+ export default function (props: ColumnReadonlyProps & {
7
+ schema: {
8
+ fieldProps?: {
9
+ loadUrl: string,
10
+ }
11
+ },
12
+ }) {
13
+
14
+ const [loading, setLoading] = useState(true);
15
+ const [fileList, setFileList] = useState<UploadFile[]>([]);
16
+
17
+ useEffect(() => {
18
+ if (props.entity.value && props.schema.fieldProps?.loadUrl) {
19
+ http({
20
+ url: props.schema.fieldProps.loadUrl,
21
+ params: {
22
+ ids: props.entity.value
23
+ },
24
+ method: 'get',
25
+ }).then(res => {
26
+ setFileList(res.data.map((item: any) => {
27
+ return {
28
+ uid: item.id,
29
+ status: 'done',
30
+ url: item.url,
31
+ name: item.name,
32
+ response: {
33
+ file_id: item.id,
34
+ }
35
+ }
36
+ }))
37
+ setLoading(false)
38
+ })
39
+ } else {
40
+ setLoading(false)
41
+ }
42
+ }, []);
43
+
44
+
45
+ return <>
46
+ <Spin spinning={loading}>
47
+ <Upload
48
+ disabled={true}
49
+ listType="text"
50
+ fileList={fileList}
51
+ ></Upload>
52
+ </Spin>
53
+ </>
54
+ }
@@ -0,0 +1,78 @@
1
+ import {ColumnReadonlyProps} from "./types";
2
+ import {Image, Spin, Upload, UploadFile} from "antd";
3
+ import {FileType, getBase64} from "../../../lib/upload";
4
+ import React, {useEffect, useState} from "react";
5
+ import http from "../../../lib/http";
6
+
7
+ export default function (props: ColumnReadonlyProps & {
8
+ schema: {
9
+ fieldProps?: {
10
+ loadUrl: string,
11
+ }
12
+ },
13
+ }) {
14
+
15
+ const [loading, setLoading] = useState(true);
16
+ const [fileList, setFileList] = useState<UploadFile[]>([]);
17
+ const [previewImage, setPreviewImage] = useState('');
18
+ const [previewOpen, setPreviewOpen] = useState(false);
19
+
20
+ const handlePreview = async (file: UploadFile) => {
21
+ if (!file.url && !file.preview) {
22
+ file.preview = await getBase64(file.originFileObj as FileType);
23
+ }
24
+
25
+ setPreviewImage(file.url || (file.preview as string));
26
+ setPreviewOpen(true);
27
+ };
28
+
29
+ useEffect(() => {
30
+ if (props.entity.value && props.schema.fieldProps?.loadUrl) {
31
+ http({
32
+ url: props.schema.fieldProps.loadUrl,
33
+ params: {
34
+ ids: props.entity.value
35
+ },
36
+ method: 'get',
37
+ }).then(res => {
38
+ setFileList(res.data.map((item: any) => {
39
+ return {
40
+ uid: item.id,
41
+ status: 'done',
42
+ url: item.url,
43
+ name: '',
44
+ response: {
45
+ file_id: item.id,
46
+ }
47
+ }
48
+ }))
49
+ setLoading(false)
50
+ })
51
+ } else {
52
+ setLoading(false)
53
+ }
54
+ }, []);
55
+
56
+
57
+ return <>
58
+ <Spin spinning={loading}>
59
+ <Upload
60
+ disabled={true}
61
+ listType="picture-card"
62
+ fileList={fileList}
63
+ onPreview={handlePreview}
64
+ ></Upload>
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
+ </Spin>
77
+ </>
78
+ }
@@ -0,0 +1,58 @@
1
+ import {Component, lazy, useEffect, useState} from "react";
2
+ import {ReactComponentLike} from "prop-types";
3
+ import container from "../../../lib/container";
4
+ import {Flex} from "antd";
5
+ import {ColumnReadonlyProps} from "./types";
6
+ import {asyncFilter, handleRules} from "../../../lib/helpers";
7
+ import {Rules} from "@rc-component/async-validator/lib/interface";
8
+ import upperFirst from "lodash/upperFirst";
9
+
10
+ type ComponentType = {
11
+ component: ReactComponentLike,
12
+ props: any,
13
+ }
14
+
15
+ export default ({options, record}: ColumnReadonlyProps & {
16
+ options?: {
17
+ type: string,
18
+ title: string,
19
+ showRules?: Rules,
20
+ }[],
21
+ }) => {
22
+
23
+ const [Components, setComponents] = useState<ComponentType[]>([]);
24
+
25
+ useEffect(() => {
26
+ if (options) {
27
+ asyncFilter(options, async (Component) => {
28
+ if (!Component.showRules) {
29
+ return true
30
+ }
31
+ return await handleRules(Component.showRules, record)
32
+ }).then((Components: { type: string }[]) => setComponents(Components.map(a => {
33
+ const c = `Table.Option.${upperFirst(a.type)}`
34
+ return {
35
+ props: {
36
+ ...a,
37
+ record,
38
+ },
39
+ component: lazy(container.get(c)),
40
+ }
41
+ })))
42
+ }
43
+ }, []);
44
+
45
+
46
+ return <>
47
+ {
48
+ <Flex wrap={true}>
49
+ {
50
+ Components.map(Component => {
51
+ return <Component.component
52
+ key={Component.props.title} {...Component.props}></Component.component>
53
+ })
54
+ }
55
+ </Flex>
56
+ }
57
+ </>
58
+ }
@@ -0,0 +1,9 @@
1
+ import {ProSchema} from "@ant-design/pro-components";
2
+ import {ReactNode} from "react";
3
+
4
+ export type ColumnReadonlyProps = {
5
+ dom: ReactNode,
6
+ entity: any,
7
+ schema: ProSchema,
8
+ record?: any,
9
+ }