feat: beautify

This commit is contained in:
HK560 2025-05-13 23:43:51 +08:00
parent 825f15d551
commit 2682d8cfce
10 changed files with 155 additions and 70 deletions

View file

@ -2,9 +2,9 @@
<html lang="zh">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<link rel="icon" href="/image/icon.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>泰坦陨落服务器列表</title>
<title>R1Delta Server List</title>
</head>
<body>
<div id="app"></div>

View file

@ -8,7 +8,7 @@
"preview": "vite preview"
},
"dependencies": {
"@heroicons/vue": "^2.0.18",
"@heroicons/vue": "^2.2.0",
"axios": "^1.4.0",
"vue": "^3.3.4",
"vue-i18n": "11",

2
pnpm-lock.yaml generated
View file

@ -9,7 +9,7 @@ importers:
.:
dependencies:
'@heroicons/vue':
specifier: ^2.0.18
specifier: ^2.2.0
version: 2.2.0(vue@3.5.13(typescript@5.8.3))
axios:
specifier: ^1.4.0

BIN
public/image/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
public/image/wallpaper.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB

View file

@ -1,9 +1,10 @@
<template>
<div class="min-h-screen bg-titanfall-primary text-white flex flex-col">
<div class="min-h-screen bg-titanfall-primary text-white flex flex-col bg-cover bg-center bg-no-repeat bg-fixed" >
<header class="bg-titanfall-secondary shadow-lg">
<div class="container mx-auto px-4 py-6">
<div class="flex justify-between items-center">
<h1 class="text-3xl font-bold text-titanfall-accent">
<h1 class="text-3xl font-bold text-titanfall-highlight flex items-center gap-3">
<img src="/image/icon.png" alt="Site Icon" class="w-12 h-12" />
{{ t('serverList.title') }}
</h1>
<div class="flex gap-2">
@ -63,61 +64,78 @@
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div
v-for="server in filteredServers"
:key="`${server.ip}:${server.port}`"
class="bg-titanfall-secondary rounded-lg overflow-hidden shadow-lg hover:shadow-xl transition-shadow flex flex-col h-[500px]"
>
<div class="relative h-48 flex-shrink-0">
<img
:src="getMapImage(server.map_name)"
:alt="server.map_name"
class="w-full h-full object-cover"
/>
<div class="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black to-transparent p-4">
<h2 class="text-xl font-bold">{{ server.host_name }}</h2>
</div>
</div>
<div class="p-6 space-y-4 flex-grow flex flex-col">
<div class="space-y-2">
<p class="flex justify-between">
<span class="text-gray-400">{{ t('serverList.status.players') }}:</span>
<span>{{ server.total_players }}/{{ server.max_players }}</span>
</p>
<p class="flex justify-between">
<span class="text-gray-400">{{ t('serverList.status.map') }}:</span>
<span>{{ t(`serverList.map.${server.map_name}`) }}</span>
</p>
<p class="flex justify-between">
<span class="text-gray-400">{{ t('serverList.status.mode') }}:</span>
<span>{{ t(`serverList.filter.${server.game_mode}`) }}</span>
</p>
<p class="flex justify-between">
<span class="text-gray-400">{{ t('serverList.status.version') }}:</span>
<span>{{ server.version }}</span>
</p>
<div class="flex items-center justify-between mb-2">
<span class="text-gray-400">服务器地址</span>
<div class="flex items-center bg-gray-800 rounded-lg p-2 flex-shrink-0">
<span class="text-sm font-mono mr-2">{{ server.ip }}:{{ server.port }}</span>
<button
@click="copyConnectCommand(server)"
class="px-3 py-1 bg-titanfall-accent hover:bg-titanfall-accent/80 rounded transition-colors text-sm"
>
{{ copiedServer === `${server.ip}:${server.port}` ? '已复制' : '复制' }}
</button>
</div>
</div>
</div>
<div class="overflow-y-auto flex-grow border-t border-gray-700 py-2">
<p class="text-sm text-gray-400">{{ server.description }}</p>
</div>
</div>
<Transition name="fade" mode="out-in">
<div v-if="isLoading" class="flex justify-center items-center min-h-[400px]">
<div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-titanfall-accent"></div>
</div>
</div>
<div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<TransitionGroup name="fade-list">
<div
v-for="server in filteredServers"
:key="`${server.ip}:${server.port}`"
class="bg-titanfall-secondary rounded-lg overflow-hidden shadow-lg hover:shadow-xl transition-shadow flex flex-col h-[450px]"
>
<div class="relative h-48 flex-shrink-0">
<img
:src="getMapImage(server.map_name)"
:alt="server.map_name"
class="w-full h-full object-cover"
/>
<div class="absolute top-2 right-2 flex items-center px-3 py-1 rounded-full shadow-lg"
:class="{
'bg-gray-600': server.total_players === 0,
'bg-green-600': server.total_players > 0 && server.total_players < server.max_players,
'bg-red-600': server.total_players === server.max_players
}">
<UserGroupIcon class="w-5 h-5 mr-1" />
<span class="font-bold">{{ server.total_players }}/{{ server.max_players }}</span>
</div>
<div class="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black to-transparent p-4">
<h2 class="text-xl font-bold">{{ server.host_name }}</h2>
</div>
</div>
<div class="p-6 space-y-4 flex-grow flex flex-col">
<div class="space-y-2">
<!-- <p class="flex justify-between">
<span class="text-gray-400">{{ t('serverList.status.players') }}:</span>
<span>{{ server.total_players }}/{{ server.max_players }}</span>
</p> -->
<p class="flex justify-between">
<span class="text-gray-400">{{ t('serverList.status.map') }}:</span>
<span>{{ t(`serverList.map.${server.map_name}`) }}</span>
</p>
<p class="flex justify-between">
<span class="text-gray-400">{{ t('serverList.status.mode') }}:</span>
<span>{{ t(`serverList.filter.${server.game_mode}`) }}</span>
</p>
<p class="flex justify-between">
<span class="text-gray-400">{{ t('serverList.status.version') }}:</span>
<span>{{ server.version }}</span>
</p>
<div class="flex items-center justify-between mb-2">
<span class="text-gray-400">{{ t('serverList.status.address') }}</span>
<div class="flex items-center bg-gray-800 rounded-lg p-2 flex-shrink-0">
<span class="text-sm font-mono mr-2">{{ server.ip }}:{{ server.port }}</span>
<button
@click="copyConnectCommand(server)"
class="px-3 py-1 bg-titanfall-accent hover:bg-titanfall-accent/80 rounded transition-colors text-sm"
>
{{ copiedServer === `${server.ip}:${server.port}` ? t('serverList.button.copied') : t('serverList.button.copy') }}
</button>
</div>
</div>
</div>
<div class="overflow-y-auto max-h-[80px] flex-grow border-t border-gray-700 py-2">
<p class="text-sm text-gray-400">{{ server.description === "" ? t('serverList.message.nodescription') : server.description }}</p>
</div>
</div>
</div>
</TransitionGroup>
</div>
</Transition>
</main>
<Footer />
</div>
@ -130,6 +148,7 @@ import axios from 'axios'
import type { Server, GameMode, GameModeOption } from './types'
import { mapImageMapping } from './data/data'
import Footer from './components/Footer.vue'
import { UserGroupIcon } from '@heroicons/vue/24/solid'
const { t, locale } = useI18n()
@ -137,6 +156,7 @@ const servers = ref<Server[]>([])
const searchQuery = ref('')
const selectedMode = ref<GameMode>('all')
const copiedServer = ref('')
const isLoading = ref(true)
const gameModes: GameModeOption[] = [
{ value: 'all', label: 'All' },
@ -161,15 +181,18 @@ const filteredServers = computed(() => {
const fetchServers = async (): Promise<void> => {
try {
isLoading.value = true
const response = await axios.get<Server[]>('/r1d-api/servers')
servers.value = response.data
} catch (error) {
console.error('Error fetching servers:', error)
} finally {
isLoading.value = false
}
}
const getMapImage = (mapName: string): string => {
return mapImageMapping[mapName.toLowerCase()] || '/image/maps/default_map.jpg'
return mapImageMapping[mapName.toLowerCase()] || '/image/maps/Menu_Background.jpg'
}
const copyConnectCommand = async (server: Server): Promise<void> => {
@ -187,8 +210,8 @@ const copyConnectCommand = async (server: Server): Promise<void> => {
onMounted(() => {
fetchServers()
// 30
setInterval(fetchServers, 30000)
// 1
setInterval(fetchServers, 60000)
})
</script>
@ -196,4 +219,49 @@ onMounted(() => {
@tailwind base;
@tailwind components;
@tailwind utilities;
/* 自定义滚动条样式 */
.overflow-y-auto::-webkit-scrollbar {
width: 6px;
}
.overflow-y-auto::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.1);
border-radius: 3px;
}
.overflow-y-auto::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 3px;
transition: background 0.2s ease;
}
.overflow-y-auto::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.3);
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
.fade-list-enter-active,
.fade-list-leave-active {
transition: all 0.3s ease;
}
.fade-list-enter-from,
.fade-list-leave-to {
opacity: 0;
transform: translateY(20px);
}
.fade-list-move {
transition: transform 0.3s ease;
}
</style>

View file

@ -1,12 +1,12 @@
export default {
serverList: {
title: 'Titanfall Server List',
title: 'R1Delta Server List',
search: 'Search servers...',
filter: {
all: 'All',
at: 'Attrition',
campaign: 'Campaign',
coop: 'Co-op',
coop: 'Frontier Defense',
ctf: 'Capture the Flag',
lts: 'Last Titan Standing',
mfdp: 'Marked for Death Pro',
@ -45,7 +45,15 @@ export default {
maxPlayers: 'Max Players',
map: 'Map',
mode: 'Mode',
version: 'Version'
version: 'Version',
address: 'Address'
},
button: {
copy: 'Copy',
copied: 'Copied'
},
message: {
nodescription: 'No description'
}
}
}

View file

@ -5,7 +5,7 @@ import en from './en'
const i18n = createI18n({
legacy: false,
globalInjection: true,
locale: 'zh',
locale: 'en',
fallbackLocale: 'en',
messages: {
zh,

View file

@ -1,6 +1,6 @@
export default {
serverList: {
title: 'R1Delta 服务器列表(本页面开发中)',
title: 'R1Delta 服务器列表',
search: '搜索服务器...',
filter: {
all: '全部',
@ -45,7 +45,15 @@ export default {
maxPlayers: '最大玩家数',
map: '地图',
mode: '模式',
version: '版本'
version: '版本',
address: '地址'
},
button: {
copy: '复制',
copied: '已复制'
},
message: {
nodescription: '此服务器无描述🫡'
}
}
}

View file

@ -10,7 +10,8 @@ module.exports = {
'titanfall': {
'primary': '#1a1a1a',
'secondary': '#2d2d2d',
'accent': '#ff4655'
'accent': '#9A333E',
'highlight': '#FF4655'
}
}
},