【前端打包相关】electron-builder中NSIS配置项解析👩‍🌾

2,757 阅读6分钟

electron-builder中NSIS配置项的简要说明

  • oneClicktrue布尔值-是创建一键安装程序还是辅助安装程序。

  • perMachinefalse布尔值-是否显示辅助安装程序的安装模式安装程序页面(选择按机器还是按用户)。或者是否始终按所有用户(每台计算机)安装。

    如果oneClicktrue(默认):是否按所有用户(每台计算机)安装。

    如果oneClickis falseperMachineis true:无安装模式安装程序页面,请始终按机器安装。

    如果oneClickfalseperMachinefalse(默认):安装模式安装程序页面。


  • allowElevationtrue布尔值- 仅辅助安装程序。 允许请求提升。如果为false,则用户将不得不以提升的权限重新启动安装程序。

  • allowToChangeInstallationDirectoryfalse布尔值- 仅辅助安装程序。 是否允许用户更改安装目录。

  • installerIcon字符串-安装程序图标的路径,相对于构建资源或项目目录。默认为build/installerIcon.ico或应用程序图标。

  • uninstallerIcon字符串-相对于构建资源或项目目录的卸载程序图标的路径。默认为build/uninstallerIcon.ico或应用程序图标。

  • installerHeaderbuild/installerHeader.bmp字符串- *仅辅助安装程序。 MUI_HEADERIMAGE,相对于构建资源或项目目录。
  • installerHeaderIcon字符串- 仅一键安装程序。 相对于构建资源或项目目录的标题图标(进度条上方)的路径。默认为build/installerHeaderIcon.ico或应用程序图标。

  • installerSidebar字符串- 仅辅助安装程序。  MUI_WELCOMEFINISHPAGE_BITMAP,相对于构建资源或项目目录。默认为build/installerSidebar.bmp${NSISDIR}\Contrib\Graphics\Wizard\nsis3-metro.bmp。图像尺寸164×314像素。

  • uninstallerSidebar字符串- 仅辅助安装程序。  MUI_UNWELCOMEFINISHPAGE_BITMAP,相对于构建资源或项目目录。默认为installerSidebar选项或build/uninstallerSidebar.bmpbuild/installerSidebar.bmp${NSISDIR}\Contrib\Graphics\Wizard\nsis3-metro.bmp

  • uninstallDisplayName${productName} ${version}字符串-控制面板中的卸载程序显示名称。

  • include字符串-NSIS包含定制安装程序脚本的路径。默认为build/installer.nsh。请参阅自定义NSIS脚本

  • script字符串-用于自定义安装程序的NSIS脚本的路径。默认为build/installer.nsi。请参阅自定义NSIS脚本

  • license字符串-EULA许可证文件的路径。默认为license.txteula.txt(或大写变体)。除了txt,rtf andhtml supported (don't forget to usetarget =“ _ blank”`以外的链接)。

    支持使用不同语言的多个许可证文件-使用lang后缀(例如_de_ru)。例如,创建文件license_de.txtlicense_en.txt在构建资源中。如果操作系统语言为德语,license_de.txt将显示。请参阅语言代码到名称的映射。

    将通过用户OS语言选择适当的许可证文件。



  • deleteAppDataOnUninstallfalse布尔值- 仅一键安装程序。 是否在卸载时删除应用程序数据。

  • differentialPackage布尔值- true网络安装程序(nsis-web)的默认设置

  • displayLanguageSelectorfalse布尔值-是否显示语言选择对话框。不推荐(默认情况下将使用OS语言检测到)。

  • installerLanguagesArray | 字符串-安装程序语言(例如en_USde_DE)。仅当您了解自己的工作和目的时才进行更改。

  • language字符串-LCID Dec,默认为1033English - United States)。

  • multiLanguageInstaller布尔值-是否创建多语言安装程序。默认为unicode选项值。

  • packElevateHelpertrue布尔值-是否打包提升的可执行文件(如果使用了每台机器安装程序,或者将来可以使用,则对于电子更新程序是必需的)。忽略是否perMachine设置为true

  • preCompressedFileExtensions[".avi", ".mov", ".m4v", ".mp4", ".m4p", ".qt", ".mkv", ".webm", ".vmdk"]Array <字符串> | 字符串-将不被压缩的文件的文件扩展名。仅适用于extraResourcesextraFiles文件。



  • warningsAsErrorstrue布尔值-如果warningsAsErrorstrue(默认):NSIS将把警告视为错误。如果warningsAsErrorsfalse:NSIS将允许警告。

  • runAfterFinishtrue布尔值-完成后是否运行已安装的应用程序。对于辅助安装程序,将删除相应的复选框。

  • createDesktopShortcuttrue布尔| “始终”-是否创建桌面快捷方式。设置为always是否在重新安装时也重新创建(即使被用户删除)。

  • createStartMenuShortcuttrue布尔值-是否创建开始菜单快捷方式。

  • menuCategoryfalse布尔| 字符串-是否为开始菜单快捷方式和程序文件目录创建子菜单。如果为true,则将使用公司名称。或字符串值。

  • shortcutName字符串-将用于所有快捷方式的名称。默认为应用程序名称。

