目录
  • 结合React+TypeScript进行Electron开发
    • 1. electron基本简介
      • 为什么选择electron?
    • 2. 快速上手
      • 2.1 安装React(template为ts)
      • 2.2 快速配置React
      • 2.3 安装electron
      • 2.4 配置main.jspreload.jspackage.json文件
      • 2.5 运行electron项目
      • 2.6 打包项目
    • 3. 自动刷新页面
    • 4. 主进程和渲染进程
    • 5.定义原生菜单、顶部菜单
      • 5.1 自定义菜单
      • 5.2 给菜单定义点击事件
      • 5.3 抽离菜单定义
      • 5.4 自定义顶部菜单
      • 5.5 在渲染进程中使用主进程方法remote和electron(点击创建新窗口)
      • 5.6 点击打开浏览器
    • 6. 打开对话框读取文件
      • 6.1 读取文件
      • 6.2 保存文件
    • 7. 定义快捷键
      • 7.1 主线程定义
      • 7.2在渲染进程中定义
    • 8. 主进程和渲染进程通讯

结合React+TypeScript进行Electron开发

1. electron基本简介

electron是使用JavaScript,HTML和CSS构建跨平台桌面应用程序。我们可以使用一套代码打包成Mac、Windows和Linux的应用,electron比你想象的更简单,如果把你可以建一个网站,你就可以建一个桌面应用程序,我们只需要把精力放在应用的核心上即可。

Electron结合React和TypeScript进行开发

为什么选择electron?

  • Electron 可以让你使用纯JavaScript调用丰富的原生APIs来创造桌面应用。你可以把它看作是专注于桌面应用。
  • 在PC端桌面应用开发中,nwjs和electron都是可选的方案,它们都是基于Chromium和Node的结合体,而electron相对而言是更好的选择方案,它的社区相对比较活跃,bug比较少,文档相对利索简洁。
  • electron相对来说比nw.js靠谱,有一堆成功的案例:Atom编辑器 Visual Studio Code WordPress等等。
  • Node.js的所有内置模块都在Electron中可用。

2. 快速上手

2.1 安装React(template为ts)

yarn create react-app electron-demo-ts --template typescript

2.2 快速配置React

工程架构

Electron结合React和TypeScript进行开发

index.html

                                            electron App                

App.tsx

import React from 'react'  export default function App() {   return (     
App
) }

index.tsx

