Files
users/app/components/Sidebar.tsx
v0 b17b488f8e refactor: restructure navigation and module layout
Reorganize navigation and module structure based on new requirements.

Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
2026-01-31 04:32:36 +00:00

209 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import Link from "next/link"
import { usePathname } from "next/navigation"
import {
LayoutDashboard,
Database,
Tags,
Brain,
Package,
Monitor,
ChevronDown,
ChevronRight,
FileText,
Users,
Zap,
Target,
BarChart3,
Shield,
Bell,
Server,
MessageSquare,
Sparkles,
GitBranch,
Calendar,
Bot,
Search,
FileOutput,
Webhook,
Activity,
ScrollText,
} from "lucide-react"
import { cn } from "@/lib/utils"
import { useState } from "react"
// 按照5个HTML文档重构的导航结构
const NAV_ITEMS = [
// 第一部分:数据概览
{
href: "/",
label: "数据概览",
icon: LayoutDashboard,
description: "用户洞察仪表板"
},
// 第二部分:数据接入
{
href: "/data-ingestion",
label: "数据接入",
icon: Database,
children: [
{ href: "/data-ingestion/sources", label: "数据源管理", icon: Database },
{ href: "/data-ingestion/cleaning", label: "清洗规则", icon: Zap },
{ href: "/data-ingestion/tasks", label: "任务调度", icon: Calendar },
{ href: "/data-ingestion/lineage", label: "数据血缘", icon: GitBranch },
{ href: "/data-ingestion/quality", label: "质量监控", icon: Shield },
],
},
// 第三部分:标签画像
{
href: "/tag-portrait",
label: "标签画像",
icon: Tags,
children: [
{ href: "/tag-portrait/tags", label: "标签体系", icon: Tags },
{ href: "/tag-portrait/portrait", label: "用户画像", icon: Users },
{ href: "/tag-portrait/crowd", label: "人群圈选", icon: Target },
],
},
// 第四部分AI Agent智能系统
{
href: "/ai-agent",
label: "AI Agent",
icon: Bot,
children: [
{ href: "/ai-agent/chat", label: "智能对话", icon: MessageSquare },
{ href: "/ai-agent/smart-tag", label: "AI打标", icon: Sparkles },
{ href: "/ai-agent/data-cleaning", label: "AI清洗", icon: Zap },
{ href: "/ai-agent/nlq", label: "自然语言查询", icon: Search },
{ href: "/ai-agent/report", label: "智能报告", icon: FileText },
],
},
// 第五部分:数据输出
{
href: "/data-output",
label: "数据输出",
icon: FileOutput,
children: [
{ href: "/data-output/packages", label: "流量包", icon: Package },
{ href: "/data-output/api-market", label: "API市场", icon: Server },
{ href: "/data-output/subscription", label: "数据订阅", icon: Webhook },
],
},
// 系统监控(独立模块)
{
href: "/system",
label: "系统监控",
icon: Monitor,
children: [
{ href: "/system/health", label: "系统健康", icon: Activity },
{ href: "/system/alerts", label: "告警中心", icon: Bell },
{ href: "/system/logs", label: "操作日志", icon: ScrollText },
{ href: "/system/metrics", label: "业务指标", icon: BarChart3 },
],
},
] as const
type NavItem = (typeof NAV_ITEMS)[number]
function NavItemComponent({ item, level = 0 }: { item: NavItem; level?: number }) {
const pathname = usePathname()
const [isOpen, setIsOpen] = useState(false)
const hasChildren = "children" in item && item.children && item.children.length > 0
const isActive = pathname === item.href || (item.href !== "/" && pathname.startsWith(item.href))
// Auto expand if child is active
const childActive =
hasChildren && item.children?.some((child) => pathname === child.href || pathname.startsWith(child.href))
return (
<li>
{hasChildren ? (
<div>
<button
onClick={() => setIsOpen(!isOpen)}
className={cn(
"w-full flex items-center justify-between gap-3 rounded-xl px-4 py-3 text-sm font-medium transition-all duration-200",
isActive || childActive
? "bg-blue-50 text-blue-600"
: "text-gray-600 hover:bg-gray-100 hover:text-gray-900",
)}
>
<div className="flex items-center gap-3">
<item.icon className="h-5 w-5" />
<span>{item.label}</span>
</div>
{isOpen || childActive ? <ChevronDown className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />}
</button>
{(isOpen || childActive) && (
<ul className="mt-1 ml-4 space-y-1 border-l-2 border-gray-100 pl-4">
{item.children?.map((child) => {
const childIsActive = pathname === child.href
return (
<li key={child.href}>
<Link
href={child.href}
className={cn(
"flex items-center gap-2 rounded-lg px-3 py-2 text-sm transition-all duration-200",
childIsActive
? "bg-gradient-to-r from-blue-500 to-purple-500 text-white shadow-md"
: "text-gray-500 hover:bg-gray-100 hover:text-gray-900",
)}
>
<child.icon className="h-4 w-4" />
<span>{child.label}</span>
</Link>
</li>
)
})}
</ul>
)}
</div>
) : (
<Link
href={item.href}
aria-current={isActive ? "page" : undefined}
className={cn(
"flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition-all duration-200",
isActive
? "bg-gradient-to-r from-blue-500 to-purple-500 text-white shadow-md"
: "text-gray-600 hover:bg-gray-100 hover:text-gray-900",
)}
>
<item.icon className="h-5 w-5" />
<span>{item.label}</span>
</Link>
)}
</li>
)
}
export default function Sidebar() {
return (
<aside className="hidden md:flex flex-col w-64 shrink-0 border-r bg-white/80 backdrop-blur-md h-screen sticky top-0">
<div className="p-6">
<h2 className="text-2xl font-bold text-gray-900 mb-1"></h2>
<p className="text-sm text-gray-600"></p>
</div>
<nav className="flex-1 px-4 pb-4 overflow-y-auto">
<ul className="space-y-1">
{NAV_ITEMS.map((item) => (
<NavItemComponent key={item.href} item={item} />
))}
</ul>
</nav>
<div className="p-4 border-t border-gray-100">
<div className="flex items-center gap-3 px-2">
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-blue-500 to-purple-500 flex items-center justify-center text-white text-sm font-bold">
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-gray-900 truncate"></p>
<p className="text-xs text-gray-500 truncate">admin@archer.com</p>
</div>
</div>
</div>
</aside>
)
}