前言

Electron 是一个可以使用 Web 技术如 JavaScript、HTML 和 CSS 来创建跨平台原生桌面应用的框架。借助 Electron,我们可以使用纯 JavaScript 来调用丰富的原生 APIs。

一个 electron-react 栗子

1️⃣-Demo 安装 react 脚手架

  • 终端执行命令npx create-react-app react-electron自动进行配置安装
  • 进入react-electron目录下执行yarn start,项目自动运行在 3000 端口

2️⃣-Demo 配置 electron 主进程

  • 因为public文件夹不会被webpack打包处理,会直接复制一份到dist目录下,所以在public中新建electron.js作为主进程
  • 在主进程中只需要从 electron 包中结构出 app, BrowserWindow,并监听 app 的'ready'事件,使用 BrowserWindow 生成实例对象,从而判断环境进行加载静态文件 or 端口
  1. const { app, BrowserWindow } = require("electron");
  2. const isDev = process.env.NODE_ENV !== "development";
  3. app.on("ready", () => {
  4. mainWindow = new BrowserWindow();
  5. isDev
  6. ? mainWindow.loadURL(`file://${__dirname}\\index.html`)
  7. : mainWindow.loadURL(`http://localhost:3000`);
  8. });

3️⃣-Demo 配置 react-cli

需要引入的库

  1. yarn add electron electron-builder nodemon -D //安装到生产环境
  2. yarn add concurrently cross-env -S //安装到开发环境
  • 在 package.json 中通过 mian 标明主进程执行目录,配置 homepage

  • 配置scriptsbuild字段,在 react 启动后打开 electron 桌面应用、通过 cross-env 添加环境变量、以及在打包时如何进行配置(只进行 win 下打包)

    1. {
    2. "name": "my-app",
    3. "version": "0.1.0",
    4. "private": true,
    5. "main": "public/electron.js",
    6. "homepage": ".",
    7. "scripts": {
    8. "start": "cross-env NODE_ENV=development concurrently \"yarn run client\" \"wait-on http://localhost:3000 && yarn run electron:watch\" ",
    9. "build": "yarn run build-client && yarn run build-electron",
    10. "client": "set BROWSER=none && react-scripts start",
    11. "electron:watch": "nodemon --watch public/electron.js --exec electron .",
    12. "electron": "electron .",
    13. "build-client": "react-scripts build",
    14. "build-electron": "electron-builder build -w",
    15. "test": "react-scripts test",
    16. "eject": "react-scripts eject"
    17. },
    18. "build": {
    19. "productName": "electron-demos",
    20. "files": ["build/","main.js"],
    21. "dmg": {
    22. "contents": [
    23. {"x": 110,"y": 150},
    24. {"x": 240,"y": 150,"type": "link", "path": "/Applications"}
    25. ]
    26. },
    27. "win": {
    28. "target": [{"target": "nsis", "arch": ["x64" ]}]
    29. },
    30. "directories": {
    31. "buildResources": "assets",
    32. "output": "release"
    33. }
    34. },
    35. }

此时我们可以运行yarn start 将之前的react起始页通过桌面程序的方式打开,也可以通过执行yarn build 将我们的桌面程序打包生成.exe文件进行安装 over。

electron-react 每日壁纸

既然我们可以利用 react &electron 构建桌面应用,就可以利用众多 npm 包去实现一个能用在生活中可以用到的功能,前段时间由于兴趣使然,接触 node 爬虫比较多,所以我想结合 puppeteer实现每日壁纸的桌面应用

1️⃣-wallpaper 明确需求

  • 壁纸进行分类获取,所有主题的壁纸通过合集的方式保存
  • 每天的壁纸按时更新,更新过的壁纸会保存到数据库中
  • 壁纸合集中的壁纸可以通过喜欢功能进行收藏或取消
  • 壁纸可以预览、下载,并可进行一键设置
  • 在收藏的壁纸中可以开启是否进行每天自动设置当前壁纸
  • 风格简约,自适应布局

2️⃣-wallpaper 功能实现

1、electron 部分

需要引入的库

  • dayjs 判断和添加日期时
  • electron-store 数据存储 (如果使用mongodb数据库在开发环境正常,但是打包后就会报错)
  • electron-dl 图片下载

首先进行BrowserWindow的初始化配置

  1. mainWindow = new BrowserWindow({
  2. show: false,
  3. width: 900,
  4. height: 700,
  5. minHeight: 700,
  6. minWidth: 310,
  7. frame: false, //无边框
  8. transparent: false, //透明
  9. alwaysOnTop: false,
  10. hasShadow: false, //阴影
  11. resizable: true,
  12. webPreferences: {
  13. nodeIntegration: true, //是否使用 node
  14. enableRemoteModule: true, //是否有子页面
  15. contextIsolation: false, //是否禁止 node
  16. nodeIntegrationInSubFrames: true, //否允许在子页面(iframe)或子窗口(child window)中集成 Node.js
  17. },
  18. });

