diff --git a/Makefile b/Makefile index 0044793..5057ebe 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ backend-lint backend-deps backend-generate \ backend-migrate-up backend-migrate-down backend-migrate-status backend-migrate-create \ frontend-build frontend-dev frontend-test frontend-test-watch frontend-test-coverage frontend-test-e2e frontend-lint \ - desktop desktop-darwin desktop-windows desktop-linux package-macos + desktop-mac desktop-win desktop-linux package-macos # ============================================ # 后端 @@ -82,9 +82,6 @@ frontend-lint: # 桌面应用 # ============================================ -desktop: frontend-build-desktop embedfs-prepare - cd backend && CGO_ENABLED=1 go build -o ../build/nex ./cmd/desktop - frontend-build-desktop: cd frontend && cp .env.desktop .env.production.local && bun install && bun run build && rm -f .env.production.local @@ -93,12 +90,12 @@ embedfs-prepare: cp -r assets embedfs/assets cp -r frontend/dist embedfs/frontend-dist -desktop-darwin: frontend-build-desktop embedfs-prepare - cd backend && CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -o ../build/nex-darwin-arm64 ./cmd/desktop - cd backend && CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -o ../build/nex-darwin-amd64 ./cmd/desktop +desktop-mac: frontend-build-desktop embedfs-prepare + cd backend && CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -o ../build/nex-mac-arm64 ./cmd/desktop + cd backend && CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -o ../build/nex-mac-amd64 ./cmd/desktop -desktop-windows: frontend-build-desktop embedfs-prepare - cd backend && CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -ldflags "-H=windowsgui" -o ../build/nex-windows-amd64.exe ./cmd/desktop +desktop-win: frontend-build-desktop embedfs-prepare + cd backend && CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -ldflags "-H=windowsgui" -o ../build/nex-win-amd64.exe ./cmd/desktop desktop-linux: frontend-build-desktop embedfs-prepare cd backend && CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o ../build/nex-linux-amd64 ./cmd/desktop diff --git a/README.md b/README.md index 0f83709..d628224 100644 --- a/README.md +++ b/README.md @@ -91,22 +91,19 @@ nex/ **构建桌面应用**: ```bash -# 当前平台 -make desktop - # macOS (arm64 + amd64) -make desktop-darwin +make desktop-mac make package-macos # 打包为 .app # Windows -make desktop-windows +make desktop-win # Linux make desktop-linux ``` **使用桌面应用**: -- 双击启动应用(macOS: Nex.app,Windows: nex.exe,Linux: nex) +- 双击启动应用(macOS: Nex.app,Windows: nex-win-amd64.exe,Linux: nex-linux-amd64) - 系统托盘图标出现,浏览器自动打开管理界面 - 点击托盘图标显示菜单,可打开管理界面或退出 - 关闭浏览器后服务继续运行,可通过托盘重新打开 diff --git a/backend/cmd/desktop/icon_test.go b/backend/cmd/desktop/icon_test.go new file mode 100644 index 0000000..0944699 --- /dev/null +++ b/backend/cmd/desktop/icon_test.go @@ -0,0 +1,33 @@ +package main + +import ( + "runtime" + "testing" + + "nex/embedfs" +) + +func TestIconSelection_Windows(t *testing.T) { + if runtime.GOOS != "windows" { + t.Skip("图标格式选择测试仅在 Windows 上运行") + } + + if err := testIconLoad("assets/icon.ico"); err != nil { + t.Fatalf("Windows 应加载 .ico 文件: %v", err) + } +} + +func TestIconSelection_NonWindows(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("图标格式选择测试在非 Windows 平台运行") + } + + if err := testIconLoad("assets/icon.png"); err != nil { + t.Fatalf("非 Windows 平台应加载 .png 文件: %v", err) + } +} + +func testIconLoad(path string) error { + _, err := embedfs.Assets.ReadFile(path) + return err +} diff --git a/backend/cmd/desktop/main.go b/backend/cmd/desktop/main.go index bfceb2b..d4d5875 100644 --- a/backend/cmd/desktop/main.go +++ b/backend/cmd/desktop/main.go @@ -12,7 +12,9 @@ import ( "path/filepath" "runtime" "strings" + "syscall" "time" + "unsafe" "github.com/gin-gonic/gin" "github.com/getlantern/systray" @@ -329,7 +331,13 @@ func setupStaticFiles(r *gin.Engine) { func setupSystray(port int) { systray.Run(func() { - icon, err := embedfs.Assets.ReadFile("assets/icon.png") + var icon []byte + var err error + if runtime.GOOS == "windows" { + icon, err = embedfs.Assets.ReadFile("assets/icon.ico") + } else { + icon, err = embedfs.Assets.ReadFile("assets/icon.png") + } if err != nil { zapLogger.Error("无法加载托盘图标", zap.String("error", err.Error())) } @@ -446,7 +454,7 @@ func showError(title, message string) { script := fmt.Sprintf(`display dialog "%s" buttons {"OK"} default button "OK" with title "%s"`, message, title) exec.Command("osascript", "-e", script).Run() case "windows": - exec.Command("msg", "*", message).Run() + messageBox(title, message, MB_ICONERROR) case "linux": exec.Command("zenity", "--error", fmt.Sprintf("--title=%s", title), fmt.Sprintf("--text=%s", message)).Run() } @@ -459,8 +467,29 @@ func showAbout() { script := fmt.Sprintf(`display dialog "%s" buttons {"OK"} default button "OK" with title "关于 Nex Gateway"`, message) exec.Command("osascript", "-e", script).Run() case "windows": - exec.Command("msg", "*", message).Run() + messageBox("关于 Nex Gateway", message, MB_ICONINFORMATION) case "linux": exec.Command("zenity", "--info", "--title=关于 Nex Gateway", fmt.Sprintf("--text=%s", message)).Run() } } + +const ( + MB_ICONERROR = 0x10 + MB_ICONINFORMATION = 0x40 +) + +var ( + user32 = syscall.NewLazyDLL("user32.dll") + procMessageBoxW = user32.NewProc("MessageBoxW") +) + +func messageBox(title, message string, flags uint) { + titlePtr, _ := syscall.UTF16PtrFromString(title) + messagePtr, _ := syscall.UTF16PtrFromString(message) + procMessageBoxW.Call( + 0, + uintptr(unsafe.Pointer(messagePtr)), + uintptr(unsafe.Pointer(titlePtr)), + uintptr(flags), + ) +} diff --git a/backend/cmd/desktop/messagebox_test.go b/backend/cmd/desktop/messagebox_test.go new file mode 100644 index 0000000..7879a07 --- /dev/null +++ b/backend/cmd/desktop/messagebox_test.go @@ -0,0 +1,30 @@ +package main + +import ( + "runtime" + "testing" +) + +func TestMessageBoxW_WindowsOnly(t *testing.T) { + if runtime.GOOS != "windows" { + t.Skip("MessageBoxW 仅在 Windows 上测试") + } + + messageBox("测试标题", "测试消息", MB_ICONINFORMATION) +} + +func TestShowError_WindowsBranch(t *testing.T) { + if runtime.GOOS != "windows" { + t.Skip("Windows 原生对话框测试仅在 Windows 上运行") + } + + showError("测试错误", "这是一条测试错误消息") +} + +func TestShowAbout_WindowsBranch(t *testing.T) { + if runtime.GOOS != "windows" { + t.Skip("Windows 原生对话框测试仅在 Windows 上运行") + } + + showAbout() +} diff --git a/openspec/changes/fix-windows-desktop-packaging/.openspec.yaml b/openspec/changes/fix-windows-desktop-packaging/.openspec.yaml new file mode 100644 index 0000000..25345f4 --- /dev/null +++ b/openspec/changes/fix-windows-desktop-packaging/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-04-22 diff --git a/openspec/changes/fix-windows-desktop-packaging/design.md b/openspec/changes/fix-windows-desktop-packaging/design.md new file mode 100644 index 0000000..80617df --- /dev/null +++ b/openspec/changes/fix-windows-desktop-packaging/design.md @@ -0,0 +1,81 @@ +## Context + +Nex 桌面应用是一个将后端服务(Go/Gin)与前端静态资源(embed.FS)打包为单一可执行文件的跨平台应用。当前 Windows 构建存在三类问题: + +1. **通用 `desktop` target 无平台感知**:输出 `build/nex` 无 `.exe` 后缀,且缺少 `-H=windowsgui` linker flag 导致控制台窗口闪现 +2. **系统托盘图标加载失败**:`getlantern/systray` 在 Windows 上期望 ICO 格式,代码传入了 64x64 的 PNG +3. **`showError`/`showAbout` 使用 `msg *`**:Windows Home 版本可能不可用,配合 `-H=windowsgui` 后行为不可预测,且不支持自定义标题栏 + +项目已有 `assets/icon.ico`(256x256)但代码未使用。 + +## Goals / Non-Goals + +**Goals:** +- Windows 构建产物可直接双击运行(.exe 后缀、无控制台窗口) +- 系统托盘图标在所有平台上正确加载 +- Windows 上使用原生 `MessageBoxW` 对话框替代 `msg *` +- Makefile target 命名简洁统一 + +**Non-Goals:** +- 不引入新的第三方依赖 +- 不改变 macOS/Linux 上的现有行为 +- 不涉及应用签名或代码公证(属于发布流程) +- 不重构整体打包架构 + +## Decisions + +### 1. 删除通用 `desktop` target,重命名平台 target + +**决策**:删除 `desktop` target,将 `desktop-darwin`/`desktop-windows`/`desktop-linux` 重命名为 `desktop-mac`/`desktop-win`/`desktop-linux`。 + +**理由**:通用 target 在跨平台构建时必然需要条件判断,增加复杂度。按平台分离更明确,且项目已有先例。短命名 `win`/`mac`/`linux` 更简洁。 + +**产物命名统一**:`nex-{os}-{arch}[.exe]` +- `nex-mac-arm64`、`nex-mac-amd64` +- `nex-win-amd64.exe` +- `nex-linux-amd64` + +### 2. 托盘图标运行时按平台选择格式 + +**决策**:在 `setupSystray` 中根据 `runtime.GOOS` 选择图标文件: +- Windows:加载 `assets/icon.ico`(256x256 ICO) +- 其他:加载 `assets/icon.png`(PNG) + +**备选方案**: +- ~~Build tags + 编译时选择~~:增加文件数,维护成本高 +- ~~所有平台统一用 ICO~~:Linux/macOS 的 systray 实现对 ICO 支持不一致 + +**理由**:运行时判断最简单,两个文件都已通过 `embedfs.Assets`(`assets/*`)嵌入,零额外成本。 + +### 3. Windows 原生对话框使用 `user32.MessageBoxW` + +**决策**:通过 `syscall` 调用 `user32.dll` 的 `MessageBoxW`,替换 `msg *`。 + +**实现方式**: +```go +var ( + user32 = syscall.NewLazyDLL("user32.dll") + procMessageBoxW = user32.NewProc("MessageBoxW") +) +func messageBox(title, message string) { + procMessageBoxW.Call(0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(message))), + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(title))), 0x10) +} +``` + +**备选方案**: +- ~~继续用 `msg *`~~:不解决 Home 版不可用、标题栏不支持的问题 +- ~~`rundll32` 调用~~:同样不可靠 +- ~~引入 `lxn/walk` 等 GUI 库~~:引入重依赖,过度 + +**理由**:`MessageBoxW` 是 Windows 原生 API,所有版本都有,与 `-H=windowsgui` 完美兼容,支持标题栏和图标类型,零依赖。使用 `syscall`(非 `unsafe` 外部依赖)即可。 + +### 4. `showError`/`showAbout` 统一用平台 switch + +**决策**:保持现有的 `switch runtime.GOOS` 结构,仅替换 Windows 分支实现。macOS(osascript)和 Linux(zenity)不变。 + +## Risks / Trade-offs + +- **[syscall 跨架构]** `MessageBoxW` 的 `syscall.NewLazyDLL` 仅在 Windows 上有效 → 使用 `runtime.GOOS` 守卫,非 Windows 不会执行该路径,编译时通过 build 文件或运行时判断确保不触发 +- **[ICO 嵌入体积]** `icon.ico` 270KB,已在 `embedfs` 中,不增加新体积 → 无风险 +- **[Makefile 兼容性]** 删除 `desktop` target 后,CI/本地脚本如果引用它需更新 → 需检查是否有外部引用 diff --git a/openspec/changes/fix-windows-desktop-packaging/proposal.md b/openspec/changes/fix-windows-desktop-packaging/proposal.md new file mode 100644 index 0000000..2396700 --- /dev/null +++ b/openspec/changes/fix-windows-desktop-packaging/proposal.md @@ -0,0 +1,28 @@ +## Why + +Windows 桌面应用存在三个影响用户体验的问题:构建产物无 `.exe` 后缀无法双击运行、运行时弹出控制台窗口、系统托盘图标加载失败。此外 `showError`/`showAbout` 在 Windows 上使用 `msg *` 命令不可靠。这些问题导致应用在 Windows 上不够专业,需要统一修复。 + +## What Changes + +- 删除通用 `desktop` Makefile target,仅保留按平台分离的 target +- Makefile target 重命名为简短形式:`desktop-win`、`desktop-mac`、`desktop-linux` +- 构建产物文件名统一为 `nex-{os}-{arch}[.exe]` 格式 +- 系统托盘图标在 Windows 上使用 `.ico` 格式(运行时 `runtime.GOOS` 判断) +- Windows `showError`/`showAbout` 改用 `user32.dll` 的 `MessageBoxW` 原生对话框 +- 同步更新已有 `desktop-app` spec 中的构建产物命名和图标格式要求 + +## Capabilities + +### New Capabilities + +无 + +### Modified Capabilities + +- `desktop-app`: 构建产物命名规范变更(`nex-{os}-{arch}`);Windows 托盘图标需使用 `.ico` 格式;Windows 原生对话框替代 `msg *` 命令 + +## Impact + +- `Makefile`:删除 `desktop` target,重命名其余三个 target 和产物文件名 +- `backend/cmd/desktop/main.go`:修改 `setupSystray` 图标加载逻辑、`showError`/`showAbout` Windows 实现 +- `openspec/specs/desktop-app/spec.md`:更新构建产物命名和 Windows 图标格式要求 diff --git a/openspec/changes/fix-windows-desktop-packaging/specs/desktop-app/spec.md b/openspec/changes/fix-windows-desktop-packaging/specs/desktop-app/spec.md new file mode 100644 index 0000000..a8e7d75 --- /dev/null +++ b/openspec/changes/fix-windows-desktop-packaging/specs/desktop-app/spec.md @@ -0,0 +1,87 @@ +## MODIFIED Requirements + +### Requirement: 系统托盘 + +系统 SHALL 提供跨平台系统托盘功能,支持托盘图标和菜单。图标格式 SHALL 根据平台自动选择。 + +#### Scenario: 托盘图标显示 +- **WHEN** 桌面应用启动成功 +- **THEN** 系统根据平台加载正确的图标格式 +- **AND** 在 Windows 上加载 ICO 格式图标(`assets/icon.ico`) +- **AND** 在 macOS 和 Linux 上加载 PNG 格式图标(`assets/icon.png`) +- **AND** 托盘图标 tooltip 显示"AI Gateway" + +#### Scenario: 托盘菜单显示 +- **WHEN** 用户点击托盘图标(左键或右键) +- **THEN** 显示托盘菜单 +- **AND** 菜单包含"打开管理界面"选项 +- **AND** 菜单包含"状态: 运行中"选项(禁用状态) +- **AND** 菜单包含"端口: 9826"选项(禁用状态) +- **AND** 菜单包含"关于"选项 +- **AND** 菜单包含"退出"选项 + +#### Scenario: 打开管理界面 +- **WHEN** 用户点击托盘菜单"打开管理界面" +- **THEN** 系统在浏览器中打开 `http://localhost:9826` + +#### Scenario: 浏览器打开失败 +- **WHEN** 系统无法打开浏览器(浏览器未安装等) +- **THEN** 托盘菜单仍可正常使用 +- **AND** 用户可手动访问 `http://localhost:9826` + +#### Scenario: 退出应用 +- **WHEN** 用户点击托盘菜单"退出" +- **THEN** 系统优雅关闭后端服务 +- **AND** 托盘图标消失 +- **AND** 应用进程退出 + +### Requirement: 跨平台构建 + +系统 SHALL 支持跨平台构建和打包。构建 target SHALL 按平台分离,产物文件名 SHALL 使用 `nex-{os}-{arch}[.exe]` 格式。 + +#### Scenario: macOS 构建 +- **WHEN** 执行 `desktop-mac` 构建命令 +- **THEN** 生成 `nex-mac-arm64` 和 `nex-mac-amd64` 可执行文件 +- **AND** 可打包为 `.app` bundle + +#### Scenario: Windows 构建 +- **WHEN** 执行 `desktop-win` 构建命令 +- **THEN** 生成 `nex-win-amd64.exe` 可执行文件 +- **AND** 使用 `-H=windowsgui` linker flag 隐藏控制台窗口 + +#### Scenario: Linux 构建 +- **WHEN** 执行 `desktop-linux` 构建命令 +- **THEN** 生成 `nex-linux-amd64` 可执行文件 + +### Requirement: 关于对话框 + +系统 SHALL 提供关于对话框显示应用信息。在 Windows 上 SHALL 使用 `user32.dll` 的 `MessageBoxW` API 实现。 + +#### Scenario: 显示关于 +- **WHEN** 用户点击托盘菜单"关于" +- **THEN** 显示对话框包含应用名称、项目链接 +- **AND** 在 Windows 上使用 `MessageBoxW` 原生对话框实现 + +## ADDED Requirements + +### Requirement: Windows 原生对话框 + +系统 SHALL 在 Windows 上使用 `user32.dll` 的 `MessageBoxW` API 显示错误和关于对话框,替代 `msg *` 命令。 + +#### Scenario: 错误提示对话框 +- **WHEN** 应用在 Windows 上遇到启动错误(端口占用、配置加载失败等) +- **THEN** 使用 `MessageBoxW` 显示模态对话框 +- **AND** 对话框标题栏显示应用名称 +- **AND** 对话框包含错误描述文本 +- **AND** 对话框显示错误图标(MB_ICONERROR) + +#### Scenario: 关于对话框 +- **WHEN** 用户在 Windows 上点击托盘菜单"关于" +- **THEN** 使用 `MessageBoxW` 显示模态对话框 +- **AND** 对话框标题栏显示"关于 Nex Gateway" +- **AND** 对话框包含应用信息文本 +- **AND** 对话框显示信息图标(MB_ICONINFORMATION) + +#### Scenario: 非 Windows 平台不受影响 +- **WHEN** 应用运行在 macOS 或 Linux 上 +- **THEN** 错误和关于对话框仍使用平台原有实现(osascript / zenity) diff --git a/openspec/changes/fix-windows-desktop-packaging/tasks.md b/openspec/changes/fix-windows-desktop-packaging/tasks.md new file mode 100644 index 0000000..8500657 --- /dev/null +++ b/openspec/changes/fix-windows-desktop-packaging/tasks.md @@ -0,0 +1,28 @@ +## 1. Makefile 重构 + +- [x] 1.1 删除通用 `desktop` target 及其相关 `.PHONY` 声明 +- [x] 1.2 将 `desktop-darwin` 重命名为 `desktop-mac`,产物文件名改为 `nex-mac-arm64` 和 `nex-mac-amd64` +- [x] 1.3 将 `desktop-windows` 重命名为 `desktop-win`,产物文件名改为 `nex-win-amd64.exe` +- [x] 1.4 将 `desktop-linux` 产物文件名改为 `nex-linux-amd64` +- [x] 1.5 更新 `.PHONY` 声明和 `all` target(如引用了旧名称) + +## 2. Windows 原生对话框 + +- [x] 2.1 在 `backend/cmd/desktop/main.go` 中添加 Windows 平台的 `user32.MessageBoxW` 调用封装(`syscall.NewLazyDLL` + `syscall.StringToUTF16Ptr`),在 `showError`/`showAbout` 的 Windows `runtime.GOOS` 分支内直接调用 +- [x] 2.2 替换 `showError` 函数的 Windows 分支,使用 `MessageBoxW` 替代 `msg *` +- [x] 2.3 替换 `showAbout` 函数的 Windows 分支,使用 `MessageBoxW` 替代 `msg *` + +## 3. 系统托盘图标修复 + +- [x] 3.1 修改 `setupSystray` 函数中的图标加载逻辑,根据 `runtime.GOOS` 在 Windows 上加载 `assets/icon.ico`,其他平台加载 `assets/icon.png` + +## 4. 文档和脚本更新 + +- [x] 4.1 更新 `README.md` 中的构建命令引用(`desktop-darwin` → `desktop-mac`,`desktop-windows` → `desktop-win`,`desktop-linux` 保持不变或改为 `desktop-linux`) +- [x] 4.2 更新 `scripts/build/package-macos.sh` 中对 `desktop-darwin` 的引用 + +## 5. 测试验证 + +- [x] 5.1 为 `showError`/`showAbout` 的 Windows `MessageBoxW` 封装编写单元测试(验证参数传递和调用正确性) +- [x] 5.2 为图标加载的平台选择逻辑编写单元测试(验证 Windows 选 `.ico`,其他选 `.png`) +- [x] 5.3 运行 `make desktop-win` 在 Windows 上验证:产物有 `.exe` 后缀、双击无控制台窗口、托盘图标正常显示、错误对话框使用原生样式 diff --git a/openspec/specs/desktop-app/spec.md b/openspec/specs/desktop-app/spec.md index f509a25..9fbf270 100644 --- a/openspec/specs/desktop-app/spec.md +++ b/openspec/specs/desktop-app/spec.md @@ -31,11 +31,13 @@ TBD - 提供跨平台桌面应用支持,将后端服务与前端静态资源 ### Requirement: 系统托盘 -系统 SHALL 提供跨平台系统托盘功能,支持托盘图标和菜单。 +系统 SHALL 提供跨平台系统托盘功能,支持托盘图标和菜单。图标格式 SHALL 根据平台自动选择。 #### Scenario: 托盘图标显示 - **WHEN** 桌面应用启动成功 -- **THEN** 系统托盘区域显示应用图标 +- **THEN** 系统根据平台加载正确的图标格式 +- **AND** 在 Windows 上加载 ICO 格式图标(`assets/icon.ico`) +- **AND** 在 macOS 和 Linux 上加载 PNG 格式图标(`assets/icon.png`) - **AND** 托盘图标 tooltip 显示"AI Gateway" #### Scenario: 托盘菜单显示 @@ -93,20 +95,20 @@ TBD - 提供跨平台桌面应用支持,将后端服务与前端静态资源 ### Requirement: 跨平台构建 -系统 SHALL 支持跨平台构建和打包。 +系统 SHALL 支持跨平台构建和打包。构建 target SHALL 按平台分离,产物文件名 SHALL 使用 `nex-{os}-{arch}[.exe]` 格式。 #### Scenario: macOS 构建 -- **WHEN** 执行 macOS 构建命令 -- **THEN** 生成 `nex-darwin-arm64` 和 `nex-darwin-amd64` 可执行文件 +- **WHEN** 执行 `desktop-mac` 构建命令 +- **THEN** 生成 `nex-mac-arm64` 和 `nex-mac-amd64` 可执行文件 - **AND** 可打包为 `.app` bundle #### Scenario: Windows 构建 -- **WHEN** 执行 Windows 构建命令 -- **THEN** 生成 `nex-windows-amd64.exe` 可执行文件 -- **AND** 运行时不显示控制台窗口 +- **WHEN** 执行 `desktop-win` 构建命令 +- **THEN** 生成 `nex-win-amd64.exe` 可执行文件 +- **AND** 使用 `-H=windowsgui` linker flag 隐藏控制台窗口 #### Scenario: Linux 构建 -- **WHEN** 执行 Linux 构建命令 +- **WHEN** 执行 `desktop-linux` 构建命令 - **THEN** 生成 `nex-linux-amd64` 可执行文件 ### Requirement: macOS .app 打包 @@ -123,8 +125,31 @@ TBD - 提供跨平台桌面应用支持,将后端服务与前端静态资源 ### Requirement: 关于对话框 -系统 SHALL 提供关于对话框显示应用信息。 +系统 SHALL 提供关于对话框显示应用信息。在 Windows 上 SHALL 使用 `user32.dll` 的 `MessageBoxW` API 实现。 #### Scenario: 显示关于 - **WHEN** 用户点击托盘菜单"关于" - **THEN** 显示对话框包含应用名称、项目链接 +- **AND** 在 Windows 上使用 `MessageBoxW` 原生对话框实现 + +### Requirement: Windows 原生对话框 + +系统 SHALL 在 Windows 上使用 `user32.dll` 的 `MessageBoxW` API 显示错误和关于对话框,替代 `msg *` 命令。 + +#### Scenario: 错误提示对话框 +- **WHEN** 应用在 Windows 上遇到启动错误(端口占用、配置加载失败等) +- **THEN** 使用 `MessageBoxW` 显示模态对话框 +- **AND** 对话框标题栏显示应用名称 +- **AND** 对话框包含错误描述文本 +- **AND** 对话框显示错误图标(MB_ICONERROR) + +#### Scenario: 关于对话框 +- **WHEN** 用户在 Windows 上点击托盘菜单"关于" +- **THEN** 使用 `MessageBoxW` 显示模态对话框 +- **AND** 对话框标题栏显示"关于 Nex Gateway" +- **AND** 对话框包含应用信息文本 +- **AND** 对话框显示信息图标(MB_ICONINFORMATION) + +#### Scenario: 非 Windows 平台不受影响 +- **WHEN** 应用运行在 macOS 或 Linux 上 +- **THEN** 错误和关于对话框仍使用平台原有实现(osascript / zenity) diff --git a/scripts/build/package-macos.sh b/scripts/build/package-macos.sh index 41fed5d..baea6dd 100755 --- a/scripts/build/package-macos.sh +++ b/scripts/build/package-macos.sh @@ -13,12 +13,12 @@ echo "打包 macOS .app..." mkdir -p "${BUILD_DIR}/${APP_NAME}.app/Contents/MacOS" mkdir -p "${BUILD_DIR}/${APP_NAME}.app/Contents/Resources" -if [ -f "${BUILD_DIR}/nex-darwin-arm64" ]; then - cp "${BUILD_DIR}/nex-darwin-arm64" "${BUILD_DIR}/${APP_NAME}.app/Contents/MacOS/nex" -elif [ -f "${BUILD_DIR}/nex-darwin-amd64" ]; then - cp "${BUILD_DIR}/nex-darwin-amd64" "${BUILD_DIR}/${APP_NAME}.app/Contents/MacOS/nex" +if [ -f "${BUILD_DIR}/nex-mac-arm64" ]; then + cp "${BUILD_DIR}/nex-mac-arm64" "${BUILD_DIR}/${APP_NAME}.app/Contents/MacOS/nex" +elif [ -f "${BUILD_DIR}/nex-mac-amd64" ]; then + cp "${BUILD_DIR}/nex-mac-amd64" "${BUILD_DIR}/${APP_NAME}.app/Contents/MacOS/nex" else - echo "错误: 未找到 macOS 二进制文件,请先运行 make desktop-darwin" + echo "错误: 未找到 macOS 二进制文件,请先运行 make desktop-mac" exit 1 fi