HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

l-floating-panel 添加的多标签的主题内容使用transform: translateY(10px);会导致l-floating-panel 主题内容里面的image无法加载显示

最近开发一个浮动弹框的功能,插件选择的是l-floating-panel ,插件主题内容里面有一个可以跟随软键盘弹出收回的输入框,开始使用到了ransform: translateY(10px);加transiton 来实现,在功能实现之后发现组件内的主题内容里面的所有的标签image都不能显示,花费大量的事件排查之后,发现需要删除ransform: translateY(10px);才可以显示,最后尝试改用margin-bottom或bottom来实现,大家必坑,希望官方能排查下解决。

继续阅读 »

最近开发一个浮动弹框的功能,插件选择的是l-floating-panel ,插件主题内容里面有一个可以跟随软键盘弹出收回的输入框,开始使用到了ransform: translateY(10px);加transiton 来实现,在功能实现之后发现组件内的主题内容里面的所有的标签image都不能显示,花费大量的事件排查之后,发现需要删除ransform: translateY(10px);才可以显示,最后尝试改用margin-bottom或bottom来实现,大家必坑,希望官方能排查下解决。

收起阅读 »

【鸿蒙征文】从鸿蒙适配到迎娶白富美:一个程序员的逆袭之路

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

从鸿蒙适配到迎娶白富美:一个程序员的逆袭之路

2025年春天,公司正处在发展的十字路口。我们是一家深耕工业物联网多年的科技企业,手握多项核心专利,却始终卡在“最后一公里”——客户落地难、交付周期长、跨平台兼容性差。就在这个节骨眼上,一通电话打破了平静。

“华为生态链头部客户要下大单!但前提是——必须在两周内完成APP对鸿蒙Next系统的完整适配,并支持元服务功能。”
——董事长在紧急会议上宣布,语气中带着前所未有的凝重。

会议室鸦雀无声。技术总监老张脸色发白:“鸿蒙Next已经彻底抛弃安卓兼容层,这意味着我们现有的Android原生App几乎要推倒重来……两周?不可能!”

我坐在角落,默默听着大家的焦虑。作为公司里唯一长期使用 uni-app 开发多端应用的工程师,我心里却燃起一丝希望。


危机中的转机:uni-app 成为破局关键

那天晚上,我翻出自己过去三年用 uni-app 维护的十几个项目——覆盖微信小程序、iOS、Android,甚至 H5。突然想起不久前看到的一条消息:uni-app v3.99+ 已全面支持鸿蒙 Next 与元服务

“如果能复用现有代码,或许真有机会!”我立刻打开 DCloud 官网文档,确认了三点关键信息:

  1. 语法兼容:依然使用 Vue 3 + TypeScript,无需学习 ArkTS;
  2. API 桥接uni.login()uni.share() 等接口已自动适配鸿蒙环境;
  3. 元服务支持:只需在 pages.json 中标记页面为原子化服务即可。

第二天一早,我直接敲开了董事长办公室的门。

“王董,给我三天时间。如果搞不定,我主动辞职。”

他盯着我看了十秒,最终点头:“好,资源你随便调,失败不追责,成功——你就是技术负责人。”


72 小时极限攻坚:从零到鸿蒙上架

第一天:环境搭建 + 证书配置(4 小时)

我迅速安装 HUAWEI DevEco Studio,导入公司主力产品“智联工控”的 uni-app 工程。得益于 uni-app 的标准化结构,项目几乎无痛迁移。

# 使用HBX命令行工具初始化鸿蒙项目  
hbx create --type app --name SmartFactory --template uni-preset-vue

接着在华为AGC平台申请四大证书(p12、csr、cer、p7b)。过程比想象中简单,尤其对比苹果那套繁琐流程,华为的自动化程度令人惊喜。

第二天:核心代码改造(8小时)

重点改造四个模块:

1. 登录体系切换为华为账号

原代码依赖自建账号系统,现需接入华为OAuth:

// 改造前(自建登录)  
uni.request({  
  url: 'https://api.company.com/login',  
  data: { username, password }  
});  

// 改造后(鸿蒙静默登录)  
uni.login({  
  success: (res) => {  
    // 获取 authCode  
    const code = res.code;  
    // 后台用 code 换取 OpenID 和手机号  
    uni.request({  
      url: 'https://api.company.com/harmony/login',  
      method: 'POST',  
      data: { auth_code: code }  
    });  
  }  
});

后端同事配合极快,当晚就完成了华为OAuth2.0对接。

2. 权限申请适配

鸿蒙对权限管理更严格,需动态申请:

// 请求位置权限  
uni.authorize({  
  scope: 'scope.location',  
  success() {  
    uni.getLocation({ type: 'gcj02' });  
  }  
});

3. 分享功能切换 provider

// 原微信分享  
uni.share({  
  provider: 'weixin',  
  scene: 'WXSceneSession'  
});  

// 鸿蒙分享(自动识别环境)  
uni.share({  
  provider: 'harmony', // uni-app 自动路由到鸿蒙API  
  title: '设备运行状态报告',  
  summary: '点击查看实时数据'  
});

4. 元服务卡片开发

最关键的突破!我们在 pages.json 中新增原子化页面:

{  
  "pages": [  
    {  
      "path": "pages/dashboard/mini",  
      "style": {  
        "isEntry": true,  
        "isAtomic": true,  
        "navigationBarTitleText": "设备监控"  
      }  
    }  
  ]  
}

用户无需安装完整APP,即可通过服务卡片查看设备温度、能耗等核心指标——这正是客户最看重的功能!

第三天:真机调试 + 上架审核(6小时)

用华为nova 12真机测试,启动速度比Android版快40%。提交至华为应用市场后,仅5小时就通过审核!

当我在群里发出“已上架”截图时,整个技术部沸腾了。


董事长的赏识与命运的转折

庆功宴上,董事长举杯对我说:“小陈,从今天起,你就是CTO助理,直接向我汇报。”

更让我意外的是,他女儿林婉清——公司新任业务总监,竟主动加我微信:“下次客户演示,你跟我一起去。”

林婉清,海归MBA,肤白貌美,气质干练。过去我只敢远远看一眼,如今却要并肩作战。

接下来三个月,我们接连拿下三个鸿蒙生态订单:

  1. 智慧园区项目:用uni-app快速输出鸿蒙+元服务双版本,客户现场扫码即用;
  2. 医疗巡检系统:利用uni-app的跨端能力,一套代码同时交付iOS、Android、HarmonyOS;
  3. 零售POS终端:通过鸿蒙分布式能力,实现手机与POS机无缝协同。

每次演示,她负责讲解业务价值,我负责现场编码调试。有一次客户临时要求增加NFC打卡功能,我当场用uni-app插件市场集成鸿蒙NFC模块:

// 调用鸿蒙NFC读取标签  
const nfc = uni.requireNativePlugin('HarmonyNFC');  
nfc.readTag((data) => {  
  console.log('NFC标签内容:', data);  
});

客户当场签约。林婉清看着我,眼里闪着光:“你真是我的幸运星。”


技术成就爱情:uni-app成红娘

渐渐地,我们的合作从工作延伸到生活。她喜欢喝星巴克,我就用uni-app写了个“咖啡优惠券聚合”小程序送她;她出差怕丢行李,我给她做了个带蓝牙追踪的鸿蒙元服务卡片。

去年圣诞节,我在公司年会上公开表白,背景屏播放着我们共同开发的六个鸿蒙应用图标,最后定格在一行代码:

<template>  
  <view class="love">  
    {{ herName }} + {{ myName }} = Forever  
  </view>  
</template>  

<script setup>  
const herName = 'Lin Wanqing';  
const myName = 'Chen Tech';  
</script>

全场欢呼。董事长笑着点头:“我早就看出来了——技术过硬,人品可靠,配得上我女儿。”

今年五一,我们领证了。婚礼上,我送给她的不是钻戒,而是一个定制的uni-app项目——《我们的爱情编年史》,支持鸿蒙、iOS、Android三端同步,连元服务卡片都做了纪念日提醒。


结语:时代红利属于准备好的人

回望这段经历,我深知:不是我有多厉害,而是选对了工具,踩准了风口。

uni-app让我在技术变革浪潮中脱颖而出;鸿蒙Next的生态红利,给了普通人逆袭的机会。如果你也在焦虑“是否要学ArkTS”“旧项目怎么办”,我的答案是:

别重写,用uni-app改造!三天,足够改变命运。

如今,作为公司技术负责人,我带队全面转向uni-app + 鸿蒙架构。而林婉清常说:“当初那个敢赌三天的男人,现在是我最坚实的依靠。”

技术改变世界,也改变了我的人生。

继续阅读 »

从鸿蒙适配到迎娶白富美:一个程序员的逆袭之路

2025年春天,公司正处在发展的十字路口。我们是一家深耕工业物联网多年的科技企业,手握多项核心专利,却始终卡在“最后一公里”——客户落地难、交付周期长、跨平台兼容性差。就在这个节骨眼上,一通电话打破了平静。

“华为生态链头部客户要下大单!但前提是——必须在两周内完成APP对鸿蒙Next系统的完整适配,并支持元服务功能。”
——董事长在紧急会议上宣布,语气中带着前所未有的凝重。

会议室鸦雀无声。技术总监老张脸色发白:“鸿蒙Next已经彻底抛弃安卓兼容层,这意味着我们现有的Android原生App几乎要推倒重来……两周?不可能!”

我坐在角落,默默听着大家的焦虑。作为公司里唯一长期使用 uni-app 开发多端应用的工程师,我心里却燃起一丝希望。


危机中的转机:uni-app 成为破局关键

那天晚上,我翻出自己过去三年用 uni-app 维护的十几个项目——覆盖微信小程序、iOS、Android,甚至 H5。突然想起不久前看到的一条消息:uni-app v3.99+ 已全面支持鸿蒙 Next 与元服务

“如果能复用现有代码,或许真有机会!”我立刻打开 DCloud 官网文档,确认了三点关键信息:

  1. 语法兼容:依然使用 Vue 3 + TypeScript,无需学习 ArkTS;
  2. API 桥接uni.login()uni.share() 等接口已自动适配鸿蒙环境;
  3. 元服务支持:只需在 pages.json 中标记页面为原子化服务即可。

第二天一早,我直接敲开了董事长办公室的门。

“王董,给我三天时间。如果搞不定,我主动辞职。”

他盯着我看了十秒,最终点头:“好,资源你随便调,失败不追责,成功——你就是技术负责人。”


72 小时极限攻坚:从零到鸿蒙上架

第一天:环境搭建 + 证书配置(4 小时)

我迅速安装 HUAWEI DevEco Studio,导入公司主力产品“智联工控”的 uni-app 工程。得益于 uni-app 的标准化结构,项目几乎无痛迁移。

# 使用HBX命令行工具初始化鸿蒙项目  
hbx create --type app --name SmartFactory --template uni-preset-vue

接着在华为AGC平台申请四大证书(p12、csr、cer、p7b)。过程比想象中简单,尤其对比苹果那套繁琐流程,华为的自动化程度令人惊喜。

第二天:核心代码改造(8小时)

重点改造四个模块:

1. 登录体系切换为华为账号

原代码依赖自建账号系统,现需接入华为OAuth:

// 改造前(自建登录)  
uni.request({  
  url: 'https://api.company.com/login',  
  data: { username, password }  
});  

// 改造后(鸿蒙静默登录)  
uni.login({  
  success: (res) => {  
    // 获取 authCode  
    const code = res.code;  
    // 后台用 code 换取 OpenID 和手机号  
    uni.request({  
      url: 'https://api.company.com/harmony/login',  
      method: 'POST',  
      data: { auth_code: code }  
    });  
  }  
});

后端同事配合极快,当晚就完成了华为OAuth2.0对接。

2. 权限申请适配

鸿蒙对权限管理更严格,需动态申请:

// 请求位置权限  
uni.authorize({  
  scope: 'scope.location',  
  success() {  
    uni.getLocation({ type: 'gcj02' });  
  }  
});

3. 分享功能切换 provider

// 原微信分享  
uni.share({  
  provider: 'weixin',  
  scene: 'WXSceneSession'  
});  

// 鸿蒙分享(自动识别环境)  
uni.share({  
  provider: 'harmony', // uni-app 自动路由到鸿蒙API  
  title: '设备运行状态报告',  
  summary: '点击查看实时数据'  
});

4. 元服务卡片开发

最关键的突破!我们在 pages.json 中新增原子化页面:

{  
  "pages": [  
    {  
      "path": "pages/dashboard/mini",  
      "style": {  
        "isEntry": true,  
        "isAtomic": true,  
        "navigationBarTitleText": "设备监控"  
      }  
    }  
  ]  
}

用户无需安装完整APP,即可通过服务卡片查看设备温度、能耗等核心指标——这正是客户最看重的功能!

第三天:真机调试 + 上架审核(6小时)

用华为nova 12真机测试,启动速度比Android版快40%。提交至华为应用市场后,仅5小时就通过审核!

当我在群里发出“已上架”截图时,整个技术部沸腾了。


董事长的赏识与命运的转折

庆功宴上,董事长举杯对我说:“小陈,从今天起,你就是CTO助理,直接向我汇报。”

更让我意外的是,他女儿林婉清——公司新任业务总监,竟主动加我微信:“下次客户演示,你跟我一起去。”

林婉清,海归MBA,肤白貌美,气质干练。过去我只敢远远看一眼,如今却要并肩作战。

接下来三个月,我们接连拿下三个鸿蒙生态订单:

  1. 智慧园区项目:用uni-app快速输出鸿蒙+元服务双版本,客户现场扫码即用;
  2. 医疗巡检系统:利用uni-app的跨端能力,一套代码同时交付iOS、Android、HarmonyOS;
  3. 零售POS终端:通过鸿蒙分布式能力,实现手机与POS机无缝协同。

