feat(player): 完善 H5 投注端与 API 演示数据

- 球赛/串关/优胜冠军、赛事详情、历史投注与个人资料编辑
- 固定顶栏、公告与底栏,仅内容区滚动
- 底部导航与站点 favicon 使用 logo,登录页精简
- API 种子、冠军盘与历史注单增强

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-02 17:18:11 +08:00
parent 7af2e418c3
commit b5dca1bfb1
75 changed files with 7077 additions and 384 deletions

View File

@@ -1,10 +1,29 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { ref, onMounted, computed } from 'vue';
import { useRouter } from 'vue-router';
import api from '../api';
import emptyMatchesImg from '../assets/images/empty-matches.svg';
import BannerCarousel from '../components/BannerCarousel.vue';
import { resolveBanners } from '../constants/defaultBanner';
const router = useRouter();
const home = ref<{ banners: unknown[]; hotMatches: Match[]; ticker: unknown[] } | null>(null);
const home = ref<{
banners: Banner[];
hotMatches: Match[];
ticker: ContentItem[];
notices: ContentItem[];
} | null>(null);
interface ContentItem {
translation?: { title?: string; body?: string; imageUrl?: string };
}
interface Banner {
id?: string;
linkType?: string | null;
linkTarget?: string | null;
translation?: { title?: string; body?: string; imageUrl?: string };
}
interface Match {
id: string;
@@ -12,9 +31,10 @@ interface Match {
awayTeamName: string;
startTime: string;
isHot: boolean;
markets?: Array<{ marketType: string; selections: Array<{ id: string; selectionName: string; odds: string; oddsVersion: string }> }>;
}
const displayBanners = computed(() => resolveBanners(home.value?.banners));
onMounted(async () => {
const { data } = await api.get('/player/home');
home.value = data.data;
@@ -27,13 +47,7 @@ function goMatch(id: string) {
<template>
<div>
<div v-if="home?.banners?.length" class="banner card">
{{ (home.banners[0] as { translation?: { title?: string } })?.translation?.title || 'Welcome' }}
</div>
<div v-if="home?.ticker?.length" class="ticker">
{{ (home.ticker[0] as { translation?: { body?: string } })?.translation?.body }}
</div>
<BannerCarousel :banners="displayBanners" />
<h2 class="section-title">热门赛事</h2>
<div v-for="match in home?.hotMatches || []" :key="match.id" class="card match-card" @click="goMatch(match.id)">
@@ -41,17 +55,18 @@ function goMatch(id: string) {
<div class="match-time">{{ new Date(match.startTime).toLocaleString() }}</div>
</div>
<div v-if="!home?.hotMatches?.length" class="empty">暂无赛事</div>
<div v-if="home && !home.hotMatches?.length" class="empty">
<img :src="emptyMatchesImg" alt="" class="empty-icon" />
<p>暂无赛事</p>
</div>
</div>
</template>
<style scoped>
.banner { background: linear-gradient(135deg, #1a472a, #0f1419); padding: 24px; font-size: 18px; font-weight: 600; }
.ticker { background: #243044; padding: 8px 12px; font-size: 12px; margin-bottom: 12px; border-radius: 4px; overflow: hidden; white-space: nowrap; }
.section-title { font-size: 16px; margin-bottom: 12px; }
.match-card { cursor: pointer; }
.match-card:hover { background: var(--bg-hover); }
.match-teams { font-weight: 600; margin-bottom: 4px; }
.match-time { font-size: 12px; color: var(--text-muted); }
.empty { text-align: center; color: var(--text-muted); padding: 40px; }
.match-card { cursor: pointer; transition: border-color 0.2s, box-shadow 0.2s; }
.match-card:active { border-color: var(--border-gold-soft); }
.match-teams { font-weight: 800; margin-bottom: 8px; font-size: 16px; }
.match-time { font-size: 13px; color: var(--text-muted); font-weight: 500; }
.empty { text-align: center; color: var(--text-muted); padding: 40px 20px; font-weight: 600; }
.empty-icon { width: 96px; height: 96px; margin-bottom: 14px; }
</style>