feat(player): 完善 H5 投注端与 API 演示数据
- 球赛/串关/优胜冠军、赛事详情、历史投注与个人资料编辑 - 固定顶栏、公告与底栏,仅内容区滚动 - 底部导航与站点 favicon 使用 logo,登录页精简 - API 种子、冠军盘与历史注单增强 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
188
apps/player/src/components/outright/OutrightPanel.vue
Normal file
188
apps/player/src/components/outright/OutrightPanel.vue
Normal file
@@ -0,0 +1,188 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import api from '../../api';
|
||||
import OutrightOptionCard from './OutrightOptionCard.vue';
|
||||
import OutrightBetModal, { type OutrightPick } from './OutrightBetModal.vue';
|
||||
import emptyMatchesImg from '../../assets/images/empty-matches.svg';
|
||||
import saishiImg from '../../assets/images/saishi.png';
|
||||
|
||||
interface OutrightSelection {
|
||||
id: string;
|
||||
teamCode: string;
|
||||
teamName: string;
|
||||
odds: string;
|
||||
oddsVersion: string;
|
||||
}
|
||||
|
||||
interface OutrightEvent {
|
||||
id: string;
|
||||
leagueId: string;
|
||||
leagueName: string;
|
||||
title: string;
|
||||
selections: OutrightSelection[];
|
||||
}
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const loading = ref(true);
|
||||
const events = ref<OutrightEvent[]>([]);
|
||||
const expanded = ref<Set<string>>(new Set());
|
||||
const modalOpen = ref(false);
|
||||
const activePick = ref<OutrightPick | null>(null);
|
||||
|
||||
async function load() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const { data } = await api.get('/player/outrights');
|
||||
events.value = data.data ?? [];
|
||||
if (events.value.length && expanded.value.size === 0) {
|
||||
expanded.value = new Set([events.value[0].id]);
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load);
|
||||
|
||||
function toggle(id: string) {
|
||||
const next = new Set(expanded.value);
|
||||
if (next.has(id)) next.delete(id);
|
||||
else next.add(id);
|
||||
expanded.value = next;
|
||||
}
|
||||
|
||||
function openBet(event: OutrightEvent, sel: OutrightSelection) {
|
||||
activePick.value = {
|
||||
selectionId: sel.id,
|
||||
oddsVersion: sel.oddsVersion,
|
||||
teamCode: sel.teamCode,
|
||||
teamName: sel.teamName,
|
||||
odds: sel.odds,
|
||||
eventTitle: event.title,
|
||||
};
|
||||
modalOpen.value = true;
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
modalOpen.value = false;
|
||||
activePick.value = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="outright-panel">
|
||||
<div v-if="loading" class="state">{{ t('bet.loading') }}</div>
|
||||
|
||||
<div v-else-if="events.length" class="event-list">
|
||||
<section v-for="event in events" :key="event.id" class="event-block">
|
||||
<button type="button" class="event-head" @click="toggle(event.id)">
|
||||
<span class="toggle-icon">
|
||||
<span class="toggle-mark">{{ expanded.has(event.id) ? '−' : '+' }}</span>
|
||||
</span>
|
||||
<span class="event-title">{{ event.title }}</span>
|
||||
<img :src="saishiImg" alt="" class="event-saishi" />
|
||||
</button>
|
||||
|
||||
<div v-if="expanded.has(event.id)" class="options-grid">
|
||||
<OutrightOptionCard
|
||||
v-for="sel in event.selections"
|
||||
:key="sel.id"
|
||||
:team-code="sel.teamCode"
|
||||
:team-name="sel.teamName"
|
||||
:odds="sel.odds"
|
||||
@pick="openBet(event, sel)"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div v-else class="empty">
|
||||
<img :src="emptyMatchesImg" alt="" class="empty-icon" />
|
||||
<p>{{ t('bet.no_outright') }}</p>
|
||||
</div>
|
||||
|
||||
<OutrightBetModal :open="modalOpen" :pick="activePick" @close="closeModal" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.outright-panel {
|
||||
padding: 4px 12px 0;
|
||||
}
|
||||
|
||||
.event-block {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.event-head {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 10px;
|
||||
background: #141414;
|
||||
border: 1px solid #2e2e2e;
|
||||
border-radius: 6px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.toggle-icon {
|
||||
flex-shrink: 0;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 50%;
|
||||
background: #141414;
|
||||
border: 1px solid var(--border-gold-soft);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.toggle-mark {
|
||||
color: var(--primary-light);
|
||||
font-size: 17px;
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.event-title {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
color: var(--primary-light);
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.event-saishi {
|
||||
flex-shrink: 0;
|
||||
height: 44px;
|
||||
width: auto;
|
||||
max-width: 40px;
|
||||
object-fit: contain;
|
||||
padding-left: 8px;
|
||||
border-left: 1px solid #2a2a2a;
|
||||
}
|
||||
|
||||
.options-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
gap: 6px;
|
||||
padding: 10px 0 4px;
|
||||
}
|
||||
|
||||
.state,
|
||||
.empty {
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
padding: 48px 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user