开发预览 更新于 2026-05-10

NumberInput 数字输入

数字输入框,带 +/− 步进按钮、min/max/step 约束、键盘上下步进、自动 clamp。

基础用法

绑定 number | null —— null 代表空值,便于跟「未填写」区分开。min / max 约束,离焦时自动 clamp。

背景
<script setup lang="ts">
import { ref } from 'vue';
import { CfNumberInput } from '@chufix-design/vue';

const value = ref<number | null>(10);
</script>

<template>
  <div style="max-width: 16rem;">
    <CfNumberInput v-model="value" :min="0" :max="100" />
  </div>
</template>
<script setup>
import { ref } from 'vue';
import { CfNumberInput } from '@chufix-design/vue';

const value = ref<number | null>(10);
</script>

<template>
  <div style="max-width: 16rem;">
    <CfNumberInput v-model="value" :min="0" :max="100" />
  </div>
</template>
<CfNumberInput value={value} onChange={setValue} min={0} max={100} />
<CfNumberInput value={value} onChange={setValue} min={0} max={100} />

步进与精度

step 是按钮 / 键盘每次的步进单位;小数 step 会自动推断 precisionstep=0.05 → 显示 2 位小数)。也可以显式传 precision 强制位数。空值 null 时占位文本生效。

背景
<script setup lang="ts">
import { ref } from 'vue';
import { CfNumberInput } from '@chufix-design/vue';

const ratio = ref<number | null>(0.25);
const price = ref<number | null>(99.9);
const optional = ref<number | null>(null);
</script>

<template>
  <div class="demo-stack">
    <div class="demo-row" style="gap: 1rem;">
      <CfNumberInput v-model="ratio" :step="0.05" :min="0" :max="1" />
      <CfNumberInput v-model="price" :step="0.1" :precision="2" />
      <CfNumberInput v-model="optional" placeholder="留空表示不限" />
    </div>
  </div>
</template>
<script setup>
import { ref } from 'vue';
import { CfNumberInput } from '@chufix-design/vue';

const ratio = ref<number | null>(0.25);
const price = ref<number | null>(99.9);
const optional = ref<number | null>(null);
</script>

<template>
  <div class="demo-stack">
    <div class="demo-row" style="gap: 1rem;">
      <CfNumberInput v-model="ratio" :step="0.05" :min="0" :max="1" />
      <CfNumberInput v-model="price" :step="0.1" :precision="2" />
      <CfNumberInput v-model="optional" placeholder="留空表示不限" />
    </div>
  </div>
</template>
<CfNumberInput value={ratio} onChange={setRatio} step={0.05} min={0} max={1} />
<CfNumberInput value={price} onChange={setPrice} step={0.1} precision={2} />
<CfNumberInput value={optional} onChange={setOptional} placeholder="留空表示不限" />
<CfNumberInput value={ratio} onChange={setRatio} step={0.05} min={0} max={1} />
<CfNumberInput value={price} onChange={setPrice} step={0.1} precision={2} />
<CfNumberInput value={optional} onChange={setOptional} placeholder="留空表示不限" />

三档尺寸

size 控制控件高度,与 Input / Select 保持一致 — sm 紧凑型 / md 默认 / lg 大号触摸友好。

背景
<script setup lang="ts">
import { ref } from 'vue';
import { CfNumberInput } from '@chufix-design/vue';

const a = ref<number | null>(50);
const b = ref<number | null>(50);
const c = ref<number | null>(50);
</script>

<template>
  <div class="demo-row" style="gap: 1rem;">
    <CfNumberInput v-model="a" size="sm" />
    <CfNumberInput v-model="b" size="md" />
    <CfNumberInput v-model="c" size="lg" />
  </div>
</template>
<script setup>
import { ref } from 'vue';
import { CfNumberInput } from '@chufix-design/vue';

const a = ref<number | null>(50);
const b = ref<number | null>(50);
const c = ref<number | null>(50);
</script>

<template>
  <div class="demo-row" style="gap: 1rem;">
    <CfNumberInput v-model="a" size="sm" />
    <CfNumberInput v-model="b" size="md" />
    <CfNumberInput v-model="c" size="lg" />
  </div>
