feat(competition): 支持决赛环节并添加加时赛管理功能
- 扩展竞赛环节类型,支持决赛环节(RoundType=3) - 新增加时赛创建功能,支持多分组队伍选择 - 添加题目库禁用/启用功能,支持批量操作 - 优化用户端竞赛流程,区分常规赛和加时赛 - 重构操作按钮组件,提高代码复用性
This commit is contained in:
@ -55,6 +55,7 @@ function handleNext() {
|
||||
groupsId: groupsId.value,
|
||||
teamId: teamId.value,
|
||||
teamIds: teamIds.value,
|
||||
RoundType: Number(currentQuestionMainInfo.value?.Activity_Question.RoundType) || 0,
|
||||
},
|
||||
})
|
||||
// 清空当前题目信息
|
||||
@ -73,7 +74,7 @@ async function initQuestions() {
|
||||
const { data: outlineData, error } = await fetchGetCurrentQuestion({
|
||||
ActivityID: Number(activityId.value),
|
||||
GroupID: Number(groupsId.value),
|
||||
RoundType: 0,
|
||||
RoundType: Number(currentQuestionMainInfo.value?.Activity_Question.RoundType) || 0,
|
||||
})
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(outlineData, 'outlineData')
|
||||
|
||||
@ -45,6 +45,8 @@ const groupsId = computed(() => Number(route.query?.groupsId)) || activityInfoSt
|
||||
const teamId = computed(() => activityInfoStore.teamId || Number(route.query?.teamId))
|
||||
const teamIds = computed(() => route.query?.teamIds as string)
|
||||
|
||||
const RoundType = route.query?.RoundType || 0
|
||||
|
||||
async function getQuestions() {
|
||||
if (!activityId.value || !groupsId.value || !teamId.value) {
|
||||
window?.$message?.error('参数错误')
|
||||
@ -53,7 +55,7 @@ async function getQuestions() {
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const { data, error } = await fetchGetQuestionTableByActivityID(String(activityId.value))
|
||||
const { data, error } = await fetchGetQuestionTableByActivityID(String(activityId.value), Number(RoundType))
|
||||
if (error) {
|
||||
window?.$message?.error(error.message)
|
||||
return
|
||||
@ -115,6 +117,7 @@ async function handleNext() {
|
||||
groupsId: groupsId.value,
|
||||
teamId: teamId.value,
|
||||
teamIds: teamIds.value,
|
||||
RoundType: Number(RoundType),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ const activityId = computed(() => activityInfoStore.activityId || route.query?.a
|
||||
const groupsId = computed(() => activityInfoStore.groupId || route.query?.groupsId as string)
|
||||
const teamId = computed(() => activityInfoStore.teamId || route.query?.teamId as string)
|
||||
const teamIds = computed(() => route.query?.teamIds as string)
|
||||
const RoundType = computed(() => Number(route.query?.RoundType || 0) as Api.Competition.CompetitionRoundType)
|
||||
|
||||
const store = useCompetitionStore()
|
||||
|
||||
@ -39,7 +40,7 @@ async function initQuestions() {
|
||||
const { data: outlineData, error } = await fetchGetCurrentQuestion({
|
||||
ActivityID: Number(activityId.value),
|
||||
GroupID: Number(groupsId.value),
|
||||
RoundType: 0,
|
||||
RoundType: RoundType.value,
|
||||
})
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(outlineData, 'outlineData')
|
||||
@ -78,6 +79,7 @@ async function handleCardClick(item: Api.Competition.CurrentQuestionResponse) {
|
||||
routerPushByKey('user_groups', {
|
||||
query: {
|
||||
activityId: activityId.value,
|
||||
RoundType: RoundType.value,
|
||||
},
|
||||
})
|
||||
return
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import CompetitionLayout from '@/components/custom/user/CompetitionLayout.vue'
|
||||
import { useRouterPush } from '@/hooks/common/router'
|
||||
@ -13,6 +13,12 @@ const loading = ref(false)
|
||||
const activityId = route.query.activityId as string
|
||||
const isAllEnd = ref(false)
|
||||
|
||||
// 是否有加时赛
|
||||
const hasExtraTime = computed(() => {
|
||||
const extraTimeGroups = groups.value.filter(item => item.RoundType === 1)
|
||||
return extraTimeGroups.length > 0 && extraTimeGroups.every(item => item.IsEnd)
|
||||
})
|
||||
|
||||
async function getGroups() {
|
||||
if (!activityId)
|
||||
return
|
||||
@ -32,7 +38,7 @@ async function getGroups() {
|
||||
icon: 'activity',
|
||||
}))
|
||||
}
|
||||
isAllEnd.value = list.every((item: Api.Competition.ActivityTeamGroup) => item.IsEnd)
|
||||
isAllEnd.value = list.filter(item => item.RoundType === 0).every((item: Api.Competition.ActivityTeamGroup) => item.IsEnd)
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
@ -46,7 +52,7 @@ function handleBack() {
|
||||
}
|
||||
|
||||
function handleNext() {
|
||||
routerPushByKey('user_rank-list', { query: { activityId } })
|
||||
routerPushByKey('user_rank-list', { query: { activityId, RoundType: 0 } })
|
||||
}
|
||||
|
||||
function selectGroup(item: Api.Competition.ActivityTeamGroup) {
|
||||
@ -54,7 +60,14 @@ function selectGroup(item: Api.Competition.ActivityTeamGroup) {
|
||||
return
|
||||
|
||||
// 选择组别逻辑
|
||||
routerPushByKey('user_teams', { query: { activityId, groupsId: item.Id } })
|
||||
routerPushByKey('user_teams', { query: { activityId, groupsId: item.Id, RoundType: item.RoundType } })
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看加时赛结果
|
||||
*/
|
||||
function handleNext2() {
|
||||
routerPushByKey('user_rank-list', { query: { activityId, RoundType: 1 } }, true)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@ -67,8 +80,9 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<CompetitionLayout
|
||||
:show-back="true" :show-next="isAllEnd" next-btn-text="查看队伍结果" :title-bg-type="2"
|
||||
title="展示组别" @back="handleBack" @next="handleNext"
|
||||
:show-back="true" :show-next="isAllEnd" next-btn-text="查看常规赛结果" next-btn-text2="查看加时赛结果"
|
||||
:show-next-btn2="hasExtraTime && isAllEnd" :title-bg-type="2" title="展示组别" @back="handleBack" @next="handleNext"
|
||||
@next2="handleNext2"
|
||||
>
|
||||
<!-- Groups Grid -->
|
||||
<div class="groups-container">
|
||||
|
||||
@ -0,0 +1,121 @@
|
||||
<script setup lang="ts">
|
||||
import { NButton, NCard, NInput, NModal, NSelect, NSpace, useMessage } from 'naive-ui'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRouterPush } from '@/hooks/common/router'
|
||||
import { fetchAddExtraTime } from '@/service/api/game'
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
activityId: number
|
||||
teamOptions: { label: string, value: number }[]
|
||||
}>()
|
||||
const emit = defineEmits(['update:show', 'submit'])
|
||||
|
||||
const { routerPushByKey } = useRouterPush()
|
||||
const message = useMessage()
|
||||
const extraTimeGroups = ref<{
|
||||
GroupName: string
|
||||
TeamInfos: number[]
|
||||
}[]>([{ GroupName: '', TeamInfos: [] }])
|
||||
|
||||
const selectedTeams = computed(() => extraTimeGroups.value.flatMap(g => g.TeamInfos))
|
||||
|
||||
function getFilteredOptions(currentGroupIndex: number) {
|
||||
const currentGroupTeams = extraTimeGroups.value[currentGroupIndex].TeamInfos
|
||||
return props.teamOptions.filter(option =>
|
||||
!selectedTeams.value.includes(option.value) || currentGroupTeams.includes(option.value),
|
||||
)
|
||||
}
|
||||
|
||||
function addGroup() {
|
||||
extraTimeGroups.value.push({ GroupName: '', TeamInfos: [] })
|
||||
}
|
||||
|
||||
function removeGroup(index: number) {
|
||||
extraTimeGroups.value.splice(index, 1)
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
const allSelectedTeams = new Set<number>()
|
||||
for (const group of extraTimeGroups.value) {
|
||||
if (!group.GroupName) {
|
||||
message.warning('请输入所有分组的名称')
|
||||
return
|
||||
}
|
||||
if (group.TeamInfos.length < 2) {
|
||||
message.warning(`分组 "${group.GroupName}" 至少需要选择两个队伍`)
|
||||
return
|
||||
}
|
||||
for (const teamId of group.TeamInfos) {
|
||||
if (allSelectedTeams.has(teamId)) {
|
||||
message.warning('同一个队伍不能出现在多个分组中')
|
||||
return
|
||||
}
|
||||
allSelectedTeams.add(teamId)
|
||||
}
|
||||
}
|
||||
|
||||
const params: Api.Competition.AddExtraTimeParams = extraTimeGroups.value.map(group => ({
|
||||
...group,
|
||||
ActivityID: props.activityId,
|
||||
TeamInfos: group.TeamInfos.map(teamId => ({
|
||||
TeamID: teamId,
|
||||
TeamName: props.teamOptions.find(opt => opt.value === teamId)?.label || '',
|
||||
})),
|
||||
}))
|
||||
|
||||
const { data, error } = await fetchAddExtraTime(params)
|
||||
if (error) {
|
||||
message.error(error.message || '创建加时赛失败')
|
||||
return
|
||||
}
|
||||
|
||||
message.success('加时赛已创建')
|
||||
emit('submit', data)
|
||||
emit('update:show', false)
|
||||
|
||||
if (data) {
|
||||
routerPushByKey('user_cover', { query: { activityId: props.activityId, groupsId: data.data, RoundType: 1 } })
|
||||
}
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
emit('update:show', false)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NModal :show="show" @update:show="handleClose">
|
||||
<NCard style="width: 600px" title="创建加时赛" :bordered="false" size="huge" role="dialog" aria-modal="true">
|
||||
<NSpace vertical>
|
||||
<div v-for="(group, index) in extraTimeGroups" :key="index" class="group-item">
|
||||
<NInput v-model:value="group.GroupName" placeholder="请输入分组名称" />
|
||||
<NSelect
|
||||
v-model:value="group.TeamInfos"
|
||||
multiple
|
||||
:options="getFilteredOptions(index)"
|
||||
placeholder="请选择队伍"
|
||||
/>
|
||||
<NButton v-if="extraTimeGroups.length > 1" text @click="() => removeGroup(index)">
|
||||
删除
|
||||
</NButton>
|
||||
</div>
|
||||
<NButton type="dashed" block @click="addGroup">
|
||||
新增分组
|
||||
</NButton>
|
||||
<NButton type="primary" block @click="handleSubmit">
|
||||
确定
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NCard>
|
||||
</NModal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.group-item {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
@ -4,11 +4,23 @@ import { NDataTable, NImage, NTag, useMessage } from 'naive-ui'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import title from '@/assets/imgs/user/rank-title.svg'
|
||||
import ActionButton from '@/components/custom/user/ActionButton.vue'
|
||||
import { fetchUserRankList } from '@/service/api/rank'
|
||||
import ExtraTimeModal from './components/ExtraTimeModal.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const message = useMessage()
|
||||
const MainID = Number(route.query.activityId)
|
||||
const RoundType = Number(route.query.RoundType) || 0
|
||||
|
||||
const hasExtraTimeBtn = RoundType === 0
|
||||
|
||||
const showExtraTimeModal = ref(false)
|
||||
const teamOptions = ref<{ label: string, value: number }[]>([])
|
||||
|
||||
function handleAddTime() {
|
||||
showExtraTimeModal.value = true
|
||||
}
|
||||
|
||||
// 定义数据结构
|
||||
interface TeamData {
|
||||
@ -46,7 +58,7 @@ const columns = ref<DataTableColumns<TeamData>>([
|
||||
const tableData = ref<TeamData[]>([])
|
||||
|
||||
async function getUserRankList() {
|
||||
const { data, error } = await fetchUserRankList(MainID)
|
||||
const { data, error } = await fetchUserRankList(MainID, RoundType)
|
||||
if (error) {
|
||||
message.error(error.message || '获取排行榜失败')
|
||||
return
|
||||
@ -101,6 +113,8 @@ async function getUserRankList() {
|
||||
...questions,
|
||||
} as TeamData
|
||||
})
|
||||
|
||||
teamOptions.value = data.data.map(item => ({ label: item.Name, value: item.TeamID }))
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,6 +143,7 @@ function rowClassName(row: TeamData) {
|
||||
<p class="mt--20px text-center text-sm text-#6b778d">
|
||||
"勤学如春起之苗,不见其增,日有所长"
|
||||
</p>
|
||||
<ActionButton v-if="hasExtraTimeBtn" class="position-absolute bottom-20px right-20px" type="text" text="加时赛" @click="handleAddTime" />
|
||||
</div>
|
||||
<div class="leaderboard-content">
|
||||
<NDataTable
|
||||
@ -137,6 +152,7 @@ function rowClassName(row: TeamData) {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ExtraTimeModal v-model:show="showExtraTimeModal" :activity-id="MainID" :team-options="teamOptions" />
|
||||
</CompetitionLayout>
|
||||
</template>
|
||||
|
||||
|
||||
@ -14,13 +14,14 @@ const activityId = computed(() => activityInfoStore.activityId || route.query?.a
|
||||
const groupsId = computed(() => activityInfoStore.groupId || route.query?.groupsId as string)
|
||||
const teamId = computed(() => activityInfoStore.teamId || route.query?.teamId as string)
|
||||
const teamIds = computed(() => route.query?.teamIds as string)
|
||||
const RoundType = computed(() => Number(route.query?.RoundType || 0))
|
||||
|
||||
const { routerPushByKey, routerBack } = useRouterPush()
|
||||
|
||||
const activityName = ref('规则介绍')
|
||||
const rulesContent = ref('')
|
||||
|
||||
const mockContent = computed(() => activityInfo.value?.ActivityContent || '')
|
||||
const mockContent = computed(() => RoundType.value === 0 ? activityInfo.value?.ActivityContent || '' : activityInfo.value?.ExtraTimeContent || '')
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
@ -40,6 +41,7 @@ function handleNext() {
|
||||
groupsId: groupsId.value,
|
||||
teamId: teamId.value,
|
||||
teamIds: teamIds.value,
|
||||
RoundType: RoundType.value,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import CompetitionLayout from '@/components/custom/user/CompetitionLayout.vue'
|
||||
import { useRouterPush } from '@/hooks/common/router'
|
||||
@ -20,6 +20,8 @@ const activityId = (route.query?.activityId) as string || ''
|
||||
const groupsId = (route.query?.groupsId) as string || ''
|
||||
const teamIds = ref<number[]>([])
|
||||
|
||||
const RoundType = computed(() => Number(route.query?.RoundType || 0))
|
||||
|
||||
async function getTeams() {
|
||||
if (!groupsId)
|
||||
return
|
||||
@ -61,7 +63,7 @@ function handleNext() {
|
||||
|
||||
function selectTeam(_id: number) {
|
||||
// 选择队伍逻辑
|
||||
routerPushByKey('user_cover', { query: { activityId, groupsId, teamId: _id, teamIds: teamIds.value.join(',') } })
|
||||
routerPushByKey('user_cover', { query: { activityId, groupsId, teamId: _id, teamIds: teamIds.value.join(','), RoundType: RoundType.value } })
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
Reference in New Issue
Block a user