☰
cube-ui TimePicker 时间选择器完全指南:从 API 配置到源码级原理
2026/9/25 5:48:25 网站建设 项目流程
  • 前端
  • UI组件
  • 移动开发

【免费下载链接】cube-ui

:large_orange_diamond: A fantastic mobile ui lib implement by Vue

项目地址:https://gitcode.com/gh_mirrors/cu/cube-ui
点击查看免费下载

TimePicker是 cube-ui 中基于 Picker/CascadePicker 封装的三列联动时间选择组件(日期 + 小时 + 分钟),面向移动端场景提供“日期列 + 时间列”的级联滚动选择能力,并支持"现在"快捷项、分钟步长、最小/最大可选时间边界以及手动置位等能力。阅读本文后,你将掌握$createTimePicker的全部配置项、事件与实例方法,并能结合源码理解其列数据生成与取整规则背后的实现原理。

TimePicker组件提供了常用的日期选择功能。由于该组件基于 create-api 实现,因此在正式使用之前,请确保先了解 create-api 的用法——正是通过$createTimePicker这一命令式 API,我们才能像调用函数一样快捷地创建并弹出时间选择器。

基本用法:命令式创建与事件回调

TimePicker通过$createTimePicker方法创建,调用后返回实例,再调用实例的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: 'Picker canceled', time: 1000 }).show() } }).show() } } }
  • showNow用于控制是否显示"现在"时间选项(默认true);
  • minuteStep用于控制分钟的步长,例如设为 5 时,分钟列只出现 0、5、10、15…… ;
  • delay表示当前时间向后推迟的分钟数,它决定了最小可选时间(默认 15 分钟,即默认最早可选 15 分钟之后的时间)。

从源码看,showNow、minuteStep、delay等配置会被传入 time-picker.vue 中定义的同名 props,组件内部再把这些配置转化为级联列数据(cascadeData),交给底层的cube-cascade-picker渲染。这也是 TimePicker 模板部分仅有一个<cube-cascade-picker>元素的原因:

<cube-cascade-picker ref="picker" v-model="isVisible" :data="cascadeData" :selected-index="selectedIndex" :title="_title" :subtitle="subtitle" :cancel-txt="_cancelTxt" :confirm-txt="_confirmTxt" :swipe-time="swipeTime" :z-index="zIndex" :mask-closable="maskClosable" @select="_pickerSelect" @cancel="_pickerCancel" @change="_pickerChange"> </cube-cascade-picker>

日期选项配置:day 的 len / filter / format

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: ['今天', '明天'], format: 'M月d日' }, 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: 'Picker canceled', time: 1000 }).show() } }).show() } } }
  • len:设置日期列需要展示的日期长度(从当前时间算起往后推的天数,默认 3;注:仅当未设置max时有效);
  • filter:设置日期列展示的文案,将日期映射为数组中的文案内容,例如['今天', '明天'];
  • format:格式化日期显示的方式,例如'M月d日'。当len的数量大于filter数组长度时,超出部分会按format的格式显示文案。

结合 time-picker.vue 的days计算属性可以看到具体实现:组件从minTime开始逐天生成时间戳,文案优先取filter[dayDiff + i](即 filter 数组中对应天数的文案),取不到时才回退到formatDate(new Date(timestamp), this._day.format):

days() { const days = [] const dayDiff = getDayDiff(this.minTime, this.now) const len = this.max ? getDayDiff(this.maxTime, this.minTime) + 1 : this._day.len for (let i = 0; i < len; i++) { const timestamp = +this.minTime + i * DAY_TIMESTAMP days.push({ value: timestamp, text: (this._day.filter && this._day.filter[dayDiff + i]) || formatDate(new Date(timestamp), this._day.format) }) } return days }

注意day的默认值定义在 time-picker.vue 中只有{ len: 3 },而filter(默认['今日'])与format(默认'M月D日')来自 locale 多语言配置,参见 _day 计算属性 与 zh-CN locale 文件。

配置 select 事件的格式化时间:format(1.10.0+)

通过format属性可以配置select事件第三个参数formatedTime的格式,默认值为'YYYY/M/D hh:mm':

<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() } } }

format支持的时间占位符由 src/common/lang/date.js 中的formatDate函数解析,包括:Y(年)、M(月)、D(日)、h(小时)、m(分钟)、s(秒)、q(季度)、S(毫秒)。占位符重复出现时(如hh)会自动补零,例如 9 点 5 分按'hh:mm'格式化得到"09:05"。这一格式化能力同时被组件的 select 事件与测试用例复用,参见 time-picker.spec.js 中 format 相关断言。

