HarmonyOS 应用开发基础案例(十):制作我的计算器

29 阅读13分钟

1. 界面布局

新建项目 MyCalculator,开始布局。

2. 静态布局

代码如下:

// etc/pages/Index.ets
@Entry
@Component
struct Index {
  build() {
    Column() {
      /**
       * 运算区
       */
      Column() {
        TextInput({ text: '12x13' })
          .height('100%')
          .fontSize(32)
          .enabled(false)
          .fontColor(Color.Black)
          .textAlign(TextAlign.End)
          .backgroundColor('#f5f5f5')
      }
      .width('100%')
      .height(86)
      .alignItems(HorizontalAlign.End)
      .margin({
        right: 20,
        top: 120
      })

      /**
       * 结果区
       */
      Column() {
        Text('123')
          .fontSize('32fp')
          .fontColor('#182431')
      }
      .width('100%')
      .height(42)
      .alignItems(HorizontalAlign.End)
      .margin({
        right: 20,
        bottom: 64
      })

      /**
       * 输入面板区
       */
      Column() {
        Row() {
          // 多行子元素
          Column() {
            // 多列子元素
            Column() {
              Column() {
                Image($r('app.media.ic_clean'))
                  .width(32)
                  .height(32)
              }
              .width(70)
              .height(60)
              .borderWidth(1)
              .borderColor("#f5f5f5")
              .borderRadius(36)
              .backgroundColor(Color.White)
              .alignItems(HorizontalAlign.Center)
              .justifyContent(FlexAlign.Center)
            }
            .layoutWeight(1)
            .width('100%')
            .justifyContent(FlexAlign.Center)

            Column() {
              Column() {
                Text('7')
                  .fontSize('32fp')
                  .width(19)
                  .height(43)
              }
              .width(70)
              .height(60)
              .borderWidth(1)
              .borderColor("#f5f5f5")
              .borderRadius(36)
              .backgroundColor(Color.White)
              .alignItems(HorizontalAlign.Center)
              .justifyContent(FlexAlign.Center)
            }
            .layoutWeight(1)
            .width('100%')
            .justifyContent(FlexAlign.Center)

            Column() {
              Column() {
                Text('4')
                  .fontSize('32fp')
                  .width(19)
                  .height(43)
              }
              .width(70)
              .height(60)
              .borderWidth(1)
              .borderColor("#f5f5f5")
              .borderRadius(36)
              .backgroundColor(Color.White)
              .alignItems(HorizontalAlign.Center)
              .justifyContent(FlexAlign.Center)
            }
            .layoutWeight(1)
            .width('100%')
            .justifyContent(FlexAlign.Center)

            Column() {
              Column() {
                Text('1')
                  .fontSize('32fp')
                  .width(19)
                  .height(43)
              }
              .width(70)
              .height(60)
              .borderWidth(1)
              .borderColor("#f5f5f5")
              .borderRadius(36)
              .backgroundColor(Color.White)
              .alignItems(HorizontalAlign.Center)
              .justifyContent(FlexAlign.Center)
            }
            .layoutWeight(1)
            .width('100%')
            .justifyContent(FlexAlign.Center)

            Column() {
              Column() {
                Text('%')
                  .fontSize('32fp')
                  .width(25)
                  .height(43)
              }
              .width(70)
              .height(60)
              .borderWidth(1)
              .borderColor("#f5f5f5")
              .borderRadius(36)
              .backgroundColor(Color.White)
              .alignItems(HorizontalAlign.Center)
              .justifyContent(FlexAlign.Center)
            }
            .layoutWeight(1)
            .width('100%')
            .justifyContent(FlexAlign.Center)
          }
          .layoutWeight(1)
          .margin({
            top: 30,
            bottom: 30
          })

          Column() {
            // 多列子元素
            Column() {
              Column() {
                Image($r('app.media.ic_div'))
                  .width(32)
                  .height(32)
              }
              .width(70)
              .height(60)
              .borderWidth(1)
              .borderColor("#f5f5f5")
              .borderRadius(36)
              .backgroundColor(Color.White)
              .alignItems(HorizontalAlign.Center)
              .justifyContent(FlexAlign.Center)
            }
            .layoutWeight(1)
            .width('100%')
            .justifyContent(FlexAlign.Center)

            Column() {
              Column() {
                Text('8')
                  .fontSize('32fp')
                  .width(19)
                  .height(43)
              }
              .width(70)
              .height(60)
              .borderWidth(1)
              .borderColor("#f5f5f5")
              .borderRadius(36)
              .backgroundColor(Color.White)
              .alignItems(HorizontalAlign.Center)
              .justifyContent(FlexAlign.Center)
            }
            .layoutWeight(1)
            .width('100%')
            .justifyContent(FlexAlign.Center)

            Column() {
              Column() {
                Text('5')
                  .fontSize('32fp')
                  .width(19)
                  .height(43)
              }
              .width(70)
              .height(60)
              .borderWidth(1)
              .borderColor("#f5f5f5")
              .borderRadius(36)
              .backgroundColor(Color.White)
              .alignItems(HorizontalAlign.Center)
              .justifyContent(FlexAlign.Center)
            }
            .layoutWeight(1)
            .width('100%')
            .justifyContent(FlexAlign.Center)

            Column() {
              Column() {
                Text('2')
                  .fontSize('32fp')
                  .width(19)
                  .height(43)
              }
              .width(70)
              .height(60)
              .borderWidth(1)
              .borderColor("#f5f5f5")
              .borderRadius(36)
              .backgroundColor(Color.White)
              .alignItems(HorizontalAlign.Center)
              .justifyContent(FlexAlign.Center)
            }
            .layoutWeight(1)
            .width('100%')
            .justifyContent(FlexAlign.Center)

            Column() {
              Column() {
                Text('0')
                  .fontSize('32fp')
                  .width(19)
                  .height(43)
              }
              .width(70)
              .height(60)
              .borderWidth(1)
              .borderColor("#f5f5f5")
              .borderRadius(36)
              .backgroundColor(Color.White)
              .alignItems(HorizontalAlign.Center)
              .justifyContent(FlexAlign.Center)
            }
            .layoutWeight(1)
            .width('100%')
            .justifyContent(FlexAlign.Center)
          }
          .layoutWeight(1)
          .margin({
            top: 30,
            bottom: 30
          })

          Column() {
            // 多列子元素
            Column() {
              Column() {
                Image($r('app.media.ic_mul'))
                  .width(32)
                  .height(32)
              }
              .width(70)
              .height(60)
              .borderWidth(1)
              .borderColor("#f5f5f5")
              .borderRadius(36)
              .backgroundColor(Color.White)
              .alignItems(HorizontalAlign.Center)
              .justifyContent(FlexAlign.Center)
            }
            .layoutWeight(1)
            .width('100%')
            .justifyContent(FlexAlign.Center)

            Column() {
              Column() {
                Text('9')
                  .fontSize(32)
                  .width(19)
                  .height(43)
              }
              .width(70)
              .height(60)
              .borderWidth(1)
              .borderColor("#f5f5f5")
              .borderRadius(36)
              .backgroundColor(Color.White)
              .alignItems(HorizontalAlign.Center)
              .justifyContent(FlexAlign.Center)
            }
            .layoutWeight(1)
            .width('100%')
            .justifyContent(FlexAlign.Center)

            Column() {
              Column() {
                Text('6')
                  .fontSize(32)
                  .width(19)
                  .height(43)
              }
              .width(70)
              .height(60)
              .borderWidth(1)
              .borderColor("#f5f5f5")
              .borderRadius(36)
              .backgroundColor(Color.White)
              .alignItems(HorizontalAlign.Center)
              .justifyContent(FlexAlign.Center)
            }
            .layoutWeight(1)
            .width('100%')
            .justifyContent(FlexAlign.Center)

            Column() {
              Column() {
                Text('3')
                  .fontSize(32)
                  .width(19)
                  .height(43)
              }
              .width(70)
              .height(60)
              .borderWidth(1)
              .borderColor("#f5f5f5")
              .borderRadius(36)
              .backgroundColor(Color.White)
              .alignItems(HorizontalAlign.Center)
              .justifyContent(FlexAlign.Center)
            }
            .layoutWeight(1)
            .width('100%')
            .justifyContent(FlexAlign.Center)

            Column() {
              Column() {
                Text('.')
                  .fontSize('42fp')
                  .width(19)
                  .height(43)
              }
              .width(70)
              .height(60)
              .borderWidth(1)
              .borderColor("#f5f5f5")
              .borderRadius(36)
              .backgroundColor(Color.White)
              .alignItems(HorizontalAlign.Center)
              .justifyContent(FlexAlign.Center)
            }
            .layoutWeight(1)
            .width('100%')
            .justifyContent(FlexAlign.Center)
          }
          .layoutWeight(1)
          .margin({
            top: 30,
            bottom: 30
          })

          Column() {
            // 多列子元素
            Column() {
              Column() {
                Image($r('app.media.ic_del'))
                  .width(30)
                  .height(20)
              }
              .width(70)
              .height(60)
              .borderWidth(1)
              .borderColor("#f5f5f5")
              .borderRadius(36)
              .backgroundColor(Color.White)
              .alignItems(HorizontalAlign.Center)
              .justifyContent(FlexAlign.Center)
            }
            .layoutWeight(1)
            .width('100%')
            .justifyContent(FlexAlign.Center)

            Column() {
              Column() {
                Image($r('app.media.ic_min'))
                  .width(24)
                  .height(24)
              }
              .width(70)
              .height(60)
              .borderWidth(1)
              .borderColor("#f5f5f5")
              .borderRadius(36)
              .backgroundColor(Color.White)
              .alignItems(HorizontalAlign.Center)
              .justifyContent(FlexAlign.Center)
            }
            .layoutWeight(1)
            .width('100%')
            .justifyContent(FlexAlign.Center)

            Column() {
              Column() {
                Image($r('app.media.ic_add'))
                  .width(32)
                  .height(32)
              }
              .width(70)
              .height(60)
              .borderWidth(1)
              .borderColor("#f5f5f5")
              .borderRadius(36)
              .backgroundColor(Color.White)
              .alignItems(HorizontalAlign.Center)
              .justifyContent(FlexAlign.Center)
            }
            .layoutWeight(1)
            .width('100%')
            .justifyContent(FlexAlign.Center)

            Column() {
              Column() {
                Image($r('app.media.ic_equ'))
                  .width(32)
                  .height(32)
              }
              .width(70)
              .height(125)
              .borderWidth(1)
              .borderColor("#f5f5f5")
              .borderRadius(36)
              .backgroundColor('#007DFF')
              .alignItems(HorizontalAlign.Center)
              .justifyContent(FlexAlign.Center)
            }
            .layoutWeight(2)
            .width('100%')
            .justifyContent(FlexAlign.Center)
          }
          .layoutWeight(1)
          .margin({
            top: 30,
            bottom: 30
          })
        }
        .height('100%')
        .alignItems(VerticalAlign.Top)
        .margin({
          left: 20,
          right: 20
        })
      }
      .layoutWeight(1)
      .width('100%')
      .backgroundColor('#f8f8ff')
    }
    .height('100%')
    .backgroundColor('#f5f5f5')
  }
}

