1. 程式人生 > 其它 >electron自定義快捷視窗按鈕,大小視窗切換

electron自定義快捷視窗按鈕,大小視窗切換

關注公眾號: 微信搜尋 前端工具人 ; 收貨更多的乾貨

原文連結: 自己掘金文章: https://juejin.cn/post/7067815153374330888/

一、需求

主要是一下幾個常見的需求:

  • 自定義頂部選單欄, 可拖拽;
  • 自定義最小化、最大化、退出按鈕、重新整理按鈕(類似瀏覽器的重新載入、用於開發階段除錯);
  • 小視窗 - 中視窗 - 全屏視窗, 相互切換;

二、electron 解讀

electron 區分了兩種程序:主程序和渲染程序

2.1 主程序:

  • 建立程序、視窗...
  • 控制應用生命週期(啟動、退出APP、事件監聽..)
  • 呼叫系統底層功能 (Electron API)、呼叫原生API
    Node.js 與本地互動...)

2.2 渲染程序

  • 主要是內建 Chromium 瀏覽, 來實現頁面的渲染;
  • 可以理解成 electron 渲染程序 為 Chromium 的視窗; 所以和日常開發區別不大

三、需求實現

專案入口檔案 render.js, 主程序事件檔案 ipc.event.js

render.js

'use strict'

import { app, protocol, BrowserWindow, Menu } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
// import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
import initIpcEvent from './services/ipc.event'
const path = require('path');

const isDevelopment = process.env.NODE_ENV !== 'production'

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

async function createWindow () {
  // Create the browser window.
  const win = new BrowserWindow({
    width: 400,
    height: 500,
    center: true,
    frame: false,
    useContentSize: true,
    // resizable: false,
    webPreferences: {
      webSecurity: false,
      enableRemoteModule: true,
      // Use pluginOptions.nodeIntegration, leave this alone
      // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
      nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
      contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION
    },
    icon: path.join(__dirname, '../public/favicon32.ico')
  })

  if (process.env.WEBPACK_DEV_SERVER_URL) {
    // Load the url of the dev server if in development mode
    await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
    // win.webContents.openDevTools()
    if (!process.env.IS_TEST) win.webContents.openDevTools()
  } else {
    createProtocol('app')
    // Load the index.html when not in development
    win.loadURL('app://./index.html')
  }
  win.setMenu(null)
  global.mainWindow = win
  // 初始化程序之間事件監聽
  initIpcEvent()
  // 隱藏選單
  createMenu()
}

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  // On macOS 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()
  }
})

app.on('activate', () => {
  // 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()
})

// 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.on('ready', async () => {
  if (isDevelopment && !process.env.IS_TEST) {
    // Install Vue Devtools
    try {
      // await installExtension(VUEJS_DEVTOOLS)
    } catch (e) {
      console.error('Vue Devtools failed to install:', e.toString())
    }
  }
  createWindow()
})

// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
  if (process.platform === 'win32') {
    process.on('message', (data) => {
      if (data === 'graceful-exit') {
        app.quit()
      }
    })
  } else {
    process.on('SIGTERM', () => {
      app.quit()
    })
  }
}

// 設定選單欄
function createMenu() {
  // darwin表示macOS,針對macOS的設定
  if (process.platform === 'darwin') {
    const template = [{
      label: 'Electron',
      submenu: [{
        role: 'about'
      }, {
        role: 'quit'
      }]
    }]
    const menu = Menu.buildFromTemplate(template)
    Menu.setApplicationMenu(menu)
  } else {
    // windows及linux系統
    Menu.setApplicationMenu(null)
  }
}

ipc.event.js

import { ipcMain, app, BrowserWindow } from 'electron'

