🐾 鸿蒙应用实战:从零开发「宠物成长日记」—— 记录喂食、洗澡、疫苗与体重

版本:HarmonyOS 6.1.0 (API 12) | 开发工具:DevEco Studio | 语言:ArkTS + ArkUI
项目源码:基于 Stage 模型,RDB 数据库存储,纯 ArkTS 实现


📖 一、前言

1.1 为什么做这个应用?

养宠物的人越来越多,但毛孩子的健康管理却常常靠「脑子记」—— 今天喂了吗?上次洗澡是什么时候?疫苗什么时候打的?体重有没有变化?这些琐碎信息拼在一起,才是宠物健康的全貌。

市面上的宠物 App 要么功能臃肿,要么需要注册账号。我想要的是一 个轻量、离线、打开即用的记录工具。于是就有了这个「宠物成长日记」。

1.2 技术选型

维度选择理由
开发语言ArkTSHarmonyOS 原生声明式语言
UI 框架ArkUI组件化、响应式、一站式
数据存储relationalStore (RDB)结构化数据,SQL 查询灵活
项目模型Stage Model推荐的新一代 Ability 模型
API 版本6.1.0 (23)兼容当前主流鸿蒙设备
导航方式router轻量页面路由

🏗️ 二、项目架构设计

2.1 整体结构

entry/src/main/ets/
├── model/                  # 数据模型层
│   └── PetRecord.ets       # 记录类型枚举、接口定义
├── database/               # 数据持久层
│   └── DatabaseHelper.ets  # RDB 建表、CRUD 操作
├── pages/                  # UI 页面层
│   ├── Index.ets           # 首页仪表盘
│   ├── AddRecord.ets       # 添加记录
│   ├── RecordDetail.ets    # 记录列表
│   └── PetInfo.ets         # 宠物信息编辑
└── entryability/
    └── EntryAbility.ets    # 应用入口

2.2 三层架构说明

应用严格遵循 Model → Database → Pages 的三层分离:

  1. Model 层:定义 PetRecord 和 PetInfo 接口,以及 RecordType 枚举。纯数据契约,不包含任何 UI 逻辑。
  2. Database 层:封装所有 RDB 操作,对外暴露 async 函数。上层只需调用 getRecords()、addRecord() 等,无需关心 SQL 细节。
  3. Pages 层:纯 UI 渲染,通过 @State 驱动视图更新,数据从 Database 层获取。

这种分层的好处是:如果以后要迁移到云端存储,只需要替换 Database 层的实现,UI 层完全不动。


💾 三、数据模型与数据库设计

3.1 实体关系

应用只有两个实体,关系非常简单:

PetInfo (1) ── 拥有 ──▶ (N) PetRecord

一 只宠物对应多条成长记录。

3.2 PetRecord 表设计

CREATE TABLE IF NOT EXISTS pet_records (
    id           INTEGER PRIMARY KEY AUTOINCREMENT,
    type         TEXT NOT NULL,       -- 'feeding' | 'bathing' | 'vaccine' | 'weight'
    pet_name     TEXT NOT NULL,       -- 宠物名字(冗余存储,方便查询)
    date         TEXT NOT NULL,       -- 记录日期 YYYY-MM-DD
    time         TEXT NOT NULL,       -- 记录时间 HH:MM
    detail_value TEXT DEFAULT '',     -- 具体数值(体重/喂食量/疫苗名/洗澡方式)
    notes        TEXT DEFAULT '',     -- 备注文字
    created_at   TEXT NOT NULL        -- 系统创建时间戳
);

3.3 PetInfo 表设计

CREATE TABLE IF NOT EXISTS pet_info (
    id       INTEGER PRIMARY KEY AUTOINCREMENT,
    name     TEXT NOT NULL,           -- 宠物名字
    species  TEXT DEFAULT '',         -- 种类(猫/狗/兔子...)
    birthday TEXT DEFAULT '',         -- 生日
    avatar   TEXT DEFAULT ''          -- 头像路径(预留)
);

3.4 为什么选用 RDB 而非 Preferences?

HarmonyOS 提供两种主要本地存储方案:

方案适用场景本应用选择理由
Preferences键值对,少量配置不适合存储记录列表
relationalStore结构化数据,SQL 查询✅ 需要按类型筛选、排序、统计

本应用需要频繁做「按类型查询」「按日期排序」「统计今日次数」等操作,用 RDB 一 条 SQL 就能搞定,比 Preferences 手写遍历高效得多。

