Appearance
electron-vite 笔记
https://cn-evite.netlify.app/guide/debugging.html
前后通讯
ts
// 主进程发
mainWindow.webContents.send('dm-message-to-client', JSON.stringify(msg))
// 渲染进程收
window.electron.ipcRenderer.on('dm-message-to-client',(msg)=>{
console.log(msg)
})
// 渲染进程发
window.electron.ipcRenderer.send('openPage', window.encodeURIComponent(appUrl))
// 主进程收
ipcMain.on('openPage', (msg)=>{
console.log(msg)
})
注意主进程给渲染进程发消息要在渲染进程准备就绪
ts
mainWindow.webContents.on('did-finish-load', () => {
mainWindow.webContents.send('version', version)
// 将 port 告诉渲染进程
mainWindow.webContents.send('port', port)
})
消息发送时最好是字符串,不然可能开发环境好好的,打包后不行了
注册快捷键
主进程入口
ts
import { app, BrowserWindow, globalShortcut, dialog } from 'electron'
globalShortcut.register('F12', () => {
BrowserWindow.getAllWindows().forEach((item) => {
item.webContents.openDevTools()
})
})
globalShortcut.register('F5', () => {
// 当前激活的窗口刷新
BrowserWindow.getAllWindows().forEach((item) => {
if (item.isFocused()) {
item.reload()
}
})
})
多窗口
https://cn-evite.netlify.app/guide/dev.html#多窗口应用程序
以上是官方的多窗口配置
个人为了简介 引入 vue-router 来实现多窗口管理 createWindow.ts
ts
import { BrowserWindow, Menu } from 'electron'
import { join } from 'path'
import { is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'
export default function (options) {
// shell.openExternal(arg)
// 打开一个新的浏览器窗口
const win = new BrowserWindow({
width: options.width || 400,
height: options.height || 800,
autoHideMenuBar: true,
...(process.platform === 'linux' ? { icon } : {}),
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
nodeIntegration: true,
contextIsolation: false,
sandbox: false,
devTools: true
}
})
// win.webContents.setWindowOpenHandler((details) => {
// shell.openExternal(details.url)
// return { action: 'deny' }
// })
// 添加右键菜单
win.webContents.on('context-menu', (_e, params) => {
const { x, y } = params
Menu.buildFromTemplate([
{
label: '刷新',
click: () => {
win.webContents.reload()
}
},
{
label: 'Inspect element',
click: () => {
win.webContents.inspectElement(x, y)
}
}
]).popup({ window: win })
})
// win.webContents.openDevTools()
win.on('ready-to-show', () => {
win.show()
})
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
win.loadURL(process.env['ELECTRON_RENDERER_URL'] + `/index.html#${options.hash}`)
} else {
win.loadFile(join(__dirname, `../renderer/index.html`), options)
}
return win
}
渲染进程入口main.ts
ts
import { createApp } from 'vue'
import App from './App.vue'
import Home from './app/Home.vue'
import Iframe from './Iframe.vue'
import * as VueRouter from 'vue-router'
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: Iframe }
]
// 3. 创建路由实例并传递 `routes` 配置
// 你可以在这里输入更多的配置,但我们在这里
// 暂时保持简单
const router = VueRouter.createRouter({
// 4. 内部提供了 history 模式的实现。为了简单起见,我们在这里使用 hash 模式。
history: VueRouter.createWebHashHistory(),
routes // `routes: routes` 的缩写
})
createApp(App).use(router).mount('#app')
App.vue
入口
vue
<script setup lang="ts">
import { useRouter } from 'vue-router'
const $router = useRouter()
const $route = $router.currentRoute.value.fullPath
console.log($route)
</script>
<template>
<router-view></router-view>
</template>
<style lang="less">
@import './assets/css/styles.less';
</style>
创建新窗口
ts
const win = createWindow({
hash: `about?appUrl=${arg}`
})
这么做会导致一个问题就是后续想通过 win.webContents.send
发送消息时,是没办法区分窗口. 因为是个 spa 应用,属于同一个窗口,同一个渲染进程
获取所有窗口 发消息
ts
import { ipcMain, BrowserWindow } from 'electron'
BrowserWindow.getAllWindows().forEach((window) => {
window.webContents.send('dm-message-to-client', msg)
})
控制打开 webview 的大小
https://developer.mozilla.org/zh-CN/docs/Web/API/Window/open
ts
window.open(url,'_blank','width=400,height=800')
打开第三方应用
安装 node 模块 open npm i open
ts
open.openApp('QQ')
启动协议
https://www.electron.build/configuration/configuration#overridable-per-platform-options
electron-builder.yml
配置
yaml
protocols:
name: banban-barrage
schemes:
- banban-barrage
role: Editor
主进程入口
ts
/// mac linux
if (process.defaultApp) {
if (process.argv.length >= 2) {
app.setAsDefaultProtocolClient('banban-barrage', process.execPath, [resolve(process.argv[1])])
}
} else {
app.setAsDefaultProtocolClient('banban-barrage')
}
app.on('open-url', (_event, _url) => {
dialog.showErrorBox('欢迎回来', `导向自: ${_url}`)
})
/// win
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit()
} else {
app.on('second-instance', (_event, commandLine: any, _workingDirectory) => {
// 用户正在尝试运行第二个实例,我们需要让焦点指向我们的窗口
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore()
mainWindow.focus()
}
// the commandLine is array of strings in which last element is deep link url
// the url str ends with /
dialog.showErrorBox('Welcome Back', `You arrived from: ${commandLine.pop().slice(0, -1)}`)
})
}
打包后使用安装程序安装.dmg
或setup.exe
安装后在浏览器输入 banban-barrage://open
就可以调起应用
打包后调试
https://github.com/pd4d10/debugtron
配合 playwright
ts
const browser = await chromium.launch(
{
channel: 'chrome',
headless: false,
args: ['--disable-blink-features=AutomationControlled']
}
)
const page = await browser.newPage()
await page.on('websocket', (ws) => {
console.log(`WebSocket opened: ${ws.url()}>`)
ws.on('framereceived', (event) => {
// DmByte({ msg: event.payload, platform: platform }, (msg) => {
// IpcMainListener.sendMessageToClient(msg)
// })
})
ws.on('close', () => console.log('WebSocket closed'))
})
await page.goto(url, { timeout: 0 })
playwright 去掉无痕模式
ts
const browser = await chromium.launchPersistentContext(
path.resolve(process.execPath, '../Resources/userData'),
{
channel: 'chrome',
headless: false,
args: ['--disable-blink-features=AutomationControlled']
}
)
ts
console.log('__dirname', __dirname)
console.log('execPath', process.execPath)
console.log('cwd', process.cwd())
node下获取当前程序的当前路径,确保路径正确打包后才能启动浏览器
设置托盘图片
文档 https://www.electronjs.org/zh/docs/latest/api/tray
示例
ts
import { app, BrowserWindow, globalShortcut, Menu, Tray } from 'electron'
import icon from '../../resources/16x16.png?asset'
// app.ready
// ================== 托盘 ===============
const tray = new Tray(icon)
tray.setToolTip('互动助手')
const contextMenu = Menu.buildFromTemplate([
{
label: '互动助手',
click: () => {
// 是否是mac
if (!mainWindow || mainWindow.isDestroyed()) {
mainWindow = createMainWindow({ width: 600, hash: '' })
IpcMainListener.$listeners(mainWindow, expressWs)
} else {
mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show()
}
}
},
{
label: '退出',
click: () => {
mainWindow.destroy()
app.exit(0)
app.quit()
}
}
])
tray.setContextMenu(contextMenu)
tray.setTitle('1')
app.dock.setBadge('1')
tray.on('click', () => {})
开机自启
ts
app.setLoginItemSettings({
openAtLogin: true,
path: process.execPath,
args: []
})
调试技巧: 1.dev开发模式下,可以使用debugger,断点调试奇怪问题 2.在打包前,先运行preview,模式打包后的情况,有问题,容易发现 3.打包后有问题,可以使用--trace-warnings 运行看看错误 4.通常preview正常,而打包后不正常,大概率就是有些包没有打包进去,这时候就要检查依赖安装是否正确,也有可能是pnpm问题(如果使用的话) 这几步走下来,遇到的问题都会很快找到