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

一、设计目标

几乎所有面向全球的 App 都有这个注册环节:“选择你的国家/地区和语言”。它看似简单,却是本地化体验的第一道门——选错了,后面所有体验都是错的。本应用把"创建账户"的第一步做成三步向导,UI 设计要回答三个问题:

  1. 三步选择如何零困惑:国家/地区 → 语言 → 货币,每一步的选项、默认值、联动关系都要让用户一眼看懂;
  2. "建议值"如何表达:选了日本,语言自动建议日语、货币自动建议日元——但用户有权改。UI 既要体现"智能联动",又不能绑架用户选择;
  3. 选择结果如何即时验证:底部本地化预览卡实时展示"所选 locale 下的日期格式与数字格式"——用户不用等注册完成,当场看到选择的效果。

因此本页 UI 的核心原则是:选择即预览,联动可覆盖——每个选择都立刻产生可见结果(日期/数字预览),所有自动建议都可被用户手动改掉。

二、页面信息架构(组件树)

LocaleRegionPicker(@Entry 根组件)
└─ Scroll(整页滚动,背景 #F8FAFC)
   └─ Column(宽 100%)
      ├─ Row:标题栏("创建账户" + 副标题 + 🌍 图标)
      ├─ Flex:界面语言徽章组(简中/繁中/EN/日/韩,选中绿色高亮)
      ├─ Text + Flex:第 1 步 国家/地区(8 个徽章:🇨🇳 中国 / 🇺🇸 美国 / 🇯🇵 日本 …)
      ├─ Text + Flex:第 2 步 语言(8 个徽章:简体中文 / 繁體中文 / English …)
      ├─ Text + Flex:第 3 步 货币(8 个徽章:🇨🇳 CNY / 🇺🇸 USD / 🇯🇵 JPY …)
      ├─ Text + Column:本地化预览卡(浅绿底 #F0FDF4)
      │    ├─ 完整 locale:zh-Hans-CN · CNY
      │    ├─ 日期格式:2026年8月19日星期三
      │    └─ 数字格式:1,234,567.89
      └─ Button:完成注册(全宽绿色)

架构上严格遵循**“自上而下的流程感”**:界面语言开关在最上(控制整个页面的语言)、三个步骤依次向下、预览卡承接结果、完成按钮收尾——用户心智模型与页面结构完全一致。

三、色彩与字号规范

本页主题色为绿色系(#15803D)——注册/成功/确认的语义,与系列其他应用的蓝/橙/紫/靛蓝区分:

元素 色值 字号 字重
页面标题 #1F2937 22 Bold
副标题 #9CA3AF 12 Normal
步骤标题(1./2./3.) #1F2937 14 Bold
选中徽章 文字 #FFF / 底 #15803D 12 Normal
未选中徽章 文字 #374151 / 底 #E5E7EB 12 Normal
预览卡 locale 行 #15803D 12 Normal
预览标签(日期/数字) #6B7280 13 Normal
预览值 #1F2937 13 Normal
完成按钮 文字 #FFF / 底 #15803D 15 Normal
预览卡背景 #F0FDF4

色彩规则延续系列:绿 = 选中/强调。绿色同时用于界面语言徽章、三步徽章、预览卡 locale 行、完成按钮——“当前生效/可确认"的语义贯穿全页,用户看到绿色即知"这是我当前的选择”。

四、第 1 步:国家/地区——名称本地化的展示

Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Start }) {
  ForEach(REGIONS, (r: Region, idx: number) => {
    Text(`${r.flag} ${this.regionName(r.code)}`)
      .fontSize(12).padding({ left: 10, right: 10, top: 6, bottom: 6 }).borderRadius(16)
      .backgroundColor(this.regionIdx === idx ? '#15803D' : '#E5E7EB')
      .fontColor(this.regionIdx === idx ? Color.White : '#374151')
      .onClick(() => { this.selectRegion(idx); })
  }, (r: Region) => r.code)
}
.width('94%').margin({ top: 6 })
private regionName(code: string): string {
  try {
    return i18n.getDisplayCountry(code, this.currentLocale, true);
  } catch (err) {
    return code;
  }
}

设计要点:

  • 地区名走 i18n.getDisplayCountry():界面语言是中文时显示"中国/美国/日本/法国/德国/英国/印度",切英文界面自动变 “China / United States / Japan / France / Germany / United Kingdom / India”——国家名不是翻译的,是本地化的(与 11 应用的国家名同一机制);
  • flag emoji 辅助识别:🇨🇳🇺🇸🇯🇵🇰🇷🇫🇷🇩🇪🇬🇧🇮🇳 快速定位,不依赖文字;
  • FlexWrap 自动换行:8 个地区徽章在窄屏折行,360dp 下两到三排;
  • 点击即联动selectRegion(idx) 不只是高亮地区,还会自动设置建议语言与货币(见技术篇)——一次点击,三步联动

五、第 2 步:语言——建议值与自由选择

Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Start }) {
  ForEach(LANGS, (l: string, idx: number) => {
    Text(this.langName(l))
      .fontSize(12).padding({ left: 10, right: 10, top: 6, bottom: 6 }).borderRadius(16)
      .backgroundColor(this.langIdx === idx ? '#15803D' : '#E5E7EB')
      .fontColor(this.langIdx === idx ? Color.White : '#374151')
      .onClick(() => { this.langIdx = idx; })
  }, (l: string) => l)
}
.width('94%').margin({ top: 6 })
private langName(code: string): string {
  try {
    return i18n.getDisplayLanguage(code, this.currentLocale, true);
  } catch (err) {
    return code;
  }
}

