ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于uni-app和Vue的健身房私教预约系统开发实战

基于uni-app和Vue的健身房私教预约系统开发实战 先交代一下背景最近我一直在用uni-app Vue做一套健身房教练私教预约系统项目同时输出微信小程序端和管理后台整个开发周期从需求梳理到上线迭代差不多一个半月。这套系统面向的用户是健身房店长和私教会员核心场景是会员在微信小程序里查看教练排期、预约私教课、购买课时包教练侧则需要在微信小程序和后台里维护自己的可约时间、确认或取消预约、记录上课结果。今天把整个项目从设计到落地的关键节点拆开聊一聊顺便把我在这个过程中踩过的坑、验证过的方案整理出来想做私教预约、上门服务预约这类项目的朋友可以直接抄作业。1. 内容整体设计与思路拆解1.1 核心需求解析和目标用户画像私教预约系统和普通的商品预约系统有个明显的区别预约的核心资源不是“物品”而是“教练的时间段”。所以整个项目的数据模型、业务流程、甚至是页面上“时间卡位”的交互逻辑都必须围绕教练的可约时段来设计。先梳理我的目标用户一共三类角色会员端微信小程序内完成登录、查看教练、查看排期、预约课程、购买课程、取消预约。教练端微信小程序内查看自己的课表、上下课签到、修改可预约时间偶尔在管理后台处理会员的约课审批。管理者端主要是系统管理后台H5网页/桌面浏览器负责维护教练资料、课程信息、课时包、统计核销数据。从这三类角色出发我给系统划分了核心功能模块会员模块、教练模块、课程课时包模块、排期预约模块、订单与支付模块、消息通知模块、数据统计模块。其中排期预约模块是整个系统的业务核心后面我会重点讲。1.2 技术选型为什么是uniapp vue这里先说结论如果你做的是一次性交付多个平台的小程序或Appuniapp vue确实是我的首选组合理由非常实际最直接的理由是“一套代码多端输出”。我这次主要输出微信小程序但健身房这个行业经常希望以后能顺手出个支付宝小程序、抖音小程序、甚至Android/iOS的App。uni-app用Vue语法开发一次编译到微信小程序、支付宝小程序、H5、App都没有问题后期扩展成本低很多。Vue的开发体验确实舒服。Vue的双向绑定、组件化、生命周期管理在小程序原生开发里都是需要自己手写或借助第三方框架解决的。用Vue的语法写微信小程序心智负担会小不少尤其是在复杂页面的数据联动上Vue的响应式系统帮了大忙。uni-app内置了很多跨端API。比如uni.login、uni.request、uni.getLocation等虽然是统一的API但编译到微信小程序时会自动转为wx.login、wx.request。这意味着你不需要为微信小程序单独写一套数据请求逻辑开发速度和代码整洁度都会高很多。生态和组件库的优势。uni-app有uView UI、uni-ui等组件库像预约日历、时间选择、表单校验这些通用组件都有现成的不用自己从零造轮子。当然技术选型也要有边界。如果你的项目锁定只在微信小程序这一个平台、并且团队已经非常熟悉原生小程序开发那原生开发也是可以的。但在“多端兼顾、快速迭代”这个诉求下uni-app是个务实的选择。1.3 整体架构与数据库设计思路整个项目的前后端我采用的方案是前端uni-app后端选择Node.js Express或Koa数据库使用MySQL缓存用Redis。这里重点说数据库设计因为预约系统90%的坑都在数据一致性上。核心数据表如下member会员表存储会员基础信息union_id手机号昵称头像。coach教练表姓名、照片、擅长课程、简介、经验年限。course课程表课程名称、所属分类、单人/小团课、课时数、价格。course_package课时包表套餐名称、含课节数、有效期、价格、状态。schedule排期表教练ID、课程ID、可预约时段begin_time、end_time、已预约人数、可预约总人数、状态。appointment预约记录表会员ID、教练ID、课程ID、排期ID、预约状态、创建时间、取消时间、签到时间。order订单表会员ID、商品类型课时包/单次课、商品ID、实付金额、支付状态、支付渠道。notification消息通知表接收人、消息类型、消息内容、是否已读。这个表结构中承载预约系统业务的核心就是schedule和appointment两张表。排期表锁定了教练的“可约时间段”预约记录表负责记录“谁在哪个时间段约了哪节课”。支付和课时包订单虽然必不可少但它们和预约流程是解耦的。2. 核心细节解析与实操要点2.1 微信小程序端的页面设计与路由规划微信小程序的页面我这边按用户角色拆成了两套会员端页面pages/index/index首页展示热门教练、推荐课程、健身资讯。pages/coach/list教练列表支持按擅长课程筛选。pages/coach/detail教练详情展示照片、简介、课程列表、可约排期日历。pages/appointment/booking预约页选择课程、选择时间、提交预约。pages/appointment/list我的预约列表区分待上课、已完成、已取消。pages/member/profile个人中心课时包信息、优惠券、设置。教练端页面pages/coach/home教练首页今日课表、待确认预约提醒。pages/coach/schedule排期管理设置可预约时间段。pages/coach/appointment_detail预约详情申请请假或确认上课。这个路由规划的好处是“会员流程”和“教练流程”完全分离。会员只需要关注约课教练只需要关注管理排期和上课不会互相干扰。2.2 基于uni-app的车位时段预约设计思路私教预约本质上是一种“资源预约”资源是教练的时间段。我把时间段称为“时段”。每个时段最小粒度设为30分钟从早上8点到晚上10点共28个时段——当然这个时段长度可以根据健身房运营情况调整30分钟是系统设计上的最小单位。现实运营中私教课一般是60分钟或90分钟所以教练创建排期的时候不是让教练自己选“每周一 18:00-19:00”而是让教练提交“每周固定工作时间段”例如“周一、周三、周五 18:00-22:00”系统再自动按课程时长切成具体的“可预约slot”例如18:00-19:00、19:00-20:00、20:00-21:00。这样做的好处是教练侧的录入成本最低不需要一个个去创建时段。系统侧的时段粒度可控避免教练手动乱填。会员侧展示清晰选“时间段”比选“节点”更直观。2.3 时间冲突校验和并发控制的实现这是整个系统里最容易出问题的地方也是最需要经验积累的部分。场景是这样教练周二18:00-19:00这个时段只放出1个可约名额私教课通常是一对一如果两个会员在同一个时间同时点击预约系统会出现“超卖”。解决并发预约问题的常用方案有两种方案一数据库层加锁。在appointment写入前先对对应的schedule行进行SELECT ... FOR UPDATE行级锁确认当前时段的可预约名额还有余量再插入预约记录并更新额度。这个方案在MySQL InnoDB引擎下是最稳妥的能真正避免并发超卖。方案二Redis分布式锁 原子自减。在写入数据库前用Redis对“schedule_id 时段”做SETNX加锁锁成功后再检查余量并扣减。这种方式性能和跨服务能力更好适合系统拆分更细的场景。我这次的项目规模采用方案一就足够了配合事务BEGIN/COMMIT可以保证数据一致性。我列一下伪代码BEGIN; SELECT * FROM schedule WHERE id ? AND appointment_count max_count FOR UPDATE; -- 在业务代码中判断是否还有余量无则回滚 UPDATE schedule SET appointment_count appointment_count 1 WHERE id ?; INSERT INTO appointment (member_id, coach_id, schedule_id, ...) VALUES (...); COMMIT;这个方案在并发量不是特别极端的情况下完全够用。如果你是做全国连锁级别的健身品牌再考虑引入分布式锁和消息队列也不迟。2.4 会员登录与微信授权微信小程序登录看起来简单真做起来还是会踩坑的。最主要的坑不要依赖wx.getUserProfile返回的真实昵称和手机号来做核心用户标识。微信早就收紧了用户昵称头像能力现在头像昵称获取要么走“头像昵称填写能力”用户主动填写要么用open-typechooseAvatar和input typenickname来让用户体验更顺滑。更关键的是你的系统必须用wx.login返回的code在后端换取openid和session_key。会员唯一标识应该是openid或unionid而不是昵称。我实现登录的方式是小程序启动时前端调用uni.login获取code将code通过uni.request发给后端后端用该code调微信接口换取openid和session_key。如果数据库中发现该openid不存在则自动注册新用户。如果用户绑定了手机号通过uni.getPhoneNumber按钮则再更新手机号字段。这样用户就算不授权手机号也能先浏览预约流程真正预约时再要求绑定手机号即可。2.5 页面跳转和权限控制微信小程序页面跳转我用的是uni-app的uni.navigateTo、uni.redirectTo、uni.switchTab。但有一件事必须提醒新手tabBar页面只能用uni.switchTab跳转不能用uni.navigateTo否则会报错“页面不存在或非tabBar页面”。我一开始没注意首页跳转个人中心一直失败排查了半天才发现是跳转方式用错了。权限控制方面我封装了一个auth.js工具专门负责检查当前用户是否登录本地存储中不存openid——openid是敏感信息不能直接暴露在前端前端只应存token。未登录时统一跳转到登录页并携带redirect_url作为回调参数。已登录但个人信息不完整时比如没绑手机号拦截到补全信息页面。3. 实操过程与核心环节实现3.1 项目初始化和工程结构搭建新建uni-app项目我选择的是vue3vite版本。如果你还在用vue2建议尽早考虑迁移因为很多uni-app的新特性已经不再维护vue2版本了。初始化命令npx degit dcloudio/uni-preset-vue#vite my-gym-app cd my-gym-app npm install npm run dev:mp-weixin如果不想命令行操作也可以直接用HBuilderX创建项目可视化界面下选择“uni-app项目”模板即可。项目跑起来之后会生成dist/dev/mp-weixin目录用微信开发者工具选择“导入项目”就能打开编译好的小程序。工程结构我一般这样组织src/ |-- pages/ // 页面集合 |-- components/ // 公共组件 |-- api/ // 接口请求模块按业务拆分 | |-- appointment.js | |-- payment.js |-- store/ // Pinia/Vuex状态管理 |-- utils/ // 公共工具函数 |-- static/ // 静态资源 |-- App.vue |-- main.js |-- manifest.json // uni-app应用配置 |-- pages.json // 页面路由与tabBar配置] 注意使用uni.request封装请求时要统一带上token请求头并且要对返回状态码做统一拦截处理比如401跳转到登录页。我自己维护了一个request.js就是基于uni.request封装Promise化的请求函数避免回调地狱。3.2 教练排期与会员预约的完整流程实现我以会员预约一节6月的私教课为例讲讲完整链路。第一步教练创建/维护排期教练在“排期管理”页选择未来两周的某几天设置开始时间、结束时间、课程时长接口请求后端生成对应的schedule记录。生成的时候后端会做一次校验排除掉已经存在相互重叠的时段。接口设计大概是// 生成排期 POST /api/coach/schedule { coachId: 12, courseId: 3, date: 2024-06-18, timeSlots: [ 18:00-19:00, 19:00-20:00 ] }第二步会员查看教练可约时段会员进入教练详情页调用接口获取该教练未来几天带可约时段的日历GET /api/coach/detail?coachId12startDate2024-06-18endDate2024-06-24返回的数据里我会专门用available字段标记对应时段是否还能约。available: true时按钮高亮否则置灰并提示“已约满”。第三步会员提交预约会员选择时段点击“立即预约”。这时候前端只需要把coachId、courseId、scheduleId传给后端。后端收到请求后执行我在上面说的事务逻辑检查schedule是否存在且状态为enabled。检查appointment_count是否小于max_count。检查该会员在这个时间段是否已经预约了防止重复预约导致冲突。扣减名额生成预约记录状态为“待上课”。第四步支付/课时包扣减预约私教课有两种支付方式单次支付走微信支付预支付订单用户支付成功后预约才生效。课时包扣减用户在购买课时包后系统给会员账户充入对应的课时余额。预约时从余额里扣减。我这边设计的是“先预约再扣课”和“先支付再确认”两条并行路径。因为实际操作中教练和会员之间常有口头预约或者教练临时有空闲课时需要打折出售的灵活场景所以纯硬扣课时币并不人性化。3.3 微信支付接入的完整落地过程微信小程序的支付流程是前端uni.requestPayment发起支付需要后端先生成预支付订单拿到payment参数时间戳、随机字符串、签名等。我实际接入时的步骤是这样用户提交订单请求后端createOrder。后端生成商户订单号OrderNo调用微信支付的统一下单接口获取prepay_id。后端用获取到的参数调wx.requestPayment发给前端。前端收到支付成功的回调后调用后端接口confirmPayment后端通过微信支付查询接口确认订单状态然后变更订单状态和课时余额。支付回调这里有一个需要特别留意的问题前端收到支付成功回调不等于订单最终成功。在弱网环境和部分极端情况下前端可能因为回调异常没拿到成功状态用户其实已经转账成功。所以必须在后端提供主动查询接口在“我的订单”页加载时向后端查询订单真实状态避免页面展示和实际支付状态不一致。3.4 底部导航和我的页面pages.json里直接配tabBar{ pages: [ { path: pages/index/index, style: { navigationBarTitleText: 首页 } }, { path: pages/appointment/list, style: { navigationBarTitleText: 我的预约 } }, { path: pages/member/profile, style: { navigationBarTitleText: 个人中心 } } ], tabBar: { color: #999999, selectedColor: #0f8b4b, list: [ { pagePath: pages/index/index, text: 首页 }, { pagePath: pages/appointment/list, text: 预约 }, { pagePath: pages/member/profile, text: 我的 } ] } }tabBar的图标可以自己准备注意尺寸要符合微信小程序规定建议用81px * 81px的PNG图片不要用网络图标避免加载失败出现空白。3.5 数据统计与后台管理后台管理页面我单独做了一个基于Vue3 Element Plus的管理端。功能包括教练管理新增编辑删除教练管理教练关联课程。排期总览按日/周查看所有教练的预约情况支持手动修改预约。订单管理查看所有订单支持退款核销课时。财务报表按日/周/月查看营收、预约率、取消率。这一块界面不多主要价值在于高效解决运营问题。“预约率”是我在后台重点统计的数据计算公式是已预约时段数 / 可预约时段总数。当预约率低于40%时系统会提醒运营人员增加教练推广或调整排期策略。4. 常见问题与排查技巧实录4.1 uni.requestPayment 无法拉起微信支付这个痛点比较常见。我先列原因再给排查方法。原因1小程序没有绑定微信支付商户号或者主体不一致。开发者工具中可以打开“详情-项目配置-服务内容声明”确认是否开通了微信支付。原因2支付参数签名错误。最容易错的地方是timeStamp必须是字符串类型number类型会报错。原因3在开发者工具里体验版和开发版不能用真实的微信支付必须用测试号或者真机预览。排查方法你可以在后端把给前端的支付参数打印出来和微信商户平台中的参数一一比对。遇到requestPayment:fail错误时去微信官方文档里查对应的errMsg最常见的变化是invalid signature和missing parameter。4.2 预约日历在 iPhone 上显示错位这是典型的CSS兼容性问题。我在项目里用到的日历组件是基于uni-datetime-picker改造的安卓上显示正常iPhone上出现了日期错位或无法滑动。排查定位的结果是微信小程序的iOS渲染对position: fixed和overflow-y: auto的组合支持不够稳定。解决方案是给日历容器加transform: translateZ(0)强制开启GPU加速或者改用scroll-view组件配合scroll-y来实现日期滚动。另外iOS里日期格式要避免直接传2024-06-18这种字符串用getTime()生成的时间戳才能保证时间解析一致。4.3 微信小程序中 H5 页面返回箭头消失因为没有等onLoad完成就跳转了或者页面栈被清理了。在使用uni.navigateTo打开web-view页后如果页面又从其他入口进了新页面返回栈就被顶掉了。解决方法是在web-view页面中使用onUnload去做页面栈检查尽量避免连续跳转。4.4 微信开发者工具加载白屏manifest.json中的mp-weixin配置里把lazyCodeLoading设置成了requiredComponents在某些低版本基础库下会白屏。排查方法把基础库版本切换到最新稳定版。如果白屏无法解决关闭treeShaking或lazyCodeLoading配置试试。4.5 打包上传之后页面样式错乱这个坑很隐蔽但很致命。我遇到的现象是本地开发和开发者工具里一切正常上传体验版之后部分组件样式错乱。最后定位是样式单位适配问题。如果你用了uni-app的rpx单位但又在个别页面用了px混合排版在小程序编译到不同屏幕尺寸时可能出现差异。解决方法是所有自定义组件内部统一使用rpx全局字体可以用vw/vh做适配最少用px。如果确实需要px比如边框线也要按750设计稿换算成对应rpx值。4.6 常见问题速查表常见问题可能原因解决方法登录后无网络请求token未在请求头中携带统一封装请求拦截器里自动加token订单支付后状态未更新前端回调无效后端主动查询支付状态预约提交后“时段超卖”未使用行锁/事务使用SELECT ... FOR UPDATEiOS滑动卡顿旧版基础库合成层异常升级基础库或启用GPU加速日期组件错位小程序渲染差异改用scroll-view自绘日历图片无法展示域名未配置到白名单小程序后台添加downloadFile合法域名4.7 微信小程序抓包开发调试阶段Charles抓包能帮你看到前端到后端的所有请求详情。但小程序上线后微信平台会自动开启强校验部分请求抓包会比较困难。我的做法是本地开发时后端接口用HTTP配合Charles抓包和断点调试上线环境统一HTTPS抓包仅作为参考实际调试还是以后端日志为准。5. 一些实操心得和避坑总结5.1 关于预约系统设计的核心心法做预约系统最容易犯的错误就是把所有业务规则都塞到前端页面里。实际上预约系统的“可预约名额”、“可预约时间”、“取消规则”这些业务能力一定要在后端统一收敛。前端只负责展示和后端返回的数据不直接决定“这个时段能不能约”。这样才能避免前端改动导致业务逻辑失控也方便日后多端微信小程序、App、H5接入时复用同一套接口逻辑。我实际开发中后端接口的返回结构一般是这样{ code: 0, message: ok, data: { canBook: true, reasonCode: , earliestTime: 2024-06-18 08:00:00, latestTime: 2024-06-18 22:00:00 } }canBook就是后端拍板的结果前端不需要再额外计算。5.2 微信小程序与 H5 的差异处理uni-app支持输出H5版但有些API在H5端和微信小程序端行为不同。比如uni.login在H5端是不存在的比如uni.getLocation在H5端依赖浏览器的定位授权。这些API的使用场景要有条件判断或平台判断// #ifdef MP-WEIXIN uni.login({ success: (res) { // 拿到code发送后端 } }); // #endif // #ifdef H5 // H5环境走自己的登录逻辑比如扫码登录或账号密码 // #endif如果你们未来要嵌H5进公众号授权逻辑和定位逻辑必须单独处理。比如微信公众号里获取定位需要在前端页面用wx.config去调用JS-SDK这个和微信小程序里的uni.getLocation完全是两码事。5.3 自动打包发布与持续部署uni-app项目推荐配置一套自动化构建流水线。我的做法是使用GitLab CI/CDpush到master分支后触发构建。构建命令是npm run build:mp-weixin产物目录是dist/build/mp-weixin。再把产物目录上传到微信小程序后台使用微信的CI机器人miniprogram-ci完成上传。miniprogram-ci是微信官方提供的命令行上传工具使用私钥即可实现自动上传本地不再需要人工打开开发者工具点上传。在项目中配置node脚本const ci require(miniprogram-ci); const project new ci.Project({ appid: 你的小程序appid, type: miniProgram, projectPath: dist/build/mp-weixin, privateKeyPath: path/to/private.key, ignores: [node_modules/**/*] }); const uploadResult await ci.upload({ project, version: 1.0.0, desc: 自动部署, setting: { es6: true, minify: true } });直接在package.json里配置npm run upload就能触发上传。5.4 关于微信小程序审核与上架建议健身私教预约涉及用户信息和在线支付微信小程序审核有几个坑第一类目选择。如果你涉及课程售卖和支付一般建议选择“教育 在线视频课程”或“生活服务 运动健身”具体看实际业务。如果选择运动健身可能需要提供相关资质文件。第二隐私政策。小程序必须展示《用户隐私保护指引》尤其是涉及手机号绑定和位置信息时必须在用户第一次使用或触发该功能时弹窗告知。第三支付类目审核。使用微信支付需要在商户平台开通对应的支付产品商户号和Appid的绑定关系要提前处理好否则审核或真实支付阶段会遇到“商家账号异常”之类的报错。第四上线前测试。在提审前用体验版把完整的会员预约、教练端操作、支付全链路流程走一遍。微信审核团队会模拟用户注册、下单、支付任何一个环节有问题都可能被拒。5.5 技术栈扩展与团队协作建议这套系统的架构虽然不是Big名字级别的但在真实业务场景里非常可靠。如果团队未来要做大了几个可以提前规划的方向数据层引入分库分表当预约记录到达百万级时单表查询会有性能瓶颈。引入消息队列当遇到赛事活动、高峰秒杀等场景用RabbitMQ或RocketMQ做削峰填谷。引入WebSocket教练端实时接收新预约通知、会员端实时看到教练状态变更为“已约满”相比轮询体验会好很多。团队协作上我建议前端代码和后端API并行开发。前端先按约定好的接口文档Mock数据后端按约定开发并逐步接入真实接口。这样能缩短整个项目周期将近三分之一。6. 写在项目最后的一点体会这个项目从需求评审到上线我最大的感受是预约系统看上去是个“小项目”但因为涉及支付、时间冲突、多角色权限、多端兼容实际复杂度比预期高得多。真正把系统做稳定靠的不是某个炫酷的功能点而是把一个一个多小时细节死磕清楚——比如并发预约的锁、支付回调的状态同步、iOS的日历渲染、审核时机的类目选择。如果你正在做同类项目我的建议是先把业务流程梳理清楚——从用户进店、选课、约课、付款、上课、确认核销走一遍全流程再回来写代码。另外小程序端不要一口气把所有功能全堆上去先交付“教练展示排期查看预约提交”最小可用版本再逐步迭代支付、课时包、统计报表这些进阶功能。最后再分享一个小细节项目初期为了快速验证我用的是微信开发者工具里自带的模拟数据没有接真实后端很多页面看着是能跑了结果一接真实接口就全面崩盘。后来我要求前端每写一个页面就先接真实后端接口联调虽然前期慢了后期反而非常顺。这种“慢就是快”的经验在预约系统这类重业务逻辑的项目里尤其有效。
返回列表