fix(player): 优化桌面快速下注 hover 并修复充值 API 重复方法
删除 deposit.service 中重复的 getPlayerDepositOrder,避免 API 编译失败引发 500;快速下注弹层按卡片实例激活,同场赛事不再多处联动,移入快速下注区域时保持 VS 动画;记录列表改为整行可点,编号统一中性色样式。
This commit is contained in:
@@ -601,47 +601,6 @@ 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) {
|
async getPlayerDepositOrders(playerId: bigint, page = 1, pageSize = 20) {
|
||||||
const skip = (page - 1) * pageSize;
|
const skip = (page - 1) * pageSize;
|
||||||
const where = { playerId };
|
const where = { playerId };
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed, getCurrentInstance } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { teamFlagUrl } from '../utils/teamFlag';
|
import { teamFlagUrl } from '../utils/teamFlag';
|
||||||
@@ -82,10 +82,15 @@ const liveScoreText = computed(() => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const slip = useBetSlipStore();
|
const slip = useBetSlipStore();
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
const { openAt, visible: popoverVisible, pendingItem, cancelClose, scheduleClose } = useDesktopBetPopover();
|
const { openAt, visible: popoverVisible, pendingItem, cancelClose, scheduleClose, isActiveForCard } = useDesktopBetPopover();
|
||||||
|
|
||||||
|
const cardRootId = `bet-card-${props.match.id}-${getCurrentInstance()?.uid ?? 0}`;
|
||||||
|
|
||||||
|
const isCardEngaged = computed(() => isActiveForCard(cardRootId));
|
||||||
|
|
||||||
function getHoveredSide() {
|
function getHoveredSide() {
|
||||||
if (!popoverVisible.value || !pendingItem.value) return '';
|
if (!popoverVisible.value || !pendingItem.value) return '';
|
||||||
|
if (!isActiveForCard(cardRootId)) return '';
|
||||||
if (String(pendingItem.value.matchId) !== String(props.match.id)) return '';
|
if (String(pendingItem.value.matchId) !== String(props.match.id)) return '';
|
||||||
|
|
||||||
const selId = pendingItem.value.selectionId;
|
const selId = pendingItem.value.selectionId;
|
||||||
@@ -199,8 +204,15 @@ function goMatchDetail() {
|
|||||||
<template>
|
<template>
|
||||||
<article
|
<article
|
||||||
class="match-card"
|
class="match-card"
|
||||||
:class="{ 'match-card--phase': phase !== 'open', 'no-quick-bet': noQuickBet }"
|
:data-bet-card-root="cardRootId"
|
||||||
|
:class="{
|
||||||
|
'match-card--phase': phase !== 'open',
|
||||||
|
'no-quick-bet': noQuickBet,
|
||||||
|
'match-card--engaged': isCardEngaged,
|
||||||
|
}"
|
||||||
@click="emit('bet', match.id)"
|
@click="emit('bet', match.id)"
|
||||||
|
@mouseenter="cancelClose"
|
||||||
|
@mouseleave="scheduleClose()"
|
||||||
>
|
>
|
||||||
<span v-if="phase === 'open'" class="status-tag status-tag--open">{{ t('bet.status_open') }}</span>
|
<span v-if="phase === 'open'" class="status-tag status-tag--open">{{ t('bet.status_open') }}</span>
|
||||||
<span v-else-if="phase === 'settled'" class="status-tag status-tag--settled">{{ phaseLabel }}</span>
|
<span v-else-if="phase === 'settled'" class="status-tag status-tag--settled">{{ phaseLabel }}</span>
|
||||||
@@ -250,7 +262,7 @@ function goMatchDetail() {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Morphing Quick Bet panel (Desktop Only, disabled when noQuickBet=true) -->
|
<!-- Morphing Quick Bet panel (Desktop Only, disabled when noQuickBet=true) -->
|
||||||
<div v-if="!noQuickBet" class="card-quick-bet" @click.stop @mouseenter="cancelClose" @mouseleave="scheduleClose()">
|
<div v-if="!noQuickBet" class="card-quick-bet" @click.stop>
|
||||||
<div class="quick-bet-tabs" v-if="getAvailableMarketsForMatch(match).length">
|
<div class="quick-bet-tabs" v-if="getAvailableMarketsForMatch(match).length">
|
||||||
<button
|
<button
|
||||||
v-for="m in getAvailableMarketsForMatch(match)"
|
v-for="m in getAvailableMarketsForMatch(match)"
|
||||||
@@ -486,36 +498,36 @@ function goMatchDetail() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
.match-card:not(.no-quick-bet):hover {
|
.match-card:not(.no-quick-bet):is(:hover, .match-card--engaged) {
|
||||||
border-color: var(--border-gold);
|
border-color: var(--border-gold);
|
||||||
box-shadow: var(--shadow-gold);
|
box-shadow: var(--shadow-gold);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 整行保持原位,间距自然 */
|
/* 整行保持原位,间距自然 */
|
||||||
.match-card:not(.no-quick-bet):hover .teams-row {
|
.match-card:not(.no-quick-bet):is(:hover, .match-card--engaged) .teams-row {
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
padding: 0 40px;
|
padding: 0 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* .team 取消自身 translateY */
|
/* .team 取消自身 translateY */
|
||||||
.match-card:not(.no-quick-bet):hover .team {
|
.match-card:not(.no-quick-bet):is(:hover, .match-card--engaged) .team {
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
gap: 3px;
|
gap: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 旗帜缩小到 40×28 */
|
/* 旗帜缩小到 40×28 */
|
||||||
.match-card:not(.no-quick-bet):hover .team-flag {
|
.match-card:not(.no-quick-bet):is(:hover, .match-card--engaged) .team-flag {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.match-card:not(.no-quick-bet):hover .team-flag.flag-logo {
|
.match-card:not(.no-quick-bet):is(:hover, .match-card--engaged) .team-flag.flag-logo {
|
||||||
width: 28px;
|
width: 28px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 队名缩小并禁止换行 */
|
/* 队名缩小并禁止换行 */
|
||||||
.match-card:not(.no-quick-bet):hover .team-name {
|
.match-card:not(.no-quick-bet):is(:hover, .match-card--engaged) .team-name {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -524,7 +536,7 @@ function goMatchDetail() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 隐藏开赛时间 */
|
/* 隐藏开赛时间 */
|
||||||
.match-card:not(.no-quick-bet):hover .kickoff {
|
.match-card:not(.no-quick-bet):is(:hover, .match-card--engaged) .kickoff {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -533,19 +545,19 @@ function goMatchDetail() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* VS / 比分 */
|
/* VS / 比分 */
|
||||||
.match-card:not(.no-quick-bet):hover .vs,
|
.match-card:not(.no-quick-bet):is(:hover, .match-card--engaged) .vs,
|
||||||
.match-card:not(.no-quick-bet):hover .live-score {
|
.match-card:not(.no-quick-bet):is(:hover, .match-card--engaged) .live-score {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 下注按钮淡出(保留布局空间避免高度抖动) */
|
/* 下注按钮淡出(保留布局空间避免高度抖动) */
|
||||||
.match-card:not(.no-quick-bet):hover .bet-btn {
|
.match-card:not(.no-quick-bet):is(:hover, .match-card--engaged) .bet-btn {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 快速下注面板淡入 */
|
/* 快速下注面板淡入 */
|
||||||
.match-card:not(.no-quick-bet):hover .card-quick-bet {
|
.match-card:not(.no-quick-bet):is(:hover, .match-card--engaged) .card-quick-bet {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const emit = defineEmits<{
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { scheduleClose, cancelClose } = useDesktopBetPopover();
|
const { cancelClose } = useDesktopBetPopover();
|
||||||
|
|
||||||
const columns = computed(() =>
|
const columns = computed(() =>
|
||||||
groupCorrectScoreSelections(
|
groupCorrectScoreSelections(
|
||||||
@@ -51,14 +51,18 @@ function formatOdds(odds: string) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="cs-panel" :class="{ 'cs-panel--locked': locked, 'cs-panel--dense': dense }">
|
<div
|
||||||
|
class="cs-panel"
|
||||||
|
:class="{ 'cs-panel--locked': locked, 'cs-panel--dense': dense }"
|
||||||
|
@mouseenter="cancelClose"
|
||||||
|
>
|
||||||
<div class="cols-head">
|
<div class="cols-head">
|
||||||
<span>{{ t('bet.col_home') }}</span>
|
<span>{{ t('bet.col_home') }}</span>
|
||||||
<span>{{ t('bet.col_draw') }}</span>
|
<span>{{ t('bet.col_draw') }}</span>
|
||||||
<span>{{ t('bet.col_away') }}</span>
|
<span>{{ t('bet.col_away') }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="cols-grid" @mouseenter="cancelClose" @mouseleave="scheduleClose()">
|
<div class="cols-grid">
|
||||||
<div v-for="colKey in ['home', 'draw', 'away'] as const" :key="colKey" class="col">
|
<div v-for="colKey in ['home', 'draw', 'away'] as const" :key="colKey" class="col">
|
||||||
<button
|
<button
|
||||||
v-for="sel in columns[colKey]"
|
v-for="sel in columns[colKey]"
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const emit = defineEmits<{ pick: [id: string, event?: MouseEvent] }>();
|
const emit = defineEmits<{ pick: [id: string, event?: MouseEvent] }>();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { scheduleClose, cancelClose } = useDesktopBetPopover();
|
const { cancelClose } = useDesktopBetPopover();
|
||||||
|
|
||||||
function label(sel: (typeof props.selections)[number]) {
|
function label(sel: (typeof props.selections)[number]) {
|
||||||
if (sel.selectionDisplayName?.trim()) return sel.selectionDisplayName;
|
if (sel.selectionDisplayName?.trim()) return sel.selectionDisplayName;
|
||||||
@@ -56,7 +56,7 @@ const panelStyle = computed(() =>
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="wrap" :class="{ compact, horizontal, desktopCard, locked }" @mouseenter="cancelClose" @mouseleave="scheduleClose()">
|
<div class="wrap" :class="{ compact, horizontal, desktopCard, locked }" @mouseenter="cancelClose">
|
||||||
<div
|
<div
|
||||||
class="panel"
|
class="panel"
|
||||||
:class="{ horizontal, 'desktop-card': desktopCard }"
|
:class="{ horizontal, 'desktop-card': desktopCard }"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const visible = ref(false);
|
|||||||
const anchorX = ref(0);
|
const anchorX = ref(0);
|
||||||
const anchorY = ref(0);
|
const anchorY = ref(0);
|
||||||
const pendingItem = ref<SlipItem | null>(null);
|
const pendingItem = ref<SlipItem | null>(null);
|
||||||
|
const activeCardRootId = ref<string | null>(null);
|
||||||
const placement = ref<'top' | 'bottom'>('bottom');
|
const placement = ref<'top' | 'bottom'>('bottom');
|
||||||
let closeTimer: number | undefined = undefined;
|
let closeTimer: number | undefined = undefined;
|
||||||
|
|
||||||
@@ -45,12 +46,15 @@ export function useDesktopBetPopover() {
|
|||||||
anchorX.value = x;
|
anchorX.value = x;
|
||||||
anchorY.value = y;
|
anchorY.value = y;
|
||||||
pendingItem.value = item;
|
pendingItem.value = item;
|
||||||
|
const root = el?.closest?.('[data-bet-card-root]') as HTMLElement | null;
|
||||||
|
activeCardRootId.value = root?.dataset.betCardRoot ?? null;
|
||||||
visible.value = true;
|
visible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
visible.value = false;
|
visible.value = false;
|
||||||
pendingItem.value = null;
|
pendingItem.value = null;
|
||||||
|
activeCardRootId.value = null;
|
||||||
if (closeTimer !== undefined) {
|
if (closeTimer !== undefined) {
|
||||||
window.clearTimeout(closeTimer);
|
window.clearTimeout(closeTimer);
|
||||||
closeTimer = undefined;
|
closeTimer = undefined;
|
||||||
@@ -64,6 +68,7 @@ export function useDesktopBetPopover() {
|
|||||||
closeTimer = window.setTimeout(() => {
|
closeTimer = window.setTimeout(() => {
|
||||||
visible.value = false;
|
visible.value = false;
|
||||||
pendingItem.value = null;
|
pendingItem.value = null;
|
||||||
|
activeCardRootId.value = null;
|
||||||
closeTimer = undefined;
|
closeTimer = undefined;
|
||||||
}, delay);
|
}, delay);
|
||||||
}
|
}
|
||||||
@@ -75,15 +80,22 @@ export function useDesktopBetPopover() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isActiveForCard(cardRootId: string | null | undefined) {
|
||||||
|
if (!cardRootId || !visible.value || !pendingItem.value) return false;
|
||||||
|
return activeCardRootId.value === cardRootId;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
visible,
|
visible,
|
||||||
anchorX,
|
anchorX,
|
||||||
anchorY,
|
anchorY,
|
||||||
pendingItem,
|
pendingItem,
|
||||||
|
activeCardRootId,
|
||||||
placement,
|
placement,
|
||||||
openAt,
|
openAt,
|
||||||
close,
|
close,
|
||||||
scheduleClose,
|
scheduleClose,
|
||||||
cancelClose,
|
cancelClose,
|
||||||
|
isActiveForCard,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,24 +208,23 @@
|
|||||||
transition: background 0.15s;
|
transition: background 0.15s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.desktop-records-table tbody tr.hover-row:hover {
|
.desktop-records-table tbody tr.clickable-row {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop-records-table tbody tr.hover-row:hover,
|
||||||
|
.desktop-records-table tbody tr.clickable-row:hover {
|
||||||
background: rgba(255, 255, 255, 0.04);
|
background: rgba(255, 255, 255, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
.desktop-records-table .link-cell {
|
.desktop-records-table .id-cell {
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-decoration: none;
|
|
||||||
font-family: 'SF Mono', 'Consolas', monospace;
|
font-family: 'SF Mono', 'Consolas', monospace;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
letter-spacing: 0.03em;
|
letter-spacing: 0.03em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.desktop-records-table .link-cell:hover {
|
|
||||||
color: #fff;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.desktop-records-table .num-cell {
|
.desktop-records-table .num-cell {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const { banners, hotMatches, upcomingMatches, loading, load, announcementItems }
|
|||||||
|
|
||||||
const slip = useBetSlipStore();
|
const slip = useBetSlipStore();
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
const { openAt, visible: popoverVisible, pendingItem, cancelClose, scheduleClose } = useDesktopBetPopover();
|
const { openAt, visible: popoverVisible, pendingItem, cancelClose, scheduleClose, isActiveForCard } = useDesktopBetPopover();
|
||||||
|
|
||||||
const activeMarketTypes = ref<Record<string, string>>({});
|
const activeMarketTypes = ref<Record<string, string>>({});
|
||||||
|
|
||||||
@@ -141,8 +141,9 @@ function getSelLabel(sel: any, marketType: string, lineValue?: string | number |
|
|||||||
return resolveSelectionLabel(t, sel.selectionCode || '', sel.selectionName || '', { lineValue });
|
return resolveSelectionLabel(t, sel.selectionCode || '', sel.selectionName || '', { lineValue });
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHoveredSideForMatch(match: PlayerHomeMatch | null) {
|
function getHoveredSideForMatch(match: PlayerHomeMatch | null, cardRootId: string) {
|
||||||
if (!match || !popoverVisible.value || !pendingItem.value) return '';
|
if (!match || !popoverVisible.value || !pendingItem.value) return '';
|
||||||
|
if (!isActiveForCard(cardRootId)) return '';
|
||||||
if (String(pendingItem.value.matchId) !== String(match.id)) return '';
|
if (String(pendingItem.value.matchId) !== String(match.id)) return '';
|
||||||
|
|
||||||
const selId = pendingItem.value.selectionId;
|
const selId = pendingItem.value.selectionId;
|
||||||
@@ -203,7 +204,15 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null) {
|
|||||||
|
|
||||||
<template v-else-if="featuredMatch">
|
<template v-else-if="featuredMatch">
|
||||||
<!-- 焦点大卡 -->
|
<!-- 焦点大卡 -->
|
||||||
<div class="featured-match anim-fade-up" style="--anim-delay:240ms" @click.self="goMatch(featuredMatch.id)">
|
<div
|
||||||
|
class="featured-match anim-fade-up"
|
||||||
|
:class="{ 'featured-match--engaged': isActiveForCard(`featured-${featuredMatch.id}`) }"
|
||||||
|
:data-bet-card-root="`featured-${featuredMatch.id}`"
|
||||||
|
style="--anim-delay:240ms"
|
||||||
|
@click.self="goMatch(featuredMatch.id)"
|
||||||
|
@mouseenter="cancelClose"
|
||||||
|
@mouseleave="scheduleClose()"
|
||||||
|
>
|
||||||
<div class="featured-top" @click="goMatch(featuredMatch.id)">
|
<div class="featured-top" @click="goMatch(featuredMatch.id)">
|
||||||
<div class="featured-meta">
|
<div class="featured-meta">
|
||||||
<span class="league-tag lg">{{ featuredMatch.leagueName }}</span>
|
<span class="league-tag lg">{{ featuredMatch.leagueName }}</span>
|
||||||
@@ -215,7 +224,7 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null) {
|
|||||||
<span class="featured-time">{{ formatKickoff(featuredMatch.startTime) }}</span>
|
<span class="featured-time">{{ formatKickoff(featuredMatch.startTime) }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="featured-teams" :class="getHoveredSideForMatch(featuredMatch)" @click="goMatch(featuredMatch.id)">
|
<div class="featured-teams" :class="getHoveredSideForMatch(featuredMatch, `featured-${featuredMatch.id}`)" @click="goMatch(featuredMatch.id)">
|
||||||
<div class="featured-team">
|
<div class="featured-team">
|
||||||
<TeamEmblem
|
<TeamEmblem
|
||||||
size="lg"
|
size="lg"
|
||||||
@@ -266,7 +275,11 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null) {
|
|||||||
v-for="(match, idx) in restHotMatches"
|
v-for="(match, idx) in restHotMatches"
|
||||||
:key="match.id"
|
:key="match.id"
|
||||||
class="match-card anim-fade-up"
|
class="match-card anim-fade-up"
|
||||||
|
:class="{ 'match-card--engaged': isActiveForCard(`hot-${match.id}`) }"
|
||||||
|
:data-bet-card-root="`hot-${match.id}`"
|
||||||
:style="{ '--anim-delay': (320 + idx * 60) + 'ms' }"
|
:style="{ '--anim-delay': (320 + idx * 60) + 'ms' }"
|
||||||
|
@mouseenter="cancelClose"
|
||||||
|
@mouseleave="scheduleClose()"
|
||||||
>
|
>
|
||||||
<div class="match-info-side" @click="goMatch(match.id)">
|
<div class="match-info-side" @click="goMatch(match.id)">
|
||||||
<!-- Default info layout (visible at rest) -->
|
<!-- Default info layout (visible at rest) -->
|
||||||
@@ -302,7 +315,7 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Hover versus layout (visible on hover) -->
|
<!-- Hover versus layout (visible on hover) -->
|
||||||
<div class="hover-versus-layout" :class="getHoveredSideForMatch(match)">
|
<div class="hover-versus-layout" :class="getHoveredSideForMatch(match, `hot-${match.id}`)">
|
||||||
<div class="hover-team home-slide">
|
<div class="hover-team home-slide">
|
||||||
<TeamEmblem
|
<TeamEmblem
|
||||||
size="md"
|
size="md"
|
||||||
@@ -375,7 +388,11 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null) {
|
|||||||
v-for="(match, idx) in upcomingList"
|
v-for="(match, idx) in upcomingList"
|
||||||
:key="match.id"
|
:key="match.id"
|
||||||
class="match-card anim-fade-up"
|
class="match-card anim-fade-up"
|
||||||
|
:class="{ 'match-card--engaged': isActiveForCard(`upcoming-${match.id}`) }"
|
||||||
|
:data-bet-card-root="`upcoming-${match.id}`"
|
||||||
:style="{ '--anim-delay': (260 + idx * 50) + 'ms' }"
|
:style="{ '--anim-delay': (260 + idx * 50) + 'ms' }"
|
||||||
|
@mouseenter="cancelClose"
|
||||||
|
@mouseleave="scheduleClose()"
|
||||||
>
|
>
|
||||||
<div class="match-info-side" @click="goMatch(match.id)">
|
<div class="match-info-side" @click="goMatch(match.id)">
|
||||||
<!-- Default info layout (visible at rest) -->
|
<!-- Default info layout (visible at rest) -->
|
||||||
@@ -410,7 +427,7 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Hover versus layout (visible on hover) -->
|
<!-- Hover versus layout (visible on hover) -->
|
||||||
<div class="hover-versus-layout" :class="getHoveredSideForMatch(match)">
|
<div class="hover-versus-layout" :class="getHoveredSideForMatch(match, `upcoming-${match.id}`)">
|
||||||
<div class="hover-team home-slide">
|
<div class="hover-team home-slide">
|
||||||
<TeamEmblem
|
<TeamEmblem
|
||||||
size="md"
|
size="md"
|
||||||
@@ -483,8 +500,12 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null) {
|
|||||||
v-for="(m, idx) in sideRecommendMatches"
|
v-for="(m, idx) in sideRecommendMatches"
|
||||||
:key="m.id"
|
:key="m.id"
|
||||||
class="side-recommend-item anim-fade-up"
|
class="side-recommend-item anim-fade-up"
|
||||||
|
:class="{ 'side-recommend-item--engaged': isActiveForCard(`sidebar-${m.id}`) }"
|
||||||
|
:data-bet-card-root="`sidebar-${m.id}`"
|
||||||
:style="{ '--anim-delay': (200 + idx * 70) + 'ms' }"
|
:style="{ '--anim-delay': (200 + idx * 70) + 'ms' }"
|
||||||
@click="goMatch(m.id)"
|
@click="goMatch(m.id)"
|
||||||
|
@mouseenter="cancelClose"
|
||||||
|
@mouseleave="scheduleClose()"
|
||||||
>
|
>
|
||||||
<div class="sri-top-row">
|
<div class="sri-top-row">
|
||||||
<span class="sri-league">{{ m.leagueName }}</span>
|
<span class="sri-league">{{ m.leagueName }}</span>
|
||||||
@@ -502,7 +523,7 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null) {
|
|||||||
<span class="sri-preview-league">{{ m.leagueName }}</span>
|
<span class="sri-preview-league">{{ m.leagueName }}</span>
|
||||||
<span class="sri-preview-time">{{ formatKickoff(m.startTime) }}</span>
|
<span class="sri-preview-time">{{ formatKickoff(m.startTime) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="sri-preview-vs" :class="getHoveredSideForMatch(m)">
|
<div class="sri-preview-vs" :class="getHoveredSideForMatch(m, `sidebar-${m.id}`)">
|
||||||
<div class="sri-preview-team home-team">
|
<div class="sri-preview-team home-team">
|
||||||
<TeamEmblem size="md" :team-code="m.homeTeamCode" :team-name="m.homeTeamName" :logo-url="m.homeTeamLogoUrl" />
|
<TeamEmblem size="md" :team-code="m.homeTeamCode" :team-name="m.homeTeamName" :logo-url="m.homeTeamLogoUrl" />
|
||||||
<span>{{ m.homeTeamName }}</span>
|
<span>{{ m.homeTeamName }}</span>
|
||||||
@@ -814,7 +835,7 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null) {
|
|||||||
min-height: 76px;
|
min-height: 76px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.match-card:hover {
|
.match-card:is(:hover, .match-card--engaged) {
|
||||||
border-color: var(--border-gold);
|
border-color: var(--border-gold);
|
||||||
box-shadow: var(--shadow-gold);
|
box-shadow: var(--shadow-gold);
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
@@ -840,7 +861,7 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null) {
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.match-card:hover .match-info-side {
|
.match-card:is(:hover, .match-card--engaged) .match-info-side {
|
||||||
background: rgba(255, 255, 255, 0.02);
|
background: rgba(255, 255, 255, 0.02);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1186,7 +1207,7 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null) {
|
|||||||
transition: opacity 0.35s cubic-bezier(0.25, 0.8, 0.25, 1), transform 0.35s cubic-bezier(0.25, 0.8, 0.25, 1);
|
transition: opacity 0.35s cubic-bezier(0.25, 0.8, 0.25, 1), transform 0.35s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.match-card:hover .default-info-layout {
|
.match-card:is(:hover, .match-card--engaged) .default-info-layout {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(-8px) scale(0.95);
|
transform: translateY(-8px) scale(0.95);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
@@ -1211,7 +1232,7 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null) {
|
|||||||
border-radius: 8px 0 0 8px;
|
border-radius: 8px 0 0 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.match-card:hover .hover-versus-layout {
|
.match-card:is(:hover, .match-card--engaged) .hover-versus-layout {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateY(0) scale(1);
|
transform: translateY(0) scale(1);
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
@@ -1229,7 +1250,7 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null) {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.match-card:hover .hover-team.home-slide {
|
.match-card:is(:hover, .match-card--engaged) .hover-team.home-slide {
|
||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
@@ -1246,7 +1267,7 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null) {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.match-card:hover .hover-team.away-slide {
|
.match-card:is(:hover, .match-card--engaged) .hover-team.away-slide {
|
||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
@@ -1276,7 +1297,7 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null) {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.match-card:hover .hover-vs-glow {
|
.match-card:is(:hover, .match-card--engaged) .hover-vs-glow {
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
animation: vs-clash-impact 0.3s ease-out 0.35s;
|
animation: vs-clash-impact 0.3s ease-out 0.35s;
|
||||||
@@ -1347,7 +1368,7 @@ function getHoveredSideForMatch(match: PlayerHomeMatch | null) {
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-recommend-item:hover .sri-preview-card {
|
.side-recommend-item:is(:hover, .side-recommend-item--engaged) .sri-preview-card {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateY(-50%) translateX(0);
|
transform: translateY(-50%) translateX(0);
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const router = useRouter();
|
|||||||
const { t, locale } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
const slip = useBetSlipStore();
|
const slip = useBetSlipStore();
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
const { openAt, visible: popoverVisible, pendingItem: popoverItem } = useDesktopBetPopover();
|
const { openAt, visible: popoverVisible, pendingItem: popoverItem, cancelClose, scheduleClose } = useDesktopBetPopover();
|
||||||
const { pendingLocate, clearLocate } = useDesktopBetLocate();
|
const { pendingLocate, clearLocate } = useDesktopBetLocate();
|
||||||
|
|
||||||
function goBack() {
|
function goBack() {
|
||||||
@@ -349,7 +349,12 @@ function getHoveredSide() {
|
|||||||
|
|
||||||
<template v-else-if="match">
|
<template v-else-if="match">
|
||||||
<!-- High Density Score Band -->
|
<!-- High Density Score Band -->
|
||||||
<div class="match-score-band" :class="[{ 'phase-settled': matchPhase === 'settled' }, getHoveredSide()]">
|
<div
|
||||||
|
class="match-score-band"
|
||||||
|
:class="[{ 'phase-settled': matchPhase === 'settled' }, getHoveredSide()]"
|
||||||
|
@mouseenter="cancelClose"
|
||||||
|
@mouseleave="scheduleClose()"
|
||||||
|
>
|
||||||
<div class="team-side home">
|
<div class="team-side home">
|
||||||
<TeamEmblem size="xl" :team-code="match.homeTeamCode" :team-name="match.homeTeamName" :logo-url="match.homeTeamLogoUrl" />
|
<TeamEmblem size="xl" :team-code="match.homeTeamCode" :team-name="match.homeTeamName" :logo-url="match.homeTeamLogoUrl" />
|
||||||
<span class="name">{{ match.homeTeamName }}</span>
|
<span class="name">{{ match.homeTeamName }}</span>
|
||||||
@@ -413,6 +418,8 @@ function getHoveredSide() {
|
|||||||
:key="market.id"
|
:key="market.id"
|
||||||
class="market-card"
|
class="market-card"
|
||||||
:class="{ locked: isMarketLocked(market) }"
|
:class="{ locked: isMarketLocked(market) }"
|
||||||
|
@mouseenter="cancelClose"
|
||||||
|
@mouseleave="scheduleClose()"
|
||||||
>
|
>
|
||||||
<div class="market-hdr">
|
<div class="market-hdr">
|
||||||
<span class="market-title">{{ marketLabel(market) }}</span>
|
<span class="market-title">{{ marketLabel(market) }}</span>
|
||||||
@@ -441,6 +448,8 @@ function getHoveredSide() {
|
|||||||
:key="market.id"
|
:key="market.id"
|
||||||
class="score-block"
|
class="score-block"
|
||||||
:class="{ locked: isMarketLocked(market) }"
|
:class="{ locked: isMarketLocked(market) }"
|
||||||
|
@mouseenter="cancelClose"
|
||||||
|
@mouseleave="scheduleClose()"
|
||||||
>
|
>
|
||||||
<div class="score-block-hdr">
|
<div class="score-block-hdr">
|
||||||
<span>{{ marketLabel(market) }}</span>
|
<span>{{ marketLabel(market) }}</span>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onActivated } from 'vue';
|
import { ref, onMounted, onActivated } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { RouterLink } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import api from '../../api';
|
import api from '../../api';
|
||||||
import GoldSpinner from '../../components/GoldSpinner.vue';
|
import GoldSpinner from '../../components/GoldSpinner.vue';
|
||||||
import Pagination from '../../components/desktop/Pagination.vue';
|
import Pagination from '../../components/desktop/Pagination.vue';
|
||||||
@@ -11,6 +11,7 @@ import { useOnLocaleChange } from '../../composables/useOnLocaleChange';
|
|||||||
const COL_COUNT = 8;
|
const COL_COUNT = 8;
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const items = ref<BetHistoryItem[]>([]);
|
const items = ref<BetHistoryItem[]>([]);
|
||||||
const total = ref(0);
|
const total = ref(0);
|
||||||
@@ -149,10 +150,13 @@ function statusLabel(status: string) {
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<template v-else-if="items.length">
|
<template v-else-if="items.length">
|
||||||
<tr v-for="bet in items" :key="bet.betNo" class="hover-row">
|
<tr
|
||||||
<td>
|
v-for="bet in items"
|
||||||
<RouterLink :to="`/bets/${bet.betNo}`" class="link-cell">{{ bet.betNo }}</RouterLink>
|
:key="bet.betNo"
|
||||||
</td>
|
class="hover-row clickable-row"
|
||||||
|
@click="router.push(`/bets/${bet.betNo}`)"
|
||||||
|
>
|
||||||
|
<td class="id-cell">{{ bet.betNo }}</td>
|
||||||
<td>{{ bet.matchTitle || '-' }}</td>
|
<td>{{ bet.matchTitle || '-' }}</td>
|
||||||
<td class="muted-cell">{{ bet.pickLabel || '-' }}</td>
|
<td class="muted-cell">{{ bet.pickLabel || '-' }}</td>
|
||||||
<td class="num-cell">{{ bet.totalOdds ?? '-' }}</td>
|
<td class="num-cell">{{ bet.totalOdds ?? '-' }}</td>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onActivated } from 'vue';
|
import { ref, onMounted, onActivated } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { RouterLink } from 'vue-router';
|
import { RouterLink, useRouter } from 'vue-router';
|
||||||
import api from '../../api';
|
import api from '../../api';
|
||||||
import GoldSpinner from '../../components/GoldSpinner.vue';
|
import GoldSpinner from '../../components/GoldSpinner.vue';
|
||||||
import Pagination from '../../components/desktop/Pagination.vue';
|
import Pagination from '../../components/desktop/Pagination.vue';
|
||||||
@@ -11,6 +11,7 @@ import { formatMoney } from '../../utils/localeDisplay';
|
|||||||
const COL_COUNT = 5;
|
const COL_COUNT = 5;
|
||||||
|
|
||||||
const { t, locale } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
type RechargeOrder = {
|
type RechargeOrder = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -104,12 +105,13 @@ function statusLabel(status: string) {
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<template v-else-if="items.length">
|
<template v-else-if="items.length">
|
||||||
<tr v-for="order in items" :key="order.id" class="hover-row">
|
<tr
|
||||||
<td>
|
v-for="order in items"
|
||||||
<RouterLink :to="`/wallet/recharge/history/${order.id}`" class="link-cell">
|
:key="order.id"
|
||||||
{{ order.orderNo || order.id }}
|
class="hover-row clickable-row"
|
||||||
</RouterLink>
|
@click="router.push(`/wallet/recharge/history/${order.id}`)"
|
||||||
</td>
|
>
|
||||||
|
<td class="id-cell">{{ order.orderNo || order.id }}</td>
|
||||||
<td>{{ methodLabel(order) }}</td>
|
<td>{{ methodLabel(order) }}</td>
|
||||||
<td class="num-cell">{{ formatMoney(order.amount, locale) }}</td>
|
<td class="num-cell">{{ formatMoney(order.amount, locale) }}</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
@@ -93,8 +93,7 @@ onActivated(() => loadPage(1));
|
|||||||
<tr
|
<tr
|
||||||
v-for="tx in items"
|
v-for="tx in items"
|
||||||
:key="tx.transactionId"
|
:key="tx.transactionId"
|
||||||
class="hover-row"
|
class="hover-row clickable-row"
|
||||||
style="cursor:pointer"
|
|
||||||
@click="router.push(`/wallet/transactions/${tx.transactionId}`)"
|
@click="router.push(`/wallet/transactions/${tx.transactionId}`)"
|
||||||
>
|
>
|
||||||
<td class="type-cell">{{ txLabel(tx) }}</td>
|
<td class="type-cell">{{ txLabel(tx) }}</td>
|
||||||
|
|||||||
@@ -112,8 +112,7 @@ onActivated(() => fetchData(1));
|
|||||||
<tr
|
<tr
|
||||||
v-for="tx in items"
|
v-for="tx in items"
|
||||||
:key="tx.transactionId"
|
:key="tx.transactionId"
|
||||||
class="hover-row"
|
class="hover-row clickable-row"
|
||||||
style="cursor: pointer"
|
|
||||||
@click="router.push(`/wallet/transactions/${tx.transactionId}`)"
|
@click="router.push(`/wallet/transactions/${tx.transactionId}`)"
|
||||||
>
|
>
|
||||||
<td class="type-cell">{{ txLabel(tx) }}</td>
|
<td class="type-cell">{{ txLabel(tx) }}</td>
|
||||||
|
|||||||
Reference in New Issue
Block a user