feat: 全面UI优化 — 5套主题系统 + 设计体系 + 菜单重组 + 页面优化
1. 主题系统扩展 (5套主题) - ocean-deep(深海蓝) / light-professional(明亮白) / midnight-purple(午夜紫) - pine-green(松林绿) / orange-vitality(活力橙) - useTheme hook 重写支持5主题 + 向后兼容 - AppearanceSettings 5主题预览卡片 2. 设计体系 (index.css) - 按钮系统: dg-btn-primary/secondary/ghost/danger + 尺寸变体 - 徽章系统: dg-badge-* (8色) - 卡片/表格: dg-card-section, dg-table-row, dg-table-header - 状态点: dg-status-dot-* (4色) - 动效关键帧: slideInLeft, scaleIn 3. 菜单重组 (menu.ts) - 5分组: 概览/数据源/工单/权限/系统 - 6个图标更新: Code2/FileEdit/ShieldCheck/FolderLock/ScrollText/Timer - 新增 permission 权限码字段 4. Layout 侧边栏增强 - 左侧渐变指示条 + 发光效果 - hover 微动画 + 图标变色 - 面包屑导航头部 - 主题切换循环5主题 5. 核心页面优化 - TicketsPage: 设计系统类 + 颜色对齐 - QueriesPage: 去Card依赖 + dg-table/dg-btn统一 - Breadcrumb组件 + useBreadcrumb hook
This commit is contained in:
1 parent
4e2df4d3d7
commit
746a000e8a
25 files changed
+7718
-178
No files matched your search
@@ -1,18 +1,19 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
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, BookOpen
|
||||
Code2, FileEdit, Users, ShieldCheck, FolderLock, Shield, GitBranch, Bell,
|
||||
LogOut, Settings, Timer, ScrollText, Megaphone, Sun, Moon, BookOpen,
|
||||
ChevronRight, Monitor
|
||||
} from 'lucide-react';
|
||||
import { menuSections } from '@/constants/menu';
|
||||
import { menuSections, menuItems } from '@/constants/menu';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { useTheme } from '@/hooks/useTheme';
|
||||
import { useTheme, THEME_OPTIONS } 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, BookOpen,
|
||||
Code2, FileEdit, Users, ShieldCheck, FolderLock, Shield, GitBranch, Bell,
|
||||
Settings, Timer, ScrollText, Megaphone, BookOpen,
|
||||
};
|
||||
|
||||
export default function Layout() {
|
||||
@@ -29,10 +30,25 @@ export default function Layout() {
|
||||
.catch(() => {});
|
||||
}, [location.pathname]);
|
||||
|
||||
const getPageTitle = () => {
|
||||
const item = visibleMenuItems.find(m => m.id === currentPath);
|
||||
return item?.label || '仪表盘';
|
||||
};
|
||||
// Build breadcrumb info from current path
|
||||
const breadcrumbInfo = useMemo(() => {
|
||||
const currentItem = menuItems.find(m => m.id === currentPath);
|
||||
if (!currentItem) {
|
||||
return { sectionLabel: '', pageLabel: '仪表盘' };
|
||||
}
|
||||
const section = menuSections.find(s => s.id === currentItem.section);
|
||||
return {
|
||||
sectionLabel: section?.label || '',
|
||||
pageLabel: currentItem.label,
|
||||
};
|
||||
}, [currentPath]);
|
||||
|
||||
const themeOption = useMemo(() => THEME_OPTIONS.find(o => o.id === theme), [theme]);
|
||||
const themeIcon = useMemo(() => {
|
||||
if (!themeOption) return <Monitor className="w-4 h-4" />;
|
||||
if (themeOption.isDark) return <Moon className="w-4 h-4" />;
|
||||
return <Sun className="w-4 h-4" />;
|
||||
}, [themeOption]);
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-[var(--dg-bg-primary)] text-[var(--dg-text-primary)] overflow-hidden">
|
||||
@@ -68,16 +84,33 @@ export default function Layout() {
|
||||
key={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] '
|
||||
'group flex items-center mx-2 px-3 py-2 rounded-lg cursor-pointer '
|
||||
+ 'transition-all duration-200 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)]')
|
||||
? 'bg-[var(--dg-blue)]/10 text-[var(--dg-blue)] font-medium'
|
||||
: 'text-[var(--dg-text-secondary)] '
|
||||
+ 'hover:bg-[var(--dg-bg-hover)] hover:text-[var(--dg-text-primary)] '
|
||||
+ 'hover:ml-0.5')
|
||||
}
|
||||
>
|
||||
{/* Active indicator bar - gradient with glow */}
|
||||
{isActive && (
|
||||
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-[3px] h-4 bg-blue-400 rounded-r" />
|
||||
<div
|
||||
className="absolute left-0 top-1/2 -translate-y-1/2 w-[3px] h-5 rounded-r animate-[slideInLeft_200ms_ease-out]"
|
||||
style={{
|
||||
background: 'linear-gradient(180deg, var(--dg-blue), var(--dg-purple, var(--dg-blue)))',
|
||||
boxShadow: '0 0 8px 1px var(--dg-blue), 0 0 16px 2px color-mix(in srgb, var(--dg-blue) 40%, transparent)',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Icon className="w-4 h-4 flex-shrink-0" />
|
||||
<Icon
|
||||
className={
|
||||
'w-4 h-4 flex-shrink-0 transition-colors duration-200 '
|
||||
+ (isActive
|
||||
? 'text-[var(--dg-blue)]'
|
||||
: 'group-hover:text-[var(--dg-blue)]')
|
||||
}
|
||||
/>
|
||||
<span className="ml-2.5">{item.label}</span>
|
||||
</div>
|
||||
);
|
||||
@@ -92,27 +125,44 @@ export default function Layout() {
|
||||
<main className="flex-1 flex flex-col min-w-0 overflow-hidden">
|
||||
{/* 顶部栏 */}
|
||||
<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)]">{getPageTitle()}</span>
|
||||
{/* Breadcrumb area */}
|
||||
<div className="flex items-center gap-1.5 text-sm">
|
||||
<span className="text-[var(--dg-text-muted)] font-medium">SQL-Platform</span>
|
||||
{breadcrumbInfo.sectionLabel && (
|
||||
<>
|
||||
<ChevronRight className="w-3.5 h-3.5 text-[var(--dg-text-muted)] opacity-50" />
|
||||
<span className="text-[var(--dg-text-muted)] opacity-70">{breadcrumbInfo.sectionLabel}</span>
|
||||
</>
|
||||
)}
|
||||
<ChevronRight className="w-3.5 h-3.5 text-[var(--dg-text-muted)] opacity-50" />
|
||||
<span className="text-[var(--dg-text-primary)] font-medium">{breadcrumbInfo.pageLabel}</span>
|
||||
</div>
|
||||
|
||||
{/* Right actions */}
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Theme toggle with tooltip */}
|
||||
<button
|
||||
onClick={toggleTheme}
|
||||
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' ? '浅色模式' : '深色模式'}
|
||||
className="w-8 h-8 rounded-lg flex items-center justify-center text-[var(--dg-text-muted)] hover:bg-[var(--dg-bg-hover)] hover:text-[var(--dg-text-primary)] transition-all duration-200"
|
||||
title={`当前主题: ${themeOption?.name || theme}\n点击切换下一个主题`}
|
||||
>
|
||||
{theme === 'dark' ? <Sun className="w-4 h-4" /> : <Moon className="w-4 h-4" />}
|
||||
{themeIcon}
|
||||
</button>
|
||||
|
||||
{/* Notification bell */}
|
||||
<button onClick={() => navigate('/tickets')}
|
||||
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">
|
||||
className="w-8 h-8 rounded-lg flex items-center justify-center text-[var(--dg-text-muted)] hover:bg-[var(--dg-bg-hover)] hover:text-[var(--dg-text-primary)] transition-all duration-200 relative">
|
||||
<Bell className="w-4 h-4" />
|
||||
{pendingCount > 0 && (
|
||||
<span className="absolute -top-0.5 -right-0.5 min-w-[16px] h-4 px-1 bg-red-500 rounded-full text-[10px] text-white font-bold flex items-center justify-center">
|
||||
<span className="absolute -top-0.5 -right-0.5 min-w-[16px] h-4 px-1 bg-red-500 rounded-full text-[10px] text-white font-bold flex items-center justify-center animate-pulse">
|
||||
{pendingCount > 99 ? '99+' : pendingCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
|
||||
<div className="w-px h-5 bg-[var(--dg-border)] mx-1" />
|
||||
|
||||
{/* User area */}
|
||||
<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}
|
||||
@@ -120,7 +170,7 @@ export default function Layout() {
|
||||
<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"
|
||||
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 duration-200"
|
||||
title="退出登录"
|
||||
>
|
||||
<LogOut className="w-4 h-4" />
|
||||
@@ -134,6 +184,22 @@ export default function Layout() {
|
||||
<Outlet />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Keyframe animation for active indicator */}
|
||||
<style>{`
|
||||
@keyframes slideInLeft {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-50%) scaleX(0);
|
||||
transform-origin: left;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(-50%) scaleX(1);
|
||||
transform-origin: left;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user