每次演示,她负责讲解业务价值,我负责现场编码调试。有一次客户临时要求增加NFC打卡功能,我当场用uni-app插件市场集成鸿蒙NFC模块:

// 调用鸿蒙NFC读取标签  
const nfc = uni.requireNativePlugin('HarmonyNFC');  
nfc.readTag((data) => {  
  console.log('NFC标签内容:', data);  
});

客户当场签约。林婉清看着我,眼里闪着光:“你真是我的幸运星。”


技术成就爱情:uni-app成红娘

渐渐地,我们的合作从工作延伸到生活。她喜欢喝星巴克,我就用uni-app写了个“咖啡优惠券聚合”小程序送她;她出差怕丢行李,我给她做了个带蓝牙追踪的鸿蒙元服务卡片。

去年圣诞节,我在公司年会上公开表白,背景屏播放着我们共同开发的六个鸿蒙应用图标,最后定格在一行代码:

<template>  
  <view class="love">  
    {{ herName }} + {{ myName }} = Forever  
  </view>  
</template>  

<script setup>  
const herName = 'Lin Wanqing';  
const myName = 'Chen Tech';  
</script>

全场欢呼。董事长笑着点头:“我早就看出来了——技术过硬,人品可靠,配得上我女儿。”

今年五一,我们领证了。婚礼上,我送给她的不是钻戒,而是一个定制的uni-app项目——《我们的爱情编年史》,支持鸿蒙、iOS、Android三端同步,连元服务卡片都做了纪念日提醒。


结语:时代红利属于准备好的人

回望这段经历,我深知:不是我有多厉害,而是选对了工具,踩准了风口。

uni-app让我在技术变革浪潮中脱颖而出;鸿蒙Next的生态红利,给了普通人逆袭的机会。如果你也在焦虑“是否要学ArkTS”“旧项目怎么办”,我的答案是:

别重写,用uni-app改造!三天,足够改变命运。

如今,作为公司技术负责人,我带队全面转向uni-app + 鸿蒙架构。而林婉清常说:“当初那个敢赌三天的男人,现在是我最坚实的依靠。”

技术改变世界,也改变了我的人生。

收起阅读 »

关于uni-push VIVO厂商本地通知分类管控公告

vivo unipush

关于uni-push,即日起vivo推送基于《 消息分类说明》对本地通知进行分类管控,若应用在2026年1月1日前未完成渠道备案申请以及未设置channel_id,发送的本地通知将默认为运营消息。详情请查阅《 vivo本地通知分类管控公告》。

为了不影响您应用的重要消息推送,建议您:

  1. 在vivo开发者平台完成渠道备案申请,接入流程详见《 本地通知接入说明》。

  2. 若您发送个推在线消息是纯透传类型,并且应用端在接收消息后,调用本地通知接口展示消息到通知栏,则需要对应设置所申请的通知渠道id和渠道名称,个推服务端API无需改动;若您发送个推在线消息是通知类型,则调用个推服务端API时,设置channel_id和channel_name参数,对应填入所申请的通知渠道id和渠道名称,接口详见《公共请求参数-push_message 在线个推通道消息内容 接口》。

(注:

  1. 本地通知指应用在运行时,直接调用系统接口,不通过厂商vpush系统通道发送的通知。

  2. 透传,即只发送数据给应用端,个推并不做任何处理,应用端可以自己解析字符串进行业务逻辑的实现,比如语音播报、或者创建自定义通知等;通知,即为经个推sdk自动处理后,在通知栏以通知形式展示。)

以上请尽快完成适配,根据应用业务场景,发送不同类别的消息。

如有疑问,可以添加微信客服群进行咨询,加群方式参考文档:https://uniapp.dcloud.net.cn/unipush-v2.html#%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98

继续阅读 »

关于uni-push,即日起vivo推送基于《 消息分类说明》对本地通知进行分类管控,若应用在2026年1月1日前未完成渠道备案申请以及未设置channel_id,发送的本地通知将默认为运营消息。详情请查阅《 vivo本地通知分类管控公告》。

为了不影响您应用的重要消息推送,建议您:

  1. 在vivo开发者平台完成渠道备案申请,接入流程详见《 本地通知接入说明》。

  2. 若您发送个推在线消息是纯透传类型,并且应用端在接收消息后,调用本地通知接口展示消息到通知栏,则需要对应设置所申请的通知渠道id和渠道名称,个推服务端API无需改动;若您发送个推在线消息是通知类型,则调用个推服务端API时,设置channel_id和channel_name参数,对应填入所申请的通知渠道id和渠道名称,接口详见《公共请求参数-push_message 在线个推通道消息内容 接口》。

(注:

  1. 本地通知指应用在运行时,直接调用系统接口,不通过厂商vpush系统通道发送的通知。

  2. 透传,即只发送数据给应用端,个推并不做任何处理,应用端可以自己解析字符串进行业务逻辑的实现,比如语音播报、或者创建自定义通知等;通知,即为经个推sdk自动处理后,在通知栏以通知形式展示。)

以上请尽快完成适配,根据应用业务场景,发送不同类别的消息。

如有疑问,可以添加微信客服群进行咨询,加群方式参考文档:https://uniapp.dcloud.net.cn/unipush-v2.html#%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98

收起阅读 »

【插件】鸿蒙激励计划小助手 - 让数据统计更轻松

鸿蒙征文

一个专为华为鸿蒙激励计划开发者打造的数据统计与可视化 Chrome 浏览器插件


阶段表:

最新更新 (v2.0.0)

  • 新增日活数据统计 - 显示昨日新增、首月、次月、第三月日活数据
  • 新增趋势图表 - 点击应用展开查看日活数据可视化图表
  • 新增鸿蒙功德木鱼 - 趣味互动功能,点击木鱼积累功德

功能特点

核心功能

  • 实时数据统计 - 动态计算应用总数、激励金额、达标情况等关键指标
  • 可视化展示 - 美观的侧边栏界面,清晰展示所有统计数据
  • 分类统计 - 自动区分应用和游戏类型,分别统计
  • 阶段追踪 - 追踪应用在各个激励阶段的分布情况
  • 日活数据统计 - 展示应用的昨日新增、首月、次月、第三月日活数据
  • 趋势图表 - 可视化显示应用日活数据走势图,支持展开查看详情
  • 海报生成 - 一键生成精美的数据统计海报,支持下载分享
  • 鸿蒙功德木鱼 - 趣味互动,点击敲木鱼积累功德

界面特性

  • ✓ 现代化的渐变色设计
  • ✓ 固定在页面右侧,不影响正常浏览
  • ✓ 可收起/展开,灵活控制显示
  • ✓ 响应式布局,适配不同屏幕
  • ✓ 清晰的数据可视化卡片
  • ✓ 支持暗色主题适配

统计指标

  • 应用总数 - 显示应用和游戏的总数量
  • 预估激励 - 根据应用/游戏数量预估总激励(应用¥10,000,游戏¥2,000)
  • 已获激励 - 实际已达标的激励金额总和
  • 平均激励 - 单个应用的平均激励金额
  • 达标情况 - 基础激励、一阶段、二阶段的达标数量和比例
  • 阶段分布 - 各阶段应用数量的可视化展示
  • 日活数据 - 昨日新增日活、首月(1-30天)、次月(31-60天)、第三月(61-90天)日活统计
  • 趋势可视化 - 点击应用卡片展开,查看日活数据趋势图表

安全保障

隐私与安全

  • 本地运行 - 所有数据处理均在浏览器本地完成,无需远程服务器
  • 完全离线 - 插件不联网,不收集、不上传任何用户数据
  • 开源透明 - 所有代码完全开源,可随时审查和验证
  • 数据安全 - 数据仅存储在本地浏览器,用户完全掌控
  • 无后门风险 - 无任何第三方服务依赖,无隐私泄露风险

本插件严格遵循隐私保护原则,您的数据安全是我们的首要承诺!


安装方法

方式一:Chrome 网上应用店安装(推荐)

  1. 访问插件页面

  2. 安装插件

    • 点击"添加至 Chrome"按钮
    • 在弹出的确认对话框中点击"添加扩展程序"
    • 等待安装完成
  3. 开始使用

    • 访问华为鸿蒙激励计划数据查询页面即可自动使用

方式二:开发者模式安装(开发测试)

  1. 下载项目代码

    https://github.com/zwpro/harmonyos-incentive.git  
  2. 打开 Chrome 扩展管理页面

    • 在地址栏输入:chrome://extensions/
    • 或点击菜单 -> 更多工具 -> 扩展程序
  3. 启用开发者模式

    • 打开页面右上角的"开发者模式"开关
  4. 加载插件

    • 点击"加载已解压的扩展程序"
    • 选择项目所在的文件夹
    • 确认加载成功

使用说明

基本使用

  1. 访问目标页面

  2. 等待数据加载

    • 插件会自动在页面右侧显示侧边栏
    • 等待页面数据加载完成(或切换分页、刷新页面)
  3. 查看统计数据

    • 侧边栏自动展示所有应用的统计信息
    • 包括总数、激励金额、达标情况等

高级功能

生成统计海报

  1. 点击侧边栏中的"生成海报"按钮
  2. 等待海报生成(基于 html2canvas 技术)
  3. 在弹窗中预览海报效果
  4. 点击"下载海报"保存到本地,或直接关闭

应用详情查看

  • 每个应用卡片显示:应用名称、包名、类型、当前阶段、各阶段激励金额
  • 支持按阶段筛选和查看
  • 清晰的达标状态标识
  • 日活数据展示:显示昨日新增、首月、次月、第三月日活数据
  • 趋势图表:点击应用卡片展开,查看日活数据的可视化趋势图

鸿蒙功德木鱼

趣味互动功能,为开发之旅增添乐趣:

  • 点击木鱼图标积累功德,配合敲击动画和音效
  • 统计今日和总计敲击次数,数据本地保存

实时计算多维度统计指标:

  • 总激励 = Σ(各应用激励)
  • 预估激励 = 应用数 × 10000 + 游戏数 × 2000
  • 达标率 = 达标数量 / 总数量 × 100%

贡献指南

欢迎提交 Issue 和 Pull Request!


更新日志

v2.0.0 (2025-11-08)

  • 新增日活数据统计 - 显示昨日新增、首月、次月、第三月日活数据
  • 新增趋势图表 - 点击应用展开查看日活数据可视化图表
  • 新增鸿蒙功德木鱼 - 趣味互动功能,点击木鱼积累功德
  • 优化表格展示,增加日活数据列
  • 木鱼敲击动画、音效和特效
  • 功德数据本地持久化存储
  • 集成 Chart.js 图表库

v1.0.0 (2025-11-02)

  • 首次发布
  • 支持自动数据捕获和统计
  • 实现侧边栏可视化展示
  • 支持海报生成和下载
  • 完整的应用和游戏分类统计
  • 多维度数据指标展示

常见问题

Q: 为什么看不到数据?

A: 请确保:

  1. 已正确安装插件并刷新页面
  2. 访问的是正确的华为开发者平台页面
  3. 页面数据已加载完成(可尝试切换分页)

Q: 海报生成失败怎么办?

A: 可能原因:

  1. html2canvas 库未正确加载 - 尝试重新加载插件
  2. 浏览器兼容性问题 - 建议使用最新版 Chrome

Q: 如何更新插件?

A:

  • 从 Chrome 网上应用店安装的用户:插件会自动更新,无需手动操作
  • 开发者模式安装的用户
    1. 拉取最新代码:git pull
    2. 在扩展管理页面点击刷新图标

许可证

本项目基于 MIT License 开源。


相关链接

Chrome 网上应用店 - 安装插件
GitHub 仓库
华为鸿蒙激励计划 - 官方页面
uniapp - 官方页面
问题反馈 - 提交 Issue


交流与支持

如果这个插件对您有帮助,欢迎:

    • Star 本项目
    • 提交 Bug 报告
    • 提出新功能建议
    • 分享给其他开发者

为 HarmonyOS 开发者用心制作
Code by Uniapp

继续阅读 »

一个专为华为鸿蒙激励计划开发者打造的数据统计与可视化 Chrome 浏览器插件


阶段表:

最新更新 (v2.0.0)

  • 新增日活数据统计 - 显示昨日新增、首月、次月、第三月日活数据
  • 新增趋势图表 - 点击应用展开查看日活数据可视化图表
  • 新增鸿蒙功德木鱼 - 趣味互动功能,点击木鱼积累功德

功能特点

核心功能

  • 实时数据统计 - 动态计算应用总数、激励金额、达标情况等关键指标
  • 可视化展示 - 美观的侧边栏界面,清晰展示所有统计数据
  • 分类统计 - 自动区分应用和游戏类型,分别统计
  • 阶段追踪 - 追踪应用在各个激励阶段的分布情况
  • 日活数据统计 - 展示应用的昨日新增、首月、次月、第三月日活数据
  • 趋势图表 - 可视化显示应用日活数据走势图,支持展开查看详情
  • 海报生成 - 一键生成精美的数据统计海报,支持下载分享
  • 鸿蒙功德木鱼 - 趣味互动,点击敲木鱼积累功德

界面特性

  • ✓ 现代化的渐变色设计
  • ✓ 固定在页面右侧,不影响正常浏览
  • ✓ 可收起/展开,灵活控制显示
  • ✓ 响应式布局,适配不同屏幕
  • ✓ 清晰的数据可视化卡片
  • ✓ 支持暗色主题适配

统计指标

  • 应用总数 - 显示应用和游戏的总数量
  • 预估激励 - 根据应用/游戏数量预估总激励(应用¥10,000,游戏¥2,000)
  • 已获激励 - 实际已达标的激励金额总和
  • 平均激励 - 单个应用的平均激励金额
  • 达标情况 - 基础激励、一阶段、二阶段的达标数量和比例
  • 阶段分布 - 各阶段应用数量的可视化展示
  • 日活数据 - 昨日新增日活、首月(1-30天)、次月(31-60天)、第三月(61-90天)日活统计
  • 趋势可视化 - 点击应用卡片展开,查看日活数据趋势图表

