avatar

目录
Electron-跨平台桌面开发

引言:

  • 使用 JavaScript,HTML 和 CSS 构建跨平台的桌面应用程序
  • Electron 基于 Chromium 和 Node.js
  • Electron 兼容 Mac、Windows 和 Linux,可以构建出三个平台的应用程序。

参考:

概述&启动

  • 使用html\css\js开发桌面跨平台应用(mac/linux/windows)
  • 基于Chromium和Nodejs(就是把浏览器一起给打包进去)
  • 国内案例:微信、迅雷桌面端
  • 使用的技术:nodejs\npm\前端三套

开发环境

node git npm

编辑器:微软的 Visual Studio Code

官方快速启动

Code
1
2
3
4
5
6
7
8
# 克隆示例项目的仓库
$ git clone https://github.com/electron/electron-quick-start

# 进入这个仓库
$ cd electron-quick-start

# 安装依赖并运行
$ npm install && npm start

效果:

image-20191231114712989

第一个Electron应用

主进程和渲染进程

由于 Electron 使用了 Chromium 来展示 web 页面,所以 Chromium 的多进程架构也被使用到。 每个 Electron 中的 web 页面运行在它自己的渲染进程中。

相当于浏览器窗口时主进程,每个tab是渲染进程。

主进程:

  • 使用和系统对接的Electron API, 就是调用系统API;
  • 创建渲染进程;
  • 全面支持nodejs
  • 只有一个,是程序的入口

渲染进程:

  • 多个,单独
  • 支持 nodejs\ dom api
  • 使用部分Electron API

BrowserWindow

程序入口:main.js

监控main文件变化

避免修改重启应用:

Code
1
npm install nodemon --save-dev

修改mian.js

Code
1
2
3
"scripts": {
"start": "electron ."
}

改为:

Code
1
2
3
"scripts": {
"start": "nodemon --watch main.js --exec electron ."
}

main.js

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 加载模块
const { app, BrowserWindow } = require('electron')

// 当app加载结束时,回调函数
app.on('ready', ()=>{
// 创建主窗口
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
// 允许使用nodejs的api
nodeIntegration: true
}
})
// 加载文件
mainWindow.loadFile('index.html')

// 创建第二个窗口
const secondWindow = new BrowserWindow({
width: 400,
height: 300,
webPreferences: {
nodeIntegration: true
},
// 父窗口关闭,子窗口也会关闭
parent: mainWindow
})
secondWindow.loadFile('second.html')
})

index.html

html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World!</title>
</head>
<body>
<h1>Hello World! 2333</h1>
<script>
// 使用node语法
require('./renderer.js')
</script>
</body>
</html>

renderer.js

Javascript
1
2
3
4
5
6
7
8
9
10
// 可使用node api 和 dom api

// node api
alert(process.versions.node)


// dom api
window.addEventListener('DOMContentLoaded', () => {
alert('dom加载完成')
})

进程间通信

  • 使用IPC 在进程间通信,Electron提供了接口
image-20191231130105472

renderer process 和 main proces 互发消息

renderer.js

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
// 通过 renderer process 向 main proces 发送消息
const { ipcRenderer } = require('electron')

window.addEventListener('DOMContentLoaded', () => {
// 发送
ipcRenderer.send('message', 'hello from renderer')
// 接收
ipcRenderer.on('reply', (event, arg) => {
// 打印到页面
document.getElementById('message').innerHTML = arg
})
})

main.js

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ipcMain
const { app, BrowserWindow, ipcMain } = require('electron')

app.on('ready', ()=>{
// ...

ipcMain.on('message', (event, arg) => {
// 接受
console.log(arg)
// 回复
// event.sender.send('reply', 'hello from main')
// 等价上面语法
mainWindow.send('reply', 'hello from main')
})
})

案例:本地音乐播放器

原型&功能点

主页

1、添加歌曲到曲库

2、播放暂停音乐

3、播放信息:歌名、时间、进度

4、删除歌曲

添加音乐页

1、选择音乐

2、列表展示

3、导入音乐:列表到主页

功能流程&文件结构

image-20191231142422764

css样式使用bootstrap

Code
1
npm install bootstrap --save

文件结构

image-20191231143024376

功能-添加音乐窗口

首页开始

renderer/index.html

html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- 主页面 -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>本地播放器</title>
<!-- 引入样式 -->
<link rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-4">
<h1>我的播放器</h1>
<button type="button" class="btn btn-primary btn-lg btn-block mt-4">
添加歌曲到曲库
</button>
</div>
</body>
</html>

效果:

image-20191231144659769

创建添加音乐窗口

index.html

html
1
2
3
<script>
require('./index.js')
</script>

index.js

Javascript
1
2
3
4
5
6
// 主窗口
const {ipcRenderer} = require('electron')
// 只有main process可以添加窗口,所以需要通信
document.getElementById('add-music-button').addEventListener('click', () => {
ipcRenderer.send('add-music-window')
})

main.js

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
...
ipcMain.on('add-music-window', () => {
// 添加窗口
const addWindow = new BrowserWindow({
width: 500,
height: 400,
webPreferences: {
nodeIntegration: true
},
parent: mainWindow
})
addWindow.loadFile('./renderer/add.html')
})

效果:

image-20191231163804884

重构-封装创建窗口类

使用es6的class

main.js

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 封装创建窗口类
class AppWindow extends BrowserWindow {
constructor(config, fileLocation) {
const basicConfig = {
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
}

// 使用config配置覆盖默认配置
// const finalConfig = Object.assign(basicConfig, config)
// 第二种写法
const finalConfig = {...basicConfig, ...config}
super(finalConfig)
this.loadFile(fileLocation)
// 优化:在加载页面时,渲染进程第一次完成绘制时,会发出 ready-to-show 事件 。 在此事件后显示窗口将没有视觉闪烁
this.once('ready-to-show', () => {
this.show()
})
}
}

// 重构代码
app.on('ready', ()=>{
const mainWindow = new AppWindow({},'./renderer/index.html')

ipcMain.on('add-music-window', () => {
const addWindow = new AppWindow({
width: 500,
height: 400,
parent: mainWindow
},'./renderer/add.html')
})
})

使用Dialog模块添加音乐文件

Dialog模块可以调用原生api,打开文件。

分装工具类helper.js

Javascript
1
2
3
4
// 工具类
exports.$ = (id) => {
return document.getElementById(id)
}

add.html

html
1
2
3
<script>
require('./add.js')
</script>

add.js

Javascript
1
2
3
4
5
6
7
// 添加音乐
const {ipcRenderer} = require('electron')
const { $ } = require('./helper')
// 重构
$('select-music-button').addEventListener('click', () => {
ipcRenderer.send('open-music-file')
})

main.js

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
// 选择音乐
ipcMain.on('open-music-file', () => {
dialog.showOpenDialog({
properties: ['openFile', 'multiSelections'],
filters: [
{ name: 'Music', extensions: ['mp3'] }
]
}).then(result => {
console.log(result.filePaths)
}).catch(err => {
console.log(err)
})
})

效果:

image-20191231163945891

展示添加的文件列表

main.js

Javascript
1
2
3
4
5
6
7
8
9
10
ipcMain.on('open-music-file', (event) => {
dialog.showOpenDialog({
// ...
}).then(result => {
if(result.filePaths){
// 传递给add
event.sender.send('selected-file', result.filePaths)
}
})
})

add.html

html
1
<div id="musicList" class="mb-2">您还未选择任何音乐文件</div>

add.js

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const renderListHTML = (pathes) => {
const musicList = $('musicList')
const musicItemsHTML = pathes.reduce((html, music) => {
html += `<li class="list-group-item">${path.basename(music)}</li>`
return html
}, '')

musicList.innerHTML = `<ul class="list-group">${musicItemsHTML}</ul>`
}

ipcRenderer.on('selected-file', (event, path) => {
if(Array.isArray(path)){
// 渲染歌曲List
renderListHTML(path)
}
})

效果:

image-20191231164011422

使用Electron store持久化数据

安装

Code
1
npm install electron-store --save

使用:

封装-持久化存储类

安装uuid库

Code
1
npm install uuid --save

MusicDataStore.js

Javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
const Store = require('electron-store')
const uuidv4 = require('uuid/v4')
const path = require('path')

class DataStore extends Store {
constructor(settings) {
super(settings)
this.tracks = this.get('tracks') || []
}

// 保存音乐文件
saveTracks() {
this.set('tracks', this.tracks)
return this
}

// 获取音乐信息
getTracks() {
return this.get('tracks') || []
}

// 添加音乐信息
addTracks(tracks) {
// id 文件路径 文件名称 去重
const tracksWithProps = tracks.map(track => {
return {
id: uuidv4(),
path: track,
fileName: path.basename(track)
}
}).filter(track => {
const currentTacksPath = this.getTracks().map(track => track.path)
return currentTacksPath.indexOf(track.path) < 0
})
this.tracks = [...thid.tracks, ...tracksWithProps]
return this.saveTracks();
}
}

module.exports = DataStore

使用存储类保存数据

查看数据库存储地址

