Refactor server data structure and update localization for server categories and status

This commit is contained in:
2025-10-04 22:48:57 +08:00
parent da6909ccf7
commit d79c6b1b49
3 changed files with 150 additions and 220 deletions

View File

@@ -86,15 +86,15 @@
"title": "Game Servers",
"subtitle": "Join active Counter-Strike servers and compete with players worldwide",
"players": "Players",
"ping": "Ping",
"status": "Status",
"joinServer": "Join Server",
"maintenance": "Maintenance",
"offline": "Offline",
"serverStats": "Server Statistics",
"onlineServers": "Online Servers",
"totalPlayers": "Total Players",
"regions": "Regions",
"uptime": "Uptime"
"categories": "Categories",
"totalServers": "Total Servers"
},
"forum": {
"title": "Community Forum",

View File

@@ -85,15 +85,15 @@
"title": "游戏服务器",
"subtitle": "加入活跃的反恐精英服务器,与全球玩家竞技",
"players": "玩家",
"ping": "延迟",
"status": "状态",
"joinServer": "加入服务器",
"maintenance": "维护中",
"offline": "离线",
"serverStats": "服务器统计",
"onlineServers": "在线服务器",
"totalPlayers": "总玩家数",
"regions": "地区",
"uptime": "正常运行时间"
"categories": "分类",
"totalServers": "总服务器数"
},
"forum": {
"title": "社区论坛",

View File

@@ -1,184 +1,106 @@
import { useTranslation } from 'react-i18next'
import { useState, useEffect } from 'react'
import Layout from '../components/Layout'
import '../App.css'
interface Server {
id: number
interface ServerInfo {
name: string
map: string
gameMode: string
players: number
ip: string
port: number
category: 'Surf' | 'Kz' | 'Bhop'
}
interface A2SResponse {
appID: number
botCount: number
environment: string
gameDescription: string
gameDirectory: string
mapName: string
maxPlayers: number
ping: number
region: string
difficulty: 'Easy' | 'Normal' | 'Hard' | 'Expert'
status: 'online' | 'offline' | 'maintenance'
description: string
tags: string[]
playerCount: number
serverName: string
serverType: string
vac: number
version: string
visibility: number
}
interface ServerData extends ServerInfo {
a2sData?: A2SResponse
status: 'online' | 'offline' | 'loading'
error?: string
}
function Servers() {
const { t } = useTranslation()
// Mock server data
const servers: Server[] = [
{
id: 1,
name: "Dust2 Classic - Competitive",
map: "de_dust2",
gameMode: "Competitive",
players: 8,
maxPlayers: 10,
ping: 23,
region: "Asia-Pacific",
difficulty: "Normal",
status: "online",
description: "Classic Dust2 competitive matches with skilled players",
tags: ["Competitive", "Ranked", "Dust2"]
},
{
id: 2,
name: "Mirage Pro League",
map: "de_mirage",
gameMode: "Premier",
players: 10,
maxPlayers: 10,
ping: 45,
region: "Europe",
difficulty: "Hard",
status: "online",
description: "Professional Mirage matches for experienced players",
tags: ["Premier", "Mirage", "Pro"]
},
{
id: 3,
name: "Inferno Casual",
map: "de_inferno",
gameMode: "Casual",
players: 6,
maxPlayers: 12,
ping: 67,
region: "North America",
difficulty: "Easy",
status: "online",
description: "Relaxed Inferno matches for all skill levels",
tags: ["Casual", "Inferno", "Beginner"]
},
{
id: 4,
name: "Nuke Tactical",
map: "de_nuke",
gameMode: "Tactical",
players: 9,
maxPlayers: 10,
ping: 34,
region: "Asia-Pacific",
difficulty: "Expert",
status: "online",
description: "Tactical Nuke gameplay with strategic objectives",
tags: ["Tactical", "Nuke", "Strategy"]
},
{
id: 5,
name: "Vertigo Deathmatch",
map: "de_vertigo",
gameMode: "Deathmatch",
players: 12,
maxPlayers: 16,
ping: 28,
region: "Europe",
difficulty: "Normal",
status: "online",
description: "Fast-paced Vertigo deathmatch action",
tags: ["Deathmatch", "Vertigo", "FFA"]
},
{
id: 6,
name: "Ancient Wingman",
map: "de_ancient",
gameMode: "Wingman",
players: 2,
maxPlayers: 4,
ping: 52,
region: "North America",
difficulty: "Normal",
status: "online",
description: "2v2 Wingman matches on Ancient",
tags: ["Wingman", "Ancient", "2v2"]
},
{
id: 7,
name: "Overpass Scrim",
map: "de_overpass",
gameMode: "Scrimmage",
players: 8,
maxPlayers: 10,
ping: 41,
region: "South America",
difficulty: "Hard",
status: "online",
description: "Team scrimmage matches for practice",
tags: ["Scrimmage", "Overpass", "Practice"]
},
{
id: 8,
name: "Cache Community",
map: "de_cache",
gameMode: "Community",
players: 0,
maxPlayers: 10,
ping: 89,
region: "Europe",
difficulty: "Easy",
status: "maintenance",
description: "Community server - currently under maintenance",
tags: ["Community", "Cache", "Social"]
},
{
id: 9,
name: "Train Pro League",
map: "de_train",
gameMode: "Premier",
players: 10,
maxPlayers: 10,
ping: 31,
region: "Asia-Pacific",
difficulty: "Expert",
status: "online",
description: "Elite Train matches for top players",
tags: ["Premier", "Train", "Elite"]
},
{
id: 10,
name: "Cobblestone Casual",
map: "de_cbble",
gameMode: "Casual",
players: 4,
maxPlayers: 12,
ping: 76,
region: "North America",
difficulty: "Easy",
status: "online",
description: "Casual gameplay on the classic Cobblestone",
tags: ["Casual", "Cobblestone", "Classic"]
}
// Server list from database export
const serverList: ServerInfo[] = [
{ name: 'Surf 66 Tick #1', ip: '14.103.233.1', port: 27015, category: 'Surf' },
{ name: 'Surf 66 Tick #2', ip: '14.103.233.1', port: 27016, category: 'Surf' },
{ name: 'Surf 66 Tick #3', ip: '14.103.233.1', port: 27017, category: 'Surf' },
{ name: 'Surf 100 Tick #1', ip: '14.103.233.1', port: 28015, category: 'Surf' },
{ name: 'Surf 100 Tick #2', ip: '14.103.233.1', port: 28016, category: 'Surf' },
{ name: 'Kz #1', ip: '14.103.233.1', port: 29015, category: 'Kz' },
{ name: 'Kz #2', ip: '14.103.233.1', port: 29016, category: 'Kz' },
{ name: 'Bhop #1', ip: '14.103.233.1', port: 30015, category: 'Bhop' },
{ name: 'Bhop #2', ip: '14.103.233.1', port: 30016, category: 'Bhop' }
]
const [servers, setServers] = useState<ServerData[]>([])
const [selectedCategory, setSelectedCategory] = useState<string>('All')
// Fetch A2S data for all servers
useEffect(() => {
const fetchServerData = async () => {
const serverPromises = serverList.map(async (server) => {
try {
const response = await fetch('/api/server/statistics/a2s-query', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
serverIP: server.ip,
serverPort: server.port,
timeout: 3000
})
})
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
const a2sData: A2SResponse = await response.json()
return { ...server, a2sData, status: 'online' as const }
} catch (error) {
console.error(`Failed to fetch data for ${server.name}:`, error)
return {
...server,
status: 'offline' as const,
error: error instanceof Error ? error.message : 'Unknown error'
}
}
})
const results = await Promise.all(serverPromises)
setServers(results)
}
fetchServerData()
}, [])
// Filter servers by category
const filteredServers = selectedCategory === 'All'
? servers
: servers.filter(server => server.category === selectedCategory)
const categories = ['All', ...Array.from(new Set(serverList.map(s => s.category)))]
const getStatusColor = (status: string) => {
switch (status) {
case 'online': return '#10b981'
case 'offline': return '#ef4444'
case 'maintenance': return '#f59e0b'
default: return '#6b7280'
}
}
const getDifficultyColor = (difficulty: string) => {
switch (difficulty) {
case 'Easy': return '#10b981'
case 'Normal': return '#3b82f6'
case 'Hard': return '#f59e0b'
case 'Expert': return '#ef4444'
case 'loading': return '#f59e0b'
default: return '#6b7280'
}
}
@@ -210,6 +132,34 @@ function Servers() {
}}>
{t('servers.subtitle')}
</p>
{/* Category Filter */}
<div style={{
display: 'flex',
justifyContent: 'center',
gap: '1rem',
marginBottom: '2rem',
flexWrap: 'wrap'
}}>
{categories.map(category => (
<button
key={category}
onClick={() => setSelectedCategory(category)}
style={{
padding: '0.5rem 1rem',
border: `2px solid ${selectedCategory === category ? 'var(--accent-primary)' : 'var(--border-color)'}`,
background: selectedCategory === category ? 'var(--accent-primary)' : 'var(--bg-secondary)',
color: selectedCategory === category ? 'white' : 'var(--text-primary)',
borderRadius: '20px',
cursor: 'pointer',
fontWeight: 'bold',
transition: 'all 0.3s ease'
}}
>
{category}
</button>
))}
</div>
</div>
</section>
@@ -224,9 +174,9 @@ function Servers() {
gridTemplateColumns: 'repeat(auto-fit, minmax(350px, 1fr))',
gap: '2rem'
}}>
{servers.map((server) => (
{filteredServers.map((server) => (
<div
key={server.id}
key={`${server.ip}:${server.port}`}
className="server-card"
style={{
background: 'var(--bg-secondary)',
@@ -275,9 +225,9 @@ function Servers() {
fontSize: '0.9rem',
color: 'var(--text-secondary)'
}}>
<span>🗺 {server.map}</span>
<span>🗺 {server.a2sData?.mapName || 'Loading...'}</span>
<span></span>
<span>{server.gameMode}</span>
<span>{server.category}</span>
</div>
</div>
@@ -301,7 +251,7 @@ function Servers() {
fontWeight: 'bold',
color: 'var(--text-primary)'
}}>
{server.players}/{server.maxPlayers}
{server.a2sData ? `${server.a2sData.playerCount - server.a2sData.botCount}/${server.a2sData.maxPlayers}` : 'Loading...'}
</div>
</div>
<div>
@@ -310,14 +260,14 @@ function Servers() {
color: 'var(--text-secondary)',
marginBottom: '0.25rem'
}}>
{t('servers.ping')}
{t('servers.status')}
</div>
<div style={{
fontSize: '1.1rem',
fontWeight: 'bold',
color: server.ping < 50 ? '#10b981' : server.ping < 100 ? '#f59e0b' : '#ef4444'
color: server.status === 'online' ? '#10b981' : server.status === 'offline' ? '#ef4444' : '#f59e0b'
}}>
{server.ping}ms
{server.status === 'online' ? 'Online' : server.status === 'offline' ? 'Offline' : 'Loading'}
</div>
</div>
</div>
@@ -332,45 +282,27 @@ function Servers() {
color: 'var(--text-secondary)',
marginBottom: '0.5rem'
}}>
<span>🌍 {server.region}</span>
<span><EFBFBD> {server.ip}:{server.port}</span>
<span style={{
color: getDifficultyColor(server.difficulty),
background: 'var(--accent-primary)',
color: 'white',
padding: '0.25rem 0.5rem',
borderRadius: '4px',
fontSize: '0.8rem',
fontWeight: 'bold'
}}>
{server.difficulty}
{server.category}
</span>
</div>
<p style={{
fontSize: '0.9rem',
color: 'var(--text-secondary)',
marginBottom: '1rem'
}}>
{server.description}
</p>
</div>
{/* Tags */}
<div style={{
display: 'flex',
flexWrap: 'wrap',
gap: '0.5rem',
marginBottom: '1rem'
}}>
{server.tags.map((tag, index) => (
<span
key={index}
style={{
background: 'var(--accent-primary)',
color: 'white',
padding: '0.25rem 0.5rem',
borderRadius: '4px',
fontSize: '0.8rem',
fontWeight: 'bold'
}}
>
{tag}
</span>
))}
{server.error && (
<p style={{
fontSize: '0.9rem',
color: '#ef4444',
marginBottom: '1rem'
}}>
Error: {server.error}
</p>
)}
</div>
{/* Join Button */}
@@ -394,9 +326,7 @@ function Servers() {
}
}}
>
{server.status === 'online' ? t('servers.joinServer') :
server.status === 'maintenance' ? t('servers.maintenance') :
t('servers.offline')}
{server.status === 'online' ? t('servers.joinServer') : t('servers.offline')}
</button>
</div>
))}
@@ -446,7 +376,7 @@ function Servers() {
color: 'var(--accent-primary)',
marginBottom: '0.5rem'
}}>
{servers.reduce((sum, s) => sum + s.players, 0)}
{servers.reduce((sum, s) => sum + (s.a2sData ? (s.a2sData.playerCount - s.a2sData.botCount) : 0), 0)}
</div>
<div style={{
color: 'var(--text-secondary)',
@@ -462,13 +392,13 @@ function Servers() {
color: 'var(--accent-primary)',
marginBottom: '0.5rem'
}}>
4
{Array.from(new Set(servers.map(s => s.category))).length}
</div>
<div style={{
color: 'var(--text-secondary)',
fontSize: '1.1rem'
}}>
{t('servers.regions')}
{t('servers.categories')}
</div>
</div>
<div>
@@ -478,13 +408,13 @@ function Servers() {
color: 'var(--accent-primary)',
marginBottom: '0.5rem'
}}>
95%
{servers.length}
</div>
<div style={{
color: 'var(--text-secondary)',
fontSize: '1.1rem'
}}>
{t('servers.uptime')}
{t('servers.totalServers')}
</div>
</div>
</div>