HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

【鸿蒙征文】 炸裂!我用uni-app三天让旧应用通杀鸿蒙Next+元服务,华为商店已上架!2W奖励金即将到账。

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

就在其他开发者还在为鸿蒙适配焦头烂额时,我的多款uni-app旧应用已经悄然上架华为应用商店。更让人惊喜的是,连鸿蒙元服务都一并搞定!

一、缘起:鸿蒙风暴前的抉择

9月,华为宣布鸿蒙NEXT不再兼容安卓,整个互联网圈炸开了锅。我的心情和大家一样复杂——手头有10+款用uni-app开发的应用,做的最成功的应用已经在微信小程序、安卓和iOS稳定运行了8年多,累积获得用户总量50+万,服务厂商接近200个。为了我这50+万的粉丝,我必须继续披荆斩棘,用最快的速度适配鸿蒙Next,任务的紧急程度已经迫在眉睫,哪怕这50万人只有10个人用鸿蒙Next系统,我也要让他们拥有原生鸿蒙的最佳体验。问题来了,难道我真的要全部重写整个应用?噢,我滴神呀!救救我吧。

哈哈,,,转机来得比想象中更快。

作为uni-app的资深老玩家,当然是时刻追踪着uni-app适配鸿蒙的进度,期间也在不断学习原生鸿蒙ArkTS语言,但是作为Vue重度使用者(本人已使用Vue长达8年),ArkTS的语法的确让人感到繁杂,虽然都能看懂,无非还是前端的那些东西,但是真正写起来,还是不够舒畅。于是,我决定赌一把,继续使用uni-app去完成所有鸿蒙Next的适配,结果令人震惊:第一个应用仅仅只用了3天,最短的甚至不到8小时,就完成了从原有应用到鸿蒙NEXT+元服务的全适配!

二、实战全记录:72小时创造奇迹

第一阶段:环境搭建(2小时)

说实话,最开始我是忐忑的。但整个过程出乎意料的顺畅:

开发工具:

  • HUAWEI DevEco Studio
  • uni-app v3.99+(支持鸿蒙NEXT)
  • 现有项目直接导入

安装工具还是比较快的,就是下载工具和手机端模拟器比较费时间,这就占用了1个小时,接下来就是创建一个新应用,先把工具跑起来,模拟器跑起来。走一个空白的Hello Word的空项目。

如果是window系统,需要开启虚拟化Hyper-V 、window虚拟机监控平台的相关配置,如果出错了也会有对应的提示,不得不说DevEco工具还是非常人性化的。赞一个!

第二阶段:证书申请(30分钟)

这是最关键的也是最简单,如果申请过苹果的证书,就会非常得心应手。
4大证书分别是:

  1. p12:使用华为开发者工具DevEco 即可创建该证书,该证书不区分开发证书和生产证书。
  2. csr:使用华为开发者工具DevEco 即可创建该证书,该证书不区分开发证书和生产证书。
  3. cer:打开AGC平台需要登陆华为账号进行申请。在证书、APPID 和Profile菜单下操作,需要区分开发和生产证书类型。
  4. p7b:打开AGC平台需要登陆华为账号进行申请。该证书是根据应用来申请的。

申请p12和csr证书截图:

申请cer和p7b证书截图:

在HBX里面的完整配置

证书部分到此结束,还是非常简单滴!主要我这边是从注册账户到注册应用,再到注册证书,所以耗时多一点,如果再添加一个新应用的配置,那就是分分钟钟的事情了。

第三阶段:代码适配(核心8小时)

这里可能是大家最关心的部分——到底要改多少代码?

答案是:少得惊人!

我以一个物流类型的应用为案例进行举例,主要修改点包括:

  1. 登录模块:用华为账户替换以前的注册,华为账户和业务账户的绑定。
  2. 权限模块:调用华为的API获取手机号
  3. 账户注销:所有上架的应用都需要支持用户自主取消关联和账户注销。
  4. 分享功能:适配华为分享

一、登录模块主要调整的原因是:华为要求统一使用华为账户进行静默登录,那么如果以前已经有账户了,那就需要进行做关联即可。使用华为提供的code通过后台接口获取到对应的UnionID,OpenID,通过这些就可以换到手机号了,手机号的权限需要在后台开通,这个申请比较慢,说个小技巧提交申请后,立马就挂个工单,这样就审核的特别快了。

其实很多路子和微信小程序很相似,只是需要后端的配合,支持调用华为的API才行。

// 修改前:微信小程序端  

uni.login({  
  provider: 'weixin', //使用微信登录  
  success: function (loginRes) {  
    console.log(loginRes.authResult);  
  }  
});  

// 修改后:支持鸿蒙获取账户 获取后走接口拿手机号  
uni.login({  
  success: function (loginRes) {  
    console.log(loginRes.authResult);  
  }  
});  

二、权限模块的调整主要是为了满足在华为手机上能有拍照权限,坐标权限,这些自行申请,也都是比较简单的。

三、关于账户注销,这个还是要求必须得有的,其实,这个更简单,上架过iOS的都知道,这个是必须的,基本上无需调整,咱们这里其实也就是改成解除和华为账户的关联关系。
这里就不多说了。

四、分享等调用微信相关的,都需要根据鸿蒙的环境改成调用鸿蒙的即可。这个就需要大面积的排查代码了。简单是简单就是多,所以费时间。

// 修改前:通用分享  
uni.share({  
  provider: "weixin",  
  scene: "WXSceneSession",  
  type: 0,  
  title: "分享标题",  
  success: function (res) {  
    console.log("success:" + JSON.stringify(res));  
  }  
});  

// 修改后:鸿蒙分享  
uni.share({  
  provider: "harmony",  
  type: 0,  
  title: "分享标题",  
  success: function (res) {  
    console.log("鸿蒙分享成功:" + JSON.stringify(res));  
  }  
});

工作量统计:

  • 登录模块:2小时
  • 权限调试:2小时
  • 账户注销:1小时
  • 其他权限和证书适配打包APP:3小时
  • 总计:8小时

其实总结一句话:代码大改的地方真的很少,很少。

建议弟兄们,赶紧上手吧!

第四阶段:调试上架(8小时)

真机调试让我眼前一亮。鸿蒙设备的流畅度确实出色,应用启动速度比安卓版本就是快,用的华为navo12 真机运行非常流畅。

上架过程同样顺畅:

  • 华为开发者账号注册:1小时
  • 应用信息填写:1小时
  • 打包提交审核:1小时
  • 审核通过:一天会审核多次(比苹果快太多了!)

三、意外之喜:元服务的惊喜邂逅

在适配过程中,我发现鸿蒙元服务这个概念很有意思。简单来说,它让用户不用下载完整APP就能使用核心功能。

关键发现:uni-app对元服务的支持几乎是开箱即用!

// 在pages.json中配置元服务页面  
{  
  "pages": [  
    {  
      "path": "pages/index/index",  
      "style": {  
        "isEntry": true,  
        "isAtomic": true  // 标记为元服务页面  
      }  
    }  
  ]  
}

我的一个工具类应用,通过元服务实现了:

  • 用户无需安装即可使用核心计算功能
  • 服务卡片直接展示关键信息
  • 转化率提升明显

四、技术对比:uni-app的降维打击

用过uni-app 开发的朋友都知道,他最大的优势就是全终端兼容,安卓+iOS+小程序+鸿蒙,可以说是天下无敌了,一套代码,真的是省心省力,真的是早用早享受。只要你会点前端,懂Vue,就没有任何技术难度。
当然为了让大家更直观地理解,我做了个对比表:

特性 原生鸿蒙开发 uni-app鸿蒙适配
学习成本 需要学习ArkTS等新技术 使用熟悉的vue语法
代码复用 从零开始 90%+代码可直接复用
开发周期 2-4周/应用 1-3天/应用
多端支持 仅鸿蒙 同时支持小程序、iOS、安卓
维护成本 独立代码库 统一代码库

最让我震撼的是: 旧应用的适配工作远比你担心的要少太多了,只要能跑起来,运行起来,就基本上没啥问题。在这里必须感谢一下uni-app 团队的技术支持,我在适配过程中,有专门的钉钉群提供技术服务,这也是我为何能在短短3天时间就能解决所有兼容的关键。在此一并感谢整个团队!

五、避坑指南:实战中遇到的坑

当然,过程并非完全一帆风顺。记录几个可能帮到大家的坑:

  1. 图片路径问题:鸿蒙对绝对路径更敏感,建议使用相对路径
  2. CSS兼容性:部分CSS3特性需要添加鸿蒙前缀
  3. API异步处理:鸿蒙的API调用更强调异步编程
  4. 代码运行不生效:如果感觉代码没问题,热更新不生效,记得重启整个应用,甚至重启HBX,重启模拟器,有时缓存问题不得不说很难受。
// 推荐使用async/await处理鸿蒙API  
async function initHarmonyFeatures() {  
  try {  
    const result = await uni.harmony.someAPI();  
    // 处理结果  
  } catch (error) {  
    console.error('API调用失败:', error);  
  }  
}

六、成果展示:数字说话

截至目前,我的6款应用全部成功上架:

  • 🎯 适配成功率:100%(6/6)
  • 最快适配记录:8小时(旧的成熟应用)
  • 🚀 平均适配时间:2天/应用
  • 💰 成本节约:相比原生开发,节省约85%成本
  • 📈 用户增长:鸿蒙渠道日均新增用户300+

七、给开发者同仁的真诚建议

  1. 立即行动:鸿蒙生态红利期就在眼前
  2. 从简单应用开始:先拿一个功能简单的应用试水
  3. 关注元服务:这是鸿蒙的独特优势,不要错过
  4. 利用uni-app社区:遇到的问题基本都能找到解决方案

结语:一个人的速度,一个时代的变革

有开发者朋友问我:"现在入场是不是太晚了?"

我的回答是:"当你知道的时候,就是最好的时机。"

uni-app + 鸿蒙的组合,让我在技术变革的浪潮中抓住了先机。 从焦虑观望到全面上架,只用了不到一周时间。这种效率,在过去的移动开发史上是从未有过的。

现在,轮到你了。

在这里顺带解释一下奖励金,华为最近的活动,只要上架成功,有活跃用户,或者参加uni-app的活动,都是可以拿到奖励金的。大家加油!

在得瑟下我们的用户量;


【实战资源分享】

欢迎在评论区交流适配经验,我会第一时间回复大家的问题!

继续阅读 »

就在其他开发者还在为鸿蒙适配焦头烂额时,我的多款uni-app旧应用已经悄然上架华为应用商店。更让人惊喜的是,连鸿蒙元服务都一并搞定!

一、缘起:鸿蒙风暴前的抉择

9月,华为宣布鸿蒙NEXT不再兼容安卓,整个互联网圈炸开了锅。我的心情和大家一样复杂——手头有10+款用uni-app开发的应用,做的最成功的应用已经在微信小程序、安卓和iOS稳定运行了8年多,累积获得用户总量50+万,服务厂商接近200个。为了我这50+万的粉丝,我必须继续披荆斩棘,用最快的速度适配鸿蒙Next,任务的紧急程度已经迫在眉睫,哪怕这50万人只有10个人用鸿蒙Next系统,我也要让他们拥有原生鸿蒙的最佳体验。问题来了,难道我真的要全部重写整个应用?噢,我滴神呀!救救我吧。

哈哈,,,转机来得比想象中更快。

作为uni-app的资深老玩家,当然是时刻追踪着uni-app适配鸿蒙的进度,期间也在不断学习原生鸿蒙ArkTS语言,但是作为Vue重度使用者(本人已使用Vue长达8年),ArkTS的语法的确让人感到繁杂,虽然都能看懂,无非还是前端的那些东西,但是真正写起来,还是不够舒畅。于是,我决定赌一把,继续使用uni-app去完成所有鸿蒙Next的适配,结果令人震惊:第一个应用仅仅只用了3天,最短的甚至不到8小时,就完成了从原有应用到鸿蒙NEXT+元服务的全适配!

二、实战全记录:72小时创造奇迹

第一阶段:环境搭建(2小时)

说实话,最开始我是忐忑的。但整个过程出乎意料的顺畅:

开发工具:

  • HUAWEI DevEco Studio
  • uni-app v3.99+(支持鸿蒙NEXT)
  • 现有项目直接导入

安装工具还是比较快的,就是下载工具和手机端模拟器比较费时间,这就占用了1个小时,接下来就是创建一个新应用,先把工具跑起来,模拟器跑起来。走一个空白的Hello Word的空项目。

如果是window系统,需要开启虚拟化Hyper-V 、window虚拟机监控平台的相关配置,如果出错了也会有对应的提示,不得不说DevEco工具还是非常人性化的。赞一个!

第二阶段:证书申请(30分钟)

这是最关键的也是最简单,如果申请过苹果的证书,就会非常得心应手。
4大证书分别是:

  1. p12:使用华为开发者工具DevEco 即可创建该证书,该证书不区分开发证书和生产证书。
  2. csr:使用华为开发者工具DevEco 即可创建该证书,该证书不区分开发证书和生产证书。
  3. cer:打开AGC平台需要登陆华为账号进行申请。在证书、APPID 和Profile菜单下操作,需要区分开发和生产证书类型。
  4. p7b:打开AGC平台需要登陆华为账号进行申请。该证书是根据应用来申请的。

申请p12和csr证书截图:

申请cer和p7b证书截图:

在HBX里面的完整配置

证书部分到此结束,还是非常简单滴!主要我这边是从注册账户到注册应用,再到注册证书,所以耗时多一点,如果再添加一个新应用的配置,那就是分分钟钟的事情了。

第三阶段:代码适配(核心8小时)

这里可能是大家最关心的部分——到底要改多少代码?

答案是:少得惊人!

我以一个物流类型的应用为案例进行举例,主要修改点包括:

  1. 登录模块:用华为账户替换以前的注册,华为账户和业务账户的绑定。
  2. 权限模块:调用华为的API获取手机号
  3. 账户注销:所有上架的应用都需要支持用户自主取消关联和账户注销。
  4. 分享功能:适配华为分享

一、登录模块主要调整的原因是:华为要求统一使用华为账户进行静默登录,那么如果以前已经有账户了,那就需要进行做关联即可。使用华为提供的code通过后台接口获取到对应的UnionID,OpenID,通过这些就可以换到手机号了,手机号的权限需要在后台开通,这个申请比较慢,说个小技巧提交申请后,立马就挂个工单,这样就审核的特别快了。

其实很多路子和微信小程序很相似,只是需要后端的配合,支持调用华为的API才行。

// 修改前:微信小程序端  

uni.login({  
  provider: 'weixin', //使用微信登录  
  success: function (loginRes) {  
    console.log(loginRes.authResult);  
  }  
});  

// 修改后:支持鸿蒙获取账户 获取后走接口拿手机号  
uni.login({  
  success: function (loginRes) {  
    console.log(loginRes.authResult);  
  }  
});  

二、权限模块的调整主要是为了满足在华为手机上能有拍照权限,坐标权限,这些自行申请,也都是比较简单的。

三、关于账户注销,这个还是要求必须得有的,其实,这个更简单,上架过iOS的都知道,这个是必须的,基本上无需调整,咱们这里其实也就是改成解除和华为账户的关联关系。
这里就不多说了。

四、分享等调用微信相关的,都需要根据鸿蒙的环境改成调用鸿蒙的即可。这个就需要大面积的排查代码了。简单是简单就是多,所以费时间。

// 修改前:通用分享  
uni.share({  
  provider: "weixin",  
  scene: "WXSceneSession",  
  type: 0,  
  title: "分享标题",  
  success: function (res) {  
    console.log("success:" + JSON.stringify(res));  
  }  
});  

// 修改后:鸿蒙分享  
uni.share({  
  provider: "harmony",  
  type: 0,  
  title: "分享标题",  
  success: function (res) {  
    console.log("鸿蒙分享成功:" + JSON.stringify(res));  
  }  
});

工作量统计:

  • 登录模块:2小时
  • 权限调试:2小时
  • 账户注销:1小时
  • 其他权限和证书适配打包APP:3小时
  • 总计:8小时

其实总结一句话:代码大改的地方真的很少,很少。

建议弟兄们,赶紧上手吧!

第四阶段:调试上架(8小时)

真机调试让我眼前一亮。鸿蒙设备的流畅度确实出色,应用启动速度比安卓版本就是快,用的华为navo12 真机运行非常流畅。

上架过程同样顺畅:

  • 华为开发者账号注册:1小时
  • 应用信息填写:1小时
  • 打包提交审核:1小时
  • 审核通过:一天会审核多次(比苹果快太多了!)

三、意外之喜:元服务的惊喜邂逅

在适配过程中,我发现鸿蒙元服务这个概念很有意思。简单来说,它让用户不用下载完整APP就能使用核心功能。

关键发现:uni-app对元服务的支持几乎是开箱即用!

// 在pages.json中配置元服务页面  
{  
  "pages": [  
    {  
      "path": "pages/index/index",  
      "style": {  
        "isEntry": true,  
        "isAtomic": true  // 标记为元服务页面  
      }  
    }  
  ]  
}

我的一个工具类应用,通过元服务实现了:

  • 用户无需安装即可使用核心计算功能
  • 服务卡片直接展示关键信息
  • 转化率提升明显

四、技术对比:uni-app的降维打击

用过uni-app 开发的朋友都知道,他最大的优势就是全终端兼容,安卓+iOS+小程序+鸿蒙,可以说是天下无敌了,一套代码,真的是省心省力,真的是早用早享受。只要你会点前端,懂Vue,就没有任何技术难度。
当然为了让大家更直观地理解,我做了个对比表:

特性 原生鸿蒙开发 uni-app鸿蒙适配
学习成本 需要学习ArkTS等新技术 使用熟悉的vue语法
代码复用 从零开始 90%+代码可直接复用
开发周期 2-4周/应用 1-3天/应用
多端支持 仅鸿蒙 同时支持小程序、iOS、安卓
维护成本 独立代码库 统一代码库

最让我震撼的是: 旧应用的适配工作远比你担心的要少太多了,只要能跑起来,运行起来,就基本上没啥问题。在这里必须感谢一下uni-app 团队的技术支持,我在适配过程中,有专门的钉钉群提供技术服务,这也是我为何能在短短3天时间就能解决所有兼容的关键。在此一并感谢整个团队!

五、避坑指南:实战中遇到的坑

当然,过程并非完全一帆风顺。记录几个可能帮到大家的坑:

  1. 图片路径问题:鸿蒙对绝对路径更敏感,建议使用相对路径
  2. CSS兼容性:部分CSS3特性需要添加鸿蒙前缀
  3. API异步处理:鸿蒙的API调用更强调异步编程
  4. 代码运行不生效:如果感觉代码没问题,热更新不生效,记得重启整个应用,甚至重启HBX,重启模拟器,有时缓存问题不得不说很难受。
// 推荐使用async/await处理鸿蒙API  
async function initHarmonyFeatures() {  
  try {  
    const result = await uni.harmony.someAPI();  
    // 处理结果  
  } catch (error) {  
    console.error('API调用失败:', error);  
  }  
}

六、成果展示:数字说话

截至目前,我的6款应用全部成功上架:

  • 🎯 适配成功率:100%(6/6)
  • 最快适配记录:8小时(旧的成熟应用)
  • 🚀 平均适配时间:2天/应用
  • 💰 成本节约:相比原生开发,节省约85%成本
  • 📈 用户增长:鸿蒙渠道日均新增用户300+

七、给开发者同仁的真诚建议

  1. 立即行动:鸿蒙生态红利期就在眼前
  2. 从简单应用开始:先拿一个功能简单的应用试水
  3. 关注元服务:这是鸿蒙的独特优势,不要错过
  4. 利用uni-app社区:遇到的问题基本都能找到解决方案

结语:一个人的速度,一个时代的变革

有开发者朋友问我:"现在入场是不是太晚了?"

我的回答是:"当你知道的时候,就是最好的时机。"

uni-app + 鸿蒙的组合,让我在技术变革的浪潮中抓住了先机。 从焦虑观望到全面上架,只用了不到一周时间。这种效率,在过去的移动开发史上是从未有过的。

现在,轮到你了。

在这里顺带解释一下奖励金,华为最近的活动,只要上架成功,有活跃用户,或者参加uni-app的活动,都是可以拿到奖励金的。大家加油!

在得瑟下我们的用户量;


【实战资源分享】

欢迎在评论区交流适配经验,我会第一时间回复大家的问题!

收起阅读 »

2025年DCloud插件大赛获奖名单

插件大赛

本次插件大赛收到大量优质插件,尤其是大量常用插件已完成鸿蒙Next的兼容适配。

获奖名单

一等奖

奖项 分类 获奖作者 获奖插件
一等奖 前端组件 陌上华年 LimeUi 轻量高效的 Uni 生态组件库【鸿蒙Next】
一等奖 UTS插件 COOL团队 【支持鸿蒙】Cool Unix|UI组件库

二等奖

奖项 分类 获奖作者 获奖插件
二等奖 UTS插件 1530948626 安卓保活 ios保活 鸿蒙保活 保应用程序稳定后台运行(uniapp,uniappx保活 长期维护)
二等奖 UTS插件 照相 【z-paging-x下拉刷新、上拉加载】z-paging uniappx版已上线!
二等奖 UTS插件 tmui TM-UI-4.0原生应用开发解决方案套装
二等奖 UTS插件 前端码农boy 【Android+IOS+harmonyOS】银联云闪付支付
二等奖 前端组件 UxFrame 【支持原生鸿蒙】UxFrame 低代码高性能UI框架
二等奖 前端组件 TuiPlus TuiPlus 4.0 焕新发布 轻如鸿毛 快如闪电
二等奖 前端组件 rice_z RiceUI 基于uniappx 的UI框架【支持APP 鸿蒙 小程序 H5】
二等奖 前端组件 VK168 【开箱即用】uView Vue3 横空出世,继承uView1意志,再战江湖,风云再起!