分钟步长 minuteStep:数字与对象两种形态

通过minuteStep属性可配置分钟数的步长,默认为 10 分钟。此时可选的分钟为 10、20、30、40、50。

在 v1.10.5+ 中,minuteStep还支持传入一个对象,通过子属性rule配置取整规则(ceil向上取整、floor向下取整、round四舍五入),子属性step表示步长:

<cube-button @click="showMinuteStepPicker">Config minute step</cube-button>
export default { methods: { showFormatPicker() { if (!this.minuteStepPicker) { this.minuteStepPicker = this.$createTimePicker({ minuteStep: { rule: 'ceil', step: 15 }, onSelect: this.selectHandler, onCancel: this.cancelHandler }) } this.minuteStepPicker.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() } } }

minuteStep对象形态的rule仅用于最小可选时间的取整;对于最大时间,组件固定使用floor规则。相关实现位于 minuteStepRule / minuteStepNumber 计算属性:

minuteStepRule() { const minuteStep = this.minuteStep return (typeof minuteStep === 'object' && Math[INT_RULE[minuteStep.rule]]) || Math[INT_RULE.floor] }, minuteStepNumber() { const minuteStep = this.minuteStep return typeof minuteStep === 'number' ? minuteStep : (minuteStep.step || DEFAULT_STEP) }

其中INT_RULE = { floor: 'floor', ceil: 'ceil', round: 'round' },DEFAULT_STEP = 10定义在组件文件顶部(time-picker.vue 第 46-52 行)。测试用例 testMinuteStep 覆盖了数字、{rule: 'ceil'}、{step: 15}、{rule: 'floor', step: 5}、{rule: 'round', step: 10}等多种配置,验证列数据首项与Mathrule * step一致。

最小可选时间 min(1.12.6+)

通过min属性可设置最小可选时间。它既可以接受Date类型的日期时间,也可以接受Number类型的时间戳:

<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, 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() } } }

上例中min被设置为"当前时间往前 2 小时 20 分钟"的时间戳,即允许用户选择过去 2 小时 20 分钟以内的任意时间点。

从源码看,minTime 计算属性 的优先级是:+this.min || +this.now + this.delay * MINUTE_TIMESTAMP——即设置min后,delay将不再生效(文档 Props 表中delay也注明了"仅当未设置min时有效")。同时,minTime会按minuteStepRule对分钟取整,例如默认floor规则下 10 点 37 分会被对齐到 10 点 30 分:

minTime() { let minTimeStamp = +this.min || +this.now + this.delay * MINUTE_TIMESTAMP // Handle the minTime selectable change caused by minute step. const minute = new Date(minTimeStamp).getMinutes() const intMinute = Math.min(this.minuteStepRule(minute / this.minuteStepNumber) * this.minuteStepNumber, 60) minTimeStamp += (intMinute - minute) * MINUTE_TIMESTAMP return new Date(minTimeStamp) }

对应的边界测试见 testMin 用例,其验证了min为 null 以及正负多种时间偏移时,日期列长度始终等于getDayDiff(vm.maxTime, vm.minTime) + 1。

最大可选时间 max(1.12.6+)

通过max属性可设置最大可选时间,同样支持Date类型或Number类型时间戳:

<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, 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() } } }

上例中max为"当前时间往后 2 天 2 小时 20 分钟",同时delay: 0使最小可选时间即为当前时刻。max的设置同样会让day.len失效——days 计算属性 中const len = this.max ? getDayDiff(this.maxTime, this.minTime) + 1 : this._day.len,即日期列长度改由min与max的跨度决定。

maxTime 计算属性 的默认值逻辑是"minTime 当天之后_day.len天的零点再减 1 毫秒";并且对最大时间固定使用floor取整分钟:

maxTime() { let maxTimeStamp = +this.max || (getZeroStamp(new Date(+this.minTime + this._day.len * DAY_TIMESTAMP)) - 1) const minute = new Date(maxTimeStamp).getMinutes() const intMinute = Math.floor(minute / this.minuteStepNumber) * this.minuteStepNumber maxTimeStamp -= (minute - intMinute) * MINUTE_TIMESTAMP return new Date(maxTimeStamp) }

一个值得注意的边界:当maxTime比minTime小超过一个分钟步长(源码判定阈值为-60000即 1 分钟)时,cascadeData会返回空数组并输出警告 "The max is smaller than the min optional time.",对应逻辑见 cascadeData 计算属性。相关测试见 testMax 用例。

手动设置时间:setTime 实例方法

timePicker实例向外暴露setTime方法,用于手动设置组件显示的时间,参数为时间戳。当时间戳小于当前时间戳时,实例会默认显示当前时间:

<cube-button @click="showTimePicker">TimePicker - setTime(next hour)</cube-button>
export default { methods: { const time = new Date().valueOf() + 1 * 60 * 60 * 1000 showTimePicker () { const timePicker = this.$createTimePicker({ showNow: true, minuteStep: 10, delay: 15, day: { len: 5, filter: ['今天', '明天', '后天'], format: 'M月D日' }, 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: 'Picker canceled', time: 1000 }).show() } }) timePicker.setTime(time) timePicker.show() } } }

setTime的源码实现见 time-picker.vue 第 262-266 行:它把时间戳存入内部value,若组件当前已可见则立即更新selectedIndex。更完整的索引换算逻辑在 _updateSelectedIndex:按"天数索引 + 小时索引 + 分钟索引"换算,且当目标时间超出可选范围时会警告 "Use "setTime" to set a time exceeded to the option range do not actually work."。注意当传入时间早于minTime时,组件会回退到第一列第一项[0, 0, 0];测试用例还验证了通过setTime将选中时间切到次日时滚轮位移与文案的正确性(time-picker.spec.js 第 50-64 行)。

Props 配置总览

| 参数 | 说明 | 类型 | 默认值 | | - | - | - | - | | day | 日期配置 | Object | { len: 3, filter: ['今日'], format: 'M月D日' } | | showNow | 是否显示现在;以及现在选项的文案(1.9.0+ 支持 Object) | Boolean, Object(1.9.0+) | true | | minuteStep | 分钟数的步长。为 Object 时可配置取整规则,详见下方minuteStep子配置项(1.10.5+) | Number, Object(1.10.5+) | 10 | | delay | 将当前时间向后推算的分钟数,决定最小可选时间(注:仅当未设置min时有效) | Number | 15 | | min(1.12.6+) | 最小可选时间 | Date, Number | null | | max(1.12.6+) | 最大可选时间 | Date, Number | null | | title | 标题 | String | '选择时间' | | subtitle(1.8.1+) | 副标题 | String | '' | | cancelTxt(1.8.1+) | 取消按钮文案 | String | '取消' | | confirmTxt(1.8.1+) | 确定按钮文案 | String | '确定' | | swipeTime | 快速滑动选择器滚轮时,惯性滚动动画的时长,单位:ms | Number | 2500 | | visible(1.8.1+) | 显示状态,是否可见,v-model绑定值 | Boolean | false | | maskClosable(1.9.6+) | 点击蒙层是否隐藏 | Boolean | true | | format(1.10.0+) | select 事件参数 formatedTime 的格式 | String | 'YYYY/M/D hh:mm' | | zIndex(1.9.6+) | 样式 z-index 的值 | Number | 100 |

其中title、subtitle、cancelTxt、confirmTxt、swipeTime、maskClosable的定义可追溯至 picker mixin,cancelTxt/confirmTxt为空时回退到 locale 文案(_cancelTxt、_confirmTxt);title为空时则回退到selectTime(中文为"选择时间"),参见 time-picker.vue 第 105-108 行。

day 子配置项

| 参数 | 说明 | 类型 | 默认值 | | - | - | - | - | | len | 日期列,从当前时间算起,往后推 len 天(注:仅当未设置max时有效) | Number | 3 | | filter | 日期列,将时间映射为 filter 中的文案内容 | Array | ['今日'] | | format | 时间格式化 | String | 'M月D日' |

showNow 子配置项(1.9.0+)

当showNow传入对象时,可配置"现在"选项的文案:

| 参数 | 说明 | 类型 | 默认值 | | - | - | - | - | | text | 现在选项的文案 | String | '现在' |

实现上,nowText 计算属性 优先取this.showNow.text,否则回退到 locale 默认值;"现在"选项会以{ value: 'now', text: this.nowText }的形式被unshift到当日小时列的首位(cascadeData 中 showNow 处理逻辑),并且仅当"今天"在可选范围内(dayDiff <= 0)时才会插入。测试用例分别覆盖了showNow: false、showNow: { text: 'now text' }两种形态(time-picker.spec.js 第 69-118 行)。

minuteStep 子配置项(1.10.5+)

| 参数 | 说明 | 类型 | 可选值 | 默认值 | | - | - | - | - | - | | rule | 取整的规则(仅用于设置最小可选时间的取整规则,对于最大时间,固定为 floor) | String | floor / ceil / round | 'floor' | | step | 分钟数的步长 | Number | - | 10 |

事件

