From 6df1f643760245da17bdb86009d7135d67937b4b Mon Sep 17 00:00:00 2001 From: liguang <1590686939@qq.com> Date: Tue, 17 Jun 2025 13:44:42 +0800 Subject: [PATCH] =?UTF-8?q?refactor(layout):=20=E9=87=8D=E6=9E=84=E4=BE=A7?= =?UTF-8?q?=E8=BE=B9=E6=A0=8F=E5=92=8C=E7=94=A8=E6=88=B7=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新了 AppSidebar、NavMain 和 NavUser 组件的结构和样式 - 添加了新的导航项和图标 - 调整了用户信息的展示方式 - 优化了侧边栏的布局和样式 --- bun.lock | 20 +- package.json | 8 +- src/app/dashboard/data.json | 122 +++++ src/app/dashboard/page.tsx | 17 + src/app/globals.css | 41 +- src/components/app-sidebar.tsx | 212 +++++---- src/components/chart-area-interactive.tsx | 292 ++++++++++++ src/components/data-table.tsx | 548 ++++++++++++++++++++++ src/components/nav-documents.tsx | 85 ++++ src/components/nav-main.tsx | 111 ++--- src/components/nav-secondary.tsx | 42 ++ src/components/nav-user.tsx | 85 ++-- src/components/section-cards.tsx | 101 ++++ src/components/site-header.tsx | 11 + tailwind.config.ts | 164 +++---- 15 files changed, 1567 insertions(+), 292 deletions(-) create mode 100644 src/app/dashboard/data.json create mode 100644 src/app/dashboard/page.tsx create mode 100644 src/components/chart-area-interactive.tsx create mode 100644 src/components/data-table.tsx create mode 100644 src/components/nav-documents.tsx create mode 100644 src/components/nav-secondary.tsx create mode 100644 src/components/section-cards.tsx create mode 100644 src/components/site-header.tsx diff --git a/bun.lock b/bun.lock index 8f61547..2a048e0 100644 --- a/bun.lock +++ b/bun.lock @@ -8,6 +8,10 @@ "@ai-sdk/openai": "^1.3.0", "@ai-sdk/react": "^1.2.0", "@auth/prisma-adapter": "^2.8.0", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@fontsource/fira-code": "^5.1.1", "@hookform/resolvers": "^5.1.1", "@monaco-editor/react": "^4.7.0", @@ -38,7 +42,7 @@ "@radix-ui/react-toggle": "^1.1.9", "@radix-ui/react-toggle-group": "^1.1.10", "@radix-ui/react-tooltip": "^1.2.7", - "@tanstack/react-table": "^8.21.2", + "@tanstack/react-table": "^8.21.3", "@types/vscode": "^1.97.0", "ai": "^4.2.0", "bcryptjs": "^3.0.2", @@ -83,7 +87,7 @@ "vaul": "^1.1.2", "vscode-languageclient": "^9.0.1", "vscode-ws-jsonrpc": "^3.4.0", - "zod": "^3.25.64", + "zod": "^3.25.67", "zustand": "^5.0.3", }, "devDependencies": { @@ -141,6 +145,16 @@ "@date-fns/tz": ["@date-fns/tz@1.2.0", "https://registry.npmmirror.com/@date-fns/tz/-/tz-1.2.0.tgz", {}, "sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg=="], + "@dnd-kit/accessibility": ["@dnd-kit/accessibility@3.1.1", "https://registry.npmmirror.com/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw=="], + + "@dnd-kit/core": ["@dnd-kit/core@6.3.1", "https://registry.npmmirror.com/@dnd-kit/core/-/core-6.3.1.tgz", { "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ=="], + + "@dnd-kit/modifiers": ["@dnd-kit/modifiers@9.0.0", "https://registry.npmmirror.com/@dnd-kit/modifiers/-/modifiers-9.0.0.tgz", { "dependencies": { "@dnd-kit/utilities": "^3.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@dnd-kit/core": "^6.3.0", "react": ">=16.8.0" } }, "sha512-ybiLc66qRGuZoC20wdSSG6pDXFikui/dCNGthxv4Ndy8ylErY0N3KVxY2bgo7AWwIbxDmXDg3ylAFmnrjcbVvw=="], + + "@dnd-kit/sortable": ["@dnd-kit/sortable@10.0.0", "https://registry.npmmirror.com/@dnd-kit/sortable/-/sortable-10.0.0.tgz", { "dependencies": { "@dnd-kit/utilities": "^3.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@dnd-kit/core": "^6.3.0", "react": ">=16.8.0" } }, "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg=="], + + "@dnd-kit/utilities": ["@dnd-kit/utilities@3.2.2", "https://registry.npmmirror.com/@dnd-kit/utilities/-/utilities-3.2.2.tgz", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg=="], + "@emnapi/core": ["@emnapi/core@1.4.3", "https://registry.npmmirror.com/@emnapi/core/-/core-1.4.3.tgz", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" } }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="], "@emnapi/runtime": ["@emnapi/runtime@1.4.3", "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.4.3.tgz", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="], @@ -1943,7 +1957,7 @@ "yocto-queue": ["yocto-queue@0.1.0", "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - "zod": ["zod@3.25.64", "https://registry.npmmirror.com/zod/-/zod-3.25.64.tgz", {}, "sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g=="], + "zod": ["zod@3.25.67", "https://registry.npmmirror.com/zod/-/zod-3.25.67.tgz", {}, "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw=="], "zod-to-json-schema": ["zod-to-json-schema@3.24.5", "https://registry.npmmirror.com/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="], diff --git a/package.json b/package.json index b417046..99cec9b 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,10 @@ "@ai-sdk/openai": "^1.3.0", "@ai-sdk/react": "^1.2.0", "@auth/prisma-adapter": "^2.8.0", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@fontsource/fira-code": "^5.1.1", "@hookform/resolvers": "^5.1.1", "@monaco-editor/react": "^4.7.0", @@ -47,7 +51,7 @@ "@radix-ui/react-toggle": "^1.1.9", "@radix-ui/react-toggle-group": "^1.1.10", "@radix-ui/react-tooltip": "^1.2.7", - "@tanstack/react-table": "^8.21.2", + "@tanstack/react-table": "^8.21.3", "@types/vscode": "^1.97.0", "ai": "^4.2.0", "bcryptjs": "^3.0.2", @@ -92,7 +96,7 @@ "vaul": "^1.1.2", "vscode-languageclient": "^9.0.1", "vscode-ws-jsonrpc": "^3.4.0", - "zod": "^3.25.64", + "zod": "^3.25.67", "zustand": "^5.0.3" }, "devDependencies": { diff --git a/src/app/dashboard/data.json b/src/app/dashboard/data.json new file mode 100644 index 0000000..b62bde6 --- /dev/null +++ b/src/app/dashboard/data.json @@ -0,0 +1,122 @@ +[ + { + "id": "1", + "name": "张三", + "email": "zhangsan@example.com", + "createdAt": "2023-10-01T08:00:00Z" + }, + { + "id": "2", + "name": "李四", + "email": "lisi@example.com", + "createdAt": "2023-10-02T09:30:00Z" + }, + { + "id": "3", + "name": "王五", + "email": "wangwu@example.com", + "createdAt": "2023-10-03T11:45:00Z" + }, + { + "id": "4", + "name": "赵六", + "email": "zhaoliu@example.org", + "createdAt": "2023-10-04T14:00:00Z" + }, + { + "id": "5", + "name": "孙七", + "email": "sunqi@example.net", + "createdAt": "2023-10-05T16:15:00Z" + }, + { + "id": "6", + "name": "周八", + "email": "zhouba@example.org", + "createdAt": "2023-10-06T18:30:00Z" + }, + { + "id": "7", + "name": "吴九", + "email": "wujiu@example.net", + "createdAt": "2023-10-07T20:45:00Z" + }, + { + "id": "8", + "name": "郑十", + "email": "zhengshi@example.org", + "createdAt": "2023-10-08T23:00:00Z" + }, + { + "id": "9", + "name": "钱十一", + "email": "qian11@example.net", + "createdAt": "2023-10-09T01:15:00Z" + }, + { + "id": "10", + "name": "孙十二", + "email": "sun12@example.org", + "createdAt": "2023-10-10T03:30:00Z" + }, + { + "id": "11", + "name": "周十三", + "email": "zhou13@example.net", + "createdAt": "2023-10-11T05:45:00Z" + }, + { + "id": "12", + "name": "吴十四", + "email": "wushi14@example.org", + "createdAt": "2023-10-12T08:00:00Z" + }, + { + "id": "13", + "name": "郑十五", + "email": "zheng15@example.net", + "createdAt": "2023-10-13T10:15:00Z" + }, + { + "id": "14", + "name": "钱十六", + "email": "qian16@example.org", + "createdAt": "2023-10-14T12:30:00Z" + }, + { + "id": "15", + "name": "孙十七", + "email": "sun17@example.net", + "createdAt": "2023-10-15T14:45:00Z" + }, + { + "id": "16", + "name": "周十八", + "email": "zhou18@example.org", + "createdAt": "2023-10-16T17:00:00Z" + }, + { + "id": "17", + "name": "吴十九", + "email": "wujiu19@example.net", + "createdAt": "2023-10-17T19:15:00Z" + }, + { + "id": "18", + "name": "郑二十", + "email": "zheng20@example.org", + "createdAt": "2023-10-18T21:30:00Z" + }, + { + "id": "19", + "name": "周十九", + "email": "zhou19@example.org", + "createdAt": "2023-10-19T15:30:00Z" + }, + { + "id": "20", + "name": "郑二十", + "email": "zheng20@example.net", + "createdAt": "2023-10-20T17:45:00Z" + } +] diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx new file mode 100644 index 0000000..6128e58 --- /dev/null +++ b/src/app/dashboard/page.tsx @@ -0,0 +1,17 @@ +import { DataTable } from "@/components/data-table" +import { SiteHeader } from "@/components/site-header" + +import data from "./data.json" + +export default function Page() { + return ( +
+ +
+
+ +
+
+
+ ) +} diff --git a/src/app/globals.css b/src/app/globals.css index 26923bb..a257927 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -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; + } +} diff --git a/src/components/app-sidebar.tsx b/src/components/app-sidebar.tsx index 526e620..7aa16dc 100644 --- a/src/components/app-sidebar.tsx +++ b/src/components/app-sidebar.tsx @@ -1,129 +1,181 @@ -"use client"; +"use client" +import * as React from "react" import { - AudioWaveform, - Bot, - Command, - Frame, - GalleryVerticalEnd, - Map, - PieChart, - Settings2, - SquareTerminal, -} from "lucide-react"; -import * as React from "react"; + ArrowUpCircleIcon, + BarChartIcon, + CameraIcon, + ClipboardListIcon, + DatabaseIcon, + FileCodeIcon, + FileIcon, + FileTextIcon, + FolderIcon, + HelpCircleIcon, + LayoutDashboardIcon, + ListIcon, + SearchIcon, + SettingsIcon, + UsersIcon, +} from "lucide-react" + +import { NavDocuments } from "@/components/nav-documents" +import { NavMain } from "@/components/nav-main" +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", - icon: SquareTerminal, - isActive: true, + title: "Dashboard", + url: "#", + icon: LayoutDashboardIcon, }, { - title: "Models", + title: "Lifecycle", + url: "#", + icon: ListIcon, + }, + { + title: "Analytics", + url: "#", + icon: BarChartIcon, + }, + { + title: "Projects", + url: "#", + icon: FolderIcon, + }, + { + title: "Team", + url: "#", + icon: UsersIcon, + }, + ], + navClouds: [ + { + title: "Capture", + icon: CameraIcon, + isActive: true, url: "#", - icon: Bot, items: [ { - title: "Genesis", + title: "Active Proposals", url: "#", }, { - title: "Explorer", - url: "#", - }, - { - title: "Quantum", + title: "Archived", url: "#", }, ], }, + { + title: "Proposal", + icon: FileTextIcon, + url: "#", + items: [ + { + title: "Active Proposals", + url: "#", + }, + { + title: "Archived", + url: "#", + }, + ], + }, + { + title: "Prompts", + icon: FileCodeIcon, + url: "#", + items: [ + { + title: "Active Proposals", + url: "#", + }, + { + title: "Archived", + url: "#", + }, + ], + }, + ], + navSecondary: [ { title: "Settings", - url: "/dashboard/settings", - icon: Settings2, - items: [ - { - title: "General", - url: "/general", - }, - { - title: "Language Server", - url: "/language-server", - }, - ], + url: "#", + icon: SettingsIcon, + }, + { + title: "Get Help", + url: "#", + icon: HelpCircleIcon, + }, + { + title: "Search", + url: "#", + icon: SearchIcon, }, ], - projects: [ + documents: [ { - name: "Design Engineering", + name: "Data Library", url: "#", - icon: Frame, + icon: DatabaseIcon, }, { - name: "Sales & Marketing", + name: "Reports", url: "#", - icon: PieChart, + icon: ClipboardListIcon, }, { - name: "Travel", + name: "Word Assistant", url: "#", - icon: Map, + icon: FileIcon, }, ], -}; - -interface AppSidebarProps extends React.ComponentProps { - user: NavUserProps["user"]; } -export function AppSidebar({ - user, - ...props -}: AppSidebarProps) { +export function AppSidebar({ ...props }: React.ComponentProps) { return ( - + - + + + + + + Acme Inc. + + + + - + + - + - - ); + ) } diff --git a/src/components/chart-area-interactive.tsx b/src/components/chart-area-interactive.tsx new file mode 100644 index 0000000..294c421 --- /dev/null +++ b/src/components/chart-area-interactive.tsx @@ -0,0 +1,292 @@ +"use client" + +import * as React from "react" +import { Area, AreaChart, CartesianGrid, XAxis } from "recharts" + +import { useIsMobile } from "@/hooks/use-mobile" +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card" +import { + ChartConfig, + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from "@/components/ui/chart" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { + ToggleGroup, + ToggleGroupItem, +} from "@/components/ui/toggle-group" +const chartData = [ + { date: "2024-04-01", desktop: 222, mobile: 150 }, + { date: "2024-04-02", desktop: 97, mobile: 180 }, + { date: "2024-04-03", desktop: 167, mobile: 120 }, + { date: "2024-04-04", desktop: 242, mobile: 260 }, + { date: "2024-04-05", desktop: 373, mobile: 290 }, + { date: "2024-04-06", desktop: 301, mobile: 340 }, + { date: "2024-04-07", desktop: 245, mobile: 180 }, + { date: "2024-04-08", desktop: 409, mobile: 320 }, + { date: "2024-04-09", desktop: 59, mobile: 110 }, + { date: "2024-04-10", desktop: 261, mobile: 190 }, + { date: "2024-04-11", desktop: 327, mobile: 350 }, + { date: "2024-04-12", desktop: 292, mobile: 210 }, + { date: "2024-04-13", desktop: 342, mobile: 380 }, + { date: "2024-04-14", desktop: 137, mobile: 220 }, + { date: "2024-04-15", desktop: 120, mobile: 170 }, + { date: "2024-04-16", desktop: 138, mobile: 190 }, + { date: "2024-04-17", desktop: 446, mobile: 360 }, + { date: "2024-04-18", desktop: 364, mobile: 410 }, + { date: "2024-04-19", desktop: 243, mobile: 180 }, + { date: "2024-04-20", desktop: 89, mobile: 150 }, + { date: "2024-04-21", desktop: 137, mobile: 200 }, + { date: "2024-04-22", desktop: 224, mobile: 170 }, + { date: "2024-04-23", desktop: 138, mobile: 230 }, + { date: "2024-04-24", desktop: 387, mobile: 290 }, + { date: "2024-04-25", desktop: 215, mobile: 250 }, + { date: "2024-04-26", desktop: 75, mobile: 130 }, + { date: "2024-04-27", desktop: 383, mobile: 420 }, + { date: "2024-04-28", desktop: 122, mobile: 180 }, + { date: "2024-04-29", desktop: 315, mobile: 240 }, + { date: "2024-04-30", desktop: 454, mobile: 380 }, + { date: "2024-05-01", desktop: 165, mobile: 220 }, + { date: "2024-05-02", desktop: 293, mobile: 310 }, + { date: "2024-05-03", desktop: 247, mobile: 190 }, + { date: "2024-05-04", desktop: 385, mobile: 420 }, + { date: "2024-05-05", desktop: 481, mobile: 390 }, + { date: "2024-05-06", desktop: 498, mobile: 520 }, + { date: "2024-05-07", desktop: 388, mobile: 300 }, + { date: "2024-05-08", desktop: 149, mobile: 210 }, + { date: "2024-05-09", desktop: 227, mobile: 180 }, + { date: "2024-05-10", desktop: 293, mobile: 330 }, + { date: "2024-05-11", desktop: 335, mobile: 270 }, + { date: "2024-05-12", desktop: 197, mobile: 240 }, + { date: "2024-05-13", desktop: 197, mobile: 160 }, + { date: "2024-05-14", desktop: 448, mobile: 490 }, + { date: "2024-05-15", desktop: 473, mobile: 380 }, + { date: "2024-05-16", desktop: 338, mobile: 400 }, + { date: "2024-05-17", desktop: 499, mobile: 420 }, + { date: "2024-05-18", desktop: 315, mobile: 350 }, + { date: "2024-05-19", desktop: 235, mobile: 180 }, + { date: "2024-05-20", desktop: 177, mobile: 230 }, + { date: "2024-05-21", desktop: 82, mobile: 140 }, + { date: "2024-05-22", desktop: 81, mobile: 120 }, + { date: "2024-05-23", desktop: 252, mobile: 290 }, + { date: "2024-05-24", desktop: 294, mobile: 220 }, + { date: "2024-05-25", desktop: 201, mobile: 250 }, + { date: "2024-05-26", desktop: 213, mobile: 170 }, + { date: "2024-05-27", desktop: 420, mobile: 460 }, + { date: "2024-05-28", desktop: 233, mobile: 190 }, + { date: "2024-05-29", desktop: 78, mobile: 130 }, + { date: "2024-05-30", desktop: 340, mobile: 280 }, + { date: "2024-05-31", desktop: 178, mobile: 230 }, + { date: "2024-06-01", desktop: 178, mobile: 200 }, + { date: "2024-06-02", desktop: 470, mobile: 410 }, + { date: "2024-06-03", desktop: 103, mobile: 160 }, + { date: "2024-06-04", desktop: 439, mobile: 380 }, + { date: "2024-06-05", desktop: 88, mobile: 140 }, + { date: "2024-06-06", desktop: 294, mobile: 250 }, + { date: "2024-06-07", desktop: 323, mobile: 370 }, + { date: "2024-06-08", desktop: 385, mobile: 320 }, + { date: "2024-06-09", desktop: 438, mobile: 480 }, + { date: "2024-06-10", desktop: 155, mobile: 200 }, + { date: "2024-06-11", desktop: 92, mobile: 150 }, + { date: "2024-06-12", desktop: 492, mobile: 420 }, + { date: "2024-06-13", desktop: 81, mobile: 130 }, + { date: "2024-06-14", desktop: 426, mobile: 380 }, + { date: "2024-06-15", desktop: 307, mobile: 350 }, + { date: "2024-06-16", desktop: 371, mobile: 310 }, + { date: "2024-06-17", desktop: 475, mobile: 520 }, + { date: "2024-06-18", desktop: 107, mobile: 170 }, + { date: "2024-06-19", desktop: 341, mobile: 290 }, + { date: "2024-06-20", desktop: 408, mobile: 450 }, + { date: "2024-06-21", desktop: 169, mobile: 210 }, + { date: "2024-06-22", desktop: 317, mobile: 270 }, + { date: "2024-06-23", desktop: 480, mobile: 530 }, + { date: "2024-06-24", desktop: 132, mobile: 180 }, + { date: "2024-06-25", desktop: 141, mobile: 190 }, + { date: "2024-06-26", desktop: 434, mobile: 380 }, + { date: "2024-06-27", desktop: 448, mobile: 490 }, + { date: "2024-06-28", desktop: 149, mobile: 200 }, + { date: "2024-06-29", desktop: 103, mobile: 160 }, + { date: "2024-06-30", desktop: 446, mobile: 400 }, +] + +const chartConfig = { + visitors: { + label: "Visitors", + }, + desktop: { + label: "Desktop", + color: "hsl(var(--chart-1))", + }, + mobile: { + label: "Mobile", + color: "hsl(var(--chart-2))", + }, +} satisfies ChartConfig + +export function ChartAreaInteractive() { + const isMobile = useIsMobile() + const [timeRange, setTimeRange] = React.useState("30d") + + React.useEffect(() => { + if (isMobile) { + setTimeRange("7d") + } + }, [isMobile]) + + const filteredData = chartData.filter((item) => { + const date = new Date(item.date) + const referenceDate = new Date("2024-06-30") + let daysToSubtract = 90 + if (timeRange === "30d") { + daysToSubtract = 30 + } else if (timeRange === "7d") { + daysToSubtract = 7 + } + const startDate = new Date(referenceDate) + startDate.setDate(startDate.getDate() - daysToSubtract) + return date >= startDate + }) + + return ( + + + Total Visitors + + + Total for the last 3 months + + Last 3 months + +
+ + + Last 3 months + + + Last 30 days + + + Last 7 days + + + +
+
+ + + + + + + + + + + + + + + { + const date = new Date(value) + return date.toLocaleDateString("en-US", { + month: "short", + day: "numeric", + }) + }} + /> + { + return new Date(value).toLocaleDateString("en-US", { + month: "short", + day: "numeric", + }) + }} + indicator="dot" + /> + } + /> + + + + + +
+ ) +} diff --git a/src/components/data-table.tsx b/src/components/data-table.tsx new file mode 100644 index 0000000..9252210 --- /dev/null +++ b/src/components/data-table.tsx @@ -0,0 +1,548 @@ +"use client" + +import * as React from "react" +import { + DndContext, + KeyboardSensor, + MouseSensor, + TouchSensor, + closestCenter, + useSensor, + useSensors, + type DragEndEvent, + type UniqueIdentifier, +} from "@dnd-kit/core" +import { restrictToVerticalAxis } from "@dnd-kit/modifiers" +import { + SortableContext, + arrayMove, + useSortable, + verticalListSortingStrategy, +} from "@dnd-kit/sortable" +import { CSS } from "@dnd-kit/utilities" +import { + ColumnDef, + ColumnFiltersState, + Row, + SortingState, + VisibilityState, + flexRender, + getCoreRowModel, + getFacetedRowModel, + getFacetedUniqueValues, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, +} from "@tanstack/react-table" +import { + ChevronDownIcon, + ChevronLeftIcon, + ChevronRightIcon, + ChevronsLeftIcon, + ChevronsRightIcon, + ColumnsIcon, + GripVerticalIcon, + LoaderIcon, + MoreHorizontalIcon, + MoreVerticalIcon, + PlusIcon, + EyeIcon, + PencilIcon, + TrashIcon, +} from "lucide-react" +import { Area, AreaChart, CartesianGrid, XAxis } from "recharts" +import { toast } from "sonner" +import { z } from "zod" + +import { useIsMobile } from "@/hooks/use-mobile" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { + ChartConfig, + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from "@/components/ui/chart" +import { Checkbox } from "@/components/ui/checkbox" +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { Separator } from "@/components/ui/separator" +import { + Sheet, + SheetClose, + SheetContent, + SheetDescription, + SheetFooter, + SheetHeader, + SheetTitle, + SheetTrigger, +} from "@/components/ui/sheet" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table" +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from "@/components/ui/tabs" + +export const schema = z.object({ + id: z.number(), + name: z.string(), + email: z.string(), + createdAt: z.string().datetime(), +}) + +// Create a separate component for the drag handle +function DragHandle({ id }: { id: number }) { + const { attributes, listeners } = useSortable({ + id, + }) + + return ( + + ) +} + +const columns: ColumnDef>[] = [ + { + id: "select", + header: ({ table }) => ( +
+ table.toggleAllPageRowsSelected(!!value)} + aria-label="Select all" + /> +
+ ), + cell: ({ row }) => ( +
+ row.toggleSelected(!!value)} + aria-label={`Select row ${row.original.id}`} + /> +
+ ), + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: "id", + header: "ID", + cell: ({ row }) =>
{row.original.id}
, + }, + { + accessorKey: "name", + header: "姓名", + cell: ({ row }) => , + }, + { + accessorKey: "email", + header: "邮箱", + cell: ({ row }) =>
{row.original.email}
, + }, + { + accessorKey: "createdAt", + header: "创建时间", + cell: ({ row }) => ( +
+ {new Date(row.original.createdAt).toLocaleDateString()} +
+ ), + }, + { + id: "actions", + enableHiding: false, + header: () =>
操作
, + cell: ({ row }) => { + const item = row.original + return ( +
+ + + +
+ ) + }, + } +] + +function DraggableRow({ row }: { row: Row> }) { + const { transform, transition, setNodeRef, isDragging } = useSortable({ + id: row.original.id, + }) + + return ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ) +} + +function TableCellViewer({ item }: { item: z.infer }) { + const isMobile = useIsMobile() + + return ( + + + + + + + ID: {item.id} + 姓名: {item.name} + +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+
+ + + + + + +
+
+ ) +} + +export function DataTable({ + data: initialData, +}: { + data: z.infer[] +}) { + const [data, setData] = React.useState(() => initialData) + const [rowSelection, setRowSelection] = React.useState({}) + const [columnVisibility, setColumnVisibility] = React.useState({ + drag: false, + // 仅保留必要列可见性控制 + }) + const [columnFilters, setColumnFilters] = React.useState([]) + const [sorting, setSorting] = React.useState([]) + const [pagination, setPagination] = React.useState({ + pageIndex: 0, + pageSize: 10, + }) + const sortableId = React.useId() + const sensors = useSensors( + useSensor(MouseSensor, {}), + useSensor(TouchSensor, {}), + useSensor(KeyboardSensor, {}) + ) + + const dataIds = React.useMemo( + () => data?.map(({ id }) => id) || [], + [data] + ) + + const table = useReactTable({ + data, + columns, + state: { + sorting, + columnVisibility, + rowSelection, + columnFilters, + pagination, + }, + getRowId: (row) => row.id.toString(), + enableRowSelection: true, + onRowSelectionChange: setRowSelection, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + onColumnVisibilityChange: setColumnVisibility, + onPaginationChange: setPagination, + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + getFacetedRowModel: getFacetedRowModel(), + getFacetedUniqueValues: getFacetedUniqueValues(), + }) + + function handleDragEnd(event: DragEndEvent) { + const { active, over } = event + if (active && over && active.id !== over.id) { + setData((data) => { + const oldIndex = dataIds.indexOf(active.id) + const newIndex = dataIds.indexOf(over.id) + return arrayMove(data, oldIndex, newIndex) + }) + } + } + + return ( + +
+ {/* 修改视图标题区域为左侧固定内容 */} +
+ 管理员列表 +
+ +
+ +
+ + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ) + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + + {table.getRowModel().rows.map((row) => ( + + ))} + + ) : ( + + + 暂无数据 + + + )} + +
+
+
+ {/* 仅调整背景色,保持原有分页结构 */} +
+
+ {table.getFilteredSelectedRowModel().rows.length} / {table.getFilteredRowModel().rows.length} 行已选择 +
+
+
+ + +
+
+ 第 {table.getState().pagination.pageIndex + 1} 页 + 共 {table.getPageCount()} 页 +
+
+ + + + +
+
+
+
+ +
+
+ +
+
+ +
+
+
+ ) +} + +const chartData = [ + { month: "January", desktop: 186, mobile: 80 }, + { month: "February", desktop: 305, mobile: 200 }, + { month: "March", desktop: 237, mobile: 120 }, + { month: "April", desktop: 73, mobile: 190 }, + { month: "May", desktop: 209, mobile: 130 }, + { month: "June", desktop: 214, mobile: 140 }, +] + +const chartConfig = { + desktop: { + label: "Desktop", + color: "var(--primary)", + }, + mobile: { + label: "Mobile", + color: "var(--primary)", + }, +} satisfies ChartConfig diff --git a/src/components/nav-documents.tsx b/src/components/nav-documents.tsx new file mode 100644 index 0000000..6f4f182 --- /dev/null +++ b/src/components/nav-documents.tsx @@ -0,0 +1,85 @@ +"use client" + +import { + FolderIcon, + MoreHorizontalIcon, + ShareIcon, + type LucideIcon, +} from "lucide-react" + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { + SidebarGroup, + SidebarGroupLabel, + SidebarMenu, + SidebarMenuAction, + SidebarMenuButton, + SidebarMenuItem, + useSidebar, +} from "@/components/ui/sidebar" + +export function NavDocuments({ + items, +}: { + items: { + name: string + url: string + icon: LucideIcon + }[] +}) { + const { isMobile } = useSidebar() + + return ( + + Documents + + {items.map((item) => ( + + + + + {item.name} + + + + + + + More + + + + + + Open + + + + Share + + + + + ))} + + + + More + + + + + ) +} diff --git a/src/components/nav-main.tsx b/src/components/nav-main.tsx index 7706335..a7b4716 100644 --- a/src/components/nav-main.tsx +++ b/src/components/nav-main.tsx @@ -1,83 +1,58 @@ -"use client"; +"use client" +import { MailIcon, PlusCircleIcon, type LucideIcon } from "lucide-react" + +import { Button } from "@/components/ui/button" import { SidebarGroup, - SidebarGroupLabel, + SidebarGroupContent, SidebarMenu, 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; - items?: { - title: string; - url: string; - }[]; - }[]; -} - -export function NavMain({ items }: NavMainProps) { + title: string + url: string + icon?: LucideIcon + }[] +}) { return ( - Platform - - {items.map((item) => - !item.items ? ( + + + + + + Quick Create + + + + + + {items.map((item) => ( - - - {item.icon && } - {item.title} - + + {item.icon && } + {item.title} - ) : ( - - - - - {item.icon && } - {item.title} - - - - - - {item.items.map((subItem) => ( - - - - {subItem.title} - - - - ))} - - - - - ) - )} - + ))} + + - ); + ) } diff --git a/src/components/nav-secondary.tsx b/src/components/nav-secondary.tsx new file mode 100644 index 0000000..e3d707e --- /dev/null +++ b/src/components/nav-secondary.tsx @@ -0,0 +1,42 @@ +"use client" + +import * as React from "react" +import { 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) { + return ( + + + + {items.map((item) => ( + + + + + {item.title} + + + + ))} + + + + ) +} diff --git a/src/components/nav-user.tsx b/src/components/nav-user.tsx index 66190a5..2eb61f9 100644 --- a/src/components/nav-user.tsx +++ b/src/components/nav-user.tsx @@ -1,19 +1,18 @@ -"use client"; +"use client" import { - BadgeCheck, - Bell, - ChevronsUpDown, - CreditCard, - LogOut, - Sparkles, -} from "lucide-react"; + BellIcon, + CreditCardIcon, + LogOutIcon, + MoreVerticalIcon, + UserCircleIcon, +} from "lucide-react" + import { - SidebarMenu, - SidebarMenuButton, - SidebarMenuItem, - useSidebar, -} from "@/components/ui/sidebar"; + Avatar, + AvatarFallback, + AvatarImage, +} from "@/components/ui/avatar" import { DropdownMenu, DropdownMenuContent, @@ -22,21 +21,24 @@ 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() return ( @@ -47,15 +49,17 @@ export function NavUser({ size="lg" className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground" > - + CN
- {user.name} - {user.email} + {user.name} + + {user.email} +
- + CN
- {user.name} - {user.email} + {user.name} + + {user.email} +
- - Upgrade to Pro - - - - - - + Account - + Billing - + Notifications - + Log out
- ); + ) } diff --git a/src/components/section-cards.tsx b/src/components/section-cards.tsx new file mode 100644 index 0000000..579f3cc --- /dev/null +++ b/src/components/section-cards.tsx @@ -0,0 +1,101 @@ +import { TrendingDownIcon, TrendingUpIcon } from "lucide-react" + +import { Badge } from "@/components/ui/badge" +import { + Card, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card" + +export function SectionCards() { + return ( +
+ + + Total Revenue + + $1,250.00 + +
+ + + +12.5% + +
+
+ +
+ Trending up this month +
+
+ Visitors for the last 6 months +
+
+
+ + + New Customers + + 1,234 + +
+ + + -20% + +
+
+ +
+ Down 20% this period +
+
+ Acquisition needs attention +
+
+
+ + + Active Accounts + + 45,678 + +
+ + + +12.5% + +
+
+ +
+ Strong user retention +
+
Engagement exceed targets
+
+
+ + + Growth Rate + + 4.5% + +
+ + + +4.5% + +
+
+ +
+ Steady performance +
+
Meets growth projections
+
+
+
+ ) +} diff --git a/src/components/site-header.tsx b/src/components/site-header.tsx new file mode 100644 index 0000000..0801404 --- /dev/null +++ b/src/components/site-header.tsx @@ -0,0 +1,11 @@ +import { Separator } from "@/components/ui/separator" + +export function SiteHeader() { + return ( +
+
+

管理员

+
+
+ ) +} diff --git a/tailwind.config.ts b/tailwind.config.ts index 6bb95d9..25ee55d 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -12,87 +12,89 @@ export default { "./src/lib/**/*.{js,ts,jsx,tsx,mdx}", ], theme: { - extend: { - colors: { - background: "hsl(var(--background))", - foreground: "hsl(var(--foreground))", - card: { - DEFAULT: "hsl(var(--card))", - foreground: "hsl(var(--card-foreground))", - }, - popover: { - DEFAULT: "hsl(var(--popover))", - foreground: "hsl(var(--popover-foreground))", - }, - primary: { - DEFAULT: "hsl(var(--primary))", - foreground: "hsl(var(--primary-foreground))", - }, - secondary: { - DEFAULT: "hsl(var(--secondary))", - foreground: "hsl(var(--secondary-foreground))", - }, - muted: { - DEFAULT: "hsl(var(--muted))", - foreground: "hsl(var(--muted-foreground))", - }, - accent: { - DEFAULT: "hsl(var(--accent))", - foreground: "hsl(var(--accent-foreground))", - }, - destructive: { - DEFAULT: "hsl(var(--destructive))", - foreground: "hsl(var(--destructive-foreground))", - }, - border: "hsl(var(--border))", - input: "hsl(var(--input))", - ring: "hsl(var(--ring))", - chart: { - "1": "hsl(var(--chart-1))", - "2": "hsl(var(--chart-2))", - "3": "hsl(var(--chart-3))", - "4": "hsl(var(--chart-4))", - "5": "hsl(var(--chart-5))", - }, - sidebar: { - DEFAULT: "hsl(var(--sidebar-background))", - foreground: "hsl(var(--sidebar-foreground))", - primary: "hsl(var(--sidebar-primary))", - "primary-foreground": "hsl(var(--sidebar-primary-foreground))", - accent: "hsl(var(--sidebar-accent))", - "accent-foreground": "hsl(var(--sidebar-accent-foreground))", - border: "hsl(var(--sidebar-border))", - ring: "hsl(var(--sidebar-ring))", - }, - }, - borderRadius: { - lg: "var(--radius)", - md: "calc(var(--radius) - 2px)", - sm: "calc(var(--radius) - 4px)", - }, - keyframes: { - "accordion-down": { - from: { - height: "0", - }, - to: { - height: "var(--radix-accordion-content-height)", - }, - }, - "accordion-up": { - from: { - height: "var(--radix-accordion-content-height)", - }, - to: { - height: "0", - }, - }, - }, - animation: { - "accordion-down": "accordion-down 0.2s ease-out", - "accordion-up": "accordion-up 0.2s ease-out", - }, - }, + extend: { + colors: { + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))' + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))' + }, + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))' + }, + secondary: { + DEFAULT: 'hsl(var(--secondary))', + foreground: 'hsl(var(--secondary-foreground))' + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))' + }, + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))' + }, + destructive: { + DEFAULT: 'hsl(var(--destructive))', + foreground: 'hsl(var(--destructive-foreground))' + }, + border: 'hsl(var(--border))', + input: 'hsl(var(--input))', + ring: 'hsl(var(--ring))', + chart: { + '1': 'hsl(var(--chart-1))', + '2': 'hsl(var(--chart-2))', + '3': 'hsl(var(--chart-3))', + '4': 'hsl(var(--chart-4))', + '5': 'hsl(var(--chart-5))' + }, + sidebar: { + DEFAULT: 'hsl(var(--sidebar-background))', + foreground: 'hsl(var(--sidebar-foreground))', + primary: 'hsl(var(--sidebar-primary))', + 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))', + accent: 'hsl(var(--sidebar-accent))', + 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))', + border: 'hsl(var(--sidebar-border))', + ring: 'hsl(var(--sidebar-ring))', + 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))', + 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))' + } + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)' + }, + keyframes: { + 'accordion-down': { + from: { + height: '0' + }, + to: { + height: 'var(--radix-accordion-content-height)' + } + }, + 'accordion-up': { + from: { + height: 'var(--radix-accordion-content-height)' + }, + to: { + height: '0' + } + } + }, + animation: { + 'accordion-down': 'accordion-down 0.2s ease-out', + 'accordion-up': 'accordion-up 0.2s ease-out' + } + } }, plugins: [animate], } satisfies Config;