InputNumber 数字输入
带步进按钮的数字输入框,支持区间限制与精度控制。
基础用法
<script setup>
import { ref } from 'vue'
const basic = ref(1)
const limited = ref(50)
</script>
<template>
<SrInputNumber v-model="basic" />
<SrInputNumber v-model="limited" :min="0" :max="100" placeholder="0 ~ 100" />
</template>值类型是 number | null
不是字符串。内部虽然维护着一份显示文本(要容纳 -、1. 这类还没输完的中间态), 但对外只发数字。
allowEmpty 为真(默认)时,清空输入会得到 null;设为 false 则回落到下界 (没有 min 就落到 0)。
<SrInputNumber v-model="value" :min="1" :allow-empty="false" />步长与精度
precision 不传时会从 step 推断——传 0.01 就该显示两位小数, 这是最符合直觉的默认,不必再写一遍 :precision="2"。
<SrInputNumber v-model="dec" :step="0.1" :min="0" :max="1" />
<SrInputNumber v-model="price" :step="0.01" :min="0" />浮点误差已被处理
上面第一个组件,连点 10 次 + 会严格得到 1,而不是 0.9999999999999999。
这不是碰巧。三个看起来都对的写法实测全错:
Math.round(1.005 * 100) / 100 // → 1 (期望 1.01)
Number((1.005).toFixed(2)) // → 1 (期望 1.01)
Number((2.675).toFixed(2)) // → 2.67 (期望 2.68)根因不在舍入方式,而在字面量本身:1.005 在 IEEE 754 下实际存的是 1.00499999999999989…,按真实值舍入确实不该进位——数学上没错, 但和敲下 1.005 时的预期不符,而且这种偏差会一路带进业务数据。
本组件的做法是先用 toPrecision(15) 抹掉存储噪声,再放大取整。 负数的舍入方向也做了对称处理(Math.round 对 .5 一律向正无穷, 直接用会导致 -1.005 与 1.005 的进位方向相反)。
步进按钮布局
controls-position 支持右侧上下箭头(默认,与浏览器原生数字框的习惯一致) 与两侧圆形按钮。
<SrInputNumber v-model="value" />
<SrInputNumber v-model="value" controls-position="sides" />
<SrInputNumber v-model="value" :controls="false" />长按连续步进
按住按钮不放会连续步进:按下先立即走一步(保证单击的即时反馈), 420ms 后进入每 70ms 一步的连发,到头自动停下。
用 pointerdown 而不是 click 实现——后者只在松手时触发一次, 拿不到「按下」的时刻,长按便无从判断起点。
键盘操作
上下方向键步进,回车派发 enter 事件。可用 keyboard="false" 关闭。
尺寸
<SrInputNumber size="sm" />
<SrInputNumber size="md" />
<SrInputNumber size="lg" />为什么不用 type="number"
原生数字输入框看着合适,实则三处都不合用:
- 没法保留中间态。浏览器会把
-、1.判为无效值,读数拿到空串—— 用户敲减号的过程就丢了。 - 上下键行为由浏览器接管,且与「步进一次等于
step」的语义不一致。 - 各浏览器对内部子控件的样式控制不一致,做不了统一的玻璃质感。
本组件用 type="text" 加 inputmode="decimal"——后者负责在移动端唤起数字键盘, 这才是真正需要的部分。
输入过程中不钳制区间
输 100 时中间会经过 1。若此刻按 min: 10 纠正,你永远输不进 100。 钳制留到失焦时做。
但光标下的值会实时同步给 v-model,只是不做区间纠正—— 需要「只在最终确定后取值」时监听 change 而非 update:modelValue。
Props
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
id | string | — | 唯一标识,同时用于关联 label |
modelValue | number | null | null | 当前值 |
min / max | number | — | 区间限制 |
step | number | 1 | 每次步进的数量 |
precision | number | 自动推断 | 小数位数 |
size | 'sm' | 'md' | 'lg' | 'md' | 尺寸 |
placeholder | string | '' | 占位文本 |
disabled / readonly | boolean | false | 禁用 / 只读 |
controls | boolean | true | 是否显示步进按钮 |
controlsPosition | 'right' | 'sides' | 'right' | 步进按钮位置 |
keyboard | boolean | true | 是否可用上下键步进 |
invalid | boolean | false | 是否处于错误态 |
allowEmpty | boolean | true | 是否允许为空 |
事件与方法
| 事件 | 参数 | 说明 |
|---|---|---|
update:modelValue | number | null | 值变化(输入过程中即触发,不钳制区间) |
change | number | null | 提交(失焦或步进后),已规范化 |
focus / blur | FocusEvent | 获得 / 失去焦点 |
enter | KeyboardEvent | 按下回车 |
通过 ref 可调用 focus() / blur()。
工具函数
数值处理的工具函数已导出,可单独引用:
import { roundTo, stepValue, getPrecision, parseNumber, clamp } from '@starriver/ui'
roundTo(1.005, 2) // 1.01 —— 已处理浮点存储噪声
stepValue(0.9, 0.1, 1, 1) // 1 —— 步进一次并钳制
getPrecision(1.005) // 3 —— 推断小数位数
parseNumber('1e5') // null —— 只接受完整十进制写法无障碍
- 焦点指示由容器的
:focus-within统一提供 - 步进按钮带
aria-label="增加"/"减少" - 达到区间边界时对应按钮进入
disabled,读屏软件能正确播报不可用