feat: 企业级设计系统 v3 — 高级侧边栏/排版/阴影/动效
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { CalendarDays, Rows3, ChartColumn, GraduationCap, Shapes, SlidersHorizontal, LogIn, ShieldCheck, Cloud } from 'lucide-react';
|
||||
import { CalendarDays, Rows3, ChartColumn, GraduationCap, Shapes, SlidersHorizontal, LogIn, LogOut, ShieldCheck, Cloud } from 'lucide-react';
|
||||
import { serverMe, clearToken, getToken } from '@/lib/api';
|
||||
import { LoginDialog } from '@/components/auth/LoginDialog';
|
||||
import { cn } from '@/lib/utils';
|
||||
@@ -13,25 +13,26 @@ const NAV = [
|
||||
{ to: '/timeline', label: '时间线', icon: CalendarDays },
|
||||
{ to: '/records', label: '记录', icon: Rows3 },
|
||||
{ to: '/grades', label: '成绩', icon: GraduationCap },
|
||||
{ to: '/dashboard', label: '统计', icon: ChartColumn }
|
||||
]
|
||||
{ to: '/dashboard', label: '统计', icon: ChartColumn },
|
||||
],
|
||||
},
|
||||
{
|
||||
section: '配置',
|
||||
items: [
|
||||
{ to: '/admin', label: '管理', icon: ShieldCheck },
|
||||
{ to: '/types', label: '记录类型', icon: Shapes },
|
||||
{ to: '/taxonomy', label: '字典与维度', icon: SlidersHorizontal }
|
||||
]
|
||||
}
|
||||
{ to: '/taxonomy', label: '字典与维度', icon: SlidersHorizontal },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
function LogoMark() {
|
||||
return (
|
||||
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" aria-hidden>
|
||||
<rect x="1" y="1" width="24" height="24" rx="6" fill="var(--brand)" />
|
||||
<ellipse cx="11" cy="10" rx="4.2" ry="5.2" fill="#fff" opacity="0.95" transform="rotate(-18 11 10)" />
|
||||
<circle cx="17.5" cy="17.5" r="3.1" fill="#fff" opacity="0.7" />
|
||||
<svg width='28' height='28' viewBox='0 0 26 26' fill='none' aria-hidden>
|
||||
<defs><linearGradient id='lg' x1='0' y1='0' x2='0' y2='26'><stop stopColor='#4359e8'/><stop offset='1' stopColor='#2a3ab8'/></linearGradient></defs>
|
||||
<rect x='1' y='1' width='24' height='24' rx='7' fill='url(#lg)'/>
|
||||
<ellipse cx='11' cy='10' rx='4.2' ry='5.2' fill='#fff' opacity='.95' transform='rotate(-18 11 10)'/>
|
||||
<circle cx='17.5' cy='17.5' r='3.1' fill='#fff' opacity='.7'/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -42,47 +43,51 @@ export function Sidebar({ open, onClose }: { open: boolean; onClose: () => void
|
||||
queryKey: ['me'],
|
||||
queryFn: () => serverMe(),
|
||||
enabled: !!getToken(),
|
||||
retry: 0
|
||||
retry: 0,
|
||||
});
|
||||
const [loginOpen, setLoginOpen] = useState(false);
|
||||
const me = meQ.data ?? null;
|
||||
const [loginOpen, setLoginOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<aside
|
||||
className={
|
||||
'flex w-[224px] shrink-0 flex-col border-r border-line bg-surface' +
|
||||
' max-lg:fixed max-lg:inset-y-0 max-lg:left-0 max-lg:z-40 max-lg:transition-transform' +
|
||||
(open ? ' max-lg:translate-x-0' : ' max-lg:-translate-x-full')
|
||||
}
|
||||
className={cn(
|
||||
'flex w-[236px] shrink-0 flex-col border-r border-line bg-surface',
|
||||
'max-lg:fixed max-lg:inset-y-0 max-lg:left-0 max-lg:z-40 max-lg:transition-transform',
|
||||
open ? 'max-lg:translate-x-0' : 'max-lg:-translate-x-full',
|
||||
)}
|
||||
>
|
||||
<div className="flex h-14 items-center gap-2.5 border-b border-line px-4">
|
||||
<LoginDialog open={loginOpen} onClose={() => setLoginOpen(false)} defaultUrl='https://footpr.linyux.cn' />
|
||||
|
||||
{/* Logo */}
|
||||
<div className="flex h-[60px] items-center gap-3 border-b border-line px-5">
|
||||
<LogoMark />
|
||||
<div className="leading-tight">
|
||||
<div className="text-[14px] font-semibold text-ink-1">足迹</div>
|
||||
<div className="text-[10px] font-medium tracking-[0.18em] text-ink-3">FOOTPRINTS</div>
|
||||
<div className="text-[15px] font-bold tracking-tight text-ink-1">足迹</div>
|
||||
<div className="text-[9px] font-medium tracking-[0.24em] text-ink-3">FOOTPRINTS</div>
|
||||
</div>
|
||||
</div>
|
||||
<LoginDialog open={loginOpen} onClose={() => setLoginOpen(false)} defaultUrl={'https://footpr.linyux.cn'} />
|
||||
<nav className="flex-1 overflow-y-auto px-2 py-3">
|
||||
{NAV.map(group => (
|
||||
<div key={group.section} className="mb-4">
|
||||
<div className="px-2 pb-1.5 text-[11px] font-medium text-ink-3">{group.section}</div>
|
||||
|
||||
{/* 导航 */}
|
||||
<nav className="flex-1 overflow-y-auto px-2.5 py-4">
|
||||
{NAV.map((group) => (
|
||||
<div key={group.section} className="mb-5">
|
||||
<div className="px-2.5 pb-1.5 text-[10px] font-semibold uppercase tracking-[0.08em] text-ink-3">{group.section}</div>
|
||||
<div className="flex flex-col gap-0.5">
|
||||
{group.items.map(item => (
|
||||
{group.items.map((item) => (
|
||||
<NavLink
|
||||
key={item.to}
|
||||
to={item.to}
|
||||
onClick={onClose}
|
||||
className={({ isActive }) =>
|
||||
cn(
|
||||
'flex h-8 items-center gap-2.5 rounded-md px-2 text-[13px] transition-colors',
|
||||
'relative flex h-[34px] items-center gap-3 rounded-lg px-3 text-[13px] font-medium transition-all duration-150',
|
||||
isActive
|
||||
? 'bg-brand-subtle font-medium text-brand-text'
|
||||
: 'text-ink-2 hover:bg-surface-3 hover:text-ink-1'
|
||||
? 'bg-brand-subtle text-brand-text nav-active'
|
||||
: 'text-ink-2 hover:bg-surface-3 hover:text-ink-1',
|
||||
)
|
||||
}
|
||||
>
|
||||
<item.icon size={15} strokeWidth={2} />
|
||||
<item.icon size={16} strokeWidth={1.8} />
|
||||
{item.label}
|
||||
</NavLink>
|
||||
))}
|
||||
@@ -91,43 +96,43 @@ export function Sidebar({ open, onClose }: { open: boolean; onClose: () => void
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<div className="border-t border-line px-4 py-3">
|
||||
{/* 左下角:账号区(云端模式) */}
|
||||
{/* 左下角:账号区 */}
|
||||
<div className="border-t border-line px-3 py-3">
|
||||
{getToken() && me ? (
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="rounded-lg bg-surface-2 px-3 py-2.5">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-1.5 min-w-0">
|
||||
<Cloud size={13} className="shrink-0 text-success" />
|
||||
<div className="min-w-0 truncate text-[12px] font-medium text-ink-1" title={me.username}>
|
||||
{me.is_admin && <span className="mr-0.5 text-brand-text">管理员</span>}
|
||||
{me.username}
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<div className="flex size-6 shrink-0 items-center justify-center rounded-full bg-brand text-[10px] font-bold text-white">
|
||||
{me.username.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="truncate text-[12px] font-semibold text-ink-1" title={me.username}>{me.username}</div>
|
||||
{me.is_admin && <div className='text-[10px] text-brand-text'>管理员</div>}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="shrink-0 text-[11px] text-ink-3 underline transition-colors hover:text-danger"
|
||||
className="shrink-0 rounded p-1 text-ink-3 transition-colors hover:bg-surface-3 hover:text-danger"
|
||||
title="退出登录"
|
||||
onClick={() => { clearToken(); qc.clear(); window.location.reload(); }}
|
||||
>
|
||||
退出
|
||||
<LogOut size={13} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 text-[10px] text-success">
|
||||
<div className="mt-1.5 flex items-center gap-1 text-[10px] text-success">
|
||||
<Cloud size={10} />
|
||||
云端同步已开启
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<button
|
||||
className="flex w-full items-center gap-1.5 rounded-md bg-brand-subtle px-2 py-1.5 text-[12.5px] font-medium text-brand-text transition-colors hover:brightness-[0.97]"
|
||||
onClick={() => setLoginOpen(true)}
|
||||
>
|
||||
<LogIn size={13} />
|
||||
登录账号
|
||||
</button>
|
||||
<div className="text-[10px] text-ink-3">登录后数据云端互通</div>
|
||||
</div>
|
||||
<button
|
||||
className="flex w-full items-center justify-center gap-2 rounded-lg border border-brand/30 bg-brand-subtle px-3 py-2 text-[13px] font-medium text-brand-text transition-all hover:shadow-brand"
|
||||
onClick={() => setLoginOpen(true)}
|
||||
>
|
||||
<LogIn size={14} />
|
||||
登录我的档案
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user