This commit is contained in:
贾同学
2025-10-16 20:01:57 +08:00
commit 4768ef2d26
79 changed files with 3358 additions and 0 deletions

22
.gitignore vendored Normal file
View File

@@ -0,0 +1,22 @@
node_modules
out/
logs/
runtime/
.idea/
package-lock.json
data/
.vscode/launch.json
public/electron/
public/dist/
pnpm-lock.yaml
.yalc/
yalc.lock
go/tmp/
build/extraResources/java-app.jar
build/extraResources/jre1.8.0_201/
python/.venv/
python/*.spec
python/build/
python/dist/
*DS_Store
yalc.lock

4
.npmrc Normal file
View File

@@ -0,0 +1,4 @@
registry=https://registry.npmmirror.com/
disturl=https://registry.npmmirror.com/-/binary/node
electron_mirror=https://npmmirror.com/mirrors/electron/
electron-builder-binaries_mirror=https://registry.npmmirror.com/-/binary/electron-builder-binaries/

221
README.md Normal file
View File

@@ -0,0 +1,221 @@
[![star](https://gitee.com/dromara/electron-egg/badge/star.svg?theme=gvp)](https://gitee.com/dromara/electron-egg/stargazers)
<div align=center>
<h3>🎉🎉🎉 ElectronEgg V4 已发布! 🎉🎉🎉</h3>
</div>
<br>
<div align=center>
<img src="./public/images/example/logo.png" width="150" height="150" />
</div>
<div align=center>
<h3><strong>一个入门简单、跨平台、企业级桌面软件开发框架</strong></h3>
</div>
<br>
<!-- ## 🌏 [English](https://www.yuque.com/u34495/ee-doc) | [中文](https://www.kaka996.com/) -->
## 📋 介绍
> 框架已经广泛应用于记账、政务、企业、医疗、学校、股票交易、ERP、娱乐、视频等领域客户端请放心使用
## 👦 谁可以使用
项目已经有 5 个交流群,覆盖`前端``java``go``python``php` 等开发者。
无论你是前端、服务端、运维、游戏、客户端等,都可以很快入门,
## 🐶 精彩案例
- [**点击查看**](#项目案例)
## 📺 特点
- 🍩 **为什么使用?** 桌面软件(办公方向、 个人工具仍然是未来十几年PC端需求之一提高工作效率
- 🍉 **简单:** 只需懂 JavaScript
- 🍑 **愿景:** 所有开发者都能学会桌面软件研发
- 🍰 **gitee** https://gitee.com/dromara/electron-egg **5100+**
- 🍨 **github** https://github.com/dromara/electron-egg **1800+**
- 🏆 码云最有价值开源项目
![](./public/images/example/ee-zs.png)
## 📚 文档
- 快速体验:[教程文档](https://www.kaka996.com/)
![](./public/images/example/v3-home.png)
## 📦 特性
1. 🍄 跨平台一套代码可以打包成windows版、Mac版、Linux版、国产UOS、Deepin、麒麟等
2. 🌹 架构:单业务进程/模块化/多任务(进程,线程,渲染进程),让开发大型项目变的简单。
3. 🌱 简单高效:只需学习 js 语言
4. 🌴 前端独立理论上支持任何前端技术vue、react、html等等
5. 🍁 工程化:可以用前端、服务端的开发思维,来编写桌面软件
6. 🌷 高性能事件驱动、非阻塞式IO
7. 🌰 功能丰富:配置、通信、插件、数据库、升级、打包、工具... 应有尽有
8. 💐 安全:支持字节码加密、压缩混淆加密
9. 🌻 功能demo桌面软件常见功能框架集成或提供demo
## ✈️ 使用场景
### 1. 🚀 常规桌面软件
- 🚖 windows平台
![](./public/images/example/ee-win-home.png)
- 🚍 macOS平台
![](./public/images/example/ee-mac-home.png)
- 🚔 linux平台 - 国产UOS、Deepin
![](./public/images/example/uos-home.png)
- 🚔 linux平台 - ubuntu
![](./public/images/example/ubuntu-db.png)
### 🚐 2. vue、react、angular、web 转换成桌面软件
- 🚙 vue-ant-design本地
![](./public/images/example/vue-antd.png)
- 🚙 禅道项目管理web项目地址
![](./public/images/example/ee-project-7.png)
### 🚂 3. 游戏h5相关技术开发
- 🚊 忍者100层
![](./public/images/example/ee_game_1.png)
## 📒 开始使用
- ✒️ [安装文档](https://www.kaka996.com/pages/e64ff6/)
## 项目案例
- 🐟 框架已经应用于医疗、学校、政务、股票交易、ERP、娱乐、视频、企业等领域客户端
### 🐸 远控
- RQ Center
![](./public/images/example/rq-1.png)
![](./public/images/example/rq-2.png)
### 🐸 云盘
- FM Cloud
![](./public/images/example/fm-p2.png)
![](./public/images/example/fm-p1.png)
![](./public/images/example/fm-p4.png)
### 🐸 IM
- Cede IM
![](./public/images/example/im-p1.png)
![](./public/images/example/im-p5.png)
![](./public/images/example/im-p1.png)
### 🐸 壁纸
- warpar
![](./public/images/example/aw-3.png)
### 🐸 英雄联盟助手
- Serendlplty
![](./public/images/example/lol-zhanji.png)
### 🐸 更多
- [更多案例](https://www.kaka996.com/pages/eadf46/)
## 💬 交流
1. [讨论](https://www.kaka996.com/pages/c2720e/)
## 📌 关于pr
请前往[GitHub项目](https://github.com/dromara/electron-egg)提pr避免代码同步后pr被覆盖掉感谢
地址https://github.com/dromara/electron-egg
## 📔 框架核心包 ee-core
ee-core[https://github.com/wallace5303/ee-core](https://github.com/wallace5303/ee-core)
## 📚 Dromara 成员项目
<p align="center">
<a href="https://gitee.com/dromara/TLog" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/tlog2.png" title="一个轻量级的分布式日志标记追踪神器10分钟即可接入自动对日志打标签完成微服务的链路追踪" width="15%">
</a>
<a href="https://gitee.com/dromara/liteFlow" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/liteflow.png" title="轻量,快速,稳定,可编排的组件式流程引擎" width="15%">
</a>
<a href="https://hutool.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/hutool.jpg" title="小而全的Java工具类库使Java拥有函数式语言般的优雅让Java语言也可以“甜甜的”。" width="15%">
</a>
<a href="https://sa-token.dev33.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/sa-token.png" title="一个轻量级 java 权限认证框架,让鉴权变得简单、优雅!" width="15%">
</a>
<a href="https://gitee.com/dromara/hmily" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/hmily.png" title="高性能一站式分布式事务解决方案。" width="15%">
</a>
<a href="https://gitee.com/dromara/Raincat" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/raincat.png" title="强一致性分布式事务解决方案。" width="15%">
</a>
</p>
<p align="center">
<a href="https://gitee.com/dromara/myth" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/myth.png" title="可靠消息分布式事务解决方案。" width="15%">
</a>
<a href="https://cubic.jiagoujishu.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/cubic.png" title="一站式问题定位平台以agent的方式无侵入接入应用完整集成arthas功能模块致力于应用级监控帮助开发人员快速定位问题" width="15%">
</a>
<a href="https://maxkey.top/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/maxkey.png" title="业界领先的身份管理和认证产品" width="15%">
</a>
<a href="http://forest.dtflyx.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/forest-logo.png" title="Forest能够帮助您使用更简单的方式编写Java的HTTP客户端" width="15%">
</a>
<a href="https://jpom.io/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/jpom.png" title="一款简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件" width="15%">
</a>
<a href="https://su.usthe.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/sureness.png" title="面向 REST API 的高性能认证鉴权框架" width="15%">
</a>
</p>
<p align="center">
<a href="https://easy-es.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/easy-es2.png" title="傻瓜级ElasticSearch搜索引擎ORM框架" width="15%">
</a>
<a href="https://gitee.com/dromara/northstar" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/northstar_logo.png" title="Northstar盈富量化交易平台" width="15%">
</a>
<a href="https://hertzbeat.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/hertzbeat_brand.jpg" title="易用友好的云监控系统" width="15%">
</a>
<a href="https://plugins.sheng90.wang/fast-request/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/fast-request.gif" title="Idea 版 Postman为简化调试API而生" width="15%">
</a>
<a href="https://www.jeesuite.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/mendmix.png" title="开源分布式云原生架构一站式解决方案" width="15%">
</a>
<a href="https://gitee.com/dromara/koalas-rpc" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/koalas-rpc2.png" title="企业生产级百亿日PV高可用可拓展的RPC框架。" width="15%">
</a>
</p>
<p align="center">
<a href="https://async.sizegang.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/gobrs-async.png" title="配置极简功能强大的异步任务动态编排框架" width="15%">
</a>
<a href="https://dynamictp.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/dynamic-tp.png" title="基于配置中心的轻量级动态可监控线程池" width="15%">
</a>
<a href="https://www.x-easypdf.cn" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/x-easypdf.png" title="一个用搭积木的方式构建pdf的框架基于pdfbox" width="15%">
</a>
<a href="http://dromara.gitee.io/image-combiner" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/image-combiner.png" title="一个专门用于图片合成的工具,没有很复杂的功能,简单实用,却不失强大" width="15%">
</a>
<a href="https://www.herodotus.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/dante-cloud2.png" title="Dante-Cloud 是一款企业级微服务架构和服务能力开发平台。" width="15%">
</a>
<a href="https://dromara.org/zh/projects/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/dromara.png" title="让每一位开源爱好者,体会到开源的快乐。" width="15%">
</a>
</p>

221
README.zh-CN.md Normal file
View File

@@ -0,0 +1,221 @@
[![star](https://gitee.com/dromara/electron-egg/badge/star.svg?theme=gvp)](https://gitee.com/dromara/electron-egg/stargazers)
<div align=center>
<h3>🎉🎉🎉 ElectronEgg V4 已发布! 🎉🎉🎉</h3>
</div>
<br>
<div align=center>
<img src="./public/images/example/logo.png" width="150" height="150" />
</div>
<div align=center>
<h3><strong>一个入门简单、跨平台、企业级桌面软件开发框架</strong></h3>
</div>
<br>
<!-- ## 🌏 [English](https://www.yuque.com/u34495/ee-doc) | [中文](https://www.kaka996.com/) -->
## 📋 介绍
> 框架已经广泛应用于记账、政务、企业、医疗、学校、股票交易、ERP、娱乐、视频等领域客户端请放心使用
## 👦 谁可以使用
项目已经有 5 个交流群,覆盖`前端``java``go``python``php` 等开发者。
无论你是前端、服务端、运维、游戏、客户端等,都可以很快入门,
## 🐶 精彩案例
- [**点击查看**](#项目案例)
## 📺 特点
- 🍩 **为什么使用?** 桌面软件(办公方向、 个人工具仍然是未来十几年PC端需求之一提高工作效率
- 🍉 **简单:** 只需懂 JavaScript
- 🍑 **愿景:** 所有开发者都能学会桌面软件研发
- 🍰 **gitee** https://gitee.com/dromara/electron-egg **5100+**
- 🍨 **github** https://github.com/dromara/electron-egg **1800+**
- 🏆 码云最有价值开源项目
![](./public/images/example/ee-zs.png)
## 📚 文档
- 快速体验:[教程文档](https://www.kaka996.com/)
![](./public/images/example/v3-home.png)
## 📦 特性
1. 🍄 跨平台一套代码可以打包成windows版、Mac版、Linux版、国产UOS、Deepin、麒麟等
2. 🌹 架构:单业务进程/模块化/多任务(进程,线程,渲染进程),让开发大型项目变的简单。
3. 🌱 简单高效:只需学习 js 语言
4. 🌴 前端独立理论上支持任何前端技术vue、react、html等等
5. 🍁 工程化:可以用前端、服务端的开发思维,来编写桌面软件
6. 🌷 高性能事件驱动、非阻塞式IO
7. 🌰 功能丰富:配置、通信、插件、数据库、升级、打包、工具... 应有尽有
8. 💐 安全:支持字节码加密、压缩混淆加密
9. 🌻 功能demo桌面软件常见功能框架集成或提供demo
## ✈️ 使用场景
### 1. 🚀 常规桌面软件
- 🚖 windows平台
![](./public/images/example/ee-win-home.png)
- 🚍 macOS平台
![](./public/images/example/ee-mac-home.png)
- 🚔 linux平台 - 国产UOS、Deepin
![](./public/images/example/uos-home.png)
- 🚔 linux平台 - ubuntu
![](./public/images/example/ubuntu-db.png)
### 🚐 2. vue、react、angular、web 转换成桌面软件
- 🚙 vue-ant-design本地
![](./public/images/example/vue-antd.png)
- 🚙 禅道项目管理web项目地址
![](./public/images/example/ee-project-7.png)
### 🚂 3. 游戏h5相关技术开发
- 🚊 忍者100层
![](./public/images/example/ee_game_1.png)
## 📒 开始使用
- ✒️ [安装文档](https://www.kaka996.com/pages/e64ff6/)
## 项目案例
- 🐟 框架已经应用于医疗、学校、政务、股票交易、ERP、娱乐、视频、企业等领域客户端
### 🐸 远控
- RQ Center
![](./public/images/example/rq-1.png)
![](./public/images/example/rq-2.png)
### 🐸 云盘
- FM Cloud
![](./public/images/example/fm-p2.png)
![](./public/images/example/fm-p1.png)
![](./public/images/example/fm-p4.png)
### 🐸 IM
- Cede IM
![](./public/images/example/im-p1.png)
![](./public/images/example/im-p5.png)
![](./public/images/example/im-p1.png)
### 🐸 壁纸
- warpar
![](./public/images/example/aw-3.png)
### 🐸 英雄联盟助手
- Serendlplty
![](./public/images/example/lol-zhanji.png)
### 🐸 更多
- [更多案例](https://www.kaka996.com/pages/eadf46/)
## 💬 交流
1. [讨论](https://www.kaka996.com/pages/c2720e/)
## 📌 关于pr
请前往[GitHub项目](https://github.com/dromara/electron-egg)提pr避免代码同步后pr被覆盖掉感谢
地址https://github.com/dromara/electron-egg
## 📔 框架核心包 ee-core
ee-core[https://github.com/wallace5303/ee-core](https://github.com/wallace5303/ee-core)
## 📚 Dromara 成员项目
<p align="center">
<a href="https://gitee.com/dromara/TLog" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/tlog2.png" title="一个轻量级的分布式日志标记追踪神器10分钟即可接入自动对日志打标签完成微服务的链路追踪" width="15%">
</a>
<a href="https://gitee.com/dromara/liteFlow" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/liteflow.png" title="轻量,快速,稳定,可编排的组件式流程引擎" width="15%">
</a>
<a href="https://hutool.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/hutool.jpg" title="小而全的Java工具类库使Java拥有函数式语言般的优雅让Java语言也可以“甜甜的”。" width="15%">
</a>
<a href="https://sa-token.dev33.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/sa-token.png" title="一个轻量级 java 权限认证框架,让鉴权变得简单、优雅!" width="15%">
</a>
<a href="https://gitee.com/dromara/hmily" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/hmily.png" title="高性能一站式分布式事务解决方案。" width="15%">
</a>
<a href="https://gitee.com/dromara/Raincat" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/raincat.png" title="强一致性分布式事务解决方案。" width="15%">
</a>
</p>
<p align="center">
<a href="https://gitee.com/dromara/myth" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/myth.png" title="可靠消息分布式事务解决方案。" width="15%">
</a>
<a href="https://cubic.jiagoujishu.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/cubic.png" title="一站式问题定位平台以agent的方式无侵入接入应用完整集成arthas功能模块致力于应用级监控帮助开发人员快速定位问题" width="15%">
</a>
<a href="https://maxkey.top/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/maxkey.png" title="业界领先的身份管理和认证产品" width="15%">
</a>
<a href="http://forest.dtflyx.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/forest-logo.png" title="Forest能够帮助您使用更简单的方式编写Java的HTTP客户端" width="15%">
</a>
<a href="https://jpom.io/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/jpom.png" title="一款简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件" width="15%">
</a>
<a href="https://su.usthe.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/sureness.png" title="面向 REST API 的高性能认证鉴权框架" width="15%">
</a>
</p>
<p align="center">
<a href="https://easy-es.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/easy-es2.png" title="傻瓜级ElasticSearch搜索引擎ORM框架" width="15%">
</a>
<a href="https://gitee.com/dromara/northstar" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/northstar_logo.png" title="Northstar盈富量化交易平台" width="15%">
</a>
<a href="https://hertzbeat.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/hertzbeat_brand.jpg" title="易用友好的云监控系统" width="15%">
</a>
<a href="https://plugins.sheng90.wang/fast-request/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/fast-request.gif" title="Idea 版 Postman为简化调试API而生" width="15%">
</a>
<a href="https://www.jeesuite.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/mendmix.png" title="开源分布式云原生架构一站式解决方案" width="15%">
</a>
<a href="https://gitee.com/dromara/koalas-rpc" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/koalas-rpc2.png" title="企业生产级百亿日PV高可用可拓展的RPC框架。" width="15%">
</a>
</p>
<p align="center">
<a href="https://async.sizegang.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/gobrs-async.png" title="配置极简功能强大的异步任务动态编排框架" width="15%">
</a>
<a href="https://dynamictp.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/dynamic-tp.png" title="基于配置中心的轻量级动态可监控线程池" width="15%">
</a>
<a href="https://www.x-easypdf.cn" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/x-easypdf.png" title="一个用搭积木的方式构建pdf的框架基于pdfbox" width="15%">
</a>
<a href="http://dromara.gitee.io/image-combiner" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/image-combiner.png" title="一个专门用于图片合成的工具,没有很复杂的功能,简单实用,却不失强大" width="15%">
</a>
<a href="https://www.herodotus.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/dante-cloud2.png" title="Dante-Cloud 是一款企业级微服务架构和服务能力开发平台。" width="15%">
</a>
<a href="https://dromara.org/zh/projects/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/dromara.png" title="让每一位开源爱好者,体会到开源的快乐。" width="15%">
</a>
</p>

View File

@@ -0,0 +1 @@
chrome应用商店ctx文件解压后放置在此目录中打包时会将资源加入安装包内。

View File

@@ -0,0 +1 @@
建议第三方软件放置在此目录中,打包时会将资源加入安装包内。

BIN
build/icons/256x256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
build/icons/32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
build/icons/512x512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

BIN
build/icons/64x64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

BIN
build/icons/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
build/icons/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

View File

199
cmd/bin.js Normal file
View File

@@ -0,0 +1,199 @@
/**
* ee-bin 配置
* 仅适用于开发环境
*/
module.exports = {
/**
* development serve ("frontend" "electron" )
* ee-bin dev
*/
dev: {
frontend: {
directory: './frontend',
cmd: 'npm',
args: ['run', 'dev'],
port: 8080,
},
electron: {
directory: './',
cmd: 'electron',
args: ['.', '--env=local'],
watch: true,
delay: 1000,
}
},
/**
* 构建
* ee-bin build
*/
build: {
frontend: {
directory: './frontend',
cmd: 'npm',
args: ['run', 'build'],
},
electron: {
type: 'typescript',
},
win64: {
cmd: 'electron-builder',
directory: './',
args: ['--config=./cmd/builder.json', '-w=nsis', '--x64'],
},
win32: {
args: ['--config=./cmd/builder.json', '-w=nsis', '--ia32'],
},
win_e: {
args: ['--config=./cmd/builder.json', '-w=portable', '--x64'],
},
win_7z: {
args: ['--config=./cmd/builder.json', '-w=7z', '--x64'],
},
mac: {
args: ['--config=./cmd/builder-mac.json', '-m'],
},
mac_arm64: {
args: ['--config=./cmd/builder-mac-arm64.json', '-m', '--arm64'],
},
linux: {
args: ['--config=./cmd/builder-linux.json', '-l=deb', '--x64'],
},
linux_arm64: {
args: ['--config=./cmd/builder-linux.json', '-l=deb', '--arm64'],
},
go_w: {
directory: './go',
cmd: 'go',
args: ['build', '-o=../build/extraResources/goapp.exe'],
},
go_m: {
directory: './go',
cmd: 'go',
args: ['build', '-o=../build/extraResources/goapp'],
},
go_l: {
directory: './go',
cmd: 'go',
args: ['build', '-o=../build/extraResources/goapp'],
},
python: {
directory: './python',
cmd: 'python',
args: ['./setup.py', 'build'],
},
},
/**
* 移动资源
* ee-bin move
*/
move: {
frontend_dist: {
src: './frontend/dist',
dest: './public/dist'
},
go_static: {
src: './frontend/dist',
dest: './go/public/dist'
},
go_config: {
src: './go/config',
dest: './go/public/config'
},
go_package: {
src: './package.json',
dest: './go/public/package.json'
},
go_images: {
src: './public/images',
dest: './go/public/images'
},
python_dist: {
src: './python/dist',
dest: './build/extraResources/py'
},
},
/**
* 预发布模式prod
* ee-bin start
*/
start: {
directory: './',
cmd: 'electron',
args: ['.', '--env=prod']
},
/**
* 加密
*/
encrypt: {
frontend: {
type: 'none',
files: [
'./public/dist/**/*.(js|json)',
],
cleanFiles: ['./public/dist'],
confusionOptions: {
compact: true,
stringArray: true,
stringArrayEncoding: ['none'],
stringArrayCallsTransform: true,
numbersToExpressions: true,
target: 'browser',
}
},
electron: {
type: 'confusion',
files: [
'./public/electron/**/*.(js|json)',
],
cleanFiles: ['./public/electron'],
specificFiles: [
'./public/electron/main.js',
'./public/electron/preload/bridge.js',
],
confusionOptions: {
compact: true,
stringArray: true,
stringArrayEncoding: ['none'],
deadCodeInjection: false,
stringArrayCallsTransform: true,
numbersToExpressions: true,
target: 'node',
}
}
},
/**
* 执行自定义命令
* ee-bin exec
*/
exec: {
// 单独调试air 实现 go 热重载
go: {
directory: './go',
cmd: 'air',
args: ['-c=config/.air.toml' ],
},
// windows 单独调试air 实现 go 热重载
go_w: {
directory: './go',
cmd: 'air',
args: ['-c=config/.air.windows.toml' ],
},
// 单独调试,以基础方式启动 go
go2: {
directory: './go',
cmd: 'go',
args: ['run', './main.go', '--env=dev','--basedir=../', '--port=7073'],
},
python: {
directory: './python',
cmd: 'python',
args: ['./main.py', '--port=7074'],
stdio: "inherit", // ignore
},
},
};

