移动端导航数字键盘

数字键盘NumberKeyboard

为金额、验证码等纯数字输入提供自绘键盘(比系统键盘更可控、更安全)

数据录入 规格 17 · 数字键盘 NumberKeyboard 6 端实现 触摸优先

引入(H5 原生;其余 5 端见「快速开始」)
<!-- ① 令牌:PC 令牌 + 移动端 --kole-m-* 合成单文件,引一次 -->
<link rel="stylesheet" href="kole-ui/mobile/tokens.css">

<!-- ② 本组件样式(全量则用 kole-ui/mobile/components/index.css) -->
<link rel="stylesheet" href="kole-ui/mobile/components/mobile-numberkeyboard.css">

<!-- ③ 结构照抄下方任一演示块(类名与 6 端实现一致) -->

演示

每个演示都是真实渲染:预览帧加载 frameworks-mobile/NumberKeyboard.html?demo=<id>(只显示该演示块),代码是该演示块在演示页里的原文,可复制。全部演示同屏可看 演示页 ↗。

01 组件类型

金额键盘type=number

含小数点与删除键;按键只回传字符,写入哪个输入框由宿主决定。

查看代码(演示页原文 · 25 行)
frameworks-mobile/NumberKeyboard.html · number
<section class="demo-block" data-demo="number">
  <p class="demo-label">金额键盘(type=number:0-9 + 小数点 + 删除;点删除键打标记)</p>
  <div class="demo-frame" data-assert="keyboard-number">
    <div class="demo-frame__body">
      <input class="demo-field" type="text" inputmode="none" value="128.50" readonly aria-label="金额">
      <p style="margin: var(--kole-space-8) 0 0">键盘只 emit 按键事件,写入哪个输入框由宿主决定。</p>
    </div>
    <div class="kole-m-numberkeyboard is-open" id="kb-number" role="group" aria-label="金额键盘">
      <button class="kole-m-numberkeyboard__key" type="button">1</button>
      <button class="kole-m-numberkeyboard__key" type="button">2</button>
      <button class="kole-m-numberkeyboard__key" type="button">3</button>
      <button class="kole-m-numberkeyboard__key" type="button">4</button>
      <button class="kole-m-numberkeyboard__key" type="button">5</button>
      <button class="kole-m-numberkeyboard__key" type="button">6</button>
      <button class="kole-m-numberkeyboard__key" type="button">7</button>
      <button class="kole-m-numberkeyboard__key" type="button">8</button>
      <button class="kole-m-numberkeyboard__key" type="button">9</button>
      <button class="kole-m-numberkeyboard__key" type="button">.</button>
      <button class="kole-m-numberkeyboard__key" type="button">0</button>
      <button class="kole-m-numberkeyboard__key kole-m-numberkeyboard__key--delete" type="button"
              id="kb-delete" aria-label="删除"
              data-behavior="click-sets-attr:#kb-number|data-key|delete">⌫</button>
    </div>
  </div>
</section>
纯数字键盘type=digit

验证码场景:没有小数点键,最后两格是删除与收起。

查看代码(演示页原文 · 22 行)
frameworks-mobile/NumberKeyboard.html · digit
<section class="demo-block" data-demo="digit">
  <p class="demo-label">纯数字键盘(type=digit:验证码场景,没有小数点键)</p>
  <div class="demo-frame" data-assert="keyboard-digit">
    <div class="demo-frame__body">
      <input class="demo-field" type="text" inputmode="none" value="4 8 2" readonly aria-label="验证码">
    </div>
    <div class="kole-m-numberkeyboard is-open" role="group" aria-label="验证码键盘">
      <button class="kole-m-numberkeyboard__key" type="button">1</button>
      <button class="kole-m-numberkeyboard__key" type="button">2</button>
      <button class="kole-m-numberkeyboard__key" type="button">3</button>
      <button class="kole-m-numberkeyboard__key" type="button">4</button>
      <button class="kole-m-numberkeyboard__key" type="button">5</button>
      <button class="kole-m-numberkeyboard__key" type="button">6</button>
      <button class="kole-m-numberkeyboard__key" type="button">7</button>
      <button class="kole-m-numberkeyboard__key" type="button">8</button>
      <button class="kole-m-numberkeyboard__key" type="button">9</button>
      <button class="kole-m-numberkeyboard__key kole-m-numberkeyboard__key--delete" type="button" aria-label="删除">⌫</button>
      <button class="kole-m-numberkeyboard__key" type="button">0</button>
      <button class="kole-m-numberkeyboard__key" type="button" aria-label="收起键盘">收起</button>
    </div>
  </div>
</section>
带确认键showConfirm=true

确认键跨两列并用品牌色实底,与数字键在视觉上分层。

查看代码(演示页原文 · 19 行)
frameworks-mobile/NumberKeyboard.html · confirm
<section class="demo-block" data-demo="confirm">
  <p class="demo-label">带确认键(showConfirm=true:确认键跨两列、品牌色实底)</p>
  <div class="demo-frame" data-assert="keyboard-confirm">
    <div class="kole-m-numberkeyboard is-open" role="group" aria-label="带确认的键盘">
      <button class="kole-m-numberkeyboard__key" type="button">1</button>
      <button class="kole-m-numberkeyboard__key" type="button">2</button>
      <button class="kole-m-numberkeyboard__key" type="button">3</button>
      <button class="kole-m-numberkeyboard__key" type="button">4</button>
      <button class="kole-m-numberkeyboard__key" type="button">5</button>
      <button class="kole-m-numberkeyboard__key" type="button">6</button>
      <button class="kole-m-numberkeyboard__key" type="button">7</button>
      <button class="kole-m-numberkeyboard__key" type="button">8</button>
      <button class="kole-m-numberkeyboard__key" type="button">9</button>
      <button class="kole-m-numberkeyboard__key kole-m-numberkeyboard__key--delete" type="button" aria-label="删除">⌫</button>
      <button class="kole-m-numberkeyboard__key" type="button">0</button>
      <button class="kole-m-numberkeyboard__confirm" type="button">确认</button>
    </div>
  </div>