安全保障

隐私与安全

  • 本地运行 - 所有数据处理均在浏览器本地完成,无需远程服务器
  • 完全离线 - 插件不联网,不收集、不上传任何用户数据
  • 开源透明 - 所有代码完全开源,可随时审查和验证
  • 数据安全 - 数据仅存储在本地浏览器,用户完全掌控
  • 无后门风险 - 无任何第三方服务依赖,无隐私泄露风险

本插件严格遵循隐私保护原则,您的数据安全是我们的首要承诺!


安装方法

方式一:Chrome 网上应用店安装(推荐)

  1. 访问插件页面

  2. 安装插件

    • 点击"添加至 Chrome"按钮
    • 在弹出的确认对话框中点击"添加扩展程序"
    • 等待安装完成
  3. 开始使用

    • 访问华为鸿蒙激励计划数据查询页面即可自动使用

方式二:开发者模式安装(开发测试)

  1. 下载项目代码

    https://github.com/zwpro/harmonyos-incentive.git  
  2. 打开 Chrome 扩展管理页面

    • 在地址栏输入:chrome://extensions/
    • 或点击菜单 -> 更多工具 -> 扩展程序
  3. 启用开发者模式

    • 打开页面右上角的"开发者模式"开关
  4. 加载插件

    • 点击"加载已解压的扩展程序"
    • 选择项目所在的文件夹
    • 确认加载成功

使用说明

基本使用

  1. 访问目标页面

  2. 等待数据加载

    • 插件会自动在页面右侧显示侧边栏
    • 等待页面数据加载完成(或切换分页、刷新页面)
  3. 查看统计数据

    • 侧边栏自动展示所有应用的统计信息
    • 包括总数、激励金额、达标情况等

高级功能

生成统计海报

  1. 点击侧边栏中的"生成海报"按钮
  2. 等待海报生成(基于 html2canvas 技术)
  3. 在弹窗中预览海报效果
  4. 点击"下载海报"保存到本地,或直接关闭

应用详情查看

  • 每个应用卡片显示:应用名称、包名、类型、当前阶段、各阶段激励金额
  • 支持按阶段筛选和查看
  • 清晰的达标状态标识
  • 日活数据展示:显示昨日新增、首月、次月、第三月日活数据
  • 趋势图表:点击应用卡片展开,查看日活数据的可视化趋势图

鸿蒙功德木鱼

趣味互动功能,为开发之旅增添乐趣:

  • 点击木鱼图标积累功德,配合敲击动画和音效
  • 统计今日和总计敲击次数,数据本地保存

实时计算多维度统计指标:

  • 总激励 = Σ(各应用激励)
  • 预估激励 = 应用数 × 10000 + 游戏数 × 2000
  • 达标率 = 达标数量 / 总数量 × 100%

贡献指南

欢迎提交 Issue 和 Pull Request!


更新日志

v2.0.0 (2025-11-08)

  • 新增日活数据统计 - 显示昨日新增、首月、次月、第三月日活数据
  • 新增趋势图表 - 点击应用展开查看日活数据可视化图表
  • 新增鸿蒙功德木鱼 - 趣味互动功能,点击木鱼积累功德
  • 优化表格展示,增加日活数据列
  • 木鱼敲击动画、音效和特效
  • 功德数据本地持久化存储
  • 集成 Chart.js 图表库

v1.0.0 (2025-11-02)

  • 首次发布
  • 支持自动数据捕获和统计
  • 实现侧边栏可视化展示
  • 支持海报生成和下载
  • 完整的应用和游戏分类统计
  • 多维度数据指标展示

常见问题

Q: 为什么看不到数据?

A: 请确保:

  1. 已正确安装插件并刷新页面
  2. 访问的是正确的华为开发者平台页面
  3. 页面数据已加载完成(可尝试切换分页)

Q: 海报生成失败怎么办?

A: 可能原因:

  1. html2canvas 库未正确加载 - 尝试重新加载插件
  2. 浏览器兼容性问题 - 建议使用最新版 Chrome

Q: 如何更新插件?

A:

  • 从 Chrome 网上应用店安装的用户:插件会自动更新,无需手动操作
  • 开发者模式安装的用户
    1. 拉取最新代码:git pull
    2. 在扩展管理页面点击刷新图标

许可证

本项目基于 MIT License 开源。


相关链接

Chrome 网上应用店 - 安装插件
GitHub 仓库
华为鸿蒙激励计划 - 官方页面
uniapp - 官方页面
问题反馈 - 提交 Issue


交流与支持

如果这个插件对您有帮助,欢迎:

    • Star 本项目
    • 提交 Bug 报告
    • 提出新功能建议
    • 分享给其他开发者

为 HarmonyOS 开发者用心制作
Code by Uniapp

收起阅读 »

【解决】el-form里面只有一个el-input,按回车键会刷新页面

pc

在el-form标签上添加@submit.native.prevent
例如:

<el-form @submit.native.prevent></el-form>

在el-form标签上添加@submit.native.prevent
例如:

<el-form @submit.native.prevent></el-form>

游戏上架 App Store 需要什么?从开发者资质到开心上架(Appuploader)免 Mac 上传的全流程指南

iOS

'''相比普通工具类应用,游戏上架 App Store 的要求更高,不仅需要苹果开发者资质,还涉及 内容审查、游戏版号、隐私合规 等复杂流程。

尤其在中国大陆地区,游戏上架还需要提供版号与出版备案证明。
因此,对游戏开发者来说,提前了解苹果上架要求并准备好所有资料,是节省时间、避免审核退回的关键。

本文将结合真实上架经验,带你一步步了解上架所需材料、流程与实操工具。


一、游戏上架 App Store 的整体流程

阶段 内容 说明
1 注册 Apple 开发者账号 官方必备,费用 99 美元/年
2 获取游戏版号(中国区) 国内游戏需提供出版备案号
3 准备签名证书与描述文件 用于 IPA 签名验证
4 打包生成 IPA 安装包 游戏的可执行文件
5 上传至 App Store 可使用 Appuploader 免 Mac 上传
6 提交审核与发布上线 苹果人工审核 1–3 个工作日

如果游戏面向全球发行,流程更简单;若面向中国大陆用户,则需额外提供版号信息。


二、注册 Apple Developer 开发者账号

访问 Apple Developer 官网,使用 Apple ID 登录并加入开发者计划。

账号类型 适用对象 年费 特点
个人账号 独立开发者 $99 注册简单,适合个人游戏
公司账号 游戏公司团队 $99 支持多人协作,推荐团队使用

注册公司账号时需提供营业执照和 DUNS(邓氏编码)。
注册


三、国内游戏必备:游戏版号与备案要求

若你的游戏计划上架中国大陆区 App Store,必须提供以下两项法律文件:

文件 说明 发放机构
出版备案号(ISBN) 游戏出版合法性凭证 国家新闻出版署
网络文化经营许可证 游戏上线前置审批 文化和旅游部

版号的游戏在中国区无法正式上架,但仍可在海外区发布。


四、创建签名证书与描述文件

所有游戏 App 必须通过苹果签名系统验证。

证书类型 用途
开发证书(Development) 真机调试测试
发布证书(Distribution) 上架 App Store 使用
描述文件(Provisioning Profile) 绑定 App ID 与设备信息

使用 开心上架(Appuploader) 创建证书

证书

优点:

  • 支持 Windows / Linux / macOS;
  • 免钥匙串助手与 Xcode;
  • 可多人共享使用;
  • 快速生成描述文件,减少配置错误。

五、打包生成 IPA 文件

打包 IPA 是上架前的关键步骤。

技术框架 打包方式
Unity 使用 Xcode 或云构建导出 IPA
Cocos / Cocos Creator Xcode 或命令行构建
Flutter / React Native flutter build ios --release
uni-app / HBuilderX 云打包生成 IPA

若没有 Mac,可直接通过 HBuilder 云打包 + Appuploader CLI 实现完整流程。


六、准备 App Store Connect 上架信息

登录 App Store Connect 并创建游戏项目:

项目 说明
App 名称 与游戏品牌一致
Bundle ID 与证书匹配
SKU 内部追踪编号
App 图标 1024×1024 PNG
截图 5.5”、6.5” 两种尺寸
隐私政策链接 审核必填
版号信息 国内游戏需填写 ISBN 编号

若提交的隐私政策无效链接,App 将被退回。
asc


七、上传 IPA 到 App Store

传统上传方式(Mac 设备)

  • Xcode 上传
  • Transporter App 拖拽上传
  • altool 命令行上传(已弃用)

这些方式都依赖 macOS 环境,对 Windows 或 Linux 用户极不友好。


免 Mac 方案:开心上架(Appuploader) 命令行上传

新版命令行工具支持全平台跨系统上传。

appuploader_cli -u dev@icloud.com -p xxx-xxx-xxx-xxx -c 2 -f ./build/MyGame.ipa
参数 含义
-u Apple 开发者账号
-p App 专用密码
-c 上传通道(1=旧通道,2=新通道)
-f 指定 IPA 文件路径

特点:

  • 支持 Win / Linux / Mac 全系统;
  • 上传日志实时输出;
  • 可批量上传多语言版本;
  • 可集成 CI/CD 自动化。

八、App 审核与上架发布

苹果审核流程通常包含以下阶段:

阶段 内容 时间
自动检测 系统验证 IPA 签名与元数据 数分钟
人工审核 审查内容、UI、功能、隐私政策 1~3 天
上架发布 审核通过后自动上线 立即

审核重点

  • 不得含违规内容(赌博、色情、政治)
  • 不得频繁弹出广告
  • 不得使用未经授权的音乐或素材

九、常见审核拒绝原因与解决方案

问题 原因 解决方式
审核被拒 4.0 App 闪退或不稳定 修复代码并重新打包
审核被拒 5.1 隐私声明缺失 更新 Info.plist 权限说明
审核被拒 3.1 支付体系违规 虚拟商品需使用苹果内购(IAP)
截图问题 尺寸错误或含营销语 按规范上传 5.5” + 6.5” 截图
审核延迟 版号缺失(中国区) 上传 ISBN 备案截图

十、自动化上架实战:Fastlane + 开心上架 CLI

开发团队可实现一键打包上传:

fastlane gym --scheme "MyGame" --output_directory "./build"  
appuploader_cli -u team@icloud.com -p xxx-xxx-xxx-xxx -c 2 -f ./build/MyGame.ipa

优点:

  • 自动化构建、签名、上传;
  • 支持持续集成(Jenkins / GitLab CI);
  • 无需人工干预;
  • 适合定期更新的游戏项目。

游戏上架 App Store 是一个技术、合规与内容协同的过程。
准备充分的开发者不仅能减少审核退回率,还可借助跨平台上传工具,在任意操作系统中完成上架工作。

无论你是独立游戏作者,还是跨平台开发团队,只要掌握了正确流程,上架 App Store 不再困难。

'''

继续阅读 »

'''相比普通工具类应用,游戏上架 App Store 的要求更高,不仅需要苹果开发者资质,还涉及 内容审查、游戏版号、隐私合规 等复杂流程。

尤其在中国大陆地区,游戏上架还需要提供版号与出版备案证明。
因此,对游戏开发者来说,提前了解苹果上架要求并准备好所有资料,是节省时间、避免审核退回的关键。

本文将结合真实上架经验,带你一步步了解上架所需材料、流程与实操工具。


一、游戏上架 App Store 的整体流程

阶段 内容 说明
1 注册 Apple 开发者账号 官方必备,费用 99 美元/年
2 获取游戏版号(中国区) 国内游戏需提供出版备案号
3 准备签名证书与描述文件 用于 IPA 签名验证
4 打包生成 IPA 安装包 游戏的可执行文件
5 上传至 App Store 可使用 Appuploader 免 Mac 上传
6 提交审核与发布上线 苹果人工审核 1–3 个工作日

如果游戏面向全球发行,流程更简单;若面向中国大陆用户,则需额外提供版号信息。


二、注册 Apple Developer 开发者账号

访问 Apple Developer 官网,使用 Apple ID 登录并加入开发者计划。

账号类型 适用对象 年费 特点
个人账号 独立开发者 $99 注册简单,适合个人游戏
公司账号 游戏公司团队 $99 支持多人协作,推荐团队使用

注册公司账号时需提供营业执照和 DUNS(邓氏编码)。
注册


三、国内游戏必备:游戏版号与备案要求

若你的游戏计划上架中国大陆区 App Store,必须提供以下两项法律文件:

文件 说明 发放机构
出版备案号(ISBN) 游戏出版合法性凭证 国家新闻出版署
网络文化经营许可证 游戏上线前置审批 文化和旅游部

版号的游戏在中国区无法正式上架,但仍可在海外区发布。


四、创建签名证书与描述文件

所有游戏 App 必须通过苹果签名系统验证。

证书类型 用途
开发证书(Development) 真机调试测试
发布证书(Distribution) 上架 App Store 使用
描述文件(Provisioning Profile) 绑定 App ID 与设备信息

使用 开心上架(Appuploader) 创建证书

证书

优点:

  • 支持 Windows / Linux / macOS;
  • 免钥匙串助手与 Xcode;
  • 可多人共享使用;
  • 快速生成描述文件,减少配置错误。

五、打包生成 IPA 文件

打包 IPA 是上架前的关键步骤。

技术框架 打包方式
Unity 使用 Xcode 或云构建导出 IPA
Cocos / Cocos Creator Xcode 或命令行构建
Flutter / React Native flutter build ios --release
uni-app / HBuilderX 云打包生成 IPA

若没有 Mac,可直接通过 HBuilder 云打包 + Appuploader CLI 实现完整流程。


六、准备 App Store Connect 上架信息

登录 App Store Connect 并创建游戏项目:

项目 说明
App 名称 与游戏品牌一致
Bundle ID 与证书匹配
SKU 内部追踪编号
App 图标 1024×1024 PNG
截图 5.5”、6.5” 两种尺寸
隐私政策链接 审核必填
版号信息 国内游戏需填写 ISBN 编号

若提交的隐私政策无效链接,App 将被退回。
asc


七、上传 IPA 到 App Store

传统上传方式(Mac 设备)

  • Xcode 上传
  • Transporter App 拖拽上传
  • altool 命令行上传(已弃用)

这些方式都依赖 macOS 环境,对 Windows 或 Linux 用户极不友好。


免 Mac 方案:开心上架(Appuploader) 命令行上传

新版命令行工具支持全平台跨系统上传。

appuploader_cli -u dev@icloud.com -p xxx-xxx-xxx-xxx -c 2 -f ./build/MyGame.ipa
参数 含义
-u Apple 开发者账号
-p App 专用密码
-c 上传通道(1=旧通道,2=新通道)
-f 指定 IPA 文件路径

特点:

  • 支持 Win / Linux / Mac 全系统;
  • 上传日志实时输出;
  • 可批量上传多语言版本;
  • 可集成 CI/CD 自动化。

八、App 审核与上架发布

苹果审核流程通常包含以下阶段:

阶段 内容 时间
自动检测 系统验证 IPA 签名与元数据 数分钟
人工审核 审查内容、UI、功能、隐私政策 1~3 天
上架发布 审核通过后自动上线 立即

审核重点

  • 不得含违规内容(赌博、色情、政治)
  • 不得频繁弹出广告
  • 不得使用未经授权的音乐或素材

九、常见审核拒绝原因与解决方案

问题 原因 解决方式
审核被拒 4.0 App 闪退或不稳定 修复代码并重新打包
审核被拒 5.1 隐私声明缺失 更新 Info.plist 权限说明
审核被拒 3.1 支付体系违规 虚拟商品需使用苹果内购(IAP)
截图问题 尺寸错误或含营销语 按规范上传 5.5” + 6.5” 截图
审核延迟 版号缺失(中国区) 上传 ISBN 备案截图

十、自动化上架实战:Fastlane + 开心上架 CLI

开发团队可实现一键打包上传:

fastlane gym --scheme "MyGame" --output_directory "./build"  
appuploader_cli -u team@icloud.com -p xxx-xxx-xxx-xxx -c 2 -f ./build/MyGame.ipa

优点:

  • 自动化构建、签名、上传;
  • 支持持续集成(Jenkins / GitLab CI);
  • 无需人工干预;
  • 适合定期更新的游戏项目。

游戏上架 App Store 是一个技术、合规与内容协同的过程。
准备充分的开发者不仅能减少审核退回率,还可借助跨平台上传工具,在任意操作系统中完成上架工作。

无论你是独立游戏作者,还是跨平台开发团队,只要掌握了正确流程,上架 App Store 不再困难。

'''

