mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-03 23:06:36 +08:00
Refactor code structure and remove redundant sections for improved readability and maintainability
This commit is contained in:
@@ -1,26 +1,164 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
import { dashboardNavigation } from '@/lib/constants/navigation';
|
||||
import { cn } from '@/lib/utils/cn';
|
||||
import { isPathActive } from '@/lib/utils/navigation';
|
||||
import { useAppShellStore } from '@/store/app-shell';
|
||||
|
||||
export function DashboardSidebar() {
|
||||
const pathname = usePathname();
|
||||
const currentPath = pathname ?? '/';
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
import { dashboardNavigation } from '@/lib/constants/navigation';
|
||||
import { cn } from '@/lib/utils/cn';
|
||||
import { isNavigationItemActive } from '@/lib/utils/navigation';
|
||||
import { useAppShellStore } from '@/store/app-shell';
|
||||
import type { NavigationIconKey, NavigationItem } from '@/types/navigation';
|
||||
|
||||
function SidebarIcon({ icon }: { icon: NavigationIconKey }) {
|
||||
const commonProps = {
|
||||
className: 'h-[18px] w-[18px]',
|
||||
fill: 'none',
|
||||
stroke: 'currentColor',
|
||||
strokeWidth: 1.8,
|
||||
strokeLinecap: 'round' as const,
|
||||
strokeLinejoin: 'round' as const,
|
||||
viewBox: '0 0 24 24',
|
||||
};
|
||||
|
||||
switch (icon) {
|
||||
case 'home':
|
||||
return (
|
||||
<svg {...commonProps}>
|
||||
<path d='M3 10.5 12 3l9 7.5' />
|
||||
<path d='M5.5 9.5V21h13V9.5' />
|
||||
<path d='M9.5 21v-6h5v6' />
|
||||
</svg>
|
||||
);
|
||||
case 'node':
|
||||
return (
|
||||
<svg {...commonProps}>
|
||||
<rect x='4' y='4' width='6' height='6' rx='1.5' />
|
||||
<rect x='14' y='4' width='6' height='6' rx='1.5' />
|
||||
<rect x='9' y='14' width='6' height='6' rx='1.5' />
|
||||
<path d='M10 7h4M12 10v4' />
|
||||
</svg>
|
||||
);
|
||||
case 'website':
|
||||
return (
|
||||
<svg {...commonProps}>
|
||||
<circle cx='12' cy='12' r='8.5' />
|
||||
<path d='M3.5 12h17' />
|
||||
<path d='M12 3.5c2.4 2.2 3.8 5.2 3.8 8.5S14.4 18.3 12 20.5C9.6 18.3 8.2 15.3 8.2 12S9.6 5.7 12 3.5Z' />
|
||||
</svg>
|
||||
);
|
||||
case 'domain':
|
||||
return (
|
||||
<svg {...commonProps}>
|
||||
<path d='M4 8.5h16' />
|
||||
<path d='M4 15.5h16' />
|
||||
<rect x='3.5' y='5' width='17' height='14' rx='3' />
|
||||
</svg>
|
||||
);
|
||||
case 'certificate':
|
||||
return (
|
||||
<svg {...commonProps}>
|
||||
<path d='M7 4.5h10l2 2V14a7 7 0 1 1-14 0V4.5Z' />
|
||||
<path d='M9 10h6' />
|
||||
<path d='M10 14h4' />
|
||||
</svg>
|
||||
);
|
||||
case 'proxy':
|
||||
return (
|
||||
<svg {...commonProps}>
|
||||
<path d='M7 7h10' />
|
||||
<path d='M7 12h10' />
|
||||
<path d='M7 17h6' />
|
||||
<path d='m14 15 3 2-3 2' />
|
||||
</svg>
|
||||
);
|
||||
case 'release':
|
||||
return (
|
||||
<svg {...commonProps}>
|
||||
<path d='M12 4v10' />
|
||||
<path d='m8.5 7.5 3.5-3.5 3.5 3.5' />
|
||||
<path d='M5 19.5h14' />
|
||||
</svg>
|
||||
);
|
||||
case 'user':
|
||||
return (
|
||||
<svg {...commonProps}>
|
||||
<circle cx='12' cy='8' r='3.25' />
|
||||
<path d='M5 19.5c1.7-3 4.1-4.5 7-4.5s5.3 1.5 7 4.5' />
|
||||
</svg>
|
||||
);
|
||||
case 'setting':
|
||||
return (
|
||||
<svg {...commonProps}>
|
||||
<circle cx='12' cy='12' r='3.5' />
|
||||
<path d='M12 3.5v2.2M12 18.3v2.2M20.5 12h-2.2M5.7 12H3.5M18.1 5.9l-1.6 1.6M7.5 16.5l-1.6 1.6M18.1 18.1l-1.6-1.6M7.5 7.5 5.9 5.9' />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function SidebarNavItem({
|
||||
item,
|
||||
currentPath,
|
||||
isSidebarCollapsed,
|
||||
depth = 0,
|
||||
}: {
|
||||
item: NavigationItem;
|
||||
currentPath: string;
|
||||
isSidebarCollapsed: boolean;
|
||||
depth?: number;
|
||||
}) {
|
||||
const active = isNavigationItemActive(currentPath, item);
|
||||
const hasChildren = Boolean(item.children?.length);
|
||||
|
||||
return (
|
||||
<div className='space-y-2'>
|
||||
<Link
|
||||
href={item.href}
|
||||
className={cn(
|
||||
'flex items-center gap-3 rounded-2xl border px-3 py-3 transition-colors',
|
||||
depth > 0 && 'ml-3 rounded-xl py-2.5',
|
||||
active
|
||||
? 'border-[var(--border-strong)] bg-[var(--accent-soft)] text-[var(--foreground-primary)]'
|
||||
: 'border-transparent text-[var(--foreground-secondary)] hover:border-[var(--border-default)] hover:bg-[var(--surface-muted)] hover:text-[var(--foreground-primary)]',
|
||||
)}
|
||||
>
|
||||
<span className='flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-[var(--control-background)] text-[var(--foreground-primary)]'>
|
||||
<SidebarIcon icon={item.icon} />
|
||||
</span>
|
||||
{!isSidebarCollapsed ? (
|
||||
<span className='min-w-0 flex-1 text-sm font-medium'>{item.label}</span>
|
||||
) : null}
|
||||
</Link>
|
||||
{!isSidebarCollapsed && hasChildren ? (
|
||||
<div className='space-y-2'>
|
||||
{item.children?.map((child) => (
|
||||
<SidebarNavItem
|
||||
key={child.href}
|
||||
item={child}
|
||||
currentPath={currentPath}
|
||||
isSidebarCollapsed={isSidebarCollapsed}
|
||||
depth={depth + 1}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function DashboardSidebar() {
|
||||
const pathname = usePathname();
|
||||
const currentPath = pathname ?? '/';
|
||||
const isSidebarCollapsed = useAppShellStore((state) => state.isSidebarCollapsed);
|
||||
|
||||
return (
|
||||
<aside
|
||||
className={cn(
|
||||
'sticky top-0 hidden h-screen shrink-0 border-r border-[var(--border-default)] bg-[var(--surface-panel)]/95 px-3 py-6 backdrop-blur lg:block',
|
||||
isSidebarCollapsed ? 'w-24' : 'w-72',
|
||||
)}
|
||||
>
|
||||
<div className='flex h-full flex-col gap-6'>
|
||||
<aside
|
||||
className={cn(
|
||||
'sticky top-0 hidden h-screen shrink-0 overflow-hidden border-r border-[var(--border-default)] bg-[var(--surface-panel)]/95 px-3 py-6 backdrop-blur lg:block',
|
||||
isSidebarCollapsed ? 'w-24' : 'w-72',
|
||||
)}
|
||||
>
|
||||
<div className='flex h-full flex-col gap-6'>
|
||||
<div className='flex items-center gap-3 rounded-2xl border border-[var(--border-default)] bg-[var(--surface-muted)] px-3 py-3'>
|
||||
<div className='flex h-11 w-11 items-center justify-center rounded-2xl bg-[var(--brand-primary-soft)] text-lg font-semibold text-[var(--brand-primary)]'>
|
||||
AF
|
||||
@@ -33,43 +171,25 @@ export function DashboardSidebar() {
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<nav className='flex-1 space-y-2'>
|
||||
{dashboardNavigation.map((item) => {
|
||||
const active = isPathActive(currentPath, item.href);
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={cn(
|
||||
'flex items-start gap-3 rounded-2xl border px-3 py-3 transition-colors',
|
||||
active
|
||||
? 'border-[var(--border-strong)] bg-[var(--accent-soft)] text-[var(--foreground-primary)]'
|
||||
: 'border-transparent text-[var(--foreground-secondary)] hover:border-[var(--border-default)] hover:bg-[var(--surface-muted)] hover:text-[var(--foreground-primary)]',
|
||||
)}
|
||||
>
|
||||
<span className='mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-xl bg-[var(--control-background)] text-xs font-semibold'>
|
||||
{item.shortLabel.slice(0, 1)}
|
||||
</span>
|
||||
{!isSidebarCollapsed ? (
|
||||
<span className='min-w-0'>
|
||||
<span className='block text-sm font-medium'>{item.label}</span>
|
||||
<span className='mt-1 block text-xs leading-5 text-inherit/80'>
|
||||
{item.description}
|
||||
</span>
|
||||
</span>
|
||||
) : null}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
<nav className='flex-1 space-y-2'>
|
||||
<div className='flex max-h-full min-h-0 flex-col gap-2 overflow-y-auto pr-1'>
|
||||
{dashboardNavigation.map((item) => (
|
||||
<SidebarNavItem
|
||||
key={item.href}
|
||||
item={item}
|
||||
currentPath={currentPath}
|
||||
isSidebarCollapsed={isSidebarCollapsed}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{!isSidebarCollapsed ? (
|
||||
<div className='rounded-2xl border border-[var(--status-success-border)] bg-[var(--status-success-soft)] px-4 py-4 text-xs leading-6 text-[var(--status-success-foreground)]'>
|
||||
新版管理端已覆盖认证、核心链路和边缘模块,旧版前端代码已完成移除。
|
||||
新版控制台已切换为生产导向的信息架构,核心操作统一收敛到列表与弹窗。
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useAuth } from '@/components/providers/auth-provider';
|
||||
import { ThemeToggle } from '@/components/ui/theme-toggle';
|
||||
import { getCurrentNavigationItem } from '@/lib/utils/navigation';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useAuth } from '@/components/providers/auth-provider';
|
||||
import { ThemeToggle } from '@/components/ui/theme-toggle';
|
||||
import { getCurrentNavigationItem } from '@/lib/utils/navigation';
|
||||
import { publicEnv } from '@/lib/env/public-env';
|
||||
import { useAppShellStore } from '@/store/app-shell';
|
||||
import { usePathname } from 'next/navigation';
|
||||
@@ -31,12 +31,12 @@ export function DashboardTopbar() {
|
||||
<div className='flex items-center gap-3'>
|
||||
<button
|
||||
type='button'
|
||||
onClick={toggleSidebar}
|
||||
className='inline-flex h-11 w-11 items-center justify-center rounded-2xl border border-[var(--border-default)] bg-[var(--control-background)] text-lg text-[var(--foreground-primary)] transition hover:bg-[var(--control-background-hover)]'
|
||||
aria-label='切换侧边栏'
|
||||
>
|
||||
≡
|
||||
</button>
|
||||
onClick={toggleSidebar}
|
||||
className='inline-flex h-11 w-11 items-center justify-center rounded-2xl border border-[var(--border-default)] bg-[var(--control-background)] text-lg text-[var(--foreground-primary)] transition hover:bg-[var(--control-background-hover)]'
|
||||
aria-label='切换侧边栏'
|
||||
>
|
||||
☰
|
||||
</button>
|
||||
<div>
|
||||
<p className='text-xs uppercase tracking-[0.24em] text-[var(--foreground-secondary)]'>当前模块</p>
|
||||
<h2 className='text-lg font-semibold text-[var(--foreground-primary)]'>
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
interface PageHeaderProps {
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export function PageHeader({ title, description }: PageHeaderProps) {
|
||||
return (
|
||||
<div className='space-y-3'>
|
||||
<p className='text-sm font-medium uppercase tracking-[0.24em] text-[var(--brand-primary)]'>ATSFlare</p>
|
||||
<div className='space-y-2'>
|
||||
<h1 className='text-3xl font-semibold tracking-tight text-[var(--foreground-primary)]'>{title}</h1>
|
||||
<p className='max-w-3xl text-sm leading-7 text-[var(--foreground-secondary)]'>
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
interface PageHeaderProps {
|
||||
title: string;
|
||||
description: string;
|
||||
action?: ReactNode;
|
||||
}
|
||||
|
||||
export function PageHeader({ title, description, action }: PageHeaderProps) {
|
||||
return (
|
||||
<div className='flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between'>
|
||||
<div className='space-y-3'>
|
||||
<p className='text-sm font-medium uppercase tracking-[0.24em] text-[var(--brand-primary)]'>ATSFlare</p>
|
||||
<div className='space-y-2'>
|
||||
<h1 className='text-3xl font-semibold tracking-tight text-[var(--foreground-primary)]'>{title}</h1>
|
||||
<p className='max-w-3xl text-sm leading-7 text-[var(--foreground-secondary)]'>
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{action ? <div className='flex flex-wrap gap-3'>{action}</div> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, type ReactNode } from 'react';
|
||||
|
||||
import { cn } from '@/lib/utils/cn';
|
||||
|
||||
interface AppModalProps {
|
||||
isOpen: boolean;
|
||||
title: string;
|
||||
description?: string;
|
||||
children: ReactNode;
|
||||
footer?: ReactNode;
|
||||
onClose: () => void;
|
||||
size?: 'md' | 'lg' | 'xl';
|
||||
}
|
||||
|
||||
const sizeClassNameMap = {
|
||||
md: 'max-w-2xl',
|
||||
lg: 'max-w-4xl',
|
||||
xl: 'max-w-5xl',
|
||||
} satisfies Record<NonNullable<AppModalProps['size']>, string>;
|
||||
|
||||
export function AppModal({
|
||||
isOpen,
|
||||
title,
|
||||
description,
|
||||
children,
|
||||
footer,
|
||||
onClose,
|
||||
size = 'lg',
|
||||
}: AppModalProps) {
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
const previousOverflow = document.body.style.overflow;
|
||||
document.body.style.overflow = 'hidden';
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
return () => {
|
||||
document.body.style.overflow = previousOverflow;
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [isOpen, onClose]);
|
||||
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='fixed inset-0 z-50 flex items-center justify-center bg-slate-950/55 px-4 py-6' role='dialog' aria-modal='true' aria-label={title}>
|
||||
<button type='button' className='absolute inset-0 cursor-default' onClick={onClose} aria-label='关闭弹窗' />
|
||||
<div
|
||||
className={cn(
|
||||
'relative flex max-h-[calc(100vh-3rem)] w-full flex-col overflow-hidden rounded-[28px] border border-[var(--border-default)] bg-[var(--surface-panel)] shadow-2xl',
|
||||
sizeClassNameMap[size],
|
||||
)}
|
||||
>
|
||||
<div className='flex items-start justify-between gap-4 border-b border-[var(--border-default)] px-6 py-5'>
|
||||
<div className='space-y-2'>
|
||||
<h2 className='text-xl font-semibold text-[var(--foreground-primary)]'>{title}</h2>
|
||||
{description ? (
|
||||
<p className='max-w-2xl text-sm leading-6 text-[var(--foreground-secondary)]'>{description}</p>
|
||||
) : null}
|
||||
</div>
|
||||
<button
|
||||
type='button'
|
||||
onClick={onClose}
|
||||
className='inline-flex h-10 w-10 items-center justify-center rounded-2xl border border-[var(--border-default)] bg-[var(--control-background)] text-lg text-[var(--foreground-primary)] transition hover:bg-[var(--control-background-hover)]'
|
||||
aria-label='关闭弹窗'
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<div className='min-h-0 flex-1 overflow-y-auto px-6 py-6'>{children}</div>
|
||||
{footer ? <div className='border-t border-[var(--border-default)] px-6 py-5'>{footer}</div> : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user