本案例使用元服务创建了一个智能房贷计算器项目,项目功能包括商业贷款、公积金贷款、组合贷款三种计算方式。
1. 案例效果截图
2. 案例运用到的知识点
(1)核心知识点
- 元服务开发流程
- 元服务调试
- 工具类元服务开发
(2)其他知识点
- ArkTS 语言基础
- V2版状态管理:@ComponentV2/@Local/等
- Stage模型
- 自定义组件和组件生命周期
- @Builder装饰器:自定义构建函数
- if/else:条件渲染
- 日志管理类的编写
- MVVM模式
3. 代码结构解读
├──entry/src/main/ets // 代码区
│ ├──components // 组件
│ │ └──DialogComponent.ets // 贷款年限的弹框组件
│ │ └──MonthlyPaymentComponent.ets // 贷款详情展示每月支付的列表组件
│ ├──entryability
│ │ └──EntryAbility.ets
│ ├──interface.ts // 接口类型文件
│ ├──pages
│ │ └──MortgageLoan.ets // 计算器首页
│ ├──utils.ts // 计算通用方法页
│ └──views
│ └──CalculationDetails.ets // 计算详情页
└──entry/src/main/resources // 应用资源目录
4. 房贷计算器首页表搭建
- 基于Navigation进行页面路由搭建。
- 根据UI抽离出 formRadioItemBuilder 和 formInputItemBuilder 实现UI复用。
- 基于openCustomDialog实现更灵活的Dialog组件。
- 监听表单变化将页面表单数据存到mortgageLoanForm里。
- 点击计算跳转到详情页并通过NavPathStack 把表单数据传到详情页方便计算
import { DialogBuilder } from '../components/DialogComponent'
import { InterestRateType, LoanType, MortgageLoanForm, RadioOption } from '../interface'
import { ComponentContent } from '@kit.ArkUI'
import { CalculationDetails } from '../views/CalculationDetails'
@Entry
@ComponentV2
struct MortgageLoan {
@Local mortgageYear: number = 1
private loanTypes: RadioOption[] = [
{
label: "商业",
value: "Commercial"
},
{
label: "公积金",
value: "ProvidentFund"
},
{
label: "组合",
value: "Combination"
}
]
private rates: RadioOption[] = [
{
label: 'LPR',
value: 'LPR',
},
{
label: '基准利率',
value: 'Benchmark',
},
]
@Provider('pageInfo') pageInfo: NavPathStack = new NavPathStack();
@Local mortgageLoanForm: MortgageLoanForm = {
loanAmount: 0,
loanYears: 1,
interestRateType: 'LPR',
calculationMethod: 'EqualPrincipalAndInterest',
loanType: 'Commercial'
}
@Builder
private formRadioItemBuilder(title: string,
options: RadioOption[],
groupName: string,
cb?: (val: string) => void) {
Flex({ alignItems: ItemAlign.Center,
justifyContent: FlexAlign.SpaceBetween }) {
Text(title)
.fontSize(16)
Row({ space: 10 }) {
ForEach(options, (item: RadioOption) => {
Row({ space: 2 }) {
Text(item.label)
Radio({ value: item.value, group: groupName })
.radioStyle({
checkedBackgroundColor: '#d99756'
})
.height(30)
.width(30)
.onChange((val) => {
cb && cb(item.value)
})
}
})
}
.justifyContent(FlexAlign.End)
.layoutWeight(1)
}
}
@Builder
private formInputItemBuilder(title: string,
unit: string,
cb?: (val: string) => void) {
Flex({ alignItems: ItemAlign.Center,
justifyContent: FlexAlign.SpaceBetween }) {
Text(title)
.fontSize(16)
Flex({ alignItems: ItemAlign.Center }) {
TextInput()
.layoutWeight(1)
.backgroundColor(Color.Transparent)
.borderRadius(0)
.type(InputType.Number)
.textAlign(TextAlign.End)
.onChange((val) => {
cb && cb(val)
})
Text(unit)
}
.layoutWeight(1)
}
}
private ctx: UIContext = this.getUIContext();
private contentNode: ComponentContent<Object> =
new ComponentContent(this.ctx,
wrapBuilder(DialogBuilder), this.selectMortgageYear.bind(this));
selectMortgageYear(index: number) {
this.mortgageLoanForm.loanYears = index + 1
this.ctx.getPromptAction().closeCustomDialog(this.contentNode)
this.mortgageLoanForm = JSON.parse(JSON.stringify(this.mortgageLoanForm))
}
@Builder
titleBuilder() {
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Text('房贷计算')
.fontSize(20)
.fontWeight(700)
}
.height('100%')
.width('100%')
}
@Builder
PageMap() {
CalculationDetails()
}
build() {
Column() {
Navigation(this.pageInfo) {
Column({ space: 20 }) {
this.formRadioItemBuilder('贷款类型', this.loanTypes, 'loanType',
(val) => {
this.mortgageLoanForm.loanType = val as LoanType
this.mortgageLoanForm =
JSON.parse(JSON.stringify(this.mortgageLoanForm))
})
if (['ProvidentFund', 'Combination']
.includes(this.mortgageLoanForm.loanType)) {
this.formInputItemBuilder('公积金贷款数', '万元', (val) => {
this.mortgageLoanForm.providentFundLoanAmount =
Number(val) * 10000
})
this.formInputItemBuilder('公积金利率', '%',
(val) => {
this.mortgageLoanForm.providentFundInterestRate =
Number(val) * 0.01
})
}
if (['Commercial', 'Combination']
.includes(this.mortgageLoanForm.loanType)) {
this.formInputItemBuilder('商业贷款', '万元', (val) => {
this.mortgageLoanForm.loanAmount = Number(val) * 10000
})
this.formRadioItemBuilder('利率方式', this.rates,
'interestRateType', (val) => {
this.mortgageLoanForm.interestRateType = val as InterestRateType
})
this.formInputItemBuilder('LPR', '%', (val) => {
this.mortgageLoanForm.lprRate = Number(val) * 0.01
})
this.formInputItemBuilder('基点', '%', (val) => {
this.mortgageLoanForm.basisPoints = Number(val) * 0.01
})
}
Flex({ alignItems: ItemAlign.Center,
justifyContent: FlexAlign.SpaceBetween }) {
Text('按揭年数')
.fontSize(16)
Flex({ alignItems: ItemAlign.Center,
justifyContent: FlexAlign.End }) {
Text(`${this.mortgageLoanForm.loanYears}年
(${this.mortgageLoanForm.loanYears * 12})期`)
.width('100%')
.textAlign(TextAlign.End)
}
.layoutWeight(1)
.onClick(() => {
this.ctx.getPromptAction().openCustomDialog(this.contentNode)
})
}
}
.padding(10)
.margin({ top: 40 })
Button('开始计算')
.backgroundColor('#d99756')
.width('90%')
.margin({ top: 100 })
.onClick(() => {
this.pageInfo.pushPath({ name: 'default',
param: this.mortgageLoanForm });
})
}
.height('100%')
.width('100%')
.navDestination(this.PageMap)
.mode(NavigationMode.Stack)
.title(this.titleBuilder)
.titleMode(NavigationTitleMode.Mini)
.hideBackButton(true)
}
.height('100%')
.width('100%')
}
}
5. 房贷计算核心逻辑实现
(1)等额本息计算函数
功能:根据贷款金额、月利率和总月数,计算等额本息还款方式下每月的还款信息。
步骤:
- 运用等额本息公式计算每月还款金额。
- 借助循环逐月计算利息、本金偿还额和剩余本金。
- 把每月的还款信息存储到 paymentInfoList 数组中并返回。
(2)等额本金计算函数
功能:依据贷款金额、月利率和总月数,计算等额本金还款方式下每月的还款信息。
步骤:
- 算出每月固定的本金偿还额。
- 利用循环逐月计算利息、每月还款金额和剩余本金。
- 将每月的还款信息存入 paymentInfoList 数组并返回。
function calculateEqualPrincipal(loanAmount: number, monthlyInterestRate: numbe,
totalMonths: number) {
const principalRepayment = loanAmount / totalMonths;
const paymentInfoList = [];
let remainingPrincipal = loanAmount;
for (let i = 0; i < totalMonths; i++) {
const interestRepayment = remainingPrincipal * monthlyInterestRate;
const monthlyPayment = principalRepayment + interestRepayment;
remainingPrincipal -= principalRepayment;
paymentInfoList.push(
new MonthlyPaymentInfo(
monthlyPayment,
principalRepayment,
interestRepayment,
remainingPrincipal)
);
}
return paymentInfoList;
}
(3)商业房贷计算函数
功能:按照 MortgageLoanForm 中的信息,计算商业房贷的还款信息。
步骤:
- 从 mortgageLoanForm 中解构出所需的参数。
- 把贷款年限转换为总月数。
- 根据利率方式(LPR 或其他)计算月利率。
- 依据计算方式(等额本息或等额本金)调用相应的计算函数。
- 返回每月还款信息数组。
export function calculateMortgagePayment(mortgageLoanForm: MortgageLoanForm) {
const {
loanAmount,
loanYears,
interestRateType,
lprRate,
basisPoints,
calculationMethod
} = mortgageLoanForm;
const totalMonths = loanYears * 12;
let monthlyInterestRate;
if (interestRateType === 'LPR') {
monthlyInterestRate = (lprRate + basisPoints / 10000) / 12;
} else {
// 这里可以根据基准利率的逻辑进行处理,暂时简化为 0
monthlyInterestRate = 0;
}
if (calculationMethod === 'EqualPrincipalAndInterest') {
return calculateEqualPrincipalAndInterest(loanAmount,
monthlyInterestRate,
totalMonths);
} else if (calculationMethod === 'EqualPrincipal') {
return calculateEqualPrincipal(loanAmount,
monthlyInterestRate,
totalMonths);
}
return [];
}
(4)公积金贷款计算函数
功能:根据 MortgageLoanForm 中的信息,计算公积金贷款的还款信息。
步骤:
- 从 mortgageLoanForm 中解构出公积金贷款相关的参数。
- 将贷款年限转换为总月数。
- 计算月利率。
- 根据计算方式(等额本息或等额本金)调用相应的计算函数。
- 返回每月还款信息数组。
export function calculateProvidentFundLoan(mortgageLoanForm: MortgageLoanForm) {
const { providentFundLoanAmount,
providentFundInterestRate,
loanYears,
calculationMethod } = mortgageLoanForm;
const totalMonths = loanYears * 12;
const monthlyInterestRate = providentFundInterestRate / 12;
if (calculationMethod === 'EqualPrincipalAndInterest') {
return calculateEqualPrincipalAndInterest(providentFundLoanAmount,
monthlyInterestRate,
totalMonths);
} else if (calculationMethod === 'EqualPrincipal') {
return calculateEqualPrincipal(providentFundLoanAmount,
monthlyInterestRate,
totalMonths);
}
return [];
}
(5)组合贷款计算函数
功能:根据 MortgageLoanForm 中的信息,计算组合贷款(商业贷与公积金贷组合)的还款信息。
步骤:
- 分别调用 calculateMortgagePayment 和 calculateProvidentFundLoan 函数,计算商业贷款和公积金贷款的还款信息。
- 遍历两个还款信息数组,将每月的商业贷款和公积金贷款的还款金额、本金偿还额、利息偿还额和剩余本金相加。
- 将相加后的结果存储到 combinedPaymentInfo 数组中并返回。
export function calculateCombinedLoan(mortgageLoanForm: MortgageLoanForm) {
const commercialPaymentInfo =
calculateMortgagePayment(mortgageLoanForm);
const providentFundPaymentInfo =
calculateProvidentFundLoan(mortgageLoanForm);
const combinedPaymentInfo: MonthlyPaymentInfo[] = [];
for (let i = 0; i < commercialPaymentInfo.length; i++) {
const commercial = commercialPaymentInfo[i];
const providentFund = providentFundPaymentInfo[i];
const totalMonthlyPayment = commercial.monthlyPayment
+ providentFund.monthlyPayment;
const totalPrincipalRepayment = commercial.principalRepayment
+ providentFund.principalRepayment;
const totalInterestRepayment = commercial.interestRepayment
+ providentFund.interestRepayment;
const totalRemainingPrincipal = commercial.remainingPrincipal
+ providentFund.remainingPrincipal;
combinedPaymentInfo.push(
new MonthlyPaymentInfo(totalMonthlyPayment,
totalPrincipalRepayment,
totalInterestRepayment,
totalRemainingPrincipal)
);
}
return combinedPaymentInfo;
}
5. 房贷计算页
- 通过getParamByName方法拿到表单页传入的参数
- 通过calculate方法实现计算贷款方式
- 基于Tabs实现等额本息和等额本金的切换
- 通过MonthlyPaymentComponent展示贷款内容
import { MonthlyPaymentComponent } from "../components/MonthlyPaymentComponent";
import { MonthlyPaymentInfo, MortgageLoanForm } from "../interface";
import { calculateCombinedLoan,
calculateMortgagePayment,
calculateProvidentFundLoan } from "../utils";
@ComponentV2
export struct CalculationDetails {
@Consumer('pageInfo') pageInfo: NavPathStack = new NavPathStack();
@Local mortgageLoanForm: MortgageLoanForm = {
loanAmount: 0,
loanYears: 1,
interestRateType: 'LPR',
calculationMethod: 'EqualPrincipalAndInterest',
loanType: "Commercial"
}
@Local calculatorResult: MonthlyPaymentInfo[] = []
@Builder
listItemGroupTitleBuilder(title: string) {
Text(title)
.width('100%')
.fontSize(20)
.fontWeight(500)
}
aboutToAppear() {
const mortgageLoanForms =
this.pageInfo.getParamByName('default') as MortgageLoanForm[]
this.mortgageLoanForm = mortgageLoanForms[0]
this.calculate()
}
calculate () {
if (this.mortgageLoanForm.loanType === 'Commercial') {
this.calculatorResult =
calculateMortgagePayment((this.mortgageLoanForm))
return
}
if (this.mortgageLoanForm.loanType === 'ProvidentFund') {
this.calculatorResult =
calculateProvidentFundLoan((this.mortgageLoanForm))
return
}
if (this.mortgageLoanForm.loanType === 'Combination') {
this.calculatorResult =
calculateCombinedLoan(this.mortgageLoanForm)
return
}
}
build() {
NavDestination() {
Tabs() {
TabContent() {
MonthlyPaymentComponent({ calculatorResult: this.calculatorResult })
}
.width('100%')
.height('100%')
.tabBar('等额本息')
TabContent() {
MonthlyPaymentComponent({ calculatorResult: this.calculatorResult })
}
.width('100%')
.height('100%')
.tabBar('等额本息')
}
.width('100%')
.height('100%')
.onChange((index) => {
this.mortgageLoanForm.calculationMethod =
index === 0 ? 'EqualPrincipalAndInterest' : 'EqualPrincipal'
this.calculate()
})
}.width('100%')
.height('100%')
.title('计算结果')
}
}
6. 尾声
完整代码下载:关注Raink老师公众号,到下载区下载。
学习鸿蒙开发,请观看视频教程:《HarmonyOS应用开发实战指南》