宠物成长日记_鸿蒙开发实战
🐾 鸿蒙应用实战:从零开发「宠物成长日记」—— 记录喂食、洗澡、疫苗与体重
版本:HarmonyOS 6.1.0 (API 12) | 开发工具:DevEco Studio | 语言:ArkTS + ArkUI
项目源码:基于 Stage 模型,RDB 数据库存储,纯 ArkTS 实现
📖 一、前言
1.1 为什么做这个应用?
养宠物的人越来越多,但毛孩子的健康管理却常常靠「脑子记」—— 今天喂了吗?上次洗澡是什么时候?疫苗什么时候打的?体重有没有变化?这些琐碎信息拼在一起,才是宠物健康的全貌。
市面上的宠物 App 要么功能臃肿,要么需要注册账号。我想要的是一 个轻量、离线、打开即用的记录工具。于是就有了这个「宠物成长日记」。
1.2 技术选型
| 维度 | 选择 | 理由 |
|---|---|---|
| 开发语言 | ArkTS | HarmonyOS 原生声明式语言 |
| 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 的三层分离:
- Model 层:定义
PetRecord和PetInfo接口,以及RecordType枚举。纯数据契约,不包含任何 UI 逻辑。 - Database 层:封装所有 RDB 操作,对外暴露
async函数。上层只需调用getRecords()、addRecord()等,无需关心 SQL 细节。 - 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()—— 插入新记录,返回rowIdgetRecords(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 个字段:
- 日期:默认当天,可编辑
- 时间:默认当前时间,可编辑
- 详情:根据类型不同,placeholder 不同
- 备注:多行文本
点击「保存」调用 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() |
RdbPredicates |
string[] (列名) |
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 页面生命周期
| 页面 | 数据加载时机 | 刷新机制 |
|---|---|---|
| Index | onPageShow() |
每次返回首页自动刷新 |
| AddRecord | aboutToAppear() |
仅创建时加载 |
| RecordDetail | onPageShow() |
每次返回列表自动刷新 |
| PetInfo | aboutToAppear() |
仅创建时加载 |
🧪 七、构建与部署
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 真机运行
- 用 USB 连接鸿蒙设备,开启开发者模式
- DevEco Studio 选择设备后点击 ▶️ 运行
- 首次安装会自动创建数据库和默认宠物信息
🌟 八、优化方向与未来规划
当前版本已实现核心功能,后续可以从以下几个方向优化:
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 应用开发的完整流程:
- Stage 模型:理解了
UIAbility+ 页面路由的生命周期管理 - ArkUI 声明式 UI:掌握了
@State、@Builder、ForEach等核心装饰器 - relationalStore:熟练使用 RDB 进行增删改查,理解了
query与querySql的区别 - 页面导航:掌握了
router.pushUrl/router.back/router.getParams的传参方式 - 调试技巧:通过 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:(可根据需要托管)




更多推荐



所有评论(0)