</section>

02 组件状态

确认键禁用状态 disabled

金额为 0 时确认键置灰不可点,避免无效提交。

查看代码(演示页原文 · 19 行)
frameworks-mobile/NumberKeyboard.html · disabled
<section class="demo-block" data-demo="disabled">
  <p class="demo-label">确认键禁用(disabled:金额为 0 时不可提交)</p>
  <div class="demo-frame" data-assert="keyboard-disabled">
    <div class="kole-m-numberkeyboard is-open" role="group" aria-label="金额键盘(不可提交)">
      <button class="kole-m-numberkeyboard__key" type="button">1</button>
      <button class="kole-m-numberkeyboard__key" type="button">2</button>
      <button class="kole-m-numberkeyboard__key" type="button">3</button>
      <button class="kole-m-numberkeyboard__key" type="button">4</button>
      <button class="kole-m-numberkeyboard__key" type="button">5</button>
      <button class="kole-m-numberkeyboard__key" type="button">6</button>
      <button class="kole-m-numberkeyboard__key" type="button">7</button>
      <button class="kole-m-numberkeyboard__key" type="button">8</button>
      <button class="kole-m-numberkeyboard__key" type="button">9</button>
      <button class="kole-m-numberkeyboard__key kole-m-numberkeyboard__key--delete" type="button" aria-label="删除">⌫</button>
      <button class="kole-m-numberkeyboard__key" type="button">0</button>
      <button class="kole-m-numberkeyboard__confirm" type="button" disabled aria-label="确认(金额为 0,不可提交)">确认</button>
    </div>
  </div>
</section>

API

props / events / slots 为 6 端实现的公共接口(说明文字取自规格对应小节)。字段名与各端源码逐名核对:node tools/verify-mobile-docs.mjs

Props

名称类型默认值说明必传
type'number' | 'digit''number'变体 type:number 含小数点键,digit 为纯 0-9(规格 §17.3)N
showDeletebooleantrue变体 showDelete:是否显示删除键(规格 §17.3)N
showConfirmbooleanfalse变体 showConfirm:是否显示确认键(跨两列、品牌色实底)(规格 §17.3)N
openbooleanfalse状态 open:键盘是否展开(规格 §17.4)N
confirmDisabledbooleanfalse状态 disabled:确认键是否不可用(规格 §17.4)N

「必传」按严格定义:实现里**没有默认值**时才为 Y(本门禁逐条核对 props 与各端源码的默认值,防止契约与实现脱节)。

事件

名称参数说明
inputkey: string按下按键时触发:数字键回传该字符,删除键回传 'delete',确认键回传 'confirm';键盘不持有输入值(规格 §17.5)

插槽

名称说明
default额外的自定义按键(追加在标准按键之后)

CSS 变量

组件级变量(在组件样式表里定义)。业务侧可在自己的作用域内覆盖,不必改组件源码。

名称默认值说明
--kole-m-numberkeyboard-gap1px组件内部默认值,可在业务侧覆盖

何时使用

  • 为金额、验证码等纯数字输入提供自绘键盘(比系统键盘更可控、更安全)
  • 按键热区不小于 44×44,网格等分排列
  • 按下用 :active,不做悬停态
  • 键盘本身不持有输入值 —— 只 emit 按键事件,由宿主决定写入哪个输入框
  • 容器 role="group" + aria-label
  • 每个按键为原生 button,文字即按键名(读屏读「1」「删除」)
  • 删除键用 aria-label="删除"(不读成符号)

交互与触控

  • 按键热区不小于 44×44,网格等分排列
  • 按下用 :active,不做悬停态
  • 键盘本身不持有输入值 —— 只 emit 按键事件,由宿主决定写入哪个输入框

无障碍

  • 容器 role="group" + aria-label
  • 每个按键为原生 button,文字即按键名(读屏读「1」「删除」)
  • 删除键用 aria-label="删除"(不读成符号)

从「该用哪一个」的角度区分;PC 端的对应实现见 PC 文档站。

组件何时用它而不是本组件
对话框Dialog金额确认这类需要「先输入再决定」的场景,键盘配合对话框一起用
轻提示Toast键盘输入完成后的结果反馈用轻提示
弹出层Popup键盘本身就是底部浮层;不要再用弹出层包一层同向浮层

规格未定 / 禁止发明

类别条目
禁止发明键盘高度的手势拖拽调整
禁止发明与系统键盘的互斥逻辑
规格未定是否支持自定义按键顺序
规格未定长按连续删除的间隔

结构(anatomy)

字段说明
keyboard根容器,固定在底部
keyboard__key单个按键
keyboard__key--delete删除键
keyboard__confirm确认键(showConfirm=true 时)

变体维度与类名映射

类名映射由构建脚本从契约 variantClasses 生成,并被 verify:mobile-docs 逐条对照组件 CSS 校验(类/变量必须真实存在)。

