HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

经验分享 鸿蒙里的权限设置,如何获取、查询权限

鸿蒙next 鸿蒙征文

鸿蒙里的权限

鸿蒙的权限可以分成三类:

开放权限:system_grant, 比如 INTERNET网络权限、VIBRATE 手机震动权限等。无需用户同意。具体可见 开放权限(系统授权)
用户授权:user_grant,弹窗询问用户是否允许位置定位、发送通知等。具体可见 开放权限(用户授权)
敏感权限:需要在华为后台单独填写表格申请获得,比如修改用户公共目录文件、API 读取剪切板等。具体可见 受限开放权限
还有一些针对特定企业管理的权限,场景比较特殊,这里不做进一步描述。

细节可以看文档 《鸿蒙权限配置指南

如何定义权限

举例定位中用到的模糊定位、精准定位。需要参考文档,在 requestPermissions

如何查询权限是否授权?

const auth = () => {  
    const res = uni.getAppAuthorizeSetting()  
    console.log(res)  
  }

如何主动申请用户授权特定的权限?

先见 uts-api 鸿蒙插件,填写下面代码, uni_modules/harmony-harmony/utssdk/app-harmony/index.uts

import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit';  

export const requestSystemPermission = () => {  

  const permissionList : Array<Permissions> = ['ohos.permission.APPROXIMATELY_LOCATION']  
  UTSHarmony.requestSystemPermission(permissionList, (allRight : boolean, grantedList : Array<string>) => {  
    console.log('res', allRight, grantedList);  
  }, (doNotAskAgain : boolean, grantedList : Array<string>) => {  
    console.log('fail', doNotAskAgain, grantedList);  
  })  
}

在 vue 代码中这样使用

<script setup lang="uts">  
  import { requestSystemPermission } from '@/uni_modules/harmony-harmony'  

  const permisson = () => {  
    requestSystemPermission()  
  }  
</script>

如何打开系统设置?

可引导用户打开设置重新授权。

uni.openAppAuthorizeSetting()  

https://uniapp.dcloud.net.cn/api/system/openappauthorizesetting.html

继续阅读 »

鸿蒙里的权限

鸿蒙的权限可以分成三类:

开放权限:system_grant, 比如 INTERNET网络权限、VIBRATE 手机震动权限等。无需用户同意。具体可见 开放权限(系统授权)
用户授权:user_grant,弹窗询问用户是否允许位置定位、发送通知等。具体可见 开放权限(用户授权)
敏感权限:需要在华为后台单独填写表格申请获得,比如修改用户公共目录文件、API 读取剪切板等。具体可见 受限开放权限
还有一些针对特定企业管理的权限,场景比较特殊,这里不做进一步描述。

细节可以看文档 《鸿蒙权限配置指南

如何定义权限

举例定位中用到的模糊定位、精准定位。需要参考文档,在 requestPermissions

如何查询权限是否授权?

const auth = () => {  
    const res = uni.getAppAuthorizeSetting()  
    console.log(res)  
  }

如何主动申请用户授权特定的权限?

先见 uts-api 鸿蒙插件,填写下面代码, uni_modules/harmony-harmony/utssdk/app-harmony/index.uts

import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit';  

export const requestSystemPermission = () => {  

  const permissionList : Array<Permissions> = ['ohos.permission.APPROXIMATELY_LOCATION']  
  UTSHarmony.requestSystemPermission(permissionList, (allRight : boolean, grantedList : Array<string>) => {  
    console.log('res', allRight, grantedList);  
  }, (doNotAskAgain : boolean, grantedList : Array<string>) => {  
    console.log('fail', doNotAskAgain, grantedList);  
  })  
}

在 vue 代码中这样使用

<script setup lang="uts">  
  import { requestSystemPermission } from '@/uni_modules/harmony-harmony'  

  const permisson = () => {  
    requestSystemPermission()  
  }  
</script>

如何打开系统设置?

可引导用户打开设置重新授权。

uni.openAppAuthorizeSetting()  

https://uniapp.dcloud.net.cn/api/system/openappauthorizesetting.html

收起阅读 »

经验分享 鸿蒙中如何隐藏底部触控小白条?

鸿蒙next 鸿蒙征文

在鸿蒙底部有触控小白条,用来响应系统级用户手势。在应用开发时候,有些业务场景需要隐藏底部触控小白条,鸿蒙提供了响应 API,代码比较简单,使用 UTS 几行代码轻松切换展示。

在 HBuilderX 中新建 uni_modules 文件夹,在 uni_modules 文件夹右键选择创建 UTS-API 插件,创建并编辑 app-harmony/index.uts 文件夹,如果没有就新建该文件。

在文件中填写下面代码:


/**  
 * 展示底部小白条  
 */  
export const showNavigationIndicator = () => {  
  const window = UTSHarmony.getCurrentWindow()  
  window.setSpecificSystemBarEnabled('navigationIndicator', true)  
}  

/**  
 * 隐藏底部小白条  
 */  
export const hideNavigationIndicator = () => {  
  const window = UTSHarmony.getCurrentWindow()  
  window.setSpecificSystemBarEnabled('navigationIndicator', false)  
}  

在 Vue 页面中导入并使用即可。

<template>  
  <view>  
    <button @click="showNavigationIndicator">showNavigationIndicator</button>  
    <button @click="hideNavigationIndicator">hideNavigationIndicator</button>  
  </view>  
</template>  
<script setup lang="uts">  
  import {  
    showNavigationIndicator,  
    hideNavigationIndicator,  
  } from '@/uni_modules/harmony-toggle-navigation-indicator'  
</script>  

当用户点击 hideNavigationIndicator 按钮之后,系统大概一秒后会隐藏小白条。点击 showNavigationIndicator 系统会展示小白条。

继续阅读 »

在鸿蒙底部有触控小白条,用来响应系统级用户手势。在应用开发时候,有些业务场景需要隐藏底部触控小白条,鸿蒙提供了响应 API,代码比较简单,使用 UTS 几行代码轻松切换展示。

在 HBuilderX 中新建 uni_modules 文件夹,在 uni_modules 文件夹右键选择创建 UTS-API 插件,创建并编辑 app-harmony/index.uts 文件夹,如果没有就新建该文件。

在文件中填写下面代码:


/**  
 * 展示底部小白条  
 */  
export const showNavigationIndicator = () => {  
  const window = UTSHarmony.getCurrentWindow()  
  window.setSpecificSystemBarEnabled('navigationIndicator', true)  
}  

/**  
 * 隐藏底部小白条  
 */  
export const hideNavigationIndicator = () => {  
  const window = UTSHarmony.getCurrentWindow()  
  window.setSpecificSystemBarEnabled('navigationIndicator', false)  
}  

在 Vue 页面中导入并使用即可。

<template>  
  <view>  
    <button @click="showNavigationIndicator">showNavigationIndicator</button>  
    <button @click="hideNavigationIndicator">hideNavigationIndicator</button>  
  </view>  
</template>  
<script setup lang="uts">  
  import {  
    showNavigationIndicator,  
    hideNavigationIndicator,  
  } from '@/uni_modules/harmony-toggle-navigation-indicator'  
</script>  

当用户点击 hideNavigationIndicator 按钮之后,系统大概一秒后会隐藏小白条。点击 showNavigationIndicator 系统会展示小白条。

收起阅读 »

关于如何查看apk 的 so 是否支持16 KB 内存页面大小

命令行执行

sh /Users/xxx/Desktop/check_elf_alignment.sh /Users/xxx/Desktop/2.apk 

