HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

香港开发者无法完成手机号验证

开发者账号

尊敬的 DCloud 技术支持团队,

你们好!

我是一位来自香港的开发者,正在使用你们的 uni-app 框架进行开发,非常感谢你们提供优秀的工具。
目前,我在进行开发者账户的 手机号验证 时遇到了一个阻碍。在“验证手机号”的页面中,系统只允许输入11位数字的手机号码,并且 没有提供国家/地区代码的下拉选择框。
我的香港手机号码格式为 +852 XXXX XXXX(共8位),无法在目前仅支持+86号码的输入框中完成验证。当我尝试输入号码时,系统提示“您填写的手机号码不正确”。

作为一名国际开发者,我非常希望完成手机验证以使用账户的全部功能并保障账户安全。
因此,我想请问:

  1. 是否有针对香港或国际开发者的特殊验证流程?
  2. 能否在验证页面添加国家代码(例如+852)的选择功能?
  3. 或者,能否请你们协助手动为我的账户完成手机号验证?
    感谢你们的时间和帮助!期待你们的回复。
    祝好,
继续阅读 »

尊敬的 DCloud 技术支持团队,

你们好!

我是一位来自香港的开发者,正在使用你们的 uni-app 框架进行开发,非常感谢你们提供优秀的工具。
目前,我在进行开发者账户的 手机号验证 时遇到了一个阻碍。在“验证手机号”的页面中,系统只允许输入11位数字的手机号码,并且 没有提供国家/地区代码的下拉选择框。
我的香港手机号码格式为 +852 XXXX XXXX(共8位),无法在目前仅支持+86号码的输入框中完成验证。当我尝试输入号码时,系统提示“您填写的手机号码不正确”。

作为一名国际开发者,我非常希望完成手机验证以使用账户的全部功能并保障账户安全。
因此,我想请问:

  1. 是否有针对香港或国际开发者的特殊验证流程?
  2. 能否在验证页面添加国家代码(例如+852)的选择功能?
  3. 或者,能否请你们协助手动为我的账户完成手机号验证?
    感谢你们的时间和帮助!期待你们的回复。
    祝好,
收起阅读 »

1024星光不负,码向未来——以 uni-app 筑梦鸿蒙生态我的uni-app鸿蒙开发之旅

鸿蒙6 uni-app鸿蒙开发实践 鸿蒙征文

写在前面的话
当 1024 程序员节的代码星光点亮行业夜空,DCloud 发起的 “星光不负,码向未来” 鸿蒙主题征文活动,恰似一座连接开发者与生态未来的桥梁。作为一名深耕跨端开发五年的技术人,我始终感恩 DCloud 为我们搭建了如此优质的交流舞台,更庆幸能借 uni-app 技术栈,深度参与到鸿蒙生态的建设浪潮中。今天,我想分享一款基于 uni-app 开发的鸿蒙元服务项目实战经历,既是对这段开发旅程的复盘,也是对鸿蒙生态璀璨前景的致敬。

一、项目缘起:从痛点出发的技术选型

我们团队今年承接了一款本地生活类鸿蒙元服务的开发需求,核心目标是实现 “即时触达、轻量化交互、跨设备兼容”—— 用户无需安装 App,通过鸿蒙桌面卡片即可快速查看周边商户、领取优惠券并完成预约。在技术选型阶段,我们对比了多种开发方案:原生开发虽能深度适配鸿蒙特性,但跨设备兼容成本高;其他跨端框架对鸿蒙元服务的支持尚不完善。最终,uni-app 凭借其对鸿蒙生态的深度适配、与 Vue 语法的无缝衔接,以及 “一次开发、多端部署” 的核心优势,成为了我们的最优解。
更让我们惊喜的是,DCloud 提供的 uni-app 鸿蒙插件市场(很多鸿蒙插件给我们提供极大便利,开发中也得到鸿蒙插件作者@An_king 帮助)、官方文档与社区支持,为项目启动扫清了初期障碍。从基础配置到进阶功能,这让我们对项目落地充满了信心。

