在这里插入图片描述

目录

  1. 概述
  2. BMI 算法原理
  3. Kotlin 实现代码(KMP)
  4. 编译后的 JavaScript 代码说明
  5. ArkTS 页面集成与调用
  6. 交互示例
  7. 编译与文件复制流程
  8. 总结

概述

本案例展示如何使用 Kotlin Multiplatform (KMP) 编写一个 BMI 体质指数计算器,并通过 Kotlin/JS 编译为 JavaScript,最终在 OpenHarmony ArkTS 页面 中调用,实现鸿蒙端的跨语言复用。

特点:

  • 输入:身高(cm)+ 体重(kg)
  • 输出:BMI 数值、体型分类、理想体重区间、增减重建议、健康提示
  • 技术栈:Kotlin(Multiplatform)→ JavaScript(ESModule)→ ArkTS(UI 页面)

BMI 算法原理

BMI(Body Mass Index,体质指数)的计算公式为:

BMI = 体重(kg) / 身高(m)^2

常见的分类(此处采用简化的中国成人标准):

  • BMI < 18.5 → 偏瘦
  • 18.5 ≤ BMI < 24.0 → 正常
  • 24.0 ≤ BMI < 28.0 → 超重
  • BMI ≥ 28.0 → 肥胖

理想体重区间可以根据 BMI 正常区间(18.5 ~ 24.0)反推:

理想体重下限 = 18.5 × 身高(m)^2
理想体重上限 = 24.0 × 身高(m)^2

Kotlin 实现代码(KMP)

下面是定义在 src/jsMain/kotlin/App.kt 中的 bmiCalculator 方法,通过 @JsExport 导出供 JS/ArkTS 调用:

@OptIn(ExperimentalJsExport::class)
@JsExport
fun bmiCalculator(inputText: String = "170 65"): String {
    val parts = inputText.trim().split(" ").filter { it.isNotEmpty() }

    if (parts.size < 2) {
        return "❌ 错误: 请按 '身高 体重' 的格式输入,例如: 170 65"
    }

    val heightCm = parts[0].toDoubleOrNull()
    val weightKg = parts[1].toDoubleOrNull()

    if (heightCm == null || weightKg == null || heightCm <= 0 || weightKg <= 0) {
        return "❌ 错误: 身高或体重无效\n请输入正数,例如: 170 65"
    }

    // 1. 计算 BMI
    val heightM = heightCm / 100.0
    val bmi = weightKg / (heightM * heightM)
    val bmiRounded = (bmi * 10).toInt() / 10.0

    // 2. 体型分类
    val category = when {
        bmi < 18.5 -> "偏瘦"
        bmi < 24.0 -> "正常"
        bmi < 28.0 -> "超重"
        else -> "肥胖"
    }

    // 3. 理想体重区间
    val minWeight = 18.5 * heightM * heightM
    val maxWeight = 24.0 * heightM * heightM

    // 4. 当前与理想区间的差值
    val deltaToMin = weightKg - minWeight
    val deltaToMax = weightKg - maxWeight

    // 5. 增减重建议
    val adjustSuggestion = when {
        bmi < 18.5 -> {
            val gain = (minWeight - weightKg)
            val gainRounded = (gain * 10).toInt() / 10.0
            "建议增重约 ${gainRounded}kg 以达到正常范围下限。"
        }
        bmi > 24.0 -> {
            val lose = (weightKg - maxWeight)
            val loseRounded = (lose * 10).toInt() / 10.0
            "建议减重约 ${loseRounded}kg 以回到正常范围上限。"
        }
        else -> "目前体重在正常范围内,继续保持良好生活习惯。"
    }

    // 6. 健康提示
    val healthTip = when {
        bmi < 18.5 -> "注意营养均衡,多摄入优质蛋白和复合碳水,避免过度节食。"
        bmi < 24.0 -> "继续保持规律作息和适量运动,是当前最好的状态。"
        bmi < 28.0 -> "建议增加有氧运动,适当控制精制碳水和高脂肪食物摄入。"
        else -> "建议在专业人士指导下进行饮食控制和运动计划,循序渐进减重。"
    }

    return "⚖️ BMI 体质指数计算器\n" +
           "━━━━━━━━━━━━━━━━━━━━━\n" +
           "输入: 身高 ${heightCm}cm, 体重 ${weightKg}kg\n\n" +
           "1️⃣ 计算结果:\n" +
           "  BMI: ${bmiRounded}\n" +
           "  体型: ${category}\n\n" +
           "2️⃣ 理想体重区间:\n" +
           "  范围: ${(minWeight * 10).toInt() / 10.0}kg ~ ${(maxWeight * 10).toInt() / 10.0}kg\n" +
           "  当前相对下限: ${(deltaToMin * 10).toInt() / 10.0}kg\n" +
           "  当前相对上限: ${(deltaToMax * 10).toInt() / 10.0}kg\n\n" +
           "3️⃣ 调整建议:\n" +
           "  ${adjustSuggestion}\n\n" +
           "4️⃣ 健康提示:\n" +
           "  ${healthTip}\n\n" +
           "━━━━━━━━━━━━━━━━━━━━━\n" +
           "✅ 分析完成!"
}

