Skip to content

组件名称:BaseInputNumber

简介:BaseInputNumber 是一个封装了 Ant Design 的 InputNumber 组件,提供了数字输入框的功能,并扩展了更多的自定义选项和状态管理功能。该组件主要用于处理表单中需要限制范围、可编辑性以及与外部逻辑交互的数字输入场景。

Props(属性):

  • details: IBaseInputNumberProps['details']

    • 描述:包含表单控件详细配置的对象。
    • 示例:
      typescript
      {
      	unit?: string; // 单位文本,显示在输入框后
      	decimal?: number; // 小数点后的位数
      	step?: number; // 步进值
      	hasControl?: boolean; // 是否显示增减按钮,默认为 true
      	valueRange?: { min?: number; max?: number }; // 可输入数值范围
      	isEditable?: boolean; // 表示当前字段是否可编辑,默认为 true
      }
  • value: string | { value: any; switch?: boolean }

    • 描述:输入框当前值,可以是简单类型的值,也可以是一个包含 value 和可选的 switch 属性的对象,用于控制是否禁用输入框。
  • onChange: (value: any) => void

    • 描述:当输入框值发生变化时触发的回调函数。
  • changeAllFormData: (value: any, fieldName: string, onChange: (value: any) => void, onBlur: () => void) => void

    • 描述:统一更改所有表单数据的方法,会在内部调用以更新表单状态。
  • changeTableFormData: (value: any) => void

    • 描述:用于更改二级表单数据的方法,旨在优化性能。
  • fieldName: string

    • 描述:表单字段名称。
  • outerBlur: () => void

    • 描述:外部提供的失焦事件处理函数。
  • relatedChange: (value: any, fieldName: string) => void

    • 描述:当关联字段变化时触发的回调函数。
  • extraConfig: { switch?: { value?: boolean } }

    • 描述:额外配置项,其中可能包含控制输入框开关状态的 switch 属性。
  • hasControl: boolean

    • 描述:如果设置为 true,则显示 InputNumber 上的增加/减少按钮。
  • hasToolTip: boolean

    • 描述:如果为 true,则为 InputNumber 添加 Tooltip 提示。
  • formDisable: boolean

    • 描述:表示整个表单是否禁用,如果为 true,将禁用此输入框。
  • valueRange: { min?: number; max?: number }

    • 描述:可输入值的范围限制,防止计算出现负数或除数为0的情况。
  • onBlur: () => void

    • 描述:失焦触发的回调函数。
  • coverConfig: Record<string, any>

    • 描述:用来覆盖后台动态表单部分配置的外部传入对象。

使用示例:

jsx
import BaseInputNumber from '@/components/BaseInputNumber';

const ExampleComponent = () => {
	const [inputValue, setInputValue] = useState < number > (0);
	const handleInputChange = (value: number) => {
		setInputValue(value);
	};

	return (
		<div>
			<BaseInputNumber
				value={inputValue}
				onChange={handleInputChange}
				details={{
					unit: '元',
					decimal: 2,
					valueRange: { min: 0, max: 100 },
				}}
				formDisable={false}
				hasToolTip
			/>
		</div>
	);
};

export default ExampleComponent;

注意:

  1. 组件根据 detailsvalueRangeextraConfig 等属性确定输入框的状态和行为。
  2. 当表单值改变时,会通过 changeVal 方法触发一系列内部及外部回调函数来更新表单状态和关联字段。
  3. 如果有 ToolTip 需求,可以通过设置 hasToolTiptrue 来启用。
展开查看源码
ts
/**
 * 定义BaseInputNumber组件的props接口
 */
export interface IBaseInputNumberProps {
	// 其他动态属性,键为字符串,值为任意类型
	[val: string]: any;

	// 输入框的当前值,可能是简单类型的值,也可能是包含value和switch的对象
	value?: { value: any; switch?: boolean } | string;

	// 当值改变时触发的回调函数
	onChange?: (value: any) => void;

	// 改变所有表单数据的回调函数
	changeAllFormData?: (
		value: any,
		fieldName: string,
		onChange: (value: any) => void,
		onBlur: () => void
	) => void;

	// 更改二级表单数据的回调函数,用于性能优化
	changeTableFormData?: (value: any) => void;

	// 表单字段名称
	fieldName: string;

	// 外部提供的失焦事件处理函数
	outerBlur?: () => void;

	// 关联字段变化时触发的回调函数
	relatedChange?: (value: any, fieldName: string) => void;