维度取值对应类名 / 变量
typenumber / digit
number (由数据驱动,无专属类)
digit (由数据驱动,无专属类)
showDeletefalse / true
false (由数据驱动,无专属类)
true .kole-m-numberkeyboard__key--delete
showConfirmfalse / true
false (由数据驱动,无专属类)
true .kole-m-numberkeyboard__confirm

代表变体

变体标签
type=number · showDelete=true · showConfirm=false金额键盘(含小数点)
type=digit · showDelete=true · showConfirm=false纯数字键盘
type=number · showDelete=true · showConfirm=true带确认键

用到的令牌

构建时从本组件样式表扫描得出。蓝色为移动端自有令牌,绿色为继承的 PC 令牌(改一处两端生效)。

--kole-m-action-height --kole-m-duration-slide --kole-m-ease-slide --kole-m-font-size-body --kole-m-font-size-label --kole-m-font-size-title --kole-m-gutter --kole-m-safe-bottom --kole-m-touch-target --kole-color-border --kole-color-brand --kole-color-brand-active --kole-color-card-bg --kole-color-disabled-bg --kole-color-focus-ring --kole-color-table-header-bg --kole-color-text-body --kole-color-text-disabled --kole-color-text-inverse --kole-color-text-secondary --kole-font-family --kole-radius-base --kole-m-numberkeyboard-gap

6 端源码

同一组件的六份实现(生产环境的类名与结构一致,差异只在技术栈写法与单位)。点开查看,右侧可复制。

frameworks-mobile/NumberKeyboard.css · 纯样式(CSS) · 105 行
frameworks-mobile/NumberKeyboard.css
/* Kole UI Mobile · NumberKeyboard 样式 — 对齐移动端规格 §17
   数字键盘:固定在底部;按键热区 ≥44px、网格等分;按下用 :active(不做悬停态)。 */

.kole-m-numberkeyboard {
  --kole-m-numberkeyboard-gap: 1px;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2001;
  box-sizing: border-box;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--kole-m-numberkeyboard-gap);
  padding-bottom: var(--kole-m-safe-bottom);
  background: var(--kole-color-border);
  font-family: var(--kole-font-family);
  transform: translateY(100%);
  transition: transform var(--kole-m-duration-slide) var(--kole-m-ease-slide);
}

.kole-m-numberkeyboard.is-open { transform: translateY(0); }

.kole-m-numberkeyboard__key {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: var(--kole-m-action-height);
  padding: 0;
  border: 0;
  background: var(--kole-color-card-bg);
  color: var(--kole-color-text-body);
  font-family: inherit;
  font-size: var(--kole-m-font-size-title);
  line-height: 1;
  cursor: pointer;
  touch-action: manipulation;
}

.kole-m-numberkeyboard__key:focus-visible {
  outline: 2px solid var(--kole-color-focus-ring);
  outline-offset: -2px;
}

.kole-m-numberkeyboard__key:active { background: var(--kole-color-table-header-bg); }

/* 变体 type=digit:纯 0-9,没有小数点键时删除键独占最后一格 */
.kole-m-numberkeyboard__key--delete { color: var(--kole-color-text-secondary); }

/* 变体 showConfirm=true:确认键跨两列 */
.kole-m-numberkeyboard__confirm {
  grid-column: span 2;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: var(--kole-m-action-height);
  padding: 0;
  border: 0;
  background: var(--kole-color-brand);
  color: var(--kole-color-text-inverse);
  font-family: inherit;
  font-size: var(--kole-m-font-size-body);
  line-height: 1;
  cursor: pointer;
  touch-action: manipulation;
}

.kole-m-numberkeyboard__confirm:focus-visible {
  outline: 2px solid var(--kole-color-focus-ring);
  outline-offset: -2px;
}

.kole-m-numberkeyboard__confirm:active { background: var(--kole-color-brand-active); }

/* 状态 disabled:确认键不可用 */
.kole-m-numberkeyboard__confirm:disabled,
.kole-m-numberkeyboard__confirm.is-disabled {
  background: var(--kole-color-disabled-bg);
  color: var(--kole-color-text-disabled);
  cursor: not-allowed;
}

.kole-m-numberkeyboard__confirm:disabled:active { background: var(--kole-color-disabled-bg); }

/* 演示页触发器(非组件交付样式) */
.kole-m-numberkeyboard__trigger {
  min-height: var(--kole-m-touch-target);
  padding: 0 var(--kole-m-gutter);
  border: 1px solid var(--kole-color-border);
  border-radius: var(--kole-radius-base);
  background: var(--kole-color-card-bg);
  color: var(--kole-color-text-body);
  font-family: inherit;
  font-size: var(--kole-m-font-size-label);
  cursor: pointer;
  touch-action: manipulation;
}

