复刻 HarmonyKit 工具箱:基于新版 @kit 开发本地工具 App

很多鸿蒙开发者都听过开源项目「HarmonyKit」------纯 ArkTS 原生开发、全本地离线运行、无广告无登录,集成了编解码、格式化、系统工具等几十种实用能力,是很多开发者手机里的常备调试工具。

其实从零实现一款这样的工具 App 门槛并不高。依托鸿蒙 7 的 @kit 套件体系,核心能力全部由系统原生提供,不需要任何第三方依赖,新手也能快速复刻。本文就带你基于全套 @kit 标准 API,从零搭建一个精简版本地工具箱,覆盖 8 个高频实用工具,代码可直接复制运行,全程纯本地、不联网、无需额外敏感权限。

一、项目整体规划与技术选型

1.1 功能清单

我们复刻核心的三类工具,覆盖日常开发调试 90% 的高频需求:

  • 编解码类:Base64 编解码、URL 编解码、MD5 哈希、SHA-256 哈希
  • 转换类:时间戳日期互转、进制转换、JSON 格式化/压缩
  • 系统类:一键复制结果、本地文件导出、沙箱文件管理

1.2 技术栈与选型原则

  • 开发模型:Stage 模型(HarmonyOS NEXT 官方标准)
  • SDK 版本:API 26(鸿蒙 7.0)
  • UI 框架:ArkUI 声明式开发
  • 核心依赖:全部基于官方 @kit 套件,零第三方 npm 包
  • 网络权限:无,所有计算纯本地完成,不上传任何数据

1.3 项目核心优势

  1. 极致隐私:所有运算在本地完成,无网络请求,符合鸿蒙沙箱安全规范
  2. 零权限申请:仅使用应用沙箱与系统剪贴板标准接口,无需用户授权
  3. 原生高性能:加密、文件等能力由系统底层优化,速度远快于纯 JS 第三方库
  4. 模块化易扩展:工具逻辑与 UI 完全分离,新增工具只需加一个方法和对应区块

二、工程基础配置

2.1 新建工程

打开 DevEco Studio 5.0 及以上版本,新建 Stage 模型的 Empty Ability 工程:

  • 语言选择 ArkTS
  • 设备类型勾选 Phone
  • Compile SDK 选择 HarmonyOS NEXT API 26

2.2 编译配置校准

打开工程根目录 build-profile.json5,确认 SDK 版本与运行环境配置正确:

json5 复制代码
{
  "app": {
    "signingConfigs": [],
    "compileSdkVersion": "26.0.0",
    "compatibleSdkVersion": "26.0.0",
    "targets": [
      {
        "name": "default",
        "runtimeOS": "HarmonyOS"
      }
    ]
  }
}

2.3 权限说明

本项目所有能力都在应用沙箱与系统标准接口范围内,无需申请任何敏感权限

  • 剪贴板使用系统标准 API,无需额外授权
  • 文件操作仅在应用私有沙箱内执行,符合鸿蒙隐私安全规范
  • 无网络、无定位、无通讯录读取,纯离线工具

三、核心工具模块封装

我们把所有工具逻辑统一封装到工具类中,与 UI 彻底分离,便于复用与维护。新建 entry/src/main/ets/utils/ToolUtils.ets 文件,以下代码全部放入该文件。

3.1 哈希加密:基于 @kit.SecurityKit

使用系统原生加密框架实现 MD5 与 SHA-256 哈希计算,性能远高于第三方 JS 实现。

typescript 复制代码
import { cryptoFramework } from '@kit.SecurityKit';

/**
 * 哈希加密工具
 */
export class HashUtils {
  /**
   * 计算字符串哈希值
   * @param text 原文
   * @param algorithm 算法:MD5 / SHA256
   */
  static computeHash(text: string, algorithm: 'MD5' | 'SHA256'): string {
    if (!text) return '';
    const mdInstance = cryptoFramework.createMd(algorithm);
    const textBuffer = new TextEncoder().encode(text);
    mdInstance.update(textBuffer.buffer);
    const result = mdInstance.digest();
    return Array.from(new Uint8Array(result))
      .map(byte => byte.toString(16).padStart(2, '0'))
      .join('');
  }

