快速入手Electron,拥有一个自己的桌面应用

46,703 阅读17分钟

前言

小浪学习electron的原因是软件构造课需要交一个软件作业,不想用java写,还不能是网页,一开始想到的是用uniapp写个项目打包成APP,然后想了想,一直听说 electron 可以把前端页面(原生/h5/vue/react...)打包成桌面应用,把前端页面当做GUI这岂不是很Nice,Typora 就是 electron 做的,很好奇,就去学学看,下面是小浪学习 electron 的笔记,希望能给大家一点帮助,学习 electron 教程好像很多,但是还是官方文档比较清晰全面,有可能你在视频教程里看见的能使用的,自己去敲的时候发现各种问题,还以为是自己哪里拼错了,一看是官方文档更新了...

1.基础使用

要想弄个桌面端的应用,那我们得快速的去了解它

1.1终端乱码问题

tip: electron 控制打印会出现中文乱码

image-20211004134055911

只需要在终端(cmd)输入 chcp 65001 运行下就行了

image-20211004134132071

1.2安装

// 如果没有node 的话先装 node
http://nodejs.cn/download/

// 在当前目录安装最新
npm i -D electron

// 全局安装最新
cnpm install electron -g

// 当然你可以指定版本号安装
npm i -D electron@11.0.4

node -v electron -v查看是否安装成功

1.3快速创建

开始创建一个 electron

  • 首先说下目录必须包括:package.json 这个文件
  • 然后要有个入口文件下面这个例子我用 index.js举例,不过一般写成 main.js比较好
  • 起码你需要个展示的GUI界面,一般是前端页面,也可以直接放个网址

新建一个目录(项目):

初始化package.json文件

npm init

描述记得写,这个electron 打包的时候我记得需要描述

启动命令写 "test": "nodemon --watch index.js --exec electron ." ,这样子最后在终端输入 npm test这样每次修改index.js 主进程文件都会重新启动项目了,index.js可以自行修改 main.js等等

来看看最后的的 package.json文件吧

// package.json 文件

{
  "name": "electron_demo",
  "version": "1.0.0",
  "description": "\"这是一个electron demo\"",
  "main": "index.js",
  "scripts": {
    "test": "nodemon --watch index.js --exec electron ."
  },
  "author": "",
  "license": "ISC"
}

我的目录下放了以下几个文件

image-20211002115657083

electron 分为两个进程 主进程渲染进程

index.js 这个文件是 主进程

官方是这样写的

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

function createWindow () {   
  // 创建浏览器窗口
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true
    }
  })

  // 并且为你的应用加载index.html
  win.loadFile('index.html')

  // 打开开发者工具
  win.webContents.openDevTools()
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// 部分 API 在 ready 事件触发后才能使用。
app.whenReady().then(createWindow)

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  // 在 macOS 上,除非用户用 Cmd + Q 确定地退出,
  // 否则绝大部分应用及其菜单栏会保持激活。
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', () => {
  // 在macOS上,当单击dock图标并且没有其他窗口打开时,
  // 通常在应用程序中重新创建一个窗口。
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow()
  }
})

// In this file you can include the rest of your app's specific main process
// code. 也可以拆分成几个文件,然后用 require 导入。

我看其他人差不多是这样写的

const { app, BrowserWindow } = require('electron')
let win
// 监听electron 加载完毕的时候的创建窗口等等
app.on('ready', function () {
    // 创建一个窗口 设置属性
    win = new BrowserWindow({
    //fullscreen: true   //全屏
    //frame: false,   	//让桌面应用没有边框,这样菜单栏也会消失
    resizable: false,   //不允许用户改变窗口大小
    width: 800,         //设置窗口宽高
    height: 600,
    icon: iconPath,     //应用运行时的标题栏图标
    minWidth: 300,     // 最小宽度
    minHeight: 500,    // 最小高度
    maxWidth: 300,    // 最大宽度
    maxHeight: 600,    // 最大高度
    // 进行对首选项的设置
    webPreferences:{    
      backgroundThrottling: false,   //设置应用在后台正常运行
      nodeIntegration:true,     //设置能在页面使用nodejs的API
      contextIsolation: false,  //关闭警告信息
      //preload: path.join(__dirname, './preload.js')
    }
  })
  // 这里让主进程加载一个index.html
  win.loadFile('index.html')
  // 设置为最顶层
  //win.setAlwaysOnTop(true)
  //win.loadURL(`www.baidu.com`) 可以让主进程打开文件或者一个链接
  // 监听窗口关闭事件
  win.on('closed',()=>{
      //释放win
      win = null
  })
})

