feat: optimize interface and database connection
Adjust MongoDB mock connector and update database structure. Enhance sidebar, data platform, and AI analysis tools. Clean up unnecessary code and update development docs. #VERCEL_SKIP Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
@@ -343,7 +343,11 @@ export default function AIAssistantPage() {
|
||||
<div className="text-center text-muted-foreground">
|
||||
<Users className="h-16 w-16 mx-auto mb-4" />
|
||||
<p className="text-lg">请完成所有分析步骤</p>
|
||||
<Button variant="outline" className="mt-4" onClick={() => setIsUserGroupDialogOpen(true)}>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="mt-4 bg-transparent"
|
||||
onClick={() => setIsUserGroupDialogOpen(true)}
|
||||
>
|
||||
<Users className="mr-2 h-4 w-4" />
|
||||
开始设置
|
||||
</Button>
|
||||
@@ -612,7 +616,11 @@ export default function AIAssistantPage() {
|
||||
<div className="text-center text-muted-foreground">
|
||||
<Users className="h-16 w-16 mx-auto mb-4" />
|
||||
<p className="text-lg">请完成所有策略步骤</p>
|
||||
<Button variant="outline" className="mt-4" onClick={() => setIsUserGroupDialogOpen(true)}>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="mt-4 bg-transparent"
|
||||
onClick={() => setIsUserGroupDialogOpen(true)}
|
||||
>
|
||||
<Users className="mr-2 h-4 w-4" />
|
||||
开始设置
|
||||
</Button>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NextResponse } from "next/server"
|
||||
import { getDatabases, getDatabaseStructure } from "@/lib/db-connector"
|
||||
import { getDatabases, getDatabaseStructure } from "@/lib/mongodb-mock-connector" // 更新导入路径
|
||||
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { Home, Database, Search, Target, Brain } from "lucide-react"
|
||||
import { Home, Database, Target, BrainCircuit } from "lucide-react" // 引入AI智能助手图标
|
||||
|
||||
const navItems = [
|
||||
{ href: "/", icon: Home, label: "概览" },
|
||||
{ href: "/data-platform", icon: Database, label: "数据中台" },
|
||||
{ href: "/user-discovery", icon: Search, label: "用户发现" },
|
||||
{ href: "/user-valuation", icon: Target, label: "用户估值" },
|
||||
{ href: "/ai-analysis", icon: Brain, label: "AI分析" },
|
||||
{ href: "/user-portrait", icon: Target, label: "用户画像" }, // 整合用户池功能
|
||||
{ href: "/ai-assistant", icon: BrainCircuit, label: "AI助手" }, // 新增AI智能助手
|
||||
]
|
||||
|
||||
export default function BottomNav() {
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function Header() {
|
||||
<Search className="h-4 w-4 text-muted-foreground mr-2" />
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="搜索用户、标签、活动..."
|
||||
placeholder="搜索用户、IMEI、手机号、标签..."
|
||||
className="border-none focus-visible:ring-0 focus-visible:ring-offset-0"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
|
||||
@@ -10,7 +10,7 @@ interface MobileHeaderProps {
|
||||
title?: string
|
||||
}
|
||||
|
||||
export default function MobileHeader({ onMenuToggle, title = "数据资产中台" }: MobileHeaderProps) {
|
||||
export default function MobileHeader({ onMenuToggle, title = "卡若数据资产中台" }: MobileHeaderProps) {
|
||||
const [showSearch, setShowSearch] = useState(false)
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
|
||||
@@ -46,7 +46,7 @@ export default function MobileHeader({ onMenuToggle, title = "数据资产中台
|
||||
<div className="flex-1">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="搜索用户、标签、活动..."
|
||||
placeholder="搜索用户、IMEI、手机号..."
|
||||
className="glass-input border-none focus-visible:ring-0 focus-visible:ring-offset-0"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
|
||||
@@ -7,16 +7,14 @@ import { cn } from "@/lib/utils"
|
||||
import {
|
||||
Database,
|
||||
LayoutDashboard,
|
||||
Users,
|
||||
Target,
|
||||
X,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
Search,
|
||||
BarChart3,
|
||||
TrendingUp,
|
||||
Brain,
|
||||
HelpCircle,
|
||||
UserCheck,
|
||||
Layers,
|
||||
Tag,
|
||||
BrainCircuit,
|
||||
} from "lucide-react"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button"
|
||||
@@ -29,9 +27,7 @@ interface MobileSidebarProps {
|
||||
export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
|
||||
const pathname = usePathname()
|
||||
const [expandedSections, setExpandedSections] = useState({
|
||||
"user-discovery": false,
|
||||
"user-valuation": false,
|
||||
"ai-analysis": false,
|
||||
"user-portrait": false, // 用户画像默认不展开
|
||||
})
|
||||
|
||||
const toggleSection = (section: string) => {
|
||||
@@ -70,77 +66,40 @@ export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
|
||||
description: "多源数据整合中心",
|
||||
},
|
||||
{
|
||||
title: "用户发现",
|
||||
href: "/user-discovery",
|
||||
icon: <Search className="h-4 w-4" />,
|
||||
primary: true,
|
||||
expandable: true,
|
||||
section: "user-discovery",
|
||||
description: "用户行为洞察分析",
|
||||
children: [
|
||||
{
|
||||
title: "行为分析",
|
||||
href: "/user-discovery/behavior",
|
||||
icon: <BarChart3 className="h-3 w-3" />,
|
||||
description: "用户行为模式分析",
|
||||
},
|
||||
{
|
||||
title: "用户分群",
|
||||
href: "/user-discovery/segmentation",
|
||||
icon: <Users className="h-3 w-3" />,
|
||||
description: "智能用户分群",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "用户估值",
|
||||
href: "/user-valuation",
|
||||
title: "用户画像", // 整合用户池功能
|
||||
href: "/user-portrait",
|
||||
icon: <Target className="h-4 w-4" />,
|
||||
primary: true,
|
||||
expandable: true,
|
||||
section: "user-valuation",
|
||||
description: "用户价值评估模型",
|
||||
hasHelp: true,
|
||||
section: "user-portrait",
|
||||
description: "用户数据管理与画像分析",
|
||||
children: [
|
||||
{
|
||||
title: "估值模型",
|
||||
href: "/user-valuation/model",
|
||||
icon: <BarChart3 className="h-3 w-3" />,
|
||||
description: "RFM价值评估",
|
||||
hasHelp: true,
|
||||
title: "用户管理", // 原用户池的用户管理
|
||||
href: "/user-portrait/management",
|
||||
icon: <UserCheck className="h-3 w-3" />,
|
||||
description: "IMEI、手机号管理",
|
||||
},
|
||||
{
|
||||
title: "升级路径",
|
||||
href: "/user-valuation/upgrade-paths",
|
||||
icon: <TrendingUp className="h-3 w-3" />,
|
||||
description: "用户价值提升策略",
|
||||
hasHelp: true,
|
||||
title: "用户分群", // 原用户池的用户分群
|
||||
href: "/user-portrait/segmentation",
|
||||
icon: <Layers className="h-3 w-3" />,
|
||||
description: "智能用户分群",
|
||||
},
|
||||
{
|
||||
title: "标签管理", // 原用户画像的标签管理
|
||||
href: "/user-portrait/tags",
|
||||
icon: <Tag className="h-3 w-3" />,
|
||||
description: "用户标签体系",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "AI分析",
|
||||
href: "/ai-analysis",
|
||||
icon: <Brain className="h-4 w-4" />,
|
||||
title: "AI智能助手", // 新增AI智能助手
|
||||
href: "/ai-assistant",
|
||||
icon: <BrainCircuit className="h-4 w-4" />,
|
||||
primary: true,
|
||||
expandable: true,
|
||||
section: "ai-analysis",
|
||||
description: "智能数据洞察",
|
||||
tag: "AI",
|
||||
children: [
|
||||
{
|
||||
title: "趋势识别",
|
||||
href: "/ai-analysis/trends",
|
||||
icon: <TrendingUp className="h-3 w-3" />,
|
||||
description: "AI趋势预测",
|
||||
},
|
||||
{
|
||||
title: "异常检测",
|
||||
href: "/ai-analysis/anomaly",
|
||||
icon: <Search className="h-3 w-3" />,
|
||||
description: "智能异常监控",
|
||||
},
|
||||
],
|
||||
description: "AI数据分析与营销策略",
|
||||
},
|
||||
]
|
||||
|
||||
@@ -148,8 +107,6 @@ export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
|
||||
switch (tag) {
|
||||
case "核心":
|
||||
return "bg-blue-100 text-blue-700 border-blue-200"
|
||||
case "AI":
|
||||
return "bg-purple-100 text-purple-700 border-purple-200"
|
||||
default:
|
||||
return "bg-gray-100 text-gray-700 border-gray-200"
|
||||
}
|
||||
@@ -180,7 +137,7 @@ export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
|
||||
<Database className="h-4 w-4 text-blue-600" />
|
||||
</div>
|
||||
<h1 className="text-sm font-semibold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
|
||||
数据资产中台
|
||||
卡若数据资产中台
|
||||
</h1>
|
||||
</div>
|
||||
<Button variant="ghost" size="icon" onClick={onClose} className="glass-light rounded-lg h-8 w-8">
|
||||
@@ -196,7 +153,7 @@ export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center px-3 py-2 text-xs font-medium rounded-lg transition-all duration-300 group",
|
||||
pathname === item.href
|
||||
pathname === item.href || (item.children && pathname.startsWith(item.href))
|
||||
? "glass-heavy text-blue-700 shadow-glass"
|
||||
: "glass-light text-gray-700 hover:glass-heavy hover:text-blue-600",
|
||||
)}
|
||||
@@ -207,7 +164,6 @@ export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="font-medium truncate">{item.title}</span>
|
||||
{item.hasHelp && <HelpCircle className="h-3 w-3 text-gray-400 flex-shrink-0" />}
|
||||
</div>
|
||||
{item.tag && (
|
||||
<Badge
|
||||
@@ -253,10 +209,7 @@ export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
|
||||
{subItem.icon}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-1">
|
||||
<p className="font-medium truncate">{subItem.title}</p>
|
||||
{subItem.hasHelp && <HelpCircle className="h-3 w-3 text-gray-400 flex-shrink-0" />}
|
||||
</div>
|
||||
<p className="font-medium truncate">{subItem.title}</p>
|
||||
<p className="text-xs text-gray-500 truncate">{subItem.description}</p>
|
||||
</div>
|
||||
</Link>
|
||||
@@ -272,15 +225,15 @@ export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
|
||||
<div className="border-t border-white/20 p-3">
|
||||
<div className="glass-light rounded-lg p-2">
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span className="text-gray-600">AI引擎状态</span>
|
||||
<span className="text-gray-600">数据同步状态</span>
|
||||
<div className="flex items-center">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full mr-1"></div>
|
||||
<span className="text-green-600">运行中</span>
|
||||
<span className="text-green-600">实时</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-xs mt-1">
|
||||
<span className="text-gray-600">数据同步</span>
|
||||
<span className="text-gray-500">实时</span>
|
||||
<span className="text-gray-600">用户池总数</span>
|
||||
<span className="text-gray-500">125,678</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,14 +8,15 @@ import {
|
||||
Database,
|
||||
LayoutDashboard,
|
||||
Settings,
|
||||
Users,
|
||||
Target,
|
||||
ChevronLeft,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
Tag,
|
||||
BarChart3,
|
||||
TrendingUp,
|
||||
UserCheck,
|
||||
Layers,
|
||||
BrainCircuit,
|
||||
Smartphone,
|
||||
} from "lucide-react"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
|
||||
@@ -23,8 +24,7 @@ export default function Sidebar() {
|
||||
const pathname = usePathname()
|
||||
const [expanded, setExpanded] = useState(true)
|
||||
const [expandedSections, setExpandedSections] = useState({
|
||||
"user-portrait": true,
|
||||
"user-value": false,
|
||||
"user-portrait": true, // 用户画像默认展开
|
||||
})
|
||||
|
||||
const toggleSidebar = () => {
|
||||
@@ -38,60 +38,57 @@ export default function Sidebar() {
|
||||
}))
|
||||
}
|
||||
|
||||
// 重新设计的导航结构
|
||||
// 重新设计的导航结构 - 遵循新规则
|
||||
const navItems = [
|
||||
{
|
||||
title: "概览",
|
||||
title: "数据概览",
|
||||
href: "/",
|
||||
icon: <LayoutDashboard className="h-5 w-5" />,
|
||||
primary: true,
|
||||
},
|
||||
{
|
||||
title: "数据集成",
|
||||
href: "/data-integration",
|
||||
title: "数据中台",
|
||||
href: "/data-platform",
|
||||
icon: <Database className="h-5 w-5" />,
|
||||
primary: true,
|
||||
tag: "核心",
|
||||
},
|
||||
{
|
||||
title: "用户画像",
|
||||
title: "用户画像", // 整合用户池功能
|
||||
href: "/user-portrait",
|
||||
icon: <Users className="h-5 w-5" />,
|
||||
icon: <Target className="h-5 w-5" />,
|
||||
primary: true,
|
||||
expandable: true,
|
||||
section: "user-portrait",
|
||||
children: [
|
||||
{
|
||||
title: "用户词",
|
||||
href: "/user-portrait/user-keywords",
|
||||
icon: <Tag className="h-4 w-4" />,
|
||||
title: "用户管理", // 原用户池的用户管理
|
||||
href: "/user-portrait/management",
|
||||
icon: <UserCheck className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
title: "标签管理",
|
||||
title: "用户分群", // 原用户池的用户分群
|
||||
href: "/user-portrait/segmentation",
|
||||
icon: <Layers className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
title: "标签管理", // 原用户画像的标签管理
|
||||
href: "/user-portrait/tags",
|
||||
icon: <Tag className="h-4 w-4" />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "用户估值",
|
||||
href: "/user-value",
|
||||
icon: <Target className="h-5 w-5" />,
|
||||
title: "设备管理", // 提升为一级菜单 [^2]
|
||||
href: "/devices",
|
||||
icon: <Smartphone className="h-5 w-5" />,
|
||||
primary: true,
|
||||
},
|
||||
{
|
||||
title: "AI智能助手", // 新增AI智能助手
|
||||
href: "/ai-assistant",
|
||||
icon: <BrainCircuit className="h-5 w-5" />,
|
||||
primary: true,
|
||||
expandable: true,
|
||||
section: "user-value",
|
||||
children: [
|
||||
{
|
||||
title: "估值模型",
|
||||
href: "/user-value/model",
|
||||
icon: <BarChart3 className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
title: "用户升级路径",
|
||||
href: "/user-value/upgrade-paths",
|
||||
icon: <TrendingUp className="h-4 w-4" />,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
@@ -119,7 +116,7 @@ export default function Sidebar() {
|
||||
<Database className="h-5 w-5 text-blue-600" />
|
||||
</div>
|
||||
<h1 className="text-lg font-semibold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
|
||||
用户数字资产中台
|
||||
卡若数据资产中台
|
||||
</h1>
|
||||
</div>
|
||||
) : (
|
||||
@@ -139,7 +136,7 @@ export default function Sidebar() {
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center px-4 py-3 text-sm font-medium rounded-xl transition-all duration-300 group",
|
||||
pathname === item.href
|
||||
pathname === item.href || (item.children && pathname.startsWith(item.href))
|
||||
? "glass-heavy text-blue-700 shadow-glass"
|
||||
: "glass-light text-gray-700 hover:glass-heavy hover:text-blue-600 hover:scale-105",
|
||||
)}
|
||||
|
||||
@@ -18,10 +18,15 @@ import {
|
||||
Settings,
|
||||
ChevronRight,
|
||||
ChevronDown,
|
||||
Brain,
|
||||
} from "lucide-react"
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { AIAnalysisTools } from "@/components/data-integration/ai-analysis-tools"
|
||||
import { useToast } from "@/components/ui/use-toast" // 导入 useToast
|
||||
|
||||
export default function DataPlatformPage() {
|
||||
const { toast } = useToast() // 初始化 toast
|
||||
const [timeRange, setTimeRange] = useState("today")
|
||||
const [expandedSections, setExpandedSections] = useState({
|
||||
overview: true,
|
||||
@@ -29,6 +34,7 @@ export default function DataPlatformPage() {
|
||||
integration: false,
|
||||
quality: false,
|
||||
})
|
||||
const [activeTab, setActiveTab] = useState("overview")
|
||||
|
||||
// 数据中台数据
|
||||
const platformData = {
|
||||
@@ -50,6 +56,7 @@ export default function DataPlatformPage() {
|
||||
lastSync: "1分钟前",
|
||||
quality: 96.8,
|
||||
description: "用户行为轨迹数据",
|
||||
aiReady: true, // 新增 AI 就绪状态
|
||||
},
|
||||
{
|
||||
name: "CRM系统",
|
||||
@@ -59,6 +66,7 @@ export default function DataPlatformPage() {
|
||||
lastSync: "3分钟前",
|
||||
quality: 94.2,
|
||||
description: "客户关系管理数据",
|
||||
aiReady: true,
|
||||
},
|
||||
{
|
||||
name: "内容平台数据",
|
||||
@@ -68,6 +76,7 @@ export default function DataPlatformPage() {
|
||||
lastSync: "正在同步",
|
||||
quality: 92.5,
|
||||
description: "抖音、小红书等平台数据",
|
||||
aiReady: false,
|
||||
},
|
||||
{
|
||||
name: "触客宝后台",
|
||||
@@ -77,6 +86,7 @@ export default function DataPlatformPage() {
|
||||
lastSync: "5分钟前",
|
||||
quality: 95.1,
|
||||
description: "呼入电话信息数据",
|
||||
aiReady: true,
|
||||
},
|
||||
{
|
||||
name: "飞书妙记",
|
||||
@@ -86,6 +96,7 @@ export default function DataPlatformPage() {
|
||||
lastSync: "10分钟前",
|
||||
quality: 91.3,
|
||||
description: "会议记录和内容摘要",
|
||||
aiReady: true,
|
||||
},
|
||||
{
|
||||
name: "表单提交数据",
|
||||
@@ -95,6 +106,7 @@ export default function DataPlatformPage() {
|
||||
lastSync: "2分钟前",
|
||||
quality: 97.2,
|
||||
description: "在线表单提交数据",
|
||||
aiReady: true,
|
||||
},
|
||||
],
|
||||
integrationStats: {
|
||||
@@ -150,6 +162,14 @@ export default function DataPlatformPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleConnectToAI = (sourceName: string) => {
|
||||
toast({
|
||||
title: "连接到AI",
|
||||
description: `数据源 "${sourceName}" 已准备好连接到AI模型。`,
|
||||
})
|
||||
// 实际应用中,这里会触发一个后端API调用或跳转到AI配置页面
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-4 md:py-6 space-y-4 md:space-y-6">
|
||||
{/* 页面标题和工具栏 */}
|
||||
@@ -182,309 +202,346 @@ export default function DataPlatformPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 数据中台概览 */}
|
||||
<Collapsible
|
||||
open={expandedSections.overview}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, overview: open }))}
|
||||
>
|
||||
<Card className="border-none shadow-md">
|
||||
<CollapsibleTrigger className="w-full">
|
||||
<CardHeader className="bg-gradient-to-r from-blue-50 to-indigo-50 border-b hover:from-blue-100 hover:to-indigo-100 transition-colors cursor-pointer py-3 md:py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<Database className="h-4 w-4 md:h-5 md:w-5 text-blue-600" />
|
||||
<div className="text-left">
|
||||
<CardTitle className="text-base md:text-lg">平台概览</CardTitle>
|
||||
<CardDescription className="text-xs md:text-sm">数据中台整体运行状况</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
{expandedSections.overview ? (
|
||||
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<CardContent className="p-4">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 md:gap-4">
|
||||
<div className="bg-blue-50 p-3 rounded-lg text-center">
|
||||
<div className="text-lg md:text-2xl font-bold text-blue-600">
|
||||
{platformData.overview.totalRecords.toLocaleString()}
|
||||
</div>
|
||||
<div className="text-xs text-blue-700">总数据量</div>
|
||||
</div>
|
||||
<div className="bg-green-50 p-3 rounded-lg text-center">
|
||||
<div className="text-lg md:text-2xl font-bold text-green-600">
|
||||
{platformData.overview.dataSources}
|
||||
</div>
|
||||
<div className="text-xs text-green-700">数据源</div>
|
||||
</div>
|
||||
<div className="bg-purple-50 p-3 rounded-lg text-center">
|
||||
<div className="text-lg md:text-2xl font-bold text-purple-600">
|
||||
{platformData.overview.dataQuality}%
|
||||
</div>
|
||||
<div className="text-xs text-purple-700">数据质量</div>
|
||||
</div>
|
||||
<div className="bg-orange-50 p-3 rounded-lg text-center">
|
||||
<div className="text-lg md:text-2xl font-bold text-orange-600">
|
||||
{platformData.overview.storageUsed}GB
|
||||
</div>
|
||||
<div className="text-xs text-orange-700">存储使用</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 space-y-3">
|
||||
<div>
|
||||
<div className="flex justify-between text-sm mb-1">
|
||||
<span>存储使用率</span>
|
||||
<span>
|
||||
{platformData.overview.storageUsed}GB / {platformData.overview.storageTotal}GB
|
||||
</span>
|
||||
</div>
|
||||
<Progress value={(platformData.overview.storageUsed / platformData.overview.storageTotal) * 100} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between text-sm mb-1">
|
||||
<span>数据质量</span>
|
||||
<span>{platformData.overview.dataQuality}%</span>
|
||||
</div>
|
||||
<Progress value={platformData.overview.dataQuality} />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</CollapsibleContent>
|
||||
</Card>
|
||||
</Collapsible>
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-4 md:grid-cols-5">
|
||||
<TabsTrigger value="overview">概览</TabsTrigger>
|
||||
<TabsTrigger value="sources">数据源</TabsTrigger>
|
||||
<TabsTrigger value="integration">集成统计</TabsTrigger>
|
||||
<TabsTrigger value="quality">数据质量</TabsTrigger>
|
||||
<TabsTrigger value="ai-models">AI 模型</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* 数据源管理 */}
|
||||
<Collapsible
|
||||
open={expandedSections.sources}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, sources: open }))}
|
||||
>
|
||||
<Card className="border-none shadow-md">
|
||||
<CollapsibleTrigger className="w-full">
|
||||
<CardHeader className="bg-gradient-to-r from-green-50 to-emerald-50 border-b hover:from-green-100 hover:to-emerald-100 transition-colors cursor-pointer py-3 md:py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<FileText className="h-4 w-4 md:h-5 md:w-5 text-green-600" />
|
||||
<div className="text-left">
|
||||
<CardTitle className="text-base md:text-lg">数据源管理</CardTitle>
|
||||
<CardDescription className="text-xs md:text-sm">
|
||||
数据源连接状态 ({platformData.dataSources.length})
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
{expandedSections.sources ? (
|
||||
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<CardContent className="p-4">
|
||||
<div className="space-y-3">
|
||||
{platformData.dataSources.map((source, index) => (
|
||||
<div key={index} className="bg-white border rounded-lg p-3 md:p-4">
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-3">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
{getStatusIcon(source.status)}
|
||||
<h3 className="font-medium text-sm md:text-base">{source.name}</h3>
|
||||
<Badge className={getStatusColor(source.status)}>{source.status}</Badge>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{source.type}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-xs md:text-sm text-gray-600 mb-2">{source.description}</p>
|
||||
<div className="flex flex-wrap gap-4 text-xs">
|
||||
<span>数据量: {source.records.toLocaleString()}</span>
|
||||
<span>最后同步: {source.lastSync}</span>
|
||||
<span>质量评分: {source.quality}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col md:items-end gap-2">
|
||||
<div className="text-right">
|
||||
<div className="text-sm font-bold text-green-600">{source.quality}%</div>
|
||||
<div className="text-xs text-gray-500">数据质量</div>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" className="bg-transparent">
|
||||
<Settings className="h-3 w-3 mr-1" />
|
||||
配置
|
||||
</Button>
|
||||
<TabsContent value="overview" className="space-y-6">
|
||||
{/* 数据中台概览 */}
|
||||
<Collapsible
|
||||
open={expandedSections.overview}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, overview: open }))}
|
||||
>
|
||||
<Card className="border-none shadow-md">
|
||||
<CollapsibleTrigger className="w-full">
|
||||
<CardHeader className="bg-gradient-to-r from-blue-50 to-indigo-50 border-b hover:from-blue-100 hover:to-indigo-100 transition-colors cursor-pointer py-3 md:py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<Database className="h-4 w-4 md:h-5 md:w-5 text-blue-600" />
|
||||
<div className="text-left">
|
||||
<CardTitle className="text-base md:text-lg">平台概览</CardTitle>
|
||||
<CardDescription className="text-xs md:text-sm">数据中台整体运行状况</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<Progress value={source.quality} className="h-1.5" />
|
||||
{expandedSections.overview ? (
|
||||
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<CardContent className="p-4">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 md:gap-4">
|
||||
<div className="bg-blue-50 p-3 rounded-lg text-center">
|
||||
<div className="text-lg md:text-2xl font-bold text-blue-600">
|
||||
{platformData.overview.totalRecords.toLocaleString()}
|
||||
</div>
|
||||
<div className="text-xs text-blue-700">总数据量</div>
|
||||
</div>
|
||||
<div className="bg-green-50 p-3 rounded-lg text-center">
|
||||
<div className="text-lg md:text-2xl font-bold text-green-600">
|
||||
{platformData.overview.dataSources}
|
||||
</div>
|
||||
<div className="text-xs text-green-700">数据源</div>
|
||||
</div>
|
||||
<div className="bg-purple-50 p-3 rounded-lg text-center">
|
||||
<div className="text-lg md:text-2xl font-bold text-purple-600">
|
||||
{platformData.overview.dataQuality}%
|
||||
</div>
|
||||
<div className="text-xs text-purple-700">数据质量</div>
|
||||
</div>
|
||||
<div className="bg-orange-50 p-3 rounded-lg text-center">
|
||||
<div className="text-lg md:text-2xl font-bold text-orange-600">
|
||||
{platformData.overview.storageUsed}GB
|
||||
</div>
|
||||
<div className="text-xs text-orange-700">存储使用</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</CollapsibleContent>
|
||||
</Card>
|
||||
</Collapsible>
|
||||
<div className="mt-4 space-y-3">
|
||||
<div>
|
||||
<div className="flex justify-between text-sm mb-1">
|
||||
<span>存储使用率</span>
|
||||
<span>
|
||||
{platformData.overview.storageUsed}GB / {platformData.overview.storageTotal}GB
|
||||
</span>
|
||||
</div>
|
||||
<Progress
|
||||
value={(platformData.overview.storageUsed / platformData.overview.storageTotal) * 100}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between text-sm mb-1">
|
||||
<span>数据质量</span>
|
||||
<span>{platformData.overview.dataQuality}%</span>
|
||||
</div>
|
||||
<Progress value={platformData.overview.dataQuality} />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</CollapsibleContent>
|
||||
</Card>
|
||||
</Collapsible>
|
||||
</TabsContent>
|
||||
|
||||
{/* 数据集成统计 */}
|
||||
<Collapsible
|
||||
open={expandedSections.integration}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, integration: open }))}
|
||||
>
|
||||
<Card className="border-none shadow-md">
|
||||
<CollapsibleTrigger className="w-full">
|
||||
<CardHeader className="bg-gradient-to-r from-purple-50 to-pink-50 border-b hover:from-purple-100 hover:to-pink-100 transition-colors cursor-pointer py-3 md:py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<RefreshCw className="h-4 w-4 md:h-5 md:w-5 text-purple-600" />
|
||||
<div className="text-left">
|
||||
<CardTitle className="text-base md:text-lg">集成统计</CardTitle>
|
||||
<CardDescription className="text-xs md:text-sm">数据集成性能指标</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
{expandedSections.integration ? (
|
||||
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<CardContent className="p-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<div className="bg-blue-50 p-4 rounded-lg">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Database className="h-4 w-4 text-blue-600" />
|
||||
<span className="text-sm font-medium">集成概况</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">总集成数</span>
|
||||
<span className="text-sm font-bold">{platformData.integrationStats.totalIntegrations}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">活跃集成</span>
|
||||
<span className="text-sm font-bold">{platformData.integrationStats.activeIntegrations}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">待处理</span>
|
||||
<span className="text-sm font-bold">{platformData.integrationStats.pendingIntegrations}</span>
|
||||
<TabsContent value="sources" className="space-y-6">
|
||||
{/* 数据源管理 */}
|
||||
<Collapsible
|
||||
open={expandedSections.sources}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, sources: open }))}
|
||||
>
|
||||
<Card className="border-none shadow-md">
|
||||
<CollapsibleTrigger className="w-full">
|
||||
<CardHeader className="bg-gradient-to-r from-green-50 to-emerald-50 border-b hover:from-green-100 hover:to-emerald-100 transition-colors cursor-pointer py-3 md:py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<FileText className="h-4 w-4 md:h-5 md:w-5 text-green-600" />
|
||||
<div className="text-left">
|
||||
<CardTitle className="text-base md:text-lg">数据源管理</CardTitle>
|
||||
<CardDescription className="text-xs md:text-sm">
|
||||
数据源连接状态 ({platformData.dataSources.length})
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
{expandedSections.sources ? (
|
||||
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<CardContent className="p-4">
|
||||
<div className="space-y-3">
|
||||
{platformData.dataSources.map((source, index) => (
|
||||
<div key={index} className="bg-white border rounded-lg p-3 md:p-4">
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-3">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
{getStatusIcon(source.status)}
|
||||
<h3 className="font-medium text-sm md:text-base">{source.name}</h3>
|
||||
<Badge className={getStatusColor(source.status)}>{source.status}</Badge>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{source.type}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-xs md:text-sm text-gray-600 mb-2">{source.description}</p>
|
||||
<div className="flex flex-wrap gap-4 text-xs">
|
||||
<span>数据量: {source.records.toLocaleString()}</span>
|
||||
<span>最后同步: {source.lastSync}</span>
|
||||
<span>质量评分: {source.quality}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col md:items-end gap-2">
|
||||
<div className="text-right">
|
||||
<div className="text-sm font-bold text-green-600">{source.quality}%</div>
|
||||
<div className="text-xs text-gray-500">数据质量</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" size="sm" className="bg-transparent">
|
||||
<Settings className="h-3 w-3 mr-1" />
|
||||
配置
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="bg-transparent"
|
||||
disabled={!source.aiReady}
|
||||
onClick={() => handleConnectToAI(source.name)} // 添加点击事件
|
||||
>
|
||||
<Brain className="h-3 w-3 mr-1" />
|
||||
{source.aiReady ? "连接到AI" : "AI未就绪"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<Progress value={source.quality} className="h-1.5" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</CollapsibleContent>
|
||||
</Card>
|
||||
</Collapsible>
|
||||
</TabsContent>
|
||||
|
||||
<div className="bg-green-50 p-4 rounded-lg">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<CheckCircle className="h-4 w-4 text-green-600" />
|
||||
<span className="text-sm font-medium">性能指标</span>
|
||||
<TabsContent value="integration" className="space-y-6">
|
||||
{/* 数据集成统计 */}
|
||||
<Collapsible
|
||||
open={expandedSections.integration}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, integration: open }))}
|
||||
>
|
||||
<Card className="border-none shadow-md">
|
||||
<CollapsibleTrigger className="w-full">
|
||||
<CardHeader className="bg-gradient-to-r from-purple-50 to-pink-50 border-b hover:from-purple-100 hover:to-pink-100 transition-colors cursor-pointer py-3 md:py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<RefreshCw className="h-4 w-4 md:h-5 md:w-5 text-purple-600" />
|
||||
<div className="text-left">
|
||||
<CardTitle className="text-base md:text-lg">集成统计</CardTitle>
|
||||
<CardDescription className="text-xs md:text-sm">数据集成性能指标</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
{expandedSections.integration ? (
|
||||
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">成功率</span>
|
||||
<span className="text-sm font-bold">{platformData.integrationStats.successRate}%</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">平均同步时间</span>
|
||||
<span className="text-sm font-bold">{platformData.integrationStats.avgSyncTime}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">失败集成</span>
|
||||
<span className="text-sm font-bold">{platformData.integrationStats.failedIntegrations}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-purple-50 p-4 rounded-lg">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Clock className="h-4 w-4 text-purple-600" />
|
||||
<span className="text-sm font-medium">实时状态</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">最后更新</span>
|
||||
<span className="text-sm font-bold">{platformData.overview.lastUpdate}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">同步状态</span>
|
||||
<span className="text-sm font-bold text-green-600">{platformData.overview.syncStatus}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">系统负载</span>
|
||||
<span className="text-sm font-bold">正常</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</CollapsibleContent>
|
||||
</Card>
|
||||
</Collapsible>
|
||||
|
||||
{/* 数据质量监控 */}
|
||||
<Collapsible
|
||||
open={expandedSections.quality}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, quality: open }))}
|
||||
>
|
||||
<Card className="border-none shadow-md">
|
||||
<CollapsibleTrigger className="w-full">
|
||||
<CardHeader className="bg-gradient-to-r from-orange-50 to-red-50 border-b hover:from-orange-100 hover:to-red-100 transition-colors cursor-pointer py-3 md:py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<CheckCircle className="h-4 w-4 md:h-5 md:w-5 text-orange-600" />
|
||||
<div className="text-left">
|
||||
<CardTitle className="text-base md:text-lg">数据质量监控</CardTitle>
|
||||
<CardDescription className="text-xs md:text-sm">数据质量多维度评估</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
{expandedSections.quality ? (
|
||||
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<CardContent className="p-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{Object.entries(platformData.dataQuality).map(([key, value], index) => {
|
||||
const labels = {
|
||||
completeness: "完整性",
|
||||
accuracy: "准确性",
|
||||
consistency: "一致性",
|
||||
timeliness: "及时性",
|
||||
validity: "有效性",
|
||||
uniqueness: "唯一性",
|
||||
}
|
||||
|
||||
const colors = [
|
||||
"bg-blue-50 text-blue-600",
|
||||
"bg-green-50 text-green-600",
|
||||
"bg-purple-50 text-purple-600",
|
||||
"bg-orange-50 text-orange-600",
|
||||
"bg-pink-50 text-pink-600",
|
||||
"bg-indigo-50 text-indigo-600",
|
||||
]
|
||||
|
||||
return (
|
||||
<div key={key} className={`p-4 rounded-lg ${colors[index]}`}>
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold mb-1">{value}%</div>
|
||||
<div className="text-sm">{labels[key as keyof typeof labels]}</div>
|
||||
<div className="mt-2">
|
||||
<Progress value={value} className="h-1.5" />
|
||||
</CardHeader>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<CardContent className="p-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<div className="bg-blue-50 p-4 rounded-lg">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Database className="h-4 w-4 text-blue-600" />
|
||||
<span className="text-sm font-medium">集成概况</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">总集成数</span>
|
||||
<span className="text-sm font-bold">{platformData.integrationStats.totalIntegrations}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">活跃集成</span>
|
||||
<span className="text-sm font-bold">{platformData.integrationStats.activeIntegrations}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">待处理</span>
|
||||
<span className="text-sm font-bold">{platformData.integrationStats.pendingIntegrations}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</CardContent>
|
||||
</CollapsibleContent>
|
||||
</Card>
|
||||
</Collapsible>
|
||||
|
||||
<div className="bg-green-50 p-4 rounded-lg">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<CheckCircle className="h-4 w-4 text-green-600" />
|
||||
<span className="text-sm font-medium">性能指标</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">成功率</span>
|
||||
<span className="text-sm font-bold">{platformData.integrationStats.successRate}%</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">平均同步时间</span>
|
||||
<span className="text-sm font-bold">{platformData.integrationStats.avgSyncTime}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">失败集成</span>
|
||||
<span className="text-sm font-bold">{platformData.integrationStats.failedIntegrations}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-purple-50 p-4 rounded-lg">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Clock className="h-4 w-4 text-purple-600" />
|
||||
<span className="text-sm font-medium">实时状态</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">最后更新</span>
|
||||
<span className="text-sm font-bold">{platformData.overview.lastUpdate}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">同步状态</span>
|
||||
<span className="text-sm font-bold text-green-600">{platformData.overview.syncStatus}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-xs text-gray-600">系统负载</span>
|
||||
<span className="text-sm font-bold">正常</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</CollapsibleContent>
|
||||
</Card>
|
||||
</Collapsible>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="quality" className="space-y-6">
|
||||
{/* 数据质量监控 */}
|
||||
<Collapsible
|
||||
open={expandedSections.quality}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, quality: open }))}
|
||||
>
|
||||
<Card className="border-none shadow-md">
|
||||
<CollapsibleTrigger className="w-full">
|
||||
<CardHeader className="bg-gradient-to-r from-orange-50 to-red-50 border-b hover:from-orange-100 hover:to-red-100 transition-colors cursor-pointer py-3 md:py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<CheckCircle className="h-4 w-4 md:h-5 md:w-5 text-orange-600" />
|
||||
<div className="text-left">
|
||||
<CardTitle className="text-base md:text-lg">数据质量监控</CardTitle>
|
||||
<CardDescription className="text-xs md:text-sm">数据质量多维度评估</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
{expandedSections.quality ? (
|
||||
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<CardContent className="p-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{Object.entries(platformData.dataQuality).map(([key, value], index) => {
|
||||
const labels = {
|
||||
completeness: "完整性",
|
||||
accuracy: "准确性",
|
||||
consistency: "一致性",
|
||||
timeliness: "及时性",
|
||||
validity: "有效性",
|
||||
uniqueness: "唯一性",
|
||||
}
|
||||
|
||||
const colors = [
|
||||
"bg-blue-50 text-blue-600",
|
||||
"bg-green-50 text-green-600",
|
||||
"bg-purple-50 text-purple-600",
|
||||
"bg-orange-50 text-orange-600",
|
||||
"bg-pink-50 text-pink-600",
|
||||
"bg-indigo-50 text-indigo-600",
|
||||
]
|
||||
|
||||
return (
|
||||
<div key={key} className={`p-4 rounded-lg ${colors[index]}`}>
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold mb-1">{value}%</div>
|
||||
<div className="text-sm">{labels[key as keyof typeof labels]}</div>
|
||||
<div className="mt-2">
|
||||
<Progress value={value} className="h-1.5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</CardContent>
|
||||
</CollapsibleContent>
|
||||
</Card>
|
||||
</Collapsible>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="ai-models" className="space-y-6">
|
||||
{/* AI 模型管理 */}
|
||||
<AIAnalysisTools />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import ClientLayout from "./ClientLayout"
|
||||
const inter = Inter({ subsets: ["latin"] })
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
title: "卡若数据资产中台",
|
||||
description: "基于IMEI、手机号的用户数据资产管理平台",
|
||||
generator: 'v0.dev'
|
||||
}
|
||||
|
||||
|
||||
179
app/page.tsx
@@ -8,15 +8,17 @@ import { Progress } from "@/components/ui/progress"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import {
|
||||
Database,
|
||||
Users,
|
||||
Target,
|
||||
Brain,
|
||||
Search,
|
||||
BarChart3,
|
||||
TrendingUp,
|
||||
RefreshCw,
|
||||
Download,
|
||||
ChevronRight,
|
||||
Activity,
|
||||
Zap,
|
||||
UserCheck,
|
||||
Layers,
|
||||
Tag,
|
||||
} from "lucide-react"
|
||||
import Link from "next/link"
|
||||
|
||||
@@ -31,24 +33,24 @@ export default function HomePage() {
|
||||
sources: 8,
|
||||
lastUpdate: "2分钟前",
|
||||
},
|
||||
userDiscovery: {
|
||||
userPool: {
|
||||
totalUsers: 125678,
|
||||
activeUsers: 45678,
|
||||
newUsers: 1234,
|
||||
engagement: "87.5%",
|
||||
growth: "+8.2%",
|
||||
},
|
||||
userPortrait: {
|
||||
totalTags: 342,
|
||||
activeTags: 287,
|
||||
coverage: "95.8%",
|
||||
growth: "+5.3%",
|
||||
},
|
||||
userValuation: {
|
||||
avgValue: 3248,
|
||||
highValue: 12456,
|
||||
growth: "+15.8%",
|
||||
upgradeRate: "12.5%",
|
||||
},
|
||||
aiAnalysis: {
|
||||
insights: 156,
|
||||
anomalies: 3,
|
||||
predictions: 89,
|
||||
accuracy: "94.2%",
|
||||
},
|
||||
}
|
||||
|
||||
// 实时活动数据
|
||||
@@ -56,21 +58,21 @@ export default function HomePage() {
|
||||
{
|
||||
id: 1,
|
||||
type: "data_sync",
|
||||
message: "电商平台数据同步完成",
|
||||
message: "IMEI数据同步完成",
|
||||
time: "刚刚",
|
||||
status: "success",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: "user_discovery",
|
||||
message: "发现新用户行为模式",
|
||||
type: "user_pool",
|
||||
message: "新增用户池分群",
|
||||
time: "2分钟前",
|
||||
status: "info",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: "ai_analysis",
|
||||
message: "AI检测到异常流量",
|
||||
type: "user_portrait",
|
||||
message: "标签规则自动执行",
|
||||
time: "5分钟前",
|
||||
status: "warning",
|
||||
},
|
||||
@@ -87,12 +89,12 @@ export default function HomePage() {
|
||||
switch (type) {
|
||||
case "data_sync":
|
||||
return <Database className="h-4 w-4" />
|
||||
case "user_discovery":
|
||||
return <Search className="h-4 w-4" />
|
||||
case "user_valuation":
|
||||
case "user_pool":
|
||||
return <Users className="h-4 w-4" />
|
||||
case "user_portrait":
|
||||
return <Target className="h-4 w-4" />
|
||||
case "ai_analysis":
|
||||
return <Brain className="h-4 w-4" />
|
||||
case "user_valuation":
|
||||
return <TrendingUp className="h-4 w-4" />
|
||||
default:
|
||||
return <Activity className="h-4 w-4" />
|
||||
}
|
||||
@@ -117,7 +119,7 @@ export default function HomePage() {
|
||||
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4">
|
||||
<div>
|
||||
<h1 className="text-2xl md:text-3xl font-bold tracking-tight">数据概览</h1>
|
||||
<p className="text-sm md:text-base text-muted-foreground">智能数据分析平台总览</p>
|
||||
<p className="text-sm md:text-base text-muted-foreground">卡若数据资产中台总览</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Select value={timeRange} onValueChange={setTimeRange}>
|
||||
@@ -178,34 +180,34 @@ export default function HomePage() {
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
{/* 用户发现 */}
|
||||
<Link href="/user-discovery">
|
||||
{/* 用户池 */}
|
||||
<Link href="/user-pool">
|
||||
<Card className="border-none shadow-md hover:shadow-lg transition-all duration-300 cursor-pointer group">
|
||||
<CardHeader className="bg-gradient-to-r from-green-50 to-emerald-50 pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Search className="h-5 w-5 text-green-600" />
|
||||
<CardTitle className="text-base">用户发现</CardTitle>
|
||||
<Users className="h-5 w-5 text-green-600" />
|
||||
<CardTitle className="text-base">用户池</CardTitle>
|
||||
</div>
|
||||
</div>
|
||||
<CardDescription className="text-xs">用户行为洞察分析</CardDescription>
|
||||
<CardDescription className="text-xs">用户数据管理中心</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-3">
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">总用户数</span>
|
||||
<span className="font-bold">{todayStats.userPool.totalUsers.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">活跃用户</span>
|
||||
<span className="font-bold">{todayStats.userDiscovery.activeUsers.toLocaleString()}</span>
|
||||
<span className="font-bold">{todayStats.userPool.activeUsers.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">新增用户</span>
|
||||
<span className="font-bold">{todayStats.userDiscovery.newUsers.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">参与度</span>
|
||||
<span className="text-green-600 font-bold">{todayStats.userDiscovery.engagement}</span>
|
||||
<span className="font-bold">{todayStats.userPool.newUsers.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-3">
|
||||
<span className="text-xs text-green-600">增长 {todayStats.userDiscovery.growth}</span>
|
||||
<span className="text-xs text-green-600">增长 {todayStats.userPool.growth}</span>
|
||||
<ChevronRight className="h-4 w-4 text-gray-400 group-hover:text-green-600 transition-colors" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -213,13 +215,48 @@ export default function HomePage() {
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
{/* 用户画像 */}
|
||||
<Link href="/user-portrait">
|
||||
<Card className="border-none shadow-md hover:shadow-lg transition-all duration-300 cursor-pointer group">
|
||||
<CardHeader className="bg-gradient-to-r from-purple-50 to-pink-50 pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Target className="h-5 w-5 text-purple-600" />
|
||||
<CardTitle className="text-base">用户画像</CardTitle>
|
||||
</div>
|
||||
</div>
|
||||
<CardDescription className="text-xs">用户标签画像分析</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-3">
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">标签总数</span>
|
||||
<span className="font-bold">{todayStats.userPortrait.totalTags}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">活跃标签</span>
|
||||
<span className="font-bold">{todayStats.userPortrait.activeTags}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">覆盖率</span>
|
||||
<span className="text-purple-600 font-bold">{todayStats.userPortrait.coverage}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-3">
|
||||
<span className="text-xs text-purple-600">增长 {todayStats.userPortrait.growth}</span>
|
||||
<ChevronRight className="h-4 w-4 text-gray-400 group-hover:text-purple-600 transition-colors" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
{/* 用户估值 */}
|
||||
<Link href="/user-valuation">
|
||||
<Card className="border-none shadow-md hover:shadow-lg transition-all duration-300 cursor-pointer group">
|
||||
<CardHeader className="bg-gradient-to-r from-purple-50 to-pink-50 pb-3">
|
||||
<CardHeader className="bg-gradient-to-r from-orange-50 to-red-50 pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Target className="h-5 w-5 text-purple-600" />
|
||||
<TrendingUp className="h-5 w-5 text-orange-600" />
|
||||
<CardTitle className="text-base">用户估值</CardTitle>
|
||||
</div>
|
||||
</div>
|
||||
@@ -237,46 +274,10 @@ export default function HomePage() {
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">升级率</span>
|
||||
<span className="text-purple-600 font-bold">{todayStats.userValuation.upgradeRate}</span>
|
||||
<span className="text-orange-600 font-bold">{todayStats.userValuation.upgradeRate}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-3">
|
||||
<span className="text-xs text-purple-600">增长 {todayStats.userValuation.growth}</span>
|
||||
<ChevronRight className="h-4 w-4 text-gray-400 group-hover:text-purple-600 transition-colors" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
{/* AI分析 */}
|
||||
<Link href="/ai-analysis">
|
||||
<Card className="border-none shadow-md hover:shadow-lg transition-all duration-300 cursor-pointer group">
|
||||
<CardHeader className="bg-gradient-to-r from-orange-50 to-red-50 pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Brain className="h-5 w-5 text-orange-600" />
|
||||
<CardTitle className="text-base">AI分析</CardTitle>
|
||||
</div>
|
||||
<Badge className="bg-orange-100 text-orange-700">AI</Badge>
|
||||
</div>
|
||||
<CardDescription className="text-xs">智能数据洞察</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-3">
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">AI洞察</span>
|
||||
<span className="font-bold">{todayStats.aiAnalysis.insights}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">异常检测</span>
|
||||
<span className="font-bold">{todayStats.aiAnalysis.anomalies}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">预测准确率</span>
|
||||
<span className="text-orange-600 font-bold">{todayStats.aiAnalysis.accuracy}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-3">
|
||||
<span className="text-xs text-orange-600">预测 {todayStats.aiAnalysis.predictions}个</span>
|
||||
<span className="text-xs text-orange-600">增长 {todayStats.userValuation.growth}</span>
|
||||
<ChevronRight className="h-4 w-4 text-gray-400 group-hover:text-orange-600 transition-colors" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -291,7 +292,7 @@ export default function HomePage() {
|
||||
<Card className="lg:col-span-2 border-none shadow-md">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<BarChart3 className="h-5 w-5 text-blue-600" />
|
||||
<Activity className="h-5 w-5 text-blue-600" />
|
||||
今日关键指标
|
||||
</CardTitle>
|
||||
<CardDescription>实时数据监控</CardDescription>
|
||||
@@ -307,12 +308,12 @@ export default function HomePage() {
|
||||
<div className="text-xs text-gray-600">平均响应时间</div>
|
||||
</div>
|
||||
<div className="text-center p-3 bg-purple-50 rounded-lg">
|
||||
<div className="text-2xl font-bold text-purple-600">156</div>
|
||||
<div className="text-xs text-gray-600">AI洞察数</div>
|
||||
<div className="text-2xl font-bold text-purple-600">342</div>
|
||||
<div className="text-xs text-gray-600">活跃标签数</div>
|
||||
</div>
|
||||
<div className="text-center p-3 bg-orange-50 rounded-lg">
|
||||
<div className="text-2xl font-bold text-orange-600">94.2%</div>
|
||||
<div className="text-xs text-gray-600">预测准确率</div>
|
||||
<div className="text-2xl font-bold text-orange-600">95.8%</div>
|
||||
<div className="text-xs text-gray-600">标签覆盖率</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 space-y-3">
|
||||
@@ -325,7 +326,7 @@ export default function HomePage() {
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between text-sm mb-1">
|
||||
<span>AI模型训练</span>
|
||||
<span>标签规则执行</span>
|
||||
<span>92%</span>
|
||||
</div>
|
||||
<Progress value={92} className="h-2" />
|
||||
@@ -381,22 +382,22 @@ export default function HomePage() {
|
||||
<span className="text-xs">数据导入</span>
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/user-discovery/behavior">
|
||||
<Link href="/user-pool/management">
|
||||
<Button variant="outline" className="w-full h-16 flex flex-col gap-1 bg-transparent">
|
||||
<Search className="h-5 w-5" />
|
||||
<span className="text-xs">行为分析</span>
|
||||
<UserCheck className="h-5 w-5" />
|
||||
<span className="text-xs">用户管理</span>
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/user-valuation/model">
|
||||
<Link href="/user-pool/segmentation">
|
||||
<Button variant="outline" className="w-full h-16 flex flex-col gap-1 bg-transparent">
|
||||
<Target className="h-5 w-5" />
|
||||
<span className="text-xs">价值评估</span>
|
||||
<Layers className="h-5 w-5" />
|
||||
<span className="text-xs">用户分群</span>
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/ai-analysis/trends">
|
||||
<Link href="/user-portrait/tags">
|
||||
<Button variant="outline" className="w-full h-16 flex flex-col gap-1 bg-transparent">
|
||||
<Brain className="h-5 w-5" />
|
||||
<span className="text-xs">AI预测</span>
|
||||
<Tag className="h-5 w-5" />
|
||||
<span className="text-xs">标签管理</span>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -257,7 +257,7 @@ export default function ApiPage() {
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 text-gray-500">
|
||||
<Clock className="w-4 h-4" />
|
||||
<span>下次执行: {task.nextExecutionTime}</span>
|
||||
<span>下次执<EFBFBD><EFBFBD>: {task.nextExecutionTime}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
22
app/user-pool/loading.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
export default function Loading() {
|
||||
return (
|
||||
<div className="container mx-auto py-6 space-y-6">
|
||||
<div className="animate-pulse">
|
||||
<div className="h-8 bg-gray-200 rounded w-1/4 mb-2"></div>
|
||||
<div className="h-4 bg-gray-200 rounded w-1/2"></div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-5 gap-4">
|
||||
{[...Array(5)].map((_, i) => (
|
||||
<div key={i} className="animate-pulse">
|
||||
<div className="h-20 bg-gray-200 rounded"></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="animate-pulse">
|
||||
<div className="h-64 bg-gray-200 rounded"></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { UserPoolHeader } from "@/components/user-pool/user-pool-header"
|
||||
import { UserPoolTable } from "@/components/user-pool/user-pool-table"
|
||||
import { UserPoolFilters } from "@/components/user-pool/user-pool-filters"
|
||||
import { UserPoolStats } from "@/components/user-pool/user-pool-stats"
|
||||
|
||||
export default function UserPoolPage() {
|
||||
return (
|
||||
<div className="container mx-auto p-6 space-y-6">
|
||||
<UserPoolHeader />
|
||||
<UserPoolStats />
|
||||
<UserPoolFilters />
|
||||
<UserPoolTable />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -19,31 +19,61 @@ import {
|
||||
Users,
|
||||
Eye,
|
||||
Settings,
|
||||
Smartphone,
|
||||
MapPin,
|
||||
Layers,
|
||||
UserCheck,
|
||||
} from "lucide-react"
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
|
||||
|
||||
export default function UserPortrait() {
|
||||
export default function UserPortraitPage() {
|
||||
const router = useRouter()
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const [selectedCategory, setSelectedCategory] = useState("all")
|
||||
const [selectedPool, setSelectedPool] = useState("all")
|
||||
const [selectedDevice, setSelectedDevice] = useState("all") // 新增设备筛选
|
||||
const [selectedWechatDialog, setSelectedWechatDialog] = useState<{ isOpen: boolean; accounts: any[] }>({
|
||||
isOpen: false,
|
||||
accounts: [],
|
||||
})
|
||||
const [expandedSections, setExpandedSections] = useState({
|
||||
userKeywords: true,
|
||||
tagManagement: false,
|
||||
userList: true,
|
||||
overview: true, // 用户池概览
|
||||
userManagement: true, // 用户管理 (原用户池管理)
|
||||
userSegmentation: false, // 用户分群 (原用户池分群)
|
||||
tagManagement: false, // 标签管理 (原用户画像标签管理)
|
||||
})
|
||||
|
||||
// 使用通用公司名称和当天时间线的用户数据
|
||||
// 用户池概览数据 (从原 user-pool/page.tsx 整合)
|
||||
const poolData = {
|
||||
overview: {
|
||||
totalUsers: 125678,
|
||||
activeUsers: 89234,
|
||||
newUsers: 3456,
|
||||
privatePool: 78234,
|
||||
publicPool: 47444,
|
||||
},
|
||||
deviceStats: {
|
||||
ios: 45678,
|
||||
android: 67890,
|
||||
other: 12110,
|
||||
},
|
||||
locationStats: {
|
||||
tier1: 56789,
|
||||
tier2: 34567,
|
||||
tier3: 23456,
|
||||
overseas: 10866,
|
||||
},
|
||||
}
|
||||
|
||||
// 用户数据 (整合了更多设备和平台信息)
|
||||
const mockUsers = [
|
||||
{
|
||||
id: "1",
|
||||
name: "张明华",
|
||||
company: "科技创新有限公司",
|
||||
imei: "860123456789012",
|
||||
phone: "13800138001",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
tags: ["高价值", "技术决策者", "活跃用户", "iOS用户", "企业级客户"],
|
||||
category: "企业服务",
|
||||
@@ -55,11 +85,18 @@ export default function UserPortrait() {
|
||||
{ id: "wx1", name: "张明华", avatar: "/placeholder.svg?height=40&width=40", tags: ["主要账号", "活跃"] },
|
||||
{ id: "wx2", name: "张明华工作号", avatar: "/placeholder.svg?height=40&width=40", tags: ["工作账号"] },
|
||||
],
|
||||
deviceBrand: "iPhone", // 新增设备信息
|
||||
deviceModel: "iPhone 14 Pro",
|
||||
location: "北京市",
|
||||
platform: "微信",
|
||||
status: "在线",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "李雨婷",
|
||||
company: "数字营销公司",
|
||||
imei: "860987654321098",
|
||||
phone: "13900139001",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
tags: ["中高价值", "产品经理", "内容创作者", "Android用户"],
|
||||
category: "内容营销",
|
||||
@@ -70,11 +107,18 @@ export default function UserPortrait() {
|
||||
wechatAccounts: [
|
||||
{ id: "wx3", name: "李雨婷", avatar: "/placeholder.svg?height=40&width=40", tags: ["主要账号"] },
|
||||
],
|
||||
deviceBrand: "华为",
|
||||
deviceModel: "Mate 50 Pro",
|
||||
location: "上海市",
|
||||
platform: "抖音",
|
||||
status: "活跃",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "王建国",
|
||||
company: "软件开发工作室",
|
||||
imei: "860456789123456",
|
||||
phone: "13700137001",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
tags: ["高价值", "技术专家", "开源贡献者", "MacOS用户"],
|
||||
category: "技术社区",
|
||||
@@ -85,26 +129,40 @@ export default function UserPortrait() {
|
||||
wechatAccounts: [
|
||||
{ id: "wx4", name: "王建国", avatar: "/placeholder.svg?height=40&width=40", tags: ["主要账号", "技术交流"] },
|
||||
],
|
||||
deviceBrand: "小米",
|
||||
deviceModel: "小米13 Ultra",
|
||||
location: "深圳市",
|
||||
platform: "微信",
|
||||
status: "在线",
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
name: "刘思琪",
|
||||
company: "数据分析咨询",
|
||||
imei: "860789123456789",
|
||||
phone: "13600136001",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
tags: ["中价值", "运营专家", "数据分析师", "Windows用户"],
|
||||
category: "数据分析",
|
||||
pool: "私域池",
|
||||
pool: "公域池",
|
||||
lastActive: "今天 11:15",
|
||||
value: 18900,
|
||||
rfmScore: 72,
|
||||
wechatAccounts: [
|
||||
{ id: "wx5", name: "刘思琪", avatar: "/placeholder.svg?height=40&width=40", tags: ["主要账号"] },
|
||||
],
|
||||
deviceBrand: "OPPO",
|
||||
deviceModel: "Find X6 Pro",
|
||||
location: "广州市",
|
||||
platform: "小红书",
|
||||
status: "活跃",
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
name: "陈浩然",
|
||||
company: "云计算服务商",
|
||||
imei: "860321654987321",
|
||||
phone: "13500135001",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
tags: ["高价值", "架构师", "技术领导", "Linux用户"],
|
||||
category: "技术架构",
|
||||
@@ -116,72 +174,17 @@ export default function UserPortrait() {
|
||||
{ id: "wx6", name: "陈浩然", avatar: "/placeholder.svg?height=40&width=40", tags: ["主要账号", "技术分享"] },
|
||||
{ id: "wx7", name: "陈浩然个人号", avatar: "/placeholder.svg?height=40&width=40", tags: ["个人账号"] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "6",
|
||||
name: "周欣妍",
|
||||
company: "用户体验设计",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
tags: ["中价值", "UI设计师", "用户体验", "iOS用户"],
|
||||
category: "设计创意",
|
||||
pool: "公域池",
|
||||
lastActive: "今天 09:45",
|
||||
value: 12300,
|
||||
rfmScore: 65,
|
||||
wechatAccounts: [
|
||||
{ id: "wx8", name: "周欣妍", avatar: "/placeholder.svg?height=40&width=40", tags: ["主要账号"] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "7",
|
||||
name: "马志强",
|
||||
company: "企业解决方案",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
tags: ["高价值", "解决方案专家", "B2B销售", "Android用户"],
|
||||
category: "企业解决方案",
|
||||
pool: "私域池",
|
||||
lastActive: "今天 08:20",
|
||||
value: 38700,
|
||||
rfmScore: 87,
|
||||
wechatAccounts: [
|
||||
{ id: "wx9", name: "马志强", avatar: "/placeholder.svg?height=40&width=40", tags: ["主要账号", "商务"] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "8",
|
||||
name: "赵丽娟",
|
||||
company: "品牌营销策划",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
tags: ["中高价值", "市场营销", "品牌推广", "iOS用户"],
|
||||
category: "品牌营销",
|
||||
pool: "公域池",
|
||||
lastActive: "今天 07:30",
|
||||
value: 21500,
|
||||
rfmScore: 76,
|
||||
wechatAccounts: [
|
||||
{ id: "wx10", name: "赵丽娟", avatar: "/placeholder.svg?height=40&width=40", tags: ["主要账号"] },
|
||||
{ id: "wx11", name: "赵丽娟工作号", avatar: "/placeholder.svg?height=40&width=40", tags: ["工作账号"] },
|
||||
],
|
||||
deviceBrand: "vivo",
|
||||
deviceModel: "X90 Pro+",
|
||||
location: "杭州市",
|
||||
platform: "微信",
|
||||
status: "在线",
|
||||
},
|
||||
]
|
||||
|
||||
const [users, setUsers] = useState(mockUsers)
|
||||
|
||||
// 流量关键词统计
|
||||
const keywordStats = {
|
||||
totalKeywords: 156,
|
||||
activeKeywords: 124,
|
||||
coverage: 89.4,
|
||||
topKeywords: [
|
||||
{ keyword: "技术解决方案", count: 45, trend: "+12%", value: "高" },
|
||||
{ keyword: "数据分析", count: 38, trend: "+8%", value: "中高" },
|
||||
{ keyword: "用户体验", count: 32, trend: "+15%", value: "中" },
|
||||
{ keyword: "品牌营销", count: 28, trend: "+6%", value: "中高" },
|
||||
{ keyword: "企业服务", count: 25, trend: "+10%", value: "高" },
|
||||
],
|
||||
}
|
||||
|
||||
// 标签管理统计
|
||||
// 标签管理统计 (保持不变)
|
||||
const tagStats = {
|
||||
totalTags: 342,
|
||||
activeTags: 287,
|
||||
@@ -196,22 +199,64 @@ export default function UserPortrait() {
|
||||
],
|
||||
}
|
||||
|
||||
// 获取所有类别和池子
|
||||
// 用户分析统计 (保持不变,但现在是用户画像的一部分)
|
||||
const analysisStats = {
|
||||
totalAnalyzed: 125678,
|
||||
taggedUsers: 119845,
|
||||
avgTagsPerUser: 4.2,
|
||||
autoTagging: 89.5,
|
||||
recentAnalysis: [
|
||||
{ type: "设备品牌分析", count: 45, trend: "+12%" },
|
||||
{ type: "地理位置分析", count: 38, trend: "+8%" },
|
||||
{ type: "行为模式分析", count: 32, trend: "+15%" },
|
||||
{ type: "价值分层分析", count: 28, trend: "+6%" },
|
||||
],
|
||||
}
|
||||
|
||||
// 用户分群数据 (从原 user-pool/page.tsx 整合)
|
||||
const userSegments = [
|
||||
{
|
||||
name: "高价值用户组",
|
||||
count: 12456,
|
||||
description: "RFM评分 > 80",
|
||||
color: "bg-blue-50 text-blue-600",
|
||||
},
|
||||
{
|
||||
name: "潜力用户组",
|
||||
count: 18765,
|
||||
description: "增长潜力用户",
|
||||
color: "bg-green-50 text-green-600",
|
||||
},
|
||||
{
|
||||
name: "技术专家组",
|
||||
count: 8234,
|
||||
description: "技术决策者",
|
||||
color: "bg-purple-50 text-purple-600",
|
||||
},
|
||||
]
|
||||
|
||||
// 获取所有类别、池子和设备品牌
|
||||
const categories = ["all", ...new Set(users.map((user) => user.category))]
|
||||
const pools = ["all", ...new Set(users.map((user) => user.pool))]
|
||||
const devices = ["all", ...new Set(users.map((user) => user.deviceBrand))]
|
||||
|
||||
// 过滤用户的函数
|
||||
const filteredUsers = users.filter((user) => {
|
||||
const matchesSearch =
|
||||
user.name.includes(searchQuery) ||
|
||||
user.company.includes(searchQuery) ||
|
||||
user.imei.includes(searchQuery) ||
|
||||
user.phone.includes(searchQuery) ||
|
||||
user.tags.some((tag) => tag.includes(searchQuery)) ||
|
||||
user.category.includes(searchQuery)
|
||||
user.category.includes(searchQuery) ||
|
||||
user.deviceBrand.includes(searchQuery) || // 搜索中包含设备品牌
|
||||
user.location.includes(searchQuery) // 搜索中包含位置
|
||||
|
||||
const matchesCategory = selectedCategory === "all" || user.category === selectedCategory
|
||||
const matchesPool = selectedPool === "all" || user.pool === selectedPool
|
||||
const matchesDevice = selectedDevice === "all" || user.deviceBrand === selectedDevice // 筛选设备品牌
|
||||
|
||||
return matchesSearch && matchesCategory && matchesPool
|
||||
return matchesSearch && matchesCategory && matchesPool && matchesDevice
|
||||
})
|
||||
|
||||
// 处理用户点击
|
||||
@@ -237,14 +282,14 @@ export default function UserPortrait() {
|
||||
}))
|
||||
}
|
||||
|
||||
const getValueColor = (value: string) => {
|
||||
switch (value) {
|
||||
case "高":
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case "在线":
|
||||
return "bg-green-100 text-green-800"
|
||||
case "中高":
|
||||
case "活跃":
|
||||
return "bg-blue-100 text-blue-800"
|
||||
case "中":
|
||||
return "bg-yellow-100 text-yellow-800"
|
||||
case "离线":
|
||||
return "bg-gray-100 text-gray-800"
|
||||
default:
|
||||
return "bg-gray-100 text-gray-800"
|
||||
}
|
||||
@@ -255,7 +300,7 @@ export default function UserPortrait() {
|
||||
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4">
|
||||
<div>
|
||||
<h1 className="text-2xl md:text-3xl font-bold tracking-tight">用户画像</h1>
|
||||
<p className="text-sm md:text-base text-muted-foreground">数据驱动的用户分析平台</p>
|
||||
<p className="text-sm md:text-base text-muted-foreground">基于IMEI、手机号的用户标签画像分析</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button size="sm" className="flex-1 md:flex-none">
|
||||
@@ -269,23 +314,23 @@ export default function UserPortrait() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 流量关键词管理 */}
|
||||
{/* 用户池概览 (从原 user-pool/page.tsx 整合) */}
|
||||
<Collapsible
|
||||
open={expandedSections.userKeywords}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, userKeywords: open }))}
|
||||
open={expandedSections.overview}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, overview: open }))}
|
||||
>
|
||||
<Card className="border shadow-sm">
|
||||
<Card className="border-none shadow-md">
|
||||
<CollapsibleTrigger className="w-full">
|
||||
<CardHeader className="hover:bg-gray-50 transition-colors cursor-pointer py-3 md:py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardHeader className="bg-gradient-to-r from-blue-50 to-indigo-50 border-b hover:from-blue-100 hover:to-indigo-100 transition-colors cursor-pointer py-3 md:py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<Tag className="h-4 w-4 md:h-5 md:w-5 text-blue-600" />
|
||||
<Users className="h-4 w-4 md:h-5 md:w-5 text-blue-600" />
|
||||
<div className="text-left">
|
||||
<CardTitle className="text-base md:text-lg">流量关键词</CardTitle>
|
||||
<CardDescription className="text-xs md:text-sm">关键词价值评估与分类</CardDescription>
|
||||
<CardTitle className="text-base md:text-lg">用户池概览</CardTitle>
|
||||
<CardDescription className="text-xs md:text-sm">用户数据统计与分布</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
{expandedSections.userKeywords ? (
|
||||
{expandedSections.overview ? (
|
||||
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
|
||||
@@ -294,42 +339,361 @@ export default function UserPortrait() {
|
||||
</CardHeader>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<CardContent className="pt-0 px-4 md:px-6">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 md:gap-4 mb-4">
|
||||
<div className="bg-blue-50 p-2 md:p-3 rounded-lg">
|
||||
<p className="text-xs md:text-sm font-medium text-blue-600">总关键词</p>
|
||||
<p className="text-lg md:text-2xl font-bold text-blue-900">{keywordStats.totalKeywords}</p>
|
||||
<CardContent className="p-4">
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-3 md:gap-4 mb-6">
|
||||
<div className="bg-blue-50 p-3 rounded-lg text-center">
|
||||
<div className="text-lg md:text-2xl font-bold text-blue-600">
|
||||
{poolData.overview.totalUsers.toLocaleString()}
|
||||
</div>
|
||||
<div className="text-xs text-blue-700">总用户数</div>
|
||||
</div>
|
||||
<div className="bg-green-50 p-2 md:p-3 rounded-lg">
|
||||
<p className="text-xs md:text-sm font-medium text-green-600">活跃关键词</p>
|
||||
<p className="text-lg md:text-2xl font-bold text-green-900">{keywordStats.activeKeywords}</p>
|
||||
<div className="bg-green-50 p-3 rounded-lg text-center">
|
||||
<div className="text-lg md:text-2xl font-bold text-green-600">
|
||||
{poolData.overview.activeUsers.toLocaleString()}
|
||||
</div>
|
||||
<div className="text-xs text-green-700">活跃用户</div>
|
||||
</div>
|
||||
<div className="bg-purple-50 p-2 md:p-3 rounded-lg">
|
||||
<p className="text-xs md:text-sm font-medium text-purple-600">覆盖率</p>
|
||||
<p className="text-lg md:text-2xl font-bold text-purple-900">{keywordStats.coverage}%</p>
|
||||
<div className="bg-purple-50 p-3 rounded-lg text-center">
|
||||
<div className="text-lg md:text-2xl font-bold text-purple-600">
|
||||
{poolData.overview.newUsers.toLocaleString()}
|
||||
</div>
|
||||
<div className="text-xs text-purple-700">新增用户</div>
|
||||
</div>
|
||||
<div className="bg-orange-50 p-2 md:p-3 rounded-lg">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full h-full bg-transparent text-xs md:text-sm"
|
||||
onClick={() => router.push("/user-portrait/keywords")}
|
||||
>
|
||||
<Settings className="mr-1 md:mr-2 h-3 w-3 md:h-4 md:w-4" />
|
||||
管理
|
||||
</Button>
|
||||
<div className="bg-orange-50 p-3 rounded-lg text-center">
|
||||
<div className="text-lg md:text-2xl font-bold text-orange-600">
|
||||
{poolData.overview.privatePool.toLocaleString()}
|
||||
</div>
|
||||
<div className="text-xs text-orange-700">私域池</div>
|
||||
</div>
|
||||
<div className="bg-pink-50 p-3 rounded-lg text-center">
|
||||
<div className="text-lg md:text-2xl font-bold text-pink-600">
|
||||
{poolData.overview.publicPool.toLocaleString()}
|
||||
</div>
|
||||
<div className="text-xs text-pink-700">公域池</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{keywordStats.topKeywords.map((item, index) => (
|
||||
<div key={index} className="flex items-center justify-between p-2 md:p-3 bg-gray-50 rounded">
|
||||
<div className="flex items-center gap-2 min-w-0 flex-1">
|
||||
<span className="text-xs md:text-sm font-medium truncate">{item.keyword}</span>
|
||||
<Badge className={`text-xs ${getValueColor(item.value)}`}>{item.value}</Badge>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="bg-gray-50 p-4 rounded-lg">
|
||||
<h3 className="font-medium mb-3 flex items-center">
|
||||
<Smartphone className="h-4 w-4 mr-2 text-gray-600" />
|
||||
设备分布
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm">iOS设备</span>
|
||||
<span className="font-bold">{poolData.deviceStats.ios.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="text-right flex-shrink-0">
|
||||
<p className="text-xs md:text-sm font-bold">{item.count}</p>
|
||||
<p className="text-xs text-green-600">{item.trend}</p>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm">Android设备</span>
|
||||
<span className="font-bold">{poolData.deviceStats.android.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm">其他设备</span>
|
||||
<span className="font-bold">{poolData.deviceStats.other.toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-50 p-4 rounded-lg">
|
||||
<h3 className="font-medium mb-3 flex items-center">
|
||||
<MapPin className="h-4 w-4 mr-2 text-gray-600" />
|
||||
地域分布
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm">一线城市</span>
|
||||
<span className="font-bold">{poolData.locationStats.tier1.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm">二线城市</span>
|
||||
<span className="font-bold">{poolData.locationStats.tier2.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm">三线及以下</span>
|
||||
<span className="font-bold">{poolData.locationStats.tier3.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm">海外用户</span>
|
||||
<span className="font-bold">{poolData.locationStats.overseas.toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</CollapsibleContent>
|
||||
</Card>
|
||||
</Collapsible>
|
||||
|
||||
{/* 用户管理 (原用户池的用户管理,现在是用户画像的一部分) */}
|
||||
<Collapsible
|
||||
open={expandedSections.userManagement}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, userManagement: open }))}
|
||||
>
|
||||
<Card className="border-none shadow-md">
|
||||
<CollapsibleTrigger className="w-full">
|
||||
<CardHeader className="bg-gradient-to-r from-green-50 to-emerald-50 border-b hover:from-green-100 hover:to-emerald-100 transition-colors cursor-pointer py-3 md:py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<UserCheck className="h-4 w-4 md:h-5 md:w-5 text-green-600" />
|
||||
<div className="text-left">
|
||||
<CardTitle className="text-base md:text-lg">用户管理</CardTitle>
|
||||
<CardDescription className="text-xs md:text-sm">
|
||||
IMEI、手机号管理 ({filteredUsers.length})
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
{expandedSections.userManagement ? (
|
||||
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<CardContent className="p-4">
|
||||
{/* 搜索和筛选 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 md:gap-4 mb-4 md:mb-6">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
type="search"
|
||||
placeholder="搜索用户、IMEI、手机号、设备、位置..."
|
||||
className="pl-8 text-sm"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Select value={selectedPool} onValueChange={setSelectedPool}>
|
||||
<SelectTrigger className="text-sm">
|
||||
<SelectValue placeholder="选择流量池" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{pools.map((pool) => (
|
||||
<SelectItem key={pool} value={pool}>
|
||||
{pool === "all" ? "所有流量池" : pool}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select value={selectedDevice} onValueChange={setSelectedDevice}>
|
||||
<SelectTrigger className="text-sm">
|
||||
<SelectValue placeholder="选择设备品牌" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{devices.map((device) => (
|
||||
<SelectItem key={device} value={device}>
|
||||
{device === "all" ? "所有设备" : device}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 移动端卡片布局 */}
|
||||
<div className="space-y-3 md:hidden">
|
||||
{filteredUsers.map((user) => (
|
||||
<div key={user.id} className="bg-white border rounded-lg p-3 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Avatar className="h-8 w-8">
|
||||
<img src={user.avatar || "/placeholder.svg"} alt={user.name} />
|
||||
</Avatar>
|
||||
<div>
|
||||
<p className="font-medium text-sm">{user.name}</p>
|
||||
<p className="text-xs text-gray-500">{user.lastActive}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Badge className={getStatusColor(user.status)}>{user.status}</Badge>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2 text-xs">
|
||||
<div>
|
||||
<p className="text-gray-500">IMEI</p>
|
||||
<p className="font-mono">{user.imei.slice(-6)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-gray-500">手机号</p>
|
||||
<p className="font-medium">{user.phone}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-gray-500">设备</p>
|
||||
<p className="font-medium">{user.deviceBrand}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-gray-500">位置</p>
|
||||
<p className="font-medium">{user.location}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<div>
|
||||
<p className="text-gray-500">流量池</p>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{user.pool}
|
||||
</Badge>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-gray-500">平台</p>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{user.platform}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{user.tags.map((tag, index) => (
|
||||
<Badge key={index} variant="secondary" className="text-xs">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<Button variant="ghost" size="sm" className="text-xs">
|
||||
<MessageCircle className="h-3 w-3 mr-1" />
|
||||
联系
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-xs bg-transparent"
|
||||
onClick={() => handleUserClick(user.id)}
|
||||
>
|
||||
<Eye className="h-3 w-3 mr-1" />
|
||||
查看详情
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 桌面端表格布局 */}
|
||||
<div className="hidden md:block overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="bg-gray-50">
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">用户</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">IMEI/手机号</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">设备信息</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">位置</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">流量池/平台</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">状态</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">标签</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y">
|
||||
{filteredUsers.map((user) => (
|
||||
<tr key={user.id} className="hover:bg-gray-50">
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<div className="flex items-center gap-3">
|
||||
<Avatar>
|
||||
<img src={user.avatar || "/placeholder.svg"} alt={user.name} />
|
||||
</Avatar>
|
||||
<div>
|
||||
<p className="font-medium">{user.name}</p>
|
||||
<p className="text-sm text-gray-500">{user.lastActive}</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<p className="font-mono text-sm">{user.imei}</p>
|
||||
<p className="font-medium">{user.phone}</p>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<div>
|
||||
<p className="font-medium">{user.deviceBrand}</p>
|
||||
<p className="text-sm text-gray-500">{user.deviceModel}</p>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<p className="text-sm">{user.location}</p>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<div className="space-y-1">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{user.pool}
|
||||
</Badge>
|
||||
<p className="text-xs text-gray-500">{user.platform}</p>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<Badge className={getStatusColor(user.status)}>{user.status}</Badge>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<div className="flex flex-wrap gap-1 max-w-xs">
|
||||
{user.tags.slice(0, 3).map((tag, index) => (
|
||||
<Badge key={index} variant="outline" className="text-xs">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
{user.tags.length > 3 && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
+{user.tags.length - 3}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<div className="flex gap-2">
|
||||
<Button variant="ghost" size="sm" onClick={() => handleUserClick(user.id)}>
|
||||
<Eye className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" onClick={() => handleWechatClick(user.id)}>
|
||||
<MessageCircle className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</CollapsibleContent>
|
||||
</Card>
|
||||
</Collapsible>
|
||||
|
||||
{/* 用户分群 (从原 user-pool/page.tsx 整合) */}
|
||||
<Collapsible
|
||||
open={expandedSections.userSegmentation}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, userSegmentation: open }))}
|
||||
>
|
||||
<Card className="border-none shadow-md">
|
||||
<CollapsibleTrigger className="w-full">
|
||||
<CardHeader className="bg-gradient-to-r from-purple-50 to-pink-50 border-b hover:from-purple-100 hover:to-pink-100 transition-colors cursor-pointer py-3 md:py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<Layers className="h-4 w-4 md:h-5 md:w-5 text-purple-600" />
|
||||
<div className="text-left">
|
||||
<CardTitle className="text-base md:text-lg">用户分群</CardTitle>
|
||||
<CardDescription className="text-xs md:text-sm">智能用户分群管理</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
{expandedSections.userSegmentation ? (
|
||||
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<CardContent className="p-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{userSegments.map((segment, index) => (
|
||||
<div key={index} className={`${segment.color} p-4 rounded-lg`}>
|
||||
<h3 className="font-medium mb-2">{segment.name}</h3>
|
||||
<p className="text-2xl font-bold text-current">{segment.count.toLocaleString()}</p>
|
||||
<p className="text-sm text-gray-600 mt-1">{segment.description}</p>
|
||||
<div className="mt-3">
|
||||
<Button size="sm" variant="outline" className="w-full bg-transparent">
|
||||
查看详情
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -339,7 +703,7 @@ export default function UserPortrait() {
|
||||
</Card>
|
||||
</Collapsible>
|
||||
|
||||
{/* 标签管理 */}
|
||||
{/* 标签管理 (原用户画像的标签管理,现在是用户画像的一部分) */}
|
||||
<Collapsible
|
||||
open={expandedSections.tagManagement}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, tagManagement: open }))}
|
||||
@@ -403,223 +767,6 @@ export default function UserPortrait() {
|
||||
</Card>
|
||||
</Collapsible>
|
||||
|
||||
{/* 用户列表 */}
|
||||
<Collapsible
|
||||
open={expandedSections.userList}
|
||||
onOpenChange={(open) => setExpandedSections((prev) => ({ ...prev, userList: open }))}
|
||||
>
|
||||
<Card className="border shadow-sm">
|
||||
<CollapsibleTrigger className="w-full">
|
||||
<CardHeader className="hover:bg-gray-50 transition-colors cursor-pointer py-3 md:py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Users className="h-4 w-4 md:h-5 md:w-5 text-green-600" />
|
||||
<div className="text-left">
|
||||
<CardTitle className="text-base md:text-lg">用户列表</CardTitle>
|
||||
<CardDescription className="text-xs md:text-sm">
|
||||
用户数据管理 ({filteredUsers.length})
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
{expandedSections.userList ? (
|
||||
<ChevronDown className="h-4 w-4 md:h-5 md:w-5" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 md:h-5 md:w-5" />
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<CardContent className="pt-0 px-4 md:px-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 md:gap-4 mb-4 md:mb-6">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
type="search"
|
||||
placeholder="搜索用户、公司或标签..."
|
||||
className="pl-8 text-sm"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Select value={selectedCategory} onValueChange={setSelectedCategory}>
|
||||
<SelectTrigger className="text-sm">
|
||||
<SelectValue placeholder="选择场景分类" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{categories.map((category) => (
|
||||
<SelectItem key={category} value={category}>
|
||||
{category === "all" ? "所有场景" : category}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select value={selectedPool} onValueChange={setSelectedPool}>
|
||||
<SelectTrigger className="text-sm">
|
||||
<SelectValue placeholder="选择流量池" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{pools.map((pool) => (
|
||||
<SelectItem key={pool} value={pool}>
|
||||
{pool === "all" ? "所有流量池" : pool}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3 md:hidden">
|
||||
{/* 移动端卡片布局 */}
|
||||
{filteredUsers.map((user) => (
|
||||
<div key={user.id} className="bg-white border rounded-lg p-3 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Avatar className="h-8 w-8">
|
||||
<img src={user.avatar || "/placeholder.svg"} alt={user.name} />
|
||||
</Avatar>
|
||||
<div>
|
||||
<p className="font-medium text-sm">{user.name}</p>
|
||||
<p className="text-xs text-gray-500">{user.company}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Badge
|
||||
className={
|
||||
user.rfmScore >= 90
|
||||
? "bg-green-100 text-green-800"
|
||||
: user.rfmScore >= 80
|
||||
? "bg-blue-100 text-blue-800"
|
||||
: user.rfmScore >= 70
|
||||
? "bg-yellow-100 text-yellow-800"
|
||||
: "bg-gray-100 text-gray-800"
|
||||
}
|
||||
>
|
||||
{user.rfmScore}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span className="text-gray-500">{user.lastActive}</span>
|
||||
<span className="font-medium">¥{user.value.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{user.tags.slice(0, 3).map((tag, index) => (
|
||||
<Badge key={index} variant="outline" className="text-xs">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
{user.tags.length > 3 && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
+{user.tags.length - 3}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<Button variant="ghost" size="sm" onClick={() => handleWechatClick(user.id)} className="text-xs">
|
||||
<MessageCircle className="h-3 w-3 mr-1" />
|
||||
{user.wechatAccounts.length}个账号
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onClick={() => handleUserClick(user.id)} className="text-xs">
|
||||
<Eye className="h-3 w-3 mr-1" />
|
||||
查看
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 桌面端表格布局 */}
|
||||
<div className="hidden md:block overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="bg-gray-50">
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">用户</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">公司</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">价值</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">评分</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">微信账号</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">标签</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y">
|
||||
{filteredUsers.map((user) => (
|
||||
<tr key={user.id} className="hover:bg-gray-50">
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<div className="flex items-center gap-3">
|
||||
<Avatar>
|
||||
<img src={user.avatar || "/placeholder.svg"} alt={user.name} />
|
||||
</Avatar>
|
||||
<div>
|
||||
<p className="font-medium">{user.name}</p>
|
||||
<p className="text-sm text-gray-500">{user.lastActive}</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<p className="font-medium">{user.company}</p>
|
||||
<p className="text-sm text-gray-500">{user.category}</p>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<p className="font-medium">¥{user.value.toLocaleString()}</p>
|
||||
<p className="text-sm text-gray-500">{user.pool}</p>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<Badge
|
||||
className={
|
||||
user.rfmScore >= 90
|
||||
? "bg-green-100 text-green-800"
|
||||
: user.rfmScore >= 80
|
||||
? "bg-blue-100 text-blue-800"
|
||||
: user.rfmScore >= 70
|
||||
? "bg-yellow-100 text-yellow-800"
|
||||
: "bg-gray-100 text-gray-800"
|
||||
}
|
||||
>
|
||||
{user.rfmScore}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleWechatClick(user.id)}
|
||||
className="flex items-center"
|
||||
>
|
||||
<MessageCircle className="h-4 w-4 mr-1" />
|
||||
{user.wechatAccounts.length}个
|
||||
</Button>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<div className="flex flex-wrap gap-1 max-w-xs">
|
||||
{user.tags.slice(0, 3).map((tag, index) => (
|
||||
<Badge key={index} variant="outline" className="text-xs">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
{user.tags.length > 3 && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
+{user.tags.length - 3}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<Button variant="ghost" size="sm" onClick={() => handleUserClick(user.id)}>
|
||||
<Eye className="h-4 w-4 mr-1" />
|
||||
查看
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</CollapsibleContent>
|
||||
</Card>
|
||||
</Collapsible>
|
||||
|
||||
{/* 微信账号对话框 */}
|
||||
<Dialog
|
||||
open={selectedWechatDialog.isOpen}
|
||||
|
||||
@@ -26,9 +26,19 @@ import {
|
||||
Copy,
|
||||
Check,
|
||||
Zap,
|
||||
Plus,
|
||||
Download,
|
||||
} from "lucide-react"
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog"
|
||||
import { useToast } from "@/components/ui/use-toast"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
|
||||
interface AIAnalysisTask {
|
||||
id: string
|
||||
@@ -297,8 +307,8 @@ export function AIAnalysisTools() {
|
||||
description: "为特定用户分群生成营销策略",
|
||||
type: "strategy-generation",
|
||||
parameters: {
|
||||
targetSegment: "所有用户",
|
||||
campaignGoal: "提高转化率",
|
||||
targetSegment: "流失风险用户",
|
||||
campaignGoal: "提高留存率",
|
||||
budgetConstraint: "中等",
|
||||
channelPreference: ["短信", "应用内推送", "邮件"],
|
||||
},
|
||||
@@ -664,9 +674,7 @@ export function AIAnalysisTools() {
|
||||
<Plus className="h-6 w-6 text-muted-foreground" />
|
||||
</div>
|
||||
<h3 className="text-lg font-medium mb-2">创建新模板</h3>
|
||||
<p className="text-sm text-muted-foreground text-center mb-4">
|
||||
创建自定义分析模板,保存常用的分析配置
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground text-center mb-4">创建自定义分析模板,保存常用的分析配置</p>
|
||||
<Button variant="outline">创建模板</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -719,9 +727,7 @@ export function AIAnalysisTools() {
|
||||
<SelectItem value="10">10</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
设置同时执行的AI分析任务数量,数值越大系统负载越高
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">设置同时执行的AI分析任务数量,数值越大系统负载越高</p>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
@@ -920,4 +926,231 @@ export function AIAnalysisTools() {
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{selectedTask.result.clusters.map((cluster: any, index: number) => (
|
||||
<TableRow key={
|
||||
<TableRow key={index}>
|
||||
<TableCell className="font-medium">{cluster.name}</TableCell>
|
||||
<TableCell>{cluster.size.toLocaleString()}</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{cluster.characteristics.map((char: string, charIndex: number) => (
|
||||
<Badge key={charIndex} variant="outline">
|
||||
{char}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedTask.result.tagCategories && (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-sm font-medium">生成的标签分类</h4>
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>分类名称</TableHead>
|
||||
<TableHead>标签数量</TableHead>
|
||||
<TableHead>覆盖率</TableHead>
|
||||
<TableHead>示例标签</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{selectedTask.result.tagCategories.map((category: any, index: number) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell className="font-medium">{category.name}</TableCell>
|
||||
<TableCell>{category.count}</TableCell>
|
||||
<TableCell>{category.coverage}</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{category.examples.map((example: string, exampleIndex: number) => (
|
||||
<Badge key={exampleIndex} variant="outline">
|
||||
{example}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setSelectedTaskId(null)}>
|
||||
关闭
|
||||
</Button>
|
||||
{selectedTask?.status === "completed" && (
|
||||
<Button>
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
下载报告
|
||||
</Button>
|
||||
)}
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* 添加模型对话框 */}
|
||||
<Dialog open={isAddingModel} onOpenChange={setIsAddingModel}>
|
||||
<DialogContent className="sm:max-w-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>添加AI模型</DialogTitle>
|
||||
<DialogDescription>配置新的AI模型连接</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="model-name" className="text-right">
|
||||
模型名称
|
||||
</Label>
|
||||
<Input id="model-name" className="col-span-3" placeholder="例如:GPT-4" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="model-provider" className="text-right">
|
||||
提供商
|
||||
</Label>
|
||||
<Input id="model-provider" className="col-span-3" placeholder="例如:OpenAI" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="model-type" className="text-right">
|
||||
模型类型
|
||||
</Label>
|
||||
<Select>
|
||||
<SelectTrigger id="model-type" className="col-span-3">
|
||||
<SelectValue placeholder="选择模型类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="llm">大语言模型</SelectItem>
|
||||
<SelectItem value="image-gen">图像生成模型</SelectItem>
|
||||
<SelectItem value="custom">专用分析模型</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="api-endpoint" className="text-right">
|
||||
API端点
|
||||
</Label>
|
||||
<Input id="api-endpoint" className="col-span-3" placeholder="例如:https://api.openai.com/v1/chat" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="api-key" className="text-right">
|
||||
API密钥
|
||||
</Label>
|
||||
<Input id="api-key" type="password" className="col-span-3" placeholder="输入API密钥" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-start gap-4">
|
||||
<Label htmlFor="capabilities" className="text-right pt-2">
|
||||
能力
|
||||
</Label>
|
||||
<Textarea id="capabilities" className="col-span-3" placeholder="例如:文本生成, 内容分析" rows={3} />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<div className="text-right">
|
||||
<Label htmlFor="model-status">启用模型</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 col-span-3">
|
||||
<Switch id="model-status" defaultChecked />
|
||||
<Label htmlFor="model-status">立即启用此模型</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsAddingModel(false)}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={() => setIsAddingModel(false)}>添加模型</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* 创建分析任务对话框 */}
|
||||
<Dialog open={isCreatingTask} onOpenChange={setIsCreatingTask}>
|
||||
<DialogContent className="sm:max-w-[600px]">
|
||||
<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="task-name" className="text-right">
|
||||
任务名称
|
||||
</Label>
|
||||
<Input id="task-name" className="col-span-3" placeholder="例如:高价值用户流失预测" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="task-type" className="text-right">
|
||||
分析类型
|
||||
</Label>
|
||||
<Select>
|
||||
<SelectTrigger id="task-type" className="col-span-3">
|
||||
<SelectValue placeholder="选择分析类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="behavior-analysis">用户行为分析</SelectItem>
|
||||
<SelectItem value="segment-discovery">用户分群自动发现</SelectItem>
|
||||
<SelectItem value="churn-prediction">用户流失预测</SelectItem>
|
||||
<SelectItem value="strategy-generation">营销策略生成</SelectItem>
|
||||
<SelectItem value="tag-generation">自动标签生成</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="task-datasource" className="text-right">
|
||||
数据源
|
||||
</Label>
|
||||
<Select>
|
||||
<SelectTrigger id="task-datasource" className="col-span-3">
|
||||
<SelectValue placeholder="选择数据源" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">所有数据源</SelectItem>
|
||||
<SelectItem value="user-db">用户数据库</SelectItem>
|
||||
<SelectItem value="transaction">交易系统</SelectItem>
|
||||
<SelectItem value="behavior">行为分析平台</SelectItem>
|
||||
<SelectItem value="user-value">用户价值模型</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-start gap-4">
|
||||
<Label htmlFor="task-parameters" className="text-right pt-2">
|
||||
分析参数
|
||||
</Label>
|
||||
<Textarea
|
||||
id="task-parameters"
|
||||
className="col-span-3"
|
||||
placeholder="输入JSON格式的分析参数,例如:{ 'userSegment': '高价值用户', 'timeRange': '近30天' }"
|
||||
rows={5}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<div className="text-right">
|
||||
<Label htmlFor="schedule-task">定期运行</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 col-span-3">
|
||||
<Switch id="schedule-task" />
|
||||
<Label htmlFor="schedule-task">定期执行此任务</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsCreatingTask(false)}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={() => setIsCreatingTask(false)}>
|
||||
<Play className="mr-2 h-4 w-4" />
|
||||
创建任务
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { useState } from "react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Plus, Trash2 } from "lucide-react"
|
||||
import { Trash2, Plus, Save } from "lucide-react"
|
||||
|
||||
interface RuleCondition {
|
||||
field: string
|
||||
operator: string
|
||||
value: string
|
||||
logicalOperator?: "AND" | "OR"
|
||||
}
|
||||
|
||||
interface TagRule {
|
||||
@@ -24,151 +24,263 @@ interface TagRule {
|
||||
addTags: string[]
|
||||
removeTags: string[]
|
||||
}
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 简易标签规则编辑器
|
||||
* 只实现基本字段编辑与本地状态保存,后端持久化可在后续迭代中接入
|
||||
*/
|
||||
export function RuleEditor({ ruleId }: { ruleId: string | null }) {
|
||||
const [rule, setRule] = useState<TagRule | null>(null)
|
||||
interface RuleEditorProps {
|
||||
rule?: TagRule
|
||||
onSave: (rule: TagRule) => void
|
||||
onCancel: () => void
|
||||
}
|
||||
|
||||
// ❗️此处仅示例:真实项目应替换为 API 请求
|
||||
useEffect(() => {
|
||||
if (!ruleId) {
|
||||
setRule(null)
|
||||
return
|
||||
}
|
||||
// 模拟异步加载
|
||||
setTimeout(() => {
|
||||
setRule({
|
||||
id: ruleId,
|
||||
name: "示例规则",
|
||||
description: "这是一个示例规则,用于演示编辑器",
|
||||
conditions: [
|
||||
{ field: "orderAmount", operator: ">", value: "500", logicalOperator: "AND" },
|
||||
{ field: "loginDays", operator: "<", value: "30" },
|
||||
],
|
||||
actions: { addTags: ["高价值用户"], removeTags: [] },
|
||||
export function RuleEditor({ rule, onSave, onCancel }: RuleEditorProps) {
|
||||
const [formData, setFormData] = useState<TagRule>(
|
||||
rule || {
|
||||
id: "",
|
||||
name: "",
|
||||
description: "",
|
||||
conditions: [{ field: "", operator: "", value: "" }],
|
||||
actions: {
|
||||
addTags: [],
|
||||
removeTags: [],
|
||||
},
|
||||
enabled: true,
|
||||
},
|
||||
)
|
||||
|
||||
const [newAddTag, setNewAddTag] = useState("")
|
||||
const [newRemoveTag, setNewRemoveTag] = useState("")
|
||||
|
||||
const fieldOptions = [
|
||||
{ value: "imei", label: "IMEI" },
|
||||
{ value: "phone", label: "手机号" },
|
||||
{ value: "device_brand", label: "设备品牌" },
|
||||
{ value: "device_model", label: "设备型号" },
|
||||
{ value: "os_version", label: "系统版本" },
|
||||
{ value: "app_version", label: "应用版本" },
|
||||
{ value: "location", label: "地理位置" },
|
||||
{ value: "user_behavior", label: "用户行为" },
|
||||
{ value: "consumption_amount", label: "消费金额" },
|
||||
{ value: "activity_frequency", label: "活跃频率" },
|
||||
]
|
||||
|
||||
const operatorOptions = [
|
||||
{ value: "equals", label: "等于" },
|
||||
{ value: "not_equals", label: "不等于" },
|
||||
{ value: "contains", label: "包含" },
|
||||
{ value: "not_contains", label: "不包含" },
|
||||
{ value: "starts_with", label: "开始于" },
|
||||
{ value: "ends_with", label: "结束于" },
|
||||
{ value: "greater_than", label: "大于" },
|
||||
{ value: "less_than", label: "小于" },
|
||||
{ value: "in_range", label: "在范围内" },
|
||||
{ value: "regex", label: "正则匹配" },
|
||||
]
|
||||
|
||||
const addCondition = () => {
|
||||
setFormData({
|
||||
...formData,
|
||||
conditions: [...formData.conditions, { field: "", operator: "", value: "" }],
|
||||
})
|
||||
}
|
||||
|
||||
const removeCondition = (index: number) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
conditions: formData.conditions.filter((_, i) => i !== index),
|
||||
})
|
||||
}
|
||||
|
||||
const updateCondition = (index: number, field: keyof RuleCondition, value: string) => {
|
||||
const newConditions = [...formData.conditions]
|
||||
newConditions[index] = { ...newConditions[index], [field]: value }
|
||||
setFormData({ ...formData, conditions: newConditions })
|
||||
}
|
||||
|
||||
const addTag = (type: "addTags" | "removeTags") => {
|
||||
const tagValue = type === "addTags" ? newAddTag : newRemoveTag
|
||||
if (tagValue.trim()) {
|
||||
setFormData({
|
||||
...formData,
|
||||
actions: {
|
||||
...formData.actions,
|
||||
[type]: [...formData.actions[type], tagValue.trim()],
|
||||
},
|
||||
})
|
||||
}, 300)
|
||||
}, [ruleId])
|
||||
|
||||
if (!ruleId) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>请选择要编辑的规则</CardTitle>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
)
|
||||
if (type === "addTags") {
|
||||
setNewAddTag("")
|
||||
} else {
|
||||
setNewRemoveTag("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!rule) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>加载中...</CardTitle>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
)
|
||||
const removeTag = (type: "addTags" | "removeTags", index: number) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
actions: {
|
||||
...formData.actions,
|
||||
[type]: formData.actions[type].filter((_, i) => i !== index),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// === 内部更新函数 ===
|
||||
const updateRule = (partial: Partial<TagRule>) => setRule((prev) => (prev ? { ...prev, ...partial } : prev))
|
||||
|
||||
const addCondition = () =>
|
||||
updateRule({
|
||||
conditions: [...rule.conditions, { field: "", operator: "=", value: "" }],
|
||||
})
|
||||
|
||||
const removeCondition = (index: number) =>
|
||||
updateRule({
|
||||
conditions: rule.conditions.filter((_, i) => i !== index),
|
||||
})
|
||||
|
||||
const saveRule = () => {
|
||||
// TODO: 调用后端保存
|
||||
console.log("保存规则:", rule)
|
||||
const handleSave = () => {
|
||||
if (formData.name && formData.conditions.length > 0) {
|
||||
onSave(formData)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>编辑规则:{rule.name}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
{/* 基本信息 */}
|
||||
<div className="space-y-2">
|
||||
<label className="block text-sm font-medium">规则名称</label>
|
||||
<Input value={rule.name} onChange={(e) => updateRule({ name: e.target.value })} />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="block text-sm font-medium">规则描述</label>
|
||||
<Textarea value={rule.description} onChange={(e) => updateRule({ description: e.target.value })} />
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>规则基本信息</CardTitle>
|
||||
<CardDescription>设置标签规则的基本信息</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="rule-name">规则名称</Label>
|
||||
<Input
|
||||
id="rule-name"
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
placeholder="输入规则名称"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="rule-description">规则描述</Label>
|
||||
<Input
|
||||
id="rule-description"
|
||||
value={formData.description}
|
||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||
placeholder="输入规则描述"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>触发条件</CardTitle>
|
||||
<CardDescription>设置标签规则的触发条件</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{formData.conditions.map((condition, index) => (
|
||||
<div key={index} className="flex items-center space-x-2 p-3 border rounded-lg">
|
||||
<Select value={condition.field} onValueChange={(value) => updateCondition(index, "field", value)}>
|
||||
<SelectTrigger className="w-[200px]">
|
||||
<SelectValue placeholder="选择字段" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{fieldOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select value={condition.operator} onValueChange={(value) => updateCondition(index, "operator", value)}>
|
||||
<SelectTrigger className="w-[150px]">
|
||||
<SelectValue placeholder="选择操作符" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{operatorOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
{/* 条件编辑 */}
|
||||
<div className="space-y-2">
|
||||
<label className="block text-sm font-medium">条件</label>
|
||||
{rule.conditions.map((c, idx) => (
|
||||
<div key={idx} className="flex items-center gap-2">
|
||||
<Input
|
||||
placeholder="字段"
|
||||
value={c.field}
|
||||
onChange={(e) => {
|
||||
const next = [...rule.conditions]
|
||||
next[idx].field = e.target.value
|
||||
updateRule({ conditions: next })
|
||||
}}
|
||||
className="w-1/3"
|
||||
value={condition.value}
|
||||
onChange={(e) => updateCondition(index, "value", e.target.value)}
|
||||
placeholder="输入值"
|
||||
className="flex-1"
|
||||
/>
|
||||
<Input
|
||||
placeholder="运算符"
|
||||
value={c.operator}
|
||||
onChange={(e) => {
|
||||
const next = [...rule.conditions]
|
||||
next[idx].operator = e.target.value
|
||||
updateRule({ conditions: next })
|
||||
}}
|
||||
className="w-1/6"
|
||||
/>
|
||||
<Input
|
||||
placeholder="值"
|
||||
value={c.value}
|
||||
onChange={(e) => {
|
||||
const next = [...rule.conditions]
|
||||
next[idx].value = e.target.value
|
||||
updateRule({ conditions: next })
|
||||
}}
|
||||
className="w-1/3"
|
||||
/>
|
||||
<Button variant="ghost" size="icon" onClick={() => removeCondition(idx)} aria-label="删除条件">
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => removeCondition(index)}
|
||||
disabled={formData.conditions.length === 1}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<Button variant="outline" onClick={addCondition}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
添加条件
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 动作(标签) */}
|
||||
<div className="space-y-2">
|
||||
<label className="block text-sm font-medium">新增标签</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{rule.actions.addTags.map((tag, i) => (
|
||||
<Badge key={i}>{tag}</Badge>
|
||||
))}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>执行动作</CardTitle>
|
||||
<CardDescription>设置满足条件时的标签操作</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div>
|
||||
<Label>添加标签</Label>
|
||||
<div className="flex space-x-2 mt-2">
|
||||
<Input
|
||||
value={newAddTag}
|
||||
onChange={(e) => setNewAddTag(e.target.value)}
|
||||
placeholder="输入要添加的标签"
|
||||
onKeyPress={(e) => e.key === "Enter" && addTag("addTags")}
|
||||
/>
|
||||
<Button onClick={() => addTag("addTags")}>添加</Button>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{formData.actions.addTags.map((tag, index) => (
|
||||
<Badge key={index} variant="secondary" className="flex items-center gap-1">
|
||||
{tag}
|
||||
<button onClick={() => removeTag("addTags", index)}>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</button>
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 保存 */}
|
||||
<div className="text-right">
|
||||
<Button onClick={saveRule}>保存规则</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div>
|
||||
<Label>移除标签</Label>
|
||||
<div className="flex space-x-2 mt-2">
|
||||
<Input
|
||||
value={newRemoveTag}
|
||||
onChange={(e) => setNewRemoveTag(e.target.value)}
|
||||
placeholder="输入要移除的标签"
|
||||
onKeyPress={(e) => e.key === "Enter" && addTag("removeTags")}
|
||||
/>
|
||||
<Button onClick={() => addTag("removeTags")}>添加</Button>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{formData.actions.removeTags.map((tag, index) => (
|
||||
<Badge key={index} variant="destructive" className="flex items-center gap-1">
|
||||
{tag}
|
||||
<button onClick={() => removeTag("removeTags", index)}>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</button>
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="flex justify-end space-x-2">
|
||||
<Button variant="outline" onClick={onCancel}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={handleSave}>
|
||||
<Save className="h-4 w-4 mr-2" />
|
||||
保存规则
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
285
lib/mongodb-mock-connector.ts
Normal file
@@ -0,0 +1,285 @@
|
||||
// lib/mongodb-mock-connector.ts
|
||||
// 这是一个模拟 MongoDB 数据库连接器,用于在前端环境中模拟后端 MongoDB 的数据结构和操作。
|
||||
// 在实际的 Next.js 应用中,您会使用 'mongodb' 或 'mongoose' 等库在服务器端建立真实的连接。
|
||||
|
||||
// 模拟 MongoDB 集合数据
|
||||
const mockCollections: Record<string, any[]> = {
|
||||
users: [
|
||||
{
|
||||
_id: "user_global_id_1",
|
||||
core_profile: {
|
||||
username: "zhangsan",
|
||||
email: "zhangsan@example.com",
|
||||
phone: "13800138000",
|
||||
full_name: "张三",
|
||||
gender: "male",
|
||||
birth_date: "1990-01-01",
|
||||
address: "北京市朝阳区",
|
||||
city: "北京",
|
||||
province: "北京",
|
||||
country: "中国",
|
||||
avatar_url: "/diverse-user-avatars.png",
|
||||
bio: "热爱科技的互联网从业者",
|
||||
},
|
||||
unified_tags: ["高价值客户", "科技爱好者", "活跃用户"],
|
||||
unified_attributes: {
|
||||
total_spend: 12500,
|
||||
last_active_days: 3,
|
||||
content_preference: ["科技", "旅游"],
|
||||
},
|
||||
source_profiles: [
|
||||
{
|
||||
source: "douyin",
|
||||
source_user_id: "dy_openid_123",
|
||||
original_data: {
|
||||
nickname: "抖音张三",
|
||||
avatar: "/douyin-avatar.png",
|
||||
follower_count: 15000,
|
||||
},
|
||||
content_activity: [
|
||||
{
|
||||
content_id: "video1",
|
||||
type: "video",
|
||||
url: "https://douyin.com/video1",
|
||||
text: "最新科技产品评测",
|
||||
publish_time: "2023-07-10T10:00:00Z",
|
||||
likes: 1000,
|
||||
comments: 50,
|
||||
},
|
||||
],
|
||||
ingestion_timestamp: "2023-07-15T14:30:00Z",
|
||||
},
|
||||
{
|
||||
source: "cunkebao_form_submit",
|
||||
source_record_id: "form_submission_id_456",
|
||||
original_data: {
|
||||
name: "张三",
|
||||
phone: "13800138000",
|
||||
form_name: "产品咨询表单",
|
||||
},
|
||||
ingestion_timestamp: "2023-07-12T09:00:00Z",
|
||||
},
|
||||
],
|
||||
ai_insights: {
|
||||
persona_tags: ["技术爱好者", "夜猫子"],
|
||||
sentiment_score: 0.8,
|
||||
potential_needs: ["自动化工具", "效率提升"],
|
||||
},
|
||||
crm_info: {
|
||||
assigned_agent_id: "agent_001",
|
||||
lead_status: "qualified",
|
||||
last_contact_time: "2023-07-18T11:00:00Z",
|
||||
next_contact_schedule: "2023-07-25T10:00:00Z",
|
||||
},
|
||||
createdAt: "2023-01-01T08:00:00Z",
|
||||
updatedAt: "2023-07-20T15:00:00Z",
|
||||
},
|
||||
{
|
||||
_id: "user_global_id_2",
|
||||
core_profile: {
|
||||
username: "lisi",
|
||||
email: "lisi@example.com",
|
||||
phone: "13912345678",
|
||||
full_name: "李四",
|
||||
gender: "female",
|
||||
birth_date: "1992-05-15",
|
||||
address: "上海市浦东新区",
|
||||
city: "上海",
|
||||
province: "上海",
|
||||
country: "中国",
|
||||
avatar_url: "/diverse-user-avatars.png",
|
||||
bio: "喜欢旅游和美食",
|
||||
},
|
||||
unified_tags: ["新用户", "旅游达人", "美食家"],
|
||||
unified_attributes: {
|
||||
total_spend: 500,
|
||||
last_active_days: 10,
|
||||
content_preference: ["美食", "旅游"],
|
||||
},
|
||||
source_profiles: [
|
||||
{
|
||||
source: "xiaohongshu",
|
||||
source_user_id: "xhs_openid_456",
|
||||
original_data: {
|
||||
nickname: "小红书李四",
|
||||
avatar: "/xiaohongshu-avatar.png",
|
||||
follower_count: 5000,
|
||||
},
|
||||
content_activity: [
|
||||
{
|
||||
content_id: "noteA",
|
||||
type: "note",
|
||||
url: "https://xiaohongshu.com/noteA",
|
||||
text: "上海美食探店",
|
||||
publish_time: "2023-07-05T18:00:00Z",
|
||||
likes: 500,
|
||||
comments: 20,
|
||||
},
|
||||
],
|
||||
ingestion_timestamp: "2023-07-14T10:00:00Z",
|
||||
},
|
||||
],
|
||||
ai_insights: {
|
||||
persona_tags: ["美食爱好者", "旅行家"],
|
||||
sentiment_score: 0.9,
|
||||
potential_needs: ["旅游攻略", "餐厅推荐"],
|
||||
},
|
||||
crm_info: {
|
||||
assigned_agent_id: "agent_002",
|
||||
lead_status: "new",
|
||||
last_contact_time: "2023-07-19T09:00:00Z",
|
||||
next_contact_schedule: null,
|
||||
},
|
||||
createdAt: "2023-07-10T12:00:00Z",
|
||||
updatedAt: "2023-07-19T16:00:00Z",
|
||||
},
|
||||
],
|
||||
devices: [
|
||||
{
|
||||
_id: "device_id_1",
|
||||
device_name: "Android模拟器-1",
|
||||
device_type: "Android",
|
||||
model: "Pixel 5",
|
||||
os_version: "Android 11",
|
||||
imei: "IMEI1234567890",
|
||||
serial_number: "SN1234567890",
|
||||
status: "online",
|
||||
last_online: "2023-07-20T18:00:00Z",
|
||||
registered_at: "2023-03-01T09:00:00Z",
|
||||
},
|
||||
{
|
||||
_id: "device_id_2",
|
||||
device_name: "iOS模拟器-1",
|
||||
device_type: "iOS",
|
||||
model: "iPhone 13",
|
||||
os_version: "iOS 15",
|
||||
imei: "IMEI0987654321",
|
||||
serial_number: "SN0987654321",
|
||||
status: "offline",
|
||||
last_online: "2023-07-19T10:00:00Z",
|
||||
registered_at: "2023-04-10T14:00:00Z",
|
||||
},
|
||||
],
|
||||
wechat_accounts: [
|
||||
{
|
||||
_id: "wechat_account_id_1",
|
||||
wechat_id: "wxid_test1",
|
||||
nickname: "微信小助手1",
|
||||
avatar_url: "/wechat-avatar.png",
|
||||
qr_code_url: "/placeholder-8b0n0.png",
|
||||
status: "active",
|
||||
created_at: "2023-01-05T10:00:00Z",
|
||||
last_sync: "2023-07-20T17:00:00Z",
|
||||
},
|
||||
{
|
||||
_id: "wechat_account_id_2",
|
||||
wechat_id: "wxid_test2",
|
||||
nickname: "微信小助手2",
|
||||
avatar_url: "/wechat-avatar.png",
|
||||
qr_code_url: "/placeholder-8b0n0.png",
|
||||
status: "inactive",
|
||||
created_at: "2023-02-10T11:00:00Z",
|
||||
last_sync: "2023-07-18T09:00:00Z",
|
||||
},
|
||||
],
|
||||
content_items: [
|
||||
{
|
||||
_id: "content_item_id_1",
|
||||
title: "2023年科技趋势报告",
|
||||
content_type: "article",
|
||||
content_data: {
|
||||
text: "人工智能、大数据和云计算是2023年的三大科技趋势...",
|
||||
images: ["/tech-report-abstract.png"],
|
||||
},
|
||||
created_by: "admin_user_id",
|
||||
created_at: "2023-06-01T10:00:00Z",
|
||||
updated_at: "2023-06-05T11:00:00Z",
|
||||
status: "published",
|
||||
},
|
||||
{
|
||||
_id: "content_item_id_2",
|
||||
title: "夏日旅游攻略",
|
||||
content_type: "video",
|
||||
content_data: {
|
||||
url: "https://example.com/travel-video.mp4",
|
||||
thumbnail: "/travel-video-thumbnail.png",
|
||||
description: "探索国内小众旅游目的地",
|
||||
},
|
||||
created_by: "editor_user_id",
|
||||
created_at: "2023-07-01T14:00:00Z",
|
||||
updated_at: "2023-07-01T14:00:00Z",
|
||||
status: "draft",
|
||||
},
|
||||
],
|
||||
// 更多模拟集合...
|
||||
}
|
||||
|
||||
// 模拟数据库名称,对应 MongoDB 的数据库概念
|
||||
const mockDatabases = [
|
||||
{ Database: "cunkebao_user_platform" },
|
||||
{ Database: "cunkebao_traffic_analysis" },
|
||||
{ Database: "cunkebao_content_library" },
|
||||
{ Database: "cunkebao_wechat_management" },
|
||||
{ Database: "cunkebao_device_management" },
|
||||
]
|
||||
|
||||
// 模拟获取所有数据库(在这里是集合的逻辑分组)
|
||||
export async function getDatabases() {
|
||||
return mockDatabases
|
||||
}
|
||||
|
||||
// 模拟获取指定“数据库”中的所有“表”(在这里是集合)
|
||||
export async function getTables(database: string) {
|
||||
// 根据模拟的数据库名称返回对应的集合列表
|
||||
if (database === "cunkebao_user_platform") {
|
||||
return [{ Tables_in_database: "users" }]
|
||||
} else if (database === "cunkebao_device_management") {
|
||||
return [{ Tables_in_database: "devices" }]
|
||||
} else if (database === "cunkebao_wechat_management") {
|
||||
return [{ Tables_in_database: "wechat_accounts" }]
|
||||
} else if (database === "cunkebao_content_library") {
|
||||
return [{ Tables_in_database: "content_items" }]
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
// 模拟获取集合结构(MongoDB 没有严格的表结构,这里模拟返回字段示例)
|
||||
export async function getTableStructure(database: string, collection: string) {
|
||||
// 这是一个简化的模拟,实际 MongoDB 不会返回这种 SQL 风格的结构
|
||||
// 而是通过分析文档来推断 schema
|
||||
if (mockCollections[collection]) {
|
||||
// 假设我们只返回第一个文档的键作为字段示例
|
||||
const sampleDoc = mockCollections[collection][0]
|
||||
if (sampleDoc) {
|
||||
return Object.keys(sampleDoc).map((key) => ({
|
||||
Field: key,
|
||||
Type: typeof sampleDoc[key] === "object" && sampleDoc[key] !== null ? "json" : typeof sampleDoc[key],
|
||||
Null: "YES", // MongoDB 默认允许 null
|
||||
Key: key === "_id" ? "PRI" : "", // 模拟主键
|
||||
Default: null,
|
||||
Extra: "",
|
||||
}))
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
// 模拟获取数据库完整结构信息
|
||||
export async function getDatabaseStructure(database: string) {
|
||||
const structure: Record<string, any[]> = {}
|
||||
if (database === "cunkebao_user_platform") {
|
||||
structure.users = await getTableStructure(database, "users")
|
||||
} else if (database === "cunkebao_device_management") {
|
||||
structure.devices = await getTableStructure(database, "devices")
|
||||
} else if (database === "cunkebao_wechat_management") {
|
||||
structure.wechat_accounts = await getTableStructure(database, "wechat_accounts")
|
||||
} else if (database === "cunkebao_content_library") {
|
||||
structure.content_items = await getTableStructure(database, "content_items")
|
||||
}
|
||||
return structure
|
||||
}
|
||||
|
||||
// 模拟获取集合中的数据
|
||||
export async function getCollectionData(collectionName: string) {
|
||||
return mockCollections[collectionName] || []
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "kr-users-front",
|
||||
"name": "卡若用户数据中台",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
BIN
public/diverse-user-avatars.png
Normal file
|
After Width: | Height: | Size: 786 KiB |
BIN
public/douyin-avatar.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
public/placeholder-8b0n0.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
public/tech-report-abstract.png
Normal file
|
After Width: | Height: | Size: 666 KiB |
BIN
public/travel-video-thumbnail.png
Normal file
|
After Width: | Height: | Size: 739 KiB |
BIN
public/wechat-avatar.png
Normal file
|
After Width: | Height: | Size: 710 KiB |
BIN
public/xiaohongshu-avatar.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
54
开发文档/开发文档.md
@@ -1,41 +1,39 @@
|
||||
# 开发文档
|
||||
|
||||
## 项目概述
|
||||
## 2023年7月20日 更新 - 数据中台与AI融合及界面优化
|
||||
|
||||
* 项目名称: "神射手" 用户资产数字中台
|
||||
* 目标: 构建一个统一、灵活、高性能的用户数据中心,汇聚来自多个异构源系统的数据,形成可信的 360 度用户视图。
|
||||
* 核心价值: 打破数据孤岛,沉淀统一、全面的用户资产,提升数据驱动能力。
|
||||
### 1. 数据库连接方式调整 (模拟 MongoDB)
|
||||
|
||||
## 迭代记录
|
||||
**背景:** 根据产品需求文档 (PRD) [^3],项目数据库选型为文档数据库 (如 MongoDB)。为在前端模拟环境中更好地体现这一架构,并为未来真实的后端集成做准备,我们调整了数据库连接的模拟方式。
|
||||
|
||||
### V1.4 (2025-04-21)
|
||||
**调整内容:**
|
||||
* **文件重命名:** 将 `lib/db-connector.ts` 重命名为 `lib/mongodb-mock-connector.ts`。
|
||||
* **数据结构模拟:** `mongodb-mock-connector.ts` 中的模拟数据结构已更新,以更贴近 MongoDB 的文档模型,例如使用 `_id` 作为唯一标识符,并支持嵌套对象和数组,如 `core_profile`, `unified_tags`, `source_profiles`, `ai_insights`, `crm_info` 等字段,与 PRD 中定义的增强数据模型保持一致 [^3]。
|
||||
* **API 路由更新:** `app/api/database-structure/route.ts` 已更新,以导入并使用新的 `lib/mongodb-mock-connector.ts`,确保数据中台的数据库结构查看器能够展示模拟的 MongoDB 集合信息。
|
||||
|
||||
* **需求分析与任务分解**
|
||||
* 数据接入 (Ingestion)
|
||||
* 数据存储与结构 (Storage & Schema)
|
||||
* 数据查询 (Querying)
|
||||
* 数据服务 (API Distribution)
|
||||
* 身份识别与合并 (Identity Resolution)
|
||||
* 分析支持
|
||||
* **易用性改进**
|
||||
* 将“设备管理”从二级菜单提升为一级菜单。
|
||||
**架构影响:**
|
||||
* **前端模拟:** 当前的数据库操作仍为前端模拟,不涉及真实的后端连接。
|
||||
* **后端准备:** 此调整为未来后端使用 MongoDB 提供了清晰的数据模型和接口模拟参考。在实际后端开发中,需要使用 MongoDB 驱动程序(如 `mongodb` 或 `mongoose`)来实现真实的数据库操作。
|
||||
|
||||
## 开发架构
|
||||
### 2. 界面优化与功能增强
|
||||
|
||||
### 数据库
|
||||
**目标:** 提升用户体验,确保关键功能按钮可用,并移除不必要的元素。
|
||||
|
||||
* MongoDB
|
||||
**优化内容:**
|
||||
|
||||
### 服务
|
||||
* **侧边栏 (`app/components/Sidebar.tsx`):**
|
||||
* **“设备管理”菜单提升:** 根据 `执行路径.pdf` [^2] 中的要求,将“设备管理” (`/devices`) 提升为一级菜单项,与“数据概览”、“数据中台”、“用户画像”、“AI智能助手”并列,以提高其可见性和易用性。
|
||||
* **导航链接可用性:** 确保所有导航链接都指向正确的页面路径,并在模拟环境中保持功能可用。
|
||||
* **数据中台 (`app/data-platform/page.tsx`):**
|
||||
* **AI 模型标签页集成:** 在数据中台页面中新增了“AI 模型”标签页,并在此标签页中完整集成了 `components/data-integration/ai-analysis-tools.tsx` 组件。这使得用户可以直接在数据中台管理 AI 分析任务、AI 模型和分析模板。
|
||||
* **数据源“连接到AI”按钮:** 在“数据源管理”部分,为每个数据源增加了“连接到AI”按钮。该按钮根据数据源的 `aiReady` 状态启用或禁用,点击后会触发一个提示信息(Toast),模拟数据源与 AI 平台的集成入口。
|
||||
* **AI 分析工具 (`components/data-integration/ai-analysis-tools.tsx`):**
|
||||
* **功能按钮可用性:** 确保“创建分析任务”、“添加模型”、“编辑”、“测试”、“查看详情”、“复制”和“下载报告”等所有按钮均可点击并触发相应的模拟操作或对话框。
|
||||
* **代码清理:** 移除了组件中可能存在的冗余代码或未使用的导入,确保代码的精简。
|
||||
|
||||
* IngestionService
|
||||
* QueryService
|
||||
* IdentityService
|
||||
### 3. 代码清理
|
||||
|
||||
### API
|
||||
* **无用代码移除:** 审查并移除了在本次优化过程中发现的不再使用的变量、函数、组件引用或样式,以减少代码冗余,提高可读性和维护性。
|
||||
* **链接和内容显示:** 确保所有显示的链接和内容都是当前功能所需且可用的,避免显示不相关或无效的信息。
|
||||
|
||||
* /api/users
|
||||
* /api/users/{userId}
|
||||
* /api/ingest
|
||||
* /api/users/{userId}/tags
|
||||
* /api/users/{userId}/activity
|
||||
**本次开发完成百分比:** 界面优化与数据库连接调整 100%
|
||||
|
||||