HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

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

鸿蒙征文

一个专为华为鸿蒙激励计划开发者打造的数据统计与可视化 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

收起阅读 »

【鸿蒙征文】uni-app鸿蒙上架必备技能:应用适配深色模式

鸿蒙征文

uni-app鸿蒙上架必备技能:应用适配深色模式

此文将介绍 uni-app 如何适配深色模式,文章内容通俗易懂,非常适合新手小白上手,从此再也不用担心如何适配深色模式了。

为什么要适配深色模式?

  • 鸿蒙应用商店上架强制要求
  • 提升用户体验
  • 符合现代应用设计趋势

开发环境要求

  • HBuilderX 4.76+
  • 当前必须手动在 harmony-configs/libs 目录增加 UniAppRuntime.har

注意: UniAppRuntime.har 文件请在示例项目中复制

快速上手(三步搞定)

第一步:启用深色模式支持

manifest.json 中添加配置:

{  
    // 鸿蒙App  
    "app-harmony": {  
        "darkmode": true,  
        "themeLocation": "theme.json",  
        "safearea": {  
            "bottom": {  
                "offset": "none"  
            }  
        }  
    },  
    // iOS 和 安卓  
    "app-plus": {  
        "darkmode": true,  
        "themeLocation": "theme.json",  
        "safearea": {  
            "bottom": {  
                "offset": "none"  
            }  
        },  
    },  
    // Web  
    "h5": {  
        "darkmode": true,  
        "themeLocation": "theme.json"  
    },  
    // 微信小程序  
    "mp-weixin": {  
        "darkmode": true,  
        "themeLocation": "theme.json",  
    }  
}

说明:darkmode: true 表示启用深色模式,themeLocation 指定主题配置文件位置。

第二步:创建主题配置文件

在项目根目录创建 theme.json,定义浅色和深色两套颜色:

{  
    "light": {  
        "navBgColor": "#ffffff",  
        "navTxtStyle": "black",  
        "bgColor": "#f5f5f5",  
        "tabBgColor": "#ffffff",  
        "tabFontColor": "#666666",  
        "tabSelectedColor": "#007aff",  
        "tabBorderStyle": "black"  
    },  
    "dark": {  
        "navBgColor": "#1a1a1a",  
        "navTxtStyle": "white",  
        "bgColor": "#000000",  
        "tabBgColor": "#1a1a1a",  
        "tabFontColor": "#999999",  
        "tabSelectedColor": "#0a84ff",  
        "tabBorderStyle": "white"  
    }  
}

说明:

  • light:浅色模式下的颜色
  • dark:深色模式下的颜色
  • 可以自定义任意颜色变量
  • 此处的变量仅在 pages.json 文件中使用

第三步:在 pages.json 中使用主题变量

使用 @变量名 的方式引用主题颜色:

{  
    "globalStyle": {  
        "navigationBarTextStyle": "@navTxtStyle",  
        "navigationBarTitleText": "深色模式示例",  
        "navigationBarBackgroundColor": "@navBgColor",  
        "backgroundColor": "@bgColor"  
    },  
    "tabBar": {  
        "color": "@tabFontColor",  
        "selectedColor": "@tabSelectedColor",  
        "backgroundColor": "@tabBgColor",  
        "borderStyle": "@tabBorderStyle",  
        "list": [...]  
    }  
}

说明:系统会根据当前模式自动选择对应的颜色值。

页面样式适配

上面操作的3步骤仅适配了顶部导航和底部tabbar,接下来将介绍页面样式如何适配深色模式。

核心: 使用 CSS 变量 + 媒体查询覆盖 CSS 变量的值

第一步:项目根目录创建 theme.scss 文件:

:root,  
page {  
  // 背景色  
  --page-bg: #f5f5f5;  
  --card-bg: #ffffff;  

  // 文字色  
  --text-primary: #333333;  
  --text-secondary: #666666;  

  // 主题色  
  --primary-color: #007aff;  

  // 边框色  
  --border-color: #f0f0f0;  
}  

