智算房贷元服务开发

本案例使用元服务创建了一个智能房贷计算器项目,项目功能包括商业贷款、公积金贷款、组合贷款三种计算方式。

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应用开发实战指南》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值