简介面向共享棋牌室、茶室、台球室等自助休闲场景的运营者与小程序开发者这份设计源码提供了一套基于JavaScript与微信小程序的前端工程覆盖共享空间预约、计时计费、门店管理等常见需求也可迁移至共享私人影院、运动场馆和民宿等场景。压缩包约2000个文件、5.53MB包含578个JavaScript、251个TypeScript逻辑文件以及WXML、WXSS、WXS、JSON等页面模板、样式、配置与图片资源JS/TS负责业务逻辑WXML/WXSS组织界面与视觉JSON统一管理配置整体目录结构清楚适合作为二次开发或学习微信小程序工程组织的参考。包内还集成iconfont字体图标脚本、Markdown说明与命令行辅助文件可方便地按需生成不同平台的分享图标。目前已有186人学习对想低成本搭建共享棋牌室、茶室小程序原型的读者具有实用价值。1. 共享棋牌室小程序设计源码一张桌子的预约生意经共享棋牌室、茶室、台球室这类生意本质上是在卖“闲时”。一套微信小程序源码把选桌、选时段、下单支付、到店核销这四步串起来就是这份设计源码的核心价值。它不是宣传页模板而是能直接跑通预约闭环的完整业务工程房间列表、时段价格、订单状态都有完整实现。适合两类人一类是自己经营共享空间、想把人工排班换成系统排期的老板另一类是刚接触小程序开发、想找一个带真实业务逻辑的完整工程做参考的从业者。如果你手头有场地资源或者正在找共享经济方向的落地项目这份源码值得一次完整拆解。2. JavaScript 在小程序里的落地方式不是网页的 JS是带环境限制的 JS小程序的逻辑层确实是 JavaScript但它是跑在 JSCore 里的 JavaScript没有 window、document。很多从网页转过来的开发者第一反应是 document.getElementById一运行就报错。理解了这个边界后面调起来才顺手。2.1 小程序 JS 与浏览器 JS 的差别typeof、setData 与 wx API小程序的 JS 是模块化的默认支持 CommonJS 风格的 require/module.exports。和浏览器 JS 相比少了 DOM/BOM多了一整套 wx 开头的 API。判断数据类型还是靠 typeof 和 Array.isArray比如判断接口返回的字段是不是数组习惯用 Array.isArray 而不是 length 属性去试探。环境差异最直接的体现不能用 document 改样式要改界面状态就走 setData。WXS 是小程序特有的一门语言语法像 JS但独立于逻辑层运行不能调用 wx API。我一般只在需要高性能的列表筛选和模板计算里用 WXS业务逻辑都放在逻辑层 JS 里。这个取舍的核心依据是WXS 运行在视图层不参与逻辑层的状态管理放复杂业务反而难调试。开发者工具里还有一个容易被忽略的点基础库版本。旧的工具版本打开新语法会报错JS 里的可选链和空值合并操作符在低版本基础库上会直接在运行时中断。做小程序第一位的是兼容性新语法用之前先看一眼目标版本。2.2 工程目录与页面注册app.json 才是真正的骨架每个页面有四个文件.js、.wxml、.wxss、.json。但真正决定页面路径的是 app.jsonpages 数组里第一个就是启动页。tabBar 配置也写在这里最多五个页面路径不能写错错了工具会白屏。app.js 里放全局逻辑比如读取用户登录状态、设置全局数据 globalData。要注意 globalData 不是响应式的页面里改了不会自动刷新视图只有页面 data 里绑定的字段才能驱动界面更新。这个区别新手常常翻车。我一般把请求封装放在 utils/request.js拦截器统一处理登录态失效和错误码业务方只管调用。既然每个界面都可能发请求统一封一层后续改域名、加 token 都方便。项目的四个文件里page.json 负责配置窗口标题、下拉刷新、背景色标题栏高度这些参数也在这里调不要到 wxss 里硬改导航栏。2.3 首页数据绑定wx.request setData 的标准写法房间列表是首页最主要的内容。页面加载时发请求拿到数据后 setDataWXML 里用 wx:for 渲染。// utils/request.js 里封一个 Promise 风格的 GET const request require(./request) Page({ data: { rooms: [], loading: false }, onLoad() { this.fetchRooms() }, fetchRooms() { this.setData({ loading: true }) request.get(/api/rooms, { status: open }) .then((res) { this.setData({ rooms: res.data }) }) .catch(() { wx.showToast({ title: 加载失败下拉重试, icon: none }) }) .finally(() { this.setData({ loading: false }) }) } })逻辑说明onLoad 是页面生命周期首次进入只触发一次想要下拉刷新需要在 page.json 开启 enablePullDownRefresh再在 onPullDownRefresh 里重发请求。逻辑层的 loading 状态控制页面 loading 组件显隐避免用户重复点击。参数说明setData 是官方推荐的视图更新入口但不要在循环里频繁调用一次更新一大块数据比多次小更新性能好。房间列表这种量级的数据直接整体 setData 没问题但如果列表超过几百条要考虑分页。WXML 端展示注意几个细节。房间状态是“营业中”才显示“预约”按钮closed 的直接置灰加载态用骨架屏而不是单纯 loading 组件骨架屏体验好很多实现也不复杂列表占位灰色块数据到位后替换。2.4 条件渲染与列表渲染wx:if 和 wx:for 的边界页面里最常用的两个指令wx:if 控制节点是否存在wx:for 遍历数组生成多个节点。它们不是万事大吉的我见过把整个房间列表包在 wx:if 里的案例每次数据变化整个列表重渲染白屏感明显。正确做法是外层用 wx:if 控制“空态/有数据”的分支内层 wx:for 只渲染列表项。wx:key 也必须写否则列表项复用会出现索引错乱尤其是时段格子这种需要高亮的组件。时段选择器里我用的 key 是 slot.id而不是 index。这两者的差别在项目越来越大以后特别明显用 index 时删除或插入项后视图和数据的对应关系会错位。链式弹窗也在这个环节经常遇到房间详情页里的“立即预约”按钮点击后先判断用户是否登录再弹确认框。这两个逻辑串在一个函数里容易踩到异步回调的坑我一般把登录判断拆成独立函数返回 Promise后续操作 await 它。这样做的好处是登录流程变化时只改一处页面逻辑不用动。3. 共享时段与预约一套能跑的时段表和防并发下单共享空间的核心是卖时段。一天 24 小时按小时或按 2 小时切成格子每格独立定价。这个业务模型决定了后面所有表结构和接口设计。3.1 时段表与计价模型工作日、节假日分开定价我见到的多数共享棋牌室、茶室项目定价不是固定死的。工作日下午和周末晚上的价格差一倍很正常有些场地还区分节假日。所以时段表里至少要放两个价格字段。房间表 rooms 记录场地基础信息名称、类型棋牌室/茶室/台球室、可容纳人数、封面图。时段表 time_slots 才是真正卖的东西核心字段room_id、date、start_time、end_time、price_weekday、price_holiday、status。status 有 open、occupied、locked 三个状态open 可售occupied 已成交locked 是管理员暂锁或场次被占用后防止继续销售的兜底。表名关键字段说明roomsid, name, type, capacity, cover, statustype 区分棋牌室/茶室/台球室time_slotsid, room_id, date, start_time, end_time, price_weekday, price_holiday, status每天每个房间一组时段ordersid, order_no, room_id, slot_id, user_id, amount, status, pay_time订单金额写快照不后查价格计价在订单生成时写死到 orders 表不要订单里动态去查时段价格。原因很简单价格会调整订单是历史记录金额必须和支付金额对得上。前端显示价格时用 toFixed(2) 保留两位小数避免 19.9 显示成 19.900000000000002 这种尴尬。下单接口的链路走通时时间段的归属务必清醒按开始时间归属日期跨天的场次结束时可能到第二天核销记录要按开始时间统计到当天报表。这个问题和节假日跨天是同类具体踩坑放第 5 章展开。3.2 时段选择器交互选中态、置灰态与只读态房间详情页顶部是轮播图下面是商家信息再往下就是时段选择器。时段选择器由日期栏和时段格子组成。日期栏横排 7 天点击切换时段格子显示“14:00-16:00”“68 元/2h”这样的信息。交互状态分三种可预订、已被订、已过期。已经被订的格子置灰且不可点击今天已过时间的格子同样置灰不能买这是很多新手会漏的逻辑。选中后格子里出现勾选标记再点一下取消选中还是保持选中直到下单完成这个交互细节各家习惯不同但至少要做到选中高亮清晰不能是微妙的颜色变化。// 选择时段维护选中状态 selectSlot(e) { const id e.currentTarget.dataset.id const slot this.data.slots.find((item) item.id id) if (!slot || slot.status ! open) return this.setData({ selectedSlotId: this.data.selectedSlotId id ? null : id }) }代码逻辑找到当前点击的时段只有 open 状态才允许选中再次点击同一时段取消选中态。dataset 里传 id 时数据必须是基本类型传对象会被序列化成字符串这个是踩过坑才记得住的。时段列表的加载按需做默认选中今天日期切换日期时重新请求。切日期后先清空 slots 并显示短暂 loading再填入新数据。这个“先清空再填”的顺序不能反过来反过来会出现新数据到达前用户能点到旧日期格子的情况。3.3 订单创建与防并发后端校验才是最后一道门前端做不了并发控制只有后端事务能做到。下单接口的常规逻辑先查房间里当天此时段是否存在 open 状态记录存在则创建 pending 订单并把它改成 occupied不存在直接返回“已被预订”。查和改必须在同一个事务里并且给 time_slots 表加唯一索引room_id, date, start_time, end_time即使两个请求同时进来数据库层面也能兜住。下单接口成功返回订单 ID 后前端跳到支付页。注意下单接口和支付动作是两步order_id 在创建时生成支付参数后端再根据 order_id 去组装。不要把下单和支付混在一个接口里回调状态会很难理清。提示唯一索引是防并发的最后一道保险业务层事务失效时数据库还能拦住重复插入。前端也需要做防重复提交。按钮点击后进入 submitting 状态禁止二次点击请求成功跳转支付页失败才恢复。完整逻辑submitOrder() { if (this.data.submitting) return if (!this.data.selectedSlotId) { wx.showToast({ title: 请先选择时段, icon: none }) return } this.setData({ submitting: true }) request.post(/api/orders, { roomId: this.data.room.id, slotId: this.data.selectedSlotId }).then((res) { wx.navigateTo({ url: /pages/pay/pay?orderId res.data.id }) }).catch(() { wx.showToast({ title: 下单失败请重试, icon: none }) }).finally(() { this.setData({ submitting: false }) }) }参数说明submitting 是布尔值直接挡住重复提交。跳转用 navigateTo 而不是 redirectTo保证支付失败后用户还能后退回到房间详情页重新选。4. 支付与订单状态机从下单到核销的完整闭环共享空间的支付环节比普通电商复杂一点因为订单不仅关乎钱还关乎到场核销和场地占用。状态机设计不清楚后面对账和退款的坑能磨掉一整周。4.1 支付前置openid 与 wx.requestPayment 的参数来源微信支付必须在后端完成预下单。前端能做的是先 wx.login 拿 code把 code 传给后端后端用 code 换 openid。openid 是用户在小程序里的身份标识不能只靠前端传来传去后端必须自己持有。发起支付时后端先调用微信支付统一下单接口拿到 prepay_id再生成支付参数返回给前端。前端调用 wx.requestPayment参数是 timeStamp、nonceStr、package以 prepay_id 开头、signType、paySign 这五个。注意 package 的值不是裸的 prepay_id是拼接好的字符串。一个容易翻车的地方支付参数带有时效性一般几分钟。用户在下单页停留时间太长再点支付会拿到过期参数界面无反应或报错。我一般在前端做参数过期提示或者在后端支付接口里校验订单创建时间和当前时间的差值超过时效返回“订单超时请重新下单”。签名的生成必须放服务端不能把商户密钥暴露在小程序代码包里。密钥外泄最坏结果是别人用你的商户号发起退款这个风险不值得赌。4.2 支付回调和订单状态机以服务端回调为准支付成功的判断不能依赖 wx.requestPayment 的成功回调。正确做法前端只用它来引导用户走到“已完成支付”页面真正的状态更新等后端回调。后端收到微信支付结果通知后先做验签校验订单金额和回调里的金额一致再更新订单状态。订单状态我习惯做成这样状态含义触发时机pending已下单未支付创建订单时paid已支付待核销支付回调验签通过used已核销核销接口验证通过refunding退款中商家发起退款refunded已退款退款回调通知canceled已取消或已关单用户取消或超时释放这个状态机里两个容易出问题的点一是重复回调同一个支付结果微信可能通知多次后端必须按幂等处理判断订单当前状态已经是 paid 就不再更新二是金额核对回调里传的是分数据库存的是元还是分团队里必须统一。我经历过的坑是习惯用元存结果回调用分一核对就发现差了 100 倍。提示金额单位在团队内统一为分订单表和回调核对都以分做比较前端展示时再转成元。订单列表页存的金额要和支付参数里的金额一致不能比。小程序端很多人直接信任后端返回的 amount 字段但支付参数里的 total_fee 才是用户实际掏的钱。后端在创建支付参数时把订单表和支付参数绑定回调里校验 total_fee 和 order.amount 相等这一步省不掉。4.3 核销码与桌位验证前台扫码的闭环支付完成后的订单详情页里展示一个 6 位数字核销码商家端小程序或后台输入核销码完成验证。核销码由后端在支付回调成功时生成绑定到订单上核销接口校验码、校验订单状态是 paid才能改成 used同一核销码只允许核销一次。核销码生成不能是递增整数会被猜。我一般用随机函数生成六位数字。订单号也不要直接用主键用年月日时分秒加随机数生成业务单号主键自己留着关系表用。核销界面关注两件事显示房间名、桌位号、使用时间段验证通过才入库失败要有明确的原因文案。多桌位的项目比如台球室有几张球桌核销如果只到房间维度会出现同一房间两张核销单的情况。提前把房间和桌位拆成两张表核销绑定桌位而不是房间。我习惯每个桌位一个二维码贴桌上用户到店扫桌码核销省去前台输入步骤。4.4 退款与账务日报钱怎么出去才是安全闭环手工退款逻辑也要纳入状态机。发起退款后订单进入 refunding微信支付退款结果通知到达后才改成 refunded。退款金额不能超过原支付金额而且退款必须走微信支付原路退回不能线下转账线下转账行为在小程序生态里是违规的。账务日报对经营共享空间特别重要。每天凌晨统计前一天的支付金额、核销数、退款数发送给经营者。这个日报我一般让后端定时任务来出不依赖前端页面。你不用等小程序打开才看到数据每天早上自然收到一条汇总不紧急但很有用。5. 避坑共享空间小程序最容易翻车的五个地方5.1 时段跨天23 点开场凌晨结束日期怎么算现象用户预订 23:00-01:00 的场次订单记录里日期是当前天但核销时商家发现时间跨度到了第二天日报统计的金额归错了天。原因时段表只存开始时间没存结束日期跨天场次的归属被简单放在开始日期。解决时段表加 end_date 字段跨天场次单独存结束日期日报统计统一按开始时间归属且跨天场次在核销页里明确展示“23:00-次日 01:00”而不是只有一组时间。5.2 支付回调延迟用户已付款订单还显示待支付现象用户支付成功前端页面返回成功但订单列表里仍然是“待支付”用户反复刷新后以为没付再点一次下单生成了第二个订单。原因后端回调还没有到达前端只信任服务端回调却没有做中间态。解决前端把 wx.requestPayment 的 success 回调当作“支付凭证”先进入支付确认中状态再轮询订单详情接口看到 paid 后自动刷新。中间态文案用“支付确认中”比“待支付”更能减少误解。5.3 未支付订单占住时段20 分钟后时段还是不可订现象用户提交订单但没有支付时段被占住其他用户无法预订。原因订单只在创建时改成 occupied没有超时释放逻辑。解决创建订单时记录 expire_at一般为 10 到 15 分钟。后端定时扫描或者在下单前检查过期时间过期则把订单改为 canceled 并释放时段。释放时段必须把 time_slots.status 改回 open否则订单没了时段却永远卖不出去。5.4 setData 传大数据卡顿7 天的时段格子一次拉回来现象房间详情页一进去转圈几秒滑动时卡顿明显。原因把未来 7 天每个时段全量塞进 datasetData 的负载太大渲染层处理不过来。解决按日期分段加载一次只请求当前选中的那一天数据时段格子用 wx:key 稳定列表切换日期后再请求新数据。数据量超过几百行时优先分页不要一次处理整周数据。5.5 桌位编号和房间概念混在一起核销时出现两张单现象台球室有两张球桌同一时间段的房间被订出去两次核销时商家手里两个核销码都能过。原因订单绑定在房间维度没有拆到桌位维度两个用户各自订了房间但实际占用同一间。解决房间和桌位拆成两张表订单绑定桌位而不是房间。桌位二维码贴在桌上到店扫码识别桌位 ID核销时同时校验订单里的桌位 ID 和房间 ID两端一致才通过。这个设计最好在阶段初期就做后期改数据模型非常痛苦。6. 进阶管理端接口与数据统计落地前端如果已经跑通剩下的落地重点在管理端。共享棋牌室这类生意老板需要看到的是“今天哪几张桌子卖出去了、明天哪个时段空着”这种经营视角而不是记账本。管理端接口不复杂核心就四个房间管理增删改查房间、桌位、开放状态、时段管理批量生成某天或某周的时段、订单管理查看订单、处理退款、手动核销、统计报表按日周月汇总营收、核销率、退款率。我习惯把这些接口和用户端接口放在同一个服务里而不是单独拆后台服务小规模场地撑不起来两套服务反而增加了部署和运维成本。数据统计落地时先定一个原则日报和月报都从订单表取数用支付时间而不是核销时间来归因。核销时间归因会因为用户到店晚导致前一天数据飘移对经营分析不友好。营收 支付成功的订单金额之和核销率 已核销订单数 ÷ 已支付订单数退款率同理。一张简单的统计表就够看清门店健康度。有用的小技巧是给房间列表加上“今日可用时段数”和“近 7 天营收”两个字段列表页直接对比出哪个房间是主力。开发时前端用 wx:for 渲染接口数据里预先聚合好不要在 WXML 里临时计算。最后说一个我的习惯每次调整价格、日期规则、桌位状态这类关键配置之后我都会强制走一遍“下单 → 支付 → 核销 → 退款”的闭环测试用测试号跑一遍而不是只在开发者工具里预览。共享空间场景的坑大多藏在业务规则边界里——跨天、并发、回调延迟不在真机里走一遍很难发现。希望这一套拆解能帮到你把这张桌子的预约生意做成真正能自动跑起来的系统。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?