# 一、前言
NW.js和Electron都可以用前端的知识来开发桌面应用。NW.js和Electron起初是同一 个作者开发。后来种种原因分为两个产品。一个命名为NW.js(英特尔公司提供技术支持)、 另一命名为Electron(Github 公司提供技术支持)。NW.js和Electron可以用Nodejs中几乎所有的模块。NW.js和Electron不仅可以把html写的web页面打包成跨平台可以安装到电脑上面的软件,也可以通过javascript访问操作 系统原生的UI和Api(控制窗口、添加菜单项目、托盘应用菜单、读写文件、访问剪贴板)。
github的atom编辑器、微软的vscode编辑器,包括阿里内部的一些 软件也是用electron开发的
1. Electron 是由谁开发的?
Electron是由Github开发
2. Electron 是什么?
Electron是一个用HTML,CSS和JavaScript来构建跨平台桌面应用程序的一个开源库
3. Electron 把 HTML,CSS 和 JavaScript 组合的程序构建为跨平台桌面应用程序的原理 是什么?
原理为
Electron通过将Chromium和Node.js合并到同一个运行时环境中,并将其打包为Mac,Windows和Linux系统下的应用来实现这一目的。
4. Electron 何时出现的,为什么会出现?
Electron于2013年作为构建Atom的框架而被开发出来。这两个项目在2014春季开源。 (Atom:为 Github 上可编程的文本编辑器)
一些历史:
2013年4月Atom Shell项目启动 。2014年5月Atom Shell被开源 。2015年4月Atom Shell被重命名为Electron2016年5月Electron发布了v1.0.0版本
5. Electron 当前流行程度?
目前
Electron已成为开源开发者、初创企业和老牌公司常用的开发工具。
6. Electron 当前由那些人在维护支持?
Electron当前由Github上的一支团队和一群活跃的贡献者维护。有些贡献者是独立开发者,有些则在用Electron构建应用的大型公司里工作。
7. Electron 新版本多久发布一次?
Electron的版本发布相当频繁。每当Chromium、Node.js有重要的bug修复,新API或是版本更新时Electron会发布新版本。
- 一般 
Chromium发行新的稳定版后的一到两周之内,Electron中Chromium的版本会对其进行更新,具体时间根据升级所需的工作量而定。 一般Node.js发行新的稳定版一个月后,Electron中Node.js的版本会对其进行更新,具 体时间根据升级所需的工作量而定。 
8. Electron 的核心理念是什么?
Electron的核心理念是:保持Electron的体积小和可持续性开发。 如:为了保持Electron的小巧 (文件体积) 和可持续性开发 (以防依赖库和API的泛滥) ,Electron限制了所使用的核心项目的数量。 比如Electron只用了Chromium的渲染库而不是其全部组件。这使得升级Chromium更加容易,但也意味着Electron缺少了Google Chrome里的一些浏览器相关的特性。 添加到Electron的新功能应该主要是原生API。 如果可以的话,一个功能应该尽可能的成 为一个Node.js模块。
9. Electron 当前的最新版本为多少?
Electron当前的最新版本为4.0.1(当前时间为2019年1月6号)
# 二、环境搭建
1. 安装 electron
npm install -g electron
2. 克隆一个仓库、快速启动一个项目
# 克隆示例项目的仓库
git clone https://github.com/electron/electron-quick-start
# 进入这个仓库
cd electron-quick-start
# 安装依赖并运行
npm install && npm start
3. 手动搭建一个 electron 项目
- 新建一个项目目录 例如: 
electrondemo01 - 在 
electrondemo01目录下面新建三个文件:index.html、main.js、package.json index.html里面用css进行布局(以前怎么写现在还是怎么写)- 在 
main.js中写如下代码 
var electron =require('electron'); //electron 对象的引用
const app=electron.app; //BrowserWindow 类的引用
const BrowserWindow=electron.BrowserWindow;
let mainWindow=null; //监听应用准备完成的事件 app.on('ready',function(){
//监听应用准备完成的事件
app.on('ready',function(){
    //创建窗口
    mainWindow=new BrowserWindow({width: 800, height: 600}); mainWindow.loadFile('index.html');
    
    mainWindow.on('closed', function () {
        mainWindow = null; })
    })
})
//监听所有窗口关闭的事件 
app.on('window-all-closed', function () {
    // On OS X 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(); 
    }
})
- 运行
 
electron . #注意:命令后面有个点
4. electron-forge 搭建一个 electron 项目
electron-forge相当于electron的一个脚手架,可以让我们更方便的创建、运行、打包electron项目
npm install -g electron-forge 
electron-forge init my-new-app 
cd my-new-app
npm start
# 三、Electron 运行流程
# 3.1 Electron 运行的流程

# 3.2 Electron 主进程和渲染进程
Electron运行package.json的main脚本的进程被称为主进程。- 在主进程中运行的脚本通过创建 
web页面来展示用户界面。 一个Electron应用总是有且只有一个主进程。 - 由于 
Electron使用了Chromium(谷歌浏览器)来展示web页面,所以Chromium的 多进程架构也被使用到。 每个Electron中的web页面运行在它自己的渲染进程中。 - 主进程使用 
BrowserWindow实例创建页面。每个BrowserWindow实例都在自己的渲 染进程里运行页面。 当一个BrowserWindow实例被销毁后,相应的渲染进程也会被终止 


- 进程:进程是计算机中的程序关于某数据集合上的一次运行活动,是 系统进行资源分配和调度的基本单位,是操作系统结构的基础。
 - 线程:在一个程序里的一个执行路线就叫做线程(
thread)。更准确的定义是: 线程是“一个进程内部的控制序列”。 - 线程和进程:一个程序至少有一个进程,一个进程至少有一个线程
 
