首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >用 WorkBuddy 从零做一个 PWA 黄金记账 App:62 笔备忘录数据、三种盈亏算法与 8 个真实踩坑

用 WorkBuddy 从零做一个 PWA 黄金记账 App:62 笔备忘录数据、三种盈亏算法与 8 个真实踩坑

原创
作者头像
用户12825227
发布于 2026-10-17 16:31:23
发布于 2026-10-17 16:31:23
740
举报

一切的起点,是手机备忘录里一份密密麻麻的黄金买卖记录。

一、起因:一份读不懂的备忘录

朋友手机备忘录里记了两年多的黄金买卖,长这样:

有日期、有价格、有克数、有自己手算的盈亏,还有一堆看不懂的「抵3」「汇1」。

需求很朴素:做个手机 App 专门记这个。但要真做成,得先把三件事聊明白。

1.1 盈亏到底怎么算?

这是第一个分歧点。同样一批买卖,算法不同,数字能差出几千块。最终做了三种并存、可随时切换:

算法

逻辑

适合谁

移动加权平均

每次买入后重算持仓均价,卖出按当前均价算盈亏

长期持有、看整体成本

先进先出 FIFO

卖出时按最早的买入批次配对

波段、想知道"这波赚了多少"

手动对应批次

卖出时自己指定对应哪几笔买入

精细化核对

1.2 手续费要不要单独记?

答案是不记。因为记录时手续费已经折算进金价了,再单列一个字段只会造成重复计算。需求里看起来"应该有"的功能,有时候恰恰不该做。

1.3 「汇」和「抵」是什么?

这是整个项目最有意思的部分。朋友做波段有自己的一套黑话:

  • 汇 N:把连续几天买入的黄金汇总,算出一个均价
  • 抵 N:后面卖出把这组抵平,算出这一波的短期盈利
  • 未抵:买入价太高被挂住了,还没卖出去

比如「汇1」汇总出 45 克、均价 1097.57;「抵3」卖掉后盈利 300。这些是人家的真实工作流,不是通用的金融概念。App 要好用,就得把这个习惯做进去,而不是让人来适应软件。


二、技术选型:为什么是 PWA

需求里有两个硬约束:手机上要像 App 一样打开、数据不能上传。

原生 App 开发成本高、还要装包;纯网页又没法"添加到桌面"。PWA 正好卡在中间:

  • manifest.webmanifest + sw.js,安卓和 iOS 都能添加到主屏幕
  • 数据全在 localStorage,没有服务器,天然不上传
  • 静态文件部署,改一行代码刷新就能看到

三、核心实现

3.1 数据模型

一笔交易长这样:

group 字段是整个「汇/抵」功能的支点——有了它,才能按组算均价、算波段盈亏、判断抵没抵平。

3.2 三种算法的统一抽象

三种算法共用一套 lots(批次)模型,区别只在于卖出时怎么挑批次:

按品种各自维护 lots,所以「积存金」和「实物金」的盈亏互不干扰。

3.3 批组(汇/抵)的自动计算

对每一组,算四件事:

再加两个实用提示:回本价(= 组均价),以及按当前金价算的浮亏/浮盈。

3.4 从备忘录文本解析数据

62 笔手工录入不现实,所以写了个解析器,把备忘录原文直接变成结构化数据:

解析完做了交叉验证——App 算出来的盈亏和朋友自己手算的逐条对上了:

批组

手算

App 计算

抵0

150

150.00

抵3

300

300.00

抵5

397.4

397.40

汇1(45克)

均价 1097

1097.57


四、8 个真实踩坑(重点)

这部分比实现本身值钱。

坑 1:String.replace 把 $' 当成了特殊符号

为了生成一个"双击就能打开"的单文件 HTML,需要把 CSS/JS 内联进去。最初这么写:

结果整页 JS 报错。原因是 JS 的 String.replace 里,$' 表示"匹配之后的所有内容"——脚本里恰好有这个字符组合,于是 </body></html> 被注入进了 <script> 里。

修法是永远用函数式替换:

坑 2:Service Worker 让用户永远看到旧版本

最早 sw.js 用的是「缓存优先」+ 固定缓存名 gold-ledger-v1。改完代码重新部署,用户打开还是旧页面。

三处整改:

  1. 缓存名升级到 v2,fetch 策略改成在线优先、失败回退缓存
  2. activate 时给页面发消息,检测到新 SW 就自动重载一次(用 sessionStorage 防抖,避免刷新死循环)
  3. 设置里加一个「强制刷新 / 检查更新」按钮,注销 SW + 清空缓存 + 带时间戳重载

