fix: resolve deployment errors and add missing files

Fix CSS issue and create missing documentation files
Add new data platform and mobile optimization features

Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
This commit is contained in:
v0
2025-07-19 02:34:18 +00:00
parent ea95107713
commit 901763fae0
20 changed files with 3216 additions and 1882 deletions

View File

@@ -0,0 +1,47 @@
export default function AIAnalysisLoading() {
return (
<div className="container mx-auto py-4 md:py-6 space-y-4 md:space-y-6">
{/* 页面标题骨架 */}
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4">
<div>
<div className="h-8 bg-gray-200 rounded w-32 mb-2 animate-pulse"></div>
<div className="h-4 bg-gray-200 rounded w-48 animate-pulse"></div>
</div>
<div className="flex gap-2">
<div className="h-9 w-24 bg-gray-200 rounded animate-pulse"></div>
<div className="h-9 w-9 bg-gray-200 rounded animate-pulse"></div>
<div className="h-9 w-9 bg-gray-200 rounded animate-pulse"></div>
</div>
</div>
{/* 概览卡片骨架 */}
<div className="bg-white rounded-lg shadow-md p-4">
<div className="h-6 bg-gray-200 rounded w-32 mb-4 animate-pulse"></div>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-3 md:gap-4">
{Array.from({ length: 6 }).map((_, i) => (
<div key={i} className="bg-gray-50 p-3 rounded-lg">
<div className="h-8 bg-gray-200 rounded w-12 mb-2 animate-pulse"></div>
<div className="h-3 bg-gray-200 rounded w-16 animate-pulse"></div>
</div>
))}
</div>
</div>
{/* 内容卡片骨架 */}
{Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="bg-white rounded-lg shadow-md p-4">
<div className="h-6 bg-gray-200 rounded w-40 mb-4 animate-pulse"></div>
<div className="space-y-3">
{Array.from({ length: 2 }).map((_, j) => (
<div key={j} className="bg-gray-50 border rounded-lg p-3">
<div className="h-5 bg-gray-200 rounded w-48 mb-2 animate-pulse"></div>
<div className="h-4 bg-gray-200 rounded w-full mb-2 animate-pulse"></div>
<div className="h-3 bg-gray-200 rounded w-32 animate-pulse"></div>
</div>
))}
</div>
</div>
))}
</div>
)
}

442
app/ai-analysis/page.tsx Normal file
View File

