import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; import { useControllableValue } from 'ahooks'; import type { FormInstance } from 'antd'; import { Form, Popconfirm, Space } from 'antd'; import type { SizeType } from 'antd/lib/config-provider/SizeContext'; import type { SpaceSize } from 'antd/lib/space'; import Link from 'antd/lib/typography/Link'; import { Flex } from 'antd5'; import { differenceWith, forEach, isEqual, isFunction, keys } from 'lodash'; import type { CSSProperties, ReactElement } from 'react'; import React, { Children, forwardRef, isValidElement, useCallback, useEffect, useImperativeHandle, useMemo, } from 'react'; import { FormattedMessage } from 'umi'; import FormListItem from './FormListItem'; import { component } from './util'; const styles: Record<'link' | 'flex' | 'space' | 'form', CSSProperties> = { link: { fontSize: 18, }, flex: { width: 50, }, space: { width: '100%', }, form: { width: '100%', }, }; const listFieldNameKey = 'dynamicform'; export type ChildrenType = React.ReactChildren | React.ReactElement | React.ReactNode | React.FC; export type LayoutType = 'horizontal' | 'vertical'; export interface DynamicFormProps { /** * @description 只读 */ readOnly?: boolean; /** * @description 'layout' */ labeLayout?: LayoutType; /** * @description value */ value?: any; /** * @description onChange */ onChange?: (value: any) => any; /** * @description 格式化 */ format?: (value: any) => any; /** * @description 增删模版 */ children: ChildrenType; /** * @description 行间距 */ spaceSize?: SpaceSize | [SpaceSize, SpaceSize]; /** * @description 行表单项间距 */ flexGap?: CSSProperties['gap'] | SizeType; } export interface DynamicFormRef { validateFields: () => Promise; form: () => FormInstance; } const DynamicForm = forwardRef((props, ref) => { const { children, labeLayout = 'vertical', format, readOnly = false, spaceSize = 0, flexGap = 8, } = props; const [value, onChange] = useControllableValue(props); const [form] = Form.useForm(); useImperativeHandle(ref, () => ({ validateFields: async () => { const allValues = await form.validateFields(); return allValues?.[listFieldNameKey]; }, form: () => form, })); // 表单项变更清除配置 const changeCleans = useMemo(() => { const child = Array.isArray(children) ? children.map((item) => component(item)) : component(children, value); const map = {}; Children.forEach(child, (c) => { if (isValidElement(c as ReactElement)) { if (c?.props?.name && c?.props?.changeCleans) { map[c.props.name] = c.props.changeCleans; } } }); return map; }, [children, value]); useEffect(() => { if (value && value?.length > 0) { form?.setFieldsValue({ [listFieldNameKey]: value, }); } else { form?.setFieldsValue({ [listFieldNameKey]: [undefined], }); } }, [value, form]); const valsChangeHandler = useCallback( (changedValues, allValues) => { const changedData: any[] = changedValues?.[listFieldNameKey]; const allData: any[] = allValues?.[listFieldNameKey]; let index = -1; const resetprops = {}; if ( differenceWith(allData, value, isEqual)?.filter((item) => !!item)?.length === 1 && changedData?.length > 0 ) { changedData.forEach((item: any, i: number) => { if (item) { index = i; forEach(changeCleans[keys(item)?.[0]], (k) => { resetprops[k] = undefined; }); } }); } const vals = allData?.map((item: any, i: number) => ({ ...item, ...(i === index ? resetprops : {}), })); onChange(isFunction(format) ? format(vals) : vals); form.validateFields(); }, [changeCleans, form, format, onChange, value], ); return (
{(fields, { add, remove }) => ( {fields.map((field, index) => ( {children} {!Boolean(readOnly) && ( } disabled={fields.length <= 1} onConfirm={() => remove(field.name)} > add(undefined, index + 1)}> )} ))} )}
); }); export default DynamicForm;