# electron-vue3-ts **Repository Path**: yindj/electron-vue3-ts ## Basic Information - **Project Name**: electron-vue3-ts - **Description**: vue3 +ts +electron - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2023-05-18 - **Last Updated**: 2024-03-04 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Vue 3 + TypeScript + Vite + Electron ## 开发vite插件搭建Electron项目 ## electron运行原理 ### 主进程 - 可以看做是 package.json 中 main 属性对应的文件 - 一个应用只会有一个主进程 - 只有主进程可以进行 GUI 的 API 操作 ### 渲染进程 - Windows 中展示的界面通过渲染进程表现 - 一个应用可以有多个渲染进程 ## 生命周期 - [x] ready: app 初始化完成 - [ ] dom-ready: 一个窗口中的文本加载完成 - [ ] did-finsh-load: 导航完成时触发 - [ ] window-all-closed: 所有窗口都被关闭时触发 - [ ] before-quit: 在关闭窗口之前触发 - [ ] will-quit: 在窗口关闭并且应用退出时触发 - [ ] quit: 当所有窗口被关闭时触发 - [ ] closed: 当窗口关闭时触发,此时应删除窗口引用 ```js const { app, BrowserWindow } = require('electron') // 创建窗口 function createWindow() { let mainWin = new BrowserWindow({ width: 800, height: 400 }) mainWin.loadFile('index.html') mainWin.webContents.on('did-finish-load', () => { console.log('33333--->did-finish-load') }) mainWin.webContents.on('dom-ready', () => { console.log('22222--->dom-ready') }) mainWin.on('close', () => { console.log('88888--->this window is closed') mainWin = null }) } app.on('ready', () => { console.log('11111----->ready') createWindow() }) app.on('window-all-closed', () => { console.log('44444---->window-all-closed') app.quit() }) app.on('before-quit', () => { console.log('5555->before-quit') }) app.on('will-quit', () => { console.log('66666->will-quit') }) app.on('quit', () => { console.log('777777-quitquit') }) ``` ### 设置窗口大小 ```js const { app, BrowserWindow } = require('electron') // 将创建窗口独立成一个函数 function createWindow() { let mainWin = new BrowserWindow({ x: 100, y: 100, // 设置窗口显示的位置,相对于当前屏幕的左上角 show: false, // 默认情况下创建一个窗口对象之后就会显示,设置为false 就不会显示了 width: 800, height: 400, maxHeight: 600, maxWidth: 1000, minHeight: 200, minWidth: 300, // 可以通过 min max 来设置当前应用窗口的最大和最小尺寸 resizable: false // 是否允许缩放应用的窗口大小 }) mainWin.loadFile('index.html') mainWin.on('ready-to-show', () => { mainWin.show() }) mainWin.on('close', () => { console.log('mainWin is closed') mainWin = null }) } app.on('ready', createWindow) app.on('window-all-closed', () => { console.log('all window is closed') app.quit() }) ``` ### 自定义窗口 ```js let mainWin = new BrowserWindow({ show: true, width: 800, height: 600, frame: true, // 用于自定义 menu ,设置为 false 可以将默认的菜单栏隐藏 // transparent: true, autoHideMenuBar: true, icon: 'lg.ico', // 设置一个图片路径,可以自定义当前应用的显示图标 title: "拉勾教育", // 自定义当前应用的显示标题 webPreferences: { nodeIntegration: true, enableRemoteModule: true } }) ``` ### 创建新窗口 ```js // 主进程 let mainWin = new BrowserWindow({ frame: false, width: 800, height: 400, webPreferences: { // 用于控制窗口加载的网页是否集成 node.js 环境 nodeIntegration: true, enableRemoteModule: true } }) // 渲染进程 const { remote } = require("electron") window.addEventListener('DOMContentLoaded',()=>{ const oBtn = document.getElementById('btn'); oBtn.addEventListener('click',()=>{ const render = new remote.BrowseWindow({ width: 200, heigth: 200 }) render.loadFile('list.html'); render.on("close",()=>{ render = null; }) }) }) ``` ### 窗口的最大化和最小化 ```js // 渲染进程 const { remote } = require('electron') window.addEventListener('DOMContentLoaded', () => { // 利用 remote 可以获取当前窗口对象 let mainWin = remote.getCurrentWindow() // 获取元素添加点击操作的监听 let aBtn = document.getElementsByClassName('windowTool')[0].getElementsByTagName('div') aBtn[0].addEventListener('click', () => { // 当前事件发生后说明需要关闭窗口 mainWin.close() }) aBtn[1].addEventListener('click', () => { // 这里需要执行的最大化操作 console.log(mainWin.isMaximized()) if (!mainWin.isMaximized()) { mainWin.maximize() // 让当前窗口最大化 } else { mainWin.restore() // 回到原始的状态 } }) aBtn[2].addEventListener('click', () => { // 实现最小化 if (!mainWin.isMinimized()) { mainWin.minimize() } }) }) ``` ### 阻止窗口直接关闭,加点提示语 ```js window.onbeforeunload = function () { let oBox = document.getElementsByClassName('isClose')[0] oBox.style.display = 'block' let yesBtn = oBox.getElementsByTagName('span')[0] let noBtn = oBox.getElementsByTagName('span')[1] yesBtn.addEventListener('click', () => { mainWin.destroy() }) noBtn.addEventListener('click', () => { oBox.style.display = 'none' }) return false } ``` ### 子窗口模态窗口 ```js const { remote } = require('electron') window.addEventListener('DOMContentLoaded', () => { let oBtn = document.getElementById('btn') oBtn.addEventListener('click', () => { let subWin = new remote.BrowserWindow({ parent: remote.getCurrentWindow(),// 设置父级窗口 width: 200, height: 200, modal: true // 设置模态窗口 }) subWin.loadFile('sub.html') subWin.on('close', () => { subWin = null }) }) }) ``` ### 自定义菜单 ```js console.log(process.platform) // 获取平台类型 // 定义自己需要的菜单项 let menuTemp = [ { label: '文件', submenu: [ { label: '打开文件', // 这里是在主进程显示的? click() { console.log('当前需要做的就是打开某一个具体的文件') } }, { type: 'separator' }, { label: '关闭文件夹' }, { label: '关于', role: 'about' } ] }, { label: '编辑' } ] // 利用上述的模板然后生成一个菜单项 let menu = Menu.buildFromTemplate(menuTemp) // 将上述的自定义菜单添加到应用里 Menu.setApplicationMenu(menu) ``` ### 菜单角色和类型 ```js // 01 自定义菜单的内容 let menuTemp = [ { label: '角色', submenu: [ { label: '复制', role: 'copy' }, { label: '剪切', role: 'cut' }, { label: '粘贴', role: 'paste' }, { label: '最小化', role: 'minimize' }, ] }, { label: '类型', submenu: [ { label: '选项1', type: 'checkbox' }, { label: '选项2', type: 'checkbox' }, { label: '选项3', type: 'checkbox' }, { type: "separator" },// 设置分隔符 { label: 'item1', type: "radio" }, { label: 'item2', type: "radio" }, { type: "separator" }, { label: 'windows', type: 'submenu', role: 'windowMenu' } // 还是个菜单 ] }, { label: '其它', submenu: [ { label: '打开', icon: './open.png', accelerator: 'ctrl + o',// 设置快捷键 click() { console.log('open操作执行了') } } ] } ] // 02 依据上述的数据创建一个 menu let menu = Menu.buildFromTemplate(menuTemp) // 03 将上述的菜单添加至 app 身上 Menu.setApplicationMenu(menu) ``` ### 动态创建菜单 ```js const { remote } = require('electron') const Menu = remote.Menu const MenuItem = remote.MenuItem window.addEventListener('DOMContentLoaded', () => { // 获取要应的元素 let addMenu = document.getElementById('addMenu') let menuCon = document.getElementById('menuCon') let addItem = document.getElementById('addItem') // 自定义全局变量存放菜单项 let menuItem = new Menu() // 生成自定义的菜单 addMenu.addEventListener('click', () => { // 创建菜单 let menuFile = new MenuItem({ label: '文件', type: 'normal' }) let menuEdit = new MenuItem({ label: '编辑', type: 'normal' }) let customMenu = new MenuItem({ label: '自定义菜单项', submenu: menuItem }) // 将创建好的自定义菜单添加至 menu let menu = new Menu() menu.append(menuFile) menu.append(menuEdit) menu.append(customMenu) // 将menu 放置于 app 中显示 Menu.setApplicationMenu(menu) }) // 动态添加菜单项 addItem.addEventListener('click', () => { // 获取当前 input 输入框当中的内容 let con = menuCon.value.trim() if (con) { menuItem.append(new MenuItem({ label: con, type: 'normal' })) menuCon.value = '' } }) }) ``` ### 自定义右键菜单 ```js const { remote } = require('electron') const Menu = remote.Menu // 定义菜单的内容 let contextTemp = [ { label: 'Run Code' }, { label: '转到定义' }, { type: 'separator' }, { label: '其它功能', // 这是在渲染进程中实现的,所以在浏览器中查看,主进程中 // 看不到效果 click() { console.log('其它功能选项被点击了') } }, ] // 依据上述的内容来创建 menu let menu = Menu.buildFromTemplate(contextTemp) // 给鼠标右击添加监听 window.addEventListener('DOMContentLoaded', () => { window.addEventListener('contextmenu', (ev) => { ev.preventDefault() menu.popup({ window: remote.getCurrentWindow() }) }, false) }) ``` ## 进程间通信 ### 异步通信 ```js // 渲染进程向主进程发送消息异步消息 const { ipcRenderer } = require('electron') // 01 采用异步的 API 在渲染进程中给主进程发送消息 aBtn[0].addEventListener('click', () => { ipcRenderer.send('msg1', '当前是来自于渲染进程的一条异步消息') }) // 2.主进程接收到消息后,发送确认消息 ipcMain.on('msg1', (ev, data) => { console.log(data) // ev.sender == ipcMain ev.sender.send('msg1Re', '这是一条来自于主进程的异步消息') }) // 3. 渲染进程接收到主进程的确认消息 // 当前区域是接收消息 ipcRenderer.on('msg1Re', (ev, data) => { console.log(data) }) ``` ### 同步通信 ```js // 1.渲染进程向主进程发送同步消息 aBtn[1].addEventListener('click', () => { let val = ipcRenderer.sendSync('msg2', '同步消息') // 3. val便是主进程发送回来的同步消息 console.log(val) }) // 2. 主进程接收同步消息并回传 ipcMain.on('msg2', (ev, data) => { console.log(data) ev.returnValue = '来自于主进程的同步消息' }) ``` ### 主进程与渲染进程通信 ```js // 1. 主进程主动发送异步消息 let temp = [ { label: 'send', click() { BrowserWindow.getFocusedWindow().webContents.send('mtp', '来自于自进程的消息') } } ] let menu = Menu.buildFromTemplate(temp) Menu.setApplicationMenu(menu) mainWin.loadFile('index.html') // 打开终端面板 mainWin.webContents.openDevTools() // 2. 渲染进程接收消息 ipcRenderer.on('mtp', (ev, data) => { console.log(data) }) ``` ### 渲染进程之间通信 #### 通过localStorage ```js // 1.主进程中控制创建渲染进程 const { app, BrowserWindow, ipcMain } = require('electron') // 定义全局变量存放主窗口 Id let mainWinId = null const createWindow = function () { let mainWin = new BrowserWindow({ frame: true, show: false, title: '拉勾教育', width: 800, height: 600, webPreferences: { nodeIntegration: true, enableRemoteModule: true } }) mainWin.loadFile('index.html') mainWinId = mainWin.id mainWin.on('ready-to-show', () => { mainWin.show() }) mainWin.on('close', () => { mainWin = null }) } // 3.接收渲染进程1发送的消息后,创建渲染进程2 ipcMain.on('openWin2', () => { // 接收到渲染进程中按钮点击信息之后完成窗口2 的打开 let subWin1 = new BrowserWindow({ width: 400, height: 300, parent: BrowserWindow.fromId(mainWinId), webPreferences: { nodeIntegration: true, enableRemoteModule: true } }) subWin1.loadFile('subWin1.html') subWin1.on('close', () => { subWin1 = null }) }) app.on('ready', createWindow) app.on('window-all-closed', () => { app.quit() }) // 2.渲染进程一 发送消息到主进程,保存信息到localStorage中 const { ipcRenderer } = require('electron') window.onload = function () { // 获取元素 let oBtn = document.getElementById('btn') oBtn.addEventListener('click', () => { ipcRenderer.send('openWin2') // 打开窗口2之后,保存数据至.... localStorage.setItem('name', '拉勾教育') }) } // 4.渲染进程2 从localStorage中获取值 window.onload = function () { let oInput = document.getElementById('txt') let val = localStorage.getItem('name') oInput.value = val } ``` ### 渲染进程以主进程为媒介进行通信 ```js // 1. 主进程 const { app, BrowserWindow, ipcMain } = require('electron') // 定义全局变量存放主窗口 Id let mainWinId = null const createWindow = function () { let mainWin = new BrowserWindow({ frame: true, show: false, title: '拉勾教育', width: 800, height: 600, webPreferences: { nodeIntegration: true, enableRemoteModule: true } }) mainWin.loadFile('index.html') mainWinId = mainWin.id mainWin.on('ready-to-show', () => { mainWin.show() }) mainWin.on('close', () => { mainWin = null }) } // 接收其它进程发送的数据,然后完成后续的逻辑 ipcMain.on('openWin2', (ev, data) => { // 接收到渲染进程中按钮点击信息之后完成窗口2 的打开 let subWin1 = new BrowserWindow({ width: 400, height: 300, parent: BrowserWindow.fromId(mainWinId), webPreferences: { nodeIntegration: true, enableRemoteModule: true } }) subWin1.loadFile('subWin1.html') subWin1.on('close', () => { subWin1 = null }) // 此时我们是可以直接拿到 sub 进程的窗口对象,因此我们需要考虑的就是等到它里面的所有内容 // 加载完成之后再执行数据发送 subWin1.webContents.on('did-finish-load', () => { subWin1.webContents.send('its', data) }) }) ipcMain.on('stm', (ev, data) => { // 当前我们需要将 data 经过 main 进程转交给指定的渲染进程 // 此时我们可以依据指定的窗口 ID 来获取对应的渲染进程,然后执行消息的发送 let mainWin = BrowserWindow.fromId(mainWinId) mainWin.webContents.send('mti', data) }) app.on('ready', createWindow) app.on('window-all-closed', () => { app.quit() }) // 2.渲染进程(A) const { ipcRenderer } = require('electron') window.onload = function () { // 获取元素 let oBtn = document.getElementById('btn') oBtn.addEventListener('click', () => { ipcRenderer.send('openWin2', '来自于 index 进程') }) // 接收消息 ipcRenderer.on('mti', (ev, data) => { console.log(data) }) } // 3. 渲染进程B const { ipcRenderer } = require('electron') window.onload = function () { let oInput = document.getElementById('txt') let val = localStorage.getItem('name') oInput.value = val // 在 sub 中发送数据给 index.js let oBtn = document.getElementById('btn') oBtn.addEventListener('click', () => { ipcRenderer.send('stm', '来自于sub进程') }) // 接收数据 ipcRenderer.on('its', (ev, data) => { console.log(data) }) } ``` ## 对话框 ```js const { remote } = require('electron') window.onload = function () { let oBtn = document.getElementById('btn') let oBtnErr = document.getElementById('btnErr') oBtn.addEventListener('click', () => { remote.dialog.showOpenDialog({ defaultPath: __dirname, buttonLabel: '请选择', title: '拉勾教育', properties: ['openFile', 'multiSelections'], filters: [ { "name": '代码文件', extensions: ['js', 'json', 'html'] }, { "name": '图片文件', extensions: ['ico', 'jpeg', 'png'] }, { "name": '媒体类型', extensions: ['avi', 'mp4', 'mp3'] } ] }).then((ret) => { console.log(ret) }) }) oBtnErr.addEventListener('click', () => { remote.dialog.showErrorBox('自定义标题', '当前错误内容') }) } ``` ## shell与iframe ```js // 主进程 let tmp = [ { label: '菜单', submenu: [ { label: '关于', click() { // 默认浏览器中打开 shell.openExternal('https://kaiwu.lagou.com/') } }, { label: '打开', click() { // 让渲染进程打开iframe BrowserWindow.getFocusedWindow().webContents.send('openUrl') } }, ] } ] // 2.渲染进程 const path = require('path') const { shell, ipcRenderer } = require('electron') // window.onload = function () { // // 1 获取元素 // let oBtn1 = document.getElementById('openUrl') // let oBtn2 = document.getElementById('openFolder') // oBtn1.addEventListener('click', (ev) => { // ev.preventDefault() // let urlPath = oBtn1.getAttribute('href') // shell.openExternal(urlPath) // }) // oBtn2.addEventListener('click', (ev) => { // shell.showItemInFolder(path.resolve(__filename)) // }) // } ipcRenderer.on('openUrl', () => { let iframe = document.getElementById('webview') iframe.src = 'https://www.lagou.com/' }) ``` ## 消息通知 ### 基于H5的Notification ```js window.onload = function () { let oBtn = document.getElementById('btn') oBtn.addEventListener('click', () => { let option = { title: '拉勾教育', body: '互联网人的实战大学,大前端', icon: './msg.png' } let myNotification = new window.Notification(option.title, option) myNotification.onclick = function () { console.log('点击了消息页卡') } }) } ``` ## 注册与取消快捷键(也可在渲染进程中实现) ```js app.on('ready', () => { // 注册 let ret = globalShortcut.register('ctrl + q', () => { console.log('快捷键注册成功') }) if (!ret) { console.log('注册失败') } console.log(globalShortcut.isRegistered('ctrl + q')) console.log(ret, '~~~~~') }) app.on('will-quit', () => { console.log(666) globalShortcut.unregister('ctrl + q') globalShortcut.unregisterAll() }) ``` ## 剪切板 ```js const { clipboard, nativeImage } = require('electron') window.onload = function () { // 获取元素 let aBtn = document.getElementsByTagName('button') let aInput = document.getElementsByTagName('input') let oBtn = document.getElementById('clipImg') let ret = null aBtn[0].onclick = function () { // 复制内容 ret = clipboard.writeText(aInput[0].value) } aBtn[1].onclick = function () { // 粘贴内容 aInput[1].value = clipboard.readText(ret) } oBtn.onclick = function () { // 将图片放置于剪切板当中的时候要求图片类型属于 nativeImage 实例 let oImage = nativeImage.createFromPath('./msg.png') clipboard.writeImage(oImage) // 将剪切板中的图片做为 DOM 元素显示在界面上 let oImg = clipboard.readImage() let oImgDom = new Image() oImgDom.src = oImg.toDataURL() document.body.appendChild(oImgDom) } } ``` ## 系统托盘 ```js const {Tray} = requore("electron"); const tray = new Tray('./tray.png') const contextMenu = Menu.buildFromTemplate([ { role: 'minmize', label: '最小化', click: ()=>{ mainWindow.minimize() } }, { role: 'togglefullscreen', label: '全屏', click:()=>{ mainWindow.setFullScreen(mainWindow.isFullScreen()!==true); } }, { label: '退出', role: 'quit', click: () => { app.quit() } } ]) // 托盘名字 tray.setToolTip(app.name) // 右键单击显示菜单 tray.on('right-click',()=>{ tray.popUpContextMenu(contextMenu) }) // 点击显示 tray.on('click',(e,bounds)=>{ mainWindow.show() }) ```