# 3.3 Electron 渲染进程中通过 Nodejs 读取本地文件
在普通的浏览器中,
web页面通常在一个沙盒环境中运行,不被允许去接触原生的资源。 然而Electron的用户在Node.js的API支持下可以在页面中和操作系统进行一些底层交 互。Nodejs在主进程和渲染进程中都可以使用。渲染进程因为安全限制,不能直接操作生GUI。虽然如此,因为集成了 Nodejs,渲染进程也有了操作系统底层API的能力,Nodejs中常用的Path、fs、Crypto等模块在Electron可以直接使用,方便我们处理链接、路径、 文件MD5等,同时npm还有成千上万的模块供我们选择。
var fs = require('fs');
var content = document.getElementById('content'); 
var button = document.getElementById('button');
button.addEventListener('click',function(e){
    fs.readFile('package.json','utf8',function(err,data){ 
        content.textContent = data;
        console.log(data);
    }); 
});
# 3.4 Electron 开启调试模式
 mainWindow.webContents.openDevTools();

# 四、Electron 模块介绍
Electron模块介绍、remote模块、通 过BrowserWindow打开新窗口
# 4.1 Electron 主进程和渲染进程中的模块

# 4.2 Electron remote 模块
remote模块提供了一种在渲染进程(网页)和主进程之间进行进程间通讯(IPC)的简便途径
Electron中, 与GUI相关的模块(如dialog,menu等)只存在于主进程,而不在渲染进程中 。为了能从渲染进程中使用它们,需要用ipc模块来给主进程发送进程间消息。使用remote模块,可以调用主进程对象的方法,而无需显式地发送进程间消息,这类似于Java的RMI
# 4.3 通过BrowserWindow 打开新窗口
Electron渲染进程中通过remote模块调用主进程中的BrowserWindow打开新窗口
https://electronjs.org/docs/api/browser-window
// 主进程代码
const electron = require('electron'); 
// 控制应用生命周期的模块 
const {app} = electron;
// 创建本地浏览器窗口的模块 
const {BrowserWindow} = electron;
// 指向窗口对象的一个全局引用,如果没有这个引用,那么当该 javascript 对象被垃圾回收 的
// 时候该窗口将会自动关闭
let win;
function createWindow() {
    // 创建一个新的浏览器窗口
    win = new BrowserWindow({width: 1104, height: 620});//570+50
    
    // 并且装载应用的 index.html 页面
    win.loadURL(`file://${__dirname}/html/index.html`);
    
    // 打开开发工具页面
    win.webContents.openDevTools();
    
    //当窗口关闭时调用的方法
    win.on('closed', () => {
        // 解除窗口对象的引用,通常而言如果应用支持多个窗口的话,你会在一个数组里 // 存放窗口对象,在窗口关闭的时候应当删除相应的元素。
        win = null;
    });
}
// 当 Electron 完成初始化并且已经创建了浏览器窗口,则该方法将会被调用。
// 有些 API 只能在该事件发生后才能被使用
app.on('ready', createWindow);
// 当所有的窗口被关闭后退出应用 
app.on('window-all-closed', () => {
    // 对于 OS X 系统,应用和相应的菜单栏会一直激活直到用户通过 Cmd + Q 显式退出 
    if (process.platform !== 'darwin') {
        app.quit(); 
    }
});
app.on('activate', () => {
    // 对于 OS X 系统,当 dock 图标被点击后会重新创建一个 app 窗口,并且不会有其他
    // 窗口打开
    if (win === null) {
        createWindow(); 
    }
});
// 在这个文件后面你可以直接包含你应用特定的由主进程运行的代码。 
// 也可以把这些代码放在另一个文件中然后在这里导入
// 渲染进程代码 /src/render/index.js
// 打开新窗口属性用法有点类似vscode打开新的窗口
const btn = document.querySelector('#btn');
const path = require('path');
const BrowerWindow = require('electron').remote.BrowserWindow;
btn.onclick = () => {
    win = new BrowerWindow({ 
        width: 300,
        height: 200, 
        frame: false, // false隐藏关闭按钮、菜单选项 true显示
        fullscreen:true, // 全屏展示
        transparent: true 
    }) 
    win.loadURL(path.join('file:',__dirname,'news.html'));
    win.on('close',()=>{win = null});
}

# 五、自定义顶部菜单/右键菜单

# 5.1 主进程中调用Menu模块-自定义软件顶部菜单
https://electronjs.org/docs/api/menu-item
Electron中Menu模块可以用来创建原生菜单,它可用作应用菜单和context菜单
这个模块是一个主进程的模块,并且可以通过
remote模块给渲染进程调用
// main/menu.js
const { Menu }  = require('electron')
// 文档 https://electronjs.org/docs/api/menu-item
// 菜单项目
let menus = [
    {
        label: '文件',
        submenu: [
            {
                label: '新建文件',
                accelerator: 'ctrl+n', // 绑定快捷键
                click: function () { // 绑定事件
                    console.log('新建文件')
                }
            },
            {
                label: '新建窗口',
                click: function () {
                    console.log('新建窗口')
                }
            }
        ]
    },
    {
        label: '编辑',
        submenu: [
            {
                label: '复制',
                role: 'copy' // 调用内置角色实现对应功能
            },
            {
                label: '剪切',
                role: 'cut'  // 调用内置角色实现对应功能
            }
        ]
    },
    {
        label: '视图',
        submenu: [
            {
                label: '浏览'
            },
            {
                label: '搜索'
            }
        ]
    }
]
let m = Menu.buildFromTemplate(menus)
Menu.setApplicationMenu(m)
// 在主进程src/index.js中引入
const createWindow = () => {
  // 创建菜单  
  // 引入菜单模块
  require('./main/menu.js')
};

我们给菜单绑定事件,在命令行控制台可以看到

