'use client' import { useState, useEffect, useRef } from 'react' import { useSession } from 'next-auth/react' import { useToast } from '@/hooks/useToast' import ToastBubble from './ToastBubble' interface Danmaku { id: string content: string username: string createdAt: string } export default function DanmakuWidget() { const { data: session } = useSession() const [isOpen, setIsOpen] = useState(false) const [content, setContent] = useState('') const [danmakus, setDanmakus] = useState([]) const [sending, setSending] = useState(false) const messagesEndRef = useRef(null) const containerRef = useRef(null) const { toast, showToast } = useToast() // 滚动到底部 const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }) } // 获取弹幕 useEffect(() => { if (!isOpen) return const fetchDanmakus = async () => { try { const response = await fetch('/api/danmaku?limit=100') if (response.ok) { const data = await response.json() setDanmakus(data.reverse()) setTimeout(scrollToBottom, 100) } } catch (error) { console.error('Failed to fetch danmakus:', error) } } fetchDanmakus() // 轮询新弹幕 const interval = setInterval(async () => { try { const response = await fetch('/api/danmaku?limit=1') if (response.ok) { const data = await response.json() if (data.length > 0 && data[0].id !== danmakus[danmakus.length - 1]?.id) { setDanmakus(prev => [...prev, data[0]]) setTimeout(scrollToBottom, 100) } } } catch (error) { console.error('Failed to poll danmakus:', error) } }, 2000) return () => clearInterval(interval) }, [isOpen]) // 发送弹幕 const handleSend = async () => { if (!content.trim() || sending) return setSending(true) try { const response = await fetch('/api/danmaku', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ content: content.trim() }), }) if (response.ok) { const newDanmaku = await response.json() setDanmakus(prev => [...prev, newDanmaku]) setContent('') setTimeout(scrollToBottom, 100) } else { const { parseFetchError } = await import('@/lib/error-handler') const error = await parseFetchError(response) showToast(error.message, { variant: 'error' }) } } catch (error) { console.error('Send danmaku error:', error) const { handleError } = await import('@/lib/error-handler') handleError(error, showToast) } finally { setSending(false) } } // 点击外部关闭 useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (containerRef.current && !containerRef.current.contains(event.target as Node)) { setIsOpen(false) } } if (isOpen) { document.addEventListener('mousedown', handleClickOutside) } return () => { document.removeEventListener('mousedown', handleClickOutside) } }, [isOpen]) if (!session) return null return (
{/* 弹幕对话框 */} {isOpen && (
{/* 头部 */}

全站弹幕(匿名)

{/* 弹幕列表 */}
{danmakus.length === 0 ? (

暂无弹幕,快来发送第一条吧!

) : ( danmakus.map((danmaku) => (
{danmaku.username.charAt(0).toUpperCase()}
{danmaku.username} {new Date(danmaku.createdAt).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })}

{danmaku.content}

)) )}
{/* 输入框 */}
setContent(e.target.value)} onKeyPress={(e) => { if (e.key === 'Enter' && !sending) { handleSend() } }} placeholder="匿名发送弹幕..." className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none text-sm" maxLength={100} disabled={sending} />

{content.length}/100 · 按Enter快速发送 · 匿名发送,保护隐私

)} {/* 悬浮按钮 */} {/* 统一的Toast提示 */} {toast && toast.show && ( )}
) }