Expense-tracker 记账应用开发教程
项目介绍
项目背景
记账应用是一个帮助用户记录日常收入和支出的个人财务管理工具。在现代生活中,管理个人财务变得越来越重要。一个好的记账应用可以帮助用户了解自己的消费习惯,控制支出,实现财务目标。
财务管理是一项重要的生活技能,它影响着我们的生活质量、未来规划和财务安全。如果没有适当的跟踪,很容易失去对资金流向的了解,导致超支和财务压力。本应用提供了一个简单而有效的解决方案,用于跟踪每一笔交易。
应用场景
日常记账:实时记录每一笔收入和支出交易。无论是一杯咖啡、一顿餐厅用餐还是一笔工资存款,每笔交易都可以记录详细信息,包括金额、分类、描述和日期。
预算管理:通过提供清晰的收支概览,帮助用户控制支出。用户可以设定月度预算并跟踪进度。
财务分析:了解消费模式,找出可以减少支出的领域。通过将交易分类,用户可以看到哪些类别消耗了最多的资源。
目标追踪:设定储蓄目标并追踪进度。应用可以帮助用户可视化他们的财务决策如何影响长期目标。
功能特性
- 交易记录:记录每笔交易的金额、分类、描述和日期。
- 分类管理:支持多种消费分类(餐饮、交通、购物等)。
- 余额统计:实时显示总收入、总支出和当前余额。
- 交易列表:按时间顺序显示所有交易记录。
- 删除功能:允许用户删除错误的交易记录。
- 收支切换:轻松切换记录收入和支出。
最终效果
应用采用清新的绿色主题,象征着财务增长和财富。主界面包含:
- 顶部导航栏,显示应用标题和添加按钮
- 余额统计卡片,显示总收入、总支出和净余额
- 交易列表,显示所有已记录的交易,包含分类图标
技术栈
- 开发框架:HarmonyOS NEXT (API 20+)
- 编程语言:ArkTS
- UI框架:ArkUI 声明式 UI
- 核心组件:Column, Row, List, Button, TextInput, Toggle
知识点讲解
1. @State 状态管理
@State是 ArkUI 中最常用的装饰器,用于声明组件的内部状态。当状态变量的值发生变化时,框架会自动重新渲染使用该状态的 UI 部分。
@Componentstruct ExpenseTracker{// 声明状态变量@Statebalance:number=0@StatetotalIncome:number=0@StatetotalExpense:number=0@StateshowAddForm:boolean=false@Statetransactions:Transaction[]=[]build(){Column(){// 在 UI 中使用状态变量Text(`余额: ¥${this.balance}`).fontSize(24).fontWeight(FontWeight.Bold)// 修改状态会触发 UI 更新Button('显示表单').onClick(()=>{this.showAddForm=true// 状态变化,UI 自动更新})}}}@State 的关键点:
@State变量只能在声明它的组件内使用- 修改
@State变量会触发组件重新渲染 - 建议将相关的状态放在一起管理
- 状态变化应该是不可变操作,特别是对于复杂对象
2. 接口定义 (interface)
接口用于定义数据结构,确保类型安全,提高代码可维护性。
// 定义交易记录的数据结构interfaceTransaction{id:number// 唯一标识amount:number// 交易金额category:string// 分类名称description:string// 交易描述date:string// 交易日期type:'income'|'expense'// 交易类型}// 使用接口创建数据constnewTransaction:Transaction={id:Date.now(),amount:100,category:'餐饮',description:'午餐',date:'2024-01-20',type:'expense'}接口的优势:
- 类型检查:编译时检查数据结构的正确性
- 代码提示:IDE 可以提供属性和方法的自动补全
- 文档作用:清晰地定义数据的结构
3. List 列表组件
List组件用于显示可滚动的列表,是 ArkUI 中最常用的组件之一。
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 提供了丰富的数组操作方法,在记账应用中广泛使用。
// filter: 过滤数组元素constincomeTransactions=this.transactions.filter(t=>t.type==='income')// reduce: 累加计算consttotalIncome=this.transactions.filter(t=>t.type==='income').reduce((sum,t)=>sum+t.amount,0)// unshift: 在数组开头添加元素this.transactions.unshift(newTransaction)// findIndex: 查找元素索引constindex=this.transactions.findIndex(t=>t.id===id)// splice: 删除指定位置的元素this.transactions.splice(index,1)// map: 映射数组元素constcategories=this.transactions.map(t=>t.category)// find: 查找第一个匹配的元素consttransaction=this.transactions.find(t=>t.id===id)// some: 检查是否有任何元素匹配consthasIncome=this.transactions.some(t=>t.type==='income')// every: 检查是否所有元素匹配constallExpenses=this.transactions.every(t=>t.type==='expense')5. 条件渲染
使用if/else或三元运算符根据条件显示不同的 UI。
// 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. 事件处理
使用onClick、onChange等方法处理用户交互。
// 点击事件Button('添加').onClick(()=>{this.addTransaction()})// 输入变化事件TextInput({placeholder:'请输入金额',text:this.newAmount}).onChange((value:string)=>{this.newAmount=value})7. 样式设置
使用链式调用设置组件的各种样式属性。
Text('记账应用').fontSize(24)// 字体大小.fontWeight(FontWeight.Bold)// 字体粗细.fontColor('#1e293b')// 字体颜色.margin({top:16})// 外边距.padding({left:20})// 内边距.width('100%')// 宽度.textAlign(TextAlign.Center)// 文本对齐8. 布局组件
ArkUI 提供了多种布局组件,用于构建复杂的界面。
// 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. 组件生命周期
@Componentstruct MyComponent{// 组件即将出现时调用aboutToAppear(){console.log('组件即将出现')// 适合进行初始化操作}// 组件即将消失时调用aboutToDisappear(){console.log('组件即将消失')// 适合进行清理操作}build(){// 构建 UI}}10. 字符串模板
使用模板字符串进行格式化输出。
// 基本模板constmessage=`当前余额: ¥${this.balance.toFixed(2)}`// 格式化数字constamount=`¥${transaction.amount.toFixed(2)}`// 组合字符串constdisplayText=`${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. 添加交易记录
privateaddTransaction(){// 验证输入if(this.newAmount===''||isNaN(Number(this.newAmount))){return}constamount=Number(this.newAmount)// 创建交易记录对象consttransaction:Transaction={id:Date.now(),// 使用时间戳作为唯一IDamount:amount,category:this.newCategory,description:this.newDescription,date:newDate().toLocaleDateString(),type:this.newType}// 添加到列表开头this.transactions.unshift(transaction)// 更新余额统计this.updateBalance()// 清空表单this.clearForm()// 关闭表单this.showAddForm=false}2. 更新余额统计
privateupdateBalance(){// 计算总收入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. 删除交易记录
privatedeleteTransaction(id:number){// 过滤掉指定ID的记录this.transactions=this.transactions.filter(t=>t.id!==id)// 更新余额统计this.updateBalance()}4. 格式化金额
privateformatAmount(amount:number):string{// 保留两位小数returnamount.toFixed(2)}常见问题与解决方案
问题1:添加记录后列表不更新
现象:点击添加按钮后,列表没有显示新记录。
原因:直接修改数组元素不会触发 UI 更新。
解决方案:
// 错误方式:直接修改数组this.transactions[0].amount=100// 不会触发更新// 正确方式:创建新数组this.transactions=[...this.transactions]// 或者使用 unshift/push 等方法this.transactions.unshift(newTransaction)问题2:金额输入验证
现象:输入非数字字符时程序出错。
解决方案:
privateaddTransaction(){// 验证输入是否为有效数字if(this.newAmount===''||isNaN(Number(this.newAmount))){// 显示错误提示return}constamount=Number(this.newAmount)// 验证金额是否为正数if(amount<=0){return}// 继续添加记录...}问题3:日期格式不一致
现象:不同设备显示的日期格式不同。
解决方案:
// 使用固定的日期格式privateformatDate(date:Date):string{constyear=date.getFullYear()constmonth=(date.getMonth()+1).toString().padStart(2,'0')constday=date.getDate().toString().padStart(2,'0')return`${year}-${month}-${day}`}问题4:列表滚动性能
现象:记录很多时,列表滚动卡顿。
解决方案:
// 使用 LazyForEach 替代 ForEachList(){LazyForEach(this.dataSource,(transaction:Transaction)=>{ListItem(){// 列表项内容}},(transaction:Transaction)=>transaction.id.toString())}扩展学习
可添加功能
数据持久化
- 使用
Preferences存储用户设置 - 使用
RDB(关系型数据库)存储交易记录
- 使用
图表统计
- 使用 Canvas 绘制饼图显示消费分类
- 使用折线图显示收支趋势
多币种支持
- 支持人民币、美元、欧元等多种货币
- 实时汇率转换
预算管理
- 设置每月预算
- 超支提醒
导出功能
- 导出为 CSV 文件
- 生成月度报告
优化建议
性能优化
- 使用
LazyForEach处理长列表 - 避免在
build方法中进行复杂计算 - 使用
@Watch监听状态变化
- 使用
用户体验
- 添加加载动画
- 提供操作反馈(震动、声音)
- 支持手势操作(左滑删除)
代码质量
- 提取公共组件
- 使用常量管理颜色和尺寸
- 编写单元测试
总结
通过本教程,您学会了:
- @State 状态管理:如何声明和使用状态变量
- 接口定义:如何使用 interface 定义数据结构
- List 组件:如何创建可滚动的列表
- 数组操作:如何使用 filter、reduce 等方法处理数据
- 条件渲染:如何根据条件显示不同的 UI
- 事件处理:如何处理用户交互
- 样式设置:如何设置组件的样式
这些知识点是 HarmonyOS NEXT 开发的基础,掌握它们后,您可以轻松构建更复杂的应用。