2025-10-22 14:43:42 +08:00
|
|
|
|
import { InfoCircleOutlined } from "@ant-design/icons";
|
|
|
|
|
|
import {
|
|
|
|
|
|
Checkbox,
|
|
|
|
|
|
Col,
|
|
|
|
|
|
DatePicker,
|
|
|
|
|
|
Divider,
|
|
|
|
|
|
Form,
|
|
|
|
|
|
Input,
|
|
|
|
|
|
InputNumber,
|
|
|
|
|
|
Radio,
|
|
|
|
|
|
Select,
|
|
|
|
|
|
Tooltip,
|
|
|
|
|
|
} from "antd";
|
|
|
|
|
|
import dayjs from "dayjs";
|
|
|
|
|
|
import { FORM_ITEM_RENDER_ENUM } from "../../enum/formItemRender";
|
|
|
|
|
|
|
|
|
|
|
|
const { TextArea } = Input;
|
|
|
|
|
|
const { RangePicker } = DatePicker;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 表单项渲染器组件
|
|
|
|
|
|
*/
|
|
|
|
|
|
const FormItemsRenderer = ({
|
|
|
|
|
|
options,
|
2025-10-28 12:30:57 +08:00
|
|
|
|
labelCol,
|
2025-10-22 14:43:42 +08:00
|
|
|
|
span = 12,
|
|
|
|
|
|
collapse = false,
|
|
|
|
|
|
useAutoGenerateRequired = true,
|
2025-10-29 09:38:36 +08:00
|
|
|
|
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 || {});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 获取传给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 {
|
2025-10-28 08:48:18 +08:00
|
|
|
|
valueKey: option?.itemsField?.valueKey || "id",
|
|
|
|
|
|
labelKey: option?.itemsField?.labelKey || "name",
|
2025-10-22 14:43:42 +08:00
|
|
|
|
};
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 获取验证规则
|
|
|
|
|
|
const getRules = (option) => {
|
|
|
|
|
|
if (!useAutoGenerateRequired)
|
|
|
|
|
|
return option.rules ? (Array.isArray(option.rules) ? option.rules : [option.rules]) : [];
|
|
|
|
|
|
if (option.render === FORM_ITEM_RENDER_ENUM.DIVIDER)
|
|
|
|
|
|
return [];
|
|
|
|
|
|
|
|
|
|
|
|
// 支持动态计算 required
|
|
|
|
|
|
const required = typeof option.required === "function"
|
2025-10-29 09:38:36 +08:00
|
|
|
|
? option.required(getFormValues())
|
2025-10-24 17:51:03 +08:00
|
|
|
|
: (option.required ?? true);
|
2025-10-22 14:43:42 +08:00
|
|
|
|
|
|
|
|
|
|
if (required) {
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
|
|
const rules = [
|
|
|
|
|
|
{
|
|
|
|
|
|
required: true,
|
|
|
|
|
|
message: `${isBlurTrigger ? "请输入" : "请选择"}${option.label}`,
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
if (option.rules) {
|
|
|
|
|
|
if (Array.isArray(option.rules)) {
|
|
|
|
|
|
rules.push(...option.rules);
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
rules.push(option.rules);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return rules;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return option.rules ? (Array.isArray(option.rules) ? option.rules : [option.rules]) : [];
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染表单控件
|
|
|
|
|
|
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:
|
|
|
|
|
|
return <Input placeholder={placeholder} {...componentProps} />;
|
|
|
|
|
|
|
|
|
|
|
|
case FORM_ITEM_RENDER_ENUM.TEXTAREA:
|
|
|
|
|
|
return <TextArea placeholder={placeholder} rows={3} {...componentProps} />;
|
|
|
|
|
|
|
|
|
|
|
|
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 (
|
|
|
|
|
|
<Select placeholder={placeholder} {...componentProps}>
|
|
|
|
|
|
{(option.items || []).map((item) => {
|
2025-10-28 08:48:18 +08:00
|
|
|
|
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) => {
|
2025-10-28 08:48:18 +08:00
|
|
|
|
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) => {
|
2025-10-28 08:48:18 +08:00
|
|
|
|
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:
|
|
|
|
|
|
return <DatePicker picker="month" placeholder={placeholder} format="YYYY-MM" style={{ width: "100%" }} {...componentProps} />;
|
|
|
|
|
|
|
|
|
|
|
|
case FORM_ITEM_RENDER_ENUM.DATE_YEAR:
|
|
|
|
|
|
return <DatePicker picker="year" placeholder={placeholder} format="YYYY" style={{ width: "100%" }} {...componentProps} />;
|
|
|
|
|
|
|
|
|
|
|
|
case FORM_ITEM_RENDER_ENUM.DATE_WEEK:
|
|
|
|
|
|
return <DatePicker picker="week" placeholder={placeholder} format="YYYY-wo" style={{ width: "100%" }} {...componentProps} />;
|
|
|
|
|
|
|
|
|
|
|
|
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-10-29 15:33:10 +08:00
|
|
|
|
if (typeof render === "function" || typeof render === "object") {
|
2025-10-22 14:43:42 +08:00
|
|
|
|
const CustomComponent = render;
|
2025-10-29 15:33:10 +08:00
|
|
|
|
if (typeof render === "function")
|
|
|
|
|
|
return <CustomComponent {...componentProps} formValues={getFormValues()} />;
|
|
|
|
|
|
if (typeof render === "object")
|
|
|
|
|
|
return <CustomComponent {...componentProps} />;
|
2025-10-22 14:43:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
return <Input placeholder={placeholder} {...componentProps} />;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染 label(带提示)
|
|
|
|
|
|
const renderLabel = (option) => {
|
|
|
|
|
|
if (!option.tip)
|
|
|
|
|
|
return option.label;
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<>
|
|
|
|
|
|
{option.label}
|
|
|
|
|
|
<Tooltip title={option.tip}>
|
|
|
|
|
|
<InfoCircleOutlined style={{ marginLeft: 4, fontSize: 12 }} />
|
|
|
|
|
|
</Tooltip>
|
|
|
|
|
|
</>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<>
|
|
|
|
|
|
{options.map((option, index) => {
|
2025-10-28 12:30:57 +08:00
|
|
|
|
// 列数
|
2025-10-22 14:43:42 +08:00
|
|
|
|
const itemSpan = option.render === FORM_ITEM_RENDER_ENUM.DIVIDER ? 24 : option.span ?? span;
|
2025-10-28 13:33:19 +08:00
|
|
|
|
const itemLabelCol = option.labelCol ?? (itemSpan === 24 ? { span: labelCol.span / 2 } : labelCol);
|
|
|
|
|
|
const itemWrapperCol = option.wrapperCol ?? { span: 24 - itemLabelCol.span };
|
2025-10-28 12:30:57 +08:00
|
|
|
|
|
2025-10-22 14:43:42 +08:00
|
|
|
|
// 使用 style 控制显示/隐藏
|
|
|
|
|
|
const style = collapse && index >= 3 ? { display: "none" } : undefined;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是分割线
|
|
|
|
|
|
if (option.render === FORM_ITEM_RENDER_ENUM.DIVIDER) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Col key={option.name || index} span={itemSpan} style={style}>
|
|
|
|
|
|
<Divider orientation="left">{option.label}</Divider>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果配置了 shouldUpdate 或 dependencies,使用 Form.Item 的联动机制
|
2025-10-24 17:51:03 +08:00
|
|
|
|
if ((option.shouldUpdate ?? option.dependencies) || (option?.componentProps?.shouldUpdate ?? option?.componentProps?.dependencies)) {
|
2025-10-22 14:43:42 +08:00
|
|
|
|
return (
|
|
|
|
|
|
option.customizeRender
|
|
|
|
|
|
? (renderFormControl(option))
|
|
|
|
|
|
: (
|
|
|
|
|
|
<Col key={option.name || index} span={itemSpan} style={style}>
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
noStyle
|
2025-10-24 17:51:03 +08:00
|
|
|
|
shouldUpdate={option.shouldUpdate ?? option?.componentProps?.shouldUpdate}
|
2025-10-22 14:43:42 +08:00
|
|
|
|
dependencies={option.dependencies || option?.componentProps?.dependencies}
|
|
|
|
|
|
>
|
2025-10-29 09:38:36 +08:00
|
|
|
|
{() => {
|
2025-10-22 14:43:42 +08:00
|
|
|
|
// 支持动态计算 hidden
|
|
|
|
|
|
const hidden = typeof option.hidden === "function"
|
2025-10-29 09:38:36 +08:00
|
|
|
|
? option.hidden(getFormValues())
|
2025-10-24 17:51:03 +08:00
|
|
|
|
: (option.hidden ?? false);
|
2025-10-22 14:43:42 +08:00
|
|
|
|
|
|
|
|
|
|
if (hidden)
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name={option.name}
|
|
|
|
|
|
label={renderLabel(option)}
|
|
|
|
|
|
rules={getRules(option)}
|
2025-10-28 12:30:57 +08:00
|
|
|
|
labelCol={itemLabelCol}
|
|
|
|
|
|
wrapperCol={itemWrapperCol}
|
2025-10-22 14:43:42 +08:00
|
|
|
|
{...getFormItemProps(option)}
|
|
|
|
|
|
>
|
|
|
|
|
|
{renderFormControl(option)}
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
);
|
|
|
|
|
|
}}
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
)
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 普通表单项(静态配置)
|
2025-10-29 09:38:36 +08:00
|
|
|
|
// 支持动态计算 hidden
|
|
|
|
|
|
const hidden = typeof option.hidden === "function"
|
|
|
|
|
|
? option.hidden(getFormValues())
|
|
|
|
|
|
: (option.hidden ?? false);
|
|
|
|
|
|
|
|
|
|
|
|
if (hidden)
|
2025-10-22 14:43:42 +08:00
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
2025-10-28 12:30:57 +08:00
|
|
|
|
<Col key={option.name || index} span={itemSpan} style={style}>
|
|
|
|
|
|
{
|
|
|
|
|
|
option.customizeRender
|
|
|
|
|
|
? (renderFormControl(option))
|
|
|
|
|
|
: (
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name={option.name}
|
|
|
|
|
|
label={renderLabel(option)}
|
|
|
|
|
|
rules={getRules(option)}
|
|
|
|
|
|
labelCol={itemLabelCol}
|
|
|
|
|
|
wrapperCol={itemWrapperCol}
|
|
|
|
|
|
{...getFormItemProps(option)}
|
|
|
|
|
|
>
|
|
|
|
|
|
{renderFormControl(option)}
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
</Col>
|
2025-10-22 14:43:42 +08:00
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
FormItemsRenderer.displayName = "FormItemsRenderer";
|
|
|
|
|
|
|
|
|
|
|
|
export default FormItemsRenderer;
|