# 5.2 渲染进程中调用Menu模块
不推荐使用这种方式,建议在主进程中使用
1. remote
通过
remote调用主进程的方法
// 菜单引入的方式发生变化
const { Menu }  = require('electron').remote
// 其他代码和上面菜单一样
// ...
2. 加入index.html
<script src="render/menu.js"></script>
# 5.3 渲染进程中自定义右键菜单
1. 定义菜单
// render/menu.js
// 在渲染进程中通过remote模块调用主进程中的模块
const { Menu }  = require('electron').remote
const { remote } = require('electron')
// 文档 https://electronjs.org/docs/api/menu-item
// 菜单项目
let menus = [
    {
        label: '文件',
        submenu: [
            {
                label: '新建文件',
                accelerator: 'ctrl+n', // 绑定快捷键
                click: function () { // 绑定事件
                    console.log('新建文件')
                }
            },
            {
                label: '新建窗口',
                click: function () {
                    console.log('新建窗口')
                }
            }
        ]
    },
    {
        label: '编辑',
        submenu: [
            {
                label: '复制',
                role: 'copy' // 调用内置角色实现对应功能
            },
            {
                label: '剪切',
                role: 'cut'  // 调用内置角色实现对应功能
            }
        ]
    },
    {
        label: '视图',
        submenu: [
            {
                label: '浏览'
            },
            {
                label: '搜索'
            }
        ]
    }
]
let m = Menu.buildFromTemplate(menus)
// Menu.setApplicationMenu(m)
// 绑定右键菜单
window.addEventListener('contextmenu', (e)=>{
   e.preventDefault()
   m.popup({
    window: remote.getCurrentWindow()
   })
}, false)

2. 引入
<!--index.html-->
<script src="render/menu.js"></script>
# 六、进程通信

- 渲染进程 https://electronjs.org/docs/api/ipc-renderer
 - 主进程 https://electronjs.org/docs/api/ipc-main
 
# 6.1 主进程与渲染进程之间的通信
有时候我们想在渲染进程中通过一个事件去执行主进程里面的方法。或者在渲染进程中通知 主进程处理事件,主进程处理完成后广播一个事件让渲染进程去处理一些事情。这个时候就 用到了主进程和渲染进程之间的相互通信
Electron主进程,和渲染进程的通信主要用到两个模块:ipcMain和ipcRenderer
ipcMain:当在主进程中使用时,它处理从渲染器进程(网页)发送出来的异步和同步信息,当然也有可能从主进程向渲染进程发送消息。ipcRenderer: 使用它提供的一些方法从渲染进程 (web页面) 发送同步或异步的消息到主进程。 也可以接收主进程回复的消息
# 6.1.1 渲染进程给主进程发送异步消息
间接实现渲染进程执行主进程里面的方法
1. 引入ipcRender
<!--src/index.html-->
<button id="send">在 渲染进程中执行主进程里的方法(异步)</button>
<script src="render/ipcRender.js"></script>
2. 引入ipcMain
// 在主进程src/index.js中引入
const createWindow = () => {
  // 创建菜单  
  // 引入菜单模块
  require('./main/ipcMain.js')
};
3. 渲染进程发送消息
// src/render/ipcRender.js
//渲染进程
let send = document.querySelector('#send');
const { ipcRenderer } = require('electron');
send.onclick = function () {
    // 传递消息给主进程
    // 异步
    ipcRenderer.send('sendMsg', {name:'poetries', age: 23})
}
2. 主进程接收消息
// src/main/ipcMain.js
//主进程
const { ipcMain }  = require('electron')
// 主进程处理渲染进程广播数据
ipcMain.on('sendMsg', (event, data)=> {
    console.log('data\n ', data)
    console.log('event\n ', event)
})

# 6.1.2 渲染进程发送消息,主进程接收消息并反馈
渲染进程给主进程发送异步消息,主进程接收到异步消息以后通知渲染进程
1. 引入ipcRender
<!--src/index.html-->
<button id="sendFeedback">在 渲染进程中执行主进程里的方法,并反馈给主进程(异步)</button>
<script src="render/ipcRender.js"></script>
2. 引入ipcMain
// 在主进程src/index.js中引入
const createWindow = () => {
  // 创建菜单  
  // 引入菜单模块
  require('./main/ipcMain.js')
};
3. 渲染进程发送消息
// src/render/ipcRender.js
//渲染进程
let sendFeedback = document.querySelector('#sendFeedback');
const { ipcRenderer } = require('electron');
// 向主进程发送消息
sendFeedback.onclick = function () {
    // 触发主进程里面的方法
    ipcRenderer.send('sendFeedback', {name:'poetries', age: 23})
}
4. 主进程收到消息处理并广播反馈通知渲染进程
// src/main/ipcMain.js
//主进程
const { ipcMain }  = require('electron')
// 主进程处理渲染进程广播数据,并反馈给渲染进程
ipcMain.on('sendFeedback', (event, data)=> {
    // console.log('data\n ', data)
    // console.log('event\n ', event)
    
    // 主进程给渲染进程广播数据
    event.sender.send('sendFeedbackToRender', '来自主进程的反馈')
})
5. 渲染进程处理主进程广播的数据
// src/render/ipcRender.js
// 向主进程发送消息后,接收主进程广播的事件
ipcRenderer.on('sendFeedbackToRender', (e, data)=>{
    console.log('event\n ', e)
    console.log('data\n ', data)
})

# 6.1.3 渲染进程给主进程发送同步消息
1. 引入ipcRender
<!--src/index.html-->
 <button id="sendSync">渲染进程和主进程同步通信</button>
<script src="render/ipcRender.js"></script>
2. 引入ipcMain
// 在主进程src/index.js中引入
const createWindow = () => {
  // 创建菜单  
  // 引入菜单模块
  require('./main/ipcMain.js')
};
3. 渲染进程给主进程同步通信
// src/render/ipcMain.js
let sendSync = document.querySelector('#sendSync');
// 渲染进程和主进程同步通信
sendSync.onclick = function () {
    // 同步广播数据
   let msg =  ipcRenderer.sendSync('sendsync', {name:'poetries', age: 23})
    
   // 同步返回主进程反馈的数据
   console.log('msg\n ', msg)
}
4. 主进程接收数据处理
// src/main/ipcMain.js
// 渲染进程和主进程同步通信 接收同步广播
ipcMain.on('sendsync', (event, data)=> {
    // console.log('data\n ', data)
    // console.log('event\n ', event)
    // 主进程给渲染进程广播数据
    event.returnValue ='渲染进程和主进程同步通信 接收同步广播,来自主进程的反馈.';
})

# 6.1.4 渲染进程广播通知主进程打开窗口
一般都是在渲染进程中执行广播操作,去通知主进程完成任务
1. 引入openWindow
<!--src/index.html-->
 <button id="sendSync">渲染进程和主进程同步通信</button>