3.5 DatabaseHelper 的核心实现

所有数据库操作集中在一个模块中,核心函数包括:

  • getDatabase() —— 懒汉单例,首次调用时建表,并自动插入默认宠物信息
  • getPetInfo() / updatePetInfo() —— 宠物信息的查改
  • addRecord() —— 插入新记录,返回 rowId
  • getRecords(type?) —— 按类型筛选(可选),按日期时间降序
  • getTodayFeedingCount() —— 统计当日喂食次数,首页仪表盘使用
  • getLatestRecordByType() —— 获取某类型最新一 条记录

关键代码片段(简化):

export async function getDatabase(context: common.Context): Promise<relationalStore.RdbStore> {
    if (store) return store; // 单例
    const config: relationalStore.StoreConfig = {
        name: 'pet_diary.db',
        securityLevel: relationalStore.SecurityLevel.S1
    };
    store = await relationalStore.getRdbStore(context, config);
    await store.executeSql(CREATE_TABLE_RECORDS);
    await store.executeSql(CREATE_TABLE_PET_INFO);
    // 自动初始化默认宠物
    const result = await store.querySql(`SELECT COUNT(*) as cnt FROM pet_info`);
    if (result.rowCount > 0) {
        result.goToFirstRow();
        if (result.getLong(0) === 0) {
            await store.executeSql(
                `INSERT INTO pet_info (name, species, birthday) VALUES (?, ?, ?)`,
                ['我的宠物', '未知', '']
            );
        }
    }
    result.close();
    return store;
}

💡 注意:querySql() 接收 SQL 字符串 + 参数列表,返回 ResultSet;而 query() 接收的是 RdbPredicates 对象,两者不可混用。这是初学者容易踩的坑。


🎨 四、UI 设计与 ArkUI 实现

4.1 整体设计风格

应用采用 卡片式布局 + 柔和配色:

  • 主色调:#5B8DEF(清新蓝)
  • 功能色:🍽️ #FF9F43 / 🛁 #54A0FF / 💉 #5F27CD / ⚖️ #00D2D3
  • 背景色:#F5F6FA(浅灰)
  • 卡片:纯白 + 轻微投影

4.2 首页仪表盘(Index.ets)

首页是用户打开应用第一眼看到的内容,承载了四个核心功能:

顶部区域
  • 应用名称 + 宠物名
  • 蓝色渐变背景
统计卡片(4 个)
卡片数据来源刷新时机
🍽️ 今日喂食getTodayFeedingCount()每次页面显示
🛁 最近洗澡getLatestRecordByType('bathing')每次页面显示
💉 最近疫苗getLatestRecordByType('vaccine')每次页面显示
⚖️ 最新体重getLatestRecordByType('weight')每次页面显示

这里使用了 ArkUI 的 @Builder 装饰器来复用卡片 UI:

@Builder
statCard(icon: string, value: string, color: string) {
    Column() {
        Text(icon).fontSize(24)
        Text(value).fontSize(13).fontColor('#333')
    }
    .width('45%')
    .padding(12)
    .backgroundColor(Color.White)
    .borderRadius(10)
    .shadow({ radius: 3, color: 'rgba(0,0,0,0.05)' })
}
宠物信息卡片

点击可跳转到编辑页面。使用 Blank() 组件实现左右分布 —— 左侧「宠物信息」标题,右侧「编辑 >」入口。

快捷操作按钮

4 个按钮对应 4 种记录类型,点击跳转到 AddRecord 页面并携带 recordType 参数:

router.pushUrl({
    url: 'pages/AddRecord',
    params: { recordType: RecordType.FEEDING }
});

⚠️ router.pushUrl 已在较新版本标记为 deprecated,建议关注 @ohos.router 的新 API 迁移。

最近记录列表

通过 ForEach 渲染 recentRecords 数组,每个记录项显示:类型图标 + 标题 + 简述 + 日期时间。

4.3 添加记录页面(AddRecord.ets)

表单页面的核心挑战是 根据记录类型动态调整 UI。

方案:在 aboutToAppear 中读取 router.getParams() 获取类型,然后调用 updateUI() 更新标题和占位符:

updateUI(): void {
    switch (this.recordType) {
        case RecordType.FEEDING:
            this.title = '🍽️ 记录喂食';
            this.detailPlaceholder = '喂食内容(如:猫粮50g)';
            break;
        case RecordType.BATHING:
            this.title = '🛁 记录洗澡';
            this.detailPlaceholder = '洗澡方式(如:干洗/水洗)';
            break;
        // ... 省略类似代码
    }
}

