Files
cunkebao_v3/Cunkebao/app/workspace/auto-group/components/device-selection.tsx
2025-04-02 16:00:10 +08:00

330 lines
13 KiB
TypeScript
Executable File

"use client"
import { useState, useEffect, useRef } from "react"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Checkbox } from "@/components/ui/checkbox"
import { Badge } from "@/components/ui/badge"
import { Search, CheckCircle2, AlertCircle, Smartphone, Laptop } from "lucide-react"
import { ScrollArea } from "@/components/ui/scroll-area"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
// 模拟设备数据
const mockDevices = Array.from({ length: 20 }).map((_, i) => ({
id: `device-${i + 1}`,
name: `设备 ${i + 1}`,
model: i % 3 === 0 ? "iPhone 13" : i % 3 === 1 ? "Xiaomi 12" : "Huawei P40",
status: i % 5 === 0 ? "offline" : i % 7 === 0 ? "busy" : "online",
account: `wxid_${100 + i}`,
type: i % 2 === 0 ? "mobile" : "emulator",
group: i % 3 === 0 ? "常用设备" : i % 3 === 1 ? "备用设备" : "测试设备",
successRate: Math.floor(70 + Math.random() * 30),
restrictCount: Math.floor(Math.random() * 5),
}))
interface DeviceSelectionProps {
onNext: () => void
onPrevious: () => void
initialSelectedDevices?: string[]
onDevicesChange: (deviceIds: string[]) => void
}
export function DeviceSelection({
onNext,
onPrevious,
initialSelectedDevices = [],
onDevicesChange,
}: DeviceSelectionProps) {
const [selectedDevices, setSelectedDevices] = useState<string[]>(initialSelectedDevices)
const [searchQuery, setSearchQuery] = useState("")
const [statusFilter, setStatusFilter] = useState<string>("all")
const [typeFilter, setTypeFilter] = useState<string>("all")
const [groupFilter, setGroupFilter] = useState<string>("all")
// 使用ref来跟踪是否已经通知了父组件初始选择
const initialNotificationRef = useRef(false)
const deviceGroups = Array.from(new Set(mockDevices.map((device) => device.group)))
const filteredDevices = mockDevices.filter((device) => {
const matchesSearch =
device.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
device.account.toLowerCase().includes(searchQuery.toLowerCase())
const matchesStatus = statusFilter === "all" || device.status === statusFilter
const matchesType = typeFilter === "all" || device.type === typeFilter
const matchesGroup = groupFilter === "all" || device.group === groupFilter
return matchesSearch && matchesStatus && matchesType && matchesGroup
})
// 只在选择变化时通知父组件,使用防抖
useEffect(() => {
if (!initialNotificationRef.current) {
initialNotificationRef.current = true
return
}
const timer = setTimeout(() => {
onDevicesChange(selectedDevices)
}, 300)
return () => clearTimeout(timer)
}, [selectedDevices, onDevicesChange])
const handleDeviceToggle = (deviceId: string) => {
setSelectedDevices((prev) => (prev.includes(deviceId) ? prev.filter((id) => id !== deviceId) : [...prev, deviceId]))
}
const handleSelectAll = () => {
setSelectedDevices(filteredDevices.map((device) => device.id))
}
const handleDeselectAll = () => {
setSelectedDevices([])
}
const getStatusColor = (status: string) => {
switch (status) {
case "online":
return "bg-green-500"
case "offline":
return "bg-gray-500"
case "busy":
return "bg-yellow-500"
default:
return "bg-gray-500"
}
}
const getStatusText = (status: string) => {
switch (status) {
case "online":
return "在线"
case "offline":
return "离线"
case "busy":
return "繁忙"
default:
return status
}
}
return (
<div className="space-y-6">
<Card>
<CardContent className="pt-6">
<div className="space-y-4">
<div className="flex items-center space-x-2">
<div className="relative flex-1">
<Search className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
<Input
placeholder="搜索设备名称或账号"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9"
/>
</div>
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger className="w-[120px]">
<SelectValue placeholder="状态" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
<SelectItem value="online">线</SelectItem>
<SelectItem value="offline">线</SelectItem>
<SelectItem value="busy"></SelectItem>
</SelectContent>
</Select>
<Select value={typeFilter} onValueChange={setTypeFilter}>
<SelectTrigger className="w-[120px]">
<SelectValue placeholder="设备类型" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
<SelectItem value="mobile"></SelectItem>
<SelectItem value="emulator"></SelectItem>
</SelectContent>
</Select>
<Select value={groupFilter} onValueChange={setGroupFilter}>
<SelectTrigger className="w-[120px]">
<SelectValue placeholder="设备分组" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
{deviceGroups.map((group) => (
<SelectItem key={group} value={group}>
{group}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex items-center justify-between">
<div className="text-sm text-gray-500">
<span className="font-medium text-blue-600">{selectedDevices.length}</span>
</div>
<div className="space-x-2">
<Button variant="outline" size="sm" onClick={handleSelectAll}>
</Button>
<Button variant="outline" size="sm" onClick={handleDeselectAll}>
</Button>
</div>
</div>
<Tabs defaultValue="list" className="w-full">
<TabsList className="grid w-60 grid-cols-2">
<TabsTrigger value="list"></TabsTrigger>
<TabsTrigger value="grid"></TabsTrigger>
</TabsList>
<TabsContent value="list" className="mt-2">
<ScrollArea className="h-[400px] rounded-md border">
<div className="divide-y">
{filteredDevices.map((device) => (
<div key={device.id} className="flex items-center justify-between p-3 hover:bg-gray-50">
<div className="flex items-center space-x-3">
<Checkbox
id={device.id}
checked={selectedDevices.includes(device.id)}
onCheckedChange={() => handleDeviceToggle(device.id)}
disabled={device.status === "offline"}
/>
{device.type === "mobile" ? (
<Smartphone className="h-5 w-5 text-gray-500" />
) : (
<Laptop className="h-5 w-5 text-gray-500" />
)}
<div>
<div className="font-medium">{device.name}</div>
<div className="text-sm text-gray-500">
{device.model} | {device.account}
</div>
</div>
</div>
<div className="flex items-center space-x-3">
<div className="text-sm">
:{" "}
<span className={device.successRate > 90 ? "text-green-600" : "text-yellow-600"}>
{device.successRate}%
</span>
</div>
<div className="text-sm">
:{" "}
<span className={device.restrictCount === 0 ? "text-green-600" : "text-red-600"}>
{device.restrictCount}
</span>
</div>
<Badge className={`${getStatusColor(device.status)} text-white`}>
{getStatusText(device.status)}
</Badge>
</div>
</div>
))}
{filteredDevices.length === 0 && (
<div className="p-4 text-center text-gray-500"></div>
)}
</div>
</ScrollArea>
</TabsContent>
<TabsContent value="grid" className="mt-2">
<ScrollArea className="h-[400px] rounded-md border">
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 p-4">
{filteredDevices.map((device) => (
<div
key={device.id}
className={`border rounded-md p-3 ${
selectedDevices.includes(device.id) ? "border-blue-500 bg-blue-50" : ""
} ${device.status === "offline" ? "opacity-60" : ""}`}
>
<div className="flex justify-between items-start mb-2">
<div className="flex items-center">
{device.type === "mobile" ? (
<Smartphone className="h-4 w-4 text-gray-500 mr-1" />
) : (
<Laptop className="h-4 w-4 text-gray-500 mr-1" />
)}
<span className="font-medium truncate">{device.name}</span>
</div>
<Badge className={`${getStatusColor(device.status)} text-white text-xs`}>
{getStatusText(device.status)}
</Badge>
</div>
<div className="text-xs text-gray-500 mb-2">{device.model}</div>
<div className="text-xs text-gray-500 mb-3">{device.account}</div>
<div className="flex justify-between text-xs mb-3">
<div>
:{" "}
<span className={device.successRate > 90 ? "text-green-600" : "text-yellow-600"}>
{device.successRate}%
</span>
</div>
<div>
:{" "}
<span className={device.restrictCount === 0 ? "text-green-600" : "text-red-600"}>
{device.restrictCount}
</span>
</div>
</div>
<Button
variant={selectedDevices.includes(device.id) ? "default" : "outline"}
size="sm"
className="w-full"
onClick={() => handleDeviceToggle(device.id)}
disabled={device.status === "offline"}
>
{selectedDevices.includes(device.id) ? (
<>
<CheckCircle2 className="h-4 w-4 mr-1" />
</>
) : (
"选择设备"
)}
</Button>
</div>
))}
{filteredDevices.length === 0 && (
<div className="col-span-full p-4 text-center text-gray-500"></div>
)}
</div>
</ScrollArea>
</TabsContent>
</Tabs>
{selectedDevices.length === 0 && (
<div className="flex items-center p-3 bg-yellow-50 rounded-md text-yellow-800">
<AlertCircle className="h-4 w-4 mr-2" />
</div>
)}
</div>
</CardContent>
</Card>
<div className="flex justify-between">
<Button variant="outline" onClick={onPrevious}>
</Button>
<Button onClick={onNext} className="bg-blue-500 hover:bg-blue-600" disabled={selectedDevices.length === 0}>
</Button>
</div>
</div>
)
}