Files
thebet365/apps/player/src/views/desktop/DesktopCashbackRecordsView.vue
mars e7e2b77906 feat(player): theme-3 移动端/PC 双端拆分与桌面投注工作台
新增 DesktopShell 及桌面端首页、赛事、钱包、记录等页面,原 H5 视图迁移至 Mobile* 并由路由 wrapper 按视口切换。补齐右侧投注单栏、赔率快捷浮层、联赛/赛事侧栏、串关与定位能力;桌面下注成功改为全局 Toast,修复侧栏成功遮罩被裁剪与底部汇总黑底。同步悬浮客服、公告卡片、三语 i18n、桌面样式体系,以及 API 赛事与 shared 包相关调整。
2026-07-07 17:07:38 +08:00

184 lines
5.3 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 { computed, ref, onMounted, onActivated, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import api from '../../api';
import GoldSpinner from '../../components/GoldSpinner.vue';
import Pagination from '../../components/desktop/Pagination.vue';
import DesktopWalletSubNav from '../../components/desktop/DesktopWalletSubNav.vue';
import { formatMoney, formatMoneyCompact } from '../../utils/localeDisplay';
import { parseCashbackApiData, type CashbackRecord } from '../../utils/cashback';
const COL_COUNT = 6;
const { t, locale } = useI18n();
const allItems = ref<CashbackRecord[]>([]);
const loading = ref(true);
const page = ref(1);
const pageSize = ref(20);
const total = computed(() => allItems.value.length);
const items = computed(() => {
const start = (page.value - 1) * pageSize.value;
return allItems.value.slice(start, start + pageSize.value);
});
const totalAmount = computed(() =>
allItems.value.reduce((sum, row) => sum + Math.abs(parseFloat(row.amount) || 0), 0),
);
function formatPeriod(start: string, end: string) {
const opts: Intl.DateTimeFormatOptions = { 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, {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: false,
});
}
async function loadData() {
loading.value = true;
try {
const { data } = await api.get('/player/cashbacks');
allItems.value = parseCashbackApiData(data.data);
const maxPage = Math.max(1, Math.ceil(allItems.value.length / pageSize.value));
if (page.value > maxPage) page.value = maxPage;
} catch {
allItems.value = [];
page.value = 1;
} finally {
loading.value = false;
}
}
function onPageChange(p: number) {
page.value = p;
}
watch(pageSize, () => {
page.value = 1;
});
onMounted(loadData);
onActivated(loadData);
</script>
<template>
<div class="desktop-records-page">
<header class="desktop-records-header">
<div class="desktop-records-header-main">
<DesktopWalletSubNav />
</div>
<div v-if="!loading && allItems.length" class="desktop-records-actions summary-chip">
<span class="summary-label">{{ t('cashback.total_received') }}</span>
<span class="summary-value">{{ formatMoney(totalAmount, locale) }}</span>
</div>
</header>
<section class="desktop-records-panel">
<div class="desktop-records-table-wrap">
<table class="desktop-records-table">
<thead>
<tr>
<th>{{ t('cashback.period') }}</th>
<th>{{ t('cashback.effective_stake') }}</th>
<th>{{ t('cashback.rate') }}</th>
<th class="num-th">{{ t('cashback.amount') }}</th>
<th>{{ t('cashback.batch_no') }}</th>
<th>{{ t('wallet.time') }}</th>
</tr>
</thead>
<tbody>
<tr v-if="loading" class="state-row">
<td :colspan="COL_COUNT">
<div class="desktop-records-loading-cell">
<GoldSpinner :size="36" />
</div>
</td>
</tr>
<template v-else-if="items.length">
<tr v-for="row in items" :key="row.id" class="hover-row">
<td>{{ formatPeriod(row.periodStart, row.periodEnd) }}</td>
<td>{{ formatMoneyCompact(row.effectiveStake, locale) }}</td>
<td>{{ formatRate(row.rate) }}</td>
<td class="num-cell desktop-records-amount pos">{{ formatMoney(row.amount, locale) }}</td>
<td class="batch-cell">{{ row.batchNo }}</td>
<td class="date-cell">{{ formatTime(row.confirmedAt ?? row.createdAt) }}</td>
</tr>
</template>
<tr v-else class="state-row">
<td :colspan="COL_COUNT" class="desktop-records-table-empty">
<div>{{ t('cashback.empty') }}</div>
<p class="empty-hint">{{ t('cashback.empty_hint') }}</p>
</td>
</tr>
</tbody>
</table>
</div>
<div v-if="!loading && total > 0" class="desktop-records-pagination">
<Pagination
v-model="page"
v-model:pageSize="pageSize"
:total="total"
@change="onPageChange"
/>
</div>
</section>
</div>
</template>
<style scoped>
.summary-chip {
display: inline-flex;
align-items: baseline;
gap: 8px;
padding: 8px 14px;
border: 1px solid rgba(244, 162, 97, 0.22);
border-radius: 8px;
background: rgba(244, 162, 97, 0.08);
}
.summary-label {
font-size: 12px;
color: var(--text-muted);
font-weight: 700;
}
.summary-value {
font-size: 16px;
font-weight: 800;
color: var(--primary-light);
font-variant-numeric: tabular-nums;
}
.batch-cell {
font-family: 'SF Mono', Consolas, monospace;
font-size: 12px;
color: var(--text-muted);
}
.empty-hint {
margin: 8px 0 0;
font-size: 13px;
font-weight: 500;
line-height: 1.6;
color: var(--text-muted);
}
</style>