// 监听所有的窗口都关闭了
app.on('window-all-closed', () => {
    
    console.log('窗口全部都关闭了')
})

index.html渲染进程也就是前端页面里面随便写点东西,这里相当是把前端当成 GUI 了

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>electron test</title>
  </head>
  <body>
    electron demo
    <script></script>
  </body>
</html>

这样使用 npm test 就可以出来这么一个界面了,test这个命令 是 package.json script 中配的

image-20211003211108491

2.Remote 模块

在渲染进程里(比如index.html里面加载了一些js文件,那里面的js如果要使用到 BrowserWindow 这些属性的话就必须使用 remote

使用 remote 模块, 你可以调用 main 进程对象的方法

2.1.electron14.0之前版本使用

在主进程的窗口中加入enableRemoteModule: true参数才能够调用remote模块

const { app, BrowserWindow } = require('electron')
app.on('ready', function () {
  let win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      //这里进行加入
      enableRemoteModule: true,
      nodeIntegration: true,
      contextIsolation: false,
    },
  })
  win.loadFile('index.html')
  // 监听所有的窗口都关闭了
  app.on('window-all-closed', () => {
    //释放win
    win = null
    console.log('窗口全部都关闭了')
  })
})

然后在渲染进程里写,这里我直接内嵌js了

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>electron test</title>
  </head>
  <body>
    electron demo
    <button id="btn">添加新的窗口</button>
    <script>
      const { log } = console
      // 导入 remote 中的  BrowserWindow
      const { BrowserWindow } = require('electron').remote

      const btn = document.getElementById('btn')
      btn.onclick = () => {
        let newWin = new BrowserWindow({
          width: 800,
          height: 600,
        })
        // newWin.loadURL('www.baidu.com')
        win.loadFile('index2.html')

        newWin.on('close', () => {
          newWin = null
        })
      }
    </script>
  </body>
</html>

这里点击按钮,就又可以创建一个新的窗口了

2.2.electron14.0版本API修改

但是这里是有版本的区分的,这里一开始也困扰了我很久很久...最后看了下文档14.0后 改了,我用的15。。。

image-20211003215043335

1.还得自行安装 remote

npm i -D @electron/remote

2.主进程中导入

app.on('ready',function(){
	require('@electron/remote/main').initialize()
})

3.渲染进程中

//这样来引入remote
const { BrowserWindow } = require('@electron/remote') 

3.创建系统菜单

1.新建一个 menu.js

// 1.导入 electron 中的 Menu
const { Menu } = require('electron')

// 2.创建菜单模板,数组里的每一个对象都是一个菜单
const template = [
  {
    label: '菜单一',
    // submenu 代表下一级菜单
    submenu: [
      { 
          label: '子菜单一' ,
          // 添加快捷键
          accelerator: 'ctrl+n'
      },
      { label: '子菜单二' },
      { label: '子菜单三' },
      { label: '子菜单四' },
    ],
  },
  {
    label: '菜单二',
    // submenu 代表下一级菜单
    submenu: [
      { label: '子菜单一' },
      { label: '子菜单二' },
      { label: '子菜单三' },
      { label: '子菜单四' },
    ],
  },
]

// 3.从模板中创建菜单
const myMenu = Menu.buildFromTemplate(template)

// 4.设置为应用程序菜单
Menu.setApplicationMenu(myMenu)

accelerator: 'ctrl+n'可以指定菜单的快捷键

2.随便写个页面

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>自定义菜单</title>
  </head>
  <body>
    自定义菜单
    <script></script>
  </body>
</html>

3.写 main.js

// 1.引入 electron
const { app, BrowserWindow } = require('electron')
// 定义一个窗口
let win = null
// 2.引入自定义的菜单
require('./menu')

// 3.监听ready
app.on('ready', function () {
  win = new BrowserWindow({
    width: 800,
    height: 600,
  })
  // 打开控制台
  win.webContents.openDevTools()
  win.loadFile('./index.html')
  // 4.监听窗口关闭事件
  win.on('close', () => {
    win = null
  })
})

npm test启动

46

