cube-ui TimePicker 时间选择器完全指南:配置、事件与源码原理

原创2026-09-24 17:30:231,725 阅读
文章标签:前端UI组件移动开发

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 计算属性确认):

  1. min 优先于 delay:minTime 取 +this.min || +this.now + this.delay * MINUTE_TIMESTAMP,一旦设置了 min,delay 即失效;
  2. max 决定日期列长度:days 的长度在设置 max 时变为 getDayDiff(this.maxTime, this.minTime) + 1,此时 day.len 不再决定列数,而是由 min/max 之间的天数差决定;
  3. 若 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 绑定),从而驱动三列滚轮渲染。整个构建过程如下:

  1. 分钟列(minutes):从 0 开始按 minuteStep 步进生成 00、05、10…,文本用 pad 补零;
  2. 小时列(hours):生成 0~23 共 24 个小时,每个小时挂载完整的 minutes 作为其子级;
  3. 日期列(days):以 minTime 为起点,按天递增生成 len(或由 max 计算出的天数)个日期项,文本优先取 day.filter[dayDiff + i],取不到时回退到 formatDate(timestamp, day.format);
  4. 首尾裁剪:第一天的可选小时从 minTime.getHours() 开始、最后一小时的小分钟从 minTime 分钟向上取整(对齐 minuteStep)开始;最后一天的结束小时为 maxTime.getHours()、分钟向下取整对齐,保证「超过范围的时间根本不出现在滚轮里」;
  5. 插入「现在」:当 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 的对齐逻辑处理跨日时间段选择)。

登录后查看全文
cube-ui