二、核心实践:鸿蒙能力与 uni-app 的深度融合

2.1 鸿蒙云开发的集成与落地

项目初期,我们面临着 “数据实时同步” 与 “服务器部署成本” 的双重挑战。鸿蒙云开发提供的云数据库、云函数等能力,恰好契合我们的需求。通过 uni-app 的扩展 API,我们实现了三大关键突破:
数据双向同步优化:利用鸿蒙云数据库的实时监听能力,结合 uni-app 的响应式数据绑定,实现了商户库存、优惠券余量与用户端的毫秒级同步。此前我们曾遭遇 “本地缓存与云端数据不一致” 的问题,通过自定义云函数,在数据更新时触发云端校验与本地缓存刷新,成功解决了这一痛点。
云函数的轻量化部署:将用户登录验证、优惠券核销等核心业务逻辑部署在鸿蒙云函数中,通过 uni-app 的云对象方法配合调用。这一方案不仅减少了客户端代码体积,更通过云端扩容能力,全面提升了App的性能。
权限申请的优雅处理:鸿蒙系统对用户权限的管控极为严格,我们借助 uni-app 的权限申请封装 API,结合鸿蒙元服务的特性,设计了 “按需申请、分步授权” 的交互流程。例如,在用户首次点击 “获取周边商户” 时才申请定位权限,并通过弹窗清晰说明权限用途,将授权通过率提升了 40%。

2.2 预加载能力的创新应用

元服务的 “秒开体验” 是项目的核心竞争力之一,而uniapp的预加载能力成为了关键突破口。我们通过 uni-app 的app.vue生命周期钩子,结合封装鸿蒙的原生组件,实现了精准化对接预加载策略:
针对高频访问的 “商户列表页”,在元服务启动前预加载核心数据与页面组件,将首屏加载时间从 1.2秒压缩至 0.4秒;
基于用户行为分析,对 “优惠券中心” 等次级页面实现 “智能预加载”—— 当用户浏览商户详情超过 3 秒时,后台异步预加载优惠券数据,既保证了响应速度,又避免了资源浪费。
这一过程中,我们曾遇到 “预加载资源占用过高导致卡顿” 的问题。通过 DCloud 社区的技术交流,我们借鉴了其他开发者分享的 “超级下拉列表“和“资源优先级排序” 方案,对预加载内容按重要性分级,优先加载文本数据,延迟加载图片资源,最终完美解决了性能瓶颈。

2.3 全流程落地:从开发到完成的实践复盘

需求与适配阶段:我们充分利用 uni-app 的多端适配能力,针对安卓、苹果、各家小程序、鸿蒙手机、平板、智慧屏等不同设备,通过uni.getSystemInfoSync()获取设备参数,实现了页面布局的自适应调整。特别是针对鸿蒙元服务的卡片尺寸限制,设计了 “核心信息精简展示、点击展开详情” 的交互模式。
调试与测试阶段:借助鸿蒙云测试平台与 uni-app 的真机调试功能,我们完成了多机型兼容性测试。DCloud 提供的 uni-app 调试工具能够精准定位鸿蒙特有的语法兼容问题,让我们在短时间内修复了 “组件生命周期触发异常”“API 调用权限不足” 等关键 Bug。

三、生态感悟:感恩同行,共赴未来

回望这段开发旅程,我们既惊叹于鸿蒙生态的高速发展,更感激 DCloud 为开发者提供的坚实支撑。从最初对鸿蒙技术的懵懂探索,到如今能熟练运用 uni-app 实现复杂业务场景,每一步成长都离不开 DCloud 官方的技术赋能与社区伙伴的经验分享。uni-app 就像一座桥梁,让我们这些跨端开发者能够低门槛地融入鸿蒙生态,用熟悉的技术栈创造出符合新时代需求的应用产品。
如今,HarmonyOS 6 的到来开启了生态发展的新篇章,流畅的性能、AI 原生能力等创新特性,为开发者提供了更广阔的创新空间。作为生态建设的参与者,我们深刻意识到,每一行代码都是生态的基石,每一次分享都是技术的传承。DCloud 发起的本次征文活动,不仅为我们提供了展示技术成果的舞台,更促进了鸿蒙生态开发者之间的思想碰撞,这种技术共享的氛围,正是行业进步的核心动力。

