用 Claude Code 从零做一个 Beancount 记账 Web 应用(全程不写一行代码)

转载请注明出处❤️

作者:测试蔡坨坨

原文链接:caituotuo.top/9f4uiher


前言

你好,我是测试蔡坨坨。

上一篇文章里,我介绍了 Claude Code 的基本使用方式:从安装、对话,到让 AI 直接在终端里修改代码。同时,还做了一个简单的记账软件作为 demo,不过当时更多只是演示流程,记的是“流水账”。

这篇文章,我们来做一次更 真实、更完整的实践

Claude Code 从零搭建一个服务于 Beancount 的个人记账 Web 应用

在开始之前,其实还有几篇背景文章可以一起看看,算是这篇实践的基础铺垫:

  1. 为什么要记账?
  2. 什么是复式记账?
  3. Beancount 复式记账的基本概念和账本结构
  4. 如何用 Python 自动解析微信、支付宝账单 CSV,并生成 Beancount 格式的交易记录

有了这些基础之后,一个新的问题就出现了:

在日常记账时,如何更方便地录入和维护 Beancount 的账务数据?

这篇文章,我们就用 Claude Code,一步一步把这个问题变成一个可以真正使用的 Web 工具。

背景

Beancount 是一套基于 纯文本文件 的复式记账系统。它的核心理念很简单:所有账务数据都保存在普通文本中,每一笔交易都必须满足严格的借贷平衡原则

相比 Excel 或各种商业记账 App,Beancount 有几个很明显的优势:

  • 数据自主:账本就是本地文件,不依赖任何云服务
  • 版本控制友好:天然适合用 Git 管理,所有修改历史都清晰可追溯
  • 可编程:由于是纯文本结构,可以用 Python、Shell 等任何语言进行处理和自动化
  • 复式记账模型:每笔交易都包含借方和贷方,财务结构更严谨,也更容易做统计分析

不过,Beancount 本身其实只是一个 命令行工具,主要负责解析账本、校验借贷是否平衡,以及生成各种报表,并没有自带完整的图形界面。

官方提供的 Web 界面工具叫 Fava,可以用来浏览账本、查看报表和分析资产变化。

Fava 的局限性

Fava 提供了相当完善的报表和可视化能力,但在日常使用中,还是会遇到一些不太顺手的地方:

  1. 移动端体验不佳

    Fava 的界面主要是为桌面端设计的,在手机上操作时,页面布局会显得拥挤,录入和修改数据都比较麻烦。

  2. 新增交易不够友好

    添加一笔交易时,需要手动编写完整的 Beancount 语法,没有表单式的引导,对于日常快速记账来说不太方便。

  3. 无法直接编辑余额断言

    balance 断言(用于记录账户在某一时间点的余额)通常分散在不同文件中。每次对账时,都需要手动找到对应文件并修改内容,过程比较繁琐。

  4. 理财记录管理不方便

    像基金收益、利息收入这类周期性或重复性记录,在 Fava 中没有专门的录入方式,往往还是需要手动编辑账本文件。

  5. 账单导入工具与界面割裂

    比如账单解压密码、对账日期等信息,通常写在独立的 Python 脚本里,与 Fava 的界面没有任何关联,整个流程比较分散。

这些痛点让日常记账的摩擦成本变高

于是我决定用 Claude Code 从零做一个 专门服务于 Beancount 数据文件的轻量级 Web 应用,把这些高频操作集中到一个简单的界面里,让日常记账变得更顺手。

技术选型

核心挑战:浏览器如何读写本地文件?

传统的 Web 应用如果需要访问本地文件,通常必须通过后端服务来完成。但这样一来,就会引入服务器部署、接口维护等额外复杂度,这和 Beancount 本地账本的理念其实是相违背的

好在现代浏览器已经提供了 File System Access API,允许网页在用户授权后,直接读写本地文件系统,这正好契合我们的使用场景。

1
2
3
4
5
6
7
// 用户授权选择目录
const handle = await window.showDirectoryPicker({ mode: 'readwrite' });

// 读取文件
const fileHandle = await dirHandle.getFileHandle('2.bean');
const file = await fileHandle.getFile();
const content = await file.text();

用户只需要 首次选择账本所在目录并授权,页面就可以直接读取和修改其中的 .bean 文件。

为了避免每次打开页面都重新授权,可以把目录句柄(FileSystemDirectoryHandle)保存到 IndexedDB 中。这样在下次访问页面时就可以自动恢复权限,无需重复选择目录。

技术栈

整体技术方案尽量保持 简单、轻量、本地优先

层级 选型
框架 React 18 + TypeScript
构建工具 Vite
图表 Chart.js
样式 原生 CSS(CSS Variables)
本地存储 File System Access API + IndexedDB

整个应用 没有后端、没有数据库、也没有任何云服务依赖

所有数据都直接存储在本地的 Beancount 账本文件中,Web 应用只负责读取、解析和编辑这些文件。

这也意味着:

只要有浏览器,就能随时打开账本进行管理。

功能介绍

1. 目录管理与权限持久化

首次使用时,用户需要通过文件选择器授权 Beancount 账本所在的目录。应用会将获得的目录句柄保存到 IndexedDB 中,之后再次打开页面时即可自动恢复权限,无需每次重新授权。

同时也支持 随时切换目录,如果有多套账本(例如个人账本、家庭账本等),可以在不同目录之间自由切换。

2. 记账流水查看

应用会按照 年月维度 展示当月的所有交易记录,并支持在不同月份之间快速切换。

