【鸿蒙原生应用开发--ArkUI--014】Expense-tracker 记账应用开发教程

Expense-tracker 记账应用开发教程

项目介绍

项目背景

记账应用是一个帮助用户记录日常收入和支出的个人财务管理工具。在现代生活中,管理个人财务变得越来越重要。一个好的记账应用可以帮助用户了解自己的消费习惯,控制支出,实现财务目标。

财务管理是一项重要的生活技能,它影响着我们的生活质量、未来规划和财务安全。如果没有适当的跟踪,很容易失去对资金流向的了解,导致超支和财务压力。本应用提供了一个简单而有效的解决方案,用于跟踪每一笔交易。

应用场景

  • 日常记账:实时记录每一笔收入和支出交易。无论是一杯咖啡、一顿餐厅用餐还是一笔工资存款,每笔交易都可以记录详细信息,包括金额、分类、描述和日期。

  • 预算管理:通过提供清晰的收支概览,帮助用户控制支出。用户可以设定月度预算并跟踪进度。

  • 财务分析:了解消费模式,找出可以减少支出的领域。通过将交易分类,用户可以看到哪些类别消耗了最多的资源。

  • 目标追踪:设定储蓄目标并追踪进度。应用可以帮助用户可视化他们的财务决策如何影响长期目标。

功能特性

  1. 交易记录:记录每笔交易的金额、分类、描述和日期。
  2. 分类管理:支持多种消费分类(餐饮、交通、购物等)。
  3. 余额统计:实时显示总收入、总支出和当前余额。
  4. 交易列表:按时间顺序显示所有交易记录。
  5. 删除功能:允许用户删除错误的交易记录。
  6. 收支切换:轻松切换记录收入和支出。

最终效果

应用采用清新的绿色主题,象征着财务增长和财富。主界面包含:

  • 顶部导航栏,显示应用标题和添加按钮
  • 余额统计卡片,显示总收入、总支出和净余额
  • 交易列表,显示所有已记录的交易,包含分类图标

技术栈

  • 开发框架:HarmonyOS NEXT (API 20+)
  • 编程语言:ArkTS
  • UI框架:ArkUI 声明式 UI
  • 核心组件:Column, Row, List, Button, TextInput, Toggle

知识点讲解

1. @State 状态管理

@State 是 ArkUI 中最常用的装饰器,用于声明组件的内部状态。当状态变量的值发生变化时,框架会自动重新渲染使用该状态的 UI 部分。

typescript 复制代码
@Component
struct ExpenseTracker {
  // 声明状态变量
  @State balance: number = 0
  @State totalIncome: number = 0
  @State totalExpense: number = 0
  @State showAddForm: boolean = false
  @State transactions: Transaction[] = []
  
  build() {
    Column() {
      // 在 UI 中使用状态变量
      Text(`余额: ¥${this.balance}`)
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
      
      // 修改状态会触发 UI 更新
      Button('显示表单')
        .onClick(() => {
          this.showAddForm = true  // 状态变化,UI 自动更新
        })
    }
  }
}

@State 的关键点:

  • @State 变量只能在声明它的组件内使用
  • 修改 @State 变量会触发组件重新渲染
  • 建议将相关的状态放在一起管理
  • 状态变化应该是不可变操作,特别是对于复杂对象

2. 接口定义 (interface)

接口用于定义数据结构,确保类型安全,提高代码可维护性。

typescript 复制代码
// 定义交易记录的数据结构
interface Transaction {
  id: number           // 唯一标识
  amount: number       // 交易金额
  category: string     // 分类名称
  description: string  // 交易描述
  date: string         // 交易日期
  type: 'income' | 'expense'  // 交易类型
}

// 使用接口创建数据
const newTransaction: Transaction = {
  id: Date.now(),
  amount: 100,
  category: '餐饮',
  description: '午餐',
  date: '2024-01-20',
  type: 'expense'
}

接口的优势:

  • 类型检查:编译时检查数据结构的正确性
  • 代码提示:IDE 可以提供属性和方法的自动补全
  • 文档作用:清晰地定义数据的结构

3. List 列表组件

List 组件用于显示可滚动的列表,是 ArkUI 中最常用的组件之一。

