美业微信小程序预约源码:含完整闭环与支付预留
2026/9/10 17:28:16 网站建设 项目流程

简介:这是一套面向美容美发行业商家与微信小程序开发者的在线预约系统源码,解决传统门店预约效率低、客户流失率高、服务管理粗放等痛点,适用于中小型美业机构快速部署自有预约服务。资源共67个文件,涵盖12个JS逻辑层文件、12个WXSS样式文件、11个WXML视图文件、15个JSON配置与数据文件,以及PNG图标、LICENSE协议等,完整支撑小程序前端渲染、用户交互、服务调度与基础配置,压缩包仅1.64MB,轻量易集成。已有1337人学习下载,说明其在实战落地中具备较高参考价值。开发者可直接运行调试,快速掌握预约流程(服务选择→技师匹配→时段确认→订单生成)、微信支付对接、用户登录态管理及后台数据结构设计;目录结构清晰,pages下分模块组织(login/register/order/proDetail等),app.json与project.config.json配置完备,便于二次定制品牌视觉与业务规则。

1. 这不是“套模板”,而是一套能跑通预约闭环的微信小程序源码

你拿到的美容美发在线预约微信小程序源码.zip,不是那种改个 logo 就叫“可商用”的 Demo 级项目。它真实包含从用户打开小程序 → 查看门店/技师 → 选择服务与时段 → 提交预约 → 支付(v3 接口预留)→ 后台管理订单的完整链路,且所有页面均基于微信原生框架构建,无第三方跨端框架封装痕迹。项目结构清晰对应微信官方推荐的app.js+app.json+pages/目录规范,project.config.json中已预设开发者工具基础配置,sitemap.json显式声明了可被微信搜索收录的页面路径——这意味着它不是“写完就扔”的练习代码,而是按生产环境标准组织的、可直接部署上线的最小可行系统(MVP)。适合两类人:一是中小型美业门店老板,想快速拥有自己的预约入口,无需招前端;二是刚入行的小程序开发者,需要一个结构完整、功能闭环、无冗余依赖的真实项目来拆解学习——尤其适合练手「预约冲突校验」「多角色权限隔离」「微信支付 v3 预留接入点」这三个高频实战难点。

2. 源码结构解析与核心模块定位:从app.jsonpages/order/

2.1 项目骨架:app.json定义路由与窗口样式,是启动逻辑的总开关

app.json是整个小程序的“地图”和“门面”。打开该文件,你会看到pages数组明确列出了 9 个页面路径,按访问顺序排列:pages/index/index(首页)、pages/login/login(登录)、pages/register/register(注册)、pages/technician/technician(技师列表)、pages/proDetail/proDetail(服务详情)、pages/order/order(预约下单)、pages/my/my(个人中心)、pages/reset/reset(密码重置)、pages/start/start(启动页)。这种顺序并非随意,它直接影响微信小程序的页面栈行为和 tabBar 的默认选中项。特别注意tabBar配置中list仅包含indexordermy三个页面,说明该项目采用“首页+预约中心+我的”三栏导航,符合美业用户高频操作路径(找服务→下单→查记录)。window节点下navigationBarTitleText统一设为“美丽预约”,navigationStyledefault,表明未自定义导航栏,直接复用微信原生标题栏——这是降低兼容性风险的稳妥做法,也意味着后续若需修改顶部导航栏高度或添加自定义按钮,必须通过wx.setNavigationBarColorwx.setNavigationBarTitle动态控制,而非在app.json中硬编码。

提示:sitemap.json"rules"数组将indexproDetailtechnician设为"allow",说明这三页内容允许被微信搜索抓取。若你上线后发现小程序搜不到,第一检查点就是此处是否误删或配错路径。

2.2 页面级逻辑:以pages/order/order.js为例,拆解预约核心流程

预约下单页order.js是业务中枢,其onLoad生命周期函数执行三步关键初始化:

// pages/order/order.js Page({ data: { serviceId: '', technicianId: '', selectedTime: '', isSubmitting: false }, onLoad(options) { // 1. 获取上一页传来的服务ID与技师ID this.setData({ serviceId: options.serviceId || '', technicianId: options.technicianId || '' }); // 2. 调用云函数获取该技师当日可预约时段(关键!避免前端硬编码时间) wx.cloud.callFunction({ name: 'getAvailableTimes', data: { technicianId: this.data.technicianId } }).then(res => { this.setData({ timeSlots: res.result.data || [] }); }).catch(err => { console.error('获取时段失败', err); wx.showToast({ title: '时段加载失败', icon: 'none' }); }); // 3. 初始化日期选择器,默认为今日 const today = new Date(); this.setData({ date: `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}` }); }, // 用户点击时段后的处理 selectTime(e) { const time = e.currentTarget.dataset.time; this.setData({ selectedTime: time }); }, // 提交预约 submitOrder() { if (!this.data.selectedTime) { wx.showToast({ title: '请选择预约时间', icon: 'none' }); return; } this.setData({ isSubmitting: true }); // 构造预约数据并调用云函数提交 const orderData = { serviceId: this.data.serviceId, technicianId: this.data.technicianId, date: this.data.date, time: this.data.selectedTime, userId: wx.getStorageSync('userId') // 从本地缓存读取用户ID }; wx.cloud.callFunction({ name: 'createOrder', data: orderData }).then(res => { if (res.result.success) { wx.showToast({ title: '预约成功', icon: 'success' }); setTimeout(() => wx.navigateBack(), 1500); } else { wx.showToast({ title: res.result.message || '预约失败', icon: 'none' }); } }).catch(err => { wx.showToast({ title: '网络错误,请重试', icon: 'none' }); }).finally(() => { this.setData({ isSubmitting: false }); }); } });

这段代码揭示了三个关键设计点:

  • 参数传递options.serviceIdoptions.technicianId来自navigator组件的url属性,如<navigator url="/pages/order/order?serviceId=101&technicianId=205">,这是小程序页面间传参的标准方式;
  • 时段动态生成getAvailableTimes云函数负责查询数据库中该技师在指定日期的已预约记录,排除冲突时段后返回可用列表,避免前端静态写死导致超约;
  • 状态防重复提交isSubmitting数据绑定到按钮disabled属性,配合finally清除状态,防止用户连续点击触发多次请求。

2.3 用户体系:login.jsregister.js如何对接微信授权

登录页login.js并非传统账号密码模式,而是深度集成微信wx.login+wx.getUserProfile(替代已废弃的wx.getUserInfo):

// pages/login/login.js Page({ data: { hasUserInfo: false }, onShow() { // 检查本地是否已有用户信息(避免重复授权) const userInfo = wx.getStorageSync('userInfo'); if (userInfo && userInfo.nickName) { this.setData({ hasUserInfo: true }); wx.switchTab({ url: '/pages/index/index' }); } }, getUserProfile() { // 弹出授权弹窗,获取用户昵称头像 wx.getUserProfile({ desc: '用于完善会员资料' }).then(res => { const userInfo = res.userInfo; // 调用云函数将用户信息存入数据库,并返回唯一 openid wx.cloud.callFunction({ name: 'registerUser', data: { userInfo } }).then(res => { wx.setStorageSync('userInfo', userInfo); wx.setStorageSync('userId', res.result.userId); // 存储业务侧用户ID this.setData({ hasUserInfo: true }); wx.switchTab({ url: '/pages/index/index' }); }); }).catch(err => { console.log('用户拒绝授权', err); wx.showToast({ title: '授权失败,无法登录', icon: 'none' }); }); } });

这里的关键在于registerUser云函数的实现逻辑:它接收userInfo对象,调用wx.cloud.database().collection('users').add()插入新记录,并利用event.userInfo.openid(云函数上下文自动注入)作为主键关联微信身份。userId字段是业务自增 ID(如U20240001),与openid分离,为后续对接短信、邮箱等多渠道登录预留扩展空间。wx.setStorageSync缓存userInfouserId,使后续页面(如order.js)能直接读取,无需反复调用授权接口。

3. 后端能力落地:云函数与数据库设计如何支撑预约业务

3.1 云函数命名规范与职责划分:getAvailableTimescreateOrder的协作逻辑

