全部学科
NodeJS全栈
nodejs
Python全栈
python

uni-app 鸿蒙 App 自定义配置全攻略:bundleName、图标、权限

📅 2026-07-23 uni-app HarmonyOS 鸿蒙 HBuilderX bundleName 权限 图标

uni-app 鸿蒙 App 自定义配置全攻略:bundleName、图标、权限

项目:Vue 3 + uni-app 3.0 App,编译目标 HarmonyOS App
环境:HBuilderX 5.x + DevEco Studio 5.0 + 鸿蒙真机
配置方式:harmony-configs/ 目录 + Vite 插件自动同步


一、背景

在 uni-app 中开发鸿蒙 App,我们通常需要自定义三个东西:

  1. bundleName(包名):区分不同产品线(如 cn.juanwang.dev vs cn.juanwang.sgt
  2. App 图标:每个产品有不同的品牌图标
  3. 系统权限:需要声明 WRITE_IMAGEVIDEO、VIBRATE 等敏感权限

这三点都无法通过 uni-app 的 manifest.json 配置生效——鸿蒙构建系统对此支持有限。正确的方式是利用 HBuilderX 内置的 harmony-configs 机制


二、harmony-configs 是什么

HBuilderX 编译 uni-app 到鸿蒙时,会自动读取项目根目录下 harmony-configs/ 文件夹,将其内容原样复制/合并到生成的鸿蒙原生工程中。

目录映射关系:

json5
harmony-configs/
├── AppScope/
│   ├── app.json5                      → dist/dev/app-harmony/AppScope/app.json5
│   └── resources/base/media/
│       ├── foreground.png             → dist/dev/app-harmony/AppScope/.../media/foreground.png
│       ├── background.png             → dist/dev/app-harmony/AppScope/.../media/background.png
│       └── startIcon.png              → dist/dev/app-harmony/AppScope/.../media/startIcon.png
└── entry/src/main/
    ├── module.json5                   → dist/dev/app-harmony/entry/src/main/module.json5
    └── resources/base/element/
        └── string.json               → dist/dev/app-harmony/entry/.../element/string.json

核心要点: 你在 harmony-configs/ 下放什么,构建后就得到什么。这是官方推荐的配置方式,不依赖任何第三方脚本。


三、配置清单

3.1 app.json5 —— bundleName + 版本号

harmony-configs/AppScope/app.json5 中:

json5
{
  "app": {
    "bundleName": "cn.juanwang.dev",
    "vendor": "juanwangdev",
    "versionCode": 20101,
    "versionName": "2.1.1",
    "icon": "$media:layered_image",
    "label": "$string:app_name"
  }
}
字段说明
bundleName应用包名,全网唯一,需与 AppGallery Connect 注册一致
versionCode整数版本号,规则:major×10000 + minor×100 + patch
versionName展示版本号,如 2.1.1
icon引用 $media:layered_image,对应 foreground.png + background.png

3.2 模块权限 —— module.json5

harmony-configs/entry/src/main/module.json5 中声明需要的权限:

JSON
{
  "module": {
    "name": "entry",
    "type": "entry",
    "mainElement": "EntryAbility",
    "deviceTypes": ["phone", "tablet", "2in1"],
    "requestPermissions": [
      { "name": "ohos.permission.INTERNET" },
      { "name": "ohos.permission.VIBRATE" },
      {
        "name": "ohos.permission.WRITE_IMAGEVIDEO",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.READ_IMAGEVIDEO",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      }
    ]
  }
}

踩坑提醒: manifest.json 中的 app-harmony.distribute.requestPermissions 不会被构建系统处理。权限只能在 module.json5 中声明,而 module.json5 只能通过 harmony-configs/ 注入。

3.3 权限说明文案 —— string.json

权限申请时需要向用户展示用途说明,在 harmony-configs/entry/src/main/resources/base/element/string.json 中定义:

JSON
{
  "string": [
    {
      "name": "Reason_SaveImage",
      "value": "用于将二维码保存到系统相册"
    }
  ]
}

3.4 App 图标

harmony-configs/AppScope/resources/base/media/ 下放三个图标文件:

文件尺寸作用
foreground.png216×216dp前景图标(带透明通道)
background.png216×216dp背景底色
startIcon.png约 80×80冷启动/加载时显示的图标

图标源文件可以按产品分开存放,例如 app-icons/jwdev/app-icons/jwsgt/


四、多产品(bundleName)切换

一套源码打出多个包名的 App(如 cn.juanwang.devcn.juanwang.sgt),核心思路是模板 + 同步

4.1 模板文件

JavaScript
harmony-configs/
├── AppScope/app.json5          ← 当前激活的(同步脚本写入,可 .gitignore)
├── jwdev/AppScope/app.json5    ← jwdev 模板(bundleName=cn.juanwang.dev)
├── jwsgt/AppScope/app.json5    ← jwsgt 模板(bundleName=cn.juanwang.sgt)
└── entry/                      ← 公共的 module.json5、string.json

4.2 repo.config.json

JavaScript
{
  "jwdev": {
    "harmonyBundleName": "cn.juanwang.dev",
    "agcProjectId": "101653523864525656",
    "agcClientId": "1993650698687927040"
  },
  "jwsgt": {
    "harmonyBundleName": "cn.juanwang.sgt",
    "agcProjectId": "101653523864554490",
    "agcClientId": "1996303095587430208"
  }
}

4.3 自动同步方案

创建 scripts/sync-harmony-configs.js,在构建前自动执行:

JavaScript
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '..');

function syncHarmonyConfigs() {
  // 1. 读取 REPO_CODE
  const baseUrl = fs.readFileSync(path.join(ROOT, 'src', 'baseUrl.js'), 'utf8');
  const repoCode = baseUrl.match(/REPO_CODE\s*=\s*'([^']+)'/)[1];

  // 2. 读取版本号
  const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
  const parts = pkg.version.split('.').map(Number);
  const versionCode = parts[0] * 10000 + parts[1] * 100 + parts[2];

  // 3. 读取 bundleName
  const config = JSON.parse(fs.readFileSync(path.join(ROOT, 'repo.config.json'), 'utf8'));
  const bundleName = config[repoCode].harmonyBundleName;

  // 4. 从模板复制 app.json5,注入版本号
  const template = path.join(ROOT, 'harmony-configs', repoCode, 'AppScope', 'app.json5');
  const appJson5 = JSON.parse(fs.readFileSync(template, 'utf8'));
  appJson5.app.bundleName = bundleName;
  appJson5.app.versionCode = versionCode;
  appJson5.app.versionName = pkg.version;
  fs.writeFileSync(
    path.join(ROOT, 'harmony-configs', 'AppScope', 'app.json5'),
    JSON.stringify(appJson5, null, 2) + '\n'
  );

  // 5. 复制图标
  const iconDest = path.join(ROOT, 'harmony-configs', 'AppScope', 'resources', 'base', 'media');
  ['foreground.png', 'background.png', 'startIcon.png'].forEach(name => {
    const src = path.join(ROOT, 'app-icons', repoCode, name);
    if (fs.existsSync(src)) fs.copyFileSync(src, path.join(iconDest, name));
  });

  console.log(`[harmony-configs] bundleName=${bundleName} v${pkg.version}`);
}

