Electron 应用中的系统检测方案对比与鸿蒙适配实践
在 Electron 应用中稳定显示用户设备的操作系统类型与平台信息,并适配鸿蒙容器(例如 ArkWeb/系统 Web 引擎)可能带来的沙箱与权限限制。
·
本文面向在鸿蒙(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 / Linux
const 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-require
os = require('node:os');
} catch (_) {
try {
// 退回传统形式
// eslint-disable-next-line node/no-missing-require
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'
};
}
}
// —— 五种系统检测方法 ——
// 方法一: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 || '';
// macOS
if (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)';
}
// Windows
if (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 / Android
if (/Android/i.test(ua)) return 'Android';
if (/Linux/i.test(ua)) return 'Linux';
// iOS
if (/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"
}
}
完毕。所以你学会了吗?
更多推荐






所有评论(0)