HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

经验分享 鸿蒙通过 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 等文件。

收起阅读 »

微信小程序主包体积又超了?试试这个插件

体积 体积优化 微信小程序

你是否在使用UniApp开发微信小程序时,遇到微信小程序主包超出2M,导致无法上传的问题?

题主自己所在团队曾被这个问题反复困扰,针对这个问题,我们尝试过不同的办法,例如:

  1. 对图片下手腾大小:在编译阶段,对所有图片资源路径都替换成远端图片地址,主包内基本不存在图片资源。
  2. 对项目进行了重构:1️⃣ 删除已下线业务代码;2️⃣ 合理拆分子包,将非首屏页面,或者业务层级较深的页面根据流程类别拆成独立子包;3️⃣ 主包只包含首页;
  3. 尽量使用支持模块化的第三方库,规范import方式,以确保使用时只引用真实使用到的模块,例如lodash-es替换lodash,引入时 import {find} from 'lodash-es',而不是import * as _ from 'loadash-es'

以上办法都有不错的效果,但随着业务功能的迭代,主包还是超了。 T_T

于是我们对主包体积进行分析,发现对主包影响最大的是公共组件,也即是components目录底下的组件,他们被多个子包共用,因此它们被放在主包,原理上是没有问题的(众所周知子包能应用主包的东西,但不能应用其他子包的东西)。

但是项目临上线才突然出现超包问题,时间紧急,我们好几次都是通过在UniApp项目里,将一些公共组件,从主包目录挪到子包目录里,并修正组件的引用,进而使得编译出来的小程序项目的主包目录中的组件减少,从而体积变小。

这种临时做法收效很快,但缺点也很明显:一方面,对于单次组件迁移操作,相对独立的组件比较好迁移,测试回归范围也不大,但不少组件之间存在相互依赖的关系,迁移的成本和风险会大大增加;另一方面,组件被迁移至多个子包后便有了多份副本,增加了长期维护成本和风险。另外,此举治标不治本,不知道后面还要踩多少次坑。

那么,有没有两全之法?答案当然是有的!

如果我们公共组件迁移的操作,不是基于UniApp项目工程本身,而是对编译出来微信小程序工程动刀子,问题就迎刃而解了。
为了实现这个目的,我们写了一个十分易用的vite插件,只需要引入插件,正常build一下,组件的迁移就自动完成了。

如果你也遇到了类似的问题,可以直接把插件down下来放到自己的项目里使用。

传送门:https://github.com/ohyeahhh/wechatMiniProgramPackageOptimizer

注:这个插件是基于我们团队的项目情况写的,具备一定的通用性,也存在一些限制(具体说明见上述链接readme),不完善之处欢迎大家一起指正、讨论、改善。笔芯。

^_^ 一起加油共同进步。

继续阅读 »

你是否在使用UniApp开发微信小程序时,遇到微信小程序主包超出2M,导致无法上传的问题?

题主自己所在团队曾被这个问题反复困扰,针对这个问题,我们尝试过不同的办法,例如:

  1. 对图片下手腾大小:在编译阶段,对所有图片资源路径都替换成远端图片地址,主包内基本不存在图片资源。
  2. 对项目进行了重构:1️⃣ 删除已下线业务代码;2️⃣ 合理拆分子包,将非首屏页面,或者业务层级较深的页面根据流程类别拆成独立子包;3️⃣ 主包只包含首页;
  3. 尽量使用支持模块化的第三方库,规范import方式,以确保使用时只引用真实使用到的模块,例如lodash-es替换lodash,引入时 import {find} from 'lodash-es',而不是import * as _ from 'loadash-es'

以上办法都有不错的效果,但随着业务功能的迭代,主包还是超了。 T_T

于是我们对主包体积进行分析,发现对主包影响最大的是公共组件,也即是components目录底下的组件,他们被多个子包共用,因此它们被放在主包,原理上是没有问题的(众所周知子包能应用主包的东西,但不能应用其他子包的东西)。

但是项目临上线才突然出现超包问题,时间紧急,我们好几次都是通过在UniApp项目里,将一些公共组件,从主包目录挪到子包目录里,并修正组件的引用,进而使得编译出来的小程序项目的主包目录中的组件减少,从而体积变小。

