【前端】【Electron】Electron 知识点详解,看着一篇文章就够了

06-01 1334阅读

Electron 知识点详解


第一章:Electron 入门与核心概念

  1. 什么是 Electron?

    • 定义:一个使用 Web 技术 (HTML, CSS, JavaScript) 构建跨平台桌面应用程序的开源框架。
    • 核心组成:Chromium (用于渲染界面) + Node.js (用于访问操作系统和后端能力) + 自定义 APIs。
    • 目标:让 Web 开发者能够轻松创建功能丰富的桌面应用。
    • 为什么选择 Electron?

      • 跨平台: 一套代码库,可构建 Windows, macOS, Linux 应用。
      • Web 技术栈: 复用现有的 Web 开发知识和生态系统 (NPM 包、框架如 Vue/React/Angular)。
      • 快速开发: 利用 Web 技术的开发效率。
      • 强大的能力: 可以访问完整的操作系统 API (通过 Node.js) 和 Electron 提供的原生 API。
      • 成熟的社区和生态: 广泛使用 (VS Code, Slack, Discord 等),拥有丰富的文档和第三方库。
      • Electron 的主要挑战/缺点:

        • 包体积大: 每个应用都内嵌了 Chromium 和 Node.js,导致基础包体积较大 (几十 MB 到上百 MB)。
        • 内存占用: 相较于原生应用,内存占用可能更高。
        • 性能: 对于极其注重性能的场景,可能不如原生应用。
        • 安全风险: 如果不注意,将 Node.js 能力暴露给渲染进程可能带来安全风险。
        • 核心架构:主进程 (Main Process) 与渲染进程 (Renderer Process)

          • 主进程 (Main Process):
            • 唯一,程序的入口点 (main.js 或 package.json 中指定的入口文件)。
            • 拥有完整的 Node.js 环境。
            • 负责管理应用的生命周期、创建和管理 BrowserWindow (渲染进程)、处理原生操作系统交互 (菜单、对话框、托盘等)。
            • 不负责渲染 HTML/CSS。
            • 渲染进程 (Renderer Process):
              • 每个 BrowserWindow 实例拥有一个独立的渲染进程。
              • 本质上是一个 Chromium 浏览器窗口环境,负责渲染 HTML, CSS, 执行 JavaScript (UI 逻辑)。
              • 默认情况下不能直接访问 Node.js API 或操作系统资源 (出于安全考虑)。
              • 可以通过特定的机制 (IPC, Preload Script) 与主进程通信以获取系统能力。
              • 理解进程模型是掌握 Electron 的关键。

第二章:环境搭建与基础项目

  1. 环境要求:

    • Node.js (自带 npm 或使用 yarn)
    • 代码编辑器 (如 VS Code)
    • 创建基础项目:

      • 创建项目目录:mkdir my-electron-app && cd my-electron-app
      • 初始化 npm 项目:npm init -y
      • 安装 Electron:npm install --save-dev electron
      • 创建入口文件 (main.js) 和界面文件 (index.html)。
      • package.json 关键配置:

        • "main": 指定主进程入口文件 (e.g., "main": "main.js")。
        • "scripts":
          • "start": "electron .": 定义启动应用的命令。
          • main.js (主进程) 基础代码:

            • 引入 app 和 BrowserWindow 模块:const { app, BrowserWindow } = require('electron')
            • 创建窗口函数 createWindow()。
            • 在 app 的 ready 事件触发时调用 createWindow()。
            • 加载 HTML 文件:win.loadFile('index.html') 或 win.loadURL('http://localhost:3000') (用于加载开发服务器)。
            • 处理应用生命周期事件 (如 window-all-closed, activate)。
            • index.html (渲染进程) 基础代码:

              • 标准的 HTML 结构。
              • 可以通过 引入渲染进程的 JavaScript 文件。
              • 启动与调试:

                • 启动应用:npm start
                • 打开开发者工具:在 BrowserWindow 实例上调用 win.webContents.openDevTools()。

第三章:主进程 (Main Process) 详解

  1. app 模块:

    【前端】【Electron】Electron 知识点详解,看着一篇文章就够了
    (图片来源网络,侵删)
    • 控制应用程序的事件生命周期。
    • 常用事件:ready, window-all-closed, activate, before-quit, will-quit。
    • 常用方法:app.quit(), app.getPath(name) (获取系统路径), app.getName(), app.getVersion(), app.isPackaged。
    • BrowserWindow 模块:

      • 创建和控制浏览器窗口。
      • 构造函数选项 (new BrowserWindow({...})):
        • width, height: 窗口尺寸。
        • x, y: 窗口位置。
        • frame: 是否显示窗口边框和标题栏。
        • show: 创建时是否立即显示。
        • webPreferences: 配置网页功能的关键选项 (见下)。
        • 实例方法:win.loadURL(), win.loadFile(), win.close(), win.show(), win.hide(), win.maximize(), win.minimize(), win.isMaximized(), win.webContents (访问 WebContents 对象)。
        • 实例事件:closed, focus, blur, resize, move。
        • webPreferences 选项 (在 BrowserWindow 中配置):

          【前端】【Electron】Electron 知识点详解,看着一篇文章就够了
          (图片来源网络,侵删)
          • nodeIntegration (boolean, 默认 false): 是否在渲染进程中启用 Node.js 集成。强烈建议保持 false 以提高安全性。
          • contextIsolation (boolean, 默认 true): 是否启用上下文隔离。强烈建议保持 true。这使得 preload 脚本和渲染进程的 JavaScript 运行在不同的上下文中,更安全。
          • preload (string): 指定一个预加载脚本的路径。该脚本在渲染进程加载网页之前运行,并且可以访问 Node.js API (即使 nodeIntegration: false) 和 DOM API。这是连接主进程和渲染进程、安全暴露特定 Node.js 功能的关键。
          • sandbox (boolean, 默认 false): 是否启用 Chromium OS 级别的沙盒。

