【HarmonyOS 6】个人中心数据可视化实战

一、案例背景

在健康管理类应用中,用户希望在"个人中心"快速查看周期性的健康汇总。相比单一数据,健康报告弹窗能在一个页面中集中展示平均分、每日评分、分项进度与健康建议,阅读效率更高。

本案例面向 HarmonyOS 6 初学者,聚焦一个移动端功能点:个人中心菜单入口 + 健康报告弹窗 + 分项进度展示

你将学到:

  • 如何在"更多功能"中设置报告入口
  • 如何控制弹窗显示/隐藏
  • 如何用环形进度展示综合评分
  • 如何用线性进度展示分项平均分

二、完整代码实现

本功能主要由两部分组成:

  1. 个人中心的"健康报告"入口
  2. 健康报告弹窗的内容结构与进度展示

2.1 菜单入口(点击打开报告弹窗)

typescript 复制代码
          Column() {
            CommonCard({ title: '更多功能' }) {
              Column() {
                this.MenuRow('📊 健康报告', () => {
                  this.showReportDialog = true;
                  this.loadReportData(this.reportPeriod);
                })
                Divider().margin({ top: this.getDividerMargin(), bottom: this.getDividerMargin() } as Padding)
                this.MenuRow('📚 养生知识', () => {
                  this.selectedArticleIndex = -1;
                  this.showKnowledgeDialog = true;
                })
                Divider().margin({ top: this.getDividerMargin(), bottom: this.getDividerMargin() } as Padding)
                this.MenuRow('⚙️ 设置', () => {
                  this.tempWaterTarget = this.dailyWaterTarget;
                  this.tempExerciseTarget = this.weeklyExerciseTarget;
                  this.tempSleepHours = this.sleepTargetHours;
                  this.loadReminderSettings();
                  this.showSettingsDialog = true;
                })
                Divider().margin({ top: this.getDividerMargin(), bottom: this.getDividerMargin() } as Padding)
                this.MenuRow('ℹ️ 关于', () => {
                  this.showAboutDialog = true;
                })
              }
            }
          }
          .margin({ left: this.getCardMargin(), right: this.getCardMargin(), bottom: this.getCardMargin() } as Padding)

入口逻辑很清晰:

  • 点击"📊 健康报告"
  • 打开弹窗 showReportDialog = true
  • 同步加载本周/本月数据

2.2 健康报告弹窗结构

弹窗包含三个核心区域:综合评分、每日评分、各项平均分。

