HarmonyOS ArkTS 实战:实现一个简洁记账本应用
HarmonyOS ArkTS 实战:实现一个简洁记账本应用
项目效果
本文实现的是一个基于 HarmonyOS 和 ArkTS 的简洁记账本应用。项目中使用 ArkUI 组件完成页面布局,通过 @State 管理状态数据,实现收入支出记录、金额输入、分类选择、备注添加、账单列表、月度统计、收支筛选和数据清空等功能。
最终运行效果如下:
页面主要包含以下内容:
- 顶部应用标题和月度收支统计;
- 收入支出切换;
- 金额输入区域;
- 消费分类选择;
- 备注输入;
- 记一笔按钮;
- 账单筛选(全部/收入/支出);
- 账单列表;
- 空状态提示;
- 页面整体采用 ArkUI 声明式布局。
本文重点是演示如何在 HarmonyOS 项目中使用 ArkTS 和 ArkUI 实现一个实用的财务管理类单页面应用。项目代码主要写在 entry/src/main/ets/pages/Index.ets 文件中,适合作为 HarmonyOS ArkTS 入门到进阶之间的练习案例。
前言
个人财务管理是日常生活中非常重要的一部分。一个简洁好用的记账本可以帮助我们了解自己的收支情况,合理规划消费,养成良好的理财习惯。市面上的记账应用往往功能复杂,广告繁多,而我们需要一个纯粹、简洁的工具来记录日常收支。
从应用开发角度来看,这个项目不依赖后端接口,也不需要数据库,但能练习 ArkTS 中的状态管理、数字输入、分类选择、列表渲染、数据统计、条件筛选和数组操作等内容。
本文基于 HarmonyOS 和 ArkTS 实现一个简洁记账本应用。用户可以记录收入和支出,选择消费分类,添加备注,查看账单列表,按月统计收支情况,按类型筛选账单。
这个项目的核心不是简单的数字记录,而是完整的个人财务管理流程。每一笔收入支出的记录、统计和筛选,本质上都是对账单数组的操作。状态变化后,页面会自动刷新,这正是 ArkUI 声明式开发的基本思想。
一、项目目标
本次实践主要实现以下目标:
- 创建 HarmonyOS ArkTS 页面;
- 使用
@Entry和@Component定义页面组件; - 使用
@State管理页面状态; - 支持收入和支出两种类型记录;
- 实现金额数字输入;
- 提供常用消费分类选择;
- 支持添加备注信息;
- 实现账单记录功能;
- 显示账单列表;
- 支持按类型筛选账单;
- 统计月度总收入和总支出;
- 计算月度结余;
- 支持删除单条账单;
- 使用
List和ForEach渲染账单列表; - 使用空状态提示优化无账单页面;
- 使用
@Builder封装统计卡片、分类按钮和账单项; - 完成一个可以运行的记账本页面。
这个项目虽然是单页面应用,但它有完整的记账、统计、筛选和删除流程,比普通静态页面更适合练习 ArkTS。
二、技术栈
| 类型 | 内容 |
|---|---|
| 开发方向 | HarmonyOS 应用开发 |
| 开发语言 | ArkTS |
| UI 框架 | ArkUI |
| SDK 版本 | HarmonyOS API 23 及以上 |
| 工程模型 | Stage 模型 |
| 核心组件 | Text / TextInput / Button / Column / Row / List / ForEach / Tabs |
| 状态管理 | @State |
| 数据处理 | 数字计算 / 数组统计 / filter / reduce |
| 项目入口 | entry/src/main/ets/pages/Index.ets |
| 运行平台 | 模拟器或真机 |
本项目是 HarmonyOS 原生 ArkTS 项目。页面主体由 ArkUI 组件构建,核心逻辑写在 Index.ets 文件中,不依赖后端接口,也不需要额外配置数据库。
三、为什么选择记账本项目
简洁记账本应用适合作为 ArkTS 练习项目,主要有以下几个原因。
第一,实用性强。记账是每个人都需要的理财习惯,一个简洁无广告的记账本可以直接应用于日常生活。
第二,业务场景真实。无论是日常消费还是工资收入,都需要记录和管理,记账本是最基础的个人财务工具。
第三,适合练习状态管理。记录类型、金额、分类、备注、筛选条件和账单列表都属于页面状态。状态变化后,页面会自动刷新。
第四,适合练习数据统计。总收入、总支出和结余都需要从账单数组动态计算,可以练习数组的reduce方法。
第五,适合练习表单交互。金额输入、分类选择、备注输入都是常见的表单交互形式。
第六,扩展空间比较大。基础功能完成后,可以继续增加本地存储、多账本、预算管理、消费图表、分类统计和数据导出。
在本项目中,bills 是核心数据源。页面中的统计数据、账单列表和筛选结果都由它计算或渲染得到。
四、功能规则说明
本文中的账单类型分为两类:
| 类型 | 颜色 | 说明 |
|---|---|---|
| 支出 | #EF4444 | 消费支出,金额显示为红色 |
| 收入 | #10B981 | 工资等收入,金额显示为绿色 |
支出分类包括:
| 分类 | 图标 | 说明 |
|---|---|---|
| 餐饮 | 🍜 | 吃饭、外卖等 |
| 交通 | 🚗 | 打车、公交、地铁等 |
| 购物 | 🛒 | 网购、超市购物等 |
| 娱乐 | 🎮 | 电影、游戏等 |
| 居住 | 🏠 | 房租、水电等 |
| 医疗 | 💊 | 看病、买药等 |
| 其他 | 📝 | 其他支出 |
收入分类包括:
| 分类 | 图标 | 说明 |
|---|---|---|
| 工资 | 💰 | 工资收入 |
| 奖金 | 🎁 | 奖金、红包 |
| 投资 | 📈 | 投资收益 |
| 其他 | 📝 | 其他收入 |
筛选规则如下:
全部:显示所有账单
收入:只显示收入记录
支出:只显示支出记录
统计规则如下:
本月收入 = 当月所有收入记录金额之和
本月支出 = 当月所有支出记录金额之和
本月结余 = 本月收入 - 本月支出
每次添加或删除账单后,统计数据都会自动更新。
五、项目结构
本项目主要修改首页文件:
entry
└── src
└── main
└── ets
└── pages
└── Index.ets
其中:
| 文件 | 作用 |
|---|---|
| Index.ets | 编写页面结构、状态数据和记账逻辑 |
本文不涉及复杂路由,也不需要额外创建多个页面。对于练习项目来说,把主要逻辑集中在一个 Index.ets 文件中更方便理解。
如果后续继续扩展,可以考虑把金额输入器、分类选择器、账单项和统计卡片拆分成独立组件。
六、核心实现思路
本项目的核心流程如下:
- 定义账单数据结构;
- 使用
@State保存记录类型、金额、选中分类、备注、筛选条件和账单列表; - 用户选择收入或支出类型;
- 用户输入金额;
- 用户选择分类;
- 用户输入备注;
- 点击记一笔按钮创建新账单;
- 使用数组保存所有账单;
- 根据筛选条件展示不同账单;
- 使用
ForEach渲染账单列表; - 点击删除按钮删除指定账单;
- 顶部统计卡片根据账单数据自动计算;
- 没有账单时显示空状态提示。
项目中最重要的状态变量如下:
@State recordType: string = '支出'
@State amount: string = ''
@State selectedCategory: string = '餐饮'
@State note: string = ''
@State filterType: string = '全部'
@State bills: Bill[] = []
@State nextId: number = 1
其中:
| 状态变量 | 作用 |
|---|---|
| recordType | 当前记录类型(收入/支出) |
| amount | 输入的金额 |
| selectedCategory | 选中的分类 |
| note | 备注内容 |
| filterType | 当前筛选条件 |
| bills | 账单数组 |
| nextId | 生成账单唯一编号 |
bills 是本项目中最重要的数据。所有账单展示、筛选和统计都依赖这个数组。
七、Index.ets 完整代码
打开文件:
entry/src/main/ets/pages/Index.ets
将其中内容替换为下面代码:
interface Bill {
id: number
type: string
amount: number
category: string
note: string
time: string
date: string
}
@Entry
@Component
struct Index {
@State recordType: string = '支出'
@State amount: string = ''
@State selectedCategory: string = '餐饮'
@State note: string = ''
@State filterType: string = '全部'
@State nextId: number = 4
@State bills: Bill[] = [
{
id: 1,
type: '支出',
amount: 35.5,
category: '餐饮',
note: '午餐外卖',
time: '12:30',
date: this.getCurrentDate()
},
{
id: 2,
type: '支出',
amount: 15,
category: '交通',
note: '打车上班',
time: '09:15',
date: this.getCurrentDate()
},
{
id: 3,
type: '收入',
amount: 15000,
category: '工资',
note: '7月工资',
time: '10:00',
date: this.getCurrentDate()
}
]
private expenseCategories: string[] = ['餐饮', '交通', '购物', '娱乐', '居住', '医疗', '其他']
private incomeCategories: string[] = ['工资', '奖金', '投资', '其他']
private addBill(): void {
let amountNum = parseFloat(this.amount)
if (isNaN(amountNum) || amountNum <= 0) {
return
}
let bill: Bill = {
id: this.nextId,
type: this.recordType,
amount: amountNum,
category: this.selectedCategory,
note: this.note.trim() || '无备注',
time: this.getCurrentTime(),
date: this.getCurrentDate()
}
this.bills = [bill, ...this.bills]
this.nextId++
this.amount = ''
this.note = ''
}
private deleteBill(id: number): void {
this.bills = this.bills.filter((bill: Bill) => bill.id !== id)
}
private getFilteredBills(): Bill[] {
if (this.filterType === '收入') {
return this.bills.filter((bill: Bill) => bill.type === '收入')
}
if (this.filterType === '支出') {
return this.bills.filter((bill: Bill) => bill.type === '支出')
}
return this.bills
}
private getMonthlyIncome(): number {
let now = new Date()
let currentMonth = `${now.getFullYear()}-${this.formatTwo(now.getMonth() + 1)}`
return this.bills
.filter((bill: Bill) => bill.type === '收入' && bill.date.startsWith(currentMonth))
.reduce((sum: number, bill: Bill) => sum + bill.amount, 0)
}
private getMonthlyExpense(): number {
let now = new Date()
let currentMonth = `${now.getFullYear()}-${this.formatTwo(now.getMonth() + 1)}`
return this.bills
.filter((bill: Bill) => bill.type === '支出' && bill.date.startsWith(currentMonth))
.reduce((sum: number, bill: Bill) => sum + bill.amount, 0)
}
private getMonthlyBalance(): number {
return this.getMonthlyIncome() - this.getMonthlyExpense()
}
private getCurrentTime(): string {
let date = new Date()
return `${this.formatTwo(date.getHours())}:${this.formatTwo(date.getMinutes())}`
}
private getCurrentDate(): string {
let date = new Date()
return `${date.getFullYear()}-${this.formatTwo(date.getMonth() + 1)}-${this.formatTwo(date.getDate())}`
}
private formatTwo(value: number): string {
return value < 10 ? '0' + value.toString() : value.toString()
}
private getCategoryIcon(category: string): string {
const icons: Record<string, string> = {
'餐饮': '🍜', '交通': '🚗', '购物': '🛒', '娱乐': '🎮',
'居住': '🏠', '医疗': '💊', '工资': '💰', '奖金': '🎁',
'投资': '📈', '其他': '📝'
}
return icons[category] || '📝'
}
private getTypeColor(type: string): string {
return type === '收入' ? '#10B981' : '#EF4444'
}
private switchType(type: string): void {
this.recordType = type
this.selectedCategory = type === '支出' ? '餐饮' : '工资'
}
@Builder
StatCard(title: string, value: string, color: string) {
Column() {
Text(value)
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor(color)
Text(title)
.fontSize(13)
.fontColor('#6B7280')
.margin({ top: 4 })
}
.width('31%')
.height(80)
.justifyContent(FlexAlign.Center)
.backgroundColor(Color.White)
.borderRadius(16)
.shadow({
radius: 10,
color: '#12000000',
offsetX: 0,
offsetY: 4
})
}
@Builder
TypeButton(text: string) {
Button(text)
.height(40)
.layoutWeight(1)
.fontSize(16)
.fontColor(this.recordType === text ? Color.White : '#4B5563')
.backgroundColor(this.recordType === text ? this.getTypeColor(text) : '#F3F4F6')
.borderRadius(20)
.onClick(() => {
this.switchType(text)
})
}
@Builder
CategoryButton(text: string) {
Button(`${this.getCategoryIcon(text)} ${text}`)
.height(42)
.padding({ left: 12, right: 12 })
.fontSize(13)
.fontColor(this.selectedCategory === text ? Color.White : '#4B5563')
.backgroundColor(this.selectedCategory === text ? '#0A59F7' : '#F3F4F6')
.borderRadius(21)
.onClick(() => {
this.selectedCategory = text
})
}
@Builder
FilterButton(text: string) {
Button(text)
.height(36)
.layoutWeight(1)
.fontSize(14)
.fontColor(this.filterType === text ? Color.White : '#4B5563')
.backgroundColor(this.filterType === text ? '#0A59F7' : '#F3F4F6')
.borderRadius(18)
.onClick(() => {
this.filterType = text
})
}
@Builder
BillItem(bill: Bill) {
Row() {
Text(this.getCategoryIcon(bill.category))
.fontSize(28)
.width(44)
.height(44)
.textAlign(TextAlign.Center)
.backgroundColor('#F5F7FA')
.borderRadius(22)
Column() {
Text(bill.category)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#182431')
Text(bill.note)
.fontSize(13)
.fontColor('#6B7280')
.margin({ top: 4 })
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.margin({ left: 12, right: 12 })
Column() {
Text(`${bill.type === '收入' ? '+' : '-'}¥${bill.amount.toFixed(2)}`)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(this.getTypeColor(bill.type))
Text(bill.time)
.fontSize(12)
.fontColor('#9CA3AF')
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.End)
Button('×')
.width(24)
.height(24)
.fontSize(18)
.fontColor('#9CA3AF')
.backgroundColor(Color.Transparent)
.margin({ left: 8 })
.onClick(() => {
this.deleteBill(bill.id)
})
}
.width('100%')
.padding(14)
.margin({ bottom: 10 })
.backgroundColor(Color.White)
.borderRadius(12)
.shadow({
radius: 6,
color: '#08000000',
offsetX: 0,
offsetY: 2
})
}
build() {
Column() {
Text('简洁记账本')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.fontColor('#182431')
.margin({ top: 22 })
Text('基于 HarmonyOS ArkTS 的个人财务管理工具')
.fontSize(14)
.fontColor('#6B7280')
.margin({ top: 8, bottom: 20 })
Row() {
this.StatCard('本月收入', `¥${this.getMonthlyIncome().toFixed(0)}`, '#10B981')
this.StatCard('本月支出', `¥${this.getMonthlyExpense().toFixed(0)}`, '#EF4444')
this.StatCard('本月结余', `¥${this.getMonthlyBalance().toFixed(0)}`, this.getMonthlyBalance() >= 0 ? '#0A59F7' : '#EF4444')
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.margin({ bottom: 16 })
Column() {
Row() {
this.TypeButton('支出')
Blank().width(12)
this.TypeButton('收入')
}
.width('100%')
.margin({ bottom: 16 })
Text('金额')
.fontSize(14)
.fontColor('#6B7280')
.width('100%')
.margin({ bottom: 8 })
Row() {
Text('¥')
.fontSize(32)
.fontWeight(FontWeight.Bold)
.fontColor(this.getTypeColor(this.recordType))
TextInput({
placeholder: '0.00',
text: this.amount
})
.layoutWeight(1)
.height(50)
.fontSize(32)
.fontWeight(FontWeight.Bold)
.fontColor(this.getTypeColor(this.recordType))
.backgroundColor(Color.Transparent)
.type(InputType.Number)
.onChange((value: string) => {
this.amount = value
})
}
.width('100%')
.padding({ left: 16, right: 16 })
.backgroundColor('#F9FAFB')
.borderRadius(12)
.margin({ bottom: 16 })
Text('分类')
.fontSize(14)
.fontColor('#6B7280')
.width('100%')
.margin({ bottom: 8 })
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.recordType === '支出' ? this.expenseCategories : this.incomeCategories, (category: string) => {
this.CategoryButton(category)
}, (category: string) => category)
}
.width('100%')
.margin({ bottom: 16 })
Text('备注')
.fontSize(14)
.fontColor('#6B7280')
.width('100%')
.margin({ bottom: 8 })
TextInput({
placeholder: '添加备注(选填)',
text: this.note
})
.height(44)
.fontSize(15)
.backgroundColor('#F9FAFB')
.borderRadius(12)
.padding({ left: 12, right: 12 })
.margin({ bottom: 16 })
.onChange((value: string) => {
this.note = value
})
Button('记一笔')
.height(48)
.fontSize(18)
.fontColor(Color.White)
.backgroundColor(this.getTypeColor(this.recordType))
.borderRadius(24)
.width('100%')
.onClick(() => {
this.addBill()
})
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(16)
.margin({ bottom: 16 })
.shadow({
radius: 8,
color: '#08000000',
offsetX: 0,
offsetY: 2
})
Text('账单明细')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#182431')
.width('100%')
.margin({ bottom: 12 })
Row() {
this.FilterButton('全部')
Blank().width(8)
this.FilterButton('收入')
Blank().width(8)
this.FilterButton('支出')
}
.width('100%')
.margin({ bottom: 12 })
if (this.getFilteredBills().length === 0) {
Column() {
Text('暂无账单记录')
.fontSize(16)
.fontColor('#6B7280')
Text('开始记录你的第一笔收支吧')
.fontSize(13)
.fontColor('#9CA3AF')
.margin({ top: 8 })
}
.width('100%')
.padding(30)
.backgroundColor(Color.White)
.borderRadius(16)
} else {
List() {
ForEach(this.getFilteredBills(), (bill: Bill) => {
ListItem() {
this.BillItem(bill)
}
}, (bill: Bill) => bill.id.toString())
}
.width('100%')
.layoutWeight(1)
.scrollBar(BarState.Off)
}
}
.width('100%')
.height('100%')
.padding({ left: 18, right: 18 })
.backgroundColor('#F5F7FA')
}
}
八、代码实现说明
1. 定义账单数据结构
项目中使用 Bill 接口描述每一笔账单:
interface Bill {
id: number
type: string
amount: number
category: string
note: string
time: string
date: string
}
字段说明如下:
| 字段 | 作用 |
|---|---|
| id | 账单唯一编号 |
| type | 账单类型(收入/支出) |
| amount | 金额 |
| category | 分类 |
| note | 备注 |
| time | 记录时间 |
| date | 记录日期 |
其中 id 用于区分不同账单,type 用于筛选和统计,date 用于月度统计。
2. 使用 @State 管理页面状态
本项目中使用多个状态变量:
@State recordType: string = '支出'
@State amount: string = ''
@State selectedCategory: string = '支出'
@State note: string = ''
@State filterType: string = '全部'
@State bills: Bill[] = []
@State nextId: number = 1
这些状态变量分别保存记录类型、金额、选中分类、备注、筛选条件和账单列表。当用户切换类型、输入金额、选择分类或添加账单时,状态数据都会发生变化。ArkUI 会根据最新状态自动刷新页面。
3. 添加账单记录
添加账单通过 addBill 方法实现:
private addBill(): void {
let amountNum = parseFloat(this.amount)
if (isNaN(amountNum) || amountNum <= 0) {
return
}
let bill: Bill = {
id: this.nextId,
type: this.recordType,
amount: amountNum,
category: this.selectedCategory,
note: this.note.trim() || '无备注',
time: this.getCurrentTime(),
date: this.getCurrentDate()
}
this.bills = [bill, ...this.bills]
this.nextId++
this.amount = ''
this.note = ''
}
如果金额无效(非数字或小于等于0)则不添加。备注为空时自动填充"无备注"。新账单放在数组最前面,添加后清空金额和备注输入。
4. 类型切换功能
用户可以在收入和支出之间切换:
private switchType(type: string): void {
this.recordType = type
this.selectedCategory = type === '支出' ? '餐饮' : '工资'
}
切换类型时会自动重置选中的分类,避免分类和类型不匹配。
5. 月度统计计算
月度收入、支出和结余通过数组方法动态计算:
private getMonthlyIncome(): number {
let now = new Date()
let currentMonth = `${now.getFullYear()}-${this.formatTwo(now.getMonth() + 1)}`
return this.bills
.filter((bill: Bill) => bill.type === '收入' && bill.date.startsWith(currentMonth))
.reduce((sum: number, bill: Bill) => sum + bill.amount, 0)
}
使用 filter 筛选当月的收入记录,再用 reduce 求和。支出和结余的计算方式类似。
6. 账单筛选功能
筛选通过 filterType 和 getFilteredBills 方法实现:
private getFilteredBills(): Bill[] {
if (this.filterType === '收入') {
return this.bills.filter((bill: Bill) => bill.type === '收入')
}
if (this.filterType === '支出') {
return this.bills.filter((bill: Bill) => bill.type === '支出')
}
return this.bills
}
筛选不会改变原始数组,只会改变当前页面展示内容。
7. 删除账单功能
删除账单通过 deleteBill 方法实现:
private deleteBill(id: number): void {
this.bills = this.bills.filter((bill: Bill) => bill.id !== id)
}
使用唯一id删除账单,避免误删。
8. 分类图标显示
每个分类都有对应的emoji图标,让界面更生动:
private getCategoryIcon(category: string): string {
const icons: Record<string, string> = {
'餐饮': '🍜', '交通': '🚗', '购物': '🛒', '娱乐': '🎮',
'居住': '🏠', '医疗': '💊', '工资': '💰', '奖金': '🎁',
'投资': '📈', '其他': '📝'
}
return icons[category] || '📝'
}
九、运行项目
代码编写完成后,在 DevEco Studio 中选择模拟器或真机运行项目。
运行成功后,页面会显示"简洁记账本"。用户可以切换收入/支出类型,输入金额,选择分类,添加备注,记录账单,查看统计和账单列表。
可以按照以下步骤进行测试:
- 打开应用,检查页面是否正常显示预置的示例账单;
- 检查统计卡片是否显示正确的本月收入、支出和结余;
- 切换收入/支出类型,检查金额颜色和分类是否变化;
- 输入金额;
- 选择不同的分类;
- 输入备注;
- 点击"记一笔"按钮,检查新账单是否出现在列表顶部;
- 检查统计数据是否更新;
- 点击"收入"筛选,检查是否只显示收入记录;
- 点击"支出"筛选,检查是否只显示支出记录;
- 点击账单右上角的×按钮,检查账单是否被删除;
- 检查统计数据是否更新;
- 删除所有账单,检查空状态提示是否显示。
测试结果如下:
| 测试功能 | 测试结果 |
|---|---|
| 页面正常显示 | 成功 |
| 类型切换 | 成功 |
| 金额输入 | 成功 |
| 分类选择 | 成功 |
| 备注输入 | 成功 |
| 添加账单 | 成功 |
| 账单筛选 | 成功 |
| 删除账单 | 成功 |
| 月度统计 | 成功 |
| 空状态提示 | 成功 |
经过测试,应用主要功能可以正常运行,账单数据和统计都能根据用户操作及时更新。
十、开发中遇到的问题
1. 金额输入验证
需要验证输入的金额是否为有效数字,避免NaN或负数:
let amountNum = parseFloat(this.amount)
if (isNaN(amountNum) || amountNum <= 0) {
return
}
2. 类型和分类联动
切换收入/支出类型时,分类选项也要跟着变化,并且默认选中对应类型的第一个分类:
private switchType(type: string): void {
this.recordType = type
this.selectedCategory = type === '支出' ? '餐饮' : '工资'
}
3. 月度统计日期匹配
需要正确匹配当月的账单,使用日期字符串前缀匹配:
bill.date.startsWith(currentMonth)
4. 结余颜色显示
结余为正显示蓝色,为负显示红色,直观反映财务状况:
this.getMonthlyBalance() >= 0 ? '#0A59F7' : '#EF4444'
5. 金额格式化
显示金额时保留两位小数,符合货币显示习惯:
bill.amount.toFixed(2)
十一、总结
本文基于 HarmonyOS 和 ArkTS 实现了一个简洁记账本应用。项目通过 @State 管理页面数据,使用 ArkUI 组件完成页面布局,并实现了收支记录、分类选择、金额输入、账单列表、月度统计和账单筛选等功能。
通过本次实践,主要完成了以下内容:
- 使用
@Entry和@Component创建页面组件; - 使用
@State保存记录类型、金额、分类和账单列表; - 使用
TextInput实现金额和备注输入; - 支持收入和支出两种记录类型;
- 提供丰富的收支分类选择;
- 实现账单添加和删除功能;
- 支持按类型筛选账单;
- 动态计算月度收入、支出和结余;
- 使用emoji图标美化分类显示;
- 使用
List和ForEach渲染账单列表; - 使用条件渲染显示空状态;
- 使用
@Builder封装页面局部结构; - 完成一个可以运行的 HarmonyOS 个人财务管理页面。
这个项目虽然是一个单页面练习,但它完整实现了记账应用的核心功能。它展示了 ArkTS 中表单处理、数字计算、数组统计和状态管理的基本方法。
后续可以在这个基础上继续扩展,例如:
- 增加本地数据持久化;
- 增加多账本支持;
- 增加预算管理功能;
- 增加消费统计图表;
- 增加分类支出占比分析;
- 增加账单编辑功能;
- 增加自定义分类;
- 增加数据导出功能;
- 增加深色模式适配;
- 增加账单搜索功能。
整体来看,简洁记账本应用适合作为 HarmonyOS ArkTS 的练习项目。它功能实用、贴近生活,也能体现 ArkUI 声明式开发和状态驱动页面刷新的基本思想。
更多推荐



所有评论(0)