electron项目初始化配置速记

vite.config.js

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

import electron from "vite-plugin-electron";
// import electronRender from "vite-plugin-electron-renderer";

export default defineConfig({
  plugins: [
    vue(),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    electron({
      main: {
        entry: "_electron_/index.js",
      },
    }),
    //electronRender()
  ]
})

项目配置文件 package.json

{
  "name": "wp",
  "private": true,
  "version": "0.0.1",
  "homepage": "/",
  "main": "_electron_/index.js",
  "scripts": {
    "dev": "cross-env NODE_ENV=development vite",
    "build": "vite build  && electron-builder"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^5.0.4",
    "cross-env": "^7.0.3",
    "electron": "^9.2.1",
    "electron-builder": "^22.7.0",
    "element-plus": "^2.6.1",
    "unplugin-auto-import": "^0.17.5",
    "unplugin-vue-components": "^0.26.0",
    "v-contextmenu": "^3.0.0",
    "vite": "^5.1.4",
    "vite-plugin-electron": "^0.8.8",
    "vue": "^3.4.19"
  },
  "build": {
    "appId": "xxxx",
    "productName": "xxxx",
    "asar": true,
    "copyright": "Copyright © 2022 electron",
    "directories": {
      "output": "release/"
    },
    "files": [
      "_electron_/**/*",
      "dist/**/*"
    ],
    "mac": {
      "artifactName": "${productName}_${version}.${ext}",
      "target": [
        "dmg"
      ]
    },
    "win": {
      "icon": "/public/icon.png",
      "target": [
        {
          "target": "nsis",
          "arch": [
            "x64"
          ]
        }
      ],
      "artifactName": "${productName}_${version}.${ext}"
    },
    "nsis": {
      "artifactName": "${productName}_${version}.${ext}",
      "uninstallDisplayName": "SC",
      "oneClick": false,
      "allowToChangeInstallationDirectory": true,
      "createDesktopShortcut": true,
      "createStartMenuShortcut": true,
      "runAfterFinish": true,
      "installerLanguages": [
        "zh_CN"
      ]
    },
    "electronDownload": {
      "cache": "D:/Download",
      "version": "9.2.1"
    },
    "publish": [
      {
        "provider": "generic",
        "url": ""
      }
    ],
    "releaseInfo": {
      "releaseNotes": ""
    },
    "extends": null
  }
}

主进程配置 electron/index.js

参考配置-1

const { app, BrowserWindow, ipcMain, Notification } = require('electron')
const path = require('path')

let originWidth = 1000
let originHeight = 580
let win

