相关文章推荐
英俊的豆芽  ·  python 显示obj文件-掘金·  1 年前    · 
  • 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 被重命名为 Electron
  • 2016 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 的版本会对其进行更新,具 体时间根据升级所需的工作量而定。
  • Electron 的核心理念是:保持 Electron 的体积小和可持续性开发。
    如:为了保持 Electron 的小巧 (文件体积) 和可持续性开发 (以防依赖库和 API 的泛滥) , Electron 限制了所使用的核心项目的数量。
    比如 Electron 只用了 Chromium 的渲染库而不是其全部组件。这使得升级 Chromium 更加容易,但也意味着 Electron 缺少了 Google Chrome 里的一些浏览器相关的特性。 添加到 Electron 的新功能应该主要是原生 API 。 如果可以的话,一个功能应该尽可能的成 为一个 Node.js 模块。

  • 新建一个项目目录 例如: 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 运行 package.jsonmain 脚本的进程被称为主进程。
  • 在主进程中运行的脚本通过创建 web 页面来展示用户界面。 一个 Electron 应用总是有且只有一个主进程。
  • 由于 Electron 使用了 Chromium(谷歌浏览器)来展示 web 页面,所以 Chromium 的 多进程架构也被使用到。 每个 Electron 中的 web 页面运行在它自己的渲染进程中。
  • 主进程使用 BrowserWindow 实例创建页面。每个 BrowserWindow 实例都在自己的渲 染进程里运行页面。 当一个 BrowserWindow实例被销毁后,相应的渲染进程也会被终止
  • 进程:进程是计算机中的程序关于某数据集合上的一次运行活动,是 系统进行资源分配和调度的基本单位,是操作系统结构的基础。
  • 线程:在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是: 线程是“一个进程内部的控制序列”。
  • 线程和进程:一个程序至少有一个进程,一个进程至少有一个线程
  • 3.3 Electron 渲染进程中通过 Nodejs 读取本地文件

    在普通的浏览器中,web页面通常在一个沙盒环境中运行,不被允许去接触原生的资源。 然而 Electron 的用户在 Node.jsAPI支持下可以在页面中和操作系统进行一些底层交 互。
    Nodejs 在主进程和渲染进程中都可以使用。渲染进程因为安全限制,不能直接操作生 GUI。虽然如此,因为集成了 Nodejs,渲染进程也有了操作系统底层 API的能力,Nodejs 中常用的 PathfsCrypto 等模块在 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 中, 与 GUI 相关的模块(如 dialog, menu 等)只存在于主进程,而不在渲染进程中 。为了能从渲染进程中使用它们,需要用ipc模块来给主进程发送进程间消息。使用 remote 模块,可以调用主进程对象的方法,而无需显式地发送进程间消息,这类似于 JavaRMI

    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});
    // 在渲染进程中通过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)
  • 渲染进程 https://electronjs.org/docs/api/ipc-renderer
  • 主进程 https://electronjs.org/docs/api/ipc-main
  • 6.1 主进程与渲染进程之间的通信

    有时候我们想在渲染进程中通过一个事件去执行主进程里面的方法。或者在渲染进程中通知 主进程处理事件,主进程处理完成后广播一个事件让渲染进程去处理一些事情。这个时候就 用到了主进程和渲染进程之间的相互通信

    Electron 主进程,和渲染进程的通信主要用到两个模块:ipcMainipcRenderer

  • 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)
    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)
    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 ='渲染进程和主进程同步通信 接收同步广播,来自主进程的反馈.';
    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;
    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>
        <meta charset="utf-8">
        <title></title>
      </head>
        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方式实现

    通过 BrowserWindowwebContents 模块实现渲染进程和渲染进程的通信

    webContents 是一个事件发出者.它负责渲染并控制网页,也是 BrowserWindow 对象的属性

    需要了解的几个知识点

  • 获取当前窗口的 id
  • const winId = BrowserWindow.getFocusedWindow().id;
    win.webContents.on('did-finish-load',(event) => {
        win.webContents.send('msg',winId,'我是 index.html 的数据');
    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渲染进程

    var { ipcRenderer } = require('electron'); // let username = localStorage.getItem('username') // console.log(username) // 监听主进程传递过来的数据 ipcRenderer.on('toNews',(e, userInfo)=>{ console.log(userInfo) // 接收到广播 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)
    const { shell } = require('electron')
    let shellDom = document.querySelector('#shellDom');
    shellDom.onclick = function (e) {
       shell.openExternal('https://github.com/poetries')
    

    7.2 Electron DOM <webview> 标签

    Webviewiframe 有点相似,但是与 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')
    

    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('警告', '操作有误')
    
    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

    十、系统托盘、托盘右键菜单、托盘图标闪烁

    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);
    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上的消息通知

    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 注册全局快捷键

    // 检测快捷键是否注册成功 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 模块

    // 双击复制消息 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 目录结构分析

    # 安装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'
    

    electron-packagerelectron-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