</template>
<CfNumberInput value={a} onChange={setA} size="sm" />
<CfNumberInput value={b} onChange={setB} size="md" />
<CfNumberInput value={c} onChange={setC} size="lg" />
<CfNumberInput value={a} onChange={setA} size="sm" />
<CfNumberInput value={b} onChange={setB} size="md" />
<CfNumberInput value={c} onChange={setC} size="lg" />

隐藏步进按钮 / 禁用

hideSteppers 隐藏右侧 +/− 按钮但保留键盘步进 —— 在表格内或紧凑表单里更清爽。disabled 完全禁用交互。

背景
默认(带 +/− 按钮)
hideSteppers
disabled
<script setup lang="ts">
import { ref } from 'vue';
import { CfNumberInput } from '@chufix-design/vue';

const a = ref<number | null>(42);
const b = ref<number | null>(42);
</script>

<template>
  <div class="demo-stack">
    <div>
      <div style="font-size: 12px; color: var(--fg-3); margin-bottom: 4px;">默认(带 +/− 按钮)</div>
      <CfNumberInput v-model="a" />
    </div>
    <div>
      <div style="font-size: 12px; color: var(--fg-3); margin-bottom: 4px;">hideSteppers</div>
      <CfNumberInput v-model="b" hide-steppers />
    </div>
    <div>
      <div style="font-size: 12px; color: var(--fg-3); margin-bottom: 4px;">disabled</div>
      <CfNumberInput :model-value="42" disabled />
    </div>
  </div>
</template>
<script setup>
import { ref } from 'vue';
import { CfNumberInput } from '@chufix-design/vue';

const a = ref<number | null>(42);
const b = ref<number | null>(42);
</script>

<template>
  <div class="demo-stack">
    <div>
      <div style="font-size: 12px; color: var(--fg-3); margin-bottom: 4px;">默认(带 +/− 按钮)</div>
      <CfNumberInput v-model="a" />
    </div>
    <div>
      <div style="font-size: 12px; color: var(--fg-3); margin-bottom: 4px;">hideSteppers</div>
      <CfNumberInput v-model="b" hide-steppers />
    </div>
    <div>
      <div style="font-size: 12px; color: var(--fg-3); margin-bottom: 4px;">disabled</div>
      <CfNumberInput :model-value="42" disabled />
    </div>
  </div>
</template>
<CfNumberInput value={a} onChange={setA} />
<CfNumberInput value={b} onChange={setB} hideSteppers />
<CfNumberInput value={42} disabled />
<CfNumberInput value={a} onChange={setA} />
<CfNumberInput value={b} onChange={setB} hideSteppers />
<CfNumberInput value={42} disabled />

事件与表单

NumberInput 会区分输入中的原始文本、提交后的数字值、步进来源和非法输入。传入 name / id 后会直接落到原生 input,适合放进表单或自动化测试。

背景
ready
输入、步进或提交后会显示事件流。
<script setup lang="ts">
import { ref } from 'vue';
import { CfBadge, CfNumberInput, type NumberInputChangeReason } from '@chufix-design/vue';

const value = ref<number | null>(25);
const phase = ref('ready');
const logs = ref(['输入、步进或提交后会显示事件流。']);

function record(name: string, detail: string) {
  logs.value = [`${name}: ${detail}`, ...logs.value].slice(0, 5);
}

function onChange(next: number | null, meta: { raw: string; reason: NumberInputChangeReason }) {
  value.value = next;
  phase.value = meta.reason;
  record('change', `${String(next ?? 'null')} / raw=${meta.raw || 'empty'} / ${meta.reason}`);
}
</script>

<template>
  <div class="number-events">
    <CfNumberInput
      :model-value="value"
      name="retry-budget"
      :min="0"
      :max="100"
      :step="5"
      placeholder="0 - 100"
      @input="(raw) => record('input', raw || 'empty')"
      @change="onChange"
      @step="(next, meta) => record('step', `${next} / direction=${meta.direction}`)"
      @invalid="(meta) => {
        phase = 'invalid';
        record('invalid', `${meta.raw} 不是有效数字`);
      }"
      @focus="record('focus', 'focused')"
      @blur="record('blur', 'committed')"
    />
    <div class="number-events__status">
      <CfBadge tone="info" :content="phase" />
      <div class="number-events__log" aria-live="polite">
        <code v-for="entry in logs" :key="entry">{{ entry }}</code>
      </div>
    </div>
  </div>