数据通过electron-store进行操作,使用方便,引入后操作实例对象调取getsetdelete进行获取、设置和删除,但缺点同样明显,不能像mongodb一样通过mongoose构建模型进行数据操作

  1. const Store = require("electron-store");
  2. const store = new Store(test);
  3. store.set("test", true); //设置
  4. store.get("test"); //获取
  5. store.delete("test"); //删除

需求界面 UI 简洁,所以通过 electron 中的 ipcMainipcRenderer 通信模块结合前端antd/icons设置应用的最小化按钮、全屏按钮、恢复按钮,当点击最小化时,界面隐藏置系统托盘,托盘点击控制界面出现和隐藏,托盘图标右键进行关闭


更改为

  1. const {
  2. Menu: { buildFromTemplate, setApplicationMenu },
  3. } = require("electron");
  4. setApplicationMenu(buildFromTemplate([])); //取消默认工具栏

ipcMainipcRenderer 都是 EventEmitter类的一个实例。而EventEmitter类由NodeJS中的events模块导出,EventEmitter 类是 NodeJS 事件的基础,实现了事件模型需要的接口, 包括 addListenerremoveListener, emit 及其它工具方法. 同原生 JavaScript 事件类似, 采用了发布/订阅(观察者)的方式, 使用内部 _events 列表来记录注册的事件处理器。

  1. const { Tray } = require("electron");
  2. var appTray;
  3. ipcMain.on("max-icon", () => {
  4. //点击最大化时,主进程响应
  5. mainWindow.isMaximized() ? mainWindow.restore() : mainWindow.maximize();
  6. });
  7. ipcMain.on("mini-icon", () => {
  8. //点击最小化时
  9. mainWindow.minimize(); //界面最小化
  10. mainWindow.hide(); //隐藏界面
  11. if (!appTray) {
  12. appTray = new Tray(path.join(__dirname, "favicon.ico")); //设置托盘图标
  13. appTray.setToolTip("one wallpaper"); //托盘图标hover时触发
  14. appTray.on("click", () =>
  15. //托盘图标点击时触发
  16. mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show()
  17. );
  18. appTray.setContextMenu(
  19. //托盘图标右击时触发
  20. buildFromTemplate([
  21. {
  22. label: "退出",
  23. click: () => app.quit(),
  24. },
  25. ])
  26. );
  27. }
  28. });

electron 其余部分就是利用ipcMainipcRenderer通信,使用electron-store操作数据储存、处理并返回前端,当需要设置壁纸时通过electron-dl进行下载,并返回下载后图片的绝对路径给前端,用于设置桌面壁纸

2、前端部分

需要引入的库

  • antd 页面样式
  • wallpaper 设置壁纸
  • puppeteer 爬虫
  • node-schedule 定时任务

前端页面初始化时先通过ipcRenderer进行数据库,如果存在则对比数据库中time字段保存的时间与当前时间是否为同一天,都符合则获取展示,否则调取puppeteer重新进行最新壁纸页面的数据爬取,并将爬取的数据saveormerge到数据库,更新time字段,点击对应集合时,进行对应集合的爬取并添加到当前children字段进行保存, 数据结构为如下所示

  1. [
  2. {
  3. time:'xxxx-xx-xx'
  4. },
  5. {
  6. href: "当前集合链接",
  7. srcmini: "集合缩略图.jpg",
  8. title: "集合名称",
  9. children: [
  10. {
  11. like: true, //该壁纸是否添加收藏
  12. href: "壁纸所属集合链接",
  13. maxsrc: "壁纸缩略图.jpg",
  14. srcmini: "壁纸大图.jpg",
  15. },
  16. ...
  17. ],
  18. }
  19. ...
  20. ];

前端选用的是 react+antd 进行开发,需要引入的 node 库时在 utils.js 文件下进行引入处理、并通过 es6 方式进行导出,由于electron通信的回调函数在 es6 中并不友好,所以在utils.js中进行统一的异步封装,以xxx-reply作为响应 ipcRenderer 通信的标准格式,调用时直接传入通信事件名await ipcasync('xxx')

  1. export const { ipcRenderer } = window.require("electron");
  2. export const ipcasync = async (name, obj = null) => {
  3. ipcRenderer.send(name, obj);
  4. return await new Promise(resolve => {
  5. ipcRenderer.on(`${name}-reply`, (event, arg) => resolve(arg));
  6. });
  7. };