4.给菜单添加事件

比如给子菜单添加一个点击事件新建一个窗口

// 1.导入 electron 中的 Menu
const { Menu, BrowserWindow } = require('electron')

// 2.创建菜单模板,数组里的每一个对象都是一个菜单
const template = [
  {
    label: '菜单一',
    // submenu 代表下一级菜单
    submenu: [
      {
        label: '子菜单一',
        // 添加点击事件
        click: () => {
          // 创建一个新的窗口
          let sonWin = new BrowserWindow({
            width: 200,
            height: 200,
          })
          sonWin.loadFile('./index2.html')
          // 为关闭的时候进行清空
          sonWin.on('close', () => {
            sonWin = null
          })
        },
      },
      { label: '子菜单二' },
      { label: '子菜单三' },
      { label: '子菜单四' },
    ],
  },
  {
    label: '菜单二',
    // submenu 代表下一级菜单
    submenu: [
      { label: '子菜单一' },
      { label: '子菜单二' },
      { label: '子菜单三' },
      { label: '子菜单四' },
    ],
  },
]

// 3.从模板中创建菜单
const myMenu = Menu.buildFromTemplate(template)

// 4.设置为应用程序菜单
Menu.setApplicationMenu(myMenu)

效果图

47

上面的的开发者工具和chrome/edge浏览器一样,在菜单栏的View -> Toggle Developer Tools,或者 Ctrl + Shift + I就能调用出来,用来调试页面

5.使用Node.js 模块/API

比如写个读写文件例子

在主线程创建窗口的时候 webPreferences一定在加上 nodeIntegration: truecontextIsolation: false

这样在渲染进程才能使用node 的一些语法

main.js

// 导入 electron
const { app, BrowserWindow } = require('electron')

let mainWindow = null

app.on('ready', () => {
  // // 新建一个窗口
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true, // 是否集成 Nodejs,把之前预加载的js去了,发现也可以运行
      contextIsolation: false,
    },
  })
  // 加载渲染文件
  mainWindow.loadFile('./main.html')
  // 窗口关闭后清空变量
  mainWindow.on('close', () => {
    mainWindow = null
  })
})

main.html 主要的渲染文件

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>读写文件测试</title>
  </head>
  <body>
    <button onclick="readFile()">读取文件</button>
    <button onclick="writeFile()">写入文件</button>
    <p id="show_file_content">页面内容</p>
    <script src="./index.js"></script>
  </body>
</html>

index.js 加载需要的js

可以看出,在渲染进程中,就是main.html 里面加载的 index.js 中,既可以使用 docment.getElementById 这些 WebAPI,又能使用用 node 的模块进行混写

// 导入 node 的模块
const fs = require('fs')
const path = require('path')
const { log } = console

// 获取到文件展示的dom
const showContent = document.getElementById('show_file_content')

// 读取文件
function readFile() {
  console.log('读取文件')
  fs.readFile(path.join(__dirname, '/test.txt'), (err, data) => {
    if (err) {
      throw new Error(err, '读取文件失败')
    }
    showContent.innerText = data
  })
}
// 需要写入的内容
const content = '今天是国庆的第二天,在学 electron'

// 写入文件
function writeFile() {
  fs.writeFile(
    path.join(__dirname, '/test.txt'),
    content,
    'utf8',
    (err, data) => {
      if (err) {
        return new Error(err, '读取文件失败')
      }
      log('写入文件成功')
    }
  )
}

测试用的 txt

今天是国庆的第二天,在学 electron

项目的目录

image-20211002155537033

效果图

45

6.设置无边框

在创建窗口的时候 可以设置无边框,带的菜单也消失了

let win = new BrowserWindow({
    frame: false,   	//让桌面应用没有边框,这样菜单栏也会消失
    width: 800,         //设置窗口宽高
    height: 600,
})

菜单其实它还在,你仍然可以通过快捷键调用出菜单,可以直接删除菜单win.removeMenu()

没有菜单栏怎么去拖拽窗口

在css中你可以设置哪个可以进行拖拽/禁止拖拽

比如 body{ -webkit-app-region: drag | no-drag;}

效果图:无边框,在body设置可拖拽

44

7.系统托盘

image-20211004102538924

看到上面这个图大家都应该清楚吧,当我们关闭一个应用程序的时候,它其实关闭了,但是没有完全关闭,只是隐藏了,有的就存在系统托盘中,那么如何在electron 设置系统托盘呢

