diff --git a/.claude/launch.json b/.claude/launch.json index 3263e92..b62ef28 100644 --- a/.claude/launch.json +++ b/.claude/launch.json @@ -7,6 +7,13 @@ "runtimeArgs": ["run", "dev"], "cwd": "web", "port": 5173 + }, + { + "name": "admin", + "runtimeExecutable": "npm", + "runtimeArgs": ["run", "dev"], + "cwd": "admin", + "port": 5174 } ] } diff --git a/admin/src/App.tsx b/admin/src/App.tsx index 3802189..11e20e8 100644 --- a/admin/src/App.tsx +++ b/admin/src/App.tsx @@ -2,6 +2,7 @@ import { Navigate, Outlet, Route, Routes } from 'react-router-dom' import { getToken } from '@/api/client' import { AdminLayout } from '@/components/admin-layout' import LoginPage from '@/pages/login' +import DashboardPage from '@/pages/dashboard' import PostsListPage from '@/pages/posts-list' import PostEditPage from '@/pages/post-edit' @@ -16,7 +17,8 @@ export default function App() { } /> }> }> - } /> + } /> + } /> } /> } /> diff --git a/admin/src/api/posts.ts b/admin/src/api/posts.ts index c431d0a..7073117 100644 --- a/admin/src/api/posts.ts +++ b/admin/src/api/posts.ts @@ -61,3 +61,14 @@ export function updatePost(id: string, form: PostForm) { export function deletePost(id: string) { return request(`/api/admin/posts/${id}`, { method: 'DELETE' }) } + +export interface Stats { + total: number + published: number + draft: number + recent: PostListItem[] +} + +export function fetchStats() { + return request('/api/admin/stats') +} diff --git a/admin/src/components/admin-layout.tsx b/admin/src/components/admin-layout.tsx index 0b2007b..c5f538c 100644 --- a/admin/src/components/admin-layout.tsx +++ b/admin/src/components/admin-layout.tsx @@ -1,52 +1,37 @@ -import { Link, Outlet, useNavigate } from 'react-router-dom' -import { LogOut, Moon, Sun } from 'lucide-react' -import { logout } from '@/api/auth' -import { useTheme } from '@/components/theme-provider' +import { useState } from 'react' +import { Outlet } from 'react-router-dom' +import { Menu } from 'lucide-react' +import { AdminSidebar } from '@/components/admin-sidebar' import { LogoMark } from '@/components/logo' export function AdminLayout() { - const navigate = useNavigate() - const { resolved, setTheme } = useTheme() + const [open, setOpen] = useState(false) return ( -
-
-
- - - sundynix admin - -
- - -
-
-
-
- -
+
+ setOpen(false)} /> + +
+ {/* 移动端顶部条 */} +
+ + + sundynix{' '} + admin + +
+ +
+ +
+
) } diff --git a/admin/src/components/admin-sidebar.tsx b/admin/src/components/admin-sidebar.tsx new file mode 100644 index 0000000..dc5777d --- /dev/null +++ b/admin/src/components/admin-sidebar.tsx @@ -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 && ( +