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

看到效果

electron 界面结合python后端 electron界面开发教程_css


后续操作省略,可以对应文件夹实现

目录如下

electron 界面结合python后端 electron界面开发教程_css_02


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
})