cube-ui TimePicker 时间选择器完全指南:配置、事件与源码原理
cube-ui TimePicker 时间选择器完全指南:配置、事件与源码原理
TimePicker 是 cube-ui(基于 Vue 2 的移动端组件库)中用于选择时间的滚动式选择器组件,覆盖「日期 + 小时 + 分钟」三列联动选择、快捷「现在」选项、最小/最大可选时间限制以及自定义时间格式等常见场景。阅读本文后,你将能够通过 $createTimePicker 命令式 API 快速接入时间选择,掌握 day、minuteStep、delay、min、max、format 等全部配置项的含义与边界条件,并能结合源码理解其底层级联数据(cascade data)的构建与时间戳换算逻辑。
组件定位与调用方式
TimePicker 在 cube-ui 中的定位是「日期选择的常用功能封装」:它在内部复用了 CascadePicker(级联选择器)的滚轮交互能力,将「日期 / 小时 / 分钟」组织成三级联动的列数据,同时对外暴露一套面向业务的时间语义配置。
它的调用方式属于命令式 API(create-api),也就是说组件实例不是写在模板里,而是通过 this.$createTimePicker({...}).show() 动态创建并展示。因此在使用 TimePicker 之前,需要先了解 create-api 的使用方式。在源码层面,这一注册逻辑位于 src/modules/time-picker/api.js:它调用 createAPI(Vue, TimePicker, ['select', 'cancel', 'change']) 注册 $createTimePicker,并声明该组件不支持单例模式(传入 single 参数时会给出 tip 提示),即每次调用都会创建一个独立实例,因此你不必担心多个时间选择器实例之间互相覆盖状态。
在 Vue 中接入组件:
import Vue from 'vue'
import TimePicker from 'cube-ui/lib/time-picker' // 按需引入
// 或 import { TimePicker } from 'cube-ui'
Vue.use(TimePicker) // 注册后即可使用 this.$createTimePicker
基本用法:创建并展示时间选择器
最基本的用法是:点击按钮时创建一个 TimePicker 实例,配置回调事件,然后调用 .show() 展示。完整示例(可直接复制运行):
<cube-button @click="showTimePicker">TimePicker</cube-button>
export default {
methods: {
showTimePicker () {
this.$createTimePicker({
showNow: true,
minuteStep: 5,
delay: 15,
onSelect: (selectedTime, selectedText, formatedTime) => {
this.$createDialog({
type: 'warn',
title: `selected time: ${selectedTime}`,
content: `selected text: ${selectedText}<br>format time: ${formatedTime}`,
icon: 'cubeic-alert'
}).show()
},
onCancel: () => {
this.$createToast({
type: 'correct',
txt: 'Clicked cancel button',
time: 1000
}).show()
}
}).show()
}
}
}
上述代码中三个核心配置的含义:
showNow:控制是否展示「现在」选项。true时会在小时列的最前面插入一个「现在」快捷项,选中它即代表当前时刻。minuteStep:分钟列的步进值。设为5时,分钟列只展示00、05、10、…、55。delay:从当前时刻向后推迟的分钟数,它决定了最小可选时间(未显式设置min时生效)。delay: 15表示至少 15 分钟之后的时间才可以选。
选中「确定」后触发 onSelect,回调参数分别为:selectedTime(当前选中时间的时间戳)、selectedText(选中文本,形如 今日 14:30)、formatedTime(按 format 格式化的时间字符串);点击「取消」则触发 onCancel。
配置日期列:day 的 len / filter / format
第一列是日期列,默认展示从今天起向后推迟 len 天的日期。通过 day 对象可以精细控制日期列的展示长度、文案和格式:
<cube-button @click="showTimePicker">TimePicker - day options</cube-button>
export default {
methods: {
showTimePicker () {
this.$createTimePicker({
showNow: true,
minuteStep: 10,
delay: 10,
day: {
len: 5,
filter: ['Today', 'Tomorrow'],
format: 'M year d day'
},
onSelect: (selectedTime, selectedText, formatedTime) => {
this.$createDialog({
type: 'warn',
title: `selected time: ${selectedTime}`,
content: `selected text: ${selectedText}<br>format time: ${formatedTime}`,
icon: 'cubeic-alert'
}).show()
},
onCancel: () => {
this.$createToast({
type: 'correct',
txt: 'Clicked cancel button',
time: 1000
}).show()
}
}).show()
}
}
}
day 三个子属性的作用:
len:日期列展示的长度,即从最小可选时间起向后推迟len天(未显式设置max时生效)。上例中len: 5表示第一列展示 5 个日期。filter:日期列的文案映射数组。组件会把日期与filter数组按「距今天的差值」一一对应,filter[0]对应今天、filter[1]对应明天。上例中前两行会显示Today、Tomorrow。format:当日期超出filter数组长度时,剩余日期按format渲染,例如M year d day。len大于filter长度时该属性必然生效。
这三个属性的默认值在源码 src/components/time-picker/time-picker.vue 中定义:day 默认 { len: 3 },并通过 locale 混合 _day 计算属性合并默认文案——filter 默认为 <a href="https://link.gitcode.com/i/163596cdeb8f0f9a83d8fec81310be4e" target="_blank">'今日'],format 默认 'M月D日'(对应 [src/locale/lang/zh-CN.js 中的 today 与 formatDate 词条,英文环境则为 ['Today'] 与 'M-D')。
自定义时间格式:format
从 1.10.0 版本起,可以通过 format 属性配置 select 事件第三个参数 formatedTime 的格式,默认值为 'YYYY/M/D hh:mm'。格式令牌由 src/common/lang/date.js 中的 formatDate 实现,支持:Y(年)、M(月)、D(日)、h(时)、m(分)、s(秒)、q(季度)、S(毫秒),重复次数决定补零行为(如 hh 输出两位小时)。
<cube-button @click="showFormatPicker">Config format</cube-button>
export default {
methods: {
showFormatPicker() {
if (!this.formatPicker) {
this.formatPicker = this.$createTimePicker({
format: 'hh:mm',
onSelect: this.selectHandler,
onCancel: this.cancelHandler
})
}
this.formatPicker.show()
},
selectHandler(selectedTime, selectedText, formatedTime) {
this.$createDialog({
type: 'warn',
title: `selected time: ${selectedTime}`,
content: `selected text: ${selectedText}<br>format time: ${formatedTime}`,
icon: 'cubeic-alert'
}).show()
},
cancelHandler() {
this.$createToast({
type: 'correct',
txt: 'Picker canceled',
time: 1000
}).show()
}
}
}
这个示例同时演示了另一种推荐实践:将 picker 实例缓存到 this.formatPicker,只创建一次,之后通过 .show() 反复复用,避免重复实例化带来的开销。
限制可选时间范围:min 与 max
从 1.12.6 版本起,TimePicker 支持显式设置可选时间的最小值与最大值,两者均可接受 Date 类型或时间戳(Number 类型)。
设置最小可选时间 min:
<cube-button @click="showMinPicker">Config min</cube-button>
export default {
methods: {
showMinPicker() {
if (!this.minPicker) {
this.minPicker = this.$createTimePicker({
min: +new Date() - (2 * 60 + 20) * 60 * 1000, // 当前时间往前 2 小时 20 分钟
onSelect: this.selectHandler,
onCancel: this.cancelHandler
})
}
this.minPicker.show()
},
selectHandler(selectedTime, selectedText, formatedTime) {
this.$createDialog({
type: 'warn',
title: `selected time: ${selectedTime}`,
content: `selected text: ${selectedText}<br>format time: ${formatedTime}`,
icon: 'cubeic-alert'
}).show()
},
cancelHandler() {
this.$createToast({
type: 'correct',
txt: 'Picker canceled',
time: 1000
}).show()
}
}
}
设置最大可选时间 max(同时把 delay 置为 0,避免与 min 逻辑叠加干扰):
<cube-button @click="showMaxPicker">Config max</cube-button>
export default {
methods: {
showMaxPicker() {
if (!this.maxPicker) {
this.maxPicker = this.$createTimePicker({
delay: 0,
max: +new Date() + ((2 * 24 + 2) * 60 + 20) * 60 * 1000, // 当前时间往后 2 天 2 小时 20 分钟
onSelect: this.selectHandler,
onCancel: this.cancelHandler
})
}
this.maxPicker.show()
},
selectHandler(selectedTime, selectedText, formatedTime) {
this.$createDialog({
type: 'warn',
title: `selected time: ${selectedTime}`,
content: `selected text: ${selectedText}<br>format time: ${formatedTime}`,
icon: 'cubeic-alert'
}).show()
},
cancelHandler() {
this.$createToast({
type: 'correct',
txt: 'Picker canceled',
time: 1000
}).show()
}
}
}
设置 min/max 后需要了解两个联动行为(可从 src/components/time-picker/time-picker.vue 的 minTime/maxTime/days 计算属性确认):
min优先于delay:minTime取+this.min || +this.now + this.delay * MINUTE_TIMESTAMP,一旦设置了min,delay即失效;max决定日期列长度:days的长度在设置max时变为getDayDiff(this.maxTime, this.minTime) + 1,此时day.len不再决定列数,而是由min/max之间的天数差决定;- 若
maxTime比minTime小超过 1 分钟(maxTime - minTime <= -60000),组件会调用warn输出「The max is smaller than the min optional time.」警告并返回空数据。
另外,minTime 与 maxTime 的分钟值都会向 minuteStep 对齐:min 的分钟向上取整(规则见下文 minuteStep 的 rule),max 的分钟向下取整,从而保证首尾列内没有「不可选」的间隙选项。
手动设置时间:setTime
除了通过滚轮选择,还可以用实例方法 setTime 手动指定选择器展示的时间,参数为时间戳。下面这个示例把时间设到「下一个整点之后 1 小时」,同时演示了 day 三种配置的完整组合:
<cube-button @click="showTimePicker">TimePicker - setTime(next hour)</cube-button>
export default {
methods: {
showTimePicker () {
const time = new Date().valueOf() + 1 * 60 * 60 * 1000 // 下一小时
const timePicker = this.$createTimePicker({
showNow: true,
minuteStep: 10,
delay: 15,
day: {
len: 5,
filter: ['Today', 'Tomorrow', 'The day after tomorrow'],
format: 'M year D day'
},
onSelect: (selectedTime, selectedText, formatedTime) => {
this.$createDialog({
type: 'warn',
title: `selected time: ${selectedTime}`,
content: `selected text: ${selectedText}<br>format time: ${formatedTime}`,
icon: 'cubeic-alert'
}).show()
},
onCancel: () => {
this.$createToast({
type: 'correct',
txt: 'Clicked cancel button',
time: 1000
}).show()
}
})
timePicker.setTime(time)
timePicker.show()
}
}
}
关于 setTime 的行为,源码 _updateSelectedIndex 揭示了两个边界规则:
- 小于最小值时回退:若传入的时间戳小于当前最小可选时间,选择器默认回退到第一个可选项(
selectedIndex = [0, 0, 0]); - 超出范围时忽略:若传入时间对应的日期索引超出
days.length,setTime不会生效,并输出警告「Use "setTime" to set a time exceeded to the option range do not actually work.」。
这也解释了文档中「当时间戳小于当前时间戳时,timePicker 默认展示当前时间」的行为。
Props 配置总表
以下为 TimePicker 全部 Props,来自 document/components/docs/en-US/time-picker.md 并对照源码 src/components/time-picker/time-picker.vue 与 src/common/mixins/picker.js、src/common/mixins/popup.js 核实:
| Attribute | Description | Type | Default |
|---|---|---|---|
| day | date configuration | Object | { len: 3, filter: ['今日'], format: 'M月D日' } |
| showNow | whether to display now; configure the text of option now1.9.0 | Boolean, Object1.9.0 | true |
| minuteStep | step of the minute | Number | 10 |
| delay | minutes that postponed backwards from now, which determines the minimal optional time (only effect without setting min) |
Number | 15 |
| min1.12.6 | the min optional time | Date, Number | null |
| max1.12.6 | the max optional time | Date, Number | null |
| title | title | String | '选择时间' |
| subtitle1.8.1 | subtitle | String | '' |
| cancelTxt1.8.1 | the text of the cancel button | String | '取消' |
| confirmTxt1.8.1 | the text of the confirm button | String | '确定' |
| swipeTime | the duration of the momentum animation when user flicks the wheel of the picker, Unit: ms | Number | 2500 |
| visible1.8.1 | whether visible. Bind to v-model |
Boolean | false |
| maskClosable1.9.6 | whether hide the component when clicked the mask layer | Boolean | true |
| format1.10.0 | the format of formatedTime the third argument of select event | String | 'YYYY/M/D hh:mm' |
| zIndex1.9.6 | the value of the style z-index | Number | 100 |
需要补充的源码级细节:
title默认为空字符串,实际渲染时若为空则回退到 locale 词条selectTime(中文「选择时间」/ 英文 "Select time"),见 src/locale/lang/zh-CN.js;cancelTxt/confirmTxt同理,空值时回退到 locale 的cancel/ok;visible由 src/common/mixins/visibility.js 提供,支持v-model双向绑定;minuteStep源码中的类型是[Number, Object],除数字外还支持对象{ step: 5, rule: 'ceil' }形式,其中rule决定对最小可选时间分钟数的取整规则,取值floor/ceil/round(见 src/components/time-picker/time-picker.vue 的minuteStepRule计算属性与INT_RULE常量);showNow为对象时仅使用其text字段作为「现在」选项的文案,见下述子配置。
day 子配置
| Attribute | Description | Type | Default |
|---|---|---|---|
| len | date column, postpone len days backwards from now (only effect without setting max) |
Number | 3 |
| filter | date column, map time to the text in filter | Array | ['今日'] |
| format | format time | String | 'M月D日' |
showNow 子配置
| Attribute | Description | Type | Default |
|---|---|---|---|
| text1.9.0 | the text of option now | String | '现在' |
Events 事件总表
| Event Name | Description | Parameters 1 | Parameters 2 | Parameters 3 |
|---|---|---|---|---|
| select | triggers when clicking the confirm button | selectedTime: currently selected timestamp | selectText: text of currently selected time | formatedTime1.10.0 |
| change | triggers when the roller scrolls | index: Number, index of current scrolling roller | selectedIndex: Number, index of selected item in current column | - |
| cancel | triggers when clicking the cancel button | - | - | - |
在命令式用法中,这三个事件对应创建实例时传入的 onSelect、onChange、onCancel 回调。change 事件在任意一列滚动后触发,可用于实时感知用户正在浏览的列与选中项。
实例方法
| Method name | Description | Parameters |
|---|---|---|
| setTime | manually set time displayed in time-picker with the time stamp as time format | time stamp |
| show | show | - |
| hide | hide | - |
show / hide 由 visibility mixin 提供(见 src/common/mixins/visibility.js),它们与 v-model 绑定、点击遮罩关闭(maskClosable)、以及内部 isVisible 状态的 watch 通知(toggle 事件)共同构成完整的显隐控制链路。
源码原理:级联数据如何构建
理解 TimePicker 的底层机制,关键在于 src/components/time-picker/time-picker.vue 中 cascadeData 计算属性——它最终被透传给内部的 <cube-cascade-picker>(源码 data 绑定),从而驱动三列滚轮渲染。整个构建过程如下:
- 分钟列(minutes):从 0 开始按
minuteStep步进生成00、05、10…,文本用pad补零; - 小时列(hours):生成 0~23 共 24 个小时,每个小时挂载完整的
minutes作为其子级; - 日期列(days):以
minTime为起点,按天递增生成len(或由max计算出的天数)个日期项,文本优先取day.filter[dayDiff + i],取不到时回退到formatDate(timestamp, day.format); - 首尾裁剪:第一天的可选小时从
minTime.getHours()开始、最后一小时的小分钟从minTime分钟向上取整(对齐minuteStep)开始;最后一天的结束小时为maxTime.getHours()、分钟向下取整对齐,保证「超过范围的时间根本不出现在滚轮里」; - 插入「现在」:当
showNow为真且今天在可选范围内时,把{ value: 'now', text: nowText, children: [] }用unshift插入到当天小时列的最前面。
选中「确定」后,_pickerSelect 把三列值换算为时间戳:若第二列值是特殊标记 'now',直接取当前时刻 +new Date();否则用 getZeroStamp(日期列值) + 小时 * HOUR_TIMESTAMP + 分钟 * MINUTE_TIMESTAMP 计算,其中 getZeroStamp 返回当日零点时间戳(见 src/common/lang/date.js)。随后用 formatDate(new Date(timestamp), this.format) 生成 formatedTime 作为 select 事件第三个参数。
测试用例验证
仓库中 test/unit/specs/time-picker.spec.js 对上述行为做了系统性验证,可作为理解组件的补充材料:
- 渲染正确性:断言组件渲染出 3 个滚轮(
wheels.length === 3)、取消/确认按钮文案为「取消」「确定」、首列首项为「今日」、小时列首项为「现在」(showNow默认开启); - showNow 文案:传入
showNow: { text: 'now text' }后断言小时列首项文本变为'now text'; - 事件触发:用 spy 分别验证
select、cancel、change事件的触发次数与参数; - setTime 定位:
setTime(+nextDate)后断言滚轮滚动位移为-36px,且对应日期文本正确渲染为M月D日格式。
这些用例与本文前述的配置行为一一对应,如果你要基于 TimePicker 做二次封装或深度定制,建议先通读该测试文件确认边界行为。
小结
TimePicker 通过 cascade-picker 的级联滚轮封装出开箱即用的时间选择体验:day 控制日期列、minuteStep 控制分钟粒度、delay/min/max 控制可选范围、format 控制输出格式、setTime 支持编程式定位,配合 select/change/cancel 三个事件即可覆盖绝大多数移动端时间选择场景。理解了它内部「级联数据裁剪 + 时间戳换算」的构建方式后,你也可以在此基础上扩展出更复杂的业务时间选择器(例如结合 formatDate 自定义展示文案,或复用 minTime/maxTime 的对齐逻辑处理跨日时间段选择)。