文章目录
- 一、使用electron-vite新建项目
- 二、目录结构
- 三、渲染进程调用主进程
- 1、方式一 —— 允许有返回值
- · src/main/index.js
- · src/preload/index.js
- · src/renderer/index.html
- 2、方式二—— 允许有返回值 (推荐写法)
- · src/main/index.js
- · src/preload/index.js
- · src/renderer/index.html
- 3、方式三 —— 无返回值,不等待响应
- · src/main/index.js
- · src/preload/index.js
- · src/renderer/index.html
- 四、主进程触发渲染进程
- · src/main/index.js
- · src/preload/index.js
- · src/renderer/index.html
- 五、功能介绍
- 1、透明窗口设置
- 2、系统截图功能
- 3、系统托盘图标及闪烁功能
- 六、问题处理
- 1、图标打包路径无法获取问题
- · 修改图标路径获取
- 2、允许更改安装目录
- · package.json 配置
- 3、loadFile添加参数
- 4、http数据请求与cookie设置
- 5、iframe CSP
- 6、多页面打包
- · electron.vite.config.mjs
- 7、路由切换主进程代码多次执行
- 七、npm库
- References
一、使用electron-vite新建项目
- 1、npm命令
npm create @quick-start/electron
- 2、yarn命令
yarn create @quick-start/electron
- 3、electron镜像地址:
electron_mirror=https://npmmirror.com/mirrors/electron/
electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/
- 4、填写项目名称
? Project name: electron-vue-app
- 5、选择vue框架:
? Select a framework:
vanilla
> vue
react
svelte
solid
- 6、其他配置项
? Add TypeScript? » No / Yes
Yes
? Add Electron updater plugin? » No / Yes
Yes
? Enable Electron download mirror proxy? » No / Yes
Yes
二、目录结构
├─ /.vscode
├─ /build
├─ /node_modules
├─ /out # 运行时的输出目录
├─ /resources # 主进程和预加载脚本资源文件目录
├─ /src
| ├─ /main # Electron主进程
| ├─ /preload # Electron预加载脚本
| └─ /renderer # Electron渲染进程, vue常规目录结构
| | ├─ /assets
| | ├─ /components
| | ├─ /views
| | ├─ App.vue
| | ├─ main.js
| | └─ index.html
├─ .editorconfig
├─ .eslintignore
├─ .eslintrc.cjs
├─ .gitignore
├─ .npmrc
├─ .prettierignore
├─ .prettierrc.yaml
├─ dev-app-update.yml
├─ electron-builder.yml
├─ electron.vite.config.mjs
├─ package.json
├─ README.md
三、渲染进程调用主进程
1、方式一 —— 允许有返回值
· src/main/index.js
import { app, ipcMain, BrowserWindow } from 'electron'
app.whenReady().then(() => {
// ...
new BrowserWindow({
width: 800,
height: 600,
// 其他窗口配置...
})
ipcMain.handle('renderderCallMain', async (event, value) => {
console.log('renderder call main...', value)
})
// ...
})
· src/preload/index.js
import { contextBridge } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('electron', electronAPI)
} catch (error) {
console.error(error)
}
} else {
window.electron = electronAPI
}
· src/renderer/index.html
window.electron.ipcRenderer.invoke('renderderCallMain', 'hello world!')
2、方式二—— 允许有返回值 (推荐写法)
· src/main/index.js
import { app, ipcMain, BrowserWindow } from 'electron'
app.whenReady().then(() => {
// ...
new BrowserWindow({
width: 800,
height: 600,
// 其他窗口配置...
})
ipcMain.handle('renderderCallMain', async (event, value) => {
console.log('renderder call main...', value)
})
// ...
})
· src/preload/index.js
import { contextBridge, ipcRenderer } from 'electron'
const call = {
renderderCallMain: (value) => ipcRenderer.invoke('renderderCallMain', value),
}
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('call', call)
} catch (error) {
console.error(error)
}
} else {
window.call = call
}
· src/renderer/index.html
window.call.renderderCallMain('hello world!')
3、方式三 —— 无返回值,不等待响应
· src/main/index.js
import { app, ipcMain, BrowserWindow } from 'electron'
app.whenReady().then(() => {
// ...
new BrowserWindow({
width: 800,
height: 600,
// 其他窗口配置...
})
ipcMain.on('ping', () => console.log('pong'))
// ...
}
· src/preload/index.js
import { contextBridge, ipcRenderer } from 'electron'
const call = {
ping: () => ipcRenderer.send('ping'),
}
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('call', call)
} catch (error) {
console.error(error)
}
} else {
window.call = call
}
· src/renderer/index.html
window.call.ping()
四、主进程触发渲染进程
· src/main/index.js
import { app, globalShortcut, BrowserWindow } from 'electron'
app.whenReady().then(() => {
// ...
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
// 其他窗口配置...
})
globalShortcut.register('CommandOrControl+S', () => {
mainWindow.webContents.send('commandOrControlS', 'command or control + s...')
})
// ...
})
· src/preload/index.js
import { contextBridge, ipcRenderer } from 'electron'
const listen = {
commandOrControlS: (callback) => ipcRenderer.on('commandOrControlS', async (event, value) => callback(value)),
}
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('listen', listen)
} catch (error) {
console.error(error)
}
} else {
window.listen = listen
}
· src/renderer/index.html
window.listen.commandOrControlS((value) => {
console.log(value)
})
五、功能介绍
1、透明窗口设置
const mainWindow = new BrowserWindow({
width: 80,
height: 162,
frame: false, // 透明窗口
transparent: true, // 透明窗口
backgroundColor: '#00000000', // 透明窗口
autoHideMenuBar: true, // 透明窗口
})
2、系统截图功能
import { desktopCapturer } from 'electron'
async function desktopCapturerHandle() {
const sources = await desktopCapturer.getSources({
types: ['window'],
thumbnailSize: {
width: 1920,
height: 1080,
},
})
return sources[0]?.thumbnail.toDataURL('image/png'),
}
3、系统托盘图标及闪烁功能
import { app, BrowserWindow, Tray, Menu } from 'electron'
let flickerTimer = null
app.whenReady().then(() => {
// ...
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
// 其他窗口配置...
})
const tray = new Tray('./icon.ico')
const contextMenu = Menu.buildFromTemplate([
{ label: '显示 ', click: () => {
mainWindow.show()
stopTray(tray)
}},
{ type: 'separator' },
{ label: '退出 ', click: () => {
mainWindow.off('close', closeHandle)
app.quit()
}},
])
tray.setContextMenu(contextMenu)
tray.on('double-click', function() {
mainWindow.show() // 点击图标时恢复窗口
stopTray(tray)
})
tray.on('click', function() {
if (flickerTimer) {
mainWindow.show() // 点击图标时恢复窗口
stopTray(tray)
}
})
// ...
})
function flickerTray(tray) {
if (!flickerTimer) {
let hasIco = false
flickerTimer = setInterval(() => {
// 图标与透明图标切换以实现闪烁功能
tray.setImage(hasIco ? './icon.ico' : './empty.ico')
hasIco = !hasIco
}, 500)
}
}
function stopTray(tray) {
if (flickerTimer) {
clearInterval(flickerTimer)
flickerTimer = null
}
tray.setImage('./icon.ico')
}
六、问题处理
1、图标打包路径无法获取问题
· 修改图标路径获取
import { app } from 'electron'
const path = require('path')
function resolvePath() {
const publicPath = 'build'
const fileName = 'icon.ico'
if (process.env.NODE_ENV === 'development') {
return './' + publicPath + '/' + fileName
}
return path.join(path.dirname(app.getPath('exe')), '/resources/' + publicPath + '/' + 'fileName')
}
· package.json 配置图标打包
{
...
"build": {
"extraResources": [
{
"from": "./build",
"to": "./build"
}
]
}
...
}
图标大小需 256x256
,格式为ico
2、允许更改安装目录
· package.json 配置
{
...
"build": {
"nsis": {
"allowToChangeInstallationDirectory": true, // 允许更改安装目录
"installerIcon": "./build/icon.ico", // 安装图标
"uninstallerIcon": "./build/icon.ico", // 卸载图标
"installerHeaderIcon": "./build/icon.ico", // 安装程序头部图标
"createDesktopShortcut": true, // 创建桌面快捷方式
"createStartMenuShortcut": false, // 加入开始菜单
}
}
...
}
3、loadFile添加参数
import { app, BrowserWindow } from 'electron'
import { join } from 'path'
app.whenReady().then(() => {
// ...
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
// 其他窗口配置...
})
mainWindow.loadFile(join(__dirname, '../renderer/index.html'), {
search: 'id=test',
})
// ...
})
4、http数据请求与cookie设置
- 1)、由于
fill://
协议无法设置cookie,所以请求只能在主进程发起 - 2)、使用Chromium的原生网络库发出HTTP / HTTPS请求 或 Node.js的HTTP 和 HTTPS 模块
const { app } = require('electron')
app.whenReady().then(() => {
const { net } = require('electron')
const request = net.request('https://github.com')
request.on('response', (response) => {
console.log(response)
response.on('data', (chunk) => {
console.log('BODY: ' + chunk)
})
response.on('end', () => {
console.log('No more data in response.')
})
})
request.end()
})
- 3)、从请求结果中
set-cookie
获取cookie并返回给渲染端进行存储
5、iframe CSP
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' https://example.com; img-src https://example.com"
/>
修改html中的meta属性,这将允许在iframe中加载来自 self
和 example.com
域的脚本,并允许加载来自 example.com
域的图像
6、多页面打包
· electron.vite.config.mjs
export default defineConfig({
renderer: {
build: {
rollupOptions: {
input: {
index: './src/renderer/index.html',
other: './src/renderer/other.html',
}
}
}
}
})
7、路由切换主进程代码多次执行
检查 ipcRenderer.on()
路由切换后是否多次在 onMounted 注册
,需在 onBeforeUnmount
中及时调用主进程 ipcRenderer.removeListener()
注销事件的监听
七、npm库
- 1、
"@jitsi/robotjs": "^0.6.11"
适用于自动化工具,有控制I/O、系统截图等功能 - 2、
"tesseract.js": "^5.0.5"
基于js的图像识别 - 3、
"jimp": "^0.22.12"
图像处理工具,有图片裁剪、保存、图片大小更改等功能 - 4、
"nodemailer": "^6.9.13"
基于node的邮件发送工具
References
[1] Electron文档
[2] 基于electron-vite构建Vue桌面客户端