Files
thebet365/apps/player/src/components/desktop/DesktopOutrightEventCard.vue

331 lines
7.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue';
import { useI18n } from 'vue-i18n';
import type { OutrightEvent, OutrightSelection } from '../outright/OutrightEventSection.vue';
import saishiImg from '../../assets/images/saishi.webp';
const props = defineProps<{
event: OutrightEvent;
}>();
const emit = defineEmits<{
open: [];
pick: [selection: OutrightSelection];
}>();
const { t } = useI18n();
const headTitle = computed(() => {
const raw = props.event.title.replace(/^\*+/, '').trim();
return raw || props.event.leagueName || t('bet.tab_outright');
});
const teamCount = computed(() => props.event.selectionCount ?? props.event.selections.length);
const isSettled = computed(
() => props.event.bettingOpen === false || props.event.status === 'SETTLED',
);
// Dynamically limit visible buttons based on card width
const MIN_BTN_W = 52; // px
const GAP = 4; // px
const cardRef = ref<HTMLElement | null>(null);
const cardWidth = ref(400);
let ro: ResizeObserver | null = null;
onMounted(() => {
if (!cardRef.value) return;
ro = new ResizeObserver(([entry]) => {
// panel uses left/right: 12px, so panel width = card - 24
cardWidth.value = entry.contentRect.width;
});
ro.observe(cardRef.value);
});
onUnmounted(() => ro?.disconnect());
const maxVisible = computed(() => {
const panelW = cardWidth.value - 24; // account for left/right: 12px
// n items need: n * MIN_BTN_W + (n-1) * GAP <= panelW
// n <= (panelW + GAP) / (MIN_BTN_W + GAP)
return Math.max(1, Math.min(5, Math.floor((panelW + GAP) / (MIN_BTN_W + GAP))));
});
// Sort by odds asc, show up to 5 but limited by available space
const topSelections = computed(() => {
if (!props.event.selections) return [];
return [...props.event.selections]
.sort((a, b) => {
const oa = parseFloat(a.odds) || 9999;
const ob = parseFloat(b.odds) || 9999;
return oa - ob;
})
.slice(0, maxVisible.value);
});
</script>
<template>
<article ref="cardRef" class="outright-event-card" :class="{ settled: isSettled }">
<!-- Clickable upper area to open detail -->
<div class="card-clickable-area" @click="emit('open')">
<div class="card-main">
<div class="title-row">
<span class="title">{{ headTitle }}</span>
<span v-if="isSettled" class="settled-tag">{{ t('bet.outright_settled') }}</span>
</div>
<p v-if="event.leagueName && event.leagueName !== headTitle" class="league">{{ event.leagueName }}</p>
<p class="meta">{{ t('bet.outright_teams_count', { n: teamCount }) }}</p>
</div>
<img :src="saishiImg" alt="" class="saishi" />
</div>
<!-- Quick bet popular teams panel -->
<div v-if="!isSettled && topSelections.length" class="quick-bet-panel">
<div class="quick-bet-title">{{ t('bet.quick_bet') || '快速下注' }}<span class="quick-bet-hint">{{ t('bet.popular_teams') || '热门队伍' }}</span></div>
<div class="odds-row">
<button
v-for="sel in topSelections"
:key="sel.id"
type="button"
class="odds-btn"
@click.stop="emit('pick', sel)"
>
<span class="selection-name" :title="sel.teamName">{{ sel.teamName }}</span>
<span class="selection-odds">{{ sel.odds }}</span>
</button>
</div>
<button type="button" class="view-all-btn" @click.stop="emit('open')">
{{ t('bet.outright_view_all') || '查看全部队伍' }}
</button>
</div>
</article>
</template>
<style scoped>
.outright-event-card {
position: relative;
width: 100%;
min-height: 140px;
border: 1px solid rgba(140, 140, 140, 0.35);
border-radius: 6px;
background: linear-gradient(180deg, #1a1a1a 0%, #0d0d0d 100%);
overflow: hidden;
display: flex;
flex-direction: column;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
transition: border-color 0.25s ease, box-shadow 0.25s ease;
}
.outright-event-card:hover {
border-color: var(--border-gold);
box-shadow: var(--shadow-gold);
}
.card-clickable-area {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 14px;
cursor: pointer;
flex: 1;
transition: transform 0.25s cubic-bezier(0.25, 0.8, 0.25, 1);
}
.outright-event-card:hover .card-clickable-area {
transform: translateY(-10px);
}
.card-main {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 4px;
}
.title-row {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.title {
font-size: 14px;
font-weight: 800;
color: #fff;
line-height: 1.35;
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
transition: color 0.2s;
}
.outright-event-card:hover .title {
color: var(--primary-light);
}
.settled-tag {
font-size: 10px;
font-weight: 800;
color: #c9a227;
border: 1px solid rgba(201, 162, 39, 0.45);
border-radius: 999px;
padding: 1px 7px;
background: rgba(201, 162, 39, 0.1);
}
.league {
margin: 0;
font-size: 11px;
font-weight: 600;
color: var(--text-muted);
opacity: 1;
transition: opacity 0.15s ease;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.meta {
margin: 0;
font-size: 11px;
font-weight: 600;
color: #888;
opacity: 1;
transition: opacity 0.15s ease;
}
.outright-event-card:hover .league,
.outright-event-card:hover .meta {
opacity: 0;
}
.saishi {
flex-shrink: 0;
height: 48px;
width: auto;
max-width: 44px;
object-fit: contain;
opacity: 0.85;
transition: transform 0.25s ease, opacity 0.2s ease;
}
.outright-event-card:hover .saishi {
transform: scale(0.9) rotate(5deg);
opacity: 0;
}
/* Quick Bet Panel — absolute, card height stays fixed */
.quick-bet-panel {
position: absolute;
left: 12px;
right: 12px;
bottom: 10px;
display: flex;
flex-direction: column;
gap: 5px;
overflow: hidden;
opacity: 0;
pointer-events: none;
transform: translateY(12px);
transition: opacity 0.2s ease, transform 0.22s cubic-bezier(0.25, 0.8, 0.25, 1);
z-index: 5;
}
.outright-event-card:hover .quick-bet-panel {
opacity: 1;
pointer-events: auto;
transform: translateY(0);
}
.quick-bet-title {
font-size: 9px;
font-weight: 700;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.quick-bet-hint {
font-size: 9px;
font-weight: 600;
color: #666;
text-transform: none;
letter-spacing: 0;
margin-left: 1px;
}
.odds-row {
display: flex;
gap: 4px;
width: 100%;
overflow: hidden;
}
.odds-btn {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex: 1; /* equal share of available width */
gap: 1px;
padding: 2px 4px;
border-radius: 4px;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.08);
cursor: pointer;
min-height: 32px;
transition: all 0.2s ease;
}
.odds-btn:hover {
background: var(--border-gold-soft);
border-color: var(--border-gold);
box-shadow: 0 0 8px rgba(212, 175, 55, 0.25);
}
.selection-name {
font-size: 9px;
font-weight: 700;
color: #bbb;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
line-height: 1.2;
}
.odds-btn:hover .selection-name {
color: #fff;
}
.selection-odds {
font-size: 11px;
font-weight: 900;
color: var(--primary-light);
line-height: 1.1;
}
.odds-btn:hover .selection-odds {
color: #fff;
}
.view-all-btn {
align-self: flex-end;
font-size: 10px;
font-weight: 700;
color: var(--primary-light);
background: none;
border: none;
cursor: pointer;
padding: 2px 0;
opacity: 0.7;
transition: opacity 0.15s ease;
letter-spacing: 0.02em;
}
.view-all-btn:hover {
opacity: 1;
}
</style>