diff --git a/.tauri/signing-key b/.tauri/signing-key new file mode 100644 index 0000000..0cfba4e --- /dev/null +++ b/.tauri/signing-key @@ -0,0 +1 @@ +dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5eVJXUG1sdWpVNnBJUHpXSjFZWnczeVpPVjNZMEVvOGFkQlB5UFZDSFNEc0FBQkFBQUFBQUFBQUFBQUlBQUFBQVVmWE1BVXlmNXNqRFRmSEhSU3ltY3hqT1h4aE1hRVNEenJ2RUhkZ0JZaFJWc1owb1hpY3UwaEdqeTNQelRYaVd4M3E4WWR2MTloYWJSZFVTQm5scUhaTjRKOXlqeVlPRVpybWZTY2p2cUQzNkpDZGZGRzBtTEgvdFVBajNzdnBPTXVROFZXQXFUTGc9Cg== \ No newline at end of file diff --git a/.tauri/signing-key.pub b/.tauri/signing-key.pub new file mode 100644 index 0000000..e3e6dd4 --- /dev/null +++ b/.tauri/signing-key.pub @@ -0,0 +1 @@ +dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDY0Q0YxMzNBM0Q5OEU4NEMKUldSTTZKZzlPaFBQWkVXRVdBVHdTdlNvVnZSL2FFQkZtbFpRTzdjNGxHbjJsZ1R5MFRLdkpmYzMK \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index d507e16..97d4022 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## 常用命令 -项目使用 **npm** 作为包管理器(存在 package-lock.json)。 +项目使用 **pnpm** 作为包管理器(存在 package-lock.json)。 ```bash # 启动开发模式(前端 + Tauri) @@ -18,7 +18,7 @@ pnpm run tauri dev pnpm run dev # 构建生产版本 -npm run tauri build +pnpm run tauri build # 前端类型检查 + 构建 pnpm run build @@ -60,7 +60,7 @@ cd src-tauri && cargo clippy - `src-tauri/capabilities/default.json` - Tauri 权限配置(HTTP 白名单、插件权限) - `src-tauri/tauri.conf.json` - 应用配置(窗口、图标、bundle) -- 用户配置文件路径:`%APPDATA%/tauri-app/config.json`(Windows) +- 用户配置文件路径:`%APPDATA%/work-order-monitor/config.json`(Windows) ## 依赖的 Tauri 插件 diff --git a/README_APP.md b/README_APP.md index 31b17d5..024db1c 100644 --- a/README_APP.md +++ b/README_APP.md @@ -1,137 +1,127 @@ -# 系统监测应用程序 +# 工单系统监测 -这是一个基于 Tauri + Vue 开发的系统监测应用程序,具有以下功能: +基于 **Tauri 2.x + Vue 3** 的工单监测桌面应用,用于监测 CRM 系统的待处理工单并发送系统通知。 -## 功能特性 +## 环境要求 -1. **用户设置界面** - 配置用户名和密码 -2. **系统托盘集成** - 应用启动后自动最小化到系统托盘 -3. **自动登录** - 保存用户凭据并支持自动登录 -4. **定时API调用** - 登录后每1分钟调用一次接口 -5. **系统通知** - 根据API返回值弹出系统消息 -6. **模块化架构** - 代码按功能模块组织,便于维护 +- Node.js >= 18 +- Rust (stable) +- Visual Studio Build Tools(Windows,含 C++ 工具链) + +## 开发 + +```bash +# 安装依赖 +pnpm install + +# 启动开发模式(前端 + Tauri) +pnpm tauri dev + +# 仅启动前端 +pnpm dev +``` + +## 构建 + +签名密钥已包含在项目中(`.tauri/` 目录),构建脚本会自动加载,直接运行: + +```bash +pnpm run tauri-build +``` + +构建产物位于 `src-tauri/target/release/bundle/nsis/`。 + +## 版本号管理 + +使用自定义脚本统一更新 `package.json`、`tauri.conf.json`、`Cargo.toml` 三个文件的版本号: + +```bash +node scripts/bump-version.mjs # 默认 patch: 0.2.0 → 0.2.1 +node scripts/bump-version.mjs minor # minor: 0.2.0 → 0.3.0 +node scripts/bump-version.mjs major # major: 0.2.0 → 1.0.0 +node scripts/bump-version.mjs 1.2.3 # 指定版本 +``` + +## 发布 + +构建完成后,运行发布脚本将安装包和更新元数据上传到远程服务器: + +```bash +node scripts/publish.mjs --notes "更新说明" +``` + +脚本会自动: +1. 从构建产物中读取 `.nsis.zip` 和 `.nsis.zip.sig` +2. 生成 `update.json`(供应用内自动更新使用) +3. 通过 SCP 上传到服务器 `192.168.2.127:/var/www/html/work-order-monitor/` + +更新地址:`http://web.nps.yunvip123.cn/work-order-monitor/update.json` + +## 完整发版流程 + +```bash +# 1. 更新版本号 +node scripts/bump-version.mjs minor + +# 2. 构建 +pnpm run tauri-build + +# 3. 发布到服务器 +node scripts/publish.mjs --notes "v0.3.0 新功能说明" +``` + +## 签名密钥 + +项目已内置 updater 签名密钥(`.tauri/signing-key` 和 `.tauri/signing-key.pub`),无密码保护,任何人克隆项目后即可直接构建。 + +如需重新生成密钥: + +```bash +pnpm tauri signer generate -w .tauri/signing-key -p "" --ci -f +``` + +生成后需将 `.tauri/signing-key.pub` 的内容更新到 `src-tauri/tauri.conf.json` 的 `plugins.updater.pubkey` 字段。 ## 项目结构 ``` -src-tauri/src/ -├── lib.rs # 主模块,应用程序入口和命令处理 -├── config.rs # 配置管理模块 -├── api.rs # API调用模块 -├── tray.rs # 系统托盘模块 -└── scheduler.rs # 定时任务模块 +src/ # 前端(Vue 3) +├── App.vue # 主界面 +├── stores/monitor.ts # Pinia 状态管理(核心业务逻辑) +├── components/ +│ ├── MonitorControl.vue # 监测面板(工单数据卡片) +│ ├── LoginForm.vue # 登录表单 +│ ├── SettingsPanel.vue # 配置面板 +│ ├── StatusHeader.vue # 顶部状态栏 +│ ├── LogViewer.vue # 日志查看器 +│ ├── CloseDialog.vue # 关闭确认弹窗 +│ └── UpdateDialog.vue # 更新确认弹窗 -src/ -└── App.vue # Vue设置界面 +src-tauri/ # Rust 后端 +├── src/lib.rs # 应用初始化、插件注册 +├── src/notification.rs # Windows Toast 通知 +├── tauri.conf.json # Tauri 配置 +├── capabilities/default.json # 权限配置 + +scripts/ +├── build.mjs # 构建(自动加载签名密钥) +├── bump-version.mjs # 版本号管理 +└── publish.mjs # 构建发布 ``` -## 使用说明 +## 监测的 API -### 1. 启动应用程序 -```bash -# 开发模式 -pnpm tauri dev +| 接口 | 说明 | 触发通知条件 | +| --- | --- | --- | +| `SystemUser/Login` | 登录获取 Session | - | +| `DemandManage/QueryIndexCount` | 工单统计 | PendingCount / StaycloseCount / ConfirmCount > 0 | +| `DemandManage/GetWorkOrderListPage` | 待审核工单列表 | DataCount > 0 | -# 构建生产版本 -pnpm tauri build -``` +## Tauri 插件 -### 2. 配置设置 -- 应用启动后会自动最小化到系统托盘 -- 右键点击托盘图标选择"显示设置"打开设置界面 -- 输入用户名和密码,点击"保存设置" - -### 3. 登录和监测 -- 保存设置后点击"登录"按钮 -- 登录成功后会自动启动定时任务(每1分钟执行一次) -- 系统会根据API返回值显示通知消息 - -### 4. 托盘操作 -- **左键点击托盘图标** - 显示设置界面 -- **右键点击托盘图标** - 显示菜单(显示设置/退出) -- **关闭窗口** - 应用会隐藏到托盘而不是退出 - -## 配置说明 - -### API接口 -应用程序已配置为使用以下API接口: - -- **登录接口**: `https://crm.yunvip123.com/api/SystemUser/Login` -- **检查接口**: `https://crm.yunvip123.com/api/DemandManage/QueryIndexCount` - -### 认证机制 -- 登录时获取 `ASP.NET_SessionId` Cookie -- 所有后续请求都需要携带此Cookie进行认证 - -### API响应格式 - -**登录响应 (LoginResponse):** -```json -{ - "success": true, - "code": null, - "msg": "执行成功", - "data": { - "GID": "512860ad-aa4a-4268-a5d9-6c7d2e28680b", - "SU_UserName": "A田森林" - } -} -``` - -**检查响应 (CheckResponse):** -```json -{ - "success": true, - "code": null, - "msg": "执行成功", - "data": { - "PendingCount": 0, - "StaycloseCount": 0, - "ConfirmCount": 0 - } -} -``` - -### 通知逻辑 -- 当 `PendingCount`、`StaycloseCount`、`ConfirmCount` 三个值都为0时,不发送通知 -- 当任意一个值大于0时,发送系统通知,显示具体的工单数量 - -## 开发说明 - -### 添加新功能 -1. **新的API端点** - 在 `api.rs` 中添加新的方法 -2. **新的配置项** - 在 `config.rs` 中添加到 `AppConfig` 结构体 -3. **新的UI组件** - 在 `App.vue` 中添加新的界面元素 -4. **新的命令** - 在 `lib.rs` 中添加新的 Tauri 命令 - -### 错误处理 -- 所有模块都使用 `anyhow::Result` 进行错误处理 -- 错误信息会显示在UI的消息卡片中 -- 日志记录使用 `log` crate,可通过环境变量 `RUST_LOG` 控制级别 - -### 数据持久化 -- 用户配置保存在系统配置目录:`%APPDATA%/tauri-app/config.json` (Windows) -- 配置文件使用JSON格式,包含用户名、密码等信息 - -## 注意事项 - -1. **安全性** - 密码以明文形式存储在配置文件中,生产环境建议加密 -2. **网络请求** - API调用失败时会显示错误通知 -3. **系统权限** - 需要通知权限来显示系统消息 -4. **托盘图标** - 使用应用程序默认图标,可根据登录状态切换不同图标 - -## TODO 项目 - -- [x] 配置实际的API地址和端点 -- [x] 实现Cookie认证机制 -- [x] 适配实际API响应格式 -- [x] 添加更多的配置选项(检查间隔等) -- [x] 添加日志系统和日志显示 -- [x] 实现自动启动功能 -- [x] 添加网络连接状态检测 -- [x] 添加测试按钮(手动检查、停止/开始、最小化等) -- [ ] 实现密码加密存储 -- [ ] 支持不同登录状态的托盘图标(图标文件) -- [ ] 添加日志文件输出 -- [ ] 实现会话过期自动重新登录 -- [ ] 解决Windows编译环境问题(需要Visual Studio Build Tools) +- `tauri-plugin-http` — HTTP 请求 +- `tauri-plugin-notification` — 系统通知 +- `tauri-plugin-autostart` — 开机自启动 +- `tauri-plugin-opener` — 打开外部链接 +- `tauri-plugin-updater` — 应用内自动更新 +- `tauri-plugin-process` — 进程管理(重启) diff --git a/data/deploy.js b/data/deploy.js new file mode 100644 index 0000000..7ca33f6 --- /dev/null +++ b/data/deploy.js @@ -0,0 +1,39 @@ +const { NodeSSH } = require("node-ssh"); +const os = require("node:os"); + +const config = { + host: "192.168.2.250", + username: "admin", + privateKeyPath: `${os.homedir()}\\.ssh\\id_rsa_2048`, + localDirectory: "dist", + remoteDirectory: "/D:/nginx/nginx-1.27.1/erp", +}; + +async function main() { + const ssh = new NodeSSH(); + + await ssh.connect({ + host: config.host, + username: config.username, + privateKeyPath: config.privateKeyPath, + }); + + const res = await ssh.putDirectory( + config.localDirectory, + config.remoteDirectory, + { + recursive: true, + } + ); + if (res) { + console.log("传输完成", res); + } else { + console.error(res); + } + + const connection = ssh.getConnection(); + connection.destroy(); + // await ssh.dispose(); +} + +main(); diff --git a/package-lock.json b/package-lock.json index 7ac1b95..afc99c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "tauri-app", - "version": "0.1.3", + "name": "work-order-monitor", + "version": "0.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "tauri-app", - "version": "0.1.3", + "name": "work-order-monitor", + "version": "0.2.1", "dependencies": { "@ant-design/icons-vue": "^7.0.1", "@tauri-apps/api": "^2.10.1", @@ -14,13 +14,15 @@ "@tauri-apps/plugin-http": "^2.5.7", "@tauri-apps/plugin-notification": "^2.3.3", "@tauri-apps/plugin-opener": "^2.5.3", + "@tauri-apps/plugin-process": "^2.3.1", + "@tauri-apps/plugin-updater": "^2.10.0", "ant-design-vue": "^4.2.6", "dayjs": "^1.11.19", "pinia": "^3.0.4", "vue": "^3.5.29" }, "devDependencies": { - "@tauri-apps/cli": "^2", + "@tauri-apps/cli": "^2.10.1", "@vitejs/plugin-vue": "^6.0.4", "typescript": "~5.9.3", "vite": "^7.3.1", @@ -957,9 +959,9 @@ } }, "node_modules/@tauri-apps/cli": { - "version": "2.10.0", - "resolved": "https://registry.npmmirror.com/@tauri-apps/cli/-/cli-2.10.0.tgz", - "integrity": "sha512-ZwT0T+7bw4+DPCSWzmviwq5XbXlM0cNoleDKOYPFYqcZqeKY31KlpoMW/MOON/tOFBPgi31a2v3w9gliqwL2+Q==", + "version": "2.10.1", + "resolved": "https://registry.npmmirror.com/@tauri-apps/cli/-/cli-2.10.1.tgz", + "integrity": "sha512-jQNGF/5quwORdZSSLtTluyKQ+o6SMa/AUICfhf4egCGFdMHqWssApVgYSbg+jmrZoc8e1DscNvjTnXtlHLS11g==", "dev": true, "license": "Apache-2.0 OR MIT", "bin": { @@ -973,23 +975,23 @@ "url": "https://opencollective.com/tauri" }, "optionalDependencies": { - "@tauri-apps/cli-darwin-arm64": "2.10.0", - "@tauri-apps/cli-darwin-x64": "2.10.0", - "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.0", - "@tauri-apps/cli-linux-arm64-gnu": "2.10.0", - "@tauri-apps/cli-linux-arm64-musl": "2.10.0", - "@tauri-apps/cli-linux-riscv64-gnu": "2.10.0", - "@tauri-apps/cli-linux-x64-gnu": "2.10.0", - "@tauri-apps/cli-linux-x64-musl": "2.10.0", - "@tauri-apps/cli-win32-arm64-msvc": "2.10.0", - "@tauri-apps/cli-win32-ia32-msvc": "2.10.0", - "@tauri-apps/cli-win32-x64-msvc": "2.10.0" + "@tauri-apps/cli-darwin-arm64": "2.10.1", + "@tauri-apps/cli-darwin-x64": "2.10.1", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.1", + "@tauri-apps/cli-linux-arm64-gnu": "2.10.1", + "@tauri-apps/cli-linux-arm64-musl": "2.10.1", + "@tauri-apps/cli-linux-riscv64-gnu": "2.10.1", + "@tauri-apps/cli-linux-x64-gnu": "2.10.1", + "@tauri-apps/cli-linux-x64-musl": "2.10.1", + "@tauri-apps/cli-win32-arm64-msvc": "2.10.1", + "@tauri-apps/cli-win32-ia32-msvc": "2.10.1", + "@tauri-apps/cli-win32-x64-msvc": "2.10.1" } }, "node_modules/@tauri-apps/cli-darwin-arm64": { - "version": "2.10.0", - "resolved": "https://registry.npmmirror.com/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.10.0.tgz", - "integrity": "sha512-avqHD4HRjrMamE/7R/kzJPcAJnZs0IIS+1nkDP5b+TNBn3py7N2aIo9LIpy+VQq0AkN8G5dDpZtOOBkmWt/zjA==", + "version": "2.10.1", + "resolved": "https://registry.npmmirror.com/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.10.1.tgz", + "integrity": "sha512-Z2OjCXiZ+fbYZy7PmP3WRnOpM9+Fy+oonKDEmUE6MwN4IGaYqgceTjwHucc/kEEYZos5GICve35f7ZiizgqEnQ==", "cpu": [ "arm64" ], @@ -1004,9 +1006,9 @@ } }, "node_modules/@tauri-apps/cli-darwin-x64": { - "version": "2.10.0", - "resolved": "https://registry.npmmirror.com/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.10.0.tgz", - "integrity": "sha512-keDmlvJRStzVFjZTd0xYkBONLtgBC9eMTpmXnBXzsHuawV2q9PvDo2x6D5mhuoMVrJ9QWjgaPKBBCFks4dK71Q==", + "version": "2.10.1", + "resolved": "https://registry.npmmirror.com/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.10.1.tgz", + "integrity": "sha512-V/irQVvjPMGOTQqNj55PnQPVuH4VJP8vZCN7ajnj+ZS8Kom1tEM2hR3qbbIRoS3dBKs5mbG8yg1WC+97dq17Pw==", "cpu": [ "x64" ], @@ -1021,9 +1023,9 @@ } }, "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { - "version": "2.10.0", - "resolved": "https://registry.npmmirror.com/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.10.0.tgz", - "integrity": "sha512-e5u0VfLZsMAC9iHaOEANumgl6lfnJx0Dtjkd8IJpysZ8jp0tJ6wrIkto2OzQgzcYyRCKgX72aKE0PFgZputA8g==", + "version": "2.10.1", + "resolved": "https://registry.npmmirror.com/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.10.1.tgz", + "integrity": "sha512-Hyzwsb4VnCWKGfTw+wSt15Z2pLw2f0JdFBfq2vHBOBhvg7oi6uhKiF87hmbXOBXUZaGkyRDkCHsdzJcIfoJC2w==", "cpu": [ "arm" ], @@ -1038,9 +1040,9 @@ } }, "node_modules/@tauri-apps/cli-linux-arm64-gnu": { - "version": "2.10.0", - "resolved": "https://registry.npmmirror.com/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.10.0.tgz", - "integrity": "sha512-YrYYk2dfmBs5m+OIMCrb+JH/oo+4FtlpcrTCgiFYc7vcs6m3QDd1TTyWu0u01ewsCtK2kOdluhr/zKku+KP7HA==", + "version": "2.10.1", + "resolved": "https://registry.npmmirror.com/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.10.1.tgz", + "integrity": "sha512-OyOYs2t5GkBIvyWjA1+h4CZxTcdz1OZPCWAPz5DYEfB0cnWHERTnQ/SLayQzncrT0kwRoSfSz9KxenkyJoTelA==", "cpu": [ "arm64" ], @@ -1055,9 +1057,9 @@ } }, "node_modules/@tauri-apps/cli-linux-arm64-musl": { - "version": "2.10.0", - "resolved": "https://registry.npmmirror.com/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.10.0.tgz", - "integrity": "sha512-GUoPdVJmrJRIXFfW3Rkt+eGK9ygOdyISACZfC/bCSfOnGt8kNdQIQr5WRH9QUaTVFIwxMlQyV3m+yXYP+xhSVA==", + "version": "2.10.1", + "resolved": "https://registry.npmmirror.com/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.10.1.tgz", + "integrity": "sha512-MIj78PDDGjkg3NqGptDOGgfXks7SYJwhiMh8SBoZS+vfdz7yP5jN18bNaLnDhsVIPARcAhE1TlsZe/8Yxo2zqg==", "cpu": [ "arm64" ], @@ -1072,9 +1074,9 @@ } }, "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { - "version": "2.10.0", - "resolved": "https://registry.npmmirror.com/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.10.0.tgz", - "integrity": "sha512-JO7s3TlSxshwsoKNCDkyvsx5gw2QAs/Y2GbR5UE2d5kkU138ATKoPOtxn8G1fFT1aDW4LH0rYAAfBpGkDyJJnw==", + "version": "2.10.1", + "resolved": "https://registry.npmmirror.com/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.10.1.tgz", + "integrity": "sha512-X0lvOVUg8PCVaoEtEAnpxmnkwlE1gcMDTqfhbefICKDnOTJ5Est3qL0SrWxizDackIOKBcvtpejrSiVpuJI1kw==", "cpu": [ "riscv64" ], @@ -1089,9 +1091,9 @@ } }, "node_modules/@tauri-apps/cli-linux-x64-gnu": { - "version": "2.10.0", - "resolved": "https://registry.npmmirror.com/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.10.0.tgz", - "integrity": "sha512-Uvh4SUUp4A6DVRSMWjelww0GnZI3PlVy7VS+DRF5napKuIehVjGl9XD0uKoCoxwAQBLctvipyEK+pDXpJeoHng==", + "version": "2.10.1", + "resolved": "https://registry.npmmirror.com/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.10.1.tgz", + "integrity": "sha512-2/12bEzsJS9fAKybxgicCDFxYD1WEI9kO+tlDwX5znWG2GwMBaiWcmhGlZ8fi+DMe9CXlcVarMTYc0L3REIRxw==", "cpu": [ "x64" ], @@ -1106,9 +1108,9 @@ } }, "node_modules/@tauri-apps/cli-linux-x64-musl": { - "version": "2.10.0", - "resolved": "https://registry.npmmirror.com/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.10.0.tgz", - "integrity": "sha512-AP0KRK6bJuTpQ8kMNWvhIpKUkQJfcPFeba7QshOQZjJ8wOS6emwTN4K5g/d3AbCMo0RRdnZWwu67MlmtJyxC1Q==", + "version": "2.10.1", + "resolved": "https://registry.npmmirror.com/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.10.1.tgz", + "integrity": "sha512-Y8J0ZzswPz50UcGOFuXGEMrxbjwKSPgXftx5qnkuMs2rmwQB5ssvLb6tn54wDSYxe7S6vlLob9vt0VKuNOaCIQ==", "cpu": [ "x64" ], @@ -1123,9 +1125,9 @@ } }, "node_modules/@tauri-apps/cli-win32-arm64-msvc": { - "version": "2.10.0", - "resolved": "https://registry.npmmirror.com/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.10.0.tgz", - "integrity": "sha512-97DXVU3dJystrq7W41IX+82JEorLNY+3+ECYxvXWqkq7DBN6FsA08x/EFGE8N/b0LTOui9X2dvpGGoeZKKV08g==", + "version": "2.10.1", + "resolved": "https://registry.npmmirror.com/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.10.1.tgz", + "integrity": "sha512-iSt5B86jHYAPJa/IlYw++SXtFPGnWtFJriHn7X0NFBVunF6zu9+/zOn8OgqIWSl8RgzhLGXQEEtGBdR4wzpVgg==", "cpu": [ "arm64" ], @@ -1140,9 +1142,9 @@ } }, "node_modules/@tauri-apps/cli-win32-ia32-msvc": { - "version": "2.10.0", - "resolved": "https://registry.npmmirror.com/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.10.0.tgz", - "integrity": "sha512-EHyQ1iwrWy1CwMalEm9z2a6L5isQ121pe7FcA2xe4VWMJp+GHSDDGvbTv/OPdkt2Lyr7DAZBpZHM6nvlHXEc4A==", + "version": "2.10.1", + "resolved": "https://registry.npmmirror.com/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.10.1.tgz", + "integrity": "sha512-gXyxgEzsFegmnWywYU5pEBURkcFN/Oo45EAwvZrHMh+zUSEAvO5E8TXsgPADYm31d1u7OQU3O3HsYfVBf2moHw==", "cpu": [ "ia32" ], @@ -1157,9 +1159,9 @@ } }, "node_modules/@tauri-apps/cli-win32-x64-msvc": { - "version": "2.10.0", - "resolved": "https://registry.npmmirror.com/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.10.0.tgz", - "integrity": "sha512-NTpyQxkpzGmU6ceWBTY2xRIEaS0ZLbVx1HE1zTA3TY/pV3+cPoPPOs+7YScr4IMzXMtOw7tLw5LEXo5oIG3qaQ==", + "version": "2.10.1", + "resolved": "https://registry.npmmirror.com/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.10.1.tgz", + "integrity": "sha512-6Cn7YpPFwzChy0ERz6djKEmUehWrYlM+xTaNzGPgZocw3BD7OfwfWHKVWxXzdjEW2KfKkHddfdxK1XXTYqBRLg==", "cpu": [ "x64" ], @@ -1209,6 +1211,24 @@ "@tauri-apps/api": "^2.8.0" } }, + "node_modules/@tauri-apps/plugin-process": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/@tauri-apps/plugin-process/-/plugin-process-2.3.1.tgz", + "integrity": "sha512-nCa4fGVaDL/B9ai03VyPOjfAHRHSBz5v6F/ObsB73r/dA3MHHhZtldaDMIc0V/pnUw9ehzr2iEG+XkSEyC0JJA==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } + }, + "node_modules/@tauri-apps/plugin-updater": { + "version": "2.10.0", + "resolved": "https://registry.npmmirror.com/@tauri-apps/plugin-updater/-/plugin-updater-2.10.0.tgz", + "integrity": "sha512-ljN8jPlnT0aSn8ecYhuBib84alxfMx6Hc8vJSKMJyzGbTPFZAC44T2I1QNFZssgWKrAlofvJqCC6Rr472JWfkQ==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.10.1" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", diff --git a/package.json b/package.json index e85e038..1120446 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "tauri-app", + "name": "work-order-monitor", "private": true, - "version": "0.1.3", + "version": "0.2.2", "type": "module", "scripts": { "dev": "vite", @@ -9,7 +9,7 @@ "preview": "vite preview", "tauri": "tauri", "tauri-dev": "tauri dev", - "tauri-build": "tauri build" + "tauri-build": "node scripts/build.mjs" }, "dependencies": { "@ant-design/icons-vue": "^7.0.1", @@ -18,13 +18,15 @@ "@tauri-apps/plugin-http": "^2.5.7", "@tauri-apps/plugin-notification": "^2.3.3", "@tauri-apps/plugin-opener": "^2.5.3", + "@tauri-apps/plugin-process": "^2.3.1", + "@tauri-apps/plugin-updater": "^2.10.0", "ant-design-vue": "^4.2.6", "dayjs": "^1.11.19", "pinia": "^3.0.4", "vue": "^3.5.29" }, "devDependencies": { - "@tauri-apps/cli": "^2", + "@tauri-apps/cli": "^2.10.1", "@vitejs/plugin-vue": "^6.0.4", "typescript": "~5.9.3", "vite": "^7.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1507a80..9c4a66d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,12 @@ importers: '@tauri-apps/plugin-opener': specifier: ^2.5.3 version: 2.5.3 + '@tauri-apps/plugin-process': + specifier: ^2.3.1 + version: 2.3.1 + '@tauri-apps/plugin-updater': + specifier: ^2.10.0 + version: 2.10.0 ant-design-vue: specifier: ^4.2.6 version: 4.2.6(vue@3.5.29(typescript@5.9.3)) @@ -40,8 +46,8 @@ importers: version: 3.5.29(typescript@5.9.3) devDependencies: '@tauri-apps/cli': - specifier: ^2 - version: 2.8.4 + specifier: ^2.10.1 + version: 2.10.1 '@vitejs/plugin-vue': specifier: ^6.0.4 version: 6.0.4(vite@7.3.1)(vue@3.5.29(typescript@5.9.3)) @@ -388,79 +394,79 @@ packages: '@tauri-apps/api@2.10.1': resolution: {integrity: sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==} - '@tauri-apps/cli-darwin-arm64@2.8.4': - resolution: {integrity: sha512-BKu8HRkYV01SMTa7r4fLx+wjgtRK8Vep7lmBdHDioP6b8XH3q2KgsAyPWfEZaZIkZ2LY4SqqGARaE9oilNe0oA==} + '@tauri-apps/cli-darwin-arm64@2.10.1': + resolution: {integrity: sha512-Z2OjCXiZ+fbYZy7PmP3WRnOpM9+Fy+oonKDEmUE6MwN4IGaYqgceTjwHucc/kEEYZos5GICve35f7ZiizgqEnQ==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@tauri-apps/cli-darwin-x64@2.8.4': - resolution: {integrity: sha512-imb9PfSd/7G6VAO7v1bQ2A3ZH4NOCbhGJFLchxzepGcXf9NKkfun157JH9mko29K6sqAwuJ88qtzbKCbWJTH9g==} + '@tauri-apps/cli-darwin-x64@2.10.1': + resolution: {integrity: sha512-V/irQVvjPMGOTQqNj55PnQPVuH4VJP8vZCN7ajnj+ZS8Kom1tEM2hR3qbbIRoS3dBKs5mbG8yg1WC+97dq17Pw==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@tauri-apps/cli-linux-arm-gnueabihf@2.8.4': - resolution: {integrity: sha512-Ml215UnDdl7/fpOrF1CNovym/KjtUbCuPgrcZ4IhqUCnhZdXuphud/JT3E8X97Y03TZ40Sjz8raXYI2ET0exzw==} + '@tauri-apps/cli-linux-arm-gnueabihf@2.10.1': + resolution: {integrity: sha512-Hyzwsb4VnCWKGfTw+wSt15Z2pLw2f0JdFBfq2vHBOBhvg7oi6uhKiF87hmbXOBXUZaGkyRDkCHsdzJcIfoJC2w==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@tauri-apps/cli-linux-arm64-gnu@2.8.4': - resolution: {integrity: sha512-pbcgBpMyI90C83CxE5REZ9ODyIlmmAPkkJXtV398X3SgZEIYy5TACYqlyyv2z5yKgD8F8WH4/2fek7+jH+ZXAw==} + '@tauri-apps/cli-linux-arm64-gnu@2.10.1': + resolution: {integrity: sha512-OyOYs2t5GkBIvyWjA1+h4CZxTcdz1OZPCWAPz5DYEfB0cnWHERTnQ/SLayQzncrT0kwRoSfSz9KxenkyJoTelA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [glibc] - '@tauri-apps/cli-linux-arm64-musl@2.8.4': - resolution: {integrity: sha512-zumFeaU1Ws5Ay872FTyIm7z8kfzEHu8NcIn8M6TxbJs0a7GRV21KBdpW1zNj2qy7HynnpQCqjAYXTUUmm9JAOw==} + '@tauri-apps/cli-linux-arm64-musl@2.10.1': + resolution: {integrity: sha512-MIj78PDDGjkg3NqGptDOGgfXks7SYJwhiMh8SBoZS+vfdz7yP5jN18bNaLnDhsVIPARcAhE1TlsZe/8Yxo2zqg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [musl] - '@tauri-apps/cli-linux-riscv64-gnu@2.8.4': - resolution: {integrity: sha512-qiqbB3Zz6IyO201f+1ojxLj65WYj8mixL5cOMo63nlg8CIzsP23cPYUrx1YaDPsCLszKZo7tVs14pc7BWf+/aQ==} + '@tauri-apps/cli-linux-riscv64-gnu@2.10.1': + resolution: {integrity: sha512-X0lvOVUg8PCVaoEtEAnpxmnkwlE1gcMDTqfhbefICKDnOTJ5Est3qL0SrWxizDackIOKBcvtpejrSiVpuJI1kw==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] libc: [glibc] - '@tauri-apps/cli-linux-x64-gnu@2.8.4': - resolution: {integrity: sha512-TaqaDd9Oy6k45Hotx3pOf+pkbsxLaApv4rGd9mLuRM1k6YS/aw81YrsMryYPThrxrScEIUcmNIHaHsLiU4GMkw==} + '@tauri-apps/cli-linux-x64-gnu@2.10.1': + resolution: {integrity: sha512-2/12bEzsJS9fAKybxgicCDFxYD1WEI9kO+tlDwX5znWG2GwMBaiWcmhGlZ8fi+DMe9CXlcVarMTYc0L3REIRxw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [glibc] - '@tauri-apps/cli-linux-x64-musl@2.8.4': - resolution: {integrity: sha512-ot9STAwyezN8w+bBHZ+bqSQIJ0qPZFlz/AyscpGqB/JnJQVDFQcRDmUPFEaAtt2UUHSWzN3GoTJ5ypqLBp2WQA==} + '@tauri-apps/cli-linux-x64-musl@2.10.1': + resolution: {integrity: sha512-Y8J0ZzswPz50UcGOFuXGEMrxbjwKSPgXftx5qnkuMs2rmwQB5ssvLb6tn54wDSYxe7S6vlLob9vt0VKuNOaCIQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [musl] - '@tauri-apps/cli-win32-arm64-msvc@2.8.4': - resolution: {integrity: sha512-+2aJ/g90dhLiOLFSD1PbElXX3SoMdpO7HFPAZB+xot3CWlAZD1tReUFy7xe0L5GAR16ZmrxpIDM9v9gn5xRy/w==} + '@tauri-apps/cli-win32-arm64-msvc@2.10.1': + resolution: {integrity: sha512-iSt5B86jHYAPJa/IlYw++SXtFPGnWtFJriHn7X0NFBVunF6zu9+/zOn8OgqIWSl8RgzhLGXQEEtGBdR4wzpVgg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@tauri-apps/cli-win32-ia32-msvc@2.8.4': - resolution: {integrity: sha512-yj7WDxkL1t9Uzr2gufQ1Hl7hrHuFKTNEOyascbc109EoiAqCp0tgZ2IykQqOZmZOHU884UAWI1pVMqBhS/BfhA==} + '@tauri-apps/cli-win32-ia32-msvc@2.10.1': + resolution: {integrity: sha512-gXyxgEzsFegmnWywYU5pEBURkcFN/Oo45EAwvZrHMh+zUSEAvO5E8TXsgPADYm31d1u7OQU3O3HsYfVBf2moHw==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] - '@tauri-apps/cli-win32-x64-msvc@2.8.4': - resolution: {integrity: sha512-XuvGB4ehBdd7QhMZ9qbj/8icGEatDuBNxyYHbLKsTYh90ggUlPa/AtaqcC1Fo69lGkTmq9BOKrs1aWSi7xDonA==} + '@tauri-apps/cli-win32-x64-msvc@2.10.1': + resolution: {integrity: sha512-6Cn7YpPFwzChy0ERz6djKEmUehWrYlM+xTaNzGPgZocw3BD7OfwfWHKVWxXzdjEW2KfKkHddfdxK1XXTYqBRLg==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@tauri-apps/cli@2.8.4': - resolution: {integrity: sha512-ejUZBzuQRcjFV+v/gdj/DcbyX/6T4unZQjMSBZwLzP/CymEjKcc2+Fc8xTORThebHDUvqoXMdsCZt8r+hyN15g==} + '@tauri-apps/cli@2.10.1': + resolution: {integrity: sha512-jQNGF/5quwORdZSSLtTluyKQ+o6SMa/AUICfhf4egCGFdMHqWssApVgYSbg+jmrZoc8e1DscNvjTnXtlHLS11g==} engines: {node: '>= 10'} hasBin: true @@ -476,6 +482,12 @@ packages: '@tauri-apps/plugin-opener@2.5.3': resolution: {integrity: sha512-CCcUltXMOfUEArbf3db3kCE7Ggy1ExBEBl51Ko2ODJ6GDYHRp1nSNlQm5uNCFY5k7/ufaK5Ib3Du/Zir19IYQQ==} + '@tauri-apps/plugin-process@2.3.1': + resolution: {integrity: sha512-nCa4fGVaDL/B9ai03VyPOjfAHRHSBz5v6F/ObsB73r/dA3MHHhZtldaDMIc0V/pnUw9ehzr2iEG+XkSEyC0JJA==} + + '@tauri-apps/plugin-updater@2.10.0': + resolution: {integrity: sha512-ljN8jPlnT0aSn8ecYhuBib84alxfMx6Hc8vJSKMJyzGbTPFZAC44T2I1QNFZssgWKrAlofvJqCC6Rr472JWfkQ==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -970,52 +982,52 @@ snapshots: '@tauri-apps/api@2.10.1': {} - '@tauri-apps/cli-darwin-arm64@2.8.4': + '@tauri-apps/cli-darwin-arm64@2.10.1': optional: true - '@tauri-apps/cli-darwin-x64@2.8.4': + '@tauri-apps/cli-darwin-x64@2.10.1': optional: true - '@tauri-apps/cli-linux-arm-gnueabihf@2.8.4': + '@tauri-apps/cli-linux-arm-gnueabihf@2.10.1': optional: true - '@tauri-apps/cli-linux-arm64-gnu@2.8.4': + '@tauri-apps/cli-linux-arm64-gnu@2.10.1': optional: true - '@tauri-apps/cli-linux-arm64-musl@2.8.4': + '@tauri-apps/cli-linux-arm64-musl@2.10.1': optional: true - '@tauri-apps/cli-linux-riscv64-gnu@2.8.4': + '@tauri-apps/cli-linux-riscv64-gnu@2.10.1': optional: true - '@tauri-apps/cli-linux-x64-gnu@2.8.4': + '@tauri-apps/cli-linux-x64-gnu@2.10.1': optional: true - '@tauri-apps/cli-linux-x64-musl@2.8.4': + '@tauri-apps/cli-linux-x64-musl@2.10.1': optional: true - '@tauri-apps/cli-win32-arm64-msvc@2.8.4': + '@tauri-apps/cli-win32-arm64-msvc@2.10.1': optional: true - '@tauri-apps/cli-win32-ia32-msvc@2.8.4': + '@tauri-apps/cli-win32-ia32-msvc@2.10.1': optional: true - '@tauri-apps/cli-win32-x64-msvc@2.8.4': + '@tauri-apps/cli-win32-x64-msvc@2.10.1': optional: true - '@tauri-apps/cli@2.8.4': + '@tauri-apps/cli@2.10.1': optionalDependencies: - '@tauri-apps/cli-darwin-arm64': 2.8.4 - '@tauri-apps/cli-darwin-x64': 2.8.4 - '@tauri-apps/cli-linux-arm-gnueabihf': 2.8.4 - '@tauri-apps/cli-linux-arm64-gnu': 2.8.4 - '@tauri-apps/cli-linux-arm64-musl': 2.8.4 - '@tauri-apps/cli-linux-riscv64-gnu': 2.8.4 - '@tauri-apps/cli-linux-x64-gnu': 2.8.4 - '@tauri-apps/cli-linux-x64-musl': 2.8.4 - '@tauri-apps/cli-win32-arm64-msvc': 2.8.4 - '@tauri-apps/cli-win32-ia32-msvc': 2.8.4 - '@tauri-apps/cli-win32-x64-msvc': 2.8.4 + '@tauri-apps/cli-darwin-arm64': 2.10.1 + '@tauri-apps/cli-darwin-x64': 2.10.1 + '@tauri-apps/cli-linux-arm-gnueabihf': 2.10.1 + '@tauri-apps/cli-linux-arm64-gnu': 2.10.1 + '@tauri-apps/cli-linux-arm64-musl': 2.10.1 + '@tauri-apps/cli-linux-riscv64-gnu': 2.10.1 + '@tauri-apps/cli-linux-x64-gnu': 2.10.1 + '@tauri-apps/cli-linux-x64-musl': 2.10.1 + '@tauri-apps/cli-win32-arm64-msvc': 2.10.1 + '@tauri-apps/cli-win32-ia32-msvc': 2.10.1 + '@tauri-apps/cli-win32-x64-msvc': 2.10.1 '@tauri-apps/plugin-autostart@2.5.1': dependencies: @@ -1033,6 +1045,14 @@ snapshots: dependencies: '@tauri-apps/api': 2.10.1 + '@tauri-apps/plugin-process@2.3.1': + dependencies: + '@tauri-apps/api': 2.10.1 + + '@tauri-apps/plugin-updater@2.10.0': + dependencies: + '@tauri-apps/api': 2.10.1 + '@types/estree@1.0.8': {} '@vitejs/plugin-vue@6.0.4(vite@7.3.1)(vue@3.5.29(typescript@5.9.3))': diff --git a/scripts/build.mjs b/scripts/build.mjs new file mode 100644 index 0000000..b22c860 --- /dev/null +++ b/scripts/build.mjs @@ -0,0 +1,24 @@ +#!/usr/bin/env node +/** + * 构建脚本 - 自动加载项目内置签名密钥后执行 tauri build + */ + +import { readFileSync } from "node:fs"; +import { resolve, dirname } from "node:path"; +import { execSync } from "node:child_process"; +import { fileURLToPath } from "node:url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const root = resolve(__dirname, ".."); +const keyPath = resolve(root, ".tauri/signing-key"); + +try { + process.env.TAURI_SIGNING_PRIVATE_KEY = readFileSync(keyPath, "utf-8").trim(); + process.env.TAURI_SIGNING_PRIVATE_KEY_PASSWORD = ""; +} catch { + console.error(`错误: 未找到签名密钥 ${keyPath}`); + console.error("请运行: pnpm tauri signer generate -w .tauri/signing-key -p \"\" --ci"); + process.exit(1); +} + +execSync("pnpm tauri build", { stdio: "inherit", cwd: root }); diff --git a/scripts/bump-version.mjs b/scripts/bump-version.mjs new file mode 100644 index 0000000..80b75ad --- /dev/null +++ b/scripts/bump-version.mjs @@ -0,0 +1,103 @@ +/* + * @file /scripts/bump-version.mjs + * @description 统一更新 Tauri 项目版本号 + * @author tsl (randy1924@163.com) + * @date 2026-03-23 10:23:42 + * @lastModified 2026-03-23 10:25:08 + * @lastModifiedBy tsl (randy1924@163.com) + * @example node scripts/bump-version.mjs [patch|minor|major|x.y.z] + * @default patch + */ + +import { readFileSync, writeFileSync } from "node:fs"; +import { resolve, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const root = resolve(__dirname, ".."); + +// 读取当前版本 +function getCurrentVersion() { + const pkg = JSON.parse(readFileSync(resolve(root, "package.json"), "utf-8")); + return pkg.version; +} + +// 根据 bump 类型计算新版本 +function bumpVersion(current, type) { + const parts = current.replace(/-.*$/, "").split(".").map(Number); + switch (type) { + case "major": + return `${parts[0] + 1}.0.0`; + case "minor": + return `${parts[0]}.${parts[1] + 1}.0`; + case "patch": + return `${parts[0]}.${parts[1]}.${parts[2] + 1}`; + default: + return null; + } +} + +const arg = process.argv[2] || "patch"; +const BUMP_TYPES = ["patch", "minor", "major"]; +const current = getCurrentVersion(); +let version; + +if (BUMP_TYPES.includes(arg)) { + version = bumpVersion(current, arg); +} else if (/^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/.test(arg)) { + version = arg; +} else { + console.error(`错误: 无效参数 "${arg}",应为 patch / minor / major 或 x.y.z 格式`); + process.exit(1); +} + +const files = [ + { + path: "package.json", + update(content) { + const json = JSON.parse(content); + const old = json.version; + json.version = version; + return { result: JSON.stringify(json, null, 2) + "\n", old }; + }, + }, + { + path: "src-tauri/tauri.conf.json", + update(content) { + const json = JSON.parse(content); + const old = json.version; + json.version = version; + return { result: JSON.stringify(json, null, 2) + "\n", old }; + }, + }, + { + path: "src-tauri/Cargo.toml", + update(content) { + let old = ""; + let replaced = false; + const result = content.replace( + /^version\s*=\s*"([^"]*)"/m, + (match, v) => { + if (replaced) return match; + replaced = true; + old = v; + return `version = "${version}"`; + } + ); + return { result, old }; + }, + }, +]; + +console.log(`\n${current} → ${version} (${BUMP_TYPES.includes(arg) ? arg : "指定版本"})\n`); + +for (const file of files) { + const filePath = resolve(root, file.path); + const content = readFileSync(filePath, "utf-8"); + const { result, old } = file.update(content); + writeFileSync(filePath, result, "utf-8"); + console.log(` ✔ ${file.path} ${old} → ${version}`); +} + +console.log("\n全部更新完成!"); +console.log("提示: package-lock.json 和 Cargo.lock 会在下次 install/build 时自动同步\n"); diff --git a/scripts/publish.mjs b/scripts/publish.mjs new file mode 100644 index 0000000..b3e203a --- /dev/null +++ b/scripts/publish.mjs @@ -0,0 +1,120 @@ +#!/usr/bin/env node +/** + * @file /scripts/publish.mjs + * @description 构建后生成 update.json 并上传到远程服务器 + * @usage node scripts/publish.mjs [--notes "更新说明"] + * + * 前置条件: + * 1. 已运行 `pnpm run tauri-build` 生成构建产物 + * 2. 远程服务器 SSH 可达(192.168.2.127:22) + */ + +import { readFileSync, writeFileSync, readdirSync, existsSync } from "node:fs"; +import { resolve, dirname, basename } from "node:path"; +import { fileURLToPath } from "node:url"; +import { execSync } from "node:child_process"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const root = resolve(__dirname, ".."); + +// ===== 配置 ===== +const REMOTE_USER = "root_test"; +const REMOTE_HOST = "192.168.2.127"; +const REMOTE_PORT = 22; +const REMOTE_DIR = "/var/www/html/work-order-monitor"; +const BASE_URL = "http://web.nps.yunvip123.cn/work-order-monitor"; + +// ===== 解析参数 ===== +const args = process.argv.slice(2); +let notes = ""; +const notesIdx = args.indexOf("--notes"); +if (notesIdx !== -1 && args[notesIdx + 1]) { + notes = args[notesIdx + 1]; +} + +// ===== 读取版本号 ===== +const pkg = JSON.parse(readFileSync(resolve(root, "package.json"), "utf-8")); +const version = pkg.version; + +// ===== 查找构建产物 ===== +const bundleDir = resolve(root, "src-tauri/target/release/bundle/nsis"); + +if (!existsSync(bundleDir)) { + console.error(`错误: 未找到构建目录 ${bundleDir}`); + console.error("请先运行: pnpm run tauri-build"); + process.exit(1); +} + +const files = readdirSync(bundleDir); +const setupFile = files.find((f) => f.includes(version) && f.endsWith("-setup.exe")); +const sigFile = files.find((f) => f.includes(version) && f.endsWith("-setup.exe.sig")); + +if (!setupFile || !sigFile) { + console.error(`错误: 未找到版本 ${version} 的 -setup.exe 或 -setup.exe.sig 文件`); + console.error(`目录内容: ${files.join(", ")}`); + process.exit(1); +} + +const setupPath = resolve(bundleDir, setupFile); +const sigPath = resolve(bundleDir, sigFile); +const signature = readFileSync(sigPath, "utf-8").trim(); + +console.log(`\n版本: ${version}`); +console.log(`安装包: ${setupFile}`); +console.log(`签名文件: ${sigFile}`); +console.log(`签名: ${signature.substring(0, 40)}...`); + +// ===== 生成 update.json ===== +const encodedFileName = encodeURIComponent(setupFile); +const updateJson = { + version, + notes: notes || `v${version} 更新`, + pub_date: new Date().toISOString(), + platforms: { + "windows-x86_64": { + url: `${BASE_URL}/${encodedFileName}`, + signature, + }, + }, +}; + +const updateJsonPath = resolve(bundleDir, "update.json"); +writeFileSync(updateJsonPath, JSON.stringify(updateJson, null, 2), "utf-8"); +console.log(`\nupdate.json 已生成`); +console.log(JSON.stringify(updateJson, null, 2)); + +// ===== 上传到远程服务器 ===== +console.log(`\n正在上传到 ${REMOTE_HOST}:${REMOTE_DIR} ...`); + +try { + // 确保远程目录存在 + execSync( + `ssh -p ${REMOTE_PORT} ${REMOTE_USER}@${REMOTE_HOST} "mkdir -p ${REMOTE_DIR}"`, + { stdio: "inherit" } + ); + + // 上传安装包 + console.log(` 上传 ${setupFile} ...`); + execSync( + `scp -P ${REMOTE_PORT} "${setupPath}" ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}/`, + { stdio: "inherit" } + ); + + // 上传 update.json + console.log(` 上传 update.json ...`); + execSync( + `scp -P ${REMOTE_PORT} "${updateJsonPath}" ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}/`, + { stdio: "inherit" } + ); + + console.log("\n全部上传完成!"); + console.log(`更新地址: ${BASE_URL}/update.json`); + console.log(`下载地址: ${BASE_URL}/${encodedFileName}\n`); +} catch (e) { + console.error("\n上传失败:", e.message); + console.error("请检查 SSH 连接是否正常,或手动上传以下文件:"); + console.error(` 1. ${setupPath}`); + console.error(` 2. ${updateJsonPath}`); + console.error(` → 目标: ${REMOTE_HOST}:${REMOTE_DIR}/`); + process.exit(1); +} diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index b98eb2c..3b2b9b5 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -47,6 +47,15 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "async-broadcast" version = "0.7.2" @@ -717,6 +726,17 @@ dependencies = [ "serde_core", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "derive_more" version = "0.99.20" @@ -1018,6 +1038,17 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "filetime" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +dependencies = [ + "cfg-if", + "libc", + "libredox", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -2114,6 +2145,7 @@ checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags 2.11.0", "libc", + "redox_syscall 0.7.3", ] [[package]] @@ -2225,6 +2257,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minisign-verify" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f9645cb765ea72b8111f36c522475d2daa0d22c957a9826437e97534bc4e9e" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2286,10 +2324,10 @@ dependencies = [ "libc", "log", "openssl", - "openssl-probe", + "openssl-probe 0.1.6", "openssl-sys", "schannel", - "security-framework", + "security-framework 2.11.1", "security-framework-sys", "tempfile", ] @@ -2548,6 +2586,18 @@ dependencies = [ "objc2-core-foundation", ] +[[package]] +name = "objc2-osa-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + [[package]] name = "objc2-quartz-core" version = "0.3.2" @@ -2649,6 +2699,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "openssl-sys" version = "0.9.110" @@ -2677,6 +2733,20 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "osakit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b" +dependencies = [ + "objc2", + "objc2-foundation", + "objc2-osa-kit", + "serde", + "serde_json", + "thiserror 2.0.18", +] + [[package]] name = "pango" version = "0.18.3" @@ -2726,7 +2796,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", "windows-link 0.2.1", ] @@ -3305,6 +3375,15 @@ dependencies = [ "bitflags 2.11.0", ] +[[package]] +name = "redox_syscall" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +dependencies = [ + "bitflags 2.11.0", +] + [[package]] name = "redox_users" version = "0.4.6" @@ -3436,15 +3515,20 @@ dependencies = [ "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-util", "js-sys", "log", "percent-encoding", "pin-project-lite", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", "serde", "serde_json", "sync_wrapper", "tokio", + "tokio-rustls", "tokio-util", "tower", "tower-http", @@ -3512,6 +3596,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe 0.2.1", + "rustls-pki-types", + "schannel", + "security-framework 3.5.1", +] + [[package]] name = "rustls-pki-types" version = "1.14.0" @@ -3522,6 +3618,33 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework 3.5.1", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.103.9" @@ -3633,6 +3756,19 @@ dependencies = [ "security-framework-sys", ] +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + [[package]] name = "security-framework-sys" version = "2.15.0" @@ -3923,7 +4059,7 @@ dependencies = [ "objc2-foundation", "objc2-quartz-core", "raw-window-handle", - "redox_syscall", + "redox_syscall 0.5.18", "tracing", "wasm-bindgen", "web-sys", @@ -4137,6 +4273,17 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "tar" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "target-lexicon" version = "0.12.16" @@ -4195,24 +4342,6 @@ dependencies = [ "windows 0.61.3", ] -[[package]] -name = "tauri-app" -version = "0.1.2" -dependencies = [ - "env_logger", - "log", - "serde", - "serde_json", - "tauri", - "tauri-build", - "tauri-plugin-autostart", - "tauri-plugin-http", - "tauri-plugin-notification", - "tauri-plugin-opener", - "tauri-plugin-store", - "windows 0.58.0", -] - [[package]] name = "tauri-build" version = "2.5.5" @@ -4394,6 +4523,16 @@ dependencies = [ "zbus", ] +[[package]] +name = "tauri-plugin-process" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d55511a7bf6cd70c8767b02c97bf8134fa434daf3926cfc1be0a0f94132d165a" +dependencies = [ + "tauri", + "tauri-plugin", +] + [[package]] name = "tauri-plugin-store" version = "2.4.2" @@ -4410,6 +4549,39 @@ dependencies = [ "tracing", ] +[[package]] +name = "tauri-plugin-updater" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fe8e9bebd88fc222938ffdfbdcfa0307081423bd01e3252fc337d8bde81fc61" +dependencies = [ + "base64 0.22.1", + "dirs 6.0.0", + "flate2", + "futures-util", + "http", + "infer", + "log", + "minisign-verify", + "osakit", + "percent-encoding", + "reqwest 0.13.2", + "rustls", + "semver", + "serde", + "serde_json", + "tar", + "tauri", + "tauri-plugin", + "tempfile", + "thiserror 2.0.18", + "time", + "tokio", + "url", + "windows-sys 0.60.2", + "zip", +] + [[package]] name = "tauri-runtime" version = "2.10.0" @@ -5305,6 +5477,15 @@ dependencies = [ "system-deps", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "1.0.6" @@ -5984,6 +6165,26 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "work-order-monitor" +version = "0.2.2" +dependencies = [ + "env_logger", + "log", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-autostart", + "tauri-plugin-http", + "tauri-plugin-notification", + "tauri-plugin-opener", + "tauri-plugin-process", + "tauri-plugin-store", + "tauri-plugin-updater", + "windows 0.58.0", +] + [[package]] name = "writeable" version = "0.6.2" @@ -6056,6 +6257,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + [[package]] name = "yoke" version = "0.8.1" @@ -6220,6 +6431,18 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "zip" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1" +dependencies = [ + "arbitrary", + "crc32fast", + "indexmap 2.13.0", + "memchr", +] + [[package]] name = "zmij" version = "1.0.21" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index dabcc07..c9c15cd 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "tauri-app" -version = "0.1.2" +name = "work-order-monitor" +version = "0.2.2" description = "A Tauri App" authors = ["you"] edition = "2021" @@ -11,7 +11,7 @@ edition = "2021" # The `_lib` suffix may seem redundant but it is necessary # to make the lib name unique and wouldn't conflict with the bin name. # This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 -name = "tauri_app_lib" +name = "work_order_monitor_lib" crate-type = ["staticlib", "cdylib", "rlib"] [build-dependencies] @@ -28,6 +28,8 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" log = "0.4" env_logger = "0.10" +tauri-plugin-updater = "2.10.0" +tauri-plugin-process = "2.3.1" [target.'cfg(windows)'.dependencies] windows = { version = "0.58", features = [ diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index f2f6d0a..badb8da 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -41,6 +41,8 @@ "core:window:allow-set-title", "core:window:allow-unminimize", "core:image:default", - "core:image:allow-from-path" + "core:image:allow-from-path", + "updater:default", + "process:allow-restart" ] } diff --git a/src-tauri/gen/android/app/build.gradle.kts b/src-tauri/gen/android/app/build.gradle.kts index 5a7b5af..ea0fad6 100644 --- a/src-tauri/gen/android/app/build.gradle.kts +++ b/src-tauri/gen/android/app/build.gradle.kts @@ -15,10 +15,10 @@ val tauriProperties = Properties().apply { android { compileSdk = 36 - namespace = "com.administrator.tauri_app" + namespace = "com.yunpu.workordermonitor" defaultConfig { manifestPlaceholders["usesCleartextTraffic"] = "false" - applicationId = "com.administrator.tauri_app" + applicationId = "com.yunpu.workordermonitor" minSdk = 24 targetSdk = 36 versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt() diff --git a/src-tauri/gen/android/app/src/main/java/com/administrator/tauri_app/MainActivity.kt b/src-tauri/gen/android/app/src/main/java/com/yunpu/workordermonitor/MainActivity.kt similarity index 83% rename from src-tauri/gen/android/app/src/main/java/com/administrator/tauri_app/MainActivity.kt rename to src-tauri/gen/android/app/src/main/java/com/yunpu/workordermonitor/MainActivity.kt index d0288d2..da256db 100644 --- a/src-tauri/gen/android/app/src/main/java/com/administrator/tauri_app/MainActivity.kt +++ b/src-tauri/gen/android/app/src/main/java/com/yunpu/workordermonitor/MainActivity.kt @@ -1,4 +1,4 @@ -package com.administrator.tauri_app +package com.yunpu.workordermonitor import android.os.Bundle import androidx.activity.enableEdgeToEdge diff --git a/src-tauri/gen/android/app/src/main/res/values/strings.xml b/src-tauri/gen/android/app/src/main/res/values/strings.xml index b0037cb..29e5978 100644 --- a/src-tauri/gen/android/app/src/main/res/values/strings.xml +++ b/src-tauri/gen/android/app/src/main/res/values/strings.xml @@ -1,4 +1,4 @@ - tauri-app - tauri-app + work-order-monitor + work-order-monitor \ No newline at end of file diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 5e87bd6..7df0fe2 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -91,6 +91,8 @@ pub fn run() { MacosLauncher::LaunchAgent, Some(vec![]), )) + .plugin(tauri_plugin_updater::Builder::new().build()) + .plugin(tauri_plugin_process::init()) .setup(|app| { start_notification_callback_server(app.handle().clone()); Ok(()) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 4a9b77a..b7d9dd2 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -2,5 +2,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] fn main() { - tauri_app_lib::run() + work_order_monitor_lib::run() } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 2b1608f..43ca691 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,8 +1,8 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "工单系统监测", - "version": "0.1.3", - "identifier": "com.administrator.tauri-app", + "version": "0.2.2", + "identifier": "com.yunpu.workordermonitor", "build": { "beforeDevCommand": "pnpm dev", "devUrl": "http://localhost:1420", @@ -23,7 +23,10 @@ }, "bundle": { "active": true, - "targets": ["nsis"], + "targets": [ + "nsis" + ], + "createUpdaterArtifacts": true, "icon": [ "icons/32x32.png", "icons/128x128.png", @@ -35,5 +38,17 @@ "icons/32x32.png", "icons/icon.ico" ] + }, + "plugins": { + "updater": { + "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDY0Q0YxMzNBM0Q5OEU4NEMKUldSTTZKZzlPaFBQWkVXRVdBVHdTdlNvVnZSL2FFQkZtbFpRTzdjNGxHbjJsZ1R5MFRLdkpmYzMK", + "endpoints": [ + "http://web.nps.yunvip123.cn/work-order-monitor/update.json" + ], + "dangerousInsecureTransportProtocol": true, + "windows": { + "installMode": "passive" + } + } } } diff --git a/src/App.vue b/src/App.vue index e7e3cc9..e466a9a 100644 --- a/src/App.vue +++ b/src/App.vue @@ -15,6 +15,7 @@ import SettingsPanel from "./components/SettingsPanel.vue"; import LogViewer from "./components/LogViewer.vue"; import AppStatusBar, { type StatusBarItem } from "./components/AppStatusBar.vue"; import CloseDialog from "./components/CloseDialog.vue"; +import UpdateDialog from "./components/UpdateDialog.vue"; import { useMonitorStore } from "./stores/monitor"; const activeTab = ref("monitor"); @@ -81,6 +82,7 @@ onUnmounted(() => store.cleanup()); + diff --git a/src/stores/monitor.ts b/src/stores/monitor.ts index 3ece053..7bee5dd 100644 --- a/src/stores/monitor.ts +++ b/src/stores/monitor.ts @@ -18,6 +18,8 @@ import { import { invoke } from "@tauri-apps/api/core"; import { listen, type UnlistenFn } from "@tauri-apps/api/event"; import { openUrl } from "@tauri-apps/plugin-opener"; +import { check, type Update } from "@tauri-apps/plugin-updater"; +import { relaunch } from "@tauri-apps/plugin-process"; export const APP_NAME = "工单系统监测"; const API_BASE = "https://crm.yunvip123.com/api"; @@ -76,6 +78,10 @@ export const useMonitorStore = defineStore("monitor", () => { }); const showCloseDialog = ref(false); const postponeResumeTime = ref(null); + const showUpdateDialog = ref(false); + const updateInfo = ref<{ version: string; notes: string; date: string } | null>(null); + const updateProgress = ref<{ total: number; downloaded: number } | null>(null); + const isUpdating = ref(false); // ===== 内部状态 ===== let sessionId = ""; @@ -85,7 +91,9 @@ export const useMonitorStore = defineStore("monitor", () => { let postponeTimer: ReturnType | null = null; let unlistenNotification: UnlistenFn | null = null; let unlistenClose: UnlistenFn | null = null; + let pendingUpdate: Update | null = null; let trayIconInstance: TrayIcon | null = null; + let cachedSvgImg: HTMLImageElement | null = null; // ===== 工具函数 ===== function formatTime(date: Date): string { @@ -541,6 +549,60 @@ export const useMonitorStore = defineStore("monitor", () => { } // ===== 配置更新 ===== + + async function checkForUpdates() { + try { + addLog("INFO", "正在检查应用更新...", "UPDATER"); + const update = await check(); + if (update) { + pendingUpdate = update; + updateInfo.value = { + version: update.version, + notes: update.body || "", + date: update.date || "", + }; + showUpdateDialog.value = true; + addLog("INFO", `发现新版本: ${update.version}`, "UPDATER"); + } else { + addLog("INFO", "当前已是最新版本", "UPDATER"); + } + } catch (e: any) { + addLog("WARN", `检查更新失败: ${e.message || e}`, "UPDATER"); + } + } + + async function confirmUpdate() { + if (!pendingUpdate) return; + isUpdating.value = true; + updateProgress.value = { total: 0, downloaded: 0 }; + try { + addLog("INFO", "开始下载更新...", "UPDATER"); + await pendingUpdate.downloadAndInstall((event) => { + if (event.event === "Started" && event.data.contentLength) { + updateProgress.value = { total: event.data.contentLength, downloaded: 0 }; + } else if (event.event === "Progress") { + if (updateProgress.value) { + updateProgress.value.downloaded += event.data.chunkLength; + } + } else if (event.event === "Finished") { + addLog("INFO", "更新下载完成,即将重启...", "UPDATER"); + } + }); + await relaunch(); + } catch (e: any) { + addLog("ERROR", `更新安装失败: ${e.message || e}`, "UPDATER"); + message.value = "更新安装失败"; + isUpdating.value = false; + updateProgress.value = null; + } + } + + function dismissUpdate() { + showUpdateDialog.value = false; + pendingUpdate = null; + updateInfo.value = null; + } + async function updateConfig() { isLoading.value = true; try { @@ -563,29 +625,59 @@ export const useMonitorStore = defineStore("monitor", () => { // ===== 系统托盘 ===== - /** 用 Canvas 渲染彩色圆形背景 + 白色"工"字,返回 RGBA 像素数据 */ - function makeTrayIconRGBA(r: number, g: number, b: number, size = 32): Uint8Array { + /** 加载 SVG 图片(带缓存) */ + async function loadSvgImage(): Promise { + if (cachedSvgImg) return cachedSvgImg; + return new Promise((resolve, reject) => { + const img = new window.Image(); + img.onload = () => { cachedSvgImg = img; resolve(img); }; + img.onerror = reject; + img.src = "/gong.svg"; + }); + } + + /** 加载 SVG → Canvas 渲染 → 像素替色 → 返回 RGBA 数据 */ + async function makeTrayIconRGBA(r: number, g: number, b: number, size = 32): Promise { const canvas = document.createElement("canvas"); canvas.width = size; canvas.height = size; const ctx = canvas.getContext("2d")!; - // 圆形背景 - ctx.clearRect(0, 0, size, size); - ctx.beginPath(); - ctx.arc(size / 2, size / 2, size / 2 - 1, 0, Math.PI * 2); - ctx.fillStyle = `rgb(${r},${g},${b})`; - ctx.fill(); + try { + const svgImg = await loadSvgImage(); - // 白色"工"字 - ctx.fillStyle = "#ffffff"; - ctx.font = `bold ${Math.round(size * 0.62)}px "Microsoft YaHei","SimHei",sans-serif`; - ctx.textAlign = "center"; - ctx.textBaseline = "middle"; - ctx.fillText("工", size / 2, size / 2 + 1); + ctx.drawImage(svgImg, 0, 0, size, size); + const imageData = ctx.getImageData(0, 0, size, size); + const data = imageData.data; - const imageData = ctx.getImageData(0, 0, size, size); - return new Uint8Array(imageData.data.buffer); + for (let i = 0; i < data.length; i += 4) { + // 根据亮度判断:暗像素(文字) → 白色,亮像素(背景) → 状态色 + const brightness = (data[i] * 0.299 + data[i + 1] * 0.587 + data[i + 2] * 0.114); + if (brightness < 128) { + data[i] = data[i + 1] = data[i + 2] = 255; + } else { + data[i] = r; + data[i + 1] = g; + data[i + 2] = b; + } + data[i + 3] = 255; + } + + ctx.putImageData(imageData, 0, 0); + } catch { + // SVG 加载失败,回退到纯 Canvas 绘制 + addLog("WARN", "SVG 图标加载失败,使用回退方案", "SYSTEM"); + ctx.fillStyle = `rgb(${r},${g},${b})`; + ctx.fillRect(0, 0, size, size); + ctx.fillStyle = "#ffffff"; + ctx.font = `bold ${Math.round(size * 0.62)}px "Microsoft YaHei","SimHei",sans-serif`; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillText("工", size / 2, size / 2 + 1); + } + + const finalData = ctx.getImageData(0, 0, size, size); + return new Uint8Array(finalData.data.buffer); } /** 根据当前应用状态更新托盘图标和提示文字 */ @@ -620,7 +712,7 @@ export const useMonitorStore = defineStore("monitor", () => { tooltip = `${APP_NAME} - 监测中`; } - const icon = await Image.new(makeTrayIconRGBA(...color), 32, 32); + const icon = await Image.new(await makeTrayIconRGBA(...color), 32, 32); await trayIconInstance.setIcon(icon); await trayIconInstance.setTooltip(tooltip); } catch (e: any) { @@ -686,7 +778,7 @@ export const useMonitorStore = defineStore("monitor", () => { const menu = await buildTrayMenu(); // 初始状态:灰色(未开始) - const icon = await Image.new(makeTrayIconRGBA(158, 158, 158), 32, 32); + const icon = await Image.new(await makeTrayIconRGBA(158, 158, 158), 32, 32); trayIconInstance = await TrayIcon.new({ icon, @@ -763,6 +855,7 @@ export const useMonitorStore = defineStore("monitor", () => { } checkNetworkStatus(); + checkForUpdates(); if (config.value.auto_monitor && rememberPassword.value) { const canAutoStart = loginMode.value === "token" @@ -795,12 +888,14 @@ export const useMonitorStore = defineStore("monitor", () => { isLoggedIn, isMonitoring, isPostponed, postponeResumeTime, autoStartEnabled, logs, networkStatus, config, ticketCounts, showCloseDialog, + showUpdateDialog, updateInfo, updateProgress, isUpdating, // 方法 initialize, cleanup, addLog, clearLogs, clearMessage, saveSettings, exportLogs, startMonitoring, stopMonitoring, resumeMonitoring, manualCheck, testNotification, checkNetworkStatus, updateConfig, minimizeToTray, handleCloseAction, + checkForUpdates, confirmUpdate, dismissUpdate, }; });