在这里插入图片描述
在这里插入图片描述

一、设计目标

记账本的 UI 目标:一眼分清"花了什么、花了多少(原币)、折合多少(本币)"。设计原则:

  1. 原币金额是主角:账单行右侧加粗青色(#0E7490);
  2. 本币折算是小注:每笔账下灰色小字"折算 ¥××",不喧宾夺主;
  3. 汇总要震撼:顶部总额 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)

层级设计:

  1. 描述(左):15 号 Medium,layoutWeight(1) 自适应——长描述不挤占金额;
  2. 原币金额(右):14 号加粗青色——用户真实花的钱,最醒目;
  3. 折算金额(行下):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/+100Math.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),金额固定右侧
折算行对齐 灰色小字统一缩进,与主金额行区分

六、设计取舍

  1. 为什么青色:系列内 01 蓝(信息)/02 紫(时间)/03 青(钱),色彩语义与业务绑定,用户跨应用可迁移认知;
  2. 为什么汇总卡浅青底:与白色账单行区分层级——"总计"是结论,"明细"是过程;
  3. 为什么折算用灰色小字而非同样醒目:原币是用户真实花的钱(事实),折算只是参考(视角)——视觉权重必须与信息权重一致;
  4. 为什么语言切换用平铺胶囊:5 种语言,与 02 同一模式复用,保持系列交互一致性。

七、小结

记账 UI 的成败在层级:原币(事实)最醒目、本币(视角)次之、汇率(说明)最弱。颜色(青 vs 灰)+ 字号(14 vs 12 vs 11)+ 位置(行内 vs 行下 vs 页脚)三重区分,任何语言、任何币种下用户都不会混淆"我花了什么"和"它值多少"。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