373 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
			
		
		
	
	
			373 lines
		
	
	
		
			12 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,
 | ||
|                              labelCol,
 | ||
|                              span = 12,
 | ||
|                              collapse = false,
 | ||
|                              useAutoGenerateRequired = true,
 | ||
|                              initialValues,
 | ||
|                            }) => {
 | ||
|   const form = Form.useFormInstance();
 | ||
| 
 | ||
|   // 获取表单值,优先使用 initialValues
 | ||
|   const getFormValues = () => {
 | ||
|     const formValues = form.getFieldsValue();
 | ||
|     // 如果表单值为空但有 initialValues,则使用 initialValues
 | ||
|     if (Object.keys(formValues).length === 0 && initialValues) {
 | ||
|       return initialValues;
 | ||
|     }
 | ||
|     return formValues;
 | ||
|   };
 | ||
| 
 | ||
|   // 获取传给组件的属性
 | ||
|   const getComponentProps = (option) => {
 | ||
|     return typeof option.componentProps === "function"
 | ||
|       ? option.componentProps(getFormValues())
 | ||
|       : (option.componentProps || {});
 | ||
|   };
 | ||
| 
 | ||
|   // 获取传给formItem的属性
 | ||
|   const getFormItemProps = (option) => {
 | ||
|     const formItemProps = typeof option.formItemProps === "function"
 | ||
|       ? option.formItemProps(getFormValues())
 | ||
|       : (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(getFormValues())
 | ||
|       : (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" || typeof render === "object") {
 | ||
|           const CustomComponent = render;
 | ||
|           if (typeof render === "function")
 | ||
|             return <CustomComponent {...componentProps} formValues={getFormValues()} />;
 | ||
|           if (typeof render === "object")
 | ||
|             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;
 | ||
|         const itemLabelCol = option.labelCol ?? (itemSpan === 24 ? { span: labelCol.span / 2 } : labelCol);
 | ||
|         const itemWrapperCol = option.wrapperCol ?? { span: 24 - itemLabelCol.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))
 | ||
|               : (
 | ||
|                 <Form.Item
 | ||
|                   key={option.name || index}
 | ||
|                   noStyle
 | ||
|                   shouldUpdate={option.shouldUpdate ?? option?.componentProps?.shouldUpdate}
 | ||
|                   dependencies={option.dependencies || option?.componentProps?.dependencies}
 | ||
|                 >
 | ||
|                   {() => {
 | ||
|                     // 支持动态计算 hidden
 | ||
|                     const hidden = typeof option.hidden === "function"
 | ||
|                       ? option.hidden(getFormValues())
 | ||
|                       : (option.hidden ?? false);
 | ||
| 
 | ||
|                     if (hidden)
 | ||
|                       return null;
 | ||
| 
 | ||
|                     return (
 | ||
|                       <Col key={option.name || index} span={itemSpan} style={style}>
 | ||
|                         <Form.Item
 | ||
|                           name={option.name}
 | ||
|                           label={renderLabel(option)}
 | ||
|                           rules={getRules(option)}
 | ||
|                           labelCol={itemLabelCol}
 | ||
|                           wrapperCol={itemWrapperCol}
 | ||
|                           {...getFormItemProps(option)}
 | ||
|                         >
 | ||
|                           {renderFormControl(option)}
 | ||
|                         </Form.Item>
 | ||
|                       </Col>
 | ||
|                     );
 | ||
|                   }}
 | ||
|                 </Form.Item>
 | ||
|               )
 | ||
|           );
 | ||
|         }
 | ||
| 
 | ||
|         // 普通表单项(静态配置)
 | ||
|         // 支持动态计算 hidden
 | ||
|         const hidden = typeof option.hidden === "function"
 | ||
|           ? option.hidden(getFormValues())
 | ||
|           : (option.hidden ?? false);
 | ||
| 
 | ||
|         if (hidden)
 | ||
|           return null;
 | ||
| 
 | ||
|         return (
 | ||
|           <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>
 | ||
|         );
 | ||
|       })}
 | ||
|     </>
 | ||
|   );
 | ||
| };
 | ||
| 
 | ||
| FormItemsRenderer.displayName = "FormItemsRenderer";
 | ||
| 
 | ||
| export default FormItemsRenderer;
 |