1249 lines
29 KiB
Vue
1249 lines
29 KiB
Vue
<template>
|
||
<div class="competition-page">
|
||
<div v-if="isLoading" class="loading-overlay">
|
||
<div class="loading-spinner"></div>
|
||
</div>
|
||
<div v-else class="detail-card">
|
||
<div class="nav-back">
|
||
<button class="back-btn" @click="handleBack">← 返回列表</button>
|
||
<div class="action-buttons">
|
||
<button class="status-btn" @click="openStatusDialog">修改状态</button>
|
||
<button class="edit-btn" @click="openEditDialog">编辑赛事</button>
|
||
<button class="delete-btn" @click="confirmDelete">删除赛事</button>
|
||
</div>
|
||
</div>
|
||
<div class="page-header">
|
||
<h1>{{ competition.name }}</h1>
|
||
<div class="header-subtitle">
|
||
<span>赛制:{{ competition.format }}</span>
|
||
<span>|组织者:{{ competition.organizer }}</span>
|
||
<span>|QQ:{{ competition.qq_code }}</span>
|
||
<span>|开始时间:{{ formatDate(competition.start_time) }}</span>
|
||
<span>|结束时间:{{ formatDate(competition.end_time) }}</span>
|
||
<span :class="['status-tag', competition.status]">
|
||
{{ statusMap[competition.status] }}
|
||
</span>
|
||
</div>
|
||
<div class="edit-controls" v-if="isOrganizer">
|
||
<button class="edit-mode-btn" @click="toggleEditMode">
|
||
{{ isEditMode ? '退出编辑' : '编辑对阵图' }}
|
||
</button>
|
||
<button v-if="isEditMode" class="save-btn" @click="saveChanges" :disabled="isSaving">
|
||
{{ isSaving ? '保存中...' : '保存修改' }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 报名玩家列表 -->
|
||
<div v-if="competition.status === 'prepare' || competition.status === 'starting'" class="registered-players">
|
||
<div class="section-title">报名玩家列表</div>
|
||
<div class="players-list">
|
||
<div v-if="registeredPlayers.length === 0" class="no-players">
|
||
暂无报名玩家
|
||
</div>
|
||
<table v-else class="players-table">
|
||
<thead>
|
||
<tr>
|
||
<th>玩家名称</th>
|
||
<th>队伍名称</th>
|
||
<th>胜场</th>
|
||
<th>负场</th>
|
||
<th>状态</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr v-for="player in registeredPlayers" :key="player.id">
|
||
<td>{{ player.sign_name }}</td>
|
||
<td>{{ player.team_name || '个人' }}</td>
|
||
<td>{{ player.win }}</td>
|
||
<td>{{ player.lose }}</td>
|
||
<td>{{ player.status }}</td>
|
||
<td class="action-buttons">
|
||
<button class="edit-player-btn" @click="handleEditPlayer(player)">修改</button>
|
||
<button class="remove-btn" @click="handleRemovePlayer(player.id)">移除</button>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 非筹备中状态显示的内容 -->
|
||
<template v-if="competition.status !== 'prepare'">
|
||
<rank-contestant
|
||
v-if="competition.status === 'finish'"
|
||
:tournament-id="parseInt(route.query.id)"
|
||
/>
|
||
<tournament-bracket
|
||
v-if="competition.status === 'starting'"
|
||
:tournament-id="parseInt(route.query.id)"
|
||
@refreshPlayers="fetchRegisteredPlayers"
|
||
/>
|
||
</template>
|
||
|
||
<div v-if="showEditForm" class="edit-dialog-overlay">
|
||
<div class="edit-dialog">
|
||
<h3>编辑赛事</h3>
|
||
<div class="edit-form">
|
||
<div class="form-group">
|
||
<label>赛事名称:</label>
|
||
<input type="text" v-model="editForm.name" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>赛制类型:</label>
|
||
<select v-model="editForm.format">
|
||
<option value="single">单败淘汰</option>
|
||
<option value="double">双败淘汰</option>
|
||
<option value="count">积分赛</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>组织者:</label>
|
||
<input type="text" v-model="editForm.organizer" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>QQ号:</label>
|
||
<input type="text" v-model="editForm.qq_code" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>开始时间:</label>
|
||
<input type="date" v-model="editForm.start_time" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>结束时间:</label>
|
||
<input type="date" v-model="editForm.end_time" />
|
||
</div>
|
||
<div class="dialog-buttons">
|
||
<button class="cancel-btn" @click="closeEditDialog">取消</button>
|
||
<button class="confirm-btn" @click="handleUpdate" :disabled="isUpdating">
|
||
{{ isUpdating ? '保存中...' : '保存' }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="showDeleteConfirm" class="edit-dialog-overlay">
|
||
<div class="edit-dialog">
|
||
<h3>确认删除</h3>
|
||
<p class="confirm-message">确定要删除该赛事吗?此操作不可恢复。</p>
|
||
<div class="dialog-buttons">
|
||
<button class="cancel-btn" @click="showDeleteConfirm = false">取消</button>
|
||
<button class="delete-confirm-btn" @click="handleDelete" :disabled="isDeleting">
|
||
{{ isDeleting ? '删除中...' : '确认删除' }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="showStatusDialog" class="edit-dialog-overlay">
|
||
<div class="edit-dialog">
|
||
<h3>修改赛事状态</h3>
|
||
<div class="status-options">
|
||
<button v-for="(label, value) in statusMap" :key="value"
|
||
:class="['status-option', { active: selectedStatus === value }]" @click="selectedStatus = value">
|
||
{{ label }}
|
||
</button>
|
||
</div>
|
||
<div class="dialog-buttons">
|
||
<button class="cancel-btn" @click="closeStatusDialog">取消</button>
|
||
<button class="confirm-btn" @click="handleStatusUpdate" :disabled="isUpdating">
|
||
{{ isUpdating ? '保存中...' : '保存' }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 编辑玩家弹窗 -->
|
||
<div v-if="showPlayerEditDialog" class="edit-dialog-overlay">
|
||
<div class="edit-dialog">
|
||
<h3>编辑参赛信息</h3>
|
||
<div class="edit-form">
|
||
<div class="form-group">
|
||
<label>参赛人员名称:</label>
|
||
<input type="text" v-model="playerEditForm.sign_name" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>队伍名称:</label>
|
||
<input type="text" v-model="playerEditForm.team_name" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>胜利局数:</label>
|
||
<input type="text" v-model="playerEditForm.win" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>失败局数:</label>
|
||
<input type="text" v-model="playerEditForm.lose" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>状态:</label>
|
||
<select v-model="playerEditForm.status">
|
||
<option value="win">胜利</option>
|
||
<option value="lose">失败</option>
|
||
<option value="tie">平局</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>阵营:</label>
|
||
<select v-model="playerEditForm.faction">
|
||
<option value="allied">盟军</option>
|
||
<option value="soviet">苏联</option>
|
||
<option value="empire">帝国</option>
|
||
<option value="ob">OB</option>
|
||
<option value="voice">解说</option>
|
||
<option value="random">随机</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>QQ:</label>
|
||
<input type="text" v-model="playerEditForm.qq" />
|
||
</div>
|
||
<div class="dialog-buttons">
|
||
<button class="cancel-btn" @click="closePlayerEditDialog">取消</button>
|
||
<button class="confirm-btn" @click="handlePlayerUpdate" :disabled="isUpdating">
|
||
{{ isUpdating ? '保存中...' : '保存' }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
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 {
|
||
getTournamentList,
|
||
updateTournament,
|
||
deleteTournament,
|
||
getSignUpResultList,
|
||
updateSignUpResult,
|
||
deleteSignUpResult
|
||
} from '@/api/tournament'
|
||
|
||
const router = useRouter()
|
||
const route = useRoute()
|
||
|
||
// 状态映射
|
||
const formatMap = {
|
||
'single': '单败淘汰',
|
||
'double': '双败淘汰',
|
||
'count': '积分赛'
|
||
}
|
||
|
||
const statusMap = {
|
||
'prepare': '筹备中',
|
||
'starting': '进行中',
|
||
'finish': '已结束'
|
||
}
|
||
|
||
// const factionMap = {
|
||
// 'allied': '盟军',
|
||
// 'soviet': '苏联',
|
||
// 'empire': '帝国',
|
||
// 'ob': 'OB',
|
||
// 'voice': '解说',
|
||
// 'random': '随机'
|
||
// }
|
||
|
||
// 状态管理
|
||
const competition = ref({
|
||
name: '',
|
||
format: '',
|
||
organizer: '',
|
||
qq_code: '',
|
||
start_time: '',
|
||
end_time: '',
|
||
status: ''
|
||
})
|
||
const finalResults = ref([])
|
||
const isEditMode = ref(false)
|
||
const isOrganizer = ref(false) // TODO: 从用户状态获取
|
||
const isSaving = ref(false)
|
||
const showEditForm = ref(false)
|
||
const isUpdating = ref(false)
|
||
const editForm = ref({
|
||
name: '',
|
||
format: '',
|
||
organizer: '',
|
||
qq_code: '',
|
||
start_time: '',
|
||
end_time: ''
|
||
})
|
||
const showDeleteConfirm = ref(false)
|
||
const isDeleting = ref(false)
|
||
const showStatusDialog = ref(false)
|
||
const selectedStatus = ref('')
|
||
const registeredPlayers = ref([]) // 报名玩家列表
|
||
const isLoading = ref(true) // 添加加载状态
|
||
const showPlayerEditDialog = ref(false) // 玩家编辑弹窗
|
||
const playerEditForm = ref({ // 玩家编辑表单
|
||
id: '',
|
||
username: '',
|
||
type: '',
|
||
tournament_id: ''
|
||
})
|
||
|
||
// 格式化日期
|
||
const formatDate = (date) => {
|
||
if (!date) return ''
|
||
return date.replace(/\//g, '-')
|
||
}
|
||
|
||
// 格式化赛制
|
||
const formatType = (type) => {
|
||
return formatMap[type] || type
|
||
}
|
||
|
||
// // 格式化阵营
|
||
// const formatFaction = (faction) => {
|
||
// return factionMap[faction] || faction
|
||
// }
|
||
|
||
// 返回列表
|
||
const handleBack = () => {
|
||
router.push('/competition')
|
||
}
|
||
|
||
// 切换编辑模式
|
||
const toggleEditMode = () => {
|
||
isEditMode.value = !isEditMode.value
|
||
}
|
||
|
||
|
||
// 打开编辑弹窗
|
||
const openEditDialog = () => {
|
||
const getFormatValue = (format) => {
|
||
const formatEntries = Object.entries(formatMap)
|
||
const entry = formatEntries.find(([_, value]) => value === format)
|
||
return entry ? entry[0] : format
|
||
}
|
||
|
||
editForm.value = {
|
||
name: competition.value.name,
|
||
format: getFormatValue(competition.value.format),
|
||
organizer: competition.value.organizer,
|
||
qq_code: competition.value.qq_code,
|
||
start_time: competition.value.start_time.replace(/\//g, '-'),
|
||
end_time: competition.value.end_time.replace(/\//g, '-')
|
||
}
|
||
showEditForm.value = true
|
||
}
|
||
|
||
// 关闭编辑弹窗
|
||
const closeEditDialog = () => {
|
||
showEditForm.value = false
|
||
editForm.value = {
|
||
name: '',
|
||
format: '',
|
||
organizer: '',
|
||
qq_code: '',
|
||
start_time: '',
|
||
end_time: ''
|
||
}
|
||
}
|
||
|
||
// 确认删除
|
||
const confirmDelete = () => {
|
||
showDeleteConfirm.value = true
|
||
}
|
||
|
||
// 处理更新
|
||
const handleUpdate = async () => {
|
||
try {
|
||
isUpdating.value = true
|
||
const tournamentId = route.query.id
|
||
|
||
// 转换日期格式
|
||
const formatDate = (dateStr) => {
|
||
const [year, month, day] = dateStr.split('-')
|
||
return `${year}/${month}/${day}`
|
||
}
|
||
|
||
const updateData = {
|
||
...editForm.value,
|
||
start_time: formatDate(editForm.value.start_time),
|
||
end_time: formatDate(editForm.value.end_time),
|
||
status: competition.value.status // 保持原有状态
|
||
}
|
||
|
||
await updateTournament(tournamentId, updateData)
|
||
alert('更新成功!')
|
||
closeEditDialog()
|
||
fetchTournamentDetail() // 刷新数据
|
||
} catch (error) {
|
||
console.error('更新失败:', error)
|
||
alert(error.response?.data?.message || '更新失败,请重试')
|
||
} finally {
|
||
isUpdating.value = false
|
||
}
|
||
}
|
||
|
||
// 处理删除
|
||
const handleDelete = async () => {
|
||
try {
|
||
isDeleting.value = true
|
||
const tournamentId = route.query.id
|
||
await deleteTournament(tournamentId)
|
||
alert('删除成功!')
|
||
router.push('/competition')
|
||
} catch (error) {
|
||
console.error('删除失败:', error)
|
||
alert(error.response?.data?.message || '删除失败,请重试')
|
||
} finally {
|
||
isDeleting.value = false
|
||
showDeleteConfirm.value = false
|
||
}
|
||
}
|
||
|
||
// 获取赛事详情
|
||
const fetchTournamentDetail = async () => {
|
||
try {
|
||
isLoading.value = true
|
||
const tournamentId = parseInt(route.query.id)
|
||
console.log('获取到的赛事ID:', tournamentId)
|
||
|
||
const data = await getTournamentList()
|
||
console.log('获取到的赛事列表:', data)
|
||
|
||
// 根据ID查找对应的赛事
|
||
const tournament = data.find(item => item.id === tournamentId)
|
||
|
||
if (tournament) {
|
||
console.log('找到赛事:', tournament)
|
||
competition.value = {
|
||
...tournament,
|
||
format: formatType(tournament.format)
|
||
}
|
||
|
||
// 如果是筹备中状态,获取报名玩家列表
|
||
if (tournament.status === 'prepare') {
|
||
await fetchRegisteredPlayers()
|
||
}
|
||
// 如果是进行中状态,生成树状图
|
||
else if (tournament.status === 'starting') {
|
||
await fetchRegisteredPlayers()
|
||
}
|
||
// 如果是已结束状态,获取最终结果
|
||
else if (tournament.status === 'finish') {
|
||
await fetchFinalResults()
|
||
}
|
||
} else {
|
||
console.log('未找到赛事')
|
||
alert('未找到赛事信息')
|
||
router.push('/competition')
|
||
}
|
||
} catch (error) {
|
||
console.error('获取赛事详情失败:', error)
|
||
alert('获取赛事详情失败,请重试')
|
||
} finally {
|
||
isLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 获取报名玩家列表
|
||
const fetchRegisteredPlayers = async () => {
|
||
try {
|
||
const tournamentId = parseInt(route.query.id)
|
||
const response = await getSignUpResultList()
|
||
// 调试日志
|
||
console.log('报名玩家原始数据:', response)
|
||
console.log('当前赛事ID:', tournamentId)
|
||
// 只保留 tournament_id 等于当前赛事 id 的玩家
|
||
registeredPlayers.value = response.filter(player =>
|
||
player.tournament_id === tournamentId
|
||
)
|
||
console.log('筛选后的玩家数据:', registeredPlayers.value)
|
||
} catch (error) {
|
||
console.error('获取报名玩家列表失败:', error)
|
||
}
|
||
}
|
||
|
||
// 移除玩家
|
||
const handleRemovePlayer = async (playerId) => {
|
||
if (!confirm('确定要移除该玩家吗?')) return
|
||
|
||
try {
|
||
await deleteSignUpResult(playerId)
|
||
await fetchRegisteredPlayers() // 刷新列表
|
||
alert('移除成功!')
|
||
} catch (error) {
|
||
console.error('移除玩家失败:', error)
|
||
alert(error.response?.data?.message || '移除失败,请重试')
|
||
}
|
||
}
|
||
|
||
// 打开状态修改弹窗
|
||
const openStatusDialog = () => {
|
||
selectedStatus.value = competition.value.status
|
||
showStatusDialog.value = true
|
||
}
|
||
|
||
// 关闭状态修改弹窗
|
||
const closeStatusDialog = () => {
|
||
showStatusDialog.value = false
|
||
selectedStatus.value = ''
|
||
}
|
||
|
||
// 处理状态更新
|
||
const handleStatusUpdate = async () => {
|
||
try {
|
||
isUpdating.value = true
|
||
const tournamentId = route.query.id
|
||
|
||
// 将中文赛制转换回英文值
|
||
const getFormatValue = (format) => {
|
||
const formatEntries = Object.entries(formatMap)
|
||
const entry = formatEntries.find(([_, value]) => value === format)
|
||
return entry ? entry[0] : format
|
||
}
|
||
|
||
// 构建更新数据
|
||
const updateData = {
|
||
name: competition.value.name,
|
||
format: getFormatValue(competition.value.format),
|
||
organizer: competition.value.organizer,
|
||
qq_code: competition.value.qq_code,
|
||
start_time: competition.value.start_time,
|
||
end_time: competition.value.end_time,
|
||
status: selectedStatus.value
|
||
}
|
||
|
||
console.log('更新赛事状态,发送数据:', updateData)
|
||
await updateTournament(tournamentId, updateData)
|
||
alert('状态更新成功!')
|
||
closeStatusDialog()
|
||
fetchTournamentDetail() // 刷新数据
|
||
} catch (error) {
|
||
console.error('状态更新失败:', error)
|
||
console.error('错误详情:', {
|
||
message: error.message,
|
||
response: error.response?.data,
|
||
status: error.response?.status
|
||
})
|
||
alert(error.message || '状态更新失败,请重试')
|
||
} finally {
|
||
isUpdating.value = false
|
||
}
|
||
}
|
||
|
||
// 打开玩家编辑弹窗
|
||
const handleEditPlayer = (player) => {
|
||
playerEditForm.value = {
|
||
id: player.id,
|
||
tournament_id: player.tournament_id,
|
||
tournament_name: competition.value.name,
|
||
team_name: player.team_name || '个人',
|
||
sign_name: player.sign_name,
|
||
win: player.win || '0',
|
||
lose: player.lose || '0',
|
||
status: player.status || 'tie',
|
||
faction: player.faction || 'random',
|
||
qq: player.qq || ''
|
||
}
|
||
showPlayerEditDialog.value = true
|
||
}
|
||
|
||
// 关闭玩家编辑弹窗
|
||
const closePlayerEditDialog = () => {
|
||
showPlayerEditDialog.value = false
|
||
}
|
||
|
||
// 处理玩家信息更新
|
||
const handlePlayerUpdate = async () => {
|
||
try {
|
||
isUpdating.value = true
|
||
console.log('更新前的数据:', playerEditForm.value)
|
||
|
||
// 构建更新数据
|
||
const updateData = {
|
||
tournament_id: parseInt(playerEditForm.value.tournament_id),
|
||
tournament_name: playerEditForm.value.tournament_name,
|
||
team_name: playerEditForm.value.team_name === '个人' ? null : playerEditForm.value.team_name,
|
||
sign_name: playerEditForm.value.sign_name,
|
||
win: playerEditForm.value.win.toString(),
|
||
lose: playerEditForm.value.lose.toString(),
|
||
status: playerEditForm.value.status,
|
||
faction: playerEditForm.value.faction,
|
||
qq: playerEditForm.value.qq
|
||
}
|
||
|
||
console.log('发送到API的数据:', updateData)
|
||
await updateSignUpResult(playerEditForm.value.id, updateData)
|
||
await fetchRegisteredPlayers() // 刷新列表
|
||
closePlayerEditDialog()
|
||
alert('更新成功!')
|
||
} catch (error) {
|
||
console.error('更新玩家信息失败:', error)
|
||
console.error('错误详情:', {
|
||
message: error.message,
|
||
response: error.response?.data,
|
||
status: error.response?.status
|
||
})
|
||
alert(error.response?.data?.detail || error.message || '更新失败,请重试')
|
||
} finally {
|
||
isUpdating.value = false
|
||
}
|
||
}
|
||
|
||
// 保存对阵图修改
|
||
const saveChanges = async () => {
|
||
try {
|
||
isSaving.value = true
|
||
// TODO: 调用保存对阵图的API
|
||
// await saveTournamentBracket(route.query.id, tournamentRounds.value)
|
||
alert('保存成功!')
|
||
isEditMode.value = false
|
||
} catch (error) {
|
||
console.error('保存失败:', error)
|
||
alert('保存失败,请重试')
|
||
} finally {
|
||
isSaving.value = false
|
||
}
|
||
}
|
||
|
||
// 获取最终结果
|
||
const fetchFinalResults = async () => {
|
||
try {
|
||
const tournamentId = parseInt(route.query.id)
|
||
const response = await getSignUpResultList()
|
||
// 筛选当前赛事的玩家并按胜场数排序
|
||
const results = response
|
||
.filter(player => player.tournament_id === tournamentId)
|
||
.map((player, index) => ({
|
||
rank: index + 1,
|
||
username: player.sign_name,
|
||
qq: player.qq,
|
||
faction: player.faction,
|
||
score: `${player.win}胜${player.lose}负`
|
||
}))
|
||
.sort((a, b) => {
|
||
const aScore = parseInt(a.score.split('胜')[0])
|
||
const bScore = parseInt(b.score.split('胜')[0])
|
||
return bScore - aScore
|
||
})
|
||
|
||
finalResults.value = results
|
||
} catch (error) {
|
||
console.error('获取最终结果失败:', error)
|
||
}
|
||
}
|
||
|
||
// 保证每个玩家有 id 和 name 字段
|
||
const mappedPlayers = computed(() =>
|
||
registeredPlayers.value.map(p => ({
|
||
id: p.id,
|
||
name: p.sign_name || p.name || '未知选手',
|
||
team_name: p.team_name || '个人',
|
||
win: p.win || '0',
|
||
lose: p.lose || '0',
|
||
status: p.status || 'tie'
|
||
}))
|
||
)
|
||
|
||
// 初始化
|
||
onMounted(() => {
|
||
fetchTournamentDetail()
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.competition-page {
|
||
min-height: 100vh;
|
||
padding: 20px;
|
||
}
|
||
|
||
.detail-card {
|
||
background: white;
|
||
border-radius: 8px;
|
||
padding: 24px;
|
||
margin: 0 auto;
|
||
max-width: 1200px;
|
||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.nav-back {
|
||
margin-bottom: 20px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.back-btn {
|
||
background: #409EFF;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 4px;
|
||
padding: 8px 16px;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.back-btn:hover {
|
||
background: #66b1ff;
|
||
}
|
||
|
||
.action-buttons {
|
||
display: flex;
|
||
gap: 12px;
|
||
}
|
||
|
||
.status-btn,
|
||
.edit-btn,
|
||
.delete-btn {
|
||
padding: 8px 16px;
|
||
border-radius: 4px;
|
||
border: none;
|
||
cursor: pointer;
|
||
font-weight: 500;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.status-btn {
|
||
background: #67C23A;
|
||
color: white;
|
||
}
|
||
|
||
.status-btn:hover {
|
||
background: #85ce61;
|
||
}
|
||
|
||
.edit-btn {
|
||
background: #409EFF;
|
||
color: white;
|
||
}
|
||
|
||
.edit-btn:hover {
|
||
background: #66b1ff;
|
||
}
|
||
|
||
.delete-btn {
|
||
background: #F56C6C;
|
||
color: white;
|
||
}
|
||
|
||
.delete-btn:hover {
|
||
background: #f78989;
|
||
}
|
||
|
||
.page-header h1 {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
margin-bottom: 12px;
|
||
color: #303133;
|
||
}
|
||
|
||
.header-subtitle {
|
||
color: #909399;
|
||
font-size: 14px;
|
||
margin-bottom: 24px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.status-tag {
|
||
display: inline-block;
|
||
padding: 4px 8px;
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.status-tag.prepare {
|
||
background-color: #e6a23c;
|
||
color: #fff;
|
||
}
|
||
|
||
.status-tag.starting {
|
||
background-color: #67c23a;
|
||
color: #fff;
|
||
}
|
||
|
||
.status-tag.finish {
|
||
background-color: #909399;
|
||
color: #fff;
|
||
}
|
||
|
||
.final-results-modern {
|
||
margin-top: 30px;
|
||
}
|
||
|
||
.final-title {
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
margin-bottom: 24px;
|
||
color: #303133;
|
||
}
|
||
|
||
.final-top3 {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 20px;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.final-card {
|
||
background: white;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
display: flex;
|
||
align-items: center;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||
border: 1px solid #EBEEF5;
|
||
}
|
||
|
||
.final-card:nth-child(1) {
|
||
background: #FFF9EB;
|
||
border: 1px solid #FFE4B5;
|
||
}
|
||
|
||
.final-card:nth-child(2) {
|
||
background: #F8F9FA;
|
||
border: 1px solid #E4E7ED;
|
||
}
|
||
|
||
.final-card:nth-child(3) {
|
||
background: #FDF6EC;
|
||
border: 1px solid #F3D19E;
|
||
}
|
||
|
||
.rank-number {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
margin-right: 16px;
|
||
min-width: 50px;
|
||
text-align: center;
|
||
}
|
||
|
||
.final-card:nth-child(1) .rank-number {
|
||
color: #E6A23C;
|
||
}
|
||
|
||
.final-card:nth-child(2) .rank-number {
|
||
color: #909399;
|
||
}
|
||
|
||
.final-card:nth-child(3) .rank-number {
|
||
color: #F56C6C;
|
||
}
|
||
|
||
.player-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.player-name {
|
||
font-size: 16px;
|
||
font-weight: 500;
|
||
color: #303133;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.player-qq {
|
||
font-size: 14px;
|
||
color: #909399;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.player-faction {
|
||
font-size: 14px;
|
||
color: #409EFF;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.player-score {
|
||
font-size: 14px;
|
||
color: #67C23A;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.final-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.final-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 16px;
|
||
background: white;
|
||
border-radius: 4px;
|
||
border: 1px solid #EBEEF5;
|
||
}
|
||
|
||
.rank {
|
||
width: 40px;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #909399;
|
||
text-align: center;
|
||
margin-right: 16px;
|
||
}
|
||
|
||
.tournament-section {
|
||
margin: 30px 0;
|
||
border-radius: 8px;
|
||
background: #f8f9fa;
|
||
padding: 20px;
|
||
}
|
||
|
||
.edit-controls {
|
||
margin-top: 16px;
|
||
display: flex;
|
||
gap: 12px;
|
||
}
|
||
|
||
.edit-mode-btn,
|
||
.save-btn {
|
||
padding: 8px 16px;
|
||
border-radius: 4px;
|
||
border: none;
|
||
cursor: pointer;
|
||
font-weight: 500;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.edit-mode-btn {
|
||
background: #4a90e2;
|
||
color: white;
|
||
}
|
||
|
||
.edit-mode-btn:hover {
|
||
background: #357abd;
|
||
}
|
||
|
||
.save-btn {
|
||
background: #42b983;
|
||
color: white;
|
||
}
|
||
|
||
.save-btn:hover {
|
||
background: #3aa876;
|
||
}
|
||
|
||
.save-btn:disabled {
|
||
background: #a8a8a8;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.edit-dialog-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
z-index: 1000;
|
||
}
|
||
|
||
.edit-dialog {
|
||
background: white;
|
||
border-radius: 8px;
|
||
padding: 24px;
|
||
width: 500px;
|
||
max-width: 90%;
|
||
}
|
||
|
||
.edit-dialog h3 {
|
||
margin: 0 0 20px;
|
||
color: #333;
|
||
text-align: center;
|
||
}
|
||
|
||
.form-group {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.form-group label {
|
||
display: block;
|
||
margin-bottom: 8px;
|
||
color: #666;
|
||
}
|
||
|
||
.form-group input,
|
||
.form-group select {
|
||
width: 100%;
|
||
padding: 8px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.form-group input:focus,
|
||
.form-group select:focus {
|
||
border-color: #409EFF;
|
||
outline: none;
|
||
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
||
}
|
||
|
||
.dialog-buttons {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 12px;
|
||
margin-top: 24px;
|
||
}
|
||
|
||
.cancel-btn,
|
||
.confirm-btn {
|
||
padding: 8px 16px;
|
||
border-radius: 4px;
|
||
border: none;
|
||
cursor: pointer;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.cancel-btn {
|
||
background: #f5f5f5;
|
||
color: #666;
|
||
}
|
||
|
||
.confirm-btn {
|
||
background: #409EFF;
|
||
color: white;
|
||
}
|
||
|
||
.cancel-btn:hover {
|
||
background: #e8e8e8;
|
||
}
|
||
|
||
.confirm-btn:hover {
|
||
background: #66b1ff;
|
||
}
|
||
|
||
.confirm-btn:disabled {
|
||
background: #a0cfff;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.confirm-message {
|
||
color: #666;
|
||
margin: 20px 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.delete-confirm-btn {
|
||
background: #F56C6C;
|
||
color: white;
|
||
padding: 8px 16px;
|
||
border-radius: 4px;
|
||
border: none;
|
||
cursor: pointer;
|
||
font-weight: 500;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.delete-confirm-btn:hover {
|
||
background: #f78989;
|
||
}
|
||
|
||
.delete-confirm-btn:disabled {
|
||
background: #fab6b6;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.status-options {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
margin: 20px 0;
|
||
}
|
||
|
||
.status-option {
|
||
padding: 12px;
|
||
border: 1px solid #DCDFE6;
|
||
border-radius: 4px;
|
||
background: white;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
text-align: left;
|
||
}
|
||
|
||
.status-option:hover {
|
||
border-color: #409EFF;
|
||
color: #409EFF;
|
||
}
|
||
|
||
.status-option.active {
|
||
background: #409EFF;
|
||
color: white;
|
||
border-color: #409EFF;
|
||
}
|
||
|
||
.status-option.active.prepare {
|
||
background: #e6a23c;
|
||
border-color: #e6a23c;
|
||
}
|
||
|
||
.status-option.active.starting {
|
||
background: #67c23a;
|
||
border-color: #67c23a;
|
||
}
|
||
|
||
.status-option.active.finish {
|
||
background: #909399;
|
||
border-color: #909399;
|
||
}
|
||
|
||
.registered-players {
|
||
margin-top: 30px;
|
||
background: white;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.players-list {
|
||
min-height: 100px;
|
||
}
|
||
|
||
.no-players {
|
||
text-align: center;
|
||
color: #909399;
|
||
padding: 40px 0;
|
||
}
|
||
|
||
.players-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
margin-top: 20px;
|
||
background: white;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.players-table th,
|
||
.players-table td {
|
||
padding: 12px 16px;
|
||
text-align: left;
|
||
border-bottom: 1px solid #EBEEF5;
|
||
}
|
||
|
||
.players-table th {
|
||
background: #F5F7FA;
|
||
color: #606266;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.players-table tr:last-child td {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.players-table tr:hover {
|
||
background: #F5F7FA;
|
||
}
|
||
|
||
.action-buttons {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.edit-player-btn {
|
||
background: #409EFF;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 4px;
|
||
padding: 6px 12px;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.edit-player-btn:hover {
|
||
background: #66b1ff;
|
||
}
|
||
|
||
.remove-btn {
|
||
background: #F56C6C;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 4px;
|
||
padding: 6px 12px;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.remove-btn:hover {
|
||
background: #f78989;
|
||
}
|
||
|
||
.remove-btn:disabled {
|
||
background: #fab6b6;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.final-top3 {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.detail-card {
|
||
padding: 16px;
|
||
}
|
||
|
||
.header-subtitle {
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 8px;
|
||
}
|
||
|
||
.players-table {
|
||
display: block;
|
||
overflow-x: auto;
|
||
}
|
||
|
||
.action-buttons {
|
||
flex-direction: column;
|
||
}
|
||
}
|
||
|
||
.loading-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(255, 255, 255, 0.9);
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
z-index: 1000;
|
||
}
|
||
|
||
.loading-spinner {
|
||
width: 40px;
|
||
height: 40px;
|
||
border: 4px solid #f3f3f3;
|
||
border-top: 4px solid #409EFF;
|
||
border-radius: 50%;
|
||
animation: spin 1s linear infinite;
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% {
|
||
transform: rotate(0deg);
|
||
}
|
||
|
||
100% {
|
||
transform: rotate(360deg);
|
||
}
|
||
}
|
||
</style> |