3. 动态布局

3.1. 定义视图模型

  • PressKeysItem.ets
// ets/viewmodel/PressKeysModel.ets

export class PressKeysItem {
  flag: number
  width: string
  height: string
  value: string
  source?: Resource

  constructor(flag: number, width: string, height: string, value: string, source?: Resource) {
    this.flag = flag
    this.width = width
    this.height = height
    this.value = value
    this.source = source
  }
}

export class PressKeysInnerObject {
  id: string
  data: PressKeysItem

  constructor(id: string, data: PressKeysItem) {
    this.id = id
    this.data = data
  }
}

export class PressKeysOuterObject {
  id: string
  data: Array<PressKeysInnerObject>

  constructor(id: string, data: Array<PressKeysInnerObject>) {
    this.id = id
    this.data = data
  }
}
  • PressKeysViewModel.ets
// ets/viewmodel/PressKeysViewModel.ets

import { PressKeysInnerObject, PressKeysOuterObject, PressKeysItem } from './PressKeysModel'

export class PressKeysViewModel {
  getPressKeys(): Array<PressKeysOuterObject> {
    return [
      new PressKeysOuterObject('01',
        [
          new PressKeysInnerObject('010', new PressKeysItem(0, '32vp', '32vp', 'clean', $r('app.media.ic_clean'))),
          new PressKeysInnerObject('011', new PressKeysItem(1, '19vp', '43vp', '7')),
          new PressKeysInnerObject('012', new PressKeysItem(1, '19vp', '43vp', '4')),
          new PressKeysInnerObject('013', new PressKeysItem(1, '19vp', '43vp', '1')),
          new PressKeysInnerObject('014', new PressKeysItem(1, '25vp', '43vp', '%'))
        ]
      ),

      new PressKeysOuterObject('02',
        [
          new PressKeysInnerObject('020', new PressKeysItem(0, '32vp', '32vp', 'div', $r('app.media.ic_div'))),
          new PressKeysInnerObject('021', new PressKeysItem(1, '19vp', '43vp', '8')),
          new PressKeysInnerObject('022', new PressKeysItem(1, '19vp', '43vp', '5')),
          new PressKeysInnerObject('023', new PressKeysItem(1, '19vp', '43vp', '2')),
          new PressKeysInnerObject('024', new PressKeysItem(1, '19vp', '43vp', '0'))
        ]
      ),

      new PressKeysOuterObject('03',
        [
          new PressKeysInnerObject('030', new PressKeysItem(0, '32vp', '32vp', 'mul', $r('app.media.ic_mul'))),
          new PressKeysInnerObject('031', new PressKeysItem(1, '19vp', '43vp', '9')),
          new PressKeysInnerObject('032', new PressKeysItem(1, '19vp', '43vp', '6')),
          new PressKeysInnerObject('033', new PressKeysItem(1, '19vp', '43vp', '3')),
          new PressKeysInnerObject('034', new PressKeysItem(1, '19vp', '43vp', '.'))
        ]
      ),

      new PressKeysOuterObject('04',
        [
          new PressKeysInnerObject('040', new PressKeysItem(0, '30.48vp', '20vp', 'del', $r('app.media.ic_del'))),
          new PressKeysInnerObject('041', new PressKeysItem(0, '24vp', '24vp', 'min', $r('app.media.ic_min'))),
          new PressKeysInnerObject('042', new PressKeysItem(0, '32vp', '32vp', 'add', $r('app.media.ic_add'))),
          new PressKeysInnerObject('043', new PressKeysItem(0, '32vp', '32vp', 'equ', $r('app.media.ic_equ')))
        ]
      )
    ]
  }
}

