老奶奶记账: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)设计模式,将应用分为三个主要层次:表示层、业务逻辑层和数据访问层。这种架构模式具有以下优点:

  1. 关注点分离:各层职责明确,便于开发和维护
  2. 可扩展性:可以独立地对各层进行扩展和修改
  3. 可测试性:各层可以独立进行单元测试
  4. 代码复用:业务逻辑和数据访问逻辑可以被多个界面复用

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数据库
     ↓                                                          ↑
  界面更新 ←──────────────────────────────────────────────────────

数据流详细说明:

  1. 用户交互层:用户在UI层进行操作(点击按钮、输入内容等)
  2. 事件处理层:UI层接收用户事件,调用对应的业务方法
  3. 业务逻辑层:业务方法进行数据验证和处理
  4. 数据访问层:调用数据层的数据库操作方法
  5. 持久化存储:数据层执行SQLite操作,将数据写入数据库
  6. 状态同步:数据库操作完成后,重新查询数据
  7. 界面渲染:UI层根据新状态重新渲染界面

这种单向数据流模式的优势在于:

  • 可追溯性:数据变化的源头清晰,便于调试和定位问题
  • 可预测性:数据流向单一,状态变化易于理解
  • 可测试性:各层可以独立进行测试

2.4 设计模式应用

本应用在设计过程中运用了以下设计模式:

设计模式 应用场景 实现方式
单例模式 数据库连接管理 Database类采用延迟初始化,确保连接唯一性
数据访问对象模式(DAO) 数据库操作封装 Database类封装所有SQLite操作
观察者模式 状态驱动UI更新 使用@State装饰器实现响应式数据绑定
模板方法模式 数据库操作流程 各CRUD方法遵循统一的连接检查→操作→返回结果流程

2.5 架构设计原则

在架构设计过程中,我们遵循以下原则:

  1. 单一职责原则:每个类只负责一个功能模块
  2. 开闭原则:对扩展开放,对修改关闭
  3. 依赖倒置原则:依赖抽象而非具体实现
  4. 接口隔离原则:使用细粒度接口
  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 数据库安全性

考虑到老年人用户的数据安全,本应用采取以下安全措施:

  1. 数据加密:使用SQLite的加密功能保护数据库文件
  2. 安全级别设置:设置为S1级别,确保数据安全存储
  3. 本地存储:数据仅存储在设备本地,不进行云端传输
  4. 权限控制:数据库文件存储在应用私有目录,其他应用无法访问
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由数据库自动生成

这种设计的优势:

  1. 类型安全:明确区分创建和查询操作的数据结构
  2. 防止错误:避免用户手动设置id导致的数据冲突
  3. 代码清晰:通过接口名称即可理解数据用途

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();
}

这种模式的优势:

  1. 资源节约:只有在需要时才创建数据库连接
  2. 启动加速:应用启动时不需要立即建立数据库连接
  3. 按需加载:如果用户不使用某些功能,就不会创建相应的资源

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;
}

流程说明:

  1. 用户点击"记收入"或"记支出"按钮
  2. 弹出添加弹窗,用户输入金额和备注
  3. 点击确定后验证金额有效性
  4. 构造InsertRecord对象,调用数据库插入方法
  5. 刷新记录列表和统计数据
  6. 显示操作结果提示

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 数据库优化

  1. 索引优化:对于频繁查询的字段(如type、date),可以创建索引
  2. 批量操作:如果需要批量插入数据,使用事务可以显著提升性能
  3. 连接池:复用数据库连接,避免频繁创建和销毁连接

9.2 UI优化

  1. 列表虚拟化:当记录数量较大时,使用虚拟化列表提升滚动性能
  2. 图片懒加载:如果未来需要添加图片功能,实现懒加载
  3. 减少重渲染:合理使用@State、@Prop等装饰器,避免不必要的UI更新

9.3 代码优化

  1. 异步操作优化:合理使用async/await,避免回调地狱
  2. 错误处理:添加完善的错误捕获和处理逻辑
  3. 代码复用:提取公共组件和工具函数

十、部署与发布

10.1 应用打包

打包流程:

  1. 构建HAP包:使用Hvigor构建工具生成HAP(HarmonyOS Application Package)包
  2. 签名配置:配置签名信息,确保应用可以在设备上安装
  3. 版本管理:设置版本号和版本名称
  4. 分发准备:准备应用描述、图标和截图

打包命令:

# 构建调试版本
./hvigorw assembleDebug

# 构建发布版本
./hvigorw assembleRelease

# 构建带签名的发布版本
./hvigorw assembleRelease --sign

HAP包结构:

entry-default-signed.hap/
├── assets/           # 静态资源
├── libs/             # 原生库
├── resources/        # 应用资源
├── index.abc         # 编译后的字节码
└── module.json       # 模块配置

10.2 签名配置

签名文件准备:

  1. 生成密钥库:使用keytool生成密钥库文件
  2. 配置签名信息:在build-profile.json5中配置签名信息
  3. 验证签名:确保签名配置正确

签名配置示例:

{
  "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 项目成果

本项目成功实现了一个老年人友好型记账应用,主要成果包括:

  1. 完整的CRUD功能:支持记账记录的添加、查看、编辑和删除
  2. 老年人友好设计:大字体、大按钮、高对比度配色
  3. 数据持久化:使用SQLite数据库存储数据
  4. 收支统计:实时显示总收入和总支出
  5. 良好的用户反馈:Toast提示操作结果

11.2 技术亮点

  • 使用ArkTS声明式UI构建简洁界面
  • 采用分层架构,代码结构清晰
  • 数据库操作封装为独立模块,便于维护
  • 严格遵循HarmonyOS开发规范

11.3 后续改进

未来可以从以下方面进一步优化:

  1. 添加更多统计分析功能
  2. 支持数据导出和备份
  3. 优化性能,支持大数据量
  4. 添加更多老年人友好特性,如语音输入等

附录:完整项目结构

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
Logo

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

更多推荐