表单包含 4 个字段:

  1. 日期:默认当天,可编辑
  2. 时间:默认当前时间,可编辑
  3. 详情:根据类型不同,placeholder 不同
  4. 备注:多行文本

点击「保存」调用 addRecord() 后 router.back() 返回首页。

4.4 记录列表页面(RecordDetail.ets)

这是数据展示的核心页面,包含两个关键设计:

筛选逻辑
  • 默认显示「全部」记录
  • 点击「切换」展开筛选芯片:全部 / 喂食 / 洗澡 / 疫苗 / 体重
  • 使用 @State activeFilter 控制当前筛选状态
applyFilter(): void {
    if (this.activeFilter === 'all') {
        this.filteredRecords = this.allRecords;
    } else {
        this.filteredRecords = this.allRecords.filter(r => r.type === this.activeFilter);
    }
}
记录卡片

每个卡片以 Column 包裹,包含:

  • 类型标签(带颜色标识)+ 日期时间(右上)
  • 详情值(如「5.2kg」)
  • 备注文字(可选)

4.5 宠物信息编辑页面(PetInfo.ets)

三个输入框分别编辑名字、种类、生日。保存后返回首页,首页的 onPageShow() 会自动刷新数据。

这里有一个细节:首页使用了 onPageShow() 生命周期而不是 aboutToAppear(),因为 aboutToAppear 只在页面创建时触发一 次,而 onPageShow 每次页面可见都会触发。这样从编辑页返回后,首页数据会自动更新。

onPageShow(): void {
    this.loadData(); // 每次回到首页都刷新数据
}

🔍 五、踩坑与解决方案

5.1 ❌ query() vs querySql() 的误解

问题:直接传 SQL 字符串给 store.query(),报错 Argument of type 'string' is not assignable to parameter of type 'RdbPredicates'。

原因:HarmonyOS 的 RdbStore 有两个不同的查询方法:

方法第一参数第二参数
query()RdbPredicatesstring[] (列名)
querySql()string (SQL)ValueType[] (参数)

解决:所有传入 SQL 字符串的地方改为 querySql()。

5.2 ❌ justifyContent / alignItems 误用于 Text

问题:给 Text 组件设置了 .justifyContent() 和 .alignItems()。

原因:这两个属性是容器组件(Row / Column)专有,Text 是基础组件,没有这些方法。

解决:在 Text 外面嵌套一 层 Row 作为容器:

Row() {
    Text('🍽️').fontSize(22)
}
.width(44).height(44)
.borderRadius(22)
.backgroundColor('#F0F4FF')
.justifyContent(FlexAlign.Center)   // ✅ Row 可以
.alignItems(VerticalAlign.Center)   // ✅ Row 的 alignItems 用 VerticalAlign

5.3 ❌ Row 的 alignItems 参数类型

问题:Row.alignItems(HorizontalAlign.Center) 报类型错误。

原因:Row 是水平方向容器,它的 alignItems 控制的是垂直方向的对齐,所以参数类型必须是 VerticalAlign,而不是 HorizontalAlign。

类比记忆:

容器alignItems 控制方向参数类型
Row垂直方向(交叉轴)VerticalAlign
Column水平方向(交叉轴)HorizontalAlign

5.4 ❌ 系统颜色资源不存在

问题:$r('sys.color.ohos_id_color_complementary') 报资源找不到。

原因:ohos_id_color_complementary 和 ohos_id_color_success 在目标 API 版本中不存在。

解决:统一使用十六进制色值字符串代替系统资源引用:

backgroundColor('#5F27CD')  // ✅ 用字符串
backgroundColor($r('sys.color.ohos_id_color_complementary'))  // ❌ 不存在

5.5 ⚠️ app_name 资源冲突

问题:编译警告 'app_name' conflict, first declared in AppScope。

原因:app_name 应该在 AppScope/resources/base/element/string.json 中定义,但在 entry 模块中又定义了一 次。

解决:删除 entry 模块中的 app_name 定义,只保留 AppScope 中的。


📊 六、数据流与生命周期

6.1 页面数据流

