HarmonyOS 应用 · 班费管理成员页深度解析:paid 布尔位与三色缴费统计

项目编号:47-class-fund 技术栈:HarmonyOS ArkTS · ArkUI 声明式 UI · Stage 模型 本篇页面:成员页 Func2Tab.ets(101 行) 系列导航:(一)首页 · (二)收支 · (三)成员 · (四)统计

47 成员页首屏


一、Member interface:paid 缴费布尔位

interface Member {
  id: number;
  emoji: string;
  name: string;
  paid: boolean;
}

四个字段:id 是 key 锚点,emoji 是头像图标,name 是姓名,paid 是"是否已缴费"的布尔位。paid 是成员页的核心状态字段——驱动缴费标签的文案/颜色/底色三态变化。

  @State tab: number = 0;

  private tabs: string[] = ['全部', '已缴费', '未缴费'];
  private members: Member[] = [
    { id: 1, emoji: '🧑', name: '李强', paid: true },
    { id: 2, emoji: '👩', name: '王梅', paid: true },
    { id: 3, emoji: '👨', name: '陈浩', paid: false },
    { id: 4, emoji: '👩', name: '张娜', paid: true },
    { id: 5, emoji: '🧑', name: '刘洋', paid: false },
    { id: 6, emoji: '👨', name: '赵磊', paid: true }
  ];

@State tab 是分段筛选状态(0=全部/1=已缴费/2=未缴费)。六位成员:4 人已缴费 + 2 人未缴费。tabs 数组定义三个分段标签。


