2026-01-31 04:32:36 +00:00
|
|
|
|
"use client"
|
2025-07-18 13:47:12 +00:00
|
|
|
|
|
2026-01-31 04:32:36 +00:00
|
|
|
|
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,
|
2026-02-03 11:36:53 +08:00
|
|
|
|
Globe,
|
2026-01-31 04:32:36 +00:00
|
|
|
|
} from "lucide-react"
|
|
|
|
|
|
import { cn } from "@/lib/utils"
|
|
|
|
|
|
import { useState } from "react"
|
2025-08-08 11:46:31 +00:00
|
|
|
|
|
2026-02-03 11:36:53 +08:00
|
|
|
|
// 五大核心模块导航结构(不可删除)
|
2025-08-08 11:46:31 +00:00
|
|
|
|
const NAV_ITEMS = [
|
2026-01-31 04:32:36 +00:00
|
|
|
|
// 第一部分:数据概览
|
|
|
|
|
|
{
|
|
|
|
|
|
href: "/",
|
|
|
|
|
|
label: "数据概览",
|
|
|
|
|
|
icon: LayoutDashboard,
|
2026-02-03 11:36:53 +08:00
|
|
|
|
description: "AI对话 · 数据仪表板"
|
2026-01-31 04:32:36 +00:00
|
|
|
|
},
|
|
|
|
|
|
// 第二部分:数据接入
|
|
|
|
|
|
{
|
|
|
|
|
|
href: "/data-ingestion",
|
|
|
|
|
|
label: "数据接入",
|
|
|
|
|
|
icon: Database,
|
|
|
|
|
|
children: [
|
|
|
|
|
|
{ href: "/data-ingestion/sources", label: "数据源管理", icon: Database },
|
2026-02-03 11:36:53 +08:00
|
|
|
|
{ href: "/data-ingestion/ai-engine", label: "AI标签引擎", icon: Brain },
|
2026-01-31 04:32:36 +00:00
|
|
|
|
{ href: "/data-ingestion/cleaning", label: "清洗规则", icon: Zap },
|
|
|
|
|
|
{ href: "/data-ingestion/tasks", label: "任务调度", icon: Calendar },
|
|
|
|
|
|
{ href: "/data-ingestion/lineage", label: "数据血缘", icon: GitBranch },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
// 第三部分:标签画像
|
|
|
|
|
|
{
|
|
|
|
|
|
href: "/tag-portrait",
|
|
|
|
|
|
label: "标签画像",
|
|
|
|
|
|
icon: Tags,
|
|
|
|
|
|
children: [
|
2026-02-03 11:36:53 +08:00
|
|
|
|
{ href: "/tag-portrait/tags", label: "标签管理", icon: Tags },
|
2026-01-31 04:32:36 +00:00
|
|
|
|
{ href: "/tag-portrait/portrait", label: "用户画像", icon: Users },
|
2026-02-03 11:36:53 +08:00
|
|
|
|
{ href: "/tag-portrait/crowd", label: "流量池", icon: Target },
|
2026-01-31 04:32:36 +00:00
|
|
|
|
],
|
|
|
|
|
|
},
|
2026-02-03 11:36:53 +08:00
|
|
|
|
// 第四部分:AI Agent(对接飞书/企微等外部平台)
|
2026-01-31 04:32:36 +00:00
|
|
|
|
{
|
|
|
|
|
|
href: "/ai-agent",
|
|
|
|
|
|
label: "AI Agent",
|
|
|
|
|
|
icon: Bot,
|
|
|
|
|
|
children: [
|
2026-02-03 11:36:53 +08:00
|
|
|
|
{ href: "/ai-agent/channels", label: "渠道配置", icon: Webhook },
|
2026-01-31 04:32:36 +00:00
|
|
|
|
{ href: "/ai-agent/smart-tag", label: "AI打标", icon: Sparkles },
|
|
|
|
|
|
{ href: "/ai-agent/data-cleaning", label: "AI清洗", icon: Zap },
|
|
|
|
|
|
{ href: "/ai-agent/report", label: "智能报告", icon: FileText },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
2026-02-03 11:36:53 +08:00
|
|
|
|
// 第五部分:数据市场
|
2026-01-31 04:32:36 +00:00
|
|
|
|
{
|
2026-02-03 11:36:53 +08:00
|
|
|
|
href: "/data-market",
|
|
|
|
|
|
label: "数据市场",
|
|
|
|
|
|
icon: Package,
|
2026-01-31 04:32:36 +00:00
|
|
|
|
children: [
|
2026-02-03 11:36:53 +08:00
|
|
|
|
{ href: "/data-market/packages", label: "流量包", icon: Package },
|
|
|
|
|
|
{ href: "/data-market/api", label: "API服务", icon: Server },
|
|
|
|
|
|
{ href: "/data-market/open-api", label: "开放接口", icon: Globe },
|
2026-01-31 04:32:36 +00:00
|
|
|
|
],
|
|
|
|
|
|
},
|
2026-02-03 11:36:53 +08:00
|
|
|
|
] as const
|
|
|
|
|
|
|
|
|
|
|
|
// 底部工具菜单(系统监控等)
|
|
|
|
|
|
const BOTTOM_NAV_ITEMS = [
|
2026-01-31 04:32:36 +00:00
|
|
|
|
{
|
2026-02-03 11:36:53 +08:00
|
|
|
|
href: "/monitoring/health",
|
2026-01-31 04:32:36 +00:00
|
|
|
|
label: "系统监控",
|
|
|
|
|
|
icon: Monitor,
|
|
|
|
|
|
},
|
2025-08-08 11:46:31 +00:00
|
|
|
|
] as const
|
2025-07-18 13:47:12 +00:00
|
|
|
|
|
2026-01-31 04:32:36 +00:00
|
|
|
|
type NavItem = (typeof NAV_ITEMS)[number]
|
|
|
|
|
|
|
|
|
|
|
|
function NavItemComponent({ item, level = 0 }: { item: NavItem; level?: number }) {
|
2025-07-18 13:47:12 +00:00
|
|
|
|
const pathname = usePathname()
|
2026-01-31 04:32:36 +00:00
|
|
|
|
const [isOpen, setIsOpen] = useState(false)
|
|
|
|
|
|
const hasChildren = "children" in item && item.children && item.children.length > 0
|
2026-02-03 11:36:53 +08:00
|
|
|
|
const isExactActive = pathname === item.href
|
|
|
|
|
|
const isActive = isExactActive || (item.href !== "/" && pathname.startsWith(item.href))
|
2026-01-31 04:32:36 +00:00
|
|
|
|
|
|
|
|
|
|
// Auto expand if child is active
|
|
|
|
|
|
const childActive =
|
|
|
|
|
|
hasChildren && item.children?.some((child) => pathname === child.href || pathname.startsWith(child.href))
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<li>
|
|
|
|
|
|
{hasChildren ? (
|
|
|
|
|
|
<div>
|
2026-02-03 11:36:53 +08:00
|
|
|
|
{/* 父级菜单:左侧可点击跳转,右侧按钮展开子菜单 */}
|
|
|
|
|
|
<div className={cn(
|
|
|
|
|
|
"flex items-center rounded-xl transition-all duration-200",
|
|
|
|
|
|
isExactActive
|
|
|
|
|
|
? "bg-gradient-to-r from-blue-500 to-purple-500 text-white shadow-md"
|
|
|
|
|
|
: isActive || childActive
|
2026-01-31 04:32:36 +00:00
|
|
|
|
? "bg-blue-50 text-blue-600"
|
2026-02-03 11:36:53 +08:00
|
|
|
|
: "text-gray-600 hover:bg-gray-100",
|
|
|
|
|
|
)}>
|
|
|
|
|
|
<Link
|
|
|
|
|
|
href={item.href}
|
|
|
|
|
|
className="flex-1 flex items-center gap-3 px-4 py-3 text-sm font-medium"
|
|
|
|
|
|
>
|
2026-01-31 04:32:36 +00:00
|
|
|
|
<item.icon className="h-5 w-5" />
|
|
|
|
|
|
<span>{item.label}</span>
|
2026-02-03 11:36:53 +08:00
|
|
|
|
</Link>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
|
setIsOpen(!isOpen)
|
|
|
|
|
|
}}
|
|
|
|
|
|
className={cn(
|
|
|
|
|
|
"px-3 py-3 rounded-r-xl transition-colors",
|
|
|
|
|
|
isExactActive
|
|
|
|
|
|
? "hover:bg-white/10"
|
|
|
|
|
|
: "hover:bg-gray-200"
|
|
|
|
|
|
)}
|
|
|
|
|
|
aria-label={isOpen ? "收起子菜单" : "展开子菜单"}
|
|
|
|
|
|
>
|
|
|
|
|
|
{isOpen || childActive ? <ChevronDown className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-01-31 04:32:36 +00:00
|
|
|
|
{(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>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
2025-07-18 13:47:12 +00:00
|
|
|
|
|
2026-01-31 04:32:36 +00:00
|
|
|
|
export default function Sidebar() {
|
2025-07-18 13:47:12 +00:00
|
|
|
|
return (
|
2026-01-31 04:32:36 +00:00
|
|
|
|
<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>
|
2026-02-03 11:36:53 +08:00
|
|
|
|
<p className="text-sm text-gray-600">用户资产数字化平台</p>
|
2026-01-31 04:32:36 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<nav className="flex-1 px-4 pb-4 overflow-y-auto">
|
2025-08-08 11:46:31 +00:00
|
|
|
|
<ul className="space-y-1">
|
2026-01-31 04:32:36 +00:00
|
|
|
|
{NAV_ITEMS.map((item) => (
|
|
|
|
|
|
<NavItemComponent key={item.href} item={item} />
|
|
|
|
|
|
))}
|
2025-08-08 11:46:31 +00:00
|
|
|
|
</ul>
|
|
|
|
|
|
</nav>
|
2026-01-31 04:32:36 +00:00
|
|
|
|
<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>
|
2025-08-08 11:46:31 +00:00
|
|
|
|
</aside>
|
2025-07-18 13:47:12 +00:00
|
|
|
|
)
|
|
|
|
|
|
}
|