当前位置: 首页 > news >正文

react+hook+vite项目使用eletron打包成桌面应用+可以热更新

 使用Hooks-Admin的架构

Hooks-Admin: 🚀🚀🚀 Hooks Admin,基于 React18、React-Router V6、React-Hooks、Redux、TypeScript、Vite2、Ant-Design 开源的一套后台管理框架。icon-default.png?t=O83Ahttps://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: &#x1f680;&#x1f680;&#x1f680; Hooks Admin&#xff0c;基于 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工程这里使用的是上篇文章&#xff08;STM32 ADC — DMA采样&#xff09;中生成的工程配置 软件切换实现乒乓 cubeMX默认生成的工程中是打开DMA中断…...

SpringCloud基础 入门级 学习SpringCloud 超详细(简单通俗易懂)

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

【Windows 常用工具系列 20 -- MobaXterm 登录 WSL】

文章目录 MobaXterm 登录 WSL MobaXterm 登录 WSL 在 WSL 启动之后&#xff0c;打开 MobaXterm&#xff1a; 在 Distribution 中选择自己本地安装的 ubuntu 版本&#xff0c;我这里使用的是ubuntu-20.4&#xff0c;然后在 runmethod 中选择 Localhost connection. 连接成功之…...

【vmware+ubuntu16.04】ROS学习_博物馆仿真克隆ROS-Academy-for-Beginners软件包处理依赖报错问题

首先安装git 进入终端&#xff0c;输入sudo apt-get install git 安装后&#xff0c;创建一个工作空间名为tutorial_ws&#xff0c; 输入 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&#xff0c;无法通过代理请求远程的接口并返回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动态参数逆向+隧道代理+自定义中间件的猎聘招聘数据爬取

在本篇博客中&#xff0c;我们将介绍如何使用 Scrapy 框架结合 JS 逆向技术、代理服务器和自定义中间件&#xff0c;来爬取猎聘网站的招聘数据。猎聘是一个国内知名的招聘平台&#xff0c;提供了大量的企业招聘信息和职位信息。本项目的目标是抓取指定城市的招聘信息&#xff0…...

Spring 中的 BeanDefinitionParserDelegate 和 NamespaceHandler

一、BeanDefinitionParserDelegate Spring在解析xml文件的时候&#xff0c;在遇到<bean>标签的时候&#xff0c;我们会使用BeanDefinitionParserDelegate对象类解析<bean>标签的内容&#xff0c;包括<bean>标签的多个属性&#xff0c;例如 id name class in…...

BERT模型核心组件详解及其实现

摘要 BERT&#xff08;Bidirectional Encoder Representations from Transformers&#xff09;是一种基于Transformer架构的预训练模型&#xff0c;在自然语言处理领域取得了显著的成果。本文详细介绍了BERT模型中的几个关键组件及其实现&#xff0c;包括激活函数、变量初始化…...

图论-代码随想录刷题记录[JAVA]

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

c#加载shellcode

本地加载bin文件 SharpPELoader项目如下&#xff1a; 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&#xff08;鸿蒙操作系统&#xff09;作为一种面向全场景多设备的智能操作系统&#xff0c;正逐渐在市场上崭露头角。为了进入HarmonyOS生态&#xff0c;开发者需要搭建一个高效的开发环境。本文将详细介绍如何搭建HarmonyOS开发环境&#xff0c;特别是如何安装和配置…...

【网络云计算】2024第46周周考-磁盘管理的基础知识-RAID篇

文章目录 1、画出各个RAID的结构图&#xff0c;6句话说明优点和缺点&#xff0c;以及磁盘可用率和坏盘数量&#xff0c;磁盘总的数量2、写出TCP五层模型以及对应的常用协议 【网络云计算】2024第46周周考-磁盘管理的基础知识-RAID篇 1、画出各个RAID的结构图&#xff0c;6句话说…...

深入理解 SQL_MODE 之 ANSI_QUOTES

引言 在 MySQL 数据库中&#xff0c;sql_mode 是一个重要的配置参数&#xff0c;它定义了 MySQL 应该遵循的 SQL 语法标准以及数据验证规则。其中&#xff0c;ANSI_QUOTES 是 sql_mode 中的一个重要选项&#xff0c;它改变了 MySQL 对于字符串和标识符的识别方式&#xff0c;使…...

容器技术在持续集成与持续交付中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 容器技术在持续集成与持续交付中的应用 容器技术在持续集成与持续交付中的应用 容器技术在持续集成与持续交付中的应用 引言 容器…...

【嵌入式软件-STM32】OLED显示屏+调试方法

目录 一、调试方式 1&#xff09;串口调试 优势 弊端 2&#xff09;显示屏调试 优势 弊端 3&#xff09;Keil调试模式 4&#xff09;点灯调试法 5&#xff09;注释调试法 6&#xff09;对照法 二、OLED简介 OLED组件 OLED显示屏 0.96寸OLED模块 OLED外观和种类…...

kubernetes简单入门实战

本章将介绍如何在kubernetes集群中部署一个nginx服务&#xff0c;并且能够对其访问 Namespace Namespace是k8s系统中一个非常重要的资源&#xff0c;它的主要作用是用来实现多套环境的资源隔离或者多租户的资源隔离。 默认情况下&#xff0c;k8s集群中的所有的Pod都是可以相…...

Python连接Mysql、Postgre、ClickHouse、Redis常用库及封装方法

博主在这里分享一些常见的python连接数据库或中间件的库和封装方案&#xff0c;希望对大家有用。 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包

前言 开发中遇到一个问题&#xff0c;配置 Element Plus 自定义主题时&#xff0c;添加了 ElementPlusResolver({ importStyle: "sass" }) 后&#xff0c;控制台出现报错&#xff0c;这是因为 Dart Sass 2.0 不再支持使用 !global 来声明新变量&#xff0c;虽然当前…...

黑马Mybatis

Mybatis 表现层&#xff1a;页面展示 业务层&#xff1a;逻辑处理 持久层&#xff1a;持久数据化保存 在这里插入图片描述 Mybatis快速入门 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/6501c2109c4442118ceb6014725e48e4.png //logback.xml <?xml ver…...

k8s从入门到放弃之Ingress七层负载

k8s从入门到放弃之Ingress七层负载 在Kubernetes&#xff08;简称K8s&#xff09;中&#xff0c;Ingress是一个API对象&#xff0c;它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress&#xff0c;你可…...

【入坑系列】TiDB 强制索引在不同库下不生效问题

文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

对WWDC 2025 Keynote 内容的预测

借助我们以往对苹果公司发展路径的深入研究经验&#xff0c;以及大语言模型的分析能力&#xff0c;我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际&#xff0c;我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测&#xff0c;聊作存档。等到明…...

1.3 VSCode安装与环境配置

进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件&#xff0c;然后打开终端&#xff0c;进入下载文件夹&#xff0c;键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...

Cinnamon修改面板小工具图标

Cinnamon开始菜单-CSDN博客 设置模块都是做好的&#xff0c;比GNOME简单得多&#xff01; 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

微服务商城-商品微服务

数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南

&#x1f680; C extern 关键字深度解析&#xff1a;跨文件编程的终极指南 &#x1f4c5; 更新时间&#xff1a;2025年6月5日 &#x1f3f7;️ 标签&#xff1a;C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言&#x1f525;一、extern 是什么&#xff1f;&…...

Java多线程实现之Thread类深度解析

Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek

文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama&#xff08;有网络的电脑&#xff09;2.2.3 安装Ollama&#xff08;无网络的电脑&#xff09;2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...