一个旧版 uni-app 牌局原型的代码复盘
本文目录
本文复核的是一个较早期的 uni-app/Vue 2 页面原型。结论来自公开默认分支的静态源码,不代表它曾经上线、产生真实交易或运行在所有 uni-app 目标端。页面把“余额”和红黑两侧奖池做成浏览器本地模拟;源码没有账号、服务端账本或真实支付接口,不能将它描述成具备真实结算能力的产品。
仓库实际包含什么
检查时默认分支 main 指向提交 fa02e4f。完整递归目录树有 53 个文件;排除 unpackage 下的开发构建产物后,主要源码只有一个页面、应用入口、页面配置、清单和全局样式。README 只有标题与简短名称;仓库没有 package.json、自动化测试文件或可复现的命令行构建说明。树中有 39 个 unpackage/dist 开发构建文件,其中包含 4 个 source map。
主页面在挂载时初始化本地余额、选择金额、启动倒计时,再通过 DOM 查询更新界面。它按时间发牌、判定红黑双方的手牌并更新历史结果。这个原型适合用于回顾早期跨端页面如何逐步形成,但还不是便于维护、验证或迁移的跨端工程。
首轮倒计时会中断
settime 的倒计时回调在声明局部变量 waittimer 之前调用 clearInterval(waittimer)。waittimer 使用 let 声明,因此这次读取发生在该变量的暂时性死区内,会抛出 ReferenceError。首轮倒计时触发后,后续等待和新一轮定时器无法按预期启动。另一个 off-by-one 是倒计时从 20 开始,却在递减后小于 0 才结算,所以触发时间与界面显示的秒数不一致。
定时器也没有随页面卸载而统一清理。用户离开页面后,旧回调仍可能访问已经销毁的 DOM;如果再次进入页面,还可能叠加新的计时器。
更稳妥的实现是让回合状态和定时器句柄具有明确所有者:启动新回合前清理旧句柄,进入揭牌阶段后只保留一个等待句柄,并在页面卸载时清除两者。倒计时显示值、阶段和结算只能由同一个状态机驱动,避免 DOM 文本和计时器各自维护一份状态。
手牌比较有几处确定的逻辑错误
- 同花判断写成了链式相等。 JavaScript 的 a == b == c 会先比较 a 和 b,再把得到的布尔值与 c 比较;这里的花色字符串因此不会按“三张牌花色相同”工作。同花顺和同花分支都依赖这段判断,结果会漏掉这些牌型。
- 洗牌不均匀。 sort 使用随机返回 -1 或 1 的比较器,比较结果不满足稳定一致的排序关系;它不能作为均匀洗牌算法。应改为 Fisher–Yates,并把随机源作为参数注入,方便测试。
- 对子比较没有正确携带踢脚牌。 当前分数主要取中间那张和最后一张的点数。对子在不同排序位置时,“对子点数”和“单张踢脚牌”的位置不同,单靠固定下标会把牌型信息混在一起,导致同为对子时的比较结果失真。
- 没有定义 A-2-3 顺子的规则。 代码只检查连续递减的点数,没有单独处理 Ace 作低牌的情况。无论最终决定支持还是不支持,都应该在规则和测试中明确。
- 平局被固定判给先比较的一方。 comparePoints 找到最大分数后直接返回第一次出现的位置,没有表达牌型规则中的平局或后续比较规则。
牌型函数不应通过改 DOM 来表达结果。建议先把每张牌表示为 rank 和 suit,再把评估结果表示为“牌型等级 + 按优先级排列的比较点数”。例如同花比较使用三张牌的降序点数;对子比较使用对子点数和踢脚点数;相同牌型则逐项比较。UI 只负责渲染评估结果。
本地余额不是账本
余额保存在 localStorage,初始金额、每次下注和结算都由浏览器端自行更新,因此用户可以随意修改。初始化时还把存储值直接写入 innerHTML;持久化数据不是可信输入,应先校验为有限、非负的数字,再以文本节点显示,不能把它当作 HTML。
结算代码把获胜方自己的奖池乘以二后发回余额,而另一方投入的金额没有进入该计算,也没有独立的抽成或余额账本说明。这不是一个可验证的资金守恒模型。由于此项目没有服务端资金记录,这些数字只能用于本地 UI 演示,不应展示为真实资产或真实派奖。
金额选择与页面展示也通过 document.querySelector、innerHTML 和元素内联样式直接改变 DOM,而不是使用 Vue 响应式状态。这样会让余额、奖池、选择项、历史记录和倒计时分别散落在 DOM 里,难以可靠地重置、测试或回放。
跨端配置和权限需要收敛
页面直接依赖 document、window.localStorage 和浏览器节点事件。即使清单中列出了多个平台配置,也不能据此认定同一份页面已经通过 H5、App 和小程序验证;小程序端没有通用浏览器 DOM,平台存储也需要使用对应的 uni-app API 并逐目标构建测试。
Android 清单声明了通讯录、电话、定位、相机、录音、日志和系统设置等多项权限,但被检查的页面没有可见的对应能力调用。声明权限不等于页面已经使用这些能力,不过仍应逐项确认目的并移除无关项。开发构建输出和 source map 也应从源码仓库排除,除非有明确、受控的发布归档需求。
建议的修复顺序
- 先把发牌、洗牌、牌型评估和比较抽成无 DOM 的纯函数,写确定性牌例测试,覆盖每种牌型、同牌型 kicker、A-2-3、平局及无效输入。
- 用 Vue 状态替换直接 DOM 操作;将余额限制为纯演示状态,校验持久化值,并以文本插值渲染。
- 把倒计时改成可清理的回合状态机;测试首次结算、暂停/继续、快速离页和重新进入,确认不会有遗留计时器。
- 明确 H5、App、小程序各自的适配范围和存储 API;每个声称支持的目标都要有可重复构建与基本交互验证。
- 审核并删除未使用的系统权限,忽略开发构建目录和 source map,补齐依赖、运行、构建与测试说明,再决定是否继续维护这个旧项目。
本次没有修改或运行原仓库,也没有验证真机行为。它目前更适合作为一份历史原型与代码审查案例,而不是作为成熟跨端或资金服务项目展示。
Live Notes
留言提交后会先进入审核,审核通过后对其他读者公开。