</template>

<style scoped>
.number-events {
  display: grid;
  gap: 12px;
  width: min(100%, 420px);
}

.number-events__status {
  display: flex;
  align-items: flex-start;
  gap: 10px;
}

.number-events__log {
  display: grid;
  gap: 4px;
  min-width: 0;
}

.number-events__log code {
  white-space: normal;
}
</style>
<script setup>
import { ref } from 'vue';
import { CfBadge, CfNumberInput } from '@chufix-design/vue';

const value = ref<number | null>(25);
const phase = ref('ready');
const logs = ref(['输入、步进或提交后会显示事件流。']);

function record(name, detail) {
  logs.value = [`${name}: ${detail}`, ...logs.value].slice(0, 5);
}

function onChange(next, meta: { raw: string; reason: NumberInputChangeReason }) {
  value.value = next;
  phase.value = meta.reason;
  record('change', `${String(next ?? 'null')} / raw=${meta.raw || 'empty'} / ${meta.reason}`);
}
</script>

<template>
  <div class="number-events">
    <CfNumberInput
      :model-value="value"
      name="retry-budget"
      :min="0"
      :max="100"
      :step="5"
      placeholder="0 - 100"
      @input="(raw) => record('input', raw || 'empty')"
      @change="onChange"
      @step="(next, meta) => record('step', `${next} / direction=${meta.direction}`)"
      @invalid="(meta) => {
        phase = 'invalid';
        record('invalid', `${meta.raw} 不是有效数字`);
      }"
      @focus="record('focus', 'focused')"
      @blur="record('blur', 'committed')"
    />
    <div class="number-events__status">
      <CfBadge tone="info" :content="phase" />
      <div class="number-events__log" aria-live="polite">
        <code v-for="entry in logs" :key="entry">{{ entry }}</code>
      </div>
    </div>
  </div>
</template>

<style scoped>
.number-events {
  display: grid;
  gap: 12px;
  width: min(100%, 420px);
}

.number-events__status {
  display: flex;
  align-items: flex-start;
  gap: 10px;
}

.number-events__log {
  display: grid;
  gap: 4px;
  min-width: 0;
}

.number-events__log code {
  white-space: normal;
}
</style>
<CfNumberInput
value={value}
name="retry-budget"
min={0}
max={100}
step={5}
onInput={(raw) => console.log('input', raw)}
onChange={(value, meta) => console.log('change', value, meta.reason)}
onStep={(value, meta) => console.log('step', value, meta.direction)}
onInvalid={(meta) => console.log('invalid', meta.raw)}
/>
<CfNumberInput
value={value}
name="retry-budget"
min={0}
max={100}
step={5}
onInput={(raw) => console.log('input', raw)}
onChange={(value, meta) => console.log('change', value, meta.reason)}
onStep={(value, meta) => console.log('step', value, meta.direction)}
onInvalid={(meta) => console.log('invalid', meta.raw)}
/>

键盘交互

  • / —— 按 step 步进
  • PageUp / PageDown —— 按 10 倍 step 步进
  • Home / End —— 跳到 min / max(存在边界时)
  • Enter —— 提交并 clamp
  • 离焦 —— 自动 clamp 到 [min, max],不在范围内的输入会被纠正
  • 非数字输入会触发 invalid 并回滚到上一个有效值

API

Prop类型默认值说明
modelValue (Vue) / value (React)number | nullnull当前值;null 表示空
minnumber最小值
maxnumber最大值
stepnumber1步进
precisionnumber推断小数位数;不传则从 step 推断
size'sm' | 'md' | 'lg''md'高度
hideSteppersbooleanfalse隐藏 +/− 按钮(仍可键盘步进)
placeholderstring占位文本(值为 null 时显示)
disabledbooleanfalse禁用
namestring原生 input name,参与表单提交
idstring原生 input id

Events

Vue 事件React 回调payload说明
inputonInputraw输入中的原始字符串
changeonChange(value, { raw, reason })提交后的数字值,reasonblur / enter / step / home / end
steponStep(value, { direction })点击按钮或方向键步进
invalidonInvalid{ raw, reason }提交非法数字时触发
focus / bluronFocus / onBlurFocusEvent原生焦点事件

反馈与讨论

NumberInput 数字输入 的讨论

0
0 / 600
一键发送
正在加载评论...