官方文档:Tray

主进程 index.js

electron 这里一开始我就添加系统托盘,当然你可以监听窗口被关闭的时候在创建托盘

// 引入托盘 Tray,和 Menu 等下要创建菜单,nativeImage创建 icon图标
const { app, BrowserWindow, Tray, Menu, nativeImage } = require('electron')
const path = require('path')
let win, tray
app.on('ready', function () {
  win = new BrowserWindow({
    width: 800,
    height: 600,
  })
  // 创建icon我这里使用的是一个png
  const icon = nativeImage.createFromPath(
    path.join(__dirname, '/static/icon.png')
  )
  // 实例化一个 托盘对象,传入的是托盘的图标
  tray = new Tray(icon)
  // 移动到托盘上的提示
  tray.setToolTip('electron demo is running')
  // 还可以设置 titlle
  tray.setTitle('electron demo')

  // 监听托盘右键事件
  tray.on('right-click', () => {
    // 右键菜单模板
    const tempate = [
      {
        label: '无操作',
      },
      {
        label: '退出',
        click: () => app.quit(),
      },
    ]
    //通过 Menu 创建菜单
    const menuConfig = Menu.buildFromTemplate(tempate)
    // 让我们的写的托盘右键的菜单替代原来的
    tray.popUpContextMenu(menuConfig)
  })
  //监听点击托盘的事件
  tray.on('click', () => {
    // 这里来控制窗口的显示和隐藏
    if (win.isVisible()) {
      win.hide()
    } else {
      win.show()
    }
  })
  win.loadFile('index.html')
})
// 监听所有的窗口都关闭了
app.on('window-all-closed', () => {
  //释放win
  win = null
  console.log('窗口全部都关闭了')
})

效果图

48

8.进程间通信

electron中主进程和渲染进程两者之间需要通信

官方文档:

ipcMain

ipcRenderer

webContents

主线程渲染线程 通过 webContents.send 来发送 --->ipcRenderer.on 来监听

渲染线程主线程 需要通过 ipcRenderer.send发送 ---> ipcMain.on来监听

8.1.主进程到渲染进程

webContents.send(channel, ...args)

  • channel String
  • ...args any[]

主进程 mian.js

在主进程中使用 webContents.send 发送消息

//主进程
const { app, BrowserWindow } = require('electron')
let win
// 监听electron 加载完毕的时候的创建窗口等等
app.on('ready', function () {
  // 创建一个窗口
  win = new BrowserWindow({
    width: 800, //设置窗口宽高
    height: 600,

    // 进行对首选项的设置
    webPreferences: {
      enableRemoteModule: true,
      nodeIntegration: true, //设置能在页面使用nodejs的API
      contextIsolation: false, //关闭警告信息
    },
  })
  // 发送给渲染线程
  setTimeout(() => {
    win.webContents.send('mainMsg', '我是主线程发送的消息')
  }, 3000)
  // 这里让主进程加载一个main.html
  win.loadFile('main.html')
})

// 监听所有的窗口都关闭了
app.on('window-all-closed', () => {
  //释放win
  win = null
  app.quit()
  console.log('窗口全部都关闭了')
})

渲染进程 main.html 外链一个 render.js

在渲染线程中使用 ipcRenderer.on来进行监听

ipcRenderer.on(channel, listener)

  • channel String
  • listener Function

监听 channel, 当有新消息到达,使用 listener(event, args...) 调用 listener .

还有个监听一次的消息ipcRenderer.once(channel, listener)

为这个事件添加一个一次性 listener 函数.这个 listener 将在下一次有新消息被发送到 channel 的时候被请求调用,之后就被删除了

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>通信测试</title>
  </head>
  <body>
    通信测试
    <p id="receive">接收信息</p>
    <script src="./render.js"></script>
  </body>
</html>

//渲染进程

//引入ipcRenderer
const electron = require('electron')
const { ipcRenderer } = require('electron')
const { log } = console

log(ipcRenderer)
ipcRenderer.on('mainMsg', (event, task) => {
  log(task)
  document.getElementById('receive').innerText = task
})

效果图

49

8.2.渲染进程到主进程

render.js 渲染线程中进行发送 ipcRenderer.send