40
cmd/builder-linux.json Normal file
View File

@@ -0,0 +1,40 @@
{
"productName": "PQS9100工具箱",
"appId": "com.njcn.pqs9100.tool",
"copyright": "© 2025 njcn Technology Co., Ltd.",
"directories": {
"output": "out"
},
"asar": true,
"files": [
"**/*",
"!cmd/",
"!data/",
"!electron/",
"!frontend/",
"!logs/",
"!out/",
"!go/",
"!python/"
],
"extraResources": [
{
"from": "build/extraResources",
"to": "extraResources"
}
],
"publish": [
{
"provider": "generic",
"url": ""
}
],
"linux": {
"icon": "build/icons/icon.icns",
"artifactName": "${productName}-${os}-${version}-${arch}.${ext}",
"target": [
"deb"
],
"category": "Utility"
}
}

View File

@@ -0,0 +1,38 @@
{
"productName": "PQS9100工具箱",
"appId": "com.njcn.pqs9100.tool",
"copyright": "© 2025 njcn Technology Co., Ltd.",
"directories": {
"output": "out"
},
"asar": true,
"files": [
"**/*",
"!cmd/",
"!data/",
"!electron/",
"!frontend/",
"!logs/",
"!out/",
"!go/",
"!python/"
],
"extraResources": [
{
"from": "build/extraResources",
"to": "extraResources"
}
],
"publish": [
{
"provider": "generic",
"url": ""
}
],
"mac": {
"icon": "build/icons/icon.icns",
"artifactName": "${productName}-${os}-${version}-${arch}.${ext}",
"darkModeSupport": true,
"hardenedRuntime": false
}
}

38
cmd/builder-mac.json Normal file
View File

@@ -0,0 +1,38 @@
{
"productName": "PQS9100工具箱",
"appId": "com.njcn.pqs9100.tool",
"copyright": "© 2025 njcn Technology Co., Ltd.",
"directories": {
"output": "out"
},
"asar": true,
"files": [
"**/*",
"!cmd/",
"!data/",
"!electron/",
"!frontend/",
"!logs/",
"!out/",
"!go/",
"!python/"
],
"extraResources": [
{
"from": "build/extraResources",
"to": "extraResources"
}
],
"publish": [
{
"provider": "generic",
"url": ""
}
],
"mac": {
"icon": "build/icons/icon.icns",
"artifactName": "${productName}-${os}-${version}-${arch}.${ext}",
"darkModeSupport": true,
"hardenedRuntime": false
}
}

50
cmd/builder.json Normal file
View File

