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,我们通常需要自定义三个东西:
- bundleName(包名):区分不同产品线(如
cn.juanwang.devvscn.juanwang.sgt) - App 图标:每个产品有不同的品牌图标
- 系统权限:需要声明 WRITE_IMAGEVIDEO、VIBRATE 等敏感权限
这三点都无法通过 uni-app 的 manifest.json 配置生效——鸿蒙构建系统对此支持有限。正确的方式是利用 HBuilderX 内置的 harmony-configs 机制。
二、harmony-configs 是什么
HBuilderX 编译 uni-app 到鸿蒙时,会自动读取项目根目录下 harmony-configs/ 文件夹,将其内容原样复制/合并到生成的鸿蒙原生工程中。
目录映射关系:
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 中:
{
"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 中声明需要的权限:
{
"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 中定义:
{
"string": [
{
"name": "Reason_SaveImage",
"value": "用于将二维码保存到系统相册"
}
]
}
3.4 App 图标
harmony-configs/AppScope/resources/base/media/ 下放三个图标文件:
| 文件 | 尺寸 | 作用 |
|---|---|---|
foreground.png | 216×216dp | 前景图标(带透明通道) |
background.png | 216×216dp | 背景底色 |
startIcon.png | 约 80×80 | 冷启动/加载时显示的图标 |
图标源文件可以按产品分开存放,例如 app-icons/jwdev/、app-icons/jwsgt/。
四、多产品(bundleName)切换
一套源码打出多个包名的 App(如 cn.juanwang.dev 和 cn.juanwang.sgt),核心思路是模板 + 同步:
4.1 模板文件
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
{
"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,在构建前自动执行:
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 中注册插件,检测到鸿蒙构建时自动调用同步:
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:
{
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 全局变量注入
css: {
preprocessorOptions: {
scss: {
additionalData: `@use "@/styles/design-tokens-active.scss" as *;\n`
}
}
}
注意:使用
@use ... as *而非@import,避免 Dart Sass 2.0 的废弃警告。
六、完整构建链路
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 → 真机
七、要点总结
- harmony-configs 是官方机制,文件放在
harmony-configs/下会被自动合并到原生工程 - manifest.json 的
app-harmony.distribute字段支持极少(基本只有 icons),权限和包名都无法通过 manifest 配置 - 权限必须在
module.json5中声明,通过harmony-configs/entry/src/main/module.json5注入 - 多产品切换用「模板 + 同步」模式:
harmony-configs/{product}/AppScope/app.json5模板 → 同步脚本 →harmony-configs/AppScope/app.json5 - Vite 插件自动触发同步:
enforce: 'pre'+configResolved,检测到app-harmony平台时自动执行 - 鸿蒙必须禁用代码分割:
inlineDynamicImports: true,否则 IIFE 编译失败
本文整理了基于 HBuilderX 的 uni-app 鸿蒙开发中,配置 bundleName、App 图标和系统权限的完整方案。核心思路是利用 harmony-configs 官方机制,配合 Vite 插件实现自动化,告别手动跑批处理脚本的繁琐流程。