第四章:渲染进程 (Renderer Process) 详解

  1. 角色:

    【前端】【Electron】Electron 知识点详解,看着一篇文章就够了
    (图片来源网络,侵删)
    • 负责展示用户界面 (HTML/CSS)。
    • 执行用户界面的交互逻辑 (JavaScript)。
    • 运行标准的 Web API (Fetch, DOM 操作, Canvas 等)。
    • 访问 Node.js (不推荐直接开启 nodeIntegration):

      • 安全隐患: 如果 nodeIntegration: true,渲染进程中的任何脚本 (包括第三方库) 都可以访问文件系统、执行命令等,容易受到 XSS 攻击影响。
      • 推荐方式: 使用 preload 脚本 + contextBridge。
      • preload.js 脚本:

        • 在 webPreferences 中通过 preload 选项指定。
        • 运行在具有 Node.js 环境但与渲染器隔离的上下文中 (当 contextIsolation: true)。
        • 可以访问 window 和 document 对象。
        • 主要用途:
          • 使用 contextBridge.exposeInMainWorld(apiKey, apiObject) 安全地向渲染进程暴露选择性的 Node.js 功能或 IPC 调用接口。
          • 监听来自主进程的 IPC 消息。
          • renderer.js (渲染进程脚本):

            • 通过 标签在 HTML 中引入。
            • 负责 DOM 操作、事件处理、调用 preload 脚本暴露的 API。
            • 如果使用了 contextBridge,可以通过 window[apiKey] 访问暴露的接口。

第五章:进程间通信 (Inter-Process Communication - IPC)

  1. 为什么需要 IPC?

    • 主进程和渲染进程是独立的进程,需要一种机制来传递消息和数据。
    • 渲染进程需要请求主进程执行特权操作 (如读写文件、显示原生对话框)。
    • 主进程需要通知渲染进程更新 UI 或传递数据。
    • 主要模块:

      • ipcMain (在主进程中使用)
      • ipcRenderer (在渲染进程或 preload 脚本中使用)
      • contextBridge (在 preload 脚本中使用,用于安全暴露 API)
      • 通信模式:

        • 渲染进程 -> 主进程 (单向):
          • 渲染进程 (preload 或 renderer): ipcRenderer.send(channel, ...args)
          • 主进程: ipcMain.on(channel, (event, ...args) => { ... })
          • 渲染进程 -> 主进程 -> 渲染进程 (双向异步,请求/响应):
            • 渲染进程 (preload 或 renderer): const result = await ipcRenderer.invoke(channel, ...args)
            • 主进程: ipcMain.handle(channel, async (event, ...args) => { ...; return result; })
            • 主进程 -> 渲染进程 (单向):
              • 主进程 (需要 webContents 对象): win.webContents.send(channel, ...args)
              • 渲染进程 (preload 或 renderer): ipcRenderer.on(channel, (event, ...args) => { ... })
              • 安全 IPC 的最佳实践 (使用 contextBridge):

                • main.js: 使用 ipcMain.handle 或 ipcMain.on 处理来自渲染进程的请求。
                • preload.js:
                  const { contextBridge, ipcRenderer } = require('electron');
                  contextBridge.exposeInMainWorld('electronAPI', {
                      // 暴露一个调用主进程函数的接口
                      doSomething: (data) => ipcRenderer.invoke('do-something', data),
                      // 暴露一个监听主进程消息的接口
                      onUpdateCounter: (callback) => ipcRenderer.on('update-counter', (_event, value) => callback(value)),
                      // 需要注意移除监听器以防内存泄漏
                      removeAllListeners: (channel) => ipcRenderer.removeAllListeners(channel)
                  });
                  
                • renderer.js:
                  // 调用暴露的函数
                  const result = await window.electronAPI.doSomething('some data');
                  console.log(result);
                  // 监听暴露的事件
                  window.electronAPI.onUpdateCounter((value) => {
                      console.log('Counter updated:', value);
                  });
                  // 在组件卸载或页面关闭时清理监听器
                  // window.electronAPI.removeAllListeners('update-counter');
                  

第六章:原生 UI 元素

  1. 应用程序菜单 (Menu):

    • 创建自定义的顶部应用程序菜单 (File, Edit, View 等)。
    • 创建上下文菜单 (右键菜单)。
    • 使用 Menu.buildFromTemplate(template) 创建菜单。
    • template 是一个包含菜单项对象的数组 (e.g., { label: 'File', submenu: [...] }, { label: 'Quit', role: 'quit' }, { type: 'separator' })。
    • 通过 Menu.setApplicationMenu(menu) 设置应用菜单。
    • 通过 win.webContents.on('context-menu', ...) 弹出上下文菜单 (menu.popup())。
    • role 属性可以快速创建标准菜单项 (如 undo, redo, cut, copy, paste, quit, toggledevtools)。
    • 对话框 (dialog):

      • 显示原生的系统对话框。
      • dialog.showOpenDialogSync() / dialog.showOpenDialog(): 文件/文件夹选择框。
      • dialog.showSaveDialogSync() / dialog.showSaveDialog(): 文件保存框。
      • dialog.showMessageBoxSync() / dialog.showMessageBox(): 消息提示框 (info, warning, error, question)。
      • dialog.showErrorBox(): 显示错误信息框。
      • 注意: dialog 模块只能在主进程中使用,渲染进程需要通过 IPC 调用。
      • 系统托盘 (Tray):

        • 在操作系统的通知区域 (系统托盘) 创建图标。
        • new Tray('/path/to/icon.png') 创建实例。
        • tray.setToolTip('Tooltip text') 设置鼠标悬停提示。
        • tray.setContextMenu(menu) 设置右键菜单。
        • 监听点击事件 (click, right-click 等)。
        • 原生通知 (Notification):

          • 显示操作系统的原生通知。
          • new Notification({ title: 'Title', body: 'Body text' }).show()
          • 可以在主进程或支持的渲染进程中使用 (需要用户授权)。

