This commit is contained in:
170-carry 2026-04-28 11:35:19 +08:00
parent e65f57f387
commit 7aed3ce16d
2 changed files with 109 additions and 10 deletions

View File

@ -40,7 +40,7 @@ const routes: RouteRecordRaw[] = [
path: 'specified-gift-weekly-rank',
alias: 'week-star',
component: () => import('#/views/resident-activity/week-star.vue'),
meta: { title: '指定礼物周榜' },
meta: { title: '周星' },
},
{
name: 'ResidentRoomTurnoverReward',

View File

@ -48,6 +48,15 @@ const fullscreen = ref(false);
const visualMode = ref(false);
const loading = ref(false);
const dashboard = ref<CountryDashboardResult | null>(null);
const upDashboards = ref<{
day: CountryDashboardResult | null;
threeDay: CountryDashboardResult | null;
week: CountryDashboardResult | null;
}>({
day: null,
threeDay: null,
week: null,
});
const dateRange = ref<[string, string] | null>([
dayjs().subtract(6, 'day').format('YYYY-MM-DD'),
dayjs().format('YYYY-MM-DD'),
@ -220,7 +229,11 @@ const columns: any[] = [
{ align: 'right', dataIndex: 'gamePayoutRate', key: 'gamePayoutRate', title: '游戏返奖率', width: 140 },
{ align: 'right', dataIndex: 'gameProfit', key: 'gameProfit', title: '游戏利润', width: 130 },
{ align: 'right', dataIndex: 'gameProfitRate', key: 'gameProfitRate', title: '游戏利润率', width: 140 },
];
].map((column) => ({
...column,
sortDirections: ['ascend', 'descend'],
sorter: compareTableColumn(String(column.dataIndex)),
}));
const tableScrollX = columns.reduce((totalWidth, item) => totalWidth + Number(item.width || 0), 0);
const rawRecords = computed(() => dashboard.value?.records || []);
@ -238,11 +251,7 @@ const countryOptions = computed(() => {
.toSorted((current, next) => current.label.localeCompare(next.label));
});
const records = computed(() => {
const selected = new Set(selectedCountryCodes.value);
if (selected.size === 0) {
return rawRecords.value;
}
return rawRecords.value.filter((item) => selected.has(String(item.countryCode || '')));
return filterRecordsBySelectedCountries(rawRecords.value);
});
const chartRecords = computed(() => records.value.filter((item) => item.countryCode !== 'ALL'));
const visibleSummary = computed(() => buildVisibleSummary(records.value));
@ -273,6 +282,14 @@ const appDataCards = computed(() => {
{ label: '30 日留存率', value: formatPercent(summary.d30RetentionRate) },
];
});
const upValueCards = computed(() => {
return [
{ label: '日 UP 值', value: formatUpValue(upDashboards.value.day) },
{ label: '3 日 UP 值', value: formatUpValue(upDashboards.value.threeDay) },
{ label: '周 UP 值', value: formatUpValue(upDashboards.value.week) },
{ label: '筛选 UP 值', value: formatAmount(calcUpValue(visibleSummary.value)) },
];
});
function hasPermission(code: string) {
const codes = accessCodes.value;
@ -338,15 +355,43 @@ function buildParams() {
return params;
}
function buildRecentDayParams(dayCount: number) {
const end = dayjs();
const start = end.subtract(dayCount - 1, 'day');
return {
periodType: 'DAY',
startDate: start.format('YYYY-MM-DD'),
endDate: end.format('YYYY-MM-DD'),
sysOrigin: query.sysOrigin || undefined,
};
}
async function loadDashboard() {
if (!canQuery.value) {
return;
}
loading.value = true;
try {
dashboard.value = await countryDashboard(buildParams());
const [mainDashboard, dayUpDashboard, threeDayUpDashboard, weekUpDashboard] =
await Promise.all([
countryDashboard(buildParams()),
countryDashboard(buildRecentDayParams(1)),
countryDashboard(buildRecentDayParams(3)),
countryDashboard(buildRecentDayParams(7)),
]);
dashboard.value = mainDashboard;
upDashboards.value = {
day: dayUpDashboard,
threeDay: threeDayUpDashboard,
week: weekUpDashboard,
};
} catch (error) {
dashboard.value = null;
upDashboards.value = {
day: null,
threeDay: null,
week: null,
};
message.error('数据大屏加载失败');
throw error;
} finally {
@ -450,6 +495,19 @@ function resolveMetricRate(record: Record<string, any>, key: string) {
return 0;
}
function calcUpValue(summary: Record<string, any>) {
const activeUser = toAmount(summary.dailyActiveUser);
if (activeUser === 0) {
return 0;
}
return toAmount(summary.totalRecharge) / activeUser;
}
function formatUpValue(result?: CountryDashboardResult | null) {
const summary = buildVisibleSummary(filterRecordsBySelectedCountries(result?.records || []));
return formatAmount(calcUpValue(summary));
}
function emptySummary(): SummaryMetric {
return {
countryNewUser: 0,
@ -529,6 +587,29 @@ function tableCellText(record: Record<string, any>, key: string) {
return formatAmount(record[key] as number | string);
}
function compareTableColumn(key: string) {
return (current: Record<string, any>, next: Record<string, any>) => {
if (metricColumns.has(key)) {
const currentValue = percentMetricColumns.has(key)
? resolveMetricRate(current, key)
: toAmount(current[key]);
const nextValue = percentMetricColumns.has(key)
? resolveMetricRate(next, key)
: toAmount(next[key]);
return currentValue - nextValue;
}
return String(current[key] || '').localeCompare(String(next[key] || ''));
};
}
function filterRecordsBySelectedCountries(list: CountryDashboardMetric[]) {
const selected = new Set(selectedCountryCodes.value);
if (selected.size === 0) {
return list;
}
return list.filter((item) => selected.has(String(item.countryCode || '')));
}
function rowKey(record: Record<string, any>) {
return `${record.countryCode}-${record.periodKey}`;
}
@ -805,6 +886,19 @@ onBeforeUnmount(() => {
</div>
</div>
</section>
<section class="panel metric-section">
<div class="metric-section__title">UP </div>
<div class="metric-card-grid metric-card-grid--up">
<div
v-for="item in upValueCards"
:key="item.label"
class="metric-card"
>
<div class="metric-card__label">{{ item.label }}</div>
<div class="metric-card__value">{{ item.value }}</div>
</div>
</div>
</section>
</div>
<section class="panel summary-table-panel">
@ -956,7 +1050,7 @@ onBeforeUnmount(() => {
.metric-section-grid {
display: grid;
gap: 16px;
grid-template-columns: minmax(0, 3fr) minmax(280px, 2fr);
grid-template-columns: minmax(0, 3fr) minmax(260px, 2fr) minmax(260px, 2fr);
}
.metric-section {
@ -980,6 +1074,10 @@ onBeforeUnmount(() => {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.metric-card-grid--up {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.metric-card {
background: #111827;
border: 1px solid rgba(45, 212, 191, 0.18);
@ -1096,7 +1194,8 @@ onBeforeUnmount(() => {
.metric-section-grid,
.metric-card-grid,
.metric-card-grid--app {
.metric-card-grid--app,
.metric-card-grid--up {
grid-template-columns: 1fr;
}
}