【HarmonyOS ArkUI】自助取款机 - Demo分享

82 阅读4分钟

ATM record.gif

一、项目概述

本Demo是一个基于ArkTS语言的HarmonyOS ATM 应用简易实现,主要功能包括插卡进入系统、余额查询、存款与取款操作。该项目采用声明式UI设计和组件化开发思想,实现了基础的银行ATM交互逻辑。


二、技术架构

1. 项目结构

2. 核心技术栈

  • ArkTS:基于TypeScript扩展的声明式编程语言。
  • HarmonyOS SDK:用于调用系统级能力如弹窗提示等。
  • 组件化开发模式:通过装饰器实现组件间通信。

三、核心功能实现

1. 状态管理

使用 @Provide@Consume 装饰器进行跨层级组件状态共享:

// Index.ets - 提供全局状态
@Provide balanceMoney: number = 0
@Provide enterAmount: string = ''
@Provide isStart: boolean = false

// DisplayReveal & KeyBord - 消费全局状态
@Consume balanceMoney: number
@Consume enterAmount: string
@Consume isStart: boolean

作用机制: @Provide 提供的状态可在其子组件树中被任意层级的 @Consume 消费。 状态更新时,所有消费该状态的组件会自动刷新 UI

2. 主要组件解析

a. TopTitle 顶部标题栏,展示静态内容“ATM”。
@Component
struct TopTitle {
  build() {
    Row(){
      Text('ATM')
        .fontSize(18)
        .fontWeight(600)
        .fontColor(Color.Blue)
    }
    .justifyContent(FlexAlign.Center)
    .border({width:{bottom:1},color:Color.Blue})
    .padding(5)
    .width('100%')
  }
}
b. DisplayReveal显示账户余额及输入框,根据是否插卡状态切换显示内容。
@Component
struct DisplayReveal {
  @Consume balanceMoney:number
  @Consume enterAmount: string
  @Consume isStart: boolean
  build() {
    Column(){
      Row(){
        Text() {
          if (this.isStart) {
            Span('余额:  ¥')
            Span(`${this.balanceMoney.toFixed(2)}`)
          } else {
            Span('请先插卡进入银行管理系统..')
          }

        }
        .layoutWeight(1)
        .fontColor('#405046')
      }
      .border({width:{bottom:1},style:BorderStyle.Dashed})
      .padding({left: 16,right: 16,top: 10,bottom: 10})
      .width('100%')
      TextInput({placeholder:'请输入金额...',text: $$this.enterAmount})
        .type(InputType.Number)
        .borderRadius(0)
        .backgroundColor(Color.Transparent)
    }
    .backgroundColor('#798d6b')
    .borderRadius({topLeft: 10, topRight: 10})
  }
}
c. KeyBord 虚拟键盘组件,处理插卡/拔卡、金额输入、存取款等业务逻辑。
@Component
struct KeyBord {
  @Consume balanceMoney: number
  @Consume enterAmount: string
  @Consume isStart: boolean
  build() {
    Column(){
      Grid(){
        ForEach(Array(15).fill(''), (t: string, i: number) => {
          GridItem(){
            if (i < 2) {
              ControlButton({text: `${i == 0 ? '插卡' : '拔卡'}`, fontColor: `${i ==0 ? '#768fff' : '#b168fc'}` })
            } else if (i === 2){
              ControlButton({icon: 'ic_del_arrow', imgFillColor: Color.Red})
            } else if (i === 12) {
              ControlButton({text: '取', bgcColor: Color.Red})
            } else if (i === 14) {
              ControlButton({text: '存', bgcColor: Color.Blue})
            } else {
              Text(`${(i - 3) === 10 ? 0 : ((i - 3) + 1)}`)
                .width(60)
                .height(60)
                .borderRadius(50)
                .fontSize(24)
                .fontColor(Color.White)
                .textAlign(TextAlign.Center)
                .backgroundColor('#5e636c')
            }
          }
          .onClick(() => {
            if (!this.isStart && i !== 0) {
              promptAction.showToast({message: '请先插卡!谢谢!', textColor: Color.Red})
              return
            }

            if (i === 2) { // 删除按键
              this.enterAmount = this.enterAmount.slice(0, -1)
              return
            }

            if (i === 0) { //进入按键(插卡)
              if (this.isStart) {
                promptAction.showToast({message: '请勿重复插卡!谢谢!', textColor: Color.Red})
                return;
              }
              this.isStart = true
              this.balanceMoney = 100000
              this.enterAmount = ''
              promptAction.showToast({
                message: '欢迎进入银行账户资金管理系统!',
                textColor:Color.Pink,
                duration: 3000})
              return
            }

            if (i === 1){ // 退出按键(拔卡)
              this.balanceMoney = 0
              this.enterAmount = ''
              promptAction.showToast({message: '谢谢,欢迎下次光临,已拔卡!',textColor:Color.Blue})
              this.isStart = false
              return
            }

            if (i === 12 || i === 14) {
              if (!this.enterAmount.trim()) {
                promptAction.showToast({message: '请先输入金额!',textColor:Color.Red})
                return
              }
              if (i === 12) { // 取钱

                if ( Number(this.enterAmount) > this.balanceMoney ) {
                  promptAction.showToast({message: '余额不足!',textColor:Color.Red})
                  return
                }
                this.balanceMoney -= Number(this.enterAmount)
                promptAction.showToast({
                  message: `已取出人民币${Number(this.enterAmount).toFixed(2)}元,剩余人民币${this.balanceMoney.toFixed(2)}元,请继续操作!`,
                  textColor:Color.Green,
                  duration: 2000
                })

              } else { // 存钱

                this.balanceMoney += Number(this.enterAmount)
                promptAction.showToast({
                  message: `存入人民币${Number(this.enterAmount).toFixed(2)}元,余额${this.balanceMoney.toFixed(2)}元,10个小目标指日可待!`,
                  textColor:Color.Green,
                  duration: 2000
                })
              }
              this.enterAmount = ''
              return;
            }


            const entry = computedKeyBordNum(i - 3)
            this.enterAmount += `${entry}`
          })
        })
      }
      .rowsGap(10)
      .columnsTemplate('1fr 1fr 1fr')
      .borderStyle(BorderStyle.Dotted)
    }
    .height(380)
    .padding({top: 15})
    .backgroundColor('#eaecf3')

  }
}
  • 按钮布局:使用 Grid + ForEach 实现15个按键。
  • 事件绑定:每个按键通过 onClick 处理不同行为。
  • 逻辑控制:判断用户是否已插卡,验证金额合法性等。