@@ -0,0 +1,50 @@
{
"productName": "PQS9100工具箱",
"appId": "com.njcn.pqs9100.tool",
"copyright": "© 2025 njcn Technology Co., Ltd.",
"directories": {
"output": "out"
},
"asar": true,
"files": [
"**/*",
"!cmd/",
"!data/",
"!electron/",
"!frontend/",
"!logs/",
"!out/",
"!go/",
"!python/"
],
"extraResources": {
"from": "build/extraResources/",
"to": "extraResources"
},
"nsis": {
"oneClick": false,
"allowElevation": true,
"allowToChangeInstallationDirectory": true,
"installerIcon": "build/icons/icon.ico",
"uninstallerIcon": "build/icons/icon.ico",
"installerHeaderIcon": "build/icons/icon.ico",
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "PQS9100工具箱"
},
"publish": [
{
"provider": "generic",
"url": ""
}
],
"win": {
"icon": "build/icons/icon.ico",
"artifactName": "${productName}-${os}-${version}-${arch}.${ext}",
"target": [
{
"target": "nsis"
}
]
}
}

View File

@@ -0,0 +1,66 @@
import path from 'path';
import {getBaseDir} from 'ee-core/ps';
import {type AppConfig} from 'ee-core/config';
const config: () => AppConfig = () => {
return {
openDevTools: false,
singleLock: true,
windowsOption: {
title: 'PQS9100工具箱', // 软件标题
width: 980, // 软件窗口宽度
height: 650, // 软件窗口高度
minWidth: 800, // 软件窗口最小宽度
minHeight: 650, // 软件窗口最小高度
autoHideMenuBar: true, // 默认不显示菜单栏,
webPreferences: {
webSecurity: true,
contextIsolation: false,
nodeIntegration: true,
},
frame: true,
show: false,
icon: path.join(getBaseDir(), 'public', 'images', 'logo-32.png'),
},
logger: {
level: 'INFO',
outputJSON: false,
appLogName: 'pqs-9100_tool.log',
coreLogName: 'pqs-9100_tool-core.log',
errorLogName: 'pqs-9100_tool-error.log',
},
remote: {
enable: false,
url: '',
},
socketServer: {
enable: true,
port: 7070,
path: "/socket.io/",
connectTimeout: 45000,
pingTimeout: 30000,
pingInterval: 25000,
maxHttpBufferSize: 1e8,
transports: ["polling", "websocket"],
cors: {
origin: true,
},
channel: 'socket-channel',
},
httpServer: {
enable: true,
https: {
enable: false,
key: '/public/ssl/localhost+1.key',
cert: '/public/ssl/localhost+1.pem',
},
host: '127.0.0.1',
port: 7071,
},
mainServer: {
indexPath: '/public/dist/index.html',
}
};
};
export default config;

View File

@@ -0,0 +1,14 @@
import { type AppConfig } from 'ee-core/config';
const config: () => AppConfig = () => {
return {
openDevTools: {
mode: 'bottom'
},
jobs: {
messageLog: false
}
};
};
export default config;

View File

@@ -0,0 +1,9 @@
import { type AppConfig } from 'ee-core/config';
const config: () => AppConfig = () => {
return {
openDevTools: false,
};
};
export default config;

View File

@@ -0,0 +1,63 @@
import { crossService } from '../service/cross';
/**
* Cross
* @class
*/
class CrossController {
/**
* View process service information
*/
info() {
crossService.info();
return 'hello electron-egg';
}
/**
* Get service url
*/
async getUrl(args: { name: string }): Promise<string> {
const { name } = args;
const serverUrl = crossService.getUrl(name);
return serverUrl;
}
/**
* kill service
* By default (modifiable), killing the process will exit the electron application.
*/
async killServer(args: { type: string; name: string }): Promise<void> {
const { type, name } = args;
crossService.killServer(type, name);
return;
}
/**
* create service
*/
async createServer(args: { program: string }): Promise<void> {
const { program } = args;
if (program == 'go') {
crossService.createGoServer();
} else if (program == 'java') {
crossService.createJavaServer();
} else if (program == 'python') {
crossService.createPythonServer();
}
return;
}
/**
* Access the api for the cross service
*/
async requestApi(args: { name: string; urlPath: string; params: any }): Promise<any> {
const { name, urlPath, params} = args;
const data = await crossService.requestApi(name, urlPath, params);
return data;
}
}
CrossController.toString = () => '[class CrossController]';
export default CrossController;

View File

@@ -0,0 +1,63 @@
import { dialog } from 'electron';
import { getMainWindow } from 'ee-core/electron';
/**
* effect - demo
* @class
*/
class EffectController {
/**
* select file
*/
selectFile(): string | null {
const filePaths = dialog.showOpenDialogSync({
properties: ['openFile']
});
if (!filePaths) {
return null
}
return filePaths[0];
}
/**
* login window
*/
loginWindow(args: { width?: number; height?: number }): void {
const { width, height } = args;
const win = getMainWindow();
const size = {
width: width || 400,
height: height || 300
}
win.setSize(size.width, size.height);
win.setResizable(true);
win.center();
win.show();
win.focus();
}
/**
* restore window
*/
restoreWindow(args: { width?: number; height?: number }): void {
const { width, height } = args;
const win = getMainWindow();
const size = {
width: width || 980,
height: height || 650
}
win.setSize(size.width, size.height);
win.setResizable(true);
win.center();
win.show();
win.focus();
}
}
EffectController.toString = () => '[class EffectController]';
export default EffectController;

View File

@@ -0,0 +1,16 @@
/**
* example
* @class
*/
class ExampleController {
/**
* test
*/
async test(): Promise<string> {
return 'hello electron-egg';
}
}
ExampleController.toString = () => '[class ExampleController]';
export default ExampleController;

167
electron/controller/os.ts Normal file
View File

@@ -0,0 +1,167 @@
import fs from 'fs';
import path from 'path';
import { app as electronApp, dialog, shell } from 'electron';
import { windowService } from '../service/os/window';
/**
* example
* @class
*/
class OsController {
/**
* All methods receive two parameters
* @param args Parameters transmitted by the frontend
* @param event - Event are only available during IPC communication. For details, please refer to the controller documentation
*/
/**
* Message prompt dialog box
*/
messageShow(): string {
dialog.showMessageBoxSync({
type: 'info', // "none", "info", "error", "question" 或者 "warning"
title: 'Custom Title',
message: 'Customize message content',
detail: 'Other additional information'
})
return 'Opened the message box';
}
/**
* Message prompt and confirmation dialog box
*/
messageShowConfirm(): string {
const res = dialog.showMessageBoxSync({
type: 'info',
title: 'Custom Title',
message: 'Customize message content',
detail: 'Other additional information',
cancelId: 1, // Index of buttons used to cancel dialog boxes
defaultId: 0, // Set default selected button
buttons: ['confirm', 'cancel'],
})
let data = (res === 0) ? 'click the confirm button' : 'click the cancel button';
return data;
}
/**
* Select Directory
*/
selectFolder() {
const filePaths = dialog.showOpenDialogSync({
properties: ['openDirectory', 'createDirectory']
});
if (!filePaths) {
return ""
}
return filePaths[0];
}
/**
* open directory
*/
openDirectory(args: { id: any }): boolean {
const { id } = args;
if (!id) {
return false;
}
let dir = '';
if (path.isAbsolute(id)) {
dir = id;
} else {
dir = electronApp.getPath(id);
}
shell.openPath(dir);
return true;
}
/**
* Select Picture
*/
selectPic(): string | null {
const filePaths = dialog.showOpenDialogSync({
title: 'select pic',
properties: ['openFile'],
filters: [
{ name: 'Images', extensions: ['jpg', 'png', 'gif'] },
]
});
if (!filePaths) {
return null
}
try {
const data = fs.readFileSync(filePaths[0]);
const pic = 'data:image/jpeg;base64,' + data.toString('base64');
return pic;
} catch (err) {
console.error(err);
return null;
}
}
/**
* Open a new window
*/
createWindow(args: any): any {
const wcid = windowService.createWindow(args);
return wcid;
}
/**
* Get Window contents id
*/
getWCid(args: any): any {
const wcid = windowService.getWCid(args);
return wcid;
}
/**
* Realize communication between two windows through the transfer of the main process
*/
window1ToWindow2(args: any): void {
windowService.communicate(args);
return;
}
/**
* Realize communication between two windows through the transfer of the main process
*/
window2ToWindow1(args: any): void {
windowService.communicate(args);
return;
}
/**
* Create system notifications
*/
sendNotification(args: { title?: string; subtitle?: string; body?: string; silent?: boolean }, event: any): boolean {
const { title, subtitle, body, silent} = args;
const options: any = {};
if (title) {
options.title = title;
}
if (subtitle) {
options.subtitle = subtitle;
}
if (body) {
options.body = body;
}
if (silent !== undefined) {
options.silent = silent;
}
windowService.createNotification(options, event);
return true
}
}
OsController.toString = () => '[class OsController]';
export default OsController;

View File

@@ -0,0 +1,10 @@
import { logger } from 'ee-core/log';
/**
* Welcome function
*/
function welcome(): void {
logger.info('[child-process] [jobs/example/hello] welcome !');
}
export { welcome };

View File

@@ -0,0 +1,98 @@
import { logger } from 'ee-core/log';
import { isChildJob, exit } from 'ee-core/ps';
import { childMessage } from 'ee-core/message';
import { welcome } from './hello';
import { UserService } from '../../service/job/user';
import { sqlitedbService } from '../../service/database/sqlitedb';
/**
* example - TimerJob
* @class
*/
class TimerJob {
timer: NodeJS.Timeout | undefined;
timeoutTimer: NodeJS.Timeout | undefined;
number: number;
countdown: number;
constructor() {
this.timer = undefined;
this.timeoutTimer = undefined;
this.number = 0;
this.countdown = 10; // 倒计时
sqlitedbService.init();
}
/**
* handle() method is necessary and will be automatically called
* params transferred parameters
*/
async handle(params: any): Promise<void> {
logger.info("[child-process] TimerJob params: ", params);
const { jobId } = params;
// Use service in child process
// 1. Ensure that the service does not have Electron's API or dependencies, as Electron does not support them
const userService = new UserService();
userService.hello('job');
// Execute the task
this.number = 0;
this.countdown = 10;
this.doTimer(jobId);
// sqlite
const userList = await sqlitedbService.getAllTestDataSqlite();
logger.info('[child-process] Sqlite userList:', userList);
}
/**
* Pause the job
*/
async pause(jobId: string): Promise<void> {
logger.info("[child-process] Pause timerJob, jobId: ", jobId);
clearInterval(this.timer);
clearInterval(this.timeoutTimer);
}
/**
* Resume the job
*/
async resume(jobId: string, pid: number): Promise<void> {
logger.info("[child-process] Resume timerJob, jobId: ", jobId, ", pid: ", pid);
this.doTimer(jobId);
}
/**
* Run the task
*/
async doTimer(jobId) {
// Timer to simulate the task
const eventName = 'job-timer-progress-' + jobId;
this.timer = setInterval(() => {
welcome();
childMessage.send(eventName, {jobId, number: this.number, end: false});
this.number++;
this.countdown--;
}, 1000);
// Use setTimeout to simulate the task duration
this.timeoutTimer = setTimeout(() => {
// Stop the timer to simulate the task
clearInterval(this.timer);
// Task completed, reset the front-end display
childMessage.send(eventName, {jobId, number:0, pid:0, end: true});
// If it is a childJob task, call exit() to exit the process, otherwise it will stay in memory
// If it is a childPoolJob task, stay in memory and wait for the next business
if (isChildJob()) {
exit();
}
}, this.countdown * 1000)
}
}
TimerJob.toString = () => '[class TimerJob]';
export default TimerJob;

19
electron/main.ts Normal file
View File

@@ -0,0 +1,19 @@
import { ElectronEgg } from 'ee-core';
import { Lifecycle } from './preload/lifecycle';
import { preload } from './preload';
// New app
const app = new ElectronEgg();
// Register lifecycle
const life = new Lifecycle();
app.register("ready", life.ready);
app.register("electron-app-ready", life.electronAppReady);
app.register("window-ready", life.windowReady);
app.register("before-close", life.beforeClose);
// Register preload
app.register("preload", preload);
// Run
app.run();

View File

@@ -0,0 +1,16 @@
/*
* 如果启用了上下文隔离渲染进程无法使用electron的api
* 可通过contextBridge 导出api给渲染进程使用
*/
import { type IpcRenderer, contextBridge, ipcRenderer } from 'electron';
// 确保contextBridge.exposeInMainWorld的参数类型正确这里进行简单的类型定义示例
type ElectronApi = {
ipcRenderer: IpcRenderer;
};
const ele: ElectronApi = {
ipcRenderer,
};
contextBridge.exposeInMainWorld('electron', ele);

21
electron/preload/index.ts Normal file
View File