	// 额外配置项,可以覆盖后台动态表单的部分配置
	extraConfig?: {
		switch?: { value?: boolean };
	};

	// 是否显示增减按钮,也可设置自定义箭头图标
	hasControl?: boolean;

	// 是否显示工具提示
	hasToolTip?: boolean;

	// 表单是否禁用
	formDisable?: boolean;

	// 可输入值的范围限制
	valueRange?: { min?: number; max?: number };

	// 失焦时触发的回调函数
	onBlur?: () => void;

	// 用来覆盖后台动态表单部分配置的外部传入对象
	coverConfig?: Record<string, any>;

	// 表单控件详细信息
	details?: {
		unit?: string; // 单位
		decimal?: number; // 小数位数
		step?: number; // 步长
		hasControl?: boolean; // 是否显示增减按钮,也可设置自定义箭头图标
		isEditable?: boolean; // 是否可编辑
		valueRange?: { min?: number; max?: number }; // 后台设置的值范围限制
	};
}
tsx
import React, { memo, useCallback, useMemo } from "react";
import { InputNumber, Tooltip } from "antd";
import { isObject } from "lodash-es";
import styles from "./index.module.less";
import { IBaseInputNumberProps } from "@/components/BaseInputNumber/IBaseInputNumberProps.ts";

function judgeVal(arg0: any): any {
	if (arg0 === undefined) 
return false;
	return true;
}

