HarmonyOS 校园应用系列之鸿蒙原生实战:用 ArkUI 搭水电缴费首页 —— 消费趋势柱图与排行列表

应用背景:10 水电缴费(utility-pay)是鸿蒙原生校园工具系列第 10 个应用。本文逐行拆解 HomeTab.ets(310 行),聚焦 HeroCard 渐变统计卡WeekChart 柱状趋势图BillList 近期消费列表RankList 消费排行 四大核心组件。

应用效果图

一、页面整体布局

// HomeTab.ets — 页面根结构
Column() {
  this.Header()
  Scroll() {
    Column({ space: 14 }) {
      this.HeroCard()
      this.SearchBar()
      this.QuickAction()
      this.WeekChart()
      this.SectionTitle('近期消费', '全部 >')
      this.BillList()
      this.SectionTitle('消费排行', '详情 >')
      this.RankList()
    }
    .width('100%')
    .padding({ left: D.pad, right: D.pad, top: 14, bottom: D.pad + this.safeBottom + 20 })
  }
  .layoutWeight(1).scrollBar(BarState.Off).align(Alignment.Top)
}
.width('100%').height('100%').backgroundColor(C.bg)

根布局为 Column 两段式:Header 固定在 Scroll 外部不随内容滚动,滚动区内部 Column({ space: 14 }) 纵向排布 8 个模块,隐藏滚动条。底部 padding 动态叠加 safeBottom 安全区高度,避免内容被系统导航条遮挡。

Header 是白底标题栏:"水电缴费" 22vp 粗体,顶部 padding 叠加 safeTop 适配状态栏:

// Header — 标题栏
@Builder
Header() {
  Column() {
    Text('水电缴费')
      .fontSize(22).fontWeight(FontWeight.Bold).fontColor(C.text)
      .width('100%')
      .padding({ top: this.safeTop + 10, left: D.pad, right: D.pad, bottom: 12 })
  }
  .width('100%').backgroundColor(C.card)
}

项目源码开源:https://gitee.com/codenestFlow/HarmonyOSHub

配图

二、HeroCard 渐变头部统计卡

2.1 渐变背景与圆角策略

// HeroCard — 外层容器
Column({ space: 16 }) {
  // 宿舍信息行
  Row() {
    Text('梅园 6 栋 · 302 室').fontSize(13).fontColor('#FFFFFF').opacity(0.85)
    Blank()
    Text('本月统计').fontSize(11).fontColor('#FFFFFF').opacity(0.7)
  }
  .width('100%')

  // 三维统计
  Row({ space: 12 }) {
    ForEach(this.stats, (s: StatItem) => {
      Column({ space: 4 }) {
        Text(s.value).fontSize(22).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
        Text(s.label).fontSize(11).fontColor('#FFFFFF').opacity(0.8)
      }
      .layoutWeight(1).alignItems(HorizontalAlign.Center)
    }, (s: StatItem) => s.label)
  }
  .width('100%')

  // 双按钮行
  Row({ space: 12 }) {
    Button('立即缴费')
      .fontSize(14).fontColor(C.primary).backgroundColor('#FFFFFF')
      .borderRadius(D.rSm).height(40).layoutWeight(1)
      .onClick(() => { promptAction.showToast({ message: '前往缴费' }); })
    Button('查看账单')
      .fontSize(14).fontColor('#FFFFFF').backgroundColor('#33FFFFFF')
      .borderRadius(D.rSm).height(40).layoutWeight(1)
      .onClick(() => { promptAction.showToast({ message: '查看账单' }); })
  }
  .width('100%')
}
.width('100%')
.padding(20)
.borderRadius(D.rLg)
.linearGradient({ angle: 135, colors: [[C.primary, 0.0], [C.accent, 1.0]] })

