下载更新
思路:
- 使用通信方案进行main层和renderer层的通信
- main层实现下载逻辑
- renderer实现渲染逻辑,并在完成后通过electron的shell对象打开文件进行安装
通信方案在之前文章有提到过。不赘述
代码实现逻辑
main层
- 向renderer层的通信
src/main/services/ipcMain
//* 用于管理main层的监听工具类
import { ipcMain } from "electron"
//* 引入通信后需要使用的工具类
import DownloadFile from "./downloadFile"
//* 引入工具类
import { getMainWindowByIpcEvent, getDownloadPath, getHistoryPath } from '../utils/MainWindow'
//* 通信后需要使用的对象
let appUpdate = null
function onEvent() { //* 监听从rendener层传送过来的数据
//开始下载事件监听
ipcMain.handle('start-download', (event, msg) => {
new DownloadFile(getMainWindowByIpcEvent(event), getDownloadPath(), getHistoryPath()).start()
})
}
export function Mainfunc() {
//* 监听事件
onEvent()
}
//* 用于管理ipc通信
export default {
}
src/main/utils/MainWindow.ts 工具类
//* 关于mainWindow的工具类
import { app, BrowserWindow, IpcMainInvokeEvent } from "electron";
import path from "path";
// import { arch, platform } from "os";
// import packageInfo from '../../../package.json'
//* 根据Ipc获取mainWindow
export function getMainWindowByIpcEvent(event: IpcMainInvokeEvent) {
return BrowserWindow.fromWebContents(event.sender);
}
//* 发送信息给渲染层(vue)
export function sendToRender(
mainWindow: BrowserWindow | undefined | null,
code: string,
type: number,
data?: string
) {
if(!mainWindow) {
return
}
const senddata = {
state: type,
msg: data || "",
};
mainWindow.webContents.send(code, senddata);
}
const fileName = 'electron-vite-demo Setup 1.0.0.exe'
// 获取下载路径
export function getDownloadPath() {
// const version: string = packageInfo.version //* 获取版本
// const Sysarch: string = arch().includes('64') ? 'win64' : 'win32' //* 获取window版本
// const isWin: boolean = platform().includes('win32')
return 'http://127.0.0.1:5500/' + fileName
}
export function getHistoryPath() {
return path.join(app.getPath('downloads'), fileName)
}
src/main/services/downloadFile.ts 下载工具类
通过外部传过来的url和历史路径进行下载操作
import { app, BrowserWindow, dialog } from "electron";
import { stat, remove } from "fs-extra";
import { join } from "path";
//* 下载文件 用于下载更新功能 如果有两处需要用到该文件 需要把提示逻辑等给抽出来
class DownloadFile {
public downloadUrl: string; //* 下载的路径
public historyFilePath: string; //* 历史路径 用于判断该文件是否存在,是否需要删除
public mainWindow: BrowserWindow|null; //* 使用mainWindow自带方法进行下载
constructor(
mainWindow: BrowserWindow,
downloadUrl: string,
historyFilePath: string
) {
this.mainWindow = mainWindow;
this.downloadUrl = downloadUrl;
this.historyFilePath = historyFilePath;
}
start() {
//* 开始下载
this.startDownload();
//* 监听事件 设置下载路径
this.onEvent()
}
startDownload() {
//* 开始下载
//* 判断是否存在同名文件 存在则删除 如果没有就开始下载
stat(this.historyFilePath, async (err, stats) => {
try {
if (stats) {
await remove(this.historyFilePath);
}
console.log('开始下载')
this.mainWindow.webContents.downloadURL(this.downloadUrl);
} catch (error) {
console.log(error);
}
});
}
onEvent() {
//* 监听下载过程中的事件
this.mainWindow.webContents.session.on(
"will-download",
(event: any, item: any, webContents: any) => {
const filePath = join(app.getPath("downloads"), item.getFilename());
item.setSavePath(filePath);
item.on("updated", (event: any, state: string) => {
console.log(state, '文件下载中途')
switch (state) {
case "progressing":
this.mainWindow.webContents.send(
"download-progress",
(
(item.getReceivedBytes() / item.getTotalBytes()) *
100
).toFixed(0)
);
break;
default:
this.mainWindow.webContents.send("download-error", true);
dialog.showErrorBox(
"下载出错",
"由于网络或其他未知原因导致下载出错"
);
break;
}
});
item.once("done", (event: any, state: string) => {
console.log('文件下载完成', state)
switch (state) {
case "completed":
const data = {
filePath,
};
this.mainWindow.webContents.send("download-done", data);
break;
case "interrupted":
this.mainWindow.webContents.send("download-error", true);
dialog.showErrorBox(
"下载出错",
"由于网络或其他未知原因导致下载出错."
);
break;
default:
break;
}
});
}
);
}
}
export default DownloadFile
renderer:和main层进行通信通知更新,更新完成后使用shell打开下载的exe文件进行安装
src/renderer/pages/index/index.vue
<template>
<h1>更新界面</h1>
文件地址{{ filePath }}
<div class="actions">
<el-button @click="checkUpdate">
检查更新,下载更新
</el-button>
</div>
<el-dialog
v-model="dialogVisible"
title="进度"
center
width="14%"
top="45vh"
>
<div class="conten">
<el-progress
type="dashboard"
:percentage="percentage"
:color="colors"
:status="progressStaus"
/>
</div>
</el-dialog>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { send, on } from '@renderer/utils/ipcRenderer'
//* element-plus 工具类封装
import { ElMessageBox } from "element-plus";
const { shell } = require("electron");
let percentage = ref(0);
let colors = ref([
{ color: "#f56c6c", percentage: 20 },
{ color: "#e6a23c", percentage: 40 },
{ color: "#6f7ad3", percentage: 60 },
{ color: "#1989fa", percentage: 80 },
{ color: "#5cb87a", percentage: 100 },
]);
let filePath = ref('')
let dialogVisible = ref(false);
let progressStaus = ref('');
function checkUpdate() {
//* 检查更新方法 发送通信
send('start-download') //通信到main层
dialogVisible.value = true
}
on("download-progress", (event, arg) => {//* 进度条
percentage.value = Number(arg);
});
on("download-error", (event, arg) => {
if (arg) {
progressStaus.value = "exception";
percentage.value = 40;
colors.value = "#d81e06";
}
});
on("download-paused", (event, arg) => {
if (arg) {
progressStaus.value = "warning";
ElMessageBox.alert("下载由于未知原因被中断!", "提示", {
confirmButtonText: "重试",
callback: (action) => {
checkUpdate();
},
});
}
});
on("download-done", (event, age) => {
filePath.value = age.filePath;
progressStaus.value = "success";
ElMessageBox.alert("更新下载完成!", "提示", {
confirmButtonText: "确定",
callback: (action) => {
shell.openPath(filePath.value);
},
});
});
</script>