Electron + Vue 简单实现窗口程序(Windows)从零到一

【Electron+Vue】【一】开发跨平台桌面应用了解+构建 目录 基础综合介绍: 环境搭建: 安装Electron Electron应用程序分成三个基础模块:主进程、进程间通信和渲染进程。 【主进程】 【渲染进程】 【主进程与渲染进程通信】 src目录结构 main目录 渲染进程 qq音乐播放器 网易云音乐 其他基于 ELECTRON-VUE 的作品 基础综合介绍: 虽然B/S是目前开发的主流,但是C/S仍然有很大的市场需求。受限于浏览器的沙盒限制,网页应用无法满足某些场景下的使用需求,而桌面应用可以读写本地文件、调用更多系统资源,.. 阅读详情
AI摘要

本文适合有前端基础者,介绍用Electron和Vue开发桌面应用。涵盖基本环境、技术栈说明,环境搭建,分析开发包括自定义菜单、窗口加载组件、页面功能实现、图标与托盘设置,还介绍了打包成Electron程序和安装程序,最后进行安装测试并给出项目源码。

前言

想做一个桌面应用程序,一直没有找到简单快速可上手的框架。刚好有点前端的底子,就发现了Electron。关于Electron的介绍,请移步 https://www.electronjs.org/ 查阅。

简单来说,引用官网的话,Electron是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架,就是可以用前端代码开发跨平台的桌面应用程序(GUI);又因为有CSS的存在,所以可以引入很多的UI样式库或者手搓样式,让应用美观一些。

我相信很多朋友也可能喜欢写点桌面应用程序或者称为小工具,所以本篇适合想用前端来实现功能的伙伴,因此需要至少有html、css、js、Vue2基础。

废话少说,先看下完成图。

很简单的功能,就当作一个Demo吧。

其中关键在于如何组织Vue路由、创建子窗口、在 Vue Component 中如何与主进程通信(渲染进程与主进程)、如何设置图标等内容,东西不多不少,刚好够起步。当把这些配置好了就可以专注于应用功能的开发了。

共19000左右字的Demo教程,准备好纸巾,让我们开始吧!

目录

前言

零、项目源码

一、基本环境说明

二、技术栈(框架)

三、环境搭建

四、分析与开发

4.1 自定义菜单栏的实现

4.1.1 菜单显示

4.1.2 公共窗口创建

4.1.3 功能实现

4.2 窗口进阶,使某个窗口加载指定 Vue Component

4.2.1 安装并配置Vue路由

4.2.2 布局组件与子窗口

4.3 页面、功能窗口的实现

4.3.1 渲染进程与主进程通信

4.3.2 工具页面点击创建窗口实现

4.4 窗口图标与托盘显示

4.4.1 主窗口图标设置

4.4.2 子窗口设置图标

4.4.3 托盘设置

五、打包

5.1 打包Electron程序

5.2 使用inno setup 打包为安装程序

六、安装测试

七、项目源码

hyf/utils-hub-demo - 码云 - 开源中国https://gitee.com/fan-hongyu/utils-hub-demo

结语


零、项目源码

hyf/utils-hub-demo - 码云 - 开源中国icon-default.png?t=O83Ahttps://gitee.com/fan-hongyu/utils-hub-demo

一、基本环境说明

  • npm版本:8.6.0
  • node版本:v18.0.0
  • Vue/cli版本:@vue/cli 5.0.8
  • IDE:WebStorm

二、技术栈(框架)

  • Vue 2
  • ElementUI
  • Electron 13

三、环境搭建

为避免篇幅冗长(最终还是冗长了),基础环境搭建请移步我的另一篇文章

【桌面应用程序】Vue-Electron 环境构建、打包与测试(Windows)-优快云博客

环境搭建好了如图

注释掉自动安装 VUEJS_DEVTOOLS,否则可能导致启动缓慢。

src/background.js

启动示例

四、分析与开发

4.1 自定义菜单栏的实现

4.1.1 菜单显示

首先我们来实现菜单栏的显示。

在 src 下新建一个 menu.js 文件,用来实现菜单的逻辑。

src/menu.js

import {Menu} from "electron";

const template = [
    {
        label: '帮助',
        submenu: [
            {
                label: '关于',
                accelerator: 'CmdOrCtrl+H'
            }
        ]
    }

]

Menu.setApplicationMenu(Menu.buildFromTemplate(template))

代码解释:从 electron 将 Menu 导入进来;定义一个菜单栏模板,里面包含了一级菜单帮助,他有一个子选项 关于,快捷键为 Mac(Command)/ Windows(Ctrl) + H 。通过 Menu.setApplicationMenu传入一个Menu.buildFromTemplate(模板),即可实现自定义菜单栏实现。

现在菜单就定义完了,如何应用到窗口上呢?

在 backgroud.js 中引入即可。

src/backgroud.js

//引入自定义菜单
require('./menu')

启动测试,可以看到已经实现了。需要什么,就在 模板数组 中添加即可。

4.1.2 公共窗口创建

为了使点击 “关于” 选项,可以弹出新窗口。那么我们需要一个创建窗口的方法。

思考:应用可能会随着开发功能越来越多,那么每次创建窗口都要写一个单独的方法吗?这样会导致代码冗余,难以维护。

所以要创建一个公共的创建窗口的方法。

在 src 下新建一个 windowManager.js 文件,并填写以下代码。

src/windowManager.js

/** 窗口管理器 */
import {BrowserWindow} from "electron";

const path = require('path')

/**
 *
 * @param param 窗口参数对象
 */
const winURL = process.env.NODE_ENV === 'development'
    ? 'http://localhost:8080'
    : `file://${__dirname}/index.html`

