feat: optimize interface and database connection
Adjust MongoDB mock connector and update database structure. Enhance sidebar, data platform, and AI analysis tools. Clean up unnecessary code and update development docs. #VERCEL_SKIP Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
This commit is contained in:
@@ -343,7 +343,11 @@ export default function AIAssistantPage() {
|
||||
<div className="text-center text-muted-foreground">
|
||||
<Users className="h-16 w-16 mx-auto mb-4" />
|
||||
<p className="text-lg">请完成所有分析步骤</p>
|
||||
<Button variant="outline" className="mt-4" onClick={() => setIsUserGroupDialogOpen(true)}>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="mt-4 bg-transparent"
|
||||
onClick={() => setIsUserGroupDialogOpen(true)}
|
||||
>
|
||||
<Users className="mr-2 h-4 w-4" />
|
||||
开始设置
|
||||
</Button>
|
||||
@@ -612,7 +616,11 @@ export default function AIAssistantPage() {
|
||||
<div className="text-center text-muted-foreground">
|
||||
<Users className="h-16 w-16 mx-auto mb-4" />
|
||||
<p className="text-lg">请完成所有策略步骤</p>
|
||||
<Button variant="outline" className="mt-4" onClick={() => setIsUserGroupDialogOpen(true)}>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="mt-4 bg-transparent"
|
||||
onClick={() => setIsUserGroupDialogOpen(true)}
|
||||
>
|
||||
<Users className="mr-2 h-4 w-4" />
|
||||
开始设置
|
||||
</Button>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NextResponse } from "next/server"
|
||||
import { getDatabases, getDatabaseStructure } from "@/lib/db-connector"
|
||||
import { getDatabases, getDatabaseStructure } from "@/lib/mongodb-mock-connector" // 更新导入路径
|
||||
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { Home, Database, Search, Target, Brain } from "lucide-react"
|
||||
import { Home, Database, Target, BrainCircuit } from "lucide-react" // 引入AI智能助手图标
|
||||
|
||||
const navItems = [
|
||||
{ href: "/", icon: Home, label: "概览" },
|
||||
{ href: "/data-platform", icon: Database, label: "数据中台" },
|
||||
{ href: "/user-discovery", icon: Search, label: "用户发现" },
|
||||
{ href: "/user-valuation", icon: Target, label: "用户估值" },
|
||||
{ href: "/ai-analysis", icon: Brain, label: "AI分析" },
|
||||
{ href: "/user-portrait", icon: Target, label: "用户画像" }, // 整合用户池功能
|
||||
{ href: "/ai-assistant", icon: BrainCircuit, label: "AI助手" }, // 新增AI智能助手
|
||||
]
|
||||
|
||||
export default function BottomNav() {
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function Header() {
|
||||
<Search className="h-4 w-4 text-muted-foreground mr-2" />
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="搜索用户、标签、活动..."
|
||||
placeholder="搜索用户、IMEI、手机号、标签..."
|
||||
className="border-none focus-visible:ring-0 focus-visible:ring-offset-0"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
|
||||
@@ -10,7 +10,7 @@ interface MobileHeaderProps {
|
||||
title?: string
|
||||
}
|
||||
|
||||
export default function MobileHeader({ onMenuToggle, title = "数据资产中台" }: MobileHeaderProps) {
|
||||
export default function MobileHeader({ onMenuToggle, title = "卡若数据资产中台" }: MobileHeaderProps) {
|
||||
const [showSearch, setShowSearch] = useState(false)
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
|
||||
@@ -46,7 +46,7 @@ export default function MobileHeader({ onMenuToggle, title = "数据资产中台
|
||||
<div className="flex-1">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="搜索用户、标签、活动..."
|
||||
placeholder="搜索用户、IMEI、手机号..."
|
||||
className="glass-input border-none focus-visible:ring-0 focus-visible:ring-offset-0"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
|
||||
@@ -7,16 +7,14 @@ import { cn } from "@/lib/utils"
|
||||
import {
|
||||
Database,
|
||||
LayoutDashboard,
|
||||
Users,
|
||||
Target,
|
||||
X,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
Search,
|
||||
BarChart3,
|
||||
TrendingUp,
|
||||
Brain,
|
||||
HelpCircle,
|
||||
UserCheck,
|
||||
Layers,
|
||||
Tag,
|
||||
BrainCircuit,
|
||||
} from "lucide-react"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button"
|
||||
@@ -29,9 +27,7 @@ interface MobileSidebarProps {
|
||||
export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
|
||||
const pathname = usePathname()
|
||||
const [expandedSections, setExpandedSections] = useState({
|
||||
"user-discovery": false,
|
||||
"user-valuation": false,
|
||||
"ai-analysis": false,
|
||||
"user-portrait": false, // 用户画像默认不展开
|
||||
})
|
||||
|
||||
const toggleSection = (section: string) => {
|
||||
@@ -70,77 +66,40 @@ export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
|
||||
description: "多源数据整合中心",
|
||||
},
|
||||
{
|
||||
title: "用户发现",
|
||||
href: "/user-discovery",
|
||||
icon: <Search className="h-4 w-4" />,
|
||||
primary: true,
|
||||
expandable: true,
|
||||
section: "user-discovery",
|
||||
description: "用户行为洞察分析",
|
||||
children: [
|
||||
{
|
||||
title: "行为分析",
|
||||
href: "/user-discovery/behavior",
|
||||
icon: <BarChart3 className="h-3 w-3" />,
|
||||
description: "用户行为模式分析",
|
||||
},
|
||||
{
|
||||
title: "用户分群",
|
||||
href: "/user-discovery/segmentation",
|
||||
icon: <Users className="h-3 w-3" />,
|
||||
description: "智能用户分群",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "用户估值",
|
||||
href: "/user-valuation",
|
||||
title: "用户画像", // 整合用户池功能
|
||||
href: "/user-portrait",
|
||||
icon: <Target className="h-4 w-4" />,
|
||||
primary: true,
|
||||
expandable: true,
|
||||
section: "user-valuation",
|
||||
description: "用户价值评估模型",
|
||||
hasHelp: true,
|
||||
section: "user-portrait",
|
||||
description: "用户数据管理与画像分析",
|
||||
children: [
|
||||
{
|
||||
title: "估值模型",
|
||||
href: "/user-valuation/model",
|
||||
icon: <BarChart3 className="h-3 w-3" />,
|
||||
description: "RFM价值评估",
|
||||
hasHelp: true,
|
||||
title: "用户管理", // 原用户池的用户管理
|
||||
href: "/user-portrait/management",
|
||||
icon: <UserCheck className="h-3 w-3" />,
|
||||
description: "IMEI、手机号管理",
|
||||
},
|
||||
{
|
||||
title: "升级路径",
|
||||
href: "/user-valuation/upgrade-paths",
|
||||
icon: <TrendingUp className="h-3 w-3" />,
|
||||
description: "用户价值提升策略",
|
||||
hasHelp: true,
|
||||
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分析",
|
||||
href: "/ai-analysis",
|
||||
icon: <Brain className="h-4 w-4" />,
|
||||
title: "AI智能助手", // 新增AI智能助手
|
||||
href: "/ai-assistant",
|
||||
icon: <BrainCircuit className="h-4 w-4" />,
|
||||
primary: true,
|
||||
expandable: true,
|
||||
section: "ai-analysis",
|
||||
description: "智能数据洞察",
|
||||
tag: "AI",
|
||||
children: [
|
||||
{
|
||||
title: "趋势识别",
|
||||
href: "/ai-analysis/trends",
|
||||
icon: <TrendingUp className="h-3 w-3" />,
|
||||
description: "AI趋势预测",
|
||||
},
|
||||
{
|
||||
title: "异常检测",
|
||||
href: "/ai-analysis/anomaly",
|
||||
icon: <Search className="h-3 w-3" />,
|
||||
description: "智能异常监控",
|
||||
},
|
||||
],
|
||||
description: "AI数据分析与营销策略",
|
||||
},
|
||||
]
|
||||
|
||||
@@ -148,8 +107,6 @@ export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
|
||||
switch (tag) {
|
||||
case "核心":
|
||||
return "bg-blue-100 text-blue-700 border-blue-200"
|
||||
case "AI":
|
||||
return "bg-purple-100 text-purple-700 border-purple-200"
|
||||
default:
|
||||
return "bg-gray-100 text-gray-700 border-gray-200"
|
||||
}
|
||||
@@ -180,7 +137,7 @@ export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
|
||||
<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">
|
||||
@@ -196,7 +153,7 @@ export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center px-3 py-2 text-xs font-medium rounded-lg transition-all duration-300 group",
|
||||
pathname === item.href
|
||||
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",
|
||||
)}
|
||||
@@ -207,7 +164,6 @@ export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="font-medium truncate">{item.title}</span>
|
||||
{item.hasHelp && <HelpCircle className="h-3 w-3 text-gray-400 flex-shrink-0" />}
|
||||
</div>
|
||||
{item.tag && (
|
||||
<Badge
|
||||
@@ -253,10 +209,7 @@ export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
|
||||
{subItem.icon}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-1">
|
||||
<p className="font-medium truncate">{subItem.title}</p>
|
||||
{subItem.hasHelp && <HelpCircle className="h-3 w-3 text-gray-400 flex-shrink-0" />}
|
||||
</div>
|
||||
<p className="font-medium truncate">{subItem.title}</p>
|
||||
<p className="text-xs text-gray-500 truncate">{subItem.description}</p>
|
||||
</div>
|
||||
</Link>
|
||||
@@ -272,15 +225,15 @@ export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
|
||||
<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">AI引擎状态</span>
|
||||
<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>
|
||||
<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">实时</span>
|
||||
<span className="text-gray-600">用户池总数</span>
|
||||
<span className="text-gray-500">125,678</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,14 +8,15 @@ import {
|
||||
Database,
|
||||
LayoutDashboard,
|
||||
Settings,
|
||||
Users,
|
||||
Target,
|
||||
ChevronLeft,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
Tag,
|
||||
BarChart3,
|
||||
TrendingUp,
|
||||
UserCheck,
|
||||
Layers,
|
||||
BrainCircuit,
|
||||
Smartphone,
|
||||
} from "lucide-react"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
|
||||
@@ -23,8 +24,7 @@ export default function Sidebar() {
|
||||
const pathname = usePathname()
|
||||
const [expanded, setExpanded] = useState(true)
|
||||
const [expandedSections, setExpandedSections] = useState({
|
||||
"user-portrait": true,
|
||||
"user-value": false,
|
||||
"user-portrait": true, // 用户画像默认展开
|
||||
})
|
||||
|
||||
const toggleSidebar = () => {
|
||||
@@ -38,60 +38,57 @@ export default function Sidebar() {
|
||||
}))
|
||||
}
|
||||
|
||||
// 重新设计的导航结构
|
||||
// 重新设计的导航结构 - 遵循新规则
|
||||
const navItems = [
|
||||
{
|
||||
title: "概览",
|
||||
title: "数据概览",
|
||||
href: "/",
|
||||
icon: <LayoutDashboard className="h-5 w-5" />,
|
||||
primary: true,
|
||||
},
|
||||
{
|
||||
title: "数据集成",
|
||||
href: "/data-integration",
|
||||
title: "数据中台",
|
||||
href: "/data-platform",
|
||||
icon: <Database className="h-5 w-5" />,
|
||||
primary: true,
|
||||
tag: "核心",
|
||||
},
|
||||
{
|
||||
title: "用户画像",
|
||||
title: "用户画像", // 整合用户池功能
|
||||
href: "/user-portrait",
|
||||
icon: <Users className="h-5 w-5" />,
|
||||
icon: <Target className="h-5 w-5" />,
|
||||
primary: true,
|
||||
expandable: true,
|
||||
section: "user-portrait",
|
||||
children: [
|
||||
{
|
||||
title: "用户词",
|
||||
href: "/user-portrait/user-keywords",
|
||||
icon: <Tag className="h-4 w-4" />,
|
||||
title: "用户管理", // 原用户池的用户管理
|
||||
href: "/user-portrait/management",
|
||||
icon: <UserCheck className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
title: "标签管理",
|
||||
title: "用户分群", // 原用户池的用户分群
|
||||
href: "/user-portrait/segmentation",
|
||||
icon: <Layers className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
title: "标签管理", // 原用户画像的标签管理
|
||||
href: "/user-portrait/tags",
|
||||
icon: <Tag className="h-4 w-4" />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "用户估值",
|
||||
href: "/user-value",
|
||||
icon: <Target className="h-5 w-5" />,
|
||||
title: "设备管理", // 提升为一级菜单 [^2]
|
||||
href: "/devices",
|
||||
icon: <Smartphone className="h-5 w-5" />,
|
||||
primary: true,
|
||||
},
|
||||
{
|
||||
title: "AI智能助手", // 新增AI智能助手
|
||||
href: "/ai-assistant",
|
||||
icon: <BrainCircuit className="h-5 w-5" />,
|
||||
primary: true,
|
||||
expandable: true,
|
||||
section: "user-value",
|
||||
children: [
|
||||
{
|
||||
title: "估值模型",
|
||||
href: "/user-value/model",
|
||||
icon: <BarChart3 className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
title: "用户升级路径",
|
||||
href: "/user-value/upgrade-paths",
|
||||
icon: <TrendingUp className="h-4 w-4" />,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
@@ -119,7 +116,7 @@ export default function Sidebar() {
|
||||
<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>
|
||||
</div>
|
||||
) : (
|
||||
@@ -139,7 +136,7 @@ export default function Sidebar() {
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center px-4 py-3 text-sm font-medium rounded-xl transition-all duration-300 group",
|
||||
pathname === item.href
|
||||
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",
|
||||
)}
|
||||
|
||||
@@ -18,10 +18,15 @@ import {
|
||||
Settings,
|
||||
ChevronRight,
|
||||
ChevronDown,
|
||||
Brain,
|
||||
} from "lucide-react"
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { AIAnalysisTools } from "@/components/data-integration/ai-analysis-tools"
|
||||
import { useToast } from "@/components/ui/use-toast" // 导入 useToast
|
||||
|
||||
export default function DataPlatformPage() {
|
||||
const { toast } = useToast() // 初始化 toast
|
||||
const [timeRange, setTimeRange] = useState("today")
|
||||
const [expandedSections, setExpandedSections] = useState({
|
||||
overview: true,
|
||||
@@ -29,6 +34,7 @@ export default function DataPlatformPage() {
|
||||
integration: false,
|
||||
quality: false,
|
||||
})
|
||||
const [activeTab, setActiveTab] = useState("overview")
|
||||
|
||||
// 数据中台数据
|
||||
const platformData = {
|
||||
@@ -50,6 +56,7 @@ export default function DataPlatformPage() {
|
||||
lastSync: "1分钟前",
|
||||
quality: 96.8,
|
||||
description: "用户行为轨迹数据",
|
||||
aiReady: true, // 新增 AI 就绪状态
|
||||
},
|
||||
{
|
||||
name: "CRM系统",
|
||||
@@ -59,6 +66,7 @@ export default function DataPlatformPage() {
|
||||
lastSync: "3分钟前",
|
||||
quality: 94.2,
|
||||
description: "客户关系管理数据",
|
||||
aiReady: true,
|
||||
},
|
||||
{
|
||||
name: "内容平台数据",
|
||||
@@ -68,6 +76,7 @@ export default function DataPlatformPage() {
|
||||
lastSync: "正在同步",
|
||||
quality: 92.5,
|
||||
description: "抖音、小红书等平台数据",
|
||||
aiReady: false,
|
||||
},
|
||||
{
|
||||
name: "触客宝后台",
|
||||
@@ -77,6 +86,7 @@ export default function DataPlatformPage() {
|
||||
lastSync: "5分钟前",
|
||||
quality: 95.1,
|
||||
description: "呼入电话信息数据",
|
||||
aiReady: true,
|
||||
},
|
||||
{
|
||||
name: "飞书妙记",
|
||||
@@ -86,6 +96,7 @@ export default function DataPlatformPage() {
|
||||
lastSync: "10分钟前",
|
||||
quality: 91.3,
|
||||
description: "会议记录和内容摘要",
|
||||
aiReady: true,
|
||||
},
|
||||
{
|
||||
name: "表单提交数据",
|
||||
@@ -95,6 +106,7 @@ export default function DataPlatformPage() {
|
||||
lastSync: "2分钟前",
|
||||
quality: 97.2,
|
||||
description: "在线表单提交数据",
|
||||
aiReady: true,
|
||||
},
|
||||
],
|
||||
integrationStats: {
|
||||
@@ -150,6 +162,14 @@ export default function DataPlatformPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleConnectToAI = (sourceName: string) => {
|
||||
toast({
|
||||
title: "连接到AI",
|
||||
description: `数据源 "${sourceName}" 已准备好连接到AI模型。`,
|
||||
})
|
||||
// 实际应用中,这里会触发一个后端API调用或跳转到AI配置页面
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-4 md:py-6 space-y-4 md:space-y-6">
|
||||
{/* 页面标题和工具栏 */}
|
||||
@@ -182,309 +202,346 @@ export default function DataPlatformPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 数据中台概览 */}
|
||||
<Collapsible
|
||||
open={expandedSections.overview}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, overview: open }))}
|
||||
>
|
||||
<Card className="border-none shadow-md">
|
||||
<CollapsibleTrigger className="w-full">
|
||||
<CardHeader className="bg-gradient-to-r from-blue-50 to-indigo-50 border-b hover:from-blue-100 hover:to-indigo-100 transition-colors cursor-pointer py-3 md:py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<Database className="h-4 w-4 md:h-5 md:w-5 text-blue-600" />
|
||||
<div className="text-left">
|
||||
<CardTitle className="text-base md:text-lg">平台概览</CardTitle>
|
||||
<CardDescription className="text-xs md:text-sm">数据中台整体运行状况</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
{expandedSections.overview ? (
|
||||
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<CardContent className="p-4">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 md:gap-4">
|
||||
<div className="bg-blue-50 p-3 rounded-lg text-center">
|
||||
<div className="text-lg md:text-2xl font-bold text-blue-600">
|
||||
{platformData.overview.totalRecords.toLocaleString()}
|
||||
</div>
|
||||
<div className="text-xs text-blue-700">总数据量</div>
|
||||
</div>
|
||||
<div className="bg-green-50 p-3 rounded-lg text-center">
|
||||
<div className="text-lg md:text-2xl font-bold text-green-600">
|
||||
{platformData.overview.dataSources}
|
||||
</div>
|
||||
<div className="text-xs text-green-700">数据源</div>
|
||||
</div>
|
||||
<div className="bg-purple-50 p-3 rounded-lg text-center">
|
||||
<div className="text-lg md:text-2xl font-bold text-purple-600">
|
||||
{platformData.overview.dataQuality}%
|
||||
</div>
|
||||
<div className="text-xs text-purple-700">数据质量</div>
|
||||
</div>
|
||||
<div className="bg-orange-50 p-3 rounded-lg text-center">
|
||||
<div className="text-lg md:text-2xl font-bold text-orange-600">
|
||||
{platformData.overview.storageUsed}GB
|
||||
</div>
|
||||
<div className="text-xs text-orange-700">存储使用</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 space-y-3">
|
||||
<div>
|
||||
<div className="flex justify-between text-sm mb-1">
|
||||
<span>存储使用率</span>
|
||||
<span>
|
||||
{platformData.overview.storageUsed}GB / {platformData.overview.storageTotal}GB
|
||||
</span>
|
||||
</div>
|
||||
<Progress value={(platformData.overview.storageUsed / platformData.overview.storageTotal) * 100} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between text-sm mb-1">
|
||||
<span>数据质量</span>
|
||||
<span>{platformData.overview.dataQuality}%</span>
|
||||
</div>
|
||||
<Progress value={platformData.overview.dataQuality} />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</CollapsibleContent>
|
||||
</Card>
|
||||
</Collapsible>
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-4 md:grid-cols-5">
|
||||
<TabsTrigger value="overview">概览</TabsTrigger>
|
||||
<TabsTrigger value="sources">数据源</TabsTrigger>
|
||||
<TabsTrigger value="integration">集成统计</TabsTrigger>
|
||||
<TabsTrigger value="quality">数据质量</TabsTrigger>
|
||||
<TabsTrigger value="ai-models">AI 模型</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* 数据源管理 */}
|
||||
<Collapsible
|
||||
open={expandedSections.sources}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, sources: open }))}
|
||||
>
|
||||
<Card className="border-none shadow-md">
|
||||
<CollapsibleTrigger className="w-full">
|
||||
<CardHeader className="bg-gradient-to-r from-green-50 to-emerald-50 border-b hover:from-green-100 hover:to-emerald-100 transition-colors cursor-pointer py-3 md:py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<FileText className="h-4 w-4 md:h-5 md:w-5 text-green-600" />
|
||||
<div className="text-left">
|
||||
<CardTitle className="text-base md:text-lg">数据源管理</CardTitle>
|
||||
<CardDescription className="text-xs md:text-sm">
|
||||
数据源连接状态 ({platformData.dataSources.length})
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
{expandedSections.sources ? (
|
||||
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<CardContent className="p-4">
|
||||
<div className="space-y-3">
|
||||
{platformData.dataSources.map((source, index) => (
|
||||
<div key={index} className="bg-white border rounded-lg p-3 md:p-4">
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-3">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
{getStatusIcon(source.status)}
|
||||
<h3 className="font-medium text-sm md:text-base">{source.name}</h3>
|
||||
<Badge className={getStatusColor(source.status)}>{source.status}</Badge>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{source.type}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-xs md:text-sm text-gray-600 mb-2">{source.description}</p>
|
||||
<div className="flex flex-wrap gap-4 text-xs">
|
||||
<span>数据量: {source.records.toLocaleString()}</span>
|
||||
<span>最后同步: {source.lastSync}</span>
|
||||
<span>质量评分: {source.quality}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col md:items-end gap-2">
|
||||
<div className="text-right">
|
||||
<div className="text-sm font-bold text-green-600">{source.quality}%</div>
|
||||
<div className="text-xs text-gray-500">数据质量</div>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" className="bg-transparent">
|
||||
<Settings className="h-3 w-3 mr-1" />
|
||||
配置
|
||||
</Button>
|
||||
<TabsContent value="overview" className="space-y-6">
|
||||
{/* 数据中台概览 */}
|
||||
<Collapsible
|
||||
open={expandedSections.overview}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, overview: open }))}
|
||||
>
|
||||
<Card className="border-none shadow-md">
|
||||
<CollapsibleTrigger className="w-full">
|
||||
<CardHeader className="bg-gradient-to-r from-blue-50 to-indigo-50 border-b hover:from-blue-100 hover:to-indigo-100 transition-colors cursor-pointer py-3 md:py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<Database className="h-4 w-4 md:h-5 md:w-5 text-blue-600" />
|
||||
<div className="text-left">
|
||||
<CardTitle className="text-base md:text-lg">平台概览</CardTitle>
|
||||
<CardDescription className="text-xs md:text-sm">数据中台整体运行状况</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<Progress value={source.quality} className="h-1.5" />
|
||||
{expandedSections.overview ? (
|
||||
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<CardContent className="p-4">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 md:gap-4">
|
||||
<div className="bg-blue-50 p-3 rounded-lg text-center">
|
||||
<div className="text-lg md:text-2xl font-bold text-blue-600">
|
||||
{platformData.overview.totalRecords.toLocaleString()}
|
||||
</div>
|
||||
<div className="text-xs text-blue-700">总数据量</div>
|
||||
</div>
|
||||
<div className="bg-green-50 p-3 rounded-lg text-center">
|
||||
<div className="text-lg md:text-2xl font-bold text-green-600">
|
||||
{platformData.overview.dataSources}
|
||||
</div>
|
||||
<div className="text-xs text-green-700">数据源</div>
|
||||
</div>
|
||||
<div className="bg-purple-50 p-3 rounded-lg text-center">
|
||||
<div className="text-lg md:text-2xl font-bold text-purple-600">
|
||||
{platformData.overview.dataQuality}%
|
||||
</div>
|
||||
<div className="text-xs text-purple-700">数据质量</div>
|
||||
</div>
|
||||
<div className="bg-orange-50 p-3 rounded-lg text-center">
|
||||
<div className="text-lg md:text-2xl font-bold text-orange-600">
|
||||
{platformData.overview.storageUsed}GB
|
||||
</div>
|
||||
<div className="text-xs text-orange-700">存储使用</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</CollapsibleContent>
|
||||
</Card>
|
||||
</Collapsible>
|
||||
<div className="mt-4 space-y-3">
|
||||
<div>
|
||||
<div className="flex justify-between text-sm mb-1">
|
||||
<span>存储使用率</span>
|
||||
<span>
|
||||
{platformData.overview.storageUsed}GB / {platformData.overview.storageTotal}GB
|
||||
</span>
|
||||
</div>
|
||||
<Progress
|
||||
value={(platformData.overview.storageUsed / platformData.overview.storageTotal) * 100}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between text-sm mb-1">
|
||||
<span>数据质量</span>
|
||||
<span>{platformData.overview.dataQuality}%</span>
|
||||
</div>
|
||||
<Progress value={platformData.overview.dataQuality} />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</CollapsibleContent>
|
||||
</Card>
|
||||
</Collapsible>
|
||||
</TabsContent>
|
||||
|
||||
{/* 数据集成统计 */}
|
||||
<Collapsible
|
||||
open={expandedSections.integration}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, integration: open }))}
|
||||
>
|
||||
<Card className="border-none shadow-md">
|
||||
<CollapsibleTrigger className="w-full">
|
||||
<CardHeader className="bg-gradient-to-r from-purple-50 to-pink-50 border-b hover:from-purple-100 hover:to-pink-100 transition-colors cursor-pointer py-3 md:py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<RefreshCw className="h-4 w-4 md:h-5 md:w-5 text-purple-600" />
|
||||
<div className="text-left">
|
||||
<CardTitle className="text-base md:text-lg">集成统计</CardTitle>
|
||||
<CardDescription className="text-xs md:text-sm">数据集成性能指标</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
{expandedSections.integration ? (
|
||||
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<CardContent className="p-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<div className="bg-blue-50 p-4 rounded-lg">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Database className="h-4 w-4 text-blue-600" />
|
||||
<span className="text-sm font-medium">集成概况</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">总集成数</span>
|
||||
<span className="text-sm font-bold">{platformData.integrationStats.totalIntegrations}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">活跃集成</span>
|
||||
<span className="text-sm font-bold">{platformData.integrationStats.activeIntegrations}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">待处理</span>
|
||||
<span className="text-sm font-bold">{platformData.integrationStats.pendingIntegrations}</span>
|
||||
<TabsContent value="sources" className="space-y-6">
|
||||
{/* 数据源管理 */}
|
||||
<Collapsible
|
||||
open={expandedSections.sources}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, sources: open }))}
|
||||
>
|
||||
<Card className="border-none shadow-md">
|
||||
<CollapsibleTrigger className="w-full">
|
||||
<CardHeader className="bg-gradient-to-r from-green-50 to-emerald-50 border-b hover:from-green-100 hover:to-emerald-100 transition-colors cursor-pointer py-3 md:py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<FileText className="h-4 w-4 md:h-5 md:w-5 text-green-600" />
|
||||
<div className="text-left">
|
||||
<CardTitle className="text-base md:text-lg">数据源管理</CardTitle>
|
||||
<CardDescription className="text-xs md:text-sm">
|
||||
数据源连接状态 ({platformData.dataSources.length})
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
{expandedSections.sources ? (
|
||||
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<CardContent className="p-4">
|
||||
<div className="space-y-3">
|
||||
{platformData.dataSources.map((source, index) => (
|
||||
<div key={index} className="bg-white border rounded-lg p-3 md:p-4">
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-3">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
{getStatusIcon(source.status)}
|
||||
<h3 className="font-medium text-sm md:text-base">{source.name}</h3>
|
||||
<Badge className={getStatusColor(source.status)}>{source.status}</Badge>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{source.type}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-xs md:text-sm text-gray-600 mb-2">{source.description}</p>
|
||||
<div className="flex flex-wrap gap-4 text-xs">
|
||||
<span>数据量: {source.records.toLocaleString()}</span>
|
||||
<span>最后同步: {source.lastSync}</span>
|
||||
<span>质量评分: {source.quality}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col md:items-end gap-2">
|
||||
<div className="text-right">
|
||||
<div className="text-sm font-bold text-green-600">{source.quality}%</div>
|
||||
<div className="text-xs text-gray-500">数据质量</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" size="sm" className="bg-transparent">
|
||||
<Settings className="h-3 w-3 mr-1" />
|
||||
配置
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="bg-transparent"
|
||||
disabled={!source.aiReady}
|
||||
onClick={() => handleConnectToAI(source.name)} // 添加点击事件
|
||||
>
|
||||
<Brain className="h-3 w-3 mr-1" />
|
||||
{source.aiReady ? "连接到AI" : "AI未就绪"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<Progress value={source.quality} className="h-1.5" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</CollapsibleContent>
|
||||
</Card>
|
||||
</Collapsible>
|
||||
</TabsContent>
|
||||
|
||||
<div className="bg-green-50 p-4 rounded-lg">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<CheckCircle className="h-4 w-4 text-green-600" />
|
||||
<span className="text-sm font-medium">性能指标</span>
|
||||
<TabsContent value="integration" className="space-y-6">
|
||||
{/* 数据集成统计 */}
|
||||
<Collapsible
|
||||
open={expandedSections.integration}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, integration: open }))}
|
||||
>
|
||||
<Card className="border-none shadow-md">
|
||||
<CollapsibleTrigger className="w-full">
|
||||
<CardHeader className="bg-gradient-to-r from-purple-50 to-pink-50 border-b hover:from-purple-100 hover:to-pink-100 transition-colors cursor-pointer py-3 md:py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<RefreshCw className="h-4 w-4 md:h-5 md:w-5 text-purple-600" />
|
||||
<div className="text-left">
|
||||
<CardTitle className="text-base md:text-lg">集成统计</CardTitle>
|
||||
<CardDescription className="text-xs md:text-sm">数据集成性能指标</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
{expandedSections.integration ? (
|
||||
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">成功率</span>
|
||||
<span className="text-sm font-bold">{platformData.integrationStats.successRate}%</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">平均同步时间</span>
|
||||
<span className="text-sm font-bold">{platformData.integrationStats.avgSyncTime}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">失败集成</span>
|
||||
<span className="text-sm font-bold">{platformData.integrationStats.failedIntegrations}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-purple-50 p-4 rounded-lg">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Clock className="h-4 w-4 text-purple-600" />
|
||||
<span className="text-sm font-medium">实时状态</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">最后更新</span>
|
||||
<span className="text-sm font-bold">{platformData.overview.lastUpdate}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">同步状态</span>
|
||||
<span className="text-sm font-bold text-green-600">{platformData.overview.syncStatus}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">系统负载</span>
|
||||
<span className="text-sm font-bold">正常</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</CollapsibleContent>
|
||||
</Card>
|
||||
</Collapsible>
|
||||
|
||||
{/* 数据质量监控 */}
|
||||
<Collapsible
|
||||
open={expandedSections.quality}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, quality: open }))}
|
||||
>
|
||||
<Card className="border-none shadow-md">
|
||||
<CollapsibleTrigger className="w-full">
|
||||
<CardHeader className="bg-gradient-to-r from-orange-50 to-red-50 border-b hover:from-orange-100 hover:to-red-100 transition-colors cursor-pointer py-3 md:py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<CheckCircle className="h-4 w-4 md:h-5 md:w-5 text-orange-600" />
|
||||
<div className="text-left">
|
||||
<CardTitle className="text-base md:text-lg">数据质量监控</CardTitle>
|
||||
<CardDescription className="text-xs md:text-sm">数据质量多维度评估</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
{expandedSections.quality ? (
|
||||
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<CardContent className="p-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{Object.entries(platformData.dataQuality).map(([key, value], index) => {
|
||||
const labels = {
|
||||
completeness: "完整性",
|
||||
accuracy: "准确性",
|
||||
consistency: "一致性",
|
||||
timeliness: "及时性",
|
||||
validity: "有效性",
|
||||
uniqueness: "唯一性",
|
||||
}
|
||||
|
||||
const colors = [
|
||||
"bg-blue-50 text-blue-600",
|
||||
"bg-green-50 text-green-600",
|
||||
"bg-purple-50 text-purple-600",
|
||||
"bg-orange-50 text-orange-600",
|
||||
"bg-pink-50 text-pink-600",
|
||||
"bg-indigo-50 text-indigo-600",
|
||||
]
|
||||
|
||||
return (
|
||||
<div key={key} className={`p-4 rounded-lg ${colors[index]}`}>
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold mb-1">{value}%</div>
|
||||
<div className="text-sm">{labels[key as keyof typeof labels]}</div>
|
||||
<div className="mt-2">
|
||||
<Progress value={value} className="h-1.5" />
|
||||
</CardHeader>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<CardContent className="p-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<div className="bg-blue-50 p-4 rounded-lg">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Database className="h-4 w-4 text-blue-600" />
|
||||
<span className="text-sm font-medium">集成概况</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">总集成数</span>
|
||||
<span className="text-sm font-bold">{platformData.integrationStats.totalIntegrations}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">活跃集成</span>
|
||||
<span className="text-sm font-bold">{platformData.integrationStats.activeIntegrations}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">待处理</span>
|
||||
<span className="text-sm font-bold">{platformData.integrationStats.pendingIntegrations}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</CardContent>
|
||||
</CollapsibleContent>
|
||||
</Card>
|
||||
</Collapsible>
|
||||
|
||||
<div className="bg-green-50 p-4 rounded-lg">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<CheckCircle className="h-4 w-4 text-green-600" />
|
||||
<span className="text-sm font-medium">性能指标</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">成功率</span>
|
||||
<span className="text-sm font-bold">{platformData.integrationStats.successRate}%</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">平均同步时间</span>
|
||||
<span className="text-sm font-bold">{platformData.integrationStats.avgSyncTime}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">失败集成</span>
|
||||
<span className="text-sm font-bold">{platformData.integrationStats.failedIntegrations}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-purple-50 p-4 rounded-lg">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Clock className="h-4 w-4 text-purple-600" />
|
||||
<span className="text-sm font-medium">实时状态</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">最后更新</span>
|
||||
<span className="text-sm font-bold">{platformData.overview.lastUpdate}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">同步状态</span>
|
||||
<span className="text-sm font-bold text-green-600">{platformData.overview.syncStatus}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">系统负载</span>
|
||||
<span className="text-sm font-bold">正常</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</CollapsibleContent>
|
||||
</Card>
|
||||
</Collapsible>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="quality" className="space-y-6">
|
||||
{/* 数据质量监控 */}
|
||||
<Collapsible
|
||||
open={expandedSections.quality}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, quality: open }))}
|
||||
>
|
||||
<Card className="border-none shadow-md">
|
||||
<CollapsibleTrigger className="w-full">
|
||||
<CardHeader className="bg-gradient-to-r from-orange-50 to-red-50 border-b hover:from-orange-100 hover:to-red-100 transition-colors cursor-pointer py-3 md:py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<CheckCircle className="h-4 w-4 md:h-5 md:w-5 text-orange-600" />
|
||||
<div className="text-left">
|
||||
<CardTitle className="text-base md:text-lg">数据质量监控</CardTitle>
|
||||
<CardDescription className="text-xs md:text-sm">数据质量多维度评估</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
{expandedSections.quality ? (
|
||||
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<CardContent className="p-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{Object.entries(platformData.dataQuality).map(([key, value], index) => {
|
||||
const labels = {
|
||||
completeness: "完整性",
|
||||
accuracy: "准确性",
|
||||
consistency: "一致性",
|
||||
timeliness: "及时性",
|
||||
validity: "有效性",
|
||||
uniqueness: "唯一性",
|
||||
}
|
||||
|
||||
const colors = [
|
||||
"bg-blue-50 text-blue-600",
|
||||
"bg-green-50 text-green-600",
|
||||
"bg-purple-50 text-purple-600",
|
||||
"bg-orange-50 text-orange-600",
|
||||
"bg-pink-50 text-pink-600",
|
||||
"bg-indigo-50 text-indigo-600",
|
||||
]
|
||||
|
||||
return (
|
||||
<div key={key} className={`p-4 rounded-lg ${colors[index]}`}>
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold mb-1">{value}%</div>
|
||||
<div className="text-sm">{labels[key as keyof typeof labels]}</div>
|
||||
<div className="mt-2">
|
||||
<Progress value={value} className="h-1.5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</CardContent>
|
||||
</CollapsibleContent>
|
||||
</Card>
|
||||
</Collapsible>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="ai-models" className="space-y-6">
|
||||
{/* AI 模型管理 */}
|
||||
<AIAnalysisTools />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import ClientLayout from "./ClientLayout"
|
||||
const inter = Inter({ subsets: ["latin"] })
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
title: "卡若数据资产中台",
|
||||
description: "基于IMEI、手机号的用户数据资产管理平台",
|
||||
generator: 'v0.dev'
|
||||
}
|
||||
|
||||
|
||||
179
app/page.tsx
179
app/page.tsx
@@ -8,15 +8,17 @@ import { Progress } from "@/components/ui/progress"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import {
|
||||
Database,
|
||||
Users,
|
||||
Target,
|
||||
Brain,
|
||||
Search,
|
||||
BarChart3,
|
||||
TrendingUp,
|
||||
RefreshCw,
|
||||
Download,
|
||||
ChevronRight,
|
||||
Activity,
|
||||
Zap,
|
||||
UserCheck,
|
||||
Layers,
|
||||
Tag,
|
||||
} from "lucide-react"
|
||||
import Link from "next/link"
|
||||
|
||||
@@ -31,24 +33,24 @@ export default function HomePage() {
|
||||
sources: 8,
|
||||
lastUpdate: "2分钟前",
|
||||
},
|
||||
userDiscovery: {
|
||||
userPool: {
|
||||
totalUsers: 125678,
|
||||
activeUsers: 45678,
|
||||
newUsers: 1234,
|
||||
engagement: "87.5%",
|
||||
growth: "+8.2%",
|
||||
},
|
||||
userPortrait: {
|
||||
totalTags: 342,
|
||||
activeTags: 287,
|
||||
coverage: "95.8%",
|
||||
growth: "+5.3%",
|
||||
},
|
||||
userValuation: {
|
||||
avgValue: 3248,
|
||||
highValue: 12456,
|
||||
growth: "+15.8%",
|
||||
upgradeRate: "12.5%",
|
||||
},
|
||||
aiAnalysis: {
|
||||
insights: 156,
|
||||
anomalies: 3,
|
||||
predictions: 89,
|
||||
accuracy: "94.2%",
|
||||
},
|
||||
}
|
||||
|
||||
// 实时活动数据
|
||||
@@ -56,21 +58,21 @@ export default function HomePage() {
|
||||
{
|
||||
id: 1,
|
||||
type: "data_sync",
|
||||
message: "电商平台数据同步完成",
|
||||
message: "IMEI数据同步完成",
|
||||
time: "刚刚",
|
||||
status: "success",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: "user_discovery",
|
||||
message: "发现新用户行为模式",
|
||||
type: "user_pool",
|
||||
message: "新增用户池分群",
|
||||
time: "2分钟前",
|
||||
status: "info",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: "ai_analysis",
|
||||
message: "AI检测到异常流量",
|
||||
type: "user_portrait",
|
||||
message: "标签规则自动执行",
|
||||
time: "5分钟前",
|
||||
status: "warning",
|
||||
},
|
||||
@@ -87,12 +89,12 @@ export default function HomePage() {
|
||||
switch (type) {
|
||||
case "data_sync":
|
||||
return <Database className="h-4 w-4" />
|
||||
case "user_discovery":
|
||||
return <Search className="h-4 w-4" />
|
||||
case "user_valuation":
|
||||
case "user_pool":
|
||||
return <Users className="h-4 w-4" />
|
||||
case "user_portrait":
|
||||
return <Target className="h-4 w-4" />
|
||||
case "ai_analysis":
|
||||
return <Brain className="h-4 w-4" />
|
||||
case "user_valuation":
|
||||
return <TrendingUp className="h-4 w-4" />
|
||||
default:
|
||||
return <Activity className="h-4 w-4" />
|
||||
}
|
||||
@@ -117,7 +119,7 @@ export default function HomePage() {
|
||||
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4">
|
||||
<div>
|
||||
<h1 className="text-2xl md:text-3xl font-bold tracking-tight">数据概览</h1>
|
||||
<p className="text-sm md:text-base text-muted-foreground">智能数据分析平台总览</p>
|
||||
<p className="text-sm md:text-base text-muted-foreground">卡若数据资产中台总览</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Select value={timeRange} onValueChange={setTimeRange}>
|
||||
@@ -178,34 +180,34 @@ export default function HomePage() {
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
{/* 用户发现 */}
|
||||
<Link href="/user-discovery">
|
||||
{/* 用户池 */}
|
||||
<Link href="/user-pool">
|
||||
<Card className="border-none shadow-md hover:shadow-lg transition-all duration-300 cursor-pointer group">
|
||||
<CardHeader className="bg-gradient-to-r from-green-50 to-emerald-50 pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Search className="h-5 w-5 text-green-600" />
|
||||
<CardTitle className="text-base">用户发现</CardTitle>
|
||||
<Users className="h-5 w-5 text-green-600" />
|
||||
<CardTitle className="text-base">用户池</CardTitle>
|
||||
</div>
|
||||
</div>
|
||||
<CardDescription className="text-xs">用户行为洞察分析</CardDescription>
|
||||
<CardDescription className="text-xs">用户数据管理中心</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-3">
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">总用户数</span>
|
||||
<span className="font-bold">{todayStats.userPool.totalUsers.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">活跃用户</span>
|
||||
<span className="font-bold">{todayStats.userDiscovery.activeUsers.toLocaleString()}</span>
|
||||
<span className="font-bold">{todayStats.userPool.activeUsers.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">新增用户</span>
|
||||
<span className="font-bold">{todayStats.userDiscovery.newUsers.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">参与度</span>
|
||||
<span className="text-green-600 font-bold">{todayStats.userDiscovery.engagement}</span>
|
||||
<span className="font-bold">{todayStats.userPool.newUsers.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-3">
|
||||
<span className="text-xs text-green-600">增长 {todayStats.userDiscovery.growth}</span>
|
||||
<span className="text-xs text-green-600">增长 {todayStats.userPool.growth}</span>
|
||||
<ChevronRight className="h-4 w-4 text-gray-400 group-hover:text-green-600 transition-colors" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -213,13 +215,48 @@ export default function HomePage() {
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
{/* 用户画像 */}
|
||||
<Link href="/user-portrait">
|
||||
<Card className="border-none shadow-md hover:shadow-lg transition-all duration-300 cursor-pointer group">
|
||||
<CardHeader className="bg-gradient-to-r from-purple-50 to-pink-50 pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Target className="h-5 w-5 text-purple-600" />
|
||||
<CardTitle className="text-base">用户画像</CardTitle>
|
||||
</div>
|
||||
</div>
|
||||
<CardDescription className="text-xs">用户标签画像分析</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-3">
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">标签总数</span>
|
||||
<span className="font-bold">{todayStats.userPortrait.totalTags}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">活跃标签</span>
|
||||
<span className="font-bold">{todayStats.userPortrait.activeTags}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">覆盖率</span>
|
||||
<span className="text-purple-600 font-bold">{todayStats.userPortrait.coverage}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-3">
|
||||
<span className="text-xs text-purple-600">增长 {todayStats.userPortrait.growth}</span>
|
||||
<ChevronRight className="h-4 w-4 text-gray-400 group-hover:text-purple-600 transition-colors" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
{/* 用户估值 */}
|
||||
<Link href="/user-valuation">
|
||||
<Card className="border-none shadow-md hover:shadow-lg transition-all duration-300 cursor-pointer group">
|
||||
<CardHeader className="bg-gradient-to-r from-purple-50 to-pink-50 pb-3">
|
||||
<CardHeader className="bg-gradient-to-r from-orange-50 to-red-50 pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Target className="h-5 w-5 text-purple-600" />
|
||||
<TrendingUp className="h-5 w-5 text-orange-600" />
|
||||
<CardTitle className="text-base">用户估值</CardTitle>
|
||||
</div>
|
||||
</div>
|
||||
@@ -237,46 +274,10 @@ export default function HomePage() {
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">升级率</span>
|
||||
<span className="text-purple-600 font-bold">{todayStats.userValuation.upgradeRate}</span>
|
||||
<span className="text-orange-600 font-bold">{todayStats.userValuation.upgradeRate}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-3">
|
||||
<span className="text-xs text-purple-600">增长 {todayStats.userValuation.growth}</span>
|
||||
<ChevronRight className="h-4 w-4 text-gray-400 group-hover:text-purple-600 transition-colors" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
{/* AI分析 */}
|
||||
<Link href="/ai-analysis">
|
||||
<Card className="border-none shadow-md hover:shadow-lg transition-all duration-300 cursor-pointer group">
|
||||
<CardHeader className="bg-gradient-to-r from-orange-50 to-red-50 pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Brain className="h-5 w-5 text-orange-600" />
|
||||
<CardTitle className="text-base">AI分析</CardTitle>
|
||||
</div>
|
||||
<Badge className="bg-orange-100 text-orange-700">AI</Badge>
|
||||
</div>
|
||||
<CardDescription className="text-xs">智能数据洞察</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-3">
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">AI洞察</span>
|
||||
<span className="font-bold">{todayStats.aiAnalysis.insights}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">异常检测</span>
|
||||
<span className="font-bold">{todayStats.aiAnalysis.anomalies}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">预测准确率</span>
|
||||
<span className="text-orange-600 font-bold">{todayStats.aiAnalysis.accuracy}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-3">
|
||||
<span className="text-xs text-orange-600">预测 {todayStats.aiAnalysis.predictions}个</span>
|
||||
<span className="text-xs text-orange-600">增长 {todayStats.userValuation.growth}</span>
|
||||
<ChevronRight className="h-4 w-4 text-gray-400 group-hover:text-orange-600 transition-colors" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -291,7 +292,7 @@ export default function HomePage() {
|
||||
<Card className="lg:col-span-2 border-none shadow-md">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<BarChart3 className="h-5 w-5 text-blue-600" />
|
||||
<Activity className="h-5 w-5 text-blue-600" />
|
||||
今日关键指标
|
||||
</CardTitle>
|
||||
<CardDescription>实时数据监控</CardDescription>
|
||||
@@ -307,12 +308,12 @@ export default function HomePage() {
|
||||
<div className="text-xs text-gray-600">平均响应时间</div>
|
||||
</div>
|
||||
<div className="text-center p-3 bg-purple-50 rounded-lg">
|
||||
<div className="text-2xl font-bold text-purple-600">156</div>
|
||||
<div className="text-xs text-gray-600">AI洞察数</div>
|
||||
<div className="text-2xl font-bold text-purple-600">342</div>
|
||||
<div className="text-xs text-gray-600">活跃标签数</div>
|
||||
</div>
|
||||
<div className="text-center p-3 bg-orange-50 rounded-lg">
|
||||
<div className="text-2xl font-bold text-orange-600">94.2%</div>
|
||||
<div className="text-xs text-gray-600">预测准确率</div>
|
||||
<div className="text-2xl font-bold text-orange-600">95.8%</div>
|
||||
<div className="text-xs text-gray-600">标签覆盖率</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 space-y-3">
|
||||
@@ -325,7 +326,7 @@ export default function HomePage() {
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between text-sm mb-1">
|
||||
<span>AI模型训练</span>
|
||||
<span>标签规则执行</span>
|
||||
<span>92%</span>
|
||||
</div>
|
||||
<Progress value={92} className="h-2" />
|
||||
@@ -381,22 +382,22 @@ export default function HomePage() {
|
||||
<span className="text-xs">数据导入</span>
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/user-discovery/behavior">
|
||||
<Link href="/user-pool/management">
|
||||
<Button variant="outline" className="w-full h-16 flex flex-col gap-1 bg-transparent">
|
||||
<Search className="h-5 w-5" />
|
||||
<span className="text-xs">行为分析</span>
|
||||
<UserCheck className="h-5 w-5" />
|
||||
<span className="text-xs">用户管理</span>
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/user-valuation/model">
|
||||
<Link href="/user-pool/segmentation">
|
||||
<Button variant="outline" className="w-full h-16 flex flex-col gap-1 bg-transparent">
|
||||
<Target className="h-5 w-5" />
|
||||
<span className="text-xs">价值评估</span>
|
||||
<Layers className="h-5 w-5" />
|
||||
<span className="text-xs">用户分群</span>
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/ai-analysis/trends">
|
||||
<Link href="/user-portrait/tags">
|
||||
<Button variant="outline" className="w-full h-16 flex flex-col gap-1 bg-transparent">
|
||||
<Brain className="h-5 w-5" />
|
||||
<span className="text-xs">AI预测</span>
|
||||
<Tag className="h-5 w-5" />
|
||||
<span className="text-xs">标签管理</span>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -257,7 +257,7 @@ export default function ApiPage() {
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 text-gray-500">
|
||||
<Clock className="w-4 h-4" />
|
||||
<span>下次执行: {task.nextExecutionTime}</span>
|
||||
<span>下次执<EFBFBD><EFBFBD>: {task.nextExecutionTime}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
22
app/user-pool/loading.tsx
Normal file
22
app/user-pool/loading.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
export default function Loading() {
|
||||
return (
|
||||
<div className="container mx-auto py-6 space-y-6">
|
||||
<div className="animate-pulse">
|
||||
<div className="h-8 bg-gray-200 rounded w-1/4 mb-2"></div>
|
||||
<div className="h-4 bg-gray-200 rounded w-1/2"></div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-5 gap-4">
|
||||
{[...Array(5)].map((_, i) => (
|
||||
<div key={i} className="animate-pulse">
|
||||
<div className="h-20 bg-gray-200 rounded"></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="animate-pulse">
|
||||
<div className="h-64 bg-gray-200 rounded"></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { UserPoolHeader } from "@/components/user-pool/user-pool-header"
|
||||
import { UserPoolTable } from "@/components/user-pool/user-pool-table"
|
||||
import { UserPoolFilters } from "@/components/user-pool/user-pool-filters"
|
||||
import { UserPoolStats } from "@/components/user-pool/user-pool-stats"
|
||||
|
||||
export default function UserPoolPage() {
|
||||
return (
|
||||
<div className="container mx-auto p-6 space-y-6">
|
||||
<UserPoolHeader />
|
||||
<UserPoolStats />
|
||||
<UserPoolFilters />
|
||||
<UserPoolTable />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -19,31 +19,61 @@ import {
|
||||
Users,
|
||||
Eye,
|
||||
Settings,
|
||||
Smartphone,
|
||||
MapPin,
|
||||
Layers,
|
||||
UserCheck,
|
||||
} from "lucide-react"
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
|
||||
|
||||
export default function UserPortrait() {
|
||||
export default function UserPortraitPage() {
|
||||
const router = useRouter()
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const [selectedCategory, setSelectedCategory] = useState("all")
|
||||
const [selectedPool, setSelectedPool] = useState("all")
|
||||
const [selectedDevice, setSelectedDevice] = useState("all") // 新增设备筛选
|
||||
const [selectedWechatDialog, setSelectedWechatDialog] = useState<{ isOpen: boolean; accounts: any[] }>({
|
||||
isOpen: false,
|
||||
accounts: [],
|
||||
})
|
||||
const [expandedSections, setExpandedSections] = useState({
|
||||
userKeywords: true,
|
||||
tagManagement: false,
|
||||
userList: true,
|
||||
overview: true, // 用户池概览
|
||||
userManagement: true, // 用户管理 (原用户池管理)
|
||||
userSegmentation: false, // 用户分群 (原用户池分群)
|
||||
tagManagement: false, // 标签管理 (原用户画像标签管理)
|
||||
})
|
||||
|
||||
// 使用通用公司名称和当天时间线的用户数据
|
||||
// 用户池概览数据 (从原 user-pool/page.tsx 整合)
|
||||
const poolData = {
|
||||
overview: {
|
||||
totalUsers: 125678,
|
||||
activeUsers: 89234,
|
||||
newUsers: 3456,
|
||||
privatePool: 78234,
|
||||
publicPool: 47444,
|
||||
},
|
||||
deviceStats: {
|
||||
ios: 45678,
|
||||
android: 67890,
|
||||
other: 12110,
|
||||
},
|
||||
locationStats: {
|
||||
tier1: 56789,
|
||||
tier2: 34567,
|
||||
tier3: 23456,
|
||||
overseas: 10866,
|
||||
},
|
||||
}
|
||||
|
||||
// 用户数据 (整合了更多设备和平台信息)
|
||||
const mockUsers = [
|
||||
{
|
||||
id: "1",
|
||||
name: "张明华",
|
||||
company: "科技创新有限公司",
|
||||
imei: "860123456789012",
|
||||
phone: "13800138001",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
tags: ["高价值", "技术决策者", "活跃用户", "iOS用户", "企业级客户"],
|
||||
category: "企业服务",
|
||||
@@ -55,11 +85,18 @@ export default function UserPortrait() {
|
||||
{ id: "wx1", name: "张明华", avatar: "/placeholder.svg?height=40&width=40", tags: ["主要账号", "活跃"] },
|
||||
{ id: "wx2", name: "张明华工作号", avatar: "/placeholder.svg?height=40&width=40", tags: ["工作账号"] },
|
||||
],
|
||||
deviceBrand: "iPhone", // 新增设备信息
|
||||
deviceModel: "iPhone 14 Pro",
|
||||
location: "北京市",
|
||||
platform: "微信",
|
||||
status: "在线",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "李雨婷",
|
||||
company: "数字营销公司",
|
||||
imei: "860987654321098",
|
||||
phone: "13900139001",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
tags: ["中高价值", "产品经理", "内容创作者", "Android用户"],
|
||||
category: "内容营销",
|
||||
@@ -70,11 +107,18 @@ export default function UserPortrait() {
|
||||
wechatAccounts: [
|
||||
{ id: "wx3", name: "李雨婷", avatar: "/placeholder.svg?height=40&width=40", tags: ["主要账号"] },
|
||||
],
|
||||
deviceBrand: "华为",
|
||||
deviceModel: "Mate 50 Pro",
|
||||
location: "上海市",
|
||||
platform: "抖音",
|
||||
status: "活跃",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "王建国",
|
||||
company: "软件开发工作室",
|
||||
imei: "860456789123456",
|
||||
phone: "13700137001",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
tags: ["高价值", "技术专家", "开源贡献者", "MacOS用户"],
|
||||
category: "技术社区",
|
||||
@@ -85,26 +129,40 @@ export default function UserPortrait() {
|
||||
wechatAccounts: [
|
||||
{ id: "wx4", name: "王建国", avatar: "/placeholder.svg?height=40&width=40", tags: ["主要账号", "技术交流"] },
|
||||
],
|
||||
deviceBrand: "小米",
|
||||
deviceModel: "小米13 Ultra",
|
||||
location: "深圳市",
|
||||
platform: "微信",
|
||||
status: "在线",
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
name: "刘思琪",
|
||||
company: "数据分析咨询",
|
||||
imei: "860789123456789",
|
||||
phone: "13600136001",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
tags: ["中价值", "运营专家", "数据分析师", "Windows用户"],
|
||||
category: "数据分析",
|
||||
pool: "私域池",
|
||||
pool: "公域池",
|
||||
lastActive: "今天 11:15",
|
||||
value: 18900,
|
||||
rfmScore: 72,
|
||||
wechatAccounts: [
|
||||
{ id: "wx5", name: "刘思琪", avatar: "/placeholder.svg?height=40&width=40", tags: ["主要账号"] },
|
||||
],
|
||||
deviceBrand: "OPPO",
|
||||
deviceModel: "Find X6 Pro",
|
||||
location: "广州市",
|
||||
platform: "小红书",
|
||||
status: "活跃",
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
name: "陈浩然",
|
||||
company: "云计算服务商",
|
||||
imei: "860321654987321",
|
||||
phone: "13500135001",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
tags: ["高价值", "架构师", "技术领导", "Linux用户"],
|
||||
category: "技术架构",
|
||||
@@ -116,72 +174,17 @@ export default function UserPortrait() {
|
||||
{ id: "wx6", name: "陈浩然", avatar: "/placeholder.svg?height=40&width=40", tags: ["主要账号", "技术分享"] },
|
||||
{ id: "wx7", name: "陈浩然个人号", avatar: "/placeholder.svg?height=40&width=40", tags: ["个人账号"] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "6",
|
||||
name: "周欣妍",
|
||||
company: "用户体验设计",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
tags: ["中价值", "UI设计师", "用户体验", "iOS用户"],
|
||||
category: "设计创意",
|
||||
pool: "公域池",
|
||||
lastActive: "今天 09:45",
|
||||
value: 12300,
|
||||
rfmScore: 65,
|
||||
wechatAccounts: [
|
||||
{ id: "wx8", name: "周欣妍", avatar: "/placeholder.svg?height=40&width=40", tags: ["主要账号"] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "7",
|
||||
name: "马志强",
|
||||
company: "企业解决方案",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
tags: ["高价值", "解决方案专家", "B2B销售", "Android用户"],
|
||||
category: "企业解决方案",
|
||||
pool: "私域池",
|
||||
lastActive: "今天 08:20",
|
||||
value: 38700,
|
||||
rfmScore: 87,
|
||||
wechatAccounts: [
|
||||
{ id: "wx9", name: "马志强", avatar: "/placeholder.svg?height=40&width=40", tags: ["主要账号", "商务"] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "8",
|
||||
name: "赵丽娟",
|
||||
company: "品牌营销策划",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
tags: ["中高价值", "市场营销", "品牌推广", "iOS用户"],
|
||||
category: "品牌营销",
|
||||
pool: "公域池",
|
||||
lastActive: "今天 07:30",
|
||||
value: 21500,
|
||||
rfmScore: 76,
|
||||
wechatAccounts: [
|
||||
{ id: "wx10", name: "赵丽娟", avatar: "/placeholder.svg?height=40&width=40", tags: ["主要账号"] },
|
||||
{ id: "wx11", name: "赵丽娟工作号", avatar: "/placeholder.svg?height=40&width=40", tags: ["工作账号"] },
|
||||
],
|
||||
deviceBrand: "vivo",
|
||||
deviceModel: "X90 Pro+",
|
||||
location: "杭州市",
|
||||
platform: "微信",
|
||||
status: "在线",
|
||||
},
|
||||
]
|
||||
|
||||
const [users, setUsers] = useState(mockUsers)
|
||||
|
||||
// 流量关键词统计
|
||||
const keywordStats = {
|
||||
totalKeywords: 156,
|
||||
activeKeywords: 124,
|
||||
coverage: 89.4,
|
||||
topKeywords: [
|
||||
{ keyword: "技术解决方案", count: 45, trend: "+12%", value: "高" },
|
||||
{ keyword: "数据分析", count: 38, trend: "+8%", value: "中高" },
|
||||
{ keyword: "用户体验", count: 32, trend: "+15%", value: "中" },
|
||||
{ keyword: "品牌营销", count: 28, trend: "+6%", value: "中高" },
|
||||
{ keyword: "企业服务", count: 25, trend: "+10%", value: "高" },
|
||||
],
|
||||
}
|
||||
|
||||
// 标签管理统计
|
||||
// 标签管理统计 (保持不变)
|
||||
const tagStats = {
|
||||
totalTags: 342,
|
||||
activeTags: 287,
|
||||
@@ -196,22 +199,64 @@ export default function UserPortrait() {
|
||||
],
|
||||
}
|
||||
|
||||
// 获取所有类别和池子
|
||||
// 用户分析统计 (保持不变,但现在是用户画像的一部分)
|
||||
const analysisStats = {
|
||||
totalAnalyzed: 125678,
|
||||
taggedUsers: 119845,
|
||||
avgTagsPerUser: 4.2,
|
||||
autoTagging: 89.5,
|
||||
recentAnalysis: [
|
||||
{ type: "设备品牌分析", count: 45, trend: "+12%" },
|
||||
{ type: "地理位置分析", count: 38, trend: "+8%" },
|
||||
{ type: "行为模式分析", count: 32, trend: "+15%" },
|
||||
{ type: "价值分层分析", count: 28, trend: "+6%" },
|
||||
],
|
||||
}
|
||||
|
||||
// 用户分群数据 (从原 user-pool/page.tsx 整合)
|
||||
const userSegments = [
|
||||
{
|
||||
name: "高价值用户组",
|
||||
count: 12456,
|
||||
description: "RFM评分 > 80",
|
||||
color: "bg-blue-50 text-blue-600",
|
||||
},
|
||||
{
|
||||
name: "潜力用户组",
|
||||
count: 18765,
|
||||
description: "增长潜力用户",
|
||||
color: "bg-green-50 text-green-600",
|
||||
},
|
||||
{
|
||||
name: "技术专家组",
|
||||
count: 8234,
|
||||
description: "技术决策者",
|
||||
color: "bg-purple-50 text-purple-600",
|
||||
},
|
||||
]
|
||||
|
||||
// 获取所有类别、池子和设备品牌
|
||||
const categories = ["all", ...new Set(users.map((user) => user.category))]
|
||||
const pools = ["all", ...new Set(users.map((user) => user.pool))]
|
||||
const devices = ["all", ...new Set(users.map((user) => user.deviceBrand))]
|
||||
|
||||
// 过滤用户的函数
|
||||
const filteredUsers = users.filter((user) => {
|
||||
const matchesSearch =
|
||||
user.name.includes(searchQuery) ||
|
||||
user.company.includes(searchQuery) ||
|
||||
user.imei.includes(searchQuery) ||
|
||||
user.phone.includes(searchQuery) ||
|
||||
user.tags.some((tag) => tag.includes(searchQuery)) ||
|
||||
user.category.includes(searchQuery)
|
||||
user.category.includes(searchQuery) ||
|
||||
user.deviceBrand.includes(searchQuery) || // 搜索中包含设备品牌
|
||||
user.location.includes(searchQuery) // 搜索中包含位置
|
||||
|
||||
const matchesCategory = selectedCategory === "all" || user.category === selectedCategory
|
||||
const matchesPool = selectedPool === "all" || user.pool === selectedPool
|
||||
const matchesDevice = selectedDevice === "all" || user.deviceBrand === selectedDevice // 筛选设备品牌
|
||||
|
||||
return matchesSearch && matchesCategory && matchesPool
|
||||
return matchesSearch && matchesCategory && matchesPool && matchesDevice
|
||||
})
|
||||
|
||||
// 处理用户点击
|
||||
@@ -237,14 +282,14 @@ export default function UserPortrait() {
|
||||
}))
|
||||
}
|
||||
|
||||
const getValueColor = (value: string) => {
|
||||
switch (value) {
|
||||
case "高":
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case "在线":
|
||||
return "bg-green-100 text-green-800"
|
||||
case "中高":
|
||||
case "活跃":
|
||||
return "bg-blue-100 text-blue-800"
|
||||
case "中":
|
||||
return "bg-yellow-100 text-yellow-800"
|
||||
case "离线":
|
||||
return "bg-gray-100 text-gray-800"
|
||||
default:
|
||||
return "bg-gray-100 text-gray-800"
|
||||
}
|
||||
@@ -255,7 +300,7 @@ export default function UserPortrait() {
|
||||
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4">
|
||||
<div>
|
||||
<h1 className="text-2xl md:text-3xl font-bold tracking-tight">用户画像</h1>
|
||||
<p className="text-sm md:text-base text-muted-foreground">数据驱动的用户分析平台</p>
|
||||
<p className="text-sm md:text-base text-muted-foreground">基于IMEI、手机号的用户标签画像分析</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button size="sm" className="flex-1 md:flex-none">
|
||||
@@ -269,23 +314,23 @@ export default function UserPortrait() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 流量关键词管理 */}
|
||||
{/* 用户池概览 (从原 user-pool/page.tsx 整合) */}
|
||||
<Collapsible
|
||||
open={expandedSections.userKeywords}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, userKeywords: open }))}
|
||||
open={expandedSections.overview}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, overview: open }))}
|
||||
>
|
||||
<Card className="border shadow-sm">
|
||||
<Card className="border-none shadow-md">
|
||||
<CollapsibleTrigger className="w-full">
|
||||
<CardHeader className="hover:bg-gray-50 transition-colors cursor-pointer py-3 md:py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardHeader className="bg-gradient-to-r from-blue-50 to-indigo-50 border-b hover:from-blue-100 hover:to-indigo-100 transition-colors cursor-pointer py-3 md:py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<Tag className="h-4 w-4 md:h-5 md:w-5 text-blue-600" />
|
||||
<Users className="h-4 w-4 md:h-5 md:w-5 text-blue-600" />
|
||||
<div className="text-left">
|
||||
<CardTitle className="text-base md:text-lg">流量关键词</CardTitle>
|
||||
<CardDescription className="text-xs md:text-sm">关键词价值评估与分类</CardDescription>
|
||||
<CardTitle className="text-base md:text-lg">用户池概览</CardTitle>
|
||||
<CardDescription className="text-xs md:text-sm">用户数据统计与分布</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
{expandedSections.userKeywords ? (
|
||||
{expandedSections.overview ? (
|
||||
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
|
||||
@@ -294,42 +339,361 @@ export default function UserPortrait() {
|
||||
</CardHeader>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<CardContent className="pt-0 px-4 md:px-6">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 md:gap-4 mb-4">
|
||||
<div className="bg-blue-50 p-2 md:p-3 rounded-lg">
|
||||
<p className="text-xs md:text-sm font-medium text-blue-600">总关键词</p>
|
||||
<p className="text-lg md:text-2xl font-bold text-blue-900">{keywordStats.totalKeywords}</p>
|
||||
<CardContent className="p-4">
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-3 md:gap-4 mb-6">
|
||||
<div className="bg-blue-50 p-3 rounded-lg text-center">
|
||||
<div className="text-lg md:text-2xl font-bold text-blue-600">
|
||||
{poolData.overview.totalUsers.toLocaleString()}
|
||||
</div>
|
||||
<div className="text-xs text-blue-700">总用户数</div>
|
||||
</div>
|
||||
<div className="bg-green-50 p-2 md:p-3 rounded-lg">
|
||||
<p className="text-xs md:text-sm font-medium text-green-600">活跃关键词</p>
|
||||
<p className="text-lg md:text-2xl font-bold text-green-900">{keywordStats.activeKeywords}</p>
|
||||
<div className="bg-green-50 p-3 rounded-lg text-center">
|
||||
<div className="text-lg md:text-2xl font-bold text-green-600">
|
||||
{poolData.overview.activeUsers.toLocaleString()}
|
||||
</div>
|
||||
<div className="text-xs text-green-700">活跃用户</div>
|
||||
</div>
|
||||
<div className="bg-purple-50 p-2 md:p-3 rounded-lg">
|
||||
<p className="text-xs md:text-sm font-medium text-purple-600">覆盖率</p>
|
||||
<p className="text-lg md:text-2xl font-bold text-purple-900">{keywordStats.coverage}%</p>
|
||||
<div className="bg-purple-50 p-3 rounded-lg text-center">
|
||||
<div className="text-lg md:text-2xl font-bold text-purple-600">
|
||||
{poolData.overview.newUsers.toLocaleString()}
|
||||
</div>
|
||||
<div className="text-xs text-purple-700">新增用户</div>
|
||||
</div>
|
||||
<div className="bg-orange-50 p-2 md:p-3 rounded-lg">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full h-full bg-transparent text-xs md:text-sm"
|
||||
onClick={() => router.push("/user-portrait/keywords")}
|
||||
>
|
||||
<Settings className="mr-1 md:mr-2 h-3 w-3 md:h-4 md:w-4" />
|
||||
管理
|
||||
</Button>
|
||||
<div className="bg-orange-50 p-3 rounded-lg text-center">
|
||||
<div className="text-lg md:text-2xl font-bold text-orange-600">
|
||||
{poolData.overview.privatePool.toLocaleString()}
|
||||
</div>
|
||||
<div className="text-xs text-orange-700">私域池</div>
|
||||
</div>
|
||||
<div className="bg-pink-50 p-3 rounded-lg text-center">
|
||||
<div className="text-lg md:text-2xl font-bold text-pink-600">
|
||||
{poolData.overview.publicPool.toLocaleString()}
|
||||
</div>
|
||||
<div className="text-xs text-pink-700">公域池</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{keywordStats.topKeywords.map((item, index) => (
|
||||
<div key={index} className="flex items-center justify-between p-2 md:p-3 bg-gray-50 rounded">
|
||||
<div className="flex items-center gap-2 min-w-0 flex-1">
|
||||
<span className="text-xs md:text-sm font-medium truncate">{item.keyword}</span>
|
||||
<Badge className={`text-xs ${getValueColor(item.value)}`}>{item.value}</Badge>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="bg-gray-50 p-4 rounded-lg">
|
||||
<h3 className="font-medium mb-3 flex items-center">
|
||||
<Smartphone className="h-4 w-4 mr-2 text-gray-600" />
|
||||
设备分布
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm">iOS设备</span>
|
||||
<span className="font-bold">{poolData.deviceStats.ios.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="text-right flex-shrink-0">
|
||||
<p className="text-xs md:text-sm font-bold">{item.count}</p>
|
||||
<p className="text-xs text-green-600">{item.trend}</p>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm">Android设备</span>
|
||||
<span className="font-bold">{poolData.deviceStats.android.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm">其他设备</span>
|
||||
<span className="font-bold">{poolData.deviceStats.other.toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-50 p-4 rounded-lg">
|
||||
<h3 className="font-medium mb-3 flex items-center">
|
||||
<MapPin className="h-4 w-4 mr-2 text-gray-600" />
|
||||
地域分布
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm">一线城市</span>
|
||||
<span className="font-bold">{poolData.locationStats.tier1.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm">二线城市</span>
|
||||
<span className="font-bold">{poolData.locationStats.tier2.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm">三线及以下</span>
|
||||
<span className="font-bold">{poolData.locationStats.tier3.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm">海外用户</span>
|
||||
<span className="font-bold">{poolData.locationStats.overseas.toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</CollapsibleContent>
|
||||
</Card>
|
||||
</Collapsible>
|
||||
|
||||
{/* 用户管理 (原用户池的用户管理,现在是用户画像的一部分) */}
|
||||
<Collapsible
|
||||
open={expandedSections.userManagement}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, userManagement: open }))}
|
||||
>
|
||||
<Card className="border-none shadow-md">
|
||||
<CollapsibleTrigger className="w-full">
|
||||
<CardHeader className="bg-gradient-to-r from-green-50 to-emerald-50 border-b hover:from-green-100 hover:to-emerald-100 transition-colors cursor-pointer py-3 md:py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<UserCheck className="h-4 w-4 md:h-5 md:w-5 text-green-600" />
|
||||
<div className="text-left">
|
||||
<CardTitle className="text-base md:text-lg">用户管理</CardTitle>
|
||||
<CardDescription className="text-xs md:text-sm">
|
||||
IMEI、手机号管理 ({filteredUsers.length})
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
{expandedSections.userManagement ? (
|
||||
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<CardContent className="p-4">
|
||||
{/* 搜索和筛选 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 md:gap-4 mb-4 md:mb-6">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
type="search"
|
||||
placeholder="搜索用户、IMEI、手机号、设备、位置..."
|
||||
className="pl-8 text-sm"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Select value={selectedPool} onValueChange={setSelectedPool}>
|
||||
<SelectTrigger className="text-sm">
|
||||
<SelectValue placeholder="选择流量池" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{pools.map((pool) => (
|
||||
<SelectItem key={pool} value={pool}>
|
||||
{pool === "all" ? "所有流量池" : pool}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select value={selectedDevice} onValueChange={setSelectedDevice}>
|
||||
<SelectTrigger className="text-sm">
|
||||
<SelectValue placeholder="选择设备品牌" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{devices.map((device) => (
|
||||
<SelectItem key={device} value={device}>
|
||||
{device === "all" ? "所有设备" : device}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 移动端卡片布局 */}
|
||||
<div className="space-y-3 md:hidden">
|
||||
{filteredUsers.map((user) => (
|
||||
<div key={user.id} className="bg-white border rounded-lg p-3 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Avatar className="h-8 w-8">
|
||||
<img src={user.avatar || "/placeholder.svg"} alt={user.name} />
|
||||
</Avatar>
|
||||
<div>
|
||||
<p className="font-medium text-sm">{user.name}</p>
|
||||
<p className="text-xs text-gray-500">{user.lastActive}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Badge className={getStatusColor(user.status)}>{user.status}</Badge>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2 text-xs">
|
||||
<div>
|
||||
<p className="text-gray-500">IMEI</p>
|
||||
<p className="font-mono">{user.imei.slice(-6)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-gray-500">手机号</p>
|
||||
<p className="font-medium">{user.phone}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-gray-500">设备</p>
|
||||
<p className="font-medium">{user.deviceBrand}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-gray-500">位置</p>
|
||||
<p className="font-medium">{user.location}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<div>
|
||||
<p className="text-gray-500">流量池</p>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{user.pool}
|
||||
</Badge>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-gray-500">平台</p>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{user.platform}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{user.tags.map((tag, index) => (
|
||||
<Badge key={index} variant="secondary" className="text-xs">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<Button variant="ghost" size="sm" className="text-xs">
|
||||
<MessageCircle className="h-3 w-3 mr-1" />
|
||||
联系
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-xs bg-transparent"
|
||||
onClick={() => handleUserClick(user.id)}
|
||||
>
|
||||
<Eye className="h-3 w-3 mr-1" />
|
||||
查看详情
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 桌面端表格布局 */}
|
||||
<div className="hidden md:block overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="bg-gray-50">
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">用户</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">IMEI/手机号</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">设备信息</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">位置</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">流量池/平台</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">状态</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">标签</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y">
|
||||
{filteredUsers.map((user) => (
|
||||
<tr key={user.id} className="hover:bg-gray-50">
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<div className="flex items-center gap-3">
|
||||
<Avatar>
|
||||
<img src={user.avatar || "/placeholder.svg"} alt={user.name} />
|
||||
</Avatar>
|
||||
<div>
|
||||
<p className="font-medium">{user.name}</p>
|
||||
<p className="text-sm text-gray-500">{user.lastActive}</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<p className="font-mono text-sm">{user.imei}</p>
|
||||
<p className="font-medium">{user.phone}</p>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<div>
|
||||
<p className="font-medium">{user.deviceBrand}</p>
|
||||
<p className="text-sm text-gray-500">{user.deviceModel}</p>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<p className="text-sm">{user.location}</p>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<div className="space-y-1">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{user.pool}
|
||||
</Badge>
|
||||
<p className="text-xs text-gray-500">{user.platform}</p>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<Badge className={getStatusColor(user.status)}>{user.status}</Badge>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<div className="flex flex-wrap gap-1 max-w-xs">
|
||||
{user.tags.slice(0, 3).map((tag, index) => (
|
||||
<Badge key={index} variant="outline" className="text-xs">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
{user.tags.length > 3 && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
+{user.tags.length - 3}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<div className="flex gap-2">
|
||||
<Button variant="ghost" size="sm" onClick={() => handleUserClick(user.id)}>
|
||||
<Eye className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" onClick={() => handleWechatClick(user.id)}>
|
||||
<MessageCircle className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</CollapsibleContent>
|
||||
</Card>
|
||||
</Collapsible>
|
||||
|
||||
{/* 用户分群 (从原 user-pool/page.tsx 整合) */}
|
||||
<Collapsible
|
||||
open={expandedSections.userSegmentation}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, userSegmentation: open }))}
|
||||
>
|
||||
<Card className="border-none shadow-md">
|
||||
<CollapsibleTrigger className="w-full">
|
||||
<CardHeader className="bg-gradient-to-r from-purple-50 to-pink-50 border-b hover:from-purple-100 hover:to-pink-100 transition-colors cursor-pointer py-3 md:py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<Layers className="h-4 w-4 md:h-5 md:w-5 text-purple-600" />
|
||||
<div className="text-left">
|
||||
<CardTitle className="text-base md:text-lg">用户分群</CardTitle>
|
||||
<CardDescription className="text-xs md:text-sm">智能用户分群管理</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
{expandedSections.userSegmentation ? (
|
||||
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<CardContent className="p-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{userSegments.map((segment, index) => (
|
||||
<div key={index} className={`${segment.color} p-4 rounded-lg`}>
|
||||
<h3 className="font-medium mb-2">{segment.name}</h3>
|
||||
<p className="text-2xl font-bold text-current">{segment.count.toLocaleString()}</p>
|
||||
<p className="text-sm text-gray-600 mt-1">{segment.description}</p>
|
||||
<div className="mt-3">
|
||||
<Button size="sm" variant="outline" className="w-full bg-transparent">
|
||||
查看详情
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -339,7 +703,7 @@ export default function UserPortrait() {
|
||||
</Card>
|
||||
</Collapsible>
|
||||
|
||||
{/* 标签管理 */}
|
||||
{/* 标签管理 (原用户画像的标签管理,现在是用户画像的一部分) */}
|
||||
<Collapsible
|
||||
open={expandedSections.tagManagement}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, tagManagement: open }))}
|
||||
@@ -403,223 +767,6 @@ export default function UserPortrait() {
|
||||
</Card>
|
||||
</Collapsible>
|
||||
|
||||
{/* 用户列表 */}
|
||||
<Collapsible
|
||||
open={expandedSections.userList}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, userList: open }))}
|
||||
>
|
||||
<Card className="border shadow-sm">
|
||||
<CollapsibleTrigger className="w-full">
|
||||
<CardHeader className="hover:bg-gray-50 transition-colors cursor-pointer py-3 md:py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Users className="h-4 w-4 md:h-5 md:w-5 text-green-600" />
|
||||
<div className="text-left">
|
||||
<CardTitle className="text-base md:text-lg">用户列表</CardTitle>
|
||||
<CardDescription className="text-xs md:text-sm">
|
||||
用户数据管理 ({filteredUsers.length})
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
{expandedSections.userList ? (
|
||||
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<CardContent className="pt-0 px-4 md:px-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 md:gap-4 mb-4 md:mb-6">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
type="search"
|
||||
placeholder="搜索用户、公司或标签..."
|
||||
className="pl-8 text-sm"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Select value={selectedCategory} onValueChange={setSelectedCategory}>
|
||||
<SelectTrigger className="text-sm">
|
||||
<SelectValue placeholder="选择场景分类" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{categories.map((category) => (
|
||||
<SelectItem key={category} value={category}>
|
||||
{category === "all" ? "所有场景" : category}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select value={selectedPool} onValueChange={setSelectedPool}>
|
||||
<SelectTrigger className="text-sm">
|
||||
<SelectValue placeholder="选择流量池" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{pools.map((pool) => (
|
||||
<SelectItem key={pool} value={pool}>
|
||||
{pool === "all" ? "所有流量池" : pool}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3 md:hidden">
|
||||
{/* 移动端卡片布局 */}
|
||||
{filteredUsers.map((user) => (
|
||||
<div key={user.id} className="bg-white border rounded-lg p-3 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Avatar className="h-8 w-8">
|
||||
<img src={user.avatar || "/placeholder.svg"} alt={user.name} />
|
||||
</Avatar>
|
||||
<div>
|
||||
<p className="font-medium text-sm">{user.name}</p>
|
||||
<p className="text-xs text-gray-500">{user.company}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Badge
|
||||
className={
|
||||
user.rfmScore >= 90
|
||||
? "bg-green-100 text-green-800"
|
||||
: user.rfmScore >= 80
|
||||
? "bg-blue-100 text-blue-800"
|
||||
: user.rfmScore >= 70
|
||||
? "bg-yellow-100 text-yellow-800"
|
||||
: "bg-gray-100 text-gray-800"
|
||||
}
|
||||
>
|
||||
{user.rfmScore}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span className="text-gray-500">{user.lastActive}</span>
|
||||
<span className="font-medium">¥{user.value.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{user.tags.slice(0, 3).map((tag, index) => (
|
||||
<Badge key={index} variant="outline" className="text-xs">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
{user.tags.length > 3 && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
+{user.tags.length - 3}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<Button variant="ghost" size="sm" onClick={() => handleWechatClick(user.id)} className="text-xs">
|
||||
<MessageCircle className="h-3 w-3 mr-1" />
|
||||
{user.wechatAccounts.length}个账号
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onClick={() => handleUserClick(user.id)} className="text-xs">
|
||||
<Eye className="h-3 w-3 mr-1" />
|
||||
查看
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 桌面端表格布局 */}
|
||||
<div className="hidden md:block overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="bg-gray-50">
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">用户</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">公司</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">价值</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">评分</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">微信账号</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">标签</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y">
|
||||
{filteredUsers.map((user) => (
|
||||
<tr key={user.id} className="hover:bg-gray-50">
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<div className="flex items-center gap-3">
|
||||
<Avatar>
|
||||
<img src={user.avatar || "/placeholder.svg"} alt={user.name} />
|
||||
</Avatar>
|
||||
<div>
|
||||
<p className="font-medium">{user.name}</p>
|
||||
<p className="text-sm text-gray-500">{user.lastActive}</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<p className="font-medium">{user.company}</p>
|
||||
<p className="text-sm text-gray-500">{user.category}</p>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<p className="font-medium">¥{user.value.toLocaleString()}</p>
|
||||
<p className="text-sm text-gray-500">{user.pool}</p>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<Badge
|
||||
className={
|
||||
user.rfmScore >= 90
|
||||
? "bg-green-100 text-green-800"
|
||||
: user.rfmScore >= 80
|
||||
? "bg-blue-100 text-blue-800"
|
||||
: user.rfmScore >= 70
|
||||
? "bg-yellow-100 text-yellow-800"
|
||||
: "bg-gray-100 text-gray-800"
|
||||
}
|
||||
>
|
||||
{user.rfmScore}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleWechatClick(user.id)}
|
||||
className="flex items-center"
|
||||
>
|
||||
<MessageCircle className="h-4 w-4 mr-1" />
|
||||
{user.wechatAccounts.length}个
|
||||
</Button>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<div className="flex flex-wrap gap-1 max-w-xs">
|
||||
{user.tags.slice(0, 3).map((tag, index) => (
|
||||
<Badge key={index} variant="outline" className="text-xs">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
{user.tags.length > 3 && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
+{user.tags.length - 3}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<Button variant="ghost" size="sm" onClick={() => handleUserClick(user.id)}>
|
||||
<Eye className="h-4 w-4 mr-1" />
|
||||
查看
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</CollapsibleContent>
|
||||
</Card>
|
||||
</Collapsible>
|
||||
|
||||
{/* 微信账号对话框 */}
|
||||
<Dialog
|
||||
open={selectedWechatDialog.isOpen}
|
||||
|
||||
Reference in New Issue
Block a user