// 深色模式  
@media (prefers-color-scheme: dark) {  
  :root,  
  page {  
    // 背景色  
    --page-bg: #000000;  
    --card-bg: #1a1a1a;  

    // 文字色  
    --text-primary: #ffffff;  
    --text-secondary: #999999;  

    // 主题色  
    --primary-color: #0a84ff;  

    // 边框色  
    --border-color: #2a2a2a;  
  }  
}  

page {  
  background-color: var(--page-bg);  
}

第二步:在 App.vue 中引入:

<style lang="scss">  
  @import "./theme.scss";  
</style>

第三步:在页面中使用 CSS 变量:

<style lang="scss" scoped>  
.container {  
  background-color: var(--page-bg);  
}  

.card {  
  background-color: var(--card-bg);  
  color: var(--text-primary);  
}  

.text {  
  color: var(--text-secondary);  
}  
</style>

常见问题

1. 如何测试深色模式?

  • 鸿蒙设备:设置 → 显示与亮度 → 深色模式 → 全天开启
  • iOS/Android:在系统设置中切换外观
  • 谷歌浏览器:设置 → 外观 → 主题
  • 微信小程序:跟随微信App的设置

2. 底部tabbar的图标怎么适配?

建议使用通用的黑白图标,或者在 theme.json 中定义不同的图标路径。

核心要点总结

  1. manifest.json 中启用 darkmode: true
  2. 创建 theme.json 定义颜色变量
  3. pages.json 中使用 @变量名 引用
  4. 使用 CSS 变量 + 媒体查询适配页面样式
  5. 测试浅色和深色两种模式下的显示效果
继续阅读 »

uni-app鸿蒙上架必备技能:应用适配深色模式

此文将介绍 uni-app 如何适配深色模式,文章内容通俗易懂,非常适合新手小白上手,从此再也不用担心如何适配深色模式了。

为什么要适配深色模式?

  • 鸿蒙应用商店上架强制要求
  • 提升用户体验
  • 符合现代应用设计趋势

开发环境要求

  • HBuilderX 4.76+
  • 当前必须手动在 harmony-configs/libs 目录增加 UniAppRuntime.har

注意: UniAppRuntime.har 文件请在示例项目中复制

快速上手(三步搞定)

第一步:启用深色模式支持

manifest.json 中添加配置:

{  
    // 鸿蒙App  
    "app-harmony": {  
        "darkmode": true,  
        "themeLocation": "theme.json",  
        "safearea": {  
            "bottom": {  
                "offset": "none"  
            }  
        }  
    },  
    // iOS 和 安卓  
    "app-plus": {  
        "darkmode": true,  
        "themeLocation": "theme.json",  
        "safearea": {  
            "bottom": {  
                "offset": "none"  
            }  
        },  
    },  
    // Web  
    "h5": {  
        "darkmode": true,  
        "themeLocation": "theme.json"  
    },  
    // 微信小程序  
    "mp-weixin": {  
        "darkmode": true,  
        "themeLocation": "theme.json",  
    }  
}

说明:darkmode: true 表示启用深色模式,themeLocation 指定主题配置文件位置。

第二步:创建主题配置文件

在项目根目录创建 theme.json,定义浅色和深色两套颜色:

{  
    "light": {  
        "navBgColor": "#ffffff",  
        "navTxtStyle": "black",  
        "bgColor": "#f5f5f5",  
        "tabBgColor": "#ffffff",  
        "tabFontColor": "#666666",  
        "tabSelectedColor": "#007aff",  
        "tabBorderStyle": "black"  
    },  
    "dark": {  
        "navBgColor": "#1a1a1a",  
        "navTxtStyle": "white",  
        "bgColor": "#000000",  
        "tabBgColor": "#1a1a1a",  
        "tabFontColor": "#999999",  
        "tabSelectedColor": "#0a84ff",  
        "tabBorderStyle": "white"  
    }  
}

说明:

  • light:浅色模式下的颜色
  • dark:深色模式下的颜色
  • 可以自定义任意颜色变量
  • 此处的变量仅在 pages.json 文件中使用

第三步:在 pages.json 中使用主题变量

使用 @变量名 的方式引用主题颜色:

{  
    "globalStyle": {  
        "navigationBarTextStyle": "@navTxtStyle",  
        "navigationBarTitleText": "深色模式示例",  
        "navigationBarBackgroundColor": "@navBgColor",  
        "backgroundColor": "@bgColor"  
    },  
    "tabBar": {  
        "color": "@tabFontColor",  
        "selectedColor": "@tabSelectedColor",  
        "backgroundColor": "@tabBgColor",  
        "borderStyle": "@tabBorderStyle",  
        "list": [...]  
    }  
}

说明:系统会根据当前模式自动选择对应的颜色值。

页面样式适配

上面操作的3步骤仅适配了顶部导航和底部tabbar,接下来将介绍页面样式如何适配深色模式。

核心: 使用 CSS 变量 + 媒体查询覆盖 CSS 变量的值

第一步:项目根目录创建 theme.scss 文件:

:root,  
page {  
  // 背景色  
  --page-bg: #f5f5f5;  
  --card-bg: #ffffff;  

  // 文字色  
  --text-primary: #333333;  
  --text-secondary: #666666;  

  // 主题色  
  --primary-color: #007aff;  

  // 边框色  
  --border-color: #f0f0f0;  
}  

// 深色模式  
@media (prefers-color-scheme: dark) {  
  :root,  
  page {  
    // 背景色  
    --page-bg: #000000;  
    --card-bg: #1a1a1a;  

    // 文字色  
    --text-primary: #ffffff;  
    --text-secondary: #999999;  

    // 主题色  
    --primary-color: #0a84ff;  

    // 边框色  
    --border-color: #2a2a2a;  
  }  
}  

page {  
  background-color: var(--page-bg);  
}

第二步:在 App.vue 中引入:

<style lang="scss">  
  @import "./theme.scss";  
</style>

第三步:在页面中使用 CSS 变量:

<style lang="scss" scoped>  
.container {  
  background-color: var(--page-bg);  
}  

.card {  
  background-color: var(--card-bg);  
  color: var(--text-primary);  
}  

.text {  
  color: var(--text-secondary);  
}  
</style>

常见问题

1. 如何测试深色模式?

  • 鸿蒙设备:设置 → 显示与亮度 → 深色模式 → 全天开启
  • iOS/Android:在系统设置中切换外观
  • 谷歌浏览器:设置 → 外观 → 主题
  • 微信小程序:跟随微信App的设置

2. 底部tabbar的图标怎么适配?

建议使用通用的黑白图标,或者在 theme.json 中定义不同的图标路径。

核心要点总结

  1. manifest.json 中启用 darkmode: true
  2. 创建 theme.json 定义颜色变量
  3. pages.json 中使用 @变量名 引用
  4. 使用 CSS 变量 + 媒体查询适配页面样式
  5. 测试浅色和深色两种模式下的显示效果
收起阅读 »

【鸿蒙征文】从现在起,你的非原生弹窗“组件”们(自定义Toast、Modal等)只需要配置一次!

鸿蒙征文

介绍

👋 hello, 您那要是早上,那祝你早安。要是下午,那祝你午安。晚上看?那还是别看了,点个赞,明天再看

我是 skiyee 是本篇的作者,常活跃在 uni-app 生态领域

随着 Harmony 不断增强以及大力推广,更多的应用场景进入我们的眼帘

我就在想,要是 uni-app 也适配了 Harmony,那不就省事了,一键多端共同开发,不再需要花费更多的学习成本

您猜怎么着,uni-app 还真适配 Harmony Next 了,那么我们就可以结合 uni-app 原本的生态来开发了!

痛点

很多朋友在编写弹窗时,觉得原生的鸿蒙弹窗不好看,就想自定义一个美美滴。但发现,uni-app 中居然没有地方能够一键进行设置,全局就可以调用的

朋友们所期望的应该是像体验原生一般,如 uni.showToast({...}) 这种,在任何地方都能调起的

为了解决这个痛点,我结合 uni-app 使用的底层构建工具 vite 开发了一个模拟根组件能力的组件!@uni-ku/root

开始

安装

在我们的 HBuilder 中,选择我们的项目打开命令行窗口,输入命令

npm install -D @uni-ku/root


配置

在 vite.config.js 中引入 @uni-ku/root