const createWindow = () => {
    win = new BrowserWindow({
        frame: true,//关闭原生导航栏
        width: originWidth,
        height: originHeight,
        minWidth: 1000,//1440,
        minHeight: 580,//680,
        title: "xxxx", //默认窗口标题 -最低优先级
        backgroundColor: "#000", //窗口背景色 默认-#FFF
        autoHideMenuBar: true,
        webPreferences: {
            nodeIntegration: true,
            contextIsolation: false, //上下文隔离
        }
    })
    //判断环境 开发环境 or 生产环境
    //app.isPackaged 无效
    if (process.env.NODE_ENV != 'development') {
        win.loadFile(path.join(__dirname, "../dist/index.html"))
    } else {
        win.loadURL(`${process.env['VITE_DEV_SERVER_URL']}`)
    }
    // win.setMenu(null)
    // 显示菜单栏
    // win.setMenuBarVisibility(true)
}

app

const getTheLock = app.requestSingleInstanceLock()
if (!getTheLock) {
    app.exit() //强制关闭
    if (win)
        win.show();
} else {
    app.whenReady().then(createWindow)
}

参考配置-2

const {
  app,
  shell,
  BrowserWindow,
  session,
  Menu,
  nativeImage,
  ipcMain,
  Tray,
  Notification,
} = require('electron')
var path = require('path');
const {
  machineIdSync
} = require('node-machine-id')
const crypto = require('crypto')
const config = require('./config')
const serverKey = config.serverKey
let win = null
const ex = process.execPath; // 获取可执行文件位置
const loginWidth = 380 + 30
const loginHeight = 460 + 40
let loginPage = null
let errorTimeoutCount = 1
let newwin;
let settingWin

//创建连接-本地文件数据库
var SqliteDB = require('./sqlite.js').SqliteDB;
var file = "thenKeys.db";
var sqliteDB = new SqliteDB(file);
//创建数据库:如果没有则创建
let tableName = 'tiles' //表名
let keys = [ //表内字段  ---字段名 存储类型---
  "key VARCHER", //VARCHER-字符
  "value VARCHER",
]
let strKeys = keys.join(',') //转换为可用字符串
var createTableSql = `create table if not exists ${tableName}(${strKeys});`;
sqliteDB.createTable(createTableSql);

function sign(id, ts) {
  let str = 'Windows' + id + serverKey + ts
  const md5 = crypto.createHash('md5')
  md5.update(str, 'utf8')
  const signstr = md5.digest('hex').toUpperCase()
  return signstr
}
ex
//设置开机自起
function onAppReady() {
  // const exeName = path.basename(ex);//Electron 的process对象是从Node.js process对象扩展而来的。
  app.setLoginItemSettings({
    openAtLogin: true, //true在登录时启动应用,false 移除应用作为登录启动项默认为 false.
    openAsHidden: false, //macOs专用 true 表示以隐藏的方式启动应用。 默认为false。
    path: ex, //Windows - 在登录时启动的可执行文件,具体的为打包后的APP所在的exe文件路径。默认为 process.execPath
    args: [], // args: ["--processStart", `"${exeName}"`], //要传递给可执行文件的命令行参数。默认为空数组。注意用引号将路径换行。
  });
}