@@ -0,0 +1,442 @@
"use client"
import { useState } from "react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { Progress } from "@/components/ui/progress"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import {
Brain,
TrendingUp,
AlertTriangle,
Zap,
Filter,
RefreshCw,
Download,
ChevronRight,
ChevronDown,
} from "lucide-react"
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
export default function AIAnalysisPage() {
const [timeRange, setTimeRange] = useState("today")
const [expandedSections, setExpandedSections] = useState({
overview: true,
trends: true,
anomaly: false,
predictions: false,
})
// AI分析数据
const aiData = {
overview: {
totalInsights: 156,
trendsIdentified: 23,
anomaliesDetected: 3,
predictionsGenerated: 89,
accuracy: 94.2,
modelPerformance: 97.8,
},
trendAnalysis: [
{
trend: "用户活跃度上升趋势",
confidence: 95.8,
impact: "高",
timeframe: "未来7天",
description: "基于用户行为模式分析,预测用户活跃度将持续上升",
recommendation: "增加内容推送频率,优化用户体验",
},
{
trend: "移动端使用增长",
confidence: 89.2,
impact: "中高",
timeframe: "未来14天",
description: "移动端用户访问量呈现明显增长趋势",
recommendation: "优化移动端界面,提升响应速度",
},
{
trend: "高价值用户转化提升",
confidence: 92.5,
impact: "高",
timeframe: "未来30天",
description: "高价值用户转化率预计将显著提升",
recommendation: "制定针对性营销策略,加强用户维护",
},
],
anomalyDetection: [
{
type: "流量异常",
severity: "中等",
detected: "今天 13:45",
description: "检测到异常流量峰值比平时高出45%",
status: "已处理",
action: "已启动自动扩容机制",
},
{
type: "用户行为异常",
severity: "低",
detected: "今天 11:20",
description: "部分用户群体行为模式发生变化",
status: "监控中",
action: "持续观察用户行为变化",
},
{
type: "数据质量异常",
severity: "高",
detected: "今天 09:15",
description: "数据源同步出现延迟,影响实时分析",
status: "已修复",
action: "已重新同步数据,恢复正常",
},
],
predictiveModels: [
{
model: "用户流失预测模型",
accuracy: 94.2,
lastTrained: "今天 06:00",
predictions: 1245,
status: "运行中",
description: "预测用户流失风险准确率达94.2%",
},
{
model: "价值提升预测模型",
accuracy: 91.8,
lastTrained: "今天 05:30",
predictions: 856,
status: "运行中",
description: "预测用户价值提升潜力,指导运营策略",
},
{
model: "内容推荐模型",
accuracy: 87.5,
lastTrained: "今天 04:45",
predictions: 2341,
status: "运行中",
description: "个性化内容推荐,提升用户参与度",
},
],
}
const toggleSection = (section: string) => {
setExpandedSections((prev) => ({
...prev,
[section]: !prev[section],
}))
}
const getImpactColor = (impact: string) => {
switch (impact) {
case "高":
return "bg-red-100 text-red-800"
case "中高":
return "bg-orange-100 text-orange-800"
case "中":
return "bg-yellow-100 text-yellow-800"
case "低":
return "bg-green-100 text-green-800"
default:
return "bg-gray-100 text-gray-800"
}
}
const getSeverityColor = (severity: string) => {
switch (severity) {
case "高":
return "bg-red-100 text-red-800"
case "中等":
return "bg-yellow-100 text-yellow-800"
case "低":
return "bg-green-100 text-green-800"
default:
return "bg-gray-100 text-gray-800"
}
}
const getStatusColor = (status: string) => {
switch (status) {
case "运行中":
return "bg-green-100 text-green-800"
case "已处理":
return "bg-blue-100 text-blue-800"
case "监控中":
return "bg-yellow-100 text-yellow-800"
case "已修复":
return "bg-green-100 text-green-800"
default:
return "bg-gray-100 text-gray-800"
}
}
return (
<div className="container mx-auto py-4 md:py-6 space-y-4 md:space-y-6">
{/* 页面标题和工具栏 */}
<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">AI分析</h1>
<p className="text-sm md:text-base text-muted-foreground mt-1"></p>
</div>
<div className="flex flex-wrap gap-2">
<Select value={timeRange} onValueChange={setTimeRange}>
<SelectTrigger className="w-[120px] text-sm">
<SelectValue placeholder="时间范围" />
</SelectTrigger>
<SelectContent>
<SelectItem value="today"></SelectItem>
<SelectItem value="week"></SelectItem>
<SelectItem value="month"></SelectItem>
</SelectContent>
</Select>
<Button variant="outline" size="icon" className="h-9 w-9 bg-transparent">
<Filter className="h-4 w-4" />
</Button>
<Button variant="outline" size="icon" className="h-9 w-9 bg-transparent">
<RefreshCw className="h-4 w-4" />
</Button>
<Button variant="outline" size="sm" className="hidden md:flex bg-transparent">
<Download className="mr-2 h-4 w-4" />
</Button>
</div>
</div>
{/* AI分析概览 */}
<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-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">
<Brain 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">AI分析概览</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-3 lg:grid-cols-6 gap-3 md:gap-4">
<div className="bg-purple-50 p-3 rounded-lg text-center">
<div className="text-lg md:text-2xl font-bold text-purple-600">{aiData.overview.totalInsights}</div>
<div className="text-xs text-purple-700">AI洞察</div>
</div>
<div className="bg-blue-50 p-3 rounded-lg text-center">
<div className="text-lg md:text-2xl font-bold text-blue-600">{aiData.overview.trendsIdentified}</div>
<div className="text-xs text-blue-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">
{aiData.overview.anomaliesDetected}
</div>
<div className="text-xs text-orange-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">
{aiData.overview.predictionsGenerated}
</div>
<div className="text-xs text-green-700"></div>
</div>
<div className="bg-indigo-50 p-3 rounded-lg text-center">
<div className="text-lg md:text-2xl font-bold text-indigo-600">{aiData.overview.accuracy}%</div>
<div className="text-xs text-indigo-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">{aiData.overview.modelPerformance}%</div>
<div className="text-xs text-pink-700"></div>
</div>
</div>
</CardContent>
</CollapsibleContent>
</Card>
</Collapsible>
{/* 趋势识别 */}
<Collapsible
open={expandedSections.trends}
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, trends: 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">
<TrendingUp 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">AI驱动的趋势预测分析</CardDescription>
</div>
</div>
{expandedSections.trends ? (
<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">
{aiData.trendAnalysis.map((trend, 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-start justify-between gap-3">
<div className="flex-1">
<div className="flex items-center gap-2 mb-2">
<h3 className="font-medium text-sm md:text-base">{trend.trend}</h3>
<Badge className={getImpactColor(trend.impact)}>{trend.impact}</Badge>
</div>
<p className="text-xs md:text-sm text-gray-600 mb-2">{trend.description}</p>
<p className="text-xs text-blue-600 font-medium">: {trend.recommendation}</p>
</div>
<div className="flex flex-col md:items-end gap-2">
<div className="text-right">
<div className="text-sm font-bold text-green-600">{trend.confidence}%</div>
<div className="text-xs text-gray-500"></div>
</div>
<div className="text-right">
<div className="text-sm font-medium">{trend.timeframe}</div>
<div className="text-xs text-gray-500"></div>
</div>
</div>
</div>
<div className="mt-3">
<div className="flex justify-between text-xs mb-1">
<span></span>
<span>{trend.confidence}%</span>
</div>
<Progress value={trend.confidence} className="h-1.5" />
</div>
</div>
))}
</div>
</CardContent>
</CollapsibleContent>
</Card>
</Collapsible>
{/* 异常检测 */}
<Collapsible
open={expandedSections.anomaly}
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, anomaly: 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">
<AlertTriangle 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.anomaly ? (
<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">
{aiData.anomalyDetection.map((anomaly, 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">
<h3 className="font-medium text-sm md:text-base">{anomaly.type}</h3>
<Badge className={getSeverityColor(anomaly.severity)}>{anomaly.severity}</Badge>
<Badge className={getStatusColor(anomaly.status)}>{anomaly.status}</Badge>
</div>
<p className="text-xs md:text-sm text-gray-600 mb-2">{anomaly.description}</p>
<p className="text-xs text-blue-600 font-medium">: {anomaly.action}</p>
</div>
<div className="text-right">
<div className="text-sm font-medium">{anomaly.detected}</div>
<div className="text-xs text-gray-500"></div>
</div>
</div>
</div>
))}
</div>
</CardContent>
</CollapsibleContent>
</Card>
</Collapsible>
{/* 预测模型 */}
<Collapsible
open={expandedSections.predictions}
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, predictions: 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">
<Zap 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"></CardDescription>
</div>
</div>
{expandedSections.predictions ? (
<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">
{aiData.predictiveModels.map((model, 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">
<h3 className="font-medium text-sm md:text-base">{model.model}</h3>
<Badge className={getStatusColor(model.status)}>{model.status}</Badge>
</div>
<p className="text-xs md:text-sm text-gray-600 mb-2">{model.description}</p>
<div className="flex flex-wrap gap-4 text-xs">
<span>: {model.lastTrained}</span>
<span>: {model.predictions.toLocaleString()}</span>
</div>
</div>
<div className="text-right">
<div className="text-lg font-bold text-green-600">{model.accuracy}%</div>
<div className="text-xs text-gray-500"></div>
</div>
</div>
<div className="mt-3">
<div className="flex justify-between text-xs mb-1">
<span></span>
<span>{model.accuracy}%</span>
</div>
<Progress value={model.accuracy} className="h-1.5" />
</div>
</div>
))}
</div>
</CardContent>
</CollapsibleContent>
</Card>
</Collapsible>
</div>
)
}

View File

@@ -2,13 +2,14 @@
import Link from "next/link"
import { usePathname } from "next/navigation"
import { Home, Users, Target, Database } from "lucide-react"
import { Home, Database, Search, Target, Brain } from "lucide-react"
const navItems = [
{ href: "/", icon: Home, label: "概览" },
{ href: "/data-integration", icon: Database, label: "数据集成" },
{ href: "/user-portrait", icon: Users, label: "用户画像" },
{ href: "/user-value", icon: Target, 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分析" },
]
export default function BottomNav() {
@@ -24,7 +25,7 @@ export default function BottomNav() {
<Link
key={item.href}
href={item.href}
className={`flex flex-col items-center justify-center px-2 py-2 rounded-xl transition-all duration-300 min-w-0 flex-1 ${
className={`flex flex-col items-center justify-center px-1 py-2 rounded-xl transition-all duration-300 min-w-0 flex-1 ${
isActive ? "glass-heavy text-blue-600 scale-105" : "text-gray-600 hover:glass-light hover:text-blue-500"
}`}
>

View File

@@ -12,9 +12,11 @@ import {
X,
ChevronDown,
ChevronRight,
Tag,
Search,
BarChart3,
TrendingUp,
Brain,
HelpCircle,
} from "lucide-react"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
@@ -27,8 +29,9 @@ interface MobileSidebarProps {
export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
const pathname = usePathname()
const [expandedSections, setExpandedSections] = useState({
"user-portrait": false,
"user-value": false,
"user-discovery": false,
"user-valuation": false,
"ai-analysis": false,
})
const toggleSection = (section: string) => {
@@ -59,56 +62,83 @@ export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
description: "平台整体数据分析",
},
{
title: "数据集成",
href: "/data-integration",
title: "数据中台",
href: "/data-platform",
icon: <Database className="h-4 w-4" />,
primary: true,
tag: "核心",
description: "多源数据整合平台",
description: "多源数据整合中心",
},
{
title: "用户画像",
href: "/user-portrait",
icon: <Users className="h-4 w-4" />,
title: "用户发现",
href: "/user-discovery",
icon: <Search className="h-4 w-4" />,
primary: true,
expandable: true,
section: "user-portrait",
description: "用户标签与分群分析",
section: "user-discovery",
description: "用户行为洞察分析",
children: [
{
title: "流量关键词",
href: "/user-portrait/keywords",
icon: <Tag className="h-3 w-3" />,
description: "关键词价值评估",
title: "行为分析",
href: "/user-discovery/behavior",
icon: <BarChart3 className="h-3 w-3" />,
description: "用户行为模式分析",
},
{
title: "标签管理",
href: "/user-portrait/tags",
icon: <Tag className="h-3 w-3" />,
description: "用户标签分类",
title: "用户分群",
href: "/user-discovery/segmentation",
icon: <Users className="h-3 w-3" />,
description: "智能用户分群",
},
],
},
{
title: "价值评估",
href: "/user-value",
title: "用户估值",
href: "/user-valuation",
icon: <Target className="h-4 w-4" />,
primary: true,
expandable: true,
section: "user-value",
description: "用户价值分析模型",
section: "user-valuation",
description: "用户价值评估模型",
hasHelp: true,
children: [
{
title: "估值模型",
href: "/user-value/model",
href: "/user-valuation/model",
icon: <BarChart3 className="h-3 w-3" />,
description: "RFM价值模型",
description: "RFM价值评估",
hasHelp: true,
},
{
title: "升级路径",
href: "/user-value/upgrade-paths",
href: "/user-valuation/upgrade-paths",
icon: <TrendingUp className="h-3 w-3" />,
description: "用户价值提升",
description: "用户价值提升策略",
hasHelp: true,
},
],
},
{
title: "AI分析",
href: "/ai-analysis",
icon: <Brain 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: "智能异常监控",
},
],
},
@@ -118,6 +148,8 @@ 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"
}
@@ -137,7 +169,7 @@ export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
{/* 侧边栏 */}
<div
className={cn(
"fixed left-0 top-0 h-full w-72 glass-nav safe-area-top safe-area-left z-50 transition-transform duration-300 ease-out",
"fixed left-0 top-0 h-full w-80 glass-nav safe-area-top safe-area-left z-50 transition-transform duration-300 ease-out",
isOpen ? "translate-x-0" : "-translate-x-full",
)}
>
@@ -173,7 +205,10 @@ export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
<div className="transition-colors duration-300 flex-shrink-0">{item.icon}</div>
<div className="flex-1 ml-2 min-w-0">
<div className="flex items-center justify-between">
<span className="font-medium truncate">{item.title}</span>
<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
className={cn("text-xs px-1.5 py-0.5 rounded ml-1 flex-shrink-0", getTagColor(item.tag))}
@@ -218,7 +253,10 @@ export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
{subItem.icon}
</div>
<div className="min-w-0 flex-1">
<p className="font-medium truncate">{subItem.title}</p>
<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="text-xs text-gray-500 truncate">{subItem.description}</p>
</div>
</Link>
@@ -234,15 +272,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"></span>
<span className="text-gray-600">AI引擎状态</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">2</span>
<span className="text-gray-600"></span>
<span className="text-gray-500"></span>
</div>
</div>
</div>

View File

@@ -1,3 +1,61 @@
export default function Loading() {
return null
export default function DataPlatformLoading() {
return (
<div className="container mx-auto py-4 md:py-6 space-y-4 md:space-y-6">
{/* 页面标题骨架 */}
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4">
<div>
<div className="h-8 bg-gray-200 rounded w-32 mb-2 animate-pulse"></div>
<div className="h-4 bg-gray-200 rounded w-48 animate-pulse"></div>
</div>
<div className="flex gap-2">
<div className="h-9 w-24 bg-gray-200 rounded animate-pulse"></div>
<div className="h-9 w-9 bg-gray-200 rounded animate-pulse"></div>
<div className="h-9 w-9 bg-gray-200 rounded animate-pulse"></div>
</div>
</div>
{/* 概览卡片骨架 */}
<div className="bg-white rounded-lg shadow-md p-4">
<div className="h-6 bg-gray-200 rounded w-32 mb-4 animate-pulse"></div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 md:gap-4">
{Array.from({ length: 4 }).map((_, i) => (
<div key={i} className="bg-gray-50 p-3 rounded-lg">
<div className="h-8 bg-gray-200 rounded w-12 mb-2 animate-pulse"></div>
<div className="h-3 bg-gray-200 rounded w-16 animate-pulse"></div>
</div>
))}
</div>
</div>
{/* 数据源卡片骨架 */}
<div className="bg-white rounded-lg shadow-md p-4">
<div className="h-6 bg-gray-200 rounded w-40 mb-4 animate-pulse"></div>
<div className="space-y-3">
{Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="bg-gray-50 border rounded-lg p-3">
<div className="h-5 bg-gray-200 rounded w-48 mb-2 animate-pulse"></div>
<div className="h-4 bg-gray-200 rounded w-full mb-2 animate-pulse"></div>
<div className="h-3 bg-gray-200 rounded w-32 animate-pulse"></div>
</div>
))}
</div>
</div>
{/* 其他内容骨架 */}
{Array.from({ length: 2 }).map((_, i) => (
<div key={i} className="bg-white rounded-lg shadow-md p-4">
<div className="h-6 bg-gray-200 rounded w-40 mb-4 animate-pulse"></div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{Array.from({ length: 3 }).map((_, j) => (
<div key={j} className="bg-gray-50 p-4 rounded-lg">
<div className="h-4 bg-gray-200 rounded w-24 mb-2 animate-pulse"></div>
<div className="h-6 bg-gray-200 rounded w-16 mb-2 animate-pulse"></div>
<div className="h-2 bg-gray-200 rounded w-full animate-pulse"></div>
</div>
))}
</div>
</div>
))}
</div>
)
}

View File

@@ -1,433 +1,490 @@
"use client"
import { useState } from "react"
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Database, Server, Link, RefreshCw, Download, Mail, FileText } from "lucide-react"
import { Textarea } from "@/components/ui/textarea"
import { Badge } from "@/components/ui/badge"
import { Progress } from "@/components/ui/progress"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Badge } from "@/components/ui/badge"
import { Checkbox } from "@/components/ui/checkbox"
import { Label } from "@/components/ui/label"
import { DataSourceList } from "@/components/data-integration/data-source-list"
import { DataFieldMapping } from "@/components/data-integration/data-field-mapping"
import { DataCollectionSettings } from "@/components/data-integration/data-collection-settings"
import { ApiDocumentation } from "@/components/data-integration/api-documentation"
interface DataSource {
id: string
name: string
type: "mysql" | "postgresql" | "mongodb" | "oracle" | "sqlserver" | "api" | "file"
status: "connected" | "disconnected" | "error"
lastSync: string
tables: number
records: number
}
Database,
Upload,
Download,
RefreshCw,
CheckCircle,
AlertCircle,
Clock,
FileText,
Settings,
ChevronRight,
ChevronDown,
} from "lucide-react"
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
export default function DataPlatformPage() {
const [activeTab, setActiveTab] = useState("data-integration")
const [selectedUserSegment, setSelectedUserSegment] = useState("")
const [analysisPrompt, setAnalysisPrompt] = useState("")
const [selectedEmail, setSelectedEmail] = useState("")
const [isAddingDataSource, setIsAddingDataSource] = useState(false)
const [timeRange, setTimeRange] = useState("today")
const [expandedSections, setExpandedSections] = useState({
overview: true,
sources: true,
integration: false,
quality: false,
})
// 数据源列表
const dataSources: DataSource[] = [
{
id: "1",
name: "用户数据库",
type: "mysql",
status: "connected",
lastSync: "2023-07-21 15:30",
tables: 12,
records: 1250000,
// 数据中台数据
const platformData = {
overview: {
totalRecords: 1245678,
dataSources: 8,
syncStatus: "正常",
lastUpdate: "2分钟前",
dataQuality: 94.6,
storageUsed: 2.3,
storageTotal: 10.0,
},
{
id: "2",
name: "交易系统",
type: "postgresql",
status: "connected",
lastSync: "2023-07-21 14:45",
tables: 8,
records: 3450000,
dataSources: [
{
name: "用户行为数据库",
type: "MySQL",
status: "connected",
records: 456789,
lastSync: "1分钟前",
quality: 96.8,
description: "用户行为轨迹数据",
},
{
name: "CRM系统",
type: "API",
status: "connected",
records: 125678,
lastSync: "3分钟前",
quality: 94.2,
description: "客户关系管理数据",
},
{
name: "内容平台数据",
type: "API",
status: "syncing",
records: 234567,
lastSync: "正在同步",
quality: 92.5,
description: "抖音、小红书等平台数据",
},
{
name: "触客宝后台",
type: "API",
status: "connected",
records: 89234,
lastSync: "5分钟前",
quality: 95.1,
description: "呼入电话信息数据",
},
{
name: "飞书妙记",
type: "API",
status: "connected",
records: 12345,
lastSync: "10分钟前",
quality: 91.3,
description: "会议记录和内容摘要",
},
{
name: "表单提交数据",
type: "Webhook",
status: "connected",
records: 67890,
lastSync: "2分钟前",
quality: 97.2,
description: "在线表单提交数据",
},
],
integrationStats: {
totalIntegrations: 8,
activeIntegrations: 7,
failedIntegrations: 0,
pendingIntegrations: 1,
avgSyncTime: "2.3分钟",
successRate: 98.7,
},
{
id: "3",
name: "用户行为数据",
type: "mongodb",
status: "connected",
lastSync: "2023-07-21 13:20",
tables: 5,
records: 7800000,
dataQuality: {
completeness: 94.6,
accuracy: 96.2,
consistency: 92.8,
timeliness: 95.4,
validity: 93.7,
uniqueness: 97.1,
},
{
id: "4",
name: "CRM系统",
type: "oracle",
status: "error",
lastSync: "2023-07-20 09:15",
tables: 15,
records: 2100000,
},
]
// 用户画像分群列表
const userSegments = [
{ id: "high-value", name: "高价值用户", count: 12500 },
{ id: "new-users", name: "新注册用户", count: 45600 },
{ id: "inactive", name: "非活跃用户", count: 28900 },
{ id: "potential", name: "潜在高转化用户", count: 18700 },
{ id: "loyal", name: "忠诚用户", count: 9800 },
{ id: "risk", name: "流失风险用户", count: 15400 },
]
// 邮箱列表
const emailList = [
{ id: "1", email: "marketing@company.com", name: "营销团队" },
{ id: "2", email: "sales@company.com", name: "销售团队" },
{ id: "3", email: "product@company.com", name: "产品团队" },
{ id: "4", email: "executive@company.com", name: "管理层" },
]
const getDataSourceIcon = (type: DataSource["type"]) => {
switch (type) {
case "mysql":
case "postgresql":
case "oracle":
case "sqlserver":
return <Database className="h-8 w-8 text-blue-500" />
case "mongodb":
return <Server className="h-8 w-8 text-green-500" />
case "api":
case "file":
return <Link className="h-8 w-8 text-purple-500" />
}
}
const getDataSourceTypeName = (type: DataSource["type"]) => {
switch (type) {
case "mysql":
return "MySQL"
case "postgresql":
return "PostgreSQL"
case "mongodb":
return "MongoDB"
case "oracle":
return "Oracle"
case "sqlserver":
return "SQL Server"
case "api":
return "API"
case "file":
return "文件"
}
const toggleSection = (section: string) => {
setExpandedSections((prev) => ({
...prev,
[section]: !prev[section],
}))
}
const getStatusBadge = (status: DataSource["status"]) => {
const getStatusColor = (status: string) => {
switch (status) {
case "connected":
return <Badge className="bg-green-100 text-green-800"></Badge>
case "disconnected":
return <Badge className="bg-gray-100 text-gray-800"></Badge>
return "bg-green-100 text-green-800"
case "syncing":
return "bg-yellow-100 text-yellow-800"
case "error":
return <Badge className="bg-red-100 text-red-800"></Badge>
return "bg-red-100 text-red-800"
case "disconnected":
return "bg-gray-100 text-gray-800"
default:
return "bg-gray-100 text-gray-800"
}
}
const getStatusIcon = (status: string) => {
switch (status) {
case "connected":
return <CheckCircle className="h-4 w-4 text-green-600" />
case "syncing":
return <Clock className="h-4 w-4 text-yellow-600 animate-spin" />
case "error":
return <AlertCircle className="h-4 w-4 text-red-600" />
default:
return <AlertCircle className="h-4 w-4 text-gray-600" />
}
}
return (
<div className="container mx-auto p-6 space-y-6">
<div className="container mx-auto py-4 md:py-6 space-y-4 md:space-y-6">
{/* 页面标题和工具栏 */}
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4">
<div>
<h1 className="text-3xl font-bold"></h1>
<p className="text-muted-foreground mt-1">API管理与数据采集</p>
<h1 className="text-2xl md:text-3xl font-bold"></h1>
<p className="text-sm md:text-base text-muted-foreground mt-1"></p>
</div>
<div className="flex gap-2">
<Button variant="outline" size="icon">
<div className="flex flex-wrap gap-2">
<Select value={timeRange} onValueChange={setTimeRange}>
<SelectTrigger className="w-[120px] text-sm">
<SelectValue placeholder="时间范围" />
</SelectTrigger>
<SelectContent>
<SelectItem value="today"></SelectItem>
<SelectItem value="week"></SelectItem>
<SelectItem value="month"></SelectItem>
</SelectContent>
</Select>
<Button variant="outline" size="icon" className="h-9 w-9 bg-transparent">
<Upload className="h-4 w-4" />
</Button>
<Button variant="outline" size="icon" className="h-9 w-9 bg-transparent">
<RefreshCw className="h-4 w-4" />
</Button>
<Button variant="outline">
<Button variant="outline" size="sm" className="hidden md:flex bg-transparent">
<Download className="mr-2 h-4 w-4" />
</Button>
</div>
</div>
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-4">
<TabsList className="grid grid-cols-4 w-full max-w-2xl">
<TabsTrigger value="data-integration"></TabsTrigger>
<TabsTrigger value="data-collection"></TabsTrigger>
<TabsTrigger value="data-mapping"></TabsTrigger>
<TabsTrigger value="api-management">API管理</TabsTrigger>
</TabsList>
{/* 数据集成标签页 */}
<TabsContent value="data-integration" className="space-y-4">
<Card className="border shadow-md">
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
{/* 数据中台概览 */}
<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>
<CardContent>
<DataSourceList />
</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>
</Card>
</CollapsibleContent>
</Card>
</Collapsible>
<Card className="border shadow-md">
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>IDIMEI设备号的数据整合</CardDescription>
{/* 数据源管理 */}
<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>
<CardContent>
<div className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Card className="border shadow-sm">
<CardContent className="p-4 flex flex-col items-center justify-center">
<div className="bg-blue-100 p-3 rounded-full mb-2">
<Database className="h-6 w-6 text-blue-600" />
</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>
<h3 className="font-medium text-center">ID</h3>
<p className="text-sm text-muted-foreground text-center">1,245,678 </p>
</CardContent>
</Card>
<Card className="border shadow-sm">
<CardContent className="p-4 flex flex-col items-center justify-center">
<div className="bg-green-100 p-3 rounded-full mb-2">
<FileText className="h-6 w-6 text-green-600" />
<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>
</div>
<h3 className="font-medium text-center"></h3>
<p className="text-sm text-muted-foreground text-center">987,543 </p>
</CardContent>
</Card>
<Card className="border shadow-sm">
<CardContent className="p-4 flex flex-col items-center justify-center">
<div className="bg-purple-100 p-3 rounded-full mb-2">
<FileText className="h-6 w-6 text-purple-600" />
</div>
<h3 className="font-medium text-center"></h3>
<p className="text-sm text-muted-foreground text-center">654,321 </p>
</CardContent>
</Card>
<Card className="border shadow-sm">
<CardContent className="p-4 flex flex-col items-center justify-center">
<div className="bg-orange-100 p-3 rounded-full mb-2">
<Server className="h-6 w-6 text-orange-600" />
</div>
<h3 className="font-medium text-center">IMEI设备号</h3>
<p className="text-sm text-muted-foreground text-center">789,456 </p>
</CardContent>
</Card>
</div>
<div className="flex justify-between items-center">
<div>
<h3 className="font-medium"></h3>
<p className="text-sm text-muted-foreground">最后更新: 2023-07-21 16:30</p>
</div>
<div className="mt-3">
<Progress value={source.quality} className="h-1.5" />
</div>
</div>
<Button>
<RefreshCw className="mr-2 h-4 w-4" />
</Button>
</div>
))}
</div>
</CardContent>
</CollapsibleContent>
</Card>
</Collapsible>
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="font-medium"></span>
<span className="text-green-600 font-medium">87%</span>
{/* 数据集成统计 */}
<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 className="w-full bg-gray-200 rounded-full h-2.5">
<div className="bg-green-600 h-2.5 rounded-full" style={{ width: "87%" }}></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>
</div>
</div>
</div>
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="font-medium"></span>
<span className="text-blue-600 font-medium">92/100</span>
<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="w-full bg-gray-200 rounded-full h-2.5">
<div className="bg-blue-600 h-2.5 rounded-full" style={{ width: "92%" }}></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="space-y-2">
<div className="flex justify-between items-center">
<span className="font-medium"></span>
<span className="text-yellow-600 font-medium">5.2%</span>
<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="w-full bg-gray-200 rounded-full h-2.5">
<div className="bg-yellow-600 h-2.5 rounded-full" style={{ width: "5.2%" }}></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>
</Card>
</TabsContent>
</CollapsibleContent>
</Card>
</Collapsible>
{/* 数据采集标签页 */}
<TabsContent value="data-collection" className="space-y-4">
<DataCollectionSettings />
</TabsContent>
{/* 数据质量监控 */}
<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: "唯一性",
}
{/* 数据映射标签页 */}
<TabsContent value="data-mapping" className="space-y-4">
<DataFieldMapping />
</TabsContent>
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",
]
{/* API管理标签页 */}
<TabsContent value="api-management" className="space-y-4">
<ApiDocumentation />
</TabsContent>
</Tabs>
{/* 添加数据源对话框 */}
<Dialog open={isAddingDataSource} onOpenChange={setIsAddingDataSource}>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="datasource-type" className="text-right">
</Label>
<Select>
<SelectTrigger className="col-span-3">
<SelectValue placeholder="选择数据源类型" />
</SelectTrigger>
<SelectContent>
<SelectItem value="mysql">MySQL</SelectItem>
<SelectItem value="postgresql">PostgreSQL</SelectItem>
<SelectItem value="mongodb">MongoDB</SelectItem>
<SelectItem value="oracle">Oracle</SelectItem>
<SelectItem value="sqlserver">SQL Server</SelectItem>
<SelectItem value="api">API</SelectItem>
<SelectItem value="file"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="name" className="text-right">
</Label>
<Input id="name" className="col-span-3" />
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="host" className="text-right">
</Label>
<Input id="host" className="col-span-3" />
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="port" className="text-right">
</Label>
<Input id="port" className="col-span-3" />
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="username" className="text-right">
</Label>
<Input id="username" className="col-span-3" />
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="password" className="text-right">
</Label>
<Input id="password" type="password" className="col-span-3" />
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="database" className="text-right">
</Label>
<Input id="database" className="col-span-3" />
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setIsAddingDataSource(false)}>
</Button>
<Button onClick={() => setIsAddingDataSource(false)}></Button>
<Button onClick={() => setIsAddingDataSource(false)}></Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* AI数据分析对话框 */}
<Dialog>
<DialogTrigger asChild>
<Button className="hidden">AI数据分析</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[600px]">
<DialogHeader>
<DialogTitle>AI数据分析</DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="space-y-2">
<Label></Label>
<Select value={selectedUserSegment} onValueChange={setSelectedUserSegment}>
<SelectTrigger>
<SelectValue placeholder="选择用户分群" />
</SelectTrigger>
<SelectContent>
{userSegments.map((segment) => (
<SelectItem key={segment.id} value={segment.id}>
{segment.name} ({segment.count.toLocaleString()})
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label></Label>
<Textarea
placeholder="例如:分析该用户群体的消费习惯和偏好"
value={analysisPrompt}
onChange={(e) => setAnalysisPrompt(e.target.value)}
rows={3}
/>
</div>
<div className="space-y-2">
<Label></Label>
<Select value={selectedEmail} onValueChange={setSelectedEmail}>
<SelectTrigger>
<SelectValue placeholder="选择接收邮箱" />
</SelectTrigger>
<SelectContent>
{emailList.map((item) => (
<SelectItem key={item.id} value={item.id}>
{item.name} ({item.email})
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex items-center space-x-2">
<Checkbox id="schedule" />
<Label htmlFor="schedule"></Label>
</div>
</div>
<DialogFooter>
<Button variant="outline"></Button>
<Button>
<Mail className="mr-2 h-4 w-4" />
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
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>
</div>
)
}

View File

@@ -103,15 +103,18 @@
/* 移动端毛玻璃效果优化 */
@media (max-width: 768px) {
.glass {
@apply backdrop-blur-sm bg-white/15;
@apply backdrop-blur-sm;
background-color: rgba(255, 255, 255, 0.15);
}
.glass-light {
@apply backdrop-blur-sm bg-white/25;
@apply backdrop-blur-sm;
background-color: rgba(255, 255, 255, 0.25);
}
.glass-heavy {
@apply backdrop-blur-md bg-white/35;
@apply backdrop-blur-md;
background-color: rgba(255, 255, 255, 0.35);
}
}

3
app/loading.tsx Normal file
View File

@@ -0,0 +1,3 @@
export default function Loading() {
return null
}

View File

@@ -1,308 +1,407 @@
"use client"
import { useRouter } from "next/navigation"
import { cn } from "@/lib/utils"
import { useState } from "react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { Progress } from "@/components/ui/progress"
import { Database, Users, Target, TrendingUp, Activity, CheckCircle, ArrowRight, BarChart3, Zap } from "lucide-react"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import {
Database,
Target,
Brain,
Search,
BarChart3,
RefreshCw,
Download,
ChevronRight,
Activity,
Zap,
} from "lucide-react"
import Link from "next/link"
export default function Home() {
const router = useRouter()
export default function HomePage() {
const [timeRange, setTimeRange] = useState("today")
// 真实的数据概览
const systemOverview = {
totalUsers: 156234,
activeUsers: 89167,
dataIntegrations: 12,
avgUserValue: 3248,
dataQuality: 94.6,
syncStatus: "正常",
// 今日数据概览
const todayStats = {
dataSync: {
total: 1245678,
growth: "+12.5%",
sources: 8,
lastUpdate: "2分钟前",
},
userDiscovery: {
activeUsers: 45678,
newUsers: 1234,
engagement: "87.5%",
growth: "+8.2%",
},
userValuation: {
avgValue: 3248,
highValue: 12456,
growth: "+15.8%",
upgradeRate: "12.5%",
},
aiAnalysis: {
insights: 156,
anomalies: 3,
predictions: 89,
accuracy: "94.2%",
},
}
// 数据集成状态
const dataIntegrations = [
{ name: "用户行为数据", status: "connected", lastSync: "2分钟前", records: 245673 },
{ name: "CRM系统", status: "connected", lastSync: "5分钟前", records: 156234 },
{ name: "内容互动数据", status: "connected", lastSync: "1分钟前", records: 892456 },
{ name: "营销活动数据", status: "syncing", lastSync: "正在同步", records: 0 },
]
// 用户画像概览
const userPortraitStats = {
totalTags: 342,
activeTags: 287,
userCoverage: 95.8,
segmentCount: 24,
}
// 用户估值概览
const userValueStats = {
highValueUsers: 18234,
mediumValueUsers: 47891,
lowValueUsers: 32156,
upgradeRate: 12.3,
}
const quickActions = [
// 实时活动数据
const realtimeActivities = [
{
title: "数据集成",
description: "管理数据源和同步",
icon: <Database className="h-5 w-5" />,
href: "/data-integration",
color: "bg-blue-50 hover:bg-blue-100 border-blue-200",
id: 1,
type: "data_sync",
message: "电商平台数据同步完成",
time: "刚刚",
status: "success",
},
{
title: "用户画像",
description: "查看用户标签和分群",
icon: <Users className="h-5 w-5" />,
href: "/user-portrait",
color: "bg-purple-50 hover:bg-purple-100 border-purple-200",
id: 2,
type: "user_discovery",
message: "发现新用户行为模式",
time: "2分钟前",
status: "info",
},
{
title: "用户估值",
description: "分析用户价值和升级路径",
icon: <Target className="h-5 w-5" />,
href: "/user-value",
color: "bg-green-50 hover:bg-green-100 border-green-200",
id: 3,
type: "ai_analysis",
message: "AI检测到异常流量",
time: "5分钟前",
status: "warning",
},
{
id: 4,
type: "user_valuation",
message: "用户价值模型更新完成",
time: "8分钟前",
status: "success",
},
]
const getActivityIcon = (type: string) => {
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":
return <Target className="h-4 w-4" />
case "ai_analysis":
return <Brain className="h-4 w-4" />
default:
return <Activity className="h-4 w-4" />
}
}
const getStatusColor = (status: string) => {
switch (status) {
case "success":
return "text-green-600"
case "warning":
return "text-yellow-600"
case "info":
return "text-blue-600"
default:
return "text-gray-600"
}
}
return (
<div className="container mx-auto py-6 space-y-8">
{/* 页面标题 */}
<div className="text-center space-y-2">
<h1 className="text-3xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
</h1>
<p className="text-muted-foreground"></p>
<div className="container mx-auto py-4 md:py-6 space-y-4 md:space-y-6">
{/* 页面标题和工具栏 */}
<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>
</div>
<div className="flex gap-2">
<Select value={timeRange} onValueChange={setTimeRange}>
<SelectTrigger className="w-[120px] text-sm">
<SelectValue placeholder="时间范围" />
</SelectTrigger>
<SelectContent>
<SelectItem value="today"></SelectItem>
<SelectItem value="week"></SelectItem>
<SelectItem value="month"></SelectItem>
</SelectContent>
</Select>
<Button variant="outline" size="icon" className="h-9 w-9 bg-transparent">
<RefreshCw className="h-4 w-4" />
</Button>
<Button variant="outline" size="sm" className="hidden md:flex bg-transparent">
<Download className="mr-2 h-4 w-4" />
</Button>
</div>
</div>
{/* 系统概览 */}
<Card className="border-none shadow-lg">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<BarChart3 className="h-5 w-5" />
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="bg-gradient-to-r from-blue-50 to-blue-100 rounded-lg p-4">
{/* 核心功能模块 */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{/* 数据中台 */}
<Link href="/data-platform">
<Card className="border-none shadow-md hover:shadow-lg transition-all duration-300 cursor-pointer group">
<CardHeader className="bg-gradient-to-r from-blue-50 to-indigo-50 pb-3">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-blue-600"></p>
<p className="text-2xl font-bold text-blue-900">{systemOverview.totalUsers.toLocaleString()}</p>
<div className="flex items-center gap-2">
<Database className="h-5 w-5 text-blue-600" />
<CardTitle className="text-base"></CardTitle>
</div>
<Users className="h-8 w-8 text-blue-600" />
<Badge className="bg-blue-100 text-blue-700"></Badge>
</div>
<div className="mt-2">
<p className="text-xs text-blue-600">
: {systemOverview.activeUsers.toLocaleString()} (
{((systemOverview.activeUsers / systemOverview.totalUsers) * 100).toFixed(1)}%)
</p>
</div>
</div>
<div className="bg-gradient-to-r from-purple-50 to-purple-100 rounded-lg p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-purple-600"></p>
<p className="text-2xl font-bold text-purple-900">¥{systemOverview.avgUserValue.toLocaleString()}</p>
<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.dataSync.total.toLocaleString()}</span>
</div>
<Target className="h-8 w-8 text-purple-600" />
</div>
<div className="mt-2">
<p className="text-xs text-purple-600"> 8.2%</p>
</div>
</div>
<div className="bg-gradient-to-r from-green-50 to-green-100 rounded-lg p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-green-600"></p>
<p className="text-2xl font-bold text-green-900">{systemOverview.dataQuality}%</p>
<div className="flex justify-between items-center">
<span className="text-sm text-gray-600"></span>
<span className="font-bold">{todayStats.dataSync.sources}</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.dataSync.growth}</span>
</div>
<div className="flex items-center justify-between mt-3">
<span className="text-xs text-gray-500">: {todayStats.dataSync.lastUpdate}</span>
<ChevronRight className="h-4 w-4 text-gray-400 group-hover:text-blue-600 transition-colors" />
</div>
<CheckCircle className="h-8 w-8 text-green-600" />
</div>
<div className="mt-2">
<Progress value={systemOverview.dataQuality} className="h-2" />
</div>
</div>
</div>
</CardContent>
</Card>
{/* 快速操作 */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{quickActions.map((action, index) => (
<Card
key={index}
className={cn(
"border-2 cursor-pointer transition-all duration-300 hover:shadow-lg hover:scale-105",
action.color,
)}
onClick={() => router.push(action.href)}
>
<CardContent className="p-6">
<div className="flex items-center justify-between mb-4">
<div className="p-2 rounded-lg bg-white/50">{action.icon}</div>
<ArrowRight className="h-5 w-5 text-gray-400" />
</div>
<h3 className="font-semibold text-lg mb-2">{action.title}</h3>
<p className="text-sm text-gray-600">{action.description}</p>
</CardContent>
</Card>
))}
</Link>
{/* 用户发现 */}
<Link href="/user-discovery">
<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>
</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.userDiscovery.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>
</div>
<div className="flex items-center justify-between mt-3">
<span className="text-xs text-green-600"> {todayStats.userDiscovery.growth}</span>
<ChevronRight className="h-4 w-4 text-gray-400 group-hover:text-green-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">
<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.userValuation.avgValue.toLocaleString()}</span>
</div>
<div className="flex justify-between items-center">
<span className="text-sm text-gray-600"></span>
<span className="font-bold">{todayStats.userValuation.highValue.toLocaleString()}</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.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>
<ChevronRight className="h-4 w-4 text-gray-400 group-hover:text-orange-600 transition-colors" />
</div>
</div>
</CardContent>
</Card>
</Link>
</div>
{/* 详细状态 */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* 数据集成状态 */}
<Card>
{/* 实时数据流 */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4 md:gap-6">
{/* 今日关键指标 */}
<Card className="lg:col-span-2 border-none shadow-md">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Database className="h-5 w-5" />
<BarChart3 className="h-5 w-5 text-blue-600" />
</CardTitle>
<CardDescription></CardDescription>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="text-center p-3 bg-blue-50 rounded-lg">
<div className="text-2xl font-bold text-blue-600">98.5%</div>
<div className="text-xs text-gray-600"></div>
</div>
<div className="text-center p-3 bg-green-50 rounded-lg">
<div className="text-2xl font-bold text-green-600">2.3s</div>
<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>
<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>
</div>
<div className="mt-4 space-y-3">
<div>
<div className="flex justify-between text-sm mb-1">
<span></span>
<span>87%</span>
</div>
<Progress value={87} className="h-2" />
</div>
<div>
<div className="flex justify-between text-sm mb-1">
<span>AI模型训练</span>
<span>92%</span>
</div>
<Progress value={92} className="h-2" />
</div>
</div>
</CardContent>
</Card>
{/* 实时活动 */}
<Card className="border-none shadow-md">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Activity className="h-5 w-5 text-green-600" />
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-3">
{dataIntegrations.map((integration, index) => (
<div key={index} className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div className="flex items-center gap-3">
<div
className={cn(
"w-3 h-3 rounded-full",
integration.status === "connected"
? "bg-green-500"
: integration.status === "syncing"
? "bg-yellow-500"
: "bg-red-500",
)}
/>
<div>
<p className="font-medium text-sm">{integration.name}</p>
<p className="text-xs text-gray-500">{integration.lastSync}</p>
</div>
{realtimeActivities.map((activity) => (
<div key={activity.id} className="flex items-start gap-3 p-2 hover:bg-gray-50 rounded-lg">
<div className={`p-1 rounded-full ${getStatusColor(activity.status)}`}>
{getActivityIcon(activity.type)}
</div>
<div className="text-right">
<p className="text-sm font-medium">{integration.records.toLocaleString()}</p>
<p className="text-xs text-gray-500"></p>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium truncate">{activity.message}</p>
<p className="text-xs text-gray-500">{activity.time}</p>
</div>
</div>
))}
</div>
<Button
variant="outline"
className="w-full mt-4 bg-transparent"
onClick={() => router.push("/data-integration")}
>
</Button>
</CardContent>
</Card>
{/* 用户画像概览 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Users className="h-5 w-5" />
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 gap-4 mb-4">
<div className="text-center p-3 bg-purple-50 rounded-lg">
<p className="text-2xl font-bold text-purple-600">{userPortraitStats.totalTags}</p>
<p className="text-sm text-gray-600"></p>
</div>
<div className="text-center p-3 bg-blue-50 rounded-lg">
<p className="text-2xl font-bold text-blue-600">{userPortraitStats.segmentCount}</p>
<p className="text-sm text-gray-600"></p>
</div>
</div>
<div className="space-y-3">
<div className="flex justify-between items-center">
<span className="text-sm"></span>
<span className="text-sm font-medium">{userPortraitStats.userCoverage}%</span>
</div>
<Progress value={userPortraitStats.userCoverage} className="h-2" />
<div className="flex justify-between items-center">
<span className="text-sm"></span>
<span className="text-sm font-medium">{userPortraitStats.activeTags}</span>
</div>
<Progress value={(userPortraitStats.activeTags / userPortraitStats.totalTags) * 100} className="h-2" />
</div>
<Button
variant="outline"
className="w-full mt-4 bg-transparent"
onClick={() => router.push("/user-portrait")}
>
</Button>
</CardContent>
</Card>
{/* 用户估值概览 */}
<Card className="lg:col-span-2">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Target className="h-5 w-5" />
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="bg-green-50 p-4 rounded-lg">
<div className="flex items-center justify-between mb-2">
<p className="text-sm font-medium text-green-600"></p>
<TrendingUp className="h-4 w-4 text-green-600" />
</div>
<p className="text-xl font-bold text-green-900">{userValueStats.highValueUsers.toLocaleString()}</p>
<p className="text-xs text-green-600">
{((userValueStats.highValueUsers / systemOverview.totalUsers) * 100).toFixed(1)}%
</p>
</div>
<div className="bg-blue-50 p-4 rounded-lg">
<div className="flex items-center justify-between mb-2">
<p className="text-sm font-medium text-blue-600"></p>
<Activity className="h-4 w-4 text-blue-600" />
</div>
<p className="text-xl font-bold text-blue-900">{userValueStats.mediumValueUsers.toLocaleString()}</p>
<p className="text-xs text-blue-600">
{((userValueStats.mediumValueUsers / systemOverview.totalUsers) * 100).toFixed(1)}%
</p>
</div>
<div className="bg-gray-50 p-4 rounded-lg">
<div className="flex items-center justify-between mb-2">
<p className="text-sm font-medium text-gray-600"></p>
<Users className="h-4 w-4 text-gray-600" />
</div>
<p className="text-xl font-bold text-gray-900">{userValueStats.lowValueUsers.toLocaleString()}</p>
<p className="text-xs text-gray-600">
{((userValueStats.lowValueUsers / systemOverview.totalUsers) * 100).toFixed(1)}%
</p>
</div>
<div className="bg-orange-50 p-4 rounded-lg">
<div className="flex items-center justify-between mb-2">
<p className="text-sm font-medium text-orange-600"></p>
<Zap className="h-4 w-4 text-orange-600" />
</div>
<p className="text-xl font-bold text-orange-900">{userValueStats.upgradeRate}%</p>
<p className="text-xs text-orange-600"> 2.1%</p>
</div>
</div>
<Button variant="outline" className="w-full mt-4 bg-transparent" onClick={() => router.push("/user-value")}>
<Button variant="outline" className="w-full mt-4 text-sm bg-transparent">
</Button>
</CardContent>
</Card>
</div>
{/* 快速操作 */}
<Card className="border-none shadow-md">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Zap className="h-5 w-5 text-yellow-600" />
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
<Link href="/data-platform">
<Button variant="outline" className="w-full h-16 flex flex-col gap-1 bg-transparent">
<Database className="h-5 w-5" />
<span className="text-xs"></span>
</Button>
</Link>
<Link href="/user-discovery/behavior">
<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>
</Button>
</Link>
<Link href="/user-valuation/model">
<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>
</Button>
</Link>
<Link href="/ai-analysis/trends">
<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>
</Button>
</Link>
</div>
</CardContent>
</Card>
</div>
)
}

View File

@@ -0,0 +1,3 @@
export default function Loading() {
return null
}

429
app/user-discovery/page.tsx Normal file
View File

@@ -0,0 +1,429 @@
"use client"
import { useState } from "react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { Progress } from "@/components/ui/progress"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import {
Search,
Users,
TrendingUp,
BarChart3,
Filter,
RefreshCw,
Download,
ChevronRight,
ChevronDown,
Eye,
Clock,
Smartphone,
} from "lucide-react"
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
export default function UserDiscoveryPage() {
const [timeRange, setTimeRange] = useState("today")
const [searchQuery, setSearchQuery] = useState("")
const [expandedSections, setExpandedSections] = useState({
overview: true,
behavior: true,
segmentation: false,
engagement: false,
})
// 用户发现数据
const discoveryData = {
overview: {
totalUsers: 125678,
activeUsers: 45678,
newUsers: 1234,
engagement: 87.5,
growth: "+12.3%",
},
behaviorPatterns: [
{
pattern: "购买决策路径",
users: 12456,
avgTime: "3.2天",
conversion: "23.5%",
trend: "+8.2%",
},
{
pattern: "内容浏览偏好",
users: 8765,
avgTime: "15分钟",
conversion: "45.8%",
trend: "+12.1%",
},
{
pattern: "社交分享行为",
users: 6543,
avgTime: "2.1分钟",
conversion: "67.3%",
trend: "+5.7%",
},
{
pattern: "移动端使用习惯",
users: 9876,
avgTime: "8.5分钟",
conversion: "34.2%",
trend: "+15.4%",
},
],
userSegments: [
{
name: "高价值活跃用户",
count: 12456,
percentage: 28.6,
characteristics: ["高频使用", "高消费", "高推荐"],
color: "bg-green-100 text-green-800",
},
{
name: "潜力增长用户",
count: 18765,
percentage: 43.2,
characteristics: ["中频使用", "增长潜力", "易转化"],
color: "bg-blue-100 text-blue-800",
},
{
name: "新注册用户",
count: 8234,
percentage: 18.9,
characteristics: ["新用户", "探索期", "需引导"],
color: "bg-purple-100 text-purple-800",
},
{
name: "流失风险用户",
count: 4123,
percentage: 9.3,
characteristics: ["低活跃", "流失风险", "需挽回"],
color: "bg-red-100 text-red-800",
},
],
engagementMetrics: {
dailyActive: 45678,
weeklyActive: 89234,
monthlyActive: 125678,
avgSessionTime: "12.5分钟",
bounceRate: "23.4%",
returnRate: "67.8%",
},
}
const toggleSection = (section: string) => {
setExpandedSections((prev) => ({
...prev,
[section]: !prev[section],
}))
}
return (
<div className="container mx-auto py-4 md:py-6 space-y-4 md:space-y-6">
{/* 页面标题和工具栏 */}
<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"></h1>
<p className="text-sm md:text-base text-muted-foreground mt-1"></p>
</div>
<div className="flex flex-wrap gap-2">
<Select value={timeRange} onValueChange={setTimeRange}>
<SelectTrigger className="w-[120px] text-sm">
<SelectValue placeholder="时间范围" />
</SelectTrigger>
<SelectContent>
<SelectItem value="today"></SelectItem>
<SelectItem value="week"></SelectItem>
<SelectItem value="month"></SelectItem>
</SelectContent>
</Select>
<Button variant="outline" size="icon" className="h-9 w-9 bg-transparent">
<Filter className="h-4 w-4" />
</Button>
<Button variant="outline" size="icon" className="h-9 w-9 bg-transparent">
<RefreshCw className="h-4 w-4" />
</Button>
<Button variant="outline" size="sm" className="hidden md:flex bg-transparent">
<Download className="mr-2 h-4 w-4" />
</Button>
</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-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">
<Search 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"></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-5 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">
{discoveryData.overview.totalUsers.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">
{discoveryData.overview.activeUsers.toLocaleString()}
</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">
{discoveryData.overview.newUsers.toLocaleString()}
</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">
{discoveryData.overview.engagement}%
</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">{discoveryData.overview.growth}</div>
<div className="text-xs text-pink-700"></div>
</div>
</div>
</CardContent>
</CollapsibleContent>
</Card>
</Collapsible>
{/* 行为模式分析 */}
<Collapsible
open={expandedSections.behavior}
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, behavior: 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">
<BarChart3 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.behavior ? (
<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">
{discoveryData.behaviorPatterns.map((pattern, 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-2">
<div className="flex-1">
<h3 className="font-medium text-sm md:text-base">{pattern.pattern}</h3>
<p className="text-xs text-gray-500">{pattern.users.toLocaleString()} </p>
</div>
<div className="grid grid-cols-3 gap-3 md:gap-4 text-center">
<div>
<div className="text-sm font-bold">{pattern.avgTime}</div>
<div className="text-xs text-gray-500"></div>
</div>
<div>
<div className="text-sm font-bold">{pattern.conversion}</div>
<div className="text-xs text-gray-500"></div>
</div>
<div>
<div className="text-sm font-bold text-green-600">{pattern.trend}</div>
<div className="text-xs text-gray-500"></div>
</div>
</div>
</div>
</div>
))}
</div>
</CardContent>
</CollapsibleContent>
</Card>
</Collapsible>
{/* 用户分群 */}
<Collapsible
open={expandedSections.segmentation}
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, segmentation: 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">
<Users 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.segmentation ? (
<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">
{discoveryData.userSegments.map((segment, 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">
<Badge className={segment.color}>{segment.name}</Badge>
<span className="text-sm font-bold">{segment.count.toLocaleString()}</span>
<span className="text-xs text-gray-500">({segment.percentage}%)</span>
</div>
<div className="flex flex-wrap gap-1">
{segment.characteristics.map((char, charIndex) => (
<Badge key={charIndex} variant="outline" className="text-xs">
{char}
</Badge>
))}
</div>
</div>
<div className="w-full md:w-32">
<Progress value={segment.percentage} className="h-2" />
</div>
</div>
</div>
))}
</div>
</CardContent>
</CollapsibleContent>
</Card>
</Collapsible>
{/* 参与度指标 */}
<Collapsible
open={expandedSections.engagement}
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, engagement: 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">
<TrendingUp 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.engagement ? (
<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">
<Clock 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">
{discoveryData.engagementMetrics.dailyActive.toLocaleString()}
</span>
</div>
<div className="flex justify-between">
<span className="text-xs text-gray-600"></span>
<span className="text-sm font-bold">
{discoveryData.engagementMetrics.weeklyActive.toLocaleString()}
</span>
</div>
<div className="flex justify-between">
<span className="text-xs text-gray-600"></span>
<span className="text-sm font-bold">
{discoveryData.engagementMetrics.monthlyActive.toLocaleString()}
</span>
</div>
</div>
</div>
<div className="bg-green-50 p-4 rounded-lg">
<div className="flex items-center gap-2 mb-2">
<Eye 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">{discoveryData.engagementMetrics.avgSessionTime}</span>
</div>
<div className="flex justify-between">
<span className="text-xs text-gray-600"></span>
<span className="text-sm font-bold">{discoveryData.engagementMetrics.bounceRate}</span>
</div>
<div className="flex justify-between">
<span className="text-xs text-gray-600">访</span>
<span className="text-sm font-bold">{discoveryData.engagementMetrics.returnRate}</span>
</div>
</div>
</div>
<div className="bg-purple-50 p-4 rounded-lg">
<div className="flex items-center gap-2 mb-2">
<Smartphone 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">68.5%</span>
</div>
<div className="flex justify-between">
<span className="text-xs text-gray-600"></span>
<span className="text-sm font-bold">24.3%</span>
</div>
<div className="flex justify-between">
<span className="text-xs text-gray-600"></span>
<span className="text-sm font-bold">7.2%</span>
</div>
</div>
</div>
</div>
</CardContent>
</CollapsibleContent>
</Card>
</Collapsible>
</div>
)
}

View File

@@ -0,0 +1,3 @@
export default function Loading() {
return null
}

871
app/user-valuation/page.tsx Normal file
View File

@@ -0,0 +1,871 @@
"use client"
import { useState } from "react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { Progress } from "@/components/ui/progress"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { Input } from "@/components/ui/input"
import {
ArrowUpRight,
Download,
Filter,
RefreshCw,
Target,
TrendingUp,
Users,
Layers,
Tag,
BarChart3,
Search,
HelpCircle,
ChevronRight,
ChevronDown,
} from "lucide-react"
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"
// 使用通用公司名称和当天时间线的用户数据
const mockUsers = [
{
id: "1",
name: "张明华",
company: "科技创新有限公司",
recency: 1,
frequency: 28,
monetary: 45600,
rfmScore: 95,
level: "高价值",
growth: "+8.5%",
upgradeFromDays: 45,
nextUpgrade: "VIP客户",
upgradeProbability: 85,
lastActive: "今天 14:30",
},
{
id: "2",
name: "李雨婷",
company: "数字营销公司",
recency: 3,
frequency: 22,
monetary: 32800,
rfmScore: 88,
level: "高价值",
growth: "+6.2%",
upgradeFromDays: 28,
nextUpgrade: "白金会员",
upgradeProbability: 78,
lastActive: "今天 13:45",
},
{
id: "3",
name: "王建国",
company: "软件开发工作室",
recency: 2,
frequency: 25,
monetary: 38900,
rfmScore: 91,
level: "高价值",
growth: "+7.8%",
upgradeFromDays: 38,
nextUpgrade: "企业VIP",
upgradeProbability: 82,
lastActive: "今天 12:20",
},
{
id: "4",
name: "刘思琪",
company: "数据分析咨询",
recency: 7,
frequency: 18,
monetary: 25400,
rfmScore: 76,
level: "中高价值",
growth: "+4.1%",
upgradeFromDays: 12,
nextUpgrade: "高价值用户",
upgradeProbability: 68,
lastActive: "今天 11:15",
},
{
id: "5",
name: "陈浩然",
company: "云计算服务商",
recency: 4,
frequency: 20,
monetary: 28700,
rfmScore: 82,
level: "中高价值",
growth: "+5.3%",
upgradeFromDays: 22,
nextUpgrade: "高价值用户",
upgradeProbability: 72,
lastActive: "今天 10:30",
},
{
id: "6",
name: "周欣妍",
company: "用户体验设计",
recency: 12,
frequency: 15,
monetary: 18600,
rfmScore: 65,
level: "中等价值",
growth: "+3.2%",
upgradeFromDays: 8,
nextUpgrade: "中高价值用户",
upgradeProbability: 55,
lastActive: "今天 09:45",
},
{
id: "7",
name: "马志强",
company: "企业解决方案",
recency: 6,
frequency: 16,
monetary: 22100,
rfmScore: 71,
level: "中等价值",
growth: "+4.8%",
upgradeFromDays: 15,
nextUpgrade: "中高价值用户",
upgradeProbability: 61,
lastActive: "今天 08:20",
},
{
id: "8",
name: "赵丽娟",
company: "品牌营销策划",
recency: 15,
frequency: 12,
monetary: 15800,
rfmScore: 58,
level: "中等价值",
growth: "+2.1%",
upgradeFromDays: 5,
nextUpgrade: "中高价值用户",
upgradeProbability: 45,
lastActive: "今天 07:30",
},
]
export default function UserValuationPage() {
const [timeRange, setTimeRange] = useState("30days")
const [searchQuery, setSearchQuery] = useState("")
const [showModelDialog, setShowModelDialog] = useState(false)
const [showUpgradeDialog, setShowUpgradeDialog] = useState(false)
const [expandedSections, setExpandedSections] = useState({
overview: true,
model: false,
analysis: true,
upgradePaths: false,
})
const filteredUsers = mockUsers.filter(
(user) =>
user.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
user.company.toLowerCase().includes(searchQuery.toLowerCase()),
)
const getRFMLevelColor = (level: string) => {
switch (level) {
case "高价值":
return "bg-green-100 text-green-800"
case "中高价值":
return "bg-blue-100 text-blue-800"
case "中等价值":
return "bg-yellow-100 text-yellow-800"
case "低价值":
return "bg-gray-100 text-gray-800"
case "流失风险":
return "bg-red-100 text-red-800"
default:
return "bg-gray-100 text-gray-800"
}
}
const toggleSection = (section: string) => {
setExpandedSections((prev) => ({
...prev,
[section]: !prev[section],
}))
}
return (
<div className="container mx-auto p-4 space-y-4 md:space-y-6">
{/* 页面标题和工具栏 */}
<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"></h1>
<p className="text-sm md:text-base text-muted-foreground mt-1"></p>
</div>
<div className="flex flex-wrap gap-2">
<Select value={timeRange} onValueChange={setTimeRange}>
<SelectTrigger className="w-[120px] md:w-[140px] text-sm">
<SelectValue placeholder="时间范围" />
</SelectTrigger>
<SelectContent>
<SelectItem value="7days">7</SelectItem>
<SelectItem value="30days">30</SelectItem>
<SelectItem value="90days">90</SelectItem>
</SelectContent>
</Select>
<Button variant="outline" size="icon" className="h-9 w-9 bg-transparent">
<Filter className="h-4 w-4" />
</Button>
<Button variant="outline" size="icon" className="h-9 w-9 bg-transparent">
<RefreshCw className="h-4 w-4" />
</Button>
<Button variant="outline" size="sm" className="hidden md:flex bg-transparent">
<Download className="mr-2 h-4 w-4" />
</Button>
</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-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">
<Target 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 flex items-center gap-2">
<Button
variant="ghost"
size="sm"
onClick={(e) => {
e.stopPropagation()
setShowModelDialog(true)
}}
className="h-5 w-5 md:h-6 md:w-6 p-0"
>
<HelpCircle className="h-3 w-3 md:h-4 md:w-4" />
</Button>
</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">
<Card className="bg-gradient-to-br from-green-50 to-emerald-50 border-none shadow-sm">
<CardContent className="p-3 md:p-4">
<div className="flex justify-between items-start">
<div>
<div className="text-xs md:text-sm font-medium text-green-600"></div>
<div className="text-lg md:text-2xl font-bold mt-1 md:mt-2">¥3,248</div>
<div className="text-xs text-green-600 mt-1 flex items-center">
<ArrowUpRight className="h-3 w-3 md:h-4 md:w-4 mr-1" />
+8.2%
</div>
</div>
<div className="bg-green-100 p-1.5 md:p-2 rounded-full">
<Target className="h-3 w-3 md:h-5 md:w-5 text-green-600" />
</div>
</div>
<div className="mt-2 md:mt-3">
<div className="flex justify-between text-xs mb-1">
<span></span>
<span>94%</span>
</div>
<Progress value={94} className="h-1 md:h-1.5 bg-green-100" />
</div>
</CardContent>
</Card>
<Card className="bg-gradient-to-br from-purple-50 to-pink-50 border-none shadow-sm">
<CardContent className="p-3 md:p-4">
<div className="flex justify-between items-start">
<div>
<div className="text-xs md:text-sm font-medium text-purple-600"></div>
<div className="text-lg md:text-2xl font-bold mt-1 md:mt-2">45,678</div>
<div className="text-xs text-green-600 mt-1 flex items-center">
<ArrowUpRight className="h-3 w-3 md:h-4 md:w-4 mr-1" />
+12.3%
</div>
</div>
<div className="bg-purple-100 p-1.5 md:p-2 rounded-full">
<TrendingUp className="h-3 w-3 md:h-5 md:w-5 text-purple-600" />
</div>
</div>
<div className="mt-2 md:mt-3">
<div className="flex justify-between text-xs mb-1">
<span></span>
<span>28.6%</span>
</div>
<Progress value={28.6} className="h-1 md:h-1.5 bg-purple-100" />
</div>
</CardContent>
</Card>
<Card className="bg-gradient-to-br from-blue-50 to-indigo-50 border-none shadow-sm">
<CardContent className="p-3 md:p-4">
<div className="flex justify-between items-start">
<div>
<div className="text-xs md:text-sm font-medium text-blue-600"></div>
<div className="text-lg md:text-2xl font-bold mt-1 md:mt-2">15.8%</div>
<div className="text-xs text-green-600 mt-1 flex items-center">
<ArrowUpRight className="h-3 w-3 md:h-4 md:w-4 mr-1" />
+2.1%
</div>
</div>
<div className="bg-blue-100 p-1.5 md:p-2 rounded-full">
<BarChart3 className="h-3 w-3 md:h-5 md:w-5 text-blue-600" />
</div>
</div>
<div className="mt-2 md:mt-3">
<div className="flex justify-between text-xs mb-1">
<span></span>
<span>11.2%</span>
</div>
<Progress value={141} className="h-1 md:h-1.5 bg-blue-100" />
</div>
</CardContent>
</Card>
<Card className="bg-gradient-to-br from-amber-50 to-yellow-50 border-none shadow-sm">
<CardContent className="p-3 md:p-4">
<div className="flex justify-between items-start">
<div>
<div className="text-xs md:text-sm font-medium text-amber-600"></div>
<div className="text-lg md:text-2xl font-bold mt-1 md:mt-2">12.5%</div>
<div className="text-xs text-green-600 mt-1 flex items-center">
<ArrowUpRight className="h-3 w-3 md:h-4 md:w-4 mr-1" />
+1.8%
</div>
</div>
<div className="bg-amber-100 p-1.5 md:p-2 rounded-full">
<Users className="h-3 w-3 md:h-5 md:w-5 text-amber-600" />
</div>
</div>
<div className="mt-2 md:mt-3">
<div className="flex justify-between text-xs mb-1">
<span></span>
<span>15%</span>
</div>
<Progress value={83} className="h-1 md:h-1.5 bg-amber-100" />
</div>
</CardContent>
</Card>
</div>
</CardContent>
</CollapsibleContent>
</Card>
</Collapsible>
{/* RFM估值模型 */}
<Collapsible
open={expandedSections.model}
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, model: 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">
<BarChart3 className="h-4 w-4 md:h-5 md:w-5 text-blue-600" />
<div className="text-left">
<CardTitle className="flex items-center gap-2 text-base md:text-lg">
RFM估值模型
<Button
variant="ghost"
size="sm"
onClick={(e) => {
e.stopPropagation()
setShowModelDialog(true)
}}
className="h-5 w-5 md:h-6 md:w-6 p-0"
>
<HelpCircle className="h-3 w-3 md:h-4 md:w-4" />
</Button>
</CardTitle>
<CardDescription className="text-xs md:text-sm"></CardDescription>
</div>
</div>
{expandedSections.model ? (
<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-4 md:gap-6">
<div className="space-y-2">
<h3 className="font-semibold flex items-center text-sm md:text-base">
<span className="bg-red-100 text-red-800 w-5 h-5 md:w-6 md:h-6 rounded-full flex items-center justify-center mr-2 text-xs md:text-sm">
R
</span>
</h3>
<p className="text-xs md:text-sm text-muted-foreground"></p>
<div className="bg-gray-50 p-2 md:p-3 rounded-md">
<div className="text-xs md:text-sm space-y-1">
<div className="flex justify-between">
<span>1-3</span>
<span className="font-medium">5</span>
</div>
<div className="flex justify-between">
<span>4-7</span>
<span className="font-medium">4</span>
</div>
<div className="flex justify-between">
<span>8-14</span>
<span className="font-medium">3</span>
</div>
<div className="flex justify-between">
<span>15-30</span>
<span className="font-medium">2</span>
</div>
<div className="flex justify-between">
<span>30</span>
<span className="font-medium">1</span>
</div>
</div>
</div>
</div>
<div className="space-y-2">
<h3 className="font-semibold flex items-center text-sm md:text-base">
<span className="bg-green-100 text-green-800 w-5 h-5 md:w-6 md:h-6 rounded-full flex items-center justify-center mr-2 text-xs md:text-sm">
F
</span>
</h3>
<p className="text-xs md:text-sm text-muted-foreground"></p>
<div className="bg-gray-50 p-2 md:p-3 rounded-md">
<div className="text-xs md:text-sm space-y-1">
<div className="flex justify-between">
<span>25/</span>
<span className="font-medium">5</span>
</div>
<div className="flex justify-between">
<span>20-24/</span>
<span className="font-medium">4</span>
</div>
<div className="flex justify-between">
<span>15-19/</span>
<span className="font-medium">3</span>
</div>
<div className="flex justify-between">
<span>10-14/</span>
<span className="font-medium">2</span>
</div>
<div className="flex justify-between">
<span>1-9/</span>
<span className="font-medium">1</span>
</div>
</div>
</div>
</div>
<div className="space-y-2">
<h3 className="font-semibold flex items-center text-sm md:text-base">
<span className="bg-blue-100 text-blue-800 w-5 h-5 md:w-6 md:h-6 rounded-full flex items-center justify-center mr-2 text-xs md:text-sm">
M
</span>
</h3>
<p className="text-xs md:text-sm text-muted-foreground"></p>
<div className="bg-gray-50 p-2 md:p-3 rounded-md">
<div className="text-xs md:text-sm space-y-1">
<div className="flex justify-between">
<span>30000</span>
<span className="font-medium">5</span>
</div>
<div className="flex justify-between">
<span>20000-29999</span>
<span className="font-medium">4</span>
</div>
<div className="flex justify-between">
<span>10000-19999</span>
<span className="font-medium">3</span>
</div>
<div className="flex justify-between">
<span>5000-9999</span>
<span className="font-medium">2</span>
</div>
<div className="flex justify-between">
<span>5000</span>
<span className="font-medium">1</span>
</div>
</div>
</div>
</div>
</div>
<div className="mt-4 md:mt-6">
<h3 className="font-semibold mb-3 text-sm md:text-base">RFM评分等级</h3>
<div className="grid grid-cols-2 md:grid-cols-5 gap-2 md:gap-3">
<div className="bg-green-100 p-2 md:p-3 rounded-md text-center">
<p className="font-medium text-green-800 text-xs md:text-sm"></p>
<p className="text-xs text-green-700">85-100</p>
</div>
<div className="bg-blue-100 p-2 md:p-3 rounded-md text-center">
<p className="font-medium text-blue-800 text-xs md:text-sm"></p>
<p className="text-xs text-blue-700">70-84</p>
</div>
<div className="bg-yellow-100 p-2 md:p-3 rounded-md text-center">
<p className="font-medium text-yellow-800 text-xs md:text-sm"></p>
<p className="text-xs text-yellow-700">55-69</p>
</div>
<div className="bg-gray-100 p-2 md:p-3 rounded-md text-center">
<p className="font-medium text-gray-800 text-xs md:text-sm"></p>
<p className="text-xs text-gray-700">40-54</p>
</div>
<div className="bg-red-100 p-2 md:p-3 rounded-md text-center">
<p className="font-medium text-red-800 text-xs md:text-sm"></p>
<p className="text-xs text-red-700">0-39</p>
</div>
</div>
</div>
</CardContent>
</CollapsibleContent>
</Card>
</Collapsible>
{/* 用户价值分析 */}
<Collapsible
open={expandedSections.analysis}
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, analysis: 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">
<Users 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">
({filteredUsers.length})
</CardDescription>
</div>
</div>
{expandedSections.analysis ? (
<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="mb-4">
<div className="relative w-full md:w-64">
<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>
</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>
<p className="font-medium text-sm">{user.name}</p>
<p className="text-xs text-gray-500">{user.company}</p>
</div>
<Badge className={`${getRFMLevelColor(user.level)} text-xs`}>{user.rfmScore}</Badge>
</div>
<div className="grid grid-cols-3 gap-2 text-xs">
<div className="text-center">
<p className="text-gray-500">R值</p>
<p className="font-medium">{user.recency}</p>
</div>
<div className="text-center">
<p className="text-gray-500">F值</p>
<p className="font-medium">{user.frequency}</p>
</div>
<div className="text-center">
<p className="text-gray-500">M值</p>
<p className="font-medium">¥{(user.monetary / 1000).toFixed(0)}K</p>
</div>
</div>
<div className="flex items-center justify-between">
<span className="text-xs text-gray-500">{user.lastActive}</span>
<span className="text-xs text-green-600">{user.growth}</span>
</div>
</div>
))}
</div>
{/* 桌面端表格布局 */}
<div className="hidden md:block rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead className="text-center">RFM评分</TableHead>
<TableHead className="text-center">R值</TableHead>
<TableHead className="text-center">F值</TableHead>
<TableHead className="text-center">M值</TableHead>
<TableHead className="text-right"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredUsers.map((user) => (
<TableRow key={user.id} className="hover:bg-gray-50">
<TableCell className="font-medium">{user.name}</TableCell>
<TableCell>{user.company}</TableCell>
<TableCell className="text-center">
<Badge className={`${getRFMLevelColor(user.level)}`}>
{user.rfmScore} ({user.level})
</Badge>
</TableCell>
<TableCell className="text-center">{user.recency}</TableCell>
<TableCell className="text-center">{user.frequency}/</TableCell>
<TableCell className="text-center">¥{user.monetary.toLocaleString()}</TableCell>
<TableCell className="text-right text-green-600">{user.growth}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</CardContent>
</CollapsibleContent>
</Card>
</Collapsible>
{/* 用户升级路径分析 */}
<Collapsible
open={expandedSections.upgradePaths}
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, upgradePaths: 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">
<TrendingUp className="h-4 w-4 md:h-5 md:w-5 text-orange-600" />
<div className="text-left">
<CardTitle className="flex items-center gap-2 text-base md:text-lg">
<Button
variant="ghost"
size="sm"
onClick={(e) => {
e.stopPropagation()
setShowUpgradeDialog(true)
}}
className="h-5 w-5 md:h-6 md:w-6 p-0"
>
<HelpCircle className="h-3 w-3 md:h-4 md:w-4" />
</Button>
</CardTitle>
<CardDescription className="text-xs md:text-sm"></CardDescription>
</div>
</div>
{expandedSections.upgradePaths ? (
<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">
<Card className="bg-white shadow-sm">
<CardHeader className="pb-2">
<CardTitle className="text-sm md:text-lg flex items-center">
<Target className="h-4 w-4 md:h-5 md:w-5 mr-2 text-green-600" />
</CardTitle>
</CardHeader>
<CardContent className="text-xs md:text-sm">
<div className="space-y-1 md:space-y-2">
<div className="flex justify-between items-center">
<span></span>
<span className="font-medium">2,345</span>
</div>
<div className="flex justify-between items-center">
<span></span>
<span className="font-medium">5,678</span>
</div>
<div className="flex justify-between items-center">
<span></span>
<span className="font-medium">1,234</span>
</div>
</div>
</CardContent>
</Card>
<Card className="bg-white shadow-sm">
<CardHeader className="pb-2">
<CardTitle className="text-sm md:text-lg flex items-center">
<Layers className="h-4 w-4 md:h-5 md:w-5 mr-2 text-blue-600" />
</CardTitle>
</CardHeader>
<CardContent className="text-xs md:text-sm">
<div className="space-y-1 md:space-y-2">
<div className="flex justify-between items-center">
<span></span>
<span className="font-medium">12.5%</span>
</div>
<div className="flex justify-between items-center">
<span></span>
<span className="font-medium">28</span>
</div>
<div className="flex justify-between items-center">
<span></span>
<span className="font-medium">1,856</span>
</div>
</div>
</CardContent>
</Card>
<Card className="bg-white shadow-sm">
<CardHeader className="pb-2">
<CardTitle className="text-sm md:text-lg flex items-center">
<Tag className="h-4 w-4 md:h-5 md:w-5 mr-2 text-purple-600" />
</CardTitle>
</CardHeader>
<CardContent className="text-xs md:text-sm">
<div className="space-y-1 md:space-y-2">
<div className="flex justify-between items-center">
<span></span>
<span className="font-medium">¥2.3M</span>
</div>
<div className="flex justify-between items-center">
<span></span>
<span className="font-medium">¥1,245</span>
</div>
<div className="flex justify-between items-center">
<span>ROI提升</span>
<span className="font-medium">35.8%</span>
</div>
</div>
</CardContent>
</Card>
</div>
<div className="space-y-3 md:space-y-4">
<h3 className="text-sm md:text-lg font-medium"></h3>
<div className="space-y-2 md:space-y-3">
{filteredUsers.slice(0, 5).map((user) => (
<div
key={user.id}
className="flex flex-col md:flex-row md:items-center justify-between p-3 md:p-4 border rounded-lg hover:bg-gray-50 space-y-2 md:space-y-0"
>
<div className="flex items-center gap-3 md:gap-4">
<div>
<p className="font-medium text-sm md:text-base">{user.name}</p>
<p className="text-xs md:text-sm text-gray-500">{user.company}</p>
</div>
<Badge className={`${getRFMLevelColor(user.level)} text-xs`}>{user.level}</Badge>
</div>
<div className="flex flex-col md:flex-row items-start md:items-center gap-2 md:gap-4">
<div className="text-left md:text-right">
<p className="text-xs md:text-sm font-medium">: {user.nextUpgrade}</p>
<p className="text-xs text-gray-500">: {user.upgradeProbability}%</p>
</div>
<div className="w-full md:w-24">
<Progress value={user.upgradeProbability} className="h-1.5 md:h-2" />
</div>
<Button variant="outline" size="sm" className="text-xs bg-transparent">
</Button>
</div>
</div>
))}
</div>
</div>
</CardContent>
</CollapsibleContent>
</Card>
</Collapsible>
{/* RFM模型说明对话框 */}
<Dialog open={showModelDialog} onOpenChange={setShowModelDialog}>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle>RFM估值模型说明</DialogTitle>
<DialogDescription>RFM模型是一种用于客户价值评估的经典方法</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="p-4 bg-blue-50 rounded-lg">
<h4 className="font-medium mb-2">RFM模型</h4>
<p className="text-sm text-gray-600">
RFM模型是一种用于分析客户价值和客户创利能力的重要工具RecencyFrequencyMonetary
</p>
</div>
<div className="p-4 bg-green-50 rounded-lg">
<h4 className="font-medium mb-2">使RFM评分</h4>
<p className="text-sm text-gray-600">
1-5150-100便
</p>
</div>
<div className="p-4 bg-purple-50 rounded-lg">
<h4 className="font-medium mb-2"></h4>
<p className="text-sm text-gray-600">
RFM三个维度
</p>
</div>
</div>
</DialogContent>
</Dialog>
{/* 升级路径说明对话框 */}
<Dialog open={showUpgradeDialog} onOpenChange={setShowUpgradeDialog}>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="p-4 bg-purple-50 rounded-lg">
<h4 className="font-medium mb-2"></h4>
<p className="text-sm text-gray-600">
RFM评分
</p>
</div>
<div className="p-4 bg-orange-50 rounded-lg">
<h4 className="font-medium mb-2"></h4>
<p className="text-sm text-gray-600">
RFM中得分较低的
</p>
</div>
<div className="p-4 bg-green-50 rounded-lg">
<h4 className="font-medium mb-2"></h4>
<p className="text-sm text-gray-600">
</p>
</div>
</div>
</DialogContent>
</Dialog>
</div>
)
}