三等奖

奖项 分类 获奖作者 获奖插件
三等奖 UTS插件 GraceUI uXui 【主流平台全兼容版】一款基于 uni-app x 的、免费、开源的 UI 框架
三等奖 UTS插件 珊瑚 Android端的AES、MD5、RSA、SHA、SM2、SM3、SM4加密解密
三等奖 UTS插件 shmily121314 usb-serial
三等奖 UTS插件 1530948626 Ble低功耗蓝牙uts插件 支持安卓ios 鸿蒙 微信小程序
三等奖 UTS插件 小白2023 sanfor-atrust(深信服-vpn插件)
三等奖 UTS插件 30天只能改一次 高德定位、地图、导航全功能,简单易用,支持安卓和iOS
三等奖 UTS插件 kux kux-marked
三等奖 HBuilderX 猫猫猫猫 GitHub Copilot
三等奖 前端组件 UviewPlus 零云®uview-plus3.0重磅发布,全面的Vue3鸿蒙跨端移动组件库,组件丰富维护更新稳定
三等奖 前端组件 不如摸鱼去 wot-design-uni 基于vue3+Typescript的高颜值组件库
三等奖 前端组件 uViewNext uView Next全面适配Vue3和鸿蒙,组件库更丰富,功能更全,更稳定,更高质量的UI框架
三等奖 前端组件 wenju 【ECharts组件】支持官方所有图表,支持vue3、分包、鸿蒙、nvue、uts、uniapp x
三等奖 项目模板 useryang 智慧医疗
三等奖 项目模板 小疯子呵 福袋抽奖-看广告变现

鼓励奖

因很多插件值得推荐,所以增加了贡献奖的名额。

奖项 分类 获奖作者 获奖插件
贡献奖 UTS插件 鑫时代 xsd-request
贡献奖 UTS插件 1530948626 android ios 原生gps前后台定位,系统定位
贡献奖 UTS插件 shmily121314 rs232-serial
贡献奖 UTS插件 kaka_ 蓝牙 USB Wi-Fi 打印机UTS插件.支持安卓 iOS 支持TSPL CPCL ESC
贡献奖 UTS插件 1530948626 安卓悬浮窗插件 可自定义内容 悬浮窗 画中画 可拖拽,可切换浮窗大小
贡献奖 UTS插件 csr_hb read-nfc
贡献奖 UTS插件 康爱公社 kux-dayjs
贡献奖 UTS插件 1530948626 android mqtt 消息推送
贡献奖 UTS插件 kux kux-crypto
贡献奖 UTS插件 Face_AI FaceAISDK
贡献奖 UTS插件 李子 【Android+iOS+HarmonyNext】腾讯云即时通讯(IM)UTS插件
贡献奖 UTS插件 billkes_bg 高德定位 UTS-API 插件 - 全平台定位解决方案
贡献奖 UTS插件 陌上华年 lime-sqlite
贡献奖 UTS插件 Greetty Telegram分享,WA分享,Twitter分享,Instagram分享
贡献奖 UTS插件 文艺程序猿 facebook登录
贡献奖 UTS插件 文艺程序猿 OneSignal海外App推送服务 支持iOS和Andriod
贡献奖 UTS插件 倜傥 苹果登录
贡献奖 UTS插件 UxFrame UxFrame 微信SDK
贡献奖 UTS插件 叶柳垂杨 TTS(安卓、ios、鸿蒙、web)将文字转成语音播报
贡献奖 UTS插件 文若不是苟或 文若IM
贡献奖 UTS插件 early_Summer es-paypal
贡献奖 UTS插件 天生DR UTS 苹果登录
贡献奖 uniCloud 希语 有奖猜歌游戏--改猜字谜答题游戏
贡献奖 uniCloud eric_peng 【精品】拍照算数批改
贡献奖 HBuilderX 智谱AI CodeGeeX: AI Code AutoComplete, Chat, Auto Comment
贡献奖 HBuilderX luhaoyu_ ucoder-AI编程助手
贡献奖 HBuilderX raise 日志桥,让cursor自动识别HBuilderX运行报错日志,自动修复错误代码
贡献奖 JS SDK Blue_ 安卓权限申请、权限申请的使用目的、华为上架、小米上架
贡献奖 JS SDK 一泽 request uni-ajax 请求
贡献奖 JS SDK jones2000 K线 分时 通达信指标 深度图 报价列表 订单流 筹码分布 hqchart.v2
贡献奖 前端组件 zerojs zero-markdown-view(markdown解析)
贡献奖 前端组件 lishanjun 全能文件选择上传3.0-纯前端
贡献奖 前端组件 ikun_ui 【ikun-qrcode】极简的二维码生成组件,使用view而非canvas避免层级问题
贡献奖 前端组件 193***@qq.com sse 客户端组件,支持兼容:v2、v3、安卓、ios、浏览器、微信小程序
贡献奖 前端组件 101***@qq.com 好用视频播放器
贡献奖 前端组件 月上柳梢 解决软件在运行时,未见向用户告知权限申请的目的,华为等上架被拒问题
贡献奖 前端组件 119***@qq.com 3D模型展示、动画播放、模型点击 - three.js(threeJs)
贡献奖 前端组件 FreeAlive 图片编辑器
贡献奖 前端组件 不如摸鱼去 微信小程序隐私保护弹出框 隐私协议弹出框 隐私弹框
贡献奖 前端组件 前端组件开发 基于原生input增强选择picker插件用于地图定位选择位置 页面跳转选择数据
贡献奖 前端组件 100***@qq.com rn-signature 电子签名、兼容小程序、H5、APP、鸿蒙 横屏展示
贡献奖 前端组件 SingmyAaronLan SinleUI - uni-app x UI 框架
贡献奖 项目模板 SheepJS Shopro商城 vue3+pinia+vite前端项目模板
贡献奖 项目模板 张宇凡 免费、商用数字人再开源,UI美得一塌糊涂!
贡献奖 项目模板 森林君 上门按摩专业版(仿东郊到家、上门预约类皆适用)
贡献奖 项目模板 jcodeapp 充电桩扫码充电小程序纯前端模板
贡献奖 项目模板 优雅草科技 优雅草蜻蜓hr人才招聘系统-前端模板文件开源-企业人才招聘系统
贡献奖 项目模板 tangniyuqi UniPet宠物领养平台前端开源项目
贡献奖 项目模板 用户2737414 社区论坛、校园论坛系统、圈子模板
贡献奖 项目模板 billkes_bg 考试答题系统模版

奖项设置:

本次大赛与往届有一个差别,就是更加普惠。本次没有特等奖,三等奖也发放价值2699元的纯血鸿蒙手机(全新机)。

一等奖(2名):

奖品:1万元插件包销 + 鸿蒙手机1部 + 插件市场置顶推荐半个月 + HBuilderX预置 + HBuilderX超大鼠标垫 + DCloud奖牌

二等奖(8名):

奖品:1000元插件包销 + 鸿蒙手机1部 + 插件市场置顶推荐1个星期 + HBuilderX超大鼠标垫 + DCloud奖牌

三等奖(14名):

奖品:200元uniCloud代金券 + 鸿蒙手机1部 + HBuilderX超大鼠标垫 + DCloud奖牌

说明:三等奖原设置为 20 名,但插件评分满足条件的插件数量不足,很遗憾本次只能评选出 14 名;开发者可继续迭代更新自己的插件,截止 2025 年 12 月 31 日之前,发布更新版本的插件作者,可邮件到service@dcloud.io进行评选申请,评委会审核通过后,会补发剩余的 6 个三等奖。

贡献奖(50名):

奖品:HBuilderX超大鼠标垫

奖品说明:

  1. “插件包销”,是指获奖插件通过插件市场销售,DCloud兜底包销。以1等奖的1万元包销为例,如果获奖插件在插件市场1年内销售额没有达到1万元,则由DCloud付差额给获奖者进行兜底。包销只针对付费插件,如免费插件获得二等奖及以上奖励,其中的包销奖励无效。包销插件需持续迭代,如插件作者放弃维护,则包销无效。

  2. “HBuilderX预置”,是在HBuilderX新建项目界面,可直接选择该项目模板。这为插件带来大量的流量。不适合预置的插件类型,无法领取此奖项。

  3. 本次插件大赛,目标是普惠更广大开发者,解决很多工程师缺少鸿蒙真机的困境,故本次奖励的鸿蒙手机为统一型号为nova 14 256GB

  4. 鸿蒙手机的奖励需满足两个条件:

    • 插件兼容鸿蒙平台
    • 插件作者通过DCloud专属链接到鸿蒙开发者平台注册一个新的开发者账号

除上述奖品外:

  • 二等奖及以上获奖插件作者,都将进入DCloud VIP技术支持群,享受优先的技术支付、问题反馈。
  • 所有获奖插件的集锦页面,还将通过HBuilderX工具、论坛、IM/QQ/微信群进行全量推广,给予优秀插件充分的曝光。

HBuilderX预置窗体界面如下:

奖牌照片如下:

奖品领取

请各位获奖作者尽快提交自己的邮寄地址,我们会陆续联系获奖人员发放奖品;
邮寄地址提交方式:登录ask社区,点击右上角个人头像,进入设置界面,设置界面下方补充快递邮寄地址。

已获奖的插件作者请继续升级迭代插件;

未获奖的今年还有机会,官方会继续为建设更好的uni-app x生态及更好的鸿蒙支持,推出其他计划。

不管是为了下次大赛获奖,还是为了把握uni-app x及鸿蒙替代的新浪潮,或者在插件市场通过售卖插件变现,都是值得期待的好事。

继续阅读 »

本次插件大赛收到大量优质插件,尤其是大量常用插件已完成鸿蒙Next的兼容适配。

获奖名单

一等奖

奖项 分类 获奖作者 获奖插件
一等奖 前端组件 陌上华年 LimeUi 轻量高效的 Uni 生态组件库【鸿蒙Next】
一等奖 UTS插件 COOL团队 【支持鸿蒙】Cool Unix|UI组件库

二等奖

奖项 分类 获奖作者 获奖插件
二等奖 UTS插件 1530948626 安卓保活 ios保活 鸿蒙保活 保应用程序稳定后台运行(uniapp,uniappx保活 长期维护)
二等奖 UTS插件 照相 【z-paging-x下拉刷新、上拉加载】z-paging uniappx版已上线!
二等奖 UTS插件 tmui TM-UI-4.0原生应用开发解决方案套装
二等奖 UTS插件 前端码农boy 【Android+IOS+harmonyOS】银联云闪付支付
二等奖 前端组件 UxFrame 【支持原生鸿蒙】UxFrame 低代码高性能UI框架
二等奖 前端组件 TuiPlus TuiPlus 4.0 焕新发布 轻如鸿毛 快如闪电
二等奖 前端组件 rice_z RiceUI 基于uniappx 的UI框架【支持APP 鸿蒙 小程序 H5】
二等奖 前端组件 VK168 【开箱即用】uView Vue3 横空出世,继承uView1意志,再战江湖,风云再起!

三等奖

奖项 分类 获奖作者 获奖插件
三等奖 UTS插件 GraceUI uXui 【主流平台全兼容版】一款基于 uni-app x 的、免费、开源的 UI 框架
三等奖 UTS插件 珊瑚 Android端的AES、MD5、RSA、SHA、SM2、SM3、SM4加密解密
三等奖 UTS插件 shmily121314 usb-serial
三等奖 UTS插件 1530948626 Ble低功耗蓝牙uts插件 支持安卓ios 鸿蒙 微信小程序
三等奖 UTS插件 小白2023 sanfor-atrust(深信服-vpn插件)
三等奖 UTS插件 30天只能改一次 高德定位、地图、导航全功能,简单易用,支持安卓和iOS
三等奖 UTS插件 kux kux-marked
三等奖 HBuilderX 猫猫猫猫 GitHub Copilot
三等奖 前端组件 UviewPlus 零云®uview-plus3.0重磅发布,全面的Vue3鸿蒙跨端移动组件库,组件丰富维护更新稳定
三等奖 前端组件 不如摸鱼去 wot-design-uni 基于vue3+Typescript的高颜值组件库
三等奖 前端组件 uViewNext uView Next全面适配Vue3和鸿蒙,组件库更丰富,功能更全,更稳定,更高质量的UI框架
三等奖 前端组件 wenju 【ECharts组件】支持官方所有图表,支持vue3、分包、鸿蒙、nvue、uts、uniapp x
三等奖 项目模板 useryang 智慧医疗
三等奖 项目模板 小疯子呵 福袋抽奖-看广告变现

鼓励奖

因很多插件值得推荐,所以增加了贡献奖的名额。

奖项 分类 获奖作者 获奖插件
贡献奖 UTS插件 鑫时代 xsd-request
贡献奖 UTS插件 1530948626 android ios 原生gps前后台定位,系统定位
贡献奖 UTS插件 shmily121314 rs232-serial
贡献奖 UTS插件 kaka_ 蓝牙 USB Wi-Fi 打印机UTS插件.支持安卓 iOS 支持TSPL CPCL ESC
贡献奖 UTS插件 1530948626 安卓悬浮窗插件 可自定义内容 悬浮窗 画中画 可拖拽,可切换浮窗大小
贡献奖 UTS插件 csr_hb read-nfc
贡献奖 UTS插件 康爱公社 kux-dayjs
贡献奖 UTS插件 1530948626 android mqtt 消息推送
贡献奖 UTS插件 kux kux-crypto
贡献奖 UTS插件 Face_AI FaceAISDK
贡献奖 UTS插件 李子 【Android+iOS+HarmonyNext】腾讯云即时通讯(IM)UTS插件
贡献奖 UTS插件 billkes_bg 高德定位 UTS-API 插件 - 全平台定位解决方案
贡献奖 UTS插件 陌上华年 lime-sqlite
贡献奖 UTS插件 Greetty Telegram分享,WA分享,Twitter分享,Instagram分享
贡献奖 UTS插件 文艺程序猿 facebook登录
贡献奖 UTS插件 文艺程序猿 OneSignal海外App推送服务 支持iOS和Andriod
贡献奖 UTS插件 倜傥 苹果登录
贡献奖 UTS插件 UxFrame UxFrame 微信SDK
贡献奖 UTS插件 叶柳垂杨 TTS(安卓、ios、鸿蒙、web)将文字转成语音播报
贡献奖 UTS插件 文若不是苟或 文若IM
贡献奖 UTS插件 early_Summer es-paypal
贡献奖 UTS插件 天生DR UTS 苹果登录
贡献奖 uniCloud 希语 有奖猜歌游戏--改猜字谜答题游戏
贡献奖 uniCloud eric_peng 【精品】拍照算数批改
贡献奖 HBuilderX 智谱AI CodeGeeX: AI Code AutoComplete, Chat, Auto Comment
贡献奖 HBuilderX luhaoyu_ ucoder-AI编程助手
贡献奖 HBuilderX raise 日志桥,让cursor自动识别HBuilderX运行报错日志,自动修复错误代码
贡献奖 JS SDK Blue_ 安卓权限申请、权限申请的使用目的、华为上架、小米上架
贡献奖 JS SDK 一泽 request uni-ajax 请求
贡献奖 JS SDK jones2000 K线 分时 通达信指标 深度图 报价列表 订单流 筹码分布 hqchart.v2
贡献奖 前端组件 zerojs zero-markdown-view(markdown解析)
贡献奖 前端组件 lishanjun 全能文件选择上传3.0-纯前端
贡献奖 前端组件 ikun_ui 【ikun-qrcode】极简的二维码生成组件,使用view而非canvas避免层级问题
贡献奖 前端组件 193***@qq.com sse 客户端组件,支持兼容:v2、v3、安卓、ios、浏览器、微信小程序
贡献奖 前端组件 101***@qq.com 好用视频播放器
贡献奖 前端组件 月上柳梢 解决软件在运行时,未见向用户告知权限申请的目的,华为等上架被拒问题
贡献奖 前端组件 119***@qq.com 3D模型展示、动画播放、模型点击 - three.js(threeJs)
贡献奖 前端组件 FreeAlive 图片编辑器
贡献奖 前端组件 不如摸鱼去 微信小程序隐私保护弹出框 隐私协议弹出框 隐私弹框
贡献奖 前端组件 前端组件开发 基于原生input增强选择picker插件用于地图定位选择位置 页面跳转选择数据
贡献奖 前端组件 100***@qq.com rn-signature 电子签名、兼容小程序、H5、APP、鸿蒙 横屏展示
贡献奖 前端组件 SingmyAaronLan SinleUI - uni-app x UI 框架
贡献奖 项目模板 SheepJS Shopro商城 vue3+pinia+vite前端项目模板
贡献奖 项目模板 张宇凡 免费、商用数字人再开源,UI美得一塌糊涂!
贡献奖 项目模板 森林君 上门按摩专业版(仿东郊到家、上门预约类皆适用)
贡献奖 项目模板 jcodeapp 充电桩扫码充电小程序纯前端模板
贡献奖 项目模板 优雅草科技 优雅草蜻蜓hr人才招聘系统-前端模板文件开源-企业人才招聘系统
贡献奖 项目模板 tangniyuqi UniPet宠物领养平台前端开源项目
贡献奖 项目模板 用户2737414 社区论坛、校园论坛系统、圈子模板
贡献奖 项目模板 billkes_bg 考试答题系统模版

奖项设置:

本次大赛与往届有一个差别,就是更加普惠。本次没有特等奖,三等奖也发放价值2699元的纯血鸿蒙手机(全新机)。

一等奖(2名):

奖品:1万元插件包销 + 鸿蒙手机1部 + 插件市场置顶推荐半个月 + HBuilderX预置 + HBuilderX超大鼠标垫 + DCloud奖牌

二等奖(8名):

奖品:1000元插件包销 + 鸿蒙手机1部 + 插件市场置顶推荐1个星期 + HBuilderX超大鼠标垫 + DCloud奖牌

三等奖(14名):

奖品:200元uniCloud代金券 + 鸿蒙手机1部 + HBuilderX超大鼠标垫 + DCloud奖牌

说明:三等奖原设置为 20 名,但插件评分满足条件的插件数量不足,很遗憾本次只能评选出 14 名;开发者可继续迭代更新自己的插件,截止 2025 年 12 月 31 日之前,发布更新版本的插件作者,可邮件到service@dcloud.io进行评选申请,评委会审核通过后,会补发剩余的 6 个三等奖。

贡献奖(50名):

奖品:HBuilderX超大鼠标垫

奖品说明:

  1. “插件包销”,是指获奖插件通过插件市场销售,DCloud兜底包销。以1等奖的1万元包销为例,如果获奖插件在插件市场1年内销售额没有达到1万元,则由DCloud付差额给获奖者进行兜底。包销只针对付费插件,如免费插件获得二等奖及以上奖励,其中的包销奖励无效。包销插件需持续迭代,如插件作者放弃维护,则包销无效。

  2. “HBuilderX预置”,是在HBuilderX新建项目界面,可直接选择该项目模板。这为插件带来大量的流量。不适合预置的插件类型,无法领取此奖项。

  3. 本次插件大赛,目标是普惠更广大开发者,解决很多工程师缺少鸿蒙真机的困境,故本次奖励的鸿蒙手机为统一型号为nova 14 256GB

  4. 鸿蒙手机的奖励需满足两个条件:

    • 插件兼容鸿蒙平台
    • 插件作者通过DCloud专属链接到鸿蒙开发者平台注册一个新的开发者账号

除上述奖品外:

  • 二等奖及以上获奖插件作者,都将进入DCloud VIP技术支持群,享受优先的技术支付、问题反馈。
  • 所有获奖插件的集锦页面,还将通过HBuilderX工具、论坛、IM/QQ/微信群进行全量推广,给予优秀插件充分的曝光。

HBuilderX预置窗体界面如下:

奖牌照片如下:

奖品领取

请各位获奖作者尽快提交自己的邮寄地址,我们会陆续联系获奖人员发放奖品;
邮寄地址提交方式:登录ask社区,点击右上角个人头像,进入设置界面,设置界面下方补充快递邮寄地址。

已获奖的插件作者请继续升级迭代插件;

未获奖的今年还有机会,官方会继续为建设更好的uni-app x生态及更好的鸿蒙支持,推出其他计划。

不管是为了下次大赛获奖,还是为了把握uni-app x及鸿蒙替代的新浪潮,或者在插件市场通过售卖插件变现,都是值得期待的好事。

收起阅读 »

iOS 上架应用市场全流程指南,App Store 审核机制、证书管理与跨平台免 Mac 上传发布方案(含开心上架实战)

iOS