<script src="render/openWindow.js"></script>
2. 引入ipcMain2
// 在主进程src/index.js中引入
const createWindow = () => {
  // 创建菜单  
  // 引入菜单模块
  require('./main/ipcMain2.js')
};
3. 渲染进程通知主进程打开窗口
// src/render/openWindow.js
/* eslint-disable */
let openWindow = document.querySelector('#openWindow');
var { ipcRenderer } = require('electron');
// 渲染进程和渲染进程直接的通信========
openWindow.onclick = function () {
    // 通过广播的形式 通知主进程执行操作
    ipcRenderer.send('openwindow', {name:'poetries', age: 23})
}
4. 主进程收到通知执行操作
// src/main/ipcMain2.js
/* eslint-disable */
let { ipcMain,BrowserWindow } = require('electron')
const path = require('path')
let win;
// 接收到广播
ipcMain.on('openwindow', (e, data)=> {
    // 调用window打开新窗口
    win = new BrowserWindow({
        width: 400,
        height: 300,
    });
    win.loadURL(path.join('file:',__dirname, '../news.html'));
    win.webContents.openDevTools()
    win.on('closed', () => {
        win = null;
      });
})

# 6.2 渲染进程与渲染进程之间的通信
也就是两个窗口直接的通信
# 6.2.1 localstorage传值
Electron渲染进程通过localstorage给另一个渲染进程传值
1. 引入openWindow
<!--src/index.html-->
 <button id="sendSync">渲染进程和主进程同步通信</button>
<script src="render/openWindow.js"></script>
2. 引入ipcMain2
// 在主进程src/index.js中引入
const createWindow = () => {
  // 创建菜单  
  // 引入菜单模块
  require('./main/ipcMain2.js')
};
3. 渲染进程通知主进程打开窗口
// src/render/openWindow.js
/* eslint-disable */
let openWindow = document.querySelector('#openWindow');
var { ipcRenderer } = require('electron');
// 渲染进程和渲染进程直接的通信========
openWindow.onclick = function () {
    // 通过广播的形式 通知主进程执行操作
    ipcRenderer.send('openwindow', {name:'poetries', age: 23})
    
    // ======= localstorage传值 =====
     localStorage.setItem('username', 'poetries')
}
4. 新建news页面
<!--src/news.html-->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
    news page
  </body>
  <script src="render/news.js"></script>
</html>
// src/render/news.js
let username = localStorage.getItem('username')
console.log(username)
5. 主进程收到通知执行操作
// src/main/ipcMain2.js
/* eslint-disable */
let { ipcMain,BrowserWindow } = require('electron')
const path = require('path')
let win;
// 接收到广播
ipcMain.on('openwindow', (e, data)=> {
    // 调用window打开新窗口
    win = new BrowserWindow({
        width: 400,
        height: 300,
    });
    win.loadURL(path.join('file:',__dirname, '../news.html'));
    win.webContents.openDevTools()
    win.on('closed', () => {
        win = null;
      });
})
# 6.2.2 BrowserWindow和webContents方式实现
通过
BrowserWindow和webContents模块实现渲染进程和渲染进程的通信
webContents是一个事件发出者.它负责渲染并控制网页,也是BrowserWindow对象的属性
需要了解的几个知识点
- 获取当前窗口的 
id 
const winId = BrowserWindow.getFocusedWindow().id;
- 监听当前窗口加载完成的事件
 
win.webContents.on('did-finish-load',(event) => {
    
})
- 同一窗口之间广播数据
 
win.webContents.on('did-finish-load',(event) => {
    win.webContents.send('msg',winId,'我是 index.html 的数据');
})
- 通过 
id查找窗口 
let win = BrowserWindow.fromId(winId);
下面是具体演示
1. 引入openWindow
<!--src/index.html-->
 <button id="sendSync">渲染进程和主进程同步通信</button>
<script src="render/openWindow.js"></script>
2. 引入ipcMain2
// 在主进程src/index.js中引入
const createWindow = () => {
  // 创建菜单  
  // 引入菜单模块
  require('./main/ipcMain2.js')
};
3. 渲染进程通知主进程打开窗口
// src/render/openWindow.js
/* eslint-disable */
let openWindow = document.querySelector('#openWindow');
var { ipcRenderer } = require('electron');
// 渲染进程和渲染进程直接的通信========
openWindow.onclick = function () {
    // 通过广播的形式 通知主进程执行操作
    ipcRenderer.send('openwindow', {name:'poetries', age: 23})
}
4. 主进程收到通知执行操作
// src/main/ipcMain2.js
let { ipcMain,BrowserWindow } = require('electron')
const path = require('path')
let win;
// 接收到广播
ipcMain.on('openwindow', (e, userInfo)=> {
    // 调用window打开新窗口
    win = new BrowserWindow({
        width: 400,
        height: 300,
    });
    win.loadURL(path.join('file:',__dirname, '../news.html'));
    // 新开窗口调试模式
    win.webContents.openDevTools()
    // 把渲染进程传递过来的数据再次传递给渲染进程news
    // 等待窗口加载完
    win.webContents.on('did-finish-load', ()=>[
        win.webContents.send('toNews', userInfo)
    ])
    
    win.on('closed', () => {
        win = null;
      });
})
5. news接收主进程传递的数据
数据经过渲染进程->主进程->
news渲染进程
<!--news页面-->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
    news page
  </body>
  <script src="render/news.js"></script>
</html>
// src/render/news.js
var { ipcRenderer } = require('electron');
// let username = localStorage.getItem('username')
// console.log(username)
// 监听主进程传递过来的数据 
ipcRenderer.on('toNews',(e, userInfo)=>{
    console.log(userInfo)
})


那么,这里有一个问题,
news进程接收到了广播后如何给出反馈呢?

