单败重构
This commit is contained in:
parent
f5bf2f01f5
commit
afd5323ade
@ -2,43 +2,6 @@
|
||||
<div class="tournament-bracket-root">
|
||||
<h1>双败淘汰赛赛程树状图</h1>
|
||||
<div class="container">
|
||||
<!-- 控制面板 -->
|
||||
<div class="control-panel">
|
||||
<div class="tournament-info">
|
||||
<div>
|
||||
<strong>当前比赛:</strong> {{ tournament.name || '加载中...' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>参赛者列表</h2>
|
||||
<div id="player-list">
|
||||
<div v-if="loading" class="loading">加载中...</div>
|
||||
<div v-else>
|
||||
<p v-if="participants.length === 0">暂无参赛者</p>
|
||||
<template v-else>
|
||||
<p>共 {{ participants.length }} 位参赛者</p>
|
||||
<ul>
|
||||
<li v-for="participant in participants" :key="participant.id">
|
||||
{{ participant.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>生成赛程</h2>
|
||||
<button @click="generateDoubleEliminationBracket" :disabled="!canGenerateBracket">
|
||||
生成双败淘汰赛程
|
||||
</button>
|
||||
|
||||
<div v-if="champion" class="final-ranking">
|
||||
<h3>最终排名</h3>
|
||||
<p v-if="champion">🏆 冠军: {{ champion }}</p>
|
||||
<p v-if="runnerUp">🥈 亚军: {{ runnerUp }}</p>
|
||||
<p v-if="thirdPlace">🥉 季军: {{ thirdPlace }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 比赛对战表 -->
|
||||
<div class="bracket-main">
|
||||
<!-- 胜者组 -->
|
||||
@ -145,9 +108,7 @@ let winnerssvgSelection = null;
|
||||
let losersZoomBehavior = null;
|
||||
let loserssvgSelection = null;
|
||||
|
||||
const canGenerateBracket = computed(() => {
|
||||
return participants.value.length >= 4;
|
||||
});
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
loadTournamentData();
|
||||
@ -155,7 +116,6 @@ onMounted(() => {
|
||||
// Add keyboard shortcuts for zoom control
|
||||
const handleKeydown = (event) => {
|
||||
if (event.target.tagName === 'INPUT') return;
|
||||
|
||||
switch(event.key) {
|
||||
case '+':
|
||||
case '=':
|
||||
@ -220,6 +180,11 @@ const loadTournamentData = async () => {
|
||||
qq_code: item.qq_code || '',
|
||||
status: item.status || ''
|
||||
}));
|
||||
|
||||
// 数据加载完成后自动生成赛程
|
||||
if (participants.value.length >= 4) {
|
||||
generateDoubleEliminationBracket();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('获取比赛数据失败:', err);
|
||||
} finally {
|
||||
@ -248,21 +213,17 @@ const generateDoubleEliminationBracket = () => {
|
||||
while (shuffledParticipants.length < totalSlots) {
|
||||
shuffledParticipants.push(null);
|
||||
}
|
||||
|
||||
console.log(`总参赛者: ${totalPlayers}, 总槽位: ${totalSlots}`);
|
||||
|
||||
// 生成胜者组第一轮
|
||||
const winnersRound1 = [];
|
||||
for (let i = 0; i < totalSlots; i += 2) {
|
||||
const p1 = shuffledParticipants[i];
|
||||
const p2 = shuffledParticipants[i + 1];
|
||||
|
||||
// 处理轮空情况
|
||||
let winner = null;
|
||||
let decided = false;
|
||||
let score1 = 0;
|
||||
let score2 = 0;
|
||||
|
||||
if (p1 && !p2) {
|
||||
// p1轮空直接晋级
|
||||
winner = p1;
|
||||
@ -297,7 +258,6 @@ const generateDoubleEliminationBracket = () => {
|
||||
});
|
||||
}
|
||||
winnersBracket.value = [winnersRound1];
|
||||
|
||||
// 预生成胜者组的所有轮次结构
|
||||
let currentRoundSize = Math.ceil(totalSlots / 2);
|
||||
while (currentRoundSize > 1) {
|
||||
@ -318,12 +278,9 @@ const generateDoubleEliminationBracket = () => {
|
||||
winnersBracket.value.push(nextRound);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`胜者组轮次数: ${winnersBracket.value.length}`);
|
||||
|
||||
// 预生成败者组结构
|
||||
generateLosersBracketStructure(totalPlayers);
|
||||
|
||||
// 处理第一轮的轮空晋级
|
||||
winnersRound1.forEach((match, index) => {
|
||||
if (match.decided && match.winner) {
|
||||
@ -370,11 +327,9 @@ const generateLosersBracketStructure = (totalPlayers) => {
|
||||
for (let round = 0; round < losersRounds; round++) {
|
||||
const roundMatches = [];
|
||||
let matchCount = 1;
|
||||
|
||||
// 败者组比赛数量规律:
|
||||
// LR1: 1场 (胜者组第1轮败者们对战,但要考虑轮空)
|
||||
// LR2: 1场 (LR1胜者 vs 胜者组第2轮败者)
|
||||
|
||||
if (round === 0) {
|
||||
// 第一轮败者组:计算实际的败者数量
|
||||
const firstRoundLosers = Math.floor(totalPlayers / 2); // 第一轮实际对战产生的败者数
|
||||
@ -387,10 +342,8 @@ const generateLosersBracketStructure = (totalPlayers) => {
|
||||
} else {
|
||||
matchCount = 1; // 其他轮次通常是1场
|
||||
}
|
||||
|
||||
// 确保至少有0场比赛(可能某轮不需要比赛)
|
||||
matchCount = Math.max(0, matchCount);
|
||||
|
||||
if (matchCount > 0) {
|
||||
for (let i = 0; i < matchCount; i++) {
|
||||
roundMatches.push({
|
||||
@ -430,7 +383,6 @@ const drawD3LosersBracket = () => {
|
||||
|
||||
drawD3Bracket(svg, losersBracket.value, 'losersBracketContainer', 'losers');
|
||||
};
|
||||
|
||||
// Generic D3.js bracket drawing function
|
||||
const drawD3Bracket = (svg, bracket, containerId, bracketType) => {
|
||||
const margin = { top: 40, right: 40, bottom: 40, left: 40 };
|
||||
@ -1134,37 +1086,8 @@ h1 {
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
.control-panel {
|
||||
flex: 1;
|
||||
min-width: 280px;
|
||||
background: #f0f0f0;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tournament-info {
|
||||
margin-bottom: 15px;
|
||||
padding: 10px;
|
||||
background: #e6f7ff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.control-panel h2 {
|
||||
color: #1a237e;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
margin: 20px 0 10px 0;
|
||||
}
|
||||
|
||||
.loading {
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.bracket-main {
|
||||
flex: 3;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
@ -1336,31 +1259,5 @@ button:hover:not(:disabled) {
|
||||
background: #66b1ff;
|
||||
}
|
||||
|
||||
.final-ranking {
|
||||
background: #fffbdb;
|
||||
border: 1px solid #f0e68c;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.final-ranking h3 {
|
||||
margin-top: 0;
|
||||
color: #b8860b;
|
||||
}
|
||||
|
||||
.final-ranking p {
|
||||
margin: 5px 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 20px;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 4px;
|
||||
list-style: disc;
|
||||
}
|
||||
</style>
|
@ -291,18 +291,18 @@ const generateRoundMatches = async (round, roundData) => {
|
||||
|
||||
// 处理已有数据的比赛
|
||||
for (const player of roundData) {
|
||||
if (usedPlayers.has(player.id)) {
|
||||
if (usedPlayers.has(player.id)) {
|
||||
console.log(`玩家 ${player.sign_name} (ID: ${player.id}) 已被使用,跳过`);
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const rivalName = player.rival_name;
|
||||
const isBye = rivalName === '轮空' || rivalName === 'bye' || rivalName === 'BYE' || rivalName === 'Bye';
|
||||
const isPending = rivalName === '待定' || rivalName === 'pending' || rivalName === 'PENDING' || rivalName === 'Pending';
|
||||
|
||||
if (isBye || isPending) {
|
||||
if (isBye || isPending) {
|
||||
console.log(`玩家 ${player.sign_name} 的对手是 ${rivalName},跳过正常比赛处理`);
|
||||
continue;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 寻找对手
|
||||
@ -665,7 +665,7 @@ const matchOpponentsInRound = async (round) => {
|
||||
} else {
|
||||
console.log(`第${round}轮待定玩家数量为${roundData.length},无法匹配`);
|
||||
}
|
||||
} catch (error) {
|
||||
} catch (error) {
|
||||
console.error(`匹配第${round}轮对手失败:`, error);
|
||||
}
|
||||
};
|
||||
|
@ -366,7 +366,6 @@ function handlePasswordChangeError(errorMessage) {
|
||||
<router-link to="competition" class="nav-link" @click.prevent="handleNavClick('/competition')">赛程信息</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 公共信息区 一级菜单 -->
|
||||
<div class="nav-dropdown">
|
||||
<span class="nav-link">公共信息区</span>
|
||||
|
Loading…
x
Reference in New Issue
Block a user