module.exports = { syncHarmonyConfigs };

4.4 Vite 插件自动触发

vite.config.js 中注册插件,检测到鸿蒙构建时自动调用同步:

JavaScript
import { syncHarmonyConfigs } from './scripts/sync-harmony-configs.js';

export default defineConfig({
  plugins: [
    // 必须在 uni() 之前,ensure: 'pre' 确保最先执行
    (() => {
      let synced = false;
      return {
        name: 'harmony-configs-sync',
        enforce: 'pre',
        configResolved() {
          if (process.env.UNI_PLATFORM === 'app-harmony' && !synced) {
            synced = true;
            syncHarmonyConfigs();
          }
        }
      };
    })(),
    // ... 其他插件(harmony-fix-iife、uni()、iconfontCSSPlugin 等)
  ]
});

日常开发完全无感。HBuilderX 点击「运行到鸿蒙」→ 自动同步配置 → 自动构建 → 自动打开 DevEco Studio。


五、vite.config.js 关键适配

鸿蒙构建还有两个必须在 vite.config.js 中处理的问题:

5.1 代码分割禁用

鸿蒙不支持动态 chunk,必须强制内联所有动态 import:

text
{
  name: 'harmony-fix-iife',
  enforce: 'post',
  configResolved(config) {
    const isHarmony = process.env.UNI_PLATFORM === 'app-harmony'
                   || process.env.UNI_PLATFORM === 'mp-harmony';
    if (isHarmony) {
      config.build.rollupOptions.output.inlineDynamicImports = true;
      delete config.build.rollupOptions.output.manualChunks;
    }
  }
}

5.2 SCSS 全局变量注入

text
css: {
  preprocessorOptions: {
    scss: {
      additionalData: `@use "@/styles/design-tokens-active.scss" as *;\n`
    }
  }
}

注意:使用 @use ... as * 而非 @import,避免 Dart Sass 2.0 的废弃警告。


六、完整构建链路

text
HBuilderX 点击「运行到鸿蒙」
  │
  ├─ [Vite configResolved] harmony-configs-sync 插件(enforce: pre)
  │    ├─ 从模板复制 app.json5 → harmony-configs/AppScope/app.json5
  │    ├─ 注入 bundleName + versionCode + versionName
  │    └─ 复制图标 → harmony-configs/AppScope/resources/base/media/
  │
  ├─ [Vite build] uni build -p app-harmony
  │    ├─ harmony-fix-iife → 禁用代码分割
  │    └─ 编译前端代码 → dist/dev/app-harmony/
  │
  ├─ [HBuilderX] 读取 harmony-configs/ → 合并到原生工程
  │    ├─ AppScope/app.json5 → bundleName + 版本
  │    ├─ entry/module.json5 → 权限声明
  │    └─ AppScope/resources/media/* → App 图标
  │
  └─ 打开 DevEco Studio → Build → Run → 真机

七、要点总结

  1. harmony-configs 是官方机制,文件放在 harmony-configs/ 下会被自动合并到原生工程
  2. manifest.json 的 app-harmony.distribute 字段支持极少(基本只有 icons),权限和包名都无法通过 manifest 配置
  3. 权限必须在 module.json5 中声明,通过 harmony-configs/entry/src/main/module.json5 注入
  4. 多产品切换用「模板 + 同步」模式harmony-configs/{product}/AppScope/app.json5 模板 → 同步脚本 → harmony-configs/AppScope/app.json5
  5. Vite 插件自动触发同步enforce: 'pre' + configResolved,检测到 app-harmony 平台时自动执行
  6. 鸿蒙必须禁用代码分割inlineDynamicImports: true,否则 IIFE 编译失败

本文整理了基于 HBuilderX 的 uni-app 鸿蒙开发中,配置 bundleName、App 图标和系统权限的完整方案。核心思路是利用 harmony-configs 官方机制,配合 Vite 插件实现自动化,告别手动跑批处理脚本的繁琐流程。

本文为开发实践记录,内容如有错误或不足,欢迎搜索微信公众号「卷王开发者」批评指正。共同进步,感谢阅读。

扫码体验小程序
加载中
想在手机上刷题学习?
使用微信卷王开发者小程序,打开首页顶部扫码功能识别二维码