1. 在主进程中获取窗口ID传递
// src/main/ipcMain2.js
let { ipcMain,BrowserWindow } = require('electron')
const path = require('path')
let win;
// 接收到广播
ipcMain.on('openwindow', (e, userInfo)=> {
      // 获取当前窗口ID 放在第一行保险  因为后面也打开了新窗口使得获取的ID有问题
    let winId = BrowserWindow.getFocusedWindow().id
    // 调用window打开新窗口
    win = new BrowserWindow({
        width: 400,
        height: 300,
    });
    win.loadURL(path.join('file:',__dirname, '../news.html'));
    // 新开窗口调试模式
    win.webContents.openDevTools()
  
    // 把渲染进程传递过来的数据再次传递给渲染进程news
    // 等待窗口加载完
    win.webContents.on('did-finish-load', ()=>[
        win.webContents.send('toNews', userInfo, winId)
    ])
    
    win.on('closed', () => {
        win = null;
      });
})
2. 在news进程中广播数据
// src/render/news.js
var { ipcRenderer } = require('electron');
// 注意这里 在渲染进程中需要从remote中获取BrowserWindow
const BrowerWindow = require('electron').remote.BrowserWindow;
// let username = localStorage.getItem('username')
// console.log(username)
// 监听主进程传递过来的数据 
ipcRenderer.on('toNews',(e, userInfo, winId)=>{
    // windID 第一个窗口ID
    // 获取对应ID的窗口
    let firstWin = BrowerWindow.fromId(winId)
    firstWin.webContents.send('toIndex', '来自news进程反馈的信息')
    console.log(userInfo)
})
3. 在另一个渲染进程中处理广播
/* eslint-disable */
let openWindow = document.querySelector('#openWindow');
var { ipcRenderer } = require('electron');
// 渲染进程和渲染进程直接的通信========
openWindow.onclick = function () {
    // 传递消息给主进程
    ipcRenderer.send('openwindow', {name:'poetries', age: 23})
    // 传递给打开的窗口 渲染进程和渲染进程直接的通信
    localStorage.setItem('username', 'poetries')
    
}
// 接收news渲染进程传递回来的消息
ipcRenderer.on('toIndex', (e, data)=>{
    console.log('===', data)
})

# 七、Electron Shell 模块

# 7.1 Shell 模块使用
文档 https://electronjs.org/docs/api/shell
Electron Shell模块在用户默认浏览器 中打开URL以及Electron DOM webview标签。Shell既属于主进程模块又是渲染进程模块
shell模块提供了集成其他桌面客户端的关联功能
1. 引入
<!--index.html-->
<button id="shellDom">通过shell打开外部链接</button>
<script src="render/shell.js"></script>
2. shell.js
// src/render/shell.js
const { shell } = require('electron')
let shellDom = document.querySelector('#shellDom');
shellDom.onclick = function (e) {
   shell.openExternal('https://github.com/poetries')
}
# 7.2 Electron DOM <webview> 标签
 
Webview与iframe有点相似,但是与iframe不同,webview和你的应用运行的是不同的进程。它不拥有渲染进程的权限,并且应用和嵌入内容之间的交互全部都是异步的。因为这能 保证应用的安全性不受嵌入内容的影响。
<!--src/index.html中引入-->
<webview id="webview" src="http://blog.poetries.top" style="position:fixed; width:100%; height:100%">
</webview>
# 7.3 shell模块<webview>结合Menu模块使用案例
 1. 新建src/render/webview.js
/* eslint-disable */
var { ipcRenderer } = require('electron');
let myWebview = document.querySelector('#myWebview')
ipcRenderer.on('openwebview', (e, url)=>{
    myWebview.src = url
})
2. 引入src/index.html
<webview id="myWebview" src="http://blog.poetries.top" style="position:fixed; width:100%; height:100%">
</webview>
    
<script src="render/webview.js"></script>
3. 新建src/main/menu.js
/* eslint-disable */
const { shell, Menu, BrowserWindow } = require('electron');
// 当前窗口渲染网页
function openWebView(url) {
    // 获取当前窗口Id
    let win = BrowserWindow.getFocusedWindow()
    // 广播通知渲染进程打开webview
    win.webContents.send('openwebview', url)
}
// 在窗口外打开网页
function openWeb(url) {
    shell.openExternal(url)
}
let template = [
    {
        label: '帮助',
        submenu: [
            {
                label: '关于我们',
                click: function () {
                    openWeb('http://blog.poetries.top')
                }
            },
            {
                type: 'separator'
            },
            {
                label: '联系我们',
                click: function () {
                    openWeb('https://github.com/poetries')
                }
            }
        ]
    },
   {
        label: '加载网页',
        submenu: [
            {
                label: '博客',
                click: function () {
                    openWebView('http://blog.poetries.top')
                }
            },
            {
                type: 'separator' // 分隔符
            },
            {
                label: 'GitHub',
                click: function () {
                    openWebView('https://github.com/poetries')
                }
            },
            {
                type: 'separator' // 分隔符
            },
            {
                label: '简书',
                click: function () {
                    openWebView('https://www.jianshu.com/users/94077fcddfc0/timeline')
                }
            }
        ]
   },
   {
    label: '视频网站',
    submenu: [
        {
            label: '优酷',
            click: function () {
                openWebView('https://www.youku.com')
            }
        },
        {
            type: 'separator' // 分隔符
        },
        {
            label: '爱奇艺',
            click: function () {
                openWebView('https://www.iqiyi.com/')
            }
        },
        {
            type: 'separator' // 分隔符
        },
        {
            label: '腾讯视频',
            click: function () {
                openWebView('https://v.qq.com/')
            }
        }
    ]
    }
]
let m = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(m)
4. 引入menu
// 在主进程src/index.js中引入
const createWindow = () => {
  // 创建菜单  
  // 引入菜单模块
  require('./main/menu.js')
};

# 八、Electron dialog 弹出框