let keysModel = new PressKeysViewModel()

export default keysModel as PressKeysViewModel

3.2. 布局代码

// etc/pages/Index.ets

import { PressKeysOuterObject, PressKeysInnerObject } from '../viewmodel/PressKeysModel'
import keysModel from '../viewmodel/PressKeysViewModel'

// 入口组件定义
@Entry
@Component
struct Index {
  // 构建组件界面
  build() {
    Column() {
      /**
       * 运算区
       */
      Column() {
        TextInput({ text: '12x13' }) // 显示输入的格式化值
          .height('100%')
          .fontSize(32)
          .enabled(false) // 禁用输入
          .fontColor(Color.Black)
          .textAlign(TextAlign.End)
          .backgroundColor('#f5f5f5')
      }
      .width('100%')
      .height(86)
      .alignItems(HorizontalAlign.End)
      .margin({
        right: 20,
        top: 120
      })

      /**
       * 结果区
       */
      Column() {
        Text('123') // 显示计算结果
          .fontSize('32fp')
          .fontColor('#182431')
      }
      .width('100%')
      .height(42)
      .alignItems(HorizontalAlign.End)
      .margin({
        right: 20,
        bottom: 64
      })

      /**
       * 输入面板区
       */
      Column() {
        Row() {
          // 遍历键盘模型,生成按键
          ForEach(keysModel.getPressKeys(), (columnItem: PressKeysOuterObject, columnItemIndex: number) => {
            Column() {
              ForEach(columnItem.data, (keyItem: PressKeysInnerObject, keyItemIndex: number) => {
                Column() {
                  Column() {
                    // 根据按键类型生成图像或文本
                    if (keyItem.data.flag === 0) {
                      Image(keyItem.data.source !== undefined ? keyItem.data.source : '')
                        .width(keyItem.data.width)
                        .height(keyItem.data.height)
                    } else {
                      Text(keyItem.data.value)
                        .fontSize(keyItem.data.value === '.' ? '42fp' : '32fp')
                    }
                  }
                  .width(70)
                  .height(
                    (columnItemIndex === keysModel.getPressKeys().length - 1 &&
                      keyItemIndex === columnItem.data.length - 1) ?
                      '125vp' : '60vp'
                  )
                  .borderWidth(1)
                  .borderColor("#f5f5f5")
                  .borderRadius(36)
                  .backgroundColor(
                    (columnItemIndex === keysModel.getPressKeys().length - 1 &&
                      keyItemIndex === columnItem.data.length - 1) ?
                      '#007DFF' : Color.White
                  )
                  .alignItems(HorizontalAlign.Center)
                  .justifyContent(FlexAlign.Center)
                }
                .layoutWeight(
                  (columnItemIndex === keysModel.getPressKeys().length - 1 &&
                    keyItemIndex === columnItem.data.length - 1) ?
                    2 : 1
                )
                .width('100%')
                .justifyContent(FlexAlign.Center)
              }, (keyItem: PressKeysInnerObject) => keyItem.id)
            }
            .layoutWeight(1)
            .margin({
              top: 30,
              bottom: 30
            })
          }, (columnItem: PressKeysOuterObject) => columnItem.id)
        }
        .height('100%')
        .alignItems(VerticalAlign.Top)
        .margin({
          left: 20,
          right: 20
        })
      }
      .layoutWeight(1)
      .width('100%')
      .backgroundColor('#f8f8ff')
    }
    .height('100%')
    .backgroundColor('#f5f5f5')
  }
}