function createWindow() {
  session.defaultSession.webRequest.onBeforeSendHeaders(((details, callback) => {
    details.requestHeaders['User-Agent'] = 'TioIm Client 1.0.0'
    const identifier = machineIdSync()
    const ts = new Date().getTime()
    details.requestHeaders['Identifier'] = identifier
    details.requestHeaders['Timestamp'] = ts + ''
    details.requestHeaders['Signature'] = sign(identifier, ts)
    details.requestHeaders['Platform'] = 'Windows'
    callback({
      requestHeaders: details.requestHeaders
    })
  }))

  // 创建浏览器窗口
  let counter = 0
  win = new BrowserWindow({
    show: false, //窗口是否在创建时显示
    paintWhenInitiallyHidden: false, //配合 show: false 使用
    width: loginWidth,
    height: loginHeight,
    minWidth: loginWidth,
    minHeight: loginHeight,
    // frame: false,//关闭原生导航栏
    title: "xxxx", //默认窗口标题 -最低优先级
    backgroundColor: "#FFF", //窗口背景色 默认-#FFF
    hasShadow: true, //窗口是否有阴影. 默认值为 true。
    hasShadow: false, //boolean (可选) - 窗口是否有阴影
    webPreferences: { //网页功能设置
      nodeIntegration: true, //与托盘消息相关   是否启用Node integration. 默认值为 false.
      enableRemoteModule: true,
      contextIsolation: false, //与托盘消息相关
      session: session.fromPartition(`${counter++}`),
    }
  })
  win.loadURL(config.serverUrl)
  win.setMenu(null)
  // win.webContents.openDevTools()

  // 拖盘图标-路径
  let iconURL = ''
  if (app.isPackaged) {
    iconURL = nativeImage.createFromPath(path.join(__dirname, 'tp.png')) //生产环境
  } else {
    iconURL = 'icons/tp.png' //开发环境
  }
  // 拖盘图标-有消息-路径
  let iconURLXx = ''
  if (app.isPackaged) {
    iconURLXx = nativeImage.createFromPath(path.join(__dirname, 'xx.png')) //生产环境
  } else {
    iconURLXx = 'icons/xx.png' //开发环境
  }

  // 创建拖盘
  var iconTray = new Tray(iconURL);

  var timer = null
  var count = 0

  // 鼠标悬停托盘提示
  iconTray.setToolTip('xxxx');
  // 配置右键菜单
  var trayMenu = Menu.buildFromTemplate([{
      label: '查看',
      click: function () {
        win.show()
      }
    },
    {
      label: '退出',
      click: function () {
        app.exit()
      }
    }
  ]);
  // 绑定右键菜单到托盘
  iconTray.setContextMenu(trayMenu);

  //恢复托盘状态
  let recoverIconTray = function () {
    iconTray.setImage(iconURL)
    iconTray.setToolTip('xxxx')
    clearInterval(timer)
    timer = null
    count = 0
  }

  //-----处理来自渲染进程的讯息-------------------------------------------------------------------------------------------------------------------------------------------------------------------

  // 渲染线程通知,有新的消息
  ipcMain.on('haveMessage', (event, arg) => {
    if (!win.isVisible() || win.isMinimized()) {
      iconTray.removeBalloon()
      iconTray.setToolTip('您有一条新消息')
      clearInterval(timer)
      timer = setInterval(() => {
        iconTray.setImage(count++ % 2 === 0 ? iconURL : nativeImage.createFromPath(null))
      }, 500)
      //8秒后消息不在闪动
      setTimeout(() => {
        clearInterval(timer)
        iconTray.setImage(iconURLXx)
      }, 1000 * 8)
    }
  })

  // 渲染线程通知
  ipcMain.on('overMessage', (event, arg) => {
    recoverIconTray()
  })

  // 使用默认浏览器打开指定网页
  ipcMain.on('goUrlMessage', (event, arg) => {
    shell.openExternal(arg); //直接在浏览器打开
  })

  // 使用子窗口打开指定页面-设置页面
  ipcMain.on('addSettingMessage', (event, arg) => {
    if (settingWin) {
      settingWin.show()
    } else {
      settingWin = new BrowserWindow({
        width: 400 + 30 + 1,
        height: 500 + 40 + 5,
        parent: win, //win是主窗口
        title: "设置", //默认窗口标题 -最低优先级
        hasShadow: true, //窗口是否有阴影. 默认值为 true。
        hasShadow: false, //boolean (可选) - 窗口是否有阴影
        webPreferences: {
          session: win.webContents.session //共享session
        }
      })
      // settingWin.loadURL(path.join(__dirname, 'view/setting/index.html')) 
      settingWin.loadURL(path.join(__dirname, './view/setting/index.html'))
      settingWin.setMenu(null)
      settingWin.setResizable(false)
      settingWin.on('closed', () => {
        settingWin = null
      })
    }
  })

  // 使用子窗口打开指定网页
  ipcMain.on('addUrlMessage', (event, arg) => {
    if (newwin) {
      newwin.show()
    } else {
      newwin = new BrowserWindow({
        width: 1200,
        height: 900,
        parent: win, //win是主窗口
        minWidth: 1200,
        minHeight: 900,
        title: "视频会议", //默认窗口标题 -最低优先级
        hasShadow: true, //窗口是否有阴影. 默认值为 true。
        hasShadow: false, //boolean (可选) - 窗口是否有阴影
        webPreferences: {
          session: win.webContents.session
        }
      })
      newwin.loadURL(arg) //https://oim.openice.cn/vm/
      newwin.setMenu(null)
      // newwin.webContents.openDevTools()
      newwin.on('closed', () => {
        newwin = null
      })
    }
  })

  //渲染页面叫我切换到登录页状态
  ipcMain.on('minViewMessage', (event, arg) => {
    win.hide()
    win.setSize(loginWidth, loginHeight) //改变窗口大小
    setTimeout(() => {
      win.center() //将窗口移动到屏幕中央。
      win.setResizable(false) //设置用户是否可以手动调整窗口大小-不可以
      loginPage = true
      win.show()
    }, 1500)
  })

  //渲染页面叫我切换到主体页状态
  ipcMain.on('maxViewMessage', (event, arg) => {
    win.hide()
    win.setSize(1280, 720) //改变窗口大小 --- win.setContentSize(1400, 768)//改变窗口大小-将窗口的工作区
    setTimeout(() => {
      win.center() //将窗口移动到屏幕中央。
      win.setResizable(true) //设置用户是否可以手动调整窗口大小-可以
      loginPage = false
      win.show()
    }, 1500)
  })

  // 开启 开机自启动
  ipcMain.on('openAutoStart', () => {
    app.setLoginItemSettings({
      openAtLogin: true,
      openAsHidden: false,
      path: ex,
      args: []
    });
  });
  // 关闭 开机自启动
  ipcMain.on('closeAutoStart', () => {
    app.setLoginItemSettings({
      openAtLogin: false,
      openAsHidden: false,
      path: ex,
      args: []
    });
  })
  //修改本地文件
  ipcMain.on('asynchronous-message-set', function (event, key, value) {
    let urlName = 'asynchronous-reply-set' //消息发布-发布名称
    var delSql = `delete from ${tableName} where key = '${key}'`;
    sqliteDB.delData(delSql, () => {
      var tileData = [
        [key + '', value + '']
      ];
      var insertTileSql = `insert into ${tableName}(key,value) values(?, ?)`;
      sqliteDB.insertData(insertTileSql, tileData);
    });
    event.sender.send(urlName, 200, "写入成功");
  });
  //删除本地文件json中的某一个键[和值]
  ipcMain.on('asynchronous-message-del', function (event, key) {
    let urlName = 'asynchronous-reply-del' //消息发布-发布名称
    var delSql = `delete from ${tableName} where key = '${key}'`;
    sqliteDB.delData(delSql, (code) => {
      if (code == 200) {
        event.sender.send(urlName, 200, "删除成功");
      } else {
        event.sender.send(urlName, 500, "删除失败");
      }
    });

  });
  // 读取本地文件所有数据
  ipcMain.on('opanGetLoginData', (event) => {
    let urlName = 'getLoginData' //消息发布-发布名称
    // 传给渲染进程数据
    var querySql = `select * from ${tableName}`;
    sqliteDB.queryData(querySql, (code, res) => {
      if (code == 200) {
        event.sender.send(urlName, 200, res);
      } else {
        event.sender.send(urlName, 500, "读取失败");
      }
    });
  })

  ipcMain.on('errorTimeout', () => {
    if (errorTimeoutCount == 2) {
      let NOTIFICATION_TITLE = '提示:请求超时,请检查网络'
      let NOTIFICATION_BODY = new Date().toLocaleTimeString()
      new Notification({
        title: NOTIFICATION_TITLE,
        body: NOTIFICATION_BODY
      }).show()
      win.reload(); //重新加载页面
    }
    errorTimeoutCount++
  })

  //------事件回调相关------------------------------------------------------------------------------------------------------------------------------------------------------------------

  // 窗口显示时的回调事件
  win.on('show', (e) => {});

  // 在窗口关闭时触发
  win.on('closed', (e) => {});

  //因为强制关机或机器重启或会话注销而导致窗口会话结束时触发
  win.on('session-end', (e) => {});

  //网页变得未响应时触发
  win.on('unresponsive', (e) => {
    win.reload(); //重新加载页面
  });

  // 点击关闭按钮让应用保存在托盘
  win.on('close', (e) => {
    if (loginPage) {
      app.exit()
    } else {
      // 阻止窗口的关闭事件
      e.preventDefault();
      win.hide();
    }
  });

  // 任务栏图标单击托盘打开应用
  iconTray.on('click', function () {
    if (win.isVisible()) {
      if (win.isMinimized()) {
        win.show()
        recoverIconTray()
      } else {
        win.hide()
      }
    } else {
      win.show()
      recoverIconTray()
    }
  });

  // 任务栏图标双击托盘打开应用
  iconTray.on('double-click', function () {
    win.show();
  });

  //-------渲染进程监听------webContents---------------------------------------------------------
  app.setAppUserModelId('xxxx');

  //当渲染进程-请求资源失败-时调用
  win.webContents.on('did-fail-load', () => {
    let NOTIFICATION_TITLE = '提示:网络异常,服务器资源请求失败'
    let NOTIFICATION_BODY = new Date().toLocaleTimeString()
    new Notification({
      title: NOTIFICATION_TITLE,
      body: NOTIFICATION_BODY
    }).show()
    app.exit() //直接强制退出程序
  });

  //当渲染进程-崩溃-的时候调用
  win.webContents.on('crashed', () => {
    let NOTIFICATION_TITLE = '提示:出现未知异常,导致xxxx崩溃'
    let NOTIFICATION_BODY = new Date().toLocaleTimeString()
    new Notification({
      title: NOTIFICATION_TITLE,
      body: NOTIFICATION_BODY
    }).show()
    win.reload(); //重新加载页面
  });

  //当渲染进程-重定向-的时候调用
  win.webContents.on('did-get-redirect-request', () => {
    let NOTIFICATION_TITLE = '提示:异常重定向'
    let NOTIFICATION_BODY = new Date().toLocaleTimeString()
    new Notification({
      title: NOTIFICATION_TITLE,
      body: NOTIFICATION_BODY
    }).show()
    app.exit() //直接强制退出程序
  });

  //当验证证书或url-失败-的时候发出事件
  win.webContents.on('certificate-error', () => {
    let NOTIFICATION_TITLE = '提示:验证证书或访问服务器失败'
    let NOTIFICATION_BODY = new Date().toLocaleTimeString()
    new Notification({
      title: NOTIFICATION_TITLE,
      body: NOTIFICATION_BODY
    }).show()
    app.exit() //直接强制退出程序
  });
  //---------------------------------------------------------------------------------------------
}

