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;
 |