@@ -0,0 +1,21 @@
/**
* Preload module, this file will be loaded when the program starts.
*/
import {logger} from 'ee-core/log';
import {trayService} from '../service/os/tray';
import {securityService} from '../service/os/security';
import {autoUpdaterService} from '../service/os/auto_updater';
function preload(): void {
// Example feature module, optional to use and modify
logger.info('[preload] load 5');
trayService.create();
securityService.create();
autoUpdaterService.create();
}
/**
* Entry point of the preload module
*/
export { preload };

View File

@@ -0,0 +1,70 @@
import { app as electronApp, screen } from 'electron';
import { logger } from 'ee-core/log';
import { getConfig } from 'ee-core/config';
import { getMainWindow } from 'ee-core/electron';
class Lifecycle {
/**
* Core app has been loaded
*/
async ready(): Promise<void> {
logger.info('[lifecycle] ready');
}
/**
* Electron app is ready
*/
async electronAppReady(): Promise<void> {
logger.info('[lifecycle] electron-app-ready');
// When double clicking the icon, display the opened window
electronApp.on('second-instance', () => {
const win = getMainWindow();
if (win.isMinimized()) {
win.restore();
}
win.show();
win.focus();
});
}
/**
* Main window has been loaded
*/
async windowReady(): Promise<void> {
logger.info('[lifecycle] window-ready');
const win = getMainWindow();
// The window is centered and scaled proportionally
// Obtain the size information of the main screen, calculate the width and height of the window as a percentage of the screen,
// and calculate the coordinates of the upper left corner when the window is centered
const mainScreen = screen.getPrimaryDisplay();
const { width, height } = mainScreen.workAreaSize;
const windowWidth = Math.floor(width * 0.6);
const windowHeight = Math.floor(height * 0.8);
const x = Math.floor((width - windowWidth) / 2);
const y = Math.floor((height - windowHeight) / 2);
win.setBounds({ x, y, width: windowWidth, height: windowHeight });
// Delay loading, no white screen
const config = getConfig();
const { windowsOption } = config;
if (windowsOption?.show == false) {
win.once('ready-to-show', () => {
win.show();
win.focus();
});
}
}
/**
* Before app close
*/
async beforeClose(): Promise<void> {
logger.info('[lifecycle] before-close');
}
}
Lifecycle.toString = () => '[class Lifecycle]';
export { Lifecycle };

144
electron/service/cross.ts Normal file
View File

@@ -0,0 +1,144 @@
import { logger } from 'ee-core/log';
import { getExtraResourcesDir, getLogDir } from 'ee-core/ps';
import path from 'path';
import axios from 'axios';
import { is } from 'ee-core/utils';
import { cross } from 'ee-core/cross';
/**
* cross
* @class
*/
class CrossService {
info(): string {
const pids = cross.getPids();
logger.info('cross pids:', pids);
let num = 1;
pids.forEach(pid => {
let entity = cross.getProc(pid);
logger.info(`server-${num} name:${entity.name}`);
logger.info(`server-${num} config:`, entity.config);
num++;
})
return 'hello electron-egg';
}
getUrl(name: string): string {
const serverUrl = cross.getUrl(name);
return serverUrl;
}
killServer(type: string, name: string): void {
if (type == 'all') {
cross.killAll();
} else {
cross.killByName(name);
}
}
/**
* create go service
* In the default configuration, services can be started with applications.
* Developers can turn off the configuration and create it manually.
*/
async createGoServer(): Promise<void> {
// method 1: Use the default Settings
//const entity = await cross.run(serviceName);
// method 2: Use custom configuration
const serviceName = "go";
const opt = {
name: 'goapp',
cmd: path.join(getExtraResourcesDir(), 'goapp'),
directory: getExtraResourcesDir(),
args: ['--port=7073'],
appExit: true,
}
const entity = await cross.run(serviceName, opt);
logger.info('server name:', entity.name);
logger.info('server config:', entity.config);
logger.info('server url:', entity.getUrl());
}
/**
* create java server
*/
async createJavaServer(): Promise<void> {
const serviceName = "java";
const jarPath = path.join(getExtraResourcesDir(), 'java-app.jar');
const opt = {
name: 'javaapp',
cmd: path.join(getExtraResourcesDir(), 'jre1.8.0_201/bin/javaw.exe'),
directory: getExtraResourcesDir(),
args: ['-jar', '-server', '-Xms512M', '-Xmx512M', '-Xss512k', '-Dspring.profiles.active=prod', `-Dserver.port=18080`, `-Dlogging.file.path=${getLogDir()}`, `${jarPath}`],
appExit: false,
}
if (is.macOS()) {
// Setup Java program
opt.cmd = path.join(getExtraResourcesDir(), 'jre1.8.0_201.jre/Contents/Home/bin/java');
}
if (is.linux()) {
// Setup Java program
}
const entity = await cross.run(serviceName, opt);
logger.info('server name:', entity.name);
logger.info('server config:', entity.config);
logger.info('server url:', cross.getUrl(entity.name));
}
/**
* create python service
* In the default configuration, services can be started with applications.
* Developers can turn off the configuration and create it manually.
*/
async createPythonServer(): Promise<void> {
// method 1: Use the default Settings
//const entity = await cross.run(serviceName);
// method 2: Use custom configuration
const serviceName = "python";
const opt = {
name: 'pyapp',
cmd: path.join(getExtraResourcesDir(), 'py', 'pyapp'),
directory: path.join(getExtraResourcesDir(), 'py'),
args: ['--port=7074'],
windowsExtname: true,
appExit: true,
}
const entity = await cross.run(serviceName, opt);
logger.info('server name:', entity.name);
logger.info('server config:', entity.config);
logger.info('server url:', entity.getUrl());
}
async requestApi(name: string, urlPath: string, params: any): Promise<any> {
const serverUrl = cross.getUrl(name);
const apiHello = serverUrl + urlPath;
console.log('Server Url:', serverUrl);
const response = await axios({
method: 'get',
url: apiHello,
timeout: 1000,
params,
proxy: false,
});
if (response.status == 200) {
const { data } = response;
return data;
}
return null;
}
}
CrossService.toString = () => '[class CrossService]';
const crossService = new CrossService();
export {
CrossService,
crossService
};

View File

@@ -0,0 +1,23 @@
import { logger } from 'ee-core/log';
// effect service
class EffectService {
// hello
async hello(args: any): Promise<{ status: string; params: any }> {
let obj = {
status:'ok',
params: args
}
logger.info('EffectService obj:', obj);
return obj;
}
}
EffectService.toString = () => '[class EffectService]';
const effectService = new EffectService();
export {
EffectService,
effectService
}

View File

@@ -0,0 +1,21 @@
import { logger } from 'ee-core/log';
// example service
class ExampleService {
async test(args: any): Promise<{ status: string; params: any }> {
let obj = {
status:'ok',
params: args
}
logger.info('ExampleService obj:', obj);
return obj;
}
}
ExampleService.toString = () => '[class ExampleService]';
const exampleService = new ExampleService();
export {
ExampleService,
exampleService
};

View File

@@ -0,0 +1,19 @@
import { logger } from 'ee-core/log';
/**
* UserService class
*/
class UserService {
async hello(args: any): Promise<{ status: string; params: any }> {
const obj = {
status: 'ok',
params: args,
};
logger.info('UserService obj:', obj);
return obj;
}
}
UserService.toString = () => '[class UserService]';
export { UserService };

View File

@@ -0,0 +1,177 @@
import { app as electronApp } from 'electron';
import { autoUpdater } from 'electron-updater';
import { is } from 'ee-core/utils';
import { logger } from 'ee-core/log';
import { getMainWindow, setCloseAndQuit } from 'ee-core/electron';
/**
* AutoUpdaterService class for automatic updates
*/
class AutoUpdaterService {
private config: {
windows: boolean;
macOS: boolean;
linux: boolean;
options: any;
};
constructor() {
this.config = {
windows: false,
macOS: false,
linux: false,
options: {
provider: 'generic',
url: 'http://kodo.qiniu.com/'
},
}
}
/**
* Create and configure the auto updater
*/
create(): void {
logger.info('[autoUpdater] load');
const cfg = this.config;
if ((is.windows() && cfg.windows) ||
(is.macOS() && cfg.macOS) ||
(is.linux() && cfg.linux)) {
// continue
} else {
return;
}
const status = {
error: -1,
available: 1,
noAvailable: 2,
downloading: 3,
downloaded: 4,
};
const version = electronApp.getVersion();
logger.info('[autoUpdater] current version: ', version);
// Set the download server address
let server = cfg.options.url;
const lastChar = server.substring(server.length - 1);
server = lastChar === '/' ? server : server + "/";
cfg.options.url = server;
try {
autoUpdater.setFeedURL(cfg.options);
} catch (error) {
logger.error('[autoUpdater] setFeedURL error : ', error);
}
autoUpdater.on('checking-for-update', () => {
// sendStatusToWindow('正在检查更新...');
});
autoUpdater.on('update-available', () => {
const data = {
status: status.available,
desc: '有可用更新',
};
this.sendStatusToWindow(data);
});
autoUpdater.on('update-not-available', () => {
const data = {
status: status.noAvailable,
desc: '没有可用更新',
};
this.sendStatusToWindow(data);
});
autoUpdater.on('error', (err) => {
const data = {
status: status.error,
desc: err,
};
this.sendStatusToWindow(data);
});
autoUpdater.on('download-progress', (progressObj) => {
const percentNumber = progressObj.percent;
const totalSize = this.bytesChange(progressObj.total);
const transferredSize = this.bytesChange(progressObj.transferred);
let text = '已下载 ' + percentNumber + '%';
text = text + ' (' + transferredSize + "/" + totalSize + ')';
const data = {
status: status.downloading,
desc: text,
percentNumber,
totalSize,
transferredSize,
};
logger.info('[addon:autoUpdater] progress: ', text);
this.sendStatusToWindow(data);
});
autoUpdater.on('update-downloaded', () => {
const data = {
status: status.downloaded,
desc: '下载完成',
};
this.sendStatusToWindow(data);
// Allow the window to close
setCloseAndQuit(true);
// Install updates and exit the application
autoUpdater.quitAndInstall();
});
}
/**
* Check for updates
*/
checkUpdate(): void {
autoUpdater.checkForUpdates();
}
/**
* Download updates
*/
download(): void {
autoUpdater.downloadUpdate();
}
/**
* Send status to the frontend
*/
sendStatusToWindow(content: any = {}): void {
const textJson = JSON.stringify(content);
const channel = 'custom/app/updater';
const win = getMainWindow();
win.webContents.send(channel, textJson);
}
/**
* Convert bytes to a more readable format
*/
bytesChange(limit: number): string {
let size = "";
if (limit < 0.1 * 1024) {
size = limit.toFixed(2) + "B";
} else if (limit < 0.1 * 1024 * 1024) {
size = (limit / 1024).toFixed(2) + "KB";
} else if (limit < 0.1 * 1024 * 1024 * 1024) {
size = (limit / (1024 * 1024)).toFixed(2) + "MB";
} else {
size = (limit / (1024 * 1024 * 1024)).toFixed(2) + "GB";
}
let sizeStr = size + "";
let index = sizeStr.indexOf(".");
let dou = sizeStr.substring(index + 1, index + 3);
if (dou === "00") {
return sizeStr.substring(0, index) + sizeStr.substring(index + 3, index + 5);
}
return size;
}
}
AutoUpdaterService.toString = () => '[class AutoUpdaterService]';
const autoUpdaterService = new AutoUpdaterService();
export {
AutoUpdaterService,
autoUpdaterService
};

View File

@@ -0,0 +1,15 @@
const {getMainWindow} = require("ee-core/electron");
class IconService {
update(iconPath) {
const win = getMainWindow();
win.setIcon(iconPath);
}
}
module.exports = {
iconService: new IconService()
};

View File

@@ -0,0 +1,31 @@
import { logger } from 'ee-core/log';
import { app as electronApp } from 'electron';
/**
* SecurityService class for handling security-related operations
*/
class SecurityService {
/**
* Create and configure the security service
*/
create(): void {
logger.info('[security] load');
const runWithDebug = process.argv.find((e) => {
const isHasDebug = e.includes('--inspect') || e.includes('--inspect-brk') || e.includes('--remote-debugging-port');
return isHasDebug;
});
// Do not allow remote debugging
if (runWithDebug) {
logger.error('[error] Remote debugging is not allowed, runWithDebug:', runWithDebug);
electronApp.quit();
}
}
}
SecurityService.toString = () => '[class SecurityService]';
const securityService = new SecurityService();
export {
SecurityService,
securityService
};

View File

