"use client" import { useState, useCallback, useRef, useEffect } from "react" import { useRouter, useSearchParams } from "next/navigation" import { Card } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { ChevronLeft, Filter, Search, RefreshCw, Tag } from "lucide-react" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs" import { Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, } from "@/components/ui/pagination" import { useToast } from "@/components/ui/use-toast" import { useDebounce } from "@/hooks/use-debounce" import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { Badge } from "@/components/ui/badge" import { api } from "@/lib/api" interface UserTag { id: string name: string color: string } interface TrafficUser { id: string avatar: string nickname: string wechatId: string phone: string region: string note: string status: number addTime: string source: string assignedTo: string category: "potential" | "customer" | "lost" tags: UserTag[] } interface StatusType { id: number name: string code: string } interface SourceType { id: number name: string } interface ApiResponse { code: number msg: string data: T } // 修改流量池数据类型定义 interface TrafficPoolUser { id: string avatar: string nickname: string name: string wechatId: string phone: string region: string note: string status: number createTime: string fromd: string assignedTo: string category: "potential" | "customer" | "lost" tags: UserTag[] } interface TrafficPoolResponse { list: TrafficPoolUser[] pagination: { total: number current: number pageSize: number totalPages: number } } interface Statistics { totalCount: number todayAddCount: number } interface ConvertedUser { id: number nickname: string avatar: string wechatId: string fromd: string tags: string[] createTime: string status: number } interface ConvertedResponse { list: ConvertedUser[] total: number } export default function TrafficPoolPage() { const router = useRouter() const searchParams = useSearchParams() const [users, setUsers] = useState([]) const [loading, setLoading] = useState(true) const [activeCategory, setActiveCategory] = useState("potential") const [sourceFilter, setSourceFilter] = useState("all") const [statusTypes, setStatusTypes] = useState([]) const [sourceTypes, setSourceTypes] = useState([]) const [statusFilter, setStatusFilter] = useState("all") const [searchQuery, setSearchQuery] = useState("") const [currentPage, setCurrentPage] = useState(1) const [hasMore, setHasMore] = useState(true) const [isFetching, setIsFetching] = useState(false) const [stats, setStats] = useState({ totalCount: 0, todayAddCount: 0 }) const [selectedUser, setSelectedUser] = useState(null) const [showUserDetail, setShowUserDetail] = useState(false) const { toast } = useToast() const observerRef = useRef(null) const loadingRef = useRef(null) const abortControllerRef = useRef(null) const debouncedSearchQuery = useDebounce(searchQuery, 300) // 添加格式化时间的函数 const formatDateTime = (dateString: string) => { if (!dateString) return '--'; try { const date = new Date(dateString); return date.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }).replace(/\//g, '-'); } catch (error) { return dateString; } }; const fetchUsers = useCallback(async (page: number = 1, isNewSearch: boolean = false) => { try { if (abortControllerRef.current) { abortControllerRef.current.abort() } abortControllerRef.current = new AbortController() setIsFetching(true) const params = new URLSearchParams({ page: page.toString(), limit: "30" }) if (debouncedSearchQuery) { params.append("keyword", debouncedSearchQuery) } if (sourceFilter !== "all") { const selectedSource = sourceTypes.find(source => source.id.toString() === sourceFilter) if (selectedSource) { params.append("fromd", selectedSource.name) } } if (statusFilter !== "all") { params.append("status", statusFilter) } const endpoint = activeCategory === "customer" ? '/v1/traffic/pool/converted' : '/v1/traffic/pool' const response = await api.get>( `${endpoint}?${params.toString()}`, { headers: { Authorization: `Bearer ${localStorage.getItem('token')}` } } as any ) if (response.code === 200) { const { list } = response.data const transformedUsers: TrafficUser[] = list.map((user: any) => ({ id: user.id.toString(), avatar: user.avatar, nickname: user.nickname || user.name || '未知用户', wechatId: user.wechatId, phone: user.phone || '', region: user.region || '', note: user.note || '', status: activeCategory === "customer" ? 3 : user.status, addTime: formatDateTime(user.createTime), source: user.fromd || '未知来源', assignedTo: user.assignedTo || '', category: activeCategory as "potential" | "customer" | "lost", tags: Array.isArray(user.tags) ? user.tags.map((tag: string) => ({ id: tag, name: tag, color: 'bg-blue-100 text-blue-800' })) : [] })) setUsers(prev => isNewSearch ? transformedUsers : [...prev, ...transformedUsers]) setCurrentPage(page) setHasMore(list.length === 30) } else { toast({ title: "获取数据失败", description: response.msg || "请稍后重试", variant: "destructive", }) } } catch (error) { if (error instanceof Error && error.name === "AbortError") { return } toast({ title: "获取数据失败", description: "请检查网络连接或稍后重试", variant: "destructive", }) } finally { setIsFetching(false) setLoading(false) } }, [debouncedSearchQuery, sourceFilter, statusFilter, sourceTypes, activeCategory]) const fetchStatusTypes = useCallback(async () => { try { const response = await api.get>('/v1/traffic/pool/types', { headers: { Authorization: `Bearer ${localStorage.getItem('token')}` } } as any) if (response.code === 200) { setStatusTypes(response.data) } else { toast({ title: "获取状态列表失败", description: response.msg || "请稍后重试", variant: "destructive", }) } } catch (error) { console.error("获取状态列表失败:", error) toast({ title: "获取状态列表失败", description: "请检查网络连接或稍后重试", variant: "destructive", }) } }, []) const fetchSourceTypes = useCallback(async () => { try { const response = await api.get>('/v1/traffic/pool/sources', { headers: { Authorization: `Bearer ${localStorage.getItem('token')}` } } as any) if (response.code === 200) { setSourceTypes(response.data) } else { toast({ title: "获取来源列表失败", description: response.msg || "请稍后重试", variant: "destructive", }) } } catch (error) { console.error("获取来源列表失败:", error) toast({ title: "获取来源列表失败", description: "请检查网络连接或稍后重试", variant: "destructive", }) } }, []) const fetchStatistics = useCallback(async () => { try { const response = await api.get>('/v1/traffic/pool/statistics', { headers: { Authorization: `Bearer ${localStorage.getItem('token')}` } } as any) if (response.code === 200) { setStats(response.data) } else { toast({ title: "获取统计数据失败", description: response.msg || "请稍后重试", variant: "destructive", }) } } catch (error) { console.error("获取统计数据失败:", error) toast({ title: "获取统计数据失败", description: "请检查网络连接或稍后重试", variant: "destructive", }) } }, []) // 处理搜索 const handleSearch = useCallback(() => { setUsers([]) setCurrentPage(1) setHasMore(true) fetchUsers(1, true) }, [fetchUsers]) // 初始化 IntersectionObserver useEffect(() => { const observer = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting && hasMore && !isFetching) { fetchUsers(currentPage + 1) } }, { threshold: 0.5 } ) if (loadingRef.current) { observer.observe(loadingRef.current) } return () => { if (loadingRef.current) { observer.unobserve(loadingRef.current) } } }, [hasMore, isFetching, currentPage, fetchUsers]) // 初始化数据 useEffect(() => { fetchStatusTypes() fetchSourceTypes() fetchStatistics() fetchUsers(1, true) }, []) // 监听筛选条件变化 useEffect(() => { setUsers([]) setCurrentPage(1) setHasMore(true) fetchUsers(1, true) }, [activeCategory, sourceFilter, statusFilter, debouncedSearchQuery]) const handleUserClick = (user: TrafficUser) => { setSelectedUser(user) setShowUserDetail(true) } // 添加状态码转换函数 const getStatusFromCode = (statusCode: number): number => { return statusCode; } // 添加刷新处理函数 const handleRefresh = useCallback(() => { setUsers([]) setCurrentPage(1) setHasMore(true) fetchUsers(1, true) fetchStatistics() }, [fetchUsers, fetchStatistics]) return (

