Files
reader-star/apps/admin/src/views/user/groups/index.vue
rjl e70409082a feat: 新增测试页面并优化用户界面及API调用
- 新增测试页面 `/test`,用于展示题目答案的交互式网格和列表布局
- 为 CompetitionLayout 组件添加标题背景类型支持,优化视觉一致性
- 更新 `fetchUpdateCurrentQuestionUse` API 参数,简化调用逻辑
- 优化用户界面细节,包括图标替换、样式调整和布局改进
2026-02-27 08:37:45 +08:00

215 lines
4.7 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script lang="ts" setup>
import { onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import CompetitionLayout from '@/components/custom/user/CompetitionLayout.vue'
import { useRouterPush } from '@/hooks/common/router'
import { fetchGetGroupListByActivityID } from '@/service/api/game'
const { routerPushByKey, routerBack } = useRouterPush()
const route = useRoute()
const groups = ref<Api.Competition.ActivityTeamGroup[]>([])
const loading = ref(false)
const activityId = route.query.activityId as string
const isAllEnd = ref(false)
async function getGroups() {
if (!activityId)
return
loading.value = true
try {
const { data, error } = await fetchGetGroupListByActivityID(activityId, 0)
if (error) {
window?.$message?.error(error.message)
return
}
const list = data?.data || []
if (list && Array.isArray(list)) {
groups.value = list.map((item: any) => ({
...item,
// id: item.Id,
// name: item.Name,
icon: 'activity', // Default icon since API might not provide one
}))
}
isAllEnd.value = list.every((item: Api.Competition.ActivityTeamGroup) => item.IsEnd)
// console.log(groups.value, 'groups.value')
}
finally {
loading.value = false
}
}
const showContent = ref(false)
function handleBack() {
routerBack()
}
function handleNext() {
routerPushByKey('user')
}
function selectGroup(item: Api.Competition.ActivityTeamGroup) {
if (item.IsEnd)
return
// 选择组别逻辑
routerPushByKey('user_teams', { query: { activityId, groupsId: item.Id } })
}
onMounted(() => {
setTimeout(() => {
showContent.value = true
getGroups()
}, 100)
})
</script>
<template>
<CompetitionLayout
:show-back="true" :show-next="isAllEnd" next-btn-text="查看队伍结果" :title-bg-type="2"
title="展示组别" @back="handleBack" @next="handleNext"
>
<!-- Groups Grid -->
<div class="groups-container">
<TransitionGroup name="list-anim" tag="div" class="groups-grid">
<div
v-for="(item, index) in groups" v-show="showContent" :key="item.Id" class="group-item"
:class="{ 'is-disabled': item.IsEnd }" :style="{ '--delay': `${index * 0.05}s` }" @click="selectGroup(item)"
>
<div class="icon-wrapper">
<div class="flower-bg" />
<div class="inner-icon">
<img src="@/assets/imgs/user/star-icon.svg" alt="star" class="star-img">
</div>
</div>
<div class="group-name-tag">
<span class="sun-icon"></span>
{{ item.Name }}
</div>
</div>
</TransitionGroup>
</div>
</CompetitionLayout>
</template>
<style lang="scss" scoped>
.groups-container {
width: 100%;
padding: 0 60px;
margin-top: 60px;
flex: 1;
display: flex;
justify-content: center;
align-items: flex-start;
overflow-y: auto;
}
.groups-grid {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 20px;
width: 100%;
max-width: 1200px;
}
.group-item {
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
transition: transform 0.3s ease;
width: calc(20% - 16px);
margin: 0;
margin-bottom: 30px;
&.is-disabled {
opacity: 0.6;
cursor: not-allowed;
filter: grayscale(100%);
&:hover {
transform: none;
}
}
&:hover {
transform: translateY(-5px);
}
.icon-wrapper {
width: 100px;
height: 100px;
position: relative;
display: flex;
align-items: center;
justify-content: center;
background-size: contain;
.star-img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.group-name-tag {
margin-top: 10px;
background: #e0f2f1;
padding: 4px 16px;
border-radius: 16px;
font-size: 1rem;
color: $group-text-color;
font-weight: bold;
position: relative;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
border: 2px solid #fff;
.sun-icon {
position: absolute;
right: -8px;
top: -8px;
font-size: 1rem;
}
&::before {
content: '🌸';
position: absolute;
left: -12px;
bottom: -4px;
font-size: 0.9rem;
}
}
}
// Transitions
.fade-slide-down-enter-active,
.fade-slide-down-leave-active {
transition: all 0.8s ease;
}
.fade-slide-down-enter-from,
.fade-slide-down-leave-to {
opacity: 0;
transform: translateY(-30px);
}
.list-anim-enter-active,
.list-anim-leave-active {
transition: all 0.5s cubic-bezier(0.5, 0, 0.25, 1);
transition-delay: var(--delay);
}
.list-anim-enter-from,
.list-anim-leave-to {
opacity: 0;
transform: scale(0.5);
}
/* Responsive styles auto-handled by flex-wrap */
</style>