二、页面骨架:Header + SummaryCard + MemberList

  build() {
    Column() {
      this.Header()
      Scroll() {
        Column({ space: 14 }) {
          this.SummaryCard()
          this.MemberList()
        }
        .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)
  }

Header 在 Scroll 外固定,Scroll 内两块:SummaryCard(三色统计)+ MemberList(成员列表)。Header 包含标题和分段筛选 Tab——分段筛选随 Header 固定,滚动时不消失。


三、Header:标题 + tab 分段筛选

  @Builder
  Header() {
    Column() {
      Row() { Text('成员缴费').fontSize(20).fontWeight(FontWeight.Bold).fontColor(C.text) }
      .width('100%').height(this.safeTop + 52).padding({ top: this.safeTop, left: D.pad, right: D.pad })
      .alignItems(VerticalAlign.Bottom)
      Row() {
        ForEach(this.tabs, (t: string, idx: number) => {
          Text(t).fontSize(14)
            .fontColor(this.tab === idx ? C.primary : C.textSub)
            .fontWeight(this.tab === idx ? FontWeight.Bold : FontWeight.Normal)
            .layoutWeight(1).textAlign(TextAlign.Center).padding({ bottom: 12 })
            .onClick(() => { this.tab = idx; })
        }, (t: string) => t)
      }.width('100%')
    }.width('100%').backgroundColor(C.card)
  }

双行 Header——标题行 + 分段筛选行:

  • 标题行:20 号加粗"成员缴费",safeTop + 52 避让状态栏
  • 筛选行:ForEach 遍历 tabs,三段等分。tab === idx 驱动字色与字重双态——选中绿色加粗、未选灰色常规。无底色区分,仅靠字色/字重表达选中态——比胶囊式分段更轻量

onClick 设 this.tab = idx 触发 @State 刷新,筛选成员列表。


四、SummaryCard:三色缴费统计

  @Builder
  SummaryCard() {
    Row() {
      Column({ space: 4 }) {
        Text('40').fontSize(22).fontWeight(FontWeight.Bold).fontColor(C.primary)
        Text('应缴人数').fontSize(11).fontColor(C.textDim)
      }.layoutWeight(1)
      Column({ space: 4 }) {
        Text('36').fontSize(22).fontWeight(FontWeight.Bold).fontColor(C.ok)
        Text('已缴费').fontSize(11).fontColor(C.textDim)
      }.layoutWeight(1)
      Column({ space: 4 }) {
        Text('4').fontSize(22).fontWeight(FontWeight.Bold).fontColor(C.danger)
        Text('未缴费').fontSize(11).fontColor(C.textDim)
      }.layoutWeight(1)
    }
    .width('100%').padding({ top: 16, bottom: 16 }).backgroundColor(C.card).borderRadius(D.rLg).border({ width: 1, color: C.stroke })
  }

三色统计——应缴/已缴/未缴各用不同颜色:

// 应缴 40:主色绿
Text('40').fontColor(C.primary)   // #00A862

// 已缴 36:成功绿
Text('36').fontColor(C.ok)        // #2BB673

// 未缴 4:危险红
Text('4').fontColor(C.danger)     // #FF5A6E

三列 layoutWeight(1) 等分,22 号加粗数字 + 11 号灰色标签。红色"4"突出未缴人数——催缴的核心目标。


五、MemberList:paid 驱动的双态缴费标签

  @Builder
  MemberList() {
    Column({ space: 10 }) {
      ForEach(this.members, (m: Member) => {
        Row({ space: 12 }) {
          Row() { Text(m.emoji).fontSize(22) }
          .width(42).height(42).backgroundColor(C.cardSoft).borderRadius(21).justifyContent(FlexAlign.Center)
          Text(m.name).fontSize(14).fontColor(C.text).layoutWeight(1)
          Text(m.paid ? '已缴费' : '待缴费').fontSize(12)
            .fontColor(m.paid ? C.ok : C.danger)
            .padding({ left: 10, right: 10, top: 5, bottom: 5 })
            .backgroundColor(m.paid ? '#E8F7EE' : '#FDEAEA').borderRadius(D.rSm)
            .onClick(() => { promptAction.showToast({ message: m.paid ? '已缴费' : '催缴 ' + m.name }); })
        }
        .width('100%').padding(12).backgroundColor(C.card).borderRadius(D.rMd).border({ width: 1, color: C.stroke })
      }, (m: Member) => m.id.toString())
    }.width('100%')
  }

paid 驱动三元变化——文案、字色、底色三路分支:

// 文案:已缴费 vs 待缴费
Text(m.paid ? '已缴费' : '待缴费')

// 字色:绿色 vs 红色
.fontColor(m.paid ? C.ok : C.danger)

// 底色:浅绿 vs 浅红
.backgroundColor(m.paid ? '#E8F7EE' : '#FDEAEA')

paid === true 时标签"已缴费"绿字浅绿底(C.ok + #E8F7EE),paid === false 时标签"待缴费"红字浅红底(C.danger + #FDEAEA)。#E8F7EE 和 #FDEAEA 是绿/红的浅化版,做标签底色不抢眼。

标签独立 onClick:已缴费点击弹"已缴费"(无操作),待缴费点击弹"催缴 + 姓名"——催缴入口仅对未缴费成员开放。

47 成员页完整列表

47 成员页未缴费分段

47 成员页催缴 Toast


六、tab 筛选的实现路径

Demo 中 tab 已绑定 onClick,但 MemberList 仍遍历完整 this.members——筛选未接入。实现筛选需用过滤函数:

private getFiltered(): Member[] {
  if (this.tab === 0) {
    return this.members;
  }
  if (this.tab === 1) {
    return this.members.filter((m: Member) => m.paid);
  }
  return this.members.filter((m: Member) => !m.paid);
}

tab === 0 全部、tab === 1 已缴费(paid === true)、tab === 2 未缴费(paid === false)。ForEach 遍历 getFiltered() 而非 this.members——点击分段即时过滤列表。


七、可演进方向

方向现状改法收益
分段筛选tab 未接入filter + getFiltered()列表按缴费状态过滤
催缴通知Toast 模拟推送通知 + 记录催缴次数真实催缴触达
缴费标记静态 paid手动标记缴费 + 时间记录线下缴费可标记
数据源静态 6 人服务端拉取全班成员完整成员名单
统计联动写死 40/36/4从 members 动态统计统计随数据更新

成员页是班费管理的缴费跟踪——SummaryCard 的三色统计量化缴费进度、MemberList 的 paid 驱动双态标签区分已缴/待缴。从分段筛选的轻量选中态到催缴入口的条件开放,每一层都有明确的职责边界。

系列导航:(一)首页 · (二)收支 · (三)成员 · (四)统计

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