react+hook+vite项目使用eletron打包成桌面应用+可以热更新
使用Hooks-Admin的架构
Hooks-Admin: 🚀🚀🚀 Hooks Admin,基于 React18、React-Router V6、React-Hooks、Redux、TypeScript、Vite2、Ant-Design 开源的一套后台管理框架。https://gitee.com/HalseySpicy/Hooks-Adminexe桌面应用效果图
一.安装依赖包
npm i electron-updater element-plus is-electron
npm i electron@26.1.0 electron-builder electron-log vite-plugin-electron vite-plugin-electron-renderer --save-dev
安装后的package.json文件
二.配置package.json
1.添加 "main": "dist-electron/main.js",
2.在scripts下的build:test属性 先删除--mode test 然后添加 && electron-builder
3.在底部添加build打包配置
"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": "exe应用服务器地址"}],"releaseInfo": {"releaseNotes": "版本更新的具体内容"}}
三.配置vite.config.ts
给plugins属性添加两个插件,屏蔽eslint插件。
import electron from "vite-plugin-electron";
import renderer from "vite-plugin-electron-renderer";electron([{ entry: "electron/main.ts" }]),
renderer()
四.配置electron工具
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)})
}
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: ["关闭", "跳过这个版本", "安装更新"],title: "软件更新提醒",message: `${pkg.build.productName} 最新版本是 ${version},您现在的版本是 ${app.getVersion()},现在要下载更新吗?`,defaultId: 2,// checkboxLabel: "以后自动下载并安装更新",// checkboxChecked: false,textWidth: 300,});if ([1, 2].includes(response)) {let updaterData = {version: version,skip: false,// auto: checkboxChecked,};setLocalData({updater: {...updaterData,},});if (response === 2) autoUpdater.downloadUpdate();logger.info(["更新操作", JSON.stringify(updaterData)]);} else {logger.info(["更新操作", "关闭更新提醒"]);}}
}
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();}});// if (process.env.NODE_ENV !== "development") { app.whenReady().then(createWindow);// }
}
五.定义更新应用版本组件
在src目录下定义layouts\Updater\index.tsx
import { useState, useEffect } from "react";
import { Modal, message, Progress } from "antd";const Updater = (props: any) => {const { ipcRenderer } = window.require("electron");const [showUpdater, setShowUpdater] = useState(false);const [downloadProcess, setDownloadProcess] = useState({percent: 10,speed: 0,transferred: "1kb",total: "2M",});const handleKeyDown = () => {document.onkeydown = (e) => {// 点击键盘F12键打开控制台if (e.key === "F12") {ipcRenderer.invoke("openDevTools");}};};const setIpcRenderer = () => {// 最新版本ipcRenderer.on("autoUpdater-available", (event: any, info: any) => {message.success(`【v${info.version}】当前是最新版本啦`);});// 发现新版本 onceipcRenderer.on("autoUpdater-canUpdate", (event: any, info: any) => {/** 这儿会监听,如果info.version比现在版本小;就会触发;反之,不会触发*/Modal.confirm({title: "提示",content: `发现有新版本【v${info.version}】,是否更新?`,maskClosable: false,onOk() {ipcRenderer.send("autoUpdater-toDownload");},onCancel() {},});});// 下载进度ipcRenderer.on("autoUpdater-progress", (event: any, process: any) => {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);setDownloadProcess({ ...downloadProcess, ...process });setShowUpdater(true);});// 下载更新失败ipcRenderer.once("autoUpdater-error", () => {setShowUpdater(false);message.error("更新失败");});// 下载完成ipcRenderer.once("autoUpdater-downloaded", () => {setShowUpdater(false);Modal.confirm({title: "提示",content: "更新完成,是否关闭应用程序安装新版本?",maskClosable: false,onOk() {ipcRenderer.send("exit-app");},onCancel() {},});});};useEffect(() => {window.addEventListener("keydown", handleKeyDown);setIpcRenderer();return () => {window.removeEventListener("keydown", handleKeyDown);};}, []);return (<><Modaltitle="更新中......"visible={showUpdater}footer={[]}maskClosable={false}closable={false}><p>当前:【{downloadProcess.transferred}】 / 共【{downloadProcess.total}】</p><Progress percent={downloadProcess.percent} strokeWidth={18} /><p>正在下载({downloadProcess.speed})......</p></Modal></>);
};export default Updater;
五.App.vue根组件引用
六.打包
npm run build:test
生成exe应用,点击可安装
安装完后,打开exe应用当本地版本和服务器地址的版本不一致自动会弹出更新提示
相关文章:

react+hook+vite项目使用eletron打包成桌面应用+可以热更新
使用Hooks-Admin的架构 Hooks-Admin: 🚀🚀🚀 Hooks Admin,基于 React18、React-Router V6、React-Hooks、Redux、TypeScript、Vite2、Ant-Design 开源的一套后台管理框架。https://gitee.com/HalseySpicy/Hooks-Adminexe桌面应用…...
STM32 ADC --- DMA乒乓缓存
STM32 ADC — DMA乒乓缓存 文章目录 STM32 ADC --- DMA乒乓缓存软件切换实现乒乓利用DMA双缓冲实现乒乓 通过cubeMX配置生成HAL工程这里使用的是上篇文章(STM32 ADC — DMA采样)中生成的工程配置 软件切换实现乒乓 cubeMX默认生成的工程中是打开DMA中断…...

SpringCloud基础 入门级 学习SpringCloud 超详细(简单通俗易懂)
Spring Cloud 基础入门级学习 超详细(简单通俗易懂) 一、SpringCloud核心组件第一代:SpringCloud Netflix组件第二代:SpringCloud Alibaba组件SpringCloud原生组件 二、SpringCloud体系架构图三、理解分布式与集群分布式集群 四、…...

【Windows 常用工具系列 20 -- MobaXterm 登录 WSL】
文章目录 MobaXterm 登录 WSL MobaXterm 登录 WSL 在 WSL 启动之后,打开 MobaXterm: 在 Distribution 中选择自己本地安装的 ubuntu 版本,我这里使用的是ubuntu-20.4,然后在 runmethod 中选择 Localhost connection. 连接成功之…...

【vmware+ubuntu16.04】ROS学习_博物馆仿真克隆ROS-Academy-for-Beginners软件包处理依赖报错问题
首先安装git 进入终端,输入sudo apt-get install git 安装后,创建一个工作空间名为tutorial_ws, 输入 mkdir tutorial_ws#创建工作空间 cd tutorial_ws#进入 mkdir src cd src git clone https://github.com/DroidAITech/ROS-Academy-for-Be…...

UniApp的Vue3版本中H5配置代理的最佳方法
UniApp的Vue3版本中H5项目在本地开发时需要配置跨域请求调试 最开始在 manifest.json中配置 总是报404,无法通过代理请求远程的接口并返回404错误。 经过验证在项目根目录创建 vite.config.js文件 vite.config.js内容: // vite.config.js import {defineConfig }…...

深入了解Pod
Pod是Kubernetes中最小的单元,它由一组、一个或多个容器组成,每个Pod还包含了一个Pause容器,Pause容器是Pod的父容器,主要负责僵尸进程的回收管理,通过Pause容器可以使同一个Pod里面的多个容器共享存储、网络、PID、IPC等。 1、Pod 是由一组紧耦合的容器组成的容器组,当然…...

基于Spider异步爬虫框架+JS动态参数逆向+隧道代理+自定义中间件的猎聘招聘数据爬取
在本篇博客中,我们将介绍如何使用 Scrapy 框架结合 JS 逆向技术、代理服务器和自定义中间件,来爬取猎聘网站的招聘数据。猎聘是一个国内知名的招聘平台,提供了大量的企业招聘信息和职位信息。本项目的目标是抓取指定城市的招聘信息࿰…...
Spring 中的 BeanDefinitionParserDelegate 和 NamespaceHandler
一、BeanDefinitionParserDelegate Spring在解析xml文件的时候,在遇到<bean>标签的时候,我们会使用BeanDefinitionParserDelegate对象类解析<bean>标签的内容,包括<bean>标签的多个属性,例如 id name class in…...
BERT模型核心组件详解及其实现
摘要 BERT(Bidirectional Encoder Representations from Transformers)是一种基于Transformer架构的预训练模型,在自然语言处理领域取得了显著的成果。本文详细介绍了BERT模型中的几个关键组件及其实现,包括激活函数、变量初始化…...

图论-代码随想录刷题记录[JAVA]
文章目录 前言深度优先搜索理论基础所有可达路径岛屿数量岛屿最大面积孤岛的总面积沉默孤岛Floyd 算法dijkstra(朴素版)最小生成树之primkruskal算法 前言 新手小白记录第一次刷代码随想录 1.自用 抽取精简的解题思路 方便复盘 2.代码尽量多加注释 3.记录…...

c#加载shellcode
本地加载bin文件 SharpPELoader项目如下: using System; using System.IO; using System.Runtime.InteropServices;namespace TestShellCode {internal class Program{private const uint MEM_COMMIT 0x1000;private const uint PAGE_EXECUTE_READWRITE 0x40;pr…...

HarmonyOS 开发环境搭建
HarmonyOS(鸿蒙操作系统)作为一种面向全场景多设备的智能操作系统,正逐渐在市场上崭露头角。为了进入HarmonyOS生态,开发者需要搭建一个高效的开发环境。本文将详细介绍如何搭建HarmonyOS开发环境,特别是如何安装和配置…...
【网络云计算】2024第46周周考-磁盘管理的基础知识-RAID篇
文章目录 1、画出各个RAID的结构图,6句话说明优点和缺点,以及磁盘可用率和坏盘数量,磁盘总的数量2、写出TCP五层模型以及对应的常用协议 【网络云计算】2024第46周周考-磁盘管理的基础知识-RAID篇 1、画出各个RAID的结构图,6句话说…...
深入理解 SQL_MODE 之 ANSI_QUOTES
引言 在 MySQL 数据库中,sql_mode 是一个重要的配置参数,它定义了 MySQL 应该遵循的 SQL 语法标准以及数据验证规则。其中,ANSI_QUOTES 是 sql_mode 中的一个重要选项,它改变了 MySQL 对于字符串和标识符的识别方式,使…...
容器技术在持续集成与持续交付中的应用
💓 博客主页:瑕疵的CSDN主页 📝 Gitee主页:瑕疵的gitee主页 ⏩ 文章专栏:《热点资讯》 容器技术在持续集成与持续交付中的应用 容器技术在持续集成与持续交付中的应用 容器技术在持续集成与持续交付中的应用 引言 容器…...

【嵌入式软件-STM32】OLED显示屏+调试方法
目录 一、调试方式 1)串口调试 优势 弊端 2)显示屏调试 优势 弊端 3)Keil调试模式 4)点灯调试法 5)注释调试法 6)对照法 二、OLED简介 OLED组件 OLED显示屏 0.96寸OLED模块 OLED外观和种类…...

