@hzab/form-render 1.6.1 → 1.6.3

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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ # @hzab/form-render@1.6.2
2
+
3
+ feat:自增列表组件新增删除二次确认功能
4
+
1
5
  # @hzab/form-render@1.6.1
2
6
 
3
7
  fix:列表新增-上传组件删除后清除对应的值
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hzab/form-render",
3
- "version": "1.6.1",
3
+ "version": "1.6.3",
4
4
  "description": "",
5
5
  "main": "src",
6
6
  "scripts": {
@@ -0,0 +1,349 @@
1
+ import { CopyOutlined, DeleteOutlined, DownOutlined, MenuOutlined, PlusOutlined, UpOutlined } from "@ant-design/icons";
2
+ import { ArrayField } from "@formily/core";
3
+ import { JSXComponent, Schema, useField, useFieldSchema } from "@formily/react";
4
+ import { clone, isUndef, isValid } from "@formily/shared";
5
+ import { Button, Popconfirm } from "antd";
6
+ import { ButtonProps } from "antd/lib/button";
7
+ import cls from "classnames";
8
+ import React, { createContext, ReactNode, useContext } from "react";
9
+ import { SortableHandle, usePrefixCls } from "c-formily-antd/lib/__builtins__";
10
+
11
+ export interface IArrayBaseAdditionProps extends ButtonProps {
12
+ title?: string;
13
+ method?: "push" | "unshift";
14
+ defaultValue?: any;
15
+ }
16
+ export interface IArrayBaseOperationProps extends ButtonProps {
17
+ title?: string;
18
+ index?: number;
19
+ ref?: React.Ref<HTMLElement>;
20
+ }
21
+
22
+ export interface IArrayBaseContext {
23
+ props: IArrayBaseProps;
24
+ field: ArrayField;
25
+ schema: Schema;
26
+ }
27
+
28
+ export interface IArrayBaseItemProps {
29
+ index: number;
30
+ record: ((index: number) => Record<string, any>) | Record<string, any>;
31
+ }
32
+
33
+ export type ArrayBaseMixins = {
34
+ Addition?: React.FC<React.PropsWithChildren<IArrayBaseAdditionProps>>;
35
+ Copy?: React.FC<React.PropsWithChildren<IArrayBaseOperationProps & { index?: number }>>;
36
+ Remove?: React.FC<React.PropsWithChildren<IArrayBaseOperationProps & { index?: number }>>;
37
+ MoveUp?: React.FC<React.PropsWithChildren<IArrayBaseOperationProps & { index?: number }>>;
38
+ MoveDown?: React.FC<React.PropsWithChildren<IArrayBaseOperationProps & { index?: number }>>;
39
+ SortHandle?: React.FC<React.PropsWithChildren<IArrayBaseOperationProps & { index?: number }>>;
40
+ Index?: React.FC;
41
+ useArray?: () => IArrayBaseContext;
42
+ useIndex?: (index?: number) => number;
43
+ useRecord?: (record?: number) => any;
44
+ deletePopconfirmTitle?: ReactNode;
45
+ deletePopconfirm?: boolean;
46
+ };
47
+
48
+ export interface IArrayBaseProps {
49
+ disabled?: boolean;
50
+ onAdd?: (index: number) => void;
51
+ onCopy?: (index: number) => void;
52
+ onRemove?: (index: number) => void;
53
+ onMoveDown?: (index: number) => void;
54
+ onMoveUp?: (index: number) => void;
55
+ deletePopconfirmTitle?: () => ReactNode;
56
+ deletePopconfirm?: boolean;
57
+ }
58
+
59
+ type ComposedArrayBase = React.FC<React.PropsWithChildren<IArrayBaseProps>> &
60
+ ArrayBaseMixins & {
61
+ Item?: React.FC<React.PropsWithChildren<IArrayBaseItemProps>>;
62
+ mixin?: <T extends JSXComponent>(target: T) => T & ArrayBaseMixins;
63
+ };
64
+
65
+ const ArrayBaseContext = createContext<IArrayBaseContext>(null);
66
+
67
+ const ItemContext = createContext<IArrayBaseItemProps>(null);
68
+
69
+ const takeRecord = (val: any, index?: number) => (typeof val === "function" ? val(index) : val);
70
+
71
+ const useArray = () => {
72
+ return useContext(ArrayBaseContext);
73
+ };
74
+
75
+ const useIndex = (index?: number) => {
76
+ const ctx = useContext(ItemContext);
77
+ return ctx ? ctx.index : index;
78
+ };
79
+
80
+ const useRecord = (record?: number) => {
81
+ const ctx = useContext(ItemContext);
82
+ return takeRecord(ctx ? ctx.record : record, ctx?.index);
83
+ };
84
+
85
+ const getSchemaDefaultValue = (schema: Schema) => {
86
+ if (schema?.type === "array") return [];
87
+ if (schema?.type === "object") return {};
88
+ if (schema?.type === "void") {
89
+ for (let key in schema.properties) {
90
+ const value = getSchemaDefaultValue(schema.properties[key]);
91
+ if (isValid(value)) return value;
92
+ }
93
+ }
94
+ };
95
+
96
+ const getDefaultValue = (defaultValue: any, schema: Schema) => {
97
+ if (isValid(defaultValue)) return clone(defaultValue);
98
+ if (Array.isArray(schema?.items)) return getSchemaDefaultValue(schema?.items[0]);
99
+ return getSchemaDefaultValue(schema?.items);
100
+ };
101
+
102
+ export const ArrayBase: ComposedArrayBase = (props: any) => {
103
+ const field = useField<ArrayField>();
104
+ const schema = useFieldSchema();
105
+ return <ArrayBaseContext.Provider value={{ field, schema, props }}>{props.children}</ArrayBaseContext.Provider>;
106
+ };
107
+
108
+ ArrayBase.Item = ({ children, ...props }) => {
109
+ return <ItemContext.Provider value={props}>{children}</ItemContext.Provider>;
110
+ };
111
+
112
+ const SortHandle = SortableHandle((props: any) => {
113
+ const prefixCls = usePrefixCls("formily-array-base");
114
+ return (
115
+ <MenuOutlined {...props} className={cls(`${prefixCls}-sort-handle`, props.className)} style={{ ...props.style }} />
116
+ );
117
+ }) as any;
118
+
119
+ ArrayBase.SortHandle = (props) => {
120
+ const array = useArray();
121
+ if (!array) return null;
122
+ if (array.field?.pattern !== "editable") return null;
123
+ return <SortHandle {...props} />;
124
+ };
125
+
126
+ ArrayBase.Index = (props) => {
127
+ const index = useIndex();
128
+ const prefixCls = usePrefixCls("formily-array-base");
129
+ return (
130
+ <span {...props} className={`${prefixCls}-index`}>
131
+ #{index + 1}.
132
+ </span>
133
+ );
134
+ };
135
+
136
+ ArrayBase.Addition = (props) => {
137
+ const self = useField();
138
+ const array = useArray();
139
+ const prefixCls = usePrefixCls("formily-array-base");
140
+ if (!array) return null;
141
+ if (array.field?.pattern !== "editable" && array.field?.pattern !== "disabled") return null;
142
+ return (
143
+ <Button
144
+ type="dashed"
145
+ block
146
+ {...props}
147
+ disabled={self?.disabled}
148
+ className={cls(`${prefixCls}-addition`, props.className)}
149
+ onClick={(e) => {
150
+ if (array.props?.disabled) return;
151
+ if (props.onClick) {
152
+ props.onClick(e);
153
+ if (e.defaultPrevented) return;
154
+ }
155
+ const defaultValue = getDefaultValue(props.defaultValue, array.schema);
156
+ if (props.method === "unshift") {
157
+ array.field?.unshift?.(defaultValue);
158
+ array.props?.onAdd?.(0);
159
+ } else {
160
+ array.field?.push?.(defaultValue);
161
+ array.props?.onAdd?.(array?.field?.value?.length - 1);
162
+ }
163
+ }}
164
+ icon={isUndef(props.icon) ? <PlusOutlined /> : props.icon}
165
+ >
166
+ {props.title || self.title}
167
+ </Button>
168
+ );
169
+ };
170
+
171
+ ArrayBase.Copy = React.forwardRef((props, ref) => {
172
+ const self = useField();
173
+ const array = useArray();
174
+ const index = useIndex(props.index);
175
+ const prefixCls = usePrefixCls("formily-array-base");
176
+ if (!array) return null;
177
+ if (array.field?.pattern !== "editable") return null;
178
+ return (
179
+ <Button
180
+ type="text"
181
+ {...props}
182
+ disabled={self?.disabled}
183
+ className={cls(`${prefixCls}-copy`, self?.disabled ? `${prefixCls}-copy-disabled` : "", props.className)}
184
+ ref={ref}
185
+ onClick={(e) => {
186
+ if (self?.disabled) return;
187
+ e.stopPropagation();
188
+ if (array.props?.disabled) return;
189
+ if (props.onClick) {
190
+ props.onClick(e);
191
+ if (e.defaultPrevented) return;
192
+ }
193
+ const value = clone(array?.field?.value[index]);
194
+ const distIndex = index + 1;
195
+ array.field?.insert?.(distIndex, value);
196
+ array.props?.onCopy?.(distIndex);
197
+ }}
198
+ icon={isUndef(props.icon) ? <CopyOutlined /> : props.icon}
199
+ >
200
+ {props.title || self.title}
201
+ </Button>
202
+ );
203
+ });
204
+
205
+ ArrayBase.Remove = React.forwardRef((props, ref) => {
206
+ const index = useIndex(props.index);
207
+ const self = useField();
208
+ const array = useArray();
209
+ const prefixCls = usePrefixCls("formily-array-base");
210
+ if (!array) return null;
211
+ if (array.field?.pattern !== "editable") return null;
212
+
213
+ return (
214
+ <>
215
+ {array.props?.deletePopconfirm && !self?.disabled ? (
216
+ <Popconfirm
217
+ {...array.props}
218
+ title={array.props?.deletePopconfirmTitle() || null}
219
+ onConfirm={() => {
220
+ if (self?.disabled) return;
221
+ array.field?.remove?.(index);
222
+ array.props?.onRemove?.(index);
223
+ }}
224
+ >
225
+ <Button
226
+ type="text"
227
+ {...props}
228
+ disabled={self?.disabled}
229
+ className={cls(
230
+ `${prefixCls}-remove`,
231
+ self?.disabled ? `${prefixCls}-remove-disabled` : "",
232
+ props.className,
233
+ )}
234
+ ref={ref}
235
+ icon={isUndef(props.icon) ? <DeleteOutlined /> : props.icon}
236
+ >
237
+ {props.title || self.title}
238
+ </Button>
239
+ </Popconfirm>
240
+ ) : (
241
+ <Button
242
+ type="text"
243
+ {...props}
244
+ disabled={self?.disabled}
245
+ className={cls(`${prefixCls}-remove`, self?.disabled ? `${prefixCls}-remove-disabled` : "", props.className)}
246
+ ref={ref}
247
+ onClick={(e) => {
248
+ if (self?.disabled) return;
249
+ e.stopPropagation();
250
+ if (props.onClick) {
251
+ props.onClick(e);
252
+ if (e.defaultPrevented) return;
253
+ }
254
+ array.field?.remove?.(index);
255
+ array.props?.onRemove?.(index);
256
+ }}
257
+ icon={isUndef(props.icon) ? <DeleteOutlined /> : props.icon}
258
+ >
259
+ {props.title || self.title}
260
+ </Button>
261
+ )}
262
+ </>
263
+ );
264
+ });
265
+
266
+ ArrayBase.MoveDown = React.forwardRef((props, ref) => {
267
+ const index = useIndex(props.index);
268
+ const self = useField();
269
+ const array = useArray();
270
+ const prefixCls = usePrefixCls("formily-array-base");
271
+ if (!array) return null;
272
+ if (array.field?.pattern !== "editable") return null;
273
+ return (
274
+ <Button
275
+ type="text"
276
+ {...props}
277
+ disabled={self?.disabled}
278
+ className={cls(
279
+ `${prefixCls}-move-down`,
280
+ self?.disabled ? `${prefixCls}-move-down-disabled` : "",
281
+ props.className,
282
+ )}
283
+ ref={ref}
284
+ onClick={(e) => {
285
+ if (self?.disabled) return;
286
+ e.stopPropagation();
287
+ if (props.onClick) {
288
+ props.onClick(e);
289
+ if (e.defaultPrevented) return;
290
+ }
291
+ array.field?.moveDown?.(index);
292
+ array.props?.onMoveDown?.(index);
293
+ }}
294
+ icon={isUndef(props.icon) ? <DownOutlined /> : props.icon}
295
+ >
296
+ {props.title || self.title}
297
+ </Button>
298
+ );
299
+ });
300
+
301
+ ArrayBase.MoveUp = React.forwardRef((props, ref) => {
302
+ const index = useIndex(props.index);
303
+ const self = useField();
304
+ const array = useArray();
305
+ const prefixCls = usePrefixCls("formily-array-base");
306
+ if (!array) return null;
307
+ if (array.field?.pattern !== "editable") return null;
308
+ return (
309
+ <Button
310
+ type="text"
311
+ {...props}
312
+ disabled={self?.disabled}
313
+ className={cls(`${prefixCls}-move-up`, self?.disabled ? `${prefixCls}-move-up-disabled` : "", props.className)}
314
+ ref={ref}
315
+ onClick={(e) => {
316
+ if (self?.disabled) return;
317
+ e.stopPropagation();
318
+ if (props.onClick) {
319
+ props.onClick(e);
320
+ if (e.defaultPrevented) return;
321
+ }
322
+ array?.field?.moveUp(index);
323
+ array?.props?.onMoveUp?.(index);
324
+ }}
325
+ icon={isUndef(props.icon) ? <UpOutlined /> : props.icon}
326
+ >
327
+ {props.title || self.title}
328
+ </Button>
329
+ );
330
+ });
331
+
332
+ ArrayBase.useArray = useArray;
333
+ ArrayBase.useIndex = useIndex;
334
+ ArrayBase.useRecord = useRecord;
335
+ ArrayBase.mixin = (target: any) => {
336
+ target.Index = ArrayBase.Index;
337
+ target.SortHandle = ArrayBase.SortHandle;
338
+ target.Addition = ArrayBase.Addition;
339
+ target.Copy = ArrayBase.Copy;
340
+ target.Remove = ArrayBase.Remove;
341
+ target.MoveDown = ArrayBase.MoveDown;
342
+ target.MoveUp = ArrayBase.MoveUp;
343
+ target.useArray = ArrayBase.useArray;
344
+ target.useIndex = ArrayBase.useIndex;
345
+ target.useRecord = ArrayBase.useRecord;
346
+ return target;
347
+ };
348
+
349
+ export default ArrayBase;
@@ -0,0 +1,91 @@
1
+ @root-entry-name: 'default';
2
+ @import (reference) '~antd/es/style/themes/index.less';
3
+
4
+ @array-base-prefix-cls: ~'@{ant-prefix}-formily-array-base';
5
+
6
+ .@{array-base-prefix-cls}-remove,
7
+ .@{array-base-prefix-cls}-copy {
8
+ transition: all 0.25s ease-in-out;
9
+ color: @text-color;
10
+ font-size: 14px;
11
+ margin-left: 6px;
12
+ padding: 0;
13
+ border: none;
14
+ width: auto;
15
+ height: auto;
16
+
17
+ &:hover {
18
+ color: @primary-5;
19
+ }
20
+
21
+ &-disabled {
22
+ color: @disabled-color;
23
+ cursor: not-allowed !important;
24
+
25
+ &:hover {
26
+ color: @disabled-color;
27
+ }
28
+ }
29
+ }
30
+
31
+ .@{array-base-prefix-cls}-sort-handle {
32
+ cursor: move;
33
+ color: #888 !important;
34
+
35
+ // overrid iconfont.less .anticon[tabindex] cursor
36
+ &.anticon[tabindex] {
37
+ cursor: move;
38
+ }
39
+ }
40
+
41
+ .@{array-base-prefix-cls}-addition {
42
+ transition: all 0.25s ease-in-out;
43
+ }
44
+
45
+ .@{array-base-prefix-cls}-move-down {
46
+ transition: all 0.25s ease-in-out;
47
+ color: @text-color;
48
+ font-size: 14px;
49
+ margin-left: 6px;
50
+ padding: 0;
51
+ border: none;
52
+ width: auto;
53
+ height: auto;
54
+
55
+ &:hover {
56
+ color: @primary-5;
57
+ }
58
+
59
+ &-disabled {
60
+ color: @disabled-color;
61
+ cursor: not-allowed !important;
62
+
63
+ &:hover {
64
+ color: @disabled-color;
65
+ }
66
+ }
67
+ }
68
+
69
+ .@{array-base-prefix-cls}-move-up {
70
+ transition: all 0.25s ease-in-out;
71
+ color: @text-color;
72
+ font-size: 14px;
73
+ margin-left: 6px;
74
+ padding: 0;
75
+ border: none;
76
+ width: auto;
77
+ height: auto;
78
+
79
+ &:hover {
80
+ color: @primary-5;
81
+ }
82
+
83
+ &-disabled {
84
+ color: @disabled-color;
85
+ cursor: not-allowed !important;
86
+
87
+ &:hover {
88
+ color: @disabled-color;
89
+ }
90
+ }
91
+ }
@@ -0,0 +1,2 @@
1
+ import 'antd/lib/button/style/index'
2
+ import './style.less'
@@ -0,0 +1,149 @@
1
+ import React from "react";
2
+ import { Card, Empty } from "antd";
3
+ import { CardProps } from "antd/lib/card";
4
+ import { ArrayField } from "@formily/core";
5
+ import { useField, observer, useFieldSchema, RecursionField } from "@formily/react";
6
+ import cls from "classnames";
7
+ import { ISchema } from "@formily/json-schema";
8
+ import { usePrefixCls } from "c-formily-antd/lib/__builtins__";
9
+ import { ArrayBase, ArrayBaseMixins, IArrayBaseProps } from "../ArrayBase";
10
+
11
+ type ComposedArrayCards = React.FC<React.PropsWithChildren<CardProps & IArrayBaseProps>> & ArrayBaseMixins;
12
+
13
+ const isAdditionComponent = (schema: ISchema) => {
14
+ return schema["x-component"]?.indexOf("Addition") > -1;
15
+ };
16
+
17
+ const isIndexComponent = (schema: ISchema) => {
18
+ return schema["x-component"]?.indexOf?.("Index") > -1;
19
+ };
20
+
21
+ const isRemoveComponent = (schema: ISchema) => {
22
+ return schema["x-component"]?.indexOf?.("Remove") > -1;
23
+ };
24
+
25
+ const isCopyComponent = (schema: ISchema) => {
26
+ return schema["x-component"]?.indexOf?.("Copy") > -1;
27
+ };
28
+
29
+ const isMoveUpComponent = (schema: ISchema) => {
30
+ return schema["x-component"]?.indexOf?.("MoveUp") > -1;
31
+ };
32
+
33
+ const isMoveDownComponent = (schema: ISchema) => {
34
+ return schema["x-component"]?.indexOf?.("MoveDown") > -1;
35
+ };
36
+
37
+ const isOperationComponent = (schema: ISchema) => {
38
+ return (
39
+ isAdditionComponent(schema) ||
40
+ isRemoveComponent(schema) ||
41
+ isCopyComponent(schema) ||
42
+ isMoveDownComponent(schema) ||
43
+ isMoveUpComponent(schema)
44
+ );
45
+ };
46
+
47
+ export const ArrayCards: ComposedArrayCards = observer((props: any) => {
48
+ const field = useField<ArrayField>();
49
+ const schema = useFieldSchema();
50
+ const dataSource = Array.isArray(field.value) ? field.value : [];
51
+ const prefixCls = usePrefixCls("formily-array-cards", props);
52
+ const { onAdd, onCopy, onRemove, onMoveDown, onMoveUp, deletePopconfirm, deletePopconfirmTitle } = props;
53
+ if (!schema) throw new Error("can not found schema object");
54
+
55
+ const renderItems = () => {
56
+ return dataSource?.map((item, index) => {
57
+ const items = Array.isArray(schema.items) ? schema.items[index] || schema.items[0] : schema.items;
58
+ const title = (
59
+ <span>
60
+ <RecursionField
61
+ schema={items}
62
+ name={index}
63
+ filterProperties={(schema) => {
64
+ if (!isIndexComponent(schema)) return false;
65
+ return true;
66
+ }}
67
+ onlyRenderProperties
68
+ />
69
+ {props.title || field.title}
70
+ </span>
71
+ );
72
+ const extra = (
73
+ <span>
74
+ <RecursionField
75
+ schema={items}
76
+ name={index}
77
+ filterProperties={(schema) => {
78
+ if (!isOperationComponent(schema)) return false;
79
+ return true;
80
+ }}
81
+ onlyRenderProperties
82
+ />
83
+ {props.extra}
84
+ </span>
85
+ );
86
+ const content = (
87
+ <RecursionField
88
+ schema={items}
89
+ name={index}
90
+ filterProperties={(schema) => {
91
+ if (isIndexComponent(schema)) return false;
92
+ if (isOperationComponent(schema)) return false;
93
+ return true;
94
+ }}
95
+ />
96
+ );
97
+ return (
98
+ <ArrayBase.Item key={index} index={index} record={() => field.value?.[index]}>
99
+ <Card
100
+ {...props}
101
+ onChange={() => {}}
102
+ className={cls(`${prefixCls}-item`, props.className)}
103
+ title={title}
104
+ extra={extra}
105
+ >
106
+ {content}
107
+ </Card>
108
+ </ArrayBase.Item>
109
+ );
110
+ });
111
+ };
112
+
113
+ const renderAddition = () => {
114
+ return schema.reduceProperties((addition, schema, key) => {
115
+ if (isAdditionComponent(schema)) {
116
+ return <RecursionField schema={schema} name={key} />;
117
+ }
118
+ return addition;
119
+ }, null);
120
+ };
121
+
122
+ const renderEmpty = () => {
123
+ if (dataSource?.length) return;
124
+ return (
125
+ <Card
126
+ {...props}
127
+ onChange={() => {}}
128
+ className={cls(`${prefixCls}-item`, props.className)}
129
+ title={props.title || field.title}
130
+ >
131
+ <Empty />
132
+ </Card>
133
+ );
134
+ };
135
+
136
+ return (
137
+ <ArrayBase {...props} onAdd={onAdd} onCopy={onCopy} onRemove={onRemove} onMoveUp={onMoveUp} onMoveDown={onMoveDown}>
138
+ {renderEmpty()}
139
+ {renderItems()}
140
+ {renderAddition()}
141
+ </ArrayBase>
142
+ );
143
+ });
144
+
145
+ ArrayCards.displayName = "ArrayCards";
146
+
147
+ ArrayBase.mixin(ArrayCards);
148
+
149
+ export default ArrayCards;
@@ -0,0 +1,15 @@
1
+ @root-entry-name: 'default';
2
+ @import (reference) '~antd/es/style/themes/index.less';
3
+
4
+ @array-base-prefix-cls: ~'@{ant-prefix}-formily-array-base';
5
+ @array-cards-prefix-cls: ~'@{ant-prefix}-formily-array-cards';
6
+
7
+ .@{array-cards-prefix-cls}-item {
8
+ margin-bottom: 10px !important;
9
+ }
10
+
11
+ .ant-card-extra {
12
+ .@{array-base-prefix-cls}-copy {
13
+ margin-left: 6px;
14
+ }
15
+ }
@@ -0,0 +1,4 @@
1
+ import 'antd/lib/card/style/index'
2
+ import 'antd/lib/empty/style/index'
3
+ import 'antd/lib/button/style/index'
4
+ import './style.less'
@@ -0,0 +1,411 @@
1
+ import React, { Fragment, useState, useRef, useEffect, createContext, useContext, useCallback } from "react";
2
+ import { Table, Pagination, Space, Select, Badge } from "antd";
3
+ import { PaginationProps } from "antd/lib/pagination";
4
+ import { TableProps, ColumnProps } from "antd/lib/table";
5
+ import { SelectProps } from "antd/lib/select";
6
+ import cls from "classnames";
7
+ import { GeneralField, FieldDisplayTypes, ArrayField } from "@formily/core";
8
+ import { useField, observer, useFieldSchema, RecursionField, ReactFC } from "@formily/react";
9
+ import { isArr, isBool, isFn } from "@formily/shared";
10
+ import { Schema } from "@formily/json-schema";
11
+ import { usePrefixCls, SortableContainer, SortableElement } from "c-formily-antd/lib/__builtins__";
12
+ import { ArrayBase, ArrayBaseMixins, IArrayBaseProps } from "../ArrayBase";
13
+
14
+ interface ObservableColumnSource {
15
+ field: GeneralField;
16
+ columnProps: ColumnProps<any>;
17
+ schema: Schema;
18
+ display: FieldDisplayTypes;
19
+ name: string;
20
+ }
21
+ interface IArrayTablePaginationProps extends PaginationProps {
22
+ dataSource?: any[];
23
+ showPagination?: boolean;
24
+ children?: (
25
+ dataSource: any[],
26
+ pagination: React.ReactNode,
27
+ options: {
28
+ startIndex: number;
29
+ },
30
+ ) => React.ReactElement;
31
+ }
32
+
33
+ interface IStatusSelectProps extends SelectProps<any> {
34
+ pageSize?: number;
35
+ }
36
+
37
+ type ComposedArrayTable = React.FC<React.PropsWithChildren<TableProps<any> & IArrayBaseProps>> &
38
+ ArrayBaseMixins & {
39
+ Column?: React.FC<React.PropsWithChildren<ColumnProps<any>>>;
40
+ };
41
+
42
+ interface PaginationAction {
43
+ totalPage?: number;
44
+ pageSize?: number;
45
+ showPagination?: boolean;
46
+ changePage?: (page: number) => void;
47
+ }
48
+
49
+ const SortableRow = SortableElement((props: any) => <tr {...props} />);
50
+ const SortableBody = SortableContainer((props: any) => <tbody {...props} />);
51
+
52
+ const isColumnComponent = (schema: Schema) => {
53
+ return schema["x-component"]?.indexOf("Column") > -1;
54
+ };
55
+
56
+ const isOperationsComponent = (schema: Schema) => {
57
+ return schema["x-component"]?.indexOf("Operations") > -1;
58
+ };
59
+
60
+ const isAdditionComponent = (schema: Schema) => {
61
+ return schema["x-component"]?.indexOf("Addition") > -1;
62
+ };
63
+
64
+ const useArrayTableSources = () => {
65
+ const arrayField = useField();
66
+ const schema = useFieldSchema();
67
+ const parseSources = (schema: Schema): ObservableColumnSource[] => {
68
+ if (isColumnComponent(schema) || isOperationsComponent(schema) || isAdditionComponent(schema)) {
69
+ if (!schema["x-component-props"]?.["dataIndex"] && !schema["name"]) return [];
70
+ const name = schema["x-component-props"]?.["dataIndex"] || schema["name"];
71
+ const field = arrayField.query(arrayField.address.concat(name)).take();
72
+ const columnProps = field?.component?.[1] || schema["x-component-props"] || {};
73
+ const display = field?.display || schema["x-display"] || "visible";
74
+ return [
75
+ {
76
+ name,
77
+ display,
78
+ field,
79
+ schema,
80
+ columnProps,
81
+ },
82
+ ];
83
+ } else if (schema.properties) {
84
+ return schema.reduceProperties((buf, schema) => {
85
+ return buf.concat(parseSources(schema));
86
+ }, []);
87
+ }
88
+ };
89
+
90
+ const parseArrayItems = (schema: Schema["items"]) => {
91
+ if (!schema) return [];
92
+ const sources: ObservableColumnSource[] = [];
93
+ const items = isArr(schema) ? schema : [schema];
94
+ return items.reduce((columns, schema) => {
95
+ const item = parseSources(schema);
96
+ if (item) {
97
+ return columns.concat(item);
98
+ }
99
+ return columns;
100
+ }, sources);
101
+ };
102
+
103
+ if (!schema) throw new Error("can not found schema object");
104
+
105
+ return parseArrayItems(schema.items);
106
+ };
107
+
108
+ const useArrayTableColumns = (
109
+ dataSource: any[],
110
+ field: ArrayField,
111
+ sources: ObservableColumnSource[],
112
+ ): TableProps<any>["columns"] => {
113
+ return sources.reduce((buf, { name, columnProps, schema, display }, key) => {
114
+ if (display !== "visible") return buf;
115
+ if (!isColumnComponent(schema)) return buf;
116
+ return buf.concat({
117
+ ...columnProps,
118
+ key,
119
+ dataIndex: name,
120
+ render: (value: any, record: any) => {
121
+ const index = dataSource?.indexOf(record);
122
+ const children = (
123
+ <ArrayBase.Item index={index} record={() => field?.value?.[index]}>
124
+ <RecursionField schema={schema} name={index} onlyRenderProperties />
125
+ </ArrayBase.Item>
126
+ );
127
+ return children;
128
+ },
129
+ });
130
+ }, []);
131
+ };
132
+
133
+ const useAddition = () => {
134
+ const schema = useFieldSchema();
135
+ return schema.reduceProperties((addition, schema, key) => {
136
+ if (isAdditionComponent(schema)) {
137
+ return <RecursionField schema={schema} name={key} />;
138
+ }
139
+ return addition;
140
+ }, null);
141
+ };
142
+
143
+ const schedulerRequest = {
144
+ request: null,
145
+ };
146
+
147
+ const StatusSelect: ReactFC<IStatusSelectProps> = observer(
148
+ (props) => {
149
+ const field = useField<ArrayField>();
150
+ const prefixCls = usePrefixCls("formily-array-table");
151
+ const errors = field.errors;
152
+ const parseIndex = (address: string) => {
153
+ return Number(address.slice(address.indexOf(field.address.toString()) + 1).match(/(\d+)/)?.[1]);
154
+ };
155
+ const options = props.options?.map(({ label, value }) => {
156
+ const val = Number(value);
157
+ const hasError = errors.some(({ address }) => {
158
+ const currentIndex = parseIndex(address);
159
+ const startIndex = (val - 1) * props.pageSize;
160
+ const endIndex = val * props.pageSize;
161
+ return currentIndex >= startIndex && currentIndex <= endIndex;
162
+ });
163
+ return {
164
+ label: hasError ? <Badge dot>{label}</Badge> : label,
165
+ value,
166
+ };
167
+ });
168
+
169
+ const width = String(options?.length).length * 15;
170
+
171
+ return (
172
+ <Select
173
+ value={props.value}
174
+ onChange={props.onChange}
175
+ options={options}
176
+ virtual
177
+ style={{
178
+ width: width < 60 ? 60 : width,
179
+ }}
180
+ className={cls(`${prefixCls}-status-select`, {
181
+ "has-error": errors?.length,
182
+ })}
183
+ />
184
+ );
185
+ },
186
+ {
187
+ scheduler: (update) => {
188
+ clearTimeout(schedulerRequest.request);
189
+ schedulerRequest.request = setTimeout(() => {
190
+ update();
191
+ }, 100);
192
+ },
193
+ },
194
+ );
195
+
196
+ const PaginationContext = createContext<PaginationAction>({});
197
+ const usePagination = () => {
198
+ return useContext(PaginationContext);
199
+ };
200
+
201
+ const ArrayTablePagination: ReactFC<IArrayTablePaginationProps> = (props) => {
202
+ const [current, setCurrent] = useState(1);
203
+ const prefixCls = usePrefixCls("formily-array-table");
204
+ const showPagination = props.showPagination ?? true;
205
+ const pageSize = props.pageSize || 10;
206
+ const size = props.size || "default";
207
+ const dataSource = props.dataSource || [];
208
+ const startIndex = (current - 1) * pageSize;
209
+ const endIndex = startIndex + pageSize - 1;
210
+ const total = dataSource?.length || 0;
211
+ const totalPage = Math.ceil(total / pageSize);
212
+ const pages = Array.from(new Array(totalPage)).map((_, index) => {
213
+ const page = index + 1;
214
+ return {
215
+ label: page,
216
+ value: page,
217
+ };
218
+ });
219
+ const handleChange = (current: number) => {
220
+ setCurrent(current);
221
+ };
222
+
223
+ useEffect(() => {
224
+ if (totalPage > 0 && totalPage < current) {
225
+ handleChange(totalPage);
226
+ }
227
+ }, [totalPage, current]);
228
+
229
+ const renderPagination = () => {
230
+ if (totalPage <= 1 || !showPagination) return;
231
+ return (
232
+ <div className={`${prefixCls}-pagination`}>
233
+ <Space>
234
+ <StatusSelect
235
+ value={current}
236
+ pageSize={pageSize}
237
+ onChange={handleChange}
238
+ options={pages}
239
+ notFoundContent={false}
240
+ />
241
+ <Pagination
242
+ {...props}
243
+ pageSize={pageSize}
244
+ current={current}
245
+ total={dataSource.length}
246
+ size={size}
247
+ showSizeChanger={false}
248
+ onChange={handleChange}
249
+ />
250
+ </Space>
251
+ </div>
252
+ );
253
+ };
254
+
255
+ return (
256
+ <Fragment>
257
+ <PaginationContext.Provider
258
+ value={{
259
+ totalPage,
260
+ pageSize,
261
+ changePage: handleChange,
262
+ showPagination,
263
+ }}
264
+ >
265
+ {props.children?.(
266
+ showPagination ? dataSource?.slice(startIndex, endIndex + 1) : dataSource,
267
+ renderPagination(),
268
+ { startIndex },
269
+ )}
270
+ </PaginationContext.Provider>
271
+ </Fragment>
272
+ );
273
+ };
274
+
275
+ const RowComp: ReactFC<React.HTMLAttributes<HTMLTableRowElement>> = (props) => {
276
+ const prefixCls = usePrefixCls("formily-array-table");
277
+ const index = props["data-row-key"] || 0;
278
+ return (
279
+ <SortableRow
280
+ lockAxis="y"
281
+ {...props}
282
+ index={index}
283
+ className={cls(props.className, `${prefixCls}-row-${index + 1}`)}
284
+ />
285
+ );
286
+ };
287
+
288
+ export const ArrayTable: ComposedArrayTable = observer((props) => {
289
+ const ref = useRef<HTMLDivElement>();
290
+ const field = useField<ArrayField>();
291
+ const prefixCls = usePrefixCls("formily-array-table");
292
+ const dataSource = Array.isArray(field.value) ? field.value.slice() : [];
293
+ const sources = useArrayTableSources();
294
+ const columns = useArrayTableColumns(dataSource, field, sources);
295
+ const pagination = isBool(props.pagination) ? { showPagination: props.pagination } : props.pagination;
296
+ const addition = useAddition();
297
+ const { onAdd, onCopy, onRemove, onMoveDown, onMoveUp, deletePopconfirm, deletePopconfirmTitle } = props;
298
+ const defaultRowKey = (record: any) => {
299
+ return dataSource.indexOf(record);
300
+ };
301
+ const addTdStyles = (id: number) => {
302
+ const node = ref.current?.querySelector(`.${prefixCls}-row-${id}`);
303
+ const helper = document.body.querySelector(`.${prefixCls}-sort-helper`);
304
+ if (!helper) return;
305
+ const tds = node?.querySelectorAll("td");
306
+ if (!tds) return;
307
+ requestAnimationFrame(() => {
308
+ helper.querySelectorAll("td").forEach((td, index) => {
309
+ if (tds[index]) {
310
+ td.style.width = getComputedStyle(tds[index]).width;
311
+ }
312
+ });
313
+ });
314
+ };
315
+ const getWrapperComp = useCallback(
316
+ (dataSource: any[], start: number) => (props: any) =>
317
+ (
318
+ <SortableBody
319
+ {...props}
320
+ start={start}
321
+ list={dataSource.slice()}
322
+ accessibility={{
323
+ container: ref.current || undefined,
324
+ }}
325
+ onSortStart={(event) => {
326
+ addTdStyles(event.active.id as number);
327
+ }}
328
+ onSortEnd={({ oldIndex, newIndex }) => {
329
+ field.move(oldIndex, newIndex);
330
+ }}
331
+ className={cls(`${prefixCls}-sort-helper`, props.className)}
332
+ />
333
+ ),
334
+ [field],
335
+ );
336
+
337
+ return (
338
+ <ArrayTablePagination {...pagination} dataSource={dataSource}>
339
+ {(dataSource, pager, { startIndex }) => (
340
+ <div ref={ref} className={prefixCls}>
341
+ <ArrayBase
342
+ {...props}
343
+ onAdd={onAdd}
344
+ onCopy={onCopy}
345
+ onRemove={onRemove}
346
+ onMoveUp={onMoveUp}
347
+ onMoveDown={onMoveDown}
348
+ >
349
+ <Table
350
+ size="small"
351
+ bordered
352
+ rowKey={defaultRowKey}
353
+ {...props}
354
+ onChange={() => {}}
355
+ pagination={false}
356
+ columns={columns}
357
+ dataSource={dataSource}
358
+ components={{
359
+ body: {
360
+ wrapper: getWrapperComp(dataSource, startIndex),
361
+ row: RowComp,
362
+ },
363
+ }}
364
+ />
365
+ <div style={{ marginTop: 5, marginBottom: 5 }}>{pager}</div>
366
+ {sources.map((column, key) => {
367
+ //专门用来承接对Column的状态管理
368
+ if (!isColumnComponent(column.schema)) return;
369
+ return React.createElement(RecursionField, {
370
+ name: column.name,
371
+ schema: column.schema,
372
+ onlyRenderSelf: true,
373
+ key,
374
+ });
375
+ })}
376
+ {addition}
377
+ </ArrayBase>
378
+ </div>
379
+ )}
380
+ </ArrayTablePagination>
381
+ );
382
+ });
383
+
384
+ ArrayTable.displayName = "ArrayTable";
385
+
386
+ ArrayTable.Column = () => {
387
+ return <Fragment />;
388
+ };
389
+
390
+ ArrayBase.mixin(ArrayTable);
391
+
392
+ const Addition: ArrayBaseMixins["Addition"] = (props) => {
393
+ const array = ArrayBase.useArray();
394
+ const { totalPage = 0, pageSize = 10, changePage, showPagination } = usePagination();
395
+ return (
396
+ <ArrayBase.Addition
397
+ {...props}
398
+ onClick={(e) => {
399
+ // 如果添加数据后将超过当前页,则自动切换到下一页
400
+ const total = array?.field?.value.length || 0;
401
+ if (showPagination && total === totalPage * pageSize + 1 && isFn(changePage)) {
402
+ changePage(totalPage + 1);
403
+ }
404
+ props.onClick?.(e);
405
+ }}
406
+ />
407
+ );
408
+ };
409
+ ArrayTable.Addition = Addition;
410
+
411
+ export default ArrayTable;
@@ -0,0 +1,53 @@
1
+ @root-entry-name: 'default';
2
+ @import (reference) '~antd/es/style/themes/index.less';
3
+
4
+ @array-table-prefix-cls: ~'@{ant-prefix}-formily-array-table';
5
+
6
+ .@{array-table-prefix-cls} {
7
+ .@{array-table-prefix-cls}-pagination {
8
+ display: flex;
9
+ justify-content: center;
10
+
11
+ .@{array-table-prefix-cls}-status-select.has-error {
12
+ .@{ant-prefix}-select-selector {
13
+ border-color: @error-color !important;
14
+ }
15
+ }
16
+ }
17
+
18
+ .@{ant-prefix}-table {
19
+ table {
20
+ overflow: hidden;
21
+ }
22
+
23
+ td {
24
+ visibility: visible;
25
+
26
+ .@{ant-prefix}-formily-item:not(.@{ant-prefix}-formily-item-feedback-layout-popover) {
27
+ margin-bottom: 0 !important;
28
+
29
+ .@{ant-prefix}-formily-item-help {
30
+ position: absolute;
31
+ font-size: 12px;
32
+ top: 100%;
33
+ background: #fff;
34
+ width: 100%;
35
+ margin-top: 3px;
36
+ padding: 3px;
37
+ z-index: 1;
38
+ border-radius: 3px;
39
+ box-shadow: 0 0 10px #eee;
40
+ animation: none;
41
+ transform: translateY(0);
42
+ opacity: 1;
43
+ }
44
+ }
45
+ }
46
+ }
47
+
48
+ .@{array-table-prefix-cls}-sort-helper {
49
+ background: #fff;
50
+ border: 1px solid #eee;
51
+ z-index: 10;
52
+ }
53
+ }
@@ -0,0 +1,7 @@
1
+ import 'antd/lib/table/style/index'
2
+ import 'antd/lib/button/style/index'
3
+ import 'antd/lib/select/style/index'
4
+ import 'antd/lib/space/style/index'
5
+ import 'antd/lib/badge/style/index'
6
+ import 'antd/lib/pagination/style/index'
7
+ import './style.less'
@@ -1,7 +1,10 @@
1
1
  .popup-box {
2
- position: fixed;
2
+ position: absolute;
3
3
  z-index: 9;
4
4
  min-width: 200px;
5
+ left: 50%;
6
+ top: 50%;
7
+ transform: translate(-50%, -50%);
5
8
  padding: 20px;
6
9
  border-radius: 4px;
7
10
  background-color: #fff;
@@ -1,4 +1,4 @@
1
- import { useRef, forwardRef, useImperativeHandle, useState } from "react";
1
+ import { useRef, forwardRef, useImperativeHandle, useState, useEffect } from "react";
2
2
  import { createPortal } from "react-dom";
3
3
  import { Button } from "antd";
4
4
 
@@ -15,6 +15,7 @@ export interface IPopupProps {
15
15
  schema;
16
16
  initialValues?: Object;
17
17
  };
18
+ mountElement?: HTMLElement;
18
19
  }
19
20
 
20
21
  export interface IData {
@@ -24,7 +25,7 @@ export interface IData {
24
25
  }
25
26
 
26
27
  export const Popup = forwardRef((props: IPopupProps, parentRef) => {
27
- const { formProps = { schema: RangeSchema } } = props;
28
+ const { formProps = { schema: RangeSchema }, mountElement } = props;
28
29
 
29
30
  const [data, setData] = useState<IData>();
30
31
 
@@ -62,9 +63,10 @@ export const Popup = forwardRef((props: IPopupProps, parentRef) => {
62
63
 
63
64
  const schema = mergeSchema(AddressSchema, formProps?.schema ?? RangeSchema);
64
65
 
66
+
65
67
  return data
66
68
  ? createPortal(
67
- <div className="popup-box" style={{ top: data?.top, left: data?.left }}>
69
+ <div className="popup-box">
68
70
  <div className="popup-body">
69
71
  {/* TODO: schema 中插入地址字段 text 格式 */}
70
72
  <div>地址:{data?.address}</div>
@@ -82,7 +84,7 @@ export const Popup = forwardRef((props: IPopupProps, parentRef) => {
82
84
  <Button onClick={onCancel}>取消</Button>
83
85
  </div>
84
86
  </div>,
85
- document.body,
87
+ mountElement || document.body,
86
88
  )
87
89
  : null;
88
90
  });
@@ -48,6 +48,7 @@ export const LocationListPicker = observer((props) => {
48
48
  const currentItemRef = useRef();
49
49
  const popupRef = useRef();
50
50
  const isEditingRef = useRef();
51
+ const mapBoxRef = useRef(null)
51
52
 
52
53
  useEffect(() => {
53
54
  return () => {
@@ -469,7 +470,7 @@ export const LocationListPicker = observer((props) => {
469
470
 
470
471
  return (
471
472
  <div className="location-list-picker">
472
- <div className="map-box">
473
+ <div className="map-box" ref={mapBoxRef}>
473
474
  <AMapCom init={mapInit} loading={loading} markerIconConf={markerIconConf} />
474
475
  {!readOnly && !disabled && <MapSearch setPoint={setPoint} isAutoSearch={isAutoSearch} />}
475
476
  {showTip && (
@@ -485,7 +486,7 @@ export const LocationListPicker = observer((props) => {
485
486
  新增地址
486
487
  </Button>
487
488
  )}
488
- <Popup ref={popupRef} {...popupProps} />
489
+ <Popup ref={popupRef} mountElement={mapBoxRef.current} {...popupProps} />
489
490
  </div>
490
491
  <AddrList
491
492
  {...listProps}
@@ -1,4 +1,6 @@
1
1
  import Text from "./Text";
2
+ export * from "./ArrayCards";
3
+ export * from "./ArrayTable";
2
4
 
3
5
  export * from "./Upload";
4
6