┌─────────┐   router.pushUrl(params)   ┌────────────┐
│  Index   │ ───────────────────────▶  │ AddRecord   │
│ (首页)   │                           │ (添加页)    │
└────┬────┘                           └──────┬─────┘
     │ onPageShow()                          │ save + back
     ▼                                       ▼
┌─────────┐                           ┌────────────┐
│ Database │ ◀──────────────────────  │ Database    │
│ 层 RDB   │                          │ 层 RDB      │
└─────────┘                           └────────────┘

所有页面通过 Database 层与 RDB 交互,页面之间不直接传递数据对象,只传递参数(如 recordType)。

6.2 页面生命周期

页面数据加载时机刷新机制
IndexonPageShow()每次返回首页自动刷新
AddRecordaboutToAppear()仅创建时加载
RecordDetailonPageShow()每次返回列表自动刷新
PetInfoaboutToAppear()仅创建时加载

🧪 七、构建与部署

7.1 构建命令

# 调试构建
hvigorw --mode module -p module=entry assembleHap --no-daemon

# 发布构建(需配置签名)
hvigorw --mode module -p module=entry assembleHap --no-daemon -p buildMode=release

7.2 签名配置

在 build-profile.json5 中配置 signingConfigs:

{
  "app": {
    "signingConfigs": [{
      "name": "default",
      "material": {
        "certpath": "路径/xxx.cer",
        "keypair": "路径/xxx.p7b",
        "profile": "路径/xxx.p7b"
      }
    }]
  }
}

未配置签名时构建会跳过签名步骤,生成的 HAP 只能在调试模式下运行。

7.3 DevEco Studio 真机运行

  1. 用 USB 连接鸿蒙设备,开启开发者模式
  2. DevEco Studio 选择设备后点击 ▶️ 运行
  3. 首次安装会自动创建数据库和默认宠物信息

🌟 八、优化方向与未来规划

当前版本已实现核心功能,后续可以从以下几个方向优化:

8.1 多宠物支持

  • pet_info 表增加多条记录
  • 首页增加宠物切换器
  • 所有查询增加 pet_name 筛选

8.2 图片记录

  • 使用 @ohos.multimedia.camera 拍照
  • 使用 @ohos.file.picker 从相册选择
  • 图片存为本地文件路径,RDB 只存路径

8.3 数据导出

  • 导出 JSON / CSV 到本地
  • 使用 @ohos.file.fs 写入文件
  • 通过 @ohos.share 分享

8.4 图表统计

  • 使用 Canvas 或第三方图表库
  • 体重变化曲线
  • 喂食频率热力图

8.5 提醒通知

  • 定时提醒喂食/洗澡/疫苗
  • 使用 @ohos.backgroundTaskManager 后台任务
  • 使用 @ohos.notification 推送通知

📝 九、总结

9.1 项目收获

通过这个项目,完整实践了 HarmonyOS 应用开发的完整流程:

  1. Stage 模型:理解了 UIAbility + 页面路由的生命周期管理
  2. ArkUI 声明式 UI:掌握了 @State、@Builder、ForEach 等核心装饰器
  3. relationalStore:熟练使用 RDB 进行增删改查,理解了 query 与 querySql 的区别
  4. 页面导航:掌握了 router.pushUrl / router.back / router.getParams 的传参方式
  5. 调试技巧:通过 hvigor 编译错误定位问题,快速修复类型和 API 使用错误

9.2 数据

  • 代码量:约 650 行 ArkTS
  • 页面数:4 个主页面
  • 数据表:2 张
  • API 调用:8 个 Database 操作函数
  • 编译时间:约 8 秒(含预热)

9.3 感受

HarmonyOS 的 ArkTS + ArkUI 组合在开发体验上非常接近 SwiftUI 和 Jetpack Compose,声明式语法上手快,组件丰富。但 API 文档的完整性和社区资源相比 Android 仍有差距,部分系统资源在不同 API 版本中存在差异,需要实际编译验证。

不过,鸿蒙生态正在快速完善。从 API 12 开始,@kit.* 模块化导入规范了 API 组织方式,开发体验有了明显提升。对于个人开发者和小团队来说,开发一个纯粹、好用的鸿蒙原生应用,现在正当时。


项目名称:宠物成长日记(Pet Growth Diary)
开发平台:HarmonyOS 6.1.0 (API 12)
源码结构:Stage Model + ArkTS + RDB
功能特性:喂食记录、洗澡记录、疫苗记录、体重记录、宠物信息管理
GitHub:(可根据需要托管)


在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Logo

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

更多推荐