四、展望未来:以代码为笔,书写生态新篇

星光不负赶路人,时光不负奋斗者。在未来的开发道路上,我们团队将继续深耕 uni-app 与鸿蒙生态的融合创新,计划基于鸿蒙近场能力开发 “设备间优惠券共享” 功能,进一步拓展元服务的应用场景。同时,我们也将积极参与 DCloud 社区的技术分享,把项目中积累的经验转化为帮助他人的力量,就像曾经那些帮助过我们的开发者一样。
我们坚信,随着鸿蒙生态的持续完善与 DCloud 的不断赋能,会有更多开发者加入这场技术革新的浪潮。当无数开发者的代码星光汇聚,必将照亮鸿蒙生态的未来之路。最后,再次感谢 DCloud 举办本次征文活动,感谢每一位为生态建设默默付出的技术人。愿我们以代码为翼,与鸿蒙共成长,在数字时代的浪潮中,书写属于开发者的璀璨篇章!

继续阅读 »

写在前面的话
当 1024 程序员节的代码星光点亮行业夜空,DCloud 发起的 “星光不负,码向未来” 鸿蒙主题征文活动,恰似一座连接开发者与生态未来的桥梁。作为一名深耕跨端开发五年的技术人,我始终感恩 DCloud 为我们搭建了如此优质的交流舞台,更庆幸能借 uni-app 技术栈,深度参与到鸿蒙生态的建设浪潮中。今天,我想分享一款基于 uni-app 开发的鸿蒙元服务项目实战经历,既是对这段开发旅程的复盘,也是对鸿蒙生态璀璨前景的致敬。

一、项目缘起:从痛点出发的技术选型

我们团队今年承接了一款本地生活类鸿蒙元服务的开发需求,核心目标是实现 “即时触达、轻量化交互、跨设备兼容”—— 用户无需安装 App,通过鸿蒙桌面卡片即可快速查看周边商户、领取优惠券并完成预约。在技术选型阶段,我们对比了多种开发方案:原生开发虽能深度适配鸿蒙特性,但跨设备兼容成本高;其他跨端框架对鸿蒙元服务的支持尚不完善。最终,uni-app 凭借其对鸿蒙生态的深度适配、与 Vue 语法的无缝衔接,以及 “一次开发、多端部署” 的核心优势,成为了我们的最优解。
更让我们惊喜的是,DCloud 提供的 uni-app 鸿蒙插件市场(很多鸿蒙插件给我们提供极大便利,开发中也得到鸿蒙插件作者@An_king 帮助)、官方文档与社区支持,为项目启动扫清了初期障碍。从基础配置到进阶功能,这让我们对项目落地充满了信心。

二、核心实践:鸿蒙能力与 uni-app 的深度融合

2.1 鸿蒙云开发的集成与落地

项目初期,我们面临着 “数据实时同步” 与 “服务器部署成本” 的双重挑战。鸿蒙云开发提供的云数据库、云函数等能力,恰好契合我们的需求。通过 uni-app 的扩展 API,我们实现了三大关键突破:
数据双向同步优化:利用鸿蒙云数据库的实时监听能力,结合 uni-app 的响应式数据绑定,实现了商户库存、优惠券余量与用户端的毫秒级同步。此前我们曾遭遇 “本地缓存与云端数据不一致” 的问题,通过自定义云函数,在数据更新时触发云端校验与本地缓存刷新,成功解决了这一痛点。
云函数的轻量化部署:将用户登录验证、优惠券核销等核心业务逻辑部署在鸿蒙云函数中,通过 uni-app 的云对象方法配合调用。这一方案不仅减少了客户端代码体积,更通过云端扩容能力,全面提升了App的性能。
权限申请的优雅处理:鸿蒙系统对用户权限的管控极为严格,我们借助 uni-app 的权限申请封装 API,结合鸿蒙元服务的特性,设计了 “按需申请、分步授权” 的交互流程。例如,在用户首次点击 “获取周边商户” 时才申请定位权限,并通过弹窗清晰说明权限用途,将授权通过率提升了 40%。

