旅行记账本 — UI 设计篇-鸿蒙国际旅行
·


一、设计目标
记账本的 UI 目标:一眼分清"花了什么、花了多少(原币)、折合多少(本币)"。设计原则:
- 原币金额是主角:账单行右侧加粗青色(#0E7490);
- 本币折算是小注:每笔账下灰色小字"折算 ¥××",不喧宾夺主;
- 汇总要震撼:顶部总额 24px 大号,是整页视觉焦点。
二、页面结构(组件树)
Scroll
└─ Column(宽 100%)
├─ Row:标题「旅行记账本」+ 💱
├─ Flex:语言切换(青色选中)
├─ Row 汇总卡(浅青底 #ECFEFF)
│ ├─ Text:旅行总花费(灰小字)
│ └─ Text:¥4,823.65(24px 青、加粗)
├─ Text:消费账单(小标题)
├─ ForEach → 账单行(Column 白卡)
│ ├─ Row:描述(左)| 原币金额(右青加粗)
│ └─ Text:折算 ¥××(灰小字)
├─ Text:汇率换算器(小标题)
├─ Column 卡(浅蓝底 #F0F9FF)
│ ├─ Text:换算成(币种按钮组,青色选中)
│ ├─ Row:−100 | 金额 | +100(步进器)
│ ├─ Row:原币金额 | ≈ 本币金额(右青加粗)
│ └─ Text:汇率 1 EUR = 7.85 CNY(灰小字)
└─ Text:货币样式对比(小标题)
└─ Column 白卡:SYMBOL/CODE/NAME 三行
三、色彩与字号规范
主题色 #0E7490(青,象征"水/流动/钱"),与 01 蓝、02 紫区分:
| 元素 | 色值 | 字号 | 说明 |
|---|---|---|---|
| 页面标题 | #1F2937 | 22 Bold | |
| 总额(主角) | #0E7490 | 24 Bold | 汇总卡 |
| 原币金额 | #0E7490 | 14 Bold | 每笔账单 |
| 折算金额 | #9CA3AF | 12 | 小注 |
| 账单描述 | #1F2937 | 15 Medium | |
| 汇率文字 | #9CA3AF | 11 | 透明参考值 |
| 选中币种 | 底 #0E7490 / 字白 | 12 |
四、核心实现(UI 代码)
4.1 汇总卡:视觉焦点
Row() {
Column({ space: 2 }) {
Text(this.T(K.total)).fontSize(12).fontColor('#6B7280')
Text(this.fmtTotal())
.fontSize(24).fontWeight(FontWeight.Bold).fontColor('#0E7490').margin({ top: 2 })
}.alignItems(HorizontalAlign.Start)
}
.width('94%').padding(16).backgroundColor('#ECFEFF').borderRadius(14).margin({ top: 12 })
设计要点:总额 24px 是全页最大字号,浅青底 #ECFEFF 让"总计"独立于白色明细区——结论与过程用底色分区。
4.2 账单行:原币/折算双行
ForEach(BILLS, (b: Bill) => {
Column() {
Row() {
Text(b.desc).fontSize(15).fontWeight(FontWeight.Medium).fontColor('#1F2937').layoutWeight(1)
Text(fmtMoney(this.currentLocale, b.currency, b.amount))
.fontSize(14).fontWeight(FontWeight.Bold).fontColor('#0E7490')
}.width('100%')
Text(`${this.T(K.converted)}: ${fmtConverted(this.currentLocale, b)}`)
.fontSize(12).fontColor('#9CA3AF').width('100%').margin({ top: 4 })
}
.width('94%').padding(12).backgroundColor(Color.White).borderRadius(12).margin({ top: 8 })
}, (b: Bill) => b.id)
层级设计:
- 描述(左):15 号 Medium,
layoutWeight(1)自适应——长描述不挤占金额; - 原币金额(右):14 号加粗青色——用户真实花的钱,最醒目;
- 折算金额(行下):12 号灰色小字"折算 ¥××"——参考视角,不喧宾夺主。
4.3 汇率换算器:步进 + 实时换算
Column() {
Text(`${this.T(K.to)}:`).fontSize(13).fontColor('#6B7280').width('100%')
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Start }) {
ForEach(CURRENCIES, (c: Currency, idx: number) => {
Text(`${c.flag} ${c.code}`)
.fontSize(12).padding({ left: 10, right: 10, top: 6, bottom: 6 }).borderRadius(14)
.backgroundColor(this.convertIdx === idx ? '#0E7490' : '#E5E7EB')
.fontColor(this.convertIdx === idx ? Color.White : '#374151')
.onClick(() => { this.convertIdx = idx; })
}, (c: Currency) => c.code)
}.width('100%').margin({ top: 6 })
Row({ space: 10 }) {
Button('−100').fontSize(12).backgroundColor('#E5E7EB').fontColor('#1F2937')
.onClick(() => { this.convertAmount = Math.max(1, this.convertAmount - 100); })
Text(`${this.convertAmount}`).fontSize(18).fontWeight(FontWeight.Bold).width('60').textAlign(TextAlign.Center)
Button('+100').fontSize(12).backgroundColor('#E5E7EB').fontColor('#1F2937')
.onClick(() => { this.convertAmount += 100; })
}.width('100%').margin({ top: 8 })
Row() {
Text(`${fmtMoney(this.currentLocale, this.convertCur().code, this.convertAmount)}`)
.fontSize(16).fontWeight(FontWeight.Bold).fontColor('#1F2937').layoutWeight(1)
Text(`≈ ${fmtMoney(this.currentLocale, HOME_CURRENCY, this.convertAmount * this.convertCur().rateToCny)}`)
.fontSize(14).fontWeight(FontWeight.Bold).fontColor('#0E7490')
}.width('100%').margin({ top: 10 })
Text(`${this.T(K.rate)}: 1 ${this.convertCur().code} = ${fmtRate(this.currentLocale, this.convertCur().rateToCny)} CNY`)
.fontSize(11).fontColor('#9CA3AF').width('100%').margin({ top: 4 })
}
.width('94%').padding(14).backgroundColor('#F0F9FF').borderRadius(14).margin({ top: 8 })
设计要点:
- 步进器 −100/+100:
Math.max(1, ...)防止减到负数,中间金额 18px 加粗; - ≈ 符号:明示这是估算(参考汇率),不是精确对账;
- 汇率小字:11 号最浅灰,标注"参考值"性质,不误导用户当实时牌价。
4.4 货币样式对比表
ForEach(['symbol', 'code', 'name'] as string[], (disp: string) => {
Row() {
Text(disp.toUpperCase()).fontSize(12).fontColor('#6B7280').width('64').textAlign(TextAlign.Start)
Text(fmtMoneyDisp(this.currentLocale, disp)).fontSize(13).fontColor('#1F2937')
}.width('100%')
}, (disp: string) => disp)
固定 width('64') 让左侧标签对齐,右侧三种样式($1,234.50 / USD 1,234.50 / 1,234.50 US dollars)对比直观。
五、多语言排版验证清单
| 检查项 | 方法 |
|---|---|
| 符号位置差异 | $ 前 / ¥ 前 / € 前,交给 NumberFormat,UI 不拼符号 |
| JPY 无小数位 | 自动省略 .00,无需判断 |
| 千分位差异 | 德语 1.299,00 vs 英文 1,299.00,locale 决定 |
| 长描述不溢出 | 描述 layoutWeight(1),金额固定右侧 |
| 折算行对齐 | 灰色小字统一缩进,与主金额行区分 |
六、设计取舍
- 为什么青色:系列内 01 蓝(信息)/02 紫(时间)/03 青(钱),色彩语义与业务绑定,用户跨应用可迁移认知;
- 为什么汇总卡浅青底:与白色账单行区分层级——"总计"是结论,"明细"是过程;
- 为什么折算用灰色小字而非同样醒目:原币是用户真实花的钱(事实),折算只是参考(视角)——视觉权重必须与信息权重一致;
- 为什么语言切换用平铺胶囊:5 种语言,与 02 同一模式复用,保持系列交互一致性。
七、小结
记账 UI 的成败在层级:原币(事实)最醒目、本币(视角)次之、汇率(说明)最弱。颜色(青 vs 灰)+ 字号(14 vs 12 vs 11)+ 位置(行内 vs 行下 vs 页脚)三重区分,任何语言、任何币种下用户都不会混淆"我花了什么"和"它值多少"。
更多推荐




所有评论(0)