在这里插入图片描述

目录

  1. 概述
  2. 功能设计
  3. Kotlin 实现代码(KMP)
  4. JavaScript 调用示例
  5. ArkTS 页面集成与调用
  6. 数据输入与交互体验
  7. 编译与自动复制流程
  8. 总结

概述

本案例在 Kotlin Multiplatform (KMP) 工程中实现了一个 个人收支预算分析工具

  • 输入:一串数字,第一位是总收入,后面若干位是不同类别的支出,例如:10000 5000 2000 1500
  • 输出:
    • 总收入、总支出、余额统计
    • 各项支出占总收入的比例明细
    • 简单的预算评语(是否超支、结余多少)
  • 技术路径:Kotlin → Kotlin/JS → JavaScript 模块 → ArkTS 页面调用。

相比之前的 BMI 体质指数计算器贷款等额本息还款计算器成绩排行榜生成与分析工具,这个案例更贴近日常生活中的理财场景,可以帮助用户快速感知自己的支出结构和预算健康程度,也进一步体现了“算法逻辑写在 Kotlin,一次编写、多端复用;界面交互交给 ArkTS 来完成”的模式。

在这个过程中,你可以看到完整的链路:

  • 如何在 KMP 的 jsMain 源集中实现业务算法,并用 @JsExport 导出给 JS 使用。
  • Kotlin 代码如何被编译成 ES Module 形式的 hellokjs.mjs,同时生成类型声明 hellokjs.d.ts
  • 在 OpenHarmony 的 ArkTS 页面中,如何像调用普通 JS 模块一样调用 Kotlin 导出的函数,并通过状态管理与 UI 绑定结果。

功能设计

输入数据格式

预算分析工具的输入设计得尽量简单:

  • 使用空格分隔不同的数字,方便从终端、表单、记事本等地方复制粘贴。
  • 第 1 个数字代表当月或当期的总收入,例如工资、奖金等的合计。
  • 后续每个数字代表一项支出,可以不区分具体类别,只关注金额本身。
  • 示例输入:
10000 5000 2000 1500

这个输入可以简单理解为:

  • 一个月收入 10000 元
  • 三项支出:5000、2000 和 1500 元

算法会计算:

  • 总支出 = 5000 + 2000 + 1500 = 8500
  • 余额 = 10000 - 8500 = 1500
  • 各项支出占收入的比例,比如第一项 5000 / 10000 = 50%

输出信息结构

为了方便在 ArkTS 界面中直接展示,Kotlin 函数返回的是一段 多行文本,按照清晰的分区格式组织:

  1. 标题区:展示工具名称,比如“📊 个人收支预算分析”。
  2. 收支统计:列出总收入、总支出和余额。
  3. 支出明细:按序号给出每一项支出的金额和占收入的百分比。
  4. 预算评语:简单判断是否超支,并给出一句话说明。

这种返回方式有几个好处:

  • 在 ArkTS 中不需要再拆分和格式化,直接绑定到 Text 控件即可滚动展示。
  • 同一段文本可以方便地复制到日志、调试输出或者其它终端环境中使用。
  • 如果后续需要做富文本展示,也可以在字符串中保留结构,方便二次解析。

Kotlin 实现代码(KMP)

核心逻辑位于 src/jsMain/kotlin/App.kt 中,通过 @JsExport 导出给 JavaScript 使用:

@OptIn(ExperimentalJsExport::class)
@JsExport
fun personalBudgetAnalyzer(inputText: String = "10000 5000 2000 1500"): String {
    // 输入格式: "总收入 支出1 支出2 ...", 例如 "10000 5000 2000 1500"
    val parts = inputText.trim().split(" ").filter { it.isNotEmpty() }

    if (parts.size < 2) {
        return "❌ 错误: 请按 '总收入 支出1 支出2 ...' 的格式输入,例如: 10000 5000 2000 1500"
    }

    val income = parts[0].toDoubleOrNull()
    val expenses = parts.drop(1).mapNotNull { it.toDoubleOrNull() }

    if (income == null || expenses.any { it <= 0 }) {
        return "❌ 错误: 总收入或支出无效\n请输入正确的正数,例如: 10000 5000 2000 1500"
    }

    val totalExpense = expenses.sum()
    val balance = income - totalExpense

    val expenseRatios = expenses.map { (it / income * 100).toInt() / 10.0 }

    val expenseReport = expenses.zip(expenseRatios).mapIndexed { index, (expense, ratio) ->
        "  支出${index + 1}: ${expense} 元 (${ratio}%)"
    }

    val budgetComment = when {
        balance >= 0 -> "预算良好,剩余 ${balance} 元。"
        else -> "预算超支,缺口 ${-balance} 元。"
    }

    return "📊 个人收支预算分析\n" +
           "━━━━━━━━━━━━━━━━━━━━━\n" +
           "原始输入: $inputText\n\n" +
           "1️⃣ 收支统计:\n" +
           "  总收入: ${income} 元\n" +
           "  总支出: ${totalExpense} 元\n" +
           "  余额: ${balance} 元\n\n" +
           "2️⃣ 支出明细:\n" +
           expenseReport.joinToString("\n") + "\n\n" +
           "3️⃣ 预算评语:\n" +
           "  $budgetComment\n\n" +
           "━━━━━━━━━━━━━━━━━━━━━\n" +
           "✅ 分析完成!"
}

