353 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
			
		
		
	
	
			353 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,
 | ||
|   labelCol,
 | ||
|   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;
 | ||
|         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))
 | ||
|               : (
 | ||
|                   <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={itemLabelCol}
 | ||
|                             wrapperCol={itemWrapperCol}
 | ||
|                             {...getFormItemProps(option)}
 | ||
|                           >
 | ||
|                             {renderFormControl(option)}
 | ||
|                           </Form.Item>
 | ||
|                         );
 | ||
|                       }}
 | ||
|                     </Form.Item>
 | ||
|                   </Col>
 | ||
|                 )
 | ||
|           );
 | ||
|         }
 | ||
| 
 | ||
|         // 普通表单项(静态配置)
 | ||
|         if (option.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;
 |