文档 https://electronjs.org/docs/api/dialog
dialog属于主进程中的模块
dialog模块提供了api来展示原生的系统对话框,例如打开文件框,alert框, 所以web应用可以给用户带来跟系统应用相同的体验
1. 在src/index.html中引入
<button id="showError">showError</button><br />
<button id="showMsg">showMsg</button><br />
<button id="showOpenDialog">showOpenDialog</button><br />
<button id="saveDialog">saveDialog</button><br />
<script src="render/dialog.js"></script>
2. 新建render/dialog.js
// render/dialog.js
let showError = document.querySelector('#showError');
let showMsg = document.querySelector('#showMsg');
let showOpenDialog = document.querySelector('#showOpenDialog');
let saveDialog = document.querySelector('#saveDialog');
var {remote} = require('electron')
showError.onclick = function () {
    remote.dialog.showErrorBox('警告', '操作有误')
}
showMsg.onclick = function () {
    remote.dialog.showMessageBox({
        type: 'info',
        title: '提示信息',
        message: '内容',
        buttons: ['确定', '取消']
    },function(index){
        console.log(index)
    })
}
showOpenDialog.onclick = function () {
    remote.dialog.showOpenDialog({
        // 打开文件夹
        properties: ['openDirectory', 'openFile']
        // 打开文件
        // properties: ['openFile']
    }, function (data) {
        console.log(data)
    })
}
saveDialog.onclick = function () {
    remote.dialog.showSaveDialog({
        title: 'Save File',
        defaultPath: '/Users/poetry/Downloads/',
        // filters 指定一个文件类型数组,用于规定用户可见或可选的特定类型范围
        filters: [
            { name: 'Images', extensions: ['jpg', 'png', 'gif'] },
            { name: 'Movies', extensions: ['mkv', 'avi', 'mp4'] },
            { name: 'Custom File Type', extensions: ['as'] },
            { name: 'All Files', extensions: ['*'] }
        ]
    }, function (path) {
        // 不是真的保存 ,具体还需nodejs处理
        console.log(path)
    })
}
showError
remote.dialog.showErrorBox('警告', '操作有误')

showMessageBox
remote.dialog.showMessageBox({
    type: 'info',
    title: '提示信息',
    message: '内容',
    buttons: ['确定', '取消']
},function(index){
    console.log(index)
})

showOpenDialog
remote.dialog.showOpenDialog({
    // 打开文件夹
    properties: ['openDirectory', 'openFile']
    // 打开文件
    // properties: ['openFile']
}, function (data) {
    console.log(data)
})

showSaveDialog
remote.dialog.showSaveDialog({
    title: 'Save File',
    defaultPath: '/Users/poetry/Downloads/',
    // filters 指定一个文件类型数组,用于规定用户可见或可选的特定类型范围
    filters: [
        { name: 'Images', extensions: ['jpg', 'png', 'gif'] },
        { name: 'Movies', extensions: ['mkv', 'avi', 'mp4'] },
        { name: 'Custom File Type', extensions: ['as'] },
        { name: 'All Files', extensions: ['*'] }
    ]
}, function (path) {
    // 不是真的保存 ,具体还需nodejs处理
    console.log(path)
})

# 九、实现一个类似EditPlus的简易记事本代码编辑器
代码 https://github.com/poetries/electron-demo/tree/master/notepad
# 十、系统托盘、托盘右键菜单、托盘图标闪烁

文档 https://electronjs.org/docs/api/tray
系统托盘,托盘右键菜单、托盘图标闪烁 点击右上角关闭按钮隐藏到托盘(仿杀毒软件)
1. 引入文件
// src/index.js
const createWindow = () => {
    require('./main/tray.js')
};
2. Electron 创建任务栏图标以及任务栏图标右键菜单
// src/main/tray.js
var {
    Menu, Tray, app, BrowserWindow
} = require('electron');
const path = require('path');
var appIcon = new Tray(path.join(__dirname, '../static/lover.png'));
const menu = Menu.buildFromTemplate([
    {
        label: '设置',
        click: function() {} //打开相应页面 
    },
    {
        label: '帮助',
        click: function() {}
    },
    {
        label: '关于',
        click: function() {}
    },
    {
        label: '退出',
        click: function() { 
            // BrowserWindow.getFocusedWindow().webContents().send('close-main-window');
            app.quit();
    }
}])
// 鼠标放上去提示信息
appIcon.setToolTip('hello poetries');
appIcon.setContextMenu(menu);

3. 监听任务栏图标的单击、双击事件
// 实现点击关闭按钮,让应用保存在托盘里面,双击托盘打开
let win = BrowserWindow.getFocusedWindow()
win.on('close', (e)=>{
    e.preventDefault()
    win.hide()
})
iconTray.on('double-click', (e)=>{
    win.show()
})
4. Electron 点击右上角关闭按钮隐藏任务栏图标
const win = BrowserWindow.getFocusedWindow();
win.on('close', (e) =>{
    console.log(win.isFocused());
    
    if (!win.isFocused()) {
        win = null;
    } else {
        e.preventDefault();/*阻止应用退出*/
        win.hide();/*隐藏当前窗口*/
    }
})
5. Electron 实现任务栏闪烁图标
var appIcon = new Tray(path.join(__dirname, '../static/lover.png'));
timer = setInterval(function() {
    count++;
    if (count % 2 == 0) {
        appIcon.setImage(path.join(__dirname, '../static/empty.ico'))
    } else {
        appIcon.setImage(path.join(__dirname, '../static/lover.png'))
    }
},
500);
# 十一、消息通知、监听网络变 化、网络变化弹出通知框
# 11.1 消息通知
1. Electron 实现消息通知
Electron里面的消息通知是基于h5的通知api实现的
文档 https://developer.mozilla.org/zh-CN/docs/Web/API/notification
1. 新建notification.js
// h5api实现通知
const path = require('path')
let options = {
    title: 'electron 通知API',
    body: 'hello poetries',
    icon: path.join('../static/img/favicon2.ico') // 通知图标
}
document.querySelector('#showNotification').onclick = function () {
    let myNotification  = new window.Notification(options.title, options)
    
    // 消息可点击
    myNotification.onclick = function () {
        console.log('click notification')
    }
}
2. 引入
<!--src/index.html-->
<button id="showNotification">弹出消息通知</button>
<script src="render/notification.js"></script>
mac上的消息通知

# 11.2 监听网络变化
1. 基本使用
 // 监听网络变化
// 端开网络 再次连接测试
 window.addEventListener('online', function(){
    console.log('online')
 }); 
 
 window.addEventListener('offline', function(){
    console.log('offline')
 });
