专注小程序 / APP 开发,用代码改变生活

获取方案
全国分站 主站首页 北京分站 石家庄分站 唐山分站 保定分站 廊坊分站 沧州分站 郑州分站

私教预约小程序开发实战指南

小程序开发 2026-10-12 3 阅读 0 点赞 原创

做健身私教、瑜伽老师或者独立教练的朋友,几乎都会遇到同一个问题:排课、约课、改课全靠微信聊天记录,一到月底对账就头大。我去年帮一位做普拉提的朋友从零做了一套私教预约小程序,上线三个月后她的排课效率提升了至少一半。这篇文章就把整个开发思路和关键实现拆开讲清楚。

为什么私教场景值得单独做一个小程序

通用的预约工具很多,但私教业务有几个特殊点,通用工具很难覆盖:

  • 一对一排课:每个时间段只能被一个学员占用,冲突检测必须严格。
  • 课时包概念:学员买的是10次、20次的课时包,每次预约要扣减剩余次数。
  • 改约与取消规则:通常要求提前24小时取消,否则扣课时,这条规则必须写进系统。
  • 教练视角:教练需要看到自己未来一周的排期,而不是所有订单混在一起。

这些需求决定了数据库设计和业务逻辑不能照搬通用预约模板。

核心数据表设计

小程序端用微信云开发或者自建后端都可以,我这里以自建 Node.js + MySQL 为例。最关键的几张表:

-- 教练可预约时间段
CREATE TABLE coach_slots (
  id INT PRIMARY KEY AUTO_INCREMENT,
  coach_id INT NOT NULL,
  start_time DATETIME NOT NULL,
  end_time DATETIME NOT NULL,
  status TINYINT DEFAULT 0, -- 0可约 1已约 2已锁定
  UNIQUE KEY uk_coach_time (coach_id, start_time)
);

-- 学员课时包
CREATE TABLE user_packages (
  id INT PRIMARY KEY AUTO_INCREMENT,
  user_id INT NOT NULL,
  total_times INT NOT NULL,
  used_times INT DEFAULT 0,
  expire_at DATE
);

-- 预约记录
CREATE TABLE bookings (
  id INT PRIMARY KEY AUTO_INCREMENT,
  user_id INT NOT NULL,
  slot_id INT NOT NULL,
  package_id INT NOT NULL,
  status TINYINT DEFAULT 0, -- 0待确认 1已确认 2已取消 3已完成
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

注意 coach_slots 上的唯一索引,它能在数据库层面直接防止同一教练同一时间被重复预约,比在应用层做判断更可靠。

预约接口的并发处理

私教预约最容易出问题的地方就是并发。两个学员同时点同一个时段,如果只是先查再写,很容易超卖。正确做法是用事务加行锁:

async function bookSlot(userId, slotId, packageId) {
  const conn = await pool.getConnection();
  try {
    await conn.beginTransaction();
    // 锁定该时段
    const [slots] = await conn.query(
      'SELECT * FROM coach_slots WHERE id = ? FOR UPDATE',
      [slotId]
    );
    if (!slots.length || slots[0].status !== 0) {
      throw new Error('该时段已被预约');
    }
    // 校验课时包
    const [pkgs] = await conn.query(
      'SELECT * FROM user_packages WHERE id = ? AND user_id = ? FOR UPDATE',
      [packageId, userId]
    );
    if (!pkgs.length || pkgs[0].used_times >= pkgs[0].total_times) {
      throw new Error('课时不足');
    }
    // 更新时段状态
    await conn.query('UPDATE coach_slots SET status = 1 WHERE id = ?', [slotId]);
    // 扣减课时
    await conn.query(
      'UPDATE user_packages SET used_times = used_times + 1 WHERE id = ?',
      [packageId]
    );
    // 写入预约记录
    await conn.query(
      'INSERT INTO bookings (user_id, slot_id, package_id) VALUES (?, ?, ?)',
      [userId, slotId, packageId]
    );
    await conn.commit();
    return { success: true };
  } catch (e) {
    await conn.rollback();
    throw e;
  } finally {
    conn.release();
  }
}

这段代码里 FOR UPDATE 是关键,它保证同一时段在事务提交前不会被其他请求读到可约状态。

小程序端的关键页面

前端我建议只做三个核心页面,避免功能堆砌:

  1. 排期日历页:用日历组件展示教练未来两周的可约时段,点击直接进入确认页。
  2. 我的课时页:展示剩余课时、有效期,支持直接发起预约。
  3. 教练工作台:仅教练角色可见,查看当日和本周排期,支持手动锁定时间段。

微信小程序的 wx.request 记得统一封装,把 token 和错误提示处理好,不然每个页面都写一遍会很痛苦。

取消与改约规则

这是私教场景里最容易被忽略但最影响体验的部分。我的建议是把规则做成可配置项,存在教练配置表里,比如提前取消小时数、是否扣课时。默认逻辑:

  • 距离上课时间大于24小时取消:不扣课时,时段释放。
  • 小于24小时取消:扣1次课时,时段不释放(教练时间已经被占用)。
  • 改约:先取消再重新预约,同样走上面的规则。

规则用后端统一判断,前端只负责展示提示,避免规则被绕过。

上线后的一些经验

这套小程序从开发到上线大概用了三周,其中两天专门用来做并发测试。上线后有几个真实反馈值得分享:学员其实很少用日历,更习惯直接看“最近可约”列表,所以后来我把列表视图提到了日历前面;教练端最常用的功能是手动锁定时间,而不是查看订单。做这类工具,功能优先级一定要跟着真实使用习惯走,而不是按开发者的直觉来。

如果你也在做类似项目,建议先把预约冲突和课时扣减这两条链路跑通,其他都是锦上添花。

从零开发一个球场订场小程序从零开发游泳馆小程序:预约与核销实战

评论 (0)

还没有评论,快来抢沙发吧~

好想法,值得被认真交付

从小程序、APP 到全栈网站,一站式把想法变成可落地的产品

微信咨询

微信扫码,直接沟通需求