總結(jié)
主要執(zhí)行以下命令
全局安裝electron:npm install electron -g
安裝electron-builder依賴:vue add electron-builder
運(yùn)行桌面程序:npm run electron:serve
打包應(yīng)用程序:npm run electron:build
更新應(yīng)用程序:npm i electron-updater --save
electron-builder 教程
一、本人比較笨,拷貝原始項(xiàng)目后譬巫,安轉(zhuǎn)依賴并啟動(dòng)
執(zhí)行命令:npm install
執(zhí)行命令:npm run serve
二糟袁、全局安裝electron:
執(zhí)行命令:npm install electron -g
三、安裝electron-builder依賴:
執(zhí)行命令:vue add electron-builder
electron-builder安裝成功后項(xiàng)目目錄如下:
會(huì)發(fā)現(xiàn)目錄里多了一個(gè)background.js 的文件,并且package.json 里多了依賴和執(zhí)行命令其中這個(gè)“main”: “background.js” 是electron的入口文件
四、運(yùn)行桌面程序
執(zhí)行命令:npm run electron:serve
執(zhí)行以上命令后項(xiàng)目啟動(dòng)速度較慢,可以注釋掉background.js文件中await installExtension(VUEJS_DEVTOOLS) 這一句代碼
項(xiàng)目運(yùn)行后顯示界面如下:
五格侯、打包
執(zhí)行命令:npm run electron:build
執(zhí)行打包命令后報(bào)錯(cuò):"directories" in the root is deprecated, please specify in the "build",如下圖顯示:
解決辦法财著,刪掉“directories”這部分內(nèi)容:
打包成功后exe程序所在位置:
根目錄/dist_electron/win-unpacked/vue-antd-pro.exe 联四,如下圖所示:
六、在vue.config.js中module.exports中添加pluginOptions配置:更換exe應(yīng)用程序中的名稱和圖標(biāo)
打包教程地址:https://blog.csdn.net/Assassin_EZI0/article/details/107144377
執(zhí)行打包命令:npm run electron:build撑教,重新打包后
七朝墩、更新應(yīng)用程序?參考網(wǎng)址
客戶安裝應(yīng)用以后,如果有新功能更新伟姐,那客戶那邊如何更新收苏?解決方案亿卤,安裝:electron-updater
執(zhí)行命令:npm i electron-updater --save
介紹一下原理:使用electron-updater, 打包后會(huì)生成一個(gè)latest.yml 的文件鹿霸,里面包含了版本號(hào)等描述信息排吴,
并且electron-updater 提供了一系列監(jiān)聽事件,允許應(yīng)用向服務(wù)器檢測(cè)當(dāng)前版本是否可以更新懦鼠,
如果可以更新钻哩,在對(duì)應(yīng)的監(jiān)聽事件里做了相關(guān)處理(下載最新版本,下載完成后退出并重新安裝)
?在目錄下新增一個(gè)目錄_main, 里面有兩個(gè)文件肛冶,events.js(定義了事件名稱) 和 updater.js (處理接收)和 helper.js(是封裝的持久化數(shù)據(jù)相關(guān)操作方法)街氢。
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都會(huì)用
export default {
downLoadUpdate: 'downLoadUpdate', // 手動(dòng)下載更新
checkUpdate: 'checkUpdate', // 請(qǐng)求檢查更新
startCheckUpdate: 'startCheckUpdate', // 開始檢查更新
checkUpdateError: 'checkUpdateError', // 檢查更新出錯(cuò)
checkingUpdate: 'checkingUpdate', // 正在檢查更新
updateAvailable: 'updateAvailable', // 有新版本更新
updateNotAvailable: 'updateNotAvailable', // 沒(méi)有新版本更新
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' // 是封裝的持久化數(shù)據(jù)相關(guān)操作方法。
import { app, dialog } from 'electron'
// // 主進(jìn)程接收渲染進(jìn)程(頁(yè)面)派發(fā)過(guò)來(lái)的 檢測(cè)更新事件
// ipcMain.on(Events.checkUpdate, () => {
//? // 向服務(wù)端查詢現(xiàn)在是否有可用的更新睦袖。在調(diào)用這個(gè)方法之前珊肃,必須要先調(diào)用 setFeedURL
//? autoUpdater.checkForUpdates()
// })
// // 主進(jìn)程接收渲染進(jìn)程(頁(yè)面)派發(fā)過(guò)來(lái)的 下載更新事件
// ipcMain.on(Events.downLoadUpdate, () => {
//? autoUpdater.downloadUpdate()
// })
export async function listenUpdater(mainWindow, feedUrl) {
? autoUpdater.setFeedURL(feedUrl)
? await sleep(5000)
? //每次啟動(dòng)自動(dòng)更新檢查 更新版本 --可以根據(jù)自己方式更新、定時(shí)或者什么
? autoUpdater.checkForUpdates()
? autoUpdater.autoDownload = false
? // 當(dāng)更新發(fā)生錯(cuò)誤的時(shí)候觸發(fā)
? autoUpdater.on('error', function(error) {
? ? mainWindow.webContents.send(Events.checkUpdateError, JSON.stringify(error))
? })
? // 當(dāng)開始檢查更新的時(shí)候觸發(fā)
? autoUpdater.on('checking-for-update', function() {
? ? mainWindow.webContents.send(Events.checkingUpdate)
? ? // const { version } = info
? ? // askUpdate(version)
? })
? // 當(dāng)發(fā)現(xiàn)一個(gè)可用更新的時(shí)候觸發(fā)扣泊,更新包下載會(huì)自動(dòng)開始
? autoUpdater.on('update-available', function(info) {
? ? mainWindow.webContents.send(Events.updateAvailable, info)
? ? // logger.info('檢查到有更新近范,開始下載新版本')
? ? // logger.info(info)
? ? const { version } = info
? ? askUpdate(version)
? })
? // 當(dāng)沒(méi)有可用更新的時(shí)候觸發(fā)
? autoUpdater.on('update-not-available', function(info) {
? ? mainWindow.webContents.send(Events.updateNotAvailable, info)
? })
? // 更新下載進(jìn)度事件
? 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創(chuàng)建之后
? ? dialog
? ? ? .showMessageBox({
? ? ? ? title: '升級(jí)提示!',
? ? ? ? message: '已為您下載最新應(yīng)用剑辫,點(diǎn)擊確定馬上替換為最新版本摘昌!',
? ? ? })
? ? ? .then(() => {
? ? ? ? // logger.info('退出應(yīng)用,安裝開始虱颗!')
? ? ? ? //重啟應(yīng)用并在下載后安裝更新沥匈。 它只應(yīng)在發(fā)出 update-downloaded 后方可被調(diào)用。
? ? ? ? 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) {
? ? // 不再詢問(wèn) 直接下載更新
? ? autoUpdater.downloadUpdate()
? } else {
? ? const { response, checkboxChecked } = await dialog.showMessageBox({
? ? ? type: 'info',
? ? ? buttons: ['關(guān)閉', '跳過(guò)這個(gè)版本', '安裝更新'],
? ? ? title: '軟件更新提醒',
? ? ? message: `最新版本是 ${version}忘渔,您現(xiàn)在的版本是 ${app.getVersion()}高帖,現(xiàn)在要下載更新嗎?`,
? ? ? defaultId: 2,
? ? ? cancelId: -1,
? ? ? checkboxLabel: '以后自動(dòng)下載并安裝更新',
? ? ? 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(['更新操作', '關(guān)閉更新提醒'])
? ? }
? }
}
在background.js 里執(zhí)行監(jiān)聽
每次更新時(shí)只需把exe文件和 latest.yml 文件放到服務(wù)器上就行了畦粮!
為了打包時(shí)生成latest.yml文件散址,須要在 build 參數(shù)中添加 publish 配置
八:自定義electron窗口導(dǎo)航欄
方法一:自定義electron窗口
1、在background.js文件中找到BrowserWindow中設(shè)置frame為false宣赔,關(guān)閉原生導(dǎo)航欄
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">窗口標(biāo)題</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()
? ? },
//恢復(fù)
? ? restore(){
? ? remote.getCurrentWindow().restore()
? ? this.isMaxSize=!this.isMaxSize
? ? },
//最大化
? ? maxsize(){
? ? ? console.log(remote.getCurrentWindow())
? ? ? var win=remote.getCurrentWindow()
? ? ? win.maximize()
? ? ? this.isMaxSize=!this.isMaxSize
? ? },
//關(guān)閉
? ? 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>
注意:使用上述代碼方法時(shí)需要注意在background.js文件中找到BrowserWindow將enableRemoteModule設(shè)置為true
方法二:Electron自定義導(dǎo)航欄
1儒将、在主進(jìn)程background.js文件中配置frame: false? 關(guān)閉原生導(dǎo)航欄
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="關(guān)閉" class="close noDrag" @click="setWin('close')"></i>
? ? ? ? </div>
? ? </section>
</template>
<script>
/**渲染進(jìn)程(也就是當(dāng)前頁(yè)面)通過(guò)ipcRenderer.send 將方法字段名傳遞給主進(jìn)程钩蚊。**/
const {ipcRenderer} = window.require("electron");
? ? export default {
? ? ? ? methods: {
? ? ? ? ? ? setWin(type){
? ? ? ? ? ? ? ? ipcRenderer.send(type);
? ? ? ? ? ? }
? ? ? ? }
</script>
<style>
/**自定義的導(dǎo)航欄鼠標(biāo)點(diǎn)擊是無(wú)法拖動(dòng)的贡翘,需要在css中增加-webkit-app-region: drag; 讓鼠標(biāo)可以拖動(dòng)蹈矮,若可拖動(dòng)的html結(jié)構(gòu)中有點(diǎn)擊事件,那//需要在點(diǎn)擊元素的css中另加-webkit-app-region: no-drag; 讓其可觸發(fā)點(diǎn)擊事件鸣驱,若不加該css則點(diǎn)擊事件無(wú)效含滴。**/
.MainHeader {
? ? ? ? -webkit-app-region: drag;
? ? ? ? .noDrag{
? ? ? ? ? ? -webkit-app-region: no-drag;
? ? ? ?}
}
</style>
4、在主進(jìn)程background.js中執(zhí)行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());
九:自定義桌面應(yīng)用程序后谈况,瀏覽器端無(wú)法識(shí)別window,客戶端無(wú)法啟動(dòng),因此可以檢查代碼是否正在Electron或?yàn)g覽器中執(zhí)行递胧,來(lái)判斷是否引用window.require('electron')
檢查代碼是否正在Electron或?yàn)g覽器中執(zhí)行
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或?yàn)g覽器中執(zhí)行? ? var {remote}=window.require('electron')? ? console.log("Electron中執(zhí)行 !");}else{? ? console.log("瀏覽器中執(zhí)行");}
如圖所示:
十:去掉導(dǎo)航欄菜單
十一:electron修改vue項(xiàng)目打包后的exe圖標(biāo)
注意:以下生成圖標(biāo)的地址和打包牡蠣都在dist_electron文件中
1碑韵、準(zhǔn)備一張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缎脾、執(zhí)行以下命令
npm run electron:generate-icons
就會(huì)在dist_electron文件夾中生成一系列打包所需的圖標(biāo)文件祝闻,如下:
4、在vue.config.json中pluginOptions對(duì)象下配置圖標(biāo)路徑遗菠,其中directories 為打包地址联喘,win為圖標(biāo)路徑。