👉 Electron 是一個可使用 Web 技術如 JavaScript、HTML 和 CSS 來建立跨平臺原生桌面應用的框架。藉助 Electron,咱們可使用純 JavaScript 來調用豐富的原生 APIs。 👈javascript
npx create-react-app react-electron
自動進行配置安裝react-electron
目錄下執行yarn start
,項目自動運行在 3000 端口public
文件夾不會被webpack
打包處理,會直接複製一份到dist
目錄下,因此在public
中新建electron.js
做爲主進程const { app, BrowserWindow } = require("electron"); const isDev = process.env.NODE_ENV !== "development"; app.on("ready", () => { mainWindow = new BrowserWindow(); isDev ? mainWindow.loadURL(`file://${__dirname}\\index.html`) : mainWindow.loadURL(`http://localhost:3000`); });
須要引入的庫html
yarn add electron electron-builder nodemon -D //安裝到生產環境 yarn add concurrently cross-env -S //安裝到開發環境
在 package.json 中經過 mian 標明主進程執行目錄,配置 homepage前端
配置scripts
和build
字段,在 react 啓動後打開 electron 桌面應用、經過 cross-env
添加環境變量、以及在打包時如何進行配置(只進行 win 下打包)java
{ "name": "my-app", "version": "0.1.0", "private": true, "main": "public/electron.js", "homepage": ".", "scripts": { "start": "cross-env NODE_ENV=development concurrently \"yarn run client\" \"wait-on http://localhost:3000 && yarn run electron:watch\" ", "build": "yarn run build-client && yarn run build-electron", "client": "set BROWSER=none && react-scripts start", "electron:watch": "nodemon --watch public/electron.js --exec electron .", "electron": "electron .", "build-client": "react-scripts build", "build-electron": "electron-builder build -w", "test": "react-scripts test", "eject": "react-scripts eject" }, "build": { "productName": "electron-demos", "files": ["build/","main.js"], "dmg": { "contents": [ {"x": 110,"y": 150}, {"x": 240,"y": 150,"type": "link", "path": "/Applications"} ] }, "win": { "target": [{"target": "nsis", "arch": ["x64" ]}] }, "directories": { "buildResources": "assets", "output": "release" } }, }
此時咱們能夠運行yarn start
將以前的react
起始頁經過桌面程序的方式打開,也能夠經過執行yarn build
將咱們的桌面程序打包生成.exe
文件進行安裝 over。node
既然咱們能夠利用
react
&electron
構建桌面應用,就能夠利用衆多 npm 包去實現一個能用在生活中能夠用到的功能,前段時間因爲興趣使然,接觸 node 爬蟲比較多,因此我想結合puppeteer
實現每日壁紙的桌面應用react
須要引入的庫webpack
dayjs
判斷和添加日期時electron-store
數據存儲 (若是使用mongodb
數據庫在開發環境正常,可是打包後就會報錯)electron-dl
圖片下載首先進行BrowserWindow
的初始化配置git
mainWindow = new BrowserWindow({ show: false, width: 900, height: 700, minHeight: 700, minWidth: 310, frame: false, //無邊框 transparent: false, //透明 alwaysOnTop: false, hasShadow: false, //陰影 resizable: true, webPreferences: { nodeIntegration: true, //是否使用 node enableRemoteModule: true, //是否有子頁面 contextIsolation: false, //是否禁止 node nodeIntegrationInSubFrames: true, //否容許在子頁面(iframe)或子窗口(child window)中集成 Node.js }, });
數據經過electron-store
進行操做,使用方便,引入後操做實例對象調取get
、set
、delete
進行獲取、設置和刪除,但缺點一樣明顯,不能像mongodb
同樣經過mongoose
構建模型進行數據操做es6
const Store = require("electron-store"); const store = new Store(test); store.set("test", true); //設置 store.get("test"); //獲取 store.delete("test"); //刪除
需求界面 UI 簡潔,因此經過 electron 中的 ipcMain
和 ipcRenderer
通訊模塊結合前端antd/icons
設置應用的最小化按鈕、全屏按鈕、恢復按鈕,當點擊最小化時,界面隱藏置系統托盤,托盤點擊控制界面出現和隱藏,托盤圖標右鍵進行關閉github
👇👇👇👇👇 更改成
const { Menu: { buildFromTemplate, setApplicationMenu }, } = require("electron"); setApplicationMenu(buildFromTemplate([])); //取消默認工具欄
ipcMain
和 ipcRenderer
都是 EventEmitter
類的一個實例。而EventEmitter
類由NodeJS
中的events
模塊導出,EventEmitter
類是 NodeJS 事件的基礎,實現了事件模型須要的接口, 包括 addListener
,removeListener
, emit
及其它工具方法. 同原生 JavaScript 事件相似, 採用了發佈/訂閱(觀察者)的方式, 使用內部 _events
列表來記錄註冊的事件處理器。
const { Tray } = require("electron"); var appTray; ipcMain.on("max-icon", () => { //點擊最大化時,主進程響應 mainWindow.isMaximized() ? mainWindow.restore() : mainWindow.maximize(); }); ipcMain.on("mini-icon", () => { //點擊最小化時 mainWindow.minimize(); //界面最小化 mainWindow.hide(); //隱藏界面 if (!appTray) { appTray = new Tray(path.join(__dirname, "favicon.ico")); //設置托盤圖標 appTray.setToolTip("one wallpaper💎"); //托盤圖標hover時觸發 appTray.on("click", () => //托盤圖標點擊時觸發 mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show() ); appTray.setContextMenu( //托盤圖標右擊時觸發 buildFromTemplate([ { label: "退出", click: () => app.quit(), }, ]) ); } });
electron 其他部分就是利用ipcMain
和 ipcRenderer
通訊,使用electron-store
操做數據儲存、處理並返回前端,當須要設置壁紙時經過electron-dl
進行下載,並返回下載後圖片的絕對路徑給前端,用於設置桌面壁紙
須要引入的庫
antd
頁面樣式wallpaper
設置壁紙puppeteer
爬蟲node-schedule
定時任務前端頁面初始化時先經過ipcRenderer
進行數據庫,若是存在則對比數據庫中time
字段保存的時間與當前時間是否爲同一天,都符合則獲取展現,不然調取puppeteer
從新進行最新壁紙頁面的數據爬取,並將爬取的數據save
ormerge
到數據庫,更新time
字段,點擊對應集合時,進行對應集合的爬取並添加到當前children
字段進行保存, 數據結構爲以下所示
[ { time:'xxxx-xx-xx' }, { href: "當前集合連接", srcmini: "集合縮略圖.jpg", title: "集合名稱", children: [ { like: true, //該壁紙是否添加收藏 href: "壁紙所屬集合連接", maxsrc: "壁紙縮略圖.jpg", srcmini: "壁紙大圖.jpg", }, ... ], } ... ];
前端選用的是 react
+antd
進行開發,須要引入的 node 庫時在 utils.js
文件下進行引入處理、並經過 es6 方式進行導出,因爲electron
通訊的回調函數在 es6 中並不友好,因此在utils.js
中進行統一的異步封裝,以xxx-reply
做爲響應 ipcRenderer 通訊的標準格式,調用時直接傳入通訊事件名await ipcasync('xxx')
export const { ipcRenderer } = window.require("electron"); export const ipcasync = async (name, obj = null) => { ipcRenderer.send(name, obj); return await new Promise(resolve => { ipcRenderer.on(`${name}-reply`, (event, arg) => resolve(arg)); }); };
爬蟲使用的puppeteer
庫,經過無頭瀏覽器進行爬取,防止網頁動態加載致使獲取不到數據,並能夠進行點擊、輸入等模擬用戶真實行爲,弊端在於爬取速度較慢,因此會將爬取到的數據保存,避免二次爬取,在爬取壁紙集合時,會根據 electron 獲取到的頁面大小進行匹配壁紙尺寸進行爬取
爬取當前最新壁紙
const getHomePage = async url => { let urls = "壁紙網站域名/" + url; //url即子域名 const browser = await puppeteer.launch(config); const page = await browser.newPage(); await page.goto(urls); await page.waitForSelector(".wrapper", { visible: true }); const arr = await page.$$eval(".main>ul a", el => el.map(i => ({ href: "壁紙網站域名/" + i.getAttribute("href"), srcmini: i.firstChild.getAttribute("src"), title: i.firstChild.getAttribute("title"), children: [], })) ); browser.close(); return arr; };
爬取指定集合下壁紙
const getPages = async (url, screen) => { const browser = await puppeteer.launch(config); const page = await browser.newPage(); await page.goto(url); const all = await page.$eval(".wrapper span", el => el.textContent); const allPage = all.split("/")[1].replace(")", ""); await page.waitForSelector(".wrapper", { visible: true }); const arr = await page.$$eval("#showImg li a", el => el.map(i => ({ href: "壁紙網站域名/" + i.getAttribute("href"), srcmini: i.firstElementChild.getAttribute("src") || i.firstElementChild.getAttribute("srcs"), })) ); for (let i = 0; i < arr.length; i++) { console.log(`總共爬取 ${allPage} 張,當前爬取第 ${i} 張`); await page.goto(arr[i].href); await page.waitForSelector(`#tagfbl`, { visible: true }); const hrefItems = await page.evaluate( el => document.querySelector(el) ? document.querySelector(el).getAttribute("href") : document.querySelector(`a[id="1920x1080"]`) ? document.querySelector(`a[id="1920x1080"]`).getAttribute("href") : document.querySelector(`#tagfbl a`).getAttribute("href"), `a[id="${screen}"]` ); await page.goto("壁紙網站域名/" + hrefItems); await page.waitForSelector("body img", { visible: true }); const hrefItem = await page.$eval("body img", el => el.src); arr[i].maxsrc = hrefItem; } browser.close(); return arr; };
功能展現
至此,謝謝各位在百忙之中點開這篇文章,但願對大家能有所幫助,相信你對 electron 結合 react 開發以及有了大概的認實,總的來講優化的點還有不少,好比 webpack 的打包配置、爬蟲、等等...此項目爲了你們能更熟練上手在上手 electron+react 的業務需求,若有問題歡迎各位大佬指正。
求個 star,謝謝你們了