[TOC]

前言

Electron是一个基于Chromium和 Node.js,可以使用 HTML、CSS和JavaScript构建跨平台应用的技术框架,兼容 Mac、Windows 和 Linux。受限于浏览器的沙盒限制,网页应用无法满足某些场景下的使用需求,而桌面应用可以读写本地文件、调用更多系统资源,基于这些特性,催生我们开发报税系统桌面端,可以实现登录后单点维护登录态等等。
本文不涉及Electron、vue、antd的开发教程,仅以实现搭建桌面应用从0到1为目的,如深入学习Electron、vue、antd,请访问官方文档:
Electron: tps://electronjs.org/
vue: https://cn.vuejs.org/
vue-cli: https://cli.vuejs.org/zh/
antD: antdv.com/docs/vue/introduce-cn/

目录

1 创建项目
1.1 使用cnpm加速下载
1.2 为什么不使用SimulatedGREG/electron-vue
1.3 安装/升级vue-cli3
1.4 创建vue项目
1.5 自动安装electron 与 ant-design-vue
1.6 手动安装electron 与 ant-design-vue
1.7 编译并启动APP
2 配置项目
2.1 配置ESLint代码格式检查工具
2.2 配置vue
3 项目基本设定
3.1 主进程和渲染进程简介
3.2 APP窗口大小
3.3 取消跨域限制
3.4 取消菜单栏
3.5 设置APP窗口图标
3.6 设置APP窗口标题栏名称
3.7 项目根目录下创建或修改vue.config.js
3.8 项目使用ant-design-vue
3.9项目引入自动更新与前端vue页面修改
4 build最终产品
4.1 设置APP及安装包图标
4.2 设置APP名称
4.3 更新软件地址设置(自己服务文件目录位置)
4.4 打包APP
4.5 新版自动更新
5 关于项目开发的一些经验
5.1 src目录结构参考
5.2 从Electron旧版(4.x)升级到最新(9.x)
5.3 注册快捷键打开devTools
5.4 可能遇到的问题
5.5参考文档

1 创建项目

1.1 使用cnpm加速下载

npm有时下载速度很慢,可以安装cnpm,从国内淘宝镜像下载,执行以下命令:

npm install -g cnpm --registry=https://registry.npm.taobao.org

以后npm直接替换成cnpm使用。

※注:习惯使用yarn 也可以先切换及安装

全局安装yarn
npm install -g yarn

设置源
yarn config set registry https://registry.npm.taobao.org -g

1.2 为什么不使用SimulatedGREG/electron-vue

SimulatedGREG/electron-vue已经很久没有更新了,而且其生成的工程结构并不是vue-cli3。所以放弃使用。

1.3 安装/升级vue-cli3

先执行以下命令,确认下本地安装的vue-cli版本:

vue -V

在写本文时,我使用的是4.2.3版本。
如果本地使用的是vue-cli2.x或者更早版本,可先卸载:

cnpm uninstall vue-cli -g

※注:vue-cli3使用了新的npm包名,与旧版本不一样。 如果还没有安装vue-cli3,先执行以下命令安装:

cnpm install @vue/cli -g

如果你已安装vue-cli3,但不是最新版本,可执行以下命令升级:
(我这里使用cnpm并没有完成升级,所以使用了npm)

cnpm update @vue/cli -g

cnpm方式升级不成功,可换回npm来更新。
npm update @vue/cli -g

1.4 创建vue项目

在gitbash下找到你所存放项目目录,执行以下命令,创建vue项目:
(这里把项目名称定为electron-vue-v3)

vue create electron-vue-v3

会出现以下选项(如果熟悉此步骤可跳过本节内容):

Vue CLI v4.2.3
? Please pick a preset: (Use arrow keys)
  default (babel, eslint) 
> Manually select features

选择“Manually select features” (自定义安装)。

※注:如果你的gitbash 不支持上下方向键选择,可以直接输入“2”,表示选择序号为2的选项。第一个是默认安装,经测试默认安装也可以,但后面需要手动安装回一些插件及依赖。 ※注:gitbash怎么支持上下方向键选择,网上说升级git即可,本人偿试过,无效,经查可输入数字来选择,就不纠结。

? Check the features needed for your project: (Press <space> to select, <a> to t
oggle all, <i> to invert selection)
❯◉ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◉ Router
 ◉ Vuex
 ◉ CSS Pre-processors
 ◉ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing

这里选择了常用的模块,请根据实际需求进行选择。

※注:如果你的gitbash 不支持上下方向键选择,可以先直接按回车,后面再按需要安装模块。

? Use history mode for router? (Requires proper server setup for index fallback 
in production) (Y/n)  n

如果选择了router,这里会询问是否使用history模式。
vue-router 默认使用hash模式(即通过url#hash来跳转页面),使用URL的hash来模拟一个完整的 URL,当URL改变时,页面不会重新加载。 如果使用history,URL就像正常的url,例如 http://yoursite.com/user/id ,比较好看。但是还需要后台配置支持。
这里我们选择“n”。

? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported 
by default): (Use arrow keys)
  Sass/SCSS (with dart-sass) 
  Sass/SCSS (with node-sass) 
  Less 
❯ Stylus

选择CSS预处理模块,这里我们使用“Stylus”。

? Pick a linter / formatter config: (Use arrow keys)
  ESLint with error prevention only 
  ESLint + Airbnb config 
❯ ESLint + Standard config 
  ESLint + Prettier

选择ESLint代码格式检查工具的配置,选择“ESLint + Standard config”,标准配置。

? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i
> to invert selection)
❯◉ Lint on save
 ◯ Lint and fix on commit

Line on save表示在保存代码的时候,进行格式检查。
Lint and fix on commit表示在git commit的时候自动纠正格式。
这里只选择“Lint on save”。

? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? 
  In dedicated config files 
❯ In package.json

这里问把 babel, postcss, eslint 这些配置文件放哪?
In dedicated config files 表示独立文件
In package.json 表示放在package.json里
这里选择“In package.json”。

? Save this as a preset for future projects? (y/N) N

是否为以后的项目保留这些设置?选择“N”。
然后耐心等待项目安装完成。

1.5 自动安装electron 与 ant-design-vue

※注:此过程可能需要科学上网,由于直接从国外镜像下载较慢,可能需要等待很漫长的时间。如果你对自己的网速没有超强自信,请跳过本节,前往1.6小节手动安装。

安装electron

进入到项目根目录,执行:

vue add electron-builder

在安装过程中,很可能会卡在这一步不动了:

node ./download-chromedriver.js

没关系,我们先强制结束掉。再执行一次vue add electron-builder,然后就可以顺利通过了。
接下来出现配置选项:

? Choose Electron Version (Use arrow keys)
  ^7.0.0 
  ^8.0.0 
❯ ^9.0.0

选择Electron最新版本。选择 “^9.0.0”。
然后耐心等待安装完成。如果中间出现错误中断了,请重复此步骤。
安装完成后会自动在src目录下生成background.js并修改了package.json。 ※注:由于网络原因,如果中间出现过中断失败,再次重新安装可能会很快完成,但实际上electron可能并未安装完全。建议完成以上步骤后,直接删除项目根目录的node_modules/,并且执行cnpm install,从国内镜像重新安装所有依赖包。

安装ant-design-vue

进入到项目根目录

cnpm add ant-design-vue
or
yarn add ant-design-vue

1.6 手动安装electron 与 ant-design-vue

※注:如果已经通过1.5章节的操作,请直接跳过本小节。

修改package.json,添加以下9行:

...
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
+   "electron:build": "vue-cli-service electron:build",
+   "electron:serve": "vue-cli-service electron:serve",
+   "postinstall": "electron-builder install-app-deps",
+   "postuninstall": "electron-builder install-app-deps"
  },
+ "main": "background.js",
  "dependencies": {
+   "@ant-design-vue/pro-layout": "^0.3.6-0",
+   "ant-design-vue": "^1.6.4",
    "core-js": "^3.6.5",
    "vue": "^2.6.11",
    "vue-router": "^3.4.3",
    "vuex": "^3.5.1"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "~4.5.0",
    "@vue/cli-plugin-eslint": "~4.5.0",
    "@vue/cli-service": "~4.5.0",
    "@vue/eslint-config-standard": "^5.1.2",
    "babel-eslint": "^10.1.0",
+   "electron": "^9.0.0",
    "eslint": "^6.7.2",
    "eslint-plugin-vue": "^5.0.0",
    "stylus": "^0.54.5",
    "stylus-loader": "^3.0.2",
+   "vue-cli-plugin-electron-builder": "~2.0.0-rc.4",
    "vue-template-compiler": "^2.6.10"
  },
  ...

新建src/background.js

在src目录下新建background.js,复制以下代码:

'use strict'
import { app, protocol, BrowserWindow } from 'electron'
import {
  createProtocol,
  installVueDevtools
} from 'vue-cli-plugin-electron-builder/lib'
const isDevelopment = process.env.NODE_ENV !== 'production'
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([{
  scheme: 'app',
  privileges: {
    secure: true,
    standard: true
  }
}])
function createWindow () {
  // Create the browser window.
  win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true
    }
  })
  if (process.env.WEBPACK_DEV_SERVER_URL) {
    // Load the url of the dev server if in development mode
    win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
    if (!process.env.IS_TEST) win.webContents.openDevTools()
  } else {
    createProtocol('app')
    // Load the index.html when not in development
    win.loadURL('app://./index.html')
  }
  win.on('closed', () => {
    win = null
  })
}
// Quit when all windows are closed.
app.on('window-all-closed', () => {
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit()
  }
})
app.on('activate', () => {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (win === null) {
    createWindow()
  }
})
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', async () => {
  if (isDevelopment && !process.env.IS_TEST) {
    // Install Vue Devtools
    try {
      await installVueDevtools()
    } catch (e) {
      console.error('Vue Devtools failed to install:', e.toString())
    }
  }
  createWindow()
})
// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
  if (process.platform === 'win32') {
    process.on('message', data => {
      if (data === 'graceful-exit') {
        app.quit()
      }
    })
  } else {
    process.on('SIGTERM', () => {
      app.quit()
    })
  }
}

以上代码是1.5小节使用自动化方式安装后生成的。

安装依赖包

在项目根目录执行,安装全部依赖包:

cnpm install

如果安装过程中报错:Error: post install error, please remove node_modules before retry!可以忽略,不影响后续使用。

1.7 编译并启动APP

vue-cli3界面下启动

gitbash命令行下执行

vue ui

导入我们刚刚创建的项目,导入后在任务中,选择 electron:build 图标后再选择运行。
image.png

命令行工具下启动

执行以下命令,开始编译APP,并启动开发环境APP:

npm run electron:serve

首次启动可能会等待很久,出现以下信息:

INFO  Launching Electron...
Failed to fetch extension, trying 4 more times
Failed to fetch extension, trying 3 more times
Failed to fetch extension, trying 2 more times
...

