一、写在前面

欢迎加入鸿蒙PC开发者社区,共同打造开发者工具生态:鸿蒙PC开发者社区:https://harmonypc.csdn.net/

项目开源地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_beekeeper-studio

欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper

环境搭建文章:https://blog.csdn.net/lbcyllqj/article/details/161286249?sharetype=blogdetail&sharerId=161286249&sharerefer=PC&sharesource=lbcyllqj&spm=1011.2480.3001.8118

这篇文章记录的是一次 Beekeeper Studio 迁移到 OpenHarmony / HarmonyOS PC 的适配实践。

Beekeeper Studio 是一个跨平台 SQL 编辑器和数据库管理工具,桌面端基于 Electron、Vue 2、TypeScript 构建。它不是一个简单的静态页面应用,而是包含数据库连接、连接配置、SQL 编辑器、数据表浏览、插件、主题、通知、内部 SQLite 数据库、主进程和渲染进程通信等一整套桌面软件能力。

这类项目迁移到鸿蒙 PC 时,最容易出现一种误判:应用能启动、页面也不是白屏,于是第一反应会认为“前端资源应该没问题”。但这次遇到的核心问题恰好不是 JS 没加载,也不是 Vue 没挂载,而是页面已经渲染出来了,UI 却像“样式丢失”一样退回到了浏览器默认样式。

最终排查下来,根因在于 OpenHarmony 运行路径没有触发桌面 Electron 原有的设置初始化逻辑,导致 body 上没有写入 theme-light / theme-dark 等主题 class。而 Beekeeper Studio 的大量 SCSS 都依赖 body.theme-lightbody.theme-dark 这类选择器生效,所以看起来就像整个 UI 丢失了一样。

这篇文章会从项目结构、鸿蒙 HAP 构建、真机安装、问题现象、定位过程、代码修复和最终验证几个阶段展开。重点不是简单记录命令,而是复盘一次 Electron 项目在鸿蒙 PC 上“能运行但样式异常”的真实排障过程。

在这里插入图片描述


二、项目背景

Beekeeper Studio 是一个比较典型的现代 Electron 桌面应用。项目整体是 monorepo 结构,主要目录如下:

beekeeper-studio-master/
├── apps/
│   ├── studio/
│   │   ├── src/
│   │   ├── src-commercial/
│   │   ├── vite.config.mjs
│   │   ├── esbuild.mjs
│   │   └── electron-builder-config.js
│   ├── ui-kit/
│   └── sqltools/
├── ohos_hap/
├── package.json
└── yarn.lock

其中 apps/studio 是主应用,包含 Electron 主进程、preload、Vue 渲染进程以及业务逻辑。和这次鸿蒙适配关系最密切的入口主要有:

apps/studio/src-commercial/entrypoints/main.ts
apps/studio/src-commercial/entrypoints/renderer.ts
apps/studio/src-commercial/entrypoints/preload.ts
apps/studio/src/App.vue
apps/studio/src/assets/styles/app.scss
ohos_hap/

桌面端构建时,主进程由 ESBuild 处理,渲染进程由 Vite 处理。迁移到鸿蒙 PC 后,还需要把构建好的 Electron 应用资源同步到 ohos_hap 工程里,再通过 hvigor 生成可安装的 HAP。

本次适配目标不是只让窗口出现,而是要让 Beekeeper Studio 在鸿蒙 PC 上至少具备下面这些基础能力:

  1. HAP 可以正常构建、签名和安装
  2. 应用可以在真实设备上启动
  3. 渲染进程可以加载 Vue 页面
  4. 连接首页可以显示 Demo Database 和 New Connection 面板
  5. Beekeeper Studio 的主题样式能够正常生效
  6. 修复后可以通过真机截图确认效果

在这里插入图片描述


三、构建和同步流程

这个项目使用 Yarn 管理依赖。在当前环境里直接执行 yarn 不一定在 PATH 中,因此使用 corepack yarn 更稳妥。

渲染进程构建命令如下:

corepack yarn workspace beekeeper-studio build:vite

主进程构建命令如下:

corepack yarn workspace beekeeper-studio build:esbuild

构建完成以后,需要把 Electron 产物同步到鸿蒙 HAP 工程:

OHOS_BEEKEEPER_SKIP_BUILD=1 corepack yarn ohos:sync

