全球会议排期工具 — UI 设计篇_鸿蒙国际化


一、设计目标
会议排期工具的 UI 目标:用户 3 秒内回答"这个会对我几点的"。设计围绕三个原则:
- 时间即主角:所有时间文本加粗、紫色高亮(#7C3AED),UTC 对照用小灰字;
- 城市即维度:每个时间都带城市旗标/名称,绝不出现"裸时间";
- 实时即信任:世界时钟秒级跳动,让用户相信"这是此刻"。
二、页面结构(组件树)
Scroll(整页滚动)
└─ Column(宽 100%)
├─ Row:标题「全球会议排期」+ 🗺️
├─ Flex:语言切换(5 语言,紫色选中)
├─ Text:会议列表(小标题)
├─ ForEach → 会议卡(Column)
│ ├─ Row:主题(加粗)| 时长徽标(90m 紫底)
│ ├─ Text:发起人
│ ├─ Text:UTC 时刻(灰)
│ └─ Text:本地时刻(紫、加粗)
├─ Text:世界时钟(小标题)
├─ Column 卡:6 城市当前时间(等宽字体 monospace 对齐秒位)
├─ Text:日期风格对比(小标题)
├─ Column 卡:full/long/medium/short 四行
└─ Text:时区换算器(小标题)
└─ Column 卡(浅紫底 #F5F3FF)
├─ Text:UTC 输入时刻
├─ Flex:城市选择
└─ Text:→ 换算结果(大号紫色)
三、色彩与字号规范
主题色 #7C3AED(紫,象征"时间/宇宙"),区分于 01 的蓝(新闻)与 13 的青(钱):
| 元素 | 色值 | 字号 | 说明 |
|---|---|---|---|
| 页面标题 | #1F2937 | 22 Bold | |
| 会议主题 | #1F2937 | 15 Bold | |
| 本地时刻(重点) | #7C3AED | 14 Bold | 用户最关心的 |
| UTC 对照 | #9CA3AF | 12 | 辅助信息 |
| 时钟时间 | #7C3AED | 17 Bold + monospace | 秒位对齐 |
| 换算结果 | #7C3AED | 18 Bold | |
| 发起人/时长 | #6B7280 | 12 |
fontFamily('monospace') 让不同城市的时分秒逐位对齐,用户扫读快且专业。
四、语言切换栏的设计
语言切换放在标题正下方、内容之上,是页面的"第一操作"。与 01 的下拉面板不同,这里用5 个平铺胶囊按钮——因为会议排期场景语言数量少(简中/繁中/英/日/韩),平铺比折叠更直接:
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Start }) {
ForEach(['zh_CN', 'zh_TW', 'en_US', 'ja_JP', 'ko_KR'] as string[], (code: string) => {
Text(code === 'zh_CN' ? '🇨🇳 简中' : code === 'zh_TW' ? '🇭🇰 繁中' :
code === 'en_US' ? '🇺🇸 EN' : code === 'ja_JP' ? '🇯🇵 日' : '🇰🇷 韩')
.fontSize(12).padding({ left: 10, right: 10, top: 6, bottom: 6 }).borderRadius(16)
.backgroundColor(this.currentLocale === code ? '#7C3AED' : '#E5E7EB')
.fontColor(this.currentLocale === code ? Color.White : '#374151')
.onClick(() => { this.currentLocale = code; })
}, (code: string) => code)
}
.width('94%').margin({ top: 10 })
设计要点:
- 选中态"紫底白字":与页面主题色一致,一眼看出当前语言;
- 标签带国旗 emoji:
🇨🇳 简中让语言辨识成本降到零——用户不读文字也能找到自己的语言; - 5 个按钮一行放不下自动换行:
FlexWrap.Wrap保证小屏也不溢出。
五、会议卡片:UTC 与本地双行并列
会议卡片是本页 UI 的信息层级示范——同一时刻的两个投影(UTC 事实 + 本地体验)上下排列,但视觉权重刻意区分:
ForEach(MEETINGS, (m: Meeting) => {
Column() {
Row() {
Text(m.topic).fontSize(15).fontWeight(FontWeight.Bold).fontColor('#1F2937').layoutWeight(1)
Text(`${m.durationMin}m`).fontSize(11).fontColor('#7C3AED')
.padding({ left: 8, right: 8, top: 3, bottom: 3 }).backgroundColor('#F3E8FF').borderRadius(8)
}.width('100%')
Row() {
Text(`${this.T(K.host)}: ${m.host}`).fontSize(12).fontColor('#6B7280').layoutWeight(1)
}.width('100%').margin({ top: 6 })
Text(`${this.T(K.utcLabel)}: ${this.meetingUtc(m)}`)
.fontSize(12).fontColor('#9CA3AF').width('100%').margin({ top: 4 })
Text(`${this.T(K.local)}: ${this.meetingLocal(m)}`)
.fontSize(14).fontWeight(FontWeight.Bold).fontColor('#7C3AED').width('100%').margin({ top: 2 })
}
.width('94%').padding(14).backgroundColor(Color.White).borderRadius(14).margin({ top: 8 })
}, (m: Meeting) => m.id)
层级设计:
- 主题 + 时长徽标(第一眼):主题加粗 15 号,时长
90m用紫底圆角徽标——开会的人先看"什么会、多久"; - 发起人(第二眼):小灰字 12 号,回答"谁定的";
- UTC 对照(第三眼,最弱):
#9CA3AF最浅灰,是事实层但非用户主要关注点; - 本地时刻(第四眼,最强):14 号加粗紫色——这是用户真正要的答案,放在最后是"答案在底"的阅读模式:扫完标题、发起人、UTC,最后落到自己的时间。
六、世界时钟:等宽字体逐位对齐
6 个城市的当前时间同屏对比,是"秒位对齐"的典型场景:
Column({ space: 6 }) {
ForEach(CITIES, (c: City) => {
Row() {
Text(`${c.flag} ${c.name}`).fontSize(14).fontColor('#374151').layoutWeight(1)
Text(clockTz(this.currentLocale, c.tz, this.now))
.fontSize(16).fontWeight(FontWeight.Bold).fontColor('#7C3AED')
.fontFamily('monospace')
}.width('100%').padding({ left: 4, right: 4 })
}, (c: City) => c.key)
}
.width('94%').padding(14).backgroundColor(Color.White).borderRadius(14).margin({ top: 8 })
fontFamily('monospace') 的价值:普通字体下 08:07:03 与 23:59:58 的数字宽度不同,各行秒针位会左右漂移;等宽字体保证每一位数字占位相同,6 行时间秒位垂直对齐成一条线,扫读速度快一倍。
七、日期风格对比区
四种 dateStyle 竖排成表,前缀小灰字标 FULL/LONG/MEDIUM/SHORT:
ForEach(['full', 'long', 'medium', 'short'] as string[], (s: string) => {
Row() {
Text(s.toUpperCase()).fontSize(12).fontColor('#6B7280')
.width('62').textAlign(TextAlign.Start)
Text(fmtStyle(this.currentLocale, s)).fontSize(13).fontColor('#1F2937')
}.width('100%')
}, (s: string) => s)
这个区域不是功能,而是教学对比——让用户(和开发者)直观看到同一日期在 8/19/26(英文 short)与 26-8-19(中文 short)之间的写法差异。固定 width('62') 让左侧标签对齐,右侧结果长度不同也互不干扰。
八、时区换算器:视觉闭环
换算器用浅紫底 #F5F3FF 与卡片区分,形成"工具区"的视觉锚点:
Column() {
Text(`${this.T(K.utcLabel)}: ${fmtInTz(this.currentLocale, 'UTC', Date.UTC(2026, 7, 19, 7, 0))}`)
.fontSize(13).fontColor('#6B7280').width('100%')
Text(`${this.T(K.pickTz)}:`).fontSize(13).fontColor('#6B7280').width('100%').margin({ top: 8 })
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Start }) {
ForEach(CITIES, (c: City, idx: number) => {
Text(`${c.flag} ${c.name}`)
.fontSize(12).padding({ left: 10, right: 10, top: 6, bottom: 6 }).borderRadius(14)
.backgroundColor(this.convertIdx === idx ? '#7C3AED' : '#E5E7EB')
.fontColor(this.convertIdx === idx ? Color.White : '#374151')
.onClick(() => { this.convertIdx = idx; })
}, (c: City) => c.key)
}.width('100%').margin({ top: 6 })
Text(`→ ${fmtInTz(this.currentLocale, CITIES[this.convertIdx].tz, Date.UTC(2026, 7, 19, 7, 0))}`)
.fontSize(18).fontWeight(FontWeight.Bold).fontColor('#7C3AED').width('100%').margin({ top: 10 })
}
.width('94%').padding(14).backgroundColor('#F5F3FF').borderRadius(14).margin({ top: 8, bottom: 16 })
设计闭环:选中的城市按钮是紫底白字,换算结果也是紫色加粗——“选谁,结果就是谁"的视觉闭环,用户不需要读文字也能理解操作与结果的对应关系。结果字号 18 是全页最大的时间,突出"答案”。
九、多语言排版验证清单
| 检查项 | 方法 |
|---|---|
| 12/24 小时制差异 | 统一 hour12: false,避免 AM/PM 造成对比错位 |
| 长城市名不溢出 | 城市名 layoutWeight(1) 自适应,时间固定右侧 |
| 星期名长度差异 | 德语星期名(Mittwoch)很长,dateStyle 输出由 intl 保证,UI 不拼 |
| 秒位对齐 | monospace 字体 + 固定格式,任何语言下时分秒逐位对齐 |
| 时间文本方向 | 时区名/数字为中性内容,LTR 语言下自然显示 |
| 语言切换全重算 | currentLocale 变化 → 会议/时钟/风格/换算全部刷新 |
十、设计取舍
- 为什么紫色:系列内每应用一个主题色(01 蓝/13 青/02 紫),既统一卡片风格又便于区分;
- 为什么 UTC 对照不省略:UTC 是"事实层",本地是"体验层",双行展示让用户既能信任又能看懂——这也是真实会议工具的常见做法(Google Calendar 的时区双行);
- 为什么换算器用固定时刻:真实产品应输入任意时刻,但固定演示时刻(春节/独立日等预设日期在应用 10 复用)降低了操作成本,聚焦"换算"本身;
- 为什么语言切换用平铺而非下拉:语言只有 5 种,平铺胶囊按钮点击一步到位,比展开面板更快;语言很多时才需要下拉/面板(见应用 01、08)。
十一、小结
时间类 UI 的成败在对比:多个城市的时刻必须逐位对齐(monospace)、同一事实的 UTC 与本地必须双行并列、切换语言必须即时重算。做到这三点,跨时区用户就能像看自己手机时钟一样信任这个工具。
本文的 4 个可复用模式(平铺语言胶囊、UTC/本地双行层级、等宽字体对齐、选中态-结果同色闭环)适用于一切"多时区对比"型界面。下一篇技术篇将深入 intl.DateTimeFormat 的 timeZone 选项、夏令时自动处理与秒级刷新的生命周期管理。
更多推荐




所有评论(0)