数字键盘NumberKeyboard
为金额、验证码等纯数字输入提供自绘键盘(比系统键盘更可控、更安全)
数据录入 规格 17 · 数字键盘 NumberKeyboard 6 端实现 触摸优先
<!-- ① 令牌: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 组件类型
含小数点与删除键;按键只回传字符,写入哪个输入框由宿主决定。
查看代码(演示页原文 · 25 行)
<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>验证码场景:没有小数点键,最后两格是删除与收起。
查看代码(演示页原文 · 22 行)
<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>确认键跨两列并用品牌色实底,与数字键在视觉上分层。
查看代码(演示页原文 · 19 行)
<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 组件状态
金额为 0 时确认键置灰不可点,避免无效提交。
查看代码(演示页原文 · 19 行)
<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 |
showDelete | boolean | true | 变体 showDelete:是否显示删除键(规格 §17.3) | N |
showConfirm | boolean | false | 变体 showConfirm:是否显示确认键(跨两列、品牌色实底)(规格 §17.3) | N |
open | boolean | false | 状态 open:键盘是否展开(规格 §17.4) | N |
confirmDisabled | boolean | false | 状态 disabled:确认键是否不可用(规格 §17.4) | N |
「必传」按严格定义:实现里**没有默认值**时才为 Y(本门禁逐条核对 props 与各端源码的默认值,防止契约与实现脱节)。
事件
| 名称 | 参数 | 说明 |
|---|---|---|
input | key: string | 按下按键时触发:数字键回传该字符,删除键回传 'delete',确认键回传 'confirm';键盘不持有输入值(规格 §17.5) |
插槽
| 名称 | 说明 |
|---|---|
default | 额外的自定义按键(追加在标准按键之后) |
CSS 变量
组件级变量(在组件样式表里定义)。业务侧可在自己的作用域内覆盖,不必改组件源码。
| 名称 | 默认值 | 说明 |
|---|---|---|
--kole-m-numberkeyboard-gap | 1px | 组件内部默认值,可在业务侧覆盖 |
何时使用
- 为金额、验证码等纯数字输入提供自绘键盘(比系统键盘更可控、更安全)
- 按键热区不小于 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 校验(类/变量必须真实存在)。
| 维度 | 取值 | 对应类名 / 变量 |
|---|---|---|
type | number / digit | number (由数据驱动,无专属类) digit (由数据驱动,无专属类) |
showDelete | false / true | false (由数据驱动,无专属类) true .kole-m-numberkeyboard__key--delete |
showConfirm | false / 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 令牌(改一处两端生效)。
6 端源码
同一组件的六份实现(生产环境的类名与结构一致,差异只在技术栈写法与单位)。点开查看,右侧可复制。
frameworks-mobile/NumberKeyboard.css · 纯样式(CSS) · 105 行
/* 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 行
<!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 行
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 行
<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 行
<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 行
<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": "键盘本身就是底部浮层;不要再用弹出层包一层同向浮层"
}
]
}