HarmonyOS NEXT 企业级记账APP:首页布局与 SummaryCard 设计

发布时间:2026/7/29 2:19:35
HarmonyOS NEXT 企业级记账APP:首页布局与 SummaryCard 设计 首页布局与 SummaryCard 设计本文是《HarmonyOS NEXT 企业级开发实战30篇打造智能记账APP》系列的第05篇对应 Git Tagv0.0.5。承接第 04 篇的底导航本篇开发首页核心 UI今日支出、今日收入、最近账单列表并封装 SummaryCard 汇总卡片组件。前言首页是用户进入应用后的第一印象需要在3 秒内告诉用户今天花了多少、挣了多少、最近账单如何。HarmonyLedger 首页采用卡片化布局用 SummaryCard 展示收支汇总用 List 展示最近账单。本篇暂时用 Mock 数据填充后续 v0.0.7 篇接入真实 Repository。本文将带你设计首页整体布局汇总卡 最近账单 FAB封装 SummaryCard 通用汇总卡片组件使用 List ListItem 渲染最近账单列表集成主题系统实现深色模式企业级核心原则首页必须一目了然、层次清晰、操作直达。参考 ArkUI List 组件 了解官方约定。一、首页布局规划1.1 页面结构树HomeView首页 ├─ TitleBar标题栏今日账单汇总 ├─ SummaryCard今日支出卡 ├─ SummaryCard今日收入卡 ├─ SectionTitle最近账单标题 └─ List最近账单列表 └ ListItem账单项1.2 关键交互区操作行为支出汇总卡点击跳转今日支出详情收入汇总卡点击跳转今日收入详情账单项点击跳转账单详情账单项长按弹出删除菜单FAB 按钮点击跳转新增账单页二、SummaryCard 组件封装2.1 组件设计目标大数字突出金额字号 36加粗显示语义色收入绿、支出红、预算蓝图标装饰右上角小图标辅助识别点击回调通过BuilderParam暴露点击事件响应深色模式通过 AppStorage 集成主题2.2 组件实现// components/card/SummaryCard.ets import { AppColors } from ../../theme/Colors; import { AppFontSize, AppFontWeight } from ../../theme/Typography; import { AppSpace } from ../../theme/Spacing; Component export struct SummaryCard { Prop title: string ; // 今日支出 Prop money: string 0.00; // 123.45 Prop icon: Resource $r(app.media.icon_expense); // 图标 Prop semanticColor: string AppColors.Expense; // 主题色 BuilderParam onClick: () void; build() { Column() { Row() { Column() { Text(this.title) .fontSize(AppFontSize.SM) .fontColor(AppColors.SecondaryText) Text(¥${this.money}) .fontSize(AppFontSize.Display) .fontColor(this.semanticColor) .fontWeight(AppFontWeight.Bold) .margin({ top: AppSpace.XS }) } .alignItems(HorizontalAlign.Start) Image(this.icon) .width(28) .height(28) .fillColor(this.semanticColor) } .width(100%) .justifyContent(FlexAlign.SpaceBetween) .alignItems(HorizontalAlign.Center) } .padding(AppSpace.CardPadding) .backgroundColor(AppColors.CardBackground) .borderRadius(AppSpace.CardRadius) .onClick(() { this.onClick(); }) } }2.3 资源占用资源用途icon_expense.png支出汇总图标icon_income.png收入汇总图标icon_budget.png预算汇总图标三、首页布局实现3.1 HomeView 完整代码// pages/HomeView.ets import { SummaryCard } from ../components/card/SummaryCard; import { SectionTitle } from ../components/text/SectionTitle; import { BillCard } from ../components/bill/BillCard; import { AppColors } from ../theme/Colors; import { AppSpace } from ../theme/Spacing; import { HomeViewModel } from ../viewmodel/HomeViewModel; Entry Component struct HomeView { State viewModel: HomeViewModel new HomeViewModel(); aboutToAppear() { this.viewModel.loadMockData(); } build() { Scroll() { Column() { // 标题 Text(今日账单汇总) .fontSize(20) .fontWeight(FontWeight.Bold) .margin({ top: AppSpace.XL, bottom: AppSpace.MD }) // 收支汇总卡 Row() { SummaryCard({ title: 今日支出, money: this.viewModel.todayExpense, icon: $r(app.media.icon_expense), semanticColor: AppColors.Expense, onClick: () { this.viewModel.goExpenseDetail(); } }) SummaryCard({ title: 今日收入, money: this.viewModel.todayIncome, icon: $r(app.media.icon_income), semanticColor: AppColors.Income, onClick: () { this.viewModel.goIncomeDetail(); } }) } .width(100%) .justifyContent(FlexAlign.SpaceBetween) // 最近账单标题 SectionTitle({ title: 最近账单 }) .margin({ top: AppSpace.XL, bottom: AppSpace.SM }) // 最近账单列表 List({ space: AppSpace.SM }) { ForEach(this.viewModel.recentBills, (bill: Bill, index: number) { ListItem() { BillCard({ money: bill.money, type: bill.type, category: bill.category, remark: bill.remark }) } }, (bill: Bill) bill.id) } .width(100%) .layoutWeight(1) .padding({ bottom: 80 }) } .width(100%) .padding({ left: AppSpace.XL, right: AppSpace.XL }) } .width(100%) .height(100%) .scrollBar(BarState.Off) } }3.2 FAB 快捷按钮// 在 HomeView build 内追加 FAB Stack() { Scroll() { ... } // 上述内容 Image($r(app.media.icon_add)) .width(56) .height(56) .fillColor(AppColors.White) .backgroundColor(AppColors.Budget) .borderRadius(28) .shadow({ radius: 8, color: #66000000, offsetY: 4 }) .position({ x: 85%, y: 85% }) .onClick(() { this.viewModel.goAddBill(); }) }四、HomeViewModel 雏创4.1 ViewModel 设计企业级 MVVM 要求页面只管 UI业务逻辑放 ViewModel// viewmodel/HomeViewModel.ets import { Bill } from ../model/Bill; import { MoneyUtil } from ../utils/MoneyUtil; export class HomeViewModel { todayExpense: string 0.00; todayIncome: string 0.00; recentBills: Bill[] []; /** 加载今日数据本篇用 Mock */ loadMockData(): void { const mockBills: Bill[] [ new Bill(1, 3500, expense, food, 午餐, Date.now()), new Bill(2, 8000, income, salary, 今日工资, Date.now()), new Bill(3, 2500, expense, transport, 打车, Date.now()) ]; this.recentBills mockBills; const expenseSum mockBills.filter(b b.type expense).sum(b b.money); const incomeSum mockBills.filter(b b.type income).sum(b b.money); this.todayExpense MoneyUtil.format(expenseSum); this.todayIncome MoneyUtil.format(incomeSum); } goExpenseDetail(): void { /* RouterUtil.go(/expense/today); */ } goIncomeDetail(): void { /* RouterUtil.go(/income/today); */ } goAddBill(): void { /* RouterUtil.go(/bill/add); */ } }4.2 Mock 数据 Bill// model/Bill.ets占位版 export class Bill { id: string; money: number; // 分 type: string; // expense / income category: string; // 分类名 remark: string; date: number; constructor(id: string, money: number, type: string, category: string, remark: string, date: number) { this.id id; this.money money; this.type type; this.category category; this.remark remark; this.date date; } }4.3 MoneyUtil 工具类// utils/MoneyUtil.ets export class MoneyUtil { /** 分 → 元字符串 */ static format(cents: number): string { return (cents / 100).toFixed(2); } /** 元 → 分 */ static toCents(yuan: number): number { return Math.round(yuan * 100); } }设计要点金额统一以分为单位存储避免浮点精度问题。这是金融类应用的基本规范。五、SectionTitle 与 BillCard 占位5.1 SectionTitle// components/text/SectionTitle.ets Component export struct SectionTitle { Prop title: string ; build() { Text(this.title) .fontSize(18) .fontWeight(FontWeight.Bold) } }5.2 BillCard 占位// components/bill/BillCard.ets占位v0.0.6 篇完善 import { AppColors } from ../../theme/Colors; import { AppFontSize } from ../../theme/Typography; import { AppSpace } from ../../theme/Spacing; import { MoneyUtil } from ../../utils/MoneyUtil; Component export struct BillCard { Prop money: number 0; Prop type: string expense; Prop category: string ; Prop remark: string ; build() { Row() { Column() { Text(this.category) .fontSize(AppFontSize.MD) .fontColor(AppColors.PrimaryText) Text(this.remark) .fontSize(AppFontSize.SM) .fontColor(AppColors.SecondaryText) .margin({ top: 2 }) } .alignItems(HorizontalAlign.Start) Text(¥${MoneyUtil.format(this.money)}) .fontSize(AppFontSize.LG) .fontColor(this.type income ? AppColors.Income : AppColors.Expense) .fontWeight(FontWeight.Bold) } .width(100%) .padding(AppSpace.CardPadding) .backgroundColor(AppColors.CardBackground) .borderRadius(AppSpace.CardRadius) .justifyContent(FlexAlign.SpaceBetween) .alignItems(HorizontalAlign.Center) } }六、最佳实践6.1 MVVM 分层View (HomeView) ↓ 持有 ViewModel (HomeViewModel) ↓ 调用 Repository (BillRepository) ← v0.0.7 接入 ↓ 操作 Model (Bill)为什么必须分层View 只管渲染不写业务逻辑ViewModel 可单元测试Repository 屏蔽存储实现Model 集中数据定义6.2 Mock 数据策略// 开发期用 Mock上线期换真实 Repository loadData(): void { if (DEBUG) { this.loadMockData(); // Mock 数据 } else { BillRepository.getTodayBills() // 真实数据 .then(bills { ... }); } }6.3 List 性能配置说明List({ space: 8 })Item 间距 8dp.layoutWeight(1)占满剩余空间ForEachkey必须用 bill.id禁止用 index踩坑点ForEachkey 用 index 会导致列表项状态错乱必须用业务 id。七、运行验证7.1 编译检查hvigorw assembleHap--modemodule-pproductdefault7.2 视觉验证首页顶部显示今日账单汇总标题支出卡与收入卡并排金额用对应语义色最近账单列表显示 3 条 Mock 数据FAB 按钮浮在右下角蓝色圆形深色模式自动切换卡片背景色八、常见问题8.1 Scroll 嵌套 List 冲突// 错误Scroll 嵌套 List 会冲突 Scroll() { Column() { List() { ... } // ❌ List 无法滚动 } } // 解决用 Column List.layoutWeight(1) 替代 Column() { Column() { ... } // 顶部固定内容 List() { ... }.layoutWeight(1) // 自动占满剩余 }8.2 ForEach key 缺失// 错误ForEach 缺 key列表项刷新错乱 ForEach(this.bills, (bill) { ListItem() { ... } }) // 正解必须提供稳定 key ForEach(this.bills, (bill) { ... }, (bill) bill.id)九、Git 提交9.1 Commit Messagegitadd.gitcommit-mfeat(home): 完成首页布局与 SummaryCard 设计 - 新增 SummaryCard 通用汇总卡片组件 - 新增 SectionTitle、BillCard 占位组件 - 集成 HomeView 完整布局标题 汇总卡 列表 FAB - 创建 HomeViewModel 处理业务逻辑 - 创建 Mock Bill 数据模型与 MoneyUtil 工具类9.2 CHANGELOG## [v0.0.5] - 2026-07-27 ### Added - components/card/SummaryCard.ets汇总卡片 - components/text/SectionTitle.ets区块标题 - components/bill/BillCard.ets账单卡片占位 - pages/HomeView.ets完整首页布局 - viewmodel/HomeViewModel.ets首页业务逻辑 - model/Bill.ets账单数据模型占位版 - utils/MoneyUtil.ets金额工具类总结本文完整介绍了首页布局与 SummaryCard 组件包含页面结构、组件封装、ViewModel 抽离、Mock 数据策略。通过本篇你可以设计首页一目了然的卡片化布局封装 SummaryCard 通用汇总组件使用 List ForEach 渲染账单列表抽离 HomeViewModel 落地 MVVM 架构处理 Scroll 与 List 嵌套冲突下一篇预告《封装 BillCard 通用组件》将完善 BillCard 组件支持分类图标、金额语义色、长按删除、滑动删除等交互。如果这篇文章对你有帮助欢迎点赞、收藏⭐、关注你的支持是我持续创作的动力相关资源本篇源码GitHub Tag v0.0.5ArkUI List 组件listArkUI Scroll 组件scrollForEach 性能指南foreach-performanceMVVM 架构实践mvvm-practice鸿蒙状态管理state-managementArkUI Stack 布局stack