1、概述
開發平臺OS:windows
開發平臺IDE:vs code
本篇章將介紹自定義標題列和右鍵選單項,基於electron現有版本安全性的建議,此次的改造中主程序和渲染程序彼此語境隔離,通過預載入(preload.js)和程序間通訊(ipc)的方式來完成。
2、視窗最大化
一些應用在實際情況中,希望啟動的時候就以視窗最大化的方式呈現,BrowserWindow物件提供了視窗最大化的方法:win.maximize(),具體如下所示:
const win = new BrowserWindow({ //表單寬度(畫素),預設800畫素 width: 800, //表單高度(畫素),預設600畫素 height: 600, //視窗標題,如果在載入的 HTML 檔案中定義了 HTML 標籤 `<title>`,則該屬性將被忽略。 title: `${process.env.VUE_APP_NAME}(${process.env.VUE_APP_VERSION})`, webPreferences: { // 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, }, }); //表單最大化 win.maximize();
通過設定後,啟動應用就會發現,最大化的過程中會出現黑底閃屏,這樣會給使用者造成困擾。
造成這個現象的原因是範例化表單的時候,預設顯示了視窗,然後再最大化,從預設視窗大小到最大化視窗大小的這個過程中表單還沒繪製好,就會出現黑色背景直至最大化完成後,現在稍加改造就可以解決這個問題:範例化的時候不顯示錶單,最大化後再顯示錶單。
const win = new BrowserWindow({ //表單寬度(畫素),預設800畫素 width: 800, //表單高度(畫素),預設600畫素 height: 600, //視窗標題,如果在載入的 HTML 檔案中定義了 HTML 標籤 `<title>`,則該屬性將被忽略。 title: `${process.env.VUE_APP_NAME}(${process.env.VUE_APP_VERSION})`, //不顯示錶單 show: false, webPreferences: { // 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, }, }); //表單最大化 win.maximize(); //顯示錶單 win.show();
3、自定義標題列
為什麼要自定義標題列?electron應用自帶的標題列不能滿足日益複雜的功能需求時,就只能自定義了。自定義標題除了實現基本的視窗功能外,它還能方便的快速的擴充套件其他功能需求。
自定義標題列使用的是css3-flex+scss 來實現佈局和樣式的編寫,其主體劃分為兩個區域:標題列區域和功能區域,如下圖所示:
為了使用scss語言來編寫樣式,我們需要安裝 sass-loader 外掛,在終端輸入命令:npm install sass-loader@^10 sass --save-dev 指定版本尤為重要,高版本對於webpack版本也有要求
3.1、iconfront 圖示新增
功能區域處的功能按鈕需要圖示,此塊是在 iconfront 官網上找了合適的圖示加入購物車後以下載程式碼的方式下載資源,然後通過下載的demo中第二種方式整合在專案中。
3.2、編寫標題列頁面
在src/renderer/App.vue 修改其內容以完成標題列的改造,主要是通過css3-flex來完成的佈局,包含了標題列原有的基本功能,改造後效果(gif有失真效果)以及改造的程式碼如下所示:
<template> <div id="app"> <header> <div class="titleArea"> <img :src="winIcon" /> <span>{{ winTitle }}</span> </div> <div class="featureArea"> <div title="擴充套件"> <span class="iconfont icon-xiakuozhanjiantou"></span> </div> <div title="最小化"> <span class="iconfont icon-minimum"></span> </div> <div :title="maximizeTitle"> <span :class="{ iconfont: true, 'icon-zuidahua': isMaximized, 'icon-window-max_line': !isMaximized, }" ></span> </div> <div title="關閉"> <span class="iconfont icon-guanbi"></span> </div> </div> </header> <main>我是主體</main> </div> </template> <script> export default { data: () => ({ winIcon: `${process.env.BASE_URL}favicon.ico`, winTitle: process.env.VUE_APP_NAME, isMaximized: true, }), computed: { maximizeTitle() { return this.isMaximized ? "向下還原" : "最大化"; }, }, }; </script> <style lang="scss"> $titleHeight: 40px; body { margin: 0px; } #app { font-family: "微軟雅黑"; color: #2c3e50; display: flex; flex-direction: column; header { background: #16407b; color: #8c8663; height: $titleHeight; width: 100%; display: flex; .titleArea { flex-grow: 10; padding-left: 5px; display: flex; align-items: center; img { width: 24px; height: 24px; } span { padding-left: 5px; } } .featureArea { flex-grow: 1; display: flex; justify-content: flex-end; div { width: 30px; height: 30px; line-height: 30px; text-align: center; } /* 最小化 最大化懸浮效果 */ div:hover { background: #6fa8ff; } /* 關閉懸浮效果 */ div:last-child:hover { background: red; } } } // 主體區域鋪滿剩餘的整個寬、高度 main { background: #e8eaed; width: 100%; height: calc(100vh - $titleHeight); } } </style>
3.3、標題列頁面新增互動
從electron機制上來說,BrowserWindow是屬於主程序模組,要想實現在頁面中(渲染程序)呼叫主程序視窗的功能,這涉及到渲染程序與主程序的通訊和安全性,在這通過預載入(preload.js)和 ipc 來實現該需求。
import { contextBridge, ipcRenderer } from "electron"; //表單操作api contextBridge.exposeInMainWorld("windowApi", { //最小化 minimize: () => { ipcRenderer.send("window-min"); }, //向下還原|最大化 maximize: () => { ipcRenderer.send("window-max"); }, //關閉 close: () => { ipcRenderer.send("window-close"); }, /** * 視窗重置大小 * @param {重置大小後的回撥函數} callback */ resize: (callback) => { ipcRenderer.on("window-resize", callback); }, });
"use strict"; import { app, protocol, BrowserWindow, ipcMain } from "electron"; import { createProtocol } from "vue-cli-plugin-electron-builder/lib"; import path from "path"; // 取消安裝devtools後,則不需要用到此物件,可以註釋掉 // import installExtension, { VUEJS_DEVTOOLS } from "electron-devtools-installer"; 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 } }, ]); //建立應用主視窗 const createWindow = async () => { const win = new BrowserWindow({ //表單寬度(畫素),預設800畫素 width: 800, //表單高度(畫素),預設600畫素 height: 600, //視窗標題,如果在載入的 HTML 檔案中定義了 HTML 標籤 `<title>`,則該屬性將被忽略。 title: `${process.env.VUE_APP_NAME}(${process.env.VUE_APP_VERSION})`, //不顯示錶單 show: false, webPreferences: { // Use pluginOptions.nodeIntegration, leave this alone // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info // 是否開啟node整合,預設false nodeIntegration: false, // 否在獨立 JavaScript 環境中執行 Electron API和指定的preload 指令碼. 預設為 true contextIsolation: true, //在頁面執行其他指令碼之前預先載入指定的指令碼 preload: path.join(__dirname, "preload.js"), }, //fasle:無框表單(沒有標題列、選單欄) frame: false, }); //表單最大化 win.maximize(); //顯示錶單 win.show(); 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); if (!process.env.IS_TEST) win.webContents.openDevTools(); } else { createProtocol("app"); // Load the index.html when not in development await win.loadURL("app://./index.html"); } //監聽視窗重置大小後事件,若觸發則給渲染程序傳送訊息 win.on("resize", () => { win.webContents.send("window-resize", win.isMaximized()); }); }; // 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(); }); // 只有在 app 模組的 ready 事件能觸發後才能建立 BrowserWindows 範例。 您可以藉助 app.whenReady() API 來等待此事件 // 通常我們使用觸發器的 .on 函數來監聽 Node.js 事件。 // 但是 Electron 暴露了 app.whenReady() 方法,作為其 ready 事件的專用監聽器,這樣可以避免直接監聽 .on 事件帶來的一些問題。 參見 https://github.com/electron/electron/pull/21972。 app.whenReady().then(() => { createWindow(); //視窗最小化 ipcMain.on("window-min", function (event) { const win = BrowserWindow.fromId(event.sender.id); win.minimize(); }); //視窗向下還原|最大化 ipcMain.on("window-max", function (event) { const win = BrowserWindow.fromId(event.sender.id); const isMaximized = win.isMaximized(); if (isMaximized) { win.unmaximize(); } else { win.maximize(); } }); //視窗關閉 ipcMain.on("window-close", function (event) { const win = BrowserWindow.fromId(event.sender.id); win.destroy(); }); }); // 註釋了此種方式改用官方推薦的專用方法來實現事件的監聽 // 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(); }); } }
解決辦法:根目錄下vue.config.js 檔案 pluginOptions.electronBuilder 節點新增內容 preload: "src/main/preload.js",具體內容如下所示:
pluginOptions: { electronBuilder: { mainProcessFile: "src/main/index.js", // 主程序入口檔案 mainProcessWatch: ["src/main"], // 檢測主程序檔案在更改時將重新編譯主程序並重新啟動 preload: "src/main/preload.js", // 預載入js }, },
<template> <div id="app"> <header> <div class="titleArea"> <img :src="winIcon" /> <span>{{ winTitle }}</span> </div> <div class="featureArea"> <div title="擴充套件" @click="expand"> <span class="iconfont icon-xiakuozhanjiantou"></span> </div> <div title="最小化" @click="minimize"> <span class="iconfont icon-minimum"></span> </div> <div :title="maximizeTitle" @click="maximize"> <span :class="{ iconfont: true, 'icon-zuidahua': isMaximized, 'icon-window-max_line': !isMaximized, }" ></span> </div> <div title="關閉" @click="close"> <span class="iconfont icon-guanbi"></span> </div> </div> </header> <main>我是主體</main> </div> </template> <script> export default { data: () => ({ winIcon: `${process.env.BASE_URL}favicon.ico`, winTitle: process.env.VUE_APP_NAME, isMaximized: true, }), mounted() { window.windowApi.resize(this.resize); }, computed: { maximizeTitle() { return this.isMaximized ? "向下還原" : "最大化"; }, }, methods: { //擴充套件 expand() { this.$message({ type: "success", message: "我點選了擴充套件", }); }, //最小化 minimize() { window.windowApi.minimize(); }, //向下還原|最大化 maximize() { window.windowApi.maximize(); }, // 視窗關閉 close() { window.windowApi.close(); }, /** * 重置表單大小後的回撥函數 * @param {事件源物件} event * @param {引數} args */ resize(event, args) { this.isMaximized = args; }, }, }; </script> <style lang="scss"> $titleHeight: 40px; $iconSize: 35px; body { margin: 0px; } #app { font-family: "微軟雅黑"; color: #2c3e50; display: flex; flex-direction: column; header { background: #16407b; color: #8c8663; height: $titleHeight; width: 100%; display: flex; .titleArea { flex-grow: 10; padding-left: 5px; display: flex; align-items: center; img { width: 24px; height: 24px; } span { padding-left: 5px; } } .featureArea { flex-grow: 1; display: flex; justify-content: flex-end; color: white; div { width: $iconSize; height: $iconSize; line-height: $iconSize; text-align: center; } /* 最小化 最大化懸浮效果 */ div:hover { background: #6fa8ff; } /* 關閉懸浮效果 */ div:last-child:hover { background: red; } } } // 主體區域鋪滿剩餘的整個寬、高度 main { background: #e8eaed; width: 100%; height: calc(100vh - $titleHeight); } } </style>
標題列最終的互動效果,如下圖所示:
4、自定義右鍵選單項
當前在開發模式下啟動應用後也會自啟動偵錯工具(devtools)便於技術人員分析並定位問題,如果關閉偵錯工具後就沒有渠道再次啟用偵錯工具了。還有場景就是在非開發模式下預設是不啟用偵錯工具的,應用出現問題後也需要啟用偵錯工具來分析定位問題。這個時候呢,參考瀏覽器滑鼠右鍵功能,給應用新增右鍵選單項功能包含有:重新載入、偵錯工具等。右鍵選單項在主程序中 src/main/index.js 管理,通過給 BrowserWindow 物件 webContents 屬性繫結滑鼠右鍵處理監聽處理,具體內容如下所示:
"use strict"; import { app, protocol, BrowserWindow, ipcMain, Menu } from "electron"; import { createProtocol } from "vue-cli-plugin-electron-builder/lib"; import path from "path"; // 取消安裝devtools後,則不需要用到此物件,可以註釋掉 // import installExtension, { VUEJS_DEVTOOLS } from "electron-devtools-installer"; 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 } }, ]); //建立應用主視窗 const createWindow = async () => { const win = new BrowserWindow({ //表單寬度(畫素),預設800畫素 width: 800, //表單高度(畫素),預設600畫素 height: 600, //視窗標題,如果在載入的 HTML 檔案中定義了 HTML 標籤 `<title>`,則該屬性將被忽略。 title: `${process.env.VUE_APP_NAME}(${process.env.VUE_APP_VERSION})`, //不顯示錶單 show: false, webPreferences: { // Use pluginOptions.nodeIntegration, leave this alone // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info // 是否開啟node整合,預設false nodeIntegration: false, // 否在獨立 JavaScript 環境中執行 Electron API和指定的preload 指令碼. 預設為 true contextIsolation: true, //在頁面執行其他指令碼之前預先載入指定的指令碼 preload: path.join(__dirname, "preload.js"), }, //fasle:無框表單(沒有標題列、選單欄) frame: false, }); //表單最大化 win.maximize(); //顯示錶單 win.show(); 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); if (!process.env.IS_TEST) win.webContents.openDevTools(); } else { createProtocol("app"); // Load the index.html when not in development await win.loadURL("app://./index.html"); } //監聽視窗重置大小後事件,若觸發則給渲染程序傳送訊息 win.on("resize", () => { win.webContents.send("window-resize", win.isMaximized()); }); //新增右鍵選單項 createContextMenu(win); }; //給指定表單建立右鍵選單項 const createContextMenu = (win) => { //自定義右鍵選單 const template = [ { label: "重新載入", accelerator: "ctrl+r", //快捷鍵 click: function () { win.reload(); }, }, { label: "偵錯工具", click: function () { const isDevToolsOpened = win.webContents.isDevToolsOpened(); if (isDevToolsOpened) { win.webContents.closeDevTools(); } else { win.webContents.openDevTools(); } }, }, ]; const contextMenu = Menu.buildFromTemplate(template); win.webContents.on("context-menu", () => { contextMenu.popup({ window: win }); }); }; // 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(); }); // 只有在 app 模組的 ready 事件能觸發後才能建立 BrowserWindows 範例。 您可以藉助 app.whenReady() API 來等待此事件 // 通常我們使用觸發器的 .on 函數來監聽 Node.js 事件。 // 但是 Electron 暴露了 app.whenReady() 方法,作為其 ready 事件的專用監聽器,這樣可以避免直接監聽 .on 事件帶來的一些問題。 參見 https://github.com/electron/electron/pull/21972。 app.whenReady().then(() => { createWindow(); //視窗最小化 ipcMain.on("window-min", function (event) { const win = BrowserWindow.fromId(event.sender.id); win.minimize(); }); //視窗向下還原|最大化 ipcMain.on("window-max", function (event) { const win = BrowserWindow.fromId(event.sender.id); const isMaximized = win.isMaximized(); if (isMaximized) { win.unmaximize(); } else { win.maximize(); } }); //視窗關閉 ipcMain.on("window-close", function (event) { const win = BrowserWindow.fromId(event.sender.id); win.destroy(); }); }); // 註釋了此種方式改用官方推薦的專用方法來實現事件的監聽 // 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(); }); } }
下一篇中將介紹專案打包等事宜
感謝您閱讀本文,如果本文給了您幫助或者啟發,還請三連支援一下,點贊、關注、收藏,作者會持續與大家分享更多幹貨~
原始碼地址:https://gitee.com/libaitianya/electron-vue-element-template