使用Vue Cli4+Electron 13搭建桌面端应用小记
项目配置
- Vue: 2
- Vue Cli: 4.5.14
- Electron: 13.6.0
- Nodejs: 14.17.0
项目搭建
- 创建Vue项目;
vue create vue-project
本文使用的vue2 default,直接安装完成。建议自定义,然后把less安装上去,以便结合antd vue使用。
- 进入项目目录;
cd vue-project
- 安装并调用vue-cli-plugin-electron-builder的生成器;
vue add electron-builder
这步需要等待差不多1分钟左右,如果网络不稳定造成electron安装失败,需要重新执行这一步。
- 安装好依赖,就可以运行项目了;
yarn electron:serve
或
npm run electron:serve
- 项目打包成桌面端app;
yarn electron:build
或
npm run electron:build
应用配置
开发桌面端应用,关于less、应用标题、打包等配置如下:
安装LESS
如果安装的vue2项目没有引入less,在基于vue cli4的项目里安装less,以及使用antd vue的less版本方法如下:
- 安装
less
和less-loader
;
npm i less@3.9.0 less-loader@6.1.0
在node
14.17.0下使用less-loader
6.1.0版本没有问题,使用7以上的版本就报错了。
- 安装以下依赖:
npm i style-resources-loader vue-cli-plugin-style-resources-loader -D
- 在项目根目录的
vue.config.js
文件添加如下配置:
const path = require('path')
module.exports = {
pluginOptions: {
'style-resources-loader': {
preProcessor: 'less',
patterns: [
path.resolve(__dirname, 'src/global.less')
]
}
}
}
说明:global.less
文件需要新建或改为你自己的全局less文件名;
-
现在可以在vue项目里愉快的使用less了,如需引用antd的less继续往下操作;
-
在项目根目录的
babel.config.js
文件里的plugins
下面添加如下配置:
module.exports = {
plugins: [
[
'import',
{ libraryName: 'ant-design-vue', libraryDirectory: 'es', style: true }
]
]
}
- 在项目的
main.js
里可以像下面这样按需引用antd的组件了,而不是引用整个css文件;
import {
Badge, Button, Card, Divider,
} from 'ant-design-vue'
应用标题配置
默认electron开启了边框显示,标题会显示在窗体底部。如果设置了不显示边框则标题不显示。
module.exports = {
chainWebpack: config => {
config.plugin('html')
.tap(args => {
args[0].title = "应用名称"
return args
})
}
}
这里设置好就可以了,就不用到index.html
里修改了。
Vue读写文件配置
默认在vue里无法读取本地文件,需要在根目录的vue.config.js
文件添加如下配置:
module.exports = {
pluginOptions: {
electronBuilder: {
nodeIntegration: true
}
}
}
完整配置文件
babel.config.js
配置
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
plugins: [
[
'import',
{ libraryName: 'ant-design-vue', libraryDirectory: 'es', style: true }
]
]
}
vue.config.js
配置
const path = require('path')
module.exports = {
css: {
loaderOptions: {
less: {
lessOptions:{
modifyVars: {},
javascriptEnabled: true,
}
}
}
},
chainWebpack: config =>{
config.plugin('html')
.tap(args => {
args[0].title = "应用名称"
return args
})
},
pluginOptions: {
'style-resources-loader': {
preProcessor: 'less',
patterns: [
path.resolve(__dirname, 'src/global.less')
]
},
electronBuilder: {
builderOptions: {
extraResources: {
from: './public/data',
to: './data'
},
mac: {
icon: './public/app.png'
},
win: {
icon: './public/app.ico'
},
productName: 'build-app-name',
},
nodeIntegration: true
}
}
}
Electron开发技术点
发送消息
- 渲染进程(vue端)向主线程(electron端)发送数据:
import { ipcRenderer } from 'electron'
const data = { a: 1 }
ipcRenderer.send('call', data)
发送的数据支持数组或对象格式。
- 主线程接收渲染进程发过来的数据,并返回数据;
import { ipcMain } from 'electron'
ipcMain.on('call', (event, data) => {
console.log('data', data)
// ...
const replyData = { success: true, message: '收到数据了' }
event.reply('reply', replyData)
})
- 渲染进程接收主线程发送数据:
import { ipcRenderer } from 'electron'
ipcRenderer.on('reply', function(event, data) {
console.log('data', data)
})
reply
为上面主线程background.js
里定义的事件名。至此3步,就是从渲染进程发送到接收数据的一个过程了。
- 如果需要从主进程主动向渲染进程发送数据,方式如下:
win.webContents.send('fromElectron', { b: 2 })
这里的win
是electron初始化后的窗体。
background.js
代码写法
这是项目生成的electron入口文件,默认是多个function同步写法,建议改造成Class
的写法,便于后期扩展。
'use strict'
import { app, protocol, BrowserWindow, ipcMain, nativeTheme } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
const isDevelopment = process.env.NODE_ENV !== 'production'
class ElectronApp {
constructor() {
this.mainWindow = null
}
init() {
this.initApp()
this.bindEvents()
}
initApp() {
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
{ scheme: 'app', privileges: { secure: true, standard: true } }
])
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
// 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) this.createWindow()
})
// 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.on('ready', async () => {
if (isDevelopment && !process.env.IS_TEST) {
// Install Vue Devtools
try {
await installExtension(VUEJS_DEVTOOLS)
} catch (e) {
console.error('Vue Devtools failed to install:', e.toString())
}
}
await this.createWindow()
})
// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
if (process.platform === 'win32') {
process.on('message', (data) => {
if (data === 'graceful-exit') {
app.quit()
}
})
} else {
process.on('SIGTERM', () => {
app.quit()
})
}
}
}
bindEvents() {
// 切换操作
ipcMain.on('call', async (event, app, project) => {
const replyData = { success: true, message: '收到数据了' }
event.reply('reply', replyData)
})
}
async createWindow() {
// Create the browser window.
const win = new BrowserWindow({
width: 800,
height: 600,
minWidth: 800,
minHeight: 600,
webPreferences: {
// Use pluginOptions.nodeIntegration, leave this alone
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
},
})
win.setMenu(null)
if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode
await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
if (!process.env.IS_TEST) win.webContents.openDevTools()
} else {
createProtocol('app')
// Load the index.html when not in development
win.loadURL('app://./index.html')
}
this.mainWindow = win
}
}
new ElectronApp().init()