第七章:系统集成与常用 API

  1. 访问文件系统 (Node.js fs 模块):

    • 在主进程或通过 preload 脚本安全暴露给渲染进程。
    • fs.readFile(), fs.writeFile(), fs.mkdir(), fs.readdir(), etc.
    • 配合 Node.js path 模块处理路径。
    • shell 模块:

      • 管理文件和外部 URL。
      • shell.openExternal('https://electronjs.org'): 在默认浏览器打开链接。
      • shell.openPath('/path/to/file'): 用默认程序打开文件或目录。
      • shell.showItemInFolder('/path/to/item'): 在文件管理器中显示文件。
      • shell.trashItem('/path/to/item'): 将文件移动到回收站。
      • 剪贴板 (clipboard):

        • 读写系统剪贴板。
        • clipboard.writeText('Example Text')
        • clipboard.readText()
        • clipboard.writeImage(nativeImage)
        • clipboard.readImage()
        • 屏幕信息 (screen):

          • 获取屏幕尺寸、显示器信息、鼠标位置等。
          • screen.getPrimaryDisplay().workAreaSize
          • screen.getAllDisplays()
          • screen.getCursorScreenPoint()
          • 系统主题 (nativeTheme):

            • 检测和响应操作系统的颜色主题 (亮色/暗色模式)。
            • nativeTheme.shouldUseDarkColors (boolean)
            • nativeTheme.on('updated', () => { ... }) 监听主题变化。
            • nativeTheme.themeSource = 'dark' / 'light' / 'system' 设置应用主题模式。
            • 其他常用模块:

              • powerMonitor: 监控系统电源状态 (如进入睡眠、唤醒)。
              • globalShortcut: 注册/注销全局键盘快捷键。
              • protocol: 注册自定义协议 (myapp://...)。

第八章:安全

  1. 核心原则:最小权限原则

    • 不要给渲染进程不必要的权限。
    • 默认配置 (nodeIntegration: false, contextIsolation: true) 是最安全的起点。
    • 关键安全设置 (webPreferences):

      • contextIsolation: true (默认): 强烈推荐。隔离 preload 脚本和渲染进程的 JavaScript 上下文,防止渲染进程直接访问 Node.js 或 Electron API。
      • nodeIntegration: false (默认): 强烈推荐。禁止在渲染进程中使用 require() 和 Node.js 全局变量。
      • sandbox: true: 启用 Chromium 沙盒,进一步限制渲染进程的能力。通常需要配合 contextBridge 和 IPC 使用。
      • preload 脚本的重要性:

        • 作为受信任的脚本,连接隔离的渲染进程和主进程。
        • 使用 contextBridge.exposeInMainWorld 安全地暴露有限的、必要的 API 给渲染进程。不要暴露整个 ipcRenderer 或 Node.js 模块。
        • 内容安全策略 (CSP - Content Security Policy):

          • 通过 HTTP Header (session.defaultSession.webRequest.onHeadersReceived) 或 标签设置。
          • 限制资源加载来源 (脚本、样式、图片等),防止 XSS 攻击。
          • 例如:default-src 'self' 只允许加载同源资源。
          • 校验 IPC 消息:

            • 不要完全信任来自渲染进程的任何数据。
            • 在主进程的 IPC 处理函数中,对接收到的参数进行严格的类型、格式和范围校验。
            • 限制导航:

              • 监听 webContents 的 will-navigate 和 new-window 事件,阻止应用导航到非预期的外部网站或打开恶意窗口。
              • 检查依赖项:

                • 定期更新依赖项 (npm audit),注意第三方库可能存在的安全漏洞。

第九章:打包与分发

  1. 为什么需要打包?

    • 将应用程序代码、Electron 可执行文件、Node.js 模块等捆绑成用户可以直接安装和运行的格式 (如 .exe, .dmg, .deb)。
    • 简化用户安装过程。
    • 常用打包工具:

      • electron-builder: 功能强大,配置灵活,支持多种目标格式和自动更新。推荐使用。
      • electron-packager: 相对简单,只负责基础打包,不包含安装程序制作和自动更新。
      • electron-builder 配置 (通常在 package.json 的 build 字段或 electron-builder.yml 文件中):

        • appId: 应用程序的唯一标识符 (如 com.example.myapp)。
        • productName: 应用名称。
        • files: 指定需要包含在打包中的文件/目录。
        • directories: 指定输出目录 (output) 和构建资源目录 (buildResources)。
        • 特定平台配置 (win, mac, linux):
          • target: 打包的目标格式 (e.g., nsis for Windows installer, dmg for macOS, AppImage, deb, rpm for Linux)。
          • icon: 指定应用程序图标。
          • asar: 是否将应用源码打包成 asar 归档文件 (提高读取性能,隐藏源码)。
          • 打包命令 (使用 electron-builder):

            • npm run build 或 yarn build (通常配置在 scripts 中,e.g., "build": "electron-builder")。
            • 可以指定平台:electron-builder --win --mac --linux。
            • 代码签名 (Code Signing):

              • 目的: 向操作系统和用户证明应用程序来源可信,未被篡改。
              • macOS: 必须进行签名和公证 (Notarization) 才能在较新系统上顺利分发。需要 Apple Developer ID 证书。
              • Windows: 推荐使用 EV 证书或标准代码签名证书进行签名,以避免 SmartScreen 警告。
              • electron-builder 支持配置签名证书。
              • 自动更新 (electron-updater):

                • electron-builder 内置支持 electron-updater 模块。
                • 需要在主进程中集成更新逻辑 (检查更新、下载、安装)。
                • 配置 publish 选项 (如 GitHub Releases, S3 等) 来指定更新包的发布位置。

第十章:进阶主题与最佳实践

  1. 性能优化:

    • 懒加载: 按需加载模块和资源,避免启动时加载所有内容。
    • 优化 IPC: 避免频繁、大量数据的 IPC 通信。考虑合并请求,使用 invoke/handle 代替多次 send/on。
    • 避免在渲染进程中执行阻塞操作: 将耗时任务 (如复杂计算、文件读写) 放到主进程或 Web Workers 中。
    • 管理窗口: 不用的窗口及时销毁 (win.close()) 而不是隐藏 (win.hide()),以释放资源。
    • 使用 V8 代码缓存: app.enableSandbox() 或通过 webPreferences 控制。
    • 分析性能: 使用 Chrome DevTools 的 Performance 和 Memory 面板。
    • 状态管理:

      • 对于复杂应用,在多个窗口/进程间同步状态可能比较复杂。
      • 方案:
        • 将状态主要存储在主进程,通过 IPC 同步给需要的渲染进程。
        • 使用 electron-store 等库持久化简单配置。
        • 使用 Redux, Vuex, Pinia 等状态管理库,并配合 IPC 或 electron-redux, vuex-electron 等桥接库进行跨进程同步。
        • 测试:

          • 单元测试: 使用 Jest, Mocha 等测试框架测试独立的模块和函数 (主进程、渲染进程逻辑)。
          • 端到端 (E2E) 测试: 使用 Spectron (官方维护,基于 WebDriver) 或 Playwright/Puppeteer (需要额外配置) 来模拟用户交互,测试整个应用程序的行为。
          • 使用现代前端框架 (Vue, React, Angular):

            • 可以将 Vue/React/Angular 项目构建后的静态文件 (dist 目录) 加载到 Electron 的 BrowserWindow 中 (win.loadFile('dist/index.html'))。
            • 通常使用 Vite 或 Webpack 等构建工具。
            • 需要配置好 preload 脚本和 IPC 通信,以连接前端框架和 Electron 的原生能力。
            • 社区有模板项目 (如 electron-vite, electron-react-boilerplate) 可以快速启动。
            • 主进程与渲染进程代码分离:

              • 保持清晰的项目结构,将主进程代码、preload 脚本、渲染进程 UI 代码分别放在不同的目录中。

这份总结覆盖了 Electron 开发的主要方面。掌握这些知识点将为构建稳定、安全、功能丰富的桌面应用打下坚实的基础。在实践中不断深入探索和学习特定 API 及最佳实践非常重要。

示例

Electron 知识点详解 (带示例)


第一章:Electron 入门与核心概念

(本章偏重概念,代码示例从第二章开始)

  1. 什么是 Electron?

    • 定义:使用 HTML, CSS, JavaScript 构建跨平台桌面应用的框架。
    • 核心:Chromium + Node.js + 自定义 APIs。
    • 为什么选择 Electron?

      • 跨平台、Web 技术栈、快速开发、强大能力、成熟生态。
      • 主要挑战/缺点:

        • 包体积大、内存占用、潜在性能瓶颈、安全需关注。
        • 核心架构:主进程 (Main Process) 与渲染进程 (Renderer Process)

          • 主进程: 唯一的 Node.js 后端环境,管理窗口和系统交互。
          • 渲染进程: 每个窗口的浏览器环境,负责 UI 渲染和前端逻辑。

第二章:环境搭建与基础项目

  1. 环境要求: Node.js, npm/yarn。

  2. 创建基础项目:

    # 1. 创建目录并进入
    mkdir my-electron-app && cd my-electron-app
    # 2. 初始化 npm 项目
    npm init -y
    # 3. 安装 Electron
    npm install --save-dev electron
    # 4. 创建文件
    touch main.js index.html renderer.js
    
  3. package.json 关键配置:

    // package.json
    {
      "name": "my-electron-app",
      "version": "1.0.0",
      "description": "My First Electron App",
      "main": "main.js", // 指定主进程入口文件
      "scripts": {
        "start": "electron .", // 定义启动命令
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "keywords": [],
      "author": "Your Name",
      "license": "MIT",
      "devDependencies": {
        "electron": "^28.0.0" // 版本号可能不同
      }
    }
    
  4. main.js (主进程) 基础代码:

    // main.js
    const { app, BrowserWindow } = require('electron');
    const path = require('path');
    function createWindow() {
      // 创建浏览器窗口
      const mainWindow = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
          // preload: path.join(__dirname, 'preload.js') // 预加载脚本,后续章节会用到
        }
      });
      // 加载 index.html
      mainWindow.loadFile('index.html');
      // 打开开发者工具 (可选)
      mainWindow.webContents.openDevTools();
    }
    // Electron 会在初始化后并准备
    // 创建浏览器窗口时,调用这个函数。
    // 部分 API 在 ready 事件触发后才能使用。
    app.whenReady().then(() => {
      createWindow();
      // 在 macOS 上,当单击 dock 图标并且没有其他窗口打开时,
      // 通常在应用程序中重新创建一个窗口。
      app.on('activate', function () {
        if (BrowserWindow.getAllWindows().length === 0) createWindow();
      });
    });
    // 除了 macOS 外,当所有窗口都被关闭的时候退出程序。 因此,通常对程序和它们在
    // 任务栏上的图标来说,应当保持活跃状态,直到用户使用 Cmd + Q 退出。
    app.on('window-all-closed', function () {
      if (process.platform !== 'darwin') app.quit();
    });
    
  5. index.html (渲染进程) 基础代码:

    
    
        
        
        
        Hello World!
    
    
        

    Hello World!

    We are using Node.js , Chromium , and Electron .
  6. renderer.js (渲染进程) 基础代码 (演示访问 process 对象,但依赖 nodeIntegration,后续会用更安全的方式):

    // renderer.js
    // 注意:直接访问 process 等 Node.js API 需要在 BrowserWindow 中开启 nodeIntegration: true
    // 这不是推荐的安全做法,后续会通过 preload 脚本实现
    const information = document.getElementById('info');
    const nodeVersionSpan = document.getElementById('node-version');
    const chromeVersionSpan = document.getElementById('chrome-version');
    const electronVersionSpan = document.getElementById('electron-version');
    // 尝试获取版本信息 (如果 nodeIntegration: false, 这会报错)
    try {
        nodeVersionSpan.innerText = process.versions.node;
        chromeVersionSpan.innerText = process.versions.chrome;
        electronVersionSpan.innerText = process.versions.electron;
    } catch (error) {
        console.error("Could not access process.versions. Is nodeIntegration enabled?", error);
        nodeVersionSpan.innerText = 'N/A';
        chromeVersionSpan.innerText = 'N/A';
        electronVersionSpan.innerText = 'N/A';
    }
    
    • 重要提示: 上述 renderer.js 示例直接访问 process。为了让它工作,你需要在 main.js 的 BrowserWindow 配置中添加 webPreferences: { nodeIntegration: true, contextIsolation: false }。但这极不安全! 我们将在第五章展示如何使用 preload 和 contextBridge 安全地实现类似功能。
    • 启动与调试:

      • 启动:npm start
      • 调试:在 main.js 中添加 mainWindow.webContents.openDevTools(); 后启动,即可在窗口中看到 Chrome 开发者工具。

