基于Node.js+MongoDB的美发店预约系统排班转可预约时段咨询
美发店预约系统排班与预约功能实现方案
现有排班结构优化建议
你当前拆分午休时段的设计逻辑是合理的,适配了美发店的常规运营规则,仅补充几个小优化点降低后续开发风险:
- 统一字段规则:所有
status = true的排班记录必须携带shift数组,避免示例中周三排班缺失shift字段导致的解析报错 - 时间格式改为24小时制(如
08:00、13:00),省去上下午标识的解析逻辑,降低时间计算出错概率 - 顶层新增
staffId字段关联对应员工,方便多员工场景下的批量查询与数据隔离
1. 排班数据转换为用户侧可预约时段
核心思路是按门店服务的最小时间粒度(比如剪发30分钟、烫染可按多倍粒度计算)拆分排班时间段,生成标准化的时间槽位:
实现步骤:
- 提前配置服务最小粒度,比如
const MIN_SERVICE_DURATION = 30; // 单位:分钟 - 遍历员工排班列表,筛选出
status = true的有效排班日期 - 对每个排班日的多段工作时间,按最小粒度拆分生成连续的时间槽,每个槽位携带日期、起始/结束时间、关联员工ID等属性
示例代码:
// 时间转分钟数,方便计算 const timeToMinutes = (timeStr) => { const [hour, minute] = timeStr.split(':'); return parseInt(hour) * 60 + parseInt(minute); }; // 分钟数转时间字符串 const minutesToTime = (minutes) => { const hour = String(Math.floor(minutes / 60)).padStart(2, '0'); const minute = String(minutes % 60).padStart(2, '0'); return `${hour}:${minute}`; }; // 生成可预约槽位 const generateAvailableSlots = (staffShifts, serviceDuration = 30) => { const slots = []; staffShifts.forEach(dayShift => { if (!dayShift.status || !dayShift.shift?.length) return; dayShift.shift.forEach(timeRange => { const startMin = timeToMinutes(timeRange.startTime); const endMin = timeToMinutes(timeRange.endTime); let current = startMin; while (current + serviceDuration <= endMin) { slots.push({ day: dayShift.day, startTime: minutesToTime(current), endTime: minutesToTime(current + serviceDuration), isAvailable: true }); current += serviceDuration; } }); }); return slots; };
2. 预约成功后标记时段为已预约
首先需要新增MongoDB的appointments预约集合,核心字段设计如下:
{ staffId: ObjectId, // 关联提供服务的员工 date: String, // 预约日期,格式为YYYY-MM-DD startTime: String, // 预约开始时间 endTime: String, // 预约结束时间 userId: ObjectId, // 关联预约用户 status: Number, // 预约状态:0=已取消 1=已生效 2=已完成 serviceType: String // 预约的服务项目 }
实现流程:
- 用户提交预约请求时,先校验请求的时段是否在员工排班范围内,且未被其他用户预约
- 校验通过后往
appointments集合插入一条状态为1的预约记录 - 为了避免并发预约导致的超卖问题,可以给
staffId + date + startTime组合字段加唯一索引,确保同个员工同时段只能有一个有效预约
3. 过滤已预约时段返回可用空位
核心逻辑是将生成的全量可预约槽位,和对应员工对应日期的已生效预约记录做差集,过滤掉已占用的槽位:
实现步骤:
- 接收前端查询参数:员工ID、查询的日期范围
- 拉取该员工在查询范围内的排班数据,调用第一步的方法生成全量可预约槽位
- 查询
appointments集合,筛选出该员工、该日期范围内status = 1的所有有效预约记录 - 遍历全量槽位,将已存在于预约记录中的槽位标记为不可用,或直接过滤掉,最终返回可用槽位列表
示例代码:
const getFilteredAvailableSlots = async (staffId, queryDate, serviceDuration) => { // 1. 拉取员工排班 const staff = await StaffModel.findOne({ _id: staffId }); const allSlots = generateAvailableSlots(staff.shift, serviceDuration); // 2. 拉取对应日期的已生效预约 const appointments = await AppointmentModel.find({ staffId, date: queryDate, status: 1 }); const occupiedSlots = new Set(appointments.map(item => item.startTime)); // 3. 过滤已占用槽位 return allSlots.filter(slot => !occupiedSlots.has(slot.startTime)); };
内容的提问来源于stack exchange,提问作者Ahsan Abrar
相关产品推荐
相关产品推荐

