
简介这是一套开箱即用的家政预约类微信小程序模板源码面向家政服务企业、个体创业者及小程序初学者解决线上服务预约系统快速搭建难题。资源共106个文件涵盖21个JS逻辑层代码、18个WXML页面结构、19个WXSS样式文件、22个JSON配置及24个PNG图标素材完整呈现前端交互、服务列表展示、时间预约、在线支付与评价反馈等核心功能模块压缩包仅1.18MB轻量易部署。已有307人学习下载适合无编程基础者通过配置快速上线也便于开发者基于现有结构扩展地图定位、预约提醒或多服务分类等功能。源码结构清晰含homepage.js、index.js、add.js等典型页面逻辑配合预览中的wxb.js工具库与多张界面截图可直观理解业务流程与UI组织方式是学习小程序工程化开发与生活服务类应用落地的实用参考。1. 家政预约微信小程序模板源码不是“拿来就能上线”的套壳工程而是能跑通「用户下单→服务派单→订单闭环」的最小可行业务骨架你花20分钟下载、解压、用开发者工具打开这个家政预约的微信小程序模板源码下载.zip会发现它不像某些“UI炫酷但点不动”的Demo——首页真能选保洁/搬家/维修服务点击立即预约后表单能提交、手机号能校验、订单号能生成、甚至模拟了师傅接单状态变更。这不是一个纯静态页面集合而是一套基于微信原生框架WXML/WXSS/JS构建、已预置基础云函数调用逻辑、数据库字段结构清晰、且明确标注了「需替换为真实环境」位置的业务型模板。它解决的不是“怎么写一个按钮”而是“如何让家政服务从微信里真正流转起来”用户侧有服务分类时间选择地址联动后台侧预留了订单状态机待接单/已接单/服务中/已完成/已取消、服务人员池管理入口、以及关键的微信登录手机号授权链路占位。适合两类人刚接手家政类小程序外包需求的前端工程师想快速验证业务流程是否合理或是创业团队技术负责人需要评估自建小程序的开发成本边界——它不承诺“零代码上线”但能让你在3小时内跑通从首页到订单详情页的全链路省掉至少80%的基建踩坑时间。2. 源码结构拆解与核心模块定位看清这6个文件夹你就掌握了家政小程序的“脏器分布”2.1 项目根目录miniprogram是唯一主战场project.config.json决定能否打开解压后你会看到典型的微信小程序目录结构但重点只在miniprogram/这个文件夹——它才是运行时实际加载的源码主体。project.config.json文件必须存在且配置正确否则开发者工具会报错“未找到 project.config.json”。该文件中appid字段默认为wx1234567890abcdef占位符首次打开前必须替换成你自己的小程序 AppID否则所有网络请求、登录、支付都会失败。注意这个 AppID 必须已在微信公众平台注册并完成认证个体工商户可认证否则云开发环境无法绑定。{ description: 家政预约小程序模板, setting: { urlCheck: true, es6: true, enhance: true, postcss: true, preloadBackgroundComponents: true, enableAppxNg: false, minified: true, newFeature: true }, compileType: miniprogram, libVersion: 2.28.4, appid: wx1234567890abcdef, // ← 此处必须修改 projectname: jiazheng-appointment-template }提示libVersion字段值2.28.4表示该模板基于微信基础库 2.28.4 版本开发。若你的开发者工具基础库版本低于此如 2.25.x部分新 API如wx.getPhoneNumber的needLogin参数可能不可用需升级工具或降级适配。2.2 页面层pages/下的5个核心页面每个都带业务语义命名pages/目录下不是一堆indexlistdetail这种泛化命名而是直接体现家政场景index/首页含服务分类轮播图 快捷入口保洁/家电清洗/搬家 热门服务卡片order/订单列表页区分「待服务」「已完成」「已取消」Tab每条订单显示服务类型、时间、状态标签book/预约下单页关键组件包括picker时间选择器限制未来7天、map地址选择器调用微信地图API、form表单含姓名、电话、服务需求描述my/个人中心页集成微信头像昵称、订单统计、常用地址管理、客服入口detail/订单详情页展示订单号、服务时间、地址、服务内容、师傅信息模拟数据、状态操作按钮仅用户可见“取消订单”师傅端需另行开发每个页面的.js文件中onLoad()函数内均有注释标明“此处应调用云函数获取数据”例如book.js中// book.js Page({ data: { serviceType: cleaning, selectedTime: , address: }, onLoad() { // 【关键占位】此处应调用云函数 getServicesList 获取服务类型列表 // 当前为模拟数据上线前需替换为真实云函数调用 this.setData({ services: [ { id: cleaning, name: 家庭保洁, price: 80元/小时 }, { id: appliance, name: 家电清洗, price: 120元/台 } ] }) } })2.3 云函数层cloudfunctions/中的3个函数是业务逻辑的“心脏”模板已预置云函数目录结构无需手动创建但需在开发者工具中右键“上传部署”云函数名功能说明关键参数注意事项createOrder创建订单生成订单号、校验手机号格式、写入云数据库orders集合serviceType,time,address,phone,userId依赖wx.cloud.database()需提前在云开发控制台创建orders集合并设置权限建议设为“仅管理员可读写”getOrderList查询用户订单列表按userId过滤支持分页userId,status,page,limit返回字段包含statusText中文状态映射避免前端硬编码状态文案updateOrderStatus更新订单状态如师傅接单需校验操作权限orderId,newStatus,operatorId,operatorTypeuser/staff当前未实现权限校验逻辑上线前必须补全否则任意用户可篡改他人订单状态注意云函数部署后其名称会自动同步到小程序端wx.cloud.callFunction()调用中。若修改函数名必须同步更新pages/order/order.js中的name: getOrderList字段。2.4 数据库设计cloudDB预置2个集合字段命名直指业务本质云开发数据库中模板已规划好两个核心集合orders集合订单主表_id: ObjectId自动生成orderNo: String格式JD202405200001年月日4位序号userId: String微信 openidserviceType: Stringcleaning, moving, repairtime: Date服务时间address: Object含province,city,district,detailstatus: Number0待接单, 1已接单, 2服务中, 3已完成, 4已取消createdAt: Date创建时间staff集合服务人员池仅作示意_id: ObjectIdname: Stringphone: Stringskills: Array[保洁, 家电清洗]available: Boolean是否可接单提示orders集合的status字段使用数字而非字符串是为了便于数据库索引优化和状态机流转判断。前端展示时通过statusMap {0:待接单,1:已接单...}映射避免在数据库中存冗余中文。2.5 登录与授权app.js中的onLaunch是整个流程的“第一道闸门”小程序启动时app.js的onLaunch函数执行初始化逻辑这里埋了两个关键钩子// app.js App({ onLaunch() { // 1. 检查登录态检查本地缓存的 openid const openid wx.getStorageSync(openid) if (!openid) { // 2. 未登录则跳转到 login 页面非强制可改为静默登录 wx.navigateTo({ url: /pages/login/login }) return } // 3. 已登录初始化全局数据 this.globalData.openid openid }, globalData: { openid: , userInfo: null } })而真正的微信登录与手机号获取被拆解到两个独立页面pages/login/login.js调用wx.login()获取 code再通过云函数login换取 openid模板中该云函数已存在但需部署pages/book/book.js在提交订单前调用wx.getPhoneNumber()获取加密手机号注意此 API 需用户主动触发 button 点击且 button 必须设置open-typegetPhoneNumber模板中book.wxml已写好该 button但bindgetphonenumber事件处理器为空需自行实现解密逻辑。提示wx.getPhoneNumber返回的encryptedData和iv必须由后端云函数调用微信接口auth.getPhoneNumber解密。小程序端无法直接解密——这是微信的安全设计也是新手最容易翻车的点试图在前端 JS 里用 AES 解密结果永远失败。2.6 样式与组件components/中的address-picker是复用性最高的自定义组件模板将地址选择逻辑封装为独立组件components/address-picker/其优势在于封装了wx.chooseLocation()调用、坐标逆解析wx.getAddress()、地址格式化省市区详细地址全流程对外只暴露bind:select事件父页面只需监听即可获取结构化地址对象支持disabled属性控制是否可编辑适配订单详情页只读场景在book.wxml中引用方式简洁address-picker bind:selectonAddressSelect disabled{{false}} /而address-picker.js的onSelect方法内部已处理了微信地图 API 的错误兜底如用户拒绝位置授权时自动 fallback 到手动输入框。这种封装极大降低了页面耦合度——后续开发“上门维修”页面时直接复用该组件即可无需重复写地图逻辑。3. 微信登录与手机号获取从wx.login()到wx.getPhoneNumber()的完整链路实操3.1wx.login()获取临时登录凭证 code不是 openid很多新手误以为wx.login()返回的就是 openid这是典型认知偏差。wx.login()实际返回的是一个一次性的code它必须发送给后端云函数由后端调用微信服务器接口https://api.weixin.qq.com/sns/jscode2session用appidappsecretcode换取openid和session_key。模板中cloudfunctions/login/index.js已实现该逻辑// cloudfunctions/login/index.js const cloud require(wx-server-sdk) cloud.init() exports.main async (event, context) { const { code } event try { const res await cloud.http.request({ method: GET, url: https://api.weixin.qq.com/sns/jscode2session?appid${process.env.APPID}secret${process.env.APPSECRET}js_code${code}grant_typeauthorization_code }) const { openid, session_key } res.data // 将 openid 存入数据库或返回给小程序端 return { openid, session_key } } catch (err) { console.error(login error:, err) return { error: login failed } } }关键点process.env.APPID和process.env.APPSECRET需在云开发控制台的「环境变量」中配置绝不能硬编码在代码里。APPSECRET 是敏感密钥泄露即导致小程序账号被盗用。3.2wx.getPhoneNumber()必须用button open-typegetPhoneNumber触发微信严格规定获取手机号必须由用户主动点击按钮触发且该按钮必须设置open-typegetPhoneNumber。模板中book.wxml的写法是合规的button classphone-btn open-typegetPhoneNumber bindgetphonenumberonGetPhoneNumber 授权获取手机号 /button当用户点击后onGetPhoneNumber事件处理器被触发event.detail中包含encryptedData和iv// book.js onGetPhoneNumber(e) { if (e.detail.errMsg getPhoneNumber:ok) { const { encryptedData, iv } e.detail // 将 encryptedData 和 iv 发送给云函数 decryptPhone wx.cloud.callFunction({ name: decryptPhone, data: { encryptedData, iv } }).then(res { console.log(手机号解密成功:, res.result.phoneNumber) this.setData({ phone: res.result.phoneNumber }) }) } }3.3 云函数decryptPhone用cloud.openapi安全解密微信官方推荐使用cloud.openapi模块调用auth.getPhoneNumber接口它比手动 HTTP 请求更安全自动携带 access_token// cloudfunctions/decryptPhone/index.js const cloud require(wx-server-sdk) cloud.init() exports.main async (event, context) { const { encryptedData, iv } event try { const result await cloud.openapi.auth.getPhoneNumber({ phoneNumber: encryptedData, iv: iv }) return { phoneNumber: result.phoneNumber } } catch (err) { console.error(decrypt failed:, err) return { error: decrypt failed } } }注意cloud.openapi.auth.getPhoneNumber是云开发提供的封装方法底层仍是调用微信服务器接口。它要求云函数运行环境为Node.js 16或更高版本且需在云开发控制台开通「开放能力」权限。3.4 前端手机号校验别只信wx.getPhoneNumber还得自己验格式即使微信返回了手机号前端仍需做二次校验因为用户可能在授权弹窗中点了“取消”此时e.detail.errMsg为getPhoneNumber:fail user deny但新手常忽略此分支导致phone字段为空提交微信返回的phoneNumber是标准 11 位数字字符串如13812345678但用户手动输入时可能带空格或-需统一清理模板中book.js的提交逻辑做了双保险submitOrder() { const { phone, address, time } this.data // 1. 前端格式校验 if (!/^1[3-9]\d{9}$/.test(phone)) { wx.showToast({ title: 手机号格式错误, icon: none }) return } // 2. 必填项校验 if (!address.province || !time) { wx.showToast({ title: 请完善地址和时间, icon: none }) return } // 3. 调用云函数创建订单 wx.cloud.callFunction({ name: createOrder, data: { phone, address, time } }) }3.5 状态持久化wx.setStorageSync(openid)是登录态的“后悔药”小程序没有 Cookie 概念登录态必须手动维护。模板采用最简方案wx.setStorageSync(openid, openid)将 openid 存入本地缓存。app.js的onLaunch会优先读取该缓存避免每次启动都重新登录。但要注意wx.setStorageSync有 10MB 本地存储上限且不同小程序间数据隔离。对于家政类应用openid 是核心标识必须确保其写入成功。因此模板在login.js中增加了写入校验// pages/login/login.js wx.cloud.callFunction({ name: login, data: { code } }).then(res { const { openid } res.result if (openid) { wx.setStorageSync(openid, openid) // 写入后立即读取验证 const cached wx.getStorageSync(openid) if (cached openid) { wx.switchTab({ url: /pages/index/index }) } else { wx.showToast({ title: 登录态保存失败, icon: none }) } } })提示wx.setStorageSync是同步 API但写入失败不会抛异常只会静默失败。因此必须用wx.getStorageSync反向验证这是生产环境必备的“血泪经验”。3.6 避坑微信登录与手机号获取的5个高频翻车点现象1wx.login()返回的 code 过期导致jscode2session接口返回invalid code原因code 有效期仅为 5 分钟且只能使用一次。若用户从登录页跳转到其他页面再回来code 已失效。解决在onLoad中立即调用wx.login()获取 code 后立刻传给云函数不要缓存 code。模板中login.js的onLoad即执行登录规避此问题。现象2wx.getPhoneNumber()点击无反应控制台报open-typegetPhoneNumber is not supported原因开发者工具版本过低 1.05.2205100或小程序基础库版本低于 2.21.0。解决升级开发者工具至最新版并在project.config.json中将libVersion设为2.28.4或更高。现象3云函数decryptPhone报错access denied原因未在云开发控制台开通「开放能力」或云函数环境未启用Node.js 16。解决进入云开发控制台 → 「设置」→ 「开放能力」→ 开启「手机号」权限云函数详情页 → 「运行环境」→ 选择Node.js 16。现象4用户授权手机号后e.detail.errMsg为getPhoneNumber:fail但无具体错误信息原因用户在系统设置中关闭了小程序的“获取手机号”权限或微信版本过低 8.0.30。解决增加引导提示“如无法获取手机号请前往微信【我】→【设置】→【隐私】→【授权管理】中开启权限”并在bindgetphonenumber中捕获e.detail.errMsg并友好提示。现象5wx.chooseLocation()在真机上白屏或返回坐标但逆解析失败原因未在微信公众平台开通「地图组件」权限或wx.getAddress()接口未在app.json的permission中声明。解决登录微信公众平台 →「开发管理」→「开发设置」→「接口设置」→ 开通「获取地理位置」和「获取用户地址」app.json中添加permission: { scope.userLocation: { desc: 用于获取您的位置信息以便为您推荐附近服务 }, scope.address: { desc: 用于获取您的详细地址以便安排师傅上门 } }4. 订单状态机与派单逻辑从“用户下单”到“师傅接单”的状态流转设计4.1 状态定义5个数字状态码覆盖家政服务全生命周期模板采用整型状态码而非字符串既节省数据库空间又便于状态机流转判断。orders集合中的status字段定义如下状态码中文含义前端展示样式可触发操作备注0待接单黄色标签无等待师傅抢单用户提交后初始状态1已接单蓝色标签用户可“取消订单”师傅可“开始服务”师傅点击“接单”按钮后更新2服务中橙色标签用户可“联系师傅”师傅可“完成服务”师傅点击“开始服务”后更新3已完成绿色标签用户可“评价”师傅无操作师傅点击“完成服务”后更新4已取消灰色标签无用户或师傅主动取消前端通过statusMap对象映射展示文案避免硬编码// utils/status.js const statusMap { 0: { text: 待接单, color: #FFA500 }, 1: { text: 已接单, color: #1890FF }, 2: { text: 服务中, color: #FAAD14 }, 3: { text: 已完成, color: #52C418 }, 4: { text: 已取消, color: #BFBFBF } } module.exports { statusMap }4.2 状态更新updateOrderStatus云函数的原子性保障订单状态变更必须保证原子性防止并发冲突如两个师傅同时点击“接单”。模板中updateOrderStatus函数使用db.collection(orders).doc(orderId).update()的field更新方式并加入条件判断// cloudfunctions/updateOrderStatus/index.js exports.main async (event, context) { const { orderId, newStatus, operatorId, operatorType } event const db cloud.database() try { // 1. 先查询原订单校验当前状态是否允许变更 const order await db.collection(orders).doc(orderId).get() if (!order.data) throw new Error(订单不存在) const oldStatus order.data.status // 2. 状态流转规则校验仅允许合法流转 const validTransitions { 0: [1], // 待接单 → 已接单 1: [2, 4], // 已接单 → 服务中 / 已取消 2: [3, 4], // 服务中 → 已完成 / 已取消 3: [], // 已完成不可变 4: [] // 已取消不可变 } if (!validTransitions[oldStatus].includes(newStatus)) { throw new Error(状态 ${oldStatus} 不可流转至 ${newStatus}) } // 3. 执行更新带条件防止并发覆盖 await db.collection(orders).doc(orderId).update({ data: { status: newStatus, updatedAt: db.serverDate(), ...(operatorType staff ? { staffId: operatorId } : {}), ...(newStatus 1 ? { acceptTime: db.serverDate() } : {}) } }) return { success: true } } catch (err) { console.error(update status failed:, err) return { error: err.message } } }关键点db.serverDate()确保时间戳由服务器生成避免客户端时间篡改...(operatorType staff ? { staffId: operatorId } : {})仅在师傅操作时记录staffId为后续派单分析留痕。4.3 师傅端逻辑如何让“接单”按钮只对匹配师傅可见模板当前仅实现用户端但为扩展师傅端预留了接口。核心思路是师傅端首页需实时拉取“待接单”订单并按技能匹配过滤。假设staff集合中师傅skills: [保洁, 家电清洗]则查询待接单订单时需匹配serviceType// 师傅端伪代码需另建 staff-pages/ const db cloud.database() const res await db.collection(orders).where({ status: 0, // 待接单 serviceType: _.in([cleaning, appliance]) // 匹配师傅技能 }).orderBy(createdAt, desc).get()提示_.in是云数据库的数组包含查询操作符比更精准。若师傅技能为[保洁]则serviceType: cleaning的订单会被匹配而moving不会。4.4 订单号生成JD202405200001格式背后的防重逻辑订单号JD202405200001由三部分组成前缀JD 日期20240520 序号0001。模板中createOrder函数通过数据库事务保证序号唯一// cloudfunctions/createOrder/index.js exports.main async (event, context) { const { serviceType, time, address, phone, userId } event const db cloud.database() const _ db.command try { // 1. 查询今日最大序号 const today new Date().toISOString().slice(0, 10).replace(/-/g, ) // 20240520 const maxOrder await db.collection(orders).where({ orderNo: _.regex({ regexp: ^JD${today}\\d{4}$ }) }).orderBy(orderNo, desc).limit(1).get() let seq 1 if (maxOrder.data.length 0) { const lastSeq parseInt(maxOrder.data[0].orderNo.slice(-4)) seq lastSeq 1 } // 2. 生成订单号 const orderNo JD${today}${seq.toString().padStart(4, 0)} // 3. 写入数据库 await db.collection(orders).add({ data: { orderNo, userId, serviceType, time, address, phone, status: 0, createdAt: db.serverDate() } }) return { orderNo } } catch (err) { console.error(create order failed:, err) return { error: create failed } } }注意此逻辑在高并发下仍有极小概率重复如两请求同时查到seq1生产环境应改用云数据库的autoIncrement字段或分布式 ID 生成器。模板作为入门级方案已足够应对中小家政公司日均百单量级。4.5 状态通知如何让师傅实时收到新订单微信小程序不支持服务端主动推送必须依赖客户端轮询或订阅消息。模板采用轻量级轮询方案师傅端页面onShow时启动定时器每30秒调用getOrderList查询status: 0的订单为减少无效请求可增加lastCheckedTime缓存只查询createdAt lastCheckedTime的新订单// staff-pages/index/index.js Page({ data: { newOrders: [] }, onLoad() { this.checkNewOrders() }, checkNewOrders() { const lastTime wx.getStorageSync(lastCheckedTime) || 0 wx.cloud.callFunction({ name: getOrderList, data: { status: 0, since: lastTime } }).then(res { if (res.result.data.length 0) { this.setData({ newOrders: res.result.data }) wx.showTabBarBadge({ index: 0, text: res.result.data.length.toString() }) } wx.setStorageSync(lastCheckedTime, Date.now()) // 继续轮询 setTimeout(() this.checkNewOrders(), 30000) }) } })提示wx.showTabBarBadge在 TabBar 上显示红点徽章是提升师傅响应速度的有效手段。但需在app.json的tabBar配置中为对应页面开启badge权限。4.6 避坑订单状态流转的3个玄学陷阱现象1用户取消订单后状态变为4但师傅端仍显示“已接单”原因状态更新未广播师傅端缓存的订单数据未刷新。解决用户取消后在updateOrderStatus云函数中触发一个云函数事件如sendNotification通知相关师傅或师傅端页面onShow时强制重新拉取订单列表。现象2两个师傅同时点击“接单”订单状态变为1但staffId只记录了后一个师傅原因updateOrderStatus未加锁第二个请求覆盖了第一个请求的staffId。解决在更新时加入where条件确保只更新status 0的订单await db.collection(orders).doc(orderId).update({ data: { status: 1, staffId: operatorId }, where: { status: 0 } // ← 关键只有原状态为0才更新 })现象3订单状态从1变为2后用户端订单详情页未刷新状态原因页面未监听onShow事件或onShow中未调用getOrderDetail重新拉取数据。解决在detail.js的onShow中添加onShow() { const orderId this.data.orderId wx.cloud.callFunction({ name: getOrderDetail, data: { orderId } }) .then(res this.setData({ order: res.result })) }5. 本地调试与真机联调绕过“开发者工具假数据”直连真实微信环境5.1 开发者工具调试用“条件编译”区分测试与生产环境模板中所有云函数调用均通过wx.cloud.callFunction()但env参数需动态切换。app.js中定义全局环境变量// app.js App({ globalData: { env: release, // test or release cloudEnv: jiazheng-test // 测试环境云开发环境ID } })在utils/request.js中封装请求方法自动注入env// utils/request.js const app getApp() function callCloud(name, data) { return wx.cloud.callFunction({ name, data, env: app.globalData.env test ? app.globalData.cloudEnv : true // true 表示使用默认环境 }) } module.exports { callCloud }提示env: true表示使用小程序默认云开发环境无需显式指定。测试时设为具体环境ID上线前改为true即可。5.2 真机调试wx.openSetting()是排查权限问题的终极武器真机上常因权限缺失导致功能异常如地图白屏、手机号获取失败。模板在pages/book/book.js的onLoad中加入一键授权入口// book.js onLoad() { // 检查必要权限 wx.getSetting({ success: (res) { if (!res.authSetting[scope.userLocation]) { this.setData({ needLocationAuth: true }) } if (!res.authSetting[scope.address]) { this.setData({ needAddressAuth: true }) } } }) }, openSetting() { wx.openSetting({ success: (res) { console.log(openSetting success, res) // 权限开启后重新执行业务逻辑 this.checkPermissions() } }) }book.wxml中对应按钮view wx:if{{needLocationAuth || needAddressAuth}} classauth-tip text请授权位置和地址权限/text button bindtapopenSetting classauth-btn去设置/button /view关键点wx.openSetting()会跳转到系统设置页用户开启权限后返回小程序此时onShow会再次触发可重新检查权限状态。5.3 日志追踪console.log在真机上不可见必须用wx.reportAnalytics开发者工具的console.log在真机上不输出调试时需依赖微信提供的wx.reportAnalytics上报关键节点// book.js submitOrder() { wx.reportAnalytics(order_submit_start, { serviceType: this.data.serviceType }) // ... 提交逻辑 wx.reportAnalytics(order_submit_success, { orderNo: res.result.orderNo }) }然后在微信公众平台 →「数据分析」→「自定义事件」中查看上报数据可精准定位哪一步失败。5.4 网络抓包用charles抓取微信小程序真实请求非模拟虽然模板基于云开发但若后续接入第三方 API如支付、短信需抓包分析。charles是主流方案但需注意微信小程序默认不信任charles证书需在 iOS 设置中安装并信任证书Android 需在系统证书管理中导入本文还有配套的精品资源点击获取