第三章:主进程 (Main Process) 详解

  1. app 模块:

    • 示例:获取应用路径
      // main.js
      const { app } = require('electron');
      console.log('User Data Path:', app.getPath('userData'));
      console.log('App Path:', app.getAppPath());
      console.log('Is Packaged:', app.isPackaged); // 开发时为 false, 打包后为 true
      
    • 示例:处理退出
      // main.js
      app.on('before-quit', (event) => {
        console.log('App is about to quit...');
        // event.preventDefault(); // 可以阻止退出
      });
      
    • BrowserWindow 模块:

      • 示例:创建无边框窗口
        // main.js (在 createWindow 函数内)
        const win = new BrowserWindow({
          width: 400,
          height: 300,
          frame: false, // 移除窗口边框和标题栏
          webPreferences: { /* ... */ }
        });
        
      • 示例:窗口加载完成后显示 (避免白屏)
        // main.js (在 createWindow 函数内)
        const win = new BrowserWindow({
          show: false, // 先不显示
          width: 800,
          height: 600,
          webPreferences: { /* ... */ }
        });
        win.loadFile('index.html');
        win.once('ready-to-show', () => {
          win.show(); // 页面加载好后再显示
        });
        
      • webPreferences 选项 (关键配置):

        • 示例:配置 preload 脚本 (安全)
          // main.js
          const path = require('path');
          // ...
          const mainWindow = new BrowserWindow({
              width: 800,
              height: 600,
              webPreferences: {
                  // --- 安全推荐设置 ---
                  nodeIntegration: false, // 禁用 Node.js 集成 (渲染进程)
                  contextIsolation: true, // 开启上下文隔离
                  preload: path.join(__dirname, 'preload.js') // 指定预加载脚本
                  // --------------------
                  // sandbox: true, // 更严格的沙盒,需要更多 IPC 配置
              }
          });
          
          • preload.js 的内容将在下一章展示。

