Money-Log
一个用 Electron + React + TypeScript 构建的轻量级桌面记账工具。同样的 OpenLogos 方法论,不同的 AI 工具与技术栈。
.opencode/plugins/openlogos.js,配合 commands/ hooks/,在终端里实现方法论驱动的开发。 需求 —— 谁需要它,为什么?
来源:logos/resources/prd/1-product-requirements/01-requirements.md
用户画像与痛点
上班族,24-50 岁 —— 月收入 5K-30K 元。没有主动记账的习惯,只会被动查看支付宝/微信的交易记录。需要快速记账、分类消费,以及消费趋势洞察。
场景概览
| ID | 场景 | 触发 | 痛点 | 优先级 |
|---|---|---|---|---|
| S01 | 快速记账 | 用户打开应用记一笔 | P02 | P0 |
| S02 | 查看统计报表 | 用户进入统计页 | P01, P03 | P0 |
| S03 | 管理消费分类 | 用户管理分类 | P01 | P1 |
| S04 | 设置密码锁 | 用户开启/关闭锁 | P04 | P1 |
验收标准 —— S01:快速记账
产品设计 —— 用户会看到什么、做什么?
来源:logos/resources/prd/2-product-design/1-feature-specs/01-main-design.md
页面架构
┌─────────────────────────────────────────┐
│ Money-Log Logo │
├─────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Log │ │ Stats │ │ Settings│ │
│ │(default)│ │ │ │ │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ ┌─────────────────────────────────┐ │
│ │ [Page Content Area] │ │
│ │ │ │
│ └─────────────────────────────────┘ │
│ │
└─────────────────────────────────────────┘
Window: 800 x 600 px (min: 600 x 500)
3 tabs: Log / Statistics / Settings UI 原型 —— 桌面应用
来源:logos/resources/prd/2-product-design/2-page-design/01-prototype.html
包含全部 3 个标签页(记账 / 统计 / 设置)的交互式 HTML 原型 —— 在写任何代码之前就设计好了。
S01 交互 —— 快速记账流程
- 应用打开 → 显示默认记账页,含分类网格(8 个带 emoji 图标的预设分类)
- 用户点选一个分类 → 高亮选中项,其余变暗
- 用户用数字键盘输入金额 → 实时校验,过滤非数字字符
- 用户可选地添加备注(最多 200 字符)
- 点击「记一笔」→ 保存到 SQLite → 显示「已记录」提示 → 清空金额,保留分类
| 字段 | 类型 | 必填 | 校验 |
|---|---|---|---|
| 分类 | 图标网格选择 | 是 | 必须选择一个 |
| 金额 | 数字输入 | 是 | > 0,最大 999999.99 |
| 备注 | 文本输入 | 否 | 最多 200 字符 |
实现 —— 从架构到验证通过的代码
架构概览
来源:logos/resources/prd/3-technical-plan/1-architecture/01-architecture-overview.md
graph TB
subgraph Renderer["Renderer Process (React)"]
UI["React Components
UI Layer"]
State["Zustand Store
State Management"]
IPC["IPC Bridge
Process Communication"]
end
subgraph Client["Client (Electron Main Process)"]
Main["Main Process
Backend Logic"]
DB["SQLite Database
Local Storage"]
Store["electron-store
Config Storage"]
end
UI --> State
State --> IPC
IPC --> Main
Main --> DB
Main --> Store UI 放在渲染进程(React + Zustand),数据放在主进程(better-sqlite3)。IPC 桥负责所有通信。密码锁配置存在 electron-store 中,与消费数据分开。
场景 → 时序图
来源:logos/resources/prd/3-technical-plan/2-scenario-implementation/S01-快速记账.md
S01:快速记账 —— 时序图
sequenceDiagram
participant U as User
participant UI as React UI
participant Store as Zustand Store
participant IPC as IPC Bridge
participant DB as SQLite Database
U->>UI: Step 1: Select category
UI->>UI: Step 2: Update selection — highlight active
UI->>U: Step 3: Show selected state
U->>UI: Step 4: Enter amount via keyboard
UI->>UI: Step 5: Validate format — filter invalid chars
UI->>UI: Step 6: Enable/disable button by validity
UI->>U: Step 7: Display amount
U->>UI: Step 8: Click "Log" button
UI->>Store: Step 9: Submit {amount, category_id}
Store->>IPC: Step 10: saveRecord(amount, category_id)
IPC->>DB: Step 11: INSERT INTO records
DB-->>IPC: Step 12: Return write result
IPC-->>Store: Step 13: Return success/failure
Store-->>UI: Step 14: Update state
UI->>U: Step 15: Show "Recorded" toast
UI->>UI: Step 16: Clear amount input
UI->>UI: Step 17: Keep category selected
UI->>U: Step 18: Ready for next entry 步骤
- 用户点选一个分类标签(例如「餐饮」)。
- React UI 更新选中状态,将当前分类视觉高亮。
- UI 向用户展示选中反馈。
- 用户通过键盘输入金额。
- React UI 校验输入格式,过滤非数字字符。
- React UI 根据输入是否有效启用/禁用「记一笔」按钮(未选分类或金额为 0 时禁用)。
- UI 实时显示已输入的金额。
- 用户点击「记一笔」按钮提交。
- Zustand Store 接收消费数据
amount, category_id。 - Store 通过 IPC 调用主进程的
saveRecord()。 - SQLite 执行
INSERT INTO records。 - 数据库返回写入结果(成功或错误)。
- IPC 将结果返回给 Store。
- Store 更新状态,通知 UI。
- UI 显示「已记录」提示。
- React UI 清空金额输入框,准备记下一笔。
- React UI 保留分类选中,减少重复选择。
- UI 进入就绪状态,等待下一笔记账。
异常情况
API 规格 + 数据库 Schema
API 规格 —— Electron IPC 通道
来源:logos/resources/api/local-api.yaml
数据库 Schema —— SQLite 表
来源:logos/resources/database/schema.yaml
测试用例设计(先于代码!)
来源:logos/resources/test/S01-test-cases.md
S01 测试用例 —— 单元测试(节选)
| ID | 描述 | 输入 | 预期 |
|---|---|---|---|
UT-S01-01 | 金额必填 | {} | 400:缺少必填字段 |
UT-S01-02 | 金额必须 > 0 | {amount: 0} | 400:金额必须 > 0 |
UT-S01-05 | 分类 ID 必须在数据库中存在 | category_id: 999 | 400:分类不存在 |
UT-S01-06 | 备注最多 200 字符 | "a".repeat(201) | 400:备注过长 |
UT-S01-10 | 金额截断为 2 位小数 | 12.345 | 存储为 12.34 |
UT-S01-11 | 过滤非数字字符 | "abc" | 过滤为空 |
S01 场景测试(节选)
| ID | 描述 | 覆盖 |
|---|---|---|
ST-S01-01 | 完整记账流程:选择 → 输入 → 保存 → 提示 | Steps 1-18 |
ST-S01-02 | 同一分类连续记账 | Steps 1-18 |
ST-S01-05 | 无效金额格式被拒绝 | EX-5.1 |
代码生成 —— 经由 OpenCode 的设计驱动实现
来源:src/main/database.js · src/renderer/pages/ · src/__tests__/
OpenCode 通过斜杠命令读取时序图和 API 规格,然后批量生成 Electron 主进程 + React 前端 + Vitest 测试。每个批次都闭环:业务代码 → 测试代码 → OpenLogos reporter。
数据层 —— Electron 主进程 → 对应 S01 Steps 10-13
class DatabaseManager {
// S01 Steps 10-13: Save a new expense record
saveRecord(amount, category_id, remark = null, created_at = null) {
const amountInCents = Math.round(amount * 100);
const timestamp = created_at || new Date().toISOString();
this.db.run(
`INSERT INTO records (amount, category_id, remark, created_at)
VALUES (?, ?, ?, ?)`,
[amountInCents, category_id, remark, timestamp]
);
const result = this.db.exec('SELECT last_insert_rowid() as id');
this.save();
return result[0].values[0][0];
}
}单元测试 + 场景测试 → 验证 S01 验收标准
describe('S01: Quick Logging - Unit Tests', () => {
describe('UT-S01-01: Amount is required', () => {
it('should reject empty amount', async () => {
const data = {};
const hasAmount = data.amount !== undefined;
expect(hasAmount).toBe(false);
});
});
describe('UT-S01-10: Amount truncated to 2 decimals', () => {
it('should auto-truncate to 2 decimal places', () => {
const amount = 12.345;
const formatted = Math.floor(amount * 100) / 100;
expect(formatted).toBe(12.34);
});
});
});
describe('S01: Quick Logging - Scenario Tests', () => {
describe('ST-S01-01: Full logging flow', () => {
it('should correctly convert amount to cents', () => {
const amount = 35.5;
const amountInCents = Math.round(amount * 100);
expect(amountInCents).toBe(3550);
});
});
});openlogos verify —— 自动化验收
来源:logos/resources/verify/acceptance-report.md
设计期覆盖断言
部署与 smoke 门禁
示例项目补齐部署完成标记,并通过部署后的最小健康检查。
部署执行已完成,状态由 OpenLogos 受控记录。
部署后 smoke 用例通过,可追溯到 smoke 报告。
变更追踪 —— 每次迭代都洞察影响范围
传统的 AI 编程(「vibe coding」)只改代码,却忘了设计文档、API 规格和测试用例。久而久之,文档与现实脱节,变得毫无用处。OpenLogos 用一套结构化的 delta 工作流解决这一问题 —— 任何变更,无论多小,都必须评估它在整条产物链上的连锁影响。
openlogos mergeopenlogos deploy-doneopenlogos smoke变更传播规则
变更类型决定了最小更新范围。一个看似微小的功能请求,可能会沿着整条链条层层传导:
真实示例 —— 功能:为记账增加备注
来源:logos/changes/archive/每天记账增加备注功能/proposal.md
为记账增加备注字段
需求:用户需要给消费添加备注(例如「火锅聚餐」「打车去医院」)。数据库列 remark 已经存在 —— 但前端 UI 没有输入框,IPC 通道也没有把参数传进去。
影响分析
save-record 通道 —— 增加 remark 参数(字符串,可选,最多 200 字符)records.remark 列已存在(TEXT,可空)更多已归档提案
Money-Log 有 8 个已归档的变更提案 —— 每一个都遵循相同的结构化 delta 工作流。
修复:密码未持久化
代码级修复 —— electron-store 的写入在关闭前未被 await。
月度消费明细视图
设计级 —— 在统计图表下方新增列表视图。
想亲手试试?
克隆仓库在本地运行 Money-Log,或用 OpenLogos 开启你自己的项目。