收起阅读 »

【鸿蒙征文】已解决隐私弹窗问题,华为应用商店已经 4 次驳回我的应用上线

鸿蒙征文

一. 前言

不得不说,华为应用商店的审核还是过于严格了,最近提交的新版本应用又被拒绝了!已经提交了4个版本了,再这样下去,我就要崩溃了!

好多人已经对华为的这项审核怨言颇深了!

其实,华为审核严格是一方面,另一方面主要在于 uni-app,由于该应用是使用 uni-app 开发并打包的,所以受限于 uni-app 框架,而它给添加了太多没有用的东西,都集成在框架中,并且删除不掉。

而像本次华为应用商店驳回审核,需要修改的地方已经给罗列的很清楚了,只需要按照他们的说明整改即可,最起码我们能通过自己修改代码就可以完成,不用找官方解决,比较简单。

所以有两个问题需要整改:

  • 隐私政策声明
  • 申请权限时需告知用户使用目的

隐私政策文件的整改很简单,以 com.bun.miitmdid 为例,需要在隐私政策中添加相关说明即可。本篇文章我们直接进行申请权限时的整改,接下来进入正文!

二. 修改权限申请逻辑

前面提到华为应用商店的审核还是过于严格了,其实相比较其他国内应用市场(小米/VIVO/OPPO)来说,区别就在于用户在申请敏感权限时,需同步告知用户申请该权限的目的。对于申请的权限,都必须有明确、合理的使用场景和功能说明,禁止诱导或误导用户授权。

如下图所示:

目前应用内申请权限时是这样的:

接下来我们要进行整改,整改完成后是这样的:

三. 权限申请

1. 使用 plus.android.requestPermissions

统一通过 plus.android.requestPermissions 向系统请求权限,如果权限属于危险权限并且用户没有授权则会弹出系统提示框由用户授权确认。

plus.android.requestPermissions(permissions, successCallback, errorCallback)

2. 参数说明

  • permissions: 申请的权限列表,权限列表参考 Android 官方列表

  • successCallback:  申请权限成功回调函数,参考 AndroidSuccessCallback,返回申请权限的结果,可能被用户允许,回调函数的参数 event 包含以下属性:

    • granted - Array[String]字符串数组,已获取权限列表;
    • deniedPresent - Array[String]字符串数据,已拒绝(临时)的权限列表;
    • deniedAlways - Array[String]字符串数据,永久拒绝的权限列表。
  • errorCallback:  申请权限失败回调函数,参考 AndroidErrorCallback

    • 通常传入参数错误时触发此回调。

注意:Android 系统 6+版本(API 等级 23+),并且必须设置 targetSdkVersion>=23。

如果已经授权或被用户拒绝则返回结果。 授权结果在 successCallback 回调参数中可获取。

为了便于统一申请权限,封装为以下方法,可直接复制使用!

// Android权限查询  
export function requestAndroidPermission(permissionID) {  
  return new Promise((resolve, reject) => {  
    plus.android.requestPermissions(  
      // 理论上支持多个权限同时查询,但实际上本函数封装只处理了一个权限的情况。有需要的可自行扩展封装  
      [permissionID],  
      function (resultObj) {  
        var result = 0  
        for (var i = 0; i < resultObj.granted.length; i++) {  
          var grantedPermission = resultObj.granted[i]  
          console.log('已获取的权限:' + grantedPermission)  
          result = 1  
        }  
        for (var i = 0; i < resultObj.deniedPresent.length; i++) {  
          var deniedPresentPermission = resultObj.deniedPresent[i]  
          console.log('拒绝本次申请的权限:' + deniedPresentPermission)  
          result = 0  
        }  
        for (var i = 0; i < resultObj.deniedAlways.length; i++) {  
          var deniedAlwaysPermission = resultObj.deniedAlways[i]  
          console.log('永久拒绝申请的权限:' + deniedAlwaysPermission)  
          result = -1  
        }  
        uni.setStorageSync('permisionStatus_' + permissionID, result)  

        resolve(result)  
        // 若所需权限被拒绝,则打开APP设置界面,可以在APP设置界面打开相应权限  
        if (result != 1) {  
          // gotoAppPermissionSetting()  
          uni.showModal({  
            content: '权限已经被拒绝,请前往APP设置界面打开相应权限'  
          })  
        }  
      },  
      function (error) {  
        console.log('申请权限错误:' + error.code + ' = ' + error.message)  
        resolve({  
          code: error.code,  
          message: error.message  
        })  
      }  
    )  
  })  
}

此文件来源于 https://ext.dcloud.net.cn/plugin?id=594 部分片段

使用方式:

requestAndroidPermission('android.permission.WRITE_EXTERNAL_STORAGE').then(  
  result => {  
    // result 表示:1已获取,0已拒绝,-1永久拒绝。可根据返回码定向处理  
  }  
)

四. 原生弹窗

接下来我们应该构造一个弹窗类 NativePopup 用于在应用内申请权限时弹窗告知用户,说明申请权限的使用目的。

在这里,App 端使用 plus.nativeObj.view 绘制原生内容,参考:uni-app 中使用 5+界面控件plus.nativeObj.view 规范

代码如下,可直接复制使用!

export class NativePopup {  
  constructor(options = {}) {  
    this.sysInfo = uni.getSystemInfoSync()  

    const {  
      bgColor = '#fff',  
      titleColor = '#000',  
      contentColor = '#272727'  
    } = options  

    this.bgColor = bgColor  
    this.titleColor = titleColor  
    this.contentColor = contentColor  
  }  

  createPopup = () => {  
    const { statusBarHeight, screenWidth } = this.sysInfo  

    const popupView = new plus.nativeObj.View('popupView', {  
      top: 0,  
      left: 0,  
      width: screenWidth,  
      height: 110 + statusBarHeight + 'px'  
      // backgroundColor: 'blue' // debug  
    })  

    popupView.addEventListener('click', this.close)  

    const bgPadding = 15  

    popupView.drawRect(  
      {  
        color: 'rgba(0, 0, 0, 0.1)',  
        radius: '10px'  
      },  
      {  
        top: statusBarHeight + 7 + 'px',  
        left: bgPadding - 2 + 'px',  
        width: screenWidth - bgPadding * 2 + 4 + 'px',  
        height: '100px'  
      }  
    )  

    popupView.drawRect(  
      {  
        color: this.bgColor,  
        radius: '10px'  
      },  
      {  
        top: statusBarHeight + 5 + 'px',  
        left: bgPadding + 'px',  
        width: screenWidth - bgPadding * 2 + 'px',  
        height: '100px'  
      }  
    )  

    const padding = 10  

    popupView.drawText(  
      this.title,  
      {  
        top: statusBarHeight + 10 + 'px',  
        left: padding + bgPadding + 'px',  
        height: '30px',  
        width: screenWidth - bgPadding * 2 - padding * 2 + 'px'  
      },  
      {  
        size: '16px',  
        weight: 'bold',  
        align: 'left',  
        color: this.titleColor  
      },  
      {  
        onClick: function (e) {  
          console.log(e)  
        }  
      }  
    )  

    popupView.drawText(  
      this.content,  
      {  
        top: statusBarHeight + 40 + 'px',  
        height: '60px',  
        left: padding + bgPadding + 'px',  
        width: screenWidth - bgPadding * 2 - padding * 2 + 'px'  
      },  
      {  
        size: '14px',  
        align: 'left',  
        color: this.contentColor,  
        whiteSpace: 'normal'  
      }  
    )  

    this.popupView = popupView  

    return popupView  
  }  

  show = (options = {}) => {  
    this.close()  

    const { title = '权限申请说明', content = '' } = options  
    this.title = title  
    this.content = content  

    this.createPopup()  

    this.popupView.show()  
  }  

  close = () => {  
    this.popupView && this.popupView.close()  
  }  
}  

export const popup = new NativePopup()

使用方式如下:

import { popup } from './nativePopup.js'  
// 显示  
popup.show({  
  title: '权限申请说明',  
  content: '为了xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'  
})  
// 关闭  
popup.close()

五. 监听权限申请

createRequestPermissionListener

华为应用商店审核时要求:APP在调用终端权限时,应同步告知用户申请该权限的目的,可使用 uni.createRequestPermissionListener(),在 app.vue 里全局监听。

在 Android 平台,可使用该 API 监听应用权限申请确认框的弹出和关闭。不管是哪处的业务代码在申请权限,当弹出和关闭权限申请确认框时均会触发本监听事件。

创建监听对象后,返回 RequestPermissionListener,然后调起 onConfirmonComplete

  • 当权限申请的确认框在手机端弹出时,会触发 onConfirm,回调中会以数组方式提供权限名称列表。
  • 当权限申请的确认框被用户关闭后,会触发 onComplete

所以,通过监听权限申请,在 onConfirm 回调中弹窗,可以实现不改动业务代码,全局处理权限弹窗问题!

以下代码已经声明了大部分默认权限申请说明信息,如有新增或调整,可以进行更改或传入!

import { popup } from './nativePopup.js'  
import permisionUtil from './permission.js'  

let permissionListener = null  

const prefix = 'permisionStatus_'  
const { uniPlatform, platform, osAndroidAPILevel } = uni.getSystemInfoSync()  

const log = (...args) => {  
  console.log(...args)  
}  

// 默认权限申请说明信息,可以按照以下形式进行拓展  
const defaultPermissionExplainMap = {  
  'android.permission.BLUETOOTH_SCAN': {  
    title: '蓝牙扫描权限申请说明',  
    content: '应用需要扫描附近的蓝牙设备,以便进行连接或数据传输。'  
  },  
  'android.permission.BLUETOOTH_CONNECT': {  
    title: '蓝牙连接权限申请说明',  
    content: '应用需要连接蓝牙设备,以便提供音频播放或数据通信功能。'  
  },  
  'android.permission.READ_MEDIA_IMAGE': {  
    title: '读取图片权限申请说明',  
    content: '应用需要访问您的图片库,以便加载和选择照片。'  
  }  
}  

export const createRequestPermissionListener = (permissionExplainMap = {}) => {  
  if (uniPlatform != 'app' || platform != 'android') return  

  if (typeof permissionExplainMap != 'object')  
    throw Error('permissionExplainMap 类型错误')  

  permissionListener =  
    permissionListener || uni.createRequestPermissionListener()  

  permissionListener.onRequest(e => {  
    log('onRequest', JSON.stringify(e))  
  })  

  permissionListener.onConfirm(e => {  
    const [permissionName] = e  

    const status = uni.getStorageSync(prefix + permissionName)  
    log('onConfirm permissionName', permissionName, status)  
    const content =  
      permissionExplainMap[permissionName] ||  
      defaultPermissionExplainMap[permissionName]  
    if (!status && content) popup.show(content)  
  })  

  permissionListener.onComplete(e => {  
    const [permissionName] = e  

    const status = uni.getStorageSync(prefix + permissionName)  
    log('onComplete permissionName', permissionName, status)  
    popup.close()  
  })  
}  

export const stopRequestPermissionListener = () => {  
  permissionListener && permissionListener.stop()  
}  

export { permisionUtil }

六. 用法说明

1. 引入全局监听

App.vue 的生命周期中开始监听,停止监听。