这种临时做法收效很快,但缺点也很明显:一方面,对于单次组件迁移操作,相对独立的组件比较好迁移,测试回归范围也不大,但不少组件之间存在相互依赖的关系,迁移的成本和风险会大大增加;另一方面,组件被迁移至多个子包后便有了多份副本,增加了长期维护成本和风险。另外,此举治标不治本,不知道后面还要踩多少次坑。

那么,有没有两全之法?答案当然是有的!

如果我们公共组件迁移的操作,不是基于UniApp项目工程本身,而是对编译出来微信小程序工程动刀子,问题就迎刃而解了。
为了实现这个目的,我们写了一个十分易用的vite插件,只需要引入插件,正常build一下,组件的迁移就自动完成了。

如果你也遇到了类似的问题,可以直接把插件down下来放到自己的项目里使用。

传送门:https://github.com/ohyeahhh/wechatMiniProgramPackageOptimizer

注:这个插件是基于我们团队的项目情况写的,具备一定的通用性,也存在一些限制(具体说明见上述链接readme),不完善之处欢迎大家一起指正、讨论、改善。笔芯。

^_^ 一起加油共同进步。

收起阅读 »

【鸿蒙征文】星光不负,码向未来!分享你的uni-app鸿蒙开发实践,赢取精美好礼!

鸿蒙征文 公告

各位DCloud的开发者们:

在1024程序员节即将到来之际,DCloud 诚挚邀请您参与本次 “星光不负,码向未来” 鸿蒙主题征文活动。

我们特别希望看到基于 uni-app 和 uni-app x 在鸿蒙生态中的开发实践、经验总结与创新思考。无论是开发鸿蒙App、元服务,还是打造兼容鸿蒙的插件/UI库,你的每一行代码和每一次分享,都将是鸿蒙生态建设中闪耀的星光。

活动主题:星光不负,码向未来

活动时间:

  • 征文招募期: 2025年10月20日 - 11月20日
  • 文章展示期: 从2025年10月24日起,优秀投稿将陆续在专题页展示
  • 文章评审期: 2025年11月21日 - 11月30日
  • 结果公示期: 2025年12月1日之后

征文方向

我们特别关注以下方向与uni-app结合的实践

请选择以下任一方向,分享你的鸿蒙开发故事:

方向一 成长纪实

分享你从零开始,使用 uni-app 开发鸿蒙 App 或鸿蒙元服务 的学习之路。可以是从基础语法到核心概念的入门笔记,也可以是封装第一个兼容鸿蒙的组件、插件的经历,更欢迎你分享在学习、适配、调试过程中的心得、踩坑记录与解决方案。

方向二 案例实战

鸿蒙能力集成:分享你在结合 uni-app 的鸿蒙项目中,集成并应用鸿蒙开放能力(如云开发、云测试、预加载、Applinking、APMS、近场能力、应用分析、HarmonyOS SDK等)解决实际问题的过程、效果与心得。真实经历,尤为珍贵。

项目全流程落地:详细阐述基于 uni-app/uni-app x 的鸿蒙项目从需求制定、技术选型、技术适配(如元服务特性应用)、到最终上架、获取用户反馈的全流程经验。基于uni-app、uni-app x开发的鸿蒙应用已经有上千款,非常欢迎这些开发者分享自己的开发、上架经验。
方向二的文章,我们还设计了特别奖品,见下文。

方向三 参赛心得

如果你近两年参加过HarmonyOS创新赛、极客松等大赛,欢迎分享你的参赛作品案例与技术心得。请侧重讲解如何利用 uni-app 技术栈,结合HarmonyOS的新技术特性(特别是HarmonyOS NEXT)完成作品,并解说其中的创新点与技术难点。

奖项设置

我们为优秀的你准备了丰厚的礼品作为奖励:

一等奖(5名)
如上奖品为二选一,随机发放;

  • 华为手环7-NFC版
  • 华为智能水杯450ml + 露营灯-无级调光-太阳能+Type-C充电 组合

二等奖(15名)
露营灯-无级调光-太阳能+Type-C充电

三等奖(30名)
HUAWEI 无线蓝牙鼠标-双模办公-灰色 (价值99元)

