amis 低代码框架 input-number 数字输入框:精度、单位、大数与清空策略全解析 amis 低代码框架 input-number 数字输入框精度、单位、大数与清空策略全解析【免费下载链接】amis前端低代码框架通过 JSON 配置就能生成各种页面。项目地址: https://gitcode.com/GitHub_Trending/am/amis在 amis 低代码框架中input-number是表单体系里处理数值录入的核心表单项它通过 JSON Schema 即可配置出带步进按钮、精度控制、前后缀、千分分隔、单位选择和移动端原生实现的数字输入框。本文基于仓库内组件文档与源码实现完整覆盖precision、resetValue、unitOptions、big、clearValueOnEmpty等关键配置的行为规则并给出可复制的 Schema 示例与底层归一化逻辑取值裁剪、四舍五入、单位解析的源码级解读读完后可直接在 amis 表单中落地数值录入、数值展示与跨组件数值联动场景。基本用法一个 form input-number 的最小 Schemainput-number作为表单项使用最基本的配置只需要type、name、label提交时走表单的api{ type: form, api: /api/mock2/form/saveForm, body: [ { type: input-number, name: number, label: 数字 } ] }从源码结构看该组件由 NumberControl 实现并通过FormItem({type: input-number, detectProps: [unitOptions, precision, suffix]})注册为表单项。值得注意的是NumberControlRenderer的默认属性中内置了校验规则static defaultProps: PartialFormControlProps { validations: isNumeric, ...NumberControl.defaultProps };也就是说input-number默认带isNumeric校验非数字内容在表单提交时会被拦截这是它与普通input-text的默认行为差异之一见 packages/amis/src/renderers/Form/InputNumber.tsx。设置精度precision 与 step 的精度合并规则precision设置数字的显示精度一般需要配合step属性使用以实现细粒度调整。注意带有单位的输入不支持配置精度属性。若设置了step值则会基于step和precision的值选择更高的精度。若输入的内容不满足精度要求组件会按照精度自动处理遵循四舍五入规则。{ type: form, debug: true, api: /api/mock2/form/saveForm, data: { number2: 3.1234 }, body: [ { type: input-number, name: number1, label: 数字, precision: 2, step: 0.01, value: 2.98786 }, { type: input-number, name: number2, label: 数字2, precision: 3, step: 0.001 }, { type: input-number, name: number3, label: 数字3, step: 0.001, description: 不设置precision仅设置step, 实际精度为3 } ] }这个“取更高精度”的规则在源码中有明确实现。底层 UI 组件 NumberInput.normalizePrecision 的逻辑是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; };即precision合法0 和正整数时取max(precision, step 的小数位数)precision缺省时完全由step的小数位数决定两者都没设则为 0。这就是示例中step: 0.001单独生效、实际精度为 3 的原因。四舍五入本身发生在 NumberControl.formatNumber// 精度处理遵循四舍五入的处理规则 const normalizedValue parseFloat( toFixed(value.toString(), ., normalizedPrecision) );其中toFixed来自rc-component/mini-decimal用于避免原生Number.toFixed的浮点误差。同时源码中有两处与文档说明对应的边界条件配置了unitOptions带单位时跳过精度处理(!unit || unitOptions.length 0)即“带单位的输入不支持配置精度”big大数模式下也跳过精度处理因为大数模式下输入输出都是字符串。另外源码还暴露了一个文档未展开的属性showAsPercent当suffix %且开启showAsPercent时展示值会乘以 100而内部存储值保持原样精度在计算时额外加 2见 NumberInput.handleChange适合百分比展示场景。重置值 resetValue清空与重置的取值链清空/重置组件输入后组件绑定的值将被设置为resetValue默认为。若resetValue为合法数字时会根据min、max和precision属性将组件值设置为满足条件的值。若resetValue为非数字则组件清空/重置后设置为该值。{ 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.5, 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不是一个合法的数字 } ] }这套行为由 NumberInput.normalizeValue 统一完成处理顺序是非法输入输入值不是数字时若resetValue也不是数字返回开启clearValueOnEmpty时返回undefined否则回退为resetValuemin/max 裁剪对 number 类型直接Math.max(value, min)/Math.min(value, max)对 string 类型大数场景用getMiniDecimal比较后钳制精度处理非大数模式下若当前精度与目标精度不一致按“只截断、不四舍五入”的方式对齐精度。这里有一个容易踩的细节示例 2 中resetValue: 0且min: 100最终值是100而不是0——因为 min/max 裁剪优先级高于resetValue原值。而reset动作本身还有一个额外来源从源码 NumberControl.doAction 可以看到重置时优先取formStore.pristine表单初始值中对应name的值pristine中不存在时才回落到resetValue配置} else if (actionType reset) { const pristineVal getVariable(formStore?.pristine ?? store?.pristine, name) ?? resetValue; const value NumberInput.normalizeValue( pristineVal ?? , this.filterNum(min, big), this.filterNum(max, big), finalPrecision, pristineVal ?? , clearValueOnEmpty, big ); onChange?.(clearValueOnEmpty value ? undefined : value); }前后缀与千分分隔显示格式化而不改变数据值prefix、suffix、kilobitSeparator三者只做展示层格式化数据域中的值始终是纯数字{ type: form, api: /api/mock2/form/saveForm, body: [ { type: input-number, name: number, label: 数字, value: 111111, prefix: $, suffix: %, kilobitSeparator: true } ] }源码中这对formatter/parser是动态构造的见 NumberControl.render只有配置了kilobitSeparator、prefix、suffix任意一项时才会启用formatter。格式化逻辑区分两种状态用户正在输入且输入框内容与格式化值一致时只做千分位插入parts[0].replace(/\B(?(\d{3})(?!\d))/g, ,)避免光标跳动非用户输入状态如 blur走numberFormatter(value, finalPrecision)同时完成千分位 精度处理。parser则负责反向还原依次去掉prefix、suffix、千分位逗号保证onChange抛出的仍是可解析的数字。此外还有一个隐藏的光标位置计算函数changeCursorPos会补偿 prefix 长度与千分位逗号数量防止输入时光标乱跳。带单位数字 unitOptions值是字符串输出带单位1.4.0 及以上版本可以通过unitOptions设置数字的单位选项和前面的前后缀不同它的输出结果也将会是字符串包含单位默认取选项的第一个。{ type: form, api: /api/mock2/form/saveForm, debug: true, body: [ { type: input-number, name: number, label: 数字, unitOptions: [px, %, em] } ] }单位机制在源码中分为“解析”和“输出”两条链路packages/amis/src/renderers/Form/InputNumber.tsx单位解析getUnitL300-L324当已有值且是字符串时从字符串尾部匹配单位。匹配前会先按长度倒序排序选项——注释写明“先找长的字符这样如果有 ab 和 b 两种后缀相同的也能识别”即长单位优先防止12ab被误解析为12ab。无值时默认取第一个单位。单位输出getValueL326-L341getValue(inputValue: any) { const {resetValue, unitOptions} this.props; if (inputValue typeof inputValue ! number typeof inputValue ! string) { return; } if (inputValue ! null unitOptions this.state.unit) { inputValue inputValue String(this.state.unit); } return inputValue null ? resetValue ?? null : inputValue; }即每次变更时把当前单位拼接到数字后面数据域中的值形如12px所以文档特别强调“带单位的输入不支持配置精度属性”。单位切换handleChangeUnitL418-L427当unitOptions有多个选项时渲染一个Select切换单位会把旧单位从值里替换掉再拼上新单位只有一个选项时则渲染只读文本。相关测试用例见 packages/amis/tests/renderers/Form/number.test.tsx覆盖unitOptions、带默认值、initApi初始值等场景。加强版输入框 displayModedisplayMode取值base | enhance默认base{ type: form, api: /api/mock2/form/saveForm, body: [ { type: input-number, name: number, label: 数字, displayMode: enhance } ] }enhance模式下步进区域的交互由 NumberInput.handleEnhanceModeChange 驱动点击加/减按钮按step增量变化而非普通模式下依赖输入框上下小箭头。从源码结构看displayMode还会体现在 CSS 类名上如focused、disabled状态类名会带上Number-displayMode-前缀便于两种样式分别定制主题。大数支持 big突破 JavaScript 数字范围2.3.0 及以上版本默认情况下使用 JavaScript 原生数字类型但如果要支持输入超过 JavaScript 支持范围的整数或浮点数可以通过big: true开启大数支持开启之后输入输出都将是字符串。{ type: form, debug: true, api: /api/mock2/form/saveForm, body: [ { type: input-number, name: number, label: 数字, big: true } ] }源码中多处针对大数做了分支精度处理被跳过formatNumber中big ! true才做四舍五入因为大数值是字符串不能直接参与浮点运算min/max的比较通过getMiniDecimal完成normalizeValue中对 string 类型的钳制分支保证超大整数的边界判断正确filterNum在大数模式下不会把字符串强转为 numbervalue /^[-]?\d/.test(value) ? (isbig ? value : value) : undefined避免超过Number.MAX_SAFE_INTEGER时精度丢失。相关测试见 number.test.tsx 中的Renderer:number with big value用例。内容清空时删除字段 clearValueOnEmpty2.8.0 及以上版本如果设置了clearValueOnEmpty: true当输入框的值清空时会从数据域中删除该表单项对应的值。比较常见的用法是在combo、input-array等组件中避免input-number清空后提交空字符串。{ type: form, debug: true, debugConfig: { levelExpand: 2 }, body: [ { type: group, body: [ { name: numberClear, type: input-number, label: 清空, value: 123, clearValueOnEmpty: true }, { name: numberNotClear, type: input-number, label: 不清空, value: 456 } ] }, { type: combo, name: user, label: 用户, items: [ { name: text, label: 名字, type: input-text }, { name: gender, label: 性别, type: select, options: [男, 女] }, { name: age, label: 年龄, type: input-number, clearValueOnEmpty: true } ] } ] }实现上clearValueOnEmpty的效果是把“空值”从字符串换成undefined表示“字段不存在”输入变更路径handleChange 中let resultValue clearValueOnEmpty value ? undefined : value;动作路径doAction的clear分支为onChange?.(clearValueOnEmpty ? undefined : )归一化路径normalizeValue/normalizeValue2对非法输入也返回clearValueOnEmpty ? undefined : 。对后端接口来说这两者的区别是“提交age: ”还是“提交时根本没有age字段”在combo、input-array这类嵌套对象结构中尤为有用。原生数字组件 native-number原生数字组件将直接使用浏览器的实现最终展现效果和浏览器有关并且只支持min、max和step这几个属性设置这个功能主要是给移动端浏览器使用的PC 下不建议使用。{ type: form, api: /api/mock2/form/saveForm, body: [ { type: native-number, name: number, label: 数字 } ] }从源码结构看native-number是input-text注册时声明的别名之一packages/amis/src/renderers/Form/InputText.tsx 中alias: [input-password, native-date, native-time, native-number]渲染时生成的是input typenumber因此天然支持移动端系统弹出的数字软键盘但代价是样式和步进交互完全由浏览器决定。属性表当做选择器表单项使用时除了支持 普通表单项属性表 中的配置以外还支持下面一些配置属性名类型默认值说明版本min模板最小值max模板最大值stepnumber步长precisionnumber精度即小数点后几位支持 0 和正整数showStepsbooleantrue是否显示上下点击按钮readOnlybooleanfalse只读prefixstring前缀suffixstring后缀unitOptionsstring[]单位选项1.4.0kilobitSeparatorbooleanfalse千分分隔keyboardbooleantrue键盘事件方向上下bigbooleanfalse是否使用大数2.3.0displayModebase \| enhancebase样式类型borderModefull \| half \| nonefull边框模式全边框还是半边框或者没边框resetValuenumber \| string清空输入内容时组件值将设置为resetValueclearValueOnEmptybooleanfalse内容为空时从数据域中删除该表单项对应的值2.8.0需要说明的一点min、max在文档中标注为模板类型源码里也印证了这一点——filterNum 会对这些数字类属性做filter(value, this.props.data)即支持写成${变量}从数据域中取值/** 处理数字类的props支持从数据域获取变量值 */ filterNum(value: number | string | undefined, isbig: boolean false) { if (typeof value undefined) { return undefined; } if (typeof value ! number) { value filter(value, this.props.data); // 大数模式不转数字 value /^[-]?\d/.test(value) ? (isbig ? value : value) : undefined; } return value; }这使得min/max可以根据上下文动态设置例如max: ${stock.maxValue}。事件表当前组件会对外派发以下事件可以通过onEvent来监听这些事件并通过actions来配置执行的动作在actions中可以通过${事件参数名}或${event.data.[事件参数名]}来获取事件产生的数据详细请查看事件动作。[name]表示当前组件绑定的名称即name属性如果没有配置name属性则通过value取值。事件名称事件参数说明change[name]: number组件的值输入值变化时触发blur[name]: number组件的值-focus[name]: number组件的值-源码中事件派发经过dispatchEvent事件参数由resolveEventData统一填充组件名、组件 id 等并且change事件支持prevented拦截若onEvent.change的 actions 执行结果标记了阻止onChange将不会真正提交新值见 NumberControl.handleChange。这意味着可以在change事件里通过 action 拦截非法的中间值。动作表clear / reset / setValue当前组件对外暴露以下特性动作其他组件可以通过指定actionType: 动作名称、componentId: 该组件id来触发这些动作动作配置可以通过args: {动作配置项名称: xxx}来配置具体的参数详细请查看事件动作。动作名称动作配置说明clear-清空reset-将值重置为resetValue若没有配置resetValue则清空setValuevalue: number更新的数值更新数据clear{ type: form, debug: true, body: [ { type: input-number, name: number, label: 数字, id: clear_text, value: 1 }, { type: button, label: 清空, onEvent: { click: { actions: [ { actionType: clear, componentId: clear_text } ] } } } ] }clear动作的源码入口是 doActionactionType clear时直接onChange(clearValueOnEmpty ? undefined : )——即它同样遵循clearValueOnEmpty语义两个配置叠加使用时清空即删字段。reset如果配置了resetValue则重置时使用resetValue的值否则使用初始值。{ type: form, debug: true, body: [ { type: input-number, name: number, label: 数字, id: reset_text, value: 1 }, { type: button, label: 重置, onEvent: { click: { actions: [ { actionType: reset, componentId: reset_text } ] } } } ] }如前文“重置值”一节所述reset实际优先取表单 pristine 初始值再回落到resetValue并经过min/max/precision归一化后写回数据域。setValue{ type: form, debug: true, body: [ { type: input-number, name: number, label: 数字, id: setvalue_text, value: 1 }, { type: button, label: 赋值, onEvent: { click: { actions: [ { actionType: setValue, componentId: setvalue_text, args: { value: 2 } } ] } } } ] }setValue是 amis 表单项的通用赋值动作配合${}模板可以把事件参数动态传入args实现联动赋值例如另一个下拉选择后自动把对应价格写入数字框。相关组件与延伸阅读表单基础概念属性表通用部分、数据域见 表单项文档 与 数据域概念只读展示数字如列表单元格应使用number展示组件其实现见 packages/amis/src/renderers/Number.tsx支持percent、unitOptions等展示能力与input-number的录入能力形成互补数值格式化、模板取值等底层工具函数集中在packages/amis-core/src/utils目录numberFormatter、numberReverter即来自其中组件行为回归测试覆盖精度、单位、大数、静态渲染等场景见 packages/amis/tests/renderers/Form/number.test.tsx。【免费下载链接】amis前端低代码框架通过 JSON 配置就能生成各种页面。项目地址: https://gitcode.com/GitHub_Trending/am/amis创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考