import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import reportWebVitals from './reportWebVitals';  const root = ReactDOM.createRoot(   document.getElementById('root') as HTMLElement ); root.render(   //         //  );  // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals();

2.3 安装electron

electron包安装到您的应用程序的devDependencies.

// npm npm install --save-dev electron // yarn yarn add --dev electron

2.4 配置main.jspreload.jspackage.json文件

main.js

// 导入app、BrowserWindow模块 // app 控制应用程序的事件生命周期。事件调用app.on('eventName', callback),方法调用app.functionName(arg) // BrowserWindow 创建和控制浏览器窗口。new BrowserWindow([options]) 事件和方法调用同app // Electron参考文档 https://www.electronjs.org/docs const {app, BrowserWindow, nativeImage } = require('electron') const path = require('path') // const url = require('url');   function createWindow () {     // Create the browser window.     const mainWindow = new BrowserWindow({         width: 800, // 窗口宽度         height: 600,  // 窗口高度         // title: "Electron app", // 窗口标题,如果由loadURL()加载的HTML文件中含有标签,该属性可忽略         icon: nativeImage.createFromPath('public/favicon.ico'), // "string" || nativeImage.createFromPath('public/favicon.ico')从位于 path 的文件创建新的 NativeImage 实例         webPreferences: { // 网页功能设置             webviewTag: true, // 是否使用<webview>标签 在一个独立的 frame 和进程里显示外部 web 内容             webSecurity: false, // 禁用同源策略             preload: path.join(__dirname, 'preload.js'),             nodeIntegration: true // 是否启用node集成 渲染进程的内容有访问node的能力,建议设置为true, 否则在render页面会提示node找不到的错误         }     })       // 加载应用 --打包react应用后,__dirname为当前文件路径     // mainWindow.loadURL(url.format({     //   pathname: path.join(__dirname, './build/index.html'),     //   protocol: 'file:',     //   slashes: true     // }));           // 因为我们是加载的react生成的页面,并不是静态页面     // 所以loadFile换成loadURL。     // 加载应用 --开发阶段  需要运行 yarn start     mainWindow.loadURL('http://localhost:3000');      // 解决应用启动白屏问题     mainWindow.on('ready-to-show', () => {         mainWindow.show();         mainWindow.focus();     });      // 当窗口关闭时发出。在你收到这个事件后,你应该删除对窗口的引用,并避免再使用它。     mainWindow.on('closed', () => {         mainWindow = null;     });          // 在启动的时候打开DevTools     mainWindow.webContents.openDevTools() }  app.allowRendererProcessReuse =true;  // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. app.whenReady().then(() =>{     console.log('qpp---whenready');     createWindow();})  // Quit when all windows are closed. app.on('window-all-closed', function () {     // On macOS it is common for applications and their menu bar     // to stay active until the user quits explicitly with Cmd + Q     console.log('window-all-closed');     if (process.platform !== 'darwin') app.quit() })  app.on('activate', function () {     // On macOS it's common to re-create a window in the app when the     // dock icon is clicked and there are no other windows open.     if (BrowserWindow.getAllWindows().length === 0) createWindow() })  // In this file you can include the rest of your app's specific main process // code. You can also put them in separate files and require them here.</code></pre> <p><strong>package.json</strong></p> <p>这时候我们来修改<code>package.json</code>文件。</p> <ol> <li>配置启动文件,添加<code>main</code>字段,我们这里也就是main.js文件。如果没有添加,Electron 将尝试加载包含在<code>package.json</code>文件目录中的<code>index.js</code>文件。</li> <li>配置运行命令,使用<strong>"electron": "electron ."</strong> 区别于react的启动命令<strong>"start": "react-scripts start",</strong></li> <li>安装concurrently:<code>yarn add concurrently</code></li> </ol> <pre><code class="language-json">{     ...     "main": "main.js", // 配置启动文件   	"homepage": ".", // 设置应用打包的根路径     "scripts": {         "start": "react-scripts start",  // react 启动命令         "build": "react-scripts build",         "test": "react-scripts test",         "eject": "react-scripts eject",         "electron": "electron .",  // electron 启动命令         "dev": "concurrently \"npm run start\" \"npm run electron\""     }, }</code></pre> <p><strong>preload.js</strong></p> <pre><code class="language-js">window.addEventListener('DOMContentLoaded', () => {     const replaceText = (selector, text) => {         const element = document.getElementById(selector)             if (element) element.innerText = text     }        for (const dependency of ['chrome', 'node', 'electron']) {         replaceText(`${dependency}-version`, process.versions[dependency])     } })</code></pre> <p><strong>此时的工程架构</strong></p> <p><img decoding="async" src="http://img.555519.xyz/uploads3/20220509/21bda287cdd4e0cdf3a621ecc3d9347e.jpg" alt="Electron结合React和TypeScript进行开发"></p> <h3 id="25-运行electron项目">2.5 运行electron项目</h3> <ol> <li>先<code>yarn start</code> 然后再开一个终端<code>yarn electron</code></li> <li>或者是<code>npm run dev</code></li> </ol> <p><img decoding="async" src="http://img.555519.xyz/uploads3/20220509/411dcebe0cf8f95220fabc19a26539ea.jpg" alt="Electron结合React和TypeScript进行开发"></p> <p>其实我们就可以看出Electron就是一个应用套了一个谷歌浏览器壳子,然后里面是前端页面。</p> <h3 id="26-打包项目">2.6 打包项目</h3> <p>使用<strong>electron-packager</strong>依赖:</p> <pre><code>yarn add --dev electron-packager</code></pre> <p>package.json配置打包命令:</p> <pre><code class="language-json">"package": "electron-packager . bleak-electron-app --platform=win32 --arch=x64 --overwrite --electron-version=18.1.0 --icon=./public/favicon.ico"</code></pre> <p>配置解释:</p> <pre><code>electron-packager <应用目录> <应用名称> <打包平台> <架构x86 还是 x64> <架构> <electron版本> <图标> overwrite 如果输出目录已经存在,替换它</code></pre> <p>然后运行命令:</p> <pre><code>yarn package</code></pre> <p>打包时间慢的话可按照下面两种方式优化:</p> <pre><code>方法1: 在执行electron-packager前先运行set ELECTRON_MIRROR=http://npm.taobao.org/mirrors/electron/ 方法2: 在electron-packager命令行加入参数--download.mirrorOptions.mirror=https://npm.taobao.org/mirrors/electron/ (Windows x64)完整版如下: electron-packager . bleak-electron-app --platform=win32 --arch=x64 --overwrite --electron-version=18.0.4 --download.mirrorOptions.mirror=https://npm.taobao.org/mirrors/electron/</code></pre> <p>然后运行<code>bleak-electron-app-win32-x64</code>里面的exe文件就可以了。</p> <p><img decoding="async" src="http://img.555519.xyz/uploads3/20220509/6ab8c596a5da2c191d158537a19f1b57.jpg" alt="Electron结合React和TypeScript进行开发"></p> <h2 id="3-自动刷新页面">3. 自动刷新页面</h2> <p>当你用react开发的时候,网页内容会自动热更新,但是electron窗口的main.js中代码发生变化时不能热加载。</p> <p>安装插件electron-reloader:</p> <pre><code>yarn add --dev electron-reloader npm install --save-develectron-reloader</code></pre> <p>然后在路口引用插件:</p> <pre><code class="language-js">const reloader = require('electron-reloader') reloader(module)</code></pre> <p>就可以实现electron插件热更新。</p> <h2 id="4-主进程和渲染进程">4. 主进程和渲染进程</h2> <p>Electron运行package.json的main脚本的进程称为<strong>主进程</strong>。在主进程中运行的脚本通过创建web页面来展示用户节面,一个Electron应用总是有且只有一个主进程。</p> <p>由于Electron使用了Chromium来展示web页面,所以Chromium的多进程架构也被使用到,每个Electron钟大哥web页面运行在它的叫<strong>渲染进程</strong>的进程中。</p> <p>在普通的浏览器中,web页面无法访问操作系统的原生资源。然而Electron的用户在Node.js的API支持下可以在页面中和操作系统进行一些底层交互。</p> <p>ctrl + shift + i 打开渲染进程调试(devtools)</p> <p>默认打开调试:</p> <pre><code class="language-tsx">// 在启动的时候打开DevTools mainWindow.webContents.openDevTools()</code></pre> <h2 id="5定义原生菜单顶部菜单">5.定义原生菜单、顶部菜单</h2> <h3 id="51-自定义菜单">5.1 自定义菜单</h3> <p>可以使用Menu菜单来创建原生应用菜单和上下文菜单。</p> <ol> <li>首先判断是什么平台,是mac还是其他:</li> </ol> <pre><code class="language-js">const isMac = process.platform === 'darwin'</code></pre> <ol start="2"> <li>创建菜单模板:</li> </ol> <p>其是由一个个MenuItem组成的,可以在<a href="http://www.m6000.cn/wp-content/themes/begin%20lts/inc/go.php?url=https://www.electronjs.org/zh/docs/latest/api/menu-item"  target="_blank" rel="nofollow">菜单项官网API查看。</p> <pre><code class="language-js">const template = [   // { role: 'appMenu' }   // 如果是mac系统才有   ...(isMac ? [{     label: app.name,     submenu: [       { role: 'about' },       { type: 'separator' },       { role: 'services' },       { type: 'separator' },       { role: 'hide' },       { role: 'hideOthers' },       { role: 'unhide' },       { type: 'separator' },       { role: 'quit' }     ]   }] : []),   // { role: 'fileMenu' }   {     label: '文件',     submenu: [       isMac ? { role: 'close' } : { role: 'quit', label: '退出' }     ]   },   // { role: 'editMenu' }   {     label: '编辑',     submenu: [       { role: 'undo', label: '撤消' },       { role: 'redo', label: '恢复' },       { type: 'separator' },       { role: 'cut', label: '剪切' },       { role: 'copy', label: '复制' },       { role: 'paste', label: '粘贴' },       ...(isMac ? [         { role: 'pasteAndMatchStyle' },         { role: 'delete' },         { role: 'selectAll' },         { type: 'separator' },         {           label: 'Speech',           submenu: [             { role: 'startSpeaking' },             { role: 'stopSpeaking' }           ]         }       ] : [         { role: 'delete', label: '删除' },         { type: 'separator' },         { role: 'selectAll', label: '全选' }       ])     ]   },   // { role: 'viewMenu' }   {     label: '查看',     submenu: [       { role: 'reload', label: '重新加载' },       { role: 'forceReload', label: '强制重新加载' },       { role: 'toggleDevTools', label: '切换开发工具栏' },       { type: 'separator' },       { role: 'resetZoom', label: '原始开发工具栏窗口大小' },       { role: 'zoomIn', label: '放大开发工具栏窗口'},       { role: 'zoomOut', label: '缩小开发工具栏窗口' },       { type: 'separator' },       { role: 'togglefullscreen', label:'切换开发工具栏全屏' }     ]   },   // { role: 'windowMenu' }   {     label: '窗口',     submenu: [       { role: 'minimize', label:'最小化' },       ...(isMac ? [         { type: 'separator' },         { role: 'front' },         { type: 'separator' },         { role: 'window' }       ] : [         { role: 'close', label: '关闭' }       ])     ]   },   {     role: 'help',     label: '帮助',     submenu: [       {         label: '从Electron官网学习更多',         click: async () => {           const { shell } = require('electron')           await shell.openExternal('https://electronjs.org')         }       }     ]   } ]</code></pre> <ol start="3"> <li>根据模板创建menu:</li> </ol> <pre><code class="language-js">const menu = Menu.buildFromTemplate(template)</code></pre> <ol start="4"> <li>设置菜单:</li> </ol> <pre><code class="language-js">Menu.setApplicationMenu(menu)</code></pre> <h3 id="52-给菜单定义点击事件">5.2 给菜单定义点击事件</h3> <p>可以通过<code>click</code>属性来设置点击事件</p> <h3 id="53-抽离菜单定义">5.3 抽离菜单定义</h3> <p>创建一个menu.js:</p> <pre><code class="language-js">const {app, Menu } = require('electron')  const isMac = process.platform === 'darwin'  const template = [   // { role: 'appMenu' }   // 如果是mac系统才有   ...(isMac ? [{     label: app.name,     submenu: [       { role: 'about' },       { type: 'separator' },       { role: 'services' },       { type: 'separator' },       { role: 'hide' },       { role: 'hideOthers' },       { role: 'unhide' },       { type: 'separator' },       { role: 'quit' }     ]   }] : []),   // { role: 'fileMenu' }   {     label: '文件',     submenu: [       isMac ? { role: 'close' } : { role: 'quit', label: '退出' }     ]   },   // { role: 'editMenu' }   {     label: '编辑',     submenu: [       { role: 'undo', label: '撤消' },       { role: 'redo', label: '恢复' },       { type: 'separator' },       { role: 'cut', label: '剪切' },       { role: 'copy', label: '复制' },       { role: 'paste', label: '粘贴' },       ...(isMac ? [         { role: 'pasteAndMatchStyle' },         { role: 'delete' },         { role: 'selectAll' },         { type: 'separator' },         {           label: 'Speech',           submenu: [             { role: 'startSpeaking' },             { role: 'stopSpeaking' }           ]         }       ] : [         { role: 'delete', label: '删除' },         { type: 'separator' },         { role: 'selectAll', label: '全选' }       ])     ]   },   // { role: 'viewMenu' }   {     label: '查看',     submenu: [       { role: 'reload', label: '重新加载' },       { role: 'forceReload', label: '强制重新加载' },       { role: 'toggleDevTools', label: '切换开发工具栏' },       { type: 'separator' },       { role: 'resetZoom', label: '原始开发工具栏窗口大小' },       { role: 'zoomIn', label: '放大开发工具栏窗口'},       { role: 'zoomOut', label: '缩小开发工具栏窗口' },       { type: 'separator' },       { role: 'togglefullscreen', label:'切换开发工具栏全屏' }     ]   },   // { role: 'windowMenu' }   {     label: '窗口',     submenu: [       { role: 'minimize', label:'最小化' },       ...(isMac ? [         { type: 'separator' },         { role: 'front' },         { type: 'separator' },         { role: 'window' }       ] : [         { role: 'close', label: '关闭' }       ])     ]   },   {     role: 'help',     label: '帮助',     submenu: [       {         label: '从Electron官网学习更多',         click: async () => {           const { shell } = require('electron')           await shell.openExternal('https://electronjs.org')         }       }     ]   } ]  const menu = Menu.buildFromTemplate(template) Menu.setApplicationMenu(menu)</code></pre> <p>然后在<code>main.js</code>中<code>createWindow</code>的方法使用<code>require</code>调用:</p> <pre><code class="language-js">const createWindow = () => {     ......     require('./menu')     ...... }</code></pre> <h3 id="54-自定义顶部菜单">5.4 自定义顶部菜单</h3> <p>我们可以自定义顶部菜单,通过以下两个步骤进行:</p> <ul> <li>先通过frame创建无边框窗口。</li> </ul> <pre><code class="language-js">function createWindow () {     const mainWindow = new BrowserWindow({         ......         frame: false     })     }</code></pre> <ul> <li>然后再通过前端页面布局设置顶部菜单</li> </ul> <p>如果想让顶部菜单支持拖拽,可以加如下css:</p> <pre><code>-webkit-app-region: drag;</code></pre> <h3 id="55-在渲染进程中使用主进程方法remote和electron点击创建新窗口">5.5 在渲染进程中使用主进程方法remote和electron(点击创建新窗口)</h3> <p>我们想要通过<strong>remote</strong>来使用主进程方法和功能。</p> <ol> <li>首先要安装<code>@electron/remote</code></li> </ol> <pre><code class="language-tsx">yarn add @electron/remote</code></pre> <ol start="2"> <li>在主进程main.js中配置remote:</li> </ol> <pre><code class="language-tsx">const remote = require("@electron/remote/main") remote.initialize()  const createWindow = () => {     let mainWindow = new BrowserWindow({         ......         webPreferences: { // 网页功能设置         	......             nodeIntegration: true, // 是否启用node集成 渲染进程的内容有访问node的能力,建议设置为true, 否则在render页面会提示node找不到的错误             contextIsolation : false, //允许渲染进程使用Nodejs         }     })     remote.enable(mainWindow.webContents) }</code></pre> <ol start="3"> <li>在渲染进程中使用remote的BrowserWindow:App.tsx</li> </ol> <pre><code class="language-tsx">import React from 'react' // 使用electron的功能 // const electron = window.require('electron') // 使用remote const { BrowserWindow } = window.require("@electron/remote")  export default function App() {   const openNewWindow = () => {     new BrowserWindow({       width:500,       height:500     })   }    return (     <div>       App       <div>         <button onClick={openNewWindow}>点我开启新窗口</button>       </div>     </div>   ) }</code></pre> <p>我们想要通过使用electron提供给渲染进程的API:</p> <pre><code class="language-tsx">const electron = window.require('electron')</code></pre> <p>然后从electron中提取方法。</p> <h3 id="56-点击打开浏览器">5.6 点击打开浏览器</h3> <p>使用electron中的shell可以实现此功能:</p> <pre><code class="language-tsx">import React from 'react' // 使用electron的功能 // const electron = window.require('electron') // 使用remote // const { BrowserWindow } = window.require("@electron/remote") // 使用shell const { shell } = window.require('electron')  export default function App() {   const openNewWindow = () => {     shell.openExternal('https://www.baidu.com')   }    return (     <div>       App       <div>         <button onClick={openNewWindow}>点我开启新窗口打开百度</button>       </div>     </div>   ) }</code></pre> <h2 id="6-打开对话框读取文件">6. 打开对话框读取文件</h2> <h3 id="61-读取文件">6.1 读取文件</h3> <p>主进程中的dialog模块可以显示用于打开和保存文件、警报等的本机系统对话框。</p> <p>因为dialog模块属于主进程,如果我们在渲染进程中需要使用则需要使用remote模块。</p> <p><strong>App.tsx</strong></p> <pre><code class="language-tsx">import React,{ useRef } from 'react' // 使用electron的功能 // const electron = window.require('electron')  // 使用remote // const remote = window.require('@electron/remote') // const { BrowserWindow } = window.require("@electron/remote") const { dialog } = window.require("@electron/remote")  // 使用shell const { shell } = window.require('electron')  // 使用fs const fs = window.require('fs')  export default function App() {   // ref    const textRef = useRef<HTMLTextAreaElement | null>(null)    const openNewWindow = () => {     shell.openExternal('https://www.baidu.com')   }    const openFile = () => {     const res = dialog.showOpenDialogSync({       title: '读取文件', // 对话框窗口的标题       buttonLabel: "读取", // 按钮的自定义标签, 当为空时, 将使用默认标签。       filters: [ // 用于规定用户可见或可选的特定类型范围         //{ name: 'Images', extensions: ['jpg', 'png', 'gif', 'jpeg', 'webp'] },         //{ name: 'Movies', extensions: ['mkv', 'avi', 'mp4'] },         { name: 'Custom File Type', extensions: ['js'] },         { name: 'All Files', extensions: ['*'] },       ]     })     const fileContent:string  = fs.readFileSync(res[0]).toString();     (textRef.current as HTMLTextAreaElement).value = fileContent   }    return (     <div>       App Test       <div>         <button onClick={openNewWindow}>点我开启新窗口打开百度</button>       </div>       <div>         <button onClick={openFile}>打开文件</button>         <textarea ref={textRef}></textarea>       </div>     </div>   ) }</code></pre> <h3 id="62-保存文件">6.2 保存文件</h3> <p>保存文件需要使用dialog函数里的<code>showSaveDialogSync</code>,与之前的读取文件所用到的<code>showOpenDialogSync</code>类似:</p> <pre><code class="language-tsx">const saveFile = () => {     const res = dialog.showSaveDialogSync({         title:'保存文件',         buttonLable: "保存",         filters: [             { name: 'index', extensions: ['js']}         ]     })     fs.writeFileSync(res, textRef.current?.value) }</code></pre> <h2 id="7-定义快捷键">7. 定义快捷键</h2> <h3 id="71-主线程定义">7.1 主线程定义</h3> <p>引入<code>globalShortcut</code></p> <pre><code class="language-js">const {app, BrowserWindow, nativeImage, globalShortcut } = require('electron')</code></pre> <p>注册快捷键打印字符串、窗口最大化、窗口最小化、关闭窗口。</p> <pre><code class="language-js">const createWindow = () => {     ......     // 注册快捷键     globalShortcut.register('CommandOrControl+X', () => {         console.log('CommandOrControl + X is pressed')     })      globalShortcut.register('CommandOrControl+M', () => {         mainWindow.maximize()     })      globalShortcut.register('CommandOrControl+T', () => {         mainWindow.unmaximize()     })      globalShortcut.register('CommandOrControl+H', () => {         mainWindow.close()     })      // 检查快捷键是否注册成功     // console.log(globalShortcut.isRegistered('CommandOrControl+X')) }  // 将要退出时的生命周期,注销快捷键 app.on('will-quit', () => {     // 注销快捷键     globalShortcut.unregister('CommandOrControl+X')     // 注销所有快捷键     globalShortcut.unregisterAll() })</code></pre> <h3 id="72在渲染进程中定义">7.2在渲染进程中定义</h3> <p>通过retmote来定义</p> <pre><code class="language-tsx">const { globalShortcut } = window.require("@electron/remote")   globalShortcut.register("Ctrl+O", () => {     console.log('ctrl+O is pressed.') })</code></pre> <h2 id="8-主进程和渲染进程通讯">8. 主进程和渲染进程通讯</h2> <p>在渲染进程使用ipcRenderer,主进程使用ipcMain,可以实现主进程和渲染进程的通讯:</p> <p>App.tsx</p> <pre><code class="language-tsx">...... import React,{ useState, useRef } from 'react' const { shell, ipcRenderer } = window.require('electron') export default function App() {     // state   	const [windowSize, setWindowSize] = useState('max-window')     ......     // 传参     const maxWindow = () => {         ipcRenderer.send('max-window', windowSize);         if(windowSize === 'max-window') {             setWindowSize('unmax-window')         } else {             setWindowSize('max-window')         }     }         ......         return (             <div>                     <div>                         <button onClick={maxWindow}>与主进程进行通讯,窗口最大化或取消窗口最大化</button>                     </div>                 </div>             </div>         ) }</code></pre> <p>main.js</p> <pre><code class="language-js">const {app, BrowserWindow, nativeImage, globalShortcut, ipcMain } = require('electron') const createWindow = () => {     let mainWindow = ......     ......     // 定义通讯事件     ipcMain.on('max-window', (event, arg) => {         if(arg === 'max-window') {             mainWindow.maximize()         } else if (arg === 'unmax-window') {             mainWindow.unmaximize()         }     })     ...... } ......</code></pre> 			                                            <div class="col-md-12 mt-5">
                                                                                                <p>上一个:<a href="/html/category/article-2979.htm">Java中List.contains(Object object)方法使用_java</a></p>
                                                                                                <p>下一个:<a href="/html/category/article-2981.htm">vue实现图形验证码</a></p>
                                                                                            </div>
                                                                                    </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="col-md-3">
                    <div class="panel panel-default">
    <div class="panel-heading">
        <h3 class="panel-title">热门文章</h3>
    </div>
    <div class="panel-body">
        <ul class="p-0 x-0" style="list-style: none;margin: 0;padding: 0;">
                        <li class="py-2"><a href="/html/category/article-9993.htm" title="狗咬了多久过安全期可以去***吗(狗咬了多久过安全期过了一年有事吗)">狗咬了多久过安全期可以去***吗(狗咬了多久过安全期过了一年有事吗)</a></li>
                        <li class="py-2"><a href="/html/category/article-9946.htm" title="动物疫苗和人体疫苗生产有什么区别呢图片 动物疫苗和人体疫苗生产有什么区别呢图片大全">动物疫苗和人体疫苗生产有什么区别呢图片 动物疫苗和人体疫苗生产有什么区别呢图片大全</a></li>
                        <li class="py-2"><a href="/html/category/article-10222.htm" title="动物疫苗的储存要求(动物疫苗冷藏温度)">动物疫苗的储存要求(动物疫苗冷藏温度)</a></li>
                        <li class="py-2"><a href="/html/category/article-10084.htm" title="中国品牌窗帘十大排名(中国十大名牌窗帘的牌子)">中国品牌窗帘十大排名(中国十大名牌窗帘的牌子)</a></li>
                        <li class="py-2"><a href="/html/category/article-10545.htm" title="兽药疫苗采购(兽用疫苗销售)">兽药疫苗采购(兽用疫苗销售)</a></li>
                        <li class="py-2"><a href="/html/category/article-10637.htm" title="开个宠物食品厂怎么样啊(开宠物食品厂有利润吗)">开个宠物食品厂怎么样啊(开宠物食品厂有利润吗)</a></li>
                        <li class="py-2"><a href="/html/category/article-10360.htm" title="快乐大本营彭冠英是哪一期 图揭彭冠英张含韵怎么认识的(快乐大本营彭冠英蔡文静)">快乐大本营彭冠英是哪一期 图揭彭冠英张含韵怎么认识的(快乐大本营彭冠英蔡文静)</a></li>
                        <li class="py-2"><a href="/html/category/article-10821.htm" title="办理动物医院需要什么手续和证件呢英文(动物医院许可证)">办理动物医院需要什么手续和证件呢英文(动物医院许可证)</a></li>
                        <li class="py-2"><a href="/html/category/article-10544.htm" title="领养宠物协议合法吗现在(领养宠物协议怎么写)">领养宠物协议合法吗现在(领养宠物协议怎么写)</a></li>
                        <li class="py-2"><a href="/html/category/article-9624.htm" title="猫咪三针多久打完(猫咪三针多久打完有效)">猫咪三针多久打完(猫咪三针多久打完有效)</a></li>
                    </ul>
    </div>
</div>

<div class="panel panel-default">
    <div class="panel-heading">
        <h3 class="panel-title">归纳</h3>
    </div>
    <div class="panel-body">
        <ul class="p-0 x-0" style="list-style: none;margin: 0;padding: 0;">
                        <li class="py-2">
                <h4><span class="badge" style="float: right;">48</span> <a href="/html/date/2024-10/" title="2024-10 归档">2024-10</a></h4>
            </li>
                        <li class="py-2">
                <h4><span class="badge" style="float: right;">52</span> <a href="/html/date/2024-09/" title="2024-09 归档">2024-09</a></h4>
            </li>
                        <li class="py-2">
                <h4><span class="badge" style="float: right;">62</span> <a href="/html/date/2024-08/" title="2024-08 归档">2024-08</a></h4>
            </li>
                        <li class="py-2">
                <h4><span class="badge" style="float: right;">62</span> <a href="/html/date/2024-07/" title="2024-07 归档">2024-07</a></h4>
            </li>
                        <li class="py-2">
                <h4><span class="badge" style="float: right;">60</span> <a href="/html/date/2024-06/" title="2024-06 归档">2024-06</a></h4>
            </li>
                        <li class="py-2">
                <h4><span class="badge" style="float: right;">62</span> <a href="/html/date/2024-05/" title="2024-05 归档">2024-05</a></h4>
            </li>
                        <li class="py-2">
                <h4><span class="badge" style="float: right;">60</span> <a href="/html/date/2024-04/" title="2024-04 归档">2024-04</a></h4>
            </li>
                        <li class="py-2">
                <h4><span class="badge" style="float: right;">62</span> <a href="/html/date/2024-03/" title="2024-03 归档">2024-03</a></h4>
            </li>
                        <li class="py-2">
                <h4><span class="badge" style="float: right;">44</span> <a href="/html/date/2024-02/" title="2024-02 归档">2024-02</a></h4>
            </li>
                    </ul>
    </div>
</div>


                </div>
            </div>
        </div>
    </div>
    <!-- End Blog -->
        <!-- Star Footer
    ============================================= -->
    <footer class="bg-dark text-light">
        <!-- Footer Bottom -->
        <div class="footer-bottom">
            <div class="container">
                <div class="row">
                    <div class="col-lg-6">
                        <p>
                            Oman Address 版权所有
                            <br />
                            Powered by WordPress
                        </p>
                    </div>
                    <div class="col-lg-6 text-right link">
                        <ul>
                            <li>
                                <a href="#">Terms</a>
                            </li>
                            <li>
                                <a href="#">Privacy</a>
                            </li>
                            <li>
                                <a href="#">Support</a>
                            </li>
                        </ul>
                    </div>
                </div>
            </div>
        </div>
        <!-- End Footer Bottom -->
    </footer>
    <!-- End Footer-->

    <!-- jQuery Frameworks
    ============================================= -->
    <script src="/assets/website/js/frontend/omanaddress/jquery-1.12.4.min.js"></script>
    <script src="/assets/website/js/frontend/omanaddress/popper.min.js"></script>
    <script src="/assets/website/js/frontend/omanaddress/bootstrap.min.js"></script>
    <script src="/assets/website/js/frontend/omanaddress/jquery.appear.js"></script>
    <script src="/assets/website/js/frontend/omanaddress/jquery.easing.min.js"></script>
    <script src="/assets/website/js/frontend/omanaddress/jquery.magnific-popup.min.js"></script>
    <script src="/assets/website/js/frontend/omanaddress/modernizr.custom.13711.js"></script>
    <script src="/assets/website/js/frontend/omanaddress/owl.carousel.min.js"></script>
    <script src="/assets/website/js/frontend/omanaddress/wow.min.js"></script>
    <script src="/assets/website/js/frontend/omanaddress/progress-bar.min.js"></script>
    <script src="/assets/website/js/frontend/omanaddress/isotope.pkgd.min.js"></script>
    <script src="/assets/website/js/frontend/omanaddress/imagesloaded.pkgd.min.js"></script>
    <script src="/assets/website/js/frontend/omanaddress/count-to.js"></script>
    <script src="/assets/website/js/frontend/omanaddress/YTPlayer.min.js"></script>
    <script src="/assets/website/js/frontend/omanaddress/jquery.nice-select.min.js"></script>
    <script src="/assets/website/js/frontend/omanaddress/loopcounter.js"></script>
    <script src="/assets/website/js/frontend/omanaddress/bootsnav.js"></script>
    <script src="/assets/website/js/frontend/omanaddress/main.js"></script>
    <script>
    $(function() {
        $('.js_to').click(function() {
            var url = $(this).data('url');
            var code = $(this).data('code');
            url += code;

            window.open(url);
        })
    });
    </script>
</body>

</html>