electron

node GUI

1. NW.js和Electron都是基于NodeJs的GUI框架,使用HTML、CSS、JS来构建UI,处理与用户的交互
2. NW.js和Electron专注于开发桌面跨平台应用,而不是web服务器
3. NW.js的原称为Node-Webkit,目前由因特尔公司维护,而Electron由Github维护;
4. NW.js和Electron都使用了开源浏览器Chromium,并使用NodeJs访问系统、文件、网络...

Electron

1. Electron的原理:将Chromium和Node.js整合到一个运行时环境中,且可以跨平台打包;
    1. Chromium是一个谷歌开源浏览器,可以调用所有前端相关的API,且不需要考虑兼容性问题;
    2. 浏览器并不能访问原生的资源,但Node.Js的API支持在页面中和操作系统进行交互。
2. npm install -g electron:全局安装Electron
    1. 可以在Github上下载Electron的demo:electron-quick-start
    2. 进入demo目录,执行 cnpm install 安装依赖,执行 npm start 运行demo
3. electron-forge:相当于Electron的脚手架,可以快速创建、运行、打包项目;
    1. npm install -g electron-forge:全局安装
    2. electron-forge init myDemo:创建项目,默认使用 npm i 安装依赖;
    3. 如果执行失败,可以删除项目中的node_modules,执行 cnpm i,手动安装依赖;
4. 手动搭建项目:创建目录,在目录中创建main.js和index.html,执行cnpm init;
    1. main.js就是主进程,项目运行的入口文件;
    2. 一个应用只有一个主进程,但可以有多个渲染进程,一个窗口就表示一个渲染进程;
    3. electron .:运行项目。
5. 拖放打开文件
    <textarea id="container"></textarea>
    1. 调用H5的拖动API,获取拖放文件的路径
    let box = document.querySelector('#container')
    box.ondragenter=box.ondragover=box.ondragleave = (e) => {
        return false; --> 组织默认行为
    }
    box.ondrop=(e) => {
        e.preventDefault(); --> 阻止默认行为
        let path = e.dataTransfer.files[0].path; --> 获取文件的路径
    }
    2. 使用Node.js的fs模块读取文件,赋值给box.value:let fs = require('fs')
6. 相关模块
    1. Electron中的模块分为:主进程模块,渲染进程模块,公用模块;
    2. app:主进程模块,控制应用的生命周期,写法比较固定,注册固定的几个事件;
    3. BrowserWindow:主进程模块,窗口相关的模块;
    let win = BrowserWindow.getFocusedWindow();  -->获取当前窗口的对象
    win.center();  -->把窗口移动到屏幕中央
    4. remote:渲染进程的模块,用于间接调用部分主进程的模块,如BrowserWindow
    5. Menu:主进程的菜单模块,包括顶部菜单、右键菜单、系统托盘的右键菜单。
7. Menu菜单功能必须在窗口加载完成之后才能执行;
    app.on('ready', {
        ...... //创建窗口BrowserWindow
        require('./main/menu.js') //加载创建菜单的js模块
    })
    1. 虽然渲染进程也能通过remote模块调用Menu模块,实现自定义的顶部菜单,但可能会闪现默认
    的顶部菜单,所以还是建议在主进程中实现顶部菜单。

进程间通信IPC

1. 被Electron直接运行的脚本(package.json中指定的main节点)称为主进程,有且只有一个;
2. 用于展示的web界面都运行在一个独立的进程中,称为渲染进程;
3. 进程间通信的相关模块:ipcMain(主进程)、ipcRenderer(渲染进程)

主进程与渲染进程

1. 渲染进程向主进程发送异步消息:ipcRenderer.send('事件名1', 消息内容)
    1. 主进程中监听消息:ipcMain.on('事件名1', (event, arg) => { ... })
    2. 主进程给渲染进程回复消息:event.sender.send('事件名2', 消息内容)
    3. 渲染进程监听回复的消息:ipcRenderer.on('事件名2', (event, arg) => {})
2. 渲染进程向主进程发送同步消息:
    1. let msg = ipcRenderer.sendSync('事件名1', 消息内容)
    2. 与异步消息不同,主进程收到同步消息,必须回复,否则渲染进程会阻塞;
    ipcMain.on('事件名1', (event, arg) => {
        event.returnValue = 'sync reply' ---> 回复给渲染进程
    }) ----> sendSync()的返回值就是主进程回复的消息内容
3. 主进程把数据挂载到node的全局对象global上,渲染进程通过 getGlobal() 获取数据
    1. 主进程:global.username = 'abc';
    2. 渲染进程:remote.getGlobal('username');
    3. 这种方式污染了全局对象global,并不建议使用。

渲染进程与渲染进程

1. 渲染进程无法直接通信,最简单的方式是通过localStorage实现;
2. 通过BrowserWindow和webContents模块实现:
    1. webContents是一个事件发出者,负责渲染并控制网页,也是BrowserWindow的属性;
