【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. 线性进度展示分项平均分

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

  • 数据趋势折线图
  • 日历高亮
  • 评分等级标签
相关推荐
攻城狮在此11 分钟前
华为交换机Console口密码如何清除
运维·网络·华为
一直在想名19 分钟前
Flutter 框架跨平台鸿蒙开发 - 步数换算器
flutter·华为·harmonyos
chenyingjian3 小时前
鸿蒙|能力特性-统一文件预览
前端·harmonyos
Utopia^3 小时前
Flutter 框架跨平台鸿蒙开发 - 起床战争
flutter·华为·harmonyos
autumn20053 小时前
Flutter 框架跨平台鸿蒙开发 - 习惯养成塔
flutter·华为·harmonyos
李李李勃谦3 小时前
Flutter 框架跨平台鸿蒙开发 - 决策硬币
flutter·华为·harmonyos
samroom4 小时前
【鸿蒙应用开发 Dev ECO Studio 5.0版本】从0到1!从无到有!最全!计算器------按钮动画、滑动退格、中缀表达式转后缀表达式、UI设计
数据结构·ui·华为·typescript·harmonyos·鸿蒙
李李李勃谦4 小时前
Flutter 框架跨平台鸿蒙开发 - 废话生成器
flutter·华为·harmonyos
2301_822703204 小时前
开源鸿蒙跨平台Flutter开发:非侵入式血压预估:基于 HRV 与脉搏波的建模与实现
flutter·开源·harmonyos
一直在想名5 小时前
Flutter 框架跨平台鸿蒙开发 - 胶片相机模拟
数码相机·flutter·华为·harmonyos