4. 计算逻辑

4.1. 常量定义文件

// ets/constants/CommonConstants.ets
export class CommonConstants {
  // 定义操作符字符串
  static readonly OPERATORS: string = '+-×÷';

  // 定义操作符优先级字符串
  static readonly OPERATORS_PRIORITY: string = '×÷';

  // 定义各个操作符
  static readonly ADD: string = '+';
  static readonly MIN: string = '-';
  static readonly MUL: string = '×';
  static readonly DIV: string = '÷';

  // 定义其他常量
  static readonly PERCENT_SIGN: string = '%'; // 百分号
  static readonly DOTS: string = '.'; // 小数点
  static readonly TWO: number = 2; // 数字2
  static readonly TEN: number = 10; // 数字10
  static readonly ONE_HUNDRED: string = '100'; // 字符串'100'
  static readonly INPUT_LENGTH_MAX: number = 9; // 最大输入长度
  static readonly INDEX_TWO: number = 2; // 索引2
  static readonly NUM_MAX_LEN: number = 16; // 数字最大长度
  static readonly E: string = 'e'; // 科学计数法中的e
  static readonly ZERO: string = '0'; // 字符串'0'
  static readonly ZERO_DOTS: string = '0.'; // 字符串'0.'
}

// 定义操作符的枚举
export enum Symbol {
  ADD = 'add',
  MIN = 'min',
  MUL = 'mul',
  DIV = 'div',
  CLEAN = 'clean',
  DEL = 'del',
  EQU = 'equ'
}

// 定义操作符优先级的枚举
export enum Priority {
  HIGH = 2,
  MEDIUM = 1,
  LOW = 0
}

// 定义符号枚举,用于识别不同的数学符号
export enum SymbolicEnumeration {
  ADD = '+',
  MIN = '-',
  MUL = '×',
  DIV = '÷'
}

4.2. 判空工具类

// ets/uitls/CheckEmptyUtil.ets

class CheckEmptyUtil {
  // 检查对象或字符串是否为空
  isEmpty(obj: object | string): boolean {
    return (typeof obj === 'undefined' || obj === null || obj === ''); // 判断是否为 undefined、null 或空字符串
  }

  // 检查字符串是否为空(去除空格后)
  checkStrIsEmpty(str: string): boolean {
    return str.trim().length === 0; // 如果去除空格后长度为 0,返回 true
  }

  // 检查数组是否为空
  isEmptyArr(arr: Array<string>) {
    return arr.length === 0; // 如果数组长度为 0,返回 true
  }
}

export default new CheckEmptyUtil(); // 导出 CheckEmptyUtil 的单例

4.3. 计算器核心类

// ets/uitis/CalculateUtil.ets

import CheckEmptyUtil from './CheckEmptyUtil' // 导入检查空值的工具类
import { CommonConstants, Priority, SymbolicEnumeration } from '../constants/CommonConstants' // 导入常量和枚举

class CalculateUtil {
  // 检查给定值是否为操作符
  isSymbol(value: string) {
    if (CheckEmptyUtil.isEmpty(value)) {
      return // 如果值为空,返回 undefined
    }
    // 判断值是否在操作符列表中
    return (CommonConstants.OPERATORS.indexOf(value) !== -1);
  }

