开源鸿蒙平台 KMP_CMP 三方库「Clikt」适配全流程

本文记录 clikt 接入 OpenHarmony 的完整过程,覆盖 KMP 工程盘点、ohosArm64

目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、命令行解析示例、

HAP 构建、签名和真机验收。

Clikt 本身是 Kotlin Multiplatform 命令行解析库,不是 Compose Multiplatform UI

组件库。因此本次适配复用 KMP 侧的真实解析器和测试模型,使用 ArkUI 作为

OpenHarmony 宿主页面,验证同一份 Kotlin 代码可以从 JVM 进入 OpenHarmony ARM64

运行时,而不是在 ArkTS 页面中重新实现一套解析规则。

项目地址: AtomGit/oh-tpc/clikt

上游仓库: ajalt/clikt

开发工具: 华为云码道

环境安装: 环境安装指南

一、背景

1.1 为什么做开源鸿蒙平台 KMP/CMP 适配

Clikt 的核心能力是把命令行参数解析为类型安全的 Kotlin 值,并统一处理默认值、

选项、flag、校验和错误退出码。原仓库面向 JVM、JS、Wasm、Apple、Linux 和 Windows

等平台,OpenHarmony 侧需要增加真正的 Kotlin/Native 目标,才能证明解析器进入了

OpenHarmony 运行时。

如果只把页面重写成 ArkTS,再在页面里手工判断 --name、--count 和 --verbose,

页面虽然可以显示几个字符串,却无法证明共享 Kotlin 解析器、Native 动态库和 N-API

桥接已经连通。本次适配需要解决以下问题:

障碍 具体问题
目标缺失 KMP 模块默认没有 OpenHarmony 目标,必须加入 ohosArm64() 才能生成 KLIB 和动态库。
工具链不一致 Kotlin/Native、Gradle、OpenHarmony LLVM、Native SDK 和 DevEco Hvigor 必须使用匹配版本。
平台边界 ArkTS 不能直接持有 Kotlin data class,需要使用 C ABI、C++ N-API 和 JSON。
参数生命周期 输入框内容、快捷预设和解析结果必须保持一致,避免页面展示的参数与实际解析参数不同。
错误可见性 Clikt 的校验错误、退出码和 Native 异常都要返回页面,不能只在 Native 日志里失败。
内存释放 Kotlin/Native 返回的 C 字符串必须由明确的 CliktFree 入口释放。
交付链路复杂 Native 动态库、CMake、HAP、签名、设备安装和 ARM64 依赖需要分别验证。

因此,本项目把适配边界放在三个地方:Kotlin/Native 目标配置、C ABI/N-API 桥接层和

ArkUI 示例页面。参数解析和错误语义仍由 Clikt 维护,ArkTS 只负责输入、按钮交互、

结果展示和页面状态。

1.2 Clikt 示例提供的能力

example/shared 中的公共示例使用 Clikt 核心 API 定义了一个简单命令:

  • --name / -n:字符串选项,默认值为 OpenHarmony;
  • --count / -c:整数选项,默认值为 1,有效范围为 1..5;
  • --verbose / -v:flag 选项,用于切换详细输出;
  • CommandLineParser.tokenize:把输入框中的命令行文本拆成参数列表;
  • CliktError:将参数错误转换为可展示的错误文本和退出码;
  • runChecks:在 JVM 示例和 OpenHarmony Native 页面中复用相同的验收检查。

解析结果使用以下结构:

字段 含义
id 当前结果的来源标识,例如 command 或 preset-1
argv 实际参与解析的参数文本
output 成功时的命令输出,失败时的 Clikt 错误信息
success 是否解析和执行成功
exitCode 成功为 0,参数错误通常为 1

页面中的三个按钮职责不同:

按钮 行为 示例
默认参数 写入预设并立即解析 --name OpenHarmony --count 1
详细模式 写入带 flag 的预设并立即解析 --name Clikt --verbose
解析命令 只解析输入框当前内容 用户可以自行编辑任意参数

1.3 实现适配矩阵

维度 适配方式
代码复用 公共命令、Clikt 参数声明、tokenize、错误处理和检查逻辑由 Kotlin 共享。
平台目标 为核心 clikt 和示例工程加入 ohosArm64,生成 libclikt.so。
桥接稳定 使用少量 C ABI 函数和 UTF-8 JSON,避免把 Kotlin 对象地址交给 ArkTS。
UI 完整 页面支持输入参数、默认预设、详细预设、手动解析、成功结果和错误结果。
可测试 JVM 测试、Native 链接、ELF 依赖、HAP 构建、设备安装分别验收。
签名安全 源码只保留签名入口,证书、profile、p12 和密码由开发者本机配置。
仓库规范 项目、文章、截图和示例链接统一使用 AtomGit。

本项目没有把 Compose Multiplatform UI 强行移植到 ArkUI。KMP 负责共享 Clikt

解析能力,ArkUI 负责 OpenHarmony 宿主页面;这种边界可以让其他 KMP/CMP 应用复用

Clikt 核心 API,并自行决定页面实现方式。

二、实现路线图

text 复制代码
第 1 阶段:项目初始化     ── 盘点 KMP 模块、解析示例和 OpenHarmony 工程边界
第 2 阶段:目标与依赖打通 ── 加入 ohosArm64、独立 example 工程和 Native 构建任务
第 3 阶段:解析与序列化   ── 建立 CliktExample、错误状态和稳定 JSON 契约
第 4 阶段:原生桥接       ── Kotlin/Native C ABI、C++ N-API、类型检查和内存释放
第 5 阶段:ArkUI 页面      ── 输入框、预设按钮、手动解析、状态提示和错误展示
第 6 阶段:示例与验证     ── HAP 构建、签名、设备安装、按钮交互和效果图

每个阶段都使用真实产物作为下一阶段输入:先用 JVM 验证 Clikt 解析,再把同一份

commonMain 代码链接为 ARM64 动态库,随后由 CMake 和 N-API 加载到 Stage 工程,

最后安装签名 HAP 观察设备页面上的解析结果。

三、逐步实现过程

第 1 阶段:项目初始化

1.1 盘点公共 API 和工程边界

项目采用清晰的 KMP、Native 和 DevEco 分层:

text 复制代码
clikt/                     Clikt 核心多平台库和 ohosArm64 目标
clikt-mordant/             Mordant 输出扩展
clikt-mordant-markdown/    Markdown 输出扩展
example/shared/            公共命令示例和 JVM 验收测试
example/nativeApp/         ohosArm64 Kotlin/Native 动态库
example/ohosApp/           DevEco Stage 工程、C++ N-API 和 ArkUI 页面
scripts/                   Native、HAP、签名工程和依赖检查脚本
docs/openharmony/          OpenHarmony 验收记录和真机效果图

example 是独立 Gradle 工程,不把 DevEco 工程直接当作根工程的 Kotlin 子模块。

这样可以分别执行 Gradle 和 Hvigor,也可以把 ohosApp 复制到另一个目录后在

DevEco Studio 中配置签名。

1.2 固定工具链和版本矩阵

本次适配使用以下版本约定:

项目 配置 用途
Kotlin Multiplatform 2.2.21-1.0.0 JVM、Kotlin/Native 和 KLIB
Gradle 8.14.3 根工程和 example 工程构建
JDK 21 Gradle、Kotlin 编译和 Native 任务
OpenHarmony 目标 ohosArm64 ARM64 真机动态库
DevEco product 6.0.0(20) 工程兼容和目标版本
DevEco Studio 6.0.0 Release IDE 版本,负责工程同步、HAP 构建与签名
ABI arm64-v8a HAP 原生库架构

执行 Gradle 脚本前先选择 JDK 21:

bash 复制代码
export JAVA_HOME="/path/to/jdk-21"
java -version

OpenHarmony SDK 版本满足编译条件,只说明工程可以生成 HAP;应用是否能安装和运行,

还要由 DevEco 和 ARM64 设备单独验证。

1.3 创建 OpenHarmony 示例目录

示例页面围绕"当前输入、解析结果、退出码、检查状态和最近一次解析"组织:

text 复制代码
标题区          Clikt 工作台 / KMP NATIVE
输入区          命令行输入框、解析次数和快捷示例
操作区          默认参数 / 详细模式 / 解析命令
结果区          SUCCESS 或 EXIT、输出文本和实际 argv
检查区          公共检查通过数量和 Native 错误文本

