feat: refactor data asset center for enhanced search and analytics

Refactor homepage for focused search and data display; streamline data platform; enhance user and tag management; focus AI assistant on data analysis and report generation.

Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
This commit is contained in:
v0
2025-07-25 06:42:34 +00:00
parent ecd8a48863
commit 4eed69520c
40 changed files with 6853 additions and 6111 deletions

View File

@@ -1,23 +1,8 @@
"use client"
import { useEffect, useState } from "react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
import {
Database,
LayoutDashboard,
Target,
X,
ChevronDown,
ChevronRight,
UserCheck,
Layers,
Tag,
BrainCircuit,
} from "lucide-react"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import Link from "next/link"
interface MobileSidebarProps {
isOpen: boolean
@@ -25,219 +10,43 @@ interface MobileSidebarProps {
}
export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
const pathname = usePathname()
const [expandedSections, setExpandedSections] = useState({
"user-portrait": false, // 用户画像默认不展开
})
const toggleSection = (section: string) => {
setExpandedSections((prev) => ({
...prev,
[section]: !prev[section],
}))
}
useEffect(() => {
if (isOpen) {
document.body.style.overflow = "hidden"
} else {
document.body.style.overflow = "unset"
}
return () => {
document.body.style.overflow = "unset"
}
}, [isOpen])
const navItems = [
{
title: "数据概览",
href: "/",
icon: <LayoutDashboard className="h-4 w-4" />,
primary: true,
description: "平台整体数据分析",
},
{
title: "数据中台",
href: "/data-platform",
icon: <Database className="h-4 w-4" />,
primary: true,
tag: "核心",
description: "多源数据整合中心",
},
{
title: "用户画像", // 整合用户池功能
href: "/user-portrait",
icon: <Target className="h-4 w-4" />,
primary: true,
expandable: true,
section: "user-portrait",
description: "用户数据管理与画像分析",
children: [
{
title: "用户管理", // 原用户池的用户管理
href: "/user-portrait/management",
icon: <UserCheck className="h-3 w-3" />,
description: "IMEI、手机号管理",
},
{
title: "用户分群", // 原用户池的用户分群
href: "/user-portrait/segmentation",
icon: <Layers className="h-3 w-3" />,
description: "智能用户分群",
},
{
title: "标签管理", // 原用户画像的标签管理
href: "/user-portrait/tags",
icon: <Tag className="h-3 w-3" />,
description: "用户标签体系",
},
],
},
{
title: "AI智能助手", // 新增AI智能助手
href: "/ai-assistant",
icon: <BrainCircuit className="h-4 w-4" />,
primary: true,
description: "AI数据分析与营销策略",
},
]
const getTagColor = (tag: string) => {
switch (tag) {
case "核心":
return "bg-blue-100 text-blue-700 border-blue-200"
default:
return "bg-gray-100 text-gray-700 border-gray-200"
}
}
return (
<>
{/* 背景遮罩 */}
<div
className={cn(
"fixed inset-0 bg-black/20 backdrop-blur-sm z-40 transition-opacity duration-300",
isOpen ? "opacity-100" : "opacity-0 pointer-events-none",
)}
onClick={onClose}
/>
<div
className={cn(
"fixed inset-0 z-40 transition-transform duration-300 md:hidden",
isOpen ? "translate-x-0" : "translate-x-full",
)}
aria-hidden={!isOpen}
>
{/* Backdrop */}
<div className="absolute inset-0 bg-black/40 backdrop-blur-sm" onClick={onClose} />
{/* 侧边栏 */}
<div
className={cn(
"fixed left-0 top-0 h-full w-80 glass-nav safe-area-top safe-area-left z-50 transition-transform duration-300 ease-out",
isOpen ? "translate-x-0" : "-translate-x-full",
)}
>
{/* 头部 */}
<div className="flex items-center justify-between px-4 py-3 border-b border-white/20">
<div className="flex items-center space-x-2">
<div className="w-6 h-6 rounded-lg glass-light flex items-center justify-center">
<Database className="h-4 w-4 text-blue-600" />
</div>
<h1 className="text-sm font-semibold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
</h1>
</div>
<Button variant="ghost" size="icon" onClick={onClose} className="glass-light rounded-lg h-8 w-8">
<X className="h-4 w-4" />
</Button>
{/* Drawer */}
<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>
<button onClick={onClose} aria-label="Close menu" className="rounded p-1 hover:bg-gray-100">
<X className="h-5 w-5" />
</button>
</div>
{/* 导航菜单 */}
<div className="flex-1 overflow-y-auto py-2">
<nav className="space-y-1 px-3">
{navItems.map((item) => (
<div key={item.href}>
<div
className={cn(
"flex items-center px-3 py-2 text-xs font-medium rounded-lg transition-all duration-300 group",
pathname === item.href || (item.children && pathname.startsWith(item.href))
? "glass-heavy text-blue-700 shadow-glass"
: "glass-light text-gray-700 hover:glass-heavy hover:text-blue-600",
)}
>
<Link href={item.href} onClick={onClose} className="flex items-center flex-1 min-w-0">
<div className="transition-colors duration-300 flex-shrink-0">{item.icon}</div>
<div className="flex-1 ml-2 min-w-0">
<div className="flex items-center justify-between">
<div className="flex items-center gap-1">
<span className="font-medium truncate">{item.title}</span>
</div>
{item.tag && (
<Badge
className={cn("text-xs px-1.5 py-0.5 rounded ml-1 flex-shrink-0", getTagColor(item.tag))}
>
{item.tag}
</Badge>
)}
</div>
<p className="text-xs text-gray-500 truncate mt-0.5">{item.description}</p>
</div>
</Link>
{item.expandable && (
<button
onClick={() => toggleSection(item.section!)}
className="ml-1 p-1 hover:bg-white/20 rounded transition-colors duration-200 flex-shrink-0"
>
{expandedSections[item.section!] ? (
<ChevronDown className="h-3 w-3" />
) : (
<ChevronRight className="h-3 w-3" />
)}
</button>
)}
</div>
{/* 子菜单 */}
{item.children && expandedSections[item.section!] && (
<div className="ml-4 mt-1 space-y-1">
{item.children.map((subItem) => (
<Link
key={subItem.href}
href={subItem.href}
onClick={onClose}
className={cn(
"flex items-center px-2 py-1.5 text-xs rounded transition-all duration-300",
pathname === subItem.href
? "glass-light text-blue-600 shadow-glass-sm"
: "text-gray-600 hover:glass-light hover:text-blue-500",
)}
>
<div className="mr-2 text-gray-400 transition-colors duration-300 flex-shrink-0">
{subItem.icon}
</div>
<div className="min-w-0 flex-1">
<p className="font-medium truncate">{subItem.title}</p>
<p className="text-xs text-gray-500 truncate">{subItem.description}</p>
</div>
</Link>
))}
</div>
)}
</div>
))}
</nav>
</div>
{/* 底部信息 */}
<div className="border-t border-white/20 p-3">
<div className="glass-light rounded-lg p-2">
<div className="flex items-center justify-between text-xs">
<span className="text-gray-600"></span>
<div className="flex items-center">
<div className="w-2 h-2 bg-green-500 rounded-full mr-1"></div>
<span className="text-green-600"></span>
</div>
</div>
<div className="flex items-center justify-between text-xs mt-1">
<span className="text-gray-600"></span>
<span className="text-gray-500">125,678</span>
</div>
</div>
</div>
</div>
</>
{/* Navigation links — keep in sync with Sidebar.tsx */}
<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>
</aside>
</div>
)
}