// vite.config.js  
import Uni from '@dcloudio/vite-plugin-uni'  
import UniKuRoot from '@uni-ku/root'  
import { defineConfig } from 'vite'  

export default defineConfig({  
  plugins: [  
    // 文档: https://github.com/uni-ku/root  
    UniKuRoot(),  
    Uni()  
  ]  
})

使用

创建关键 App.ku.vue 文件,并通过标签 <KuRootView /><ku-root-view /> 指定视图存放位置

<script setup>  
import { ref } from 'vue'  

const UniKuRoot = ref('Hello UniKu Root')  
</script>  

<template>  
  <div>{{ UniKuRoot }}</div>  
  <!-- 视图存放位置 -->  
  <KuRootView />  
</template>

封装

编写自定义的 Toast 组件

<!-- components/GlobalToast.vue -->  

<script setup>  
import { useToast } from '@/composables/useToast'  

const { globalToastState, hideToast } = useToast()  
</script>  

<template>  
  <div v-if="globalToastState" class="toast-wrapper" @click="hideToast">  
    <div class="toast-box">  
      welcome to use @uni-ku/root  
    </div>  
  </div>  
</template>  

<style scoped>  
.toast-wrapper{  
  position: fixed;  
  top: 0;  
  left: 0;  
  width: 100%;  
  height: 100%;  
  background-color: rgba(0, 0, 0, 0.5);  
  display: flex;  
  align-items: center;  
  justify-content: center;  
}  

.toast-box{  
  background: white;  
  color: black;  
}  
</style>

实现 Toast 调起方法

// composables/useToast.js  

import { ref } from 'vue'  

const globalToastState = ref(false)  

export function useToast() {  
  function showToast() {  
    globalToastState.value = true  
  }  

  function hideToast() {  
    globalToastState.value = false  
  }  

  return {  
    globalToastState,  
    showToast,  
    hideToast,  
  }  
}

挂载至 App.ku.vue

<!-- App.ku.vue -->  

<script setup>  
import GlobalToast from '@/components/GlobalToast.vue'  
</script>  

<template>  
  <KuRootView />  
  <GlobalToast />  
</template>

视图内部触发 Toast 组件

<!-- pages/index(或者其他页面).vue -->  

<script setup lang="ts">  
import { useToast } from '@/composables/useToast'  

const { showToast } = useToast()  
</script>  

<template>  
  <view>  
    Hello UniKuRoot  
  </view>  
  <button @click="showToast">  
    视图内触发展示Toast  
  </button>  
</template>

具体代码详见:示例

最终效果

打开我们的鸿蒙应用,来展示我们的最终效果

上面这个Toast只是一个示例,最终需要利用 UniKuRoot 去实现什么效果,是开发者自行可以想象的!

结语

结语就说些心里话吧,随着对 uni-app 的越来越多的理解,我觉得目前 uni-app 是跨平台小程序做的最棒的、App跨端方面是生态最好的。

虽然有些功能还不是很棒,但是目前仍在不断的更新迭代、不断的完善,我相信有开发人员和生态建设的爱好者们的参与会越来越棒!

鼓励

如果为此感到兴奋,可以通过以下渠道让我们知道!

生态

以下都是关于 UniApp 生态相关的仓库

继续阅读 »

介绍

👋 hello, 您那要是早上,那祝你早安。要是下午,那祝你午安。晚上看?那还是别看了,点个赞,明天再看

我是 skiyee 是本篇的作者,常活跃在 uni-app 生态领域

随着 Harmony 不断增强以及大力推广,更多的应用场景进入我们的眼帘

我就在想,要是 uni-app 也适配了 Harmony,那不就省事了,一键多端共同开发,不再需要花费更多的学习成本

您猜怎么着,uni-app 还真适配 Harmony Next 了,那么我们就可以结合 uni-app 原本的生态来开发了!

痛点

很多朋友在编写弹窗时,觉得原生的鸿蒙弹窗不好看,就想自定义一个美美滴。但发现,uni-app 中居然没有地方能够一键进行设置,全局就可以调用的

朋友们所期望的应该是像体验原生一般,如 uni.showToast({...}) 这种,在任何地方都能调起的

