我的博客原文:http://localhost:4000/blog/2019/12/31/20191231112532671/
引言:
- 使用 JavaScript,HTML 和 CSS 构建跨平台的桌面应用程序
- Electron 基于 Chromium 和 Node.js
- Electron 兼容 Mac、Windows 和 Linux,可以构建出三个平台的应用程序。
参考:
- https://www.imooc.com/learn/1198
概述&启动
- 使用html\css\js开发桌面跨平台应用(mac/linux/windows)
- 基于Chromium和Nodejs(就是把浏览器一起给打包进去)
- 国内案例:微信、迅雷桌面端
- 使用的技术:nodejs\npm\前端三套
开发环境
node git npm
编辑器:微软的 Visual Studio Code
官方快速启动
# 克隆示例项目的仓库
$ git clone https://github.com/electron/electron-quick-start
# 进入这个仓库
$ cd electron-quick-start
# 安装依赖并运行
$ npm install && npm start
效果:
第一个Electron应用
主进程和渲染进程
由于 Electron 使用了 Chromium 来展示 web 页面,所以 Chromium 的多进程架构也被使用到。 每个 Electron 中的 web 页面运行在它自己的渲染进程中。
相当于浏览器窗口时主进程,每个tab是渲染进程。
主进程:
- 使用和系统对接的Electron API, 就是调用系统API;
- 创建渲染进程;
- 全面支持nodejs
- 只有一个,是程序的入口
渲染进程:
- 多个,单独
- 支持 nodejs\ dom api
- 使用部分Electron API
BrowserWindow
程序入口:main.js
监控main文件变化
避免修改重启应用:
npm install nodemon --save-dev
修改mian.js
"scripts": {
"start": "electron ."
}
改为:
"scripts": {
"start": "nodemon --watch main.js --exec electron ."
}
main.js
// 加载模块
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
<!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
// 可使用node api 和 dom api
// node api
alert(process.versions.node)
// dom api
window.addEventListener('DOMContentLoaded', () => {
alert('dom加载完成')
})
进程间通信
- 使用IPC 在进程间通信,Electron提供了接口
renderer process 和 main proces 互发消息
renderer.js
// 通过 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
// 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、导入音乐:列表到主页
功能流程&文件结构
css样式使用bootstrap
npm install bootstrap --save
文件结构
功能-添加音乐窗口
首页开始
renderer/index.html
<!-- 主页面 -->
<!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>
效果:
创建添加音乐窗口
index.html
<script>
require('./index.js')
</script>
index.js
// 主窗口
const {ipcRenderer} = require('electron')
// 只有main process可以添加窗口,所以需要通信
document.getElementById('add-music-button').addEventListener('click', () => {
ipcRenderer.send('add-music-window')
})
main.js
...
ipcMain.on('add-music-window', () => {
// 添加窗口
const addWindow = new BrowserWindow({
width: 500,
height: 400,
webPreferences: {
nodeIntegration: true
},
parent: mainWindow
})
addWindow.loadFile('./renderer/add.html')
})
效果:
重构-封装创建窗口类
使用es6的class
main.js
// 封装创建窗口类
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
// 工具类
exports.$ = (id) => {
return document.getElementById(id)
}
add.html
<script>
require('./add.js')
</script>
add.js
// 添加音乐
const {ipcRenderer} = require('electron')
const { $ } = require('./helper')
// 重构
$('select-music-button').addEventListener('click', () => {
ipcRenderer.send('open-music-file')
})
main.js
// 选择音乐
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)
})
})
效果:
展示添加的文件列表
main.js
ipcMain.on('open-music-file', (event) => {
dialog.showOpenDialog({
// ...
}).then(result => {
if(result.filePaths){
// 传递给add
event.sender.send('selected-file', result.filePaths)
}
})
})
add.html
<div id="musicList" class="mb-2">您还未选择任何音乐文件</div>
add.js
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)
}
})
效果:
使用Electron store持久化数据
-
数据库软件(太大)
-
HTML5的浏览器对象(缓存会过期)
-
本地文件
提供了api模块electron-store: https://github.com/sindresorhus/electron-store
安装
npm install electron-store --save
使用:
…
封装-持久化存储类
安装uuid库
npm install uuid --save
MusicDataStore.js
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
使用存储类保存数据
查看数据库存储地址
console.log(app.getPath('userData'))
// C:\Users\Machine\AppData\Roaming\electron-quick-start
add.js
$('add-music-button').addEventListener('click', () => {
ipcRenderer.send('add-tracks',musicFilesPath)
})
main.js
// 存储数据
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
{
"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
获取数据 渲染主窗口列表
主窗口播放音乐
应用打包与分发
- Electron builder: https://github.com/electron-userland/electron-builder
npm install electron-builder --save-dev
配置参考:
https://www.electron.build/configuration/win#WindowsConfiguration-target
https://github.com/zulip/zulip-electron/blob/master/package.json
package.json
{
"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 图标
运行:
npm run dist
在dist目录下会生成安装包
Electron+Vue
electron-vue脚手架+elementUI
参考:
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的,所以直接用吧~
# 安装 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后,跑起来能看到界面了
接下来就是加入element:参考https://element.eleme.cn/#/zh-CN/component/quickstart
安装
npm i element-ui -S
完整引入:
src/renderer/main.js中增加
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
表格控件显示不正常的解决:element-ui需要加入到白名单里面
.electron-vue/webpack.renderer.config.js,改为
let whiteListedModules = ['vue','element-ui']
css初始化样式重置,使用normalize.css
npm install normalize.css --save
main.js
import 'normalize.css/normalize.css'
修改src/renderer/components/LandingPage.vue,就是修改主页了,使用ElementUI的元素。
效果:
electron-vue-admin脚手架
参考:
文档:https://panjiachen.github.io/vue-element-admin-site/zh/
项目地址:https://github.com/PanJiaChen/electron-vue-admin
这个脚手架是 electron+vue+ElementUI整合的后台模板。拥有菜单+导航的基本功能。
# 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
效果:
进入: