518 lines
12 KiB
Vue
518 lines
12 KiB
Vue
<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>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted, watch } from 'vue'
|
||
import { useRoute, useRouter } from 'vue-router'
|
||
import { addSignUp } from '@/api/tournament'
|
||
|
||
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: ''
|
||
})
|
||
|
||
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) {
|
||
alert('请填写完整的队伍信息')
|
||
return
|
||
}
|
||
} else {
|
||
if (!signupForm.value.username) {
|
||
alert('请填写完整的个人信息')
|
||
return
|
||
}
|
||
}
|
||
|
||
// 验证 sign_name
|
||
const signName = signupForm.value.username.trim()
|
||
if (!signName) {
|
||
alert('参赛人员名称不能为空')
|
||
return
|
||
}
|
||
if (signName.length < 2) {
|
||
alert('参赛人员名称至少需要2个字符')
|
||
return
|
||
}
|
||
if (signName.length > 20) {
|
||
alert('参赛人员名称不能超过20个字符')
|
||
return
|
||
}
|
||
// 只允许中文、英文、数字和下划线
|
||
if (!/^[\u4e00-\u9fa5a-zA-Z0-9_]+$/.test(signName)) {
|
||
alert('参赛人员名称只能包含中文、英文、数字和下划线')
|
||
return
|
||
}
|
||
// 不允许纯数字
|
||
if (/^\d+$/.test(signName)) {
|
||
alert('参赛人员名称不能为纯数字')
|
||
return
|
||
}
|
||
// 不允许纯下划线
|
||
if (/^_+$/.test(signName)) {
|
||
alert('参赛人员名称不能为纯下划线')
|
||
return
|
||
}
|
||
|
||
try {
|
||
// 确保所有必需字段都存在
|
||
if (!competitionInfo.value.id || !competitionInfo.value.name) {
|
||
alert('比赛信息不完整,请返回重试')
|
||
return
|
||
}
|
||
|
||
const submitData = {
|
||
id: parseInt(competitionInfo.value.id),
|
||
tournament_name: competitionInfo.value.name,
|
||
type: signupForm.value.type,
|
||
team_name: signupForm.value.type === 'teamname' ? signupForm.value.teamName : '',
|
||
sign_name: signName,
|
||
faction: signupForm.value.faction,
|
||
qq_code: String(competitionInfo.value.qq_code) // 确保 qq_code 是字符串类型
|
||
}
|
||
|
||
console.log('提交的报名数据:', submitData)
|
||
const result = await addSignUp(submitData)
|
||
console.log('报名结果:', result)
|
||
|
||
if (result.signup && result.result) {
|
||
alert('报名成功!')
|
||
router.push('/competition')
|
||
} else {
|
||
console.error('报名结果不完整:', result)
|
||
throw new Error('报名数据不完整,请重试')
|
||
}
|
||
} catch (error) {
|
||
console.error('报名失败:', error)
|
||
console.error('错误详情:', {
|
||
message: error.message,
|
||
response: error.response?.data,
|
||
status: error.response?.status
|
||
})
|
||
|
||
// 显示具体的错误信息
|
||
if (error.message.includes('返回数据为空')) {
|
||
alert('服务器返回数据为空,请稍后重试')
|
||
} else if (error.message.includes('数据不完整')) {
|
||
alert('报名数据不完整,请重试')
|
||
} else if (error.message.includes('网络连接')) {
|
||
alert('网络连接失败,请检查网络后重试')
|
||
} else {
|
||
alert(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> |