一、设计目标
"现在几点了"是一个比想象中复杂的问题——同一时刻,北京是下午、伦敦是上午、纽约是凌晨。本应用以「世界时钟」为载体,把时区、国家名称、航班时刻三个本地化维度整合到一个页面,UI 设计要回答三个问题:
- 5 个城市的实时时钟如何一眼看懂:城市名、flag、当前时间、UTC 偏移要在同一行内清晰呈现,且每秒刷新不闪烁;
- 时差对比如何设计成"零学习成本"的交互:用户选两个城市就能看到 “+13h”/“-5h”,不需要理解 UTC 概念;
- 航班时刻的"本地时间"如何表达:同一航班,起飞显示出发地当地时间、到达显示目的地当地时间——两边时区不同,UI 要避免用户误解成"飞行 20 小时"。
因此本页 UI 的核心原则是:时间是相对的,界面是绝对的——每个时间都带时区上下文,绝不让用户自己心算时差。
二、页面信息架构(组件树)
RegionTimeZonePage(@Entry 根组件) └─ Scroll(整页滚动,背景 #F8FAFC) └─ Column(宽 100%) ├─ Row:标题栏("世界时钟" + 副标题 + ✈️ 图标) ├─ Flex:界面语言徽章组(简中/繁中/EN/日/韩,选中深蓝高亮) ├─ Text + Column:世界时钟卡(5 城市实时时间,白色卡片) │ └─ 每行 Row:🇨🇳 北京(左) + HH:MM:SS(右,monospace 深蓝) │ └─ 副行:当前: +8 UTC ├─ Text + Column:时差对比卡(浅蓝底 #F0F9FF) │ ├─ 说明文字:选择两个城市对比时差 │ ├─ 两排国旗徽章:A 排 [🇨🇳🇯🇵🇬🇧🇺🇸🇦🇪] VS B 排 [同 5 个] │ └─ 结果行:北京 vs 纽约: +13h ├─ Text + ForEach:航班时刻卡(4 个航班) │ └─ 每卡:航班号 MU583(蓝) + 🇨🇳 北京 → 🇺🇸 纽约 │ ├─ 起飞(出发地本地): 2026年8月20日 19:30 │ ├─ 到达(目的地本地): 2026年8月20日 21:00(加粗蓝) │ └─ 飞行时长: 14h 30m · 国家: 美国 └─ 底部留白信息架构上三个区块对应三类用户诉求:实时查看(时钟)、主动比较(时差)、行程规划(航班)。一屏覆盖差旅场景的完整信息链。
三、色彩与字号规范
本页主题色为深蓝色系——时钟/航班/出行的冷静、专业气质,与 09 橙(烹饪)、10 紫(文化)明确区分:
| 元素 | 色值 | 字号 | 字重 |
|---|---|---|---|
| 页面标题 | #1F2937 | 22 | Bold |
| 副标题 | #9CA3AF | 12 | Normal |
| 区块标题 | #1F2937 | 14 | Bold |
| 城市名 | #1F2937 | 14 | Normal |
| 时钟数字 | #0369A1 | 17 | Bold(monospace) |
| UTC 偏移副行 | #9CA3AF | 10 | Normal |
| 时差结果 | #0369A1 | 16 | Bold |
| 航班号 | #0369A1 | 14 | Bold |
| 路线文字 | #1F2937 | 13 | Normal |
| 起飞行 | #6B7280 | 12 | Normal |
| 到达行 | #0369A1 | 14 | Bold |
| 时长/国家行 | #9CA3AF | 11 | Normal |
| 选中徽章 | 文字 #FFFFFF / 底 #0369A1 | 11~12 | Normal |
| 未选中徽章 | 文字 #374151 / 底 #E5E7EB | 11~12 | Normal |
| 时差卡片背景 | #F0F9FF | — | — |
色彩规则延续系列统一语义:深蓝 = 选中/强调、灰 = 未选中;时钟数字与到达时间用深蓝加粗(信息终点),起飞/时长用灰色(辅助信息)——层级靠颜色 + 字重双重编码。
四、世界时钟卡:每秒刷新的实时数据
Column({space:8}){ForEach(CITIES,(c:City)=>{Row(){Text(`${c.flag}${c.name}`).fontSize(14).fontColor('#1F2937').layoutWeight(1)Column({space:1}){Text(fmtTz(this.currentLocale,c.tz,this.now)).fontSize(17).fontWeight(FontWeight.Bold).fontColor('#0369A1').fontFamily('monospace')Text(`${this.T(K.now)}:${this.offsetHours(c)}UTC`).fontSize(10).fontColor('#9CA3AF')}.alignItems(HorizontalAlign.End)}.width('100%').padding({left:4,right:4})},(c:City)=>c.key)}.width('94%').padding(14).backgroundColor(Color.White).borderRadius(14).margin({top:8})设计要点:
- monospace 字体 + 右对齐:时钟数字用等宽字体,秒数跳动时宽度不抖动(比例字体
14:30:05→14:30:59会左右晃动);时间列右对齐,5 个城市的数字处于同一垂直线上,方便快速扫读; - flag + 城市名:图标辅助识别,城市名用本地名称(北京/东京/伦敦/纽约/迪拜),不随界面语言翻译(城市是专有名词);
- 副行给 UTC 偏移:
当前: +8 UTC——把"这个时钟基于哪个时区"直接告诉用户,这是时区产品的诚实设计:没有偏移说明的时间是无意义的; - 每秒刷新:
this.now = Date.now()由 1 秒定时器驱动,所有城市共用同一个now状态——一次更新、五处联动,保证 5 个时钟显示的是同一个瞬间(时间一致性是时钟 UI 的生命线)。
五、时差对比:国旗 VS 国旗
时差对比用两排国旗徽章实现"选两个城市":
Row({space:6}){ForEach(CITIES,(c:City,idx:number)=>{Text(`${c.flag}`).fontSize(14).padding({left:8,right:8,top:6,bottom:6}).borderRadius(12).backgroundColor(this.diffA===idx?'#0369A1':'#E5E7EB').onClick(()=>{this.diffA=idx;})},(c:City)=>`a-${c.key}`)Text('VS').fontSize(12).fontColor('#9CA3AF')ForEach(CITIES,(c:City,idx:number)=>{Text(`${c.flag}`).fontSize(14).padding({left:8,right:8,top:6,bottom:6}).borderRadius(12).backgroundColor(this.diffB===idx?'#0369A1':'#E5E7EB').onClick(()=>{this.diffB=idx;})},(c:City)=>`b-${c.key}`)}.width('100%').margin({top:8}).justifyContent(FlexAlign.Start)设计决策:
- 纯国旗徽章:对比选择器不需要文字——5 个国旗已经是"城市集合"的语义表达,省去下拉框的两次点击;
- A/B 两排 + VS 分隔:视觉上直接呈现"两者对比"关系,选中态深蓝底高亮;
- ForEach key 加前缀
a-/b-:两排渲染同一个 CITIES 数组,key 必须区分阵营,否则 ArkUI 复用错乱; - 结果行:
北京 vs 纽约: +13h——城市名 + 时差,字号 16 加粗深蓝,是全卡唯一的结果,信息终点突出。
六、航班时刻卡:本地时间的正确表达
航班区块展示"出发地本地 / 目的地本地"双时区时刻:
ForEach(FLIGHTS,(f:Flight)=>{Column(){Row(){Text(f.no).fontSize(14).fontWeight(FontWeight.Bold).fontColor('#0369A1')Text(`${this.cityOf(f.from).flag}${this.cityOf(f.from).name}→${this.cityOf(f.to).flag}${this.cityOf(f.to).name}`).fontSize(13).fontColor('#1F2937').layoutWeight(1).textAlign(TextAlign.End)}.width('100%')Text(`${this.T(K.depart)}:${this.flightDepart(f)}`).fontSize(12).fontColor('#6B7280').width('100%').margin({top:6})Text(`${this.T(K.arrive)}:${this.flightArrive(f)}`).fontSize(14).fontWeight(FontWeight.Bold).fontColor('#0369A1').width('100%').margin({top:2})Text(`${this.T(K.duration)}:${this.flightDur(f)}·${this.T(K.country)}:${countryName(this.currentLocale,this.cityOf(f.to).country)}`).fontSize(11).fontColor('#9CA3AF').width('100%').margin({top:4})}.width('94%').padding(12).backgroundColor(Color.White).borderRadius(12).margin({top:8})},(f:Flight)=>f.id)设计要点:
- 航班号 + 路线一行:航班号(MU583)左、路线(🇨🇳 北京 → 🇺🇸 纽约)右对齐——路线用箭头表达方向,比文字"北京到纽约"更紧凑;
- 起飞灰、到达蓝:到达是用户最关心的信息(几点到),用深蓝加粗;起飞是背景信息用灰色——信息优先级决定视觉权重;
- 时长与国家名合并一行:
飞行时长: 14h 30m · 国家: 美国——时长用 Hh Mm 格式(14h 30m)避免与时间戳混淆;国家名走i18n.getDisplayCountry()本地化(中文界面显示"美国",英文界面显示 “United States”); - 卡片圆角 12 间距 8:与时钟卡(14)略微区分,区块内聚、区块间疏离。
七、排版验证清单(多语言下自查)
| 检查项 | 方法 | 验证结果 |
|---|---|---|
| 时钟数字不抖动 | monospace + 右对齐 | 秒数变化宽度稳定 |
| 时间格式随语言 | hour12: false固定 24 小时制 | 中英日韩界面一致显示 14:30 |
| 日期格式随语言 | fmtTzFull()用dateStyle:'medium' | 中文"2026年8月20日"、英文 “Aug 20, 2026” |
| 国家名本地化 | i18n.getDisplayCountry(code, country, true) | 英文界面 “United States”、中文"美国" |
| 时差正负号 | d >= 0 ? '+' : '' | +13h / -5h 明确 |
| 航班本地时刻 | 起降分别用出发/到达城市时区 | 北京 19:30 起飞 = 纽约 21:00 到达(同日) |
| 窄屏不溢出 | 徽章组 FlexWrap + 路线 layoutWeight | 360dp 下两排国旗正常显示 |
八、可访问性(多语言 UI 加分项)
- 时差结果不用纯颜色传达:
+13h文字本身带符号语义,深蓝只是强调——色弱用户仍能读出正负; - 国旗 emoji 有文字备选:时差对比器用纯国旗,但结果行有城市名(“北京 vs 纽约”),屏幕朗读器可读出城市名;
- 对比度:正文 #1F2937 on 白 ≈ 15:1、深蓝 #0369A1 on 白 ≈ 5.4:1、浅灰 #9CA3AF on 白 ≈ 2.9:1(仅用于辅助行,达标基线放宽);
- 刷新节奏克制:时钟每秒刷新,但只更新 5 行文本,不引起整页重排(行高固定)——对视力障碍用户,闪烁内容控制在最小范围;
- 字号分层:时钟 17、结果 16、到达 14、辅助 10~12,形成清晰的信息层级,弱视用户可借字号判断优先级。
九、小结
本页 UI 的核心理念是**“把时区上下文还给时间”**:时钟行带 UTC 偏移、航班起降分别标注本地时间、时差对比用"城市 vs 城市"而非裸数字。三个区块共用同一套"深蓝 = 重点"的视觉语义,用户扫一眼就能分辨主次。这套模式可直接迁移到任何含时间/时区的业务——会议调度、物流追踪、全球客服排班。下一篇文章深入技术实现,讲解intl.DateTimeFormat的 timeZone 机制与i18n.getTimeZone()的偏移计算。