为了让流水一眼可读,每条记录都会根据账户类型显示不同颜色:

  • 蓝色:转账(Assets → Assets)
  • 红色:支出(Expenses)
  • 绿色:收入(Income)或退款

其中 退款 是一个特殊场景:当贷方账户以 Expenses: 开头时,会被识别为退款,而不是收入。这样处理后,退款金额会 从支出中扣除,而不会被统计为收入

3. 新增交易(引导式表单)

针对 Beancount 复式记账的特点,新增交易时设计成分层引导的方式,减少手写语法的成本。

第一层:交易分类预设

提供四种常见类型:

  • 支出
  • 收入
  • 转账
  • 还款

选择后会自动填充借贷方账户的默认结构。

第二层:模板快捷选择

在分类下会显示常用模板,例如:

1
2
3
4
支出类:餐饮、交通、购物、娱乐...
收入类:工资、公积金、年终奖...
转账类:微信转账、支付宝转账...
还款类:信用卡还款...

选择模板后,付款账户、借贷账户以及备注信息都会自动填充,大幅减少重复输入。

为了方便后续精确删除,每一笔交易写入文件时都会附带一个 内联 ID

1
2
3
2026-03-10 * "XXX有限公司" "三月工资" ; id: abc123xyz
Assets:Current:Wechat:Wallet 3000.00 CNY
Income:Salary -3000.00 CNY

4. 会计恒等式展示

页面 Header 中常驻展示经典的 会计恒等式

1
资产 + 费用 = 负债 + 所有者权益 + 收入

这是 Beancount 复式记账的核心约束,通过持续展示这个公式,也在提醒账务结构始终保持逻辑闭环。

5. 余额断言编辑(assert.bean)

balance 断言是 Beancount 的重要对账机制,用于记录某一天各账户的真实余额,例如:

1
2
3
4
2026-03-04 balance Assets:Current:Wechat:Wallet         0.00 CNY ;微信钱包
2026-03-04 balance Assets:Current:Wechat:MiniFund 153061.40 CNY ;零钱通
2026-03-04 balance Assets:Current:Bank:CMB8721 1678.37 CNY ;招商银行卡
2026-03-04 balance Liabilities:JDBaitiao -0.00 CNY ;京东白条

应用为此提供了专门的编辑界面:

  • 统一日期:所有断言共享同一个对账日期,一处修改即可全部更新
  • 逐行金额编辑:每个账户都有独立输入框,资产类与负债类用不同颜色区分
  • 符号保护:负债账户的 - 前缀是 Beancount 的借贷约定,无法修改,只允许编辑数值部分

保存时会 精确保留原文件的列对齐格式,避免破坏账本结构。

6. 理财收益管理(Investments.bean)

针对定期收益类记录(例如货币基金或银行活期理财),应用提供了单独的管理界面。内置常见模板,例如:

微信零钱通收益

1
2
3
2026-03-03 * "零钱通收益" "202603"
Assets:Current:Wechat:MiniFund 00.00 CNY
Income:Investments -00.00 CNY

主要功能包括:

  • 按收益类型分组展示,清晰区分不同来源
  • 新增记录时,账单期默认填入当月(例如 202603)
  • 支持 年份切换,方便查看历史收益

为了避免误操作,这个界面 不提供删除功能。如果确实需要删除,可以直接编辑原始文件。

7. 账单导入工具集成

配套提供了一个 Python 脚本,用于从 QQ 邮箱自动下载支付宝和微信账单 ZIP 文件并解压。Web 应用也为这个脚本提供了辅助管理界面。

密码管理

支付宝和微信账单的解压密码会定期更换,因此可以直接在界面中修改。应用会通过正则替换 Python 文件中的对应变量:

1
2
self.alipay_zip_password = ''
self.wechat_zip_password = ''

已对账日期

应用会自动扫描 data/bank_statements/ 目录中的支付宝账单文件名,例如:

1
支付宝交易明细(20260304-20260311).csv

并解析出最新对账日期,展示为:

1
2026-03-11

运行脚本

提供一键复制命令到剪贴板:

1
cd /path/to/beancount-importer && python -m src.main

由于浏览器的安全限制,网页无法直接执行本地进程,因此采用 复制命令 → 用户在终端执行 的方式作为中转。

8. 自定义 Toast 通知

应用统一替换了浏览器原生的 alert / confirm 弹窗,改为自定义 Toast 通知组件

  • 支持 info / success / error / confirm 四种类型
  • 全局单例,通过函数式调用:toast(‘操作成功’, ‘success’)
  • 删除等敏感操作使用 toastConfirm,避免原生对话框打断页面体验

这样既保持了交互的一致性,也让整体 UI 更加流畅自然。

小结

整个应用 没有任何后端,也没有数据库,所有数据都直接存储在本地的 Beancount 账本文件中。

其中:

  • File System Access API 解决了浏览器读写本地文件的问题
  • IndexedDB 用于持久化目录句柄,避免每次都重新授权

通过这两项能力,就可以在浏览器里实现一个真正 本地优先(local-first) 的 Web 应用。

对于有一定技术背景、同时使用 Beancount 管理个人财务的人来说,这套工具链提供了一种比 Fava 更贴近日常操作习惯的方式。它并不是要取代 Fava 强大的报表与分析能力,而是作为一个 专注于数据录入和维护的前端工具,与 Fava 形成互补。

而 Beancount 最迷人的地方依然没有改变:

所有数据始终是纯文本。

这意味着:

  • 任何时候都可以直接打开文件查看或修改
  • 可以用 Git 进行版本管理
  • 可以用任何编程语言进行自动化处理

数据不会被锁在某个应用或平台里,这正是 纯文本记账最大的魅力