  // 获取操作符的优先级
  getPriority(value: string): number {
    if (CheckEmptyUtil.isEmpty(value)) {
      return Priority.LOW; // 如果值为空,默认优先级为低
    }
    let result = 0; // 初始化优先级结果
    switch (value) {
      case SymbolicEnumeration.ADD: // 如果是加法
      case SymbolicEnumeration.MIN: // 如果是减法
        result = Priority.MEDIUM; // 设置为中等优先级
        break;
      case SymbolicEnumeration.MUL: // 如果是乘法
      case SymbolicEnumeration.DIV: // 如果是除法
        result = Priority.HIGH; // 设置为高优先级
        break;
      default:
        result = Priority.LOW; // 其他符号默认为低优先级
        break;
    }
    return result; // 返回优先级结果
  }

  // 比较两个操作符的优先级
  comparePriority(arg1: string, arg2: string): boolean {
    // 检查任一操作符是否为空
    if (CheckEmptyUtil.isEmpty(arg1) || CheckEmptyUtil.isEmpty(arg2)) {
      return false // 如果有空值,返回 false
    }
    // 返回 arg1 是否优先级低于等于 arg2
    return (this.getPriority(arg1) <= this.getPriority(arg2));
  }

  // 解析表达式,使用逆波兰表示法(后缀表达式)
  // 3×5+4÷2
  parseExpression(expressions: Array<string>): string {
    // 检查表达式数组是否为空
    if (CheckEmptyUtil.isEmpty(expressions)) {
      return 'NaN' // 如果为空,返回 'NaN'
    }
    let len = expressions.length; // 获取表达式的长度
    let outputStack: string[] = []; // 初始化操作符栈
    let outputQueue: string[] = []; // 初始化输出队列

    // 处理表达式数组
    expressions.forEach((item: string, index: number) => {
      // 处理百分号,将百分数转换为小数
      if (item.indexOf(CommonConstants.PERCENT_SIGN) !== -1) {
        expressions[index] = (this.mulOrDiv(item.slice(0, item.length - 1),
          CommonConstants.ONE_HUNDRED, CommonConstants.DIV)).toString();
      }
      // 如果是最后一个元素且为符号,则移除
      if ((index === len - 1) && this.isSymbol(item)) {
        expressions.pop(); // 从数组中移除最后的符号
      }
    });

    // 遍历表达式,构建输出队列
    while (expressions.length > 0) {
      let current: string | undefined = expressions.shift(); // 获取当前元素
      if (current !== undefined) { // 如果当前元素存在
        if (this.isSymbol(current)) { // 如果当前元素是符号
          // 比较当前符号与栈顶符号的优先级
          while (outputStack.length > 0 && this.comparePriority(current, outputStack[outputStack.length - 1])) {
            let popValue: string | undefined = outputStack.pop(); // 弹出栈顶符号
            if (popValue !== undefined) {
              outputQueue.push(popValue); // 将弹出的符号加入输出队列
            }
          }
          outputStack.push(current); // 将当前符号推入栈中
        } else {
          outputQueue.push(current); // 如果不是符号,直接加入输出队列
        }
      }
    }
    
    // 将栈中剩余的符号加入输出队列
    while (outputStack.length > 0) {
      let popValue: string | undefined = outputStack.pop();
      if (popValue !== undefined) {
        outputQueue.push(popValue); // 将剩余的符号加入输出队列
      }
    }

    // 处理输出队列,计算最终结果
    return this.dealQueue(outputQueue);
  }

  // 处理输出队列,计算结果
  dealQueue(queue: Array<string>): string {
    // 检查队列是否为空
    if (CheckEmptyUtil.isEmpty(queue)) {
      return 'NaN'; // 如果为空,返回 'NaN'
    }
    let outputStack: string[] = []; // 初始化输出栈

    // 遍历队列,计算结果
    while (queue.length > 0) {
      let current: string | undefined = queue.shift(); // 获取当前元素
      if (current !== undefined) { // 如果当前元素存在
        if (!this.isSymbol(current)) { // 如果不是符号,加入输出栈
          outputStack.push(current);
        } else {
          // 弹出栈顶两个元素作为操作数
          let second: string | undefined = outputStack.pop(); // 弹出第二个操作数
          let first: string | undefined = outputStack.pop(); // 弹出第一个操作数
          if (first !== undefined && second !== undefined) {
            // 计算结果并加入输出栈
            let calResultValue: string = this.calResult(first, second, current);
            outputStack.push(calResultValue); // 将计算结果加入栈
          }
        }
      }
    }

    // 检查输出栈的状态,确保只有一个结果
    if (outputStack.length !== 1) {
      return 'NaN'; // 如果不止一个元素,返回 'NaN'
    } else {
      // 处理结果的小数点
      let end: string = outputStack[0]?.endsWith(CommonConstants.DOTS) ?
      outputStack[0].substring(0, outputStack[0].length - 1) : outputStack[0];
      return end; // 返回最终结果
    }
  }

  // 计算两个操作数和符号之间的结果
  calResult(arg1: string, arg2: string, symbol: string): string {
    // 检查参数是否为空
    if (CheckEmptyUtil.isEmpty(arg1) || CheckEmptyUtil.isEmpty(arg2) || CheckEmptyUtil.isEmpty(symbol)) {
      return 'NaN'; // 如果有空值,返回 'NaN'
    }
    let result = 0; // 初始化计算结果
    switch (symbol) {
      case SymbolicEnumeration.ADD:
        // 处理加法
        result = this.add(arg1, arg2, CommonConstants.ADD);
        break;
      case SymbolicEnumeration.MIN:
        // 处理减法
        result = this.add(arg1, arg2, CommonConstants.MIN);
        break;
      case SymbolicEnumeration.MUL:
        // 处理乘法
        result = this.mulOrDiv(arg1, arg2, CommonConstants.MUL);
        break;
      case SymbolicEnumeration.DIV:
        // 处理除法
        result = this.mulOrDiv(arg1, arg2, CommonConstants.DIV);
        break;
      default:
        break; // 其他操作符不处理
    }
    return this.numberToScientificNotation(result); // 返回结果(可能为科学计数法)
  }

