feat(admin,api,player): 赛事分组管理、盘口独立页与多语言展示优化
- 管理端按联赛展示单场,新增赛事/单场流程与列表展开状态保持 - 盘口赔率迁至独立页面,保存按钮仅在有修改时高亮 - API 新增联赛列表与子场查询,按 locale 返回队名并修复编译 - 波胆其它选项与促销标签等 i18n 补齐,文案更易懂
This commit is contained in:
@@ -2,10 +2,13 @@
|
||||
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/卡片.png';
|
||||
import BannerCarousel from '../components/BannerCarousel.vue';
|
||||
import { usePlayerHome } from '../composables/usePlayerHome';
|
||||
import { teamFlagUrl } from '../utils/teamFlag';
|
||||
|
||||
const matchCardBg = `url(${cardBg})`;
|
||||
const { t, locale } = useI18n();
|
||||
const router = useRouter();
|
||||
const { banners, hotMatches, loading } = usePlayerHome();
|
||||
@@ -42,7 +45,7 @@ function awayFlag(match: (typeof hotMatches.value)[number]) {
|
||||
<div
|
||||
v-for="match in hotMatches"
|
||||
:key="match.id"
|
||||
class="card match-card"
|
||||
class="match-card"
|
||||
@click="goMatch(match.id)"
|
||||
>
|
||||
<div class="match-info">
|
||||
@@ -52,7 +55,31 @@ function awayFlag(match: (typeof hotMatches.value)[number]) {
|
||||
<div class="match-flags" aria-hidden="true">
|
||||
<img v-if="homeFlag(match)" :src="homeFlag(match)" alt="" class="flag" />
|
||||
<span v-else class="flag-ph">⚽</span>
|
||||
<span class="vs">VS</span>
|
||||
<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>
|
||||
<img v-if="awayFlag(match)" :src="awayFlag(match)" alt="" class="flag" />
|
||||
<span v-else class="flag-ph">⚽</span>
|
||||
</div>
|
||||
@@ -67,16 +94,43 @@ function awayFlag(match: (typeof hotMatches.value)[number]) {
|
||||
|
||||
<style scoped>
|
||||
.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: border-color 0.2s, box-shadow 0.2s;
|
||||
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 {
|
||||
border-color: var(--border-gold-soft);
|
||||
opacity: 0.92;
|
||||
transform: scale(0.995);
|
||||
}
|
||||
|
||||
.match-info,
|
||||
.match-flags {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.match-info {
|
||||
@@ -102,35 +156,199 @@ function awayFlag(match: (typeof hotMatches.value)[number]) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 8px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid #2a2a2a;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.flag {
|
||||
width: 32px;
|
||||
height: 22px;
|
||||
width: 40px;
|
||||
height: 28px;
|
||||
object-fit: cover;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.flag-ph {
|
||||
width: 32px;
|
||||
height: 22px;
|
||||
width: 40px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
font-size: 16px;
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.vs {
|
||||
font-size: 11px;
|
||||
font-weight: 900;
|
||||
color: var(--primary-light);
|
||||
letter-spacing: 0.04em;
|
||||
.vs-arena {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
width: 72px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.hz-lightning {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
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;
|
||||
}
|
||||
|
||||
.hz-path-main {
|
||||
animation: hz-strike-main 2.6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.hz-path-sub {
|
||||
stroke-width: 1.6;
|
||||
animation: hz-strike-sub 2.6s ease-in-out infinite;
|
||||
animation-delay: 0.12s;
|
||||
}
|
||||
|
||||
.hz-beam {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
height: 2px;
|
||||
transform: translateY(-50%);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
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;
|
||||
}
|
||||
|
||||
.vs-img {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 26px;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user