一、InputNumber设置精度后失去焦点会四舍五入
Ant Design(3.x版本)的InputNumber(数字输入框)设置precision(数值精度),输入的值超过精度,失去焦点默认会四舍五入处理。例如:设置precision=2,输入3.145失去焦点后会变成3.15
有些情况是不能四舍五入处理,只能丢弃超过数值精度。比如买卖股票时输入价格,如果四舍五入会影响交易
1、实现方式一
思路:在parser()中获取到输入的值,以"."分割,如果小数位超过精度,就丢失超过精度的值,再重新组装成数值。
注意:不能在onChange中处理,onChange会自动处理末尾的0,例如输入14.20,会被处理成14.2
import * as React from 'react';
import { InputNumber } from 'antd';
interface IInputNumberDiscard {
defaultValue?: number;
precision: number;
}
const InputNumberDiscard = ({ defaultValue, precision, ...othersPropsValue }: IInputNumberDiscard) => {
const [splitValue, setSplitValue] = React.useState([]);
const [precisionValue, setPrecisionValue] = React.useState<number>();
const [numberValue, setNumberValue] = React.useState(defaultValue);
const checkValidValue = (value) => value !== undefined && value !== null && value !== '';
React.useEffect(() => {
if (!splitValue && splitValue.length < 1) {
return;
}
const integerValue = splitValue[0];
let decimalValue = splitValue[1];
if (Number.isNaN(integerValue) || Number.isNaN(decimalValue)) {
return;
}
if (checkValidValue(decimalValue) && decimalValue.length > precision) {
decimalValue = decimalValue.slice(0, precision);
}
setPrecisionValue(decimalValue?.length);
setNumberValue(checkValidValue(decimalValue) ? `${integerValue}.${decimalValue}` : integerValue);
}, [splitValue]);
React.useEffect(() => {
if (!checkValidValue(defaultValue)) {
return;
}
const defaultValueArray = String(defaultValue).split('.');
setSplitValue(defaultValueArray);
}, []);
return (
<InputNumber
{...othersPropsValue}
precision={precisionValue}
value={numberValue}
parser={(value) => {
setSplitValue(String(value).split('.'));
return value;
}}
/>
);
};
InputNumberDiscard.defaultProps = {
defaultValue: undefined,
};
export default InputNumberDiscard;
2、实现方式二
思路:用Input配合一些正则表达式来处理
import * as React from 'react';
import { Input } from 'antd';
interface IInputNumberDiscard {
defaultValue?: number;
precision: number;
}
const InputNumberDiscard = ({ defaultValue, precision, ...othersPropsValue }: IInputNumberDiscard) => {
const [numberValue, setNumberValue] = React.useState(defaultValue);
const checkValidValue = (value) => value !== undefined && value !== null && value !== '';
/**
* 限制输入几位小数
* @param number
* @param decimalPlaces
* @returns
*/
function limitDecimalPlaces(number, decimalPlaces) {
// replace(/^(\\-)*(\d+)\.(\d\d).*$/, '$1$2.$3'); // 保留两位
// $1表示(\\-),是"-"。$2表示(\d+),是0次或多次数字。$3表示(\d\d),是两位数字
// replace(/^(\\-)*(\d+)\.(\d\d\d).*$/, '$1$2.$3'); // 保留三位(保留多少位就加多少个\d)
// $1表示(\\-),是"-"。$2表示(\d+),是0次或多次数字。$3表示(\d\d\d),是三位数字
const decimalPattern = `(\\d+\\.\\d{${decimalPlaces}}).*`;
const regex = new RegExp(`^(\\-)*${decimalPattern}$`);
const replacement = '$1$2';
return number.replace(regex, replacement);
}
const handleValue = (value) => {
let newValue = value;
newValue = value.replace(/[^\d.]/g, ''); // 清除"数字"和"."以外的字符
newValue = newValue.replace(/^\./g, ''); // 不能为"."开头
newValue = newValue.replace('.', '$#$').replace(/\./g, '').replace('$#$', '.'); // 只能输入一个小数点且只保留一个
// replace('.', '$#$'); // 将第一个小数点替换为$#$
// replace(/\./g, ''); // 移除所有的小数点
// replace('$#$', '.'); // 将$#$替换回小数点
newValue = newValue.startsWith('0') && !newValue.startsWith('0.') ? Number(newValue).toString() : newValue; // 处理0123->123
newValue = limitDecimalPlaces(newValue, precision); // 限制输入几位小数
setNumberValue(newValue);
};
const handleChange = (e) => {
const { value } = e.target;
handleValue(value);
};
const handleBlur = (e) => {
const { value } = e.target;
const zeros = '0'.repeat(precision);
const newValue = value.endsWith('.') ? `${value}${zeros}` : value; // 处理123.->123.00
setNumberValue(newValue);
};
React.useEffect(() => {
if (checkValidValue(defaultValue)) {
handleValue(defaultValue);
}
}, []);
return <Input {...othersPropsValue} type="text" onChange={handleChange} value={numberValue} onBlur={handleBlur} />;
};
InputNumberDiscard.defaultProps = {
defaultValue: undefined,
};
export default InputNumberDiscard;
1、实现方式三
思路:在parser()中获取输入的值并赋值给Ref对象,在失去焦点(onBlur)把输入的值直接截取并赋值给InputNumber组件
import Decimal from 'decimal.js';
export interface IInputNumberFormItemProps extends InputNumberProps {
form: any;
label?: any;
style?: React.CSSProperties;
id: string;
rules?: any[];
initialValue?: number;
precision: number;
}
const InputNumberFormItem: React.FC<IInputNumberFormItemProps> = ({
form,
label,
style,
id,
rules,
initialValue,
precision,
...otherProps
}) => {
const { getFieldDecorator, setFieldsValue } = form;
const inputValueRef = React.useRef<any>();
const truncateDecimals = (value) => {
if (Number.isNaN(Number(value)) || value === null || value === '') return undefined;
// 不四舍五入,直接截取
if (value >= 0) {
return new Decimal(value).toFixed(precision, Decimal.ROUND_FLOOR);
}
return new Decimal(value).toFixed(precision, Decimal.ROUND_CEIL);
};
const handleValue = () => {
// 去掉输入第二个.以及.后面的值
const num = Number(String(inputValueRef.current).replace(/(\d+\.\d+)\..*/, '$1'));
setFieldsValue({ [id]: truncateDecimals(num) });
};
return (
<Form.Item label={label}>
{getFieldDecorator(id, {
rules,
initialValue: truncateDecimals(initialValue),
})(
<InputNumber
precision={precision}
onBlur={() => {
handleValue();
}}
parser={(value) => {
inputValueRef.current = value;
return value;
}}
style={style ?? { width: '400px' }}
{...otherProps}
/>,
)}
</Form.Item>
);
};
InputNumberFormItem.defaultProps = {
label: '',
rules: undefined,
initialValue: undefined,
};
export default InputNumberFormItem;
二、下拉框和表单组合,选项框随页面滚动分离
下拉框(Select,TimePicker等)和表单(Form)组合。选项框随页面滚动分离,如下图
1、实现方式一
思路:通过设置getPopupContainer来修改滚动的区域
<Form layout="vertical">
<Form.Item label="">
{getFieldDecorator('key', {
rules: [{ required: true, message: '' }],
initialValue: '',
})(
<Select placeholder="" getPopupContainer={(triggerNode) => triggerNode.parentNode}>
{[].map((item, index) => (
<Option key={index} value={item.value}>
{item.label}
</Option>
))}
</Select>,
)}
</Form.Item>
******
</Form>
缺点:如果Form中有多个下拉框,每一个都需要设置
2、实现方式二
思路:Form添加样式设置滚动,在滚动的时候获取焦点
export const ScrollAntForm = ({ children, ...props }) => {
const handleScroll = (e) => {
if (e.target?.nodeName !== 'FORM') return;
e.target.focus();
};
return (
<Form onScroll={handleScroll} {...props} tabIndex={0}>
{children}
</Form>
);
};
<div styleName="form-wrap">
<ScrollAntForm layout="vertical">
<Form.Item label="">
{getFieldDecorator('key', {
rules: [{ required: true, message: '' }],
initialValue: '',
})(
<Select placeholder="">
{[].map((item, index) => (
<Option key={index} value={item.value}>
{item.label}
</Option>
))}
</Select>,
)}
</Form.Item>
******
</ScrollAntForm>
</div>
.form-wrap {
height: 60vh;
overflow: hidden;
position: relative;
form {
padding: 0 32px;
height: 100%;
overflow-y: scroll;
}
}
参考: