Organize project by 5 core modules based on requirement docs. Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
409 lines
17 KiB
TypeScript
409 lines
17 KiB
TypeScript
"use client"
|
|
|
|
import { useState } from "react"
|
|
import Link from "next/link"
|
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
|
|
import { Badge } from "@/components/ui/badge"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Input } from "@/components/ui/input"
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|
import { Database, RefreshCw, CheckCircle2, AlertTriangle, Search, FileText, Upload, Download, Filter, TrendingUp, Zap, Tag, Plus, Plug, Shield, ArrowRight } from 'lucide-react'
|
|
import { Progress } from "@/components/ui/progress"
|
|
|
|
export default function DataManagement() {
|
|
const [searchQuery, setSearchQuery] = useState("")
|
|
|
|
// 数据源管理
|
|
const dataSources = [
|
|
{
|
|
id: 1,
|
|
name: "微信好友数据",
|
|
type: "微信生态",
|
|
status: "active",
|
|
lastSync: "2分钟前",
|
|
users: 1234567,
|
|
syncRate: 98.5,
|
|
dataSize: "85GB",
|
|
},
|
|
{
|
|
id: 2,
|
|
name: "抖音粉丝数据",
|
|
type: "抖音平台",
|
|
status: "active",
|
|
lastSync: "5分钟前",
|
|
users: 856432,
|
|
syncRate: 95.2,
|
|
dataSize: "21GB",
|
|
},
|
|
{
|
|
id: 3,
|
|
name: "小红书数据",
|
|
type: "小红书",
|
|
status: "pending",
|
|
lastSync: "待配置",
|
|
users: 0,
|
|
syncRate: 0,
|
|
dataSize: "0",
|
|
},
|
|
{
|
|
id: 4,
|
|
name: "企业CRM数据",
|
|
type: "数据库直连",
|
|
status: "active",
|
|
lastSync: "1分钟前",
|
|
users: 2456789,
|
|
syncRate: 99.1,
|
|
dataSize: "156GB",
|
|
},
|
|
{
|
|
id: 5,
|
|
name: "淘宝店铺数据",
|
|
type: "API接入",
|
|
status: "error",
|
|
lastSync: "授权过期",
|
|
users: 345678,
|
|
syncRate: 0,
|
|
dataSize: "18GB",
|
|
},
|
|
]
|
|
|
|
// 数据清洗进度
|
|
const cleaningTasks = [
|
|
{ name: "手机号格式校验", total: 4285670, processed: 4200000, progress: 98.0, errors: 1250 },
|
|
{ name: "重复数据去除", total: 4285670, processed: 3850000, progress: 89.8, errors: 85000 },
|
|
{ name: "缺失字段补全", total: 4285670, processed: 3250000, progress: 75.8, errors: 125000 },
|
|
{ name: "异常值检测", total: 4285670, processed: 4100000, progress: 95.7, errors: 8500 },
|
|
]
|
|
|
|
// 标签分类
|
|
const tagCategories = [
|
|
{ name: "用户价值", count: 5, color: "bg-violet-100 text-violet-700 dark:bg-violet-900/30 dark:text-violet-400" },
|
|
{ name: "活跃度", count: 4, color: "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400" },
|
|
{ name: "消费行为", count: 6, color: "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" },
|
|
{ name: "兴趣偏好", count: 12, color: "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400" },
|
|
{ name: "生命周期", count: 5, color: "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400" },
|
|
{ name: "地域分布", count: 8, color: "bg-cyan-100 text-cyan-700 dark:bg-cyan-900/30 dark:text-cyan-400" },
|
|
]
|
|
|
|
// 标签系统统计
|
|
const tagStats = {
|
|
totalTags: 40,
|
|
systemTags: 25,
|
|
customTags: 15,
|
|
activeRules: 28,
|
|
todayTagged: 28450,
|
|
}
|
|
|
|
// 数据质量检测
|
|
const qualityChecks = [
|
|
{ type: "重复数据", count: 85420, percentage: 2.0, severity: "warning" },
|
|
{ type: "异常数据", count: 12850, percentage: 0.3, severity: "error" },
|
|
{ type: "缺失字段", count: 325680, percentage: 7.6, severity: "warning" },
|
|
{ type: "格式错误", count: 5280, percentage: 0.12, severity: "error" },
|
|
]
|
|
|
|
const formatNumber = (num: number): string => {
|
|
if (num >= 1000000000) return `${(num / 1000000000).toFixed(1)}B`
|
|
if (num >= 1000000) return `${(num / 1000000).toFixed(1)}M`
|
|
if (num >= 1000) return `${(num / 1000).toFixed(1)}K`
|
|
return num.toString()
|
|
}
|
|
|
|
return (
|
|
<div className="flex-1 space-y-6 p-6 md:p-8">
|
|
{/* Header */}
|
|
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
|
<div>
|
|
<h1 className="text-2xl md:text-3xl font-bold tracking-tight">数据中台</h1>
|
|
<p className="text-muted-foreground mt-1">管理数据源、清洗规则和标签体系</p>
|
|
</div>
|
|
<div className="flex items-center gap-3">
|
|
<Button variant="outline" className="bg-transparent">
|
|
<Upload className="w-4 h-4 mr-2" />
|
|
上传数据
|
|
</Button>
|
|
<Button>
|
|
<Plus className="w-4 h-4 mr-2" />
|
|
添加数据源
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 子功能导航卡片 */}
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
<Link href="/data-platform/sources">
|
|
<Card className="hover:border-primary/50 transition-colors cursor-pointer">
|
|
<CardContent className="p-4 flex items-center gap-3">
|
|
<div className="h-10 w-10 rounded-lg bg-violet-100 dark:bg-violet-900/30 flex items-center justify-center">
|
|
<Plug className="h-5 w-5 text-violet-600" />
|
|
</div>
|
|
<div>
|
|
<p className="font-medium">数据源管理</p>
|
|
<p className="text-xs text-muted-foreground">5个数据源</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</Link>
|
|
<Link href="/data-platform/cleaning">
|
|
<Card className="hover:border-primary/50 transition-colors cursor-pointer">
|
|
<CardContent className="p-4 flex items-center gap-3">
|
|
<div className="h-10 w-10 rounded-lg bg-blue-100 dark:bg-blue-900/30 flex items-center justify-center">
|
|
<Database className="h-5 w-5 text-blue-600" />
|
|
</div>
|
|
<div>
|
|
<p className="font-medium">数据清洗</p>
|
|
<p className="text-xs text-muted-foreground">4个任务运行中</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</Link>
|
|
<Link href="/data-platform/tags">
|
|
<Card className="hover:border-primary/50 transition-colors cursor-pointer">
|
|
<CardContent className="p-4 flex items-center gap-3">
|
|
<div className="h-10 w-10 rounded-lg bg-emerald-100 dark:bg-emerald-900/30 flex items-center justify-center">
|
|
<Tag className="h-5 w-5 text-emerald-600" />
|
|
</div>
|
|
<div>
|
|
<p className="font-medium">标签管理</p>
|
|
<p className="text-xs text-muted-foreground">40个标签</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</Link>
|
|
<Link href="/data-platform/quality">
|
|
<Card className="hover:border-primary/50 transition-colors cursor-pointer">
|
|
<CardContent className="p-4 flex items-center gap-3">
|
|
<div className="h-10 w-10 rounded-lg bg-amber-100 dark:bg-amber-900/30 flex items-center justify-center">
|
|
<Shield className="h-5 w-5 text-amber-600" />
|
|
</div>
|
|
<div>
|
|
<p className="font-medium">数据质量</p>
|
|
<p className="text-xs text-muted-foreground">质量分: 92</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</Link>
|
|
</div>
|
|
|
|
<Tabs defaultValue="sources" className="space-y-4">
|
|
<TabsList>
|
|
<TabsTrigger value="sources">数据源</TabsTrigger>
|
|
<TabsTrigger value="cleaning">数据清洗</TabsTrigger>
|
|
<TabsTrigger value="tags">标签体系</TabsTrigger>
|
|
<TabsTrigger value="quality">质量检测</TabsTrigger>
|
|
</TabsList>
|
|
|
|
{/* 数据源管理 */}
|
|
<TabsContent value="sources" className="space-y-4">
|
|
{/* 搜索栏 */}
|
|
<div className="flex items-center gap-4">
|
|
<div className="relative flex-1">
|
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-muted-foreground" />
|
|
<Input
|
|
placeholder="搜索数据源..."
|
|
value={searchQuery}
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
className="pl-10"
|
|
/>
|
|
</div>
|
|
<Button variant="outline" className="bg-transparent">
|
|
<Filter className="w-4 h-4 mr-2" />
|
|
筛选
|
|
</Button>
|
|
</div>
|
|
|
|
{/* 数据源列表 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>已接入数据源</CardTitle>
|
|
<CardDescription>管理所有已配置的数据源连接</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-4">
|
|
{dataSources.map((source) => (
|
|
<div key={source.id} className="flex items-center justify-between p-4 border rounded-lg">
|
|
<div className="flex items-center gap-4">
|
|
<div className="h-10 w-10 rounded-lg bg-muted flex items-center justify-center">
|
|
<Database className="h-5 w-5" />
|
|
</div>
|
|
<div>
|
|
<p className="font-medium">{source.name}</p>
|
|
<p className="text-sm text-muted-foreground">
|
|
{source.type} · {formatNumber(source.users)} 条记录
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-4">
|
|
<div className="text-right">
|
|
<Badge
|
|
variant={
|
|
source.status === "active"
|
|
? "default"
|
|
: source.status === "pending"
|
|
? "secondary"
|
|
: "destructive"
|
|
}
|
|
>
|
|
{source.status === "active" && <CheckCircle2 className="mr-1 h-3 w-3" />}
|
|
{source.status === "pending" && <RefreshCw className="mr-1 h-3 w-3" />}
|
|
{source.status === "error" && <AlertTriangle className="mr-1 h-3 w-3" />}
|
|
{source.status === "active" ? "已连接" : source.status === "pending" ? "待配置" : "错误"}
|
|
</Badge>
|
|
<p className="text-xs text-muted-foreground mt-1">{source.lastSync}</p>
|
|
</div>
|
|
<Button variant="ghost" size="sm">
|
|
<RefreshCw className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
|
|
{/* 数据清洗 */}
|
|
<TabsContent value="cleaning" className="space-y-4">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>清洗任务</CardTitle>
|
|
<CardDescription>数据清洗与标准化任务进度</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-4">
|
|
{cleaningTasks.map((task, index) => (
|
|
<div key={index} className="p-4 border rounded-lg">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<div className="flex items-center gap-2">
|
|
<Zap className="h-4 w-4 text-amber-500" />
|
|
<p className="font-medium">{task.name}</p>
|
|
<Badge variant="outline">{formatNumber(task.processed)}</Badge>
|
|
</div>
|
|
<Badge
|
|
variant={task.progress === 100 ? "default" : task.progress > 0 ? "secondary" : "outline"}
|
|
>
|
|
{task.progress === 100 ? "已完成" : task.progress > 0 ? "运行中" : "等待中"}
|
|
</Badge>
|
|
</div>
|
|
<Progress value={task.progress} className="h-2" />
|
|
<div className="flex justify-between text-xs text-muted-foreground mt-2">
|
|
<span>{task.progress}% 完成</span>
|
|
<span className="text-red-500">{formatNumber(task.errors)} 异常</span>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
|
|
{/* 标签系统 */}
|
|
<TabsContent value="tags" className="space-y-4">
|
|
{/* 统计卡片 */}
|
|
<div className="grid grid-cols-2 md:grid-cols-5 gap-4">
|
|
<Card className="bg-gradient-to-br from-violet-500/10 to-purple-500/10 border-violet-200 dark:border-violet-800">
|
|
<CardContent className="p-4 text-center">
|
|
<Tag className="h-6 w-6 text-violet-600 mx-auto mb-2" />
|
|
<div className="text-2xl font-bold">{tagStats.totalTags}</div>
|
|
<div className="text-xs text-muted-foreground">标签总数</div>
|
|
</CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardContent className="p-4 text-center">
|
|
<FileText className="h-6 w-6 text-blue-600 mx-auto mb-2" />
|
|
<div className="text-2xl font-bold">{tagStats.systemTags}</div>
|
|
<div className="text-xs text-muted-foreground">系统标签</div>
|
|
</CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardContent className="p-4 text-center">
|
|
<Tag className="h-6 w-6 text-green-600 mx-auto mb-2" />
|
|
<div className="text-2xl font-bold">{tagStats.customTags}</div>
|
|
<div className="text-xs text-muted-foreground">自定义标签</div>
|
|
</CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardContent className="p-4 text-center">
|
|
<Zap className="h-6 w-6 text-amber-600 mx-auto mb-2" />
|
|
<div className="text-2xl font-bold">{tagStats.activeRules}</div>
|
|
<div className="text-xs text-muted-foreground">活跃规则</div>
|
|
</CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardContent className="p-4 text-center">
|
|
<TrendingUp className="h-6 w-6 text-orange-600 mx-auto mb-2" />
|
|
<div className="text-2xl font-bold">{formatNumber(tagStats.todayTagged)}</div>
|
|
<div className="text-xs text-muted-foreground">今日打标</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* 标签分类 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>标签分类</CardTitle>
|
|
<CardDescription>用户标签体系管理</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
|
{tagCategories.map((category) => (
|
|
<Link key={category.name} href={`/data-platform/tags/${category.name}`}>
|
|
<Card className="hover:border-primary/50 transition-colors cursor-pointer">
|
|
<CardContent className="p-4">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="font-medium">{category.name}</p>
|
|
<p className="text-sm text-muted-foreground">{category.count} 个标签</p>
|
|
</div>
|
|
<ArrowRight className="h-4 w-4 text-muted-foreground" />
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
|
|
{/* 质量检测 */}
|
|
<TabsContent value="quality" className="space-y-4">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>数据质量报告</CardTitle>
|
|
<CardDescription>检测并修复数据质量问题</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{qualityChecks.map((check, index) => (
|
|
<div key={index} className="p-4 border rounded-lg">
|
|
<div className="flex items-center justify-between mb-3">
|
|
<div className="flex items-center gap-2">
|
|
<AlertTriangle className={`h-5 w-5 ${check.severity === "error" ? "text-red-500" : "text-amber-500"}`} />
|
|
<p className="font-medium">{check.type}</p>
|
|
</div>
|
|
<Badge variant={check.severity === "error" ? "destructive" : "secondary"}>
|
|
{check.severity === "error" ? "严重" : "警告"}
|
|
</Badge>
|
|
</div>
|
|
<div className="flex justify-between items-end">
|
|
<div>
|
|
<p className="text-2xl font-bold">{formatNumber(check.count)}</p>
|
|
<p className="text-xs text-muted-foreground">问题数量</p>
|
|
</div>
|
|
<div className="text-right">
|
|
<p className="text-lg font-semibold">{check.percentage}%</p>
|
|
<p className="text-xs text-muted-foreground">占比</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
</Tabs>
|
|
</div>
|
|
)
|
|
}
|