前言
本文档主要介绍如何使用electron+vite+vue3+electron-forge来构建桌面应用程序。
Electron是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。
Electron-forge是一个处理 Electron 应用程序打包与分发的一体化工具。
环境准备
-
在开始之前,确保你的开发环境已经安装了Node和npm。
node: v16.18.1
npm: 8.19.2
安装 Vite 和 Vue 3
-
创建并进入项目目录,安装
Vite
和Vue 3
mkdir 你的项目
cd 你的项目
npm install vite vue@next
配置Vite
-
在项目根目录下会有一个
vite.config.js
文件,进行如下配置
修改vue的
base
属性为./
相对路径
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
base: './', //修改vue的base属性为./ 相对路径
plugins: [
vue(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server: {
host: '0.0.0.0', // 启动后浏览器窗口输入地址就可以进行访问
port: 8081, // 端口号
open: true, //是否自动打开浏览器
proxy: { // 反向代理配置
}
},
})
项目引入Electron
-
安装electron
# 安装Electron
npm install --save-dev electron
-
在项目根目录下创建electron的目录,里面创建
main.js
和preload.js
的文件
创建项目启动主窗口,加载vue的页面
const { app, BrowserWindow, ipcMain, shell, dialog } = require('electron')
const path = require('node:path')
const createWindow = () => {
const win = new BrowserWindow({
width: 1100,
height: 800,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
preload: path.join(__dirname, './preload.js')
},
autoHideMenuBar: true, // 是否自动隐藏菜单栏
icon: "icon/logo.png"
})
// 应用标题
win.setTitle('xxxx')
// 加载 index.html路径
win.loadFile('dist/index.html')
// win.loadURL('xxxx')
}
// 这段程序将会在 Electron 结束初始化
// 和创建浏览器窗口的时候调用
// 部分 API 在 ready 事件触发后才能使用。
app.whenReady().then(() => {
ipcMain.handle('ping', () => 'pong')
createWindow()
app.on('activate', () => {
// 在 macOS 系统内, 如果没有已开启的应用窗口
// 点击托盘图标时通常会重新创建一个新窗口
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
})
// 除了 macOS 外,当所有窗口都被关闭的时候退出程序。 因此, 通常
// 对应用程序和它们的菜单栏来说应该时刻保持激活状态,
// 直到用户使用 Cmd + Q 明确退出
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron,
ping: () => ipcRenderer.invoke('ping')
// 除函数之外,我们也可以暴露变量
})
-
修改package.json文件内容
配置electron启动文件,删除
package.json
文件中的 type:“module” 行,否则会有一个警告!
{
"name": "xxx",
"version": "xxx",
"description": "xxx",
// "type": "module",
"main": "electron/main.js",
"author": "xxx",
"license": "xxx",
"scripts": {
"dev": "vite",
"electron":"electron .",
},
}
Electron-forge配置
-
安装electron-forge
# electron-forge依赖安装
npm install --save-dev @electron-forge/cli
-
运行electron-forge import
# electron-forge自动构建运行脚本
npx electron-forge import
-
转换脚本完成后,Forge 会将一些脚本添加到您的
package.json
文件中。
//...
"scripts": {
"dev": "vite",
"electron":"electron .",
"start": "electron-forge start",
"package": "electron-forge package",
"make": "electron-forge make"
},
//...
-
您还应该注意到您的
package.json
现在安装了更多的包 在devDependencies
下,以及一个导出配置的新forge.config.js
文件。
"devDependencies": {
"@electron-forge/cli": "^7.4.0",
"@electron-forge/maker-deb": "^7.4.0",
"@electron-forge/maker-rpm": "^7.4.0",
"@electron-forge/maker-squirrel": "^7.4.0",
"@electron-forge/maker-zip": "^7.4.0",
"@electron-forge/plugin-auto-unpack-natives": "^7.4.0",
"@electron-forge/plugin-fuses": "^7.4.0",
"@electron/fuses": "^1.8.0",
"electron": "^30.1.0",
},
-
接下来让我们配置一下
forge.config.js
文件。
module.exports = {
packagerConfig: {
asar: true,
},
rebuildConfig: {},
makers: [
{
name: '@electron-forge/maker-squirrel',
config: {},
},
{
name: '@electron-forge/maker-zip',
platforms: ['darwin'],
},
{
name: '@electron-forge/maker-deb',
config: {},
},
{
name: '@electron-forge/maker-rpm',
config: {},
},
],
};
完整代码展示
-
package.json
{
"name": "xxx", // 项目的名称,也是打包之后的程序名称
"version": "1.0.1", // 版本信息
"description": "xxx", // 描述信息,必填
"main": "electron/main.js",
"author": "xxx", // 作者信息,必填
"license": "MIT",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"start": "electron-forge start", // 启动electron的脚本
"package": "electron-forge package",
"make": "electron-forge make" // 打包electron的脚本
},
"dependencies": {
"@vueuse/core": "^10.10.0",
"axios": "^1.6.8",
"chinese-lunar": "^0.1.4",
"chinese-lunar-calendar": "^1.0.1",
"dayjs": "^1.11.11",
"electron-squirrel-startup": "^1.0.1",
"element-plus": "^2.7.2",
"js-cookie": "^3.0.5",
"jsencrypt": "^3.3.2",
"postcss-px2rem": "^0.3.0",
"vue": "^3.4.21",
"vue-router": "^4.3.2"
},
"devDependencies": {
"@electron-forge/cli": "^7.4.0",
"@electron-forge/maker-deb": "^7.4.0",
"@electron-forge/maker-rpm": "^7.4.0",
"@electron-forge/maker-squirrel": "^7.4.0",
"@electron-forge/maker-zip": "^7.4.0",
"@electron-forge/plugin-auto-unpack-natives": "^7.4.0",
"@electron-forge/plugin-fuses": "^7.4.0",
"@electron/fuses": "^1.8.0",
"@vitejs/plugin-vue": "^5.0.4",
"concurrently": "^8.2.2",
"electron": "^30.1.0",
"file-saver": "2.0.5",
"sass": "^1.77.0",
"vite": "^5.2.8"
},
"config": {
"forge": {
"packagerConfig": {
"appVersion": "1.0.1",
"icon": "./icon/icon.ico",
"name": "xxx"
},
"makers": [
{
"name": "@electron-forge/maker-squirrel",
"config": {
"name": "xxx",
"icon": "./icon/icon.ico"
}
},
{
"name": "@electron-forge/maker-zip",
"platforms": [
"darwin"
]
},
{
"name": "@electron-forge/maker-deb",
"config": {}
},
{
"name": "@electron-forge/maker-rpm",
"config": {}
}
]
}
}
}
-
electron/main.js
const { app, BrowserWindow, ipcMain, shell, dialog } = require('electron')
const path = require('node:path')
const createWindow = () => {
const win = new BrowserWindow({
width: 1100,
height: 800,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
preload: path.join(__dirname, './preload.js')
},
autoHideMenuBar: true, // 是否自动隐藏菜单栏
icon: "icon/logo.png"
})
// 应用标题
win.setTitle('控势安全')
// 加载 index.html
win.loadFile('dist/index.html')
// win.loadURL('http://192.168.110.224:8081/')
// 捕获新窗口事件并使用shell打开链接
ipcMain.on('open-url', (event, url) => {
event.preventDefault();
shell.openExternal(url);
});
ipcMain.on('open-local-file', (event, folderPath) => {
shell.openPath(folderPath)
});
ipcMain.on('click-notification', (event) => {
win.focus()
});
// 监听渲染进程发送的 'select-folder' 消息
ipcMain.on('select-folder', (event) => {
dialog.showOpenDialog(win, {
properties: ['openDirectory']
}).then(result => {
if (result.canceled) {
console.log('用户取消了选择');
event.reply('select-folder-reply', null); // 发送取消选择的消息
} else {
const folderPath = result.filePaths[0]; // 获取用户选择的第一个文件夹路径
console.log('用户选择的文件夹路径:', folderPath);
event.reply('select-folder-reply', folderPath); // 发送选择的文件夹路径给渲染进程
}
}).catch(err => {
console.error('选择文件夹时出错:', err);
event.reply('select-folder-reply', null); // 发送错误消息
});
});
}
// 这段程序将会在 Electron 结束初始化
// 和创建浏览器窗口的时候调用
// 部分 API 在 ready 事件触发后才能使用。
app.whenReady().then(() => {
ipcMain.handle('ping', () => 'pong')
createWindow()
app.on('activate', () => {
// 在 macOS 系统内, 如果没有已开启的应用窗口
// 点击托盘图标时通常会重新创建一个新窗口
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
})
// 除了 macOS 外,当所有窗口都被关闭的时候退出程序。 因此, 通常
// 对应用程序和它们的菜单栏来说应该时刻保持激活状态,
// 直到用户使用 Cmd + Q 明确退出
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
-
electron/preload.js
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron,
ping: () => ipcRenderer.invoke('ping')
// 除函数之外,我们也可以暴露变量
})
-
forge.config.js
module.exports = {
packagerConfig: {
asar: true,
},
rebuildConfig: {},
makers: [
{
name: '@electron-forge/maker-squirrel',
config: {},
},
{
name: '@electron-forge/maker-zip',
platforms: ['darwin'],
},
{
name: '@electron-forge/maker-deb',
config: {},
},
{
name: '@electron-forge/maker-rpm',
config: {},
},
],
};
项目打包
# 先打包vue项目
npm run build
# 再打包electron
npm run make
项目打包完成后,编译器会返回打包生成路径地址,从本地进入生成文件路径。
里面会存在一个make(含安装包)文件夹和一个打包出来的项目文件夹。
双击进入项目文件夹,点击运行你的应用程序吧!
参考文档
-
Electron 官方文档:https://www.electronjs.org/docs
-
Vite 官方文档:https://vitejs.dev/guide/
-
Vue 3 官方文档:https://v3.vuejs.org/guide/
-
Electron Forge 官方文档:https://www.electronforge.io/docs/en/getting-started/installation
注意
项目启动electron应用需要修改vue项目的路由模式,使用hash模式,不然会出现应用运行启动空白界面
const router = createRouter({
// mode: 'history',
history: createWebHashHistory(),
routes
})
const routes = [
{
path: '/',
name: '欢迎界面',
component: () => import('@/views/Welcome.vue')
},
]