-e /var/folders/ww/xcthf5jn7gq5_p_v4hkmrm580000gn/T/5_out_XXXXX.xK6hDLJKG2/lib/arm64-v8a/libweexjst.so: \e[32mALIGNED\e[0m (214)
-e /var/folders/ww/xcthf5jn7gq5_p_v4hkmrm580000gn/T/5_out_XXXXX.xK6hDLJKG2/lib/armeabi-v7a/libweexjsb.so: \e[31mUNALIGNED\e[0m (2
12)

UNALIGNED - 支持

ALIGNED - 不支持

继续阅读 »

命令行执行

sh /Users/xxx/Desktop/check_elf_alignment.sh /Users/xxx/Desktop/2.apk 

-e /var/folders/ww/xcthf5jn7gq5_p_v4hkmrm580000gn/T/5_out_XXXXX.xK6hDLJKG2/lib/arm64-v8a/libweexjst.so: \e[32mALIGNED\e[0m (214)
-e /var/folders/ww/xcthf5jn7gq5_p_v4hkmrm580000gn/T/5_out_XXXXX.xK6hDLJKG2/lib/armeabi-v7a/libweexjsb.so: \e[31mUNALIGNED\e[0m (2
12)

UNALIGNED - 支持

ALIGNED - 不支持

收起阅读 »

打开apkApp应用灰屏

bug都是自己写出来的,仔细检查下代码吧

bug都是自己写出来的,仔细检查下代码吧

app端uniapp分片上传文件

思路如下

1.获取大文件的临时目录tempFilePath

  1. tempFilePath转应用的安全路径safePath
  2. 根据大文件的size和每片大小进行分片,得到每个分片的base64
    4.直接把每片的base64上传到后端,让后端处理
    5.删除安全路径的文件

具体方法如下
2.tempFilePath转应用的安全路径safePath

let rootDir = await this.getDbFolder();  

      let safePath = rootDir.fullPath + filename;  
      let existFlag = await this.checkFileExists(filename);  
      if (!existFlag){  
        //复制到安全路径下  
        safePath = await this.copyToSafePath(tempFilePath, rootDir,filename);  
      }  

    async getDbFolder() { // 返回doc的应用私有目录对象,用于文件copy接口使用  
      return new Promise((resolve, reject) => {  
        plus.io.requestFileSystem(plus.io.PRIVATE_DOC, function(fs) {  
          resolve(fs.root)  
        });  
      });  
    },  

    async checkFileExists(fileName) {  
      return new Promise((resolve) => {  
        plus.io.requestFileSystem(plus.io.PRIVATE_DOC, (fs) => {  
          fs.root.getFile(fileName, { create: false },  
              () => resolve(true),  // 文件存在  
              () => resolve(false)  // 文件不存在  
          );  
        });  
      });  
    },  

async copyToSafePath(tempFilePath, rootDir,filename) {  
      return new Promise((resolve, reject) => {  
        // 使用plus.io读取文件  
        plus.io.resolveLocalFileSystemURL(tempFilePath, (entry) => {  
          entry.copyTo(rootDir,filename,function (res){  
            console.log("视频复制成功:" + res.fullPath);  
            resolve(res.fullPath);  
          },function (e){  
            console.log("视频复制成功:" ,e);  
            reject(e);  
          })  
        });  
      });  
    },  
  1. 根据大文件的size和每片大小进行分片,得到每个分片的base64
for (let i = 0; i < totalChunks; i++) {  
          const start = i * chunkSize;  
          const length = Math.min(size - start, chunkSize); // 实际读取长度  

          // 读取分片base64  
          const chunkBase64 = await this.readFileChunk(safePath, start, length);  
....  

/**  
     * 获取分片的base64  
     */  
     readFileChunk(safePath, start, length) {  
      return new Promise((resolve, reject) => {  
        // 使用plus.io读取文件  
        plus.io.resolveLocalFileSystemURL(safePath, (entry) => {  
          entry.file((file) => {  
            const reader = new plus.io.FileReader();  
            // 设置读取成功回调  
            reader.onloadend  = (e)=> {  
              let base64 = e.target.result;  
              resolve(base64);  
            };  

            // 设置读取错误回调  
            reader.onerror = function(error) {  
              console.error('读取文件分片失败:', error);  
              reject(error);  
            };  

            // 截取文件片段并读取  slice是左右都会取到的,所以要减1  
            const fileSlice = file.slice(start, start + length - 1);  
            reader.readAsDataURL(fileSlice);  
          }, (error) => {  
            console.error('获取文件对象失败:', error);  
            reject(error);  
          });  
        }, (error) => {  
          console.error('解析文件路径失败:', error);  
          reject(error);  
        });  
      });  
    },

5.删除安全路径的文件

removeSafePath(safePath) {  
      uni.removeSavedFile({  
        filePath: safePath,  
        success: (res) => {  
          console.log('文件删除成功', safePath);  
        },  
        fail: (err) => {  
          console.error('文件删除失败', err);  
        }  
      });  
    },  
继续阅读 »

思路如下

1.获取大文件的临时目录tempFilePath

  1. tempFilePath转应用的安全路径safePath
  2. 根据大文件的size和每片大小进行分片,得到每个分片的base64
    4.直接把每片的base64上传到后端,让后端处理
    5.删除安全路径的文件

具体方法如下
2.tempFilePath转应用的安全路径safePath

let rootDir = await this.getDbFolder();  

      let safePath = rootDir.fullPath + filename;  
      let existFlag = await this.checkFileExists(filename);  
      if (!existFlag){  
        //复制到安全路径下  
        safePath = await this.copyToSafePath(tempFilePath, rootDir,filename);  
      }  

    async getDbFolder() { // 返回doc的应用私有目录对象,用于文件copy接口使用  
      return new Promise((resolve, reject) => {  
        plus.io.requestFileSystem(plus.io.PRIVATE_DOC, function(fs) {  
          resolve(fs.root)  
        });  
      });  
    },  

    async checkFileExists(fileName) {  
      return new Promise((resolve) => {  
        plus.io.requestFileSystem(plus.io.PRIVATE_DOC, (fs) => {  
          fs.root.getFile(fileName, { create: false },  
              () => resolve(true),  // 文件存在  
              () => resolve(false)  // 文件不存在  
          );  
        });  
      });  
    },  

async copyToSafePath(tempFilePath, rootDir,filename) {  
      return new Promise((resolve, reject) => {  
        // 使用plus.io读取文件  
        plus.io.resolveLocalFileSystemURL(tempFilePath, (entry) => {  
          entry.copyTo(rootDir,filename,function (res){  
            console.log("视频复制成功:" + res.fullPath);  
            resolve(res.fullPath);  
          },function (e){  
            console.log("视频复制成功:" ,e);  
            reject(e);  
          })  
        });  
      });  
    },  
  1. 根据大文件的size和每片大小进行分片,得到每个分片的base64
for (let i = 0; i < totalChunks; i++) {  
          const start = i * chunkSize;  
          const length = Math.min(size - start, chunkSize); // 实际读取长度  

          // 读取分片base64  
          const chunkBase64 = await this.readFileChunk(safePath, start, length);  
....  

/**  
     * 获取分片的base64  
     */  
     readFileChunk(safePath, start, length) {  
      return new Promise((resolve, reject) => {  
        // 使用plus.io读取文件  
        plus.io.resolveLocalFileSystemURL(safePath, (entry) => {  
          entry.file((file) => {  
            const reader = new plus.io.FileReader();  
            // 设置读取成功回调  
            reader.onloadend  = (e)=> {  
              let base64 = e.target.result;  
              resolve(base64);  
            };  

            // 设置读取错误回调  
            reader.onerror = function(error) {  
              console.error('读取文件分片失败:', error);  
              reject(error);  
            };  

            // 截取文件片段并读取  slice是左右都会取到的,所以要减1  
            const fileSlice = file.slice(start, start + length - 1);  
            reader.readAsDataURL(fileSlice);  
          }, (error) => {  
            console.error('获取文件对象失败:', error);  
            reject(error);  
          });  
        }, (error) => {  
          console.error('解析文件路径失败:', error);  
          reject(error);  
        });  
      });  
    },

5.删除安全路径的文件

removeSafePath(safePath) {  
      uni.removeSavedFile({  
        filePath: safePath,  
        success: (res) => {  
          console.log('文件删除成功', safePath);  
        },  
        fail: (err) => {  
          console.error('文件删除失败', err);  
        }  
      });  
    },  
收起阅读 »

摩尔斯电码转换器

鸿蒙征文

摩尔斯电码转换器 📡

一个基于 uni-app 开发的摩尔斯电码编解码应用,支持文本与摩尔斯电码的双向转换。

License
UniApp

✨ 功能特性

image-20251022144208411

核心功能

  • 🔤 文本转摩尔斯电码:将英文文本编码为摩尔斯电码
  • 🔡 摩尔斯电码转文本:将摩尔斯电码解码为可读文本
  • 🔄 双向转换:一键切换编码/解码模式
  • 📋 对照表:内置完整的摩尔斯电码对照表,可随时查阅

支持字符

  • ✅ 26个英文字母(A-Z)
  • ✅ 10个数字(0-9)
  • ✅ 常用标点符号:. , ? ' ! / ( ) & : ; = + - _ " $ @

界面特色

  • 🎨 现代化渐变设计
  • 📱 响应式布局,适配多种屏幕尺寸
  • 💫 流畅的动画过渡效果
  • 🌈 直观的视觉反馈
  • 📝 可选中复制输出结果

📸 预览

编码模式

将文本转换为摩尔斯电码:

输入:HELLO WORLD  
输出:.... . .-.. .-.. --- / .-- --- .-. .-.. -..

解码模式

将摩尔斯电码转换为文本:

输入:.... . .-.. .-.. --- / .-- --- .-. .-.. -..  
输出:HELLO WORLD

🚀 快速开始

环境要求

  • HBuilderX 3.0+(运行到 HarmonyOS 需要 5.0+ 版本)
  • uni-app 框架
  • 支持 uni-app 的运行环境:
    • 微信小程序:微信开发者工具
    • H5:现代浏览器(Chrome、Firefox、Safari 等)
    • Android/iOS:Android Studio / Xcode
    • HarmonyOS:HarmonyOS 5.0+ 设备或模拟器

安装步骤

  1. 克隆项目
git clone [your-repository-url]  
cd Morse
  1. 使用 HBuilderX 打开项目

    • 启动 HBuilderX
    • 文件 → 打开目录 → 选择项目文件夹
  2. 运行项目

    • 运行 → 运行到浏览器 → Chrome(H5)
    • 或运行到微信开发者工具(小程序)
    • 或运行到手机模拟器(App)
    • 或运行到 HarmonyOS(鸿蒙应用)

运行到 HarmonyOS

前置要求

  • 安装 HBuilderX 4.0+ 版本
  • 配置 HarmonyOS 开发环境
  • 安装 DevEco Studio(可选,用于更高级的调试)

运行步骤

  1. 在 HBuilderX 中打开项目
  2. 点击菜单栏:运行 → 运行到手机或模拟器 → 运行到 HarmonyOS
  3. 选择设备
    • 连接 HarmonyOS 真机(需开启开发者模式和 USB 调试)
    • 或使用 HarmonyOS 模拟器
  4. 等待编译:首次运行会自动下载依赖并编译
  5. 查看效果:应用会自动安装并启动到设备上

注意事项

  • 确保设备系统版本为 HarmonyOS5.0 或更高版本
  • 真机调试需要在设置中开启"开发者选项"和"USB调试"
  • 如遇到编译问题,请检查 HBuilderX 的 HarmonyOS 插件是否已安装

📖 使用说明

编码(文本 → 摩尔斯电码)

  1. 点击顶部的「文本 → 摩尔斯」按钮切换到编码模式
  2. 在输入框中输入要编码的文本(支持英文字母、数字和常用符号)
  3. 点击「编码」按钮
  4. 编码结果将显示在输出区域

注意事项

  • 字母之间用空格分隔
  • 单词之间用 / 分隔
  • 不支持的字符会被自动忽略

解码(摩尔斯电码 → 文本)

  1. 点击顶部的「摩尔斯 → 文本」按钮切换到解码模式
  2. 在输入框中输入摩尔斯电码
    • 使用空格分隔不同的字母
    • 使用 / 分隔不同的单词
  3. 点击「解码」按钮
  4. 解码结果将显示在输出区域

示例输入

.... . .-.. .-.. --- / .-- --- .-. .-.. -..

查看对照表

点击底部的「▶ 摩尔斯电码对照表」可展开完整的字符对照表,方便学习和参考。

🛠️ 技术栈

  • 框架:uni-app
  • 语言:TypeScript/UTS
  • UI:uni-app 组件库
  • 样式:CSS3(渐变、阴影、动画)

📂 项目结构

Morse/  
├── pages/  
│   └── index/  
│       └── index.uvue          # 主页面(摩尔斯转换器)  
├── static/  
│   └── logo.png                # 应用图标  
├── App.uvue                    # 应用配置  
├── main.uts                    # 入口文件  
├── manifest.json               # 应用配置清单  
├── pages.json                  # 页面路由配置  
├── uni.scss                    # 全局样式变量  
├── LICENSE                     # MIT 许可证  
└── README.md                   # 项目说明文档

🎯 核心代码说明

摩尔斯电码映射表

项目内置完整的摩尔斯电码映射表,包含:

  • 26个字母
  • 10个数字
  • 24个常用符号
morseCode: {  
    'A': '.-',    'B': '-...',  'C': '-.-.',  // ...  
    '0': '-----', '1': '.----', // ...  
    '.': '.-.-.-', ',': '--..--', // ...  
}

编码算法

将文本转换为摩尔斯电码的核心逻辑:

  1. 将输入文本转为大写
  2. 按空格分割成单词
  3. 遍历每个单词的字符,查找对应的摩尔斯电码
  4. 字母间用空格连接,单词间用 / 连接

解码算法

将摩尔斯电码转换为文本的核心逻辑:

  1. 创建反向映射表(摩尔斯 → 字符)
  2. / 分割成单词
  3. 每个单词按空格分割成字母
  4. 查找每个摩尔斯码对应的字符
  5. 无法识别的码用 ? 表示

🌟 特色亮点

  1. 智能容错:解码时遇到无法识别的码会用 ? 标记,不会中断整个转换过程
  2. 实时反馈:输入为空时会友好提示用户
  3. 一键清空:快速清除输入和输出内容
  4. 学习工具:内置对照表,既是工具也是学习资源
  5. 视觉设计:渐变背景、卡片阴影、动画效果,提供优秀的视觉体验

📱 平台支持

  • ✅ HarmonyOS App(鸿蒙原生应用)
    • 支持 HarmonyOS 5.0+
    • 完整的原生性能体验
    • 适配鸿蒙设计规范
  • ✅ H5(网页版)
  • ✅ 微信小程序
  • ✅ Android App
  • ✅ iOS App
  • ✅ 快应用
  • ✅ 其他 uni-app 支持的平台

🤝 贡献指南

欢迎提交 Issue 和 Pull Request!

  1. Fork 本项目
  2. 创建特性分支 (git checkout -b feature/AmazingFeature)
  3. 提交更改 (git commit -m 'Add some AmazingFeature')
  4. 推送到分支 (git push origin feature/AmazingFeature)
  5. 开启 Pull Request

📝 更新日志

v1.0.0 (2025-10-22)

  • ✨ 初始版本发布
  • 🎉 实现文本转摩尔斯电码功能
  • 🎉 实现摩尔斯电码转文本功能
  • 🎨 设计现代化 UI 界面
  • 📋 添加摩尔斯电码对照表
  • 🚀 支持 HarmonyOS 平台(鸿蒙原生应用)
  • 📱 多平台适配(H5、小程序、App 等)

🔮 未来计划

  • [ ] 添加音频播放功能(播放摩尔斯电码声音)
  • [ ] 支持闪光灯模式(用手机闪光灯展示摩尔斯电码)
  • [ ] 添加振动反馈(鸿蒙设备支持)
  • [ ] 添加历史记录功能
  • [ ] 支持更多语言(中文电码等)
  • [ ] 添加学习模式(摩尔斯电码训练)
  • [ ] 支持语音输入
  • [ ] 鸿蒙卡片服务(快速转换)
  • [ ] 适配鸿蒙折叠屏设备

❓ 常见问题

Q: 为什么有些字符无法转换?

A: 目前只支持英文字母、数字和常用标点符号。中文字符需要另外的电码系统(如中文电码)。

Q: 解码时出现问号是什么意思?

A: 表示该摩尔斯电码无法识别,可能是输入格式错误或不在支持的字符范围内。

Q: 如何正确输入摩尔斯电码?

A: 使用点 . 和横 - 组成字符,字符间用空格分隔,单词间用 / 分隔。

Q: 如何在 HarmonyOS 设备上安装?

A: 使用 HBuilderX 连接 HarmonyOS 设备,选择"运行到 HarmonyOS"即可自动编译并安装。确保设备已开启开发者模式和 USB 调试。

Q: 支持哪些 HarmonyOS 版本?

A: 支持 HarmonyOS 5.0 及以上版本,建议使用 HarmonyOS 5.0+ 以获得最佳体验。

📄 许可证

本项目采用 MIT 许可证 - 查看 LICENSE 文件了解详情

👨‍💻 作者

坚果

🙏 致谢

感谢 uni-app 团队提供的优秀跨平台框架!


⭐ 如果这个项目对你有帮助,请给它一个星标!

继续阅读 »

摩尔斯电码转换器 📡

一个基于 uni-app 开发的摩尔斯电码编解码应用,支持文本与摩尔斯电码的双向转换。

License
UniApp

✨ 功能特性

image-20251022144208411

核心功能

  • 🔤 文本转摩尔斯电码:将英文文本编码为摩尔斯电码
  • 🔡 摩尔斯电码转文本:将摩尔斯电码解码为可读文本
  • 🔄 双向转换:一键切换编码/解码模式
  • 📋 对照表:内置完整的摩尔斯电码对照表,可随时查阅

支持字符

  • ✅ 26个英文字母(A-Z)
  • ✅ 10个数字(0-9)
  • ✅ 常用标点符号:. , ? ' ! / ( ) & : ; = + - _ " $ @

界面特色

  • 🎨 现代化渐变设计
  • 📱 响应式布局,适配多种屏幕尺寸
  • 💫 流畅的动画过渡效果
  • 🌈 直观的视觉反馈
  • 📝 可选中复制输出结果

📸 预览

编码模式

将文本转换为摩尔斯电码:

输入:HELLO WORLD  
输出:.... . .-.. .-.. --- / .-- --- .-. .-.. -..

解码模式

将摩尔斯电码转换为文本:

输入:.... . .-.. .-.. --- / .-- --- .-. .-.. -..  
输出:HELLO WORLD

🚀 快速开始

环境要求

  • HBuilderX 3.0+(运行到 HarmonyOS 需要 5.0+ 版本)
  • uni-app 框架
  • 支持 uni-app 的运行环境:
    • 微信小程序:微信开发者工具
    • H5:现代浏览器(Chrome、Firefox、Safari 等)
    • Android/iOS:Android Studio / Xcode
    • HarmonyOS:HarmonyOS 5.0+ 设备或模拟器

安装步骤

  1. 克隆项目
git clone [your-repository-url]  
cd Morse
  1. 使用 HBuilderX 打开项目

    • 启动 HBuilderX
    • 文件 → 打开目录 → 选择项目文件夹
  2. 运行项目

    • 运行 → 运行到浏览器 → Chrome(H5)
    • 或运行到微信开发者工具(小程序)
    • 或运行到手机模拟器(App)
    • 或运行到 HarmonyOS(鸿蒙应用)

运行到 HarmonyOS

前置要求

  • 安装 HBuilderX 4.0+ 版本
  • 配置 HarmonyOS 开发环境
  • 安装 DevEco Studio(可选,用于更高级的调试)

运行步骤

  1. 在 HBuilderX 中打开项目
  2. 点击菜单栏:运行 → 运行到手机或模拟器 → 运行到 HarmonyOS
  3. 选择设备
    • 连接 HarmonyOS 真机(需开启开发者模式和 USB 调试)
    • 或使用 HarmonyOS 模拟器
  4. 等待编译:首次运行会自动下载依赖并编译
  5. 查看效果:应用会自动安装并启动到设备上

注意事项

  • 确保设备系统版本为 HarmonyOS5.0 或更高版本
  • 真机调试需要在设置中开启"开发者选项"和"USB调试"
  • 如遇到编译问题,请检查 HBuilderX 的 HarmonyOS 插件是否已安装

📖 使用说明

编码(文本 → 摩尔斯电码)

  1. 点击顶部的「文本 → 摩尔斯」按钮切换到编码模式
  2. 在输入框中输入要编码的文本(支持英文字母、数字和常用符号)
  3. 点击「编码」按钮
  4. 编码结果将显示在输出区域

注意事项

  • 字母之间用空格分隔
  • 单词之间用 / 分隔
  • 不支持的字符会被自动忽略

解码(摩尔斯电码 → 文本)

  1. 点击顶部的「摩尔斯 → 文本」按钮切换到解码模式
  2. 在输入框中输入摩尔斯电码
    • 使用空格分隔不同的字母
    • 使用 / 分隔不同的单词
  3. 点击「解码」按钮
  4. 解码结果将显示在输出区域

示例输入

.... . .-.. .-.. --- / .-- --- .-. .-.. -..

查看对照表

点击底部的「▶ 摩尔斯电码对照表」可展开完整的字符对照表,方便学习和参考。

🛠️ 技术栈

  • 框架:uni-app
  • 语言:TypeScript/UTS
  • UI:uni-app 组件库
  • 样式:CSS3(渐变、阴影、动画)

📂 项目结构

Morse/  
├── pages/  
│   └── index/  
│       └── index.uvue          # 主页面(摩尔斯转换器)  
├── static/  
│   └── logo.png                # 应用图标  
├── App.uvue                    # 应用配置  
├── main.uts                    # 入口文件  
├── manifest.json               # 应用配置清单  
├── pages.json                  # 页面路由配置  
├── uni.scss                    # 全局样式变量  
├── LICENSE                     # MIT 许可证  
└── README.md                   # 项目说明文档

🎯 核心代码说明

摩尔斯电码映射表

项目内置完整的摩尔斯电码映射表,包含:

  • 26个字母
  • 10个数字
  • 24个常用符号
morseCode: {  
    'A': '.-',    'B': '-...',  'C': '-.-.',  // ...  
    '0': '-----', '1': '.----', // ...  
    '.': '.-.-.-', ',': '--..--', // ...  
}

编码算法

将文本转换为摩尔斯电码的核心逻辑:

  1. 将输入文本转为大写
  2. 按空格分割成单词
  3. 遍历每个单词的字符,查找对应的摩尔斯电码
  4. 字母间用空格连接,单词间用 / 连接

解码算法

将摩尔斯电码转换为文本的核心逻辑:

  1. 创建反向映射表(摩尔斯 → 字符)
  2. / 分割成单词
  3. 每个单词按空格分割成字母
  4. 查找每个摩尔斯码对应的字符
  5. 无法识别的码用 ? 表示

🌟 特色亮点

  1. 智能容错:解码时遇到无法识别的码会用 ? 标记,不会中断整个转换过程
  2. 实时反馈:输入为空时会友好提示用户
  3. 一键清空:快速清除输入和输出内容
  4. 学习工具:内置对照表,既是工具也是学习资源
  5. 视觉设计:渐变背景、卡片阴影、动画效果,提供优秀的视觉体验

📱 平台支持

  • ✅ HarmonyOS App(鸿蒙原生应用)
    • 支持 HarmonyOS 5.0+
    • 完整的原生性能体验
    • 适配鸿蒙设计规范
  • ✅ H5(网页版)
  • ✅ 微信小程序
  • ✅ Android App
  • ✅ iOS App
  • ✅ 快应用
  • ✅ 其他 uni-app 支持的平台

🤝 贡献指南

欢迎提交 Issue 和 Pull Request!

  1. Fork 本项目
  2. 创建特性分支 (git checkout -b feature/AmazingFeature)
  3. 提交更改 (git commit -m 'Add some AmazingFeature')
  4. 推送到分支 (git push origin feature/AmazingFeature)
  5. 开启 Pull Request

📝 更新日志

v1.0.0 (2025-10-22)

  • ✨ 初始版本发布
  • 🎉 实现文本转摩尔斯电码功能
  • 🎉 实现摩尔斯电码转文本功能
  • 🎨 设计现代化 UI 界面
  • 📋 添加摩尔斯电码对照表
  • 🚀 支持 HarmonyOS 平台(鸿蒙原生应用)
  • 📱 多平台适配(H5、小程序、App 等)

🔮 未来计划

  • [ ] 添加音频播放功能(播放摩尔斯电码声音)
  • [ ] 支持闪光灯模式(用手机闪光灯展示摩尔斯电码)
  • [ ] 添加振动反馈(鸿蒙设备支持)
  • [ ] 添加历史记录功能
  • [ ] 支持更多语言(中文电码等)
  • [ ] 添加学习模式(摩尔斯电码训练)
  • [ ] 支持语音输入
  • [ ] 鸿蒙卡片服务(快速转换)
  • [ ] 适配鸿蒙折叠屏设备

❓ 常见问题

Q: 为什么有些字符无法转换?

A: 目前只支持英文字母、数字和常用标点符号。中文字符需要另外的电码系统(如中文电码)。

Q: 解码时出现问号是什么意思?

A: 表示该摩尔斯电码无法识别,可能是输入格式错误或不在支持的字符范围内。

Q: 如何正确输入摩尔斯电码?

A: 使用点 . 和横 - 组成字符,字符间用空格分隔,单词间用 / 分隔。

Q: 如何在 HarmonyOS 设备上安装?

A: 使用 HBuilderX 连接 HarmonyOS 设备,选择"运行到 HarmonyOS"即可自动编译并安装。确保设备已开启开发者模式和 USB 调试。

Q: 支持哪些 HarmonyOS 版本?

A: 支持 HarmonyOS 5.0 及以上版本,建议使用 HarmonyOS 5.0+ 以获得最佳体验。

📄 许可证

本项目采用 MIT 许可证 - 查看 LICENSE 文件了解详情

👨‍💻 作者

坚果

🙏 致谢

感谢 uni-app 团队提供的优秀跨平台框架!


⭐ 如果这个项目对你有帮助,请给它一个星标!

收起阅读 »

经验分享 鸿蒙通过 WebView 打开页面渲染成桌面 pc 模式怎么办?

鸿蒙next 鸿蒙征文

鸿蒙开发时候可使用 WebView 组件加载网页,展示网页内容并通信。

历史改动

在 HBuilderX 4.81 之后, uniapp 使用 WebView 展示在线网页时候,会默认添加 metaViwe=true,读取并启用 meta viewport 字段。

userAgent 适配

还有一部分网页是读取的 useragent 属性,通过特征判断再渲染展示网页,有的 isMobile 的判断里缺少鸿蒙的判断,只判断了 iphone/ipad/android 等字段,没有判断 OpenHarmony AkWeb 字段,如果是这种响应式展示移动端的方案,一方面可以更新 isMobile 的判断,添加对 harmony 的解析。另一方面可以在 HBuilderX 的 mainfest.json 中主动设置 UserAgent 来规避这个问题。

按照下面操作步骤:
打开 mianfest.json 切换到源码模式,找到 app-harmony 字段,追加下面字段

{"useragent":{"value":"Android","concatenate" : true}}

这下面在系统默认的 UserAgent 之后追加 Android 字段,通过这种方式主动适配网页。

uniapp 默认的 userAgent

Mozilla/5.0 (Phone; OpenHarmony 5.1)  
AppleWebKit/537.36 (KHTML, like Gecko)  
Chrome/114.0.0.0 Safari/537.36 ArkWeb/5.1.0.211  
Mobile uni-app
继续阅读 »

鸿蒙开发时候可使用 WebView 组件加载网页,展示网页内容并通信。

历史改动

在 HBuilderX 4.81 之后, uniapp 使用 WebView 展示在线网页时候,会默认添加 metaViwe=true,读取并启用 meta viewport 字段。

userAgent 适配

还有一部分网页是读取的 useragent 属性,通过特征判断再渲染展示网页,有的 isMobile 的判断里缺少鸿蒙的判断,只判断了 iphone/ipad/android 等字段,没有判断 OpenHarmony AkWeb 字段,如果是这种响应式展示移动端的方案,一方面可以更新 isMobile 的判断,添加对 harmony 的解析。另一方面可以在 HBuilderX 的 mainfest.json 中主动设置 UserAgent 来规避这个问题。

按照下面操作步骤:
打开 mianfest.json 切换到源码模式,找到 app-harmony 字段,追加下面字段

{"useragent":{"value":"Android","concatenate" : true}}

这下面在系统默认的 UserAgent 之后追加 Android 字段,通过这种方式主动适配网页。

uniapp 默认的 userAgent

Mozilla/5.0 (Phone; OpenHarmony 5.1)  
AppleWebKit/537.36 (KHTML, like Gecko)  
Chrome/114.0.0.0 Safari/537.36 ArkWeb/5.1.0.211  
Mobile uni-app
收起阅读 »

uniappx插件nutpi-idcard 开发与使用指南(适配鸿蒙)

鸿蒙征文

uniappx插件nutpi-idcard 开发与使用指南(适配鸿蒙)

前言

nutpi-idcard 是一个基于 UTS (uni-app TypeScript Syntax) 开发的 uni-app 插件适配鸿蒙,主要用于解析身份证号码,提取其中的关键信息,如地区、出生日期、性别等。本插件支持中国居民身份证、港澳台居民居住证以及外国人永久居留身份证。

本文将详细介绍 nutpi-idcard 插件的开发过程和使用方法,希望能为其他开发者提供一些参考。

插件功能

  • 身份证号码解析:能够从身份证号码中提取省市区(或国家/地区)、出生日期、性别等信息。
  • 支持多种证件类型
    • 中国居民身份证
    • 港澳台居民居住证
    • 外国人永久居留身份证
  • 纯 UTS 实现:确保了插件在 uni-app x 及其他支持 UTS 的环境中的兼容性和性能。
  • 跨平台支持:理论上支持所有 uni-app 支持的平台,特别是针对 App (Android, iOS, HarmonyOS) 进行了适配。

开发过程

1. 项目初始化与环境搭建

插件的开发基于 HBuilderX,利用其对 uni-app 和 UTS 的良好支持。

  • 创建 uni-app 项目:首先,创建一个标准的 uni-app 项目(如果还没有的话)。
  • 创建 uni_module:在项目根目录下创建 uni_modules 文件夹(如果不存在),然后在其中创建 nutpi-idcard 文件夹作为插件的根目录。
  • 配置文件 package.json:在 nutpi-idcard 目录下创建 package.json 文件,用于定义插件的基本信息、依赖、平台支持等。关键配置项包括:
    • id: 插件的唯一标识。
    • displayName: 插件在 HBuilderX 中显示的名称。
    • version: 插件版本号。
    • description: 插件描述。
    • author: 作者信息-坚果派。
    • contact: 联系方式。
    • repository: 代码仓库地址。
    • engines: HBuilderX 版本要求。
    • dcloudext: DCloud 扩展配置,如插件类型 (uts)、销售信息等。
    • uni_modules: uni-app 模块配置,如依赖、加密、平台支持等。

2. 核心逻辑实现 (utssdk)

插件的核心代码位于 utssdk 目录下,针对不同平台可以有不同的实现,但本项目中主要关注通用的 UTS 实现,特别是针对 HarmonyOS 的适配。

  • 目录结构

    nutpi-idcard/  
    ├── utssdk/  
    │   ├── app-harmony/         # HarmonyOS 平台特定代码  
    │   │   ├── index.uts        # HarmonyOS 入口及核心逻辑  
    │   │   ├── interfaces.uts   # TypeScript 接口定义  
    │   │   └── module/  
    │   │       └── data/        # 数据文件 (行政区划、国家代码)  
    │   │           ├── china.uts  
    │   │           └── international.uts  
    │   ├── app-android/       # Android 平台 (如果需要特定实现)  
    │   ├── app-ios/           # iOS 平台 (如果需要特定实现)  
    │   ├── index.uts          # 插件主入口 (通常导出各平台实现)  
    │   └── interfaces.uts     # 通用接口定义  
    ├── package.json  
    ├── readme.md  
    └── changelog.md  
  • 数据准备 (module/data/)

    • china.uts: 存储中国行政区划代码与名称的映射。
    • international.uts: 存储 ISO 3166-1 国家代码与名称的映射。
  • 接口定义 (interfaces.uts)
    定义了身份证解析结果的数据结构 IDResult

    export interface IDResult {  
      type?: string;       // 证件类型  
      sign?: string;       // 签发机关或地区  
      country?: string;    // 国家或地区  
      birthday?: string;   // 出生日期 (YYYY-MM-DD)  
      sex?: string;        // 性别 ('男' 或 '女')  
      isValid?: boolean;   // 校验结果 (当前版本简单返回 true)  
    }  
  • 核心解析逻辑 (app-harmony/index.uts)
    这是插件的核心,包含了主要的解析函数。

    • parseID(id: string): IDResult: 公开的 API 函数,根据身份证号码的格式(通过正则表达式判断)调用相应的内部解析函数。
    • parserChina(id: string): IDResult: 解析中国居民身份证和港澳台居民居住证。
    • 通过身份证号码的前6位确定省市区。
    • 通过第7到14位确定出生日期。
    • 通过第17位(顺序码的最后一位)确定性别。
    • parserInternational(id: string): IDResult: 解析外国人永久居留身份证。
    • 通过第1到3位(国家或地区代码)和 international.uts 数据确定国家。
    • 通过第7到14位确定出生日期。
    • 通过第17位确定性别。
    • isIdCardValidInternal(id: string): boolean: 身份证号码有效性校验函数。目前简单返回 true,未来可以根据国家标准实现更复杂的校验逻辑(如校验码计算)。
    // idcard/uni_modules/nutpi-idcard/utssdk/app-harmony/index.uts  
    import { chinaData as _china } from './module/data/china.uts';  
    import { internationalData as _international } from './module/data/international.uts';  
    import type { IDResult } from './interfaces.uts';  
    
    function parserInternational(id: string): IDResult { /* ... */ }  
    function parserChina(id: string): IDResult { /* ... */ }  
    function isIdCardValidInternal(id: string): boolean { /* ... */ }  
    
    export function parseID(id: string): IDResult {  
      if(id.match(/^9\d{16}[0-9xX]$/)){ // 外国人永久居留身份证特征 (假设以9开头)  
          return parserInternational(id);  
      }else if(id.match(/^\d{17}[0-9xX]$/)){ // 中国居民身份证特征  
          return parserChina(id);  
      }else{  
          return { type: '未知类型' };  
      }  
    }  

3. 插件入口 (index.uts)

nutpi-idcard 根目录下的 index.uts 文件通常作为插件的统一入口,它会根据当前运行平台导出相应平台的 parseID 函数。

// idcard/uni_modules/nutpi-idcard/index.uts  
// #ifdef APP-HARMONY  
export * from './utssdk/app-harmony/index.uts';  
// #endif  

// #ifdef APP-PLUS || APP-VUE  
// 假设 Android 和 iOS 使用相同的 UTS 逻辑,或者有单独的 app-android/index.uts 和 app-ios/index.uts  
// 如果 utssdk/index.uts 包含了 Android 和 iOS 的通用逻辑,可以这样导出:  
// export * from './utssdk/index.uts';   
// 或者分别导出  
// #ifdef APP-ANDROID  
// export * from './utssdk/app-android/index.uts';  
// #endif  
// #ifdef APP-IOS  
// export * from './utssdk/app-ios/index.uts';  
// #endif  
// #endif  

// 默认导出 (如果需要在非特定App平台使用,或者作为H5等平台的兜底)  
// export * from './utssdk/index.uts'; // 假设 utssdk/index.uts 包含通用或web实现

注意:上述 index.uts 的条件编译部分需要根据实际支持的平台和代码组织来编写。如果主要目标是 HarmonyOS,则 APP-HARMONY 部分是关键。

4. 文档编写

  • readme.md: 提供插件的详细说明,包括功能特性、安装方法、API 文档、使用示例、作者信息等。
  • changelog.md: 记录插件的版本更新历史和主要变更。

5. 测试与调试

  • 在 HBuilderX 中创建测试页面,引入插件并调用 parseID 函数,传入不同的身份证号码进行测试。
  • 关注控制台输出,确保解析结果的准确性。
  • 针对不同平台(特别是 HarmonyOS)进行真机或模拟器测试。

遇到的问题与解决

  • UTS 模块导入路径:UTS 中模块导入路径需要精确。最初可能因为 method.utsindex.uts 的拆分导致函数重复声明或找不到定义的问题。通过将 method.uts 的内容合并到 index.uts 中解决了此问题。
  • Git 推送标签失败:在版本发布时,如果本地没有对应的 Git 标签,git push origin <tagname> 会失败。通过先执行 git tag <tagname> 创建本地标签,然后再推送解决。
  • 函数未定义错误:在页面中调用插件函数时,如果导入路径不正确或插件未正确导出函数,会导致 xxx is not defined 错误。仔细检查插件的 index.uts 导出逻辑和页面中的导入路径,确保一致。

如何使用 nutpi-idcard 插件

  1. 安装插件

    • 从 DCloud 插件市场安装。插件地址:https://ext.dcloud.net.cn/plugin?id=23728
    • 或者,如果手动引入,将 nutpi-idcard 整个文件夹复制到你的 uni-app 项目的 uni_modules 目录下。
  2. 引入插件:在需要使用的页面或组件的 <script setup lang="uts"><script lang="uts"> 中引入插件。

    // 示例:在页面的 <script setup lang="uts"> 中  
    import { parseID } from '@/uni_modules/nutpi-idcard'; // HBuilderX 会自动处理路径映射  
    // 如果在 uni-app x 项目的 .uvue 文件中,路径可能需要更明确,或者依赖 HBuilderX 的智能提示  
  3. 调用解析函数:使用 parseID 函数解析身份证号码。

    const idNumber = '110101199003070978'; // 替换为实际的身份证号码  
    const idInfo = parseID(idNumber);  
    
    if (idInfo) {  
       console.log('证件类型:', idInfo.type);  
       console.log('签发地/国家:', idInfo.sign ?? idInfo.country);  
       console.log('出生日期:', idInfo.birthday);  
       console.log('性别:', idInfo.sex);  
       console.log('是否有效:', idInfo.isValid);  
    }  

API 参考

parseID(id: string): IDResult

解析身份证号码并返回包含详细信息的对象。

  • 参数

    • id: string - 需要解析的身份证号码(18位中国居民身份证,或外国人永久居留身份证等)。
  • 返回值IDResult 对象,其结构如下:

    interface IDResult {  
      type?: string;       // 证件类型 (例如:'居民身份证', '外国人永久居留身份证', '港澳台居民居住证', '未知类型')  
      sign?: string;       // 签发机关或地区信息 (例如:'北京市市辖区', '北京市朝阳区')  
      country?: string;    // 国家或地区 (例如:'中国', '无国籍' 或其他国家名称,主要用于外国人身份证)  
      birthday?: string;   // 出生日期,格式为 'YYYY-MM-DD'  
      sex?: string;        // 性别 ('男' 或 '女')  
      isValid?: boolean;   // 身份证号码是否有效 (当前版本简单返回true,待实现详细校验逻辑)  
    }  

未来展望

  • 完善校验逻辑:实现更严格的身份证号码校验,包括校验码的计算与验证。
  • 更广泛的证件类型支持:考虑支持更多国家或地区的身份证件类型。
  • 性能优化:对数据查找和字符串处理进行优化,提高解析效率。
  • 更详细的错误提示:当输入格式错误或无法解析时,提供更具体的错误信息。
  • 单元测试:为插件编写完善的单元测试,确保代码质量和稳定性。

作者与联系方式

希望这个插件能对您有所帮助!如果您有任何问题或建议,欢迎联系。

相关链接

继续阅读 »

uniappx插件nutpi-idcard 开发与使用指南(适配鸿蒙)

前言

nutpi-idcard 是一个基于 UTS (uni-app TypeScript Syntax) 开发的 uni-app 插件适配鸿蒙,主要用于解析身份证号码,提取其中的关键信息,如地区、出生日期、性别等。本插件支持中国居民身份证、港澳台居民居住证以及外国人永久居留身份证。

本文将详细介绍 nutpi-idcard 插件的开发过程和使用方法,希望能为其他开发者提供一些参考。

插件功能

  • 身份证号码解析:能够从身份证号码中提取省市区(或国家/地区)、出生日期、性别等信息。
  • 支持多种证件类型
    • 中国居民身份证
    • 港澳台居民居住证
    • 外国人永久居留身份证
  • 纯 UTS 实现:确保了插件在 uni-app x 及其他支持 UTS 的环境中的兼容性和性能。
  • 跨平台支持:理论上支持所有 uni-app 支持的平台,特别是针对 App (Android, iOS, HarmonyOS) 进行了适配。

开发过程

1. 项目初始化与环境搭建

插件的开发基于 HBuilderX,利用其对 uni-app 和 UTS 的良好支持。

  • 创建 uni-app 项目:首先,创建一个标准的 uni-app 项目(如果还没有的话)。
  • 创建 uni_module:在项目根目录下创建 uni_modules 文件夹(如果不存在),然后在其中创建 nutpi-idcard 文件夹作为插件的根目录。
  • 配置文件 package.json:在 nutpi-idcard 目录下创建 package.json 文件,用于定义插件的基本信息、依赖、平台支持等。关键配置项包括:
    • id: 插件的唯一标识。
    • displayName: 插件在 HBuilderX 中显示的名称。
    • version: 插件版本号。
    • description: 插件描述。
    • author: 作者信息-坚果派。
    • contact: 联系方式。
    • repository: 代码仓库地址。
    • engines: HBuilderX 版本要求。
    • dcloudext: DCloud 扩展配置,如插件类型 (uts)、销售信息等。
    • uni_modules: uni-app 模块配置,如依赖、加密、平台支持等。

2. 核心逻辑实现 (utssdk)

插件的核心代码位于 utssdk 目录下,针对不同平台可以有不同的实现,但本项目中主要关注通用的 UTS 实现,特别是针对 HarmonyOS 的适配。

  • 目录结构

    nutpi-idcard/  
    ├── utssdk/  
    │   ├── app-harmony/         # HarmonyOS 平台特定代码  
    │   │   ├── index.uts        # HarmonyOS 入口及核心逻辑  
    │   │   ├── interfaces.uts   # TypeScript 接口定义  
    │   │   └── module/  
    │   │       └── data/        # 数据文件 (行政区划、国家代码)  
    │   │           ├── china.uts  
    │   │           └── international.uts  
    │   ├── app-android/       # Android 平台 (如果需要特定实现)  
    │   ├── app-ios/           # iOS 平台 (如果需要特定实现)  
    │   ├── index.uts          # 插件主入口 (通常导出各平台实现)  
    │   └── interfaces.uts     # 通用接口定义  
    ├── package.json  
    ├── readme.md  
    └── changelog.md  
  • 数据准备 (module/data/)

    • china.uts: 存储中国行政区划代码与名称的映射。
    • international.uts: 存储 ISO 3166-1 国家代码与名称的映射。
  • 接口定义 (interfaces.uts)
    定义了身份证解析结果的数据结构 IDResult

    export interface IDResult {  
      type?: string;       // 证件类型  
      sign?: string;       // 签发机关或地区  
      country?: string;    // 国家或地区  
      birthday?: string;   // 出生日期 (YYYY-MM-DD)  
      sex?: string;        // 性别 ('男' 或 '女')  
      isValid?: boolean;   // 校验结果 (当前版本简单返回 true)  
    }  
  • 核心解析逻辑 (app-harmony/index.uts)
    这是插件的核心,包含了主要的解析函数。

    • parseID(id: string): IDResult: 公开的 API 函数,根据身份证号码的格式(通过正则表达式判断)调用相应的内部解析函数。
    • parserChina(id: string): IDResult: 解析中国居民身份证和港澳台居民居住证。
    • 通过身份证号码的前6位确定省市区。
    • 通过第7到14位确定出生日期。
    • 通过第17位(顺序码的最后一位)确定性别。
    • parserInternational(id: string): IDResult: 解析外国人永久居留身份证。
    • 通过第1到3位(国家或地区代码)和 international.uts 数据确定国家。
    • 通过第7到14位确定出生日期。
    • 通过第17位确定性别。
    • isIdCardValidInternal(id: string): boolean: 身份证号码有效性校验函数。目前简单返回 true,未来可以根据国家标准实现更复杂的校验逻辑(如校验码计算)。
    // idcard/uni_modules/nutpi-idcard/utssdk/app-harmony/index.uts  
    import { chinaData as _china } from './module/data/china.uts';  
    import { internationalData as _international } from './module/data/international.uts';  
    import type { IDResult } from './interfaces.uts';  
    
    function parserInternational(id: string): IDResult { /* ... */ }  
    function parserChina(id: string): IDResult { /* ... */ }  
    function isIdCardValidInternal(id: string): boolean { /* ... */ }  
    
    export function parseID(id: string): IDResult {  
      if(id.match(/^9\d{16}[0-9xX]$/)){ // 外国人永久居留身份证特征 (假设以9开头)  
          return parserInternational(id);  
      }else if(id.match(/^\d{17}[0-9xX]$/)){ // 中国居民身份证特征  
          return parserChina(id);  
      }else{  
          return { type: '未知类型' };  
      }  
    }  

3. 插件入口 (index.uts)

nutpi-idcard 根目录下的 index.uts 文件通常作为插件的统一入口,它会根据当前运行平台导出相应平台的 parseID 函数。

// idcard/uni_modules/nutpi-idcard/index.uts  
// #ifdef APP-HARMONY  
export * from './utssdk/app-harmony/index.uts';  
// #endif  

// #ifdef APP-PLUS || APP-VUE  
// 假设 Android 和 iOS 使用相同的 UTS 逻辑,或者有单独的 app-android/index.uts 和 app-ios/index.uts  
// 如果 utssdk/index.uts 包含了 Android 和 iOS 的通用逻辑,可以这样导出:  
// export * from './utssdk/index.uts';   
// 或者分别导出  
// #ifdef APP-ANDROID  
// export * from './utssdk/app-android/index.uts';  
// #endif  
// #ifdef APP-IOS  
// export * from './utssdk/app-ios/index.uts';  
// #endif  
// #endif  

// 默认导出 (如果需要在非特定App平台使用,或者作为H5等平台的兜底)  
// export * from './utssdk/index.uts'; // 假设 utssdk/index.uts 包含通用或web实现

注意:上述 index.uts 的条件编译部分需要根据实际支持的平台和代码组织来编写。如果主要目标是 HarmonyOS,则 APP-HARMONY 部分是关键。

4. 文档编写

  • readme.md: 提供插件的详细说明,包括功能特性、安装方法、API 文档、使用示例、作者信息等。
  • changelog.md: 记录插件的版本更新历史和主要变更。

5. 测试与调试

  • 在 HBuilderX 中创建测试页面,引入插件并调用 parseID 函数,传入不同的身份证号码进行测试。
  • 关注控制台输出,确保解析结果的准确性。
  • 针对不同平台(特别是 HarmonyOS)进行真机或模拟器测试。

遇到的问题与解决

  • UTS 模块导入路径:UTS 中模块导入路径需要精确。最初可能因为 method.utsindex.uts 的拆分导致函数重复声明或找不到定义的问题。通过将 method.uts 的内容合并到 index.uts 中解决了此问题。
  • Git 推送标签失败:在版本发布时,如果本地没有对应的 Git 标签,git push origin <tagname> 会失败。通过先执行 git tag <tagname> 创建本地标签,然后再推送解决。
  • 函数未定义错误:在页面中调用插件函数时,如果导入路径不正确或插件未正确导出函数,会导致 xxx is not defined 错误。仔细检查插件的 index.uts 导出逻辑和页面中的导入路径,确保一致。

如何使用 nutpi-idcard 插件

  1. 安装插件

    • 从 DCloud 插件市场安装。插件地址:https://ext.dcloud.net.cn/plugin?id=23728
    • 或者,如果手动引入,将 nutpi-idcard 整个文件夹复制到你的 uni-app 项目的 uni_modules 目录下。
  2. 引入插件:在需要使用的页面或组件的 <script setup lang="uts"><script lang="uts"> 中引入插件。

    // 示例:在页面的 <script setup lang="uts"> 中  
    import { parseID } from '@/uni_modules/nutpi-idcard'; // HBuilderX 会自动处理路径映射  
    // 如果在 uni-app x 项目的 .uvue 文件中,路径可能需要更明确,或者依赖 HBuilderX 的智能提示  
  3. 调用解析函数:使用 parseID 函数解析身份证号码。

    const idNumber = '110101199003070978'; // 替换为实际的身份证号码  
    const idInfo = parseID(idNumber);  
    
    if (idInfo) {  
       console.log('证件类型:', idInfo.type);  
       console.log('签发地/国家:', idInfo.sign ?? idInfo.country);  
       console.log('出生日期:', idInfo.birthday);  
       console.log('性别:', idInfo.sex);  
       console.log('是否有效:', idInfo.isValid);  
    }  

API 参考

parseID(id: string): IDResult

解析身份证号码并返回包含详细信息的对象。

  • 参数

    • id: string - 需要解析的身份证号码(18位中国居民身份证,或外国人永久居留身份证等)。
  • 返回值IDResult 对象,其结构如下:

    interface IDResult {  
      type?: string;       // 证件类型 (例如:'居民身份证', '外国人永久居留身份证', '港澳台居民居住证', '未知类型')  
      sign?: string;       // 签发机关或地区信息 (例如:'北京市市辖区', '北京市朝阳区')  
      country?: string;    // 国家或地区 (例如:'中国', '无国籍' 或其他国家名称,主要用于外国人身份证)  
      birthday?: string;   // 出生日期,格式为 'YYYY-MM-DD'  
      sex?: string;        // 性别 ('男' 或 '女')  
      isValid?: boolean;   // 身份证号码是否有效 (当前版本简单返回true,待实现详细校验逻辑)  
    }  

未来展望

  • 完善校验逻辑:实现更严格的身份证号码校验,包括校验码的计算与验证。
  • 更广泛的证件类型支持:考虑支持更多国家或地区的身份证件类型。
  • 性能优化:对数据查找和字符串处理进行优化,提高解析效率。
  • 更详细的错误提示:当输入格式错误或无法解析时,提供更具体的错误信息。
  • 单元测试:为插件编写完善的单元测试,确保代码质量和稳定性。

作者与联系方式

希望这个插件能对您有所帮助!如果您有任何问题或建议,欢迎联系。

相关链接

收起阅读 »

从零到一:使用 uni-app x 开发鸿蒙 GitCode 目录树生成器

鸿蒙征文

从零到一:使用 uni-app x 开发鸿蒙 GitCode 目录树生成器

GitCodeTree Logo

作者: 徐建国

📖 目录

🎯 项目背景

起源故事

作为一名开发者,我经常需要在文档、博客和技术分享中展示项目的目录结构。传统的方法是手动使用 tree 命令或者写脚本生成,但这种方式有几个痛点:

  1. 不够直观:命令行操作对非技术人员不友好
  2. 缺乏灵活性:难以快速调整显示层级和过滤规则
  3. 移动端受限:在手机上无法方便地查看和分享
  4. 重复劳动:每次都要重新生成,没有历史记录

于是,我决定开发一个跨平台的移动端应用,让目录树生成变得简单、快速、优雅。

image-20251021194818129

需求分析

核心需求

  • ✅ 快速生成 GitCode 项目的目录树结构
  • ✅ 支持自定义显示深度和过滤规则
  • ✅ 一键复制到剪贴板,方便分享
  • ✅ 本地安全存储访问令牌

进阶需求

  • 🔄 深色模式支持
  • 📱 原生体验和流畅交互
  • 🎨 现代化的 UI 设计
  • 🔒 安全的数据管理

🛠️ 技术选型

为什么选择 uni-app x?

在技术选型阶段,我对比了多个跨平台框架:

框架 优势 劣势 适配性
uni-app x 原生性能、TypeScript、一次开发多端运行 生态相对较新 ⭐⭐⭐⭐⭐
React Native 生态成熟、组件丰富 性能略差、包体积大 ⭐⭐⭐⭐
Flutter 性能优秀、UI 精美 Dart 学习成本、包体积大 ⭐⭐⭐⭐
原生开发 性能最佳 开发成本高、维护困难 ⭐⭐⭐

最终选择 uni-app x 的原因

  1. 原生性能:基于原生渲染,性能接近原生应用
  2. TypeScript 支持:强类型带来更好的开发体验
  3. 一次开发,多端运行:同时支持 iOS、Android、HarmonyOS
  4. 开发效率高:Vue 语法简洁,开发速度快
  5. HarmonyOS 支持:未来趋势,提前布局

技术栈组成

┌─────────────────────────────────────┐  
│          应用层 (Application)        │  
│    GitCodeTree - 目录树生成器        │  
└─────────────────────────────────────┘  
                 ↓  
┌─────────────────────────────────────┐  
│          框架层 (Framework)          │  
│   uni-app x + Vue 3 + TypeScript    │  
└─────────────────────────────────────┘  
                 ↓  
┌─────────────────────────────────────┐  
│          API 层 (API Service)        │  
│        GitCode REST API v5          │  
└─────────────────────────────────────┘  
                 ↓  
┌─────────────────────────────────────┐  
│          平台层 (Platform)           │  
│  iOS / Android / HarmonyOS          │  
└─────────────────────────────────────┘

核心技术

  • 前端框架: uni-app x (Vue 3)
  • 编程语言: TypeScript / UTS
  • 数据请求: uni.request API
  • 状态管理: uni.storage (本地持久化)
  • UI 组件: 原生组件 + 自定义样式

🏗️ 架构设计

整体架构

采用单页面应用 (SPA) + 组件化的架构模式:

pages/  
└── gittree/  
    └── gittree.uvue          # 主页面组件  
        ├── Template          # 视图层  
        ├── Script            # 逻辑层  
        └── Style             # 样式层

数据流设计

用户输入  
  ↓  
输入验证  
  ↓  
解析项目信息 (owner/repo)  
  ↓  
API 请求  
  ├── 获取项目信息  
  └── 递归获取目录结构  
      ↓  
数据处理  
  ├── 过滤 (仅文件夹/完整)  
  ├── 深度控制 (1-5层/全部)  
  └── 格式化 (树形文本)  
      ↓  
UI 渲染  
  ├── 项目信息卡片  
  └── 目录树展示  
      ↓  
用户操作  
  ├── 复制到剪贴板  
  └── 下载/分享

组件结构

<GitTreePage>  
│  
├── <Header>                  # 顶部导航  
│   ├── Logo  
│   └── 主题切换按钮  
│  
├── <TokenSection>            # Token 配置区  
│   ├── 输入框  
│   ├── 保存按钮  
│   └── 提示信息  
│  
├── <MainSection>             # 主功能区  
│   ├── 项目输入框  
│   ├── 历史记录列表 (新)  
│   ├── 高级选项  
│   │   ├── 深度选择器  
│   │   └── 视图类型选择器  
│   ├── 生成按钮  
│   └── 测试按钮  
│  
├── <ProjectInfo>             # 项目信息卡片  
│   ├── 项目名称  
│   ├── 描述  
│   └── 统计信息  
│  
└── <DirectoryTree>           # 目录树展示  
    ├── 树形文本  
    └── 复制按钮

💻 核心功能实现

1. GitCode API 集成

API 认证

GitCode 使用 Personal Access Token (PAT) 进行身份验证:

// API 请求头配置  
const headers = {  
  'Authorization': `Bearer ${this.token}`,  
  'Accept': 'application/json',  
  'Content-Type': 'application/json'  
}

关键点

  • Token 必须在请求头中以 Bearer 前缀传递
  • 需要确保 Token 具有 user_infoprojects 权限
  • Token 存储在本地,使用 uni.setStorageSync 持久化

获取项目信息

async getProjectInfo(owner: string, repo: string) {  
  return new Promise((resolve, reject) => {  
    uni.request({  
      url: `https://api.gitcode.com/api/v5/repos/${owner}/${repo}`,  
      method: 'GET',  
      header: {  
        'Authorization': `Bearer ${this.token}`,  
        'Accept': 'application/json'  
      },  
      success: (res) => {  
        if (res.statusCode === 200) {  
          resolve(res.data)  
        } else {  
          reject(new Error(this.getErrorMessage(res.statusCode)))  
        }  
      },  
      fail: (err) => {  
        reject(new Error('网络连接失败,请检查网络设置'))  
      }  
    })  
  })  
}

错误处理策略

getErrorMessage(statusCode: number): string {  
  const errorMap: Record<number, string> = {  
    400: '请求参数错误',  
    401: '访问令牌无效或已过期,请重新配置',  
    403: '没有访问权限,请检查令牌权限设置',  
    404: '项目不存在,请检查项目路径',  
    429: '请求过于频繁,请稍后再试',  
    500: 'GitCode 服务器错误',  
    503: 'GitCode 服务暂时不可用'  
  }  
  return errorMap[statusCode] || `请求失败 (${statusCode})`  
}

2. 递归目录树生成

这是项目的核心算法,需要:

  • 递归遍历所有子目录
  • 支持深度限制
  • 处理异步请求
  • 优化性能

递归算法实现

async getProjectDirectory(  
  owner: string,  
  repo: string,  
  path: string = '',  
  currentDepth: number = 0,  
  maxDepth: number = 0  
): Promise<any[]> {  
  // 深度限制检查  
  if (maxDepth > 0 && currentDepth >= maxDepth) {  
    return []  
  }  

  try {  
    // 获取当前目录内容  
    const data: any = await this.fetchDirectoryContent(owner, repo, path)  

    if (!Array.isArray(data)) {  
      return []  
    }  

    // 并行处理所有子目录  
    const promises = data.map(async (item: any) => {  
      if (item.type === 'dir') {  
        item.children = await this.getProjectDirectory(  
          owner,  
          repo,  
          item.path,  
          currentDepth + 1,  
          maxDepth  
        )  
      }  
      return item  
    })  

    return await Promise.all(promises)  
  } catch (error) {  
    console.error(`获取目录失败: ${path}`, error)  
    return []  
  }  
}

性能优化点

  1. 并行请求:使用 Promise.all 同时处理多个子目录
  2. 深度限制:避免无限递归,减少 API 调用
  3. 错误隔离:单个目录失败不影响其他目录
  4. 缓存机制:同一项目避免重复请求(计划中)

时间复杂度分析

假设项目有 N 个目录,平均每个目录有 M 个子项:

  • 串行方式: O(N) - 总请求数 = N
  • 并行方式: O(log N) - 实际时间大幅减少
  • 深度限制: 最多 O(M^D) 其中 D 是最大深度

3. 树形文本格式化

生成美观的 ASCII 树形结构:

generateTreeText(  
  items: any[],  
  prefix: string = '',  
  isRoot: boolean = true,  
  viewType: string = 'all'  
): string {  
  let text = ''  

  // 过滤项目(如果只显示文件夹)  
  const filteredItems = viewType === 'folders'   
    ? items.filter(item => item.type === 'dir')  
    : items  

  filteredItems.forEach((item, index) => {  
    const isLast = index === filteredItems.length - 1  
    const connector = isLast ? '└── ' : '├── '  
    const icon = item.type === 'dir' ? '📁' : '📄'  

    // 构建当前行  
    text += `${prefix}${connector}${icon} ${item.name}\n`  

    // 递归处理子目录  
    if (item.type === 'dir' && item.children?.length > 0) {  
      const newPrefix = prefix + (isLast ? '    ' : '│   ')  
      text += this.generateTreeText(  
        item.children,  
        newPrefix,  
        false,  
        viewType  
      )  
    }  
  })  

  return text  
}

输出示例

📁 项目名称  
├── 📁 src  
│   ├── 📁 components  
│   │   ├── 📄 Button.vue  
│   │   └── 📄 Input.vue  
│   ├── 📁 pages  
│   │   └── 📄 Home.vue  
│   └── 📄 main.ts  
├── 📁 static  
│   └── 📄 logo.png  
└── 📄 package.json

4. 本地存储管理

使用 uni.storage API 实现数据持久化:

// 保存 Token  
saveToken() {  
  if (!this.token.trim()) {  
    this.showError('请输入访问令牌')  
    return  
  }  

  try {  
    uni.setStorageSync('gitcode_token', this.token)  
    this.isTokenSaved = true  
    this.showSuccess('访问令牌已保存')  
  } catch (error) {  
    this.showError('保存失败,请重试')  
  }  
}  

// 加载 Token  
loadToken() {  
  try {  
    const savedToken = uni.getStorageSync('gitcode_token')  
    if (savedToken) {  
      this.token = savedToken  
      this.isTokenSaved = true  
    }  
  } catch (error) {  
    console.error('加载 Token 失败', error)  
  }  
}  

// 保存主题设置  
saveTheme() {  
  uni.setStorageSync('theme', this.isDarkMode ? 'dark' : 'light')  
}  

// 加载主题设置  
loadTheme() {  
  const savedTheme = uni.getStorageSync('theme')  
  this.isDarkMode = savedTheme === 'dark'  
}

存储结构

LocalStorage  
├── gitcode_token        # GitCode 访问令牌  
├── theme               # 主题设置 (light/dark)  
├── project_history     # 项目历史记录 (新增)  
└── favorites           # 收藏项目 (计划中)

5. 用户体验优化

Toast 通知

替换传统的错误/成功消息显示:

// 错误提示  
showError(message: string) {  
  uni.showToast({  
    title: message,  
    icon: 'error',  
    duration: 3000  
  })  
  uni.vibrateShort() // 震动反馈  
}  

// 成功提示  
showSuccess(message: string) {  
  uni.showToast({  
    title: message,  
    icon: 'success',  
    duration: 2000  
  })  
  uni.vibrateShort()  
}

项目历史记录

实现智能输入建议:

// 数据结构  
data() {  
  return {  
    projectHistory: [] as string[],  // 历史记录列表  
    showHistory: false                // 控制显示  
  }  
}  

// 添加到历史  
addToHistory(project: string) {  
  // 移除重复项  
  const index = this.projectHistory.indexOf(project)  
  if (index !== -1) {  
    this.projectHistory.splice(index, 1)  
  }  

  // 添加到最前面  
  this.projectHistory.unshift(project)  

  // 限制最多 10 条  
  if (this.projectHistory.length > 10) {  
    this.projectHistory.pop()  
  }  

  this.saveHistory()  
}  

// 选择历史项  
selectHistoryItem(item: string) {  
  this.projectInput = item  
  this.showHistory = false  
}

触觉反馈

在关键操作点添加震动反馈:

// 复制成功  
copyTree() {  
  uni.setClipboardData({  
    data: this.directoryTree,  
    success: () => {  
      this.showSuccess('已复制到剪贴板')  
      uni.vibrateShort({ type: 'light' }) // 轻震动  
    }  
  })  
}  

// 生成完成  
async generateTree() {  
  // ... 生成逻辑 ...  

  this.showSuccess('生成成功')  
  uni.vibrateShort({ type: 'medium' }) // 中等震动  
}

🚀 性能优化

1. 并行请求优化

问题:串行请求导致大型项目生成时间过长

解决方案:使用 Promise.all 并行处理

// ❌ 串行方式 (慢)  
for (let item of items) {  
  if (item.type === 'dir') {  
    item.children = await getDirectory(item.path)  
  }  
}  

// ✅ 并行方式 (快)  
const promises = items.map(async (item) => {  
  if (item.type === 'dir') {  
    item.children = await getDirectory(item.path)  
  }  
  return item  
})  
await Promise.all(promises)

性能提升

  • 小型项目 (< 10 目录): 提升 30-50%
  • 中型项目 (10-50 目录): 提升 50-70%
  • 大型项目 (> 50 目录): 提升 70-85%

2. 深度控制优化

提供深度选项,避免不必要的请求:

const depthOptions = [  
  { label: '1层', value: 1 },  
  { label: '2层', value: 2 },  
  { label: '3层(推荐)', value: 3 },  
  { label: '4层', value: 4 },  
  { label: '5层', value: 5 },  
  { label: '全部', value: 0 }  
]

API 调用次数对比

假设每层平均 5 个子目录:

深度 API 调用次数 用时估算
1层 ~1 次 < 1s
2层 ~6 次 1-2s
3层 ~31 次 3-5s
4层 ~156 次 10-15s
5层 ~781 次 30-60s
全部 不确定 可能很长

建议

  • 快速预览:使用 2-3 层
  • 完整文档:使用 3-4 层
  • 详尽分析:使用全部(小心使用)

3. UI 渲染优化

虚拟滚动(计划中)

对于超大目录树,使用虚拟滚动:

// 只渲染可见区域的节点  
<scroll-view   
  scroll-y   
  :scroll-into-view="scrollIntoView"  
  @scroll="onScroll"  
>  
  <view v-for="item in visibleItems" :key="item.id">  
    {{ item.content }}  
  </view>  
</scroll-view>

分块加载

对于大型项目,分块显示:

// 分块渲染,避免卡顿  
async renderTree() {  
  const chunkSize = 100  
  let index = 0  

  while (index < this.treeLines.length) {  
    const chunk = this.treeLines.slice(index, index + chunkSize)  
    this.displayedTree += chunk.join('\n')  
    index += chunkSize  

    // 让出主线程,避免阻塞 UI  
    await new Promise(resolve => setTimeout(resolve, 0))  
  }  
}

🎨 UI/UX 设计

设计原则

遵循 Material DesigniOS Human Interface Guidelines

  1. 简洁直观:减少操作步骤,核心功能 3 步完成
  2. 视觉层次:使用卡片、阴影、颜色区分功能区
  3. 即时反馈:每个操作都有明确的视觉和触觉反馈
  4. 一致性:保持 UI 风格和交互模式统一

色彩系统

/* 主色调 - 渐变紫色 */  
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);  
--primary-color: #667eea;  
--primary-dark: #764ba2;  

/* 功能色 */  
--success-color: #10b981;  /* 成功/正面 */  
--error-color: #ef4444;    /* 错误/危险 */  
--warning-color: #f59e0b;  /* 警告 */  
--info-color: #3b82f6;     /* 信息 */  

/* 中性色 */  
--text-primary: #1a202c;   /* 主要文字 */  
--text-secondary: #718096; /* 次要文字 */  
--bg-primary: #ffffff;     /* 主背景 */  
--bg-secondary: #f8fafc;   /* 次背景 */  
--border-color: #e2e8f0;   /* 边框 */

响应式布局

/* 基础单位 rpx (responsive pixel) */  
/* 1rpx = 屏幕宽度 / 750 */  

.card {  
  margin: 20rpx 30rpx;  
  padding: 40rpx;  
  border-radius: 20rpx;  
}  

.input-field {  
  width: 100%;  
  height: 80rpx;  
  padding: 0 30rpx;  
  font-size: 28rpx;  
}  

/* 适配不同屏幕 */  
@media (max-width: 375px) {  
  .card {  
    margin: 15rpx 20rpx;  
    padding: 30rpx;  
  }  
}

交互动画

/* 按钮按下效果 */  
.btn:active {  
  transform: scale(0.98);  
  opacity: 0.9;  
}  

/* 卡片展开动画 */  
.card-content {  
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);  
  max-height: 0;  
  overflow: hidden;  
}  

.card-content.show {  
  max-height: 1000rpx;  
}  

/* 加载动画 */  
@keyframes spin {  
  from { transform: rotate(0deg); }  
  to { transform: rotate(360deg); }  
}  

.loading-icon {  
  animation: spin 1s linear infinite;  
}

🐛 踩坑经验

1. 滚动问题

问题描述:页面无法滚动,内容超出屏幕时无法查看

原因分析

  • uni-app x 中,<view> 组件默认不支持滚动
  • 需要使用 <scroll-view> 组件

解决方案

<!-- ❌ 错误写法 -->  
<view class="container">  
  <!-- 大量内容 -->  
</view>  

<!-- ✅ 正确写法 -->  
<scroll-view class="page" scroll-y="true">  
  <view class="container">  
    <!-- 大量内容 -->  
  </view>  
</scroll-view>

CSS 配置

.page {  
  width: 100%;  
  height: 100vh;  /* 必须设置高度 */  
  background-color: #f8fafc;  
}

2. 异步请求错误处理

问题描述:API 请求失败时,应用崩溃或无响应

原因分析

  • 没有正确处理 Promise 的 reject
  • 错误信息没有传递到 UI 层

解决方案

// ❌ 错误写法  
async getProjectInfo(owner, repo) {  
  const res = await uni.request({ /* ... */ })  
  return res.data  
}  

// ✅ 正确写法  
async getProjectInfo(owner, repo) {  
  return new Promise((resolve, reject) => {  
    uni.request({  
      url: `...`,  
      success: (res) => {  
        if (res.statusCode === 200) {  
          resolve(res.data)  
        } else {  
          reject(new Error(this.getErrorMessage(res.statusCode)))  
        }  
      },  
      fail: (err) => {  
        reject(new Error('网络连接失败'))  
      }  
    })  
  })  
}  

// 调用时使用 try-catch  
try {  
  const info = await this.getProjectInfo(owner, repo)  
  // 处理成功  
} catch (error) {  
  this.showError(error.message)  
}

3. TypeScript 类型问题

问题描述:uni-app x 的 API 类型定义不完整

解决方案

// 定义扩展类型  
interface UniRequestResponse {  
  statusCode: number  
  data: any  
  header: Record<string, any>  
  cookies: string[]  
}  

// 使用类型断言  
const res = await uni.request({ /* ... */ }) as UniRequestResponse  

// 或定义全局类型  
declare global {  
  interface Uni {  
    request(options: UniRequestOptions): Promise<UniRequestResponse>  
  }  
}

4. 存储同步问题

问题描述:多次快速操作导致数据丢失

原因分析

  • 异步存储没有等待完成
  • 并发写入导致数据覆盖

解决方案

// ❌ 错误写法  
saveHistory() {  
  uni.setStorage({  
    key: 'history',  
    data: this.history  
  })  
}  

// ✅ 正确写法(同步)  
saveHistory() {  
  try {  
    uni.setStorageSync('history', JSON.stringify(this.history))  
  } catch (error) {  
    console.error('保存失败', error)  
  }  
}  

// ✅ 或使用异步 + 防抖  
let saveTimer: number | null = null  

saveHistory() {  
  if (saveTimer) clearTimeout(saveTimer)  

  saveTimer = setTimeout(() => {  
    uni.setStorage({  
      key: 'history',  
      data: JSON.stringify(this.history),  
      success: () => console.log('保存成功'),  
      fail: (err) => console.error('保存失败', err)  
    })  
  }, 500)  
}

5. 递归深度限制

问题描述:深度过大导致调用栈溢出或请求超时

解决方案

// 添加深度限制和超时控制  
async getProjectDirectory(  
  owner: string,  
  repo: string,  
  path: string = '',  
  currentDepth: number = 0,  
  maxDepth: number = 5,  // 默认限制  
  timeout: number = 30000 // 30秒超时  
) {  
  // 深度检查  
  if (maxDepth > 0 && currentDepth >= maxDepth) {  
    return []  
  }  

  // 超时控制  
  const timeoutPromise = new Promise((_, reject) => {  
    setTimeout(() => reject(new Error('请求超时')), timeout)  
  })  

  const fetchPromise = this.fetchDirectoryContent(owner, repo, path)  

  try {  
    const data = await Promise.race([fetchPromise, timeoutPromise])  
    // 继续处理...  
  } catch (error) {  
    if (error.message === '请求超时') {  
      this.showError('获取目录超时,请尝试减小深度')  
    }  
    return []  
  }  
}

6. JSON 配置文件注释

问题描述pages.json 中的注释导致解析错误

原因:JSON 标准不支持注释

解决方案

// ❌ 错误写法  
{  
  "pages": [  
    // 这是首页  
    {  
      "path": "pages/index/index"  
    }  
  ]  
}  

// ✅ 正确写法  
{  
  "pages": [  
    {  
      "path": "pages/index/index"  
    }  
  ]  
}

📊 数据与性能

测试环境

  • 设备: mate 60 pro(鸿蒙6)
  • 测试项目: 中型开源项目 (约 50 个目录,200 个文件)

性能指标

操作 时间 优化后 提升
初始加载 0.8s 0.6s 25%
Token 保存 0.1s 0.05s 50%
API 连接测试 1.2s 0.9s 25%
生成目录树 (3层) 4.5s 2.8s 38%
生成目录树 (全部) 18s 10s 44%
复制到剪贴板 0.2s 0.1s 50%

内存占用

场景 内存占用 峰值
启动应用 45 MB 60 MB
生成小型树 50 MB 70 MB
生成大型树 80 MB 120 MB
长时间运行 55 MB 85 MB

优化措施

  • ✅ 及时清理临时变量
  • ✅ 分块处理大数据
  • ✅ 避免内存泄漏
  • 🔄 实现虚拟滚动(计划中)

🔐 安全性考虑

Token 安全

存储安全

// 使用 uni.storage 本地加密存储  
uni.setStorageSync('gitcode_token', this.token)  

// 未来计划:使用设备密钥加密  
import crypto from 'crypto'  

function encryptToken(token: string, key: string): string {  
  const cipher = crypto.createCipher('aes-256-cbc', key)  
  let encrypted = cipher.update(token, 'utf8', 'hex')  
  encrypted += cipher.final('hex')  
  return encrypted  
}

传输安全

  • ✅ 使用 HTTPS 协议
  • ✅ Token 仅在请求头传递
  • ✅ 不在 URL 中暴露 Token

使用建议

  • 🔐 定期更换 Token
  • 🔐 为应用单独生成 Token
  • 🔐 限制 Token 权限范围
  • 🔐 不要分享 Token

数据隐私

  • 本地处理:所有数据处理在本地完成
  • 无数据上传:不向第三方服务器发送数据
  • 权限最小化:仅请求必要的 API 权限
  • 透明度:开源代码,可审计

🎓 最佳实践总结

1. 代码组织

// ✅ 良好的代码结构  
export default {  
  data() {  
    // 1. 基础数据  
    // 2. UI 状态  
    // 3. 业务数据  
  },  

  onLoad() {  
    // 页面加载时的初始化  
  },  

  methods: {  
    // 1. 用户交互方法  
    // 2. API 请求方法  
    // 3. 数据处理方法  
    // 4. 工具方法  
  }  
}

2. 错误处理

// ✅ 完善的错误处理  
try {  
  const result = await this.apiCall()  
  this.handleSuccess(result)  
} catch (error) {  
  // 记录错误  
  console.error('操作失败:', error)  

  // 用户友好的提示  
  this.showError(this.getUserFriendlyMessage(error))  

  // 恢复 UI 状态  
  this.resetUIState()  
}

3. 用户体验

// ✅ 完整的用户反馈流程  
async performAction() {  
  // 1. 显示加载状态  
  this.isLoading = true  

  try {  
    // 2. 执行操作  
    const result = await this.doSomething()  

    // 3. 成功反馈  
    this.showSuccess('操作成功')  
    uni.vibrateShort()  

    // 4. 更新 UI  
    this.updateUI(result)  
  } catch (error) {  
    // 5. 错误反馈  
    this.showError(error.message)  
  } finally {  
    // 6. 清理状态  
    this.isLoading = false  
  }  
}

4. 性能优化

// ✅ 性能优化技巧  

// 1. 防抖  
const debouncedSearch = debounce(this.search, 300)  

// 2. 节流  
const throttledScroll = throttle(this.onScroll, 100)  

// 3. 懒加载  
const lazyLoadImages = () => {  
  // 仅加载可见区域图片  
}  

// 4. 缓存  
const cache = new Map()  
async function fetchWithCache(key) {  
  if (cache.has(key)) {  
    return cache.get(key)  
  }  
  const data = await fetch(key)  
  cache.set(key, data)  
  return data  
}

🚀 未来规划

v1.1.0 - 用户体验增强(开发中)

  • [x] Toast 通知替代传统提示
  • [x] 触觉反馈优化
  • [x] 项目历史记录
  • [ ] 深色模式完善
  • [ ] 收藏夹功能
  • [ ] 下拉刷新

v1.2.0 - 功能扩展(规划中)

  • [ ] 目录树导出为图片
  • [ ] 精美分享卡片
  • [ ] 项目搜索/过滤
  • [ ] 自定义样式主题
  • [ ] 批量处理项目

v1.3.0 - 高级功能(未来)

  • [ ] 离线缓存机制
  • [ ] Token 加密存储
  • [ ] 生物识别认证
  • [ ] 多语言支持
  • [ ] 云同步功能

v2.0.0 - 架构升级(愿景)

  • [ ] 支持更多 Git 平台(GitHub)
  • [ ] 插件系统
  • [ ] 自定义脚本
  • [ ] AI 智能分析项目结构
  • [ ] 协作功能

📚 参考资源

官方文档

相关项目

学习资源


💭 个人思考

技术选型的权衡

选择 uni-app x 是一个大胆的决定。它相对较新,生态还在完善中,但它的跨平台能力和原生性能让我觉得这是一个值得投资的技术方向。

在开发过程中,我深刻体会到:

  • 没有完美的技术,只有最适合的选择
  • 跨平台不是银弹,但能显著提高效率
  • 用户体验永远是第一位的

开发中的收获

  1. 深入理解异步编程:递归 + Promise 的组合让我对异步有了更深的认识
  2. API 设计的重要性:良好的 API 设计能让开发事半功倍
  3. 性能优化是持续的过程:不要过早优化,但也要时刻关注性能
  4. 用户反馈很重要:很多优化点都来自真实用户的反馈

给开发者的建议

  1. 从小做起:先实现核心功能,再逐步完善
  2. 注重细节:小的体验改进能带来大的满意度提升
  3. 持续学习:技术在不断进步,保持学习的热情
  4. 开源分享:分享你的代码,帮助更多人

🎉 总结

GitCodeTree 是我第一个使用 uni-app x 开发的完整应用,从技术选型到最终发布,整个过程充满挑战和收获。

核心成果

  • ✅ 实现了完整的跨平台目录树生成功能
  • ✅ 性能优化达到 40% 以上的提升
  • ✅ 用户体验优化,操作流畅自然
  • ✅ 代码结构清晰,易于维护和扩展

技术亮点

  • 🎯 递归算法 + 并行优化
  • 🎯 完善的错误处理机制
  • 🎯 优雅的 UI/UX 设计
  • 🎯 安全的数据存储

经验教训

  • 💡 性能优化要基于实际场景
  • 💡 用户体验细节决定产品质量
  • 💡 完善的错误处理能避免很多问题
  • 💡 持续迭代比一次完美更重要

📞 联系我

如果你对这个项目感兴趣,或者有任何问题和建议,欢迎联系我:


<div align="center">

⭐ 如果这篇文章对你有帮助,请给项目一个 Star!⭐

📖 更多技术文章,敬请期待!

GitCodeTree Logo

继续阅读 »

从零到一:使用 uni-app x 开发鸿蒙 GitCode 目录树生成器

GitCodeTree Logo

作者: 徐建国

📖 目录

🎯 项目背景

起源故事

作为一名开发者,我经常需要在文档、博客和技术分享中展示项目的目录结构。传统的方法是手动使用 tree 命令或者写脚本生成,但这种方式有几个痛点:

  1. 不够直观:命令行操作对非技术人员不友好
  2. 缺乏灵活性:难以快速调整显示层级和过滤规则
  3. 移动端受限:在手机上无法方便地查看和分享
  4. 重复劳动:每次都要重新生成,没有历史记录

于是,我决定开发一个跨平台的移动端应用,让目录树生成变得简单、快速、优雅。

image-20251021194818129

需求分析

核心需求

  • ✅ 快速生成 GitCode 项目的目录树结构
  • ✅ 支持自定义显示深度和过滤规则
  • ✅ 一键复制到剪贴板,方便分享
  • ✅ 本地安全存储访问令牌

进阶需求

  • 🔄 深色模式支持
  • 📱 原生体验和流畅交互
  • 🎨 现代化的 UI 设计
  • 🔒 安全的数据管理

🛠️ 技术选型

为什么选择 uni-app x?

在技术选型阶段,我对比了多个跨平台框架:

框架 优势 劣势 适配性
uni-app x 原生性能、TypeScript、一次开发多端运行 生态相对较新 ⭐⭐⭐⭐⭐
React Native 生态成熟、组件丰富 性能略差、包体积大 ⭐⭐⭐⭐
Flutter 性能优秀、UI 精美 Dart 学习成本、包体积大 ⭐⭐⭐⭐
原生开发 性能最佳 开发成本高、维护困难 ⭐⭐⭐

最终选择 uni-app x 的原因

  1. 原生性能:基于原生渲染,性能接近原生应用
  2. TypeScript 支持:强类型带来更好的开发体验
  3. 一次开发,多端运行:同时支持 iOS、Android、HarmonyOS
  4. 开发效率高:Vue 语法简洁,开发速度快
  5. HarmonyOS 支持:未来趋势,提前布局

技术栈组成

┌─────────────────────────────────────┐  
│          应用层 (Application)        │  
│    GitCodeTree - 目录树生成器        │  
└─────────────────────────────────────┘  
                 ↓  
┌─────────────────────────────────────┐  
│          框架层 (Framework)          │  
│   uni-app x + Vue 3 + TypeScript    │  
└─────────────────────────────────────┘  
                 ↓  
┌─────────────────────────────────────┐  
│          API 层 (API Service)        │  
│        GitCode REST API v5          │  
└─────────────────────────────────────┘  
                 ↓  
┌─────────────────────────────────────┐  
│          平台层 (Platform)           │  
│  iOS / Android / HarmonyOS          │  
└─────────────────────────────────────┘

核心技术

  • 前端框架: uni-app x (Vue 3)
  • 编程语言: TypeScript / UTS
  • 数据请求: uni.request API
  • 状态管理: uni.storage (本地持久化)
  • UI 组件: 原生组件 + 自定义样式

🏗️ 架构设计

整体架构

采用单页面应用 (SPA) + 组件化的架构模式:

pages/  
└── gittree/  
    └── gittree.uvue          # 主页面组件  
        ├── Template          # 视图层  
        ├── Script            # 逻辑层  
        └── Style             # 样式层

数据流设计

用户输入  
  ↓  
输入验证  
  ↓  
解析项目信息 (owner/repo)  
  ↓  
API 请求  
  ├── 获取项目信息  
  └── 递归获取目录结构  
      ↓  
数据处理  
  ├── 过滤 (仅文件夹/完整)  
  ├── 深度控制 (1-5层/全部)  
  └── 格式化 (树形文本)  
      ↓  
UI 渲染  
  ├── 项目信息卡片  
  └── 目录树展示  
      ↓  
用户操作  
  ├── 复制到剪贴板  
  └── 下载/分享

组件结构

<GitTreePage>  
│  
├── <Header>                  # 顶部导航  
│   ├── Logo  
│   └── 主题切换按钮  
│  
├── <TokenSection>            # Token 配置区  
│   ├── 输入框  
│   ├── 保存按钮  
│   └── 提示信息  
│  
├── <MainSection>             # 主功能区  
│   ├── 项目输入框  
│   ├── 历史记录列表 (新)  
│   ├── 高级选项  
│   │   ├── 深度选择器  
│   │   └── 视图类型选择器  
│   ├── 生成按钮  
│   └── 测试按钮  
│  
├── <ProjectInfo>             # 项目信息卡片  
│   ├── 项目名称  
│   ├── 描述  
│   └── 统计信息  
│  
└── <DirectoryTree>           # 目录树展示  
    ├── 树形文本  
    └── 复制按钮

💻 核心功能实现

1. GitCode API 集成

API 认证

GitCode 使用 Personal Access Token (PAT) 进行身份验证:

// API 请求头配置  
const headers = {  
  'Authorization': `Bearer ${this.token}`,  
  'Accept': 'application/json',  
  'Content-Type': 'application/json'  
}

关键点

  • Token 必须在请求头中以 Bearer 前缀传递
  • 需要确保 Token 具有 user_infoprojects 权限
  • Token 存储在本地,使用 uni.setStorageSync 持久化

获取项目信息

async getProjectInfo(owner: string, repo: string) {  
  return new Promise((resolve, reject) => {  
    uni.request({  
      url: `https://api.gitcode.com/api/v5/repos/${owner}/${repo}`,  
      method: 'GET',  
      header: {  
        'Authorization': `Bearer ${this.token}`,  
        'Accept': 'application/json'  
      },  
      success: (res) => {  
        if (res.statusCode === 200) {  
          resolve(res.data)  
        } else {  
          reject(new Error(this.getErrorMessage(res.statusCode)))  
        }  
      },  
      fail: (err) => {  
        reject(new Error('网络连接失败,请检查网络设置'))  
      }  
    })  
  })  
}

错误处理策略

getErrorMessage(statusCode: number): string {  
  const errorMap: Record<number, string> = {  
    400: '请求参数错误',  
    401: '访问令牌无效或已过期,请重新配置',  
    403: '没有访问权限,请检查令牌权限设置',  
    404: '项目不存在,请检查项目路径',  
    429: '请求过于频繁,请稍后再试',  
    500: 'GitCode 服务器错误',  
    503: 'GitCode 服务暂时不可用'  
  }  
  return errorMap[statusCode] || `请求失败 (${statusCode})`  
}

2. 递归目录树生成

这是项目的核心算法,需要:

  • 递归遍历所有子目录
  • 支持深度限制
  • 处理异步请求
  • 优化性能

递归算法实现

async getProjectDirectory(  
  owner: string,  
  repo: string,  
  path: string = '',  
  currentDepth: number = 0,  
  maxDepth: number = 0  
): Promise<any[]> {  
  // 深度限制检查  
  if (maxDepth > 0 && currentDepth >= maxDepth) {  
    return []  
  }  

  try {  
    // 获取当前目录内容  
    const data: any = await this.fetchDirectoryContent(owner, repo, path)  

    if (!Array.isArray(data)) {  
      return []  
    }  

    // 并行处理所有子目录  
    const promises = data.map(async (item: any) => {  
      if (item.type === 'dir') {  
        item.children = await this.getProjectDirectory(  
          owner,  
          repo,  
          item.path,  
          currentDepth + 1,  
          maxDepth  
        )  
      }  
      return item  
    })  

    return await Promise.all(promises)  
  } catch (error) {  
    console.error(`获取目录失败: ${path}`, error)  
    return []  
  }  
}

性能优化点

  1. 并行请求:使用 Promise.all 同时处理多个子目录
  2. 深度限制:避免无限递归,减少 API 调用
  3. 错误隔离:单个目录失败不影响其他目录
  4. 缓存机制:同一项目避免重复请求(计划中)

时间复杂度分析

假设项目有 N 个目录,平均每个目录有 M 个子项:

  • 串行方式: O(N) - 总请求数 = N
  • 并行方式: O(log N) - 实际时间大幅减少
  • 深度限制: 最多 O(M^D) 其中 D 是最大深度

3. 树形文本格式化

生成美观的 ASCII 树形结构:

generateTreeText(  
  items: any[],  
  prefix: string = '',  
  isRoot: boolean = true,  
  viewType: string = 'all'  
): string {  
  let text = ''  

  // 过滤项目(如果只显示文件夹)  
  const filteredItems = viewType === 'folders'   
    ? items.filter(item => item.type === 'dir')  
    : items  

  filteredItems.forEach((item, index) => {  
    const isLast = index === filteredItems.length - 1  
    const connector = isLast ? '└── ' : '├── '  
    const icon = item.type === 'dir' ? '📁' : '📄'  

    // 构建当前行  
    text += `${prefix}${connector}${icon} ${item.name}\n`  

    // 递归处理子目录  
    if (item.type === 'dir' && item.children?.length > 0) {  
      const newPrefix = prefix + (isLast ? '    ' : '│   ')  
      text += this.generateTreeText(  
        item.children,  
        newPrefix,  
        false,  
        viewType  
      )  
    }  
  })  

  return text  
}

输出示例

📁 项目名称  
├── 📁 src  
│   ├── 📁 components  
│   │   ├── 📄 Button.vue  
│   │   └── 📄 Input.vue  
│   ├── 📁 pages  
│   │   └── 📄 Home.vue  
│   └── 📄 main.ts  
├── 📁 static  
│   └── 📄 logo.png  
└── 📄 package.json

4. 本地存储管理

使用 uni.storage API 实现数据持久化:

// 保存 Token  
saveToken() {  
  if (!this.token.trim()) {  
    this.showError('请输入访问令牌')  
    return  
  }  

  try {  
    uni.setStorageSync('gitcode_token', this.token)  
    this.isTokenSaved = true  
    this.showSuccess('访问令牌已保存')  
  } catch (error) {  
    this.showError('保存失败,请重试')  
  }  
}  

// 加载 Token  
loadToken() {  
  try {  
    const savedToken = uni.getStorageSync('gitcode_token')  
    if (savedToken) {  
      this.token = savedToken  
      this.isTokenSaved = true  
    }  
  } catch (error) {  
    console.error('加载 Token 失败', error)  
  }  
}  

// 保存主题设置  
saveTheme() {  
  uni.setStorageSync('theme', this.isDarkMode ? 'dark' : 'light')  
}  

// 加载主题设置  
loadTheme() {  
  const savedTheme = uni.getStorageSync('theme')  
  this.isDarkMode = savedTheme === 'dark'  
}

存储结构

LocalStorage  
├── gitcode_token        # GitCode 访问令牌  
├── theme               # 主题设置 (light/dark)  
├── project_history     # 项目历史记录 (新增)  
└── favorites           # 收藏项目 (计划中)

5. 用户体验优化

Toast 通知

替换传统的错误/成功消息显示:

// 错误提示  
showError(message: string) {  
  uni.showToast({  
    title: message,  
    icon: 'error',  
    duration: 3000  
  })  
  uni.vibrateShort() // 震动反馈  
}  

// 成功提示  
showSuccess(message: string) {  
  uni.showToast({  
    title: message,  
    icon: 'success',  
    duration: 2000  
  })  
  uni.vibrateShort()  
}

项目历史记录

实现智能输入建议:

// 数据结构  
data() {  
  return {  
    projectHistory: [] as string[],  // 历史记录列表  
    showHistory: false                // 控制显示  
  }  
}  

// 添加到历史  
addToHistory(project: string) {  
  // 移除重复项  
  const index = this.projectHistory.indexOf(project)  
  if (index !== -1) {  
    this.projectHistory.splice(index, 1)  
  }  

  // 添加到最前面  
  this.projectHistory.unshift(project)  

  // 限制最多 10 条  
  if (this.projectHistory.length > 10) {  
    this.projectHistory.pop()  
  }  

  this.saveHistory()  
}  

// 选择历史项  
selectHistoryItem(item: string) {  
  this.projectInput = item  
  this.showHistory = false  
}

触觉反馈

在关键操作点添加震动反馈:

// 复制成功  
copyTree() {  
  uni.setClipboardData({  
    data: this.directoryTree,  
    success: () => {  
      this.showSuccess('已复制到剪贴板')  
      uni.vibrateShort({ type: 'light' }) // 轻震动  
    }  
  })  
}  

// 生成完成  
async generateTree() {  
  // ... 生成逻辑 ...  

  this.showSuccess('生成成功')  
  uni.vibrateShort({ type: 'medium' }) // 中等震动  
}

🚀 性能优化

1. 并行请求优化

问题:串行请求导致大型项目生成时间过长

解决方案:使用 Promise.all 并行处理

// ❌ 串行方式 (慢)  
for (let item of items) {  
  if (item.type === 'dir') {  
    item.children = await getDirectory(item.path)  
  }  
}  

// ✅ 并行方式 (快)  
const promises = items.map(async (item) => {  
  if (item.type === 'dir') {  
    item.children = await getDirectory(item.path)  
  }  
  return item  
})  
await Promise.all(promises)

性能提升

  • 小型项目 (< 10 目录): 提升 30-50%
  • 中型项目 (10-50 目录): 提升 50-70%
  • 大型项目 (> 50 目录): 提升 70-85%

2. 深度控制优化

提供深度选项,避免不必要的请求:

const depthOptions = [  
  { label: '1层', value: 1 },  
  { label: '2层', value: 2 },  
  { label: '3层(推荐)', value: 3 },  
  { label: '4层', value: 4 },  
  { label: '5层', value: 5 },  
  { label: '全部', value: 0 }  
]

API 调用次数对比

假设每层平均 5 个子目录:

深度 API 调用次数 用时估算
1层 ~1 次 < 1s
2层 ~6 次 1-2s
3层 ~31 次 3-5s
4层 ~156 次 10-15s
5层 ~781 次 30-60s
全部 不确定 可能很长

建议

  • 快速预览:使用 2-3 层
  • 完整文档:使用 3-4 层
  • 详尽分析:使用全部(小心使用)

3. UI 渲染优化

虚拟滚动(计划中)

对于超大目录树,使用虚拟滚动:

// 只渲染可见区域的节点  
<scroll-view   
  scroll-y   
  :scroll-into-view="scrollIntoView"  
  @scroll="onScroll"  
>  
  <view v-for="item in visibleItems" :key="item.id">  
    {{ item.content }}  
  </view>  
</scroll-view>

分块加载

对于大型项目,分块显示:

// 分块渲染,避免卡顿  
async renderTree() {  
  const chunkSize = 100  
  let index = 0  

  while (index < this.treeLines.length) {  
    const chunk = this.treeLines.slice(index, index + chunkSize)  
    this.displayedTree += chunk.join('\n')  
    index += chunkSize  

    // 让出主线程,避免阻塞 UI  
    await new Promise(resolve => setTimeout(resolve, 0))  
  }  
}

🎨 UI/UX 设计

设计原则

遵循 Material DesigniOS Human Interface Guidelines

  1. 简洁直观:减少操作步骤,核心功能 3 步完成
  2. 视觉层次:使用卡片、阴影、颜色区分功能区
  3. 即时反馈:每个操作都有明确的视觉和触觉反馈
  4. 一致性:保持 UI 风格和交互模式统一

色彩系统

/* 主色调 - 渐变紫色 */  
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);  
--primary-color: #667eea;  
--primary-dark: #764ba2;  

/* 功能色 */  
--success-color: #10b981;  /* 成功/正面 */  
--error-color: #ef4444;    /* 错误/危险 */  
--warning-color: #f59e0b;  /* 警告 */  
--info-color: #3b82f6;     /* 信息 */  

/* 中性色 */  
--text-primary: #1a202c;   /* 主要文字 */  
--text-secondary: #718096; /* 次要文字 */  
--bg-primary: #ffffff;     /* 主背景 */  
--bg-secondary: #f8fafc;   /* 次背景 */  
--border-color: #e2e8f0;   /* 边框 */

响应式布局

/* 基础单位 rpx (responsive pixel) */  
/* 1rpx = 屏幕宽度 / 750 */  

.card {  
  margin: 20rpx 30rpx;  
  padding: 40rpx;  
  border-radius: 20rpx;  
}  

.input-field {  
  width: 100%;  
  height: 80rpx;  
  padding: 0 30rpx;  
  font-size: 28rpx;  
}  

/* 适配不同屏幕 */  
@media (max-width: 375px) {  
  .card {  
    margin: 15rpx 20rpx;  
    padding: 30rpx;  
  }  
}

交互动画

/* 按钮按下效果 */  
.btn:active {  
  transform: scale(0.98);  
  opacity: 0.9;  
}  

/* 卡片展开动画 */  
.card-content {  
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);  
  max-height: 0;  
  overflow: hidden;  
}  

.card-content.show {  
  max-height: 1000rpx;  
}  

/* 加载动画 */  
@keyframes spin {  
  from { transform: rotate(0deg); }  
  to { transform: rotate(360deg); }  
}  

.loading-icon {  
  animation: spin 1s linear infinite;  
}

🐛 踩坑经验

1. 滚动问题

问题描述:页面无法滚动,内容超出屏幕时无法查看

原因分析

  • uni-app x 中,<view> 组件默认不支持滚动
  • 需要使用 <scroll-view> 组件

解决方案

<!-- ❌ 错误写法 -->  
<view class="container">  
  <!-- 大量内容 -->  
</view>  

<!-- ✅ 正确写法 -->  
<scroll-view class="page" scroll-y="true">  
  <view class="container">  
    <!-- 大量内容 -->  
  </view>  
</scroll-view>

CSS 配置

.page {  
  width: 100%;  
  height: 100vh;  /* 必须设置高度 */  
  background-color: #f8fafc;  
}

2. 异步请求错误处理

问题描述:API 请求失败时,应用崩溃或无响应

原因分析

  • 没有正确处理 Promise 的 reject
  • 错误信息没有传递到 UI 层

解决方案

// ❌ 错误写法  
async getProjectInfo(owner, repo) {  
  const res = await uni.request({ /* ... */ })  
  return res.data  
}  

// ✅ 正确写法  
async getProjectInfo(owner, repo) {  
  return new Promise((resolve, reject) => {  
    uni.request({  
      url: `...`,  
      success: (res) => {  
        if (res.statusCode === 200) {  
          resolve(res.data)  
        } else {  
          reject(new Error(this.getErrorMessage(res.statusCode)))  
        }  
      },  
      fail: (err) => {  
        reject(new Error('网络连接失败'))  
      }  
    })  
  })  
}  

// 调用时使用 try-catch  
try {  
  const info = await this.getProjectInfo(owner, repo)  
  // 处理成功  
} catch (error) {  
  this.showError(error.message)  
}

3. TypeScript 类型问题

问题描述:uni-app x 的 API 类型定义不完整

解决方案

// 定义扩展类型  
interface UniRequestResponse {  
  statusCode: number  
  data: any  
  header: Record<string, any>  
  cookies: string[]  
}  

// 使用类型断言  
const res = await uni.request({ /* ... */ }) as UniRequestResponse  

// 或定义全局类型  
declare global {  
  interface Uni {  
    request(options: UniRequestOptions): Promise<UniRequestResponse>  
  }  
}

4. 存储同步问题

问题描述:多次快速操作导致数据丢失

原因分析

  • 异步存储没有等待完成
  • 并发写入导致数据覆盖

解决方案

// ❌ 错误写法  
saveHistory() {  
  uni.setStorage({  
    key: 'history',  
    data: this.history  
  })  
}  

// ✅ 正确写法(同步)  
saveHistory() {  
  try {  
    uni.setStorageSync('history', JSON.stringify(this.history))  
  } catch (error) {  
    console.error('保存失败', error)  
  }  
}  

// ✅ 或使用异步 + 防抖  
let saveTimer: number | null = null  

saveHistory() {  
  if (saveTimer) clearTimeout(saveTimer)  

  saveTimer = setTimeout(() => {  
    uni.setStorage({  
      key: 'history',  
      data: JSON.stringify(this.history),  
      success: () => console.log('保存成功'),  
      fail: (err) => console.error('保存失败', err)  
    })  
  }, 500)  
}

5. 递归深度限制

问题描述:深度过大导致调用栈溢出或请求超时

解决方案

// 添加深度限制和超时控制  
async getProjectDirectory(  
  owner: string,  
  repo: string,  
  path: string = '',  
  currentDepth: number = 0,  
  maxDepth: number = 5,  // 默认限制  
  timeout: number = 30000 // 30秒超时  
) {  
  // 深度检查  
  if (maxDepth > 0 && currentDepth >= maxDepth) {  
    return []  
  }  

  // 超时控制  
  const timeoutPromise = new Promise((_, reject) => {  
    setTimeout(() => reject(new Error('请求超时')), timeout)  
  })  

  const fetchPromise = this.fetchDirectoryContent(owner, repo, path)  

  try {  
    const data = await Promise.race([fetchPromise, timeoutPromise])  
    // 继续处理...  
  } catch (error) {  
    if (error.message === '请求超时') {  
      this.showError('获取目录超时,请尝试减小深度')  
    }  
    return []  
  }  
}

6. JSON 配置文件注释

问题描述pages.json 中的注释导致解析错误

原因:JSON 标准不支持注释

解决方案

// ❌ 错误写法  
{  
  "pages": [  
    // 这是首页  
    {  
      "path": "pages/index/index"  
    }  
  ]  
}  

// ✅ 正确写法  
{  
  "pages": [  
    {  
      "path": "pages/index/index"  
    }  
  ]  
}

📊 数据与性能

测试环境

  • 设备: mate 60 pro(鸿蒙6)
  • 测试项目: 中型开源项目 (约 50 个目录,200 个文件)

性能指标

操作 时间 优化后 提升
初始加载 0.8s 0.6s 25%
Token 保存 0.1s 0.05s 50%
API 连接测试 1.2s 0.9s 25%
生成目录树 (3层) 4.5s 2.8s 38%
生成目录树 (全部) 18s 10s 44%
复制到剪贴板 0.2s 0.1s 50%

内存占用

场景 内存占用 峰值
启动应用 45 MB 60 MB
生成小型树 50 MB 70 MB
生成大型树 80 MB 120 MB
长时间运行 55 MB 85 MB

优化措施

  • ✅ 及时清理临时变量
  • ✅ 分块处理大数据
  • ✅ 避免内存泄漏
  • 🔄 实现虚拟滚动(计划中)

🔐 安全性考虑

Token 安全

存储安全

// 使用 uni.storage 本地加密存储  
uni.setStorageSync('gitcode_token', this.token)  

// 未来计划:使用设备密钥加密  
import crypto from 'crypto'  

function encryptToken(token: string, key: string): string {  
  const cipher = crypto.createCipher('aes-256-cbc', key)  
  let encrypted = cipher.update(token, 'utf8', 'hex')  
  encrypted += cipher.final('hex')  
  return encrypted  
}

传输安全

  • ✅ 使用 HTTPS 协议
  • ✅ Token 仅在请求头传递
  • ✅ 不在 URL 中暴露 Token

使用建议

  • 🔐 定期更换 Token
  • 🔐 为应用单独生成 Token
  • 🔐 限制 Token 权限范围
  • 🔐 不要分享 Token

数据隐私

  • 本地处理:所有数据处理在本地完成
  • 无数据上传:不向第三方服务器发送数据
  • 权限最小化:仅请求必要的 API 权限
  • 透明度:开源代码,可审计

🎓 最佳实践总结

1. 代码组织

// ✅ 良好的代码结构  
export default {  
  data() {  
    // 1. 基础数据  
    // 2. UI 状态  
    // 3. 业务数据  
  },  

  onLoad() {  
    // 页面加载时的初始化  
  },  

  methods: {  
    // 1. 用户交互方法  
    // 2. API 请求方法  
    // 3. 数据处理方法  
    // 4. 工具方法  
  }  
}

2. 错误处理

// ✅ 完善的错误处理  
try {  
  const result = await this.apiCall()  
  this.handleSuccess(result)  
} catch (error) {  
  // 记录错误  
  console.error('操作失败:', error)  

  // 用户友好的提示  
  this.showError(this.getUserFriendlyMessage(error))  

  // 恢复 UI 状态  
  this.resetUIState()  
}

3. 用户体验

// ✅ 完整的用户反馈流程  
async performAction() {  
  // 1. 显示加载状态  
  this.isLoading = true  

  try {  
    // 2. 执行操作  
    const result = await this.doSomething()  

    // 3. 成功反馈  
    this.showSuccess('操作成功')  
    uni.vibrateShort()  

    // 4. 更新 UI  
    this.updateUI(result)  
  } catch (error) {  
    // 5. 错误反馈  
    this.showError(error.message)  
  } finally {  
    // 6. 清理状态  
    this.isLoading = false  
  }  
}

4. 性能优化

// ✅ 性能优化技巧  

// 1. 防抖  
const debouncedSearch = debounce(this.search, 300)  

// 2. 节流  
const throttledScroll = throttle(this.onScroll, 100)  

// 3. 懒加载  
const lazyLoadImages = () => {  
  // 仅加载可见区域图片  
}  

// 4. 缓存  
const cache = new Map()  
async function fetchWithCache(key) {  
  if (cache.has(key)) {  
    return cache.get(key)  
  }  
  const data = await fetch(key)  
  cache.set(key, data)  
  return data  
}

🚀 未来规划

v1.1.0 - 用户体验增强(开发中)

  • [x] Toast 通知替代传统提示
  • [x] 触觉反馈优化
  • [x] 项目历史记录
  • [ ] 深色模式完善
  • [ ] 收藏夹功能
  • [ ] 下拉刷新

v1.2.0 - 功能扩展(规划中)

  • [ ] 目录树导出为图片
  • [ ] 精美分享卡片
  • [ ] 项目搜索/过滤
  • [ ] 自定义样式主题
  • [ ] 批量处理项目

v1.3.0 - 高级功能(未来)

  • [ ] 离线缓存机制
  • [ ] Token 加密存储
  • [ ] 生物识别认证
  • [ ] 多语言支持
  • [ ] 云同步功能

v2.0.0 - 架构升级(愿景)

  • [ ] 支持更多 Git 平台(GitHub)
  • [ ] 插件系统
  • [ ] 自定义脚本
  • [ ] AI 智能分析项目结构
  • [ ] 协作功能

📚 参考资源

官方文档

相关项目

学习资源


💭 个人思考

技术选型的权衡

选择 uni-app x 是一个大胆的决定。它相对较新,生态还在完善中,但它的跨平台能力和原生性能让我觉得这是一个值得投资的技术方向。

在开发过程中,我深刻体会到:

  • 没有完美的技术,只有最适合的选择
  • 跨平台不是银弹,但能显著提高效率
  • 用户体验永远是第一位的

开发中的收获

  1. 深入理解异步编程:递归 + Promise 的组合让我对异步有了更深的认识
  2. API 设计的重要性:良好的 API 设计能让开发事半功倍
  3. 性能优化是持续的过程:不要过早优化,但也要时刻关注性能
  4. 用户反馈很重要:很多优化点都来自真实用户的反馈

给开发者的建议

  1. 从小做起:先实现核心功能,再逐步完善
  2. 注重细节:小的体验改进能带来大的满意度提升
  3. 持续学习:技术在不断进步,保持学习的热情
  4. 开源分享:分享你的代码,帮助更多人

🎉 总结

GitCodeTree 是我第一个使用 uni-app x 开发的完整应用,从技术选型到最终发布,整个过程充满挑战和收获。

核心成果

  • ✅ 实现了完整的跨平台目录树生成功能
  • ✅ 性能优化达到 40% 以上的提升
  • ✅ 用户体验优化,操作流畅自然
  • ✅ 代码结构清晰,易于维护和扩展

技术亮点

  • 🎯 递归算法 + 并行优化
  • 🎯 完善的错误处理机制
  • 🎯 优雅的 UI/UX 设计
  • 🎯 安全的数据存储

经验教训

  • 💡 性能优化要基于实际场景
  • 💡 用户体验细节决定产品质量
  • 💡 完善的错误处理能避免很多问题
  • 💡 持续迭代比一次完美更重要

📞 联系我

如果你对这个项目感兴趣,或者有任何问题和建议,欢迎联系我:


<div align="center">

⭐ 如果这篇文章对你有帮助,请给项目一个 Star!⭐

📖 更多技术文章,敬请期待!

GitCodeTree Logo

收起阅读 »

uniapp极速上手鸿蒙开发

鸿蒙征文

uniapp极速上手鸿蒙开发

uniapp 团队 与版本 4.28.2024092502 起,支持鸿蒙应用开发,现在是 4.76.2025082103,同时支持鸿蒙应用和元服务开发了。

我们现在上手感受一下

环境配置

底下主要针对真机做的演示

项目要求

在创建uniapp项目的时候,需要选择 vue3,vue2不支持

搭建流程

  1. AGC平台上新建项目,目的是活动boundleName以及调试和发布证书
  2. 下载和安装 DevEco Studio
  3. 使用 DevEco Studio 创建项目,然后配置boundleName和调试和发布证书
  4. 复制证书相关的配置
  5. 下载和安装 hbuilder
  6. hbuilder 下载相关插件
  7. hbuilder 配置 DevEco Studio 工具路径
  8. hbuilder新建uniapp vue3工程
  9. hbuilder配置鸿蒙应用的证书
  10. hbuilder运行项目

AGC平台上新建项目

可以根据需求选择新建工程还是项目。 这里选择项目

下载和安装 DevEco Studio

下载和安装DevEco Studio

boundleName和调试和发布证书

因为真机在调试时候需要使用调试证书

应用在发布的时候需要使用发布整数,因此一次性都配置获得即可

配置链接

image-20241219092436235

DevEco Studio 新建项目获得证书配置信息

这个步骤主要为了得到证书的配置代码,uniapp运行项目的时候需要用到

在使用DevEco Studio新建完项目后,参考链接 进行证书的配置

得到配置文件 build-profile.json5 后续复制整个代码到uniapp创建的项目即可

image-20241219093119586

下载和安装 hbuilder

这里下载和安装

hbuilder 下载相关插件

工具-插件安装 关键是这几个 鸿蒙、vue3

image-20241219093722816

hbuilder 配置 DevEco Studio 工具路径

这里配置DevEco Studio 工具的路径 工具-设置

image-20241219093840013

hbuilder新建uniapp vue3工程

新建vue3工程

image-20241219093925639

hbuilder配置鸿蒙应用的证书

在项目根目录下配置 \harmony-configs\build-profile.json5 如果不存在,则手动新建。

然后复制、粘贴证书代码进去

image-20241219094156393

hbuilder运行项目

最后,运行项目

image-20241219094225699

效果

image-20241219094414865

继续阅读 »

uniapp极速上手鸿蒙开发

uniapp 团队 与版本 4.28.2024092502 起,支持鸿蒙应用开发,现在是 4.76.2025082103,同时支持鸿蒙应用和元服务开发了。

我们现在上手感受一下

环境配置

底下主要针对真机做的演示

项目要求

在创建uniapp项目的时候,需要选择 vue3,vue2不支持

搭建流程

  1. AGC平台上新建项目,目的是活动boundleName以及调试和发布证书
  2. 下载和安装 DevEco Studio
  3. 使用 DevEco Studio 创建项目,然后配置boundleName和调试和发布证书
  4. 复制证书相关的配置
  5. 下载和安装 hbuilder
  6. hbuilder 下载相关插件
  7. hbuilder 配置 DevEco Studio 工具路径
  8. hbuilder新建uniapp vue3工程
  9. hbuilder配置鸿蒙应用的证书
  10. hbuilder运行项目

AGC平台上新建项目

可以根据需求选择新建工程还是项目。 这里选择项目

下载和安装 DevEco Studio

下载和安装DevEco Studio

boundleName和调试和发布证书

因为真机在调试时候需要使用调试证书

应用在发布的时候需要使用发布整数,因此一次性都配置获得即可

配置链接

image-20241219092436235

DevEco Studio 新建项目获得证书配置信息

这个步骤主要为了得到证书的配置代码,uniapp运行项目的时候需要用到

在使用DevEco Studio新建完项目后,参考链接 进行证书的配置

得到配置文件 build-profile.json5 后续复制整个代码到uniapp创建的项目即可

image-20241219093119586

下载和安装 hbuilder

这里下载和安装

hbuilder 下载相关插件

工具-插件安装 关键是这几个 鸿蒙、vue3

image-20241219093722816

hbuilder 配置 DevEco Studio 工具路径

这里配置DevEco Studio 工具的路径 工具-设置

image-20241219093840013

hbuilder新建uniapp vue3工程

新建vue3工程

image-20241219093925639

hbuilder配置鸿蒙应用的证书

在项目根目录下配置 \harmony-configs\build-profile.json5 如果不存在,则手动新建。

然后复制、粘贴证书代码进去

image-20241219094156393

hbuilder运行项目

最后,运行项目

image-20241219094225699

效果

image-20241219094414865

收起阅读 »

经验分享 如何在鸿蒙应用中唤起鸿蒙应用、元服务

鸿蒙next

鸿蒙应用如何注册和声明 DeepLink 和 AppLinking 可参考 《通过 URL Scheme 唤起鸿蒙应用

这里重点介绍如何在应用中唤起其他应用和元服务。

鸿蒙唤起鸿蒙、元服务已经迁移到文档 《鸿蒙应用唤起鸿蒙应用、元服务

鸿蒙元服务唤起鸿蒙应用已经迁移到文档 《鸿蒙元服务唤起鸿蒙应用

继续阅读 »

鸿蒙应用如何注册和声明 DeepLink 和 AppLinking 可参考 《通过 URL Scheme 唤起鸿蒙应用

这里重点介绍如何在应用中唤起其他应用和元服务。

鸿蒙唤起鸿蒙、元服务已经迁移到文档 《鸿蒙应用唤起鸿蒙应用、元服务

鸿蒙元服务唤起鸿蒙应用已经迁移到文档 《鸿蒙元服务唤起鸿蒙应用

收起阅读 »

鸿蒙 UTS 插件使用三方依赖、本地依赖

uts插件 鸿蒙征文

鸿蒙 UTS 插件使用三方依赖

在鸿蒙开发中市场需要使用三方依赖,可能是三方包,可能是一个本地 har 包,这里介绍如何接入。

接入三方依赖

这里举例 https://ohpm.openharmony.cn/ 最受欢迎的三方库 @pura/harmony-utils 。这个库,提供了众多方法,可以加速功能开发。

更新:为了辅助说明,这里提供了 uts 源码,可对比参考 https://ext.dcloud.net.cn/plugin?id=24849

harmony-utils 一款功能丰富且极易上手的HarmonyOS工具库,借助众多实用工具类,致力于助力开发者迅速构建鸿蒙应用。其封装的工具涵盖了APP、设备、屏幕、授权、通知、线程间通信、弹框、吐司、生物认证、用户首选项、拍照、相册、扫码、文件、日志,异常捕获、字符、字符串、数字、集合、日期、随机、base64、加密、解密、JSON等一系列的功能和操作,能够满足各种不同的开发需求。

从原生鸿蒙角度开发,使用这个工具库,需要两个步骤

  • 安装依赖
  • 调用方法

在 UTS 中使用这个工具库,需要三个步骤

  • 创建 UTS 插件
  • 引用依赖
  • 调用方法

下面介绍具体步骤

假设我们希望通过 harmony-utils 获取当前的工具包名。调用的是 AppUtil.getBundleName

1. 创建 UTS 插件

在 HBuilderX 中操作。首先创建 uni_modules 功能,如果当前目录中没有对应文件夹,可在项目文件夹节点单击右键选择 新建 uni_modules 目录

这会创建 uni_moduels 文件夹,在这个文件夹上单击右键。

在新窗口中选择 UTS 插件-API 插件,点击创建。

假定插件的 ID 是 invoke-utils,找到这个文件夹,观察是否存在对应的文件,如果没有就创建 uni_modules/invoke-utils/utssdk/app-harmony/index.uts

这样插件就创建好了。这部分可参考 UTS 插件介绍原生混编 做进一步了解。

2. 安装依赖

创建 uni_modules/invoke-utils/utssdk/app-harmony/config.json,添加依赖。可参考文档 配置uts插件依赖

鸿蒙的库管理工具是ohpm。类似于js的npm,Android的仓储。鸿蒙的三方sdk封装文件为.har,类似于Android的.aar
uts插件的utssdk/app-harmony/config.json文件内可以配置依赖使用鸿蒙的三方库

代码填写下面方案:

{  
   "dependencies": {  
     "@pura/harmony-utils":"1.3.6"  
   }  
}

接下来准备使用依赖功能。

3. 调用方法

在 index.uts 中添加下面逻辑

import { AppUtil } from '@pura/harmony-utils'  

UTSHarmony.onAppAbilityCreate(() => {  
  const abilityCtx = UTSHarmony.getUIAbilityContext();  
  const ctx = abilityCtx  
  AppUtil.init(ctx);  
})  

export const getAppId = () => {  
  let bundleName = AppUtil.getBundleName();  
  return bundleName  
}

在原始文档中要求在 AbilityCreate 中初始化,这里可以使用 UTSHarmony.onAppAbilityCreate 来实现初始化。

和 TS 代码类似,调用了提供的方法,返回了具体数据。

在实际的 Vue 逻辑中,比如 button 通过 click 调用下面逻辑即可

<script setup>  
  import { getAppId } from '@/uni_modules/invoke-utils'  
  function openTest() {  
    const res = getAppId()  
    console.log('获取应用ID:', res)  
  }  
</script>

调用此方法,观察控制台,可以看到包名。这说明工具调用成功。

接入 har 依赖

接入 har 依赖。如何制作 har 依赖,在下面单独说明。

假定已经得到了一个 localLib.har 文件。放置 har 文件在 uts 插件内,比如在 index.uts 的同级目录, libs/localLib.har 路径。

修改 config.json,配置相对路径。

{  
  "dependencies": {  
    "locallib": "./libs/localLib.har"  
  }  
}

在 index.uts 中引用和导出。

注意:这里提到的 locaLib 名称不是随便起的,类似于 npm 的 packages.json 依赖, "vue":"3.4" ,这里的 vue 要和实际安装的包名要一致。

包名和导出的内容如何知晓?把 har 包改成 zip 并解压,得到产物。有两个文件需要注意

  • oh-package.json 里面的 name 是包的名字,可以复制出来,不要随意写成 localib 以实际为准
  • 包导出的内容可以在 index.d.ts 中查看,看具体 export 的内容是什么,不要随意写成 add 以实际为准
import { add } from 'locallib'  

export const addFun = (a:number, b:number):number => {  
  return add(a, b)  
}

在页面中引入这个 uts 插件并使用即可。

<script setup>  
  import { addFun} from '@/uni_modules/otto-thirdhar'  
  function openTest() {  
    console.log(addFun(3,4))  
  }  
</script>

执行这个方法,顺利的话可以看到控制台打印数字 7.

注意事项:

  • 引入 har 可能有版本兼容性要求,可在 harmony-configs/build-profile.json5 内修改 compatibleSdkVersion
  • har 可能构建内容有误,实际运行不正常,可在原生工程项目中自测,排除 har 文件内部问题

如何构建 har 模块

在 DevEco 中打开一个项目,选择 文件 - 新建 - 模块 - Static Library,定义模块名选择创建。

在创建的文件中选择 index.ets 找到模块入口,可导出组件、方法。

在 DevEco 中选择 构建(在重构和运行中间) - 构建模块(第一个选项),等待编译结束,观察模块目录中的 build/default/outputs/default 找到 har 文件。

har 文件本身是一个压缩文件,可自行拆包了解结构,内部存在方法 d.ets 等文件。

继续阅读 »

鸿蒙 UTS 插件使用三方依赖

在鸿蒙开发中市场需要使用三方依赖,可能是三方包,可能是一个本地 har 包,这里介绍如何接入。

接入三方依赖

这里举例 https://ohpm.openharmony.cn/ 最受欢迎的三方库 @pura/harmony-utils 。这个库,提供了众多方法,可以加速功能开发。

更新:为了辅助说明,这里提供了 uts 源码,可对比参考 https://ext.dcloud.net.cn/plugin?id=24849

harmony-utils 一款功能丰富且极易上手的HarmonyOS工具库,借助众多实用工具类,致力于助力开发者迅速构建鸿蒙应用。其封装的工具涵盖了APP、设备、屏幕、授权、通知、线程间通信、弹框、吐司、生物认证、用户首选项、拍照、相册、扫码、文件、日志,异常捕获、字符、字符串、数字、集合、日期、随机、base64、加密、解密、JSON等一系列的功能和操作,能够满足各种不同的开发需求。

从原生鸿蒙角度开发,使用这个工具库,需要两个步骤

  • 安装依赖
  • 调用方法

在 UTS 中使用这个工具库,需要三个步骤

  • 创建 UTS 插件
  • 引用依赖
  • 调用方法

下面介绍具体步骤

假设我们希望通过 harmony-utils 获取当前的工具包名。调用的是 AppUtil.getBundleName

1. 创建 UTS 插件

在 HBuilderX 中操作。首先创建 uni_modules 功能,如果当前目录中没有对应文件夹,可在项目文件夹节点单击右键选择 新建 uni_modules 目录

这会创建 uni_moduels 文件夹,在这个文件夹上单击右键。

在新窗口中选择 UTS 插件-API 插件,点击创建。

假定插件的 ID 是 invoke-utils,找到这个文件夹,观察是否存在对应的文件,如果没有就创建 uni_modules/invoke-utils/utssdk/app-harmony/index.uts

这样插件就创建好了。这部分可参考 UTS 插件介绍原生混编 做进一步了解。

2. 安装依赖

创建 uni_modules/invoke-utils/utssdk/app-harmony/config.json,添加依赖。可参考文档 配置uts插件依赖

鸿蒙的库管理工具是ohpm。类似于js的npm,Android的仓储。鸿蒙的三方sdk封装文件为.har,类似于Android的.aar
uts插件的utssdk/app-harmony/config.json文件内可以配置依赖使用鸿蒙的三方库

代码填写下面方案:

{  
   "dependencies": {  
     "@pura/harmony-utils":"1.3.6"  
   }  
}

接下来准备使用依赖功能。

3. 调用方法

在 index.uts 中添加下面逻辑

import { AppUtil } from '@pura/harmony-utils'  

UTSHarmony.onAppAbilityCreate(() => {  
  const abilityCtx = UTSHarmony.getUIAbilityContext();  
  const ctx = abilityCtx  
  AppUtil.init(ctx);  
})  

export const getAppId = () => {  
  let bundleName = AppUtil.getBundleName();  
  return bundleName  
}

在原始文档中要求在 AbilityCreate 中初始化,这里可以使用 UTSHarmony.onAppAbilityCreate 来实现初始化。

和 TS 代码类似,调用了提供的方法,返回了具体数据。

在实际的 Vue 逻辑中,比如 button 通过 click 调用下面逻辑即可

<script setup>  
  import { getAppId } from '@/uni_modules/invoke-utils'  
  function openTest() {  
    const res = getAppId()  
    console.log('获取应用ID:', res)  
  }  
</script>

调用此方法,观察控制台,可以看到包名。这说明工具调用成功。

接入 har 依赖

接入 har 依赖。如何制作 har 依赖,在下面单独说明。

假定已经得到了一个 localLib.har 文件。放置 har 文件在 uts 插件内,比如在 index.uts 的同级目录, libs/localLib.har 路径。

修改 config.json,配置相对路径。

{  
  "dependencies": {  
    "locallib": "./libs/localLib.har"  
  }  
}

在 index.uts 中引用和导出。

注意:这里提到的 locaLib 名称不是随便起的,类似于 npm 的 packages.json 依赖, "vue":"3.4" ,这里的 vue 要和实际安装的包名要一致。

包名和导出的内容如何知晓?把 har 包改成 zip 并解压,得到产物。有两个文件需要注意

  • oh-package.json 里面的 name 是包的名字,可以复制出来,不要随意写成 localib 以实际为准
  • 包导出的内容可以在 index.d.ts 中查看,看具体 export 的内容是什么,不要随意写成 add 以实际为准
import { add } from 'locallib'  

export const addFun = (a:number, b:number):number => {  
  return add(a, b)  
}

在页面中引入这个 uts 插件并使用即可。

<script setup>  
  import { addFun} from '@/uni_modules/otto-thirdhar'  
  function openTest() {  
    console.log(addFun(3,4))  
  }  
</script>

执行这个方法,顺利的话可以看到控制台打印数字 7.

注意事项:

  • 引入 har 可能有版本兼容性要求,可在 harmony-configs/build-profile.json5 内修改 compatibleSdkVersion
  • har 可能构建内容有误,实际运行不正常,可在原生工程项目中自测,排除 har 文件内部问题

如何构建 har 模块

在 DevEco 中打开一个项目,选择 文件 - 新建 - 模块 - Static Library,定义模块名选择创建。

在创建的文件中选择 index.ets 找到模块入口,可导出组件、方法。

在 DevEco 中选择 构建(在重构和运行中间) - 构建模块(第一个选项),等待编译结束,观察模块目录中的 build/default/outputs/default 找到 har 文件。

har 文件本身是一个压缩文件,可自行拆包了解结构,内部存在方法 d.ets 等文件。

收起阅读 »