二次元绘画创作
56.21M · 2026-02-04
作为一名既爱写代码又爱打台球的程序员,我一直想做一款“纯粹”的台球工具。市面上的 App 要么广告满天飞,要么功能单一——能计分的不能复盘,能复盘的体验太差。
于是,我决定自己造个轮子。完全根据我自己需要来定制化开发。
这款名为**“追分记”的小程序,不仅支持中式黑八/九球的复杂计分(断点续打、三人追分)、自定义训练模式(统计五分点等训练记录),还内置了一个基于物理交互的战术板**。
这篇文章我将从产品构思、AI 辅助设计、本地存储架构等维度,复盘这次独立开发的完整过程。
在这次开发中,我尝试了一种新的工作流:全程 AI 开发。从灵感到 UI,再到代码落地,让AI 扮演“产品助理”、“UI 设计师”、开发工程师的角色。
在写第一行代码前,我先整理了自己的“吐槽清单”:
基于此,确定了 MVP(最小可行性产品)的三大核心功能:智能计分器、断点续打系统、便携战术板。
作为程序员,配色和 UI 往往是短板。这次我没有自己瞎折腾 CSS,而是利用 AI 生成设计灵感。
从一个新的项目开始,我让 Trae 帮我生成一个完整prd文档,包含需求分析,功能模块,页面细节。
然后让它开始实现这些功能,然而最后实现的UI界面效果很不理想。
于是,我把这个截图扔给 v0(v0.app/),让他按照这个截图的哪内容帮我从新生成一个页面UI,并且产出一个完整的配色,以便其他页面复用。提示词如下:
最后它帮我产出了一个html文件,我打开一天,天啦,这也太牛逼 plus了。
这效果已经超出我的想象了。于是我立马让
Trae 把这个页面1:1还原到对应的小程序页面。
拿到视觉基调后,我将其转化为全局 CSS 变量(app.wxss),确保全站风格统一:
page {
--bg-primary: #0a0f1a;
--text-primary: #f8fafc;
--accent-green: #10b981;
--card-bg: rgba(30, 41, 59, 0.7);
background: linear-gradient(160deg, var(--bg-primary) 0%, var(--bg-secondary) 100%);
}
从最初简陋的线框图,到引入 tdesign 组件库,再到配合 AI 生成的图标,产品的“颜值”经过了三轮大的迭代,最终呈现出一种“极客范儿”的精致感。
业务逻辑和UI进行了完美的融合,没有任何bug。简直完美。
做“断点续打”功能时,我遇到了一个典型问题:
用户可能开了局,没打完就退出了;过几天又开了新局。久而久之,Storage 里会堆积大量废弃的比赛快照(Snapshot)。
为了解决这个问题,我设计了一套基于索引的 GC(垃圾回收)机制。
ongoingMatches):记录所有“进行中”比赛的元数据(ID、时间、模式)。match_current_{id}):每场比赛的具体状态单独存一个 Key,防止单条数据过大。每次小程序启动或进入列表页时,触发 cleanupOrphans 函数:
// utils/storage.js
function cleanupOrphans() {
// 1. 获取“存活”的比赛 ID 集合
const list = ongoing.getList()
const activeIds = new Set(list.map(v => v.id))
// 2. 遍历 Storage 中所有的 Key
const keys = wx.getStorageInfoSync().keys
keys.forEach(k => {
// 3. 识别比赛快照 Key 格式
if (k.startsWith('zhongba_match_current_')) {
const id = Number(k.split('_').pop())
// 4. 如果 ID 不在存活列表中,视为“孤儿数据”,直接删除
if (!activeIds.has(id)) {
safeRemove(k)
console.log(`[GC] Cleaned up orphan match: ${id}`)
}
}
})
}
这个机制保证了 Storage 永远只存储有用的数据,既节省空间又维护了整洁。
最后我还让它生成了一份数据快照,以便以后接入服务端的时候可以快速的创建数据库:
小程序在切入后台(onHide)后,setInterval 定时器往往会暂停或变慢。为了保证比赛计时的准确性,不能单纯依赖定时器累加。
解决方案:时间戳校准法
onHide 时,记录当前时间戳 runningSince。onShow 时,计算 (Date.now() - runningSince)。elapsedTime 中。// pages/nine-ball/nine-ball.js
onShow() {
if (this.data.isRunning) {
const now = Date.now()
const rs = this.data.runningSince
const delta = Math.floor((now - rs) / 1000)
// 即使在后台挂起了一小时,回来时间也会瞬间补齐
if (delta > 0) {
this.setData({ elapsedTime: this.data.elapsedTime + delta })
}
}
}
“追分记”虽然只是一个工具类小程序,但“麻雀虽小,五脏俱全”。
独立开发最迷人的地方,莫过于看着一个想法,在自己的键盘下一点点变成触手可及的现实。
如果你也是台球爱好者,或者对小程序开发感兴趣,欢迎在微信搜索**“追分记”**体验,也欢迎在评论区交流技术细节!