本篇教程是使用Electron + React18进行开发,这里主要讲electron的使用。首先我们需要一个react的项目环境,react的项目搭建及开发教程可以参考我的react专栏里的文章:react相关技术这里都有
Electron是一个开源的跨平台桌面应用程序开发框架,允许开发者使用前端 Web 技术(HTML、CSS 和 JavaScript)来构建桌面应用程序 背景和起源 Electron 最初由 GitHub 公司开发,最早用于构建 GitHub Desktop。随着其成功,Electron 逐渐成为一个受欢迎的开发框架,许多知名应用程序如 Visual Studio Code、Slack、WhatsApp 等也使用 Electron 构建。 基本原理 Electron 使用 Chromium 渲染引擎来显示 Web 内容,同时结合 Node.js 来提供对操作系统的访问和控制。这使得开发者能够使用 Web 技术来构建桌面应用程序,同时还能够利用底层操作系统的功能。 主要特点
核心组件
开发流程
核心架构图解
首先,我们需要在一个常规的React项目中,安装electron,为了使我们功能代码部分和electron窗口部分更清晰,我们可以在项目的根目录新建一个desktop文件夹,专门用来存放electron部分代码和资源。目录结构大概如图所示:
我们cd desktop到desktop文件夹下,执行npm init -y初始化包管理器,然后安装electron相关包: electron:electron核心包 cross-env:cross-env 是一个用于设置跨平台环境变量的工具。它可以在 Windows、Linux 和 macOS 等操作系统上提供一致的环境变量设置方式,使得在不同平台上运行脚本时能够保持一致的行为。 electron-builder:electron-builder 是一个用于打包、构建和部署 Electron 应用程序的强大工具
powershell复制代码npm i electron cross-env electron-builder
在electron应用的运行过程中存在着自己的生命周期,在不同的生命周期中我们可以做对应的事情,下面介绍一些常用的生命周期,electron的生命周期通过electron中的app实例监听,我们在desktop目录下新建一个index.js文件,作为electron的入口文件,并在其中监听应用的各个生命周期 ready 触发时机:当 Electron 初始化完成并且应用程序准备好创建浏览器窗口时。 作用:通常用于初始化应用程序的主要界面和一些基础设施。 示例:在 ready 事件中创建主窗口和初始化托盘。 certificate-error 触发时机:当在加载网页时发生证书错误时。 作用:可以在这个事件中拦截证书错误并决定是否继续加载页面。 示例:在证书错误时阻止默认行为并返回 true 以继续加载页面。 before-quit 触发时机:当用户尝试退出应用程序时,通常是通过关闭所有窗口或者点击关闭按钮。 作用:在应用程序退出之前执行一些清理操作。 示例:可以在这个事件中执行一些清理或保存操作。 window-all-closed 触发时机:所有应用程序窗口都被关闭时。 作用:在此事件中通常用于在应用程序完全退出之前保留某些功能。 示例:在 macOS 下通常会保留菜单栏。 activate 触发时机:在点击 Dock 图标(macOS)或者任务栏图标(Windows)时。 作用:通常用于在所有窗口都已关闭的情况下,重新创建主窗口。 示例:在 macOS 下,当点击 Dock 图标时,可以重新创建主窗口。 quit 触发时机:应用程序即将退出时。 作用:在应用程序退出之前执行最后的清理操作。 示例:在这个事件中可以销毁托盘或其他资源。 will-quit 触发时机:在应用程序即将退出时,但在 quit 事件之前。 作用:在应用程序退出之前执行一些清理或保存操作。 示例:在这个事件中可以执行一些清理或保存操作。 will-finish-launching 触发时机:在应用程序即将完成启动时。 作用:可以在此事件中执行一些在应用程序完全启动之前需要完成的操作。 示例:在这个事件中可以初始化一些启动时需要的资源。
javascript复制代码const { app } = require('electron')const { createMainWindow } = require('./windows/mainWindow')app.on('ready', () => { createMainWindow()})app.on('certificate-error', (event, webContents, url, error, certificate, callback) => { event.preventDefault() callback(true)})app.on('before-quit', () => { console.log('app before-quit')})app.on('window-all-closed', function () { console.log('window-all-closed')})app.on('activate', function () { console.log('activate')})app.on('quit', function () { console.log('quit') getTray() && getTray().destroy()})app.on('will-quit', function () { console.log('will-quit')})app.on('will-finish-launching', function () { console.log('will-finish-launching')})
我们在desktop文件夹中创建一个windows文件夹,里面存放每个窗口的相关代码(我们项目中通常不止一个窗口),我们在windows文件夹中创建一个mainWindow.js文件,用于创建一个简单的窗口
javascript复制代码// 在主进程中.const { BrowserWindow } = require('electron')const path = require('path')const win = new BrowserWindow({ width: 800, height: 600 })// Load a remote URLwin.loadURL('http://localhost:8000/')// Or load a local HTML filewin.loadFile(path.resolve(__dirname, '../../../build/index.html'))
其中loadURL用于加载一个服务器地址,运行后将会在窗口中显示该地址的内容,我们这里的http://localhost:8000/是代码运行的本地环境地址 loadFile是加载一个静态文件,该文件就是渲染层代码打包后的入口文件。
由于创建窗口需要在app.on('ready', () => {})中,因此我们可以把创建窗口封装成一个函数并导出,在app.on('ready', () => {})中执行,例如: 封装mainWindow.js
javascript复制代码const { BrowserWindow, ipcMain } = require('electron')const path = require('path')const isDevelopment = process.env.NODE_ENV === 'development'let mainWindow = nullfunction createMainWindow() { mainWindow = new BrowserWindow({ width: 1160, height: 752, minHeight: 632, minWidth: 960, show: false, frame: false, title: 'Harbour', webPreferences: { nodeIntegration: true, preload: path.resolve(__dirname, '../utils/contextBridge.js') }, icon: path.resolve(__dirname, '../assets/logo.png') }) if (isDevelopment) { mainWindow.loadURL('http://localhost:8000/') } else { const entryPath = path.resolve(__dirname, '../../build/index.html') mainWindow.loadFile(entryPath) } mainWindow.once('ready-to-show', () => { mainWindow.show() })}module.exports = { createMainWindow }代码解析:我们这里使用process.env.NODE_ENV的值判断当前的运行环境,这里的运行环境需要说明一下,当我们使用下面配置的npm run dev-electron运行时,该值为"development",当我们将渲染层代码打包后,使用npm run prod-electron运行时,该值为"production",然而当我们使用electron-builder打包出来的安装包运行时,该值不存在为undefined。因此只有当该值是"development"时我们才加载一个我们渲染层启动的服务地址,其他两种情况下我们都需要加载我们渲染层打包后的入口文件即:build目录下的index.html``我们在窗口触发"ready-to-show"时显示窗口是为了使加载时的白屏时间不被用户看到
index.js导入并在app.on('ready', () => {})中执行
javascript复制代码const { app } = require('electron')const { createMainWindow } = require('./windows/mainWindow')app.on('ready', () => { createMainWindow()})
此时我们就可以运行electron,我们在package.json中配置运行命令
json复制代码{ "scripts": { "dev-electron": "cross-env NODE_ENV=development electron main/index.js", "prod-electron": "cross-env NODE_ENV=production electron main/index.js", }}
执行命令,启动开发环境
powershell复制代码npm run dev-electron
运行成功,出现如下窗口(窗口内部内容可自行定义)
使用npm run prod-electron命令可以启动生产环境,该生产环境指的是渲染层的功能代码使用webpack打包后的代码,使其渲染到窗口中。真正的生产环境应该是下面介绍的使用electron-builder打包后的应用程序,此时process.env.NODE_ENV为undefined
上面我们启动electron的应用都是使用的node_modules中的electron包,我们想要得到一个真正可以安装的安装包,还需要使用第三方打包工具进行打包,上面有提到过,我们将使用electron-builder打包成可安装的安装包。上面我们已经安装了electron-builder,下面我们需要在package.json中配置build属性来自定义安装配置。(限于自身设备问题,这里只介绍在Windows系统的打包配置,electron可以打包成各种安装包,使其可以在mac,Linux系统上运行,其他系统的配置可自行查阅资料。)下面我们介绍一下配置内容和各个配置含义。 package.json完整配置
json复制代码{ "name": "desktop", "productName": "Harbour", "version": "1.0.0", "description": "", "main": "main/index.js", "scripts": { "dev-electron": "cross-env NODE_ENV=development electron main/index.js", "prod-electron": "cross-env NODE_ENV=production electron main/index.js", "build-electron-win64": "electron-builder -w --x64" }, "build": { "productName": "Harbour", "appId": "harbour.electron.app", "files": [ "build/**/*", "main/**/*" ], "directories": { "output": "dist" }, "nsis": { "oneClick": false, "allowElevation": true, "allowToChangeInstallationDirectory": true, "installerIcon": "./main/assets/logo.ico", "uninstallerIcon": "./main/assets/logo.ico", "installerHeaderIcon": "./main/assets/logo.png", "createDesktopShortcut": true, "createStartMenuShortcut": true, "shortcutName": "Harbour" }, "win": { "icon": "./main/assets/logo.ico", "artifactName": "${productName}-${version}-${os}-${arch}.${ext}", "target": "nsis" }, "electronDist": "./electron" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "cross-env": "^7.0.3", "electron": "^26.1.0", "electron-builder": "^24.6.3" }}配置解释
特别注意 这里有几个需要特别注意的点:
打包后的内容
dist目录下就是打包生成的内容,其中第一个红框的Harbour.exe是可直接执行的文件,无需安装,第二个红框中的.exe可执行文件就是可安装的文件,在文件夹中,双击即可进入安装流程。
在我们创建窗口时可以配置很多自定义配置,下面是一些常用配置及解析:
javascript复制代码const { BrowserWindow } = require('electron');const mainWindow = new BrowserWindow({ width: 800, height: 600, x: 100, y: 100, fullscreen: false, resizable: true, minWidth: 400, minHeight: 300, frame: true, title: 'My Electron App', icon: '/path/to/icon.png', backgroundColor: '#ffffff', webPreferences: { nodeIntegration: true, contextIsolation: false, preload: 'path/to/preload.js', devTools: true, webSecurity: true }, alwaysOnTop: false, fullscreenable: true, show: true, transparent: false, closable: true});mainWindow.loadFile('index.html');
窗口有很多实例事件,使用window.on来监听,可以在这些事件触发时做一切操作例如下面是一些常用的实例事件: close 触发时机:窗口即将关闭时触发,但实际关闭前。 作用:允许执行一些在窗口关闭前的清理操作,或者阻止窗口关闭。 closed 触发时机:窗口已经关闭时触发。 作用:通常用于释放资源或执行一些在窗口关闭后的最终操作。 resize 触发时机:窗口大小发生变化时触发。 作用:允许在窗口大小变化时执行一些操作。 move 触发时机:窗口位置发生变化时触发。 作用:允许在窗口位置变化时执行一些操作。 focus 触发时机:窗口获得焦点时触发。 作用:允许在窗口获得焦点时执行一些操作。 blur 触发时机:窗口失去焦点时触发。 作用:允许在窗口失去焦点时执行一些操作。 minimize 触发时机:窗口被最小化时触发。 作用:允许在窗口最小化时执行一些操作。 maximize 触发时机:窗口被最大化时触发。 作用:允许在窗口最大化时执行一些操作。 unmaximize 触发时机:窗口从最大化状态恢复时触发。 作用:允许在窗口从最大化状态恢复时执行一些操作。 ready-to-show 触发时机:当窗口完成初始化并且准备好显示时触发。 作用:允许在窗口已准备好显示之后执行一些操作。这通常在窗口加载内容后并准备好显示时触发,用于控制窗口的显示时机。 show 触发时机:当窗口被显示时触发。 作用:允许在窗口显示时执行一些操作。 hide 触发时机:当窗口被隐藏时触发。 作用:允许在窗口隐藏时执行一些操作。 enter-full-screen 触发时机:当窗口进入全屏模式时触发。 作用:允许在窗口进入全屏模式时执行一些操作。 leave-full-screen 触发时机:当窗口离开全屏模式时触发。 作用:允许在窗口离开全屏模式时执行一些操作。
javascript复制代码// main.jsconst { app, BrowserWindow } = require('electron');let mainWindow;function createMainWindow() { mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: true } }); // 加载你的 HTML 文件 mainWindow.loadFile('index.html'); // 事件: 关闭 mainWindow.on('close', (event) => { // 允许或阻止窗口关闭 // event.preventDefault(); // 执行清理操作 }); // 事件: 关闭后 mainWindow.on('closed', () => { // 释放资源或执行最终操作 mainWindow = null; }); // 事件: 调整大小 mainWindow.on('resize', () => { // 在窗口调整大小时执行操作 }); // 事件: 移动 mainWindow.on('move', () => { // 在窗口移动时执行操作 }); // 事件: 获得焦点 mainWindow.on('focus', () => { // 在窗口获得焦点时执行操作 }); // 事件: 失去焦点 mainWindow.on('blur', () => { // 在窗口失去焦点时执行操作 }); // 事件: 最小化 mainWindow.on('minimize', () => { // 在窗口最小化时执行操作 }); // 事件: 最大化 mainWindow.on('maximize', () => { // 在窗口最大化时执行操作 }); // 事件: 还原 mainWindow.on('unmaximize', () => { // 在窗口从最大化状态还原时执行操作 }); // 事件: 准备好显示 mainWindow.on('ready-to-show', () => { // 在窗口准备好显示后执行操作 mainWindow.show(); }); // 事件: 显示 mainWindow.on('show', () => { // 在窗口显示时执行操作 }); // 事件: 隐藏 mainWindow.on('hide', () => { // 在窗口隐藏时执行操作 }); // 事件: 进入全屏模式 mainWindow.on('enter-full-screen', () => { // 在窗口进入全屏模式时执行操作 }); // 事件: 离开全屏模式 mainWindow.on('leave-full-screen', () => { // 在窗口离开全屏模式时执行操作 });}
窗口自身存在很多的实例属性,可以使我们获取到窗口的一些当前状态。下面是一些常用的实例属性。
对于一个桌面应用来说,右下角的系统托盘必不可少,electron应用的系统托盘使用tray这个api实现,下面是封装的专门处理系统托盘的文件 systemTray.js
javascript复制代码const { app, Tray, Menu } = require('electron')const path = require('path')const { getMainWindow, mainWindowIsExist } = require('./windows/mainWindow')let tray = nullconst iconPath = path.resolve(__dirname, './assets/logo.png')function initTray() { tray = new Tray(iconPath) const contextMenu = Menu.buildFromTemplate([ { label: '打开应用', click: () => { mainWindowIsExist() && getMainWindow().show() } }, { label: '退出应用', click: () => { app.quit() } }, ]) tray.setToolTip('Harbour') // 设置鼠标悬停时显示的提示信息 tray.setContextMenu(contextMenu) tray.on('click', () => { mainWindowIsExist() && getMainWindow().show() })}function getTray() { return tray}module.exports = { initTray, getTray }代码解析
初始化系统托盘 系统托盘的初始化需要在app.on('ready')之后,因此我们将初始化系统托盘的方法封装好导出,在app.on('ready')中执行
javascript复制代码const { app } = require('electron')const { createMainWindow } = require('./windows/mainWindow')const { initTray, getTray } = require('./systemTray')app.on('ready', () => { createMainWindow() initTray()})
应用层和主进程之间的通信流程是:
图解如下
我们知道ipcMain和ipcRender都是electron的Api,要想在应用层使用ipcRender就需要先将其注入到应用层,在electron中使用
contextBridge.exposeInMainWorld方法将electron的Api注入到应用层,注入之后我们就可以在应用层的window上访问注入的属性。我们这里将ipcRender和process两个属性注入到应用层,分别用来实现通信和判断当前运行环境。 封装contextBridge.js文件
javascript复制代码const { contextBridge, ipcRenderer } = require('electron')/** * contextBridge.exposeInMainWorld的作用就是将主进程的某些API注入到渲染进程, * 供渲染进程使用(主进程并非所有的API或对象都能注入给渲染进程,需要参考文档) * ipcRenderer 渲染进程通过window.ipcRenderer调用 */contextBridge.exposeInMainWorld('ipcRenderer', { send: (channel, ...args) => { if (args?.length > 0) { ipcRenderer.send(channel, ...args) } else { ipcRenderer.send(channel) } }, on: (channel, func) => { ipcRenderer.on(channel, func) }, once: (channel, func) => { ipcRenderer.once(channel, func) }, removeListener: (channel, func) => { ipcRenderer.removeListener(channel, func) }, sendSync: (channel, ...args) => { if (args?.length > 0) { return ipcRenderer.sendSync(channel, ...args) } else { return ipcRenderer.sendSync(channel) } }, invoke: (channel, ...args) => { try { return ipcRenderer.invoke(channel, ...args) } catch (error) { console.error(`Error invoking API: ${channel}`, error) } },})contextBridge.exposeInMainWorld('process', { NODE_ENV: process.env.NODE_ENV})
这里我们将ipcRender的send,on,once,removeListener,sendSync,invoke方法及process.env.NODE_ENV注入到应用层,后续可在应用层进行使用 注意,该方法需要在应用层渲染时执行,因此我们刚好可以用到创建窗口中的
option.webPreferences.preload来加载该文件,后续有案例代码。
我们将ipcRender和process注入到应用层后,为了后期的维护我们可以将所有的方法再次进行封装,放在一个统一的文件中, 封装desktopUtils.ts
typescript复制代码declare global { interface Window { ipcRenderer: { send: (...args: any[]) => void, on: (channel: string, listener: (...args: any[]) => void) => void, once: (channel: string, listener: (...args: any[]) => void) => void, removeListener: (channel: string, listener: (...args: any[]) => void) => void, sendSync: (...args: any[]) => any, invoke: (...args: any[]) => Promise<any>, }, process: { NODE_ENV: 'development' | 'production' } }}type ArgsType = string | number | boolean | { [key: string]: any } | any[]export const isDesktop = () => { return !!window.ipcRenderer}export const getProcessNodeEnv = () => { return window?.process.NODE_ENV}export const ipcRendererSend = (eventName: string, ...args: ArgsType[]) => { window.ipcRenderer?.send(eventName, ...args)}export const ipcRendererSendSync = (eventName: string, ...args: ArgsType[]) => { return window.ipcRenderer?.sendSync(eventName, ...args)}export const ipcRendererInvoke = (eventName: string, ...args: ArgsType[]) => { try { return window.ipcRenderer?.invoke(eventName, ...args) } catch (error) { console.error(`Error invoking IPC: ${eventName}`, error) return null }}export const ipcRendererOn = (eventName: string, listener: (...args: ArgsType[]) => void) => { window.ipcRenderer?.on(eventName, listener)}export const ipcRendererOnce = (eventName: string, listener: (...args: ArgsType[]) => void) => { window.ipcRenderer?.once(eventName, listener)}export const ipcRendererRemoveListener = (eventName: string, listener: (...args: ArgsType[]) => void) => { window.ipcRenderer?.removeListener(eventName, listener)}
这里的isDesktop是用来判断当前是否是桌面端的,因为很多时候我们使用electron开发的桌面端应用需要兼容web端,由于应用层代码几乎相同,我们只需要在一些情况下特别处理桌面端的逻辑即可。由于web端的window上一定没有ipcRender这个属性,因此可以根据window.ipcRenderer来判断 getProcessNodeEnv是用来获取当前桌面端的运行环境的,这里可以返回当前是开发环境还是生产环境,如果是web端的话,直接用process.env.NODE_ENV即可判断
我们封装好了方法之后就可以进行使用了,我们做一个简单的案例,应用的header,有最小化,最大化,关闭,和恢复按钮,在点击时使用ipcRendererSend方法将事件传给主进程并进行相应操作。 由于我们需要一个状态判断显示最大化按钮还是恢复按钮因此需要监听主进程在执行最大化和恢复之后传回的事件和当前状态。 desktopHeader.tsx
tsx复制代码import React, { memo, useState, useEffect } from 'react'import './desktopHeader.less'import SvgIcon from '@components/svgIcon'import { ipcRendererSend, ipcRendererOn, ipcRendererRemoveListener} from '@common/desktopUtils'import logoImage from '@assets/logo.png'function DesktopHeader() { const [windowIsMax, setWindowIsMax] = useState(false) useEffect(() => { const handleSetIsMax = (event: any, isMax: boolean) => { setWindowIsMax(isMax) } ipcRendererOn('mainWindowIsMax', handleSetIsMax) return () => { ipcRendererRemoveListener('mainWindowIsMax', handleSetIsMax) } }, []) const handleWindow = (eventName: string) => { ipcRendererSend(`mainWindow-${eventName}`) } return ( <div className="desktop-header"> <div className="header-logo-box"> <img src={logoImage} alt="" /> <span>Harbour</span> </div> <div className="header-handle-box"> <div className="handle-icon-box" onClick={handleWindow.bind(this, 'min')}> <SvgIcon svgName="min-icon" needPointer iconColor="#737780" iconSize={24} /> </div> {windowIsMax ? ( <div className="handle-icon-box" onClick={handleWindow.bind(this, 'restore')}> <SvgIcon svgName="restore-icon" needPointer iconColor="#737780" iconSize={24} /> </div> ) : ( <div className="handle-icon-box" onClick={handleWindow.bind(this, 'max')}> <SvgIcon svgName="max-icon" needPointer iconColor="#737780" iconSize={24} /> </div> )} <div className="handle-icon-box handle-close-icon" onClick={handleWindow.bind(this, 'close')}> <SvgIcon svgName="close-icon" needPointer hasHover iconColor="#737780" hoverColor="#fff" iconSize={24} /> </div> </div> </div> )}export default memo(DesktopHeader)
在主进程中我们使用ipcMain.on来监听事件,并进行相应操作,并在相应操作之后在需要的时候发送事件到应用层。下面我们封装mainWindow.js里面包含创建窗口方法,获取窗口实例方法,获取窗口是否存在方法及事件监听方法 mainWindow.js
javascript复制代码const { BrowserWindow, ipcMain } = require('electron')const path = require('path')const isProduction = process.env.NODE_ENV === 'development'let mainWindow = nullfunction createMainWindow() { mainWindow = new BrowserWindow({ width: 1160, height: 752, minHeight: 632, minWidth: 960, show: false, frame: false, title: 'Harbour', webPreferences: { nodeIntegration: true, preload: path.resolve(__dirname, '../utils/contextBridge.js') }, icon: path.resolve(__dirname, '../assets/logo.png') }) if (isDevelopment) { mainWindow.loadURL('http://localhost:8000/') } else { const entryPath = path.resolve(__dirname, '../../build/index.html') mainWindow.loadFile(entryPath) } mainWindow.once('ready-to-show', () => { mainWindow.show() }) mainWindowListenEvents()}function mainWindowListenEvents() { ipcMain.on('mainWindow-min', () => { mainWindowIsExist() && mainWindow.minimize() }) ipcMain.on('mainWindow-max', () => { if (mainWindowIsExist()) { mainWindow.maximize() mainWindow.webContents.send('mainWindowIsMax', true) } }) ipcMain.on('mainWindow-restore', () => { if (mainWindowIsExist()) { mainWindow.unmaximize() mainWindow.webContents.send('mainWindowIsMax', false) } }) ipcMain.on('mainWindow-close', () => { mainWindowIsExist() && mainWindow.hide() }) ipcMain.on('mainWindow-open-devtool', () => { mainWindowIsExist() && mainWindow.webContents.openDevTools() })}function mainWindowIsExist() { return mainWindow && !mainWindow.isDestroyed()}function getMainWindow() { return mainWindow}module.exports = { getMainWindow, createMainWindow, mainWindowIsExist}
要实现窗口之间的通信,我们实际上就是使用应用层和主进程之间的通信,由于主进程可以接收到任意窗口发过来的事件,因此我们想实现窗口之间的通信,只需要在主进程中进行转发就好,下面是图解。
javascript复制代码// 主窗口ipcRendererSend('sendToSecond', '123')// 主进程ipcMain.on('sendToSecond', (e, data) => { secondWindow.webContents.send('sendToSecond', data)})// 第二窗口ipcRendererOn('sendToSecond', handler)
clipboard 是 Electron 提供的模块之一,用于在应用程序中进行剪贴板操作。它允许你读取和写入系统剪贴板中的文本、图像等数据。以下是一些常用的 clipboard 模块方法 clipboard.writeText(text[, type]) 将文本写入剪贴板。 text:要写入剪贴板的文本内容。 type(可选):可指定数据类型,默认为 clipboard。可以是 selection(用于选区)或 clipboard(用于剪贴板)。
javascript复制代码const { clipboard } = require('electron')clipboard.writeText('Hello, World!')
clipboard.readText([type]) 从剪贴板中读取文本内容。 type(可选):可指定数据类型,默认为 clipboard。可以是 selection(用于选区)或 clipboard(用于剪贴板)。
javascript复制代码const { clipboard } = require('electron')const text = clipboard.readText()console.log(text)
clipboard.writeHTML(markup[, type]) 将 HTML 内容写入剪贴板。 markup:要写入剪贴板的 HTML 内容。 type(可选):可指定数据类型,默认为 clipboard。可以是 selection(用于选区)或 clipboard(用于剪贴板)。
javascript复制代码const { clipboard } = require('electron')const html = '<div><h1>Hello, World!</h1></div>'clipboard.writeHTML(html)
clipboard.readHTML([type]) 从剪贴板中读取 HTML 内容。 type(可选):可指定数据类型,默认为 clipboard。可以是 selection(用于选区)或 clipboard(用于剪贴板)。
javascript复制代码const { clipboard } = require('electron')const html = clipboard.readHTML()console.log(html)
clipboard.writeImage(image[, type]) 将图像写入剪贴板。 image:要写入剪贴板的图像,可以是一个 nativeImage 对象或者一个文件路径。 type(可选):可指定数据类型,默认为 clipboard。可以是 selection(用于选区)或 clipboard(用于剪贴板)。
javascript复制代码const { clipboard, nativeImage } = require('electron')const image = nativeImage.createFromPath('/path/to/image.png')clipboard.writeImage(image)
clipboard.readImage([type]) 从剪贴板中读取图像。 type(可选):可指定数据类型,默认为 clipboard。可以是 selection(用于选区)或 clipboard(用于剪贴板)。
javascript复制代码const { clipboard } = require('electron')const image = clipboard.readImage()
clipboard.clear([type]) 清空剪贴板内容。 type(可选):可指定数据类型,默认为 clipboard。可以是 selection(用于选区)或 clipboard(用于剪贴板)。
javascript复制代码const { clipboard } = require('electron')clipboard.clear()
dialog 是 Electron 提供的模块之一,用于在桌面应用程序中创建对话框,以便与用户进行交互。它可以用于打开文件、保存文件、显示警告、错误等信息,以及进行用户输入的获取等操作。以下是一些常用的 dialog 模块方法
dialog.showOpenDialog([browserWindow, ]options)打开一个文件选择对话框,允许用户选择一个或多个文件。
javascript复制代码const { dialog } = require('electron');const options = { title: '选择文件', defaultPath: '/path/to/default/folder', filters: [ { name: 'Text Files', extensions: ['txt', 'text'] }, { name: 'All Files', extensions: ['*'] } ], properties: ['openFile', 'multiSelections']};dialog.showOpenDialog(null, options).then(result => { console.log(result.filePaths)}).catch(err => { console.log(err)})
dialog.showSaveDialog([browserWindow, ]options)打开一个文件保存对话框,允许用户选择保存的路径和文件名。
javascript复制代码const { dialog } = require('electron');const options = { title: '保存文件', defaultPath: '/path/to/default/folder', filters: [ { name: 'Text Files', extensions: ['txt', 'text'] }, { name: 'All Files', extensions: ['*'] } ]};dialog.showSaveDialog(null, options).then(result => { console.log(result.filePath);}).catch(err => { console.log(err);});
dialog.showMessageBox([browserWindow, ]options)显示一个消息框,通常用于警告或者通知用户。
javascript复制代码const { dialog } = require('electron');const options = { type: 'info', title: '信息', message: '这是一个信息框。', buttons: ['OK']};dialog.showMessageBox(null, options).then(result => { console.log(result.response);}).catch(err => { console.log(err);});
dialog.showErrorBox(title, content)显示一个错误框,用于显示错误信息。
javascript复制代码const { dialog } = require('electron');dialog.showErrorBox('发生错误', '这是一个错误框的示例。');
globalShortcut 是 Electron 提供的模块之一,用于注册和响应全局键盘快捷键。这允许你在你的 Electron 应用程序中创建全局快捷键,以执行特定操作或触发事件。以下是一些常用的 globalShortcut 模块方法 globalShortcut.register(accelerator, callback)注册全局快捷键。
javascript复制代码const { globalShortcut } = require('electron');globalShortcut.register('CmdOrCtrl+X', () => { // 执行某些操作});
globalShortcut.isRegistered(accelerator)检查是否已经注册了指定的全局快捷键。 accelerator:要检查的快捷键。
javascript复制代码const { globalShortcut } = require('electron');const isRegistered = globalShortcut.isRegistered('CmdOrCtrl+X');if (isRegistered) { console.log('已注册');} else { console.log('未注册');}
globalShortcut.unregister(accelerator)注销已注册的全局快捷键。 accelerator:要注销的快捷键。
javascript复制代码const { globalShortcut } = require('electron');globalShortcut.unregister('CmdOrCtrl+X');
globalShortcut.unregisterAll()注销所有已注册的全局快捷键。
javascript复制代码const { globalShortcut } = require('electron');globalShortcut.unregisterAll();
globalShortcut.getRegisteredKeys()获取当前已注册的全局快捷键的列表。
javascript复制代码const { globalShortcut } = require('electron');const registeredKeys = globalShortcut.getRegisteredKeys();console.log(registeredKeys);
Menu 是 Electron 中用于创建和管理应用程序菜单的模块。它允许你在应用程序的菜单栏、上下文菜单等位置定义菜单项,以便用户可以通过点击菜单项执行特定的操作。以下是一些常用的 Menu 模块方法和属性
创建顶级菜单javascript复制代码const { Menu } = require('electron');const template = [ { label: 'File', submenu: [ { role: 'openFile' }, { role: 'saveFile' }, { role: 'quit' } ] }, { label: 'Edit', submenu: [ { role: 'copy' }, { role: 'paste' } ] }];const menu = Menu.buildFromTemplate(template);Menu.setApplicationMenu(menu)创建上下文菜单javascript复制代码const { Menu } = require('electron');const template = [ { role: 'cut' }, { role: 'copy' }, { role: 'paste' }];const contextMenu = Menu.buildFromTemplate(template);
Menu.buildFromTemplate(template)从模板数组创建一个菜单。 template:一个包含菜单项的数组。每个菜单项都是一个对象,包含 label、click 等属性。 Menu.setApplicationMenu(menu)设置应用程序菜单,通常用于顶级菜单栏。 menu:要设置为应用程序菜单的 Menu 对象。
javascript复制代码const template = [ { label: 'File', submenu: [ { label: 'Open', accelerator: 'CmdOrCtrl+O', click: () => { /* 打开文件 */ } }, { role: 'save' }, { type: 'separator' }, // 分隔线 { role: 'quit' } ] }, { label: 'Edit', submenu: [ { role: 'copy' }, { role: 'cut' }, { role: 'paste' }, { label: 'Select All', accelerator: 'CmdOrCtrl+A', click: () => { /* 选择所有内容 */ } } ] }];
nativeImage 是 Electron 提供的模块之一,用于处理图像。它可以加载图像文件、从屏幕截取图像、创建空白图像等。nativeImage 支持跨平台,可以在主进程和渲染进程中使用。
javascript复制代码const { nativeImage } = require('electron');const emptyImage = nativeImage.createEmpty();
javascript复制代码const { nativeImage } = require('electron');const imagePath = '/path/to/image.png';const image = nativeImage.createFromPath(imagePath);
javascript复制代码const { nativeImage } = require('electron');const fs = require('fs');const buffer = fs.readFileSync('/path/to/image.png');const image = nativeImage.createFromBuffer(buffer);
javascript复制代码const { nativeImage } = require('electron');const dataURL = 'data:image/png;base64,iVBORw0KGg...';const image = nativeImage.createFromDataURL(dataURL);
javascript复制代码const { nativeImage } = require('electron');const image = nativeImage.createFromNamedImage('NSStopProgressTemplate', { h: 0, s: 0, l: 0 });
javascript复制代码const { nativeImage } = require('electron');const imagePath = '/path/to/image.png';const size = { width: 100, height: 100 };nativeImage.createThumbnailFromPath(imagePath, size, (thumbnail) => { console.log(thumbnail);});
javascript复制代码const { nativeImage } = require('electron');const imagePath = '/path/to/image.png';const image = nativeImage.createFromPath(imagePath);console.log(nativeImage.isMacTemplateImage(image)); // true or false
javascript复制代码const { nativeImage } = require('electron');const imagePath = '/path/to/image.png';const image = nativeImage.createFromPath(imagePath);const dataURL = image.toDataURL({ scaleFactor: 2.0 });
screen 是 Electron 提供的模块之一,用于获取有关屏幕和显示器的信息,以及执行与屏幕相关的操作。以下是一些常用的 screen 模块方法和属性:
在我们开发过程中,需要经常用的控制台,而我们在开发时直接打开控制台又有些不友好。还有生产环境中我们有时也需要打开控制台定位一些问题,但是生产环境又不能那么轻易让用户能打开控制台,因此我们可以在开发环境和生产环境分别预留一个接口打开控制台,生产环境的方式要复杂一些。 实现思路 我们可以在应用层监听键盘事件,当在开发环境中,按下ctrl + F12时,我们就打开控制台。 而在生产环境中我们需要设计的复杂一些,可以在代码中放一个不显示的输入框(设置宽高边框均为0,并且固定定位就好),当按下特殊组合键时,聚焦输入框,并输入openDevtool之后打开控制台,我这里设置的组合键为ctrl + win + alt + F12 代码如下
javascript复制代码import React, { useEffect, useRef, useCallback, ChangeEvent } from 'react'import DesktopHeader from '@components/desktopHeader'import './app.less'import { isDesktop, getProcessNodeEnv, ipcRendererSend} from '@common/desktopUtils'import electronImg from '@assets/electronImg.png'function App() { const openDevtoolInput = useRef<HTMLInputElement>(null) const isDevelopment = useRef(getProcessNodeEnv() === 'development') const openDevtool = useCallback(() => { ipcRendererSend('mainWindow-open-devtool') }, []) const openDevtoolInputChange = (event: ChangeEvent<HTMLInputElement>) => { const { value } = event.target if (value === 'openDevtool') { openDevtool() } } useEffect(() => { document.addEventListener('keydown', (e) => { const { ctrlKey, metaKey, altKey, key } = e // 开发环境使用ctrl + F12打开控制台 if (isDevelopment.current && ctrlKey && key === 'F12') { openDevtool() } // 开发环境使用ctrl + win + alt + F12,然后键入'open devtool'打开控制台 if (!isDevelopment.current && ctrlKey && metaKey && altKey && key === 'F12') { if (openDevtoolInput.current) { openDevtoolInput.current.focus() } } }) }, [openDevtool]) return ( <div id="electron-app"> {!isDevelopment.current && ( <input className="open-devtool-input" ref={openDevtoolInput} type="text" onChange={openDevtoolInputChange} onBlur={(e) => { e.target.value = '' }} /> )} {isDesktop() && <DesktopHeader />} <div className={isDesktop() ? 'desktop-app-content' : 'app-content'}> <div className="electron-img"> <img src={electronImg} alt="" /> </div> </div> </div> )}export default App在主进程监听该事件并打开控制台javascript复制代码ipcMain.on('mainWindow-open-devtool', () => { mainWindowIsExist() && mainWindow.webContents.openDevTools()})
作者:Harbour 链接:
https://juejin.cn/post/7277799192961925172 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。