然后构建并签名 HAP:

OHOS_BEEKEEPER_SKIP_BUILD=1 OHOS_SIGN_HAP=1 corepack yarn ohos:build

最终生成的 HAP 位于:

ohos_hap/electron/build/default/outputs/default/electron-default-signed.hap

安装到真机:

hdc install -r ohos_hap/electron/build/default/outputs/default/electron-default-signed.hap

启动应用:

hdc shell aa force-stop io.beekeeperstudio.ohos
hdc shell aa start -a EntryAbility -b io.beekeeperstudio.ohos -m electron

真机截图可以使用:

hdc shell snapshot_display -f /data/local/tmp/beekeeper_screen.jpeg
hdc file recv /data/local/tmp/beekeeper_screen.jpeg ./beekeeper_screen.jpeg

在这里插入图片描述

四、第一次真机运行:不是白屏,而是 UI 像丢了

应用安装到鸿蒙 PC 真机以后,第一眼看到的画面比较迷惑。

它不是典型的白屏,也不是黑屏。页面上能看到:

  • Beekeeper Studio 标题
  • Demo Database
  • New Connection
  • Connection Type 下拉框
  • Welcome to Beekeeper Studio 提示

这说明几个关键点已经成立:

  1. HAP 本身可以安装
  2. Electron 容器可以启动
  3. 渲染进程 JS 已经执行
  4. Vue 应用已经挂载
  5. 基础数据也能显示出来

但问题是,整个页面看起来像没有经过 Beekeeper Studio 的 UI 系统处理:链接是默认的紫色,下拉框和按钮像浏览器原生样式,布局也缺少正常主题下的视觉层级。

这时候如果只从“资源加载失败”方向看,很容易走偏。因为如果 CSS 文件真的完全没加载,页面表现会更乱;而这里的现象更像是 CSS 载入了,但核心选择器没有命中。


五、先确认不是崩溃,也不是 Vue 没启动

排查这类问题时,第一步要先排除“程序已经崩了”的情况。

通过进程和应用状态可以确认应用仍然在运行:

hdc shell ps -ef | rg 'io.beekeeperstudio|beekeeper|electron'
hdc shell aa dump -a io.beekeeperstudio.ohos

同时,从真机截图看,页面上不仅有静态标题,还有 Demo Database、连接表单、欢迎提示等内容。也就是说,Vue 组件树已经渲染出来了。

这个判断很重要。因为它把问题范围从“启动链路断了”缩小到了“运行时状态或样式作用条件不对”。

接下来重点看 Beekeeper Studio 的样式体系。

apps/studio/src-commercial/entrypoints/renderer.ts 中,渲染入口确实引入了全局样式:

import '@/assets/styles/app.scss'

所以问题不太像是 app.scss 完全没有进入打包产物。继续查看 SCSS 后可以发现,很多样式都挂在主题选择器下面,例如:

body.theme-light {
  ...
}

body.theme-dark {
  ...
}

body.theme-system {
  ...
}

这就给了一个关键方向:如果 body 上没有 theme-lighttheme-dark,那么大量主题样式就不会命中。

在这里插入图片描述


六、问题根因:OpenHarmony 路径没有初始化 settings

继续看 App.vue,可以看到主题 class 是根据 Vuex 里的 settings/themeValue 写到 document.body.className 上的。

原来的逻辑大致是:

if (this.themeValue) {
  document.body.className = `theme-${this.themeValue}`
}

也就是说,只有 themeValue 有值时,body 才会得到 theme-lighttheme-dark

那么 themeValue 从哪里来?继续看渲染进程入口 renderer.ts

桌面 Electron 路径下,settings 初始化发生在渲染进程收到主进程传来的 MessagePort 之后:

window.onmessage = (event) => {
  if (event.source === window && event.data.type === 'port') {
    const [port] = event.ports;
    const { sId } = event.data;

    Vue.prototype.$util.setPort(port, sId);
    app.$store.dispatch('settings/initializeSettings');
  }
}

这套逻辑在桌面 Electron 中是成立的。问题在于,OpenHarmony 适配路径使用的是 fallback utility 连接方式,并不走桌面端这个 MessagePort 分支。

