feat(nav): restructure navigation items into groups with metadata and update styles

This commit is contained in:
LTbinglingfeng
2026-05-26 01:15:59 +08:00
parent 8d3a4822db
commit 544b365fa6
6 changed files with 228 additions and 25 deletions
+104 -22
View File
@@ -384,18 +384,85 @@ export function MainLayout() {
});
}, [fetchConfig]);
const navItems = [
{ path: '/', label: t('nav.dashboard'), icon: sidebarIcons.dashboard },
{ path: '/config', label: t('nav.config_management'), icon: sidebarIcons.config },
{ path: '/ai-providers', label: t('nav.ai_providers'), icon: sidebarIcons.aiProviders },
{ path: '/auth-files', label: t('nav.auth_files'), icon: sidebarIcons.authFiles },
{ path: '/oauth', label: t('nav.oauth', { defaultValue: 'OAuth' }), icon: sidebarIcons.oauth },
{ path: '/quota', label: t('nav.quota_management'), icon: sidebarIcons.quota },
...(config?.loggingToFile
? [{ path: '/logs', label: t('nav.logs'), icon: sidebarIcons.logs }]
: []),
{ path: '/system', label: t('nav.system_info'), icon: sidebarIcons.system },
const navGroups = [
{
id: 'operate',
labelKey: 'nav_groups.operate',
items: [
{
path: '/',
labelKey: 'nav.dashboard',
metaKey: 'nav_meta.dashboard',
icon: sidebarIcons.dashboard,
},
],
},
{
id: 'gateway',
labelKey: 'nav_groups.gateway',
items: [
{
path: '/ai-providers',
labelKey: 'nav.ai_providers',
metaKey: 'nav_meta.ai_providers',
icon: sidebarIcons.aiProviders,
},
{
path: '/auth-files',
labelKey: 'nav.auth_files',
metaKey: 'nav_meta.auth_files',
icon: sidebarIcons.authFiles,
},
{
path: '/oauth',
labelKey: 'nav.oauth',
metaKey: 'nav_meta.oauth',
icon: sidebarIcons.oauth,
},
],
},
{
id: 'observe',
labelKey: 'nav_groups.observe',
items: [
{
path: '/quota',
labelKey: 'nav.quota_management',
metaKey: 'nav_meta.quota_management',
icon: sidebarIcons.quota,
},
...(config?.loggingToFile
? [
{
path: '/logs',
labelKey: 'nav.logs',
metaKey: 'nav_meta.logs',
icon: sidebarIcons.logs,
},
]
: []),
],
},
{
id: 'control',
labelKey: 'nav_groups.control',
items: [
{
path: '/config',
labelKey: 'nav.config_management',
metaKey: 'nav_meta.config_management',
icon: sidebarIcons.config,
},
{
path: '/system',
labelKey: 'nav.system_info',
metaKey: 'nav_meta.system_info',
icon: sidebarIcons.system,
},
],
},
];
const navItems = navGroups.flatMap((group) => group.items);
const navOrder = navItems.map((item) => item.path);
const getRouteOrder = (pathname: string) => {
const trimmedPath =
@@ -652,17 +719,32 @@ export function MainLayout() {
</div>
<div className="nav-section">
{navItems.map((item) => (
<NavLink
key={item.path}
to={item.path}
className={({ isActive }) => `nav-item ${isActive ? 'active' : ''}`}
onClick={() => setSidebarOpen(false)}
title={showSidebarLabels ? undefined : item.label}
>
<span className="nav-icon">{item.icon}</span>
{showSidebarLabels && <span className="nav-label">{item.label}</span>}
</NavLink>
{navGroups.map((group, idx) => (
<div className="nav-group" key={group.id}>
{showSidebarLabels
? <div className="nav-group-label">{t(group.labelKey)}</div>
: idx > 0 && <div className="nav-group-divider" aria-hidden="true" />}
{group.items.map((item) => {
const itemLabel = t(item.labelKey);
return (
<NavLink
key={item.path}
to={item.path}
className={({ isActive }) => `nav-item ${isActive ? 'active' : ''}`}
onClick={() => setSidebarOpen(false)}
title={showSidebarLabels ? undefined : itemLabel}
>
<span className="nav-icon">{item.icon}</span>
{showSidebarLabels && (
<span className="nav-text">
<span className="nav-label">{itemLabel}</span>
<span className="nav-meta">{t(item.metaKey)}</span>
</span>
)}
</NavLink>
);
})}
</div>
))}
</div>
</aside>
+16
View File
@@ -120,6 +120,22 @@
"logs": "Logs Viewer",
"system_info": "Management Center Info"
},
"nav_groups": {
"operate": "Operate",
"gateway": "Gateway",
"observe": "Observe",
"control": "Control"
},
"nav_meta": {
"dashboard": "Operations overview",
"ai_providers": "Upstream model routing",
"auth_files": "Auth files & credentials",
"oauth": "OAuth authorization",
"quota_management": "API keys & limits",
"logs": "Request tracing",
"config_management": "Gateway configuration",
"system_info": "Runtime & diagnostics"
},
"dashboard": {
"title": "Dashboard",
"subtitle": "Welcome to CLI Proxy API Management Center",
+16
View File
@@ -120,6 +120,22 @@
"logs": "Просмотр логов",
"system_info": "Информация системы"
},
"nav_groups": {
"operate": "Работа",
"gateway": "Шлюз",
"observe": "Наблюдение",
"control": "Управление"
},
"nav_meta": {
"dashboard": "Обзор работы",
"ai_providers": "Маршрутизация моделей",
"auth_files": "Файлы и учётные данные",
"oauth": "Авторизация OAuth",
"quota_management": "API ключи и лимиты",
"logs": "Трассировка запросов",
"config_management": "Базовая конфигурация шлюза",
"system_info": "Состояние и диагностика"
},
"dashboard": {
"title": "Панель управления",
"subtitle": "Добро пожаловать в Центр управления CLI Proxy API",
+16
View File
@@ -120,6 +120,22 @@
"logs": "日志查看",
"system_info": "中心信息"
},
"nav_groups": {
"operate": "运行",
"gateway": "网关",
"observe": "观测",
"control": "控制"
},
"nav_meta": {
"dashboard": "运行总览",
"ai_providers": "上游模型路由",
"auth_files": "Auth 文件与凭证",
"oauth": "OAuth 授权登录",
"quota_management": "API Key 与限额",
"logs": "请求追踪与排查",
"config_management": "网关基础配置",
"system_info": "运行与诊断信息"
},
"dashboard": {
"title": "仪表盘",
"subtitle": "欢迎使用 CLI Proxy API 管理中心",
+16
View File
@@ -120,6 +120,22 @@
"logs": "記錄檢視",
"system_info": "中心資訊"
},
"nav_groups": {
"operate": "運行",
"gateway": "閘道",
"observe": "觀測",
"control": "控制"
},
"nav_meta": {
"dashboard": "運行總覽",
"ai_providers": "上游模型路由",
"auth_files": "Auth 檔案與憑證",
"oauth": "OAuth 授權登入",
"quota_management": "API Key 與限額",
"logs": "請求追蹤與排查",
"config_management": "閘道基礎設定",
"system_info": "運行與診斷資訊"
},
"dashboard": {
"title": "儀表板",
"subtitle": "歡迎使用 CLI Proxy API 管理中心",
+60 -3
View File
@@ -3,7 +3,7 @@
:root {
--header-height: 80px;
--shell-gutter: 24px;
--sidebar-panel-width: 240px;
--sidebar-panel-width: 256px;
--sidebar-collapsed-width: 68px;
--sidebar-active-width: var(--sidebar-panel-width);
--sidebar-gap: 32px;
@@ -428,6 +428,14 @@
justify-content: center;
padding: 10px;
}
.nav-group-label {
display: none;
}
.nav-group-divider {
display: block;
}
}
.sidebar-brand {
@@ -462,12 +470,36 @@
.nav-section {
display: flex;
flex-direction: column;
gap: $spacing-sm;
gap: 14px;
flex: 1;
}
.nav-group {
display: flex;
flex-direction: column;
gap: $spacing-xs;
}
.nav-group-label {
padding: 0 12px 4px;
font-size: 10.5px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: color-mix(in srgb, var(--text-primary) 42%, transparent);
user-select: none;
}
.nav-group-divider {
display: none;
height: 1px;
width: 24px;
margin: 4px auto;
background: color-mix(in srgb, var(--border-color) 70%, transparent);
}
.nav-item {
padding: 10px 12px;
padding: 9px 12px;
border-radius: 11px;
border: 1px solid transparent;
color: var(--text-primary);
@@ -509,6 +541,13 @@
}
}
.nav-text {
display: flex;
flex-direction: column;
min-width: 0;
line-height: 1.2;
}
.nav-label {
min-width: 0;
overflow: hidden;
@@ -516,6 +555,16 @@
white-space: nowrap;
}
.nav-meta {
margin-top: 2px;
font-size: 11.5px;
font-weight: 500;
color: color-mix(in srgb, var(--text-primary) 50%, transparent);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&:hover {
background: color-mix(in srgb, var(--text-primary) 7%, transparent);
border-color: color-mix(in srgb, var(--border-color) 58%, transparent);
@@ -527,6 +576,10 @@
color-mix(in srgb, var(--bg-primary) 88%, transparent);
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--border-hover) 86%, transparent);
}
.nav-meta {
color: color-mix(in srgb, var(--text-primary) 62%, transparent);
}
}
&.active {
@@ -542,6 +595,10 @@
color-mix(in srgb, var(--primary-color) 14%, var(--bg-primary));
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--primary-color) 24%, transparent);
}
.nav-meta {
color: color-mix(in srgb, var(--text-primary) 68%, transparent);
}
}
}
}