简介:一款专为喝酒与聚会场景设计的微信小程序源码,面向需要快速搭建派对互动工具的小程序开发者、二次开发者及聚会组织者;这个“千寻百念修复版”针对旧版进行了优化,内置多种互动游戏与娱乐工具,可用于活跃聚会氛围、增强参与者互动,适合各类规模的社交场景。资源包为 zip 格式,共 447 个文件,整体仅 1.38MB,以 png、js、json、mp3、wxss、wxml 等类型为主,覆盖图片素材、逻辑脚本、配置文件、音频资源和页面结构,目录分层清晰;目前已有 53 人学习,可作为小程序项目参考。源码虽未列出全部功能细节,但描述中强调功能完备且修复了旧版缺陷,可直接部署使用;目录区分了公共组件、游戏逻辑、核心页面等模块,方便开发者定位与二次开发,节省基础搭建时间。对于想学习小程序结构或低成本落地聚会娱乐应用的人,这是一份小巧实用的基础代码包。
1. 喝酒小程序源码:拿到修好的“千寻百念”先看这三处
如果你搜过“喝酒小程序源码”,大概率见过那款叫“千寻百念”的微信小程序——转盘、骰子、真心话大冒险、输赢罚酒,一套典型的线下聚会助兴工具。这套源码流传的版本很多,但不少是半成品:有的转盘动画卡死,有的分享回调拿不到参数,有的登录直接报错。我拿到这版“修复版”源码之后,从页面结构、事件绑定到微信登录链路都过了一遍,确认它可以正常跑起来。这篇笔记把拆包过程、部署步骤和踩过的坑写清楚,让你拿到的源码不只是“能打开模拟器”,而是真正能上传体验版、拿手机号登录、分享给好友一起玩的完整项目。适合有基础小程序开发经验、想直接改来用的读者。
2. 源码头绪:小程序的页面栈与“千寻百念”的文件结构
2.1 为什么这版源码能叫“修复版”:先看 app.json 和页面路由
微信小程序和普通网页不同,它没有 URL 地址栏,所有页面都靠app.json里的pages数组注册。打开项目第一件事,不是看代码,而是看这个文件——页面顺序、窗口样式、tabBar、插件都在这里定义。
{ "pages": [ "pages/index/index", "pages/game/dice", "pages/game/turntable", "pages/game/truth", "pages/user/profile", "pages/user/history" ], "window": { "navigationBarTitleText": "千寻百念", "navigationBarBackgroundColor": "#2B2B2B", "navigationBarTextStyle": "white", "backgroundColor": "#1A1A1A" }, "style": "v2", "sitemapLocation": "sitemap.json", "lazyCodeLoading": "requiredComponents" }这段配置说明三件事:第一,这个项目有 6 个页面,核心玩法是骰子、转盘、真心话,用户相关的是主页和个人历史记录;第二,全局导航栏是深色底白字,背景也是深色,说明这版 UI 走的是“酒吧暗色风”;第三,lazyCodeLoading开了按需注入,能减少首包体积。拿到源码后先核对pages数组,如果目录里少了一个页面文件,编译时就会白屏报错。常见做法是直接从项目根目录往下捋,把页面文件缺失表拉出来。
2.2 关键目录功能对照:components / utils / services
修复版和原始版的差别主要在代码组织上。我拆开后的目录结构大致如下:
├── app.js ├── app.json ├── app.wxss ├── components/ │ ├── dice/ │ │ ├── dice.js │ │ ├── dice.json │ │ ├── dice.wxml │ │ └── dice.wxss │ ├── turntable/ │ │ ├── turntable.js │ │ ├── turntable.json │ │ ├── turntable.wxml │ │ └── turntable.wxss │ └── share-card/ │ ├── share-card.js │ ├── share-card.json │ ├── share-card.wxml │ └── share-card.wxss ├── pages/ │ ├── index/ │ ├── game/ │ ├── user/ │ └── ... ├── services/ │ ├── auth.js │ ├── game.js │ └── user.js ├── utils/ │ ├── wxapi.js │ ├── format.js │ └── config.js └── project.config.jsoncomponents存的是自定义组件,骰子和转盘是独立组件包装的,方便复用;services层封装了登录、游戏数据上报、用户信息读取的接口;utils/wxapi.js统一包了一层微信 API 调用,把成功和失败回调转成 Promise。这里有一点我之前翻车过的经验:很多流传的源码把 wx.request 直接写进页面里,改需求时要把每个页面翻一遍;这版把请求收拢到 services,算是修复版里比较像样的改动。
2.3 app.js 里的全局登录态:为什么拿手机号登录前必须先 wx.login
小程序的登录和传统网页登录不一样,它没有 session 中间件,核心是wx.login换取 code,再由后端拿着 code 去微信接口换 openid。看这版 app.js 的逻辑:
App({ globalData: { openid: '', userInfo: null, loggedIn: false }, onLaunch() { this.initLogin(); }, initLogin() { const token = wx.getStorageSync('token'); if (token) { this.globalData.loggedIn = true; } else { this.doWxLogin(); } }, doWxLogin() { wx.login({ success: (res) => { if (res.code) { // 将 code 发给后端,后端用 code 换 openid 和 session_key this.exchangeCode(res.code); } } }); }, exchangeCode(code) { const { apiBase } = require('./utils/config'); wx.request({ url: `${apiBase}/auth/login`, method: 'POST', data: { code }, success: (res) => { if (res.data.code === 0) { wx.setStorageSync('token', res.data.data.token); this.globalData.loggedIn = true; } } }); } });wx.login拿到的 code 有效期只有 5 分钟,而且只能用一次。这里的参数res.code是一串临时凭证,后端拿它换 openid。设计上注意两点:一是服务端要处理 code 重复使用的情况,否则并发请求时第二次必然失败;二是 token 存储用wx.setStorageSync,简单直接,如果后续要多端同步,再换成wx.setStorage的异步版本。之前有人问为什么不直接把 code 存进 storage,其实 code 是一次性的,存了没意义,应该存的是后端返回的 token。
3. 部署到开发者工具:三步把源码跑起来,附带 project.config.json 调优
3.1 导入项目:AppID 与编译环境的正确设置
用微信开发者工具导入源码时,关键不是点“导入”按钮,而是先看project.config.json里的appid字段。很多流传源码里的 appid 是别人的测试号,直接导入会报“appid 不存在”。
{ "appid": "你的小程序AppID", "compileType": "miniprogram", "libVersion": "latest", "setting": { "es6": true, "postcss": true, "minified": true, "urlCheck": false }, "projectname": "qianxun-bainian", "simulatorType": "wechat", "condition": {} }urlCheck: false是我建议设的。不关掉的话,后端接口域名没配置时,调试阶段每个请求都会被拦截。正式发布之前,记得去微信公众平台把 request 合法域名配好,再把urlCheck改回 true。libVersion设为latest能让工具自动匹配最新的基础库,但如果你用的组件库有兼容性要求,这里要锁定到具体版本,否则上线后低版本微信打不开。
3.2 真机预览前的必要检查:不校验合法域名、调试器开关
开发者工具里跑通不代表真机没问题。真机预览之前,我一般会强制走一遍这几个开关:详情 - 本地设置 - 不校验合法域名、web-view 域名、TLS 版本以及 HTTPS 证书,打开;调试器 - 打开调试。真机预览开启调试,才能在小程序右上角菜单里看到 vConsole 入口。
这一步做不到的话,真机一请求后端就白屏,控制台打不开,你根本不知道是域名问题还是接口报错。最常见的现象是:模拟器里一切正常,真机上所有 wx.request 都失败,报ERR_CERT_COMMON_NAME_INVALID,其实就是证书域名对不上。这属于配置问题,不是代码问题,但卡住新手很久。
3.3 本地后端联调:用 Node 起一个临时登录接口
源码里大概率没有后端,只有前端。不要慌,本地用一个 Node 服务模拟后端登录接口即可把流程跑通。我这边常用的方案是 node + express,十几行代码就能搭一个登录接口。
const express = require('express'); const app = express(); app.use(express.json()); // 模拟微信登录接口 app.post('/auth/login', (req, res) => { const { code } = req.body; if (!code) { return res.json({ code: 1, msg: 'code is required' }); } // 实际项目中这里用 code 请求微信接口,拿到 openid 后下发 token const mockOpenid = 'mock_openid_' + Date.now(); const token = 'mock_token_' + Math.random().toString(36).slice(2); res.json({ code: 0, data: { token, openid: mockOpenid } }); }); app.listen(3000, () => { console.log('mock server running at http://localhost:3000'); });然后修改utils/config.js里的apiBase,把它指向本地地址:
module.exports = { // 本地联调用 http://localhost // 上线换成 https://your.domain.com apiBase: 'http://localhost:3000', version: '1.0.0' };注意微信开发者工具模拟器里请求 localhost 是没问题的,但真机预览时不行——手机上的 localhost 指的是手机自己。真机调本地接口,要把apiBase改成电脑的局域网 IP,比如http://192.168.1.101:3000。这一步很容易忽略,我最初调试时在这里浪费了一个晚上。
4. 核心玩法代码解读:骰子动画、转盘抽奖与分享参数的透传
4.1 骰子组件的随机数与动画状态机
骰子是小程序里最容易写崩的部分,原因不是随机数,而是动画状态控制。看这版组件的核心逻辑:
Component({ data: { diceValue: 1, rolling: false }, methods: { rollDice() { if (this.data.rolling) return; this.setData({ rolling: true }); // 随机 1-6 const value = Math.floor(Math.random() * 6) + 1; // 先播动画,动画结束后再显示结果 setTimeout(() => { this.setData({ diceValue: value, rolling: false }); // 触发自定义事件,让父页面可以拿到结果 this.triggerEvent('rollend', { value }); }, 800); } } });这里的rolling标志位就是防重复触发。如果不加,用户在动画期间连点,会连续触发多个 setTimeout,结果就是骰子还没停下又变了。triggerEvent是组件向父页面通信的标准方式,和 Vue 的$emit类似。父页面这样接:
onDiceRollEnd(e) { const value = e.detail.value; // 根据点数决定谁喝酒 this.setData({ currentDice: value }); }4.2 转盘抽奖:canvas 绘制与指针停靠角度计算
转盘组件用的是 canvas,绘制扇形和文字都在 canvas 里完成。停靠角度是这里的重灾区:很多源码转动结束没有“归位”逻辑,指针停在两个扇形交界处,结果就出现争议。修复版的做法是已知每个扇区角度为 60 度,然后算出目标扇区中心角,再让转盘转 N 圈后精确停在中心位置。
const sectors = ['一杯', '两杯', '半杯', '免喝', '一杯', '两杯']; const sectorAngle = 360 / sectors.length; spin() { if (this.data.spinning) return; const targetIndex = Math.floor(Math.random() * sectors.length); // 转 5 圈 + 目标扇区中心角 + 随机偏移 15 度/-15 度 const targetAngle = 360 * 5 + targetIndex * sectorAngle + sectorAngle / 2; this.setData({ spinning: true, currentAngle: targetAngle }); }关键参数是targetAngle。先随机决定目标扇区,再围绕它的中心角给一个 ±15 度的偏移,视觉上看起来像“差一点就转到隔壁”,实际上结果已经定了。偏移量最好不要超过 15 度,否则会滑到相邻扇区,你说是按中心算还是按边缘算?会吵起来。
4.3 分享给好友:onShareAppMessage 的参数透传坑
喝酒小程序的核心场景是多人聚会,必须依赖微信分享。这版的分享逻辑在pages/game/game.js里:
onShareAppMessage() { const roomId = this.data.roomId || 'default'; return { title: '来骰一把,输了认罚', path: `/pages/game/game?roomId=${roomId}`, imageUrl: '/images/share-card.png' }; }这里最容易踩的坑是path。如果页面路径拼错,分享卡片点开就是“页面不存在”。另外如果要带参数,参数必须是字符串,不能是对象。imageUrl可以自定义分享卡片图片,不传的话默认截屏。之前有人问分享卡片能不能直接拉起骰子动画,不行,onShareAppMessage只负责分享,不负责页面行为。分享出去后,好友点开的路径解析方式:
onLoad(options) { if (options.roomId) { this.setData({ roomId: options.roomId }); } // 从分享链接进来,自动加入房间 this.joinRoom(this.data.roomId); }从分享卡片进入时,options里能拿到 query 参数。注意第一次进入小程序时onLoad一定触发,但如果小程序已经在后台存活,分享链接再次打开只会触发onShow。所以严谨做法是参数解析同时写在onLoad和onShow里,或者在onLoad里判断场景值。这个坑在社交类小程序里很典型,分享参数丢失很多人遇到过。
5. 避坑指南:千寻百念修复版里我踩过的四个实际问题
5.1 转盘在 iOS 真机上旋转卡顿
现象:安卓模拟器里转盘丝滑,iPhone 上旋转掉帧明显,卡顿感很强。
原因:转盘用的不是 canvas 2D 绘制,而是 CSS3transform: rotate,每帧都在改变角度,iOS 上 Safari 的合成器对高频 transform 更新处理不够好。加上转盘背景图是几张合成的大图,内存占用偏高。
解决:把背景图先压缩到 750px 宽,并用canvas绘制每一帧。注意代码里不要同时修改变形和透明度,因为一个触发合成层,一个触发重绘。
5.2 分享到群里,好友点开收不到 roomId
现象:从分享卡片进入后,页面上房间号为空,直接进到了默认房间。
原因:分享路径里的参数如果含有特殊字符,比如&或中文,微信会编码,但onLoad里拿到后没有 decode 导致错乱。这版源码里的 roomId 是uuid格式,本身安全,但老版本里有用数字自增 id 的,偏偏拼在 URL 里时漏了编码处理。
解决:统一用encodeURIComponent包一层。另外不要用“客户端生成 roomId”的方式,多个端同时生成容易撞,改了之后用服务端返的 id。
5.3 真机预览时手机号登录一直转圈
现象:点了“手机号登录”按钮,loading 转圈超过 10 秒,最后提示“login fail”。
原因:手机号登录需要两个条件同时满足:一是小程序是认证过的企业主体,个人主体没有权限;二是必须用button组件设置open-type="getPhoneNumber"触发,不能直接用wx.getPhoneNumber。很多流传源码在这里用了 wx 方法,所以个人开发者调试时必然失败。
解决:检查 app.json 里是否声明了requiredPrivateInfos,然后看按钮写法:
<button open-type="getPhoneNumber" bindgetphonenumber="onGetPhoneNumber"> 手机号快捷登录 </button>bindgetphonenumber回调里拿到的e.detail.code也是临时凭证,后端要用这个 code 换手机号,不能直接解析手机号。
5.4 后台上传代码时提示“分包过大”
现象:开发者工具上传代码时提示主包超过 2MB,无法上传。
原因:项目里的图片资源没有压缩,尤其转盘背景图和骰子贴图可能都是 1MB 级别。一个小程序的主包限制是 2MB,超过就传不上去。
解决:图片全部走压缩流程,PNG 转 WebP 或压缩到 60% 质量,代码里引用的路径同步改名。另外可以做分包处理,把游戏页面扔进subpackages里。
{ "subpackages": [ { "root": "pages/game", "name": "game", "pages": [ "dice", "turntable", "truth" ] } ] }注意subpackages里的页面路径不用再出现在pages数组中,app.json里会以分包形式加载。改完之后真机预览时这些页面会延迟加载,首次进入会有一点白屏时间,属于正常现象。
6. 进阶:把“千寻百念”接上真实后端,登录链路全通后的下一步
当你把前端的登录、游戏、分享都跑通之后,可以接入真实后端。微信小程序手机号登录的正确流程是:前端通过open-type="getPhoneNumber"拿到code,用这个 code 请求后端,后端再拿 code 去微信接口换取手机号。接口地址是固定的,关键参数是code和openid。
// services/auth.js const { apiBase } = require('../utils/config'); function phoneLogin(phoneCode) { return new Promise((resolve, reject) => { wx.request({ url: `${apiBase}/auth/phone-login`, method: 'POST', data: { phoneCode, // 这里同时带上 wx.login 的 code,服务端用来换 openid loginCode: this.globalData.loginCode }, success: (res) => resolve(res.data), fail: (err) => reject(err) }); }); }服务端拿phoneCode换手机号的请求,返回的手机号是加密的,需要结合session_key解密。具体流程标准实现是:后端先用loginCode换session_key,再拿phoneCode换手机号密文,最后 AES 解密。不建议前端自己解密,密钥管理放后端更安全。
接入真实后端之后,建议做一件事:把utils/config.js里的apiBase改成正式域名,并且在微信公众平台配置 request 合法域名。这一步还有一个附加好处:后续加排行榜、防沉迷提醒、历史战绩同步等功能,不用再动前端登录逻辑,直接在 services 层加方法就行。
另外一个我从这次拆包中养成的习惯:每次拿到修复版源码,第一件事先全局搜索TODO和FIXME,第二件事看project.config.json里的appid,第三件事跑一遍登录链路,第四件事检查图片大小。这四步走完,基本上是能用的;少走一步,后面都可能返工。希望这篇笔记帮到你,让你在“喝酒小程序源码”这个关键词下少走一些弯路。
本文还有配套的精品资源,点击获取