fix: InputNumber组件设置精度为0时仍然可以输入小数问题 (#5599)

This commit is contained in:
RUNZE LU 2022-10-24 14:18:51 +08:00 committed by GitHub
parent 977c37ba55
commit 92069f588a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 227 additions and 76 deletions

View File

@ -26,7 +26,7 @@ order: 32
## 设置精度
`precision` 设置数字的显示精度,一般需要配合`step`属性使用,以实现细粒度调整。注意带有单位的输入不支持配置精度属性。
`precision` 设置数字的显示精度,一般需要配合`step`属性使用,以实现细粒度调整。注意带有单位的输入不支持配置精度属性。若设置了`step`值,则会基于`step` 和`precision`的值,选择更高的精度。若输入的内容不满足精度要求,组件会按照精度自动处理,遵循四舍五入规则。
```schema: scope="body"
{
@ -51,6 +51,61 @@ order: 32
"label": "数字2",
"precision": 3,
"step": 0.001
},
{
"type": "input-number",
"name": "number3",
"label": "数字3",
"step": 0.001,
"description": "不设置precision仅设置step, 实际精度为3"
}
]
}
```
## 重置值
清空/重置组件输入后,组件绑定的值将被设置为`resetValue`,默认为`""`。若`resetValue`为合法数字时,会根据`min`、`max`和`precision`属性,将组件值设置为满足条件的值。若`resetValue`为非数字,则组件清空/重置后设置为该值。
```schema: scope="body"
{
"type": "form",
"debug": true,
"api": "/api/mock2/form/saveForm",
"body": [
{
"type": "input-number",
"name": "number1",
"label": "数字resetValue为0",
"resetValue": 0,
"value": 1234
},
{
"type": "input-number",
"name": "number2",
"label": "数字带有min",
"min": 100,
"resetValue": 0,
"value": 1234,
"description": "清空输入后组件值变为100因为设置了最小值min为100"
},
{
"type": "input-number",
"name": "number3",
"label": "数字带有max和precision",
"max": 100.50,
"precision": 2,
"resetValue": 1000,
"value": 1234,
"description": "清空输入后组件值变为100.5因为设置了最大值max为100.5"
},
{
"type": "input-number",
"name": "number4",
"label": "数字未设置resetValue",
"resetValue": "string",
"value": 1234,
"description": "清空输入后组件值变为\"string\"因为resetValue不是一个合法的数字"
}
]
}
@ -160,11 +215,11 @@ order: 32
当做选择器表单项使用时,除了支持 [普通表单项属性表](./formitem#%E5%B1%9E%E6%80%A7%E8%A1%A8) 中的配置以外,还支持下面一些配置
| 属性名 | 类型 | 默认值 | 说明 |
| ---------------- | --------------------------------------- | ------ | -------------------- |
| ---------------- | --------------------------------------- | ------ | ------------------------------------------ |
| min | [模板](../../../docs/concepts/template) | | 最小值 |
| max | [模板](../../../docs/concepts/template) | | 最大值 |
| step | `number` | | 步长 |
| precision | `number` | | 精度,即小数点后几位 |
| precision | `number` | | 精度,即小数点后几位,支持 0 和正整数 |
| showSteps | `boolean` | | 是否显示上下点击按钮 |
| prefix | `string` | | 前缀 |
| suffix | `string` | | 后缀 |
@ -172,6 +227,7 @@ order: 32
| keyboard | `boolean` | | 键盘事件(方向上下) |
| big | `boolean` | | 是否使用大数 |
| displayMode | `string` | | 样式类型 |
| resetValue | `any` | `""` | 清空输入内容时,组件值将设置为`resetValue` |
## 事件表

View File

@ -1,11 +1,15 @@
import React from 'react';
import isInteger from 'lodash/isInteger';
// @ts-ignore
import InputNumber from 'rc-input-number';
import getMiniDecimal, {
DecimalClass,
toFixed
} from 'rc-input-number/lib/utils/MiniDecimal';
import {getNumberPrecision} from 'rc-input-number/lib/utils/numberUtil';
import {
getNumberPrecision,
num2str
} from 'rc-input-number/lib/utils/numberUtil';
import {Icon} from './icons';
import {ThemeProps, themeable} from 'amis-core';
@ -56,13 +60,114 @@ export interface NumberProps extends ThemeProps {
*
*/
big?: boolean;
/**
*
*/
resetValue?: any;
}
export class NumberInput extends React.Component<NumberProps, any> {
static defaultProps: Pick<NumberProps, 'step' | 'readOnly' | 'borderMode'> = {
static defaultProps: Pick<
NumberProps,
'step' | 'readOnly' | 'borderMode' | 'resetValue'
> = {
step: 1,
readOnly: false,
borderMode: 'full'
borderMode: 'full',
resetValue: ''
};
/**
* value值
*
* @param value value
* @param min
* @param max
* @param precision
* @param resetValue
* @param isBig
*/
static normalizeValue = (
value: any,
min: number | undefined,
max: number | undefined,
precision: number,
resetValue: any,
isBig: boolean | undefined
) => {
/**
* resetValue
* resetValue为非数字
* resetValue为数字maxminprecision
*/
if (value == null) {
if (typeof resetValue !== 'number') {
return resetValue ?? '';
}
value = resetValue;
}
// 处理max & min
if (typeof value === 'number') {
if (typeof min === 'number') {
value = Math.max(value, min);
}
if (typeof max === 'number') {
value = Math.min(value, max);
}
}
// 处理string类型输入
if (typeof value === 'string') {
let val = getMiniDecimal(value);
if (typeof min !== 'undefined') {
let minValue = getMiniDecimal(min);
if (val.lessEquals(minValue)) {
value = min;
}
}
if (typeof max !== 'undefined') {
let maxValue = getMiniDecimal(max);
if (maxValue.lessEquals(val)) {
value = max;
}
}
}
/**
* value值
*/
if (!isBig && getNumberPrecision(value) !== precision) {
value = getMiniDecimal(
toFixed(num2str(value), '.', precision)
).toNumber();
}
return value;
};
/**
* 00
* stepstep的精度生成使
*
* @param precision
* @param step
*/
static normalizePrecision = (precision: any, step?: number): number => {
if (
typeof precision === 'number' &&
isInteger(precision) &&
precision >= 0
) {
return Math.max(precision, getNumberPrecision(step ?? 1));
}
// 如果设置了step就基于step和precision选取更高精度
if (step != null) {
return Math.max(0, getNumberPrecision(step));
}
return 0;
};
/**
@ -80,36 +185,20 @@ export class NumberInput extends React.Component<NumberProps, any> {
@autobind
handleChange(value: any) {
const {min, max, onChange} = this.props;
const {min, max, step, precision, resetValue, onChange} = this.props;
const finalPrecision = NumberInput.normalizePrecision(precision, step);
const result = NumberInput.normalizeValue(
value,
min,
max,
finalPrecision,
resetValue,
this.isBig
);
if (typeof value === 'number') {
if (typeof min === 'number') {
value = Math.max(value, min);
onChange?.(result);
}
if (typeof max === 'number') {
value = Math.min(value, max);
}
}
if (typeof value === 'string') {
let val = getMiniDecimal(value);
if (typeof min !== 'undefined') {
let minValue = getMiniDecimal(min);
if (val.lessEquals(minValue)) {
value = min;
}
}
if (typeof max !== 'undefined') {
let maxValue = getMiniDecimal(max);
if (maxValue.lessEquals(val)) {
value = max;
}
}
}
onChange?.(value);
}
@autobind
handleFocus(e: React.SyntheticEvent<HTMLElement>) {
const {onFocus} = this.props;
@ -154,7 +243,7 @@ export class NumberInput extends React.Component<NumberProps, any> {
let updateValue = newValue;
const numStr = updateValue.toString();
const mergedPrecision = getPrecision(numStr);
if (mergedPrecision! >= 0) {
if (mergedPrecision >= 0) {
updateValue = getMiniDecimal(toFixed(numStr, '.', mergedPrecision));
}
@ -182,7 +271,6 @@ export class NumberInput extends React.Component<NumberProps, any> {
min,
disabled,
placeholder,
onChange,
showSteps,
formatter,
parser,
@ -192,12 +280,10 @@ export class NumberInput extends React.Component<NumberProps, any> {
inputRef,
keyboard
} = this.props;
const precisionProps: any = {
precision: NumberInput.normalizePrecision(precision, step)
};
let precisionProps: any = {};
if (typeof precision === 'number') {
precisionProps.precision = precision;
}
return (
<InputNumber
className={cx(
@ -230,10 +316,8 @@ export class NumberInput extends React.Component<NumberProps, any> {
}
render(): JSX.Element {
const {
classPrefix: ns,
classnames: cx,
value,
precision,
max,
min,
disabled,
@ -243,11 +327,6 @@ export class NumberInput extends React.Component<NumberProps, any> {
displayMode
} = this.props;
let precisionProps: any = {};
if (typeof precision === 'number') {
precisionProps.precision = precision;
}
return (
<>
{displayMode === 'enhance' ? (

View File

@ -100,6 +100,9 @@ export interface NumberProps extends FormControlProps {
max?: number | string;
min?: number | string;
step?: number;
/**
*
*/
precision?: number;
/**
*
@ -161,8 +164,11 @@ export default class NumberControl extends React.Component<
this.handleChangeUnit = this.handleChangeUnit.bind(this);
const unit = this.getUnit();
const unitOptions = normalizeOptions(props.unitOptions);
const {formItem, setPrinstineValue, precision, value} = props;
const normalizedPrecision = this.filterNum(precision);
const {formItem, setPrinstineValue, precision, step, value} = props;
const normalizedPrecision = NumberInput.normalizePrecision(
this.filterNum(precision),
this.filterNum(step)
);
/**
* precision需要处理入参value的精度
@ -191,12 +197,24 @@ export default class NumberControl extends React.Component<
*/
doAction(action: ActionObject, args: any) {
const actionType = action?.actionType as string;
const {resetValue, onChange} = this.props;
const {min, max, precision, step, resetValue, big, onChange} = this.props;
if (actionType === 'clear') {
onChange?.('');
} else if (actionType === 'reset') {
const value = this.getValue(resetValue ?? '');
const finalPrecision = NumberInput.normalizePrecision(
this.filterNum(precision),
this.filterNum(step)
);
const value = NumberInput.normalizeValue(
resetValue ?? '',
this.filterNum(min),
this.filterNum(max),
finalPrecision,
resetValue ?? '',
big
);
onChange?.(value);
}
}
@ -256,7 +274,6 @@ export default class NumberControl extends React.Component<
async handleChange(inputValue: any) {
const {onChange, dispatchEvent} = this.props;
const value = this.getValue(inputValue);
const rendererEvent = await dispatchEvent(
'change',
resolveEventData(this.props, {value}, 'value')
@ -264,9 +281,11 @@ export default class NumberControl extends React.Component<
if (rendererEvent?.prevented) {
return;
}
onChange(value);
}
/** 处理数字类的props支持从数据域获取变量值 */
filterNum(value: number | string | undefined) {
if (typeof value === 'undefined') {
return undefined;
@ -343,14 +362,10 @@ export default class NumberControl extends React.Component<
readOnly,
keyboard,
displayMode,
big
big,
resetValue
} = this.props;
let precisionProps: any = {};
const finalPrecision = this.filterNum(precision);
if (typeof finalPrecision === 'number') {
precisionProps.precision = finalPrecision;
}
const unit = this.state?.unit;
// 数据格式化
const formatter = (value: string | number) => {
@ -388,6 +403,7 @@ export default class NumberControl extends React.Component<
<NumberInput
inputRef={this.inputRef}
value={finalValue}
resetValue={resetValue}
step={step}
max={this.filterNum(max)}
min={this.filterNum(min)}

View File

@ -297,11 +297,11 @@ export class Input extends React.Component<RangeItemProps, any> {
* @param e React.ChangeEvent
*/
@autobind
onChange(value: number) {
const {multiple, value: originValue, type, min} = this.props;
handleInputNumberChange(value: number) {
const {multiple, value: originValue, type, min, onChange} = this.props;
const _value = this.getValue(value, type);
this.props.onChange(
onChange?.(
multiple
? {...(originValue as MultipleValue), [type]: _value}
: value ?? min
@ -457,7 +457,7 @@ export class Input extends React.Component<RangeItemProps, any> {
step={step}
max={this.checkNum(max)}
min={this.checkNum(min)}
onChange={this.onChange}
onChange={this.handleInputNumberChange}
disabled={disabled}
onBlur={this.onBlur}
onFocus={this.onFocus}
@ -574,7 +574,7 @@ export default class RangeControl extends React.PureComponent<
* @param value
*/
@autobind
async onChange(value: FormatValue) {
async handleChange(value: FormatValue) {
this.setState({value: this.getValue(value)});
const {onChange, dispatchEvent} = this.props;
const result = this.getFormatValue(value);
@ -632,7 +632,7 @@ export default class RangeControl extends React.PureComponent<
const props: RangeItemProps = {
...this.props,
value,
onChange: this.onChange,
onChange: this.handleChange,
onAfterChange: this.onAfterChange
};