在这里插入图片描述

手把手教你用 ArkTS 构建 HarmonyOS 记账应用

一、前言

2024 年华为正式发布了 HarmonyOS NEXT 系统,"纯血鸿蒙"时代已经到来。对于开发者,使用 ArkTS + ArkUI 构建原生鸿蒙应用正在成为主流。

最近我基于 HarmonyOS NEXT(API 26)开发了一款名为**“老奶奶记账”**的小应用。它虽简单,却涵盖了 HarmonyOS 开发的核心知识点:Ability 生命周期、RDB 数据库、声明式 UI、弹窗交互等。本文将从项目结构、数据层、UI 层、交互逻辑四个方面深入剖析,为正在学习鸿蒙开发的朋友提供一份实战参考。


二、项目概览

项目 内容
应用名称 老奶奶记账
BundleName com.example.project
目标 SDK 26.0.0(HarmonyOS NEXT)
数据存储 RDB(关系型数据库)
页面路由 单页面(pages/Index)

核心功能:

  • 收支记录:支持支出 / 收入两种类型
  • 分类管理:8种支出分类 + 5种收入分类,配有 Emoji 图标
  • 金额汇总:总收入、总支出、结余一目了然
  • 记录 CRUD:添加、修改、删除记账记录
  • 空状态提示:无记录时引导用户操作
  • 数据持久化:本地 RDB 数据库存储

三、项目结构

project/
├── AppScope/                         # 应用级配置
│   ├── app.json5                     # 应用全局配置
│   └── resources/                    # 应用图标和名称
│
├── entry/src/main/ets/
│   ├── database/
│   │   └── DatabaseHelper.ets        # 数据库操作封装(~207 行)
│   ├── entryability/
│   │   └── EntryAbility.ets          # Ability 生命周期(~49 行)
│   ├── entrybackupability/
│   │   └── EntryBackupAbility.ets    # 系统备份能力
│   └── pages/
│       └── Index.ets                 # 主页面 UI + 逻辑(~464 行)
│
├── build-profile.json5               # 构建配置(targetSdk 26.0.0)
└── oh-package.json5                  # 包管理

应用模块配置了两个组件:EntryAbility(主入口,桌面图标启动)和 EntryBackupAbility(备份扩展,支持系统级云备份)。这是一大亮点——记账数据可自动参与系统备份,用户换机时无需担心数据丢失。


四、Ability 生命周期与初始化

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // 跟随系统颜色模式
    this.context.getApplicationContext()
      .setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
    // 初始化数据库
    DatabaseHelper.setContext(this.context);
    DatabaseHelper.init();
  }
  onWindowStageCreate(windowStage: window.WindowStage): void {
    windowStage.loadContent('pages/Index', (err) => { /* 加载主页 */ });
  }
}

关键点:

  1. onCreate 中初始化数据库:传入 UIAbilityContext,数据层在 Ability 创建时准备就绪,避免页面加载后的等待
  2. setColorMode(COLOR_MODE_NOT_SET):跟随系统主题,尊重用户偏好
  3. onWindowStageCreate 加载页面:路径 'pages/Index'main_pages.json 配置对应

五、数据层:DatabaseHelper 详解

数据库使用 @ohos.data.relationalStore,它封装了 SQLite 并提供异步 API。

5.1 数据模型

export class Record {
  id: number;
  amount: number;    // 支出为负数,收入为正数
  type: number;      // 0=支出,1=收入
  category: string;  // 分类
  note: string;      // 备注
  createTime: string; // "YYYY-MM-DD HH:mm"
}

一个设计巧思:金额用有符号数,支出为负、收入为正,统计时只需 SUM(amount) 即可得到结余,无需额外逻辑。

5.2 数据库初始化

static async init(): Promise<void> {
  const config: relationalStore.StoreConfig = {
    name: 'accountbook.db',
    securityLevel: relationalStore.SecurityLevel.S1,  // 最低安全等级
    encrypt: false,
  };
  DatabaseHelper.rdbStore = await relationalStore.getRdbStore(ctx, config);
  await DatabaseHelper.createTable(); // CREATE TABLE IF NOT EXISTS
}

S1 安全等级适合不包含敏感信息的应用场景,记账数据虽涉及金额,但不属于严格意义上的敏感信息。

5.3 CRUD 操作

方法 操作 说明
insert() INSERT 插入记录
update() UPDATE 按 ID 更新
delete() DELETE 按 ID 删除
queryAll() SELECT 时间倒序查询全部
queryById() SELECT 按 ID 查询单条
sumByType() SUM 按类型统计金额总和

