引言:
- 使用 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
官方快速启动
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
|
效果:
第一个Electron应用
主进程和渲染进程
由于 Electron 使用了 Chromium 来展示 web 页面,所以 Chromium 的多进程架构也被使用到。 每个 Electron 中的 web 页面运行在它自己的渲染进程中。
相当于浏览器窗口时主进程,每个tab是渲染进程。
主进程:
- 使用和系统对接的Electron API, 就是调用系统API;
- 创建渲染进程;
- 全面支持nodejs
- 只有一个,是程序的入口
渲染进程:
- 多个,单独
- 支持 nodejs\ dom api
- 使用部分Electron API
BrowserWindow
程序入口:main.js
监控main文件变化
避免修改重启应用:
1
| npm install nodemon --save-dev
|
修改mian.js
1 2 3
| "scripts": { "start": "electron ." }
|
改为:
1 2 3
| "scripts": { "start": "nodemon --watch main.js --exec electron ." }
|
main.js
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.on('ready', ()=>{ const mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { 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
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> require('./renderer.js') </script> </body> </html>
|
renderer.js
1 2 3 4 5 6 7 8 9 10
|
alert(process.versions.node)
window.addEventListener('DOMContentLoaded', () => { alert('dom加载完成') })
|
进程间通信
- 使用IPC 在进程间通信,Electron提供了接口
renderer process 和 main proces 互发消息
renderer.js
1 2 3 4 5 6 7 8 9 10 11 12
| 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const { app, BrowserWindow, ipcMain } = require('electron')
app.on('ready', ()=>{ ipcMain.on('message', (event, arg) => { console.log(arg) mainWindow.send('reply', 'hello from main') }) })
|
案例:本地音乐播放器
原型&功能点
主页
1、添加歌曲到曲库
2、播放暂停音乐
3、播放信息:歌名、时间、进度
4、删除歌曲
添加音乐页
1、选择音乐
2、列表展示
3、导入音乐:列表到主页
功能流程&文件结构
css样式使用bootstrap
1
| npm install bootstrap --save
|
文件结构
功能-添加音乐窗口
首页开始
renderer/index.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>
|
效果:
创建添加音乐窗口
index.html
1 2 3
| <script> require('./index.js') </script>
|
index.js
1 2 3 4 5 6
| const {ipcRenderer} = require('electron')
document.getElementById('add-music-button').addEventListener('click', () => { ipcRenderer.send('add-music-window') })
|
main.js
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') })
|
效果:
重构-封装创建窗口类
使用es6的class
main.js
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 } }
const finalConfig = {...basicConfig, ...config} super(finalConfig) this.loadFile(fileLocation) 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
1 2 3 4
| exports.$ = (id) => { return document.getElementById(id) }
|
add.html
1 2 3
| <script> require('./add.js') </script>
|
add.js
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
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) }) })
|
效果:
展示添加的文件列表
main.js
1 2 3 4 5 6 7 8 9 10
| ipcMain.on('open-music-file', (event) => { dialog.showOpenDialog({ }).then(result => { if(result.filePaths){ event.sender.send('selected-file', result.filePaths) } }) })
|
add.html
1
| <div id="musicList" class="mb-2">您还未选择任何音乐文件</div>
|
add.js
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)){ renderListHTML(path) } })
|
效果:
使用Electron store持久化数据
安装
1
| npm install electron-store --save
|
使用:
…
封装-持久化存储类
安装uuid库
MusicDataStore.js
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) { 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
|
使用存储类保存数据
查看数据库存储地址
1 2
| console.log(app.getPath('userData')) // C:\Users\Machine\AppData\Roaming\electron-quick-start
|
add.js
1 2 3
| $('add-music-button').addEventListener('click', () => { ipcRenderer.send('add-tracks',musicFilesPath) })
|
main.js
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
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
获取数据 渲染主窗口列表
主窗口播放音乐
应用打包与分发
1
| npm install electron-builder --save-dev
|
配置参考:
1 2
| https://www.electron.build/configuration/win#WindowsConfiguration-target https://github.com/zulip/zulip-electron/blob/master/package.json
|
package.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 图标
运行:
在dist目录下会生成安装包
Electron+Vue
electron-vue脚手架+elementUI
参考:
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的,所以直接用吧~
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后,跑起来能看到界面了
接下来就是加入element:参考https://element.eleme.cn/#/zh-CN/component/quickstart
安装
完整引入:
src/renderer/main.js中增加
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,改为
1
| let whiteListedModules = ['vue','element-ui']
|
css初始化样式重置,使用normalize.css
1
| npm install normalize.css --save
|
main.js
1
| import 'normalize.css/normalize.css'
|
修改src/renderer/components/LandingPage.vue,就是修改主页了,使用ElementUI的元素。
效果:
参考:
1 2
| 文档:https://panjiachen.github.io/vue-element-admin-site/zh/ 项目地址:https://github.com/PanJiaChen/electron-vue-admin
|
这个脚手架是 electron+vue+ElementUI整合的后台模板。拥有菜单+导航的基本功能。
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-20200103152246982 image-20200103152246982]()