ipcRenderer.send(channel[, arg1][, arg2][, ...])

  • channel String
  • arg (可选)

还有发送同步消息的ipcRenderer.sendSync(channel[, arg1][, arg2][, ...])

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>通信测试</title>
  </head>
  <body>
    通信测试
    <p id="receive">接收信息</p>
    <button onclick="sendMain()">发送消息给主线程</button>
    <script src="./render.js"></script>
  </body>
</html>

const electron = require('electron')
const { ipcRenderer } = require('electron')

//...

function sendMain() {
  ipcRenderer.send('task', '退出程序')
}

main.js 主进程里面 ipcMain.on 进行监听,这里退出程序

ipcMain.on(channel, listener)

  • channel String
  • listener Function

监听 channel, 当新消息到达,将通过 listener(event, args...) 调用 listener.

还有个 ipcMain.once(channel, listener)为事件添加一个一次性用的listener 函数.这个 listener 只有在下次的消息到达 channel 时被请求调用,之后就被删除了.

const { app, BrowserWindow, ipcMain } = require('electron')
// 监听electron 加载完毕的时候的创建窗口等等
app.on('ready', function () {
  //...
})

ipcMain.on('task', (event, info) => {
  if (info === '退出程序') {
    app.quit()
  }
})

效果图

50

这样就方便里我做一些窗口交互,比如todoList,到时间了右下角弹出一个新的窗口进行提醒

8.3渲染进程到渲染进程

ipcRenderer.sendTo(webContentsId, channel, ...args)

  • webContentsId Number
  • channel String
  • ...args any[]

通过 channel 发送消息到带有 webContentsId 的窗口.

前提是要知道对应的渲染进程的ID

当然也可以让主进程作为中转站,先发到主进程在到其他的渲染进程

9.Vue + Electron

那么 Vue 怎么使用 Electron 打包呢?毕竟学习这个初衷,就是把 Vue 项目变成一个桌面应用,前面讲的都是原生的方法,那么继续往下面看吧

9.1你需要有个Vue项目

如果手上没有,那么用 vue ui 创建一个Vue项目/或者直接在命令行里用 vue create 创建

vue ui

相信大家都会,这里我就是简单用vue ui的建一个,这里大家可以略过

默认打开一个8000端口的服务,一个可视化的UI界面就出来了

image-20211005094817591

选择左下角 更多--->项目管理器

image-20211005095048918

创建

image-20211005095255377

选择好目录后在此创建

image-20211005095412222

填写一些基本信息,包管理我这里用 npm ,然后下一步

image-20211005095517636

选择预设,我这里选手动

image-20211005095738225

需要哪些插件选哪些,我这里就默认了,因为是个简单的例子

image-20211005095856488

选择 Vue 的版本2.x 还是 3这些按照你的习惯来,平时写什么选什么,下面的选项我选择的标准

image-20211005100020344

创建项目

我这里就不保存预设了,然后就是漫长的等待image-20211005100221788

创建完毕后运行改项目

image-20211005102004448

启动项目

image-20211005102132311

就会得到一个这样的默认页面

image-20211005102159772

好了创建项目完毕,继续

9.2添加 electron 插件

在插件-->添加插件 搜索 vue-cli-plugin-electron-builder,安装第一个

image-20211005102527050

我这里默认选择electorn 13.0.0版本

image-20211005102834003

安装完成后会出现在已安装插件里面

image-20211005103108921

当然也可以在 命令行中进行安装

vue add electron-builder

9.3运行

在当前vue项目下的命令行输入下面的命令运行

npm run electron:serve

image-20211005103731994

很好,已经运行出来了

9.4 package.json background.js

查看package.json文件找找主进程文件在哪

image-20211005104023792

image-20211005104135614

主进程文件是 background.js,这个文件在 Vue项目/src/下面

'use strict'

import { app, protocol, BrowserWindow } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
const isDevelopment = process.env.NODE_ENV !== 'production'

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

async function createWindow() {
  // Create the browser window.
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      
      // 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,
      contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION
    }
  })

  if (process.env.WEBPACK_DEV_SERVER_URL) {
    // Load the url of the dev server if in development mode
    await 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')
  }
}

// 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 (BrowserWindow.getAllWindows().length === 0) 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 installExtension(VUEJS_DEVTOOLS)
    } 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()
    })
  }
}