2.2 预加载能力的创新应用

元服务的 “秒开体验” 是项目的核心竞争力之一,而uniapp的预加载能力成为了关键突破口。我们通过 uni-app 的app.vue生命周期钩子,结合封装鸿蒙的原生组件,实现了精准化对接预加载策略:
针对高频访问的 “商户列表页”,在元服务启动前预加载核心数据与页面组件,将首屏加载时间从 1.2秒压缩至 0.4秒;
基于用户行为分析,对 “优惠券中心” 等次级页面实现 “智能预加载”—— 当用户浏览商户详情超过 3 秒时,后台异步预加载优惠券数据,既保证了响应速度,又避免了资源浪费。
这一过程中,我们曾遇到 “预加载资源占用过高导致卡顿” 的问题。通过 DCloud 社区的技术交流,我们借鉴了其他开发者分享的 “超级下拉列表“和“资源优先级排序” 方案,对预加载内容按重要性分级,优先加载文本数据,延迟加载图片资源,最终完美解决了性能瓶颈。

2.3 全流程落地:从开发到完成的实践复盘

需求与适配阶段:我们充分利用 uni-app 的多端适配能力,针对安卓、苹果、各家小程序、鸿蒙手机、平板、智慧屏等不同设备,通过uni.getSystemInfoSync()获取设备参数,实现了页面布局的自适应调整。特别是针对鸿蒙元服务的卡片尺寸限制,设计了 “核心信息精简展示、点击展开详情” 的交互模式。
调试与测试阶段:借助鸿蒙云测试平台与 uni-app 的真机调试功能,我们完成了多机型兼容性测试。DCloud 提供的 uni-app 调试工具能够精准定位鸿蒙特有的语法兼容问题,让我们在短时间内修复了 “组件生命周期触发异常”“API 调用权限不足” 等关键 Bug。

三、生态感悟:感恩同行,共赴未来

回望这段开发旅程,我们既惊叹于鸿蒙生态的高速发展,更感激 DCloud 为开发者提供的坚实支撑。从最初对鸿蒙技术的懵懂探索,到如今能熟练运用 uni-app 实现复杂业务场景,每一步成长都离不开 DCloud 官方的技术赋能与社区伙伴的经验分享。uni-app 就像一座桥梁,让我们这些跨端开发者能够低门槛地融入鸿蒙生态,用熟悉的技术栈创造出符合新时代需求的应用产品。
如今,HarmonyOS 6 的到来开启了生态发展的新篇章,流畅的性能、AI 原生能力等创新特性,为开发者提供了更广阔的创新空间。作为生态建设的参与者,我们深刻意识到,每一行代码都是生态的基石,每一次分享都是技术的传承。DCloud 发起的本次征文活动,不仅为我们提供了展示技术成果的舞台,更促进了鸿蒙生态开发者之间的思想碰撞,这种技术共享的氛围,正是行业进步的核心动力。

四、展望未来:以代码为笔,书写生态新篇

星光不负赶路人,时光不负奋斗者。在未来的开发道路上,我们团队将继续深耕 uni-app 与鸿蒙生态的融合创新,计划基于鸿蒙近场能力开发 “设备间优惠券共享” 功能,进一步拓展元服务的应用场景。同时,我们也将积极参与 DCloud 社区的技术分享,把项目中积累的经验转化为帮助他人的力量,就像曾经那些帮助过我们的开发者一样。
我们坚信,随着鸿蒙生态的持续完善与 DCloud 的不断赋能,会有更多开发者加入这场技术革新的浪潮。当无数开发者的代码星光汇聚,必将照亮鸿蒙生态的未来之路。最后,再次感谢 DCloud 举办本次征文活动,感谢每一位为生态建设默默付出的技术人。愿我们以代码为翼,与鸿蒙共成长,在数字时代的浪潮中,书写属于开发者的璀璨篇章!