.kole-m-numberkeyboard__trigger:focus-visible {
  outline: 2px solid var(--kole-color-focus-ring);
  outline-offset: 2px;
}
frameworks-mobile/NumberKeyboard.html · H5 原生(无框架) · 151 行
frameworks-mobile/NumberKeyboard.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>Kole UI Mobile · NumberKeyboard(H5)</title>
<link rel="stylesheet" href="../.design_library/kole-ui-mobile/colors_and_type.css">
<link rel="stylesheet" href="NumberKeyboard.css">
<style>
  body { margin: 0; background: var(--kole-color-page-bg); font-family: var(--kole-font-family); color: var(--kole-color-text-body); }
  .demo { max-width: 375px; margin: 0 auto; padding: var(--kole-m-gutter) 0; }
  .demo-label { margin: 0; padding: var(--kole-space-12) var(--kole-m-gutter) var(--kole-space-8);
    font-size: var(--kole-m-font-size-label); color: var(--kole-color-text-secondary); }
  /* 展示框:transform 建立包含块,键盘限定在框内(生产环境固定在视口底部) */
  .demo-frame { position: relative; max-width: 375px; margin: 0 auto; height: 236px;
    overflow: hidden; transform: translateZ(0); background: var(--kole-color-page-bg);
    border-block: 1px solid var(--kole-color-border); }
  .demo-frame__body { padding: var(--kole-m-gutter); font-size: var(--kole-m-font-size-label);
    color: var(--kole-color-text-secondary); line-height: 1.7; }
  .demo-field { display: block; width: 100%; box-sizing: border-box; min-height: var(--kole-m-touch-target);
    padding: 0 var(--kole-space-12); border: 1px solid var(--kole-color-border);
    border-radius: var(--kole-radius-base); background: var(--kole-color-card-bg);
    color: var(--kole-color-text-body); font-family: inherit; font-size: var(--kole-m-font-size-body); }
  .demo-block[hidden] { display: none; }
</style>
</head>
<body>
<div class="demo">
  <section class="demo-block" data-demo="number">
    <p class="demo-label">金额键盘(type=number:0-9 + 小数点 + 删除;点删除键打标记)</p>
    <div class="demo-frame" data-assert="keyboard-number">
      <div class="demo-frame__body">
        <input class="demo-field" type="text" inputmode="none" value="128.50" readonly aria-label="金额">
        <p style="margin: var(--kole-space-8) 0 0">键盘只 emit 按键事件,写入哪个输入框由宿主决定。</p>
      </div>
      <div class="kole-m-numberkeyboard is-open" id="kb-number" role="group" aria-label="金额键盘">
        <button class="kole-m-numberkeyboard__key" type="button">1</button>
        <button class="kole-m-numberkeyboard__key" type="button">2</button>
        <button class="kole-m-numberkeyboard__key" type="button">3</button>
        <button class="kole-m-numberkeyboard__key" type="button">4</button>
        <button class="kole-m-numberkeyboard__key" type="button">5</button>
        <button class="kole-m-numberkeyboard__key" type="button">6</button>
        <button class="kole-m-numberkeyboard__key" type="button">7</button>
        <button class="kole-m-numberkeyboard__key" type="button">8</button>
        <button class="kole-m-numberkeyboard__key" type="button">9</button>
        <button class="kole-m-numberkeyboard__key" type="button">.</button>
        <button class="kole-m-numberkeyboard__key" type="button">0</button>
        <button class="kole-m-numberkeyboard__key kole-m-numberkeyboard__key--delete" type="button"
                id="kb-delete" aria-label="删除"
                data-behavior="click-sets-attr:#kb-number|data-key|delete">⌫</button>
      </div>
    </div>
  </section>

  <section class="demo-block" data-demo="digit">
    <p class="demo-label">纯数字键盘(type=digit:验证码场景,没有小数点键)</p>
    <div class="demo-frame" data-assert="keyboard-digit">
      <div class="demo-frame__body">
        <input class="demo-field" type="text" inputmode="none" value="4 8 2" readonly aria-label="验证码">
      </div>
      <div class="kole-m-numberkeyboard is-open" role="group" aria-label="验证码键盘">
        <button class="kole-m-numberkeyboard__key" type="button">1</button>
        <button class="kole-m-numberkeyboard__key" type="button">2</button>
        <button class="kole-m-numberkeyboard__key" type="button">3</button>
        <button class="kole-m-numberkeyboard__key" type="button">4</button>
        <button class="kole-m-numberkeyboard__key" type="button">5</button>
        <button class="kole-m-numberkeyboard__key" type="button">6</button>
        <button class="kole-m-numberkeyboard__key" type="button">7</button>
        <button class="kole-m-numberkeyboard__key" type="button">8</button>
        <button class="kole-m-numberkeyboard__key" type="button">9</button>
        <button class="kole-m-numberkeyboard__key kole-m-numberkeyboard__key--delete" type="button" aria-label="删除">⌫</button>
        <button class="kole-m-numberkeyboard__key" type="button">0</button>
        <button class="kole-m-numberkeyboard__key" type="button" aria-label="收起键盘">收起</button>
      </div>
    </div>
  </section>

  <section class="demo-block" data-demo="confirm">
    <p class="demo-label">带确认键(showConfirm=true:确认键跨两列、品牌色实底)</p>
    <div class="demo-frame" data-assert="keyboard-confirm">
      <div class="kole-m-numberkeyboard is-open" role="group" aria-label="带确认的键盘">
        <button class="kole-m-numberkeyboard__key" type="button">1</button>
        <button class="kole-m-numberkeyboard__key" type="button">2</button>
        <button class="kole-m-numberkeyboard__key" type="button">3</button>
        <button class="kole-m-numberkeyboard__key" type="button">4</button>
        <button class="kole-m-numberkeyboard__key" type="button">5</button>
        <button class="kole-m-numberkeyboard__key" type="button">6</button>
        <button class="kole-m-numberkeyboard__key" type="button">7</button>
        <button class="kole-m-numberkeyboard__key" type="button">8</button>
        <button class="kole-m-numberkeyboard__key" type="button">9</button>
        <button class="kole-m-numberkeyboard__key kole-m-numberkeyboard__key--delete" type="button" aria-label="删除">⌫</button>
        <button class="kole-m-numberkeyboard__key" type="button">0</button>
        <button class="kole-m-numberkeyboard__confirm" type="button">确认</button>
      </div>
    </div>
  </section>

  <section class="demo-block" data-demo="disabled">
    <p class="demo-label">确认键禁用(disabled:金额为 0 时不可提交)</p>
    <div class="demo-frame" data-assert="keyboard-disabled">
      <div class="kole-m-numberkeyboard is-open" role="group" aria-label="金额键盘(不可提交)">
        <button class="kole-m-numberkeyboard__key" type="button">1</button>
        <button class="kole-m-numberkeyboard__key" type="button">2</button>
        <button class="kole-m-numberkeyboard__key" type="button">3</button>
        <button class="kole-m-numberkeyboard__key" type="button">4</button>
        <button class="kole-m-numberkeyboard__key" type="button">5</button>
        <button class="kole-m-numberkeyboard__key" type="button">6</button>
        <button class="kole-m-numberkeyboard__key" type="button">7</button>
        <button class="kole-m-numberkeyboard__key" type="button">8</button>
        <button class="kole-m-numberkeyboard__key" type="button">9</button>
        <button class="kole-m-numberkeyboard__key kole-m-numberkeyboard__key--delete" type="button" aria-label="删除">⌫</button>
        <button class="kole-m-numberkeyboard__key" type="button">0</button>
        <button class="kole-m-numberkeyboard__confirm" type="button" disabled aria-label="确认(金额为 0,不可提交)">确认</button>
      </div>
    </div>
  </section>
