Files
thebet365/apps/admin/src/views/MatchesOutrights.vue

274 lines
7.8 KiB
Vue

<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { useRoute } from 'vue-router';
import { useAdminLocale } from '../composables/useAdminLocale';
import api from '../api';
import MatchesSubNav from '../components/MatchesSubNav.vue';
import LeagueOutrightOddsPanel from './matches/LeagueOutrightOddsPanel.vue';
import {
readMatchesListUiState,
writeMatchesListUiState,
} from '../utils/matchesListState';
const { t } = useAdminLocale();
const route = useRoute();
const leagues = ref<unknown[]>([]);
const total = ref(0);
const page = ref(1);
const pageSize = ref(10);
const keyword = ref('');
const expandedRowKeys = ref<string[]>([]);
function persistListUiState() {
writeMatchesListUiState({
expandedLeagueIds: [...expandedRowKeys.value],
page: page.value,
pageSize: pageSize.value,
filterStatus: '',
keyword: keyword.value,
});
}
function applyExpandedFromSaved(savedIds: string[]) {
const allowed = new Set(leagues.value.map((row) => leagueId(row)));
expandedRowKeys.value = savedIds.filter((id) => allowed.has(id));
}
type LoadOptions = { restoreExpand?: boolean; keepExpand?: boolean };
async function load(options: LoadOptions = {}) {
const saved = options.restoreExpand ? readMatchesListUiState() : null;
if (saved) {
page.value = saved.page;
pageSize.value = saved.pageSize;
keyword.value = saved.keyword;
}
const { data } = await api.get('/admin/leagues', {
params: {
page: page.value,
pageSize: pageSize.value,
keyword: keyword.value.trim() || undefined,
},
});
leagues.value = data.data.items;
total.value = data.data.total;
if (options.restoreExpand && saved) {
applyExpandedFromSaved(saved.expandedLeagueIds);
} else if (!options.keepExpand) {
expandedRowKeys.value = [];
} else {
applyExpandedFromSaved(expandedRowKeys.value);
}
persistListUiState();
}
function onSearch() {
page.value = 1;
expandedRowKeys.value = [];
load();
}
async function resolveExpandFromQuery() {
const qLeague = route.query.leagueId;
if (typeof qLeague === 'string' && qLeague.trim()) {
expandedRowKeys.value = [qLeague.trim()];
persistListUiState();
return;
}
const qMatch = route.query.matchId;
if (typeof qMatch === 'string' && qMatch.trim()) {
try {
const { data } = await api.get(`/admin/outrights/${qMatch.trim()}`);
const lid = data.data?.leagueId as string | undefined;
if (lid) {
expandedRowKeys.value = [lid];
persistListUiState();
}
} catch {
/* ignore */
}
}
}
onMounted(async () => {
await load({ restoreExpand: true });
await resolveExpandFromQuery();
});
onBeforeUnmount(persistListUiState);
function onPageChange(p: number) {
page.value = p;
load({ keepExpand: true });
}
function onSizeChange(size: number) {
pageSize.value = size;
page.value = 1;
load({ keepExpand: true });
}
function onExpandChange(_row: unknown, expanded: unknown[]) {
expandedRowKeys.value = expanded.map((r) => leagueId(r));
persistListUiState();
}
function onRowClick(row: unknown, _column: unknown, event: MouseEvent) {
if ((event.target as HTMLElement).closest('.el-table__expand-icon')) return;
const id = leagueId(row);
expandedRowKeys.value = expandedRowKeys.value.includes(id) ? [] : [id];
persistListUiState();
}
function rowClassName() {
return 'row-expandable';
}
function rowOf(row: unknown) {
return row as Record<string, unknown>;
}
function leagueId(row: unknown) {
return String(rowOf(row).id ?? '');
}
function leagueNameZh(row: unknown) {
const zh = String(rowOf(row).leagueZh ?? '').trim();
return zh || '—';
}
function leagueNameEn(row: unknown) {
const en = String(rowOf(row).leagueEn ?? '').trim();
return en || '—';
}
function outrightTeamCount(row: unknown) {
return Number(rowOf(row).outrightTeamCount ?? 0);
}
function isLeagueExpanded(id: string) {
return expandedRowKeys.value.includes(id);
}
</script>
<template>
<div class="admin-list-page matches-page">
<div class="list-chrome">
<div class="list-chrome__row">
<div class="list-chrome__left">
<MatchesSubNav embedded />
<div class="list-chrome__filters">
<div class="list-chrome__field">
<span class="list-chrome__label">{{ t('common.keyword') }}</span>
<el-input
v-model="keyword"
:placeholder="t('match.filter.keyword_ph')"
clearable
style="width: 200px"
@keyup.enter="onSearch"
/>
</div>
<el-button type="primary" class="list-chrome__submit" @click="onSearch">
{{ t('common.search') }}
</el-button>
</div>
</div>
</div>
</div>
<section class="list-panel">
<p class="list-hint">{{ t('match.expand_outright_hint') }}</p>
<div class="table-wrap">
<el-table
:data="leagues"
stripe
row-key="id"
:expand-row-keys="expandedRowKeys"
:row-class-name="rowClassName"
@expand-change="onExpandChange"
@row-click="onRowClick"
>
<el-table-column type="expand" width="40">
<template #default="{ row }">
<LeagueOutrightOddsPanel
v-if="isLeagueExpanded(leagueId(row))"
:league-id="leagueId(row)"
@updated="load({ keepExpand: true })"
/>
</template>
</el-table-column>
<el-table-column type="index" :index="(i: number) => (page - 1) * pageSize + i + 1" :label="t('common.seq')" width="70" align="center" />
<el-table-column :label="t('match.col.league')" width="148" show-overflow-tooltip>
<template #default="{ row }">
<div class="league-cell">
<img
v-if="rowOf(row).logoUrl"
:src="String(rowOf(row).logoUrl)"
alt=""
class="league-logo"
/>
<span class="matchup-link">{{ leagueNameZh(row) }}</span>
</div>
</template>
</el-table-column>
<el-table-column :label="t('match.col.league_en')" width="180" show-overflow-tooltip>
<template #default="{ row }">
<span class="league-en">{{ leagueNameEn(row) }}</span>
</template>
</el-table-column>
<el-table-column :label="t('outright.col.teams_total')" width="120" align="center">
<template #default="{ row }">{{ outrightTeamCount(row) }}</template>
</el-table-column>
<el-table-column :label="t('match.col.league_code')" width="140" show-overflow-tooltip>
<template #default="{ row }">{{ rowOf(row).code }}</template>
</el-table-column>
</el-table>
</div>
<div class="pager">
<el-pagination
v-model:current-page="page"
v-model:page-size="pageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next"
background
@current-change="onPageChange"
@size-change="onSizeChange"
/>
</div>
</section>
</div>
</template>
<style scoped>
.matches-page .table-wrap .el-table {
height: auto !important;
}
.matches-page .table-wrap :deep(.el-table__header),
.matches-page .table-wrap :deep(.el-table__body) {
width: 100% !important;
}
.matches-page :deep(.el-table__expanded-cell) {
padding: 0 !important;
background: #fbfaf7;
}
.list-panel :deep(.row-expandable) {
cursor: pointer;
}
.matchup-link {
color: var(--primary-link);
}
.league-cell {
display: flex;
align-items: center;
gap: 8px;
}
.league-logo {
width: 28px;
height: 28px;
object-fit: contain;
flex-shrink: 0;
}
.league-en {
color: var(--text-muted);
font-size: 13px;
}
</style>