结果就是:

  1. 渲染进程能启动
  2. Vue 能挂载
  3. 页面能显示
  4. settings/initializeSettings 没有在 OpenHarmony 路径下执行
  5. settings/themeValue 没有及时写入
  6. body 上没有 theme-light
  7. SCSS 中依赖主题 class 的样式全部不命中

这就是“UI 看起来丢失了”的真正原因。

它不是资源包没打进去,也不是 Vue 页面没有渲染,而是一个运行时初始化分支在鸿蒙路径下缺了一步。

在这里插入图片描述


七、第一处修复:OpenHarmony 下主动初始化 settings

修复思路很直接:在 OpenHarmony 路径下,不等待桌面端 MessagePort,而是在创建 Vue 实例并设置 utility 后主动初始化 settings。

修改文件:

apps/studio/src-commercial/entrypoints/renderer.ts

修复后的关键代码如下:

const app = new Vue({
  render: h => h(App),
  store,
})

Vue.prototype.$util = utility;
if (window.platformInfo.isOpenHarmony) {
  await app.$store.dispatch('settings/initializeSettings');
}
window.main.attachPortListener();
window.onmessage = (event) => {
  if (event.source === window && event.data.type === 'port') {
    const [port] = event.ports;
    const { sId } = event.data;
    log.log('Received port in renderer with sId: ', sId);

    Vue.prototype.$util.setPort(port, sId);
    app.$store.dispatch('settings/initializeSettings');
  }
}

这段修改的含义是:

  • 桌面 Electron 仍然保留原来的 MessagePort 初始化路径
  • OpenHarmony 下先使用 fallback utility 初始化 settings
  • 尽早让 settings/themeValue 有值
  • 让后续 App.vue 可以正确写入 body.theme-light

这类修复要尽量小心,不要为了鸿蒙路径破坏桌面路径。这里通过 window.platformInfo.isOpenHarmony 做平台判断,避免影响原本的桌面 Electron 行为。

在这里插入图片描述


八、第二处修复:给 App.vue 增加默认 light 主题兜底

只修 renderer.ts 还不够稳。因为 settings 初始化本质上依赖运行时能力,如果后续某次读取设置失败,页面仍然可能回到没有主题 class 的状态。

所以第二处修复是在 App.vue 中给主题写入增加默认兜底:当 themeValue 为空时,默认使用 light

修改文件:

apps/studio/src/App.vue

主题监听器修改为:

themeValue() {
  document.body.className = `theme-${this.themeValue || 'light'}`
  this.trigger(AppEvent.changedTheme, this.themeValue)
}

挂载阶段也改成无条件写入默认主题:

document.body.className = `theme-${this.themeValue || 'light'}`

这一步的意义是给渲染层加一道保护。即使 settings 还没回来,body 上至少也会有 theme-light,不会让整套主题 SCSS 全部失效。

从迁移经验看,这种兜底非常必要。桌面端一些“初始化一定会成功”的隐含前提,到了鸿蒙环境里可能会因为通信方式、沙箱路径、数据库初始化顺序发生变化。UI 层如果没有默认态,就很容易出现这次这种“页面活着,但样式像没了”的问题。

在这里插入图片描述


九、重新构建、同步、签名和安装

修完以后重新走完整构建流程。

先构建渲染进程:

corepack yarn workspace beekeeper-studio build:vite

再构建主进程:

corepack yarn workspace beekeeper-studio build:esbuild

同步到鸿蒙 HAP 工程:

OHOS_BEEKEEPER_SKIP_BUILD=1 corepack yarn ohos:sync

构建签名 HAP:

OHOS_BEEKEEPER_SKIP_BUILD=1 OHOS_SIGN_HAP=1 corepack yarn ohos:build

安装:

hdc install -r ohos_hap/electron/build/default/outputs/default/electron-default-signed.hap

本次构建过程中,Vite 阶段会出现 Sass deprecation warning 和 chunk size warning,ESBuild 阶段也会有个别 external 相关 warning;鸿蒙构建阶段也会有 ArkTS warning。只要最终 HAP 成功产出并安装成功,这些 warning 不影响本次 UI 修复验证。

安装成功后可以看到:

install bundle successfully.
AppMod finish

十、真机验证:主题样式恢复

安装新的 HAP 后,强停并重新启动应用:

hdc shell aa force-stop io.beekeeperstudio.ohos
hdc shell aa start -a EntryAbility -b io.beekeeperstudio.ohos -m electron

