当前位置: 首页 > 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应用当本地版本和服务器地址的版本不一致自动会弹出更新提示

http://www.lryc.cn/news/484922.html

相关文章:

  • STM32 ADC --- DMA乒乓缓存
  • SpringCloud基础 入门级 学习SpringCloud 超详细(简单通俗易懂)
  • 【Windows 常用工具系列 20 -- MobaXterm 登录 WSL】
  • 【vmware+ubuntu16.04】ROS学习_博物馆仿真克隆ROS-Academy-for-Beginners软件包处理依赖报错问题
  • UniApp的Vue3版本中H5配置代理的最佳方法
  • 深入了解Pod
  • 基于Spider异步爬虫框架+JS动态参数逆向+隧道代理+自定义中间件的猎聘招聘数据爬取
  • Spring 中的 BeanDefinitionParserDelegate 和 NamespaceHandler
  • BERT模型核心组件详解及其实现
  • 图论-代码随想录刷题记录[JAVA]
  • c#加载shellcode
  • HarmonyOS 开发环境搭建
  • 【网络云计算】2024第46周周考-磁盘管理的基础知识-RAID篇
  • 深入理解 SQL_MODE 之 ANSI_QUOTES
  • 容器技术在持续集成与持续交付中的应用
  • 【嵌入式软件-STM32】OLED显示屏+调试方法
  • kubernetes简单入门实战
  • Python连接Mysql、Postgre、ClickHouse、Redis常用库及封装方法
  • 如何修改npm包
  • Django 2024全栈开发指南(三):数据库模型与ORM操作(上篇)
  • 低代码可视化-uniapp开关选择组件-低码生成器
  • 【arxiv‘24】Vision-Language Navigation with Continual Learning
  • 如何在 Ubuntu 上安装 Jupyter Notebook
  • 免费申请 Let‘s Encrypt SSL 证书
  • 【JAVA】Java基础—面向对象编程:继承—重写父类方法
  • 【C++初阶】C++入门
  • 自然推理系统:的拒取式的解析
  • OceanBase 分区表详解
  • Java中 LinkedList<>,ArrayDeque<>的区别 || Queue和Deque的区别
  • freemarker 读取template.xml ,通过response 输出文件,解决中文乱码问题