@simpleform/render 1.0.0 → 1.0.2
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/README.md +544 -3
- package/README_CN.md +556 -0
- package/lib/types.d.ts +3 -3
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -1,10 +1,551 @@
|
|
|
1
1
|
# `@simpleform/render`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
English | [中文说明](./README_CN.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@simpleform/render)
|
|
6
6
|
|
|
7
|
+
> A lightweight dynamic forms engine that makes it easy to dynamically render forms.
|
|
8
|
+
|
|
9
|
+
## Introduction
|
|
10
|
+
- Component Registration: Form controls used in `@simpleform/render` must be controlled components with `value` and `onChange` props.
|
|
11
|
+
- Component Description: `properties` supports rendering of object or array types, and adding nested object fields via the `properties` property.
|
|
12
|
+
- Component Rendering: `Form` component handles form values, `FormChildren` component handles form rendering, a `Form` component can support multiple `FormChildren` components rendering inside.
|
|
13
|
+
- Component linkage: All form properties can support string expressions to describe linkage conditions (except `properties`).
|
|
14
|
+
|
|
15
|
+
## install
|
|
16
|
+
- [Node.js](https://nodejs.org/en/) Version >= 18.18.2
|
|
17
|
+
- [react](https://nodejs.org/en/) Version >= 16.8.0
|
|
18
|
+
```bash
|
|
19
|
+
npm install @simpleform/render --save
|
|
20
|
+
# 或者
|
|
21
|
+
yarn add @simpleform/render
|
|
22
|
+
```
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
### 1. First register the basic components (Take antd@5.x as an example)
|
|
26
|
+
```javascript
|
|
27
|
+
// register
|
|
28
|
+
import FormRenderCore, { FormChildren as FormChildrenCore, FormNodeProps, FormRenderProps, FormChildrenProps, GenerateParams } from '@simpleform/render';
|
|
29
|
+
import '@simpleform/render/lib/css/main.css';
|
|
30
|
+
import React from 'react';
|
|
31
|
+
import dayjs from 'dayjs';
|
|
32
|
+
import {
|
|
33
|
+
Input,
|
|
34
|
+
InputNumber,
|
|
35
|
+
Checkbox,
|
|
36
|
+
DatePicker,
|
|
37
|
+
Mentions,
|
|
38
|
+
Radio,
|
|
39
|
+
Rate,
|
|
40
|
+
Select,
|
|
41
|
+
Slider,
|
|
42
|
+
Switch,
|
|
43
|
+
TimePicker,
|
|
44
|
+
} from 'antd';
|
|
45
|
+
|
|
46
|
+
export * from '@simpleform/render';
|
|
47
|
+
|
|
48
|
+
export const widgets = {
|
|
49
|
+
"Input": Input,
|
|
50
|
+
"Input.TextArea": Input.TextArea,
|
|
51
|
+
"Input.Password": Input.Password,
|
|
52
|
+
"Input.Search": Input.Search,
|
|
53
|
+
"InputNumber": InputNumber,
|
|
54
|
+
"Checkbox": Checkbox,
|
|
55
|
+
'Checkbox.Group': Checkbox.Group,
|
|
56
|
+
"DatePicker": DatePicker,
|
|
57
|
+
"DatePicker.RangePicker": DatePicker.RangePicker,
|
|
58
|
+
"Mentions": Mentions,
|
|
59
|
+
"Mentions.Option": Mentions.Option,
|
|
60
|
+
"Radio": Radio,
|
|
61
|
+
"Radio.Group": Radio.Group,
|
|
62
|
+
"Radio.Button": Radio.Button,
|
|
63
|
+
"Rate": Rate,
|
|
64
|
+
"Select": Select,
|
|
65
|
+
"Select.Option": Select.Option,
|
|
66
|
+
"TreeSelect": TreeSelect,
|
|
67
|
+
"Slider": Slider,
|
|
68
|
+
"Switch": Switch,
|
|
69
|
+
"TimePicker": TimePicker,
|
|
70
|
+
"TimePicker.RangePicker": TimePicker.RangePicker
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type CustomFormChildrenProps = FormChildrenProps<any>;
|
|
74
|
+
export function FormChildren(props: CustomFormChildrenProps) {
|
|
75
|
+
const { components, plugins, ...rest } = props;
|
|
76
|
+
return (
|
|
77
|
+
<FormChildrenCore
|
|
78
|
+
options={{ props: { autoComplete: 'off' } }}
|
|
79
|
+
components={{ ...widgets, ...components }}
|
|
80
|
+
plugins={{ ...plugins, dayjs }}
|
|
81
|
+
{...rest}
|
|
82
|
+
/>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
export type CustomFormRenderProps = FormRenderProps<any>;
|
|
86
|
+
export default function FormRender(props: CustomFormRenderProps) {
|
|
87
|
+
const { components, plugins, ...rest } = props;
|
|
88
|
+
return (
|
|
89
|
+
<FormRenderCore
|
|
90
|
+
options={{ props: { autoComplete: 'off' } }}
|
|
91
|
+
components={{ ...widgets, ...components }}
|
|
92
|
+
plugins={{ ...plugins, dayjs }}
|
|
93
|
+
{...rest}
|
|
94
|
+
/>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
### 2. Introduce the components that were registered in the first step.
|
|
99
|
+
```javascript
|
|
100
|
+
import { Button } from 'antd';
|
|
101
|
+
import React, { useState } from 'react';
|
|
102
|
+
import FormRender, { useSimpleForm, useSimpleFormRender } from './form-render';
|
|
103
|
+
export default function Demo5(props) {
|
|
104
|
+
|
|
105
|
+
const watch = {
|
|
106
|
+
'name2': (newValue, oldValue) => {
|
|
107
|
+
console.log(newValue, oldValue)
|
|
108
|
+
},
|
|
109
|
+
'name3[0]': (newValue, oldValue) => {
|
|
110
|
+
console.log(newValue, oldValue)
|
|
111
|
+
},
|
|
112
|
+
'name4': (newValue, oldValue) => {
|
|
113
|
+
console.log(newValue, oldValue)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const properties = {
|
|
118
|
+
name1: {
|
|
119
|
+
label: "readonly",
|
|
120
|
+
readOnly: true,
|
|
121
|
+
readOnlyRender: "readonly component",
|
|
122
|
+
initialValue: 1111,
|
|
123
|
+
hidden: '{{formvalues && formvalues.name6 == true}}',
|
|
124
|
+
type: 'Input',
|
|
125
|
+
props: {}
|
|
126
|
+
},
|
|
127
|
+
name2: {
|
|
128
|
+
label: "input",
|
|
129
|
+
rules: [{ required: true, message: 'input empty' }],
|
|
130
|
+
initialValue: 1,
|
|
131
|
+
hidden: '{{formvalues && formvalues.name6 == true}}',
|
|
132
|
+
type: 'Input',
|
|
133
|
+
props: {}
|
|
134
|
+
},
|
|
135
|
+
name3: {
|
|
136
|
+
// type: '',
|
|
137
|
+
// props: {},
|
|
138
|
+
properties: [{
|
|
139
|
+
label: 'list[0]',
|
|
140
|
+
rules: [{ required: true, message: 'list[0] empty' }],
|
|
141
|
+
initialValue: { label: 'option1', value: '1', key: '1' },
|
|
142
|
+
type: 'Select',
|
|
143
|
+
props: {
|
|
144
|
+
labelInValue: true,
|
|
145
|
+
style: { width: '100%' },
|
|
146
|
+
children: [
|
|
147
|
+
{ type: 'Select.Option', props: { key: 1, value: '1', children: 'option1' } },
|
|
148
|
+
{ type: 'Select.Option', props: { key: 2, value: '2', children: 'option2' } }
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
}, {
|
|
152
|
+
label: 'list[1]',
|
|
153
|
+
rules: [{ required: true, message: 'list[1] empty' }],
|
|
154
|
+
type: 'Select',
|
|
155
|
+
props: {
|
|
156
|
+
labelInValue: true,
|
|
157
|
+
style: { width: '100%' },
|
|
158
|
+
children: [
|
|
159
|
+
{ type: 'Select.Option', props: { key: 1, value: '1', children: 'option1' } },
|
|
160
|
+
{ type: 'Select.Option', props: { key: 2, value: '2', children: 'option2' } }
|
|
161
|
+
]
|
|
162
|
+
}
|
|
163
|
+
}]
|
|
164
|
+
},
|
|
165
|
+
name4: {
|
|
166
|
+
// type: '',
|
|
167
|
+
// props: {},
|
|
168
|
+
properties: {
|
|
169
|
+
first: {
|
|
170
|
+
label: 'first',
|
|
171
|
+
rules: [{ required: true, message: 'first empty' }],
|
|
172
|
+
type: 'Select',
|
|
173
|
+
props: {
|
|
174
|
+
style: { width: '100%' },
|
|
175
|
+
children: [{ type: 'Select.Option', props: { key: 1, value: '1', children: 'option1' } }]
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
second: {
|
|
179
|
+
label: 'second',
|
|
180
|
+
rules: [{ required: true, message: 'second empty' }],
|
|
181
|
+
type: 'Select',
|
|
182
|
+
props: {
|
|
183
|
+
style: { width: '100%' },
|
|
184
|
+
children: [{ type: 'Select.Option', props: { key: 1, value: '1', children: 'option1' } }]
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
name5: {
|
|
190
|
+
label: 'name5',
|
|
191
|
+
initialValue: { span: 12 },
|
|
192
|
+
valueSetter: "{{(value)=> (value && value['span'])}}",
|
|
193
|
+
valueGetter: "{{(value) => ({span: value})}}",
|
|
194
|
+
type: 'Select',
|
|
195
|
+
props: {
|
|
196
|
+
style: { width: '100%' },
|
|
197
|
+
children: [
|
|
198
|
+
{ type: 'Select.Option', props: { key: 1, value: 12, children: 'option1' } },
|
|
199
|
+
{ type: 'Select.Option', props: { key: 2, value: 6, children: 'option2' } },
|
|
200
|
+
{ type: 'Select.Option', props: { key: 3, value: 4, children: 'option3' } }
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
name6: {
|
|
205
|
+
label: 'checkbox',
|
|
206
|
+
valueProp: 'checked',
|
|
207
|
+
initialValue: true,
|
|
208
|
+
rules: [{ required: true, message: 'checkbox empty' }],
|
|
209
|
+
type: 'Checkbox',
|
|
210
|
+
props: {
|
|
211
|
+
style: { width: '100%' },
|
|
212
|
+
children: 'option'
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const form = useSimpleForm();
|
|
218
|
+
// const formrender = useSimpleFormRender();
|
|
219
|
+
|
|
220
|
+
const onSubmit = async (e) => {
|
|
221
|
+
e?.preventDefault?.();
|
|
222
|
+
const result = await form.validate();
|
|
223
|
+
console.log(result, 'result');
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<div>
|
|
228
|
+
<FormRender
|
|
229
|
+
form={form}
|
|
230
|
+
// formrender={formrender}
|
|
231
|
+
properties={properties}
|
|
232
|
+
watch={watch} />
|
|
233
|
+
<div style={{ marginLeft: '120px' }}>
|
|
234
|
+
<Button onClick={onSubmit}>submit</Button>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
### 3. Multi-module rendering
|
|
241
|
+
The form engine also supports rendering by multiple `FormChildren` components, which then unify the processing of form values by the `Form` component.
|
|
242
|
+
- `useSimpleForm`: Creates a managed instance of a form value.
|
|
243
|
+
- `useSimpleFormRender`: creates an instance that renders the form.
|
|
244
|
+
```javascript
|
|
245
|
+
import React, { useState } from 'react';
|
|
246
|
+
import { FormChildren, Form, useSimpleForm } from './form-render';
|
|
247
|
+
import { Button } from 'antd';
|
|
248
|
+
export default function Demo(props) {
|
|
249
|
+
|
|
250
|
+
const properties1 = {
|
|
251
|
+
part1: {
|
|
252
|
+
label: "part1input",
|
|
253
|
+
rules: [{ required: true, message: 'name1 empty' }],
|
|
254
|
+
initialValue: 1,
|
|
255
|
+
type: 'Input',
|
|
256
|
+
props: {}
|
|
257
|
+
},
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const properties2 = {
|
|
261
|
+
part2: {
|
|
262
|
+
label: "part2input",
|
|
263
|
+
rules: [{ required: true, message: 'name1 empty' }],
|
|
264
|
+
initialValue: 1,
|
|
265
|
+
type: 'Input',
|
|
266
|
+
props: {}
|
|
267
|
+
},
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const form = useSimpleForm();
|
|
271
|
+
// const formrender1 = useSimpleFormRender()
|
|
272
|
+
// const formrender2 = useSimpleFormRender()
|
|
273
|
+
|
|
274
|
+
const onSubmit = async (e) => {
|
|
275
|
+
e?.preventDefault?.();
|
|
276
|
+
const result = await form.validate();
|
|
277
|
+
console.log(result, 'result');
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
return (
|
|
281
|
+
<div style={{ padding: '0 8px' }}>
|
|
282
|
+
<Form form={form}>
|
|
283
|
+
<div>
|
|
284
|
+
<p>part1</p>
|
|
285
|
+
<FormChildren
|
|
286
|
+
// formrender={formrender1}
|
|
287
|
+
properties={properties1}
|
|
288
|
+
/>
|
|
289
|
+
</div>
|
|
290
|
+
<div>
|
|
291
|
+
<p>part2</p>
|
|
292
|
+
<FormChildren
|
|
293
|
+
// formrender={formrender2}
|
|
294
|
+
properties={properties2}
|
|
295
|
+
/>
|
|
296
|
+
</div>
|
|
297
|
+
</Form>
|
|
298
|
+
<div style={{ marginLeft: '120px' }}>
|
|
299
|
+
<Button onClick={onSubmit}>submit</Button>
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
### 4. List Demo
|
|
306
|
+
支持数组渲染
|
|
307
|
+
```javascript
|
|
308
|
+
import React, { useState } from 'react';
|
|
309
|
+
import FormRender, { useSimpleForm } from './form-render';
|
|
310
|
+
import { Button } from 'antd';
|
|
311
|
+
export default function Demo(props) {
|
|
312
|
+
const form = useSimpleForm();
|
|
313
|
+
const properties =
|
|
314
|
+
[
|
|
315
|
+
{
|
|
316
|
+
label: "list-0",
|
|
317
|
+
rules: [{ required: true, message: 'name1 empty' }],
|
|
318
|
+
initialValue: 1,
|
|
319
|
+
type: 'Input',
|
|
320
|
+
props: {}
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
label: "list-1",
|
|
324
|
+
rules: [{ required: true, message: 'name1 empty' }],
|
|
325
|
+
initialValue: 2,
|
|
326
|
+
type: 'Input',
|
|
327
|
+
props: {}
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
label: "list-2",
|
|
331
|
+
rules: [{ required: true, message: 'name1 empty' }],
|
|
332
|
+
initialValue: 3,
|
|
333
|
+
type: 'Input',
|
|
334
|
+
props: {}
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
label: "list-3",
|
|
338
|
+
rules: [{ required: true, message: 'name1 empty' }],
|
|
339
|
+
initialValue: 4,
|
|
340
|
+
type: 'Input',
|
|
341
|
+
props: {}
|
|
342
|
+
},
|
|
343
|
+
];
|
|
344
|
+
|
|
345
|
+
const onSubmit = async (e) => {
|
|
346
|
+
e?.preventDefault?.();
|
|
347
|
+
const result = await form.validate();
|
|
348
|
+
console.log(result, 'result');
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
return (
|
|
352
|
+
<div style={{ padding: '0 8px' }}>
|
|
353
|
+
<FormRender
|
|
354
|
+
form={form}
|
|
355
|
+
properties={properties}
|
|
356
|
+
/>
|
|
357
|
+
<div style={{ marginLeft: '120px' }}>
|
|
358
|
+
<Button onClick={onSubmit}>submit</Button>
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
## API
|
|
365
|
+
|
|
366
|
+
### Form`s props
|
|
367
|
+
Sourced from [@simpleform/form](... /form);
|
|
368
|
+
|
|
369
|
+
### FormChildren's props
|
|
370
|
+
- `properties`: `{ [name: string]: FormNodeProps } | FormNodeProps[]` Renders the form's DSL form json data
|
|
371
|
+
- `components`: registers all components in the form.
|
|
372
|
+
- `plugins`: foreign libraries to be introduced in the form.
|
|
373
|
+
- `options`: `GenerateFormNodeProps | ((field: GenerateFormNodeProps) => any)` Parameter information passed to the form node components. The priority is lower than the form node's own parameters
|
|
374
|
+
- `renderList`: Provides a function to customize the rendered list.
|
|
375
|
+
- `renderItem`: provides a function to customize the rendering of the node.
|
|
376
|
+
- `onPropertiesChange`: `(newValue: PropertiesData) => void;` `Properties` change callback function.
|
|
377
|
+
- `formrender`: The form class responsible for rendering. Created by `useSimpleFormRender()`, optional.
|
|
378
|
+
- `form`: Class responsible for the value of the form. Created via `useSimpleForm()`, optional.
|
|
379
|
+
- `uneval`: does not execute string expressions in the form.
|
|
380
|
+
- `plugin`: External library to be introduced in the string expression.
|
|
381
|
+
|
|
382
|
+
### SimpleFormRender's Methods
|
|
383
|
+
- `updateItemByPath`: `(data?: any, path?: string, attributeName?: string) => void` Updates the node corresponding to the path `path`, if updating a specific attribute in the node then the `attributeName` parameter is required.
|
|
384
|
+
- `setItemByPath`: `(data?: any, path?: string, attributeName?: string) => void` Sets the node corresponding to the path `path`, the `attributeName` parameter is required if you are updating a specific attribute in the node.
|
|
385
|
+
- `updateNameByPath`: `(newName?: string, path: string) => void` Updates the name key of the specified path.
|
|
386
|
+
- `delItemByPath`: `(path?: string, attributeName?: string) => void` Deletes the node corresponding to the path `path`, if deleting a specific attribute in the node then the `attributeName` parameter is required.
|
|
387
|
+
- `insertItemByIndex`: `(data: InsertItemType, index?: number, parent?: { path?: string, attributeName?: string }) => void` Adds an option based on the serial number and the path of the parent node.
|
|
388
|
+
- `getItemByPath`: `(path?: string, attributeName?: string) => void` Get the node that corresponds to the path `path`, or the `attributeName` parameter if it is a specific attribute in the node.
|
|
389
|
+
- `moveItemByPath`: `(from: { parent?: string, index: number }, to: { parent?: string, index?: number })` Swap options in the tree from one location to another.
|
|
390
|
+
- `setProperties`: `(data?: Partial<FormNodeProps>) => void` sets `properties`.
|
|
391
|
+
|
|
392
|
+
### Hooks
|
|
393
|
+
- `useSimpleFormRender()`: create `new SimpleFormRender()`.
|
|
394
|
+
- `useSimpleForm(defaultValues)`: create `new SimpleForm()`
|
|
395
|
+
|
|
396
|
+
## Other
|
|
397
|
+
|
|
398
|
+
### Properties structure description
|
|
399
|
+
Each item in the `properties` property is a form node, and the nodes are categorized into nested nodes and control nodes.
|
|
400
|
+
- Nested nodes.
|
|
401
|
+
Nodes that have a `properties` property that describes which component the node is, via the `type` and `props` fields, and do not carry a form field component (`Form.Item`).
|
|
402
|
+
- Node.
|
|
403
|
+
A node with no `properties` attribute that carries the form field component (`Form.Item`) by default.
|
|
404
|
+
```javascript
|
|
405
|
+
const properties = {
|
|
406
|
+
name3: {
|
|
407
|
+
// Nested nodes
|
|
408
|
+
// type: '',
|
|
409
|
+
// props: {},
|
|
410
|
+
properties: {
|
|
411
|
+
// Control node
|
|
412
|
+
first: {
|
|
413
|
+
label: 'first',
|
|
414
|
+
rules: [{ required: true, message: 'first empty' }],
|
|
415
|
+
type: 'Select',
|
|
416
|
+
props: {
|
|
417
|
+
style: { width: '100%' },
|
|
418
|
+
children: [{ type: 'Select.Option', props: { key: 1, value: '1', children: 'option1' } }]
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
},
|
|
423
|
+
}
|
|
7
424
|
```
|
|
425
|
+
- Form Node's types
|
|
426
|
+
```javascript
|
|
427
|
+
export interface FormComponent {
|
|
428
|
+
type?: string;
|
|
429
|
+
props?: any & { children?: any | Array<FormComponent> };
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export type UnionComponent<P> =
|
|
433
|
+
| React.ComponentType<P>
|
|
434
|
+
| React.ForwardRefExoticComponent<P>
|
|
435
|
+
| React.FC<P>
|
|
436
|
+
| keyof React.ReactHTML;
|
|
8
437
|
|
|
9
|
-
//
|
|
438
|
+
// Component
|
|
439
|
+
export type CustomUnionType = FormComponent | Array<FormComponent> | UnionComponent<any> | Function | ReactNode
|
|
440
|
+
// FormRender's properties
|
|
441
|
+
export type PropertiesData = { [name: string]: FormNodeProps } | FormNodeProps[]
|
|
442
|
+
// field's props(String expressions after compilation)
|
|
443
|
+
export type GenerateFormNodeProps<T = {}> = FormComponent & FormItemProps & T & {
|
|
444
|
+
ignore?: boolean; // Mark the current node as a non-form node
|
|
445
|
+
inside?: CustomUnionType; // Nested components within nodes
|
|
446
|
+
outside?: CustomUnionType; // Nested components in the outer layer of the node
|
|
447
|
+
readOnly?: boolean; // read-only mode
|
|
448
|
+
readOnlyRender?: CustomUnionType; // Read-only mode rendering
|
|
449
|
+
typeRender?: CustomUnionType; // Registering components for custom rendering
|
|
450
|
+
properties?: PropertiesData;
|
|
451
|
+
hidden?: boolean;
|
|
452
|
+
}
|
|
453
|
+
// field's props(String expressions before compilation)
|
|
454
|
+
export type FormNodeProps = {
|
|
455
|
+
[key in keyof GenerateFormNodeProps]: key extends 'rules' ?
|
|
456
|
+
(string | Array<{ [key in keyof FormRule]: FormRule[key] | string }> | GenerateFormNodeProps[key])
|
|
457
|
+
: (key extends 'properties' ? GenerateFormNodeProps[key] : (string | GenerateFormNodeProps[key]))
|
|
458
|
+
}
|
|
10
459
|
```
|
|
460
|
+
### parameter injection
|
|
461
|
+
- The properties of the form node are set globally:
|
|
462
|
+
```javascript
|
|
463
|
+
|
|
464
|
+
<FormRender
|
|
465
|
+
options={{
|
|
466
|
+
layout: 'vertical',
|
|
467
|
+
props: { disabled: true }
|
|
468
|
+
}}
|
|
469
|
+
/>
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
- Parameters received by the registration component of the form:
|
|
473
|
+
```javascript
|
|
474
|
+
export interface GenerateParams<T = {}> {
|
|
475
|
+
name?: string;
|
|
476
|
+
path?: string;
|
|
477
|
+
field?: GenerateFormNodeProps<T>;
|
|
478
|
+
parent?: { name?: string; path?: string, field?: GenerateFormNodeProps<T>; };
|
|
479
|
+
formrender?: SimpleFormRender;
|
|
480
|
+
form?: SimpleForm;
|
|
481
|
+
};
|
|
482
|
+
```
|
|
483
|
+
### Path path rules involved in forms
|
|
484
|
+
Forms are allowed to be nested, so the form involves looking for a certain property. The paths follow certain rules
|
|
485
|
+
Example:
|
|
486
|
+
- `a[0]` denotes the first option below the array `a`
|
|
487
|
+
- `a.b` denotes the `b` attribute of the `a` object
|
|
488
|
+
- `a[0].b` represents the `b` attribute of the first option below the array `a`
|
|
489
|
+
|
|
490
|
+
### String Expression Usage
|
|
491
|
+
Property fields in a form node can support string expressions for linkage, except for `properties`.
|
|
492
|
+
1. Quick use: computational expressions wrapping target attribute values in `{{` and `}}`
|
|
493
|
+
```javascript
|
|
494
|
+
const properties = {
|
|
495
|
+
name1: {
|
|
496
|
+
label: 'name1',
|
|
497
|
+
valueProp: 'checked',
|
|
498
|
+
initialValue: true,
|
|
499
|
+
type: 'Checkbox',
|
|
500
|
+
props: {
|
|
501
|
+
children: 'option'
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
name2: {
|
|
505
|
+
label: "name2",
|
|
506
|
+
rules: '{{[{ required: formvalues && formvalues.name1 === true, message: "name2 empty" }]}}',
|
|
507
|
+
initialValue: 1,
|
|
508
|
+
type: 'Input',
|
|
509
|
+
props: {}
|
|
510
|
+
},
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// OR
|
|
514
|
+
|
|
515
|
+
const properties = {
|
|
516
|
+
name1: {
|
|
517
|
+
label: 'name1',
|
|
518
|
+
valueProp: 'checked',
|
|
519
|
+
initialValue: true,
|
|
520
|
+
type: 'Checkbox',
|
|
521
|
+
props: {
|
|
522
|
+
children: 'option'
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
name2: {
|
|
526
|
+
label: "name2",
|
|
527
|
+
hidden: '{{formvalues && formvalues.name1 === true}}',
|
|
528
|
+
initialValue: 1,
|
|
529
|
+
type: 'Input',
|
|
530
|
+
props: {}
|
|
531
|
+
},
|
|
532
|
+
}
|
|
533
|
+
```
|
|
534
|
+
2. Rules for the use of string expressions
|
|
535
|
+
- A string has and can have only one pair of `{{` and `}}`.
|
|
536
|
+
- In addition to the three built-in variables (`form` (i.e., `useSimpleForm()`), `formrender` (i.e., `useSimpleFormRender()`), `formvalues`)), you can introduce an external variable via `plugins` and then reference the variable name directly in the string expression. in a string expression.
|
|
537
|
+
```javascript
|
|
538
|
+
import dayjs from 'dayjs';
|
|
539
|
+
import FormRender from "./form-render";
|
|
540
|
+
|
|
541
|
+
const properties = {
|
|
542
|
+
name3: {
|
|
543
|
+
label: "name3",
|
|
544
|
+
initialValue: "{{dayjs().format('YYYY-MM-DD')}}",
|
|
545
|
+
type: 'Input',
|
|
546
|
+
props: {}
|
|
547
|
+
},
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
<FormRender properties={properties} plugins={{ dayjs }} />
|
|
551
|
+
```
|
package/README_CN.md
ADDED
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
# `@simpleform/render`
|
|
2
|
+
|
|
3
|
+
[English](./README.md) | 中文说明
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@simpleform/render)
|
|
6
|
+
|
|
7
|
+
> 轻量级动态表单引擎,实现动态渲染表单很简单.
|
|
8
|
+
|
|
9
|
+
## 介绍
|
|
10
|
+
- 组件注册: 在`@simpleform/render`中使用的表单控件必须是具有`value`和`onChange`两个props的受控组件.
|
|
11
|
+
- 组件描述:`properties`支持对象或者数组类型的渲染,支持通过`properties`属性添加嵌套对象字段。
|
|
12
|
+
- 组件渲染:`Form`组件处理表单的值, `FormChildren`组件处理表单的渲染, 一个`Form`组件可以支持多个`FormChildren`组件在内部渲染.
|
|
13
|
+
- 组件联动:表单属性均可以支持字符串表达式描述联动条件(`properties`除外).
|
|
14
|
+
|
|
15
|
+
## 安装
|
|
16
|
+
- [Node.js](https://nodejs.org/en/) Version >= 18.18.2
|
|
17
|
+
- [react](https://nodejs.org/en/) Version >= 16.8.0
|
|
18
|
+
```bash
|
|
19
|
+
npm install @simpleform/render --save
|
|
20
|
+
# 或者
|
|
21
|
+
yarn add @simpleform/render
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 快速启动
|
|
25
|
+
|
|
26
|
+
### 1.首先注册基本组件(以antd@5.x组件库为例)
|
|
27
|
+
```javascript
|
|
28
|
+
// register
|
|
29
|
+
import FormRenderCore, { FormChildren as FormChildrenCore, FormNodeProps, FormRenderProps, FormChildrenProps, GenerateParams } from '@simpleform/render';
|
|
30
|
+
import '@simpleform/render/lib/css/main.css';
|
|
31
|
+
import React from 'react';
|
|
32
|
+
import dayjs from 'dayjs';
|
|
33
|
+
import {
|
|
34
|
+
Input,
|
|
35
|
+
InputNumber,
|
|
36
|
+
Checkbox,
|
|
37
|
+
DatePicker,
|
|
38
|
+
Mentions,
|
|
39
|
+
Radio,
|
|
40
|
+
Rate,
|
|
41
|
+
Select,
|
|
42
|
+
Slider,
|
|
43
|
+
Switch,
|
|
44
|
+
TimePicker,
|
|
45
|
+
} from 'antd';
|
|
46
|
+
|
|
47
|
+
export * from '@simpleform/render';
|
|
48
|
+
|
|
49
|
+
export const widgets = {
|
|
50
|
+
"Input": Input,
|
|
51
|
+
"Input.TextArea": Input.TextArea,
|
|
52
|
+
"Input.Password": Input.Password,
|
|
53
|
+
"Input.Search": Input.Search,
|
|
54
|
+
"InputNumber": InputNumber,
|
|
55
|
+
"Checkbox": Checkbox,
|
|
56
|
+
'Checkbox.Group': Checkbox.Group,
|
|
57
|
+
"DatePicker": DatePicker,
|
|
58
|
+
"DatePicker.RangePicker": DatePicker.RangePicker,
|
|
59
|
+
"Mentions": Mentions,
|
|
60
|
+
"Mentions.Option": Mentions.Option,
|
|
61
|
+
"Radio": Radio,
|
|
62
|
+
"Radio.Group": Radio.Group,
|
|
63
|
+
"Radio.Button": Radio.Button,
|
|
64
|
+
"Rate": Rate,
|
|
65
|
+
"Select": Select,
|
|
66
|
+
"Select.Option": Select.Option,
|
|
67
|
+
"TreeSelect": TreeSelect,
|
|
68
|
+
"Slider": Slider,
|
|
69
|
+
"Switch": Switch,
|
|
70
|
+
"TimePicker": TimePicker,
|
|
71
|
+
"TimePicker.RangePicker": TimePicker.RangePicker
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export type CustomFormChildrenProps = FormChildrenProps<any>;
|
|
75
|
+
export function FormChildren(props: CustomFormChildrenProps) {
|
|
76
|
+
const { components, plugins, ...rest } = props;
|
|
77
|
+
return (
|
|
78
|
+
<FormChildrenCore
|
|
79
|
+
options={{ props: { autoComplete: 'off' } }}
|
|
80
|
+
components={{ ...widgets, ...components }}
|
|
81
|
+
plugins={{ ...plugins, dayjs }}
|
|
82
|
+
{...rest}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
export type CustomFormRenderProps = FormRenderProps<any>;
|
|
87
|
+
export default function FormRender(props: CustomFormRenderProps) {
|
|
88
|
+
const { components, plugins, ...rest } = props;
|
|
89
|
+
return (
|
|
90
|
+
<FormRenderCore
|
|
91
|
+
options={{ props: { autoComplete: 'off' } }}
|
|
92
|
+
components={{ ...widgets, ...components }}
|
|
93
|
+
plugins={{ ...plugins, dayjs }}
|
|
94
|
+
{...rest}
|
|
95
|
+
/>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
### 2. 引入第一步已经注册完的组件
|
|
100
|
+
```javascript
|
|
101
|
+
import { Button } from 'antd';
|
|
102
|
+
import React, { useState } from 'react';
|
|
103
|
+
import FormRender, { useSimpleForm, useSimpleFormRender } from './form-render';
|
|
104
|
+
export default function Demo5(props) {
|
|
105
|
+
|
|
106
|
+
const watch = {
|
|
107
|
+
'name2': (newValue, oldValue) => {
|
|
108
|
+
console.log(newValue, oldValue)
|
|
109
|
+
},
|
|
110
|
+
'name3[0]': (newValue, oldValue) => {
|
|
111
|
+
console.log(newValue, oldValue)
|
|
112
|
+
},
|
|
113
|
+
'name4': (newValue, oldValue) => {
|
|
114
|
+
console.log(newValue, oldValue)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const properties = {
|
|
119
|
+
name1: {
|
|
120
|
+
label: "readonly",
|
|
121
|
+
readOnly: true,
|
|
122
|
+
readOnlyRender: "readonly component",
|
|
123
|
+
initialValue: 1111,
|
|
124
|
+
hidden: '{{formvalues && formvalues.name6 == true}}',
|
|
125
|
+
type: 'Input',
|
|
126
|
+
props: {}
|
|
127
|
+
},
|
|
128
|
+
name2: {
|
|
129
|
+
label: "input",
|
|
130
|
+
rules: [{ required: true, message: 'input empty' }],
|
|
131
|
+
initialValue: 1,
|
|
132
|
+
hidden: '{{formvalues && formvalues.name6 == true}}',
|
|
133
|
+
type: 'Input',
|
|
134
|
+
props: {}
|
|
135
|
+
},
|
|
136
|
+
name3: {
|
|
137
|
+
// type: '',
|
|
138
|
+
// props: {},
|
|
139
|
+
properties: [{
|
|
140
|
+
label: 'list[0]',
|
|
141
|
+
rules: [{ required: true, message: 'list[0] empty' }],
|
|
142
|
+
initialValue: { label: 'option1', value: '1', key: '1' },
|
|
143
|
+
type: 'Select',
|
|
144
|
+
props: {
|
|
145
|
+
labelInValue: true,
|
|
146
|
+
style: { width: '100%' },
|
|
147
|
+
children: [
|
|
148
|
+
{ type: 'Select.Option', props: { key: 1, value: '1', children: 'option1' } },
|
|
149
|
+
{ type: 'Select.Option', props: { key: 2, value: '2', children: 'option2' } }
|
|
150
|
+
]
|
|
151
|
+
}
|
|
152
|
+
}, {
|
|
153
|
+
label: 'list[1]',
|
|
154
|
+
rules: [{ required: true, message: 'list[1] empty' }],
|
|
155
|
+
type: 'Select',
|
|
156
|
+
props: {
|
|
157
|
+
labelInValue: true,
|
|
158
|
+
style: { width: '100%' },
|
|
159
|
+
children: [
|
|
160
|
+
{ type: 'Select.Option', props: { key: 1, value: '1', children: 'option1' } },
|
|
161
|
+
{ type: 'Select.Option', props: { key: 2, value: '2', children: 'option2' } }
|
|
162
|
+
]
|
|
163
|
+
}
|
|
164
|
+
}]
|
|
165
|
+
},
|
|
166
|
+
name4: {
|
|
167
|
+
// type: '',
|
|
168
|
+
// props: {},
|
|
169
|
+
properties: {
|
|
170
|
+
first: {
|
|
171
|
+
label: 'first',
|
|
172
|
+
rules: [{ required: true, message: 'first empty' }],
|
|
173
|
+
type: 'Select',
|
|
174
|
+
props: {
|
|
175
|
+
style: { width: '100%' },
|
|
176
|
+
children: [{ type: 'Select.Option', props: { key: 1, value: '1', children: 'option1' } }]
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
second: {
|
|
180
|
+
label: 'second',
|
|
181
|
+
rules: [{ required: true, message: 'second empty' }],
|
|
182
|
+
type: 'Select',
|
|
183
|
+
props: {
|
|
184
|
+
style: { width: '100%' },
|
|
185
|
+
children: [{ type: 'Select.Option', props: { key: 1, value: '1', children: 'option1' } }]
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
name5: {
|
|
191
|
+
label: 'name5',
|
|
192
|
+
initialValue: { span: 12 },
|
|
193
|
+
valueSetter: "{{(value)=> (value && value['span'])}}",
|
|
194
|
+
valueGetter: "{{(value) => ({span: value})}}",
|
|
195
|
+
type: 'Select',
|
|
196
|
+
props: {
|
|
197
|
+
style: { width: '100%' },
|
|
198
|
+
children: [
|
|
199
|
+
{ type: 'Select.Option', props: { key: 1, value: 12, children: 'option1' } },
|
|
200
|
+
{ type: 'Select.Option', props: { key: 2, value: 6, children: 'option2' } },
|
|
201
|
+
{ type: 'Select.Option', props: { key: 3, value: 4, children: 'option3' } }
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
name6: {
|
|
206
|
+
label: 'checkbox',
|
|
207
|
+
valueProp: 'checked',
|
|
208
|
+
initialValue: true,
|
|
209
|
+
rules: [{ required: true, message: 'checkbox empty' }],
|
|
210
|
+
type: 'Checkbox',
|
|
211
|
+
props: {
|
|
212
|
+
style: { width: '100%' },
|
|
213
|
+
children: 'option'
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const form = useSimpleForm();
|
|
219
|
+
// const formrender = useSimpleFormRender();
|
|
220
|
+
|
|
221
|
+
const onSubmit = async (e) => {
|
|
222
|
+
e?.preventDefault?.();
|
|
223
|
+
const result = await form.validate();
|
|
224
|
+
console.log(result, 'result');
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<div>
|
|
229
|
+
<FormRender
|
|
230
|
+
form={form}
|
|
231
|
+
// formrender={formrender}
|
|
232
|
+
properties={properties}
|
|
233
|
+
watch={watch} />
|
|
234
|
+
<div style={{ marginLeft: '120px' }}>
|
|
235
|
+
<Button onClick={onSubmit}>submit</Button>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### 3. 多模块渲染
|
|
243
|
+
表单引擎还支持多个`FormChildren`组件渲染,然后由`Form`组件统一处理表单值.
|
|
244
|
+
- `useSimpleForm`: 创建表单值的管理实例.
|
|
245
|
+
- `useSimpleFormRender`: 创建渲染表单的实例.
|
|
246
|
+
```javascript
|
|
247
|
+
import React, { useState } from 'react';
|
|
248
|
+
import { FormChildren, Form, useSimpleForm } from './form-render';
|
|
249
|
+
import { Button } from 'antd';
|
|
250
|
+
export default function Demo(props) {
|
|
251
|
+
|
|
252
|
+
const properties1 = {
|
|
253
|
+
part1: {
|
|
254
|
+
label: "part1input",
|
|
255
|
+
rules: [{ required: true, message: 'name1 empty' }],
|
|
256
|
+
initialValue: 1,
|
|
257
|
+
type: 'Input',
|
|
258
|
+
props: {}
|
|
259
|
+
},
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const properties2 = {
|
|
263
|
+
part2: {
|
|
264
|
+
label: "part2input",
|
|
265
|
+
rules: [{ required: true, message: 'name1 empty' }],
|
|
266
|
+
initialValue: 1,
|
|
267
|
+
type: 'Input',
|
|
268
|
+
props: {}
|
|
269
|
+
},
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const form = useSimpleForm();
|
|
273
|
+
// const formrender1 = useSimpleFormRender()
|
|
274
|
+
// const formrender2 = useSimpleFormRender()
|
|
275
|
+
|
|
276
|
+
const onSubmit = async (e) => {
|
|
277
|
+
e?.preventDefault?.();
|
|
278
|
+
const result = await form.validate();
|
|
279
|
+
console.log(result, 'result');
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<div style={{ padding: '0 8px' }}>
|
|
284
|
+
<Form form={form}>
|
|
285
|
+
<div>
|
|
286
|
+
<p>part1</p>
|
|
287
|
+
<FormChildren
|
|
288
|
+
// formrender={formrender1}
|
|
289
|
+
properties={properties1}
|
|
290
|
+
/>
|
|
291
|
+
</div>
|
|
292
|
+
<div>
|
|
293
|
+
<p>part2</p>
|
|
294
|
+
<FormChildren
|
|
295
|
+
// formrender={formrender2}
|
|
296
|
+
properties={properties2}
|
|
297
|
+
/>
|
|
298
|
+
</div>
|
|
299
|
+
</Form>
|
|
300
|
+
<div style={{ marginLeft: '120px' }}>
|
|
301
|
+
<Button onClick={onSubmit}>submit</Button>
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
### 4. 数组数据
|
|
308
|
+
支持数组渲染
|
|
309
|
+
```javascript
|
|
310
|
+
import React, { useState } from 'react';
|
|
311
|
+
import FormRender, { useSimpleForm } from './form-render';
|
|
312
|
+
import { Button } from 'antd';
|
|
313
|
+
export default function Demo(props) {
|
|
314
|
+
const form = useSimpleForm();
|
|
315
|
+
const properties =
|
|
316
|
+
[
|
|
317
|
+
{
|
|
318
|
+
label: "list-0",
|
|
319
|
+
rules: [{ required: true, message: 'name1 empty' }],
|
|
320
|
+
initialValue: 1,
|
|
321
|
+
type: 'Input',
|
|
322
|
+
props: {}
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
label: "list-1",
|
|
326
|
+
rules: [{ required: true, message: 'name1 empty' }],
|
|
327
|
+
initialValue: 2,
|
|
328
|
+
type: 'Input',
|
|
329
|
+
props: {}
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
label: "list-2",
|
|
333
|
+
rules: [{ required: true, message: 'name1 empty' }],
|
|
334
|
+
initialValue: 3,
|
|
335
|
+
type: 'Input',
|
|
336
|
+
props: {}
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
label: "list-3",
|
|
340
|
+
rules: [{ required: true, message: 'name1 empty' }],
|
|
341
|
+
initialValue: 4,
|
|
342
|
+
type: 'Input',
|
|
343
|
+
props: {}
|
|
344
|
+
},
|
|
345
|
+
];
|
|
346
|
+
|
|
347
|
+
const onSubmit = async (e) => {
|
|
348
|
+
e?.preventDefault?.();
|
|
349
|
+
const result = await form.validate();
|
|
350
|
+
console.log(result, 'result');
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
return (
|
|
354
|
+
<div style={{ padding: '0 8px' }}>
|
|
355
|
+
<FormRender
|
|
356
|
+
form={form}
|
|
357
|
+
properties={properties}
|
|
358
|
+
/>
|
|
359
|
+
<div style={{ marginLeft: '120px' }}>
|
|
360
|
+
<Button onClick={onSubmit}>submit</Button>
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
## API
|
|
368
|
+
|
|
369
|
+
### Form`s props
|
|
370
|
+
来源于[@simpleform/form](../form);
|
|
371
|
+
|
|
372
|
+
### FormChildren's props
|
|
373
|
+
- `properties`: `{ [name: string]: FormNodeProps } | FormNodeProps[]` 渲染表单的DSL形式的json数据
|
|
374
|
+
- `components`:注册表单中的所有组件;
|
|
375
|
+
- `plugins`:表单中需要引入的外来库;
|
|
376
|
+
- `options`: `GenerateFormNodeProps | ((field: GenerateFormNodeProps) => any)` 传递给表单节点组件的参数信息. 优先级比表单节点自身的参数要低
|
|
377
|
+
- `renderList`:提供自定义渲染列表的函数.
|
|
378
|
+
- `renderItem`:提供自定义渲染节点的函数.
|
|
379
|
+
- `onPropertiesChange`: `(newValue: PropertiesData) => void;` `properties`更改时回调函数
|
|
380
|
+
- `formrender`: 负责渲染的表单类。通过`useSimpleFormRender()`创建,选填.
|
|
381
|
+
- `form`: 负责表单的值的类。通过`useSimpleForm()`创建,选填.
|
|
382
|
+
- `uneval`: 不执行表单中的字符串表达式.
|
|
383
|
+
- `plugin`: 在字符串表达式中引入的外部的库.
|
|
384
|
+
|
|
385
|
+
### SimpleFormRender's Methods
|
|
386
|
+
- `updateItemByPath`: `(data?: any, path?: string, attributeName?: string) => void` 更新路径`path`对应的节点,如果更新节点中的具体属性则需要`attributeName`参数
|
|
387
|
+
- `setItemByPath`: `(data?: any, path?: string, attributeName?: string) => void` 设置路径`path`对应的节点,如果设置节点中的具体属性则需要`attributeName`参数
|
|
388
|
+
- `updateNameByPath`: `(newName?: string, path: string) => void` 更新指定路径的name键
|
|
389
|
+
- `delItemByPath`: `(path?: string, attributeName?: string) => void` 删除路径`path`对应的节点,如果删除节点中的具体属性则需要`attributeName`参数
|
|
390
|
+
- `insertItemByIndex`: `(data: InsertItemType, index?: number, parent?: { path?: string, attributeName?: string }) => void` 根据序号和父节点路径添加选项
|
|
391
|
+
- `getItemByPath`: `(path?: string, attributeName?: string) => void` 获取路径`path`对应的节点,如果是节点中的具体属性则需要`attributeName`参数
|
|
392
|
+
- `moveItemByPath`: `(from: { parent?: string, index: number }, to: { parent?: string, index?: number })` 把树中的选项从一个位置调换到另外一个位置
|
|
393
|
+
- `setProperties`: `(data?: Partial<FormNodeProps>) => void` 设置`properties`;
|
|
394
|
+
|
|
395
|
+
### Hooks
|
|
396
|
+
- `useSimpleFormRender()`: 创建 `new SimpleFormRender()`.
|
|
397
|
+
- `useSimpleForm(defaultValues)`: 创建 `new SimpleForm()`
|
|
398
|
+
|
|
399
|
+
## 其他
|
|
400
|
+
|
|
401
|
+
### properties结构说明
|
|
402
|
+
`properties`属性中每一项均为一个表单节点,节点分为嵌套节点和控件节点。
|
|
403
|
+
- 嵌套节点:
|
|
404
|
+
有`properties`属性的节点,通过`type`和`props`字段描述该节点为哪个组件,不携带表单域组件(`Form.Item`)。
|
|
405
|
+
- 节点:
|
|
406
|
+
无`properties`属性的节点,默认携带表单域组件(`Form.Item`)。
|
|
407
|
+
```javascript
|
|
408
|
+
const properties = {
|
|
409
|
+
name3: {
|
|
410
|
+
// 嵌套节点
|
|
411
|
+
// type: '',
|
|
412
|
+
// props: {},
|
|
413
|
+
properties: {
|
|
414
|
+
// 控件节点
|
|
415
|
+
first: {
|
|
416
|
+
label: 'first',
|
|
417
|
+
rules: [{ required: true, message: 'first empty' }],
|
|
418
|
+
type: 'Select',
|
|
419
|
+
props: {
|
|
420
|
+
style: { width: '100%' },
|
|
421
|
+
children: [{ type: 'Select.Option', props: { key: 1, value: '1', children: 'option1' } }]
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
- 节点的类型
|
|
429
|
+
```javascript
|
|
430
|
+
export interface FormComponent {
|
|
431
|
+
type?: string;
|
|
432
|
+
props?: any & { children?: any | Array<FormComponent> };
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export type UnionComponent<P> =
|
|
436
|
+
| React.ComponentType<P>
|
|
437
|
+
| React.ForwardRefExoticComponent<P>
|
|
438
|
+
| React.FC<P>
|
|
439
|
+
| keyof React.ReactHTML;
|
|
440
|
+
|
|
441
|
+
// 表单上的组件联合类型
|
|
442
|
+
export type CustomUnionType = FormComponent | Array<FormComponent> | UnionComponent<any> | Function | ReactNode
|
|
443
|
+
// 表单对象
|
|
444
|
+
export type PropertiesData = { [name: string]: FormNodeProps } | FormNodeProps[]
|
|
445
|
+
// 表单域(字符串表达式编译后)
|
|
446
|
+
export type GenerateFormNodeProps<T = {}> = FormComponent & FormItemProps & T & {
|
|
447
|
+
ignore?: boolean; // 标记当前节点为非表单节点
|
|
448
|
+
inside?: CustomUnionType; // 节点内层嵌套组件
|
|
449
|
+
outside?: CustomUnionType; // 节点外层嵌套组件
|
|
450
|
+
readOnly?: boolean; // 只读模式
|
|
451
|
+
readOnlyRender?: CustomUnionType; // 只读模式展示的组件
|
|
452
|
+
typeRender?: CustomUnionType; // 自定义注册组件
|
|
453
|
+
properties?: PropertiesData;
|
|
454
|
+
hidden?: boolean;
|
|
455
|
+
}
|
|
456
|
+
// 表单域(字符串表达式编译前)
|
|
457
|
+
export type FormNodeProps = {
|
|
458
|
+
[key in keyof GenerateFormNodeProps]: key extends 'rules' ?
|
|
459
|
+
(string | Array<{ [key in keyof FormRule]: FormRule[key] | string }> | GenerateFormNodeProps[key])
|
|
460
|
+
: (key extends 'properties' ? GenerateFormNodeProps[key] : (string | GenerateFormNodeProps[key]))
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### 参数注入
|
|
465
|
+
- 表单节点的属性全局设置:
|
|
466
|
+
```javascript
|
|
467
|
+
|
|
468
|
+
<FormRender
|
|
469
|
+
options={{
|
|
470
|
+
layout: 'vertical',
|
|
471
|
+
props: { disabled: true }
|
|
472
|
+
}}
|
|
473
|
+
/>
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
- 表单中的注册组件接收的参数:
|
|
477
|
+
```javascript
|
|
478
|
+
export interface GenerateParams<T = {}> {
|
|
479
|
+
name?: string;
|
|
480
|
+
path?: string;
|
|
481
|
+
field?: GenerateFormNodeProps<T>;
|
|
482
|
+
parent?: { name?: string; path?: string, field?: GenerateFormNodeProps<T>; };
|
|
483
|
+
formrender?: SimpleFormRender;
|
|
484
|
+
form?: SimpleForm;
|
|
485
|
+
};
|
|
486
|
+
```
|
|
487
|
+
### 表单的中涉及的path路径规则
|
|
488
|
+
表单允许嵌套,所以表单中会涉及寻找某个属性。其路径遵循一定的规则
|
|
489
|
+
|
|
490
|
+
举例:
|
|
491
|
+
- `a[0]`表示数组a下面的第一个选项
|
|
492
|
+
- `a.b` 表示a对象的b属性
|
|
493
|
+
- `a[0].b`表示数组a下面的第一个选项的b属性
|
|
494
|
+
|
|
495
|
+
### 字符串表达式用法
|
|
496
|
+
表单节点中属性字段除`properties`外均可以支持字符串表达式来进行联动
|
|
497
|
+
1. 快速使用:用`{{`和`}}`包裹目标属性值的计算表达式
|
|
498
|
+
```javascript
|
|
499
|
+
const properties = {
|
|
500
|
+
name1: {
|
|
501
|
+
label: 'name1',
|
|
502
|
+
valueProp: 'checked',
|
|
503
|
+
initialValue: true,
|
|
504
|
+
type: 'Checkbox',
|
|
505
|
+
props: {
|
|
506
|
+
children: 'option'
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
name2: {
|
|
510
|
+
label: "name2",
|
|
511
|
+
rules: '{{[{ required: formvalues && formvalues.name1 === true, message: "name2 empty" }]}}',
|
|
512
|
+
initialValue: 1,
|
|
513
|
+
type: 'Input',
|
|
514
|
+
props: {}
|
|
515
|
+
},
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// OR
|
|
519
|
+
|
|
520
|
+
const properties = {
|
|
521
|
+
name1: {
|
|
522
|
+
label: 'name1',
|
|
523
|
+
valueProp: 'checked',
|
|
524
|
+
initialValue: true,
|
|
525
|
+
type: 'Checkbox',
|
|
526
|
+
props: {
|
|
527
|
+
children: 'option'
|
|
528
|
+
}
|
|
529
|
+
},
|
|
530
|
+
name2: {
|
|
531
|
+
label: "name2",
|
|
532
|
+
hidden: '{{formvalues && formvalues.name1 === true}}',
|
|
533
|
+
initialValue: 1,
|
|
534
|
+
type: 'Input',
|
|
535
|
+
props: {}
|
|
536
|
+
},
|
|
537
|
+
}
|
|
538
|
+
```
|
|
539
|
+
2. 字符串表达式的使用规则
|
|
540
|
+
- 一个字符串有且只能有一对`{{`和`}}`.
|
|
541
|
+
- 除了内置的三个变量(`form`(即`useSimpleForm()`), `formrender`(即`useSimpleFormRender()`), `formvalues`(表单值对象))以外, 还可以通过`plugins`引入外部变量, 然后在字符串表达式内直接引用该变量名.
|
|
542
|
+
```javascript
|
|
543
|
+
import dayjs from 'dayjs';
|
|
544
|
+
import FormRender from "./form-render";
|
|
545
|
+
|
|
546
|
+
const properties = {
|
|
547
|
+
name3: {
|
|
548
|
+
label: "name3",
|
|
549
|
+
initialValue: "{{dayjs().format('YYYY-MM-DD')}}",
|
|
550
|
+
type: 'Input',
|
|
551
|
+
props: {}
|
|
552
|
+
},
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
<FormRender properties={properties} plugins={{ dayjs }} />
|
|
556
|
+
```
|
package/lib/types.d.ts
CHANGED
|
@@ -9,6 +9,9 @@ export interface FormComponent {
|
|
|
9
9
|
}
|
|
10
10
|
export type UnionComponent<P> = React.ComponentType<P> | React.ForwardRefExoticComponent<P> | React.FC<P> | keyof React.ReactHTML;
|
|
11
11
|
export type CustomUnionType = FormComponent | Array<FormComponent> | UnionComponent<any> | Function | ReactNode;
|
|
12
|
+
export type PropertiesData = {
|
|
13
|
+
[name: string]: FormNodeProps;
|
|
14
|
+
} | FormNodeProps[];
|
|
12
15
|
export type GenerateFormNodeProps<T = {}> = FormComponent & FormItemProps & T & {
|
|
13
16
|
ignore?: boolean;
|
|
14
17
|
inside?: CustomUnionType;
|
|
@@ -19,9 +22,6 @@ export type GenerateFormNodeProps<T = {}> = FormComponent & FormItemProps & T &
|
|
|
19
22
|
properties?: PropertiesData;
|
|
20
23
|
hidden?: boolean;
|
|
21
24
|
};
|
|
22
|
-
export type PropertiesData = {
|
|
23
|
-
[name: string]: FormNodeProps;
|
|
24
|
-
} | FormNodeProps[];
|
|
25
25
|
export type CustomRenderType = (params: GenerateParams<any> & {
|
|
26
26
|
children?: any;
|
|
27
27
|
}) => any;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simpleform/render",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "dynamic rendering core of simple form",
|
|
5
5
|
"author": "mezhanglei <496623925@qq.com>",
|
|
6
6
|
"homepage": "https://github.com/mezhanglei/simpleform#readme",
|
|
@@ -47,7 +47,9 @@
|
|
|
47
47
|
"react-router-dom": "^6.19.0"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"
|
|
51
|
-
"react
|
|
50
|
+
"antd": "4.0.0",
|
|
51
|
+
"react": "^16.8.0",
|
|
52
|
+
"react-dom": "^16.8.0",
|
|
53
|
+
"react-router-dom": "^5.0.0"
|
|
52
54
|
}
|
|
53
55
|
}
|