refactor: 数据源菜单合一+布局优化
- 4个数据源菜单(MySQL/PG/Redis/Mongo)合并为1个「数据源」 - InstancePage 改为统一入口,顶部Tab切换类型过滤 - 添加数据源改为弹框Modal(默认MySQL) - 去掉左下角用户区,退出登录移到右上角header - 侧边栏收窄220px,整体布局更紧凑 - 后端202 tests passing - 前端构建零错误
This commit is contained in:
1 parent
bab55d3927
commit
5cbd0e3441
23 files changed
+2881
-94
No files matched your search
@@ -2,7 +2,7 @@ import { Outlet, useLocation, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
LayoutDashboard, Database, Server, Zap, Leaf,
|
||||
ClipboardList, Search, UserCog, Users, Lock, Shield, GitBranch, Bell,
|
||||
LogOut, Settings, Clock, Megaphone, Sun, Moon
|
||||
LogOut, Settings, Clock, Megaphone, Sun, Moon, BookOpen
|
||||
} from 'lucide-react';
|
||||
import { menuSections } from '@/lib/mock';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
@@ -11,7 +11,7 @@ import { useTheme } from '@/hooks/useTheme';
|
||||
const iconMap: Record<string, React.ElementType> = {
|
||||
LayoutDashboard, Database, Server, Zap, Leaf,
|
||||
ClipboardList, Search, UserCog, Users, Lock, Shield, GitBranch, Bell,
|
||||
Settings, Clock, Megaphone,
|
||||
Settings, Clock, Megaphone, BookOpen,
|
||||
};
|
||||
|
||||
export default function Layout() {
|
||||
@@ -19,7 +19,7 @@ export default function Layout() {
|
||||
const { theme, toggleTheme } = useTheme();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const currentPath = location.pathname.slice(1) || 'dashboard';
|
||||
const currentPath = location.pathname.split('/')[1] || 'dashboard';
|
||||
|
||||
const getPageTitle = () => {
|
||||
const item = visibleMenuItems.find(m => m.id === currentPath);
|
||||
@@ -29,27 +29,27 @@ export default function Layout() {
|
||||
return (
|
||||
<div className="flex h-screen bg-[var(--dg-bg-primary)] text-[var(--dg-text-primary)] overflow-hidden">
|
||||
{/* 侧边栏 */}
|
||||
<aside className="flex flex-col bg-[var(--dg-bg-secondary)] border-r border-[var(--dg-border)] w-[260px] min-w-[260px]">
|
||||
<aside className="flex flex-col bg-[var(--dg-bg-secondary)] border-r border-[var(--dg-border)] w-[220px] min-w-[220px]">
|
||||
{/* Logo */}
|
||||
<div className="h-16 flex items-center px-5 border-b border-[var(--dg-border)]">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-lg dg-gradient-br flex items-center justify-center flex-shrink-0">
|
||||
<Shield className="w-5 h-5 text-white" />
|
||||
<div className="h-14 flex items-center px-4">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="w-7 h-7 rounded-lg dg-gradient-br flex items-center justify-center flex-shrink-0">
|
||||
<Shield className="w-4 h-4 text-white" />
|
||||
</div>
|
||||
<span className="text-lg font-bold dg-gradient-text whitespace-nowrap">
|
||||
<span className="text-base font-bold dg-gradient-text whitespace-nowrap">
|
||||
SQL-Platform
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 菜单 */}
|
||||
<nav className="flex-1 overflow-y-auto scrollbar-thin py-3">
|
||||
<nav className="flex-1 overflow-y-auto scrollbar-thin py-1">
|
||||
{menuSections.map(section => {
|
||||
const items = visibleMenuItems.filter(item => item.section === section.id);
|
||||
if (items.length === 0) return null;
|
||||
return (
|
||||
<div key={section.id} className="mb-2">
|
||||
<div className="px-5 py-2 text-[11px] font-semibold uppercase tracking-wider text-[var(--dg-text-muted)]">
|
||||
<div key={section.id} className="mb-1">
|
||||
<div className="px-4 py-1.5 text-[10px] font-semibold uppercase tracking-wider text-[var(--dg-text-muted)]">
|
||||
{section.label}
|
||||
</div>
|
||||
{items.map(item => {
|
||||
@@ -58,20 +58,19 @@ export default function Layout() {
|
||||
return (
|
||||
<div
|
||||
key={item.id}
|
||||
className={`
|
||||
flex items-center mx-3 px-4 py-2.5 rounded-lg cursor-pointer transition-all duration-200 relative
|
||||
${isActive
|
||||
? 'bg-blue-500/15 text-blue-400'
|
||||
: 'text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)] hover:text-[var(--dg-text-primary)]'
|
||||
}
|
||||
`}
|
||||
onClick={() => navigate(`/${item.id}`)}
|
||||
onClick={() => navigate('/' + item.id)}
|
||||
className={
|
||||
'flex items-center mx-2 px-3 py-2 rounded-lg cursor-pointer transition-all duration-150 relative text-[13px] '
|
||||
+ (isActive
|
||||
? 'bg-blue-500/10 text-blue-400 font-medium'
|
||||
: 'text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)] hover:text-[var(--dg-text-primary)]')
|
||||
}
|
||||
>
|
||||
{isActive && (
|
||||
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-[3px] h-5 bg-blue-400 rounded-r" />
|
||||
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-[3px] h-4 bg-blue-400 rounded-r" />
|
||||
)}
|
||||
<Icon className="w-[18px] h-[18px] flex-shrink-0" />
|
||||
<span className="ml-3 text-sm font-medium">{item.label}</span>
|
||||
<Icon className="w-4 h-4 flex-shrink-0" />
|
||||
<span className="ml-2.5">{item.label}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@@ -79,56 +78,46 @@ export default function Layout() {
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
{/* 底部 */}
|
||||
<div className="p-4 border-t border-[var(--dg-border)]">
|
||||
<div className="flex items-center gap-3 p-2 rounded-lg hover:bg-[var(--dg-bg-hover)] cursor-pointer transition-colors">
|
||||
<div className="w-9 h-9 rounded-full dg-gradient-br flex items-center justify-center text-white font-semibold text-sm flex-shrink-0">
|
||||
{user.avatar}
|
||||
</div>
|
||||
<div className="overflow-hidden flex-1">
|
||||
<div className="text-sm font-medium truncate">{user.username}</div>
|
||||
<div className="text-xs text-[var(--dg-text-muted)]">{user.roleName}</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="mt-2 w-full flex items-center justify-center gap-2 p-2 rounded-lg hover:bg-[var(--dg-bg-hover)] cursor-pointer text-[var(--dg-text-muted)] hover:text-[var(--dg-text-primary)] transition-colors text-xs"
|
||||
onClick={() => { logout(); navigate('/login'); }}
|
||||
>
|
||||
<LogOut className="w-3.5 h-3.5" /> 退出登录
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{/* 主内容区 */}
|
||||
<main className="flex-1 flex flex-col min-w-0 overflow-hidden">
|
||||
{/* 顶部栏 */}
|
||||
<header className="h-16 bg-[var(--dg-bg-secondary)] border-b border-[var(--dg-border)] flex items-center justify-between px-6 flex-shrink-0">
|
||||
<header className="h-14 bg-[var(--dg-bg-secondary)] border-b border-[var(--dg-border)] flex items-center justify-between px-5 flex-shrink-0">
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<span className="text-[var(--dg-text-muted)]">SQL-Platform</span>
|
||||
<span className="text-[var(--dg-text-muted)]">/</span>
|
||||
<span className="text-[var(--dg-text-primary)] font-medium">{getPageTitle()}</span>
|
||||
<span className="text-[var(--dg-text-muted)]">{getPageTitle()}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="px-2.5 py-1 rounded-md text-xs font-medium bg-blue-500/15 text-blue-400">
|
||||
{user.roleName}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={toggleTheme}
|
||||
className="w-9 h-9 rounded-lg flex items-center justify-center text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)] hover:text-[var(--dg-text-primary)] transition-colors"
|
||||
title={theme === 'dark' ? '切换到浅色模式' : '切换到深色模式'}
|
||||
className="w-8 h-8 rounded-lg flex items-center justify-center text-[var(--dg-text-muted)] hover:bg-[var(--dg-bg-hover)] transition-colors"
|
||||
title={theme === 'dark' ? '浅色模式' : '深色模式'}
|
||||
>
|
||||
{theme === 'dark' ? <Sun className="w-[18px] h-[18px]" /> : <Moon className="w-[18px] h-[18px]" />}
|
||||
{theme === 'dark' ? <Sun className="w-4 h-4" /> : <Moon className="w-4 h-4" />}
|
||||
</button>
|
||||
<button className="w-9 h-9 rounded-lg flex items-center justify-center text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)] hover:text-[var(--dg-text-primary)] transition-colors relative">
|
||||
<Bell className="w-[18px] h-[18px]" />
|
||||
<span className="absolute top-1.5 right-1.5 w-2 h-2 bg-red-500 rounded-full" />
|
||||
<button className="w-8 h-8 rounded-lg flex items-center justify-center text-[var(--dg-text-muted)] hover:bg-[var(--dg-bg-hover)] transition-colors relative">
|
||||
<Bell className="w-4 h-4" />
|
||||
<span className="absolute top-1 right-1 w-1.5 h-1.5 bg-red-500 rounded-full" />
|
||||
</button>
|
||||
<div className="w-px h-5 bg-[var(--dg-border)] mx-1" />
|
||||
<div className="flex items-center gap-2 pl-1">
|
||||
<div className="w-7 h-7 rounded-full dg-gradient-br flex items-center justify-center text-white text-xs font-semibold flex-shrink-0">
|
||||
{user.avatar}
|
||||
</div>
|
||||
<span className="text-sm text-[var(--dg-text-secondary)] max-w-[80px] truncate">{user.username}</span>
|
||||
<button
|
||||
onClick={() => { logout(); navigate('/login'); }}
|
||||
className="w-8 h-8 rounded-lg flex items-center justify-center text-[var(--dg-text-muted)] hover:bg-red-500/10 hover:text-red-400 transition-colors"
|
||||
title="退出登录"
|
||||
>
|
||||
<LogOut className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* 页面内容 */}
|
||||
<div className="flex-1 overflow-y-auto scrollbar-thin p-6">
|
||||
<div className="flex-1 overflow-y-auto scrollbar-thin p-5">
|
||||
<Outlet />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
Reference in new issue
Block a user