| 事件名 | 说明 | 参数1 | 参数2 | 参数3 | | - | - | - | - | - | | select | 点击确认按钮触发此事件 | selectedTime:当前选中的 timestamp | selectText:当前选中的时间文案 | formatedTime:格式化日期(1.10.0+) | | change | 滚轴滚动后触发此事件 | index:当前滚动列次序,Number 类型 | selectedIndex:当前列选中项的索引,Number 类型 | - | | cancel | 点击取消按钮触发此事件 | - | - | - |

select事件的触发逻辑见 _pickerSelect:当选中的是第一列的"现在"(selectedVal[1] === NOW.value)时,timestamp取+new Date()的当前时刻;否则由日期零刻时间戳 + 小时 + 分钟合成timestamp,text形如"今日 10:30",再经formatDate(new Date(timestamp), this.format)生成formatedTime并随事件抛出。change、cancel则分别由_pickerChange、_pickerCancel透传底层 cascade-picker 的事件(time-picker.vue 第 308-328 行)。

三个事件名['select', 'cancel', 'change']同时被注册进 create-api,见 src/modules/time-picker/api.js;完整的类型定义(含onSelect/onCancel/onChange回调签名)可查阅 types/components/TimePicker.ts。

实例方法

| 方法名 | 说明 | 参数 | | - | - | - | | setTime | 手动设置 time-picker 组件显示的时间,数据格式为时间戳 | 时间戳 | | show | 显示 | - | | hide | 隐藏 | - |

从源码理解组件架构与可选范围推导

综合上述内容可以梳理出 TimePicker 的完整工作链路:

  1. 命令式入口:Vue.use(TimePicker)时,src/modules/time-picker/index.js 会注册cube-picker、cube-time-picker两个组件,并分别调用addPicker、addTimePicker注入$createPicker与$createTimePickerAPI;$createTimePicker通过createAPI(基于vue-create-api,见 src/common/helpers/create-api.js)生成,api.before中会提示 TimePicker 不支持单例模式(single传 true 时输出警告,对应 api.js 第 6-10 行 与测试用例)。

  2. 列数据生成:组件依据min/delay推导minTime,依据max/day.len推导maxTime,再据此生成"日期列(days)→ 小时列(hours)→ 分钟列(minutes)"的三级cascadeData。日期列基于 src/common/lang/date.js 的getZeroStamp/getDayDiff计算天数差;小时列对首尾两天做裁剪(首日从minTime.getHours()起、末日到maxTime.getHours()止);分钟列按minuteStepNumber遍历 0~59(time-picker.vue 第 161-181 行)。

  3. 级联渲染与交互:最终数据交给cube-cascade-picker完成三列滚轮渲染、惯性滑动(swipeTime控制惯性时长)以及select/change/cancel事件的上抛。

  4. 国际化:标题、按钮文案、now/today文案及formatDate默认值均接入 locale,中文默认值见 src/locale/lang/zh-CN.js。

  5. 可复现的示例:完整的可运行示例位于 example/pages/time-picker.vue,覆盖基本用法、day 配置、format、minuteStep、min、max、setTime 七种场景;单元测试见 test/unit/specs/time-picker.spec.js,可作为理解各配置项行为边界的第一手资料。

实践建议

  • 预约类业务:用delay(分钟)限制最早可选时间,或用min/max(时间戳或 Date)精确限定可选区间,两者可组合使用但注意delay在设置min后失效;
  • 快递/配送时效:配合day.len限定未来 N 天,并借助day.filter(如['今天', '明天'])+day.format(如'M月d日')定制日期文案,注意len在设置max后失效;
  • 高频操作:用format: 'hh:mm'等格式定制select事件回传的formatedTime,便于直接渲染或提交;
  • 业务既有选择回显:组件实例创建后可调用setTime(timestamp)定位到指定时间;需注意超出可选范围的时间会被忽略并输出警告,早于最小可选时间的值会回退到首项;
  • 自定义文案与样式:通过title/subtitle/cancelTxt/confirmTxt覆盖按钮与标题,通过zIndex控制层级,maskClosable: false可禁止点击蒙层关闭;
  • 分钟粒度控制:按需选择minuteStep数字(如 5、15、30)或对象形态{ rule: 'ceil', step: 15 },其中rule只影响最小可选时间的对齐,最大时间恒为floor对齐。
  • 前端
  • UI组件
  • 移动开发

【免费下载链接】cube-ui

:large_orange_diamond: A fantastic mobile ui lib implement by Vue

项目地址:https://gitcode.com/gh_mirrors/cu/cube-ui
点击查看免费下载

相关推荐

上一篇:如何配置Bruno实现API测试会话持久化:告别重复登录的终极指南
下一篇:标题:突出核心价值(如"IT求职一站式知识库")

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询