这是因为在请求安装vuejs devtools插件。需要科学上网才能安装成功。如果不能科学上网也没关系,耐心等待5次请求失败后会自动跳过。

编译成功后界面

编译成功后,就会出现开发环境的APP了。
image.png
启动界面

2 配置项目

2.1 配置ESLint代码格式检查工具

ESlint可以高效的检查代码格式,让参与项目的所有工程师都能保持统一的代码风格。其检测精度甚至可以精确到是否多一个空格或者少一个空格。代码格式的统一对提高团队的协同开发效率有很大的帮助,特别是对有代码洁癖的工程师。
在项目根目录下创建.eslintrc.js (注意文件名前面有个“.”)
请粘贴以下代码:

module.exports = {
  root: true,
  env: {
    node: true
  },
  'extends': [
    'plugin:vue/essential',
    '@vue/standard'
  ],
  rules: {
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
    // 不检测语句末尾的分号
    'semi': ['off', 'always'],
    // 强制缩进为2个空格
    'indent': ['error', 2],
    // 关闭函数名称跟括号之间的空格检测
    'space-before-function-paren': 0,
    // 忽略大括号内的空格
    'object-curly-spacing': 0
  },
  parserOptions: {
    parser: 'babel-eslint'
  }
}

这里说明下关于indent缩进的配置,要配合项目根目录下的.editorconfig

[*.{js,jsx,ts,tsx,vue}]
indent_style = space   <--这里定义缩进类型是空格还是tab
indent_size = 2        <--这里需要与.eslintrc.js的indent对应
trim_trailing_whitespace = true
insert_final_newline = true

.editorconfig 用于IDE自动格式化代码.eslintrc.js 用于ESlint检测 以上是我常用的配置。如果你有更多的配置需求,可参阅:
https://cloud.tencent.com/developer/doc/1078

2.2 配置vue

在项目根目录下创建vue.config.js,粘贴以下代码:

const path = require('path');
function resolve (dir) {
  return path.join(__dirname, dir);
}
module.exports = {
  publicPath: './',
  devServer: {
    // can be overwritten by process.env.HOST
    host: 'localhost',  
    port: 8080
  },
  chainWebpack: config => {
    config.resolve.alias
      .set('@', resolve('src'))
      .set('src', resolve('src'))
      .set('common', resolve('src/common'))
      .set('components', resolve('src/components'));
  }
};

devServer 用于设置开发环境的服务,这里表示在本地8080端口启动web服务。
chainWebpack 我们给项目目录起了“别名(alias)”,在代码中,我们可以直接用“别名”访问资源,省去了每次输入完整相对路径的麻烦。 ※注:◉ 在js代码中可直接使用别名,例如:@/common/js/xxx.js 等价于 src/common/js/xxx.jscommon/js/xxx.js 等价于 src/common/js/xxx.js◉ 在css或者html中使用别名,需要在别名前加“~”,例如:@import “~common/stylus/font.styl”;

3 项目基本设定

3.1 主进程和渲染进程简介

在开始下面的步骤之前,很有必要简单了解下Electron的应用架构。

主进程

Electron 运行 package.json 的 main 脚本(background.js)的进程被称为主进程。 在主进程中运行的脚本通过创建web页面来展示用户界面。 一个 Electron 应用总是有且只有一个主进程。

渲染进程

由于 Electron 使用了 Chromium 来展示 web 页面,所以 Chromium 的多进程架构也被使用到。 每个 Electron 中的 web 页面运行在它自己的渲染进程中。
在普通的浏览器中,web页面通常在一个沙盒环境中运行,不被允许去接触原生的资源。 然而 Electron 的用户在 Node.js 的 API 支持下可以在页面中和操作系统进行一些底层交互。

主进程与渲染进程的关系

主进程使用 BrowserWindow 实例创建页面。 每个 BrowserWindow 实例都在自己的渲染进程里运行页面。 当一个 BrowserWindow 实例被销毁后,相应的渲染进程也会被终止。
主进程管理所有的web页面和它们对应的渲染进程。 每个渲染进程都是独立的,它只关心它所运行的 web 页面。
具体可参阅官方文档:
https://electronjs.org/docs/tutorial/application-architecture#main-and-renderer-processes

※注:如果直接想看整个项目效果可直接从3.6开始操作

3.2 APP窗口大小

修改background.js:

function createWindow () {
      // Create the browser window.
      win = new BrowserWindow({
M       width: 1200,
M       height: '100%',
        webPreferences: {
          nodeIntegration: true
        }
      })

3.3 取消跨域限制

修改background.js:

function createWindow () {
      // Create the browser window.
      win = new BrowserWindow({
        width: 1200,
        height: '100%',
        webPreferences: {
+         webSecurity: false,
          nodeIntegration: true
        }
      })

3.4 取消菜单栏

在我们生成的桌面APP中,我们可以看到默认的菜单栏。
在windows中,菜单栏在APP窗口内的顶部;在macOS中,菜单栏位于电脑屏幕顶部。
为了方便项目将来也能直接生成纯web应用,尽量把APP的全部功能都做到渲染进程里,这里我们取消菜单栏。
由于macOS的特殊性,顶部菜单栏无法删除,所以我们针对macOS特殊处理,把菜单栏只保留“关于”和“退出”。
修改background.js:

M   import { app, protocol, BrowserWindow, Menu } from 'electron'
    ...
    function createWindow () {
        ...
        win.on('closed', () => {
            win = null
        })
+       createMenu()
    }
+   // 设置菜单栏
+   function createMenu() {
+       // darwin表示macOS,针对macOS的设置
+       if (process.platform === 'darwin') {
+           const template = [
+           {
+               label: 'App Demo',
+               submenu: [
+                   {
+                       role: 'about'
+                   },
+                   {
+                       role: 'quit'
+                   }]
+           }]
+           let menu = Menu.buildFromTemplate(template)
+           Menu.setApplicationMenu(menu)
+       } else {
+           // windows及linux系统
+           Menu.setApplicationMenu(null)
+       }
+   }

image.pngwindows界面菜单栏已消失
image.pngmacOS界面菜单栏已简化 macOS菜单栏名称label的“App Demo”会在build版本生效,dev版本会显示“Electron”。 更多关于菜单栏设置,请参阅:
https://electronjs.org/docs/api/menu

3.5 设置APP窗口标题栏名称

修改public/index.html:

...
M <title>72mao</title>
...

3.6 设置APP窗口图标

准备windows和macOS两版图标。 windows: app.ico 最小尺寸:256x256macOS: app.png或app.icns 最小尺寸:512x512 (后续4.1章节用到) 把图标文件放到public/ 与 static/目录下,项目结构如下:

※注:static目录默认没有,需要手动创建。 public目录为本地开发环境资源目录,static 常量会指到这里。打包后static不会默认指定到public。 static目录为生产环境资源目录,需要手动指定才会到这里。

|- /dist_electron
  (略)
|- /public
   |- app.ico
   |- app.png
   |- favicon.ico
   |- index.html
|- /static
   |- app.ico
   |- app.png
   |- favicon.ico
|- /src
  (略)
|- .editorconfig    
|- .eslintrc.js
|- .gitignore
|- babel.config.js
|- package.json
|- package-lock.json
|- README.md

可以顺便把favicon.ico也修改一下,但是在桌面版APP上是用不到的。如果以后生成纯web项目才会用到。
修改background.js,让APP窗口应用图标:

页头添加
...
+ const path = require('path')
+ /**
+  * Set `__static` path to static files in production
+  * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html
+  */
+ if (process.env.NODE_ENV !== 'development') {
+  global.__static = path.join(__dirname, '/static').replace(/\\/g, '\\\\')
+ }
...
function createWindow () {
      // Create the browser window.
      win = new BrowserWindow({
        width: 1200,
        height: '100%',
        webPreferences: {
          nodeIntegration: true
        },
+       // eslint-disable-next-line no-undef
+       icon: `${__static}/app.ico`
      })
...

※注:这里的${__static}对应的是public目录** 现在,Windows系统上可以看到开发环境的APP窗口图标已经生效了。
macOS图标请参照4.1章节,并且需要在build后才能生效。
windows APP窗口及任务栏图标

3.7 项目根目录下创建或修改vue.config.js

可整个复制 ``` const path = require(‘path’);

function resolve (dir) { return path.join(__dirname, dir); }

module.exports = { publicPath: process.env.NODE_ENV === ‘production’ ? ‘./‘ : ‘/‘, devServer: { // can be overwritten by process.env.HOST host: ‘localhost’, port: 8080 }, chainWebpack: config => { config.resolve.alias .set(‘@’, resolve(‘src’)) .set(‘src’, resolve(‘src’)) .set(‘common’, resolve(‘src/common’)) .set(‘components’, resolve(‘src/components’)); }, pluginOptions: { electronBuilder: { builderOptions: { appId: ‘com.72mao.tax’, productName: ‘72mao’, copyright: ‘Copyright @ 2020企业猫税局助手’, icon: ‘./static/app.ico’, files: [‘*/‘, ‘static/*’], asar: true, releaseInfo: { releaseNotes: ‘1.添加了 自动更新\n2.更新了logo’ }, publish: [ { provider: ‘generic’, // 这个意思是,使用自己的文件服务器 url: ‘http://tax.vm-72mao.com/apk/‘ // 这里指向存放的目录而非文件 } ], mac: { icon: ‘./static/app.png’ }, win: { icon: ‘./static/app.ico’ } }, preload: ‘src/preload.js’ // 预加载js 给前端vue使用 } }, configureWebpack: (config) => { if (process.env.NODE_ENV === ‘production’) { // 为生产环境修改配置… config.mode = ‘production’; config.performance = {// 打包文件大小配置 maxEntrypointSize: 10000000, maxAssetSize: 30000000 } } } };

<a name="lWR2E"></a>
## 3.8 项目使用ant-design-vue
在项目src目录下:<br />创建inc目录并创建 loadComponents.js<br />src/inc/loadComponents.js

import Vue from ‘vue’

import {Alert, AutoComplete, Avatar, Badge, Breadcrumb, Button, Card, Cascader, Checkbox, Col, Collapse, ConfigProvider, DatePicker, Descriptions, Divider, Drawer, Dropdown, Form, FormModel, Icon, Input, InputNumber, Layout, List, Menu, Modal, PageHeader, Popconfirm, Popover, Progress, Radio, Rate, Row, Select, Skeleton, Spin, Statistic, Steps, Switch, Table, Tabs, Tag, TimePicker, Tooltip, Transfer, Upload} from ‘ant-design-vue’

Vue.use(ConfigProvider) Vue.use(Cascader) Vue.use(Layout) Vue.use(AutoComplete) Vue.use(Input) Vue.use(Spin) Vue.use(Collapse) Vue.use(InputNumber) Vue.use(Button) Vue.use(Switch) Vue.use(Radio) Vue.use(Checkbox) Vue.use(Rate) Vue.use(Select) Vue.use(PageHeader) Vue.use(Descriptions) Vue.use(Card) Vue.use(Form) Vue.use(FormModel) Vue.use(Row) Vue.use(Col) Vue.use(Modal) Vue.use(Table) Vue.use(Tabs) Vue.use(Icon) Vue.use(Badge) Vue.use(Popover) Vue.use(Dropdown) Vue.use(List) Vue.use(Avatar) Vue.use(Breadcrumb) Vue.use(Steps) Vue.use(Menu) Vue.use(Drawer) Vue.use(Tooltip) Vue.use(Transfer) Vue.use(Alert) Vue.use(Tag) Vue.use(Divider) Vue.use(DatePicker) Vue.use(TimePicker) Vue.use(Upload) Vue.use(Progress) Vue.use(Skeleton) Vue.use(Popconfirm) Vue.use(Statistic)

修改src\main.js 文件

import Vue from ‘vue’ import App from ‘./App.vue’ import ‘ant-design-vue/dist/antd.css’ import ‘./inc/loadComponents’

Vue.config.productionTip = false

new Vue({ render: h => h(App) }).$mount(‘#app’)

<a name="qVbZu"></a>
## 3.9项目引入自动更新与前端vue页面修改
新加src/preload.js (预加载ipcRenderer 给vue页面使用 )

import { ipcRenderer } from ‘electron’ window.ipcRenderer = ipcRenderer

新加src/update.js(自动更新检测脚本)

import { autoUpdater } from ‘electron-updater’ import { ipcMain } from ‘electron’ const path = require(‘path’)

let mainWindow = null export function updateHandle(window, feedUrl) { if (process.env.NODE_ENV === ‘development’) { autoUpdater.updateConfigPath = path.join(__dirname, ‘win-unpacked/resources/app-update.yml’) // mac的地址是’Contents/Resources/app-update.yml’ } mainWindow = window; // eslint-disable-next-line no-unused-vars const message = { error: ‘检查更新出错’, checking: ‘正在检查更新……’, updateAva: ‘检测到新版本,正在下载……’, updateNotAva: ‘现在使用的就是最新版本,不用更新’ } // 设置更新包的地址 autoUpdater.setFeedURL(feedUrl); // 监听升级失败事件 autoUpdater.on(‘error’, function (error) { sendUpdateMessage({cmd: ‘error’, message: error}) }); // 监听开始检测更新事件 autoUpdater.on(‘checking-for-update’, function (message) { sendUpdateMessage({cmd: ‘checking-for-update’, message: message}) }); // 监听发现可用更新事件 autoUpdater.on(‘update-available’, function (message) { sendUpdateMessage({cmd: ‘update-available’, message: message}) }); // 监听没有可用更新事件 autoUpdater.on(‘update-not-available’, function (message) { sendUpdateMessage({cmd: ‘update-not-available’, message: message}) }); // 更新下载进度事件 autoUpdater.on(‘download-progress’, function (progressObj) { sendUpdateMessage({cmd: ‘download-progress’, message: progressObj}) }); // 监听下载完成事件 autoUpdater.on(‘update-downloaded’, function (event, releaseNotes, releaseName, releaseDate, updateUrl) { ipcMain.on(‘isUpdateNow’, (e, arg) => { sendUpdateMessage({cmd: ‘isUpdateNow2’, message: ‘开始更新’}); // some code here to handle event autoUpdater.quitAndInstall() // 退出并安装更新包 mainWindow.destroy() // 真实关闭程序、托盘 }) sendUpdateMessage({cmd: ‘isUpdateNow’, message: ‘isUpdateNow’}) }); // 接收渲染进程消息,开始检查更新 ipcMain.on(‘checkForUpdate’, (e, arg) => { sendUpdateMessage({cmd: ‘checkForUpdate’, message: ‘执行自动更新检查’}) autoUpdater.checkForUpdates() // 执行自动更新检查 }) } // 给渲染进程发送消息 function sendUpdateMessage(text) { mainWindow.webContents.send(‘message’, text) }

修改src/App.vue

修改src/background.js (支持系统托盘、检测自动更新)

‘use strict’

import { app, protocol, BrowserWindow, Menu, globalShortcut, Tray } from ‘electron’ import { createProtocol } from ‘vue-cli-plugin-electron-builder/lib’ import installExtension, { VUEJS_DEVTOOLS } from ‘electron-devtools-installer’ import {updateHandle} from ‘./update.js’

const isDevelopment = process.env.NODE_ENV !== ‘production’ const path = require(‘path’)

/**

// 浏览器对象 let win // 托盘对象 let appTray = null

// Scheme must be registered before the app is ready protocol.registerSchemesAsPrivileged([ { scheme: ‘app’, privileges: { secure: true, standard: true } } ])

function createWindow () { // 创建浏览器 win = new BrowserWindow({ width: 1200, height: ‘100%’, transparent: true, // 透明窗口 frame: true, // 值为true或false, 表示是否创建无边框窗口,默认的程序窗口是带外壳的(标题栏,工具栏,边框等) center: true, // 窗口是否在屏幕居中;true or false resizable: true, // 窗口是否可以改变尺寸;true or false,默认值为true movable: true, // 窗口能否可以被移动;true or false,默认值为 true show: true, // 是否显示窗口 kiosk: false, // 是否使用kiosk模式。如果使用kiosk模式,应用程序将全屏显示,并且阻止用户离开应用。true or false autoHideMenuBar: true, // 是否隐藏菜单栏 webPreferences: { webSecurity: false, // Use pluginOptions.nodeIntegration, leave this alone // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info // nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION nodeIntegration: true }, // eslint-disable-next-line no-undef icon: ${__static}/favicon.ico })

if (process.env.WEBPACK_DEV_SERVER_URL) { // Load the url of the dev server if in development mode win.loadURL(process.env.WEBPACK_DEV_SERVER_URL) if (!process.env.IS_TEST) win.webContents.openDevTools() } else { createProtocol(‘app’) // Load the index.html when not in development win.loadURL(‘app://./index.html’) }

win.on(‘closed’, () => { win = null }) // 当我们点击关闭时触发close事件,我们按照之前的思路在关闭时,隐藏窗口,隐藏任务栏窗口 // event.preventDefault(); 禁止关闭行为(非常必要,因为我们并不是想要关闭窗口,所以需要禁止默认行为) win.on(‘close’, (event) => { win.hide(); win.setSkipTaskbar(true); event.preventDefault(); }); win.on(‘show’, () => { }) win.on(‘hide’, () => { }) createTray() // 创建托盘 createMenu() // 创建菜单mac下需要退出菜单 updateHandle(win, ‘http://tax.vm-72mao.com/apk/‘) // 挂载下载地址 }

// 创建系统托盘 function createTray() { // 系统托盘图标目录 appTray = new Tray(__static + ‘/app.ico’) // 系统托盘右键菜单 var trayMenuTemplate = [ { label: ‘退出’, click: () => { win.destroy() } }// 我们需要在这里有一个真正的退出(这里直接强制退出) ] const contextMenu = Menu.buildFromTemplate(trayMenuTemplate) appTray.setToolTip(‘72mao报税系统助手’) appTray.setContextMenu(contextMenu) appTray.on(‘click’, () => { // 我们这里模拟桌面程序点击通知区图标实现打开关闭应用的功能 win.isVisible() ? win.hide() : win.show() win.isVisible() ? win.setSkipTaskbar(false) : win.setSkipTaskbar(true) }) }

// 设置菜单栏 function createMenu() { // darwin表示macOS,针对macOS的设置 if (process.platform === ‘darwin’) { const template = [ { label: ‘72mao’, submenu: [ { role: ‘about’ }, { role: ‘quit’ }] }] const menu = Menu.buildFromTemplate(template) Menu.setApplicationMenu(menu) } else { // windows及linux系统 Menu.setApplicationMenu(null) } }

// Quit when all windows are closed. app.on(‘window-all-closed’, () => { // On macOS it is common for applications and their menu bar // to stay active until the user quits explicitly with Cmd + Q if (process.platform !== ‘darwin’) { app.quit() } })

app.on(‘activate’, () => { // On macOS it’s common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (win === null) { createWindow() // console.error(‘app.ico ++’) } })

// This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. app.on(‘ready’, async () => { // 注释掉目的为了不检测是否使得开发工具 // if (isDevelopment && !process.env.IS_TEST) { // // Install Vue Devtools // try { // await installExtension(VUEJS_DEVTOOLS) // } catch (e) { // console.error(‘Vue Devtools failed to install:’, e.toString()) // } // } // 在开发环境和生产环境均可通过快捷键打开devTools // 在windows下,按Ctrl+F12即可打开devTools // 在macOS下,按Commond+F12即可打开devTools globalShortcut.register(‘CommandOrControl+F12’, function () { win.webContents.openDevTools() }) createWindow() // 创建浏览器窗口 })

// Exit cleanly on request from parent process in development mode. if (isDevelopment) { if (process.platform === ‘win32’) { process.on(‘message’, (data) => { if (data === ‘graceful-exit’) { app.quit() } }) } else { process.on(‘SIGTERM’, () => { app.quit() }) } }


本地运行效果<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/126752/1601192401372-ea7f45a6-a89d-4acc-a09f-19342d27a22a.png#align=left&display=inline&height=1080&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1080&originWidth=1920&size=1075125&status=done&style=none&width=1920)


<a name="gkS4c"></a>
# 4 build最终产品
这里我们已经集成了electron-builder工具,<br />官方文档可以参阅:<br />[https://www.electron.build/](https://link.zhihu.com/?target=https%3A//www.electron.build/)
<a name="jwLcP"></a>
## 4.1 设置APP及安装包图标
在3.5章节,我们的图标生效于运行APP的窗口。本小节将生效于最终生成的可执行文件和安装包图标。需要准备的图标文件请回看3.5章节。<br />修改项目下vue.config.js,完整参考看回3.7章节

chainWebpack: config => {…},

  • pluginOptions: {
  • electronBuilder: {
  • builderOptions: {
  • win: {
  • icon: ‘./static/app.ico’
  • },
  • mac: {
  • icon: ‘./static/app.png’
  • }
  • }
  • }
  • } … ```

更多详细介绍,可参阅:
https://www.electron.build/icons.html

4.2 设置APP名称

APP名称包括安装包中APP的名称、可执行文件的文件名。
修改vue.config.js,完整参考看回3.7章节

pluginOptions: {
        electronBuilder: {
            builderOptions: {
                win: {
                    icon: './public/app.ico'
                },
                mac: {
                    icon: './public/app.png'
                },
+               productName: '72mao'
            }
        }
    }

4.3 更新软件地址设置(自己服务文件目录位置)

修改vue.config.js,完整参考看回3.7章节

  pluginOptions: {
    electronBuilder: {
      builderOptions: {
      ...
        releaseInfo: {
          releaseNotes: '1.添加了 自动更新\n2.更新了logo' // 更新日志
        },
        publish: [
          {
            provider: 'generic', // 这个意思是,使用自己的文件服务器
            url: 'http://tax.vm-72mao.com/apk/' // 这里指向存放的目录而非文件
          }
        ],
        ...
      }
    }
 }

注意同步修改 src/background.js 中的下载地址

...
updateHandle(win, 'http://tax.vm-72mao.com/apk/') // 挂载下载地址
...

4.4 打包APP

在vue-cli3工具下点击electron:build菜单,执行运行按钮。
image.png
或者
执行以下命令,可以build工程:

npm run electron:build

最终在dist_electron目录下生成build后的产品。

windows版本

目录如下:

/dist_electron
|- /bundled
  (略)
|- /win-unpacked  <-- 绿色版
  (略)
|- 72mao Setup 0.1.1.exe  <-- 安装文件,新版自动更新会使用到。
|- 72mao Setup 0.1.1.exe.blockmap
|- latest.yml   <-- 更新yml 文件
|- index.js
|- package.json
|- preload.js

这里就win-unpacked和72mao Setup 0.1.0.exe有用。 ※注:在32位环境下打包生成的是32位APP,在64位环境下打包生成的是64位APP。

mac版本

/dist_electron
|- /bundled
  (略)
|- /mac
|- 72mao   <-- 绿色版
|- 72mao-0.1.1-mac.zip  <-- 绿色版压缩包
|- 72mao-0.1.1-mac.dmg  <-- 安装包
|- 72mao-0.1.1.dmg.blockmap
|- latest.yml
|- index.js

4.5 新版自动更新

把打包出来的.exe 与 latest.yml 两个文件更新到服务器地址目录上
http://tax.vm-72mao.com/apk/

5 关于项目开发的一些经验

在完成以上章节后,后面基本可以完全按照web方式开发了。这里简单分享下一些小经验。

5.1 src目录结构参考

/src
|- /common
   |- /fonts
   |- /images
   |- /js
      |- api
      |- libs
   |- /stylus
   |- /components
   |- /base
   |- /modules
      |- /moduleA
      |- /moduleB
      ...
   |- /views
   |- App.vue
   |- background.js
   |- main.js
   |- router.js
   |- store.js

下面对部分重要目录简要说明:

common/ - 项目公用库
common/fonts/ - 字体文件
common/images/ - 公用图片
common/js/ - 公用js目录
common/js/api/ - 把api按类别封装成函数,并export出去,减少业务逻辑中的重复代码
common/js/lib/ - 存放一些公用函数库、定义的常量库等
common/stylus/ - Stylus样式文件
components/ - vue组件目录
component/base/ - vue基础组件,例如自定义的CheckBox、日期选择器、Dialog、Toaster、分页组件等
component/modules/ - vue模块
views/ - vue页面

5.2 从Electron旧版(4.x)升级到最新(9.x)

如果你之前用的是Electron4.x,升级到9.x很简单。
修改package.json中electron的版本(写作本文时是9.0.0):

...
M   "electron": "^9.0.0",
    ...

修改background.js中的这部分:

// Scheme must be registered before the app is ready
// Electron 4.x代码
// protocol.registerStandardSchemes(['app'], {secure: true})
// Electron 9.x代码 (5.x以后需要修改)
protocol.registerSchemesAsPrivileged([{
  scheme: 'app',
  privileges: {
    secure: true,
    standard: true
  }
}])

然后执行,等待升级安装完成:

cnpm install

5.3 注册快捷键打开devTools

在Electron中打开devTools是通过主线程中调用win.webContents.openDevTools()实现的。以上教程仅在开发环境初始启动的时候打开devTools,但是一旦关闭就不能再打开了。下面讲一下怎么通过快捷键打开devTools。
修改background.js:

...
M   import { app, protocol, BrowserWindow, Menu, globalShortcut } from 'electron'
    ...
    app.on('ready', async () => {
        if (isDevelopment && !process.env.IS_TEST) {
        // Install Vue Devtools
            try {
                await installVueDevtools()
            } catch (e) {
                console.error('Vue Devtools failed to install:', e.toString())
            }
        }
        // 在开发环境和生产环境均可通过快捷键打开devTools
+       globalShortcut.register('CommandOrControl+F12', function () {
+           win.webContents.openDevTools()
+       })
        createWindow()
})

在windows下,按Ctrl+F12i即可打开devTools
在macOS下,按Commond+F12即可打开devTools
为什么没用F12?因为windows系统中,F12是系统保留快捷键,无法使用。官方原话是这么解释的: The F12 key is reserved for use by the debugger at all times, so it should not be registered as a hot key. Even when you are not debugging an application, F12 is reserved in case a kernel-mode debugger or a just-in-time debugger is resident. 但是可以使用Ctrl+F12,只要不单独使用F12就可以。
以上代码在开发环境和生产环境中均有效,为保证生产环境的安全,建议不要在生产环境中使用。放到上面的if语句中即可。

5.4 可能遇到的问题

01、如何解决vue add vue-cli-plugin-electron-builder过慢的问题?

vue add vue-cli-plugin-electron-builder

用Vue/cli@3和vue-cli-plugin-electron-builder构建Vue+Electron的方式写桌面端,在添加上述的插件的时候下载速度过慢,主要原因是 electron-chromedriver 的锅!
然后,用yarn先下载了依赖

yarn add -D electron-chromedriver
yarn add vue-cli-plugin-electron-builder
or
cnpm install vue-cli-plugin-electron-builder

02、但是执行 vue add vue-cli-plugin-electron-builder 的时候还是重新卡在了 electron-chromedriver,如何解决?

解决办法:
设置npm、yarn chromedriver_cdnurl

cnpm config set chromedriver_cdnurl=http://cdn.npm.taobao.org/dist/chromedriver
cnpm install chromedriver --chromedriver_cdnurl=http://cdn.npm.taobao.org/dist/chromedriver

03、entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit
在vue.config.js中 添加

config["performance"] = {//打包文件大小配置
        "maxEntrypointSize": 10000000,
        "maxAssetSize": 30000000
      }

04、electron打包太慢解决方法 提示https://github.com/electron/electron/releases 下载包慢慢

到阿里镜像下载
https://npm.taobao.org/mirrors/electron 找对应版本


Windows: $LOCALAPPDATA/electron/Cache or ~/AppData/Local/electron/Cache/

把checksum文件也下载下来放到相同目录,修改文件名后面加上版本号

例如 SHASUMS256.txt-1.8.1

一、打开 C:\Users\Administrator\AppData\Local , 执行完打包命令以后会发现,变成了两个文件夹

使用Electron+vue-cli3+antd开发跨平台桌面应用 - 图6

二、具体报错:报错是下面的挨着下载失败的,总共要下载四个

1、electron-v9.3.3-win32-x64.zip

这里如果按照我第一步的步骤是不会有这个报错的,这个文件时相当的大的,可以点我下载不同版本,也可以把报错下载的后面的那个地址,放在浏览器上,直接下载,然后打开

C:\Users\Administrator\AppData\Local\electron\Cache 将你下载的压缩包放进去,如图所示,我这里下载了64位和32位两个

使用Electron+vue-cli3+antd开发跨平台桌面应用 - 图7

这个放完以后就报错第二个winCodeSign-2.6.0.7z了

2、winCodeSign-2.6.0.7z (这里注意哈,剩下的几步,需要放的地方都是 electron-builder 这底下)

这里 具体版本和地址,你根据你报错的后面 down 后面的那个路径复制了,然后在浏览器 下载下来,比如,我的报错,down:后面的地址就是下面的

https://github.com/electron-userland/electron-builder-binaries/releases/download/winCodeSign-2.6.0/winCodeSign-2.6.0.7z

(1)打开 C:\Users\Administrator\AppData\Local\electron-builder\Cache ,新建文件夹取名 winCodeSign,在这里面再新建一个文件夹 取名 winCodeSign-2.6.0 ,这个取名不是随便取的,是根据上面下载的路径前面的那个名字而来,把下载的东西解压到这个文件夹

使用Electron+vue-cli3+antd开发跨平台桌面应用 - 图8

使用Electron+vue-cli3+antd开发跨平台桌面应用 - 图9

这个放完以后就报错第三个nsis-3.0.4.1.7z了

3、nsis-3.0.4.1.7z

这里 具体版本和地址,你根据你报错的后面 down 后面的那个路径复制了,然后在浏览器 下载下来,比如,我的报错,down:后面的地址就是下面的

https://github.com/electron-userland/electron-builder-binaries/releases/download/nsis-3.0.4.1/nsis-3.0.4.1.7z

(1)打开 C:\Users\Administrator\AppData\Local\electron-builder\Cache ,新建文件夹取名 nsis,在这里面再新建一个文件夹 取名 nsis-3.0.4.1,这个取名不是随便取的,是根据上面下载的路径前面的那个名字而来,把下载的东西解压到这个文件夹

使用Electron+vue-cli3+antd开发跨平台桌面应用 - 图10

使用Electron+vue-cli3+antd开发跨平台桌面应用 - 图11

这个放完以后就报错第四个nsis-resources-3.4.1.7z了

4、nsis-resources-3.4.1.7z

这里 具体版本和地址,你根据你报错的后面 down 后面的那个路径复制了,然后在浏览器 下载下来,比如,我的报错,down:后面的地址就是下面的

https://github.com/electron-userland/electron-builder-binaries/releases/download/nsis-resources-3.4.1/nsis-resources-3.4.1.7z

(1)打开 C:\Users\Administrator\AppData\Local\electron-builder\Cache\nsis ,新建文件夹取名 nsis-resources-3.4.1,把下载的东西解压到这个文件夹

使用Electron+vue-cli3+antd开发跨平台桌面应用 - 图12
使用Electron+vue-cli3+antd开发跨平台桌面应用 - 图13

5、恭喜恭喜,到这里以后就没别的问题了,你再执行npm run e-build 应该是这样的,就代表打包成功了,快看看你的dis_electron吧

使用Electron+vue-cli3+antd开发跨平台桌面应用 - 图14
使用Electron+vue-cli3+antd开发跨平台桌面应用 - 图15

到这里就结束了,最好试试32位的和64位,如果你两个都下载了!

5.5参考文档

01、SimulatedGREG/electron-vue
https://github.com/SimulatedGREG/electron-vue

02、手把手教你使用Electron5+vue-cli3开发跨平台桌面应用
https://zhuanlan.zhihu.com/p/75764907

03、Package.json 可配置项
https://blog.csdn.net/wenxuhonghe/article/details/108146399

04、使用electron中遇到的问题总结
https://www.jianshu.com/p/d2d4deaccdc1

05、BrowserWindow 浏览器参数
https://www.electronjs.org/docs/api/browser-window#browserwindow