diff --git a/apps/api/src/applications/player/player.controller.ts b/apps/api/src/applications/player/player.controller.ts index 5f7c6fd..311fd1f 100644 --- a/apps/api/src/applications/player/player.controller.ts +++ b/apps/api/src/applications/player/player.controller.ts @@ -436,6 +436,15 @@ export class PlayerController { return jsonResponse(result); } + @Get('deposit-orders/:id') + async myDepositOrder( + @CurrentUser('id') userId: bigint, + @Param('id') id: string, + ) { + const order = await this.deposit.getPlayerDepositOrder(userId, BigInt(id)); + return jsonResponse(order); + } + @Get('deposit-orders/:id/audit-logs') async myDepositOrderAuditLogs( @CurrentUser('id') userId: bigint, diff --git a/apps/api/src/domains/deposit/deposit.service.ts b/apps/api/src/domains/deposit/deposit.service.ts index 83cab4a..b928d5a 100644 --- a/apps/api/src/domains/deposit/deposit.service.ts +++ b/apps/api/src/domains/deposit/deposit.service.ts @@ -416,6 +416,85 @@ export class DepositService { return map; } + private formatPlayerDepositOrder( + o: { + id: bigint; + orderNo: string; + paymentMethodId: bigint; + methodType: string; + amount: Decimal; + screenshotUrl: string; + status: string; + approvedAmount: Decimal | null; + rejectReason: string | null; + remark: string | null; + createdAt: Date; + reviewedAt: Date | null; + paymentMethod: { + bankName: string | null; + usdtAddress: string | null; + displayName: string | null; + methodType: string; + } | null; + }, + auditLogs: Array<{ + id: string; + action: string; + actorType: string; + statusBefore: string | null; + statusAfter: string; + amount: string | null; + approvedAmount: string | null; + remark: string | null; + createdAt: string; + }>, + ) { + return { + id: o.id.toString(), + orderNo: o.orderNo, + paymentMethodId: o.paymentMethodId.toString(), + methodType: o.methodType, + amount: o.amount.toString(), + screenshotUrl: o.screenshotUrl, + status: o.status, + approvedAmount: o.approvedAmount?.toString() ?? null, + rejectReason: o.rejectReason, + remark: o.remark, + createdAt: o.createdAt, + reviewedAt: o.reviewedAt, + paymentMethodName: + o.paymentMethod?.displayName ?? o.paymentMethod?.bankName ?? o.paymentMethod?.usdtAddress ?? null, + auditLogs, + }; + } + + async getPlayerDepositOrder(playerId: bigint, orderId: bigint) { + const order = await this.prisma.depositOrder.findFirst({ + where: { id: orderId, playerId }, + include: { + paymentMethod: { + select: { bankName: true, usdtAddress: true, displayName: true, methodType: true }, + }, + }, + }); + if (!order) throw appBadRequest('ORDER_NOT_FOUND'); + + const auditMap = await this.attachPlayerAuditLogs([order]); + const auditLogs = (auditMap.get(order.id.toString()) ?? []).map((log) => ({ + id: log.id, + action: log.action, + actorType: log.actorType, + statusBefore: log.statusBefore, + statusAfter: log.statusAfter, + amount: log.amount, + approvedAmount: log.approvedAmount, + remark: log.remark, + createdAt: log.createdAt, + })); + + return this.formatPlayerDepositOrder(order, auditLogs); + } + async createDepositOrder( playerId: bigint, paymentMethodId: bigint, @@ -522,6 +601,47 @@ export class DepositService { }); } + async getPlayerDepositOrder(playerId: bigint, orderId: bigint) { + const order = await this.prisma.depositOrder.findFirst({ + where: { id: orderId, playerId }, + include: { + paymentMethod: { + select: { bankName: true, usdtAddress: true, displayName: true, methodType: true }, + }, + }, + }); + if (!order) throw appBadRequest('ORDER_NOT_FOUND'); + + const auditMap = await this.attachPlayerAuditLogs([order]); + const o = order; + return { + id: o.id.toString(), + orderNo: o.orderNo, + paymentMethodId: o.paymentMethodId.toString(), + methodType: o.methodType, + amount: o.amount.toString(), + screenshotUrl: o.screenshotUrl, + status: o.status, + approvedAmount: o.approvedAmount?.toString() ?? null, + rejectReason: o.rejectReason, + remark: o.remark, + createdAt: o.createdAt, + reviewedAt: o.reviewedAt, + paymentMethodName: o.paymentMethod?.displayName ?? o.paymentMethod?.bankName ?? o.paymentMethod?.usdtAddress ?? null, + auditLogs: (auditMap.get(o.id.toString()) ?? []).map((log) => ({ + id: log.id, + action: log.action, + actorType: log.actorType, + statusBefore: log.statusBefore, + statusAfter: log.statusAfter, + amount: log.amount, + approvedAmount: log.approvedAmount, + remark: log.remark, + createdAt: log.createdAt, + })), + }; + } + async getPlayerDepositOrders(playerId: bigint, page = 1, pageSize = 20) { const skip = (page - 1) * pageSize; const where = { playerId }; @@ -543,32 +663,22 @@ export class DepositService { const auditMap = await this.attachPlayerAuditLogs(items); return { - items: items.map((o) => ({ - id: o.id.toString(), - orderNo: o.orderNo, - paymentMethodId: o.paymentMethodId.toString(), - methodType: o.methodType, - amount: o.amount.toString(), - screenshotUrl: o.screenshotUrl, - status: o.status, - approvedAmount: o.approvedAmount?.toString() ?? null, - rejectReason: o.rejectReason, - remark: o.remark, - createdAt: o.createdAt, - reviewedAt: o.reviewedAt, - paymentMethodName: o.paymentMethod?.displayName ?? o.paymentMethod?.bankName ?? o.paymentMethod?.usdtAddress ?? null, - auditLogs: (auditMap.get(o.id.toString()) ?? []).map((log) => ({ - id: log.id, - action: log.action, - actorType: log.actorType, - statusBefore: log.statusBefore, - statusAfter: log.statusAfter, - amount: log.amount, - approvedAmount: log.approvedAmount, - remark: log.remark, - createdAt: log.createdAt, - })), - })), + items: items.map((o) => + this.formatPlayerDepositOrder( + o, + (auditMap.get(o.id.toString()) ?? []).map((log) => ({ + id: log.id, + action: log.action, + actorType: log.actorType, + statusBefore: log.statusBefore, + statusAfter: log.statusAfter, + amount: log.amount, + approvedAmount: log.approvedAmount, + remark: log.remark, + createdAt: log.createdAt, + })), + ), + ), total, page, pageSize, diff --git a/apps/player/src/components/CashBalanceChip.vue b/apps/player/src/components/CashBalanceChip.vue index 6a42e6f..925f6ea 100644 --- a/apps/player/src/components/CashBalanceChip.vue +++ b/apps/player/src/components/CashBalanceChip.vue @@ -68,7 +68,12 @@ onUnmounted(() => { diff --git a/apps/player/src/components/FloatingMailbox.vue b/apps/player/src/components/FloatingMailbox.vue index e731dc3..27e3d48 100644 --- a/apps/player/src/components/FloatingMailbox.vue +++ b/apps/player/src/components/FloatingMailbox.vue @@ -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(() => { diff --git a/apps/player/src/components/UserAvatarMenu.vue b/apps/player/src/components/UserAvatarMenu.vue index 8a2aee4..2868a54 100644 --- a/apps/player/src/components/UserAvatarMenu.vue +++ b/apps/player/src/components/UserAvatarMenu.vue @@ -58,7 +58,12 @@ function logout() { diff --git a/apps/player/src/components/desktop/DesktopOddsBetPopover.vue b/apps/player/src/components/desktop/DesktopOddsBetPopover.vue index c933bae..604126e 100644 --- a/apps/player/src/components/desktop/DesktopOddsBetPopover.vue +++ b/apps/player/src/components/desktop/DesktopOddsBetPopover.vue @@ -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(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" > -
{{ pendingItem.matchName }}
{{ pendingItem.marketName }}
@@ -233,104 +251,146 @@ function addToParlayList() { @confirm="confirmPlaceNow" /> + + diff --git a/apps/player/src/components/match-detail/CorrectScorePanel.vue b/apps/player/src/components/match-detail/CorrectScorePanel.vue index 8a8f457..7ee9447 100644 --- a/apps/player/src/components/match-detail/CorrectScorePanel.vue +++ b/apps/player/src/components/match-detail/CorrectScorePanel.vue @@ -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) { {{ t('bet.col_away') }} -
+
- -

{{ t('recharge.audit_title') }}

- -
-
- - {{ selectedOrder.methodType }} - - - {{ statusLabel(selectedOrder.status) }} - -
-
{{ formatMoney(selectedOrder.amount, locale) }}
-
- {{ t('recharge.credited') }}: {{ formatMoney(selectedOrder.approvedAmount, locale) }} -
-
{{ selectedOrder.paymentMethodName || '-' }}
-
- {{ t('recharge.apply_time') }} - {{ formatOrderTime(selectedOrder.createdAt) }} -
-
- {{ t('recharge.review_time') }} - {{ formatOrderTime(selectedOrder.reviewedAt) }} -
-
- {{ orderNoteLine(selectedOrder) }} -
-
- -
-

{{ t('recharge.audit_title') }}

-
-
- -
-
- {{ auditActionLabel(entry.log.action) }} - -
-

{{ entry.actor }}

-

- {{ t('recharge.audit_credited') }} {{ formatMoney(entry.log.approvedAmount, locale) }} -

-
- {{ t('recharge.reject_reason') }} - {{ entry.remark.text }} -
-

- {{ auditNoteDisplayText(entry.remark) }} -

-
-
-
-
- - -
-
-
@@ -435,9 +243,7 @@ function auditStepCount(order: DepositOrder) { cursor: pointer; -webkit-tap-highlight-color: transparent; } -.order-card:active { - opacity: 0.92; -} +.order-card:active { opacity: 0.92; } .order-card::before { content: ''; position: absolute; @@ -453,14 +259,9 @@ function auditStepCount(order: DepositOrder) { background: linear-gradient(90deg, transparent, rgba(245, 108, 108, 0.35), transparent); } .order-card.rejected .order-amount { - background: none; - -webkit-background-clip: unset; - background-clip: unset; color: #bbb; } -.order-card.rejected .info-label { - color: #888; -} +.order-card.rejected .info-label { color: #888; } .order-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; } .method-badge { padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 700; } .method-badge.bank { background: rgba(30, 58, 95, 0.6); color: #66b1ff; } @@ -469,15 +270,18 @@ function auditStepCount(order: DepositOrder) { .status-pending { color: #e6a23c; } .status-approved { color: #67c23a; } .status-rejected { color: #f56c6c; } -.order-body { } .order-amount { font-size: 22px; font-weight: 900; margin-bottom: 4px; - background: linear-gradient(135deg, #f0d060, #d4a830); - -webkit-background-clip: text; - background-clip: text; - color: transparent; + color: var(--text); +} +.order-no { + font-size: 11px; + color: var(--text-muted); + font-family: ui-monospace, monospace; + margin-bottom: 6px; + letter-spacing: 0.02em; } .approved-amount { font-size: 12px; color: #67c23a; margin-bottom: 6px; font-weight: 600; } .order-info-row { margin-bottom: 8px; } @@ -520,39 +324,17 @@ function auditStepCount(order: DepositOrder) { } .card-detail-summary { font-size: 11px; - color: rgba(212, 175, 55, 0.65); + color: var(--text-muted); font-weight: 600; } .card-detail-link { font-size: 11px; - color: var(--primary-light); + color: var(--text-muted); font-weight: 700; white-space: nowrap; } -.card-detail-link-only { - display: block; - width: 100%; - margin-top: 10px; - padding: 0; - border: none; - background: none; - text-align: right; - font-size: 11px; - color: var(--primary-light); - font-weight: 700; - cursor: pointer; -} - -.sentinel { - height: 1px; -} - -.load-more-spinner { - display: flex; - justify-content: center; - padding: 20px 0 8px; -} - +.sentinel { height: 1px; } +.load-more-spinner { display: flex; justify-content: center; padding: 20px 0 8px; } .end-hint { text-align: center; font-size: 12px; @@ -561,291 +343,4 @@ function auditStepCount(order: DepositOrder) { padding: 16px 0 4px; letter-spacing: 0.03em; } - -.detail-overlay { - position: fixed; - inset: 0; - z-index: 200; - background: rgba(0, 0, 0, 0.48); - display: flex; - align-items: flex-end; - justify-content: center; - padding: 0; -} - -.detail-modal { - position: relative; - width: 100%; - max-width: 480px; - max-height: min(88vh, 720px); - overflow-y: auto; - background: #141414; - border: 1px solid #2a2a2a; - border-bottom: none; - border-radius: 14px 14px 0 0; - padding: 16px 16px calc(14px + env(safe-area-inset-bottom, 0px)); - box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.28); -} - -.detail-close { - position: absolute; - top: 12px; - right: 12px; - width: 28px; - height: 28px; - border: none; - border-radius: 50%; - background: transparent; - color: #777; - font-size: 13px; - cursor: pointer; - line-height: 1; -} - -.detail-title { - margin: 0 28px 12px 0; - font-size: 15px; - font-weight: 600; - color: #e8e8e8; -} - -.detail-summary { - margin-bottom: 14px; - padding: 12px; - border-radius: 10px; - background: rgba(255, 255, 255, 0.02); - border: 1px solid #222; -} - -.detail-summary-head { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 10px; -} - -.detail-amount { - font-size: 22px; - font-weight: 700; - margin-bottom: 4px; - color: var(--primary-light, #d4af37); - letter-spacing: -0.02em; -} - -.detail-method-name { - font-size: 12px; - color: #999; - font-weight: 500; - margin-bottom: 8px; -} - -.detail-row { - display: flex; - justify-content: space-between; - align-items: flex-start; - gap: 12px; - margin-bottom: 6px; -} - -.detail-label { - font-size: 12px; - color: #888; - flex-shrink: 0; -} - -.detail-value { - font-size: 12px; - color: #ccc; - text-align: right; - word-break: break-word; -} - -.detail-summary .order-remark { - margin-top: 8px; - font-size: 11px; - color: #999; - background: transparent; - padding: 6px 0 0; - border-radius: 0; - border: none; - border-top: 1px solid #222; - line-height: 1.45; - word-break: break-word; -} - -.detail-summary .reject-reason { - margin-top: 8px; - font-size: 11px; - color: #b08888; - background: transparent; - padding: 6px 0 0; - border-radius: 0; - border: none; - border-top: 1px solid rgba(245, 108, 108, 0.15); - line-height: 1.45; - word-break: break-word; -} - -.detail-summary .approved-amount { - font-size: 11px; - font-weight: 500; -} - -.detail-audit { - margin-top: 2px; - padding-top: 12px; - border-top: 1px solid #222; -} - -.detail-audit-title { - margin: 0 0 10px; - font-size: 12px; - font-weight: 600; - color: #888; - letter-spacing: 0.02em; -} - -.audit-track { - display: flex; - flex-direction: column; - gap: 0; -} - -.audit-step { - display: flex; - gap: 8px; - min-height: 0; -} - -.audit-step-rail { - flex: 0 0 10px; - display: flex; - flex-direction: column; - align-items: center; - padding-top: 4px; -} - -.audit-dot { - width: 5px; - height: 5px; - border-radius: 50%; - background: #555; - flex-shrink: 0; -} - -.audit-line { - flex: 1; - width: 1px; - min-height: 10px; - margin: 3px 0; - background: #2a2a2a; -} - -.audit-step-body { - flex: 1; - min-width: 0; - padding-bottom: 12px; -} - -.audit-step:last-child .audit-step-body { - padding-bottom: 0; -} - -.audit-step-head { - display: flex; - align-items: baseline; - justify-content: space-between; - gap: 8px; -} - -.audit-step-title { - font-size: 12px; - font-weight: 600; - color: #ccc; - line-height: 1.35; -} - -.audit-step-time { - font-size: 10px; - color: #666; - font-variant-numeric: tabular-nums; - white-space: nowrap; - flex-shrink: 0; -} - -.audit-step-actor { - margin: 2px 0 0; - font-size: 11px; - color: #999; - line-height: 1.4; -} - -.audit-step-credited { - margin: 3px 0 0; - font-size: 11px; - font-weight: 500; - color: #7eb87a; - line-height: 1.4; -} - -.audit-step-note { - margin: 4px 0 0; - font-size: 11px; - color: #888; - line-height: 1.45; - word-break: break-word; -} - -.audit-step-box { - margin-top: 4px; - padding: 6px 8px; - border-radius: 6px; - display: flex; - flex-direction: column; - gap: 1px; - line-height: 1.4; - word-break: break-word; -} - -.audit-step-box--reject { - background: transparent; - border: 1px solid rgba(245, 108, 108, 0.22); -} - -.audit-step-box-label { - font-size: 10px; - font-weight: 500; - color: #c07070; - letter-spacing: 0.01em; -} - -.audit-step-box-text { - font-size: 11px; - color: #b08888; -} - -.audit-step--submitted .audit-dot { - background: #c9a227; -} -.audit-step--approved .audit-dot { - background: #5fad5a; -} -.audit-step--rejected .audit-dot { - background: #d06060; -} -.audit-step--revoked .audit-dot { - background: #777; -} -.audit-step--reopened .audit-dot { - background: #c9a227; -} - -.modal-reapply-btn { - width: 100%; - margin-top: 14px; - padding: 11px 16px; - font-size: 13px; - font-weight: 600; - cursor: pointer; -} diff --git a/apps/player/src/views/MobileRechargeView.vue b/apps/player/src/views/MobileRechargeView.vue index 850b9c4..77cead2 100644 --- a/apps/player/src/views/MobileRechargeView.vue +++ b/apps/player/src/views/MobileRechargeView.vue @@ -681,7 +681,7 @@ onMounted(fetchMethods); .success-state { text-align: center; padding: 40px 16px; } .success-icon { font-size: 40px; color: #67c23a; margin-bottom: 10px; } .success-state h3 { margin: 0 0 6px; font-size: 16px; } -.order-no { font-family: monospace; color: var(--primary-light); font-size: 13px; margin: 4px 0; } +.order-no { font-family: monospace; color: var(--text-muted); font-size: 13px; margin: 4px 0; } .success-hint { font-size: 12px; color: var(--text-muted); margin-bottom: 20px; } .btn-primary { background: linear-gradient(135deg, #f0d060, #d4a830); diff --git a/apps/player/src/views/MobileWalletTransactionDetailView.vue b/apps/player/src/views/MobileWalletTransactionDetailView.vue index eb8e194..65b13fe 100644 --- a/apps/player/src/views/MobileWalletTransactionDetailView.vue +++ b/apps/player/src/views/MobileWalletTransactionDetailView.vue @@ -370,9 +370,9 @@ function goCashbackDetail() { margin-top: 12px; padding: 10px 14px; border-radius: 10px; - border: 1px solid var(--border-gold-soft, rgba(212, 175, 55, 0.35)); - background: rgba(212, 175, 55, 0.08); - color: var(--primary-light); + border: 1px solid rgba(255, 255, 255, 0.12); + background: rgba(255, 255, 255, 0.04); + color: var(--text); font-size: 13px; font-weight: 700; cursor: pointer; diff --git a/apps/player/src/views/RechargeDetailView.vue b/apps/player/src/views/RechargeDetailView.vue new file mode 100644 index 0000000..92f4b5d --- /dev/null +++ b/apps/player/src/views/RechargeDetailView.vue @@ -0,0 +1,12 @@ + + + diff --git a/apps/player/src/views/desktop/DesktopFootballView.vue b/apps/player/src/views/desktop/DesktopFootballView.vue index 10d6362..e70f05f 100644 --- a/apps/player/src/views/desktop/DesktopFootballView.vue +++ b/apps/player/src/views/desktop/DesktopFootballView.vue @@ -209,13 +209,16 @@ void loadParlayMatches(true);

{{ lg.leagueName }}

- + + +
@@ -350,46 +353,86 @@ void loadParlayMatches(true); } .match-grid { + display: contents; +} + +.match-grid-inner { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 8px; } -.match-grid :deep(.match-card) { +/* --- Card list entrance animation --- */ +.card-list-enter-active { + animation: card-fade-up 0.4s cubic-bezier(0.22, 1, 0.36, 1) both; + animation-delay: calc(var(--i, 0) * 50ms); +} + +.card-list-leave-active { + animation: card-fade-down 0.25s ease both; +} + +.card-list-move { + transition: transform 0.35s ease; +} + +@keyframes card-fade-up { + from { + opacity: 0; + transform: translateY(16px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes card-fade-down { + from { + opacity: 1; + transform: translateY(0); + } + to { + opacity: 0; + transform: translateY(-8px); + } +} + +.match-grid-inner :deep(.match-card) { padding: 8px 6px; gap: 6px; } -.match-grid :deep(.team-name) { +.match-grid-inner :deep(.team-name) { font-size: 13px; } -.match-grid :deep(.team-flag) { +.match-grid-inner :deep(.team-flag) { width: 56px; height: 38px; } -.match-grid :deep(.team-flag.flag-logo) { +.match-grid-inner :deep(.team-flag.flag-logo) { width: 44px; height: 44px; object-fit: contain; } -.match-grid :deep(.kickoff) { +.match-grid-inner :deep(.kickoff) { font-size: 10px; } -.match-grid :deep(.vs) { +.match-grid-inner :deep(.vs) { font-size: 11px; } -.match-grid :deep(.bet-btn) { +.match-grid-inner :deep(.bet-btn) { font-size: 11px; padding: 5px 10px; min-height: 28px; } -.match-grid :deep(.status-tag) { +.match-grid-inner :deep(.status-tag) { font-size: 9px; padding: 2px 6px; } diff --git a/apps/player/src/views/desktop/DesktopHomeView.vue b/apps/player/src/views/desktop/DesktopHomeView.vue index 372d296..b33b5bf 100644 --- a/apps/player/src/views/desktop/DesktopHomeView.vue +++ b/apps/player/src/views/desktop/DesktopHomeView.vue @@ -22,7 +22,7 @@ const { banners, hotMatches, upcomingMatches, loading, load, announcementItems } const slip = useBetSlipStore(); const auth = useAuthStore(); -const { openAt, visible: popoverVisible, pendingItem } = useDesktopBetPopover(); +const { openAt, visible: popoverVisible, pendingItem, cancelClose, scheduleClose } = useDesktopBetPopover(); const activeMarketTypes = ref>({}); @@ -140,6 +140,37 @@ function handleOddsClick(match: PlayerHomeMatch, market: any, selId: string, eve function getSelLabel(sel: any, marketType: string, lineValue?: string | number | null) { return resolveSelectionLabel(t, sel.selectionCode || '', sel.selectionName || '', { lineValue }); } + +function getHoveredSideForMatch(match: PlayerHomeMatch | null) { + if (!match || !popoverVisible.value || !pendingItem.value) return ''; + if (String(pendingItem.value.matchId) !== String(match.id)) return ''; + + const selId = pendingItem.value.selectionId; + // Scan all markets across all tabs to find the selection + for (const market of 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'; + if (code === 'OVER') return 'home'; + if (code === 'UNDER') return 'away'; + 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 ''; +}