2056 lines
62 KiB
Vue
2056 lines
62 KiB
Vue
<template>
|
|
<view class="worldcup-page">
|
|
<view class="worldcup-header" :style="{ paddingTop: statusBarHeight + 'px' }">
|
|
<view class="worldcup-header__bar">
|
|
<view class="worldcup-header__back" @tap="goBack">
|
|
<u-icon name="arrow-left" size="38" color="#ffffff"></u-icon>
|
|
</view>
|
|
<view class="worldcup-header__switch" @tap="goBack">
|
|
<text>其他赛事</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="worldcup-header__hero">
|
|
<text class="worldcup-header__watermark">2</text>
|
|
<view class="worldcup-header__badge">
|
|
<text class="worldcup-header__badge-cup">🏆</text>
|
|
<text class="worldcup-header__badge-label">FIFA</text>
|
|
</view>
|
|
<text class="worldcup-header__title">2026年美加墨世界杯</text>
|
|
</view>
|
|
|
|
<scroll-view scroll-x class="worldcup-nav" show-scrollbar="false">
|
|
<view class="worldcup-nav__inner">
|
|
<view v-for="tab in tabs" :key="tab.key" class="worldcup-nav__item"
|
|
:class="{ 'worldcup-nav__item--active': activeTab === tab.key }" @tap="switchTab(tab.key)">
|
|
<text>{{ tab.label }}</text>
|
|
</view>
|
|
</view>
|
|
</scroll-view>
|
|
</view>
|
|
|
|
<scroll-view scroll-y scroll-with-animation class="worldcup-scroll"
|
|
:scroll-into-view="activeTab === 'schedule' ? scheduleScrollIntoView : ''">
|
|
<block v-if="activeTab === 'live'">
|
|
<view v-if="liveCards.length" class="worldcup-live-grid">
|
|
<view v-for="live in liveCards" :key="`live-${live.live_id}`" class="worldcup-live-card"
|
|
@tap="openWorldCupLiveCard(live)">
|
|
<view class="worldcup-live-card__player-wrap"
|
|
:class="{ 'worldcup-live-card__player-wrap--poster': useLiveCardPosterMode }">
|
|
<video v-if="!useLiveCardPosterMode" :id="buildWorldCupLiveVideoId(live)"
|
|
class="worldcup-live-card__player" :src="resolveWorldCupLivePlayUrl(live)"
|
|
:poster="live.home_icon || live.away_icon || ''" :autoplay="true" :muted="true"
|
|
:controls="!hideLiveCardControlsOnAndroid"
|
|
:show-play-btn="!hideLiveCardControlsOnAndroid"
|
|
:show-fullscreen-btn="!hideLiveCardControlsOnAndroid" object-fit="cover" />
|
|
<view v-else class="worldcup-live-card__poster">
|
|
<image v-if="resolveWorldCupLivePoster(live)" class="worldcup-live-card__poster-image"
|
|
:src="resolveWorldCupLivePoster(live)" mode="aspectFill" />
|
|
<view v-else class="worldcup-live-card__poster-empty">
|
|
<text>{{ livePosterTitle(live) }}</text>
|
|
</view>
|
|
<view class="worldcup-live-card__poster-shade"></view>
|
|
<view class="worldcup-live-card__play">
|
|
<view class="worldcup-live-card__play-icon"></view>
|
|
</view>
|
|
<text class="worldcup-live-card__status">{{ liveStatusText(live) }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="worldcup-live-card__teams">
|
|
<text class="worldcup-live-card__line">{{ liveLineTitle(live) }}</text>
|
|
</view>
|
|
|
|
<view class="worldcup-live-card__meta">
|
|
<view class="worldcup-live-card__anchor">
|
|
<image v-if="liveAnchorAvatar(live)" class="worldcup-live-card__avatar"
|
|
:src="liveAnchorAvatar(live)" mode="aspectFill" />
|
|
<view v-else class="worldcup-live-card__avatar worldcup-live-card__avatar--empty">
|
|
<text>主</text>
|
|
</view>
|
|
<text class="worldcup-live-card__anchor-name">{{ liveAnchorName(live) }}</text>
|
|
</view>
|
|
<view class="worldcup-live-card__stats">
|
|
<text class="worldcup-live-card__stat">观看 {{ formatLiveCount(live.room_view_count)
|
|
}}</text>
|
|
<text class="worldcup-live-card__stat">关注 {{ formatLiveCount(live.room_focus_count)
|
|
}}</text>
|
|
</view>
|
|
<text class="worldcup-live-card__notice">{{ liveRoomNotice(live) }}</text>
|
|
</view>
|
|
|
|
</view>
|
|
</view>
|
|
<view v-else class="worldcup-empty">
|
|
<u-empty text="暂无直播" mode="data" />
|
|
</view>
|
|
</block>
|
|
|
|
<block v-else-if="activeTab === 'schedule'">
|
|
<view class="worldcup-sortbar" @tap="toggleScheduleSort">
|
|
<text class="worldcup-sortbar__label">{{ scheduleSortLabel }}</text>
|
|
<text class="worldcup-sortbar__icon">↕</text>
|
|
</view>
|
|
|
|
<block v-if="scheduleGroups.length">
|
|
<view v-for="group in scheduleGroups" :key="group.key" :id="scheduleGroupAnchorId(group.key)"
|
|
class="worldcup-group">
|
|
<view class="worldcup-group__title">
|
|
<view class="worldcup-group__dot"></view>
|
|
<text>{{ group.label }}</text>
|
|
</view>
|
|
|
|
<view v-for="(match, index) in group.items" :key="match.id || match.match_id"
|
|
class="wc-match-card" @tap="goMatchDetail(match)">
|
|
<view class="wc-match-card__team wc-match-card__team--left">
|
|
<image v-if="match.home_icon" class="wc-match-card__logo" :src="match.home_icon"
|
|
mode="aspectFit" />
|
|
<text class="wc-match-card__team-name">{{ match.home_team }}</text>
|
|
</view>
|
|
|
|
<view class="wc-match-card__center">
|
|
<text class="wc-match-card__time">{{ matchCenterText(match) }}</text>
|
|
<text class="wc-match-card__meta">{{ matchMetaText(match, index) }}</text>
|
|
</view>
|
|
|
|
<view class="wc-match-card__team wc-match-card__team--right">
|
|
<image v-if="match.away_icon" class="wc-match-card__logo" :src="match.away_icon"
|
|
mode="aspectFit" />
|
|
<text class="wc-match-card__team-name">{{ match.away_team }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</block>
|
|
<view v-else class="worldcup-empty">
|
|
<u-empty text="暂无数据" mode="data" />
|
|
</view>
|
|
</block>
|
|
|
|
<block v-else-if="activeTab === 'odds'">
|
|
<view class="worldcup-odds-filter">
|
|
<scroll-view scroll-x class="worldcup-odds-filter__tabs" show-scrollbar="false">
|
|
<view class="worldcup-odds-filter__tabs-inner">
|
|
<view v-for="item in oddsShowTypeOptions" :key="item.key" class="worldcup-odds-filter__tab"
|
|
:class="{ 'worldcup-odds-filter__tab--active': oddsShowType === item.key }"
|
|
@tap="switchOddsShowType(item.key)">
|
|
<text>{{ item.label }}</text>
|
|
</view>
|
|
</view>
|
|
</scroll-view>
|
|
|
|
<view class="worldcup-odds-filter__search">
|
|
<u-icon name="search" size="30" color="#94a3b8"></u-icon>
|
|
<input v-model="oddsSearchText" class="worldcup-odds-filter__input" confirm-type="search"
|
|
placeholder="搜索联赛/球队" placeholder-class="worldcup-odds-filter__placeholder"
|
|
@confirm="confirmOddsSearch" />
|
|
<view v-if="oddsSearchText || oddsKeyword" class="worldcup-odds-filter__clear"
|
|
@tap="clearOddsKeyword">
|
|
<u-icon name="close" size="24" color="#94a3b8"></u-icon>
|
|
</view>
|
|
<view class="worldcup-odds-filter__button" @tap="confirmOddsSearch">
|
|
<text>筛选</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="worldcup-odds-filter__meta">
|
|
<text>{{ oddsFilterSummary }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view v-if="oddsLoading" class="worldcup-loading">
|
|
<u-loading mode="circle" />
|
|
<text>加载中...</text>
|
|
</view>
|
|
|
|
<block v-else-if="oddsList.length">
|
|
<view v-for="match in oddsList" :key="oddsMatchKey(match)" class="worldcup-odds-card"
|
|
@tap="goOddsMatchDetail(match)">
|
|
<view class="worldcup-odds-card__head">
|
|
<text class="worldcup-odds-card__league">{{ match.league_name || '赛事赔率' }}</text>
|
|
<text class="worldcup-odds-card__time">{{ oddsTimeText(match) }}</text>
|
|
</view>
|
|
|
|
<view class="worldcup-odds-card__teams">
|
|
<view class="worldcup-odds-card__team">
|
|
<image v-if="match.home_icon" class="worldcup-odds-card__logo"
|
|
:src="match.home_icon" mode="aspectFit" />
|
|
<text class="worldcup-odds-card__team-name">{{ match.home_team }}</text>
|
|
</view>
|
|
<view class="worldcup-odds-card__vs">
|
|
<text>{{ oddsCenterText(match) }}</text>
|
|
</view>
|
|
<view class="worldcup-odds-card__team worldcup-odds-card__team--right">
|
|
<image v-if="match.away_icon" class="worldcup-odds-card__logo"
|
|
:src="match.away_icon" mode="aspectFit" />
|
|
<text class="worldcup-odds-card__team-name">{{ match.away_team }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="worldcup-odds-platform" v-for="platform in match.platforms"
|
|
:key="`${oddsMatchKey(match)}-${platform.platform_key}-${platform.source_match_id}`">
|
|
<view class="worldcup-odds-platform__head">
|
|
<view class="worldcup-odds-platform__name">
|
|
<text>{{ platform.platform_name }}</text>
|
|
<text class="worldcup-odds-platform__tag">{{ platform.show_type_text }}</text>
|
|
</view>
|
|
<text class="worldcup-odds-platform__update">{{ formatOddsUpdateTime(platform.update_time)
|
|
}}</text>
|
|
</view>
|
|
|
|
<view class="worldcup-odds-market" v-for="market in platform.markets"
|
|
:key="`${platform.source_match_id}-${market.label}`">
|
|
<view class="worldcup-odds-market__title">
|
|
<text>{{ market.label }}</text>
|
|
<text v-if="market.line" class="worldcup-odds-market__line">{{ market.line }}</text>
|
|
</view>
|
|
<view class="worldcup-odds-market__values">
|
|
<view class="worldcup-odds-value" v-for="item in market.values"
|
|
:key="`${market.label}-${item.label}`">
|
|
<text class="worldcup-odds-value__label">{{ item.label }}</text>
|
|
<text class="worldcup-odds-value__number">{{ item.value }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</block>
|
|
|
|
<view v-else class="worldcup-empty">
|
|
<u-empty text="暂无赔率" mode="data" />
|
|
</view>
|
|
</block>
|
|
|
|
<view v-else-if="activeTab === 'rank'" class="worldcup-section">
|
|
<ranking-nav v-model="rankingMode" />
|
|
|
|
<view v-if="rankingMode === 'standings'">
|
|
<standings-panel :groups="standingData.groups" />
|
|
</view>
|
|
|
|
<view v-else>
|
|
<player-ranking-panel :title="currentRankingLabel" :header="currentRankingHeader"
|
|
:list="currentRankingList" />
|
|
</view>
|
|
</view>
|
|
|
|
<view v-else-if="activeTab === 'bracket'" class="worldcup-section">
|
|
<scroll-view scroll-x class="bracket-round-nav" show-scrollbar="false">
|
|
<view class="bracket-round-nav__inner">
|
|
<view v-for="tab in bracketTabs" :key="tab.key" class="bracket-round-nav__item"
|
|
:class="{ 'bracket-round-nav__item--active': activeBracketTab === tab.key }"
|
|
@tap="switchBracketTab(tab.key)">
|
|
<text>{{ tab.label }}</text>
|
|
</view>
|
|
</view>
|
|
</scroll-view>
|
|
|
|
<view v-if="activeBracketRound || activeBracketTab === '决赛'" class="bracket-stage">
|
|
<view v-if="activeBracketRound?.matches?.length" class="bracket-stage__list">
|
|
<view v-for="(pair, pairIndex) in activeBracketPairs" :key="`pair-${pairIndex}`"
|
|
class="bracket-stage__pair">
|
|
<view class="bracket-stage__cards">
|
|
<view v-for="match in pair" :key="match.match_id" class="bracket-stage__item">
|
|
<view class="bracket-stage__card" @tap="goMatchDetail(match)">
|
|
<view class="bracket-stage__time">
|
|
<text class="bracket-stage__clock">{{ formatMatchTime(match.match_time)
|
|
}}</text>
|
|
<text class="bracket-stage__date">{{ formatBracketDate(match.match_time)
|
|
}}</text>
|
|
</view>
|
|
<view class="bracket-stage__divider"></view>
|
|
<view class="bracket-stage__teams">
|
|
<view class="bracket-stage__team">
|
|
<image v-if="resolveBracketTeamIcon(match.home_team, match.home_icon)"
|
|
class="bracket-stage__logo"
|
|
:src="resolveBracketTeamIcon(match.home_team, match.home_icon)"
|
|
mode="aspectFit" />
|
|
<text class="bracket-stage__name"
|
|
:class="{ 'bracket-stage__name--multi': isBracketTeamGroup(match.home_team) }">{{
|
|
resolveBracketTeamName(match.home_team) }}</text>
|
|
</view>
|
|
<view class="bracket-stage__team">
|
|
<image v-if="resolveBracketTeamIcon(match.away_team, match.away_icon)"
|
|
class="bracket-stage__logo"
|
|
:src="resolveBracketTeamIcon(match.away_team, match.away_icon)"
|
|
mode="aspectFit" />
|
|
<text class="bracket-stage__name"
|
|
:class="{ 'bracket-stage__name--multi': isBracketTeamGroup(match.away_team) }">{{
|
|
resolveBracketTeamName(match.away_team) }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<view v-if="activeBracketTab !== '决赛'" class="bracket-stage__linezone"></view>
|
|
</view>
|
|
</view>
|
|
|
|
<view v-if="activeBracketTab === '决赛' && thirdPlaceRound?.matches?.length" class="bracket-extra">
|
|
<text class="bracket-extra__title">三四名决赛</text>
|
|
<view class="bracket-stage__list bracket-stage__list--final">
|
|
<view v-for="match in thirdPlaceRound.matches" :key="match.match_id"
|
|
class="bracket-stage__item">
|
|
<view class="bracket-stage__card" @tap="goMatchDetail(match)">
|
|
<view class="bracket-stage__time">
|
|
<text class="bracket-stage__clock">{{ formatMatchTime(match.match_time)
|
|
}}</text>
|
|
<text class="bracket-stage__date">{{ formatBracketDate(match.match_time)
|
|
}}</text>
|
|
</view>
|
|
<view class="bracket-stage__divider"></view>
|
|
<view class="bracket-stage__teams">
|
|
<view class="bracket-stage__team">
|
|
<image v-if="resolveBracketTeamIcon(match.home_team, match.home_icon)"
|
|
class="bracket-stage__logo"
|
|
:src="resolveBracketTeamIcon(match.home_team, match.home_icon)"
|
|
mode="aspectFit" />
|
|
<text class="bracket-stage__name"
|
|
:class="{ 'bracket-stage__name--multi': isBracketTeamGroup(match.home_team) }">{{
|
|
resolveBracketTeamName(match.home_team) }}</text>
|
|
</view>
|
|
<view class="bracket-stage__team">
|
|
<image v-if="resolveBracketTeamIcon(match.away_team, match.away_icon)"
|
|
class="bracket-stage__logo"
|
|
:src="resolveBracketTeamIcon(match.away_team, match.away_icon)"
|
|
mode="aspectFit" />
|
|
<text class="bracket-stage__name"
|
|
:class="{ 'bracket-stage__name--multi': isBracketTeamGroup(match.away_team) }">{{
|
|
resolveBracketTeamName(match.away_team) }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<view v-else class="worldcup-empty">
|
|
<u-empty text="暂无数据" mode="data" />
|
|
</view>
|
|
</view>
|
|
|
|
<view v-else class="worldcup-section">
|
|
<view v-if="newsLoading" class="worldcup-loading">
|
|
<u-loading mode="circle" />
|
|
<text>加载中...</text>
|
|
</view>
|
|
<view v-else-if="newsList.length">
|
|
<TranslatedArticleCard v-for="item in newsList" :key="item.id" :item="item" variant="worldcup"
|
|
author-fallback="Sport Era Editorial" />
|
|
</view>
|
|
<view v-else class="worldcup-empty">
|
|
<u-empty text="暂无数据" mode="data" />
|
|
</view>
|
|
</view>
|
|
</scroll-view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
|
import RankingNav from '../pages/worldcup/components/ranking/RankingNav.vue'
|
|
import StandingsPanel from '../pages/worldcup/components/ranking/StandingsPanel.vue'
|
|
import PlayerRankingPanel from '../pages/worldcup/components/ranking/PlayerRankingPanel.vue'
|
|
import TranslatedArticleCard from '@/components/translated-article-card/translated-article-card.vue'
|
|
import { getArticleCate, getArticleList } from '@/api/news'
|
|
import {
|
|
getWorldCupLiveCards,
|
|
getWorldCupOdds,
|
|
getWorldCupRankings,
|
|
getWorldCupSchedule,
|
|
getWorldCupStandings,
|
|
type WorldCupLiveCardItem,
|
|
type WorldCupOddsMatchItem,
|
|
} from '@/api/match'
|
|
import { getLocalMatchDate } from '@/utils/match-time'
|
|
|
|
type MainTab = 'live' | 'schedule' | 'odds' | 'rank' | 'bracket' | 'news'
|
|
type RankingMode = 'standings' | 'goals' | 'assists'
|
|
type ScheduleSort = 'time' | 'group'
|
|
type BracketTabKey = '1/16决赛' | '1/8决赛' | '1/4决赛' | '半决赛' | '决赛'
|
|
type OddsShowType = '' | 'live' | 'today' | 'early'
|
|
|
|
const props = withDefaults(defineProps<{
|
|
initialTab?: string
|
|
initialCid?: number | null
|
|
}>(), {
|
|
initialTab: 'live',
|
|
initialCid: null
|
|
})
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'back'): void
|
|
}>()
|
|
|
|
const tabs: { key: MainTab; label: string }[] = [
|
|
{ key: 'live', label: '直播' },
|
|
{ key: 'schedule', label: '比分和赛程' },
|
|
{ key: 'odds', label: '赔率' },
|
|
{ key: 'rank', label: '排名' },
|
|
{ key: 'bracket', label: '对阵图' },
|
|
{ key: 'news', label: '新闻' }
|
|
]
|
|
|
|
const oddsShowTypeOptions: { key: OddsShowType; label: string }[] = [
|
|
{ key: '', label: '全部盘口' },
|
|
{ key: 'live', label: '滚球盘' },
|
|
{ key: 'today', label: '今日盘' },
|
|
{ key: 'early', label: '早盘' }
|
|
]
|
|
|
|
const WORLD_CUP_MATCH_CONTEXT = { competition_id: 61, league_name: '世界杯' }
|
|
|
|
const bracketTabs: { key: BracketTabKey; label: string }[] = [
|
|
{ key: '1/16决赛', label: '32强' },
|
|
{ key: '1/8决赛', label: '十六强' },
|
|
{ key: '1/4决赛', label: '四分之一决赛' },
|
|
{ key: '半决赛', label: '半决赛' },
|
|
{ key: '决赛', label: '决赛' }
|
|
]
|
|
|
|
const activeTab = ref<MainTab>('live')
|
|
const rankingMode = ref<RankingMode>('standings')
|
|
const scheduleSort = ref<ScheduleSort>('time')
|
|
const activeBracketTab = ref<BracketTabKey>(bracketTabs[0].key)
|
|
const scheduleScrollIntoView = ref('')
|
|
const statusBarHeight = ref(0)
|
|
const hideLiveCardControlsOnAndroid = ref(false)
|
|
const useLiveCardPosterMode = ref(false)
|
|
const liveCards = ref<WorldCupLiveCardItem[]>([])
|
|
const liveCardsLoaded = ref(false)
|
|
const oddsList = ref<WorldCupOddsMatchItem[]>([])
|
|
const oddsLoading = ref(false)
|
|
const oddsLoaded = ref(false)
|
|
const oddsShowType = ref<OddsShowType>('')
|
|
const oddsKeyword = ref('')
|
|
const oddsSearchText = ref('')
|
|
const oddsRequestSeq = ref(0)
|
|
|
|
const scheduleData = ref<any>({
|
|
current_round_name: '',
|
|
bracket_rounds: [],
|
|
schedule_rounds: []
|
|
})
|
|
|
|
const standingData = ref<any>({
|
|
stage_name: '',
|
|
groups: []
|
|
})
|
|
|
|
const rankingData = ref<Record<RankingMode, any[]>>({
|
|
standings: [],
|
|
goals: [],
|
|
assists: []
|
|
})
|
|
|
|
const rankingLoaded = ref<Record<RankingMode, boolean>>({
|
|
standings: false,
|
|
goals: false,
|
|
assists: false
|
|
})
|
|
|
|
const newsCategoryId = ref<number | null>(null)
|
|
const newsList = ref<any[]>([])
|
|
const newsLoading = ref(false)
|
|
const newsLoaded = ref(false)
|
|
|
|
const resolveNewsCategoryId = async (cid?: string | number | null) => {
|
|
const parsedCid = Number(cid || 0)
|
|
if (parsedCid > 0) {
|
|
newsCategoryId.value = parsedCid
|
|
return newsCategoryId.value
|
|
}
|
|
|
|
if (newsCategoryId.value) return newsCategoryId.value
|
|
|
|
const cateList = await getArticleCate()
|
|
const cate = (cateList || []).find((item: any) => {
|
|
const name = item?.name || item?.label || item?.title
|
|
return String(name) === '世界杯'
|
|
})
|
|
newsCategoryId.value = cate?.id || null
|
|
return newsCategoryId.value
|
|
}
|
|
|
|
const currentRankingHeader = computed(() => {
|
|
if (rankingMode.value === 'goals') return ['球员', '球队', '进球']
|
|
if (rankingMode.value === 'assists') return ['球员', '球队', '助攻']
|
|
return ['球员', '球队', '积分']
|
|
})
|
|
|
|
const currentRankingLabel = computed(() => {
|
|
if (rankingMode.value === 'goals') return '射手排名'
|
|
if (rankingMode.value === 'assists') return '助攻排名'
|
|
return '积分排名'
|
|
})
|
|
|
|
const currentRankingList = computed(() => {
|
|
if (rankingMode.value === 'goals') return rankingData.value.goals
|
|
if (rankingMode.value === 'assists') return rankingData.value.assists
|
|
return rankingData.value.standings
|
|
})
|
|
|
|
const oddsFilterSummary = computed(() => {
|
|
const showTypeLabel = oddsShowTypeOptions.find((item) => item.key === oddsShowType.value)?.label || '全部盘口'
|
|
const keyword = oddsKeyword.value.trim()
|
|
const parts = [showTypeLabel]
|
|
if (keyword) parts.push(`"${keyword}"`)
|
|
parts.push(`${oddsList.value.length}场`)
|
|
return parts.join(' · ')
|
|
})
|
|
|
|
const groupRankTeamMap = computed(() => {
|
|
const map = new Map<string, { name: string; logo: string }>()
|
|
; (standingData.value.groups || []).forEach((group: any) => {
|
|
const groupKey = String(group?.group_name || '').match(/([A-L])/i)?.[1]?.toUpperCase()
|
|
if (!groupKey) return
|
|
|
|
; (group?.teams || []).forEach((team: any, index: number) => {
|
|
const rank = Number(team?.rank || team?.row_order || index + 1)
|
|
if (!rank) return
|
|
map.set(`${groupKey}${rank}`, {
|
|
name: team?.team_name || '',
|
|
logo: team?.team_logo || ''
|
|
})
|
|
})
|
|
})
|
|
return map
|
|
})
|
|
|
|
const canResolveBracketTeams = computed(() => {
|
|
const currentRoundName = String(scheduleData.value.current_round_name || '')
|
|
if (currentRoundName && !currentRoundName.includes('小组赛')) {
|
|
return true
|
|
}
|
|
|
|
const groups = standingData.value.groups || []
|
|
if (!groups.length) return false
|
|
|
|
return groups.every((group: any) =>
|
|
(group?.teams || []).every((team: any) => Number(team?.played || 0) >= 3)
|
|
)
|
|
})
|
|
|
|
const bracketRankTokenPattern = /^[A-L](?:[1-4])$/i
|
|
const bracketRankGroupPattern = /^[A-L](?:[1-4])(?:\/[A-L](?:[1-4]))+$/i
|
|
|
|
const resolveBracketRankEntry = (rawValue: any) => {
|
|
const value = String(rawValue || '').trim().toUpperCase()
|
|
if (!canResolveBracketTeams.value) return null
|
|
if (!value || !bracketRankTokenPattern.test(value)) return null
|
|
return groupRankTeamMap.value.get(value) || null
|
|
}
|
|
|
|
const isBracketTeamGroup = (rawValue: any) => {
|
|
return bracketRankGroupPattern.test(String(rawValue || '').trim().toUpperCase())
|
|
}
|
|
|
|
const resolveBracketTeamName = (rawValue: any) => {
|
|
const raw = String(rawValue || '').trim()
|
|
if (!raw) return ''
|
|
|
|
const exact = resolveBracketRankEntry(raw)
|
|
if (exact?.name) return exact.name
|
|
|
|
if (isBracketTeamGroup(raw)) {
|
|
return raw
|
|
.split('/')
|
|
.map((token) => resolveBracketRankEntry(token)?.name || token.trim())
|
|
.join('/')
|
|
}
|
|
|
|
return raw
|
|
}
|
|
|
|
const resolveBracketTeamIcon = (rawValue: any, fallback = '') => {
|
|
const raw = String(rawValue || '').trim()
|
|
const exact = resolveBracketRankEntry(rawValue)
|
|
if (exact?.logo) return exact.logo
|
|
if (bracketRankTokenPattern.test(raw) || isBracketTeamGroup(raw) || /^第\d+场/.test(raw)) return ''
|
|
return fallback
|
|
}
|
|
|
|
const normalizeLiveText = (value: unknown) => String(value || '').trim()
|
|
|
|
const liveLineTitle = (live: WorldCupLiveCardItem) => {
|
|
return normalizeLiveText(live.title) || '直播线路'
|
|
}
|
|
|
|
const liveAnchorAvatar = (live: WorldCupLiveCardItem) => {
|
|
return normalizeLiveText(live.anchor_avatar)
|
|
}
|
|
|
|
const liveAnchorName = (live: WorldCupLiveCardItem) => {
|
|
return normalizeLiveText(live.anchor_name) || '主播'
|
|
}
|
|
|
|
const liveRoomNotice = (live: WorldCupLiveCardItem) => {
|
|
return normalizeLiveText(live.room_notice) || '暂无公告'
|
|
}
|
|
|
|
const resolveWorldCupLivePoster = (live: WorldCupLiveCardItem) => {
|
|
return liveAnchorAvatar(live) || normalizeLiveText(live.home_icon) || normalizeLiveText(live.away_icon)
|
|
}
|
|
|
|
const livePosterTitle = (live: WorldCupLiveCardItem) => {
|
|
const teams = [normalizeLiveText(live.home_team), normalizeLiveText(live.away_team)].filter(Boolean)
|
|
return teams.length ? teams.join(' VS ') : liveLineTitle(live)
|
|
}
|
|
|
|
const formatLiveCount = (value: unknown) => {
|
|
const count = Number(value || 0)
|
|
if (!Number.isFinite(count) || count <= 0) return '0'
|
|
if (count >= 10000) {
|
|
return `${(count / 10000).toFixed(count >= 100000 ? 0 : 1).replace(/\.0$/, '')}万`
|
|
}
|
|
return String(Math.floor(count))
|
|
}
|
|
|
|
const buildWorldCupLiveVideoId = (live: WorldCupLiveCardItem) => {
|
|
return `worldcup-live-${live.live_id || live.id || 0}`
|
|
}
|
|
|
|
const resolveWorldCupLivePlayUrl = (live: WorldCupLiveCardItem) => {
|
|
const urls: string[] = [
|
|
live.play_url,
|
|
live.default_play_url,
|
|
].map(normalizeLiveText).filter(Boolean)
|
|
|
|
if (Array.isArray(live.stream_options)) {
|
|
live.stream_options.forEach((option) => {
|
|
urls.push(
|
|
normalizeLiveText(option?.url),
|
|
normalizeLiveText(option?.play_url),
|
|
normalizeLiveText(option?.play_url_m3u8),
|
|
normalizeLiveText(option?.play_url_hd_m3u8),
|
|
normalizeLiveText(option?.play_url_flv),
|
|
normalizeLiveText(option?.play_url_hd_flv)
|
|
)
|
|
})
|
|
}
|
|
|
|
return urls.find((url) => /\.(m3u8|mp4|flv)(\?|$)/i.test(url)) || urls[0] || ''
|
|
}
|
|
|
|
const resolveWorldCupLiveSourceUrl = (live: WorldCupLiveCardItem) => {
|
|
return normalizeLiveText(live.source_url) || resolveWorldCupLivePlayUrl(live)
|
|
}
|
|
|
|
const liveStatusText = (live: WorldCupLiveCardItem) => {
|
|
if ((live.status ?? 0) === 1) {
|
|
return normalizeLiveText(live.current_minute) || '进行中'
|
|
}
|
|
return '未开赛'
|
|
}
|
|
|
|
const flattenScheduleMatches = computed(() => {
|
|
const matches: any[] = []
|
|
; (scheduleData.value.schedule_rounds || []).forEach((round: any) => {
|
|
; (round.matches || []).forEach((match: any, index: number) => {
|
|
matches.push({
|
|
...match,
|
|
round_name: round.round_name,
|
|
round_index: index + 1
|
|
})
|
|
})
|
|
})
|
|
return matches.sort((a, b) => (a.match_time || 0) - (b.match_time || 0))
|
|
})
|
|
|
|
const scheduleGroups = computed(() => {
|
|
const groups: Array<{ key: string; label: string; items: any[] }> = []
|
|
|
|
if (scheduleSort.value === 'time') {
|
|
const map = new Map<string, any[]>()
|
|
flattenScheduleMatches.value.forEach((match) => {
|
|
const key = formatDateKey(match.match_time)
|
|
if (!map.has(key)) map.set(key, [])
|
|
map.get(key)!.push(match)
|
|
})
|
|
map.forEach((items, key) => {
|
|
groups.push({
|
|
key,
|
|
label: items[0] ? formatDateLabel(items[0].match_time) : '未分组',
|
|
items
|
|
})
|
|
})
|
|
} else {
|
|
const map = new Map<string, any[]>()
|
|
flattenScheduleMatches.value.forEach((match) => {
|
|
const key = normalizeGroupName(match.stage || match.round_name)
|
|
if (!map.has(key)) map.set(key, [])
|
|
map.get(key)!.push(match)
|
|
})
|
|
map.forEach((items, key) => {
|
|
groups.push({ key, label: key, items })
|
|
})
|
|
}
|
|
|
|
return groups
|
|
})
|
|
|
|
const scheduleSortLabel = computed(() => scheduleSort.value === 'time' ? '时间排序' : '小组排序')
|
|
|
|
const activeBracketRound = computed(() => {
|
|
return (scheduleData.value.bracket_rounds || []).find((item: any) => item.round_name === activeBracketTab.value) || null
|
|
})
|
|
|
|
const thirdPlaceRound = computed(() => {
|
|
return (scheduleData.value.bracket_rounds || []).find((item: any) => item.round_name === '季军赛') || null
|
|
})
|
|
|
|
const activeBracketPairs = computed(() => {
|
|
const matches = activeBracketRound.value?.matches || []
|
|
const pairs: any[][] = []
|
|
for (let i = 0; i < matches.length; i += 2) {
|
|
pairs.push(matches.slice(i, i + 2))
|
|
}
|
|
return pairs
|
|
})
|
|
|
|
const initLayout = () => {
|
|
const info = uni.getSystemInfoSync()
|
|
const isAndroidPlatform = String(info.platform || '').toLowerCase() === 'android'
|
|
hideLiveCardControlsOnAndroid.value = isAndroidPlatform
|
|
useLiveCardPosterMode.value = false
|
|
// #ifdef APP-PLUS
|
|
useLiveCardPosterMode.value = isAndroidPlatform
|
|
// #endif
|
|
// #ifdef APP-PLUS || MP
|
|
statusBarHeight.value = info.statusBarHeight || 44
|
|
// #endif
|
|
// #ifdef H5
|
|
statusBarHeight.value = 0
|
|
// #endif
|
|
}
|
|
|
|
const scheduleGroupAnchorId = (key: string) => `worldcup-schedule-group-${key}`
|
|
|
|
const fetchWorldCupLiveCards = async () => {
|
|
if (liveCardsLoaded.value) return
|
|
const data = await getWorldCupLiveCards()
|
|
liveCards.value = Array.isArray(data?.list) ? data.list : []
|
|
liveCardsLoaded.value = true
|
|
}
|
|
|
|
const fetchSchedule = async () => {
|
|
scheduleData.value = await getWorldCupSchedule() || { current_round_name: '', bracket_rounds: [], schedule_rounds: [] }
|
|
if (activeTab.value === 'schedule') {
|
|
void scrollScheduleToToday()
|
|
}
|
|
}
|
|
|
|
const buildOddsRequestParams = () => {
|
|
const params: {
|
|
show_type?: 'live' | 'today' | 'early'
|
|
keyword?: string
|
|
include_special: number
|
|
limit: number
|
|
} = {
|
|
include_special: 0,
|
|
limit: 300
|
|
}
|
|
if (oddsShowType.value) {
|
|
params.show_type = oddsShowType.value
|
|
}
|
|
const keyword = oddsKeyword.value.trim()
|
|
if (keyword) {
|
|
params.keyword = keyword
|
|
}
|
|
return params
|
|
}
|
|
|
|
const fetchOdds = async (force = false) => {
|
|
if (!force && (oddsLoaded.value || oddsLoading.value)) return
|
|
const requestSeq = oddsRequestSeq.value + 1
|
|
oddsRequestSeq.value = requestSeq
|
|
oddsLoading.value = true
|
|
try {
|
|
const data = await getWorldCupOdds(buildOddsRequestParams())
|
|
if (requestSeq === oddsRequestSeq.value) {
|
|
oddsList.value = Array.isArray(data?.list) ? data.list : []
|
|
oddsLoaded.value = true
|
|
}
|
|
} finally {
|
|
if (requestSeq === oddsRequestSeq.value) {
|
|
oddsLoading.value = false
|
|
}
|
|
}
|
|
}
|
|
|
|
const refreshOdds = async () => {
|
|
oddsLoaded.value = false
|
|
await fetchOdds(true)
|
|
}
|
|
|
|
const fetchStandings = async () => {
|
|
standingData.value = await getWorldCupStandings() || { stage_name: '', groups: [] }
|
|
}
|
|
|
|
const fetchRanking = async (mode: RankingMode) => {
|
|
if (rankingLoaded.value[mode]) return
|
|
if (mode === 'standings') {
|
|
rankingLoaded.value.standings = true
|
|
return
|
|
}
|
|
const data = await getWorldCupRankings({ type: mode })
|
|
rankingData.value[mode] = data?.list || []
|
|
rankingLoaded.value[mode] = true
|
|
}
|
|
|
|
const fetchNews = async () => {
|
|
if (newsLoaded.value || newsLoading.value) return
|
|
newsLoading.value = true
|
|
try {
|
|
await resolveNewsCategoryId(props.initialCid)
|
|
|
|
if (newsCategoryId.value) {
|
|
const res = await getArticleList({
|
|
cid: newsCategoryId.value,
|
|
page_no: 1,
|
|
page_size: 8
|
|
})
|
|
const list = res?.lists || []
|
|
newsList.value = list
|
|
} else {
|
|
newsList.value = []
|
|
}
|
|
} finally {
|
|
newsLoaded.value = true
|
|
newsLoading.value = false
|
|
}
|
|
}
|
|
|
|
const ensureTabData = async (tab: MainTab) => {
|
|
if (tab === 'live') {
|
|
await fetchWorldCupLiveCards()
|
|
} else if (tab === 'schedule') {
|
|
await fetchSchedule()
|
|
} else if (tab === 'odds') {
|
|
await fetchOdds()
|
|
} else if (tab === 'rank') {
|
|
if (rankingMode.value === 'standings') {
|
|
await fetchStandings()
|
|
} else {
|
|
await fetchRanking(rankingMode.value)
|
|
}
|
|
} else if (tab === 'bracket') {
|
|
await fetchSchedule()
|
|
} else if (tab === 'news') {
|
|
await fetchNews()
|
|
}
|
|
}
|
|
|
|
const switchTab = (tab: MainTab) => {
|
|
activeTab.value = tab
|
|
}
|
|
|
|
const switchOddsShowType = (showType: OddsShowType) => {
|
|
if (oddsShowType.value === showType) return
|
|
oddsShowType.value = showType
|
|
void refreshOdds()
|
|
}
|
|
|
|
const confirmOddsSearch = () => {
|
|
const keyword = oddsSearchText.value.trim()
|
|
if (oddsKeyword.value === keyword && oddsLoaded.value) return
|
|
oddsKeyword.value = keyword
|
|
void refreshOdds()
|
|
}
|
|
|
|
const clearOddsKeyword = () => {
|
|
if (!oddsSearchText.value && !oddsKeyword.value) return
|
|
oddsSearchText.value = ''
|
|
oddsKeyword.value = ''
|
|
void refreshOdds()
|
|
}
|
|
|
|
const switchBracketTab = (tab: BracketTabKey) => {
|
|
activeBracketTab.value = tab
|
|
}
|
|
|
|
const toggleScheduleSort = () => {
|
|
scheduleSort.value = scheduleSort.value === 'time' ? 'group' : 'time'
|
|
}
|
|
|
|
const goBack = () => {
|
|
emit('back')
|
|
}
|
|
|
|
const goMatchDetail = (match: any) => {
|
|
const id = match?.id || match?.match_id
|
|
if (!id) return
|
|
uni.navigateTo({ url: `/pages/match_detail/match_detail?id=${id}` })
|
|
}
|
|
|
|
const goOddsMatchDetail = (match: WorldCupOddsMatchItem) => {
|
|
if (!match?.id) return
|
|
goMatchDetail(match)
|
|
}
|
|
|
|
const openWorldCupLiveWebview = (url: string) => {
|
|
uni.navigateTo({ url: `/pages/webview/webview?url=${encodeURIComponent(url)}` })
|
|
}
|
|
|
|
const openWorldCupLiveCard = (live: WorldCupLiveCardItem) => {
|
|
const playUrl = resolveWorldCupLivePlayUrl(live)
|
|
const sourceUrl = resolveWorldCupLiveSourceUrl(live)
|
|
if (!playUrl && !sourceUrl) {
|
|
uni.showToast({ title: '暂无直播链接', icon: 'none' })
|
|
return
|
|
}
|
|
|
|
if (useLiveCardPosterMode.value) {
|
|
openWorldCupLiveWebview(sourceUrl || playUrl)
|
|
return
|
|
}
|
|
|
|
try {
|
|
const videoContext = uni.createVideoContext(buildWorldCupLiveVideoId(live))
|
|
videoContext?.requestFullScreen?.({
|
|
direction: 90,
|
|
})
|
|
videoContext?.play?.()
|
|
} catch {
|
|
uni.showToast({ title: '当前设备暂不支持全屏', icon: 'none' })
|
|
}
|
|
}
|
|
|
|
const getWorldCupDisplayDate = (timestamp: number) => {
|
|
return getLocalMatchDate(timestamp, WORLD_CUP_MATCH_CONTEXT)
|
|
}
|
|
|
|
const formatDateKeyFromDate = (date: Date | null) => {
|
|
if (!date) return '0'
|
|
const y = date.getFullYear()
|
|
const m = String(date.getMonth() + 1).padStart(2, '0')
|
|
const day = String(date.getDate()).padStart(2, '0')
|
|
return `${y}-${m}-${day}`
|
|
}
|
|
|
|
const formatDateKey = (timestamp: number) => {
|
|
if (!timestamp) return '0'
|
|
return formatDateKeyFromDate(getWorldCupDisplayDate(timestamp))
|
|
}
|
|
|
|
const formatDateLabel = (timestamp: number) => {
|
|
if (!timestamp) return '未分组'
|
|
const d = getWorldCupDisplayDate(timestamp)
|
|
if (!d) return '未分组'
|
|
const weeks = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
|
|
return `${d.getFullYear()}年${d.getMonth() + 1}月${d.getDate()}日 ${weeks[d.getDay()]}`
|
|
}
|
|
|
|
const normalizeGroupName = (value: string) => {
|
|
if (!value) return '未分组'
|
|
if (/^[A-Z]$/.test(value)) return `${value}组`
|
|
return value
|
|
}
|
|
|
|
const formatMatchTime = (timestamp: number) => {
|
|
if (!timestamp) return '--'
|
|
const date = getWorldCupDisplayDate(timestamp)
|
|
if (!date) return '--'
|
|
return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`
|
|
}
|
|
|
|
const formatBracketDate = (timestamp: number) => {
|
|
if (!timestamp) return '--'
|
|
const date = getWorldCupDisplayDate(timestamp)
|
|
if (!date) return '--'
|
|
return `${date.getMonth() + 1}月${date.getDate()}日`
|
|
}
|
|
|
|
const getTodayScheduleDateKey = () => formatDateKeyFromDate(new Date())
|
|
|
|
const resolveTodayScheduleAnchorId = () => {
|
|
const todayKey = getTodayScheduleDateKey()
|
|
if (!todayKey) return ''
|
|
|
|
if (scheduleSort.value === 'time') {
|
|
const group = scheduleGroups.value.find((item) => item.key === todayKey && item.items.length)
|
|
if (group) return scheduleGroupAnchorId(group.key)
|
|
}
|
|
|
|
const group = scheduleGroups.value.find((item) =>
|
|
item.items.some((match: any) => formatDateKey(match.match_time) === todayKey)
|
|
)
|
|
if (!group) return ''
|
|
return scheduleGroupAnchorId(group.key)
|
|
}
|
|
|
|
const scrollScheduleToToday = async () => {
|
|
if (activeTab.value !== 'schedule') return
|
|
|
|
const targetId = resolveTodayScheduleAnchorId()
|
|
if (!targetId) return
|
|
|
|
scheduleScrollIntoView.value = ''
|
|
await nextTick()
|
|
scheduleScrollIntoView.value = targetId
|
|
}
|
|
|
|
const matchCenterText = (match: any) => {
|
|
if (match.status === 1) return match.current_minute || '进行中'
|
|
if (match.status === 2) return `${match.home_score}-${match.away_score}`
|
|
if (match.status === 3) return '已推迟'
|
|
return formatMatchTime(match.match_time)
|
|
}
|
|
|
|
const matchMetaText = (match: any, index: number) => {
|
|
const parts: string[] = []
|
|
if (match.stage) parts.push(normalizeGroupName(match.stage))
|
|
if (match.round_name && match.round_name !== match.stage) parts.push(match.round_name)
|
|
if (match.round_index || index >= 0) parts.push(`第${match.round_index || index + 1}场`)
|
|
return parts.join(' · ') || '世界杯'
|
|
}
|
|
|
|
const oddsMatchKey = (match: WorldCupOddsMatchItem) => {
|
|
const sourceId = match.source_match_ids?.[0] || ''
|
|
return `${match.home_team}-${match.away_team}-${match.match_time_text || match.match_time || sourceId}`
|
|
}
|
|
|
|
const oddsCenterText = (match: WorldCupOddsMatchItem) => {
|
|
if (match.status === 1) return match.current_minute || '进行中'
|
|
if (match.status === 2) return `${match.home_score || 0}-${match.away_score || 0}`
|
|
return 'VS'
|
|
}
|
|
|
|
const oddsTimeText = (match: WorldCupOddsMatchItem) => {
|
|
if (match.match_time) {
|
|
return formatMatchTime(match.match_time)
|
|
}
|
|
return match.match_time_text || '--'
|
|
}
|
|
|
|
const formatOddsUpdateTime = (timestamp?: number) => {
|
|
const value = Number(timestamp || 0)
|
|
if (!value) return ''
|
|
const date = new Date(value * 1000)
|
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
const day = String(date.getDate()).padStart(2, '0')
|
|
const hour = String(date.getHours()).padStart(2, '0')
|
|
const minute = String(date.getMinutes()).padStart(2, '0')
|
|
return `${month}-${day} ${hour}:${minute}`
|
|
}
|
|
|
|
const initViewState = async () => {
|
|
initLayout()
|
|
|
|
const legacyTab = String(props.initialTab || 'live')
|
|
await resolveNewsCategoryId(props.initialCid)
|
|
if (legacyTab === 'live') {
|
|
activeTab.value = 'live'
|
|
} else if (legacyTab === 'schedule') {
|
|
activeTab.value = 'schedule'
|
|
} else if (legacyTab === 'odds') {
|
|
activeTab.value = 'odds'
|
|
} else if (legacyTab === 'rank' || legacyTab === 'standings') {
|
|
activeTab.value = 'rank'
|
|
rankingMode.value = 'standings'
|
|
} else if (legacyTab === 'goals') {
|
|
activeTab.value = 'rank'
|
|
rankingMode.value = 'goals'
|
|
} else if (legacyTab === 'assists') {
|
|
activeTab.value = 'rank'
|
|
rankingMode.value = 'assists'
|
|
} else if (legacyTab === 'news') {
|
|
activeTab.value = 'news'
|
|
} else {
|
|
activeTab.value = 'bracket'
|
|
}
|
|
|
|
await Promise.all([fetchSchedule(), fetchStandings()])
|
|
|
|
if (activeTab.value === 'rank' && rankingMode.value !== 'standings') {
|
|
await fetchRanking(rankingMode.value)
|
|
}
|
|
if (activeTab.value === 'odds') {
|
|
await fetchOdds()
|
|
}
|
|
if (activeTab.value === 'news') {
|
|
await fetchNews()
|
|
}
|
|
}
|
|
|
|
watch(activeTab, (tab) => {
|
|
ensureTabData(tab)
|
|
}, { immediate: true })
|
|
|
|
watch(rankingMode, () => {
|
|
if (activeTab.value === 'rank') {
|
|
ensureTabData('rank')
|
|
}
|
|
})
|
|
|
|
watch(scheduleSort, () => {
|
|
if (activeTab.value === 'schedule') {
|
|
void scrollScheduleToToday()
|
|
}
|
|
})
|
|
|
|
onMounted(() => {
|
|
initViewState()
|
|
})
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.worldcup-page {
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: #fff;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.worldcup-header {
|
|
position: relative;
|
|
background: linear-gradient(180deg, #3552ff 0%, #2c48ea 100%);
|
|
color: #fff;
|
|
overflow: hidden;
|
|
flex-shrink: 0;
|
|
padding-bottom: 2px;
|
|
margin-bottom: 2px;
|
|
}
|
|
|
|
.worldcup-header__bar {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 12rpx 14rpx 0;
|
|
}
|
|
|
|
.worldcup-header__back,
|
|
.worldcup-header__switch {
|
|
width: 56rpx;
|
|
height: 56rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.worldcup-header__switch {
|
|
width: auto;
|
|
min-width: 82rpx;
|
|
padding: 0 14rpx;
|
|
border-radius: 999rpx;
|
|
background: rgba(255, 255, 255, 0.16);
|
|
font-size: 24rpx;
|
|
font-weight: 600;
|
|
color: #fff;
|
|
line-height: 1;
|
|
}
|
|
|
|
.worldcup-header__hero {
|
|
position: relative;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 0 14rpx 14rpx;
|
|
}
|
|
|
|
.worldcup-header__watermark {
|
|
position: absolute;
|
|
top: -6rpx;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
font-size: 170rpx;
|
|
font-weight: 900;
|
|
line-height: 1;
|
|
color: rgba(255, 255, 255, 0.08);
|
|
}
|
|
|
|
.worldcup-header__badge {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 86rpx;
|
|
height: 86rpx;
|
|
border-radius: 22rpx;
|
|
background: rgba(255, 255, 255, 0.15);
|
|
margin-bottom: 8rpx;
|
|
z-index: 1;
|
|
}
|
|
|
|
.worldcup-header__badge-cup {
|
|
font-size: 34rpx;
|
|
line-height: 1;
|
|
}
|
|
|
|
.worldcup-header__badge-label {
|
|
margin-top: 4rpx;
|
|
font-size: 18rpx;
|
|
font-weight: 600;
|
|
line-height: 1;
|
|
letter-spacing: 1rpx;
|
|
}
|
|
|
|
.worldcup-header__title {
|
|
position: relative;
|
|
z-index: 1;
|
|
font-size: 30rpx;
|
|
font-weight: 600;
|
|
line-height: 1.4;
|
|
text-align: center;
|
|
}
|
|
|
|
.worldcup-nav {
|
|
white-space: nowrap;
|
|
padding: 0 14rpx;
|
|
font-size: 0;
|
|
line-height: 0;
|
|
}
|
|
|
|
.worldcup-nav__inner {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 10rpx;
|
|
white-space: nowrap;
|
|
vertical-align: top;
|
|
}
|
|
|
|
.worldcup-nav__item {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 10rpx 16rpx;
|
|
border-radius: 24rpx;
|
|
font-size: 24rpx;
|
|
line-height: 1;
|
|
color: rgba(255, 255, 255, 0.9);
|
|
flex-shrink: 0;
|
|
|
|
&--active {
|
|
background: rgba(255, 255, 255, 0.22);
|
|
color: #fff;
|
|
font-weight: 600;
|
|
}
|
|
}
|
|
|
|
.worldcup-scroll {
|
|
flex: 1;
|
|
height: 0;
|
|
min-height: 0;
|
|
padding: 0 14rpx 0;
|
|
padding-bottom: constant(safe-area-inset-bottom);
|
|
padding-bottom: env(safe-area-inset-bottom);
|
|
box-sizing: border-box;
|
|
background: #fff;
|
|
}
|
|
|
|
.worldcup-section {
|
|
min-height: 100%;
|
|
}
|
|
|
|
.worldcup-sortbar {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 12rpx 34rpx;
|
|
border-radius: 0;
|
|
background: #fff;
|
|
margin: 0 -14rpx 8rpx;
|
|
}
|
|
|
|
.worldcup-sortbar__label {
|
|
font-size: 26rpx;
|
|
color: #111827;
|
|
}
|
|
|
|
.worldcup-sortbar__icon {
|
|
font-size: 30rpx;
|
|
color: #6b7280;
|
|
}
|
|
|
|
.worldcup-group {
|
|
margin-bottom: 14rpx;
|
|
}
|
|
|
|
.worldcup-group__title {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6rpx;
|
|
margin: 4rpx 0 10rpx;
|
|
font-size: 26rpx;
|
|
font-weight: 600;
|
|
color: #111827;
|
|
}
|
|
|
|
.worldcup-group__dot {
|
|
width: 12rpx;
|
|
height: 12rpx;
|
|
border-radius: 50%;
|
|
background: #185dff;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.worldcup-live-grid {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
margin: 12rpx -6rpx 0;
|
|
padding-top: 12rpx;
|
|
}
|
|
|
|
.worldcup-live-card {
|
|
position: relative;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12rpx;
|
|
width: calc(50% - 12rpx);
|
|
min-width: 0;
|
|
box-sizing: border-box;
|
|
margin: 0 6rpx 12rpx;
|
|
padding: 20rpx 18rpx;
|
|
border-radius: 22rpx;
|
|
background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
|
|
box-shadow: 0 10rpx 28rpx rgba(37, 99, 235, 0.08);
|
|
}
|
|
|
|
.worldcup-live-card__player-wrap {
|
|
position: relative;
|
|
width: 100%;
|
|
height: 210rpx;
|
|
border-radius: 18rpx;
|
|
overflow: hidden;
|
|
background: #0f172a;
|
|
}
|
|
|
|
.worldcup-live-card__player-wrap--poster {
|
|
background: linear-gradient(135deg, #0f172a 0%, #1e40af 100%);
|
|
}
|
|
|
|
.worldcup-live-card__player {
|
|
width: 100%;
|
|
height: 100%;
|
|
display: block;
|
|
}
|
|
|
|
.worldcup-live-card__poster {
|
|
position: relative;
|
|
width: 100%;
|
|
height: 100%;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.worldcup-live-card__poster-image {
|
|
width: 100%;
|
|
height: 100%;
|
|
display: block;
|
|
}
|
|
|
|
.worldcup-live-card__poster-empty {
|
|
width: 100%;
|
|
height: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 18rpx;
|
|
box-sizing: border-box;
|
|
text-align: center;
|
|
color: rgba(255, 255, 255, 0.86);
|
|
font-size: 22rpx;
|
|
font-weight: 600;
|
|
line-height: 1.35;
|
|
}
|
|
|
|
.worldcup-live-card__poster-shade {
|
|
position: absolute;
|
|
left: 0;
|
|
right: 0;
|
|
top: 0;
|
|
bottom: 0;
|
|
background: rgba(15, 23, 42, 0.28);
|
|
}
|
|
|
|
.worldcup-live-card__play {
|
|
position: absolute;
|
|
left: 50%;
|
|
top: 50%;
|
|
width: 58rpx;
|
|
height: 58rpx;
|
|
border-radius: 50%;
|
|
transform: translate(-50%, -50%);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: rgba(255, 255, 255, 0.24);
|
|
border: 2rpx solid rgba(255, 255, 255, 0.78);
|
|
}
|
|
|
|
.worldcup-live-card__play-icon {
|
|
width: 0;
|
|
height: 0;
|
|
margin-left: 4rpx;
|
|
border-top: 12rpx solid transparent;
|
|
border-bottom: 12rpx solid transparent;
|
|
border-left: 18rpx solid #ffffff;
|
|
}
|
|
|
|
.worldcup-live-card__status {
|
|
position: absolute;
|
|
right: 12rpx;
|
|
top: 12rpx;
|
|
max-width: 128rpx;
|
|
height: 34rpx;
|
|
line-height: 34rpx;
|
|
padding: 0 12rpx;
|
|
border-radius: 999rpx;
|
|
background: rgba(15, 23, 42, 0.58);
|
|
color: #ffffff;
|
|
font-size: 20rpx;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.worldcup-live-card__top {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 10rpx;
|
|
}
|
|
|
|
.worldcup-live-card__badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-width: 68rpx;
|
|
height: 36rpx;
|
|
padding: 0 12rpx;
|
|
border-radius: 999rpx;
|
|
background: rgba(53, 82, 255, 0.12);
|
|
color: #3552ff;
|
|
font-size: 22rpx;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.worldcup-live-card__count {
|
|
font-size: 22rpx;
|
|
color: #64748b;
|
|
}
|
|
|
|
.worldcup-live-card__meta {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
gap: 8rpx;
|
|
min-width: 0;
|
|
font-size: 22rpx;
|
|
color: #64748b;
|
|
}
|
|
|
|
.worldcup-live-card__anchor,
|
|
.worldcup-live-card__stats {
|
|
display: flex;
|
|
align-items: center;
|
|
min-width: 0;
|
|
}
|
|
|
|
.worldcup-live-card__anchor {
|
|
gap: 8rpx;
|
|
}
|
|
|
|
.worldcup-live-card__avatar {
|
|
width: 34rpx;
|
|
height: 34rpx;
|
|
border-radius: 50%;
|
|
flex-shrink: 0;
|
|
background: #e2e8f0;
|
|
}
|
|
|
|
.worldcup-live-card__avatar--empty {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: #64748b;
|
|
font-size: 18rpx;
|
|
}
|
|
|
|
.worldcup-live-card__anchor-name {
|
|
flex: 1;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
color: #334155;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.worldcup-live-card__stats {
|
|
flex-wrap: wrap;
|
|
gap: 8rpx 12rpx;
|
|
}
|
|
|
|
.worldcup-live-card__stat {
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.worldcup-live-card__notice {
|
|
display: block;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.worldcup-live-card__teams {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: flex-start;
|
|
min-width: 0;
|
|
}
|
|
|
|
.worldcup-live-card__line {
|
|
flex: 1;
|
|
min-width: 0;
|
|
font-size: 24rpx;
|
|
font-weight: 600;
|
|
color: #1e293b;
|
|
line-height: 1.4;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.wc-match-card {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 16rpx 12rpx;
|
|
margin-bottom: 8rpx;
|
|
border-radius: 18rpx;
|
|
background: #fff;
|
|
}
|
|
|
|
.wc-match-card__team {
|
|
flex: 1;
|
|
min-width: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8rpx;
|
|
|
|
&--right {
|
|
justify-content: flex-end;
|
|
}
|
|
}
|
|
|
|
.wc-match-card__logo {
|
|
width: 46rpx;
|
|
height: 46rpx;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.wc-match-card__team-name {
|
|
font-size: 30rpx;
|
|
font-weight: 600;
|
|
color: #111827;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.wc-match-card__center {
|
|
width: 150rpx;
|
|
flex-shrink: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 4rpx;
|
|
}
|
|
|
|
.wc-match-card__time {
|
|
font-size: 32rpx;
|
|
font-weight: 700;
|
|
color: #111827;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.wc-match-card__meta {
|
|
font-size: 20rpx;
|
|
color: #8b93a7;
|
|
line-height: 1.3;
|
|
text-align: center;
|
|
}
|
|
|
|
.worldcup-odds-filter {
|
|
padding: 10rpx 0 8rpx;
|
|
}
|
|
|
|
.worldcup-odds-filter__tabs {
|
|
white-space: nowrap;
|
|
font-size: 0;
|
|
line-height: 0;
|
|
}
|
|
|
|
.worldcup-odds-filter__tabs-inner {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 10rpx;
|
|
padding-right: 6rpx;
|
|
vertical-align: top;
|
|
}
|
|
|
|
.worldcup-odds-filter__tab {
|
|
height: 56rpx;
|
|
padding: 0 22rpx;
|
|
border-radius: 14rpx;
|
|
background: #f1f5f9;
|
|
color: #475569;
|
|
font-size: 24rpx;
|
|
font-weight: 700;
|
|
line-height: 56rpx;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.worldcup-odds-filter__tab--active {
|
|
background: #1d4ed8;
|
|
color: #ffffff;
|
|
}
|
|
|
|
.worldcup-odds-filter__search {
|
|
height: 72rpx;
|
|
margin-top: 14rpx;
|
|
padding: 0 10rpx 0 18rpx;
|
|
border: 1rpx solid #e2e8f0;
|
|
border-radius: 16rpx;
|
|
background: #ffffff;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10rpx;
|
|
box-shadow: 0 8rpx 22rpx rgba(15, 23, 42, 0.04);
|
|
}
|
|
|
|
.worldcup-odds-filter__input {
|
|
flex: 1;
|
|
min-width: 0;
|
|
height: 70rpx;
|
|
color: #111827;
|
|
font-size: 26rpx;
|
|
line-height: 70rpx;
|
|
}
|
|
|
|
.worldcup-odds-filter__placeholder {
|
|
color: #94a3b8;
|
|
font-size: 26rpx;
|
|
}
|
|
|
|
.worldcup-odds-filter__clear {
|
|
width: 44rpx;
|
|
height: 44rpx;
|
|
border-radius: 14rpx;
|
|
background: #f1f5f9;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.worldcup-odds-filter__button {
|
|
width: 92rpx;
|
|
height: 52rpx;
|
|
border-radius: 14rpx;
|
|
background: #0f172a;
|
|
color: #ffffff;
|
|
font-size: 24rpx;
|
|
font-weight: 700;
|
|
line-height: 52rpx;
|
|
text-align: center;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.worldcup-odds-filter__meta {
|
|
margin-top: 10rpx;
|
|
color: #64748b;
|
|
font-size: 22rpx;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.worldcup-odds-card {
|
|
margin: 12rpx 0;
|
|
padding: 18rpx 16rpx;
|
|
border-radius: 18rpx;
|
|
background: #fff;
|
|
box-shadow: 0 8rpx 24rpx rgba(15, 23, 42, 0.06);
|
|
}
|
|
|
|
.worldcup-odds-card__head,
|
|
.worldcup-odds-platform__head,
|
|
.worldcup-odds-platform__name,
|
|
.worldcup-odds-card__teams,
|
|
.worldcup-odds-card__team {
|
|
display: flex;
|
|
align-items: center;
|
|
min-width: 0;
|
|
}
|
|
|
|
.worldcup-odds-card__head,
|
|
.worldcup-odds-platform__head {
|
|
justify-content: space-between;
|
|
gap: 12rpx;
|
|
}
|
|
|
|
.worldcup-odds-card__league {
|
|
flex: 1;
|
|
min-width: 0;
|
|
color: #64748b;
|
|
font-size: 22rpx;
|
|
line-height: 1.35;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.worldcup-odds-card__time,
|
|
.worldcup-odds-platform__update {
|
|
flex-shrink: 0;
|
|
color: #94a3b8;
|
|
font-size: 20rpx;
|
|
line-height: 1.3;
|
|
}
|
|
|
|
.worldcup-odds-card__teams {
|
|
justify-content: space-between;
|
|
gap: 12rpx;
|
|
padding: 16rpx 0 12rpx;
|
|
}
|
|
|
|
.worldcup-odds-card__team {
|
|
flex: 1;
|
|
gap: 8rpx;
|
|
|
|
&--right {
|
|
justify-content: flex-end;
|
|
}
|
|
}
|
|
|
|
.worldcup-odds-card__logo {
|
|
width: 42rpx;
|
|
height: 42rpx;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.worldcup-odds-card__team-name {
|
|
min-width: 0;
|
|
color: #111827;
|
|
font-size: 28rpx;
|
|
font-weight: 700;
|
|
line-height: 1.3;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.worldcup-odds-card__vs {
|
|
width: 104rpx;
|
|
flex-shrink: 0;
|
|
text-align: center;
|
|
color: #2563eb;
|
|
font-size: 28rpx;
|
|
font-weight: 800;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.worldcup-odds-platform {
|
|
padding: 14rpx 0 0;
|
|
border-top: 1rpx solid #eef2f7;
|
|
|
|
& + & {
|
|
margin-top: 14rpx;
|
|
}
|
|
}
|
|
|
|
.worldcup-odds-platform__name {
|
|
gap: 8rpx;
|
|
color: #0f172a;
|
|
font-size: 24rpx;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.worldcup-odds-platform__tag {
|
|
height: 32rpx;
|
|
line-height: 32rpx;
|
|
padding: 0 10rpx;
|
|
border-radius: 999rpx;
|
|
background: #eaf1ff;
|
|
color: #2563eb;
|
|
font-size: 20rpx;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.worldcup-odds-market {
|
|
display: grid;
|
|
grid-template-columns: 104rpx minmax(0, 1fr);
|
|
align-items: stretch;
|
|
gap: 10rpx;
|
|
margin-top: 10rpx;
|
|
}
|
|
|
|
.worldcup-odds-market__title {
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
gap: 4rpx;
|
|
min-width: 0;
|
|
color: #475569;
|
|
font-size: 22rpx;
|
|
line-height: 1.25;
|
|
}
|
|
|
|
.worldcup-odds-market__line {
|
|
color: #0f172a;
|
|
font-size: 24rpx;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.worldcup-odds-market__values {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
gap: 8rpx;
|
|
min-width: 0;
|
|
}
|
|
|
|
.worldcup-odds-value {
|
|
min-width: 0;
|
|
padding: 10rpx 8rpx;
|
|
border-radius: 12rpx;
|
|
background: #f8fafc;
|
|
text-align: center;
|
|
}
|
|
|
|
.worldcup-odds-value__label,
|
|
.worldcup-odds-value__number {
|
|
display: block;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.worldcup-odds-value__label {
|
|
color: #64748b;
|
|
font-size: 20rpx;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.worldcup-odds-value__number {
|
|
margin-top: 4rpx;
|
|
color: #dc2626;
|
|
font-size: 26rpx;
|
|
font-weight: 800;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.bracket-round-nav {
|
|
margin-bottom: 10rpx;
|
|
white-space: nowrap;
|
|
font-size: 0;
|
|
line-height: 0;
|
|
}
|
|
|
|
.bracket-round-nav__inner {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 10rpx;
|
|
padding-right: 6rpx;
|
|
vertical-align: top;
|
|
}
|
|
|
|
.bracket-round-nav__item {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 8rpx 18rpx;
|
|
border-radius: 24rpx;
|
|
background: #fff;
|
|
color: #7b8190;
|
|
font-size: 24rpx;
|
|
line-height: 1;
|
|
flex-shrink: 0;
|
|
|
|
&--active {
|
|
background: #2f67ff;
|
|
color: #fff;
|
|
font-weight: 600;
|
|
}
|
|
}
|
|
|
|
.bracket-stage {
|
|
overflow: hidden;
|
|
}
|
|
|
|
.bracket-stage__list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10rpx;
|
|
}
|
|
|
|
.bracket-stage__list--final {
|
|
gap: 8rpx;
|
|
}
|
|
|
|
.bracket-stage__pair {
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 8fr) minmax(64rpx, 2fr);
|
|
align-items: stretch;
|
|
column-gap: 0;
|
|
}
|
|
|
|
.bracket-stage__cards {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6rpx;
|
|
min-width: 0;
|
|
}
|
|
|
|
.bracket-stage__linezone {
|
|
position: relative;
|
|
min-width: 64rpx;
|
|
|
|
&::before,
|
|
&::after {
|
|
content: '';
|
|
position: absolute;
|
|
border-color: #2f67ff;
|
|
border-style: solid;
|
|
}
|
|
|
|
&::before {
|
|
left: 0;
|
|
top: 70rpx;
|
|
width: 24rpx;
|
|
height: 150rpx;
|
|
border-top-width: 2rpx;
|
|
border-right-width: 2rpx;
|
|
border-bottom-width: 2rpx;
|
|
border-left-width: 0;
|
|
border-radius: 0 18rpx 18rpx 0;
|
|
}
|
|
|
|
&::after {
|
|
left: 24rpx;
|
|
top: 145rpx;
|
|
width: calc(100% - 24rpx);
|
|
height: 1px;
|
|
background: #2f67ff;
|
|
border: 0;
|
|
transform: translateY(-50%);
|
|
}
|
|
}
|
|
|
|
.bracket-stage__item {
|
|
position: relative;
|
|
}
|
|
|
|
.bracket-stage__card {
|
|
display: flex;
|
|
align-items: center;
|
|
min-height: 140rpx;
|
|
padding: 14rpx 14rpx;
|
|
border-radius: 24rpx;
|
|
background: #fff;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.bracket-stage__time {
|
|
width: 86rpx;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
gap: 8rpx;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.bracket-stage__clock {
|
|
font-size: 30rpx;
|
|
font-weight: 600;
|
|
line-height: 1;
|
|
color: #111827;
|
|
}
|
|
|
|
.bracket-stage__date {
|
|
font-size: 18rpx;
|
|
line-height: 1;
|
|
color: #8b93a7;
|
|
}
|
|
|
|
.bracket-stage__divider {
|
|
width: 1rpx;
|
|
align-self: stretch;
|
|
margin: 0 12rpx 0 10rpx;
|
|
background: #edf0f5;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.bracket-stage__teams {
|
|
flex: 1;
|
|
min-width: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
gap: 8rpx;
|
|
}
|
|
|
|
.bracket-stage__team {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10rpx;
|
|
}
|
|
|
|
.bracket-stage__logo {
|
|
width: 42rpx;
|
|
height: 22rpx;
|
|
flex-shrink: 0;
|
|
background: #f2f4f7;
|
|
border-radius: 2rpx;
|
|
}
|
|
|
|
.bracket-stage__name {
|
|
flex: 1;
|
|
min-width: 0;
|
|
font-size: 28rpx;
|
|
line-height: 1.3;
|
|
color: #111827;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.bracket-stage__name--multi {
|
|
white-space: normal;
|
|
display: -webkit-box;
|
|
-webkit-box-orient: vertical;
|
|
-webkit-line-clamp: 2;
|
|
}
|
|
|
|
.bracket-extra {
|
|
margin-top: 10rpx;
|
|
}
|
|
|
|
.bracket-extra__title {
|
|
display: block;
|
|
margin-bottom: 8rpx;
|
|
font-size: 24rpx;
|
|
font-weight: 500;
|
|
color: #111827;
|
|
}
|
|
|
|
.worldcup-empty,
|
|
.worldcup-loading {
|
|
padding: 80rpx 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.worldcup-loading {
|
|
flex-direction: column;
|
|
gap: 12rpx;
|
|
color: #8b93a7;
|
|
font-size: 24rpx;
|
|
}
|
|
</style>
|