☰
微信小程序实战:3Q工具箱如何用原生框架构建13个玩法的离线聚会工具
2026/10/1 10:24:26 网站建设 项目流程

项目背景

最近在研究一个叫"3Q工具箱·真心话大冒险"的微信小程序项目,技术栈是微信原生框架 + TypeScript,纯单机、零网络、零后端。这个项目有几个工程上的亮点值得拆一下:13 个玩法+派对模式如何用一个轻量架构承载、个人主体如何在合规约束下做产品完整性、以及如何用工程化手段兜底合规要求。

本文从源码角度聊聊它的几个关键设计。

架构总览

miniprogram/ ├── app.ts # 入口,导航度量只在 onLaunch 算一次 ├── pages/ # 主包 9 页 │ ├── home/ # 首页(玩法分组+派对 CTA) │ ├── party-config/ # 派对配置 │ ├── party/ # 派对进行中 │ ├── party-result/ # 派对结算 │ ├── players/ # 玩家名单 │ ├── records/ # 惩罚记录 │ ├── bank/ # 题库 │ ├── settings/ # 设置 │ └── about/ # 关于 ├── packageDice/ # 分包:骰子专区 ├── packageLuck/ # 分包:运气类 ├── packageCard/ # 分包:卡牌文字类 └── utils/ ├── games-meta.ts # 玩法注册表(单一数据源) ├── party.ts # 派对调度纯函数 ├── store.ts # 手写极简 store ├── storage.ts # 本机存储+版本迁移 └── types.ts # 类型定义

主包 9 页 + 3 个分包共 13 个玩法页。preloadRule 在进入对应分组时预加载分包。

关键设计一:一份元数据驱动多处

这是整个项目最值得学的地方。

utils/games-meta.ts里定义了一份 GAME_META 数组,每个玩法一条记录,包含 key、name、subtitle、group、color、badge、path、title、minPlayers、party、needsAi 等字段。

这一份数据同时驱动:

  • 首页分组渲染:groupedGames(aiOpponent)函数返回按组分类的卡片数据,首页 wxml 直接 wx:for 渲染
  • 派对模式玩法池:partyGames()函数过滤 party=true 的玩法
  • 人机开关文案切换:resolveAi(game, aiOpponent)函数根据开关返回带 aiSubtitle/aiBadge 的形态
  • 页面标题 SEO:title 字段对应各页 index.json 的 navigationBarTitleText

玩法落地时只需要填上 path 字段,首页和派对池自动出现,不用改首页逻辑。这种"单一数据源"的设计在玩法数量增长时优势明显——加一个玩法只改一处,不会出现首页有入口但派对池没有的漂移。

exportfunctionresolveAi<TextendsGameMeta>(game:T,aiOpponent:boolean):T{if(!game.needsAi||!aiOpponent)returngamereturn{...game,subtitle:game.aiSubtitle||game.subtitle,badge:game.aiBadge||game.badge,minPlayers:game.aiMinPlayers===undefined?game.minPlayers:game.aiMinPlayers,}}

更妙的是,首页分组时把奇数个玩法的最后一张标记为 wide,由 CSS 跨两列占满,不论将来每组有几个玩法都不会出现空缺。

关键设计二:派对调度器是纯函数

utils/party.ts里的调度逻辑是纯函数,输入(已用玩法、池子、轮次)→ 输出(下一个玩法)。

核心约束是"同一玩法在连续 3 轮内不重复",用滑动窗口实现:

  • 维护最近 3 轮的玩法 key 列表
  • 从池子里过滤掉这 3 个
  • 池子不足 3 个时降级为"不与上一轮重复"
  • 都不满足时才允许重复

这个逻辑不依赖小程序运行时,可以单独写 Jest 单测。项目里有 13 个测试文件覆盖各玩法的纯函数。

派对模式不改 13 个玩法页——这是低耦合的关键。玩法页完全无感知派对模式的存在。派对模式通过 roundStartedAt 时间戳筛"本轮新增的惩罚记录",玩法页该怎么写怎么写,派对结算时按时间戳聚合即可。

关键设计三:人机开关约束与 precheck

个人主体做小程序有一个隐形陷阱:人机对战的文案一致性。

需求文档里明确写了三条约束:

  1. 电脑是否参与只由settings.aiOpponent决定,玩法内部不得有第二个开关
  2. 玩法注册表里用 needsAi 标记,并同时给出开关打开时的副标题/角标/最少人数
  3. 开关关闭时该玩法必须有一个可玩的真人形态

这三条不是靠人肉 review,而是靠scripts/precheck.mjs在发布前自动校验。precheck 会扫描所有 needsAi=true 的玩法,检查是否有 aiSubtitle、aiBadge、aiMinPlayers,并验证首页文案逻辑调用了 resolveAi。