let win;
function commonCreateWindow(param) {
    let win = new BrowserWindow({
        width: param.width || 400,
        height: param.height || 200,
        autoHideMenuBar: param.isAutoHideMenuBar || false,
        title: param.title || 'utils-hub',
        show: param.show || false,
        minWidth: param.minWidth || 400,
        minHeight: param.minHeight || 200,
        //如果没有传icon,那么就使用默认的图标,在 public/下
        icon: path.join(__dirname, param.iconName || 'app.ico'),
        minimizable: param.minimizable,
        maximizable: param.maximizable,
        resizable: param.resizable,
        closable: true,
        webPreferences: {
            preload: path.resolve(__dirname, './preload.js'),
            nodeIntegration: false,
            contextIsolation: true,
            enableRemoteModule: false, // 禁用 remote 模块以提高安全性
        }
    })
    // win.webContents.on('did-finish-load', () => {
    //     win.setTitle(param.title); // 设置窗口标题
    //     win.show();
    // })
    win.loadURL(`${winURL}` + "/#/sub-win/" + param.url);
    // console.log(win)
    win.once('ready-to-show', () => {
        if (param.isMax) {
            win.maximize();
        }
        win.setTitle(param.title)
        win.show();      // 显示窗口
    });



    // 打开开发者工具(仅在开发环境中启用)
    if (process.env.NODE_ENV === 'development') {
        win.webContents.openDevTools();
    }

    win.on('closed', () => {
        console.log('执行')
        win = null;
    });
}

export default commonCreateWindow;

让我们来解读以下以上代码:

  • 定义了一个winUrl,判断当前是否为开发环境。如果是,那么窗口就加载 http://localhost:8080(当启动electron时,开发环境会启动本地服务器,从而使页面加载);如果不是,那么通过file协议访问打包后的通过node构建路径(__dirname)来找到你的index.html,并让窗口加载此文件。由于我们的Vue实例是挂载到index.html中,所以可以显示Vue组件内容。(构建的文件将会被自动注入)

  • 定义了一个方法,接受窗口参数并创建窗口。调用 electron 的 new BrowserWindow 来实现一个窗口的创建。
  • 窗口参数解释
属性释义
width窗口宽度
height

窗口高度

autoHideMenuBar自动隐藏菜单栏
title窗口标题
show是否显示窗口
minWidth窗口最小宽度
minHeight窗口最小高度
icon窗口图标,没有则使用 public/favicon.icon
minimizable窗口是否可以被最小化
maximizable窗口是否可以被最大化
webPreferences.preload预加载脚本,实现渲染进程与主进程通信
webPreferences.nodeIntegration禁用 Node.js 集成以提高安全性
webPreferences.contextIsolation启用上下文隔离以进一步增强安全性
webPreferences.enableRemoteModule禁用 remote 模块以提高安全性
  • win.once('ready-to-show',()=>{}),在该方法中调用show方法,用于避免electron启动后白屏或闪屏现象。
  •  param.isMax,自定义的属性。窗口启动后是否立即最大化显示。
  •  win.webContents.openDevTools(); 该上下行代码表示,是否启动开发者工具,建议开发环境开启此选项,用于排查错误。
  • win.loadURL(`${winURL}` + "/#" + param.url); ,用于加载窗口的路径,此处使用Vue 路由,并且 mode 为 hash。参考 electron实现打开子窗口,窗口加载vue路由指定的组件页面_vue electron单独打开子窗口-优快云博客
  • win.on('closed', () =>{}),监听窗口关闭事件,将win置为null。
  • || xxx 为默认值。

 通过解读以上代码,大家应该了解了窗口的创建过程。下面我们创建预加载脚本。

在 src 下新建 preload.js

src/preload.js

console.log('预加载脚本执行')

打印测试。

当然现在还不能启动,因为启动了也会报找不到 preload.js。

配置 vue.config.js

vue.config.js

const {defineConfig} = require('@vue/cli-service')
module.exports = defineConfig({
    // 禁用eslint
    lintOnSave: false,
    transpileDependencies: true,
    //添加预加载脚本
    pluginOptions:{
        electronBuilder:{
            preload: {
                preload: 'src/preload.js'  // 确保路径正确
            },
        }
    }
})

这样,就定义好了。然后执行

npm run build

 好了,现在预加载脚本就配置好了。

4.1.3 功能实现

现在创建窗口的方法有了,预加载脚本也有了,是不是能实现点击 “关于” 选项,弹出窗口了呢?

测试一下,让我们回到 menu.js ,并在 关于 中添加 click 。

src/menu.js

import {Menu} from "electron";

//导入公共创建窗口的方法
import commonCreateWindow from "@/windowManager";

const template = [
    {
        label: '帮助',
        submenu: [
            {
                label: '关于',
                accelerator: 'CmdOrCtrl+H',


                //添加单击方法
                click: () => {
                    //构建 关于 窗口参数对象
                    const aboutWindowObject = {
                        //宽高使用默认值
                        //自动隐藏菜单栏
                        isAutoHideMenuBar: true,
                        title: '关于',
                        //默认隐藏
                        //最大宽度和最大高度使用默认值
                        //TODO 对于ICON,最后处理
                        //禁止最大化最小化
                        minimizable: false,
                        maximizable: false,
                        //禁止窗口调整大小
                        resizable:false,
                        //禁止启动后最大化
                        isMax: false,
                        url: 'about-win'
                    }
                    //调用公共创建窗口的方法
                    commonCreateWindow(aboutWindowObject)
                }
            }
        ]
    }

]

Menu.setApplicationMenu(Menu.buildFromTemplate(template))

启动程序测试。

启动后,发现 dist_electron目录中多了preload.js,这说明已经预加载脚本配置好了。

点击 帮助 -> 关于,或者使用快捷键 Ctrl+h。

此时可以看到,窗口已经成功弹出了,并且预加载脚本也执行了。

4.2 窗口进阶,使某个窗口加载指定 Vue Component

当我们打开了 “关于” 窗口后,发现了一个问题,这个窗口所显示的内容并不是我们想要的。

我需要让这个窗口加载我的Vue页面(About.vue)。

先看下我们的 windowManger.js 中的commonCreateWindow方法中的一段代码。

win.loadURL(`${winURL}` + "/#" + param.url);

我们在上面创建 “关于” 窗口时,aboutWindowObject 对象中没有 url属性。所以我们的窗口访问路径就是 http://localhost:8080/#(开发环境)

