礼金记账这件事很怪:一辈子用不了几次,但每次都用得极其狼狈。宾客排着队, 你一手接红包一手写字——写完还得当场念一遍名字确认。 于是我从纸质礼簿的格子出发,重做了一本会自己留痕的电子簿。

原文发布在掘金 · 有完整代码与踩坑表

下面是这篇笔记的结构与要点整理,完整正文、代码片段与验证方式请到掘金阅读。

在掘金阅读全文

一句话要点

  • 把「先写纸上、事后再录」的两步,压成当场一次录入,并且改过的每一笔都有据可查。
  • 留痕不做在前端——让数据库自己记账,前端永远改不动历史。
  • 隐私靠行级安全(RLS)兜住:前端拿不到别人的数据,不是因为前端守规矩,而是数据库根本不返回。

一、缘起:上一版只解决了一半

之前那版是纯本地的,数据都在浏览器里。好处是快、不联网也能用;坏处也很明显——换台设备就没了, 想和家人一起记账更不可能。所以这一版换成云端,但必须把本地版丢掉的东西补回来。

二、先想清楚:换成云端会丢掉什么

云端化最让人不放心的就是隐私。做法是把「谁能看见什么」下沉到数据库层: 所有表开启行级安全,每条记录带 owner_id,策略强制 owner_id = auth.uid()。 前端只能调接口,接口只能看到自己的行。

三、技术栈

  • Vue 3.5 + <script setup> + TypeScript 5.7
  • Vite 6(构建)+ vue-tsc(类型检查)
  • Tailwind CSS 4 做原子样式
  • vue-router 4,走 hash 路由,静态托管友好
  • xlsx 负责 Excel 导出,云 SDK 负责数据库与登录

源码 src/ 约 6900 行、42 个文件:6 个页面、7 个组件、5 个数据访问模块, 样式按职责拆成 9 份。数据模型刻意做得极薄,只有两张核心表:

-- 一本礼簿
ledgers (title, subtitle, host_name, event_date,
         book_type,      -- 喜事 / 满月 / 周岁 / 寿宴 / 升学 / 白事
         theme,          -- 由 book_type 派生,不单独给用户选
         cover_enabled, texture, page_size, owner_id)

-- 一条礼金
gift_entries (ledger_id, seq, guest_name,
              amount,        -- 只收正整数:CHECK (amount >= 1 AND amount = trunc(amount))
              pay_method, gift_item, relation, phone, address, note,
              UNIQUE (ledger_id, seq))

六个场景的差异全部收在 BOOK_TYPES 注册表的一行里:封面印面字、内页用字 (白事印「奠仪」、寿宴印「寿礼」)、礼品名后缀,以及绑定好的那套皮肤。 加第七个场景只需要加一行,不用碰组件。

四、真正花了时间的地方

4.1 礼簿网格:四种信息叠在同一个格子上

纸质礼簿的格子是这样的:姓名竖着写,中间一个「贺」字,下面是中文大写金额,最下面一行小写数字。 这四段必须在视觉上严格对齐成一条竖列,还要能塞满整页。用的是四行 + 12 列的网格, 列数由 CSS 变量驱动,可以跟着纸张规格改。

4.2 中文大写金额:不要用浮点数做这个

金额只收正整数,用 CHECK 约束在数据库层就卡住;换算成大写时全程走整数运算, 避开浮点误差。

4.3 打印铺满 A4:全文的重点

网页排版和打印排版是两套逻辑。这一节处理的是分页、缩放与页边距,让内容真正铺满整张 A4, 而不是留一堆白边。

4.4 封面书法字体,以及「囍」字带来的一个下午

一个看起来很简单的字,字体覆盖与字形回退能把人耗掉半天。

4.5 修改留痕:让数据库自己记账

这是全文我最想强调的一条:留痕不写在前端,而是用数据库触发器完成。 前端即使写错代码,也改不掉历史记录。

4.6 微信快捷登录:三条约束,加一道门槛

顺手记录了一个反直觉的结论:本地永远看不到微信登录按钮,这是设计而不是 bug。 它要求 https 且非 localhost,所以本地永远是「不可用态」, 要验证那条分支只能发布后用线上域名加真实 UA 看。

4.7 让「场景」在列表里变成看得见的颜色

六个场景铺成真实卡片,靠色差一眼分辨,而不是靠读文字。

4.8 演示数据:自动铺,但只铺一次

4.9 农历:一张 201 个数的表,胜过装一个依赖

4.10 姓名竖排:两端对齐,而不是居中

居中会让长短名字的视觉重心飘忽,两端对齐才像印出来的。

4.11 收尾三件事:并发、包体、样式

五、怎么验证的(以及为什么不用截图)

排版这类东西,截图看不准。所以走的是「脚本 + 无头浏览器出 PDF」:

pnpm build       # vue-tsc --noEmit 后再出产物
pnpm test:amount # 金额只收正整数的 51 条断言
pnpm test:lunar  # 农历换算的 16 个基准点
pnpm test:date   # 公历中文写法,扫 372 组月日反例防漏
pnpm test:env    # 微信登录的 11 种环境组合 + 文案下限断言
pnpm print:test  # 生成排版校验页,再用无头 Chrome 转 PDF

另外准备了三个不连数据库、不需要登录的预览页, 改样式的时候不用先造数据:封面/内页/汇总页连排、六场景列表、挂真组件的录入表单。

整件事的收获不是「又做了一个小工具」,而是:把约束放到更下层—— 金额的约束给数据库、留痕给触发器、权限给 RLS、排版给打印校验页。 每一层都少一点「靠自觉」,结果就稳一点。
© 2026 HOLZWEGE 返回随笔列表