'use client' import { useState, useEffect, useRef } from 'react' interface RankingUser { username: string count: number } interface RankingData { contributors: RankingUser[] downloads: RankingUser[] likes: RankingUser[] } interface Position { x: number y: number } type RankingPanelProps = { appearance?: 'dark' | 'light' safeLeft?: number dockRight?: boolean } export default function RankingPanel({ appearance = 'dark', safeLeft = 0, dockRight = false }: RankingPanelProps) { const [isVisible, setIsVisible] = useState(!dockRight) const [currentTab, setCurrentTab] = useState<'contributors' | 'downloads' | 'likes'>('contributors') const [rankings, setRankings] = useState({ contributors: [], downloads: [], likes: [] }) const [loading, setLoading] = useState(true) const [position, setPosition] = useState(() => ({ x: 32, y: typeof window === 'undefined' ? 200 : window.innerHeight / 2 - 200, })) const [isDragging, setIsDragging] = useState(false) const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 }) const panelRef = useRef(null) const buttonRef = useRef(null) const [buttonPosition, setButtonPosition] = useState(() => ({ x: 32, y: typeof window === 'undefined' ? 200 : window.innerHeight / 2 - 24, })) const [isButtonDragging, setIsButtonDragging] = useState(false) const [buttonDragOffset, setButtonDragOffset] = useState({ x: 0, y: 0 }) const clickStartRef = useRef<{ x: number; y: number; time: number } | null>(null) const isLight = appearance === 'light' // 从localStorage恢复状态 useEffect(() => { if (dockRight) { setIsVisible(false) return } try { const saved = localStorage.getItem('ranking_panel_state') if (saved) { const state = JSON.parse(saved) // 默认显示为迷你按钮(isVisible默认为false) setIsVisible(state.isVisible ?? false) if (state.position) { setPosition(state.position) } if (state.buttonPosition) { setButtonPosition(state.buttonPosition) } } else { // 首次访问,默认为迷你按钮 setIsVisible(false) } } catch (error) { console.error('恢复榜单面板状态失败:', error) setIsVisible(false) } }, [dockRight]) useEffect(() => { const minimumX = window.innerWidth >= 1024 ? safeLeft : 0 setPosition((current) => current.x < minimumX ? { ...current, x: minimumX + 16 } : current) setButtonPosition((current) => current.x < minimumX ? { ...current, x: minimumX + 16 } : current) }, [safeLeft]) // 保存状态到localStorage const saveState = () => { try { localStorage.setItem('ranking_panel_state', JSON.stringify({ isVisible, position, buttonPosition })) } catch (error) { console.error('保存榜单面板状态失败:', error) } } // 状态变化时保存 useEffect(() => { saveState() }, [isVisible, position, buttonPosition]) // 自动轮播 useEffect(() => { const tabs: ('contributors' | 'downloads' | 'likes')[] = ['contributors', 'downloads', 'likes'] let currentIndex = 0 const interval = setInterval(() => { currentIndex = (currentIndex + 1) % tabs.length setCurrentTab(tabs[currentIndex]) }, 5000) return () => clearInterval(interval) }, []) // 获取排行榜数据 useEffect(() => { const fetchRankings = async () => { try { const response = await fetch('/api/rankings') if (response.ok) { const data = await response.json() setRankings(data) } } catch (error) { console.error('获取排行榜失败:', error) } finally { setLoading(false) } } fetchRankings() const interval = setInterval(fetchRankings, 30000) return () => clearInterval(interval) }, []) // 拖拽处理 const handleMouseDown = (e: React.MouseEvent) => { if (panelRef.current) { setIsDragging(true) const rect = panelRef.current.getBoundingClientRect() setDragOffset({ x: e.clientX - rect.left, y: e.clientY - rect.top }) } } useEffect(() => { const handleMouseMove = (e: MouseEvent) => { if (isDragging) { const newX = e.clientX - dragOffset.x const newY = e.clientY - dragOffset.y // 边界约束 const maxX = window.innerWidth - 150 const maxY = window.innerHeight - 400 setPosition({ x: Math.max(window.innerWidth >= 1024 ? safeLeft : 0, Math.min(newX, maxX)), y: Math.max(0, Math.min(newY, maxY)) }) } } const handleMouseUp = () => { setIsDragging(false) } if (isDragging) { document.addEventListener('mousemove', handleMouseMove) document.addEventListener('mouseup', handleMouseUp) } return () => { document.removeEventListener('mousemove', handleMouseMove) document.removeEventListener('mouseup', handleMouseUp) } }, [isDragging, dragOffset, safeLeft]) // 缩略按钮拖动处理 const handleButtonMouseDown = (e: React.MouseEvent) => { if (buttonRef.current) { const rect = buttonRef.current.getBoundingClientRect() setButtonDragOffset({ x: e.clientX - rect.left, y: e.clientY - rect.top }) setIsButtonDragging(true) // 记录点击开始位置和时间 clickStartRef.current = { x: e.clientX, y: e.clientY, time: Date.now() } } } useEffect(() => { const handleButtonMouseMove = (e: MouseEvent) => { if (isButtonDragging) { const newX = e.clientX - buttonDragOffset.x const newY = e.clientY - buttonDragOffset.y // 边界约束 const maxX = window.innerWidth - 48 const maxY = window.innerHeight - 48 setButtonPosition({ x: Math.max(window.innerWidth >= 1024 ? safeLeft : 0, Math.min(newX, maxX)), y: Math.max(0, Math.min(newY, maxY)) }) } } const handleButtonMouseUp = () => { setIsButtonDragging(false) } if (isButtonDragging) { document.addEventListener('mousemove', handleButtonMouseMove) document.addEventListener('mouseup', handleButtonMouseUp) } return () => { document.removeEventListener('mousemove', handleButtonMouseMove) document.removeEventListener('mouseup', handleButtonMouseUp) } }, [isButtonDragging, buttonDragOffset, safeLeft]) const handleClose = () => { setIsVisible(false) } const handleOpen = () => { setIsVisible(true) // 打开时使用按钮的当前位置 setPosition(buttonPosition) } const getMedalIcon = (index: number) => { switch (index) { case 0: return ( ) case 1: return ( ) case 2: return ( ) default: return null } } const getTabTitle = () => { switch (currentTab) { case 'contributors': return '🏆 贡献排行榜' case 'downloads': return '📥 下载排行榜' case 'likes': return '❤️ 点赞排行榜' } } const getCurrentData = () => { switch (currentTab) { case 'contributors': return rankings.contributors case 'downloads': return rankings.downloads case 'likes': return rankings.likes } } // 迷你按钮 if (!isVisible) { return ( ) } return (
{/* 拖拽区域 */}

{getTabTitle()}

{/* 关闭按钮 */} {/* 指示器 */}
{/* 排行榜内容 */}
{loading ? (
加载中...
) : getCurrentData().length === 0 ? (
暂无数据
) : (
{getCurrentData().map((user, index) => (
{index < 3 ? ( getMedalIcon(index) ) : ( {index + 1} )}
{user.username}
{user.count}
))}
)}
) }