Merge branch 'main' of http://8.153.108.90:3000/YD/YD_Event.Web
This commit is contained in:
commit
4e851c9e36
@ -3,15 +3,8 @@
|
||||
<ProTable ref="tableRef" :request-api="usereventregistrationListGet" :columns="columns" :pagination="true"
|
||||
:tool-button="false" :init-param="initParam" row-key="id">
|
||||
<template #coverImage="scope">
|
||||
<PreviewImage
|
||||
v-if="scope.row?.coverImage"
|
||||
:src="scope.row.coverImage"
|
||||
width="80px"
|
||||
height="60px"
|
||||
fit="cover"
|
||||
radius="4px"
|
||||
:show-zoom-icon="true"
|
||||
/>
|
||||
<PreviewImage v-if="scope.row?.coverImage" :src="scope.row.coverImage" width="80px" height="60px"
|
||||
fit="cover" radius="4px" :show-zoom-icon="true" />
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
|
||||
@ -61,7 +54,8 @@
|
||||
</ProTable>
|
||||
|
||||
<!-- 赛事详情弹窗 -->
|
||||
<el-dialog title="赛事详情" v-model="isShowDetailDialog" width="80%" style="min-height: 600px;" @closed="clearDetailState">
|
||||
<el-dialog title="赛事详情" v-model="isShowDetailDialog" width="80%" style="min-height: 600px;"
|
||||
@closed="clearDetailState">
|
||||
<el-skeleton :rows="12" animated v-if="detailLoading" />
|
||||
<template v-else>
|
||||
<div v-if="formData">
|
||||
@ -103,22 +97,21 @@
|
||||
<div class="project-card" v-for="project in projects" :key="project.id">
|
||||
<div class="project-card-left">
|
||||
<div class="project-card-name">名称:{{ project.name }}</div>
|
||||
<div class="project-card-type">类型:{{ project.parentProjectTypeName ?? project.projectTypeName ?? '' }}</div>
|
||||
<div class="project-card-type">类型:{{ project.parentProjectTypeName ??
|
||||
project.projectTypeName ?? '' }}</div>
|
||||
<div class="project-card-count">完成人数:{{ project.completionCount ?? 0 }}</div>
|
||||
</div>
|
||||
<div class="project-card-right">
|
||||
<div
|
||||
v-for="g in (project.groups ?? [])"
|
||||
:key="g.id"
|
||||
class="group-tag"
|
||||
<div v-for="g in (project.groups ?? [])" :key="g.id" class="group-tag"
|
||||
:class="{ 'group-tag-selected': selectedGroupIds.includes(g.id) }"
|
||||
@click="toggleGroupSelection(g.id)"
|
||||
>
|
||||
@click="toggleGroupSelection(g.id)">
|
||||
<span class="group-tag-name">{{ g.groupName ?? g.name }}</span>
|
||||
<span class="group-tag-date" v-if="g.birthDateStart || g.birthDateEnd">{{ formatGroupDate(g) }}</span>
|
||||
<span class="group-tag-date" v-if="g.birthDateStart || g.birthDateEnd">{{
|
||||
formatGroupDate(g) }}</span>
|
||||
<span class="group-tag-gender">{{ formatGender(g.gender) }}</span>
|
||||
</div>
|
||||
<el-empty v-if="!(project.groups?.length)" description="暂无组别" :image-size="40" style="padding: 8px 0;" />
|
||||
<el-empty v-if="!(project.groups?.length)" description="暂无组别" :image-size="40"
|
||||
style="padding: 8px 0;" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -126,14 +119,8 @@
|
||||
<div class="selected-summary" v-if="selectedGroupItems.length > 0">
|
||||
<div class="selected-summary-header">报名项目</div>
|
||||
<div class="selected-summary-list">
|
||||
<el-tag
|
||||
v-for="item in selectedGroupItems"
|
||||
:key="item.id"
|
||||
closable
|
||||
@close="toggleGroupSelection(item.id)"
|
||||
style="margin: 4px;"
|
||||
size="large"
|
||||
>
|
||||
<el-tag v-for="item in selectedGroupItems" :key="item.id" closable
|
||||
@close="toggleGroupSelection(item.id)" style="margin: 4px;" size="large">
|
||||
{{ item.groupName ?? item.name }}
|
||||
<template v-if="item.dateRange"> · {{ item.dateRange }}</template>
|
||||
· {{ formatGender(item.gender) }}
|
||||
@ -144,11 +131,7 @@
|
||||
<!-- 底部操作区域 -->
|
||||
<div class="step-footer" style="display: flex; justify-content: flex-end; margin-top: 20px;">
|
||||
<el-button @click="isShowRegisterDialog = false" style="margin-right: 10px;">取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handleStep1Next"
|
||||
:loading="registerLoading"
|
||||
>
|
||||
<el-button type="primary" @click="handleStep1Next" :loading="registerLoading">
|
||||
下一步
|
||||
</el-button>
|
||||
</div>
|
||||
@ -160,101 +143,84 @@
|
||||
<div class="step2-layout">
|
||||
<!-- 左侧:已选组别,每个组别可分配队员 -->
|
||||
<div class="step2-left">
|
||||
<div
|
||||
class="assign-card"
|
||||
v-for="(group, gIdx) in selectedGroupItems"
|
||||
:key="group.id"
|
||||
<div class="assign-card" v-for="(group, gIdx) in selectedGroupItems" :key="group.id"
|
||||
:class="{ 'assign-card-active': activeAssignGroupId === group.id }"
|
||||
@click="activeAssignGroupId = group.id"
|
||||
>
|
||||
@click="activeAssignGroupId = group.id">
|
||||
<div class="assign-card-header">
|
||||
<span>
|
||||
({{ group.parentProjectTypeName ?? group.projectTypeName ?? (group.projectTeamSize > 1 ? '团队' : '个人') }}/{{ group.projectCompletionCount ?? 0 }})
|
||||
{{ group.projectName }}-{{ group.groupName ?? group.name }}/{{ formatGroupDate(group) }}/{{ formatGender(group.gender) }}
|
||||
({{ group.parentProjectTypeName ?? group.projectTypeName ??
|
||||
(group.projectTeamSize > 1 ?
|
||||
'团队' : '个人') }}/{{ group.projectCompletionCount ?? 0 }})
|
||||
{{ group.projectName }}-{{ group.groupName ?? group.name }}/{{
|
||||
formatGroupDate(group) }}/{{
|
||||
formatGender(group.gender) }}
|
||||
</span>
|
||||
<el-icon class="assign-card-close" @click.stop="toggleGroupSelection(group.id)"><Close /></el-icon>
|
||||
<el-icon class="assign-card-close" @click.stop="toggleGroupSelection(group.id)">
|
||||
<Close />
|
||||
</el-icon>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 团队数量信息和添加团队按钮 -->
|
||||
<div class="team-info-section" v-if="isTeamProject(group) && getGroupMaxTeams(group.id) > 1">
|
||||
<div class="team-info-section"
|
||||
v-if="isTeamProject(group) && getGroupMaxTeams(group.id) > 1">
|
||||
<div class="team-count-info">
|
||||
已添加 {{ getGroupTeams(group.id).length }} / {{ getGroupMaxTeams(group.id) }} 队
|
||||
</div>
|
||||
<el-button
|
||||
v-if="getGroupTeams(group.id).length < getGroupMaxTeams(group.id)"
|
||||
type="primary"
|
||||
size="small"
|
||||
text
|
||||
@click.stop="addTeam(group.id)"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
<el-button v-if="getGroupTeams(group.id).length < getGroupMaxTeams(group.id)"
|
||||
type="primary" size="small" text @click.stop="addTeam(group.id)">
|
||||
<el-icon>
|
||||
<Plus />
|
||||
</el-icon>
|
||||
添加团队
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 团队列表 -->
|
||||
<div v-if="isTeamProject(group) && getGroupMaxTeams(group.id) > 1" class="team-section">
|
||||
<div
|
||||
v-for="team in getGroupTeams(group.id)"
|
||||
:key="team.teamIndex"
|
||||
class="team-card"
|
||||
<div v-for="team in getGroupTeams(group.id)" :key="team.teamIndex" class="team-card"
|
||||
:class="{ 'team-card-active': activeTeamKey === `${group.id}_${team.teamIndex}` }"
|
||||
@click.stop="setActiveTeam(group.id, team.teamIndex)"
|
||||
>
|
||||
:data-team-key="`${group.id}_${team.teamIndex}`"
|
||||
@click.stop="setActiveTeam(group.id, team.teamIndex)">
|
||||
<div class="team-card-header">
|
||||
<span class="team-name">团队{{ getTeamNumber(team.teamIndex) }}</span>
|
||||
<el-button
|
||||
v-if="getGroupTeams(group.id).length > 1"
|
||||
type="danger"
|
||||
size="small"
|
||||
text
|
||||
@click.stop="removeTeam(group.id, team.teamIndex)"
|
||||
>
|
||||
<el-icon><Delete /></el-icon>
|
||||
<el-button v-if="getGroupTeams(group.id).length > 1" type="danger"
|
||||
size="small" text @click.stop="removeTeam(group.id, team.teamIndex)">
|
||||
<el-icon>
|
||||
<Delete />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="team-members">
|
||||
<el-tag
|
||||
v-for="m in team.members"
|
||||
:key="m.userId ?? m.id"
|
||||
closable
|
||||
<el-tag v-for="m in team.members" :key="m.userId ?? m.id" closable
|
||||
:type="getMemberValidationResult(m, group).isValid ? '' : 'danger'"
|
||||
@close.stop="removeMemberFromTeam(group.id, team.teamIndex, m.userId ?? m.id)"
|
||||
style="margin: 3px;"
|
||||
>
|
||||
style="margin: 3px;">
|
||||
{{ m.name }}
|
||||
</el-tag>
|
||||
<span v-if="!team.members.length" class="team-placeholder">选择此团队后添加队员</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 单团队模式(向后兼容) -->
|
||||
<div v-else class="assign-card-members">
|
||||
<el-tag
|
||||
v-for="m in (groupMemberMap[group.id] ?? [])"
|
||||
:key="m.userId ?? m.id"
|
||||
closable
|
||||
:type="getMemberValidationResult(m, group).isValid ? '' : 'danger'"
|
||||
@close="removeMemberFromGroup(group.id, m.userId ?? m.id)"
|
||||
style="margin: 3px;"
|
||||
>
|
||||
<el-tag v-for="m in (groupMemberMap[group.id] ?? [])" :key="m.userId ?? m.id"
|
||||
closable :type="getMemberValidationResult(m, group).isValid ? '' : 'danger'"
|
||||
@close="removeMemberFromGroup(group.id, m.userId ?? m.id)" style="margin: 3px;">
|
||||
{{ m.name }}
|
||||
</el-tag>
|
||||
<span v-if="!(groupMemberMap[group.id]?.length)" class="assign-card-placeholder">点击此卡片后,从右侧选择队员</span>
|
||||
<span v-if="!(groupMemberMap[group.id]?.length)"
|
||||
class="assign-card-placeholder">点击此卡片后,从右侧选择队员</span>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 队员验证错误提示 -->
|
||||
<div v-if="hasValidationErrorsForGroup(group)" class="validation-error-section">
|
||||
<div class="validation-error-item" v-for="m in (isTeamProject(group) && getGroupMaxTeams(group.id) > 1 ? getGroupTeams(group.id).flatMap(t => t.members) : (groupMemberMap[group.id] ?? []))" :key="m.userId ?? m.id">
|
||||
<el-alert
|
||||
v-if="!getMemberValidationResult(m, group).isValid"
|
||||
:title="getMemberValidationResult(m, group).message"
|
||||
type="error"
|
||||
:closable="false"
|
||||
size="small"
|
||||
style="margin-top: 4px;"
|
||||
/>
|
||||
<div class="validation-error-item"
|
||||
v-for="m in (isTeamProject(group) && getGroupMaxTeams(group.id) > 1 ? getGroupTeams(group.id).flatMap(t => t.members) : (groupMemberMap[group.id] ?? []))"
|
||||
:key="m.userId ?? m.id">
|
||||
<el-alert v-if="!getMemberValidationResult(m, group).isValid"
|
||||
:title="getMemberValidationResult(m, group).message" type="error"
|
||||
:closable="false" size="small" style="margin-top: 4px;" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -264,33 +230,19 @@
|
||||
<!-- 右侧:团队选择器 + 成员列表 -->
|
||||
<div class="step2-right">
|
||||
<div class="team-selector">
|
||||
<el-select
|
||||
v-model="selectedTeamId"
|
||||
placeholder="请选择团队"
|
||||
style="width: 100%;"
|
||||
@change="onTeamChange"
|
||||
:loading="teamListLoading"
|
||||
filterable
|
||||
>
|
||||
<el-option
|
||||
v-for="t in teamList"
|
||||
:key="t.id"
|
||||
:label="t.name"
|
||||
:value="t.id"
|
||||
/>
|
||||
<el-select v-model="selectedTeamId" placeholder="请选择团队" style="width: 100%;"
|
||||
@change="onTeamChange" :loading="teamListLoading" filterable>
|
||||
<el-option v-for="t in teamList" :key="t.id" :label="t.name" :value="t.id" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="team-members-panel">
|
||||
<el-skeleton v-if="teamMemberLoading" :rows="6" animated />
|
||||
<el-empty v-else-if="!teamMembers.length" :description="selectedTeamId ? '该团队暂无运动员' : '请先选择团队'" :image-size="60" />
|
||||
<el-empty v-else-if="!teamMembers.length"
|
||||
:description="selectedTeamId ? '该团队暂无运动员' : '请先选择团队'" :image-size="60" />
|
||||
<template v-else>
|
||||
<div
|
||||
v-for="m in teamMembers"
|
||||
:key="m.userId ?? m.id"
|
||||
class="team-member-tag"
|
||||
<div v-for="m in teamMembers" :key="m.userId ?? m.id" class="team-member-tag"
|
||||
:class="{ 'team-member-tag-assigned': isMemberAssignedToActive(m.userId ?? m.id) }"
|
||||
@click="assignMemberToActiveTeam(m)"
|
||||
>
|
||||
@click="assignMemberToActiveTeam(m)">
|
||||
{{ m.name }}
|
||||
</div>
|
||||
</template>
|
||||
@ -301,48 +253,29 @@
|
||||
<!-- 验证提示区域 -->
|
||||
<div class="validation-notes-section">
|
||||
<!-- 年龄性别不符合要求提示 -->
|
||||
<el-alert
|
||||
v-if="hasAnyValidationErrors()"
|
||||
title="队员信息不符合组别要求"
|
||||
type="error"
|
||||
:closable="false"
|
||||
style="margin-bottom: 12px;"
|
||||
/>
|
||||
<el-alert v-if="hasAnyValidationErrors()" title="队员信息不符合组别要求" type="error" :closable="false"
|
||||
style="margin-bottom: 12px;" />
|
||||
|
||||
<!-- 限报提示 -->
|
||||
<template v-if="hasAnyRegistrationLimit()">
|
||||
<el-alert
|
||||
v-for="(detail, index) in (registrationLimitCheckResult?.limitDetails ?? [])"
|
||||
:key="index"
|
||||
:title="detail?.limitMessage ?? '报名限制'"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
style="margin-bottom: 12px;"
|
||||
/>
|
||||
<el-alert v-for="(detail, index) in (registrationLimitCheckResult?.limitDetails ?? [])"
|
||||
:key="index" :title="detail?.limitMessage ?? '报名限制'" type="warning" :closable="false"
|
||||
style="margin-bottom: 12px;" />
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<!-- 队伍数量限制提示 -->
|
||||
<el-alert
|
||||
v-if="showTeamLimitHint"
|
||||
title="若管理后台配置一个组别只能限报2队,添加2队后,'添加'按钮隐藏"
|
||||
type="info"
|
||||
:closable="false"
|
||||
style="margin-bottom: 12px;"
|
||||
/>
|
||||
<el-alert v-if="showTeamLimitHint" title="若管理后台配置一个组别只能限报2队,添加2队后,'添加'按钮隐藏" type="info"
|
||||
:closable="false" style="margin-bottom: 12px;" />
|
||||
</div>
|
||||
|
||||
<!-- 底部操作区域 -->
|
||||
<div class="step-footer" style="display: flex; justify-content: flex-end; margin-top: 20px;">
|
||||
<el-button @click="isShowRegisterDialog = false" style="margin-right: 10px;">取消</el-button>
|
||||
<el-button @click="handlePrevious" style="margin-right: 10px;">上一步</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handleStep2Next"
|
||||
:loading="submitDraftLoading"
|
||||
:disabled="hasAnyValidationErrors() || hasAnyRegistrationLimit()"
|
||||
>
|
||||
<el-button type="primary" @click="handleStep2Next" :loading="submitDraftLoading"
|
||||
:disabled="hasAnyValidationErrors() || hasAnyRegistrationLimit()">
|
||||
下一步
|
||||
</el-button>
|
||||
</div>
|
||||
@ -378,13 +311,15 @@
|
||||
</div>
|
||||
<!-- 支付二维码显示区域(微信/支付宝) -->
|
||||
<div v-else-if="paymentData" class="payment-qrcode-section">
|
||||
<div class="qrcode-title">请使用{{ paymentMethod === 2 ? '支付宝' : '微信'}}扫码支付</div>
|
||||
<div class="qrcode-title">请使用{{ paymentMethod === 2 ? '支付宝' : '微信' }}扫码支付</div>
|
||||
<div class="qrcode-wrapper">
|
||||
<div v-if="paymentData.qrcodeUrl" class="qrcode-container">
|
||||
<div ref="qrcodeRef"></div>
|
||||
</div>
|
||||
<div v-else class="qrcode-placeholder">
|
||||
<el-icon :size="60" color="#909399"><Picture /></el-icon>
|
||||
<el-icon :size="60" color="#909399">
|
||||
<Picture />
|
||||
</el-icon>
|
||||
<div class="qrcode-tip">支付链接获取失败</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -393,7 +328,9 @@
|
||||
<span class="amount-value">¥ {{ finalPaymentAmount }}</span>
|
||||
</div>
|
||||
<div class="payment-status" v-if="pollingStatus">
|
||||
<el-icon class="is-loading"><Loading /></el-icon>
|
||||
<el-icon class="is-loading">
|
||||
<Loading />
|
||||
</el-icon>
|
||||
{{ pollingStatus }}
|
||||
</div>
|
||||
</div>
|
||||
@ -408,7 +345,8 @@
|
||||
<el-table-column prop="projectTypeName" label="类型" align="center" width="180" />
|
||||
<el-table-column label="参赛队员" align="center" min-width="200">
|
||||
<template #default="scope">
|
||||
<el-tag size="small" style="margin: 2px;" v-for="member in scope.row?.members" :key="member.userId">
|
||||
<el-tag size="small" style="margin: 2px;" v-for="member in scope.row?.members"
|
||||
:key="member.userId">
|
||||
{{ member.name }}
|
||||
</el-tag>
|
||||
</template>
|
||||
@ -419,32 +357,23 @@
|
||||
<div class="coupon-section">
|
||||
<div class="detail-section-title">优惠券</div>
|
||||
<div class="coupon-input-wrapper">
|
||||
<el-input
|
||||
v-model="couponCode"
|
||||
placeholder="请输入优惠券兑换码"
|
||||
:disabled="!!validatedCoupon"
|
||||
style="flex: 1; margin-right: 10px;"
|
||||
clearable
|
||||
/>
|
||||
<el-button
|
||||
v-if="!validatedCoupon"
|
||||
type="primary"
|
||||
:loading="couponValidationLoading"
|
||||
@click="validateCoupon"
|
||||
>
|
||||
<el-input v-model="couponCode" placeholder="请输入优惠券兑换码" :disabled="!!validatedCoupon"
|
||||
style="flex: 1; margin-right: 10px;" clearable />
|
||||
<el-button v-if="!validatedCoupon" type="primary" :loading="couponValidationLoading"
|
||||
@click="validateCoupon">
|
||||
使用优惠券
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
type="danger"
|
||||
@click="clearCoupon"
|
||||
>
|
||||
<el-button v-else type="danger" @click="clearCoupon">
|
||||
清除优惠券
|
||||
</el-button>
|
||||
</div>
|
||||
<div v-if="validatedCoupon" class="coupon-result success">
|
||||
<el-icon style="color: #67c23a;"><CircleCheck /></el-icon>
|
||||
<span>{{ validatedCoupon.couponName }},{{validatedCoupon.discountType == 0 ? `优惠价${validatedCoupon.discountValue / 10}折` : `优惠 ${validatedCoupon.discountAmount}元`}}</span>
|
||||
<el-icon style="color: #67c23a;">
|
||||
<CircleCheck />
|
||||
</el-icon>
|
||||
<span>{{ validatedCoupon.couponName }},{{ validatedCoupon.discountType == 0 ?
|
||||
`优惠价${validatedCoupon.discountValue / 10}折` : `优惠
|
||||
${validatedCoupon.discountAmount}元`}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -462,16 +391,19 @@
|
||||
</div>
|
||||
<div class="payment-actions">
|
||||
<div class="payment-method-wrapper" v-if="!isFreePreview">
|
||||
<el-select v-model="paymentMethod" placeholder="请选择支付方式" style="width: 200px; margin-right: 10px;">
|
||||
<el-select v-model="paymentMethod" placeholder="请选择支付方式"
|
||||
style="width: 200px; margin-right: 10px;">
|
||||
<el-option :value="1" label="微信支付" />
|
||||
<el-option :value="2" label="支付宝支付" />
|
||||
<el-option :value="3" label="银行转账" />
|
||||
</el-select>
|
||||
<el-button type="primary" @click="handleCreateOrderAndPay" :loading="createOrderLoading" :disabled="!draftRegistrationId">
|
||||
<el-button type="primary" @click="handleCreateOrderAndPay"
|
||||
:loading="createOrderLoading" :disabled="!draftRegistrationId">
|
||||
确认支付
|
||||
</el-button>
|
||||
</div>
|
||||
<el-button v-else type="primary" @click="handleCreateOrderAndPay" :loading="createOrderLoading" :disabled="!draftRegistrationId">
|
||||
<el-button v-else type="primary" @click="handleCreateOrderAndPay"
|
||||
:loading="createOrderLoading" :disabled="!draftRegistrationId">
|
||||
确认报名
|
||||
</el-button>
|
||||
</div>
|
||||
@ -488,8 +420,10 @@
|
||||
<!-- 底部操作区域 -->
|
||||
<div class="step-footer step-footer--payment">
|
||||
<el-button @click="isShowRegisterDialog = false">取消</el-button>
|
||||
<el-button v-if="paymentData" type="primary" @click="refreshPayment" :loading="polling">刷新二维码</el-button>
|
||||
<el-button v-if="paymentData && paymentMethod === 3" type="primary" @click="confirmBankTransfer">确认已转账</el-button>
|
||||
<el-button v-if="paymentData" type="primary" @click="refreshPayment"
|
||||
:loading="polling">刷新二维码</el-button>
|
||||
<el-button v-if="paymentData && paymentMethod === 3" type="primary"
|
||||
@click="confirmBankTransfer">确认已转账</el-button>
|
||||
<el-button @click="handlePrevious">上一步</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@ -543,13 +477,15 @@ const columns = ref([
|
||||
{ prop: 'type', label: '赛事类型', align: 'center', width: 120 },
|
||||
{ prop: 'registrationType', label: '报名方式', align: 'center', width: 120 },
|
||||
{ prop: 'chargeMode', label: '收费模式', align: 'center', width: 120 },
|
||||
{ prop: 'status', label: '赛事状态', align: 'center', width: 120 , enum: [
|
||||
{
|
||||
prop: 'status', label: '赛事状态', align: 'center', width: 120, enum: [
|
||||
{ label: '未开始', value: 0 },
|
||||
{ label: '报名中', value: 1 },
|
||||
{ label: '报名结束', value: 2 },
|
||||
{ label: '比赛中', value: 3 },
|
||||
{ label: '已结束', value: 4 },
|
||||
],},
|
||||
],
|
||||
},
|
||||
{ prop: 'registrationStartTime', label: '报名起止时间', align: 'center', minWidth: 320 },
|
||||
{ prop: 'matchStartTime', label: '比赛起止时间', align: 'center', minWidth: 320 },
|
||||
{ prop: 'action', label: '操作', align: 'center', fixed: 'right', width: 210 },
|
||||
@ -905,8 +841,8 @@ const formatGender = (gender) => {
|
||||
if (v === 0) return '男';
|
||||
if (v === 1) return '女';
|
||||
if (v === 2) return '保密';
|
||||
if (v === 3) return '不限';
|
||||
return '不限';
|
||||
if (v === 3) return '性别不限';
|
||||
return '性别不限';
|
||||
};
|
||||
|
||||
// 格式化组别的年龄段(出生日期范围)
|
||||
@ -1066,7 +1002,7 @@ const openRegistrationDialog = async (row) => {
|
||||
ElMessage.error(data ?? '项目加载失败');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 检查报名类型,如果是只允许个人报名,则显示提示并阻止继续
|
||||
const registrationType = data?.registrationType;
|
||||
if (registrationType === 'OnlyIndividual') {
|
||||
@ -1080,7 +1016,7 @@ const openRegistrationDialog = async (row) => {
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 保存完整的详情数据,用于项目类型显示等
|
||||
detailData.value = data;
|
||||
projects.value = Array.isArray(data?.projects) ? data.projects : [];
|
||||
@ -1119,7 +1055,7 @@ const fetchTeamList = async () => {
|
||||
const onTeamChange = async (teamId) => {
|
||||
teamMembers.value = [];
|
||||
if (!teamId) return;
|
||||
|
||||
|
||||
// 检查是否已经分配了其他团队的队员
|
||||
if (usedTeamId.value && usedTeamId.value !== teamId) {
|
||||
ElMessage.warning('本次报名已选择了其他团队的队员,请先移除已分配的队员');
|
||||
@ -1127,7 +1063,7 @@ const onTeamChange = async (teamId) => {
|
||||
selectedTeamId.value = usedTeamId.value;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
teamMemberLoading.value = true;
|
||||
try {
|
||||
const { succeeded, data } = await teamDetailGet(teamId);
|
||||
@ -1154,7 +1090,20 @@ const onTeamChange = async (teamId) => {
|
||||
// 判断某成员是否已分配到当前激活的组别
|
||||
const isMemberAssignedToActive = (memberId) => {
|
||||
if (!activeAssignGroupId.value) return false;
|
||||
const list = groupMemberMap.value[activeAssignGroupId.value] ?? [];
|
||||
const gId = activeAssignGroupId.value;
|
||||
// 多团队模式:检查当前激活的 team-card 里是否有该成员
|
||||
if (activeTeamKey.value) {
|
||||
const [activeGroupId, activeTeamIdx] = activeTeamKey.value.split('_').map(Number);
|
||||
if (activeGroupId === gId) {
|
||||
const teams = groupTeamsMap.value[gId] ?? [];
|
||||
const team = teams.find(t => t.teamIndex === activeTeamIdx);
|
||||
if (team) {
|
||||
return team.members.some(m => (m.userId ?? m.id) === memberId);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 单团队模式降级
|
||||
const list = groupMemberMap.value[gId] ?? [];
|
||||
return list.some((m) => (m.userId ?? m.id) === memberId);
|
||||
};
|
||||
|
||||
@ -1200,13 +1149,13 @@ const validateAllMembers = async () => {
|
||||
|
||||
for (const group of selectedGroupItems.value) {
|
||||
const gId = group.id;
|
||||
|
||||
|
||||
// 检查是否为团队项目且启用了多团队模式
|
||||
const isMultiTeamMode = isTeamProject(group) && groupTeamsMap.value[gId] &&
|
||||
const isMultiTeamMode = isTeamProject(group) && groupTeamsMap.value[gId] &&
|
||||
(groupTeamsMap.value[gId].length > 1 || getGroupMaxTeams(gId) > 1);
|
||||
|
||||
let membersToValidate = [];
|
||||
|
||||
|
||||
if (isMultiTeamMode) {
|
||||
// 多团队模式:收集所有团队的队员
|
||||
for (const team of groupTeamsMap.value[gId]) {
|
||||
@ -1330,7 +1279,7 @@ const assignMemberToActiveGroup = async (member) => {
|
||||
const removeMemberFromGroup = (groupId, memberId) => {
|
||||
if (!groupMemberMap.value[groupId]) return;
|
||||
groupMemberMap.value[groupId] = groupMemberMap.value[groupId].filter((m) => (m.userId ?? m.id) !== memberId);
|
||||
|
||||
|
||||
// 检查是否还有队员,如果没有则重置已使用的团队
|
||||
let hasMembers = false;
|
||||
Object.values(groupMemberMap.value).forEach(members => {
|
||||
@ -1341,7 +1290,7 @@ const removeMemberFromGroup = (groupId, memberId) => {
|
||||
if (!hasMembers) {
|
||||
usedTeamId.value = null;
|
||||
}
|
||||
|
||||
|
||||
// 触发实时验证
|
||||
debounceValidateAllMembers();
|
||||
};
|
||||
@ -1364,18 +1313,18 @@ const isTeamProject = (group) => {
|
||||
// 1006: 团队花样
|
||||
return group.projectType === 1005 || group.projectType === 1006;
|
||||
}
|
||||
|
||||
|
||||
// 备用判断:使用 projectTypeName 或 parentProjectTypeName
|
||||
if (group?.projectTypeName || group?.parentProjectTypeName) {
|
||||
const typeName = group.projectTypeName || group.parentProjectTypeName;
|
||||
return typeName && typeName.includes('团队');
|
||||
}
|
||||
|
||||
|
||||
// 最后的备用判断:使用 projectTeamSize
|
||||
if (group?.projectTeamSize) {
|
||||
return group.projectTeamSize > 1;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
@ -1407,18 +1356,18 @@ const addTeam = (groupId) => {
|
||||
initGroupTeams(groupId);
|
||||
const teams = groupTeamsMap.value[groupId];
|
||||
const maxTeams = getGroupMaxTeams(groupId);
|
||||
|
||||
|
||||
if (teams.length >= maxTeams) {
|
||||
ElMessage.warning(`已达到该项目组别限报${maxTeams}队的上限`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const newTeamIndex = teams.length + 1;
|
||||
teams.push({
|
||||
teamIndex: newTeamIndex,
|
||||
members: []
|
||||
});
|
||||
|
||||
|
||||
ElMessage.success(`已添加团队${getTeamNumber(newTeamIndex)}`);
|
||||
};
|
||||
|
||||
@ -1426,12 +1375,12 @@ const addTeam = (groupId) => {
|
||||
const removeTeam = async (groupId, teamIndex) => {
|
||||
initGroupTeams(groupId);
|
||||
const teams = groupTeamsMap.value[groupId];
|
||||
|
||||
|
||||
if (teams.length <= 1) {
|
||||
ElMessage.warning('至少需要保留一个团队');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要删除团队${getTeamNumber(teamIndex)}吗?该团队的队员也会被移除。`,
|
||||
@ -1442,16 +1391,16 @@ const removeTeam = async (groupId, teamIndex) => {
|
||||
type: 'warning'
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
const teamIdx = teams.findIndex(t => t.teamIndex === teamIndex);
|
||||
if (teamIdx > -1) {
|
||||
teams.splice(teamIdx, 1);
|
||||
|
||||
|
||||
// 重新编号团队索引
|
||||
teams.forEach((team, idx) => {
|
||||
team.teamIndex = idx + 1;
|
||||
});
|
||||
|
||||
|
||||
ElMessage.success('团队删除成功');
|
||||
}
|
||||
} catch {
|
||||
@ -1465,24 +1414,29 @@ const getActiveTeamMembers = (groupId) => {
|
||||
if (!groupTeamsMap.value[groupId] || groupTeamsMap.value[groupId].length === 0) {
|
||||
return groupMemberMap.value[groupId] ?? [];
|
||||
}
|
||||
|
||||
|
||||
// 多团队模式:找到当前激活的团队
|
||||
if (!activeTeamKey.value) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
const [activeGroupId, activeTeamIdx] = activeTeamKey.value.split('_').map(Number);
|
||||
if (activeGroupId !== groupId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
const team = groupTeamsMap.value[groupId].find(t => t.teamIndex === activeTeamIdx);
|
||||
return team?.members ?? [];
|
||||
};
|
||||
|
||||
// 设置当前激活的团队
|
||||
const setActiveTeam = (groupId, teamIndex) => {
|
||||
activeAssignGroupId.value = groupId;
|
||||
activeTeamKey.value = `${groupId}_${teamIndex}`;
|
||||
nextTick(() => {
|
||||
const el = document.querySelector(`.team-card[data-team-key="${groupId}_${teamIndex}"]`);
|
||||
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
});
|
||||
};
|
||||
|
||||
// 将成员分配到当前激活的团队
|
||||
@ -1492,55 +1446,55 @@ const assignMemberToActiveTeam = async (member) => {
|
||||
ElMessage.warning('请先点击左侧的组别卡片');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
initGroupTeams(gId);
|
||||
|
||||
|
||||
// 获取当前组别信息
|
||||
const group = selectedGroupItems.value.find(g => g.id === gId);
|
||||
// 检查是否为团队项目且启用了多团队模式
|
||||
const isMultiTeamMode = group && isTeamProject(group) &&
|
||||
const isMultiTeamMode = group && isTeamProject(group) &&
|
||||
(groupTeamsMap.value[gId].length > 1 || getGroupMaxTeams(gId) > 1);
|
||||
|
||||
|
||||
if (!isMultiTeamMode) {
|
||||
// 单团队模式,使用原来的逻辑
|
||||
await assignMemberToActiveGroup(member);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 多团队模式
|
||||
if (!activeTeamKey.value) {
|
||||
ElMessage.warning('请先选择要分配队员的团队');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const [activeGroupId, activeTeamIdx] = activeTeamKey.value.split('_').map(Number);
|
||||
if (activeGroupId !== gId) {
|
||||
ElMessage.warning('请先选择对应组别的团队');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const team = groupTeamsMap.value[gId].find(t => t.teamIndex === activeTeamIdx);
|
||||
if (!team) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const memberId = member.userId ?? member.id;
|
||||
// 已存在则不重复添加
|
||||
if (team.members.some((m) => (m.userId ?? m.id) === memberId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 检查队员是否来自已选择的团队
|
||||
if (usedTeamId.value && member.teamId !== usedTeamId.value) {
|
||||
ElMessage.warning('本次报名只能选择同一个团队的队员');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 设置已使用的团队
|
||||
if (!usedTeamId.value) {
|
||||
usedTeamId.value = member.teamId;
|
||||
}
|
||||
|
||||
|
||||
team.members.push({ ...member, teamId: member.teamId });
|
||||
// 触发实时验证(包含报名限制检查)
|
||||
debounceValidateAllMembers();
|
||||
@ -1551,9 +1505,9 @@ const removeMemberFromTeam = (groupId, teamIndex, memberId) => {
|
||||
initGroupTeams(groupId);
|
||||
const team = groupTeamsMap.value[groupId].find(t => t.teamIndex === teamIndex);
|
||||
if (!team) return;
|
||||
|
||||
|
||||
team.members = team.members.filter((m) => (m.userId ?? m.id) !== memberId);
|
||||
|
||||
|
||||
// 检查是否还有队员
|
||||
let hasMembers = false;
|
||||
groupTeamsMap.value[groupId].forEach(t => {
|
||||
@ -1564,7 +1518,7 @@ const removeMemberFromTeam = (groupId, teamIndex, memberId) => {
|
||||
if (!hasMembers) {
|
||||
usedTeamId.value = null;
|
||||
}
|
||||
|
||||
|
||||
// 触发实时验证
|
||||
debounceValidateAllMembers();
|
||||
};
|
||||
@ -1679,29 +1633,29 @@ const checkRegistrationLimit = async () => {
|
||||
// 构造按组别分配的数据(与提交报名时保持一致)
|
||||
const groupMembers = [];
|
||||
const allMemberIdsSet = new Set();
|
||||
|
||||
|
||||
for (const gId of selectedGroupIds.value) {
|
||||
// 找到当前组别信息
|
||||
const group = selectedGroupItems.value.find(g => g.id === gId);
|
||||
// 检查是否为团队项目且启用了多团队模式
|
||||
const isMultiTeamMode = group && isTeamProject(group) && groupTeamsMap.value[gId] &&
|
||||
const isMultiTeamMode = group && isTeamProject(group) && groupTeamsMap.value[gId] &&
|
||||
(groupTeamsMap.value[gId].length > 1 || getGroupMaxTeams(gId) > 1);
|
||||
|
||||
|
||||
if (isMultiTeamMode) {
|
||||
// 多团队模式
|
||||
const teams = groupTeamsMap.value[gId];
|
||||
const groupTeamsData = [];
|
||||
|
||||
|
||||
for (const team of teams) {
|
||||
const memberIds = team.members.map(m => m.userId ?? m.id);
|
||||
memberIds.forEach(id => allMemberIdsSet.add(id));
|
||||
|
||||
|
||||
groupTeamsData.push({
|
||||
teamIndex: team.teamIndex,
|
||||
memberIds: memberIds
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
groupMembers.push({
|
||||
groupId: gId,
|
||||
memberIds: groupTeamsData.flatMap(t => t.memberIds), // 保持向后兼容
|
||||
@ -1711,16 +1665,16 @@ const checkRegistrationLimit = async () => {
|
||||
// 单团队模式(保持向后兼容)
|
||||
const memberIds = (groupMemberMap.value[gId] ?? []).map((m) => m.userId ?? m.id);
|
||||
memberIds.forEach(id => allMemberIdsSet.add(id));
|
||||
|
||||
|
||||
groupMembers.push({
|
||||
groupId: gId,
|
||||
memberIds: memberIds
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const allMemberIds = [...allMemberIdsSet];
|
||||
|
||||
|
||||
try {
|
||||
const { succeeded, data } = await usereventregistrationRegistrationLimitCheckPost({
|
||||
eventId: currentEventId.value,
|
||||
@ -1783,7 +1737,7 @@ const handleStep1Next = async () => {
|
||||
await fetchTeamList();
|
||||
// 加载队伍限制配置
|
||||
await fetchGroupTeamLimit();
|
||||
|
||||
|
||||
// 初始化每个组别的团队数据
|
||||
await nextTick();
|
||||
selectedGroupItems.value.forEach(group => {
|
||||
@ -1813,7 +1767,7 @@ const handleStep2Next = async () => {
|
||||
for (const g of selectedGroupItems.value) {
|
||||
const gId = g.id;
|
||||
let hasMembers = false;
|
||||
|
||||
|
||||
// 检查是否为团队项目且启用了多团队模式
|
||||
if (isTeamProject(g) && groupTeamsMap.value[gId] && groupTeamsMap.value[gId].length > 0) {
|
||||
for (const team of groupTeamsMap.value[gId]) {
|
||||
@ -1826,12 +1780,12 @@ const handleStep2Next = async () => {
|
||||
// 单团队模式
|
||||
hasMembers = !!(groupMemberMap.value[gId]?.length);
|
||||
}
|
||||
|
||||
|
||||
if (!hasMembers) {
|
||||
emptyGroups.push(g);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (emptyGroups.length > 0) {
|
||||
ElMessage.warning(`请为所有组别分配队员,"${emptyGroups[0].groupName ?? emptyGroups[0].name}"尚未分配`);
|
||||
return;
|
||||
@ -1848,35 +1802,35 @@ const handleStep2Next = async () => {
|
||||
const groupMembers = [];
|
||||
const allTeams = []; // 用于收集所有团队数据
|
||||
const allMemberIdsSet = new Set();
|
||||
|
||||
|
||||
for (const gId of selectedGroupIds.value) {
|
||||
// 找到当前组别信息
|
||||
const group = selectedGroupItems.value.find(g => g.id === gId);
|
||||
// 检查是否为团队项目且启用了多团队模式
|
||||
const isMultiTeamMode = group && isTeamProject(group) && groupTeamsMap.value[gId] &&
|
||||
const isMultiTeamMode = group && isTeamProject(group) && groupTeamsMap.value[gId] &&
|
||||
(groupTeamsMap.value[gId].length > 1 || getGroupMaxTeams(gId) > 1);
|
||||
|
||||
|
||||
if (isMultiTeamMode) {
|
||||
// 多团队模式
|
||||
const teams = groupTeamsMap.value[gId];
|
||||
const groupTeamsData = [];
|
||||
|
||||
|
||||
for (const team of teams) {
|
||||
const memberIds = team.members.map(m => m.userId ?? m.id);
|
||||
memberIds.forEach(id => allMemberIdsSet.add(id));
|
||||
|
||||
|
||||
groupTeamsData.push({
|
||||
teamIndex: team.teamIndex,
|
||||
memberIds: memberIds
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
groupMembers.push({
|
||||
groupId: gId,
|
||||
memberIds: groupTeamsData.flatMap(t => t.memberIds), // 保持向后兼容
|
||||
teams: groupTeamsData
|
||||
});
|
||||
|
||||
|
||||
allTeams.push(...groupTeamsData.map(t => ({
|
||||
groupId: gId,
|
||||
...t
|
||||
@ -1885,14 +1839,14 @@ const handleStep2Next = async () => {
|
||||
// 单团队模式(保持向后兼容)
|
||||
const memberIds = (groupMemberMap.value[gId] ?? []).map((m) => m.userId ?? m.id);
|
||||
memberIds.forEach(id => allMemberIdsSet.add(id));
|
||||
|
||||
|
||||
groupMembers.push({
|
||||
groupId: gId,
|
||||
memberIds: memberIds
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const allMemberIds = [...allMemberIdsSet];
|
||||
|
||||
submitDraftLoading.value = true;
|
||||
@ -1936,21 +1890,21 @@ const handleStep2Next = async () => {
|
||||
const generateQRCode = async (qrcodeUrl) => {
|
||||
try {
|
||||
await nextTick();
|
||||
|
||||
|
||||
// 清理之前的二维码
|
||||
if (qrcodeInstance) {
|
||||
qrcodeInstance.clear();
|
||||
qrcodeInstance = null;
|
||||
}
|
||||
|
||||
|
||||
if (!qrcodeRef.value) {
|
||||
console.error('二维码容器未找到');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 清空容器
|
||||
qrcodeRef.value.innerHTML = '';
|
||||
|
||||
|
||||
// 创建新的二维码
|
||||
qrcodeInstance = new QRCode(qrcodeRef.value, {
|
||||
text: qrcodeUrl,
|
||||
@ -1983,7 +1937,7 @@ onUnmounted(() => {
|
||||
const startPolling = (orderNo) => {
|
||||
polling.value = true;
|
||||
pollingStatus.value = '等待支付...';
|
||||
|
||||
|
||||
pollingTimer = setInterval(async () => {
|
||||
try {
|
||||
const { succeeded, data } = await usereventregistrationOrderStatusGet(orderNo);
|
||||
@ -2103,15 +2057,15 @@ const handleCreateOrderAndPay = async () => {
|
||||
|
||||
// 微信/支付宝支付,显示二维码
|
||||
paymentData.value = data;
|
||||
|
||||
|
||||
// 检查二维码是否获取成功
|
||||
if (!data.qrcodeUrl) {
|
||||
ElMessage.error('支付链接获取失败,请稍后重试');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
await generateQRCode(data.qrcodeUrl);
|
||||
|
||||
|
||||
// 开始轮询支付状态
|
||||
if (data.orderNo) {
|
||||
startPolling(data.orderNo);
|
||||
@ -2228,7 +2182,8 @@ const handleCreateOrderAndPay = async () => {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&-date, &-gender {
|
||||
&-date,
|
||||
&-gender {
|
||||
color: #888;
|
||||
font-size: 12px;
|
||||
}
|
||||
@ -2299,7 +2254,9 @@ const handleCreateOrderAndPay = async () => {
|
||||
|
||||
&-active {
|
||||
border-color: #409eff;
|
||||
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
||||
border-left: 8px solid #56b4f9;
|
||||
box-shadow: 0 4px 8px rgba(64, 158, 255, 0.35);
|
||||
// background: #f0f7ff;
|
||||
}
|
||||
|
||||
&-header {
|
||||
@ -2317,6 +2274,7 @@ const handleCreateOrderAndPay = async () => {
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
opacity: 0.8;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
@ -2369,11 +2327,11 @@ const handleCreateOrderAndPay = async () => {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
overflow: hidden;
|
||||
|
||||
|
||||
&:hover {
|
||||
border-color: #56b4f9;
|
||||
}
|
||||
|
||||
|
||||
&-active {
|
||||
border-color: #409eff;
|
||||
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user