在微信小程序中做预约、配送或服务时段选择时,可选时间通常不是固定的。例如上午十点到十二点可以约,但下午两点到四点已经被占满;或者服务时长不同,可选的最晚开始时间也要跟着变。如果只是把 picker 的开始时间和结束时间写死,业务规则一调整用户就能选到无效时段。要实现时间选择范围随业务规则动态更新,需要把可选范围的计算逻辑和组件的数据绑定打通。下面围绕原生 picker 和自定义 picker-view 两条路线展开,说明动态时间范围控制的做法和适用边界。

原生 picker 的 start 与 end 属性适合哪些场景
微信小程序的 <picker> 组件在 mode="time" 时支持 start 和 end 两个属性。它们分别表示有效时间范围的起点和终点,格式为 HH:mm。只要在 WXML 中绑定这两个值,再通过 setData 修改,就能在业务逻辑变化后立刻改变时间选择器的可选项。比如营业时间固定的门店,早上 9 点开门、18 点关门,可以把 minTime 初始化为 09:00,maxTime 初始化为 18:00。
下面是一个基础实现。WXML 中放置一个时间选择器,开始时间、结束时间和当前选中值都走数据绑定:
<picker mode="time" value="{{selectedTime}}" start="{{minTime}}" end="{{maxTime}}" bindchange="onTimeChange">
<view class="picker">选择时间:{{selectedTime}}</view>
</picker>
对应的 JS 逻辑不复杂,当后台配置或本地缓存中的营业时间发生变化时,调用 updateTimeRange 即可:
Page({
data: {
selectedTime: '10:00',
minTime: '09:00',
maxTime: '18:00'
},
onTimeChange(e) {
this.setData({ selectedTime: e.detail.value });
},
updateTimeRange(openTime, closeTime) {
this.setData({
minTime: openTime,
maxTime: closeTime
});
}
})
这种方式实现成本最低,但它只能表达一段连续的时间范围。如果可选时间是不连续的,例如上午十点到十二点、下午三点到五点,原生 start 和 end 无法排除中间不可选时段。同时,它也不能根据用户所选日期自动改变开始时间,比如用户选择今天时不能早于当前时间。这类更细粒度的控制就要交给自定义多列选择器。
用 picker-view 构建多列时间选择
<picker-view> 组件允许开发者自定义列数和每一列的内容,适合小时、分钟分开维护的场景。通过把可选小时和可选分钟拆成两个数组,可以方便地表示不连续时段。比如用户只能选整点和半点,分钟列只保留 0 和 30;某些小时已被约满,就直接从小时数组中删除,比单纯限制起止时间灵活很多。
WXML 结构通常包含两个 <picker-view-column>,一列负责小时,一列负责分钟。选中值通过 value 数组维护,例如 [0, 1] 表示第一列第 0 项、第二列第 1 项。交互变化时会在 bindchange 事件里给出新的索引数组:
<picker-view
value="{{selectedIndex}}"
bindchange="onColumnChange"
indicator-style="height: 50px;"
class="time-picker">
<picker-view-column>
<view wx:for="{{hourOptions}}" wx:key="*this">{{item}}时</view>
</picker-view-column>
<picker-view-column>
<view wx:for="{{minuteOptions}}" wx:key="*this">{{item}}分</view>
</picker-view-column>
</picker-view>
生成小时列和分钟列时,建议不要把所有时刻拼成一个大数组。可以先按业务步长遍历营业区间,然后分别收集小时和分钟。这样既能保持两列数据量小,又能应对 5 分钟、10 分钟等不同步长。下面的函数会跳过已经存在于占用列表中的开始时间:
function generateTimeColumns(openMinute, closeMinute, step, occupiedList) {
const hourSet = new Set();
const minuteSet = new Set();
for (let m = openMinute; m <= closeMinute; m += step) {
if (occupiedList.indexOf(m) > -1) continue;
hourSet.add(Math.floor(m / 60));
minuteSet.add(m % 60);
}
return {
hourOptions: Array.from(hourSet).sort((a, b) => a - b),
minuteOptions: Array.from(minuteSet).sort((a, b) => a - b)
};
}
需要注意的是,picker-view 本身不提供原生时间选择那样的格式化展示,开发者要自己决定选项文案。比如小时列可以显示为 10时,分钟列显示为 30分。如果需要完整时间文本,可以在 bindchange 中根据索引取出对应小时和分钟后拼接。
业务规则如何驱动可选范围动态更新
以一个预约上门服务为例:营业时间是 09:00 到 20:00,单次服务时长 90 分钟,用户只能预约今天及未来七天的日期,已经约满的开始时间不可再选。计算可选开始时间时,首先要把营业起止时间转换成分钟数,然后按 30 分钟步长生成候选值,再排除早于当前时间、超出服务结束时间以及已占用时段。
下面这个函数把这类常见规则组合在一起。它以分钟为最小单位,统一比较,避免字符串时间比较带来的潜在问题。入参中的 occupiedSet 可以从后端接口获取,通常是当天已约开始时间的分钟数集合:
function buildAvailableSlots({ openTime, closeTime, serviceMinutes, occupiedSet, selectedDate }) {
const open = toMinutes(openTime);
const close = toMinutes(closeTime);
const step = 30;
const slots = [];
const today = formatDate(new Date());
const nowMinutes = new Date().getHours() * 60 + new Date().getMinutes();
for (let m = open; m + serviceMinutes <= close; m += step) {
if (selectedDate === today && m <= nowMinutes) continue;
if (occupiedSet.has(m)) continue;
slots.push({ minutes: m, label: formatTime(m) });
}
return slots;
}
动态更新的触发时机也很关键。页面加载时通常先拉取基础营业时间;用户切换日期时要重新请求该日期的占用情况,拿到新数据后调用生成函数,再一次性 setData 更新小时列和分钟列。预约提交成功后也要立刻刷新,避免下一个用户看到过期的空闲时段。跨天营业的情况稍微特殊,比如 20:00 到次日 02:00,需要以营业开始日期为基准,把次日凌晨时段换算成超过 1440 的分钟数,展示时再还原成正确的日期和时间。
边界处理与体验优化
动态范围更新后,原来选中的时间可能已经不可用。如果用户在切换日期前后共用一个 selectedIndex,很容易出现高亮位置错乱或提交无效时间的问题。更稳妥的做法是每次生成新列表后检查当前值是否仍然有效,无效则回退到第一个可用项。
function safeValue(current, options) {
if (!options.length) return '';
const exists = options.some(item => item.minutes === current);
return exists ? current : options[0].minutes;
}
时间计算尽量统一使用分钟数,展示前再调用 formatTime 转成 HH:mm。这样在比较大小、判断是否早于当前时间时会更加可靠。格式化函数可以写成下面这种简单形式:
function toMinutes(hhmm) {
const parts = hhmm.split(':');
return Number(parts[0]) * 60 + Number(parts[1]);
}
function formatTime(minutes) {
const hh = String(Math.floor(minutes / 60)).padStart(2, '0');
const mm = String(minutes % 60).padStart(2, '0');
return hh + ':' + mm;
}
总体来看,如果只是简单连续区间,优先使用原生 picker 的 start 和 end 动态绑定,代码量最少。一旦涉及不连续时段、服务时长约束、已占用时间排除等业务规则,就要用 picker-view 自己维护可选时间列。把计算逻辑集中到 JS 层处理,WXML 只负责展示,能显著降低后期维护成本,也便于接入后端配置或运营调整后的最新规则。
微信小程序picker时间选择动态时间范围修改时间:2026-10-01 19:15:17