'''对于所有 iOS 开发者而言,将应用成功上架到 App Store 是开发流程的最终目标。
无论是个人独立开发者,还是跨平台团队(如使用 uni-app、Flutter、React Native 等),iOS 上架始终是最关键也最繁琐的环节之一。

上架不仅仅是“上传一个 ipa 文件”,而是一套包含开发者注册、证书管理、应用配置、截图上传、审核提交流程的完整体系。


一、iOS 应用市场(App Store)概述

苹果的 App Store 是全球最大的移动应用分发平台之一,覆盖 175 个国家和地区,对应用质量与安全有严格要求。

与 Android 不同,iOS 平台的上架流程完全由苹果审核控制,这意味着开发者需要遵守以下三个核心规范:

  • 内容规范(Content Guidelines):禁止违规内容;
  • 隐私合规(Privacy Compliance):要求隐私政策与数据声明;
  • 技术合规(Technical Requirements):必须使用合法证书签名、无崩溃错误。

因此,上架准备工作 的完整性,决定了应用能否顺利通过审核。


二、上架前准备:账号与证书

Apple Developer 账号

开发者需要注册 Apple Developer Program
分为两种类型:

类型 费用 适用场景
个人账号 99 美元/年 个人或小团队
企业账号 299 美元/年 公司或内部应用分发

注册完成后,即可在后台创建 App ID、证书(Certificates)和描述文件(Provisioning Profiles)
注册


证书类型及作用

证书类型 用途
开发证书(Development Certificate) 用于调试与测试安装
发布证书(Distribution Certificate) 用于 App Store 上架
推送证书(Push Certificate) 用于 APNs 推送功能

开心上架(Appuploader)可直接在 Windows / Linux / macOS 上创建 iOS 证书,无需 Mac 与钥匙串助手(Keychain Access)。
证书


三、IPA 文件的生成与打包方式

应用在上架前必须打包为 .ipa 文件。
根据项目类型,开发者可选择不同方案:

项目类型 打包方式
原生 iOS 项目(Xcode) Xcode → Product → Archive
跨平台项目(Flutter / uni-app) 使用命令行或 HBuilder 云打包
混合应用(React Native / Cordova) CLI 工具 + iOS 证书导出

如果你使用 HBuilder 或 uni-app,可以直接使用云打包生成 .ipa 文件,再配合 Appuploader 进行上传,无需 Mac 环境。


四、上传到 App Store 的方式对比

传统上传方式依赖 Mac 环境,如下表所示:

工具 系统要求 操作方式 缺点
Xcode macOS 打包后直接上传 需本地签名配置
Transporter App macOS 拖拽上传 IPA 无法自动化
altool / Fastlane macOS 命令行上传 依赖 Transporter
开心上架(Appuploader) Windows / Linux / macOS GUI + CLI 上传 免 Mac,支持自动化

五、开心上架(Appuploader)上传实战

命令行上传示例:

appuploader_cli -u ios@team.com -p xxx-xxx-xxx-xxx -c 2 -f ./build/app.ipa

参数说明:

参数 含义
-u Apple 开发者账号
-p App 专用密码
-c 上传通道(1=旧通道,2=新通道)
-f 指定上传的 IPA 文件路径

执行后,Appuploader 会自动连接 App Store Connect,
验证包体信息并上传,输出上传结果日志。

支持功能:

  • 上传 IPA 文件
  • 上传多语言截图与描述信息
  • 自动识别应用版本号
  • 输出可视化上传进度

六、App Store Connect 后台配置步骤

IPA 上传完成后,登录 App Store Connect
完成以下设置:

填写应用信息(名称、描述、关键词);
上传截图与隐私政策链接
选择应用分级与定价模式
提交审核

审核通过后,应用即可在全球 App Store 上架发布。
app store connect


七、跨平台团队的免 Mac 上架实践

假设你是一个在 Windows + Flutter + Jenkins CI 环境下开发的团队,整个自动化上架流程如下:

1. Fastlane 构建 IPA  
2. Appuploader CLI 上传 IPA  
3. App Store Connect 自动生成构建版本  
4. 邮件通知团队成员

脚本示例:

fastlane gym --scheme "MyApp"  
appuploader_cli -u dev@icloud.com -p xxx-xxx-xxx-xxx -c 2 -f ./build/MyApp.ipa

该流程完全不依赖 Mac 环境,可运行于 Linux 容器或 Jenkins Agent 节点。


八、常见审核与上架问题

问题 原因 解决方案
“Invalid Bundle ID” ID 不匹配 确认与 Apple Developer 保持一致
“ITMS-90161 Invalid Provisioning Profile” 签名错误 重新生成发布证书
“Missing Privacy Policy” 隐私声明缺失 提供完整链接
上传失败 网络不稳或密码错误 使用 App 专用密码并切换通道
审核延迟 应用含复杂功能 耐心等待或联系客服复核

九、iOS 应用市场上架的最佳实践

使用新通道上传(-c 2),速度更快;
上传前验证 Info.plist 的版本号与包名;
截图建议使用 6.5" + iPad Pro 尺寸自动适配;
在 App Store Connect 提交隐私政策与数据用途说明;
使用 CI 工具结合 Appuploader CLI,实现持续交付。


上架 iOS 应用市场是一项需要技术与耐心并存的工作,从证书创建到上传审核,每个环节都有其严格的规范。

第三方工具的出现,让整个流程更高效、更自由:开发者无需 Mac,即可在任意平台完成上传与发布,让 iOS 应用市场的上架不再是“平台壁垒”,而是自动化流水线的一环。'''

继续阅读 »

'''对于所有 iOS 开发者而言,将应用成功上架到 App Store 是开发流程的最终目标。
无论是个人独立开发者,还是跨平台团队(如使用 uni-app、Flutter、React Native 等),iOS 上架始终是最关键也最繁琐的环节之一。

上架不仅仅是“上传一个 ipa 文件”,而是一套包含开发者注册、证书管理、应用配置、截图上传、审核提交流程的完整体系。


一、iOS 应用市场(App Store)概述

苹果的 App Store 是全球最大的移动应用分发平台之一,覆盖 175 个国家和地区,对应用质量与安全有严格要求。

与 Android 不同,iOS 平台的上架流程完全由苹果审核控制,这意味着开发者需要遵守以下三个核心规范:

  • 内容规范(Content Guidelines):禁止违规内容;
  • 隐私合规(Privacy Compliance):要求隐私政策与数据声明;
  • 技术合规(Technical Requirements):必须使用合法证书签名、无崩溃错误。

因此,上架准备工作 的完整性,决定了应用能否顺利通过审核。


二、上架前准备:账号与证书

Apple Developer 账号

开发者需要注册 Apple Developer Program
分为两种类型:

类型 费用 适用场景
个人账号 99 美元/年 个人或小团队
企业账号 299 美元/年 公司或内部应用分发

注册完成后,即可在后台创建 App ID、证书(Certificates)和描述文件(Provisioning Profiles)
注册


证书类型及作用

证书类型 用途
开发证书(Development Certificate) 用于调试与测试安装
发布证书(Distribution Certificate) 用于 App Store 上架
推送证书(Push Certificate) 用于 APNs 推送功能

开心上架(Appuploader)可直接在 Windows / Linux / macOS 上创建 iOS 证书,无需 Mac 与钥匙串助手(Keychain Access)。
证书


三、IPA 文件的生成与打包方式

应用在上架前必须打包为 .ipa 文件。
根据项目类型,开发者可选择不同方案:

项目类型 打包方式
原生 iOS 项目(Xcode) Xcode → Product → Archive
跨平台项目(Flutter / uni-app) 使用命令行或 HBuilder 云打包
混合应用(React Native / Cordova) CLI 工具 + iOS 证书导出

如果你使用 HBuilder 或 uni-app,可以直接使用云打包生成 .ipa 文件,再配合 Appuploader 进行上传,无需 Mac 环境。


四、上传到 App Store 的方式对比

传统上传方式依赖 Mac 环境,如下表所示:

工具 系统要求 操作方式 缺点
Xcode macOS 打包后直接上传 需本地签名配置
Transporter App macOS 拖拽上传 IPA 无法自动化
altool / Fastlane macOS 命令行上传 依赖 Transporter
开心上架(Appuploader) Windows / Linux / macOS GUI + CLI 上传 免 Mac,支持自动化

五、开心上架(Appuploader)上传实战

命令行上传示例:

appuploader_cli -u ios@team.com -p xxx-xxx-xxx-xxx -c 2 -f ./build/app.ipa

参数说明:

参数 含义
-u Apple 开发者账号
-p App 专用密码
-c 上传通道(1=旧通道,2=新通道)
-f 指定上传的 IPA 文件路径

执行后,Appuploader 会自动连接 App Store Connect,
验证包体信息并上传,输出上传结果日志。

支持功能:

  • 上传 IPA 文件
  • 上传多语言截图与描述信息
  • 自动识别应用版本号
  • 输出可视化上传进度

六、App Store Connect 后台配置步骤

IPA 上传完成后,登录 App Store Connect
完成以下设置:

填写应用信息(名称、描述、关键词);
上传截图与隐私政策链接
选择应用分级与定价模式
提交审核

审核通过后,应用即可在全球 App Store 上架发布。
app store connect


七、跨平台团队的免 Mac 上架实践

假设你是一个在 Windows + Flutter + Jenkins CI 环境下开发的团队,整个自动化上架流程如下:

1. Fastlane 构建 IPA  
2. Appuploader CLI 上传 IPA  
3. App Store Connect 自动生成构建版本  
4. 邮件通知团队成员

脚本示例:

fastlane gym --scheme "MyApp"  
appuploader_cli -u dev@icloud.com -p xxx-xxx-xxx-xxx -c 2 -f ./build/MyApp.ipa

该流程完全不依赖 Mac 环境,可运行于 Linux 容器或 Jenkins Agent 节点。


八、常见审核与上架问题

问题 原因 解决方案
“Invalid Bundle ID” ID 不匹配 确认与 Apple Developer 保持一致
“ITMS-90161 Invalid Provisioning Profile” 签名错误 重新生成发布证书
“Missing Privacy Policy” 隐私声明缺失 提供完整链接
上传失败 网络不稳或密码错误 使用 App 专用密码并切换通道
审核延迟 应用含复杂功能 耐心等待或联系客服复核

九、iOS 应用市场上架的最佳实践

使用新通道上传(-c 2),速度更快;
上传前验证 Info.plist 的版本号与包名;
截图建议使用 6.5" + iPad Pro 尺寸自动适配;
在 App Store Connect 提交隐私政策与数据用途说明;
使用 CI 工具结合 Appuploader CLI,实现持续交付。


上架 iOS 应用市场是一项需要技术与耐心并存的工作,从证书创建到上传审核,每个环节都有其严格的规范。

第三方工具的出现,让整个流程更高效、更自由:开发者无需 Mac,即可在任意平台完成上传与发布,让 iOS 应用市场的上架不再是“平台壁垒”,而是自动化流水线的一环。'''

收起阅读 »

【鸿蒙征文】从创业小白到省赛获奖:我们用 uni-app 做出了"校园人人帮"

鸿蒙征文

故事的开端:宿舍里诞生的创业想法

大三上学期的某个周末,宿舍四个人正窝在床上刷手机。老三突然抱怨:"又没抢到图书馆的座位,明天还得早起占座,太痛苦了!"老二接话:"要是能花点钱找人帮忙占座就好了。"这句话像一道闪电,点亮了我的思路。

"为什么不做个平台,专门解决校园里这些互助需求呢?"我坐起来,越说越兴奋:"帮拿快递、帮带外卖、帮占座位、帮打印材料......这些需求每天都在发生,但现在都是靠朋友圈和微信群,效率太低了!"

四个人一拍即合,当晚就开始构思项目方案。我们给它起名叫"校园人人帮"——人人都能发布需求,人人都能接单帮忙,顺便赚点生活费。方案写了整整一周,我们信心满满地向学校申报了创新创业项目,还准备参加省级"互联网+"大赛。

然后,现实给了我们一记重拳。

第一道坎:我们根本不会开发 App

拿到学校的项目支持资金后,我们才发现:想法很美好,但根本不知道怎么实现

团队四个人,老大学工商管理、老二学电子商务、老三学市场营销,只有我选修过前端开发课程,勉强会点 HTML 和 CSS。要做一个真正能用的应用,我们需要:

  • Android 开发?得学 Java 或 Kotlin
  • iOS 开发?得学 Swift,还得买 Mac
  • 小程序开发?好像容易些,但功能受限
  • 鸿蒙开发?老师说比赛评委看重这个,但要学 ArkTS

每一条路都像一座大山,横在我们面前。去外面找外包团队报价,起步就要三万块,我们的启动资金根本不够。招技术合伙人?校内发了一周招募帖,只有两个人来咨询,一听说还在初期阶段,没有工资,立刻就走了。

最绝望的时候,我甚至想过放弃。"也许我们就不是做技术的料",这个念头在脑海里转了无数次。

转机:B站上的一条视频

崩溃了几天后,我决定至少要努力到最后一刻。开始在 B站、知乎、CSDN 上疯狂搜索:"零基础怎么做 App"、"大学生创业适合用什么技术"、"跨平台开发工具推荐"......

某天凌晨两点,刷到一个 up 主的视频:《大学生必看!用 uni-app 一周做出你的第一个 App》。视频里,up 主演示了用熟悉的 Vue 语法写代码,然后一键生成 Android、iOS、小程序、H5 多个平台的应用。

我整个人都精神了,立刻把视频发到宿舍群:"兄弟们,也许有救了!"

第二天一早,我就下载了 HBuilderX,跟着官方文档的 "快速上手" 开始尝试。神奇的事情发生了:

那些我在前端课上学过的 Vue 语法,在 uni-app 里几乎能直接用!

写一个任务列表页面,代码结构和我之前写的课程作业差不多:

<template>  
  <view class="task-list">  
    <view v-for="task in tasks" :key="task.id" class="task-item">  
      <text>{{ task.title }}</text>  
      <text class="reward">¥{{ task.reward }}</text>  
    </view>  
  </view>  
</template>

更神奇的是,我把官方文档分享给另外两个队友,他们一个学电子商务,一个学工商管理,之前完全没碰过代码。但跟着教程做了两天 Demo 之后,居然也能写简单的页面了。有个队友还兴奋地在群里说:"原来写代码没那么难,感觉就像搭积木一样!"

<template>  
  <view class="task-list">  
    <view v-for="task in tasks" :key="task.id" class="task-item">  
      <text>{{ task.title }}</text>  
      <text class="reward">¥{{ task.reward }}</text>  
    </view>  
  </view>  
</template>

v-for 循环、双括号插值、:key 绑定......这些不就是 Vue 的语法吗?我花了一个下午,就把第一个页面做出来了。在 HBuilderX 里点击"运行到浏览器",页面真的显示出来了!那一刻的激动,现在想起来还记忆犹新。

说服队友:技术门槛不再是问题

有了第一个 Demo,我立刻把另外三个队友拉到电脑前演示。"你们看,就写这么点代码,就能做出一个页面了!而且这套代码,可以同时在微信小程序、App、网页上运行!"

老二半信半疑:"我们又不会 Vue,能学会吗?"

"简单!"我打开官方教程,"你们看这个新手指南,图文并茂,还有视频讲解。咱们每天晚上花两小时学,一周就能上手。"

接下来的一周,宿舍变成了临时"培训班"。我带着他们从最基础的概念开始学:什么是组件、什么是数据绑定、什么是生命周期......每天晚上10点到12点,四台电脑的屏幕一起亮着,敲代码的键盘声此起彼伏。

第五天,老二做出了第一个功能——"任务发布表单"。
第七天,老三完成了"个人中心页面"。
第九天,老大(学工商管理的那位)居然把"订单列表"也做出来了。

看着他们从一脸懵逼到能独立写代码,我深刻体会到了 uni-app 的威力:它真的把编程的门槛降到了极低。你不需要是计算机专业,不需要学几年编程,只要愿意投入时间,跟着文档一步步来,就能把想法变成产品。

开发加速度:一套代码适配所有平台

学会基础语法后,我们开始全速推进开发。按照项目规划,核心功能包括:

  • 任务发布与浏览
  • 接单与抢单
  • 在线支付与结算
  • 实时消息通知
  • 用户信用评价体系

如果用传统的原生开发,我们得分别为 Android、iOS、小程序写三套代码。但用 uni-app,我们只需要写一套,然后在不同平台运行时做些微调

小程序先行策略

我们决定先做微信小程序版本,原因很简单:

  1. 校园推广最方便,扫码就能用
  2. 不需要用户下载安装
  3. 开发调试快,适合快速验证功能

写完核心功能后,在 HBuilderX 里点击"运行 → 运行到小程序模拟器 → 微信开发者工具",几秒钟就能看到效果。改完代码按保存,页面立刻刷新,这种即时反馈的开发体验,让我们的迭代速度飞快。

仅用三周,小程序第一版就上线了。

鸿蒙版的意外惊喜

老师建议我们支持鸿蒙平台,说这样在比赛中更有竞争力。说实话,一开始我心里是打鼓的:我们连 Android 原生都不会,怎么可能做鸿蒙应用?

但当我在 uni-app 官网看到"支持编译到鸿蒙"的介绍时,决定试一试。配置好证书,点击"发行 → 鸿蒙 App 打包",等待了几分钟......

安装包生成成功!

我把 .hap 文件装到队友的鸿蒙手机上,点击图标,应用启动了!界面流畅、功能正常,和小程序版几乎一模一样!

"卧槽,这也太神奇了吧!"老三拿着手机翻来覆去地测试,"同样的代码,居然真的能在鸿蒙上跑?"

那一刻我们才真正理解了 "一次开发,多端运行" 的含义。不是营销口号,而是实实在在的生产力提升。原本以为要花一个月单独开发的鸿蒙版,我们只用了一天就搞定了适配。

比赛路演:鸿蒙版成为最大亮点

省级"互联网+"创新创业大赛的路演日期到了。我们在演讲台上,把笔记本电脑、Android 手机、鸿蒙手机、平板全摆了出来。

"各位评委老师,我们的'校园人人帮'支持全平台运行。"我打开小程序,演示发布任务的流程,然后切到手机 App,演示接单操作,最后在鸿蒙平板上展示管理后台。

台下的评委们眼睛亮了起来。其中一位评委是华为的技术专家,他举手提问:"你们这个鸿蒙版本,是原生开发的吗?响应速度和适配效果都很不错。"

"报告老师,我们用的是 uni-app 跨平台框架,一套代码可以编译成不同平台的应用。"我有点紧张,不知道这个答案会不会被认为"不够原生"。

没想到,这位评委点了点头,笑着说:"很好,这才是正确的技术选型。初创团队资源有限,选对工具比硬拼技术更重要。而且现在跨平台框架的性能已经很接近原生了,你们做到了用最小的成本,覆盖最多的平台。"

另一位评委补充道:"特别是你们支持鸿蒙,说明团队有前瞻性,愿意拥抱国产生态。这个加分。"

最终,我们拿到了省赛二等奖。

虽然不是最高奖项,但对于四个非计算机专业的大学生来说,这已经是巨大的成功。更重要的是,通过这次比赛,我们验证了技术路线的正确性,也建立了对自己的信心。

真实落地:用户的认可最珍贵

比赛结束后,我们没有停止脚步。回到学校,开始在校内真正推广"校园人人帮"。

第一周,通过朋友圈、社团群、校园公众号宣传,有 200 多个同学注册了。第一天就有人发布了任务:

  • "明早7点帮我占图书馆考研座位,报酬10元" ✅ 5分钟被接单
  • "帮我去菜鸟驿站拿两个快递,报酬5元" ✅ 3分钟被接单
  • "帮我去东门买杯奶茶,报酬8元" ✅ 秒接

看着后台订单数据一条条跳动,四个人围在电脑前激动得跳了起来。"有人真的在用!"这种成就感,比拿奖更让人兴奋。

一个女生的感谢

印象最深的是一个女生用户。她连续一周在平台上发布"帮占座"任务,每次都能准时完成。有天她主动加了我们的客服微信,发来一段话:

"谢谢你们做了这个平台!我在准备考研,但宿舍离图书馆太远,每天早起占座真的很折磨。现在只要前一天晚上发个任务,第二天早上就能有人帮忙占好座位,我可以多睡一会儿。虽然花了点钱,但真的太值了!"

读到这段话,我们四个人都沉默了。原来技术的价值,不在于多么高深复杂,而在于能解决真实的问题,改善真实的生活。

快速迭代的能力

随着用户增多,我们收到了各种反馈和需求:

  • "能不能加个定位功能,显示任务地点?" → 3天加上了地图定位
  • "希望能看到接单人的信用评分" → 5天上线了评价系统
  • "消息通知总是不及时" → 2天优化了推送机制

因为用 uni-app 开发效率高,我们可以快速响应用户需求。每周发布一个小版本,三个月内迭代了 12 次。这种敏捷的开发节奏,让我们的产品体验持续优化,用户粘性越来越高。

扫码核销系统:半天就搞定的"大功能"

运营一段时间后,我们发现了一个问题:有些用户接了单却不去完成,或者完成了但双方对是否完成有争议。用户反馈说:"能不能做个扫码确认功能?发布者生成二维码,帮忙的人到现场扫码,这样就能证明真的完成了。"

这个需求听起来挺复杂:要生成二维码、要扫描识别、还要在多个平台都能用......如果是原生开发,光调研各平台的扫码 API 就得花好几天。

但在 uni-app 里,我只用了 半天时间 就完成了整个功能!

发布者端生成二维码:

// 订单确认页面,生成包含订单信息的二维码  
const qrCodeData = {  
  orderId: this.orderId,  
  userId: this.userId,  
  timestamp: Date.now()  
}  
// 使用 uni-app 的第三方组件库,直接渲染二维码  
this.qrCodeContent = JSON.stringify(qrCodeData)

接单者端扫码核销:

// 点击扫码按钮  
handleScan() {  
  uni.scanCode({  
    success: (res) => {  
      // 解析扫到的订单信息  
      const orderData = JSON.parse(res.result)  
      // 调用后端接口确认完成  
      this.confirmOrder(orderData)  
    },  
    fail: (err) => {  
      uni.showToast({ title: '扫码失败,请重试', icon: 'none' })  
    }  
  })  
}

就这么简单!uni.scanCode 这一个 API,在微信小程序、鸿蒙 App、Android App 上都能完美运行。不需要分别调用微信的 wx.scanCode、鸿蒙的扫码接口、Android 的 ZXing 库,uni-app 帮我们抹平了所有平台差异。

上线后,用户反馈说:"这个功能太实用了!现在帮忙取快递,当面扫一下码,钱就自动结算了,双方都放心。"扫码核销的使用率达到了 85%,大大提升了平台的信任度。

从提出需求到上线,整个过程只用了 一个下午加一个晚上。如果用原生开发,这至少是个一周的工作量。