3. 渲染进程1向主进程发送消息,主进程创建渲染进程2,再向渲染进程2发送消息;
    ipcMain.on('事件名1', (event, arg) => { --> 主进程监听消息事件
        win = new BrowserWindow({width:400, height:300})
        win.loadURL(path.join('file:', __dirname, '../news.html'))
        win.webContents.on('did-finish-load', () => { --> 窗口加载完毕
            win.webContents.send('事件名2', arg) --> 向渲染进程2发送事件
        }) ---> 新的渲染进程中注册'事件名2'
    })
4. 渲染进程2向渲染进程1回复消息,则需要把渲染进程1的Id也发送给渲染进程2
    ipcMain.on('事件名1', (event, arg) => {
        //必须在新窗口加载之前获取当前窗口的Id,否则获取的新窗口的Id
        let winId = BrowserWindow.getFocusedWindow().id
        win = new BrowserWindow({width:400, height:300})
        win.loadURL(path.join('file:', __dirname, '../news.html'))
        win.webContents.on('did-finish-load', () => {
            win.webContents.send('事件名2', arg, winId)
        })
    })
5. 在新窗口中注册事件,向渲染进程1回复消息,在渲染进程1中监听回复事件
    ipcRenderer.on('事件名2', (event, arg, winId) => {
        let win = BrowserWindow.fromId(winId) -->获取渲染进程1的窗口对象
        win.webContents.send('事件名3', 'reply the msg') -->回复消息
    })

shell

1. shell模块可以操作用户的默认浏览器,调用资源管理器;
2. 调用用户的默认浏览器,打开指定网址:shell.openExternal('https://...')
3. shell.showItemInFolder()/openItem():资源管理器的相关操作;
4. <webview>:与HTML的<iframe>相似,但webview与应用程序不在同一个进程;
    1. webview没有渲染进程的权限,并且与应用之间的交互都是异步的,保证应用的安全性不受嵌入
    内容的影响;
    2. <webview src="https://www.baidu.com"></webview>

dialog

1. dialog模块:不仅可以弹出信息提示框,也可以实现本地文件的打开与保存;
2. 信息提示框
    1. dialog.showErrorBox('title', 'content'):错误信息提示框;
    2. dialog.showMessageBox({}, (index)=>{...}):信息提示框;
    3. 异步变同步:let index = dialog.showMessageBox({})
3. 打开文件/目录:dialog.showOpenDialog({}, (path) => {...})
    1. 打开功能只是回调文件/目录的路径,并不能获取文件/目录的内容;
    2. 操作文件/目录的功能,需要通过nodejs实现。
4. 保存文件:dialog.showSaveDialog({}, (path) => {...})
    1. showSaveDialog()只回调保存路径,并不是真的保存,不会在本地生成任何文件;
    2. 保存功能需要借助nodejs实现,通过fs模块把内容写入本地。
5. 打印当前窗口:BrowserWindow.getFocusedWindow().webContents.print()
6. app.quit():退出应用,Electron使用的是单例模式,此时不必考虑性能问题。

系统托盘

1. Tray模块:主进程的系统托盘模块;
2. 实现系统托盘:let tray = new Tray(path.join(__dirname, 'icon.png'))
3. 设置托盘名称:tray.setToolTip('title'),鼠标悬停在托盘图标上弹出;
4. 创建托盘的右键菜单
    1. Menu模块创建菜单:let menu = Menu.buildFromTemplate([{...}, {...}])
    2. 设置托盘的右键菜单:tray.setContextMenu(menu)
5. 关闭窗口,则隐藏到系统托盘;双击系统托盘,则重新打开窗口
    1. 关闭窗口
    let win = BrowserWindow.getFocusedWindow() --> 获取当前的窗口对象
    win.on('close', (e) => { --> 监听关闭按钮的事件
        if(win.isFocused()) { -->点击托盘右键菜单上的'退出',则关闭窗口
            win = null
        } else {
            e.preventDefault() --> 阻止默认的关闭事件
            win.hide() --> 隐藏窗口到系统托盘
        }
    })
    2. app.quit()会回调'close'事件,所以需要判断当前关闭的窗口是否拥有焦点;
    3. 如果当前窗口拥有焦点,则隐藏窗口;如果没有焦点,表示窗口已经处于隐藏状态,则关闭窗口
    4. 打开窗口
    tray.on('double-click', () => { --> 监听系统托盘的双击事件
        win.show()
    })
6. 托盘图标的闪烁:设置定时器setInterval(),每隔一段时间就重新设置一次图标。

消息通知

1. Electron的消息通知是通过H5的API实现的,右下方弹出通知框的效果;
    let option = {
        title: '通知标题', body: '通知内容', icon: '通知图标的路径'
    }
    let notification = new window.Notification(option.title, option)
    notification.onclick = ()=>{...} --> 通知框的点击事件
