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 来声明新变量,虽然当前…...

Django 2024全栈开发指南(三):数据库模型与ORM操作(上篇)
目录 一、模型的定义二、数据迁移三、数据表关系四、数据表操作4.1 Shell工具4.2 数据新增4.3 数据修改4.4 数据删除4.5 数据查询 Django 对各种数据库提供了很好的支持,包括 PostgreSQL、MySQL、SQLite 和 Oracle,而且为这些数据库提供了统一的 API 方法…...

低代码可视化-uniapp开关选择组件-低码生成器
开关(Switch)选择组件是一种用户界面元素,允许用户在两种状态(通常是开/关、是/否、启用/禁用等)之间进行切换。这种组件在移动应用、桌面软件、网页以及物联网设备中广泛应用。以下是对开关Switch选择组件的详细介绍&…...

【arxiv‘24】Vision-Language Navigation with Continual Learning
论文信息 题目:Vision-Language Navigation with Continual Learning 视觉-语言导航与持续学习 作者:Zhiyuan Li, Yanfeng Lv, Ziqin Tu, Di Shang, Hong Qiao 论文创新点 VLNCL范式:这是一个新颖的框架,它使得智能体能够在适…...

如何在 Ubuntu 上安装 Jupyter Notebook
本篇文章将教你在 Ubuntu 服务器上安装 Jupyter Notebook,并使用 Nginx 和 SSL 证书进行安全配置。 我将带你一步步在云服务器上搭建 Jupyter Notebook 服务器。Jupyter Notebook 在数据科学和机器学习领域被广泛用于交互式编码、可视化和实验。在远程服务器上运行…...

免费申请 Let‘s Encrypt SSL 证书
免费申请 Lets Encrypt SSL 证书 在网络安全日益重要的今天,为网站启用 SSL 证书是保障数据安全和用户信任的关键。Lets Encrypt 提供的免费 SSL 证书是一个很好的选择。下面我们详细介绍如何为网站域名申请该证书。 一、准备工作 域名 确保已注册要使用 SSL 证书的…...

【JAVA】Java基础—面向对象编程:继承—重写父类方法
在Java开发中,重写(Override)是面向对象编程(OOP)中的一个重要概念。它允许子类提供父类方法的具体实现,从而改变或扩展父类的行为。重写是实现多态性的重要手段,使得程序在运行时能够根据对象的…...

【C++初阶】C++入门
1、C第一个程序 C是脱胎于C语言的,所以也包含了C语言绝大多数的内容,C兼容C语言绝大多数的语法,在C语言中能实现的程序在C中也是可以执行的,但需要将定义文件代码的后缀改为.cpp 就比如hello world程序 // test.cpp #include<stdio.h&g…...

自然推理系统:的拒取式的解析
要推导出 **"非A"** 的拒取式 (rejection form),首先我们要理解逻辑推理中几个基本的概念。 假设我们有以下前提: 1. **A → B** (如果A成立,那么B成立) 2. **非B** (B不成立) 我们…...

OceanBase 分区表详解
1、分区表的定义 在OceanBase数据库中,普通的表数据可以根据预设的规则被分割并存储到不同的数据区块中,同一区块的数据是在一个物理存储上。这样被分区块的表被称为分区表,而其中的每一个独立的数据区块则被称为一个分区。 如下图所示&…...

Java中 LinkedList<>,ArrayDeque<>的区别 || Queue和Deque的区别
我是你爹 LinkedList<> 和 ArrayDeque<> 的区别Queue接口 和 Deque接口区别Queue 的用法Deque 的用法 LinkedList<> 和 ArrayDeque<> 的区别 1. 数据结构实现方式: LinkedList: 基于链表结构,是一个双向链表。每个…...