还有一次紧急修复支付 bug,从发现问题、定位代码、改完测试、重新打包发布,全程只用了 3 小时。要是用原生开发,光配置打包环境就得半天,根本做不到这么快。

技术之外的深刻体会

回顾这段创业经历,uni-app 带给我们的不仅是技术层面的便利,更是三个深刻的认知升级:

第一,工具选对比技术深度更重要

我们不是最懂技术的团队,但我们选对了工具。uni-app 让我们能用有限的能力,做出超越能力范围的产品。这教会我们:创业不是比谁更厉害,而是比谁更会借力,谁能用最小的成本达成目标。

第二,小步快跑胜过完美主义

一开始我们想做得很完美,所有功能都规划好再上线。但 uni-app 的高效率让我们改变了策略:先上线最小可行产品(MVP),根据用户反馈快速迭代。事实证明,这种敏捷开发的思路,让我们少走了太多弯路。

第三,拥抱国产生态找到新机会

支持鸿蒙平台,让我们在比赛中获得了差异化优势。更重要的是,我们作为早期的鸿蒙应用开发者,积累了宝贵的经验。现在华为在大力推广鸿蒙系统,我们这份经历可能成为未来的核心竞争力。

给学弟学妹的建议

现在经常有学弟学妹问我:"学长,我也想做创业项目,但不太会编程怎么办?"

我的回答是:不要被技术门槛吓住,从现在的工具开始学起。

如果你有一点 Web 前端基础(哪怕只是 HTML + CSS),或者愿意花两周时间跟着教程学习,uni-app 就能帮你实现从想法到产品的跨越。它的学习曲线足够平缓,文档足够详细,社区足够活跃,非常适合学生创业团队。

而且,选择 uni-app 还有额外的好处:

  • 就业市场认可度高:跨平台开发是行业趋势,掌握 uni-app 的开发经验,找实习找工作都是加分项
  • 鸿蒙生态红利期:现在支持鸿蒙的开发者还不多,早入场意味着更多机会
  • 创业成本极低:不用组建庞大的技术团队,两三个人就能启动项目

最重要的是:不要等自己"准备好了"再开始,行动本身就是最好的学习。

未来的路还很长

到今天,"校园人人帮"已经运营了半年,校内注册用户突破 500 人,日均订单量稳定在 50 单左右。虽然规模不大,但每天都在解决真实的需求,每天都有同学因为我们的产品生活变得更便捷一点。

这种感觉,比任何成就感都更持久。

接下来,我们计划:

  • 推广到周边高校:验证模式的可复制性
  • 开发鸿蒙元服务:利用鸿蒙的快捷入口,降低用户使用门槛
  • 对接校园一卡通:实现更便捷的支付体验
  • 引入更多校园服务:打印、修电脑、租借物品......

技术方面,我们会继续深入学习 uni-app 的高级特性,也会关注鸿蒙生态的最新动态。毕竟,工具在进化,我们也要跟着成长。

致谢与寄语

感谢 DCloud 团队开发了 uni-app 这样优秀的跨平台框架,让我们这些非科班出身的学生也能实现技术梦想。

感谢华为鸿蒙团队的开放生态,给了我们展示作品的舞台。

感谢所有支持和使用"校园人人帮"的同学,你们的每一个订单、每一条反馈,都是我们坚持下去的动力。

也感谢那些还在为技术门槛发愁的学弟学妹们——如果我们四个"技术小白"都能做出产品并获奖,你们一定也可以。

真正的创新,不在于掌握多么高深的技术,而在于用合适的工具,解决真实的问题,创造真实的价值。

愿每一个有梦想的大学生,都能找到属于自己的技术工具,在年轻的时候勇敢试错,在国产生态的浪潮中留下自己的足迹。

星光不负,码向未来。我们的故事还在继续,你的故事也即将开始。

最后,再附上几张效果图

继续阅读 »

故事的开端:宿舍里诞生的创业想法

大三上学期的某个周末,宿舍四个人正窝在床上刷手机。老三突然抱怨:"又没抢到图书馆的座位,明天还得早起占座,太痛苦了!"老二接话:"要是能花点钱找人帮忙占座就好了。"这句话像一道闪电,点亮了我的思路。

"为什么不做个平台,专门解决校园里这些互助需求呢?"我坐起来,越说越兴奋:"帮拿快递、帮带外卖、帮占座位、帮打印材料......这些需求每天都在发生,但现在都是靠朋友圈和微信群,效率太低了!"

四个人一拍即合,当晚就开始构思项目方案。我们给它起名叫"校园人人帮"——人人都能发布需求,人人都能接单帮忙,顺便赚点生活费。方案写了整整一周,我们信心满满地向学校申报了创新创业项目,还准备参加省级"互联网+"大赛。

然后,现实给了我们一记重拳。

第一道坎:我们根本不会开发 App

拿到学校的项目支持资金后,我们才发现:想法很美好,但根本不知道怎么实现

团队四个人,老大学工商管理、老二学电子商务、老三学市场营销,只有我选修过前端开发课程,勉强会点 HTML 和 CSS。要做一个真正能用的应用,我们需要:

  • Android 开发?得学 Java 或 Kotlin
  • iOS 开发?得学 Swift,还得买 Mac
  • 小程序开发?好像容易些,但功能受限
  • 鸿蒙开发?老师说比赛评委看重这个,但要学 ArkTS

每一条路都像一座大山,横在我们面前。去外面找外包团队报价,起步就要三万块,我们的启动资金根本不够。招技术合伙人?校内发了一周招募帖,只有两个人来咨询,一听说还在初期阶段,没有工资,立刻就走了。

最绝望的时候,我甚至想过放弃。"也许我们就不是做技术的料",这个念头在脑海里转了无数次。

转机:B站上的一条视频

崩溃了几天后,我决定至少要努力到最后一刻。开始在 B站、知乎、CSDN 上疯狂搜索:"零基础怎么做 App"、"大学生创业适合用什么技术"、"跨平台开发工具推荐"......

某天凌晨两点,刷到一个 up 主的视频:《大学生必看!用 uni-app 一周做出你的第一个 App》。视频里,up 主演示了用熟悉的 Vue 语法写代码,然后一键生成 Android、iOS、小程序、H5 多个平台的应用。

我整个人都精神了,立刻把视频发到宿舍群:"兄弟们,也许有救了!"

第二天一早,我就下载了 HBuilderX,跟着官方文档的 "快速上手" 开始尝试。神奇的事情发生了:

那些我在前端课上学过的 Vue 语法,在 uni-app 里几乎能直接用!

写一个任务列表页面,代码结构和我之前写的课程作业差不多:

<template>  
  <view class="task-list">  
    <view v-for="task in tasks" :key="task.id" class="task-item">  
      <text>{{ task.title }}</text>  
      <text class="reward">¥{{ task.reward }}</text>  
    </view>  
  </view>  
</template>

更神奇的是,我把官方文档分享给另外两个队友,他们一个学电子商务,一个学工商管理,之前完全没碰过代码。但跟着教程做了两天 Demo 之后,居然也能写简单的页面了。有个队友还兴奋地在群里说:"原来写代码没那么难,感觉就像搭积木一样!"

<template>  
  <view class="task-list">  
    <view v-for="task in tasks" :key="task.id" class="task-item">  
      <text>{{ task.title }}</text>  
      <text class="reward">¥{{ task.reward }}</text>  
    </view>  
  </view>  
</template>

v-for 循环、双括号插值、:key 绑定......这些不就是 Vue 的语法吗?我花了一个下午,就把第一个页面做出来了。在 HBuilderX 里点击"运行到浏览器",页面真的显示出来了!那一刻的激动,现在想起来还记忆犹新。

说服队友:技术门槛不再是问题

有了第一个 Demo,我立刻把另外三个队友拉到电脑前演示。"你们看,就写这么点代码,就能做出一个页面了!而且这套代码,可以同时在微信小程序、App、网页上运行!"

老二半信半疑:"我们又不会 Vue,能学会吗?"

"简单!"我打开官方教程,"你们看这个新手指南,图文并茂,还有视频讲解。咱们每天晚上花两小时学,一周就能上手。"

接下来的一周,宿舍变成了临时"培训班"。我带着他们从最基础的概念开始学:什么是组件、什么是数据绑定、什么是生命周期......每天晚上10点到12点,四台电脑的屏幕一起亮着,敲代码的键盘声此起彼伏。

第五天,老二做出了第一个功能——"任务发布表单"。
第七天,老三完成了"个人中心页面"。
第九天,老大(学工商管理的那位)居然把"订单列表"也做出来了。

看着他们从一脸懵逼到能独立写代码,我深刻体会到了 uni-app 的威力:它真的把编程的门槛降到了极低。你不需要是计算机专业,不需要学几年编程,只要愿意投入时间,跟着文档一步步来,就能把想法变成产品。

开发加速度:一套代码适配所有平台

学会基础语法后,我们开始全速推进开发。按照项目规划,核心功能包括:

  • 任务发布与浏览
  • 接单与抢单
  • 在线支付与结算
  • 实时消息通知
  • 用户信用评价体系

如果用传统的原生开发,我们得分别为 Android、iOS、小程序写三套代码。但用 uni-app,我们只需要写一套,然后在不同平台运行时做些微调

小程序先行策略

我们决定先做微信小程序版本,原因很简单:

  1. 校园推广最方便,扫码就能用
  2. 不需要用户下载安装
  3. 开发调试快,适合快速验证功能

写完核心功能后,在 HBuilderX 里点击"运行 → 运行到小程序模拟器 → 微信开发者工具",几秒钟就能看到效果。改完代码按保存,页面立刻刷新,这种即时反馈的开发体验,让我们的迭代速度飞快。

仅用三周,小程序第一版就上线了。

鸿蒙版的意外惊喜

老师建议我们支持鸿蒙平台,说这样在比赛中更有竞争力。说实话,一开始我心里是打鼓的:我们连 Android 原生都不会,怎么可能做鸿蒙应用?

但当我在 uni-app 官网看到"支持编译到鸿蒙"的介绍时,决定试一试。配置好证书,点击"发行 → 鸿蒙 App 打包",等待了几分钟......

安装包生成成功!

我把 .hap 文件装到队友的鸿蒙手机上,点击图标,应用启动了!界面流畅、功能正常,和小程序版几乎一模一样!

"卧槽,这也太神奇了吧!"老三拿着手机翻来覆去地测试,"同样的代码,居然真的能在鸿蒙上跑?"

那一刻我们才真正理解了 "一次开发,多端运行" 的含义。不是营销口号,而是实实在在的生产力提升。原本以为要花一个月单独开发的鸿蒙版,我们只用了一天就搞定了适配。

比赛路演:鸿蒙版成为最大亮点

省级"互联网+"创新创业大赛的路演日期到了。我们在演讲台上,把笔记本电脑、Android 手机、鸿蒙手机、平板全摆了出来。

"各位评委老师,我们的'校园人人帮'支持全平台运行。"我打开小程序,演示发布任务的流程,然后切到手机 App,演示接单操作,最后在鸿蒙平板上展示管理后台。

台下的评委们眼睛亮了起来。其中一位评委是华为的技术专家,他举手提问:"你们这个鸿蒙版本,是原生开发的吗?响应速度和适配效果都很不错。"

"报告老师,我们用的是 uni-app 跨平台框架,一套代码可以编译成不同平台的应用。"我有点紧张,不知道这个答案会不会被认为"不够原生"。

没想到,这位评委点了点头,笑着说:"很好,这才是正确的技术选型。初创团队资源有限,选对工具比硬拼技术更重要。而且现在跨平台框架的性能已经很接近原生了,你们做到了用最小的成本,覆盖最多的平台。"

另一位评委补充道:"特别是你们支持鸿蒙,说明团队有前瞻性,愿意拥抱国产生态。这个加分。"

最终,我们拿到了省赛二等奖。

虽然不是最高奖项,但对于四个非计算机专业的大学生来说,这已经是巨大的成功。更重要的是,通过这次比赛,我们验证了技术路线的正确性,也建立了对自己的信心。

真实落地:用户的认可最珍贵

比赛结束后,我们没有停止脚步。回到学校,开始在校内真正推广"校园人人帮"。

第一周,通过朋友圈、社团群、校园公众号宣传,有 200 多个同学注册了。第一天就有人发布了任务:

  • "明早7点帮我占图书馆考研座位,报酬10元" ✅ 5分钟被接单
  • "帮我去菜鸟驿站拿两个快递,报酬5元" ✅ 3分钟被接单
  • "帮我去东门买杯奶茶,报酬8元" ✅ 秒接

看着后台订单数据一条条跳动,四个人围在电脑前激动得跳了起来。"有人真的在用!"这种成就感,比拿奖更让人兴奋。

一个女生的感谢

印象最深的是一个女生用户。她连续一周在平台上发布"帮占座"任务,每次都能准时完成。有天她主动加了我们的客服微信,发来一段话:

"谢谢你们做了这个平台!我在准备考研,但宿舍离图书馆太远,每天早起占座真的很折磨。现在只要前一天晚上发个任务,第二天早上就能有人帮忙占好座位,我可以多睡一会儿。虽然花了点钱,但真的太值了!"

读到这段话,我们四个人都沉默了。原来技术的价值,不在于多么高深复杂,而在于能解决真实的问题,改善真实的生活。

快速迭代的能力

随着用户增多,我们收到了各种反馈和需求:

  • "能不能加个定位功能,显示任务地点?" → 3天加上了地图定位
  • "希望能看到接单人的信用评分" → 5天上线了评价系统
  • "消息通知总是不及时" → 2天优化了推送机制

因为用 uni-app 开发效率高,我们可以快速响应用户需求。每周发布一个小版本,三个月内迭代了 12 次。这种敏捷的开发节奏,让我们的产品体验持续优化,用户粘性越来越高。

扫码核销系统:半天就搞定的"大功能"

运营一段时间后,我们发现了一个问题:有些用户接了单却不去完成,或者完成了但双方对是否完成有争议。用户反馈说:"能不能做个扫码确认功能?发布者生成二维码,帮忙的人到现场扫码,这样就能证明真的完成了。"

这个需求听起来挺复杂:要生成二维码、要扫描识别、还要在多个平台都能用......如果是原生开发,光调研各平台的扫码 API 就得花好几天。

但在 uni-app 里,我只用了 半天时间 就完成了整个功能!

发布者端生成二维码:

// 订单确认页面,生成包含订单信息的二维码  
const qrCodeData = {  
  orderId: this.orderId,  
  userId: this.userId,  
  timestamp: Date.now()  
}  
// 使用 uni-app 的第三方组件库,直接渲染二维码  
this.qrCodeContent = JSON.stringify(qrCodeData)

接单者端扫码核销:

// 点击扫码按钮  
handleScan() {  
  uni.scanCode({  
    success: (res) => {  
      // 解析扫到的订单信息  
      const orderData = JSON.parse(res.result)  
      // 调用后端接口确认完成  
      this.confirmOrder(orderData)  
    },  
    fail: (err) => {  
      uni.showToast({ title: '扫码失败,请重试', icon: 'none' })  
    }  
  })  
}

就这么简单!uni.scanCode 这一个 API,在微信小程序、鸿蒙 App、Android App 上都能完美运行。不需要分别调用微信的 wx.scanCode、鸿蒙的扫码接口、Android 的 ZXing 库,uni-app 帮我们抹平了所有平台差异。

上线后,用户反馈说:"这个功能太实用了!现在帮忙取快递,当面扫一下码,钱就自动结算了,双方都放心。"扫码核销的使用率达到了 85%,大大提升了平台的信任度。

从提出需求到上线,整个过程只用了 一个下午加一个晚上。如果用原生开发,这至少是个一周的工作量。

还有一次紧急修复支付 bug,从发现问题、定位代码、改完测试、重新打包发布,全程只用了 3 小时。要是用原生开发,光配置打包环境就得半天,根本做不到这么快。

技术之外的深刻体会

回顾这段创业经历,uni-app 带给我们的不仅是技术层面的便利,更是三个深刻的认知升级:

第一,工具选对比技术深度更重要

我们不是最懂技术的团队,但我们选对了工具。uni-app 让我们能用有限的能力,做出超越能力范围的产品。这教会我们:创业不是比谁更厉害,而是比谁更会借力,谁能用最小的成本达成目标。

第二,小步快跑胜过完美主义

一开始我们想做得很完美,所有功能都规划好再上线。但 uni-app 的高效率让我们改变了策略:先上线最小可行产品(MVP),根据用户反馈快速迭代。事实证明,这种敏捷开发的思路,让我们少走了太多弯路。

第三,拥抱国产生态找到新机会

支持鸿蒙平台,让我们在比赛中获得了差异化优势。更重要的是,我们作为早期的鸿蒙应用开发者,积累了宝贵的经验。现在华为在大力推广鸿蒙系统,我们这份经历可能成为未来的核心竞争力。

给学弟学妹的建议

现在经常有学弟学妹问我:"学长,我也想做创业项目,但不太会编程怎么办?"

我的回答是:不要被技术门槛吓住,从现在的工具开始学起。

如果你有一点 Web 前端基础(哪怕只是 HTML + CSS),或者愿意花两周时间跟着教程学习,uni-app 就能帮你实现从想法到产品的跨越。它的学习曲线足够平缓,文档足够详细,社区足够活跃,非常适合学生创业团队。

而且,选择 uni-app 还有额外的好处:

  • 就业市场认可度高:跨平台开发是行业趋势,掌握 uni-app 的开发经验,找实习找工作都是加分项
  • 鸿蒙生态红利期:现在支持鸿蒙的开发者还不多,早入场意味着更多机会
  • 创业成本极低:不用组建庞大的技术团队,两三个人就能启动项目

最重要的是:不要等自己"准备好了"再开始,行动本身就是最好的学习。

未来的路还很长

到今天,"校园人人帮"已经运营了半年,校内注册用户突破 500 人,日均订单量稳定在 50 单左右。虽然规模不大,但每天都在解决真实的需求,每天都有同学因为我们的产品生活变得更便捷一点。

这种感觉,比任何成就感都更持久。

接下来,我们计划:

  • 推广到周边高校:验证模式的可复制性
  • 开发鸿蒙元服务:利用鸿蒙的快捷入口,降低用户使用门槛
  • 对接校园一卡通:实现更便捷的支付体验
  • 引入更多校园服务:打印、修电脑、租借物品......

技术方面,我们会继续深入学习 uni-app 的高级特性,也会关注鸿蒙生态的最新动态。毕竟,工具在进化,我们也要跟着成长。

致谢与寄语

感谢 DCloud 团队开发了 uni-app 这样优秀的跨平台框架,让我们这些非科班出身的学生也能实现技术梦想。

感谢华为鸿蒙团队的开放生态,给了我们展示作品的舞台。

感谢所有支持和使用"校园人人帮"的同学,你们的每一个订单、每一条反馈,都是我们坚持下去的动力。

也感谢那些还在为技术门槛发愁的学弟学妹们——如果我们四个"技术小白"都能做出产品并获奖,你们一定也可以。

真正的创新,不在于掌握多么高深的技术,而在于用合适的工具,解决真实的问题,创造真实的价值。

愿每一个有梦想的大学生,都能找到属于自己的技术工具,在年轻的时候勇敢试错,在国产生态的浪潮中留下自己的足迹。

星光不负,码向未来。我们的故事还在继续,你的故事也即将开始。

最后,再附上几张效果图

收起阅读 »

【鸿蒙征文】折腾鸿蒙分享功能的那些事儿

uts插件 鸿蒙征文

事情是这样的

前两天在家撸代码,突然想给我的小破app加个分享功能。本来想偷个懒,直接用现成的插件算了。结果一看,好家伙,都要收费。我寻思着这玩意儿能有多难?不就是调个系统API嘛,自己搞一个呗。

没想到这一入坑,还真挺有意思。鸟蒙的API设计还挺人性化的,虽然踩了不少坑,但学到的东西也不少。

文件咋放的

也没搞得很复杂,就几个文件:

cool-share/  
├── utssdk/  
    ├── interface.uts      # 给别人用的接口  
    └── app-harmony/       # 鸿蒙专用代码  
        ├── index.uts      # 入口文件  
        └── share.ets      # 干活的代码

先定个规矩

接口嘛,就是告诉别人怎么调用我这个东西:

export type ShareWithSystemOptions = {  
    type: string; // 分享啥类型的玩意儿  
    title?: string; // 起个标题  
    summary?: string; // 写点描述  
    href?: string; // 链接或者文件在哪儿  
    imageUrl?: string; // 图片视频的地址  
    success?: () => void; // 成功了干啥  
    fail?: (error: string) => void; // 失败了咋办  
};

开始干活了

这块儿是重点,也是我掉坑最多的地方。

先把家伙事儿准备好

import { systemShare } from "@kit.ShareKit";  
import { uniformTypeDescriptor as utd } from "@kit.ArkData";  
import { common } from "@kit.AbilityKit";  
import { fileUri } from "@kit.CoreFileKit";  
import { UTSHarmony } from "@dcloudio/uni-app-x-runtime";

这些都是鸿蒙给咱准备的工具,分别管分享、识别文件类型、获取应用信息、处理文件路径这些活儿。

分享类型定义

enum ShareType {  
    TEXT = "text", // 纯文本  
    IMAGE = "image", // 图片  
    VIDEO = "video", // 视频  
    AUDIO = "audio", // 音频  
    FILE = "file", // 文件  
    LINK = "link" // 链接  
}

分享图片咋整

图片分享最常用,咱先搞这个:

function createImageShareData(  
    imageUrl: string,  
    title: string,  
    summary: string  
): systemShare.SharedData | null {  
    if (imageUrl === "") {  
        return null;  
    }  

    // 这里要注意,需要先获取正确的文件路径  
    const filePath = UTSHarmony.getResourcePath(imageUrl);  

    // 然后获取文件的数据类型标识符  
    const utdTypeId = getUtdTypeByPath(filePath, utd.UniformDataType.IMAGE);  

    // 最后创建分享数据对象  
    return new systemShare.SharedData({  
        utd: utdTypeId, // 数据类型  
        uri: fileUri.getUriFromPath(filePath), // 文件URI  
        title: title, // 标题  
        description: summary // 描述  
    });  
}

怎么用这玩意儿

在你的页面里这么写就行:

import { shareWithSystem } from "@/uni_modules/cool-share";  

// 分享个图片  
shareWithSystem({  
    type: "image",  
    title: "我拍的照片",  
    summary: "今天拍的,还不错吧",  
    imageUrl: "https://cool-js.com/logo.png",  
    success: () => {  
        console.log("success");  
    },  
    fail: (error) => {  
        console.log(error);  
    }  
});  

// 分享点文字  
shareWithSystem({  
    type: "text",  
    title: "今日心情",  
    summary: "今天天气不错,心情美美哒~",  
    success: () => {  
        console.log("success");  
    }  
});  

// 分享个链接  
shareWithSystem({  
    type: "link",  
    title: "发现个好网站",  
    summary: "这网站挺有意思的,你们看看",  
    href: "https://cool-js.com/",  
    success: () => {  
        console.log("success");  
    }  
});

UTD是个啥东西

鸿蒙用UTD(统一数据类型标识符)来识别文件类型。简单说就是告诉系统:"嘿,这是个图片!"或者"这是个视频!"

function getUtdTypeByPath(filePath: string, defaultType: string): string {  
    const ext = filePath?.split(".")?.pop()?.toLowerCase() ?? "";  
    if (ext === "") {  
        return defaultType;  
    }  
    return utd.getUniformDataTypeByFilenameExtension("." + ext, defaultType);  
}

这个函数就是看文件后缀名,.jpg就知道是图片,.mp4就知道是视频,就这么简单。

文件分享稍微麻烦点

文件分享比较复杂,得看是啥类型的文件:

function createFileShareData(  
    filePath: string,  
    title: string,  
    summary: string  
): systemShare.SharedData | null {  
    if (filePath === "") {  
        return null;  
    }  

    const resourcePath = UTSHarmony.getResourcePath(filePath);  
    const ext = resourcePath?.split(".")?.pop()?.toLowerCase() ?? "";  

    // 根据文件扩展名确定数据类型  
    let utdType = utd.UniformDataType.FILE;  

    switch (ext) {  
        case "zip":  
        case "rar":  
        case "7z":  
            utdType = utd.UniformDataType.ARCHIVE; // 压缩包  
            break;  
        case "pdf":  
            utdType = utd.UniformDataType.PDF; // PDF文档  
            break;  
        case "doc":  
        case "docx":  
            utdType = utd.UniformDataType.WORD_DOC; // Word文档  
            break;  
        case "xls":  
        case "xlsx":  
            utdType = utd.UniformDataType.EXCEL; // Excel表格  
            break;  
        default:  
            utdType = utd.UniformDataType.FILE; // 普通文件  
            break;  
    }  

    const utdTypeId = utd.getUniformDataTypeByFilenameExtension("." + ext, utdType);  

    return new systemShare.SharedData({  
        utd: utdTypeId,  
        uri: fileUri.getUriFromPath(resourcePath),  
        title: title,  
        description: summary  
    });  
}

最后一步,弹出分享框

数据都准备好了,现在可以叫出系统的分享面板了:

export function share(  
    type: string,  
    title: string,  
    summary: string,  
    href: string,  
    imageUrl: string,  
    success: () => void,  
    fail: (error: string) => void  
): void {  
    // 先获取当前应用的上下文,这个是必须的  
    const uiContext: UIContext = UTSHarmony.getCurrentWindow()?.getUIContext();  
    const context: common.UIAbilityContext = uiContext.getHostContext() as common.UIAbilityContext;  

    // 根据不同类型创建对应的分享数据  
    let shareData: systemShare.SharedData | null = null;  
    let errorMsg = "";  

    switch (type) {  
        case ShareType.IMAGE:  
            shareData = createImageShareData(imageUrl, title, summary);  
            errorMsg = "图片路径不能为空";  
            break;  
        case ShareType.VIDEO:  
            shareData = createVideoShareData(imageUrl, title, summary);  
            errorMsg = "视频路径不能为空";  
            break;  
        case ShareType.LINK:  
            shareData = createLinkShareData(href, title, summary);  
            break;  
        default:  
            // 默认当文本处理  
            shareData = createTextShareData(title, summary);  
            break;  
    }  

    // 检查数据是否有效  
    if (shareData === null) {  
        fail(errorMsg);  
        return;  
    }  

    // 创建分享控制器  
    const controller: systemShare.ShareController = new systemShare.ShareController(shareData);  

    // 显示分享面板  
    controller  
        .show(context, {  
            selectionMode: systemShare.SelectionMode.SINGLE, // 单选模式  
            previewMode: systemShare.SharePreviewMode.DEFAULT // 默认预览  
        })  
        .then(() => {  
            success(); // 分享成功  
        })  
        .catch((error: BusinessError) => {  
            fail(error?.message ?? "分享失败"); // 分享失败  
        });  
}

ETS开发小技巧

在写这个插件的过程中,我总结了一些ETS开发的小技巧:

1. 空值安全处理

ETS对空值检查很严格,要养成使用??操作符的习惯:

// 好习惯:使用空值合并操作符  
const ext = filePath?.split(".")?.pop()?.toLowerCase() ?? "";  

// 而不是这样(可能报错)  
const ext = filePath.split(".").pop().toLowerCase();

2. 类型断言要谨慎

尽量避免使用as进行强制类型转换,多用类型检查:

// 推荐的写法  
if (context instanceof common.UIAbilityContext) {  
    // 安全地使用context  
}  

// 而不是直接断言  
const context = uiContext.getHostContext() as common.UIAbilityContext;

3. 错误处理要完整

鸿蒙的异步操作都是Promise,记得处理catch:

controller  
    .show(context, options)  
    .then(() => {  
        success();  
    })  
    .catch((error: BusinessError) => {  
        // 一定要处理错误情况  
        const errorMessage = error?.message ?? "未知错误";  
        fail(errorMessage);  
    });

4. 文件路径处理

鸿蒙对文件路径很敏感,一定要用正确的API获取路径:

// 正确的做法  
const filePath = UTSHarmony.getResourcePath(imageUrl);  
const uri = fileUri.getUriFromPath(filePath);  

// 而不是直接拼接路径

我踩过的那些坑

1. 分享框死活出不来

刚开始的时候,代码写好了,点击分享按钮啥反应都没有。搞了半天才发现是context获取有问题:

// 错误的做法 - 可能获取不到context  
const context = UTSHarmony.getUniActivity();  

// 正确的做法  
const uiContext = UTSHarmony.getCurrentWindow()?.getUIContext();  
const context = uiContext.getHostContext() as common.UIAbilityContext;

2. 文件跟我玩捉迷藏

本地文件分享老是失败,我还以为是代码逻辑有问题。结果折腾了一晚上才发现,是文件路径搞错了:

// 错误:直接使用相对路径  
imageUrl: "./static/image.jpg";  

// 正确:使用绝对路径或者让UTSHarmony处理  
imageUrl: "/static/image.jpg";  
const realPath = UTSHarmony.getResourcePath(imageUrl);

3. 文件类型识别翻车了

有时候文件类型识别不对,分享到微信QQ就会出现奇怪的问题:

// 保险的做法:先判断扩展名,再设置UTD类型  
let utdType = utd.UniformDataType.FILE; // 默认类型  
switch (ext) {  
    case "jpg":  
    case "jpeg":  
    case "png":  
        utdType = utd.UniformDataType.IMAGE;  
        break;  
    // 其他类型...  
}

4. 异步操作坑死人

Promise的错误处理千万别偷懒,不然出了问题你都不知道哪里错了,我就吃过这个亏:

controller  
    .show(context, options)  
    .then(() => {  
        console.log("分享成功"); // 调试信息很重要  
        success();  
    })  
    .catch((error: BusinessError) => {  
        console.error("分享失败:", error); // 打印错误信息  
        fail(error?.message ?? "分享失败");  
    });

5. 权限这茬儿

有时候会遇到权限不够的情况,特别是读取文件的时候。记得检查app的权限配置,不然用户点了分享啥反应都没有。

一些常见问题

Q: 分享面板怎么是空白的?

A: 检查这几个地方:

  1. context获取对了没
  2. ShareData有没有创建成功(别返回null)
  3. 文件路径对不对
  4. UTD类型匹配不

Q: 为啥有些app收不到我分享的东西?

A: 估计是UTD类型不匹配,或者那个app不认这种数据格式。试试用通用一点的类型,比如utd.UniformDataType.FILE

Q: 网络上的图片能直接分享吗?

A: 不行,得先下载到本地,然后分享本地文件。

Q: 分享大文件会卡吗?

A: 会的,特别是视频文件。建议加个转圈圈的loading,或者提前压缩一下。

Q: 能知道用户选了哪个app分享吗?

A: 目前鸿蒙的API不支持,只能知道用户是分享成功了还是取消了。

Q: 能自己做个分享面板吗?

A: 不行,只能用系统提供的。不过可以通过selectionModepreviewMode参数稍微调整一下样式。

测试这块儿

真机测试的时候发现了不少问题,建议你们也多试试:

  • 各种文件类型:图片、视频、文档啥的都试试
  • 文件大小:小图片秒传,大视频可能要等一会儿
  • 不同app:微信、QQ、邮箱的接收效果都不一样
  • 网络状况:网不好的时候网络图片可能加载不出来

调试的时候多打印,鸿蒙的报错信息有时候说得不够清楚。

总结

折腾了好几天,总算把这个分享功能搞定了。整体感觉鸿蒙的API还挺人性化的,比我想象中好用多了。

几个心得:

  1. 多翻文档:鸿蒙官方文档写得还可以,遇到问题先去翻翻
  2. 类型别偷懒:ETS的类型检查确实严格,但写出来的代码更稳定
  3. 错误要处理好:异步操作的错误处理千万别省,不然出了bug找都找不到
  4. 真机多测试:模拟器和真机表现可能不一样,都试试保险点

现在这个小插件基本能满足日常需要了。如果你也在搞类似的东西,希望我这些踩坑经验能帮到你。有问题咱们可以一起交流。

所有代码都在uni_modules/cool-share目录里,有兴趣的朋友可以看看。

顺便安利个东西

我们团队还做了个 Cool Unix 组件库,也是基于 Uni-App X 的,完全免费开源。里面有 Tailwind CSS、多主题、国际化这些实用功能,还有挺多现成的组件和页面模板。

最有意思的是,这个组件库配合AI生成鸿蒙页面效果特别好,以后还准备加上后端接口自动生成,真正做到一句话就能搞出个app。如果你也想快速开发鸿蒙应用,可以试试看。

最后感谢一下

感谢 uni-app x 团队做出这么棒的跨平台框架,让我们这些普通开发者也能轻松搞定多端开发。特别是UTS语言和鸿蒙支持,真的降低了不少门槛。

继续阅读 »

事情是这样的

前两天在家撸代码,突然想给我的小破app加个分享功能。本来想偷个懒,直接用现成的插件算了。结果一看,好家伙,都要收费。我寻思着这玩意儿能有多难?不就是调个系统API嘛,自己搞一个呗。

没想到这一入坑,还真挺有意思。鸟蒙的API设计还挺人性化的,虽然踩了不少坑,但学到的东西也不少。

文件咋放的

也没搞得很复杂,就几个文件:

cool-share/  
├── utssdk/  
    ├── interface.uts      # 给别人用的接口  
    └── app-harmony/       # 鸿蒙专用代码  
        ├── index.uts      # 入口文件  
        └── share.ets      # 干活的代码

先定个规矩

接口嘛,就是告诉别人怎么调用我这个东西:

export type ShareWithSystemOptions = {  
    type: string; // 分享啥类型的玩意儿  
    title?: string; // 起个标题  
    summary?: string; // 写点描述  
    href?: string; // 链接或者文件在哪儿  
    imageUrl?: string; // 图片视频的地址  
    success?: () => void; // 成功了干啥  
    fail?: (error: string) => void; // 失败了咋办  
};

开始干活了

这块儿是重点,也是我掉坑最多的地方。

先把家伙事儿准备好

import { systemShare } from "@kit.ShareKit";  
import { uniformTypeDescriptor as utd } from "@kit.ArkData";  
import { common } from "@kit.AbilityKit";  
import { fileUri } from "@kit.CoreFileKit";  
import { UTSHarmony } from "@dcloudio/uni-app-x-runtime";

这些都是鸿蒙给咱准备的工具,分别管分享、识别文件类型、获取应用信息、处理文件路径这些活儿。

分享类型定义

enum ShareType {  
    TEXT = "text", // 纯文本  
    IMAGE = "image", // 图片  
    VIDEO = "video", // 视频  
    AUDIO = "audio", // 音频  
    FILE = "file", // 文件  
    LINK = "link" // 链接  
}

分享图片咋整

图片分享最常用,咱先搞这个:

function createImageShareData(  
    imageUrl: string,  
    title: string,  
    summary: string  
): systemShare.SharedData | null {  
    if (imageUrl === "") {  
        return null;  
    }  

    // 这里要注意,需要先获取正确的文件路径  
    const filePath = UTSHarmony.getResourcePath(imageUrl);  

    // 然后获取文件的数据类型标识符  
    const utdTypeId = getUtdTypeByPath(filePath, utd.UniformDataType.IMAGE);  

    // 最后创建分享数据对象  
    return new systemShare.SharedData({  
        utd: utdTypeId, // 数据类型  
        uri: fileUri.getUriFromPath(filePath), // 文件URI  
        title: title, // 标题  
        description: summary // 描述  
    });  
}

怎么用这玩意儿

在你的页面里这么写就行:

import { shareWithSystem } from "@/uni_modules/cool-share";  

// 分享个图片  
shareWithSystem({  
    type: "image",  
    title: "我拍的照片",  
    summary: "今天拍的,还不错吧",  
    imageUrl: "https://cool-js.com/logo.png",  
    success: () => {  
        console.log("success");  
    },  
    fail: (error) => {  
        console.log(error);  
    }  
});  

// 分享点文字  
shareWithSystem({  
    type: "text",  
    title: "今日心情",  
    summary: "今天天气不错,心情美美哒~",  
    success: () => {  
        console.log("success");  
    }  
});  

// 分享个链接  
shareWithSystem({  
    type: "link",  
    title: "发现个好网站",  
    summary: "这网站挺有意思的,你们看看",  
    href: "https://cool-js.com/",  
    success: () => {  
        console.log("success");  
    }  
});

UTD是个啥东西

鸿蒙用UTD(统一数据类型标识符)来识别文件类型。简单说就是告诉系统:"嘿,这是个图片!"或者"这是个视频!"

function getUtdTypeByPath(filePath: string, defaultType: string): string {  
    const ext = filePath?.split(".")?.pop()?.toLowerCase() ?? "";  
    if (ext === "") {  
        return defaultType;  
    }  
    return utd.getUniformDataTypeByFilenameExtension("." + ext, defaultType);  
}

这个函数就是看文件后缀名,.jpg就知道是图片,.mp4就知道是视频,就这么简单。

文件分享稍微麻烦点

文件分享比较复杂,得看是啥类型的文件:

function createFileShareData(  
    filePath: string,  
    title: string,  
    summary: string  
): systemShare.SharedData | null {  
    if (filePath === "") {  
        return null;  
    }  

    const resourcePath = UTSHarmony.getResourcePath(filePath);  
    const ext = resourcePath?.split(".")?.pop()?.toLowerCase() ?? "";  

    // 根据文件扩展名确定数据类型  
    let utdType = utd.UniformDataType.FILE;  

    switch (ext) {  
        case "zip":  
        case "rar":  
        case "7z":  
            utdType = utd.UniformDataType.ARCHIVE; // 压缩包  
            break;  
        case "pdf":  
            utdType = utd.UniformDataType.PDF; // PDF文档  
            break;  
        case "doc":  
        case "docx":  
            utdType = utd.UniformDataType.WORD_DOC; // Word文档  
            break;  
        case "xls":  
        case "xlsx":  
            utdType = utd.UniformDataType.EXCEL; // Excel表格  
            break;  
        default:  
            utdType = utd.UniformDataType.FILE; // 普通文件  
            break;  
    }  

    const utdTypeId = utd.getUniformDataTypeByFilenameExtension("." + ext, utdType);  

    return new systemShare.SharedData({  
        utd: utdTypeId,  
        uri: fileUri.getUriFromPath(resourcePath),  
        title: title,  
        description: summary  
    });  
}

最后一步,弹出分享框

数据都准备好了,现在可以叫出系统的分享面板了:

export function share(  
    type: string,  
    title: string,  
    summary: string,  
    href: string,  
    imageUrl: string,  
    success: () => void,  
    fail: (error: string) => void  
): void {  
    // 先获取当前应用的上下文,这个是必须的  
    const uiContext: UIContext = UTSHarmony.getCurrentWindow()?.getUIContext();  
    const context: common.UIAbilityContext = uiContext.getHostContext() as common.UIAbilityContext;  

    // 根据不同类型创建对应的分享数据  
    let shareData: systemShare.SharedData | null = null;  
    let errorMsg = "";  

    switch (type) {  
        case ShareType.IMAGE:  
            shareData = createImageShareData(imageUrl, title, summary);  
            errorMsg = "图片路径不能为空";  
            break;  
        case ShareType.VIDEO:  
            shareData = createVideoShareData(imageUrl, title, summary);  
            errorMsg = "视频路径不能为空";  
            break;  
        case ShareType.LINK:  
            shareData = createLinkShareData(href, title, summary);  
            break;  
        default:  
            // 默认当文本处理  
            shareData = createTextShareData(title, summary);  
            break;  
    }  

    // 检查数据是否有效  
    if (shareData === null) {  
        fail(errorMsg);  
        return;  
    }  

    // 创建分享控制器  
    const controller: systemShare.ShareController = new systemShare.ShareController(shareData);  

    // 显示分享面板  
    controller  
        .show(context, {  
            selectionMode: systemShare.SelectionMode.SINGLE, // 单选模式  
            previewMode: systemShare.SharePreviewMode.DEFAULT // 默认预览  
        })  
        .then(() => {  
            success(); // 分享成功  
        })  
        .catch((error: BusinessError) => {  
            fail(error?.message ?? "分享失败"); // 分享失败  
        });  
}

ETS开发小技巧

在写这个插件的过程中,我总结了一些ETS开发的小技巧:

1. 空值安全处理

ETS对空值检查很严格,要养成使用??操作符的习惯:

// 好习惯:使用空值合并操作符  
const ext = filePath?.split(".")?.pop()?.toLowerCase() ?? "";  

// 而不是这样(可能报错)  
const ext = filePath.split(".").pop().toLowerCase();

2. 类型断言要谨慎

尽量避免使用as进行强制类型转换,多用类型检查:

// 推荐的写法  
if (context instanceof common.UIAbilityContext) {  
    // 安全地使用context  
}  

// 而不是直接断言  
const context = uiContext.getHostContext() as common.UIAbilityContext;

3. 错误处理要完整

鸿蒙的异步操作都是Promise,记得处理catch:

controller  
    .show(context, options)  
    .then(() => {  
        success();  
    })  
    .catch((error: BusinessError) => {  
        // 一定要处理错误情况  
        const errorMessage = error?.message ?? "未知错误";  
        fail(errorMessage);  
    });

4. 文件路径处理

鸿蒙对文件路径很敏感,一定要用正确的API获取路径:

// 正确的做法  
const filePath = UTSHarmony.getResourcePath(imageUrl);  
const uri = fileUri.getUriFromPath(filePath);  

// 而不是直接拼接路径

我踩过的那些坑

1. 分享框死活出不来

刚开始的时候,代码写好了,点击分享按钮啥反应都没有。搞了半天才发现是context获取有问题:

// 错误的做法 - 可能获取不到context  
const context = UTSHarmony.getUniActivity();  

// 正确的做法  
const uiContext = UTSHarmony.getCurrentWindow()?.getUIContext();  
const context = uiContext.getHostContext() as common.UIAbilityContext;

2. 文件跟我玩捉迷藏

本地文件分享老是失败,我还以为是代码逻辑有问题。结果折腾了一晚上才发现,是文件路径搞错了:

// 错误:直接使用相对路径  
imageUrl: "./static/image.jpg";  

// 正确:使用绝对路径或者让UTSHarmony处理  
imageUrl: "/static/image.jpg";  
const realPath = UTSHarmony.getResourcePath(imageUrl);

3. 文件类型识别翻车了

有时候文件类型识别不对,分享到微信QQ就会出现奇怪的问题:

// 保险的做法:先判断扩展名,再设置UTD类型  
let utdType = utd.UniformDataType.FILE; // 默认类型  
switch (ext) {  
    case "jpg":  
    case "jpeg":  
    case "png":  
        utdType = utd.UniformDataType.IMAGE;  
        break;  
    // 其他类型...  
}

4. 异步操作坑死人

Promise的错误处理千万别偷懒,不然出了问题你都不知道哪里错了,我就吃过这个亏:

controller  
    .show(context, options)  
    .then(() => {  
        console.log("分享成功"); // 调试信息很重要  
        success();  
    })  
    .catch((error: BusinessError) => {  
        console.error("分享失败:", error); // 打印错误信息  
        fail(error?.message ?? "分享失败");  
    });

5. 权限这茬儿

有时候会遇到权限不够的情况,特别是读取文件的时候。记得检查app的权限配置,不然用户点了分享啥反应都没有。

