Reorganize navigation and module structure based on new requirements. Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
363 lines
14 KiB
TypeScript
363 lines
14 KiB
TypeScript
"use client"
|
|
|
|
import { useState } from "react"
|
|
import { Card, CardContent, CardHeader, CardTitle } 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,
|
|
} from "lucide-react"
|
|
import { Progress } from "@/components/ui/progress"
|
|
|
|
export default function DataMiddlePlatform() {
|
|
const [searchQuery, setSearchQuery] = useState("")
|
|
|
|
const dataSources = [
|
|
{
|
|
id: 1,
|
|
name: "私域系统数据源",
|
|
type: "微信生态",
|
|
status: "active",
|
|
lastSync: "2分钟前",
|
|
users: 2850000000,
|
|
syncRate: 98.5,
|
|
dataSize: "850TB",
|
|
},
|
|
{
|
|
id: 2,
|
|
name: "电商平台数据",
|
|
type: "淘宝/京东",
|
|
status: "active",
|
|
lastSync: "5分钟前",
|
|
users: 680000000,
|
|
syncRate: 95.2,
|
|
dataSize: "210TB",
|
|
},
|
|
{
|
|
id: 3,
|
|
name: "表单收集数据",
|
|
type: "第三方表单",
|
|
status: "active",
|
|
lastSync: "10分钟前",
|
|
users: 425000000,
|
|
syncRate: 92.8,
|
|
dataSize: "125TB",
|
|
},
|
|
{
|
|
id: 4,
|
|
name: "碰撞补全数据",
|
|
type: "数据碰撞",
|
|
status: "syncing",
|
|
lastSync: "正在同步",
|
|
users: 280000000,
|
|
syncRate: 76.3,
|
|
dataSize: "95TB",
|
|
},
|
|
{
|
|
id: 5,
|
|
name: "项目API数据源",
|
|
type: "外部API",
|
|
status: "active",
|
|
lastSync: "1分钟前",
|
|
users: 50670000,
|
|
syncRate: 99.1,
|
|
dataSize: "18TB",
|
|
},
|
|
]
|
|
|
|
const cleaningTasks = [
|
|
{ name: "手机号格式校验", total: 4285670000, processed: 4200000000, progress: 98.0, errors: 1250000 },
|
|
{ name: "重复数据去除", total: 4285670000, processed: 3850000000, progress: 89.8, errors: 85000000 },
|
|
{ name: "缺失字段补全", total: 4285670000, processed: 3250000000, progress: 75.8, errors: 125000000 },
|
|
{ name: "异常值检测", total: 4285670000, processed: 4100000000, progress: 95.7, errors: 8500000 },
|
|
]
|
|
|
|
const tagStats = { totalTags: 1258, systemTags: 485, customTags: 773, activeRules: 2850, todayTagged: 2845000 }
|
|
|
|
const qualityChecks = [
|
|
{ type: "重复数据", count: 85420000, percentage: 2.0, severity: "warning" },
|
|
{ type: "异常数据", count: 12850000, percentage: 0.3, severity: "error" },
|
|
{ type: "缺失字段", count: 325680000, percentage: 7.6, severity: "warning" },
|
|
{ type: "格式错误", count: 5280000, 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="space-y-8">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-3xl font-bold text-gray-900">数据中台</h1>
|
|
<p className="text-gray-500 mt-1">全平台数据源管理、清洗与质量监控</p>
|
|
</div>
|
|
<div className="flex items-center gap-3">
|
|
<Button variant="outline" className="bg-white/60 backdrop-blur-md">
|
|
<Upload className="w-4 h-4 mr-2" />
|
|
上传数据
|
|
</Button>
|
|
<Button variant="outline" className="bg-white/60 backdrop-blur-md">
|
|
<Download className="w-4 h-4 mr-2" />
|
|
导出报告
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<Tabs defaultValue="sources" className="space-y-6">
|
|
<TabsList className="bg-white/60 backdrop-blur-md border border-gray-200 p-1 rounded-xl">
|
|
<TabsTrigger
|
|
value="sources"
|
|
className="rounded-lg data-[state=active]:bg-white data-[state=active]:shadow-sm"
|
|
>
|
|
数据源管理
|
|
</TabsTrigger>
|
|
<TabsTrigger
|
|
value="cleaning"
|
|
className="rounded-lg data-[state=active]:bg-white data-[state=active]:shadow-sm"
|
|
>
|
|
数据清洗
|
|
</TabsTrigger>
|
|
<TabsTrigger value="tags" className="rounded-lg data-[state=active]:bg-white data-[state=active]:shadow-sm">
|
|
标签系统
|
|
</TabsTrigger>
|
|
<TabsTrigger
|
|
value="quality"
|
|
className="rounded-lg data-[state=active]:bg-white data-[state=active]:shadow-sm"
|
|
>
|
|
质量检测
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="sources" className="space-y-6">
|
|
<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-gray-400" />
|
|
<Input
|
|
placeholder="搜索数据源..."
|
|
value={searchQuery}
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
className="pl-10 bg-white/60 backdrop-blur-md border-gray-200"
|
|
/>
|
|
</div>
|
|
<Button variant="outline" className="bg-white/60 backdrop-blur-md">
|
|
<Filter className="w-4 h-4 mr-2" />
|
|
筛选
|
|
</Button>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 gap-4">
|
|
{dataSources.map((source) => (
|
|
<Card
|
|
key={source.id}
|
|
className="bg-white/60 backdrop-blur-md border-gray-200 shadow-sm hover:shadow-md transition-all"
|
|
>
|
|
<CardContent className="p-6">
|
|
<div className="flex items-center justify-between mb-6">
|
|
<div className="flex items-center gap-4">
|
|
<div className="w-12 h-12 rounded-xl bg-blue-50 flex items-center justify-center">
|
|
<Database className="w-6 h-6 text-blue-600" />
|
|
</div>
|
|
<div>
|
|
<h3 className="text-lg font-bold text-gray-900">{source.name}</h3>
|
|
<p className="text-sm text-gray-500">{source.type}</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-4">
|
|
<Badge
|
|
variant="outline"
|
|
className={
|
|
source.status === "active"
|
|
? "bg-green-50 text-green-600 border-green-200"
|
|
: "bg-yellow-50 text-yellow-600 border-yellow-200"
|
|
}
|
|
>
|
|
{source.status === "active" ? (
|
|
<>
|
|
<CheckCircle2 className="w-3 h-3 mr-1" />
|
|
运行中
|
|
</>
|
|
) : (
|
|
<>
|
|
<RefreshCw className="w-3 h-3 mr-1 animate-spin" />
|
|
同步中
|
|
</>
|
|
)}
|
|
</Badge>
|
|
<Button size="sm" variant="outline" className="bg-white">
|
|
<RefreshCw className="w-3 h-3 mr-1" />
|
|
同步
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-4 gap-8 mb-4">
|
|
<div>
|
|
<div className="text-sm text-gray-500 mb-1">用户数量</div>
|
|
<div className="text-xl font-bold text-gray-900">{formatNumber(source.users)}</div>
|
|
</div>
|
|
<div>
|
|
<div className="text-sm text-gray-500 mb-1">同步率</div>
|
|
<div className="text-xl font-bold text-green-600">{source.syncRate}%</div>
|
|
</div>
|
|
<div>
|
|
<div className="text-sm text-gray-500 mb-1">数据量</div>
|
|
<div className="text-xl font-bold text-blue-600">{source.dataSize}</div>
|
|
</div>
|
|
<div>
|
|
<div className="text-sm text-gray-500 mb-1">最后同步</div>
|
|
<div className="text-sm font-medium text-gray-900">{source.lastSync}</div>
|
|
</div>
|
|
</div>
|
|
<Progress value={source.syncRate} className="h-2 bg-gray-100" />
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="cleaning" className="space-y-6">
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
{cleaningTasks.map((task, index) => (
|
|
<Card key={index} className="bg-white/60 backdrop-blur-md border-gray-200 shadow-sm">
|
|
<CardHeader>
|
|
<CardTitle className="text-gray-900 flex items-center gap-2">
|
|
<Zap className="w-5 h-5 text-yellow-500" />
|
|
{task.name}
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-gray-500">进度</span>
|
|
<span className="text-2xl font-bold text-green-600">{task.progress}%</span>
|
|
</div>
|
|
<Progress value={task.progress} className="h-2 bg-gray-100" />
|
|
<div className="grid grid-cols-2 gap-4 pt-4 border-t border-gray-100">
|
|
<div>
|
|
<div className="text-sm text-gray-500 mb-1">已处理</div>
|
|
<div className="text-lg font-semibold text-gray-900">{formatNumber(task.processed)}</div>
|
|
</div>
|
|
<div>
|
|
<div className="text-sm text-gray-500 mb-1">异常数据</div>
|
|
<div className="text-lg font-semibold text-red-500">{formatNumber(task.errors)}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="tags" className="space-y-6">
|
|
<div className="grid grid-cols-1 lg:grid-cols-5 gap-6">
|
|
{[
|
|
{
|
|
icon: Tag,
|
|
label: "标签总数",
|
|
value: tagStats.totalTags,
|
|
color: "text-purple-600",
|
|
bg: "bg-purple-100",
|
|
},
|
|
{
|
|
icon: FileText,
|
|
label: "系统标签",
|
|
value: tagStats.systemTags,
|
|
color: "text-blue-600",
|
|
bg: "bg-blue-100",
|
|
},
|
|
{
|
|
icon: Tag,
|
|
label: "自定义标签",
|
|
value: tagStats.customTags,
|
|
color: "text-green-600",
|
|
bg: "bg-green-100",
|
|
},
|
|
{
|
|
icon: Zap,
|
|
label: "活跃规则",
|
|
value: tagStats.activeRules,
|
|
color: "text-yellow-600",
|
|
bg: "bg-yellow-100",
|
|
},
|
|
{
|
|
icon: TrendingUp,
|
|
label: "今日打标",
|
|
value: formatNumber(tagStats.todayTagged),
|
|
color: "text-orange-600",
|
|
bg: "bg-orange-100",
|
|
},
|
|
].map((stat, index) => (
|
|
<Card key={index} className="bg-white/60 backdrop-blur-md border-gray-200 shadow-sm">
|
|
<CardContent className="p-6 text-center">
|
|
<div className={`w-10 h-10 rounded-full ${stat.bg} flex items-center justify-center mx-auto mb-3`}>
|
|
<stat.icon className={`w-5 h-5 ${stat.color}`} />
|
|
</div>
|
|
<div className={`text-2xl font-bold ${stat.color} mb-1`}>{stat.value}</div>
|
|
<div className="text-sm text-gray-500">{stat.label}</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="quality" className="space-y-6">
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
{qualityChecks.map((check, index) => (
|
|
<Card key={index} className="bg-white/60 backdrop-blur-md border-gray-200 shadow-sm">
|
|
<CardContent className="p-6">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<div className="flex items-center gap-3">
|
|
<AlertTriangle
|
|
className={`w-6 h-6 ${check.severity === "error" ? "text-red-500" : "text-yellow-500"}`}
|
|
/>
|
|
<h3 className="text-lg font-semibold text-gray-900">{check.type}</h3>
|
|
</div>
|
|
<Badge
|
|
variant="outline"
|
|
className={
|
|
check.severity === "error"
|
|
? "bg-red-50 text-red-600 border-red-200"
|
|
: "bg-yellow-50 text-yellow-600 border-yellow-200"
|
|
}
|
|
>
|
|
{check.severity === "error" ? "严重" : "警告"}
|
|
</Badge>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-gray-500">问题数量</span>
|
|
<span className="text-2xl font-bold text-gray-900">{formatNumber(check.count)}</span>
|
|
</div>
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-gray-500">占比</span>
|
|
<span className="text-lg font-semibold text-gray-900">{check.percentage}%</span>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
</TabsContent>
|
|
</Tabs>
|
|
</div>
|
|
)
|
|
}
|