单败重构
This commit is contained in:
parent
f5bf2f01f5
commit
afd5323ade
@ -2,43 +2,6 @@
|
|||||||
<div class="tournament-bracket-root">
|
<div class="tournament-bracket-root">
|
||||||
<h1>双败淘汰赛赛程树状图</h1>
|
<h1>双败淘汰赛赛程树状图</h1>
|
||||||
<div class="container">
|
<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">
|
<div class="bracket-main">
|
||||||
<!-- 胜者组 -->
|
<!-- 胜者组 -->
|
||||||
@ -145,9 +108,7 @@ let winnerssvgSelection = null;
|
|||||||
let losersZoomBehavior = null;
|
let losersZoomBehavior = null;
|
||||||
let loserssvgSelection = null;
|
let loserssvgSelection = null;
|
||||||
|
|
||||||
const canGenerateBracket = computed(() => {
|
|
||||||
return participants.value.length >= 4;
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadTournamentData();
|
loadTournamentData();
|
||||||
@ -155,7 +116,6 @@ onMounted(() => {
|
|||||||
// Add keyboard shortcuts for zoom control
|
// Add keyboard shortcuts for zoom control
|
||||||
const handleKeydown = (event) => {
|
const handleKeydown = (event) => {
|
||||||
if (event.target.tagName === 'INPUT') return;
|
if (event.target.tagName === 'INPUT') return;
|
||||||
|
|
||||||
switch(event.key) {
|
switch(event.key) {
|
||||||
case '+':
|
case '+':
|
||||||
case '=':
|
case '=':
|
||||||
@ -220,6 +180,11 @@ const loadTournamentData = async () => {
|
|||||||
qq_code: item.qq_code || '',
|
qq_code: item.qq_code || '',
|
||||||
status: item.status || ''
|
status: item.status || ''
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// 数据加载完成后自动生成赛程
|
||||||
|
if (participants.value.length >= 4) {
|
||||||
|
generateDoubleEliminationBracket();
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('获取比赛数据失败:', err);
|
console.error('获取比赛数据失败:', err);
|
||||||
} finally {
|
} finally {
|
||||||
@ -248,21 +213,17 @@ const generateDoubleEliminationBracket = () => {
|
|||||||
while (shuffledParticipants.length < totalSlots) {
|
while (shuffledParticipants.length < totalSlots) {
|
||||||
shuffledParticipants.push(null);
|
shuffledParticipants.push(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`总参赛者: ${totalPlayers}, 总槽位: ${totalSlots}`);
|
console.log(`总参赛者: ${totalPlayers}, 总槽位: ${totalSlots}`);
|
||||||
|
|
||||||
// 生成胜者组第一轮
|
// 生成胜者组第一轮
|
||||||
const winnersRound1 = [];
|
const winnersRound1 = [];
|
||||||
for (let i = 0; i < totalSlots; i += 2) {
|
for (let i = 0; i < totalSlots; i += 2) {
|
||||||
const p1 = shuffledParticipants[i];
|
const p1 = shuffledParticipants[i];
|
||||||
const p2 = shuffledParticipants[i + 1];
|
const p2 = shuffledParticipants[i + 1];
|
||||||
|
|
||||||
// 处理轮空情况
|
// 处理轮空情况
|
||||||
let winner = null;
|
let winner = null;
|
||||||
let decided = false;
|
let decided = false;
|
||||||
let score1 = 0;
|
let score1 = 0;
|
||||||
let score2 = 0;
|
let score2 = 0;
|
||||||
|
|
||||||
if (p1 && !p2) {
|
if (p1 && !p2) {
|
||||||
// p1轮空直接晋级
|
// p1轮空直接晋级
|
||||||
winner = p1;
|
winner = p1;
|
||||||
@ -297,7 +258,6 @@ const generateDoubleEliminationBracket = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
winnersBracket.value = [winnersRound1];
|
winnersBracket.value = [winnersRound1];
|
||||||
|
|
||||||
// 预生成胜者组的所有轮次结构
|
// 预生成胜者组的所有轮次结构
|
||||||
let currentRoundSize = Math.ceil(totalSlots / 2);
|
let currentRoundSize = Math.ceil(totalSlots / 2);
|
||||||
while (currentRoundSize > 1) {
|
while (currentRoundSize > 1) {
|
||||||
@ -318,12 +278,9 @@ const generateDoubleEliminationBracket = () => {
|
|||||||
winnersBracket.value.push(nextRound);
|
winnersBracket.value.push(nextRound);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`胜者组轮次数: ${winnersBracket.value.length}`);
|
console.log(`胜者组轮次数: ${winnersBracket.value.length}`);
|
||||||
|
|
||||||
// 预生成败者组结构
|
// 预生成败者组结构
|
||||||
generateLosersBracketStructure(totalPlayers);
|
generateLosersBracketStructure(totalPlayers);
|
||||||
|
|
||||||
// 处理第一轮的轮空晋级
|
// 处理第一轮的轮空晋级
|
||||||
winnersRound1.forEach((match, index) => {
|
winnersRound1.forEach((match, index) => {
|
||||||
if (match.decided && match.winner) {
|
if (match.decided && match.winner) {
|
||||||
@ -370,11 +327,9 @@ const generateLosersBracketStructure = (totalPlayers) => {
|
|||||||
for (let round = 0; round < losersRounds; round++) {
|
for (let round = 0; round < losersRounds; round++) {
|
||||||
const roundMatches = [];
|
const roundMatches = [];
|
||||||
let matchCount = 1;
|
let matchCount = 1;
|
||||||
|
|
||||||
// 败者组比赛数量规律:
|
// 败者组比赛数量规律:
|
||||||
// LR1: 1场 (胜者组第1轮败者们对战,但要考虑轮空)
|
// LR1: 1场 (胜者组第1轮败者们对战,但要考虑轮空)
|
||||||
// LR2: 1场 (LR1胜者 vs 胜者组第2轮败者)
|
// LR2: 1场 (LR1胜者 vs 胜者组第2轮败者)
|
||||||
|
|
||||||
if (round === 0) {
|
if (round === 0) {
|
||||||
// 第一轮败者组:计算实际的败者数量
|
// 第一轮败者组:计算实际的败者数量
|
||||||
const firstRoundLosers = Math.floor(totalPlayers / 2); // 第一轮实际对战产生的败者数
|
const firstRoundLosers = Math.floor(totalPlayers / 2); // 第一轮实际对战产生的败者数
|
||||||
@ -387,10 +342,8 @@ const generateLosersBracketStructure = (totalPlayers) => {
|
|||||||
} else {
|
} else {
|
||||||
matchCount = 1; // 其他轮次通常是1场
|
matchCount = 1; // 其他轮次通常是1场
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保至少有0场比赛(可能某轮不需要比赛)
|
// 确保至少有0场比赛(可能某轮不需要比赛)
|
||||||
matchCount = Math.max(0, matchCount);
|
matchCount = Math.max(0, matchCount);
|
||||||
|
|
||||||
if (matchCount > 0) {
|
if (matchCount > 0) {
|
||||||
for (let i = 0; i < matchCount; i++) {
|
for (let i = 0; i < matchCount; i++) {
|
||||||
roundMatches.push({
|
roundMatches.push({
|
||||||
@ -430,7 +383,6 @@ const drawD3LosersBracket = () => {
|
|||||||
|
|
||||||
drawD3Bracket(svg, losersBracket.value, 'losersBracketContainer', 'losers');
|
drawD3Bracket(svg, losersBracket.value, 'losersBracketContainer', 'losers');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generic D3.js bracket drawing function
|
// Generic D3.js bracket drawing function
|
||||||
const drawD3Bracket = (svg, bracket, containerId, bracketType) => {
|
const drawD3Bracket = (svg, bracket, containerId, bracketType) => {
|
||||||
const margin = { top: 40, right: 40, bottom: 40, left: 40 };
|
const margin = { top: 40, right: 40, bottom: 40, left: 40 };
|
||||||
@ -1134,37 +1086,8 @@ h1 {
|
|||||||
min-height: 500px;
|
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 {
|
.bracket-main {
|
||||||
flex: 3;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
@ -1336,31 +1259,5 @@ button:hover:not(:disabled) {
|
|||||||
background: #66b1ff;
|
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>
|
</style>
|
@ -366,7 +366,6 @@ function handlePasswordChangeError(errorMessage) {
|
|||||||
<router-link to="competition" class="nav-link" @click.prevent="handleNavClick('/competition')">赛程信息</router-link>
|
<router-link to="competition" class="nav-link" @click.prevent="handleNavClick('/competition')">赛程信息</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 公共信息区 一级菜单 -->
|
<!-- 公共信息区 一级菜单 -->
|
||||||
<div class="nav-dropdown">
|
<div class="nav-dropdown">
|
||||||
<span class="nav-link">公共信息区</span>
|
<span class="nav-link">公共信息区</span>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user