  // 处理加法
  add(arg1: string, arg2: string, symbol: string): number {
    let addFlag = (symbol === CommonConstants.ADD); // 判断是否为加法
    // 处理科学计数法
    if (this.containScientificNotation(arg1) || this.containScientificNotation(arg2)) {
      if (addFlag) {
        return Number(arg1) + Number(arg2); // 直接计算加法
      }
      return Number(arg1) - Number(arg2); // 直接计算减法
    }
    // 处理零的情况
    arg1 = (arg1 === CommonConstants.ZERO_DOTS) ? '0' : arg1;
    arg2 = (arg2 === CommonConstants.ZERO_DOTS) ? '0' : arg2;
    
    // 分割小数部分
    let leftArr = arg1.split(CommonConstants.DOTS);
    let rightArr = arg2.split(CommonConstants.DOTS);
    
    // 获取小数部分长度
    let leftLen = leftArr.length > 1 ? leftArr[1] : '';
    let rightLen = rightArr.length > 1 ? rightArr[1] : '';
    
    // 获取最大小数部分长度
    let maxLen = Math.max(leftLen.length, rightLen.length);
    let multiples = Math.pow(CommonConstants.TEN, maxLen); // 计算倍数

    // 处理加法或减法
    if (addFlag) {
      return Number(((Number(arg1) * multiples + Number(arg2) * multiples) / multiples).toFixed(maxLen));
    }
    return Number(((Number(arg1) * multiples - Number(arg2) * multiples) / multiples).toFixed(maxLen));
  }

  // 处理乘法和除法
  mulOrDiv(arg1: string, arg2: string, symbol: string): number {
    let mulFlag = (symbol === CommonConstants.MUL); // 判断是否为乘法
    // 处理科学计数法
    if (this.containScientificNotation(arg1) || this.containScientificNotation(arg2)) {
      if (mulFlag) {
        return Number(arg1) * Number(arg2); // 直接计算乘法
      }
      return Number(arg1) / Number(arg2); // 直接计算除法
    }

    // 获取小数部分长度
    let leftLen = arg1.split(CommonConstants.DOTS)[1] ? arg1.split(CommonConstants.DOTS)[1].length : 0;
    let rightLen = arg2.split(CommonConstants.DOTS)[1] ? arg2.split(CommonConstants.DOTS)[1].length : 0;

    // 处理乘法
    if (mulFlag) {
      return Number(arg1.replace(CommonConstants.DOTS, '')) *
      Number(arg2.replace(CommonConstants.DOTS, '')) / Math.pow(CommonConstants.TEN, leftLen + rightLen);
    }
    // 处理除法
    return Number(arg1.replace(CommonConstants.DOTS, '')) /
      (Number(arg2.replace(CommonConstants.DOTS, '')) / Math.pow(CommonConstants.TEN, rightLen - leftLen));
  }

  // 检查字符串是否包含科学计数法
  containScientificNotation(arg: string) {
    return (arg.indexOf(CommonConstants.E) !== -1); // 判断是否含有 'e'
  }

  // 将结果转换为科学计数法格式
  numberToScientificNotation(result: number) {
    // 处理无穷大情况
    if (result === Number.NEGATIVE_INFINITY || result === Number.POSITIVE_INFINITY) {
      return 'NaN'; // 返回 'NaN',表示计算出错
    }
    let resultStr = JSON.stringify(result); // 将结果转为字符串
    // 如果已经是科学计数法,直接返回
    if (this.containScientificNotation(resultStr)) {
      return resultStr;
    }
    let prefixNumber = (resultStr.indexOf(CommonConstants.MIN) === -1) ? 1 : -1; // 处理负数前缀
    result *= prefixNumber; // 应用前缀符号
    // 检查结果长度是否超过限制
    if (resultStr.replace(CommonConstants.DOTS, '').replace(CommonConstants.MIN, '').length <
    CommonConstants.NUM_MAX_LEN) {
      return resultStr; // 如果长度合适,直接返回
    }
    // 计算科学计数法的指数部分
    let suffix = (Math.floor(Math.log(result) / Math.LN10));
    let prefix = (result * Math.pow(CommonConstants.TEN, -suffix) * prefixNumber); // 计算系数
    return (prefix + CommonConstants.E + suffix); // 返回科学计数法表示
  }
}

export default new CalculateUtil(); // 导出 CalculateUtil 的单例

4.4. 首页代码调用和实现

// etc/pages/Index.ets

import { PressKeysOuterObject, PressKeysInnerObject } from '../viewmodel/PressKeysModel'
import keysModel from '../viewmodel/PressKeysViewModel'
import CalculateUtil from '../utils/CalculateUtil'
import CheckEmptyUtil from '../utils/CheckEmptyUtil'
import { CommonConstants, Symbol } from '../constants/CommonConstants'
import Logger from '../utils/Logger'

// 入口组件定义
@Entry
@Component
struct Index {
  // 输入的表达式
  @State inputValue: string = ''
  // 计算结果
  @State calValue: string = ''
  // 存储表达式的数组
  private expressions: Array<string> = []