export default function () {
  ipcMain.on('toggle-mini', (event, params) => {
    if (params.value) {
      global.mainWindow.hide()
    } else {
      global.mainWindow.show()
    }
  })

  ipcMain.on('window-min', () => {
    global.mainWindow.minimize()
    global.mainWindow.setResizable(true)
  })

  ipcMain.on('window-login', () => {
    global.mainWindow.setMinimumSize(400, 500)
    global.mainWindow.center()
    global.mainWindow.setResizable(false)
  })

  ipcMain.on('window-password', () => {
    global.mainWindow.setSize(1366, 768)
    global.mainWindow.center()
    global.mainWindow.setResizable(true)
  })

  ipcMain.on('window-max', () => {
    if (global.mainWindow.isMaximized()) {
      global.mainWindow.restore()
    } else {
      global.mainWindow.maximize()
    }
    // global.mainWindow.setMinimumSize(1600, 900)
    global.mainWindow.setMinimumSize(1200, 800)
    global.mainWindow.center()
  })

  ipcMain.on('window-hide', () => {
    global.mainWindow.hide()
  })

  ipcMain.on('window-show', () => {
    global.mainWindow.show()
  })

  ipcMain.on('window-refresh', () => {
    global.mainWindow.reload();
  })

  // 關閉當前視窗
  ipcMain.on('window-close', () => {
    console.log("window-close")
    global.mainWindow.close()
  })

  // 關閉所有視窗
  ipcMain.on('window-all-close', () => {
    console.log("window-all-close")
    const wins = BrowserWindow.getAllWindows()
    for (let i = 0; i < wins.length; i++) {
      wins[i].close()
    }
  })

  // 所有視窗都將立即被關閉,而不詢問使用者,而且 before-quit 和 will-quit 事件也不會被觸發。
  ipcMain.on('app-exit', () => {
    app.exit()
  })

  ipcMain.on('quit-and-open', (event, data) => {
    global.downloadFile = data
    app.quit()
  })
}

3.1 自定義頂部導航欄

首先要隱藏調自帶的頂部導航欄

// createMenu() 方法就是隱藏頂部導航欄
/ 拖拽,樣式  -webkit-app-region: drag;

3.2 自定義最小化、最大化、退出按鈕、重新整理按鈕;

<!-- 頂部導航欄 -->
<template>
  ...
  <header class="common-header">
    <i class="el-icon-refresh-right" title="重新整理" @click="onChangeWindow('refresh')"></i>
    <i class="el-icon-switch-button" title="退出登入" @click="onChangeWindow('logout')"></i>
    <i class="el-icon-minus" title="最小化" @click="onChangeWindow('min')"></i>
    <i v-show="isMax" class="el-icon-copy-document" title="還原" @click="onChangeWindow('scale')"></i>
    <i v-show="!isMax" class="max-window" title="最大化" @click="onChangeWindow('scale')">
      <span></span>
    </i>
    <i class="el-icon-close" title="關閉" @click="onChangeWindow('close')"></i>
  </header>
  ...
</template>
<script>
import { ipcRenderer, remote } from 'electron'
...
// 視窗切換
onChangeWindow (type) {
  switch (type) {
    case 'min':
      ipcRenderer.send('window-min')
      break;
    case 'scale':
      ipcRenderer.send('window-max')
      const winInfo = remote.getCurrentWindow()
      this.isMax = winInfo.isMaximized()
      break;
    case 'close':
      ipcRenderer.send('window-min')
      break;
    case 'logout':
      ipcRenderer.send('window-min')
      setTimeout(() => {
        this.$router.push('/')
        ipcRenderer.send('window-login')
        ipcRenderer.send('window-show')
      }, 150)
      break;
    case 'refresh':
      ipcRenderer.send('window-refresh')
      break;
  }
...
</script>

3.3 小視窗 - 中視窗 - 全屏視窗, 相互切換;

  • 初始化登入介面是小視窗類似於微信登入一樣 400 * 500
  • 登入後跳轉到程式主介面, 全屏
  • 忘記密碼介面(中視窗)1366 * 788

自己開發時,在全屏狀態下切換回小視窗切換不了,百度了蠻久,也沒結果;

後面發現改變全屏狀態後就能切換...

方法:先最小化視窗、在 setMinimumSize 改變尺寸, 在顯示, 相當於加了個過渡一下,變換過程也沒那麼死板

具體看3.2onChangeWindow 裡的 logout 方法

有不對之處歡迎指正, 程式碼有刪減,沒做測試;

只是作為分享,讓有需要的少走彎路,節省百度時間