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>
This commit is contained in:
@@ -1,15 +1,16 @@
|
||||
'use client'
|
||||
"use client"
|
||||
|
||||
import Link from 'next/link'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { Home, Database, Users, Bot } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { LayoutDashboard, Database, Tags, Brain, Monitor } from "lucide-react"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const NAV_ITEMS = [
|
||||
{ href: '/', label: '首页', icon: Home },
|
||||
{ href: '/data-platform', label: '数据中台', icon: Database },
|
||||
{ href: '/user-portrait', label: '画像', icon: Users },
|
||||
{ href: '/ai-assistant', label: 'AI智能助手', icon: Bot },
|
||||
{ href: "/", label: "概览", icon: LayoutDashboard },
|
||||
{ href: "/data-governance", label: "数据治理", icon: Database },
|
||||
{ href: "/tag-portrait", label: "标签画像", icon: Tags },
|
||||
{ href: "/ai-insight", label: "AI洞察", icon: Brain },
|
||||
{ href: "/monitoring", label: "监控", icon: Monitor },
|
||||
] as const
|
||||
|
||||
export default function BottomNav() {
|
||||
@@ -17,20 +18,20 @@ export default function BottomNav() {
|
||||
|
||||
return (
|
||||
<nav className="fixed bottom-0 left-0 right-0 z-40 border-t bg-white/95 backdrop-blur md:hidden">
|
||||
<ul className="grid grid-cols-4">
|
||||
<ul className="grid grid-cols-5">
|
||||
{NAV_ITEMS.map(({ href, label, icon: Icon }) => {
|
||||
const active = pathname === href || pathname.startsWith(`${href}/`)
|
||||
const active = pathname === href || (href !== "/" && pathname.startsWith(`${href}/`))
|
||||
return (
|
||||
<li key={href}>
|
||||
<Link
|
||||
href={href}
|
||||
aria-current={active ? 'page' : undefined}
|
||||
aria-current={active ? "page" : undefined}
|
||||
className={cn(
|
||||
'flex flex-col items-center justify-center gap-1 py-2 text-xs',
|
||||
active ? 'text-gray-900' : 'text-gray-500',
|
||||
"flex flex-col items-center justify-center gap-1 py-2 text-xs",
|
||||
active ? "text-blue-600" : "text-gray-500",
|
||||
)}
|
||||
>
|
||||
<Icon className={cn('h-5 w-5', active && 'fill-gray-900')} />
|
||||
<Icon className={cn("h-5 w-5", active && "text-blue-600")} />
|
||||
<span>{label}</span>
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
@@ -1,15 +1,28 @@
|
||||
"use client"
|
||||
|
||||
import { X } from "lucide-react"
|
||||
import { X, LayoutDashboard, Database, Tags, LineChart, Brain, Package, Monitor, ChevronRight } from "lucide-react"
|
||||
import { cn } from "@/lib/utils"
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
|
||||
interface MobileSidebarProps {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const NAV_ITEMS = [
|
||||
{ href: "/", label: "概览", icon: LayoutDashboard },
|
||||
{ href: "/data-governance", label: "数据治理", icon: Database },
|
||||
{ href: "/tag-portrait", label: "标签画像", icon: Tags },
|
||||
{ href: "/value-model", label: "价值模型", icon: LineChart },
|
||||
{ href: "/ai-insight", label: "AI洞察", icon: Brain },
|
||||
{ href: "/data-asset", label: "数据资产", icon: Package },
|
||||
{ href: "/monitoring", label: "系统监控", icon: Monitor },
|
||||
]
|
||||
|
||||
export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
|
||||
const pathname = usePathname()
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -25,26 +38,37 @@ export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
|
||||
<aside className="relative ml-auto h-full w-64 bg-white shadow-lg">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-4 border-b">
|
||||
<h2 className="text-lg font-semibold">导航</h2>
|
||||
<div>
|
||||
<h2 className="text-lg font-bold text-gray-900">神射手</h2>
|
||||
<p className="text-xs text-gray-500">私域银行数据中台</p>
|
||||
</div>
|
||||
<button onClick={onClose} aria-label="Close menu" className="rounded p-1 hover:bg-gray-100">
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Navigation links — keep in sync with Sidebar.tsx */}
|
||||
{/* Navigation links */}
|
||||
<nav className="flex flex-col gap-1 p-4">
|
||||
<Link href="/" className="rounded px-3 py-2 text-sm hover:bg-gray-100" onClick={onClose}>
|
||||
数据概览
|
||||
</Link>
|
||||
<Link href="/data-platform" className="rounded px-3 py-2 text-sm hover:bg-gray-100" onClick={onClose}>
|
||||
数据中台
|
||||
</Link>
|
||||
<Link href="/user-portrait" className="rounded px-3 py-2 text-sm hover:bg-gray-100" onClick={onClose}>
|
||||
用户画像
|
||||
</Link>
|
||||
<Link href="/ai-assistant" className="rounded px-3 py-2 text-sm hover:bg-gray-100" onClick={onClose}>
|
||||
AI 智能助手
|
||||
</Link>
|
||||
{NAV_ITEMS.map(({ href, label, icon: Icon }) => {
|
||||
const active = pathname === href || (href !== "/" && pathname.startsWith(href))
|
||||
return (
|
||||
<Link
|
||||
key={href}
|
||||
href={href}
|
||||
className={cn(
|
||||
"flex items-center justify-between rounded-lg px-3 py-3 text-sm transition-colors",
|
||||
active ? "bg-blue-50 text-blue-600" : "text-gray-600 hover:bg-gray-100",
|
||||
)}
|
||||
onClick={onClose}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<Icon className="h-5 w-5" />
|
||||
<span>{label}</span>
|
||||
</div>
|
||||
<ChevronRight className="h-4 w-4 text-gray-400" />
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
@@ -1,46 +1,208 @@
|
||||
'use client'
|
||||
"use client"
|
||||
|
||||
import Link from 'next/link'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { Home, Database, Users, Bot } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
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: Home },
|
||||
{ href: '/data-platform', label: '数据中台', icon: Database },
|
||||
{ href: '/user-portrait', label: '画像', icon: Users },
|
||||
{ href: '/ai-assistant', label: 'AI智能助手', icon: Bot },
|
||||
// 第一部分:数据概览
|
||||
{
|
||||
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
|
||||
|
||||
export default function Sidebar() {
|
||||
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 (
|
||||
<aside className="hidden md:block w-56 shrink-0 border-r bg-white">
|
||||
<nav className="p-3">
|
||||
<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(({ href, label, icon: Icon }) => {
|
||||
const active = pathname === href || pathname.startsWith(`${href}/`)
|
||||
return (
|
||||
<li key={href}>
|
||||
<Link
|
||||
href={href}
|
||||
aria-current={active ? 'page' : undefined}
|
||||
className={cn(
|
||||
'flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors',
|
||||
active
|
||||
? 'bg-gray-900 text-white'
|
||||
: 'text-gray-700 hover:bg-gray-100',
|
||||
)}
|
||||
>
|
||||
<Icon className="h-4 w-4" />
|
||||
<span>{label}</span>
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
{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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user