diff --git a/src/app/(app)/admin/layout.tsx b/src/app/(app)/admin/layout.tsx index 1206a8f..ef5f4c2 100644 --- a/src/app/(app)/admin/layout.tsx +++ b/src/app/(app)/admin/layout.tsx @@ -1,21 +1,24 @@ -import { ReactNode } from "react"; import { AdminSidebar } from "@/components/admin/sidebar"; -import { Header } from "@/components/header"; +import { ReactNode } from "react"; -interface AdminLayoutProps { +export default function AdminLayout({ + children +}: { children: ReactNode; -} - -export default function AdminLayout({ children }: AdminLayoutProps) { +}) { return ( -
- -
- {/*
*/} -
+
+ {/* 左侧边栏 */} + + + {/* 主要内容区域 */} +
+
{children} -
-
+
+
); } \ No newline at end of file diff --git a/src/app/(app)/admin/page.tsx b/src/app/(app)/admin/page.tsx index b987b3a..257360a 100644 --- a/src/app/(app)/admin/page.tsx +++ b/src/app/(app)/admin/page.tsx @@ -1,6 +1,6 @@ -import { AdminSidebar } from "@/components/admin/sidebar"; +// import { AdminSidebar } from "@/components/admin/sidebar"; import { Header } from "@/components/header"; -import { AdminDashboard } from "@/components/admin/dashboard"; +import { MonitoringDashboard } from "@/components/admin/monitoring-dashboard"; import AdminLayout from "@/app/(app)/admin/layout"; import type { ReactElement } from "react"; import prisma from "@/lib/prisma"; @@ -13,15 +13,13 @@ export default async function AdminPage(): Promise { return ( -
- -
-
-
- -
-
+
+
+
+ {/* 监控仪表盘替代原有仪表盘 */} + +
); -} \ No newline at end of file +} diff --git a/src/components/admin/monitoring-dashboard.tsx b/src/components/admin/monitoring-dashboard.tsx new file mode 100644 index 0000000..1755239 --- /dev/null +++ b/src/components/admin/monitoring-dashboard.tsx @@ -0,0 +1,116 @@ +'use client'; + +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { useWebVitals } from "@/hooks/use-web-vitals"; +import { useEffect } from "react"; + +interface MonitoringDashboardProps { + userCount: number; + problemCount: number; +} + +export function MonitoringDashboard({ + userCount, + problemCount +}: MonitoringDashboardProps) { + const { vitals, loading } = useWebVitals(); + + // 添加日志输出性能指标读取结果(已注释) + useEffect(() => { + if (!loading) { + // console.log('[仪表盘] 当前显示指标:', { + // lcp: vitals.lcp ? `${vitals.lcp.value.toFixed(2)}秒(${vitals.lcp.rating})` : '未收集', + // fid: vitals.fid ? `${vitals.fid.value.toFixed(2)}毫秒(${vitals.fid.rating})` : '未收集', + // cls: vitals.cls ? `${vitals.cls.value.toFixed(2)}(${vitals.cls.rating})` : '未收集', + // fcp: vitals.fcp ? `${vitals.fcp.value.toFixed(2)}秒(${vitals.fcp.rating})` : '未收集' + // }); + } + }, [loading, vitals]); + + return ( +
+ {/* 用户统计 */} + + + + 活跃用户 + + + + + + + + + +
{userCount}
+

+ +20% 本月新增 +

+
+
+ + {/* 题目统计 */} + + + + 在线题目 + + + + + + + + + + +
{problemCount}
+

+ +15% 本周新增 +

+
+
+ + {/* 性能指标卡片 */} + + + 性能指标 + + +
+
LCP:
+
{loading ? '加载中...' : vitals.lcp ? `${vitals.lcp.value.toFixed(2)} 秒(${vitals.lcp.rating})` : '暂无数据'}
+ +
FID:
+
{loading ? '加载中...' : vitals.fid ? `${vitals.fid.value.toFixed(2)} 毫秒(${vitals.fid.rating})` : '暂无数据'}
+ +
CLS:
+
{loading ? '加载中...' : vitals.cls ? `${vitals.cls.value.toFixed(2)}(${vitals.cls.rating})` : '暂无数据'}
+ +
FCP:
+
{loading ? '加载中...' : vitals.fcp ? `${vitals.fcp.value.toFixed(2)} 秒(${vitals.fcp.rating})` : '暂无数据'}
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/admin/sidebar.tsx b/src/components/admin/sidebar.tsx index e3f1db1..d0efd2b 100644 --- a/src/components/admin/sidebar.tsx +++ b/src/components/admin/sidebar.tsx @@ -1,13 +1,13 @@ "use client" import { useSession } from "next-auth/react"; +import { usePathname } from "next/navigation"; +// 仅保留实际使用的组件导入 import { - Sidebar, SidebarContent, SidebarFooter, SidebarRail, } from "@/components/ui/sidebar"; import { NavMain } from "@/components/nav-main"; -import { NavProjects } from "@/components/nav-projects"; import { NavUser } from "@/components/nav-user"; import { Command, @@ -15,6 +15,9 @@ import { Settings2, } from "lucide-react"; +// 添加缺失的AppSidebar导入 +import { AppSidebar as BaseAppSidebar } from "@/components/app-sidebar"; + import { useEffect, useState } from "react"; import { PrismaClient } from "@prisma/client"; @@ -27,13 +30,6 @@ const teams = [ const adminData = { - // teams: [ - // { - // name: "Admin Team", - // logo: GalleryVerticalEnd, - // plan: "Enterprise", - // }, - // ], navMain: [ { title: "Dashboard", @@ -69,9 +65,10 @@ const adminData = { ] }; -export const AdminSidebar = ({ ...props }: React.ComponentProps) => { +export function AdminSidebar() { const { data: session } = useSession(); const [userAvatar, setUserAvatar] = useState(""); + const pathname = usePathname(); useEffect(() => { const fetchUserAvatar = async () => { @@ -99,20 +96,48 @@ export const AdminSidebar = ({ ...props }: React.ComponentProps) email: session?.user?.email || "admin@example.com", avatar: userAvatar }; - + const adminNavItems = adminData.navMain.map((item) => ({ + ...item, + items: item.items.map((subItem) => ({ + ...subItem, + active: subItem.url === pathname, + })), + })); return ( - + {/**/} {/* */} {/**/} - - + + {/* 添加监控菜单项 */} + - + ); -}; \ No newline at end of file +} diff --git a/src/hooks/use-web-vitals.ts b/src/hooks/use-web-vitals.ts new file mode 100644 index 0000000..3c4be86 --- /dev/null +++ b/src/hooks/use-web-vitals.ts @@ -0,0 +1,80 @@ +'use client'; + +import { useReportWebVitals } from 'next/web-vitals'; +import { useEffect, useState } from 'react'; + +export interface WebVital { + value: number; + rating: 'good' | 'needs-improvement' | 'poor'; +} + +interface WebVitals { + lcp?: WebVital; + fid?: WebVital; + cls?: WebVital; + fcp?: WebVital; +} + +export function useWebVitals() { + const [vitals, setVitals] = useState({ + lcp: undefined, + fid: undefined, + cls: undefined, + fcp: undefined, + }); + const [loading, setLoading] = useState(true); + + // 添加详细日志记录指标接收(已注释) + useReportWebVitals((metric) => { + // console.log(`[Web Vitals] 接收指标: ${metric.name}`, { + // value: metric.value, + // rating: getRating(metric) + // }); + + setVitals((prev) => ({ + ...prev, + [metric.name.toLowerCase()]: { + value: metric.value, + rating: getRating(metric), + }, + })); + }); + + // 添加日志记录最终指标状态(已注释) + useEffect(() => { + if (!loading) { + // console.log('[Web Vitals] 当前完整指标:', { + // lcp: vitals.lcp?.value, + // fid: vitals.fid?.value, + // cls: vitals.cls?.value, + // fcp: vitals.fcp?.value + // }); + } + }, [loading, vitals]); + + // 使用useEffect处理加载状态 + useEffect(() => { + const timer = setTimeout(() => { + setLoading(false); + }, 2000); + + return () => { + clearTimeout(timer); + }; + }, []); + + return { vitals, loading }; +} + +// 添加更具体的类型定义 +interface Metric { + name: string; + value: number; + rating: 'good' | 'needs-improvement' | 'poor'; +} + +function getRating(metric: Metric): 'good' | 'needs-improvement' | 'poor' { + if (metric.rating === 'good') return 'good'; + if (metric.rating === 'needs-improvement') return 'needs-improvement'; + return 'poor'; +} \ No newline at end of file