Electron 鸿蒙 PC 上点关闭就真退出了?我花了两天才把“最小化到托盘“做稳
上周把雷达鸭桌面版迁到鸿蒙 PC 上跑同事试了一下当头一棒“点右上角关闭应用直接没了”我愣了一下。在 Windows 和 macOS 上这套关闭到托盘的逻辑跑了小半年从来没出过事。用户点关闭窗口隐藏托盘图标还在右键能退出左键能唤回。结果到鸿蒙 PC 上这一套跟纸糊的一样一戳就破。鸿蒙 PC 的桌面环境对 Electron 来说底层走的是类 Linux 那套。但类 Linux不是就是 Linux很多你以为理所应当的行为它偏偏给你整点惊喜。第一个惊喜是window.on(close, ...)。我原先的代码大概长这样应该很多人都写过// main.tsimport{BrowserWindow,app}fromelectron;letmainWindow:BrowserWindow|nullnull;letisQuittingfalse;functioncreateWindow(){mainWindownewBrowserWindow({width:1200,height:800,webPreferences:{preload:./preload.js,},});mainWindow.on(close,(event){if(!isQuitting){event.preventDefault();mainWindow?.hide();}});}app.on(before-quit,(){isQuittingtrue;});这段代码在 Windows 上没问题。在鸿蒙 PC 上关闭窗口那一瞬间进程直接没了。event.preventDefault()像是被无视了一样。我一开始怀疑是isQuitting状态没传对加了日志发现before-quit根本来不及触发应用就已经在退出了。也就是说鸿蒙 PC 的窗口管理器在某些情况下直接把窗口进程一起带走了不管你 Electron 主进程同不同意。这是第一个坑你不能只依赖close事件。得做两层兜底。我换了个思路把关闭重新定义为最小化而不是去拦截关闭。听起来绕但反而稳了// main.ts —— 重新定义关闭为最小化mainWindow.on(close,(event){if(!isQuitting){event.preventDefault();mainWindow?.hide();}});// 再加一层拦截所有退出意图app.on(before-quit,(event){if(!isQuitting){event.preventDefault();mainWindow?.hide();}});是的我把before-quit也拦截了。有人会觉得这太极端但我实测下来在鸿蒙 PC 上如果不拦这一层用户从任务栏右键关闭窗口或者按 AltF4照样能把进程带走。拦了之后想真正退出只能走托盘菜单里的退出。接下来是托盘图标。这个坑更隐蔽。我原先用的图标路径是./assets/tray-icon.png。在 Windows 开发环境打包后没问题在鸿蒙 PC 上运行托盘区域一片空白连个占位符都没有。Electron 的Tray在 Linux 系列桌面上对图标格式和路径很敏感。我踩了一圈发现两个点第一最好把图标资源放到nativeImage里显式加载不要直接传字符串路径。第二鸿蒙 PC 对 32×32 的 PNG 支持较好但颜色通道最好不带 Alpha 的奇怪预处理否则显示出来是一团黑。// main.ts —— 托盘图标创建兼容鸿蒙 PCimport{Tray,nativeImage,Menu}fromelectron;importpathfrompath;lettray:Tray|nullnull;functioncreateTray(){consticonPathpath.join(process.env.NODE_ENVdevelopment?__dirname:process.resourcesPath,assets,tray-icon2x.png);consticonnativeImage.createFromPath(iconPath).resize({width:32,height:32,});traynewTray(icon);constcontextMenuMenu.buildFromTemplate([{label:显示主窗口,click:(){mainWindow?.show();mainWindow?.focus();},},{type:separator},{label:退出,click:(){isQuittingtrue;app.quit();},},]);tray.setToolTip(雷达鸭);tray.setContextMenu(contextMenu);}注意process.resourcesPath和__dirname的分支。在鸿蒙 PC 打包后的环境里__dirname指向的是应用内部的一个临时挂载点开发和生产的路径规则不一样。我第一次没做这个分支生产包死活找不到图标。还有一个细节nativeImage.createFromPath().resize()这一步最好别省。Electron 在 Linux 托盘里传一个过大或者尺寸不标准的图标有些桌面环境会直接拒绝渲染而不是缩放。我先前传了一个 512×512 的图标进去结果托盘区域一片空白调了半天才发现是尺寸问题。resize 到 32×32 之后至少能保证托盘宿主愿意显示它。然后是最折磨人的第三个坑托盘点击事件。在 Windows 上左键单击托盘图标窗口就弹出来。在 macOS 上习惯是双击。在鸿蒙 PC 上我绑了tray.on(click, ...)结果点烂了图标都没反应。查了半天 Electron 文档看到一段话差点没背过气去“StatusNotifierItem 规范没有规定哪个操作会触发 activation某些桌面环境下 click 事件不会触发。”翻译成人话在类 Linux 桌面上你的左键单击不保证能收到。我最终的兜底方案是左键单击做尝试右键菜单做保底同时监听double-click// main.ts —— 托盘点击兼容层functionbindTrayEvents(){if(!tray)return;// 鸿蒙 PC 上 click 不保证触发尽量别只依赖它tray.on(click,(){toggleWindow();});tray.on(double-click,(){toggleWindow();});// 右键菜单永远稳作为保底tray.on(right-click,(){tray?.popUpContextMenu();});}functiontoggleWindow(){if(!mainWindow){createWindow();return;}if(mainWindow.isVisible()mainWindow.isFocused()){mainWindow.hide();}else{mainWindow.show();mainWindow.focus();}}这一套组合拳上去鸿蒙 PC 上终于能用了。单击不响应的用户至少右键菜单能唤出窗口单击响应的体验跟 Windows 一样。测这个的时候我建议用真机别只在 DevEco Studio 的远程模拟器上跑。模拟器里的托盘行为和真机差异很大我在模拟器上 click 事件正常到真机上就不行白高兴一场。后来凡是涉及系统托盘、窗口生命周期、全局快捷键这类跟桌面环境强相关的功能我都直接上真机验证。说实话这三个坑单独看都不难但堆在一起就特别容易让人怀疑人生。尤其是你明明在 Windows 上验证过一百遍的代码换到鸿蒙 PC 就跟没写一样这种落差很消磨人。我把整个流程串起来写成一个bootstrapMainWindow函数方便以后复制// main.ts —— 完整主进程初始化Electron 鸿蒙 PC 兼容版import{app,BrowserWindow,Tray,Menu,nativeImage}fromelectron;importpathfrompath;letmainWindow:BrowserWindow|nullnull;lettray:Tray|nullnull;letisQuittingfalse;functiongetAssetPath(...parts:string[]){constbaseprocess.env.NODE_ENVdevelopment?__dirname:process.resourcesPath;returnpath.join(base,assets,...parts);}functioncreateWindow(){mainWindownewBrowserWindow({width:1200,height:800,show:false,webPreferences:{preload:getAssetPath(..,preload.js),},});mainWindow.loadURL(https://your-app-url.com);mainWindow.on(ready-to-show,(){mainWindow?.show();});mainWindow.on(close,(event){if(!isQuitting){event.preventDefault();mainWindow?.hide();}});}functioncreateTray(){consticonnativeImage.createFromPath(getAssetPath(tray-icon2x.png)).resize({width:32,height:32});traynewTray(icon);tray.setToolTip(雷达鸭);constmenuMenu.buildFromTemplate([{label:显示主窗口,click:(){mainWindow?.show();mainWindow?.focus();},},{type:separator},{label:退出,click:(){isQuittingtrue;app.quit();},},]);tray.setContextMenu(menu);tray.on(click,()mainWindow?.show());tray.on(double-click,()mainWindow?.show());tray.on(right-click,()tray?.popUpContextMenu());}app.on(before-quit,(event){if(!isQuitting){event.preventDefault();mainWindow?.hide();}});app.on(activate,(){if(!mainWindow){createWindow();}else{mainWindow.show();}});app.whenReady().then((){createWindow();createTray();});有一点我要说明这个方案不是优雅的。它同时处理了 Windows、macOS、Linux、鸿蒙 PC 四者的差异代码里带了明显的 platform-aware 味道。有人可能会说应该按平台拆分得更干净。我同意但那是在你确认每个平台行为都稳定之后的事。在适配鸿蒙 PC 的早期先把功能跑起来比代码漂亮重要一百倍。另外鸿蒙 PC 的应用市场分发机制对 Electron 应用还有一个隐藏影响每次更新后应用的资源路径可能会变。如果你的托盘图标、缓存文件、配置文件都写死了绝对路径升级一次就找不到一次。我用app.getPath(userData)存配置用process.resourcesPath读资源才算把这个问题压住。// 配置文件存到用户数据目录别跟安装包绑死import{app}fromelectron;importpathfrompath;importfsfromfs;constuserDataPathapp.getPath(userData);constconfigPathpath.join(userDataPath,app-config.json);functionreadConfig(){if(!fs.existsSync(configPath))return{};returnJSON.parse(fs.readFileSync(configPath,utf-8));}这么做还有一个好处卸载应用时不会把用户配置一起清掉重新安装后偏好设置还在。对桌面应用来说这比什么花里胡哨的功能都实在。雷达鸭桌面版现在就是这么跑的。点关闭窗口缩到托盘右键托盘能退出也能显示升级后图标和配置都不丢。看起来是桌面应用的基本功但在鸿蒙 PC 上确实让我掉了不少头发。你最近在鸿蒙 PC 上跑 Electron 时有没有遇到过什么明明代码没变行为却变了的诡异问题关于作者老三10 年以上软件开发经验软件设计师、人工智能应用工程师。目前主要做鸿蒙应用ArkTS北向开发和 Web 前端也在折腾 AI 自动化。偶尔在 CSDN 写写鸿蒙和 AI 方向的技术笔记。本文遵循 MIT 协议转载请注明出处。

