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

使用vue-next-admin的架构

vue-next-admin: 🎉🎉🔥基于vue3.x 、Typescript、vite、Element plus等,适配手机、平板、pc 的后台开源免费模板库(vue2.x请切换vue-prev-admin分支)icon-default.png?t=N7T8https://gitee.com/lyt-top/vue-next-adminexe桌面应用效果图

一.配置package.json

1.添加 "main": "dist-electron/main.js",

2.在scripts下的build属性添加   && electron-builder

2.添加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": "打包文件存放地址"
			}
		],
		"releaseInfo": {
			"releaseNotes": "版本更新的具体内容"
		}
	}

二.配置vite.config.ts

npm i  vite-plugin-electron  vite-plugin-electron-renderer

给plugins属性添加两个方法

注意:在exe桌面应用才需要加renderer(),否则在网页不支持对path的动态引入

import electron from "vite-plugin-electron";
import renderer from "vite-plugin-electron-renderer";
import isElectron from "is-electron";

electron([{ entry: "electron/main.ts" }]),
isElectron() ? renderer() : undefined,

三.创建electron文件

 logo.ico

helper.ts文件

npm  i  path  fs  electron

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)
  })
}

updater.ts文件

 npm  i  electron-updater  electron  electron-log

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文件
   */
  // autoUpdater.setFeedURL({
  //   provider: "generic",
  //   url: "", // 打包文件存放地址
  // });

  // 发送消息给渲染线程
  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,
      cancelId: -1,
      // checkboxLabel: "以后自动下载并安装更新",
      // checkboxChecked: false,
      textWidth: 300,
    });
    if ([1, 2].includes(response)) {
      let updaterData = {
        version: version,
        skip: response === 1,
        // 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";

const createWindow = () => {
  win = new BrowserWindow({
    width: 1250,
    height: 700,
    minWidth: 1250,
    minHeight: 700,
    title: "qjyiot",
    icon: path.join(__dirname, "..", "electron/icon/logo.ico"),
    webPreferences: {
      webviewTag: true,
      nodeIntegration: true,
      contextIsolation: false,
    },
  });

  if (win) {
    win.setMenu(null); // 隐藏左上角菜单
  }

  if (process.env.NODE_ENV != "development") {
    win.loadFile(path.join(__dirname, "..", "dist/index.html"));
  } else {
    let url = process.env["VITE_DEV_SERVER_URL"] as string;
    if (url?.includes("false")) {
      url = url.substring(0, url.length - 5);
    } else if (url?.includes("true")) {
      url = url.substring(0, url.length - 4);
    }
    win.loadURL(url);
  }

  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);

    // app.on("ready", () => {
    //   createWindow();
    // });
  }
}

四.定义更新应用版本组件

在src目录下定义layout\updater\index.vue

npm  i  element-plus  electron

<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 } from 'element-plus';

export default defineComponent({
    name: "layoutUpdater",
    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>

五.App.vue根组件引用

template

<Updater v-if="isElectron()"></Updater>

script 

import isElectron from "is-electron";

const Updater = defineAsyncComponent(() => import('/@/layout/updater/index.vue'));

六.调试

把 if 判断注释掉

 重新编译

自动弹出效果

七.打包

注意: 打包生成exe需要有很强大的网络才行

npm  run  build

打包完成会自动输出 dist-electron和release 

进入release\版本号 可以看见生成exe应用 双击可安装exe应用

八.exe应用版本更新

1.打包安装完成后自动弹出exe窗口 

2.当本地版本和服务器地址版本不一致自动会弹出"软件更新提示"

3.点击"安装更新",弹出下载更新中

4.更新完成提示安装

  • 28
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用 Vue 3 + Vite + TypeScript 进行项目打包混淆后导致样式丢失的问题,可能是由于混淆过程中对样式文件进行了修改或错误处理引起的。以下是一些可能的解决方法: 1. 排除样式文件:在混淆配置中,确保将样式文件(如CSS或SCSS文件)排除在混淆范围之外。这可以通过插件提供的选项或正则表达式来实现。确保只混淆与 JavaScript 相关的代码,而不是样式文件。 2. 使用其他混淆工具:尝试使用其他 JavaScript 混淆工具来替代当前使用的混淆工具。不同的工具可能会对样式文件有更好的处理方式,从而避免导致样式丢失的问题。 3. 分离样式和脚本:考虑将样式和脚本分离,先加载样式文件再加载混淆后的脚本文件。这样可以确保样式文件不会受到混淆过程的影响。 4. 检查构建配置:检查一下项目的构建配置是否正确。确保没有错误的配置导致样式文件无法正确打包。特别注意构建过程中是否有对样式文件的处理或修改。 5. 调试和排查:如果以上方法都无效,可以尝试进行调试和排查。检查混淆后的代码,查看是否有对样式相关的部分进行了错误的修改或处理。同时检查开发工具的日志和错误输出,查找可能与样式丢失有关的信息。 通过以上方法,你应该能够解决在混淆过程中导致样式丢失的问题。确保正确配置混淆插件,并将样式文件排除在混淆范围之外,可以保持样式文件的完整性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值