当前Node版本:18.12.0,npm版本:8.19.2
1.搭建脚手架项目
搭建Vue3+Vite+Ts脚手架-CSDN博客
可删掉index.html文件的title标签
2.配置package.json
{
"name": "my-vite-project",
"private": true,
"version": "1.0.0",
"main": "dist-electron/main.js",
"scripts": {
"dev": "vite",
"build": "vite build && electron-builder",
"preview": "vite preview"
},
"dependencies": {
"electron-updater": "^6.3.9",
"element-plus": "^2.8.7",
"is-electron": "^2.2.2",
"vue": "^3.5.12"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.1.4",
"electron-builder": "^24.6.4",
"electron-log": "^5.2.0",
"sass-embedded": "^1.80.6",
"typescript": "~5.6.2",
"vite": "^5.4.10",
"vite-plugin-electron": "^0.28.8",
"vite-plugin-electron-renderer": "^0.14.6",
"vue-tsc": "^2.1.8"
},
"build": {
"appId": "com.electron.desktop",
"productName": "qjyiot",
"asar": true,
"copyright": "Copyright © 2022 electron",
"directories": {
"output": "release/${version}"
},
"files": [
"dist",
"dist-electron"
],
"mac": {
"artifactName": "${productName}_${version}.${ext}",
"target": [
"dmg"
]
},
"win": {
"target": [
{
"target": "nsis",
"arch": [
"x64"
]
}
],
"artifactName": "${productName}_${version}.${ext}",
"icon": "electron/icon/logo.ico"
},
"nsis": {
"oneClick": false,
"perMachine": false,
"allowToChangeInstallationDirectory": true,
"deleteAppDataOnUninstall": false
},
"publish": [
{
"provider": "generic",
"url": "你服务器存放的桌面应用地址"
}
],
"releaseInfo": {
"releaseNotes": "版本更新的具体内容"
}
}
}
安装依赖包:
npm i electron@26.1.0 --save-dev
3.配置vite.config.mts
vite.config.mts文件名改成 vite.config.mts,解决运行项目的警告问题:The CJS build of Vite's Node API is deprecated.
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import electron from "vite-plugin-electron";
import renderer from "vite-plugin-electron-renderer";
// https://vite.dev/config/
export default defineConfig({
plugins: [vue(), electron([{ entry: "electron/main.ts" }]), renderer()],
});
4.配置打包桌面应用的工具文件
在根目录下创建electron目录,
logo图标格式:ico后缀名、分辨率256 * 256、10KB左右
main.ts
import { BrowserWindow, app, ipcMain } from "electron";
import path from "path";
let win: BrowserWindow | null;
import updater from "./updater";
import pkg from "../package.json";
const createWindow = () => {
win = new BrowserWindow({
width: 1250,
height: 700,
minWidth: 1250,
minHeight: 700,
title: pkg.build.productName,
icon: path.join(__dirname, "..", pkg.build.win.icon),
webPreferences: {
webviewTag: true,
nodeIntegration: true,
contextIsolation: false,
},
});
if (win) {
// win.setMenu(null); // 隐藏左上角菜单
}
if (process.env.NODE_ENV === "development") {
process.env.VITE_DEV_SERVER_URL &&
win.loadURL(process.env.VITE_DEV_SERVER_URL); // 使用vite开发服务的url路径访问应用
} else {
win.loadFile(path.join(__dirname, "..", "dist/index.html"));
}
updater(win);
};
// 定义关闭事件
ipcMain.handle("quit", () => {
app.quit();
});
// 打开开发者工具
ipcMain.handle("openDevTools", () => {
win && win.webContents.openDevTools();
});
// electron阻止应用多开
const additionalData = { myKey: "myValue" };
const gotTheLock = app.requestSingleInstanceLock(additionalData);
if (!gotTheLock) {
app.quit();
} else {
app.on(
"second-instance",
(event, commandLine, workingDirectory, additionalData) => {
//输入从第二个实例中接收到的数据
//有人试图运行第二个实例,我们应该关注我们的窗口
if (win) {
if (win.isMinimized()) win.restore();
win.focus();
}
}
);
app.whenReady().then(createWindow);
}
updater.ts
import { autoUpdater } from "electron-updater";
import { BrowserWindow, app, ipcMain, dialog } from "electron";
import { getLocalData, setLocalData, sleep } from "./helper";
import logger from "electron-log";
import pkg from "../package.json";
export default function updater(mainWin: BrowserWindow | null) {
autoUpdater.autoDownload = false; // 是否自动更新
autoUpdater.autoInstallOnAppQuit = false; // APP退出的时候自动安装
// autoUpdater.allowDowngrade = true // 是否可以回退的属性
/*
* 在开启更新监听事件之前设置
* 一定要保证该地址下面包含lasted.yml文件和需要更新的exe文件
*/
// 发送消息给渲染线程
function sendStatusToWindow(status?: any, params?: any) {
mainWin && mainWin.webContents.send(status, params);
}
// 检查更新
autoUpdater.on("checking-for-update", () => {
sendStatusToWindow("checking-for-update");
});
// 可以更新版本
autoUpdater.on("update-available", (info: any) => {
// sendStatusToWindow("autoUpdater-canUpdate", info);
const { version } = info;
askUpdate(version);
});
// 更新错误
autoUpdater.on("error", (err: any) => {
sendStatusToWindow("autoUpdater-error", err);
});
// 发起更新程序
ipcMain.on("autoUpdater-toDownload", () => {
autoUpdater.downloadUpdate();
});
// 正在下载的下载进度
autoUpdater.on("download-progress", (progressObj: any) => {
sendStatusToWindow("autoUpdater-progress", progressObj);
});
// 下载完成
autoUpdater.on("update-downloaded", (res) => {
sendStatusToWindow("autoUpdater-downloaded");
});
// 没有可用的更新,也就是当前是最新版本
autoUpdater.on("update-not-available", function (info: any) {
sendStatusToWindow("autoUpdater-available", info);
});
// 退出程序
ipcMain.on("exit-app", () => {
autoUpdater.quitAndInstall();
});
// 重新检查是否有新版本更新
ipcMain.on("monitor-update-system", () => {
autoUpdater.checkForUpdates();
});
// 检测是否有更新
setTimeout(() => {
autoUpdater.checkForUpdates();
}, 2000);
}
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: ["关闭", "跳过这个版本", "安装更新"],
buttons: ["关闭", "安装更新"],
title: "软件更新提醒",
message: `${
pkg.build.productName
} 最新版本是 ${version},您现在的版本是 ${app.getVersion()},现在要下载更新吗?`,
defaultId: 1,
// checkboxLabel: "以后自动下载并安装更新",
// checkboxChecked: false,
textWidth: 300,
});
if (response == 1) {
let updaterData = {
version: version,
skip: false,
// auto: checkboxChecked,
};
setLocalData({
updater: {
...updaterData,
},
});
autoUpdater.downloadUpdate();
logger.info(["更新操作", JSON.stringify(updaterData)]);
} else {
logger.info(["更新操作", "关闭更新提醒"]);
}
}
}
helper.ts
import { join } from 'path'
import fs from 'fs'
import { app } from 'electron'
const dataPath = join(app.getPath('userData'), 'data.json')
export function getLocalData(key?:any) {
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?:any, value?:any) {
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)
})
}
5.创建热更新组件
src/components/Updater.vue
<template>
<div class="updater">
<el-dialog title="更新中......" v-model="showUpdater" :close-on-click-modal="false" :close-on-press-escape="true"
:show-close="false" width="40%" top="26vh" center>
<template v-if="downloadProcess">
<p>当前:【{{ downloadProcess.transferred }}】 / 共【{{ downloadProcess.total }}】</p>
<el-progress :text-inside="true" :stroke-width="18" :percentage="downloadProcess.percent"></el-progress>
<p>正在下载({{ downloadProcess.speed }})......</p>
</template>
</el-dialog>
</div>
</template>
<script lang='ts'>
import { defineComponent, reactive, toRefs, onMounted, onUnmounted, getCurrentInstance } from "vue";
import { ipcRenderer } from "electron";
import { ElMessage, ElMessageBox, ElDialog, ElProgress } from 'element-plus';
import 'element-plus/dist/index.css';
export default defineComponent({
name: "layoutUpdater",
components: {
[ElDialog.name]: ElDialog,
[ElProgress.name]: ElProgress,
},
setup(props: any, { emit }: { emit: any }) {
onMounted(() => {
window.addEventListener('keydown', handleKeyDown)
// ipcRenderer.send("monitor-update-system"); // 检查是否有新版本更新
})
onUnmounted(() => {
window.removeEventListener('keydown', handleKeyDown)
})
const { proxy }: any = getCurrentInstance()
const data = reactive({
showUpdater: false,
downloadProcess: {
percent: 10,
speed: 0,
transferred: '1kb',
total: "2M"
},
});
const handleKeyDown = () => {
document.onkeydown = (e) => {
// 点击键盘F12键打开控制台
if (e.key === 'F12') {
ipcRenderer.invoke("openDevTools");
}
}
}
// 最新版本
ipcRenderer.on("autoUpdater-available", (event, info) => {
// ElMessage({
// type: "success",
// message: `【v${info.version}】当前是最新版本啦`,
// })
});
// 发现新版本 once
ipcRenderer.on("autoUpdater-canUpdate", (event, info) => {
/*
* 这儿会监听,如果info.version比现在版本小;就会触发;反之,不会触发
*/
ElMessageBox.confirm("发现有新版本【v{0}】,是否更新?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
closeOnClickModal: false,
type: "warning",
}).then(() => {
ipcRenderer.send("autoUpdater-toDownload");
});
});
// 下载进度
ipcRenderer.on("autoUpdater-progress", (event, process) => {
if (process.transferred >= 1024 * 1024) {
process.transferred =
(process.transferred / 1024 / 1024).toFixed(2) + "M";
} else {
process.transferred = (process.transferred / 1024).toFixed(2) + "K";
}
if (process.total >= 1024 * 1024) {
process.total = (process.total / 1024 / 1024).toFixed(2) + "M";
} else {
process.total = (process.total / 1024).toFixed(2) + "K";
}
if (process.bytesPerSecond >= 1024 * 1024) {
process.speed =
(process.bytesPerSecond / 1024 / 1024).toFixed(2) + "M/s";
} else if (process.bytesPerSecond >= 1024) {
process.speed = (process.bytesPerSecond / 1024).toFixed(2) + "K/s";
} else {
process.speed = process.bytesPerSecond + "B/s";
}
process.percent = process.percent.toFixed(2);
data.downloadProcess = process;
data.showUpdater = true;
});
// 下载更新失败
ipcRenderer.once("autoUpdater-error", () => {
ElMessage.error("更新失败");
data.showUpdater = false;
});
// 下载完成
ipcRenderer.once("autoUpdater-downloaded", () => {
data.showUpdater = false;
ElMessageBox.confirm("更新完成,是否关闭应用程序安装新版本?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
closeOnClickModal: false,
type: "warning",
}).then(() => {
ipcRenderer.send("exit-app");
});
});
return {
...toRefs(data),
};
},
});
</script>
<style scoped lang='scss'>
.updater {
:deep(.el-dialog__header) {
font-weight: 700;
.el-dialog__title {}
}
}
</style>
6. App.vue文件全局引入组件
<template>
<div style="font-size: 30px;">Vue3+Vite打包卓面应用调试</div>
<Updater v-if="isElectron()"></Updater>
</template>
<script setup lang="ts">
import { defineAsyncComponent, } from 'vue';
import isElectron from "is-electron";
const Updater = defineAsyncComponent(() => import('./components/Updater.vue'));
</script>
7.配置tsconfig.json
import导入文件或依赖包,解决找不到模块的提示问题
{
"compilerOptions": {
"allowSyntheticDefaultImports": true ,
},
"include": ["src/**/*.ts", "src/**/*.vue", "src/**/*.tsx", "src/**/*.d.ts"], // **Represents any directory, and * represents any file. Indicates that all files in the src directory will be compiled
"exclude": ["node_modules", "dist"] // Indicates the file directory that does not need to be compiled
}
8.开始打包
npm run build
9.安装exe应用程序
10
10.安装更新
当package.json文件的version属性版本号与服务器地址的版本号不匹配时,自动会弹出“软件更新提醒”提示框