基於vue3.0+electron新開窗口|Electron多開窗體|父子模態窗口

最近一直在折騰Vue3+Electron技術結合的實踐,今天就來分享一些vue3.x和electron實現開啓多窗口功能。html

 

 

開始本文以前,先來介紹下如何使用vue3和electron來快速搭建項目。vue

目前electron.js的star高達89.3K+,最新穩定版v11.2.3node

使用vue開發electron應用,網上有個比較火的腳手架electron-vue,不過裏面的版本過低,並且使用的是vue2.x語法。git

 

 

今天主要分享的是vue3語法開發electron應用,因此只能手動搭建開發環境。github

  • 安裝最新Vue CLI腳手架。

 npm install -g @vue/cli web

  • 新建vue3項目

具體的選項配置,你們根據須要勾選。vue-cli

 vue create vue3_electron npm

  • vue3項目中集成electron

安裝vue-cli-plugin-electron-builder插件。windows

 cd vue3_electron api

 vue add electron-builder 

以後的安裝過程當中會提示安裝electron版本,選擇最新版安裝就行。

  • 開發調試/打包構建

 npm run electron:serve 

 npm run electron:build 

很是簡單,沒有幾步就能快速手動搭建vue3+electron項目,下面就能愉快的開發了。

通常項目中須要新建多開窗口的功能,使用Electron來建立也是很是簡單的。通常的實現方法是  new BrowserWindow({...})  窗口,傳入參數配置便可快速新建一個窗口。

<body>
    <button id="aaa">打開A窗口</button>
    <button id="bbb">打開B窗口</button>
    <button id="ccc">打開C窗口</button>
</body>

<script>
    import path from 'path'
    import { remote } from 'electron'
    
    let BrowserWindow = remote.BrowserWindow;
    
    let winA = null;
    let winB = null;
    let winC = null;
    
    document.getElementById("aaa").onclick = function(){
        winA = new BrowserWindow ({width: 1000, height:800})
        winA.loadURL("https://aaa.com/");
        winA.on("close", function(){
            winA = null;
        })
    }
    
    document.getElementById("bbb").onclick = function(){
        winB = new BrowserWindow ({width: 900, height:650})
        winB.loadURL("https://bbb.com/");
        winB.on("close", function(){
            winB = null;
        })
    }
    
    document.getElementById("ccc").onclick = function(){
        winC = new BrowserWindow ({width: 500, height:500})
        winC.loadURL(`file://${__dirname}/news.html`);
        winC.on("close", function(){
            winC = null;
        })
    }
</script>

 

 

具體的參數配置,你們能夠去查閱文檔,electron官網中都有很是詳細的說明。

https://www.electronjs.org/docs/api/browser-window

可是這種方法每次都要新建一個BrowserWindow,有些挺麻煩的,能不能像下面代碼片斷這樣,直接經過一個函數,而後傳入配置參數生成新窗口,顯然是能夠的。

windowCreate({
      title: '管理頁面',
      route: '/manage?id=123',
      width: 750,
      height: 500,
      backgroundColor: '#f9f9f9',
      resizable: false,
      maximize: true,
      modal: true,
})

background.js配置

'use strict'

import { app, BrowserWindow } from 'electron'
const isDevelopment = process.env.NODE_ENV !== 'production'

import { Window } from './windows'

async function createWindow() {
  let window = new Window()
  
  window.listen()
  window.createWindows({isMainWin: true})
  window.createTray()
}

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) createWindow()
})

app.on('ready', async () => {
  createWindow()
})

if (isDevelopment) {
  if (process.platform === 'win32') {
    process.on('message', (data) => {
      if (data === 'graceful-exit') {
        app.quit()
      }
    })
  } else {
    process.on('SIGTERM', () => {
      app.quit()
    })
  }
}

新建一個 window.js 來處理主進程全部的函數。

import { app, BrowserWindow, ipcMain, Menu, Tray } from "electron";
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import path from 'path'

export const windowsCfg = {
    id: '', //惟一id
    title: '', //窗口標題
    width: '', //寬度
    height: '', //高度
    minWidth: '', //最小寬度
    minHeight: '', //最小高度
    route: '', // 頁面路由URL '/manage?id=123'
    resizable: true, //是否支持調整窗口大小
    maximize: false, //是否最大化
    backgroundColor:'#eee', //窗口背景色
    data: null, //數據
    isMultiWindow: false, //是否支持多開窗口 (若是爲false,當窗體存在,再次建立不會新建一個窗體 只focus顯示便可,,若是爲true,即便窗體存在,也能夠新建一個)
    isMainWin: false, //是否主窗口(當爲true時會替代當前主窗口)
    parentId: '', //父窗口id  建立父子窗口 -- 子窗口永遠顯示在父窗口頂部 【父窗口能夠操做】
    modal: false, //模態窗口 -- 模態窗口是禁用父窗口的子窗口,建立模態窗口必須設置 parent 和 modal 選項 【父窗口不能操做】
}

/**
 * 窗口配置
 */
