• electron27+react18集成搭建跨平台应用|electron窗口多开


    基于Electron27集成React18创建一个桌面端exe程序。

    electron27-vite4-react18基于electron27结合vite4构建工具快速创建react18跨端应用实践。

    在这里插入图片描述

    版本列表

    "vite": "^4.4.5"
    "react": "^18.2.0"
    "electron": "^27.0.1"
    "electron-builder": "^24.6.4"
    "vite-plugin-electron": "^0.14.1"
    
    • 1
    • 2
    • 3
    • 4
    • 5

    快速创建react18项目

    这里选择使用vite.js构建工具来快速创建一个react18项目。

    yarn create vite electron-vite4-react18
    // 选择创建react模板
    cd electron-vite4-react18
    yarn install
    yarn dev
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述
    这样一个简单的react18项目就已经创建完毕了。

    安装electron依赖包

    注意:electron依赖安装在 devDependencies 里面。

    // 安装electron
    yarn add -D electron
    // 安装electron-builder 用于构建打包可安装exe程序
    yarn add -D electron-builder
    // 安装electron-devtools-installer 用于开发调试electron项目
    yarn add -D electron-devtools-installer
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    安装的过程可能比较慢,甚至卡顿,可以考虑切换taobao镜像。

    接下来还需要安装一个vite集成electron插件。

    yarn add -D vite-plugin-electron
    
    • 1

    创建electron主进程

    在项目根目录下新建一个主进程文件electron-main.js。

    在这里插入图片描述

    const { app, BrowserWindow } = require('electron')
    
    const MultiWindow = require('./src/windows')
    
    const createWindow = () => {
        let win = new MultiWindow()
        win.createWin({ isMainWin: true })
    }
    
    app.whenReady().then(() => {
        createWindow()
        app.on('activate', () => {
            if (BrowserWindow.getAllWindows().length === 0) createWindow()
        })
    })
    
    app.on('window-all-closed', () => {
        if (process.platform !== 'darwin') app.quit()
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    配置vite.config.js主进程入口。

    import { defineConfig, loadEnv } from 'vite'
    import react from '@vitejs/plugin-react'
    import electron from 'vite-plugin-electron'
    import { resolve } from 'path'
    import { parseEnv } from './src/utils/env'
    
    // https://vitejs.dev/config/
    export default defineConfig(({ command, mode }) => {
        const viteEnv = loadEnv(mode, process.cwd())
        const env = parseEnv(viteEnv)
    
        return {
            plugins: [
                react(),
                electron({
                    entry: 'electron-main.js',
                })
            ],
    
            esbuild: {
                // 打包去除 console.log 和 debugger
                drop: env.VITE_DROP_CONSOLE && command === 'build' ? ["console", "debugger"] : []
            },
    
            /* 开发服务器配置 */
            server: {
                // 端口
                port: env.VITE_PORT,
                // 代理配置
                proxy: {
                    // ...
                }
            },
    
            resolve: {
                // 设置别名
                alias: {
                    '@': resolve(__dirname, 'src'),
                    '@assets': resolve(__dirname, 'src/assets'),
                    '@components': resolve(__dirname, 'src/components'),
                    '@views': resolve(__dirname, 'src/views')
                }
            }
        }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    简单配置下package.json

    修改package.json文件,加入"main": "electron-main.js"入口配置,并且需要去掉 “type”: “module”

    接下来运行命令 yarn electron:serve就可以启动桌面端项目了。

    在这里插入图片描述

    之前也有分享过electron+vite4+vue3创建桌面端项目的一些分享,里面有打包的一些配置,感兴趣可以去看看。

    https://blog.csdn.net/yanxinyun1990/article/details/130944508

    https://blog.csdn.net/yanxinyun1990/article/details/130977430

    electron创建无边框窗口拖拽

    在这里插入图片描述
    创建窗口配置 frame: false 即可创建一个无系统边框的窗体。

    在这里插入图片描述
    css设置 -webkit-app-region: drag 来实现拖拽区域。设置 -webkit-app-region: no-drag 取消拖拽响应。

    自定义窗口最大化/最小化/关闭功能。

    import { useState, useContext } from 'react'
    import { Modal } from '@arco-design/web-react'
    import { setWin } from '@/windows/action'
    
    function WinBtn(props) {
      const {
        color = '#fff',
        minimizable = true,
        maximizable = true,
        closable = true,
        zIndex = 2023,
    
        children
      } = props
    
      const [hasMaximized, setHasMaximized] = useState(false)
    
      window.electronAPI.invoke('win__isMaximized').then(res => {
        setHasMaximized(res)
      })
      window.electronAPI.receive('win__hasMaximized', (e, res) => {
        setHasMaximized(res)
      })
    
      // 最小化
      const handleWinMin = () => {
          window.electronAPI.send("win__minimize")
      }
      // 最大化/还原
      const handleWinMax2Min = () => {
          window.electronAPI.invoke("win__max2min").then(res => {
              console.log(res)
              setHasMaximized(res)
          })
      }
      // 关闭
      const handleWinClose = () => {
          if(window.config.isMainWin) {
            Modal.confirm({
              title: '提示',
              content: <div style={{ textAlign: 'center' }}>是否最小化至托盘,不退出程序?</div>,
              okButtonProps: {status: 'warning'},
              style: {width: 360},
              cancelText: '最小化至托盘',
              okText: '残忍退出',
              onOk: () => {
                setWin('close')
              },
              onCancel: () => {
                setWin('hide', window.config.id)
              }
            })
          }else {
              setWin('close', window.config.id)
          }
      }
    
      return (
        <>
          <div className="vui__macbtn flexbox flex-alignc" style={{zIndex: zIndex}}>
            <div className="vui__macbtn-groups flexbox flex-alignc" style={{color: color}}>
              { JSON.parse(minimizable) && <a className="mbtn min" title="最小化" onClick={handleWinMin}><svg x="0" y="0" width="10" height="10" viewBox="0 0 10 10"><path fill="#995700" d="M8.048,4.001c0.163,0.012 0.318,0.054 0.459,0.137c0.325,0.191 0.518,0.559 0.49,0.934c-0.007,0.097 -0.028,0.192 -0.062,0.283c-0.04,0.105 -0.098,0.204 -0.171,0.29c-0.083,0.098 -0.185,0.181 -0.299,0.24c-0.131,0.069 -0.271,0.103 -0.417,0.114c-2.031,0.049 -4.065,0.049 -6.096,0c-0.163,-0.012 -0.318,-0.054 -0.459,-0.137c-0.325,-0.191 -0.518,-0.559 -0.49,-0.934c0.007,-0.097 0.028,-0.192 0.062,-0.283c0.04,-0.105 0.098,-0.204 0.171,-0.29c0.083,-0.098 0.185,-0.181 0.299,-0.24c0.131,-0.069 0.271,-0.103 0.417,-0.114c2.031,-0.049 4.065,-0.049 6.096,0Z"></path></svg></a> }
              { JSON.parse(maximizable) &&
                <a className="mbtn max" title={hasMaximized ? '向下还原' : '最大化'} onClick={handleWinMax2Min}>
                  {
                    hasMaximized ?
                    <svg x="0" y="0" width="10" height="10" viewBox="0 0 10 10"><path fill="#4d0000" d="M5,10c0,0 0,-2.744 0,-4.167c0,-0.221 -0.088,-0.433 -0.244,-0.589c-0.156,-0.156 -0.368,-0.244 -0.589,-0.244c-1.423,0 -4.167,0 -4.167,0l5,5Z"></path><path fill="#006400" d="M5,0c0,0 0,2.744 0,4.167c0,0.221 0.088,0.433 0.244,0.589c0.156,0.156 0.368,0.244 0.589,0.244c1.423,0 4.167,0 4.167,0l-5,-5Z"></path></svg>
                    :
                    <svg x="0" y="0" width="10" height="10" viewBox="0 0 10 10"><path fill="#4d0000" d="M2,3c0,0 0,2.744 0,4.167c0,0.221 0.088,0.433 0.244,0.589c0.156,0.156 0.368,0.244 0.589,0.244c1.423,0 4.167,0 4.167,0l-5,-5Z"></path><path fill="#006400" d="M8,7c0,0 0,-2.744 0,-4.167c0,-0.221 -0.088,-0.433 -0.244,-0.589c-0.156,-0.156 -0.368,-0.244 -0.589,-0.244c-1.423,0 -4.167,0 -4.167,0l5,5Z"></path></svg>
                  }
              </a>
              }
              { JSON.parse(closable) && <a className="mbtn close" title="关闭" onClick={handleWinClose}><svg x="0" y="0" width="10" height="10" viewBox="0 0 10 10"><path fill="#4d0000" d="M5,3.552c0.438,-0.432 0.878,-0.861 1.322,-1.287c0.049,-0.044 0.101,-0.085 0.158,-0.119c0.149,-0.091 0.316,-0.137 0.49,-0.146c0.04,0 0.04,0 0.08,0.001c0.16,0.011 0.314,0.054 0.453,0.135c0.08,0.046 0.154,0.104 0.218,0.171c0.252,0.262 0.342,0.65 0.232,0.996c-0.045,0.141 -0.121,0.265 -0.218,0.375c-0.426,0.444 -0.855,0.884 -1.287,1.322c0.432,0.438 0.861,0.878 1.287,1.322c0.097,0.11 0.173,0.234 0.218,0.375c0.04,0.126 0.055,0.26 0.043,0.392c-0.011,0.119 -0.043,0.236 -0.094,0.344c-0.158,0.327 -0.49,0.548 -0.852,0.566c-0.106,0.005 -0.213,-0.007 -0.315,-0.035c-0.156,-0.043 -0.293,-0.123 -0.413,-0.229c-0.444,-0.426 -0.884,-0.855 -1.322,-1.287c-0.438,0.432 -0.878,0.861 -1.322,1.287c-0.11,0.097 -0.234,0.173 -0.375,0.218c-0.126,0.04 -0.26,0.055 -0.392,0.043c-0.119,-0.011 -0.236,-0.043 -0.344,-0.094c-0.327,-0.158 -0.548,-0.49 -0.566,-0.852c-0.005,-0.106 0.007,-0.213 0.035,-0.315c0.043,-0.156 0.123,-0.293 0.229,-0.413c0.426,-0.444 0.855,-0.884 1.287,-1.322c-0.432,-0.438 -0.861,-0.878 -1.287,-1.322c-0.106,-0.12 -0.186,-0.257 -0.229,-0.413c-0.025,-0.089 -0.037,-0.182 -0.036,-0.275c0.004,-0.363 0.211,-0.704 0.532,-0.874c0.13,-0.069 0.272,-0.105 0.418,-0.115c0.04,-0.001 0.04,-0.001 0.08,-0.001c0.174,0.009 0.341,0.055 0.49,0.146c0.057,0.034 0.109,0.075 0.158,0.119c0.444,0.426 0.884,0.855 1.322,1.287Z"></path></svg></a> }
              <i className="mr-10"></i>
              { children }
            </div>
            <div className="vui__mactitle">{window.config.title || '首页'}</div>
        </div>
        </>
      )
    }
    
    export default WinBtn
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    electron创建托盘功能

    在这里插入图片描述

    /**
     * Electron多窗口管理器
     * @author Andy  Q:282310962
     */
    
    const { app, BrowserWindow, ipcMain, Menu, Tray, dialog, globalShortcut } = require('electron')
    // const { loadEnv } = require('vite')
    const { join } = require('path')
    
    // 根目录路径
    process.env.ROOT = join(__dirname, '../../')
    
    const isDev = process.env.NODE_ENV === 'development'
    // const winURL = isDev ? 'http://localhost:3000/' : join(__dirname, 'dist/index.html')
    const winURL = isDev ? process.env.VITE_DEV_SERVER_URL : join(process.env.ROOT, 'dist/index.html')
    
    class MultiWindow {
        constructor() {
            // 主窗口对象
            this.main = null
            // 窗口组
            this.group = {}
    
            // 托盘图标
            this.tray = null
            this.flashTimer = null
            this.trayIco1 = join(process.env.ROOT, 'resource/tray.ico')
            this.trayIco2 = join(process.env.ROOT, 'resource/tray-empty.ico')
    
            // 监听ipcMain事件
            this.listenIpc()
            
            // 创建系统托盘
            this.createTray()
        }
    
        // 系统配置参数
        winOptions() {
            return {
                // 窗口图标
                icon: join(process.env.ROOT, 'resource/shortcut.ico'),
                backgroundColor: '#fff',
                autoHideMenuBar: true,
                titleBarStyle: 'hidden',
                width: 900,
                height: 600,
                resizable: true,
                minimizable: true,
                maximizable: true,
                frame: false, // 设置为 false 时可以创建一个无边框窗口 默认值为 true
                show: false, // 窗口是否在创建时显示
                webPreferences: {
                    contextIsolation: true, // 启用上下文隔离(为了安全性)(默认true)
                    nodeIntegration: false, // 启用Node集成(默认false)
                    preload: join(process.env.ROOT, 'electron-preload.js')
                }
            }
        }
    
        // 创建新窗口
        createWin(options) {
            // ...
        }
    
        // ...
    
        // 主进程监听事件
        listenIpc() {
            // 创建新窗体
            ipcMain.on('win-create', (event, args) => this.createWin(args))
    
            // ...
    
            // 托盘图标闪烁
            ipcMain.on('win__flashTray', (event, bool) => this.flashTray(bool))
    
            // 屏幕截图
            ipcMain.on('win__setCapture', () => {
                // ...
            })
        }
    
        // 创建系统托盘图标
        createTray() {
            console.log(__dirname)
            console.log(join(process.env.ROOT, 'resource/tray.ico'))
            
            const trayMenu = Menu.buildFromTemplate([
                {
                    label: '打开主界面',
                    icon: join(process.env.ROOT, 'resource/home.png'),
                    click: () => {
                        try {
                            for(let i in this.group) {
                                let win = this.getWin(i)
                                if(!win) return
                                // 是否主窗口
                                if(this.group[i].isMainWin) {
                                    if(win.isMinimized()) win.restore()
                                    win.show()
                                }
                            }
                        } catch (error) {
                            console.log(error)
                        }
                    }
                },
                {
                    label: '设置中心',
                    icon: join(process.env.ROOT, 'resource/setting.png'),
                    click: () => {
                        for(let i in this.group) {
                            let win = this.getWin(i)
                            if(win) win.webContents.send('win__ipcData', { type: 'CREATE_WIN_SETTING', value: null })
                        }
                    },
                },
                {
                    label: '锁屏',
                    click: () => null,
                },
                {
                    label: '关闭托盘闪烁',
                    click: () => {
                        this.flashTray(false)
                    }
                },
                {type: 'separator'},
                /* {
                    label: '重启',
                    click: () => {
                        // app.relaunch({ args: process.argv.slice(1).concat(['--relaunch']) })
                        // app.exit(0)
    
                        app.relaunch()
                        app.quit()
                    }
                }, */
                {
                    label: '关于',
                    click: () => {
                        for(let i in this.group) {
                            let win = this.getWin(i)
                            if(win) win.webContents.send('win__ipcData', { type: 'CREATE_WIN_ABOUT', value: null })
                        }
                    }
                },
                {
                    label: '关闭应用并退出',
                    icon: join(process.env.ROOT, 'resource/logout.png'),
                    click: () => {
                        dialog.showMessageBox(this.main, {
                            title: '询问',
                            message: '确定要退出应用程序吗?',
                            buttons: ['取消', '最小化托盘', '退出应用'],
                            type: 'error',
                            noLink: false,  // true传统按钮样式  false链接样式
                            cancelId: 0
                        }).then(res => {
                            console.log(res)
    
                            const index = res.response
                            if(index == 0) {
                                console.log('取消')
                            }if(index == 1) {
                                console.log('最小化托盘')
                                for(let i in this.group) {
                                    let win = this.getWin(i)
                                    if(win) win.hide()
                                }
                            }else if(index == 2) {
                                console.log('退出应用')
    
                                try {
                                    for(let i in this.group) {
                                        let win = this.getWin(i)
                                        if(win) win.webContents.send('win__ipcData', { type: 'WIN_LOGOUT', value: null })
                                    }
                                    // app.quit 和 app.exit(0) 都可退出应用。
                                    // 前者可以被打断并触发一些事件,而后者将强制应用程序退出而不触发任何事件或允许应用程序取消操作。
                                    app.quit()
                                } catch (error) {
                                    console.log(error)
                                }
                            }
                        })
                    }
                }
            ])
            this.tray = new Tray(this.trayIco1)
            this.tray.setContextMenu(trayMenu)
            this.tray.setToolTip(app.name)
            this.tray.on('double-click', () => {
                console.log('double clicked')
            })
    
            // 开启托盘闪烁
            // this.flashTray(true)
        }
    
        // 托盘图标闪烁
        flashTray(flash) {
            let hasIco = false
            
            if(flash) {
                if(this.flashTimer) return
                this.flashTimer = setInterval(() => {
                    this.tray.setImage(hasIco ? this.trayIco1 : this.trayIco2)
                    hasIco = !hasIco
                }, 500)
            }else {
                if(this.flashTimer) {
                    clearInterval(this.flashTimer)
                    this.flashTimer = null
                }
                this.tray.setImage(this.trayIco1)
            }
        }
    
        // 销毁托盘图标
        destoryTray() {
            this.flashTray(false)
            this.tray.destroy()
            this.tray = null
        }
    }
    
    module.exports = MultiWindow
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228

    综上就是electron27+react18创建跨端桌面应用的一些分享,希望能喜欢~~

    https://blog.csdn.net/yanxinyun1990/article/details/133880077

    https://blog.csdn.net/yanxinyun1990/article/details/132825719

    在这里插入图片描述

  • 相关阅读:
    抖音小店开店最新一套运营教程,低成本的风口项目,直接套用就行
    115.Kerberos的安装配置与启用
    【数据结构】一文了解链表【思路+例题学习】
    JavaScript+Flask 实现视频上传的简单demo
    【Mysql】 InnoDB引擎深入- 行溢出
    Linux网络编程一(协议、TCP协议、UDP、socket编程、TCP服务器端及客户端)
    enumerate内置函数的使用
    mackdown语法
    Node.js编译失败怎么办?
    Java操作k8s api示例:使用kubeconfig文件认证;获取所有pod;获取pod内应用容器的启动日志
  • 原文地址:https://blog.csdn.net/yanxinyun1990/article/details/134047329