import { createRequestPermissionListener } from '@/uni_modules/permission/index.js'  
export default {  
  onLaunch() {  
    createRequestPermissionListener()  
  },  
  onExit() {  
    stopRequestPermissionListener()  
  }  
}

2. 申请权限

在应用使用权限之前进行检测权限申请,例如,在进行扫码前先申请相机权限:

注意:可以不进行主动申请权限,因为在全局已经做了监听,弹窗会自动弹出!但为了特殊情况(比如在使用原生的权限申请操作,无法监听到),建议在这种情况下提前申请权限!

import { requestAndroidPermission } from '@/uni_modules/permission/index.js'  

async requestPermission() {  
    const status = await requestAndroidPermission('android.permission.CAMERA')  
    if (status != 1) {  
        // 权限被拒绝  
        return  
    }  
}

七. 注意事项

  • 如果权限已经申请并且允许之后,onConfirm不会触发。
  • 如果同时申请多个权限时,onComplete可能会触发多次。
  • 只能监听通过 uniapp 或 plus 提供的权限申请时弹出提示,如果你使用原生的权限申请操作,无法监听到!

八. 总结

本文主要介绍了如何解决华为应用市场审核的问题,主要涉及两个方面:

  1. 隐私政策声明

    • 需要在隐私政策中明确声明应用使用的 SDK 信息
    • 包括 SDK 名称、包名、使用目的、使用的权限、涉及的个人信息等
    • 以 com.bun.miitmdid 为例,需要在隐私政策中添加相关说明
  2. 权限申请优化

    • 在申请敏感权限时,需要同步告知用户申请该权限的目的
    • 提供了完整的权限申请解决方案:
      • 封装了 Android 权限申请方法
      • 实现了原生弹窗组件用于权限说明
      • 通过全局监听权限申请,自动显示权限说明
      • 提供了常用权限的默认说明文案
    • 特别针对华为应用市场做了渠道包判断
  3. 技术实现要点

    • 使用 plus.android.requestPermissions 进行权限申请
    • 使用 plus.nativeObj.view 实现原生弹窗
    • 使用 uni.createRequestPermissionListener 监听权限申请
    • 通过 plus.runtime.channel 判断应用渠道

通过以上优化,可以有效解决华为应用市场的审核问题,提升应用的用户体验和合规性。同时,这些优化措施也可以作为其他应用市场的参考,提高应用的整体质量。

参考文档

华为应用隐私合规问题小学堂

Android 平台各功能模块隐私合规协议

Android 平台权限列表参考

Android 平台权限申请 requestPermissions

Android 平台监听权限申请

Android 平台自定义渠道包

本文对应的源码已发布到插件市场,可直接使用(下载插件并导入HBuilderX):【DCloud插件市场】监听权限申请,解决华为应用商店上架问题

继续阅读 »

一. 前言

不得不说,华为应用商店的审核还是过于严格了,最近提交的新版本应用又被拒绝了!已经提交了4个版本了,再这样下去,我就要崩溃了!

好多人已经对华为的这项审核怨言颇深了!

其实,华为审核严格是一方面,另一方面主要在于 uni-app,由于该应用是使用 uni-app 开发并打包的,所以受限于 uni-app 框架,而它给添加了太多没有用的东西,都集成在框架中,并且删除不掉。

而像本次华为应用商店驳回审核,需要修改的地方已经给罗列的很清楚了,只需要按照他们的说明整改即可,最起码我们能通过自己修改代码就可以完成,不用找官方解决,比较简单。

所以有两个问题需要整改:

  • 隐私政策声明
  • 申请权限时需告知用户使用目的

隐私政策文件的整改很简单,以 com.bun.miitmdid 为例,需要在隐私政策中添加相关说明即可。本篇文章我们直接进行申请权限时的整改,接下来进入正文!

二. 修改权限申请逻辑

前面提到华为应用商店的审核还是过于严格了,其实相比较其他国内应用市场(小米/VIVO/OPPO)来说,区别就在于用户在申请敏感权限时,需同步告知用户申请该权限的目的。对于申请的权限,都必须有明确、合理的使用场景和功能说明,禁止诱导或误导用户授权。

如下图所示:

目前应用内申请权限时是这样的:

接下来我们要进行整改,整改完成后是这样的:

三. 权限申请

1. 使用 plus.android.requestPermissions

统一通过 plus.android.requestPermissions 向系统请求权限,如果权限属于危险权限并且用户没有授权则会弹出系统提示框由用户授权确认。

plus.android.requestPermissions(permissions, successCallback, errorCallback)

2. 参数说明

  • permissions: 申请的权限列表,权限列表参考 Android 官方列表

  • successCallback:  申请权限成功回调函数,参考 AndroidSuccessCallback,返回申请权限的结果,可能被用户允许,回调函数的参数 event 包含以下属性:

    • granted - Array[String]字符串数组,已获取权限列表;
    • deniedPresent - Array[String]字符串数据,已拒绝(临时)的权限列表;
    • deniedAlways - Array[String]字符串数据,永久拒绝的权限列表。
  • errorCallback:  申请权限失败回调函数,参考 AndroidErrorCallback

    • 通常传入参数错误时触发此回调。

注意:Android 系统 6+版本(API 等级 23+),并且必须设置 targetSdkVersion>=23。

如果已经授权或被用户拒绝则返回结果。 授权结果在 successCallback 回调参数中可获取。

为了便于统一申请权限,封装为以下方法,可直接复制使用!

// Android权限查询  
export function requestAndroidPermission(permissionID) {  
  return new Promise((resolve, reject) => {  
    plus.android.requestPermissions(  
      // 理论上支持多个权限同时查询,但实际上本函数封装只处理了一个权限的情况。有需要的可自行扩展封装  
      [permissionID],  
      function (resultObj) {  
        var result = 0  
        for (var i = 0; i < resultObj.granted.length; i++) {  
          var grantedPermission = resultObj.granted[i]  
          console.log('已获取的权限:' + grantedPermission)  
          result = 1  
        }  
        for (var i = 0; i < resultObj.deniedPresent.length; i++) {  
          var deniedPresentPermission = resultObj.deniedPresent[i]  
          console.log('拒绝本次申请的权限:' + deniedPresentPermission)  
          result = 0  
        }  
        for (var i = 0; i < resultObj.deniedAlways.length; i++) {  
          var deniedAlwaysPermission = resultObj.deniedAlways[i]  
          console.log('永久拒绝申请的权限:' + deniedAlwaysPermission)  
          result = -1  
        }  
        uni.setStorageSync('permisionStatus_' + permissionID, result)  

        resolve(result)  
        // 若所需权限被拒绝,则打开APP设置界面,可以在APP设置界面打开相应权限  
        if (result != 1) {  
          // gotoAppPermissionSetting()  
          uni.showModal({  
            content: '权限已经被拒绝,请前往APP设置界面打开相应权限'  
          })  
        }  
      },  
      function (error) {  
        console.log('申请权限错误:' + error.code + ' = ' + error.message)  
        resolve({  
          code: error.code,  
          message: error.message  
        })  
      }  
    )  
  })  
}

此文件来源于 https://ext.dcloud.net.cn/plugin?id=594 部分片段

使用方式:

requestAndroidPermission('android.permission.WRITE_EXTERNAL_STORAGE').then(  
  result => {  
    // result 表示:1已获取,0已拒绝,-1永久拒绝。可根据返回码定向处理  
  }  
)

四. 原生弹窗

接下来我们应该构造一个弹窗类 NativePopup 用于在应用内申请权限时弹窗告知用户,说明申请权限的使用目的。

在这里,App 端使用 plus.nativeObj.view 绘制原生内容,参考:uni-app 中使用 5+界面控件plus.nativeObj.view 规范

代码如下,可直接复制使用!

export class NativePopup {  
  constructor(options = {}) {  
    this.sysInfo = uni.getSystemInfoSync()  

    const {  
      bgColor = '#fff',  
      titleColor = '#000',  
      contentColor = '#272727'  
    } = options  

    this.bgColor = bgColor  
    this.titleColor = titleColor  
    this.contentColor = contentColor  
  }  

  createPopup = () => {  
    const { statusBarHeight, screenWidth } = this.sysInfo  

    const popupView = new plus.nativeObj.View('popupView', {  
      top: 0,  
      left: 0,  
      width: screenWidth,  
      height: 110 + statusBarHeight + 'px'  
      // backgroundColor: 'blue' // debug  
    })  

    popupView.addEventListener('click', this.close)  

    const bgPadding = 15  

    popupView.drawRect(  
      {  
        color: 'rgba(0, 0, 0, 0.1)',  
        radius: '10px'  
      },  
      {  
        top: statusBarHeight + 7 + 'px',  
        left: bgPadding - 2 + 'px',  
        width: screenWidth - bgPadding * 2 + 4 + 'px',  
        height: '100px'  
      }  
    )  

    popupView.drawRect(  
      {  
        color: this.bgColor,  
        radius: '10px'  
      },  
      {  
        top: statusBarHeight + 5 + 'px',  
        left: bgPadding + 'px',  
        width: screenWidth - bgPadding * 2 + 'px',  
        height: '100px'  
      }  
    )  

    const padding = 10  

    popupView.drawText(  
      this.title,  
      {  
        top: statusBarHeight + 10 + 'px',  
        left: padding + bgPadding + 'px',  
        height: '30px',  
        width: screenWidth - bgPadding * 2 - padding * 2 + 'px'  
      },  
      {  
        size: '16px',  
        weight: 'bold',  
        align: 'left',  
        color: this.titleColor  
      },  
      {  
        onClick: function (e) {  
          console.log(e)  
        }  
      }  
    )  

    popupView.drawText(  
      this.content,  
      {  
        top: statusBarHeight + 40 + 'px',  
        height: '60px',  
        left: padding + bgPadding + 'px',  
        width: screenWidth - bgPadding * 2 - padding * 2 + 'px'  
      },  
      {  
        size: '14px',  
        align: 'left',  
        color: this.contentColor,  
        whiteSpace: 'normal'  
      }  
    )  

    this.popupView = popupView  

    return popupView  
  }  

  show = (options = {}) => {  
    this.close()  

    const { title = '权限申请说明', content = '' } = options  
    this.title = title  
    this.content = content  

    this.createPopup()  

    this.popupView.show()  
  }  

  close = () => {  
    this.popupView && this.popupView.close()  
  }  
}  

export const popup = new NativePopup()

使用方式如下:

import { popup } from './nativePopup.js'  
// 显示  
popup.show({  
  title: '权限申请说明',  
  content: '为了xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'  
})  
// 关闭  
popup.close()

五. 监听权限申请

createRequestPermissionListener

华为应用商店审核时要求:APP在调用终端权限时,应同步告知用户申请该权限的目的,可使用 uni.createRequestPermissionListener(),在 app.vue 里全局监听。

在 Android 平台,可使用该 API 监听应用权限申请确认框的弹出和关闭。不管是哪处的业务代码在申请权限,当弹出和关闭权限申请确认框时均会触发本监听事件。

创建监听对象后,返回 RequestPermissionListener,然后调起 onConfirmonComplete

  • 当权限申请的确认框在手机端弹出时,会触发 onConfirm,回调中会以数组方式提供权限名称列表。
  • 当权限申请的确认框被用户关闭后,会触发 onComplete

所以,通过监听权限申请,在 onConfirm 回调中弹窗,可以实现不改动业务代码,全局处理权限弹窗问题!

以下代码已经声明了大部分默认权限申请说明信息,如有新增或调整,可以进行更改或传入!

import { popup } from './nativePopup.js'  
import permisionUtil from './permission.js'  

let permissionListener = null  

const prefix = 'permisionStatus_'  
const { uniPlatform, platform, osAndroidAPILevel } = uni.getSystemInfoSync()  

const log = (...args) => {  
  console.log(...args)  
}  

// 默认权限申请说明信息,可以按照以下形式进行拓展  
const defaultPermissionExplainMap = {  
  'android.permission.BLUETOOTH_SCAN': {  
    title: '蓝牙扫描权限申请说明',  
    content: '应用需要扫描附近的蓝牙设备,以便进行连接或数据传输。'  
  },  
  'android.permission.BLUETOOTH_CONNECT': {  
    title: '蓝牙连接权限申请说明',  
    content: '应用需要连接蓝牙设备,以便提供音频播放或数据通信功能。'  
  },  
  'android.permission.READ_MEDIA_IMAGE': {  
    title: '读取图片权限申请说明',  
    content: '应用需要访问您的图片库,以便加载和选择照片。'  
  }  
}  

export const createRequestPermissionListener = (permissionExplainMap = {}) => {  
  if (uniPlatform != 'app' || platform != 'android') return  

  if (typeof permissionExplainMap != 'object')  
    throw Error('permissionExplainMap 类型错误')  

  permissionListener =  
    permissionListener || uni.createRequestPermissionListener()  

  permissionListener.onRequest(e => {  
    log('onRequest', JSON.stringify(e))  
  })  

  permissionListener.onConfirm(e => {  
    const [permissionName] = e  

    const status = uni.getStorageSync(prefix + permissionName)  
    log('onConfirm permissionName', permissionName, status)  
    const content =  
      permissionExplainMap[permissionName] ||  
      defaultPermissionExplainMap[permissionName]  
    if (!status && content) popup.show(content)  
  })  

  permissionListener.onComplete(e => {  
    const [permissionName] = e  

    const status = uni.getStorageSync(prefix + permissionName)  
    log('onComplete permissionName', permissionName, status)  
    popup.close()  
  })  
}  

export const stopRequestPermissionListener = () => {  
  permissionListener && permissionListener.stop()  
}  

export { permisionUtil }

六. 用法说明

1. 引入全局监听

App.vue 的生命周期中开始监听,停止监听。

import { createRequestPermissionListener } from '@/uni_modules/permission/index.js'  
export default {  
  onLaunch() {  
    createRequestPermissionListener()  
  },  
  onExit() {  
    stopRequestPermissionListener()  
  }  
}

