A
Air官方
【Air 系列 · 鸿蒙 PC 适配全记录】10 / 56
■ 应用:轻图示(from DrawIO)(AirDiagram)
■ 定位:流程图/架构图(DrawIO)
■ 上游:DrawIO | 路线:ArkWeb 壳 | 版本:1.0.0
■ 市场状态:开发中,即将上架
■ 官网(免费下载): https://www.shunidc.cn/
————————————————————
开源鸿蒙 PC 社区: https://harmonypc.csdn.net/
PC 社区项目申请: https://atomgit.com/OpenHarmonyPCDeveloper
适配开源地址:(AtomGit 仓库建立后回填)
环境搭建文章:https://blog.csdn.net/weixin_52908342/article/details/161343743
▍一、为什么要适配轻图示
drawio(diagrams.net)是目前使用面最广的开源绘图工具之一,流程图、架构图、UML、网络拓扑、电路示意都可以用它的形状库与连线引擎完成,.drawio XML 格式在工程文档里的事实标准地位短期内没有替代者。它的桌面形态 drawio-desktop 是一个 Electron 壳,内部承载的是一套完整的 webapp(mxGraph 内核加 diagramly 界面层),这套 webapp 本身就是跨平台的,问题只在于桌面集成:文件对话框、文件读写、窗口行为都压在 Electron 的主进程上。
选择 drawio 做鸿蒙 PC 适配,价值在于验证一条低成本路线:当上游是"web 内核 + 厚桌面壳"的 Electron 应用时,不去交叉编译 Electron,也不重写绘图引擎,而是用 ArkWeb 承载原封不动的 webapp,再把 Electron 主进程的桌面能力逐项映射到鸿蒙系统能力上。这个路线零交叉编译,对一个纯前端内核的应用是最短路径。
工程中文名定为"轻图示",包名 com.airdiagram.app,版本 1.0.0,deviceTypes 覆盖 tablet、2in1 与 phone,是一个不含 native .so 的纯 ArkTS 工程。按 PORTING.md 的状态记录,M0 首帧、M1 桌面路径、汉化与 IPC 桥端到端已在 MatePad 11.5 真机完成验证,视觉验收以 DevTools DOM 取证、渲染缓冲像素对比与 OCR 三路替代完成,并明确登记了"尚缺视觉代理级复验"的待办。
▍二、先划清边界:不把编译通过当成适配完成
drawio-desktop 的价值不在 webapp 本身,而在它通过 electron-preload.js 的 contextBridge 暴露给渲染进程的 window.electron 面,以及主进程里 25 个 IPC action 的分发逻辑。如果不接这层桥,webapp 会在浏览器路径下降级运行,文件打开保存、草稿、窗口控制全部失效——那样的"首帧成功"只是把绘图页面当网页看,不是桌面应用。
因此本工程的边界划在:webapp 资源零补丁原样进入 HAP,全部桌面语义由 ArkTS 壳层等价实现。改动收敛在壳层三个文件与一个注入脚本内,上游 JavaScript 一行不改。
层次 原项目实现(drawio-desktop) 鸿蒙侧当前方案
应用入口 Electron 主进程 + BrowserWindow Stage 模型 EntryAbility + ArkTS 页面
页面承载 Chromium webContents ArkWeb Web 组件
桌面桥接 contextBridge 暴露 window.electron + ipcMain 25 action boot.js 伪造同签名对象,经 javaScriptProxy AirDiagram.command(json) 转 DiaBridge 分发
文件对话框 Electron dialog.showOpenDialog/showSaveDialog DocumentViewPicker 系统选择器
文件读写 Node.js fs @ohos.file.fs 沙箱读写,picker URI 持久化授权
静态资源加载 file:// 协议直读本地目录 虚拟域名 https://airdiagram.local + onInterceptRequest 从 rawfile/dia/ 伺服
资源打包 electron-builder files 排除表 + asar tools/package_web_assets.sh 白名单,约 51M 进 rawfile/dia/
剪贴板 Electron clipboard 中继 ArkWeb 自带剪贴板 + READ_PASTEBOARD 权限;clipboardAction 暂为桩
▍三、鸿蒙版的整体架构
整个壳层没有一行 C/C++。EntryAbility 加载 Index.ets,后者创建 Web 组件并做三件关键接线:定制 User-Agent 把 drawio 推上桌面离线路径;javaScriptOnDocumentStart 在文档起点注入 boot.js;onInterceptRequest 把虚拟域名下的所有请求映射到 rawfile。webapp 发出的每一条 IPC 都经 DiaBridge 落到 ArkTS 侧,回包再用 runJavaScript 注回页面。
text
EntryAbility(Stage 模型,无 native .so)
└── Index.ets
├── Web 组件(DESKTOP_UA + boot.js document-start 注入 + onInterceptRequest)
│ ├── drawio webapp v31.4.5(app.min.js / extensions.min.js / stencils.min.js …)
│ ├── window.electron(boot.js 伪造的 contextBridge 同签名面)
│ └── AirDiagram.command(json)(javaScriptProxy 单入口)
├── DiaBridge.ets(Electron IPC 25-action 分发:对话框 / 文件 / 窗口 / 剪贴板)
│ └── 回包经 runJavaScript 注入 window.electron._mainResp(setTimeout 脱离代理栈)
├── DiaAssets.ets(airdiagram.local → rawfile/dia/;local/ → 沙箱工作目录)
└── DocumentViewPicker / @ohos.file.fs(文件链路)
主要目录如下:
text
AirDiagram/
├── AppScope/ # 应用级配置(app.json5:com.airdiagram.app)
├── PORTING.md # 移植档案:状态、架构、资源矩阵、风险登记册
├── ai_log/ # 按日期命名的实施日志
├── entry/src/main/
│ ├── module.json5 # INTERNET 与 READ_PASTEBOARD 权限声明
│ ├── ets/
│ │ ├── entryability/EntryAbility.ets
│ │ ├── entrybackupability/EntryBackupAbility.ets
│ │ ├── dia/DiaBridge.ets # Electron IPC 分发与沙箱文件读写
│ │ ├── dia/DiaAssets.ets # 虚拟域名本地伺服与 MIME 表
│ │ └── pages/Index.ets # Web 宿主、启动屏、安全区Insets
│ └── resources/rawfile/
│ ├── boot.js # document-start 注入的 window.electron 桥
│ └── dia/ # drawio webapp 白名单资源(约 51M)
└── tools/package_web_assets.sh # webapp 瘦身打包脚本
▍四、核心适配过程
### 1. 虚拟域名把 file:// 语义搬进 https 安全上下文
上游 Electron 用 file://webapp/index.html 加载入口,drawio 内部有多处依赖安全上下文的 API 检测。DiaAssets.ets 把所有请求收敛到 https://airdiagram.local,mapUrl() 剥离 query 与 hash 后映射到 rawfile/dia/,并做了父目录穿越防御;以 local/ 开头的路径则落到应用沙箱 files/airdiagram/,用于伺服已保存的图与导出产物。mimeOf() 覆盖 html、js、wasm、字体、图片等类型,其中 wasm 与图片响应不允许追加 charset——给二进制响应加 ; charset=utf-8 会破坏解析,这是同系列工程验证过的坑,代码注释中明确引用。
### 2. 伪造 window.electron 补齐 IPC 契约
boot.js 在 document start 安装与 electron-preload.js 同签名的 window.electron:request 给每条消息编号 reqId 并挂起回调,registerMsgListener/sendMessage/listenOnce 覆盖推送通道。请求经 AirDiagram.command(json) 进入 DiaBridge.onCommand,按 {cmd:'ipc', msg:{action, reqId}} 解包后进入 25 个 action 的 switch 分发(DiaBridge.ets 的 dispatchIpc)。回包方向,ArkTS 用 runJavaScript 调 window.electron._mainResp,按 reqId 唤起挂起的回调。整个 webapp 对此无感知,ElectronApp.js 以为自己仍运行在 Electron 里。
### 3. 文件链路:对话框、读写与 stat 语义逐一对齐
doShowOpenDialog 把上游的 filters 扩展名列表折叠成 DocumentSelectOptions.fileSuffixFilters;doShowSaveDialog 因 DocumentSaveOptions 没有后缀过滤,改用 newFileNames 携带上游默认文件名,缺省为"未命名图.drawio"。saveFile/writeFile/readFile 用 @ohos.file.fs 实现文本与 base64 两种编码;saveFile 成功后回传 fs.statSync 结果,只取 mtimeMs 与 size 两个字段——这是 ElectronApp.js 保存回调实际消费的 stat 面,多给字段没有意义,少给会破坏上游的修改检测。getDocumentsFolder 指向沙箱内创建的 files/Documents。
### 4. UA 双标记与启动参数把 drawio 推上桌面路径
Index.ets 的 DESKTOP_UA 在标准 Chrome UA 后追加 Electron/44.2.0 draw.io/31.4.5,命中 bootstrap.js:72 对 mxIsElectron 的判定(UA 需同时含 electron/ 与 draw.io/),webapp 依序加载 DesktopLibrary.js → ElectronApp.js → extensions.min.js → stencils.min.js 全量桌面栈。入口 URL 按上游 electron.js 的 loadURL 参数逐项复刻:gapi/db/od/gh/gl/tr 全零禁用全部云集成,picker=0 把文件选择压到 window.electron 桥,mode=device 启用触屏 UI,appLang=zh-CN 让编辑器以中文启动。
### 5. 启动屏、安全区与输入法纪律
webapp 的 app.min.js 有 9.2M,冷启动有明显空窗。Index.ets 叠了一层原生启动屏,由页面的 firstFrame 信号(boot.js 观察到 div.geEditor 挂载)撤下,另有 15 秒超时与点击兜底。安全区处理采用"物理像素直传、页面自行缩放"的方案:applyAvoidAreas 读取系统、导航条与挖孔三类 avoid area,pushInsets 调 window.__adSetInsets 由页面缩小 body 盒;代码注释明确记录了直接给 Web 组件加 padding 无效——ArkWeb 渲染仍铺满全窗而命中区缩小,出现渲染与命中错位。输入法侧遵守壳层不拦截的原则:drawio 是文本编辑应用,系统 IME 必须可用,因此不做软键盘抑制。
▍五、适配中遇到的几个难点
### 难点一:伪造桥的异步回包会死在代理调用栈上
javaScriptProxy 的 command() 是同步调用,而 IPC 语义是异步的。如果直接在 command 栈内用 runJavaScript 回推 _mainResp,等于在代理调用未返回时重入 webview,同系列工程(AirOffice)在这里出现过挂死。DiaBridge.defer() 统一用 setTimeout 50ms 把所有回包移出代理栈,reqId 表由页面侧 boot.js 维护,回包丢失可被上游的重试机制兜住。这个时序约束写进了 DiaBridge.ets 的类注释,作为壳层的硬规则。
### 难点二:非真 Electron 环境下 drawio 会滑回在线分支
只改 UA 不够:一旦 webapp 判定云能力缺失或参数不齐,会加载在线分支,行为与离线桌面版分叉。工程用"UA 双标记 + query 云参数全零 + DRAWIO_BASE_URL='.'"三重组合把运行路径钉死在 MODE_DEVICE 桌面态,M1 验收标准是 ElectronApp.js 全量加载不出错。对应地,export3.html 云端导出(convert.diagrams.net)在离线环境不可达,导出走本地 canvas 与 pdf-lib 路径,云参数保留但失败不阻塞。
### 难点三:53M webapp 资源的取舍必须有加载链依据
webapp 原始体积远超 HAP 合理负载。tools/package_web_assets.sh 按三条证据定白名单:electron-builder 自身的 files 排除表、bootstrap.js 加载链勘定、app.min.js 运行时 mxscript 引用扫描。据此剔除了 22M 的 integrate.min.js(Confluence/Jira 集成,加载链零引用)、6.6M 嵌入查看器、42M 源码形态 stencils/(形状已内嵌于 stencils.min.js),语言资源只留中英。最终 rawfile/dia 约 51M,其中 img/ 11M 因 app.min.js 有 173 处引用不可删。getRawFileContentSync 全量读 9.2M 单文件的可行性由同系列工程 9.7M wasm 的先例支撑。
### 难点四:LastWindow 竞态让安全区第一次永远拿不到
aboutToAppear 时机早于 window stage 就绪,window.getLastWindow 会以 17100001 失败,若不处理,页面收不到 insets,状态栏直接压住编辑器工具栏。applyAvoidAreasOnce 以 300ms 间隔最多重试 10 次,拿到窗口后注册 avoidAreaChange 持续刷新,横竖屏切换时挖孔区域换边,因此四边取最大值合并。insets 推送在 onControllerAttached 与 onPageEnd 各做一次,保证页面就绪前后都能收到。
### 难点五:剪贴板权限缺失是静默失败
module.json5 的注释记录了这个坑:API 12 之后,不声明 READ_PASTEBOARD 时读剪贴板不会报错,而是静默返回空 PasteData,上游剪贴板粘贴流程看起来"坏了"却查不到原因。工程声明了 usedScene when inuse 的读剪贴板权限,INTERNET 则仅为 drawio 的导出服务与在线帮助保留,应用本体完全离线运行。
▍六、编译、安装与启动
构建前置是准备 webapp 资源:sparse clone drawio v31.4.5 后执行 tools/package_web_assets.sh(源路径经 AIRDIAGRAM_WEBAPP_SRC 环境变量指定),脚本按白名单把 webapp 复制进 entry/src/main/resources/rawfile/dia/。macOS 上命令行构建:
bash
export JAVA_HOME=/Applications/DevEco-Studio.app/Contents/jbr/Contents/Home
export DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk
hvigorw --mode module -p module=entry assembleHap --no-daemon
产物位于 entry/build/default/outputs/default/entry-default-signed.hap。连接设备后安装并启动,包名为 com.airdiagram.app:
bash
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.airdiagram.app
PORTING.md 记载的部署方式为 devecocli build 配 debug 签名、devecocli run 推到主验证设备 MatePad 11.5;应用内 trace 落 .txt 文件,hilog 只保留 ERROR 级别,避免日志洪水。
▍七、当前功能边界
按 PORTING.md 状态节与当前代码,已覆盖的能力包括:
- 可安装 HAP、Stage 模型 Ability、ArkWeb 宿主与原生启动屏(firstFrame 信号撤屏);
- drawio v31.4.5 webapp 全量桌面路径:ElectronApp.js 加载、MODE_DEVICE 触屏 UI、中文界面;
- Electron IPC 桥端到端:request/reqId 挂起、_mainResp 回推、25 个 action 分发;
- 文件打开/保存对话框(DocumentViewPicker,后缀过滤与默认文件名对齐上游语义);
- readFile/saveFile/writeFile 沙箱读写,文本与 base64 双编码,fileStat 返回 mtimeMs 与 size;
- 虚拟域名本地伺服:MIME 表、CORS 放开、wasm 二进制保真、local/ 沙箱导出目录;
- 安全区四边 insets 适配、系统 IME 直通、<input type=file> 图片导入走系统选择器、window.open 弹窗拦截。
尚未覆盖或处于桩状态的能力(依据 PORTING.md §3 里程碑与 §4 风险登记册):
- saveDraft/getBkpFile/getFileDrafts 草稿链路目前回空值,自动草稿恢复未接;
- clipboardAction 桥为桩,页面内部复制粘贴走 ArkWeb 自带剪贴板,菜单级剪贴板中继未接;
- watchFile/unwatchFile 仅应答不实现,文件变更轮询降级未做;
- 暗黑模式(S11)未做,当前强制 WebDarkMode.Off,后续应走应用自身 View 切换;
- M4 的 S1-S14 逐项对照(菜单触控、性能口径等)与视觉代理级复验仍在待办。
因此当前版本更准确的定位是"轻图示——drawio 鸿蒙 PC 桌面路径基础版":编辑内核是完整且原版的,桌面语义里文件读写与对话框已真实可用,草稿、窗口管理与暗黑等外围能力仍留在这个阶段目标之外。
▍八、总结
这个工程的示范意义在于处理"web 内核 + Electron 壳"类应用时的取舍:不碰 webapp 一行代码,把全部适配精力投入对 Electron 桌面契约的精确复刻——UA 标记、IPC 请求/响应协议、stat 字段面、对话框语义逐项对齐,让上游代码在陌生环境里按既定路径运行。相比交叉编译一条完整桌面栈,这条路线用三个 ArkTS 文件与一个注入脚本换来了可安装、可编辑、可保存的绘图工具。
后续工作的重心在 PORTING.md 的里程碑清单里:M2 文件链路的持久化授权与草稿恢复、M3 剪贴板桥补全、M4 按 S 清单逐项过触控与视觉验收。风险登记册里 R1 的回包时序、R4 的 HAP 体积、R6 的平板手势都是验收时要盯住的项目。每推进一项,仍以真机上 .drawio 文件完整打开与保存往返作为基本验收线。
■ 应用:轻图示(from DrawIO)(AirDiagram)
■ 定位:流程图/架构图(DrawIO)
■ 上游:DrawIO | 路线:ArkWeb 壳 | 版本:1.0.0
■ 市场状态:开发中,即将上架
■ 官网(免费下载): https://www.shunidc.cn/
————————————————————
开源鸿蒙 PC 社区: https://harmonypc.csdn.net/
PC 社区项目申请: https://atomgit.com/OpenHarmonyPCDeveloper
适配开源地址:(AtomGit 仓库建立后回填)
环境搭建文章:https://blog.csdn.net/weixin_52908342/article/details/161343743
▍一、为什么要适配轻图示
drawio(diagrams.net)是目前使用面最广的开源绘图工具之一,流程图、架构图、UML、网络拓扑、电路示意都可以用它的形状库与连线引擎完成,.drawio XML 格式在工程文档里的事实标准地位短期内没有替代者。它的桌面形态 drawio-desktop 是一个 Electron 壳,内部承载的是一套完整的 webapp(mxGraph 内核加 diagramly 界面层),这套 webapp 本身就是跨平台的,问题只在于桌面集成:文件对话框、文件读写、窗口行为都压在 Electron 的主进程上。
选择 drawio 做鸿蒙 PC 适配,价值在于验证一条低成本路线:当上游是"web 内核 + 厚桌面壳"的 Electron 应用时,不去交叉编译 Electron,也不重写绘图引擎,而是用 ArkWeb 承载原封不动的 webapp,再把 Electron 主进程的桌面能力逐项映射到鸿蒙系统能力上。这个路线零交叉编译,对一个纯前端内核的应用是最短路径。
工程中文名定为"轻图示",包名 com.airdiagram.app,版本 1.0.0,deviceTypes 覆盖 tablet、2in1 与 phone,是一个不含 native .so 的纯 ArkTS 工程。按 PORTING.md 的状态记录,M0 首帧、M1 桌面路径、汉化与 IPC 桥端到端已在 MatePad 11.5 真机完成验证,视觉验收以 DevTools DOM 取证、渲染缓冲像素对比与 OCR 三路替代完成,并明确登记了"尚缺视觉代理级复验"的待办。
▍二、先划清边界:不把编译通过当成适配完成
drawio-desktop 的价值不在 webapp 本身,而在它通过 electron-preload.js 的 contextBridge 暴露给渲染进程的 window.electron 面,以及主进程里 25 个 IPC action 的分发逻辑。如果不接这层桥,webapp 会在浏览器路径下降级运行,文件打开保存、草稿、窗口控制全部失效——那样的"首帧成功"只是把绘图页面当网页看,不是桌面应用。
因此本工程的边界划在:webapp 资源零补丁原样进入 HAP,全部桌面语义由 ArkTS 壳层等价实现。改动收敛在壳层三个文件与一个注入脚本内,上游 JavaScript 一行不改。
层次 原项目实现(drawio-desktop) 鸿蒙侧当前方案
应用入口 Electron 主进程 + BrowserWindow Stage 模型 EntryAbility + ArkTS 页面
页面承载 Chromium webContents ArkWeb Web 组件
桌面桥接 contextBridge 暴露 window.electron + ipcMain 25 action boot.js 伪造同签名对象,经 javaScriptProxy AirDiagram.command(json) 转 DiaBridge 分发
文件对话框 Electron dialog.showOpenDialog/showSaveDialog DocumentViewPicker 系统选择器
文件读写 Node.js fs @ohos.file.fs 沙箱读写,picker URI 持久化授权
静态资源加载 file:// 协议直读本地目录 虚拟域名 https://airdiagram.local + onInterceptRequest 从 rawfile/dia/ 伺服
资源打包 electron-builder files 排除表 + asar tools/package_web_assets.sh 白名单,约 51M 进 rawfile/dia/
剪贴板 Electron clipboard 中继 ArkWeb 自带剪贴板 + READ_PASTEBOARD 权限;clipboardAction 暂为桩
▍三、鸿蒙版的整体架构
整个壳层没有一行 C/C++。EntryAbility 加载 Index.ets,后者创建 Web 组件并做三件关键接线:定制 User-Agent 把 drawio 推上桌面离线路径;javaScriptOnDocumentStart 在文档起点注入 boot.js;onInterceptRequest 把虚拟域名下的所有请求映射到 rawfile。webapp 发出的每一条 IPC 都经 DiaBridge 落到 ArkTS 侧,回包再用 runJavaScript 注回页面。
text
EntryAbility(Stage 模型,无 native .so)
└── Index.ets
├── Web 组件(DESKTOP_UA + boot.js document-start 注入 + onInterceptRequest)
│ ├── drawio webapp v31.4.5(app.min.js / extensions.min.js / stencils.min.js …)
│ ├── window.electron(boot.js 伪造的 contextBridge 同签名面)
│ └── AirDiagram.command(json)(javaScriptProxy 单入口)
├── DiaBridge.ets(Electron IPC 25-action 分发:对话框 / 文件 / 窗口 / 剪贴板)
│ └── 回包经 runJavaScript 注入 window.electron._mainResp(setTimeout 脱离代理栈)
├── DiaAssets.ets(airdiagram.local → rawfile/dia/;local/ → 沙箱工作目录)
└── DocumentViewPicker / @ohos.file.fs(文件链路)
主要目录如下:
text
AirDiagram/
├── AppScope/ # 应用级配置(app.json5:com.airdiagram.app)
├── PORTING.md # 移植档案:状态、架构、资源矩阵、风险登记册
├── ai_log/ # 按日期命名的实施日志
├── entry/src/main/
│ ├── module.json5 # INTERNET 与 READ_PASTEBOARD 权限声明
│ ├── ets/
│ │ ├── entryability/EntryAbility.ets
│ │ ├── entrybackupability/EntryBackupAbility.ets
│ │ ├── dia/DiaBridge.ets # Electron IPC 分发与沙箱文件读写
│ │ ├── dia/DiaAssets.ets # 虚拟域名本地伺服与 MIME 表
│ │ └── pages/Index.ets # Web 宿主、启动屏、安全区Insets
│ └── resources/rawfile/
│ ├── boot.js # document-start 注入的 window.electron 桥
│ └── dia/ # drawio webapp 白名单资源(约 51M)
└── tools/package_web_assets.sh # webapp 瘦身打包脚本
▍四、核心适配过程
### 1. 虚拟域名把 file:// 语义搬进 https 安全上下文
上游 Electron 用 file://webapp/index.html 加载入口,drawio 内部有多处依赖安全上下文的 API 检测。DiaAssets.ets 把所有请求收敛到 https://airdiagram.local,mapUrl() 剥离 query 与 hash 后映射到 rawfile/dia/,并做了父目录穿越防御;以 local/ 开头的路径则落到应用沙箱 files/airdiagram/,用于伺服已保存的图与导出产物。mimeOf() 覆盖 html、js、wasm、字体、图片等类型,其中 wasm 与图片响应不允许追加 charset——给二进制响应加 ; charset=utf-8 会破坏解析,这是同系列工程验证过的坑,代码注释中明确引用。
### 2. 伪造 window.electron 补齐 IPC 契约
boot.js 在 document start 安装与 electron-preload.js 同签名的 window.electron:request 给每条消息编号 reqId 并挂起回调,registerMsgListener/sendMessage/listenOnce 覆盖推送通道。请求经 AirDiagram.command(json) 进入 DiaBridge.onCommand,按 {cmd:'ipc', msg:{action, reqId}} 解包后进入 25 个 action 的 switch 分发(DiaBridge.ets 的 dispatchIpc)。回包方向,ArkTS 用 runJavaScript 调 window.electron._mainResp,按 reqId 唤起挂起的回调。整个 webapp 对此无感知,ElectronApp.js 以为自己仍运行在 Electron 里。
### 3. 文件链路:对话框、读写与 stat 语义逐一对齐
doShowOpenDialog 把上游的 filters 扩展名列表折叠成 DocumentSelectOptions.fileSuffixFilters;doShowSaveDialog 因 DocumentSaveOptions 没有后缀过滤,改用 newFileNames 携带上游默认文件名,缺省为"未命名图.drawio"。saveFile/writeFile/readFile 用 @ohos.file.fs 实现文本与 base64 两种编码;saveFile 成功后回传 fs.statSync 结果,只取 mtimeMs 与 size 两个字段——这是 ElectronApp.js 保存回调实际消费的 stat 面,多给字段没有意义,少给会破坏上游的修改检测。getDocumentsFolder 指向沙箱内创建的 files/Documents。
### 4. UA 双标记与启动参数把 drawio 推上桌面路径
Index.ets 的 DESKTOP_UA 在标准 Chrome UA 后追加 Electron/44.2.0 draw.io/31.4.5,命中 bootstrap.js:72 对 mxIsElectron 的判定(UA 需同时含 electron/ 与 draw.io/),webapp 依序加载 DesktopLibrary.js → ElectronApp.js → extensions.min.js → stencils.min.js 全量桌面栈。入口 URL 按上游 electron.js 的 loadURL 参数逐项复刻:gapi/db/od/gh/gl/tr 全零禁用全部云集成,picker=0 把文件选择压到 window.electron 桥,mode=device 启用触屏 UI,appLang=zh-CN 让编辑器以中文启动。
### 5. 启动屏、安全区与输入法纪律
webapp 的 app.min.js 有 9.2M,冷启动有明显空窗。Index.ets 叠了一层原生启动屏,由页面的 firstFrame 信号(boot.js 观察到 div.geEditor 挂载)撤下,另有 15 秒超时与点击兜底。安全区处理采用"物理像素直传、页面自行缩放"的方案:applyAvoidAreas 读取系统、导航条与挖孔三类 avoid area,pushInsets 调 window.__adSetInsets 由页面缩小 body 盒;代码注释明确记录了直接给 Web 组件加 padding 无效——ArkWeb 渲染仍铺满全窗而命中区缩小,出现渲染与命中错位。输入法侧遵守壳层不拦截的原则:drawio 是文本编辑应用,系统 IME 必须可用,因此不做软键盘抑制。
▍五、适配中遇到的几个难点
### 难点一:伪造桥的异步回包会死在代理调用栈上
javaScriptProxy 的 command() 是同步调用,而 IPC 语义是异步的。如果直接在 command 栈内用 runJavaScript 回推 _mainResp,等于在代理调用未返回时重入 webview,同系列工程(AirOffice)在这里出现过挂死。DiaBridge.defer() 统一用 setTimeout 50ms 把所有回包移出代理栈,reqId 表由页面侧 boot.js 维护,回包丢失可被上游的重试机制兜住。这个时序约束写进了 DiaBridge.ets 的类注释,作为壳层的硬规则。
### 难点二:非真 Electron 环境下 drawio 会滑回在线分支
只改 UA 不够:一旦 webapp 判定云能力缺失或参数不齐,会加载在线分支,行为与离线桌面版分叉。工程用"UA 双标记 + query 云参数全零 + DRAWIO_BASE_URL='.'"三重组合把运行路径钉死在 MODE_DEVICE 桌面态,M1 验收标准是 ElectronApp.js 全量加载不出错。对应地,export3.html 云端导出(convert.diagrams.net)在离线环境不可达,导出走本地 canvas 与 pdf-lib 路径,云参数保留但失败不阻塞。
### 难点三:53M webapp 资源的取舍必须有加载链依据
webapp 原始体积远超 HAP 合理负载。tools/package_web_assets.sh 按三条证据定白名单:electron-builder 自身的 files 排除表、bootstrap.js 加载链勘定、app.min.js 运行时 mxscript 引用扫描。据此剔除了 22M 的 integrate.min.js(Confluence/Jira 集成,加载链零引用)、6.6M 嵌入查看器、42M 源码形态 stencils/(形状已内嵌于 stencils.min.js),语言资源只留中英。最终 rawfile/dia 约 51M,其中 img/ 11M 因 app.min.js 有 173 处引用不可删。getRawFileContentSync 全量读 9.2M 单文件的可行性由同系列工程 9.7M wasm 的先例支撑。
### 难点四:LastWindow 竞态让安全区第一次永远拿不到
aboutToAppear 时机早于 window stage 就绪,window.getLastWindow 会以 17100001 失败,若不处理,页面收不到 insets,状态栏直接压住编辑器工具栏。applyAvoidAreasOnce 以 300ms 间隔最多重试 10 次,拿到窗口后注册 avoidAreaChange 持续刷新,横竖屏切换时挖孔区域换边,因此四边取最大值合并。insets 推送在 onControllerAttached 与 onPageEnd 各做一次,保证页面就绪前后都能收到。
### 难点五:剪贴板权限缺失是静默失败
module.json5 的注释记录了这个坑:API 12 之后,不声明 READ_PASTEBOARD 时读剪贴板不会报错,而是静默返回空 PasteData,上游剪贴板粘贴流程看起来"坏了"却查不到原因。工程声明了 usedScene when inuse 的读剪贴板权限,INTERNET 则仅为 drawio 的导出服务与在线帮助保留,应用本体完全离线运行。
▍六、编译、安装与启动
构建前置是准备 webapp 资源:sparse clone drawio v31.4.5 后执行 tools/package_web_assets.sh(源路径经 AIRDIAGRAM_WEBAPP_SRC 环境变量指定),脚本按白名单把 webapp 复制进 entry/src/main/resources/rawfile/dia/。macOS 上命令行构建:
bash
export JAVA_HOME=/Applications/DevEco-Studio.app/Contents/jbr/Contents/Home
export DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk
hvigorw --mode module -p module=entry assembleHap --no-daemon
产物位于 entry/build/default/outputs/default/entry-default-signed.hap。连接设备后安装并启动,包名为 com.airdiagram.app:
bash
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.airdiagram.app
PORTING.md 记载的部署方式为 devecocli build 配 debug 签名、devecocli run 推到主验证设备 MatePad 11.5;应用内 trace 落 .txt 文件,hilog 只保留 ERROR 级别,避免日志洪水。
▍七、当前功能边界
按 PORTING.md 状态节与当前代码,已覆盖的能力包括:
- 可安装 HAP、Stage 模型 Ability、ArkWeb 宿主与原生启动屏(firstFrame 信号撤屏);
- drawio v31.4.5 webapp 全量桌面路径:ElectronApp.js 加载、MODE_DEVICE 触屏 UI、中文界面;
- Electron IPC 桥端到端:request/reqId 挂起、_mainResp 回推、25 个 action 分发;
- 文件打开/保存对话框(DocumentViewPicker,后缀过滤与默认文件名对齐上游语义);
- readFile/saveFile/writeFile 沙箱读写,文本与 base64 双编码,fileStat 返回 mtimeMs 与 size;
- 虚拟域名本地伺服:MIME 表、CORS 放开、wasm 二进制保真、local/ 沙箱导出目录;
- 安全区四边 insets 适配、系统 IME 直通、<input type=file> 图片导入走系统选择器、window.open 弹窗拦截。
尚未覆盖或处于桩状态的能力(依据 PORTING.md §3 里程碑与 §4 风险登记册):
- saveDraft/getBkpFile/getFileDrafts 草稿链路目前回空值,自动草稿恢复未接;
- clipboardAction 桥为桩,页面内部复制粘贴走 ArkWeb 自带剪贴板,菜单级剪贴板中继未接;
- watchFile/unwatchFile 仅应答不实现,文件变更轮询降级未做;
- 暗黑模式(S11)未做,当前强制 WebDarkMode.Off,后续应走应用自身 View 切换;
- M4 的 S1-S14 逐项对照(菜单触控、性能口径等)与视觉代理级复验仍在待办。
因此当前版本更准确的定位是"轻图示——drawio 鸿蒙 PC 桌面路径基础版":编辑内核是完整且原版的,桌面语义里文件读写与对话框已真实可用,草稿、窗口管理与暗黑等外围能力仍留在这个阶段目标之外。
▍八、总结
这个工程的示范意义在于处理"web 内核 + Electron 壳"类应用时的取舍:不碰 webapp 一行代码,把全部适配精力投入对 Electron 桌面契约的精确复刻——UA 标记、IPC 请求/响应协议、stat 字段面、对话框语义逐项对齐,让上游代码在陌生环境里按既定路径运行。相比交叉编译一条完整桌面栈,这条路线用三个 ArkTS 文件与一个注入脚本换来了可安装、可编辑、可保存的绘图工具。
后续工作的重心在 PORTING.md 的里程碑清单里:M2 文件链路的持久化授权与草稿恢复、M3 剪贴板桥补全、M4 按 S 清单逐项过触控与视觉验收。风险登记册里 R1 的回包时序、R4 的 HAP 体积、R6 的平板手势都是验收时要盯住的项目。每推进一项,仍以真机上 .drawio 文件完整打开与保存往返作为基本验收线。
楼主
8 天前