坑 3:红米手机上价格只能输入个位数

用户反馈"金价怎么可能只有个位数"。真机截图里那个输入框带着 type="number" 的上下箭头——小米浏览器对 type="number" 有兼容 bug,会限制位数或吞掉后续输入。

改成文本类型 + 数字键盘,问题解决:

坑 4:红米浏览器里找不到「添加到主屏幕」

小米自带浏览器把这个功能叫「添加到桌面」「添加书签 → 桌面快捷方式」,藏在「工具箱」里,名字和 Chrome 完全不一样。

加了主动安装提示条:捕获 beforeinstallprompt 事件显示一键安装按钮;不支持的浏览器则弹出针对红米的图文路径。

坑 5:小数批组号被吞

备忘录里有「抵4.5」这个组,但正则 ([汇抵])\s*(\d+) 只认整数,.5 被丢掉,抵4.5 的两笔并进了抵4。加上小数匹配才修好:

坑 6:批组号写在下一行

有些记录长这样——交易行没写组号,组号写在后面的「盈利」行里:

解法是延迟回填:解析时把缺组号的交易记进 pending,遇到「盈利…抵N」行再回填上去。

坑 7:导入去重导致改过的数据同步不过来

原逻辑是「id 已存在就跳过」。问题来了:在电脑上改了一笔,再同步到手机,改动丢了。

改成同 id 覆盖更新。但备忘录解析出的 id 是 memo_1、memo_2… 会互相覆盖,所以备忘录路径改用内容指纹去重(时间+买卖+价格+克数+品种),同一段粘两次不会翻倍,不同段也不会打架。

坑 8:jsdom 测试自己去线上拉了旧代码

写自动化测试时用 resources: 'usable' + 真实线上 URL,结果 jsdom 把已部署的旧版 app.js 拉下来覆盖了本地内联代码,断言莫名其妙失败。

改成只用本地内联脚本后稳定了。测试环境不要联网拉被测对象。


五、上线与数据迁移

部署的是不含任何数据的空壳,用户数据始终在设备本地。换手机迁移走这条路:

  1. 电脑上导出备份 JSON(或生成迁移二维码)
  2. 微信发到手机
  3. 手机端「设置 → 导入」粘贴

这里还有个意外:二维码最大容量约 2.9 KB,而 62 笔数据有 12 KB,装不下。所以做了降级——大数据自动切换成「复制文本」,走微信转发再粘贴,体验一样顺。


六、写在最后

几个复盘:

  1. 先问清楚再动手。光"盈亏怎么算"就聊了三轮,如果直接按自己的想法做,做完一定是错的。
  2. 尊重用户原有的工作流。「汇/抵」不是标准金融术语,但那是人家多年习惯,做进去了 App 才有人用。
  3. "应该有"的功能未必该做。手续费字段看着合理,实际会造成重复计算。
  4. 真机比什么都重要。前面 8 个坑,有 4 个只有在真手机上才会暴露。

最终交付:一个能添加到手机桌面的离线 App,62 笔真实数据(2025-12-22 至 2026-09-11),三种盈亏算法可切换,本地存储不上传。


附录:技术栈

  • 纯原生 HTML/CSS/JS,无框架无构建工具
  • Canvas 手绘盈亏走势图(红涨绿跌,符合国内习惯)
  • localStorage 持久化,键 gold_ledger_v1
  • PWA:manifest + service worker
  • 测试:jsdom 无头测试,覆盖解析、编辑、迁移、批组四个模块

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、起因:一份读不懂的备忘录
    • 1.1 盈亏到底怎么算?
    • 1.2 手续费要不要单独记?
    • 1.3 「汇」和「抵」是什么?
  • 二、技术选型:为什么是 PWA
  • 三、核心实现
    • 3.1 数据模型
    • 3.2 三种算法的统一抽象
    • 3.3 批组(汇/抵)的自动计算
    • 3.4 从备忘录文本解析数据
  • 四、8 个真实踩坑(重点)
    • 坑 1:String.replace 把 $' 当成了特殊符号
    • 坑 2:Service Worker 让用户永远看到旧版本
    • 坑 3:红米手机上价格只能输入个位数
    • 坑 4:红米浏览器里找不到「添加到主屏幕」
    • 坑 5:小数批组号被吞
    • 坑 6:批组号写在下一行
    • 坑 7:导入去重导致改过的数据同步不过来
    • 坑 8:jsdom 测试自己去线上拉了旧代码
  • 五、上线与数据迁移
  • 六、写在最后
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档