kubernetes简单入门实战
本章将介绍如何在kubernetes集群中部署一个nginx服务,并且能够对其访问 Namespace Namespace是k8s系统中一个非常重要的资源,它的主要作用是用来实现多套环境的资源隔离或者多租户的资源隔离。 默认情况下,k8s集群中的所有的Pod都是可以相…...
Python连接Mysql、Postgre、ClickHouse、Redis常用库及封装方法
博主在这里分享一些常见的python连接数据库或中间件的库和封装方案,希望对大家有用。 Mysql封装 #!/usr/bin/python # -*- coding: utf-8 -*- import sys import pymysql from settings import MYSQL_DB, MYSQL_PORT, MYSQL_USER, MYSQL_PASSWORD, MYSQL_HOST, EN…...

如何修改npm包
前言 开发中遇到一个问题,配置 Element Plus 自定义主题时,添加了 ElementPlusResolver({ importStyle: "sass" }) 后,控制台出现报错,这是因为 Dart Sass 2.0 不再支持使用 !global 来声明新变量,虽然当前…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序
一、开发环境准备 工具安装: 下载安装DevEco Studio 4.0(支持HarmonyOS 5)配置HarmonyOS SDK 5.0确保Node.js版本≥14 项目初始化: ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...
MySQL中【正则表达式】用法
MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现(两者等价),用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例: 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...

优选算法第十二讲:队列 + 宽搜 优先级队列
优选算法第十二讲:队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...

10-Oracle 23 ai Vector Search 概述和参数
一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...

使用LangGraph和LangSmith构建多智能体人工智能系统
现在,通过组合几个较小的子智能体来创建一个强大的人工智能智能体正成为一种趋势。但这也带来了一些挑战,比如减少幻觉、管理对话流程、在测试期间留意智能体的工作方式、允许人工介入以及评估其性能。你需要进行大量的反复试验。 在这篇博客〔原作者&a…...