2. 监听网络变化实现消息通知
// 端开网络 再次连接测试
// 监听网络变化实现消息通知
window.addEventListener('online', function(){
   console.log('online')
}); 
window.addEventListener('offline', function(){
   // 断开网络触发事件
   var options = {
       title: 'QQ邮箱',
       body: '网络异常,请检查你的网络',
       icon: path.join('../static/img/favicon2.ico') // 通知图标
   }
   var myNotification  = new window.Notification(options.title, options)
   myNotification.onclick = function () {
       console.log('click notification')
   }
});

# 十二、注册全局快捷键/剪切板事件/nativeImage 模块
Electron注册全局快捷键 (globalShortcut) 以及clipboard剪 切板事件以及nativeImage模块(实现类似播放器点击机器码自动复制功 能)
# 12.1 注册全局快捷键

1. 新建src/main/shortCut.js
const {globalShortcut, app} = require('electron')
app.on('ready', ()=>{
    // 注册全局快捷键
    globalShortcut.register('command+e', ()=>{
        console.log(1)
    })
    // 检测快捷键是否注册成功 true是注册成功
    let isRegister = globalShortcut.isRegistered('command+e')
    console.log(isRegister)
})
// 退出的时候取消全局快捷键
app.on('will-quit', ()=>{
    globalShortcut.unregister('command+e')
})
2. 引入src/index.js
// 注意在外部引入即可 不用放到app中
require('./main/shortCut.js')
# 12.2 剪切板clipboard、nativeImage 模块

1. html
<!--src/index.html-->
<div>
  <h2>双击下面信息复制</h2>
  <p id='msg'>123456789</p>
  <button id="plat">粘贴</button><br />
  <input id="text" type="text"/>
</div>.
<div>
  <h2>复制图片到界面</h2>
  <button id="copyImg">复制图片</button><br />
</div>
<script src="render/clipboard.js"></script>
2. 新建src/render/clipboard.js
// clipboard可以在主进程或渲染进程使用
const { clipboard, nativeImage }  = require('electron')
//复制
// 运行ctrl+v可看到复制的内容
// clipboard.writeText('poetries')
// clipboard.readText() //获取复制的内容 粘贴
// 双击复制消息
let msg = document.querySelector('#msg')
let plat = document.querySelector('#plat')
let text = document.querySelector('#text')
msg.ondblclick  = function () {
    clipboard.writeText(msg.innerHTML)
    alert(msg.innerHTML)
}
plat.onclick = function () {
    text.value = clipboard.readText()
}
// 复制图片显示到界面
let copyImg = document.querySelector('#copyImg')
copyImg.onclick = function () {
    // 结合nativeImage模块
    let image = nativeImage.createFromPath('../static/img/lover.png') 
    // 复制图片
    clipboard.writeImage(image)
    // 粘贴图片
    let imgSrc = clipboard.readImage().toDataURL() // base64图片
    // 显示到页面上
    let imgDom = new Image()
    imgDom.src = imgSrc 
    document.body.appendChild(imgDom)
}
# 十三、结合electron-vue
# 13.1 electron-vue 的使用
1. electron-vue 的一些资源
https://github.com/SimulatedGREG/electron-vue
Electron-vue 文档 https://simulatedgreg.gitbooks.io/electron-vue/content/cn
2. electron-vue 环境搭建、创建项目
npm install -g vue-cli
vue init simulatedgreg/electron-vue my-project
cd my-project
yarn # or npm install
yarn run dev # or npm run dev
3. electron-vue 目录结构分析

# 13.2 electron-vue 中使用 sass/ElementUi
1. electron-vue UI 框架 ElementUi 的使用
http://element-cn.eleme.io/#/zh-CN
2. electron-vue 中使用 sass
# 安装 sass-loader:
npm install --save-dev sass-loader node-sass
<!--vue 文件中修改 style 为如下代码:-->
<style lang="scss"> 
    body {
        /* SCSS */ 
    }