typescript 复制代码
List() {
  // 使用 ForEach 循环渲染列表项
  ForEach(this.transactions, (transaction: Transaction) => {
    ListItem() {
      Row() {
        // 分类图标
        Column() {
          Text(transaction.category.charAt(0))
            .fontSize(20)
            .fontColor('#ffffff')
        }
        .width(44)
        .height(44)
        .borderRadius(22)
        .backgroundColor(transaction.type === 'income' ? '#10b981' : '#ef4444')
        .justifyContent(FlexAlign.Center)
        
        // 交易信息
        Column() {
          Text(transaction.description || transaction.category)
            .fontSize(16)
            .fontColor('#1e293b')
          
          Text(`${transaction.category} · ${transaction.date}`)
            .fontSize(12)
            .fontColor('#64748b')
            .margin({ top: 4 })
        }
        .width('60%')
        .padding({ left: 12 })
        
        // 金额
        Column() {
          Text(`${transaction.type === 'income' ? '+' : '-'}¥${transaction.amount.toFixed(2)}`)
            .fontSize(16)
            .fontWeight(FontWeight.Medium)
            .fontColor(transaction.type === 'income' ? '#10b981' : '#ef4444')
        }
        .width('20%')
        .alignItems(HorizontalAlign.End)
      }
      .width('100%')
      .padding(16)
      .backgroundColor('#ffffff')
      .borderRadius(8)
      .margin({ bottom: 8 })
    }
  })
}
.width('100%')
.layoutWeight(1)  // 占据剩余空间

List 常用属性:

  • .width() / .height():设置宽高
  • .layoutWeight(1):占据父容器的剩余空间
  • .divider():设置列表项之间的分割线
  • .scrollBar():设置滚动条显示方式

4. 数组操作方法

JavaScript/TypeScript 提供了丰富的数组操作方法,在记账应用中广泛使用。

typescript 复制代码
// filter: 过滤数组元素
const incomeTransactions = this.transactions.filter(t => t.type === 'income')

// reduce: 累加计算
const totalIncome = this.transactions
  .filter(t => t.type === 'income')
  .reduce((sum, t) => sum + t.amount, 0)

// unshift: 在数组开头添加元素
this.transactions.unshift(newTransaction)

// findIndex: 查找元素索引
const index = this.transactions.findIndex(t => t.id === id)

// splice: 删除指定位置的元素
this.transactions.splice(index, 1)

// map: 映射数组元素
const categories = this.transactions.map(t => t.category)

// find: 查找第一个匹配的元素
const transaction = this.transactions.find(t => t.id === id)

// some: 检查是否有任何元素匹配
const hasIncome = this.transactions.some(t => t.type === 'income')

// every: 检查是否所有元素匹配
const allExpenses = this.transactions.every(t => t.type === 'expense')

5. 条件渲染

使用 if/else 或三元运算符根据条件显示不同的 UI。

typescript 复制代码
// if/else 方式
if (this.transactions.length === 0) {
  Column() {
    Text('暂无交易记录')
      .fontSize(16)
      .fontColor('#64748b')
    Text('点击右上角 + 添加第一笔记录')
      .fontSize(14)
      .fontColor('#94a3b8')
  }
  .width('100%')
  .height(200)
  .justifyContent(FlexAlign.Center)
} else {
  List() {
    ForEach(this.transactions, (transaction: Transaction) => {
      ListItem() {
        // 列表项内容
      }
    })
  }
}

// 三元运算符方式
Text(transaction.type === 'income' ? '+' : '-')
  .fontSize(16)
  .fontColor(transaction.type === 'income' ? '#10b981' : '#ef4444')

6. 事件处理

使用 onClickonChange 等方法处理用户交互。

typescript 复制代码
// 点击事件
Button('添加')
  .onClick(() => {
    this.addTransaction()
  })

// 输入变化事件
TextInput({ placeholder: '请输入金额', text: this.newAmount })
  .onChange((value: string) => {
    this.newAmount = value
  })

7. 样式设置

使用链式调用设置组件的各种样式属性。

typescript 复制代码
Text('记账应用')
  .fontSize(24)                    // 字体大小
  .fontWeight(FontWeight.Bold)     // 字体粗细
  .fontColor('#1e293b')            // 字体颜色
  .margin({ top: 16 })             // 外边距
  .padding({ left: 20 })           // 内边距
  .width('100%')                   // 宽度
  .textAlign(TextAlign.Center)     // 文本对齐