typescript 复制代码
      // 健康报告弹窗
      if (this.showReportDialog) {
        Column() {
          Column() {
            // 头部
            Row() {
              Text('健康报告')
                .fontSize(this.getDialogTitleSize())
                .fontWeight(FontWeight.Bold)
                .fontColor($r('app.color.text_primary'))

              Blank()

              // 周/月切换
              Row() {
                Text('周')
                  .fontSize(this.getSmallTextSize())
                  .fontColor(this.reportPeriod === 'week' ? Color.White : $r('app.color.text_primary'))
                  .padding({
                    left: this.getDividerMargin(),
                    right: this.getDividerMargin(),
                    top: this.getChipPaddingVertical(),
                    bottom: this.getChipPaddingVertical()
                  } as Padding)
                  .backgroundColor(this.reportPeriod === 'week' ? $r('app.color.primary_color') : $r('app.color.input_background'))
                  .borderRadius(this.getDividerMargin())
                  .onClick(() => {
                    if (this.reportPeriod !== 'week') {
                      this.reportPeriod = 'week';
                      this.loadReportData('week');
                    }
                  })

                Text('月')
                  .fontSize(this.getSmallTextSize())
                  .fontColor(this.reportPeriod === 'month' ? Color.White : $r('app.color.text_primary'))
                  .padding({
                    left: this.getDividerMargin(),
                    right: this.getDividerMargin(),
                    top: this.getChipPaddingVertical(),
                    bottom: this.getChipPaddingVertical()
                  } as Padding)
                  .backgroundColor(this.reportPeriod === 'month' ? $r('app.color.primary_color') : $r('app.color.input_background'))
                  .borderRadius(this.getDividerMargin())
                  .margin({ left: this.getItemGap() } as Padding)
                  .onClick(() => {
                    if (this.reportPeriod !== 'month') {
                      this.reportPeriod = 'month';
                      this.loadReportData('month');
                    }
                  })
              }

              Text('×')
                .fontSize(this.getCloseLargeSize())
                .fontColor($r('app.color.text_secondary'))
                .margin({ left: this.getSectionGap() } as Padding)
                .onClick(() => {
                  this.showReportDialog = false;
                })
            }
            .width('100%')

头部结构包含:

  • 左侧标题
  • 中部周/月切换
  • 右侧关闭按钮

2.3 综合评分(环形进度)

typescript 复制代码
                // 综合评分区
                Column() {
                  Stack() {
                    Progress({
                      value: 100,
                      total: 100,
                      type: ProgressType.Ring
                    })
                      .width(this.getReportRingSize())
                      .height(this.getReportRingSize())
                      .color($r('app.color.divider_color'))
                      .style({ strokeWidth: this.getReportRingStrokeWidth() })

                    Progress({
                      value: this.avgTotalScore,
                      total: 100,
                      type: ProgressType.Ring
                    })
                      .width(this.getReportRingSize())
                      .height(this.getReportRingSize())
                      .color(this.getScoreColor(this.avgTotalScore))
                      .style({ strokeWidth: this.getReportRingStrokeWidth() })

                    Column() {
                      Text(this.avgTotalScore.toString())
                        .fontSize(this.getReportScoreSize())
                        .fontWeight(FontWeight.Bold)
                        .fontColor(this.getScoreColor(this.avgTotalScore))
                      Text('平均分')
                        .fontSize(this.getReportLabelSize())
                        .fontColor($r('app.color.text_secondary'))
                    }
                  }

                  Text(`${this.getCurrentReportOffset() === 0 ? (this.reportPeriod === 'week' ? '本周' : '本月') : this.getReportRangeLabel()}健康评分`)
                    .fontSize(this.getSmallTextSize())
                    .fontColor($r('app.color.text_secondary'))
                    .margin({ top: this.getItemGap() } as Padding)

                }
                .width('100%')
                .padding({ top: this.getSectionGap(), bottom: this.getSectionGap() } as Padding)

这里使用两个环形进度叠加:

  • 底层灰色圆环是背景
  • 顶层彩色圆环表示平均分

2.4 各项平均分(线性进度)

typescript 复制代码
                // 模块统计
                Column() {
                  Text('各项平均')
                    .fontSize(this.getSectionTitleSize())
                    .fontWeight(FontWeight.Medium)
                    .fontColor($r('app.color.text_primary'))
                    .width('100%')

                  // 打卡
                  Row() {
                    Text('✅ 打卡')
                      .fontSize(this.getBodyTextSize())
                      .fontColor($r('app.color.text_primary'))
                    Blank()
                    Text(`${this.avgCheckInScore}分`)
                      .fontSize(this.getBodyTextSize())
                      .fontWeight(FontWeight.Medium)
                      .fontColor(this.getScoreColor(this.avgCheckInScore))
                  }
                  .width('100%')
                  .margin({ top: this.getDividerMargin() } as Padding)

                  Progress({
                    value: this.avgCheckInScore,
                    total: 100,
                    type: ProgressType.Linear
                  })
                    .height(this.getProgressHeight())
                    .color($r('app.color.primary_color'))
                    .backgroundColor($r('app.color.divider_color'))
                    .borderRadius(this.getProgressHeight() / 2)
                    .margin({ top: this.getSmallGap() } as Padding)

                  // 饮水
                  Row() {
                    Text('💧 饮水')
                      .fontSize(this.getBodyTextSize())
                      .fontColor($r('app.color.text_primary'))
                    Blank()
                    Text(`${this.avgWaterScore}分`)
                      .fontSize(this.getBodyTextSize())
                      .fontWeight(FontWeight.Medium)
                      .fontColor(this.getScoreColor(this.avgWaterScore))
                  }
                  .width('100%')
                  .margin({ top: this.getDividerMargin() } as Padding)

                  Progress({
                    value: this.avgWaterScore,
                    total: 100,
                    type: ProgressType.Linear
                  })
                    .height(this.getProgressHeight())
                    .color($r('app.color.water_blue'))
                    .backgroundColor($r('app.color.divider_color'))
                    .borderRadius(this.getProgressHeight() / 2)
                    .margin({ top: this.getSmallGap() } as Padding)

                  // 运动
                  Row() {
                    Text('🏃 运动')
                      .fontSize(this.getBodyTextSize())
                      .fontColor($r('app.color.text_primary'))
                    Blank()
                    Text(`${this.avgExerciseScore}分`)
                      .fontSize(this.getBodyTextSize())
                      .fontWeight(FontWeight.Medium)
                      .fontColor(this.getScoreColor(this.avgExerciseScore))
                  }
                  .width('100%')
                  .margin({ top: this.getDividerMargin() } as Padding)

                  Progress({
                    value: this.avgExerciseScore,
                    total: 100,
                    type: ProgressType.Linear
                  })
                    .height(this.getProgressHeight())
                    .color($r('app.color.exercise_orange'))
                    .backgroundColor($r('app.color.divider_color'))
                    .borderRadius(this.getProgressHeight() / 2)
                    .margin({ top: this.getSmallGap() } as Padding)

                  // 睡眠
                  Row() {
                    Text('😴 睡眠')
                      .fontSize(this.getBodyTextSize())
                      .fontColor($r('app.color.text_primary'))
                    Blank()
                    Text(`${this.avgSleepScore}分`)
                      .fontSize(this.getBodyTextSize())
                      .fontWeight(FontWeight.Medium)
                      .fontColor(this.getScoreColor(this.avgSleepScore))
                  }
                  .width('100%')
                  .margin({ top: this.getDividerMargin() } as Padding)

                  Progress({
                    value: this.avgSleepScore,
                    total: 100,
                    type: ProgressType.Linear
                  })
                    .height(this.getProgressHeight())
                    .color($r('app.color.sleep_purple'))
                    .backgroundColor($r('app.color.divider_color'))
                    .borderRadius(this.getProgressHeight() / 2)
                    .margin({ top: this.getSmallGap() } as Padding)
                }

四条进度条分别代表四个维度的平均分,颜色区分非常直观:

  • 打卡:主色
  • 饮水:蓝色
  • 运动:橙色
  • 睡眠:紫色

三、总结

本案例完整展示了一个移动端健康报告弹窗的实现方式,核心要点包括:

  1. 菜单入口触发弹窗显示
  2. 周/月切换控制数据周期
  3. 环形进度展示综合评分
  4. 线性进度展示分项平均分

掌握这个案例后,你可以继续扩展更多报告内容,例如:

  • 数据趋势折线图
  • 日历高亮
  • 评分等级标签
相关推荐
互联网散修2 小时前
鸿蒙应用开发UI基础第十八节:表单交互核心组件Button、Radio、Toggle示例演示
ui·交互·harmonyos
不要卷鸿蒙啊18 小时前
【鸿蒙开发】HMRouter一款和好用的管理路由三方工具
前端·harmonyos
chenyingjian19 小时前
鸿蒙|性能优化-渲染丢帧优化
harmonyos
ujainu21 小时前
在 HarmonyOS PC 上实现自定义窗口样式的 Electron 应用详解
华为·electron·harmonyos
ujainu21 小时前
Electron 极简时钟应用开发全解析:托盘驻留、精准北京时间与 HarmonyOS PC 适配实战
javascript·electron·harmonyos
盐焗西兰花21 小时前
鸿蒙学习实战之路-Share Kit系列(10/17)-目标应用接收分享(应用内处理)
学习·华为·harmonyos
江湖有缘1 天前
基于开发者空间部署OtterWiki知识管理工具【华为开发者空间】
华为
大雷神1 天前
HarmonyOS APP<玩转React>开源教程八:主题系统实现
react.js·开源·harmonyos
fei_sun1 天前
【鸿蒙智能硬件】(六)使用鸿蒙app展示环境监测数据
华为·harmonyos