const BaseInputNumber: React.FC<IBaseInputNumberProps> = (props) => {
	const {
		details,
		value,
		onChange,
		changeAllFormData, // 这是统一走更改表单值的逻辑
		changeTableFormData, // 这里为性能考虑改外层二级表单值
		fieldName,
		outerBlur,
// 外部传入的blur方法
		relatedChange,
		extraConfig,
		hasControl,
		hasToolTip,
		formDisable,
		valueRange, // 可输入的范围,注意和校验区分,这里限制可输入范围主要是不想数字参与计算时出现负数或者除数为0
		onBlur, // 失焦触发
		coverConfig, // 外部传入,用来覆盖后台动态表单的某些配置
	} = props;

	/**
	 * 真实值
	 * 例如{switch:true,value:'longmo'}
	 * 或者'longmo'
	 */
	const realVal = useMemo(() => {
		// 需要兼容形如  {switch:false,value:'fff'} 这种类型
		if (isObject(value)) 
return value.value;
		// 如果是简单类型的就直接返回
		return value;
	}, [value]);

	/**
	 * 是否显示增减按钮
	 * 例如 details.hasControl=true
	 * 或者 hasControl=true
	 */
	const isControl = useMemo(() => {
		if (judgeVal(details?.hasControl)) 
return details?.hasControl;
		if (judgeVal(hasControl)) 
return hasControl;
		return true;
	}, [hasControl, details?.hasControl]);

	/**
	 * 可输入值的范围限制
	 * 例如 details.valueRange={min:0,max:100} 这种类型
	 * 例如 valueRange={min:0,max:100} 这种类型
	 * 优先级:details.valueRange > valueRange
	 */
	const inputRange = useMemo(() => {
		// 处理数字输入框限定输入范围的上下限(注意,此处接口details设置的上下限优先级没有getCustomProps自定义属性里设置的高)
		if (details?.valueRange) {
			return details.valueRange;
		}

		if (valueRange) {
			return valueRange;
		}
		return {};
	}, [valueRange, details?.valueRange]);

	/**
	 * 是否禁用
	 * 例如{formDisable=true
	 * 或者 value?.switch=false
	 * 或者 extraConfig?.switch?.value=false
	 * 或者 details?.isEditable=false
	 */
	const isDisable = useMemo(() => {
		// 这里后续要增加下,改成异或需同时判断formDisable、value、extraConfig?.switch的值
		if (judgeVal(formDisable)) {
			return !formDisable;
		}

		// 只有对象类型的值才会有disable这种属性,或者查看当前details里的isEditable属性
		if (isObject(value) && typeof value.switch === "boolean") {
			return !value.switch;
		}

		// 有switch一定是Object类型,且如果不设置switch的value值,则直接disable掉!
		if (judgeVal(extraConfig?.switch)) 
return !extraConfig.switch?.value;

		return !(details?.isEditable ?? true);
	}, [value, details, extraConfig?.switch, formDisable]);

	/**
	 * 失焦事件
	 * 1. 触发外部传入的失焦事件
	 */
	const blurVal = useCallback(() => {
		onBlur?.();

		// 外层blur不能与form本身的blur放在一个任务队列中处理;
		// 因为不确定外层blur是否要用blur后的值,所以另开一个宏任务进行外层blur处理;
		// 如果放在同一队列,可能出现四舍五入小数位数时,outerBlur拿的是四舍五入前的值在参与逻辑处理
		setTimeout(() => {
			outerBlur?.();
		}, 10); // 这里设置10ms为了防止外层有change事件放在下一轮事件循环的情况从而导致outerBlur拿的值不是最新更改值的情况
	}, [onBlur, outerBlur]);

	/**
	 * 改变值
	 * 针对label上有switch时,外部必须传入extraConfig.switch。
	 * 并且输入框置灰以及label开关的状态有取值优先级逻辑:
	 * 如果value上有switch值,则默认取value上的值;如果value无值(一般是异常情况),则取extraConfig?.switch?.value的值
	 */
	const changeVal = useCallback(
		(valuePar?: any) => {
			let val = valuePar;
			if (isObject(value)) {
				val = { ...value, value: val };
			}
			// 加此elseIf 是为了防止后端配置项返回了,但值未返回的情况,此时如不手动设置对象类型会直接返简单类型值
			else if (judgeVal(extraConfig?.switch)) {
				// 如果label上有switch项则value值是对象类型,此时一般是初始默认值没填上需要手动改成对象类型
				//  let switchStatus=false
				//  if(judgeVal(extraConfig?.switch?.value)) {
				//   switchStatus=!!extraConfig.switch.value
				//  }
				val = { value: val, switch: !!extraConfig?.switch?.value };
			}

			// 将关联的字段进行变动,后续可以考虑拎到baseForm层
			if (relatedChange) {
				relatedChange(val, fieldName);
			}

			changeTableFormData?.(val);

			// 这里加blurVal是为了上下限情况下外部又setTimeout change时值更新不及时的问题(且更新不及时只在下限为0时能复现,
			// 解决方法一个加10ms延时或者外部调这个blur)
			changeAllFormData?.(val, fieldName, onChange, blurVal);

			if (!changeAllFormData && !changeTableFormData) {
				onChange(val);
			}
		},
		[
			value,
			fieldName,
			relatedChange,
			onChange,
			changeAllFormData,
			changeTableFormData,
			extraConfig?.switch,
			blurVal,
		]
	);

	/**
	 * 真实属性
	 * 例如{value:realVal,onChange:changeVal,onBlur:blurVal,disabled:isDisable}
	 * 或者{value:realVal,onChange:changeVal,onBlur:blurVal}
	 */
	const realProps = useMemo(() => {
		// 这里之所以将disable单独拎出来是为了防止form组件设置disable后,如果控件单独有disable的话form组件的disable就不会生效
		let disableProp = {};
		if (isDisable) {
			disableProp = { disabled: isDisable };
		}

		return {
			value: realVal,
			onChange: changeVal,
			onBlur: blurVal,
			...disableProp,
			...inputRange,
		};
	}, [realVal, changeVal, isDisable, blurVal, inputRange]);

	/**
	 * 额外属性
	 * 用于覆盖上述的属性以及增加一些特殊化配置
	 */
	const extraProps = useMemo(() => {
		// 加一些特殊化配置
		if (coverConfig) 
return coverConfig;
		return {};
	}, [coverConfig]);

	return (
		<div className={styles.baseInputWrap}>
			{hasToolTip ? (
				<Tooltip title={realVal} destroyTooltipOnHide={{ keepParent: false }}>
					<InputNumber
						type="number"
						addonAfter={details?.unit}
						precision={details?.decimal || 0}
						step={details?.step || 1}
						controls={isControl}
						{...realProps}
						{...extraProps}
						style={{ width: "100%" }}
						className={`${details?.unit ? styles.noRightBorder : ""}`}
					/>
				</Tooltip>
			) : (
				<InputNumber
					type="number"
					addonAfter={details?.unit}
					precision={details?.decimal || 0}
					step={details?.step || 1}
					controls={isControl}
					{...realProps}
					{...extraProps}
					style={{ width: "100%" }}
					className={`${details?.unit ? styles.noRightBorder : ""}`}
				/>
			)}
		</div>
	);
};

export default memo(BaseInputNumber);

Contributors

作者:Long Mo
字数统计:2.2k 字
阅读时长:8 分钟
Long Mo
文章作者:Long Mo
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Longmo Docs