@@ -0,0 +1,81 @@
import { Tray, Menu } from 'electron';
import path from 'path';
import { isDev, getBaseDir } from 'ee-core/ps';
import { logger } from 'ee-core/log';
import { app as electronApp } from 'electron';
import { getMainWindow, getCloseAndQuit, setCloseAndQuit } from 'ee-core/electron';
/**
* 托盘
* @class
*/
class TrayService {
tray: Tray | null;
config: {
title: string;
icon: string;
}
constructor() {
this.tray = null;
this.config = {
title: 'electron-egg',
icon: '/public/images/tray.png',
}
}
/**
* Create the tray icon
*/
create () {
logger.info('[tray] load');
const cfg = this.config;
const mainWindow = getMainWindow();
// tray icon
const iconPath = path.join(getBaseDir(), cfg.icon);
// Tray menu items
const trayMenuTemplate = [
{
label: '显示',
click: function () {
mainWindow.show();
}
},
{
label: '退出',
click: function () {
electronApp.quit();
}
}
]
// Set a flag to minimize to tray instead of closing
setCloseAndQuit(false);
mainWindow.on('close', (event: any) => {
if (getCloseAndQuit()) {
return;
}
mainWindow.hide();
event.preventDefault();
});
// Initialize the tray
this.tray = new Tray(iconPath);
this.tray.setToolTip(cfg.title);
const contextMenu = Menu.buildFromTemplate(trayMenuTemplate);
this.tray.setContextMenu(contextMenu);
// Show the main window when the tray icon is clicked
this.tray.on('click', () => {
mainWindow.show()
})
}
}
TrayService.toString = () => '[class TrayService]';
const trayService = new TrayService();
export {
trayService
}

View File

@@ -0,0 +1,131 @@
import path from 'path';
import { BrowserWindow, Notification } from 'electron';
import { getMainWindow } from 'ee-core/electron';
import { isProd, getBaseDir } from 'ee-core/ps';
import { getConfig } from 'ee-core/config';
import { isFileProtocol } from 'ee-core/utils';
import { logger } from 'ee-core/log';
/**
* Window
* @class
*/
class WindowService {
myNotification: Notification | null;
windows: { [key: string]: BrowserWindow };
constructor() {
this.myNotification = null;
this.windows = {}
}
/**
* Create a new window
*/
createWindow(args: { type: string; content: string; windowName: string; windowTitle: string }): number {
const { type, content, windowName, windowTitle } = args;
let contentUrl: string = '';
if (type == 'html') {
contentUrl = path.join('file://', getBaseDir(), content)
} else if (type == 'web') {
contentUrl = content;
} else if (type == 'vue') {
let addr = 'http://localhost:8080'
if (isProd()) {
const { mainServer } = getConfig();
if (mainServer && mainServer.protocol && isFileProtocol(mainServer.protocol)) {
addr = mainServer.protocol + path.join(getBaseDir(), mainServer.indexPath);
}
}
contentUrl = addr + content;
}
logger.info('[createWindow] url: ', contentUrl);
const opt = {
title: windowTitle,
x: 10,
y: 10,
width: 980,
height: 650,
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
},
}
const win = new BrowserWindow(opt);
const winContentsId = win.webContents.id;
win.loadURL(contentUrl);
win.webContents.openDevTools();
this.windows[windowName] = win;
return winContentsId;
}
/**
* Get window contents id
*/
getWCid(args: { windowName: string }): number {
const { windowName } = args;
let win: BrowserWindow;
if (windowName == 'main') {
win = getMainWindow();
} else {
win = this.windows[windowName];
}
return win.webContents.id;
}
/**
* Realize communication between two windows through the transfer of the main process
*/
communicate(args: { receiver: string; content: any }): void {
const { receiver, content } = args;
if (receiver == 'main') {
const win = getMainWindow();
win.webContents.send('controller/os/window2ToWindow1', content);
} else if (receiver == 'window2') {
const win = this.windows[receiver];
win.webContents.send('controller/os/window1ToWindow2', content);
}
}
/**
* createNotification
*/
createNotification(options: any, event: any): void {
const channel = 'controller/os/sendNotification';
this.myNotification = new Notification(options);
if (options.clickEvent) {
this.myNotification.on('click', () => {
let data = {
type: 'click',
msg: '您点击了通知消息'
}
event.reply(`${channel}`, data)
});
}
if (options.closeEvent) {
this.myNotification.on('close', () => {
let data = {
type: 'close',
msg: '您关闭了通知消息'
}
event.reply(`${channel}`, data)
});
}
this.myNotification.show();
}
}
WindowService.toString = () => '[class WindowService]';
const windowService = new WindowService();
export {
WindowService,
windowService
}

14
frontend/.editorconfig Normal file
View File

@@ -0,0 +1,14 @@
# https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

View File

@@ -0,0 +1,4 @@
VITE_TITLE="NPQS9100-自动检测平台工具箱"
VITE_RSA_PUBLIC_KEY="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnFMmIVanMxsW5S/qP8Wcxf/J3/i4631BP3UtWkRzO7jAw9HIAgK4Y7X53hXj6zMbfme1vMjQc0mq7m/KrH4WlTYpFexLO6Gnk8oH40F04tp+ABZIq93zNOydPEaVoZeTPH/LlkwrrxVGAMNNIKuebcqapp25JiWtlSFMv4kH/nDAj+2m8+P4zYVM1Ed6gO01eKDEYE3SBA1Ket2BfHTgviR/F8WKwlXh11enywsJnrHTM5dJQdlUxCjHy214TpheYOz/cv9elQnDfFAbmZW8mH5/hgMSTkm3h4uR7ITin6Erg+yc/t1kGaTWrzloyBRMSiFN/Pwr5yQjj+1wQqqUkwIDAQAB"
VITE_RSA_PRIVATE_KEY="MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCcUyYhVqczGxblL+o/xZzF/8nf+LjrfUE/dS1aRHM7uMDD0cgCArhjtfneFePrMxt+Z7W8yNBzSarub8qsfhaVNikV7Es7oaeTygfjQXTi2n4AFkir3fM07J08RpWhl5M8f8uWTCuvFUYAw00gq55typqmnbkmJa2VIUy/iQf+cMCP7abz4/jNhUzUR3qA7TV4oMRgTdIEDUp63YF8dOC+JH8XxYrCVeHXV6fLCwmesdMzl0lB2VTEKMfLbXhOmF5g7P9y/16VCcN8UBuZlbyYfn+GAxJOSbeHi5HshOKfoSuD7Jz+3WQZpNavOWjIFExKIU38/CvnJCOP7XBCqpSTAgMBAAECggEAYeWokWRE3TpvwiOZnUpR/aVMdVi75a3ROL5XIpqPV61B+t/bU3cEpl0GF9C5pUeiRi0IoStZb3mI9D1KPW/REKyUWkhabQO1gFYbTnRlkNOn6MILzKX4cwJjDaZeeo4EBPU7N+qHyOOXrU6hdH5FfxhMdV983ajm5eeuupxER1C2kAcIklTeVpTX6EKOgZb5LBp5ssOVm2P42pOauvcRozRcvZmqnErXmukv0H4l3EVNt4rHpTn9riHUC63e8JfiYzVaF6zuNUxv6nHEft0/SRMw11XSTnNfDzcKqgjz6ksFBS/6eQQYKESk+ONC53HUuYHFAknkwsPupDCT2W8FIQKBgQDLHT/xCU3nxGr4vFKBDNaO2D5oK20ECbBO4oDvLWWmQG7f+6TsMy8PgVdMnoL4RfqGlwFAKEpS6KVFHnBVqnNEhcdy9uCI7x7Xx8UnyUtxj1EDTm76uta9Ki9OrlqB6tImDM9+Ya3vGktW37ht4WOx2OsJRhG1dbf6RLwFlH7DWwKBgQDFBxvi5I1BR6hg6Tj7xd2SqOT2Y+BED3xuSYENhWbmMhLJDResaB7mjztbxlYaY2mOE0holWm2uDmVFFhMh4jYXik4hYH8nmDzq9mDpZCZ9pyjYqnAP8THoAa8EbgrUWB8A6BPH4iL3KbMnBfBKY0pIr2xrvnjQjNBAgta7KDRKQKBgCe6oe4wxrdF2TKsC2tIqpMoQxS3Icy/ZGgZr+SYuaBKTCWtoDW/UT40K3JGMxIDBhzbXphBCUCsVt9tM8Xd4EwP6tJW7dZ7B0pnve2pVwNwaAVAiz6p2yUHIle+jN+Koe5lZRSwYIg7WW81tWpwwsJfzqFyvjYDP6hJV4mz4ROvAoGAaRcdnKvjXApomShMqJ4lTPChD3q+SA8qg3jZSOj6tZXHx00gb2kp8jg7pPvpOTIFPy6x1Ha9aCRjMk0ju84fA6lVuzwa1S907wOehUVuF3Eeo1cgy9Y3k3KbpPyeixxgpkUY4JslLdSHc2NemD0dee951qhJyRmqVOZOQDUuoeECgYEAqBw2cAFk3vM97WY06TSldGA8ajVHx3BYRjj+zl62NTQthy8fw3tqxb3c5e8toOmZWKjZvDhg2TRLhsDDQWEYg3LZG87REqVIjgEPcpjNLidjygGX8n3JF2o0O5I/EMvl0s/+LVQONfduOBvhwDqr8QNisbLsyneiAq7umewMolo="

3
frontend/.env.production Normal file
View File

@@ -0,0 +1,3 @@
VITE_TITLE="NPQS9100-自动检测平台工具箱"
VITE_RSA_PUBLIC_KEY="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnFMmIVanMxsW5S/qP8Wcxf/J3/i4631BP3UtWkRzO7jAw9HIAgK4Y7X53hXj6zMbfme1vMjQc0mq7m/KrH4WlTYpFexLO6Gnk8oH40F04tp+ABZIq93zNOydPEaVoZeTPH/LlkwrrxVGAMNNIKuebcqapp25JiWtlSFMv4kH/nDAj+2m8+P4zYVM1Ed6gO01eKDEYE3SBA1Ket2BfHTgviR/F8WKwlXh11enywsJnrHTM5dJQdlUxCjHy214TpheYOz/cv9elQnDfFAbmZW8mH5/hgMSTkm3h4uR7ITin6Erg+yc/t1kGaTWrzloyBRMSiFN/Pwr5yQjj+1wQqqUkwIDAQAB"
VITE_RSA_PRIVATE_KEY="MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCcUyYhVqczGxblL+o/xZzF/8nf+LjrfUE/dS1aRHM7uMDD0cgCArhjtfneFePrMxt+Z7W8yNBzSarub8qsfhaVNikV7Es7oaeTygfjQXTi2n4AFkir3fM07J08RpWhl5M8f8uWTCuvFUYAw00gq55typqmnbkmJa2VIUy/iQf+cMCP7abz4/jNhUzUR3qA7TV4oMRgTdIEDUp63YF8dOC+JH8XxYrCVeHXV6fLCwmesdMzl0lB2VTEKMfLbXhOmF5g7P9y/16VCcN8UBuZlbyYfn+GAxJOSbeHi5HshOKfoSuD7Jz+3WQZpNavOWjIFExKIU38/CvnJCOP7XBCqpSTAgMBAAECggEAYeWokWRE3TpvwiOZnUpR/aVMdVi75a3ROL5XIpqPV61B+t/bU3cEpl0GF9C5pUeiRi0IoStZb3mI9D1KPW/REKyUWkhabQO1gFYbTnRlkNOn6MILzKX4cwJjDaZeeo4EBPU7N+qHyOOXrU6hdH5FfxhMdV983ajm5eeuupxER1C2kAcIklTeVpTX6EKOgZb5LBp5ssOVm2P42pOauvcRozRcvZmqnErXmukv0H4l3EVNt4rHpTn9riHUC63e8JfiYzVaF6zuNUxv6nHEft0/SRMw11XSTnNfDzcKqgjz6ksFBS/6eQQYKESk+ONC53HUuYHFAknkwsPupDCT2W8FIQKBgQDLHT/xCU3nxGr4vFKBDNaO2D5oK20ECbBO4oDvLWWmQG7f+6TsMy8PgVdMnoL4RfqGlwFAKEpS6KVFHnBVqnNEhcdy9uCI7x7Xx8UnyUtxj1EDTm76uta9Ki9OrlqB6tImDM9+Ya3vGktW37ht4WOx2OsJRhG1dbf6RLwFlH7DWwKBgQDFBxvi5I1BR6hg6Tj7xd2SqOT2Y+BED3xuSYENhWbmMhLJDResaB7mjztbxlYaY2mOE0holWm2uDmVFFhMh4jYXik4hYH8nmDzq9mDpZCZ9pyjYqnAP8THoAa8EbgrUWB8A6BPH4iL3KbMnBfBKY0pIr2xrvnjQjNBAgta7KDRKQKBgCe6oe4wxrdF2TKsC2tIqpMoQxS3Icy/ZGgZr+SYuaBKTCWtoDW/UT40K3JGMxIDBhzbXphBCUCsVt9tM8Xd4EwP6tJW7dZ7B0pnve2pVwNwaAVAiz6p2yUHIle+jN+Koe5lZRSwYIg7WW81tWpwwsJfzqFyvjYDP6hJV4mz4ROvAoGAaRcdnKvjXApomShMqJ4lTPChD3q+SA8qg3jZSOj6tZXHx00gb2kp8jg7pPvpOTIFPy6x1Ha9aCRjMk0ju84fA6lVuzwa1S907wOehUVuF3Eeo1cgy9Y3k3KbpPyeixxgpkUY4JslLdSHc2NemD0dee951qhJyRmqVOZOQDUuoeECgYEAqBw2cAFk3vM97WY06TSldGA8ajVHx3BYRjj+zl62NTQthy8fw3tqxb3c5e8toOmZWKjZvDhg2TRLhsDDQWEYg3LZG87REqVIjgEPcpjNLidjygGX8n3JF2o0O5I/EMvl0s/+LVQONfduOBvhwDqr8QNisbLsyneiAq7umewMolo="

