数据大屏
This commit is contained in:
parent
9d20c3a789
commit
34a84375e3
@ -15,6 +15,7 @@ export interface CountryDashboardMetric {
|
|||||||
d30RetentionUser: number;
|
d30RetentionUser: number;
|
||||||
dailyActiveUser: number;
|
dailyActiveUser: number;
|
||||||
dealerRecharge: number | string;
|
dealerRecharge: number | string;
|
||||||
|
googleRecharge: number | string;
|
||||||
gamePayoutRate: number | string;
|
gamePayoutRate: number | string;
|
||||||
gamePayout: number | string;
|
gamePayout: number | string;
|
||||||
gameProfit: number | string;
|
gameProfit: number | string;
|
||||||
@ -30,6 +31,7 @@ export interface CountryDashboardMetric {
|
|||||||
luckyGiftTotalFlow: number | string;
|
luckyGiftTotalFlow: number | string;
|
||||||
luckyGiftUser: number;
|
luckyGiftUser: number;
|
||||||
luckyGiftUserRate: number | string;
|
luckyGiftUserRate: number | string;
|
||||||
|
mifapayRecharge: number | string;
|
||||||
newUserRecharge: number | string;
|
newUserRecharge: number | string;
|
||||||
officialRecharge: number | string;
|
officialRecharge: number | string;
|
||||||
periodKey: string;
|
periodKey: string;
|
||||||
@ -47,12 +49,40 @@ export interface CountryDashboardResult {
|
|||||||
total: CountryDashboardMetric;
|
total: CountryDashboardMetric;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CountryDashboardRechargeDetail {
|
||||||
|
amount: number | string;
|
||||||
|
coinQuantity: number | string;
|
||||||
|
countryCode: string;
|
||||||
|
countryName: string;
|
||||||
|
createTime: string;
|
||||||
|
recordId: string;
|
||||||
|
remark: string;
|
||||||
|
sourceChannel: string;
|
||||||
|
sourceType: string;
|
||||||
|
userAccount: string;
|
||||||
|
userId: number | string;
|
||||||
|
userNickname: string;
|
||||||
|
}
|
||||||
|
|
||||||
export async function countryDashboard(params: Record<string, any> = {}) {
|
export async function countryDashboard(params: Record<string, any> = {}) {
|
||||||
return requestClient.get<CountryDashboardResult>('/datav/country-dashboard', {
|
return requestClient.get<CountryDashboardResult>('/datav/country-dashboard', {
|
||||||
params,
|
params,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function countryDashboardRechargeDetails(
|
||||||
|
params: Record<string, any> = {},
|
||||||
|
) {
|
||||||
|
return requestClient.get<{
|
||||||
|
current: number;
|
||||||
|
records: CountryDashboardRechargeDetail[];
|
||||||
|
size: number;
|
||||||
|
total: number;
|
||||||
|
}>('/datav/country-dashboard/recharge-details', {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function latestActiveUserCountryCode(params: Record<string, any> = {}) {
|
export async function latestActiveUserCountryCode(params: Record<string, any> = {}) {
|
||||||
return requestClient.get<Array<Record<string, any>>>(
|
return requestClient.get<Array<Record<string, any>>>(
|
||||||
'/datav/active/user-country-code',
|
'/datav/active/user-country-code',
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import type { EchartsUIType } from '@vben/plugins/echarts';
|
|||||||
|
|
||||||
import type {
|
import type {
|
||||||
CountryDashboardMetric,
|
CountryDashboardMetric,
|
||||||
|
CountryDashboardRechargeDetail,
|
||||||
CountryDashboardResult,
|
CountryDashboardResult,
|
||||||
} from '#/api/legacy/datav';
|
} from '#/api/legacy/datav';
|
||||||
|
|
||||||
@ -24,15 +25,21 @@ import {
|
|||||||
DateRangePicker,
|
DateRangePicker,
|
||||||
Empty,
|
Empty,
|
||||||
message,
|
message,
|
||||||
|
Modal,
|
||||||
Select,
|
Select,
|
||||||
Space,
|
Space,
|
||||||
Spin,
|
Spin,
|
||||||
|
TabPane,
|
||||||
Table,
|
Table,
|
||||||
|
Tabs,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from 'antdv-next';
|
} from 'antdv-next';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
import { countryDashboard } from '#/api/legacy/datav';
|
import {
|
||||||
|
countryDashboard,
|
||||||
|
countryDashboardRechargeDetails,
|
||||||
|
} from '#/api/legacy/datav';
|
||||||
import SysOriginSelect from '#/components/sys-origin-select.vue';
|
import SysOriginSelect from '#/components/sys-origin-select.vue';
|
||||||
import { getAllowedSysOrigins } from '#/views/system/shared';
|
import { getAllowedSysOrigins } from '#/views/system/shared';
|
||||||
|
|
||||||
@ -48,6 +55,7 @@ const fullscreen = ref(false);
|
|||||||
const visualMode = ref(false);
|
const visualMode = ref(false);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const dashboard = ref<CountryDashboardResult | null>(null);
|
const dashboard = ref<CountryDashboardResult | null>(null);
|
||||||
|
const activeTableTab = ref<'overview' | 'recharge'>('overview');
|
||||||
const arpuDashboards = ref<{
|
const arpuDashboards = ref<{
|
||||||
day: CountryDashboardResult | null;
|
day: CountryDashboardResult | null;
|
||||||
threeDay: CountryDashboardResult | null;
|
threeDay: CountryDashboardResult | null;
|
||||||
@ -62,6 +70,14 @@ const dateRange = ref<[string, string] | null>([
|
|||||||
dayjs().format('YYYY-MM-DD'),
|
dayjs().format('YYYY-MM-DD'),
|
||||||
]);
|
]);
|
||||||
const selectedCountryCodes = ref<string[]>([]);
|
const selectedCountryCodes = ref<string[]>([]);
|
||||||
|
const rechargeDetailOpen = ref(false);
|
||||||
|
const rechargeDetailLoading = ref(false);
|
||||||
|
const rechargeDetailRecords = ref<CountryDashboardRechargeDetail[]>([]);
|
||||||
|
const rechargeDetailPagination = reactive({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 100,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
|
||||||
const query = reactive({
|
const query = reactive({
|
||||||
periodType: 'DAY',
|
periodType: 'DAY',
|
||||||
@ -101,6 +117,7 @@ const metricColumns = new Set([
|
|||||||
'd30RetentionRate',
|
'd30RetentionRate',
|
||||||
'dailyActiveUser',
|
'dailyActiveUser',
|
||||||
'dealerRecharge',
|
'dealerRecharge',
|
||||||
|
'googleRecharge',
|
||||||
'gamePayout',
|
'gamePayout',
|
||||||
'gamePayoutRate',
|
'gamePayoutRate',
|
||||||
'gameProfit',
|
'gameProfit',
|
||||||
@ -115,6 +132,7 @@ const metricColumns = new Set([
|
|||||||
'luckyGiftTotalFlow',
|
'luckyGiftTotalFlow',
|
||||||
'luckyGiftUser',
|
'luckyGiftUser',
|
||||||
'luckyGiftUserRate',
|
'luckyGiftUserRate',
|
||||||
|
'mifapayRecharge',
|
||||||
'newUserRecharge',
|
'newUserRecharge',
|
||||||
'salaryExchange',
|
'salaryExchange',
|
||||||
'totalRecharge',
|
'totalRecharge',
|
||||||
@ -143,6 +161,8 @@ const summaryMetricKeys = [
|
|||||||
'dailyActiveUser',
|
'dailyActiveUser',
|
||||||
'newUserRecharge',
|
'newUserRecharge',
|
||||||
'dealerRecharge',
|
'dealerRecharge',
|
||||||
|
'mifapayRecharge',
|
||||||
|
'googleRecharge',
|
||||||
'salaryExchange',
|
'salaryExchange',
|
||||||
'totalRecharge',
|
'totalRecharge',
|
||||||
'giftConsume',
|
'giftConsume',
|
||||||
@ -167,6 +187,8 @@ const summaryMetricDefinitions = [
|
|||||||
{ key: 'dailyActiveUser', label: '当日活跃' },
|
{ key: 'dailyActiveUser', label: '当日活跃' },
|
||||||
{ key: 'newUserRecharge', label: '新增充值' },
|
{ key: 'newUserRecharge', label: '新增充值' },
|
||||||
{ key: 'dealerRecharge', label: '币商充值' },
|
{ key: 'dealerRecharge', label: '币商充值' },
|
||||||
|
{ key: 'mifapayRecharge', label: 'MifaPay 充值' },
|
||||||
|
{ key: 'googleRecharge', label: 'Google 充值' },
|
||||||
{ key: 'salaryExchange', label: '工资兑换' },
|
{ key: 'salaryExchange', label: '工资兑换' },
|
||||||
{ key: 'totalRecharge', label: '总充值' },
|
{ key: 'totalRecharge', label: '总充值' },
|
||||||
{ key: 'giftConsume', label: '礼物消耗' },
|
{ key: 'giftConsume', label: '礼物消耗' },
|
||||||
@ -202,7 +224,7 @@ const summaryColumns = [
|
|||||||
];
|
];
|
||||||
const summaryScrollX = summaryColumns.reduce((totalWidth, item) => totalWidth + item.width, 0);
|
const summaryScrollX = summaryColumns.reduce((totalWidth, item) => totalWidth + item.width, 0);
|
||||||
|
|
||||||
const columns: any[] = [
|
const overviewColumns: any[] = [
|
||||||
{ dataIndex: 'countryName', fixed: 'left' as const, key: 'countryName', title: '国家', width: 150 },
|
{ dataIndex: 'countryName', fixed: 'left' as const, key: 'countryName', title: '国家', width: 150 },
|
||||||
{ dataIndex: 'countryCode', key: 'countryCode', title: 'Code', width: 86 },
|
{ dataIndex: 'countryCode', key: 'countryCode', title: 'Code', width: 86 },
|
||||||
{ dataIndex: 'periodName', key: 'periodName', title: '周期', width: 110 },
|
{ dataIndex: 'periodName', key: 'periodName', title: '周期', width: 110 },
|
||||||
@ -213,6 +235,8 @@ const columns: any[] = [
|
|||||||
{ align: 'right', dataIndex: 'd30RetentionRate', key: 'd30RetentionRate', title: '30 日留存率', width: 150 },
|
{ align: 'right', dataIndex: 'd30RetentionRate', key: 'd30RetentionRate', title: '30 日留存率', width: 150 },
|
||||||
{ align: 'right', dataIndex: 'newUserRecharge', key: 'newUserRecharge', title: '新增充值', width: 130 },
|
{ align: 'right', dataIndex: 'newUserRecharge', key: 'newUserRecharge', title: '新增充值', width: 130 },
|
||||||
{ align: 'right', dataIndex: 'dealerRecharge', key: 'dealerRecharge', title: '币商充值', width: 130 },
|
{ align: 'right', dataIndex: 'dealerRecharge', key: 'dealerRecharge', title: '币商充值', width: 130 },
|
||||||
|
{ align: 'right', dataIndex: 'mifapayRecharge', key: 'mifapayRecharge', title: 'MifaPay 充值', width: 150 },
|
||||||
|
{ align: 'right', dataIndex: 'googleRecharge', key: 'googleRecharge', title: 'Google 充值', width: 140 },
|
||||||
{ align: 'right', dataIndex: 'salaryExchange', key: 'salaryExchange', title: '工资兑换', width: 130 },
|
{ align: 'right', dataIndex: 'salaryExchange', key: 'salaryExchange', title: '工资兑换', width: 130 },
|
||||||
{ align: 'right', dataIndex: 'totalRecharge', key: 'totalRecharge', title: '总充值', width: 130 },
|
{ align: 'right', dataIndex: 'totalRecharge', key: 'totalRecharge', title: '总充值', width: 130 },
|
||||||
{ align: 'right', dataIndex: 'giftConsume', key: 'giftConsume', title: '礼物消耗', width: 130 },
|
{ align: 'right', dataIndex: 'giftConsume', key: 'giftConsume', title: '礼物消耗', width: 130 },
|
||||||
@ -234,7 +258,47 @@ const columns: any[] = [
|
|||||||
sortDirections: ['ascend', 'descend'],
|
sortDirections: ['ascend', 'descend'],
|
||||||
sorter: compareTableColumn(String(column.dataIndex)),
|
sorter: compareTableColumn(String(column.dataIndex)),
|
||||||
}));
|
}));
|
||||||
const tableScrollX = columns.reduce((totalWidth, item) => totalWidth + Number(item.width || 0), 0);
|
|
||||||
|
const rechargeColumns: any[] = [
|
||||||
|
{ dataIndex: 'countryName', fixed: 'left' as const, key: 'countryName', title: '国家', width: 150 },
|
||||||
|
{ dataIndex: 'countryCode', key: 'countryCode', title: 'Code', width: 86 },
|
||||||
|
{ dataIndex: 'periodName', key: 'periodName', title: '周期', width: 110 },
|
||||||
|
{ align: 'right', dataIndex: 'dealerRecharge', key: 'dealerRecharge', title: '币商充值', width: 140 },
|
||||||
|
{ align: 'right', dataIndex: 'mifapayRecharge', key: 'mifapayRecharge', title: 'MifaPay 充值', width: 160 },
|
||||||
|
{ align: 'right', dataIndex: 'googleRecharge', key: 'googleRecharge', title: 'Google 充值', width: 150 },
|
||||||
|
{ align: 'right', dataIndex: 'totalRecharge', key: 'totalRecharge', title: '总充值', width: 140 },
|
||||||
|
].map((column) => ({
|
||||||
|
...column,
|
||||||
|
sortDirections: ['ascend', 'descend'],
|
||||||
|
sorter: compareTableColumn(String(column.dataIndex)),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const rechargeDetailColumns: any[] = [
|
||||||
|
{ dataIndex: 'createTime', key: 'createTime', title: '时间', width: 170 },
|
||||||
|
{ dataIndex: 'sourceType', key: 'sourceType', title: '来源', width: 120 },
|
||||||
|
{ dataIndex: 'user', key: 'user', title: '用户', width: 240 },
|
||||||
|
{ dataIndex: 'country', key: 'country', title: '国家', width: 160 },
|
||||||
|
{ align: 'right', dataIndex: 'amount', key: 'amount', title: '充值金额', width: 120 },
|
||||||
|
{ align: 'right', dataIndex: 'coinQuantity', key: 'coinQuantity', title: '金币', width: 130 },
|
||||||
|
{ dataIndex: 'sourceChannel', key: 'sourceChannel', title: '渠道/类型', width: 150 },
|
||||||
|
{ dataIndex: 'remark', key: 'remark', title: '备注', width: 220 },
|
||||||
|
];
|
||||||
|
const rechargeDetailScrollX = rechargeDetailColumns.reduce(
|
||||||
|
(totalWidth, item) => totalWidth + Number(item.width || 0),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
const rechargeSourceNameMap: Record<string, string> = {
|
||||||
|
DEALER: '币商充值',
|
||||||
|
MIFAPAY: 'MifaPay 充值',
|
||||||
|
OFFICIAL: '官方充值',
|
||||||
|
};
|
||||||
|
|
||||||
|
const activeColumns = computed(() =>
|
||||||
|
activeTableTab.value === 'recharge' ? rechargeColumns : overviewColumns,
|
||||||
|
);
|
||||||
|
const tableScrollX = computed(() =>
|
||||||
|
activeColumns.value.reduce((totalWidth, item) => totalWidth + Number(item.width || 0), 0),
|
||||||
|
);
|
||||||
|
|
||||||
const rawRecords = computed(() => dashboard.value?.records || []);
|
const rawRecords = computed(() => dashboard.value?.records || []);
|
||||||
const countryOptions = computed(() => {
|
const countryOptions = computed(() => {
|
||||||
@ -290,6 +354,10 @@ const arpuCards = computed(() => {
|
|||||||
{ label: '筛选 ARPU', value: formatAmount(calcArpu(visibleSummary.value)) },
|
{ label: '筛选 ARPU', value: formatAmount(calcArpu(visibleSummary.value)) },
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
const totalRechargeCard = computed(() => ({
|
||||||
|
label: '总充值',
|
||||||
|
value: formatAmount(visibleSummary.value.totalRecharge),
|
||||||
|
}));
|
||||||
|
|
||||||
function hasPermission(code: string) {
|
function hasPermission(code: string) {
|
||||||
const codes = accessCodes.value;
|
const codes = accessCodes.value;
|
||||||
@ -400,6 +468,54 @@ async function loadDashboard() {
|
|||||||
renderVisualCharts();
|
renderVisualCharts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadRechargeDetails() {
|
||||||
|
rechargeDetailLoading.value = true;
|
||||||
|
try {
|
||||||
|
const result = await countryDashboardRechargeDetails({
|
||||||
|
...buildParams(),
|
||||||
|
countryCodes: selectedCountryCodes.value.join(',') || undefined,
|
||||||
|
cursor: rechargeDetailPagination.current,
|
||||||
|
limit: rechargeDetailPagination.pageSize,
|
||||||
|
});
|
||||||
|
rechargeDetailRecords.value = result.records || [];
|
||||||
|
rechargeDetailPagination.total = Number(result.total || 0);
|
||||||
|
} finally {
|
||||||
|
rechargeDetailLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openRechargeDetails() {
|
||||||
|
rechargeDetailOpen.value = true;
|
||||||
|
rechargeDetailPagination.current = 1;
|
||||||
|
await loadRechargeDetails();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRechargeDetailTableChange(pagination: Record<string, any>) {
|
||||||
|
rechargeDetailPagination.current = Number(pagination.current || 1);
|
||||||
|
await loadRechargeDetails();
|
||||||
|
}
|
||||||
|
|
||||||
|
function rechargeDetailRowKey(record: Record<string, any>) {
|
||||||
|
return `${record.sourceType}-${record.recordId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function rechargeSourceName(sourceType: string) {
|
||||||
|
return rechargeSourceNameMap[sourceType] || sourceType || '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
function rechargeDetailUserText(record: Record<string, any>) {
|
||||||
|
const account = record.userAccount || record.userId;
|
||||||
|
return [account, record.userNickname].filter(Boolean).join(' / ') || '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
function rechargeDetailCountryText(record: Record<string, any>) {
|
||||||
|
return [record.countryName, record.countryCode].filter(Boolean).join(' / ') || '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
function showRechargeDetailTotal(total: number) {
|
||||||
|
return `共 ${total} 条`;
|
||||||
|
}
|
||||||
|
|
||||||
function toAmount(value?: null | number | string) {
|
function toAmount(value?: null | number | string) {
|
||||||
const number = Number(value || 0);
|
const number = Number(value || 0);
|
||||||
return Number.isFinite(number) ? number : 0;
|
return Number.isFinite(number) ? number : 0;
|
||||||
@ -522,6 +638,7 @@ function emptySummary(): SummaryMetric {
|
|||||||
d30RetentionUser: 0,
|
d30RetentionUser: 0,
|
||||||
dailyActiveUser: 0,
|
dailyActiveUser: 0,
|
||||||
dealerRecharge: 0,
|
dealerRecharge: 0,
|
||||||
|
googleRecharge: 0,
|
||||||
gamePayout: 0,
|
gamePayout: 0,
|
||||||
gamePayoutRate: 0,
|
gamePayoutRate: 0,
|
||||||
gameProfit: 0,
|
gameProfit: 0,
|
||||||
@ -536,6 +653,7 @@ function emptySummary(): SummaryMetric {
|
|||||||
luckyGiftTotalFlow: 0,
|
luckyGiftTotalFlow: 0,
|
||||||
luckyGiftUser: 0,
|
luckyGiftUser: 0,
|
||||||
luckyGiftUserRate: 0,
|
luckyGiftUserRate: 0,
|
||||||
|
mifapayRecharge: 0,
|
||||||
newUserRecharge: 0,
|
newUserRecharge: 0,
|
||||||
salaryExchange: 0,
|
salaryExchange: 0,
|
||||||
totalRecharge: 0,
|
totalRecharge: 0,
|
||||||
@ -738,6 +856,8 @@ async function renderVisualCharts() {
|
|||||||
amountSeries('新增用户', 'countryNewUser', list),
|
amountSeries('新增用户', 'countryNewUser', list),
|
||||||
amountSeries('新增充值', 'newUserRecharge', list),
|
amountSeries('新增充值', 'newUserRecharge', list),
|
||||||
amountSeries('币商充值', 'dealerRecharge', list),
|
amountSeries('币商充值', 'dealerRecharge', list),
|
||||||
|
amountSeries('MifaPay 充值', 'mifapayRecharge', list),
|
||||||
|
amountSeries('Google 充值', 'googleRecharge', list),
|
||||||
amountSeries('工资兑换', 'salaryExchange', list),
|
amountSeries('工资兑换', 'salaryExchange', list),
|
||||||
amountSeries('总充值', 'totalRecharge', list),
|
amountSeries('总充值', 'totalRecharge', list),
|
||||||
]));
|
]));
|
||||||
@ -859,6 +979,22 @@ onBeforeUnmount(() => {
|
|||||||
</Space>
|
</Space>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="panel metric-section recharge-total-panel">
|
||||||
|
<div class="metric-section__title">充值</div>
|
||||||
|
<div class="metric-card-grid metric-card-grid--recharge">
|
||||||
|
<div
|
||||||
|
class="metric-card metric-card--clickable"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
@click="openRechargeDetails"
|
||||||
|
@keydown.enter="openRechargeDetails"
|
||||||
|
>
|
||||||
|
<div class="metric-card__label">{{ totalRechargeCard.label }}</div>
|
||||||
|
<div class="metric-card__value">{{ totalRechargeCard.value }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div class="metric-section-grid">
|
<div class="metric-section-grid">
|
||||||
<section class="panel metric-section">
|
<section class="panel metric-section">
|
||||||
<div class="metric-section__title">幸运礼物 / 游戏</div>
|
<div class="metric-section__title">幸运礼物 / 游戏</div>
|
||||||
@ -938,9 +1074,13 @@ onBeforeUnmount(() => {
|
|||||||
<Empty v-else />
|
<Empty v-else />
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
<Tabs v-model:activeKey="activeTableTab" class="dashboard-tabs" size="small">
|
||||||
|
<TabPane key="overview" tab="指标总览" />
|
||||||
|
<TabPane key="recharge" tab="充值相关数据" />
|
||||||
|
</Tabs>
|
||||||
<Table
|
<Table
|
||||||
v-if="records.length > 0"
|
v-if="records.length > 0"
|
||||||
:columns="columns"
|
:columns="activeColumns"
|
||||||
:data-source="records"
|
:data-source="records"
|
||||||
:pagination="{ pageSize: 20, showSizeChanger: true }"
|
:pagination="{ pageSize: 20, showSizeChanger: true }"
|
||||||
:row-key="rowKey"
|
:row-key="rowKey"
|
||||||
@ -979,6 +1119,48 @@ onBeforeUnmount(() => {
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
v-model:open="rechargeDetailOpen"
|
||||||
|
:footer="null"
|
||||||
|
title="总充值明细"
|
||||||
|
width="1180px"
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
:columns="rechargeDetailColumns"
|
||||||
|
:data-source="rechargeDetailRecords"
|
||||||
|
:loading="rechargeDetailLoading"
|
||||||
|
:pagination="{
|
||||||
|
current: rechargeDetailPagination.current,
|
||||||
|
pageSize: rechargeDetailPagination.pageSize,
|
||||||
|
showSizeChanger: false,
|
||||||
|
showTotal: showRechargeDetailTotal,
|
||||||
|
total: rechargeDetailPagination.total,
|
||||||
|
}"
|
||||||
|
:row-key="rechargeDetailRowKey"
|
||||||
|
:scroll="{ x: rechargeDetailScrollX }"
|
||||||
|
size="small"
|
||||||
|
@change="handleRechargeDetailTableChange"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'sourceType'">
|
||||||
|
{{ rechargeSourceName(record.sourceType) }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'user'">
|
||||||
|
{{ rechargeDetailUserText(record) }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'country'">
|
||||||
|
{{ rechargeDetailCountryText(record) }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'amount'">
|
||||||
|
<span class="number-cell">{{ formatAmount(record.amount) }}</span>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'coinQuantity'">
|
||||||
|
<span class="number-cell">{{ formatAmount(record.coinQuantity) }}</span>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -1078,6 +1260,10 @@ onBeforeUnmount(() => {
|
|||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.metric-card-grid--recharge {
|
||||||
|
grid-template-columns: minmax(180px, 260px);
|
||||||
|
}
|
||||||
|
|
||||||
.metric-card {
|
.metric-card {
|
||||||
background: #111827;
|
background: #111827;
|
||||||
border: 1px solid rgba(45, 212, 191, 0.18);
|
border: 1px solid rgba(45, 212, 191, 0.18);
|
||||||
@ -1086,6 +1272,18 @@ onBeforeUnmount(() => {
|
|||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.metric-card--clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.16s ease, transform 0.16s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-card--clickable:focus-visible,
|
||||||
|
.metric-card--clickable:hover {
|
||||||
|
border-color: rgba(45, 212, 191, 0.72);
|
||||||
|
outline: none;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
.metric-card__label {
|
.metric-card__label {
|
||||||
color: #cbd5e1;
|
color: #cbd5e1;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@ -1108,6 +1306,22 @@ onBeforeUnmount(() => {
|
|||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dashboard-tabs {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-tabs :deep(.ant-tabs-nav) {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-tabs :deep(.ant-tabs-tab) {
|
||||||
|
color: #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-tabs :deep(.ant-tabs-tab-active .ant-tabs-tab-btn) {
|
||||||
|
color: #2dd4bf;
|
||||||
|
}
|
||||||
|
|
||||||
.chart-grid {
|
.chart-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
|||||||
@ -56,6 +56,7 @@ const query = reactive({
|
|||||||
const columns = [
|
const columns = [
|
||||||
{ dataIndex: 'sysOrigin', key: 'sysOrigin', title: '系统', width: 100 },
|
{ dataIndex: 'sysOrigin', key: 'sysOrigin', title: '系统', width: 100 },
|
||||||
{ dataIndex: 'userProfile', key: 'userProfile', title: '用户', width: 220 },
|
{ dataIndex: 'userProfile', key: 'userProfile', title: '用户', width: 220 },
|
||||||
|
{ dataIndex: 'shortId', key: 'shortId', title: '短ID', width: 120 },
|
||||||
{ dataIndex: 'regionName', key: 'regionName', title: '区域', width: 120 },
|
{ dataIndex: 'regionName', key: 'regionName', title: '区域', width: 120 },
|
||||||
{ dataIndex: 'bdQuantity', key: 'bdQuantity', title: 'BD数量', width: 100 },
|
{ dataIndex: 'bdQuantity', key: 'bdQuantity', title: 'BD数量', width: 100 },
|
||||||
{ dataIndex: 'contact', key: 'contact', title: '联系方式', width: 180 },
|
{ dataIndex: 'contact', key: 'contact', title: '联系方式', width: 180 },
|
||||||
@ -181,9 +182,6 @@ loadData(true);
|
|||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
<div class="user-name-row">
|
<div class="user-name-row">
|
||||||
<span>{{ record.userProfile?.userNickname || '-' }}</span>
|
<span>{{ record.userProfile?.userNickname || '-' }}</span>
|
||||||
<span class="user-short-id">
|
|
||||||
短ID:{{ record.userProfile?.actualAccount || '-' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="user-sub">
|
<div class="user-sub">
|
||||||
ID:{{ record.userProfile?.id || '-' }}
|
ID:{{ record.userProfile?.id || '-' }}
|
||||||
@ -191,6 +189,9 @@ loadData(true);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="column.key === 'shortId'">
|
||||||
|
{{ record.userProfile?.actualAccount || record.userProfile?.account || '-' }}
|
||||||
|
</template>
|
||||||
<template v-else-if="column.key === 'contact'">
|
<template v-else-if="column.key === 'contact'">
|
||||||
{{ canEdit ? record.contact || '-' : '-' }}
|
{{ canEdit ? record.contact || '-' : '-' }}
|
||||||
</template>
|
</template>
|
||||||
@ -283,11 +284,6 @@ loadData(true);
|
|||||||
gap: 6px;
|
gap: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-short-id {
|
|
||||||
color: #64748b;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-sub {
|
.user-sub {
|
||||||
color: #64748b;
|
color: #64748b;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user