外层 Column 设 .linearGradient(...) 从主色青蓝 C.primary(#0891B2)135° 过渡到亮青 C.accent(#22D3EE),四角统一 D.rLg(20vp)圆角,内边距 20。

2.2 三维统计

// stats 数据定义
private stats: StatItem[] = [
  { value: '¥86', label: '电费余额', color: '#FFFFFF' },
  { value: '¥42', label: '水费余额', color: '#FFFFFF' },
  { value: '¥478', label: '累计缴费', color: '#FFFFFF' }
]

三项统计通过 ForEach + layoutWeight(1) 三等分:22vp 白色粗体数值 + 80% 透明度小标签,不设分隔线,靠 12vp 间距自然分区。

2.3 双按钮操作区

两个 .layoutWeight(1) 等宽按钮:主按钮"立即缴费"白底主色字,次按钮"查看账单"20% 透明白底#33FFFFFF)白字,均高 40vp、D.rSm 圆角,点击弹 toast。

首页顶部

三、SearchBar 搜索栏

// SearchBar — 搜索组件
Row({ space: 10 }) {
  Text('🔍').fontSize(18)
  TextInput({ placeholder: '搜索缴费记录', text: this.searchText })
    .backgroundColor('transparent').borderRadius(D.rSm).height(40)
    .layoutWeight(1).placeholderColor(C.textDim).placeholderFont({ size: 14 })
    .onChange((val: string) => { this.searchText = val; })
}
.width('100%')
.padding({ left: 14, right: 14 })
.backgroundColor(C.card)
.borderRadius(D.rMd)
.border({ width: 1, color: C.stroke })

外层 Row 包裹 Emoji 图标 + TextInput,白卡底 + 1vp 描边 + D.rMd 圆角。TextInput 自身背景透明、layoutWeight(1) 占满剩余宽度,onChange 双向绑定 @State searchText

四、QuickAction 四宫格快捷入口

// QuickAction — 快捷入口
Row({ space: 10 }) {
  ForEach(this.quickActions, (item: QuickItem) => {
    Column({ space: 6 }) {
      Text(item.emoji).fontSize(26)
      Text(item.label).fontSize(12).fontColor(C.textSub)
    }
    .layoutWeight(1).padding({ top: 14, bottom: 14 })
    .backgroundColor(C.card).borderRadius(D.rMd)
    .border({ width: 1, color: C.stroke })
    .onClick(() => { promptAction.showToast({ message: item.label }); })
  }, (item: QuickItem) => item.label)
}
.width('100%')

本页用 Emoji 字符 作图标(⚡💧📜🔔):零资源依赖。四项功能:缴电费 / 缴水费 / 账单 / 余额提醒。每项 layoutWeight(1) 等分,白卡底+描边独立成卡,点击 toast 提示。

首页中部

五、WeekChart 柱状趋势图

5.1 数据定义

// 周数据定义
private weekBars: WeekBar[] = [
  { day: '一', value: 45 },
  { day: '二', value: 72 },
  { day: '三', value: 38 },
  { day: '四', value: 90 },
  { day: '五', value: 55 },
  { day: '六', value: 68 },
  { day: '日', value: 42 }
];

private maxBar: number = 90;

maxBar 固定为 90(本周峰值),作为柱高归一化基准。

5.2 柱状图渲染逻辑

// WeekChart — 柱状图主体
Column({ space: 12 }) {
  // 标题行
  Row() {
    Text('本周用电趋势').fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text)
    Blank()
    Text('峰值 90 度').fontSize(12).fontColor(C.textDim)
  }
  .width('100%')

  // 柱体区域
  Row({ space: 6 }) {
    ForEach(this.weekBars, (w: WeekBar) => {
      Column({ space: 6 }) {
        Column() {
          Column()
            .width('100%')
            .height(Math.floor(w.value / this.maxBar * 80))
            .backgroundColor(C.primary)
            .borderRadius(4)
        }
        .width('100%').height(80)

        Text(w.day).fontSize(11).fontColor(C.textDim)
      }
      .layoutWeight(1).alignItems(HorizontalAlign.Center)
    }, (w: WeekBar) => w.day)
  }
  .width('100%')
}

Math.floor(w.value / this.maxBar * 80):把 090 的原始值线性映射到 080vp 柱高。外层 80vp 高的 Column 作固定轨道,内层柱体 width('100%') 铺满、borderRadius(4) 圆角、主色纯色填充。

5.3 设计要点

柱体宽度不写死,由 layoutWeight(1) 七等分自适应屏幕;标题行右侧"峰值 90 度"与 maxBar 数值呼应,数据与展示强一致。

首页下部

六、SectionTitle 通用标题行

// SectionTitle — 通用标题
@Builder
SectionTitle(title: string, action: string) {
  Row() {
    Text(title).fontSize(16).fontWeight(FontWeight.Bold).fontColor(C.text)
    Blank()
    Text(action).fontSize(12).fontColor(C.textDim)
      .onClick(() => { promptAction.showToast({ message: action }); })
  }
  .width('100%')
}