代码说明:

这是个人收支预算分析工具的完整 Kotlin 实现。函数使用 @JsExport 装饰器将其导出为 JavaScript 可调用的函数。首先进行输入解析,使用 trim().split(" ") 按空格分割输入字符串,并过滤掉空字符串。然后进行输入验证,检查是否至少有两项数据(总收入和至少一项支出)。接着尝试将第一项转换为总收入,后续项转换为支出列表。如果收入为 null 或任何支出为非正数,返回错误提示。核心计算包括:使用 expenses.sum() 计算总支出,计算余额为收入减去总支出,计算每项支出占收入的百分比。然后使用 zip()mapIndexed() 生成格式化的支出明细报告。最后根据余额是否为正判断预算是否超支,生成相应的评语。整个结果通过字符串拼接返回,包含标题、收支统计、支出明细和预算评语等多个部分。

代码说明

  • 输入解析
    • 使用 trim().split(" ") 简单地以空格拆分输入字符串,并通过 filter { it.isNotEmpty() } 过滤掉多余空格。
    • 第一项尝试转换为 Double 作为总收入,后续每一项尝试转换为支出金额。
  • 健壮性处理
    • 如果输入项不足两项,直接返回带有示例格式的错误提示。
    • 如果收入为 null 或者任何一项支出小于等于 0,同样返回错误提示,避免后续计算出现不合理结果。
  • 核心计算
    • 总支出通过 expenses.sum() 一行即可完成,体现了 Kotlin 集合 API 的简洁。
    • 余额 balance 为收入减去总支出,是预算健康与否的关键指标。
    • 比例计算时使用 (it / income * 100).toInt() / 10.0 的方式,保留一位小数,并且避免过长的小数显示影响可读性。
  • 结果拼接
    • 整体采用字符串拼接的方式构造输出文本,使用了多种 Emoji 和分隔线,让终端输出或 ArkTS 页面中的展示更加直观。

从这个实现可以看到,Kotlin 在处理数值运算、集合遍历和字符串拼接时非常自然,而 @JsExport 则为跨端使用打开了大门,让这段代码可以被 JS / ArkTS 直接复用。


JavaScript 调用示例

Kotlin/JS 编译完成后,会在 build/js/packages/hellokjs/kotlin/ 目录下生成 hellokjs.mjshellokjs.d.ts。在普通 JavaScript 或 TypeScript 环境中,可以像下面这样导入并调用:

// 导入 Kotlin 编译生成的 ES Module
import { personalBudgetAnalyzer } from './hellokjs.mjs';

// 使用默认示例输入
const result1 = personalBudgetAnalyzer();
console.log(result1);

// 使用自定义输入,例如加入更多支出项目
const result2 = personalBudgetAnalyzer('12000 4000 3000 1000 800 600');
console.log(result2);

代码说明:

这段代码展示了如何在 JavaScript 环境中调用 Kotlin 编译生成的函数。首先使用 ES Module 的 import 语句导入 personalBudgetAnalyzer 函数,该函数由 Kotlin 通过 @JsExport 装饰器导出。可以不传入参数调用函数,此时使用 Kotlin 中定义的默认值 “10000 5000 2000 1500”。也可以传入自定义的输入字符串,例如 “12000 4000 3000 1000 800 600”,表示总收入 12000 元,五项支出分别为 4000、3000、1000、800 和 600 元。函数返回一个格式化的分析结果字符串,可以直接打印到控制台或在网页中展示。

在实际工程中,你可以把这段调用放在 Node.js 命令行工具、Web 前端页面或任何支持 ES Module 的运行环境中。借助 hellokjs.d.ts,在 TypeScript/ArkTS 中还能获得良好的类型推断和智能提示,调用体验与原生 JS 函数几乎没有差别。


ArkTS 页面集成与调用

在 OpenHarmony 工程 kmp_ceshiapp 中,我们已经将首页 Index.ets 改造成个人收支预算分析页面。ArkTS 通过普通的 import 语句引入 Kotlin 导出的函数,并在组件生命周期和按钮事件中进行调用:

import { personalBudgetAnalyzer } from './hellokjs';

@Entry
@Component
struct Index {
  @State message: string = '请输入总收入和各项支出';
  @State inputText: string = '10000 5000 2000 1500';
  @State resultText: string = '';

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

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

  build() {
    // 省略 UI 细节,这里重点展示调用关系和状态绑定
  }
}

代码说明:

这是 OpenHarmony ArkTS 页面的完整实现,展示了如何集成和调用 Kotlin 编译生成的预算分析函数。首先通过 import 语句从 ./hellokjs 模块导入 personalBudgetAnalyzer 函数。页面使用 @Entry@Component 装饰器定义为可入口的组件。定义了三个响应式状态变量:message 显示操作状态,inputText 存储用户输入的预算数据,resultText 存储分析结果。aboutToAppear() 生命周期钩子在页面加载时自动调用 calculateBudget() 进行初始化计算。calculateBudget() 方法获取输入文本,调用 Kotlin 函数进行预算分析,将结果存储在 resultText 中,并更新 message 显示计算状态。使用 try-catch 块捕获任何异常,并在错误时更新 message 显示错误信息。这种设计将算法逻辑完全交给 Kotlin,ArkTS 只负责状态管理和 UI 绑定。

这段代码演示了一个非常典型的 KMP + ArkTS 集成模式:

  • personalBudgetAnalyzer 由 Kotlin 提供,实现和测试集中在 Kotlin 工程中完成。
  • ArkTS 页面只需要负责:
    • 管理输入 inputText 和输出 resultText 的状态。
    • 在生命周期钩子(如 aboutToAppear)或者按钮点击事件中调用算法函数。
    • 根据返回结果更新界面上的提示文字 message

当后续你要调整预算算法、增加更复杂的统计逻辑时,大部分改动只需要在 Kotlin 这一端完成,ArkTS 的调用代码基本可以保持不变,从而提高跨端项目的可维护性和可扩展性。


数据输入与交互体验

在 ArkTS 页面中,输入区域使用了 TextInput 组件来接收用户输入:

  • 占位符直接给出示例:例如: 10000 5000 2000 1500,降低用户第一次使用时的理解成本。
  • 默认示例按钮会重置输入为一串合理的数据,并立即触发计算,方便用户快速看到效果。
  • 清空按钮则把输入和结果都清理掉,把状态提示还原成“请输入总收入和各项支出”,适合用户重新开始一轮完全不同的预算分析。

整体交互流程大致如下:

  1. 页面出现时自动调用一次 calculateBudget(),用默认示例填充结果区域,让用户一眼就能看到工具的作用。
  2. 用户可以修改输入并点击“开始分析”按钮,调用同一个 Kotlin 函数完成重新计算。
  3. 如果输入格式不正确(比如只填了一个数字、出现非数字字符等),Kotlin 侧会返回带有具体说明的错误文本,ArkTS 只需要原样展示,避免在多处重复输入验证逻辑。

这种“后端算法负责严谨校验与结果组织、前端 UI 负责呈现与交互细节”的分层方式,在跨端项目中非常实用,也有利于后续增加更多工具型案例。


编译与自动复制流程

整个 KMP → JS → ArkTS 的编译和文件同步由根目录下的脚本负责,你可以按以下方式执行:

  1. 在项目根目录或终端中执行 Gradle 构建:
    • Windows 批处理脚本:build-and-copy.bat
    • 或 PowerShell 脚本:build-and-copy.ps1
  2. 脚本会自动完成:
    • 运行 gradlew build,编译 Kotlin/JS 工程,生成 hellokjs.mjshellokjs.d.ts
    • 检查输出文件是否存在,给出清晰的成功或失败提示。
    • 将这两个文件复制到 kmp_ceshiapp/entry/src/main/ets/pages/ 目录下:
      • hellokjs.d.ts 原样复制。
      • hellokjs.mjs 复制并重命名为 hellokjs.js,方便 ArkTS 通过相对路径导入。

这样一来,每当你在 App.kt 中新增或修改 Kotlin 函数,只需要重新执行一次脚本,就能让 ArkTS 端使用到最新的逻辑,无需手动拷贝文件或调整路径。


总结

通过这个 个人收支预算分析工具 案例,我们再次走了一遍从 Kotlin 到 JavaScript,再到 ArkTS 的完整跨端链路:

  1. 在 Kotlin 中实现面向业务的算法逻辑,并通过 @JsExport 暴露给 JavaScript 使用。
  2. 使用 Kotlin/JS 编译器生成 ES Module 和类型声明文件,实现类型安全与模块化复用。
  3. 在 OpenHarmony 的 ArkTS 页面中像使用普通 JS 库一样导入和调用 Kotlin 提供的函数。
  4. 通过简单明了的多行文本输出,把算法结果直接展示在移动端 UI 上。

与之前的 BMI、贷款计算、成绩分析、小游戏井字棋等案例一起,这个工具形成了一个较为系统的 KMP 跨端示例集合,覆盖了算法类工具、数据分析和互动小游戏等不同场景。你可以在此基础上继续扩展,比如:

  • 为每一项支出增加类别名称,生成更详细的预算报表;
  • 增加“储蓄率”“投资比例”等高级指标;
  • 把本地计算与远端服务结合起来,实现云端同步或多设备共享。

无论如何,核心思路始终不变:让 Kotlin 负责严肃的计算与业务规则,让 ArkTS 负责友好的界面与交互体验,通过 KMP 把两者优雅地串联起来。

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

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