  static md5(text: string): string {
    return this.computeHash(text, 'MD5');
  }

  static sha256(text: string): string {
    return this.computeHash(text, 'SHA256');
  }
}

💡 提示:MD5 仅用于文件校验与普通摘要,不可用于密码存储;高安全场景请使用 SHA-256 加盐。

3.2 编解码工具:标准 API 封装

Base64 与 URL 编解码使用 ES 标准 API,开箱即用,兼容所有鸿蒙版本。

typescript 复制代码
/**
 * 编解码工具
 */
export class EncodeUtils {
  // Base64 编码
  static base64Encode(text: string): string {
    try {
      return btoa(unescape(encodeURIComponent(text)));
    } catch {
      return '编码失败';
    }
  }

  // Base64 解码
  static base64Decode(text: string): string {
    try {
      return decodeURIComponent(escape(atob(text.trim())));
    } catch {
      return '解码失败,请检查输入格式';
    }
  }

  // URL 编码
  static urlEncode(text: string): string {
    return encodeURIComponent(text);
  }

  // URL 解码
  static urlDecode(text: string): string {
    try {
      return decodeURIComponent(text);
    } catch {
      return '解码失败';
    }
  }
}

3.3 转换类工具

包含时间戳互转、进制转换、JSON 格式化三个高频工具。

typescript 复制代码
/**
 * 转换工具
 */
export class ConvertUtils {
  // 时间戳转日期字符串
  static timestampToDate(timestamp: number): string {
    if (!timestamp) return '';
    // 自动识别秒级/毫秒级
    const time = timestamp < 10000000000 ? timestamp * 1000 : timestamp;
    const date = new Date(time);
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, '0');
    const day = String(date.getDate()).padStart(2, '0');
    const hour = String(date.getHours()).padStart(2, '0');
    const minute = String(date.getMinutes()).padStart(2, '0');
    const second = String(date.getSeconds()).padStart(2, '0');
    return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
  }

  // 日期转时间戳(毫秒级)
  static dateToTimestamp(dateStr: string): number {
    if (!dateStr) return 0;
    return new Date(dateStr).getTime();
  }

  // 进制转换:支持 2/8/10/16 进制互转
  static baseConvert(value: string, fromBase: number, toBase: number): string {
    try {
      const decimal = parseInt(value, fromBase);
      if (isNaN(decimal)) return '输入格式错误';
      return decimal.toString(toBase);
    } catch {
      return '转换失败';
    }
  }

  // JSON 格式化
  static jsonFormat(jsonStr: string): string {
    try {
      const obj = JSON.parse(jsonStr);
      return JSON.stringify(obj, null, 2);
    } catch (e) {
      return 'JSON 格式错误,请检查语法';
    }
  }

  // JSON 压缩
  static jsonMinify(jsonStr: string): string {
    try {
      const obj = JSON.parse(jsonStr);
      return JSON.stringify(obj);
    } catch {
      return 'JSON 格式错误,请检查语法';
    }
  }
}

3.4 系统能力:剪贴板与文件操作

剪贴板基于 @kit.BasicServicesKit,文件操作基于 @kit.CoreFileKit,全部为系统原生能力。

typescript 复制代码
import { pasteboard } from '@kit.BasicServicesKit';
import { fs } from '@kit.CoreFileKit';
import { promptAction } from '@kit.ArkUI';

/**
 * 系统工具
 */
export class SystemUtils {
  // 复制文本到剪贴板
  static copyToClipboard(text: string): void {
    if (!text) {
      promptAction.showToast({ message: '无内容可复制' });
      return;
    }
    const data = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, text);
    pasteboard.getSystemPasteboard().setData(data);
    promptAction.showToast({ message: '已复制到剪贴板' });
  }

  // 保存文本到沙箱文件
  static saveToFile(context: Context, fileName: string, content: string): void {
    try {
      const filePath = `${context.filesDir}/${fileName}`;
      fs.writeText(filePath, content);
      promptAction.showToast({ message: `已保存到沙箱:${fileName}` });
    } catch {
      promptAction.showToast({ message: '保存失败' });
    }
  }
}

