本文面向在鸿蒙(HarmonyOS)场景中移植或运行 Electron 应用的开发者,系统梳理 Electron 中“操作系统类型/平台”检测的五种常用方案,结合本项目的实现细节,给出统一接口、页面展示、预加载容错与主进程兜底策略,并提供适配与安全注意事项。


背景与目标

  • 目标:在 Electron 应用中稳定显示用户设备的操作系统类型与平台信息,并适配鸿蒙容器(例如 ArkWeb/系统 Web 引擎)可能带来的沙箱与权限限制。
  • 痛点:
    • 预加载脚本在部分环境可能无法访问 Node 内置模块(如 os),导致检测失败或页面显示为空。
    • 时序与 CSP(Content Security Policy)可能导致页面在 electronAPI 未就绪时写入“未知”。
    • 跨平台场景下,不同 API 返回值语义各异,需要统一展示与对比。

效果图

鸿蒙PC mac
image-20251110152659778 image-20251110152713618

项目结构与关键文件

  • 本文对应目录: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: falsecontextIsolation: true;仅通过 contextBridge 暴露必要接口。
    • 若 CSP 严格,可在页面添加合理的 meta CSP(包含 unsafe-inline 前先权衡风险),或将脚本外置。
  • 体验优化:
    • 页面采用短轮询 + 主进程兜底,避免首次渲染出现“未知”。
    • 可进一步增加“一键复制平台信息/导出 JSON”按钮,便于问题上报与工单沟通。

运行与验证

  1. 进入目录:ohos_hap/web_engine/src/main/resources/resfile/resources/app
  2. 安装依赖(如需要):npm install
  3. 启动应用:npm start
  4. 验证页面:五种方式与原始字段应正常显示;打开 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"
  }
}

完毕。所以你学会了吗?

Logo

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

更多推荐