Files
cunkebao_v3/Cunkebao/app/workspace/auto-group/components/traffic-pool-selector.tsx
2025-07-07 17:08:27 +08:00

194 lines
7.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { useState, useEffect } from "react"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { Input } from "@/components/ui/input"
import { Checkbox } from "@/components/ui/checkbox"
import { ScrollArea } from "@/components/ui/scroll-area"
import { ExternalLink, Search, Tag, AlertCircle } from "lucide-react"
import Link from "next/link"
import { Alert, AlertDescription } from "@/components/ui/alert"
// 流量池标签类型定义
interface TrafficTag {
id: string
name: string
count: number
selected?: boolean
}
interface TrafficPoolSelectorProps {
onTagsSelected: (tags: TrafficTag[]) => void
selectedDevices: string[] // 已选择的设备ID列表
}
export function TrafficPoolSelector({ onTagsSelected, selectedDevices }: TrafficPoolSelectorProps) {
// 模拟流量池标签数据
const [tags, setTags] = useState<TrafficTag[]>([])
const [searchQuery, setSearchQuery] = useState("")
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [filteredDeviceCount, setFilteredDeviceCount] = useState<Record<string, number>>({})
useEffect(() => {
const fetchTags = async () => {
setLoading(true)
try {
// 模拟从API获取标签数据
await new Promise((resolve) => setTimeout(resolve, 800))
// 模拟数据
const mockTags = [
{ id: "1", name: "意向客户", count: 120 },
{ id: "2", name: "高净值", count: 85 },
{ id: "3", name: "潜在客户", count: 210 },
{ id: "4", name: "已成交", count: 65 },
{ id: "5", name: "待跟进", count: 178 },
{ id: "6", name: "活跃用户", count: 156 },
{ id: "7", name: "新客户", count: 92 },
{ id: "8", name: "老客户", count: 143 },
{ id: "9", name: "企业客户", count: 76 },
{ id: "10", name: "个人客户", count: 189 },
]
setTags(mockTags)
// 模拟计算每个标签在已选设备中的匹配数量
const deviceCounts: Record<string, number> = {}
mockTags.forEach((tag) => {
// 随机生成一个不超过已选设备数量的数字
const matchCount = Math.min(Math.floor(Math.random() * selectedDevices.length), selectedDevices.length)
deviceCounts[tag.id] = matchCount
})
setFilteredDeviceCount(deviceCounts)
} catch (error) {
console.error("获取流量池标签失败:", error)
setError("获取流量池标签失败,请稍后重试")
} finally {
setLoading(false)
}
}
if (selectedDevices.length > 0) {
fetchTags()
}
}, [selectedDevices])
// 处理标签选择
const handleTagToggle = (tagId: string) => {
const updatedTags = tags.map((tag) => (tag.id === tagId ? { ...tag, selected: !tag.selected } : tag))
setTags(updatedTags)
// 通知父组件选中的标签
const selectedTags = updatedTags.filter((tag) => tag.selected)
onTagsSelected(selectedTags)
}
// 筛选标签
const filteredTags = tags.filter((tag) => tag.name.toLowerCase().includes(searchQuery.toLowerCase()))
// 选中的标签数量
const selectedCount = tags.filter((tag) => tag.selected).length
return (
<Card className="w-full">
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-lg font-medium"></CardTitle>
<Link href="/traffic-pool" className="text-sm text-blue-600 flex items-center gap-1">
<span></span>
<ExternalLink className="h-4 w-4" />
</Link>
</CardHeader>
<CardContent>
{selectedDevices.length === 0 ? (
<Alert variant="warning" className="mb-4">
<AlertCircle className="h-4 w-4" />
<AlertDescription></AlertDescription>
</Alert>
) : (
<>
<div className="relative mb-4">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-gray-500" />
<Input
type="search"
placeholder="搜索标签..."
className="pl-8"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
<div className="mb-4">
<p className="text-sm text-gray-500 mb-2">
<Badge variant="outline">{selectedCount}</Badge>
</p>
</div>
{loading ? (
<div className="flex justify-center items-center py-8">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-500"></div>
<span className="ml-2">...</span>
</div>
) : error ? (
<Alert variant="destructive" className="mb-4">
<AlertCircle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
) : (
<ScrollArea className="h-[300px] pr-4">
<div className="space-y-2">
{filteredTags.map((tag) => (
<div
key={tag.id}
className={`flex items-center justify-between p-2 border rounded-md ${tag.selected ? "bg-blue-50 border-blue-200" : "bg-white"}`}
>
<div className="flex items-center gap-2">
<Checkbox
id={`tag-${tag.id}`}
checked={tag.selected}
onCheckedChange={() => handleTagToggle(tag.id)}
/>
<label htmlFor={`tag-${tag.id}`} className="flex items-center gap-2 cursor-pointer text-sm">
<Tag className="h-3.5 w-3.5 text-gray-500" />
{tag.name}
</label>
</div>
<div className="flex items-center gap-2">
<Badge variant="outline" className="text-xs">
{tag.count}
</Badge>
<Badge variant="secondary" className="text-xs">
: {filteredDeviceCount[tag.id] || 0}
</Badge>
</div>
</div>
))}
{filteredTags.length === 0 && <p className="text-center py-4 text-gray-500"></p>}
</div>
</ScrollArea>
)}
<div className="mt-4 pt-2 border-t">
<Button
variant="outline"
className="w-full"
onClick={() => {
const selectedTags = tags.filter((tag) => tag.selected)
onTagsSelected(selectedTags)
}}
>
</Button>
</div>
</>
)}
</CardContent>
</Card>
)
}