第四章:渲染进程 (Renderer Process) 详解

  1. 角色: UI 展示与交互。

  2. 访问 Node.js (推荐方式:preload + contextBridge)

  3. preload.js 脚本:

    • 示例:使用 contextBridge 暴露 API (安全)
      // preload.js
      const { contextBridge, ipcRenderer } = require('electron');
      const os = require('os'); // preload 可以访问 Node.js 模块
      contextBridge.exposeInMainWorld('electronAPI', {
          // 暴露一个同步获取信息的接口 (虽然不推荐同步,但可演示)
          getPlatform: () => os.platform(),
          // 暴露一个调用主进程函数的接口 (异步)
          setTitle: (title) => ipcRenderer.send('set-title', title),
          // 暴露一个双向通信的接口 (异步)
          openFile: () => ipcRenderer.invoke('dialog:openFile'),
          // 暴露一个监听主进程消息的接口
          onUpdateCounter: (callback) => ipcRenderer.on('update-counter', (_event, value) => callback(value)),
          // 移除监听器的方法
          removeUpdateCounterListener: () => ipcRenderer.removeAllListeners('update-counter')
      });
      // 也可以直接在 preload 中操作 DOM,但不推荐,应由 renderer.js 负责
      // window.addEventListener('DOMContentLoaded', () => { ... });
      
      • exposeInMainWorld 的第一个参数 'electronAPI' 是暴露到 window 对象下的键名。
      • renderer.js (渲染进程脚本):

        • 示例:调用 preload 暴露的 API
          // renderer.js
          // 调用同步方法
          const platformSpan = document.createElement('p');
          platformSpan.textContent = `Platform: ${window.electronAPI.getPlatform()}`;
          document.body.appendChild(platformSpan);
          // 调用单向 IPC
          const titleButton = document.createElement('button');
          titleButton.textContent = 'Set Window Title to "My App"';
          titleButton.onclick = () => {
              window.electronAPI.setTitle('My App');
          };
          document.body.appendChild(titleButton);
          // 调用双向 IPC
          const openFileButton = document.createElement('button');
          openFileButton.textContent = 'Open File Dialog';
          openFileButton.onclick = async () => {
              const filePath = await window.electronAPI.openFile();
              const filePathP = document.createElement('p');
              filePathP.textContent = filePath ? `Selected: ${filePath}` : 'No file selected.';
              document.body.appendChild(filePathP);
          };
          document.body.appendChild(openFileButton);
          // 监听来自主进程的消息
          const counterP = document.createElement('p');
          counterP.textContent = 'Counter: 0';
          document.body.appendChild(counterP);
          window.electronAPI.onUpdateCounter((value) => {
              counterP.textContent = `Counter: ${value}`;
          });
          // 注意:在页面/组件卸载时,应调用 removeUpdateCounterListener 清理监听
          // window.onbeforeunload = () => {
          //   window.electronAPI.removeUpdateCounterListener();
          // };
          

第五章:进程间通信 (Inter-Process Communication - IPC)

  1. 为什么需要 IPC? 隔离的进程间传递消息。

  2. 主要模块: ipcMain, ipcRenderer, contextBridge。

  3. 通信模式示例 (配合上一章的 preload.js 和 renderer.js)

    • 渲染进程 -> 主进程 (单向): (setTitle)

      • renderer.js: window.electronAPI.setTitle('New Title') (通过 preload 调用 ipcRenderer.send)
      • main.js:
        const { app, BrowserWindow, ipcMain } = require('electron');
        // ... 在 createWindow 后 ...
        ipcMain.on('set-title', (event, title) => {
          const webContents = event.sender;
          const win = BrowserWindow.fromWebContents(webContents);
          if (win) {
            win.setTitle(title);
          }
        });
        
      • 渲染进程 -> 主进程 -> 渲染进程 (双向异步): (openFile)

        • renderer.js: const filePath = await window.electronAPI.openFile() (通过 preload 调用 ipcRenderer.invoke)
        • main.js:
          const { app, BrowserWindow, ipcMain, dialog } = require('electron');
          // ...
          ipcMain.handle('dialog:openFile', async () => {
            const { canceled, filePaths } = await dialog.showOpenDialog({ properties: ['openFile'] });
            if (!canceled && filePaths.length > 0) {
              return filePaths[0];
            }
            return null; // 或者 undefined
          });
          
        • 主进程 -> 渲染进程 (单向): (update-counter)

          • main.js (示例:每秒发送一次计数器):
            // 需要 mainWindow 实例
            let counter = 0;
            setInterval(() => {
              // 确保窗口还存在
              if (mainWindow && !mainWindow.isDestroyed()) {
                  mainWindow.webContents.send('update-counter', counter++);
              }
            }, 1000);
            
            (注意: 上述代码需要将 mainWindow 变量提升到 setInterval 可访问的作用域)
          • renderer.js (通过 preload 的 onUpdateCounter 监听):
            window.electronAPI.onUpdateCounter((value) => {
              console.log('Received counter from main:', value);
              // 更新 UI...
            });
            
          • 安全 IPC 的最佳实践: 始终使用 contextBridge,如上例所示。避免直接暴露 ipcRenderer。


