使用 Electron 和 Vue 構建桌面應用程序
已發表: 2022-03-10JavaScript 曾經被稱為構建網站和 Web 應用程序的語言,尤其是它的一些框架,如 React、Vue 和 Angular,但隨著時間的推移(早在 2009 年),JavaScript 可以在瀏覽器之外運行Node.js 的出現,這是一個開源、跨平台的 JavaScript 運行時環境,可在 Web 瀏覽器之外執行 JavaScript 代碼。 這使得 JavaScript 不僅可以用於 Web 應用程序,還可以用於更多方面,其中之一就是使用 Electron.js 構建桌面應用程序。
Electron 通過提供具有豐富原生(操作系統)API 的運行時,使您能夠使用純 JavaScript 創建桌面應用程序。 您可以將其視為 Node.js 運行時的變體,專注於桌面應用程序而不是 Web 服務器。
在本教程中,我們將學習如何使用 Electron 構建桌面應用程序,我們還將學習如何使用 Vuejs 構建 Electron 應用程序。
注意:學習本教程需要 Vue.js 和 Vue CLI 的基本知識。 本教程中使用的所有代碼都可以在我的 GitHub 上找到。 隨意克隆和玩弄它!
什麼是桌面應用程序?
桌面應用程序是在台式機或膝上型計算機中獨立運行的應用程序。 它們是執行特定任務的應用程序,僅為此目的而安裝。
桌面應用程序的一個示例是您的 Microsoft Word,它用於創建和鍵入文檔。 常見桌面應用程序的其他示例包括 Web 瀏覽器、Visual Studio Code 和 Adobe Photoshop。 桌面應用程序與 Web 應用程序不同,因為您必須安裝桌面應用程序才能訪問和使用它,而且它們有時不需要互聯網訪問即可工作。 另一方面,可以通過簡單地訪問託管此類應用程序的 URL 來訪問 Web 應用程序,並且始終需要 Internet 訪問權限才能訪問它們。
用於構建桌面應用程序的框架示例包括:
- 爪哇
Java 是一種通用的編程語言,它是基於類的、面向對象的,並且設計為具有盡可能少的實現依賴關係。 它旨在讓應用程序開發人員編寫一次,隨處運行(WORA),這意味著編譯後的 Java 代碼可以在所有支持 Java 的平台上運行,而無需重新編譯。 - 爪哇外匯
根據他們的官方文檔,它是一個開源的下一代客戶端應用程序平台,適用於基於 Java 構建的桌面、移動和嵌入式系統。 - C#
C# 是一種通用的多範式編程語言,包括強類型、詞法範圍、命令式、聲明式、函數式、通用、面向對象和麵向組件的編程學科。 - 。網
.NET 是一個免費的、跨平台的、開源的開發者平台,用於構建許多不同類型的應用程序。 借助 .NET,您可以使用多種語言、編輯器和庫來構建 Web、移動、桌面、遊戲和 IoT。
什麼是電子?
Electron 是一個用於構建桌面應用程序的開源框架。 它以前稱為“Atom shell”,由 GitHub 開發和維護。 它允許您使用 HTML、CSS 和 JavaScript 編寫跨平台桌面應用程序。 這意味著您可以使用一個代碼庫為 Windows、MacOS 和其他平台構建桌面應用程序。 它基於 Node.js 和 Chromium。 使用 Electron 構建的應用程序示例包括流行的 Atom 編輯器、Visual Studio Code、桌面版 Wordpress 和 Slack。
安裝
你可以使用 NPM 在你的項目中安裝 Electron:
npm install electron --save-dev
如果您要使用以下命令大量使用電子應用程序,您也可以全局安裝它:
npm install electron -g
使用 Electron 為桌面構建 Vuejs 應用程序
如果您熟悉使用 Vuejs 構建 Web 應用程序,則可以使用 Vuejs 構建桌面應用程序。 為此,您只需要 Vue CLI Plugin Electron Builder。
Vue CLI 插件 Electron Builder
該工具允許您使用 Electron 為桌面構建 Vue 應用程序,這意味著它可以使您的 Vue 應用程序作為電子應用程序工作。 這意味著您的 Vue 應用程序(可能是 Web 應用程序)可以擴展為在桌面環境中工作,而無需在另一個框架中構建單獨的桌面應用程序。 這為 Vue 開發人員提供了超越 Web 的選項和能力。 展望未來,您可以實現您的想法,並為用戶提供桌面應用程序選項 - 可以在 Windows、macOS 和 Linux 上運行的選項。
為了看到這一點,我們將使用 News API 構建一個 News 應用程序。 該應用程序將提供突發新聞標題,並允許您使用其 API 從網絡上的新聞來源和博客中搜索文章。 開始使用它們所需的只是您的個人 API 密鑰,可以從這裡獲取。
我們將構建一個簡單的應用程序,它提供以下功能:
- 顯示來自所選國家/地區的熱門和重要標題的頁面,可以選擇使用其
/top-headlines
端點選擇國家/地區。 新聞 API 提供來自他們支持的國家/地區列表的新聞,請在此處找到列表。 - 來自選定類別的新聞,使用它們的
/everything
端點和查詢參數q
的組合,我們將使用它指定我們的類別。
獲得您的 API 密鑰後,我們可以使用 Vue CLI 創建我們的應用程序。 確保您的系統上安裝了 Vue CLI,如果沒有,請使用以下命令安裝它:
npm install -g @vue/cli # OR yarn global add @vue/cli
完成後,使用 CLI 創建您的新聞應用程序:
vue create news-app
在本教程中,我們將使用 Axios 從 News API 獲取數據,但您可以使用任何您更熟悉的替代方案。 您可以使用以下任何命令安裝 Axios:
//NPM npm install axios // YARN yarn add axios
下一步是在我們的應用程序中為全局配置設置一個 Axios 實例。 我們將在src文件夾中創建一個plugins文件夾,我們將在其中創建這個axios.js文件。 創建文件後,添加以下代碼行:
import axios from "axios"; let baseURL = `https://newsapi.org/v2`; let apiKey = process.env.VUE_APP_APIKEY; const instance = axios.create({ baseURL: baseURL, timeout: 30000, headers: { "X-Api-Key": apiKey, }, }); export default instance;
在這裡,我們定義了從 News API 獲得的baseURL
和apiKey
,並將其傳遞給 Axios 的新實例。 此實例接受baseURL
和apiKey
以及timeout
屬性。 News API 要求您在向他們的 API 發出請求時添加您的 API 密鑰,並提供 3 種將其附加到您的請求的方法,但在這裡,我們將其添加到標頭X-Api-Key
屬性中,然後導出instance
。 完成後,我們現在可以將此配置用於我們應用程序中的所有 Axios 請求。
完成後,您可以使用以下命令在 CLI 中添加 Plugin Electron 構建器:
vue add electron-builder
系統將提示您選擇首選的 Electron 版本,我選擇了9.0.0
版本,因為它是 Electron 的最新版本(在撰寫本文時)。
完成後,您現在可以使用以下命令為您的應用程序提供服務:
Using Yarn(strongly recommended) yarn electron:serve OR NPM npm run electron:serve
這將需要一些時間來編譯和提供您的應用程序。 完成後,您的應用程序將在您的系統上彈出,如下所示:
如果您關閉應用程序的開發工具,它應該如下所示:
這個電子插件非常有用且易於使用,因為該應用程序開發的每個部分都與 Vue 應用程序的工作方式相同。 這意味著您的 Web 應用程序和桌面應用程序都可以擁有一個代碼庫。 我們的應用程序將包含三個部分:
- 一個著陸頁,呈現隨機選擇的國家/地區的頭條新聞。
- 用於呈現來自用戶選擇的國家/地區的熱門新聞的頁面。
- 呈現來自用戶選擇的類別的熱門新聞的頁面。
為此,我們將需要一個用於所有導航鏈接的標題組件。 因此,讓我們在components文件夾中創建一個文件並將其命名為header.vue ,然後在其中添加以下代碼行:
<template> <header class="header"> <div class="logo"> <div class="logo__container"> <img src="../assets/logo.png" alt="News app logo" class="logo__image" /> </div> <h1>News App</h1> </div> <nav class="nav"> <h4 class="nav__link"> <router-link to="/home">Home</router-link> </h4> <h4 class="nav__link"> <router-link to="/top-news">Top News</router-link> </h4> <h4 class="nav__link"> <router-link to="/categories">News By Category</router-link> </h4> </nav> </header> </template> <script> export default { name: "app-header", }; </script> <style> .header { display: flex; flex-wrap: wrap; justify-content: space-between; } .logo { display: flex; flex-wrap: nowrap; justify-content: space-between; align-items: center; height: 50px; } .logo__container { width: 50px; height: 50px; } .logo__image { max-width: 100%; max-height: 100%; } .nav { display: flex; flex-wrap: wrap; width: 350px; justify-content: space-between; } </style>
在這裡,我們創建了一個標頭組件,其中包含我們的應用程序名稱和徽標(圖片可以在我的 GitHub 上找到)以及一個包含指向我們應用程序其他部分的鏈接的導航部分。 接下來是在我們的佈局頁面上導入這個頁面——App.vue ,這樣我們就可以在每個頁面上看到我們的標題。
<template> <div> <app-header /> <router-view /> </div> </template> <script> import appHeader from "@/components/Header.vue"; export default { name: "layout", components: { appHeader, }, }; </script> <style> @import url("https://fonts.googleapis.com/css2?family=Abel&family=Staatliches&display=swap"); html, #app { min-height: 100vh; } #app { font-family: "Abel", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; background-color: #fff; } #app h1 { font-family: "Staatliches", cursive; } a { font-weight: bold; color: #2c3e50; text-decoration: none; } a:hover { text-decoration: underline; } a.router-link-exact-active { color: #42b983; } </style>
在這裡,我們將模板部分中的默認內容替換為我們在腳本部分中導入並聲明後新創建的標題組件。 最後,我們在樣式部分為整個應用添加一些樣式。
現在,如果我們嘗試查看我們的應用程序,它應該如下所示:
下一步是將內容添加到我們的Home.vue文件中。 該頁面將託管我們應用程序的第一部分; 來自隨機選擇的國家/地區的熱門新聞。 使用以下代碼行更新您的Home.vue文件:
<template> <section class="home"> <h1>Welcome to News App</h1> <h4>Displaying Top News from {{ countryInfo.name }}</h4> <div class="articles__div" v-if="articles"> <news-card v-for="(article, index) in articles" :key="index" :article="article" ></news-card> </div> </section> </template> <script> import { mapActions, mapState } from "vuex"; import NewsCard from "../components/NewsCard"; export default { data() { return { articles: "", countryInfo: "", }; }, components: { NewsCard, }, mounted() { this.fetchTopNews(); }, computed: { ...mapState(["countries"]), }, methods: { ...mapActions(["getTopNews"]), async fetchTopNews() { let countriesLength = this.countries.length; let countryIndex = Math.floor( Math.random() * (countriesLength - 1) + 1 ); this.countryInfo = this.countries[countryIndex]; let { data } = await this.getTopNews( this.countries[countryIndex].value ); this.articles = data.articles; }, }, }; </script> <style> .articles__div { display: flex; flex-wrap: wrap; justify-content: center; } </style>
在這個文件的腳本部分,我們從 Vuex 導入mapState
和mapActions
,我們稍後將在這個文件中使用它們。 我們還導入了一個NewsCard
組件(我們將在接下來創建它),它將在此頁面上呈現所有新聞標題。 然後,我們使用fetchTopNews
方法從我們商店的countries
地區數組中隨機選擇一個國家/地區獲取最新消息。 該國家/地區被傳遞給我們的getTopNews
操作,這將被附加到baseURL
作為對國家/地區的查詢,例如baseURL/top-news?country=${randomCountry}
。 完成後,我們循環遍歷這些數據並將其傳遞給模板部分中Newscard
組件的article
屬性。 我們還有一段表明頭條新聞來自哪個國家。
接下來是設置我們的NewsCard
組件來顯示這個新聞。 在您的組件文件夾中創建一個新文件,將其命名為NewsCard.vue ,並向其中添加以下代碼行:
<template> <section class="news"> <div class="news__section"> <h1 class="news__title"> <a class="article__link" :href="article.url" target="_blank"> {{ article.title }} </a> </h1> <h3 class="news__author" v-if="article.author">{{ article.author }}</h3> <!-- <p class="article__paragraph">{{ article.description }}</p> --> <h5 class="article__published">{{ new Date(article.publishedAt) }}</h5> </div> <div class="image__container"> <img class="news__img" src="../assets/logo.png" :data-src="article.urlToImage" :alt="article.title" /> </div> </section> </template> <script> export default { name: "news-card", props: { article: Object, }, mounted() { this.lazyLoadImages(); }, methods: { lazyLoadImages() { const images = document.querySelectorAll(".news__img"); const options = { // If the image gets within 50px in the Y axis, start the download. root: null, // Page as root rootMargin: "0px", threshold: 0.1, }; const fetchImage = (url) => { return new Promise((resolve, reject) => { const image = new Image(); image.src = url; image.onload = resolve; image.onerror = reject; }); }; const loadImage = (image) => { const src = image.dataset.src; fetchImage(src).then(() => { image.src = src; }); }; const handleIntersection = (entries) => { entries.forEach((entry) => { if (entry.intersectionRatio > 0) { loadImage(entry.target); } }); }; // The observer for the images on the page const observer = new IntersectionObserver(handleIntersection, options); images.forEach((img) => { observer.observe(img); }); }, }, }; </script> <style> .news { width: 100%; display: flex; flex-direction: row; align-items: flex-start; max-width: 550px; box-shadow: 2px 1px 7px 1px #eee; padding: 20px 5px; box-sizing: border-box; margin: 15px 5px; border-radius: 4px; } .news__section { width: 100%; max-width: 350px; margin-right: 5px; } .news__title { font-size: 15px; text-align: left; margin-top: 0; } .news__author { font-size: 14px; text-align: left; font-weight: normal; } .article__published { text-align: left; } .image__container { width: 100%; max-width: 180px; max-height: 180px; } .news__img { transition: max-width 300ms cubic-bezier(0.4, 0, 1, 1), max-height 300ms cubic-bezier(0.4, 0, 1, 1); max-width: 150px; max-height: 150px; } .news__img:hover { max-width: 180px; max-height: 180px; } .article__link { text-decoration: none; color: inherit; } </style>
在這裡,我們使用article
對象道具顯示傳遞到該組件的數據。 我們還有一種方法可以延遲加載附加到每篇文章的圖像。 此方法循環遍歷我們擁有的文章圖像的數量,並在它們變得可見時延遲加載它們。 最後,我們在樣式部分中有針對此組件的樣式。
下一件事將是建立我們的商店,以便我們可以開始獲取最新消息。 將以下代碼行添加到您的index.js文件中:
import Vue from "vue"; import Vuex from "vuex"; import axios from "../plugins/axios"; Vue.use(Vuex); const store = new Vuex.Store({ state: { countries: [{ name: "United States of America", value: "us", }, { name: "Nigeria", value: "ng", }, { name: "Argentina", value: "ar", }, { name: "Canada", value: "ca", }, { name: "South Africa", value: "za", }, ], categories: [ "entertainment", "general", "health", "science", "business", "sports", "technology", ], }, mutations: {}, actions: { async getTopNews(context, country) { let res = await axios({ url: `/top-headlines?country=${country}`, method: "GET", }); return res; }, }, }); export default store;
我們正在向我們的商店添加兩個屬性,其中一個屬性是countries
。 該屬性包含一個國家對像數組。 我們還有categories
屬性; 這包含 News API 上可用類別的數組。 讀者會喜歡自由查看來自特定國家和類別的熱門新聞; 這在應用程序的多個部分中也需要,這就是我們使用商店的原因。 在我們商店的操作部分,我們有一個getTopNews
方法,它從一個國家/地區獲取頭條新聞(該國家/地區是從調用此操作的組件傳遞的)。
此時,如果我們打開我們的應用程序,我們應該會看到我們的登錄頁面,如下所示:
background.js 文件
此文件是 Electron 進入您的應用程序的入口點。 它控制此應用程序的所有類似桌面應用程序的設置。 這個文件的默認狀態可以在我的 GitHub 上找到。
在這個文件中,我們為應用程序設置了一些預定義的配置,例如您的應用程序的默認height
和width
。 讓我們看一下您可以在此文件中執行的一些操作。
啟用 Vuejs 開發工具
默認情況下,您可以訪問 Electron 中的開發工具,但安裝後未啟用。 這是由於 Windows 10 上存在的錯誤造成的,因此如果您打開background.js文件,您會發現一些註釋掉的代碼,其中的註釋說明了它們被註釋掉的原因:
// Install Vue Devtools // Devtools extensions are broken in Electron 6.0.0 and greater // See https://github.com/nklayman/vue-cli-plugin-electron-builder/issues/378 for more info // Electron will not launch with Devtools extensions installed on Windows 10 with dark mode // If you are not using Windows 10 dark mode, you may uncomment these lines // In addition, if the linked issue is closed, you can upgrade electron and uncomment these lines // try { // await installVueDevtools() // } catch (e) { // console.error('Vue Devtools failed to install:', e.toString()) // }
因此,如果您不受此錯誤的影響,您可以取消註釋try/catch
塊並在同一文件(第 5 行)中搜索installVueDevtools
並取消註釋。 完成此操作後,您的應用程序將自動重新啟動,當您檢查開發工具時,您應該會看到 Vuejs Devtools。
為您的應用選擇自定義圖標
默認情況下,Electron 圖標被設置為您的應用程序的默認圖標,並且大多數時候,您可能希望設置自己的自定義圖標。 為此,請將您的圖標移動到您的公用文件夾中,並將其重命名為icon.png 。 接下來要做的是添加所需的依賴項electron-icon-builder
。
您可以使用以下任何命令安裝它:
// With Yarn: yarn add --dev electron-icon-builder // or with NPM: npm install --save-dev electron-icon-builder
完成後,您可以運行下一個命令。 它會將您的圖標轉換為 Electron 格式,並在完成後在您的控制台中打印以下內容。
接下來是在background.js文件中設置圖標選項。 此選項位於從Electron
導入的BrowserWindow
選項中。 為此, BrowserWindow
更新為如下所示:
// Add this to the top of your file /* global __static */ // import path import path from 'path' // Replace win = new BrowserWindow({ width: 800, height: 600 }) // With win = new BrowserWindow({ width: 800, height: 600, icon: path.join(__static, 'icon.png') })
現在,如果我們運行yarn run electron:build
並查看我們的應用程序,我們應該會看到更新後的圖標被用作應用程序圖標,但它在開發過程中沒有改變。 此問題有助於解決在 macOS 上對其進行手動修復的問題。
為您的應用設置標題
您會注意到您的應用程序的標題設置為應用程序名稱(在本例中為新聞應用程序),我們需要對其進行更改。 為此,我們必須在background.js
文件中的BrowserWindow
方法中添加一個title
屬性,如下所示:
win = new BrowserWindow({ width: 600, height: 500, title: "News App", icon: path.join(__static, "icon.png"), 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, }, });
在這裡,我們將應用程序的標題設置為“新聞應用程序”。 但是,如果您的index.html文件選擇了標題或者您的標題沒有更改為此,請嘗試將此代碼添加到您的文件中:
win.on("page-title-updated", (event) => event.preventDefault());
當我們的title
從BrowserWindow
更新時,我們正在監聽一個被觸發的事件。 當這個事件被觸發時,我們告訴 Electron 不要用index.html文件中的標題更新標題。
可能值得更改的另一件事是productName
,它控制當您將鼠標懸停在應用程序圖標上時出現的名稱或您的計算機將應用程序識別為什麼名稱。 現在,我們的應用程序的名稱是Electron
。 要在生產環境中更改此名稱,請創建一個vue.config.js文件並向其中添加以下代碼行:
module.exports = { pluginOptions: { electronBuilder: { builderOptions: { productName: "News App", }, }, }, };
在這裡,我們將productName
定義為“News App”,這樣當我們為我們的應用程序運行構建命令時,名稱會從“Electron”更改為“News App”。
多平台構建
默認情況下,當您運行構建命令時,創建的應用程序取決於運行它的平台。 這意味著如果您在 Linux 上運行 build 命令,則創建的應用程序將是 Linux 桌面應用程序。 這同樣適用於其他平台(macOS 和 windows)。 但是 Electron 提供了一個選項來指定你想要生成的平台(或兩個平台)。 可用的選項有:
-
mac
-
win
-
linux
因此,為了構建應用程序的 Windows 版本,請運行以下命令:
// NPM npm electron:build -- --win nsis // YARN yarn electron:build --win nsis
結論
完成的應用程序可以在我的 GitHub 上找到。 官方 Electron 文檔提供了信息和指南,可幫助您以任何方式自定義桌面應用程序。 我嘗試過但未包含在本教程中的一些內容是:
- 在 macOS 上自定義您的擴展塢 — https://www.electronjs.org/docs/tutorial/macos-dock。
- 設置可調整大小、可最大化等等 - https://github.com/electron/electron/blob/master/docs/api/browser-window.md#new-browserwindowoptions。
因此,如果您希望使用 Electron 應用程序做更多事情,他們的官方文檔是一個很好的起點。
相關資源
- Node.js https://en.wikipedia.org/wiki/Node.js
- Java(編程語言)https://en.wikipedia.org/wiki/Java_(programming_language)
- 電子(軟件框架)
- JavaFX 14
- 電子
- 電子文檔
- Vue CLI 插件 Electron Builder
- 使用 Chris Nwamba 的 Intersection Observer 延遲加載圖像以提高性能
- axios
- 在 Nuxt 中開始使用 Axios https://www.smashingmagazine.com/2020/05/getting-started-axios-nuxt/) by Timi Omoyeni