等待几秒后截图:

hdc shell snapshot_display -f /data/local/tmp/beekeeper_screen_after.jpeg
hdc file recv /data/local/tmp/beekeeper_screen_after.jpeg ./beekeeper_screen_after.jpeg

再次查看真机画面,可以看到 Beekeeper Studio 的主题 UI 已经恢复:

  • 左侧 sidebar 样式正常
  • New Connection 按钮样式正常
  • Filter 输入框样式正常
  • Connection Type 下拉框样式正常
  • 右下角 Welcome toast 样式正常
  • 页面不再显示默认紫色链接和原生控件外观

这说明 body.theme-light 已经写入,依赖主题 class 的 SCSS 重新命中。

在这里插入图片描述


十一、这次问题容易误判的地方

这次问题比较有代表性,因为它看起来像“CSS 丢了”,但实际不是简单的静态资源加载问题。

如果 CSS 完全没有打进包里,通常会看到更彻底的裸页面,甚至布局也会明显散掉。但这次页面内容、组件层级、数据和通知都在,说明前端主逻辑并没有断。

真正的线索在于样式并不是全部缺失,而是主题相关样式没有命中。Beekeeper Studio 的全局样式体系依赖 body.theme-lightbody.theme-dark 这类 class。如果这个 class 缺失,很多组件就会退回到基础样式甚至浏览器默认样式。

所以定位时不能只问“CSS 有没有加载”,还要继续问:

  1. CSS 是否加载了
  2. 选择器是否命中了
  3. 主题 class 是否存在
  4. 主题值是否从 settings 初始化出来
  5. settings 初始化是否在当前平台路径执行

最终根因正是在第 5 步:桌面 Electron 的 MessagePort 初始化路径没有覆盖 OpenHarmony fallback utility 路径。


十二、迁移经验总结

这次 Beekeeper Studio 的鸿蒙 PC 适配,可以总结出几个经验。

第一,Electron 项目迁移到鸿蒙 PC 时,不能只看页面是不是白屏。页面能显示不代表运行时状态完整,尤其是主题、配置、插件、用户设置这类依赖主进程通信或本地数据库的状态。

第二,桌面端成立的初始化顺序,到了 OpenHarmony 路径下需要重新确认。Beekeeper Studio 桌面端通过 MessagePort 收到 utility port 后初始化 settings,但鸿蒙 fallback utility 不走这个分支,因此需要补一条平台专属初始化路径。

第三,UI 主题必须有默认兜底。像 document.body.className = theme-${themeValue} 这种逻辑,如果 themeValue 为空就不写 class,在桌面端可能长期没问题,但在迁移环境里会放大成整个 UI 异常。

第四,真机截图非常重要。很多 UI 问题只看日志很难判断,但前后截图一对比,基本就能确定是“页面没渲染”“CSS 没加载”还是“主题 class 没命中”。

第五,修复要尽量保持平台隔离。这里没有改掉桌面 Electron 原来的 MessagePort 逻辑,而是在 window.platformInfo.isOpenHarmony 下补充 settings 初始化,这样对原平台影响最小。


十三、最终结果

最终修复后,Beekeeper Studio 已经可以在鸿蒙 PC 真机上正常启动,并显示完整的浅色主题 UI。

本次关键修改有两处:

apps/studio/src-commercial/entrypoints/renderer.ts
apps/studio/src/App.vue

核心修复点如下:

  1. OpenHarmony 渲染路径下主动执行 settings/initializeSettings
  2. App.vue 中主题 class 写入增加 light 默认兜底
  3. 重新构建 Vite 和 ESBuild 产物
  4. 同步到 ohos_hap
  5. 构建签名 HAP 并安装到真机验证

这次适配的问题表面上是“UI 丢失”,本质上是 Electron 桌面运行时和 OpenHarmony 运行时初始化链路不同。把这条链路补齐以后,Beekeeper Studio 的主题系统就能恢复正常,应用也从“能启动但看起来不对”推进到了“真机上具备正常桌面 UI”的状态。


十四、常见问题(FAQ)

下面整理几个在这次适配过程中反复确认、也最容易被问到的问题。

Q1:页面能显示,但样式像丢了,怎么快速判断是不是这次同款问题?