第六章:原生 UI 元素

  1. 应用程序菜单 (Menu):

    • 示例:创建简单的应用菜单 (macOS & Windows/Linux)
      // main.js
      const { app, Menu, shell } = require('electron');
      const isMac = process.platform === 'darwin';
      const template = [
        // { role: 'appMenu' } 或者 app.getName()
        ...(isMac ? [{
          label: app.getName(),
          submenu: [
            { role: 'about' },
            { type: 'separator' },
            { role: 'services' },
            { type: 'separator' },
            { role: 'hide' },
            { role: 'hideOthers' },
            { role: 'unhide' },
            { type: 'separator' },
            { role: 'quit' }
          ]
        }] : []),
        // { role: 'fileMenu' }
        {
          label: 'File',
          submenu: [
            {
              label: 'New Window',
              accelerator: 'CmdOrCtrl+N',
              click: () => { /* 调用 createWindow() */ }
            },
            isMac ? { role: 'close' } : { role: 'quit' }
          ]
        },
        // { role: 'editMenu' }
        {
          label: 'Edit',
          submenu: [
            { role: 'undo' },
            { role: 'redo' },
            { type: 'separator' },
            { role: 'cut' },
            { role: 'copy' },
            { role: 'paste' },
            ...(isMac ? [
              { role: 'pasteAndMatchStyle' },
              { role: 'delete' },
              { role: 'selectAll' },
              { type: 'separator' },
              {
                label: 'Speech',
                submenu: [
                  { role: 'startSpeaking' },
                  { role: 'stopSpeaking' }
                ]
              }
            ] : [
              { role: 'delete' },
              { type: 'separator' },
              { role: 'selectAll' }
            ])
          ]
        },
        // { role: 'viewMenu' }
        {
          label: 'View',
          submenu: [
            { role: 'reload' },
            { role: 'forceReload' },
            { role: 'toggleDevTools' },
            { type: 'separator' },
            { role: 'resetZoom' },
            { role: 'zoomIn' },
            { role: 'zoomOut' },
            { type: 'separator' },
            { role: 'togglefullscreen' }
          ]
        },
        // { role: 'windowMenu' }
        {
          label: 'Window',
          submenu: [
            { role: 'minimize' },
            { role: 'zoom' },
            ...(isMac ? [
              { type: 'separator' },
              { role: 'front' },
              { type: 'separator' },
              { role: 'window' }
            ] : [
              { role: 'close' }
            ])
          ]
        },
        {
          role: 'help',
          submenu: [
            {
              label: 'Learn More',
              click: async () => {
                await shell.openExternal('https://electronjs.org');
              }
            }
          ]
        }
      ];
      const menu = Menu.buildFromTemplate(template);
      Menu.setApplicationMenu(menu); // 设置应用菜单
      // 也可以创建上下文菜单
      // const contextMenu = Menu.buildFromTemplate([...]);
      // window.webContents.on('context-menu', (e, params) => {
      //   contextMenu.popup(window);
      // });
      
    • 对话框 (dialog):

      • 示例:显示消息框 (主进程或通过 IPC 调用)
        // main.js (或在 ipcMain.handle 中)
        const { dialog } = require('electron');
        async function showInfoMessage() {
          await dialog.showMessageBox({
            type: 'info', // 'none', 'info', 'error', 'question', 'warning'
            title: 'Information',
            message: 'This is an informational message.',
            detail: 'Some extra details here.',
            buttons: ['OK', 'Cancel'] // 返回点击按钮的索引 (0 or 1)
          });
        }
        // 调用 showInfoMessage()
        
      • 示例:显示打开文件对话框 (已在 IPC 示例中)
      • 系统托盘 (Tray):

        • 示例:创建简单的系统托盘图标
          // main.js
          const { app, Tray, Menu, nativeImage } = require('electron');
          const path = require('path');
          let tray = null; // 需要持有引用,否则会被垃圾回收
          app.whenReady().then(() => {
            // 需要一个图标文件 (e.g., icon.png in project root)
            // 推荐使用 16x16 或 32x32 的 .png 或 .ico
            const iconPath = path.join(__dirname, 'icon.png'); // 替换为你的图标路径
            const icon = nativeImage.createFromPath(iconPath);
            tray = new Tray(icon);
            const contextMenu = Menu.buildFromTemplate([
              { label: 'Show App', click: () => { /* 显示窗口逻辑 */ } },
              { label: 'Quit', click: () => { app.quit(); } }
            ]);
            tray.setToolTip('My Electron App');
            tray.setContextMenu(contextMenu);
            tray.on('click', () => {
               // 点击托盘图标的操作,例如显示/隐藏窗口
               // mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show();
            });
          });
          
        • 原生通知 (Notification):

          • 示例:显示一个简单的通知
            // main.js (或在渲染进程中,但需检查支持性)
            const { Notification } = require('electron');
            function showNotification() {
              if (Notification.isSupported()) { // 检查系统是否支持
                const notification = new Notification({
                  title: 'Hello!',
                  body: 'This is a notification from Electron.',
                  // icon: path.join(__dirname, 'icon.png') // 可选图标
                });
                notification.show();
                notification.on('click', () => {
                  console.log('Notification clicked!');
                  // 可以添加点击后的操作,如聚焦窗口
                });
              } else {
                console.log('Notifications not supported on this system.');
              }
            }
            // 调用 showNotification()
            