一些常见问题

Q: 分享面板怎么是空白的?

A: 检查这几个地方:

  1. context获取对了没
  2. ShareData有没有创建成功(别返回null)
  3. 文件路径对不对
  4. UTD类型匹配不

Q: 为啥有些app收不到我分享的东西?

A: 估计是UTD类型不匹配,或者那个app不认这种数据格式。试试用通用一点的类型,比如utd.UniformDataType.FILE

Q: 网络上的图片能直接分享吗?

A: 不行,得先下载到本地,然后分享本地文件。

Q: 分享大文件会卡吗?

A: 会的,特别是视频文件。建议加个转圈圈的loading,或者提前压缩一下。

Q: 能知道用户选了哪个app分享吗?

A: 目前鸿蒙的API不支持,只能知道用户是分享成功了还是取消了。

Q: 能自己做个分享面板吗?

A: 不行,只能用系统提供的。不过可以通过selectionModepreviewMode参数稍微调整一下样式。

测试这块儿

真机测试的时候发现了不少问题,建议你们也多试试:

  • 各种文件类型:图片、视频、文档啥的都试试
  • 文件大小:小图片秒传,大视频可能要等一会儿
  • 不同app:微信、QQ、邮箱的接收效果都不一样
  • 网络状况:网不好的时候网络图片可能加载不出来

调试的时候多打印,鸿蒙的报错信息有时候说得不够清楚。

总结

折腾了好几天,总算把这个分享功能搞定了。整体感觉鸿蒙的API还挺人性化的,比我想象中好用多了。

几个心得:

  1. 多翻文档:鸿蒙官方文档写得还可以,遇到问题先去翻翻
  2. 类型别偷懒:ETS的类型检查确实严格,但写出来的代码更稳定
  3. 错误要处理好:异步操作的错误处理千万别省,不然出了bug找都找不到
  4. 真机多测试:模拟器和真机表现可能不一样,都试试保险点

现在这个小插件基本能满足日常需要了。如果你也在搞类似的东西,希望我这些踩坑经验能帮到你。有问题咱们可以一起交流。

所有代码都在uni_modules/cool-share目录里,有兴趣的朋友可以看看。

顺便安利个东西

我们团队还做了个 Cool Unix 组件库,也是基于 Uni-App X 的,完全免费开源。里面有 Tailwind CSS、多主题、国际化这些实用功能,还有挺多现成的组件和页面模板。

最有意思的是,这个组件库配合AI生成鸿蒙页面效果特别好,以后还准备加上后端接口自动生成,真正做到一句话就能搞出个app。如果你也想快速开发鸿蒙应用,可以试试看。

最后感谢一下

感谢 uni-app x 团队做出这么棒的跨平台框架,让我们这些普通开发者也能轻松搞定多端开发。特别是UTS语言和鸿蒙支持,真的降低了不少门槛。

收起阅读 »

手贱,离线基座打包编译错误MAMapKit

打包失败

离线基座打包
pointer not aligned in '_dbl_lnds_data_TileDataRespMsg_fields'+0x32 ~SDK/Libs/MAMapKit.framework/MAMapKit[arm64]2
老是提示弹框版本问题,我手贱,去下了一个SDK包,然后把工程的SDK换掉了;

然后就报这个错误了

找了半天,还好之前有过记录,不然怎么解决都不知道;

高德的旧版sdk,我放到网盘了
链接:https://pan.quark.cn/s/75bba91c6ec2

只要替换掉,就可以了

继续阅读 »

离线基座打包
pointer not aligned in '_dbl_lnds_data_TileDataRespMsg_fields'+0x32 ~SDK/Libs/MAMapKit.framework/MAMapKit[arm64]2
老是提示弹框版本问题,我手贱,去下了一个SDK包,然后把工程的SDK换掉了;

然后就报这个错误了

找了半天,还好之前有过记录,不然怎么解决都不知道;

高德的旧版sdk,我放到网盘了
链接:https://pan.quark.cn/s/75bba91c6ec2

只要替换掉,就可以了

收起阅读 »

动态创建web-view加载本地html 页面通讯

web_view

我们日常使用web-view最常见的方法是新建一个页面,然后放一个web-view并配置上我们的html页面地址


这里有另外一种动态创建web-view的方法,更加的灵活

  let wvPath = '/hybrid/html/webview.html'  
  let wv= plus.webview.create(  
    wvPath,  
    'map-view',  
    {  
      'uni-app': 'none',  
      top: systemInfo.statusBarHeight,  
      left: 0,  
      width: systemInfo.screenWidth,  
      height: systemInfo.screenHeight - systemInfo.statusBarHeight,  
      background: '#ffffff',  
      // 启用手势返回  
      // popGesture: 'close',  
    },  
    {  
       // 这里携带web-view的额外参数  
       key  
    }  
  )  
// 一定要记得在不需要的时候 关闭掉web-view  
wv.close()

监听动态创建的web-view发送的消息,切记plus.globalEvent.addEventListener('plusMessage', messageEvent)只需要添加一次,不要每次都添加监听

  plus.globalEvent.addEventListener('plusMessage', messageEvent)  

  function messageEvent(e) {  
    // 检查数据结构  
    if (!e.data || !e.data.args || !e.data.args.data || !e.data.args.data.arg) {  
      console.error('接收到的消息格式不正确:', e)  
      return  
    }  

    let info = e.data.args.data.arg  
  }

竟然用到了plusMessage方法,本来直接使用的message监听发现怎么都不生效
很多人的使用习惯,既然我监听了消息,那么在我不需要的时候是需要把这个监听移除掉的,否则不停的监听影响APP性能,于是写了下面的移除方法:

plus.globalEvent.removeEventListener('plusMessage',messagEvent)

但是出乎意料,移除后整个APP的所有事件都失效!点击哪里都没有反应,切记这个方法是不可以移除的!
可以看到plus.globalEvent监听的是全局的plusMessage事件,移除后影响到了全局的事件,导致APP无响应。
大家会考虑到这样不能移除的话,不是一个好的监听消息方案,频繁监听消息各种类型的消息都会监听到,严重影响APP性能。
那么接下来我们考虑更换页面通信的方案。

第二种 消息传输方式 Webview url拦截
plus.globalEvent监听uni.postMessage推送的消息会出现重复推送等问题,建议改为Webview url拦截的方式获取html文件数据。

  // html中跳转自定义url,会被拦截,不会进行跳转    
      let str = encodeURIComponent(JSON.stringify(obj))  
      location.href = `push://?${str}`  

  // 接收webview发送的通知消息  
  mapwv.overrideUrlLoading({ mode: 'reject'},(e) => {  
      let obj = JSON.parse(decodeURIComponent(e.url.split('?')[1]))  

      removeEvent()  
  })

参考文章:
https://www.html5plus.org/doc/zh_cn/webview.html
url拦截更实时,准确率更高,不会重复接收消息,只有App支持,H5 文档参考:https://www.html5plus.org/doc/zh_cn/webview.html#plus.webview.WebviewObject.overrideUrlLoading
https://ask.dcloud.net.cn/article/35083

继续阅读 »

我们日常使用web-view最常见的方法是新建一个页面,然后放一个web-view并配置上我们的html页面地址


这里有另外一种动态创建web-view的方法,更加的灵活

  let wvPath = '/hybrid/html/webview.html'  
  let wv= plus.webview.create(  
    wvPath,  
    'map-view',  
    {  
      'uni-app': 'none',  
      top: systemInfo.statusBarHeight,  
      left: 0,  
      width: systemInfo.screenWidth,  
      height: systemInfo.screenHeight - systemInfo.statusBarHeight,  
      background: '#ffffff',  
      // 启用手势返回  
      // popGesture: 'close',  
    },  
    {  
       // 这里携带web-view的额外参数  
       key  
    }  
  )  
// 一定要记得在不需要的时候 关闭掉web-view  
wv.close()

监听动态创建的web-view发送的消息,切记plus.globalEvent.addEventListener('plusMessage', messageEvent)只需要添加一次,不要每次都添加监听

  plus.globalEvent.addEventListener('plusMessage', messageEvent)  

  function messageEvent(e) {  
    // 检查数据结构  
    if (!e.data || !e.data.args || !e.data.args.data || !e.data.args.data.arg) {  
      console.error('接收到的消息格式不正确:', e)  
      return  
    }  

    let info = e.data.args.data.arg  
  }

竟然用到了plusMessage方法,本来直接使用的message监听发现怎么都不生效
很多人的使用习惯,既然我监听了消息,那么在我不需要的时候是需要把这个监听移除掉的,否则不停的监听影响APP性能,于是写了下面的移除方法:

plus.globalEvent.removeEventListener('plusMessage',messagEvent)

但是出乎意料,移除后整个APP的所有事件都失效!点击哪里都没有反应,切记这个方法是不可以移除的!
可以看到plus.globalEvent监听的是全局的plusMessage事件,移除后影响到了全局的事件,导致APP无响应。
大家会考虑到这样不能移除的话,不是一个好的监听消息方案,频繁监听消息各种类型的消息都会监听到,严重影响APP性能。
那么接下来我们考虑更换页面通信的方案。

第二种 消息传输方式 Webview url拦截
plus.globalEvent监听uni.postMessage推送的消息会出现重复推送等问题,建议改为Webview url拦截的方式获取html文件数据。

  // html中跳转自定义url,会被拦截,不会进行跳转    
      let str = encodeURIComponent(JSON.stringify(obj))  
      location.href = `push://?${str}`  

  // 接收webview发送的通知消息  
  mapwv.overrideUrlLoading({ mode: 'reject'},(e) => {  
      let obj = JSON.parse(decodeURIComponent(e.url.split('?')[1]))  

      removeEvent()  
  })

参考文章:
https://www.html5plus.org/doc/zh_cn/webview.html
url拦截更实时,准确率更高,不会重复接收消息,只有App支持,H5 文档参考:https://www.html5plus.org/doc/zh_cn/webview.html#plus.webview.WebviewObject.overrideUrlLoading
https://ask.dcloud.net.cn/article/35083

收起阅读 »

uni-app 上架 iOS 应用全流程 从云打包到开心上架(Appuploader)免 Mac 上传发布指南

iOS

'''随着 uni-app 成为跨平台应用开发的主流框架之一,越来越多的开发者开始将 Web 技术(Vue + JS)扩展至原生 App 开发领域。

uni-app 通过 HBuilderX 云打包服务 实现“一套代码,多端运行”,极大降低了 iOS 与 Android 应用的开发门槛。

然而,许多开发者在面对 iOS 应用上架时常遇到瓶颈:

没有 Mac、无法使用 Xcode 上传、App Store Connect 操作繁琐……

开心上架(Appuploader)为 uni-app 团队提供了方便的解决方案。
它支持在 Windows / Linux / macOS 环境下完成证书管理、IPA 上传、截图与多语言信息同步,让 uni-app 项目实现 全平台免 Mac 上架流程


一、uni-app 打包 iOS 应用的基本流程

uni-app 项目的构建和打包通常通过 HBuilderX 或 CLI 工具完成。

步骤 1️⃣:打包设置

在 HBuilderX 菜单栏中选择:

发行 → 原生 App-云打包 → iOS

配置内容包括:

  • 应用名称、Bundle ID(必须与 Apple Developer 一致);
  • 应用版本号与描述信息;
  • App 图标与启动图。

步骤 2️⃣:选择证书类型

HBuilderX 云打包提供两种证书方式:

选项 说明
上传自有证书 使用个人或企业 Apple 账号生成的证书
使用 DCloud 公共证书 测试用途,不能正式上架 App Store

建议使用自有证书,以保证应用可正式上架。

步骤 3️⃣:打包完成后下载 IPA

云打包完成后,系统会生成 .ipa 文件,可直接用于上传。

示例:

unpackage/release/ios/myapp.ipa

二、准备上架前的必要条件

在正式上传前,请确保具备以下条件:

条件 说明
Apple 开发者账号 注册 Apple Developer,年费 99 美元
App 专用密码 上传时使用,保护主账号安全
隐私政策链接 必填项,可托管在网站或 Github Pages
应用截图与描述 必须提供 6.5" 与 5.5" 设备截图

三、免Mac上架

开心上架(Appuploader) 是一款专为 iOS 上架设计的跨平台工具,支持图形界面与命令行两种操作方式,可在 Windows / Linux / macOS 上运行。

核心功能:

功能 说明
免 Mac 上传 IPA 不依赖 Xcode 与 Transporter
命令行支持 适配 CI/CD 自动化环境
证书管理 一键创建 iOS 开发/发布证书
多语言截图上传 支持 App Store Connect 多语言数据
通道切换 同时支持苹果新旧上传协议

特别适合使用 uni-app 的跨平台开发团队。


四、使用 Appuploader 上传 uni-app 生成的 iOS 应用

方式一:图形界面上传(适合初次上架)

打开 开心上架;
登录 Apple 开发者账号(使用 App 专用密码);
点击 “上传 IPA”;
选择 HBuilder 云打包生成的 .ipa 文件;
上传完成后在 App Store Connect 查看应用状态。
ipa上架


方式二:命令行上传(推荐开发者使用)

命令行版本支持全自动上传,可与构建流程整合。

appuploader_cli -u ios@team.com -p xxx-xxx-xxx-xxx -c 2 -f ./unpackage/release/ios/uniapp.ipa

参数说明:

参数 含义
-u Apple 开发者账号
-p App 专用密码
-c 上传通道(1=旧通道,2=新通道)
-f IPA 文件路径

执行命令后,Appuploader 会:

  • 验证包体信息;
  • 通过加密通道上传至 App Store Connect;
  • 输出上传日志与结果。

整个过程无需 Mac 环境。


五、App Store Connect 上架配置步骤

IPA 上传成功后,在 App Store Connect 中完成以下步骤:

填写应用基本信息(名称、描述、关键词);
上传截图与隐私政策;
配置应用分类与年龄评级;
添加版本号与构建文件;
点击 “提交审核” 等待审批。
app store connect

审核时间一般为 1~3 个工作日
含推送、支付功能的应用审核时间可能更长。


六、跨平台 uni-app 团队的发布实践

以一个典型的 uni-app 团队为例:

阶段 工具 功能
代码开发 HBuilderX / VSCode 使用 Vue 语法开发应用
云打包 HBuilder 云端 生成 iOS .ipa 文件
上传上架 开心上架(Appuploader) 免 Mac 上传 IPA 至 App Store
审核管理 App Store Connect 提交资料、监控审核状态

实际场景:
开发者在 Windows 环境中开发 uni-app 项目,
HBuilder 云打包生成 IPA,
然后使用以下命令上传:

appuploader_cli -u dev@icloud.com -p xxx-xxx-xxx-xxx -c 2 -f ./ios_release/uniapp.ipa

整个上架过程无需 Mac 或 Xcode,
团队协作效率提升 60% 以上。


七、常见问题与解决方案

问题 原因 解决方案
上传失败(401) 密码错误 使用 App 专用密码而非 Apple ID 密码
“Invalid Bundle ID” Bundle ID 不匹配 与 Apple Developer 注册的 ID 保持一致
“Missing Provisioning Profile” 描述文件不正确 重新生成发布证书与配置文件
审核拒绝 隐私政策或截图问题 更新资料并重新提交
Transporter 报错但 CLI 成功 网络不稳定 使用 Appuploader 命令行通道 2 上传

八、进阶:自动化上架流程(CI/CD)

可将 Appuploader 命令集成进 Jenkins 或 GitLab CI 流水线:

#!/bin/bash  
# 自动上传 uni-app 打包生成的 iOS 包  
ipa_path="./unpackage/release/ios/uniapp.ipa"  
appuploader_cli -u ios@team.com -p xxx-xxx-xxx-xxx -c 2 -f "$ipa_path"

执行后 Jenkins 将自动:

  • 构建 uni-app;
  • 上传至 App Store;
  • 生成上架日志;
  • 推送通知至团队频道(如企业微信)。

九、uni-app 上架的优势总结

环节 工具 优势
代码开发 uni-app 跨平台、一套代码多端发布
打包生成 HBuilder 云打包 无需 Xcode,本地轻量化
上传发布 开心上架(Appuploader) 支持全平台上传、自动化命令行
团队协作 CI/CD + CLI 提高上架频率与可追溯性

对前端团队而言,uni-app + Appuploader 是最轻量高效的 iOS 发布方案。


uni-app 让前端开发者能够轻松构建移动应用,而 开心上架(Appuploader)则让他们能够不依赖 Mac,实现 “跨平台上架”。

从 HBuilder 打包到 App Store 发布,整个过程简洁、高效、安全,适合个人开发者与团队工程使用。

让开发专注于创造,让上架交给自动化。'''

继续阅读 »

'''随着 uni-app 成为跨平台应用开发的主流框架之一,越来越多的开发者开始将 Web 技术(Vue + JS)扩展至原生 App 开发领域。

uni-app 通过 HBuilderX 云打包服务 实现“一套代码,多端运行”,极大降低了 iOS 与 Android 应用的开发门槛。

然而,许多开发者在面对 iOS 应用上架时常遇到瓶颈:

没有 Mac、无法使用 Xcode 上传、App Store Connect 操作繁琐……

开心上架(Appuploader)为 uni-app 团队提供了方便的解决方案。
它支持在 Windows / Linux / macOS 环境下完成证书管理、IPA 上传、截图与多语言信息同步,让 uni-app 项目实现 全平台免 Mac 上架流程


一、uni-app 打包 iOS 应用的基本流程

uni-app 项目的构建和打包通常通过 HBuilderX 或 CLI 工具完成。

步骤 1️⃣:打包设置

在 HBuilderX 菜单栏中选择:

发行 → 原生 App-云打包 → iOS

配置内容包括:

  • 应用名称、Bundle ID(必须与 Apple Developer 一致);
  • 应用版本号与描述信息;
  • App 图标与启动图。

步骤 2️⃣:选择证书类型

HBuilderX 云打包提供两种证书方式:

选项 说明
上传自有证书 使用个人或企业 Apple 账号生成的证书
使用 DCloud 公共证书 测试用途,不能正式上架 App Store

建议使用自有证书,以保证应用可正式上架。

步骤 3️⃣:打包完成后下载 IPA

云打包完成后,系统会生成 .ipa 文件,可直接用于上传。

示例:

unpackage/release/ios/myapp.ipa

二、准备上架前的必要条件

在正式上传前,请确保具备以下条件:

条件 说明
Apple 开发者账号 注册 Apple Developer,年费 99 美元
App 专用密码 上传时使用,保护主账号安全
隐私政策链接 必填项,可托管在网站或 Github Pages
应用截图与描述 必须提供 6.5" 与 5.5" 设备截图

三、免Mac上架

开心上架(Appuploader) 是一款专为 iOS 上架设计的跨平台工具,支持图形界面与命令行两种操作方式,可在 Windows / Linux / macOS 上运行。

核心功能:

功能 说明
免 Mac 上传 IPA 不依赖 Xcode 与 Transporter
命令行支持 适配 CI/CD 自动化环境
证书管理 一键创建 iOS 开发/发布证书
多语言截图上传 支持 App Store Connect 多语言数据
通道切换 同时支持苹果新旧上传协议

特别适合使用 uni-app 的跨平台开发团队。


四、使用 Appuploader 上传 uni-app 生成的 iOS 应用

方式一:图形界面上传(适合初次上架)

打开 开心上架;
登录 Apple 开发者账号(使用 App 专用密码);
点击 “上传 IPA”;
选择 HBuilder 云打包生成的 .ipa 文件;
上传完成后在 App Store Connect 查看应用状态。
ipa上架


方式二:命令行上传(推荐开发者使用)

命令行版本支持全自动上传,可与构建流程整合。

appuploader_cli -u ios@team.com -p xxx-xxx-xxx-xxx -c 2 -f ./unpackage/release/ios/uniapp.ipa

参数说明:

参数 含义
-u Apple 开发者账号
-p App 专用密码
-c 上传通道(1=旧通道,2=新通道)
-f IPA 文件路径

执行命令后,Appuploader 会:

  • 验证包体信息;
  • 通过加密通道上传至 App Store Connect;
  • 输出上传日志与结果。

整个过程无需 Mac 环境。


五、App Store Connect 上架配置步骤

IPA 上传成功后,在 App Store Connect 中完成以下步骤:

填写应用基本信息(名称、描述、关键词);
上传截图与隐私政策;
配置应用分类与年龄评级;
添加版本号与构建文件;
点击 “提交审核” 等待审批。
app store connect

审核时间一般为 1~3 个工作日
含推送、支付功能的应用审核时间可能更长。


六、跨平台 uni-app 团队的发布实践

以一个典型的 uni-app 团队为例:

阶段 工具 功能
代码开发 HBuilderX / VSCode 使用 Vue 语法开发应用
云打包 HBuilder 云端 生成 iOS .ipa 文件
上传上架 开心上架(Appuploader) 免 Mac 上传 IPA 至 App Store
审核管理 App Store Connect 提交资料、监控审核状态

实际场景:
开发者在 Windows 环境中开发 uni-app 项目,
HBuilder 云打包生成 IPA,
然后使用以下命令上传:

appuploader_cli -u dev@icloud.com -p xxx-xxx-xxx-xxx -c 2 -f ./ios_release/uniapp.ipa

整个上架过程无需 Mac 或 Xcode,
团队协作效率提升 60% 以上。


七、常见问题与解决方案

问题 原因 解决方案
上传失败(401) 密码错误 使用 App 专用密码而非 Apple ID 密码
“Invalid Bundle ID” Bundle ID 不匹配 与 Apple Developer 注册的 ID 保持一致
“Missing Provisioning Profile” 描述文件不正确 重新生成发布证书与配置文件
审核拒绝 隐私政策或截图问题 更新资料并重新提交
Transporter 报错但 CLI 成功 网络不稳定 使用 Appuploader 命令行通道 2 上传

八、进阶:自动化上架流程(CI/CD)

可将 Appuploader 命令集成进 Jenkins 或 GitLab CI 流水线:

#!/bin/bash  
# 自动上传 uni-app 打包生成的 iOS 包  
ipa_path="./unpackage/release/ios/uniapp.ipa"  
appuploader_cli -u ios@team.com -p xxx-xxx-xxx-xxx -c 2 -f "$ipa_path"

执行后 Jenkins 将自动:

  • 构建 uni-app;
  • 上传至 App Store;
  • 生成上架日志;
  • 推送通知至团队频道(如企业微信)。

九、uni-app 上架的优势总结

环节 工具 优势
代码开发 uni-app 跨平台、一套代码多端发布
打包生成 HBuilder 云打包 无需 Xcode,本地轻量化
上传发布 开心上架(Appuploader) 支持全平台上传、自动化命令行
团队协作 CI/CD + CLI 提高上架频率与可追溯性

对前端团队而言,uni-app + Appuploader 是最轻量高效的 iOS 发布方案。


uni-app 让前端开发者能够轻松构建移动应用,而 开心上架(Appuploader)则让他们能够不依赖 Mac,实现 “跨平台上架”。

从 HBuilder 打包到 App Store 发布,整个过程简洁、高效、安全,适合个人开发者与团队工程使用。

让开发专注于创造,让上架交给自动化。'''

