ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

家政上门预约小程序源码实战:订单状态机、排班冲突与支付分账

家政上门预约小程序源码实战:订单状态机、排班冲突与支付分账

简介:这份家政上门预约服务小程序源码面向希望快速搭建本地化家政预约平台的开发者与创业者,可帮助解决从服务展示、师傅调度到订单确认的完整业务闭环问题。源码支持多城市定位提醒、服务下单、次卡购买与管理员发单等多种下单方式,并内置抢单与派单机制:后台可设定距离范围,师傅仅能抢范围内订单,管理员派单页面按师傅与客户距离排序并显示电话,便于提前沟通。营销层面提供分销促销、会员折扣、积分兑换与子卡功能,后台功能较为完整。资源包共1779个文件,以js、php、html、json、wxss、wxml等前后端与小程序页面文件为主,另含gif、png、jpg等图片素材及css、less、scss样式文件,压缩包约16.7MB,目录结构清晰。目前已有4378人学习下载,适合具备一定小程序与PHP基础的开发者参考二次开发,快速理解家政预约类项目的模块划分与调度逻辑。

1. 家政上门预约服务小程序源码:从一套能跑通的代码说起

家政上门预约服务小程序源码,解决的是把“找阿姨、约时间、付定金、上门服务、评价结算”这条链路塞进微信生态里的问题。我见过太多团队拿着一个演示版小程序就敢接单,结果卡在派单冲突、服务时段重叠、退款对账上。这套源码的核心价值不是界面多漂亮,而是订单状态机、阿姨排班表、支付分账这三块能不能扛住真实业务。适合谁?想快速验证家政 O2O 模式的小团队、需要二次开发接私活的开发者、以及想拿一套可运行代码学习小程序全栈的工程师。热搜里“源码”这个词被滥用了,但真正能跑通支付和排班的家政源码,市面上并不多。

2. 家政预约小程序源码的技术选型与订单状态机设计

2.1 为什么用微信小程序原生加云开发而不是 uni-app

家政预约场景对微信生态依赖极深:微信支付、订阅消息、手机号快速验证、位置权限,这些用原生小程序加云开发是最短路径。uni-app 跨端听起来美好,但家政用户 95% 在微信里,多端适配反而增加调试成本。我一般会选原生小程序 + 云函数 + 云数据库,理由有三:第一,云开发免鉴权直接拿 openid,省掉登录态维护;第二,云函数定时触发器可以做超时未支付自动取消;第三,云数据库的 watch 能力让阿姨端和用户端订单状态实时同步,不用自己搭 WebSocket。

源码目录结构通常长这样:

miniprogram/ pages/ index/ 用户端首页 order/ 下单页 orderList/ 订单列表 aunt/ 阿姨端接单 components/ timePicker/ 时段选择器 utils/ request.js 云函数调用封装 cloudfunctions/ createOrder/ 创建订单 payCallback/ 支付回调 dispatch/ 派单逻辑 cancelOrder/ 取消订单

2.2 订单状态机:家政源码里最容易写崩的地方

家政订单不是简单的“待支付→已支付→已完成”。真实状态至少有:待支付、已支付待派单、已派单待接单、阿姨已接单、服务中、待确认完成、已完成、已取消、退款中、已退款。状态流转必须用云函数集中管理,不能在前端随便改。

// cloudfunctions/updateOrderStatus/index.js const cloud = require('wx-server-sdk') cloud.init() const db = cloud.database() // 合法状态流转表,防止前端乱传状态 const VALID_TRANSITIONS = { 'PENDING_PAY': ['PAID', 'CANCELLED'], 'PAID': ['DISPATCHED', 'REFUNDING'], 'DISPATCHED': ['ACCEPTED', 'PAID'], // 阿姨拒单退回 'ACCEPTED': ['IN_SERVICE', 'CANCELLED'], 'IN_SERVICE': ['PENDING_CONFIRM'], 'PENDING_CONFIRM': ['COMPLETED', 'REFUNDING'], 'REFUNDING': ['REFUNDED'] } exports.main = async (event) => { const { orderId, targetStatus, operator } = event const order = await db.collection('orders').doc(orderId).get() const current = order.data.status // 校验流转合法性 if (!VALID_TRANSITIONS[current] || !VALID_TRANSITIONS[current].includes(targetStatus)) { return { code: 400, msg: `非法状态流转: ${current} -> ${targetStatus}` } } // 权限校验:只有阿姨能接单,只有用户能确认完成 if (targetStatus === 'ACCEPTED' && operator.role !== 'aunt') { return { code: 403, msg: '只有阿姨可以接单' } } await db.collection('orders').doc(orderId).update({ data: { status: targetStatus, updateTime: db.serverDate(), [`timeline.${targetStatus}`]: db.serverDate() } }) return { code: 0 } }

