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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java学习者的福音:SpringBoot教学辅助平台

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理教学辅助平台的相关信息成为必然。开发合适…

JAVA基础:数组 (习题笔记)

一&#xff0c;编码题 1&#xff0c;数组查找操作&#xff1a;定义一个长度为10 的一维字符串数组&#xff0c;在每一个元素存放一个单词&#xff1b;然后运行时从命令行输入一个单词&#xff0c;程序判断数组是否包含有这个单词&#xff0c;包含这个单词就打印出“Yes”&…

网络层5——IPV6

目录 一、IPv6 vs IPv4 1、对IPv6主要变化 2、IPv4 vs IPv6 二、IPv6基本首部 1、版本——4位 2、通信量类——8位 3、流标号——20位 4、有效载荷长度——16位 5、下一个首部——8位 6、跳数限制——8位 7、源 、 目的地址——128位 8、扩展首部 三、IPv6地址 1…

AIRIS 是一种学习型人工智能,它正在自学如何玩 Minecraft

AI开发公司SingularityNET和人工超级智能联盟&#xff08;ASI Alliance&#xff09;表示,随着人工智能学习如何通过操作玩游戏,一种新的学习型AI已被留在Minecraft的实例中。名为AIRIS&#xff08;自主智能增强推断象征主义&#xff09;的AI基本上是从Minecraft内部开始学习如何…

嵌入式学习-网络高级-Day01

嵌入式学习-网络高级-Day01 【1】Modbus协议 起源 分类 优势 应用场景 【2】Modbus TCP 特点 组成 报文头&#xff1a;7个字节 寄存器&#xff08;存储数据&#xff09; 功能码 总结 练习 【3】工具安装 Modbus Slave、Poll安装 网络调试助手 wireshark 练习 【1】Modbus协议 起…