  // 构建组件界面
  build() {
    Column() {
      /**
       * 运算区
       */
      Column() {
        TextInput({ text: this.resultFormat(this.inputValue) }) // 显示输入的格式化值
          .height('100%')
          .fontSize(32)
          .enabled(false) // 禁用输入
          .fontColor(Color.Black)
          .textAlign(TextAlign.End)
          .backgroundColor('#f5f5f5')
      }
      .width('100%')
      .height(86)
      .alignItems(HorizontalAlign.End)
      .margin({
        right: 20,
        top: 120
      })

      /**
       * 结果区
       */
      Column() {
        Text(this.resultFormat(this.calValue)) // 显示计算结果
          .fontSize('32fp')
          .fontColor('#182431')
      }
      .width('100%')
      .height(42)
      .alignItems(HorizontalAlign.End)
      .margin({
        right: 20,
        bottom: 64
      })

      /**
       * 输入面板区
       */
      Column() {
        Row() {
          // 遍历键盘模型,生成按键
          ForEach(keysModel.getPressKeys(), (columnItem: PressKeysOuterObject, columnItemIndex: number) => {
            Column() {
              ForEach(columnItem.data, (keyItem: PressKeysInnerObject, keyItemIndex: number) => {
                Column() {
                  Column() {
                    // 根据按键类型生成图像或文本
                    if (keyItem.data.flag === 0) {
                      Image(keyItem.data.source !== undefined ? keyItem.data.source : '')
                        .width(keyItem.data.width)
                        .height(keyItem.data.height)
                    } else {
                      Text(keyItem.data.value)
                        .fontSize(keyItem.data.value === '.' ? '42fp' : '32fp')
                    }
                  }
                  .width(70)
                  .height(
                    (columnItemIndex === keysModel.getPressKeys().length - 1 &&
                      keyItemIndex === columnItem.data.length - 1) ?
                      '125vp' : '60vp'
                  )
                  .borderWidth(1)
                  .borderColor("#f5f5f5")
                  .borderRadius(36)
                  .backgroundColor(
                    (columnItemIndex === keysModel.getPressKeys().length - 1 &&
                      keyItemIndex === columnItem.data.length - 1) ?
                      '#007DFF' : Color.White
                  )
                  .alignItems(HorizontalAlign.Center)
                  .justifyContent(FlexAlign.Center)

                  .onClick(() => {
                    this.inputValue = keyItem.data.value // 更新输入值
                    // 根据按键类型调用相应的输入方法
                    if (keyItem.data.flag === 0) {
                      this.inputSymbol(keyItem.data.value)
                    } else {
                      this.inputNumber(keyItem.data.value)
                    }
                  })
                }
                .layoutWeight(
                  (columnItemIndex === keysModel.getPressKeys().length - 1 &&
                    keyItemIndex === columnItem.data.length - 1) ?
                    2 : 1
                )
                .width('100%')
                .justifyContent(FlexAlign.Center)
              }, (keyItem: PressKeysInnerObject) => keyItem.id)
            }
            .layoutWeight(1)
            .margin({
              top: 30,
              bottom: 30
            })
          }, (columnItem: PressKeysOuterObject) => columnItem.id)
        }
        .height('100%')
        .alignItems(VerticalAlign.Top)
        .margin({
          left: 20,
          right: 20
        })
      }
      .layoutWeight(1)
      .width('100%')
      .backgroundColor('#f8f8ff')
    }
    .height('100%')
    .backgroundColor('#f5f5f5')
  }

  // 输入符号处理
  inputSymbol(value: string) {
    if (CheckEmptyUtil.isEmpty(value)) {
      return // 检查输入是否为空
    }
    let len: number = this.expressions.length // 获取当前表达式长度
    switch (value) {
      case Symbol.CLEAN:
        this.expressions = [] // 清空表达式
        this.calValue = ''
        break;
      case Symbol.DEL:
        this.inputDelete(len) // 删除操作
        break;
      case Symbol.EQU:
        if (len === 0) {
          return; // 如果没有表达式,直接返回
        }
        // 计算结果并更新状态
        (this.getResult() as Promise<boolean>).then((result: boolean) => {
          if (!result) {
            return
          }
          this.inputValue = this.calValue // 更新输入值为计算结果
          this.calValue = ''
          this.expressions = []
          this.expressions.push(this.inputValue)
        })
        break
      default:
        this.inputOperators(len, value) // 处理其他符号输入
        break;
    }
    this.formatInputValue() // 格式化输入值
  }

  // 输入数字处理
  inputNumber(value: string) {
    if (CheckEmptyUtil.isEmpty(value)) {
      return // 检查输入是否为空
    }
    let len: number = this.expressions.length // 获取当前表达式长度
    let last: string = len > 0 ? this.expressions[len - 1] : '' // 获取最后一个输入
    let secondLast: string | undefined = len > 1 ? this.expressions[len - CommonConstants.TWO] : undefined // 获取倒数第二个输入
    // 验证输入是否合法
    if (!this.validateEnter(last, value)) {
      return
    }
    // 根据当前表达式状态更新表达式
    if (!last) {
      this.expressions.push(value) // 如果没有输入,直接添加
    } else if (!secondLast) {
      this.expressions[len - 1] += value // 如果只有一个输入,拼接
    }
    if (secondLast && CalculateUtil.isSymbol(secondLast)) {
      this.expressions[len - 1] += value // 如果倒数第二个是符号,拼接
    }
    if (secondLast && !CalculateUtil.isSymbol(secondLast)) {
      this.expressions.push(value) // 如果倒数第二个不是符号,添加新的输入
    }
    this.formatInputValue(); // 格式化输入值
    if (value !== CommonConstants.DOTS) {
      this.getResult() // 如果输入不是小数点,计算结果
    }
  }