四、完整主页面实现

我们使用 Tabs 组件分为「编解码」「转换工具」「系统工具」三个标签页,每个工具独立区块,布局清晰,操作直观。

打开 entry/src/main/ets/pages/Index.ets,替换为以下完整代码,复制即可直接运行:

typescript 复制代码
import { HashUtils, EncodeUtils, ConvertUtils, SystemUtils } from '../utils/ToolUtils';
import { promptAction } from '@kit.ArkUI';

@Entry
@Component
struct ToolBoxPage {
  @State currentTab: number = 0;

  // 编解码模块状态
  @State encodeInput: string = '';
  @State encodeResult: string = '';

  // 转换模块状态
  @State convertInput: string = '';
  @State convertResult: string = '';

  private tabTitles: string[] = ['编解码', '转换工具', '系统工具'];

  build() {
    Column() {
      // 顶部标题
      Text('开发者工具箱')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .width('90%')
        .margin({ top: 20, bottom: 16 });

      // Tab 标签栏
      Tabs({ barPosition: BarPosition.Start }) {
        // 第一页:编解码工具
        TabContent() {
          this.buildEncodeSection();
        }
        .tabBar(this.tabTitles[0])

        // 第二页:转换工具
        TabContent() {
          this.buildConvertSection();
        }
        .tabBar(this.tabTitles[1])

        // 第三页:系统工具
        TabContent() {
          this.buildSystemSection();
        }
        .tabBar(this.tabTitles[2])
      }
      .barMode(BarMode.Fixed)
      .barHeight(48)
      .layoutWeight(1)
      .onChange((index) => {
        this.currentTab = index;
      });
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F8F9FA');
  }

  // ========== 编解码工具区块 ==========
  @Builder
  buildEncodeSection() {
    Scroll() {
      Column({ space: 16 }) {
        this.buildToolCard('Base64 编解码', this.encodeInput, (val) => this.encodeInput = val, [
          { text: '编码', color: '#0A59F7', action: () => this.encodeResult = EncodeUtils.base64Encode(this.encodeInput) },
          { text: '解码', color: '#00B578', action: () => this.encodeResult = EncodeUtils.base64Decode(this.encodeInput) }
        ], this.encodeResult);

        this.buildToolCard('URL 编解码', this.encodeInput, (val) => this.encodeInput = val, [
          { text: '编码', color: '#0A59F7', action: () => this.encodeResult = EncodeUtils.urlEncode(this.encodeInput) },
          { text: '解码', color: '#00B578', action: () => this.encodeResult = EncodeUtils.urlDecode(this.encodeInput) }
        ], this.encodeResult);

        this.buildToolCard('MD5 哈希', this.encodeInput, (val) => this.encodeInput = val, [
          { text: '计算', color: '#9B59B6', action: () => this.encodeResult = HashUtils.md5(this.encodeInput) }
        ], this.encodeResult);

        this.buildToolCard('SHA-256 哈希', this.encodeInput, (val) => this.encodeInput = val, [
          { text: '计算', color: '#E67E22', action: () => this.encodeResult = HashUtils.sha256(this.encodeInput) }
        ], this.encodeResult);
      }
      .width('90%')
      .margin({ top: 16, bottom: 20 });
    }
    .width('100%');
  }

  // ========== 转换工具区块 ==========
  @Builder
  buildConvertSection() {
    Scroll() {
      Column({ space: 16 }) {
        this.buildToolCard('时间戳转日期', this.convertInput, (val) => this.convertInput = val, [
          { text: '转换', color: '#0A59F7', action: () => {
            const ts = parseInt(this.convertInput);
            this.convertResult = isNaN(ts) ? '请输入数字时间戳' : ConvertUtils.timestampToDate(ts);
          }}
        ], this.convertResult);

        this.buildToolCard('日期转时间戳', this.convertInput, (val) => this.convertInput = val, [
          { text: '转换', color: '#00B578', action: () => {
            const result = ConvertUtils.dateToTimestamp(this.convertInput);
            this.convertResult = result ? String(result) : '请输入正确日期格式';
          }}
        ], this.convertResult);

        this.buildToolCard('JSON 格式化', this.convertInput, (val) => this.convertInput = val, [
          { text: '格式化', color: '#0A59F7', action: () => this.convertResult = ConvertUtils.jsonFormat(this.convertInput) },
          { text: '压缩', color: '#E67E22', action: () => this.convertResult = ConvertUtils.jsonMinify(this.convertInput) }
        ], this.convertResult);

        this.buildToolCard('十进制转十六进制', this.convertInput, (val) => this.convertInput = val, [
          { text: '转换', color: '#9B59B6', action: () => {
            this.convertResult = ConvertUtils.baseConvert(this.convertInput, 10, 16);
          }}
        ], this.convertResult);
      }
      .width('90%')
      .margin({ top: 16, bottom: 20 });
    }
    .width('100%');
  }

  // ========== 系统工具区块 ==========
  @Builder
  buildSystemSection() {
    Column({ space: 20 }) {
      // 剪贴板演示
      this.buildToolCard('剪贴板测试', '输入内容会复制到剪贴板', (val) => {}, [
        { text: '复制结果', color: '#0A59F7', action: () => SystemUtils.copyToClipboard(this.encodeResult || this.convertResult) }
      ], '点击按钮可复制上方工具的计算结果');

      // 文件保存演示
      this.buildToolCard('保存到本地文件', '输入要保存的内容', (val) => this.convertInput = val, [
        { text: '保存到沙箱', color: '#00B578', action: () => {
          SystemUtils.saveToFile(getContext(this), 'tool_result.txt', this.convertInput);
        }}
      ], '文件会保存到应用私有沙箱目录');

      Blank();
      Text('💡 所有工具均为本地计算,无网络请求')
        .fontSize(12)
        .fontColor('#AAAAAA')
        .margin({ bottom: 20 });
    }
    .width('90%')
    .margin({ top: 16 });
  }

  // ========== 通用工具卡片组件 ==========
  @Builder
  buildToolCard(
    title: string,
    inputValue: string,
    onInputChange: (val: string) => void,
    buttons: Array<{ text: string, color: string, action: () => void }>,
    result: string
  ) {
    Column({ space: 12 }) {
      Text(title)
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .width('100%');

      TextArea({ text: inputValue, placeholder: '请输入内容' })
        .width('100%')
        .height(80)
        .borderRadius(8)
        .padding(10)
        .backgroundColor('#F5F5F5')
        .fontSize(14)
        .onChange(onInputChange);

      // 操作按钮组
      Flex({ wrap: FlexWrap.Wrap, gap: 10 }) {
        ForEach(buttons, (btn: { text: string, color: string, action: () => void }) => {
          Button(btn.text)
            .height(36)
            .padding({ left: 16, right: 16 })
            .borderRadius(18)
            .backgroundColor(btn.color)
            .fontSize(14)
            .onClick(btn.action);
        });
      }
      .width('100%');

      // 结果展示
      if (result) {
        Column({ space: 8 }) {
          Text('结果')
            .fontSize(14)
            .fontColor('#666666')
            .width('100%');

          Text(result)
            .width('100%')
            .padding(10)
            .backgroundColor('#F0F7FF')
            .borderRadius(8)
            .fontSize(14)
            .fontFamily('monospace')
            .wordBreak(WordBreak.BreakAll)
            .maxLines(6)
            .textOverflow({ overflow: TextOverflow.Ellipsis });

          Text('复制结果')
            .fontSize(13)
            .fontColor('#0A59F7')
            .width('100%')
            .textAlign(TextAlign.End)
            .onClick(() => SystemUtils.copyToClipboard(result));
        }
        .width('100%');
      }
    }
    .width('100%')
    .padding(16)
    .backgroundColor('#FFFFFF')
    .borderRadius(12)
    .shadow({ radius: 4, color: '#1A000000' });
  }
}

代码核心设计亮点

