重置密码的60s冷却🐱🐱

This commit is contained in:
Kunagisa 2025-07-25 00:58:00 +08:00
parent 0537bdb86e
commit 4c15c42ebc
3 changed files with 417 additions and 3 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,

View File

@ -0,0 +1,412 @@
<template>
<div class="tournament-bracket-root">
<h1>双败淘汰赛赛程图 (BO3)</h1>
<div class="container">
<div class="bracket-container">
<div ref="bracket" class="bracket"></div>
</div>
</div>
<div ref="finalRanking"></div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
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'
}
const disabled = match.done ? 'disabled' : ''
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)
//
if (!match.done) {
setTimeout(() => {
const btn = document.getElementById(`${type}-${roundIndex}-${matchIndex}-btn`)
if (btn) {
btn.onclick = () => submitMatch(type, roundIndex, matchIndex)
}
}, 0)
}
})
return roundDiv
}
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))
}
}
function submitMatch(type, roundIndex, matchIndex) {
let match = rounds[type][roundIndex][matchIndex]
if(match.done) return
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
}
}
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>
.tournament-bracket-root {
font-family: Arial, sans-serif;
max-width: 1200px;
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 {
display: flex;
gap: 20px;
align-items: flex-start;
min-height: 500px;
}
.tournament-bracket-root .control-panel {
flex: 1;
min-width: 280px;
background: #f0f0f0;
padding: 15px;
border-radius: 6px;
height: 100%;
box-sizing: border-box;
}
.tournament-bracket-root .bracket-container {
flex: 3;
position: relative;
overflow-x: auto;
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;
}
.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;
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;
}
.tournament-bracket-root .participant {
display: flex;
justify-content: space-between;
margin-bottom: 6px;
cursor: default;
user-select: none;
}
.tournament-bracket-root .participant.winner {
font-weight: bold;
color: #2a7f2a;
}
.tournament-bracket-root .score-input {
width: 40px;
font-size: 14px;
padding: 2px 4px;
margin-left: 6px;
border: 1px solid #aaa;
border-radius: 3px;
}
.tournament-bracket-root .score-btn {
margin-top: 4px;
width: 100%;
background: #007acc;
border: none;
color: white;
font-weight: bold;
padding: 6px 0;
border-radius: 4px;
cursor: pointer;
}
.tournament-bracket-root .score-btn:disabled {
background: #aaa;
cursor: not-allowed;
}
.tournament-bracket-root #finalRanking {
margin-top: 15px;
background: #fffbdb;
border: 1px solid #f0e68c;
padding: 12px;
border-radius: 6px;
}
.tournament-bracket-root svg.bracket-lines {
position: absolute;
top: 40px;
left: 0;
pointer-events: none;
overflow: visible;
height: 100%;
width: 100%;
}
.tournament-bracket-root svg.bracket-lines path {
stroke: #666;
fill: none;
stroke-width: 2;
}
.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>

View File

@ -75,8 +75,9 @@
v-if="competition.status === 'finish'"
:tournament-id="parseInt(route.query.id)"
/>
<DoubleEliminationBracket v-if="competition.format === '双败淘汰' && competition.status === 'starting'" />
<tournament-bracket
v-if="competition.status === 'starting'"
v-else-if="competition.status === 'starting'"
:tournament-id="parseInt(route.query.id)"
@refreshPlayers="fetchRegisteredPlayers"
/>
@ -217,6 +218,7 @@ import { ref, onMounted, computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import TournamentBracket from '@/components/TournamentBracket.vue'
import RankContestant from '@/components/RankContestant.vue'
import DoubleEliminationBracket from '@/components/DoubleEliminationBracket.vue'
import {
getTournamentList,
updateTournament,