文章目录
- 相关笔记
- 笔记说明
- 三、引入现代前端框架
- 1、配置 webpack
- (1)安装 webpack 和 electron-webpack:
- (2)自定义入口页面
- 2、引入 Vue
- (1)安装 Vue CLI
- (2)调试配置 -- 调试主进程需要增加额外的配置
- 四、窗口
- 1、自定义窗口的标题栏
- 2、窗口的控制按钮、记录与恢复窗口状态
- 3、创建不规则窗口
相关笔记
- Electron学习笔记(一)
- Electron学习笔记(二)
- 使用 electron-vite-vue 构建 electron + vue3 项目并打包
笔记说明
文本为学习《Electron 实战 入门、进阶与性能优化 刘晓伦 著》时所记录的笔记 主要将书本上的案例运行一遍,针对原理部分并无相关记录。笔记记录于 2023年9月。
三、引入现代前端框架
1、配置 webpack
(1)安装 webpack 和 electron-webpack:
说明: 这两个模块都是开发依赖,生产环境并不需要它们,所以在安装命令中都添加了 --dev 参数。
运行环境:(建议)
Node.js: 16.20.2
webpack: 4.46.0
yarn add webpack@4.46.0 --dev
yarn add electron-webpack --dev
项目目录结构:
修改 package.json 文件内容如下:
"scripts": {
"start": "electron-webpack dev",
"build": "electron-webpack build"
}
src/main目录: 放置主进程相关的代码,此目录下需要有主进程的入口文件,默认为 index.js。 index.js 文件内容如下:
const {app,BrowserWindow} = require('electron');
let path = require('path');
let URL = require('url');
let win = null;
let url = '';
app.on('ready', function() {
win = new BrowserWindow({
// 为页面集成Node.js环境
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
});
// 判断当前代码是在生产环境还是开发环境中运行
if(process.env.NODE_ENV !== 'production') {
// 开发环境
url = 'http://localhost:' + process.env.ELECTRON_WEBPACK_WDS_PORT;
}else {
// 生产环境
url = URL.format({
pathname: path.join(__dirname,'index.html'),
protocol: 'file'
});
}
win.loadURL(url);
// 程序启动后开启 开发者工具
win.webContents.openDevTools();
win.on('close',function() {
win = null;
})
});
app.on('window-all-closed',function() {
app.quit();
})
src/renderer目录: 复制渲染进程相关的代码,此目录下需要有渲染进程的入口文件,默认为index.js。
在该目录下新建一个 renderModule.js 文件,文件内容如下:
export default {
say: function() {
document.write('Hello webpack!');
}
}
index.js 文件内容如下:
import renderModule from "./renderModule";
renderModule.say();
src/common目录: 放置既会被主进程代码用到,又会被渲染进程代码用到的公共代码,一般为一些工具类代码。
src/static目录: 放置不希望被webpack打包的内容,程序可以通过__static全局变量访问到这个目录的绝对路径。
启动项目:yarn start
(项目启动后,修改 renderModule.js 中的代码,无需重启应用,即可自动刷新)
运行结果:
(2)自定义入口页面
在 package.json 中增加如下配置节,文件内容如下:
"electronWebpack": {
"renderer": {
"template": "src/renderer/index.html"
}
}
在 src/renderer目录 下新建一个 index.html 文件:
<body>
<h1>你好,渲染进程主页</h1>
</body>
运行结果:
2、引入 Vue
(1)安装 Vue CLI
yarn global add @vue/cli
使用 Vue CLI 创建一个 Vue 项目,执行以下命令:
vue create electron_basic03
根据提示选择配置项,此处选择:Vue2 + Yarn
安装 Vue 插件 electron-builder:
vue add electron-builder
启动项目:
yarn electron:serve
运行结果:
Vite + Vue3 + TS 可参考: https://blog.csdn.net/qq_45897239/article/details/138490747?spm=1001.2014.3001.5501
(2)调试配置 – 调试主进程需要增加额外的配置
打开 .vscode 目录(没有则创建),创建 tasks.json 文件,文件内容如下:
{
"version": "2.0.0",
"tasks": [
{
"label": "electron-debug",
"type": "process",
"command": "./node_modules/.bin/vue-cli-service",
"windows": {
"command": "./node_modules/.bin/vue-cli-service.cmd"
},
"isBackground": true,
"args": ["electron:serve","--debug"],
"problemMatcher": {
"owner": "custom",
"pattern": {
"regexp": ""
},
"background": {
"beginsPattern": "Starting development server\\.\\.\\",
"endsPattern": "Not launching electron as debug argument was passed\\."
}
}
}
]
}
在 .vscode 目录下创建 launch.json 文件,文件内容如下:
{
"version": "0.2.0",
"configurations": [
{
"name": "Electron: Main",
"type": "node",
"request": "launch",
"protocol": "inspector",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
},
"preLaunchTask": "electron-debug",
"args": ["--remote-debugging-port=9223","./dist_electron"],
"outFiles": ["${workspaceFolder}/dist_electron/**/*.js"]
},
{
"name": "Electron: Renderer",
"type": "chrome",
"request": "attach",
"port": 9223,
"urlFilter": "http://localhost:*",
"timeout": 30000,
"webRoot": "${workspaceFolder}/src",
"sourceMapPathOverrides": {
"webpack:///./src/*": "${webRoot}/*"
}
}
],
"compounds": [
{
"name": "Electron: All",
"configurations": ["Electron: Main","Electron: Renderer"]
}
]
}
参考链接(Electron官网):https://www.electronjs.org/zh/docs/latest/tutorial/debugging-vscode
参考链接(VSCode官网):https://code.visualstudio.com/docs/editor/debugging
四、窗口
1、自定义窗口的标题栏
使用 Vue CLI 创建完项目后:
在 src/background.js 文件中禁用默认边框:
const win = new BrowserWindow({
// 禁用窗口默认边框
frame: false,
width: 800,
height: 600,
webPreferences: {
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
// 使用 remote 模块
enableRemoteModule: true,
}
})
打开 src/App.vue,修改 template 中的代码:
<template>
<div id="app">
<div class="titleBar">
<!-- logo和标题区域 -->
<div class="title">
<div class="logo">
<img src="@/assets/logo.png" />
</div>
<div class="txt">窗口标题</div>
</div>
<!-- 窗口右上角工具栏,控制窗口的放大、缩小、关闭 -->
<div class="windowTool">
<div @click="minisize">
<i class="iconfont iconminisize"></i>
</div>
<div v-if="isMaxSize" @click="restore">
<i class="iconfont iconrestore"></i>
</div>
<div v-else @click="maxsize">
<i class="iconfont iconmaxsize"></i>
</div>
<div @click="close" class="close">
<i class="iconfont iconclose"></i>
</div>
</div>
</div>
<div class="content">
<router-view />
</div>
</div>
</template>
在 src/App.vue 中,修改 style 中的代码:
第一部分:
<style>
body,
html {
margin: 0px;
padding: 0px;
overflow: hidden;
height: 100%;
}
#app {
text-align: center;
margin: 0px;
padding: 0px;
height: 100%;
overflow: hidden;
box-sizing: border-box;
border: 1px solid #f5222d;
display: flex;
flex-direction: column;
}
</style>
第二部分:
<style lang="scss" scoped>
// 导入外部字体图标样式文件
@import url(https://at.alicdn.com/t/font_1378132_s4e44adve5.css);
.titleBar {
height: 38px;
line-height: 36px;
background: #fff1f0;
display: flex;
border-bottom: 1px solid #f5222d;
.title {
flex: 1;
display: flex;
-webkit-app-region: drag;
.logo {
padding-left: 8px;
padding-right: 6px;
img {
width: 20px;
height: 20px;
margin-top: 7px;
}
}
.txt {
text-align: left;
flex: 1;
}
}
.windowTool {
div {
color: #888;
height: 100%;
width: 38px;
display: inline-block;
cursor: pointer;
i {
font-size: 12px;
}
&:hover {
background: #ffccc7;
}
}
.close:hover {
color: #fff;
background: #ff4d4f;
}
}
}
.content {
flex: 1;
overflow-y: auto;
overflow-x: auto;
}
</style>
使用 scss 前需要下载相应的包:
环境:
Node.js: 16.20.2
sass-loader: 10
node-sass: 6
1、使用淘宝镜像源
npm set sass_binary_site http://registry.npmmirror.com/dist/node-sass
2、先下载 sass-loader (此处指定版本为@10)
yarn add sass-loader@10 --dev
3、再下载 node-sass (此处指定版本为@6)
yarn add node-sass@6 --dev
运行结果:
2、窗口的控制按钮、记录与恢复窗口状态
在 src/App.vue 中,修改 script 中的代码:
<script>
import { remote } from 'electron';
export default {
name: 'App',
data() {
return {
// 记录当前窗口是否最大化
isMaxSize: false
}
},
methods: {
// 关闭窗口
close() {
remote.getCurrentWindow().close();
},
// 窗口最小化
minisize() {
remote.getCurrentWindow().minimize();
},
// 窗口还原
restore() {
remote.getCurrentWindow().restore();
},
// 窗口最大化
maxsize() {
remote.getCurrentWindow().maximize();
},
// 窗口防抖函数
// 作用:短期内有大量的事件触发时,只会执行最后一次事件关联的任务。
debounce(fun) {
let timeout = null;
return function () {
clearTimeout(timeout);
timeout = setTimeout(() => {
fun.apply(this, arguments);
}, 300);
}
},
// 记录窗口状态
setState() {
let win = remote.getCurrentWindow();
// 返回一个 Rectangle 对象,包含窗口在屏幕上的坐标和大小消息
let rect = win.getBounds();
// 返回当前窗口是否最大化状态
let isMaxSize = win.isMaximized();
let obj = { rect, isMaxSize };
// 将信息保存在 localStorage
localStorage.setItem('winState', JSON.stringify(obj));
},
// 恢复窗口状态
// 获取存储在 localStorage 内的窗口状态数据,用于恢复之前记录的窗口状态
getState() {
let win = remote.getCurrentWindow();
let winState = localStorage.getItem('winState');
if (winState) {
winState = JSON.parse(winState);
// 如果上一次关闭窗口是窗口处在最大化,则此次也最大化显示
if (winState.isMaxSize) win.maximize();
else win.setBounds(winState.rect);
}
}
},
// 监听窗口的最大化、还原状态
mounted() {
let win = remote.getCurrentWindow();
// 监听 win 的 maximize 事件 -- 窗口最大化
win.on('maximize', () => {
this.isMaxSize = true;
this.setState();
console.log('---最大化---');
});
// 监听 win 的 unmaximize 事件 -- 退出窗口最大化
win.on('unmaximize', () => {
this.isMaxSize = false;
this.setState();
console.log('---退出最大化---');
});
// 监听 win 的 move 事件 -- 窗口拖动
win.on('move', this.debounce(() => {
console.log('--窗口被拖动--');
this.setState();
}));
// 监听 win 的 resize 事件 -- 窗口缩放
win.on('resize', this.debounce(() => {
console.log('--窗口缩放--');
this.setState();
}));
this.isMaxSize = win.isMaximized();
this.getState();
win.show();
}
}
</script>
若想使 remote 模块可以使用,还需在 vue.config.js 文件中添加配置:
const { defineConfig } = require('@vue/cli-service');
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave: false,
// 添加以下配置
pluginOptions: {
electronBuilder: {
nodeIntegration: true,
}
}
})
在 src/background.js 中,添加以下代码:
win = new BrowserWindow({
//...
show: false
})
说明:之所以关闭窗口的显示,是因为src/App.vue 中的 getState() 函数更新了窗口的位置,窗口会先显示在屏幕的正中间,然后才移动到正确的位置,我们希望程序更新完窗口的位置、大小等信息后再显示。
3、创建不规则窗口
在 src/background.js 中,添加以下代码:
const win = new BrowserWindow({
width: 800,
height: 600,
// 禁用窗口默认边框
frame: false,
// 窗口的透明属性
transparent: true,
// 窗口大小不可调整
resizable: false,
// 禁止窗口最大化
maximizable: false,
webPreferences: {
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
enableRemoteModule: true,
}
})
在 src/App.vue 文件中, template 内容如下:
<template>
<div id="app">
<HelloWorld></HelloWorld>
</div>
</template>
style 文件内容如下:
<style>
html,
body {
margin: 0px;
padding: 0px;
/* 指示该元素不再是鼠标事件的目标。鼠标事件穿透该元素 */
pointer-events: none;
}
#app {
/* 移动至屏幕正中央 */
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
box-sizing: border-box;
width: 380px;
height: 380px;
border-radius: 190px;
border: 1px solid green;
background: #fff;
overflow: hidden;
pointer-events: auto;
}
</style>
script 内容如下:
<script>
import HelloWorld from './components/HelloWorld.vue';
export default {
name: 'App',
components: {
HelloWorld,
},
mounted() {
const remote = require("electron").remote;
let win = remote.getCurrentWindow();
// 监听 mousemove 事件。
// 当鼠标移入窗口圆形内容区时,不允许鼠标事件穿透;
// 当鼠标移入透明区时,允许鼠标事件穿透
window.addEventListener("mousemove", event => {
let flag = event.target === document.documentElement;
if (flag) {
// setIgnoreMouseEvents:使窗口忽略窗口内的所有鼠标事件,
// 并且在此窗口中发生的所有鼠标事件都将被传递到此窗口背后的内容
// forward:true 只有点击事件会穿透窗口,鼠标移动事件仍会正常触发
win.setIgnoreMouseEvents(true, { forward: true });
}
else {
win.setIgnoreMouseEvents(false);
}
});
win.setIgnoreMouseEvents(true, { forward: true });
}
}
</script>
在 src\components\HelloWorld.vue 文件中,HelloWorld.vue 文件内容如下:
<template>
<div class="hello">
<img src="../assets/logo.png" alt="">
</div>
</template>
<script>
export default {
name: 'HelloWorld',
}
</script>
<style>
.hello {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
width: 80%;
height: 80%;
}
.hello img {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
object-fit: scale-down;
}
</style>
运行结果: