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

235 lines
5.5 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 { onMounted, onUnmounted } from 'vue';
import { useI18n } from 'vue-i18n';
import { formatMoney, formatMoneyCompact } from '../../utils/localeDisplay';
import type { CashbackRecord } from '../../utils/cashback';
const props = defineProps<{
visible: boolean;
record: CashbackRecord | null;
}>();
const emit = defineEmits<{
'update:visible': [value: boolean];
}>();
const { t, locale } = useI18n();
function close() {
emit('update:visible', false);
}
function onKeydown(e: KeyboardEvent) {
if (e.key === 'Escape' && props.visible) close();
}
onMounted(() => document.addEventListener('keydown', onKeydown));
onUnmounted(() => document.removeEventListener('keydown', onKeydown));
function formatPeriod(start: string, end: string) {
const opts: Intl.DateTimeFormatOptions = { year: 'numeric', month: '2-digit', day: '2-digit' };
const s = new Date(start).toLocaleDateString(locale.value, opts);
const e = new Date(end).toLocaleDateString(locale.value, opts);
return `${s} ${e}`;
}
function formatRate(rate: string) {
const n = parseFloat(rate);
if (!Number.isFinite(n)) return rate;
return `${(n * 100).toFixed(2)}%`;
}
function formatTime(value: string | null) {
if (!value) return '—';
return new Date(value).toLocaleString(locale.value);
}
</script>
<template>
<Teleport to="body">
<Transition name="modal-fade">
<div v-if="visible" class="modal-overlay" @click.self="close">
<div class="modal-container" role="dialog" aria-modal="true" :aria-label="t('cashback.title')">
<div class="modal-header">
<span class="modal-title">{{ t('cashback.title') }}</span>
<button type="button" class="modal-close" :aria-label="t('support.close')" @click="close"></button>
</div>
<div v-if="record" class="modal-body">
<div class="amount-hero pos">{{ formatMoney(record.amount, locale) }}</div>
<div class="type-label">{{ formatPeriod(record.periodStart, record.periodEnd) }}</div>
<div class="info-rows">
<div class="info-row">
<span class="row-label">{{ t('cashback.effective_stake') }}</span>
<span class="row-val">{{ formatMoneyCompact(record.effectiveStake, locale) }}</span>
</div>
<div class="info-row">
<span class="row-label">{{ t('cashback.rate') }}</span>
<span class="row-val">{{ formatRate(record.rate) }}</span>
</div>
<div class="info-row">
<span class="row-label">{{ t('nav.bet_history') }}</span>
<span class="row-val">{{ t('cashback.bet_count', { n: record.betCount }) }}</span>
</div>
<div class="info-row">
<span class="row-label">{{ t('cashback.batch_no') }}</span>
<span class="row-val mono muted">{{ record.batchNo }}</span>
</div>
<div class="info-row">
<span class="row-label">{{ t('wallet.time') }}</span>
<span class="row-val muted">{{ formatTime(record.confirmedAt ?? record.createdAt) }}</span>
</div>
</div>
</div>
<div v-else class="modal-empty">{{ t('common.not_found') }}</div>
</div>
</div>
</Transition>
</Teleport>
</template>
<style scoped>
.modal-overlay {
position: fixed;
inset: 0;
z-index: 1100;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
background: rgba(0, 0, 0, 0.65);
}
.modal-container {
width: 100%;
max-width: 480px;
max-height: 82vh;
display: flex;
flex-direction: column;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 10px;
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.35);
overflow: hidden;
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 18px;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.modal-title {
font-size: 15px;
font-weight: 800;
color: var(--text);
}
.modal-close {
background: none;
border: none;
color: var(--text-muted);
font-size: 16px;
cursor: pointer;
padding: 0 4px;
line-height: 1;
}
.modal-close:hover {
color: var(--text);
}
.modal-body {
flex: 1;
min-height: 0;
overflow-y: auto;
}
.modal-empty {
display: flex;
align-items: center;
justify-content: center;
padding: 48px 20px;
color: var(--text-muted);
font-size: 13px;
}
.amount-hero {
padding: 20px 18px 8px;
font-size: 32px;
font-weight: 800;
font-variant-numeric: tabular-nums;
font-family: var(--font-mono);
line-height: 1.1;
}
.amount-hero.pos {
color: var(--primary);
}
.type-label {
padding: 0 18px 16px;
font-size: 13px;
font-weight: 700;
color: var(--text-muted);
border-bottom: 1px solid var(--border);
}
.info-rows {
padding: 4px 0 8px;
}
.info-row {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 11px 18px;
border-bottom: 1px solid rgba(0, 0, 0, 0.04);
}
.info-row:last-child {
border-bottom: none;
}
.row-label {
width: 96px;
flex-shrink: 0;
font-size: 12px;
color: var(--text-muted);
font-weight: 600;
}
.row-val {
flex: 1;
font-size: 13px;
font-weight: 700;
color: var(--text);
word-break: break-all;
}
.row-val.muted {
color: var(--text-muted);
font-weight: 600;
}
.mono {
font-family: var(--font-mono);
font-size: 12px;
}
.modal-fade-enter-active,
.modal-fade-leave-active {
transition: opacity 0.15s ease;
}
.modal-fade-enter-from,
.modal-fade-leave-to {
opacity: 0;
}
</style>