Redesign navigation, home overview, user portrait, and valuation pages with improved functionality and responsive design. Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
988 lines
41 KiB
TypeScript
988 lines
41 KiB
TypeScript
"use client"
|
||
|
||
import { Textarea } from "@/components/ui/textarea"
|
||
|
||
import { useState } from "react"
|
||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
|
||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||
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 { Switch } from "@/components/ui/switch"
|
||
import { Badge } from "@/components/ui/badge"
|
||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||
import { Separator } from "@/components/ui/separator"
|
||
import { AlertTriangle, CheckCircle, Bell, RefreshCw, Filter, Download, Search, Clock } from "lucide-react"
|
||
import {
|
||
Dialog,
|
||
DialogContent,
|
||
DialogDescription,
|
||
DialogFooter,
|
||
DialogHeader,
|
||
DialogTitle,
|
||
} from "@/components/ui/dialog"
|
||
|
||
interface DataQualityIssue {
|
||
id: string
|
||
type: string
|
||
description: string
|
||
affectedRecords: number
|
||
affectedFields: string[]
|
||
severity: "high" | "medium" | "low"
|
||
status: "open" | "in_progress" | "resolved"
|
||
createdAt: string
|
||
dataSource: string
|
||
}
|
||
|
||
interface DataQualityRule {
|
||
id: string
|
||
name: string
|
||
description: string
|
||
type: string
|
||
enabled: boolean
|
||
severity: "high" | "medium" | "low"
|
||
dataSource: string
|
||
lastRun: string
|
||
issuesDetected: number
|
||
}
|
||
|
||
export function DataQualityMonitor() {
|
||
const [activeTab, setActiveTab] = useState("issues")
|
||
const [selectedIssueId, setSelectedIssueId] = useState<string | null>(null)
|
||
const [isAddingRule, setIsAddingRule] = useState(false)
|
||
const [filterSeverity, setFilterSeverity] = useState<string>("all")
|
||
const [filterStatus, setFilterStatus] = useState<string>("all")
|
||
|
||
// 模拟数据质量问题
|
||
const [dataQualityIssues, setDataQualityIssues] = useState<DataQualityIssue[]>([
|
||
{
|
||
id: "issue-1",
|
||
type: "缺失值",
|
||
description: "用户表中存在大量手机号字段为空的记录",
|
||
affectedRecords: 1250,
|
||
affectedFields: ["phoneNumber"],
|
||
severity: "high",
|
||
status: "open",
|
||
createdAt: "2023-07-20 10:15",
|
||
dataSource: "CRM系统",
|
||
},
|
||
{
|
||
id: "issue-2",
|
||
type: "格式错误",
|
||
description: "部分用户的身份证号格式不正确",
|
||
affectedRecords: 458,
|
||
affectedFields: ["identityNumber"],
|
||
severity: "medium",
|
||
status: "in_progress",
|
||
createdAt: "2023-07-19 15:30",
|
||
dataSource: "用户数据库",
|
||
},
|
||
{
|
||
id: "issue-3",
|
||
type: "重复数据",
|
||
description: "存在多条使用相同手机号的用户记录",
|
||
affectedRecords: 789,
|
||
affectedFields: ["userId", "phoneNumber"],
|
||
severity: "medium",
|
||
status: "open",
|
||
createdAt: "2023-07-18 09:45",
|
||
dataSource: "交易系统",
|
||
},
|
||
{
|
||
id: "issue-4",
|
||
type: "异常值",
|
||
description: "部分用户的年龄字段值异常(超过150岁)",
|
||
affectedRecords: 23,
|
||
affectedFields: ["age"],
|
||
severity: "low",
|
||
status: "resolved",
|
||
createdAt: "2023-07-17 14:20",
|
||
dataSource: "用户数据库",
|
||
},
|
||
{
|
||
id: "issue-5",
|
||
type: "不一致数据",
|
||
description: "用户在不同系统中的姓名不一致",
|
||
affectedRecords: 567,
|
||
affectedFields: ["fullName"],
|
||
severity: "high",
|
||
status: "open",
|
||
createdAt: "2023-07-16 11:05",
|
||
dataSource: "多系统",
|
||
},
|
||
])
|
||
|
||
// 模拟数据质量规则
|
||
const [dataQualityRules, setDataQualityRules] = useState<DataQualityRule[]>([
|
||
{
|
||
id: "rule-1",
|
||
name: "手机号格式检查",
|
||
description: "检查手机号是否符合中国大陆手机号格式",
|
||
type: "格式验证",
|
||
enabled: true,
|
||
severity: "high",
|
||
dataSource: "所有数据源",
|
||
lastRun: "2023-07-20 00:00",
|
||
issuesDetected: 458,
|
||
},
|
||
{
|
||
id: "rule-2",
|
||
name: "身份证号验证",
|
||
description: "验证身份证号的格式和校验码",
|
||
type: "格式验证",
|
||
enabled: true,
|
||
severity: "high",
|
||
dataSource: "所有数据源",
|
||
lastRun: "2023-07-20 00:00",
|
||
issuesDetected: 789,
|
||
},
|
||
{
|
||
id: "rule-3",
|
||
name: "必填字段检查",
|
||
description: "检查关键字段是否有值",
|
||
type: "完整性检查",
|
||
enabled: true,
|
||
severity: "medium",
|
||
dataSource: "所有数据源",
|
||
lastRun: "2023-07-20 00:00",
|
||
issuesDetected: 1250,
|
||
},
|
||
{
|
||
id: "rule-4",
|
||
name: "重复数据检测",
|
||
description: "检测并标记重复的用户记录",
|
||
type: "唯一性检查",
|
||
enabled: true,
|
||
severity: "medium",
|
||
dataSource: "所有数据源",
|
||
lastRun: "2023-07-20 00:00",
|
||
issuesDetected: 567,
|
||
},
|
||
{
|
||
id: "rule-5",
|
||
name: "数据一致性检查",
|
||
description: "检查用户在不同系统中的数据是否一致",
|
||
type: "一致性检查",
|
||
enabled: true,
|
||
severity: "high",
|
||
dataSource: "所有数据源",
|
||
lastRun: "2023-07-20 00:00",
|
||
issuesDetected: 567,
|
||
},
|
||
])
|
||
|
||
// 模拟数据质量统计
|
||
const qualityStats = {
|
||
totalIssues: 3087,
|
||
openIssues: 2606,
|
||
resolvedIssues: 481,
|
||
highSeverity: 1245,
|
||
mediumSeverity: 1356,
|
||
lowSeverity: 486,
|
||
dataQualityScore: 87,
|
||
completenessScore: 92,
|
||
accuracyScore: 85,
|
||
consistencyScore: 88,
|
||
validityScore: 84,
|
||
}
|
||
|
||
const getSeverityBadge = (severity: string) => {
|
||
switch (severity) {
|
||
case "high":
|
||
return <Badge className="bg-red-100 text-red-800">高</Badge>
|
||
case "medium":
|
||
return <Badge className="bg-yellow-100 text-yellow-800">中</Badge>
|
||
case "low":
|
||
return <Badge className="bg-green-100 text-green-800">低</Badge>
|
||
default:
|
||
return <Badge>未知</Badge>
|
||
}
|
||
}
|
||
|
||
const getStatusBadge = (status: string) => {
|
||
switch (status) {
|
||
case "open":
|
||
return <Badge className="bg-red-100 text-red-800">未解决</Badge>
|
||
case "in_progress":
|
||
return <Badge className="bg-yellow-100 text-yellow-800">处理中</Badge>
|
||
case "resolved":
|
||
return <Badge className="bg-green-100 text-green-800">已解决</Badge>
|
||
default:
|
||
return <Badge>未知</Badge>
|
||
}
|
||
}
|
||
|
||
const getStatusIcon = (status: string) => {
|
||
switch (status) {
|
||
case "open":
|
||
return <AlertTriangle className="h-5 w-5 text-red-500" />
|
||
case "in_progress":
|
||
return <RefreshCw className="h-5 w-5 text-yellow-500" />
|
||
case "resolved":
|
||
return <CheckCircle className="h-5 w-5 text-green-500" />
|
||
default:
|
||
return <AlertTriangle className="h-5 w-5" />
|
||
}
|
||
}
|
||
|
||
const filteredIssues = dataQualityIssues.filter((issue) => {
|
||
if (filterSeverity !== "all" && issue.severity !== filterSeverity) return false
|
||
if (filterStatus !== "all" && issue.status !== filterStatus) return false
|
||
return true
|
||
})
|
||
|
||
const selectedIssue = selectedIssueId ? dataQualityIssues.find((issue) => issue.id === selectedIssueId) : null
|
||
|
||
const updateIssueStatus = (id: string, status: "open" | "in_progress" | "resolved") => {
|
||
setDataQualityIssues(
|
||
dataQualityIssues.map((issue) => {
|
||
if (issue.id === id) {
|
||
return { ...issue, status }
|
||
}
|
||
return issue
|
||
}),
|
||
)
|
||
}
|
||
|
||
const toggleRuleStatus = (id: string) => {
|
||
setDataQualityRules(
|
||
dataQualityRules.map((rule) => {
|
||
if (rule.id === id) {
|
||
return { ...rule, enabled: !rule.enabled }
|
||
}
|
||
return rule
|
||
}),
|
||
)
|
||
}
|
||
|
||
return (
|
||
<div className="space-y-4">
|
||
<div className="flex justify-between items-center">
|
||
<h2 className="text-2xl font-bold">数据质量监控</h2>
|
||
<div className="flex items-center gap-2">
|
||
<Button variant="outline">
|
||
<Bell className="mr-2 h-4 w-4" />
|
||
通知设置
|
||
</Button>
|
||
<Button>
|
||
<RefreshCw className="mr-2 h-4 w-4" />
|
||
运行检查
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||
<Card>
|
||
<CardContent className="p-6">
|
||
<div className="flex flex-col space-y-2">
|
||
<p className="text-sm text-muted-foreground">数据质量评分</p>
|
||
<div className="flex items-end justify-between">
|
||
<h3 className="text-2xl font-bold">{qualityStats.dataQualityScore}/100</h3>
|
||
</div>
|
||
<div className="w-full bg-gray-200 rounded-full h-2.5">
|
||
<div
|
||
className="bg-green-600 h-2.5 rounded-full"
|
||
style={{ width: `${qualityStats.dataQualityScore}%` }}
|
||
></div>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
<Card>
|
||
<CardContent className="p-6">
|
||
<div className="flex flex-col space-y-2">
|
||
<p className="text-sm text-muted-foreground">未解决问题</p>
|
||
<div className="flex items-center justify-between">
|
||
<h3 className="text-2xl font-bold">{qualityStats.openIssues}</h3>
|
||
<AlertTriangle className="h-5 w-5 text-red-500" />
|
||
</div>
|
||
<p className="text-xs text-muted-foreground">其中高严重性: {qualityStats.highSeverity} 个问题</p>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
<Card>
|
||
<CardContent className="p-6">
|
||
<div className="flex flex-col space-y-2">
|
||
<p className="text-sm text-muted-foreground">处理中问题</p>
|
||
<div className="flex items-center justify-between">
|
||
<h3 className="text-2xl font-bold">
|
||
{dataQualityIssues.filter((issue) => issue.status === "in_progress").length}
|
||
</h3>
|
||
<RefreshCw className="h-5 w-5 text-yellow-500" />
|
||
</div>
|
||
<p className="text-xs text-muted-foreground">正在处理中的数据质量问题</p>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
<Card>
|
||
<CardContent className="p-6">
|
||
<div className="flex flex-col space-y-2">
|
||
<p className="text-sm text-muted-foreground">已解决问题</p>
|
||
<div className="flex items-center justify-between">
|
||
<h3 className="text-2xl font-bold">{qualityStats.resolvedIssues}</h3>
|
||
<CheckCircle className="h-5 w-5 text-green-500" />
|
||
</div>
|
||
<p className="text-xs text-muted-foreground">
|
||
解决率: {((qualityStats.resolvedIssues / qualityStats.totalIssues) * 100).toFixed(1)}%
|
||
</p>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
|
||
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-4">
|
||
<TabsList>
|
||
<TabsTrigger value="issues">数据问题</TabsTrigger>
|
||
<TabsTrigger value="rules">质量规则</TabsTrigger>
|
||
<TabsTrigger value="metrics">质量指标</TabsTrigger>
|
||
<TabsTrigger value="reports">质量报告</TabsTrigger>
|
||
</TabsList>
|
||
|
||
<TabsContent value="issues" className="space-y-4">
|
||
<Card>
|
||
<CardHeader>
|
||
<div className="flex justify-between items-center">
|
||
<CardTitle>数据质量问题</CardTitle>
|
||
<div className="flex items-center gap-2">
|
||
<div className="relative">
|
||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||
<Input type="text" placeholder="搜索问题..." className="pl-8 w-[200px]" />
|
||
</div>
|
||
<Select value={filterSeverity} onValueChange={setFilterSeverity}>
|
||
<SelectTrigger className="w-[130px]">
|
||
<Filter className="mr-2 h-4 w-4" />
|
||
<SelectValue placeholder="严重性" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="all">所有严重性</SelectItem>
|
||
<SelectItem value="high">高</SelectItem>
|
||
<SelectItem value="medium">中</SelectItem>
|
||
<SelectItem value="low">低</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
<Select value={filterStatus} onValueChange={setFilterStatus}>
|
||
<SelectTrigger className="w-[130px]">
|
||
<Filter className="mr-2 h-4 w-4" />
|
||
<SelectValue placeholder="状态" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="all">所有状态</SelectItem>
|
||
<SelectItem value="open">未解决</SelectItem>
|
||
<SelectItem value="in_progress">处理中</SelectItem>
|
||
<SelectItem value="resolved">已解决</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="rounded-md border">
|
||
<Table>
|
||
<TableHeader>
|
||
<TableRow>
|
||
<TableHead className="w-[50px]">状态</TableHead>
|
||
<TableHead>问题类型</TableHead>
|
||
<TableHead>描述</TableHead>
|
||
<TableHead>影响记录数</TableHead>
|
||
<TableHead>严重性</TableHead>
|
||
<TableHead>数据源</TableHead>
|
||
<TableHead>发现时间</TableHead>
|
||
<TableHead className="text-right">操作</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
{filteredIssues.map((issue) => (
|
||
<TableRow key={issue.id}>
|
||
<TableCell>{getStatusIcon(issue.status)}</TableCell>
|
||
<TableCell className="font-medium">{issue.type}</TableCell>
|
||
<TableCell>{issue.description}</TableCell>
|
||
<TableCell>{issue.affectedRecords.toLocaleString()}</TableCell>
|
||
<TableCell>{getSeverityBadge(issue.severity)}</TableCell>
|
||
<TableCell>{issue.dataSource}</TableCell>
|
||
<TableCell>{issue.createdAt}</TableCell>
|
||
<TableCell className="text-right">
|
||
<Button variant="ghost" size="sm" onClick={() => setSelectedIssueId(issue.id)}>
|
||
查看详情
|
||
</Button>
|
||
</TableCell>
|
||
</TableRow>
|
||
))}
|
||
</TableBody>
|
||
</Table>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</TabsContent>
|
||
|
||
<TabsContent value="rules" className="space-y-4">
|
||
<Card>
|
||
<CardHeader>
|
||
<div className="flex justify-between items-center">
|
||
<CardTitle>数据质量规则</CardTitle>
|
||
<Button onClick={() => setIsAddingRule(true)}>添加规则</Button>
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="rounded-md border">
|
||
<Table>
|
||
<TableHeader>
|
||
<TableRow>
|
||
<TableHead>规则名称</TableHead>
|
||
<TableHead>类型</TableHead>
|
||
<TableHead>严重性</TableHead>
|
||
<TableHead>数据源</TableHead>
|
||
<TableHead>上次运行</TableHead>
|
||
<TableHead>检测到的问题</TableHead>
|
||
<TableHead>状态</TableHead>
|
||
<TableHead className="text-right">操作</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
{dataQualityRules.map((rule) => (
|
||
<TableRow key={rule.id}>
|
||
<TableCell className="font-medium">{rule.name}</TableCell>
|
||
<TableCell>{rule.type}</TableCell>
|
||
<TableCell>{getSeverityBadge(rule.severity)}</TableCell>
|
||
<TableCell>{rule.dataSource}</TableCell>
|
||
<TableCell>{rule.lastRun}</TableCell>
|
||
<TableCell>{rule.issuesDetected.toLocaleString()}</TableCell>
|
||
<TableCell>
|
||
<div className="flex items-center space-x-2">
|
||
<Switch
|
||
checked={rule.enabled}
|
||
onCheckedChange={() => toggleRuleStatus(rule.id)}
|
||
aria-label="Toggle rule"
|
||
/>
|
||
<span>{rule.enabled ? "启用" : "禁用"}</span>
|
||
</div>
|
||
</TableCell>
|
||
<TableCell className="text-right">
|
||
<div className="flex justify-end space-x-2">
|
||
<Button variant="ghost" size="sm">
|
||
编辑
|
||
</Button>
|
||
<Button variant="ghost" size="sm">
|
||
运行
|
||
</Button>
|
||
</div>
|
||
</TableCell>
|
||
</TableRow>
|
||
))}
|
||
</TableBody>
|
||
</Table>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</TabsContent>
|
||
|
||
<TabsContent value="metrics" className="space-y-4">
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>数据完整性</CardTitle>
|
||
<CardDescription>数据字段的完整性评分</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="space-y-4">
|
||
<div className="flex justify-between items-center">
|
||
<span className="font-medium">总体完整性评分</span>
|
||
<span className="text-green-600 font-medium">{qualityStats.completenessScore}/100</span>
|
||
</div>
|
||
<div className="w-full bg-gray-200 rounded-full h-2.5">
|
||
<div
|
||
className="bg-green-600 h-2.5 rounded-full"
|
||
style={{ width: `${qualityStats.completenessScore}%` }}
|
||
></div>
|
||
</div>
|
||
|
||
<div className="space-y-2 mt-4">
|
||
<h4 className="text-sm font-medium">关键字段完整性</h4>
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-sm">用户ID</span>
|
||
<span className="text-sm">100%</span>
|
||
</div>
|
||
<div className="w-full bg-gray-200 rounded-full h-1.5">
|
||
<div className="bg-green-600 h-1.5 rounded-full" style={{ width: "100%" }}></div>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-sm">手机号</span>
|
||
<span className="text-sm">87%</span>
|
||
</div>
|
||
<div className="w-full bg-gray-200 rounded-full h-1.5">
|
||
<div className="bg-green-600 h-1.5 rounded-full" style={{ width: "87%" }}></div>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-sm">身份证号</span>
|
||
<span className="text-sm">72%</span>
|
||
</div>
|
||
<div className="w-full bg-gray-200 rounded-full h-1.5">
|
||
<div className="bg-yellow-600 h-1.5 rounded-full" style={{ width: "72%" }}></div>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-sm">IMEI设备号</span>
|
||
<span className="text-sm">68%</span>
|
||
</div>
|
||
<div className="w-full bg-gray-200 rounded-full h-1.5">
|
||
<div className="bg-yellow-600 h-1.5 rounded-full" style={{ width: "68%" }}></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>数据准确性</CardTitle>
|
||
<CardDescription>数据值的准确性评分</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="space-y-4">
|
||
<div className="flex justify-between items-center">
|
||
<span className="font-medium">总体准确性评分</span>
|
||
<span className="text-green-600 font-medium">{qualityStats.accuracyScore}/100</span>
|
||
</div>
|
||
<div className="w-full bg-gray-200 rounded-full h-2.5">
|
||
<div
|
||
className="bg-green-600 h-2.5 rounded-full"
|
||
style={{ width: `${qualityStats.accuracyScore}%` }}
|
||
></div>
|
||
</div>
|
||
|
||
<div className="space-y-2 mt-4">
|
||
<h4 className="text-sm font-medium">字段准确性</h4>
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-sm">手机号格式</span>
|
||
<span className="text-sm">92%</span>
|
||
</div>
|
||
<div className="w-full bg-gray-200 rounded-full h-1.5">
|
||
<div className="bg-green-600 h-1.5 rounded-full" style={{ width: "92%" }}></div>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-sm">身份证号格式</span>
|
||
<span className="text-sm">85%</span>
|
||
</div>
|
||
<div className="w-full bg-gray-200 rounded-full h-1.5">
|
||
<div className="bg-green-600 h-1.5 rounded-full" style={{ width: "85%" }}></div>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-sm">年龄范围</span>
|
||
<span className="text-sm">95%</span>
|
||
</div>
|
||
<div className="w-full bg-gray-200 rounded-full h-1.5">
|
||
<div className="bg-green-600 h-1.5 rounded-full" style={{ width: "95%" }}></div>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-sm">邮箱格式</span>
|
||
<span className="text-sm">78%</span>
|
||
</div>
|
||
<div className="w-full bg-gray-200 rounded-full h-1.5">
|
||
<div className="bg-yellow-600 h-1.5 rounded-full" style={{ width: "78%" }}></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>数据一致性</CardTitle>
|
||
<CardDescription>跨系统数据一致性评分</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="space-y-4">
|
||
<div className="flex justify-between items-center">
|
||
<span className="font-medium">总体一致性评分</span>
|
||
<span className="text-green-600 font-medium">{qualityStats.consistencyScore}/100</span>
|
||
</div>
|
||
<div className="w-full bg-gray-200 rounded-full h-2.5">
|
||
<div
|
||
className="bg-green-600 h-2.5 rounded-full"
|
||
style={{ width: `${qualityStats.consistencyScore}%` }}
|
||
></div>
|
||
</div>
|
||
|
||
<div className="space-y-2 mt-4">
|
||
<h4 className="text-sm font-medium">系统间一致性</h4>
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-sm">CRM与交易系统</span>
|
||
<span className="text-sm">90%</span>
|
||
</div>
|
||
<div className="w-full bg-gray-200 rounded-full h-1.5">
|
||
<div className="bg-green-600 h-1.5 rounded-full" style={{ width: "90%" }}></div>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-sm">用户数据库与行为分析</span>
|
||
<span className="text-sm">85%</span>
|
||
</div>
|
||
<div className="w-full bg-gray-200 rounded-full h-1.5">
|
||
<div className="bg-green-600 h-1.5 rounded-full" style={{ width: "85%" }}></div>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-sm">交易系统与设备管理</span>
|
||
<span className="text-sm">78%</span>
|
||
</div>
|
||
<div className="w-full bg-gray-200 rounded-full h-1.5">
|
||
<div className="bg-yellow-600 h-1.5 rounded-full" style={{ width: "78%" }}></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>数据有效性</CardTitle>
|
||
<CardDescription>数据业务规则有效性评分</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="space-y-4">
|
||
<div className="flex justify-between items-center">
|
||
<span className="font-medium">总体有效性评分</span>
|
||
<span className="text-green-600 font-medium">{qualityStats.validityScore}/100</span>
|
||
</div>
|
||
<div className="w-full bg-gray-200 rounded-full h-2.5">
|
||
<div
|
||
className="bg-green-600 h-2.5 rounded-full"
|
||
style={{ width: `${qualityStats.validityScore}%` }}
|
||
></div>
|
||
</div>
|
||
|
||
<div className="space-y-2 mt-4">
|
||
<h4 className="text-sm font-medium">业务规则有效性</h4>
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-sm">用户注册规则</span>
|
||
<span className="text-sm">92%</span>
|
||
</div>
|
||
<div className="w-full bg-gray-200 rounded-full h-1.5">
|
||
<div className="bg-green-600 h-1.5 rounded-full" style={{ width: "92%" }}></div>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-sm">交易记录规则</span>
|
||
<span className="text-sm">88%</span>
|
||
</div>
|
||
<div className="w-full bg-gray-200 rounded-full h-1.5">
|
||
<div className="bg-green-600 h-1.5 rounded-full" style={{ width: "88%" }}></div>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-sm">用户行为规则</span>
|
||
<span className="text-sm">75%</span>
|
||
</div>
|
||
<div className="w-full bg-gray-200 rounded-full h-1.5">
|
||
<div className="bg-yellow-600 h-1.5 rounded-full" style={{ width: "75%" }}></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
</TabsContent>
|
||
|
||
<TabsContent value="reports" className="space-y-4">
|
||
<Card>
|
||
<CardHeader>
|
||
<div className="flex justify-between items-center">
|
||
<CardTitle>数据质量报告</CardTitle>
|
||
<div className="flex items-center gap-2">
|
||
<Select defaultValue="weekly">
|
||
<SelectTrigger className="w-[150px]">
|
||
<Clock className="mr-2 h-4 w-4" />
|
||
<SelectValue placeholder="报告频率" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="daily">每日报告</SelectItem>
|
||
<SelectItem value="weekly">每周报告</SelectItem>
|
||
<SelectItem value="monthly">每月报告</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
<Button variant="outline">
|
||
<Download className="mr-2 h-4 w-4" />
|
||
导出报告
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="rounded-md border">
|
||
<Table>
|
||
<TableHeader>
|
||
<TableRow>
|
||
<TableHead>报告名称</TableHead>
|
||
<TableHead>生成时间</TableHead>
|
||
<TableHead>质量评分</TableHead>
|
||
<TableHead>问题数量</TableHead>
|
||
<TableHead>严重问题</TableHead>
|
||
<TableHead className="text-right">操作</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
<TableRow>
|
||
<TableCell className="font-medium">每周数据质量报告</TableCell>
|
||
<TableCell>2023-07-21</TableCell>
|
||
<TableCell>87/100</TableCell>
|
||
<TableCell>156</TableCell>
|
||
<TableCell>23</TableCell>
|
||
<TableCell className="text-right">
|
||
<Button variant="ghost" size="sm">
|
||
查看
|
||
</Button>
|
||
</TableCell>
|
||
</TableRow>
|
||
<TableRow>
|
||
<TableCell className="font-medium">每周数据质量报告</TableCell>
|
||
<TableCell>2023-07-14</TableCell>
|
||
<TableCell>85/100</TableCell>
|
||
<TableCell>178</TableCell>
|
||
<TableCell>31</TableCell>
|
||
<TableCell className="text-right">
|
||
<Button variant="ghost" size="sm">
|
||
查看
|
||
</Button>
|
||
</TableCell>
|
||
</TableRow>
|
||
<TableRow>
|
||
<TableCell className="font-medium">每周数据质量报告</TableCell>
|
||
<TableCell>2023-07-07</TableCell>
|
||
<TableCell>82/100</TableCell>
|
||
<TableCell>203</TableCell>
|
||
<TableCell>45</TableCell>
|
||
<TableCell className="text-right">
|
||
<Button variant="ghost" size="sm">
|
||
查看
|
||
</Button>
|
||
</TableCell>
|
||
</TableRow>
|
||
<TableRow>
|
||
<TableCell className="font-medium">每月数据质量报告</TableCell>
|
||
<TableCell>2023-06-30</TableCell>
|
||
<TableCell>80/100</TableCell>
|
||
<TableCell>245</TableCell>
|
||
<TableCell>52</TableCell>
|
||
<TableCell className="text-right">
|
||
<Button variant="ghost" size="sm">
|
||
查看
|
||
</Button>
|
||
</TableCell>
|
||
</TableRow>
|
||
</TableBody>
|
||
</Table>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</TabsContent>
|
||
</Tabs>
|
||
|
||
{/* 问题详情对话框 */}
|
||
<Dialog open={!!selectedIssueId} onOpenChange={(open) => !open && setSelectedIssueId(null)}>
|
||
<DialogContent className="sm:max-w-[600px]">
|
||
<DialogHeader>
|
||
<DialogTitle>问题详情</DialogTitle>
|
||
<DialogDescription>查看和处理数据质量问题</DialogDescription>
|
||
</DialogHeader>
|
||
{selectedIssue && (
|
||
<div className="space-y-4 py-4">
|
||
<div className="flex items-center gap-4">
|
||
{getStatusIcon(selectedIssue.status)}
|
||
<div>
|
||
<h3 className="font-medium text-lg">{selectedIssue.type}</h3>
|
||
<p className="text-sm text-muted-foreground">{selectedIssue.description}</p>
|
||
</div>
|
||
</div>
|
||
|
||
<Separator />
|
||
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div>
|
||
<Label className="text-sm text-muted-foreground">严重性</Label>
|
||
<p className="font-medium">{getSeverityBadge(selectedIssue.severity)}</p>
|
||
</div>
|
||
<div>
|
||
<Label className="text-sm text-muted-foreground">状态</Label>
|
||
<p className="font-medium">{getStatusBadge(selectedIssue.status)}</p>
|
||
</div>
|
||
<div>
|
||
<Label className="text-sm text-muted-foreground">数据源</Label>
|
||
<p className="font-medium">{selectedIssue.dataSource}</p>
|
||
</div>
|
||
<div>
|
||
<Label className="text-sm text-muted-foreground">发现时间</Label>
|
||
<p className="font-medium">{selectedIssue.createdAt}</p>
|
||
</div>
|
||
<div>
|
||
<Label className="text-sm text-muted-foreground">影响记录数</Label>
|
||
<p className="font-medium">{selectedIssue.affectedRecords.toLocaleString()}</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<Label className="text-sm text-muted-foreground">影响字段</Label>
|
||
<div className="flex flex-wrap gap-2 mt-1">
|
||
{selectedIssue.affectedFields.map((field, index) => (
|
||
<Badge key={index} variant="outline">
|
||
{field}
|
||
</Badge>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
<Separator />
|
||
|
||
<div className="space-y-2">
|
||
<Label>处理状态</Label>
|
||
<Select
|
||
value={selectedIssue.status}
|
||
onValueChange={(value) =>
|
||
updateIssueStatus(selectedIssue.id, value as "open" | "in_progress" | "resolved")
|
||
}
|
||
>
|
||
<SelectTrigger>
|
||
<SelectValue placeholder="选择状态" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="open">未解决</SelectItem>
|
||
<SelectItem value="in_progress">处理中</SelectItem>
|
||
<SelectItem value="resolved">已解决</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label>处理备注</Label>
|
||
<Textarea placeholder="输入处理备注..." rows={3} />
|
||
</div>
|
||
</div>
|
||
)}
|
||
<DialogFooter>
|
||
<Button variant="outline" onClick={() => setSelectedIssueId(null)}>
|
||
关闭
|
||
</Button>
|
||
<Button onClick={() => setSelectedIssueId(null)}>保存更改</Button>
|
||
</DialogFooter>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
{/* 添加规则对话框 */}
|
||
<Dialog open={isAddingRule} onOpenChange={setIsAddingRule}>
|
||
<DialogContent className="sm:max-w-[500px]">
|
||
<DialogHeader>
|
||
<DialogTitle>添加数据质量规则</DialogTitle>
|
||
<DialogDescription>创建新的数据质量检查规则</DialogDescription>
|
||
</DialogHeader>
|
||
<div className="grid gap-4 py-4">
|
||
<div className="grid grid-cols-4 items-center gap-4">
|
||
<Label htmlFor="rule-name" className="text-right">
|
||
规则名称
|
||
</Label>
|
||
<Input id="rule-name" className="col-span-3" placeholder="例如:手机号格式检查" />
|
||
</div>
|
||
<div className="grid grid-cols-4 items-center gap-4">
|
||
<Label htmlFor="rule-type" className="text-right">
|
||
规则类型
|
||
</Label>
|
||
<Select>
|
||
<SelectTrigger id="rule-type" className="col-span-3">
|
||
<SelectValue placeholder="选择规则类型" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="format">格式验证</SelectItem>
|
||
<SelectItem value="completeness">完整性检查</SelectItem>
|
||
<SelectItem value="uniqueness">唯一性检查</SelectItem>
|
||
<SelectItem value="consistency">一致性检查</SelectItem>
|
||
<SelectItem value="range">范围检查</SelectItem>
|
||
<SelectItem value="custom">自定义规则</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
<div className="grid grid-cols-4 items-center gap-4">
|
||
<Label htmlFor="rule-severity" className="text-right">
|
||
严重性
|
||
</Label>
|
||
<Select>
|
||
<SelectTrigger id="rule-severity" className="col-span-3">
|
||
<SelectValue placeholder="选择严重性" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="high">高</SelectItem>
|
||
<SelectItem value="medium">中</SelectItem>
|
||
<SelectItem value="low">低</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
<div className="grid grid-cols-4 items-center gap-4">
|
||
<Label htmlFor="rule-datasource" className="text-right">
|
||
数据源
|
||
</Label>
|
||
<Select>
|
||
<SelectTrigger id="rule-datasource" className="col-span-3">
|
||
<SelectValue placeholder="选择数据源" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="all">所有数据源</SelectItem>
|
||
<SelectItem value="crm">CRM系统</SelectItem>
|
||
<SelectItem value="transaction">交易系统</SelectItem>
|
||
<SelectItem value="user-db">用户数据库</SelectItem>
|
||
<SelectItem value="behavior">行为分析平台</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
<div className="grid grid-cols-4 items-start gap-4">
|
||
<Label htmlFor="rule-description" className="text-right pt-2">
|
||
规则描述
|
||
</Label>
|
||
<Textarea
|
||
id="rule-description"
|
||
className="col-span-3"
|
||
placeholder="描述规则的用途和检查内容..."
|
||
rows={3}
|
||
/>
|
||
</div>
|
||
<div className="grid grid-cols-4 items-center gap-4">
|
||
<div className="text-right">
|
||
<Label htmlFor="rule-enabled">启用规则</Label>
|
||
</div>
|
||
<div className="flex items-center space-x-2 col-span-3">
|
||
<Switch id="rule-enabled" defaultChecked />
|
||
<Label htmlFor="rule-enabled">立即启用此规则</Label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<DialogFooter>
|
||
<Button variant="outline" onClick={() => setIsAddingRule(false)}>
|
||
取消
|
||
</Button>
|
||
<Button onClick={() => setIsAddingRule(false)}>创建规则</Button>
|
||
</DialogFooter>
|
||
</DialogContent>
|
||
</Dialog>
|
||
</div>
|
||
)
|
||
}
|