方向二特别奖(10名)
针对【方向二·案例实战】的优质投稿,额外设立10份 华为手环 9 NFC版作为激励!

惊喜提示:积极参与、文章优质的开发者,将有机会获得 DCloud官方推荐 和 HarmonyOS开发者社区联合曝光,让你的技术影响力更进一步!

参与方式

在 DCloud 问答社区 发布新帖,选择【分享经验】,发布博客文章,并给文章添加【鸿蒙征文】的话题,无需单独报名,社区会通过话题自动筛选征文文章。

评审标准

社区将根据以下维度进行综合评选:

  1. 内容质量:内容详实、逻辑清晰、案例完整。
  2. 技术价值:技术深度、创新性、对uni-app与鸿蒙结合点的挖掘。
  3. 影响力与实用性:对其他开发者的借鉴、启发和帮助程度;如文章的阅读数、点赞数都将作为评选参考指标,欢迎大家将自己的文章分享到微博、微信等,扩散自己的文章影响力。
  4. 原创与真实性:必须为原创内容,分享真实开发经验。

行动起来吧!

技术之路,因分享而璀璨;鸿蒙生态,因你而精彩。
这不仅是赢取奖品的机会,更是向整个开发者社区展示你技术风采的舞台。

立即执笔,分享你的 uni-app 鸿蒙开发实践,与我们一起,码向未来!

============

目前已经有不少优秀的征文发布,大家可以移步鸿蒙征文系列阅读欣赏。

继续阅读 »

各位DCloud的开发者们:

在1024程序员节即将到来之际,DCloud 诚挚邀请您参与本次 “星光不负,码向未来” 鸿蒙主题征文活动。

我们特别希望看到基于 uni-app 和 uni-app x 在鸿蒙生态中的开发实践、经验总结与创新思考。无论是开发鸿蒙App、元服务,还是打造兼容鸿蒙的插件/UI库,你的每一行代码和每一次分享,都将是鸿蒙生态建设中闪耀的星光。

活动主题:星光不负,码向未来

活动时间:

  • 征文招募期: 2025年10月20日 - 11月20日
  • 文章展示期: 从2025年10月24日起,优秀投稿将陆续在专题页展示
  • 文章评审期: 2025年11月21日 - 11月30日
  • 结果公示期: 2025年12月1日之后

征文方向

我们特别关注以下方向与uni-app结合的实践

请选择以下任一方向,分享你的鸿蒙开发故事:

方向一 成长纪实

分享你从零开始,使用 uni-app 开发鸿蒙 App 或鸿蒙元服务 的学习之路。可以是从基础语法到核心概念的入门笔记,也可以是封装第一个兼容鸿蒙的组件、插件的经历,更欢迎你分享在学习、适配、调试过程中的心得、踩坑记录与解决方案。

方向二 案例实战

鸿蒙能力集成:分享你在结合 uni-app 的鸿蒙项目中,集成并应用鸿蒙开放能力(如云开发、云测试、预加载、Applinking、APMS、近场能力、应用分析、HarmonyOS SDK等)解决实际问题的过程、效果与心得。真实经历,尤为珍贵。

项目全流程落地:详细阐述基于 uni-app/uni-app x 的鸿蒙项目从需求制定、技术选型、技术适配(如元服务特性应用)、到最终上架、获取用户反馈的全流程经验。基于uni-app、uni-app x开发的鸿蒙应用已经有上千款,非常欢迎这些开发者分享自己的开发、上架经验。
方向二的文章,我们还设计了特别奖品,见下文。

方向三 参赛心得

如果你近两年参加过HarmonyOS创新赛、极客松等大赛,欢迎分享你的参赛作品案例与技术心得。请侧重讲解如何利用 uni-app 技术栈,结合HarmonyOS的新技术特性(特别是HarmonyOS NEXT)完成作品,并解说其中的创新点与技术难点。

奖项设置

我们为优秀的你准备了丰厚的礼品作为奖励:

一等奖(5名)
如上奖品为二选一,随机发放;

  • 华为手环7-NFC版
  • 华为智能水杯450ml + 露营灯-无级调光-太阳能+Type-C充电 组合

二等奖(15名)
露营灯-无级调光-太阳能+Type-C充电