这种"把合规判断写成检查脚本"的做法非常值得借鉴。人肉 review 会漏,CI 不会。

关键设计四:手写极简 store

项目没有引 MobX,自己写了一个极简的订阅/退订模式。对于这种状态不复杂的应用,引入状态管理库反而是负担。

store 的核心是:setState 时遍历订阅者回调,组件 onLoad 时订阅、onUnload 时退订。没有 selector、没有衍生状态、没有中间件——够用就行。

这个判断是合理的。很多人一上来就 MobX/Redux,其实小程序原生的 setData + 一个极简 store 能解决 90% 的问题。

关键设计五:隐私合规的工程化

打开about/index.wxml,明确写着"不收集、不上传、不共享"。这不是营销话术,而是工程约束:

  • 不调用 wx.login(不获取 openid)
  • 不获取头像昵称(不写 open-data 组件)
  • 不申请位置、相机、相册读取、手机号
  • 唯一权限是 scope.writePhotosAlbum(保存战绩图,拒绝可降级为长按保存)
  • 所有数据走 wx.setStorageSync,纯本机

更关键的是,这套约束在工程上有兜底:precheck.mjs 会扫描源码,如果发现调用了 wx.login 或获取了敏感权限,直接拦截发布。

关键设计六:导航度量只在 onLaunch 算一次

小程序的 getMenuButtonBoundingClientRect 和 statusBarHeight 是很多开发者反复在每页 onLoad 里算的东西,其实没必要。这个项目在 app.ts 的 onLaunch 里算一次存 globalData,各页直接读 globalData 即可。

// app.tsApp({onLaunch(){constsys=wx.getWindowInfo()constmenu=wx.getMenuButtonBoundingClientRect()this.globalData.navTop=menu.topthis.globalData.navHeight=menu.heightthis.globalData.statusBarHeight=sys.statusBarHeight},globalData:{}})

小细节,但能避免每页重复计算和潜在的机型适配 bug。

关键设计七:玩法分主持人式与交接式

这个设计解决了一台手机多人玩时的核心痛点:私密信息怎么不被下家看到。

  • 主持人式(公开玩法):顶部统一用"轮到 XX"提示条,出结果后必须主持人点"做到了/没做到"才推进,惩罚默认记给当前轮到的人。
  • 交接式(含私密信息如大话骰、数字炸弹):换人时插入全屏交接遮罩,必须本人亲手点开才继续。私密内容用不透明遮罩,遮罩期间不渲染秘密节点。

数字炸弹的秘密不属于某个人,用半透明遮罩,只承担"明确换人"的作用。这种"按信息私密性分流交互"的细节,是产品想清楚了的标志。

一些工程取舍的讨论

不是没有可挑剔的地方:

  • 个人主体不能开通微信支付,所以变现路径受限。这个天花板是结构性的。项目代码层预留了广告插口(ad-slot 组件),AD.enabled 为 false 时整节点不渲染,达标后改配置开关即可上线。## 关于合规的几点工程化处理

这个项目在合规上做了几件值得学的事:

  1. 类目选"工具"不选"游戏"。游戏类目需《网络文化经营许可证》+ 版号,个人主体拿不到;且平台明确"一旦选择游戏类目,不可再变为其他类目"。产品叙事落在"聚会决策与随机工具"。
  2. 零酒精词汇。参考项目里饮酒语义是主线,本项目整体改造为"惩罚动作"体系(做鬼脸、学动物叫、发朋友圈),酒精只作为用户自建内容存在。
  3. 进阶内容分级开关。默认全年龄题库,进阶档需手动开启且有二次确认,不含性暗示、身体接触、露骨、酒精内容。
  4. UGC 不传播。自定义题库仅存本地、不上传、不共享、不可跨用户传播,从机制上规避 UGC 传播风险。
  5. 诱导分享红线。任何功能不以分享为解锁条件,不做分享得次数/积分。
  6. 隐私承诺工程化。不收集、不上传、不共享用户数据,所有数据仅存本机,并在发布前用 precheck 脚本扫描敏感 API 调用,从机制上兜底合规要求。## 总结

3Q工具箱·真心话大冒险在工程上做对了几件事:

  1. 单一数据源(games-meta)驱动多处,玩法扩展只改一处
  2. 派对调度纯函数化,低耦合不改玩法页
  3. precheck 脚本兜底合规约束,不靠人肉 review
  4. 手写极简 store,不引状态管理库
  5. 隐私承诺工程化,precheck 扫描敏感 API 调用
  6. 主持人式/交接式分流,解决私密信息传递问题

对于做微信原生小程序的开发者,这个项目的架构和工程化思路值得参考。特别是"把合规判断写成检查脚本"和"一份元数据驱动多处"这两个设计,在很多场景下都能用上。

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

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

立即咨询