HarmonyOS运动健康——个人中心页面
个人中心是运动健康App的"我的"页面。用户从首页底部导航"我的"跳转到这里,查看个人资料、运动统计、快捷入口。这个页面的任务是:让用户看到"我运动了多少"和"我能去哪里"——上方是个人身份和数据,下方是功能导航。
页面结构分为三部分:顶部导航栏、用户信息卡片、统计数据行、功能菜单列表。从上到下,信息从"我是谁"到"我做了什么"再到"我能做什么"。
完整效果

导航栏
Row() {
Row() {
SymbolGlyph($r('sys.symbol.chevron_left')).fontSize(20).fontColor([T1])
}.width(34).height(34).borderRadius(17).backgroundColor('rgba(0,0,0,0.03)')
.justifyContent(FlexAlign.Center).onClick(() => { router.back() })
Text('我的').fontSize(20).fontWeight(FontWeight.Bold).fontColor(T1)
.margin({ left: 10 }).layoutWeight(1)
Text('⚙️').fontSize(20)
}.width('100%').padding({ left: 16, right: 16, top: 12, bottom: 8 })

导航栏和其他页面保持一致——左边返回按钮,中间标题。但右边多了一个⚙️设置图标。和社区页面的"发布"按钮、用户主页的"关注"按钮一样,导航栏右侧是全局操作区——每个页面可以放不同的按钮。
⚙️设置图标目前没有绑定onClick——点击不会跳转到设置页面。菜单列表里有一个"设置"入口,和导航栏的⚙️功能重复。实际项目中应该二选一:要么导航栏放设置入口,要么菜单列表放,不需要两个地方都有。
用户信息卡片
Row() {
Stack() {
Column() {}.width(64).height(64).borderRadius(32)
.linearGradient({ angle: 135, colors: [[A, 0], ['#FF9F43', 1]] })
Text('🏃').fontSize(28)
}
Column() {
Text('运动达人').fontSize(20).fontWeight(FontWeight.Bold).fontColor(T1)
Text('连续运动 15 天 · 超越 89% 的用户').fontSize(12).fontColor(T3).margin({ top: 2 })
}.alignItems(HorizontalAlign.Start).margin({ left: 14 }).layoutWeight(1)
Text('编辑 ›').fontSize(12).fontColor(A)
}.width('100%').padding(20).backgroundColor('#FFFFFF').borderRadius(18)
.margin({ left: 16, right: 16, bottom: 16 })

用户卡片是一个白色圆角Row,左边头像、中间名字和标签、右边编辑按钮。
头像的Stack实现
Stack() {
Column() {}.width(64).height(64).borderRadius(32)
.linearGradient({ angle: 135, colors: [[A, 0], ['#FF9F43', 1]] })
Text('🏃').fontSize(28)
}
头像用Stack实现——底层是一个64×64的圆形,填充红到橙的渐变色;上层是emoji文字🏃。Stack的默认对齐是居中,所以emoji自动居中在渐变圆上。
这种"渐变背景+emoji前景"的头像设计和首页右上角的头像一致——整个App用同一套头像风格。64×64是个人中心的尺寸,首页是42×42,用户主页是100×100。同一个元素在不同页面有不同尺寸,符合信息层级。
编辑按钮
Text('编辑 ›').fontSize(12).fontColor(A)
红色文字+右箭头,暗示"可点击"。但没有绑定onClick——点击不会跳转到编辑页面。这是原型阶段的典型做法:UI元素存在,但交互留到后续实现。
统计数据行
Row({ space: 8 }) {
this.Stat('累计运动', '156', '天', A)
this.Stat('累计消耗', '48,920', '千卡', '#FF9F43')
this.Stat('完成训练', '342', '次', '#5DADE2')
}.width('100%').padding({ left: 16, right: 16 }).margin({ bottom: 16 })

