perf(admin): 实现切页即时导航与列表 loading 壳

非阻塞 session 刷新、侧边栏 chunk 预取、KeepAlive 对齐,以及按 tab 延迟加载重型列表页。
This commit is contained in:
2026-06-18 16:45:01 +08:00
parent 7b5a752b8f
commit e4d6d5c048
21 changed files with 321 additions and 117 deletions

View File

@@ -1,7 +1,8 @@
<script setup lang="ts">
import { ref, onMounted, onActivated } from 'vue';
import { ref } from 'vue';
defineOptions({ name: 'AdminBets' });
import { useStaleListLifecycle } from '../composables/useStaleList';
import api from '../api';
import { formatAmount, formatAmountFull } from '../utils/format-amount';
import {
@@ -34,16 +35,6 @@ const detailVisible = ref(false);
const detail = ref<BetDetail | null>(null);
const detailLoading = ref(false);
onMounted(load);
// KeepAlive 激活时:有缓存数据则后台静默刷新,避免白屏等待
onActivated(() => { if (bets.value.length > 0) void load(); });
function betContentCounts(row: BetListRow) {
const singles = row.betType === 'SINGLE' ? 1 : 0;
const parlays = row.betType === 'PARLAY' ? 1 : 0;
return t('bet.content.bet_counts', { singles, parlays });
}
async function load() {
const { data } = await api.get('/admin/bets', {
params: {
@@ -60,15 +51,17 @@ async function load() {
total.value = data.data.total;
}
const { loading: listLoading, runLoad } = useStaleListLifecycle(() => bets.value.length > 0, load);
function onPageChange(p: number) {
page.value = p;
load();
void runLoad(true);
}
function onSizeChange(size: number) {
pageSize.value = size;
page.value = 1;
load();
void runLoad(true);
}
function resetFilters() {
@@ -78,7 +71,13 @@ function resetFilters() {
placedFrom.value = '';
placedTo.value = '';
page.value = 1;
load();
void runLoad(true);
}
function betContentCounts(row: BetListRow) {
const singles = row.betType === 'SINGLE' ? 1 : 0;
const parlays = row.betType === 'PARLAY' ? 1 : 0;
return t('bet.content.bet_counts', { singles, parlays });
}
function parentLabel(row: BetListRow) {
@@ -166,13 +165,13 @@ async function openDetail(row: BetListRow) {
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="load">{{ t('common.search') }}</el-button>
<el-button type="primary" @click="runLoad(true)">{{ t('common.search') }}</el-button>
<el-button @click="resetFilters">{{ t('common.reset') }}</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="data-card" shadow="never">
<el-card v-loading="listLoading" class="data-card" shadow="never">
<div class="table-wrap">
<el-table :data="bets" stripe class="bets-table">
<template #empty>