feat(player): 充值订单详情与审核记录,优化桌面端交互与记录列表样式
新增充值详情页及单条订单 API,修复桌面充值提交路径;充值/注单记录编号改为中性色,并包含桌面首页与投注相关 UI 改进。
This commit is contained in:
@@ -68,7 +68,12 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="root" class="cash-chip-wrap">
|
||||
<div
|
||||
ref="root"
|
||||
class="cash-chip-wrap"
|
||||
@mouseenter="open = true; refreshProfile()"
|
||||
@mouseleave="open = false"
|
||||
>
|
||||
<button type="button" class="cash-chip" @click="toggle">
|
||||
<span class="chip-body">
|
||||
<span class="chip-label">{{ t('wallet.cash_balance') }}</span>
|
||||
@@ -99,8 +104,6 @@ onUnmounted(() => {
|
||||
{{ t('recharge.title') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="open" class="backdrop" @click="close" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -19,7 +19,32 @@ const { isDesktop } = useViewport();
|
||||
const auth = useAuthStore();
|
||||
const { inboxEnabled, hubTitleKey } = useInboxFeature();
|
||||
const { unreadCount, messages, listLoaded, refreshUnreadCount, markAllRead, deleteAllMessages } = usePlayerMessages();
|
||||
const { isOpen, toggle, close } = useFloatingMailbox();
|
||||
const { isOpen, toggle, open, close } = useFloatingMailbox();
|
||||
const hovering = ref(false);
|
||||
let hoverTimer: number | undefined = undefined;
|
||||
|
||||
function onMouseEnter() {
|
||||
if (hoverTimer !== undefined) {
|
||||
clearTimeout(hoverTimer);
|
||||
hoverTimer = undefined;
|
||||
}
|
||||
hovering.value = true;
|
||||
}
|
||||
|
||||
function onMouseLeave() {
|
||||
hoverTimer = window.setTimeout(() => {
|
||||
hovering.value = false;
|
||||
hoverTimer = undefined;
|
||||
}, 150);
|
||||
}
|
||||
|
||||
function openTab(tab: HubTab) {
|
||||
activeTab.value = tab;
|
||||
selectedMessageId.value = null;
|
||||
open();
|
||||
hovering.value = false;
|
||||
}
|
||||
|
||||
|
||||
const markingAll = ref(false);
|
||||
const deletingAll = ref(false);
|
||||
@@ -91,7 +116,42 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="floating-mailbox" :class="{ 'is-betting-desktop': isBettingDesktop }">
|
||||
<div
|
||||
class="floating-mailbox"
|
||||
:class="{ 'is-betting-desktop': isBettingDesktop }"
|
||||
@mouseenter="onMouseEnter"
|
||||
@mouseleave="onMouseLeave"
|
||||
>
|
||||
<!-- Hover Menu -->
|
||||
<Transition name="fade-slide-mini">
|
||||
<div v-if="isDesktop && hovering && !isOpen" class="hover-menu">
|
||||
<button
|
||||
v-if="inboxEnabled"
|
||||
type="button"
|
||||
class="menu-item"
|
||||
@click="openTab('messages')"
|
||||
>
|
||||
<svg class="menu-icon" viewBox="0 0 24 24" width="16" height="16">
|
||||
<path fill="currentColor" d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"/>
|
||||
</svg>
|
||||
<span>{{ t('messages.tab_inbox') }}</span>
|
||||
<span v-if="unreadCount > 0" class="menu-badge">
|
||||
{{ unreadCount > 99 ? '99+' : unreadCount }}
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="menu-item"
|
||||
@click="openTab('support')"
|
||||
>
|
||||
<svg class="menu-icon" viewBox="0 0 24 24" width="16" height="16">
|
||||
<path fill="currentColor" d="M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM6 9h12v2H6V9zm8 5H6v-2h8v2zm4-6H6V6h12v2z"/>
|
||||
</svg>
|
||||
<span>{{ t('messages.tab_support') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<!-- Floating Button -->
|
||||
<button
|
||||
type="button"
|
||||
@@ -121,25 +181,9 @@ onMounted(() => {
|
||||
<Transition name="fade-slide">
|
||||
<div v-if="isOpen" class="popup-panel">
|
||||
<div class="popup-header">
|
||||
<div class="tabs">
|
||||
<button
|
||||
v-if="inboxEnabled"
|
||||
type="button"
|
||||
class="tab-btn"
|
||||
:class="{ active: activeTab === 'messages' }"
|
||||
@click="switchTab('messages')"
|
||||
>
|
||||
{{ t('messages.tab_inbox') }}
|
||||
<span v-if="unreadInList > 0" class="tab-badge">{{ unreadInList }}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="tab-btn"
|
||||
:class="{ active: activeTab === 'support' }"
|
||||
@click="switchTab('support')"
|
||||
>
|
||||
{{ t('messages.tab_support') }}
|
||||
</button>
|
||||
<div class="popup-title">
|
||||
<span>{{ activeTab === 'messages' ? t('messages.tab_inbox') : t('messages.tab_support') }}</span>
|
||||
<span v-if="activeTab === 'messages' && unreadInList > 0" class="tab-badge">{{ unreadInList }}</span>
|
||||
</div>
|
||||
|
||||
<div class="header-actions">
|
||||
@@ -168,6 +212,100 @@ onMounted(() => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.hover-menu {
|
||||
position: absolute;
|
||||
bottom: 64px;
|
||||
right: 0;
|
||||
background: rgba(22, 22, 22, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(212, 175, 55, 0.25);
|
||||
border-radius: 8px;
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
|
||||
width: 120px;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.hover-menu::after,
|
||||
.hover-menu::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.hover-menu::after {
|
||||
bottom: -6px;
|
||||
right: 22px;
|
||||
border-width: 6px 6px 0 6px;
|
||||
border-color: rgba(22, 22, 22, 0.95) transparent transparent transparent;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.hover-menu::before {
|
||||
bottom: -7px;
|
||||
right: 21px;
|
||||
border-width: 7px 7px 0 7px;
|
||||
border-color: rgba(212, 175, 55, 0.25) transparent transparent transparent;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 10px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
color: var(--primary-light);
|
||||
background: rgba(212, 175, 55, 0.08);
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
color: var(--primary-light);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.menu-badge {
|
||||
margin-left: auto;
|
||||
background: var(--danger, #ff4d4f);
|
||||
color: #fff;
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
padding: 1px 5px;
|
||||
border-radius: 8px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.fade-slide-mini-enter-active,
|
||||
.fade-slide-mini-leave-active {
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transform-origin: bottom right;
|
||||
}
|
||||
|
||||
.fade-slide-mini-enter-from,
|
||||
.fade-slide-mini-leave-to {
|
||||
opacity: 0;
|
||||
transform: scale(0.95) translateY(10px);
|
||||
}
|
||||
|
||||
.floating-mailbox {
|
||||
position: fixed;
|
||||
bottom: 32px;
|
||||
@@ -249,45 +387,23 @@ onMounted(() => {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
color: var(--text-muted);
|
||||
.popup-title {
|
||||
color: var(--primary-light);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
padding: 0 16px;
|
||||
font-weight: 700;
|
||||
padding: 0 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.tab-btn:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.tab-btn.active {
|
||||
color: var(--primary-light);
|
||||
border-bottom-color: var(--primary);
|
||||
}
|
||||
|
||||
.tab-badge {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: #fff;
|
||||
.popup-title .tab-badge {
|
||||
background: var(--primary);
|
||||
color: #141414;
|
||||
font-size: 11px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.tab-btn.active .tab-badge {
|
||||
background: var(--primary);
|
||||
color: #141414;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
|
||||
@@ -45,7 +45,13 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="root" class="locale-switch" :class="{ compact, open }">
|
||||
<div
|
||||
ref="root"
|
||||
class="locale-switch"
|
||||
:class="{ compact, open }"
|
||||
@mouseenter="open = true"
|
||||
@mouseleave="open = false"
|
||||
>
|
||||
<button type="button" class="locale-trigger" :aria-expanded="open" aria-haspopup="listbox" @click.stop="toggle">
|
||||
<LocaleFlag :locale="locale" :size="compact ? 16 : 18" />
|
||||
</button> <ul v-show="open" class="locale-menu" role="listbox" :aria-label="compact ? 'Language' : undefined">
|
||||
|
||||
@@ -82,7 +82,40 @@ const liveScoreText = computed(() => {
|
||||
const router = useRouter();
|
||||
const slip = useBetSlipStore();
|
||||
const auth = useAuthStore();
|
||||
const { openAt } = useDesktopBetPopover();
|
||||
const { openAt, visible: popoverVisible, pendingItem, cancelClose, scheduleClose } = useDesktopBetPopover();
|
||||
|
||||
function getHoveredSide() {
|
||||
if (!popoverVisible.value || !pendingItem.value) return '';
|
||||
if (String(pendingItem.value.matchId) !== String(props.match.id)) return '';
|
||||
|
||||
const selId = pendingItem.value.selectionId;
|
||||
// Scan all markets to find the selection and get its code
|
||||
for (const market of props.match.markets ?? []) {
|
||||
const sel = market.selections?.find((s: any) => s.id === selId);
|
||||
if (!sel) continue;
|
||||
const code = (sel.selectionCode || '').toUpperCase();
|
||||
if (code === 'HOME') return 'home';
|
||||
if (code === 'AWAY') return 'away';
|
||||
if (code === 'DRAW') return 'draw';
|
||||
// Over/Under: over = home side, under = away side (for visual cue)
|
||||
if (code === 'OVER') return 'home';
|
||||
if (code === 'UNDER') return 'away';
|
||||
// Odd/Even: highlight both
|
||||
if (code === 'ODD' || code === 'EVEN') return 'draw';
|
||||
// Fallback: use selection index
|
||||
const selIndex = market.selections?.findIndex((s: any) => s.id === selId) ?? -1;
|
||||
if (selIndex === -1) continue;
|
||||
if (market.marketType === 'FT_1X2') {
|
||||
if (selIndex === 0) return 'home';
|
||||
if (selIndex === 1) return 'draw';
|
||||
if (selIndex === 2) return 'away';
|
||||
} else {
|
||||
if (selIndex === 0) return 'home';
|
||||
if (selIndex === 1) return 'away';
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
const activeMarketType = ref<string>('');
|
||||
|
||||
@@ -173,7 +206,7 @@ function goMatchDetail() {
|
||||
<span v-else-if="phase === 'settled'" class="status-tag status-tag--settled">{{ phaseLabel }}</span>
|
||||
<span v-else class="status-tag status-tag--pending">{{ phaseLabel }}</span>
|
||||
|
||||
<div class="teams-row">
|
||||
<div class="teams-row" :class="getHoveredSide()">
|
||||
<div class="team">
|
||||
<span class="team-name">{{ match.homeTeamName }}</span>
|
||||
<img
|
||||
@@ -217,7 +250,7 @@ function goMatchDetail() {
|
||||
</button>
|
||||
|
||||
<!-- Morphing Quick Bet panel (Desktop Only, disabled when noQuickBet=true) -->
|
||||
<div v-if="!noQuickBet" class="card-quick-bet" @click.stop>
|
||||
<div v-if="!noQuickBet" class="card-quick-bet" @click.stop @mouseenter="cancelClose" @mouseleave="scheduleClose()">
|
||||
<div class="quick-bet-tabs" v-if="getAvailableMarketsForMatch(match).length">
|
||||
<button
|
||||
v-for="m in getAvailableMarketsForMatch(match)"
|
||||
@@ -379,7 +412,7 @@ function goMatchDetail() {
|
||||
letter-spacing: 0.08em;
|
||||
line-height: 1;
|
||||
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9);
|
||||
transition: font-size 0.25s ease;
|
||||
transition: font-size 0.25s ease, transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1), text-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
@@ -593,4 +626,35 @@ function goMatchDetail() {
|
||||
font-size: 10px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* --- VS Selection Highlight: scale selected team, no VS animation --- */
|
||||
.teams-row .team {
|
||||
transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.3s ease, filter 0.3s ease;
|
||||
}
|
||||
.teams-row.home .team:first-child {
|
||||
transform: scale(1.08);
|
||||
filter: brightness(1.3) saturate(1.2);
|
||||
z-index: 2;
|
||||
}
|
||||
.teams-row.home .team:last-child {
|
||||
opacity: 0.35 !important;
|
||||
transform: scale(0.94);
|
||||
filter: grayscale(0.6) brightness(0.7);
|
||||
}
|
||||
|
||||
.teams-row.away .team:last-child {
|
||||
transform: scale(1.08);
|
||||
filter: brightness(1.3) saturate(1.2);
|
||||
z-index: 2;
|
||||
}
|
||||
.teams-row.away .team:first-child {
|
||||
opacity: 0.35 !important;
|
||||
transform: scale(0.94);
|
||||
filter: grayscale(0.6) brightness(0.7);
|
||||
}
|
||||
|
||||
.teams-row.draw .team {
|
||||
transform: scale(1.05);
|
||||
filter: brightness(1.15) saturate(1.1);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -58,7 +58,12 @@ function logout() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="root" class="avatar-wrap">
|
||||
<div
|
||||
ref="root"
|
||||
class="avatar-wrap"
|
||||
@mouseenter="open = true"
|
||||
@mouseleave="open = false"
|
||||
>
|
||||
<button type="button" class="avatar-btn" :aria-expanded="open" @click="toggle">
|
||||
<img v-if="displayAvatarUrl" :src="displayAvatarUrl" alt="" class="avatar-img" />
|
||||
</button>
|
||||
@@ -69,8 +74,6 @@ function logout() {
|
||||
<button type="button" class="menu-item" @click="goEdit">{{ t('profile.edit') }}</button>
|
||||
<button type="button" class="menu-item danger" @click="logout">{{ t('auth.logout') }}</button>
|
||||
</div>
|
||||
|
||||
<div v-if="open" class="backdrop" @click="close" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -10,9 +10,10 @@ import { usePlayerProfile } from '../../composables/usePlayerProfile';
|
||||
import ConfirmDialog from '../ConfirmDialog.vue';
|
||||
import { buildBetPlaceConfirmMessage } from '../../utils/betPlaceConfirmMessage';
|
||||
import { useAppToast } from '../../composables/useAppToast';
|
||||
import BetSuccessOverlay from '../BetSuccessOverlay.vue';
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
const { visible, anchorX, anchorY, pendingItem, close } = useDesktopBetPopover();
|
||||
const { visible, anchorX, anchorY, pendingItem, placement, close, cancelClose, scheduleClose } = useDesktopBetPopover();
|
||||
const slip = useBetSlipStore();
|
||||
const auth = useAuthStore();
|
||||
const { refreshProfile } = usePlayerProfile();
|
||||
@@ -25,6 +26,20 @@ const error = ref('');
|
||||
const showPlaceConfirm = ref(false);
|
||||
const placeConfirmMessage = ref('');
|
||||
const MIN_STAKE = 5;
|
||||
const showSuccess = ref(false);
|
||||
const confirmingItem = ref<any>(null);
|
||||
|
||||
watch(showPlaceConfirm, (val) => {
|
||||
if (!val) {
|
||||
confirmingItem.value = null;
|
||||
}
|
||||
});
|
||||
|
||||
function onPopoverMouseLeave() {
|
||||
if (showPlaceConfirm.value) return;
|
||||
scheduleClose(200);
|
||||
}
|
||||
|
||||
|
||||
let outsideClickTimer = 0;
|
||||
|
||||
@@ -95,6 +110,7 @@ function validatePlaceNow(): boolean {
|
||||
function onPlaceNowClick() {
|
||||
if (!validatePlaceNow()) return;
|
||||
const item = pendingItem.value!;
|
||||
confirmingItem.value = item;
|
||||
placeConfirmMessage.value = buildBetPlaceConfirmMessage(t, {
|
||||
mode: 'single',
|
||||
items: [item],
|
||||
@@ -107,7 +123,7 @@ function onPlaceNowClick() {
|
||||
showPlaceConfirm.value = true;
|
||||
}
|
||||
|
||||
async function executePlaceNow(item = pendingItem.value) {
|
||||
async function executePlaceNow(item = confirmingItem.value || pendingItem.value) {
|
||||
if (!item) return;
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
@@ -122,7 +138,7 @@ async function executePlaceNow(item = pendingItem.value) {
|
||||
await refreshProfile();
|
||||
showPlaceConfirm.value = false;
|
||||
close();
|
||||
showToast(t('bet.place_success'));
|
||||
showSuccess.value = true;
|
||||
} catch (e: unknown) {
|
||||
error.value =
|
||||
(e as { response?: { data?: { error?: string } } })?.response?.data?.error ||
|
||||
@@ -134,7 +150,7 @@ async function executePlaceNow(item = pendingItem.value) {
|
||||
}
|
||||
|
||||
async function confirmPlaceNow() {
|
||||
const item = pendingItem.value;
|
||||
const item = confirmingItem.value;
|
||||
if (!item) return;
|
||||
showPlaceConfirm.value = false;
|
||||
await executePlaceNow(item);
|
||||
@@ -184,11 +200,13 @@ function addToParlayList() {
|
||||
v-if="visible && pendingItem"
|
||||
ref="popRef"
|
||||
class="bet-popover"
|
||||
:class="`placement-${placement}`"
|
||||
tabindex="-1"
|
||||
:style="{ left: `${anchorX}px`, top: `${anchorY}px` }"
|
||||
@click.stop
|
||||
@mouseenter="cancelClose"
|
||||
@mouseleave="onPopoverMouseLeave"
|
||||
>
|
||||
<button type="button" class="pop-close" aria-label="Close" @click="close">✕</button>
|
||||
|
||||
<div class="pop-match">{{ pendingItem.matchName }}</div>
|
||||
<div class="pop-market">{{ pendingItem.marketName }}</div>
|
||||
@@ -233,104 +251,146 @@ function addToParlayList() {
|
||||
@confirm="confirmPlaceNow"
|
||||
/>
|
||||
|
||||
<BetSuccessOverlay :show="showSuccess" @done="showSuccess = false" />
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.bet-popover {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
width: 280px;
|
||||
padding: 12px 28px 12px 12px;
|
||||
width: 190px;
|
||||
padding: 8px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-gold-soft);
|
||||
background: rgba(18, 18, 18, 0.98);
|
||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.55);
|
||||
border: 1px solid rgba(212, 175, 55, 0.2);
|
||||
background: rgba(22, 22, 22, 0.9);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.55);
|
||||
}
|
||||
|
||||
.pop-close {
|
||||
.bet-popover::after,
|
||||
.bet-popover::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
padding: 2px 4px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.pop-close:hover {
|
||||
color: #fff;
|
||||
.bet-popover.placement-bottom::after {
|
||||
top: -6px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border-width: 0 6px 6px 6px;
|
||||
border-color: transparent transparent rgba(22, 22, 22, 0.9) transparent;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.bet-popover.placement-bottom::before {
|
||||
top: -7px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border-width: 0 7px 7px 7px;
|
||||
border-color: transparent transparent rgba(212, 175, 55, 0.2) transparent;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.bet-popover.placement-top::after {
|
||||
bottom: -6px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border-width: 6px 6px 0 6px;
|
||||
border-color: rgba(22, 22, 22, 0.9) transparent transparent transparent;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.bet-popover.placement-top::before {
|
||||
bottom: -7px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border-width: 7px 7px 0 7px;
|
||||
border-color: rgba(212, 175, 55, 0.2) transparent transparent transparent;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.pop-match {
|
||||
font-size: 11px;
|
||||
font-size: 9px;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.35;
|
||||
margin-bottom: 4px;
|
||||
line-height: 1.3;
|
||||
margin-bottom: 1px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.pop-market {
|
||||
font-size: 10px;
|
||||
font-size: 8px;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 6px;
|
||||
margin-bottom: 4px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.pop-pick-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 8px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 4px;
|
||||
background: rgba(212, 175, 55, 0.06);
|
||||
border: 1px solid var(--border-gold-soft);
|
||||
gap: 6px;
|
||||
padding: 4px 6px;
|
||||
margin-bottom: 6px;
|
||||
border-radius: 3px;
|
||||
background: rgba(212, 175, 55, 0.04);
|
||||
border: 1px solid rgba(212, 175, 55, 0.15);
|
||||
}
|
||||
|
||||
.pick {
|
||||
font-size: 12px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.odds {
|
||||
font-size: 12px;
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
color: var(--primary-light);
|
||||
}
|
||||
|
||||
.stake-label {
|
||||
display: block;
|
||||
font-size: 10px;
|
||||
font-size: 8px;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 4px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.stake-input {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 6px 8px;
|
||||
margin-bottom: 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border);
|
||||
background: #0d0d0d;
|
||||
padding: 4px 6px;
|
||||
margin-bottom: 5px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: #080808;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.stake-input:focus {
|
||||
border-color: var(--border-gold-soft);
|
||||
border-color: rgba(212, 175, 55, 0.3);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.est-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 10px;
|
||||
font-size: 8px;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.est-val {
|
||||
@@ -339,20 +399,20 @@ function addToParlayList() {
|
||||
}
|
||||
|
||||
.pop-error {
|
||||
font-size: 10px;
|
||||
font-size: 8px;
|
||||
color: var(--danger);
|
||||
margin: 0 0 8px;
|
||||
margin: 0 0 5px;
|
||||
}
|
||||
|
||||
.pop-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.pop-actions-row {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.pop-actions-row .btn-outline {
|
||||
@@ -363,18 +423,19 @@ function addToParlayList() {
|
||||
|
||||
.btn-primary-gold {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
padding: 5px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
border-radius: 3px;
|
||||
background: linear-gradient(180deg, #f0d875 0%, #d4af37 100%);
|
||||
color: #3d2800;
|
||||
font-size: 12px;
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.btn-primary-gold:hover:not(:disabled) {
|
||||
opacity: 0.92;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.btn-primary-gold:disabled {
|
||||
@@ -384,17 +445,18 @@ function addToParlayList() {
|
||||
|
||||
.btn-outline {
|
||||
width: 100%;
|
||||
padding: 7px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border-gold-soft);
|
||||
background: rgba(212, 175, 55, 0.05);
|
||||
padding: 4px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid rgba(212, 175, 55, 0.25);
|
||||
background: rgba(212, 175, 55, 0.03);
|
||||
color: var(--primary-light);
|
||||
font-size: 11px;
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.btn-outline:hover {
|
||||
background: rgba(212, 175, 55, 0.12);
|
||||
background: rgba(212, 175, 55, 0.08);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { groupCorrectScoreSelections, type CsSelection } from '../../utils/correctScoreLayout';
|
||||
import { useDesktopBetPopover } from '../../composables/useDesktopBetPopover';
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
marketType: string;
|
||||
@@ -24,6 +26,7 @@ const emit = defineEmits<{
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const { scheduleClose, cancelClose } = useDesktopBetPopover();
|
||||
|
||||
const columns = computed(() =>
|
||||
groupCorrectScoreSelections(
|
||||
@@ -55,7 +58,7 @@ function formatOdds(odds: string) {
|
||||
<span>{{ t('bet.col_away') }}</span>
|
||||
</div>
|
||||
|
||||
<div class="cols-grid">
|
||||
<div class="cols-grid" @mouseenter="cancelClose" @mouseleave="scheduleClose()">
|
||||
<div v-for="colKey in ['home', 'draw', 'away'] as const" :key="colKey" class="col">
|
||||
<button
|
||||
v-for="sel in columns[colKey]"
|
||||
@@ -65,6 +68,7 @@ function formatOdds(odds: string) {
|
||||
:class="{ selected: isSelected(sel.id), 'score-card--locked': locked }"
|
||||
:data-bet-selection-id="sel.id"
|
||||
:disabled="locked"
|
||||
@mouseenter="onPick(sel, $event)"
|
||||
@click="onPick(sel, $event)"
|
||||
>
|
||||
<span class="score-line">{{ sel.scoreDisplay }}</span>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { resolveSelectionLabel } from '../../utils/selectionLabel';
|
||||
import { useDesktopBetPopover } from '../../composables/useDesktopBetPopover';
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
selections: {
|
||||
@@ -23,6 +25,7 @@ const props = defineProps<{
|
||||
|
||||
const emit = defineEmits<{ pick: [id: string, event?: MouseEvent] }>();
|
||||
const { t } = useI18n();
|
||||
const { scheduleClose, cancelClose } = useDesktopBetPopover();
|
||||
|
||||
function label(sel: (typeof props.selections)[number]) {
|
||||
if (sel.selectionDisplayName?.trim()) return sel.selectionDisplayName;
|
||||
@@ -53,7 +56,7 @@ const panelStyle = computed(() =>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="wrap" :class="{ compact, horizontal, desktopCard, locked }">
|
||||
<div class="wrap" :class="{ compact, horizontal, desktopCard, locked }" @mouseenter="cancelClose" @mouseleave="scheduleClose()">
|
||||
<div
|
||||
class="panel"
|
||||
:class="{ horizontal, 'desktop-card': desktopCard }"
|
||||
@@ -67,6 +70,7 @@ const panelStyle = computed(() =>
|
||||
:class="{ selected: isSelected(sel.id), 'odds-btn--locked': locked }"
|
||||
:data-bet-selection-id="sel.id"
|
||||
:disabled="locked"
|
||||
@mouseenter="onPick(sel.id, $event)"
|
||||
@click="onPick(sel.id, $event)"
|
||||
>
|
||||
<span class="label">{{ label(sel) }}</span>
|
||||
|
||||
Reference in New Issue
Block a user