  // 验证输入合法性
  validateEnter(last: string, value: string) {
    if (!last && value === CommonConstants.PERCENT_SIGN) {
      return false // 不能在没有输入的情况下输入百分号
    }
    if ((last === CommonConstants.MIN) && (value === CommonConstants.PERCENT_SIGN)) {
      return false // 负号后不能直接输入百分号
    }
    if (last.endsWith(CommonConstants.PERCENT_SIGN)) {
      return false // 不能在百分号后输入其他数字
    }
    if ((last.indexOf(CommonConstants.DOTS) !== -1) && (value === CommonConstants.DOTS)) {
      return false // 不能输入多个小数点
    }
    if ((last === '0') && (value !== CommonConstants.DOTS) &&
      (value !== CommonConstants.PERCENT_SIGN)) {
      return false // 0后不能输入非小数和百分号的数字
    }
    return true // 验证通过
  }

  // 删除操作
  inputDelete(len: number) {
    if (len === 0) {
      return // 如果没有输入,直接返回
    }
    let last = this.expressions[len - 1] // 获取最后一个输入
    let lastLen: number = last.length // 获取最后一个输入的长度
    if (lastLen === 1) {
      this.expressions.pop() // 如果长度为1,删除该输入
      len = this.expressions.length
    } else {
      this.expressions[len - 1] = last.slice(0, last.length - 1) // 删除最后一个字符
    }
    if (len === 0) {
      this.inputValue = ''
      this.calValue = ''
      return // 如果没有输入,清空值
    }
    if (!CalculateUtil.isSymbol(this.expressions[len - 1])) {
      this.getResult() // 如果最后一个不是符号,重新计算结果
    }
  }

  // 输入运算符处理
  inputOperators(len: number, value: string) {
    let last: string | undefined = len > 0 ? this.expressions[len - 1] : undefined // 获取最后一个输入
    let secondLast: string | undefined = len > 1 ? this.expressions[len - CommonConstants.TWO] : undefined // 获取倒数第二个输入
    if (!last && (value === Symbol.MIN)) {
      this.expressions.push(this.getSymbol(value)); // 处理负号的情况
      return
    }
    if (!last) {
      return // 如果没有输入,返回
    }
    if (!CalculateUtil.isSymbol(last)) {
      this.expressions.push(this.getSymbol(value)) // 如果最后一个不是符号,添加新的符号
      return
    }
    if ((value === Symbol.MIN) &&
      (last === CommonConstants.MIN || last === CommonConstants.ADD)) {
      this.expressions.pop() // 处理负号替换
      this.expressions.push(this.getSymbol(value))
      return
    }
    if (!secondLast) {
      return // 如果倒数第二个输入不存在,返回
    }
    if (value !== Symbol.MIN) {
      this.expressions.pop() // 如果不是负号,删除最后一个符号
    }
    if (CalculateUtil.isSymbol(secondLast)) {
      this.expressions.pop() // 如果倒数第二个也是符号,删除
    }
    this.expressions.push(this.getSymbol(value)) // 添加新的符号
  }

  // 获取符号对应的字符串
  getSymbol(value: string) {
    if (CheckEmptyUtil.isEmpty(value)) {
      return ''
    }
    let symbol = ''
    switch (value) {
      case Symbol.ADD:
        symbol = CommonConstants.ADD
        break
      case Symbol.MIN:
        symbol = CommonConstants.MIN
        break
      case Symbol.MUL:
        symbol = CommonConstants.MUL
        break;
      case Symbol.DIV:
        symbol = CommonConstants.DIV
        break
      default:
        break
    }
    return symbol // 返回对应的符号
  }

  // 深拷贝表达式数组
  deepCopy(): Array<string> {
    let copyExpressions: Array<string> = Array.from(this.expressions) // 使用 Array.from 深拷贝数组
    return copyExpressions
  }

  // 计算结果
  async getResult(): Promise<boolean> {
    let calResult = CalculateUtil.parseExpression(this.deepCopy()) // 解析表达式
    if (calResult === 'NaN') {
      this.calValue = this.resourceToString($r('app.string.error')) // 处理计算错误
      return false;
    }
    this.calValue = calResult; // 更新计算结果
    return true; // 计算成功
  }

  // 格式化显示结果
  resultFormat(value: string): string {
    let reg: RegExp = (value.indexOf('.') > -1) ? new RegExp("/(\d)(?=(\d{3})+.)/g") : new RegExp("/(\d)(?=(?:\d{3})+$)/g");
    return value.replace(reg, '$1,'); // 添加千位分隔符
  }

  // 从资源获取字符串
  resourceToString(resource: Resource): string {
    if (CheckEmptyUtil.isEmpty(resource)) {
      return '';
    }
    let result = '';
    try {
      result = getContext(this).resourceManager.getStringSync(resource.id); // 获取资源字符串
    } catch (error) {
      Logger.error('[CalculateModel] getResourceString fail: ' + JSON.stringify(error)) // 记录错误
    }
    return result; // 返回结果
  }

  // 格式化输入值
  formatInputValue() {
    let deepExpressions: Array<string> = [];
    this.deepCopy().forEach((item: string, index: number) => {
      deepExpressions[index] = this.resultFormat(item); // 格式化每个输入项
    });
    this.inputValue = deepExpressions.join(''); // 将格式化的项连接成字符串
  }
}

✋ 需要参加鸿蒙认证的请点击 鸿蒙认证链接