feat: 炫酷的登录页

This commit is contained in:
Blizzard
2026-04-28 16:43:34 +08:00
parent 3cade8e7ef
commit ccb36fa59c
34 changed files with 2390 additions and 253 deletions
+72 -13
View File
@@ -1,9 +1,11 @@
import { NavLink, Outlet, useNavigate, useLocation } from 'react-router-dom'
import { AnimatePresence, motion } from 'framer-motion'
import CommandPalette from '@/components/CommandPalette'
import {
LayoutDashboard, Users, Shield, MessageSquare, FolderTree, Leaf,
LogOut, ChevronDown, Menu, FileText, Settings, Book, Home, Monitor,
Hash, Folder, ChevronRight, Search, Bell, ChevronLeft, Bot, Radio,
Image, Music, ScrollText, Sun, Moon,
Image, Music, ScrollText, Sun, Moon, Trophy, Award, Star, Gift, List,
} from 'lucide-react'
import { useState, useMemo, useEffect } from 'react'
import { useAuthStore } from '@/store/auth'
@@ -12,11 +14,11 @@ import type { SystemMenu } from '@/api/system'
import { Button } from '@/components/ui/button'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { ScrollArea } from '@/components/ui/scroll-area'
import TabBar from '@/components/TabBar'
import {
DropdownMenu, DropdownMenuContent, DropdownMenuItem,
DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { Input } from '@/components/ui/input'
import { cn } from '@/lib/utils'
// Icon mapping
@@ -51,6 +53,11 @@ const iconMap: Record<string, React.ReactNode> = {
music: <Music className="h-4 w-4" />,
scroll: <ScrollText className="h-4 w-4" />,
log: <ScrollText className="h-4 w-4" />,
trophy: <Trophy className="h-4 w-4" />,
award: <Award className="h-4 w-4" />,
star: <Star className="h-4 w-4" />,
gift: <Gift className="h-4 w-4" />,
list: <List className="h-4 w-4" />,
}
function getIcon(iconName?: string): React.ReactNode {
@@ -195,25 +202,62 @@ export default function AdminLayout() {
// ==================== Shell Render ====================
import { useRef } from 'react'
function LayoutShell({ sidebarOpen, mobileMenuOpen, toggleSidebar, setMobileMenu, navItems, user, onLogout }: {
sidebarOpen: boolean; mobileMenuOpen: boolean; toggleSidebar: () => void; setMobileMenu: (v: boolean) => void
navItems: NavItem[]; user: import('@/api/system').SystemUser | null; onLogout: () => void
}) {
const { setCmdKOpen } = useAppStore()
const location = useLocation()
const spotlightRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
if (spotlightRef.current) {
spotlightRef.current.style.background = `radial-gradient(600px circle at ${e.clientX}px ${e.clientY}px, rgba(6,182,212,0.25), transparent 40%)`
}
}
window.addEventListener('mousemove', handleMouseMove)
return () => window.removeEventListener('mousemove', handleMouseMove)
}, [])
return (
<div className="flex min-h-screen bg-background p-2 lg:p-3 gap-2 lg:gap-3">
<div className="flex min-h-screen bg-gradient-to-br from-slate-100 via-white to-cyan-50 dark:from-[#080d17] dark:via-[#080d17] dark:to-[#080d17] p-2 lg:p-3 gap-2 lg:gap-3 relative overflow-hidden">
{/* Interactive Cursor Spotlight (Illuminates the glass as you move the mouse) */}
<div
ref={spotlightRef}
className="pointer-events-none fixed inset-0 z-0 transition-opacity duration-300 opacity-100 dark:opacity-60"
style={{
background: `radial-gradient(600px circle at -1000px -1000px, rgba(6,182,212,0.25), transparent 40%)`
}}
/>
{/* Global Ambient Background */}
<div className="fixed inset-0 pointer-events-none overflow-hidden z-0">
{/* Cyan Glow Top Left */}
<div className="absolute top-[-20%] left-[-10%] w-[50vw] h-[50vw] max-w-[800px] max-h-[800px] bg-cyan-200/40 dark:bg-cyan-600/20 rounded-full blur-[100px] dark:blur-[120px] mix-blend-multiply dark:mix-blend-screen" />
{/* Purple Glow Bottom Right */}
<div className="absolute bottom-[-20%] right-[-10%] w-[50vw] h-[50vw] max-w-[800px] max-h-[800px] bg-fuchsia-200/40 dark:bg-fuchsia-600/20 rounded-full blur-[100px] dark:blur-[120px] mix-blend-multiply dark:mix-blend-screen" />
{/* Subtle Tech Grid */}
<div className="absolute inset-0 opacity-[0.04] dark:opacity-[0.05]" style={{ backgroundImage: 'linear-gradient(to right, currentColor 1px, transparent 1px), linear-gradient(to bottom, currentColor 1px, transparent 1px)', backgroundSize: '60px 60px' }} />
</div>
<CommandPalette />
{/* Sidebar */}
<aside className={cn(
'fixed inset-y-2 lg:inset-y-3 left-2 lg:left-3 z-40 flex flex-col rounded-2xl border border-white/40 dark:border-white/5 glass-panel transition-all duration-300 overflow-hidden',
'fixed inset-y-2 lg:inset-y-3 left-2 lg:left-3 z-40 flex flex-col rounded-2xl border border-white/60 dark:border-white/10 bg-white/60 dark:bg-black/40 backdrop-blur-2xl shadow-lg transition-all duration-300 overflow-hidden',
sidebarOpen ? 'w-[260px]' : 'w-20',
mobileMenuOpen ? 'translate-x-0' : '-translate-x-[120%] lg:translate-x-0'
)}>
{/* Logo */}
<div className="flex h-16 items-center justify-between px-5 shrink-0">
<div className={cn("flex items-center gap-3 overflow-hidden transition-all", !sidebarOpen && "justify-center w-full")}>
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-emerald-500 to-teal-500 text-white shadow-lg shadow-emerald-500/20">
<Leaf className="h-4 w-4" />
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-white shadow-sm overflow-hidden border border-slate-200/60 dark:border-white/10">
<img src="/logo.png" alt="Logo" className="w-full h-full object-cover" />
</div>
{sidebarOpen && <span className="font-bold text-lg tracking-tight bg-clip-text text-transparent bg-gradient-to-r from-emerald-600 to-teal-600 truncate">Sundynix Console</span>}
{sidebarOpen && <span className="font-bold text-lg tracking-tight bg-clip-text text-transparent bg-gradient-to-r from-emerald-600 to-teal-600 truncate">Sundynix Cloud</span>}
</div>
</div>
{/* Nav */}
@@ -259,7 +303,7 @@ function LayoutShell({ sidebarOpen, mobileMenuOpen, toggleSidebar, setMobileMenu
{/* Main */}
<div className={cn(
'flex-1 flex flex-col min-h-[calc(100vh-1rem)] lg:min-h-[calc(100vh-1.5rem)] transition-all duration-300 bg-white/40 dark:bg-slate-900/40 rounded-2xl border border-white/50 dark:border-white/5 shadow-soft overflow-hidden relative backdrop-blur-xl',
'relative z-10 flex-1 flex flex-col min-h-[calc(100vh-1rem)] lg:min-h-[calc(100vh-1.5rem)] transition-all duration-300 bg-white/60 dark:bg-black/30 rounded-2xl border border-white/60 dark:border-white/10 shadow-xl overflow-hidden backdrop-blur-2xl',
sidebarOpen ? 'lg:ml-[268px]' : 'lg:ml-[88px]'
)}>
{/* Header */}
@@ -277,9 +321,12 @@ function LayoutShell({ sidebarOpen, mobileMenuOpen, toggleSidebar, setMobileMenu
</div>
</div>
<div className="flex items-center gap-3">
<div className="hidden md:flex relative group">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground group-focus-within:text-emerald-500 transition-colors" />
<Input placeholder="全局搜索..." className="w-56 focus:w-72 pl-9 bg-white/50 dark:bg-black/20 border-white/40 dark:border-white/10 focus:bg-white dark:focus:bg-slate-800 focus:border-emerald-500/30 transition-all h-9 text-sm rounded-full shadow-sm" />
<div className="hidden md:flex relative group cursor-pointer" onClick={() => setCmdKOpen(true)}>
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground group-focus-within:text-primary transition-colors" />
<div className="flex items-center w-56 pl-9 pr-3 bg-white/50 dark:bg-black/20 border border-white/40 dark:border-white/10 hover:bg-white dark:hover:bg-slate-800 hover:border-primary/30 transition-all h-9 text-sm rounded-full shadow-sm text-muted-foreground">
<span className="flex-1 text-left">...</span>
<span className="text-[10px] border border-border px-1.5 rounded bg-background/50 leading-tight">K</span>
</div>
</div>
<Button variant="ghost" size="icon" className="relative rounded-full h-9 w-9 hover:bg-white/50 dark:hover:bg-white/10"
onClick={() => document.documentElement.classList.toggle('dark')}>
@@ -292,11 +339,23 @@ function LayoutShell({ sidebarOpen, mobileMenuOpen, toggleSidebar, setMobileMenu
</Button>
</div>
</header>
{/* TabBar */}
<TabBar />
{/* Content */}
<ScrollArea className="flex-1">
<main className="p-4 lg:p-6 relative z-0 min-h-full">
<div className="mx-auto w-full max-w-[1600px] animate-fade-in-up space-y-6">
<Outlet />
<div className="mx-auto w-full max-w-[1600px] space-y-6">
<AnimatePresence mode="wait">
<motion.div
key={location.pathname}
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -15 }}
transition={{ duration: 0.2 }}
>
<Outlet />
</motion.div>
</AnimatePresence>
</div>
</main>
</ScrollArea>