爬虫使用的puppeteer库,通过无头浏览器进行爬取,防止网页动态加载导致获取不到数据,并可以进行点击、输入等模拟用户真实行为,弊端在于爬取速度较慢,所以会将爬取到的数据保存,避免二次爬取,在爬取壁纸集合时,会根据 electron 获取到的页面大小进行匹配壁纸尺寸进行爬取

爬取当前最新壁纸

  1. const getHomePage = async url => {
  2. let urls = "壁纸网站域名/" + url; //url即子域名
  3. const browser = await puppeteer.launch(config);
  4. const page = await browser.newPage();
  5. await page.goto(urls);
  6. await page.waitForSelector(".wrapper", { visible: true });
  7. const arr = await page.$$eval(".main>ul a", el =>
  8. el.map(i => ({
  9. href: "壁纸网站域名/" + i.getAttribute("href"),
  10. srcmini: i.firstChild.getAttribute("src"),
  11. title: i.firstChild.getAttribute("title"),
  12. children: [],
  13. }))
  14. );
  15. browser.close();
  16. return arr;
  17. };

爬取指定集合下壁纸

  1. const getPages = async (url, screen) => {
  2. const browser = await puppeteer.launch(config);
  3. const page = await browser.newPage();
  4. await page.goto(url);
  5. const all = await page.$eval(".wrapper span", el => el.textContent);
  6. const allPage = all.split("/")[1].replace(")", "");
  7. await page.waitForSelector(".wrapper", { visible: true });
  8. const arr = await page.$$eval("#showImg li a", el =>
  9. el.map(i => ({
  10. href: "壁纸网站域名/" + i.getAttribute("href"),
  11. srcmini:
  12. i.firstElementChild.getAttribute("src") ||
  13. i.firstElementChild.getAttribute("srcs"),
  14. }))
  15. );
  16. for (let i = 0; i < arr.length; i++) {
  17. console.log(`总共爬取 ${allPage} 张,当前爬取第 ${i} 张`);
  18. await page.goto(arr[i].href);
  19. await page.waitForSelector(`#tagfbl`, { visible: true });
  20. const hrefItems = await page.evaluate(
  21. el =>
  22. document.querySelector(el)
  23. ? document.querySelector(el).getAttribute("href")
  24. : document.querySelector(`a[id="1920x1080"]`)
  25. ? document.querySelector(`a[id="1920x1080"]`).getAttribute("href")
  26. : document.querySelector(`#tagfbl a`).getAttribute("href"),
  27. `a[id="${screen}"]`
  28. );
  29. await page.goto("壁纸网站域名/" + hrefItems);
  30. await page.waitForSelector("body img", { visible: true });
  31. const hrefItem = await page.$eval("body img", el => el.src);
  32. arr[i].maxsrc = hrefItem;
  33. }
  34. browser.close();
  35. return arr;
  36. };

3️⃣-wallpaper 展示

功能展示

  • 自适应布局
  • 壁纸收藏
  • 壁纸下载
  • 每日更新
  • 动态壁纸 (真不知道怎么搞,来个大佬指导一下)

4️⃣-wallpaper 总结

至此,谢谢各位在百忙之中点开这篇文章,希望对你们能有所帮助,相信你对 electron 结合 react 开发以及有了大概的认实,总的来说优化的点还有很多,比如 webpack 的打包配置、爬虫、等等...此项目为了大家能更熟练上手在上手 electron+react 的业务需求,如有问题欢迎各位大佬指正。

  • 跳转github
  • :将 package 文件中的 executablePath 更改为自己谷歌浏览器的目标路径

求个 star,谢谢大家了