</div>
<script>
  /* 演示页交互:删除键被按下时在键盘容器上打标记(真实业务里由宿主编排输入值)。 */
  (function () {
    var del = document.getElementById('kb-delete');
    var kb = document.getElementById('kb-number');
    if (!del || !kb) return;
    del.addEventListener('click', function () {
      kb.setAttribute('data-key', 'delete');
    });
  })();
</script>
<script>
  /* ?demo=<id> → 只显示该演示块(文档站按块预览用;无参数时全部显示,测试与回归走无参数路径) */
  (function () {
    var id = new URLSearchParams(location.search).get('demo');
    if (!id) return;
    var blocks = Array.prototype.slice.call(document.querySelectorAll('.demo-block'));
    var hit = false;
    blocks.forEach(function (b) {
      var on = b.getAttribute('data-demo') === id;
      if (on) hit = true;
      b.hidden = !on;
    });
    if (!hit) { blocks.forEach(function (b) { b.hidden = false; }); return; }
    document.body.classList.add('demo-single');
    blocks.forEach(function (b) {
      var label = b.querySelector('.demo-label');
      if (label && !b.hidden) label.hidden = true;
    });
  })();
</script>
</body>
</html>
frameworks-mobile/NumberKeyboard.jsx · React · 74 行
frameworks-mobile/NumberKeyboard.jsx
import React from 'react';
import './NumberKeyboard.css';

/* 数字键盘(移动端)— 规格 §17。
   键盘本身不持有输入值:只把按键名 emit 给宿主,由宿主决定写入哪个输入框(规格 §17.5)。
   type=number → 0-9 + 小数点;type=digit → 纯 0-9(没有小数点键,删除键落在最后一格)。
   showDelete=false 不渲染删除键;showConfirm=true 渲染跨两列的确认键,confirmDisabled 时禁用。
   每个按键是原生 button,文字即按键名;删除键用 aria-label="删除"(规格 §17.6,不读成符号)。
   默认插槽给宿主放「当前输入值」这类展示内容,键盘组件既不读也不写它。 */

/* 按键表:与规格 §17.3 的 type 变体一一对应(digit 少一个 ".") */
const NUMBER_KEYS = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '0'];
const DIGIT_KEYS = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];

export default function NumberKeyboard({
  type = 'number',
  showDelete = true,
  showConfirm = false,
  open = false,
  confirmDisabled = false,
  onInput,
  children = null,
}) {
  const keys = type === 'digit' ? DIGIT_KEYS : NUMBER_KEYS;
  const groupLabel = type === 'digit' ? '数字键盘(纯数字)' : '数字键盘(含小数点)';

  /* 删除键与确认键也走同一条 emit 通道:宿主要想区分,看载荷是不是 'delete' / 'confirm' */
  function press(key) {
    if (onInput) onInput(key);
  }

  return (
    <>
      {children}
      <div
        className={'kole-m-numberkeyboard' + (open ? ' is-open' : '')}
        role="group"
        aria-label={groupLabel}
      >
        {keys.map((key) => (
          <button
            key={key}
            className="kole-m-numberkeyboard__key"
            type="button"
            onClick={() => press(key)}
          >
            {key}
          </button>
        ))}
        {showDelete ? (
          <button
            className="kole-m-numberkeyboard__key kole-m-numberkeyboard__key--delete"
            type="button"
            aria-label="删除"
            onClick={() => press('delete')}
          >
            ⌫
          </button>
        ) : null}
        {showConfirm ? (
          <button
            className="kole-m-numberkeyboard__confirm"
            type="button"
            disabled={confirmDisabled}
            onClick={() => press('confirm')}
          >
            确认
          </button>
        ) : null}
      </div>
    </>
  );
}
frameworks-mobile/NumberKeyboard.vue2.vue · Vue 2 · 77 行
frameworks-mobile/NumberKeyboard.vue2.vue
<template>
  <div>
    <slot></slot>
    <div
      class="kole-m-numberkeyboard"
      :class="{ 'is-open': open }"
      role="group"
      :aria-label="groupLabel"
    >
      <button
        v-for="key in keys"
        :key="key"
        class="kole-m-numberkeyboard__key"
        type="button"
        @click="press(key)"
      >
        {{ key }}
      </button>
      <button
        v-if="showDelete"
        class="kole-m-numberkeyboard__key kole-m-numberkeyboard__key--delete"
        type="button"
        aria-label="删除"
        @click="press('delete')"
      >
        ⌫
      </button>
      <button
        v-if="showConfirm"
        class="kole-m-numberkeyboard__confirm"
        type="button"
        :disabled="confirmDisabled"
        @click="press('confirm')"
      >
        确认
      </button>
    </div>
  </div>
