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

342 lines
13 KiB
TypeScript
Executable File
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 { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Search, Filter, RefreshCw } from "lucide-react"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Checkbox } from "@/components/ui/checkbox"
import { Card } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { ScrollArea } from "@/components/ui/scroll-area"
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
interface WechatAccount {
wechatId: string
nickname: string
remainingAdds: number
maxDailyAdds: number
todayAdded: number
}
interface Device {
id: string
imei: string
name: string
status: "online" | "offline"
wechatAccounts: WechatAccount[]
usedInPlans: number
tags?: string[]
}
interface DeviceSelectionDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
selectedDevices: string[]
onSelect: (deviceIds: string[]) => void
excludeUsedDevices?: boolean
}
export function DeviceSelectionDialog({
open,
onOpenChange,
selectedDevices,
onSelect,
excludeUsedDevices = false,
}: DeviceSelectionDialogProps) {
const [devices, setDevices] = useState<Device[]>([])
const [loading, setLoading] = useState(false)
const [searchQuery, setSearchQuery] = useState("")
const [statusFilter, setStatusFilter] = useState("all")
const [tagFilter, setTagFilter] = useState("all")
const [selectedDeviceIds, setSelectedDeviceIds] = useState<string[]>([])
const [activeTab, setActiveTab] = useState("all")
// 初始化已选设备
useEffect(() => {
if (open) {
setSelectedDeviceIds(selectedDevices)
}
}, [open, selectedDevices])
// 模拟获取设备数据
useEffect(() => {
if (!open) return
const fetchDevices = async () => {
setLoading(true)
try {
// 模拟API请求
await new Promise((resolve) => setTimeout(resolve, 800))
// 生成模拟数据
const deviceTags = ["高性能", "稳定", "新设备", "已配置", "测试中", "备用"]
const mockDevices: Device[] = Array.from({ length: 30 }, (_, i) => {
// 随机生成1-3个标签
const tags = Array.from(
{ length: Math.floor(Math.random() * 3) + 1 },
() => deviceTags[Math.floor(Math.random() * deviceTags.length)],
)
// 确保标签唯一
const uniqueTags = Array.from(new Set(tags))
return {
id: `device-${i + 1}`,
imei: `IMEI-${Math.random().toString(36).substr(2, 9)}`,
name: `设备 ${i + 1}`,
status: Math.random() > 0.3 ? "online" : "offline",
wechatAccounts: Array.from({ length: Math.floor(Math.random() * 2) + 1 }, (_, j) => ({
wechatId: `wxid_${Math.random().toString(36).substr(2, 8)}`,
nickname: `微信号 ${j + 1}`,
remainingAdds: Math.floor(Math.random() * 10) + 5,
maxDailyAdds: 20,
todayAdded: Math.floor(Math.random() * 15),
})),
usedInPlans: Math.floor(Math.random() * 3),
tags: uniqueTags,
}
})
setDevices(mockDevices)
} catch (error) {
console.error("Failed to fetch devices:", error)
} finally {
setLoading(false)
}
}
fetchDevices()
}, [open])
// 过滤设备
const filteredDevices = devices.filter((device) => {
const matchesSearch =
searchQuery === "" ||
device.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
device.imei.toLowerCase().includes(searchQuery.toLowerCase()) ||
device.wechatAccounts.some(
(account) =>
account.wechatId.toLowerCase().includes(searchQuery.toLowerCase()) ||
account.nickname.toLowerCase().includes(searchQuery.toLowerCase()),
)
const matchesStatus = statusFilter === "all" || device.status === statusFilter
const matchesUsage = !excludeUsedDevices || device.usedInPlans === 0
const matchesTag = tagFilter === "all" || (device.tags && device.tags.includes(tagFilter))
const matchesTab =
activeTab === "all" ||
(activeTab === "online" && device.status === "online") ||
(activeTab === "offline" && device.status === "offline") ||
(activeTab === "unused" && device.usedInPlans === 0)
return matchesSearch && matchesStatus && matchesUsage && matchesTag && matchesTab
})
// 处理选择设备
const handleSelectDevice = (deviceId: string) => {
setSelectedDeviceIds((prev) =>
prev.includes(deviceId) ? prev.filter((id) => id !== deviceId) : [...prev, deviceId],
)
}
// 处理全选
const handleSelectAll = () => {
if (selectedDeviceIds.length === filteredDevices.length) {
setSelectedDeviceIds([])
} else {
setSelectedDeviceIds(filteredDevices.map((device) => device.id))
}
}
// 处理确认选择
const handleConfirm = () => {
onSelect(selectedDeviceIds)
onOpenChange(false)
}
// 获取所有标签选项
const allTags = Array.from(new Set(devices.flatMap((device) => device.tags || [])))
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-3xl max-h-[90vh] flex flex-col">
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
<div className="flex-1 overflow-hidden flex flex-col">
{/* 搜索和筛选区域 */}
<div className="space-y-4 mb-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="搜索设备IMEI/备注/微信号"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9"
/>
</div>
<Button variant="outline" size="icon">
<Filter className="h-4 w-4" />
</Button>
<Button variant="outline" size="icon">
<RefreshCw className="h-4 w-4" />
</Button>
</div>
{/* 分类标签页 */}
<Tabs defaultValue="all" value={activeTab} onValueChange={setActiveTab}>
<TabsList className="grid w-full grid-cols-4">
<TabsTrigger value="all"></TabsTrigger>
<TabsTrigger value="online">线</TabsTrigger>
<TabsTrigger value="offline">线</TabsTrigger>
<TabsTrigger value="unused">使</TabsTrigger>
</TabsList>
</Tabs>
{/* 筛选器 */}
<div className="flex space-x-2">
<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>
</SelectContent>
</Select>
{allTags.length > 0 && (
<Select value={tagFilter} onValueChange={setTagFilter}>
<SelectTrigger className="w-[120px]">
<SelectValue placeholder="标签" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
{allTags.map((tag) => (
<SelectItem key={tag} value={tag}>
{tag}
</SelectItem>
))}
</SelectContent>
</Select>
)}
<Button variant="outline" className="ml-auto" onClick={handleSelectAll}>
{selectedDeviceIds.length === filteredDevices.length && filteredDevices.length > 0
? "取消全选"
: "全选"}
</Button>
</div>
</div>
{/* 设备列表 */}
<ScrollArea className="flex-1">
{loading ? (
<div className="flex items-center justify-center h-40">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
</div>
) : filteredDevices.length === 0 ? (
<div className="text-center py-8 text-gray-500">
{searchQuery || statusFilter !== "all" || tagFilter !== "all" || activeTab !== "all"
? "没有符合条件的设备"
: "暂无设备数据"}
</div>
) : (
<div className="space-y-2 pr-4">
{filteredDevices.map((device) => (
<Card
key={device.id}
className={`p-3 hover:shadow-md transition-shadow ${
selectedDeviceIds.includes(device.id) ? "border-primary" : ""
}`}
>
<div className="flex items-center space-x-3">
<Checkbox
checked={selectedDeviceIds.includes(device.id)}
onCheckedChange={() => handleSelectDevice(device.id)}
id={`device-${device.id}`}
/>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between mb-1">
<label htmlFor={`device-${device.id}`} className="font-medium truncate cursor-pointer">
{device.name}
</label>
<div
className={`px-2 py-1 rounded-full text-xs ${
device.status === "online" ? "bg-green-100 text-green-800" : "bg-gray-100 text-gray-800"
}`}
>
{device.status === "online" ? "在线" : "离线"}
</div>
</div>
<div className="text-sm text-gray-500">IMEI: {device.imei}</div>
{/* 微信账号信息 */}
<div className="mt-2 space-y-2">
{device.wechatAccounts.map((account) => (
<div key={account.wechatId} className="bg-gray-50 rounded-lg p-2">
<div className="flex items-center justify-between text-sm">
<span className="font-medium">{account.nickname}</span>
<span className="text-gray-500">{account.wechatId}</span>
</div>
<div className="mt-1">
<div className="flex items-center justify-between text-sm">
<span>{account.remainingAdds}</span>
<span className="text-sm text-gray-500">
{account.todayAdded}/{account.maxDailyAdds}
</span>
</div>
</div>
</div>
))}
</div>
{/* 标签展示 */}
{device.tags && device.tags.length > 0 && (
<div className="flex flex-wrap gap-1 mt-2">
{device.tags.map((tag) => (
<Badge key={tag} variant="outline" className="text-xs">
{tag}
</Badge>
))}
</div>
)}
{device.usedInPlans > 0 && (
<div className="text-sm text-orange-500 mt-2"> {device.usedInPlans} </div>
)}
</div>
</div>
</Card>
))}
</div>
)}
</ScrollArea>
</div>
<DialogFooter className="flex items-center justify-between pt-4 border-t">
<div className="text-sm">
<span className="font-medium text-primary">{selectedDeviceIds.length}</span>
</div>
<div className="space-x-2">
<Button variant="outline" onClick={() => onOpenChange(false)}>
</Button>
<Button onClick={handleConfirm}></Button>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
)
}