Files
d1kt/src/components/TowerDefenseLeaderboard.tsx

178 lines
5.6 KiB
TypeScript

// src/components/TowerDefenseLeaderboard.tsx
import React, { useState, useEffect } from 'react';
import {
Box,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
Typography,
Pagination,
Chip,
CircularProgress
} from '@mui/material';
import EmojiEventsIcon from '@mui/icons-material/EmojiEvents';
import { API_BASE_URL } from '../config';
interface LeaderboardRecord {
userId: string;
username: string;
fullname: string;
bestWave: number;
bestScore: number;
totalGames: number;
lastPlayed: string;
}
interface LeaderboardResponse {
records: LeaderboardRecord[];
total: number;
currentPage: number;
totalPages: number;
}
const TowerDefenseLeaderboard: React.FC = () => {
const [records, setRecords] = useState<LeaderboardRecord[]>([]);
const [page, setPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetchLeaderboard();
}, [page]);
const fetchLeaderboard = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(
`${API_BASE_URL}/api/tower-defense/leaderboard?page=${page}&limit=10`
);
if (!response.ok) {
throw new Error('获取排行榜失败');
}
const data: LeaderboardResponse = await response.json();
setRecords(data.records);
setTotalPages(data.totalPages);
} catch (error) {
console.error('获取排行榜失败:', error);
setError(error instanceof Error ? error.message : '获取排行榜失败');
} finally {
setLoading(false);
}
};
const handlePageChange = (_event: React.ChangeEvent<unknown>, value: number) => {
setPage(value);
};
const getRankBadge = (rank: number) => {
if (rank === 1) return { emoji: '🥇', color: 'success' as const };
if (rank === 2) return { emoji: '🥈', color: 'primary' as const };
if (rank === 3) return { emoji: '🥉', color: 'default' as const };
return null;
};
if (loading && records.length === 0) {
return (
<Box display="flex" justifyContent="center" alignItems="center" minHeight="60vh">
<CircularProgress />
</Box>
);
}
if (error) {
return (
<Box display="flex" justifyContent="center" alignItems="center" minHeight="60vh">
<Typography color="error">{error}</Typography>
</Box>
);
}
return (
<Box sx={{ padding: 3 }}>
<Box display="flex" alignItems="center" justifyContent="center" mb={3}>
<EmojiEventsIcon sx={{ fontSize: 40, color: 'gold', mr: 1 }} />
<Typography variant="h4">
</Typography>
</Box>
<TableContainer component={Paper} sx={{ mb: 3 }}>
<Table>
<TableHead>
<TableRow>
<TableCell align="center" width="80px"></TableCell>
<TableCell></TableCell>
<TableCell align="center"></TableCell>
<TableCell align="center"></TableCell>
<TableCell align="center"></TableCell>
<TableCell align="center"></TableCell>
</TableRow>
</TableHead>
<TableBody>
{records.length === 0 ? (
<TableRow>
<TableCell colSpan={6} align="center">
<Typography variant="body2" color="textSecondary" py={3}>
</Typography>
</TableCell>
</TableRow>
) : (
records.map((record, index) => {
const rank = (page - 1) * 10 + index + 1;
const badge = page === 1 ? getRankBadge(rank) : null;
return (
<TableRow
key={record.userId}
sx={badge ? { backgroundColor: 'rgba(255, 215, 0, 0.1)' } : {}}
>
<TableCell align="center">
<Box display="flex" alignItems="center" justifyContent="center" gap={0.5}>
<Typography fontWeight={badge ? 'bold' : 'normal'}>
{rank}
</Typography>
{badge && (
<Chip size="small" label={badge.emoji} color={badge.color} />
)}
</Box>
</TableCell>
<TableCell>
<Typography fontWeight={badge ? 'bold' : 'normal'}>
{record.fullname || record.username}
</Typography>
</TableCell>
<TableCell align="center">
<Chip label={record.bestScore} color="primary" size="small" />
</TableCell>
<TableCell align="center">{record.bestWave}</TableCell>
<TableCell align="center">{record.totalGames}</TableCell>
<TableCell align="center">
{new Date(record.lastPlayed).toLocaleString('zh-CN')}
</TableCell>
</TableRow>
);
})
)}
</TableBody>
</Table>
</TableContainer>
{totalPages > 1 && (
<Box display="flex" justifyContent="center" mt={3}>
<Pagination count={totalPages} page={page} onChange={handlePageChange} color="primary" />
</Box>
)}
</Box>
);
};
export default TowerDefenseLeaderboard;