收起阅读 »

动态创建web-view加载本地html 页面通讯

web_view

我们日常使用web-view最常见的方法是新建一个页面,然后放一个web-view并配置上我们的html页面地址

这里有另外一种动态创建web-view的方法,更加的灵活

  let wvPath = '/hybrid/html/webview.html'  
  let wv= new plus.webview.create(  
    wvPath,  
    'map-view',  
    {  
      'uni-app': 'none',  
      top: systemInfo.statusBarHeight,  
      left: 0,  
      width: systemInfo.screenWidth,  
      height: systemInfo.screenHeight - systemInfo.statusBarHeight,  
      background: '#ffffff',  
      // 启用手势返回  
      // popGesture: 'close',  
    },  
    {  
       // 这里携带web-view的额外参数  
       key  
    }  
  )  
// 一定要记得再不需要的时候 关闭掉动态创建的we-view  
wv.close()

监听动态创建的web-view发送的消息

  plus.globalEvent.addEventListener('plusMessage', (e) => {    
          console.log("网页消息", e);    
  })

竟然用到了plusMessage方法,本来直接使用的message监听发现怎么都不生效
从 plus.globalEvent看出监听的是全局的plusMessage方法
很多人的使用习惯,既然我监听了消息,那么在我不需要的时候是需要把这个监听移除掉的,否则不停的监听影响APP性能,于是写了下面的移除方法:

plus.globalEvent.removeEventListener('plusMessage',messagEvent)
但是出乎意料,移除后整个APP的所有事件都失效!点击哪里都没有反应,切记这个方法是不可以移除的

第二种 消息传输方式
plus.globalEvent监听uni.postMessage推送的消息会出现重复推送等问题,建议改为Webview url拦截的方式获取html文件数据。
// html中跳转自定义url,会被拦截,不会进行跳转
window.location.href = 'push?params=loading'

// vue页面wv拦截url变更
wv.overrideUrlLoading({mode:'reject'}, e => {
var params = decodeURI(e.url.split('push?params=')[1])
})

url拦截更实时,准确率更高,不会重复接收消息,只有App支持,H5+文档参考:https://www.html5plus.org/doc/zh_cn/webview.html#plus.webview.WebviewObject.overrideUrlLoading

参考文章:
https://www.html5plus.org/doc/zh_cn/webview.html
https://ask.dcloud.net.cn/article/35083

继续阅读 »

我们日常使用web-view最常见的方法是新建一个页面,然后放一个web-view并配置上我们的html页面地址

这里有另外一种动态创建web-view的方法,更加的灵活

  let wvPath = '/hybrid/html/webview.html'  
  let wv= new plus.webview.create(  
    wvPath,  
    'map-view',  
    {  
      'uni-app': 'none',  
      top: systemInfo.statusBarHeight,  
      left: 0,  
      width: systemInfo.screenWidth,  
      height: systemInfo.screenHeight - systemInfo.statusBarHeight,  
      background: '#ffffff',  
      // 启用手势返回  
      // popGesture: 'close',  
    },  
    {  
       // 这里携带web-view的额外参数  
       key  
    }  
  )  
// 一定要记得再不需要的时候 关闭掉动态创建的we-view  
wv.close()

监听动态创建的web-view发送的消息

  plus.globalEvent.addEventListener('plusMessage', (e) => {    
          console.log("网页消息", e);    
  })

竟然用到了plusMessage方法,本来直接使用的message监听发现怎么都不生效
从 plus.globalEvent看出监听的是全局的plusMessage方法
很多人的使用习惯,既然我监听了消息,那么在我不需要的时候是需要把这个监听移除掉的,否则不停的监听影响APP性能,于是写了下面的移除方法:

plus.globalEvent.removeEventListener('plusMessage',messagEvent)
但是出乎意料,移除后整个APP的所有事件都失效!点击哪里都没有反应,切记这个方法是不可以移除的

第二种 消息传输方式
plus.globalEvent监听uni.postMessage推送的消息会出现重复推送等问题,建议改为Webview url拦截的方式获取html文件数据。
// html中跳转自定义url,会被拦截,不会进行跳转
window.location.href = 'push?params=loading'

// vue页面wv拦截url变更
wv.overrideUrlLoading({mode:'reject'}, e => {
var params = decodeURI(e.url.split('push?params=')[1])
})

url拦截更实时,准确率更高,不会重复接收消息,只有App支持,H5+文档参考:https://www.html5plus.org/doc/zh_cn/webview.html#plus.webview.WebviewObject.overrideUrlLoading

参考文章:
https://www.html5plus.org/doc/zh_cn/webview.html
https://ask.dcloud.net.cn/article/35083

收起阅读 »

uni-vue3--专为 UniApp + Vue3 + UnoCSS 打造的开箱即用模板

uni-vue3

专为 UniApp + Vue3 + UnoCSS 打造的 starter template

源码:https://github.com/vue-rookie/uni-vue3

🚀 特性

  • ✅ Vue3 + Composition API
  • ✅ UnoCSS 原子化CSS
  • ✅ TypeScript 支持
  • ✅ Vite 构建工具

📦 快速开始

小程序预览

产品

建议手机模式预览
uni-vue3框架--------------------------------代码:feature/main
uni-vue3模仿抖音---------------------------代码:feature/douyin
uni-vue3模仿小红书------------------------代码:feature/xiaohongshu

🚀 技术栈

  • 核心框架:Vue 3.4
  • 构建工具:Vite 5.0
  • 开发语言:TypeScript 5.0
  • 状态管理:Pinia 2.0
  • 样式方案:UnoCSS
  • 跨端框架:UniApp 3.0

自定义主题样式(超级自由简单的样式配置):

组件样式全部采用 tailwindcss 封装,您无需写任何 令人烦躁的 css 代码。

只需要修改对应 uno.config.ts 配置文件中的 theme 下的的色值号即可,其他无需任何更改

🪝 自定义 Hooks

项目中精心封装了丰富的自定义 Hooks,大幅提升开发效率和代码质量,所有 Hooks 支持 TypeScript 类型推导。

UI 交互类

useModal - 对话框管理

提供统一的弹窗交互解决方案,包括确认框、消息提示、加载状态等。

import { useModal } from "@/hooks"  

// 在组件中使用  
const {  
  showToast,  
  showSuccess,  
  showError,  
  showConfirm,  
  showLoading,  
  hideLoading,  
  showActionSheet,  
} = useModal()  

// 显示确认对话框  
await showConfirm({  
  title: "操作确认",  
  content: "确定要执行此操作吗?",  
})  

// 显示成功提示  
showSuccess("操作成功")  

// 显示加载状态  
const loading = showLoading()  
try {  
  // 执行异步操作  
} finally {  
  loading.hide() // 隐藏加载  
}  

// 显示底部操作菜单  
const selectedIndex = await showActionSheet({  
  itemList: ["选项一", "选项二", "选项三"],  
})

数据处理类

useStorage - 本地存储

增强版本地存储,支持过期时间、类型安全、自动序列化/反序列化、响应式存储。

import { useStorage } from "@/hooks"  

const { setStorage, getStorage, removeStorage, clearStorage, createReactiveStorage } = useStorage()  

// 存储数据,30天过期  
await setStorage("user-info", { id: 1, name: "Admin" }, 30 * 24 * 60 * 60 * 1000)  

// 读取数据  
const userInfo = await getStorage("user-info")  

// 创建响应式存储  
const count = createReactiveStorage("visit-count", 0)  
count.value++ // 自动同步到存储

useRequest - 网络请求

强大的请求管理 Hook,自动处理加载状态、错误处理、请求缓存等。

import { useRequest } from "@/hooks"  

const request = useRequest({  
  baseURL: "https://api.example.com",  
  autoHandleError: true, // 自动处理错误  
  autoLoading: true, // 自动显示加载状态  
})  

// GET 请求  
const { data } = await request.get("/users", { page: 1 })  

// POST 请求  
await request.post("/articles", { title, content })  

// 文件上传  
await request.upload("/upload", {  
  filePath: tempFilePath,  
  name: "file",  
  formData: { type: "avatar" },  
})

useInputLimit - 输入限制

提供各类输入限制函数,轻松实现各种格式检查和输入控制。

import { useInputDataLimit } from "@/hooks/useInputLimit"  

const {  
  limitNumber, // 仅限数字  
  limitLetter, // 仅限字母  
  limitNumberAndLetter, // 仅限数字和字母  
  limitToPositiveTwoDecimals, // 仅限两位小数的正数  
  limitNoChinese, // 不允许中文  
} = useInputDataLimit()  

// 在输入事件中使用  
const handleInput = (e) => {  
  const rawValue = e.detail.value  
  const formattedValue = limitToPositiveTwoDecimals(rawValue)  
  // 更新表单值  
}

设备能力类

useLocation - 位置服务

封装位置获取、地址解析、坐标转换等功能,支持高精度定位和后台定位。

import { useLocation } from "@/hooks"  

const { getLocation, startLocationUpdate, stopLocationUpdate, chooseLocation, openLocation } =  
  useLocation()  

// 获取当前位置  
const position = await getLocation({  
  type: "gcj02", // 坐标系类型  
  isHighAccuracy: true, // 高精度定位  
})  

// 打开地图选择位置  
const location = await chooseLocation()  

// 在地图上查看位置  
await openLocation({  
  latitude: 39.9087,  
  longitude: 116.3975,  
  name: "目的地",  
  address: "详细地址信息",  
  scale: 18,  
})

useCamera - 相机功能

相机相关功能封装,包括拍照、录像、选择相册等功能。

import { useCamera } from "@/hooks"  

const { takePhoto, chooseImage, chooseVideo, previewImage, compressImage } = useCamera()  

// 拍照或从相册选择  
const filePath = await takePhoto({  
  sourceType: ["camera", "album"],  
})  

// 选择多张图片  
const images = await chooseImage({  
  count: 9,  
  sizeType: ["original", "compressed"],  
})  

// 压缩图片  
const compressedPath = await compressImage(filePath, {  
  quality: 80, // 压缩质量  
})

useSystem - 系统信息

获取系统信息、设备信息、网络状态等功能。

import { useSystem } from "@/hooks"  

const { getSystemInfo, getNetworkType, onNetworkStatusChange, getDeviceInfo, vibrateShort } =  
  useSystem()  

// 获取系统信息  
const systemInfo = getSystemInfo()  
console.log(`运行平台: ${systemInfo.platform}, 系统: ${systemInfo.system}`)  

// 获取网络状态  
const networkType = await getNetworkType()  

// 监听网络变化  
onNetworkStatusChange((res) => {  
  console.log(`网络变更: ${res.networkType}, 是否连接: ${res.isConnected}`)  
})

其他实用 Hooks

useShare - 分享功能

统一的分享接口,支持小程序分享、系统分享、自定义分享等。

import { useShare } from "@/hooks"  

const { share, shareWithSystem, configMiniProgramShare } = useShare()  

// 配置页面分享参数  
configMiniProgramShare({  
  title: "分享标题",  
  path: "/pages/index/index",  
  imageUrl: "/static/share.png",  
  onShareSuccess: () => {  
    console.log("分享成功")  
  },  
})  

// 系统分享  
shareWithSystem({  
  title: "分享内容",  
  summary: "内容摘要",  
  href: "https://example.com",  
  imageUrl: "/static/share.png",  
})

useValidation - 表单验证

强大的表单验证系统,内置多种常用验证规则,支持自定义验证。

import { useValidation } from "@/hooks"  

const validation = useValidation()  

// 创建表单数据和规则  
const { formData, rules, validate, errors, resetValidation } = validation.createForm(  
  {  
    username: "",  
    password: "",  
    email: "",  
    phone: "",  
  },  
  {  
    username: [  
      { required: true, message: "用户名不能为空" },  
      { min: 3, max: 20, message: "长度在3到20个字符" },  
    ],  
    password: [  
      { required: true, message: "密码不能为空" },  
      { min: 6, message: "密码长度不能小于6位" },  
      { pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/, message: "密码必须包含大小写字母和数字" },  
    ],  
    email: [{ type: "email", message: "请输入正确的邮箱格式" }],  
    phone: [{ pattern: /^1[3-9]\d{9}$/, message: "请输入正确的手机号" }],  
  },  
)  

// 提交表单  
const handleSubmit = async () => {  
  const valid = await validate()  
  if (valid) {  
    // 表单验证通过,提交数据  
    console.log("表单数据:", formData)  
  } else {  
    // 表单验证失败  
    console.log("验证错误:", errors.value)  
  }  
}

usePageScroll - 页面滚动

页面滚动相关功能,包括滚动到指定位置、监听滚动事件等。

import { usePageScroll } from "@/hooks"  

const { scrollTo, scrollToTop, scrollToSelector, onPageScroll, getScrollPosition } = usePageScroll()  

// 滚动到顶部  
scrollToTop()  

// 滚动到指定位置  
scrollTo(0, 200, true) // 带动画效果滚动到 200px 位置  

// 滚动到指定元素  
scrollToSelector(".news-item-5", {  
  offset: -20, // 偏移量  
  duration: 300, // 动画时长  
})  

// 监听页面滚动  
onPageScroll((scrollTop) => {  
  if (scrollTop > 100) {  
    // 显示返回顶部按钮  
  }  
})

📋 环境要求

  • Node.js >= 18.0.0
  • pnpm >= 7.0.0
  • 微信开发者工具(开发小程序时使用)

🛠️ 快速开始

# 安装依赖  
pnpm install  

# 开发环境运行  
pnpm dev:mp-weixin  

# 生产环境构建  
pnpm build:mp-weixin

📱 开发指南

微信小程序开发

  1. 开发环境配置

    • 运行 pnpm dev:mp-weixin 生成开发环境代码
    • 使用微信开发者工具导入 dist/dev/mp-weixin 目录
    • 开启"不校验合法域名"选项(开发环境)
  2. 生产环境发布

    • 执行 pnpm build:mp-weixin 生成生产环境代码
    • 使用微信开发者工具导入 dist/build/mp-weixin 目录
    • 点击"上传"按钮发布小程序

项目规范

  1. 组件开发规范

    • 全局组件统一放置在 uni-module 目录下
    • 遵循 UniApp 的 easycom 组件规范
    • 组件命名采用 PascalCase 命名法
  2. 类型定义规范

    • 业务类型定义统一放在对应页面的 type.ts 文件中
    • 公共类型定义放在 types 目录下
    • 类型命名采用 PascalCase 命名法
  3. Hooks 使用规范

    • 公共 hooks 统一放在 hooks 目录下
    • 业务相关 hooks 放在对应页面目录
    • hooks 命名采用 camelCase 命名法,以 use 开头

🚀 性能优化

构建优化

  1. 代码分割

    • 使用 manualChunks 实现代码分割
    • 第三方依赖独立打包,提高缓存效率
    • 路由组件按需加载
  2. 资源优化

    • 图片资源自动压缩
    • CSS 代码压缩和优化
    • 静态资源 CDN 加速
  3. 编译优化

    • 使用 lightningcss 进行 CSS 处理
    • 配置合理的 assetsInlineLimit
    • 优化 Sass 编译配置

运行时优化

  1. 渲染优化

    • 图片懒加载
    • 虚拟列表
    • 条件渲染优化
  2. 性能监控

    • 页面加载性能监控
    • 组件渲染性能分析
    • 内存使用监控

📦 项目结构

├── src  
│   ├── pages              # 页面文件  
│   ├── pages-sub          # 子页面  
│   ├── hooks              # 自定义hooks  
│   ├── static             # 静态资源  
│   ├── types              # 类型定义  
│   ├── utils              # 工具函数  
│   └── uni-module         # 全局组件  
├── vite.config.ts         # Vite 配置  
└── package.json           # 项目配置

源码

stars
forks
watchers
license

📄 开源协议

本项目采用 MIT 协议开源,详情请查看 LICENSE 文件。

致谢

感谢 DCloud 官方,旗下出品的 uni-appuni-app-xuniClouduni-app 小程序 等多平台、多元化的技术体系。

继续阅读 »

uni-vue3

专为 UniApp + Vue3 + UnoCSS 打造的 starter template

源码:https://github.com/vue-rookie/uni-vue3

🚀 特性

  • ✅ Vue3 + Composition API
  • ✅ UnoCSS 原子化CSS
  • ✅ TypeScript 支持
  • ✅ Vite 构建工具

📦 快速开始

小程序预览

产品

建议手机模式预览
uni-vue3框架--------------------------------代码:feature/main
uni-vue3模仿抖音---------------------------代码:feature/douyin
uni-vue3模仿小红书------------------------代码:feature/xiaohongshu

🚀 技术栈

  • 核心框架:Vue 3.4
  • 构建工具:Vite 5.0
  • 开发语言:TypeScript 5.0
  • 状态管理:Pinia 2.0
  • 样式方案:UnoCSS
  • 跨端框架:UniApp 3.0

自定义主题样式(超级自由简单的样式配置):

组件样式全部采用 tailwindcss 封装,您无需写任何 令人烦躁的 css 代码。

只需要修改对应 uno.config.ts 配置文件中的 theme 下的的色值号即可,其他无需任何更改

🪝 自定义 Hooks

项目中精心封装了丰富的自定义 Hooks,大幅提升开发效率和代码质量,所有 Hooks 支持 TypeScript 类型推导。

UI 交互类

useModal - 对话框管理

提供统一的弹窗交互解决方案,包括确认框、消息提示、加载状态等。

import { useModal } from "@/hooks"  

// 在组件中使用  
const {  
  showToast,  
  showSuccess,  
  showError,  
  showConfirm,  
  showLoading,  
  hideLoading,  
  showActionSheet,  
} = useModal()  

// 显示确认对话框  
await showConfirm({  
  title: "操作确认",  
  content: "确定要执行此操作吗?",  
})  

// 显示成功提示  
showSuccess("操作成功")  

// 显示加载状态  
const loading = showLoading()  
try {  
  // 执行异步操作  
} finally {  
  loading.hide() // 隐藏加载  
}  

// 显示底部操作菜单  
const selectedIndex = await showActionSheet({  
  itemList: ["选项一", "选项二", "选项三"],  
})

数据处理类

useStorage - 本地存储

增强版本地存储,支持过期时间、类型安全、自动序列化/反序列化、响应式存储。

import { useStorage } from "@/hooks"  

const { setStorage, getStorage, removeStorage, clearStorage, createReactiveStorage } = useStorage()  

// 存储数据,30天过期  
await setStorage("user-info", { id: 1, name: "Admin" }, 30 * 24 * 60 * 60 * 1000)  

// 读取数据  
const userInfo = await getStorage("user-info")  

// 创建响应式存储  
const count = createReactiveStorage("visit-count", 0)  
count.value++ // 自动同步到存储

useRequest - 网络请求

强大的请求管理 Hook,自动处理加载状态、错误处理、请求缓存等。

import { useRequest } from "@/hooks"  

const request = useRequest({  
  baseURL: "https://api.example.com",  
  autoHandleError: true, // 自动处理错误  
  autoLoading: true, // 自动显示加载状态  
})  

// GET 请求  
const { data } = await request.get("/users", { page: 1 })  

// POST 请求  
await request.post("/articles", { title, content })  

// 文件上传  
await request.upload("/upload", {  
  filePath: tempFilePath,  
  name: "file",  
  formData: { type: "avatar" },  
})

useInputLimit - 输入限制

提供各类输入限制函数,轻松实现各种格式检查和输入控制。

import { useInputDataLimit } from "@/hooks/useInputLimit"  

const {  
  limitNumber, // 仅限数字  
  limitLetter, // 仅限字母  
  limitNumberAndLetter, // 仅限数字和字母  
  limitToPositiveTwoDecimals, // 仅限两位小数的正数  
  limitNoChinese, // 不允许中文  
} = useInputDataLimit()  

// 在输入事件中使用  
const handleInput = (e) => {  
  const rawValue = e.detail.value  
  const formattedValue = limitToPositiveTwoDecimals(rawValue)  
  // 更新表单值  
}

设备能力类

useLocation - 位置服务

封装位置获取、地址解析、坐标转换等功能,支持高精度定位和后台定位。

import { useLocation } from "@/hooks"  

const { getLocation, startLocationUpdate, stopLocationUpdate, chooseLocation, openLocation } =  
  useLocation()  

// 获取当前位置  
const position = await getLocation({  
  type: "gcj02", // 坐标系类型  
  isHighAccuracy: true, // 高精度定位  
})  