收起阅读 »

Uniapp 的鸿蒙 next 应用中隐藏和显示系统状态栏

鸿蒙征文 鸿蒙next

本示例至少需要在 HbuilderX 4.61 运行

在 uniapp 开发鸿蒙应用中,通过 UTS 插件,可以调用许多系统原生的 API,这里给出一个小功能:隐藏和显示系统状态栏

原始的界面效果:

隐藏系统状态栏之后的效果:

这个示例的参考文档有:

  • 鸿蒙官方文档: https://developer.huawei.com/consumer/cn/doc/harmonyos-faqs/faqs-arkui-193
  • UTS 插件: https://doc.dcloud.net.cn/uni-app-x/plugin/uts-plugin.html
  • UTSHarmony 的使用: https://doc.dcloud.net.cn/uni-app-x/uts/utsharmony.html

核心页面代码:

<template>  
    <view>  
        <button @click="show">显示原生状态栏</button>  
        <button @click="hide">隐藏原生状态栏</button>  
    </view>  
</template>  
<script>  
    import { showStatusBar, hideStatusBar } from '@/uni_modules/harmony-statusbar';  
    export default {  
        data() {  
            return {  
                message: 'Hello, World!'  
            }  
        },  
        methods: {  
            show() {  
                showStatusBar()  
            },  
            hide() {  
                hideStatusBar()  
            }  
        }  
    }  
</script>  
<style scoped>  
</style>

核心UTS插件代码:

import window from '@ohos.window';  

let _window : window.Window;  

UTSHarmony.onAppAbilityWindowStageCreate((windowStage : window.WindowStage) => {  
    _window = windowStage.getMainWindowSync()  
})  

export const hideStatusBar = () => {  
    _window.setWindowSystemBarEnable([])  
}  

export const showStatusBar = () => {  
    _window.setWindowSystemBarEnable(['status', 'navigation'])  
}

示例工程:

继续阅读 »

本示例至少需要在 HbuilderX 4.61 运行

在 uniapp 开发鸿蒙应用中,通过 UTS 插件,可以调用许多系统原生的 API,这里给出一个小功能:隐藏和显示系统状态栏

原始的界面效果:

隐藏系统状态栏之后的效果:

这个示例的参考文档有:

  • 鸿蒙官方文档: https://developer.huawei.com/consumer/cn/doc/harmonyos-faqs/faqs-arkui-193
  • UTS 插件: https://doc.dcloud.net.cn/uni-app-x/plugin/uts-plugin.html
  • UTSHarmony 的使用: https://doc.dcloud.net.cn/uni-app-x/uts/utsharmony.html

核心页面代码:

<template>  
    <view>  
        <button @click="show">显示原生状态栏</button>  
        <button @click="hide">隐藏原生状态栏</button>  
    </view>  
</template>  
<script>  
    import { showStatusBar, hideStatusBar } from '@/uni_modules/harmony-statusbar';  
    export default {  
        data() {  
            return {  
                message: 'Hello, World!'  
            }  
        },  
        methods: {  
            show() {  
                showStatusBar()  
            },  
            hide() {  
                hideStatusBar()  
            }  
        }  
    }  
</script>  
<style scoped>  
</style>

核心UTS插件代码:

import window from '@ohos.window';  

let _window : window.Window;  

UTSHarmony.onAppAbilityWindowStageCreate((windowStage : window.WindowStage) => {  
    _window = windowStage.getMainWindowSync()  
})  

export const hideStatusBar = () => {  
    _window.setWindowSystemBarEnable([])  
}  

export const showStatusBar = () => {  
    _window.setWindowSystemBarEnable(['status', 'navigation'])  
}

示例工程:

收起阅读 »

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

鸿蒙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

收起阅读 »