" 请稍等一会... " Please wait for a long time <我不等啦!>
Light
Electron窗口

创建接口

electron/interface/IWindowConfig.ts

export default interface IWindowConfig {
  id: number | null
  title: string
  width: number | null
  height: number | null
  minWidth: number | null
  minHeight: number | null
  route: string
  resizable: boolean
  maximize: boolean
  backgroundColor: string
  data: object | null
  isMultiWindow: boolean
  isMainWin: boolean
  parentId: number | null
  modal: boolean
}

electron/interface/IWindowOption.ts

import { BrowserWindow } from 'electron'

export default interface IWindowOption {
  width: number
  height: number
  maxWidth: number
  maxHeight: number
  minWidth: number
  minHeight: number
  center: boolean
  transparent: boolean
  backgroundColor: string
  autoHideMenuBar: boolean
  resizable: boolean
  minimizable: boolean
  maximizable: boolean
  frame: boolean
  show: boolean
  parent?: BrowserWindow
  modal: boolean
  webPreferences: {
    contextIsolation: boolean //上下文隔离
    nodeIntegration: boolean //启用 Node 集成(是否完整的支持 node)
    webSecurity: boolean
    preload: string
  }
}

创建窗口类

electron/controller/createWindow.ts

import { app, BrowserWindow, globalShortcut, IpcMainEvent, ipcMain } from 'electron'
import IWindowOption from '../interface/IWindowOption'
import IWindowConfig from '../interface/IWindowConfig'
import { join } from 'path'
const path = require('path')

// 窗口记录数组
interface IGroup {
  [index: number]: {
    windowId: number
    route: string
  }
}

export default class CreateWindow {
  // 路由与主窗口标识
  private public static group: IGroup = []
  // 记录主窗口
  private static main: BrowserWindow | null | undefined = null
  // 窗口配置项
  private defaultConfig: IWindowConfig
  // 窗口默认配置
  private defaultOptions: IWindowOption

  constructor() {
    this.defaultConfig = {
      id: null, //唯一 id
      title: '', //窗口标题
      width: null, //宽度
      height: null, //高度
      minWidth: null, //最小宽度
      minHeight: null, //最小高度
      route: '', // 页面路由 URL '/manage?id=123'
      resizable: true, //是否支持调整窗口大小
      maximize: false, //是否最大化
      backgroundColor: '#eee', //窗口背景色
      data: null, //数据
      isMultiWindow: false, //是否支持多开窗口 (如果为 false,当窗体存在,再次创建不会新建一个窗体 只 focus 显示即可,,如果为 true,即使窗体存在,也可以新建一个)
      isMainWin: false, //是否主窗口创建父子窗口 --(当为 true 时会替代当前主窗口)
      parentId: null, //父窗口 id   子窗口永远显示在父窗口顶部 【父窗口可以操作】
      modal: true //模态窗口 -- 模态窗口是禁用父窗口的子窗口,创建模态窗口必须设置 parent 和 modal 选项 【父窗口不能操作】
    }
    this.defaultOptions = {
      width: 900,
      height: 700,
      //窗口是否在屏幕居中. 默认值为 false
      center: true,
      //设置为 false 时可以创建一个无边框窗口 默认值为 true。
      frame: false,
      //窗口是否在创建时显示。 默认值为 true。
      show: true,
      transparent: true,
      maxWidth: null,
      maxHeight: null,
      minWidth: 688,
      minHeight: 560,
      backgroundColor: 'rgba(0,0,0,0)',
      autoHideMenuBar: true,
      resizable: true,
      minimizable: true,
      maximizable: true,
      /* 
        【父窗口不能操作】
         模态窗口 -- 模态窗口是禁用父窗口的子窗口,创
         建模态窗口必须设置 parent 和 modal 选项
      */
      modal: true,
      parent: null,
      webPreferences: {
        // nodeIntegration: true,
        contextIsolation: true,
        // nodeIntegrationInWorker: true,
        webSecurity: false,
        // sandbox: false,
        nodeIntegration: true,
        preload: path.join(__dirname, '../preload/preload.js')
      }
    }
  }

  // 根据 id 的窗口
  public getWindowById = (id: number): any => {
    return BrowserWindow.fromId(id)
  }

