Files
thebet365/apps/player/src/views/desktop/DesktopWalletTransactionDetailView.vue

202 lines
7.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import api from '../../api';
import GoldSpinner from '../../components/GoldSpinner.vue';
import { formatMoney } from '../../utils/localeDisplay';
import { txTypeKey, txDisplayType, txSummaryLabel } from '../../utils/walletTx';
const { t, locale } = useI18n();
const route = useRoute();
const router = useRouter();
function goBack() {
if (window.history.state && window.history.state.back) {
router.back();
} else {
router.push('/wallet');
}
}
type TxDetail = {
transactionId: string;
transactionType: string;
displayType?: string;
summary?: string | null;
amount: string;
balanceBefore?: string;
balanceAfter?: string;
frozenBefore?: string;
frozenAfter?: string;
createdAt: string;
referenceType?: string | null;
[key: string]: any;
};
const detail = ref<TxDetail | null>(null);
const loading = ref(true);
const error = ref(false);
async function loadDetail() {
const id = route.params.transactionId as string;
if (!id) return;
loading.value = true;
error.value = false;
try {
const { data } = await api.get(`/player/wallet/transactions/${id}`);
detail.value = data.data ?? null;
} catch {
error.value = true;
} finally {
loading.value = false;
}
}
onMounted(loadDetail);
function txLabel(tx: TxDetail): string {
const key = txTypeKey(txDisplayType(tx));
if (key) {
const translated = t(key);
if (translated !== key) return translated;
}
return tx.transactionType;
}
</script>
<template>
<div class="desktop-account-page">
<main class="account-main">
<div class="detail-layout">
<div class="page-header">
<button type="button" class="back-btn" @click="goBack">
â?{{ t('common.back') || 'è¿åž' }}
</button>
<h1 class="page-title">{{ t('wallet.transaction_detail') || '交易详情' }}</h1>
</div>
<div v-if="loading" class="loading-state">
<GoldSpinner :size="36" />
</div>
<div v-else-if="error" class="error-state">
<p>{{ t('common.load_failed') }}</p>
<button type="button" class="retry-btn" @click="loadDetail">{{ t('common.retry') }}</button>
</div>
<div v-else-if="detail" class="detail-card">
<div class="amount-hero" :class="parseFloat(detail.amount) >= 0 ? 'pos' : 'neg'">
{{ formatMoney(detail.amount, locale) }}
</div>
<div class="type-label">{{ txLabel(detail) }}</div>
<div class="detail-rows">
<div class="detail-row">
<span class="row-label">{{ t('wallet.type') || '类型' }}</span>
<span class="row-val">{{ txLabel(detail) }}</span>
</div>
<div class="detail-row">
<span class="row-label">{{ t('wallet.note') || '备注' }}</span>
<span class="row-val">{{ txSummaryLabel(detail, t) || '-' }}</span>
</div>
<div class="detail-row">
<span class="row-label">{{ t('wallet.balance_before') || <>˜åЍå‰<C3A5>ä½™é¢? }}</span>
<span class="row-val mono">{{ formatMoney(detail.balanceBefore, locale) }}</span>
</div>
<div class="detail-row">
<span class="row-label">{{ t('wallet.balance_after') || 'å<EFBFBD>˜åЍå<EFBFBD>Žä½é¢? }}</span>
<span class="row-val mono">{{ formatMoney(detail.balanceAfter, locale) }}</span>
</div>
<div v-if="parseFloat(detail.frozenBefore || '0') > 0 || parseFloat(detail.frozenAfter || '0') > 0" class="detail-row">
<span class="row-label">{{ t('wallet.frozen_before') || '冻结� }}</span>
<span class="row-val mono">{{ formatMoney(detail.frozenBefore, locale) }}</span>
</div>
<div v-if="parseFloat(detail.frozenBefore || '0') > 0 || parseFloat(detail.frozenAfter || '0') > 0" class="detail-row">
<span class="row-label">{{ t('wallet.frozen_after') || 'å»ç»å<EFBFBD>? }}</span>
<span class="row-val mono">{{ formatMoney(detail.frozenAfter, locale) }}</span>
</div>
<div class="detail-row">
<span class="row-label">{{ t('wallet.time') || 'æ—¶é—´' }}</span>
<span class="row-val">{{ new Date(detail.createdAt).toLocaleString() }}</span>
</div>
<div class="detail-row">
<span class="row-label">{{ t('wallet.transaction_id') || 'æµ<C3A6>æ°´å<C2B4>? }}</span>
<span class="row-val mono">{{ detail.transactionId }}</span>
</div>
</div>
</div>
<div v-else class="empty-state">{{ t('common.not_found') || '未æ¾åˆ°è®°å½? }}</div>
</div>
</main>
</div>
</template>
<style scoped>
.desktop-account-page { display: flex; flex-direction: column; min-height: 0; flex: 1; width: 100%; }
.account-main { flex: 1; min-width: 0; padding: 24px 28px; overflow-y: auto; }
.detail-layout {
display: flex;
flex-direction: column;
max-width: 560px;
margin: 0 auto;
}
.page-header { display: flex; align-items: center; gap: 16px; margin-bottom: 24px; }
.back-btn { background: none; border: none; color: var(--text-muted); font-size: 14px; font-weight: 700; cursor: pointer; padding: 0; }
.back-btn:hover { color: var(--text); }
.page-title { font-size: 18px; font-weight: 800; color: var(--primary-light); margin: 0; }
.loading-state { display: flex; justify-content: center; align-items: center; padding: 80px 0; }
.error-state { display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 80px 20px; color: var(--text-muted); }
.retry-btn { padding: 8px 24px; border-radius: 6px; border: 1px solid var(--primary); background: transparent; color: var(--primary-light); font-size: 13px; font-weight: 700; cursor: pointer; }
.empty-state { display: flex; align-items: center; justify-content: center; padding: 80px 20px; color: var(--text-muted); font-size: 14px; font-weight: 600; }
.detail-card {
background: var(--desktop-sidebar-bg);
border: 1px solid var(--desktop-border);
border-radius: 12px;
padding: 28px;
}
.amount-hero {
font-size: 36px;
font-weight: 800;
font-variant-numeric: tabular-nums;
font-family: var(--font-mono);
line-height: 1.1;
margin-bottom: 6px;
}
.amount-hero.pos { color: var(--primary-light); }
.amount-hero.neg { color: var(--danger); }
.type-label {
font-size: 13px;
font-weight: 700;
color: var(--text-muted);
margin-bottom: 24px;
padding-bottom: 20px;
border-bottom: 1px solid var(--desktop-border);
}
.detail-rows { display: flex; flex-direction: column; gap: 0; }
.detail-row {
display: flex;
gap: 16px;
padding: 12px 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.04);
}
.detail-row:last-child { border-bottom: none; }
.row-label { width: 120px; 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.pos { color: var(--primary-light); }
.row-val.neg { color: var(--danger); }
.mono { font-family: var(--font-mono); font-size: 12px; letter-spacing: 0.03em; }
</style>