</template>

<script>
/* 数字键盘(移动端)— 规格 §17。
   键盘本身不持有输入值:只把按键名 emit 给宿主,由宿主决定写入哪个输入框(规格 §17.5)。
   type=number → 0-9 + 小数点;type=digit → 纯 0-9(没有小数点键,删除键落在最后一格)。
   默认插槽给宿主放「当前输入值」这类展示内容,键盘组件既不读也不写它。 */
var NUMBER_KEYS = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '0'];
var DIGIT_KEYS = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];

export default {
  name: 'KoleMNumberKeyboard',
  props: {
    type: { type: String, default: 'number' },
    showDelete: { type: Boolean, default: true },
    showConfirm: { type: Boolean, default: false },
    open: { type: Boolean, default: false },
    confirmDisabled: { type: Boolean, default: false }
  },
  computed: {
    /* 按键表:与规格 §17.3 的 type 变体一一对应(digit 少一个 ".") */
    keys: function () {
      return this.type === 'digit' ? DIGIT_KEYS : NUMBER_KEYS;
    },
    groupLabel: function () {
      return this.type === 'digit' ? '数字键盘(纯数字)' : '数字键盘(含小数点)';
    }
  },
  methods: {
    /* 删除键与确认键也走同一条 emit 通道:宿主要想区分,看载荷是不是 'delete' / 'confirm' */
    press: function (key) {
      this.$emit('input', key);
    }
  }
};
</script>

<style src="./NumberKeyboard.css"></style>
frameworks-mobile/NumberKeyboard.vue3.vue · Vue 3 · 71 行
frameworks-mobile/NumberKeyboard.vue3.vue
<template>
  <div>
    <slot></slot>
    <div
      class="kole-m-numberkeyboard"
      :class="{ 'is-open': open }"
      role="group"
      :aria-label="groupLabel"
    >
      <button
        v-for="key in keys"
        :key="key"
        class="kole-m-numberkeyboard__key"
        type="button"
        @click="press(key)"
      >
        {{ key }}
      </button>
      <button
        v-if="showDelete"
        class="kole-m-numberkeyboard__key kole-m-numberkeyboard__key--delete"
        type="button"
        aria-label="删除"
        @click="press('delete')"
      >
        ⌫
      </button>
      <button
        v-if="showConfirm"
        class="kole-m-numberkeyboard__confirm"
        type="button"
        :disabled="confirmDisabled"
        @click="press('confirm')"
      >
        确认
      </button>
    </div>
  </div>
</template>

<script setup>
/* 数字键盘(移动端)— 规格 §17。
   键盘本身不持有输入值:只把按键名 emit 给宿主,由宿主决定写入哪个输入框(规格 §17.5)。
   type=number → 0-9 + 小数点;type=digit → 纯 0-9(没有小数点键,删除键落在最后一格)。
   默认插槽给宿主放「当前输入值」这类展示内容,键盘组件既不读也不写它。 */
import { computed } from 'vue';

const props = defineProps({
  type: { type: String, default: 'number' },
  showDelete: { type: Boolean, default: true },
  showConfirm: { type: Boolean, default: false },
  open: { type: Boolean, default: false },
  confirmDisabled: { type: Boolean, default: false }
});
const emit = defineEmits(['input']);

/* 按键表:与规格 §17.3 的 type 变体一一对应(digit 少一个 ".") */
const NUMBER_KEYS = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '0'];
const DIGIT_KEYS = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];

const keys = computed(() => (props.type === 'digit' ? DIGIT_KEYS : NUMBER_KEYS));
const groupLabel = computed(() => (props.type === 'digit' ? '数字键盘(纯数字)' : '数字键盘(含小数点)'));

/* 删除键与确认键也走同一条 emit 通道:宿主要想区分,看载荷是不是 'delete' / 'confirm' */
function press(key) {
  emit('input', key);
}
</script>

<style src="./NumberKeyboard.css"></style>
frameworks-mobile/NumberKeyboard.uniapp.vue · uni-app(跨端:小程序 / App / H5) · 138 行
frameworks-mobile/NumberKeyboard.uniapp.vue
<template>
  <view>
    <slot></slot>
    <view
      class="kole-m-numberkeyboard"
      :class="{ 'is-open': open }"
      :role="'group'"
      :aria-label="groupLabel"
    >
      <view
        v-for="key in keys"
        :key="key"
        class="kole-m-numberkeyboard__key"
        role="button"
        :aria-label="key"
        @tap="press(key)"
      >
        <text>{{ key }}</text>
      </view>
      <view
        v-if="showDelete"
        class="kole-m-numberkeyboard__key kole-m-numberkeyboard__key--delete"
        role="button"
        aria-label="删除"
        @tap="press('delete')"
      >
        <text>⌫</text>
      </view>
      <view
        v-if="showConfirm"
        class="kole-m-numberkeyboard__confirm"
        :role="'button'"
        :aria-disabled="confirmDisabled ? 'true' : 'false'"
        @tap="press('confirm')"
      >
        <text>确认</text>
      </view>
    </view>
  </view>