值得学习的编码细节:

① 懒加载模式——所有数据操作前调用 ensureStore()

static async ensureStore(): Promise<relationalStore.RdbStore | null> {
  if (DatabaseHelper.rdbStore === null) await DatabaseHelper.init();
  return DatabaseHelper.rdbStore;
}

防御性编程,确保数据操作时数据库已就绪。

② 正确的资源释放——ResultSet 在 finally 中关闭:

try { /* 读取数据 */ }
finally { await resultSet.close(); }

即使读取过程抛出异常,数据库游标也能被正确释放,防止内存泄漏。

③ 游标遍历——先 goToFirstRow() 判断是否有数据,再循环 goToNextRow() 逐行读取,符合 HarmonyOS 异步语义。


六、UI 层:ArkUI 声明式构建

6.1 整体布局

Stack (Alignment.Bottom)              ← 根容器,支持弹窗叠层
├── Column                             ← 主内容区
│   ├── 标题 "老奶奶记账"
│   ├── 统计卡片 (收入 / 支出 / 结余)
│   ├── List (记录列表 / 空状态)
│   └── Button("➕ 记一笔")            ← 底部悬浮按钮
└── [Dialog - 条件渲染]                ← 半透明遮罩 + 底部弹窗

6.2 响应式数据驱动

@State records: Record[] = [];
@State incomeTotal: number = 0;
@State expenseTotal: number = 0;
@State balance: number = 0;

@State 装饰的变量是响应式的——变化时 UI 自动重渲染。每次增删改操作后调用 refreshAll() 全量刷新数据:

async refreshAll(): Promise<void> {
  this.records = await DatabaseHelper.queryAll();
  this.incomeTotal = await DatabaseHelper.sumByType(1);
  this.expenseTotal = await DatabaseHelper.sumByType(0);
  this.balance = this.incomeTotal - this.expenseTotal;
}

6.3 分类与 Emoji

const EXPENSE_CATS: Category[] = [
  { name: '餐饮', emoji: String.fromCodePoint(0x1F35C) },  // 🍜
  { name: '交通', emoji: String.fromCodePoint(0x1F68C) },  // 🚌
  { name: '购物', emoji: String.fromCodePoint(0x1F6D2) },  // 🛒
  { name: '日用', emoji: String.fromCodePoint(0x1F9FB) },  // 🧻
  { name: '水电', emoji: String.fromCodePoint(0x1F4A1) },  // 💡
  { name: '娱乐', emoji: String.fromCodePoint(0x1F3AC) },  // 🎬
  { name: '医疗', emoji: String.fromCodePoint(0x1F48A) },  // 💊
  { name: '其他', emoji: String.fromCodePoint(0x1F4E6) },  // 📦
];

为什么用 String.fromCodePoint()?Emoji 在不同编辑器、编码环境中可能出现乱码,通过 Unicode Code Point 生成可以保证显示一致性。

每条记录左侧显示对应 Emoji,代替传统文字/图片图标,让界面更生动有趣。

6.4 空状态设计

if (this.records.length === 0) {
  ListItem() {
    Column() {
      Text(String.fromCodePoint(0x1F4DD)).fontSize(72)  // 📝
      Text('还没有记录')
      Text('点下面大按钮开始记账吧')
    }
  }
}

引导式空状态不仅告诉用户"没有数据",还提示下一步操作,这是优秀用户体验设计的关键一环。

6.5 记录列表项

每条记录展示为:

┌────────────────────────────────────────────┐
│  🍜 餐饮                      -¥35.00     │
│  2024-12-25 12:30                         │
│  午餐                                       │
├────────────────────────────────────────────┤
│  [ 修改 ]                [ 删除 ]          │
└────────────────────────────────────────────┘

金额颜色按类型区分:支出红色#C62828),收入绿色#2E7D32),符合直觉认知。


七、Dialog 交互:新增与编辑二合一

7.1 状态驱动弹窗

@State showDialog: boolean = false;
@State isEditMode: boolean = false;
@State editTargetId: number = 0;
@State dialogAmount: string = '';
@State dialogNote: string = '';
@State dialogType: number = 0;
@State dialogCategory: string = EXPENSE_CATS[0].name;

showDialogtrue 时,条件渲染遮罩层和底部 Dialog:

