Vue项目中使用Electron
一、安装
安装electron,electron-builder,vue-cli-plugin-electron-builder,electron-devtools
npm i --save-dev electron electron-builder vue-cli-plugin-electron-builder electron-devtools
二、创建background.js
vue-cli项目src下创建background.js文件
// Modules to control application life and create native browser window
const {app, BrowserWindow, screen, ipcMain, dialog, Tray, protocol} = require('electron')
import {createProtocol} from 'vue-cli-plugin-electron-builder/lib'
const fs = require('fs');
const path = require('path')
const electron = require('electron');
/*获取electron窗体的菜单栏*/
const Menu = electron.Menu;
/*隐藏electron创听的菜单栏*/
Menu.setApplicationMenu(null);
let mainWindow;
let appTray = null;
let preFix = './';
if (process.env.NODE_ENV === 'development') {
preFix = "./bundled/";
}
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([{
scheme: 'app',
privileges: {
secure: true,
standard: true,
supportFetchAPI: true
}
}])
// 隐藏主窗口,并创建托盘,绑定关闭事件
function setTray(mainWindow) {
// 用一个 Tray 来表示一个图标,这个图标处于正在运行的系统的通知区
// 通常被添加到一个 context menu 上.
// 系统托盘右键菜单
const trayMenuTemplate = [
{
type: 'checkbox',
label: '开机启动',
checked: app.getLoginItemSettings().openAtLogin,
click: function () {
if (!app.isPackaged) {
app.setLoginItemSettings({
openAtLogin: !app.getLoginItemSettings().openAtLogin,
path: process.execPath
})
} else {
app.setLoginItemSettings({
openAtLogin: !app.getLoginItemSettings().openAtLogin
})
}
}
},
{
// 系统托盘图标目录
label: '退出',
click: () => {
app.quit();
}
}
];
// 设置系统托盘图标
let filePath = preFix + 'favicon.ico';
const iconPath = path.join(__dirname, filePath);
appTray = new Tray(iconPath);
const win = BrowserWindow.getFocusedWindow()
// 图标的上下文菜单
const contextMenu = Menu.buildFromTemplate(trayMenuTemplate);
// 展示主窗口,隐藏主窗口 mainWindow.hide()
mainWindow.show();
// 设置托盘悬浮提示
appTray.setToolTip('Saturn');
// 设置托盘菜单
appTray.setContextMenu(contextMenu);
// 单击托盘小图标显示应用
// appTray.on('click', () => {
// //显示主程序
// mainWindow.show();
// //关闭托盘显示
// // appTray.destroy();
// });
appTray.on('double-click', () => {
win.isVisible() ? win.hide() : win.show()
})
return appTray;
}
function createWindow(scheme, handler) {
// Create the browser window.
const displayWorkAreaSize = screen.getAllDisplays()[0].workArea
const width = displayWorkAreaSize.width;
const height = displayWorkAreaSize.height;
mainWindow = new BrowserWindow({
width: width / 2 + 100,
height: height - 10,
x: width - (width / 2 + 100) - 5,
y: 5,
// width: 1920,
// height: 1350,
transparent: true,
resizable: false,
movable: true,
frame: false,
alwaysOnTop: false,
backgroundColor: '#00000000',
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
//关闭web权限检查,允许跨域
webSecurity: false,
nodeIntegration: true,
contextIsolation: false,
enableRemoteModule: true,
}
})
// and load the index.html of the app.
// mainWindow.loadFile('index_bak.html')
let filePath = preFix + 'index.html';
mainWindow.loadFile(path.join(__dirname, filePath))
// Open the DevTools.
// mainWindow.webContents.openDevTools()
setTray(mainWindow);
mainWindow.setSkipTaskbar(true) // 使窗口不显示在任务栏中
//程序崩溃后
mainWindow.webContents.on('crashed', () => {
const options = {
type: 'error',
title: '进程崩溃了',
message: '这个进程已经崩溃.',
buttons: ['重载', '退出'],
};
recordCrash().then(() => {
dialog.showMessageBox(options, (index) => {
if (index === 0) reloadWindow(mainWindow);
else app.quit();
});
}).catch((e) => {
console.log('err', e);
});
})
function recordCrash() {
return new Promise(resolve => {
// 崩溃日志请求成功....
resolve();
})
}
function reloadWindow(mainWin) {
if (mainWin.isDestroyed()) {
app.relaunch();
app.exit(0);
} else {
BrowserWindow.getAllWindows().forEach((w) => {
if (w.id !== mainWin.id) w.destroy();
});
mainWin.reload();
}
}
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
createProtocol('app')
createWindow()
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
/**
* 开机自动启动
* */
const exeName = path.basename(process.execPath)
app.setLoginItemSettings({
openAtLogin: true,
openAsHidden: false,
path: process.execPath,
args: [
'--processStart', `"${exeName}"`,
]
})
/**
* 窗口控制
* */
// 1. 窗口 最小化
ipcMain.on('window-min', function () { // 收到渲染进程的窗口最小化操作的通知,并调用窗口最小化函数,执行该操作
mainWindow.minimize();
})
// 2. 窗口 最大化、恢复
ipcMain.on('window-max', function () {
if (mainWindow.isMaximized()) { // 为true表示窗口已最大化
mainWindow.restore();// 将窗口恢复为之前的状态.
} else {
mainWindow.maximize();
}
})
// 3. 关闭窗口
ipcMain.on('window-close', function () {
// mainWindow.close();
mainWindow.setSkipTaskbar(true) // 使窗口不显示在任务栏中
mainWindow.hide() // 隐藏窗口
})
/**
* 读写文件
* */
ipcMain.on('read-message', function (event, arg) {
// arg是从渲染进程返回来的数据
// 这里是传给渲染进程的数据
fs.readFile(path.join(__dirname, "./loginInfo.json"), "utf8", (err, data) => {
if (err) {
event.sender.send('read-reply', "读取失败");
} else {
event.sender.send('read-reply', data);
}
})
});
ipcMain.on('write-message', function (event, arg) {
// arg是从渲染进程返回来的数据
fs.writeFile(path.join(__dirname, "./loginInfo.json"), JSON.stringify(arg), "utf8", (err) => {
if (err) {
event.sender.send('write-reply', "写入失败");
} else {
event.sender.send('write-reply', "写入成功");
}
}
)
// 通过event.sender.send给渲染进程传递数据
});
三、创建preload.js文件
在vue-cli 项目src下 创建 preload.js
import { contextBridge, ipcRenderer } from 'electron'
window.ipcRenderer = ipcRenderer
contextBridge.exposeInMainWorld('ipcRenderer', {
// 异步向主进程 发送消息
send: (channel, data) => {
let validChannels = ['toMain', 'downLoad', 'judgeUse', 'hideMenu', 'showMenu', 'window-close', 'asyncGetLocalServer']
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data)
}
},
// 同步向主进程 发送消息,
sendSync: (channel, data) => {
let validChannels = ['syncGetLocalServer']
if (validChannels.includes(channel)) {
return ipcRenderer.sendSync(channel, data)
}
},
// 异步接收主进程返回的数据
receive: async (channel) => {
let validChannels = ['authorizeBack', 'asyncBackLocalServer']
if (validChannels.includes(channel)) {
return new Promise((resolve) => {
ipcRenderer.on(channel, (event, ...args) => {
resolve(...args)
})
});
}
}
})
四、修改package.json文件
{
"name": "ruoyi",
"version": "3.1.0",
"description": "若依管理系统",
"author": "若依",
"license": "MIT",
"scripts": {
"dev": "vue-cli-service serve",
"build:prod": "vue-cli-service build",
"build:stage": "vue-cli-service build --mode staging",
"preview": "node build/index.js --preview",
"lint": "eslint --ext .js,.vue src",
"electron:build": "vue-cli-service electron:build",
"electron:serve": "vue-cli-service electron:serve",
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"src/**/*.{js,vue}": [
"eslint --fix",
"git add"
]
},
"keywords": [
"vue",
"admin",
"dashboard",
"element-ui",
"boilerplate",
"admin-template",
"management-system"
],
"repository": {
"type": "git",
"url": "https://gitee.com/y_project/RuoYi-Cloud.git"
},
"dependencies": {
"@riophae/vue-treeselect": "0.4.0",
"axios": "0.21.0",
"clipboard": "2.0.6",
"core-js": "3.8.1",
"echarts": "4.9.0",
"el-tree-transfer": "^2.4.7",
"element-ui": "2.15.5",
"file-saver": "2.0.4",
"fuse.js": "6.4.3",
"highlight.js": "9.18.5",
"js-beautify": "1.13.0",
"js-cookie": "2.2.1",
"jsencrypt": "3.0.0-rc.1",
"nprogress": "0.2.0",
"quill": "1.3.7",
"screenfull": "5.0.2",
"sortablejs": "1.10.2",
"vue": "2.6.12",
"vue-count-to": "1.0.13",
"vue-cropper": "0.5.5",
"vue-fullscreen": "^2.6.1",
"vue-meta": "^2.4.0",
"vue-router": "3.4.9",
"vue-wxlogin": "^1.0.4",
"vuedraggable": "2.24.3",
"vuex": "3.6.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "4.4.6",
"@vue/cli-plugin-eslint": "4.4.6",
"@vue/cli-service": "4.4.6",
"babel-eslint": "10.1.0",
"chalk": "4.1.0",
"connect": "3.6.6",
"dhtmlx-gantt": "^7.1.7",
"electron": "^16.0.5",
"electron-builder": "^22.14.5",
"electron-devtools": "^0.0.3",
"eslint": "7.15.0",
"eslint-plugin-vue": "7.2.0",
"lint-staged": "10.5.3",
"runjs": "4.4.2",
"sass": "1.32.0",
"sass-loader": "10.1.0",
"script-ext-html-webpack-plugin": "2.1.5",
"svg-sprite-loader": "5.1.1",
"vue-cli-plugin-electron-builder": "^2.1.1",
"vue-template-compiler": "2.6.12"
},
"engines": {
"node": ">=8.9",
"npm": ">= 3.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions"
],
"__npminstall_done": false,
"main": "background.js"
}
五、修改vue.config.js文件
'use strict'
const path = require('path')
const defaultSettings = require('./src/settings.js')
function resolve(dir) {
return path.join(__dirname, dir)
}
const name = defaultSettings.title //process.env.VUE_APP_TITLE || '若依管理系统' // 网页标题
const BASE_URL = process.env.NODE_EVN === 'production' ? './' : './'
const port = process.env.port || process.env.npm_config_port || 80 // 端口
// vue.config.js 配置说明
//官方vue.config.js 参考文档 https://cli.vuejs.org/zh/config/#css-loaderoptions
// 这里只列一部分,具体配置参考文档
module.exports = {
// 部署生产环境和开发环境下的URL。
// 默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上
// 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。
publicPath: BASE_URL,
// publicPath: process.env.NODE_ENV === "production" ? "/" : "/",
// 在npm run build 或 yarn build 时 ,生成文件的目录名称(要和baseUrl的生产环境路径一致)(默认dist)
outputDir: 'dist',
// 用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下)
assetsDir: 'static',
// 是否开启eslint保存检测,有效值:ture | false | 'error'
lintOnSave: process.env.NODE_ENV === 'development',
// 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。
productionSourceMap: false,
// webpack-dev-server 相关配置
devServer: {
host: '0.0.0.0',
port: port,
open: true,
proxy: {
// detail: https://cli.vuejs.org/config/#devserver-proxy
[process.env.VUE_APP_BASE_API]: {
// target: `http://localhost:8080`,
target: process.env.VUE_APP_SERVER,
changeOrigin: true,
/*pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: '/' + process.env.VUE_APP_BASE_API
},*/
pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: ''
},
ws: true,
},
'^/static': {
target: `http://localhost:80`,
changeOrigin: true,
ws: true,
}
},
disableHostCheck: true
},
configureWebpack: {
name: name,
resolve: {
alias: {
'@': resolve('src')
}
}
},
pluginOptions: {
electronBuilder: {
// nodeIntegration: true,
removeElectronJunk: false,
preload: './src/preload.js',
builderOptions: {
"appId": "com.electron.Saturn",
"productName": "Saturn",//项目名,也是生成的安装文件名,即aDemo.exe
"copyright": "Copyright © 2021",//版权信息
"directories": {
"output": "./build", //打包后文件所在位置
// "app": "./dist_electron" //开始位置
},
"win": {//win相关配置
"icon": "./src/assets/icons/favicon.ico",//图标,当前图标在根目录下,注意这里有两个坑
"target": [
{
"target": "nsis",//利用nsis制作安装程序
"arch": [
"x64",//64位
]
}
]
},
"asar": false,
"nsis": {
"oneClick": false, // 是否一键安装
"allowElevation": true, // 允许请求提升。 如果为false,则用户必须使用提升的权限重新启动安装程序。
"allowToChangeInstallationDirectory": true, // 允许修改安装目录
"installerIcon": "./src/assets/icons/favicon.ico",// 安装图标
"uninstallerIcon": "./src/assets/icons/favicon.ico",//卸载图标
"installerHeaderIcon": "./src/assets/icons/favicon.ico", // 安装时头部图标
"createDesktopShortcut": true, // 创建桌面图标
"createStartMenuShortcut": true,// 创建开始菜单图标
"shortcutName": "Saturn", // 图标名称
},
}
},
},
chainWebpack(config) {
config.plugins.delete('preload') // TODO: need test
config.plugins.delete('prefetch') // TODO: need test
// set svg-sprite-loader
config.module
.rule('svg')
.exclude.add(resolve('src/assets/icons'))
.end()
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(resolve('src/assets/icons'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]'
})
.end()
config
.when(process.env.NODE_ENV !== 'development',
config => {
config
.plugin('ScriptExtHtmlWebpackPlugin')
.after('html')
.use('script-ext-html-webpack-plugin', [{
// `runtime` must same as runtimeChunk name. default is `runtime`
inline: /runtime\..*\.js$/
}])
.end()
config
.optimization.splitChunks({
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial' // only package third parties that are initially dependent
},
elementUI: {
name: 'chunk-elementUI', // split elementUI into a single package
priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
},
commons: {
name: 'chunk-commons',
test: resolve('src/components'), // can customize your rules
minChunks: 3, // minimum common number
priority: 5,
reuseExistingChunk: true
}
}
})
config.optimization.runtimeChunk('single'),
{
from: path.resolve(__dirname, './public/robots.txt'), //防爬虫文件
to: './', //到根目录下
}
}
)
}
}
六、启动
1 本地启动
npm run electron:serve
2 打包
npm run electron:build
打包成功后在build文件夹会生成exe安装文件
补充:
- 启动发现资源请求不到,先执行npm run electron:build(会生成dist_electron文件夹及相关文件),再执行npm run electron:serve,然后将dist_electron/bundled/static 复制一份static放到dist_electron文件夹下
(不要问我为什么,我也不知道……时间紧,任务重,有时间再去研究-_-||) - 接口协议是File的参考上一篇解决 vue项目通过Electron生成桌面应用