//2022-09-29 增加了多窗口限制,只能开启一个[实现单实例]
const getTheLock = app.requestSingleInstanceLock()
if (!getTheLock) {
  // app.quit()//柔性关闭
  app.exit() //强制关闭
  if (win)
    win.show();
} else {
  app.on('second-instance', (event, commandLine, workingDirectory) => {
    if (win) {
      if (win.isMinimized()) {
        win.restore()
      }
      win.focus()
    }
  })

  //---------------------------------------------------------------------------启动主体
  if (app.dock) {
    app.dock.hide(); //隐藏应用在 dock 中的图标。 OS-N
  }

  onAppReady() //开机自启动

  app.whenReady().then(createWindow);

  Menu.setApplicationMenu(null) //用途:设置应用菜单(在macOS中为应用菜单,Windows 和 Linux中为窗口顶部菜单) 注意: 这个API必须置于 app 模块的 ready之后.(未验证)
  //---------------------------------------------------------------------------
}

参考配置-3

const { app, BrowserWindow, ipcMain, Notification } = require('electron')
const path = require('path')
const Store = require('electron-store');
const store = new Store();
require('./updater.js')

let originWidth = 1920
let originHeight = 1080
let win
let newWin

const fnNotificationMain = (title) => {
    let NOTIFICATION_BODY = new Date().toLocaleTimeString()
    let data = { title: title, body: NOTIFICATION_BODY }
    let thisNotification = new Notification(data)
    thisNotification.show()
}