if (this.showDialog) {
  // 半透明遮罩,点击关闭
  Column() { Blank().backgroundColor(0x80000000).onClick(() => this.closeDialog()); }
  // 底部弹窗内容
  Column() { /* 标题 + 类型选择 + 分类 + 金额 + 备注 + 按钮 */ }
    .alignSelf(ItemAlign.End)
    .borderRadius({ topLeft: 20, topRight: 20 })
}

7.2 新增与编辑复用

private openAddRecord(): void {      // 清空所有字段为默认值
  this.isEditMode = false; this.editTargetId = 0;
  this.dialogAmount = ''; this.dialogNote = '';
  this.dialogType = 0; this.dialogCategory = EXPENSE_CATS[0].name;
  this.showDialog = true;
}
private openEditRecord(r: Record): void {  // 填充现有值
  this.isEditMode = true; this.editTargetId = r.id;
  this.dialogAmount = Math.abs(r.amount).toFixed(2);
  this.dialogNote = r.note; this.dialogType = r.type;
  this.dialogCategory = r.category; this.showDialog = true;
}

保存时根据 isEditMode 区分 insert 或 update。修改时保留原始 createTime——记账场景下的合理行为,修改金额不应该改变记录的创建时间。

7.3 类型与分类联动

private selectDialogType(t: number): void {
  this.dialogType = t;
  this.dialogCategory = t === 0 ? EXPENSE_CATS[0].name : INCOME_CATS[0].name;
}

切换收支类型时自动重置到对应分类的第一个选项,减少用户操作步骤。


八、工具函数

金额格式化: 超过 10000 显示为 X.XX万,否则保留两位小数,已在为大额场景做准备。

function formatMoney(n: number): string {
  if (Math.abs(n) >= 10000) return (n / 10000).toFixed(2) + '万';
  return n.toFixed(2);
}

时间格式化: 生成 YYYY-MM-DD HH:mm 格式字符串,支持直接字典序排序(orderByDesc)。


九、资源管理

9.1 颜色系统

主色调选择了温暖的橙色 #E65100,配合暖白色 #FFF7E6 背景,营造温馨亲切的视觉感受:

{ "primary": "#E65100", "income_color": "#2E7D32",
  "expense_color": "#C62828", "app_background": "#FFF7E6" }

9.2 深色模式

资源文件按限定词目录组织,resources/dark/element/color.json 定义深色模式颜色。系统自动根据主题切换资源,代码无需额外判断。

9.3 字符串资源化

所有用户可见文本定义在 string.json,代码中通过 $r('app.string.xxx') 引用。这种做法的优势:方便国际化、统一管理、修改无需搜索代码。


十、开发心得

  1. 声明式 UI + 响应式状态:ArkUI 的 @State 类似 SwiftUI/Compose,开发者只需关注数据变化,无需手动操作 UI

  2. 异步无处不在:从数据库操作到窗口加载,HarmonyOS API 大量使用 Promise,async/await 是标配写法

  3. 资源文件提前规划:颜色、文案、字号通过资源文件管理,保证一致性,方便团队协作

  4. 数据库安全等级按需选择:S1 够用就不选更高等级,避免影响数据跨设备迁移

  5. 系统备份能力是亮点EntryBackupAbility 实现系统级云备份,记账数据随账号自动恢复


十一、可扩展方向

  • 数据图表(@kit.ArkCharts 月度趋势图/分类饼图)
  • 多账本支持
  • 搜索与筛选(日期范围、分类、关键词)
  • 数据导出(CSV/Excel)
  • 生物识别锁(指纹/面部)
  • 日历视图
  • 预算提醒

十二、总结

"老奶奶记账"虽然只有三个核心文件(EntryAbility.etsDatabaseHelper.etsIndex.ets),但完整展示了 HarmonyOS 应用开发的全链路:Ability 生命周期 → RDB 数据库操作 → ArkUI 声明式 UI → 资源管理 → 系统备份集成。

项目代码量约 720 行,覆盖了开发中的核心知识点和最佳实践——ResultSet 正确关闭、懒加载模式、防御性编程、条件渲染交互、深色模式适配等。

如果你正在学习 HarmonyOS 开发,建议从这个小项目开始,逐步理解这些核心概念,再向更复杂的多页面、多模块应用进发。


项目环境: HarmonyOS NEXT(API 26),DevEco Studio
源码路径: entry/src/main/ets/ 目录下全部 ArkTS 文件
本文发布于 2025 年,API 版本可能随官方更新,请以最新文档为准。

Logo

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

更多推荐