代码说明:

这个 bmiCalculator 函数是整个 BMI 计算器的核心逻辑,使用 @JsExport 注解将其导出为 JavaScript 可调用的函数。函数接收一个字符串参数 inputText,格式为"身高 体重"(例如"170 65"),默认值为"170 65"。

首先,函数对输入进行解析和验证。它将输入字符串按空格分割,并过滤掉空字符串。如果分割后的部分少于 2 个,说明输入格式不正确,返回错误提示。然后尝试将身高和体重转换为浮点数,使用 toDoubleOrNull() 方法安全地进行转换。如果转换失败或数值为负数或零,同样返回错误提示。

接下来进行 BMI 的计算。首先将身高从厘米转换为米(除以 100),然后使用公式 BMI = 体重 / 身高² 计算 BMI 值。为了提高可读性,将 BMI 值四舍五入到小数点后一位。

基于计算出的 BMI 值,使用 when 表达式进行体型分类。根据不同的 BMI 范围(偏瘦、正常、超重、肥胖)返回对应的分类结果。

然后计算理想体重区间。根据 BMI 正常范围(18.5 ~ 24.0)反推出对应身高下的最小和最大理想体重。同时计算当前体重与理想范围上下限的差值,用于后续的调整建议。

增减重建议部分根据当前 BMI 值判断用户的体重状态。如果 BMI 小于 18.5(偏瘦),计算需要增加的体重;如果 BMI 大于 24.0(超重或肥胖),计算需要减少的体重;如果在正常范围内,则建议保持现状。

最后,根据 BMI 值提供个性化的健康提示,包括营养建议、运动建议等。函数返回一个格式化的多行字符串,包含所有计算结果、分类、建议和提示,使用 emoji 和分隔符使输出更加直观易读。


编译后的 JavaScript 代码说明

Kotlin/JS 编译后生成的文件位置:

build/js/packages/hellokjs/kotlin/hellokjs.mjs
build/js/packages/hellokjs/kotlin/hellokjs.d.ts

hellokjs.mjs 中,对应会生成一个 bmiCalculator 导出函数,伪代码结构类似:

export function bmiCalculator(inputText = "170 65") {
  // 解析输入
  // 计算 BMI
  // 拼接多行字符串
  return resultString;
}

代码说明:

这是 Kotlin 编译后生成的 JavaScript 代码的伪代码表示。实际编译生成的代码会包含 Kotlin 运行时库和完整的函数实现。export 关键字使得这个函数可以被其他 JavaScript 模块导入使用。函数接收一个可选的字符串参数 inputText,默认值为"170 65"。函数内部的注释表示了三个主要步骤:首先解析用户输入的身高和体重数据,然后根据公式计算 BMI 值并进行分类,最后将所有结果拼接成一个格式化的多行字符串返回给调用者。这种导出方式使得 ArkTS 页面可以像调用普通 JavaScript 函数一样调用 Kotlin 编写的业务逻辑。

同时 .d.ts 文件中会有类型声明:

export declare function bmiCalculator(inputText?: string): string;

代码说明:

这是 TypeScript 类型声明文件,为编译生成的 JavaScript 函数提供类型信息。export declare 表示这是一个导出的函数声明。函数名为 bmiCalculator,接收一个可选的字符串参数 inputText(问号表示可选),返回值类型为 string。这个类型声明文件的作用是在 ArkTS/TypeScript 开发环境中提供代码补全、类型检查和文档提示,帮助开发者正确使用这个函数。当在 ArkTS 中导入 bmiCalculator 时,IDE 会根据这个声明文件提供自动补全和类型验证,确保调用的正确性。


ArkTS 页面集成与调用

在 OpenHarmony 工程中,我们将 hellokjs.mjs 复制并重命名为 hellokjs.js,放到 ArkTS 页面的同级目录,然后在 Index.ets 中导入并调用:

import { bmiCalculator } from './hellokjs';

@Entry
@Component
struct Index {
  @State message: string = '请输入身高和体重';
  @State inputHeight: string = '170';
  @State inputWeight: string = '65';
  @State resultText: string = '';

  aboutToAppear(): void {
    this.calculateBmi();
  }

  calculateBmi(): void {
    try {
      const input: string = `${this.inputHeight} ${this.inputWeight}`;
      const result: string = bmiCalculator(input);
      this.resultText = result;
      this.message = '✓ 计算完成';
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      this.message = `✗ 错误: ${errorMessage}`;
    }
  }

  build() {
    Column() {
      // 顶部标题栏、输入区域、结果展示等(略,见工程中的 Index.ets 完整代码)
    }
  }
}

代码说明:

这是 OpenHarmony ArkTS 页面的核心代码,展示了如何集成和调用 Kotlin 编译生成的 JavaScript 函数。首先通过 import 语句从 ./hellokjs 模块导入 bmiCalculator 函数,这个模块就是之前编译生成的 JavaScript 文件。

@Entry@Component 装饰器表示这是一个可入口的组件,是页面的主体。struct Index 定义了页面的结构体。

页面使用 @State 装饰器定义了四个响应式状态变量:message 用于显示状态提示信息,inputHeightinputWeight 分别存储用户输入的身高和体重(初始值为"170"和"65"),resultText 用于存储 BMI 计算的结果。

aboutToAppear() 是生命周期钩子,在组件即将显示时被调用,这里调用 calculateBmi() 方法进行初始计算。

calculateBmi() 方法是关键的业务逻辑调用点。它首先将输入的身高和体重拼接成"身高 体重"的格式字符串,然后调用 Kotlin 编译生成的 bmiCalculator 函数,就像调用普通的 JavaScript 函数一样。函数返回的结果字符串被存储在 resultText 中,并更新 message 为成功提示。整个调用被包裹在 try-catch 块中,以便捕获任何可能的错误并显示错误信息。

build() 方法定义了页面的 UI 布局,使用 Column 组件作为主容器(具体的 UI 代码在这里省略)。这种设计模式清晰地分离了关注点:Kotlin 负责复杂的业务算法,ArkTS 只负责 UI 展示和用户交互。


交互示例

示例 1:标准体重

输入:

身高: 170cm
体重: 65kg

输出片段示例:

1️⃣ 计算结果:
  BMI: 22.5
  体型: 正常

2️⃣ 理想体重区间:
  范围: 53.5kg ~ 69.4kg
  当前相对下限: 11.5kg
  当前相对上限: -4.4kg

3️⃣ 调整建议:
  目前体重在正常范围内,继续保持良好生活习惯。

示例 2:偏瘦

输入: 170 48

会给出:

  • BMI 数值较低
  • 体型为“偏瘦”
  • 建议增重若干 kg
  • 给出营养与作息方面的健康提示

示例 3:超重 / 肥胖

输入: 170 85

会给出:

  • BMI 偏高
  • 对应“超重”或“肥胖”分类
  • 建议减重的 kg 数
  • 生活方式调整建议(有氧运动、饮食控制等)

编译与文件复制流程

工程中已经通过脚本将编译与文件复制自动化:

# 在项目根目录执行
build-and-copy.bat

代码说明:

这是一个 Windows 批处理脚本,用于自动化 Kotlin/JS 编译和文件复制的整个流程。脚本的主要目的是简化开发者的工作流程,避免手动执行多个步骤。脚本主要做三件事:首先执行 gradlew build 命令编译 Kotlin 代码为 JavaScript,这个过程会生成 ES Module 格式的 JavaScript 文件和对应的 TypeScript 类型声明文件。其次检查编译是否成功,验证 hellokjs.mjshellokjs.d.ts 文件是否已经生成在 build 目录中。最后将生成的文件复制到 ArkTS 页面所在的目录,并将 .mjs 文件重命名为 .js,这样做是为了兼容 OpenHarmony 的模块加载机制。

目标位置:

d:\flutter_Obj\kmp_openharmony\kmp_ceshiapp\entry\src\main\ets\pages\hellokjs.js
d:\flutter_Obj\kmp_openharmony\kmp_ceshiapp\entry\src\main\ets\pages\hellokjs.d.ts

代码说明:

这两行文本指定了编译后的 JavaScript 文件和 TypeScript 类型声明文件的最终目标位置。第一个路径指向重命名后的 JavaScript 文件 hellokjs.js,这是 ArkTS 页面实际导入和使用的文件。第二个路径指向 TypeScript 类型声明文件 hellokjs.d.ts,它为 IDE 和编译器提供类型信息,使得在 ArkTS 中调用 Kotlin 编写的函数时能够获得完整的类型检查和代码补全支持。这两个文件都被放在 OpenHarmony 工程的 pages 目录中,与 ArkTS 页面文件放在一起,方便导入和使用。


总结

通过 BMI 体质指数计算器案例,我们完成了:

  1. 使用 Kotlin 编写可复用的业务算法(BMI 计算 + 文本分析)
  2. 通过 Kotlin/JS 将其编译为 JavaScript 模块
  3. 在 OpenHarmony ArkTS 页面中直接导入并调用该模块
  4. 实现“算法在 Kotlin,界面在 ArkTS”的清晰分层

你可以用同样的方式继续扩展更多工具或小应用(如贷款计算器、营养配比工具等),只需:

  • App.kt 中添加新的 @JsExport 方法
  • 通过脚本重新编译并复制 JS 文件
  • 在 ArkTS 页面中导入并调用新的函数

这就是 KMP 在鸿蒙端实战中的一个完整链路示例。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