sync(theme-2): 同步 main 最新 API/Admin 与玩家端逻辑

从 main 同步站内信手动发送、媒体库选择、列表子路由重构等 API/Admin 改动;玩家端仅合并 ADMIN_CUSTOM 富文本、消息预览与充值截图压缩逻辑,保留 theme-2 样式。
This commit is contained in:
2026-06-22 14:10:54 +08:00
parent fa7b8cff25
commit 27dc9eb3b0
50 changed files with 4653 additions and 1186 deletions

View File

@@ -1,13 +1,12 @@
<script setup lang="ts">
import { ref, onBeforeUnmount } from 'vue';
import { ref, computed, onBeforeUnmount, onDeactivated } from 'vue';
defineOptions({ name: 'AdminMatchesOutrights' });
import { useStaleListLifecycle } from '../composables/useStaleList';
import { useRoute } from 'vue-router';
import { useRoute, useRouter } 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,
@@ -15,17 +14,20 @@ import {
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('');
const expandedRowKeys = ref<string[]>([]);
function persistListUiState() {
writeMatchesListUiState({
expandedLeagueIds: [...expandedRowKeys.value],
page: page.value,
pageSize: pageSize.value,
filterStatus: '',
@@ -33,15 +35,10 @@ function persistListUiState() {
});
}
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 };
type LoadOptions = { restore?: boolean };
async function load(options: LoadOptions = {}) {
const saved = options.restoreExpand ? readMatchesListUiState() : null;
const saved = options.restore ? readMatchesListUiState() : null;
if (saved) {
page.value = saved.page;
pageSize.value = saved.pageSize;
@@ -57,29 +54,22 @@ async function load(options: LoadOptions = {}) {
});
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() {
async function resolveLeagueFromQuery() {
const qLeague = route.query.leagueId;
if (typeof qLeague === 'string' && qLeague.trim()) {
expandedRowKeys.value = [qLeague.trim()];
persistListUiState();
return;
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()) {
@@ -87,48 +77,51 @@ async function resolveExpandFromQuery() {
const { data } = await api.get(`/admin/outrights/${qMatch.trim()}`);
const lid = data.data?.leagueId as string | undefined;
if (lid) {
expandedRowKeys.value = [lid];
persistListUiState();
router.replace(`/matches/outrights/leagues/${lid}`);
return true;
}
} catch {
/* ignore */
}
}
return false;
}
async function initialLoad() {
await load({ restoreExpand: true });
await resolveExpandFromQuery();
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({ keepExpand: true });
load();
}
function onSizeChange(size: number) {
pageSize.value = size;
page.value = 1;
load({ keepExpand: true });
load();
}
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;
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);
expandedRowKeys.value = expandedRowKeys.value.includes(id) ? [] : [id];
persistListUiState();
if (!id) return;
void router.push({
path: `/matches/outrights/leagues/${id}`,
query: { title: leagueTitle(row) },
});
}
function rowClassName() {
return 'row-expandable';
return 'row-navigable';
}
function rowOf(row: unknown) {
@@ -145,16 +138,20 @@ 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);
}
function isLeagueExpanded(id: string) {
return expandedRowKeys.value.includes(id);
}
</script>
<template>
<div class="admin-list-page matches-page">
<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">
@@ -178,27 +175,16 @@ function isLeagueExpanded(id: string) {
</div>
</div>
<section v-loading="listLoading" class="list-panel">
<p class="list-hint">{{ t('match.expand_outright_hint') }}</p>
<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"
:expand-row-keys="expandedRowKeys"
:row-class-name="rowClassName"
@expand-change="onExpandChange"
@row-click="onRowClick"
@row-click="openLeaguePage"
>
<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 }">
@@ -240,9 +226,22 @@ function isLeagueExpanded(id: string) {
</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;
}
@@ -250,11 +249,7 @@ function isLeagueExpanded(id: string) {
.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) {
.list-panel :deep(.row-navigable) {
cursor: pointer;
}
.matchup-link {