从 main 同步站内信手动发送、媒体库选择、列表子路由重构等 API/Admin 改动;玩家端仅合并 ADMIN_CUSTOM 富文本、消息预览与充值截图压缩逻辑,保留 theme-2 样式。
274 lines
7.5 KiB
Vue
274 lines
7.5 KiB
Vue
<script setup lang="ts">
|
|
import { ref, computed, onBeforeUnmount, onDeactivated } from 'vue';
|
|
|
|
defineOptions({ name: 'AdminMatchesOutrights' });
|
|
import { useStaleListLifecycle } from '../composables/useStaleList';
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
import { useAdminLocale } from '../composables/useAdminLocale';
|
|
import api from '../api';
|
|
import MatchesSubNav from '../components/MatchesSubNav.vue';
|
|
import {
|
|
readMatchesListUiState,
|
|
writeMatchesListUiState,
|
|
} from '../utils/matchesListState';
|
|
|
|
const { t } = useAdminLocale();
|
|
const route = useRoute();
|
|
const router = useRouter();
|
|
|
|
const isOutrightChildRoute = computed(() =>
|
|
/^\/matches\/outrights\/leagues\/[^/]+/.test(route.path),
|
|
);
|
|
|
|
const leagues = ref<unknown[]>([]);
|
|
const total = ref(0);
|
|
const page = ref(1);
|
|
const pageSize = ref(10);
|
|
const keyword = ref('');
|
|
|
|
function persistListUiState() {
|
|
writeMatchesListUiState({
|
|
page: page.value,
|
|
pageSize: pageSize.value,
|
|
filterStatus: '',
|
|
keyword: keyword.value,
|
|
});
|
|
}
|
|
|
|
type LoadOptions = { restore?: boolean };
|
|
|
|
async function load(options: LoadOptions = {}) {
|
|
const saved = options.restore ? 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;
|
|
persistListUiState();
|
|
}
|
|
|
|
function onSearch() {
|
|
page.value = 1;
|
|
load();
|
|
}
|
|
|
|
async function resolveLeagueFromQuery() {
|
|
const qLeague = route.query.leagueId;
|
|
if (typeof qLeague === 'string' && qLeague.trim()) {
|
|
router.replace({
|
|
path: `/matches/outrights/leagues/${qLeague.trim()}`,
|
|
query: route.query.title ? { title: String(route.query.title) } : undefined,
|
|
});
|
|
return true;
|
|
}
|
|
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) {
|
|
router.replace(`/matches/outrights/leagues/${lid}`);
|
|
return true;
|
|
}
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
async function initialLoad() {
|
|
await load({ restore: true });
|
|
await resolveLeagueFromQuery();
|
|
}
|
|
|
|
const { loading: listLoading, runLoad } = useStaleListLifecycle(() => leagues.value.length > 0, initialLoad);
|
|
onBeforeUnmount(persistListUiState);
|
|
onDeactivated(persistListUiState);
|
|
|
|
function onPageChange(p: number) {
|
|
page.value = p;
|
|
load();
|
|
}
|
|
|
|
function onSizeChange(size: number) {
|
|
pageSize.value = size;
|
|
page.value = 1;
|
|
load();
|
|
}
|
|
|
|
function openLeaguePage(row: unknown, _column: unknown, event: Event) {
|
|
const target = event.target;
|
|
const el = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
|
|
if (!el) return;
|
|
if (el.closest('.el-button')) return;
|
|
const id = leagueId(row);
|
|
if (!id) return;
|
|
void router.push({
|
|
path: `/matches/outrights/leagues/${id}`,
|
|
query: { title: leagueTitle(row) },
|
|
});
|
|
}
|
|
|
|
function rowClassName() {
|
|
return 'row-navigable';
|
|
}
|
|
|
|
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 leagueTitle(row: unknown) {
|
|
const zh = leagueNameZh(row);
|
|
if (zh !== '—') return zh;
|
|
return leagueNameEn(row);
|
|
}
|
|
function outrightTeamCount(row: unknown) {
|
|
return Number(rowOf(row).outrightTeamCount ?? 0);
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="matches-shell">
|
|
<router-view v-if="isOutrightChildRoute" />
|
|
<div v-else 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 v-loading="listLoading" class="list-panel">
|
|
<p class="list-hint">{{ t('match.open_outright_hint') }}</p>
|
|
<div class="table-wrap">
|
|
<el-table
|
|
:data="leagues"
|
|
stripe
|
|
row-key="id"
|
|
:row-class-name="rowClassName"
|
|
@row-click="openLeaguePage"
|
|
>
|
|
<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>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.matches-shell {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
min-height: 0;
|
|
}
|
|
|
|
.matches-shell > :deep(.league-outrights-page) {
|
|
flex: 1;
|
|
min-height: 0;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
.list-panel :deep(.row-navigable) {
|
|
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>
|