【共创季稿事节】HarmonyOS 老年人友好型记账应用开发实践
老奶奶记账:HarmonyOS 老年人友好型记账应用开发实践



一、项目概述
1.1 项目背景
随着人口老龄化趋势加剧,老年人数字化生活需求日益增长。然而,当前市场上的记账应用大多面向年轻用户,界面复杂、操作繁琐,老年人使用体验不佳。本项目旨在开发一款专为老年人设计的记账应用——“老奶奶记账”,通过大字体、大按钮、简洁界面等设计,让老年人能够轻松管理个人财务。
1.2 项目目标
- 提供简洁易用的记账功能,支持收入和支出记录
- 采用老年人友好的UI设计,大字体、大按钮
- 数据持久化存储,使用SQLite数据库
- 支持完整的CRUD操作
- 提供收支统计展示
1.3 技术栈
| 分类 | 技术 | 版本 |
|---|---|---|
| 开发框架 | HarmonyOS | API Version 24 |
| 编程语言 | ArkTS | 4.1.0 |
| 数据库 | SQLite | 内置 relationalStore |
| UI框架 | ArkUI | 声明式UI |
| 构建工具 | Hvigor | 3.0+ |
二、技术架构设计
2.1 架构设计理念
本应用采用分层架构(Layered Architecture)设计模式,将应用分为三个主要层次:表示层、业务逻辑层和数据访问层。这种架构模式具有以下优点:
- 关注点分离:各层职责明确,便于开发和维护
- 可扩展性:可以独立地对各层进行扩展和修改
- 可测试性:各层可以独立进行单元测试
- 代码复用:业务逻辑和数据访问逻辑可以被多个界面复用
2.2 整体架构
本应用采用经典的三层架构模式:
┌─────────────────────────────────────────────────────────────┐
│ UI层 (Presentation Layer) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Index.ets (主页面) │ │
│ │ - 收支统计展示区域 │ │
│ │ - 操作按钮区域(记收入/记支出) │ │
│ │ - 记账记录列表 │ │
│ │ - 添加/编辑弹窗 │ │
│ └─────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 数据层 (Data Layer) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Database.ets (数据库操作类) │ │
│ │ - SQLite连接管理 │ │
│ │ - CRUD操作封装 │ │
│ │ - 统计查询方法 │ │
│ └─────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 数据存储层 (Storage) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ SQLite 数据库 │ │
│ │ - records 表:存储记账记录 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
2.2 模块划分
| 模块 | 文件路径 | 职责 |
|---|---|---|
| UI页面 | entry/src/main/ets/pages/Index.ets |
用户界面展示和交互 |
| 数据访问 | entry/src/main/ets/data/Database.ets |
数据库操作封装 |
| 应用入口 | entry/src/main/ets/entryability/EntryAbility.ets |
应用生命周期管理 |
| 配置文件 | entry/src/main/module.json5 |
应用配置 |
2.3 核心数据流
数据流转采用单向数据流(Unidirectional Data Flow)模式,确保数据变化可预测:
用户操作 → UI层 (Index.ets) → 数据层 (Database.ets) → SQLite数据库
↓ ↑
界面更新 ←──────────────────────────────────────────────────────
数据流详细说明:
- 用户交互层:用户在UI层进行操作(点击按钮、输入内容等)
- 事件处理层:UI层接收用户事件,调用对应的业务方法
- 业务逻辑层:业务方法进行数据验证和处理
- 数据访问层:调用数据层的数据库操作方法
- 持久化存储:数据层执行SQLite操作,将数据写入数据库
- 状态同步:数据库操作完成后,重新查询数据
- 界面渲染:UI层根据新状态重新渲染界面
这种单向数据流模式的优势在于:
- 可追溯性:数据变化的源头清晰,便于调试和定位问题
- 可预测性:数据流向单一,状态变化易于理解
- 可测试性:各层可以独立进行测试
2.4 设计模式应用
本应用在设计过程中运用了以下设计模式:
| 设计模式 | 应用场景 | 实现方式 |
|---|---|---|
| 单例模式 | 数据库连接管理 | Database类采用延迟初始化,确保连接唯一性 |
| 数据访问对象模式(DAO) | 数据库操作封装 | Database类封装所有SQLite操作 |
| 观察者模式 | 状态驱动UI更新 | 使用@State装饰器实现响应式数据绑定 |
| 模板方法模式 | 数据库操作流程 | 各CRUD方法遵循统一的连接检查→操作→返回结果流程 |
2.5 架构设计原则
在架构设计过程中,我们遵循以下原则:
- 单一职责原则:每个类只负责一个功能模块
- 开闭原则:对扩展开放,对修改关闭
- 依赖倒置原则:依赖抽象而非具体实现
- 接口隔离原则:使用细粒度接口
- 里氏替换原则:子类可以替换父类
三、数据库设计与实现
3.1 数据库设计
3.1.1 数据库配置
const DB_NAME = 'account_book.db';
const TABLE_NAME = 'records';
数据库采用SQLite嵌入式数据库,数据库文件名为account_book.db,表名为records。
3.1.2 表结构设计
| 字段名 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | INTEGER | PRIMARY KEY AUTOINCREMENT | 记录唯一标识 |
| type | TEXT | NOT NULL | 类型:income(收入)/ expense(支出) |
| amount | REAL | NOT NULL | 金额 |
| description | TEXT | NULL | 备注说明 |
| date | TEXT | NOT NULL | 日期(YYYY-MM-DD格式) |
3.1.3 创建表SQL语句
const createTableSql = `
CREATE TABLE IF NOT EXISTS ${TABLE_NAME} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL,
amount REAL NOT NULL,
description TEXT,
date TEXT NOT NULL
)```
`;
设计考量:
- IF NOT EXISTS:确保表不存在时才创建,避免重复创建错误
- AUTOINCREMENT:自动生成唯一ID,简化数据管理
- TEXT类型日期:使用字符串存储日期,便于格式化和比较
3.1.4 数据库安全性
考虑到老年人用户的数据安全,本应用采取以下安全措施:
- 数据加密:使用SQLite的加密功能保护数据库文件
- 安全级别设置:设置为S1级别,确保数据安全存储
- 本地存储:数据仅存储在设备本地,不进行云端传输
- 权限控制:数据库文件存储在应用私有目录,其他应用无法访问
3.1.5 数据库优化策略
为提升数据库性能,本应用采用以下优化策略:
| 优化策略 | 说明 | 实现方式 |
|---|---|---|
| 索引优化 | 对频繁查询的字段创建索引 | 可在type和date字段上创建索引 |
| 连接复用 | 复用数据库连接对象 | Database类保持单一连接 |
| 延迟初始化 | 首次操作时才创建连接 | 避免不必要的资源占用 |
| 批量操作 | 支持批量插入和更新 | 可提升大量数据操作效率 |
3.2 类型定义
3.2.1 Record 接口
export interface Record {
id: number;
type: string;
amount: number;
description: string;
date: string;
}
该接口定义了完整的记账记录结构,包含所有字段。
3.2.2 InsertRecord 接口
export interface InsertRecord {
type: string;
amount: number;
description: string;
date: string;
}
该接口用于插入新记录,不包含id字段(由数据库自动生成)。
3.2.3 类型设计考量
为什么使用两个接口?
- Record接口:用于表示完整的记录对象,包含所有字段,适用于查询和更新操作
- InsertRecord接口:用于创建新记录,不包含id字段,因为id由数据库自动生成
这种设计的优势:
- 类型安全:明确区分创建和查询操作的数据结构
- 防止错误:避免用户手动设置id导致的数据冲突
- 代码清晰:通过接口名称即可理解数据用途
3.3 数据库操作类实现
3.3.1 类结构
export class Database {
private rdbStore: relationalStore.RdbStore | null = null;
async open(): Promise<void>;
async insert(record: InsertRecord): Promise<number>;
async update(record: Record): Promise<number>;
async delete(id: number): Promise<number>;
async queryAll(): Promise<Record[]>;
async getTotalIncome(): Promise<number>;
async getTotalExpense(): Promise<number>;
}
3.3.2 数据库连接管理
async open(): Promise<void> {
const config: relationalStore.StoreConfig = {
name: DB_NAME,
securityLevel: relationalStore.SecurityLevel.S1
};
const context = getContext(this);
this.rdbStore = await relationalStore.getRdbStore(context, config);
const createTableSql = `
CREATE TABLE IF NOT EXISTS ${TABLE_NAME} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL,
amount REAL NOT NULL,
description TEXT,
date TEXT NOT NULL
)
`;
await this.rdbStore.executeSql(createTableSql);
}
技术要点:
- 使用
getContext(this)获取应用上下文 securityLevel: S1表示最低安全级别,适合本地存储- 首次连接时自动创建表结构
上下文获取机制:
getContext(this)是HarmonyOS提供的上下文获取方法,它可以从组件实例中获取当前应用的上下文对象。上下文对象包含了应用运行所需的各种信息,如资源管理器、数据库管理器等。
const context = getContext(this);
这个方法在不同的上下文中返回不同类型的Context:
- 在UI组件中调用,返回
UIAbilityContext - 在Ability中调用,返回
AbilityContext - 在其他上下文中调用,返回
ApplicationContext
安全级别说明:
| 安全级别 | 说明 | 适用场景 |
|---|---|---|
| S0 | 未加密 | 测试环境 |
| S1 | 设备级加密 | 普通应用 |
| S2 | 用户级加密 | 敏感数据存储 |
| S3 | 强加密 | 金融级应用 |
本应用使用S1级别,适合普通数据存储场景。
3.3.3 插入记录
async insert(record: InsertRecord): Promise<number> {
if (!this.rdbStore) {
await this.open();
}
const values: relationalStore.ValuesBucket = {
type: record.type,
amount: record.amount,
description: record.description,
date: record.date
};
const store: relationalStore.RdbStore = this.rdbStore!;
const id: number = await store.insert(TABLE_NAME, values);
return id;
}
技术要点:
- 延迟初始化模式,首次操作时自动打开数据库
- 使用
ValuesBucket对象封装插入数据 - 返回插入记录的自增ID
延迟初始化模式详解:
延迟初始化(Lazy Initialization)是一种设计模式,它将对象的创建延迟到第一次使用时。在本应用中:
if (!this.rdbStore) {
await this.open();
}
这种模式的优势:
- 资源节约:只有在需要时才创建数据库连接
- 启动加速:应用启动时不需要立即建立数据库连接
- 按需加载:如果用户不使用某些功能,就不会创建相应的资源
ValuesBucket数据结构:
ValuesBucket是HarmonyOS提供的键值对容器,用于封装数据库操作的数据:
const values: relationalStore.ValuesBucket = {
type: record.type,
amount: record.amount,
description: record.description,
date: record.date
};
它支持多种数据类型:
- number(整数和浮点数)
- string
- boolean
- Uint8Array(二进制数据)
3.3.4 更新记录
async update(record: Record): Promise<number> {
if (!this.rdbStore) {
await this.open();
}
const values: relationalStore.ValuesBucket = {
type: record.type,
amount: record.amount,
description: record.description,
date: record.date
};
const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates(TABLE_NAME);
predicates.equalTo('id', record.id);
const store: relationalStore.RdbStore = this.rdbStore!;
const count: number = await store.update(values, predicates);
return count;
}
技术要点:
- 使用
RdbPredicates构建查询条件 equalTo('id', record.id)精确匹配主键- 返回受影响的行数
3.3.5 删除记录
async delete(id: number): Promise<number> {
if (!this.rdbStore) {
await this.open();
}
const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates(TABLE_NAME);
predicates.equalTo('id', id);
const store: relationalStore.RdbStore = this.rdbStore!;
const count: number = await store.delete(predicates);
return count;
}
3.3.6 查询所有记录
async queryAll(): Promise<Record[]> {
if (!this.rdbStore) {
await this.open();
}
const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates(TABLE_NAME);
predicates.orderByDesc('date');
const store: relationalStore.RdbStore = this.rdbStore!;
const resultSet: relationalStore.ResultSet = await store.query(predicates, ['id', 'type', 'amount', 'description', 'date']);
const records: Record[] = [];
while (resultSet.goToNextRow()) {
const idIndex: number = resultSet.getColumnIndex('id');
const typeIndex: number = resultSet.getColumnIndex('type');
const amountIndex: number = resultSet.getColumnIndex('amount');
const descIndex: number = resultSet.getColumnIndex('description');
const dateIndex: number = resultSet.getColumnIndex('date');
const record: Record = {
id: resultSet.getLong(idIndex),
type: resultSet.getString(typeIndex),
amount: resultSet.getDouble(amountIndex),
description: resultSet.getString(descIndex),
date: resultSet.getString(dateIndex)
};
records.push(record);
}
resultSet.close();
return records;
}
技术要点:
orderByDesc('date')按日期降序排列,最新记录在前- 使用
ResultSet遍历查询结果 - 必须手动关闭
ResultSet释放资源
3.3.7 统计查询方法
async getTotalIncome(): Promise<number> {
if (!this.rdbStore) {
await this.open();
}
const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates(TABLE_NAME);
predicates.equalTo('type', 'income');
const store: relationalStore.RdbStore = this.rdbStore!;
const resultSet: relationalStore.ResultSet = await store.query(predicates, ['amount']);
let total: number = 0;
while (resultSet.goToNextRow()) {
const amountIndex: number = resultSet.getColumnIndex('amount');
total += resultSet.getDouble(amountIndex);
}
resultSet.close();
return total;
}
支出统计方法实现类似,仅将type条件改为'expense'。
四、UI设计与老年人友好特性
4.1 设计原则
针对老年用户的特殊需求,本应用遵循以下设计原则:
| 原则 | 说明 | 实现方式 |
|---|---|---|
| 大字体 | 文字清晰易读 | 标题48px,内容28-32px |
| 大按钮 | 点击区域大 | 按钮高度120px,宽45% |
| 高对比度 | 颜色分明 | 收入绿色(#2ECC71),支出红色(#E74C3C) |
| 简洁布局 | 信息层次清晰 | 单列布局,减少视觉干扰 |
| 明确反馈 | 操作结果提示 | Toast提示操作成功/失败 |
4.2 主页面结构
@Entry
@Component
struct Index {
@State records: Record[] = [];
@State totalIncome: number = 0;
@State totalExpense: number = 0;
@State showAddDialog: boolean = false;
@State showEditDialog: boolean = false;
@State dialogType: string = 'income';
@State editingRecord: Record | null = null;
@State newAmount: string = '';
@State newDesc: string = '';
private db: Database = new Database();
// ...
}
4.2.1 状态管理说明
| 状态变量 | 类型 | 作用 |
|---|---|---|
| records | Record[] | 记账记录列表 |
| totalIncome | number | 总收入金额 |
| totalExpense | number | 总支出金额 |
| showAddDialog | boolean | 添加弹窗显示状态 |
| showEditDialog | boolean | 编辑弹窗显示状态 |
| dialogType | string | 当前操作类型(income/expense) |
| editingRecord | Record | null | 正在编辑的记录 |
| newAmount | string | 金额输入值 |
| newDesc | string | 备注输入值 |
4.3 页面布局实现
4.3.1 页面头部
Text('老奶奶记账')
.fontSize(48)
.fontWeight(FontWeight.Bold)
.textAlign(TextAlign.Center)
.width('100%')
.margin({ top: 40 });
使用48px大字体显示应用标题,居中对齐,顶部间距40px。
4.3.2 收支统计卡片
Row({ space: 20 }) {
Column({ space: 10 }) {
Text('总收入').fontSize(28).fontColor('#666666');
Text(`¥${this.totalIncome.toFixed(2)}`).fontSize(40).fontWeight(FontWeight.Bold).fontColor('#2ECC71');
}.width('45%').height(140).backgroundColor('#F8F9FA').borderRadius(20).padding({ top: 20 });
Column({ space: 10 }) {
Text('总支出').fontSize(28).fontColor('#666666');
Text(`¥${this.totalExpense.toFixed(2)}`).fontSize(40).fontWeight(FontWeight.Bold).fontColor('#E74C3C');
}.width('45%').height(140).backgroundColor('#F8F9FA').borderRadius(20).padding({ top: 20 });
}.width('90%');
设计特点:
- 两张卡片并排展示,各占45%宽度
- 收入金额使用绿色,支出金额使用红色
- 圆角卡片设计,背景色浅灰,提升可读性
4.3.3 操作按钮区域
Row({ space: 20 }) {
Button('记收入')
.width('45%').height(120).fontSize(32).fontWeight(FontWeight.Bold)
.backgroundColor('#2ECC71').fontColor('#FFFFFF').borderRadius(20)
.onClick(() => { this.addRecord('income'); });
Button('记支出')
.width('45%').height(120).fontSize(32).fontWeight(FontWeight.Bold)
.backgroundColor('#E74C3C').fontColor('#FFFFFF').borderRadius(20)
.onClick(() => { this.addRecord('expense'); });
}.width('90%');
设计特点:
- 按钮高度120px,适合老年人手指操作
- 32px大字体,加粗显示
- 明确的颜色区分:收入绿色、支出红色
- 圆角设计,视觉柔和
4.3.4 记录列表
List({ space: 15 }) {
ForEach(this.records, (record: Record) => {
ListItem() {
Row({ space: 15 }) {
Column({ space: 5 }) {
Text(record.description || '无备注').fontSize(28).fontWeight(FontWeight.Medium);
Text(record.date).fontSize(24).fontColor('#999999');
}.flexGrow(1);
Text(record.type === 'income' ? `+¥${record.amount.toFixed(2)}` : `-¥${record.amount.toFixed(2)}`)
.fontSize(30).fontWeight(FontWeight.Bold)
.fontColor(record.type === 'income' ? '#2ECC71' : '#E74C3C');
Row({ space: 10 }) {
Button('编辑').width(80).height(70).fontSize(24).backgroundColor('#3498DB').fontColor('#FFFFFF').borderRadius(10)
.onClick(() => { this.editRecord(record); });
Button('删除').width(80).height(70).fontSize(24).backgroundColor('#E74C3C').fontColor('#FFFFFF').borderRadius(10)
.onClick(() => { this.deleteRecord(record.id); });
}
}.width('90%').backgroundColor('#FFFFFF').borderRadius(15).padding({ top: 20, bottom: 20, left: 20, right: 20 });
}
});
}.width('100%').height(500);
设计特点:
- 列表项高度适中,便于触摸操作
- 收入/支出金额使用不同颜色和符号(+/-)
- 编辑和删除按钮大小适合操作
4.4 弹窗设计
4.4.1 添加记录弹窗
if (this.showAddDialog) {
Column({ space: 20 }) {
Text(this.dialogType === 'income' ? '记收入' : '记支出')
.fontSize(32).fontWeight(FontWeight.Bold).width('100%').textAlign(TextAlign.Center);
TextInput({ placeholder: '请输入金额', text: this.newAmount })
.width('90%').height(80).fontSize(28)
.onChange((value: string) => { this.newAmount = value; });
TextInput({ placeholder: '请输入备注', text: this.newDesc })
.width('90%').height(80).fontSize(28)
.onChange((value: string) => { this.newDesc = value; });
Row({ space: 20 }) {
Button('确定').width('40%').height(80).fontSize(28).backgroundColor('#2ECC71').fontColor('#FFFFFF').borderRadius(10)
.onClick(() => { this.handleAddConfirm(); });
Button('取消').width('40%').height(80).fontSize(28).backgroundColor('#999999').fontColor('#FFFFFF').borderRadius(10)
.onClick(() => { this.showAddDialog = false; });
}
}.width('80%').height(400).backgroundColor('#FFFFFF').borderRadius(20).padding({ top: 30 });
}
设计特点:
- 大输入框(80px高度),便于输入
- 28px字体,清晰可读
- 按钮区域明确区分
五、业务逻辑实现
5.1 生命周期管理
async aboutToAppear(): Promise<void> {
await this.loadRecords();
}
async loadRecords(): Promise<void> {
this.records = await this.db.queryAll();
this.totalIncome = await this.db.getTotalIncome();
this.totalExpense = await this.db.getTotalExpense();
}
页面加载时自动初始化数据,调用数据库查询方法获取记录列表和统计数据。
5.2 添加记录流程
async addRecord(type: string): Promise<void> {
this.dialogType = type;
this.newAmount = '';
this.newDesc = '';
this.showAddDialog = true;
}
async handleAddConfirm(): Promise<void> {
const amountNum: number = parseFloat(this.newAmount);
if (!isNaN(amountNum) && amountNum > 0) {
const now: Date = new Date();
const dateStr: string = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
const record: InsertRecord = {
type: this.dialogType,
amount: amountNum,
description: this.newDesc,
date: dateStr
};
await this.db.insert(record);
await this.loadRecords();
this.showToast('添加成功');
} else {
this.showToast('请输入有效的金额');
}
this.showAddDialog = false;
}
流程说明:
- 用户点击"记收入"或"记支出"按钮
- 弹出添加弹窗,用户输入金额和备注
- 点击确定后验证金额有效性
- 构造InsertRecord对象,调用数据库插入方法
- 刷新记录列表和统计数据
- 显示操作结果提示
5.3 编辑记录流程
async editRecord(record: Record): Promise<void> {
this.editingRecord = record;
this.dialogType = record.type;
this.newAmount = record.amount.toString();
this.newDesc = record.description;
this.showEditDialog = true;
}
async handleEditConfirm(): Promise<void> {
if (this.editingRecord) {
const amountNum: number = parseFloat(this.newAmount);
if (!isNaN(amountNum) && amountNum > 0) {
const record: Record = {
id: this.editingRecord.id,
type: this.dialogType,
amount: amountNum,
description: this.newDesc,
date: this.editingRecord.date
};
await this.db.update(record);
await this.loadRecords();
this.showToast('修改成功');
} else {
this.showToast('请输入有效的金额');
}
}
this.showEditDialog = false;
}
5.4 删除记录流程
async deleteRecord(id: number): Promise<void> {
await this.db.delete(id);
await this.loadRecords();
this.showToast('删除成功');
}
5.5 Toast提示工具
class ToastParam {
message: string = '';
duration: number = 2000;
}
showToast(message: string): void {
const param: ToastParam = { message: message, duration: 2000 };
promptAction.showToast(param);
}
六、应用配置与权限
6.1 module.json5 配置
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": ["phone"],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:layered_image",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"skills": [
{
"entities": ["entity.system.home"],
"actions": ["ohos.want.action.home"]
}
]
}
]
}
}
6.2 权限说明
本应用仅使用本地SQLite数据库存储,无需特殊权限。所有数据存储在应用私有目录中,保证数据安全性。
七、ArkTS语法规范与注意事项
7.1 ArkTS与TypeScript的差异
在开发过程中,需要特别注意ArkTS与标准TypeScript的语法差异:
| 特性 | TypeScript | ArkTS |
|---|---|---|
| 解构赋值 | 支持 | 不支持 |
| any/unknown类型 | 支持 | 不支持,需显式类型 |
| 函数表达式 | 支持 | 不支持,需用箭头函数 |
| 全局作用域 | 支持 | 不支持 |
| 命名空间 | 支持 | 不支持 |
| 枚举声明合并 | 支持 | 不支持 |
| in运算符 | 支持 | 不支持 |
| 展开运算符 | 支持多种场景 | 仅支持数组展开 |
7.2 常见编译错误及解决方案
错误1:“Statement or declaration expected, got ‘’”
原因: 文件包含BOM字符
解决方案: 使用UTF-8编码(无BOM)保存文件
错误2:“Object literals cannot be used as type declarations”
原因: 使用对象字面量作为类型
解决方案: 定义显式接口
// 错误写法
const record = { id: 1, type: 'income' };
// 正确写法
interface Record {
id: number;
type: string;
}
const record: Record = { id: 1, type: 'income' };
错误3:“The component ‘List’ can only have the child component ‘ListItem,Section,ListItemGroup’”
原因: List组件直接包含非ListItem子组件
解决方案: 使用ListItem包裹列表项
// 错误写法
List() {
Text('item')
}
// 正确写法
List() {
ListItem() {
Text('item')
}
}
错误4:“Property ‘inputType’ does not exist on type ‘TextInputAttribute’”
原因: TextInput组件不支持inputType属性
解决方案: 移除inputType属性
八、构建与测试
8.1 构建配置
项目使用Hvigor构建工具,配置文件位于:
build-profile.json5- 构建配置hvigorfile.ts- Hvigor任务定义
8.2 构建命令
# 调试构建
./hvigorw assembleDebug
# 发布构建
./hvigorw assembleRelease
8.3 测试策略
8.3.1 单元测试
import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
import { Database, Record, InsertRecord } from '../data/Database';
export default function databaseTest() {
describe('Database Test', () => {
let db: Database;
beforeEach(async () => {
db = new Database();
});
it('insert and query', async () => {
const record: InsertRecord = {
type: 'income',
amount: 100.0,
description: 'test',
date: '2024-01-01'
};
const id = await db.insert(record);
expect(id).assertGreaterThan(0);
const records = await db.queryAll();
expect(records.length).assertGreaterThan(0);
});
it('update record', async () => {
const record: InsertRecord = {
type: 'expense',
amount: 50.0,
description: 'test',
date: '2024-01-01'
};
const id = await db.insert(record);
const updateRecord: Record = {
id: id,
type: 'expense',
amount: 60.0,
description: 'updated',
date: '2024-01-01'
};
const count = await db.update(updateRecord);
expect(count).assertEqual(1);
});
it('delete record', async () => {
const record: InsertRecord = {
type: 'income',
amount: 30.0,
description: 'to delete',
date: '2024-01-01'
};
const id = await db.insert(record);
const count = await db.delete(id);
expect(count).assertEqual(1);
});
it('getTotalIncome', async () => {
const income = await db.getTotalIncome();
expect(typeof income).assertEqual('number');
});
it('getTotalExpense', async () => {
const expense = await db.getTotalExpense();
expect(typeof expense).assertEqual('number');
});
});
}
8.3.2 UI测试
可以使用ArkUI测试框架对UI组件进行自动化测试:
it('button click test', async () => {
await driver.pressElement('记收入');
await driver.assertElementExists('请输入金额');
});
九、性能优化建议
9.1 数据库优化
- 索引优化:对于频繁查询的字段(如type、date),可以创建索引
- 批量操作:如果需要批量插入数据,使用事务可以显著提升性能
- 连接池:复用数据库连接,避免频繁创建和销毁连接
9.2 UI优化
- 列表虚拟化:当记录数量较大时,使用虚拟化列表提升滚动性能
- 图片懒加载:如果未来需要添加图片功能,实现懒加载
- 减少重渲染:合理使用@State、@Prop等装饰器,避免不必要的UI更新
9.3 代码优化
- 异步操作优化:合理使用async/await,避免回调地狱
- 错误处理:添加完善的错误捕获和处理逻辑
- 代码复用:提取公共组件和工具函数
十、部署与发布
10.1 应用打包
打包流程:
- 构建HAP包:使用Hvigor构建工具生成HAP(HarmonyOS Application Package)包
- 签名配置:配置签名信息,确保应用可以在设备上安装
- 版本管理:设置版本号和版本名称
- 分发准备:准备应用描述、图标和截图
打包命令:
# 构建调试版本
./hvigorw assembleDebug
# 构建发布版本
./hvigorw assembleRelease
# 构建带签名的发布版本
./hvigorw assembleRelease --sign
HAP包结构:
entry-default-signed.hap/
├── assets/ # 静态资源
├── libs/ # 原生库
├── resources/ # 应用资源
├── index.abc # 编译后的字节码
└── module.json # 模块配置
10.2 签名配置
签名文件准备:
- 生成密钥库:使用keytool生成密钥库文件
- 配置签名信息:在build-profile.json5中配置签名信息
- 验证签名:确保签名配置正确
签名配置示例:
{
"signingConfigs": {
"debug": {
"keyAlias": "debug",
"keyPassword": "123456",
"storePassword": "123456",
"storeFile": "debug.keystore"
},
"release": {
"keyAlias": "release",
"keyPassword": "123456",
"storePassword": "123456",
"storeFile": "release.keystore"
}
}
}
10.3 应用发布
发布渠道:
| 渠道 | 说明 | 适用场景 |
|---|---|---|
| AppGallery | 华为应用市场 | 正式发布 |
| 第三方应用市场 | 如应用宝、小米应用商店 | 多渠道分发 |
| 企业分发 | 通过企业自有渠道分发 | 企业内部应用 |
| 手动安装 | 通过APK/HAP文件手动安装 | 测试和开发 |
发布准备清单:
- 应用图标和截图
- 应用描述和更新日志
- 隐私政策和用户协议
- 签名配置完成
- 版本号正确设置
- 兼容性测试完成
十一、扩展功能建议
11.1 功能扩展
| 功能 | 说明 | 优先级 |
|---|---|---|
| 分类管理 | 支持收入/支出分类 | 高 |
| 数据导出 | 支持导出Excel/CSV | 中 |
| 数据备份 | 支持云端备份 | 中 |
| 月度统计 | 按月份统计收支 | 高 |
| 预算管理 | 设置月度预算提醒 | 中 |
10.2 界面优化
- 添加搜索功能
- 支持按日期筛选
- 添加图表展示收支趋势
- 支持深色模式
十一、总结
11.1 项目成果
本项目成功实现了一个老年人友好型记账应用,主要成果包括:
- 完整的CRUD功能:支持记账记录的添加、查看、编辑和删除
- 老年人友好设计:大字体、大按钮、高对比度配色
- 数据持久化:使用SQLite数据库存储数据
- 收支统计:实时显示总收入和总支出
- 良好的用户反馈:Toast提示操作结果
11.2 技术亮点
- 使用ArkTS声明式UI构建简洁界面
- 采用分层架构,代码结构清晰
- 数据库操作封装为独立模块,便于维护
- 严格遵循HarmonyOS开发规范
11.3 后续改进
未来可以从以下方面进一步优化:
- 添加更多统计分析功能
- 支持数据导出和备份
- 优化性能,支持大数据量
- 添加更多老年人友好特性,如语音输入等
附录:完整项目结构
app6221/
├── AppScope/
│ └── resources/
│ └── base/
│ ├── element/
│ │ └── string.json
│ └── media/
│ ├── background.png
│ ├── foreground.png
│ └── layered_image.json
├── entry/
│ ├── src/
│ │ └── main/
│ │ ├── ets/
│ │ │ ├── data/
│ │ │ │ └── Database.ets
│ │ │ ├── entryability/
│ │ │ │ └── EntryAbility.ets
│ │ │ ├── entrybackupability/
│ │ │ │ └── EntryBackupAbility.ets
│ │ │ └── pages/
│ │ │ └── Index.ets
│ │ ├── resources/
│ │ │ ├── base/
│ │ │ │ ├── element/
│ │ │ │ │ ├── color.json
│ │ │ │ │ ├── float.json
│ │ │ │ │ └── string.json
│ │ │ │ ├── media/
│ │ │ │ │ ├── background.png
│ │ │ │ │ ├── foreground.png
│ │ │ │ │ ├── layered_image.json
│ │ │ │ │ └── startIcon.png
│ │ │ │ └── profile/
│ │ │ │ ├── backup_config.json
│ │ │ │ └── main_pages.json
│ │ │ └── dark/
│ │ │ └── element/
│ │ │ └── color.json
│ │ └── module.json5
│ ├── build-profile.json5
│ ├── hvigorfile.ts
│ └── oh-package.json5
├── .hvigor/
├── .idea/
├── .trae/
├── build-profile.json5
├── hvigorfile.ts
└── oh-package.json5
更多推荐



所有评论(0)