6
frontend/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
package-lock.json

105
frontend/index.html Normal file
View File

@@ -0,0 +1,105 @@
<!DOCTYPE html>
<html lang="zh-CN">
<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, user-scalable=no, maximum-scale=1.0, minimum-scale=1.0" />
<title></title>
<!-- 优化vue渲染未完成之前先加一个css动画 -->
<style>
#loadingPage {
background-color: #dedede;
font-size: 12px;
}
.base {
height: 9em;
left: 50%;
margin: -7.5em;
padding: 3em;
position: absolute;
top: 50%;
width: 9em;
transform: rotateX(45deg) rotateZ(45deg);
transform-style: preserve-3d;
}
.cube,
.cube:after,
.cube:before {
content: '';
float: left;
height: 3em;
position: absolute;
width: 3em;
}
/* Top */
.cube {
background-color: #06cf68;
position: relative;
transform: translateZ(3em);
transform-style: preserve-3d;
transition: .25s;
box-shadow: 13em 13em 1.5em rgba(0, 0, 0, 0.1);
animation: anim 1s infinite;
}
.cube:after {
background-color: #05a151;
transform: rotateX(-90deg) translateY(3em);
transform-origin: 100% 100%;
}
.cube:before {
background-color: #026934;
transform: rotateY(90deg) translateX(3em);
transform-origin: 100% 0;
}
.cube:nth-child(1) {
animation-delay: 0.05s;
}
.cube:nth-child(2) {
animation-delay: 0.1s;
}
.cube:nth-child(3) {
animation-delay: 0.15s;
}
.cube:nth-child(4) {
animation-delay: 0.2s;
}
.cube:nth-child(5) {
animation-delay: 0.25s;
}
.cube:nth-child(6) {
animation-delay: 0.3s;
}
.cube:nth-child(7) {
animation-delay: 0.35s;
}
.cube:nth-child(8) {
animation-delay: 0.4s;
}
.cube:nth-child(9) {
animation-delay: 0.45s;
}
@keyframes anim {
50% {
transform: translateZ(0.5em);
}
}
</style>
</head>
<body>
<div id="loadingPage">
<div class='base'>
<div class='cube'></div>
<div class='cube'></div>
<div class='cube'></div>
<div class='cube'></div>
<div class='cube'></div>
<div class='cube'></div>
<div class='cube'></div>
<div class='cube'></div>
<div class='cube'></div>
</div>
</div>
<div id="app"></div>
<script type="module" src="src/main.ts"></script>
</body>
</html>

38
frontend/package.json Normal file
View File

@@ -0,0 +1,38 @@
{
"name": "ee",
"version": "4.0.0",
"scripts": {
"dev": "vite --host --port 8080",
"serve": "vite --host --port 8080",
"build-staging": "vite build --mode staging",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@ant-design/icons-vue": "^6.1.0",
"ant-design-vue": "^3.2.20",
"axios": "^1.12.2",
"jsencrypt": "^3.5.4",
"node-forge": "^1.3.1",
"pinia": "^3.0.3",
"socket.io-client": "^4.8.1",
"store2": "^2.14.4",
"vue": "^3.5.22",
"vue-router": "^4.6.2",
"vuex": "^4.1.0"
},
"devDependencies": {
"@types/node": "^20.16.0",
"@types/node-forge": "^1.3.14",
"@vitejs/plugin-vue": "^6.0.1",
"@vue/compiler-sfc": "^3.5.22",
"less": "^4.4.2",
"less-loader": "^12.3.0",
"postcss": "^8.5.6",
"postcss-pxtorem": "^6.1.0",
"terser": "^5.44.0",
"typescript": "^5.9.3",
"vite": "^6.4.0",
"vite-plugin-compression": "^0.5.1"
}
}

15
frontend/src/App.vue Normal file
View File

@@ -0,0 +1,15 @@
<template>
<router-view/>
</template>
<script setup lang="ts">
import {onMounted} from 'vue';
onMounted(() => {
const loadingElement = document.getElementById('loadingPage');
if (loadingElement) {
(loadingElement as HTMLElement).remove();
}
});
</script>
<style lang="less"></style>

84
frontend/src/api/index.ts Normal file
View File

@@ -0,0 +1,84 @@
/**
* Definition of communication channel between main process and rendering process
* formatcontroller/filename/method
* Definition of communication channels between main process and rendering process
*/
const ipcApiRoute = {
example: {
test: 'controller/example/test',
},
framework: {
checkForUpdater: 'controller/framework/checkForUpdater',
downloadApp: 'controller/framework/downloadApp',
jsondbOperation: 'controller/framework/jsondbOperation',
sqlitedbOperation: 'controller/framework/sqlitedbOperation',
uploadFile: 'controller/framework/uploadFile',
checkHttpServer: 'controller/framework/checkHttpServer',
doHttpRequest: 'controller/framework/doHttpRequest',
doSocketRequest: 'controller/framework/doSocketRequest',
ipcInvokeMsg: 'controller/framework/ipcInvokeMsg',
ipcSendSyncMsg: 'controller/framework/ipcSendSyncMsg',
ipcSendMsg: 'controller/framework/ipcSendMsg',
startJavaServer: 'controller/framework/startJavaServer',
closeJavaServer: 'controller/framework/closeJavaServer',
someJob: 'controller/framework/someJob',
timerJobProgress: 'controller/framework/timerJobProgress',
createPool: 'controller/framework/createPool',
createPoolNotice: 'controller/framework/createPoolNotice',
someJobByPool: 'controller/framework/someJobByPool',
hello: 'controller/framework/hello',
openSoftware: 'controller/framework/openSoftware',
},
// os
os: {
messageShow: 'controller/os/messageShow',
messageShowConfirm: 'controller/os/messageShowConfirm',
selectFolder: 'controller/os/selectFolder',
selectPic: 'controller/os/selectPic',
openDirectory: 'controller/os/openDirectory',
loadViewContent: 'controller/os/loadViewContent',
removeViewContent: 'controller/os/removeViewContent',
createWindow: 'controller/os/createWindow',
getWCid: 'controller/os/getWCid',
sendNotification: 'controller/os/sendNotification',
initPowerMonitor: 'controller/os/initPowerMonitor',
getScreen: 'controller/os/getScreen',
autoLaunch: 'controller/os/autoLaunch',
setTheme: 'controller/os/setTheme',
getTheme: 'controller/os/getTheme',
window1ToWindow2: 'controller/os/window1ToWindow2',
window2ToWindow1: 'controller/os/window2ToWindow1',
},
// effect
effect: {
selectFile: 'controller/effect/selectFile',
loginWindow: 'controller/effect/loginWindow',
restoreWindow: 'controller/effect/restoreWindow',
},
// cross
cross: {
crossInfo: 'controller/cross/info',
getCrossUrl: 'controller/cross/getUrl',
killCrossServer: 'controller/cross/killServer',
createCrossServer: 'controller/cross/createServer',
requestApi: 'controller/cross/requestApi',
}
}
/**
* Customize Channel
* Format: Custom (recommended to add a prefix)
*/
const specialIpcRoute = {
appUpdater: 'custom/app/updater', // updater channel
}
export {
ipcApiRoute,
specialIpcRoute
}

View File