这段代码的关键在VALID_TRANSITIONS表,它把业务规则固化在服务端。参数说明:orderId是订单唯一标识,targetStatus是目标状态,operator包含操作者角色。失败时先看云函数日志里的“非法状态流转”报错,八成是前端按钮没做状态判断。很多家政源码翻车就翻在这里——用户还没付款,阿姨端就显示可接单,最后两边扯皮。

2.3 阿姨排班表与时段冲突检测

家政预约最核心的约束是:同一个阿姨同一时段不能接两单。源码里通常用一张schedules集合存阿姨的可服务时段,下单时先查冲突。

// cloudfunctions/checkConflict/index.js const db = cloud.database() const _ = db.command exports.main = async (event) => { const { auntId, serviceDate, startTime, endTime } = event // 查询该阿姨当天已有订单,时间区间重叠即冲突 const conflict = await db.collection('orders') .where({ auntId, serviceDate, status: _.in(['DISPATCHED', 'ACCEPTED', 'IN_SERVICE']), startTime: _.lt(endTime), // 已有订单开始 < 新订单结束 endTime: _.gt(startTime) // 已有订单结束 > 新订单开始 }) .count() return { hasConflict: conflict.total > 0 } }

参数说明:serviceDate用YYYY-MM-DD字符串存,避免时区问题;startTime和endTime用HH:mm格式。冲突检测的经典错误是只比较开始时间相等,漏掉区间重叠。正确逻辑是“已有开始 < 新结束 且 已有结束 > 新开始”。如果查出来冲突但业务上允许阿姨赶场,那就在源码里加一个bufferMinutes缓冲时间,把endTime加上 30 分钟再比较。

3. 从零跑通家政小程序源码:环境、云函数与支付配置

3.1 本地环境搭建与源码导入的四个步骤

拿到一套家政上门预约服务小程序源码后,别急着点编译。先确认三件事:小程序 AppID 是否已开通云开发、微信支付商户号是否绑定、订阅消息模板是否申请。我一般按这个顺序操作:

第一步,用微信开发者工具导入源码根目录,工具会自动识别project.config.json。如果报“未找到 app.json”,说明导入的是外层文件夹,要选到miniprogram同级目录。

第二步,在app.js里替换云环境 ID:

// miniprogram/app.js App({ onLaunch() { wx.cloud.init({ env: 'your-env-id', // 替换成自己的云环境ID traceUser: true }) } })

第三步,右键cloudfunctions下每个云函数,选择“上传并部署:云端安装依赖”。注意payCallback依赖wx-server-sdk和tenpay,如果云端安装失败,检查云函数目录下是否有package.json。

第四步,在云开发控制台创建集合:orders、aunts、users、schedules、transactions。权限设置为“仅创建者可读写”的集合是users,其余设为“所有用户可读,仅管理端可写”。

3.2 微信支付配置:家政源码里最容易卡住的环节

家政订单通常收定金或全款,源码里支付逻辑走的是云函数调用统一下单。配置支付需要:商户号、APIv3 密钥、证书序列号、私钥文件。这些在cloudfunctions/payCallback/config.js里填。

// cloudfunctions/payCallback/config.js module.exports = { mchId: '1900000000', // 商户号 apiV3Key: 'your-api-v3-key', // APIv3密钥,32位 serialNo: 'your-serial-no', // 证书序列号 privateKey: `-----BEGIN PRIVATE KEY----- ...你的私钥内容... -----END PRIVATE KEY-----`, notifyUrl: 'https://your-domain.com/pay/notify' // 支付回调地址 }

参数说明:apiV3Key不是 API 密钥,是 APIv3 密钥,在商户平台“账户中心-API安全”里设置。serialNo是商户证书序列号,不是平台证书序列号。回调地址必须 HTTPS 且不能带端口。支付回调云函数里要验签,源码里一般用wxpay-jsapi或tenpay库。如果支付成功但订单没变状态,先看回调日志有没有“验签失败”,八成是apiV3Key填错。

3.3 派单逻辑:手动派单与自动派单的取舍

家政源码里派单有两种:手动派单是运营在后台选阿姨,自动派单是按距离和评分排序。我建议初期用手动派单,因为自动派单的权重调参是个玄学。源码里dispatch云函数通常长这样:

// cloudfunctions/dispatch/index.js const db = cloud.database() const _ = db.command exports.main = async (event) => { const { orderId, mode } = event const order = await db.collection('orders').doc(orderId).get() const { serviceDate, startTime, endTime, location } = order.data if (mode === 'auto') { // 查该时段空闲阿姨,按距离排序 const aunts = await db.collection('aunts') .where({ status: 'ACTIVE', serviceArea: _.geoNear({ geometry: location, maxDistance: 5000 })}) .get() // 过滤掉时段冲突的 const available = [] for (const aunt of aunts.data) { const conflict = await db.collection('orders').where({ auntId: aunt._id, serviceDate, status: _.in(['DISPATCHED', 'ACCEPTED', 'IN_SERVICE']), startTime: _.lt(endTime), endTime: _.gt(startTime) }).count() if (conflict.total === 0) available.push(aunt) } if (available.length === 0) { return { code: 404, msg: '该时段无可用阿姨' } } // 取评分最高的 available.sort((a, b) => b.rating - a.rating) await db.collection('orders').doc(orderId).update({ data: { auntId: available[0]._id, status: 'DISPATCHED' } }) return { code: 0, auntId: available[0]._id } } }

参数说明:mode为auto时走自动派单,location是 GeoPoint 类型。maxDistance单位是米。注意geoNear要求集合有地理位置索引,在云开发控制台给aunts集合的serviceArea字段建索引。自动派单的坑在于:评分最高的阿姨可能同时被多个订单选中,所以派单后要立即更新状态,或者用事务锁。

4. 家政预约源码避坑:订单、支付与排班的五个血泪教训

4.1 坑一:用户重复提交订单导致阿姨被重复预约

现象:用户网络卡顿连点两次提交,生成两笔相同订单,阿姨端看到两个待接单。原因:前端按钮没做防抖,云函数也没做幂等。解决:在createOrder云函数里用openid + serviceDate + startTime做唯一键,插入前先查重。

const exist = await db.collection('orders').where({ openid, serviceDate, startTime, status: _.neq('CANCELLED') }).count() if (exist.total > 0) return { code: 409, msg: '请勿重复下单' }

4.2 坑二:支付回调没验签导致订单状态被伪造

现象:有人直接调回调接口把订单改成已支付。原因:回调云函数没验签或验签逻辑写错。解决:用微信支付官方 SDK 验签,验签失败直接返回 500,并且回调处理要加transaction_id去重。

4.3 坑三:阿姨端和用户端状态不同步

现象:用户已取消,阿姨端还显示待接单。原因:取消订单只改了orders集合,没触发阿姨端刷新。解决:用云数据库watch监听订单变化,或者取消时往notifications集合写一条消息,阿姨端轮询。

4.4 坑四:服务时段跨天导致冲突检测失效

现象:阿姨晚上 23:00 到次日 1:00 的服务,和次日 0:00 到 2:00 的服务被判定不冲突。原因:serviceDate只存了开始日期,跨天时段没处理。解决:把startTime和endTime存成完整时间戳,或者跨天订单拆成两条记录。

4.5 坑五:退款金额算错,平台贴钱

现象:用户用了优惠券,退款时按原价退,平台多退钱。原因:退款逻辑没扣优惠分摊。解决:在transactions集合里记录每笔订单的实付金额和优惠金额,退款按实付退。

5. 家政小程序源码的二次开发:把派单算法换成自己的

源码跑通后,真正拉开差距的是派单算法。默认的“距离+评分”太粗糙,我一般会加三个因子:阿姨接单率、用户偏好、时段利用率。接单率低的阿姨降权,用户收藏过的阿姨加权,时段利用率高的阿姨优先派近单。

// 自定义派单评分 function scoreAunt(aunt, order) { const distanceScore = 1 - Math.min(aunt.distance / 5000, 1) // 距离越近越高 const ratingScore = aunt.rating / 5 const acceptRateScore = aunt.acceptRate || 0.5 const preferenceScore = order.userFavAunts?.includes(aunt._id) ? 1 : 0 return distanceScore * 0.3 + ratingScore * 0.25 + acceptRateScore * 0.25 + preferenceScore * 0.2 }

权重怎么调?先跑一周日志,看每个因子的方差,方差大的因子说明区分度高,权重可以加大。验证方法是 A/B 测试:一半订单用默认算法,一半用新算法,对比接单率和完成率。我踩过的坑是权重调得太激进,导致新阿姨永远接不到单,最后在评分里加了一个“新阿姨保护期”加权。

另一个进阶技巧是给源码加“预约提醒”:服务前 2 小时给阿姨和用户各发一条订阅消息。订阅消息模板要提前在小程序后台申请,云函数里用cloud.openapi.subscribeMessage.send发送。注意一次性订阅只能发一条,长期订阅需要用户每次授权。

最后说个习惯:每次改完派单逻辑,我都会用云开发控制台的“数据库回滚”功能先备份orders集合。家政订单数据丢了,用户和阿姨两边都找你,那可不是写个后悔药能解决的。希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进