</template>

<script setup>
/* uni-app 端 · 数字键盘(移动端)— 规格 §17
   跨端差异:小程序与 App 端没有 DOM,按键用 view + role="button" + aria-label(role 在 H5
   产物里生效),文字包在 text 里;点击用 @tap,不用 @click / PointerEvent。
   键盘本身不持有输入值,只把按键名 emit 给宿主(规格 §17.5);
   确认键的 disabled 态在 uni 端用 aria-disabled 表达(view 没有原生 disabled 属性)。
   尺寸用 rpx(88rpx = 375pt 下的 44px 触控最小边长)。 */
import { computed } from 'vue';

const props = defineProps({
  type: { type: String, default: 'number' },
  showDelete: { type: Boolean, default: true },
  showConfirm: { type: Boolean, default: false },
  open: { type: Boolean, default: false },
  confirmDisabled: { type: Boolean, default: false }
});
const emit = defineEmits(['input']);

/* 按键表:与规格 §17.3 的 type 变体一一对应(digit 少一个 ".") */
const NUMBER_KEYS = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '0'];
const DIGIT_KEYS = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];

const keys = computed(() => (props.type === 'digit' ? DIGIT_KEYS : NUMBER_KEYS));
const groupLabel = computed(() => (props.type === 'digit' ? '数字键盘(纯数字)' : '数字键盘(含小数点)'));

/* 删除键与确认键也走同一条 emit 通道:宿主要想区分,看载荷是不是 'delete' / 'confirm' */
function press(key) {
  if (key === 'confirm' && props.confirmDisabled) return;
  emit('input', key);
}
</script>

<style>
.kole-m-numberkeyboard {
  --kole-m-numberkeyboard-gap: 1rpx;
  --kole-m-action-height: 112rpx;
  --kole-m-touch-target: 88rpx;
  --kole-m-font-size-title: 34rpx;
  --kole-m-font-size-body: 32rpx;
  --kole-m-font-size-label: 28rpx;
  --kole-m-gutter: 32rpx;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2001;
  box-sizing: border-box;
  display: flex;
  flex-wrap: wrap;
  padding-bottom: constant(safe-area-inset-bottom);
  padding-bottom: env(safe-area-inset-bottom);
  background-color: var(--kole-color-border);
}

.kole-m-numberkeyboard.is-open { transform: translateY(0); }

.kole-m-numberkeyboard__key {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 33.33%;
  min-height: var(--kole-m-action-height);
  background-color: var(--kole-color-card-bg);
  border: 1rpx solid var(--kole-color-border);
  color: var(--kole-color-text-body);
  font-size: var(--kole-m-font-size-title);
  line-height: 1;
}

/* 状态 active:按下反馈;小程序无 :active,靠 hover-class 或宿主侧实现 */
.kole-m-numberkeyboard__key[aria-disabled="true"] { opacity: .5; }

/* 变体 type=digit:纯 0-9,没有小数点键时删除键独占最后一格 */
.kole-m-numberkeyboard__key--delete { color: var(--kole-color-text-secondary); }

/* 变体 showConfirm=true:确认键跨两列 */
.kole-m-numberkeyboard__confirm {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 66.66%;
  min-height: var(--kole-m-action-height);
  background-color: var(--kole-color-brand);
  color: var(--kole-color-text-inverse);
  font-size: var(--kole-m-font-size-body);
  line-height: 1;
}

/* 状态 disabled:确认键不可用 */
.kole-m-numberkeyboard__confirm[aria-disabled="true"] {
  background-color: var(--kole-color-disabled-bg);
  color: var(--kole-color-text-disabled);
}
</style>

测试与回归

断言在真实的 375×640 设备帧里跑(引擎与 PC 侧共用 tests/_runtime.js,触控行为动词来自移动端 tests/mobile/_behaviors.js)。

断言 15 条 · 全部通过 报告 2026-09-20 16:50:33

复现命令
node site/dev-server.js &
REG_BASE=http://127.0.0.1:3311 node tools/run-mobile-regression.mjs   # 全量 5 个组件
npm run verify:mobile-docs                                           # 本页内容完整性 + API 与源码一致性

设计契约