</style>
# 13.3 electron-vue 中隐藏顶部菜单隐藏
electron-vue 中隐藏顶部菜单隐藏顶部最大化、最小化、关闭按钮 自定最大化、最小化 、关闭按钮
1. electron-vue 中隐藏顶部菜单
// src/main/index.js
mainWindow.setMenu(null)
2. electron-vue 中隐藏关闭 最大化 最小化按钮
// src/main/index.js
mainWindow = new BrowserWindow({
    height: 620,
    useContentSize: true,
    width: 1280,
    frame: false /*去掉顶部导航 去掉关闭按钮 最大化最小化按钮*/
})
3 .electron-vue 自定义关闭/最大化最小化按钮
// 注意在mac下不需要监听窗口最大最小化、以为系统默认支持,这个只是针对windows平台
ipc.on('window-min',function() {
    mainWindow.minimize();
})
//登录窗口最大化 
ipc.on('window-max',function(){
    if (mainWindow.isMaximized()) {
        mainWindow.restore();
    } else {
        mainWindow.maximize();
    }
}) 
ipc.on('window-close',function() {
    mainWindow.close();
})
4. electron-vue 自定义导航可拖拽
- 可拖拽的 
css:-webkit-app-region: drag; - 不可拖拽的 
css:-webkit-app-region: no-drag; 
# 13.4 使用electron-vue开发舆情监控系统
# 13.4.1 配置开发环境
1. 项目搭建
npm install -g vue-cli
vue init simulatedgreg/electron-vue my-project
cd my-project
yarn # or npm install
yarn run dev # or npm run dev
2. 安装一些依赖
# 安装 sass-loader:
npm install --save-dev sass-loader node-sass
# 安装elementUI、js-md5
npm i element-ui  js-md5 -S
- 在
.electron-vue/webpack.renderer.config.js中配置sass-loader就可以编写``sass`了 
<!--vue 文件中修改 style 为如下代码:-->
<style lang="scss"> 
    body {
        /* SCSS */ 
    }
</style>
# 13.4.2 主进程配置
1. src/main/index.js
function createWindow () {
  // 去掉顶部菜单
  mainWindow.setMenu(null)
  
  // 菜单项
  require('./model/menu.js');
  
  // 系统托盘相关
  require('./model/tray.js');
2. src/main/menu.js菜单配置
const { Menu,ipcMain,BrowserWindow} = require('electron');
//右键菜单
const contextMenuTemplate=[
    {
        label: '复制', role: 'copy' },
    {
        label: '黏贴', role: 'paste' },        
    { type: 'separator' }, //分隔线
    {
        label: '其他功能',     
        click: () => {
        console.log('click')
         }
    }
];
const contextMenu=Menu.buildFromTemplate(contextMenuTemplate);
ipcMain.on('contextmenu',function(){
    contextMenu.popup(BrowserWindow.getFocusedWindow());
})
3. src/main/tray.js系统托盘配置
托盘点击监听事件只有在
windows下才生效,mac系统默认支持
(function () {
    const path=require('path');
    const {app,Menu,BrowserWindow,Tray, shell} = require('electron');
    //创建系统托盘
    const tray = new Tray(path.resolve(__static, 'favicon.png'))
    //给托盘增加右键菜单
    const template= [
        {
            label: '设置',
            click: function () {
                shell.openExternal('http://blog.poetries.top')
            }
        },
        {
            label: '帮助',
            click: function () {
                shell.openExternal('http://blog.poetries.top/2019/01/06/electron-summary')
            }
        },
        {
            label: '关于',
            click: function () {
                shell.openExternal('https://github.com/poetries/yuqing-monitor-electron')
            }
        },
        {
            label: '退出',
            click: function () {
                // BrowserWindow.getFocusedWindow().webContents().send('close-main-window');
                app.quit();
            
            }
        }
    ];
    const menu = Menu.buildFromTemplate(template);
    tray.setContextMenu(menu);
    tray.setToolTip('舆情监控系统');
    //监听关闭事件隐藏到系统托盘
    // 这里需要注意:在window中才生效,mac下系统默认支持
    // var win = BrowserWindow.getFocusedWindow();
    // win.on('close',(e)=>{
    //         if(!win.isFocused()){
    //             win=null;
    //         }else{
    //             e.preventDefault();  /*阻止应用退出*/
    //             win.hide(); /*隐藏当前窗口*/
    //         }       
    // })
    // //监听托盘的双击事件
    // tray.on('double-click',()=>{               
    //     win.show();
    // })
})()
4. src/main/shortCut.js快捷键配置
在src/main/index.js中引入(require('src/main/shortCut.js'))即可,不需要放到app监控中
var {globalShortcut, app} = require('electron')
app.on('ready', ()=>{
    // 注册全局快捷键
    globalShortcut.register('command+e', ()=>{
        console.log(1)
    })
    // 检测快捷键是否注册成功 true是注册成功
    let isRegister = globalShortcut.isRegistered('command+e')
    console.log(isRegister)
})
// 退出的时候取消全局快捷键
app.on('will-quit', ()=>{
    globalShortcut.unregister('command+e')
})
# 13.4.3 渲染进程配置
1. src/render/main.js配置
import Vue from 'vue'
import axios from 'axios'
import App from './App'
import router from './router'
import store from './store'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import VueHighcharts from 'vue-highcharts';
import VueSocketIO from 'vue-socket.io'
Vue.use(ElementUI);
Vue.use(VueHighcharts);
//引入socket.io配置连接
Vue.use(new VueSocketIO({
  debug: true,
  connection: 'http://118.123.14.36:3000',
  vuex: {
      store,
      actionPrefix: 'SOCKET_',
      mutationPrefix: 'SOCKET_'
  }
}))
if (!process.env.IS_WEB) Vue.use(require('vue-electron'))
Vue.http = Vue.prototype.$http = axios
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
  components: { App },
  router,
  store,
  template: '<App/>'
}).$mount('#app')
2. 路由配置src/renderer/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
  routes: [
    {
      path: '/home',
      name: 'home',
      component: require('@/components/Home').default
    },
    {
      path: '/report',
      name: 'report',
      component: require('@/components/Report').default
    },
    {
      path: '/negativereport',
      name: 'negativereport',
      component: require('@/components/NegativeReport').default
    },
    {
      path: '/positivereport',
      name: 'positivereport',
      component: require('@/components/PositiveReport').default
    },
    {
      path: '/keyword',
      name: 'keyword',
      component: require('@/components/KeyWord').default
    },
    {
      path: '/alarm',
      name: 'alarm',
      component: require('@/components/Alarm').default
    },
    {
      path: '/msg',
      name: 'msg',
      component: require('@/components/Msg').default
    },
    {
      path: '*',
      redirect: '/home'
    }
  ]
})
3. 在渲染进程中使用主进程方式
// electron挂载到了vue实例上 $electron
this.$electron.shell
# 13.4.4 多平台打包
需要注意的是打包
mac版本在mac系统上打包,打包window则在windows上打包,可以避免很多问题
# 在不同平台上执行即可打包应用
npm run build
# 13.4.4.1 打包介绍
1. electron 中构建应用最常用的模块
electron-packagerelectron-builder
electron-packager和electron-builder在自己单独创建的应用用也可以完成打包功 能。但是由于配置太复杂所以我们不建议单独配置
2. electron-forge
https://github.com/electron-userland/electron-forge
electron-forge package 
electron-forge make
3. electron-vue中的打包方式
# https://simulatedgreg.gitbooks.io/electron-vue/content/cn/using-electron-packager. html
# 之需要执行一条命令
npm run build
# 13.4.4.2 修改应用信息
1. 修改package.json

2. 修改src/index.ejs标题信息
3. 修改build/icons图标
# 13.4.4.3 打包遇到的问题
1. 创建应用托盘的时候可能会遇到错误
- 把托盘图片放在根目录
static里面,然后注意下面写法。 
var tray = new Tray(path.join(__static,'favicon.ico'))
- 如果托盘路径没有问题,还是包托盘相关错误的话,把托盘对应的图片换成
.png格式重试 
2. 模块问题可能会遇到的错误


解决办法
- 删掉 
node_modules然后重新用npm install安装依赖 - 用 
yarn来安装模块 - 用手机创建一个热点电脑连上热点重试
 
最后执行
yarn run build即可

项目源码 https://github.com/poetries/yuqing-monitor-electron