日期选择器DatePicker
从底部弹出的年 / 月 / 日选择器,用于生日、有效期这类需要日期输入的场景
数据录入 规格 18 · 日期选择器 DatePicker 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-datepicker.css">
<!-- ③ 结构照抄下方任一演示块(类名与 6 端实现一致) -->
演示
每个演示都是真实渲染:预览帧加载 frameworks-mobile/DatePicker.html?demo=<id>(只显示该演示块),代码是该演示块在演示页里的原文,可复制。全部演示同屏可看 演示页 ↗。
01 组件类型
三列滚动,选中项用品牌色高亮并以 aria-selected 标注,不依赖视觉位置。
查看代码(演示页原文 · 35 行)
<section class="demo-block" data-demo="date">
<p class="demo-label">年月日(mode=date:三列滚动,选中项品牌色高亮;点取消打标记)</p>
<div class="demo-frame" data-assert="datepicker-date">
<div class="demo-frame__body">
<p>已选日期:<span class="demo-value">2026-09-20</span></p>
</div>
<div class="kole-m-datepicker__mask is-open" aria-hidden="true"></div>
<div class="kole-m-datepicker kole-m-datepicker--round is-open" id="dp-date"
role="dialog" aria-modal="true" aria-labelledby="dp-date-title">
<div class="kole-m-datepicker__header">
<button class="kole-m-datepicker__btn" type="button" id="dp-cancel"
data-behavior="click-sets-attr:#dp-date|data-cancelled|true">取消</button>
<span class="kole-m-datepicker__title" id="dp-date-title">选择日期</span>
<button class="kole-m-datepicker__btn kole-m-datepicker__btn--confirm" type="button">确定</button>
</div>
<div class="kole-m-datepicker__columns">
<ul class="kole-m-datepicker__column" role="listbox" aria-label="年">
<li class="kole-m-datepicker__option" role="option" aria-selected="false">2025</li>
<li class="kole-m-datepicker__option" role="option" aria-selected="false">2026</li>
<li class="kole-m-datepicker__option is-selected" role="option" aria-selected="true">2027</li>
</ul>
<ul class="kole-m-datepicker__column" role="listbox" aria-label="月">
<li class="kole-m-datepicker__option" role="option" aria-selected="false">8 月</li>
<li class="kole-m-datepicker__option is-selected" role="option" aria-selected="true">9 月</li>
<li class="kole-m-datepicker__option" role="option" aria-selected="false">10 月</li>
</ul>
<ul class="kole-m-datepicker__column" role="listbox" aria-label="日">
<li class="kole-m-datepicker__option" role="option" aria-selected="false">19</li>
<li class="kole-m-datepicker__option is-selected" role="option" aria-selected="true">20</li>
<li class="kole-m-datepicker__option" role="option" aria-selected="false">21</li>
</ul>
</div>
</div>
</div>
</section>只有两列,用于账单月份这类不需要具体日期的场景。
查看代码(演示页原文 · 26 行)
<section class="demo-block" data-demo="month">
<p class="demo-label">年月(mode=month:只有两列,用于账单月份这类场景)</p>
<div class="demo-frame" data-assert="datepicker-month">
<div class="kole-m-datepicker__mask is-open" aria-hidden="true"></div>
<div class="kole-m-datepicker kole-m-datepicker--round is-open"
role="dialog" aria-modal="true" aria-labelledby="dp-month-title">
<div class="kole-m-datepicker__header">
<button class="kole-m-datepicker__btn" type="button">取消</button>
<span class="kole-m-datepicker__title" id="dp-month-title">选择月份</span>
<button class="kole-m-datepicker__btn kole-m-datepicker__btn--confirm" type="button">确定</button>
</div>
<div class="kole-m-datepicker__columns">
<ul class="kole-m-datepicker__column" role="listbox" aria-label="年">
<li class="kole-m-datepicker__option" role="option" aria-selected="false">2024</li>
<li class="kole-m-datepicker__option" role="option" aria-selected="false">2025</li>
<li class="kole-m-datepicker__option is-selected" role="option" aria-selected="true">2026</li>
</ul>
<ul class="kole-m-datepicker__column" role="listbox" aria-label="月">
<li class="kole-m-datepicker__option" role="option" aria-selected="false">8 月</li>
<li class="kole-m-datepicker__option is-selected" role="option" aria-selected="true">9 月</li>
<li class="kole-m-datepicker__option" role="option" aria-selected="false">10 月</li>
</ul>
</div>
</div>
</div>
</section>02 组件状态
默认态:遮罩 pointer-events: none,页面可正常滚动与点击。
查看代码(演示页原文 · 29 行)
<section class="demo-block" data-demo="closed">
<p class="demo-label">收起态(status=closed:默认态,遮罩与浮层都不可见、不可点)</p>
<div class="demo-frame" data-assert="datepicker-closed">
<div class="demo-frame__body">
<p>浮层收起时页面可正常滚动与点击(遮罩 pointer-events: none)。</p>
<button class="kole-m-datepicker__trigger" type="button">选择日期</button>
</div>
<div class="kole-m-datepicker__mask" aria-hidden="true"></div>
<div class="kole-m-datepicker kole-m-datepicker--round"
role="dialog" aria-modal="true" aria-labelledby="dp-closed-title" aria-hidden="true">
<div class="kole-m-datepicker__header">
<button class="kole-m-datepicker__btn" type="button">取消</button>
<span class="kole-m-datepicker__title" id="dp-closed-title">选择日期</span>
<button class="kole-m-datepicker__btn kole-m-datepicker__btn--confirm" type="button">确定</button>
</div>
<div class="kole-m-datepicker__columns">
<ul class="kole-m-datepicker__column" role="listbox" aria-label="年">
<li class="kole-m-datepicker__option is-selected" role="option" aria-selected="true">2026</li>
</ul>
<ul class="kole-m-datepicker__column" role="listbox" aria-label="月">
<li class="kole-m-datepicker__option is-selected" role="option" aria-selected="true">9 月</li>
</ul>
<ul class="kole-m-datepicker__column" role="listbox" aria-label="日">
<li class="kole-m-datepicker__option is-selected" role="option" aria-selected="true">20</li>
</ul>
</div>
</div>
</div>
</section>API
props / events / slots 为 6 端实现的公共接口(说明文字取自规格对应小节)。字段名与各端源码逐名核对:node tools/verify-mobile-docs.mjs
Props
| 名称 | 类型 | 默认值 | 说明 | 必传 |
|---|---|---|---|---|
mode | 'date' | 'month' | 'date' | 变体 mode:date 为年月日三列,month 为年月两列(规格 §18.3) | N |
round | boolean | false | 变体 round:靠内容一侧是否切圆角(规格 §18.3) | N |
open | boolean | false | 状态 open:浮层是否展开(规格 §18.4) | N |
closeOnMask | boolean | true | 遮罩点击是否关闭(规格 §18.5) | N |
value | string | '' | 当前选中值,YYYY-MM-DD(mode=month 时 YYYY-MM);用于让选中项可见(规格 §18.6) | N |
years | Array<string> | [] | 年份列选项(宿主提供,本组件不发明年份范围)(规格 §18.7) | N |
months | Array<string> | [] | 月份列选项(规格 §18.2 picker__column) | N |
days | Array<string> | [] | 日期列选项;mode=month 时不渲染该列(规格 §18.3) | N |
「必传」按严格定义:实现里**没有默认值**时才为 Y(本门禁逐条核对 props 与各端源码的默认值,防止契约与实现脱节)。
事件
| 名称 | 参数 | 说明 |
|---|---|---|
confirm | — | 点击确定时触发(规格 §18.2 picker__header) |
close | — | 点击取消或遮罩(closeOnMask=true 时)时触发(规格 §18.5) |
插槽
| 名称 | 说明 |
|---|---|
default | 自定义内容(追加在列区之后) |
CSS 变量
组件级变量(在组件样式表里定义)。业务侧可在自己的作用域内覆盖,不必改组件源码。
| 名称 | 默认值 | 说明 |
|---|---|---|
| 本组件没有组件级 CSS 变量 | ||
何时使用
- 从底部弹出的年 / 月 / 日选择器,用于生日、有效期这类需要日期输入的场景
- 遮罩点击关闭;closeOnMask=false 时不关闭
- 选项行高不小于 44px,滚动容器 -webkit-overflow-scrolling: touch
- 确定 / 取消按钮热区不小于 44px
- 浮层 role="dialog" + aria-modal="true"
- 每列 role="listbox",选项 role="option" + aria-selected
- 遮罩 aria-hidden="true"
- 选中值以 YYYY-MM-DD 文本呈现(不依赖视觉滚动位置)
交互与触控
- 遮罩点击关闭;closeOnMask=false 时不关闭
- 选项行高不小于 44px,滚动容器 -webkit-overflow-scrolling: touch
- 确定 / 取消按钮热区不小于 44px
无障碍
- 浮层 role="dialog" + aria-modal="true"
- 每列 role="listbox",选项 role="option" + aria-selected
- 遮罩 aria-hidden="true"
- 选中值以 YYYY-MM-DD 文本呈现(不依赖视觉滚动位置)
相似组件
从「该用哪一个」的角度区分;PC 端的对应实现见 PC 文档站。
| 组件 | 何时用它而不是本组件 |
|---|---|
| 动作面板ActionSheet | 从底部弹出的选择器与动作面板形态接近;选项是日期时才用日期选择器 |
| 单元格Cell | 「已选日期」的展示入口用单元格,点开才拉起选择器 |
| 弹出层Popup | 日期选择器本身就是底部浮层,不要在弹出层里再套一层 |
规格未定 / 禁止发明
| 类别 | 条目 |
|---|---|
| 禁止发明 | 日期范围的禁用规则(由宿主传入) |
| 禁止发明 | 滚轮惯性 / 吸附动画的物理参数 |
| 规格未定 | 可选年份的范围 |
| 规格未定 | 是否支持「至今」这类特殊选项 |
结构(anatomy)
| 字段 | 说明 |
|---|---|
mask | 遮罩,点击关闭 |
picker | 底部浮层 |
picker__header | 取消 / 标题 / 确定 |
picker__columns | 年 / 月 / 日三列 |
picker__column | 单列,可滚动 |
picker__option | 单个选项 |
变体维度与类名映射
类名映射由构建脚本从契约 variantClasses 生成,并被 verify:mobile-docs 逐条对照组件 CSS 校验(类/变量必须真实存在)。
| 维度 | 取值 | 对应类名 / 变量 |
|---|---|---|
mode | date / month | date (由数据驱动,无专属类) month (由数据驱动,无专属类) |
round | false / true | false (由数据驱动,无专属类) true .kole-m-datepicker--round |
代表变体
| 变体 | 标签 |
|---|---|
mode=date · round=true | 年月日三列 |
mode=month · round=true | 年月两列 |
mode=date · round=false | 直角(不含圆角) |
用到的令牌
构建时从本组件样式表扫描得出。蓝色为移动端自有令牌,绿色为继承的 PC 令牌(改一处两端生效)。
6 端源码
同一组件的六份实现(生产环境的类名与结构一致,差异只在技术栈写法与单位)。点开查看,右侧可复制。
frameworks-mobile/DatePicker.css · 纯样式(CSS) · 145 行
/* Kole UI Mobile · DatePicker 样式 — 对齐移动端规格 §18
日期选择器:底部浮层 + 三列滚动;选项行高 ≥44px;选中项品牌色高亮。
日期值的真源是 aria-selected 与文本(不依赖视觉滚动位置)。 */
.kole-m-datepicker__mask {
position: fixed;
inset: 0;
z-index: 2000;
background: var(--kole-color-mask);
opacity: 0;
pointer-events: none;
transition: opacity var(--kole-m-duration-slide) var(--kole-m-ease-slide);
}
.kole-m-datepicker__mask.is-open {
opacity: 1;
pointer-events: auto;
}
.kole-m-datepicker {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 2001;
box-sizing: border-box;
display: flex;
flex-direction: column;
padding-bottom: var(--kole-m-safe-bottom);
background: var(--kole-color-card-bg);
font-family: var(--kole-font-family);
transform: translateY(100%);
transition: transform var(--kole-m-duration-slide) var(--kole-m-ease-slide);
}
.kole-m-datepicker.is-open { transform: translateY(0); }
/* 变体 round=true:靠内容一侧切圆角 */
.kole-m-datepicker--round {
border-start-start-radius: var(--kole-radius-large);
border-start-end-radius: var(--kole-radius-large);
}
.kole-m-datepicker__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--kole-space-8);
padding: var(--kole-space-8) var(--kole-m-gutter);
border-bottom: 1px solid var(--kole-color-border);
font-size: var(--kole-m-font-size-body);
font-weight: 500;
}
.kole-m-datepicker__title {
color: var(--kole-color-text-title);
font-weight: 500;
}
.kole-m-datepicker__btn {
box-sizing: border-box;
min-height: var(--kole-m-touch-target);
padding: 0 var(--kole-space-8);
border: 0;
border-radius: var(--kole-radius-base);
background: none;
color: var(--kole-color-brand);
font-family: inherit;
font-size: var(--kole-m-font-size-label);
line-height: 1;
cursor: pointer;
touch-action: manipulation;
}
.kole-m-datepicker__btn:focus-visible {
outline: 2px solid var(--kole-color-focus-ring);
outline-offset: 2px;
}
.kole-m-datepicker__btn:active { background: var(--kole-color-brand-bg); }
.kole-m-datepicker__btn--confirm { font-weight: 500; }
.kole-m-datepicker__columns {
display: flex;
align-items: stretch;
gap: 1px;
padding: 0 0 var(--kole-space-8);
background: var(--kole-color-card-bg);
}
.kole-m-datepicker__column {
flex: 1 1 0;
min-width: 0;
max-height: 200px;
margin: 0;
padding: 0;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
list-style: none;
}
.kole-m-datepicker__option {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
min-height: var(--kole-m-touch-target);
padding: 0 var(--kole-space-4);
color: var(--kole-color-text-body);
font-size: var(--kole-m-font-size-label);
line-height: 1.3;
text-align: center;
}
/* 状态 selected:当前选中项(品牌色高亮) */
.kole-m-datepicker__option.is-selected {
color: var(--kole-color-brand);
font-weight: 500;
}
.kole-m-datepicker__option[aria-selected="true"] {
color: var(--kole-color-brand);
font-weight: 500;
}
/* 演示页触发器(非组件交付样式) */
.kole-m-datepicker__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-datepicker__trigger:focus-visible {
outline: 2px solid var(--kole-color-focus-ring);
outline-offset: 2px;
}
frameworks-mobile/DatePicker.html · H5 原生(无框架) · 152 行
<!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 · DatePicker(H5)</title>
<link rel="stylesheet" href="../.design_library/kole-ui-mobile/colors_and_type.css">
<link rel="stylesheet" href="DatePicker.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: 300px;
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-value { color: var(--kole-color-text-body); font-weight: 500; }
.demo-block[hidden] { display: none; }
</style>
</head>
<body>
<div class="demo">
<section class="demo-block" data-demo="date">
<p class="demo-label">年月日(mode=date:三列滚动,选中项品牌色高亮;点取消打标记)</p>
<div class="demo-frame" data-assert="datepicker-date">
<div class="demo-frame__body">
<p>已选日期:<span class="demo-value">2026-09-20</span></p>
</div>
<div class="kole-m-datepicker__mask is-open" aria-hidden="true"></div>
<div class="kole-m-datepicker kole-m-datepicker--round is-open" id="dp-date"
role="dialog" aria-modal="true" aria-labelledby="dp-date-title">
<div class="kole-m-datepicker__header">
<button class="kole-m-datepicker__btn" type="button" id="dp-cancel"
data-behavior="click-sets-attr:#dp-date|data-cancelled|true">取消</button>
<span class="kole-m-datepicker__title" id="dp-date-title">选择日期</span>
<button class="kole-m-datepicker__btn kole-m-datepicker__btn--confirm" type="button">确定</button>
</div>
<div class="kole-m-datepicker__columns">
<ul class="kole-m-datepicker__column" role="listbox" aria-label="年">
<li class="kole-m-datepicker__option" role="option" aria-selected="false">2025</li>
<li class="kole-m-datepicker__option" role="option" aria-selected="false">2026</li>
<li class="kole-m-datepicker__option is-selected" role="option" aria-selected="true">2027</li>
</ul>
<ul class="kole-m-datepicker__column" role="listbox" aria-label="月">
<li class="kole-m-datepicker__option" role="option" aria-selected="false">8 月</li>
<li class="kole-m-datepicker__option is-selected" role="option" aria-selected="true">9 月</li>
<li class="kole-m-datepicker__option" role="option" aria-selected="false">10 月</li>
</ul>
<ul class="kole-m-datepicker__column" role="listbox" aria-label="日">
<li class="kole-m-datepicker__option" role="option" aria-selected="false">19</li>
<li class="kole-m-datepicker__option is-selected" role="option" aria-selected="true">20</li>
<li class="kole-m-datepicker__option" role="option" aria-selected="false">21</li>
</ul>
</div>
</div>
</div>
</section>
<section class="demo-block" data-demo="month">
<p class="demo-label">年月(mode=month:只有两列,用于账单月份这类场景)</p>
<div class="demo-frame" data-assert="datepicker-month">
<div class="kole-m-datepicker__mask is-open" aria-hidden="true"></div>
<div class="kole-m-datepicker kole-m-datepicker--round is-open"
role="dialog" aria-modal="true" aria-labelledby="dp-month-title">
<div class="kole-m-datepicker__header">
<button class="kole-m-datepicker__btn" type="button">取消</button>
<span class="kole-m-datepicker__title" id="dp-month-title">选择月份</span>
<button class="kole-m-datepicker__btn kole-m-datepicker__btn--confirm" type="button">确定</button>
</div>
<div class="kole-m-datepicker__columns">
<ul class="kole-m-datepicker__column" role="listbox" aria-label="年">
<li class="kole-m-datepicker__option" role="option" aria-selected="false">2024</li>
<li class="kole-m-datepicker__option" role="option" aria-selected="false">2025</li>
<li class="kole-m-datepicker__option is-selected" role="option" aria-selected="true">2026</li>
</ul>
<ul class="kole-m-datepicker__column" role="listbox" aria-label="月">
<li class="kole-m-datepicker__option" role="option" aria-selected="false">8 月</li>
<li class="kole-m-datepicker__option is-selected" role="option" aria-selected="true">9 月</li>
<li class="kole-m-datepicker__option" role="option" aria-selected="false">10 月</li>
</ul>
</div>
</div>
</div>
</section>
<section class="demo-block" data-demo="closed">
<p class="demo-label">收起态(status=closed:默认态,遮罩与浮层都不可见、不可点)</p>
<div class="demo-frame" data-assert="datepicker-closed">
<div class="demo-frame__body">
<p>浮层收起时页面可正常滚动与点击(遮罩 pointer-events: none)。</p>
<button class="kole-m-datepicker__trigger" type="button">选择日期</button>
</div>
<div class="kole-m-datepicker__mask" aria-hidden="true"></div>
<div class="kole-m-datepicker kole-m-datepicker--round"
role="dialog" aria-modal="true" aria-labelledby="dp-closed-title" aria-hidden="true">
<div class="kole-m-datepicker__header">
<button class="kole-m-datepicker__btn" type="button">取消</button>
<span class="kole-m-datepicker__title" id="dp-closed-title">选择日期</span>
<button class="kole-m-datepicker__btn kole-m-datepicker__btn--confirm" type="button">确定</button>
</div>
<div class="kole-m-datepicker__columns">
<ul class="kole-m-datepicker__column" role="listbox" aria-label="年">
<li class="kole-m-datepicker__option is-selected" role="option" aria-selected="true">2026</li>
</ul>
<ul class="kole-m-datepicker__column" role="listbox" aria-label="月">
<li class="kole-m-datepicker__option is-selected" role="option" aria-selected="true">9 月</li>
</ul>
<ul class="kole-m-datepicker__column" role="listbox" aria-label="日">
<li class="kole-m-datepicker__option is-selected" role="option" aria-selected="true">20</li>
</ul>
</div>
</div>
</div>
</section>
</div>
<script>
/* 演示页交互:取消按钮只打标记(真实业务里由宿主关闭浮层并丢弃选择)。 */
(function () {
var cancel = document.getElementById('dp-cancel');
var dialog = document.getElementById('dp-date');
if (!cancel || !dialog) return;
cancel.addEventListener('click', function () {
dialog.setAttribute('data-cancelled', 'true');
});
})();
</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/DatePicker.jsx · React · 140 行
import React, { useRef, useState } from 'react';
import './DatePicker.css';
/* 日期选择器(移动端)— 规格 §18。
底部浮层 = 装饰性遮罩(aria-hidden)+ 面板;遮罩点击关闭,closeOnMask=false 时不关(规格 §18.5)。
面板 role="dialog" + aria-modal="true",标题元素 id 由 aria-labelledby 指向(规格 §18.6)。
每列 role="listbox"、选项 role="option" + aria-selected;选中项的 is-selected 与 aria-selected 同步
(选中值以 YYYY-MM-DD 文本呈现:value 直接消费,不依赖视觉滚动位置)。
本端不发明选择事件(契约只有 confirm / close):点选只改本地高亮,确认时由宿主读 confirm 后的语义。 */
/* 面板标题元素 id:同页多实例不撞号(不依赖 React 版本特性,模块级自增即可) */
let seq = 0;
const COLUMN_LABELS = { year: '年', month: '月', day: '日' };
/* value 形如 YYYY-MM-DD;mode=month 时只有 YYYY-MM,day 为空串 */
function partsOf(value) {
const seg = String(value || '').split('-');
return { year: seg[0] || '', month: seg[1] || '', day: seg[2] || '' };
}
/* 选项文字可能带单位(「9 月」「9月」),取首个数字段比较;纯符号选项退回逐字比较 */
function sameValue(option, part) {
if (part === undefined || part === null || part === '') return false;
const digits = String(option).match(/[0-9]+/);
if (digits) return String(Number(digits[0])) === String(Number(part));
return String(option) === String(part);
}
/* mode=date → 年/月/日 三列;mode=month → 年/月 两列(规格 §18.3) */
function columnsOf(mode, years, months, days) {
const cols = [
{ key: 'year', label: COLUMN_LABELS.year, options: years },
{ key: 'month', label: COLUMN_LABELS.month, options: months }
];
if (mode !== 'month') cols.push({ key: 'day', label: COLUMN_LABELS.day, options: days });
return cols;
}
export default function DatePicker({
mode = 'date',
round = false,
open = false,
closeOnMask = true,
value = '',
years = [],
months = [],
days = [],
onConfirm,
onClose,
children = null,
}) {
const idRef = useRef(null);
if (idRef.current === null) {
seq += 1;
idRef.current = 'kole-m-datepicker-title-' + seq;
}
const titleId = idRef.current;
/* null = 跟随 value 属性;点选后本地记账,确认前不改宿主的值 */
const [draft, setDraft] = useState(null);
const picked = draft || partsOf(value);
const title = mode === 'month' ? '选择月份' : '选择日期';
const label = title + (value ? ':' + value : '');
const columns = columnsOf(mode, years, months, days);
function select(key, option) {
setDraft(Object.assign({}, picked, { [key]: String(option) }));
}
return (
<>
{children}
<div
className={'kole-m-datepicker__mask' + (open ? ' is-open' : '')}
aria-hidden="true"
onClick={() => {
if (closeOnMask && onClose) onClose();
}}
/>
<div
className={
'kole-m-datepicker' +
(round ? ' kole-m-datepicker--round' : '') +
(open ? ' is-open' : '')
}
role="dialog"
aria-modal="true"
aria-labelledby={titleId}
aria-hidden={open ? undefined : 'true'}
>
<div className="kole-m-datepicker__header">
<button
className="kole-m-datepicker__btn"
type="button"
onClick={() => {
if (onClose) onClose();
}}
>
取消
</button>
<span className="kole-m-datepicker__title" id={titleId}>
{label}
</span>
<button
className="kole-m-datepicker__btn kole-m-datepicker__btn--confirm"
type="button"
onClick={() => {
if (onConfirm) onConfirm();
}}
>
确定
</button>
</div>
<div className="kole-m-datepicker__columns">
{columns.map((col) => (
<ul className="kole-m-datepicker__column" role="listbox" aria-label={col.label} key={col.key}>
{col.options.map((option, i) => {
const selected = sameValue(option, picked[col.key]);
return (
<li
className={'kole-m-datepicker__option' + (selected ? ' is-selected' : '')}
role="option"
aria-selected={selected ? 'true' : 'false'}
key={String(option) + i}
onClick={() => select(col.key, option)}
>
{option}
</li>
);
})}
</ul>
))}
</div>
</div>
</>
);
}
frameworks-mobile/DatePicker.vue2.vue · Vue 2 · 158 行
<template>
<div>
<slot></slot>
<div
class="kole-m-datepicker__mask"
:class="{ 'is-open': open }"
aria-hidden="true"
@click="onMaskClick"
></div>
<div
class="kole-m-datepicker"
:class="panelClass"
role="dialog"
aria-modal="true"
:aria-labelledby="titleId"
:aria-hidden="open ? null : 'true'"
>
<div class="kole-m-datepicker__header">
<button class="kole-m-datepicker__btn" type="button" @click="onCancelClick">取消</button>
<span class="kole-m-datepicker__title" :id="titleId">{{ label }}</span>
<button
class="kole-m-datepicker__btn kole-m-datepicker__btn--confirm"
type="button"
@click="onConfirmClick"
>确定</button>
</div>
<div class="kole-m-datepicker__columns">
<ul
v-for="col in columns"
:key="col.key"
class="kole-m-datepicker__column"
role="listbox"
:aria-label="col.label"
>
<li
v-for="(option, i) in col.options"
:key="col.key + '-' + i"
class="kole-m-datepicker__option"
:class="{ 'is-selected': isSelected(col.key, option) }"
role="option"
:aria-selected="isSelected(col.key, option) ? 'true' : 'false'"
@click="select(col.key, option)"
>{{ option }}</li>
</ul>
</div>
</div>
</div>
</template>
<script>
/* 日期选择器(移动端)— 规格 §18。
底部浮层 = 装饰性遮罩(aria-hidden)+ 面板;遮罩点击关闭,closeOnMask=false 时不关(规格 §18.5)。
面板 role="dialog" + aria-modal="true",标题元素 id 由 aria-labelledby 指向(规格 §18.6)。
每列 role="listbox"、选项 role="option" + aria-selected;is-selected 与 aria-selected 同步
(选中值以 YYYY-MM-DD 文本呈现:value 直接消费,不依赖视觉滚动位置)。
本端不发明选择事件(契约只有 confirm / close):点选只改本地高亮。 */
var COLUMN_LABELS = { year: '年', month: '月', day: '日' };
/* 面板标题元素 id 的自增种子:同页多实例不撞号(不依赖内部实例字段) */
var titleSeed = 0;
/* value 形如 YYYY-MM-DD;mode=month 时只有 YYYY-MM,day 为空串 */
function partsOf(value) {
var seg = String(value || '').split('-');
return { year: seg[0] || '', month: seg[1] || '', day: seg[2] || '' };
}
/* 选项文字可能带单位(「9 月」),取首个数字段比较;纯符号选项退回逐字比较 */
function sameValue(option, part) {
if (part === undefined || part === null || part === '') return false;
var digits = String(option).match(/[0-9]+/);
if (digits) return String(Number(digits[0])) === String(Number(part));
return String(option) === String(part);
}
/* mode=date → 年/月/日 三列;mode=month → 年/月 两列(规格 §18.3) */
function columnsOf(mode, years, months, days) {
var cols = [
{ key: 'year', label: COLUMN_LABELS.year, options: years },
{ key: 'month', label: COLUMN_LABELS.month, options: months }
];
if (mode !== 'month') cols.push({ key: 'day', label: COLUMN_LABELS.day, options: days });
return cols;
}
export default {
name: 'KoleMDatePicker',
props: {
mode: { type: String, default: 'date' },
round: { type: Boolean, default: false },
open: { type: Boolean, default: false },
closeOnMask: { type: Boolean, default: true },
value: { type: String, default: '' },
years: { type: Array, default: function () { return []; } },
months: { type: Array, default: function () { return []; } },
days: { type: Array, default: function () { return []; } }
},
data: function () {
/* 每个实例占一个号,标题 id 因此唯一(aria-labelledby 指向它) */
titleSeed += 1;
return {
instanceId: titleSeed,
/* null = 跟随 value 属性;点选后本地记账,确认前不改宿主的值 */
draft: null
};
},
computed: {
titleId: function () {
return 'kole-m-datepicker-title-' + this.instanceId;
},
title: function () {
return this.mode === 'month' ? '选择月份' : '选择日期';
},
label: function () {
return this.value ? this.title + ':' + this.value : this.title;
},
columns: function () {
return columnsOf(this.mode, this.years, this.months, this.days);
},
picked: function () {
return this.draft || partsOf(this.value);
},
panelClass: function () {
return [
this.round ? 'kole-m-datepicker--round' : '',
this.open ? 'is-open' : ''
].filter(Boolean);
}
},
methods: {
isSelected: function (key, option) {
return sameValue(option, this.picked[key]);
},
select: function (key, option) {
var next = partsOf(this.value);
next.year = this.picked.year;
next.month = this.picked.month;
next.day = this.picked.day;
next[key] = String(option);
this.draft = next;
},
onMaskClick: function () {
if (!this.closeOnMask) return;
this.$emit('close');
},
onCancelClick: function () {
this.$emit('close');
},
onConfirmClick: function () {
this.$emit('confirm');
}
}
};
</script>
<style src="./DatePicker.css"></style>
frameworks-mobile/DatePicker.vue3.vue · Vue 3 · 134 行
<template>
<div>
<slot></slot>
<div
class="kole-m-datepicker__mask"
:class="{ 'is-open': open }"
aria-hidden="true"
@click="onMaskClick"
></div>
<div
class="kole-m-datepicker"
:class="panelClass"
role="dialog"
aria-modal="true"
:aria-labelledby="titleId"
:aria-hidden="open ? null : 'true'"
>
<div class="kole-m-datepicker__header">
<button class="kole-m-datepicker__btn" type="button" @click="onCancelClick">取消</button>
<span class="kole-m-datepicker__title" :id="titleId">{{ label }}</span>
<button
class="kole-m-datepicker__btn kole-m-datepicker__btn--confirm"
type="button"
@click="onConfirmClick"
>确定</button>
</div>
<div class="kole-m-datepicker__columns">
<ul
v-for="col in columns"
:key="col.key"
class="kole-m-datepicker__column"
role="listbox"
:aria-label="col.label"
>
<li
v-for="(option, i) in col.options"
:key="col.key + '-' + i"
class="kole-m-datepicker__option"
:class="{ 'is-selected': isSelected(col.key, option) }"
role="option"
:aria-selected="isSelected(col.key, option) ? 'true' : 'false'"
@click="select(col.key, option)"
>{{ option }}</li>
</ul>
</div>
</div>
</div>
</template>
<script setup>
/* 日期选择器(移动端)— 规格 §18。
底部浮层 = 装饰性遮罩(aria-hidden)+ 面板;遮罩点击关闭,closeOnMask=false 时不关(规格 §18.5)。
面板 role="dialog" + aria-modal="true",标题元素 id 由 aria-labelledby 指向(规格 §18.6)。
每列 role="listbox"、选项 role="option" + aria-selected;is-selected 与 aria-selected 同步
(选中值以 YYYY-MM-DD 文本呈现:value 直接消费,不依赖视觉滚动位置)。
本端不发明选择事件(契约只有 confirm / close):点选只改本地高亮。 */
import { computed, getCurrentInstance, ref } from 'vue';
const props = defineProps({
mode: { type: String, default: 'date' },
round: { type: Boolean, default: false },
open: { type: Boolean, default: false },
closeOnMask: { type: Boolean, default: true },
value: { type: String, default: '' },
years: { type: Array, default: () => [] },
months: { type: Array, default: () => [] },
days: { type: Array, default: () => [] }
});
const emit = defineEmits(['confirm', 'close']);
const COLUMN_LABELS = { year: '年', month: '月', day: '日' };
/* 面板标题元素 id:取当前实例 uid,保证同页多实例不撞号(不依赖 Vue 3.5 才有的 useId) */
const titleId = 'kole-m-datepicker-title-' + getCurrentInstance().uid;
/* value 形如 YYYY-MM-DD;mode=month 时只有 YYYY-MM,day 为空串 */
function partsOf(value) {
const seg = String(value || '').split('-');
return { year: seg[0] || '', month: seg[1] || '', day: seg[2] || '' };
}
/* 选项文字可能带单位(「9 月」),取首个数字段比较;纯符号选项退回逐字比较 */
function sameValue(option, part) {
if (part === undefined || part === null || part === '') return false;
const digits = String(option).match(/[0-9]+/);
if (digits) return String(Number(digits[0])) === String(Number(part));
return String(option) === String(part);
}
/* null = 跟随 value 属性;点选后本地记账,确认前不改宿主的值 */
const draft = ref(null);
const picked = computed(() => draft.value || partsOf(props.value));
const title = computed(() => (props.mode === 'month' ? '选择月份' : '选择日期'));
const label = computed(() => (props.value ? title.value + ':' + props.value : title.value));
/* mode=date → 年/月/日 三列;mode=month → 年/月 两列(规格 §18.3) */
const columns = computed(() => {
const cols = [
{ key: 'year', label: COLUMN_LABELS.year, options: props.years },
{ key: 'month', label: COLUMN_LABELS.month, options: props.months }
];
if (props.mode !== 'month') cols.push({ key: 'day', label: COLUMN_LABELS.day, options: props.days });
return cols;
});
const panelClass = computed(() => [
props.round ? 'kole-m-datepicker--round' : '',
props.open ? 'is-open' : ''
].filter(Boolean));
function isSelected(key, option) {
return sameValue(option, picked.value[key]);
}
function select(key, option) {
draft.value = Object.assign({}, picked.value, { [key]: String(option) });
}
function onMaskClick() {
if (!props.closeOnMask) return;
emit('close');
}
function onCancelClick() {
emit('close');
}
function onConfirmClick() {
emit('confirm');
}
</script>
<style src="./DatePicker.css"></style>
frameworks-mobile/DatePicker.uniapp.vue · uni-app(跨端:小程序 / App / H5) · 241 行
<template>
<view>
<slot></slot>
<view
class="kole-m-datepicker__mask"
:class="{ 'is-open': open }"
aria-hidden="true"
@tap="onMaskTap"
@touchmove.stop.prevent
></view>
<view
class="kole-m-datepicker"
:class="panelClass"
:role="'dialog'"
:aria-modal="'true'"
:aria-labelledby="titleId"
:aria-hidden="open ? 'false' : 'true'"
>
<view class="kole-m-datepicker__header">
<view class="kole-m-datepicker__btn" role="button" @tap="onCancelTap">
<text>取消</text>
</view>
<text class="kole-m-datepicker__title" :id="titleId">{{ label }}</text>
<view
class="kole-m-datepicker__btn kole-m-datepicker__btn--confirm"
role="button"
@tap="onConfirmTap"
>
<text>确定</text>
</view>
</view>
<view class="kole-m-datepicker__columns">
<scroll-view
v-for="col in columns"
:key="col.key"
class="kole-m-datepicker__column"
scroll-y
:role="'listbox'"
:aria-label="col.label"
>
<view
v-for="(option, i) in col.options"
:key="col.key + '-' + i"
class="kole-m-datepicker__option"
:class="isSelected(col.key, option) ? 'is-selected' : ''"
:role="'option'"
:aria-selected="isSelected(col.key, option) ? 'true' : 'false'"
@tap="select(col.key, option)"
>
<text>{{ option }}</text>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script setup>
/* uni-app 端 · 日期选择器(移动端)— 规格 §18
跨端差异:用 view / text;每列用 scroll-view(scroll-y)代替 ul/li,滚动由小程序/App 原生接管,
role / aria-* 仍按规格 §18.6 保留(用 :role / :aria-* 绑定,H5 产物里生效)。
点击一律 @tap;遮罩用 @touchmove.stop.prevent 阻止背景滚动穿透。
面板标题元素 id 用组件实例 uid 拼出,同页多实例不撞号;aria-labelledby 指向它。
本端不发明选择事件(契约只有 confirm / close):点选只改本地高亮。
尺寸用 rpx(88rpx = 375pt 下的 44px 触控最小边长)。 */
import { computed, ref } from 'vue';
/* 面板标题元素 id 的自增种子:同页多实例不撞号(uni 端也不依赖内部实例字段) */
let titleSeed = 0;
const props = defineProps({
mode: { type: String, default: 'date' },
round: { type: Boolean, default: false },
open: { type: Boolean, default: false },
closeOnMask: { type: Boolean, default: true },
value: { type: String, default: '' },
years: { type: Array, default: () => [] },
months: { type: Array, default: () => [] },
days: { type: Array, default: () => [] }
});
const emit = defineEmits(['confirm', 'close']);
const COLUMN_LABELS = { year: '年', month: '月', day: '日' };
/* 面板标题元素 id:组件初始化时占一个号,保证同页多实例不撞号 */
titleSeed += 1;
const titleId = 'kole-m-datepicker-title-' + titleSeed;
/* value 形如 YYYY-MM-DD;mode=month 时只有 YYYY-MM,day 为空串 */
function partsOf(value) {
const seg = String(value || '').split('-');
return { year: seg[0] || '', month: seg[1] || '', day: seg[2] || '' };
}
/* 选项文字可能带单位(「9 月」),取首个数字段比较;纯符号选项退回逐字比较 */
function sameValue(option, part) {
if (part === undefined || part === null || part === '') return false;
const digits = String(option).match(/[0-9]+/);
if (digits) return String(Number(digits[0])) === String(Number(part));
return String(option) === String(part);
}
/* null = 跟随 value 属性;点选后本地记账,确认前不改宿主的值 */
const draft = ref(null);
const picked = computed(() => draft.value || partsOf(props.value));
const title = computed(() => (props.mode === 'month' ? '选择月份' : '选择日期'));
const label = computed(() => (props.value ? title.value + ':' + props.value : title.value));
/* mode=date → 年/月/日 三列;mode=month → 年/月 两列(规格 §18.3) */
const columns = computed(() => {
const cols = [
{ key: 'year', label: COLUMN_LABELS.year, options: props.years },
{ key: 'month', label: COLUMN_LABELS.month, options: props.months }
];
if (props.mode !== 'month') cols.push({ key: 'day', label: COLUMN_LABELS.day, options: props.days });
return cols;
});
const panelClass = computed(() => [
props.round ? 'kole-m-datepicker--round' : '',
props.open ? 'is-open' : ''
].filter(Boolean));
function isSelected(key, option) {
return sameValue(option, picked.value[key]);
}
function select(key, option) {
draft.value = Object.assign({}, picked.value, { [key]: String(option) });
}
function onMaskTap() {
if (!props.closeOnMask) return;
emit('close');
}
function onCancelTap() {
emit('close');
}
function onConfirmTap() {
emit('confirm');
}
</script>
<style>
.kole-m-datepicker {
--kole-m-touch-target: 88rpx;
--kole-m-action-height: 112rpx;
--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-direction: column;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
background-color: var(--kole-color-card-bg);
}
.kole-m-datepicker__mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 2000;
background-color: var(--kole-color-mask);
opacity: 0;
}
.kole-m-datepicker__mask.is-open { opacity: 1; }
/* 变体 round=true:靠内容一侧切圆角 */
.kole-m-datepicker--round {
border-top-left-radius: 16rpx;
border-top-right-radius: 16rpx;
}
.kole-m-datepicker__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16rpx var(--kole-m-gutter);
border-bottom: 1rpx solid var(--kole-color-border);
font-size: var(--kole-m-font-size-body);
}
.kole-m-datepicker__title {
color: var(--kole-color-text-title);
font-size: var(--kole-m-font-size-body);
}
.kole-m-datepicker__btn {
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
min-height: var(--kole-m-touch-target);
padding: 0 16rpx;
border-radius: 8rpx;
color: var(--kole-color-brand);
font-size: var(--kole-m-font-size-label);
}
.kole-m-datepicker__btn--confirm { font-weight: 500; }
.kole-m-datepicker__columns {
display: flex;
align-items: stretch;
}
.kole-m-datepicker__column {
flex: 1;
min-width: 0;
max-height: 400rpx;
}
.kole-m-datepicker__option {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
min-height: var(--kole-m-touch-target);
color: var(--kole-color-text-body);
font-size: var(--kole-m-font-size-label);
text-align: center;
}
/* 状态 selected:当前选中项(品牌色高亮) */
.kole-m-datepicker__option.is-selected {
color: var(--kole-color-brand);
font-weight: 500;
}
</style>
测试与回归
断言在真实的 375×640 设备帧里跑(引擎与 PC 侧共用 tests/_runtime.js,触控行为动词来自移动端 tests/mobile/_behaviors.js)。
断言 14 条 · 全部通过 报告 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-datepicker.json(点击展开原始 JSON)
{
"schemaVersion": 1,
"sourceKind": "authored-spec",
"provenance": "authored-in-repo",
"specFile": "spec/移动端规格.md",
"confidence": "high",
"specSection": "18 · 日期选择器 DatePicker",
"slug": "mobile-datepicker",
"name": "日期选择器 DatePicker",
"semanticTypeCandidates": [
"datepicker",
"calendar",
"wheel"
],
"variantDimensions": [
{
"name": "mode",
"values": [
"date",
"month"
]
},
{
"name": "round",
"values": [
"false",
"true"
]
}
],
"representativeVariants": [
{
"mode": "date",
"round": "true",
"label": "年月日三列"
},
{
"mode": "month",
"round": "true",
"label": "年月两列"
},
{
"mode": "date",
"round": "false",
"label": "直角(不含圆角)"
}
],
"anatomy": {
"mask": "遮罩,点击关闭",
"picker": "底部浮层",
"picker__header": "取消 / 标题 / 确定",
"picker__columns": "年 / 月 / 日三列",
"picker__column": "单列,可滚动",
"picker__option": "单个选项"
},
"structurePatterns": {
"mode": "date(年月日)/ month(年月)",
"round": "false / true"
},
"usageHints": [
"从底部弹出的年 / 月 / 日选择器,用于生日、有效期这类需要日期输入的场景",
"遮罩点击关闭;closeOnMask=false 时不关闭",
"选项行高不小于 44px,滚动容器 -webkit-overflow-scrolling: touch",
"确定 / 取消按钮热区不小于 44px",
"浮层 role=\"dialog\" + aria-modal=\"true\"",
"每列 role=\"listbox\",选项 role=\"option\" + aria-selected",
"遮罩 aria-hidden=\"true\"",
"选中值以 YYYY-MM-DD 文本呈现(不依赖视觉滚动位置)"
],
"doNotInvent": [
"日期范围的禁用规则(由宿主传入)",
"滚轮惯性 / 吸附动画的物理参数"
],
"unknowns": [
"可选年份的范围",
"是否支持「至今」这类特殊选项"
],
"interaction": [
"遮罩点击关闭;closeOnMask=false 时不关闭",
"选项行高不小于 44px,滚动容器 -webkit-overflow-scrolling: touch",
"确定 / 取消按钮热区不小于 44px"
],
"accessibility": [
"浮层 role=\"dialog\" + aria-modal=\"true\"",
"每列 role=\"listbox\",选项 role=\"option\" + aria-selected",
"遮罩 aria-hidden=\"true\"",
"选中值以 YYYY-MM-DD 文本呈现(不依赖视觉滚动位置)"
],
"api": {
"source": "implementation",
"note": "props / events / slots 为 6 端实现的公共接口(说明文字取自规格对应小节)。字段名与各端源码逐名核对:node tools/verify-mobile-docs.mjs",
"requiredNote": "「必传」按严格定义:实现里**没有默认值**时才为 Y(本门禁逐条核对 props 与各端源码的默认值,防止契约与实现脱节)。",
"props": [
{
"name": "mode",
"type": "'date' | 'month'",
"default": "'date'",
"desc": "变体 mode:date 为年月日三列,month 为年月两列(规格 §18.3)",
"required": false
},
{
"name": "round",
"type": "boolean",
"default": "false",
"desc": "变体 round:靠内容一侧是否切圆角(规格 §18.3)",
"required": false
},
{
"name": "open",
"type": "boolean",
"default": "false",
"desc": "状态 open:浮层是否展开(规格 §18.4)",
"required": false
},
{
"name": "closeOnMask",
"type": "boolean",
"default": "true",
"desc": "遮罩点击是否关闭(规格 §18.5)",
"required": false
},
{
"name": "value",
"type": "string",
"default": "''",
"desc": "当前选中值,YYYY-MM-DD(mode=month 时 YYYY-MM);用于让选中项可见(规格 §18.6)",
"required": false
},
{
"name": "years",
"type": "Array",
"default": "[]",
"desc": "年份列选项(宿主提供,本组件不发明年份范围)(规格 §18.7)",
"required": false
},
{
"name": "months",
"type": "Array",
"default": "[]",
"desc": "月份列选项(规格 §18.2 picker__column)",
"required": false
},
{
"name": "days",
"type": "Array",
"default": "[]",
"desc": "日期列选项;mode=month 时不渲染该列(规格 §18.3)",
"required": false
}
],
"events": [
{
"name": "confirm",
"params": "—",
"desc": "点击确定时触发(规格 §18.2 picker__header)"
},
{
"name": "close",
"params": "—",
"desc": "点击取消或遮罩(closeOnMask=true 时)时触发(规格 §18.5)"
}
],
"slots": [
{
"name": "default",
"desc": "自定义内容(追加在列区之后)"
}
]
},
"variantClasses": {
"mode": {
"date": [],
"month": []
},
"round": {
"false": [],
"true": [
".kole-m-datepicker--round"
]
}
},
"demos": [
{
"id": "date",
"group": "01 组件类型",
"title": "年月日",
"desc": "三列滚动,选中项用品牌色高亮并以 aria-selected 标注,不依赖视觉位置。",
"variant": "mode=date"
},
{
"id": "month",
"group": "01 组件类型",
"title": "年月",
"desc": "只有两列,用于账单月份这类不需要具体日期的场景。",
"variant": "mode=month"
},
{
"id": "closed",
"group": "02 组件状态",
"title": "收起态",
"desc": "默认态:遮罩 pointer-events: none,页面可正常滚动与点击。",
"variant": "状态 closed"
}
],
"related": [
{
"slug": "actionsheet",
"why": "从底部弹出的选择器与动作面板形态接近;选项是日期时才用日期选择器"
},
{
"slug": "cell",
"why": "「已选日期」的展示入口用单元格,点开才拉起选择器"
},
{
"slug": "mobile-popup",
"why": "日期选择器本身就是底部浮层,不要在弹出层里再套一层"
}
]
}