重置密码的60s冷却🐱🐱

This commit is contained in:
Kunagisa 2025-07-25 19:31:28 +08:00
parent 4c15c42ebc
commit 4828d0bcd6
10 changed files with 558 additions and 414 deletions

View File

@ -1,8 +1,8 @@
import axios from 'axios';
import { logoutUser } from '../utils/jwt'; // logoutUser会处理清除存储和重定向
//const API_BASE_URL = 'https://api.zybdatasupport.online';
const API_BASE_URL = 'http://hk.zybdatasupport.online:8000/';
const API_BASE_URL = 'https://api.zybdatasupport.online';
//const API_BASE_URL = 'http://hk.zybdatasupport.online:8000/';
const axiosInstance = axios.create({
baseURL: API_BASE_URL,

BIN
src/assets/login_4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1024 KiB

BIN
src/assets/login_5.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

View File

@ -1,251 +1,437 @@
<template>
<div class="tournament-bracket-root">
<h1>双败淘汰赛赛程 (BO3)</h1>
<h1>双败淘汰赛赛程树状</h1>
<div class="container">
<div class="bracket-container">
<div ref="bracket" class="bracket"></div>
<!-- 控制面板 -->
<div class="control-panel">
<h2>选择比赛</h2>
<div class="tournament-info">
<select v-model="selectedTournamentId" @change="handleTournamentChange">
<option value="">-- 请选择比赛 --</option>
<option v-for="tournament in tournaments" :key="tournament.id" :value="tournament.id">
{{ tournament.name }}
</option>
</select>
<div v-if="selectedTournamentId">
<strong>已选择:</strong> {{ selectedTournamentName }}
</div>
</div>
<h2>参赛者列表</h2>
<div id="player-list">
<div v-if="!selectedTournamentId" class="loading">请先选择比赛</div>
<div v-else-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>
<!-- 胜者组 -->
<div class="bracket-section">
<h2>胜者组</h2>
<ul>
<li v-for="(round, rIndex) in winnersBracket" :key="'w-' + rIndex">
<h3>胜者组 - {{ rIndex + 1 }} </h3>
<ul>
<li v-for="(match, mIndex) in round" :key="'w-' + rIndex + '-' + mIndex">
<div class="match">
<div class="participant" :class="{ winner: match.winner && match.participant1 && match.winner.id === match.participant1.id }">
{{ match.participant1 ? match.participant1.name : '轮空' }}
<input type="number" v-model.number="match.score1" :disabled="match.decided || !match.participant1 || !match.participant2" placeholder="0">
</div>
<div class="participant" :class="{ winner: match.winner && match.participant2 && match.winner.id === match.participant2.id }">
{{ match.participant2 ? match.participant2.name : '轮空' }}
<input type="number" v-model.number="match.score2" :disabled="match.decided || !match.participant1 || !match.participant2" placeholder="0">
</div>
<button @click="confirmScore(match, 'winners', rIndex, mIndex)" :disabled="match.decided || !match.participant1 || !match.participant2">
确认比分
</button>
</div>
</li>
</ul>
</li>
</ul>
</div>
<!-- 败者组 -->
<div class="bracket-section">
<h2>败者组</h2>
<ul>
<li v-for="(round, rIndex) in losersBracket" :key="'l-' + rIndex">
<h3>败者组 - {{ rIndex + 1 }} </h3>
<ul>
<li v-for="(match, mIndex) in round" :key="'l-' + rIndex + '-' + mIndex">
<div class="match">
<div class="participant" :class="{ winner: match.winner && match.participant1 && match.winner.id === match.participant1.id }">
{{ match.participant1 ? match.participant1.name : '轮空' }}
<input type="number" v-model.number="match.score1" :disabled="match.decided || !match.participant1 || !match.participant2" placeholder="0">
</div>
<div class="participant" :class="{ winner: match.winner && match.participant2 && match.winner.id === match.participant2.id }">
{{ match.participant2 ? match.participant2.name : '轮空' }}
<input type="number" v-model.number="match.score2" :disabled="match.decided || !match.participant1 || !match.participant2" placeholder="0">
</div>
<button @click="confirmScore(match, 'losers', rIndex, mIndex)" :disabled="match.decided || !match.participant1 || !match.participant2">
确认比分
</button>
</div>
</li>
</ul>
</li>
</ul>
</div>
<!-- 决赛 -->
<div class="bracket-section" v-if="finalMatch">
<h2>决赛 (BO5)</h2>
<div class="match">
<div class="participant" :class="{ winner: finalMatch.decided && finalMatch.score1 > finalMatch.score2 }">
{{ finalMatch.participant1 ? finalMatch.participant1.name : '待定' }}
<input type="number" v-model.number="finalMatch.score1" :disabled="finalMatch.decided" placeholder="0">
</div>
<div class="participant" :class="{ winner: finalMatch.decided && finalMatch.score2 > finalMatch.score1 }">
{{ finalMatch.participant2 ? finalMatch.participant2.name : '待定' }}
<input type="number" v-model.number="finalMatch.score2" :disabled="finalMatch.decided" placeholder="0">
</div>
<button @click="confirmFinal" :disabled="finalMatch.decided">确认决赛比分</button>
</div>
<div v-if="finalMatch.decided" 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>
<div ref="finalRanking"></div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
<script>
import { getSignUpResultList, updateSignUpResult, getTournamentList } from '@/api/tournament';
const totalPlayers = 8
const sampleNames = ["小明", "小红", "小刚", "小美", "阿强", "阿花", "老李", "老王", "张三", "李四", "王五", "赵六"]
const shuffledPlayers = sampleNames.sort(() => Math.random() - 0.5).slice(0, totalPlayers)
let rounds = {
winners: [], //
losers: [], //
finals: [] //
}
const bracket = ref(null)
const finalRanking = ref(null)
function initWinners() {
const firstRound = []
for(let i=0; i < totalPlayers; i+=2) {
firstRound.push({
player1: shuffledPlayers[i],
player2: shuffledPlayers[i+1],
score1: 0,
score2: 0,
done: false,
winner: null,
loser: null,
})
}
rounds.winners.push(firstRound)
}
function getLosersRoundsCount(nPlayers) {
return Math.ceil(Math.log2(nPlayers)) + 1
}
function initLosers() {
let roundsCount = getLosersRoundsCount(totalPlayers)
for(let i=0; i < roundsCount; i++) {
rounds.losers.push([])
}
}
function validScore(s1, s2) {
if(s1 === s2) return false
if(s1 < 0 || s2 < 0) return false
if(s1 > 2 || s2 > 2) return false
if(s1 !== 2 && s2 !== 2) return false
return true
}
function generateBracket() {
if (!bracket.value) return
bracket.value.innerHTML = ''
function createRound(title, matches, type, roundIndex) {
const roundDiv = document.createElement('div')
roundDiv.className = 'round'
const titleDiv = document.createElement('div')
titleDiv.className = 'round-title'
titleDiv.textContent = title
roundDiv.appendChild(titleDiv)
matches.forEach((match, matchIndex) => {
const matchDiv = document.createElement('div')
matchDiv.className = 'match'
const p1 = match.player1 || '待定'
const p2 = match.player2 || '待定'
let p1Class = ''
let p2Class = ''
if(match.done && match.winner) {
if(match.winner === match.player1) p1Class = 'participant winner'
if(match.winner === match.player2) p2Class = 'participant winner'
export default {
data() {
return {
selectedTournamentId: '',
selectedTournamentName: '',
tournaments: [],
participants: [],
winnersBracket: [],
losersBracket: [],
finalMatch: null,
champion: null,
runnerUp: null,
thirdPlace: null,
loading: false,
pointsMultiplier: {
winners: 1,
losers: 0.5
}
};
},
computed: {
canGenerateBracket() {
return this.participants.length >= 4;
}
},
mounted() {
this.fetchTournamentList();
},
methods: {
async fetchTournamentList() {
try {
const data = await getTournamentList();
this.tournaments = data;
} catch (err) {
console.error('获取比赛列表失败:', err);
}
},
async handleTournamentChange() {
if (!this.selectedTournamentId) {
this.selectedTournamentName = '';
this.participants = [];
return;
}
const disabled = match.done ? 'disabled' : ''
const selected = this.tournaments.find(t => t.id === this.selectedTournamentId);
this.selectedTournamentName = selected ? selected.name : '';
this.loadParticipants();
},
async loadParticipants() {
this.loading = true;
try {
const data = await getSignUpResultList();
const filtered = data.filter(item => item.tournament_id == this.selectedTournamentId);
this.participants = filtered.map(item => ({
id: item.id,
name: item.sign_name,
team_name: item.team_name || '',
win: item.win || '0',
lose: item.lose || '0',
qq_code: item.qq_code || '',
status: item.status || ''
}));
} catch (err) {
console.error('获取参赛者失败:', err);
} finally {
this.loading = false;
}
},
generateDoubleEliminationBracket() {
const round1 = [];
for (let i = 0; i < this.participants.length; i += 2) {
round1.push({
participant1: this.participants[i],
participant2: this.participants[i + 1] || null,
score1: 0,
score2: 0,
decided: false,
winner: null,
loser: null
});
}
this.winnersBracket = [round1];
this.losersBracket = [];
this.finalMatch = null;
this.champion = this.runnerUp = this.thirdPlace = null;
},
async confirmScore(match, bracketType, rIndex, mIndex) {
if (match.score1 === match.score2) return alert('不能平局');
match.decided = true;
match.winner = match.score1 > match.score2 ? match.participant1 : match.participant2;
match.loser = match.score1 > match.score2 ? match.participant2 : match.participant1;
//
await this.updatePlayerStats(match.participant1, match.participant2, match.score1, match.score2, bracketType);
matchDiv.innerHTML = `
<div class="participant ${p1Class}">${p1}</div>
<div class="participant ${p2Class}">${p2}</div>
<div>
<input type="number" min="0" max="2" class="score-input" id="${type}-${roundIndex}-${matchIndex}-score1" value="${match.score1}" ${disabled}>
:
<input type="number" min="0" max="2" class="score-input" id="${type}-${roundIndex}-${matchIndex}-score2" value="${match.score2}" ${disabled}>
</div>
<button class="score-btn" id="${type}-${roundIndex}-${matchIndex}-btn" ${disabled}>提交</button>
`
roundDiv.appendChild(matchDiv)
//
const bracket = bracketType === 'winners' ? this.winnersBracket : this.losersBracket;
if (!bracket[rIndex + 1]) {
bracket[rIndex + 1] = [];
}
const nextRound = bracket[rIndex + 1];
const insertIndex = Math.floor(mIndex / 2);
const isEven = mIndex % 2 === 0;
if (!nextRound[insertIndex]) {
nextRound[insertIndex] = { participant1: null, participant2: null, score1: 0, score2: 0, decided: false, winner: null, loser: null };
}
if (isEven) {
nextRound[insertIndex].participant1 = match.winner;
} else {
nextRound[insertIndex].participant2 = match.winner;
}
//
if (!match.done) {
setTimeout(() => {
const btn = document.getElementById(`${type}-${roundIndex}-${matchIndex}-btn`)
if (btn) {
btn.onclick = () => submitMatch(type, roundIndex, matchIndex)
if (bracketType === 'winners') {
this.winnersBracket[rIndex + 1] = nextRound;
//
if (!this.losersBracket[rIndex]) {
this.losersBracket[rIndex] = [];
}
const losersR = this.losersBracket[rIndex];
if (!losersR[insertIndex]) {
losersR[insertIndex] = { participant1: null, participant2: null, score1: 0, score2: 0, decided: false, winner: null, loser: null };
}
if (isEven) {
losersR[insertIndex].participant1 = match.loser;
} else {
losersR[insertIndex].participant2 = match.loser;
}
this.losersBracket[rIndex] = losersR;
} else {
//
this.losersBracket[rIndex + 1] = nextRound;
//
const lastWinnersRound = this.winnersBracket[this.winnersBracket.length - 1];
const lastLosersRound = this.losersBracket[this.losersBracket.length - 1];
if (lastWinnersRound && lastWinnersRound.length === 1 && lastLosersRound && lastLosersRound.length === 1) {
const winnerFromWinners = lastWinnersRound[0].winner;
const winnerFromLosers = lastLosersRound[0].winner;
if (winnerFromWinners && winnerFromLosers) {
this.finalMatch = {
participant1: winnerFromWinners,
participant2: winnerFromLosers,
score1: 0,
score2: 0,
decided: false
};
}
}, 0)
}
}
})
return roundDiv
}
},
async confirmFinal() {
const match = this.finalMatch;
if (!match || match.score1 === match.score2) return alert('决赛不能平局');
match.decided = true;
// participant1participant2
//
const winnerFromWinners = this.winnersBracket[this.winnersBracket.length - 1][0].winner;
const winnerFromLosers = this.losersBracket[this.losersBracket.length - 1][0].winner;
//
const winner = match.score1 > match.score2 ? match.participant1 : match.participant2;
const loser = match.score1 > match.score2 ? match.participant2 : match.participant1;
rounds.winners.forEach((r, i) => {
bracket.value.appendChild(createRound(`胜者组第 ${i+1}`, r, 'winners', i))
})
rounds.losers.forEach((r, i) => {
bracket.value.appendChild(createRound(`败者组第 ${i+1}`, r, 'losers', i))
})
if(rounds.finals.length > 0) {
bracket.value.appendChild(createRound('总决赛', rounds.finals, 'finals', 0))
}
}
// - 使
await this.updatePlayerStats(match.participant1, match.participant2, match.score1, match.score2, 'winners');
function submitMatch(type, roundIndex, matchIndex) {
let match = rounds[type][roundIndex][matchIndex]
if(match.done) return
//
this.champion = winner.name || winner;
this.runnerUp = loser.name || loser;
//
//
//
//
if (this.losersBracket.length > 0) {
const lastLoserRound = this.losersBracket[this.losersBracket.length - 1];
if (lastLoserRound && lastLoserRound.length > 0) {
const lastLoserMatch = lastLoserRound[lastLoserRound.length - 1];
if (lastLoserMatch && lastLoserMatch.loser) {
this.thirdPlace = lastLoserMatch.loser.name || lastLoserMatch.loser;
}
}
}
const s1 = parseInt(document.getElementById(`${type}-${roundIndex}-${matchIndex}-score1`).value)
const s2 = parseInt(document.getElementById(`${type}-${roundIndex}-${matchIndex}-score2`).value)
if(!validScore(s1, s2)) {
alert('比分无效,请输入有效的 BO3 胜场数,且双方比分不能相同。')
return
}
match.score1 = s1
match.score2 = s2
match.done = true
if(s1 > s2) {
match.winner = match.player1
match.loser = match.player2
} else {
match.winner = match.player2
match.loser = match.player1
}
if(type === 'winners') {
if(!rounds.winners[roundIndex+1]) rounds.winners[roundIndex+1] = []
addPlayerToNextRound(rounds.winners[roundIndex+1], match.winner)
if(!rounds.losers[roundIndex]) rounds.losers[roundIndex] = []
addPlayerToNextRound(rounds.losers[roundIndex], match.loser)
}
else if(type === 'losers') {
if(!rounds.losers[roundIndex+1]) rounds.losers[roundIndex+1] = []
addPlayerToNextRound(rounds.losers[roundIndex+1], match.winner)
}
else if(type === 'finals') {
handleFinalsResult(match)
}
generateBracket()
checkIfCanStartFinals()
}
function addPlayerToNextRound(roundMatches, player) {
if(!player) return
for(let match of roundMatches) {
if(!match.player1) {
match.player1 = player
match.done = false
match.score1 = 0
match.score2 = 0
match.winner = null
match.loser = null
return
}
if(!match.player2) {
match.player2 = player
match.done = false
match.score1 = 0
match.score2 = 0
match.winner = null
match.loser = null
return
//
if (!this.thirdPlace && this.losersBracket.length > 1) {
const semiLoserRound = this.losersBracket[this.losersBracket.length - 2];
if (semiLoserRound && semiLoserRound.length > 0) {
const semiLoserMatch = semiLoserRound[semiLoserRound.length - 1];
if (semiLoserMatch && semiLoserMatch.loser) {
this.thirdPlace = semiLoserMatch.loser.name || semiLoserMatch.loser;
}
}
}
if (!this.thirdPlace) {
this.thirdPlace = '待定';
}
//
// statuswinstatuslose
try {
const latestData = await getSignUpResultList();
//
const championData = latestData.find(item => item.sign_name === this.champion);
if (championData) {
await updateSignUpResult(championData.id, {
...championData,
status: 'win',
rank: '1'
});
}
//
const runnerUpData = latestData.find(item => item.sign_name === this.runnerUp);
if (runnerUpData) {
await updateSignUpResult(runnerUpData.id, {
...runnerUpData,
status: 'lose',
rank: '2'
});
}
//
if (this.thirdPlace && this.thirdPlace !== '待定') {
const thirdPlaceData = latestData.find(item => item.sign_name === this.thirdPlace);
if (thirdPlaceData) {
await updateSignUpResult(thirdPlaceData.id, {
...thirdPlaceData,
status: 'lose',
rank: '3'
});
}
}
} catch (err) {
console.error('更新最终排名失败:', err);
}
},
//
formatMatch(match, isFinal = false) {
if (!match) return '';
const p1Name = match.participant1 ? match.participant1.name : '待定';
const p2Name = match.participant2 ? match.participant2.name : '待定';
return `${p1Name} vs ${p2Name}`;
},
// API
async updatePlayerStats(p1, p2, s1, s2, bracketType = 'winners') {
if (!p1 || !p2) return;
try {
//
const latestData = await getSignUpResultList();
const p1Latest = latestData.find(item => item.id === p1.id);
const p2Latest = latestData.find(item => item.id === p2.id);
if (!p1Latest || !p2Latest) return;
// -
const pointMultiplier = bracketType === 'winners' ? 1 : 0.5;
const winPoints = s1 > s2 ? 1 * pointMultiplier : 0;
const losePoints = s2 > s1 ? 1 * pointMultiplier : 0;
// 1
const p1Data = {
tournament_id: this.selectedTournamentId,
tournament_name: this.selectedTournamentName,
team_name: p1.team_name || '',
sign_name: p1.name,
win: String(Number(p1Latest.win || 0) + s1),
lose: String(Number(p1Latest.lose || 0) + s2),
status: s1 > s2 ? 'win' : 'lose',
qq_code: p1.qq_code || '',
points: String(Number(p1Latest.points || 0) + (s1 > s2 ? winPoints : 0)),
bracket_type: bracketType //
};
// 2
const p2Data = {
tournament_id: this.selectedTournamentId,
tournament_name: this.selectedTournamentName,
team_name: p2.team_name || '',
sign_name: p2.name,
win: String(Number(p2Latest.win || 0) + s2),
lose: String(Number(p2Latest.lose || 0) + s1),
status: s2 > s1 ? 'win' : 'lose',
qq_code: p2.qq_code || '',
points: String(Number(p2Latest.points || 0) + (s2 > s1 ? winPoints : 0)),
bracket_type: bracketType //
};
// API
await updateSignUpResult(p1.id, p1Data);
await updateSignUpResult(p2.id, p2Data);
//
await this.loadParticipants();
} catch (err) {
console.error('更新选手数据失败:', err);
alert('更新选手数据失败');
}
}
}
roundMatches.push({
player1: player,
player2: null,
score1: 0,
score2: 0,
done: false,
winner: null,
loser: null,
})
}
function checkIfCanStartFinals() {
const winnersLastRound = rounds.winners[rounds.winners.length-1]
if(!winnersLastRound) return
if(winnersLastRound.length !== 1) return
if(!winnersLastRound[0].done) return
const losersLastRound = rounds.losers[rounds.losers.length-1]
if(!losersLastRound) return
if(losersLastRound.length !== 1) return
if(!losersLastRound[0].done) return
if(rounds.finals.length === 0) {
rounds.finals.push({
player1: winnersLastRound[0].winner,
player2: losersLastRound[0].winner,
score1: 0,
score2: 0,
done: false,
winner: null,
loser: null,
})
generateBracket()
}
}
function handleFinalsResult(match) {
if(!match.done) return
const champion = match.winner
const runnerUp = match.loser
if (!finalRanking.value) return
finalRanking.value.innerHTML = `
<h3>比赛结束 - 最终排名</h3>
<ol>
<li>冠军${champion}</li>
<li>亚军${runnerUp}</li>
<li>季军待定可扩展实现季军赛</li>
</ol>
`
}
onMounted(() => {
initWinners()
initLosers()
generateBracket()
})
};
</script>
<style scoped>
@ -255,158 +441,88 @@ onMounted(() => {
margin: 0 auto;
padding: 20px;
}
.tournament-bracket-root * {
box-sizing: border-box;
}
.tournament-bracket-root ul {
padding-left: 20px;
margin: 0 0 10px 0;
}
.tournament-bracket-root li {
margin-bottom: 4px;
list-style: disc;
}
.tournament-bracket-root button {
outline: none;
}
.tournament-bracket-root input[type=number]::-webkit-inner-spin-button,
.tournament-bracket-root input[type=number]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
.tournament-bracket-root input[type=number] {
-moz-appearance: textfield;
}
.tournament-bracket-root h1 {
text-align: center;
}
.tournament-bracket-root .container {
.container {
display: flex;
flex-wrap: wrap;
gap: 20px;
align-items: flex-start;
min-height: 500px;
}
.tournament-bracket-root .control-panel {
.control-panel {
flex: 1;
min-width: 280px;
background: #f0f0f0;
min-width: 250px;
background: #f5f5f5;
padding: 15px;
border-radius: 6px;
height: 100%;
box-sizing: border-box;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.tournament-bracket-root .bracket-container {
flex: 3;
position: relative;
overflow-x: auto;
.bracket-section {
flex: 2;
min-width: 300px;
background: #fff;
padding: 15px;
border-radius: 6px;
border: 1px solid #ccc;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
min-height: 400px;
display: flex;
align-items: flex-start;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
}
.tournament-bracket-root .bracket {
display: grid;
grid-auto-flow: column;
grid-auto-columns: 220px;
gap: 40px 10px;
position: relative;
padding-bottom: 50px;
min-width: 600px;
min-height: 350px;
}
.tournament-bracket-root .round {
display: grid;
grid-auto-rows: 70px;
gap: 20px;
}
.tournament-bracket-root .round-title {
text-align: center;
font-weight: bold;
margin-bottom: 15px;
}
.tournament-bracket-root .match {
background: #fafafa;
.match {
background: #f9f9f9;
border: 1px solid #ddd;
border-radius: 6px;
padding: 8px 12px;
box-shadow: 0 1px 3px rgb(0 0 0 / 0.1);
position: relative;
font-size: 14px;
padding: 10px;
margin-bottom: 10px;
}
.tournament-bracket-root .participant {
.participant {
display: flex;
justify-content: space-between;
margin-bottom: 6px;
cursor: default;
user-select: none;
align-items: center;
padding: 5px 0;
}
.tournament-bracket-root .participant.winner {
.participant.winner {
font-weight: bold;
color: #2a7f2a;
}
.tournament-bracket-root .score-input {
input[type="number"] {
width: 40px;
font-size: 14px;
padding: 2px 4px;
margin-left: 6px;
border: 1px solid #aaa;
border-radius: 3px;
text-align: center;
}
.tournament-bracket-root .score-btn {
margin-top: 4px;
width: 100%;
background: #007acc;
border: none;
button {
background: #4CAF50;
color: white;
font-weight: bold;
padding: 6px 0;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
margin-top: 5px;
width: 100%;
}
.tournament-bracket-root .score-btn:disabled {
background: #aaa;
button:disabled {
background: #cccccc;
cursor: not-allowed;
}
.tournament-bracket-root #finalRanking {
margin-top: 15px;
.final-ranking {
background: #fffbdb;
border: 1px solid #f0e68c;
padding: 12px;
padding: 15px;
border-radius: 6px;
margin-top: 20px;
}
.tournament-bracket-root svg.bracket-lines {
position: absolute;
top: 40px;
left: 0;
pointer-events: none;
overflow: visible;
height: 100%;
width: 100%;
.final-ranking h3 {
margin-top: 0;
color: #b8860b;
}
.tournament-bracket-root svg.bracket-lines path {
stroke: #666;
fill: none;
stroke-width: 2;
.final-ranking p {
margin: 5px 0;
font-size: 16px;
}
.tournament-bracket-root .tournament-info {
margin-bottom: 15px;
padding: 10px;
background: #e6f7ff;
border-radius: 4px;
}
.tournament-bracket-root .loading {
color: #666;
font-style: italic;
}
.tournament-bracket-root select {
width: 100%;
padding: 8px;
margin-bottom: 10px;
border-radius: 4px;
border: 1px solid #ccc;
}
</style>
</style>

View File

@ -12,7 +12,7 @@
<div class="player-info">
<div class="player-name">{{ item.username }}</div>
<div class="player-faction">{{ item.faction }}</div>
<div class="player-score">{{ item.score }}</div>
<div class="player-score" :title="`积分: ${item.points}`">{{ item.score }}</div>
</div>
</div>
</div>
@ -31,7 +31,7 @@
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ref, onMounted, watch } from 'vue'
import { getSignUpResultList } from '@/api/tournament'
const props = defineProps({
@ -46,29 +46,74 @@ const rankData = ref([])
const fetchRankData = async () => {
try {
const response = await getSignUpResultList()
//
const results = response
.filter(player => player.tournament_id === props.tournamentId)
.map((player, index) => ({
rank: index + 1,
//
const filteredPlayers = response.filter(player => player.tournament_id === props.tournamentId)
//
const playersWithPoints = filteredPlayers.map(player => {
//
let points = parseFloat(player.points || 0)
//
if (!player.points) {
const wins = parseInt(player.win) || 0
//
const multiplier = player.bracket_type === 'losers' ? 0.5 : 1
points = wins * multiplier
}
//
const rank = player.rank || ''
return {
id: player.id,
rank: rank,
username: player.sign_name,
faction: player.faction,
faction: player.faction || player.team_name || '',
win: parseInt(player.win) || 0,
lose: parseInt(player.lose) || 0,
score: `${player.win}${player.lose}`
}))
.sort((a, b) => {
//
if (b.win !== a.win) {
return b.win - a.win
}
//
return a.lose - b.lose
})
.map((player, index) => ({
...player,
rank: index + 1
}))
points: points,
status: player.status || '',
bracket_type: player.bracket_type || 'winners', //
score: `${player.win}${player.lose}负 (${points.toFixed(1)}分)`
}
})
//
const sortedPlayers = [...playersWithPoints].sort((a, b) => {
//
if (a.rank && b.rank) {
return parseInt(a.rank) - parseInt(b.rank)
}
// winlose
if (a.status === 'win' && b.status === 'lose') return -1
if (a.status === 'lose' && b.status === 'win') return 1
//
if (b.points !== a.points) {
return b.points - a.points
}
//
if (a.bracket_type !== b.bracket_type) {
return a.bracket_type === 'winners' ? -1 : 1
}
//
if (b.win !== a.win) {
return b.win - a.win
}
//
return a.lose - b.lose
})
//
const results = sortedPlayers.map((player, index) => ({
...player,
rank: player.rank || (index + 1).toString()
}))
rankData.value = results
} catch (error) {
@ -79,6 +124,13 @@ const fetchRankData = async () => {
onMounted(() => {
fetchRankData()
})
// ID
watch(() => props.tournamentId, () => {
if (props.tournamentId) {
fetchRankData()
}
})
</script>
<style scoped>

View File

@ -170,7 +170,7 @@ router.beforeEach(async (to, from, next) => {
// 登录校验:如果页面需要登录但本地没有有效 token则跳转登录页
if (requiresAuth && !hasValidToken()) {
return next({
path: '/maps',
path: '/',
query: { redirect: to.fullPath }
})
}
@ -181,7 +181,7 @@ router.beforeEach(async (to, from, next) => {
if (!user || !hasPrivilegeWithTemp(user, requiredPrivilege)) {
// 通过事件总线通知弹窗
eventBus.emit('no-privilege')
return next({ path: '/maps', replace: true })
return next({ path: '/', replace: true })
}
}

View File

@ -86,12 +86,14 @@ import SuccessDialog from '@/components/SuccessDialog.vue'
import loginBg from '@/assets/login_1.jpg'
import loginBg1 from '@/assets/login_2.jpg'
import loginBg3 from '@/assets/login_3.jpg'
import loginBg4 from '@/assets/login_4.jpg'
import loginBg5 from '@/assets/login_5.jpg'
import {hasValidToken, logoutUser} from "@/utils/jwt.js";
const route = useRoute()
const router = useRouter()
const images = [loginBg, loginBg1, loginBg3]
const images = [loginBg, loginBg1, loginBg3,loginBg4, loginBg5]
const randomIndex = Math.floor(Math.random() * images.length)
const bgImg = ref(images[randomIndex])

View File

@ -37,11 +37,13 @@ import { hasValidToken } from '@/utils/jwt'
import loginBg from '@/assets/login_1.jpg'
import loginBg1 from '@/assets/login_2.jpg'
import loginBg3 from '@/assets/login_3.jpg'
import loginBg4 from '@/assets/login_4.jpg'
import loginBg5 from '@/assets/login_5.jpg'
import ForgetModule from "@/components/forget_module.vue";
import LoginModule from '@/components/login_module.vue'
import RegisterModule from '@/components/register_module.vue'
const images = [loginBg, loginBg1,loginBg3]
const images = [loginBg, loginBg1,loginBg3, loginBg4,loginBg5]
const randomIndex = Math.floor(Math.random() * images.length)
const bgImg = ref(images[randomIndex])

View File

@ -324,7 +324,8 @@ function handlePasswordChangeError(errorMessage) {
<div class="app">
<nav class="navbar">
<div class="nav-container">
<div class="nav-brand">红色警戒3数据分析中心</div>
<a href="/" class="nav-brand">红色警戒3数据分析中心</a>
<!-- <div class="nav-brand">红色警戒3数据分析中心</div>-->
<button class="mobile-menu-toggle" @click="toggleMobileMenu">
<i class="fas fa-bars"></i>
</button>
@ -344,35 +345,9 @@ function handlePasswordChangeError(errorMessage) {
<span class="nav-link">地形与纹理</span>
<div class="dropdown-content">
<router-link to="/terrain" class="nav-link">地形图列表</router-link>
<!-- <router-link v-if="isLoggedIn" to="/terrainGenerate" class="nav-link" @click.prevent="handleNavClick('/terrainGenerate', ['lv-admin','lv-mod','lv-map','lv-competitor'])">地形纹理合成工具</router-link>-->
<router-link to="/terrainGenerate" class="nav-link" @click.prevent="handleNavClick('/terrainGenerate', ['lv-admin','lv-mod','lv-map','lv-competitor'])">地形纹理合成工具</router-link>
</div>
</div>
<!-- <template v-if="isLoggedIn">-->
<!-- &lt;!&ndash; 在线工具 一级菜单 &ndash;&gt;-->
<!-- <div class="nav-dropdown">-->
<!-- <span class="nav-link">在线工具</span>-->
<!-- <div class="dropdown-content">-->
<!-- <router-link to="/weapon-match" class="nav-link" @click.prevent="handleNavClick('/weapon-match', ['lv-admin','lv-mod'])">Weapon 匹配</router-link>-->
<!-- <router-link to="/PIC2TGA" class="nav-link" @click.prevent="handleNavClick('/PIC2TGA', ['lv-admin','lv-mod','lv-map','lv-competitor'])">在线转tga工具</router-link>-->
<!-- </div>-->
<!-- </div>-->
<!-- &lt;!&ndash; 赛事信息 一级菜单 &ndash;&gt;-->
<!-- <div class="nav-dropdown">-->
<!-- <span class="nav-link">赛事信息</span>-->
<!-- <div class="dropdown-content">-->
<!--&lt;!&ndash; <router-link to="/competition" class="nav-link" @click.prevent="handleNavClick('/competition', ['lv-admin','lv-competitor'])">赛程信息</router-link>&ndash;&gt;-->
<!-- <router-link to="/competition" class="nav-link">赛程信息</router-link>-->
<!-- </div>-->
<!-- </div>-->
<!-- &lt;!&ndash; 公共信息区 一级菜单 &ndash;&gt;-->
<!-- <div class="nav-dropdown">-->
<!-- <span class="nav-link">公共信息区</span>-->
<!-- <div class="dropdown-content">-->
<!-- <router-link to="/demands" class="nav-link">办事大厅</router-link>-->
<!-- </div>-->
<!-- </div>-->
<!-- </template>-->
<!-- 需要登陆才能访问如果没有登陆则点击跳转到登陆页面-->
<!-- 如果登陆了才能执行权限判断-->
<div class="nav-dropdown">

View File

@ -192,6 +192,7 @@
v-for="demand in visibleDemands"
:key="demand.id"
class="demand-item"
@click="viewDemand(demand.id)"
>
<div class="demand-header">
<h4 class="demand-title">{{ demand.content }}</h4>
@ -295,14 +296,6 @@ const fetchDemands = async () => {
announcements.value = demands
.slice()
.sort((a, b) => new Date(b.date) - new Date(a.date))
// .map((item, idx) => ({
// id: item.id || idx + 1,
// date: item.date,
// title: item.sendcontent || item.content || '',
// content: item.sendcontent || item.content || '',
// reward: item.reward || '',
// requester: item.requester || '',
// status: item.status || 'NORMAL' // status
.map((item, idx) => ({
id: item.id || idx + 1,
date: item.date,
@ -411,6 +404,10 @@ const viewCompetition = (id) => {
router.push(`/competition/detail?id=${id}`)
}
const viewDemand = (id) => {
router.push(`/demands?id=${id}`)
}
//
const formatDate = (dateString) => {
if (!dateString || dateString === 'Test_date') return '日期未提供'