看两点。第一,页面内容、组件层级、数据、通知都在,只是控件退回到浏览器原生外观(紫色链接、原生下拉框),说明前端主逻辑没断,更像是样式选择器没命中而不是资源没加载。第二,去看 body 上有没有 theme-light / theme-dark 这类 class。如果没有,而 SCSS 又大量挂在 body.theme-light 下,基本就能确认是主题 class 缺失导致的。

Q2:为什么不能直接判断成"CSS 没加载"?

如果 CSS 完全没打进包,页面会更彻底地裸掉,连布局都会明显散架。这次布局、数据、组件都在,只是主题相关样式不生效,所以方向应该是"选择器是否命中"“主题 class 是否存在”,而不是"文件有没有加载"。排查时按这条链路逐级往下问:CSS 是否加载 → 选择器是否命中 → 主题 class 是否存在 → 主题值是否初始化 → settings 是否在当前平台执行了初始化。

Q3:根因到底是什么?

桌面 Electron 路径下,settings/initializeSettings 是在渲染进程收到主进程传来的 MessagePort 之后才触发的。但 OpenHarmony 适配走的是 fallback utility 连接方式,不经过这个 MessagePort 分支,于是 settings 没初始化、themeValue 没值、body 没写入主题 class,依赖主题 class 的 SCSS 全部不命中。

Q4:为什么修了 renderer.ts 还要再改 App.vue?只改一处不行吗?

只改 renderer.ts 不够稳。settings 初始化本质依赖运行时能力,如果后续某次读取设置失败,页面仍可能回到没有主题 class 的状态。所以在 App.vue 里加一道默认兜底:themeValue 为空时默认用 light,保证 body 至少有 theme-light,整套主题 SCSS 不会全部失效。一处是修根因,一处是加保护,两者配合。

Q5:这套修复会不会影响桌面 Electron 的原有行为?

不会。renderer.ts 里保留了原来的 MessagePort 初始化路径,只通过 window.platformInfo.isOpenHarmony 判断,额外为鸿蒙路径补一条初始化。App.vue 的兜底逻辑对桌面端也是无害的(桌面端 themeValue 正常有值,|| 'light' 不会被触发)。修复尽量做到平台隔离。

Q6:为什么用 corepack yarn 而不是直接 yarn

这个项目用 Yarn 管理依赖,但当前环境里 yarn 不一定在 PATH 中。corepack yarn 由 Node 自带的 corepack 调起项目指定版本的 Yarn,更稳妥,能避免本机没装或装了错误版本 Yarn 的问题。

Q7:构建过程中那些 warning 要不要处理?

本次可以先不管。Vite 阶段会有 Sass deprecation warning 和 chunk size warning,ESBuild 阶段有个别 external 相关 warning,鸿蒙构建阶段有 ArkTS warning。只要最终 HAP 成功产出并安装成功,这些 warning 不影响本次 UI 修复的验证。当然长期维护时仍建议逐步清理。

Q8:完整的构建到安装顺序是什么?

四步加安装:先 build:vite 构建渲染进程,再 build:esbuild 构建主进程,然后 OHOS_BEEKEEPER_SKIP_BUILD=1 ohos:sync 同步产物到 ohos_hap,再 OHOS_BEEKEEPER_SKIP_BUILD=1 OHOS_SIGN_HAP=1 ohos:build 构建并签名 HAP,最后用 hdc install -r 安装到真机。签名 HAP 产物在 ohos_hap/electron/build/default/outputs/default/electron-default-signed.hap

Q9:为什么这次特别强调真机截图?

主题 class 这类问题只看日志很难判断到底是"页面没渲染"“CSS 没加载"还是"主题 class 没命中”。修复前后各截一张图一对比,现象立刻清楚。截图用 hdc shell snapshot_display 生成,再用 hdc file recv 拉到本地即可。

Q10:从这次适配能总结出哪条最通用的经验?

桌面端那些"初始化一定会成功""通信一定会到位"的隐含前提,到了鸿蒙环境可能因为通信方式、沙箱路径、数据库初始化顺序而变化。所以迁移时不能只看是不是白屏——页面能显示不代表运行时状态完整,尤其是主题、配置、插件、用户设置这类依赖主进程通信或本地数据库的状态,UI 层最好都有默认态兜底。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