动态表单-DynamicForm
一. 需求点
每次重复搬antd组件会很烦,如果创造一个,动态表单生成器,只需传入配置,就会生成Form组件,达到提效的作用那
二. 能力拆分
生成器 - MatchingNode
需要一个根据外界传入type(表单组件类型),就能生成对应antd组件的生成器
组合器 - CombineForm
需要一个把静态属性 和 动态属性组装成一体的组合器
合规配置
表单配置项数据格式要符合antd的传参方式
三. MatchingNode
根据传入的type匹配对应的生成类型
| 'text' | 'input' | 'inputNumber' | 'radio' | 'slider' | 'formList' | 'submit' |
|---|---|---|---|---|---|---|
| 文本 | <Input/> | <InputNumber/> | <Radio/> | <Slider/> | <Form.List/> | <Button /> |
匹配节点
switch(type) {
case 'text':
return renderCondition && renderCondition();
case 'input':
return renderCondition && renderCondition(<Input {...nodeProps} />);
case 'inputNumber':
return renderCondition && renderCondition(<InputNumber {...nodeProps} />);
case 'select':
return renderCondition && renderCondition(
<Select {...nodeProps}>
{
childrenData?.map((chil, index) => <Select.Option key={index} value={chil?.value}>{chil?.text}</Select.Option>)
}
</Select>
);
case 'radio':
return renderCondition && renderCondition(
<Radio.Group {...nodeProps}>
{
childrenData && childrenData?.map((chil) => <Radio value={chil?.value}>{chil?.text}</Radio>) || null
}
</Radio.Group>
);
case 'slider':
return renderCondition && renderCondition(
<Row>
<Col span={12}>
<Slider {...nodeProps?.slider} />
</Col>
<Col span={4}>
<InputNumber {...nodeProps?.inputnumber} />
</Col>
</Row>
);
case 'formList':
return renderCondition && renderCondition(
<Form.List {...nodeProps}>
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, ...restField }) => (
<Space key={key} {...children?.spaceStyle}>
{
childrenData?.data?.map((item) => (
<Form.Item
{...restField}
name={[name, item?.name]}
label={item?.label}
>
<Input {...item?.childrenProps} />
</Form.Item>
))
}
<MinusCircleOutlined onClick={() => remove(name)} />
</Space>
))}
<Form.Item>
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
{childrenData?.buttonName}
</Button>
</Form.Item>
</>
)}
</Form.List>
);
case 'submit':
return renderCondition && renderCondition(
<Space>
{
childrenData?.map(item => {
return item?.renderCondition(
<Button {...item.buttonProps}>
{item.text}
</Button>
);
})
}
</Space>
);
default:
return other...
};
生成 Form 表单
return (
<Form {...config?.propertys}>
{
config?.items?.map((item) => (
<Form.Item
{...item?.propertys}
>
{
matchingNode(item)
}
</ Form.Item>
))
}
</Form>
)
四. CombineForm
接收静态属性StaticAttributes 和 动态属性DynamicAttributes,将其组合成完整属性,提供给MatchingNode
静态属性
const environmentFormConfig: EnvironmentFormConfig = {
propertys: {
name: 'Environments',
labelCol: { span: 4 },
wrapperCol: { span: 14 },
autoComplete: 'off',
...formPropertys
},
items: [
{
propertys: {
label: 'Software project',
name: 'appName',
},
children: {},
},
{
propertys: {
label: 'Name',
name: 'name',
rules: [{ required: true, message: 'Please input your name!' }]
},
children: {},
}
],
......
};
合并属性
chilDynamicAttributes?.forEach((ele, index) => {
const originChildrenProp = staticAttributes.items[index].children;
staticAttributes.items[index].children = {...originChildrenProp, ...ele};
});
return staticAttributes;
五. 合规传参数
最终要生成渲染antd组件,所以参数配置要区分出来,哪些是用来匹配组件类型的,哪些props是antd组件可直接识别的,哪些props是渲染子组件的数据,例如Selection.Option 、Radio.Group -> Radio。
renderCondition()函数是 渲染条件,用处是,新增(add)和编辑(edit)在共用表单组件时,要根据路由参数、或外层组件传入的关键字区别是新增 还是 编辑,表单项会显示不同的内容,此时通过渲染函数可做预处理逻辑
const chilDynamicAttributes = [
{
type: 'text',
renderCondition: () => appName,
},
{
type: 'input',
renderCondition: (node) => unDetails ? node : initialValues?.name,
},
{
type: 'radio',
renderCondition: (node) => unDetails ? node : initialValues?.envGrade,
nodeProps: {
onChange: handleGrade,
},
childrenData: gradeDatas
},
{
type: 'select',
renderCondition: (node) => unDetails ? node : initialValues?.unitCode,
nodeProps: {
placeholder: "Select Deploy Unit",
onChange: (value) => setCurrentUnit(value),
allowClear: true
},
childrenData: deployUnit
},
{
type: 'formList',
renderCondition: (node) => node,
nodeProps: {
name: 'envVars'
},
spaceStyle: {
style: {
display: 'flex',
marginBottom: 8
},
align: "baseline"
},
childrenData: {
data: [
{
name: 'name',
label: '名称',
childrenProps: {
disabled: pathType === 'detail' && !detailEdit,
placeholder: 'name'
}
},
{
name: 'value',
label: '引用',
childrenProps: {
disabled: pathType === 'detail' && !detailEdit,
placeholder: 'value'
},
},
],
buttonName: '添加变量'
},
},
{
type: 'submit',
renderCondition: (node) => node,
childrenData: [
{
renderCondition: (node) => !unDetails && node,
buttonProps: {
type: 'primary',
onClick: () => setDetailEdit(!detailEdit)
},
text: detailEdit ? 'Details' : 'Edit'
},
{
renderCondition: (node) => node,
buttonProps: {
type: 'primary',
htmlType: "submit"
},
text: 'Submit'
},
],
},
];