commit 4768ef2d264c2144693180fcb78c10457f470687 Author: 贾同学 Date: Thu Oct 16 20:01:57 2025 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c81d9e0 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..00023c9 --- /dev/null +++ b/.npmrc @@ -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/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..61f2410 --- /dev/null +++ b/README.md @@ -0,0 +1,221 @@ +[![star](https://gitee.com/dromara/electron-egg/badge/star.svg?theme=gvp)](https://gitee.com/dromara/electron-egg/stargazers) + +
+

🎉🎉🎉 ElectronEgg V4 已发布! 🎉🎉🎉

+
+
+ +
+ +
+ +
+

一个入门简单、跨平台、企业级桌面软件开发框架

+
+
+ + + +## 📋 介绍 + +> 框架已经广泛应用于记账、政务、企业、医疗、学校、股票交易、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 成员项目 + +

+ + + + + + + + + + + + + + + + + + +

+

+ + + + + + + + + + + + + + + + + + +

+

+ + + + + + + + + + + + + + + + + + +

+

+ + + + + + + + + + + + + + + + + + +

\ No newline at end of file diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 0000000..61f2410 --- /dev/null +++ b/README.zh-CN.md @@ -0,0 +1,221 @@ +[![star](https://gitee.com/dromara/electron-egg/badge/star.svg?theme=gvp)](https://gitee.com/dromara/electron-egg/stargazers) + +
+

🎉🎉🎉 ElectronEgg V4 已发布! 🎉🎉🎉

+
+
+ +
+ +
+ +
+

一个入门简单、跨平台、企业级桌面软件开发框架

+
+
+ + + +## 📋 介绍 + +> 框架已经广泛应用于记账、政务、企业、医疗、学校、股票交易、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 成员项目 + +

+ + + + + + + + + + + + + + + + + + +

+

+ + + + + + + + + + + + + + + + + + +

+

+ + + + + + + + + + + + + + + + + + +

+

+ + + + + + + + + + + + + + + + + + +

\ No newline at end of file diff --git a/build/extraResources/chromeExtension/read.txt b/build/extraResources/chromeExtension/read.txt new file mode 100644 index 0000000..57b4b84 --- /dev/null +++ b/build/extraResources/chromeExtension/read.txt @@ -0,0 +1 @@ +chrome应用商店ctx文件,解压后,放置在此目录中,打包时会将资源加入安装包内。 \ No newline at end of file diff --git a/build/extraResources/read.txt b/build/extraResources/read.txt new file mode 100644 index 0000000..e20ade3 --- /dev/null +++ b/build/extraResources/read.txt @@ -0,0 +1 @@ +建议第三方软件放置在此目录中,打包时会将资源加入安装包内。 \ No newline at end of file diff --git a/build/icons/256x256.png b/build/icons/256x256.png new file mode 100644 index 0000000..a78f067 Binary files /dev/null and b/build/icons/256x256.png differ diff --git a/build/icons/32x32.png b/build/icons/32x32.png new file mode 100644 index 0000000..7fdd308 Binary files /dev/null and b/build/icons/32x32.png differ diff --git a/build/icons/512x512.png b/build/icons/512x512.png new file mode 100644 index 0000000..9606b31 Binary files /dev/null and b/build/icons/512x512.png differ diff --git a/build/icons/64x64.png b/build/icons/64x64.png new file mode 100644 index 0000000..b330934 Binary files /dev/null and b/build/icons/64x64.png differ diff --git a/build/icons/favicon.ico b/build/icons/favicon.ico new file mode 100644 index 0000000..4da7a78 Binary files /dev/null and b/build/icons/favicon.ico differ diff --git a/build/icons/icon.ico b/build/icons/icon.ico new file mode 100644 index 0000000..f67e8c4 Binary files /dev/null and b/build/icons/icon.ico differ diff --git a/build/script/installer.nsh b/build/script/installer.nsh new file mode 100644 index 0000000..e69de29 diff --git a/cmd/bin.js b/cmd/bin.js new file mode 100644 index 0000000..f62f4ba --- /dev/null +++ b/cmd/bin.js @@ -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 + }, + }, +}; \ No newline at end of file diff --git a/cmd/builder-linux.json b/cmd/builder-linux.json new file mode 100644 index 0000000..06b1f36 --- /dev/null +++ b/cmd/builder-linux.json @@ -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" + } +} \ No newline at end of file diff --git a/cmd/builder-mac-arm64.json b/cmd/builder-mac-arm64.json new file mode 100644 index 0000000..6e655f7 --- /dev/null +++ b/cmd/builder-mac-arm64.json @@ -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 + } +} \ No newline at end of file diff --git a/cmd/builder-mac.json b/cmd/builder-mac.json new file mode 100644 index 0000000..6e655f7 --- /dev/null +++ b/cmd/builder-mac.json @@ -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 + } +} \ No newline at end of file diff --git a/cmd/builder.json b/cmd/builder.json new file mode 100644 index 0000000..8d4fe9b --- /dev/null +++ b/cmd/builder.json @@ -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" + } + ] + } +} \ No newline at end of file diff --git a/electron/config/config.default.ts b/electron/config/config.default.ts new file mode 100644 index 0000000..4988ab4 --- /dev/null +++ b/electron/config/config.default.ts @@ -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; \ No newline at end of file diff --git a/electron/config/config.local.ts b/electron/config/config.local.ts new file mode 100644 index 0000000..7663064 --- /dev/null +++ b/electron/config/config.local.ts @@ -0,0 +1,14 @@ +import { type AppConfig } from 'ee-core/config'; + +const config: () => AppConfig = () => { + return { + openDevTools: { + mode: 'bottom' + }, + jobs: { + messageLog: false + } + }; +}; + +export default config; \ No newline at end of file diff --git a/electron/config/config.prod.ts b/electron/config/config.prod.ts new file mode 100644 index 0000000..567a8c9 --- /dev/null +++ b/electron/config/config.prod.ts @@ -0,0 +1,9 @@ +import { type AppConfig } from 'ee-core/config'; + +const config: () => AppConfig = () => { + return { + openDevTools: false, + }; +}; + +export default config; \ No newline at end of file diff --git a/electron/controller/cross.ts b/electron/controller/cross.ts new file mode 100644 index 0000000..efe4b59 --- /dev/null +++ b/electron/controller/cross.ts @@ -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 { + 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 { + const { type, name } = args; + crossService.killServer(type, name); + return; + } + + /** + * create service + */ + async createServer(args: { program: string }): Promise { + 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 { + const { name, urlPath, params} = args; + const data = await crossService.requestApi(name, urlPath, params); + return data; + } +} +CrossController.toString = () => '[class CrossController]'; + +export default CrossController; \ No newline at end of file diff --git a/electron/controller/effect.ts b/electron/controller/effect.ts new file mode 100644 index 0000000..e713ec2 --- /dev/null +++ b/electron/controller/effect.ts @@ -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; \ No newline at end of file diff --git a/electron/controller/example.ts b/electron/controller/example.ts new file mode 100644 index 0000000..a1138ef --- /dev/null +++ b/electron/controller/example.ts @@ -0,0 +1,16 @@ +/** + * example + * @class + */ +class ExampleController { + + /** + * test + */ + async test(): Promise { + return 'hello electron-egg'; + } +} +ExampleController.toString = () => '[class ExampleController]'; + +export default ExampleController; \ No newline at end of file diff --git a/electron/controller/os.ts b/electron/controller/os.ts new file mode 100644 index 0000000..29260d6 --- /dev/null +++ b/electron/controller/os.ts @@ -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; \ No newline at end of file diff --git a/electron/jobs/example/hello.ts b/electron/jobs/example/hello.ts new file mode 100644 index 0000000..16311a4 --- /dev/null +++ b/electron/jobs/example/hello.ts @@ -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 }; \ No newline at end of file diff --git a/electron/jobs/example/timer.ts b/electron/jobs/example/timer.ts new file mode 100644 index 0000000..0a12c8d --- /dev/null +++ b/electron/jobs/example/timer.ts @@ -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 { + 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 { + logger.info("[child-process] Pause timerJob, jobId: ", jobId); + clearInterval(this.timer); + clearInterval(this.timeoutTimer); + } + + /** + * Resume the job + */ + async resume(jobId: string, pid: number): Promise { + 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; diff --git a/electron/main.ts b/electron/main.ts new file mode 100644 index 0000000..f3f9a91 --- /dev/null +++ b/electron/main.ts @@ -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(); \ No newline at end of file diff --git a/electron/preload/bridge.ts b/electron/preload/bridge.ts new file mode 100644 index 0000000..0f21b65 --- /dev/null +++ b/electron/preload/bridge.ts @@ -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); \ No newline at end of file diff --git a/electron/preload/index.ts b/electron/preload/index.ts new file mode 100644 index 0000000..9b9bf54 --- /dev/null +++ b/electron/preload/index.ts @@ -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 }; \ No newline at end of file diff --git a/electron/preload/lifecycle.ts b/electron/preload/lifecycle.ts new file mode 100644 index 0000000..0a22700 --- /dev/null +++ b/electron/preload/lifecycle.ts @@ -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 { + logger.info('[lifecycle] ready'); + } + + /** + * Electron app is ready + */ + async electronAppReady(): Promise { + 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 { + 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 { + logger.info('[lifecycle] before-close'); + } +} +Lifecycle.toString = () => '[class Lifecycle]'; + +export { Lifecycle }; \ No newline at end of file diff --git a/electron/service/cross.ts b/electron/service/cross.ts new file mode 100644 index 0000000..c0f3e04 --- /dev/null +++ b/electron/service/cross.ts @@ -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 { + // 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 { + 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 { + // 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 { + 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 +}; \ No newline at end of file diff --git a/electron/service/effect.ts b/electron/service/effect.ts new file mode 100644 index 0000000..b8d23e8 --- /dev/null +++ b/electron/service/effect.ts @@ -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 +} \ No newline at end of file diff --git a/electron/service/example.ts b/electron/service/example.ts new file mode 100644 index 0000000..c8b9f1f --- /dev/null +++ b/electron/service/example.ts @@ -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 +}; \ No newline at end of file diff --git a/electron/service/job/user.ts b/electron/service/job/user.ts new file mode 100644 index 0000000..7fe2143 --- /dev/null +++ b/electron/service/job/user.ts @@ -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 }; \ No newline at end of file diff --git a/electron/service/os/auto_updater.ts b/electron/service/os/auto_updater.ts new file mode 100644 index 0000000..655144c --- /dev/null +++ b/electron/service/os/auto_updater.ts @@ -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 +}; \ No newline at end of file diff --git a/electron/service/os/icon.js b/electron/service/os/icon.js new file mode 100644 index 0000000..66ebc5b --- /dev/null +++ b/electron/service/os/icon.js @@ -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() +}; \ No newline at end of file diff --git a/electron/service/os/security.ts b/electron/service/os/security.ts new file mode 100644 index 0000000..648516f --- /dev/null +++ b/electron/service/os/security.ts @@ -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 +}; \ No newline at end of file diff --git a/electron/service/os/tray.ts b/electron/service/os/tray.ts new file mode 100644 index 0000000..79027b2 --- /dev/null +++ b/electron/service/os/tray.ts @@ -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 +} \ No newline at end of file diff --git a/electron/service/os/window.ts b/electron/service/os/window.ts new file mode 100644 index 0000000..cc9231e --- /dev/null +++ b/electron/service/os/window.ts @@ -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 +} \ No newline at end of file diff --git a/frontend/.editorconfig b/frontend/.editorconfig new file mode 100644 index 0000000..3454886 --- /dev/null +++ b/frontend/.editorconfig @@ -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 diff --git a/frontend/.env.development b/frontend/.env.development new file mode 100644 index 0000000..8afb7b4 --- /dev/null +++ b/frontend/.env.development @@ -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=" diff --git a/frontend/.env.production b/frontend/.env.production new file mode 100644 index 0000000..5f191a9 --- /dev/null +++ b/frontend/.env.production @@ -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=" diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..082d756 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,6 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local +package-lock.json \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..37dea01 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,105 @@ + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..0b182ab --- /dev/null +++ b/frontend/package.json @@ -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" + } +} diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..fd3f672 --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,15 @@ + + + diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts new file mode 100644 index 0000000..a395175 --- /dev/null +++ b/frontend/src/api/index.ts @@ -0,0 +1,84 @@ + +/** + * Definition of communication channel between main process and rendering process + * format:controller/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 +} + diff --git a/frontend/src/assets/global.less b/frontend/src/assets/global.less new file mode 100644 index 0000000..6eace16 --- /dev/null +++ b/frontend/src/assets/global.less @@ -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} diff --git a/frontend/src/assets/login.png b/frontend/src/assets/login.png new file mode 100644 index 0000000..5882c86 Binary files /dev/null and b/frontend/src/assets/login.png differ diff --git a/frontend/src/assets/logo.png b/frontend/src/assets/logo.png new file mode 100644 index 0000000..bee9067 Binary files /dev/null and b/frontend/src/assets/logo.png differ diff --git a/frontend/src/assets/theme.less b/frontend/src/assets/theme.less new file mode 100644 index 0000000..9c5445d --- /dev/null +++ b/frontend/src/assets/theme.less @@ -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); // 浮层阴影 + diff --git a/frontend/src/components/global/iconFont.ts b/frontend/src/components/global/iconFont.ts new file mode 100644 index 0000000..6c675dc --- /dev/null +++ b/frontend/src/components/global/iconFont.ts @@ -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 diff --git a/frontend/src/components/global/index.ts b/frontend/src/components/global/index.ts new file mode 100644 index 0000000..3c7401f --- /dev/null +++ b/frontend/src/components/global/index.ts @@ -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; \ No newline at end of file diff --git a/frontend/src/layouts/AppSider.vue b/frontend/src/layouts/AppSider.vue new file mode 100644 index 0000000..e359913 --- /dev/null +++ b/frontend/src/layouts/AppSider.vue @@ -0,0 +1,112 @@ + + + diff --git a/frontend/src/layouts/index.ts b/frontend/src/layouts/index.ts new file mode 100644 index 0000000..43d8cc8 --- /dev/null +++ b/frontend/src/layouts/index.ts @@ -0,0 +1,5 @@ +import AppSider from '@/layouts/AppSider.vue' + +export { + AppSider +} diff --git a/frontend/src/main.ts b/frontend/src/main.ts new file mode 100644 index 0000000..06cf297 --- /dev/null +++ b/frontend/src/main.ts @@ -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; +iconKeys.forEach(key => { + if (!whiteList.includes(key as typeof whiteList[number])) { + app.component(key.toString(), AntIcon[key]); + } +}); + +app.use(Antd).use(Router).mount('#app') diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts new file mode 100644 index 0000000..bb03e09 --- /dev/null +++ b/frontend/src/router/index.ts @@ -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 diff --git a/frontend/src/router/routerMap.ts b/frontend/src/router/routerMap.ts new file mode 100644 index 0000000..30900bf --- /dev/null +++ b/frontend/src/router/routerMap.ts @@ -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 diff --git a/frontend/src/types/env.d.ts b/frontend/src/types/env.d.ts new file mode 100644 index 0000000..1d1226d --- /dev/null +++ b/frontend/src/types/env.d.ts @@ -0,0 +1,12 @@ +/// +declare module '*.vue' { + import type { DefineComponent } from 'vue'; + const component: DefineComponent<{}, {}, any>; + export default component; +} + +// declare global { +// interface Window { +// electron?: any; +// } +// } \ No newline at end of file diff --git a/frontend/src/types/pinia.d.ts b/frontend/src/types/pinia.d.ts new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/types/shim.d.ts b/frontend/src/types/shim.d.ts new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/types/source.d.ts b/frontend/src/types/source.d.ts new file mode 100644 index 0000000..d04f0da --- /dev/null +++ b/frontend/src/types/source.d.ts @@ -0,0 +1,7 @@ +// 声明一个模块,防止引入文件时报错 +declare module '*.json'; +declare module '*.png'; +declare module '*.jpg'; +declare module '*.scss'; +declare module '*.ts'; +declare module '*.js'; \ No newline at end of file diff --git a/frontend/src/utils/iconList.ts b/frontend/src/utils/iconList.ts new file mode 100644 index 0000000..e8597aa --- /dev/null +++ b/frontend/src/utils/iconList.ts @@ -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' }, +] + \ No newline at end of file diff --git a/frontend/src/utils/ipcRenderer.ts b/frontend/src/utils/ipcRenderer.ts new file mode 100644 index 0000000..919fd77 --- /dev/null +++ b/frontend/src/utils/ipcRenderer.ts @@ -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 页面上的 元素 + */ + +/** + * ipc + */ +const ipc = Renderer.ipcRenderer || undefined; + +/** + * 是否为EE环境 + */ +const isEE = ipc ? true : false; + +export { + Renderer, ipc, isEE +}; + diff --git a/frontend/src/utils/rsa.ts b/frontend/src/utils/rsa.ts new file mode 100644 index 0000000..daeccb1 --- /dev/null +++ b/frontend/src/utils/rsa.ts @@ -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 +} diff --git a/frontend/src/views/activate/index.ts b/frontend/src/views/activate/index.ts new file mode 100644 index 0000000..1817e25 --- /dev/null +++ b/frontend/src/views/activate/index.ts @@ -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; + } + + + +} diff --git a/frontend/src/views/activate/index.vue b/frontend/src/views/activate/index.vue new file mode 100644 index 0000000..b18ad06 --- /dev/null +++ b/frontend/src/views/activate/index.vue @@ -0,0 +1,205 @@ + + + diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..7d6abf9 --- /dev/null +++ b/frontend/tsconfig.json @@ -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" + ] +} \ No newline at end of file diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..e67ddb1 --- /dev/null +++ b/frontend/vite.config.ts @@ -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, + }, + }, + }, + }; +}); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..ff2c29a --- /dev/null +++ b/package.json @@ -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" + } +} diff --git a/public/html/loading.html b/public/html/loading.html new file mode 100644 index 0000000..9d1520b --- /dev/null +++ b/public/html/loading.html @@ -0,0 +1,94 @@ + + + + + + + + + +
+
+
+
+
+
+
+
+ + diff --git a/public/html/view_example.html b/public/html/view_example.html new file mode 100644 index 0000000..d480ac7 --- /dev/null +++ b/public/html/view_example.html @@ -0,0 +1,22 @@ + + + + + + + +
+ 这是一个html页面 +
+ + \ No newline at end of file diff --git a/public/images/logo-32.png b/public/images/logo-32.png new file mode 100644 index 0000000..7fdd308 Binary files /dev/null and b/public/images/logo-32.png differ diff --git a/public/images/logo.png b/public/images/logo.png new file mode 100644 index 0000000..bee9067 Binary files /dev/null and b/public/images/logo.png differ diff --git a/public/images/tray.png b/public/images/tray.png new file mode 100644 index 0000000..3bee245 Binary files /dev/null and b/public/images/tray.png differ diff --git a/public/ssl/localhost+1.key b/public/ssl/localhost+1.key new file mode 100644 index 0000000..9a04a92 --- /dev/null +++ b/public/ssl/localhost+1.key @@ -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----- diff --git a/public/ssl/localhost+1.pem b/public/ssl/localhost+1.pem new file mode 100644 index 0000000..332a3ae --- /dev/null +++ b/public/ssl/localhost+1.pem @@ -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----- diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..78276bd --- /dev/null +++ b/tsconfig.json @@ -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/**/*" + ] +} + \ No newline at end of file