zy-react-library/components/FormBuilder/FormItemsRenderer.js

587 lines
19 KiB
JavaScript
Raw Normal View History

2025-10-22 14:43:42 +08:00
import { InfoCircleOutlined } from "@ant-design/icons";
import {
2025-12-02 17:43:11 +08:00
Button,
2025-10-22 14:43:42 +08:00
Checkbox,
Col,
DatePicker,
Divider,
Form,
Input,
InputNumber,
Radio,
2025-12-02 17:43:11 +08:00
Row,
2025-10-22 14:43:42 +08:00
Select,
Tooltip,
} from "antd";
import dayjs from "dayjs";
import { FORM_ITEM_RENDER_ENUM } from "../../enum/formItemRender";
const { TextArea } = Input;
const { RangePicker } = DatePicker;
/**
* 表单项渲染器组件
2025-12-02 17:43:11 +08:00
* @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 - 初始值
2025-10-22 14:43:42 +08:00
*/
const FormItemsRenderer = ({
options,
labelCol,
2025-12-02 17:43:11 +08:00
gutter = 24,
span = 12,
collapse = false,
useAutoGenerateRequired = true,
initialValues,
}) => {
2025-10-22 14:43:42 +08:00
const form = Form.useFormInstance();
2025-10-29 09:38:36 +08:00
// 获取表单值,优先使用 initialValues
const getFormValues = () => {
const formValues = form.getFieldsValue();
// 如果表单值为空但有 initialValues则使用 initialValues
if (Object.keys(formValues).length === 0 && initialValues) {
return initialValues;
}
return formValues;
};
2025-10-22 14:43:42 +08:00
// 获取传给组件的属性
const getComponentProps = (option) => {
return typeof option.componentProps === "function"
2025-10-29 09:38:36 +08:00
? option.componentProps(getFormValues())
2025-10-22 14:43:42 +08:00
: (option.componentProps || {});
};
2025-12-02 17:43:11 +08:00
// 获取 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 || {}),
};
};
2025-10-22 14:43:42 +08:00
// 获取传给formItem的属性
const getFormItemProps = (option) => {
const formItemProps = typeof option.formItemProps === "function"
2025-10-29 09:38:36 +08:00
? option.formItemProps(getFormValues())
2025-10-22 14:43:42 +08:00
: (option.formItemProps || {});
// 为日期组件添加特殊处理
if ([
FORM_ITEM_RENDER_ENUM.DATE,
FORM_ITEM_RENDER_ENUM.DATE_MONTH,
FORM_ITEM_RENDER_ENUM.DATE_YEAR,
FORM_ITEM_RENDER_ENUM.DATETIME,
].includes(option.render)) {
formItemProps.getValueFromEvent = (_, dateString) => dateString;
formItemProps.getValueProps = value => ({ value: value ? dayjs(value) : undefined });
}
// 为日期周组件添加特殊处理
if ([
FORM_ITEM_RENDER_ENUM.DATE_WEEK,
].includes(option.render)) {
formItemProps.getValueFromEvent = (_, dateString) => dateString;
formItemProps.getValueProps = value => ({ value: value ? dayjs(value, "YYYY-wo") : undefined });
}
// 为日期范围组件添加特殊处理
if ([
FORM_ITEM_RENDER_ENUM.DATE_RANGE,
FORM_ITEM_RENDER_ENUM.DATETIME_RANGE,
].includes(option.render)) {
formItemProps.getValueFromEvent = (_, dateString) => dateString;
formItemProps.getValueProps = value => ({ value: Array.isArray(value) ? value.map(v => v ? dayjs(v) : undefined) : undefined });
}
return formItemProps;
};
// 获取items里的value和label字段key
const getItemsFieldKey = (option) => {
return {
valueKey: option?.itemsField?.valueKey || "bianma",
labelKey: option?.itemsField?.labelKey || "name",
2025-10-22 14:43:42 +08:00
};
};
2025-12-02 17:43:11 +08:00
// 获取 required
const getRequired = (required) => {
// 支持动态计算 required
return typeof required === "function"
? required(getFormValues())
: (required ?? true);
};
2025-10-22 14:43:42 +08:00
// 获取验证规则
const getRules = (option) => {
if (option.render === FORM_ITEM_RENDER_ENUM.DIVIDER)
return [];
2025-11-24 09:38:02 +08:00
const rules = [];
/** @type {string | Function} */
const render = option.render || FORM_ITEM_RENDER_ENUM.INPUT;
switch (render) {
case FORM_ITEM_RENDER_ENUM.INPUT:
rules.push({ max: 50, message: "最多输入50字符" });
break;
case FORM_ITEM_RENDER_ENUM.TEXTAREA:
rules.push({ max: 500, message: "最多输入500字符" });
break;
case FORM_ITEM_RENDER_ENUM.INPUT_NUMBER:
case FORM_ITEM_RENDER_ENUM.NUMBER:
rules.push({ pattern: /^(\d+)(\.\d{1,2})?$/, message: "请输入正确的数字,最多保留两位小数" });
rules.push({
validator: (_, value) => {
2025-12-02 17:43:11 +08:00
if (value && Math.abs(Number.parseFloat(value)) > Number.MAX_SAFE_INTEGER) {
2025-11-24 09:38:02 +08:00
return Promise.reject("输入数值超出安全范围");
}
return Promise.resolve();
2025-12-02 17:43:11 +08:00
},
2025-11-24 09:38:02 +08:00
});
break;
}
if (!useAutoGenerateRequired)
return option.rules ? (Array.isArray(option.rules) ? [...option.rules, ...rules] : [option.rules, ...rules]) : [];
2025-12-02 17:43:11 +08:00
if (getRequired(option.required)) {
2025-10-22 14:43:42 +08:00
const isBlurTrigger = !option.render || [
FORM_ITEM_RENDER_ENUM.INPUT,
FORM_ITEM_RENDER_ENUM.TEXTAREA,
FORM_ITEM_RENDER_ENUM.INPUT_NUMBER,
FORM_ITEM_RENDER_ENUM.NUMBER,
].includes(option.render);
2025-11-24 09:38:02 +08:00
rules.push({ required: true, message: `${isBlurTrigger ? "请输入" : "请选择"}${option.label}` });
2025-10-22 14:43:42 +08:00
if (option.rules) {
if (Array.isArray(option.rules)) {
rules.push(...option.rules);
}
else {
rules.push(option.rules);
}
}
2025-11-24 09:38:02 +08:00
2025-10-22 14:43:42 +08:00
return rules;
}
2025-11-24 09:38:02 +08:00
return option.rules ? (Array.isArray(option.rules) ? [...option.rules, ...rules] : [option.rules, ...rules]) : [];
2025-10-22 14:43:42 +08:00
};
2025-11-05 14:42:39 +08:00
// 获取key
const getKey = (option) => {
2025-11-11 09:10:08 +08:00
return option.key || option.name;
};
2025-11-05 14:42:39 +08:00
2025-12-02 17:43:11 +08:00
// 使用 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 ?? []);
};
2025-10-22 14:43:42 +08:00
// 渲染表单控件
const renderFormControl = (option) => {
const componentProps = getComponentProps(option);
const itemsFieldKey = getItemsFieldKey(option);
/** @type {string | Function} */
const render = option.render || FORM_ITEM_RENDER_ENUM.INPUT;
const placeholder = option.placeholder || `${render === FORM_ITEM_RENDER_ENUM.SELECT || render === FORM_ITEM_RENDER_ENUM.RADIO || render === FORM_ITEM_RENDER_ENUM.CHECKBOX ? "选择" : "输入"}${option.label}`;
switch (render) {
case FORM_ITEM_RENDER_ENUM.INPUT:
2025-11-24 14:39:22 +08:00
return <Input placeholder={placeholder} maxLength={50} {...componentProps} />;
2025-10-22 14:43:42 +08:00
case FORM_ITEM_RENDER_ENUM.TEXTAREA:
2025-11-24 14:39:22 +08:00
return <TextArea placeholder={placeholder} maxLength={500} showCount={true} rows={3} {...componentProps} />;
2025-10-22 14:43:42 +08:00
case FORM_ITEM_RENDER_ENUM.INPUT_NUMBER:
case FORM_ITEM_RENDER_ENUM.NUMBER:
return <InputNumber placeholder={placeholder} style={{ width: "100%" }} {...componentProps} />;
case FORM_ITEM_RENDER_ENUM.SELECT:
return (
2025-11-13 15:25:31 +08:00
<Select placeholder={placeholder} showSearch allowClear optionFilterProp="children" {...componentProps}>
2025-10-22 14:43:42 +08:00
{(option.items || []).map((item) => {
const value = item[itemsFieldKey.valueKey];
const label = item[itemsFieldKey.labelKey];
2025-10-22 14:43:42 +08:00
return (
<Select.Option key={value} value={value}>
{label}
</Select.Option>
);
})}
</Select>
);
case FORM_ITEM_RENDER_ENUM.RADIO:
return (
<Radio.Group {...componentProps}>
{(option.items || []).map((item) => {
const value = item[itemsFieldKey.valueKey];
const label = item[itemsFieldKey.labelKey];
2025-10-22 14:43:42 +08:00
return (
<Radio key={value} value={value}>
{label}
</Radio>
);
})}
</Radio.Group>
);
case FORM_ITEM_RENDER_ENUM.CHECKBOX:
return (
<Checkbox.Group {...componentProps}>
{(option.items || []).map((item) => {
const value = item[itemsFieldKey.valueKey];
const label = item[itemsFieldKey.labelKey];
2025-10-22 14:43:42 +08:00
return (
<Checkbox key={value} value={value}>
{label}
</Checkbox>
);
})}
</Checkbox.Group>
);
case FORM_ITEM_RENDER_ENUM.DATE:
return <DatePicker placeholder={placeholder} format="YYYY-MM-DD" style={{ width: "100%" }} {...componentProps} />;
case FORM_ITEM_RENDER_ENUM.DATE_MONTH:
2025-12-02 17:43:11 +08:00
return (
<DatePicker
picker="month"
placeholder={placeholder}
format="YYYY-MM"
style={{ width: "100%" }}
{...componentProps}
/>
);
2025-10-22 14:43:42 +08:00
case FORM_ITEM_RENDER_ENUM.DATE_YEAR:
2025-12-02 17:43:11 +08:00
return (
<DatePicker
picker="year"
placeholder={placeholder}
format="YYYY"
style={{ width: "100%" }}
{...componentProps}
/>
);
2025-10-22 14:43:42 +08:00
case FORM_ITEM_RENDER_ENUM.DATE_WEEK:
2025-12-02 17:43:11 +08:00
return (
<DatePicker
picker="week"
placeholder={placeholder}
format="YYYY-wo"
style={{ width: "100%" }}
{...componentProps}
/>
);
2025-10-22 14:43:42 +08:00
case FORM_ITEM_RENDER_ENUM.DATE_RANGE:
return (
<RangePicker
placeholder={[`请选择开始${option.label}`, `请选择结束${option.label}`]}
format="YYYY-MM-DD"
style={{ width: "100%" }}
{...componentProps}
/>
);
case FORM_ITEM_RENDER_ENUM.DATETIME:
return (
<DatePicker
showTime
placeholder={placeholder}
format="YYYY-MM-DD HH:mm:ss"
style={{ width: "100%" }}
{...componentProps}
/>
);
case FORM_ITEM_RENDER_ENUM.DATETIME_RANGE:
return (
<RangePicker
showTime
placeholder={[`请选择开始${option.label}`, `请选择结束${option.label}`]}
format="YYYY-MM-DD HH:mm:ss"
style={{ width: "100%" }}
{...componentProps}
/>
);
case FORM_ITEM_RENDER_ENUM.DIVIDER:
return null;
default:
2025-11-05 14:24:35 +08:00
return render;
2025-10-22 14:43:42 +08:00
}
};
// 渲染 label带提示
const renderLabel = (option) => {
if (!option.tip)
return option.label;
return (
<>
{option.label}
<Tooltip title={option.tip}>
<InfoCircleOutlined style={{ marginLeft: 4, fontSize: 12 }} />
</Tooltip>
</>
);
};
2025-12-02 17:43:11 +08:00
// 渲染普通表单项
const renderFormItem = ({ option, style, col, index, name }) => {
if (getHidden(option.hidden))
return null;
return (
<Col key={getKey(option) || index} span={col.span} style={style}>
<Form.Item
name={name || option.name}
label={renderLabel(option)}
rules={getRules(option)}
labelCol={col.labelCol}
wrapperCol={col.wrapperCol}
preserve={false}
{...getFormItemProps(option)}
>
{renderFormControl(option)}
</Form.Item>
</Col>
);
};
// 渲染特殊类型的表单项
const renderOtherTypeItem = ({ option, style, col, index, name }) => {
// 如果是 customizeRender 类型,完全交给外部控制渲染
if (option.customizeRender) {
return (
<Col key={getKey(option) || index} span={col.span} style={style}>
{option.render}
</Col>
);
}
// 如果是 onlyForLabel 类型不渲染任何UI只在表单中保存数据
if (option.onlyForLabel) {
return (
<Form.Item
key={getKey(option) || index}
name={name || option.name}
noStyle
preserve={false}
>
<input type="hidden" />
</Form.Item>
);
}
// 如果是分割线
if (option.render === FORM_ITEM_RENDER_ENUM.DIVIDER) {
return (
<Col key={getKey(option) || index} span={col.span} style={style}>
<Divider orientation="left">{option.label}</Divider>
</Col>
);
}
return null;
};
// 渲染 Form.List
const renderFormList = (option, index, col, style) => {
const formListUniqueProps = getFormListUniqueProps(option);
return (
<Col key={getKey(option) || index} span={col.span} style={style}>
<Form.List name={option.name}>
{(fields, { add, remove }) => (
<>
{fields.map((field, fieldIndex) => {
const listOptions = getListOptions(option.formListUniqueProps.options, field);
return (
<Row gutter={gutter} key={field.key}>
{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 (
<Col key={getKey(listOption) || listIndex} span={col.span} style={style}>
<Form.Item
label={renderLabel(listOption)}
labelCol={col.labelCol}
wrapperCol={col.wrapperCol}
preserve={false}
required={getRequired(listOption.required)}
{...getFormItemProps(listOption)}
>
<div style={{ display: "flex", gap: 10, alignItems: "center" }}>
<Form.Item
noStyle
rules={getRules(listOption)}
name={[field.name, listOption.name]}
>
{renderFormControl(listOption)}
</Form.Item>
{
// 只有当不是第一行时才显示删除按钮
fieldIndex >= 1
? (
formListUniqueProps.showRemoveButton
&& (
<Button
type="primary"
danger
onClick={() => remove(field.name)}
>
{formListUniqueProps.removeButtonText}
</Button>
)
)
: (
// 第一行显示添加按钮
formListUniqueProps.showAddButton
&& (
<Button
type="primary"
onClick={() => add(formListUniqueProps.addDefaultValue, formListUniqueProps.addInsertIndex)}
>
{formListUniqueProps.addButtonText}
</Button>
)
)
}
</div>
</Form.Item>
</Col>
);
}
return renderFormItem(params);
})}
</Row>
);
})}
</>
)}
</Form.List>
</Col>
);
};
// 渲染需要动态更新的表单项
const renderDynamicFormItem = (option, index, style, col) => {
return (
<Form.Item
key={getKey(option) || index}
noStyle
preserve={false}
shouldUpdate={option.shouldUpdate ?? option?.componentProps?.shouldUpdate}
dependencies={option.dependencies ?? option?.componentProps?.dependencies}
>
{() => {
return renderFormItem({ option, style, col, index });
}}
</Form.Item>
);
};
2025-10-22 14:43:42 +08:00
return (
<>
{options.map((option, index) => {
2025-12-02 17:43:11 +08:00
const col = getCol(option);
const style = getStyle(index);
2025-11-11 09:10:08 +08:00
2025-12-02 17:43:11 +08:00
// 处理特殊类型的表单项
const otherTypeItem = renderOtherTypeItem({ option, style, col, index });
if (otherTypeItem)
return otherTypeItem;
2025-11-10 16:39:38 +08:00
2025-12-02 17:43:11 +08:00
// 如果是 Form.List
if (option.render === FORM_ITEM_RENDER_ENUM.FORM_LIST) {
return renderFormList(option, index, col, style);
2025-10-22 14:43:42 +08:00
}
// 如果配置了 shouldUpdate 或 dependencies使用 Form.Item 的联动机制
if ((option.shouldUpdate ?? option.dependencies) || (option?.componentProps?.shouldUpdate ?? option?.componentProps?.dependencies)) {
2025-12-02 17:43:11 +08:00
return renderDynamicFormItem(option, index, style, col);
2025-10-22 14:43:42 +08:00
}
// 普通表单项(静态配置)
2025-12-02 17:43:11 +08:00
return renderFormItem({ option, style, col, index });
2025-10-22 14:43:42 +08:00
})}
</>
);
};
FormItemsRenderer.displayName = "FormItemsRenderer";
export default FormItemsRenderer;