所以打开浏览器访问这个地址,显示出的页面和关于页面一致,这是没问题的。

好了,现在我们要加载自己的内容。

4.2.1 安装并配置Vue路由

//安装适用于Vue2的Vue-Router
npm install vue-router@3 --save

在 src 下,新建 router 目录,并且新建 index.js

路由文件创建好了,先不管。

4.2.2 布局组件与子窗口

我们要使用electron创建桌面应用,不仅有窗口,还有页面。为了将窗口与页面文件区分开,我们在 src 下新建 views 目录,并在里面新建 pages 和 windows。

  • src/views
    • pages
    • windows

那就可以直接写功能页面/窗口了吗?不,我们还需要一个布局组件。

在 src 下,我们可以发现有一个 components 目录,就它了。

在 src/components 下,新建 BaseLayout.vue 与 SubWindowLayout.vue。一个用于页面的布局,一个用于窗口的布局。

全部新建为 Options API 组件(只会Vue2)

里面放点啥呢?

简单来说,如果要是 SubWindowLayout.vue 中添加了一个按钮,那么如果你的嵌套路由中以此组件为布局组件后,所有依赖此路由的子路由的窗口都会有这个按钮。

添加 <router-view></router-view> 标签

  <div>
    <router-view></router-view>
  </div>

如需了解更多Vue-Router信息,请移步 Vue Router | The official Router for Vue.js 

接下来,我们创建 AboutWin.vue(为了与页面文件区分,所有窗口采用 xxxWin.vue 的命名方式)

在 src/views/windows/ 新建 AboutWin.vue。如果你喜欢更加清晰的层次,可以再新建一层about目录。

src/views/windowsAboutWin.vue

<script>
export default {
  name: "AboutWin"
}
</script>

<template>
  <div class="desc">
    <p>这是使用Electron开发的一款集成了多个工具的软件。</p>
    <p>版本:v1.0</p>
  </div>
</template>

<style scoped>
.desc {
  margin-top: 40px;
  font-size: 12px;
}
</style>

现在布局组件有了,关于 窗口也有了,那就配置下路由吧。

src/router/index.js

import Vue from 'vue';
import Router from 'vue-router';
/** 引入窗口布局组件 */
import SubWinLayout from "@/components/SubWindowLayout.vue";

/** 引入窗口  */
import AboutWin from "@/views/windows/AboutWin.vue";

Vue.use(Router)

const routes = [
    {
        path: '/sub-win',
        component: SubWinLayout,
        children: [
            {
                path: 'about-win', component: AboutWin
            }
        ]
    }
]

const router = new Router({
    mode: 'hash',
    routes
});
export default router;

简单解释一下:引入布局组件和关于窗口,定义路由规则。其中,mode必须为hash,使用history模式的话会找不到路径。参考

electron实现打开子窗口,窗口加载vue路由指定的组件页面_vue electron单独打开子窗口-优快云博客

 在 src/main.js 中引入路由。

src/main.js

import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import router from './router/index.js';
Vue.use(ElementUI)
Vue.config.productionTip = false

new Vue({
  router,
  render: h => h(App),
}).$mount('#app')

现在我们要稍微改动一下 src/menu.js 中的一段代码

src/menu.js

// 旧的
win.loadURL(`${winURL}` + "/#" + param.url);
// 新的
win.loadURL(`${winURL}` + "/#/sub-win/" + param.url);

由于我所有的子窗口路由都基于 sub-win 这个父路由,所以可以把这写死。如果有多个父路由,请保持旧有的即可,然后在传参时,传父+子(或者使用逻辑判断)。

现在我们修改 src/menu.js 中的菜单栏 关于 选项的click方法,添加 url 参数。

src/menu.js

url: 'about-win'

修改 src/App.vue,删除其他内容,添加 <router-view></router-view>

src/App.vue

<template>
  <div id="app">
    <router-view></router-view>
  </div>
</template>

<script>

export default {
  name: 'App',
}
</script>

<style>
</style>

启动测试。

4.3 页面、功能窗口的实现

好了,帮助 -> 关于 窗口弹出并显示指定内容已经完成了。

接下来实现一下页面上的功能。目前程序启动之后,页面是空白的,因为App.vue中仅有一个<router-view></router-view>,路由中没有指定根路由,所以什么都没有。

创建根页面

在 src/views/ 下,创建index.vue,用于首页。

src/views/index.vue

<script>
import Dashboard from "@/views/pages/Dashboard.vue";
import CommonUtils from "@/views/pages/CommonUtils.vue";

