TimePicker 时间选择 - Vant 4
TimePicker 时间选择器 ⏰
介绍
精准掌控每一分每一秒!TimePicker 是一个功能强大的时间选择器组件,支持时、分、秒的灵活选择。无论是设置闹钟、预约时间还是记录时长,都能为用户提供流畅直观的时间选择体验。✨
⏰ 时光魔法师 - 精准捕捉每一个时刻!TimePicker 时间选择器如同一位贴心的时间管家,让用户轻松穿梭在时光隧道中,精准定位到心仪的时刻。通常与弹出层组件完美搭档,为用户呈现优雅的时间选择体验。
引入
通过以下方式来全局注册组件,更多注册方式请参考组件注册。
import { createApp } from 'vue';
import { TimePicker } from 'vant';
const app = createApp();
app.use(TimePicker);代码演示
🎯 基础用法 - 时间之舞的第一步
轻松选择时间,就像转动时钟指针一样简单!通过滑动操作即可快速定位到目标时间。
<template>
<van-time-picker
v-model="currentTime"
title="选择时间"
@confirm="onConfirm"
@cancel="onCancel"
/>
</template>import { ref } from 'vue';
import { showToast } from 'vant';
export default {
setup() {
const currentTime = ref(['12', '00']);
const onConfirm = ({ selectedValues }) => {
showToast(`已选择时间:${selectedValues.join(':')}`);
};
const onCancel = () => {
showToast('取消选择');
};
return {
currentTime,
onConfirm,
onCancel
};
},
};🎨 选项类型 - 时间积木的自由拼搭
时间也能像积木一样自由组合!通过 columns-type 这个神奇的调色板,你可以随心所欲地控制时间选项的类型,支持对 hour(小时)、minute(分钟)和 second(秒)进行任意顺序的排列组合,打造专属的时间选择器。
比如:
- 传入
['hour']来单独选择小时。 - 传入
['minute']来单独选择分钟。 - 传入
['minute', 'second']来选择分钟和秒。 - 传入
['hour', 'minute', 'second']来选择小时、分钟和秒。
<!-- 选择时分 - 适合日常时间设置 -->
<van-time-picker
v-model="currentTime"
title="选择时间"
:columns-type="['hour', 'minute']"
@confirm="onTimeConfirm"
/>
<!-- 选择时分秒 - 精确到秒的时间控制 -->
<van-time-picker
v-model="preciseTime"
title="精确时间"
:columns-type="['hour', 'minute', 'second']"
@confirm="onPreciseTimeConfirm"
/>
<!-- 选择分秒 - 适合倒计时或计时器场景 -->
<van-time-picker
v-model="durationTime"
title="设置时长"
:columns-type="['minute', 'second']"
@confirm="onDurationConfirm"
/>import { ref } from 'vue';
export default {
setup() {
const currentTime = ref(['12', '00']);
const preciseTime = ref(['12', '00', '00']);
const durationTime = ref(['05', '30']);
const onTimeConfirm = ({ selectedValues }) => {
console.log('选择的时间:', selectedValues.join(':'));
};
const onPreciseTimeConfirm = ({ selectedValues }) => {
console.log('精确时间:', selectedValues.join(':'));
};
const onDurationConfirm = ({ selectedValues }) => {
console.log('设置时长:', `${selectedValues[0]}分${selectedValues[1]}秒`);
};
return {
currentTime,
preciseTime,
durationTime,
onTimeConfirm,
onPreciseTimeConfirm,
onDurationConfirm
};
}
};🚧 时间范围 - 为时光设置边界
给时间画个圈圈!你可以使用 min-hour 和 max-hour 等属性这些贴心的守护者,为小时(hour)、分钟(minute)和秒(second)设置专属的活动范围,让时间选择更加精准有序。
比如以下示例,用户可以选择的小时是 10 ~ 20 ,分钟是 30 ~ 40。
<template>
<!-- 限制小时和分钟范围 -->
<van-time-picker
v-model="currentTime"
title="选择时间"
:min-hour="10"
:max-hour="20"
:min-minute="30"
:max-minute="40"
@confirm="onConfirm"
/>
<!-- 午餐时间选择器 (11:30-14:00) -->
<van-time-picker
v-model="lunchTime"
title="选择午餐时间"
:min-hour="11"
:max-hour="14"
:min-minute="30"
@confirm="onLunchTimeConfirm"
/>
</template>import { ref } from 'vue';
import { showToast } from 'vant';
export default {
setup() {
const currentTime = ref(['12', '35']);
const lunchTime = ref(['12', '30']);
const onConfirm = ({ selectedValues }) => {
showToast(`已选择时间:${selectedValues.join(':')}`);
};
const onLunchTimeConfirm = ({ selectedValues }) => {
showToast(`午餐时间:${selectedValues.join(':')}`);
};
return {
currentTime,
lunchTime,
onConfirm,
onLunchTimeConfirm
};
},
};
### 🌍 整体时间范围 - 时光的完整疆域
一网打尽,整体把控!你可以使用 `min-time` 和 `max-time` 属性这对黄金搭档来限制整体时间范围,遵循 `10:00:00` 这个优雅的时间格式约定,让时间选择更加统一协调。
- 设置 `min-time` 后,`min-hour`、`min-minute`、`min-second` 属性将不会生效。
- 设置 `max-time` 后,`max-hour`、`max-minute`、`max-second` 属性将不会生效。
比如以下示例,用户可以选择从 `09:40:10` 到 `20:20:50` 的任意时间。
```htmlimport { ref } from'vue'; exportdefault { setup() { const currentTime = ref(['12', '00', '00']); return { currentTime }; }, };✨ 格式化选项 - 时间文字的华丽变身
让时间穿上华丽的外衣!通过传入 formatter 这个神奇的化妆师函数,可以对选项的文字进行精美的格式化处理,让每个时间单位都闪闪发光,展现独特的魅力。
<template>
<!-- 中文时间格式 -->
<van-time-picker
v-model="chineseTime"
title="选择时间"
:formatter="chineseFormatter"
@confirm="onChineseTimeConfirm"
/>
<!-- 12小时制格式 -->
<van-time-picker
v-model="ampmTime"
title="选择时间"
:formatter="ampmFormatter"
@confirm="onAmpmTimeConfirm"
/>
</template>import { ref } from 'vue';
import { showToast } from 'vant';
export default {
setup() {
const chineseTime = ref(['12', '00']);
const ampmTime = ref(['12', '00']);
// 中文格式化器
const chineseFormatter = (type, option) => {
if (type === 'hour') {
option.text += '时';
}
if (type === 'minute') {
option.text += '分';
}
if (type === 'second') {
option.text += '秒';
}
return option;
};
// 12小时制格式化器
const ampmFormatter = (type, option) => {
if (type === 'hour') {
const hour = parseInt(option.value);
if (hour === 0) {
option.text = '12 AM';
} else if (hour < 12) {
option.text = `${hour} AM`;
} else if (hour === 12) {
option.text = '12 PM';
} else {
option.text = `${hour - 12} PM`;
}
}
if (type === 'minute') {
option.text += ' min';
}
return option;
};
const onChineseTimeConfirm = ({ selectedValues }) => {
showToast(`中文时间:${selectedValues[0]}时${selectedValues[1]}分`);
};
const onAmpmTimeConfirm = ({ selectedValues }) => {
showToast(`12小时制:${selectedValues.join(':')}`);
};
return {
chineseTime,
ampmTime,
chineseFormatter,
ampmFormatter,
onChineseTimeConfirm,
onAmpmTimeConfirm
};
},
};🔍 过滤选项 - 时间的精准筛选
时间也需要精挑细选!通过传入 filter 这个智能筛选器函数,可以对选项数组进行精准过滤,优雅地剔除不需要的时间选项,轻松实现自定义时间间隔,让每个时刻都恰到好处。
<template>
<!-- 5分钟间隔选择器 -->
<van-time-picker
v-model="intervalTime"
title="选择时间(5分钟间隔)"
:filter="intervalFilter"
@confirm="onIntervalTimeConfirm"
/>
<!-- 工作时间过滤器 -->
<van-time-picker
v-model="workTime"
title="选择工作时间"
:filter="workTimeFilter"
@confirm="onWorkTimeConfirm"
/>
</template>import { ref } from 'vue';
import { showToast } from 'vant';
export default {
setup() {
const intervalTime = ref(['12', '00']);
const workTime = ref(['09', '00']);
// 5分钟间隔过滤器
const intervalFilter = (type, options) => {
if (type === 'minute') {
return options.filter((option) => Number(option.value) % 5 === 0);
}
return options;
};
// 工作时间过滤器
const workTimeFilter = (type, options) => {
if (type === 'hour') {
// 只显示工作时间 9:00-18:00
return options.filter((option) => {
const hour = Number(option.value);
return hour >= 9 && hour <= 18;
});
}
if (type === 'minute') {
// 只显示整点和半点
return options.filter((option) => {
const minute = Number(option.value);
return minute === 0 || minute === 30;
});
}
return options;
};
const onIntervalTimeConfirm = ({ selectedValues }) => {
showToast(`间隔时间:${selectedValues.join(':')}`);
};
const onWorkTimeConfirm = ({ selectedValues }) => {
showToast(`工作时间:${selectedValues.join(':')}`);
};
return {
intervalTime,
workTime,
intervalFilter,
workTimeFilter,
onIntervalTimeConfirm,
onWorkTimeConfirm
};
},
};🚀 高级用法 - 时间筛选的终极奥义
解锁时间筛选的终极技能!filter 函数的第三个参数就像一个时间侦探,能够敏锐地获取到当前选择的时间状态。这在使用非受控模式时,为你提供了更加灵活智能的过滤能力,让不需要的时间选项无所遁形。
exportdefault { setup() { constfilter = (type, options, values) => { const hour = +values[0]; if (type === 'hour') { return options.filter( (option) =>Number(option.value) >= 8 && Number(option.value) <= 18, ); } if (type === 'minute') { options = options.filter((option) =>Number(option.value) % 10 === 0); if (hour === 8) { return options.filter((option) =>Number(option.value) >= 40); } if (hour === 18) { return options.filter((option) =>Number(option.value) <= 20); } } return options; }; return { filter, }; }, };API
Props
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| v-model | 🎯 当前选中的时间,时间数组格式 | string[] | - |
| columns-type | ⚙️ 选项类型,由 hour、minute 和 second 组成的数组 | string[] | ['hour', 'minute'] |
| min-hour | 🌅 可选的最小小时,限制时间下限 | number | string | 0 |
| max-hour | 🌇 可选的最大小时,限制时间上限 | number | string | 23 |
| min-minute | ⏰ 可选的最小分钟,精确控制分钟范围 | number | string | 0 |
| max-minute | ⏰ 可选的最大分钟,精确控制分钟范围 | number | string | 59 |
| min-second | ⏱️ 可选的最小秒数,精确到秒级控制 | number | string | 0 |
| max-second | ⏱️ 可选的最大秒数,精确到秒级控制 | number | string | 59 |
min-time v4.5.0 | 🚫 可选的最小时间,格式参考 07:40:00,使用时 min-hour``min-minute``min-second 不会生效 | string | - |
max-time v4.5.0 | 🚫 可选的最大时间,格式参考 10:20:00,使用时 max-hour``max-minute``max-second 不会生效 | string | - |
| title | 📝 顶部栏标题,为选择器添加说明 | string | '' |
| confirm-button-text | ✅ 确认按钮文字,自定义确认操作文案 | string | 确认 |
| cancel-button-text | ❌ 取消按钮文字,自定义取消操作文案 | string | 取消 |
| show-toolbar | 🔧 是否显示顶部栏,控制工具栏可见性 | boolean | true |
| loading | ⏳ 是否显示加载状态,异步数据加载时使用 | boolean | false |
| readonly | 🔒 是否为只读状态,只读状态下无法切换选项 | boolean | false |
| filter | 🔍 选项过滤函数,自定义选项显示逻辑 | (type: string, options: PickerOption[], values: string[]) => PickerOption[] | - |
| formatter | 🎨 选项格式化函数,自定义选项显示格式 | (type: string, option: PickerOption) => PickerOption | - |
| option-height | 📏 选项高度,支持 px``vw``vh``rem 单位,默认 px | number | string | 44 |
| visible-option-num | 👁️ 可见的选项个数,控制显示区域大小 | number | string | 6 |
| swipe-duration | 🏃 快速滑动时惯性滚动的时长,单位 ms | number | string | 1000 |
Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| confirm | ✅ 点击完成按钮时触发,确认时间选择 | { selectedValues, selectedOptions, selectedIndexes } |
| cancel | ❌ 点击取消按钮时触发,取消时间选择 | { selectedValues, selectedOptions, selectedIndexes } |
| change | 🔄 选项改变时触发,实时响应用户操作 | { selectedValues, selectedOptions, selectedIndexes, columnIndex } |
Slots
| 名称 | 说明 | 参数 |
|---|---|---|
| toolbar | 🔧 自定义整个顶部栏的内容,完全控制工具栏样式 | - |
| title | 📝 自定义标题内容,个性化标题显示 | - |
| confirm | ✅ 自定义确认按钮内容,打造独特确认体验 | - |
| cancel | ❌ 自定义取消按钮内容,个性化取消操作 | - |
| option | 🎨 自定义选项内容,完全控制选项显示样式 | option: PickerOption, index: number |
| columns-top | ⬆️ 自定义选项上方内容,添加额外信息展示 | - |
| columns-bottom | ⬇️ 自定义选项下方内容,补充说明或操作区域 | - |
方法
通过 ref 可以获取到 Picker 实例并调用实例方法,详见组件实例方法。
| 方法名 | 说明 | 参数 | 返回值 |
|---|---|---|---|
| confirm | ✅ 停止惯性滚动并触发 confirm 事件,程序化确认选择 | - | - |
| getSelectedTime | 🎯 获取当前选中的时间,便于外部获取选择结果 | - | string[] |
类型定义 📝
完善的 TypeScript 支持,让开发更加安全可靠!组件导出以下类型定义:
import type { TimePickerProps, TimePickerColumnType } from 'vant';TimePickerInstance 是组件实例的类型,用法如下:
import { ref } from 'vue';
import type { TimePickerInstance } from 'vant';
const timePickerRef = ref<TimePickerInstance>();
// 程序化确认选择
timePickerRef.value?.confirm();
// 获取当前选中时间
const selectedTime = timePickerRef.value?.getSelectedTime();常见问题
在桌面端无法操作组件?
参见桌面端适配。
🎯 最佳实践
时间选择器与弹窗的完美结合
<template>
<van-cell title="选择时间" :value="timeText" @click="showPicker = true" />
<van-popup v-model:show="showPicker" position="bottom">
<van-time-picker
v-model="currentTime"
title="选择时间"
@confirm="onConfirm"
@cancel="showPicker = false"
/>
</van-popup>
</template>
<script>
import { ref, computed } from 'vue';
export default {
setup() {
const showPicker = ref(false);
const currentTime = ref(['12', '00']);
const timeText = computed(() => {
return currentTime.value.join(':');
});
const onConfirm = ({ selectedValues }) => {
currentTime.value = selectedValues;
showPicker.value = false;
};
return {
showPicker,
currentTime,
timeText,
onConfirm
};
}
};
</script>响应式时间范围设置
// 根据当前时间动态设置可选范围
const getCurrentTimeRange = () => {
const now = new Date();
const currentHour = now.getHours();
const currentMinute = now.getMinutes();
return {
minHour: currentHour,
minMinute: currentHour === now.getHours() ? currentMinute : 0,
maxHour: 23,
maxMinute: 59
};
};智能默认值设置
// 设置合理的默认时间
const getDefaultTime = () => {
const now = new Date();
const hour = now.getHours().toString().padStart(2, '0');
const minute = Math.ceil(now.getMinutes() / 15) * 15; // 向上取整到15分钟
return [hour, minute.toString().padStart(2, '0')];
};💡 使用技巧
多语言时间格式化
const createTimeFormatter = (locale = 'zh-CN') => {
const formatMap = {
'zh-CN': { hour: '时', minute: '分', second: '秒' },
'en-US': { hour: 'h', minute: 'm', second: 's' },
'ja-JP': { hour: '時', minute: '分', second: '秒' }
};
return (type, option) => {
const suffix = formatMap[locale][type] || '';
option.text += suffix;
return option;
};
};高级过滤功能
// 工作时间过滤器
const workTimeFilter = (type, options, values) => {
if (type === 'hour') {
return options.filter(option => {
const hour = Number(option.value);
return hour >= 9 && hour <= 18; // 只显示工作时间
});
}
if (type === 'minute') {
return options.filter(option => {
const minute = Number(option.value);
return minute % 15 === 0; // 只显示15分钟间隔
});
}
return options;
};动态列类型切换
<template>
<van-radio-group v-model="timeMode" direction="horizontal">
<van-radio name="hour">仅小时</van-radio>
<van-radio name="minute">小时分钟</van-radio>
<van-radio name="second">精确到秒</van-radio>
</van-radio-group>
<van-time-picker
v-model="currentTime"
:columns-type="columnsType"
/>
</template>
<script>
const timeMode = ref('minute');
const columnsType = computed(() => {
const modeMap = {
hour: ['hour'],
minute: ['hour', 'minute'],
second: ['hour', 'minute', 'second']
};
return modeMap[timeMode.value];
});
</script>🔧 常见问题解决
时间格式兼容性
// 处理不同时间格式的转换
const formatTimeValue = (timeArray) => {
return timeArray.map(time => {
// 确保时间值为两位数
return time.toString().padStart(2, '0');
});
};
// 解析时间字符串
const parseTimeString = (timeString) => {
const parts = timeString.split(':');
return parts.map(part => part.padStart(2, '0'));
};性能优化
// 使用防抖优化频繁的change事件
import { debounce } from 'lodash-es';
const debouncedChange = debounce((selectedValues) => {
console.log('时间选择变化:', selectedValues);
// 执行相关业务逻辑
}, 300);数据验证与错误处理
// 时间有效性验证
const validateTime = (timeArray) => {
const [hour, minute, second] = timeArray.map(Number);
if (hour < 0 || hour > 23) return false;
if (minute < 0 || minute > 59) return false;
if (second !== undefined && (second < 0 || second > 59)) return false;
return true;
};
// 错误处理
const handleTimeError = (error) => {
console.error('时间选择器错误:', error);
// 重置为默认时间
currentTime.value = ['12', '00'];
};🎨 设计灵感
主题化时间选择器
/* 深色主题 */
.van-time-picker--dark {
--van-picker-background: #1a1a1a;
--van-picker-option-text-color: #ffffff;
--van-picker-option-selected-text-color: #1989fa;
}
/* 渐变背景 */
.van-time-picker--gradient {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
/* 圆角风格 */
.van-time-picker--rounded {
border-radius: 20px;
overflow: hidden;
}动画效果增强
/* 选项切换动画 */
.van-picker-column__item {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.van-picker-column__item--selected {
transform: scale(1.1);
font-weight: bold;
color: #1989fa;
}
/* 弹出动画 */
.time-picker-enter-active,
.time-picker-leave-active {
transition: all 0.3s ease;
}
.time-picker-enter-from,
.time-picker-leave-to {
opacity: 0;
transform: translateY(100%);
}创意交互效果
// 震动反馈
const handleTimeChange = () => {
if ('vibrate' in navigator) {
navigator.vibrate(50); // 轻微震动反馈
}
};
// 声音反馈
const playTickSound = () => {
const audio = new Audio('/sounds/tick.mp3');
audio.volume = 0.3;
audio.play().catch(() => {
// 静默处理音频播放失败
});
};🚀 高级功能扩展
智能时间推荐系统
// 基于用户历史选择的智能推荐
class TimeRecommendationSystem {
constructor() {
this.history = JSON.parse(localStorage.getItem('timeHistory') || '[]');
}
// 记录用户选择
recordSelection(timeArray) {
this.history.push({
time: timeArray,
timestamp: Date.now()
});
// 只保留最近50次记录
if (this.history.length > 50) {
this.history = this.history.slice(-50);
}
localStorage.setItem('timeHistory', JSON.stringify(this.history));
}
// 获取推荐时间
getRecommendations() {
const timeFrequency = {};
this.history.forEach(record => {
const timeKey = record.time.join(':');
timeFrequency[timeKey] = (timeFrequency[timeKey] || 0) + 1;
});
return Object.entries(timeFrequency)
.sort(([,a], [,b]) => b - a)
.slice(0, 3)
.map(([time]) => time.split(':'));
}
}多时区时间选择器
// 多时区支持
const createTimezoneTimePicker = (timezone = 'Asia/Shanghai') => {
const getTimezoneTime = () => {
const now = new Date();
const utc = now.getTime() + (now.getTimezoneOffset() * 60000);
const targetTime = new Date(utc + (getTimezoneOffset(timezone) * 60000));
return [
targetTime.getHours().toString().padStart(2, '0'),
targetTime.getMinutes().toString().padStart(2, '0')
];
};
const getTimezoneOffset = (tz) => {
// 时区偏移量映射
const timezones = {
'Asia/Shanghai': 8,
'America/New_York': -5,
'Europe/London': 0,
'Asia/Tokyo': 9
};
return (timezones[tz] || 0) * 3600000;
};
return {
getTimezoneTime,
formatTimezone: (time) => {
return `${time.join(':')} (${timezone})`;
}
};
};📚 延伸阅读
技术文档
- Picker 选择器基础组件 - 了解底层选择器实现
- DatePicker 日期选择器 - 日期选择的完美搭档
- Calendar 日历组件 - 更丰富的日期时间选择
设计指南
用户体验
相关文档 📚
🎯 核心组件
- Picker 选择器 - 时间选择器的基础组件,了解底层实现原理
- DatePicker 日期选择器 - 日期选择的完美搭档,组合使用更强大
- Calendar 日历组件 - 更丰富的日期时间选择体验
🔧 辅助组件
⚡ 高级功能
- Form 表单 - 表单验证与时间选择的完美结合
- ConfigProvider 全局配置 - 统一配置时间选择器的主题和语言