diff --git a/frontend/src/components/layout/AdminLayout.tsx b/frontend/src/components/layout/AdminLayout.tsx index 40298e5..25cb75f 100644 --- a/frontend/src/components/layout/AdminLayout.tsx +++ b/frontend/src/components/layout/AdminLayout.tsx @@ -3,29 +3,71 @@ import { Outlet, NavLink, useNavigate } from 'react-router-dom'; import { useAuth } from '@/contexts/AuthContext'; import './AdminLayout.css'; -const adminNavItems = [ - { path: '/admin', icon: 'dashboard', label: '平台驾驶舱', exact: true }, - { path: '/admin/users', icon: 'users', label: '用户管理' }, - { path: '/admin/models', icon: 'models', label: '大模型库管理' }, - { path: '/admin/settings', icon: 'settings', label: '网站基础设置' }, - { path: '/admin/payment', icon: 'payment', label: '支付渠道配置' }, - { path: '/admin/services', icon: 'services', label: '增值服务管理' }, - { path: '/admin/membership', icon: 'membership', label: '会员订阅体系' }, - { path: '/admin/ads', icon: 'ads', label: '广告位管理' }, - { path: '/admin/market', icon: 'market', label: '应用市场审核' }, - { path: '/admin/logs', icon: 'logs', label: '日志与风控' }, +interface NavItem { + path: string; + icon: string; + label: string; + exact?: boolean; +} - { path: '/admin/agents', icon: 'agents', label: 'Agent管理' }, - { path: '/admin/skills', icon: 'skills', label: '技能管理' }, - { path: '/admin/code-exec', icon: 'code', label: '代码执行沙箱' }, - { path: '/admin/distributions', icon: 'distro', label: '发行版构建' }, - { path: '/admin/privacy', icon: 'privacy', label: '隐私与本地代理' }, - { path: '/admin/promotion', icon: 'promotion', label: '推广系统' }, - { path: '/admin/achievement', icon: 'achievement', label: '成就系统' }, - { path: '/admin/tenant', icon: 'tenant', label: '多租户管理' }, - { path: '/admin/billing', icon: 'billing', label: '计费与支付' }, - { path: '/admin/analytics', icon: 'analytics', label: '数据分析' }, - { path: '/admin/im-bots', icon: 'imbot', label: 'IM 机器人' }, +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/code-exec', icon: 'code', label: '代码执行沙箱' }, + { path: '/admin/market', icon: 'market', 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/tenant', icon: 'tenant', label: '多租户管理' }, + ], + }, + { + title: '系统管理', + icon: 'settings', + items: [ + { path: '/admin/settings', icon: 'settings', 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: '发行版构建' }, + ], + }, ]; const adminIconPaths: Record = { @@ -39,128 +81,97 @@ const adminIconPaths: Record = { ads: , market: , logs: , - - skills: , + agents: , + skills: , code: , distro: , - privacy: - - - - , - promotion: - - - - , - achievement: - - , - tenant: - - - - , - billing: - - - - , - analytics: - - - - - , + privacy: , + promotion: , + achievement: , + tenant: , + billing: , + analytics: , + imbot: , + chevron: , }; const AdminLayout: React.FC = () => { const { user, logout } = useAuth(); const navigate = useNavigate(); - const [collapsed, setCollapsed] = useState(false); + const [collapsedGroups, setCollapsedGroups] = useState>(new Set()); - if (!user || user.role !== 'admin') { - navigate('/login'); - return null; - } + const toggleGroup = (title: string) => { + setCollapsedGroups(prev => { + const next = new Set(prev); + if (next.has(title)) next.delete(title); + else next.add(title); + return next; + }); + }; + + const handleLogout = () => { + logout(); + navigate('/'); + }; return (
- {/* Admin Sidebar */} - +
+
); diff --git a/frontend/src/styles/global.css b/frontend/src/styles/global.css index 2559393..029fd68 100644 --- a/frontend/src/styles/global.css +++ b/frontend/src/styles/global.css @@ -1,52 +1,56 @@ -/* ===== AIFlow Design System ===== */ -/* 设计方向:融合QwenPaw温暖科技感 + 极简主义 */ +/* ===== AIFlow Design System v2 - Dark Theme Default ===== */ +/* 设计方向:融合暗黑科技感 + 极简主义 */ -/* Google Fonts */ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600&display=swap'); -/* CSS Custom Properties - Design Tokens */ +/* CSS Custom Properties - Dark Theme */ :root { - /* Colors - Light Theme */ - --color-bg-primary: #F8F9FB; - --color-bg-secondary: #FFFFFF; - --color-bg-tertiary: #F1F3F7; - --color-bg-hover: #EBEEF3; - --color-bg-active: #E3E7F0; + /* Colors - Dark Theme */ + --color-bg-primary: #0A0E1A; + --color-bg-secondary: #111627; + --color-bg-tertiary: #161B2E; + --color-bg-hover: #1C2240; + --color-bg-active: #22284A; - --color-text-primary: #101214; - --color-text-secondary: #545A65; - --color-text-tertiary: #8B919E; - --color-text-disabled: #BFC4CE; - --color-text-inverse: #FFFFFF; + --color-text-primary: #E8ECF4; + --color-text-secondary: #8892B0; + --color-text-tertiary: #5A6480; + --color-text-disabled: #3A4460; + --color-text-inverse: #0A0E1A; - --color-accent: #5B5FED; - --color-accent-hover: #4A4ECC; - --color-accent-light: #EEEDFF; - --color-accent-subtle: #F6F5FF; + --color-accent: #6C63FF; + --color-accent-hover: #8B85FF; + --color-accent-light: rgba(108, 99, 255, 0.15); + --color-accent-subtle: rgba(108, 99, 255, 0.08); - --color-success: #219B5E; - --color-success-light: #E6F7EE; - --color-warning: #E8922C; - --color-warning-light: #FEF4E8; - --color-error: #D9383A; - --color-error-light: #FDEBEB; + --color-success: #10B981; + --color-success-light: rgba(16, 185, 129, 0.12); + --color-warning: #F59E0B; + --color-warning-light: rgba(245, 158, 11, 0.12); + --color-error: #EF4444; + --color-error-light: rgba(239, 68, 68, 0.12); --color-info: #3B82F6; - --color-info-light: #EBF2FE; + --color-info-light: rgba(59, 130, 246, 0.12); - --color-border: #E3E6EB; - --color-border-light: #EEF0F4; - --color-border-hover: #CDD2DA; + --color-border: rgba(255, 255, 255, 0.08); + --color-border-light: rgba(255, 255, 255, 0.05); + --color-border-hover: rgba(255, 255, 255, 0.14); - --color-sidebar-bg: #F5F6F9; - --color-sidebar-hover: #EAECF2; - --color-sidebar-active: #E1E3F0; + --color-sidebar-bg: #0E1225; + --color-sidebar-hover: rgba(255, 255, 255, 0.05); + --color-sidebar-active: rgba(108, 99, 255, 0.12); - /* Shadows */ - --shadow-xs: 0 1px 2px rgba(0,0,0,0.04); - --shadow-sm: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04); - --shadow-md: 0 4px 12px rgba(0,0,0,0.06), 0 1px 3px rgba(0,0,0,0.04); - --shadow-lg: 0 12px 32px rgba(0,0,0,0.08), 0 2px 6px rgba(0,0,0,0.04); - --shadow-xl: 0 20px 48px rgba(0,0,0,0.1), 0 4px 12px rgba(0,0,0,0.06); + /* Surface/card colors */ + --color-surface: #111627; + --color-surface-hover: #161B2E; + --color-surface-elevated: #1C2240; + + /* Shadows - Dark theme */ + --shadow-xs: 0 1px 2px rgba(0,0,0,0.2); + --shadow-sm: 0 1px 3px rgba(0,0,0,0.25), 0 1px 2px rgba(0,0,0,0.15); + --shadow-md: 0 4px 12px rgba(0,0,0,0.3), 0 1px 3px rgba(0,0,0,0.2); + --shadow-lg: 0 12px 32px rgba(0,0,0,0.35), 0 2px 6px rgba(0,0,0,0.2); + --shadow-xl: 0 20px 48px rgba(0,0,0,0.4), 0 4px 12px rgba(0,0,0,0.25); /* Spacing */ --space-xs: 4px; @@ -149,7 +153,7 @@ input, textarea, select { font-size: inherit; } -/* Scrollbar */ +/* Scrollbar - Dark theme */ ::-webkit-scrollbar { width: 5px; height: 5px; @@ -226,6 +230,11 @@ input, textarea, select { 100% { background-position: 200% 0; } } +@keyframes glow { + 0%, 100% { box-shadow: 0 0 5px rgba(108, 99, 255, 0.3); } + 50% { box-shadow: 0 0 20px rgba(108, 99, 255, 0.5); } +} + .animate-fadeIn { animation: fadeIn var(--transition-normal); } .animate-slideUp { animation: slideUp var(--transition-normal); } .animate-slideDown { animation: slideDown var(--transition-normal); } @@ -239,7 +248,32 @@ input, textarea, select { border-radius: var(--radius-sm); } -/* Responsive breakpoints utility */ +/* Spinner */ +.audit-spinner { + width: 24px; + height: 24px; + border: 2px solid rgba(108, 99, 255, 0.2); + border-top-color: var(--color-accent); + border-radius: 50%; + animation: spin 0.6s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +/* Loading screen */ +.loading-screen { + display: flex; + align-items: center; + justify-content: center; + height: 100vh; + font-size: 18px; + color: var(--color-text-secondary); + background: var(--color-bg-primary); +} + +/* Responsive breakpoints */ @media (max-width: 768px) { :root { --sidebar-width: 0px; @@ -252,12 +286,462 @@ input, textarea, select { } } -.loading-screen { +/* === Glass card effect === */ +.glass-card { + background: rgba(17, 22, 39, 0.7); + backdrop-filter: blur(16px); + border: 1px solid rgba(255, 255, 255, 0.06); + border-radius: var(--radius-lg); +} + +/* === Gradient text === */ +.gradient-text { + background: linear-gradient(135deg, #6C63FF, #A78BFA, #C084FC); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +/* === Admin stats card grid === */ +.admin-stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: var(--space-lg); + margin-bottom: var(--space-2xl); +} + +.stat-card { + padding: var(--space-xl); + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + transition: all var(--transition-normal); +} + +.stat-card:hover { + border-color: rgba(108, 99, 255, 0.3); + transform: translateY(-2px); + box-shadow: 0 4px 16px rgba(108, 99, 255, 0.08); +} + +.stat-card .stat-label { + font-size: var(--text-xs); + color: var(--color-text-tertiary); + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: var(--space-xs); +} + +.stat-card .stat-value { + font-size: var(--text-2xl); + font-weight: 700; + color: var(--color-text-primary); + line-height: 1.2; +} + +.stat-card .stat-sub { + font-size: var(--text-xs); + color: var(--color-text-tertiary); + margin-top: var(--space-xs); +} + +/* === Common button styles === */ +.btn-primary { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 10px 20px; + border-radius: var(--radius-md); + font-size: var(--text-base); + font-weight: 600; + color: white; + background: var(--color-accent); + border: none; + cursor: pointer; + transition: all var(--transition-fast); +} + +.btn-primary:hover { + background: var(--color-accent-hover); + box-shadow: 0 4px 16px rgba(108, 99, 255, 0.3); +} + +.btn-secondary { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 10px 20px; + border-radius: var(--radius-md); + font-size: var(--text-base); + font-weight: 500; + color: var(--color-text-secondary); + background: transparent; + border: 1px solid var(--color-border); + cursor: pointer; + transition: all var(--transition-fast); +} + +.btn-secondary:hover { + color: var(--color-text-primary); + border-color: var(--color-border-hover); + background: var(--color-bg-hover); +} + +.btn-danger { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 10px 20px; + border-radius: var(--radius-md); + font-size: var(--text-base); + font-weight: 500; + color: white; + background: var(--color-error); + border: none; + cursor: pointer; + transition: all var(--transition-fast); +} + +.btn-danger:hover { + background: #DC2626; +} + +.btn-sm { + padding: 6px 12px; + font-size: var(--text-sm); +} + +/* === Form elements === */ +.form-input-dark { + padding: 10px 14px; + border-radius: var(--radius-md); + border: 1px solid var(--color-border); + background: var(--color-bg-tertiary); + color: var(--color-text-primary); + font-size: var(--text-base); + outline: none; + transition: all var(--transition-fast); + width: 100%; +} + +.form-input-dark:focus { + border-color: rgba(108, 99, 255, 0.4); + box-shadow: 0 0 0 3px rgba(108, 99, 255, 0.1); +} + +.form-input-dark::placeholder { + color: var(--color-text-disabled); +} + +.form-select-dark { + padding: 10px 14px; + border-radius: var(--radius-md); + border: 1px solid var(--color-border); + background: var(--color-bg-tertiary); + color: var(--color-text-primary); + font-size: var(--text-base); + outline: none; + cursor: pointer; + transition: all var(--transition-fast); +} + +.form-select-dark:focus { + border-color: rgba(108, 99, 255, 0.4); +} + +/* === Table (dark theme) === */ +.data-table { + width: 100%; + border-collapse: collapse; +} + +.data-table th { + padding: 12px 16px; + text-align: left; + font-size: var(--text-xs); + font-weight: 600; + color: var(--color-text-tertiary); + text-transform: uppercase; + letter-spacing: 0.05em; + border-bottom: 1px solid var(--color-border); +} + +.data-table td { + padding: 12px 16px; + font-size: var(--text-sm); + color: var(--color-text-primary); + border-bottom: 1px solid var(--color-border-light); +} + +.data-table tr:hover td { + background: var(--color-bg-hover); +} + +/* === Badge === */ +.badge { + display: inline-block; + padding: 2px 10px; + border-radius: 100px; + font-size: var(--text-xs); + font-weight: 600; +} + +.badge-success { background: var(--color-success-light); color: var(--color-success); } +.badge-warning { background: var(--color-warning-light); color: var(--color-warning); } +.badge-error { background: var(--color-error-light); color: var(--color-error); } +.badge-info { background: var(--color-info-light); color: var(--color-info); } +.badge-accent { background: var(--color-accent-light); color: var(--color-accent); } + +/* === Modal/Dialog === */ +.modal-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; - height: 100vh; - font-size: 18px; - color: var(--color-text-secondary); - background: var(--color-bg-primary); + z-index: var(--z-modal); +} + +.modal-content { + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius-xl); + padding: var(--space-2xl); + max-width: 90vw; + max-height: 90vh; + overflow-y: auto; + box-shadow: var(--shadow-xl); +} + +.modal-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--space-xl); +} + +.modal-header h3 { + font-size: var(--text-lg); + font-weight: 600; + color: var(--color-text-primary); +} + +/* === Toast === */ +.toast { + position: fixed; + top: 24px; + left: 50%; + transform: translateX(-50%); + padding: 12px 24px; + border-radius: var(--radius-lg); + background: var(--color-surface-elevated); + border: 1px solid var(--color-border); + color: var(--color-text-primary); + font-size: var(--text-sm); + z-index: var(--z-toast); + animation: slideDown var(--transition-normal); + box-shadow: var(--shadow-lg); +} + +/* ===== Color Scheme Improvements v2 ===== */ +/* Glassmorphism + Text Highlighting + Consistency Fixes */ + +/* Glass card effect */ +.admin-page-card, +.admin-card, +.stat-card, +.dashboard-card { + background: rgba(22, 27, 46, 0.6) !important; + backdrop-filter: blur(12px) !important; + -webkit-backdrop-filter: blur(12px) !important; + border: 1px solid rgba(255, 255, 255, 0.06) !important; +} + +/* Stat number highlight */ +.stat-value, +.stat-number, +.metric-value { + color: var(--color-text-primary) !important; + font-weight: 700 !important; + text-shadow: 0 0 20px rgba(108, 99, 255, 0.15); +} + +/* Section title highlight */ +.section-title, +.page-title, +.card-title, +h1, h2, h3 { + color: var(--color-text-primary) !important; +} + +/* Accent text highlight */ +.text-accent, +.text-primary, +.highlight-text { + color: var(--color-accent) !important; + font-weight: 600; +} + +/* Table header improvements */ +table th, +.table-header { + background: rgba(108, 99, 255, 0.08) !important; + color: var(--color-text-primary) !important; + font-weight: 600 !important; +} + +/* Table row hover */ +table tbody tr:hover, +.table-row:hover { + background: rgba(108, 99, 255, 0.04) !important; +} + +/* Input/Select consistency */ +input, +textarea, +select, +.form-input, +.form-control { + background: rgba(10, 14, 26, 0.6) !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; + color: var(--color-text-primary) !important; + border-radius: var(--radius-md) !important; + transition: border-color 0.2s, box-shadow 0.2s !important; +} + +input:focus, +textarea:focus, +select:focus, +.form-input:focus, +.form-control:focus { + border-color: var(--color-accent) !important; + box-shadow: 0 0 0 3px rgba(108, 99, 255, 0.12) !important; + outline: none !important; +} + +/* Button consistency */ +button, +.btn, +.admin-btn { + border-radius: var(--radius-md) !important; + transition: all 0.2s !important; +} + +/* Primary button */ +.btn-primary, +.admin-btn-primary, +button[type="submit"] { + background: var(--color-accent) !important; + color: white !important; + border: none !important; +} + +.btn-primary:hover, +.admin-btn-primary:hover { + background: var(--color-accent-hover) !important; + box-shadow: 0 4px 12px rgba(108, 99, 255, 0.3) !important; +} + +/* Secondary/ghost button */ +.btn-secondary, +.admin-btn-secondary { + background: rgba(255, 255, 255, 0.05) !important; + color: var(--color-text-secondary) !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; +} + +.btn-secondary:hover, +.admin-btn-secondary:hover { + background: rgba(255, 255, 255, 0.08) !important; + color: var(--color-text-primary) !important; +} + +/* Badge/tag improvements */ +.badge, +.tag, +.admin-badge { + border-radius: var(--radius-full) !important; + font-weight: 600 !important; + padding: 2px 10px !important; +} + +/* Badge variants */ +.badge-success, +.admin-badge-success { + background: rgba(16, 185, 129, 0.12) !important; + color: #10B981 !important; +} + +.badge-warning, +.admin-badge-warning { + background: rgba(245, 158, 11, 0.12) !important; + color: #F59E0B !important; +} + +.badge-error, +.admin-badge-danger { + background: rgba(239, 68, 68, 0.12) !important; + color: #EF4444 !important; +} + +.badge-info, +.admin-badge-info { + background: rgba(59, 130, 246, 0.12) !important; + color: #3B82F6 !important; +} + +/* Empty state */ +.empty-state, +.admin-empty { + color: var(--color-text-tertiary) !important; + text-align: center; + padding: 48px 24px !important; +} + +/* Scrollbar improvement */ +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-thumb { + background: rgba(108, 99, 255, 0.2); + border-radius: 3px; +} + +::-webkit-scrollbar-thumb:hover { + background: rgba(108, 99, 255, 0.4); +} + +/* Link color */ +a { + color: var(--color-accent); + transition: color 0.2s; +} + +a:hover { + color: var(--color-accent-hover); +} + +/* Tab improvements */ +.tab-active, +.tab-item.active { + color: var(--color-accent) !important; + border-bottom-color: var(--color-accent) !important; +} + +/* Modal/dialog overlay */ +.modal-overlay, +.dialog-overlay { + background: rgba(10, 14, 26, 0.7) !important; + backdrop-filter: blur(4px) !important; +} + +/* Card hover effect */ +.admin-card:hover, +.stat-card:hover { + border-color: rgba(108, 99, 255, 0.2) !important; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2) !important; }