第七章:系统集成与常用 API

  1. 访问文件系统 (Node.js fs 模块):

    • 示例:通过 preload 安全暴露读取文件功能
      • preload.js:
        const { contextBridge, ipcRenderer } = require('electron');
        contextBridge.exposeInMainWorld('electronAPI', {
          // ... 其他 API ...
          readFile: (filePath) => ipcRenderer.invoke('fs:readFile', filePath)
        });
        
      • main.js:
        const { ipcMain } = require('electron');
        const fs = require('fs').promises; // 使用 promise 版本
        ipcMain.handle('fs:readFile', async (event, filePath) => {
          try {
            // !! 安全警告:实际应用中必须严格校验 filePath !!
            // 防止路径遍历攻击等,例如限制在特定目录下
            console.log(`Reading file requested by renderer: ${filePath}`);
            const data = await fs.readFile(filePath, 'utf-8');
            return { success: true, data: data };
          } catch (error) {
            console.error('Error reading file:', error);
            return { success: false, error: error.message };
          }
        });
        
      • renderer.js:
        async function readMyFile() {
          // 需要用户选择文件或指定安全路径
          const result = await window.electronAPI.readFile('path/to/your/file.txt');
          if (result.success) {
            console.log('File content:', result.data);
          } else {
            console.error('Failed to read file:', result.error);
          }
        }
        
      • shell 模块:

        • 示例:打开外部链接
          // main.js 或 preload.js (暴露给渲染进程)
          const { shell } = require('electron');
          // shell.openExternal('https://www.google.com');
          // --- 通过 preload 暴露 ---
          // preload.js
          contextBridge.exposeInMainWorld('electronAPI', {
            // ...
            openExternal: (url) => shell.openExternal(url) // 注意安全,校验 URL
          });
          // renderer.js
          // window.electronAPI.openExternal('https://electronjs.org');
          
        • 剪贴板 (clipboard):

          • 示例:读写文本
            • preload.js:
              const { contextBridge, clipboard } = require('electron');
              contextBridge.exposeInMainWorld('clipboardAPI', {
                writeText: (text) => clipboard.writeText(text),
                readText: () => clipboard.readText()
              });
              
            • renderer.js:
              async function testClipboard() {
                await window.clipboardAPI.writeText('Copied from Electron!');
                const text = await window.clipboardAPI.readText();
                console.log('Clipboard content:', text);
              }
              // 调用 testClipboard()
              
            • 屏幕信息 (screen):

              • 示例:获取主显示器尺寸
                • preload.js:
                  const { contextBridge, screen } = require('electron');
                  contextBridge.exposeInMainWorld('electronAPI', {
                    // ...
                    getPrimaryDisplaySize: () => screen.getPrimaryDisplay().workAreaSize
                  });
                  
                • renderer.js:
                  const size = window.electronAPI.getPrimaryDisplaySize();
                  console.log(`Primary display work area: ${size.width}x${size.height}`);
                  
                • 系统主题 (nativeTheme):

                  • 示例:检测并响应暗色模式
                    • preload.js:
                      const { contextBridge, ipcRenderer } = require('electron');
                      contextBridge.exposeInMainWorld('electronAPI', {
                         // ...
                         isDarkMode: () => ipcRenderer.invoke('nativeTheme:isDarkMode'),
                         onThemeUpdate: (callback) => ipcRenderer.on('theme-updated', () => callback())
                      });
                      
                    • main.js:
                      const { nativeTheme, ipcMain } = require('electron');
                      ipcMain.handle('nativeTheme:isDarkMode', () => nativeTheme.shouldUseDarkColors);
                      // 监听主题变化并通知渲染进程
                      nativeTheme.on('updated', () => {
                        // 通知所有窗口
                        BrowserWindow.getAllWindows().forEach(win => {
                          if(win && !win.isDestroyed()) {
                            win.webContents.send('theme-updated');
                          }
                        });
                      });
                      
                    • renderer.js:
                      async function checkTheme() {
                        const isDark = await window.electronAPI.isDarkMode();
                        document.body.classList.toggle('dark-mode', isDark);
                        console.log(`Current theme is ${isDark ? 'dark' : 'light'}`);
                      }
                      checkTheme(); // Initial check
                      window.electronAPI.onThemeUpdate(() => {
                        console.log('Theme updated!');
                        checkTheme(); // Re-check on update
                        // 更新 UI ...
                      });
                      
                      (你需要在 CSS 中定义 .dark-mode 样式)

第八章:安全

  1. 核心原则: 最小权限。

  2. 关键安全设置 (webPreferences): 见第三章示例 (nodeIntegration: false, contextIsolation: true).

  3. preload 脚本与 contextBridge: 这是现代 Electron 安全的核心。 见第四、五章示例。永远不要在 preload 中这样写:window.ipcRenderer = require('electron').ipcRenderer;。

  4. 内容安全策略 (CSP):

    • 示例:在 HTML 中设置
      
        
        
        
        
        
        Secure App
      
      
    • 校验 IPC 消息:

      • 示例:在 ipcMain.handle 中校验
        // main.js
        ipcMain.handle('process-data', (event, input) => {
          // 假设 input 应该是一个包含 name 和 age 的对象
          if (typeof input !== 'object' || input === null) {
            throw new Error('Invalid input type: expected object.');
          }
          if (typeof input.name !== 'string' || input.name.length === 0) {
             throw new Error('Invalid input: name must be a non-empty string.');
          }
          if (typeof input.age !== 'number' || input.age  150) {
             throw new Error('Invalid input: age must be a number between 0 and 150.');
          }
          // ... 处理校验通过的数据 ...
          console.log(`Processing valid data for ${input.name}`);
          return { success: true, message: `Processed ${input.name}` };
        });
        
      • 限制导航:

        • 示例:阻止导航到外部网站
          // main.js (在 createWindow 内,获取 webContents 后)
          mainWindow.webContents.on('will-navigate', (event, url) => {
            const parsedUrl = new URL(url);
            // 允许 file:// 协议或特定安全域
            if (parsedUrl.protocol !== 'file:' /* && parsedUrl.hostname !== 'trusted.com' */) {
              console.warn(`Blocked navigation to: ${url}`);
              event.preventDefault(); // 阻止导航
              shell.openExternal(url); // 可选:在外部浏览器打开
            }
          });
          
        • 检查依赖项: npm audit