带参 @Builder 复用两次:SectionTitle('近期消费', '全部 >')SectionTitle('消费排行', '详情 >'),右侧动作文字点击 toast。

七、BillList 近期消费列表

// BillList — 消费列表
Column({ space: 10 }) {
  ForEach(this.bills, (b: Bill) => {
    Column({ space: 10 }) {
      Row({ space: 12 }) {
        Row() {
          Text(b.emoji).fontSize(22)
        }
        .width(46).height(46)
        .backgroundColor(C.primarySoft).borderRadius(D.rSm)
        .justifyContent(FlexAlign.Center)

        Column({ space: 4 }) {
          Row({ space: 8 }) {
            Text(b.name).fontSize(14).fontWeight(FontWeight.Medium).fontColor(C.text)
            Text(b.tag).fontSize(10).fontColor(C.primary)
              .padding({ left: 6, right: 6, top: 2, bottom: 2 })
              .backgroundColor(C.primarySoft).borderRadius(4)
          }
          .width('100%')
          Text(b.time).fontSize(11).fontColor(C.textDim)
          Row({ space: 2 }) {
            ForEach([1, 2, 3, 4, 5], (n: number) => {
              Text(n <= b.rating ? '★' : '☆').fontSize(12).fontColor(n <= b.rating ? C.warn : C.textDim)
            }, (n: number) => n.toString())
          }
          .width('100%')
        }
        .alignItems(HorizontalAlign.Start).layoutWeight(1)

        Column({ space: 4 }) {
          Text(b.amount).fontSize(15).fontColor(C.danger).fontWeight(FontWeight.Bold)
          Text(b.progress === 100 ? '已完成' : '处理中').fontSize(10).fontColor(C.textDim)
        }
        .alignItems(HorizontalAlign.End)
      }
      .width('100%')

      Progress({ value: b.progress, total: 100 }).color(C.primary).width('100%')
    }
    .width('100%')
    .padding(14)
    .backgroundColor(C.card).borderRadius(D.rMd)
    .border({ width: 1, color: C.stroke })
    .onClick(() => { promptAction.showToast({ message: b.name }); })
  }, (b: Bill) => b.id.toString())
}
.width('100%')

每条记录含:Emoji 图标(46vp 圆角方块浅青底)、名称+标签徽章(已缴费/自动扣)、时间、五星评分(ForEach([1,2,3,4,5]) 逐位判断实心★/空心☆)、金额(danger 红)与"已完成/处理中"状态。进度条直接用系统 Progress 组件——value/total 驱动、主色填充,无需自绘轨道。

首页底部

八、RankList 消费排行榜

// RankList — 排行榜
Column({ space: 10 }) {
  ForEach(this.ranks, (r: RankItem) => {
    Row({ space: 12 }) {
      Text(r.rank.toString()).fontSize(16).fontWeight(FontWeight.Bold)
        .fontColor(r.rank === 1 ? C.warn : C.textDim).width(24)
      Text(r.emoji).fontSize(20)
      Text(r.name).fontSize(14).fontColor(C.text).layoutWeight(1)
      Text(r.value).fontSize(14).fontColor(C.danger).fontWeight(FontWeight.Medium)
    }
    .width('100%')
    .padding({ left: 14, right: 14, top: 10, bottom: 10 })
    .backgroundColor(C.card).borderRadius(D.rSm)
    .border({ width: 1, color: C.stroke })
    .onClick(() => { promptAction.showToast({ message: r.name }); })
  }, (r: RankItem) => r.id.toString())
}
.width('100%')

排行榜位于首页底部:第 1 名序号用 C.warn 橙色突出,其余灰色;金额统一 danger 红。三项数据:电费支出 ¥320 / 水费支出 ¥158 / 燃气费 ¥0。

九、本章小结

组件核心技术学习价值
HeroCardlinearGradient 渐变卡渐变头部标准模式
QuickActionEmoji 图标 + layoutWeight 等分零资源快捷入口
WeekChartMath.floor 归一化柱高数据可视化最佳实践
SectionTitle带参 @Builder 复用通用标题行方案
BillListProgress 组件 + 五星评分列表项内嵌进度指示
RankList条件序号配色排行榜视觉层次

WeekChart 柱状图 最具学习价值——Math.floor(value / maxBar * 80) 一行完成数据到高度的归一化映射,配合 layoutWeight 等分实现纯声明式图表。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