2. 申请权限

在应用使用权限之前进行检测权限申请,例如,在进行扫码前先申请相机权限:

注意:可以不进行主动申请权限,因为在全局已经做了监听,弹窗会自动弹出!但为了特殊情况(比如在使用原生的权限申请操作,无法监听到),建议在这种情况下提前申请权限!

import { requestAndroidPermission } from '@/uni_modules/permission/index.js'  

async requestPermission() {  
    const status = await requestAndroidPermission('android.permission.CAMERA')  
    if (status != 1) {  
        // 权限被拒绝  
        return  
    }  
}

七. 注意事项

  • 如果权限已经申请并且允许之后,onConfirm不会触发。
  • 如果同时申请多个权限时,onComplete可能会触发多次。
  • 只能监听通过 uniapp 或 plus 提供的权限申请时弹出提示,如果你使用原生的权限申请操作,无法监听到!

八. 总结

本文主要介绍了如何解决华为应用市场审核的问题,主要涉及两个方面:

  1. 隐私政策声明

    • 需要在隐私政策中明确声明应用使用的 SDK 信息
    • 包括 SDK 名称、包名、使用目的、使用的权限、涉及的个人信息等
    • 以 com.bun.miitmdid 为例,需要在隐私政策中添加相关说明
  2. 权限申请优化

    • 在申请敏感权限时,需要同步告知用户申请该权限的目的
    • 提供了完整的权限申请解决方案:
      • 封装了 Android 权限申请方法
      • 实现了原生弹窗组件用于权限说明
      • 通过全局监听权限申请,自动显示权限说明
      • 提供了常用权限的默认说明文案
    • 特别针对华为应用市场做了渠道包判断
  3. 技术实现要点

    • 使用 plus.android.requestPermissions 进行权限申请
    • 使用 plus.nativeObj.view 实现原生弹窗
    • 使用 uni.createRequestPermissionListener 监听权限申请
    • 通过 plus.runtime.channel 判断应用渠道

通过以上优化,可以有效解决华为应用市场的审核问题,提升应用的用户体验和合规性。同时,这些优化措施也可以作为其他应用市场的参考,提高应用的整体质量。

参考文档

华为应用隐私合规问题小学堂

Android 平台各功能模块隐私合规协议

Android 平台权限列表参考

Android 平台权限申请 requestPermissions

Android 平台监听权限申请

Android 平台自定义渠道包

本文对应的源码已发布到插件市场,可直接使用(下载插件并导入HBuilderX):【DCloud插件市场】监听权限申请,解决华为应用商店上架问题

收起阅读 »

uniapp+vue3 setup跨三端酒店预订小程序模板【h5+小程序+app端】

vite vue3 uni-app uniapp

uniapp-vue3-hotel:一款全新自研的uni-app+vue3 setup+pinia2+uv-ui搭建跨端仿携程/同程旅游app酒店预约系统模板。提供了首页、酒店预订搜索、列表/详情、订单、聊天客服消息、我的等页面模块。支持编译到H5+小程序+APP端

使用技术

  • 开发工具:HbuilderX 4.84
  • 技术框架:uni-app+vite5+vue3
  • 状态管理:pinia2
  • UI组件库:uni-ui+uv-ui(uniapp+vue3组件库)
  • 弹框组件:uv3-popup(基于uniapp+vue3多端弹窗组件)
  • 自定义组件:uv3-navbar导航条+uv3-tabbar菜单栏
  • 缓存技术:pinia-plugin-unistorage
  • 编译支持:web+小程序+app端

项目框架结构

使用uniapp+vite5搭建项目,vue3 setup语法开发。

目前uni-vue3-hotel酒店预订项目已经发布到我的原创作品小铺。

uniapp+vue3+pinia2+uvui跨多端酒店预订app系统

想要了解更多项目的详细介绍,可以去看看下面这篇文章。

最新版uni-app+vue3+uv-ui跨端仿携程酒店预订模板【H5+小程序+App端】

热文推荐

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

继续阅读 »

uniapp-vue3-hotel:一款全新自研的uni-app+vue3 setup+pinia2+uv-ui搭建跨端仿携程/同程旅游app酒店预约系统模板。提供了首页、酒店预订搜索、列表/详情、订单、聊天客服消息、我的等页面模块。支持编译到H5+小程序+APP端

使用技术

  • 开发工具:HbuilderX 4.84
  • 技术框架:uni-app+vite5+vue3
  • 状态管理:pinia2
  • UI组件库:uni-ui+uv-ui(uniapp+vue3组件库)
  • 弹框组件:uv3-popup(基于uniapp+vue3多端弹窗组件)
  • 自定义组件:uv3-navbar导航条+uv3-tabbar菜单栏
  • 缓存技术:pinia-plugin-unistorage
  • 编译支持:web+小程序+app端

项目框架结构

使用uniapp+vite5搭建项目,vue3 setup语法开发。

目前uni-vue3-hotel酒店预订项目已经发布到我的原创作品小铺。

uniapp+vue3+pinia2+uvui跨多端酒店预订app系统

想要了解更多项目的详细介绍,可以去看看下面这篇文章。

最新版uni-app+vue3+uv-ui跨端仿携程酒店预订模板【H5+小程序+App端】

热文推荐

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

收起阅读 »

【弧形导航栏】中间凸起按钮和消息未读角标,支持鸿蒙

鸿蒙征文

Tabbar 组件使用说明

概述

自定义底部导航栏组件,支持中间凸起按钮和消息未读角标功能。

插件地址:https://ext.dcloud.net.cn/plugin?id=25774

功能特性

  • ✅ 5 个 tab 页面切换(健康、消息、守护、家庭、我的)
  • ✅ 中间守护 tab 凸起设计
  • ✅ 支持消息未读角标(红色圆点+白色数字)
  • ✅ 支持本地切换模式和路由切换模式
  • ✅ 平滑切换动画

Props

useLocalSwitch

  • 类型: Boolean
  • 默认值: false
  • 说明: 是否使用本地切换模式。如果为 true,切换 tab 时会发出 tab-change 事件而不是进行路由跳转

badges

  • 类型: Object
  • 默认值: {}
  • 说明: 未读消息角标配置,key 为页面路径,value 为未读数
  • 示例:
{  
  '/pages/message/message': 5,  
  '/pages/health/health': 2,  
  '/pages/family/family': 10,  
  '/pages/my/my': 99  
}

Events

tab-change

  • 参数: (pagePath: string) - 切换到的页面路径
  • 触发时机: 当 useLocalSwitchtrue 时,点击 tab 触发
  • 说明: 用于父组件监听 tab 切换事件

基本使用

1. 不带角标(默认模式)

<template>  
  <view>  
    <Tabbar />  
  </view>  
</template>  

<script setup>  
import Tabbar from "@/components/tabbar/tabbar.vue";  
</script>

2. 带消息未读角标

<template>  
  <view>  
    <Tabbar :badges="badgeData" />  
  </view>  
</template>  

<script setup>  
import { ref } from "vue";  
import Tabbar from "@/components/tabbar/tabbar.vue";  

const badgeData = ref({  
  "/pages/message/message": 5, // 消息页显示 5  
  "/pages/health/health": 2, // 健康页显示 2  
  "/pages/family/family": 120, // 家庭页显示 99+ (超过99)  
});  
</script>

3. 本地切换模式(用于自定义页面切换)

<template>  
  <view>  
    <Tabbar  
      :use-local-switch="true"  
      :badges="badgeData"  
      @tab-change="handleTabChange"  
    />  
  </view>  
</template>  

<script setup>  
import { ref } from "vue";  
import Tabbar from "@/components/tabbar/tabbar.vue";  

const badgeData = ref({  
  "/pages/message/message": 3,  
});  

const handleTabChange = (pagePath) => {  
  console.log("切换到页面:", pagePath);  
  // 在这里处理自定义的页面切换逻辑  
};  
</script>

4. 动态更新角标数量

<template>  
  <view>  
    <Tabbar :badges="badgeData" />  
  </view>  
</template>  

<script setup>  
import { ref, onMounted } from "vue";  
import Tabbar from "@/components/tabbar/tabbar.vue";  

const badgeData = ref({  
  "/pages/message/message": 0,  
});  

// 模拟接收新消息  
onMounted(() => {  
  // 3秒后更新消息数  
  setTimeout(() => {  
    badgeData.value["/pages/message/message"] = 5;  
  }, 3000);  
});  
</script>

5. 结合 Pinia 状态管理

<template>  
  <view>  
    <Tabbar :badges="badgeData" />  
  </view>  
</template>  

<script setup>  
import { computed } from "vue";  
import { useMessageStore } from "@/stores/message";  
import Tabbar from "@/components/tabbar/tabbar.vue";  

const messageStore = useMessageStore();  

// 从store中获取未读消息数  
const badgeData = computed(() => ({  
  "/pages/message/message": messageStore.unreadCount,  
  "/pages/health/health": messageStore.healthNoticeCount,  
  "/pages/family/family": messageStore.familyNoticeCount,  
}));  
</script>

角标显示规则

  1. 数字范围:

    • 0: 不显示角标
    • 1-99: 显示实际数字
    • >99: 显示 "99+"
  2. 样式规范:

    • 背景色: #FF3B30 (红色)
    • 文字色: #FFFFFF (白色)
    • 位置: 图标右上角
    • 形状: 圆形(固定尺寸)
    • 尺寸: 16px x 16px(固定宽高)
  3. 显示位置:

    • 左侧 tab(健康、消息): 支持角标 ✅
    • 中间 tab(守护): 不支持角标 ❌
    • 右侧 tab(家庭、我的): 支持角标 ✅

注意事项

  1. 中间凸起的守护 tab 不支持角标显示
  2. 角标数据是响应式的,可以实时更新
  3. 角标仅在数字大于 0 时显示
  4. 建议使用 Pinia 进行全局的未读消息管理
  5. 在 HarmonyOS 系统中测试确保角标显示正常

插件地址:https://ext.dcloud.net.cn/plugin?id=25774

欢迎交流讨论~

继续阅读 »

Tabbar 组件使用说明

概述

自定义底部导航栏组件,支持中间凸起按钮和消息未读角标功能。

插件地址:https://ext.dcloud.net.cn/plugin?id=25774

功能特性

  • ✅ 5 个 tab 页面切换(健康、消息、守护、家庭、我的)
  • ✅ 中间守护 tab 凸起设计
  • ✅ 支持消息未读角标(红色圆点+白色数字)
  • ✅ 支持本地切换模式和路由切换模式
  • ✅ 平滑切换动画

Props

useLocalSwitch

  • 类型: Boolean
  • 默认值: false
  • 说明: 是否使用本地切换模式。如果为 true,切换 tab 时会发出 tab-change 事件而不是进行路由跳转

badges

  • 类型: Object
  • 默认值: {}
  • 说明: 未读消息角标配置,key 为页面路径,value 为未读数
  • 示例:
{  
  '/pages/message/message': 5,  
  '/pages/health/health': 2,  
  '/pages/family/family': 10,  
  '/pages/my/my': 99  
}

Events

tab-change

  • 参数: (pagePath: string) - 切换到的页面路径
  • 触发时机: 当 useLocalSwitchtrue 时,点击 tab 触发
  • 说明: 用于父组件监听 tab 切换事件

基本使用

1. 不带角标(默认模式)

<template>  
  <view>  
    <Tabbar />  
  </view>  
</template>  

<script setup>  
import Tabbar from "@/components/tabbar/tabbar.vue";  
</script>

2. 带消息未读角标

<template>  
  <view>  
    <Tabbar :badges="badgeData" />  
  </view>  
</template>  

<script setup>  
import { ref } from "vue";  
import Tabbar from "@/components/tabbar/tabbar.vue";  

const badgeData = ref({  
  "/pages/message/message": 5, // 消息页显示 5  
  "/pages/health/health": 2, // 健康页显示 2  
  "/pages/family/family": 120, // 家庭页显示 99+ (超过99)  
});  
</script>

3. 本地切换模式(用于自定义页面切换)

<template>  
  <view>  
    <Tabbar  
      :use-local-switch="true"  
      :badges="badgeData"  
      @tab-change="handleTabChange"  
    />  
  </view>  
</template>  

<script setup>  
import { ref } from "vue";  
import Tabbar from "@/components/tabbar/tabbar.vue";  

const badgeData = ref({  
  "/pages/message/message": 3,  
});  

const handleTabChange = (pagePath) => {  
  console.log("切换到页面:", pagePath);  
  // 在这里处理自定义的页面切换逻辑  
};  
</script>

4. 动态更新角标数量

<template>  
  <view>  
    <Tabbar :badges="badgeData" />  
  </view>  
</template>  

<script setup>  
import { ref, onMounted } from "vue";  
import Tabbar from "@/components/tabbar/tabbar.vue";  

const badgeData = ref({  
  "/pages/message/message": 0,  
});  

// 模拟接收新消息  
onMounted(() => {  
  // 3秒后更新消息数  
  setTimeout(() => {  
    badgeData.value["/pages/message/message"] = 5;  
  }, 3000);  
});  
</script>

5. 结合 Pinia 状态管理

<template>  
  <view>  
    <Tabbar :badges="badgeData" />  
  </view>  
</template>  

<script setup>  
import { computed } from "vue";  
import { useMessageStore } from "@/stores/message";  
import Tabbar from "@/components/tabbar/tabbar.vue";  

const messageStore = useMessageStore();  

// 从store中获取未读消息数  
const badgeData = computed(() => ({  
  "/pages/message/message": messageStore.unreadCount,  
  "/pages/health/health": messageStore.healthNoticeCount,  
  "/pages/family/family": messageStore.familyNoticeCount,  
}));  
</script>

