Files
reader-star/apps/admin/src/views/user/groups/index.vue
rjl 02b4720b34 feat(admin): 优化用户界面组件并新增自定义按钮样式
- 新增按钮背景图片资源(btn-bg.png、progress-title.png、title-bg3.png、title-bg4.png)
- 扩展 ActionButton 组件支持自定义按钮类型,新增 custom 类型用于背景图片驱动的按钮
- 添加 btnBg 和 fontSize 属性到 ActionButton,支持动态背景和字体大小配置
- 重构 ActionButton 样式结构,分离 standard-btn 和 custom-btn 两种按钮样式
- 优化按钮禁用状态的样式处理和交互反馈
- 更新 CompetitionLayout 组件支持新的标题背景类型(titleBg3、titleBg4)
- 新增 titleTextColor 和 fontSize 属性到 CompetitionLayout,增强标题和按钮的定制化能力
- 更新所有用户页面组件(cover、draw、game、groups、rules、teams)以适配新的按钮和布局配置
2026-03-17 17:51:12 +08:00

271 lines
6.6 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.

<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import groupType1 from '@/assets/imgs/user/group-type-1.svg'
import groupType2 from '@/assets/imgs/user/group-type-2.svg'
import startIcon from '@/assets/imgs/user/star-icon.webp'
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)
// 是否有加时赛
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
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,
icon: 'activity',
}))
}
isAllEnd.value = list.filter(item => item.RoundType === 0).every((item: Api.Competition.ActivityTeamGroup) => item.IsEnd)
}
finally {
loading.value = false
}
}
const showContent = ref(false)
function handleBack() {
routerBack()
}
function handleNext() {
routerPushByKey('user_rank-list', { query: { activityId, RoundType: 0 } })
}
function selectGroup(item: Api.Competition.ActivityTeamGroup) {
if (item.IsEnd)
return
// 选择组别逻辑
routerPushByKey('user_teams', { query: { activityId, groupsId: item.Id, RoundType: item.RoundType } })
}
/**
* 查看加时赛结果
*/
function handleNext2() {
routerPushByKey('user_rank-list', { query: { activityId, RoundType: 1 } }, true)
}
onMounted(() => {
setTimeout(() => {
showContent.value = true
getGroups()
}, 100)
})
</script>
<template>
<CompetitionLayout
:show-back="true" back-btn-text="返回" :show-next="isAllEnd" next-btn-text="查看常规赛结果" font-size="22" 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">
<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">
<NImage :src="startIcon" alt="star" class="star-img" width="180" height="187" :preview-disabled="true" object-fit="contain" />
</div>
</div>
<div
class="group-name-tag"
:class="index % 2 === 0 ? 'type-moon' : 'type-sun'"
:style="{ backgroundImage: `url(${index % 2 === 0 ? groupType2 : groupType1})` }"
>
{{ 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: 15px; // 减小间距
width: 100%;
max-width: 1400px; // 增加最大宽度限制,允许在一行放下更多
}
.group-item {
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
transition: transform 0.3s ease;
// 适配不同屏幕宽度
width: calc(20% - 16px); // 默认一行5个 (100% / 5 ≈ 20%)
// 当总数大于10时启用紧凑模式一行6个
.grid-dense & {
width: calc(16.66% - 16px);
}
// 大屏下如果没启用紧凑模式依然保持一行5个或者根据需要调整
// @media (min-width: 1440px) {
// width: calc(16.66% - 16px); // 大屏一行6个
// }
// 小屏适配
@media (max-width: 1200px) {
width: calc(25% - 16px); // 中屏一行4个
.grid-dense & {
width: calc(20% - 16px); // 紧凑模式下中屏一行5个
}
}
margin: 0;
margin-bottom: 20px;
&.is-disabled {
opacity: 0.6;
cursor: not-allowed;
filter: grayscale(100%);
&:hover {
transform: none;
}
}
&:hover {
transform: translateY(-5px);
}
.icon-wrapper {
// 稍微缩小图标区域以适配更密集的布局
transform: scale(0.9);
position: relative;
display: flex;
align-items: center;
justify-content: center;
background-size: contain;
margin-left: 20px;
.star-img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.group-name-tag {
margin-top: 5px; // 减小顶部间距
background-repeat: no-repeat;
background-position: center;
background-size: 100% 100%;
width: 180px; // 稍微缩小标签宽度
height: 72px; // 按比例缩小高度
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem; // 稍微缩小字号
color: #5d4037;
font-weight: bold;
position: relative;
// border: 1px solid #5d4037;
&.type-moon {
padding-left: 20px;
padding-top: 12px;
margin-top: -5px; // 调整错落位置
}
&.type-sun {
padding-left: 30px;
padding-top: -1px;
margin-top: 0px;
}
// .sun-icon {
// position: absolute;
// right: 20px;
// top: 10px;
// font-size: 1rem;
// z-index: 2;
// }
// &::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>