feat(FormBuilder): 添加表单构建器核心功能实现

- Form.List添加表单联动
- 完善Form.List的类型推导
master
LiuJiaNan 2026-04-27 14:04:35 +08:00
parent dbebcc73b1
commit a8eee6bb71
4 changed files with 108 additions and 68 deletions

View File

@ -2,9 +2,13 @@ import type { ColProps } from "antd/es/col";
import type { FormItemProps, Rule } from "antd/es/form";
import type { FormListFieldData } from "antd/es/form/FormList";
import type { Gutter } from "antd/es/grid/row";
import type { NamePath, Store } from "rc-field-form/lib/interface";
import type { NamePath } from "rc-field-form/lib/interface";
import type { ReactElement, ReactNode } from "react";
import type { FORM_ITEM_RENDER_TYPE_MAP } from "../../enum/formItemRender";
import type { DeepPartial } from "./FormBuilder";
export type FormListOptionName<Values> = [number, NamePath<Values>];
export type FormListOptionDependencies<Values> = Array<(NamePath<Values> | number)[]> | NamePath<Values>;
/**
*
@ -32,13 +36,13 @@ export interface ItemsFieldConfig {
/**
* Form.List
*/
export interface FormListOperations {
export interface FormListOperations<Values = any> {
/** 当前表单项的数据字段信息 */
field: FormListFieldData;
/** 当前项在列表中的索引位置 */
fieldIndex: number;
/** 新增方法 */
add: (defaultValue?: Store, insertIndex?: number) => void;
add: (defaultValue?: DeepPartial<Values>, insertIndex?: number) => void;
/** 删除方法 */
remove: (index: number | number[]) => void;
/** 移动方法 */
@ -48,7 +52,7 @@ export interface FormListOperations {
/**
* Form.List
*/
export interface FormListUniqueProps {
export interface FormListUniqueProps<Values = any, AllValues = Values> {
/** 是否显示新增按钮,默认 true */
showAddButton?: boolean;
/** 是否显示删除按钮,默认 true */
@ -58,9 +62,9 @@ export interface FormListUniqueProps {
/** 删除按钮的文本,默认 '删除' */
removeButtonText?: string;
/** 表单配置项 */
options: (field: FormListFieldData, fieldIndex: number, operations: FormListOperations) => FormListOption[];
options: (field: FormListFieldData, fieldIndex: number, operations: FormListOperations<Values>) => FormListOption<Values, AllValues>[];
/** 点击新增按钮时的默认值 */
addDefaultValue?: Store;
addDefaultValue?: DeepPartial<Values>;
/** 点击新增按钮时插入的索引位置 */
addInsertIndex?: number;
}
@ -78,7 +82,14 @@ type FormOptionProperty<IsOnlyForLabel extends boolean, IsCustomizeRender extend
/**
*
*/
export interface FormOptionBase<Values = any, IsOnlyForLabel extends boolean = false, IsCustomizeRender extends boolean = false, Name = NamePath<Values>> {
export interface FormOptionBase<
Values = any,
AllValues = Values,
IsOnlyForLabel extends boolean = false,
IsCustomizeRender extends boolean = false,
Name = NamePath<Values>,
Dependencies = NamePath<Values>,
> {
/** React 需要的 key如果传递了唯一的 name则不需要 */
key?: string;
/** 表单项字段名 */
@ -88,7 +99,7 @@ export interface FormOptionBase<Values = any, IsOnlyForLabel extends boolean = f
/** 占据栅格列数,默认 12 */
span?: WhenTrue<IsOnlyForLabel, number | string>;
/** 是否必填,默认 true支持函数动态计算 */
required?: FormOptionProperty<IsOnlyForLabel, IsCustomizeRender, boolean | ((formValues: Values) => boolean)>;
required?: FormOptionProperty<IsOnlyForLabel, IsCustomizeRender, boolean | ((formValues: AllValues) => boolean)>;
/** 验证规则 */
rules?: FormOptionProperty<IsOnlyForLabel, IsCustomizeRender, Rule | Rule[]>;
/** 是否使用字符验证限制 */
@ -98,19 +109,19 @@ export interface FormOptionBase<Values = any, IsOnlyForLabel extends boolean = f
/** 提示信息,传入将在 label 右侧生成图标展示 tooltip */
tip?: FormOptionProperty<IsOnlyForLabel, IsCustomizeRender, ReactNode>;
/** 是否隐藏,默认 false支持函数动态计算 */
hidden?: WhenTrue<IsOnlyForLabel, boolean | ((formValues: Values) => boolean)>;
hidden?: WhenTrue<IsOnlyForLabel, boolean | ((formValues: AllValues) => boolean)>;
/** 是否自定义渲染,完全交给外部控制渲染,默认 false */
customizeRender?: IsCustomizeRender;
/** 传递给 Form.Item 的属性,支持函数动态计算 */
formItemProps?: FormOptionProperty<IsOnlyForLabel, IsCustomizeRender, FormItemProps | ((formValues: Values) => FormItemProps)>;
formItemProps?: FormOptionProperty<IsOnlyForLabel, IsCustomizeRender, FormItemProps | ((formValues: AllValues) => FormItemProps)>;
/** label 栅格配置,默认直接使用外层的 labelCol如果 span 等于 24是外层的 labelCol.span 一半 */
labelCol?: FormOptionProperty<IsOnlyForLabel, IsCustomizeRender, ColProps>;
/** wrapper 栅格配置,默认 24 - labelCol.span */
wrapperCol?: FormOptionProperty<IsOnlyForLabel, IsCustomizeRender, ColProps>;
/** 是否应该更新(用于表单联动) */
shouldUpdate?: FormOptionProperty<IsOnlyForLabel, IsCustomizeRender, boolean | ((prevValues: Values, nextValues: Values, info: { source?: string }) => boolean)>;
shouldUpdate?: FormOptionProperty<IsOnlyForLabel, IsCustomizeRender, boolean | ((prevValues: AllValues, nextValues: AllValues, info: { source?: string }) => boolean)>;
/** 依赖字段(用于表单联动) */
dependencies?: FormOptionProperty<IsOnlyForLabel, IsCustomizeRender, Name[]>;
dependencies?: FormOptionProperty<IsOnlyForLabel, IsCustomizeRender, Dependencies[]>;
/** 是否仅用于保存标签,不渲染到页面上,只在表单中保存数据,默认 false */
onlyForLabel?: IsOnlyForLabel;
}
@ -118,11 +129,19 @@ export interface FormOptionBase<Values = any, IsOnlyForLabel extends boolean = f
/**
* render
*/
export type FormOptionByRender<RenderType extends keyof FORM_ITEM_RENDER_TYPE_MAP, Values = any, IsOnlyForLabel extends boolean = false, IsCustomizeRender extends boolean = false, Name = NamePath<Values>> = FormOptionBase<Values, IsOnlyForLabel, IsCustomizeRender, Name> & {
export type FormOptionByRender<
RenderType extends keyof FORM_ITEM_RENDER_TYPE_MAP,
Values = any,
AllValues = Values,
IsOnlyForLabel extends boolean = false,
IsCustomizeRender extends boolean = false,
Name = NamePath<Values>,
Dependencies = NamePath<Values>,
> = FormOptionBase<Values, AllValues, IsOnlyForLabel, IsCustomizeRender, Name, Dependencies> & {
/** 渲染类型(写字面量时 componentProps 会按该类型推导) */
render: RenderType;
/** 传递给表单控件的属性,类型由 render 决定 */
componentProps?: FormOptionProperty<IsOnlyForLabel, IsCustomizeRender, FORM_ITEM_RENDER_TYPE_MAP[RenderType] | ((formValues: Values) => FORM_ITEM_RENDER_TYPE_MAP[RenderType])>;
componentProps?: FormOptionProperty<IsOnlyForLabel, IsCustomizeRender, FORM_ITEM_RENDER_TYPE_MAP[RenderType] | ((formValues: AllValues) => FORM_ITEM_RENDER_TYPE_MAP[RenderType])>;
/** 选项数据(用于 select、radio、checkbox */
items?: FormOptionProperty<IsOnlyForLabel, IsCustomizeRender, RenderType extends "select" | "radio" | "checkbox" ? OptionItem[] : never>;
/** 字段键配置 */
@ -130,17 +149,24 @@ export type FormOptionByRender<RenderType extends keyof FORM_ITEM_RENDER_TYPE_MA
/** checkbox 的栅格数量,如果不传入不使用栅格,传入才使用 */
checkboxCol?: FormOptionProperty<IsOnlyForLabel, IsCustomizeRender, RenderType extends "checkbox" ? number : never>;
/** Form.List 独有的属性 */
formListUniqueProps?: FormOptionProperty<IsOnlyForLabel, IsCustomizeRender, RenderType extends "formList" ? FormListUniqueProps | ((formValues: Values) => FormListUniqueProps) : never>;
formListUniqueProps?: FormOptionProperty<IsOnlyForLabel, IsCustomizeRender, RenderType extends "formList" ? FormListUniqueProps | ((formValues: AllValues) => FormListUniqueProps) : never>;
};
/**
* render render input
*/
export type FormOptionDefault<Values = any, IsOnlyForLabel extends boolean = false, IsCustomizeRender extends boolean = false, Name = NamePath<Values>> = FormOptionBase<Values, IsOnlyForLabel, IsCustomizeRender, Name> & {
export type FormOptionDefault<
Values = any,
AllValues = Values,
IsOnlyForLabel extends boolean = false,
IsCustomizeRender extends boolean = false,
Name = NamePath<Values>,
Dependencies = NamePath<Values>,
> = FormOptionBase<Values, AllValues, IsOnlyForLabel, IsCustomizeRender, Name, Dependencies> & {
/** 渲染类型,默认 input */
render?: "input" | undefined;
/** 传递给 Input 的属性 */
componentProps?: FORM_ITEM_RENDER_TYPE_MAP["input"] | ((formValues: Values) => FORM_ITEM_RENDER_TYPE_MAP["input"]);
componentProps?: FORM_ITEM_RENDER_TYPE_MAP["input"] | ((formValues: AllValues) => FORM_ITEM_RENDER_TYPE_MAP["input"]);
/** 选项数据(用于 select、radio、checkboxinput 时不需要 */
items?: never;
/** 字段键配置input 时不需要 */
@ -154,7 +180,14 @@ export type FormOptionDefault<Values = any, IsOnlyForLabel extends boolean = fal
/**
* render ReactNode 使
*/
export type FormOptionCustomRender<Values = any, IsOnlyForLabel extends boolean = false, IsCustomizeRender extends boolean = false, Name = NamePath<Values>> = FormOptionBase<Values, IsOnlyForLabel, IsCustomizeRender, Name> & {
export type FormOptionCustomRender<
Values = any,
AllValues = Values,
IsOnlyForLabel extends boolean = false,
IsCustomizeRender extends boolean = false,
Name = NamePath<Values>,
Dependencies = NamePath<Values>,
> = FormOptionBase<Values, AllValues, IsOnlyForLabel, IsCustomizeRender, Name, Dependencies> & {
/** 渲染类型,默认 ReactNode */
render: ReactNode;
/** 传递给表单控件的属性,自定义渲染时不需要 */
@ -170,32 +203,32 @@ export type FormOptionCustomRender<Values = any, IsOnlyForLabel extends boolean
};
/**
*
*
*/
export type FormOption<Values = any>
= | FormOptionDefault<Values, false, false>
| FormOptionDefault<Values, false, true>
| FormOptionDefault<Values, true, false>
| { [K in keyof FORM_ITEM_RENDER_TYPE_MAP]: FormOptionByRender<K, Values, false, false> }[keyof FORM_ITEM_RENDER_TYPE_MAP]
| { [K in keyof FORM_ITEM_RENDER_TYPE_MAP]: FormOptionByRender<K, Values, false, true> }[keyof FORM_ITEM_RENDER_TYPE_MAP]
| { [K in keyof FORM_ITEM_RENDER_TYPE_MAP]: FormOptionByRender<K, Values, true, false> }[keyof FORM_ITEM_RENDER_TYPE_MAP]
| FormOptionCustomRender<Values, false, false>
| FormOptionCustomRender<Values, false, true>
| FormOptionCustomRender<Values, true, false>;
export type FormOption<Values = any, AllValues = Values>
= | FormOptionDefault<Values, AllValues, false, false>
| FormOptionDefault<Values, AllValues, false, true>
| FormOptionDefault<Values, AllValues, true, false>
| { [K in keyof FORM_ITEM_RENDER_TYPE_MAP]: FormOptionByRender<K, Values, AllValues, false, false> }[keyof FORM_ITEM_RENDER_TYPE_MAP]
| { [K in keyof FORM_ITEM_RENDER_TYPE_MAP]: FormOptionByRender<K, Values, AllValues, false, true> }[keyof FORM_ITEM_RENDER_TYPE_MAP]
| { [K in keyof FORM_ITEM_RENDER_TYPE_MAP]: FormOptionByRender<K, Values, AllValues, true, false> }[keyof FORM_ITEM_RENDER_TYPE_MAP]
| FormOptionCustomRender<Values, AllValues, false, false>
| FormOptionCustomRender<Values, AllValues, false, true>
| FormOptionCustomRender<Values, AllValues, true, false>;
/**
* Form.List 使 [number, NamePath] name
* Form.List
*/
export type FormListOption<Values = any>
= | FormOptionDefault<Values, false, false, [number, NamePath<Values>]>
| FormOptionDefault<Values, false, true, [number, NamePath<Values>]>
| FormOptionDefault<Values, true, false, [number, NamePath<Values>]>
| { [K in keyof FORM_ITEM_RENDER_TYPE_MAP]: FormOptionByRender<K, Values, false, false, [number, NamePath<Values>]> }[keyof FORM_ITEM_RENDER_TYPE_MAP]
| { [K in keyof FORM_ITEM_RENDER_TYPE_MAP]: FormOptionByRender<K, Values, false, true, [number, NamePath<Values>]> }[keyof FORM_ITEM_RENDER_TYPE_MAP]
| { [K in keyof FORM_ITEM_RENDER_TYPE_MAP]: FormOptionByRender<K, Values, true, false, [number, NamePath<Values>]> }[keyof FORM_ITEM_RENDER_TYPE_MAP]
| FormOptionCustomRender<Values, false, false, [number, NamePath<Values>]>
| FormOptionCustomRender<Values, false, true, [number, NamePath<Values>]>
| FormOptionCustomRender<Values, true, false, [number, NamePath<Values>]>;
export type FormListOption<Values = any, AllValues = Values>
= | FormOptionDefault<Values, AllValues, false, false, FormListOptionName<Values>, FormListOptionDependencies<AllValues>>
| FormOptionDefault<Values, AllValues, false, true, FormListOptionName<Values>, FormListOptionDependencies<AllValues>>
| FormOptionDefault<Values, AllValues, true, false, FormListOptionName<Values>, FormListOptionDependencies<AllValues>>
| { [K in keyof FORM_ITEM_RENDER_TYPE_MAP]: FormOptionByRender<K, Values, AllValues, false, false, FormListOptionName<Values>, FormListOptionDependencies<AllValues>> }[keyof FORM_ITEM_RENDER_TYPE_MAP]
| { [K in keyof FORM_ITEM_RENDER_TYPE_MAP]: FormOptionByRender<K, Values, AllValues, false, true, FormListOptionName<Values>, FormListOptionDependencies<AllValues>> }[keyof FORM_ITEM_RENDER_TYPE_MAP]
| { [K in keyof FORM_ITEM_RENDER_TYPE_MAP]: FormOptionByRender<K, Values, AllValues, true, false, FormListOptionName<Values>, FormListOptionDependencies<AllValues>> }[keyof FORM_ITEM_RENDER_TYPE_MAP]
| FormOptionCustomRender<Values, AllValues, false, false, FormListOptionName<Values>, FormListOptionDependencies<AllValues>>
| FormOptionCustomRender<Values, AllValues, false, true, FormListOptionName<Values>, FormListOptionDependencies<AllValues>>
| FormOptionCustomRender<Values, AllValues, true, false, FormListOptionName<Values>, FormListOptionDependencies<AllValues>>;
/**
* FormItemsRenderer

View File

@ -15,6 +15,7 @@ import {
} from "antd";
import dayjs from "dayjs";
import { FORM_ITEM_RENDER_ENUM } from "../../enum/formItemRender";
import { getDataType } from "../../utils";
const { TextArea } = Input;
const { RangePicker } = DatePicker;
@ -211,7 +212,7 @@ const FormItemsRenderer = ({
// 获取key
const getKey = (option) => {
return option.key || option.name;
return option.key || (getDataType(option.name) === "Array" ? option.name.join(".") : option.name);
};
// 使用 style 控制显示/隐藏
@ -425,7 +426,7 @@ const FormItemsRenderer = ({
};
// 渲染普通表单项
const renderFormItem = ({ option, style, col, index }) => {
const renderFormItem = ({ option, style, col, index, preserve }) => {
const formItemProps = getFormItemProps(option);
delete formItemProps.dependencies;
delete formItemProps.shouldUpdate;
@ -441,7 +442,7 @@ const FormItemsRenderer = ({
rules={getRules(option)}
labelCol={col.labelCol}
wrapperCol={col.wrapperCol}
preserve={false}
preserve={preserve}
required={renderLabel(option) === " " ? false : getRequired(option.required)}
colon={renderLabel(option) !== " "}
{...formItemProps}
@ -453,7 +454,7 @@ const FormItemsRenderer = ({
};
// 渲染特殊类型的表单项
const renderOtherTypeItem = ({ option, style, col, index }) => {
const renderOtherTypeItem = ({ option, style, col, index, preserve }) => {
const componentProps = getComponentProps(option);
// 如果是 customizeRender 类型,完全交给外部控制渲染
@ -472,7 +473,7 @@ const FormItemsRenderer = ({
key={getKey(option) || index}
name={option.name}
noStyle
preserve={false}
preserve={preserve}
>
<input type="hidden" />
</Form.Item>
@ -491,6 +492,25 @@ const FormItemsRenderer = ({
return null;
};
// 渲染需要动态更新的表单项
const renderDynamicFormItem = ({ option, index, style, col, preserve }) => {
const formItemProps = getFormItemProps(option);
return (
<Form.Item
key={getKey(option) || index}
noStyle
preserve={preserve}
shouldUpdate={option.shouldUpdate ?? formItemProps.shouldUpdate}
dependencies={option.dependencies ?? formItemProps.dependencies}
>
{() => {
return renderFormItem({ option, style, col, index, preserve });
}}
</Form.Item>
);
};
// 渲染 Form.List
const renderFormList = ({ option, index, col, style }) => {
const formListUniqueProps = getFormListUniqueProps(option);
@ -517,6 +537,7 @@ const FormItemsRenderer = ({
style,
col,
index: `${fieldIndex}_${listIndex}`,
preserve: true,
};
const otherTypeItem = renderOtherTypeItem(params);
if (otherTypeItem)
@ -525,6 +546,9 @@ const FormItemsRenderer = ({
if (listOption.render === FORM_ITEM_RENDER_ENUM.FORM_LIST)
return renderFormList(params);
if ((listOption.shouldUpdate ?? listOption.dependencies) || (formItemProps.shouldUpdate ?? formItemProps.dependencies))
return renderDynamicFormItem(params);
// 判断一个项是否需要显示按钮(不是 onlyForLabel 且不是隐藏的)
const isShow = (opt) => {
return !getHidden(opt.hidden) && !opt.onlyForLabel;
@ -619,25 +643,6 @@ const FormItemsRenderer = ({
);
};
// 渲染需要动态更新的表单项
const renderDynamicFormItem = ({ option, index, style, col }) => {
const formItemProps = getFormItemProps(option);
return (
<Form.Item
key={getKey(option) || index}
noStyle
preserve={false}
shouldUpdate={option.shouldUpdate ?? formItemProps.shouldUpdate}
dependencies={option.dependencies ?? formItemProps.dependencies}
>
{() => {
return renderFormItem({ option, style, col, index });
}}
</Form.Item>
);
};
return (
<>
{options.map((option, index) => {
@ -650,6 +655,7 @@ const FormItemsRenderer = ({
style,
col,
index,
preserve: false,
};
// 处理特殊类型的表单项

View File

@ -1,6 +1,7 @@
import type { FormOption } from "./FormItemsRenderer";
import type { FormListOption, FormListUniqueProps, FormOption } from "./FormItemsRenderer";
import FormBuilder from "./FormBuilder";
export type { FormOption };
export type { FormListOption, FormListUniqueProps, FormOption };
export default FormBuilder;

View File

@ -1,9 +1,9 @@
import type { FormInstance, FormProps } from "antd/es/form";
import type { ReactElement, ReactNode } from "react";
import type { FormOption } from "../FormBuilder";
import type { FormListOption, FormListUniqueProps, FormOption } from "../FormBuilder";
import type { DeepPartial } from "../FormBuilder/FormBuilder";
export type { FormOption };
export type { FormListOption, FormListUniqueProps, FormOption };
/**
* Search