d. ControlButton 通用按钮组件,支持文本或图标形式渲染。
@Component
struct ControlButton {
  @Prop text: string = ''
  @Prop bgcColor: string | Color =  '#cfd2e0'
  @Prop icon: string = ''
  @Prop fontSize: number = 20
  @Prop fontColor: string | Color = '#fff'
  @Prop imgFillColor: string | Color

  @Styles
  controlStyles(){
    .width(60)
    .height(60)
    .borderRadius(50)
    .backgroundColor(this.bgcColor)
  }

  build() {
    if (this.icon) {
      Image($r(`app.media.${this.icon}`))
        .controlStyles()
        .padding(9)
        .fillColor(this.imgFillColor)

    } else {
      Text(`${this.text}`)
        .fontSize(this.fontSize)
        .fontColor(this.fontColor)
        .textAlign(TextAlign.Center)
        .controlStyles()
    }
  }

}

四、关键业务逻辑

1. 插卡/拔卡流程

插卡 (i === 0):初始化账户余额为100000元,设置 isStart = true

if (i === 0) {
  if (this.isStart) {
    promptAction.showToast({message: '请勿重复插卡!谢谢!'})
    return;
  }
  this.isStart = true
  this.balanceMoney = 100000
  this.enterAmount = ''
  promptAction.showToast({message: '欢迎进入银行账户资金管理系统!'})
}

拔卡 (i === 1):重置所有状态,并提示用户已拔卡。

if (i === 1){
  this.balanceMoney = 0
  this.enterAmount = ''
  promptAction.showToast({message: '谢谢,欢迎下次光临,已拔卡!'})
  this.isStart = false
}

2. 金额输入

数字键点击后调用 computedKeyBordNum(i - 3) 映射对应数字

const computedKeyBordNum = (entryNum: number) => {
  return entryNum == 10 ? '0' : `${entryNum + 1}`
}

此函数将索引值转换为对应的数字字符,例如:

  • entryNum = 0 → 1
  • entryNum = 10 → '0' 然后拼接到 enterAmount 字符串中,用于后续取款或存款判断。

3. 存取款操作

取款 (i === 12):检查余额是否足够,不足则提示。

if ( Number(this.enterAmount) > this.balanceMoney ) {
  promptAction.showToast({message: '余额不足!'})
  return
}
this.balanceMoney -= Number(this.enterAmount)
promptAction.showToast({
  message: `已取出人民币${Number(this.enterAmount).toFixed(2)}元...`
})

存款 (i === 14):直接增加余额并提示成功信息。

this.balanceMoney += Number(this.enterAmount)
promptAction.showToast({
  message: `存入人民币${Number(this.enterAmount).toFixed(2)}元...`
})

五、UI 设计亮点

  • 响应式布局:使用 FlexGrid 实现自适应布局。
  • 颜色主题统一:采用蓝色系作为主色调,提升用户体验。
  • 交互反馈机制:利用 promptAction.showToast 提供及时操作反馈。

六、可拓展性建议

  1. 数据持久化:当前余额仅在内存中维护,后续可接入数据库或本地存储。
  2. 安全性增强:添加密码验证环节,防止非法访问。
  3. 多语言支持:适配国际化需求,根据不同地区显示相应语言提示。
  4. 动画效果优化:对按钮点击、金额变化等添加过渡动画,提升视觉体验。

七、总结

该ATM Demo 展示了如何利用ArkTS构建一个结构清晰、易于维护的小型金融类应用。通过合理划分组件职责并有效管理状态,可以快速搭建出具备基本交互能力的应用原型。未来可根据实际需求进一步完善安全机制与复杂业务流程。