Code
1
2
console.log(app.getPath('userData'))
// C:\Users\Machine\AppData\Roaming\electron-quick-start

add.js

Javascript
1
2
3
$('add-music-button').addEventListener('click', () => {
ipcRenderer.send('add-tracks',musicFilesPath)
})

main.js

Javascript
1
2
3
4
5
// 存储数据
ipcMain.on('add-tracks', (event,tracks) => {
const updatedTracks = myStore.addTracks(tracks).getTracks()
console.log(updatedTracks)
})

效果:C:\Users\Machine\AppData\Roaming\electron-quick-start\Music Data.json

json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"tracks": [
{
"id": "cc3a3aee-3533-492e-b3da-8cc524db7420",
"path": "C:\\Users\\Machine\\Desktop\\mp3_test_music\\bad guy.mp3",
"fileName": "bad guy.mp3"
},
{
"id": "650f80d2-9717-45d2-91b7-d355de85d95a",
"path": "C:\\Users\\Machine\\Desktop\\mp3_test_music\\Lemon.mp3",
"fileName": "Lemon.mp3"
},
{
"id": "94b211e2-6d55-4f97-b272-0e4b6e8286db",
"path": "C:\\Users\\Machine\\Desktop\\mp3_test_music\\起风了.mp3",
"fileName": "起风了.mp3"
}
]
}

功能-播放器窗口

TODO

获取数据 渲染主窗口列表

主窗口播放音乐

应用打包与分发

Code
1
npm install electron-builder --save-dev

配置参考:

Code
1
2
https://www.electron.build/configuration/win#WindowsConfiguration-target
https://github.com/zulip/zulip-electron/blob/master/package.json

package.json

json
1
2
3
4
5
6
7
8
9
10
11
12
{
"scripts": {
"dist": "electron-builder --win --x64"
},
"build": {
"appId": "com.xxx.app",
"win": {
"target": ["nsis","zip"],
"icon": "build/icon_256_multi.ico"
}
}
}

ico制作需要俄罗斯套娃的ico格式,否则有些图标显示不出来,参考如何制作俄罗斯套娃一般的 electron 专用 ico 图标

运行:

Code
1
npm run dist

在dist目录下会生成安装包

Electron+Vue

electron-vue脚手架+elementUI

参考:

Code
1
2
3
https://www.cnblogs.com/adorkable/p/11069923.html
https://simulatedgreg.gitbooks.io/electron-vue/content/cn/
https://element.eleme.cn/#/zh-CN/component/installation

electron本就是一个加强版的浏览器的外壳,所以结合前端框架是很当然的~

这里构建一个Electron+Vue+Element首页布局效果。

有人写了脚手架electron-vue,基于vue-cli的,所以直接用吧~

Code
1
2
3
4
5
6
7
8
# 安装 vue-cli 和 脚手架样板代码
npm install -g vue-cli
vue init simulatedgreg/electron-vue my-project

# 安装依赖并运行你的程序
cd my-project
npm install
npm run dev

报错了…process is not defined…

好像是node版本太高的问题,不想改node版本,所以找了解决方案如下:

https://www.jianshu.com/p/bdf0a23e7257

解决bug后,跑起来能看到界面了

image-20200103133302022

接下来就是加入element:参考https://element.eleme.cn/#/zh-CN/component/quickstart

安装

Code
1
npm i element-ui -S

完整引入:

src/renderer/main.js中增加

Code
1
2
3
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);

表格控件显示不正常的解决:element-ui需要加入到白名单里面

.electron-vue/webpack.renderer.config.js,改为

Code
1
let whiteListedModules = ['vue','element-ui']

css初始化样式重置,使用normalize.css

Code
1
npm install normalize.css --save

main.js

Code
1
import 'normalize.css/normalize.css'

修改src/renderer/components/LandingPage.vue,就是修改主页了,使用ElementUI的元素。

效果:

image-20200103145743674

electron-vue-admin脚手架

参考:

Code
1
2
文档:https://panjiachen.github.io/vue-element-admin-site/zh/
项目地址:https://github.com/PanJiaChen/electron-vue-admin

这个脚手架是 electron+vue+ElementUI整合的后台模板。拥有菜单+导航的基本功能。

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# install dependencies
npm install

# serve with hot reload at localhost:9080
npm run dev

# build electron app for production
npm run build

# lint all JS/Vue component files in `app/src`
npm run lint

# run webpack in production
npm run pack

效果:

image-20200103152029561

进入:

image-20200103152246982
文章作者: Machine
文章链接: https://machine4869.gitee.io/2019/12/31/20191231112532671/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 哑舍
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论