186 lines
6.2 KiB
TypeScript
186 lines
6.2 KiB
TypeScript
|
|
"use client"
|
||
|
|
|
||
|
|
import { useState } from "react"
|
||
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||
|
|
import { Button } from "@/components/ui/button"
|
||
|
|
import { Badge } from "@/components/ui/badge"
|
||
|
|
import { Input } from "@/components/ui/input"
|
||
|
|
import { Edit, ExternalLink, MoreHorizontal, Search, RefreshCw } from "lucide-react"
|
||
|
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
|
||
|
|
|
||
|
|
interface DataSource {
|
||
|
|
id: string
|
||
|
|
name: string
|
||
|
|
type: "api" | "file" | "database" | "third-party"
|
||
|
|
status: "active" | "inactive" | "error" | "pending"
|
||
|
|
lastSync: Date | null
|
||
|
|
recordCount: number
|
||
|
|
createdAt: Date
|
||
|
|
}
|
||
|
|
|
||
|
|
export function DataSourceList() {
|
||
|
|
const [searchQuery, setSearchQuery] = useState("")
|
||
|
|
|
||
|
|
// 模拟数据
|
||
|
|
const dataSources: DataSource[] = [
|
||
|
|
{
|
||
|
|
id: "1",
|
||
|
|
name: "CRM系统用户数据",
|
||
|
|
type: "api",
|
||
|
|
status: "active",
|
||
|
|
lastSync: new Date(2023, 6, 15, 14, 30),
|
||
|
|
recordCount: 45892,
|
||
|
|
createdAt: new Date(2023, 3, 10),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: "2",
|
||
|
|
name: "电商平台订单数据",
|
||
|
|
type: "api",
|
||
|
|
status: "active",
|
||
|
|
lastSync: new Date(2023, 6, 15, 10, 15),
|
||
|
|
recordCount: 128456,
|
||
|
|
createdAt: new Date(2023, 2, 5),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: "3",
|
||
|
|
name: "营销活动参与用户",
|
||
|
|
type: "file",
|
||
|
|
status: "active",
|
||
|
|
lastSync: new Date(2023, 6, 14, 9, 45),
|
||
|
|
recordCount: 8754,
|
||
|
|
createdAt: new Date(2023, 5, 20),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: "4",
|
||
|
|
name: "APP用户行为数据",
|
||
|
|
type: "api",
|
||
|
|
status: "error",
|
||
|
|
lastSync: new Date(2023, 6, 10, 16, 20),
|
||
|
|
recordCount: 65432,
|
||
|
|
createdAt: new Date(2023, 1, 15),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: "5",
|
||
|
|
name: "社交媒体用户数据",
|
||
|
|
type: "third-party",
|
||
|
|
status: "active",
|
||
|
|
lastSync: new Date(2023, 6, 15, 8, 0),
|
||
|
|
recordCount: 32145,
|
||
|
|
createdAt: new Date(2023, 4, 25),
|
||
|
|
},
|
||
|
|
]
|
||
|
|
|
||
|
|
// 过滤数据源
|
||
|
|
const filteredDataSources = dataSources.filter(
|
||
|
|
(source) =>
|
||
|
|
source.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||
|
|
source.type.toLowerCase().includes(searchQuery.toLowerCase()),
|
||
|
|
)
|
||
|
|
|
||
|
|
const formatDate = (date: Date | null) => {
|
||
|
|
if (!date) return "从未同步"
|
||
|
|
return date.toLocaleString("zh-CN", {
|
||
|
|
year: "numeric",
|
||
|
|
month: "2-digit",
|
||
|
|
day: "2-digit",
|
||
|
|
hour: "2-digit",
|
||
|
|
minute: "2-digit",
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
const getStatusBadge = (status: DataSource["status"]) => {
|
||
|
|
switch (status) {
|
||
|
|
case "active":
|
||
|
|
return <Badge className="bg-green-100 text-green-800">正常</Badge>
|
||
|
|
case "inactive":
|
||
|
|
return <Badge className="bg-gray-100 text-gray-800">未启用</Badge>
|
||
|
|
case "error":
|
||
|
|
return <Badge className="bg-red-100 text-red-800">错误</Badge>
|
||
|
|
case "pending":
|
||
|
|
return <Badge className="bg-yellow-100 text-yellow-800">待配置</Badge>
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const getTypeIcon = (type: DataSource["type"]) => {
|
||
|
|
switch (type) {
|
||
|
|
case "api":
|
||
|
|
return <Badge className="bg-blue-100 text-blue-800">API接口</Badge>
|
||
|
|
case "file":
|
||
|
|
return <Badge className="bg-purple-100 text-purple-800">文件导入</Badge>
|
||
|
|
case "database":
|
||
|
|
return <Badge className="bg-green-100 text-green-800">数据库</Badge>
|
||
|
|
case "third-party":
|
||
|
|
return <Badge className="bg-orange-100 text-orange-800">第三方平台</Badge>
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="space-y-4">
|
||
|
|
<div className="flex justify-between items-center">
|
||
|
|
<div className="relative w-64">
|
||
|
|
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
||
|
|
<Input
|
||
|
|
placeholder="搜索数据源..."
|
||
|
|
className="pl-8"
|
||
|
|
value={searchQuery}
|
||
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
<Button variant="outline" size="sm">
|
||
|
|
<RefreshCw className="h-4 w-4 mr-2" />
|
||
|
|
刷新状态
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<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>
|
||
|
|
{filteredDataSources.map((source) => (
|
||
|
|
<TableRow key={source.id}>
|
||
|
|
<TableCell className="font-medium">{source.name}</TableCell>
|
||
|
|
<TableCell>{getTypeIcon(source.type)}</TableCell>
|
||
|
|
<TableCell>{getStatusBadge(source.status)}</TableCell>
|
||
|
|
<TableCell>{formatDate(source.lastSync)}</TableCell>
|
||
|
|
<TableCell>{source.recordCount.toLocaleString()}</TableCell>
|
||
|
|
<TableCell className="text-right">
|
||
|
|
<div className="flex justify-end space-x-2">
|
||
|
|
<Button variant="ghost" size="icon">
|
||
|
|
<Edit className="h-4 w-4" />
|
||
|
|
</Button>
|
||
|
|
<Button variant="ghost" size="icon">
|
||
|
|
<ExternalLink className="h-4 w-4" />
|
||
|
|
</Button>
|
||
|
|
<DropdownMenu>
|
||
|
|
<DropdownMenuTrigger asChild>
|
||
|
|
<Button variant="ghost" size="icon">
|
||
|
|
<MoreHorizontal className="h-4 w-4" />
|
||
|
|
</Button>
|
||
|
|
</DropdownMenuTrigger>
|
||
|
|
<DropdownMenuContent align="end">
|
||
|
|
<DropdownMenuItem>立即同步</DropdownMenuItem>
|
||
|
|
<DropdownMenuItem>查看日志</DropdownMenuItem>
|
||
|
|
<DropdownMenuItem>复制配置</DropdownMenuItem>
|
||
|
|
<DropdownMenuItem className="text-red-600">删除</DropdownMenuItem>
|
||
|
|
</DropdownMenuContent>
|
||
|
|
</DropdownMenu>
|
||
|
|
</div>
|
||
|
|
</TableCell>
|
||
|
|
</TableRow>
|
||
|
|
))}
|
||
|
|
</TableBody>
|
||
|
|
</Table>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|