components/mobile-numberkeyboard.json(点击展开原始 JSON)
{
  "schemaVersion": 1,
  "sourceKind": "authored-spec",
  "provenance": "authored-in-repo",
  "specFile": "spec/移动端规格.md",
  "confidence": "high",
  "specSection": "17 · 数字键盘 NumberKeyboard",
  "slug": "mobile-numberkeyboard",
  "name": "数字键盘 NumberKeyboard",
  "semanticTypeCandidates": [
    "keyboard",
    "keypad",
    "numpad"
  ],
  "variantDimensions": [
    {
      "name": "type",
      "values": [
        "number",
        "digit"
      ]
    },
    {
      "name": "showDelete",
      "values": [
        "false",
        "true"
      ]
    },
    {
      "name": "showConfirm",
      "values": [
        "false",
        "true"
      ]
    }
  ],
  "representativeVariants": [
    {
      "type": "number",
      "showDelete": "true",
      "showConfirm": "false",
      "label": "金额键盘(含小数点)"
    },
    {
      "type": "digit",
      "showDelete": "true",
      "showConfirm": "false",
      "label": "纯数字键盘"
    },
    {
      "type": "number",
      "showDelete": "true",
      "showConfirm": "true",
      "label": "带确认键"
    }
  ],
  "anatomy": {
    "keyboard": "根容器,固定在底部",
    "keyboard__key": "单个按键",
    "keyboard__key--delete": "删除键",
    "keyboard__confirm": "确认键(showConfirm=true 时)"
  },
  "structurePatterns": {
    "type": "number(0-9 + 小数点)/ digit(纯 0-9)",
    "showDelete": "false / true",
    "showConfirm": "false / true"
  },
  "usageHints": [
    "为金额、验证码等纯数字输入提供自绘键盘(比系统键盘更可控、更安全)",
    "按键热区不小于 44×44,网格等分排列",
    "按下用 :active,不做悬停态",
    "键盘本身不持有输入值 —— 只 emit 按键事件,由宿主决定写入哪个输入框",
    "容器 role=\"group\" + aria-label",
    "每个按键为原生 button,文字即按键名(读屏读「1」「删除」)",
    "删除键用 aria-label=\"删除\"(不读成符号)"
  ],
  "doNotInvent": [
    "键盘高度的手势拖拽调整",
    "与系统键盘的互斥逻辑"
  ],
  "unknowns": [
    "是否支持自定义按键顺序",
    "长按连续删除的间隔"
  ],
  "interaction": [
    "按键热区不小于 44×44,网格等分排列",
    "按下用 :active,不做悬停态",
    "键盘本身不持有输入值 —— 只 emit 按键事件,由宿主决定写入哪个输入框"
  ],
  "accessibility": [
    "容器 role=\"group\" + aria-label",
    "每个按键为原生 button,文字即按键名(读屏读「1」「删除」)",
    "删除键用 aria-label=\"删除\"(不读成符号)"
  ],
  "api": {
    "source": "implementation",
    "note": "props / events / slots 为 6 端实现的公共接口(说明文字取自规格对应小节)。字段名与各端源码逐名核对:node tools/verify-mobile-docs.mjs",
    "requiredNote": "「必传」按严格定义:实现里**没有默认值**时才为 Y(本门禁逐条核对 props 与各端源码的默认值,防止契约与实现脱节)。",
    "props": [
      {
        "name": "type",
        "type": "'number' | 'digit'",
        "default": "'number'",
        "desc": "变体 type:number 含小数点键,digit 为纯 0-9(规格 §17.3)",
        "required": false
      },
      {
        "name": "showDelete",
        "type": "boolean",
        "default": "true",
        "desc": "变体 showDelete:是否显示删除键(规格 §17.3)",
        "required": false
      },
      {
        "name": "showConfirm",
        "type": "boolean",
        "default": "false",
        "desc": "变体 showConfirm:是否显示确认键(跨两列、品牌色实底)(规格 §17.3)",
        "required": false
      },
      {
        "name": "open",
        "type": "boolean",
        "default": "false",
        "desc": "状态 open:键盘是否展开(规格 §17.4)",
        "required": false
      },
      {
        "name": "confirmDisabled",
        "type": "boolean",
        "default": "false",
        "desc": "状态 disabled:确认键是否不可用(规格 §17.4)",
        "required": false
      }
    ],
    "events": [
      {
        "name": "input",
        "params": "key: string",
        "desc": "按下按键时触发:数字键回传该字符,删除键回传 'delete',确认键回传 'confirm';键盘不持有输入值(规格 §17.5)"
      }
    ],
    "slots": [
      {
        "name": "default",
        "desc": "额外的自定义按键(追加在标准按键之后)"
      }
    ]
  },
  "variantClasses": {
    "type": {
      "number": [],
      "digit": []
    },
    "showDelete": {
      "false": [],
      "true": [
        ".kole-m-numberkeyboard__key--delete"
      ]
    },
    "showConfirm": {
      "false": [],
      "true": [
        ".kole-m-numberkeyboard__confirm"
      ]
    }
  },
  "demos": [
    {
      "id": "number",
      "group": "01 组件类型",
      "title": "金额键盘",
      "desc": "含小数点与删除键;按键只回传字符,写入哪个输入框由宿主决定。",
      "variant": "type=number"
    },
    {
      "id": "digit",
      "group": "01 组件类型",
      "title": "纯数字键盘",
      "desc": "验证码场景:没有小数点键,最后两格是删除与收起。",
      "variant": "type=digit"
    },
    {
      "id": "confirm",
      "group": "01 组件类型",
      "title": "带确认键",
      "desc": "确认键跨两列并用品牌色实底,与数字键在视觉上分层。",
      "variant": "showConfirm=true"
    },
    {
      "id": "disabled",
      "group": "02 组件状态",
      "title": "确认键禁用",
      "desc": "金额为 0 时确认键置灰不可点,避免无效提交。",
      "variant": "状态 disabled"
    }
  ],
  "related": [
    {
      "slug": "mobile-dialog",
      "why": "金额确认这类需要「先输入再决定」的场景,键盘配合对话框一起用"
    },
    {
      "slug": "mobile-toast",
      "why": "键盘输入完成后的结果反馈用轻提示"
    },
    {
      "slug": "mobile-popup",
      "why": "键盘本身就是底部浮层;不要再用弹出层包一层同向浮层"
    }
  ]
}