第九章:打包与分发

  1. 为什么需要打包? 创建可执行文件。

  2. 常用打包工具: electron-builder (推荐), electron-packager。

  3. electron-builder 配置 (示例 package.json):

    // package.json
    {
      // ... 其他配置 ...
      "scripts": {
        "start": "electron .",
        "pack": "electron-builder --dir", // 打包成未压缩目录 (测试用)
        "dist": "electron-builder" // 打包成分发格式 (exe, dmg 等)
      },
      "build": {
        "appId": "com.example.myelectronapp",
        "productName": "MyElectronApp",
        "files": [
          "main.js",
          "preload.js",
          "index.html",
          "renderer.js",
          "node_modules/**/*", // 通常 builder 会自动处理
          "assets/", // 包含你的静态资源
          "!node_modules/**/{test,tests,spec,specs,example,examples,.bin}/**/*" // 排除不必要的文件
        ],
        "directories": {
          "output": "dist", // 打包输出目录
          "buildResources": "build" // 构建资源目录 (如图标)
        },
        "win": {
          "target": "nsis", // NSIS 安装程序
          "icon": "build/icon.ico" // Windows 图标
        },
        "mac": {
          "target": "dmg", // DMG 镜像
          "icon": "build/icon.icns", // macOS 图标
          "category": "public.app-category.utilities" // App Store 分类
        },
        "linux": {
          "target": [
            "AppImage",
            "deb"
          ],
          "icon": "build/icon.png", // Linux 图标
          "category": "Utility"
        },
        "nsis": { // NSIS 安装程序特定配置
          "oneClick": false, // 非静默安装
          "allowToChangeInstallationDirectory": true
        },
        "asar": true // 将应用代码打包到 asar 存档中
      },
      "devDependencies": {
        "electron": "^28.0.0",
        "electron-builder": "^24.9.1" // 添加 electron-builder
      }
    }
    
    • 注意: 你需要创建 build 文件夹并放入相应格式的图标文件 (icon.ico, icon.icns, icon.png)。
    • 打包命令:

      • npm run dist 或 yarn dist
      • 代码签名: 需要平台特定的证书,并在 electron-builder 配置中指定 (参考其文档)。

      • 自动更新 (electron-updater):

        • 示例:主进程检查更新
          // main.js
          const { autoUpdater } = require('electron-updater');
          const { dialog } = require('electron');
          // 配置 autoUpdater (通常会自动读取 build.publish 配置)
          // autoUpdater.setFeedURL({ provider: 'github', owner: 'your-gh-username', repo: 'your-repo' });
          function checkForUpdates() {
              // 在应用启动后或菜单项点击时调用
              autoUpdater.checkForUpdatesAndNotify().catch(err => {
                  console.error('Update check failed:', err);
              });
          }
          // 监听更新事件
          autoUpdater.on('update-available', () => {
              dialog.showMessageBox({
                  type: 'info',
                  title: 'Update Available',
                  message: 'A new version is available. Do you want to download and install it now?',
                  buttons: ['Yes', 'Later']
              }).then(result => {
                  if (result.response === 0) { // 'Yes' button
                      autoUpdater.downloadUpdate();
                  }
              });
          });
          autoUpdater.on('update-downloaded', () => {
              dialog.showMessageBox({
                  type: 'info',
                  title: 'Update Ready',
                  message: 'Update downloaded. The application will now quit to install...',
                  buttons: ['OK']
              }).then(() => {
                  setImmediate(() => autoUpdater.quitAndInstall());
              });
          });
          autoUpdater.on('error', (error) => {
            dialog.showErrorBox('Update Error', error == null ? "unknown" : (error.stack || error).toString());
          });
          // 在 app ready 后调用检查更新
          app.whenReady().then(() => {
              // ... createWindow ...
              if (app.isPackaged) { // 只在打包后检查更新
                checkForUpdates();
              }
          });
          

第十章:进阶主题与最佳实践

  1. 性能优化:

    • 懒加载示例 (主进程动态 import):
      // main.js
      ipcMain.handle('load-heavy-module', async () => {
        const heavyModule = await import('./heavy-module.js'); // 动态导入
        return heavyModule.doWork();
      });
      
    • 避免阻塞操作: 将 fs.readFileSync 替换为 fs.readFile (异步)。
    • 状态管理: 使用 Redux/Vuex/Pinia 等,配合 electron-store 或自定义 IPC 同步机制。

    • 测试 (Spectron E2E 示例概念):

      // test/spec.js (概念性)
      const Application = require('spectron').Application;
      const assert = require('assert');
      const electronPath = require('electron'); // 获取 Electron 可执行文件路径
      const path = require('path');
      describe('Application launch', function () {
        this.timeout(10000); // 增加超时
        let app;
        beforeEach(function () {
          app = new Application({
            path: electronPath,
            args: [path.join(__dirname, '..')] // 指向你的 app 根目录
          });
          return app.start();
        });
        afterEach(function () {
          if (app && app.isRunning()) {
            return app.stop();
          }
        });
        it('shows an initial window', async function () {
          const count = await app.client.getWindowCount();
          assert.strictEqual(count, 1);
        });
        it('should have the correct title', async function () {
           const title = await app.client.getTitle();
           assert.strictEqual(title, 'Hello World!'); // 或你的初始标题
        });
        // ... 更多测试,如点击按钮、检查文本等
      });
      
    • 使用现代前端框架 (Vue/React/Angular):

      • 示例:加载 Vite 构建的 Vue 应用
        • 用 Vite 创建 Vue 项目: npm create vite@latest my-vue-app --template vue-ts
        • 构建 Vue 项目: cd my-vue-app && npm install && npm run build (会生成 dist 目录)
        • main.js:
          const { app, BrowserWindow } = require('electron');
          const path = require('path');
          function createWindow() {
            const mainWindow = new BrowserWindow({ /* ... webPreferences ... */ });
            if (app.isPackaged) {
              // 打包后加载构建的 index.html
              mainWindow.loadFile(path.join(__dirname, '../renderer/index.html')); // 假设 dist 目录被复制到打包后的 renderer 目录
            } else {
              // 开发时加载 Vite 开发服务器
              mainWindow.loadURL('http://localhost:5173'); // Vite 默认端口
              mainWindow.webContents.openDevTools();
            }
          }
          // ... app lifecycle ...
          
        • 你需要调整打包配置 (electron-builder),将 Vue 构建的 dist 目录包含进去,并可能调整 loadFile 的路径。使用 electron-vite 模板可以简化这个过程。
        • 主进程与渲染进程代码分离:

          • 项目结构示例:
            my-electron-app/
            ├── build/             # 图标等构建资源
            ├── dist/              # electron-builder 输出目录
            ├── node_modules/
            ├── src/
            │   ├── main/          # 主进程代码
            │   │   ├── main.js    # 主入口
            │   │   └── modules/   # 主进程其他模块
            │   ├── preload/       # Preload 脚本
            │   │   └── preload.js
            │   └── renderer/      # 渲染进程代码 (UI)
            │       ├── index.html
            │       ├── renderer.js
            │       └── style.css
            ├── package.json
            └── ... 其他配置文件 ...
            

这些示例应该能让你更具体地理解 Electron 的各个核心概念和常用功能。记住,安全和性能是 Electron 开发中需要持续关注的重要方面。

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

相关阅读

目录[+]

取消
微信二维码
微信二维码
支付宝二维码