diff --git a/.gitignore b/.gitignore index b1e7bae..396c87a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,6 @@ package-lock.json data/ .vscode/launch.json public/electron/ -public/dist/ pnpm-lock.yaml +CLAUDE.md +/public/dist/ diff --git a/.npmrc b/.npmrc index 00023c9..b359730 100644 --- a/.npmrc +++ b/.npmrc @@ -2,3 +2,9 @@ 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/ + +# 屏蔽警告配置 +legacy-peer-deps=true +audit=false +fund=false +loglevel=error diff --git a/README.md b/README.md deleted file mode 100644 index 484578d..0000000 --- a/README.md +++ /dev/null @@ -1,61 +0,0 @@ -#### 项目简介 - -#### 常用命令 -```shell - -# 运行项目 -npm run dev - -# 仅运行前端 -npm run dev-frontend - -# 仅运行后端 -npm run dev-electron - - -# 预发布模式(环境变量为:prod),请先移动资源 -npm run start - -# 移动前端静态资源 -npm run rd - -# 移动资源,可配置 -npm run move - -# 代码加密 -npm run encrypt - -# 清除加密的代码 -npm run clean - -# 生成logo -npm run icon - -# 打包 (windows版) -npm run build-w (调整为64位) -npm run build-w-32 (32位) -npm run build-w-64 (64位) -npm run build-w-arm64 (arm64) - -# 打包 (windows 免安装版) -# ee > v2.2.1 -npm run build-wz (调整为64位) -npm run build-wz-32 (32位) -npm run build-wz-64 (64位) -npm run build-wz-arm64 (arm64) - -# 打包 (mac版) -npm run build-m -npm run build-m-arm64 (m1芯片架构) - -# 打包 (linux版) -# ee > v2.2.1 -npm run build-l (默认64位 deb包) -npm run build-l-32 (32位 deb包) -npm run build-l-64 (64位 deb包) -npm run build-l-arm64 (64位 deb包 arm64) -npm run build-l-armv7l (64位 deb包 armv7l) -npm run build-lr-64 (64位 rpm包) -npm run build-lp-64 (64位 pacman包) - -``` \ No newline at end of file diff --git a/build/extraResources/chromeExtension/read.txt b/build/extraResources/chromeExtension/read.txt deleted file mode 100644 index 57b4b84..0000000 --- a/build/extraResources/chromeExtension/read.txt +++ /dev/null @@ -1 +0,0 @@ -chrome应用商店ctx文件,解压后,放置在此目录中,打包时会将资源加入安装包内。 \ No newline at end of file diff --git a/build/icons/128x128.png b/build/icons/128x128.png index 56b37e1..5c52682 100644 Binary files a/build/icons/128x128.png and b/build/icons/128x128.png differ diff --git a/build/icons/16x16.png b/build/icons/16x16.png index fc40055..9998a2c 100644 Binary files a/build/icons/16x16.png and b/build/icons/16x16.png differ diff --git a/build/icons/256x256.png b/build/icons/256x256.png index 95dc60b..da8b5a2 100644 Binary files a/build/icons/256x256.png and b/build/icons/256x256.png differ diff --git a/build/icons/32x32.png b/build/icons/32x32.png index ea7b2df..f02c0dd 100644 Binary files a/build/icons/32x32.png and b/build/icons/32x32.png differ diff --git a/build/icons/48x48.png b/build/icons/48x48.png index a85fc93..430e97d 100644 Binary files a/build/icons/48x48.png and b/build/icons/48x48.png differ diff --git a/build/icons/512x512.png b/build/icons/512x512.png index df48e85..da8b5a2 100644 Binary files a/build/icons/512x512.png and b/build/icons/512x512.png differ diff --git a/build/icons/64x64.png b/build/icons/64x64.png index 87b8273..21f80f9 100644 Binary files a/build/icons/64x64.png and b/build/icons/64x64.png differ diff --git a/build/icons/icon.ico b/build/icons/icon.ico index aefab6a..fe16b5a 100644 Binary files a/build/icons/icon.ico and b/build/icons/icon.ico differ diff --git a/build/icons/icon.png b/build/icons/icon.png index df48e85..da8b5a2 100644 Binary files a/build/icons/icon.png and b/build/icons/icon.png differ diff --git a/cmd/bin.js b/cmd/bin.js new file mode 100644 index 0000000..04ec18c --- /dev/null +++ b/cmd/bin.js @@ -0,0 +1,205 @@ +/** + * ee-bin 配置 + * 仅适用于开发环境 + */ +module.exports = { + /** + * development serve ("frontend" "electron" ) + * ee-bin dev + */ + dev: { + // frontend:前端服务 + // 说明:该配置的意思是,进入 frontend 目录,执行 npm run dev + // 运行后的服务为 http://localhost:8080 + // 如果 protocol 属性为 'file://' 那么不会执行命令,项目直接加载 indexPath 对应的文件。 + frontend: { + directory: './frontend', + cmd: 'npm', + args: ['run', 'dev'], + port: 18091, + }, + // electron:主进程服务 + // 说明:该配置的意思是,在根目录,执行 electron . --env=local + electron: { + directory: './', + cmd: 'electron', + args: ['.', '--env=local'], + watch: false, + } + }, + + /** + * 构建 + * ee-bin build + */ + build: { + frontend: { + directory: './frontend', + cmd: 'npm', + args: ['run', 'build'], + }, + electron: { + type: 'javascript', + bundleType: 'copy' + }, + 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: ['rc4'], + 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..eb4dfb2 --- /dev/null +++ b/cmd/builder-linux.json @@ -0,0 +1,40 @@ +{ + "productName": "ee", + "appId": "com.bilibili.ee", + "copyright": "© 2025 duola 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..b110fc6 --- /dev/null +++ b/cmd/builder-mac-arm64.json @@ -0,0 +1,38 @@ +{ + "productName": "ee", + "appId": "com.bilibili.ee", + "copyright": "© 2025 duola 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..b110fc6 --- /dev/null +++ b/cmd/builder-mac.json @@ -0,0 +1,38 @@ +{ + "productName": "ee", + "appId": "com.bilibili.ee", + "copyright": "© 2025 duola 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/electron/config/builder.json b/cmd/builder.json similarity index 51% rename from electron/config/builder.json rename to cmd/builder.json index 61f7340..f0c2166 100644 --- a/electron/config/builder.json +++ b/cmd/builder.json @@ -1,17 +1,21 @@ { - "productName": "pqs9100", - "appId": "com.njcn.pqs9100", - "copyright": "hongawen.com", + "productName": "NPQS9100", + "appId": "NQPS9100", + "copyright": "© 2025 南京灿能", "directories": { "output": "out" }, "asar": true, "files": [ "**/*", + "!cmd/", + "!data/", + "!electron/", "!frontend/", - "!run/", "!logs/", - "!data/" + "!out/", + "!go/", + "!python/" ], "extraResources": { "from": "build/extraResources/", @@ -26,35 +30,15 @@ "installerHeaderIcon": "build/icons/icon.ico", "createDesktopShortcut": true, "createStartMenuShortcut": true, - "shortcutName": "自动检测平台" - }, - "publish": [ - { - "provider": "generic", - "url": "http://www.shining-electric.com/" - } - ], - "mac": { - "icon": "build/icons/icon.icns", - "artifactName": "${productName}-${os}-${version}-${arch}.${ext}", - "darkModeSupport": true, - "hardenedRuntime": false + "shortcutName": "灿能检测" }, "win": { "icon": "build/icons/icon.ico", "artifactName": "${productName}-${os}-${version}-${arch}.${ext}", "target": [ { - "target": "nsis" + "target": "portable" } ] - }, - "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/doc/便携式JRE集成指南.md b/doc/便携式JRE集成指南.md new file mode 100644 index 0000000..f9319fb --- /dev/null +++ b/doc/便携式JRE集成指南.md @@ -0,0 +1,133 @@ +## 便携式 JRE/JDK8 集成指南 + +本指南介绍如何将 JRE 8 以“便携式”(解压即用、无需安装)的方式随应用打包,并在 Electron 主进程中通过绝对路径调用,从而避免要求用户在系统中安装 JDK/JRE。 + +### 为什么选择便携式 JRE +- 无需管理员权限与系统环境变量配置,用户无感知。 +- 不污染系统环境(不写入 JAVA_HOME/PATH)。 +- 跨平台一致,可精简体积、可控版本。 + +### 适用场景 +- 运行 Java 程序或 JAR 包(仅需运行时)。 +- 不需要 javac/jcmd/jmap 等开发/诊断工具(若需要,请改为随包便携式 JDK)。 + +### 推荐的 JRE 8 发行版(可再分发) +- Azul Zulu 8 JRE(可选 ZuluFX 含 JavaFX):[下载页面](https://www.azul.com/downloads/?version=java-8-lts&package=jre) +- BellSoft Liberica 8 JRE(Standard/Full,Full 含 JavaFX):[下载页面](https://bell-sw.com/pages/downloads/#/java-8-lts) +- Eclipse Temurin 8 JRE(Adoptium):[下载页面](https://adoptium.net/temurin/releases/?version=8) + +选择要点: +- 需要 AWT/Swing/字体/打印 → 选择非 headless 包。 +- 需要 JavaFX → 选择 Liberica Full 或 ZuluFX。 +- 仅命令行/服务端 → 任意 JRE 8(headless 也可)。 + +### 目录放置约定 +将解压后的 JRE 放入项目的 `build/extraResources/jre`,保证内部存在 `bin/java(.exe)`: + +``` +build/ + extraResources/ + jre/ + bin/ + java(.exe) + lib/ + ... +``` + +构建后在生产环境可通过 `process.resourcesPath` 访问: +`/resources/extraResources/jre/bin/java(.exe)`。 + +### 主进程调用示例 +在 `electron/preload/lifecycle.js` 或你的业务模块中封装 Java 运行工具(开发/生产两种路径): + +```js +const path = require('path'); +const { spawn } = require('child_process'); + +function getExtraResourcesDir() { + // 开发态:使用项目目录;生产态:使用 asar/resources 目录 + const isDev = !!process.env.EE_DEV || process.env.NODE_ENV === 'development'; + return isDev + ? path.join(process.cwd(), 'build', 'extraResources') + : path.join(process.resourcesPath, 'extraResources'); +} + +function getJavaBinPath() { + const extraDir = getExtraResourcesDir(); + const javaBinName = process.platform === 'win32' ? 'java.exe' : 'java'; + return path.join(extraDir, 'jre', 'bin', javaBinName); +} + +function runJavaJar(jarAbsPath, args = [], options = {}) { + const javaPath = getJavaBinPath(); + const child = spawn(javaPath, ['-jar', jarAbsPath, ...args], { + stdio: 'inherit', + ...options, + }); + return child; +} + +async function ensureJavaVersion(logger) { + return new Promise((resolve) => { + const child = spawn(getJavaBinPath(), ['-version']); + let out = ''; + let err = ''; + child.stdout && child.stdout.on('data', (d) => (out += d.toString())) + child.stderr && child.stderr.on('data', (d) => (err += d.toString())) + child.on('close', () => { + const text = (out + '\n' + err).trim(); + logger && logger.info('[java] version check:', text); + resolve(text.includes('1.8.0')); + }); + }); +} + +module.exports = { getJavaBinPath, runJavaJar, ensureJavaVersion }; +``` + +在生命周期中调用(示例): + +```js +const { logger } = require('ee-core/log'); +const path = require('path'); +const { runJavaJar, ensureJavaVersion } = require('./java-runner'); + +class Lifecycle { + async ready() { + const ok = await ensureJavaVersion(logger); + if (!ok) { + logger.error('[java] 未检测到 JRE 8,请检查 extraResources/jre 是否存在'); + } + } + + async windowReady() { + const jarPath = path.join(process.resourcesPath || process.cwd(), 'extraResources', 'tools', 'your-app.jar'); + // 示例:延后在某业务时机再启动 Java 进程 + // const proc = runJavaJar(jarPath, ['--arg1', 'value']); + } +} +``` + +注意:示例中的 `java-runner` 为上文工具函数文件,实际请按你的项目结构放置。 + +### 验证清单 +- 运行 `jre/bin/java -version` 输出包含 `1.8.0_xxx`。 +- 若涉及 GUI/字体/打印,验证 AWT/Swing 中文渲染与打印。 +- 若需 JavaFX,验证 JavaFX Demo 启动。 +- 若涉及 TLS/HTTPS,验证 SSL 通信正常。 + +### 许可与合规 +- Azul Zulu、Eclipse Temurin(Adoptium)、BellSoft Liberica 的 JRE/JDK 8 发行包均可免费再分发(GPLv2+CE 或厂商许可证)。 +- 建议在应用的“关于/许可证”中附上所选发行版的许可证链接与致谢。 + +### 常见问题 +1) 是否“阉割”? +— 上述 JRE 8 发行版均为标准运行时,通过兼容性测试;JRE 不包含开发者工具属于正常区别,不是删减。 + +2) 何时需要 JDK 而不是 JRE? +— 需要 `javac` 编译或 `jcmd/jmap` 等诊断工具,或你的 Java 组件依赖 `tools.jar` 时。 + +3) 体积如何优化? +— 选择 headless(若无 GUI 需求)、去除无用语言/字体包;或改用 JDK 9+ 使用 jlink(不适用于 8)。 + + diff --git a/doc/打包方案对比.md b/doc/打包方案对比.md new file mode 100644 index 0000000..b7d8b41 --- /dev/null +++ b/doc/打包方案对比.md @@ -0,0 +1,436 @@ +# 应用打包方案对比与实现 + +本文档详细说明 ElectronEgg 应用的两种打包方案:纯绿色版方案 和 双版本方案。 + +--- + +## 方案对比 + +| 特性 | 方案一:纯绿色版 | 方案二:双版本打包 | +|------|-----------------|-------------------| +| **打包产物** | 单个便携版 exe | 安装版 exe + 便携版 exe | +| **安装过程** | 无需安装 | 安装版需安装,便携版无需 | +| **桌面快捷方式** | 应用内自动创建 | 安装版自动创建,便携版手动或自动 | +| **开始菜单** | 无 | 安装版有 | +| **卸载程序** | 无(直接删除) | 安装版有 | +| **适用场景** | 临时使用、U盘携带 | 正式部署、企业分发 | +| **用户体验** | 灵活、轻量 | 专业、完整 | +| **打包时间** | 快 | 较慢(打包两次) | +| **分发复杂度** | 简单(单文件) | 中等(两个文件) | + +--- + +## 方案一:纯绿色版 + 自动创建快捷方式 + +### 特点 +- ✅ 单个 exe 文件,双击即用 +- ✅ 首次启动时询问是否创建桌面快捷方式 +- ✅ 无需安装,无需卸载 +- ✅ 适合快速分发和临时使用 + +### 实现步骤 + +#### 1. 修改打包配置 + +**文件**:[cmd/builder.json](../cmd/builder.json) + +```json +{ + "productName": "南京灿能工具", + "appId": "com.canneng.tool", + "copyright": "© 2025 hongawen", + "directories": { + "output": "out" + }, + "asar": true, + "files": [ + "**/*", + "!cmd/", + "!data/", + "!electron/", + "!frontend/", + "!logs/", + "!out/", + "!go/", + "!python/" + ], + "extraResources": { + "from": "build/extraResources/", + "to": "extraResources" + }, + "publish": [ + { + "provider": "generic", + "url": "https://your-update-server.com" + } + ], + "win": { + "icon": "build/icons/icon.ico", + "artifactName": "${productName}-${os}-${version}-${arch}.${ext}", + "target": [ + { + "target": "portable" + } + ] + } +} +``` + +#### 2. 添加自动创建快捷方式功能 + +**文件**:[electron/preload/lifecycle.js](../electron/preload/lifecycle.js) + +在 `windowReady()` 钩子中添加以下代码: + +```javascript +const { logger } = require('ee-core/log'); +const { getConfig } = require('ee-core/config'); +const { getMainWindow } = require('ee-core/electron'); + +class Lifecycle { + + async ready() { + logger.info('[lifecycle] ready'); + } + + async electronAppReady() { + logger.info('[lifecycle] electron-app-ready'); + } + + async windowReady() { + logger.info('[lifecycle] window-ready'); + + // 延迟加载,无白屏 + const { windowsOption } = getConfig(); + if (windowsOption.show == false) { + const win = getMainWindow(); + win.once('ready-to-show', () => { + win.show(); + win.focus(); + }) + } + + // 绿色版自动创建桌面快捷方式 + await this.createDesktopShortcut(); + } + + /** + * 为绿色版创建桌面快捷方式 + */ + async createDesktopShortcut() { + const { app, dialog, shell } = require('electron'); + const path = require('path'); + const fs = require('fs'); + + // 判断是否为便携版(绿色版) + // 安装版通常在 C:\Program Files 或 AppData\Local\Programs + const isPortable = process.platform === 'win32' && + !process.execPath.includes('Program Files') && + !process.execPath.includes('AppData\\Local\\Programs'); + + if (!isPortable) { + logger.info('[lifecycle] 非便携版,跳过快捷方式创建'); + return; + } + + try { + const desktopPath = app.getPath('desktop'); + const shortcutPath = path.join(desktopPath, '南京灿能工具.lnk'); + + // 如果快捷方式已存在,跳过 + if (fs.existsSync(shortcutPath)) { + logger.info('[lifecycle] 桌面快捷方式已存在'); + return; + } + + // 询问用户是否创建快捷方式 + const result = await dialog.showMessageBox({ + type: 'question', + buttons: ['创建', '跳过'], + defaultId: 0, + title: '创建桌面快捷方式', + message: '是否在桌面创建快捷方式?', + detail: '方便您下次快速启动应用' + }); + + if (result.response === 0) { + // Windows 下创建快捷方式 + const success = shell.writeShortcutLink(shortcutPath, { + target: process.execPath, + cwd: path.dirname(process.execPath), + description: '南京灿能C端工具', + icon: process.execPath, + iconIndex: 0 + }); + + if (success) { + logger.info('[lifecycle] 桌面快捷方式创建成功'); + await dialog.showMessageBox({ + type: 'info', + title: '成功', + message: '桌面快捷方式已创建', + buttons: ['确定'] + }); + } else { + logger.error('[lifecycle] 桌面快捷方式创建失败'); + } + } else { + logger.info('[lifecycle] 用户跳过创建快捷方式'); + } + } catch (error) { + logger.error('[lifecycle] 创建快捷方式时出错:', error); + } + } + + async beforeClose() { + logger.info('[lifecycle] before-close'); + } +} +Lifecycle.toString = () => '[class Lifecycle]'; + +module.exports = { + Lifecycle +}; +``` + +#### 3. 打包命令 + +```bash +npm run build # 完整构建 +npm run build-w # 打包 Windows 便携版 +``` + +#### 4. 产物说明 + +打包完成后,在 `out/` 目录下会生成: +``` +out/ +└── 南京灿能工具-win-4.0.0-x64.exe (便携版,约 150-200MB) +``` + +--- + +## 方案二:双版本打包(安装版 + 便携版) + +### 特点 +- ✅ 提供两种版本供用户选择 +- ✅ 安装版:专业、完整的安装体验 +- ✅ 便携版:灵活、轻量,无需安装 +- ✅ 适合正式产品发布 + +### 实现步骤 + +#### 1. 修改打包配置 + +**文件**:[cmd/builder.json](../cmd/builder.json) + +```json +{ + "productName": "南京灿能工具", + "appId": "com.canneng.tool", + "copyright": "© 2025 hongawen", + "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": "南京灿能工具", + "artifactName": "${productName}-Setup-${version}.${ext}" + }, + "portable": { + "artifactName": "${productName}-Portable-${version}.${ext}" + }, + "publish": [ + { + "provider": "generic", + "url": "https://your-update-server.com" + } + ], + "win": { + "icon": "build/icons/icon.ico", + "target": [ + { + "target": "nsis", + "arch": ["x64"] + }, + { + "target": "portable", + "arch": ["x64"] + } + ] + } +} +``` + +#### 2. 便携版快捷方式功能(可选) + +如果希望便携版也能自动创建快捷方式,使用**方案一**中的 `createDesktopShortcut()` 代码。 + +#### 3. 打包命令 + +```bash +npm run build # 完整构建 +npm run build-w # 打包两个版本 +``` + +#### 4. 产物说明 + +打包完成后,在 `out/` 目录下会生成: +``` +out/ +├── 南京灿能工具-Setup-4.0.0.exe (安装版,约 150MB) +└── 南京灿能工具-Portable-4.0.0.exe (便携版,约 150-200MB) +``` + +#### 5. 版本差异说明 + +**安装版 (NSIS)**: +- 需要安装到系统(默认 C:\Program Files) +- 自动创建桌面快捷方式 +- 自动创建开始菜单项 +- 提供卸载程序 +- 支持自动更新 +- 适合企业部署、长期使用 + +**便携版 (Portable)**: +- 单个 exe 文件 +- 双击直接运行(首次会自解压) +- 无需安装,无需卸载 +- 可放在 U 盘随身携带 +- 适合临时使用、测试环境 + +--- + +## 快捷方式创建原理(技术细节) + +### Windows 快捷方式 (.lnk) + +```javascript +shell.writeShortcutLink(shortcutPath, { + target: process.execPath, // 目标程序路径 + cwd: path.dirname(process.execPath), // 工作目录 + description: '应用描述', // 快捷方式描述 + icon: process.execPath, // 图标路径 + iconIndex: 0, // 图标索引 + args: '', // 启动参数(可选) + appUserModelId: 'com.app.id' // Windows 应用 ID(可选) +}) +``` + +### 判断是否为便携版 + +```javascript +const isPortable = process.platform === 'win32' && + !process.execPath.includes('Program Files') && + !process.execPath.includes('AppData\\Local\\Programs'); +``` + +**原理**: +- 安装版通常安装在 `C:\Program Files\YourApp\` +- 或者 `C:\Users\用户名\AppData\Local\Programs\YourApp\` +- 便携版可以在任意位置运行 + +--- + +## 推荐配置 + +### 企业级应用(推荐方案二) +``` +✅ 提供两个版本 +✅ 主推安装版(专业形象) +✅ 提供便携版作为备选 +``` + +### 轻量工具(推荐方案一) +``` +✅ 只提供便携版 +✅ 应用内自动创建快捷方式 +✅ 简化分发流程 +``` + +--- + +## 常见问题 + +### Q1: 便携版首次启动为什么慢? +**A**: 便携版是自解压程序,首次运行需要解压资源到临时目录(约 3-5 秒)。后续启动会快很多。 + +### Q2: 便携版数据存储在哪里? +**A**: +- 用户数据:`C:\Users\用户名\AppData\Roaming\你的appId\` +- 临时文件:`C:\Users\用户名\AppData\Local\Temp\` + +### Q3: 如何让便携版也支持自动更新? +**A**: 需要配置 `electron-updater`,但便携版更新体验不如安装版。建议: +- 安装版:使用自动更新 +- 便携版:提示用户下载新版本 + +### Q4: 可以同时运行两个版本吗? +**A**: 不建议。虽然技术上可行,但会导致数据冲突(共享同一个 userData 目录)。 + +### Q5: 如何自定义快捷方式图标? +**A**: 在 `build/icons/` 目录放置 `.ico` 文件,并在 `builder.json` 中配置: +```json +"win": { + "icon": "build/icons/custom-icon.ico" +} +``` + +--- + +## 测试检查清单 + +打包完成后,请进行以下测试: + +### 安装版测试 +- [ ] 安装到默认路径成功 +- [ ] 安装到自定义路径成功 +- [ ] 桌面快捷方式正常 +- [ ] 开始菜单项正常 +- [ ] 应用启动正常 +- [ ] 卸载程序正常 + +### 便携版测试 +- [ ] 双击 exe 正常启动 +- [ ] 首次启动自动创建快捷方式(如已实现) +- [ ] 桌面快捷方式可用 +- [ ] 应用功能正常 +- [ ] 关闭后再次启动正常 +- [ ] 可移动到其他目录运行 + +--- + +## 参考资源 + +- electron-builder 官方文档: https://www.electron.build/ +- NSIS 配置: https://www.electron.build/configuration/nsis +- Portable 配置: https://www.electron.build/configuration/portable +- Electron shell API: https://www.electronjs.org/docs/latest/api/shell + +--- + +*文档创建时间: 2025-10-14* +*作者: hongawen* diff --git a/doc/生命周期描述.md b/doc/生命周期描述.md new file mode 100644 index 0000000..b575474 --- /dev/null +++ b/doc/生命周期描述.md @@ -0,0 +1,459 @@ +# ElectronEgg 生命周期详解 + +本文档详细说明 ElectronEgg 框架的应用生命周期机制及其在项目中的实现。 + +## 生命周期流程图 + +``` +┌─────────────┐ +│ new │ 创建 ElectronEgg 实例 +└──────┬──────┘ + │ +┌──────▼──────┐ +│ ready │ core app 加载完成(ee-core 框架初始化) +└──────┬──────┘ + │ +┌──────▼─────────────┐ +│ electronAppReady │ Electron app 加载完成 +└──────┬─────────────┘ + │ + ├─────────────────┐ + │ │ +┌──────▼──────┐ ┌─────▼────────┐ +│ mainWindow │ │ windowReady │ 主窗口创建完成 +└──────┬──────┘ └─────▲────────┘ + │ │ + └─────────────────┘ + │ +┌──────▼──────┐ +│ running │ 应用运行中 +└──────┬──────┘ + │ +┌──────▼──────┐ +│beforeClose │ 退出之前触发 +└──────┬──────┘ + │ +┌──────▼──────┐ +│ quit │ 应用退出 +└─────────────┘ +``` + +## 生命周期钩子详解 + +### 1. new - 实例创建 + +**触发时机**:调用 `new ElectronEgg()` 时 + +**实现位置**:[electron/main.js](electron/main.js#L6) + +```javascript +const { ElectronEgg } = require('ee-core'); +const app = new ElectronEgg(); +``` + +**作用**: +- 创建 ElectronEgg 应用实例 +- 初始化框架核心模块 +- 准备生命周期管理器 + +--- + +### 2. ready - 核心应用就绪 + +**触发时机**:ee-core 框架加载完成,Electron app 启动之前 + +**实现位置**: +- 注册:[electron/main.js](electron/main.js#L10) +- 实现:[electron/preload/lifecycle.js](electron/preload/lifecycle.js#L12-L14) + +```javascript +// 注册 +app.register("ready", life.ready); + +// 实现 +async ready() { + logger.info('[lifecycle] ready'); + // 在这里可以做: + // - 初始化数据库连接 + // - 加载配置文件 + // - 初始化全局变量 +} +``` + +**适用场景**: +- ✅ 初始化数据库连接 +- ✅ 加载应用配置 +- ✅ 注册全局服务 +- ✅ 初始化日志系统 +- ❌ 不能操作窗口(窗口还未创建) + +--- + +### 3. electronAppReady - Electron 应用就绪 + +**触发时机**:Electron 的 `app.ready` 事件触发后,主窗口创建之前 + +**实现位置**: +- 注册:[electron/main.js](electron/main.js#L11) +- 实现:[electron/preload/lifecycle.js](electron/preload/lifecycle.js#L19-L21) + +```javascript +// 注册 +app.register("electron-app-ready", life.electronAppReady); + +// 实现 +async electronAppReady() { + logger.info('[lifecycle] electron-app-ready'); + // 在这里可以做: + // - 注册全局快捷键 + // - 设置应用菜单 + // - 初始化托盘图标 + // - 注册协议处理 +} +``` + +**适用场景**: +- ✅ 注册全局快捷键 (globalShortcut) +- ✅ 创建应用菜单 (Menu) +- ✅ 创建系统托盘 (Tray) +- ✅ 注册自定义协议 (protocol) +- ⚠️ 可以创建窗口,但通常在框架内部自动创建 + +--- + +### 4. mainWindow - 主窗口创建 + +**触发时机**:框架创建主窗口时(内部流程,不需要手动注册) + +**说明**: +- 这是框架内部自动执行的步骤 +- 根据 `electron/config/config.*.js` 中的 `windowsOption` 配置创建窗口 +- 窗口创建完成后会触发 `windowReady` 钩子 + +**配置示例**: +```javascript +// electron/config/config.default.js +windowsOption: { + width: 1200, + height: 800, + show: false, // 设置为 false 可实现无白屏启动 + webPreferences: { + contextIsolation: false, + nodeIntegration: true + } +} +``` + +--- + +### 5. windowReady - 窗口就绪 + +**触发时机**:主窗口创建完成,页面加载完毕 + +**实现位置**: +- 注册:[electron/main.js](electron/main.js#L12) +- 实现:[electron/preload/lifecycle.js](electron/preload/lifecycle.js#L26-L37) + +```javascript +// 注册 +app.register("window-ready", life.windowReady); + +// 实现 +async windowReady() { + logger.info('[lifecycle] window-ready'); + + // 延迟显示窗口,避免白屏 + const { windowsOption } = getConfig(); + if (windowsOption.show == false) { + const win = getMainWindow(); + win.once('ready-to-show', () => { + win.show(); // 显示窗口 + win.focus(); // 聚焦窗口 + }) + } + + // 在这里可以做: + // - 向渲染进程发送初始化数据 + // - 检查更新 + // - 加载用户配置 +} +``` + +**适用场景**: +- ✅ 操作主窗口 (show/hide/maximize 等) +- ✅ 向渲染进程发送消息 +- ✅ 执行自动更新检查 +- ✅ 加载用户数据并同步到前端 +- ✅ 实现无白屏启动(配合 `show: false`) + +--- + +### 6. running - 应用运行中 + +**触发时机**:窗口显示后,应用正常运行期间 + +**说明**: +- 这不是一个独立的生命周期钩子 +- 表示应用的正常运行状态 +- 此时所有功能都可用 + +**可用操作**: +- IPC 通信(前后端交互) +- 业务逻辑处理 +- 数据库操作 +- 网络请求 +- 文件系统操作 + +--- + +### 7. beforeClose - 关闭前钩子 + +**触发时机**:用户点击关闭按钮或调用 `app.quit()` 之前 + +**实现位置**: +- 注册:[electron/main.js](electron/main.js#L13) +- 实现:[electron/preload/lifecycle.js](electron/preload/lifecycle.js#L42-L44) + +```javascript +// 注册 +app.register("before-close", life.beforeClose); + +// 实现 +async beforeClose() { + logger.info('[lifecycle] before-close'); + // 在这里可以做: + // - 保存用户数据 + // - 关闭数据库连接 + // - 清理临时文件 + // - 释放系统资源 + // - 确认是否退出 +} +``` + +**适用场景**: +- ✅ 保存应用状态 +- ✅ 关闭数据库连接 +- ✅ 清理临时资源 +- ✅ 询问用户是否确认退出 +- ✅ 上传日志或统计数据 + +**阻止关闭示例**: +```javascript +async beforeClose(args, event) { + const { dialog } = require('electron'); + const result = await dialog.showMessageBox({ + type: 'question', + buttons: ['取消', '退出'], + message: '确定要退出应用吗?' + }); + + if (result.response === 0) { + // 阻止关闭 + return false; + } + + // 允许关闭 + return true; +} +``` + +--- + +### 8. quit - 应用退出 + +**触发时机**:`beforeClose` 完成后,应用进程终止 + +**说明**: +- 这是最终状态,不可逆 +- 所有资源清理应在 `beforeClose` 中完成 +- 退出后进程结束,无法执行代码 + +--- + +## 额外生命周期:preload + +### preload - 预加载模块 + +**触发时机**:应用启动时,在所有其他钩子之前 + +**实现位置**: +- 注册:[electron/main.js](electron/main.js#L16) +- 实现:[electron/preload/index.js](electron/preload/index.js#L7-L9) + +```javascript +// 注册 +app.register("preload", preload); + +// 实现 +function preload() { + logger.info('[preload] load 1'); + // 在这里可以做: + // - 加载环境变量 + // - 注册原生模块 + // - 设置全局异常处理 +} +``` + +**适用场景**: +- ✅ 加载环境变量 +- ✅ 注册 Node.js 原生模块 +- ✅ 设置全局错误处理 +- ✅ 初始化第三方 SDK + +--- + +## 实际开发示例 + +### 示例 1:数据库初始化 + +```javascript +// electron/preload/lifecycle.js +const Database = require('better-sqlite3'); +let db; + +class Lifecycle { + async ready() { + // 在 ready 钩子中初始化数据库 + const path = require('path'); + const dbPath = path.join(app.getPath('userData'), 'app.db'); + db = new Database(dbPath); + logger.info('[lifecycle] database initialized'); + } + + async beforeClose() { + // 在关闭前关闭数据库连接 + if (db) { + db.close(); + logger.info('[lifecycle] database closed'); + } + } +} +``` + +### 示例 2:自动更新检查 + +```javascript +// electron/preload/lifecycle.js +const { autoUpdater } = require('electron-updater'); + +class Lifecycle { + async windowReady() { + // 窗口就绪后检查更新 + const { getMainWindow } = require('ee-core/electron'); + const win = getMainWindow(); + + autoUpdater.checkForUpdates(); + + autoUpdater.on('update-available', () => { + win.webContents.send('update-available'); + }); + + logger.info('[lifecycle] update check started'); + } +} +``` + +### 示例 3:托盘图标 + +```javascript +// electron/preload/lifecycle.js +const { Tray, Menu } = require('electron'); +let tray; + +class Lifecycle { + async electronAppReady() { + // 在 Electron 就绪后创建托盘 + const path = require('path'); + const iconPath = path.join(__dirname, '../../public/images/tray-icon.png'); + + tray = new Tray(iconPath); + const contextMenu = Menu.buildFromTemplate([ + { label: '打开主窗口', click: () => { + const { getMainWindow } = require('ee-core/electron'); + getMainWindow().show(); + }}, + { label: '退出', click: () => { + const { app } = require('electron'); + app.quit(); + }} + ]); + + tray.setContextMenu(contextMenu); + logger.info('[lifecycle] tray created'); + } +} +``` + +### 示例 4:无白屏启动 + +```javascript +// electron/config/config.default.js +windowsOption: { + width: 1200, + height: 800, + show: false, // 关键配置:初始不显示 + backgroundColor: '#ffffff' +} + +// electron/preload/lifecycle.js +class Lifecycle { + async windowReady() { + // 页面加载完成后再显示,避免白屏 + const { getMainWindow } = require('ee-core/electron'); + const win = getMainWindow(); + + win.once('ready-to-show', () => { + win.show(); + win.focus(); + }); + } +} +``` + +--- + +## 生命周期执行顺序总结 + +``` +1. preload() # 预加载模块 +2. new ElectronEgg() # 创建应用实例 +3. ready() # 核心框架就绪 +4. electronAppReady() # Electron 就绪 +5. [创建主窗口] # 框架内部创建窗口 +6. windowReady() # 窗口就绪 +7. [应用运行中] # 正常运行状态 +8. beforeClose() # 关闭前钩子 +9. [应用退出] # 进程结束 +``` + +--- + +## 注意事项 + +1. **异步支持**:所有生命周期钩子都支持 `async/await`,可以执行异步操作 + +2. **错误处理**:建议在每个钩子中添加 try-catch 错误处理 + +3. **顺序依赖**:不要在早期钩子中访问尚未初始化的资源(如在 `ready` 中操作窗口) + +4. **性能优化**:避免在钩子中执行耗时操作,会阻塞应用启动 + +5. **资源清理**:在 `beforeClose` 中务必清理所有资源,避免内存泄漏 + +6. **日志记录**:建议在每个钩子中记录日志,方便排查问题 + +--- + +## 相关文件 + +- 生命周期注册:[electron/main.js](electron/main.js) +- 生命周期实现:[electron/preload/lifecycle.js](electron/preload/lifecycle.js) +- 预加载模块:[electron/preload/index.js](electron/preload/index.js) +- 窗口配置:[electron/config/config.default.js](electron/config/config.default.js) + +--- + +## 参考资源 + +- ElectronEgg 官方文档:https://www.kaka996.com/pages/987b1c/ +- Electron 官方文档:https://www.electronjs.org/zh/docs/latest/ diff --git a/electron/README.md b/electron/README.md deleted file mode 100644 index d1eae6d..0000000 --- a/electron/README.md +++ /dev/null @@ -1,11 +0,0 @@ -#### 基础配置 - 主进程的配置文件在./config文件夹下 -```shell -# 说明 -bin.js // 开发配置 -config.default.js // 默认配置文件,开发环境和生产环境都会加载 -config.local.js // 开发环境配置文件,追加和覆盖default配置文件 -config.prod.js // 生产环境配置文件,追加和覆盖default配置文件 -nodemon.json // 开发环境,代码(监控)热加载 -builder.json // 打包配置 -``` \ No newline at end of file diff --git a/electron/addon/autoUpdater/index.js b/electron/addon/autoUpdater/index.js deleted file mode 100644 index 63baf58..0000000 --- a/electron/addon/autoUpdater/index.js +++ /dev/null @@ -1,170 +0,0 @@ -const { app: electronApp } = require('electron'); -const { autoUpdater } = require("electron-updater"); -const is = require('ee-core/utils/is'); -const Log = require('ee-core/log'); -const Conf = require('ee-core/config'); -const CoreWindow = require('ee-core/electron/window'); -const Electron = require('ee-core/electron'); - -/** - * 自动升级插件 - * @class - */ -class AutoUpdaterAddon { - - constructor() { - } - - /** - * 创建 - */ - create () { - Log.info('[addon:autoUpdater] load'); - const cfg = Conf.getValue('addons.autoUpdater'); - if ((is.windows() && cfg.windows) - || (is.macOS() && cfg.macOS) - || (is.linux() && cfg.linux)) - { - // continue - } else { - return - } - - // 是否检查更新 - if (cfg.force) { - this.checkUpdate(); - } - - const status = { - error: -1, - available: 1, - noAvailable: 2, - downloading: 3, - downloaded: 4, - } - - const version = electronApp.getVersion(); - Log.info('[addon:autoUpdater] current version: ', version); - - // 设置下载服务器地址 - let server = cfg.options.url; - let lastChar = server.substring(server.length - 1); - server = lastChar === '/' ? server : server + "/"; - //Log.info('[addon:autoUpdater] server: ', server); - cfg.options.url = server; - - // 是否后台自动下载 - autoUpdater.autoDownload = cfg.force ? true : false; - - try { - autoUpdater.setFeedURL(cfg.options); - } catch (error) { - Log.error('[addon:autoUpdater] setFeedURL error : ', error); - } - - autoUpdater.on('checking-for-update', () => { - //sendStatusToWindow('正在检查更新...'); - }) - autoUpdater.on('update-available', (info) => { - info.status = status.available; - info.desc = '有可用更新'; - this.sendStatusToWindow(info); - }) - autoUpdater.on('update-not-available', (info) => { - info.status = status.noAvailable; - info.desc = '没有可用更新'; - this.sendStatusToWindow(info); - }) - autoUpdater.on('error', (err) => { - let info = { - status: status.error, - desc: err - } - this.sendStatusToWindow(info); - }) - autoUpdater.on('download-progress', (progressObj) => { - let percentNumber = parseInt(progressObj.percent); - let totalSize = this.bytesChange(progressObj.total); - let transferredSize = this.bytesChange(progressObj.transferred); - let text = '已下载 ' + percentNumber + '%'; - text = text + ' (' + transferredSize + "/" + totalSize + ')'; - - let info = { - status: status.downloading, - desc: text, - percentNumber: percentNumber, - totalSize: totalSize, - transferredSize: transferredSize - } - Log.info('[addon:autoUpdater] progress: ', text); - this.sendStatusToWindow(info); - }) - autoUpdater.on('update-downloaded', (info) => { - info.status = status.downloaded; - info.desc = '下载完成'; - this.sendStatusToWindow(info); - - // 托盘插件默认会阻止窗口关闭,这里设置允许关闭窗口 - Electron.extra.closeWindow = true; - - autoUpdater.quitAndInstall(); - // const mainWindow = CoreWindow.getMainWindow(); - // if (mainWindow) { - // mainWindow.destroy() - // } - // electronApp.appQuit() - }); - } - - /** - * 检查更新 - */ - checkUpdate () { - autoUpdater.checkForUpdates(); - } - - /** - * 下载更新 - */ - download () { - autoUpdater.downloadUpdate(); - } - - /** - * 向前端发消息 - */ - sendStatusToWindow(content = {}) { - const textJson = JSON.stringify(content); - const channel = 'app.updater'; - const win = CoreWindow.getMainWindow(); - win.webContents.send(channel, textJson); - } - - /** - * 单位转换 - */ - bytesChange (limit) { - 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; - } -} - -AutoUpdaterAddon.toString = () => '[class AutoUpdaterAddon]'; -module.exports = AutoUpdaterAddon; \ No newline at end of file diff --git a/electron/addon/awaken/index.js b/electron/addon/awaken/index.js deleted file mode 100644 index e4c8a35..0000000 --- a/electron/addon/awaken/index.js +++ /dev/null @@ -1,67 +0,0 @@ -const { app: electronApp } = require('electron'); -const Log = require('ee-core/log'); -const Conf = require('ee-core/config'); - -/** - * 唤醒插件 - * @class - */ -class AwakenAddon { - - constructor() { - this.protocol = ''; - } - - /** - * 创建 - */ - create () { - Log.info('[addon:awaken] load'); - - const cfg = Conf.getValue('addons.awaken'); - this.protocol = cfg.protocol; - - electronApp.setAsDefaultProtocolClient(this.protocol); - - this.handleArgv(process.argv); - electronApp.on('second-instance', (event, argv) => { - if (process.platform === 'win32') { - this.handleArgv(argv) - } - }) - - // 仅用于macOS - electronApp.on('open-url', (event, urlStr) => { - this.handleUrl(urlStr) - }) - } - - /** - * 参数处理 - */ - handleArgv(argv) { - const offset = electronApp.isPackaged ? 1 : 2; - const url = argv.find((arg, i) => i >= offset && arg.startsWith(this.protocol)); - this.handleUrl(url) - } - - /** - * url解析 - */ - handleUrl(awakeUrlStr) { - if (!awakeUrlStr || awakeUrlStr.length === 0) { - return - } - const {hostname, pathname, search} = new URL(awakeUrlStr); - let awakeUrlInfo = { - urlStr: awakeUrlStr, - urlHost: hostname, - urlPath: pathname, - urlParams: search && search.slice(1) - } - Log.info('[addon:awaken] awakeUrlInfo:', awakeUrlInfo); - } -} - -AwakenAddon.toString = () => '[class AwakenAddon]'; -module.exports = AwakenAddon; \ No newline at end of file diff --git a/electron/addon/chromeExtension/index.js b/electron/addon/chromeExtension/index.js deleted file mode 100644 index 28ec636..0000000 --- a/electron/addon/chromeExtension/index.js +++ /dev/null @@ -1,94 +0,0 @@ -const { app, session } = require('electron'); -const _ = require('lodash'); -const fs = require('fs'); -const path = require('path'); -const Log = require('ee-core/log'); - -/** - * 扩展插件 (electron自身对该功能并不完全支持,官方也不建议使用) - * @class - */ -class ChromeExtensionAddon { - - constructor() { - } - - /** - * 创建 - */ - async create () { - Log.info('[addon:chromeExtension] load'); - - const extensionIds = this.getAllIds(); - for (let i = 0; i < extensionIds.length; i++) { - await this.load(extensionIds[i]); - } - } - - /** - * 获取扩展id列表(crx解压后的目录名,即是该扩展的id) - */ - getAllIds () { - const extendsionDir = this.getDirectory(); - const ids = this.getDirs(extendsionDir); - - return ids; - } - - /** - * 扩展所在目录 - */ - getDirectory () { - let extensionDirPath = ''; - let variablePath = 'build'; // 打包前路径 - if (app.isPackaged) { - variablePath = '..'; // 打包后路径 - } - extensionDirPath = path.join(app.getAppPath(), variablePath, "extraResources", "chromeExtension"); - - return extensionDirPath; - } - - /** - * 加载扩展 - */ - async load (extensionId = '') { - if (_.isEmpty(extensionId)) { - return false - } - - try { - const extensionPath = path.join(this.getDirectory(), extensionId); - Log.info('[addon:chromeExtension] extensionPath:', extensionPath); - await session.defaultSession.loadExtension(extensionPath, { allowFileAccess: true }); - } catch (e) { - Log.info('[addon:chromeExtension] load extension error extensionId:%s, errorInfo:%s', extensionId, e.toString()); - return false - } - - return true - } - - /** - * 获取目录下所有文件夹 - */ - getDirs(dir) { - if (!dir) { - return []; - } - - const components = []; - const files = fs.readdirSync(dir); - files.forEach(function(item, index) { - const stat = fs.lstatSync(dir + '/' + item); - if (stat.isDirectory() === true) { - components.push(item); - } - }); - - return components; - }; -} - -ChromeExtensionAddon.toString = () => '[class ChromeExtensionAddon]'; -module.exports = ChromeExtensionAddon; \ No newline at end of file diff --git a/electron/addon/security/index.js b/electron/addon/security/index.js deleted file mode 100644 index 4d87e94..0000000 --- a/electron/addon/security/index.js +++ /dev/null @@ -1,33 +0,0 @@ -const Log = require('ee-core/log'); -const EE = require('ee-core/ee'); - -/** - * 安全插件 - * @class - */ -class SecurityAddon { - - constructor() { - } - - /** - * 创建 - */ - create () { - Log.info('[addon:security] load'); - const { CoreApp } = EE; - const runWithDebug = process.argv.find(function(e){ - let isHasDebug = e.includes("--inspect") || e.includes("--inspect-brk") || e.includes("--remote-debugging-port"); - return isHasDebug; - }) - - // 不允许远程调试 - if (runWithDebug) { - Log.error('[error] Remote debugging is not allowed, runWithDebug:', runWithDebug); - CoreApp.appQuit(); - } - } -} - -SecurityAddon.toString = () => '[class SecurityAddon]'; -module.exports = SecurityAddon; \ No newline at end of file diff --git a/electron/addon/tray/index.js b/electron/addon/tray/index.js deleted file mode 100644 index 106f06f..0000000 --- a/electron/addon/tray/index.js +++ /dev/null @@ -1,72 +0,0 @@ -const { Tray, Menu } = require('electron'); -const path = require('path'); -const Ps = require('ee-core/ps'); -const Log = require('ee-core/log'); -const Electron = require('ee-core/electron'); -const CoreWindow = require('ee-core/electron/window'); -const Conf = require('ee-core/config'); -const EE = require('ee-core/ee'); - -/** - * 托盘插件 - * @class - */ -class TrayAddon { - - constructor() { - this.tray = null; - } - - /** - * 创建托盘 - */ - create () { - // 开发环境,代码热更新开启时,会导致托盘中有残影 - if (Ps.isDev() && Ps.isHotReload()) return; - - Log.info('[addon:tray] load'); - const { CoreApp } = EE; - const cfg = Conf.getValue('addons.tray'); - const mainWindow = CoreWindow.getMainWindow(); - - // 托盘图标 - let iconPath = path.join(Ps.getHomeDir(), cfg.icon); - - // 托盘菜单功能列表 - let trayMenuTemplate = [ - { - label: '显示', - click: function () { - mainWindow.show(); - } - }, - { - label: '退出', - click: function () { - CoreApp.appQuit(); - } - } - ] - - // 点击关闭,最小化到托盘 - mainWindow.on('close', (event) => { - if (Electron.extra.closeWindow == true) { - return; - } - mainWindow.hide(); - event.preventDefault(); - }); - - // 实例化托盘 - this.tray = new Tray(iconPath); - this.tray.setToolTip(cfg.title); - const contextMenu = Menu.buildFromTemplate(trayMenuTemplate); - this.tray.setContextMenu(contextMenu); - this.tray.on('double-click', () => { - mainWindow.show() - }) - } -} - -TrayAddon.toString = () => '[class TrayAddon]'; -module.exports = TrayAddon; \ No newline at end of file diff --git a/electron/config/bin.js b/electron/config/bin.js deleted file mode 100644 index 52bd537..0000000 --- a/electron/config/bin.js +++ /dev/null @@ -1,108 +0,0 @@ -/** - * ee-bin 配置 - * 仅适用于开发环境 - */ -module.exports = { - /** - * development serve ("frontend" "electron" ) - * ee-bin dev - */ - dev: { - frontend: { - directory: './frontend', - cmd: 'npm', - args: ['run', 'dev'], - protocol: 'http://', - hostname: 'localhost', - port: 18091, - indexPath: 'index.html' - }, - electron: { - directory: './', - cmd: 'electron', - args: ['.', '--env=local', '--color=always'], // --env: local|prod; '--color=always' 控制台颜色 - } - }, - - /** - * 构建 - * ee-bin build - */ - build: { - frontend: { - directory: './frontend', - cmd: 'npm', - args: ['run', 'build'], - } - }, - - /** - * 移动资源 - * ee-bin move - */ - move: { - frontend_dist: { - dist: './frontend/dist', - target: './public/dist' - } - }, - - /** - * 预发布模式(prod) - * ee-bin start - */ - start: { - directory: './', - cmd: 'electron', - args: ['.', '--env=prod'] - }, - - /** - * 加密 - */ - encrypt: { - // confusion - 压缩混淆加密 - // bytecode - 字节码加密 - // strict - 先混淆加密,然后字节码加密 - type: 'confusion', - // 文件匹配 - // ! 符号开头的意思是过滤 - files: [ - 'electron/**/*.(js|json)', - '!electron/config/encrypt.js', - '!electron/config/nodemon.json', - '!electron/config/builder.json', - '!electron/config/bin.json', - ], - // 需要加密的文件后缀,暂时只支持js - fileExt: ['.js'], - // 混淆加密配置 - confusionOptions: { - // 压缩成一行 - compact: true, - // 删除字符串文字并将其放置在一个特殊数组中 - stringArray: true, - // 对stringArray的所有字符串文字进行编码,值:'none' | 'base64' | 'rc4' - stringArrayEncoding: ['none'], - // 注入死代码,注:影响性能 - deadCodeInjection: false, - } - }, - - /** - * 执行自定义命令 - * ee-bin exec - */ - exec: { - node_v: { - directory: './', - cmd: 'node', - args: ['-v'], - }, - npm_v: { - directory: './', - cmd: 'npm', - args: ['-v'], - }, - }, -}; diff --git a/electron/config/config.default.js b/electron/config/config.default.js index d4e12d9..e7728c4 100644 --- a/electron/config/config.default.js +++ b/electron/config/config.default.js @@ -1,185 +1,71 @@ 'use strict'; const path = require('path'); +const {getBaseDir} = require('ee-core/ps'); /** * 默认配置 */ -module.exports = (appInfo) => { - - const config = {}; - - /** - * 开发者工具 - */ - config.openDevTools = false; - - /** - * 应用程序顶部菜单 - */ - config.openAppMenu = true; - - /** - * 1.507 - * 主窗口 - */ - config.windowsOption = { - title: '自动检测平台', - width: 1920 /1.5, - height: 1080 /1.2, - minWidth: 1920 /1.5, - minHeight: 1080 /1.2, - webPreferences: { - //webSecurity: false, - contextIsolation: false, // false -> 可在渲染进程中使用electron的api,true->需要bridge.js(contextBridge) - nodeIntegration: true, - //preload: path.join(appInfo.baseDir, 'preload', 'bridge.js'), - }, - frame: true, - show: false, - icon: path.join(appInfo.home, 'public', 'images', 'logo-32.png'), - }; - - /** - * ee框架日志 - */ - config.logger = { - encoding: 'utf8', - level: 'INFO', - outputJSON: false, - buffer: true, - enablePerformanceTimer: false, - rotator: 'day', - appLogName: 'pqs9100.log', - coreLogName: 'pqs9100-core.log', - errorLogName: 'pqs9100-error.log' - } - - /** - * 远程模式-web地址 - */ - config.remoteUrl = { - enable: false, - url: 'http://electron-egg.kaka996.com/' - }; - - /** - * 内置socket服务 - */ - config.socketServer = { - enable: false, // 是否开启 - port: 7070,// 默认端口 - path: "/socket.io/", // 默认路径名称 - connectTimeout: 45000, // 客户端连接超时时间 - pingTimeout: 30000, // 心跳检测超时时间 - pingInterval: 25000, // 心跳检测间隔时间 - maxHttpBufferSize: 1e8, // 每条消息的数据最大值 - transports: ["polling", "websocket"], // http轮询和websocket - cors: { - origin: true, // http协议时,需要设置允许跨域 - }, - channel: 'c1' // 默认频道c1,可以自定义 - }; - - /** - * 内置http服务 - */ - config.httpServer = { - enable: false, - https: { - enable: false, - key: '/public/ssl/localhost+1.key', - cert: '/public/ssl/localhost+1.pem' - }, - host: '127.0.0.1', - port: 7071, - cors: { - origin: "*" // 默认允许跨域 - }, - body: { - multipart: true, - formidable: { - keepExtensions: true - } - }, - filterRequest: { - uris: [ - 'favicon.ico' // 默认过滤的uri favicon.ico - ], - returnData: '' +module.exports = () => { + return { + openDevTools: false, + singleLock: true, + windowsOption: { + title: 'NPQS9100-自动检测平台', + menuBarVisible: false, + width: 1920, + height: 1000, + minWidth: 1024, + minHeight: 640, + webPreferences: { + //webSecurity: false, + contextIsolation: false, // false -> 可在渲染进程中使用electron的api,true->需要bridge.js(contextBridge) + nodeIntegration: true, + //preload: path.join(getElectronDir(), 'preload', 'bridge.js'), + }, + frame: true, + show: true, + icon: path.join(getBaseDir(), 'public', 'images', 'logo-32.png'), + }, + logger: { + level: 'INFO', + outputJSON: false, + appLogName: '9100.log', + coreLogName: '9100-core.log', + errorLogName: '9100-error.log' + }, + // 远程web地址 + remote: { + enable: false, + url: '' + }, + socketServer: { + enable: false, + port: 7070, + path: "/socket.io/", + connectTimeout: 45000, + pingTimeout: 30000, + pingInterval: 25000, + maxHttpBufferSize: 1e8, + transports: ["polling", "websocket"], + cors: { + origin: true, + }, + channel: 'socket-channel' + }, + httpServer: { + enable: false, + 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', + channelSeparator: '/', + } } - }; - - /** - * 主进程 - */ - config.mainServer = { - protocol: 'file://', - indexPath: '/public/dist/index.html', - }; - - /** - * 硬件加速 - */ - config.hardGpu = { - enable: true - }; - - /** - * 异常捕获 - */ - config.exception = { - mainExit: false, // 主进程退出时是否捕获异常 - childExit: true, - rendererExit: true, - }; - - /** - * jobs - */ - config.jobs = { - messageLog: true // 是否打印进程间通信的消息log - }; - - /** - * 插件功能 - * @param window 官方内置插件 - * @param tray 托盘插件 - * @param security 安全插件 - * @param awaken 唤醒插件 - * @param autoUpdater 自动升级插件 - */ - config.addons = { - window: { - enable: true, - }, - tray: { - enable: true, - title: '自动检测平台', - icon: '/public/images/tray.png' - }, - security: { - enable: true, - }, - awaken: { - enable: true, - protocol: 'ee', - args: [] - }, - autoUpdater: { - enable: true, - windows: false, - macOS: false, - linux: false, - options: { - provider: 'generic', - url: 'http://kodo.qiniu.com/' - }, - force: false, - } - }; - - return { - ...config - }; } diff --git a/electron/config/config.local.js b/electron/config/config.local.js index 18a910d..287fe84 100644 --- a/electron/config/config.local.js +++ b/electron/config/config.local.js @@ -1,31 +1,13 @@ 'use strict'; /** - * 开发环境配置,覆盖 config.default.js + * Development environment configuration, coverage config.default.js */ -module.exports = (appInfo) => { - const config = {}; - - /** - * 开发者工具 - */ - config.openDevTools = { - mode: 'undocked' - }; - - /** - * 应用程序顶部菜单 - */ - config.openAppMenu = false; - - /** - * jobs - */ - config.jobs = { - messageLog: true - }; - +module.exports = () => { return { - ...config + openDevTools: false, + jobs: { + messageLog: false + } }; }; diff --git a/electron/config/config.prod.js b/electron/config/config.prod.js index 5c5218d..db1ce01 100644 --- a/electron/config/config.prod.js +++ b/electron/config/config.prod.js @@ -1,29 +1,10 @@ 'use strict'; /** - * 生产环境配置,覆盖 config.default.js + * coverage config.default.js */ -module.exports = (appInfo) => { - const config = {}; - - /** - * 开发者工具 - */ - config.openDevTools = false; - - /** - * 应用程序顶部菜单 - */ - config.openAppMenu = false; - - /** - * jobs - */ - config.jobs = { - messageLog: false - }; - +module.exports = () => { return { - ...config + openDevTools: false, }; }; diff --git a/electron/config/nodemon.json b/electron/config/nodemon.json deleted file mode 100644 index 96a9fe7..0000000 --- a/electron/config/nodemon.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "watch": [ - "electron/", - "main.js" - ], - "ignore": [], - "ext": "js,json", - "verbose": true, - "exec": "electron . --env=local --hot-reload=1", - "restartable": "hr", - "colours": true, - "events": {} -} \ No newline at end of file diff --git a/electron/controller/example.js b/electron/controller/example.js new file mode 100644 index 0000000..32f55f0 --- /dev/null +++ b/electron/controller/example.js @@ -0,0 +1,30 @@ +'use strict'; + +const { logger } = require('ee-core/log'); +const { exampleService } = require('../service/example'); + +/** + * example + * @class + */ +class ExampleController { + + /** + * 所有方法接收两个参数 + * @param args 前端传的参数 + * @param event - ipc通信时才有值。详情见:控制器文档 + */ + + /** + * test + */ + async test () { + const result = await exampleService.test('electron'); + logger.info('service result:', result); + + return 'hello electron-egg'; + } +} +ExampleController.toString = () => '[class ExampleController]'; + +module.exports = ExampleController; \ No newline at end of file diff --git a/electron/index.js b/electron/index.js deleted file mode 100644 index c0028e2..0000000 --- a/electron/index.js +++ /dev/null @@ -1,50 +0,0 @@ -const { Application } = require('ee-core'); - -class Index extends Application { - - constructor() { - super(); - // this === eeApp; - } - - /** - * core app have been loaded - */ - async ready () { - // do some things - } - - /** - * electron app ready - */ - async electronAppReady () { - // do some things - } - - /** - * main window have been loaded - */ - async windowReady () { - // do some things - // 延迟加载,无白屏 - const winOpt = this.config.windowsOption; - if (winOpt.show == false) { - const win = this.electron.mainWindow; - win.once('ready-to-show', () => { - win.show(); - win.focus(); - }) - } - } - - /** - * before app close - */ - async beforeClose () { - // do some things - - } -} - -Index.toString = () => '[class Index]'; -module.exports = Index; \ No newline at end of file diff --git a/electron/jobs/example/hello.js b/electron/jobs/example/hello.js deleted file mode 100644 index 66dc1dc..0000000 --- a/electron/jobs/example/hello.js +++ /dev/null @@ -1,5 +0,0 @@ -const Log = require('ee-core/log'); - -exports.welcome = function () { - Log.info('[child-process] [jobs/example/hello] welcome ! '); -} \ No newline at end of file diff --git a/electron/jobs/example/timer.js b/electron/jobs/example/timer.js deleted file mode 100644 index 343b1d8..0000000 --- a/electron/jobs/example/timer.js +++ /dev/null @@ -1,29 +0,0 @@ -const Job = require('ee-core/jobs/baseJobClass'); -const Log = require('ee-core/log'); -const Ps = require('ee-core/ps'); - -/** - * example - TimerJob - * @class - */ -class TimerJob extends Job { - - constructor(params) { - super(); - this.params = params; - } - - /** - * handle()方法是必要的,且会被自动调用 - */ - async handle () { - Log.info("[child-process] TimerJob params: ", this.params); - - if (Ps.isChildJob()) { - Ps.exit(); - } - } -} - -TimerJob.toString = () => '[class TimerJob]'; -module.exports = TimerJob; diff --git a/electron/main.js b/electron/main.js new file mode 100644 index 0000000..b005896 --- /dev/null +++ b/electron/main.js @@ -0,0 +1,19 @@ +const { ElectronEgg } = require('ee-core'); +const { Lifecycle } = require('./preload/lifecycle'); +const { preload } = require('./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/index.js b/electron/preload/index.js index 36d8349..555170f 100644 --- a/electron/preload/index.js +++ b/electron/preload/index.js @@ -1,14 +1,16 @@ /************************************************* ** preload为预加载模块,该文件将会在程序启动时加载 ** *************************************************/ -const Addon = require('ee-core/addon'); -/** -* 预加载模块入口 -*/ -module.exports = async () => { - - // 示例功能模块,可选择性使用和修改 - Addon.get('tray').create(); - Addon.get('security').create(); -} \ No newline at end of file + const { logger } = require('ee-core/log'); + + function preload() { + logger.info('[preload] load 1'); + } + + /** + * 预加载模块入口 + */ + module.exports = { + preload + } \ No newline at end of file diff --git a/electron/preload/lifecycle.js b/electron/preload/lifecycle.js new file mode 100644 index 0000000..927d3f2 --- /dev/null +++ b/electron/preload/lifecycle.js @@ -0,0 +1,58 @@ +'use strict'; + +const { logger } = require('ee-core/log'); +const { getConfig } = require('ee-core/config'); +const { getMainWindow } = require('ee-core/electron'); + +class Lifecycle { + + /** + * core app have been loaded + */ + async ready() { + logger.info('[lifecycle] ready'); + // 在这里可以做: + // - 初始化数据库连接 + // - 加载配置文件 + // - 初始化全局变量 + } + + /** + * electron app ready + */ + async electronAppReady() { + logger.info('[lifecycle] electron-app-ready'); + } + + /** + * main window have been loaded + */ + async windowReady() { + logger.info('[lifecycle] window-ready'); + + // 延迟加载,无白屏 + const win = getMainWindow(); + const { windowsOption } = getConfig(); + if (windowsOption.show == false) { + win.once('ready-to-show', () => { + win.show(); + win.focus(); + }) + } else { + win.show(); + win.focus(); + } + } + + /** + * before app close + */ + async beforeClose() { + logger.info('[lifecycle] before-close'); + } +} +Lifecycle.toString = () => '[class Lifecycle]'; + +module.exports = { + Lifecycle +}; \ No newline at end of file diff --git a/electron/service/example.js b/electron/service/example.js new file mode 100644 index 0000000..cf55fb6 --- /dev/null +++ b/electron/service/example.js @@ -0,0 +1,30 @@ +'use strict'; + +const { logger } = require('ee-core/log'); + +/** + * 示例服务 + * @class + */ +class ExampleService { + + /** + * test + */ + async test(args) { + let obj = { + status:'ok', + params: args + } + + logger.info('ExampleService obj:', obj); + + return obj; + } +} +ExampleService.toString = () => '[class ExampleService]'; + +module.exports = { + ExampleService, + exampleService: new ExampleService() +}; \ No newline at end of file diff --git a/frontend/.env.development b/frontend/.env.development index 55c97ff..5ac59f8 100644 --- a/frontend/.env.development +++ b/frontend/.env.development @@ -20,8 +20,8 @@ VITE_API_URL=/api # 开发环境跨域代理,支持配置多个 #VITE_PROXY=[["/api","http://127.0.0.1:18092/"]] -VITE_PROXY=[["/api","http://192.168.1.124:18092/"]] +VITE_PROXY=[["/api","http://192.168.1.125:18092/"]] #VITE_PROXY=[["/api","http://192.168.1.125:18092/"]] # VITE_PROXY=[["/api","http://192.168.1.138:8080/"]]张文 # 开启激活验证 -VITE_ACTIVATE_OPEN=true \ No newline at end of file +VITE_ACTIVATE_OPEN=false \ No newline at end of file diff --git a/frontend/.env.production b/frontend/.env.production index 9102cda..1650ed9 100644 --- a/frontend/.env.production +++ b/frontend/.env.production @@ -25,4 +25,4 @@ VITE_PWA=true #VITE_API_URL="/api" # 打包时用 VITE_API_URL="http://192.168.1.125:18092/" # 开启激活验证 -VITE_ACTIVATE_OPEN=true \ No newline at end of file +VITE_ACTIVATE_OPEN=false \ No newline at end of file diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 4248030..957bd38 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -6,7 +6,6 @@ - + diff --git a/frontend/src/utils/elementBind.ts b/frontend/src/utils/elementBind.ts index 2591fef..8db83d4 100644 --- a/frontend/src/utils/elementBind.ts +++ b/frontend/src/utils/elementBind.ts @@ -1,17 +1,23 @@ export const dialogSmall = { - width:'500px', + width:'26vw', + maxWidth:'500px', + minWidth:'300px', closeOnClickModal:false, draggable:true, class:'dialog-small' } export const dialogMiddle = { - width:'800px', + width:'42vw', + maxWidth:'800px', + minWidth:'600px', closeOnClickModal:false, draggable:true, class:'dialog-middle' } export const dialogBig = { - width:'1200px', + width:'62vw', + maxWidth:'1200px', + minWidth:'800px', closeOnClickModal:false, draggable:true, class:'dialog-big', diff --git a/frontend/src/utils/ipcRenderer.js b/frontend/src/utils/ipcRenderer.js deleted file mode 100644 index df2e575..0000000 --- a/frontend/src/utils/ipcRenderer.js +++ /dev/null @@ -1,39 +0,0 @@ -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/ipcRenderer.ts b/frontend/src/utils/ipcRenderer.ts new file mode 100644 index 0000000..5946cb2 --- /dev/null +++ b/frontend/src/utils/ipcRenderer.ts @@ -0,0 +1,119 @@ +/** + * Electron IPC Renderer 类型定义 + */ + +interface IpcRenderer { + /** + * 发送异步消息(invoke/handle 模型) + * @param channel 通道名称 + * @param param 传递的参数 + * @returns Promise 返回结果 + */ + invoke(channel: string, param?: any): Promise; + + /** + * 发送同步消息(send/on 模型) + * @param channel 通道名称 + * @param param 传递的参数 + * @returns 返回结果 + */ + sendSync(channel: string, param?: any): any; + + /** + * 监听 channel,当新消息到达,调用 listener + * @param channel 通道名称 + * @param listener 监听器函数 + */ + on(channel: string, listener: (event: any, ...args: any[]) => void): void; + + /** + * 添加一次性 listener 函数 + * @param channel 通道名称 + * @param listener 监听器函数 + */ + once(channel: string, listener: (event: any, ...args: any[]) => void): void; + + /** + * 为特定的 channel 从监听队列中删除特定的 listener 监听者 + * @param channel 通道名称 + * @param listener 要移除的监听器函数 + */ + removeListener(channel: string, listener: (event: any, ...args: any[]) => void): void; + + /** + * 移除所有的监听器,当指定 channel 时只移除与其相关的所有监听器 + * @param channel 通道名称(可选) + */ + removeAllListeners(channel?: string): void; + + /** + * 通过 channel 向主进程发送异步消息 + * @param channel 通道名称 + * @param args 传递的参数 + */ + send(channel: string, ...args: any[]): void; + + /** + * 发送消息到主进程 + * @param channel 通道名称 + * @param message 消息内容 + * @param transfer 传输对象(可选) + */ + postMessage(channel: string, message: any, transfer?: MessagePort[]): void; + + /** + * 通过 channel 发送消息到带有 webContentsId 的窗口 + * @param webContentsId 目标窗口的 ID + * @param channel 通道名称 + * @param args 传递的参数 + */ + sendTo(webContentsId: number, channel: string, ...args: any[]): void; + + /** + * 消息会被发送到 host 页面上的 元素 + * @param channel 通道名称 + * @param args 传递的参数 + */ + sendToHost(channel: string, ...args: any[]): void; +} + +interface Electron { + ipcRenderer?: IpcRenderer; +} + +const Renderer = (window.require && window.require('electron')) || (window as any).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: IpcRenderer | undefined = Renderer.ipcRenderer; + +/** + * 是否为EE环境 + */ +const isEE: boolean = ipc ? true : false; + +export { + type IpcRenderer, + type Electron, + Renderer, + ipc, + isEE +}; \ No newline at end of file diff --git a/frontend/src/views/home/components/compareTestPopup.vue b/frontend/src/views/home/components/compareTestPopup.vue index bc7e36c..389b58d 100644 --- a/frontend/src/views/home/components/compareTestPopup.vue +++ b/frontend/src/views/home/components/compareTestPopup.vue @@ -1,7 +1,8 @@