vue3+vite搭建脚手架项目使用eletron打包成桌面应用+可以热更新

当前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(() => {resolveclearTimeout(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}】当前是最新版本啦`,// })});// 发现新版本 onceipcRenderer.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属性版本号与服务器地址的版本号不匹配时,自动会弹出“软件更新提醒”提示框

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/465809.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Golang】validator库的使用

package mainimport ("fmt""github.com/go-playground/validator" )// MyStruct .. validate:"is-awesome"是一个结构体标签&#xff0c;它告诉验证器使用名为is-awesome的验证规则来验证String字段。 type MyStruct struct {String string vali…

Linux(CentOS)安装 MySQL

CentOS版本&#xff1a;CentOS 7 MySQL版本&#xff1a;MySQL Community Server 8.4.3 LTS 1、下载 MySQL 打开MySQL官网&#xff1a;https://www.mysql.com/ 直接下载网址&#xff1a;https://dev.mysql.com/downloads/mysql/ 其他版本 2、上传 MySQL 文件到 CentOS 使用F…

Pytorch实现transformer语言模型

转载自&#xff1a;| 03_language_model/02_Transformer语言模型.ipynb | 从头训练Transformer语言模型 |Open In Colab | Transformer语言模型 本节训练一个 sequence-to-sequence 模型&#xff0c;使用pytorch的 nn.Transformer <https://pytorch.org/docs/master/nn.ht…

<Project-20 YT-DLP> 给视频网站下载工具 yt-dlp/yt-dlp 加个页面 python web

介绍 yt-dlp Github 项目&#xff1a;https://github.com/yt-dlp/yt-dlp A feature-rich command-line audio/video downloader 一个功能丰富的视频与音频命令行下载器 原因与功能 之前我用的 cobalt 因为它不再提供Client Web功能&#xff0c;只能去它的官网使用。 翻 redd…

Sqli-Labs

目录 解题思路 题目设计原理 总结 解题思路 什么&#xff1f;sqli-labs&#xff1f;让我看看。还真是。想起了当初刚学被支配的恐惧。 悄咪咪点开第一关看看能不能秒了。测试闭合老样子&#xff0c;单引号闭合&#xff0c;双引号等都成功。这里 and 11 和 # 都不能通过检测&…

【基于Zynq FPGA对雷龙SD NAND的测试】

一、SD NAND 特征 1.1 SD 卡简介 雷龙的 SD NAND 有很多型号&#xff0c;在测试中使用的是 CSNP4GCR01-AMW 与 CSNP32GCR01-AOW。芯片是基于 NAND FLASH 和 SD 控制器实现的 SD 卡。具有强大的坏块管理和纠错功能&#xff0c;并且在意外掉电的情况下同样能保证数据的安全。 …

【NOIP提高组】引水入城

【NOIP提高组】引水入城 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 在一个遥远的国度&#xff0c;一侧是风景秀美的湖泊&#xff0c;另一侧则是漫无边际的沙漠。该国的行政 区划十分特殊&#xff0c;刚好构成一个N行M列的矩形&#xff…

鸿蒙开发:arkts 如何读取json数据

为了支持ArkTS语言的开发&#xff0c;华为提供了完善的工具链&#xff0c;包括代码编辑器、编译器、调试器、测试工具等。开发者可以使用这些工具进行ArkTS应用的开发、调试和测试。同时&#xff0c;华为还提供了DevEco Studio这一一站式的开发平台&#xff0c;为运行在Harmony…

OpenCV视觉分析之目标跟踪(11)计算两个图像之间的最佳变换矩阵函数findTransformECC的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 根据 ECC 标准 78找到两幅图像之间的几何变换&#xff08;warp&#xff09;。 该函数根据 ECC 标准 ([78]) 估计最优变换&#xff08;warpMatri…

【2024最新版Kotlin教程】Kotlin第一行代码系列第五课-类继承,抽象类,接口

【2024最新版Kotlin教程】Kotlin第一行代码系列第五课-类继承&#xff0c;抽象类&#xff0c;接口 为什么要有继承呢&#xff0c;现实中也是有继承的&#xff0c;对吧&#xff0c;你继承你爸的遗产&#xff0c;比如你爸建好了一个房子&#xff0c;儿子继承爸&#xff0c;就得了…

iOS用rime且导入自制输入方案

iPhone 16 的 cantonese 只能打传统汉字&#xff0c;没有繁简转换&#xff0c;m d sh d。考虑用「仓」输入法 [1] 使用 Rime 打字&#xff0c;且希望导入自制方案 [2]。 仓输入法有几种导入方案的方法&#xff0c;见 [3]&#xff0c;此处记录 wifi 上传法。准备工作&#xff1…

基于Zynq FPGA的雷龙SD NAND存储芯片性能测试

文章目录 前言一、SD NAND特征1.1 SD卡简介1.2 SD卡Block图 二、SD卡样片三、Zynq测试平台搭建3.1 测试流程3.2 SOC搭建 四、软件搭建五、测试结果六、总结 前言 随着嵌入式系统和物联网设备的快速发展&#xff0c;高效可靠的存储解决方案变得越来越重要。雷龙发展推出的SD NA…

【动态规划 数学】2745. 构造最长的新字符串|1607

本文涉及知识点 C动态规划 数学 LeetCode2745. 构造最长的新字符串 给你三个整数 x &#xff0c;y 和 z 。 这三个整数表示你有 x 个 “AA” 字符串&#xff0c;y 个 “BB” 字符串&#xff0c;和 z 个 “AB” 字符串。你需要选择这些字符串中的部分字符串&#xff08;可以全…

【Linux驱动开发】timer库下的jiffies时间戳和延时驱动编写

【Linux驱动开发】timer库下的jiffies时间戳和延时驱动编写 gitee地址&#xff1a; https://gitee.com/Mike_Zhou_Admin/Linux_Driver_Timestamp_Driver/更新以gitee为准 文章目录 timer库时间戳函数延时函数驱动代码应用测试附录&#xff1a;嵌入式Linux驱动开发基本步骤开发…

了解云计算工作负载保护的重要性及必要性

云计算de小白 云计算技术的快速发展使数据和应用程序安全成为一种关键需求&#xff0c;而不仅仅是一种偏好。随着越来越多的客户公司将业务迁移到云端&#xff0c;保护他们的云工作负载&#xff08;指所有部署的应用程序和服务&#xff09;变得越来越重要。云工作负载保护&…

C语言 循环高级

时间&#xff1a;2024.11.6 一、学习内容 1、无限循环 无限循环&#xff1a;循环永远停不下来 注意点&#xff1a;无限循环因为永远停不下来&#xff0c;所以下面不能再写其他的代码了 2、break 跳转控制语句&#xff1a; 在循环的过程中&#xff0c;跳到其他语句上执行 #…

易语言模拟真人动态生成鼠标滑动路径

一.简介 鼠标轨迹算法是一种模拟人类鼠标操作的程序&#xff0c;它能够模拟出自然而真实的鼠标移动路径。 鼠标轨迹算法的底层实现采用C/C语言&#xff0c;原因在于C/C提供了高性能的执行能力和直接访问操作系统底层资源的能力。 鼠标轨迹算法具有以下优势&#xff1a; 模拟…

CSS学习之Grid网格布局基本概念、容器属性

网格布局 网格布局&#xff08;Grid&#xff09;是将网页划分成一个个网格单元&#xff0c;可任意组合不同的网格&#xff0c;轻松实现各种布局效果&#xff0c;也是目前CSS中最强大布局方案&#xff0c;比Flex更强大。 基本概念 容器和项目 当一个 HTML 元素将 display 属性…

聊一聊Elasticsearch的索引的分片分配机制

1、什么是分片分配 分片分配是由ES主节点将索引分片移动到ES集群中各个节点上的过程。 该过程尽量保证&#xff0c;同一个索引的分片尽量分配到更多的节点上&#xff0c;以此来达到读写索引的时候可以利用更多硬件资源的效果。 在分配过程当中&#xff0c;也不能将某个主分片…

springboot的增删改查商城小实践(b to c)

首先准备一张表&#xff0c;根据业务去设计表 订单编号是参与业务的&#xff0c;他那订单编号里面是有特殊意义的&#xff0c;比如说像什么一些年月日什么的&#xff0c;一些用户的ID都在那编号里面呢&#xff1f;不能拿这种东西当主件啊 根据数据量去决定数据类型 价格需要注意…