8. 布局组件

ArkUI 提供了多种布局组件,用于构建复杂的界面。

typescript 复制代码
// Column: 垂直布局
Column() {
  Text('第一行')
  Text('第二行')
  Text('第三行')
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)  // 垂直居中
.alignItems(HorizontalAlign.Center) // 水平居中

// Row: 水平布局
Row() {
  Text('左侧')
  Blank()  // 占据剩余空间
  Text('右侧')
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween) // 两端对齐

9. 组件生命周期

typescript 复制代码
@Component
struct MyComponent {
  // 组件即将出现时调用
  aboutToAppear() {
    console.log('组件即将出现')
    // 适合进行初始化操作
  }
  
  // 组件即将消失时调用
  aboutToDisappear() {
    console.log('组件即将消失')
    // 适合进行清理操作
  }
  
  build() {
    // 构建 UI
  }
}

10. 字符串模板

使用模板字符串进行格式化输出。

typescript 复制代码
// 基本模板
const message = `当前余额: ¥${this.balance.toFixed(2)}`

// 格式化数字
const amount = `¥${transaction.amount.toFixed(2)}`

// 组合字符串
const displayText = `${transaction.type === 'income' ? '收入' : '支出'}: ¥${transaction.amount}`

完整代码解析

页面结构设计

复制代码
┌─────────────────────────────────┐
│         顶部导航栏              │
│    [记账应用]          [+]      │
├─────────────────────────────────┤
│      余额统计卡片              │
│  ┌───────────────────────────┐  │
│  │    总余额: ¥1,234.56      │  │
│  │                           │  │
│  │  收入: ¥5,000             │  │
│  │  支出: ¥3,765             │  │
│  └───────────────────────────┘  │
├─────────────────────────────────┤
│    交易记录标题                │
│    [交易记录]        [共 5 笔]  │
├─────────────────────────────────┤
│      交易记录列表              │
│  ┌───────────────────────────┐  │
│  │ 🍽 午餐          -¥25.00  │  │
│  │    餐饮 · 2024-01-20      │  │
│  └───────────────────────────┘  │
│  ┌───────────────────────────┐  │
│  │ 💰 工资        +¥5,000.00│  │
│  │    收入 · 2024-01-15      │  │
│  └───────────────────────────┘  │
│         ...                     │
└─────────────────────────────────┘

核心方法实现

1. 添加交易记录
typescript 复制代码
private addTransaction() {
  // 验证输入
  if (this.newAmount === '' || isNaN(Number(this.newAmount))) {
    return
  }
  
  const amount = Number(this.newAmount)
  
  // 创建交易记录对象
  const transaction: Transaction = {
    id: Date.now(),  // 使用时间戳作为唯一ID
    amount: amount,
    category: this.newCategory,
    description: this.newDescription,
    date: new Date().toLocaleDateString(),
    type: this.newType
  }
  
  // 添加到列表开头
  this.transactions.unshift(transaction)
  
  // 更新余额统计
  this.updateBalance()
  
  // 清空表单
  this.clearForm()
  
  // 关闭表单
  this.showAddForm = false
}
2. 更新余额统计
typescript 复制代码
private updateBalance() {
  // 计算总收入
  this.totalIncome = this.transactions
    .filter(t => t.type === 'income')
    .reduce((sum, t) => sum + t.amount, 0)
  
  // 计算总支出
  this.totalExpense = this.transactions
    .filter(t => t.type === 'expense')
    .reduce((sum, t) => sum + t.amount, 0)
  
  // 计算余额
  this.balance = this.totalIncome - this.totalExpense
}
3. 删除交易记录
typescript 复制代码
private deleteTransaction(id: number) {
  // 过滤掉指定ID的记录
  this.transactions = this.transactions.filter(t => t.id !== id)
  
  // 更新余额统计
  this.updateBalance()
}
4. 格式化金额
typescript 复制代码
private formatAmount(amount: number): string {
  // 保留两位小数
  return amount.toFixed(2)
}

常见问题与解决方案

问题1:添加记录后列表不更新

现象:点击添加按钮后,列表没有显示新记录。

原因:直接修改数组元素不会触发 UI 更新。

解决方案