为了解决这个痛点,我结合 uni-app 使用的底层构建工具 vite 开发了一个模拟根组件能力的组件!@uni-ku/root

开始

安装

在我们的 HBuilder 中,选择我们的项目打开命令行窗口,输入命令

npm install -D @uni-ku/root


配置

在 vite.config.js 中引入 @uni-ku/root

// vite.config.js  
import Uni from '@dcloudio/vite-plugin-uni'  
import UniKuRoot from '@uni-ku/root'  
import { defineConfig } from 'vite'  

export default defineConfig({  
  plugins: [  
    // 文档: https://github.com/uni-ku/root  
    UniKuRoot(),  
    Uni()  
  ]  
})

使用

创建关键 App.ku.vue 文件,并通过标签 <KuRootView /><ku-root-view /> 指定视图存放位置

<script setup>  
import { ref } from 'vue'  

const UniKuRoot = ref('Hello UniKu Root')  
</script>  

<template>  
  <div>{{ UniKuRoot }}</div>  
  <!-- 视图存放位置 -->  
  <KuRootView />  
</template>

封装

编写自定义的 Toast 组件

<!-- components/GlobalToast.vue -->  

<script setup>  
import { useToast } from '@/composables/useToast'  

const { globalToastState, hideToast } = useToast()  
</script>  

<template>  
  <div v-if="globalToastState" class="toast-wrapper" @click="hideToast">  
    <div class="toast-box">  
      welcome to use @uni-ku/root  
    </div>  
  </div>  
</template>  

<style scoped>  
.toast-wrapper{  
  position: fixed;  
  top: 0;  
  left: 0;  
  width: 100%;  
  height: 100%;  
  background-color: rgba(0, 0, 0, 0.5);  
  display: flex;  
  align-items: center;  
  justify-content: center;  
}  

.toast-box{  
  background: white;  
  color: black;  
}  
</style>

实现 Toast 调起方法

// composables/useToast.js  

import { ref } from 'vue'  

const globalToastState = ref(false)  

export function useToast() {  
  function showToast() {  
    globalToastState.value = true  
  }  

  function hideToast() {  
    globalToastState.value = false  
  }  

  return {  
    globalToastState,  
    showToast,  
    hideToast,  
  }  
}

挂载至 App.ku.vue

<!-- App.ku.vue -->  

<script setup>  
import GlobalToast from '@/components/GlobalToast.vue'  
</script>  

<template>  
  <KuRootView />  
  <GlobalToast />  
</template>

视图内部触发 Toast 组件

<!-- pages/index(或者其他页面).vue -->  

<script setup lang="ts">  
import { useToast } from '@/composables/useToast'  

const { showToast } = useToast()  
</script>  

<template>  
  <view>  
    Hello UniKuRoot  
  </view>  
  <button @click="showToast">  
    视图内触发展示Toast  
  </button>  
</template>

具体代码详见:示例

最终效果

打开我们的鸿蒙应用,来展示我们的最终效果

上面这个Toast只是一个示例,最终需要利用 UniKuRoot 去实现什么效果,是开发者自行可以想象的!

结语

结语就说些心里话吧,随着对 uni-app 的越来越多的理解,我觉得目前 uni-app 是跨平台小程序做的最棒的、App跨端方面是生态最好的。

虽然有些功能还不是很棒,但是目前仍在不断的更新迭代、不断的完善,我相信有开发人员和生态建设的爱好者们的参与会越来越棒!

鼓励

如果为此感到兴奋,可以通过以下渠道让我们知道!

生态

以下都是关于 UniApp 生态相关的仓库

收起阅读 »

uniapp app 端如何可以监听到声音的分贝值?我现在用的是uni.getRecorderManager() 这个方法,不支持监听声音的分贝值。

我现在想实现一个功能,实时的去监听用户讲话,想如果用户的声音的大小就识别客户已经讲完话,从而去实现对应的逻辑。有什么办法可以实现吗?麻烦各位大神指导一下,谢谢!

我现在想实现一个功能,实时的去监听用户讲话,想如果用户的声音的大小就识别客户已经讲完话,从而去实现对应的逻辑。有什么办法可以实现吗?麻烦各位大神指导一下,谢谢!