完成项目结构搭建

This commit is contained in:
2025-05-10 19:13:50 +08:00
parent 7bbcf2fac7
commit 17c065ae79
14 changed files with 151 additions and 337 deletions

View File

@@ -2,19 +2,20 @@ import { AppSidebar } from "@/components/app-sidebar"
import { import {
Breadcrumb, Breadcrumb,
BreadcrumbItem, BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList, BreadcrumbList,
BreadcrumbPage, BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb" } from "@/components/ui/breadcrumb"
import { Separator } from "@/components/ui/separator"
import { import {
SidebarInset, SidebarInset,
SidebarProvider, SidebarProvider,
SidebarTrigger, SidebarTrigger,
} from "@/components/ui/sidebar" } from "@/components/ui/sidebar"
export default function Page() { export default function ConsoleMenuLayout({
children,
}: {
children: React.ReactNode
}) {
return ( return (
<SidebarProvider> <SidebarProvider>
<AppSidebar /> <AppSidebar />
@@ -22,29 +23,23 @@ export default function Page() {
<header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12"> <header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
<div className="flex items-center gap-2 px-4"> <div className="flex items-center gap-2 px-4">
<SidebarTrigger className="-ml-1" /> <SidebarTrigger className="-ml-1" />
<Separator orientation="vertical" className="mr-2 h-4" /> <div className="w-[1px] h-4 mr-2 bg-zinc-300"></div>
<Breadcrumb> <Breadcrumb>
<BreadcrumbList> <BreadcrumbList>
<BreadcrumbItem className="hidden md:block">
<BreadcrumbLink href="#">
Building Your Application
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator className="hidden md:block" />
<BreadcrumbItem> <BreadcrumbItem>
<BreadcrumbPage>Data Fetching</BreadcrumbPage> <BreadcrumbPage>
{new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'long'
}).format(new Date())}
</BreadcrumbPage>
</BreadcrumbItem> </BreadcrumbItem>
</BreadcrumbList> </BreadcrumbList>
</Breadcrumb> </Breadcrumb>
</div> </div>
</header> </header>
<div className="flex flex-1 flex-col gap-4 p-4 pt-0"> <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"> {children}
<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> </div>
</SidebarInset> </SidebarInset>
</SidebarProvider> </SidebarProvider>

View File

@@ -0,0 +1,3 @@
export default function Page() {
return <div>console</div>
}

View File

@@ -0,0 +1,5 @@
export default function Page() {
return (
<div>storage</div>
)
}

View File

@@ -0,0 +1,5 @@
export default function Page() {
return (
<div>user</div>
)
}

View File

@@ -0,0 +1,5 @@
export default function Page() {
return (
<div>role</div>
)
}

View File

@@ -0,0 +1,5 @@
export default function Page() {
return (
<div>vserver</div>
)
}

View File

@@ -0,0 +1,5 @@
export default function Page() {
return (
<div>vspace</div>
)
}

View File

@@ -0,0 +1,7 @@
export default function Page() {
return (
<>
Blog
</>
)
}

View File

@@ -0,0 +1,7 @@
export default function Page() {
return (
<>
resource
</>
)
}

View File

@@ -2,30 +2,27 @@
import * as React from "react" import * as React from "react"
import { import {
AudioWaveform, CloudUpload,
BookOpen, Inbox,
Bot, Server,
Command,
Frame,
GalleryVerticalEnd,
Map,
PieChart,
Settings2,
SquareTerminal, SquareTerminal,
UsersRound,
} from "lucide-react" } from "lucide-react"
import { NavMain } from "@/components/nav-main" import { NavMain } from "@/components/nav-main"
import { NavProjects } from "@/components/nav-projects"
import { NavUser } from "@/components/nav-user" import { NavUser } from "@/components/nav-user"
import { TeamSwitcher } from "@/components/team-switcher"
import { import {
Sidebar, Sidebar,
SidebarContent, SidebarContent,
SidebarFooter, SidebarFooter,
SidebarHeader, SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarRail, SidebarRail,
} from "@/components/ui/sidebar" } from "@/components/ui/sidebar"
import { useRouter } from "next/navigation"
import Link from "next/link"
// This is sample data. // This is sample data.
const data = { const data = {
user: { user: {
@@ -33,125 +30,51 @@ const data = {
email: "m@example.com", email: "m@example.com",
avatar: "/avatars/shadcn.jpg", avatar: "/avatars/shadcn.jpg",
}, },
teams: [
{
name: "Acme Inc",
logo: GalleryVerticalEnd,
plan: "Enterprise",
},
{
name: "Acme Corp.",
logo: AudioWaveform,
plan: "Startup",
},
{
name: "Evil Corp.",
logo: Command,
plan: "Free",
},
],
navMain: [ navMain: [
{ {
title: "Playground", title: "网站管理",
url: "#", url: "/console/web",
icon: SquareTerminal, icon: SquareTerminal,
isActive: true,
items: [ items: [
{ {
title: "History", title: "资源",
url: "#", url: "/console/web/resource",
}, },
{ {
title: "Starred", title: "博客",
url: "#", url: "/console/web/blog",
},
{
title: "Settings",
url: "#",
}, },
], ],
}, },
{ {
title: "Models", title: "用户管理",
url: "#", url: "/console/user",
icon: Bot, icon: UsersRound,
items: [ items: [
{ {
title: "Genesis", title: "用户",
url: "#", url: "/console/user",
}, },
{ {
title: "Explorer", title: "角色/权限",
url: "#", url: "/console/user/role",
},
{
title: "Quantum",
url: "#",
}, },
], ],
}, },
{ {
title: "Documentation", title: "文件存储",
url: "#", url: "/console/storage",
icon: BookOpen, icon: CloudUpload,
items: [
{
title: "Introduction",
url: "#",
}, },
{ {
title: "Get Started", title: "虚拟云空间",
url: "#", url: "/console/vspace",
icon: Inbox,
}, },
{ {
title: "Tutorials", title: "虚拟主机",
url: "#", url: "/console/vserver",
}, icon: Server,
{
title: "Changelog",
url: "#",
},
],
},
{
title: "Settings",
url: "#",
icon: Settings2,
items: [
{
title: "General",
url: "#",
},
{
title: "Team",
url: "#",
},
{
title: "Billing",
url: "#",
},
{
title: "Limits",
url: "#",
},
],
},
],
projects: [
{
name: "Design Engineering",
url: "#",
icon: Frame,
},
{
name: "Sales & Marketing",
url: "#",
icon: PieChart,
},
{
name: "Travel",
url: "#",
icon: Map,
}, },
], ],
} }
@@ -160,11 +83,26 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
return ( return (
<Sidebar collapsible="icon" {...props}> <Sidebar collapsible="icon" {...props}>
<SidebarHeader> <SidebarHeader>
<TeamSwitcher teams={data.teams} /> <SidebarMenu>
<SidebarMenuItem>
<Link href="/console">
<SidebarMenuButton size="lg" asChild>
<div className="cursor-pointer">
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
<SquareTerminal className="size-5" />
</div>
<div className="flex flex-col gap-0.5 leading-none">
<span className="font-semibold"> - </span>
<span className="">v1.0.0</span>
</div>
</div>
</SidebarMenuButton>
</Link>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader> </SidebarHeader>
<SidebarContent> <SidebarContent>
<NavMain items={data.navMain} /> <NavMain items={data.navMain} />
<NavProjects projects={data.projects} />
</SidebarContent> </SidebarContent>
<SidebarFooter> <SidebarFooter>
<NavUser user={data.user} /> <NavUser user={data.user} />

View File

@@ -17,6 +17,9 @@ import {
SidebarMenuSubButton, SidebarMenuSubButton,
SidebarMenuSubItem, SidebarMenuSubItem,
} from "@/components/ui/sidebar" } from "@/components/ui/sidebar"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { useState, useEffect } from "react"
export function NavMain({ export function NavMain({
items, items,
@@ -34,9 +37,11 @@ export function NavMain({
}) { }) {
return ( return (
<SidebarGroup> <SidebarGroup>
<SidebarGroupLabel>Platform</SidebarGroupLabel> <SidebarGroupLabel></SidebarGroupLabel>
<SidebarMenu> <SidebarMenu>
{items.map((item) => ( {items.map((item) => (
(item.items && item.items.length > 0)
? (
<Collapsible <Collapsible
key={item.title} key={item.title}
asChild asChild
@@ -56,9 +61,9 @@ export function NavMain({
{item.items?.map((subItem) => ( {item.items?.map((subItem) => (
<SidebarMenuSubItem key={subItem.title}> <SidebarMenuSubItem key={subItem.title}>
<SidebarMenuSubButton asChild> <SidebarMenuSubButton asChild>
<a href={subItem.url}> <Link href={subItem.url}>
<span>{subItem.title}</span> <span>{subItem.title}</span>
</a> </Link>
</SidebarMenuSubButton> </SidebarMenuSubButton>
</SidebarMenuSubItem> </SidebarMenuSubItem>
))} ))}
@@ -66,6 +71,16 @@ export function NavMain({
</CollapsibleContent> </CollapsibleContent>
</SidebarMenuItem> </SidebarMenuItem>
</Collapsible> </Collapsible>
) : (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton tooltip={item.title} asChild>
<Link href={item.url}>
{item.icon && <item.icon />}
<span>{item.title}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
)
))} ))}
</SidebarMenu> </SidebarMenu>
</SidebarGroup> </SidebarGroup>

View File

@@ -1,89 +0,0 @@
"use client"
import {
Folder,
Forward,
MoreHorizontal,
Trash2,
type LucideIcon,
} from "lucide-react"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
SidebarGroup,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuAction,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
} from "@/components/ui/sidebar"
export function NavProjects({
projects,
}: {
projects: {
name: string
url: string
icon: LucideIcon
}[]
}) {
const { isMobile } = useSidebar()
return (
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
<SidebarGroupLabel>Projects</SidebarGroupLabel>
<SidebarMenu>
{projects.map((item) => (
<SidebarMenuItem key={item.name}>
<SidebarMenuButton asChild>
<a href={item.url}>
<item.icon />
<span>{item.name}</span>
</a>
</SidebarMenuButton>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuAction showOnHover>
<MoreHorizontal />
<span className="sr-only">More</span>
</SidebarMenuAction>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-48 rounded-lg"
side={isMobile ? "bottom" : "right"}
align={isMobile ? "end" : "start"}
>
<DropdownMenuItem>
<Folder className="text-muted-foreground" />
<span>View Project</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Forward className="text-muted-foreground" />
<span>Share Project</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Trash2 className="text-muted-foreground" />
<span>Delete Project</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
))}
<SidebarMenuItem>
<SidebarMenuButton className="text-sidebar-foreground/70">
<MoreHorizontal className="text-sidebar-foreground/70" />
<span>More</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroup>
)
}

View File

@@ -1,91 +0,0 @@
"use client"
import * as React from "react"
import { ChevronsUpDown, Plus } from "lucide-react"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
} from "@/components/ui/sidebar"
export function TeamSwitcher({
teams,
}: {
teams: {
name: string
logo: React.ElementType
plan: string
}[]
}) {
const { isMobile } = useSidebar()
const [activeTeam, setActiveTeam] = React.useState(teams[0])
if (!activeTeam) {
return null
}
return (
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton
size="lg"
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<div className="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
<activeTeam.logo className="size-4" />
</div>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-medium">{activeTeam.name}</span>
<span className="truncate text-xs">{activeTeam.plan}</span>
</div>
<ChevronsUpDown className="ml-auto" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
align="start"
side={isMobile ? "bottom" : "right"}
sideOffset={4}
>
<DropdownMenuLabel className="text-muted-foreground text-xs">
Teams
</DropdownMenuLabel>
{teams.map((team, index) => (
<DropdownMenuItem
key={team.name}
onClick={() => setActiveTeam(team)}
className="gap-2 p-2"
>
<div className="flex size-6 items-center justify-center rounded-md border">
<team.logo className="size-3.5 shrink-0" />
</div>
{team.name}
<DropdownMenuShortcut>{index + 1}</DropdownMenuShortcut>
</DropdownMenuItem>
))}
<DropdownMenuSeparator />
<DropdownMenuItem className="gap-2 p-2">
<div className="flex size-6 items-center justify-center rounded-md border bg-transparent">
<Plus className="size-4" />
</div>
<div className="text-muted-foreground font-medium">Add team</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
)
}

View File

@@ -2,6 +2,10 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
/* config options here */ /* config options here */
reactStrictMode: true,
devIndicators: {
position: 'bottom-right',
},
async rewrites() { async rewrites() {
return [ return [
{ {