@@ -0,0 +1,15 @@
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
/* 滚动条 */
::-webkit-scrollbar{width:8px;height:4px}
::-webkit-scrollbar-button{width:10px;height:0}
::-webkit-scrollbar-track{background:0 0}
::-webkit-scrollbar-thumb{background: #ecf3fb; border-radius: 4px;-webkit-transition:.3s;transition:.3s}
::-webkit-scrollbar-thumb:hover{background-color:#1890ff}
::-webkit-scrollbar-thumb:active{background-color:#1890ff}

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -0,0 +1,17 @@
@import 'ant-design-vue/dist/antd.less';
// 可自定义主题颜色
//@primary-color: #07C160; // 全局主色
@link-color: #1890ff; // 链接色
@success-color: #52c41a; // 成功色
@warning-color: #faad14; // 警告色
@error-color: #f5222d; // 错误色
@font-size-base: 14px; // 主字号
@heading-color: rgba(0, 0, 0, 0.85); // 标题色
@text-color: rgba(0, 0, 0, 0.65); // 主文本色
@text-color-secondary: rgba(0, 0, 0, 0.45); // 次文本色
@disabled-color: rgba(0, 0, 0, 0.25); // 失效色
@border-radius-base: 4px; // 组件/浮层圆角
@border-color-base: #dce3e8; // 边框色
@box-shadow-base: 0 2px 8px rgba(0, 0, 0, 0.15); // 浮层阴影

View File

@@ -0,0 +1,22 @@
import { createFromIconfontCN } from '@ant-design/icons-vue'
import { h, VNode } from 'vue'
const IconFont = createFromIconfontCN({
scriptUrl: 'https://at.alicdn.com/t/font_2456157_4ovzopz659q.js',
extraCommonProps: {
type: 'icon-fengche',
style: {
fontSize: '18px',
},
},
})
interface Props {
type?: string;
}
const DynamicIconFont = (props: Props): VNode => {
return h(IconFont, { type: props.type || 'icon-fengche' })
}
export default DynamicIconFont

View File

@@ -0,0 +1,19 @@
import iconFont from './iconFont';
// Use import.meta.globEager to dynamically import all .vue files in the directory
const modules: { [key: string]: { default: any } } = import.meta.glob('./*.vue', { eager: true });
// Create a map of component names to their default exports
const map: { [key: string]: any } = {};
Object.keys(modules).forEach(file => {
const moduleName = file.replace('./', '').replace('.vue', '');
map[moduleName] = modules[file].default;
});
// Combine the dynamically imported components with the iconFont component
const globalComponents = {
...map,
iconFont,
};
export default globalComponents;

View File

@@ -0,0 +1,112 @@
<template>
<a-layout has-sider id="app-layout-sider">
<a-layout-sider
v-model:collapsed="collapsed" collapsible
theme="light"
class="layout-sider"
>
<div class="logo">
<img src="@/assets/logo.png" class="pic-logo" />
<h4>PQS9100工具箱</h4>
</div>
<a-menu
theme="light"
mode="inline"
:selectedKeys="[current]"
@click="menuHandle"
>
<a-menu-item v-for="(menuInfo, index) in menu" :key="index">
<component :is="menuInfo.icon"></component>
<span>{{ menuInfo.title }}</span>
</a-menu-item>
</a-menu>
</a-layout-sider>
<a-layout :style="{ marginLeft: '200px' }">
<a-layout-content class="layout-content">
<router-view />
</a-layout-content>
</a-layout>
</a-layout>
</template>
<script setup lang="ts">
import {onMounted, ref} from 'vue';
import {useRouter} from 'vue-router';
import {FileProtectOutlined} from "@ant-design/icons-vue"; // 定义菜单项的类型
// 定义菜单项的类型
interface MenuItem {
icon: any;
title: string;
pageName: string;
}
// 定义菜单的类型
interface Menu {
[key: string]: MenuItem;
}
const router = useRouter();
const collapsed = ref<boolean>(false);
const current = ref<string>('menu_1');
const menu = ref<Menu>({
'menu_1': {
icon: FileProtectOutlined,
title: '设备激活',
pageName: 'Activate'
}
});
onMounted(() => {
menuHandle(null);
});
const menuHandle = (e: any): void => {
console.log('sider menu e:', e);
if (e) {
current.value = e.key;
}
console.log('sider menu current:', current.value);
const linkInfo = menu.value[current.value];
console.log('[home] load linkInfo:', linkInfo);
router.push({ name: linkInfo.pageName});
}
</script>
<style lang="less" scoped>
// 嵌套
#app-layout-sider {
height: 100%;
.logo {
border-bottom: 1px solid #e8e8e8;
padding:10px;
user-select: none;
}
.pic-logo {
height: 32px;
margin: 10px;
}
.layout-sider {
border-top: 1px solid #e8e8e8;
border-right: 1px solid #e8e8e8;
overflow: auto;
height: 100vh;
position: fixed;
left: 0;
top: 0;
bottom: 0;
}
.menu-item {
.ant-menu-item {
background-color: #fff;
margin-top: 0;
margin-bottom: 0;
padding: 0 !important;
}
}
.layout-content {
width: 96%;
margin: 0 auto;
}
}
</style>

View File

@@ -0,0 +1,5 @@
import AppSider from '@/layouts/AppSider.vue'
export {
AppSider
}

30
frontend/src/main.ts Normal file
View File

@@ -0,0 +1,30 @@
import * as AntIcon from '@ant-design/icons-vue';
import Antd from 'ant-design-vue';
import { createApp } from 'vue';
import App from './App.vue';
import './assets/global.less';
import './assets/theme.less';
import components from './components/global';
import Router from './router/index';
const app = createApp(App)
// components
type ComponentsType = typeof components;
for (const componentName in components) {
if (Object.prototype.hasOwnProperty.call(components, componentName)) {
const component = components[componentName as keyof ComponentsType];
app.component(componentName, component);
}
}
// icon
const whiteList = ['createFromIconfontCN', 'getTwoToneColor', 'setTwoToneColor', 'default']
const iconKeys = Object.keys(AntIcon) as Array<keyof typeof AntIcon>;
iconKeys.forEach(key => {
if (!whiteList.includes(key as typeof whiteList[number])) {
app.component(key.toString(), AntIcon[key]);
}
});
app.use(Antd).use(Router).mount('#app')

View File

@@ -0,0 +1,9 @@
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
import routerMap from './routerMap'
const Router = createRouter({
history: createWebHashHistory(),
routes: routerMap as RouteRecordRaw[],
})
export default Router

View File

@@ -0,0 +1,21 @@
/**
* 基础路由
* @type { *[] }
*/
const constantRouterMap = [
{
path: '/',
component: () => import('@/layouts/AppSider.vue'),
children: [
{
path: '/activate',
name: 'Activate',
component: () => import('@/views/activate/index.vue'),
props: { id: 'activate' }
}
]
},
]
export default constantRouterMap

12
frontend/src/types/env.d.ts vendored Normal file
View File

@@ -0,0 +1,12 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue';
const component: DefineComponent<{}, {}, any>;
export default component;
}
// declare global {
// interface Window {
// electron?: any;
// }
// }

0
frontend/src/types/pinia.d.ts vendored Normal file
View File

0
frontend/src/types/shim.d.ts vendored Normal file
View File

7
frontend/src/types/source.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
// 声明一个模块,防止引入文件时报错
declare module '*.json';
declare module '*.png';
declare module '*.jpg';
declare module '*.scss';
declare module '*.ts';
declare module '*.js';

View File

@@ -0,0 +1,27 @@
export default [
{ name: '对话框', type: 'icon-duihuakuang' },
{ name: '闹钟', type: 'icon-naozhong' },
{ name: '笑脸', type: 'icon-xiaolian' },
{ name: 'ok', type: 'icon-ok' },
{ name: '风车', type: 'icon-fengche' },
{ name: '汗颜', type: 'icon-hanyan' },
{ name: '相机', type: 'icon-xiangji' },
{ name: '礼物', type: 'icon-liwu' },
{ name: '礼花', type: 'icon-lihua' },
{ name: '扭蛋', type: 'icon-niudan' },
{ name: '流星', type: 'icon-liuxing' },
{ name: '风筝', type: 'icon-fengzheng' },
{ name: '蛋糕', type: 'icon-dangao' },
{ name: '泡泡', type: 'icon-paopao' },
{ name: '购物', type: 'icon-gouwu' },
{ name: '饮料', type: 'icon-yinliao' },
{ name: '云彩', type: 'icon-yuncai' },
{ name: '彩铅', type: 'icon-caiqian' },
{ name: '纸飞机', type: 'icon-zhifeiji' },
{ name: '点赞', type: 'icon-dianzan' },
{ name: '煎蛋', type: 'icon-jiandan' },
{ name: '小熊', type: 'icon-xiaoxiong' },
{ name: '花', type: 'icon-hua' },
{ name: '眼睛', type: 'icon-yanjing' },
]

View File

@@ -0,0 +1,39 @@
declare global {
interface Window {
electron?: any;
}
}
const Renderer = (window.require && window.require('electron')) || window.electron || {};
/**
* ipc
* 官方api说明https://www.electronjs.org/zh/docs/latest/api/ipc-renderer
*
* 属性/方法
* ipc.invoke(channel, param) - 发送异步消息invoke/handle 模型)
* ipc.sendSync(channel, param) - 发送同步消息send/on 模型)
* ipc.on(channel, listener) - 监听 channel, 当新消息到达,调用 listener
* ipc.once(channel, listener) - 添加一次性 listener 函数
* ipc.removeListener(channel, listener) - 为特定的 channel 从监听队列中删除特定的 listener 监听者
* ipc.removeAllListeners(channel) - 移除所有的监听器,当指定 channel 时只移除与其相关的所有监听器
* ipc.send(channel, ...args) - 通过channel向主进程发送异步消息
* ipc.postMessage(channel, message, [transfer]) - 发送消息到主进程
* ipc.sendTo(webContentsId, channel, ...args) - 通过 channel 发送消息到带有 webContentsId 的窗口
* ipc.sendToHost(channel, ...args) - 消息会被发送到 host 页面上的 <webview> 元素
*/
/**
* ipc
*/
const ipc = Renderer.ipcRenderer || undefined;
/**
* 是否为EE环境
*/
const isEE = ipc ? true : false;
export {
Renderer, ipc, isEE
};

47
frontend/src/utils/rsa.ts Normal file
View File

@@ -0,0 +1,47 @@
import JSEncrypt from 'jsencrypt'
// 获取 RSA 公钥
const publicKey = import.meta.env.VITE_RSA_PUBLIC_KEY
// 获取 RSA 私钥
const privateKey = import.meta.env.VITE_RSA_PRIVATE_KEY
// RSA加密
const encrypt = (data: string): string => {
try {
const encrypt = new JSEncrypt()
encrypt.setPublicKey(publicKey)
const encrypted = encrypt.encrypt(data)
if (encrypted) {
return encrypted
} else {
throw new Error('加密失败')
}
} catch (error) {
console.error('加密失败:', error)
throw new Error('RSA加密失败')
}
}
// RSA解密
const decrypt = (encryptedData: string): string => {
try {
const decrypt = new JSEncrypt()
decrypt.setPrivateKey(privateKey)
const decrypted = decrypt.decrypt(encryptedData)
if (decrypted) {
return decrypted as string
} else {
throw new Error('解密失败')
}
} catch (error) {
console.error('解密失败:', error)
throw new Error('RSA解密失败')
}
}
export default {
encrypt,
decrypt,
publicKey,
privateKey
}

View File

@@ -0,0 +1,56 @@
export namespace Activate {
export interface ApplicationModule {
/**
* 是否申请 1是 0否
*/
apply: number;
}
export interface ActivateModule extends ApplicationModule {
/**
* 是否永久 1是 0否
*/
permanently: number;
}
export interface ApplicationCodePlaintext {
/**
* 模拟式模块
*/
simulate: ApplicationModule;
/**
* 数字式模块
*/
digital: ApplicationModule;
/**
* 比对式模块
*/
contrast: ApplicationModule;
}
export interface ActivationCodePlaintext {
/**
* 模拟式模块
*/
simulate: ActivateModule;
/**
* 数字式模块
*/
digital: ActivateModule;
/**
* 比对式模块
*/
contrast: ActivateModule;
}
}

View File

@@ -0,0 +1,205 @@
<template>
<div class="activation-page">
<a-card title="RSA密钥配置" style="margin-bottom: 20px;">
<a-row :gutter="16">
<a-col :span="24">
<a-alert
message="注意:请妥善保管私钥,不要泄露给他人"
type="warning"
show-icon
style="margin-bottom: 16px;"
/>
</a-col>
<a-col :span="12">
<a-form-item label="RSA公钥">
<a-textarea
v-model:value="rsaKeys.publicKey"
:rows="5"
readonly
placeholder="RSA公钥内容"
/>
<a-button
type="primary"
ghost
size="small"
@click="copyToClipboard(rsaKeys.publicKey)"
:disabled="!rsaKeys.publicKey"
style="margin-top: 8px;"
>
复制公钥
</a-button>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="RSA私钥">
<a-textarea
v-model:value="rsaKeys.privateKey"
:rows="5"
readonly
placeholder="RSA私钥内容"
/>
<a-button
type="primary"
ghost
size="small"
@click="copyToClipboard(rsaKeys.privateKey)"
:disabled="!rsaKeys.privateKey"
style="margin-top: 8px;"
>
复制私钥
</a-button>
</a-form-item>
</a-col>
</a-row>
</a-card>
<a-card title="设备激活">
<a-row :gutter="16">
<a-col :span="24">
<a-divider orientation="left" orientation-margin="0px">设备申请码</a-divider>
<a-form-item>
<a-textarea
v-model:value="activationForm.requestCode"
:rows="3"
placeholder="请输入设备申请码"
/>
</a-form-item>
</a-col>
<a-col :span="24">
<a-divider orientation="left" orientation-margin="0px">设备申请码</a-divider>
<a-form-item>
<a-textarea
v-model:value="activationForm.activationCode"
:rows="3"
placeholder="生成的激活码将显示在这里"
readonly
/>
</a-form-item>
</a-col>
<a-col :span="24">
<a-space>
<a-button
type="primary"
@click="generateActivationCode"
:loading="generating"
:disabled="!activationForm.requestCode.trim()"
>
生成激活码
</a-button>
<a-button
@click="copyToClipboard(activationForm.activationCode)"
:disabled="!activationForm.activationCode"
>
复制激活码
</a-button>
</a-space>
</a-col>
</a-row>
</a-card>
</div>
</template>
<script setup lang="ts">
import {ref} from 'vue'
import {message} from 'ant-design-vue'
import rsa from '@/utils/rsa'
import {Activate} from "@/views/activate/index";
const rsaKeys = ref({
publicKey: rsa.publicKey,
privateKey: rsa.privateKey
})
const activationForm = ref({
requestCode: '',
activationCode: ''
})
const generating = ref(false)
// 生成激活码
const generateActivationCode = () => {
if (!activationForm.value.requestCode.trim()) {
message.error('请输入设备申请码')
return
}
generating.value = true
let activationCodePlaintext
try {
const plaintext = rsa.decrypt(activationForm.value.requestCode)
activationCodePlaintext = JSON.parse(plaintext) as Activate.ActivationCodePlaintext
} catch (e) {
console.error(e)
}
if (!activationCodePlaintext) {
generating.value = false
message.error('无效的设备申请码')
return
}
const contrast = activationCodePlaintext.contrast
const digital = activationCodePlaintext.digital
const simulate = activationCodePlaintext.simulate
if (!contrast && !digital && !simulate) {
generating.value = false
message.error('无效的设备申请码')
return
}
if (contrast.apply === 1) {
activationCodePlaintext.contrast.permanently = 1
}
if (digital.apply === 1) {
activationCodePlaintext.digital.permanently = 1
}
if (simulate.apply === 1) {
activationCodePlaintext.simulate.permanently = 1
}
const data = JSON.stringify(activationCodePlaintext)
try {
setTimeout(() => {
activationForm.value.activationCode = rsa.encrypt(data)
generating.value = false
}, 1000)
} catch (e) {
console.error(e)
generating.value = false
message.error('生成激活码失败')
}
}
// 复制到剪贴板
const copyToClipboard = (text: string) => {
if (!text) {
message.warning('没有内容可复制')
return
}
navigator.clipboard.writeText(text).then(() => {
message.success('复制成功')
}).catch(() => {
message.error('复制失败')
})
}
</script>
<style scoped lang="less">
.activation-page {
:deep(textarea) {
font-family: consolas, monospace;
resize: none;
}
:deep(.ant-form-item-label) {
font-weight: bold;
}
:deep(.ant-card) {
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
}
</style>

35
frontend/tsconfig.json Normal file
View File

@@ -0,0 +1,35 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"lib": ["esnext", "dom", "dom.iterable", "scripthost"],
"jsx": "preserve",
"strict": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"importHelpers": true,
"resolveJsonModule": true,
// 定义一个变量就必须给它一个初始值
"strictPropertyInitialization": false,
// 允许使用obj[key]访问对象属性
//"suppressImplicitAnyIndexErrors": true,
"allowJs": true,
"sourceMap": true,
"baseUrl": "./",
"paths": {
"@/*": [
"src/*"
],
}
},
"include":["src/**/*.ts", "src/**/*.vue", "src/**/*.tsx", "src/**/*.d.ts", "*.d.ts"],
"exclude": [
"node_modules",
"dist"
]
}