const createWindow = () => {
    win = new BrowserWindow({
        frame: false,//关闭原生导航栏
        width: originWidth,
        height: originHeight,
        minWidth: 1440,
        minHeight: 680,
        title: "xxxx", //默认窗口标题 -最低优先级
        backgroundColor: "#000", //窗口背景色 默认-#FFF
        autoHideMenuBar: true,
        webPreferences: {
            nodeIntegration: true,
            contextIsolation: false, //上下文隔离
        }
    })
    //判断环境 开发环境 or 生产环境
    //app.isPackaged 无效
    if (process.env.NODE_ENV != 'development') {
        win.loadFile(path.join(__dirname, "../dist/index.html"))
    } else {
        win.loadURL(`${process.env['VITE_DEV_SERVER_URL']}`)
    }
    // win.setMenu(null)
    // 显示菜单栏
    // win.setMenuBarVisibility(true)

    //--------------------------------------------------------------------------------------------导航栏功能
    //最大化
    ipcMain.on('Maximize', () => {
        win.maximize();
    })
    //最小化-还原
    ipcMain.on('Minimize', () => {
        win.unmaximize();
    })
    //隐藏
    ipcMain.on('Hide', () => {
        win.minimize()
    })
    //退出
    ipcMain.on('Quit', () => {
        app.exit()
    })
    //--------------------------------------------------------------------------------------------数据持久化
    ipcMain.on('setStore', (event, key, value) => {
        store.set(key, value)
    })
    ipcMain.on('getStore', (event, key) => {
        let value = store.get(key)
        if (value) {
            value = JSON.parse(value)
        }
        let ret = {
            key: key,
            data: value
        }
        event.sender.send('getStore', JSON.stringify(ret));
    })
    ipcMain.on('deleteStore', (event, key) => {
        store.delete(key)
    })
    //-------异常监听------webContents---------------------------------------------------------
    //当渲染进程-请求资源失败-时调用
    win.webContents.on('did-fail-load', () => {
        fnNotificationMain('xxxx:网络异常或本地资源请求失败')
        app.exit()//直接强制退出程序
    });

    //当渲染进程-崩溃-的时候调用
    win.webContents.on('crashed', () => {
        fnNotificationMain('xxxx:出现未知异常,导致崩溃')
        win.reload();//重新加载页面
    });

    //当渲染进程-重定向-的时候调用
    win.webContents.on('did-get-redirect-request', () => {
        fnNotificationMain('xxxx:异常重定向')
        app.exit()//直接强制退出程序
    });

    //当验证证书或url-失败-的时候发出事件
    win.webContents.on('certificate-error', () => {
        fnNotificationMain('xxxx:验证证书或访问服务器失败')
        app.exit()//直接强制退出程序
    });
    //---------------------------------------------------------------------------------------------
    //改变窗口大小
    ipcMain.on('onClickShowRight', (event, res) => {
        if (newWin) {
            if (res == 1) {
                newWin.setSize(newWinOriginWidth, newWinOriginHeight)
            } else {
                newWin.setSize(560, newWinOriginHeight)
            }
        }
    })
    //显示窗口
    ipcMain.on('newWinShowTrue', (event) => {
        if (newWin) {
            if (newWin.isMinimized()) {
                newWin.show()
            }
        } else {
            addView(win)
        }
    })
    //隐藏窗口
    ipcMain.on('newWinShowFalse', (event) => {
        if (newWin) {
            if (!newWin.isMinimized()) {
                newWin.minimize()
            }
        }
    })
    //销毁窗口-退出登录
    ipcMain.on('FedLogOut', (event) => {
        if (newWin) {
            newWin.destroy()
            newWin = null
        }
    })
    //刷新子窗口
    ipcMain.on('reloadView', () => {
        if (newWin) {
            newWin.destroy()
            newWin = null
            if (!newWin) {
                addView(win)
            }
        }
    })
    //隐藏子窗口
    ipcMain.on('HideBF', () => {
        if (newWin) {
            newWin.minimize()
        }
    })
}

