家庭日用品追踪小程序 · 开发过程技术文档
---项目代号:耗品记(household-consumable-tracker)
文档版本:v1.0 | 整理日期:2026-08-07
适用读者:开发者、维护者、产品评审
1. 文档说明
本技术文档用于完整记录「家庭日用品追踪小程序」从立项到当前版本(完全纯本地)的架构设计、功能实现与关键开发决策,重点沉淀开发过程中踩过的坑、做过的架构取舍,便于后续维护与知识传递。
本文内容基于源码(7 个页面 + 3 个工具模块 + 全局配置)与开发过程记录整理,关键结论均可在 overview.md 与 .workbuddy/memory/ 中找到对应时间线。
文档结构
- 项目背景与目标
- 产品功能概览
- 技术架构
- 核心模块设计与实现
- 开发历程与关键技术决策(按时间线)
- 当前架构状态与权衡
- 踩坑与经验总结
- 后续优化建议
- 附录:文件清单与验证命令
2. 项目背景与目标
2.1 痛点
家庭日用品(纸巾、洗洁精、牙膏、调料等)属于「低单价、高频消耗、易忘记补货」的品类。用户通常在「用完了才发现没买」时才补,缺乏:
- 库存余量感知(还剩多少、还能用几天)
- 到期预警(食品/洗护品临期浪费)
- 消费复盘(某类物品月均花多少钱、用得多快)
2.2 目标
做一个轻量、零门槛、纯本地的家庭耗品记账与管理工具:
- 录入物品 → 日常记录使用 → 自动算剩余量与可使用时长
- 每周盘点校准实际剩余,提升预测准确度
- 临期/低库存主动提醒,生成采购清单可一键分享
- 消耗分析,看清钱花在哪、用得有多快
2.3 产品定位决策
- 纯本地、无账号、无云端同步:首版即确定,降低复杂度与合规成本,数据完全留在用户手机。
- UI 风格 = 微信原生卡片分区风(2026-07-23 拍板):白底分区卡 + 顶部概览 + 微信绿识别度,信息最全、认知成本最低。
- 主色微信绿
#07C160,警告品牌红#FA5151,分类语义色映射微信原生 tag 配色。
3. 产品功能概览
| 页面(tab) | 文件 | 核心功能 |
|---|---|---|
| 物品(首页) | pages/index |
物品列表(分类筛选/搜索/排序/卡片/紧凑双视图)、顶部数据概览、快速记录使用、即将过期专区、提醒横幅 |
| 添加 | pages/add |
新增/编辑物品(消耗品/耐用品两类、常见日用品预设库、分类色) |
| 详情 | pages/detail |
物品完整信息、使用记录时间线、补货记录、补货/使用/编辑/删除、分享卡片(Canvas 长图) |
| 分析 | pages/analysis |
在用物品实时分析 + 历史消耗统计(支出排行条形图按分类着色) |
| 提醒 | pages/reminder |
库存预警 + 过期提醒列表、提醒开关/时间/星期设置、补货阈值/提前天数/过期提醒天数滑块、采购清单长图分享 |
| 盘点 | pages/checkin |
每周盘点:逐项填剩余量、实时算期间消耗、差异提示、覆盖式保存 |
| 我的 | pages/mine |
数据清空、隐私政策入口、关于 |
数据层(
utils/storage.js)、工具层(utils/util.js/utils/card.js)被所有页面复用,是项目的「单一数据源」。
4. 技术架构
4.1 整体架构(纯本地)
┌─────────────────────────────────────────────┐
│ 微信小程序前端(7 个页面) │
│ index / add / detail / analysis / reminder │
│ / checkin / mine │
└───────────────┬───────────────────────────────┘
│ require('../../utils/storage.js')
┌───────────────┴───────────────────────────────┐
│ 数据层 utils/storage.js(纯本地存储) │
│ 工具层 utils/util.js(日期/数值/ID/颜色) │
│ utils/card.js(Canvas 卡片绘制) │
└───────────────┬───────────────────────────────┘
│ wx.setStorageSync / getStorageSync
┌───────────────┴───────────────────────────────┐
│ 本地 Storage(用户手机,无任何云端依赖) │
│ hct_items / hct_history / hct_settings / │
│ hct_checkins │
└───────────────────────────────────────────────┘
关键事实(2026-08-03 起):项目无任何云端依赖——没有云函数、没有云数据库、没有微信订阅消息、没有账号系统。所有逻辑在客户端完成。
4.2 目录结构
household-consumable-tracker/
├── app.js / app.json / app.wxss # 全局逻辑、配置、样式令牌
├── project.config.json # 已移除 cloudfunctionRoot
├── pages/ # 7 个页面,每页 .js/.json/.wxml/.wxss
├── utils/
│ ├── storage.js # 数据存储层(单一数据源)
│ ├── util.js # 通用工具
│ └── card.js # Canvas 卡片绘制
├── tab-icons/ # 4 个 tab 的选中/未选中图标
└── overview.md # 修复与变更总览
4.3 数据模型(storage 层)
存储键(见 storage.js 顶部常量):
| Key | 内容 |
|---|---|
hct_items |
在用物品数组(含使用/补货记录、实时分析) |
hct_history |
已消耗完的物品(保留用于分析) |
hct_settings |
提醒/阈值/过期天数等配置 |
hct_checkins |
每周盘点记录(剩余量快照 + 期间消耗) |
Item 结构(核心字段):
{
id, name, category, unit,
itemType: 'consumable' | 'durable', // 消耗品 / 耐用品
totalQuantity, remainingQuantity, price,
purchaseDate, expireDate,
status: 'active' | 'consumed',
usageRecords: [{ time, quantity, note }],
restockRecords: [{ date, quantity, price }],
consumedTime, analysis
}Settings(DEFAULT_SETTINGS):lowStockThreshold(0.2)、repurchaseDays(7)、expireLeadDays(14)、notifyEnabled、notifyFreq(固定 weekly)、notifyTime('20:00')、notifyWeekday(0=周日)、lastBannerDismissDate、lastLocalAlertDate。
设计要点:所有写入都经过 round() 数值兜底,避免 NaN 写入 storage;页面读取 item 时 JSON.parse(JSON.stringify(raw)) 深拷贝,防止污染 storage 原始引用。
4.4 页面与路由
- tabBar 4 项:物品 / 分析 / 提醒 / 我的(
app.json)。 - 添加、详情、盘点为非 tab 页,通过
wx.navigateTo进出。 - 全局开启
__usePrivacyCheck__: true,隐私授权由微信系统弹窗处理(详见第 5 节与第 7 节踩坑)。
4.5 工具层
util.js:genId(时间+自增+随机,本地唯一键)、formatDate、formatRelative、daysBetween、daysUntil、round(NaN 兜底)、hexToRgba(兼容老基础库,替代 8 位 hex)。card.js:drawPurchaseListCard(采购清单长图)、captureCard(取 canvas node → 缩放 → 绘制 → 导出 tempFilePath)、roundRect(canvas 无内置 roundRect 的兼容写法)。
5. 核心模块设计与实现
5.1 物品生命周期管理
- 新增
addItem:构造 item、初始analyze()、unshift 进hct_items。 - 记录使用
addUsage:减少remainingQuantity、追加 usageRecord、实时重算 analysis;剩余 ≤ 0 时标记consumed、从在用移入历史。 - 补货
restockItem/restockConsumedItem:叠加数量、累加价格(首单+全部补货的总成本)、追加 restockRecord;已消耗完的物品从历史恢复为在用。 - 编辑
updateItem:安全更新可编辑字段,不破坏已有记录;数量字段映射为「当前剩余量」,若超过总量则把总量拉齐。
5.2 消耗分析算法(耐用品 vs 消耗品)
analyze() 是核心,对两类物品采用不同口径(见 storage.js):
- 消耗品:以「已消耗量」为基准算日均用量、已消耗成本、日均成本。
- 耐用品:不计「使用」为消耗,算「持有成本/天」= 总价 ÷ 持有天数,剩余量只记录不触发归档。
- 成本计算修正:总成本 =
item.price(已在补货时累加了所有补货价),避免了 free 首单补货时被算成 2 倍的 bug。
5.3 补货预测(三级回退)
predictPurchase() 按数据质量依次回退,保证尽量有值:
- 盘点快照(最准):用最近两次盘点的间隔与消耗量算日均。
- 使用记录:回退到「使用总量 ÷ 购买至今时长」(避免短间隔放大)。
- 历史同名均值:同名下已消耗物品的
dailyUsage均值。
最终输出daysLeft与needBuy(剩余 ≤repurchaseDays触发)。
5.4 每周盘点系统
saveInventory(date, entries, note):记录每个物品当前剩余量,计算「期间消耗 = 上次剩余 − 本次剩余」,写入 usageRecords(note 标记「盘点 日期」)。- 覆盖式保存有回滚逻辑:覆盖旧盘点时先把剩余量恢复到盘点前、移除当时写入的消耗记录,再从历史拉回曾被误归档的物品,避免重复扣减。
- 盘点只针对消耗品(
itemType !== 'durable');耐用品为持有资产无需盘点剩余量。
5.5 提醒系统(纯本地,不依赖微信)
三种表现(均只在打开小程序时可见):
- 首页横幅
shouldShowBanner:盘点日过点 或 存在紧急待办(优先级 1)时显示;lastBannerDismissDate当天不再弹。 - 提醒 tab 本地弹窗:打开提醒 tab 且
shouldNotifyToday()为真时wx.showModal「有 N 件物品即将用完」,lastLocalAlertDate每天最多一次。 - 提醒 tab 列表:库存预警(low_stock)+ 过期提醒(expire),按优先级排序。
触发判定 shouldNotifyToday():仅当 notifyEnabled 且当天为 notifyWeekday 且已过 notifyTime 才为真。过期提醒由 getExpiringItems(leadDays) 实现,按剩余天数升序,文案三态(剩X天/今天到期/已过期X天)。
5.6 分享卡片(Canvas)
- 详情页「分享卡片」用 Canvas 2D 画一张 750×1140 资产卡(分类色带 + emoji + 四项核心指标),经
wx.showShareImageMenu转发;底部统一走无码品牌文案(已去除云函数取小程序码的依赖)。 - 提醒页「采购清单」用
card.drawPurchaseListCard生成一张长图,含所有低库存物品与建议采购量,转发给微信好友/文件传输助手。 - 兼容处理:长图
dpr手动调小(≤2)避免高清屏 canvas backing store 超限;wx.showShareImageMenu不存在时降级提示更新微信。
6. 开发历程与关键技术决策(时间线)
以下每个决策点都对应
overview.md的具体修复记录,本文做归纳与归因。
6.1 初版与上线(2026-07-22 ~ 07-23)
- 完成 7 页 + 纯本地数据层初版,确定微信原生卡片分区风 UI。
- 07-23 通过 MP 后台审核正式上线(AppID:
wx6c16bedae8307769,真实非测试),服务类目「生活服务/工具」,隐私保护指引已发布。
6.2 隐私授权方案踩坑与回退(07-29)
- 第一次尝试:自建
components/privacy-popup/逐页组件,在__usePrivacyCheck__: true下弹窗。 - 问题:自研组件的「同意」
resolve无法真正把授权落库(getPrivacySetting().needAuthorization仍为 true),导致每个 tab 进入都重弹,用户实测「除一个 tab 外其他三个仍在弹」。 - 决策(二次修正):删除整个自研组件,改由微信系统在首次启动自动弹一次并正确记录授权,全局生效。
- 经验:不要和微信原生隐私机制「抢授权」,系统弹窗才是权威路径。
6.3 过期提醒改为应用内(07-29)
- MP 后台模板「物品过期提醒」失效,微信订阅推送中移除过期推送,改为纯前端应用内提醒(
getExpiringItems+ 提醒页滑块 1–30 天)。
6.4 微信订阅消息推送的尝试与最终移除(重点)
- 早期实现:接入云开发 + 云函数
sendSubMsg,通过App.onShow在打开小程序时调用wx.cloud.callFunction下发订阅消息(盘点提醒模板 77050、补货/消耗提醒)。 - 实测问题:用户设置周日 20:00 推送,却要「打开小程序切到提醒 tab 再退出」才收到。根因——
App.onShow只在小程序被打开/回到前台时触发;微信「一次性订阅」授权 7 天有效、发一次即作废,无法在 App 关闭时由服务端定时下发,天然做不到「到点自动推送」。 - 决策(2026-08-03):既然做不到准时提醒,该功能价值有限却带来云函数、授权弹窗、MP 模板配置等大量复杂度,全部移除微信推送,彻底转为纯本地小程序内提醒。
- 清理范围:删除
utils/cloud.js、utils/notify.js、cloudfunctions/sendSubMsg/;清理 app.js / index.js / checkin.js / detail.js / reminder.js 中所有推送与授权引用;storage.js删除 8 个订阅相关字段与 4 个函数;project.config.json删除cloudfunctionRoot。 - 验证:全项目 grep 推送相关标识符零残留;全部 JS
node --check通过;cloudfunctions/目录清空。
6.5 代码审查与 Bug 修复(2026-08-01)
对全量代码静态审查(node --check 全通过,无语法错),定位并修复 10 个问题(2 P1 + 6 P2 + 2 P3):
P1 功能性 Bug
- 分析页「支出排行」条形图颜色全灰:
computeSummary的聚合缺category字段,导致catColor取 undefined 回落灰色。修复后条形图按物品实际分类着色。 - 提醒页抢跑
markNotifiedToday挡掉微信推送:reminder.jsonShow 在shouldNotifyToday()为真时立即markNotifiedToday(),与 app.js 微信推送共用lastNotifyDate,导致用户先切提醒 tab 后 app.js 跳过推送。引入独立lastLocalAlertDate去重键解决。
P2 代码质量/死代码/兼容
3. 删除 cloud.js 的 getMiniCode 死代码 + cloudfunctions/getQRCode/ 目录。
4. 删除 storage.js 未调用的 getAnalysisSummary/saveCheckin/getCheckinStats 兼容旧接口。
5. checkin.wxml 的 show-scrollbar="false"(字符串)改 {{false}}(布尔)。
6. detail.wxml 的 8 位 hex(#RRGGBBAA) 改 hexToRgba() 兼容老基础库。
7. mine.wxml 删除冗余「用户协议」入口。
P3 优化
8. util.round() 加 NaN/null 兜底(返回 0)。
9. checkin.js 历史盘点 stats 不再按当前 itemType 快照过滤,避免物品改类型后历史条目丢失。
6.6 运行期日志报错修复
- 移除云函数后,开发者工具仍读已删的
sendSubMsg/index.js,产生 ENOENT 报错——需关闭并重开项目清除云函数注册缓存。 detail.json含非法的enableShareAppMessage/enableShareTimelinekey(uni-app 写法误用于原生小程序)。分享实际由 JS 的onShareAppMessage/onShareTimeline处理函数承担,直接删除这两个 json key 即恢复干净。
7. 当前架构状态与权衡
7.1 架构状态(2026-08-03 起)
小程序 = 完全纯本地、无任何云端依赖:
- 业务数据纯
wx.setStorageSync,无账号 / 无家庭 / 无云同步 / 无云函数。 - 提醒完全依赖小程序内 UI(首页 banner + 提醒 tab 弹窗/列表),无微信服务通知。
- 隐私仍由微信系统弹窗处理(
__usePrivacyCheck__: true)。 utils/仅剩storage.js/util.js/card.js。
7.2 已失去的能力(权衡)
- App 关闭时收到通知:移除微信推送后,提醒仅在打开小程序时可见。这是为「准时性不可达 + 复杂度过高」做的主动取舍。
- 若日后需恢复定时推送,需接入云函数定时触发器(cron)+ 处理一次性订阅授权窗口,复杂度高。
7.3 仍保留的能力
- 完整的本地库存管理、消耗分析、补货预测、每周盘点。
- 应用内到期/低库存提醒 + 采购清单一键分享。
- 数据完全私有、离线可用、零后端运维成本。
8. 踩坑与经验总结
- 微信隐私授权:不要自研组件去「抢」系统授权弹窗,在
__usePrivacyCheck__: true下,系统弹窗才是权威路径,逐页组件会导致每 tab 重弹且授权不落库。 - 微信订阅消息限制:
App.onShow是唯一客户端触发点,一次性订阅授权 7 天/一次,无法在 App 关闭时定时下发。需要真·到点推送必须上云函数定时触发器。 - Canvas 兼容性:老基础库不支持 8 位 hex 与内置
roundRect,统一用hexToRgba()+ 自写roundRect();长图dpr手动封顶避免 canvas 尺寸超限。 - 数据模型:页面读取 item 要深拷贝,防止污染 storage 原始对象;旧数据可能缺
analysis,读取时主动analyze()兜底。 - 数值安全:所有出入 storage 的数值经
round()兜底 NaN,避免脏数据污染统计。 - 静态审查价值:
node --check+ Grep 残留引用 是低成本高收益的「伪编译」手段,能在无真机环境下拦住大部分引用类回归。 - 过渡态清理:移除大功能(如微信推送)时,要连字段、函数、云函数、配置、孤立样式一起清,并用 grep 验证零残留,否则会留下隐蔽的运行时报错与体积浪费。
9. 后续优化建议
| 方向 | 建议 | 备注 |
|---|---|---|
| 数据导出/备份 | 增加「导出 JSON / 导入」功能,弥补纯本地无云同步的易丢风险 | 用户换机/清缓存即丢失 |
| 定时提醒 | 若确需「到点推送」,评估云函数定时触发器 + 订阅授权引导流程 | 复杂度高,需重新引入云端 |
| 多家庭/共享 | 当前无账号体系,天然单机;共享需引入云同步 + 家庭关系 | 见 MEMORY 历史方案,从未实现 |
| 可视化升级 | 分析页环形图(WXML 不支持内联 SVG,可改用 Canvas) | 当前用进度条零兼容风险 |
| 埋点分析 | 纯本地无后端,留存/崩溃需去 MP「数据分析-访问分析」看平台自带数据 | 无需自建 |
| 组件库 | 如需 100% 微信原生组件观感,可单独引入 weui-miniprogram | 当前未引入以降低构建风险 |
附录 A:关键文件清单
| 文件 | 职责 |
|---|---|
app.js |
全局初始化(storage.init),无推送逻辑 |
app.json |
页面/tabBar/隐私开关配置 |
app.wxss |
微信原生卡片分区风样式令牌 |
utils/storage.js |
数据存储层(单一数据源,含分析/预测/盘点/提醒) |
utils/util.js |
日期/数值/ID/颜色工具 |
utils/card.js |
Canvas 卡片绘制(采购清单/资产卡) |
pages/index/index.js |
首页:列表/筛选/搜索/排序/横幅/过期专区 |
pages/analysis/analysis.js |
消耗分析(条形图按分类着色) |
pages/reminder/reminder.js |
提醒中心(列表/设置/本地弹窗/分享长图) |
pages/checkin/checkin.js |
每周盘点(剩余量/差异/覆盖保存) |
pages/detail/detail.js |
详情(使用/补货/编辑/删除/分享卡片) |
pages/add/add.js |
新增/编辑(预设库/分类色) |
pages/mine/mine.js |
我的(数据清空/隐私) |
注:移除云函数后若开发者工具仍报
sendSubMsg/index.jsENOENT,需关闭并重开项目清除云函数注册缓存。