三等奖(30名)
HUAWEI 无线蓝牙鼠标-双模办公-灰色 (价值99元)

方向二特别奖(10名)
针对【方向二·案例实战】的优质投稿,额外设立10份 华为手环 9 NFC版作为激励!

惊喜提示:积极参与、文章优质的开发者,将有机会获得 DCloud官方推荐 和 HarmonyOS开发者社区联合曝光,让你的技术影响力更进一步!

参与方式

在 DCloud 问答社区 发布新帖,选择【分享经验】,发布博客文章,并给文章添加【鸿蒙征文】的话题,无需单独报名,社区会通过话题自动筛选征文文章。

评审标准

社区将根据以下维度进行综合评选:

  1. 内容质量:内容详实、逻辑清晰、案例完整。
  2. 技术价值:技术深度、创新性、对uni-app与鸿蒙结合点的挖掘。
  3. 影响力与实用性:对其他开发者的借鉴、启发和帮助程度;如文章的阅读数、点赞数都将作为评选参考指标,欢迎大家将自己的文章分享到微博、微信等,扩散自己的文章影响力。
  4. 原创与真实性:必须为原创内容,分享真实开发经验。

行动起来吧!

技术之路,因分享而璀璨;鸿蒙生态,因你而精彩。
这不仅是赢取奖品的机会,更是向整个开发者社区展示你技术风采的舞台。

立即执笔,分享你的 uni-app 鸿蒙开发实践,与我们一起,码向未来!

============

目前已经有不少优秀的征文发布,大家可以移步鸿蒙征文系列阅读欣赏。

收起阅读 »

UniApp 项目鸿蒙上线

鸿蒙征文

介绍一下背景:本项目是uni cli搭建的项目,区分是不是uni cli项目看一下项目存不存在src文件夹就行,本文介绍内容都是基于uni cli项目的。
需求:

  1. uniapp项目需要打包鸿蒙上线App,本次调研是基于Mac环境,不涉及Windows。
  2. 第三方SDK插件对接UTS
    准备工作:
  3. 下载安装HBuilderX(最新版)
  4. DevEco Studio下载安装,鸿蒙模拟器安装(或通过真机来进行调试)
  5. Dcloud开发者中心注册账号
  6. 华为开发者联盟 AppGallery Connect 注册账号
  7. 调试证书/发布证书(需要和本项目鸿蒙App管理员要)
    鸿蒙App配置:
    manifest.json-鸿蒙App配置
    包名:uni.app.snsk.ydbz
    证书签名配置包括调试证书和发布证书,调试证书相关配置如下图所示

应用包名:Android端、iOS端、HarmonyOS端可能有自己定义的包名,只要在本端下做好统一即可。在HarmonyOS端我们用的包名是uni.app.snsk.ydbz,项目中的配置要和华为开发者联盟一致。
运行设备:需要启动虚拟机或USB连接鸿蒙手机以后才能检测到。
开发调试可以选择自动申请调试证书,也可以手动配置下边的私钥库文件及证书文件。点自动申请调试证书以后会登录华为开发者联盟,生产证书回填下边的私钥库文件和证书文件等。
不选择自动申请调试证书,也可以和本项目鸿蒙端的管理员要调试证书,然后手动选择文件。
私钥库文件是.p12结尾的文件
证书文件是.cer结尾的文件
签名描述文件是.p7p结尾的文件
管理员账号在申请调试证书的时候,需要选取证书请求文件(CSR),证书请求文件需要在DevEco Studio上申请,具体操作请参考生成证书请求文件。
下载签名描述文件前需要先通过UDID将设备注册到AGC设备列表,后续Profile中指定的调试设备将从此设备列表中选取。具体步骤:华为开发者联盟的“证书、APP ID和Profile”-设备-添加设备。
配置完成以后点击保存。
发布证书相关配置如下图所示

发布证书的配置参考调试证书即可,签名描述文件分调试和正式两个文件,私钥库文件和证书文件和调试是同一个文件。
图标配置
前景图和背景图标准