看到上面的主进程文件是不是很熟悉,你可以像以前一样做一些操作,使用node混写完成一些功能

9.5打包

上面我们只是运行出来了,上交的软件,老师总不会还特意去配环境,然后npm run electron:serve吧,显然是不可能的,那我们继续进行打包成一个可执行的文件exe

命令行执行下面的命令

npm run electron:build

打包出现的问题

我在打包的时候特别不顺利... 查来查去原来electron 是有问题

我给出的建议就是 把node_modules目录下的 electron 删除

cnpm 安装 electron

如果没有 cnpm 先进行安装

全局安装cnpm

npm install -g cnpm --registry=https://registry.npm.taobao.org
# 查看是否安装成功了
cnpm -v

重新安装 electron

cnpm i electron

打包

npm run electron:build

image-20211005131543735

打包完成,打包的文件就放在项目下的 dist_electron 里面

image-20211005131647326

9.6安装

双击就自动安装了

image-20211005131805110

image-20211005131940327

桌面上就出现这么一个应用图标

image-20211005132041458

9.7自定义

点进去查看没有问题,但是是不是太low 了,一点击就是自动安装,而且使用的默认图标

安装打包工具

cnpm i electron-builder --D

9.7.1.首先找一个 icon 图片

好像有插件可以把图片转为各种大小的icon

安装下,这样就不用网站上转图片了

cnpm i electron-icon-builder 

需要在package.jsonscripts添加build-icon指令

longzhu.jpg 这个图片自己找的 卡卡罗特 可以自行修改

output 是输出文件夹


  "scripts": {
    "build-icon": "electron-icon-builder --input=./public/longzhu.jpg --output=build --flatten"
  },

命令行输入

npm run build-icon

image-20211005141605242

build完成之后,生成了不同大小的图片

9.7.2.vue.config.js

因为我们之前安装的插件是 vue-cli-plugin-electron-builder ,而不是electron-builder

electron-builder打包普通项目,build 配置直接在package.json 里面写

vue-cli-plugin-electron-builderbuild 配置是需要在 项目根目录下 vue.config.js 里面配置

如果没有请新建

module.exports = {
  pluginOptions: {
    electronBuilder: {
      builderOptions: {
        appId: "com.test.app",
        productName: "Lang", //项目名,也是生成的安装文件名,即aDemo.exe
        copyright: "Copyright © 2021", //版权信息
        directories: {
          output: "./dist" //输出文件路径
        },
        win: {
          //win相关配置
          icon: "./build/icons/icon.ico", //图标,当前图标在根目录下,注意这里有两个坑
          target: [
            {
              target: "nsis", //利用nsis制作安装程序,打包文件的后缀为exe
              arch: [
                "x64", //64位
                "ia32" //32位
              ]
            }
          ]
        },
        nsis: {
          oneClick: false, //一键安装
          language: "2052", //安装语言
          perMachine: true, //应用所有用户
          allowToChangeInstallationDirectory: true //用户可以选择路径
        }
      }
    }
  }
};

9.7.3.执行打包

npm run electron:build

image-20211005143529020

OK,打包成功!

可能遇到的问题

打包的路上不是一帆风顺的,在这一步打包失败了,因为打包的时候去下载一些依赖,然后下载失败了

解决方法1:梯子

解决方法2: 可以参考这个

image-20211005143747806

打包好的东西

打包好的东西放在我们之前配置的build output: "./dist" //输出文件路径

image-20211005144321420

可以看出图标变了

image-20211005144400797

我们可以自定义安装文件夹了

image-20211005144559596

image-20211005144749686

好了,基础的打包工作就这么结束了,大家可以自己写属于自己的软件,这里只是一个简单的应用教学

结语

Electron 真的不错诶,建议大家学习的时候多看看官方的文档,虽然官方文档还有很多地方没有翻译完整,但是并不影响我们去学习他的热情,感觉版本迭代很快,官方文档显得又多又乱,大家可以在文档上面搜索

Electron官方文档

往期精彩

还不会Vue3?一篇笔记带你快速入门

还不会TS? 带你 TypeScript 快速入门

快速上手Vuex 到 手写简易 Vuex

从了解到深入虚拟DOM和实现diff算法

手写一个简易vue响应式带你了解响应式原理

从使用到自己实现简单Vue Router看这个就行了

前端面试必不可少的基础知识,虽然少但是你不能不知道