From 790b0f9f7a47fc24c5382d753a633c2f7941591f Mon Sep 17 00:00:00 2001 From: zhx Date: Wed, 13 May 2026 17:43:42 +0800 Subject: [PATCH] feat(admin): show game consume ranking modal --- apps/src/api/legacy/datav.ts | 24 ++++ apps/src/views/statistics/datav.vue | 191 ++++++++++++++++++++-------- 2 files changed, 160 insertions(+), 55 deletions(-) diff --git a/apps/src/api/legacy/datav.ts b/apps/src/api/legacy/datav.ts index 297e789..0d8d94e 100644 --- a/apps/src/api/legacy/datav.ts +++ b/apps/src/api/legacy/datav.ts @@ -116,6 +116,30 @@ export async function countryDashboardGameMetrics( }); } +export async function countryDashboardGameSummaries( + params: Record = {}, +) { + return requestClient.get<{ + current: number; + records: CountryDashboardGameMetric[]; + size: number; + total: number; + }>('/datav/country-dashboard/game-metrics/games', { + params, + }); +} + +export async function countryDashboardGameCountryRank( + params: Record = {}, +) { + return requestClient.get( + '/datav/country-dashboard/game-metrics/country-rank', + { + params, + }, + ); +} + export async function latestActiveUserCountryCode(params: Record = {}) { return requestClient.get>>( '/datav/active/user-country-code', diff --git a/apps/src/views/statistics/datav.vue b/apps/src/views/statistics/datav.vue index 3f6ef21..8cb3ddd 100644 --- a/apps/src/views/statistics/datav.vue +++ b/apps/src/views/statistics/datav.vue @@ -42,7 +42,8 @@ import utc from 'dayjs/plugin/utc'; import { countryDashboard, - countryDashboardGameMetrics, + countryDashboardGameCountryRank, + countryDashboardGameSummaries, countryDashboardRechargeDetails, } from '#/api/legacy/datav'; import SysOriginSelect from '#/components/sys-origin-select.vue'; @@ -103,6 +104,10 @@ const gameMetricSorter = reactive<{ field: 'consumeAmount', order: 'descend', }); +const gameCountryRankOpen = ref(false); +const gameCountryRankLoading = ref(false); +const gameCountryRankRecords = ref([]); +const selectedGameMetric = ref>(null); const gameQuery = reactive<{ gameKeyword: string; gameProvider?: string; @@ -128,6 +133,13 @@ const { renderEcharts: renderLuckyChart } = useEcharts(luckyChartRef); type ChartOption = Parameters[0]; const accessCodes = computed(() => accessStore.accessCodes || []); +const gameCountryRankTitle = computed(() => { + const game = selectedGameMetric.value; + if (!game) { + return '国家消耗排名'; + } + return `${gameDisplayText(game)} 国家消耗排名`; +}); const sysOriginOptions = computed(() => { const options = getAllowedSysOrigins(accessCodes.value); return options.length > 0 ? options : getAllowedSysOrigins([]); @@ -337,12 +349,10 @@ const gameMetricIntegerColumns = new Set(['orderCount', 'userCount']); const gameMetricPercentColumns = new Set(['payoutRate', 'profitRate']); const gameMetricColumns: any[] = [ - { dataIndex: 'periodName', fixed: 'left' as const, key: 'periodName', title: '日期/周期', width: 120 }, - { dataIndex: 'countryName', key: 'countryName', title: '国家', width: 150 }, - { dataIndex: 'countryCode', key: 'countryCode', title: 'Code', width: 86 }, - { dataIndex: 'gameProvider', key: 'gameProvider', title: '游戏厂商', width: 120 }, - { dataIndex: 'gameId', key: 'gameId', title: '游戏 ID', width: 130 }, - { dataIndex: 'gameName', key: 'gameName', title: '游戏名称', width: 180 }, + { align: 'right' as const, dataIndex: 'rank', fixed: 'left' as const, key: 'rank', title: '排名', width: 72 }, + { dataIndex: 'gameName', fixed: 'left' as const, key: 'gameName', title: '游戏', width: 240 }, + { dataIndex: 'gameProvider', key: 'gameProvider', title: '游戏厂商', width: 130 }, + { dataIndex: 'gameId', key: 'gameId', title: '游戏 ID', width: 150 }, { align: 'right' as const, dataIndex: 'consumeAmount', @@ -352,44 +362,6 @@ const gameMetricColumns: any[] = [ title: '消耗', width: 130, }, - { - align: 'right' as const, - dataIndex: 'payoutAmount', - key: 'payoutAmount', - sortDirections: ['descend', 'ascend'], - sorter: true, - title: '返奖', - width: 130, - }, - { - align: 'right' as const, - dataIndex: 'profitAmount', - key: 'profitAmount', - sortDirections: ['descend', 'ascend'], - sorter: true, - title: '收益', - width: 130, - }, - { align: 'right' as const, dataIndex: 'payoutRate', key: 'payoutRate', title: '返奖率', width: 110 }, - { align: 'right' as const, dataIndex: 'profitRate', key: 'profitRate', title: '收益率', width: 110 }, - { - align: 'right' as const, - dataIndex: 'userCount', - key: 'userCount', - sortDirections: ['descend', 'ascend'], - sorter: true, - title: '游戏用户数', - width: 130, - }, - { - align: 'right' as const, - dataIndex: 'orderCount', - key: 'orderCount', - sortDirections: ['descend', 'ascend'], - sorter: true, - title: '流水笔数', - width: 120, - }, { dataIndex: 'refreshedAt', key: 'refreshedAt', title: '更新时间', width: 170 }, ]; const gameTableScrollX = gameMetricColumns.reduce( @@ -397,6 +369,18 @@ const gameTableScrollX = gameMetricColumns.reduce( 0, ); +const gameCountryRankColumns: any[] = [ + { align: 'right' as const, dataIndex: 'rank', key: 'rank', title: '排名', width: 72 }, + { dataIndex: 'countryName', key: 'countryName', title: '国家', width: 180 }, + { dataIndex: 'countryCode', key: 'countryCode', title: 'Code', width: 100 }, + { align: 'right' as const, dataIndex: 'consumeAmount', key: 'consumeAmount', title: '消耗', width: 150 }, + { dataIndex: 'refreshedAt', key: 'refreshedAt', title: '更新时间', width: 170 }, +]; +const gameCountryRankScrollX = gameCountryRankColumns.reduce( + (totalWidth, item) => totalWidth + Number(item.width || 0), + 0, +); + const rechargeDetailColumns: any[] = [ { dataIndex: 'createTime', key: 'createTime', title: '时间', width: 170 }, { dataIndex: 'sourceType', key: 'sourceType', title: '来源', width: 120 }, @@ -589,6 +573,9 @@ async function loadDashboard() { }; if (activeTableTab.value === 'game') { await loadGameMetrics(); + if (gameCountryRankOpen.value) { + await loadGameCountryRank(); + } } } catch (error) { dashboard.value = null; @@ -624,7 +611,7 @@ async function loadGameMetrics() { } gameMetricLoading.value = true; try { - const result = await countryDashboardGameMetrics(buildGameMetricParams()); + const result = await countryDashboardGameSummaries(buildGameMetricParams()); gameMetricRecords.value = result.records || []; gameMetricPagination.total = Number(result.total || 0); gameMetricPagination.current = Number(result.current || gameMetricPagination.current || 1); @@ -642,6 +629,39 @@ async function loadGameMetrics() { } } +function buildGameCountryRankParams(record: Partial) { + return { + ...buildParams(), + countryCodes: selectedCountryCodes.value.join(',') || undefined, + gameId: record.gameId, + gameProvider: record.gameProvider, + rankLimit: 200, + }; +} + +async function loadGameCountryRank(record = selectedGameMetric.value) { + if (!record?.gameId || !record.gameProvider || !canQuery.value) { + return; + } + gameCountryRankLoading.value = true; + try { + gameCountryRankRecords.value = await countryDashboardGameCountryRank( + buildGameCountryRankParams(record), + ); + } catch { + gameCountryRankRecords.value = []; + message.error('游戏国家排名加载失败'); + } finally { + gameCountryRankLoading.value = false; + } +} + +async function openGameCountryRank(record: Partial) { + selectedGameMetric.value = record; + gameCountryRankOpen.value = true; + await loadGameCountryRank(record); +} + async function handleGameMetricTableChange( pagination: Record, _filters: Record, @@ -697,13 +717,19 @@ function rechargeDetailRowKey(record: Record) { } function gameMetricRowKey(record: Record) { - return [ - record.periodKey, - record.statTimezone, - record.countryCode, - record.gameProvider, - record.gameId, - ].join('-'); + return [record.gameProvider, record.gameId].join('-'); +} + +function gameCountryRankRowKey(record: Record) { + return [record.countryCode, record.gameProvider, record.gameId].join('-'); +} + +function gameMetricRank(index: number) { + return (gameMetricPagination.current - 1) * gameMetricPagination.pageSize + index + 1; +} + +function gameDisplayText(record: Partial) { + return record.gameName || record.gameId || '-'; } function rechargeSourceName(sourceType: string) { @@ -1131,6 +1157,9 @@ watch(selectedCountryCodes, () => { if (activeTableTab.value === 'game') { gameMetricPagination.current = 1; void loadGameMetrics(); + if (gameCountryRankOpen.value) { + void loadGameCountryRank(); + } } }); @@ -1357,9 +1386,22 @@ onBeforeUnmount(() => { size="small" @change="handleGameMetricTableChange" > -