2. 监听网络变化:也是通过H5实现
    1. 网络正常:window.addEventListener('online', () => { ... })
    2. 网络断开:window.addEventListener('offline', () => { ... })

其他模块

1. globalShortcut模块:主进程模块,注册全局快捷键,在窗口创建之前注册;
    app.on('ready', () => {
        ... //注册全局快捷键 ---> ... //创建窗口 ---> ... //设置菜单
    })
    1. 注册全局快捷键:globalShortcut.register('ctrl+e', () => { ... })
    2. 判断是否注册成功:globalShortcut.isRegistered('ctrl+e')
    3. 窗口关闭时要取消全局快捷键:globalShortcut.unregister('ctrl+e')
    app.on('will-quit', () => { globalShortcut.unregister('ctrl+e') })
2. clipboard模块:公用模块,剪切板事件
    1. 复制操作:clipboard.writeText('复制内容'),系统的Ctrl+C
    2. 手动获取复制的内容:let text = clipboard.readText(),系统的Ctrl+V
    3. 除了读取文本,剪切板还能读取HTML、Image、RTF...
3. nativeImage模块:公用模块,用于读取、传递图片
    1. nativeImage.createEmpty():创建一个空的nativeImage对象;
    2. nativeImage.createFromPath('路径'):根据图片路径,创建nativeImage对象
4. 剪切板无法直接读取图片,需要借助nativeImage模块;
    let image = nativeImage.createFromPath('static/back.jpg')
    clipboard.writeImage(image) --> 复制图片
    let data = clipboard.readImage() --> 读取复制的图片,返回nativeImage对象
    let src = data.toDataURL() --> 转为图片的Base64编码
    1. 创建一个<img />:let img = new Image()
    2. 设置src属性:img.src = src
    3. 插入到HTML中:document.body.appendChild(img)

Electron-Vue

1. Electron-Vue:Electron结合Vue开发单页面桌面应用;
    1. 安装环境:cnpm install electron -g、cnpm install vue-cli -g
    2. 创建项目:vue init simulatedgreg/electron-vue my-project
    3. 下载依赖:cd my-project ---> cnpm install
    4. 运行项目:npm run dev
2. 在main.js中默认引入了electron,在Vue组件中通过this.$electron能直接使用;
    1. 主进程模块:src/main/index.js
    2. 渲染进程模块:src/renderer目录下的所有Vue组件
3. 在Vue组件中使用Node.js的模块:const path = require('path');
4. 打包后的electron应用无法找到图片
    1. 对于static目录下的图片:<img :src="'./static/img/a.png'" />
    2. 对于电脑本地的图片,直接使用图片的绝对路径,另外还要关闭窗口的安全模式
    new BrowserWindow({ webPreferences: { webSecurity: false } });

窗口操作

1. frame:false:无边框化,去除顶部菜单、最小化、最大化和关闭;
    let win = new BrowserWindow({..., useContentSize:true, frame:false})
    1. 无边框化之后,窗口的可拖拽区也随之消失,需要手动设置拖拽区,鼠标可以拖动窗口;
    2. 设置拖拽区的CSS属性:-webkit-app-region: drag
    body{ -webkit-app-region: drag } --> 设置整个窗口为拖拽区
    3. 拖拽区内的按钮必须标记为no-draggable,否则将不可点击;
    button{ -webkit-app-region: no-drag; }
2. win.setMenu(null):只隐藏顶部菜单;
3. 最大化、最小化、隐藏、关闭窗口
    1. win.hide():隐藏窗口; win.isVisible():窗口是否可见; win.close():关闭窗口
    2. win.maximize():最大化窗口,如果窗口尚未显示,这也将会显示,但不会有焦点;
    3. win.unmaximize():取消窗口最大化;  win.isMaximized():判断窗口是否最大化;
    5. win.minimize():最小化窗口;  win.isMinimized():判断窗口是否最小化;
    6. win.restore():将窗口从最小化状态恢复到以前的状态。

NeDB

1. NeDB:Nodejs实现的一个NoSQL嵌入式数据库;
    1. 与Android上的Sqlite类似,都是嵌入式数据库,一般用于本地数据的持久化;
    2. 不同的是,Sqlite是MySql类型的,使用SQL语句操作,而NeDB的操作类似于MongoDB;
    3. 可以充当内存数据库,也可以用来实现本地存储,甚至可以在浏览器中使用。
2. 使用方式:
    1. 安装操作模块:cnpm install nedb --save
    2. 在src/renderer目录下创建datastore.js,import Datastore from 'nedb'
    import path from 'path'  import {remote} from 'electron'
    export default new Datasotre({
        autoload: true,
        filename: path.join(remote.app.getPath('userData'), '/data.db')
    })
    3. 设置NeDB保存在userData目录中,该目录空间专门为应用程序所保留,不会被篡改;
    4. 在main.js中,把数据库操作对象绑定到原型上:
    import db from './datastore'  --> Vue.prototype.$db = db

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值