
一、业务需求为什么这么设计产品是一个面向全球的跨境商城 demo核心需求是把前面 12 个应用的本地化能力整合进一个真实结算流程界面语言 5 种中/繁/英/日/韩结算币种 4 种CNY/USD/EUR/JPY两个维度自由组合所有金额存人民币整数分展示时按所选币种汇率换算并本地化格式化税费 8.5%、满 ¥300 免运费否则 ¥12、满 ¥200 减 ¥50——规则在 CNY 分空间计算支付流程待支付 → 处理中1.5s→ 已支付含订单号、下单时间、状态数量用复数规则英文 “1 item”/“3 items”订单号带千分位时间随语言格式化。本文记录完整技术方案所有代码与BillFormatPage.ets一一对应。二、总体架构┌─ 语言层LANGS STRINGS5 语言文案表 t() 三级降级 ├─ 币种层CURRENCIESISO 4217 码 符号 汇率 小数位 fmtMoney() 货币格式化 ├─ 商品层PRODUCTS5 语言名称/描述 CNY 整数分价格 ├─ 计算层整数分运算小计/税费/运费/满减/合计 ├─ 格式化层fmtQty() 复数 fmtOrderNo() 千分位 fmtTime() 日期时间 └─ 状态层StorageLink locale currency双持久化 State tab/qty/paying/paidAt架构核心是两条管线金额管线CNY 分 → 汇率换算 → 货币格式化与文案管线locale → 文案表 → 界面文本。两条管线在 build 时汇合任何一维变化都触发全页重渲染。三、币种层ISO 4217 元数据interfaceCurrencyCfg{code:string;// ISO 4217symbol:string;// 展示符号rate:number;// 1 CNY ?digits:number;// 小数位}constCURRENCIES:CurrencyCfg[][{code:CNY,symbol:¥,rate:1,digits:2},{code:USD,symbol:$,rate:0.14,digits:2},{code:EUR,symbol:€,rate:0.13,digits:2},{code:JPY,symbol:¥,rate:21.5,digits:0}];三个关键设计code用 ISO 4217CNY/USD/EUR/JPYIntl.NumberFormat style:currency需要标准货币码自造符号会被格式化器拒绝digits独立配置日元无小数位¥27,928、人民币 2 位¥1,299.00——虽然style:currency会按 CLDR 自动决定小数位但digits字段用于兜底分支toFixed(digits)保证降级路径同样正确rate是演示固定值真实产品汇率来自服务端且必须带时间戳与版本见生产铁律。四、金额管线整数分 → 汇率 → 货币格式化4.1 存储与运算全部整数分interfaceProduct{priceCents:number;// CNY 整数分}// 例耳机 129900 分 ¥1,299.00为什么用整数分浮点数0.1 0.2 ! 0.3金额用number直接运算会产生1299.0000000000002之类的尾差。金额以整数分存储与运算129900加减乘除全是整数零误差——这正是页面底部 tip 文案K.tip强调的内容。4.2 换算toDisplay()privatetoDisplay(cnyCents:number):number{constratethis.curCfg().rate;if(rate1){returncnyCents/100;// CNY分 → 元}returnMath.round(cnyCents*rate)/100;// 其他币种先按汇率换算再保留两位}Math.round(cnyCents * rate) / 100先四舍五入到分再除以 100 转元——129900 × 0.14 18186分 $181.86汇率 1CNY走快路径避免无谓运算注意换算在分空间做四舍五入保证显示精确到目标币种的分。4.3 格式化fmtMoney()privatefmtMoney(cnyCents:number):string{try{constfmtnewintl.NumberFormat(this.currentLocale,{style:currency,currency:this.curCfg().code});returnfmt.format(this.toDisplay(cnyCents));}catch(err){return${this.curCfg().symbol}${this.toDisplay(cnyCents).toFixed(this.curCfg().digits)};}}style:currency的 CLDR 数据驱动能力一览localeCNY 1299.00USD 181.86JPY 27928zh_CN¥1,299.00US$181.86JP¥27,928en_USCN¥1,299.00$181.86¥27,928ja_JP1,299.00$181.8627,928符号随语言变中文界面下美元显示US$消歧义、英文界面人民币显示CN¥——CLDR 的货币符号消歧规则自动处理千分位/小数点随语言1,299.00英文逗号vs 德语1.299,00反try/catch 兜底格式化失败时用symbol toFixed(digits)手工拼接——digits字段在此刻派上用场。五、计算层规则全部在 CNY 分空间constTAX_RATE_PERCENT:number8.5;constFREE_SHIPPING_THRESHOLD:number30000;// 满 ¥300分constSHIPPING_CENTS:number1200;// 未满运费 ¥12constDISCOUNT_THRESHOLD:number20000;// 满 ¥200分constDISCOUNT_CENTS:number5000;// 减 ¥50privatetaxCents():number{returnMath.round(this.goodsTotalCents()*TAX_RATE_PERCENT/100);}privateshippingCents():number{if(this.goodsTotalCents()FREE_SHIPPING_THRESHOLD)return0;returnSHIPPING_CENTS;}privatediscountCents():number{returnthis.goodsTotalCents()DISCOUNT_THRESHOLD?DISCOUNT_CENTS:0;}privatetotalCents():number{returnthis.goodsTotalCents()this.taxCents()this.shippingCents()-this.discountCents();}三个要点规则参数化门槛/运费/满减全部提为模块级常量改规则不动逻辑Math.round处理 8.5% 税率129900 × 8.5 / 100 11041.5→ 四舍五入11042分——税费必须整数分舍入在计算层做而非显示层计算与显示彻底分离taxCents()返回分只有fmtMoney()负责呈现——规则改、显示改互不影响。六、格式化三件套复数/订单号/时间6.1 数量复数fmtQty()privatefmtQty(n:number):string{try{constprnewintl.PluralRules(this.currentLocale);constcatpr.select(n);if(this.currentLocaleen_US){returncatone?1${this.T(K.item)}:${n}${this.T(K.item)}s;}return${n};// 中/日/韩无复数形态}catch(err){return${n};}}PluralRules.select(1)→one、select(3)→other英文1 item/3 itemsitems伪复数中/日/韩直接数字语言层无复数概念与 05 应用复数规则专项呼应这里是它的轻量应用。6.2 订单号fmtOrderNo()privatefmtOrderNo():string{try{returnnewintl.NumberFormat(this.currentLocale).format(ORDER_NO);}catch(err){return${ORDER_NO};}}2026081912345678→ 英文20,260,819,123,456,78千分位分组——订单号这种数字型标识也走 NumberFormat保证分组习惯随语言。6.3 时间fmtTime()privatefmtTime(ts:number):string{try{constfmtnewintl.DateTimeFormat(this.currentLocale,{dateStyle:long,timeStyle:short});returnfmt.format(newDate(ts));}catch(err){returnnewDate(ts).toString();}}中文2026年8月19日 14:30、英文 “August 19, 2026 at 2:30 PM”——dateStyle:long timeStyle:short的组合模板由语言决定与 02 应用一致。七、状态管理与双持久化StorageLink(STORAGE_LOCALE)currentLocale:stringDEFAULT_LOCALE;StorageLink(STORAGE_CURRENCY)currentCurrency:stringDEFAULT_CURRENCY;Statetab:stringmarket;Stateqty:number[][0,0,0,0,0,0];Statepaying:booleanfalse;StatepaidAt:number-1;privatetimer:number-1;aboutToAppear():void{// locale 与 currency 各做一次 setOrCreate persistPropif(!AppStorage.getstring(STORAGE_LOCALE)){AppStorage.setOrCreate(STORAGE_LOCALE,DEFAULT_LOCALE);}PersistentStorage.persistProp(STORAGE_LOCALE,DEFAULT_LOCALE);if(!AppStorage.getstring(STORAGE_CURRENCY)){AppStorage.setOrCreate(STORAGE_CURRENCY,DEFAULT_CURRENCY);}PersistentStorage.persistProp(STORAGE_CURRENCY,DEFAULT_CURRENCY);}两个StorageLink独立持久化i18n_series_13_locale与i18n_series_13_currency两个 key——重启后中文界面 USD 结算的组合被完整记忆qty用slice()拷贝再赋值this.qty arrArkUI 的State数组必须整体替换才能触发渲染原地this.qty[i]不生效——这是 ArkTS 状态管理的经典陷阱支付状态机paying处理中→paidAt时间戳-1 表示未支付——adjust()/clearCart()修改购物车后把paidAt复位保证改单后回到待支付的语义正确timer在aboutToDisappear清理setTimeout1.5s 支付模拟页面销毁时必须clearTimeout。八、数据流复盘一次完整交互启动 → 双 persistProp语言 币种→ 中文界面 CNY → 市场页 6 商品¥1,299.00 / ¥459.00 / ¥899.00 / ¥2,199.00 / ¥299.00 / ¥159.00 用户点击 $ USD 币种徽章 → currentCurrency USDStorageLink → AppStorage → 落盘 → curCfg() USDrate 0.14 → 全页 fmtMoney 重算¥1,299.00 → $181.86 → 汇率提示条变 1 CNY ≈ $0.14 用户加购 2 件耳机 → qty[0]2 → 购物车 Tab 角标 (2) → 小计 $363.72、税费 8.5% → $30.92、运费 满¥300 → 免运费 → 合计 $394.64 用户点击立即支付 → payingtrue按钮变支付处理中… → 1.5s 后 paidAtnow → 横幅 支付成功订单号 20,260,819,… 下单时间 2026年8月19日 14:30九、ArkTS 兼容要点catch (err)不带类型注解所有intl调用包 try/catchqty: number[]用slice()产生新数组再赋值触发State渲染ForEach(PRODUCTS, (p: Product, i: number) ...)key 用p.key稳定唯一buildStrings(pairs: Pair)中type Pair Array[string, string]显式类型别名new Map(pairs)免断言PRODUCTS的names/descs是Mapstring, string取用用??链降级get(code) ?? get(DEFAULT_LOCALE) ?? keyButton(...).enabled(this.paidAt -1 !this.paying)条件禁用用enabled而非点击内拦截语义更清晰。十、性能与内存每次 build 创建 fmtMoney × 20 次6 商品单价 5 费用行 × 2 个视图分支NumberFormat实例化毫秒级生产优化模块级缓存codecurrency → fmt避免重复创建qty数组仅 6 个元素slice()拷贝开销可忽略支付setTimeout是唯一定时器aboutToDisappear清理无泄漏PRODUCTS的 5 语言 Map 在编译期构建无运行时开销。十一、小结本应用是系列的总集成测试整数分存储9 的 SI 基准思想× 货币格式化3 的深化× 复数5 的应用× 日期时间2 的应用× 文案表1 的骨架× 双持久化1 的扩展。技术上最值得记的两点金额永远整数分运算、展示永远走style:currency——前者杜绝浮点误差后者接管符号/分组/小数位的全部本地化。应用深化维度01文案表 三级降级 持久化03NumberFormat 货币专项05PluralRules 复数专项13货币数字日期复数综合生产级账单← 本文给生产环境的三条铁律① 金额存整数分或 decimal 字符串禁止浮点运算② 汇率必须带版本与生效时间换算结果缓存并在汇率更新时失效③ 货币显示用 ISO 4217 码走style:currency兜底路径才用手拼符号——因为符号随语言消歧US$ vs $ vs 手拼必然出错。