diff --git a/components/FormBuilder/FormBuilder.js b/components/FormBuilder/FormBuilder.js index 1b85b0b..4ead1e1 100644 --- a/components/FormBuilder/FormBuilder.js +++ b/components/FormBuilder/FormBuilder.js @@ -45,6 +45,7 @@ const FormBuilder = (props) => { options={options} labelCol={labelCol} span={span} + gutter={gutter} useAutoGenerateRequired={useAutoGenerateRequired} initialValues={values} /> diff --git a/components/FormBuilder/FormItemsRenderer.d.ts b/components/FormBuilder/FormItemsRenderer.d.ts index 535ecf7..4fbe18d 100644 --- a/components/FormBuilder/FormItemsRenderer.d.ts +++ b/components/FormBuilder/FormItemsRenderer.d.ts @@ -1,5 +1,7 @@ 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 } from "rc-field-form/lib/interface"; import type { FC, ReactNode } from "react"; import type { FORM_ITEM_RENDER_ENUM } from "../../enum/formItemRender"; @@ -37,6 +39,26 @@ export interface itemsFieldConfig { */ export type FormValues = Record; +/** + * Form.List 独有的属性 + */ +export interface FormListUniqueProps { + /** 是否显示新增按钮,默认 true */ + showAddButton?: boolean; + /** 是否显示删除按钮,默认 true */ + showRemoveButton?: boolean; + /** 新增按钮的文本,默认 '添加' */ + addButtonText?: string; + /** 删除按钮的文本,默认 '删除' */ + removeButtonText?: string; + /** 表单配置项 */ + options: FormOption[] | ((field: FormListFieldData) => FormOption[]); + /** 点击新增按钮时的默认值 */ + addDefaultValue?: FormValues; + /** 点击新增按钮时插入的索引位置 */ + addInsertIndex?: number; +} + /** * 表单配置项 */ @@ -81,6 +103,8 @@ export interface FormOption { dependencies?: NamePath[]; /** 是否仅用于保存标签,不渲染到页面上,只在表单中保存数据,默认 false */ onlyForLabel?: boolean; + /** Form.List 独有的属性 */ + formListUniqueProps?: FormListUniqueProps; } /** @@ -97,6 +121,10 @@ export interface FormItemsRendererProps { useAutoGenerateRequired?: boolean; /** 初始值,用于在表单未初始化时提供默认值 */ initialValues?: FormValues; + /** 栅格间距,继承自 FormBuilder */ + gutter?: Gutter | [Gutter, Gutter]; + /** label 栅格配置,继承自 FormBuilder */ + labelCol?: ColProps; } /** diff --git a/components/FormBuilder/FormItemsRenderer.js b/components/FormBuilder/FormItemsRenderer.js index 06d2aff..6acae8d 100644 --- a/components/FormBuilder/FormItemsRenderer.js +++ b/components/FormBuilder/FormItemsRenderer.js @@ -1,5 +1,6 @@ import { InfoCircleOutlined } from "@ant-design/icons"; import { + Button, Checkbox, Col, DatePicker, @@ -8,6 +9,7 @@ import { Input, InputNumber, Radio, + Row, Select, Tooltip, } from "antd"; @@ -19,10 +21,19 @@ const { RangePicker } = DatePicker; /** * 表单项渲染器组件 + * @param {object} props - 组件属性 + * @param {Array} props.options - 表单配置项数组 + * @param {object} props.labelCol - label 栅格配置 + * @param {number} props.gutter - 栅格间距 + * @param {number} props.span - 默认栅格占据列数 + * @param {boolean} props.collapse - 是否折叠(仅显示前3项) + * @param {boolean} props.useAutoGenerateRequired - 是否自动生成必填规则 + * @param {object} props.initialValues - 初始值 */ const FormItemsRenderer = ({ options, labelCol, + gutter = 24, span = 12, collapse = false, useAutoGenerateRequired = true, @@ -47,6 +58,31 @@ const FormItemsRenderer = ({ : (option.componentProps || {}); }; + // 获取 Form.List 独有的属性 + const getFormListUniqueProps = (option) => { + const defaultProps = { + showAddButton: true, + showRemoveButton: true, + addButtonText: "添加", + removeButtonText: "删除", + options: [], + addDefaultValue: {}, + addInsertIndex: undefined, + }; + + if (typeof option.formListUniqueProps === "function") { + return { + ...defaultProps, + ...option.formListUniqueProps(getFormValues()), + }; + } + + return { + ...defaultProps, + ...(option.formListUniqueProps || {}), + }; + }; + // 获取传给formItem的属性 const getFormItemProps = (option) => { const formItemProps = typeof option.formItemProps === "function" @@ -92,6 +128,14 @@ const FormItemsRenderer = ({ }; }; + // 获取 required + const getRequired = (required) => { + // 支持动态计算 required + return typeof required === "function" + ? required(getFormValues()) + : (required ?? true); + }; + // 获取验证规则 const getRules = (option) => { if (option.render === FORM_ITEM_RENDER_ENUM.DIVIDER) @@ -115,11 +159,11 @@ const FormItemsRenderer = ({ rules.push({ pattern: /^(\d+)(\.\d{1,2})?$/, message: "请输入正确的数字,最多保留两位小数" }); rules.push({ validator: (_, value) => { - if (value && Math.abs(parseFloat(value)) > Number.MAX_SAFE_INTEGER) { + if (value && Math.abs(Number.parseFloat(value)) > Number.MAX_SAFE_INTEGER) { return Promise.reject("输入数值超出安全范围"); } return Promise.resolve(); - } + }, }); break; } @@ -127,12 +171,7 @@ const FormItemsRenderer = ({ if (!useAutoGenerateRequired) return option.rules ? (Array.isArray(option.rules) ? [...option.rules, ...rules] : [option.rules, ...rules]) : []; - // 支持动态计算 required - const required = typeof option.required === "function" - ? option.required(getFormValues()) - : (option.required ?? true); - - if (required) { + if (getRequired(option.required)) { const isBlurTrigger = !option.render || [ FORM_ITEM_RENDER_ENUM.INPUT, FORM_ITEM_RENDER_ENUM.TEXTAREA, @@ -162,6 +201,34 @@ const FormItemsRenderer = ({ return option.key || option.name; }; + // 使用 style 控制显示/隐藏 + const getStyle = (index) => { + return collapse && index >= 3 ? { display: "none" } : undefined; + }; + + // 列数 + const getCol = (option) => { + const itemSpan = option.render === FORM_ITEM_RENDER_ENUM.DIVIDER ? 24 : option.span ?? span; + const itemLabelCol = option.labelCol ?? (itemSpan === 24 ? { span: labelCol.span / 2 } : labelCol); + const itemWrapperCol = option.wrapperCol ?? { span: 24 - itemLabelCol.span }; + return { span: itemSpan, labelCol: itemLabelCol, wrapperCol: itemWrapperCol }; + }; + + // 获取 hidden + const getHidden = (hidden) => { + // 支持动态计算 hidden + return typeof hidden === "function" + ? hidden(getFormValues()) + : (hidden ?? false); + }; + + // 获取 listOptions + const getListOptions = (listOptions, field) => { + return typeof listOptions === "function" + ? listOptions(field) + : (listOptions ?? []); + }; + // 渲染表单控件 const renderFormControl = (option) => { const componentProps = getComponentProps(option); @@ -230,13 +297,37 @@ const FormItemsRenderer = ({ return ; case FORM_ITEM_RENDER_ENUM.DATE_MONTH: - return ; + return ( + + ); case FORM_ITEM_RENDER_ENUM.DATE_YEAR: - return ; + return ( + + ); case FORM_ITEM_RENDER_ENUM.DATE_WEEK: - return ; + return ( + + ); case FORM_ITEM_RENDER_ENUM.DATE_RANGE: return ( @@ -293,112 +384,198 @@ const FormItemsRenderer = ({ ); }; + // 渲染普通表单项 + const renderFormItem = ({ option, style, col, index, name }) => { + if (getHidden(option.hidden)) + return null; + + return ( + + + {renderFormControl(option)} + + + ); + }; + + // 渲染特殊类型的表单项 + const renderOtherTypeItem = ({ option, style, col, index, name }) => { + // 如果是 customizeRender 类型,完全交给外部控制渲染 + if (option.customizeRender) { + return ( + + {option.render} + + ); + } + + // 如果是 onlyForLabel 类型,不渲染任何UI,只在表单中保存数据 + if (option.onlyForLabel) { + return ( + + + + ); + } + + // 如果是分割线 + if (option.render === FORM_ITEM_RENDER_ENUM.DIVIDER) { + return ( + + {option.label} + + ); + } + + return null; + }; + + // 渲染 Form.List + const renderFormList = (option, index, col, style) => { + const formListUniqueProps = getFormListUniqueProps(option); + return ( + + + {(fields, { add, remove }) => ( + <> + {fields.map((field, fieldIndex) => { + const listOptions = getListOptions(option.formListUniqueProps.options, field); + return ( + + {listOptions.map((listOption, listIndex) => { + const col = getCol(listOption); + + const params = { + option: listOption, + style, + col, + index: `${fieldIndex}_${listIndex}`, + name: [field.name, listOption.name], + }; + const otherTypeItem = renderOtherTypeItem(params); + if (otherTypeItem) + return otherTypeItem; + + // 如果是最后一个表单项,则在其后添加操作按钮 + // 这样可以确保每个表单项组都有添加/删除按钮 + if (listIndex === listOptions.length - 1) { + return ( + + +
+ + {renderFormControl(listOption)} + + { + // 只有当不是第一行时才显示删除按钮 + fieldIndex >= 1 + ? ( + formListUniqueProps.showRemoveButton + && ( + + ) + ) + : ( + // 第一行显示添加按钮 + formListUniqueProps.showAddButton + && ( + + ) + ) + } +
+
+ + ); + } + + return renderFormItem(params); + })} +
+ ); + })} + + )} +
+ + ); + }; + + // 渲染需要动态更新的表单项 + const renderDynamicFormItem = (option, index, style, col) => { + return ( + + {() => { + return renderFormItem({ option, style, col, index }); + }} + + ); + }; + return ( <> {options.map((option, index) => { - // 列数 - const itemSpan = option.render === FORM_ITEM_RENDER_ENUM.DIVIDER ? 24 : option.span ?? span; - const itemLabelCol = option.labelCol ?? (itemSpan === 24 ? { span: labelCol.span / 2 } : labelCol); - const itemWrapperCol = option.wrapperCol ?? { span: 24 - itemLabelCol.span }; + const col = getCol(option); + const style = getStyle(index); - // 使用 style 控制显示/隐藏 - const style = collapse && index >= 3 ? { display: "none" } : undefined; + // 处理特殊类型的表单项 + const otherTypeItem = renderOtherTypeItem({ option, style, col, index }); + if (otherTypeItem) + return otherTypeItem; - // 如果是 customizeRender 类型,完全交给外部控制渲染 - if (option.customizeRender) { - return ( - - {option.render} - - ); - } - - // 如果是 onlyForLabel 类型,不渲染任何UI,只在表单中保存数据 - if (option.onlyForLabel) { - return ( - - - - ); - } - - // 如果是分割线 - if (option.render === FORM_ITEM_RENDER_ENUM.DIVIDER) { - return ( - - {option.label} - - ); + // 如果是 Form.List + if (option.render === FORM_ITEM_RENDER_ENUM.FORM_LIST) { + return renderFormList(option, index, col, style); } // 如果配置了 shouldUpdate 或 dependencies,使用 Form.Item 的联动机制 if ((option.shouldUpdate ?? option.dependencies) || (option?.componentProps?.shouldUpdate ?? option?.componentProps?.dependencies)) { - return ( - - {() => { - // 支持动态计算 hidden - const hidden = typeof option.hidden === "function" - ? option.hidden(getFormValues()) - : (option.hidden ?? false); - - if (hidden) - return null; - - return ( - - - {renderFormControl(option)} - - - ); - }} - - ); + return renderDynamicFormItem(option, index, style, col); } // 普通表单项(静态配置) - // 支持动态计算 hidden - const hidden = typeof option.hidden === "function" - ? option.hidden(getFormValues()) - : (option.hidden ?? false); - - if (hidden) - return null; - - return ( - - - {renderFormControl(option)} - - - ); + return renderFormItem({ option, style, col, index }); })} ); diff --git a/enum/formItemRender/index.d.ts b/enum/formItemRender/index.d.ts index 5226853..f911110 100644 --- a/enum/formItemRender/index.d.ts +++ b/enum/formItemRender/index.d.ts @@ -32,4 +32,6 @@ export declare const FORM_ITEM_RENDER_ENUM: { DATETIME_RANGE: "datetimeRange"; /** 映射为 antd Divider */ DIVIDER: "divider"; + /** 映射为 antd FormList */ + FORM_LIST: "formList", }; diff --git a/enum/formItemRender/index.js b/enum/formItemRender/index.js index 40670ea..b2dc092 100644 --- a/enum/formItemRender/index.js +++ b/enum/formItemRender/index.js @@ -32,4 +32,6 @@ export const FORM_ITEM_RENDER_ENUM = { DATETIME_RANGE: "datetimeRange", /** 映射为 antd Divider */ DIVIDER: "divider", + /** 映射为 antd FormList */ + FORM_LIST: "formList", };