mirror of
https://gitee.com/baidu/amis.git
synced 2024-12-02 03:58:07 +08:00
fix: InputNumber组件设置精度为0时仍然可以输入小数问题 (#5599)
This commit is contained in:
parent
977c37ba55
commit
92069f588a
@ -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不是一个合法的数字"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -159,19 +214,20 @@ order: 32
|
||||
|
||||
当做选择器表单项使用时,除了支持 [普通表单项属性表](./formitem#%E5%B1%9E%E6%80%A7%E8%A1%A8) 中的配置以外,还支持下面一些配置
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ---------------- | --------------------------------------- | ------ | -------------------- |
|
||||
| min | [模板](../../../docs/concepts/template) | | 最小值 |
|
||||
| max | [模板](../../../docs/concepts/template) | | 最大值 |
|
||||
| step | `number` | | 步长 |
|
||||
| precision | `number` | | 精度,即小数点后几位 |
|
||||
| showSteps | `boolean` | | 是否显示上下点击按钮 |
|
||||
| prefix | `string` | | 前缀 |
|
||||
| suffix | `string` | | 后缀 |
|
||||
| kilobitSeparator | `boolean` | | 千分分隔 |
|
||||
| keyboard | `boolean` | | 键盘事件(方向上下) |
|
||||
| big | `boolean` | | 是否使用大数 |
|
||||
| displayMode | `string` | | 样式类型 |
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ---------------- | --------------------------------------- | ------ | ------------------------------------------ |
|
||||
| min | [模板](../../../docs/concepts/template) | | 最小值 |
|
||||
| max | [模板](../../../docs/concepts/template) | | 最大值 |
|
||||
| step | `number` | | 步长 |
|
||||
| precision | `number` | | 精度,即小数点后几位,支持 0 和正整数 |
|
||||
| showSteps | `boolean` | | 是否显示上下点击按钮 |
|
||||
| prefix | `string` | | 前缀 |
|
||||
| suffix | `string` | | 后缀 |
|
||||
| kilobitSeparator | `boolean` | | 千分分隔 |
|
||||
| keyboard | `boolean` | | 键盘事件(方向上下) |
|
||||
| big | `boolean` | | 是否使用大数 |
|
||||
| displayMode | `string` | | 样式类型 |
|
||||
| resetValue | `any` | `""` | 清空输入内容时,组件值将设置为`resetValue` |
|
||||
|
||||
## 事件表
|
||||
|
||||
|
@ -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为数字,则需要处理max,min,precision,保证抛出的值满足条件
|
||||
*/
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取精度,合法的精度为0和正整数,不合法的精度统一转化为0
|
||||
* 若设置了step,则会基于step的精度生成,最终使用更高的精度
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
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);
|
||||
onChange?.(result);
|
||||
}
|
||||
|
||||
@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' ? (
|
||||
|
@ -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)}
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user