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管理页面状态; - 支持商业贷款计算;
- 支持等额本息还款方式;
- 支持等额本金还款方式;
- 贷款金额输入(万元);
- 贷款期限选择(年);
- 年利率输入(%);
- 实时计算月供;
- 计算总还款额;
- 计算总利息;
- 显示首月/末月月供(等额本金);
- 使用
@Builder封装输入项和结果卡片; - 完成一个可以运行的房贷计算器页面。
这个项目虽然是单页面应用,但它实现了专业的房贷计算功能,有完整的输入、计算和结果展示流程,比普通静态页面更适合练习 ArkTS。
二、技术栈
| 类型 | 内容 |
|---|---|
| 开发方向 | HarmonyOS 应用开发 |
| 开发语言 | ArkTS |
| UI 框架 | ArkUI |
| SDK 版本 | HarmonyOS API 23 及以上 |
| 工程模型 | Stage 模型 |
| 核心组件 | Text / TextInput / Button / Radio / Column / Row |
| 状态管理 | @State |
| 数据处理 | 财务计算 / 数学公式 / 实时响应 |
| 项目入口 | entry/src/main/ets/pages/Index.ets |
| 运行平台 | 模拟器或真机 |
本项目是 HarmonyOS 原生 ArkTS 项目。页面主体由 ArkUI 组件构建,核心逻辑写在 Index.ets 文件中,不依赖后端接口,也不需要额外配置数据库。
三、为什么选择房贷计算器项目
房贷计算器适合作为 ArkTS 练习项目,主要有以下几个原因。
第一,实用性强。房贷计算是很多人买房时都会用到的功能,有很强的实际需求。
第二,业务场景真实。购房决策需要了解月供压力和利息成本,是非常实用的财务工具。
第三,适合练习状态管理。贷款金额、期限、利率、还款方式都属于页面状态。状态变化后,页面会自动刷新。
第四,适合练习表单交互。数字输入、单选按钮等表单控件的使用是前端开发基础。
第五,适合练习复杂计算。等额本息和等额本金有不同的计算公式,可以锻炼逻辑能力。
第六,扩展空间比较大。基础功能完成后,可以继续增加公积金贷款、组合贷、提前还款计算、还款明细和税费计算。
在本项目中,输入参数和还款方式是核心数据。所有计算结果都基于这些参数实时计算。
四、功能规则说明
还款方式:
| 方式 | 特点 |
|---|---|
| 等额本息 | 每月还款额相同,前期利息多本金少 |
| 等额本金 | 每月本金相同,月供逐月递减,总利息少 |
计算公式:
等额本息:
- 月供 = [贷款本金×月利率×(1+月利率)^还款月数] ÷ [(1+月利率)^还款月数-1]
- 总还款 = 月供 × 还款月数
- 总利息 = 总还款 - 贷款本金
等额本金:
- 首月月供 = (贷款本金÷还款月数) + (贷款本金-已还本金)×月利率
- 每月递减 = 贷款本金÷还款月数×月利率
- 总利息 = (还款月数+1)×贷款本金×月利率÷2
- 总还款 = 贷款本金 + 总利息
默认参数:
- 贷款金额:100万元
- 贷款期限:30年
- 年利率:4.9%(商业贷款基准利率)
- 还款方式:等额本息
五、项目结构
本项目主要修改首页文件:
entry
└── src
└── main
└── ets
└── pages
└── Index.ets
其中:
| 文件 | 作用 |
|---|---|
| Index.ets | 编写页面结构、状态数据和房贷计算逻辑 |
本文不涉及复杂路由,也不需要额外创建多个页面。对于练习项目来说,把主要逻辑集中在一个 Index.ets 文件中更方便理解。
六、核心实现思路
本项目的核心流程如下:
- 使用
@State保存贷款金额、期限、年利率和还款方式; - 用户输入贷款金额(万元);
- 用户选择贷款期限;
- 用户输入年利率;
- 用户选择还款方式;
- 参数变化时自动重新计算;
- 根据还款方式使用对应公式计算;
- 显示月供、总还款、总利息等结果;
- 等额本金时显示首月和末月月供;
- 格式化金额显示,添加千分位分隔符。
项目中最重要的状态变量如下:
@State loanAmount: string = '100'
@State loanYears: number = 30
@State interestRate: string = '4.9'
@State repaymentType: number = 0
其中:
| 状态变量 | 作用 |
|---|---|
| loanAmount | 贷款金额(万元) |
| loanYears | 贷款期限(年) |
| interestRate | 年利率(%) |
| repaymentType | 还款方式(0=等额本息,1=等额本金) |
这些参数是计算的基础,任何一个变化都会触发重新计算。
七、Index.ets 完整代码
打开文件:
entry/src/main/ets/pages/Index.ets
将其中内容替换为下面代码:
@Entry
@Component
struct Index {
@State loanAmount: string = '100'
@State loanYears: number = 30
@State interestRate: string = '4.9'
@State repaymentType: number = 0
private yearOptions: number[] = [5, 10, 15, 20, 25, 30]
private getMonthlyPayment(): number {
let principal = this.getPrincipal()
let months = this.loanYears * 12
let monthlyRate = this.getMonthlyRate()
if (principal <= 0 || monthlyRate <= 0 || months <= 0) return 0
if (this.repaymentType === 0) {
return principal * monthlyRate * Math.pow(1 + monthlyRate, months) /
(Math.pow(1 + monthlyRate, months) - 1)
} else {
return principal / months + principal * monthlyRate
}
}
private getLastMonthlyPayment(): number {
if (this.repaymentType === 0) return this.getMonthlyPayment()
let principal = this.getPrincipal()
let months = this.loanYears * 12
let monthlyRate = this.getMonthlyRate()
let monthlyPrincipal = principal / months
return monthlyPrincipal + monthlyPrincipal * monthlyRate
}
private getTotalPayment(): number {
let principal = this.getPrincipal()
let months = this.loanYears * 12
let monthlyRate = this.getMonthlyRate()
if (this.repaymentType === 0) {
return this.getMonthlyPayment() * months
} else {
return principal + (months + 1) * principal * monthlyRate / 2
}
}
private getTotalInterest(): number {
return this.getTotalPayment() - this.getPrincipal()
}
private getPrincipal(): number {
let amount = parseFloat(this.loanAmount)
return isNaN(amount) ? 0 : amount * 10000
}
private getMonthlyRate(): number {
let rate = parseFloat(this.interestRate)
return isNaN(rate) ? 0 : rate / 100 / 12
}
private formatMoney(amount: number): string {
return amount.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',')
}
@Builder
InputItem(label: string, value: string, unit: string, onChange: (v: string) => void, placeholder?: string) {
Row() {
Text(label)
.fontSize(15)
.fontColor('#182431')
.width(90)
TextInput({
text: value,
placeholder: placeholder || ''
})
.layoutWeight(1)
.height(44)
.fontSize(16)
.textAlign(TextAlign.End)
.backgroundColor(Color.Transparent)
.type(InputType.Number)
.onChange(onChange)
Text(unit)
.fontSize(15)
.fontColor('#6B7280')
.margin({ left: 8 })
}
.width('100%')
.padding({ left: 16, right: 16 })
.height(56)
.backgroundColor('#F9FAFB')
.borderRadius(12)
.margin({ bottom: 12 })
}
@Builder
ResultItem(label: string, value: string, highlight: boolean = false) {
Row() {
Text(label)
.fontSize(14)
.fontColor('#6B7280')
Blank()
Text(value)
.fontSize(highlight ? 24 : 18)
.fontWeight(highlight ? FontWeight.Bold : FontWeight.Medium)
.fontColor(highlight ? '#0A59F7' : '#182431')
}
.width('100%')
.padding({ top: 12, bottom: 12 })
}
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 })
Column() {
Text('贷款信息')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#182431')
.width('100%')
.margin({ bottom: 12 })
this.InputItem('贷款金额', this.loanAmount, '万元', (v: string) => {
this.loanAmount = v
}, '请输入金额')
Text('贷款期限')
.fontSize(15)
.fontColor('#182431')
.width('100%')
.margin({ bottom: 8 })
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Start }) {
ForEach(this.yearOptions, (year: number) => {
Button(`${year}年`)
.height(36)
.padding({ left: 16, right: 16 })
.fontSize(14)
.margin({ right: 8, bottom: 8 })
.fontColor(this.loanYears === year ? Color.White : '#4B5563')
.backgroundColor(this.loanYears === year ? '#0A59F7' : '#F3F4F6')
.borderRadius(18)
.onClick(() => {
this.loanYears = year
})
}, (y: number) => y.toString())
}
.width('100%')
.margin({ bottom: 12 })
this.InputItem('年利率', this.interestRate, '%', (v: string) => {
this.interestRate = v
}, '请输入利率')
Text('还款方式')
.fontSize(15)
.fontColor('#182431')
.width('100%')
.margin({ bottom: 8 })
Row() {
Row() {
Radio({ value: '0', group: 'repayType' })
.checked(this.repaymentType === 0)
.onChange((isChecked: boolean) => {
if (isChecked) this.repaymentType = 0
})
Text('等额本息')
.fontSize(15)
.fontColor('#182431')
.margin({ left: 8 })
}
.onClick(() => { this.repaymentType = 0 })
Blank().width(40)
Row() {
Radio({ value: '1', group: 'repayType' })
.checked(this.repaymentType === 1)
.onChange((isChecked: boolean) => {
if (isChecked) this.repaymentType = 1
})
Text('等额本金')
.fontSize(15)
.fontColor('#182431')
.margin({ left: 8 })
}
.onClick(() => { this.repaymentType = 1 })
}
.width('100%')
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(16)
.margin({ bottom: 16 })
.shadow({
radius: 8,
color: '#08000000',
offsetX: 0,
offsetY: 2
})
Column() {
Text('计算结果')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#182431')
.width('100%')
.margin({ bottom: 8 })
this.ResultItem(
this.repaymentType === 0 ? '每月月供' : '首月月供',
`${this.formatMoney(this.getMonthlyPayment())} 元`,
true
)
if (this.repaymentType === 1) {
this.ResultItem('末月月供', `${this.formatMoney(this.getLastMonthlyPayment())} 元`)
Divider().color('#F3F4F6')
}
this.ResultItem('还款总额', `${this.formatMoney(this.getTotalPayment())} 元`)
Divider().color('#F3F4F6')
this.ResultItem('支付利息', `${this.formatMoney(this.getTotalInterest())} 元`)
}
.width('100%')
.padding(20)
.backgroundColor(Color.White)
.borderRadius(16)
.shadow({
radius: 8,
color: '#08000000',
offsetX: 0,
offsetY: 2
})
Text('* 计算结果仅供参考,实际以银行审批为准')
.fontSize(12)
.fontColor('#9CA3AF')
.margin({ top: 16 })
}
.width('100%')
.height('100%')
.padding({ left: 18, right: 18 })
.backgroundColor('#F5F7FA')
}
}
八、代码实现说明
1. 等额本息计算
使用等额本息公式计算每月还款额:
return principal * monthlyRate * Math.pow(1 + monthlyRate, months) /
(Math.pow(1 + monthlyRate, months) - 1)
总还款 = 月供 × 月数,总利息 = 总还款 - 本金。
2. 等额本金计算
等额本金首月月供最高,逐月递减:
// 首月月供 = 月还本金 + 全额本金月利息
return principal / months + principal * monthlyRate
// 末月月供 = 月还本金 + 剩余1个月本金利息
return monthlyPrincipal + monthlyPrincipal * monthlyRate
总利息使用等差数列求和公式计算。
3. 实时计算
参数变化时自动重新计算,不需要点击计算按钮,提供流畅体验。
4. 金额格式化
使用正则表达式添加千分位分隔符,让大额数字更易读:
return amount.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',')
5. 期限快捷选择
提供常用贷款期限的快捷按钮,点击即可选择,比输入更方便。
6. 还款方式切换
使用Radio组件选择还款方式,切换后自动重新计算,等额本金时额外显示末月月供。
九、运行项目
代码编写完成后,在DevEco Studio中运行项目。测试不同金额、期限、利率和还款方式的计算结果,验证公式正确性,检查结果格式化是否正确。
十、开发中遇到的问题
- 财务公式准确性:确保等额本息和等额本金公式正确
- 边界情况处理:输入0或非法值时返回0,避免NaN
- 金额格式化:正确添加千分位分隔符,保留两位小数
- 等额本金特殊显示:需要显示首月和末月月供,因为每月递减
十一、总结
本文基于HarmonyOS和ArkTS实现了一个房贷计算器。项目通过@State管理输入参数,使用专业的财务公式实现等额本息和等额本金计算,实时显示月供、总还款和总利息。这个项目展示了ArkTS中表单输入、实时计算和数学公式应用的基本方法,适合作为入门练习项目。
更多推荐



所有评论(0)