角标显示规则

  1. 数字范围:

    • 0: 不显示角标
    • 1-99: 显示实际数字
    • >99: 显示 "99+"
  2. 样式规范:

    • 背景色: #FF3B30 (红色)
    • 文字色: #FFFFFF (白色)
    • 位置: 图标右上角
    • 形状: 圆形(固定尺寸)
    • 尺寸: 16px x 16px(固定宽高)
  3. 显示位置:

    • 左侧 tab(健康、消息): 支持角标 ✅
    • 中间 tab(守护): 不支持角标 ❌
    • 右侧 tab(家庭、我的): 支持角标 ✅

注意事项

  1. 中间凸起的守护 tab 不支持角标显示
  2. 角标数据是响应式的,可以实时更新
  3. 角标仅在数字大于 0 时显示
  4. 建议使用 Pinia 进行全局的未读消息管理
  5. 在 HarmonyOS 系统中测试确保角标显示正常

插件地址:https://ext.dcloud.net.cn/plugin?id=25774

欢迎交流讨论~

收起阅读 »

腾讯云上传图片

之前使用vue 写过后台管理系统的腾讯云直传,使用的是cos-js-sdk-v5库,使用了三方库后就非常的简单代码也不多。
这次要在uniapp项目上写腾讯云直传,于是又把以前的代码拿来用了起来,在浏览器上调试好后,真机运行到手机上发现出问题了!
在uniapp中使用"cos-js-sdk-v5"库时,提示
warning: cos-js-sdk-v5 不支持 nodejs 环境使用,请改用 cos-nodejs-sdk-v5
按照提示安装 "cos-nodejs-sdk-v5",运行APP后直接报错APP无法正常启动,如下图所示:

卡住了不知道该怎么办?

然后就找到了腾讯云文档中心的这篇文章 uni-app 直传实践
思路是:使用uni.chooseImage选择图片文件,然后把文件类型传递给后端,后端返回腾讯云的相关信息,前端使用uni.uploadFile上传文件

继续阅读 »

之前使用vue 写过后台管理系统的腾讯云直传,使用的是cos-js-sdk-v5库,使用了三方库后就非常的简单代码也不多。
这次要在uniapp项目上写腾讯云直传,于是又把以前的代码拿来用了起来,在浏览器上调试好后,真机运行到手机上发现出问题了!
在uniapp中使用"cos-js-sdk-v5"库时,提示
warning: cos-js-sdk-v5 不支持 nodejs 环境使用,请改用 cos-nodejs-sdk-v5
按照提示安装 "cos-nodejs-sdk-v5",运行APP后直接报错APP无法正常启动,如下图所示:

卡住了不知道该怎么办?

然后就找到了腾讯云文档中心的这篇文章 uni-app 直传实践
思路是:使用uni.chooseImage选择图片文件,然后把文件类型传递给后端,后端返回腾讯云的相关信息,前端使用uni.uploadFile上传文件

收起阅读 »

低成本入局鸿蒙生态!Uniapp 适配鸿蒙实战分享,一次编码跑通多端

鸿蒙征文

随着鸿蒙 OS(HarmonyOS)在手机、平板、智能穿戴等设备的全面普及,其分布式架构和全场景互联能力已成为开发者不可忽视的新赛道。而 Uniapp 作为 “一次开发,多端部署” 的标杆框架,早已实现对鸿蒙的成熟适配,让开发者无需从零学习鸿蒙原生开发(ArkTS/ArkUI),就能将现有 Uniapp 项目快速迁移至鸿蒙生态。本文结合实际项目适配经验,从环境搭建、核心适配、问题排查到优化升级,全程拆解 Uniapp 适配鸿蒙的关键步骤,助力开发者高效落地。

一、为什么选择 Uniapp 适配鸿蒙?

在决定适配前,先明确 Uniapp 适配鸿蒙的核心优势,避免重复造轮子:
技术栈复用:无需学习鸿蒙原生技术,Vue / 小程序开发者可直接上手,核心业务逻辑零修改或少量修改;
多端兼容性:适配鸿蒙后,项目仍可正常运行在 iOS、Android、微信小程序等平台,代码资产最大化利用;
官方深度支持:HBuilderX 持续迭代鸿蒙适配能力,内置编译、调试工具,降低适配门槛;
生态协同:Uniapp 可调用鸿蒙分布式能力(如设备互联、数据共享),让跨端应用具备鸿蒙特色优势。
简单说:Uniapp 是低成本切入鸿蒙生态的最优解之一,尤其适合已有 Uniapp 项目的团队快速拓展鸿蒙渠道。

二、前置准备:环境搭建与基础配置

适配前需完成环境搭建和项目基础配置,这是后续适配的前提,步骤如下:

1. 开发环境搭建

HBuilderX:安装 3.8.0 及以上版本(需支持鸿蒙编译),直接在官网下载即可;
DevEco Studio:安装 4.0 及以上版本(鸿蒙开发者工具),用于模拟器调试和应用打包,需注册鸿蒙开发者账号并完成实名认证;
鸿蒙模拟器配置:在 DevEco Studio 中创建模拟器(推荐 API Version 9+,手机 / 平板型号均可),确保模拟器与 HBuilderX 处于同一网络(如同一 Wi-Fi),避免调试连接失败。

2. 项目基础配置

新建 / 改造项目:若从零开发,选择 Uniapp “默认模板”(优先 Vue 3+Vite 架构,鸿蒙端对 Vue 3 兼容性更佳);若改造现有项目,确保项目无严重语法错误,且依赖库为最新版本;
manifest.json 配置:
打开项目根目录的manifest.json,在 “App 模块配置” 中勾选 “HarmonyOS”;
填写鸿蒙应用基础信息:应用名称、包名(需与鸿蒙开发者平台注册的包名一致)、版本号、图标等;
权限配置:在 “HarmonyOS 权限配置” 中声明所需权限(如网络、存储、相机等),鸿蒙对权限管控较严格,未声明的权限会直接导致功能失效。

pages.json 配置:添加鸿蒙端专属配置,指定使用 ArkUI 渲染(默认开启),示例:  
json  
{  
  "globalStyle": {  
    "harmonyos": {  
      "useArkUI": true, // 启用ArkUI渲染(必填)  
      "windowBackgroundColor": "#ffffff" // 鸿蒙端窗口背景色  
    }  
  }  
}

3. 依赖兼容性检查

插件兼容:移除依赖 Android/iOS 原生 SDK 的 Uniapp 插件(如某些支付、地图插件),替换为跨端兼容插件(如uni-ui、uView Plus、uni-pay等);
第三方库兼容:优先使用纯 JS/TS 库(如 axios、lodash),避免使用依赖原生模块的库(如 node-sqlite3);若必须使用,需确认库已支持鸿蒙环境。

三、核心适配:组件、API 与布局的差异化处理

Uniapp 的组件和 API 在鸿蒙端大多兼容,但因鸿蒙系统特性,部分场景需针对性适配,核心集中在以下 3 个维度:

1. 组件适配:替换不兼容组件,对齐行为差异

Uniapp 内置组件在鸿蒙端的兼容性可达 90% 以上,但部分组件存在行为差异,需重点关注:
优先使用 Uniapp 跨端组件:如<view>、<text>、<image>、<button>等,避免直接使用鸿蒙原生 ArkUI 组件(如<Text>、<Image>),否则会破坏多端兼容性;
表单组件适配:

<input>组件:type="number"在鸿蒙端需补充input-mode="numeric",确保弹出数字软键盘;placeholder-style需用内联样式,避免样式失效;  
<picker>组件:必须指定range-key(即使是简单数组),否则数据无法正常渲染,示例:  
vue  
<picker :range="array" range-key="name" @change="onPickerChange">  
  <view>选择内容</view>  
</picker>  
滚动组件适配:<scroll-view>横向滚动需显式设置scroll-x="true",且子组件需设置white-space: nowrap(避免换行),同时确保子组件宽度不超出容器;  
不兼容组件替换:  
<web-view>:鸿蒙端暂不支持,可改用uni.navigateTo跳转 H5 页面,或通过 Uniapp 插件集成鸿蒙原生WebComponent;  
<video>:鸿蒙端不支持controls属性自动显示控制栏,需自定义控制按钮(播放 / 暂停、进度条等)。

2. API 适配:处理权限、网络与环境判断

Uniapp 的uni.xxxAPI 在鸿蒙端基本兼容,但部分与系统相关的 API 需特殊处理:
权限动态申请:鸿蒙的权限体系与 Android/iOS 不同,需先在manifest.json声明权限,再通过uni.requestPermissions动态申请,示例(申请存储权限):

// 鸿蒙存储权限标识:ohos.permission.WRITE_USER_STORAGE  
uni.requestPermissions({  
  scope: 'ohos.permission.WRITE_USER_STORAGE',  
  success: (res) => {  
    if (res.granted) {  
      // 权限申请成功,执行文件读写操作  
      uni.saveFile({...});  
    } else {  
      uni.showToast({ title: '请开启存储权限以正常使用功能' });  
    }  
  }  
});

网络请求适配:
鸿蒙端默认禁止http协议请求,需在manifest.json中添加配置开启:

"harmonyos": {  
  "network": {  
    "cleartextTraffic": true // 允许http请求(开发环境可用,生产环境建议改用https)  
  }  
}

uni.request的timeout参数在鸿蒙端最小值为 1000ms,设置过小会导致请求失败;
环境判断与差异化逻辑:通过uni.getSystemInfo判断当前是否为鸿蒙环境,执行特殊逻辑:

uni.getSystemInfo({  
  success: (res) => {  
    // res.system 格式:"HarmonyOS 4.0.0"  
    this.isHarmonyOS = res.system.includes('HarmonyOS');  
    if (this.isHarmonyOS) {  
      // 鸿蒙端特殊处理(如替换组件、调整样式)  
      this.adaptHarmonyStyle();  
    }  
  }  
});

路由与页面生命周期:
uni.navigateBack在鸿蒙端需指定delta参数(如delta: 1),否则可能无法正常返回上一页;
鸿蒙端页面生命周期与小程序一致(onLoad/onShow/onUnload),但onReady触发时机略晚,避免在onReady中执行依赖 DOM 的操作(可延迟 100ms)。

3. 布局适配:适配鸿蒙多设备尺寸与特性

鸿蒙支持手机、平板、折叠屏等多设备,布局适配需兼顾 “自适应” 与 “设备特性”:
优先使用 rpx 单位:Uniapp 的 rpx 单位在鸿蒙端同样生效(1rpx = 屏幕宽度 / 750),无需额外适配尺寸,确保布局在不同屏幕尺寸的鸿蒙设备上自适应;
避免固定布局:禁止使用px固定宽度 / 高度,优先使用flex布局 +flex-grow/flex-shrink,确保组件随屏幕伸缩;

平板 / 折叠屏适配:通过mediaQuery实现不同屏幕尺寸的布局切换,示例:  
json  
// pages.json中配置  
{  
  "pages": [  
    {  
      "path": "pages/list/list",  
      "style": {  
        "mediaQuery": {  
          "min-width": "800px": { // 平板屏幕(宽度≥800px)  
            "layout": "grid",  
            "grid-template-columns": "1fr 1fr", // 双列布局  
            "grid-gap": "20rpx"  
          }  
        }  
      }  
    }  
  ]  
}

样式兼容性:
鸿蒙端不支持scoped样式中的::v-deep,Vue 3 项目需改用::deep,Vue 2 项目改用/deep/;
避免使用position: fixed(鸿蒙端可能出现层级异常),优先使用sticky或absolute+ 父容器定位;
<text>组件的line-height默认不继承,需显式设置(如line-height: 32rpx)。

四、调试与打包:避坑指南

适配过程中,调试和打包是容易踩坑的环节,分享关键注意事项:

1. 模拟器调试技巧

连接失败解决:
确认 HBuilderX 与 DevEco Studio 模拟器处于同一网络;
重启鸿蒙模拟器(在 DevEco Studio 中关闭后重新启动);
检查manifest.json的包名与鸿蒙开发者平台注册的包名一致;
日志查看:在 HBuilderX 的 “运行日志” 中查看鸿蒙端报错信息,若日志不完整,可在 DevEco Studio 中打开 “Logcat” 查看详细原生日志。

2. 打包发布注意事项

证书配置:需在鸿蒙开发者平台申请 “应用发布证书” 和 “Profile 文件”,并在 HBuilderX 的manifest.json中配置(“HarmonyOS 打包配置”);
版本号规范:鸿蒙应用的版本号(versionName)需遵循 “主版本。次版本。修订号” 格式(如 1.0.0),且需高于已发布版本;
安装失败排查:
检查证书是否过期或与包名不匹配;
确认设备系统版本≥API Version 9;
检查权限配置是否完整(缺少必要权限会导致安装失败)。

五、优化升级:让鸿蒙应用体验更原生

适配完成后,可通过以下优化提升应用的鸿蒙原生体验:

1. 接入鸿蒙分布式能力

Uniapp 支持通过插件调用鸿蒙分布式 API,让应用具备跨设备协同能力:
集成 “鸿蒙分布式路由” 插件,实现手机、平板、手表等设备间的页面跳转;
使用 “分布式数据管理” 插件,实现多设备间的数据同步(如购物车、收藏夹)。

2. 适配鸿蒙深色模式

鸿蒙系统支持深色模式,适配后可提升用户体验:

在manifest.json中开启深色模式支持:  
json  
"harmonyos": {  
  "darkMode": "auto", // 跟随系统主题  
  "theme": {  
    "light": "#ffffff", // 浅色模式背景色  
    "dark": "#1a1a1a"  // 深色模式背景色  
  }  
}

在样式中通过媒体查询适配深色模式:

css  
/* 全局样式或页面样式 */  
@media (prefers-color-scheme: dark) {  
  .container {  
    background-color: #1a1a1a;  
    color: #ffffff;  
  }  
  .btn {  
    background-color: #333333;  
    border-color: #666666;  
  }  
}

3. 性能优化