export class Window {
    constructor() {
        this.main = null; //當前頁
        this.group = {}; //窗口組
        this.tray = null; //托盤
    }

    // 窗口配置
    winOpts(wh=[]) {
        return {
            width: wh[0],
            height: wh[1],
            backgroundColor: '#f00',
            autoHideMenuBar: true,
            titleBarStyle: "hidden",
            resizable: true,
            minimizable: true,
            maximizable: true,
            frame: false,
            show: false,
            webPreferences: {
                contextIsolation: false, //上下文隔離
                // nodeIntegration: true, //啓用Node集成(是否完整的支持 node)
                nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
                // devTools: false,
                webSecurity: false,
                enableRemoteModule: true, //是否啓用遠程模塊(即在渲染進程頁面使用remote)
            }
        }
    }

    // 獲取窗口
    getWindow(id) {
        return BrowserWindow.fromId(id)
    }

    // 獲取所有窗口
    getAllWindows() {
        return BrowserWindow.getAllWindows()
    }

    // 建立窗口
    createWindows(options) {
        console.log('------------開始建立窗口...')

        console.log(options)

        let args = Object.assign({}, windowsCfg, options)
        console.log(args)

        // 判斷窗口是否存在
        for(let i in this.group) {
            if(this.getWindow(Number(i)) && this.group[i].route === args.route && !this.group[i].isMultiWindow) {
                this.getWindow(Number(i)).focus()
                return
            }
        }

        let opt = this.winOpts([args.width || 800, args.height || 600])
        if(args.parentId) {
            console.log('parentId:' + args.parentId)
            opt.parent = this.getWindow(args.parentId)
        } else if(this.main) {
            console.log(666)
        }

        if(typeof args.modal === 'boolean') opt.modal = args.modal
        if(typeof args.resizable === 'boolean') opt.resizable = args.resizable
        if(args.backgroundColor) opt.backgroundColor = args.backgroundColor
        if(args.minWidth) opt.minWidth = args.minWidth
        if(args.minHeight) opt.minHeight = args.minHeight

        console.log(opt)
        let win = new BrowserWindow(opt)
        console.log('窗口id:' + win.id)
        this.group[win.id] = {
            route: args.route,
            isMultiWindow: args.isMultiWindow,
        }
        // 是否最大化
        if(args.maximize && args.resizable) {
            win.maximize()
        }
        // 是否主窗口
        if(args.isMainWin) {
            if(this.main) {
                console.log('主窗口存在')
                delete this.group[this.main.id]
                this.main.close()
            }
            this.main = win
        }
        args.id = win.id
        win.on('close', () => win.setOpacity(0))

        // 打開網址(加載頁面)
        /**
         * 開發環境: http://localhost:8080
         * 正式環境: app://./index.html
         */
        let winURL
        if (process.env.WEBPACK_DEV_SERVER_URL) {
            // Load the url of the dev server if in development mode
            // win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
            winURL = args.route ? `http://localhost:8080${args.route}` : `http://localhost:8080`
            // 打開開發者調試工具
            // if (!process.env.IS_TEST) win.webContents.openDevTools()
        } else {
            createProtocol('app')
            // Load the index.html when not in development
            // win.loadURL('app://./index.html')
            winURL = args.route ? `app://./index.html${args.route}` : `app://./index.html`
        }
        win.loadURL(winURL)

        win.once('ready-to-show', () => {
            win.show()
        })

        // 屏蔽窗口菜單(-webkit-app-region: drag)
        win.hookWindowMessage(278, function(e){
            win.setEnabled(false)
            setTimeout(() => {
                win.setEnabled(true)
            }, 100)

            return true
        })
    }

    // 關閉全部窗口
    closeAllWindow() {
        for(let i in this.group) {
            if(this.group[i]) {
                if(this.getWindow(Number(i))) {
                    this.getWindow(Number(i)).close()
                } else {
                    console.log('---------------------------')
                    app.quit()
                }
            }
        }
    }

    // 建立托盤
    createTray() {
        console.log('建立托盤')
        const contextMenu = Menu.buildFromTemplate([
            {
                label: '顯示',
                click: () => {
                    for(let i in this.group) {
                        if(this.group[i]) {
                            // this.getWindow(Number(i)).show()
                            let win = this.getWindow(Number(i))
                            if(!win) return
                            if(win.isMinimized()) win.restore()
                            win.show()
                        }
                    }
                }
            }, {
                label: '退出',
                click: () => {
                    app.quit()
                }
            }
        ])
        console.log(__dirname)
        const trayIco = path.join(__dirname, '../static/logo.png')
        console.log(trayIco)
        this.tray = new Tray(trayIco)
        this.tray.setContextMenu(contextMenu)
        this.tray.setToolTip(app.name)
    }


    // 開啓監聽
    listen() {
        // 關閉
        ipcMain.on('window-closed', (event, winId) => {
            if(winId) {
                this.getWindow(Number(winId)).close()
                if(this.group[Number(winId)]) delete this.group[Number(winId)]
            } else {
                this.closeAllWindow()
            }
        })

        // 隱藏
        ipcMain.on('window-hide', (event, winId) => {
            if(winId) {
                this.getWindow(Number(winId)).hide()
            } else {
                for(let i in this.group) if(this.group[i]) this.getWindow(Number(i)).hide()
            }
        })

        // 顯示
        ipcMain.on('window-show', (event, winId) => {
            if(winId) {
                this.getWindow(Number(winId)).show()
            } else {
                for(let i in this.group) if(this.group[i]) this.getWindow(Number(i)).show()
            }
        })

        // 最小化
        ipcMain.on('window-mini', (event, winId) => {
            if(winId) {
                this.getWindow(Number(winId)).minimize()
            } else {
                for(let i in this.group) if(this.group[i]) this.getWindow(Number(i)).minimize()
            }
        })

        // 最大化
        ipcMain.on('window-max', (event, winId) => {
            if(winId) {
                this.getWindow(Number(winId)).maximize()
            } else {
                for(let i in this.group) if(this.group[i]) this.getWindow(Number(i)).maximize()
            }
        })

        // 最大化/最小化
        ipcMain.on('window-max-min-size', (event, winId) => {
            if(winId) {
                if(this.getWindow(winId).isMaximized()) {
                    this.getWindow(winId).unmaximize()
                }else {
                    this.getWindow(winId).maximize()
                }
            }
        })

        // 還原
        ipcMain.on('window-restore', (event, winId) => {
            if(winId) {
                this.getWindow(Number(winId)).restore()
            } else {
                for(let i in this.group) if(this.group[i]) this.getWindow(Number(i)).restore()
            }
        })

        // 從新加載
        ipcMain.on('window-reload', (event, winId) => {
            if(winId) {
                this.getWindow(Number(winId)).reload()
            } else {
                for(let i in this.group) if(this.group[i]) this.getWindow(Number(i)).reload()
            }
        })

        // 建立窗口
        ipcMain.on('window-new', (event, args) => this.createWindows(args))
    }
}

而後新建一個 plugins.js 文件,用來封裝一些調用方法。

/**
 * 建立窗口
 */
export function windowCreate(args) {
    console.log(args)
    ipcRenderer.send('window-new', args)
}

/**
 * 關閉窗口
 */
export function windowClose(id) {
    console.log('窗口id:' + id)
    ipcRenderer.send('window-closed', id)
}

// ...

在.vue頁面引入plugins.js,執行建立窗口方法。

<template>
    <div class="app">
        <h1>This is an new page</h1>
        <img src="@assets/v3-logo.png" width="100" alt="">
        <p class="mt-10"><a-button type="primary" @click="handleNewWin1">新窗口1</a-button></p>
        <p class="mt-10"><a-button type="primary" @click="handleNewWin2">新窗口2</a-button></p>
    </div>
</template>

<script>
import {windowClose, windowCreate} from '@/plugins'

export default {
    setup() {
        const handleNewWin1 = () => {
            windowCreate({
                title: '管理頁面',
                route: '/manage',
                width: 1000,
                height: 750,
                backgroundColor: '#f9f9f9',
                resizable: true,
                modal: true,
                maximize: true,
            })
        }
        
        const handleNewWin2 = () => {
            windowCreate({
                title: '關於頁面',
                route: '/about?id=13',
                width: 750,
                height: 450,
                backgroundColor: '#f90',
                resizable: false,
            })
        }
        
        ...

        return {
            handleNewWin1,
            handleNewWin2,
            ...
        }
    }
}
</script>
  • 一些小踩坑記錄

一、建立托盤圖標,一開始圖標一直不顯示,覺得__dirname指向src目錄,最後調試發現原來是指向dist_electron

console.log(__dirname)
let trayIco = path.join(__dirname, '../static/logo.png')
console.log(trayIco)

let trayIco = path.join(__dirname, '../static/logo.png')
let tray = new Tray(trayIco)

這樣就能加載本地圖片,顯示托盤圖標了。

 

 

二、設置自定義拖拽 -webkit-app-region: drag 沒法響應點擊事件,沒法禁用右鍵菜單。

當窗體設置frame: false後,能夠經過-webkit-app-region: drag來實現自定義拖動,設置後的拖動區域沒法響應其它事件,能夠給須要點擊的連接或者按鈕設置-webkit-app-region: no-drag來實現。

 

 

不過還有一個問題,設置-webkit-app-region: drag後,會出現以下圖系統右鍵菜單。

 

最後通過一番調試,能夠經過下面這個方法簡單禁用掉,親測有效~~

win.hookWindowMessage(278, function(e){
    win.setEnabled(false)
    setTimeout(() => {
        win.setEnabled(true)
    }, 150)
    
    return true
})

ok,以上就是基於vue3.x+electron實現多開窗口的思路,但願對你們有些幫助!😃

最後附上一個vite2開發的vue3+vant3.x短視頻實例項目

http://www.javashuo.com/article/p-syjvfacu-ve.html

相關文章
相關標籤/搜索