mirror of
https://github.com/massbug/judge4c.git
synced 2026-10-15 15:17:31 +00:00
测试
This commit is contained in:
@@ -11,4 +11,4 @@ export default function ProblemsetLayout({ children }: ProblemsetLayoutProps) {
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { AppSidebar } from "@/components/app-sidebar"
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/components/ui/breadcrumb"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import {
|
||||
SidebarInset,
|
||||
SidebarProvider,
|
||||
SidebarTrigger,
|
||||
} from "@/components/ui/sidebar"
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<AppSidebar />
|
||||
<SidebarInset>
|
||||
<header className="flex h-16 shrink-0 items-center gap-2">
|
||||
<div className="flex items-center gap-2 px-4">
|
||||
<SidebarTrigger className="-ml-1" />
|
||||
<Separator orientation="vertical" className="mr-2 h-4" />
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem className="hidden md:block">
|
||||
<BreadcrumbLink href="#">
|
||||
Building Your Application
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator className="hidden md:block" />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Data Fetching</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
</header>
|
||||
<div className="flex flex-1 flex-col gap-4 p-4 pt-0">
|
||||
<div className="grid auto-rows-min gap-4 md:grid-cols-3">
|
||||
<div className="aspect-video rounded-xl bg-muted/50" />
|
||||
<div className="aspect-video rounded-xl bg-muted/50" />
|
||||
<div className="aspect-video rounded-xl bg-muted/50" />
|
||||
</div>
|
||||
<div className="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min" />
|
||||
</div>
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
)
|
||||
}
|
||||
+26
-15
@@ -33,13 +33,13 @@
|
||||
--chart-5: 213 16% 16%;
|
||||
--radius: 0.5rem;
|
||||
--sidebar-background: 0 0% 98%;
|
||||
--sidebar-foreground: 213 13% 6%;
|
||||
--sidebar-primary: 213 13% 16%;
|
||||
--sidebar-primary-foreground: 213 13% 76%;
|
||||
--sidebar-accent: 0 0% 85%;
|
||||
--sidebar-accent-foreground: 0 0% 25%;
|
||||
--sidebar-border: 0 0% 95%;
|
||||
--sidebar-ring: 213 13% 16%;
|
||||
--sidebar-foreground: 240 5.3% 26.1%;
|
||||
--sidebar-primary: 240 5.9% 10%;
|
||||
--sidebar-primary-foreground: 0 0% 98%;
|
||||
--sidebar-accent: 240 4.8% 95.9%;
|
||||
--sidebar-accent-foreground: 240 5.9% 10%;
|
||||
--sidebar-border: 220 13% 91%;
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
@@ -67,14 +67,14 @@
|
||||
--chart-3: 216 28% 22%;
|
||||
--chart-4: 210 7% 28%;
|
||||
--chart-5: 210 20% 82%;
|
||||
--sidebar-background: 216 28% 5%;
|
||||
--sidebar-foreground: 210 17% 92%;
|
||||
--sidebar-primary: 210 17% 82%;
|
||||
--sidebar-primary-foreground: 210 17% 22%;
|
||||
--sidebar-accent: 216 28% 22%;
|
||||
--sidebar-accent-foreground: 216 28% 82%;
|
||||
--sidebar-border: 216 18% 12%;
|
||||
--sidebar-ring: 210 17% 82%;
|
||||
--sidebar-background: 240 5.9% 10%;
|
||||
--sidebar-foreground: 240 4.8% 95.9%;
|
||||
--sidebar-primary: 224.3 76.3% 48%;
|
||||
--sidebar-primary-foreground: 0 0% 100%;
|
||||
--sidebar-accent: 240 3.7% 15.9%;
|
||||
--sidebar-accent-foreground: 240 4.8% 95.9%;
|
||||
--sidebar-border: 240 3.7% 15.9%;
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,3 +119,14 @@ code[data-theme*=" "] span {
|
||||
color: var(--shiki-dark);
|
||||
background-color: var(--shiki-dark-bg);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
"use client"
|
||||
import { useSession } from "next-auth/react";
|
||||
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,
|
||||
House,
|
||||
PieChart,
|
||||
Settings2,
|
||||
} from "lucide-react";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
// 如果 adminData.teams 没有在别处定义,请取消注释下面的代码并提供实际值
|
||||
/*
|
||||
const teams = [
|
||||
// 在这里放置你的团队数据
|
||||
];
|
||||
*/
|
||||
|
||||
|
||||
const adminData = {
|
||||
// teams: [
|
||||
// {
|
||||
// name: "Admin Team",
|
||||
// logo: GalleryVerticalEnd,
|
||||
// plan: "Enterprise",
|
||||
// },
|
||||
// ],
|
||||
navMain: [
|
||||
{
|
||||
title: "OverView",
|
||||
url: "/",
|
||||
icon: House,
|
||||
},
|
||||
{
|
||||
title: "Dashboard",
|
||||
url: "/admin",
|
||||
icon: Settings2,
|
||||
items: [
|
||||
{
|
||||
title: "User",
|
||||
url: "/admin/users",
|
||||
},
|
||||
{
|
||||
title: "Teacher",
|
||||
url: "/admin/problems",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
projects: [
|
||||
{
|
||||
name: "System Monitoring",
|
||||
url: "/admin/monitoring",
|
||||
icon: PieChart,
|
||||
},
|
||||
{
|
||||
name: "Admin Tools",
|
||||
url: "/admin/tools",
|
||||
icon: Command,
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
export const AdminSidebar = ({ ...props }: React.ComponentProps<typeof Sidebar>) => {
|
||||
const { data: session } = useSession();
|
||||
const [userAvatar, setUserAvatar] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchUserAvatar = async () => {
|
||||
if (session?.user?.email) {
|
||||
const prisma = new PrismaClient();
|
||||
try {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email: session.user.email },
|
||||
select: { image: true }
|
||||
});
|
||||
setUserAvatar(user?.image || "");
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch user avatar:", error);
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fetchUserAvatar();
|
||||
}, [session?.user?.email]);
|
||||
|
||||
|
||||
|
||||
const user = {
|
||||
name: session?.user?.name || "Admin",
|
||||
email: session?.user?.email || "admin@example.com",
|
||||
avatar: userAvatar
|
||||
};
|
||||
|
||||
return (
|
||||
<Sidebar collapsible="icon" {...props}>
|
||||
{/*<SidebarHeader>*/}
|
||||
{/* <TeamSwitcher teams={adminData.teams} />*/}
|
||||
{/*</SidebarHeader>*/}
|
||||
<SidebarContent>
|
||||
<NavMain items={adminData.navMain} />
|
||||
<NavProjects projects={adminData.projects} />
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
<NavUser user={user} />
|
||||
</SidebarFooter>
|
||||
<SidebarRail />
|
||||
</Sidebar>
|
||||
);
|
||||
};
|
||||
@@ -1,89 +1,107 @@
|
||||
"use client";
|
||||
|
||||
"use client"
|
||||
import { siteConfig } from "@/config/site"
|
||||
import * as React from "react"
|
||||
import {
|
||||
AudioWaveform,
|
||||
Bot,
|
||||
BookOpen,
|
||||
Command,
|
||||
Frame,
|
||||
GalleryVerticalEnd,
|
||||
LifeBuoy,
|
||||
Map,
|
||||
PieChart,
|
||||
Send,
|
||||
Settings2,
|
||||
SquareTerminal,
|
||||
} from "lucide-react";
|
||||
import * as React from "react";
|
||||
} from "lucide-react"
|
||||
|
||||
import { NavMain } from "@/components/nav-main"
|
||||
import { NavProjects } from "@/components/nav-projects"
|
||||
import { NavSecondary } from "@/components/nav-secondary"
|
||||
import { NavUser } from "@/components/nav-user"
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarHeader,
|
||||
SidebarRail,
|
||||
} from "@/components/ui/sidebar";
|
||||
import { NavMain } from "@/components/nav-main";
|
||||
import { NavProjects } from "@/components/nav-projects";
|
||||
import { TeamSwitcher } from "@/components/team-switcher";
|
||||
import { NavUser, type NavUserProps } from "@/components/nav-user";
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/components/ui/sidebar"
|
||||
|
||||
const data = {
|
||||
teams: [
|
||||
{
|
||||
name: "Acme Inc",
|
||||
logo: GalleryVerticalEnd,
|
||||
plan: "Enterprise",
|
||||
},
|
||||
{
|
||||
name: "Acme Corp.",
|
||||
logo: AudioWaveform,
|
||||
plan: "Startup",
|
||||
},
|
||||
{
|
||||
name: "Evil Corp.",
|
||||
logo: Command,
|
||||
plan: "Free",
|
||||
},
|
||||
],
|
||||
user: {
|
||||
name: "shadcn",
|
||||
email: "m@example.com",
|
||||
avatar: "/avatars/shadcn.jpg",
|
||||
},
|
||||
navMain: [
|
||||
{
|
||||
title: "Problemset",
|
||||
url: "/dashboard/problemset",
|
||||
title: "Dashboard",
|
||||
url: "#",
|
||||
icon: SquareTerminal,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
title: "Models",
|
||||
url: "#",
|
||||
icon: Bot,
|
||||
items: [
|
||||
{
|
||||
title: "Genesis",
|
||||
title: "Home",
|
||||
url: "/",
|
||||
},
|
||||
{
|
||||
title: "Personal interface",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Explorer",
|
||||
title: "Problems",
|
||||
url: "/problemset",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
title: "Done Topics",
|
||||
url: "#",
|
||||
icon: BookOpen,
|
||||
items: [
|
||||
{
|
||||
title: "All Coding",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Quantum",
|
||||
title: "Correct Codingset",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Wrong Codingset",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
url: "/dashboard/settings",
|
||||
url: "#",
|
||||
icon: Settings2,
|
||||
items: [
|
||||
{
|
||||
title: "General",
|
||||
url: "/general",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Language Server",
|
||||
url: "/language-server",
|
||||
title: "Language",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
navSecondary: [
|
||||
{
|
||||
title: "Support",
|
||||
url: "/",
|
||||
icon: LifeBuoy,
|
||||
},
|
||||
{
|
||||
title: "Feedback",
|
||||
url: siteConfig.url.repo.github,
|
||||
icon: Send,
|
||||
},
|
||||
],
|
||||
projects: [
|
||||
{
|
||||
name: "Design Engineering",
|
||||
@@ -101,29 +119,36 @@ const data = {
|
||||
icon: Map,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
interface AppSidebarProps extends React.ComponentProps<typeof Sidebar> {
|
||||
user: NavUserProps["user"];
|
||||
}
|
||||
|
||||
export function AppSidebar({
|
||||
user,
|
||||
...props
|
||||
}: AppSidebarProps) {
|
||||
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
return (
|
||||
<Sidebar collapsible="icon" {...props}>
|
||||
<Sidebar variant="inset" {...props}>
|
||||
<SidebarHeader>
|
||||
<TeamSwitcher teams={data.teams} />
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton size="lg" asChild>
|
||||
<a href="#">
|
||||
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
|
||||
<Command className="size-4" />
|
||||
</div>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-semibold">Judge4c</span>
|
||||
<span className="truncate text-xs">Programming Learning</span>
|
||||
</div>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<NavMain items={data.navMain} />
|
||||
<NavProjects projects={data.projects} />
|
||||
<NavSecondary items={data.navSecondary} className="mt-auto" />
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
<NavUser user={user} />
|
||||
<NavUser user={data.user} />
|
||||
</SidebarFooter>
|
||||
<SidebarRail />
|
||||
</Sidebar>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
+52
-57
@@ -1,83 +1,78 @@
|
||||
"use client";
|
||||
"use client"
|
||||
|
||||
import { ChevronRight, type LucideIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/components/ui/collapsible"
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupLabel,
|
||||
SidebarMenu,
|
||||
SidebarMenuAction,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarMenuSub,
|
||||
SidebarMenuSubButton,
|
||||
SidebarMenuSubItem,
|
||||
} from "@/components/ui/sidebar";
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/components/ui/collapsible";
|
||||
import { ChevronRight, type LucideIcon } from "lucide-react";
|
||||
} from "@/components/ui/sidebar"
|
||||
|
||||
export interface NavMainProps {
|
||||
export function NavMain({
|
||||
items,
|
||||
}: {
|
||||
items: {
|
||||
title: string;
|
||||
url: string;
|
||||
icon?: LucideIcon;
|
||||
isActive?: boolean;
|
||||
title: string
|
||||
url: string
|
||||
icon: LucideIcon
|
||||
isActive?: boolean
|
||||
items?: {
|
||||
title: string;
|
||||
url: string;
|
||||
}[];
|
||||
}[];
|
||||
}
|
||||
|
||||
export function NavMain({ items }: NavMainProps) {
|
||||
title: string
|
||||
url: string
|
||||
}[]
|
||||
}[]
|
||||
}) {
|
||||
return (
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Platform</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{items.map((item) =>
|
||||
!item.items ? (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
{items.map((item) => (
|
||||
<Collapsible key={item.title} asChild defaultOpen={item.isActive}>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild tooltip={item.title}>
|
||||
<a href={item.url}>
|
||||
{item.icon && <item.icon />}
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
{item.items?.length ? (
|
||||
<>
|
||||
<CollapsibleTrigger asChild>
|
||||
<SidebarMenuAction className="data-[state=open]:rotate-90">
|
||||
<ChevronRight />
|
||||
<span className="sr-only">Toggle</span>
|
||||
</SidebarMenuAction>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub>
|
||||
{item.items?.map((subItem) => (
|
||||
<SidebarMenuSubItem key={subItem.title}>
|
||||
<SidebarMenuSubButton asChild>
|
||||
<a href={subItem.url}>
|
||||
<span>{subItem.title}</span>
|
||||
</a>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
))}
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
</>
|
||||
) : null}
|
||||
</SidebarMenuItem>
|
||||
) : (
|
||||
<Collapsible
|
||||
key={item.title}
|
||||
asChild
|
||||
defaultOpen={item.isActive}
|
||||
className="group/collapsible"
|
||||
>
|
||||
<SidebarMenuItem>
|
||||
<CollapsibleTrigger asChild>
|
||||
<SidebarMenuButton tooltip={item.title}>
|
||||
{item.icon && <item.icon />}
|
||||
<span>{item.title}</span>
|
||||
<ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
||||
</SidebarMenuButton>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub>
|
||||
{item.items.map((subItem) => (
|
||||
<SidebarMenuSubItem key={subItem.title}>
|
||||
<SidebarMenuSubButton asChild>
|
||||
<a href={`${item.url}${subItem.url}`}>
|
||||
<span>{subItem.title}</span>
|
||||
</a>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
))}
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
</SidebarMenuItem>
|
||||
</Collapsible>
|
||||
)
|
||||
)}
|
||||
</Collapsible>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import {
|
||||
Folder,
|
||||
Forward,
|
||||
MoreHorizontal,
|
||||
Share,
|
||||
Trash2,
|
||||
type LucideIcon,
|
||||
} from "lucide-react"
|
||||
@@ -38,7 +38,7 @@ export function NavProjects({
|
||||
|
||||
return (
|
||||
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
|
||||
<SidebarGroupLabel>Projects</SidebarGroupLabel>
|
||||
<SidebarGroupLabel>Recent programming topics</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{projects.map((item) => (
|
||||
<SidebarMenuItem key={item.name}>
|
||||
@@ -56,7 +56,7 @@ export function NavProjects({
|
||||
</SidebarMenuAction>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-48 rounded-lg"
|
||||
className="w-48"
|
||||
side={isMobile ? "bottom" : "right"}
|
||||
align={isMobile ? "end" : "start"}
|
||||
>
|
||||
@@ -65,7 +65,7 @@ export function NavProjects({
|
||||
<span>View Project</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Forward className="text-muted-foreground" />
|
||||
<Share className="text-muted-foreground" />
|
||||
<span>Share Project</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
@@ -78,8 +78,8 @@ export function NavProjects({
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton className="text-sidebar-foreground/70">
|
||||
<MoreHorizontal className="text-sidebar-foreground/70" />
|
||||
<SidebarMenuButton>
|
||||
<MoreHorizontal />
|
||||
<span>More</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import * as React from "react"
|
||||
import { type LucideIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/components/ui/sidebar"
|
||||
|
||||
export function NavSecondary({
|
||||
items,
|
||||
...props
|
||||
}: {
|
||||
items: {
|
||||
title: string
|
||||
url: string
|
||||
icon: LucideIcon
|
||||
}[]
|
||||
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
|
||||
return (
|
||||
<SidebarGroup {...props}>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
<SidebarMenuButton asChild size="sm">
|
||||
<a href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
)
|
||||
}
|
||||
+34
-27
@@ -1,19 +1,20 @@
|
||||
"use client";
|
||||
"use client"
|
||||
|
||||
import {
|
||||
BadgeCheck,
|
||||
Bell,
|
||||
ChevronsUpDown,
|
||||
CreditCard,
|
||||
UserPen,
|
||||
LogOut,
|
||||
Sparkles,
|
||||
} from "lucide-react";
|
||||
} from "lucide-react"
|
||||
import { useRouter } from "next/navigation"
|
||||
|
||||
import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
useSidebar,
|
||||
} from "@/components/ui/sidebar";
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/components/ui/avatar"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -22,21 +23,25 @@ import {
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
|
||||
export interface NavUserProps {
|
||||
user: {
|
||||
name: string;
|
||||
email: string;
|
||||
avatar: string;
|
||||
};
|
||||
}
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
useSidebar,
|
||||
} from "@/components/ui/sidebar"
|
||||
|
||||
export function NavUser({
|
||||
user,
|
||||
}: NavUserProps) {
|
||||
const { isMobile } = useSidebar();
|
||||
}: {
|
||||
user: {
|
||||
name: string
|
||||
email: string
|
||||
avatar: string
|
||||
}
|
||||
}) {
|
||||
const { isMobile } = useSidebar()
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<SidebarMenu>
|
||||
@@ -80,7 +85,7 @@ export function NavUser({
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<Sparkles />
|
||||
Upgrade to Pro
|
||||
Update
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
@@ -89,17 +94,19 @@ export function NavUser({
|
||||
<BadgeCheck />
|
||||
Account
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<CreditCard />
|
||||
Billing
|
||||
<DropdownMenuItem onClick={() => router.push("/sign-in")}>
|
||||
<UserPen />
|
||||
Switch User
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<DropdownMenuItem >
|
||||
<Bell />
|
||||
Notifications
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => {
|
||||
router.replace("/");
|
||||
}}>
|
||||
<LogOut />
|
||||
Log out
|
||||
</DropdownMenuItem>
|
||||
@@ -107,5 +114,5 @@ export function NavUser({
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// 简单的环形图组件,使用 SVG 实现
|
||||
import React from "react";
|
||||
|
||||
interface DonutChartProps {
|
||||
percent: number; // 完成比例 0-100
|
||||
size?: number; // 图表直径
|
||||
strokeWidth?: number; // 圆环宽度
|
||||
color?: string; // 完成部分颜色
|
||||
bgColor?: string; // 未完成部分颜色
|
||||
}
|
||||
|
||||
export function DonutChart({
|
||||
percent,
|
||||
size = 120,
|
||||
strokeWidth = 16,
|
||||
color = "#3b82f6",
|
||||
bgColor = "#e5e7eb",
|
||||
}: DonutChartProps) {
|
||||
const radius = (size - strokeWidth) / 2;
|
||||
const circumference = 2 * Math.PI * radius;
|
||||
const offset = circumference * (1 - percent / 100);
|
||||
|
||||
return (
|
||||
<svg width={size} height={size}>
|
||||
<circle
|
||||
cx={size / 2}
|
||||
cy={size / 2}
|
||||
r={radius}
|
||||
fill="none"
|
||||
stroke={bgColor}
|
||||
strokeWidth={strokeWidth}
|
||||
/>
|
||||
<circle
|
||||
cx={size / 2}
|
||||
cy={size / 2}
|
||||
r={radius}
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeWidth={strokeWidth}
|
||||
strokeDasharray={circumference}
|
||||
strokeDashoffset={offset}
|
||||
strokeLinecap="round"
|
||||
transform={`rotate(-90 ${size / 2} ${size / 2})`}
|
||||
/>
|
||||
<text
|
||||
x="50%"
|
||||
y="50%"
|
||||
textAnchor="middle"
|
||||
dominantBaseline="central"
|
||||
fontSize={size / 5}
|
||||
fontWeight="bold"
|
||||
fill="#222"
|
||||
>
|
||||
{percent}%
|
||||
</text>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user