代码环境 vue3+vite+electron
主要工具版本
- "electron-updater": "^6.1.1"
- "electron": "^15.1.2",
- "electron-builder": "^23.6.0",
逻辑解析
使用electron-builder
打包配置生成exe安装包(winodow),将最新的exe包放在线上地址。本地用户使用低版本的时候会出提示发现最新版本,从而下载远端线上的exe包。从而进行安装。
放在远端的文件
哪些不用我不太清楚
需要注意!我们线上的链接要做到。直接访问exe的路径是下载。访问yml的路径是在网页上打开。具体咋办,我不知道咋弄。
基础代码。
使用electron的弹出框进行升级
json
package.json文件
{
"version": "1.6.0",用于判断是否需要判断的字段
......
"build": {
.......
"publish": [用于后构建时候生成latest.yml等附属文件
{
"provider": "generic",固定字段
"url": "http://127.0.0.1:8080"
这个玩意随便填写,因为我们用的electron-builder更新
}
],
......
},
"directories": {这个vite时候有用
"buildResources": "build",
"output": "dist_electron"
}
}
js
生成electron窗口的js文件中
const { app, BrowserWindow, dialog } = require('electron')
const { autoUpdater } = require("electron-updater");
const feedUrl = `放在远端的链接/${process.platform}`;
//process.platform其实就是`win32`字段因为我们做的是固定window64升级的嘛
//所以也可以写成const feedUrl = `放在远端的链接`;
let webContents;
let sendUpdateMessage = (message, data) => {
webContents.send("message", { message, data });
};
let checkForUpdates = () => {
autoUpdater.setFeedURL(feedUrl);
autoUpdater.on(
"update-downloaded",
function (
event,
releaseNotes,
releaseName,
releaseDate,
updateUrl,
quitAndUpdate
) {
dialog
.showMessageBox({
type: "info",
title: "应用更新",
message: "发现新版本,是否更新?",
buttons: ["是", "否"],
})
.then((buttonIndex) => {
if (buttonIndex.response == 0) {
autoUpdater.quitAndInstall();
}
});
}
);
//执行自动更新检查
autoUpdater.checkForUpdates();
};
app.on('ready', () => {
let win = createWindow()
webContents = win.webContents;
// 一次更新检车
checkForUpdates();
})
使用自定义(html)弹出框进行升级,并且可以自定义说明该版本更新的内容
package.json文件内容相同
electron-builder生成后的latest.yml文件
info
字段为手动添加后在上传到远端服务器上。只有info为自定义的,其余都是生成的。且可以多个自定义字段,在autoUpdater.on"update-available"
和"update-not-available"
后的参数info都可以获取到。
js
生成electron窗口的js文件中
const { app, BrowserWindow } = require('electron')
const { autoUpdater } = require("electron-updater");
const feedUrl = `放在远端的链接/${process.platform}`;
//process.platform其实就是`win32`字段因为我们做的是固定window64升级的嘛
//所以也可以写成const feedUrl = `放在远端的链接`;
let webContents;
let sendUpdateMessage = (message, data) => {
webContents.send("message", { message, data });
};
//我改名字了,因为两个名字一样我看的不舒服
let updateHandle = () => {
autoUpdater.setFeedURL(feedUrl);
autoUpdater.autoDownload = false
autoUpdater.on("error", (data) => {
sendUpdateMessage("更新报错", data);
});
autoUpdater.on("checking-for-update", (data) => {
sendUpdateMessage("判断是否需要更新");
});
// 触发更新时候
autoUpdater.on("update-available", (info) => {
//info可以读取latest.yml中的内容
sendUpdateMessage("不是最新版本需要更新", info);
});
autoUpdater.on("update-not-available", function (ifo) {
sendUpdateMessage("最新版本不用更新", ifo);
});
autoUpdater.on('download-progress', (progressObj) => {
sendUpdateMessage('正在下载,获取进度条', progressObj);
/*后续有用,官方数据展示(下图
progressObj:typeof{
progress
bytesPerSecond
percent
total
transferred
}
*/
})
autoUpdater.on(
"update-downloaded",
function (event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) {
sendUpdateMessage('下载结束')
//会出现没有进度条进行的情况。
//该情况是已经下载到本地了,但是你没有安装。
sendUpdateMessage('正在下载,获取进度条', {
percent:100
});
}
)
ipcMain.on("updateNow", () => {
autoUpdater.quitAndInstall();
});
ipcMain.on("downloadUpdate", () => {
autoUpdater.downloadUpdate()
});
ipcMain.on("checkForUpdate", () => {
autoUpdater.checkForUpdates()
})
};
app.on('ready', () => {
let win = createWindow()
webContents = win.webContents;
// 一次更新检车
updateHandle();
})
html
App.vue页面vue3
<style lang="scss">
.dialog {
padding: 0;
border: none;
background: transparent;
.content {
padding: 128px 76px 0 76px;
box-sizing: border-box;
width: 860px;
height: 653px;
background: url("./assets/update.png");
background-size: cover;
position: relative;
}
.main {
height: 300px;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
color: #999999;
padding: 30px 0;
box-sizing: border-box;
.bt {
color: #333333;
font-size: 24px;
line-height: 36px;
margin-bottom: 20px;
flex-shrink: 0;
}
.info {
overflow-y: auto;
&::-webkit-scrollbar {
width: 0;
}
}
}
.close-btn {
width: 46px;
object-fit: contain;
position: absolute;
top: 42px;
right: 14px;
cursor: pointer;
}
.title-1 {
font-size: 40px;
font-weight: 700;
line-height: 60px;
letter-spacing: 2px;
color: #ffffff;
text-align: center;
}
.title-2 {
font-size: 24px;
line-height: 36px;
letter-spacing: 1.2px;
color: #ffffff;
text-align: center;
}
&::backdrop {
background: #00000080;
}
.operate-box {
text-align: center;
button {
cursor: pointer;
width: 160px;
height: 50px;
border-radius: 5px;
background: #377dff;
text-align: center;
border: none;
outline: none;
line-height: 50px;
color: #ffffff;
&:first-of-type {
margin-right: 80px;
}
}
}
.progress-bar {
width: 100%;
height: 20px;
background-color: #e4e8fa;
border-radius: 20px;
// overflow: hidden;
position: relative;
}
.progress-bar::before {
content: "";
display: block;
height: 100%;
background: linear-gradient(
-45deg,
#5a7ace 10%,
#699de7 0,
#699de7 50%,
#5a7ace 0,
#5a7ace 60%,
#699de7 0
);
background-size: 20px 20px;
// transition: width 1s ease-in-out;
width: var(--lk-width);
border-radius: 20px;
}
.progress-bar span.t {
width: 60px;
height: 34px;
border-radius: 26px;
border: 2px solid #fff;
background: #2b459c;
color: #fff;
text-align: center;
position: absolute;
top: -8.5px;
left: calc(var(--lk-width) - 30px);
font-size: 22px;
font-weight: 600;
span {
font-size: 14px;
font-weight: 400;
}
}
}
</style>
<template>
<dialog ref="dialog" class="dialog">
<div class="content">
<img
class="close-btn"
src="./assets/close.png"
@click="dialog.close()"
/>
<template v-if="state.isError">
<div class="title-1">升级失败</div>
<div class="title-2">很抱歉,软件升级失败</div>
<div class="main">
<div class="bt">您可以联系工作人员</div>
<div class="info">
<p>姓名:XXX</p>
<p>联系方式:XXXXXXXXXXX</p>
</div>
</div>
<div class="operate-box">
<template v-if="!state.isLoading">
<button
style="background: #bebebe"
@click="dialog.close()"
>
下次升级
</button>
<button
@click="
state.isError = false;
methods.updateExe();
"
>
重新升级
</button>
</template>
<div
v-else
class="progress-bar"
:style="{ '--lk-width': state.progressValue + '%' }"
>
<span class="t"
>{{ state.progressValue }}<span>%</span>
</span>
</div>
</div>
</template>
<template v-else
><div class="title-1">
{{ state.isLoading ? "软件升级中" : "软件升级" }}
</div>
<div class="title-2">
{{
state.isLoading
? "正在升级软件,请勿进行任何操作!"
: "快来体验新版本吧!"
}}
</div>
<div class="main">
<div class="bt">更新内容:</div>
<div class="info" v-html="state.updateInfo"></div>
</div>
<div class="operate-box">
<template v-if="!state.isLoading">
<button
style="background: #bebebe"
@click="dialog.close()"
>
下次提醒
</button>
<button @click="methods.updateExe()">立即升级</button>
</template>
<div
v-else
class="progress-bar"
:style="{ '--lk-width': state.progressValue + '%' }"
>
<span class="t"
>{{ state.progressValue }}<span>%</span>
</span>
</div>
</div></template
>
</div>
</dialog>
<router-view></router-view>
</template>
<script setup>
import { onMounted, reactive, ref } from "vue";
const dialog = ref(null);
const { ipcRenderer, ipcMain } = require("electron");
const state = reactive({
isLoading: false,
progressValue: 0,
isError: false,
updateInfo: "",
});
ipcRenderer.on("message", (event, { message, data }) => {
console.group("--------------数据------------------");
console.log(message);
console.log(data);
console.groupEnd();
switch (message) {
case "不是最新版本需要更新":
dialog.value.showModal();
state.updateInfo = data.info;
break;
case "正在下载,获取进度条":
state.progressValue = Math.floor(data.percent);
break;
case "下载结束,可以安装":
setTimeout((_) => {
//为了让他看清楚进度条满了!
ipcRenderer.send("updateNow");
}, 1000);
break;
case "更新报错":
state.isError = true;
break;
default:
break;
}
});
onMounted(() => {
ipcRenderer.send("checkForUpdate");
});
const methods = {
updateExe() {
state.isLoading = true;
ipcRenderer.send("downloadUpdate");
},
};
需要注意(或者我遇到的坑
- 逻辑性,不可以在创建窗口函数中进行autoUpdater.checkForUpdates()需要在vue页面中通讯进行更新检测
- 本地测试,进度条可能会不走这个逻辑。因为本地已经下载完成,所以需要传递在下载文成时候弄个加进度条从而前端页面赋值让用户看到进度条;
autoUpdater.downloadUpdate()
需要和autoUpdater.autoDownload = false
配合使用
总结!逻辑!
autoUpdater.checkForUpdates()
进行更新出发autoUpdater.on("checking-for-update")
监听。- 如果是最新版本不需要更新走
autoUpdater.on("update-not-available")
结束。 - 不是最新版本走
autoUpdater.on("update-available")
+autoUpdater.on('download-progress')
+autoUpdater.on("update-downloaded")
+autoUpdater.quitAndInstall()
; - 如果设置了
autoUpdater.autoDownload = false
;需要触发autoUpdater.downloadUpdate()
才能继续进行(一般都是去下载!)