恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

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

  • 首页
  • 资讯中心
  • /
  • HarmonyOS NEXT 企业级记账APP:首页布局与 SummaryCard 设计

相关资讯

多商户商城和单商户商城有什么区别? 2026/8/18 15:35:37
行空板OpenCV目标追踪实战:从特征匹配到单应性矩阵优化 2026/8/2 17:49:55
标准化SaaS系统和源码定制有什么区别? 2026/8/2 17:49:56

最新资讯

elli 安全加固清单:超时控制、请求大小限制与异常防护的 8 个最佳实践
Milvus 迁移到 RAG:先守住索引和评测口径
lainTSX 部署指南:如何将 WebGL 游戏免费发布到 Vercel 或 Netlify
深入Toto-2.0-2.5B-FT-NPU核心架构:u-μP缩放decoder-only patched transformer原理全解
menyoki高效工作流:与slop、xclip组合的5个实战技巧
NCM转MP3免安装教程:两下拖拽,三分钟搞定网易云音乐格式转换

今日推荐

数据缺失处理:从MCAR、MAR到MNAR的机制解析与多重插补实践
MAGS-SLAM:多智能体协同3D高斯泼溅SLAM系统解析
LLM智能体记忆管理:基于关键词门控的混合激活机制CAMeR详解

本周热门

【文章复现】非线性值迭代自适应动态规划(ADP):离散时间非线性系统的策略迭代自适应动态规划算法研究附Matlab代码
【双层规划,节点出清价,绿证交易,CVaR方法】两级电力市场环境下计及风险的省间交易商最优购电模型附Matlab代码
隐式mpc+自适应mpc+时变mpc,线性时变模型预测控制附Simulink仿真

本月精选

如何用DamaiHelper实现演唱会门票的智能自动化抢购:完整技术解决方案指南
第4篇:59 倍性能差距的索引瓶颈定位——一次教科书级的全表扫描调优
终极歌词批量下载神器:5分钟解决离线音乐库歌词同步难题

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

发布时间:2026/8/18 15:36:38
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

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号