1.初始化工作
midir electron-demo
cd electron-demo
npm init
//到package.json 文件下将入口文件修改为main.js
"main": "main.js",
//并且创建main.js文件
//electron 安装依赖
npm i electron -S
//安装nodemon
npm install nodemon -D
修改package.json
{
"name": "electron-demo",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon --exec electron . --watch ./ --ext .html,.js,.css" //监听html,js,css文件的变化
},
"author": "",
"license": "ISC",
"dependencies": {
"electron": "^19.0.6",
"electron-win-state": "^1.1.22"
},
"devDependencies": {
"nodemon": "^2.0.16"
}
}
修改main.js
//app应用模块
//BrowserWindow 浏览器串口模块
const { app, BrowserWindow } = require('electron')
//创建窗口
const createwindow = ()=>{
const win = new BrowserWindow({
width:800,
height:600
})
// 给窗口装载页面
// win.loadFile('./renderer/index.html')
win.loadURL('https://cn.bing.com/?mkt=zh-CN')
//自动打开发开工具
win.webContents.openDevTools()
//可以创建多个窗口
// const win2 = new BrowserWindow({
// width:800,
// height:600
// })
// win2.loadURL('https://www.baidu.com')
}
//应用就绪,可以装载窗口了
app.whenReady().then(createwindow)
启动程序
npm start
看到效果
后续操作省略,可以对应文件夹实现
目录如下
main.js 主程序文件 node环境,能读写文件
renderer 渲染文件 有关页面渲染的都在这里,可以结合vue等框架
app.js //vue模板
index.html //窗口页面
style.css //窗口样式文件
preload-js 桥接文件
因为渲染程序和主程序直接传递消息有严重的安全隐患,所以要通过这里的’桥’文件,当工具人这里面重要的核心就是通过contextBridge 暴露一个对象给渲染程序;通过ipcRenderer中的方法监听和抛发事件
controller 主程序事件控制器
主要处理主程序监听事件和抛发事件的逻辑处理
具体操作如下
main.js
// 引入事件处理器,对render的操作作出回应
require('./controller/ipcMessage')
//引入插件 作用:记录窗口的位置大小
const WinState = require('electron-win-state').default
//app应用模块
//BrowserWindow 浏览器串口模块
const { app, BrowserWindow } = require('electron')
const {resolve} =require('path')
//创建窗口
const createwindow = ()=>{
// 设置默认窗口大小
const winState = new WinState({
defaultWidth: 1400,
defaultHeight: 600,
dev:true
})
const win = new BrowserWindow({
...winState.winOptions,
// width:800,
// height:600,
show:false,
minHeight:300,
minWidth:400,
backgroundColor: 'aliceblue',
resizable:true, //默认可以 切换窗口大小
movable:true, //窗口是否可以移动
// frame:true, //是否关闭边框
// titleBarStyle: "hidden", // 控制titleBar的状态
backgroundColor:"skyblue",
webPreferences:{
//nodeIntegration 开启node contextIsolation 开启或关闭主程序和渲染程序的隔离
//这两个的开启会有安全隐患
// nodeIntegration:true,
// contextIsolation:false
//预加载,桥文件
preload: resolve(__dirname,'./preload-js')
}
})
//将窗口位置大小存起来
winState.manage(win)
// 给窗口装载页面
win.loadFile('./renderer/index.html')
// win.loadURL('https://cn.bing.com/?mkt=zh-CN')
//自动打开发开工具
// win.webContents.openDevTools()
//同win 的show一起配置 当win装载完成显示
win.once('ready-to-show',()=>{
win.show()
})
// 可以创建多个窗口
// const win2 = new BrowserWindow({
// width:800,
// height:600,
// parent:win, //配置父窗口,这里是子窗口
// modal:false //锁定子窗口
// })
// win2.loadURL('https://www.baidu.com')
//win2 关闭时 win 全屏
// win2.on('close',()=>{
// win.maximize()
// })
}
//app主进程完成加载
// app.on('will-finish-launching',()=>{
// console.log('will-finish-launching')
// })
// //app准备就绪
// app.on('ready',()=>{
// console.log('ready')
// })
// // app将要退出
// app.on('will-quit',()=>{
// console.log('will-quit')
// })
// app.on('before-quit',()=>{
// console.log('before-quit')
// })
// //app退出
// app.on('quit',()=>{
// console.log('quit')
// })
// //所有窗口都关闭
// app.on('window-all-closed',()=>{
// console.log('window-all-closed')
// //对于macos系统,关闭窗口时,不会直接退出应用,还会保留图标
// // if(process.platform!=='darwin'){
// // app.quit()
// // }
// })
//应用就绪,可以装载窗口了
app.whenReady().then(()=>{
createwindow()
//在macos下,当全部窗口都关了,点击图标,窗口再次打开
app.on('activate',()=>{
if(BrowserWindow.getAllWindows().length===0){
createwindow()
}
})
//isReady app是否装载完成
console.log(app.isReady())
//获取路径
console.log(app.getPath('desktop'))
console.log(app.getPath('music'))
console.log(app.getPath('temp'))
console.log(app.getPath('userData'))
console.log(BrowserWindow.getAllWindows().length)
})
// 程序失焦
app.on('browser-window-blur',()=>{
console.log("app blur")
})
// 程序获取焦点
app.on('browser-window-focus',()=>{
console.log('app focus')
})
// 在html中允许使用第三方资源 设为false 会有警告
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'
renderer/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- unsafe-eval 配置第三方引用安全策略 -->
<!-- <meta http-equiv="Content-Security-Policy" content="script-src 'self' unsafe-eval"> -->
<title>Document</title>
<script src="./vue.global.js"></script>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<h1>hello electron!</h1>
<input type="range" />
<div id="root"></div>
<script src="./app.js"></script>
</body>
</html>
renderer/app.js
const versions = window.myAPI.versions
const app = Vue.createApp({
template:`
<p>Chrome version: {{chromeVersion}}</p>
<p>Node version: {{NodeVersion}}</p>
<p>Electron version: {{electronVersion}}</p>
<button @click="sendSyncMsg"> 发送同步消息 </button>
<button @click="sendAsyncMsg"> 发送异步消息 </button>
`,
data(){
return {
chromeVersion: versions.chrome,
NodeVersion: versions.node,
electronVersion: versions.electron
}
},
methods:{
// 发送同步消息给桥文件,桥文件转发给主程序
sendSyncMsg(){
myAPI.sendSyncMsg("this message from render ")
},
// 主进程和渲染进程传递消息 建议IPC通信 全部通过异步
async sendAsyncMsg(){
let result = await myAPI.sendAsyncMsg()
console.log(result)
}
},
mounted() {
// 接受main 返回的消息
// myAPI.recieveSyncMsgUseCb((msg)=>{
// console.log(msg)
// console.log(this)
// })
//promise调用
// const result = await myAPI.recieveSyncMsgUsePromise()
// console.log(result,1111)
myAPI.recieveSyncMsgUsePromise().then((res)=>{
console.log(res,22222);
})
}
})
app.mount("#root")
renderer/style.css
body {
background-color: pink;
/* 设置拖拽 */
user-select: none;
-webkit-app-region:drag;
}
/* 设置input可拖拽 */
input {
-webkit-app-region: no-drag;
}
preload-js/index.js
//preload-js下面的文件我把他叫做'桥'文件,桥文件能直接访问到主程序的信息 因为渲染程序不能直接和主程序做通信 有安全隐患
// 通过桥文件让渲染程序能够获取到主程序的信息
const {contextBridge, ipcRenderer} = require('electron')
// 向main发送一个同步消息
const sendSyncMsg = (msg)=>{
console.log(msg,"发送了一次");
ipcRenderer.send('sync-send-event',msg)
}
// main收到同步消息抛发事件到这里,通过promise给Vue层
const recieveSyncMsgUsePromise = ()=>{
return new Promise((resolve,reject) => {
ipcRenderer.on('recive-sync-event',(event,msg)=>{
console.log("返回的数据 1")
resolve(msg)
})
})
}
// main收到同步消息抛发事件到这里,通过callback给Vue层(推荐使用这种方法)
const recieveSyncMsgUseCb = (cb) => {
ipcRenderer.on('recive-sync-event',(event,msg)=>{
cb(msg)
})
}
// 发送异步消息
const sendAsyncMsg = async ()=>{
const result = await ipcRenderer.invoke('my-invokable-ipc')
return result
}
//通过这里向渲染程序暴露一个对象,这个对象是myAPI
//通过直接暴露ipcRenderer给vue层有些方法会丢失(坑)
contextBridge.exposeInMainWorld('myAPI',{
versions: process.versions,
sendSyncMsg,
recieveSyncMsgUsePromise,
sendAsyncMsg,
recieveSyncMsgUseCb
})
controller/ipcMessage.js
// 事件处理器 对于桥文件抛发的事件做回应
const { ipcMain } = require("electron")
ipcMain.on('sync-send-event',(event,msg)=>{
console.log(msg,'shoudao 1');
event.reply('recive-sync-event', '我已经收到:' + msg)
// event.sender.send('a', '我已经收到:' + msg)
})
function somePromise(){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('async message from main')
},2000)
})
}
// 异步事件监听
ipcMain.handle("my-invokable-ipc", async (ecent,...args)=>{
const result = await somePromise()
return result
})