标准如下:
前景图是核心图形(如Logo)必须是透明底色的PNG
背景图纯色背景(如纯白色)必须是不透明的PNG
图标资源必须为分层资源(一张前景图和一张背景图)
图标资源尺寸必须为 1024*1024px
启动界面配置
启动界面背景色,根据项目需求填写,本项目为空
启动界面中部图标,推荐选择软件logo
模块配置
根据项目勾选,本项目未勾选
运行到鸿蒙
具体操作:运行-运行到手机或模拟器-运行到鸿蒙

勾选设备-点击运行,成功以后会显示如下信息

这个地方,本项目出现了好多编译错误,最终通过升级uniapp-cli解决
运行成功以后生成鸿蒙工程目录,用DevEco Studio打开即可,打开以后到项目结构如图:

  1. 在DevEco Studio中需要检查一下app.json5中的bundleName是不是我们的包名,改为包名即可
  2. 在DevEco Studio里配置调试证书

调试过程可以勾选自动生成证书,打正式包要选择正式的描述文件Profile file(*.p7p),该配置和HbuilderX中的配置大同小异。
OpenHarmony SDK配置

选择设备,可以是USB连接鸿蒙手机,或者在设备管理器中下载虚拟机。

点击右侧运行,鸿蒙app就运行到手机上了。

继续阅读 »

介绍一下背景:本项目是uni cli搭建的项目,区分是不是uni cli项目看一下项目存不存在src文件夹就行,本文介绍内容都是基于uni cli项目的。
需求:

  1. uniapp项目需要打包鸿蒙上线App,本次调研是基于Mac环境,不涉及Windows。
  2. 第三方SDK插件对接UTS
    准备工作:
  3. 下载安装HBuilderX(最新版)
  4. DevEco Studio下载安装,鸿蒙模拟器安装(或通过真机来进行调试)
  5. Dcloud开发者中心注册账号
  6. 华为开发者联盟 AppGallery Connect 注册账号
  7. 调试证书/发布证书(需要和本项目鸿蒙App管理员要)
    鸿蒙App配置:
    manifest.json-鸿蒙App配置
    包名:uni.app.snsk.ydbz
    证书签名配置包括调试证书和发布证书,调试证书相关配置如下图所示

应用包名:Android端、iOS端、HarmonyOS端可能有自己定义的包名,只要在本端下做好统一即可。在HarmonyOS端我们用的包名是uni.app.snsk.ydbz,项目中的配置要和华为开发者联盟一致。
运行设备:需要启动虚拟机或USB连接鸿蒙手机以后才能检测到。
开发调试可以选择自动申请调试证书,也可以手动配置下边的私钥库文件及证书文件。点自动申请调试证书以后会登录华为开发者联盟,生产证书回填下边的私钥库文件和证书文件等。
不选择自动申请调试证书,也可以和本项目鸿蒙端的管理员要调试证书,然后手动选择文件。
私钥库文件是.p12结尾的文件
证书文件是.cer结尾的文件
签名描述文件是.p7p结尾的文件
管理员账号在申请调试证书的时候,需要选取证书请求文件(CSR),证书请求文件需要在DevEco Studio上申请,具体操作请参考生成证书请求文件。
下载签名描述文件前需要先通过UDID将设备注册到AGC设备列表,后续Profile中指定的调试设备将从此设备列表中选取。具体步骤:华为开发者联盟的“证书、APP ID和Profile”-设备-添加设备。
配置完成以后点击保存。
发布证书相关配置如下图所示

发布证书的配置参考调试证书即可,签名描述文件分调试和正式两个文件,私钥库文件和证书文件和调试是同一个文件。
图标配置
前景图和背景图标准

标准如下:
前景图是核心图形(如Logo)必须是透明底色的PNG
背景图纯色背景(如纯白色)必须是不透明的PNG
图标资源必须为分层资源(一张前景图和一张背景图)
图标资源尺寸必须为 1024*1024px
启动界面配置
启动界面背景色,根据项目需求填写,本项目为空
启动界面中部图标,推荐选择软件logo
模块配置
根据项目勾选,本项目未勾选
运行到鸿蒙
具体操作:运行-运行到手机或模拟器-运行到鸿蒙

勾选设备-点击运行,成功以后会显示如下信息

这个地方,本项目出现了好多编译错误,最终通过升级uniapp-cli解决
运行成功以后生成鸿蒙工程目录,用DevEco Studio打开即可,打开以后到项目结构如图:

  1. 在DevEco Studio中需要检查一下app.json5中的bundleName是不是我们的包名,改为包名即可
  2. 在DevEco Studio里配置调试证书