let newWinOriginWidth = 1560
let newWinOriginHeight = 500
const addView = (win) => {
    setTimeout(() => {
        if (!newWin) {
            newWin = new BrowserWindow({
                width: newWinOriginWidth,
                minWidth: 560,
                height: newWinOriginHeight,
                minHeight: newWinOriginHeight + 20 - 175,
                maxHeight: newWinOriginHeight + 20,
                parent: win, //win是主窗口
                // frame: false,
                // resizable: false,
                backgroundColor: "#eee",
                title: "交易", //默认窗口标题 -最低优先级
                hasShadow: true, //窗口是否有阴影. 默认值为 true。
                hasShadow: false, //boolean (可选) - 窗口是否有阴影
                webPreferences: {
                    session: win.webContents.session,
                    nodeIntegration: true
                }
            })
            if (process.env.NODE_ENV != 'development') {
                newWin.loadFile(path.join(__dirname, "../dist/nested/index.html"))
            } else {
                newWin.loadURL(`${process.env['VITE_DEV_SERVER_URL']}nested/`)
            }
            newWin.setMenu(null)

            // 点击关闭按钮让应用隐藏
            newWin.on('close', (e) => {
                // 阻止窗口的关闭事件
                e.preventDefault();
                newWin.minimize()
            });
            //-------异常监听------webContents---------------------------------------------------------
            //当渲染进程-请求资源失败-时调用
            newWin.webContents.on('did-fail-load', () => {
                fnNotificationMain('交易:网络异常')
                onRestart()
            });

            //当渲染进程-崩溃-的时候调用
            newWin.webContents.on('crashed', () => {
                fnNotificationMain('交易:出现未知异常,导致崩溃')
                onRestart()
            });

            //当渲染进程-重定向-的时候调用
            newWin.webContents.on('did-get-redirect-request', () => {
                fnNotificationMain('交易:出现未知异常,导致崩溃')
                onRestart()
            });

            //当验证证书或url-失败-的时候发出事件
            newWin.webContents.on('certificate-error', () => {
                fnNotificationMain('交易:出现未知异常,导致崩溃')
                onRestart()
            });
        }
        /**
         * 刷新子窗口(交易窗口)
         */
        const onRestart = () => {
            if (newWin) {
                newWin.destroy()
                newWin = null
                if (!newWin) {
                    addView(win)
                }
            }
        }
    }, 500);
}


const getTheLock = app.requestSingleInstanceLock()
if (!getTheLock) {
    app.exit() //强制关闭
    if (win)
        win.show();
} else {
    app.whenReady().then(createWindow)
}

补充

[ 打包需要的zip下载 ](CNPM Binaries Mirror (npmmirror.com))