注册必填 7-32 位账号,手机号区号/本地号分存;登录默认账号模式并支持切换手机号登录;Player i18n 拆包与赛事接口优化。 Co-authored-by: Cursor <cursoragent@cursor.com>
386 lines
7.9 KiB
Vue
386 lines
7.9 KiB
Vue
<script setup lang="ts">
|
|
import { useRouter } from 'vue-router';
|
|
import { useI18n } from 'vue-i18n';
|
|
import emptyMatchesImg from '../assets/images/empty-matches.svg';
|
|
import vsImg from '../assets/images/vs.png';
|
|
import cardBg from '../assets/images/card-bg.png';
|
|
import BannerCarousel from '../components/BannerCarousel.vue';
|
|
import { usePlayerHome } from '../composables/usePlayerHome';
|
|
import TeamEmblem from '../components/TeamEmblem.vue';
|
|
import GoldSpinner from '../components/GoldSpinner.vue';
|
|
import { usePullToRefresh } from '../composables/usePullToRefresh';
|
|
|
|
const matchCardBg = `url(${cardBg})`;
|
|
const { t, locale } = useI18n();
|
|
const router = useRouter();
|
|
const { banners, hotMatches, loading, load } = usePlayerHome();
|
|
|
|
const { pullDistance, refreshing, spinning, progress } = usePullToRefresh({
|
|
onRefresh: async () => { await load(true); },
|
|
});
|
|
|
|
const pullIndicatorStyle = () => ({
|
|
height: `${pullDistance.value}px`,
|
|
opacity: Math.min(pullDistance.value / 48, 1),
|
|
});
|
|
|
|
function goMatch(id: string) {
|
|
router.push(`/match/${id}`);
|
|
}
|
|
|
|
function formatKickoff(startTime: string) {
|
|
return new Date(startTime).toLocaleString(locale.value, {
|
|
year: 'numeric',
|
|
month: 'numeric',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
second: '2-digit',
|
|
});
|
|
}
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<div
|
|
class="pull-indicator"
|
|
:style="pullIndicatorStyle()"
|
|
>
|
|
<GoldSpinner v-if="spinning" :size="28" :progress="progress" :active="spinning" />
|
|
</div>
|
|
|
|
<BannerCarousel :banners="banners" />
|
|
|
|
<h2 class="section-title">{{ t('home.hot_matches') }}</h2>
|
|
<div
|
|
v-for="(match, index) in hotMatches"
|
|
:key="match.id"
|
|
class="match-card"
|
|
:class="{ 'match-card--live-anim': index < 3 }"
|
|
@click="goMatch(match.id)"
|
|
>
|
|
<div class="match-info">
|
|
<div class="match-teams">{{ match.homeTeamName }} vs {{ match.awayTeamName }}</div>
|
|
<div class="match-time">{{ formatKickoff(match.startTime) }}</div>
|
|
</div>
|
|
<div class="match-flags" aria-hidden="true">
|
|
<TeamEmblem
|
|
size="md"
|
|
:team-code="match.homeTeamCode"
|
|
:team-name="match.homeTeamName"
|
|
:logo-url="match.homeTeamLogoUrl"
|
|
/>
|
|
<div class="vs-arena">
|
|
<svg class="hz-lightning" viewBox="0 0 72 28" aria-hidden="true">
|
|
<defs>
|
|
<linearGradient :id="`hzBoltGrad-${match.id}`" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
<stop offset="0%" stop-color="#5eb8ff" stop-opacity="0.2" />
|
|
<stop offset="35%" stop-color="#b8ecff" stop-opacity="1" />
|
|
<stop offset="50%" stop-color="#ffffff" stop-opacity="1" />
|
|
<stop offset="65%" stop-color="#ffd080" stop-opacity="1" />
|
|
<stop offset="100%" stop-color="#ff9040" stop-opacity="0.2" />
|
|
</linearGradient>
|
|
</defs>
|
|
<path
|
|
class="hz-path hz-path-main"
|
|
:stroke="`url(#hzBoltGrad-${match.id})`"
|
|
d="M1 14 H16 L20 5 L24 23 L28 9 L32 14 H40 L44 6 L48 22 L52 12 L56 14 H71"
|
|
/>
|
|
<path
|
|
class="hz-path hz-path-sub"
|
|
:stroke="`url(#hzBoltGrad-${match.id})`"
|
|
d="M3 19 H14 L18 15 L22 19 H50 L54 16 L58 19 H69"
|
|
/>
|
|
</svg>
|
|
<span class="hz-beam" aria-hidden="true" />
|
|
<img :src="vsImg" alt="" class="vs-img" />
|
|
</div>
|
|
<TeamEmblem
|
|
size="md"
|
|
:team-code="match.awayTeamCode"
|
|
:team-name="match.awayTeamName"
|
|
:logo-url="match.awayTeamLogoUrl"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="!loading && !hotMatches.length" class="empty">
|
|
<img :src="emptyMatchesImg" alt="" class="empty-icon" />
|
|
<p>{{ t('home.no_matches') }}</p>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.pull-indicator {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
overflow: hidden;
|
|
transition: height 0.15s ease;
|
|
}
|
|
|
|
.match-card {
|
|
position: relative;
|
|
isolation: isolate;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
margin-bottom: 12px;
|
|
padding: 14px 16px;
|
|
min-height: 72px;
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
box-shadow: none;
|
|
background: var(--bg-card);
|
|
cursor: pointer;
|
|
transition: opacity 0.2s, transform 0.2s;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.match-card::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
background: v-bind(matchCardBg) center / 100% 100% no-repeat;
|
|
opacity: 0.25;
|
|
z-index: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.match-card:active {
|
|
opacity: 0.92;
|
|
transform: scale(0.995);
|
|
}
|
|
|
|
.match-info,
|
|
.match-flags {
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.match-info {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.match-teams {
|
|
font-weight: 800;
|
|
margin-bottom: 8px;
|
|
font-size: 16px;
|
|
line-height: 1.3;
|
|
}
|
|
|
|
.match-time {
|
|
font-size: 13px;
|
|
color: var(--text-muted);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.match-flags {
|
|
flex-shrink: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
max-width: 46%;
|
|
}
|
|
|
|
.vs-arena {
|
|
position: relative;
|
|
flex-shrink: 0;
|
|
width: 64px;
|
|
height: 52px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.hz-lightning {
|
|
position: absolute;
|
|
inset: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
z-index: 1;
|
|
pointer-events: none;
|
|
overflow: visible;
|
|
}
|
|
|
|
.hz-path {
|
|
fill: none;
|
|
stroke-width: 2.2;
|
|
stroke-linecap: round;
|
|
stroke-linejoin: round;
|
|
filter: drop-shadow(0 0 4px rgba(120, 210, 255, 0.95)) drop-shadow(0 0 8px rgba(255, 180, 80, 0.55));
|
|
opacity: 0;
|
|
}
|
|
|
|
.match-card--live-anim .hz-path-main {
|
|
animation: hz-strike-main 2.6s ease-in-out infinite;
|
|
}
|
|
|
|
.match-card--live-anim .hz-path-sub {
|
|
stroke-width: 1.6;
|
|
animation: hz-strike-sub 2.6s ease-in-out infinite;
|
|
animation-delay: 0.12s;
|
|
}
|
|
|
|
.match-card--live-anim .hz-beam {
|
|
position: absolute;
|
|
left: 0;
|
|
right: 0;
|
|
top: 50%;
|
|
height: 2px;
|
|
transform: translateY(-50%);
|
|
pointer-events: none;
|
|
z-index: 1;
|
|
background: linear-gradient(
|
|
90deg,
|
|
rgba(94, 184, 255, 0) 0%,
|
|
rgba(184, 236, 255, 0.95) 28%,
|
|
#fff 50%,
|
|
rgba(255, 208, 128, 0.95) 72%,
|
|
rgba(255, 144, 64, 0) 100%
|
|
);
|
|
opacity: 0;
|
|
filter: blur(0.4px);
|
|
animation: hz-beam-flash 2.6s ease-in-out infinite;
|
|
}
|
|
|
|
.hz-beam {
|
|
display: none;
|
|
}
|
|
|
|
.vs-img {
|
|
position: relative;
|
|
z-index: 0;
|
|
width: 48px;
|
|
height: auto;
|
|
object-fit: contain;
|
|
filter: drop-shadow(0 0 3px rgba(212, 175, 55, 0.35));
|
|
}
|
|
|
|
.match-card--live-anim .vs-img {
|
|
animation: vs-glow 2.4s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes hz-strike-main {
|
|
0%,
|
|
72%,
|
|
100% {
|
|
opacity: 0;
|
|
}
|
|
|
|
74% {
|
|
opacity: 1;
|
|
}
|
|
|
|
75% {
|
|
opacity: 0.25;
|
|
}
|
|
|
|
76% {
|
|
opacity: 0.95;
|
|
}
|
|
|
|
78% {
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
@keyframes hz-strike-sub {
|
|
0%,
|
|
74%,
|
|
100% {
|
|
opacity: 0;
|
|
}
|
|
|
|
76% {
|
|
opacity: 0.85;
|
|
}
|
|
|
|
77% {
|
|
opacity: 0.2;
|
|
}
|
|
|
|
78% {
|
|
opacity: 0.7;
|
|
}
|
|
|
|
80% {
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
@keyframes hz-beam-flash {
|
|
0%,
|
|
71%,
|
|
100% {
|
|
opacity: 0;
|
|
transform: translateY(-50%) scaleX(0.6);
|
|
}
|
|
|
|
73% {
|
|
opacity: 0.85;
|
|
transform: translateY(-50%) scaleX(1);
|
|
}
|
|
|
|
75% {
|
|
opacity: 0.15;
|
|
transform: translateY(-50%) scaleX(0.95);
|
|
}
|
|
|
|
76% {
|
|
opacity: 0.75;
|
|
transform: translateY(-50%) scaleX(1);
|
|
}
|
|
|
|
78% {
|
|
opacity: 0;
|
|
transform: translateY(-50%) scaleX(1.05);
|
|
}
|
|
}
|
|
|
|
@keyframes vs-glow {
|
|
0%,
|
|
100% {
|
|
opacity: 0.82;
|
|
filter: drop-shadow(0 0 2px rgba(212, 175, 55, 0.3));
|
|
}
|
|
|
|
50% {
|
|
opacity: 1;
|
|
filter:
|
|
drop-shadow(0 0 3px rgba(255, 230, 140, 0.7))
|
|
drop-shadow(0 0 6px rgba(212, 175, 55, 0.35));
|
|
}
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.vs-img {
|
|
animation: none;
|
|
filter: drop-shadow(0 0 3px rgba(212, 175, 55, 0.35));
|
|
}
|
|
|
|
.hz-path,
|
|
.hz-beam {
|
|
animation: none;
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
.empty {
|
|
text-align: center;
|
|
color: var(--text-muted);
|
|
padding: 40px 20px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.empty-icon {
|
|
width: 96px;
|
|
height: 96px;
|
|
margin-bottom: 14px;
|
|
}
|
|
</style>
|