快捷按钮用于快速准备可重复的输入,解析按钮用于验证任意手动编辑的命令行。每次

解析都会更新状态文本和刷新次数,避免"参数没有变化所以看起来没有响应"。

第 2 阶段:目标与依赖打通

2.1 在核心模块加入 ohosArm64

clikt/build.gradle.kts 保留所有原有目标,并增加 OpenHarmony ARM64:

kotlin 复制代码
kotlin {
    jvm()
    js { nodejs() }
    wasmJs { nodejs() }
    wasmWasi { nodejs() }

    linuxX64()
    linuxArm64()
    ohosArm64()
    mingwX64()
    macosX64()
    macosArm64()
    iosArm64()
    iosX64()
    iosSimulatorArm64()
}

适配的关键是让公共 commonMain 代码在 ohosArm64 上可编译,而不是复制一份只

能在 ArkTS 中运行的解析实现。Mordant 模块仍然保持原有目标矩阵,因为公开 Mordant

制品没有对应的 OpenHarmony 变体。

2.2 独立 example 工程消费 Clikt

example/shared 使用平台无关的公共源码,并通过项目属性消费本地 Clikt 制品:

kotlin 复制代码
kotlin {
    explicitApi()
    jvm()
    ohosArm64()

    sourceSets {
        commonMain.dependencies {
            api("com.github.ajalt.clikt:clikt-core:${property("cliktVersion")}")
        }
        commonTest.dependencies {
            implementation(kotlin("test"))
        }
    }
}

根脚本先发布适配后的本地 Maven 制品,示例工程再使用相同版本执行 JVM 测试和

Native 链接。这样可以确认"被消费的 artifact"与刚刚修改的源码一致。

2.3 构建 Native 动态库

example/nativeApp 生成一个受 linker map 约束的 shared library:

kotlin 复制代码
kotlin {
    ohosArm64 {
        binaries.sharedLib {
            baseName = "clikt"
            linkerOpts("--entry=0", "--version-script=${project.file(
                "src/ohosArm64Main/linker/shared-library.map"
            )}")
            linkerOpts("-lace_napi.z", "-luv", "-lhilog_ndk.z")
        }
    }
}

prepareOhos 在链接成功后复制两个构建产物:

text 复制代码
entry/libs/arm64-v8a/libclikt.so
entry/src/main/cpp/include/libclikt_api.h

动态库、生成头文件和 HAP 属于构建产物,仓库会忽略它们;每台开发机都可以从源码

重新生成与本机 SDK 匹配的文件。

第 3 阶段:解析与序列化

3.1 为什么需要统一 JSON 契约

ArkTS、C++ 和 Kotlin/Native 不能直接共享 Kotlin 对象,因此边界使用 UTF-8 JSON:

text 复制代码
TextInput command line
        ↓ string
CliktClient.ets
        ↓ N-API run(commandLine)
C++ N-API entry
        ↓ C string
Kotlin/Native CliktRun
        ↓ CliktExamples.execute
CliktExample JSON
        ↓
ArkUI result card

页面只消费 JSON,不复制 option().default()、.int()、.flag() 和 .check() 的

业务规则。这样 JVM 测试和设备页面都使用同一份 Clikt 解析逻辑。

3.2 公共 DemoCommand

示例命令位于 example/shared/src/commonMain:

kotlin 复制代码
private class DemoCommand : CoreCliktCommand(name = "clikt-demo") {
    private val name by option("--name", "-n").default("OpenHarmony")
    private val count by option("--count", "-c")
        .int()
        .default(1)
        .check("count must be between 1 and 5") { it in 1..5 }
    private val verbose by option("--verbose", "-v").flag()

    var output: String = ""
        private set

    override fun run() {
        val greeting = List(count) { "Hello, $name!" }.joinToString(" ")
        output = if (verbose) "[verbose] $greeting" else greeting
    }
}

CliktExamples.execute 先调用 CommandLineParser.tokenize,再把参数列表交给

command.parse(argv)。成功结果带 exitCode = 0;CliktError 会被转换为失败结果,

保留错误消息和状态码。

3.3 结果 JSON

正常解析结果保持稳定字段:

json 复制代码
{
  "id": "command",
  "argv": "--name HarmonyOS --count 2",
  "output": "Hello, HarmonyOS! Hello, HarmonyOS!",
  "success": true,
  "exitCode": 0
}

Native 异常则返回:

json 复制代码
{
  "error": "Native Clikt error"
}

ArkTS 侧会把缺少正常字段的响应转换为结构完整的失败结果,避免页面渲染时访问

undefined 字段。

3.4 公共检查

CliktExamples.runChecks() 覆盖七项检查:

  1. 默认选项可以解析;
  2. 自定义名称和次数可以解析;
  3. 整数选项控制问候语重复次数;
  4. verbose flag 会改变输出前缀;
  5. count = 0 会被校验拒绝;
  6. 公共 API 不依赖平台类型;
  7. OpenHarmony 桥接只传递不可变 JSON 值。

页面将结果展示为 7/7 公共检查通过,这个数字来自 Native 返回,而不是页面写死。

第 4 阶段:原生桥接(技术难点)

4.1 Kotlin/Native 对象不能直接交给 ArkTS

Kotlin/Native 的 CliktExample 属于 Kotlin 运行时对象。ArkTS 只能接收 JavaScript

值,C++ 不能把 Kotlin 对象地址直接当成 JavaScript 对象。

最终采用三层桥接:

text 复制代码
ArkTS
  │ JSON string
  ▼
C++ N-API entry
  │ const char*
  ▼
Kotlin/Native C ABI
  │ CliktExamples
  ▼
UTF-8 JSON + explicit free
4.2 方案对比
方案 优点 缺点 选用
直接导出 Kotlin 对象 代码少 ABI、生命周期和类型不可控 ❌
只导出解析后的字符串 实现简单 页面无法展示参数、错误和退出码 ❌
C ABI + JSON 边界清晰、易扩展、易调试 有一次序列化开销 ✅
在 ArkTS 重写完整解析器 页面调用简单 KMP 和 ArkTS 逻辑容易分叉 ❌
4.3 Kotlin/Native 导出函数

NativeBridge.kt 对外导出四个 C ABI 符号:

kotlin 复制代码
@CName("CliktCatalog")
public fun catalogNative(): CPointer<ByteVar> = response { ... }

@CName("CliktRun")
public fun runNative(commandLine: CPointer<ByteVar>?): CPointer<ByteVar> = response {
    CliktExamples.execute(commandLine?.toKString().orEmpty()).toJson()
}

@CName("CliktRunChecks")
public fun checksNative(): CPointer<ByteVar> = response { ... }

@CName("CliktFree")
public fun freeNative(pointer: CPointer<ByteVar>?) {
    if (pointer != null) nativeHeap.free(pointer.rawValue)
}

返回值使用 Native heap 分配的、以 0 结尾的 C 字符串。每一块返回缓冲区都由

CliktFree 释放。

4.4 C++ N-API 方法分发

C++ 注册三个 ArkTS 方法:

cpp 复制代码
napi_property_descriptor methods[] = {
    {"getCatalog", nullptr, Catalog, nullptr, nullptr, nullptr,
        napi_default, nullptr},
    {"run", nullptr, Run, nullptr, nullptr, nullptr,
        napi_default, nullptr},
    {"runChecks", nullptr, RunChecks, nullptr, nullptr, nullptr,
        napi_default, nullptr},
};

run 先读取 ArkTS 字符串长度,再写入可变 std::string 缓冲区,最后调用

CliktRun。ReadNative 创建 ArkTS 字符串后立即调用 CliktFree,保证成功和异常

路径都不泄漏 Native 缓冲区。

CMake 将 Kotlin/Native 动态库作为 imported library:

cmake 复制代码
add_library(clikt SHARED IMPORTED)
set_target_properties(clikt PROPERTIES
  IMPORTED_LOCATION
  "${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/arm64-v8a/libclikt.so")

