346 lines
11 KiB
JavaScript
346 lines
11 KiB
JavaScript
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,
|
||
span = 12,
|
||
collapse = false,
|
||
useAutoGenerateRequired = true,
|
||
}) => {
|
||
const form = Form.useFormInstance();
|
||
|
||
// 获取传给组件的属性
|
||
const getComponentProps = (option) => {
|
||
return typeof option.componentProps === "function"
|
||
? option.componentProps(form.getFieldsValue())
|
||
: (option.componentProps || {});
|
||
};
|
||
|
||
// 获取传给formItem的属性
|
||
const getFormItemProps = (option) => {
|
||
const formItemProps = typeof option.formItemProps === "function"
|
||
? option.formItemProps(form.getFieldsValue())
|
||
: (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 || "id",
|
||
labelKey: option?.itemsField?.labelKey || "name",
|
||
};
|
||
};
|
||
|
||
// 获取验证规则
|
||
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"
|
||
? option.required(form.getFieldsValue())
|
||
: (option.required ?? true);
|
||
|
||
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) => {
|
||
const value = item[itemsFieldKey.valueKey];
|
||
const label = item[itemsFieldKey.labelKey];
|
||
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];
|
||
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];
|
||
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:
|
||
// 支持传入自定义组件
|
||
if (typeof render === "function") {
|
||
const CustomComponent = render;
|
||
return <CustomComponent {...componentProps} />;
|
||
}
|
||
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) => {
|
||
const itemSpan = option.render === FORM_ITEM_RENDER_ENUM.DIVIDER ? 24 : option.span ?? span;
|
||
// 使用 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 的联动机制
|
||
if ((option.shouldUpdate ?? option.dependencies) || (option?.componentProps?.shouldUpdate ?? option?.componentProps?.dependencies)) {
|
||
return (
|
||
option.customizeRender
|
||
? (renderFormControl(option))
|
||
: (
|
||
<Col key={option.name || index} span={itemSpan} style={style}>
|
||
<Form.Item
|
||
noStyle
|
||
shouldUpdate={option.shouldUpdate ?? option?.componentProps?.shouldUpdate}
|
||
dependencies={option.dependencies || option?.componentProps?.dependencies}
|
||
>
|
||
{(form) => {
|
||
// 支持动态计算 hidden
|
||
const hidden = typeof option.hidden === "function"
|
||
? option.hidden(form.getFieldsValue())
|
||
: (option.hidden ?? false);
|
||
|
||
if (hidden)
|
||
return null;
|
||
|
||
return (
|
||
<Form.Item
|
||
name={option.name}
|
||
label={renderLabel(option)}
|
||
rules={getRules(option)}
|
||
labelCol={option.labelCol}
|
||
wrapperCol={option.wrapperCol}
|
||
{...getFormItemProps(option)}
|
||
>
|
||
{renderFormControl(option)}
|
||
</Form.Item>
|
||
);
|
||
}}
|
||
</Form.Item>
|
||
</Col>
|
||
)
|
||
);
|
||
}
|
||
|
||
// 普通表单项(静态配置)
|
||
if (option.hidden)
|
||
return null;
|
||
|
||
return (
|
||
option.customizeRender
|
||
? (renderFormControl(option))
|
||
: (
|
||
<Col key={option.name || index} span={itemSpan} style={style}>
|
||
<Form.Item
|
||
name={option.name}
|
||
label={renderLabel(option)}
|
||
rules={getRules(option)}
|
||
labelCol={option.labelCol}
|
||
wrapperCol={option.wrapperCol}
|
||
{...getFormItemProps(option)}
|
||
>
|
||
{renderFormControl(option)}
|
||
</Form.Item>
|
||
</Col>
|
||
)
|
||
);
|
||
})}
|
||
</>
|
||
);
|
||
};
|
||
|
||
FormItemsRenderer.displayName = "FormItemsRenderer";
|
||
|
||
export default FormItemsRenderer;
|