uni-app 鸿蒙 App 白屏问题:从零到一的排查与修复全记录
项目:Vue 3 + uni-app 3.0 小程序/App,编译目标新增 HarmonyOS App
环境:HBuilderX 5.x + DevEco Studio 5.0 + 鸿蒙真机
一、环境配置
最低要求
- HBuilderX 4.24+ (鸿蒙支持最低版本)
- DevEco Studio 5.0.3.400+
- uni-app 依赖:@dcloudio/uni-app 3.0+、@dcloudio/uni-app-harmony
- Vue:3.x(鸿蒙仅支持 Vue 3,不支持 Vue 2)
- manifest.json 必须声明 "vueVersion": "3"
编译流程
HBuilderX 源码编译 → 生成 dist/dev/app-harmony/ 鸿蒙工程 → DevEco Studio Open → Build → Run 到真机。
关键配置文件
| 文件 | 作用 |
|---|---|
| vite.config.js | Vite 编译配置,@dcloudio/vite-plugin-uni 插件 |
| manifest.json | 含 vueVersion: "3"、appid 等 |
| pages.json | 页面路由、tabBar、subPackages |
| dist/dev/app-harmony/build-profile.json5 | 鸿蒙签名配置(勿删) |
二、故障现象
App 启动后纯白屏,没有任何内容渲染。DevEco HILOG 关键错误:
ERR_FILE_NOT_FOUND
nweb_value_convert.cc:280 AddNWebValueCef: not support value
nweb_value_convert.cc:242 AddNWebValueCef: VTYPE_LIST
早期还伴随:
HBuilderX: "目前 vue 2 项目尚不支持鸿蒙平台" (编译缓存误报)
vite build: Invalid value "iife" for option "output.format" (代码分割冲突)
三、排查过程
3.1 环境验证:创建最简 helloworld 工程
创建一个只含 1 个页面的最小 uni-app 项目:
test-hello/
index.html
vite.config.js
src/
manifest.json ← "vueVersion": "3"
pages.json ← 1 page
main.ts ← createSSRApp
App.vue ← <script setup> Composition API
pages/index/index.vue ← Hello World
结果:正常运行。由此排除工具链和 DevEco SDK 本身的问题。
3.2 编译缓存误报:"vue 2 项目尚不支持鸿蒙"
即使 manifest.json 明确声明 "vueVersion": "3",HBuilderX 仍报此错误。
原因:升级 HBuilderX 后,旧的编译缓存被污染,编译器误将 Vue 3 项目识别为 Vue 2。
解决:每次重新编译时勾选「清空缓存并重新编译」,同时删除 dist/dev/app-harmony/ 和 node_modules/ 目录。
3.3 IIFE 编译冲突
原项目 vite.config.js 中 build.rollupOptions.output.format = 'es',而 HBuilderX 内置编译器强制 IIFE 格式。项目使用了动态 import()(markdown-it、katex、highlight.js),IIFE 格式不支持代码分割,编译直接报错。
解决:在 vite.config.js 添加 configResolved 钩子,鸿蒙平台设置 inlineDynamicImports: true 并删除 manualChunks,将所有动态导入内联为单一文件。
// 插件关键代码
config.build.rollupOptions.output.inlineDynamicImports = true
delete config.build.rollupOptions.output.manualChunks
3.4 Bridge 序列化失败:VTYPE_LIST
这是最核心的故障。nweb_value_convert.cc 是 uni-app 鸿蒙运行时的 C++ 桥接层,负责 JSVM 与 WebView 之间的通信。VTYPE_LIST 错误表示桥接层在序列化一个数组时失败,无法将数据传递给 WebView 渲染。
逐步缩小范围
试探 1:去掉 tabBar → 仍白屏 试探 2:去掉 subPackages → 仍白屏 试探 3:仅保留 1 个页面 → 正常 试探 4:2 个页面 → 正常 试探 5:3 个页面(含 favorite) → 白屏 试探 6:3 个页面(含 plan_list,换一个页面) → 正常 试探 7:同一目录下移走 practice.vue → 正常
定位到具体代码
经过逐文件、逐导入的二分排查,最终锁定根因:
uni.getSystemInfoSync() 调用会通过 bridge 获取系统信息,在鸿蒙运行时的桥接层触发序列化失败的连锁反应。
项目中 39 处调用了 uni.getSystemInfoSync(),几乎每个页面都在 onLoad / onMounted / 模块顶层获取状态栏高度。
3.5 自动登录失败
日志显示 Token: NOT_FOUND,deviceAutoLogin 从未被调用。
原因:App.vue 中 deviceAutoLogin 的导入仅包裹在 #ifdef APP-PLUS 条件编译中,缺少 #ifdef APP-HARMONY 分支。
解决:添加 APP-HARMONY 条件导入。
3.6 MarkdownRender 组件 Bundle 过大
收藏中心页面引用了 MarkdownRender 组件,该组件静态导入了 3 个重型库:
import MarkdownIt from 'markdown-it'
import hljs from './hljs-langs'
import katex from 'katex'
即使使用 inlineDynamicImports,这三个库仍然会被打入 app-service.js,导致单文件体积过大(1MB+),触发桥接层数据量上限。
解决:修改 MarkdownRender.vue,用 #ifndef APP-HARMONY 包裹这三个导入,鸿蒙端改为简化的纯文本渲染。
四、最终原因总结
问题 1:HBuilderX 误报 Vue 2 根因:编译缓存污染 | 严重程度:低
问题 2:IIFE 编译错误 根因:动态 import 与 IIFE 格式冲突 | 严重程度:中
问题 3:Bridge VTYPE_LIST 白屏(核心) 根因:uni.getSystemInfoSync() 触发桥接序列化失败 | 严重程度:高
问题 4:自动登录失败 根因:deviceLogin.js 未在 HARMONY 下导入 | 严重程度:中
问题 5:收藏中心白屏 根因:MarkdownRender 的 3 个重型库 bundle 过大 | 严重程度:中
问题 6:页面点击无反应 根因:目标页面未注册到 pages.json | 严重程度:低
核心结论:鸿蒙 App 白屏的根本原因是 uni.getSystemInfoSync() 在 JSVM → WebView 桥接层触发了 CEF 值序列化失败(VTYPE_LIST)。所有页面必须将 getSystemInfoSync 替换为条件编译,鸿蒙端使用固定默认值。
五、修复方案汇总
5.1 通用修复模式(每个页面)
// #ifndef APP-HARMONY
const systemInfo = uni.getSystemInfoSync()
statusBarHeight.value = systemInfo.statusBarHeight || 0
// ... 其他平台逻辑 ...
// #endif
// #ifdef APP-HARMONY
statusBarHeight.value = 44 // 鸿蒙默认状态栏高度
tabBarHeight.value = 65 // 鸿蒙默认 tabBar 高度
// #endif
5.2 vite.config.js 修改
添加 harmony-fix-iife 插件,configResolved 阶段设置 inlineDynamicImports: true,同时删除 manualChunks。
5.3 App.vue 修改
添加 APP-HARMONY 的 deviceAutoLogin 导入和 hideTabBar 调用。
5.4 MarkdownRender 组件修改
用 #ifndef APP-HARMONY 包裹 markdown-it / katex / hljs 的导入和使用,鸿蒙端使用 plainTextParse() 纯文本渲染。
5.5 清理编译缓存
每次重大改动后,只删除资源文件(保留签名配置):
Remove-Item -Recurse -Force dist/dev/app-harmony/entry/src/main/resources/resfile
Remove-Item -Recurse -Force node_modules
HBuilderX 同时勾选「清空缓存并重新编译」。
5.6 鸿蒙上 getSystemInfoSync 替代方案
- CSS 环境变量:env(safe-area-inset-top, 44px),零 bridge 调用,WebView 原生支持
- 异步版延迟调用:uni.getSystemInfo() 放在 onReady 中调用,避免初始化阶段炸桥
- 硬编码默认值:鸿蒙设备状态栏高度基本统一为 44px
六、排查方法论
第一步:创建最简 helloworld 验证环境是否可用 第二步:逐页添加回项目,找到断点(本项目 2 页 OK,3 页挂) 第三步:逐文件、逐导入注释,定位具体模块 第四步:逐目录排查,改名或移走文件排除干扰 第五步:添加诊断日志精确到代码行 第六步:所有修改用条件编译隔离,只影响鸿蒙平台
本文记录了从零开始在 uni-app + Vue 3 项目中适配 HarmonyOS App 的完整过程,核心发现是 uni.getSystemInfoSync() 在鸿蒙桥接层的兼容性问题。希望对同样踩坑的开发者有所帮助。