add_library(entry SHARED napi_init.cpp)
target_include_directories(entry PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(entry PRIVATE clikt libace_napi.z.so)
4.5 N-API 生命周期
text 复制代码
ArkTS run(commandLine)
        │
        ▼
napi_get_value_string_utf8
        │
        ▼
CliktRun(commandLine)
        │
        ▼
napi_create_string_utf8
        │
        ▼
CliktFree(nativeBuffer)
        │
        ▼
return JS string

C++ 负责完成跨语言字符串转换和 Native 释放,ArkTS 页面不需要知道 Kotlin/Native

的堆实现。

第 5 阶段:ArkUI 页面

5.1 页面状态

Index.ets 保存输入参数、解析结果、检查状态、错误文本和解析次数:

typescript 复制代码
@State private commandLine: string = '--name OpenHarmony --count 1';
@State private selected: CliktExample = emptyExample();
@State private status: string = '正在连接 Native Clikt';
@State private errorText: string = '';
@State private parseCount: number = 0;
@State private lastAction: string = '等待操作';

页面加载时会自动执行一次 refresh()。输入框的 onChange 只更新命令行文本,真正

点击"解析命令"时才调用 Native run,避免每次键入字符都触发完整解析。

5.2 三个按钮的实现
typescript 复制代码
private usePreset(commandLine: string): void {
  this.refresh(commandLine);
}

Button('默认参数')
  .onClick(() => this.usePreset('--name OpenHarmony --count 1'))

Button('详细模式')
  .onClick(() => this.usePreset('--name Clikt --verbose'))

Button('解析命令')
  .onClick(() => this.refresh(this.commandLine))

预设按钮将参数直接传给 refresh,不会在状态尚未刷新时读取旧的输入框值。手动解析

按钮始终使用当前输入内容,并在结果卡片中显示"解析完成 · 已刷新第 N 次"。

5.3 成功与失败展示

成功时页面显示 SUCCESS、Clikt 输出和实际 argv;失败时显示 EXIT 1、错误文本

和公共检查状态。Native 返回格式错误或 JSON 解析异常也会被 CliktClient.ets

转换为失败结果,不会让页面因为字段缺失直接崩溃。

第 6 阶段:示例与验证

6.1 example 工程结构
text 复制代码
example/
├── shared/
│   ├── src/commonMain/.../CliktExamples.kt
│   └── src/commonTest/.../CliktExamplesTest.kt
├── nativeApp/
│   ├── src/ohosArm64Main/.../NativeBridge.kt
│   └── src/ohosArm64Main/linker/shared-library.map
└── ohosApp/
    ├── AppScope/
    ├── entry/src/main/cpp/
    │   ├── CMakeLists.txt
    │   └── napi_init.cpp
    └── entry/src/main/ets/
        ├── pages/Index.ets
        └── clikt/CliktClient.ets

shared 验证公共 Clikt 模型,nativeApp 产生 Native 动态库,ohosApp 负责 ArkUI

页面和 N-API 加载。三者边界清晰,任何一层失败都能单独定位。

6.2 原生模块注册

napi_init.cpp 通过 napi_module_register 注册 entry 模块,类型声明位于:

text 复制代码
example/ohosApp/entry/src/main/cpp/types/libentry/index.d.ts

ArkTS 使用:

typescript 复制代码
import cliktNative from 'libentry.so';
6.3 Native 动态库准备

执行:

bash 复制代码
export JAVA_HOME="/path/to/jdk-21"
./scripts/build-openharmony.sh

脚本依次执行根模块测试、示例测试、linkDebugSharedOhosArm64 和 prepareOhos。

成功后生成:

text 复制代码
example/ohosApp/entry/libs/arm64-v8a/libclikt.so
example/ohosApp/entry/src/main/cpp/include/libclikt_api.h

还可以检查 ARM64 ELF 的强依赖:

bash 复制代码
python3 scripts/check-native-deps.py \
  /Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native \
  example/ohosApp/entry/libs/arm64-v8a
6.4 构建、签名和安装

未配置签名时,可以在 DevEco Studio 构建本地调试 HAP;真机安装需要有效签名。配置

签名后执行:

bash 复制代码
python3 scripts/prepare-signing-project.py /absolute/path/clikt-signing
DEVECO_HOME=/Applications/DevEco-Studio.app/Contents \
  scripts/build-hap.sh /absolute/path/clikt-signing

产物位于:

text 复制代码
entry/build/default/outputs/default/entry-default-signed.hap

安装并启动:

bash 复制代码
HDCP=/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc
"$HDCP" install -r \
  /absolute/path/clikt-signing/entry/build/default/outputs/default/entry-default-signed.hap
"$HDCP" shell aa start \
  -a EntryAbility -b com.github.ajalt.clikt.sample -m entry

签名证书、profile、p12 和密码只保存在本机,不要提交到 AtomGit。

四、完整代码对照

4.1 整体架构

text 复制代码
TextInput / preset button
    │ command-line string
    ▼
CliktClient.ets
    │ libentry.so
    ▼
C++ N-API
    │ C ABI
    ▼
libclikt.so
    │ Kotlin/Native
    ▼
CliktExamples.execute
    │ CoreCliktCommand.parse
    ▼
CliktExample -> JSON -> ArkUI result card

4.2 文件清单

文件 职责
clikt/src/commonMain/... Clikt 核心解析 API
example/shared/.../CliktExamples.kt 公共命令示例、JSON 前的结果模型和自检
example/shared/.../CliktExamplesTest.kt JVM 解析和错误验收
example/nativeApp/.../NativeBridge.kt C ABI、JSON 返回和内存释放
example/ohosApp/.../CliktClient.ets N-API 调用、JSON 解析和结果兜底
example/ohosApp/.../Index.ets 输入、预设、解析按钮和结果页面
example/ohosApp/.../napi_init.cpp N-API 导出、参数读取和 C 字符串转换
scripts/build-openharmony.sh 测试、Native 链接和产物复制
scripts/build-hap.sh 已配置签名工程的 HAP 构建
docs/openharmony/VALIDATION.md 自动检查和真机验收记录

4.3 关键 API 对照

层次 API 作用
Kotlin CommandLineParser.tokenize 将命令行文本拆成参数列表
Kotlin CoreCliktCommand.parse 使用 Clikt 解析选项和 flag
Kotlin CliktError 返回错误文本和退出码
Native CliktRun 返回一条命令的 JSON 结果
Native CliktRunChecks 返回公共检查 JSON
N-API run 向 ArkTS 暴露解析方法
ArkTS refresh 更新页面输入、结果和状态

4.4 ArkTS 与 Kotlin 的边界

ArkTS 只负责输入和界面生命周期:

typescript 复制代码
const result: CliktExample = run(this.commandLine);
this.selected = result;

Kotlin 负责参数语义、默认值、类型转换、范围校验和输出:

kotlin 复制代码
val command = DemoCommand()
command.parse(argv)
CliktExample("command", argv.joinToString(" "), command.output, true, 0)

两者之间只传输字符串和 JSON,不传输 Kotlin 对象、ArkTS class 实例或未校验的动态

结构。

五、关键决策说明

决策 1:把 ohosArm64 加入核心构建约定

只有真正链接 ohosArm64 动态库,才能证明共享 Clikt 代码可以进入 OpenHarmony

运行时。只添加一个 ArkTS 页面不能代表 KMP 库已经完成适配。

决策 2:独立消费者必须通过构建产物消费

example 单独解析 Clikt,ohosApp 单独接收 .so 和头文件,避免根工程编译通过

却无法在 DevEco 中打包。

决策 3:JSON 作为跨语言数据契约

JSON 让 ArkTS、C++ 和 Kotlin/Native 的边界清楚可调试,并允许后续增加可信字段而

不暴露内部对象布局。

决策 4:桥接层只开放四个 C ABI 入口

目录、解析、自检和释放已经覆盖示例所需能力;减少 ABI 符号可以降低 Native 生命周期

和兼容风险。

决策 5:快捷预设和手动解析分开

预设按钮用于快速验证固定参数,解析按钮用于验证用户输入。两者都调用同一个

refresh,不会出现页面逻辑和库逻辑各自维护一套解析器的情况。

决策 6:把库验证和设备验证分开

JVM 测试验证 Clikt 规则,Native 链接验证 ABI,Hvigor 验证 HAP,真机验证安装和

ArkUI 页面。每一层都有明确的失败边界。

六、测试与验证

6.1 测试环境

本次真机验证使用:

  • macOS;
  • JDK 21;
  • Kotlin Multiplatform 2.2.21-1.0.0;
  • DevEco Studio 及 OpenHarmony ARM64 Native SDK;
  • DevEco 自带 Node.js 和 Hvigor;
  • 已签名 entry-default-signed.hap;
  • USB 连接的 HarmonyOS ARM64 真机;
  • hdc 设备序列号 FMR0223825079397。

6.2 静态检查与单元测试

bash 复制代码
./gradlew :clikt:jvmTest :test:jvmTest
(cd example && ./gradlew :shared:jvmTest)

测试覆盖默认选项、自定义名称、重复次数、flag、整数范围错误和公共检查列表。

6.3 原生桥接和 HAP 验证

bash 复制代码
./scripts/build-openharmony.sh
python3 scripts/check-native-deps.py \
  /Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native \
  example/ohosApp/entry/libs/arm64-v8a

本地 DevEco 构建命令:

bash 复制代码
PATH=/Applications/DevEco-Studio.app/Contents/tools/node/bin:$PATH \
  /Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw \
  assembleApp --no-daemon

验证结果:

text 复制代码
Hvigor BUILD SUCCESSFUL
entry-default-signed.hap generated
ARM64 native library packaged

6.4 功能验证用例

用例 1:默认页面和自检

启动应用后页面显示默认命令 --name OpenHarmony --count 1、SUCCESS 和公共检查

通过数量。初始结果由 Native run 生成。

用例 2:默认参数按钮

点击"默认参数",输入框恢复默认命令并重新解析,结果为:

text 复制代码
Hello, OpenHarmony!
用例 3:详细模式按钮

点击"详细模式",结果为:

text 复制代码
[verbose] Hello, Clikt!

这个用例同时验证 flag 参数已经由 Clikt 处理,而不是页面手工拼接前缀。

用例 4:自定义参数和解析按钮

输入:

text 复制代码
--name OpenHarmony --count 2

点击"解析命令"后,页面显示两次问候语、实际 argv 和"解析完成 · 已刷新第 N 次"。

用例 5:无效参数

输入 --count 0,页面显示 EXIT 1 和 count must be between 1 and 5。Native

错误不会导致 ArkUI 页面崩溃。

用例 6:重复解析

连续点击"解析命令",结果内容不变但刷新次数递增,证明按钮事件被执行且页面有明确

反馈。这个场景解决了初始默认参数与再次解析结果相同、用户误以为按钮无响应的问题。

6.5 验证结论

公共 JVM 测试、Kotlin/Native ARM64 链接、ELF 依赖检查、Hvigor HAP 构建、签名安装

和真机页面交互均已完成。真机页面可以执行默认、详细和手动参数解析,说明从 Clikt

核心到 ArkTS 展示的链路可用。

七、运行效果

7.1 真机截图

截图中可以看到:

  • 页面标题为"Clikt 工作台";
  • 副标题说明这是命令行解析示例;
  • 顶部标识显示 KMP NATIVE;
  • 输入框当前为 --name OpenHarmony --count 2;
  • 快捷按钮提供"默认参数"和"详细模式";
  • 主按钮为"解析命令";
  • 结果卡片显示两次 Hello, OpenHarmony!;
  • 状态为 SUCCESS,公共检查为 7/7 公共检查通过;
  • 解析结果下方显示实际参与解析的参数。

7.2 命令速查

bash 复制代码
# 根工程测试和 OpenHarmony Native 准备
export JAVA_HOME="/path/to/jdk-21"
./scripts/build-openharmony.sh

# 单独运行共享测试
(cd example && ./gradlew :shared:jvmTest)

# 设备依赖检查
python3 scripts/check-native-deps.py \
  /Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native \
  example/ohosApp/entry/libs/arm64-v8a

# 在已配置签名的工程中构建 HAP
./scripts/build-hap.sh /absolute/path/clikt-signing

# 安装和启动
hdc install -r \
  /absolute/path/clikt-signing/entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.github.ajalt.clikt.sample -m entry

八、遗留问题与改进方向

8.1 踩坑复盘

  1. 只改 ArkTS 页面不算 KMP 适配 :必须把共享 Clikt 代码真正编译成 ohosArm64
    动态库。
  2. N-API 不负责业务判断 :C++ 只做参数检查、字符串转换和释放,参数语义放在
    Kotlin Clikt 命令中。
  3. Native 字符串必须及时释放 :每次 CliktRun、CliktCatalog 和
    CliktRunChecks 返回后都要配对调用 CliktFree。
  4. 解析按钮需要可见反馈 :默认参数已经在页面启动时解析,再次点击若只显示相同
    文本,用户会误以为没有响应,因此页面增加了刷新次数和状态提示。
  5. JDK、Node 和 DevEco 工具链要分开确认 :Gradle 使用 JDK 21,Hvigor 使用
    DevEco 自带 Node,避免系统 Node 版本导致 Native 构建异常。
  6. 签名材料不能进入源码:证书、profile、p12 和密码必须放在独立的本机工程中。

8.2 已知问题

  • 当前示例只构建 ohosArm64 和 arm64-v8a,不覆盖 OpenHarmony x86 或其他 ABI;
  • Clikt 是命令行解析库,ArkUI 页面是验收宿主,不提供 Compose Multiplatform UI 组件;
  • Native 和 HAP 构建依赖本机 OpenHarmony SDK、DevEco 工具链和签名配置;
  • 页面采用同步 Native 调用,复杂或长时间运行的命令应在业务层增加异步调度;
  • 文章中的签名配置只适用于本地开发机,不能直接复制到其他环境。

8.3 未来优化方向

  • 增加 Flow<CliktExample> 或异步执行接口,支持更复杂的命令任务;
  • 为 Compose Multiplatform 宿主补充共享状态卡片示例,保持 ArkUI 与 CMP 的模型一致;
  • 增加命令行引号、转义和多行输入的交互测试;
  • 增加设备能力、签名状态和 Native 库版本的诊断页面;
  • 在持续集成中加入 ohosArm64 链接、ELF 依赖检查和 HAP 未签名构建任务;
  • 将 AtomGit 文档、示例截图和版本发布说明纳入同一套发布流程。

九、总结

9.1 核心难点回顾

本次适配真正需要处理的不是一个按钮,而是一条完整跨端链路:

text 复制代码
命令行文本
    → ArkUI 输入和页面状态
    → C++ N-API
    → Kotlin/Native C ABI
    → KMP Clikt 核心解析器
    → JSON
    → ArkUI 结果页面

9.2 封装层次

  • KMP 层:定义选项、默认值、类型转换、flag、范围检查和错误语义;
  • Native 层:生成 ARM64 动态库,并通过 C ABI 输出有限入口;
  • N-API 层:完成参数检查、字符串转换和 Native 内存释放;
  • ArkTS 层:管理输入框、快捷预设、解析动作、结果状态和错误提示;
  • DevEco 层:完成 CMake、HAP、签名、安装和真机运行。

9.3 三条经验

  1. 先让共享 Clikt 模型在 JVM 和 Native 通过,再接入 ArkUI;
  2. 用 JSON 和少量 C ABI 代替跨语言对象传递;
  3. 把自动测试、HAP 构建和真机交互分别记录,避免把"编译成功"误认为"设备运行链路可用"。

9.4 适配成果

当前 Clikt OpenHarmony 适配已完成:

  • clikt 核心模块新增 ohosArm64 目标;
  • example/shared 复用真实 Clikt parser、默认选项、flag 和范围校验;
  • example/nativeApp 生成 ARM64 libclikt.so 和 C ABI 头文件;
  • Kotlin/Native + C ABI + C++ N-API JSON 桥接;
  • ArkUI 页面支持输入、默认参数、详细模式和手动解析;
  • 解析成功、退出码、错误文本和公共检查状态可视化;
  • HAP 构建、签名安装和 Mate 60 Pro 真机验证;
  • AtomGit 项目文档、OpenHarmony 验收记录和真机效果图;
  • 仓库中的代码托管地址、项目文档和发布链接已统一使用 AtomGit。

适配过程 FAQ

以下问题来自 ohosArm64 目标接入、NAPI 桥接、HAP 构建与真机验收的实际流程,按出现频率排序。

Q1:DevEco Studio Sync 失败或 ohpm 依赖下载超时,怎么处理?

  • 确认 DevEco Studio 版本与工程 build-profile.json5 的 compatibleSdkVersion 一致;
  • 在 File > Settings > Build, Execution, Deployment > Ohpm 配置可用的 ohpm 仓库镜像或代理;
  • 删除 oh_modules、oh-package-lock.json5 与 .hvigor 缓存后重新 Sync;
  • 公司网络需要代理时,同时给 ohpm 与 hvigor 配置 http.proxy。

Q2:CMake 报 Missing .../libxxx.so 或找不到 Kotlin/Native 动态库?

  • 先在 example 目录执行 ./gradlew :nativeApp:linkDebugSharedOhosArm64(模拟器用 linkDebugSharedOhosX64);
  • 确认 libxxx.so 与 libc++_shared.so 已复制到 ohos/entry/libs/<abi>/;
  • 检查 entry/build-profile.json5 的 abiFilters 与设备 ABI 是否一致(真机 arm64-v8a,模拟器 x86_64);
  • 清理 entry/.cxx 与 entry/build 后重试。

Q3:ArkTS 报 Cannot find module 'libxxx_napi.so' 或调用返回 undefined?

  • 确认 CMake 的 add_library(<target> SHARED ...) 名称与 import ... from 'libxxx_napi.so' 完全一致;
  • 解包 HAP,确认 libs/<abi>/libxxx_napi.so 已打包;
  • 删除 entry/build、entry/.cxx 与 oh_modules 后重新构建;
  • 检查 module.json5 的 srcEntry 与页面路径是否正确。

Q4:真机安装报 signature verification failed 或 install failed?

  • 在 DevEco Studio 的 Signing Configs 配置证书、Profile 与密码;
  • bundleName 必须与签名 profile 中的 bundle name 一致;
  • 设备需加入 profile 的 deviceIds,调试证书有效期需覆盖当前时间;
  • 用 hdc list targets 确认设备在线,再执行 hdc install -r xxx-signed.hap。

Q5:应用启动白屏、闪退或 NAPI 调用崩溃?

  • 用 hdc hilog | grep -i "napi\|kotlin" 查看原生日志;
  • 确认 libc++_shared.so 与 Kotlin/Native 工具链版本匹配,且 ABI 与设备一致;
  • 检查 C ABI 返回的字符串是否由 Native 侧分配、ArkTS 侧复制后是否调用 XxxFree 释放;
  • 在 DevEco 的 Native 调试配置中附加 lldb,定位空指针与越界访问。

Q6:Gradle 报 ohosArm64 目标不存在或 Kotlin/Native 编译失败?

  • 使用鸿蒙发行版 Kotlin(如 2.2.21-1.0.0),并在 settings.gradle.kts 配置 eazytec 仓库;
  • Gradle 建议 8.x、JDK 21,版本不匹配时先统一工具链;
  • 确认 kotlin { ohosArm64() } 已应用,且 local.properties 的 SDK 路径有效;
  • 清理 ~/.gradle/caches 中对应版本的 Kotlin/Native 缓存后重试。

Q7:HAP 构建报 SDK 版本不匹配或 compatibleSdkVersion 错误?

  • 对齐 DevEco Studio、HarmonyOS SDK 与 build-profile.json5 中的 compatibleSdkVersion;
  • 升级 DevEco Studio 后同步升级 hvigor 与 SDK,避免 modelVersion 与 IDE 不匹配;
  • 命令行构建时设置 DEVECO_SDK_HOME 指向 DevEco 的 sdk 目录。

Q8:命令行 hvigor 构建失败,但 DevEco 里能构建?

  • 使用 DevEco 自带的 node 与 hvigor:
    "$DEVECO/Contents/tools/node/bin/node" "$DEVECO/Contents/tools/hvigor/bin/hvigorw.js" ...;
  • 设置 JAVA_HOME、NODE_HOME、DEVECO_SDK_HOME;
  • 加上 --no-daemon 排除守护进程缓存影响,并清理 entry/build 与 .hvigor。

参考文档

这里写自定义目录标题

    • 一、背景
      • [1.1 为什么做开源鸿蒙平台 KMP/CMP 适配](#1.1 为什么做开源鸿蒙平台 KMP/CMP 适配)
      • [1.2 Clikt 示例提供的能力](#1.2 Clikt 示例提供的能力)
      • [1.3 实现适配矩阵](#1.3 实现适配矩阵)
    • 二、实现路线图
    • 三、逐步实现过程
      • [第 1 阶段:项目初始化](#第 1 阶段:项目初始化)
        • [1.1 盘点公共 API 和工程边界](#1.1 盘点公共 API 和工程边界)
        • [1.2 固定工具链和版本矩阵](#1.2 固定工具链和版本矩阵)
        • [1.3 创建 OpenHarmony 示例目录](#1.3 创建 OpenHarmony 示例目录)
      • [第 2 阶段:目标与依赖打通](#第 2 阶段:目标与依赖打通)
        • [2.1 在核心模块加入 `ohosArm64`](#2.1 在核心模块加入 ohosArm64)
        • [2.2 独立 example 工程消费 Clikt](#2.2 独立 example 工程消费 Clikt)
        • [2.3 构建 Native 动态库](#2.3 构建 Native 动态库)
      • [第 3 阶段:解析与序列化](#第 3 阶段:解析与序列化)
        • [3.1 为什么需要统一 JSON 契约](#3.1 为什么需要统一 JSON 契约)
        • [3.2 公共 DemoCommand](#3.2 公共 DemoCommand)
        • [3.3 结果 JSON](#3.3 结果 JSON)
        • [3.4 公共检查](#3.4 公共检查)
      • [第 4 阶段:原生桥接(技术难点)](#第 4 阶段:原生桥接(技术难点))
        • [4.1 Kotlin/Native 对象不能直接交给 ArkTS](#4.1 Kotlin/Native 对象不能直接交给 ArkTS)
        • [4.2 方案对比](#4.2 方案对比)
        • [4.3 Kotlin/Native 导出函数](#4.3 Kotlin/Native 导出函数)
        • [4.4 C++ N-API 方法分发](#4.4 C++ N-API 方法分发)
        • [4.5 N-API 生命周期](#4.5 N-API 生命周期)
      • [第 5 阶段:ArkUI 页面](#第 5 阶段:ArkUI 页面)
        • [5.1 页面状态](#5.1 页面状态)
        • [5.2 三个按钮的实现](#5.2 三个按钮的实现)
        • [5.3 成功与失败展示](#5.3 成功与失败展示)
      • [第 6 阶段:示例与验证](#第 6 阶段:示例与验证)
        • [6.1 example 工程结构](#6.1 example 工程结构)
        • [6.2 原生模块注册](#6.2 原生模块注册)
        • [6.3 Native 动态库准备](#6.3 Native 动态库准备)
        • [6.4 构建、签名和安装](#6.4 构建、签名和安装)
    • 四、完整代码对照
      • [4.1 整体架构](#4.1 整体架构)
      • [4.2 文件清单](#4.2 文件清单)
      • [4.3 关键 API 对照](#4.3 关键 API 对照)
      • [4.4 ArkTS 与 Kotlin 的边界](#4.4 ArkTS 与 Kotlin 的边界)
    • 五、关键决策说明
      • [决策 1:把 `ohosArm64` 加入核心构建约定](#决策 1:把 ohosArm64 加入核心构建约定)
      • [决策 2:独立消费者必须通过构建产物消费](#决策 2:独立消费者必须通过构建产物消费)
      • [决策 3:JSON 作为跨语言数据契约](#决策 3:JSON 作为跨语言数据契约)
      • [决策 4:桥接层只开放四个 C ABI 入口](#决策 4:桥接层只开放四个 C ABI 入口)
      • [决策 5:快捷预设和手动解析分开](#决策 5:快捷预设和手动解析分开)
      • [决策 6:把库验证和设备验证分开](#决策 6:把库验证和设备验证分开)
    • 六、测试与验证
      • [6.1 测试环境](#6.1 测试环境)
      • [6.2 静态检查与单元测试](#6.2 静态检查与单元测试)
      • [6.3 原生桥接和 HAP 验证](#6.3 原生桥接和 HAP 验证)
      • [6.4 功能验证用例](#6.4 功能验证用例)
        • [用例 1:默认页面和自检](#用例 1:默认页面和自检)
        • [用例 2:默认参数按钮](#用例 2:默认参数按钮)
        • [用例 3:详细模式按钮](#用例 3:详细模式按钮)
        • [用例 4:自定义参数和解析按钮](#用例 4:自定义参数和解析按钮)
        • [用例 5:无效参数](#用例 5:无效参数)
        • [用例 6:重复解析](#用例 6:重复解析)
      • [6.5 验证结论](#6.5 验证结论)
    • 七、运行效果
      • [7.1 真机截图](#7.1 真机截图)
      • [7.2 命令速查](#7.2 命令速查)
    • 八、遗留问题与改进方向
      • [8.1 踩坑复盘](#8.1 踩坑复盘)
      • [8.2 已知问题](#8.2 已知问题)
      • [8.3 未来优化方向](#8.3 未来优化方向)
    • 九、总结
      • [9.1 核心难点回顾](#9.1 核心难点回顾)
      • [9.2 封装层次](#9.2 封装层次)
      • [9.3 三条经验](#9.3 三条经验)
      • [9.4 适配成果](#9.4 适配成果)
    • [适配过程 FAQ](#适配过程 FAQ)
      • [Q1:DevEco Studio Sync 失败或 ohpm 依赖下载超时,怎么处理?](#Q1:DevEco Studio Sync 失败或 ohpm 依赖下载超时,怎么处理?)
      • [Q2:CMake 报 `Missing .../libxxx.so` 或找不到 Kotlin/Native 动态库?](#Q2:CMake 报 Missing .../libxxx.so 或找不到 Kotlin/Native 动态库?)
      • [Q3:ArkTS 报 `Cannot find module 'libxxx_napi.so'` 或调用返回 `undefined`?](#Q3:ArkTS 报 Cannot find module 'libxxx_napi.so' 或调用返回 undefined?)
      • [Q4:真机安装报 `signature verification failed` 或 `install failed`?](#Q4:真机安装报 signature verification failed 或 install failed?)
      • [Q5:应用启动白屏、闪退或 NAPI 调用崩溃?](#Q5:应用启动白屏、闪退或 NAPI 调用崩溃?)
      • [Q6:Gradle 报 `ohosArm64` 目标不存在或 Kotlin/Native 编译失败?](#Q6:Gradle 报 ohosArm64 目标不存在或 Kotlin/Native 编译失败?)
      • [Q7:HAP 构建报 SDK 版本不匹配或 `compatibleSdkVersion` 错误?](#Q7:HAP 构建报 SDK 版本不匹配或 compatibleSdkVersion 错误?)
      • [Q8:命令行 hvigor 构建失败,但 DevEco 里能构建?](#Q8:命令行 hvigor 构建失败,但 DevEco 里能构建?)
    • 参考文档
  • 欢迎使用Markdown编辑器

欢迎使用Markdown编辑器

你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。

新的改变

我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:

  1. 全新的界面设计 ,将会带来全新的写作体验;
  2. 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
  3. 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
  4. 全新的 KaTeX数学公式 语法;
  5. 增加了支持甘特图的mermaid语法[1](#甘特图的mermaid语法1) 功能;
  6. 增加了 多屏幕编辑 Markdown文章功能;
  7. 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
  8. 增加了 检查列表 功能。

功能快捷键

撤销:Ctrl/Command + Z

重做:Ctrl/Command + Y

加粗:Ctrl/Command + B

斜体:Ctrl/Command + I

标题:Ctrl/Command + Shift + H

无序列表:Ctrl/Command + Shift + U

有序列表:Ctrl/Command + Shift + O

检查列表:Ctrl/Command + Shift + C

插入代码:Ctrl/Command + Shift + K

插入链接:Ctrl/Command + Shift + L

插入图片:Ctrl/Command + Shift + G

查找:Ctrl/Command + F

替换:Ctrl/Command + G

合理的创建标题,有助于目录的生成

直接输入1次#,并按下space后,将生成1级标题。

输入2次#,并按下space后,将生成2级标题。

以此类推,我们支持6级标题。有助于使用TOC语法后生成一个完美的目录。

如何改变文本的样式

强调文本 强调文本

加粗文本 加粗文本

标记文本

删除文本

引用文本

H2O is是液体。

210 运算结果是 1024.

插入链接与图片

链接: link.

图片:

带尺寸的图片:

居中的图片:

居中并且带尺寸的图片:

当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。

如何插入一段漂亮的代码片

去博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片.

javascript 复制代码
// An highlighted block
var foo = 'bar';

生成一个适合你的列表

  • 项目
    • 项目
      • 项目
  1. 项目1
  2. 项目2
  3. 项目3
  • 计划任务
  • 完成任务

创建一个表格

一个简单的表格是这么创建的:

项目 Value
电脑 $1600
手机 $12
导管 $1

设定内容居中、居左、居右

使用:---------:居中

使用:----------居左

使用----------:居右

第一列 第二列 第三列
第一列文本居中 第二列文本居右 第三列文本居左

SmartyPants

SmartyPants 是一个文本转换工具,主要功能是将普通的 ASCII 标点符号自动转换为更美观的印刷体标点符号。例如:

原始符号 转换后 说明
"引号" "引号" 直引号变弯引号
'单引号' '单引号' 直单引号变弯单引号
-- -- 两个连字符变短破折号
--- --- 三个连字符变长破折号
... ... 三个点变省略号

创建一个自定义列表

:
Text-to- conversion tool
: John
: Luke

如何创建一个注脚

一个具有注脚的文本。[2](#2)

注释也是必不可少的

Markdown将文本转换为 。

KaTeX数学公式

您可以使用渲染LaTeX数学表达式 KaTeX:

Gamma公式展示 Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N Γ(n)=(n−1)!∀n∈N 是通过欧拉积分

Γ ( z ) = ∫ 0 ∞ t z − 1 e − t d t   . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. Γ(z)=∫0∞tz−1e−tdt.

你可以找到更多关于的信息 LaTeX 数学表达式here.

新的甘特图功能,丰富你的文章

#mermaid-svg-RzN6beZGUqTf63q2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-RzN6beZGUqTf63q2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-RzN6beZGUqTf63q2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-RzN6beZGUqTf63q2 .error-icon{fill:#552222;}#mermaid-svg-RzN6beZGUqTf63q2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RzN6beZGUqTf63q2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-RzN6beZGUqTf63q2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RzN6beZGUqTf63q2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RzN6beZGUqTf63q2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-RzN6beZGUqTf63q2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RzN6beZGUqTf63q2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RzN6beZGUqTf63q2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RzN6beZGUqTf63q2 .marker.cross{stroke:#333333;}#mermaid-svg-RzN6beZGUqTf63q2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RzN6beZGUqTf63q2 p{margin:0;}#mermaid-svg-RzN6beZGUqTf63q2 .mermaid-main-font{font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-RzN6beZGUqTf63q2 .exclude-range{fill:#eeeeee;}#mermaid-svg-RzN6beZGUqTf63q2 .section{stroke:none;opacity:0.2;}#mermaid-svg-RzN6beZGUqTf63q2 .section0{fill:rgba(102, 102, 255, 0.49);}#mermaid-svg-RzN6beZGUqTf63q2 .section2{fill:#fff400;}#mermaid-svg-RzN6beZGUqTf63q2 .section1,#mermaid-svg-RzN6beZGUqTf63q2 .section3{fill:white;opacity:0.2;}#mermaid-svg-RzN6beZGUqTf63q2 .sectionTitle0{fill:#333;}#mermaid-svg-RzN6beZGUqTf63q2 .sectionTitle1{fill:#333;}#mermaid-svg-RzN6beZGUqTf63q2 .sectionTitle2{fill:#333;}#mermaid-svg-RzN6beZGUqTf63q2 .sectionTitle3{fill:#333;}#mermaid-svg-RzN6beZGUqTf63q2 .sectionTitle{text-anchor:start;font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-RzN6beZGUqTf63q2 .grid .tick{stroke:lightgrey;opacity:0.8;shape-rendering:crispEdges;}#mermaid-svg-RzN6beZGUqTf63q2 .grid .tick text{font-family:"trebuchet ms",verdana,arial,sans-serif;fill:#333;}#mermaid-svg-RzN6beZGUqTf63q2 .grid path{stroke-width:0;}#mermaid-svg-RzN6beZGUqTf63q2 .today{fill:none;stroke:red;stroke-width:2px;}#mermaid-svg-RzN6beZGUqTf63q2 .task{stroke-width:2;}#mermaid-svg-RzN6beZGUqTf63q2 .taskText{text-anchor:middle;font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-RzN6beZGUqTf63q2 .taskTextOutsideRight{fill:black;text-anchor:start;font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-RzN6beZGUqTf63q2 .taskTextOutsideLeft{fill:black;text-anchor:end;}#mermaid-svg-RzN6beZGUqTf63q2 .task.clickable{cursor:pointer;}#mermaid-svg-RzN6beZGUqTf63q2 .taskText.clickable{cursor:pointer;fill:#003163!important;font-weight:bold;}#mermaid-svg-RzN6beZGUqTf63q2 .taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163!important;font-weight:bold;}#mermaid-svg-RzN6beZGUqTf63q2 .taskTextOutsideRight.clickable{cursor:pointer;fill:#003163!important;font-weight:bold;}#mermaid-svg-RzN6beZGUqTf63q2 .taskText0,#mermaid-svg-RzN6beZGUqTf63q2 .taskText1,#mermaid-svg-RzN6beZGUqTf63q2 .taskText2,#mermaid-svg-RzN6beZGUqTf63q2 .taskText3{fill:white;}#mermaid-svg-RzN6beZGUqTf63q2 .task0,#mermaid-svg-RzN6beZGUqTf63q2 .task1,#mermaid-svg-RzN6beZGUqTf63q2 .task2,#mermaid-svg-RzN6beZGUqTf63q2 .task3{fill:#8a90dd;stroke:#534fbc;}#mermaid-svg-RzN6beZGUqTf63q2 .taskTextOutside0,#mermaid-svg-RzN6beZGUqTf63q2 .taskTextOutside2{fill:black;}#mermaid-svg-RzN6beZGUqTf63q2 .taskTextOutside1,#mermaid-svg-RzN6beZGUqTf63q2 .taskTextOutside3{fill:black;}#mermaid-svg-RzN6beZGUqTf63q2 .active0,#mermaid-svg-RzN6beZGUqTf63q2 .active1,#mermaid-svg-RzN6beZGUqTf63q2 .active2,#mermaid-svg-RzN6beZGUqTf63q2 .active3{fill:#bfc7ff;stroke:#534fbc;}#mermaid-svg-RzN6beZGUqTf63q2 .activeText0,#mermaid-svg-RzN6beZGUqTf63q2 .activeText1,#mermaid-svg-RzN6beZGUqTf63q2 .activeText2,#mermaid-svg-RzN6beZGUqTf63q2 .activeText3{fill:black!important;}#mermaid-svg-RzN6beZGUqTf63q2 .done0,#mermaid-svg-RzN6beZGUqTf63q2 .done1,#mermaid-svg-RzN6beZGUqTf63q2 .done2,#mermaid-svg-RzN6beZGUqTf63q2 .done3{stroke:grey;fill:lightgrey;stroke-width:2;}#mermaid-svg-RzN6beZGUqTf63q2 .doneText0,#mermaid-svg-RzN6beZGUqTf63q2 .doneText1,#mermaid-svg-RzN6beZGUqTf63q2 .doneText2,#mermaid-svg-RzN6beZGUqTf63q2 .doneText3{fill:black!important;}#mermaid-svg-RzN6beZGUqTf63q2 .doneText0.taskTextOutsideLeft,#mermaid-svg-RzN6beZGUqTf63q2 .doneText0.taskTextOutsideRight,#mermaid-svg-RzN6beZGUqTf63q2 .doneText1.taskTextOutsideLeft,#mermaid-svg-RzN6beZGUqTf63q2 .doneText1.taskTextOutsideRight,#mermaid-svg-RzN6beZGUqTf63q2 .doneText2.taskTextOutsideLeft,#mermaid-svg-RzN6beZGUqTf63q2 .doneText2.taskTextOutsideRight,#mermaid-svg-RzN6beZGUqTf63q2 .doneText3.taskTextOutsideLeft,#mermaid-svg-RzN6beZGUqTf63q2 .doneText3.taskTextOutsideRight{fill:black!important;}#mermaid-svg-RzN6beZGUqTf63q2 .crit0,#mermaid-svg-RzN6beZGUqTf63q2 .crit1,#mermaid-svg-RzN6beZGUqTf63q2 .crit2,#mermaid-svg-RzN6beZGUqTf63q2 .crit3{stroke:#ff8888;fill:red;stroke-width:2;}#mermaid-svg-RzN6beZGUqTf63q2 .activeCrit0,#mermaid-svg-RzN6beZGUqTf63q2 .activeCrit1,#mermaid-svg-RzN6beZGUqTf63q2 .activeCrit2,#mermaid-svg-RzN6beZGUqTf63q2 .activeCrit3{stroke:#ff8888;fill:#bfc7ff;stroke-width:2;}#mermaid-svg-RzN6beZGUqTf63q2 .doneCrit0,#mermaid-svg-RzN6beZGUqTf63q2 .doneCrit1,#mermaid-svg-RzN6beZGUqTf63q2 .doneCrit2,#mermaid-svg-RzN6beZGUqTf63q2 .doneCrit3{stroke:#ff8888;fill:lightgrey;stroke-width:2;cursor:pointer;shape-rendering:crispEdges;}#mermaid-svg-RzN6beZGUqTf63q2 .milestone{transform:rotate(45deg) scale(0.8,0.8);}#mermaid-svg-RzN6beZGUqTf63q2 .milestoneText{font-style:italic;}#mermaid-svg-RzN6beZGUqTf63q2 .doneCritText0,#mermaid-svg-RzN6beZGUqTf63q2 .doneCritText1,#mermaid-svg-RzN6beZGUqTf63q2 .doneCritText2,#mermaid-svg-RzN6beZGUqTf63q2 .doneCritText3{fill:black!important;}#mermaid-svg-RzN6beZGUqTf63q2 .doneCritText0.taskTextOutsideLeft,#mermaid-svg-RzN6beZGUqTf63q2 .doneCritText0.taskTextOutsideRight,#mermaid-svg-RzN6beZGUqTf63q2 .doneCritText1.taskTextOutsideLeft,#mermaid-svg-RzN6beZGUqTf63q2 .doneCritText1.taskTextOutsideRight,#mermaid-svg-RzN6beZGUqTf63q2 .doneCritText2.taskTextOutsideLeft,#mermaid-svg-RzN6beZGUqTf63q2 .doneCritText2.taskTextOutsideRight,#mermaid-svg-RzN6beZGUqTf63q2 .doneCritText3.taskTextOutsideLeft,#mermaid-svg-RzN6beZGUqTf63q2 .doneCritText3.taskTextOutsideRight{fill:black!important;}#mermaid-svg-RzN6beZGUqTf63q2 .vert{stroke:navy;}#mermaid-svg-RzN6beZGUqTf63q2 .vertText{font-size:15px;text-anchor:middle;fill:navy!important;}#mermaid-svg-RzN6beZGUqTf63q2 .activeCritText0,#mermaid-svg-RzN6beZGUqTf63q2 .activeCritText1,#mermaid-svg-RzN6beZGUqTf63q2 .activeCritText2,#mermaid-svg-RzN6beZGUqTf63q2 .activeCritText3{fill:black!important;}#mermaid-svg-RzN6beZGUqTf63q2 .titleText{text-anchor:middle;font-size:18px;fill:#333;font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-RzN6beZGUqTf63q2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 2014-01-07 2014-01-09 2014-01-11 2014-01-13 2014-01-15 2014-01-17 2014-01-19 2014-01-21 已完成 进行中 计划一 计划二 现有任务 Adding GANTT diagram functionality to mermaid

  • 关于 甘特图 语法,参考 这儿,

UML图表

可以使用UML图表进行渲染,例如下面产生的一个序列图:
王五 李四 张三 王五 李四 张三 #mermaid-svg-XZp5fhCHTapzTeNN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XZp5fhCHTapzTeNN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XZp5fhCHTapzTeNN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XZp5fhCHTapzTeNN .error-icon{fill:#552222;}#mermaid-svg-XZp5fhCHTapzTeNN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XZp5fhCHTapzTeNN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XZp5fhCHTapzTeNN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XZp5fhCHTapzTeNN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XZp5fhCHTapzTeNN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XZp5fhCHTapzTeNN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XZp5fhCHTapzTeNN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XZp5fhCHTapzTeNN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XZp5fhCHTapzTeNN .marker.cross{stroke:#333333;}#mermaid-svg-XZp5fhCHTapzTeNN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XZp5fhCHTapzTeNN p{margin:0;}#mermaid-svg-XZp5fhCHTapzTeNN .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-XZp5fhCHTapzTeNN text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-XZp5fhCHTapzTeNN .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-XZp5fhCHTapzTeNN .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-XZp5fhCHTapzTeNN .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-XZp5fhCHTapzTeNN .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-XZp5fhCHTapzTeNN #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-XZp5fhCHTapzTeNN .sequenceNumber{fill:white;}#mermaid-svg-XZp5fhCHTapzTeNN #sequencenumber{fill:#333;}#mermaid-svg-XZp5fhCHTapzTeNN #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-XZp5fhCHTapzTeNN .messageText{fill:#333;stroke:none;}#mermaid-svg-XZp5fhCHTapzTeNN .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-XZp5fhCHTapzTeNN .labelText,#mermaid-svg-XZp5fhCHTapzTeNN .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-XZp5fhCHTapzTeNN .loopText,#mermaid-svg-XZp5fhCHTapzTeNN .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-XZp5fhCHTapzTeNN .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-XZp5fhCHTapzTeNN .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-XZp5fhCHTapzTeNN .noteText,#mermaid-svg-XZp5fhCHTapzTeNN .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-XZp5fhCHTapzTeNN .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-XZp5fhCHTapzTeNN .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-XZp5fhCHTapzTeNN .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-XZp5fhCHTapzTeNN .actorPopupMenu{position:absolute;}#mermaid-svg-XZp5fhCHTapzTeNN .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-XZp5fhCHTapzTeNN .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-XZp5fhCHTapzTeNN .actor-man circle,#mermaid-svg-XZp5fhCHTapzTeNN line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-XZp5fhCHTapzTeNN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 李四想了很长时间, 文字太长了 不适合放在一行. 你好!李四, 最近怎么样? 你最近怎么样,王五? 我很好,谢谢! 我很好,谢谢! 打量着王五... 很好... 王五, 你怎么样?

  • 关于 UML图表 语法,参考 这儿,

流程图

#mermaid-svg-Zouu2xvAWs8wyeI8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Zouu2xvAWs8wyeI8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Zouu2xvAWs8wyeI8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Zouu2xvAWs8wyeI8 .error-icon{fill:#552222;}#mermaid-svg-Zouu2xvAWs8wyeI8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Zouu2xvAWs8wyeI8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Zouu2xvAWs8wyeI8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Zouu2xvAWs8wyeI8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Zouu2xvAWs8wyeI8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Zouu2xvAWs8wyeI8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Zouu2xvAWs8wyeI8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Zouu2xvAWs8wyeI8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Zouu2xvAWs8wyeI8 .marker.cross{stroke:#333333;}#mermaid-svg-Zouu2xvAWs8wyeI8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Zouu2xvAWs8wyeI8 p{margin:0;}#mermaid-svg-Zouu2xvAWs8wyeI8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Zouu2xvAWs8wyeI8 .cluster-label text{fill:#333;}#mermaid-svg-Zouu2xvAWs8wyeI8 .cluster-label span{color:#333;}#mermaid-svg-Zouu2xvAWs8wyeI8 .cluster-label span p{background-color:transparent;}#mermaid-svg-Zouu2xvAWs8wyeI8 .label text,#mermaid-svg-Zouu2xvAWs8wyeI8 span{fill:#333;color:#333;}#mermaid-svg-Zouu2xvAWs8wyeI8 .node rect,#mermaid-svg-Zouu2xvAWs8wyeI8 .node circle,#mermaid-svg-Zouu2xvAWs8wyeI8 .node ellipse,#mermaid-svg-Zouu2xvAWs8wyeI8 .node polygon,#mermaid-svg-Zouu2xvAWs8wyeI8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Zouu2xvAWs8wyeI8 .rough-node .label text,#mermaid-svg-Zouu2xvAWs8wyeI8 .node .label text,#mermaid-svg-Zouu2xvAWs8wyeI8 .image-shape .label,#mermaid-svg-Zouu2xvAWs8wyeI8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-Zouu2xvAWs8wyeI8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Zouu2xvAWs8wyeI8 .rough-node .label,#mermaid-svg-Zouu2xvAWs8wyeI8 .node .label,#mermaid-svg-Zouu2xvAWs8wyeI8 .image-shape .label,#mermaid-svg-Zouu2xvAWs8wyeI8 .icon-shape .label{text-align:center;}#mermaid-svg-Zouu2xvAWs8wyeI8 .node.clickable{cursor:pointer;}#mermaid-svg-Zouu2xvAWs8wyeI8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Zouu2xvAWs8wyeI8 .arrowheadPath{fill:#333333;}#mermaid-svg-Zouu2xvAWs8wyeI8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Zouu2xvAWs8wyeI8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Zouu2xvAWs8wyeI8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Zouu2xvAWs8wyeI8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Zouu2xvAWs8wyeI8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Zouu2xvAWs8wyeI8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Zouu2xvAWs8wyeI8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Zouu2xvAWs8wyeI8 .cluster text{fill:#333;}#mermaid-svg-Zouu2xvAWs8wyeI8 .cluster span{color:#333;}#mermaid-svg-Zouu2xvAWs8wyeI8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Zouu2xvAWs8wyeI8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Zouu2xvAWs8wyeI8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-Zouu2xvAWs8wyeI8 .icon-shape,#mermaid-svg-Zouu2xvAWs8wyeI8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Zouu2xvAWs8wyeI8 .icon-shape p,#mermaid-svg-Zouu2xvAWs8wyeI8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Zouu2xvAWs8wyeI8 .icon-shape .label rect,#mermaid-svg-Zouu2xvAWs8wyeI8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Zouu2xvAWs8wyeI8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Zouu2xvAWs8wyeI8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Zouu2xvAWs8wyeI8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 链接
长方形
圆
圆角长方形
菱形

  • 关于 Mermaid 语法,参考 这儿,

FLowchart流程图

我们依旧会支持flowchart.js的流程图语法:
Created with Raphaël 2.3.0 开始 我的操作 确认? 结束 yes no

  • 关于 Flowchart流程图 语法,参考 这儿.

导出与导入

导出

如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。

导入

如果你想加载一篇你写过的.md文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,

继续你的创作。


  1. mermaid语法说明 ↩︎

  2. 注脚的解释 ↩︎

*[HTML]: 超文本标记语言

相关推荐
小熊吃保安1 小时前
Windows也能用 Apple Watch 解锁
windows·rust·开源
李&@杰2 小时前
交换机PoE供电不足导致AP反复重启故障处理
网络·华为
特立独行的猫a2 小时前
用仓颉写桌面应用的“薄壳框架”cj-tauri ,以及它的 AI 对话客户端 ai-chat
人工智能·harmonyos·tauri·仓颉·鸿蒙pc·cj-tarui·ai客户端
不识君3 小时前
esl-client 2.0.0 发布:Java 项目接入 FreeSWITCH,从引入到使用
spring boot·后端·开源
Felaim4 小时前
【SLAM】多相机与动态场景 VSLAM 三篇调研:OpenMAVIS、cuVSLAM 与 DROID-W
开源·vslam·动态slam·光束法平差·多相机slam
万物智能信息科技4 小时前
Zigbee透传驱动开发—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】
驱动开发·华为·开源·harmonyos·鸿蒙
牛哥带你学代码4 小时前
鸿蒙远程控制电脑最新体验:华为荣耀鸿蒙用户2026远控软件选择指南
华为·电脑·harmonyos
m4Rk_4 小时前
【论文阅读】Agent记忆机制(111):FEB-30——别再只用问答准确率评估 Agent Memory
论文阅读·人工智能·学习·开源·github
2501_944525764 小时前
开源鸿蒙平台 KMP_CMP 三方库「Multik」适配全流程
华为·开源·harmonyos