feat: 将"关于"从系统托盘原生对话框迁移到前端页面
移除系统托盘右键菜单中的"关于"选项及各平台原生对话框实现, 在前端新增 /about 路由和关于页面展示品牌信息,侧边栏增加关于导航入口
This commit is contained in:
@@ -18,14 +18,6 @@ func showError(title, message string) {
|
||||
}
|
||||
}
|
||||
|
||||
func showAbout() {
|
||||
script := fmt.Sprintf(`display dialog "%s" buttons {"OK"} default button "OK" with title "%s"`,
|
||||
escapeAppleScript(aboutMessage()), escapeAppleScript(appAboutTitle))
|
||||
if err := exec.Command("osascript", "-e", script).Run(); err != nil {
|
||||
dialogLogger().Warn("显示关于对话框失败", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func escapeAppleScript(s string) string {
|
||||
s = strings.ReplaceAll(s, "\\", "\\\\")
|
||||
s = strings.ReplaceAll(s, "\"", "\\\"")
|
||||
|
||||
@@ -65,21 +65,3 @@ func showError(title, message string) {
|
||||
dialogLogger().Error("无法显示错误对话框")
|
||||
}
|
||||
}
|
||||
|
||||
func showAbout() {
|
||||
switch dialogTool {
|
||||
case toolZenity:
|
||||
exec.Command("zenity", "--info",
|
||||
fmt.Sprintf("--title=%s", appAboutTitle),
|
||||
fmt.Sprintf("--text=%s", aboutMessage())).Run()
|
||||
case toolKdialog:
|
||||
exec.Command("kdialog", "--msgbox", aboutMessage(), "--title", appAboutTitle).Run()
|
||||
case toolNotifySend:
|
||||
exec.Command("notify-send", appAboutTitle, aboutMessage()).Run()
|
||||
case toolXmessage:
|
||||
exec.Command("xmessage", "-center",
|
||||
fmt.Sprintf("%s: %s", appAboutTitle, aboutMessage())).Run()
|
||||
default:
|
||||
dialogLogger().Info(appAboutTitle)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,10 +21,6 @@ func showError(title, message string) {
|
||||
messageBox(title, message, MB_ICONERROR)
|
||||
}
|
||||
|
||||
func showAbout() {
|
||||
messageBox(appAboutTitle, aboutMessage(), MB_ICONINFORMATION)
|
||||
}
|
||||
|
||||
func messageBox(title, message string, flags uint) {
|
||||
titlePtr, _ := syscall.UTF16PtrFromString(title)
|
||||
messagePtr, _ := syscall.UTF16PtrFromString(message)
|
||||
|
||||
@@ -287,8 +287,6 @@ func setupSystray(port int) {
|
||||
mPort := systray.AddMenuItem(fmt.Sprintf("端口: %d", port), "")
|
||||
mPort.Disable()
|
||||
systray.AddSeparator()
|
||||
mAbout := systray.AddMenuItem("关于", "")
|
||||
systray.AddSeparator()
|
||||
mQuit := systray.AddMenuItem("退出", "停止服务并退出")
|
||||
|
||||
go func() {
|
||||
@@ -298,8 +296,6 @@ func setupSystray(port int) {
|
||||
if err := openBrowser(fmt.Sprintf("http://localhost:%d", port)); err != nil {
|
||||
zapLogger.Warn("打开浏览器失败", zap.Error(err))
|
||||
}
|
||||
case <-mAbout.ClickedCh:
|
||||
showAbout()
|
||||
case <-mQuit.ClickedCh:
|
||||
doShutdown()
|
||||
systray.Quit()
|
||||
|
||||
@@ -13,7 +13,3 @@ func TestMessageBoxW_WindowsOnly(t *testing.T) {
|
||||
func TestShowError_WindowsBranch(t *testing.T) {
|
||||
showError("测试错误", "这是一条测试错误消息")
|
||||
}
|
||||
|
||||
func TestShowAbout_WindowsBranch(t *testing.T) {
|
||||
showAbout()
|
||||
}
|
||||
|
||||
@@ -3,11 +3,6 @@ package main
|
||||
const (
|
||||
appName = "Nex"
|
||||
appTooltip = appName
|
||||
appAboutTitle = "关于 " + appName
|
||||
appDescription = "AI Gateway - 统一的大模型 API 网关"
|
||||
appWebsite = "https://github.com/nex/gateway"
|
||||
)
|
||||
|
||||
func aboutMessage() string {
|
||||
return appName + "\n\n" + appDescription + "\n\n" + appWebsite
|
||||
}
|
||||
|
||||
@@ -2,13 +2,6 @@ package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestAboutMessage(t *testing.T) {
|
||||
expected := "Nex\n\nAI Gateway - 统一的大模型 API 网关\n\nhttps://github.com/nex/gateway"
|
||||
if got := aboutMessage(); got != expected {
|
||||
t.Fatalf("aboutMessage() = %q, want %q", got, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDesktopMetadata(t *testing.T) {
|
||||
if appName != "Nex" {
|
||||
t.Fatalf("appName = %q, want %q", appName, "Nex")
|
||||
@@ -17,8 +10,4 @@ func TestDesktopMetadata(t *testing.T) {
|
||||
if appTooltip != appName {
|
||||
t.Fatalf("appTooltip = %q, want %q", appTooltip, appName)
|
||||
}
|
||||
|
||||
if appAboutTitle != "关于 Nex" {
|
||||
t.Fatalf("appAboutTitle = %q, want %q", appAboutTitle, "关于 Nex")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { useState } from 'react'
|
||||
import { Outlet, useLocation, useNavigate } from 'react-router'
|
||||
import { ServerIcon, ChartLineIcon, SettingIcon, ChevronLeftIcon, ChevronRightIcon } from 'tdesign-icons-react'
|
||||
import {
|
||||
ServerIcon,
|
||||
ChartLineIcon,
|
||||
SettingIcon,
|
||||
InfoCircleIcon,
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
} from 'tdesign-icons-react'
|
||||
import { Layout, Menu, Button } from 'tdesign-react'
|
||||
|
||||
const { MenuItem } = Menu
|
||||
@@ -14,6 +21,7 @@ export function AppLayout() {
|
||||
if (location.pathname === '/providers') return '供应商管理'
|
||||
if (location.pathname === '/stats') return '用量统计'
|
||||
if (location.pathname === '/settings') return '设置'
|
||||
if (location.pathname === '/about') return '关于'
|
||||
return 'AI Gateway'
|
||||
}
|
||||
|
||||
@@ -70,6 +78,9 @@ export function AppLayout() {
|
||||
<MenuItem value='/settings' icon={<SettingIcon />}>
|
||||
设置
|
||||
</MenuItem>
|
||||
<MenuItem value='/about' icon={<InfoCircleIcon />}>
|
||||
关于
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</Layout.Aside>
|
||||
<Layout style={{ marginLeft: asideWidth }}>
|
||||
|
||||
30
frontend/src/pages/About/index.tsx
Normal file
30
frontend/src/pages/About/index.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Card } from 'tdesign-react'
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<Card bordered={false}>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '4rem 0',
|
||||
}}
|
||||
>
|
||||
<h1 style={{ margin: 0, fontSize: '2rem' }}>Nex</h1>
|
||||
<p style={{ margin: '0.5rem 0 0', color: 'var(--td-text-color-secondary)', fontSize: '1rem' }}>
|
||||
AI Gateway - 统一的大模型 API 网关
|
||||
</p>
|
||||
<a
|
||||
href='https://github.com/nex/gateway'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
style={{ marginTop: '1rem', color: 'var(--td-brand-color)' }}
|
||||
>
|
||||
https://github.com/nex/gateway
|
||||
</a>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { AppLayout } from '@/components/AppLayout'
|
||||
const ProvidersPage = lazy(() => import('@/pages/Providers'))
|
||||
const StatsPage = lazy(() => import('@/pages/Stats'))
|
||||
const SettingsPage = lazy(() => import('@/pages/Settings'))
|
||||
const AboutPage = lazy(() => import('@/pages/About'))
|
||||
const NotFound = lazy(() => import('@/pages/NotFound'))
|
||||
|
||||
export function AppRoutes() {
|
||||
@@ -17,6 +18,7 @@ export function AppRoutes() {
|
||||
<Route path='providers' element={<ProvidersPage />} />
|
||||
<Route path='stats' element={<StatsPage />} />
|
||||
<Route path='settings' element={<SettingsPage />} />
|
||||
<Route path='about' element={<AboutPage />} />
|
||||
<Route path='*' element={<NotFound />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
|
||||
26
openspec/specs/about-page/spec.md
Normal file
26
openspec/specs/about-page/spec.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# 关于页面
|
||||
|
||||
## Purpose
|
||||
|
||||
TBD - 提供关于页面展示项目品牌信息
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: 关于页面
|
||||
|
||||
前端 SHALL 提供关于页面,使用 TDesign Card 组件居中展示项目品牌信息(应用名称、描述、项目链接)。
|
||||
|
||||
#### Scenario: 显示关于页面
|
||||
|
||||
- **WHEN** 用户访问 `/about` 路径
|
||||
- **THEN** 前端 SHALL 显示关于页面
|
||||
- **THEN** 页面 SHALL 展示应用名称"Nex"
|
||||
- **THEN** 页面 SHALL 展示应用描述"AI Gateway - 统一的大模型 API 网关"
|
||||
- **THEN** 页面 SHALL 展示项目链接"https://github.com/nex/gateway"
|
||||
|
||||
#### Scenario: 页面布局
|
||||
|
||||
- **WHEN** 渲染关于页面
|
||||
- **THEN** 页面 SHALL 使用 TDesign Card 组件作为容器
|
||||
- **THEN** Card SHALL 设置 `bordered={false}`
|
||||
- **THEN** 内容 SHALL 居中展示
|
||||
@@ -51,7 +51,6 @@ TBD - 提供跨平台桌面应用支持,将后端服务与前端静态资源
|
||||
- **AND** 菜单包含"打开管理界面"选项
|
||||
- **AND** 菜单包含"状态: 运行中"选项(禁用状态)
|
||||
- **AND** 菜单包含"端口: 9826"选项(禁用状态)
|
||||
- **AND** 菜单包含"关于"选项
|
||||
- **AND** 菜单包含"退出"选项
|
||||
|
||||
#### Scenario: 打开管理界面
|
||||
@@ -140,19 +139,9 @@ TBD - 提供跨平台桌面应用支持,将后端服务与前端静态资源
|
||||
- **AND** 包含 `Contents/Resources/icon.icns` 图标
|
||||
- **AND** `Info.plist` 中 `LSUIElement` 为 `true`(不显示 Dock 图标)
|
||||
|
||||
### Requirement: 关于对话框
|
||||
|
||||
系统 SHALL 提供关于对话框显示应用信息。在 Windows 上 SHALL 使用 `user32.dll` 的 `MessageBoxW` API 实现。
|
||||
|
||||
#### Scenario: 显示关于
|
||||
|
||||
- **WHEN** 用户点击托盘菜单"关于"
|
||||
- **THEN** 显示对话框包含应用名称、项目链接
|
||||
- **AND** 在 Windows 上使用 `MessageBoxW` 原生对话框实现
|
||||
|
||||
### Requirement: Windows 原生对话框
|
||||
|
||||
系统 SHALL 在 Windows 上使用 `user32.dll` 的 `MessageBoxW` API 显示错误和关于对话框,替代 `msg *` 命令。
|
||||
系统 SHALL 在 Windows 上使用 `user32.dll` 的 `MessageBoxW` API 显示错误对话框,替代 `msg *` 命令。
|
||||
|
||||
#### Scenario: 错误提示对话框
|
||||
|
||||
@@ -162,18 +151,10 @@ TBD - 提供跨平台桌面应用支持,将后端服务与前端静态资源
|
||||
- **AND** 对话框包含错误描述文本
|
||||
- **AND** 对话框显示错误图标(MB_ICONERROR)
|
||||
|
||||
#### Scenario: 关于对话框
|
||||
|
||||
- **WHEN** 用户在 Windows 上点击托盘菜单"关于"
|
||||
- **THEN** 使用 `MessageBoxW` 显示模态对话框
|
||||
- **AND** 对话框标题栏显示"关于 Nex Gateway"
|
||||
- **AND** 对话框包含应用信息文本
|
||||
- **AND** 对话框显示信息图标(MB_ICONINFORMATION)
|
||||
|
||||
#### Scenario: 非 Windows 平台不受影响
|
||||
|
||||
- **WHEN** 应用运行在 macOS 或 Linux 上
|
||||
- **THEN** 错误和关于对话框仍使用平台原有实现(osascript / zenity)
|
||||
- **THEN** 错误对话框仍使用平台原有实现(osascript / zenity)
|
||||
|
||||
### Requirement: Linux 对话框降级策略
|
||||
|
||||
|
||||
@@ -409,7 +409,7 @@ TBD - 提供供应商、模型配置和用量统计的前端管理界面
|
||||
- **WHEN** 渲染侧边栏
|
||||
- **THEN** 侧边栏顶部 SHALL 显示应用名称/Logo
|
||||
- **THEN** 侧边栏 SHALL 包含导航菜单
|
||||
- **THEN** 导航菜单项 SHALL 包含:供应商管理(ServerIcon 图标)、用量统计(ChartLineIcon 图标)、设置(SettingIcon 图标)
|
||||
- **THEN** 导航菜单项 SHALL 包含:供应商管理(ServerIcon 图标)、用量统计(ChartLineIcon 图标)、设置(SettingIcon 图标)、关于(InfoCircleIcon 图标)
|
||||
|
||||
#### Scenario: 导航菜单交互
|
||||
|
||||
@@ -418,7 +418,9 @@ TBD - 提供供应商、模型配置和用量统计的前端管理界面
|
||||
- **WHEN** 用户点击导航中的"用量统计"
|
||||
- **THEN** 前端 SHALL 导航到 \`/stats\` 并高亮当前菜单项
|
||||
- **WHEN** 用户点击导航中的"设置"
|
||||
- **THEN** 前端 SHALL 导航到 \`/settings\` 并高亮当前菜单项
|
||||
- **THEN** 前端 SHALL 导航到 `/settings` 并高亮当前菜单项
|
||||
- **WHEN** 用户点击导航中的"关于"
|
||||
- **THEN** 前端 SHALL 导航到 `/about` 并高亮当前菜单项
|
||||
|
||||
### Requirement: 提供导航
|
||||
|
||||
@@ -430,6 +432,8 @@ TBD - 提供供应商、模型配置和用量统计的前端管理界面
|
||||
- **THEN** 前端 SHALL 使用 React Router v7 Library 模式(BrowserRouter)
|
||||
- **THEN** `/providers` 路径 SHALL 显示供应商管理页面
|
||||
- **THEN** `/stats` 路径 SHALL 显示用量统计页面
|
||||
- **THEN** `/settings` 路径 SHALL 显示设置页面
|
||||
- **THEN** `/about` 路径 SHALL 显示关于页面
|
||||
- **THEN** `/` 路径 SHALL 重定向到 `/providers`
|
||||
- **THEN** 不存在的路径 SHALL 显示 404 页面
|
||||
|
||||
|
||||
Reference in New Issue
Block a user