typescript 复制代码
// 错误方式:直接修改数组
this.transactions[0].amount = 100  // 不会触发更新

// 正确方式:创建新数组
this.transactions = [...this.transactions]
// 或者使用 unshift/push 等方法
this.transactions.unshift(newTransaction)

问题2:金额输入验证

现象:输入非数字字符时程序出错。

解决方案

typescript 复制代码
private addTransaction() {
  // 验证输入是否为有效数字
  if (this.newAmount === '' || isNaN(Number(this.newAmount))) {
    // 显示错误提示
    return
  }
  
  const amount = Number(this.newAmount)
  
  // 验证金额是否为正数
  if (amount <= 0) {
    return
  }
  
  // 继续添加记录...
}

问题3:日期格式不一致

现象:不同设备显示的日期格式不同。

解决方案

typescript 复制代码
// 使用固定的日期格式
private formatDate(date: Date): string {
  const year = date.getFullYear()
  const month = (date.getMonth() + 1).toString().padStart(2, '0')
  const day = date.getDate().toString().padStart(2, '0')
  return `${year}-${month}-${day}`
}

问题4:列表滚动性能

现象:记录很多时,列表滚动卡顿。

解决方案

typescript 复制代码
// 使用 LazyForEach 替代 ForEach
List() {
  LazyForEach(this.dataSource, (transaction: Transaction) => {
    ListItem() {
      // 列表项内容
    }
  }, (transaction: Transaction) => transaction.id.toString())
}

扩展学习

可添加功能

  1. 数据持久化

    • 使用 Preferences 存储用户设置
    • 使用 RDB(关系型数据库)存储交易记录
  2. 图表统计

    • 使用 Canvas 绘制饼图显示消费分类
    • 使用折线图显示收支趋势
  3. 多币种支持

    • 支持人民币、美元、欧元等多种货币
    • 实时汇率转换
  4. 预算管理

    • 设置每月预算
    • 超支提醒
  5. 导出功能

    • 导出为 CSV 文件
    • 生成月度报告

优化建议

  1. 性能优化

    • 使用 LazyForEach 处理长列表
    • 避免在 build 方法中进行复杂计算
    • 使用 @Watch 监听状态变化
  2. 用户体验

    • 添加加载动画
    • 提供操作反馈(震动、声音)
    • 支持手势操作(左滑删除)
  3. 代码质量

    • 提取公共组件
    • 使用常量管理颜色和尺寸
    • 编写单元测试

总结

通过本教程,您学会了:

  1. @State 状态管理:如何声明和使用状态变量
  2. 接口定义:如何使用 interface 定义数据结构
  3. List 组件:如何创建可滚动的列表
  4. 数组操作:如何使用 filter、reduce 等方法处理数据
  5. 条件渲染:如何根据条件显示不同的 UI
  6. 事件处理:如何处理用户交互
  7. 样式设置:如何设置组件的样式

这些知识点是 HarmonyOS NEXT 开发的基础,掌握它们后,您可以轻松构建更复杂的应用。

相关推荐
不羁的木木1 小时前
《HarmonyOS技术精讲》五:实战项目 ── 智能支架助手
华为·harmonyos
枫叶丹41 小时前
【HarmonyOS 6.0】Map Kit瓦片图层深度解析:本地加载方式与瓦片数据缓存能力
开发语言·缓存·华为·harmonyos
大雷神1 小时前
第29篇|单拍按钮背后:从点击到 PhotoOutput 回调
harmonyos
不羁的木木1 小时前
《HarmonyOS底部页签-沉浸光感组件实战》模糊样式:打造毛玻璃效果
华为·harmonyos
大雷神8 小时前
第26篇|单摄预览会话:CameraInput、PreviewOutput、PhotoSession 的关系
harmonyos
博客-小覃14 小时前
Zabbix之华为交换机的日志记录信息操作详细教程
服务器·网络·华为·zabbix
不羁的木木16 小时前
Form Kit(卡片开发服务)学习笔记01-核心概念与架构设计
笔记·学习·harmonyos
不羁的木木17 小时前
ArkWeb实战学习笔记01-核心概念与架构设计
笔记·学习·harmonyos
Goway_Hui17 小时前
【鸿蒙原生应用开发--ArkUI--010】Recipe-app 菜谱应用开发教程
华为·harmonyos