Java项目实战II基于Spring Boot的问卷调查系统的设计与实现(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导 一、前言 在当今信息爆炸的时代&#xff0c;问卷调查…

【c++语言程序设计】数组(对象数组)

数组是一种按照特定顺序排列的对象集合体&#xff0c;数组中的每个对象称为“元素”。数组的每个元素都用“数组名下标”的形式来表示&#xff0c;并且同一数组内的所有元素类型相同。数组可以由任何类型的数据构成&#xff08;除 void 外&#xff09;&#xff0c;且数组的概念…

5分钟跑起来:Java构建的AI人工智能智能问答系统_springboot_spring ai_LLM_人工智能_开源免费使用

Agenda&#xff1a; 1&#xff09;介绍一下AI支持下的智能问答系统有哪些主要模块 2&#xff09;一个可以跑起来的代码样例&#xff0c;说明怎么用Java构建这个AI智能问答系统 AI人工智能智能问答系统简介 智能问答系统是一种利用人工智能技术理解并回答用户提问的应用。该系…

如何基于pdf2image实现pdf批量转换为图片

最近为了将pdf报告解析成为文本和图片&#xff0c;需要将大量多页的pdf文件拆分下单独的一页一页的图像&#xff0c;以便后续进行OCR和图像处理&#xff0c;因此就需要实现将pdf2image&#xff0c;本文主要结合开源的pdf2image和poppler&#xff0c;实现了pdf转换为png格式图片…

Pytorch用BERT对CoLA、新闻组文本数据集自然语言处理NLP:主题分类建模微调可视化分析

原文链接&#xff1a;https://tecdat.cn/?p38181 原文出处&#xff1a;拓端数据部落公众号 自然语言处理&#xff08;NLP&#xff09;领域在近年来发展迅猛&#xff0c;尤其是预训练模型的出现带来了重大变革。其中&#xff0c;BERT 模型凭借其卓越性能备受瞩目。然而&#…

Kaggle:免费 GPU 使用指南,Colab 的理想替代方案

如果电脑显卡性能不足&#xff0c;又无法访问 Colab 的免费 GPU&#xff0c;那该怎么开始之后的学习呢&#xff1f; 答案是 Kaggle。 Kaggle 不仅提供免费的 GPU 计算资源&#xff0c;还可以直连而无需翻墙&#xff0c;同时不需要海外手机号验证。接下来&#xff0c;文章将详细…

Zookeeper 简介 | 特点 | 数据存储

1、简介 zk就是一个分布式文件系统&#xff0c;不过存储数据的量极小。 1. zookeeper是一个为分布式应用程序提供的一个分布式开源协调服务框架。是Google的Chubby的一个开源实现&#xff0c;是Hadoop和Hbase的重要组件。主要用于解决分布式集群中应用系统的一致性问题。 2. 提…

神经网络基础--什么是神经网络?? 常用激活函数是什么???

前言 本专栏更新神经网络的一些基础知识&#xff1b;案例代码基于pytorch&#xff1b;欢迎收藏 关注&#xff0c; 本人将会持续更新。 神经网络 1、什么是神经网络 人工神经网络&#xff08; Artificial Neural Network&#xff0c; 简写为ANN&#xff09;也简称为神经网络…

大模型也要“私人定制“?最新综述带你解锁AI的个性化服务 | 综述!扩散模型:AI艺术创作背后的“魔法引擎“

大模型领域的发展日新月异&#xff0c;每天都有许多有趣的论文值得深入品读。下面是本期觉得比较有意思的论文&#xff1a; 1、大模型也要"私人定制"&#xff1f;最新综述带你解锁AI的个性化服务 2、综述&#xff01;扩散模型&#xff1a;AI艺术创作背后的"魔法…

【MySQL 保姆级教学】深层理解索引及其特性(重点)--上(11)

MySQL与磁盘 1. MySQL与内存和磁盘的联系2. 认识磁盘2.1 MySQL与存储2.2 磁盘结构2.3 扇区2.4 定位扇区 3. MySQL与磁盘交互基本单位4. 建立共识5. 索引的理解5.1 建立一个表并查询5.2 为何 I/O 交互要是Page 6. B树 Vs B 树数6.1 不同存储引擎支持的索引结构类型6.2 B树 Vs B树…

1分钟教你利用ai工具免费制作养生视频,自动化批量操作,效率提升10倍!

养生这个是未来比较火爆的一个赛道,很多人越来越注重养生&#xff0c;你会发现抖音各种健身操博主&#xff0c;视频播放数据都很不错。很多人上一秒说的养生&#xff0c;下一秒又熬起了夜。年纪轻轻就喝起了枸杞续命。 有想做视频号带货的家人&#xff0c;其实可以考虑养生赛道…

思通数科纸质档案扫描与识别与档案馆应用场景介绍

在传统档案馆中&#xff0c;纸质文件的处理和管理是一个重要且繁琐的环节&#xff0c;特别是面对庞大的历史资料库。思通数科的AI能力平台提供了一种高效的数字化解决方案&#xff0c;利用OCR技术将纸质档案中的信息自动提取并转化为数字文本&#xff0c;具体过程包括以下几个步…

AutoCAD的Dwg版本代号、R版本参数值以及二次开发时VS、.NET版本关系

Dwg的AC版本代号 出处&#xff1a;https://www.autodesk.com.cn/support/technical/article/caas/sfdcarticles/sfdcarticles/CHS/drawing-version-codes-for-autocad.html 以下是AutoCAD图形的不同版本代号&#xff1a; MC0.0 - DWG Release 1.1 AC1.2 - DWG R1.2 AC1.4 - DW…

微服务day02

教学文档&#xff1a; 黑马教学文档 Docker Docker的安装 镜像和容器 命令解读 常见命令 案例 查看DockerHub&#xff0c;拉取Nginx镜像&#xff0c;创建并运行容器 搜索Nginx镜像&#xff1a;在 www.hub.docker.com 网站进行查询 拉取镜像&#xff1a; docker pull ngin…

脉冲全闭环EtherCAT运动控制器的固件升级

本文导读 随着产品的发展&#xff0c;正运动技术产品的固件也在不断更新与完善&#xff0c;新固件较老固件会解决一些客户的新需求与软件上的bug&#xff0c;增加了一些指令和功能&#xff0c;添加了更多的 EtherCAT从站与io模块。因此&#xff0c;升级固件是常用功能。推荐优…