旅行记账本 — 使用环境与场景篇-鸿蒙国际化货币


一、它解决什么场景问题
旅行中记账的最大麻烦是币种混乱:钱包里同时有欧元硬币、日元纸币、刷卡消费记的是美元。旅行者想知道"我到底花了多少"——但没人愿意拿计算器把 6 种币种手动换算。本应用用旅行记账本承载货币本地化能力,解决:
- 原币记账(保持真实性)+ 本币折算(统一视角);
- 货币符号随语言习惯显示(中英日韩各有正确姿势);
- 汇率换算器随手查任意金额。
二、典型用户故事
故事 A:背包客小林(zh_CN)
小林在欧洲玩了两周,记账本里躺着 €4.5 的咖啡、£180 的酒店、$28 的门票。打开 App,每笔账下面自动跟着"折算 ¥××"(人民币),底部一行"旅行总花费 ¥4,823.65"——不用再翻汇率 App 心算了。
故事 B:留美学生 Kevin(en_US)
Kevin 把 App 切英文,账单金额变成 $1,299.00 格式(美元符号在前、千分位逗号)。他注意到汇率换算器里 JPY 显示 ¥860(日元无小数位),而 EUR 显示 €4.50——每个币种都按自己的规则显示,一看就是"做给懂行的人用的"。
故事 C:商务旅客 Tanaka(ja_JP)
Tanaka 常飞东京-伦敦。他用日元视角看账:£180 下面跟着 約 ¥32,000。他感叹汇率工具不少,但"自动把每笔账都折算好、还能按币种分类小计"的只有这个。
故事 D:自由译者 Maria(de_DE,未内置语言)
Maria 的系统语言是德语,本应用没有内置德语。此时界面文案降级到默认中文,但金额格式化仍按她选择的语言(如 en_US)正确输出 €4.50。她发现:货币格式与界面语言可以解耦——即使文案没有翻译,数字、金额、日期的本地化也应该独立工作,这是 i18n 分层设计的体现(文案层与格式化层各自降级)。
三、适用使用环境
| 环境类型 | 适配说明 |
|---|---|
| 旅行记账 | 多币种消费统一汇总 |
| 差旅报销 | 外币票据折算本币 |
| 跨境电商比价 | 同商品多币种价格对比 |
| 多币种钱包 | 资产分布查看 |
| 财务审计 | 多币种流水核对 |
四、目标用户画像
- 环球旅行者:多国多币种消费,需要统一视角看开销;
- 差旅商务人士:外币票据报销需要折算凭证;
- 跨境消费者:海外购、代购需要比价;
- 出海产品团队:验证货币格式化的多语言覆盖。
五、关键设计决策
1. 原币记账,本币折算
BILLS 存 { currency, amount } 原币金额,折算在展示时计算。理由:汇率会变,存折算结果是存死数据;展示时算,汇率更新后历史账目自动重算。
2. 参考汇率透明化
页面明确标注"汇率 1 EUR = 7.85 CNY"(换算器下方小字),用户知道这是参考值——真实产品接实时汇率,但演示场景透明比精确更重要。
3. 语言即币种视角
切换语言时,不仅符号变化,折算目标(本币)也固定在 CNY——旅行者的"家"是固定概念,不随界面语言变(与 13 应用"币种随订单走"一致的生产实践)。
五·五、货币格式化的三个维度(场景视角)
用户在不同语言下看到的货币差异,本质上来自三个独立维度,理解它们才能设计出正确的产品:
| 维度 | 由谁决定 | 例子(¥ 金额) | 错误做法 |
|---|---|---|---|
| 币种 | 数据(账单的 currency) | 日元账单显示 JPY | 把币种写死在 locale 里 |
| 符号样式 | locale(用户阅读习惯) | zh_CN 看美元是 US$,en_US 看是 $ | 自己拼 $ + 数字 |
| 精度 | 币种(JPY 0 位,CNY 2 位) | ¥860 vs ¥860.00 | 统一 toFixed(2) |
举一个真实反例:某 App 的英文界面显示 US$1,299.00,中文界面显示 US$1,299.00——两个界面一样,因为开发者把 US$ 写死在了模板里。用户虽然能看懂,但德语用户看到的是 1.299,00 US$(点号千分位、逗号小数位),如果模板写死 US$1,299.00 就会完全错误。正确的做法是三个维度都交给 NumberFormat 组合输出:
// 一句话输出:币种 + 符号样式 + 精度 全部正确
new intl.NumberFormat('de_DE', { style: 'currency', currency: 'USD' }).format(1299)
// → "1.299,00 $"(德语习惯)
这是"场景视角"最重要的一课:用户看到的每一分钱格式,都是"数据(币种)× 环境(locale)× 规则(精度)"三者相乘的结果,缺一个维度就错一个。
六、边界与降级
- 未知币种:
CURRENCIES找不到时汇率兜底 1(按 1:1 折算)并继续展示; - 格式化失败:try/catch 降级
toFixed(2)裸数字; - 汇率精度:演示值 2-3 位小数,真实场景取银行中间价并标注时效;
- 金额精度:本应用是展示型记账,浮点可直接用;涉及支付的金额必须整数分(见 13)。
七、竞品对比
| 方案 | 优点 | 缺点 | 本应用选择 |
|---|---|---|---|
| 全本币记账(入账即折算) | 展示简单 | 汇率漂移不可追溯 | 拒绝 |
| 原币记账 + 展示折算 | 真实可追溯 | 需维护汇率 | ✅ |
| 多币种分账本 | 结构清晰 | 无法跨账本汇总 | 不适用 |
| 服务端统一记账 | 数据一致 | 离线不可用 | 混合 |
七·五、一次语言切换的完整链路(场景复盘)
以"账单从中文切到英文"为例,拆解货币格式化的响应式传导:
用户点击「🇺🇸 EN」胶囊
│ 1. onClick 赋值
▼
this.currentLocale = 'en_US' // @StorageLink 全局状态
│ 2. AppStorage 广播 + PersistentStorage 落盘
▼
T(key) 重新执行 // 文案:标题/按钮/卡片标签全变英文
fmtMoney(...) 重新执行 // 账单金额: €4.50 → EUR €4.50 / $1,299.00
fmtConverted(...) 重新执行 // 折算: 折算 ¥35 → CNY 35.33
fmtTotal() 重新执行 // 汇总: ¥4,823.65 → CNY 4,823.65
fmtRate(...) 重新执行 // 汇率: 1 EUR = 7.85 → 1 EUR = 7.85
│ 3. ArkUI 增量渲染
▼
页面整体刷新,无闪烁、无空白
这个链路揭示了货币 i18n 的核心认知:符号变化不是翻译,是格式化。$1,299.00 与 US$1,299.00 不是两个翻译版本,而是同一个数字 1299 在 en_US 与 zh_CN 两种 locale 下的格式化结果。所以开发者永远不要自己拼 $ + 数字——交给 intl.NumberFormat 一处输出,符号位置、千分位、小数位一次全部正确。
八、场景工作流:一次环球消费记账
以小林的两周欧洲游为例,走一遍完整操作:
1. 巴黎左岸咖啡 €4.5 → 记账(原币 EUR)
2. 伦敦酒店两晚 £180 → 记账
3. 纽约博物馆门票 $28 → 记账
4. 打开汇总卡:"旅行总花费 ¥4,823.65"
→ 每笔账下方都有折算金额,无需手动换算
5. 用汇率换算器查:100 EUR 是多少人民币?
→ 选中 EUR,+100 步进,显示 ≈ ¥785
6. 切英文复查汇率显示:"1 EUR = 7.85 CNY" 格式不变
每一步都体现货币 i18n 的要点:原币真实(事实层)、折算统一(视角层)、符号随语言(表达层)。
九、与相邻应用的关系(场景视角)
- 应用 02 日期时间:时间要存 UTC 投影本地——与"原币记账、本币折算"是同一思想:存事实,展示时投影;
- 本应用 03 货币:币种格式化三要素(币种/符号/精度)各归其位;
- 应用 13 订单:把"展示型折算"升级为"支付级金额"——整数分存储、税费分离、发票格式化,是 03 的生产级延伸;
- 应用 05 复数:订单里"3 items / 1 item"的单复数由复数规则决定,与货币格式化组合使用。
十、扩展方向
- 实时汇率:接入行情 API,缓存 + 过期刷新;
- 历史汇率快照:每笔账记入账时汇率,供发票追溯;
- 预算预警:按币种设预算,超支提醒;
- 分享账单:本地化格式导出(与 13 的发票导出互通)。
十一、生产级注意事项
- 汇率时效标注:展示"更新于 10:00"之类的时间戳,避免用户把参考汇率当实时牌价;
- 精度策略分级:展示金额用
NumberFormat(小数位随币种),计算金额用整数分(如amount * 100存储); - 汇率来源统一:所有币种用同一来源的同一时刻汇率,避免混合牌价造成汇总偏差;
- 负数金额:退款/取消场景的负数,
NumberFormat能正确处理(-$5.00/-¥5.00),UI 不要手动加负号; - 超大金额:超过 2^53 的金额(如外币债券)必须用字符串或 Decimal 类型,普通记账场景 number 足够。
十一·五、场景陷阱:汇率写死的代价
真实产品最常见的返工,是把汇率"写死"在代码里。举两个场景:
陷阱 1:入账即折算
开发者图省事,在记账时就把 €4.5 × 7.85 = ¥35.33 存进数据库。三个月后汇率变成 7.4,历史账单还是按 7.85 折算——总花费虚高,用户对账时发现"我明明花了 ¥4800,App 却显示 ¥5000"。本应用的做法(存原币、展示时折算)让历史数据永远跟着最新汇率重算,不会失真。
陷阱 2:符号写死
开发者写 '$' + amount.toFixed(2)。中文用户看到 $4.50,德语用户看到 $4.50——都错了。正确做法是 intl.NumberFormat(locale, { style: 'currency', currency: 'USD' }).format(4.5),德语用户自动得到 4,50 $(逗号小数位、符号在后)。
陷阱 3:精度一刀切
对 JPY 用 toFixed(2) 会显示 ¥860.00,违背日元"无角分"的习惯;对 USD 用 toFixed(0) 又会丢掉 0.50。精度必须由币种决定,交给 NumberFormat 自动处理。
十二、结语
旅行记账展示的是货币 i18n 的完整闭环:原币是事实,本币是视角,汇率是桥梁,格式是语言。四者各司其职,用户才能在全球任何角落清楚地知道"我花了多少"。这个应用也为 13 的订单发票打好了货币格式化基础。
更多推荐




所有评论(0)