相关新闻

2026-07-06 GitHub 热点项目精选

2026-07-06 GitHub 热点项目精选

/* 全局样式 */* { margin: 0; padding: 0; box-sizing: border-box; }body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;max-width: 900px; margin: 0 auto; padding: 30px 20px; line-height: 1.7; color: #2d3748;backgro…

2026/7/7 19:40:01阅读更多 →
颠覆性理论刚诞生时,很少有人能看懂它的价值——超导新解:8字驻波电子磁链理论

颠覆性理论刚诞生时,很少有人能看懂它的价值——超导新解:8字驻波电子磁链理论

从事基础物理原创研究这么多年,我深刻体会到一件事:真正能颠覆现有体系、填补理论空白的原创假说,刚面世时几乎都无人重视,甚至会被主流学界下意识否定。 回顾科学史,这种案例比比皆是。魏格纳拿出大陆漂移的完整证据…

2026/7/7 19:35:01阅读更多 →
绝对真理的优先性:形式主义、哥德尔不完备定理与当代学术精神的危机

绝对真理的优先性:形式主义、哥德尔不完备定理与当代学术精神的危机

绝对真理的优先性:形式主义、哥德尔不完备定理与当代学术精神的危机摘要本文从数学实在论立场出发,系统考察了哥德尔不完备定理与绝对真理主张之间的所谓 “张力”。通过对 112 等基本算术真理先验客观性的分析,论证形式公理系统(…

2026/7/7 19:35:01阅读更多 →
curl调用大模型API的本质:HTTP协议、认证与流式响应解析

curl调用大模型API的本质:HTTP协议、认证与流式响应解析

1. 这不是“调用Qwen 3.6”,而是彻底搞懂API调用的本质逻辑 “零成本调用 Qwen 3.6 无限 Token!保姆级教程(curl 实测)”——看到这个标题,我第一反应不是点开,而是把鼠标悬停在链接上,右键复制…

2026/7/7 21:55:56阅读更多 →
SQL BETWEEN操作符避坑指南:日期精度、字符串排序与索引优化

SQL BETWEEN操作符避坑指南:日期精度、字符串排序与索引优化

1. 为什么你写的第一个 BETWEEN 查询就出错了?——一个老SQL人的真实复盘 刚入行那会儿,我给客户写报表,需求是“查2023年入职的所有员工”。我信心满满地敲下 WHERE hire_date BETWEEN 2023-01-01 AND 2023-12-31 ,结果导出的E…

2026/7/7 21:55:56阅读更多 →
Excel数据模型实战:从关系构建到业务落地

Excel数据模型实战:从关系构建到业务落地

1. 为什么普通Excel表格撑不住你的分析需求?——数据模型不是功能,是工作流的底层重构你有没有遇到过这样的场景:销售、库存、客户三张表堆在同一个Excel文件里,想算“华东区高净值客户在Q3复购率”,结果得手动VLOOKUP…

2026/7/7 21:55:56阅读更多 →
OpenClaw Windows本地智能体运行时部署全指南

OpenClaw Windows本地智能体运行时部署全指南

1. 项目概述:这不是一个“安装软件”的操作,而是一次面向生产环境的系统级能力植入OpenClaw 这个名字在公开技术社区中并无权威定义——它不是 Apache、CNCF 或微软官方维护的开源项目,也不在 PyPI、Docker Hub 主流镜像仓库中拥有标准化发布…

2026/7/7 21:55:56阅读更多 →
STM32F103语音导盲硬件+代码+文档全集:超声波测距、方向提示、TTS播报一键运行

STM32F103语音导盲硬件+代码+文档全集:超声波测距、方向提示、TTS播报一键运行

本文还有配套的精品资源,点击获取 简介:基于STM32F103VET6主控的语音导盲系统完整实现包,直接支持超声波实时测距(HC-SR04)、障碍物方位判断(左右双探头逻辑)、中文语音合成播报(…

2026/7/7 21:55:56阅读更多 →
【JAVA毕设源码分享】基于 Vue 和 SpringBoot 的旅游管理系统(程序+文档+代码讲解+一条龙定制)

【JAVA毕设源码分享】基于 Vue 和 SpringBoot 的旅游管理系统(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/7/7 21:50:53阅读更多 →
从GitHub安全案例解析常见漏洞与防护实践

从GitHub安全案例解析常见漏洞与防护实践

1. 项目概述:从GitHub Trending看安全实战 最近在GitHub Trending上看到一个项目,叫 skills4/skills ,它因为一些安全漏洞案例被大家讨论。这其实是一个挺典型的场景:一个旨在展示或教授某种技能的仓库,本身却成了安…

2026/7/7 4:43:43阅读更多 →
MLT 2026启示:因果推理与概率建模驱动下一代LLM应用

MLT 2026启示:因果推理与概率建模驱动下一代LLM应用

# MLT 2026启示:因果推理与概率建模驱动下一代LLM应用## 一、背景与挑战:从“黑箱预测”到“可信推理”2026年6月,第7届机器学习与趋势国际会议(MLT 2026)将在悉尼召开。会议议程中,“因果与可解释机器学习…

2026/7/7 2:56:31阅读更多 →
通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御

通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御

1. 项目概述与漏洞背景最近在梳理一些历史OA系统的安全风险时,通达OA v11.6版本中的一个老漏洞又进入了我的视线。这个漏洞位于/general/bi_design/appcenter/report_bi.func.php文件中,是一个典型的SQL注入点。虽然这个漏洞的利用方式看起来并不复杂&am…

2026/7/7 1:03:28阅读更多 →
Acunetix v24.8 深度解析:DAST漏洞扫描器核心原理与DevSecOps实践

Acunetix v24.8 深度解析:DAST漏洞扫描器核心原理与DevSecOps实践

1. 项目概述:Acunetix v24.8 高级版漏洞扫描器深度解析作为一名在网络安全领域摸爬滚打多年的老兵,我深知一款趁手的“兵器”对于安全测试工作意味着什么。今天要聊的,就是Web应用安全测试领域里一个响当当的名字——Acunetix。特别是其v24.8…

2026/7/7 0:02:41阅读更多 →
国产化信创改造:达梦/人大金仓适配与多数据库兼容方案实战(SpringBoot)

国产化信创改造:达梦/人大金仓适配与多数据库兼容方案实战(SpringBoot)

国产化信创改造:达梦/人大金仓适配与多数据库兼容方案实战(SpringBoot) 🌐 演示地址:http://ruoyioffice.com | 📦 源码1GitHub:ruoyi-office | 📦 源码2GitCode:ruoyi-o…

2026/7/7 0:02:41阅读更多 →
CentOS 7/8 SSH 连接失败:5步系统性排错流程与决策树

CentOS 7/8 SSH 连接失败:5步系统性排错流程与决策树

CentOS SSH连接故障排查:从基础检查到深度修复的完整指南引言当你尝试通过Xshell或其他SSH客户端连接CentOS服务器时,突然遭遇"Connection refused"或"Connection timed out"的错误提示,这种经历对任何运维人员或开发者来…

2026/7/7 0:02:41阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/7 5:11:21阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/7 5:11:21阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/7 5:11:21阅读更多 →