总结
主要执行以下命令
全局安装electron:npm install electron -g
安装electron-builder依赖:vue add electron-builder
运行桌面程序:npm run electron:serve
打包应用程序:npm run electron:build
更新应用程序:npm i electron-updater --save
electron-builder 教程
一、本人比较笨,拷贝原始项目后,安转依赖并启动
执行命令:npm install
执行命令:npm run serve
二、全局安装electron:
执行命令:npm install electron -g
三、安装electron-builder依赖:
执行命令:vue add electron-builder
electron-builder安装成功后项目目录如下:
会发现目录里多了一个background.js 的文件,并且package.json 里多了依赖和执行命令其中这个“main”: “background.js” 是electron的入口文件
四、运行桌面程序
执行命令:npm run electron:serve
执行以上命令后项目启动速度较慢,可以注释掉background.js文件中await installExtension(VUEJS_DEVTOOLS) 这一句代码
项目运行后显示界面如下:
五、打包
执行命令:npm run electron:build
执行打包命令后报错:"directories" in the root is deprecated, please specify in the "build",如下图显示:
解决办法,删掉“directories”这部分内容:
打包成功后exe程序所在位置:
根目录/dist_electron/win-unpacked/vue-antd-pro.exe ,如下图所示:
六、在vue.config.js中module.exports中添加pluginOptions配置:更换exe应用程序中的名称和图标
打包教程地址:https://blog.csdn.net/Assassin_EZI0/article/details/107144377
执行打包命令:npm run electron:build,重新打包后
七、更新应用程序 参考网址
客户安装应用以后,如果有新功能更新,那客户那边如何更新?解决方案,安装:electron-updater
执行命令:npm i electron-updater --save
介绍一下原理:使用electron-updater, 打包后会生成一个latest.yml 的文件,里面包含了版本号等描述信息,
并且electron-updater 提供了一系列监听事件,允许应用向服务器检测当前版本是否可以更新,
如果可以更新,在对应的监听事件里做了相关处理(下载最新版本,下载完成后退出并重新安装)
在目录下新增一个目录_main, 里面有两个文件,events.js(定义了事件名称) 和 updater.js (处理接收)和 helper.js(是封装的持久化数据相关操作方法)。
helper.js
import { join } from 'path'
import fs from 'fs'
import { app } from 'electron'
const dataPath = join(app.getPath('userData'), 'data.json')
export function getLocalData(key) {
if (!fs.existsSync(dataPath)) {
fs.writeFileSync(dataPath, JSON.stringify({}), { encoding: 'utf-8' })
}
let data = fs.readFileSync(dataPath, { encoding: 'utf-8' })
let json = JSON.parse(data)
return key ? json[key] : json
}
export function setLocalData(key, value) {
let args = [...arguments]
let data = fs.readFileSync(dataPath, { encoding: 'utf-8' })
let json = JSON.parse(data)
if (args.length === 0 || args[0] === null) {
json = {}
} else if (args.length === 1 && typeof key === 'object' && key) {
json = {
...json,
...args[0],
}
} else {
json[key] = value
}
fs.writeFileSync(dataPath, JSON.stringify(json), { encoding: 'utf-8' })
}
export async function sleep(ms) {
return new Promise((resolve) => {
const timer = setTimeout(() => {
resolve()
clearTimeout(timer)
}, ms)
})
}
events.js:
// ipc通信事件, main 和 render都会用
export default {
downLoadUpdate: 'downLoadUpdate', // 手动下载更新
checkUpdate: 'checkUpdate', // 请求检查更新
startCheckUpdate: 'startCheckUpdate', // 开始检查更新
checkUpdateError: 'checkUpdateError', // 检查更新出错
checkingUpdate: 'checkingUpdate', // 正在检查更新
updateAvailable: 'updateAvailable', // 有新版本更新
updateNotAvailable: 'updateNotAvailable', // 没有新版本更新
updateDownloading: 'updateDownloading', // 正在下载中
updateDownloaded: 'updateDownloaded' // 下载完成
}
updater.js:
import { autoUpdater } from 'electron-updater'
// import { ipcMain } from 'electron'
// import logger from 'electron-log'
import Events from './events'
import { getLocalData, setLocalData, sleep } from './helper' // 是封装的持久化数据相关操作方法。
import { app, dialog } from 'electron'
// // 主进程接收渲染进程(页面)派发过来的 检测更新事件
// ipcMain.on(Events.checkUpdate, () => {
// // 向服务端查询现在是否有可用的更新。在调用这个方法之前,必须要先调用 setFeedURL
// autoUpdater.checkForUpdates()
// })
// // 主进程接收渲染进程(页面)派发过来的 下载更新事件
// ipcMain.on(Events.downLoadUpdate, () => {
// autoUpdater.downloadUpdate()
// })
export async function listenUpdater(mainWindow, feedUrl) {
autoUpdater.setFeedURL(feedUrl)
await sleep(5000)
//每次启动自动更新检查 更新版本 --可以根据自己方式更新、定时或者什么
autoUpdater.checkForUpdates()
autoUpdater.autoDownload = false
// 当更新发生错误的时候触发
autoUpdater.on('error', function(error) {
mainWindow.webContents.send(Events.checkUpdateError, JSON.stringify(error))
})
// 当开始检查更新的时候触发
autoUpdater.on('checking-for-update', function() {
mainWindow.webContents.send(Events.checkingUpdate)
// const { version } = info
// askUpdate(version)
})
// 当发现一个可用更新的时候触发,更新包下载会自动开始
autoUpdater.on('update-available', function(info) {
mainWindow.webContents.send(Events.updateAvailable, info)
// logger.info('检查到有更新,开始下载新版本')
// logger.info(info)
const { version } = info
askUpdate(version)
})
// 当没有可用更新的时候触发
autoUpdater.on('update-not-available', function(info) {
mainWindow.webContents.send(Events.updateNotAvailable, info)
})
// 更新下载进度事件
autoUpdater.on('download-progress', function(progressObj) {
mainWindow.webContents.send(Events.updateDownloading, progressObj)
})
// 下载完成
autoUpdater.on('update-downloaded', function() {
// mainWindow.webContents.send(Events.updateDownloaded)
// autoUpdater.quitAndInstall()
// logger.info('下载完毕!提示安装更新')
// logger.info(res)
//dialog 想要使用,必须在BrowserWindow创建之后
dialog
.showMessageBox({
title: '升级提示!',
message: '已为您下载最新应用,点击确定马上替换为最新版本!',
})
.then(() => {
// logger.info('退出应用,安装开始!')
//重启应用并在下载后安装更新。 它只应在发出 update-downloaded 后方可被调用。
mainWindow.webContents.send(Events.updateDownloaded)
autoUpdater.quitAndInstall()
})
})
}
async function askUpdate(version) {
// logger.info(`最新版本 ${version}`)
let { updater } = getLocalData()
let { auto, version: ver, skip } = updater || {}
// logger.info(
// JSON.stringify({
// ...updater,
// ver: ver,
// })
// )
if (skip && version === ver) return
if (auto) {
// 不再询问 直接下载更新
autoUpdater.downloadUpdate()
} else {
const { response, checkboxChecked } = await dialog.showMessageBox({
type: 'info',
buttons: ['关闭', '跳过这个版本', '安装更新'],
title: '软件更新提醒',
message: `最新版本是 ${version},您现在的版本是 ${app.getVersion()},现在要下载更新吗?`,
defaultId: 2,
cancelId: -1,
checkboxLabel: '以后自动下载并安装更新',
checkboxChecked: false,
textWidth: 300,
})
if ([1, 2].includes(response)) {
let updaterData = {
version: version,
skip: response === 1,
auto: checkboxChecked,
}
setLocalData({
updater: {
...updaterData,
},
})
if (response === 2) autoUpdater.downloadUpdate()
// logger.info(['更新操作', JSON.stringify(updaterData)])
} else {
// logger.info(['更新操作', '关闭更新提醒'])
}
}
}
在background.js 里执行监听
每次更新时只需把exe文件和 latest.yml 文件放到服务器上就行了!
为了打包时生成latest.yml文件,须要在 build 参数中添加 publish 配置
八:自定义electron窗口导航栏
方法一:自定义electron窗口
1、在background.js文件中找到BrowserWindow中设置frame为false,关闭原生导航栏
2、在App.vue中引入窗口组件mainHeader.vue
3、mainHeader.vue代码如下
<template>
<div id="app">
<div class="titleBar">
<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 id="nav">
<router-link to="/about">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view/>
</div>
</template>
<script>
const {remote}=window.require('electron')
export default {
data(){
return{isMaxSize:true}
},
methods:{
//最小化
minisize(){
var win=remote.getCurrentWindow()
win.minimize()
},
//恢复
restore(){
remote.getCurrentWindow().restore()
this.isMaxSize=!this.isMaxSize
},
//最大化
maxsize(){
console.log(remote.getCurrentWindow())
var win=remote.getCurrentWindow()
win.maximize()
this.isMaxSize=!this.isMaxSize
},
//关闭
close(){
remote.getCurrentWindow().hide()
}
},
}
</script>
<style>
@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;
}
.logo img{
width:20px;
height:20px;
margin-top:7px;
}
.txt{text-align: left;
flex:1;
}
.close:hover{color: #fff;
background-color:#ff4d4f;
}
.windowTool div{
color:#888;
height:100%;
width:38px;
display: inline-block;
cursor:pointer;
}
i{font-size:12px;}
.windowTool div:hover{background: #ffccc7;}
body{margin:0;
padding:0;
overflow: hidden;
height:100%;
}
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
display: flex;
flex-direction: column;
}
#nav {
padding: 30px;
}
#nav a {
font-weight: bold;
color: #2c3e50;
}
#nav a.router-link-exact-active {
color: #42b983;
}
</style>
注意:使用上述代码方法时需要注意在background.js文件中找到BrowserWindow将enableRemoteModule设置为true
方法二:Electron自定义导航栏
1、在主进程background.js文件中配置frame: false 关闭原生导航栏
2、在App.vue中引入窗口组件mainHeader.vue
3、mainHeader.vue代码如下
<template>
<section class="MainHeader">
<div style="display: flex;">
<router-link :to="'/timetables'" class="logo noDrag" v-stat="{action:'logo'}"/>
<div class="userInfo noDrag" v-if="userInfo">
<div class="name " @click="path" v-stat="{action:'userInfo'}">
<img src="~assets/images/user.png"/>
<span>{{userInfo?userInfo.name:''}}</span>
</div>
<span class="logout" @click="logOut">退出</span>
</div>
</div>
<div class="systemBtn">
<i class="mini noDrag" title="最小化" @click="setWin('min')"></i>
<i title="最大化" class="max noDrag" @click="setWin('max')"></i>
<i title="关闭" class="close noDrag" @click="setWin('close')"></i>
</div>
</section>
</template>
<script>
/**渲染进程(也就是当前页面)通过ipcRenderer.send 将方法字段名传递给主进程。**/
const {ipcRenderer} = window.require("electron");
export default {
methods: {
setWin(type){
ipcRenderer.send(type);
}
}
</script>
<style>
/**自定义的导航栏鼠标点击是无法拖动的,需要在css中增加-webkit-app-region: drag; 让鼠标可以拖动,若可拖动的html结构中有点击事件,那//需要在点击元素的css中另加-webkit-app-region: no-drag; 让其可触发点击事件,若不加该css则点击事件无效。**/
.MainHeader {
-webkit-app-region: drag;
.noDrag{
-webkit-app-region: no-drag;
}
}
</style>
4、在主进程background.js中执行ipcMain方法。
import {BrowserWindow, ipcMain} from 'electron'
mainWindow = new BrowserWindow({
height: 690,
useContentSize: true,
width: 1100,
minWidth: 1100,
minHeight: 690,
frame: false
})
ipcMain.on('min', () => mainWindow.minimize());
ipcMain.on('max', () => {
if (mainWindow.isMaximized()) {
mainWindow.restore();
} else {
mainWindow.maximize()
}
});
ipcMain.on('close', () => mainWindow.close());
九:自定义桌面应用程序后,浏览器端无法识别window,客户端无法启动,因此可以检查代码是否正在Electron或浏览器中执行,来判断是否引用window.require('electron')
export function isElectron() {
// Renderer process
if (typeof window !== 'undefined' && typeof window.process === 'object' && window.process.type === 'renderer') {
return true;
}
// Main process
if (typeof process !== 'undefined' && typeof process.versions === 'object' && !!process.versions.electron) {
return true;
}
// Detect the user agent when the `nodeIntegration` option is set to true
if (typeof navigator === 'object' && typeof navigator.userAgent === 'string' && navigator.userAgent.indexOf('Electron') >= 0) {
return true;
}
return false;
}
在mainHeader组件中引入
import {isElectron} from '@/utils/isElectron'
if(isElectron()){//检查代码是否正在Electron或浏览器中执行 var {remote}=window.require('electron') console.log("Electron中执行 !");}else{ console.log("浏览器中执行");}
如图所示:
十:去掉导航栏菜单
十一:electron修改vue项目打包后的exe图标
注意:以下生成图标的地址和打包牡蛎都在dist_electron文件中
1、准备一张icon.png图片保存在public目录下
2.安装 electron-icon-builder
npm i electron-icon-builder --D
3、在package.json的scripts文件中添加一条命令并保存:
"electron:generate-icons": "electron-icon-builder --input=./public/icon.png --output=dist_electron --flatten"
3、执行以下命令
npm run electron:generate-icons
就会在dist_electron文件夹中生成一系列打包所需的图标文件,如下:
4、在vue.config.json中pluginOptions对象下配置图标路径,其中directories 为打包地址,win为图标路径。