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 || "value", | |||
|  |       labelKey: option?.itemsField?.labelKey || "label", | |||
|  |     }; | |||
|  |   }; | |||
|  | 
 | |||
|  |   // 获取验证规则
 | |||
|  |   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] ?? item.dictionariesId ?? item.id; | |||
|  |               const label = item[itemsFieldKey.labelKey] ?? item.name; | |||
|  |               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] ?? item.dictionariesId ?? item.id; | |||
|  |               const label = item[itemsFieldKey.labelKey] ?? item.name; | |||
|  |               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] ?? item.dictionariesId ?? item.id; | |||
|  |               const label = item[itemsFieldKey.labelKey] ?? item.name; | |||
|  |               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; |