54
frontend/vite.config.ts Normal file
View File

@@ -0,0 +1,54 @@
import vue from '@vitejs/plugin-vue';
import { defineConfig } from 'vite';
import viteCompression from 'vite-plugin-compression';
import path from 'path';
export default defineConfig((mode) => {
return {
// Project plugins
plugins: [
vue(),
viteCompression({
verbose: true,
disable: false,
threshold: 1025,
algorithm: 'gzip',
ext: '.gz',
}),
],
// Base configuration
base: './',
publicDir: 'public',
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
css: {
preprocessorOptions: {
less: {
modifyVars: {
'@border-color-base': '#dce3e8',
},
javascriptEnabled: true,
},
},
},
build: {
outDir: 'dist',
assetsDir: 'assets',
assetsInlineLimit: 4096,
cssCodeSplit: true,
brotliSize: false,
sourcemap: false,
minify: 'terser',
terserOptions: {
compress: {
// Remove console and debugger in production
drop_console: false,
drop_debugger: true,
},
},
},
};
});

50
package.json Normal file
View File

@@ -0,0 +1,50 @@
{
"name": "pqs9100_tool",
"version": "0.0.1",
"description": "pqs-9100 tool client",
"main": "./public/electron/main.js",
"scripts": {
"dev": "ee-bin dev",
"build": "npm run build-frontend && npm run build-electron && ee-bin encrypt",
"start": "ee-bin start",
"dev-frontend": "ee-bin dev --serve=frontend",
"dev-electron": "ee-bin dev --serve=electron",
"build-frontend": "ee-bin build --cmds=frontend && ee-bin move --flag=frontend_dist",
"build-electron": "ee-bin build --cmds=electron",
"encrypt": "ee-bin encrypt",
"icon": "ee-bin icon",
"build-w": "ee-bin build --cmds=win64",
"build-we": "ee-bin build --cmds=win_e",
"build-m": "ee-bin build --cmds=mac",
"build-m-arm64": "ee-bin build --cmds=mac_arm64",
"build-l": "ee-bin build --cmds=linux",
"debug-dev": "cross-env DEBUG=ee-* ee-bin dev",
"debug-encrypt": "ee-bin encrypt",
"debug-electron": "cross-env DEBUG=ee-* ee-bin dev --serve=electron",
"debug-move": "ee-bin move --flag=frontend_dist"
},
"repository": "https://github.com/dromara/electron-egg.git",
"keywords": [
"Electron",
"electron-egg",
"ElectronEgg"
],
"author": "njcn",
"devDependencies": {
"@electron/rebuild": "^3.7.1",
"@types/node": "^20.16.0",
"cross-env": "^7.0.3",
"debug": "^4.4.0",
"ee-bin": "^4.1.10",
"electron": "^31.7.6",
"electron-builder": "^25.1.8",
"icon-gen": "^5.0.0",
"typescript": "^5.4.2"
},
"dependencies": {
"axios": "^1.7.9",
"dayjs": "^1.11.13",
"ee-core": "^4.1.5",
"electron-updater": "^6.3.8"
}
}

94
public/html/loading.html Normal file
View File

@@ -0,0 +1,94 @@
<!DOCTYPE html>
<html lang="zh-CN">
<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, user-scalable=no, maximum-scale=1.0, minimum-scale=1.0" />
<style>
#loadingPage {
background-color: #dedede;
font-size: 12px;
}
.base {
left: 50%;
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
}
.desc {
margin: 0, 0, 20px, 0;
}
.loading,
.loading > div {
position: relative;
box-sizing: border-box;
}
.loading {
display: block;
font-size: 0;
color: #06b359;
}
.loading.la-dark {
color: #07C160;
}
.loading > div {
display: inline-block;
float: none;
background-color: currentColor;
border: 0 solid currentColor;
}
.loading {
width: 92px;
height: 92px;
}
.loading > div {
position: absolute;
top: 50%;
left: 50%;
background: transparent;
border-style: solid;
border-width: 2px;
border-radius: 100%;
animation: ball-clip-rotate-multiple-rotate 1s ease-in-out infinite;
}
.loading > div:first-child {
position: absolute;
width: 92px;
height: 92px;
border-right-color: transparent;
border-left-color: transparent;
}
.loading > div:last-child {
width: 16px;
height: 16px;
border-top-color: transparent;
border-bottom-color: transparent;
animation-duration: 0.5s;
animation-direction: reverse;
}
@keyframes ball-clip-rotate-multiple-rotate {
0% {
transform: translate(-50%, -50%) rotate(0deg);
}
50% {
transform: translate(-50%, -50%) rotate(180deg);
}
100% {
transform: translate(-50%, -50%) rotate(360deg);
}
}
</style>
</head>
<body>
<div id="boot">
<div class='base'>
<div class="loading">
<div></div>
<div></div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,22 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css">
body{
margin:0px auto;
}
#content {
position: absolute;
left: 50%;
top: 35%;
transform: translate(-50%, -50%);
}
</style>
<title></title>
</head>
<body>
<div id="content">
这是一个html页面
</div>
</body>
</html>

BIN
public/images/logo-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
public/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
public/images/tray.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 943 B

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDpWYqPkljVtDNp
JVwlcOxNVihQPf4T3Q/tuIt5znV5ImBmlWg+OyuG47Y5e+qPMjFCSX4ebTAtBMbY
m6AJihkKy0iKj1mVS9TPCzqcHFnUliCIqJMKFsJTWygNGgogjXhlxFaJgCZO6Gw6
ocWp6nw1gMhrMIxqT2MQIQX16SD1IH/F4JMoaYuifnR+OOgbS3yUKHDTFApkZAWn
dS4GpbT39rW9cmbJrGHCvl8bsm8MXMdXars10A++wjjmHbtZu8beFv+zKbDw4iAu
ls8p/lAoIvQy8MkLLM5b392CLMaK0517+qM3VdEZ0ph+32m9IEoj5QV8xgaPdnyP
8/8a2bU/AgMBAAECggEBAIzFZ8GVF+JUA2+7CgvMQ8Gj6E4AF/cDtUhDvGCPHG8n
PeCk4W4pY+jMFnI3PxmDvhOvIlZYqGeAKjUiLTmUBedtGyX7tJ9MT+VXcNQchlSo
/Jd0mr/LWw/OPispOlLJBYjfGRV6KaIQtLnqPcRzoNrmBgIkF5FKswhX47CmIyu8
eEXmDsqXzWmYwdOHLNDshLOrCgFgRnqK7HjxvNqb9k4qv3V9WxlJYITK7L4eJNcZ
XYvGl+QV1+n153phyYnHcbohzvE44Hv8e4hiY9uYNdWckNBNFLpk9vEmUjewCRT1
kL30woifUhZCalXBIfKEHNZg23teOMHNZTqcS+ER64ECgYEA7BAWVWS/8b9h1AmP
SYhJgudb+ck1ItuvEDjonKuUCTBYm0iQI7cgAimasIBybRO8zwKstvWGDnUJJEkB
Oh7AXF6CbKuP7navTBAaF3AXsNfDgQuEwtEg78iqiQzTyp6FOvvOT2aOtzi86Ju1
zwKu+DLV3ETeokHWrQmV1uZnbZMCgYEA/Q7LdEEvZxz8OqOjtj8iKLWAv0WvnHTO
Zjqn/BbXi2PyI8d5ntIUPEgm/MRpvXTa465Uu8Orujfq51Z/oFbyvhhEOWT/sVn/
fzzbH4xXb3bLJ3D+LILQzsm4d8yrV66Re1ehFVxRPIy5RTpssED7bOns9ds4lsHT
W9p7ibYRBSUCgYAMpJftnuXA1tUwfAqWj5wQTL/aUvJrmYR4w/OBYJcfHt3AA1Tk
9MvcEcpdJaP7P5FfLO9/JQs2/wGsVdSg/kCjMdSeaVneFbExy7L6CmDacdPgt3M2
0+iFryOjD3LQaUkNbasRCZcfLQTBGIXWPniMhnx5vZ6G5ivPPLIvvktPzQKBgQCd
s/yi5ISwE+Y0fQpnZwzYpdQoXztDm5+NIfzSI0IMgirClWt7yJwHvUdeuuDSyuIm
hdwUb6qzkGl55fP/bnA0e1b5FbIrSlTpbHl6PbG3qyaL2+TqxFNwq1Gkhw44xHex
kDi44SFXRLOpKvHVHYoSo+2igg3QFdasJYpblfUhaQKBgF0l9PpMbDLdPlI33IQz
bEzw0ig8R8nHocJzOkK/BdLI8WiItYGgq4mcZGDWsztNg17QQGTEFrH7H9B8DKAJ
p75jz5O83arjMECqAiXlSGOWtq6NhbgyJcQJxvvvN8wObVFoVkLoEbqE1TkDqZfI
CqiusA5zgG89vzP9xFhW2ia2
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,26 @@
-----BEGIN CERTIFICATE-----
MIIEXzCCAsegAwIBAgIRAOLUY4uS9d2yXx0vd6qql30wDQYJKoZIhvcNAQELBQAw
gZExHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEzMDEGA1UECwwqQklM
SUJJTElcZ2Fvc2h1YWl4aW5nQENOMjEwMTAyMjc0ICjljaHor7opMTowOAYDVQQD
DDFta2NlcnQgQklMSUJJTElcZ2Fvc2h1YWl4aW5nQENOMjEwMTAyMjc0ICjljaHo
r7opMB4XDTIyMDcyNzA4NDcyOFoXDTI0MTAyNzA4NDcyOFowXjEnMCUGA1UEChMe
bWtjZXJ0IGRldmVsb3BtZW50IGNlcnRpZmljYXRlMTMwMQYDVQQLDCpCSUxJQklM
SVxnYW9zaHVhaXhpbmdAQ04yMTAxMDIyNzQgKOWNoeivuikwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQDpWYqPkljVtDNpJVwlcOxNVihQPf4T3Q/tuIt5
znV5ImBmlWg+OyuG47Y5e+qPMjFCSX4ebTAtBMbYm6AJihkKy0iKj1mVS9TPCzqc
HFnUliCIqJMKFsJTWygNGgogjXhlxFaJgCZO6Gw6ocWp6nw1gMhrMIxqT2MQIQX1
6SD1IH/F4JMoaYuifnR+OOgbS3yUKHDTFApkZAWndS4GpbT39rW9cmbJrGHCvl8b
sm8MXMdXars10A++wjjmHbtZu8beFv+zKbDw4iAuls8p/lAoIvQy8MkLLM5b392C
LMaK0517+qM3VdEZ0ph+32m9IEoj5QV8xgaPdnyP8/8a2bU/AgMBAAGjZDBiMA4G
A1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBQn
B1E5Js/cFhxBwpZL59aoK/skLjAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEw
DQYJKoZIhvcNAQELBQADggGBAIaUncQj2XN2rNn6sE0MuaWboFqwpkydyei6FvtN
c/TY9RWW3QRYICcO721l/2jBiWplQt/ZYaJ+IWN+C+3JSAz9IYsM/nMgxHL2azLQ
zHKnASEjxptW9+mlsgVk2LTrBfbc197ikLu80M/0jQYaIBeoEOaMlhBjno139nTO
evNheyFKvAhggOseD00I9VBZkKDBxvqr6PHnGjyAU43C1/HkNjglIbQjAZdBmXlX
HuelQ97glfhzyApvmczPrc8IAqPhtYn2nJ5P6Ea35LEc3D7uVExywcjDFcSwMJCb
TXqouzM/U8pO+DGeuvgwkYrBGlA7iEE+ZQgxCBatOXwG95THtFlfW+H0ILHB2tcX
P+Kztwd+4ipPciJz+1NK7z7erwfxHO5hmXJskH9YWi6YJsIw5g1iYs0pJJ/4p7Bd
8qSGEhri/+iijcC76q+1N0xhJxQrDDlC0pKp6oAYFDGKirzwmlAf/eJBy0ORWjCj
yk+d9T622yzcXa5fw3HBZh1o6A==
-----END CERTIFICATE-----

28
tsconfig.json Normal file
View File

@@ -0,0 +1,28 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"useDefineForClassFields": true,
"skipLibCheck": true,
"types": ["node"],
"esModuleInterop": true,
/* Bundler mode */
"moduleResolution": "node", // node
"resolveJsonModule": true,
"isolatedModules": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitAny": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true,
},
"include": [
"./electron/**/*"
]
}