  1. 组件复用 :抽取通用 buildToolCard 卡片组件,新增工具只需传参数,不用重复写布局
  2. 状态统一管理:同类型工具复用输入输出状态,减少冗余变量
  3. 异常兜底:所有转换、编解码操作都加了 try-catch 异常捕获,非法输入不会导致应用崩溃
  4. 一键复制:所有结果都自带复制按钮,操作效率更高

五、运行验证与扩展指南

5.1 运行验证步骤

  1. 将工具类和页面代码复制到对应文件
  2. 同步工程,编译运行到模拟器或真机
  3. 切换三个 Tab,测试各个工具的计算效果
  4. 点击「复制结果」验证剪贴板能力,点击「保存到沙箱」验证文件写入

5.2 可扩展功能方向

原版 HarmonyKit 有几十种工具,都可以基于这套架构快速扩展:

  • 编解码类:Unicode 转码、摩斯密码、二维码生成
  • 工具类:正则表达式测试、颜色拾取、单位换算、随机数生成
  • 文件类:文件哈希校验、文本批量替换、沙箱文件管理器
  • 系统类:设备信息查看、应用详情导出、权限检测

5.3 开发最佳实践

  • 逻辑 UI 分离:工具方法统一放到 utils 目录,便于单元测试与跨页面复用
  • 优先原生 API:加密、文件、剪贴板等能力优先用 @kit 原生实现,性能与稳定性更优
  • 输入异常兜底:所有用户输入都做异常捕获,避免非法格式导致应用崩溃
  • 结果一键复制:工具类产品默认提供复制能力,大幅提升使用效率

总结

这款精简版工具箱,核心业务代码不到 500 行,全部基于鸿蒙官方 @kit 套件实现,没有任何第三方依赖。对于新手来说,通过这个项目可以一次性吃透 SecurityKit、CoreFileKit、BasicServicesKit 等多个核心 Kit 的用法,同时完成一个可日常使用的实战项目。

原版 HarmonyKit 的强大之处,本质上就是对系统原生能力的封装与整合------把零散的系统 API 包装成一个个开箱即用的工具。掌握了这套模块化封装思路,你也可以根据自己的需求,扩展出专属的开发者工具箱。

相关推荐
风华圆舞2 小时前
鸿蒙上跑通第一个 AI Agent:5 分钟接入智谱/DeepSeek
人工智能·华为·harmonyos·arkts·arkui·toolregistry
xd1855785553 小时前
电影匹配引擎-基于鸿蒙的心情电影推荐应用开发实践
人工智能·安全·华为·harmonyos·鸿蒙
千逐684 小时前
HarmonyOS 实战 | @State 不神秘——变量一变界面就跟着变
华为·harmonyos·鸿蒙
不才难以繁此生4 小时前
NavPathStack 返回错页怎么排查:中式美食详情页从搜索、收藏和推荐进入怎么回到原处
harmonyos·arkts·arkui·中式美食·navpathstack
xd1855785557 小时前
梦境解析师-基于鸿蒙的梦境心理分析应用开发实践
人工智能·华为·harmonyos·鸿蒙
千逐687 小时前
鸿蒙实战:一多自适应与原子化布局 —— 一次开发多端部署的工程化落地
华为·harmonyos·鸿蒙
解局易否结局8 小时前
鸿蒙原生开发实战:Native 并发与高性能计算
华为·harmonyos
xo198820119 小时前
鸿蒙 AI 自动化开发:让 AI 学会自己编译、自己调试
人工智能·自动化·harmonyos
HarmonyOS_SDK9 小时前
差异化推送消息能力,助力支付宝腕上消息业务闭环
harmonyos