Skip to content

InputNumber 数字输入

带步进按钮的数字输入框,支持区间限制与精度控制。

基础用法

vue
<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)。

不允许为空:清空后回落到 min
vue
<SrInputNumber v-model="value" :min="1" :allow-empty="false" />

步长与精度

precision 不传时会step 推断——传 0.01 就该显示两位小数, 这是最符合直觉的默认,不必再写一遍 :precision="2"

vue
<SrInputNumber v-model="dec" :step="0.1" :min="0" :max="1" />
<SrInputNumber v-model="price" :step="0.01" :min="0" />

浮点误差已被处理

上面第一个组件,连点 10 次 +严格得到 1,而不是 0.9999999999999999

这不是碰巧。三个看起来都对的写法实测全错:

ts
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.0051.005 的进位方向相反)。

步进按钮布局

controls-position 支持右侧上下箭头(默认,与浏览器原生数字框的习惯一致) 与两侧圆形按钮。

vue
<SrInputNumber v-model="value" />
<SrInputNumber v-model="value" controls-position="sides" />
<SrInputNumber v-model="value" :controls="false" />

长按连续步进

按住按钮不放会连续步进:按下先立即走一步(保证单击的即时反馈), 420ms 后进入每 70ms 一步的连发,到头自动停下。

pointerdown 而不是 click 实现——后者只在松手时触发一次, 拿不到「按下」的时刻,长按便无从判断起点。

键盘操作

上下方向键步进,回车派发 enter 事件。可用 keyboard="false" 关闭。

尺寸

vue
<SrInputNumber size="sm" />
<SrInputNumber size="md" />
<SrInputNumber size="lg" />

为什么不用 type="number"

原生数字输入框看着合适,实则三处都不合用:

  1. 没法保留中间态。浏览器会把 -1. 判为无效值,读数拿到空串—— 用户敲减号的过程就丢了。
  2. 上下键行为由浏览器接管,且与「步进一次等于 step」的语义不一致。
  3. 各浏览器对内部子控件的样式控制不一致,做不了统一的玻璃质感。

本组件用 type="text"inputmode="decimal"——后者负责在移动端唤起数字键盘, 这才是真正需要的部分。

输入过程中不钳制区间

100 时中间会经过 1。若此刻按 min: 10 纠正,你永远输不进 100。 钳制留到失焦时做。

光标下的值会实时同步给 v-model,只是不做区间纠正—— 需要「只在最终确定后取值」时监听 change 而非 update:modelValue

Props

名称类型默认值说明
idstring唯一标识,同时用于关联 label
modelValuenumber | nullnull当前值
min / maxnumber区间限制
stepnumber1每次步进的数量
precisionnumber自动推断小数位数
size'sm' | 'md' | 'lg''md'尺寸
placeholderstring''占位文本
disabled / readonlybooleanfalse禁用 / 只读
controlsbooleantrue是否显示步进按钮
controlsPosition'right' | 'sides''right'步进按钮位置
keyboardbooleantrue是否可用上下键步进
invalidbooleanfalse是否处于错误态
allowEmptybooleantrue是否允许为空

事件与方法

事件参数说明
update:modelValuenumber | null值变化(输入过程中即触发,不钳制区间)
changenumber | null提交(失焦或步进后),已规范化
focus / blurFocusEvent获得 / 失去焦点
enterKeyboardEvent按下回车

通过 ref 可调用 focus() / blur()

工具函数

数值处理的工具函数已导出,可单独引用:

ts
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,读屏软件能正确播报不可用

自用组件库 · 源码分发 · 不发布 npm