  // 创建窗口
  public createWindow(configurations: object, options: object) {
    // console.info(CreateWindow.group)
    // 判断是否有页面
    let windowId: number = 0
    if (
      CreateWindow.group.some((o: object, i: number) => {
        windowId = i
        return o.route === configurations.route
      })
    ) {
      console.info('window is already created')
      this.getWindowById(windowId + 1)?.blur()
      return
    }
    // 传递的配置与默认配置创建新的对象
    let windowConfig = Object.assign({}, this.defaultConfig, configurations)
    // 传递的选项与默认选项创建新的对象
    let windowOptions = Object.assign({}, this.defaultOptions, options)
    // 设定其他窗口的父窗口
    if (!windowConfig.isMainWin && CreateWindow.main) {
      windowOptions.parent = CreateWindow.main
    }
    // 创建窗口
    let win = new BrowserWindow(windowOptions)
    console.log('window id:' + win.id)
    // 记录路由与窗口 id
    CreateWindow.group[win.id - 1] = {
      windowId: win.id,
      route: windowConfig.route
    }

    // 是否最大化
    if (windowConfig.maximize && windowConfig.resizable) {
      win.maximize()
    }
    // 是否主窗口
    if (windowConfig.isMainWin) {
      if (CreateWindow.main) {
        console.info('main window already created')
        delete CreateWindow.group[0]
        CreateWindow.main.close()
      }
      // 记录主窗口
      CreateWindow.main = win
    }
    // 窗口被清除之后,清除存储
    win.on('close', () => {
      CreateWindow.group.forEach((o, i) => {
        if(this.getWindowById(o.windowId) == win)
          delete CreateWindow.group[i]
        if(win == this.main)
          app.quit()
      });
      win.setOpacity(0)
    })

    // 加载页面
    let winURL: string
    if (app.isPackaged) {
      // winURL = windowConfig.route ? `app://../../dist/index.html` : `file://${path.join(__dirname, '../../dist/index.html')}`
      win.loadFile(join(__dirname, '../../dist/index.html'), { hash: windowConfig.route })
    } else {
      winURL = windowConfig.route ? `http://localhost:${process.env['VITE_DEV_SERVER_PORT']}/#${windowConfig.route}` : `http://localhost:${process.env['VITE_DEV_SERVER_PORT']}/#`
      win.loadURL(winURL)
    }
    console.info('new window address -> ', winURL)
    win.setMenu(null)
    // 设置路由
    win.webContents.openDevTools()
    win.on('hide', () => win.webContents.closeDevTools())
    // 全局快捷键注册
    globalShortcut.register('CommandOrControl+Shift+i', function () {
      win.webContents.openDevTools()
    })
    win.once('ready-to-show', () => {
      win.show()
    })
  }
}

preload 添加方法

electron/main/preload.ts

// 打开新窗口
const createNewWindow = (optionObj: object, configObj: object) => {
  ipcRenderer.send('window-create', optionObj, configObj)
}

contextBridge.exposeInMainWorld('myApi', {
  createNewWindow
})

修改 main 入口文件

electron/main/main.ts

const { app, protocol, BrowserWindow, ipcMain } = require('electron')
// 需在当前文件内开头引入 Node.js 的 'path' 模块
const path = require('path')

import { windowControlListener } from '../controller/windowControl'
import CreateWindow from '../controller/createWindow'

// 窗口监听
windowControlListener()

// 创建其他窗口
ipcMain.on('window-create', (event, optionObj: object, configObj: object) => {
  let cw = new CreateWindow()
  cw.createWindow(optionObj, configObj)
})

// 创建主窗口
const createMainWindow = async () => {
  let mainW = new CreateWindow()
  mainW.createWindow({
    route: '/home',
    isMainWin: true,
  }, {
    width: 900,
    height: 700
  })
}

app.commandLine.appendSwitch('--ignore-certificate-errors', 'true')
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([{ scheme: 'app', privileges: { secure: true, standard: true } }])

app.whenReady().then(() => {
  // 创建窗口
  createMainWindow()
  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) createMainWindow()
  })
})

// 关闭所有窗口
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit()
})

渲染层调用

const win = window as any

const createWindow = () => {
  win.myApi.createNewWindow(
    {
      route: '/child'
    },
    {
      width: 500,
      height: 500
    }
  )
}