feat(admin): 侧边栏布局 + 仪表盘 + 全局隐藏滚动条
- 全局隐藏滚动条(web + admin),保留正常滚动 - admin 顶栏改侧边栏导航(仪表盘/文章),响应式移动端抽屉 - 路由重构:首页→仪表盘,文章列表移到 /posts - 仪表盘页:文章总数/已发布/草稿统计卡片 + 最近更新列表 - 后端 GET /api/admin/stats Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
import { Link, NavLink, useNavigate } from 'react-router-dom'
|
||||
import { FileText, LayoutDashboard, LogOut, Moon, Sun, X } from 'lucide-react'
|
||||
import { logout } from '@/api/auth'
|
||||
import { useTheme } from '@/components/theme-provider'
|
||||
import { LogoMark } from '@/components/logo'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const NAV = [
|
||||
{ to: '/', label: '仪表盘', icon: LayoutDashboard, end: true },
|
||||
{ to: '/posts', label: '文章', icon: FileText, end: false },
|
||||
]
|
||||
|
||||
interface Props {
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export function AdminSidebar({ open, onClose }: Props) {
|
||||
const navigate = useNavigate()
|
||||
const { resolved, setTheme } = useTheme()
|
||||
|
||||
const itemCls = ({ isActive }: { isActive: boolean }) =>
|
||||
cn(
|
||||
'flex items-center gap-3 rounded-lg px-3 py-2.5 text-[14px] transition-colors',
|
||||
isActive
|
||||
? 'bg-accent-soft font-medium text-accent-ink'
|
||||
: 'text-ink-2 hover:bg-ground hover:text-ink',
|
||||
)
|
||||
|
||||
const bottomBtnCls =
|
||||
'flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-[14px] text-ink-2 transition-colors hover:bg-ground hover:text-ink'
|
||||
|
||||
return (
|
||||
<>
|
||||
{open && (
|
||||
<div
|
||||
className="fixed inset-0 z-40 bg-black/40 md:hidden"
|
||||
onClick={onClose}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
<aside
|
||||
className={cn(
|
||||
'fixed inset-y-0 left-0 z-50 flex w-[220px] flex-col border-r border-hairline bg-surface transition-transform md:translate-x-0',
|
||||
open ? 'translate-x-0' : '-translate-x-full',
|
||||
)}
|
||||
>
|
||||
<div className="flex h-16 items-center justify-between border-b border-hairline px-5">
|
||||
<Link
|
||||
to="/"
|
||||
onClick={onClose}
|
||||
className="flex items-center gap-2.5 font-mono text-[14px] font-semibold"
|
||||
>
|
||||
<LogoMark size={24} />
|
||||
sundynix <em className="not-italic text-accent">admin</em>
|
||||
</Link>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
aria-label="关闭菜单"
|
||||
className="text-ink-2 md:hidden"
|
||||
>
|
||||
<X className="size-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<nav className="flex-1 space-y-1 px-3 py-4">
|
||||
{NAV.map((item) => (
|
||||
<NavLink
|
||||
key={item.to}
|
||||
to={item.to}
|
||||
end={item.end}
|
||||
onClick={onClose}
|
||||
className={itemCls}
|
||||
>
|
||||
<item.icon className="size-[18px]" />
|
||||
{item.label}
|
||||
</NavLink>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<div className="space-y-1 border-t border-hairline p-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setTheme(resolved === 'dark' ? 'light' : 'dark')}
|
||||
className={bottomBtnCls}
|
||||
>
|
||||
{resolved === 'dark' ? (
|
||||
<Sun className="size-[18px]" />
|
||||
) : (
|
||||
<Moon className="size-[18px]" />
|
||||
)}
|
||||
{resolved === 'dark' ? '浅色模式' : '深色模式'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
logout()
|
||||
navigate('/login')
|
||||
}}
|
||||
className={bottomBtnCls}
|
||||
>
|
||||
<LogOut className="size-[18px]" /> 退出登录
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
</>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user