Electron 应用中的系统检测方案对比与鸿蒙适配实践
本文面向在鸿蒙(HarmonyOS)场景中移植或运行 Electron 应用的开发者,系统梳理 Electron 中“操作系统类型/平台”检测的五种常用方案,结合本项目的实现细节,给出统一接口、页面展示、预加载容错与主进程兜底策略,并提供适配与安全注意事项。
背景与目标
- 目标:在 Electron 应用中稳定显示用户设备的操作系统类型与平台信息,并适配鸿蒙容器(例如 ArkWeb/系统 Web 引擎)可能带来的沙箱与权限限制。
- 痛点:
- 预加载脚本在部分环境可能无法访问 Node 内置模块(如
os),导致检测失败或页面显示为空。 - 时序与 CSP(Content Security Policy)可能导致页面在
electronAPI未就绪时写入“未知”。 - 跨平台场景下,不同 API 返回值语义各异,需要统一展示与对比。
- 预加载脚本在部分环境可能无法访问 Node 内置模块(如
效果图
| 鸿蒙PC | mac |
|---|---|
![]() | ![]() |
项目结构与关键文件
- 本文对应目录:
ohos_hap/web_engine/src/main/resources/resfile/resources/app- 预加载:
preload.js - 主进程:
main.js - 页面:
index.html
- 预加载:
五种检测方式(方案对比)
以下代码在预加载脚本中实现,并通过统一接口暴露给页面。每段代码都附带简述与优缺点,便于按需选择。
方式一:process.platform(附架构)
function getOSMethod1() {const platform = process.platform; // 运行平台标识const arch = process.arch; // CPU 架构const osMap = {darwin: 'macOS',win32: 'Windows',linux: 'Linux',freebsd: 'FreeBSD',openbsd: 'OpenBSD',sunos: 'SunOS',aix: 'AIX'};const osName = osMap[platform] || platform;return `${osName} (${arch})`;
}
- 优点:来源于 Node 运行时,稳定、简单;适合快速判断大平台类别。
- 缺点:没有版本信息;在容器环境(如鸿蒙)语义可能是“容器所在平台”。
方式二:os.platform()(附版本与架构)
function getOSMethod2() {const platform = os.platform(); // 与 process.platform 一致const arch = os.arch(); // 架构const release = os.release(); // 系统版本号const osMap = { darwin: 'macOS', win32: 'Windows', linux: 'Linux', freebsd: 'FreeBSD', openbsd: 'OpenBSD', sunos: 'SunOS', aix: 'AIX' };const osName = osMap[platform] || platform;return `${osName} ${release} (${arch})`;
}
- 优点:信息更完整(版本与架构);定位问题更有帮助。
- 缺点:依赖 Node 的
os模块,在沙箱环境可能不可用(后文有容错与兜底)。
方式三:os.type()(原始类型 + 版本 + 主机名)
function getOSMethod3() {const type = os.type(); // Windows_NT / Darwin / Linuxconst arch = os.arch();const release = os.release();const hostname = os.hostname();return `${type} ${release} (${arch}) @ ${hostname}`;
}
- 优点:最接近系统原始名称,便于与内核/发行版信息对照。
- 缺点:同样依赖 Node 的
os模块。
方式四:navigator.platform(Web 标准 API)
function getOSMethod4() {const platform = navigator.platform || '';let osName = 'Unknown';if (platform.includes('Mac')) osName = 'macOS (via Navigator)';else if (platform.includes('Win')) osName = 'Windows (via Navigator)';else if (platform.includes('Linux')) osName = 'Linux (via Navigator)';else if (platform.includes('iPhone') || platform.includes('iPad')) osName = 'iOS (via Navigator)';return `${osName} - ${platform}`;
}
- 优点:前端可用,无需 Node;在受限预加载环境下仍可工作。
- 缺点:浏览器标识可能不严谨(尤其在容器或定制环境)。
方式五:navigator.userAgent(UA 解析)鸿蒙不适用
function getOSMethod5() {const ua = navigator.userAgent || '';if (ua.includes('Mac OS X')) {const match = ua.match(/Mac OS X (\d+[._]\d+[._]\d+)/);return match ? `macOS ${match[1].replace(/_/g, '.')}` : 'macOS (version unknown)';}if (ua.includes('Windows NT')) {const match = ua.match(/Windows NT (\d+\.\d+)/);const map = { '10.0': 'Windows 10/11', '6.3': 'Windows 8.1', '6.2': 'Windows 8', '6.1': 'Windows 7', '6.0': 'Windows Vista', '5.1': 'Windows XP' };return match ? (map[match[1]] || `Windows NT ${match[1]}`) : 'Windows (version unknown)';}if (/Android/i.test(ua)) return 'Android';if (/Linux/i.test(ua)) return 'Linux';if (/iPhone|iPad|iPod/i.test(ua)) return 'iOS';return 'Unknown';
}
- 优点:可以解析到更细的版本信息(某些平台)。
- 缺点:UA 格式不稳定,易受浏览器/容器伪装影响;建议仅作辅助。
统一接口与页面展示
预加载统一接口(同步 + 异步兜底)
文件:app/preload.js
// 暴露到渲染进程的统一接口
contextBridge.exposeInMainWorld('electronAPI', {// 同步:优先用预加载直接计算并返回五种结果getPlatformInfo: () => ({method1: getOSMethod1(),method2: getOSMethod2(),method3: getOSMethod3(),method4: getOSMethod4(),method5: getOSMethod5(),raw: {processPlatform: process.platform,processArch: process.arch,osPlatform: os.platform(),osArch: os.arch(),osRelease: os.release(),hostname: os.hostname(),navigatorPlatform: navigator.platform,userAgent: navigator.userAgent}}),// 异步兜底:如预加载受限,走主进程 IPC 获取 Node 能给出的信息fetchPlatformInfo: () => ipcRenderer.invoke('get-platform-info')
});
页面展示逻辑(短轮询 + 兜底)
文件:app/index.html
<script>window.addEventListener('DOMContentLoaded', () => {const setText = (id, v) => {const el = document.getElementById(id);if (el) el.textContent = v ?? '未知';};const tryFill = () => {const api = window.electronAPI;if (api && typeof api.getPlatformInfo === 'function') {const info = api.getPlatformInfo();setText('p-m1', info.method1);setText('p-m2', info.method2);setText('p-m3', info.method3);setText('p-m4', info.method4);setText('p-m5', info.method5);setText('p-process', info.raw.processPlatform);setText('p-os-platform', info.raw.osPlatform);return true;}return false;};if (tryFill()) return;let attempts = 0;const timer = setInterval(() => {if (tryFill() || ++attempts > 30) clearInterval(timer);}, 100);// 超时兜底:向主进程请求平台信息(Node 能提供的部分)setTimeout(async () => {const api = window.electronAPI;if (api && typeof api.fetchPlatformInfo === 'function') {const info = await api.fetchPlatformInfo();setText('p-m1', info.method1);setText('p-m2', info.method2);setText('p-m3', info.method3);setText('p-process', info.raw.processPlatform);setText('p-os-platform', info.raw.osPlatform);}}, 3500);});
</script>
预加载容错与沙箱设置
os 模块安全加载(避免崩溃)
// preload.js 中:优先 node:os,随后 os,最后提供兜底 stub
let os;
try { os = require('node:os'); }
catch (_) {try { os = require('os'); }catch (e) {console.warn('[preload] os module unavailable, falling back to stubs:', e);os = {platform: () => 'Unavailable',arch: () => process.arch || 'Unavailable',release: () => 'Unavailable',type: () => 'Unavailable',hostname: () => 'Unavailable'};}
}
关闭沙箱(确保预加载可用 Node)
文件:app/main.js
webPreferences: {nodeIntegration: false,contextIsolation: true,sandbox: false, // 明确关闭沙箱preload: path.join(__dirname, 'preload.js')
}
主进程 IPC 兜底
ipcMain.handle('get-platform-info', () => {const method1 = /* 计算 process.platform + arch */;const method2 = /* 计算 os.platform() + release + arch */;const method3 = /* 计算 os.type() + release + arch + hostname */;return {method1, method2, method3,method4: '', method5: '',raw: { processPlatform: process.platform, osPlatform: os.platform() }};
});
鸿蒙平台适配与实践建议
- 首选策略:以 Node 侧的
process.platform/os.platform()/os.type()为主,用浏览器侧navigator.platform/userAgent作为辅助展示与对比。 - 容器环境认知:在鸿蒙容器中,Node 返回值代表“容器宿主平台”;如需识别真实设备类型,应结合业务和容器提供的能力(例如 ArkUI/ArkWeb 的扩展接口或系统服务)。
- 安全与稳定:
- 保持
nodeIntegration: false与contextIsolation: true;仅通过contextBridge暴露必要接口。 - 若 CSP 严格,可在页面添加合理的
metaCSP(包含unsafe-inline前先权衡风险),或将脚本外置。
- 保持
- 体验优化:
- 页面采用短轮询 + 主进程兜底,避免首次渲染出现“未知”。
- 可进一步增加“一键复制平台信息/导出 JSON”按钮,便于问题上报与工单沟通。
运行与验证
- 进入目录:
ohos_hap/web_engine/src/main/resources/resfile/resources/app - 安装依赖(如需要):
npm install - 启动应用:
npm start - 验证页面:五种方式与原始字段应正常显示;打开 DevTools 可查看日志:
[preload] DOMContentLoaded ... info= ...[index] electronAPI available= ... getPlatformInfo()- 如预加载受限,还可见
[index] fetchPlatformInfo()的兜底填充日志。
常见问题(FAQ)
- 预加载提示
module not found: os:确认sandbox: false已设置;预加载中已提供node:os → os → stub的容错,页面仍可显示(少量字段可能为Unavailable),并在 3.5 秒处由主进程补齐。 - 页面全为短横线:通常是
electronAPI未就绪或 CSP 阻止脚本执行。检查控制台日志与 CSP 设置;确认index.html的短轮询与兜底逻辑已启用。 - 鸿蒙设备显示与宿主不一致:这是容器语义差异,建议在产品文档中明确“宿主平台 vs 设备平台”的含义,并在需要时结合系统侧接口做二次识别。
结语
通过“同步统一接口 + 页面短轮询 + 主进程 IPC 兜底 + 预加载容错”的组合,Electron 应用在鸿蒙环境中也能稳定展示平台信息。五种检测方式相互补充,既满足工程可用性,也为定位跨平台问题提供足够的细节。
完整代码
- `app/`- `index.html`:本地页面,集中展示平台信息与五种检测方案结果。- `preload.js`:Electron 预加载脚本,暴露统一接口、执行容错与兜底逻辑。- `main.js`:主进程入口,配置窗口、关闭沙箱、提供 IPC 兜底。- `README.md`、`package.json`、`LICENSE`:说明与依赖。
index.html
<!doctype html>
<html lang="zh-CN"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>平台信息 | Electron × 鸿蒙</title><!-- 页面用于单独展示当前运行平台信息。通过 preload 暴露的 electronAPI 读取数据。 --><style>:root {color-scheme: light dark;}body {margin: 0;font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;background: #0f172a;color: #e5e7eb;display: grid;place-items: center;min-height: 100vh;}.card {width: min(720px, 92vw);background: rgba(255,255,255,0.06);backdrop-filter: blur(6px);border-radius: 16px;padding: 24px 28px;box-shadow: 0 10px 30px rgba(0,0,0,0.35);border: 1px solid rgba(255,255,255,0.12);}h1 {margin: 0 0 12px;font-size: 22px;}.desc {margin: 0 0 18px;color: #cbd5e1;font-size: 14px;}.list {display: grid;gap: 10px;}.item {background: rgba(255,255,255,0.08);border: 1px solid rgba(255,255,255,0.14);padding: 12px 14px;border-radius: 10px;}.k {color: #93c5fd;}code {color: #facc15;font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;}footer {margin-top: 16px;font-size: 12px;color: #94a3b8;}</style></head><body><main class="card" aria-labelledby="title"><h1 id="title">当前系统平台信息</h1><p class="desc">以下信息来自 Electron 预加载脚本,适配鸿蒙场景。</p><section class="list" aria-describedby="title"><div class="item"><span class="k">方式一 · process.platform</span>:<code id="p-m1">-</code></div><div class="item"><span class="k">方式二 · os.platform()</span>:<code id="p-m2">-</code></div><div class="item"><span class="k">方式三 · os.type()</span>:<code id="p-m3">-</code></div><div class="item"><span class="k">方式四 · navigator.platform</span>:<code id="p-m4">-</code></div><div class="item"><span class="k">方式五 · navigator.userAgent</span>:<code id="p-m5">-</code></div><div class="item"><span class="k">原始 · 运行平台(process.platform)</span>:<code id="p-process">-</code></div><div class="item"><span class="k">原始 · 操作系统平台(os.platform())</span>:<code id="p-os-platform">-</code></div></section><footer>Electron 页面本地加载;在鸿蒙容器中值以实际设备/容器为准。</footer></main><script>// 读取由 preload 暴露的 getPlatformInfo(含五种方案),在接口就绪后填充到页面window.addEventListener('DOMContentLoaded', () => {const setText = (id, value) => {const el = document.getElementById(id);if (el) el.textContent = value ?? '未知';};const tryFill = () => {try {const api = window.electronAPI;console.log('[index] electronAPI available=', !!api, 'type=', typeof api?.getPlatformInfo);if (api && typeof api.getPlatformInfo === 'function') {const info = api.getPlatformInfo();console.log('[index] getPlatformInfo()', info);setText('p-m1', info?.method1);setText('p-m2', info?.method2);setText('p-m3', info?.method3);setText('p-m4', info?.method4);setText('p-m5', info?.method5);// 兼容旧占位(如果还存在)setText('p-way1', info?.method3);setText('p-way2', info?.method1);setText('p-way3', info?.method5);setText('p-process', info?.raw?.processPlatform);setText('p-os-platform', info?.raw?.osPlatform);return true;}} catch (e) {console.warn('读取平台信息失败:', e);}return false;};// 立即尝试一次;若未就绪,进行短轮询等待预加载接口暴露if (tryFill()) return;let attempts = 0;const timer = setInterval(() => {if (tryFill() || ++attempts > 30) {clearInterval(timer);}}, 100);// 若最终仍未填充,使用异步兜底从主进程拉取setTimeout(async () => {try {const api = window.electronAPI;if (api && typeof api.fetchPlatformInfo === 'function') {const info = await api.fetchPlatformInfo();console.log('[index] fetchPlatformInfo()', info);setText('p-m1', info?.method1);setText('p-m2', info?.method2);setText('p-m3', info?.method3);// m4/m5 主进程不可用,保留原值setText('p-process', info?.raw?.processPlatform);setText('p-os-platform', info?.raw?.osPlatform);}} catch (e) {console.warn('异步兜底获取平台信息失败:', e);}}, 3500);});</script></body></html>
main.js
const { app, BrowserWindow, Tray, nativeImage, Menu, ipcMain } = require('electron');
const path = require('path');
const os = require('os');let mainWindow, tray;function createWindow() {// 创建系统托盘(注意:确保同级目录有electron_white.png图片)const trayIcon = nativeImage.createFromPath(path.join(__dirname, 'electron_white.png'));tray = new Tray(trayIcon);// 创建托盘右键菜单const trayMenu = Menu.buildFromTemplate([{ label: '显示窗口', click: () => {mainWindow.show(); // 显示窗口mainWindow.focus(); // 聚焦窗口} },{ type: 'separator' }, // 分隔线{ label: '退出应用', click: () => {// 退出前清理托盘和窗口tray.destroy();mainWindow.destroy();app.quit();} }]);tray.setContextMenu(trayMenu); // 绑定菜单到托盘// 创建主窗口mainWindow = new BrowserWindow({width: 800,height: 600,title: 'electron桌面版', // 窗口标题// 可选:添加窗口图标(替换为你的图标路径)// icon: path.join(__dirname, 'window_icon.png'),webPreferences: {nodeIntegration: false, // 禁用node集成(安全最佳实践)contextIsolation: true, // 启用上下文隔离(安全最佳实践)sandbox: false, // 明确关闭沙箱,确保预加载可使用 Node 内置模块// 指定预加载脚本:用于在渲染层安全地注入平台信息到页面preload: path.join(__dirname, 'preload.js')}});// 适配macOS窗口按钮mainWindow.setWindowButtonVisibility(true);// 加载本地页面显示平台信息mainWindow.loadFile(path.join(__dirname, 'index.html'));// 打开开发者工具便于观察预加载与页面日志mainWindow.webContents.openDevTools({ mode: 'detach' });// 窗口关闭时最小化到托盘(而非直接退出)mainWindow.on('close', (e) => {// 仅在用户主动退出时才真正关闭(避免托盘菜单"退出"被拦截)if (app.quitting) {mainWindow = null;} else {e.preventDefault(); // 阻止默认关闭行为mainWindow.hide(); // 隐藏窗口到托盘}});// 点击托盘图标切换窗口显示/隐藏tray.on('click', () => {if (mainWindow.isVisible()) {mainWindow.hide();} else {mainWindow.show();mainWindow.focus();}});// 可选:窗口隐藏时在托盘显示提示mainWindow.on('hide', () => {tray.displayBalloon({title: '应用已最小化',content: '点击托盘图标恢复窗口'});});
}// 应用就绪后创建窗口
app.whenReady().then(createWindow);// 处理不同平台的窗口关闭逻辑
app.on('window-all-closed', () => {// macOS:应用通常在所有窗口关闭后仍保持运行if (process.platform !== 'darwin') {app.quit();}
});// macOS:点击dock图标时重新创建窗口
app.on('activate', () => {if (BrowserWindow.getAllWindows().length === 0) {createWindow();} else {mainWindow.show();}
});// 标记应用是否正在退出(用于区分关闭窗口和退出应用)
app.on('before-quit', () => {app.quitting = true;
});// 主进程:提供平台信息的兜底获取(预加载不可用 Node 内置模块时)
ipcMain.handle('get-platform-info', () => {const method1 = (() => {const platform = process.platform;const arch = process.arch;const map = { darwin: 'macOS', win32: 'Windows', linux: 'Linux', freebsd: 'FreeBSD', openbsd: 'OpenBSD', sunos: 'SunOS', aix: 'AIX' };const name = map[platform] || platform;return `${name} (${arch})`;})();const method2 = (() => {const platform = os.platform();const arch = os.arch();const release = os.release();const map = { darwin: 'macOS', win32: 'Windows', linux: 'Linux', freebsd: 'FreeBSD', openbsd: 'OpenBSD', sunos: 'SunOS', aix: 'AIX' };const name = map[platform] || platform;return `${name} ${release} (${arch})`;})();const method3 = (() => `${os.type()} ${os.release()} (${os.arch()}) @ ${os.hostname()}`)();return {method1,method2,method3,method4: '',method5: '',raw: {processPlatform: process.platform,osPlatform: os.platform()}};
});
preload.js
// 预加载脚本:在渲染层安全地暴露必要 API,并注入平台信息到页面
const { contextBridge, ipcRenderer } = require('electron');
// 尝试加载 Node 内置 os 模块;在沙箱或受限环境下可能不可用
let os;
try {// 优先使用 node:os 前缀(新版本 Node 的推荐形式)// eslint-disable-next-line node/no-missing-requireos = require('node:os');
} catch (_) {try {// 退回传统形式// eslint-disable-next-line node/no-missing-requireos = require('os');} catch (e) {// 最终兜底:提供只读的占位实现,避免预加载脚本崩溃console.warn('[preload] os module unavailable, falling back to stubs:', e);os = {platform: () => 'Unavailable',arch: () => process.arch || 'Unavailable',release: () => 'Unavailable',type: () => 'Unavailable',hostname: () => 'Unavailable'};}
}// —— 五种系统检测方法 ——
// 方法一:process.platform(映射为友好名称并带架构)
function getOSMethod1() {const platform = process.platform;const arch = process.arch;const osMap = {darwin: 'macOS',win32: 'Windows',linux: 'Linux',freebsd: 'FreeBSD',openbsd: 'OpenBSD',sunos: 'SunOS',aix: 'AIX'};const osName = osMap[platform] || platform;return `${osName} (${arch})`;
}// 方法二:os.platform()(附版本与架构)
function getOSMethod2() {const platform = os.platform();const arch = os.arch();const release = os.release();const osMap = {darwin: 'macOS',win32: 'Windows',linux: 'Linux',freebsd: 'FreeBSD',openbsd: 'OpenBSD',sunos: 'SunOS',aix: 'AIX'};const osName = osMap[platform] || platform;return `${osName} ${release} (${arch})`;
}// 方法三:os.type()(更接近系统原始名称)
function getOSMethod3() {const type = os.type();const arch = os.arch();const release = os.release();const hostname = os.hostname();return `${type} ${release} (${arch}) @ ${hostname}`;
}// 方法四:navigator.platform(Web 标准 API)
function getOSMethod4() {try {const platform = navigator.platform || '';let osName = 'Unknown';if (platform.includes('Mac')) osName = 'macOS (via Navigator)';else if (platform.includes('Win')) osName = 'Windows (via Navigator)';else if (platform.includes('Linux')) osName = 'Linux (via Navigator)';else if (platform.includes('iPhone') || platform.includes('iPad')) osName = 'iOS (via Navigator)';return `${osName} - ${platform}`;} catch (e) {return 'Unknown';}
}// 方法五:navigator.userAgent(传统 UA 解析)
function getOSMethod5() {try {const ua = navigator.userAgent || '';// macOSif (ua.includes('Mac OS X')) {const match = ua.match(/Mac OS X (\d+[._]\d+[._]\d+)/);if (match) {const version = match[1].replace(/_/g, '.');return `macOS ${version}`;}return 'macOS (version unknown)';}// Windowsif (ua.includes('Windows NT')) {const match = ua.match(/Windows NT (\d+\.\d+)/);if (match) {const versionMap = {'10.0': 'Windows 10/11','6.3': 'Windows 8.1','6.2': 'Windows 8','6.1': 'Windows 7','6.0': 'Windows Vista','5.1': 'Windows XP'};return versionMap[match[1]] || `Windows NT ${match[1]}`;}return 'Windows (version unknown)';}// Linux / Androidif (/Android/i.test(ua)) return 'Android';if (/Linux/i.test(ua)) return 'Linux';// iOSif (/iPhone|iPad|iPod/i.test(ua)) return 'iOS';return 'Unknown';} catch (e) {return 'Unknown';}
}// 暴露安全的 API 给渲染进程
// 重构:以函数形式提供平台信息,避免时序问题导致页面读取为“未知”
contextBridge.exposeInMainWorld('electronAPI', {// 获取早报数据(保留示例)fetchZaobao: () => ipcRenderer.invoke('fetch-zaobao'),// 获取版本信息versions: {chrome: process.versions.chrome,node: process.versions.node,electron: process.versions.electron},// 同步函数返回平台信息对象(含五种检测方式与原始字段)getPlatformInfo: () => ({method1: getOSMethod1(),method2: getOSMethod2(),method3: getOSMethod3(),method4: getOSMethod4(),method5: getOSMethod5(),// 兼容旧字段(如果页面仍使用 p-way1/2/3)way1: os.type(),way2: process.platform,way3: (typeof navigator !== 'undefined' ? navigator.userAgent : ''),raw: {processPlatform: process.platform,processArch: process.arch,osPlatform: os.platform(),osArch: os.arch(),osRelease: os.release(),hostname: os.hostname(),navigatorPlatform: (typeof navigator !== 'undefined' ? navigator.platform : ''),userAgent: (typeof navigator !== 'undefined' ? navigator.userAgent : '')}}),// 异步兜底:通过主进程获取平台信息(避免预加载受限时失败)fetchPlatformInfo: () => ipcRenderer.invoke('get-platform-info')
});// 将平台信息以浮层形式展示在页面左下角
// 说明:由于主窗口当前加载的是远程页面(Electron 文档站),我们通过 preload 在隔离上下文中
// 操作 DOM 注入一个安全的提示框,不修改站点原有脚本,兼容 CSP。
function injectPlatformBanner() {const info = {processPlatform: process.platform,osPlatform: os.platform(),osType: os.type()};const banner = document.createElement('div');banner.id = 'platform-info-banner';banner.setAttribute('role', 'status');banner.setAttribute('aria-live', 'polite');banner.style.position = 'fixed';banner.style.left = '12px';banner.style.bottom = '12px';banner.style.zIndex = '2147483647';banner.style.background = 'rgba(0,0,0,0.65)';banner.style.color = '#fff';banner.style.fontFamily = 'system-ui, -apple-system, Segoe UI, Roboto, sans-serif';banner.style.fontSize = '12px';banner.style.lineHeight = '1.5';banner.style.padding = '10px 12px';banner.style.borderRadius = '8px';banner.style.boxShadow = '0 2px 8px rgba(0,0,0,0.2)';banner.style.backdropFilter = 'blur(4px)';banner.innerHTML = ['<strong>系统平台信息</strong>',`运行平台(process.platform):<code>${info.processPlatform}</code>`,`操作系统平台(os.platform()):<code>${info.osPlatform}</code>`,`操作系统类型(os.type()):<code>${info.osType}</code>`].join('<br/>');// 防止重复注入if (!document.getElementById('platform-info-banner')) {document.body.appendChild(banner);}
}// 等待文档就绪再注入,避免页面未加载完成无法插入
window.addEventListener('DOMContentLoaded', () => {try {// 优先填充本地页面占位元素(若存在),否则注入远程页面浮层const placeholders = ['p-m1','p-m2','p-m3','p-m4','p-m5','p-process','p-os-platform','p-way1','p-way2','p-way3'];const hasPlaceholders = placeholders.some(id => document.getElementById(id));const info = {method1: getOSMethod1(),method2: getOSMethod2(),method3: getOSMethod3(),method4: getOSMethod4(),method5: getOSMethod5(),raw: {processPlatform: process.platform,osPlatform: os.platform()}};const setText = (id, value) => {const el = document.getElementById(id);if (el) el.textContent = value ?? '未知';};console.log('[preload] DOMContentLoaded. hasPlaceholders=', hasPlaceholders, 'info=', info);if (hasPlaceholders) {// 新版页面占位setText('p-m1', info.method1);setText('p-m2', info.method2);setText('p-m3', info.method3);setText('p-m4', info.method4);setText('p-m5', info.method5);// 兼容旧版占位setText('p-way1', info.method1);setText('p-way2', info.method2);setText('p-way3', info.method3);setText('p-process', info.raw.processPlatform);setText('p-os-platform', info.raw.osPlatform);} else {injectPlatformBanner();}} catch (e) {console.error('[preload] 注入平台信息失败:', e);}
});
package.json
{"name": "my-electron-app","version": "1.1.0","main": "main.js","scripts": {"start": "electron .","test": "echo \"Error: no test specified\" && exit 1"},"keywords": [],"author": "","license": "ISC","description": "","devDependencies": {"electron": "^39.1.1"}
}
完毕。所以你学会了吗?


