在做一些稍复杂一点的后端管理系统时,Form.List组件的使用就会很频繁:
像下图中这种,可能只是对应一个Array<string>或者稍微复杂一点的:
Array<{name: string; label: string;}>
再有复杂一点的,可能会有多层级表单嵌套
对于这种情况,我们封装了一个方法,可以避免去写复杂的Form.List
import { Button, Card, Form, Input, Row, Col } from 'antd'
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'
import { formItemLayout, formItemLayoutWithOutLabel } from './commonConfig'
import ImgFormItem from './ImgFormItem'
const createFormList = ({
listName,
listLabel,
fields
}: {
listName: string | Array<number | string>;
listLabel: string;
fields?: Array<{
name: string; label: string; type?: string; span?: number; childFields?: Array<any>
}>
}, initData?: any) => {
const columnCount = fields?.length
const columnSpan = !columnCount ? 23 : Math.floor(23 / columnCount)
return <Form.List
name={listName}
rules={[
{
validator: async (_, names) => {
if (!names || names.length < 1) {
return Promise.reject(new Error('At least 1 item'));
}
},
},
]}
>
{(innerFields, { add, remove }, { errors }) => (
<>
{innerFields.map((innerField, index) => {
return <Form.Item
{...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)}
label={index === 0 ? listLabel : ''}
required={false}
key={innerField.key}
>
<Row style={{ background: '#eef', marginBottom: 10, padding: 10 }}>
{(fields && fields.length > 0) ?
fields?.map((field, i) =>
// 最终数据形式为
// [ { field1: xxx, field2: xxx }, { field1: xxx, field2: xxx } ]
<Col span={field.span || columnSpan} key={field.name}>
{
field.type === 'array' ?
createFormList({
listName: [innerField.name, field.name],
listLabel: field.label,
fields: field.childFields
}, initData ? initData[innerField.name] : undefined) :
field.type === 'img' ?
<ImgFormItem formVal={initData[innerField.name]} name={[innerField.name, field.name]} childName={field.name} label={field.label}></ImgFormItem> :
<Form.Item
name={[innerField.name, field.name]}
label={field.label}
validateTrigger={['onChange', 'onBlur']}
rules={[
{
required: true,
},
]}
>
<Input placeholder="请输入" style={{ width: '96%' }} />
</Form.Item>
}
</Col>
) : (
// 最终数据形式为
// [ 'xxx', 'xxx' ]
<Col span={columnSpan}>
<Form.Item
name={[innerField.name]}
validateTrigger={['onChange', 'onBlur']}
rules={[
{
required: true,
},
]}
noStyle
>
<Input placeholder="请输入" style={{ width: '96%' }} />
</Form.Item>
</Col>
)}
<Col span={1}>
{innerFields.length > 0 ? (
<MinusCircleOutlined
className="dynamic-delete-button"
onClick={() => remove(innerField.name)}
/>
) : null}
</Col>
</Row>
</Form.Item>
})}
<Form.Item
label={innerFields.length === 0 ? listLabel: ''}
{...(innerFields.length === 0 ? formItemLayout : formItemLayoutWithOutLabel)}>
<Button
type="dashed"
onClick={() => add()}
style={{ width: '100%' }}
icon={<PlusOutlined />}
>
Add field
</Button>
<Form.ErrorList errors={errors} />
</Form.Item>
</>
)}
</Form.List>
}
export default createFormList
ImgFormItem:
import { Button, Card, Form, Input } from 'antd'
import { useEffect, useState } from 'react'
export default (props: {
formVal: any;
name: string | Array<string | number>;
childName?: string;
label?: string;
}) => {
const { formVal, name, childName, label } = props
const [url, setUrl] = useState('')
useEffect(() => {
setUrl(formVal?.[childName || name as string] || '')
}, [formVal])
return <>
<Form.Item label={label || '图片链接'} name={name} rules={[{ required: true }]}>
<Input onChange={e => setUrl(e.target.value)}></Input>
</Form.Item>
<Form.Item label='预览'><div style={{ maxHeight: 300, overflow: 'auto' }}><img src={url} width={'100%'}></img></div></Form.Item>
</>
}
使用场景:
createFormList({
listName: 'menus',
listLabel: '菜单配置项',
fields: [
{ name: 'title', label: '名称' },
{ name: 'link', label: '跳转链接' },
{ name: 'submenu', label: '子菜单', type: 'array', childFields: [
{ name: 'title', label: '名称' },
{ name: 'link', label: '跳转链接' },
] },
]
})
createFormList({
listName: 'titles',
listLabel: '标题',
})
createFormList({
listName: 'ourMainCustomers',
listLabel: '下方轮播配置',
fields: [
// { name: 'imgUrl', label: '图片地址' },
{ name: 'imgUrl', label: '图片地址', type: 'img', span: 22 },
{ name: 'note', label: '说明', span: 22 },
{ name: 'previewUrl', label: '预览地址' },
{ name: 'previewImgUrl', label: '预览地址旁边小图片地址' },
]
}, formVal?.ourMainCustomers || {})