调试过程可以勾选自动生成证书,打正式包要选择正式的描述文件Profile file(*.p7p),该配置和HbuilderX中的配置大同小异。
OpenHarmony SDK配置

选择设备,可以是USB连接鸿蒙手机,或者在设备管理器中下载虚拟机。

点击右侧运行,鸿蒙app就运行到手机上了。

收起阅读 »

4.76 web端报错 can't find module 'vue-router/dist/vue-router.esm-bundler.js' 的解决方案

web uniapp vue3

背景

近期有开发者反馈web端升级到 4.76 之后报错 can't find module 'vue-router/dist/vue-router.esm-bundler.js',这个是因为你可能是安装到了 4.6.0 版本的 vue- router,这个版本移除了 vue-router.esm-bundler.js 文件,此行为导致了开发环境和生产环境不能正常运行。github上有人反馈了这个问题 详见 https://github.com/vuejs/router/issues/2569

解决方案

如果你使用 npm,需要先删除 node_modules,再重新执行 npm install,检查node_modeuls下面的vue-router依赖是否是最新的 4.6.3 版本,这个版本恢复了 vue-router.esm-bundler.js 文件

继续阅读 »

背景

近期有开发者反馈web端升级到 4.76 之后报错 can't find module 'vue-router/dist/vue-router.esm-bundler.js',这个是因为你可能是安装到了 4.6.0 版本的 vue- router,这个版本移除了 vue-router.esm-bundler.js 文件,此行为导致了开发环境和生产环境不能正常运行。github上有人反馈了这个问题 详见 https://github.com/vuejs/router/issues/2569

解决方案

如果你使用 npm,需要先删除 node_modules,再重新执行 npm install,检查node_modeuls下面的vue-router依赖是否是最新的 4.6.3 版本,这个版本恢复了 vue-router.esm-bundler.js 文件

收起阅读 »

基于vue3.5+vite7+electron38.2实战电脑端os管理系统

vue3 vue.js

vue3-electron38-os:最新原创vite7.1+electron38.2+vue3 setup+pinia3+arcoDesign+echarts跨平台仿macOS/windows风格桌面os管理系统模板。自研可拖拽栅格布局结构、自定义JSON配置桌面菜单/Dock菜单。

使用技术

  • 跨平台框架:electron^38.2.0
  • 前端技术框架:vite^7.1.7+vue^3.5.21+vue-router^4.5.1
  • 组件库:@arco-design/web-vue^2.57.0 (字节前端vue3组件库)
  • 状态管理:pinia^3.0.3
  • 拖拽插件:sortablejs^1.15.6
  • 图表组件:echarts^6.0.0
  • markdown编辑器:md-editor-v3^6.0.1
  • 模拟数据:mockjs^1.1.0
  • 打包构建:electron-builder^24.13.3
  • electron+vite插件:vite-plugin-electron^0.29.0

项目结构目录

使用最新版跨平台框架electron38+vite7创建项目模板,vue3 setup语法开发。

electron-vue3-os桌面端os项目已经同步到我的原创作品集。
electron38+vue3+arco-design客户端os系统

热文推荐

electron38-admin桌面端后台|Electron38+Vue3+ElementPlus管理系统
Electron38-Wechat电脑端聊天|vite7+electron38仿微信桌面端聊天系统
原创uniapp+vue3+deepseek+uv-ui跨端实战仿deepseek/豆包流式ai聊天对话助手。
vue3-webseek网页版AI问答|Vite6+DeepSeek+Arco流式ai聊天打字效果
最新版uni-app+vue3+uv-ui跨三端仿微信app聊天应用【h5+小程序+app端】
Flutter3-MacOS桌面OS系统|flutter3.32+window_manager客户端OS模板
最新研发flutter3.27+bitsdojo_window+getx客户端仿微信聊天Exe应用
最新版Flutter3.32+Dart3.8跨平台仿微信app聊天界面|朋友圈
最新版uniapp+vue3+uv-ui跨三端短视频+直播+聊天【H5+小程序+App端】
uniapp-vue3-os手机oa系统|uni-app+vue3跨三端os后台管理模板
Electron35-DeepSeek桌面端AI系统|vue3.5+electron+arco客户端ai模板
uniapp+vue3酒店预订|vite5+uniapp预约订房系统模板(h5+小程序+App端)
Tauri2.0+Vite5聊天室|vue3+tauri2+element-plus仿微信|tauri聊天应用
tauri2.0-admin桌面端后台系统|Tauri2+Vite5+ElementPlus管理后台EXE程序