设计要点:

  • 语言名本地化getDisplayLanguage('zh-Hans', 'en_US') → “Simplified Chinese”、('ja', 'zh_CN') → “日语”——8 种语言名随界面语言变化;
  • 联动默认值:选了日本地区后,语言徽章自动选中"日语"(selectRegion 里的 findIndex 逻辑)——但用户仍可自由改选,联动只是建议不是锁定;
  • 8 种语言覆盖主要市场:zh-Hans/zh-Hant/en/ja/ko/fr/de/hi(简中/繁中/英/日/韩/法/德/印地语);
  • 界面语言 ≠ 账户语言:界面语言徽章(顶部 5 种)只控制"这个页面用什么语言显示",第 2 步的语言是"账户默认语言"——两个语言维度并存,是本页最容易被新手混淆的点,视觉上用位置区分(顶部一行 vs 步骤区)。

六、第 3 步:货币——按地区分组展示

Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Start }) {
  ForEach(REGIONS, (r: Region, idx: number) => {
    Text(`${r.flag} ${r.currency}`)
      .fontSize(12).padding({ left: 10, right: 10, top: 6, bottom: 6 }).borderRadius(16)
      .backgroundColor(this.currencyIdx === idx ? '#15803D' : '#E5E7EB')
      .fontColor(this.currencyIdx === idx ? Color.White : '#374151')
      .onClick(() => { this.currencyIdx = idx; })
  }, (r: Region, idx: number) => `${r.code}-${idx}`)
}
.width('94%').margin({ top: 6 })

设计要点:

  • 货币用"地区 × 币种"双信息徽章🇨🇳 CNY / 🇺🇸 USD / 🇯🇵 JPY / 🇰🇷 KRW / 🇫🇷 EUR / 🇩🇪 EUR / 🇬🇧 GBP / 🇮🇳 INR——注意法国与德国共用 EUR,同一币种出现两次,这正是货币与地区多对多关系的体现;
  • ForEach key 用 `${r.code}-${idx}`:地区码本身唯一(CN/US…),但货币列表也遍历 REGIONS,key 需要区分场景——加 idx 双保险;
  • 货币联动:选地区后自动选中该地区币种(findIndex 按 currency 匹配,法国/德国都命中 EUR)。

七、本地化预览卡:选择即所见

Column({ space: 8 }) {
  Text(`${this.fullLocale()} · ${this.curRegion().currency}`)
    .fontSize(12).fontColor('#15803D').width('100%')
  Row() {
    Text(`${this.T(K.dateFmt)}:`).fontSize(13).fontColor('#6B7280').layoutWeight(1)
    Text(this.fmtDate()).fontSize(13).fontColor('#1F2937')
  }.width('100%')
  Row() {
    Text(`${this.T(K.numFmt)}:`).fontSize(13).fontColor('#6B7280').layoutWeight(1)
    Text(this.fmtNum()).fontSize(13).fontColor('#1F2937')
  }.width('100%')
}
.width('94%').padding(14).backgroundColor('#F0FDF4').borderRadius(14).margin({ top: 8 })