三个统计卡片并排,用space: 8控制间距。每个卡片展示一个核心指标。
Stat Builder
@Builder Stat(label: string, value: string, unit: string, color: string) {
Column() {
Text(value).fontSize(24).fontWeight(FontWeight.Bold).fontColor(color)
Text(unit).fontSize(10).fontColor(T3)
Text(label).fontSize(11).fontColor(T2).margin({ top: 2 })
}.layoutWeight(1).padding(14).backgroundColor('#FFFFFF').borderRadius(14)
}
每个统计卡片由三行文字组成:数值(24px加粗彩色)、单位(10px灰色)、标签(11px灰色)。数值最醒目,单位和标签视觉层级较低。
和ProgressPage的SCard Builder对比:
| 属性 | ProfilePage Stat | ProgressPage SCard |
|---|---|---|
| 数值字号 | 24px | 28px |
| 单位位置 | 第二行 | 第二行 |
| 标签位置 | 第三行 | 第三行 |
| padding | 14px | 16px |
| 圆角 | 14px | 16px |
个人中心的Stat比进度页的SCard略小——因为个人中心是"概览",进度页是"详情"。同一个数据在不同页面有不同的展示尺寸,符合信息层级。
三个统计指标
| 指标 | 数值 | 单位 | 颜色 | 含义 |
|---|---|---|---|---|
| 累计运动 | 156 | 天 | A(红色) | 总运动天数 |
| 累计消耗 | 48,920 | 千卡 | #FF9F43(橙色) | 总卡路里消耗 |
| 完成训练 | 342 | 次 | #5DADE2(蓝色) | 总训练次数 |
颜色和首页、进度页保持一致:红色=运动/步数,橙色=卡路里,蓝色=训练。用户看到红色就联想到"运动",不管在哪个页面。
48920千卡的千位分隔符是硬编码的——直接在字符串里写了逗号。和ProgressPage的76532步一样,没有动态格式化。如果数字变化,需要手动改字符串。
功能菜单列表
Column({ space: 0 }) {
this.Menu('运动记录', '📊', () => { router.pushUrl({ url: 'pages/ProgressPage' }) })
this.Div()
this.Menu('饮食记录', '🍽️', () => { router.pushUrl({ url: 'pages/MealPage' }) })
this.Div()
this.Menu('我的成就', '🏆', () => { })
this.Div()
this.Menu('身体数据', '📏', () => { })
this.Div()
this.Menu('目标设置', '🎯', () => { })
this.Div()
this.Menu('设置', '⚙️', () => { })
}.width('100%').backgroundColor('#FFFFFF').borderRadius(18)
.margin({ left: 16, right: 16 })
六个菜单项,每项之间用Divider分隔。整个菜单区域是一个白色圆角卡片。
Menu Builder
@Builder Menu(label: string, icon: string, action: () => void) {
Row() {
Text(icon).fontSize(18).margin({ right: 14 })
Text(label).fontSize(14).fontColor(T1).layoutWeight(1)
SymbolGlyph($r('sys.symbol.chevron_right')).fontSize(12).fontColor(['#DDD'])
}.width('100%').padding({ left: 18, right: 18, top: 14, bottom: 14 })
.onClick(action)
}
每个菜单项是一个Row:左边emoji图标、中间文字、右边右箭头(SymbolGlyph)。layoutWeight(1)让文字区域撑满中间空间,右箭头被推到最右边。
右箭头用SymbolGlyph($r('sys.symbol.chevron_right'))——和返回按钮的sys.symbol.chevron_left是同一套图标。左箭头=返回,右箭头=前进,这是通用的导航语义。
菜单项的功能映射
| 菜单项 | 图标 | 跳转目标 | 状态 |
|---|---|---|---|
| 运动记录 | 📊 | ProgressPage | 已实现 |
| 饮食记录 | 🍽️ | MealPage | 已实现 |
| 我的成就 | 🏆 | 无 | 未实现 |
| 身体数据 | 📏 | 无 | 未实现 |
| 目标设置 | 🎯 | 无 | 未实现 |
| 设置 | ⚙️ | 无 | 未实现 |
六个菜单中只有两个有实际跳转——运动记录去ProgressPage,饮食记录去MealPage。其余四个的action是空函数() => { },点击没有反应。这是原型阶段的正常状态:先搭好UI框架,再逐步填充功能。
Div Builder
@Builder Div() {
Divider().strokeWidth(0.5).color('#F0EEF4').margin({ left: 52 })
}
分割线用Divider组件,0.5px宽,浅灰色。margin({ left: 52 })让分割线从图标右侧开始——不延伸到左边的emoji图标下方。52px的计算:18px(padding-left)+ 18px(icon fontSize)+ 14px(margin-right)+ 约2px余量。
这种"缩进分割线"是列表设计的常见手法——分割线只在文字区域下方,不在图标下方。视觉上更干净,图标区域保持完整。
页面整体结构
Column() {
// 导航栏(固定)
Row() { ... }
// 内容区(可滚动)
Scroll() {
Column() {
// 用户卡片
// 统计数据
// 功能菜单
}
}.width('100%').layoutWeight(1)
}.width('100%').height('100%')
和其他页面一样的结构:导航栏在Scroll外面(固定),内容在Scroll里面(可滚动)。layoutWeight(1)让Scroll占据导航栏以外的所有空间。
和社区页面对比:社区页面的Scroll里是ForEach渲染的帖子列表,个人中心的Scroll里是固定结构(卡片+统计+菜单)。前者是动态内容,后者是静态结构。静态结构不需要ForEach,直接写死就行。
Builder的复用
个人中心用了三个Builder:Stat、Menu、Div。它们的职责清晰:
- Stat:统计数据卡片(数值+单位+标签)
- Menu:菜单项(图标+文字+箭头+点击事件)
- Div:分割线(0.5px+缩进)
这三个Builder只在ProfilePage内部使用——没有导出到其他页面。但Stat的结构和ProgressPage的SCard几乎一样(数值+单位+标签),Menu的结构和社区页面的关注按钮有相似之处(文字+背景色+圆角)。
如果要跨页面复用,应该把这些Builder抽到components目录下。但原型阶段每个页面自己定义Builder更简单——不需要考虑接口设计、参数兼容性等问题。过早抽象反而会增加理解成本。
和其他页面的导航关系
个人中心从首页底部导航的"我的"按钮跳转过来。底部导航有5个按钮:首页、训练、进度、社区、我的。"我的"是第5个。
个人中心内部的菜单又跳转到其他页面:运动记录→ProgressPage,饮食记录→MealPage。这意味着用户可以从"我的"→"运动记录"→ProgressPage,绕了一圈到达进度页。和直接从底部导航点"进度"效果一样,但路径更长。
这种"多入口到达同一页面"的设计是正常的——用户可以从不同路径到达ProgressPage,每个路径的上下文不同。从首页点"运动记录"是"我想看今天的运动数据",从"我的"点"运动记录"是"我想看我的运动历史"。同一个页面,不同的用户意图。
踩坑记录
导航栏右侧的⚙️没有功能。 导航栏右边放了一个⚙️图标,菜单列表底部也有一个"设置"入口。两处功能重复,且都没有实现。实际项目应该只保留一个——要么导航栏放图标,要么菜单列表放文字入口。
统计数字的千位分隔符硬编码。 48,920的逗号是字符串里直接写的,不是动态格式化的。如果数字变化(比如从48920变成100000),需要手动改代码。和ProgressPage的76,532步同样的问题。
Div的margin-left计算。 分割线的margin({ left: 52 })是手动计算的——如果Menu的padding或图标尺寸变化,52px可能不再对齐。更稳健的做法是用Row包裹分割线,让分割线从图标右侧开始,而不是用固定margin。
空菜单项的用户体验。 四个菜单项(我的成就、身体数据、目标设置、设置)点击没有反应。用户点击后没有任何反馈——既没有跳转,也没有提示"功能开发中"。真实项目应该加一个toast提示或禁用状态,避免用户困惑。
头像Stack的层级。 Stack默认居中对齐,emoji自动居中在渐变圆上。但如果emoji尺寸变化(比如换成图片),可能需要调整对齐方式。Stack的默认行为是"后添加的元素在上层",这里先放渐变圆再放emoji,顺序正确。
个人中心是运动健康App中结构最简单的页面——没有复杂布局、没有动态数据、没有网络请求。它的价值在于"信息聚合":把分散在各处的数据(运动天数、卡路里消耗、训练次数)汇总到一个页面,把分散的功能入口(运动记录、饮食记录、设置)排列成一个菜单。从技术角度看,这个页面是Builder模式的最佳练习——Stat、Menu、Div三个Builder各司其职,build方法干净整洁。如果说社区页面是状态管理的综合考试,个人中心就是组件拆分的基础练习。
更多推荐


所有评论(0)