Files
vs100/src/views/BattleRanking.vue

930 lines
26 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="battle-ranking">
<!-- 第一部分百日大战主题 -->
<section class="theme-section">
<div class="theme-container">
<h1 class="main-title">🔥 百人大战排行榜 🔥</h1>
<p class="subtitle">2025年度精英挑战赛 | 百日冲刺 · 争创佳绩</p>
<div class="timer">
<span class="label">距离结束还有</span>
<div class="countdown">
<span class="time-item">{{ days }}</span>
<span class="time-item">{{ hours }}</span>
<span class="time-item">{{ minutes }}</span>
<span class="time-item">{{ seconds }}</span>
<span class="time-item milliseconds">{{ milliseconds }}毫秒</span>
</div>
</div>
</div>
</section>
<!-- 第二部分战鼓动画浮动并支持拖放 -->
<section
class="drums-section"
@mousedown="startDrag"
@click="handleDrumClick"
:style="{ left: drumsPosition.x + 'px', top: drumsPosition.y + 'px' }"
>
<div class="drums-container">
<!-- 战鼓动画在上面 -->
<div class="drums-animation">
<div class="drum" :class="{ beating: isBeating }">🥁</div>
<div class="drum" :class="{ beating: isBeating }">🥁</div>
<div class="trophy">🏆</div>
<div class="drum" :class="{ beating: isBeating }">🥁</div>
<div class="drum" :class="{ beating: isBeating }">🥁</div>
</div>
</div>
</section>
<!-- 第三部分奖金设置行布局 -->
<section class="bonus-section">
<h2>🎯 奖金设置</h2>
<div class="bonus-rules-row">
<div
v-for="(rule, index) in displayBonusRules"
:key="index"
class="bonus-rule-item"
>
<div class="rule-header">
<span class="rank-range">名次: {{ rule.rank }}</span>
<span class="rule-desc">{{ rule.description }}</span>
</div>
<div class="rule-details">
<p>🏅 个人奖励: {{ rule.individualBonus }}</p>
<p>👥 团队奖励: {{ rule.teamBonus }}</p>
</div>
</div>
</div>
</section>
<!-- 第四部分排名明细 -->
<section class="rankings-section">
<div class="rankings-container">
<!-- 个人排名 -->
<div class="individual-rankings">
<h2 class="section-title">👤 个人排名</h2>
<div class="rank-table">
<div class="table-header" :style="{ 'grid-template-columns': individualGridTemplate }">
<span class="rank-col">排名</span>
<span class="avatar-col">头像</span>
<span class="name-col">姓名</span>
<span class="score-col">{{ localDisplayConfig.individual?.scoreColumn?.displayName || '得分' }}</span>
<span v-if="localDisplayConfig.individual?.showLevel" class="level-col">等级</span>
<span v-if="localDisplayConfig.individual?.showDepartment" class="dept-col">部门</span>
<span class="bonus-col">奖金</span>
</div>
<div
v-for="(item, index) in individualRankings"
:key="item.id"
class="table-row"
:style="{ 'grid-template-columns': individualGridTemplate }"
:class="{
'top-three': index < 3,
'highlight': index === 0
}"
>
<span class="rank-col">{{ index + 1 }}</span>
<span class="avatar-col">{{ item.avatar }}</span>
<span class="name-col">{{ item.name }}</span>
<span class="score-col">{{ localDisplayConfig.individual?.scoreColumn?.displayStyle === 'amount' ? '¥' + item.score : item.score }}</span>
<span v-if="localDisplayConfig.individual?.showLevel" class="level-col" :class="`level-${item.level}`">{{ item.level }}</span>
<span v-if="localDisplayConfig.individual?.showDepartment" class="dept-col">{{ item.department }}</span>
<span class="bonus-col">¥{{ item.bonus }}</span>
</div>
</div>
</div>
<!-- 战队排名 -->
<div class="team-rankings">
<h2 class="section-title">👥 战队排名</h2>
<div class="rank-table">
<div class="table-header" :style="{ 'grid-template-columns': teamGridTemplate }">
<span class="rank-col">排名</span>
<span class="name-col">战队名称</span>
<span class="score-col">{{ localDisplayConfig.team?.totalScoreColumn?.displayName || '总分' }}</span>
<span v-if="localDisplayConfig.team?.showMemberCount" class="member-col">人数</span>
<span v-if="localDisplayConfig.team?.showLeader" class="leader-col">队长</span>
<span class="bonus-col">奖金</span>
</div>
<div
v-for="(item, index) in teamRankings"
:key="item.id"
class="table-row"
:style="{ 'grid-template-columns': teamGridTemplate }"
:class="{
'top-three': index < 3,
'highlight': index === 0
}"
>
<span class="rank-col">{{ index + 1 }}</span>
<span class="name-col">{{ item.name }}</span>
<span class="score-col">{{ localDisplayConfig.team?.totalScoreColumn?.displayStyle === 'amount' ? '¥' + item.totalScore : item.totalScore }}</span>
<span v-if="localDisplayConfig.team?.showMemberCount" class="member-col">{{ item.memberCount }}</span>
<span v-if="localDisplayConfig.team?.showLeader" class="leader-col">{{ item.leader }}</span>
<span class="bonus-col">¥{{ item.bonus }}</span>
</div>
</div>
</div>
</div>
</section>
<!-- 浮动管理员入口 -->
<div class="admin-entry-float">
<button @click="goToAdmin" class="admin-btn-float">
🔐 管理员入口
</button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch, computed } from 'vue';
import { useRouter } from 'vue-router';
import {
individualRankings,
teamRankings,
bonusRules,
displayConfig,
battleEndTime,
drumConfig,
initializeData
} from '../data/mockData.js';
// 创建本地显示配置的副本,确保深拷贝并提供默认值
const createDefaultDisplayConfig = () => ({
individual: {
scoreColumn: {
displayName: '得分',
displayStyle: 'number'
},
showLevel: true,
showDepartment: true,
columnWidths: {}
},
team: {
totalScoreColumn: {
displayName: '总分',
displayStyle: 'number'
},
showMemberCount: true,
showLeader: true,
columnWidths: {}
}
});
// 创建默认奖金规则
const createDefaultBonusRules = () => [
{ rank: '1-3', description: '前三名', individualBonus: '¥10000, ¥8000, ¥5000', teamBonus: '¥50000, ¥30000, ¥20000' },
{ rank: '4-10', description: '四至十名', individualBonus: '¥3000/人', teamBonus: '¥10000/队' },
{ rank: '11-20', description: '十一至二十名', individualBonus: '¥1000/人', teamBonus: '¥5000/队' }
];
const localDisplayConfig = ref(
displayConfig ? JSON.parse(JSON.stringify(displayConfig)) : createDefaultDisplayConfig()
);
// 确保奖金规则有默认值
const displayBonusRules = computed(() => {
return Array.isArray(bonusRules) && bonusRules.length > 0
? bonusRules
: createDefaultBonusRules();
});
const router = useRouter();
// 计算个人排名表格的列布局
const individualGridTemplate = computed(() => {
try {
const config = localDisplayConfig.value?.individual;
if (!config) return '60px 60px 1fr 80px 80px';
const widths = config.columnWidths || {};
const cols = [];
cols.push((widths.rank || 60) + 'px'); // 排名
cols.push((widths.avatar || 60) + 'px'); // 头像
cols.push((widths.name === 1 || widths.name === '1') ? '1fr' : (widths.name || 120) + 'px'); // 姓名
cols.push((widths.score || 80) + 'px'); // 分数
if (config.showLevel) {
cols.push((widths.level || 80) + 'px'); // 等级
}
if (config.showDepartment) {
cols.push((widths.department === 1 || widths.department === '1') ? '1fr' : (widths.department || 100) + 'px'); // 部门
}
cols.push((widths.bonus || 80) + 'px'); // 奖金
return cols.join(' ');
} catch (error) {
console.error('计算个人排名表格布局出错:', error);
return '60px 60px 1fr 80px 80px'; // 兜底布局
}
});
// 计算战队排名表格的列布局
const teamGridTemplate = computed(() => {
try {
const config = localDisplayConfig.value?.team;
if (!config) return '60px 1fr 80px 80px';
const widths = config.columnWidths || {};
const cols = [];
cols.push((widths.rank || 60) + 'px'); // 排名
cols.push((widths.name === 1 || widths.name === '1') ? '1fr' : (widths.name || 150) + 'px'); // 战队名
cols.push((widths.score || 80) + 'px'); // 分数
if (config.showMemberCount) {
cols.push((widths.memberCount || 60) + 'px'); // 人数
}
if (config.showLeader) {
cols.push((widths.leader === 1 || widths.leader === '1') ? '1fr' : (widths.leader || 120) + 'px'); // 队长
}
cols.push((widths.bonus || 80) + 'px'); // 奖金
return cols.join(' ');
} catch (error) {
console.error('计算战队排名表格布局出错:', error);
return '60px 1fr 80px 80px'; // 兜底布局
}
});
// 倒计时状态
const days = ref(0);
const hours = ref(0);
const minutes = ref(0);
const seconds = ref(0);
const milliseconds = ref(0);
// 战鼓动画状态
const isBeating = ref(false);
let beatInterval = null;
let countdownInterval = null;
// 音频上下文和战鼓音效
let audioContext = null;
const isPlayingSound = ref(false);
// 战鼓位置状态
const drumsPosition = ref({ x: 20, y: 20 });
let isDragging = false;
let dragOffset = { x: 0, y: 0 };
// 开始拖动
const startDrag = (e) => {
isDragging = true;
dragOffset.x = e.clientX - drumsPosition.value.x;
dragOffset.y = e.clientY - drumsPosition.value.y;
e.preventDefault();
};
// 拖动中
const drag = (e) => {
if (isDragging) {
drumsPosition.value.x = e.clientX - dragOffset.x;
drumsPosition.value.y = e.clientY - dragOffset.y;
}
};
// 结束拖动
const endDrag = () => {
isDragging = false;
};
// 计算倒计时
const calculateCountdown = () => {
// 使用配置的结束时间
const endDateStr = `${battleEndTime.date}T${battleEndTime.time}`;
const endDate = new Date(endDateStr).getTime();
const now = new Date().getTime();
const distance = endDate - now;
if (distance > 0) {
days.value = Math.floor(distance / (1000 * 60 * 60 * 24));
hours.value = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
minutes.value = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
seconds.value = Math.floor((distance % (1000 * 60)) / 1000);
// 确保显示0-999毫秒并格式化为3位数字
milliseconds.value = Math.floor(distance % 1000).toString().padStart(3, '0');
}
};
// 初始化音频上下文
const initAudioContext = () => {
if (!audioContext) {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
};
// 播放战鼓音效
const playDrumSound = (isStrongBeat = false) => {
if (!audioContext || isPlayingSound.value) return;
isPlayingSound.value = true;
try {
// 使用配置的音效参数
const soundConfig = drumConfig?.sound || {};
const patternConfig = drumConfig?.pattern || {};
// 创建振荡器节点
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
// 连接节点
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
// 设置战鼓音效参数,支持强拍
const baseVolume = soundConfig.volume || 1.0;
// 使用pattern配置中的强拍音量倍数
const accentMultiplier = patternConfig.accentMultiplier || 1.2;
const volume = isStrongBeat ? baseVolume * accentMultiplier : baseVolume;
// 使用soundConfig中的type1
oscillator.type = soundConfig.type1 || 'sine';
// 基础频率,支持强拍频率偏移
const baseFrequency = soundConfig.frequency1 || 150;
const frequencyOffset = isStrongBeat ? (patternConfig.accentFrequencyOffset || 0) / 100 : 0;
const actualFrequency = baseFrequency * (1 + frequencyOffset);
oscillator.frequency.setValueAtTime(actualFrequency, audioContext.currentTime);
// 频率渐变
oscillator.frequency.exponentialRampToValueAtTime(actualFrequency * 0.5, audioContext.currentTime + 0.1);
// 设置音量包络
gainNode.gain.setValueAtTime(0, audioContext.currentTime);
gainNode.gain.linearRampToValueAtTime(volume, audioContext.currentTime + (soundConfig.attackTime || 0.01));
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + (soundConfig.decayTime || 0.3));
// 播放声音
oscillator.start();
oscillator.stop(audioContext.currentTime + (soundConfig.decayTime || 0.3));
// 双音调效果 - 始终使用
const oscillator2 = audioContext.createOscillator();
const gainNode2 = audioContext.createGain();
oscillator2.connect(gainNode2);
gainNode2.connect(audioContext.destination);
oscillator2.type = soundConfig.type2 || 'triangle';
oscillator2.frequency.setValueAtTime(soundConfig.frequency2 || 100, audioContext.currentTime);
gainNode2.gain.setValueAtTime(0, audioContext.currentTime);
gainNode2.gain.linearRampToValueAtTime(volume * 0.8, audioContext.currentTime + (soundConfig.attackTime || 0.01));
gainNode2.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + (soundConfig.decayTime || 0.3) + 0.2);
oscillator2.start();
oscillator2.stop(audioContext.currentTime + (soundConfig.decayTime || 0.3) + 0.2);
// 设置完成后重置播放状态
setTimeout(() => {
isPlayingSound.value = false;
}, (soundConfig.decayTime || 0.3) * 1000 + 150);
} catch (error) {
console.error('播放战鼓音效出错:', error);
isPlayingSound.value = false;
}
};
// 战鼓动画效果
const startDrumAnimation = () => {
// 使用配置的动画和节拍参数
const animationConfig = drumConfig?.animation || {};
const patternConfig = drumConfig?.pattern || {};
// 检查是否启用动画
if (animationConfig.enabled === false) return;
let beatCount = 0;
// 使用配置的节拍间隔
const interval = animationConfig.beatInterval || 200;
beatInterval = setInterval(() => {
beatCount++;
// 使用配置的节拍模式和总拍数
const totalBeats = patternConfig.totalBeats || 4;
const currentBeat = ((beatCount - 1) % totalBeats) + 1;
// 根据节拍模式确定是否是强拍
const strongBeats = patternConfig.strongBeats || [1, 4];
const isStrongBeat = strongBeats.includes(currentBeat);
// 执行动画和音效
isBeating.value = true;
// 根据是否是强拍播放音效
playDrumSound(isStrongBeat);
// 设置CSS变量支持强拍动画增强
const drums = document.querySelectorAll('.drum');
drums.forEach(drum => {
// 使用配置的动画参数
drum.style.setProperty('--drum-scale', isStrongBeat ?
(animationConfig.beatScale || 1.3) * (1 + (patternConfig.accentAnimation || 0) / 100) :
(animationConfig.beatScale || 1.3));
drum.style.setProperty('--drum-translate-y', isStrongBeat ?
`${(animationConfig.beatTranslateY || -15) * (1 + (patternConfig.accentAnimation || 0) / 100)}px` :
`${animationConfig.beatTranslateY || -15}px`);
drum.style.setProperty('--drum-rotate', `${animationConfig.beatRotate || 5}deg`);
drum.style.setProperty('--drum-brightness', isStrongBeat ? '1.4' : '1.3');
drum.style.setProperty('--drum-saturation', isStrongBeat ? '1.3' : '1.2');
});
// 根据节拍类型设置持续时间
const beatDuration = isStrongBeat
? (animationConfig.beatDuration || 150)
: (animationConfig.beatDuration || 100);
setTimeout(() => {
isBeating.value = false;
}, beatDuration);
}, interval);
};
// 处理点击战鼓播放音效
const handleDrumClick = () => {
// 如果音频上下文未初始化,初始化它
if (!audioContext) {
initAudioContext();
}
// 如果音频上下文被暂停,恢复它
if (audioContext.state === 'suspended') {
audioContext.resume();
}
// 触发战鼓动画和音效,使用配置的点击效果
const animationConfig = drumConfig?.animation || {};
isBeating.value = true;
playDrumSound(true); // 点击总是强拍
setTimeout(() => {
isBeating.value = false;
}, animationConfig.clickBeatDuration || 250);
};
// 跳转到管理员页面
const goToAdmin = () => {
router.push('/admin');
};
// 监听窗口点击事件,用于用户交互后初始化音频上下文
document.addEventListener('click', initAudioContext, { once: true });
document.addEventListener('touchstart', initAudioContext, { once: true });
const handleResize = () => {
// 计算并设置排名明细区域的最小高度,使其底部与视口对齐
const rankingsSection = document.querySelector('.rankings-section');
if (rankingsSection) {
const windowHeight = window.innerHeight;
const rankingsTop = rankingsSection.offsetTop;
const rankingsMinHeight = windowHeight - rankingsTop - 20; // 减去一些边距
if (rankingsMinHeight > 0) {
rankingsSection.style.minHeight = rankingsMinHeight + 'px';
}
}
};
onMounted(async () => {
try {
// 异步初始化数据
await initializeData();
// 更新本地显示配置
localDisplayConfig.value = displayConfig ? JSON.parse(JSON.stringify(displayConfig)) : createDefaultDisplayConfig();
} catch (error) {
console.error('初始化数据失败:', error);
// 使用默认配置
localDisplayConfig.value = createDefaultDisplayConfig();
}
calculateCountdown();
countdownInterval = setInterval(calculateCountdown, 10); // 改为10ms更新一次以显示毫秒
startDrumAnimation();
// 监听窗口大小变化,确保排名明细与底部对齐
window.addEventListener('resize', handleResize);
handleResize(); // 初始调整
// 添加拖放相关的事件监听
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', endDrag);
});
// 监听结束时间变化在真实环境中可能需要通过props或store来监听
const handleBattleEndTimeChange = () => {
calculateCountdown();
};
// 监听显示配置变化在真实环境中可能需要通过props或store来监听
const handleDisplayConfigChange = () => {
// 更新本地配置
localDisplayConfig.value = {...displayConfig};
};
// 这里模拟监听配置变化
// 在实际项目中可能需要通过WebSocket或轮询来更新配置
onUnmounted(() => {
if (countdownInterval) clearInterval(countdownInterval);
if (beatInterval) clearInterval(beatInterval);
window.removeEventListener('resize', handleResize);
// 移除拖放相关的事件监听
document.removeEventListener('mousemove', drag);
document.removeEventListener('mouseup', endDrag);
// 清理音频资源
if (audioContext) {
audioContext.close();
audioContext = null;
}
});
</script>
<style scoped>
.battle-ranking {
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a24 100%);
padding-bottom: 30px;
}
/* 主题部分 */
.theme-section {
background: rgba(255, 255, 255, 0.95);
padding: 40px 0;
text-align: center;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
.main-title {
font-size: 2.5rem;
color: #d63031;
margin-bottom: 8px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
}
.subtitle {
font-size: 1.2rem;
color: #636e72;
margin-bottom: 20px;
}
.countdown {
font-size: 1.2rem;
margin-top: 20px;
}
.timer {
display: inline-block;
background: linear-gradient(45deg, #6c5ce7, #a29bfe);
color: white;
padding: 12px 25px;
border-radius: 50px;
font-weight: bold;
margin-bottom: 14px; /* 下移4px */
}
.time-item {
background: rgba(255, 255, 255, 0.2);
padding: 5px 10px;
border-radius: 5px;
margin: 0 5px;
font-size: 1.5rem;
transition: all 0.1s ease;
}
.time-item.milliseconds {
font-size: 1.2rem;
color: #ffcc00;
font-weight: bold;
animation: blink 0.5s infinite;
}
/* 毫秒闪烁动画增强紧张感 */
@keyframes blink {
0%, 50% { opacity: 1; transform: scale(1); }
51%, 100% { opacity: 0.7; transform: scale(0.95); }
}
/* 战鼓部分 - 浮动并支持拖放 */
.drums-section {
position: fixed;
left: 20px;
top: 20px;
padding: 20px;
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
cursor: move;
z-index: 100;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
transition: box-shadow 0.3s ease;
}
.drums-section:hover {
box-shadow: 0 6px 30px rgba(0, 0, 0, 0.25);
}
.drums-section:active {
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
}
.drums-container {
display: flex;
justify-content: center;
}
.drums-animation {
display: flex;
align-items: center;
gap: 20px;
font-size: 3rem;
}
.drum {
transition: transform 0.1s ease, filter 0.1s ease;
animation: idlePulse 2s infinite alternate;
}
.drum.beating {
/* 使用CSS变量方便动态调整 */
--drum-scale: 1.3;
--drum-translate-y: -15px;
--drum-rotate: 5deg;
--drum-brightness: 1.3;
--drum-saturation: 1.2;
transform: scale(var(--drum-scale)) translateY(var(--drum-translate-y)) rotate(var(--drum-rotate));
filter: brightness(var(--drum-brightness)) saturate(var(--drum-saturation));
animation: drumBeat 0.1s ease-in-out;
}
/* 战鼓闲置时的轻微脉动动画 */
@keyframes idlePulse {
0% { transform: scale(1); }
100% { transform: scale(1.05); }
}
/* 增强跳动效果的关键帧动画 */
@keyframes drumBeat {
0% { transform: scale(1); }
50% { transform: scale(var(--drum-scale, 1.3)) translateY(var(--drum-translate-y, -15px)) rotate(var(--drum-rotate, 5deg)); }
100% { transform: scale(1); }
}
.trophy {
animation: bounce 1s infinite alternate;
}
@keyframes bounce {
from { transform: translateY(0); }
to { transform: translateY(-10px); }
}
/* 奖金设置部分(行布局) */
.bonus-section {
margin: 10px 20px 15px 20px; /* 下移10px */
padding: 15px;
background: linear-gradient(135deg, #ffeaa7, #fab1a0);
border-radius: 20px;
}
.bonus-section h2 {
color: #d63031;
margin-bottom: 10px;
text-align: center;
font-size: 1.4rem;
}
.bonus-rules-row {
display: flex;
gap: 30px;
justify-content: center;
flex-wrap: wrap;
width: 100%;
}
.bonus-rule-item {
background: rgba(255, 255, 255, 0.8);
padding: 8px 10px;
border-radius: 12px;
border-left: 5px solid #e17055;
flex: 1;
min-width: 220px;
max-width: 100%;
transition: transform 0.3s ease;
box-sizing: border-box;
}
.bonus-rule-item:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.rule-header {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 4px;
font-weight: bold;
color: #d63031;
font-size: 1.2rem;
line-height: 1.2;
}
.rule-details p {
margin: 3px 0;
font-size: 1rem;
word-wrap: break-word;
white-space: normal;
line-height: 1.2;
}
/* 排名部分 */
.rankings-section {
margin: 0 20px 30px 20px;
}
.rankings-container {
display: flex;
gap: 15px;
flex-wrap: wrap;
justify-content: center;
}
.individual-rankings,
.team-rankings {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 20px;
flex: 1;
min-width: 350px;
max-width: 550px;
}
.section-title {
color: #d63031;
text-align: center;
margin-bottom: 15px;
font-size: 1.6rem;
}
.rank-table {
border-radius: 10px;
overflow: hidden;
min-height: 600px; /* 确保至少显示10行 */
max-height: 600px;
overflow-y: auto;
position: relative;
}
.table-header {
background: linear-gradient(45deg, #6c5ce7, #a29bfe);
color: white;
display: grid;
padding: 12px 10px;
font-weight: bold;
position: sticky;
top: 0;
z-index: 10;
}
.team-rankings .table-header {
position: sticky;
top: 0;
z-index: 10;
}
.table-row {
display: grid;
padding: 12px 10px;
border-bottom: 1px solid #eee;
transition: background-color 0.3s;
}
.team-rankings .table-row {
padding: 12px 10px;
}
.table-row:hover {
background-color: #f8f9fa;
}
.table-row.top-three {
background-color: #fff3cd;
font-size: 1.1rem;
font-weight: bold;
transform: scale(1.02);
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.table-row.top-three:hover {
transform: scale(1.03);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15);
}
/* 前三名特殊背景色 */
.table-row:nth-child(1) {
background: linear-gradient(135deg, #ffd700, #ffed4a);
color: #333;
}
.table-row:nth-child(2) {
background: linear-gradient(135deg, #c0c0c0, #e0e0e0);
color: #333;
}
.table-row:nth-child(3) {
background: linear-gradient(135deg, #cd7f32, #d7ccc8);
color: #333;
}
.table-row.highlight {
background: linear-gradient(135deg, #ffd32a, #ff7979);
color: white;
}
.level-SSS {
color: #ffd700;
font-weight: bold;
}
.level-SS {
color: #c0c0c0;
font-weight: bold;
}
.level-S {
color: #cd7f32;
font-weight: bold;
}
.level-A {
color: #4caf50;
font-weight: bold;
}
.level-B {
color: #2196f3;
font-weight: bold;
}
/* 浮动管理员入口 */
.admin-entry-float {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
}
.admin-btn-float {
background: linear-gradient(45deg, #6c5ce7, #a29bfe);
color: white;
border: none;
padding: 12px 20px;
border-radius: 30px;
font-size: 1rem;
cursor: pointer;
box-shadow: 0 4px 20px rgba(108, 92, 231, 0.4);
transition: all 0.3s ease;
border: 2px solid rgba(255, 255, 255, 0.3);
}
.admin-btn-float:hover {
transform: scale(1.1) translateY(-2px);
box-shadow: 0 6px 30px rgba(108, 92, 231, 0.6);
background: linear-gradient(45deg, #7f7fd5, #86a8e7);
}
/* 响应式设计 */
@media (max-width: 768px) {
.main-title {
font-size: 2rem;
}
.drums-container {
flex-direction: column;
gap: 20px;
}
.rankings-container {
flex-direction: column;
}
.individual-rankings,
.team-rankings {
min-width: auto;
}
.table-header,
.table-row {
font-size: 0.9rem;
}
}
</style>