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

346 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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;