项目中云函数目录结构为cloudfunctions/getAvailableTimescloudfunctions/createOrder,严格遵循单一职责原则。getAvailableTimes函数的核心逻辑是查询orders集合中指定技师在某日的已预约记录,并计算空闲时段:

// cloudfunctions/getAvailableTimes/index.js const cloud = require('wx-server-sdk'); cloud.init(); const db = cloud.database(); exports.main = async (event, context) => { const { technicianId, date } = event; try { // 查询该技师当日所有已预约订单 const res = await db.collection('orders').where({ technicianId: technicianId, date: date, status: db.command.neq('cancelled') // 排除已取消订单 }).field({ time: true }).get(); // 假设营业时间为 9:00-18:00,每单耗时 60 分钟,则时段为 ['09:00', '10:00', ..., '17:00'] const allSlots = Array.from({ length: 10 }, (_, i) => `${String(9 + i).padStart(2, '0')}:00` ); // 过滤掉已被占用的时段 const bookedSlots = res.data.map(item => item.time); const availableSlots = allSlots.filter(slot => !bookedSlots.includes(slot)); return { success: true, data: availableSlots }; } catch (err) { console.error('查询时段失败', err); return { success: false, message: '服务器繁忙' }; } };

此函数的关键点在于:

  • 时间粒度控制:硬编码allSlots生成 9:00–17:00 的整点时段,实际项目中应从technicians集合读取该技师的workHours字段(如["09:00-12:00", "13:30-18:00"])再动态生成,此处为简化演示;
  • 状态过滤status: db.command.neq('cancelled')确保已取消订单不占用时段,这是预约系统防超约的核心规则;
  • 返回结构:统一{ success: boolean, data: array }格式,前端order.js可直接解构使用。

createOrder函数则负责事务性写入:先校验时段是否仍可用(二次防冲突),再插入订单,并更新技师当日预约计数:

// cloudfunctions/createOrder/index.js exports.main = async (event, context) => { const { serviceId, technicianId, date, time, userId } = event; const wxContext = cloud.getWXContext(); try { // 1. 再次检查时段是否可用(防止并发场景下瞬间被抢) const checkRes = await db.collection('orders').where({ technicianId, date, time, status: db.command.neq('cancelled') }).count(); if (checkRes.total > 0) { return { success: false, message: '该时段已被预约,请重新选择' }; } // 2. 插入新订单 const orderData = { _id: db.collection('orders').doc().id, // 自动生成唯一ID serviceId, technicianId, date, time, userId, status: 'pending', // 初始状态为待确认 createTime: new Date(), openid: wxContext.OPENID // 关联微信身份 }; await db.collection('orders').add({ data: orderData }); // 3. 更新技师统计(可选,用于后台报表) await db.collection('technicians').doc(technicianId).update({ data: { bookedCount: db.command.inc(1) } }); return { success: true, orderId: orderData._id }; } catch (err) { console.error('创建订单失败', err); return { success: false, message: '系统错误,请稍后重试' }; } };

注意:db.command.inc(1)是原子操作,避免并发更新导致计数错误。若你的业务需要更精细的统计(如按服务类型分计),应在orders集合中增加serviceType字段并在更新时按条件累加。

3.2 数据库集合设计:servicestechniciansorders三表关系

项目数据库包含三个核心集合,其字段设计直指美业预约痛点:

集合名关键字段说明
services_id,name,duration,price,imageUrl,categoryduration单位为分钟(如90表示 1.5 小时),category用于首页分类筛选(如"剪发","染烫"
technicians_id,name,avatarUrl,level,workHours,bio,bookedCountworkHours为字符串数组(["09:00-12:00", "13:30-18:00"]),bookedCount实时统计当日预约量
orders_id,serviceId,technicianId,date,time,userId,status,createTime,openidstatus枚举值:pending(待确认)、confirmed(已确认)、cancelled(已取消)、completed(已完成)

这种设计使得pages/technician/technician.js可通过db.collection('technicians').where({ category: '剪发' })快速筛选技师,pages/proDetail/proDetail.js能根据serviceId关联查出对应技师列表,形成清晰的数据流向。所有集合均启用索引:orders集合对(technicianId, date, status)建复合索引,大幅提升getAvailableTimes查询效率。

4. 个性化定制实操:修改启动页、更换主题色与替换 Logo 的具体步骤

4.1 修改刚进入的加载页面:替换pages/start/start.wxmlstart.wxss

小程序首次打开时显示的启动页由pages/start/start.wxml控制。默认内容是一个居中image组件加载images/logo.png(注意:源码中实际为image1.png,路径需核对):

<!-- pages/start/start.wxml --> <view class="container"> <image src="/images/image1.png" class="logo" mode="aspectFit"></image> <text class="loading-text">加载中...</text> </view>

要更换 Logo,只需两步:

  1. 将新图片(建议尺寸 300×300px,格式 PNG)放入images/目录,命名为logo.png
  2. 修改start.wxmlsrc属性为"/images/logo.png"
  3. start.wxss中调整.logo样式控制大小与位置:
/* pages/start/start.wxss */ .container { display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100vh; background-color: #f8f8f8; } .logo { width: 120rpx; /* 注意单位是 rpx,适配不同屏幕 */ height: 120rpx; margin-bottom: 40rpx; } .loading-text { font-size: 28rpx; color: #666; }

提示:height: 100vh在部分安卓机型上可能失效,更稳妥写法是min-height: 100vh,并确保父容器pageoverflow: hidden干扰。

4.2 全局主题色修改:通过app.wxss统一控制主色调

项目主色调定义在app.wxss:root伪类中:

/* app.wxss */ :root { --primary-color: #ff6b6b; /* 主色:珊瑚红 */ --secondary-color: #4ecdc4; /* 辅色:青绿色 */ --text-color: #333; /* 文字色 */ --border-color: #e0e0e0; /* 边框色 */ }

所有页面组件通过color: var(--primary-color)调用该变量。修改主题色只需更改--primary-color的值:

  • 想用莫兰迪灰?改为#8d99ae
  • 想用高级黑金?改为#222并同步调整--secondary-color#d4af37
  • 修改后需全局搜索var(--primary-color),检查是否有硬编码颜色值(如#ff6b6b)遗漏,确保风格统一。

4.3 替换顶部导航栏文字与背景:app.jsonpages/index/index.js协同控制

微信原生导航栏文字由app.jsonwindow.navigationBarTitleText统一设置,但首页index需要突出品牌名,因此pages/index/index.jsonLoad中动态覆盖:

// pages/index/index.js Page({ onLoad() { wx.setNavigationBarTitle({ title: 'XX美发沙龙' }); // 覆盖 app.json 设置 wx.setNavigationBarColor({ frontColor: '#ffffff', // 文字颜色(白) backgroundColor: '#ff6b6b', // 背景颜色(主色) animation: { duration: 400, timingFunc: 'easeIn' } }); } });

此写法确保首页导航栏为品牌色,其他页面(如ordermy)仍沿用app.json的默认设置。若需全站统一,直接修改app.jsonwindow节点即可。

5. 支付功能对接与调试:微信小程序支付 v3 接口的预留接入点

5.1 源码中支付模块的现状:pages/order/order.js已预留payOrder方法占位

当前order.jssubmitOrder成功后并未触发支付,而是直接跳转成功页。支付逻辑被抽象为独立方法payOrder,但未被调用:

// pages/order/order.js(片段) payOrder(orderId) { // TODO: 调用微信支付 v3 接口 console.log('准备支付订单:', orderId); // 此处应调用云函数 getPayParams 获取 prepay_id 等参数 // 然后执行 wx.requestPayment }, // submitOrder 方法末尾注释提示 // ... // wx.showToast({ title: '预约成功', icon: 'success' }); // this.payOrder(res.result.orderId); // ← 解开此行注释并实现 payOrder

5.2 接入微信支付 v3 的四步实操指令

要启用支付,需按顺序执行以下操作(需商户资质):

步骤 1:在云函数中实现getPayParams

新建云函数cloudfunctions/getPayParams,其核心是调用微信支付 v3 下单接口:

// cloudfunctions/getPayParams/index.js const cloud = require('wx-server-sdk'); const axios = require('axios'); // 需在云函数 package.json 中安装 axios cloud.init(); exports.main = async (event, context) => { const { orderId, totalFee } = event; // totalFee 单位为分 const appId = 'your-app-id'; // 替换为你的小程序 AppID const mchId = 'your-mch-id'; // 替换为你的微信支付商户号 const apiKey = 'your-api-key-v3'; // 替换为你的 APIv3 密钥 try { // 1. 构造请求参数 const timestamp = Math.floor(Date.now() / 1000); const nonceStr = Math.random().toString(36).substr(2, 15); const body = `美发预约服务`; const outTradeNo = `ORDER${timestamp}${Math.floor(Math.random() * 1000)}`; // 2. 调用微信统一下单接口(需 HTTPS) const response = await axios.post( `https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi`, { appid: appId, mchid: mchId, description: body, out_trade_no: outTradeNo, notify_url: 'https://yourdomain.com/api/pay/notify', // 支付结果回调地址 amount: { total: totalFee, currency: 'CNY' }, payer: { openid: context.OPENID } // 用户 openid }, { headers: { 'Content-Type': 'application/json', 'Authorization': `WECHATPAY2-SHA256-RSA2048 ${generateAuthHeader()}` // 签名头,需实现 generateAuthHeader } } ); // 3. 解析返回的 prepay_id 并构造支付参数 const { prepay_id } = response.data; const payParams = { appId, timeStamp: timestamp.toString(), nonceStr, package: `prepay_id=${prepay_id}`, signType: 'RSA', paySign: generatePaySign(appId, timestamp, nonceStr, `prepay_id=${prepay_id}`, apiKey) }; return { success: true, payParams }; } catch (err) { console.error('支付参数获取失败', err.response?.data || err); return { success: false, message: '支付初始化失败' }; } }; // 签名生成函数(简化版,实际需用 node-forge 或 crypto 模块) function generatePaySign(appId, timeStamp, nonceStr, packageStr, apiKey) { // 此处需实现 RSA 签名算法,细节略 return 'fake-signature'; }

注意:generateAuthHeadergeneratePaySign需使用微信官方 SDK 或自行实现 RSA 签名,密钥需妥善保管,严禁硬编码在云函数中。

步骤 2:在order.js中调用并执行支付

解开submitOrder中的注释,并补充payOrder实现:

payOrder(orderId) { wx.cloud.callFunction({ name: 'getPayParams', data: { orderId, totalFee: 19900 } // 示例:199元 = 19900分 }).then(res => { if (res.result.success) { wx.requestPayment({ ...res.result.payParams, success: (payRes) => { console.log('支付成功', payRes); wx.showToast({ title: '支付成功', icon: 'success' }); // 更新订单状态为 confirmed wx.cloud.callFunction({ name: 'updateOrderStatus', data: { orderId, status: 'confirmed' } }); }, fail: (err) => { console.log('支付失败', err); wx.showToast({ title: '支付失败', icon: 'none' }); } }); } }); }
步骤 3:配置支付域名与证书

登录 微信支付商户平台 ,完成:

  • 在「产品中心」→「开发配置」中,将云函数所在域名(如xxx.tcb.qcloud.ltd)加入「JSAPI 支付」的「支付授权目录」;
  • 在「账户中心」→「API安全」中,下载 APIv3 密钥并上传至云函数环境变量(非代码中);
  • 配置「APIv3密钥」和「证书」,确保云函数能调用 v3 接口。
步骤 4:测试与验证
  • 使用微信开发者工具「真机调试」,选择已绑定商户号的测试账号;
  • 触发支付后,检查云函数日志是否返回prepay_id
  • wx.requestPayment报错requestPayment:fail invalid signature,检查签名算法与密钥是否匹配;
  • 支付成功后,务必验证updateOrderStatus云函数是否将订单status更新为confirmed,否则影响后续服务履约。

完成以上四步,支付功能即正式接入。源码中所有占位逻辑均已就绪,你只需填充密钥与配置,无需重构页面结构。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询