"use client" import { useState, useEffect, useRef } from "react" import {Mail, Calendar, RefreshCw, Trash2} from "lucide-react" import { cn } from "@/lib/utils" import { Button } from "@/components/ui/button" import { useThrottle } from "@/hooks/use-throttle" import { EMAIL_CONFIG } from "@/config" import { useToast } from "@/components/ui/use-toast" import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"; interface Message { id: string from_address?: string to_address?: string subject: string received_at?: number sent_at?: number content?: string html?: string } interface MessageListProps { email: { id: string address: string } messageType: 'received' | 'sent' onMessageSelect: (messageId: string | null, messageType?: 'received' | 'sent') => void selectedMessageId?: string | null refreshTrigger?: number } interface MessageResponse { messages: Message[] nextCursor: string | null total: number } export function MessageList({ email, messageType, onMessageSelect, selectedMessageId, refreshTrigger }: MessageListProps) { const [messages, setMessages] = useState([]) const [loading, setLoading] = useState(true) const [refreshing, setRefreshing] = useState(false) const [nextCursor, setNextCursor] = useState(null) const [loadingMore, setLoadingMore] = useState(false) const pollTimeoutRef = useRef() const messagesRef = useRef([]) // 添加 ref 来追踪最新的消息列表 const [total, setTotal] = useState(0) const [messageToDelete, setMessageToDelete] = useState(null) const { toast } = useToast() // 当 messages 改变时更新 ref useEffect(() => { messagesRef.current = messages }, [messages]) const fetchMessages = async (cursor?: string) => { try { const url = new URL(`/api/emails/${email.id}`, window.location.origin) if (messageType === 'sent') { url.searchParams.set('type', 'sent') } if (cursor) { url.searchParams.set('cursor', cursor) } const response = await fetch(url) const data = await response.json() as MessageResponse if (!cursor) { const newMessages = data.messages const oldMessages = messagesRef.current const lastDuplicateIndex = newMessages.findIndex( newMsg => oldMessages.some(oldMsg => oldMsg.id === newMsg.id) ) if (lastDuplicateIndex === -1) { setMessages(newMessages) setNextCursor(data.nextCursor) setTotal(data.total) return } const uniqueNewMessages = newMessages.slice(0, lastDuplicateIndex) setMessages([...uniqueNewMessages, ...oldMessages]) setTotal(data.total) return } setMessages(prev => [...prev, ...data.messages]) setNextCursor(data.nextCursor) setTotal(data.total) } catch (error) { console.error("Failed to fetch messages:", error) } finally { setLoading(false) setRefreshing(false) setLoadingMore(false) } } const startPolling = () => { stopPolling() pollTimeoutRef.current = setInterval(() => { if (!refreshing && !loadingMore) { fetchMessages() } }, EMAIL_CONFIG.POLL_INTERVAL) } const stopPolling = () => { if (pollTimeoutRef.current) { clearInterval(pollTimeoutRef.current) pollTimeoutRef.current = undefined } } const handleRefresh = async () => { setRefreshing(true) await fetchMessages() } const handleScroll = useThrottle((e: React.UIEvent) => { if (loadingMore) return const { scrollHeight, scrollTop, clientHeight } = e.currentTarget const threshold = clientHeight * 1.5 const remainingScroll = scrollHeight - scrollTop if (remainingScroll <= threshold && nextCursor) { setLoadingMore(true) fetchMessages(nextCursor) } }, 200) const handleDelete = async (message: Message) => { try { const response = await fetch(`/api/emails/${email.id}/${message.id}${messageType === 'sent' ? '?type=sent' : ''}`, { method: "DELETE" }) if (!response.ok) { const data = await response.json() toast({ title: "错误", description: (data as { error: string }).error, variant: "destructive" }) return } setMessages(prev => prev.filter(e => e.id !== message.id)) setTotal(prev => prev - 1) toast({ title: "成功", description: "邮件已删除" }) if (selectedMessageId === message.id) { onMessageSelect(null) } } catch { toast({ title: "错误", description: "删除邮件失败", variant: "destructive" }) } finally { setMessageToDelete(null) } } useEffect(() => { if (!email.id) { return } setLoading(true) setNextCursor(null) fetchMessages() startPolling() return () => { stopPolling() } // eslint-disable-next-line react-hooks/exhaustive-deps }, [email.id]) useEffect(() => { if (refreshTrigger && refreshTrigger > 0) { setRefreshing(true) fetchMessages() } // eslint-disable-next-line react-hooks/exhaustive-deps }, [refreshTrigger]) return ( <>
{total > 0 ? `${total} 封邮件` : "暂无邮件"}
{loading ? (
加载中...
) : messages.length > 0 ? (
{messages.map(message => (
onMessageSelect(message.id, messageType)} className={cn( "p-3 hover:bg-primary/5 cursor-pointer group", selectedMessageId === message.id && "bg-primary/10" )} >

{message.subject}

{message.from_address || message.to_address || ''} {new Date(message.received_at || message.sent_at || 0).toLocaleString()}
))} {loadingMore && (
加载更多...
)}
) : (
{messageType === 'sent' ? '暂无发送的邮件' : '暂无收到的邮件'}
)}
setMessageToDelete(null)}> 确认删除 确定要删除邮件 {messageToDelete?.subject} 吗? 取消 messageToDelete && handleDelete(messageToDelete)} > 删除 ) }