diff --git a/frontend/src/components/layout/AdminLayout.css b/frontend/src/components/layout/AdminLayout.css index 41017b8..b11d47c 100644 --- a/frontend/src/components/layout/AdminLayout.css +++ b/frontend/src/components/layout/AdminLayout.css @@ -1,20 +1,21 @@ -/* ===== Admin Layout ===== */ +/* ===== Admin Layout (DESIGN.md: 浅色主题 + 品牌色 #5B5FED) ===== */ .admin-layout { display: flex; height: 100vh; overflow: hidden; - background: #F1F3F7; + background: var(--color-bg-tertiary); } -/* Admin Sidebar */ +/* Admin Sidebar —— 浅色侧栏 */ .admin-sidebar { width: var(--admin-sidebar-width); min-width: var(--admin-sidebar-width); - background: #1A1A2E; + background: var(--color-sidebar-bg); display: flex; flex-direction: column; transition: width var(--transition-slow), min-width var(--transition-slow); overflow: hidden; + border-right: 1px solid var(--color-border); z-index: var(--z-sticky); } @@ -25,76 +26,204 @@ .admin-sidebar-header { display: flex; - align-items: center; - justify-content: space-between; - padding: var(--space-lg); + align-items: baseline; + gap: var(--space-sm); + padding: 0 var(--space-lg); height: var(--header-height); - border-bottom: 1px solid rgba(255,255,255,0.06); + border-bottom: 1px solid var(--color-border); + flex-shrink: 0; } -.admin-brand { - color: #E0E2F0 !important; - font-size: var(--text-base) !important; +.admin-logo { + margin: 0; + font-size: var(--text-lg); + font-weight: 800; + color: var(--color-text-primary); + letter-spacing: 0.02em; +} +.admin-subtitle { + font-size: var(--text-xs); + font-weight: 500; + color: var(--color-text-tertiary); } -.admin-sidebar .collapse-btn { - color: rgba(255,255,255,0.4); +.admin-frontend-link { + display: flex; + align-items: center; + gap: var(--space-sm); + margin: var(--space-sm) var(--space-md); + padding: var(--space-sm) var(--space-md); + border-radius: var(--radius-md); + border: 1px solid var(--color-border); + background: var(--color-bg-secondary); + color: var(--color-text-secondary); + font-size: var(--text-xs); + font-weight: 500; + cursor: pointer; + text-decoration: none; + transition: all var(--transition-fast); } -.admin-sidebar .collapse-btn:hover { - background: rgba(255,255,255,0.08); - color: rgba(255,255,255,0.8); +.admin-frontend-link:hover { + background: var(--color-accent-light); + border-color: var(--color-accent-light); + color: var(--color-accent); } -/* Admin Nav */ -.admin-nav { +/* Admin Nav (侧栏分组 + 菜单项) */ +.sidebar-nav { flex: 1; - padding: var(--space-sm); + padding: var(--space-sm) var(--space-xs); overflow-y: auto; + overflow-x: hidden; display: flex; flex-direction: column; gap: 2px; } -.admin-nav-item { +.nav-group { + display: flex; + flex-direction: column; +} + +.nav-group-header { + display: flex; + align-items: center; + gap: var(--space-sm); + padding: var(--space-sm) var(--space-md); + border-radius: var(--radius-md); + cursor: pointer; + user-select: none; + color: var(--color-text-secondary); + transition: background var(--transition-fast), color var(--transition-fast); +} +.nav-group-header:hover { + background: var(--color-sidebar-hover); + color: var(--color-text-primary); +} +.nav-group-active > .nav-group-header { + color: var(--color-text-primary); + font-weight: 600; +} + +.nav-group-chevron-wrap { + display: inline-flex; + width: 14px; + height: 14px; + flex-shrink: 0; +} +.nav-group-chevron { + display: inline-flex; + color: var(--color-text-tertiary); + transition: transform var(--transition-fast); +} +.nav-group-chevron.chevron-expanded { + transform: rotate(180deg); +} +.nav-group-title { + font-size: var(--text-sm); + font-weight: 600; + letter-spacing: 0.01em; +} + +.nav-group-items { + display: flex; + flex-direction: column; + gap: 2px; + padding: 2px 0 6px; + margin-left: var(--space-lg); + border-left: 1px solid var(--color-border-light); +} + +.nav-item { display: flex; align-items: center; gap: var(--space-md); - padding: var(--space-md) var(--space-md); + padding: 9px var(--space-md); + margin-left: var(--space-sm); border-radius: var(--radius-md); - color: rgba(255,255,255,0.55); + color: var(--color-text-secondary); text-decoration: none; font-size: var(--text-sm); font-weight: 500; transition: all var(--transition-fast); white-space: nowrap; - width: 100%; - text-align: left; +} +.nav-item:hover { + background: var(--color-sidebar-hover); + color: var(--color-text-primary); +} +.nav-item.active, +.nav-item-active { + background: var(--color-accent-light); + color: var(--color-accent); +} +.nav-item.active .nav-item-icon, +.nav-item-active .nav-item-icon { + color: var(--color-accent); } -.admin-nav-item:hover { - background: rgba(255,255,255,0.06); - color: rgba(255,255,255,0.85); +.nav-item-icon { + display: inline-flex; + flex-shrink: 0; + color: var(--color-text-tertiary); } - -.admin-nav-item.active { - background: rgba(107, 99, 255, 0.2); - color: #8B8DFF; +.nav-item-label { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; } +/* Sidebar footer */ .admin-sidebar-footer { padding: var(--space-sm); - border-top: 1px solid rgba(255,255,255,0.06); + border-top: 1px solid var(--color-border); display: flex; flex-direction: column; gap: 2px; + flex-shrink: 0; } - -.logout-btn { - color: rgba(255,255,255,0.35) !important; +.admin-user-info { + display: flex; + align-items: center; + gap: var(--space-sm); + padding: var(--space-xs) var(--space-sm); } -.logout-btn:hover { - color: #FF6B6B !important; - background: rgba(255,107,107,0.1) !important; +.admin-user-avatar { + width: 34px; + height: 34px; + border-radius: var(--radius-full); + background: var(--color-accent); + color: #FFFFFF; + display: inline-flex; + align-items: center; + justify-content: center; + font-weight: 700; + font-size: var(--text-sm); + flex-shrink: 0; +} +.admin-user-detail { + display: flex; + flex-direction: column; + min-width: 0; +} +.admin-user-name { + font-size: var(--text-sm); + font-weight: 600; + color: var(--color-text-primary); +} +.admin-logout-btn { + background: none; + border: none; + padding: 0; + margin-top: 2px; + font-size: var(--text-xs); + color: var(--color-text-tertiary); + cursor: pointer; + text-align: left; + font-family: inherit; +} +.admin-logout-btn:hover { + color: var(--color-error); } /* Admin Main */ @@ -164,10 +293,6 @@ border-color: var(--color-accent); } -.admin-avatar { - background: #1A1A2E !important; -} - /* Admin Content Area */ .admin-content { flex: 1; @@ -378,10 +503,10 @@ .admin-version-info { padding: var(--space-sm) var(--space-md); font-size: var(--text-xs); - color: rgba(255,255,255,0.25); + color: var(--color-text-disabled); text-align: center; white-space: nowrap; - border-top: 1px solid rgba(255,255,255,0.04); + border-top: 1px solid var(--color-border-light); } @media (max-width: 768px) { diff --git a/frontend/src/components/layout/AdminLayout.tsx b/frontend/src/components/layout/AdminLayout.tsx index be7e91f..f3f85eb 100644 --- a/frontend/src/components/layout/AdminLayout.tsx +++ b/frontend/src/components/layout/AdminLayout.tsx @@ -1,262 +1,499 @@ import React, { useState, useEffect } from 'react'; + import { Outlet, NavLink, useNavigate, useLocation } from 'react-router-dom'; + import { useAuth } from '@/contexts/AuthContext'; + import './AdminLayout.css'; + + interface NavItem { + path: string; + icon: string; + label: string; + exact?: boolean; + } + + interface NavGroup { + title: string; + icon: string; + items: NavItem[]; + } + + const adminNavGroups: NavGroup[] = [ + { + title: '驾驶舱', + icon: 'dashboard', + items: [ + { path: '/admin', icon: 'dashboard', label: '平台驾驶舱', exact: true }, + { path: '/admin/analytics', icon: 'analytics', label: '数据分析' }, + ], + }, + { + title: 'AI 能力', + icon: 'agents', + items: [ + { path: '/admin/agents', icon: 'agents', label: 'Agent 管理' }, + { path: '/admin/skills', icon: 'skills', label: '技能管理' }, + { path: '/admin/models', icon: 'models', label: '大模型库' }, + { path: '/admin/knowledge', icon: 'knowledge', label: '知识库管理' }, + { path: '/admin/workflow', icon: 'workflow', label: '工作流编排' }, + { path: '/admin/mcp', icon: 'mcp', label: 'MCP 服务' }, + ], + }, + { + title: '应用与市场', + icon: 'market', + items: [ + { path: '/admin/apps', icon: 'apps', label: 'AI 应用管理' }, + { path: '/admin/market', icon: 'market', label: '应用市场审核' }, + { path: '/admin/code-exec', icon: 'code', label: '代码执行沙箱' }, + ], + }, + { + title: '用户运营', + icon: 'users', + items: [ + { path: '/admin/users', icon: 'users', label: '用户管理' }, + { path: '/admin/membership', icon: 'membership', label: '会员订阅' }, + { path: '/admin/promotion', icon: 'promotion', label: '推广系统' }, + { path: '/admin/achievement', icon: 'achievement', label: '成就系统' }, + ], + }, + { + title: '商业变现', + icon: 'billing', + items: [ + { path: '/admin/billing', icon: 'billing', label: '计费与支付' }, + { path: '/admin/payment', icon: 'payment', label: '支付渠道配置' }, + { path: '/admin/services', icon: 'services', label: '增值服务' }, + { path: '/admin/ads', icon: 'ads', label: '广告位管理' }, + { path: '/admin/assets', icon: 'assets', label: '资产管理' }, + { path: '/admin/tenant', icon: 'tenant', label: '多租户管理' }, + ], + }, + { + title: '系统管理', + icon: 'settings', + items: [ + { path: '/admin/settings', icon: 'settings', label: '网站设置' }, + { path: '/admin/sms', icon: 'sms', label: '短信配置' }, + { path: '/admin/logs', icon: 'logs', label: '日志与风控' }, + { path: '/admin/im-bots', icon: 'imbot', label: 'IM 机器人' }, + { path: '/admin/privacy', icon: 'privacy', label: '隐私与本地代理' }, + { path: '/admin/distributions', icon: 'distro', label: '发行版构建' }, + ], + }, + { + title: '开发与集成', + icon: 'developer', + items: [ + { path: '/admin/storage', icon: 'storage', label: '存储配置' }, + { path: '/admin/automation', icon: 'automation', label: '自动化任务' }, + { path: '/admin/developer', icon: 'developer', label: '开发者 API' }, + { path: '/admin/developer-tools', icon: 'devtools', label: '开发者工具' }, + ], + }, + ]; + + const adminIconPaths: Record = { + dashboard: , + users: , + models: , + settings: , + payment: , + services: , + membership: , + ads: , + market: , + logs: , + agents: , + skills: , + code: , + distro: , + privacy: , + promotion: , + achievement: , + tenant: , + billing: , + analytics: , + imbot: , + storage: , + automation: , + developer: , + devtools: , + knowledge: , + workflow: , + mcp: , + apps: , + assets: , + chevron: , + sms: , + }; + + const AdminLayout: React.FC = () => { + const { user, logout } = useAuth(); + const navigate = useNavigate(); + const location = useLocation(); + const navCrumb = (() => { + for (const g of adminNavGroups) { + for (const it of g.items) { + const match = it.exact + ? location.pathname === it.path + : location.pathname === it.path || location.pathname.startsWith(it.path + '/'); + if (match) return { group: g.title, label: it.label }; + } + } + return { group: '', label: '' }; + })(); + const navGroup = navCrumb.group; + const navLabel = navCrumb.label; - // Default: all groups collapsed. Auto-expand the group containing the active route. + + + // 默认展开全部功能板块,确保后台所有模块在 UI 中可见(DESIGN.md: 侧栏分类清晰) const [expandedGroups, setExpandedGroups] = useState>(() => { - const initial = new Set(); - const currentPath = location.pathname; - for (const group of adminNavGroups) { - const isActive = group.items.some(item => - item.exact ? currentPath === item.path : currentPath.startsWith(item.path) - ); - if (isActive) { - initial.add(group.title); - break; - } - } - return initial; + return new Set(adminNavGroups.map((g) => g.title)); }); + + const toggleGroup = (title: string) => { + setExpandedGroups(prev => { + const next = new Set(prev); + if (next.has(title)) next.delete(title); + else next.add(title); + return next; + }); + }; + + const handleLogout = () => { + logout(); + navigate('/'); + }; + + const handleVisitFrontend = () => { + const url = window.location.origin + '/'; + window.open(url, '_blank'); + }; + + return ( +
+ +
+
+ +
+
{user?.username?.[0]?.toUpperCase() || 'A'}
+ {user?.username || 'admin'} +
+
+ +
+
+ ); + }; + + export default AdminLayout; +