基于 react + electron 开发及结合爬虫的应用实践🎅的更多相关文章

  1. 基于Jenkins的开发测试全流程持续集成实践

    今年一直在公司实践CI,本文将近半年来的一些实践总结一下,可能不太完善或优美,但的确初步解决了我目前所在项目组的一些痛点.当然这仅是一家之言也不够完整,后续还会深入实践和引入Kubernetes进行容 ...

  2. 基于React Native的跨三端应用架构实践

    作者|陈子涵 编辑|覃云 “一次编写, 到处运行”(Write once, run anywhere ) 是很多前端团队孜孜以求的目标.实现这个目标,不但能以最快的速度,将应用推广到各个渠道,而且还能 ...

  3. RSuite 一个基于 React.js 的 Web 组件库

    RSuite http://rsuite.github.io RSuite 是一个基于 React.js 开发的 Web 组件库,参考 Bootstrap 设计,提供其中常用组件,支持响应式布局. 我 ...

  4. 基于React.js网页版弹窗|react pc端自定义对话框组件RLayer

    基于React.js实现PC桌面端自定义弹窗组件RLayer. 前几天有分享一个Vue网页版弹框组件,今天分享一个最新开发的React PC桌面端自定义对话框组件. RLayer 一款基于react. ...

  5. 基于react全家桶+antd-design+webpack2+node+express+mongodb开发的前后台博客系统

    很久没更新博客,最近也有点忙,然后业余时间搞了一个比较完整基于react全家桶+antd-design+webpack2+node+express+mongodb开发的前后台博客系统的流程系统,希望对 ...

  6. 前端基于react,后端基于.net core2.0的开发之路(1) 介绍

    文章提纲目录 1.前端基于react,后端基于.net core2.0的开发之路(1) 介绍 2.前端基于react,后端基于.net core2.0的开发之路(2) 开发环境的配置,注意事项,后端数 ...

  7. 前端基于react,后端基于.net core2.0的开发之路(2) 开发环境的配置,注意事项,后端数据初始化

    前端环境配置 项目介绍文章:前端基于react,后端基于.net core2.0的开发之路(1) 介绍 1.VSCode安装 下载地址:https://code.visualstudio.com/Do ...

  8. 实例讲解基于 React+Redux 的前端开发流程

    原文地址:https://segmentfault.com/a/1190000005356568 前言:在当下的前端界,react 和 redux 发展得如火如荼,react 在 github 的 s ...

  9. 基于React 的前端UI开发框架 及与Electron 的结合 https://cxjs.io/

    1.cxjs  基于React 的前端UI开发框架    https://cxjs.io/ coreu   http://coreui.io/ 2.antd-admin                ...

随机推荐

  1. 嵌入式设备上卷积神经网络推理时memory的优化

    以前的神经网络几乎都是部署在云端(服务器上),设备端采集到数据通过网络发送给服务器做inference(推理),结果再通过网络返回给设备端.如今越来越多的神经网络部署在嵌入式设备端上,即inferen ...

  2. 牛客挑战赛33 C 艾伦的立体机动装置(几何)

    思路: 我们需要枚举展开多少条边 然后把上底面的点放到和下底面一个平面 然后算两点之间的距离 注意判断直线与线段是否有交点 #include <bits/stdc++.h> using n ...

  3. dp practice 1

    https://codeforces.com/problemset/problem/553/A dp+组合数学 dp[i] 放前i种颜色的方法数 #include<bits/stdc++.h&g ...

  4. 2019牛客暑期多校训练营(第二场)E.MAZE(线段树+dp)

    题意:给你一个n*m的矩阵 你只能向左向右相下走 有两种操作 q次询问 一种是把一个单位翻转(即可走变为不可走 不可走变为可走) 另一种是询问从(1,x) 走到 (n,y)有多少种方案 思路:题目n为 ...

  5. poj 1511-- Invitation Cards (dijkstra+优先队列)

    刚开始想复杂了,一直做不出来,,,其实就是两遍dijkstra+优先队列(其实就是板子题,只要能有个好的板子,剩下的都不是事),做出来感觉好简单...... 题意:有n个车站和n个志愿者,早上每个志愿 ...

  6. Codeforces Round #687 (Div. 2, based on Technocup 2021 Elimination Round 2) D. XOR-gun (二进制,异或,前缀和)

    题意:给你一组非递减的数,你可以对两个连续的数进行异或,使其合并为一个数,问最少操作多少次使得这组数不满足非递减. 题解:首先,给出的这组数是非递减的,我们考虑二进制,对于三个连续的非递减的最高位相同 ...

  7. Codeforces Round #575 (Div. 3) E. Connected Component on a Chessboard

    传送门 题意: 给你一个黑白相间的1e9*1e9的棋盘,你需要从里面找出来由b个黑色的格子和w个白色的格子组成的连通器(就是你找出来的b+w个格子要连接在一起,不需要成环).问你可不可以找出来,如果可 ...

  8. Codeforces Round #649 (Div. 2) B. Most socially-distanced subsequence (数学,差分)

    题意:有一长度为\(n\)的数组,求一子序列,要求子序列中两两差的绝对值最大,并且子序列尽可能短. 题解:将数组看成坐标轴上的点,其实就是求每个单调区间的端点,用差分数组来判断单调性. 代码: #in ...

  9. C语言之库函数的模拟与使用

    C语言之库函数的模拟与使用 在我们学习C语言的过程中,难免会遇到这样的一种情况: 我们通常实现一个功能的时候,费尽心血的写出来,却有着满满的错,这时却有人来告诉你说:这个功能可以用相应的库函数来实现. ...

  10. OPENSOURCE - libcurl

    本文仅做备份存档,原文地址如下,请点链接进入 https://www.cnblogs.com/moodlxs/archive/2012/10/15/2724318.html https://www.c ...