流量池

{/* 搜索和筛选区域 */}
{ setSearchQuery(e.target.value) setCurrentPage(1) }} className="pl-9" />
{/* 统计卡片 */}
流量池总数
{stats.totalCount.toLocaleString()}
今日新增
{stats.todayAddCount.toLocaleString()}
{/* 分类标签页 */} { setActiveCategory(value) setCurrentPage(1) }} > 潜在客户 已转化 {/* 筛选器 */}
{activeCategory === "potential" && ( )}
{/* 用户列表 */}
{loading && users.length === 0 ? (
加载中...
) : users.length === 0 ? (
暂无数据
) : ( <> {users.map((user) => ( handleUserClick(user)} >
{user.nickname}
{activeCategory === "customer" ? "已通过" : user.status === 2 ? "已添加" : user.status === 1 ? "待处理" : "已失败"}
微信号: {user.wechatId}
来源: {user.source}
添加时间: {user.addTime}
{/* 标签展示 */}
{user.tags.slice(0, 2).map((tag) => ( {tag.name} ))} {user.tags.length > 2 && ( +{user.tags.length - 2} )}
))} {/* 加载状态显示 */}
{isFetching && (
加载中...
)} {!hasMore && users.length > 0 && ( 已加载全部数据 )} {!isFetching && users.length === 0 && ( 暂无数据 )}
)}
{/* 用户详情弹窗 */} 用户详情 {selectedUser && (
{selectedUser.nickname[0]}
{selectedUser.nickname}
{selectedUser.wechatId}
{activeCategory === "customer" ? "已通过" : selectedUser.status === 2 ? "已添加" : selectedUser.status === 1 ? "待处理" : "已失败"}
手机号码
{selectedUser.phone}
地区
{selectedUser.region}
来源
{selectedUser.source}
添加时间
{selectedUser.addTime}
标签
{selectedUser.tags.map((tag) => ( {tag.name} ))} {selectedUser.tags.length === 0 && 暂无标签}
{selectedUser.note && (
备注信息
{selectedUser.note}
)}
)}
) }