View File

@@ -4,124 +4,63 @@ import { useState } from "react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { cn } from "@/lib/utils"
import {
Database,
LayoutDashboard,
Settings,
Target,
ChevronLeft,
ChevronDown,
ChevronRight,
Tag,
UserCheck,
Layers,
BrainCircuit,
Smartphone,
} from "lucide-react"
import { Badge } from "@/components/ui/badge"
import { LayoutDashboard, Database, Users, BrainCircuit, Settings, ChevronLeft } from "lucide-react"
export default function Sidebar() {
const pathname = usePathname()
const [expanded, setExpanded] = useState(true)
const [expandedSections, setExpandedSections] = useState({
"user-portrait": true, // 用户画像默认展开
})
const toggleSidebar = () => {
setExpanded(!expanded)
}
const toggleSection = (section: string) => {
setExpandedSections((prev) => ({
...prev,
[section]: !prev[section],
}))
}
// 重新设计的导航结构 - 遵循新规则
// 简化的导航结构
const navItems = [
{
title: "数据概览",
href: "/",
icon: <LayoutDashboard className="h-5 w-5" />,
primary: true,
description: "平台整体数据分析与监控",
},
{
title: "数据中台",
href: "/data-platform",
icon: <Database className="h-5 w-5" />,
primary: true,
tag: "核心",
description: "多源数据整合与处理中心",
},
{
title: "用户画像", // 整合用户池功能
title: "用户画像",
href: "/user-portrait",
icon: <Target className="h-5 w-5" />,
primary: true,
expandable: true,
section: "user-portrait",
children: [
{
title: "用户管理", // 原用户池的用户管理
href: "/user-portrait/management",
icon: <UserCheck className="h-4 w-4" />,
},
{
title: "用户分群", // 原用户池的用户分群
href: "/user-portrait/segmentation",
icon: <Layers className="h-4 w-4" />,
},
{
title: "标签管理", // 原用户画像的标签管理
href: "/user-portrait/tags",
icon: <Tag className="h-4 w-4" />,
},
],
icon: <Users className="h-5 w-5" />,
description: "用户数据管理与画像分析",
},
{
title: "设备管理", // 提升为一级菜单 [^2]
href: "/devices",
icon: <Smartphone className="h-5 w-5" />,
primary: true,
},
{
title: "AI智能助手", // 新增AI智能助手
title: "AI智能助手",
href: "/ai-assistant",
icon: <BrainCircuit className="h-5 w-5" />,
primary: true,
description: "AI数据分析与营销策略",
},
]
const getTagColor = (tag: string) => {
switch (tag) {
case "核心":
return "glass-light text-orange-700 border-orange-200"
default:
return "glass-light text-gray-700 border-gray-200"
}
}
return (
<div
className={cn(
"flex flex-col h-screen glass-nav m-4 transition-all duration-300 ease-in-out",
"flex flex-col h-screen bg-white border-r border-gray-200 transition-all duration-300 ease-in-out",
expanded ? "w-72" : "w-20",
)}
>
{/* 头部 */}
<div className="flex items-center h-16 px-6 border-b border-white/20">
<div className="flex items-center h-16 px-6 border-b border-gray-200">
{expanded ? (
<div className="flex items-center space-x-3">
<div className="w-8 h-8 rounded-lg glass-light flex items-center justify-center">
<div className="w-8 h-8 rounded-lg bg-blue-100 flex items-center justify-center">
<Database className="h-5 w-5 text-blue-600" />
</div>
<h1 className="text-lg font-semibold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
</h1>
<h1 className="text-lg font-semibold text-gray-900"></h1>
</div>
) : (
<div className="mx-auto">
<div className="w-8 h-8 rounded-lg glass-light flex items-center justify-center">
<div className="w-8 h-8 rounded-lg bg-blue-100 flex items-center justify-center">
<Database className="h-5 w-5 text-blue-600" />
</div>
</div>
@@ -132,86 +71,49 @@ export default function Sidebar() {
<div className="flex-1 overflow-y-auto py-4">
<nav className="space-y-2 px-4">
{navItems.map((item) => (
<div key={item.href}>
<div
className={cn(
"flex items-center px-4 py-3 text-sm font-medium rounded-xl transition-all duration-300 group",
pathname === item.href || (item.children && pathname.startsWith(item.href))
? "glass-heavy text-blue-700 shadow-glass"
: "glass-light text-gray-700 hover:glass-heavy hover:text-blue-600 hover:scale-105",
)}
>
<Link href={item.href} className={cn("flex items-center flex-1", !expanded && "justify-center")}>
<div className="transition-colors duration-300">{item.icon}</div>
{expanded && (
<div className="flex-1 flex items-center justify-between ml-3">
<span className="font-medium">{item.title}</span>
{item.tag && (
<Badge className={cn("text-xs px-2 py-1 rounded-lg", getTagColor(item.tag))}>{item.tag}</Badge>
)}
</div>
)}
</Link>
{expanded && item.expandable && (
<button
onClick={() => toggleSection(item.section!)}
className="ml-2 p-1 hover:bg-white/20 rounded-lg transition-colors duration-200"
>
{expandedSections[item.section!] ? (
<ChevronDown className="h-4 w-4" />
) : (
<ChevronRight className="h-4 w-4" />
)}
</button>
)}
</div>
{/* 子菜单 */}
{expanded && item.children && expandedSections[item.section!] && (
<div className="ml-6 mt-2 space-y-1">
{item.children.map((subItem) => (
<Link
key={subItem.href}
href={subItem.href}
className={cn(
"flex items-center px-3 py-2 text-sm rounded-lg transition-all duration-300",
pathname === subItem.href
? "glass-light text-blue-600 shadow-glass-sm"
: "text-gray-600 hover:glass-light hover:text-blue-500",
)}
>
<div className="mr-3 text-gray-400 transition-colors duration-300">{subItem.icon}</div>
<span>{subItem.title}</span>
</Link>
))}
<Link
key={item.href}
href={item.href}
className={cn(
"flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200 group",
pathname === item.href
? "bg-blue-50 text-blue-700 border border-blue-200"
: "text-gray-700 hover:bg-gray-50 hover:text-blue-600",
)}
>
<div className="transition-colors duration-200">{item.icon}</div>
{expanded && (
<div className="flex-1 ml-3">
<div className="font-medium">{item.title}</div>
<div className="text-xs text-gray-500 mt-0.5">{item.description}</div>
</div>
)}
</div>
</Link>
))}
</nav>
</div>
{/* 底部设置和折叠按钮 */}
<div className="mt-auto border-t border-white/20">
<div className="mt-auto border-t border-gray-200">
<Link
href="/settings"
className={cn(
"flex items-center px-4 py-2 mx-4 my-2 text-xs font-medium rounded-lg transition-all duration-300",
"flex items-center px-4 py-3 mx-4 my-2 text-sm font-medium rounded-lg transition-all duration-200",
pathname === "/settings"
? "glass-heavy text-blue-700 shadow-glass"
: "glass-light text-gray-600 hover:glass-heavy hover:text-blue-500",
? "bg-blue-50 text-blue-700 border border-blue-200"
: "text-gray-600 hover:bg-gray-50 hover:text-blue-600",
)}
>
<div className={cn("transition-colors duration-300", !expanded && "mx-auto")}>
<div className={cn("transition-colors duration-200", !expanded && "mx-auto")}>
<Settings className="h-4 w-4" />
</div>
{expanded && <span className="ml-2"></span>}
{expanded && <span className="ml-2"></span>}
</Link>
<div className="p-4">
<button
onClick={toggleSidebar}
className="w-full flex items-center justify-center p-3 rounded-xl glass-button hover:scale-105 transition-all duration-300"
className="w-full flex items-center justify-center p-3 rounded-lg bg-gray-50 hover:bg-gray-100 transition-all duration-200"
>
<ChevronLeft
className={cn(