列表优化:长列表使用v-for+key,避免频繁修改 DOM;开启列表懒加载(uni-scroll-view的lower-threshold),减少一次性渲染数据量;
资源优化:压缩图片(使用 webp 格式)、懒加载非首屏图片;减少首屏网络请求,优先使用本地缓存数据;
动画优化:避免使用复杂 CSS 动画,优先使用uni.createAnimation;动画时长控制在 300ms 以内,提升流畅度。

六、总结

Uniapp 适配鸿蒙的核心逻辑是 “复用现有技术栈,补齐系统差异点”,整个过程无需从零开发,适配成本低、效率高。对于已有 Uniapp 项目的团队,仅需完成 “环境搭建→配置调整→组件 / API 差异化处理→调试打包” 四个步骤,即可快速切入鸿蒙生态;对于新项目,使用 Uniapp 开发可一次性覆盖 iOS、Android、小程序、鸿蒙等多端,最大化代码价值。
随着鸿蒙生态的持续壮大,Uniapp 对鸿蒙的适配能力也在不断升级(如支持更多鸿蒙原生 API、优化性能体验)。现在正是布局鸿蒙生态的黄金时期,借助 Uniapp 的跨端优势,开发者可快速抢占鸿蒙设备用户市场,为应用的多端发展增添新的增长点。

继续阅读 »

随着鸿蒙 OS(HarmonyOS)在手机、平板、智能穿戴等设备的全面普及,其分布式架构和全场景互联能力已成为开发者不可忽视的新赛道。而 Uniapp 作为 “一次开发,多端部署” 的标杆框架,早已实现对鸿蒙的成熟适配,让开发者无需从零学习鸿蒙原生开发(ArkTS/ArkUI),就能将现有 Uniapp 项目快速迁移至鸿蒙生态。本文结合实际项目适配经验,从环境搭建、核心适配、问题排查到优化升级,全程拆解 Uniapp 适配鸿蒙的关键步骤,助力开发者高效落地。

一、为什么选择 Uniapp 适配鸿蒙?

在决定适配前,先明确 Uniapp 适配鸿蒙的核心优势,避免重复造轮子:
技术栈复用:无需学习鸿蒙原生技术,Vue / 小程序开发者可直接上手,核心业务逻辑零修改或少量修改;
多端兼容性:适配鸿蒙后,项目仍可正常运行在 iOS、Android、微信小程序等平台,代码资产最大化利用;
官方深度支持:HBuilderX 持续迭代鸿蒙适配能力,内置编译、调试工具,降低适配门槛;
生态协同:Uniapp 可调用鸿蒙分布式能力(如设备互联、数据共享),让跨端应用具备鸿蒙特色优势。
简单说:Uniapp 是低成本切入鸿蒙生态的最优解之一,尤其适合已有 Uniapp 项目的团队快速拓展鸿蒙渠道。

二、前置准备:环境搭建与基础配置

适配前需完成环境搭建和项目基础配置,这是后续适配的前提,步骤如下:

1. 开发环境搭建

HBuilderX:安装 3.8.0 及以上版本(需支持鸿蒙编译),直接在官网下载即可;
DevEco Studio:安装 4.0 及以上版本(鸿蒙开发者工具),用于模拟器调试和应用打包,需注册鸿蒙开发者账号并完成实名认证;
鸿蒙模拟器配置:在 DevEco Studio 中创建模拟器(推荐 API Version 9+,手机 / 平板型号均可),确保模拟器与 HBuilderX 处于同一网络(如同一 Wi-Fi),避免调试连接失败。

2. 项目基础配置

新建 / 改造项目:若从零开发,选择 Uniapp “默认模板”(优先 Vue 3+Vite 架构,鸿蒙端对 Vue 3 兼容性更佳);若改造现有项目,确保项目无严重语法错误,且依赖库为最新版本;
manifest.json 配置:
打开项目根目录的manifest.json,在 “App 模块配置” 中勾选 “HarmonyOS”;
填写鸿蒙应用基础信息:应用名称、包名(需与鸿蒙开发者平台注册的包名一致)、版本号、图标等;
权限配置:在 “HarmonyOS 权限配置” 中声明所需权限(如网络、存储、相机等),鸿蒙对权限管控较严格,未声明的权限会直接导致功能失效。

pages.json 配置:添加鸿蒙端专属配置,指定使用 ArkUI 渲染(默认开启),示例:  
json  
{  
  "globalStyle": {  
    "harmonyos": {  
      "useArkUI": true, // 启用ArkUI渲染(必填)  
      "windowBackgroundColor": "#ffffff" // 鸿蒙端窗口背景色  
    }  
  }  
}

3. 依赖兼容性检查

插件兼容:移除依赖 Android/iOS 原生 SDK 的 Uniapp 插件(如某些支付、地图插件),替换为跨端兼容插件(如uni-ui、uView Plus、uni-pay等);
第三方库兼容:优先使用纯 JS/TS 库(如 axios、lodash),避免使用依赖原生模块的库(如 node-sqlite3);若必须使用,需确认库已支持鸿蒙环境。

三、核心适配:组件、API 与布局的差异化处理

Uniapp 的组件和 API 在鸿蒙端大多兼容,但因鸿蒙系统特性,部分场景需针对性适配,核心集中在以下 3 个维度:

1. 组件适配:替换不兼容组件,对齐行为差异

Uniapp 内置组件在鸿蒙端的兼容性可达 90% 以上,但部分组件存在行为差异,需重点关注:
优先使用 Uniapp 跨端组件:如<view>、<text>、<image>、<button>等,避免直接使用鸿蒙原生 ArkUI 组件(如<Text>、<Image>),否则会破坏多端兼容性;
表单组件适配:

<input>组件:type="number"在鸿蒙端需补充input-mode="numeric",确保弹出数字软键盘;placeholder-style需用内联样式,避免样式失效;  
<picker>组件:必须指定range-key(即使是简单数组),否则数据无法正常渲染,示例:  
vue  
<picker :range="array" range-key="name" @change="onPickerChange">  
  <view>选择内容</view>  
</picker>  
滚动组件适配:<scroll-view>横向滚动需显式设置scroll-x="true",且子组件需设置white-space: nowrap(避免换行),同时确保子组件宽度不超出容器;  
不兼容组件替换:  
<web-view>:鸿蒙端暂不支持,可改用uni.navigateTo跳转 H5 页面,或通过 Uniapp 插件集成鸿蒙原生WebComponent;  
<video>:鸿蒙端不支持controls属性自动显示控制栏,需自定义控制按钮(播放 / 暂停、进度条等)。

2. API 适配:处理权限、网络与环境判断

Uniapp 的uni.xxxAPI 在鸿蒙端基本兼容,但部分与系统相关的 API 需特殊处理:
权限动态申请:鸿蒙的权限体系与 Android/iOS 不同,需先在manifest.json声明权限,再通过uni.requestPermissions动态申请,示例(申请存储权限):

// 鸿蒙存储权限标识:ohos.permission.WRITE_USER_STORAGE  
uni.requestPermissions({  
  scope: 'ohos.permission.WRITE_USER_STORAGE',  
  success: (res) => {  
    if (res.granted) {  
      // 权限申请成功,执行文件读写操作  
      uni.saveFile({...});  
    } else {  
      uni.showToast({ title: '请开启存储权限以正常使用功能' });  
    }  
  }  
});

网络请求适配:
鸿蒙端默认禁止http协议请求,需在manifest.json中添加配置开启:

"harmonyos": {  
  "network": {  
    "cleartextTraffic": true // 允许http请求(开发环境可用,生产环境建议改用https)  
  }  
}

uni.request的timeout参数在鸿蒙端最小值为 1000ms,设置过小会导致请求失败;
环境判断与差异化逻辑:通过uni.getSystemInfo判断当前是否为鸿蒙环境,执行特殊逻辑:

uni.getSystemInfo({  
  success: (res) => {  
    // res.system 格式:"HarmonyOS 4.0.0"  
    this.isHarmonyOS = res.system.includes('HarmonyOS');  
    if (this.isHarmonyOS) {  
      // 鸿蒙端特殊处理(如替换组件、调整样式)  
      this.adaptHarmonyStyle();  
    }  
  }  
});

路由与页面生命周期:
uni.navigateBack在鸿蒙端需指定delta参数(如delta: 1),否则可能无法正常返回上一页;
鸿蒙端页面生命周期与小程序一致(onLoad/onShow/onUnload),但onReady触发时机略晚,避免在onReady中执行依赖 DOM 的操作(可延迟 100ms)。

3. 布局适配:适配鸿蒙多设备尺寸与特性

鸿蒙支持手机、平板、折叠屏等多设备,布局适配需兼顾 “自适应” 与 “设备特性”:
优先使用 rpx 单位:Uniapp 的 rpx 单位在鸿蒙端同样生效(1rpx = 屏幕宽度 / 750),无需额外适配尺寸,确保布局在不同屏幕尺寸的鸿蒙设备上自适应;
避免固定布局:禁止使用px固定宽度 / 高度,优先使用flex布局 +flex-grow/flex-shrink,确保组件随屏幕伸缩;

平板 / 折叠屏适配:通过mediaQuery实现不同屏幕尺寸的布局切换,示例:  
json  
// pages.json中配置  
{  
  "pages": [  
    {  
      "path": "pages/list/list",  
      "style": {  
        "mediaQuery": {  
          "min-width": "800px": { // 平板屏幕(宽度≥800px)  
            "layout": "grid",  
            "grid-template-columns": "1fr 1fr", // 双列布局  
            "grid-gap": "20rpx"  
          }  
        }  
      }  
    }  
  ]  
}

样式兼容性:
鸿蒙端不支持scoped样式中的::v-deep,Vue 3 项目需改用::deep,Vue 2 项目改用/deep/;
避免使用position: fixed(鸿蒙端可能出现层级异常),优先使用sticky或absolute+ 父容器定位;
<text>组件的line-height默认不继承,需显式设置(如line-height: 32rpx)。

四、调试与打包:避坑指南

适配过程中,调试和打包是容易踩坑的环节,分享关键注意事项:

1. 模拟器调试技巧

连接失败解决:
确认 HBuilderX 与 DevEco Studio 模拟器处于同一网络;
重启鸿蒙模拟器(在 DevEco Studio 中关闭后重新启动);
检查manifest.json的包名与鸿蒙开发者平台注册的包名一致;
日志查看:在 HBuilderX 的 “运行日志” 中查看鸿蒙端报错信息,若日志不完整,可在 DevEco Studio 中打开 “Logcat” 查看详细原生日志。

2. 打包发布注意事项

证书配置:需在鸿蒙开发者平台申请 “应用发布证书” 和 “Profile 文件”,并在 HBuilderX 的manifest.json中配置(“HarmonyOS 打包配置”);
版本号规范:鸿蒙应用的版本号(versionName)需遵循 “主版本。次版本。修订号” 格式(如 1.0.0),且需高于已发布版本;
安装失败排查:
检查证书是否过期或与包名不匹配;
确认设备系统版本≥API Version 9;
检查权限配置是否完整(缺少必要权限会导致安装失败)。

五、优化升级:让鸿蒙应用体验更原生

适配完成后,可通过以下优化提升应用的鸿蒙原生体验:

1. 接入鸿蒙分布式能力

Uniapp 支持通过插件调用鸿蒙分布式 API,让应用具备跨设备协同能力:
集成 “鸿蒙分布式路由” 插件,实现手机、平板、手表等设备间的页面跳转;
使用 “分布式数据管理” 插件,实现多设备间的数据同步(如购物车、收藏夹)。

2. 适配鸿蒙深色模式

鸿蒙系统支持深色模式,适配后可提升用户体验:

在manifest.json中开启深色模式支持:  
json  
"harmonyos": {  
  "darkMode": "auto", // 跟随系统主题  
  "theme": {  
    "light": "#ffffff", // 浅色模式背景色  
    "dark": "#1a1a1a"  // 深色模式背景色  
  }  
}

在样式中通过媒体查询适配深色模式:

css  
/* 全局样式或页面样式 */  
@media (prefers-color-scheme: dark) {  
  .container {  
    background-color: #1a1a1a;  
    color: #ffffff;  
  }  
  .btn {  
    background-color: #333333;  
    border-color: #666666;  
  }  
}

3. 性能优化

列表优化:长列表使用v-for+key,避免频繁修改 DOM;开启列表懒加载(uni-scroll-view的lower-threshold),减少一次性渲染数据量;
资源优化:压缩图片(使用 webp 格式)、懒加载非首屏图片;减少首屏网络请求,优先使用本地缓存数据;
动画优化:避免使用复杂 CSS 动画,优先使用uni.createAnimation;动画时长控制在 300ms 以内,提升流畅度。

六、总结

Uniapp 适配鸿蒙的核心逻辑是 “复用现有技术栈,补齐系统差异点”,整个过程无需从零开发,适配成本低、效率高。对于已有 Uniapp 项目的团队,仅需完成 “环境搭建→配置调整→组件 / API 差异化处理→调试打包” 四个步骤,即可快速切入鸿蒙生态;对于新项目,使用 Uniapp 开发可一次性覆盖 iOS、Android、小程序、鸿蒙等多端,最大化代码价值。
随着鸿蒙生态的持续壮大,Uniapp 对鸿蒙的适配能力也在不断升级(如支持更多鸿蒙原生 API、优化性能体验)。现在正是布局鸿蒙生态的黄金时期,借助 Uniapp 的跨端优势,开发者可快速抢占鸿蒙设备用户市场,为应用的多端发展增添新的增长点。

收起阅读 »

【解决】类似vue项目App.vue的template下添加的元素,全部页面都会显示;根组件

全局组件

原作者的文章:https://ask.dcloud.net.cn/article/39345
原作者的github地址:vue-inset-loader
vue2+webpack版本:ste-vue-inset-loader
vue3+vite版本:vue3-inset-loader

继续阅读 »

原作者的文章:https://ask.dcloud.net.cn/article/39345
原作者的github地址:vue-inset-loader
vue2+webpack版本:ste-vue-inset-loader
vue3+vite版本:vue3-inset-loader

收起阅读 »