// 打开地图选择位置  
const location = await chooseLocation()  

// 在地图上查看位置  
await openLocation({  
  latitude: 39.9087,  
  longitude: 116.3975,  
  name: "目的地",  
  address: "详细地址信息",  
  scale: 18,  
})

useCamera - 相机功能

相机相关功能封装,包括拍照、录像、选择相册等功能。

import { useCamera } from "@/hooks"  

const { takePhoto, chooseImage, chooseVideo, previewImage, compressImage } = useCamera()  

// 拍照或从相册选择  
const filePath = await takePhoto({  
  sourceType: ["camera", "album"],  
})  

// 选择多张图片  
const images = await chooseImage({  
  count: 9,  
  sizeType: ["original", "compressed"],  
})  

// 压缩图片  
const compressedPath = await compressImage(filePath, {  
  quality: 80, // 压缩质量  
})

useSystem - 系统信息

获取系统信息、设备信息、网络状态等功能。

import { useSystem } from "@/hooks"  

const { getSystemInfo, getNetworkType, onNetworkStatusChange, getDeviceInfo, vibrateShort } =  
  useSystem()  

// 获取系统信息  
const systemInfo = getSystemInfo()  
console.log(`运行平台: ${systemInfo.platform}, 系统: ${systemInfo.system}`)  

// 获取网络状态  
const networkType = await getNetworkType()  

// 监听网络变化  
onNetworkStatusChange((res) => {  
  console.log(`网络变更: ${res.networkType}, 是否连接: ${res.isConnected}`)  
})

其他实用 Hooks

useShare - 分享功能

统一的分享接口,支持小程序分享、系统分享、自定义分享等。

import { useShare } from "@/hooks"  

const { share, shareWithSystem, configMiniProgramShare } = useShare()  

// 配置页面分享参数  
configMiniProgramShare({  
  title: "分享标题",  
  path: "/pages/index/index",  
  imageUrl: "/static/share.png",  
  onShareSuccess: () => {  
    console.log("分享成功")  
  },  
})  

// 系统分享  
shareWithSystem({  
  title: "分享内容",  
  summary: "内容摘要",  
  href: "https://example.com",  
  imageUrl: "/static/share.png",  
})

useValidation - 表单验证

强大的表单验证系统,内置多种常用验证规则,支持自定义验证。

import { useValidation } from "@/hooks"  

const validation = useValidation()  

// 创建表单数据和规则  
const { formData, rules, validate, errors, resetValidation } = validation.createForm(  
  {  
    username: "",  
    password: "",  
    email: "",  
    phone: "",  
  },  
  {  
    username: [  
      { required: true, message: "用户名不能为空" },  
      { min: 3, max: 20, message: "长度在3到20个字符" },  
    ],  
    password: [  
      { required: true, message: "密码不能为空" },  
      { min: 6, message: "密码长度不能小于6位" },  
      { pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/, message: "密码必须包含大小写字母和数字" },  
    ],  
    email: [{ type: "email", message: "请输入正确的邮箱格式" }],  
    phone: [{ pattern: /^1[3-9]\d{9}$/, message: "请输入正确的手机号" }],  
  },  
)  

// 提交表单  
const handleSubmit = async () => {  
  const valid = await validate()  
  if (valid) {  
    // 表单验证通过,提交数据  
    console.log("表单数据:", formData)  
  } else {  
    // 表单验证失败  
    console.log("验证错误:", errors.value)  
  }  
}

usePageScroll - 页面滚动

页面滚动相关功能,包括滚动到指定位置、监听滚动事件等。

import { usePageScroll } from "@/hooks"  

const { scrollTo, scrollToTop, scrollToSelector, onPageScroll, getScrollPosition } = usePageScroll()  

// 滚动到顶部  
scrollToTop()  

// 滚动到指定位置  
scrollTo(0, 200, true) // 带动画效果滚动到 200px 位置  

// 滚动到指定元素  
scrollToSelector(".news-item-5", {  
  offset: -20, // 偏移量  
  duration: 300, // 动画时长  
})  

// 监听页面滚动  
onPageScroll((scrollTop) => {  
  if (scrollTop > 100) {  
    // 显示返回顶部按钮  
  }  
})

📋 环境要求

  • Node.js >= 18.0.0
  • pnpm >= 7.0.0
  • 微信开发者工具(开发小程序时使用)

🛠️ 快速开始

# 安装依赖  
pnpm install  

# 开发环境运行  
pnpm dev:mp-weixin  

# 生产环境构建  
pnpm build:mp-weixin

📱 开发指南

微信小程序开发

  1. 开发环境配置

    • 运行 pnpm dev:mp-weixin 生成开发环境代码
    • 使用微信开发者工具导入 dist/dev/mp-weixin 目录
    • 开启"不校验合法域名"选项(开发环境)
  2. 生产环境发布

    • 执行 pnpm build:mp-weixin 生成生产环境代码
    • 使用微信开发者工具导入 dist/build/mp-weixin 目录
    • 点击"上传"按钮发布小程序

项目规范

  1. 组件开发规范

    • 全局组件统一放置在 uni-module 目录下
    • 遵循 UniApp 的 easycom 组件规范
    • 组件命名采用 PascalCase 命名法
  2. 类型定义规范

    • 业务类型定义统一放在对应页面的 type.ts 文件中
    • 公共类型定义放在 types 目录下
    • 类型命名采用 PascalCase 命名法
  3. Hooks 使用规范

    • 公共 hooks 统一放在 hooks 目录下
    • 业务相关 hooks 放在对应页面目录
    • hooks 命名采用 camelCase 命名法,以 use 开头

🚀 性能优化

构建优化

  1. 代码分割

    • 使用 manualChunks 实现代码分割
    • 第三方依赖独立打包,提高缓存效率
    • 路由组件按需加载
  2. 资源优化

    • 图片资源自动压缩
    • CSS 代码压缩和优化
    • 静态资源 CDN 加速
  3. 编译优化

    • 使用 lightningcss 进行 CSS 处理
    • 配置合理的 assetsInlineLimit
    • 优化 Sass 编译配置

运行时优化

  1. 渲染优化

    • 图片懒加载
    • 虚拟列表
    • 条件渲染优化
  2. 性能监控

    • 页面加载性能监控
    • 组件渲染性能分析
    • 内存使用监控

📦 项目结构

├── src  
│   ├── pages              # 页面文件  
│   ├── pages-sub          # 子页面  
│   ├── hooks              # 自定义hooks  
│   ├── static             # 静态资源  
│   ├── types              # 类型定义  
│   ├── utils              # 工具函数  
│   └── uni-module         # 全局组件  
├── vite.config.ts         # Vite 配置  
└── package.json           # 项目配置

源码

stars
forks
watchers
license

📄 开源协议

本项目采用 MIT 协议开源,详情请查看 LICENSE 文件。

致谢

感谢 DCloud 官方,旗下出品的 uni-appuni-app-xuniClouduni-app 小程序 等多平台、多元化的技术体系。

收起阅读 »

【鸿蒙征文】星光不负,码向未来:uni-app + uniCloud 赋能社区管理系统的高效适配与生态融合实践

鸿蒙征文

前言

公司前段时间接了一个社区管理的项目,看到本次 鸿蒙征文 活动,正好做一次总结。

我们基于 uni-app 跨端框架和 uniCloud Serverless服务,开发“CommunityHub 社区管理系统”。该系统旨在连接社区物业与居民。

我会在本文中重点阐述了如何利用 uni-app 实现应用在鸿蒙 OS 上的快速适配、多设备响应式布局、已经App和鸿蒙元服务共享代码。

一、技术选型与项目概述

1.1 社区数字化面临的挑战

接到需求后,我们分析,主要有两个挑战:

  • 多端适配: 社区服务需要覆盖居民的手机、平板乃至管理员的PC,且在当下中国,鸿蒙的兼容是必须考虑的平台,维护多套代码成本极高。
  • 后端运维: 社区系统,日常请求量并不高,但需满足特殊情况下的高并发处理,需要稳定且易运维的后端支持。

1.2 uni-app + uniCloud:理想的解决方案

我们选择了 uni-app + uniCloud 这一黄金组合作为 CommunityHub 的技术底座,侧重于开发效率和后端弹性:

  • uni-app:高效跨端开发,一次编码,多端发行,开发效率高。基于 Web 技术的快速适配和高效迭代能力,能够迅速发布应用,并确保基础体验。

  • uniCloud:Serverless 后端,免运维、弹性伸缩,提供数据安全保障。极简 API 调用,为前端提供了统一、稳定的数据接口。

二、鸿蒙 OS 适配与生态融合策略

要使 CommunityHub 在鸿蒙生态中获得成功,我们采取了“高效适配先行,生态融合跟进”的策略。

2.1 策略一:统一的响应式布局,适配多设备形态

作为社区管理系统,CommunityHub 需兼顾居民的手机端和管理员的 PC/平板大屏端体验,充分利用了 uni-app 强大的响应式能力。

设计理念: 利用 uni-app 的条件编译和 CSS 媒体查询功能,配合简洁的组件化设计,实现 UI 的自适应重构。

  • 手机小屏(<768px): 采用底部 TabBar 导航,信息流以列表卡片形式展示,突出点击和触控体验。
  • PC/平板大屏(>1200px): 通过leftWindow切换为侧边栏导航和分栏布局。例如“报修管理”页面,在大屏上可以同时显示报修列表、详情和派单操作区,极大提升了管理员的效率。

这种“一套代码,两种形态”的响应式开发策略,最大限度地复用了代码,为未来应用在鸿蒙多终端上的部署打下了坚实基础。

2.2 策略二:面向鸿蒙 NEXT 生态的融合与升级路径

1. 鸿蒙元服务的支持

优势: 得益于 uni-app 对鸿蒙生态的深度支持,标准 uni-app 项目可以直接发行到鸿蒙元服务。

这极大地简化了开发流程,实现了主 App 与元服务代码基础的统一。

  • 解决方案: 我们采用 “uni-app 快速编译 + uniCloud 数据驱动” 的DCloud全家桶策略。
  • 元服务开发: 在 uni-app 项目中,使用条件编译或单独页面,针对性地开发轻量级的“快捷报修提交”功能模块。
  • 快速部署与统一代码: 将该模块通过 uni-app 编译部署为鸿蒙元服务,确保其 UI 和逻辑与主 App 保持高度一致,并享受 uni-app 的快速迭代优势。
  • 效果: 实现了“一次编码,多端复用”在 App 和元服务上的实践,确保了“即用即走”的用户体验,同时将复杂数据和业务逻辑留在了 uniCloud 统一后端,极大提升了社区服务的便捷性和开发效率。

2. 紧急公告卡片(服务卡片)设想:

对于“停水通知”等紧急公告,我们规划利用鸿蒙的服务卡片能力,通过原生卡片实时拉取 uniCloud 的最新公告数据,直接在用户桌面上显示公告摘要,确保紧急消息的触达率。

参考资料:鸿蒙元服务专题

三、关键功能实现与 uniCloud 赋能

3.1 高效报修与权限控制

核心逻辑:

  • 权限与通知: 通过 uni-id 的角色体系(居民/物业/维修人员)校验身份,并将任务自动派发给对应角色的物业管理员。
  • 进度跟踪: 利用 uni-push,居民端能实时获取报修单状态(已派单、处理中、已完成)的变更,保证了用户体验的实时性。

3.2 邻里交流与资源共享的数据安全

“邻里圈”和“资源共享”模块利用 uniCloud 强大的安全能力:

我们使用 uniCloud 的 云存储 存储用户上传的动态图片和视频,利用 权限规则 确保居民之间数据隔离。

对于“私信联系”功能,基于 uni-id 实现了安全的用户身份认证和通信机制,保护用户隐私。

3.3 技术特点

本系统全部基于uni-app内置组件和uni-ui扩展组件实现,尽可能使用图标代替图片,减少资源包大小,提升小程序的冷启动时间。

如下是对tabbar的一个配置,我们使用了uniicons.ttf,没有使用任何额外图片。

"tabBar": {  
        "color": "#7A7E83",  
        "selectedColor": "#007AFF",  
        "borderStyle": "black",  
        "backgroundColor": "#FFFFFF",  
        "iconfontSrc": "uni_modules/uni-icons/components/uni-icons/uniicons.ttf",  
        "list": [{  
            "pagePath": "pages/home/home",  
            "iconfont": {  
                "text": "\ue662",  
                "selectedText": "\ue663",  
                "fontSize": "22px",  
                "color": "#7A7E83",  
                "selectedColor": "#007AFF"  
            },  
            "text": "首页"  
        }, {  
            "pagePath": "pages/community/post-list",  
            "iconfont": {  
                "text": "\ue682",  
                "selectedText": "\ue682",  
                "fontSize": "22px",  
                "color": "#7A7E83",  
                "selectedColor": "#007AFF"  
            },  
            "text": "邻里圈"  
        }, {  
            "pagePath": "pages/message/message-list",  
            "iconfont": {  
                "text": "\ue6a6",  
                "selectedText": "\ue6c1",  
                "fontSize": "22px",  
                "color": "#7A7E83",  
                "selectedColor": "#007AFF"  
            },  
            "text": "消息"  
        }, {  
            "pagePath": "pages/ucenter/ucenter",  
            "iconfont": {  
                "text": "\ue699",  
                "selectedText": "\ue69d",  
                "fontSize": "22px",  
                "color": "#7A7E83",  
                "selectedColor": "#007AFF"  
            },  
            "text": "我的"  
        }]  
    }

四、总结与展望

感谢DCloud提供的uni-app + uniCloud纯前端方案,我们公司2个前端工程师就把整个项目上线了,系统开发周期大幅缩短,并保证了多端体验的统一性。

展望未来: 我们正积极关注 uni-app x 对鸿蒙 NEXT 的支持进度,并计划将 CommunityHub 逐步迁移至 uni-app x,彻底实现原生渲染,以达到最终的性能目标。

同时,我们也在打磨该系统的标准化,希望发布到 DCloud插件市场,供更多社区使用,或者开源出来。

继续阅读 »

前言

公司前段时间接了一个社区管理的项目,看到本次 鸿蒙征文 活动,正好做一次总结。

我们基于 uni-app 跨端框架和 uniCloud Serverless服务,开发“CommunityHub 社区管理系统”。该系统旨在连接社区物业与居民。

我会在本文中重点阐述了如何利用 uni-app 实现应用在鸿蒙 OS 上的快速适配、多设备响应式布局、已经App和鸿蒙元服务共享代码。

一、技术选型与项目概述

1.1 社区数字化面临的挑战

接到需求后,我们分析,主要有两个挑战:

  • 多端适配: 社区服务需要覆盖居民的手机、平板乃至管理员的PC,且在当下中国,鸿蒙的兼容是必须考虑的平台,维护多套代码成本极高。
  • 后端运维: 社区系统,日常请求量并不高,但需满足特殊情况下的高并发处理,需要稳定且易运维的后端支持。

1.2 uni-app + uniCloud:理想的解决方案

我们选择了 uni-app + uniCloud 这一黄金组合作为 CommunityHub 的技术底座,侧重于开发效率和后端弹性:

  • uni-app:高效跨端开发,一次编码,多端发行,开发效率高。基于 Web 技术的快速适配和高效迭代能力,能够迅速发布应用,并确保基础体验。

  • uniCloud:Serverless 后端,免运维、弹性伸缩,提供数据安全保障。极简 API 调用,为前端提供了统一、稳定的数据接口。

二、鸿蒙 OS 适配与生态融合策略

要使 CommunityHub 在鸿蒙生态中获得成功,我们采取了“高效适配先行,生态融合跟进”的策略。

2.1 策略一:统一的响应式布局,适配多设备形态

作为社区管理系统,CommunityHub 需兼顾居民的手机端和管理员的 PC/平板大屏端体验,充分利用了 uni-app 强大的响应式能力。

设计理念: 利用 uni-app 的条件编译和 CSS 媒体查询功能,配合简洁的组件化设计,实现 UI 的自适应重构。

  • 手机小屏(<768px): 采用底部 TabBar 导航,信息流以列表卡片形式展示,突出点击和触控体验。
  • PC/平板大屏(>1200px): 通过leftWindow切换为侧边栏导航和分栏布局。例如“报修管理”页面,在大屏上可以同时显示报修列表、详情和派单操作区,极大提升了管理员的效率。

这种“一套代码,两种形态”的响应式开发策略,最大限度地复用了代码,为未来应用在鸿蒙多终端上的部署打下了坚实基础。

2.2 策略二:面向鸿蒙 NEXT 生态的融合与升级路径

1. 鸿蒙元服务的支持

优势: 得益于 uni-app 对鸿蒙生态的深度支持,标准 uni-app 项目可以直接发行到鸿蒙元服务。

这极大地简化了开发流程,实现了主 App 与元服务代码基础的统一。

  • 解决方案: 我们采用 “uni-app 快速编译 + uniCloud 数据驱动” 的DCloud全家桶策略。
  • 元服务开发: 在 uni-app 项目中,使用条件编译或单独页面,针对性地开发轻量级的“快捷报修提交”功能模块。
  • 快速部署与统一代码: 将该模块通过 uni-app 编译部署为鸿蒙元服务,确保其 UI 和逻辑与主 App 保持高度一致,并享受 uni-app 的快速迭代优势。
  • 效果: 实现了“一次编码,多端复用”在 App 和元服务上的实践,确保了“即用即走”的用户体验,同时将复杂数据和业务逻辑留在了 uniCloud 统一后端,极大提升了社区服务的便捷性和开发效率。

2. 紧急公告卡片(服务卡片)设想:

对于“停水通知”等紧急公告,我们规划利用鸿蒙的服务卡片能力,通过原生卡片实时拉取 uniCloud 的最新公告数据,直接在用户桌面上显示公告摘要,确保紧急消息的触达率。

参考资料:鸿蒙元服务专题

三、关键功能实现与 uniCloud 赋能

3.1 高效报修与权限控制

核心逻辑:

  • 权限与通知: 通过 uni-id 的角色体系(居民/物业/维修人员)校验身份,并将任务自动派发给对应角色的物业管理员。
  • 进度跟踪: 利用 uni-push,居民端能实时获取报修单状态(已派单、处理中、已完成)的变更,保证了用户体验的实时性。

3.2 邻里交流与资源共享的数据安全

“邻里圈”和“资源共享”模块利用 uniCloud 强大的安全能力:

我们使用 uniCloud 的 云存储 存储用户上传的动态图片和视频,利用 权限规则 确保居民之间数据隔离。

对于“私信联系”功能,基于 uni-id 实现了安全的用户身份认证和通信机制,保护用户隐私。

3.3 技术特点

本系统全部基于uni-app内置组件和uni-ui扩展组件实现,尽可能使用图标代替图片,减少资源包大小,提升小程序的冷启动时间。

如下是对tabbar的一个配置,我们使用了uniicons.ttf,没有使用任何额外图片。

"tabBar": {  
        "color": "#7A7E83",  
        "selectedColor": "#007AFF",  
        "borderStyle": "black",  
        "backgroundColor": "#FFFFFF",  
        "iconfontSrc": "uni_modules/uni-icons/components/uni-icons/uniicons.ttf",  
        "list": [{  
            "pagePath": "pages/home/home",  
            "iconfont": {  
                "text": "\ue662",  
                "selectedText": "\ue663",  
                "fontSize": "22px",  
                "color": "#7A7E83",  
                "selectedColor": "#007AFF"  
            },  
            "text": "首页"  
        }, {  
            "pagePath": "pages/community/post-list",  
            "iconfont": {  
                "text": "\ue682",  
                "selectedText": "\ue682",  
                "fontSize": "22px",  
                "color": "#7A7E83",  
                "selectedColor": "#007AFF"  
            },  
            "text": "邻里圈"  
        }, {  
            "pagePath": "pages/message/message-list",  
            "iconfont": {  
                "text": "\ue6a6",  
                "selectedText": "\ue6c1",  
                "fontSize": "22px",  
                "color": "#7A7E83",  
                "selectedColor": "#007AFF"  
            },  
            "text": "消息"  
        }, {  
            "pagePath": "pages/ucenter/ucenter",  
            "iconfont": {  
                "text": "\ue699",  
                "selectedText": "\ue69d",  
                "fontSize": "22px",  
                "color": "#7A7E83",  
                "selectedColor": "#007AFF"  
            },  
            "text": "我的"  
        }]  
    }

四、总结与展望

感谢DCloud提供的uni-app + uniCloud纯前端方案,我们公司2个前端工程师就把整个项目上线了,系统开发周期大幅缩短,并保证了多端体验的统一性。

展望未来: 我们正积极关注 uni-app x 对鸿蒙 NEXT 的支持进度,并计划将 CommunityHub 逐步迁移至 uni-app x,彻底实现原生渲染,以达到最终的性能目标。

同时,我们也在打磨该系统的标准化,希望发布到 DCloud插件市场,供更多社区使用,或者开源出来。

收起阅读 »

ffmpeg 16KB问题

16KB

最近google市场要求16KB内存对齐的问题,市场上大多数的ffmpeg包都不支持;
我找到了一个,请在android studio中的build.gradle中增加
implementation 'com.moizhassan.ffmpeg:ffmpeg-kit-16kb:6.0.0'
通过google脚本测试,该包已修复16KB问题;

附件是我用ffmpeg写好的音视频处理页面;

继续阅读 »

最近google市场要求16KB内存对齐的问题,市场上大多数的ffmpeg包都不支持;
我找到了一个,请在android studio中的build.gradle中增加
implementation 'com.moizhassan.ffmpeg:ffmpeg-kit-16kb:6.0.0'
通过google脚本测试,该包已修复16KB问题;

附件是我用ffmpeg写好的音视频处理页面;

收起阅读 »