继续阅读 »

vue3-electron38-os:最新原创vite7.1+electron38.2+vue3 setup+pinia3+arcoDesign+echarts跨平台仿macOS/windows风格桌面os管理系统模板。自研可拖拽栅格布局结构、自定义JSON配置桌面菜单/Dock菜单。

使用技术

  • 跨平台框架:electron^38.2.0
  • 前端技术框架:vite^7.1.7+vue^3.5.21+vue-router^4.5.1
  • 组件库:@arco-design/web-vue^2.57.0 (字节前端vue3组件库)
  • 状态管理:pinia^3.0.3
  • 拖拽插件:sortablejs^1.15.6
  • 图表组件:echarts^6.0.0
  • markdown编辑器:md-editor-v3^6.0.1
  • 模拟数据:mockjs^1.1.0
  • 打包构建:electron-builder^24.13.3
  • electron+vite插件:vite-plugin-electron^0.29.0

项目结构目录

使用最新版跨平台框架electron38+vite7创建项目模板,vue3 setup语法开发。

electron-vue3-os桌面端os项目已经同步到我的原创作品集。
electron38+vue3+arco-design客户端os系统

热文推荐

electron38-admin桌面端后台|Electron38+Vue3+ElementPlus管理系统
Electron38-Wechat电脑端聊天|vite7+electron38仿微信桌面端聊天系统
原创uniapp+vue3+deepseek+uv-ui跨端实战仿deepseek/豆包流式ai聊天对话助手。
vue3-webseek网页版AI问答|Vite6+DeepSeek+Arco流式ai聊天打字效果
最新版uni-app+vue3+uv-ui跨三端仿微信app聊天应用【h5+小程序+app端】
Flutter3-MacOS桌面OS系统|flutter3.32+window_manager客户端OS模板
最新研发flutter3.27+bitsdojo_window+getx客户端仿微信聊天Exe应用
最新版Flutter3.32+Dart3.8跨平台仿微信app聊天界面|朋友圈
最新版uniapp+vue3+uv-ui跨三端短视频+直播+聊天【H5+小程序+App端】
uniapp-vue3-os手机oa系统|uni-app+vue3跨三端os后台管理模板
Electron35-DeepSeek桌面端AI系统|vue3.5+electron+arco客户端ai模板
uniapp+vue3酒店预订|vite5+uniapp预约订房系统模板(h5+小程序+App端)
Tauri2.0+Vite5聊天室|vue3+tauri2+element-plus仿微信|tauri聊天应用
tauri2.0-admin桌面端后台系统|Tauri2+Vite5+ElementPlus管理后台EXE程序

收起阅读 »

提个建议---插件 、 广告 等 所有的收益不足100不能体现

提个建议---插件 、 广告 等 所有的收益不足100不能体现,有效期时间长了,钱就没有了,这钱也是我们辛辛苦苦 赚的,好伤心。

解决方案一:
新增一个 账户充值功能 然后 凑够 100 能体现。

解决方案二:
到了有效期,提示我们,手动体现 到了 有效期 不足100也能体现

解决方案三:
账户余额不消失,不能体现,但可是用于支付 ,uniapp 等其他产品的支付也行 比如 unicloud 等。。。

等。。。。。

继续阅读 »

提个建议---插件 、 广告 等 所有的收益不足100不能体现,有效期时间长了,钱就没有了,这钱也是我们辛辛苦苦 赚的,好伤心。

解决方案一:
新增一个 账户充值功能 然后 凑够 100 能体现。

解决方案二:
到了有效期,提示我们,手动体现 到了 有效期 不足100也能体现

解决方案三:
账户余额不消失,不能体现,但可是用于支付 ,uniapp 等其他产品的支付也行 比如 unicloud 等。。。

等。。。。。

收起阅读 »