设计要点:

  • 完整 locale 展示zh-Hans-CN · CNY——把"语言_地区_币种"的组合直接显示给用户,这是本页的数据核心;
  • 日期格式预览intl.DateTimeFormat(fullLocale, { dateStyle: 'full' }) 输出所选组合下的完整日期——中文"2026年8月19日星期三"、日语"2026年8月19日水曜日"、法语 “mercredi 19 août 2026”;
  • 数字格式预览1234567.89 在英文下 “1,234,567.89”、德语 “1.234.567,89”、印地语 “१२,३४,५६७.८९”(印度数字分组)——同样的数字,完全不同的写法
  • 预览卡浅绿底 #F0FDF4:与强调色同族,形成"结果区"的视觉容器感——用户每次改选择,这里立即刷新,即时反馈是表单设计的黄金法则。

八、完成按钮与注册反馈

Button(this.T(K.done))
  .fontSize(15).width('94%').height(44).margin({ top: 14, bottom: 16 })
  .backgroundColor('#15803D')
  .onClick(() => { this.onDone(); })
private onDone(): void {
  const r = this.curRegion();
  promptAction.showToast({
    message: `${this.T(K.complete)}: ${this.regionName(r.code)} · ${this.langName(this.curLang())} · ${r.currency}`
  });
}
  • 全宽 44dp 按钮符合移动端触控规范,绿色延续"确认"语义;
  • 点击后 Toast 汇总三步选择(地区名 + 语言名 + 币种),全部走本地化函数——用户看到的是"注册配置: 日本 · 日本語 · JPY"这样的完整确认

九、排版验证清单(多语言下自查)

检查项 方法 验证结果
地区名随界面语言 getDisplayCountry(code, currentLocale, true) 中文"美国"/英文"United States"
语言名随界面语言 getDisplayLanguage(code, currentLocale, true) 中文"日语"/日文"日本語"
地区→语言联动 selectRegion()findIndex 选日本自动选中日语
地区→货币联动 按 currency 匹配 法/德都自动选 EUR
日期随完整 locale fullLocale() 拼合 zh-Hans-CN 输出中文完整日期
数字随完整 locale NumberFormat 德语 1.234.567,89 / 印地语印度分组
货币重复不冲突 ForEach key `${code}-${idx}` EUR 徽章两个都正常渲染
窄屏不溢出 徽章 FlexWrap 8 地区徽章自动折行

十、可访问性(多语言 UI 加分项)

  • 选中态三重编码:绿底 + 白字 + 徽章文字本身(国旗/语言名/币种码),色弱用户可辨识;
  • 对比度:绿 #15803D on 白 ≈ 5.2:1、正文 #1F2937 ≈ 15:1、预览标签 #6B7280 ≈ 7:1,满足 WCAG AA;
  • 步骤编号显式:步骤标题带"1. 国家/地区 / 2. 语言 / 3. 货币"——屏幕朗读器按编号读,流程感清晰;
  • 预览值可朗读:日期/数字格式化结果本身就是完整字符串,朗读器读出 “2026年8月19日星期三” 而非原始时间戳;
  • 联动变化可感知:选地区后语言/货币徽章自动高亮,视觉焦点变化明确,用户知道"系统帮我选了什么"。

十一、小结

本页 UI 把"注册第一步"做成了 i18n 的浓缩课堂:getDisplayCountry/getDisplayLanguage 名称本地化、地区→语言→货币三步联动、完整 locale 的日期数字预览——一个表单页面覆盖了"选什么、默认什么、结果是什么"的完整闭环。设计上最值得记的模式是**“联动可覆盖 + 选择即预览”**:系统给建议,用户有自由,结果立刻可见。这套三步向导 + 预览卡的结构,可直接迁移到任何"初始配置"流程(注册、设置向导、多语言初始化)。下一篇文章深入技术实现,讲解 fullLocale() 的拼合规则与 selectRegion() 的联动算法。

Logo

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

更多推荐