export default {
  name: "index",
  components: {CommonUtils, Dashboard},
  data() {
    return {
      activeIndex: 'dashboard',
      currentDate:''
    }
  },
  methods: {
    handleSelect(val) {
      console.log(val)
      this.activeIndex = val;
    },
    getCurrentDate(){
      const now = new Date();
      // 使用 toLocaleString 格式化日期和时间
      this.currentDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`;
    }
  },

  created() {
    this.getCurrentDate();

    setInterval(() => {
      this.getCurrentDate();
    }, 1000);

  }
}
</script>

<template>
  <div>
    <div><h3><i class="el-icon-time">&nbsp;&nbsp;{{currentDate}}</i></h3></div>
    <nav>
      <!--菜单拦内容-->
      <el-menu :default-active="activeIndex" class="el-menu-demo" mode="horizontal" @select="handleSelect">
        <el-menu-item index="dashboard"> <i class="el-icon-odometer"></i>控制板</el-menu-item>
        <el-menu-item index="common-utils"> <i class="el-icon-suitcase"></i>常用工具</el-menu-item>
      </el-menu>
    </nav>

    <!--  动态显示Component-->
    <br>
    <div>
      <Dashboard v-show="activeIndex==='dashboard'"></Dashboard>
      <CommonUtils v-show="activeIndex==='common-utils'"></CommonUtils>
    </div>

  </div>
</template>

<style scoped>

</style>

 简单解释一下:引入了两个自定义的组件,Dashboard.vue 和 CommonUtils.vue ,一个用于显示Dashboard,一个用于显示常用工具。通过ElementUI的  <el-menu></el-menu> 实现切换。

还有一个动态时间,实现每秒刷新。

以下是两个组件的内容

src/views/pages/Dashboard.vue

<script>
export default {
  name: "Dashboard",
  data(){
    return{
    }
  }
}
</script>

<template>
    <div>
      <p>Welcome to Utils-Hub.</p>
    </div>
</template>

<style>

</style>

Dashboard.vue 页面很简单,就一个p标签。 

src/views/pages/CommonUtils.vue

<template>
  <div>
    <div v-for="(val) in utilsList">
      <el-card class="box-card" @click.native="createUtilsWin(val.flag)">
        <div slot="header" class="clearfix">
          <h2><i :class="val.icon"></i> {{ val.title }}</h2>
        </div>
        <div class="descCls">
          {{ val.desc }}
        </div>
      </el-card>
    </div>

  </div>
</template>

<script>
export default {
  name: "CommonUtils",
  methods: {
    createUtilsWin(flag) {
      if(flag === "other"){
        this.$message.warning('暂无更多需求!')
        return false;
      }
      //创建窗口
      window.api.createUtilsWindow(flag);
    }
  },
  data() {
    return {
      utilsList: [
        {
          id: '1',
          flag: 'transfer',
          title: '大小写转换',
          desc: '键入字符,将其转换为大写字符或小写字符',
          icon: 'el-icon-refresh'
        },
        // {id: '2', flag: 'regexp', title: '正则表达式', desc: '使用简单的表达式完成复杂的需求', icon: 'el-icon-cpu'},
        // {id: '3', flag: 'cronExp', title: 'Cron表达式', desc: '? * * * * *', icon: 'el-icon-cpu'},
        // {
        //   id: '4',
        //   flag: 'TableNameExtract',
        //   title: '表名提取',
        //   desc: '仅适用于MySQL。选择标准的.sql格式文件,从中提取表名',
        //   icon: 'el-icon-top'
        // },
        // {
        //   id: '5',
        //   flag: 'uuidGenerator',
        //   title: 'UUID生成器',
        //   desc: '生成标准的uuid。',
        //   icon: 'el-icon-s-opportunity'
        // },
        {id: '6', flag: 'other', title: 'Other', desc: '需求加载中...', icon: 'el-icon-loading'},
      ]
    }
  }
}
</script>


<style scoped>
.box-card {
  width: 250px;
  height: 200px;
  float: left;
  margin-left: 43px;
  margin-top: 40px;
}

.box-card:hover {
  border: 1px solid #409EFF;
  cursor: pointer;
}

.descCls {
  font-size: 12px;
}
</style>

CommonUtils.vue 页面也很简单,让我们来解读一下:

定义了一个 utilsList 的工具列表数组,里面写的是一些常用的工具对象。使用v-for将其遍历到el-card 上,从而实现一个工具对象对应一个卡片。当点击一个卡片时,弹出对应工具卡片功能的窗口。

其中,需要注意的是,每个工具对象中,有一个 flag属性,用于窗口标识。

整段代码中,最关键的部分就是

window.api.createUtilsWindow(flag);

为便于理解,请看以下图示。

4.3.1 渲染进程与主进程通信

简单来说,就是Vue组件中定义一个函数,用于处理页面的事件;然后此函数再调用由preload.js中暴露出来的方法,通过window对象调用;preload.js中通过ipcRederer与主进程ipcMain通信。

对于ipcRenderer 有两种方式通信方法,对应ipcMain两种接收的方法

  • ipcRenderer.send 发送给主进程消息,不关心处理结果,类似void。主进程通过ipcMain.on来接收,随后处理。
  • ipcRenderer.invoke 发送给主进程调用方法,需要返回值。主进程通过ipcMain.handle来处理,随后返回返回值。

4.3.2 工具页面点击创建窗口实现

现在回过头来看看我们的页面怎么样了。

什么都没有。是的,如果有东西就奇怪了。

添加路由

src/router/index.js

import Vue from 'vue';
import Router from 'vue-router';
/** 引入窗口布局组件 */
import SubWinLayout from "@/components/SubWindowLayout.vue";
import BaseLayout from "@/components/BaseLayout.vue";

/** 引入窗口  */
import AboutWin from "@/views/windows/AboutWin.vue";


/** 引入页面 */
import index from '@/views/index.vue'
import Dashboard from "@/views/pages/Dashboard.vue";
import CommonUtils from "@/views/pages/CommonUtils.vue";

Vue.use(Router)

const routes = [
    {
        path: "/",
        component: BaseLayout,
        children: [
            {path: '', component: index},
            {path: 'dashboard', component: Dashboard},
            {path: 'common-utils', component: CommonUtils},
        ]
    },
    {


        path: '/sub-win',
        component: SubWinLayout,
        children: [
            {
                path: 'about-win', component: AboutWin
            }
        ]
    }
]

const router = new Router({
    mode: 'hash',
    routes
});
export default router;

启动测试。

直接点击 大小写转换 卡片将提示 createUtilsWindow 是未定义的。

现在打开我们的preload.js,并加入以下内容

src/preload.js

const {contextBridge, ipcRenderer} = require('electron')
contextBridge.exposeInMainWorld('api',{
    createUtilsWindow:(flag)=>{
        ipcRenderer.send('create-utils-window',flag)
    }
})

由于我们只需要创建一个窗口,并不需要返回值,所以使用ipcRenderer.send方法。

现在的情况是,渲染进程(Vue Component)中定义好创建窗口的方法了,预加载脚本(preload.js)也定义好了,那么现在就差主进程了。

项目中肯定不只有一个渲染进程与主进程的通信方法,所以我们把监听逻辑放在一个单独的文件中。

src 下新建 listener.js 。

src/listener.js

/** 主进程监听与处理 */
import {ipcMain} from 'electron'

ipcMain.on('create-utils-window',(e,flag)=>{
    console.log('主进程方法被调用了')
})

顺便提一句,如果觉得会有很多 ipcRenderer.send 和 ipcMain.on,两个文件来回跑怕打错的话,可以将其定义为常量。

伪代码

  • const CREATE_UTILS_WINDOW = 'create-utils-window';
    • ipcRenderer.send(CREATE_UTILS_WINDOW);
    • ipcMain.on(CREATE_UTILS_WINDOW);

继续,在background.js 中引入 listener.js,并且设置preload.js

//引入监听
require('./listener')
//预加载脚本
preload:path.resolve(__dirname,'./preload.js'),

启动测试。

可以看到,成功打印了。主进程在终端打印,渲染进程在控制台打印。

让我们修改一下listener.js监听方法的内容,实现窗口的创建。

src/listener.js

/** 主进程监听与处理 */
import {ipcMain} from 'electron'

import commonCreateWindow from '@/windowManager'

ipcMain.on('create-utils-window', (e, flag) => {
    // console.log('主进程方法被调用了')
    //创建窗口
    let windowObject = {}
    //判断flag
    switch (flag) {
        case "transfer":
            windowObject.width = 800;
            windowObject.height = 600;
            windowObject.isAutoHideMenuBar = true;
            windowObject.title = '大小写转换';
            windowObject.minWidth = 800;
            windowObject.minHeight = 600;
            //TODO ICON稍后处理
            windowObject.isMax = true;
            windowObject.url = 'transfer-win';
            break;
        default:
            break;

    }
    commonCreateWindow(windowObject)
})

定义好了,现在缺的是路由和窗口页面。

新建

src/views/windows/commonUtils/TransferWin.vue

<script>

export default {
  methods: {
    /** 复制内容到剪切板 */
    async copyToClipboard(text) {
      try {
        await navigator.clipboard.writeText(text);
        this.$message.success('内容已复制!')

      } catch (err) {
        this.$message.error('复制失败!')

      }
    },
    /** 转大写 */
    async handleTransferToUpper() {
      this.destText = this.srcText.toUpperCase();
       await this.copyToClipboard(this.destText)
    },
    /** 转小写 */
    async handleTransferToLower() {
      this.destText = this.srcText.toLowerCase();
      await this.copyToClipboard(this.destText)
    },
    /** 清空 */
    handleClear() {
      this.destText = '';
      this.srcText = '';
    },

  },
  name: "TransferWin",
  data() {
    return {
      srcText: '',
      destText: ''
    }
  }
}
</script>
<template>
  <div>
    <el-card class="box-card">
      <div slot="header" class="clearfix">
        <h2>大小写转换</h2>
      </div>
      输入小写或大写字符,将其转换为大写或小写。
    </el-card>


    <el-input
        class="common"
        type="textarea"
        :rows="6"
        placeholder="请输入需要转换的内容"
        v-model="srcText">
    </el-input>
    <div class="common">
      <el-button type="primary" icon="el-icon-refresh-right" @click="handleTransferToUpper">转大写</el-button>
      <el-button type="primary" icon="el-icon-refresh-left" @click="handleTransferToLower">转小写</el-button>
      <el-button type="danger" icon="el-icon-delete-solid" @click="handleClear">清空</el-button>

    </div>
    <el-input
        class="common"
        type="textarea"
        :rows="6"
        readonly
        placeholder="Console"
        v-model="destText">
    </el-input>
  </div>
</template>

<style scoped>
.common {
  margin-top: 20px;
}
</style>

功能实现很简单,就是输入字符,将其转为大写或小写,并自动复制到剪切板,可清空。

添加路由

import TransferWin from "@/views/windows/commonUtils/TransferWin.vue";

{path: 'transfer-win', component: TransferWin},

 启动测试。

到这功能基本就完事了,剩下还有一些小细节需要处理一下。

4.4 窗口图标与托盘显示

现在我们每个窗口的图标都是一样的,原因在于

没传图标参数。

4.4.1 主窗口图标设置

主窗口的图标设置会关联到应用的首页图标和任务栏图标。

为了使图标管理起来不那么混乱,我们需要一个文件来存储icon的名称。

在 src 下新建 iconManager.js

src/iconManager.js

/** 图标管理器 */
export const ICON_PATHS = {
    //应用程序的图标,首页左上角角标、任务栏角标、托盘角标
    APP_ICON: 'winIcon/app.ico',
    //关于窗口的图标
    ABOUT_WIN_ICON:'winIcon/about.ico',
    //大小写转换窗口左上角角标
    TRANSFER_WIN_ICON: 'winIcon/transfer.ico',
    //右下角角标 打开 icon
    OPEN_ICON:'winIcon/open.ico',
    //右下角角标 退出 icon
    EXIT_ICON:'winIcon/exit.ico',
}

此处需要注意,当是开发环境的时候,图标从 dist_electron 目录读取;当打包后,图标是从 public 目录获取。(因为__dirname)

所以需要在 public 和 dist_electron 目录中新建 winIcon 目录

将对应的图标放到这两个文件夹中。

打开 src/background.js,设置应用图标。

  src/background.js

import * as iconManger from '@/iconManager'


icon:path.resolve(__dirname,iconManger.ICON_PATHS.APP_ICON)

启动测试。

 

4.4.2 子窗口设置图标

子窗口图标只涉及左上角角标

打开

src/listener.js

import * as iconManger from '@/iconManager'

//TODO ICON稍后处理
windowObject.iconName = iconManger.ICON_PATHS.TRANSFER_WIN_ICON;

 启动测试。

4.4.3 托盘设置

悬浮提示文字

右键菜单

新建文件

src/tray.js

// tray.js
const { Tray, Menu } = require('electron');
const path = require('path');
import * as iconSupport from '@/iconManager'

let appTray = null;
let win = null; // 你需要从外部传递窗口实例

function createTray(app,mainWindow,nativeImage) {
    win = mainWindow;

    // 打开图标缩小设置
    const openResizedIcon = nativeImage.createFromPath(path.join(__dirname, iconSupport.ICON_PATHS.OPEN_ICON)).resize({
        width: 16,
        height: 16
    });

    // 退出图标缩小设置
    const exitResizedIcon = nativeImage.createFromPath(path.join(__dirname, iconSupport.ICON_PATHS.EXIT_ICON)).resize({
        width: 16,
        height: 16
    });

    // 系统托盘右键菜单
    let trayMenuTemplate = [
        {
            label: '打开',
            icon: openResizedIcon,
            click: function () {
                win.show();
                win.maximize();
            }
        },
        {
            label: '退出',
            icon: exitResizedIcon,
            click: function () {
                app.quit();
                app.quit();
            }
        }
    ];

    appTray = new Tray(path.join(__dirname, iconSupport.ICON_PATHS.APP_ICON));
    const contextMenu = Menu.buildFromTemplate(trayMenuTemplate);
    appTray.setToolTip('utils-hub');
    appTray.setContextMenu(contextMenu);

    // 点击托盘图标时显示窗口
    appTray.on('click', function () {
        win.show();
        win.maximize();
    });
}

export default createTray;

解读一下:

首先将两个图标进行缩放,因为太大了不好看;然后定义了一个托盘菜单模板,有 打开 和 退出功能;最后设置托盘图标,将菜单应用上。当单击菜单时,显示程序。创建托盘的方法需要三个参数,应用实例,窗口以及一个用于处理图片的对象。

我们在 

src/background.js中调用一下这个方法。

src/background.js

import { app, protocol, BrowserWindow,nativeImage } from 'electron'

import createTray from '@/tray'

createTray(app,win,nativeImage);

启动测试。

托盘就设置好了,但是现在有一个问题就是当我们点击主窗口的关闭时,程序就退出了,一般的应用可能会询问用户是退出还是驻托盘。

咱们这里不搞那么复杂了,就默认关闭按钮是驻托盘,想要退出的话使用托盘右键退出。

修改 

src/background.js 的逻辑

首先将 win 从 const 改为 let

 

src/background.js

// 部分代码
let win = new BrowserWindow({})

    
win.on('close', (e) => {
    if (win.isMinimized()) {
      win = null;
    } else {
      e.preventDefault();
      win.minimize();
      win.hide();
    }
})

解读一下:监听关闭事件,如果窗口当前是最小化的,那么就置为null;如果不是,那么就最小化并隐藏(不显示窗口)

启动测试。

可以看到窗口虽然已经关闭了,但是托盘区还是有程序存在的。单击或右键打开,都可以使窗口重新显示。

到现在,开发工作完成了。

五、打包

5.1 打包Electron程序

首先进行打包配置

vue.config.js

builderOptions: {
	appId: 'com.utils.hub',
	productName: 'UtilsHubDemo',
	directories: {
		output: 'build'
	},
	win: {
		// 应用图标,这里要确保图标文件存在且路径正确,一般为.ico 格式
		icon: 'public/winIcon/app.ico',
		// 目标架构,可以是 x64、ia32 等,根据实际需求选择
		target: [
			{
				target: 'nsis', // 使用 NSIS 打包
				arch: ['x64']   // 指定架构
			}
		]
	},
}

执行吗?

我先执行试试(此处不贴图,打包n次中)

一番操作,发现两个问题。

  • 关于窗口没有图标,忘设置了。

import * as iconManager from '@/iconManager'

iconName:iconManager.ICON_PATHS.APP_ICON,
  • ElementUI 图标没有了

参考 [已解决]electron-builder vue 打包后iconfont/element-ui字体图标不显示问题_vue打包后element图标没有了-优快云博客

在项目 public 目录下新建 element-ui 目录

然后打开项目的node_modules目录,搜索element-ui,将其中的theme-chalk目录拷贝到 public/element-ui 下

修改

public/element-ui/theme-chalk/index.css

搜索fonts,在前面都添加  ./

修改前

 修改后

修改 

public/index.html

加入以下内容

      <!-- 添加此行 -->
    <link rel="stylesheet" href="<%= BASE_URL %>element-ui/theme-chalk/index.css">

至此,问题应该都会解决了,重新打包测试。

完成!

5.2 使用inno setup 打包为安装程序

当electron打包完成后,目录下会自动生成一个 Setup.exe,双击即可安装。

但是没法选择目录什么的。(应该是需要配置nsis脚本,奈何不会呀!)

所以我们看见上面还有一个文件夹,顾名思义应该是未打包的(直译)。那么我的理解就是,把这个文件夹拿到哪,里面的程序都能正常运作(Windows平台)。

所以下载 Inno Setup

可以使用图形化的方式进行打包过程。

 下载

默认没有中文(不是软件的中文,是用于你的应用安装时的中文)。

需要下载中文包

选择简体中文下载即可。

保存下载语言包之后(注意后缀名不要是txt),打开inno setup的安装目录,放到language目录下即可。

 开始打包

这里选择前边说的win-unpacked/下面的UtilsHubDemo.exe

此处需要注意,因为仅有这一个exe是跑不起来,它还需要win-unpacked目录中的文件支持。所以我们在其他地方新建一个文件夹 test。

然后将win-unpacked中除 UtilsHubDemo.exe 之外的文件拷贝到 test 文件夹中。

继续打开inno setup操作

选择刚刚的 test 文件夹

该应用没有相关的后缀文件

建议勾选最后一个选项,勾选之后,用户安装的时候会询问

仅为我安装还是为所有用户安装(为所有用户安装需要管理员权限)。

默认即可

我这里选择仅支持简体中文。

输出目录、输出文件名、图标

保存脚本文件

六、安装测试

打开两个虚拟机,Win10和Win7测试效果。

Win10

Win7

七、项目源码

hyf/utils-hub-demo - 码云 - 开源中国icon-default.png?t=O83Ahttps://gitee.com/fan-hongyu/utils-hub-demo

结语

至此,就从开发到打包到安装全都实现啦!由于笔者水平有限,其中涉及性能、安全、高级语法的问题可能未进行过多说明,望包涵!:)

非常感谢您能看到最后!

希望可以帮到你!

最后,感谢开源!

手把手教你 Electron + Vue 搭建前端桌面应用 一、在使用 Electron 之前我们要了解 Electron 是什么? Electron 官网地址 点此 : electron 官方地址 Electron 相当于一个浏览器的外壳 , 我们将 编写的 HTML , CSS , Javascript 网页程序 嵌入进 Electron 里面 以便于在桌面上进行运行。 通俗来讲它就是一个软件 , 如 QQ 、网易云音乐、优酷视频 等等。功能至强大 超乎你的想象 二、开发环境的准备 安装 Node.js 坏境 Node.js 官网地址 点此 node 阅读详情

相关推荐

【electron + vue2】构建桌面端、打包。

Electron 是一个使用 JavaScript, HTML 和 CSS 构建跨平台桌面应用程序的开源库。能够运行在 Windows、macOS 和 Linux 上。集成现代Web API:可以访问现代 Web API,如地理位置、通知等。复杂界面设计:利用 HTML 和 CSS 的强大功能,可以创建复杂的用户界面。插件和扩展:可以集成第三方插件和扩展,增强应用功能。像流行的【微软开发的免费、开源的代码编辑器。微软的团队协作平台。Notion】一个组织笔记、任务和项目的协作工具。

2301_76692187的博客 6606

Electron + Vue3 开发桌面应用

Electron + Vue3 开发桌面应用

Hyung的博客 4万+

【Electron】vue+electron实现无边框窗口

无边框窗口是不带外壳(包括窗口边框、工具栏等),只含有网页内容的窗口。对于一个产品来讲,桌面应用带边框的很少,因为丑(我们的UI觉得–与我无关-.-)。因此我们就来展开说下,在做无边框窗口时候需要注意的事项以及我踩过的坑。

小王的技术库 3327

手把手教Electron+vue的使用

.现如今前端框架数不胜数,尤其是angular、vue吸引一大批前端开发者,在这个高新技术快速崛起的时代,自然少不了各种框架的结合使用。接下来是介绍electron+vue的结合使用。 2.Electron是什么?? 对于我来说Electron相当于一个浏览器的外壳,可以把网页程序嵌入到壳里面,可以运行在桌面上的一个程序,可以把网页打包成一个在桌面运行的程序,通俗来说就是软件,比如像Q...

weixin_43816501的博客 4353

2019 Electron+Vue+Ant Design Vue仿网易云音乐windows客户端实战分享

特点 拖拽播放 桌面歌词 mini模式 自定义托盘右键菜单 任务栏缩略图,歌曲操作 音频可视化 自动/手动检查更新 Nedb数据库持久化 自定义安装路径,安装界面美化 浏览器中启动客户端 Travis CL,AppVeyor自动构建 换肤,下载,本地歌曲匹配,网络变化桌面通知,分享歌曲/歌单/MV/视频等到QQ空间 登录,私人Fm,歌单,专辑,歌手,排行榜,MV,视频,评论,搜索,用户,动态,...

weixin_45013937的博客 6493

MacOS中使用Electron构建Vue项目在Windows环境的安装包

macOS下使用Electron构建windows安装包的一次最简单的实现

weixin_43558927的博客 4452

Electron + Vue3开发桌面应用

Electron 是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。嵌入和到 二进制的 Electron 允许您保持一个 JavaScript 代码代码库并创建 在Windows上运行的跨平台应用 macOS和Linux——不需要本地开发经验。

F520Hz的博客 2万+

Vite+Vue+TS使用Electron 极限踩坑,Webstorm WSL,Linux下build Electron Windows

Vue打包Windows,Vite+Vue+TS使用Electron 极限踩坑,Webstorm WSL,Linux下build Electron Windows 方案

qq_44652876的博客 1488

Vue3 Vite electron 开发桌面程序

Electron是一个跨平台的桌面应用程序开发框架,它允许开发人员使用Web技术(如HTML、CSS和JavaScript)构建桌面应用程序,这些应用程序可以在Windows、macOS和Linux等操作系统上运行。Electron的核心是Chromium浏览器内核和Node.js运行时环境。Chromium。

qq1195566313的博客 4万+

反相器(inverter)和缓冲器(buffer)---附加与非门原理图

下面是对反相器(Inverter)和缓冲器(Buffer)从面积、驱动能力、时序等多个角度的详细对比,列出的是一般情况下的比较,具体情况可能会受到工艺节点、设计库和特定设计要求的影响。缓冲器(Buffer)是数字电路中常见的逻辑门之一,用于增强信号的驱动能力,而不改变信号的逻辑状态。如下图所示的CMOS反相器,由一个NMOS和PMOS组成,栅端gate相连作为输入端Vin,漏端相连作为输出端Vout,NMOS的源端接地,PMOS的源端接电源VDD.与反相器不同,缓冲器的输出与输入保持相同的逻辑状态。

hzmscut的博客 1万+

基于任务的后勤智能体:三段构建与演进设计探究

这是系统智能化的核心难点。例如,对于一个“加强连级滩头阵地防御”子任务,系统会根据防御时长、预期敌军攻击强度、地形等因素,计算出所需的弹药(不同弹种的具体数量)、构筑工事的器材、人员的给养和饮水量、以及医疗救护的药品和血包数量。首先进行分词、词性标注等基础处理,然后利用在军事语料库上训练的命名实体识别(NER)模型,提取出关键实体,如任务类型(“夺控作战”)、作战对象(“Y岛”)、时间约束(“D日H时”、“24小时内”)、成功标准(“建立稳固滩头阵地”)、保障重点(“弹药充足”、“及时救治”)等。

weixin_49042442的博客 1088

electron-vue-windows

安装electron-vue-windows插件 yarn add electron-vue-windows -D

catgray的博客 2621

本体研究综述(转)

(这篇文章是从http://imarine.blog.163.com/中转载过来的,个人觉得对本体的介绍比较全面!)本体综述本体作为一种能在语义和知识层次上描述领域概念的建模工具,其目标是捕获相关领域的知识,确定该领域内共同认可的词汇,通过概念之间的关系来描述概念的语义,提供对该领域知识的共同理解。语义Web(the Semantic Web)是在本体理论基础之上对现有Web所进行的扩展[

zhenshideni611的专栏 1838

electron+vue实现桌面端程序

实现了一个基于vue框架结合Electron框架的window桌面端程序。

gzmhxsn的博客 1295

Python遥感技术实战:NDWI与MNDWI水体提取算法对比与优化

本文详细对比了NDWI与MNDWI两种水体提取算法在Python遥感技术中的应用。通过实战代码演示,分析了二者在自然与城镇场景下的表现差异,并重点介绍了阈值优化与后处理技巧,旨在帮助读者掌握高效、精准的水体提取方法,提升遥感数据分析能力。

f6g7h8i9j的博客 498

【ADCU详解】PMIC电源管理集成电路详解

自动驾驶域控制器(ADCU)中的电源管理集成电路(PMIC)是高度集成的关键部件,负责为计算核心、传感器等提供高效稳定的多路电源。其核心特点包括:符合车规AEC-Q100标准、支持功能安全ASIL等级、精确的时序控制及强抗干扰能力。PMIC由开关稳压器、LDO、时序控制器、监控保护电路等模块组成,通过分层电源树框架实现电能分配。设计需考虑效率、功率密度、噪声性能等关键维度,底层基于Buck转换器和LDO原理工作。使用中需满足严苛的车规环境要求,并通过静态/动态测试及故障注入测试进行验证,确保系统可靠性和安全

qq_40844444的博客 841

如何使用electron将Vue项目打包为本地exe文件

Electron是一个开源框架,可借助JavaScript、HTML和CSS构建跨平台桌面应用。它结合Chromium和Node.js,开发者能复用Web技术。众多知名应用如VS Code、Slack都用它开发,具备高效、灵活的特点。是一个为Electron应用量身打造的打包工具。它能把Electron应用打包成可在不同操作系统(如Windows、Mac、Linux)上直接安装运行的格式。支持自动更新、签名等功能,操作简单且功能强大,能大幅提升应用发布效率。依赖版本electron28.1.0。

振华OPPO的博客世界 3674

ThingsBoard 3.1.1版本在window本地运行之TB-Gateway服务搭建(三)

TB-gateway服务是TB不可或缺的部分,很多的协议都需要TB-gateway来做分类处理,今天我们就来做TB-Gateway服务搭建。

晚点遇见你,余生都是你! 2474

electron-vue 脚手架实战指南:用 vue-cli 快速搭建 Electron + Vue 桌面应用

本文档是 electron-vue 项目在仓库中的官方介绍与快速上手文档,核心目标是消除"手动搭建 Electron + Vue 应用"的重复劳动:它以 vue-cli 模板的形式提供了一套开箱即用的工程骨架,集成 webpack、vue-loader、vue-router、vuex 等最常用插件,并内置 electron-packager / electron-builder 打包、单元测试与端

gitblog_00843的博客 1042

【愚公系列】《AIGC辅助软件开发》041-如何使用 AI技术提升工作效率:AI辅助软件架构师实践

在数字化转型的浪潮中,软件架构师作为技术团队的核心,承载着系统设计与架构优化的重要责任。然而,随着项目复杂度的增加和技术环境的变化,架构师面临着前所未有的挑战。幸运的是,人工智能(AI)技术的迅猛发展为这一行业带来了新的机遇,帮助架构师提升工作效率、优化决策过程,并改善团队协作。本文将深入探讨AI如何辅助软件架构师的实际工作,具体分析AI在需求分析、架构设计、代码生成、测试和优化等环节的应用。

愚公智库 15万+

水声信号处理开源算法库最新进展

欢迎关注本专栏,一起探索新世界大家好,我是ZhuChunSHU。985院校硕士毕业,现担任算法研究员一职,热衷于深度学习算法研究与应用。曾获得阿里云天池比赛第三名,CCE比赛第五名,科大讯飞Q比赛第六名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法行业就业。希望和大家一起成长进步。今天继续我们的专栏分享!!!!!!!!!!!!!!!!!!!!水声信号处理是海洋科学与工程领域的核心组成部分,对于水下通信、目标探测、海洋环境监测以及生物声学研究至关重要。

u012133341的博客 1254

在 Electron + Vue3 + TypeScript 应用中实现双击窗口放大功能

在 Electron + Vue3 + TypeScript 应用中实现双击窗口放大功能,需要在主进程中监听窗口的双击事件,并控制窗口的最大化状态。下面是完整实现方案:```typescript:src/main/index.ts import { app, BrowserWindow, ipcMain } from 'electron'; import path from 'path';let mainWindow: BrowserWindow | null = null;function createW

chaoxxggg的博客 1206

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

【桌面应用程序】Vue-Electron 环境构建、打包与测试(Windows)

Vue2 Electron 环境搭建、打包测试。

一zhi小白的博客 2123

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

electron-vue 安装环境、构建+打包(mac和windows)这一篇就够了

准备环境: 1、nodejs 1、安装cnpm、yarn npm install -g cnpm --registry=https://registry.npm.taobao.org npm install -g yarn 2、修改yarn的镜像 yarn config set registry https://registry.npm.taobao.org -g 3、设置electron的yarn镜像(要不然安装依赖会出错) 打开C盘下的用户目录(mac就在用户目录下,需要显示隐..

qq_32244819的博客 6945

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

window10 Electron+Vue 最简单的原生项目创建,从环境搭建到打包

window10 Electron+Vue 最简单的原生项目创建,从环境搭建到打包

weixin_47219632的博客 2258

【vue】electron打包exe

vue项目打包成exe可执行文件 在学习的过程中,发现关于 Electron ,常见的有两种打包方法: 将自己的 vue 项目打包,放到官方的 demo 文件中,改变打包路径 在自己的 vue 的项目中引入插件,然后打包 经过一番尝试,我决定采用第一种,原因是:特!别!简!单! 下面详细介绍下第一种方法: 打包成exe可执行文件 1、把electron的官方例子扒下来,下面简称A,留着待用: git clone https://github.com/electron/electron-quick-sta

Rex的博客 1664
上一篇: 【桌面应用程序】Vue-Electron 环境构建、打包与测试(Windows)
一zhi小白
博客等级 码龄7年 126粉丝 19原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值