DCFronted/src/views/index/CompetitionSignUp.vue
2025-05-26 20:35:34 +08:00

518 lines
12 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>
</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>