DCFronted/src/views/competition/CompetitionSignUp.vue

542 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="signup-page">
<div class="page-header">
<h1>比赛报名</h1>
</div>
<div class="signup-form">
<div class="form-group">
<label>比赛信息</label>
<div class="info-grid">
<div class="info-item">
<span class="label">比赛名称</span>
<span class="value">{{ competitionInfo.name }}</span>
</div>
<div class="info-item">
<span class="label">开始时间</span>
<span class="value">{{ formatDate(competitionInfo.start_time) }}</span>
</div>
<div class="info-item">
<span class="label">结束时间</span>
<span class="value">{{ formatDate(competitionInfo.end_time) }}</span>
</div>
<div class="info-item">
<span class="label">主办方</span>
<span class="value">{{ competitionInfo.organizer }}</span>
</div>
<div class="info-item">
<span class="label">QQ号</span>
<span class="value">{{ competitionInfo.qq_code }}</span>
</div>
<div class="info-item">
<span class="label">赛制类型</span>
<span class="value">{{ formatType(competitionInfo.format) }}</span>
</div>
<div class="info-item">
<span class="label">比赛状态</span>
<span class="value status-tag" :class="competitionInfo.status">
{{ formatStatus(competitionInfo.status) }}
</span>
</div>
</div>
</div>
<div class="form-group">
<label>报名信息</label>
<div class="input-group">
<select v-model="signupForm.type" class="form-select">
<option value="teamname">队伍报名</option>
<option value="individual">个人报名</option>
</select>
</div>
<!-- 队伍报名表单 -->
<template v-if="signupForm.type === 'teamname'">
<div class="input-group">
<input
type="text"
v-model="signupForm.teamName"
placeholder="请输入队伍名称"
class="form-input"
>
</div>
<div class="input-group">
<input
type="text"
v-model="signupForm.username"
placeholder="请输入参赛人员名称"
class="form-input"
>
</div>
<div class="input-group">
<select v-model="signupForm.faction" class="form-select">
<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="input-group">
<input
type="text"
v-model="signupForm.qq"
placeholder="请输入QQ号"
class="form-input"
>
</div>
</template>
<!-- 个人报名表单 -->
<template v-if="signupForm.type === 'individual'">
<div class="input-group">
<input
type="text"
v-model="signupForm.username"
placeholder="请输入参赛人员名称"
class="form-input"
>
</div>
<div class="input-group">
<select v-model="signupForm.faction" class="form-select">
<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="input-group">
<input
type="text"
v-model="signupForm.qq"
placeholder="请输入QQ号"
class="form-input"
>
</div>
</template>
</div>
<div class="form-actions">
<button class="btn-cancel" @click="handleCancel">取消</button>
<button class="btn-submit" @click="handleSubmit">提交报名</button>
</div>
</div>
<!-- 对话框组件 -->
<SuccessDialog
:visible="successDialog.visible"
:message="successDialog.message"
@close="successDialog.visible = false"
/>
<ErrorDialog
:visible="errorDialog.visible"
:message="errorDialog.message"
@close="errorDialog.visible = false"
/>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import {addSignUp, addSignUpResult} from '@/api/tournament.js'
import SuccessDialog from '@/components/SuccessDialog.vue'
import ErrorDialog from '@/components/ErrorDialog.vue'
defineOptions({
name: 'CompetitionSignUp'
})
const route = useRoute()
const router = useRouter()
const competitionInfo = ref({
id: route.query.id || '',
name: route.query.name || '',
start_time: route.query.start_time || '',
end_time: route.query.end_time || '',
organizer: route.query.organizer || '',
qq_code: route.query.qq_code || '',
format: route.query.format || '',
status: route.query.status || ''
})
const signupForm = ref({
type: 'individual',
teamName: '',
username: '',
faction: 'random',
qq: ''
})
// Dialog state
const successDialog = ref({ visible: false, message: '' })
const errorDialog = ref({ visible: false, message: '' })
const formatDate = (date) => {
if (!date) return ''
// 将年/月/日格式转换为年-月-日格式显示
return date.replace(/\//g, '-')
}
const formatType = (type) => {
const typeMap = {
'single': '单败淘汰',
'double': '双败淘汰',
'count': '积分赛'
}
return typeMap[type] || type
}
const formatStatus = (status) => {
const statusMap = {
'prepare': '筹备中',
'starting': '进行中',
'finish': '已结束'
}
return statusMap[status] || status
}
const handleCancel = () => {
router.back()
}
const handleSubmit = async () => {
// 根据报名类型验证必填字段
if (signupForm.value.type === 'teamname') {
if (!signupForm.value.teamName || !signupForm.value.username) {
errorDialog.value = { visible: true, message: '请填写完整的队伍信息' }
return
}
} else {
if (!signupForm.value.username) {
errorDialog.value = { visible: true, message: '请填写完整的个人信息' }
return
}
}
// 验证 sign_name
const signName = signupForm.value.username.trim()
if (!signName) {
errorDialog.value = { visible: true, message: '参赛人员名称不能为空' }
return
}
if (signName.length < 2) {
errorDialog.value = { visible: true, message: '参赛人员名称至少需要2个字符' }
return
}
if (signName.length > 20) {
errorDialog.value = { visible: true, message: '参赛人员名称不能超过20个字符' }
return
}
// 只允许中文、英文、数字和下划线
if (!/^[\u4e00-\u9fa5a-zA-Z0-9_]+$/.test(signName)) {
errorDialog.value = { visible: true, message: '参赛人员名称只能包含中文、英文、数字和下划线' }
return
}
// 不允许纯数字
if (/^\d+$/.test(signName)) {
errorDialog.value = { visible: true, message: '参赛人员名称不能为纯数字' }
return
}
// 不允许纯下划线
if (/^_+$/.test(signName)) {
errorDialog.value = { visible: true, message: '参赛人员名称不能为纯下划线' }
return
}
try {
// 确保所有必需字段都存在
if (!competitionInfo.value.id || !competitionInfo.value.name) {
errorDialog.value = { visible: true, message: '比赛信息不完整,请返回重试' }
return
}
const submitData = {
tournament_id: parseInt(competitionInfo.value.id),
type: signupForm.value.type,
teamname: signupForm.value.type === 'teamname' ? signupForm.value.teamName : ' ',
username: signupForm.value.username,
faction: signupForm.value.faction,
qq: signupForm.value.qq
}
const signupResultData = {
tournament_id: parseInt(competitionInfo.value.id),
tournament_name: competitionInfo.value.name,
team_name: signupForm.value.type === 'teamname' ? signupForm.value.teamName : ' ',
sign_name: signupForm.value.username,
win: '0',
lose: '0',
status: 'tie',
round: '0',
rival_name: ''
}
const result = await addSignUp(submitData)
const resultSignup = await addSignUpResult(signupResultData)
console.log('提交的报名数据:', submitData, signupResultData)
console.log('报名结果:', result, resultSignup)
successDialog.value = { visible: true, message: '报名成功!' }
setTimeout(() => {
router.push('/competition')
}, 1500)
} catch (error) {
console.error('报名失败:', error)
console.error('错误详情:', {
message: error.message,
response: error.response?.data,
status: error.response?.status
})
// 显示具体的错误信息
if (error.message.includes('返回数据为空')) {
errorDialog.value = { visible: true, message: '服务器返回数据为空,请稍后重试' }
} else if (error.message.includes('数据不完整')) {
errorDialog.value = { visible: true, message: '报名数据不完整,请重试' }
} else if (error.message.includes('网络连接')) {
errorDialog.value = { visible: true, message: '网络连接失败,请检查网络后重试' }
} else {
errorDialog.value = { visible: true, message: error.message || '报名失败,请稍后重试' }
}
}
}
onMounted(() => {
if (!competitionInfo.value.name) {
router.push('/competition')
}
})
</script>
<style scoped>
.signup-page {
padding: 20px;
max-width: 800px;
margin: 0 auto;
}
.page-header {
margin-bottom: 30px;
}
.page-header h1 {
font-size: 24px;
color: #1a237e;
margin: 0 0 8px 0;
}
.header-subtitle {
color: #666;
font-size: 14px;
}
.competition-name {
font-weight: 500;
color: #1a237e;
}
.signup-form {
background: white;
border-radius: 12px;
padding: 24px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.form-group {
margin-bottom: 24px;
}
.form-group label {
display: block;
font-size: 16px;
font-weight: 500;
color: #1a237e;
margin-bottom: 16px;
}
.info-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
background: #f8f9fa;
padding: 20px;
border-radius: 8px;
}
.info-item {
display: flex;
gap: 8px;
align-items: center;
}
.info-item .label {
color: #666;
min-width: 80px;
font-weight: 500;
}
.info-item .value {
color: #333;
font-weight: 500;
}
.input-group {
margin-bottom: 16px;
}
.form-input {
width: 100%;
padding: 10px 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
transition: all 0.3s ease;
}
.form-input:focus {
border-color: #1a237e;
outline: none;
box-shadow: 0 0 0 2px rgba(26, 35, 126, 0.1);
}
.form-textarea {
width: 100%;
padding: 10px 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
min-height: 100px;
resize: vertical;
transition: all 0.3s ease;
}
.form-textarea:focus {
border-color: #1a237e;
outline: none;
box-shadow: 0 0 0 2px rgba(26, 35, 126, 0.1);
}
.form-select {
width: 100%;
padding: 10px 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
background-color: white;
cursor: pointer;
transition: all 0.3s ease;
}
.form-select:focus {
border-color: #1a237e;
outline: none;
box-shadow: 0 0 0 2px rgba(26, 35, 126, 0.1);
}
.form-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 24px;
}
.btn-cancel {
padding: 10px 24px;
border: 1px solid #ddd;
border-radius: 6px;
background: white;
color: #666;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-cancel:hover {
background: #f5f5f5;
border-color: #ccc;
}
.btn-submit {
padding: 10px 24px;
border: none;
border-radius: 6px;
background: linear-gradient(90deg, #71eaeb 0%, #416bdf 100%);
color: white;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-submit:hover {
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(65, 107, 223, 0.2);
}
.btn-submit:active {
transform: translateY(0);
}
@media (max-width: 768px) {
.signup-page {
padding: 15px;
}
.info-grid {
grid-template-columns: 1fr;
}
.form-actions {
flex-direction: column;
}
.btn-cancel,
.btn-submit {
width: 100%;
}
}
.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;
}
.status-select {
margin-top: 16px;
}
.status-select .form-select {
background-color: #f8f9fa;
}
.status-select .form-select option[value="tie"] {
color: #ff9800;
}
.status-select .form-select option[value="win"] {
color: #4caf50;
}
.status-select .form-select option[value="lose"] {
color: #f44336;
}
</style>