feat: enhance country config and dashboard charts
This commit is contained in:
parent
ccf7520107
commit
83ed9a1c6a
@ -214,6 +214,10 @@ export async function updateSysCountryCode(data: Record<string, any>) {
|
|||||||
return requestClient.put('/sys/country/code', data);
|
return requestClient.put('/sys/country/code', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function addSysCountryCode(data: Record<string, any>) {
|
||||||
|
return requestClient.post('/sys/country/code', data);
|
||||||
|
}
|
||||||
|
|
||||||
export async function refundAnchorTrackRecordPage(params: Record<string, any>) {
|
export async function refundAnchorTrackRecordPage(params: Record<string, any>) {
|
||||||
return requestClient.get<LegacyPageResult<Record<string, any>>>(
|
return requestClient.get<LegacyPageResult<Record<string, any>>>(
|
||||||
'/refund-anchor-track-record/page',
|
'/refund-anchor-track-record/page',
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import {
|
|||||||
simpleUploadFile,
|
simpleUploadFile,
|
||||||
} from '#/api/legacy/oss';
|
} from '#/api/legacy/oss';
|
||||||
import {
|
import {
|
||||||
|
addSysCountryCode,
|
||||||
pageSysCountryCode,
|
pageSysCountryCode,
|
||||||
updateSysCountryCode,
|
updateSysCountryCode,
|
||||||
} from '#/api/legacy/system';
|
} from '#/api/legacy/system';
|
||||||
@ -23,6 +24,7 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
Modal,
|
Modal,
|
||||||
Pagination,
|
Pagination,
|
||||||
|
Switch,
|
||||||
Table,
|
Table,
|
||||||
Tag,
|
Tag,
|
||||||
message,
|
message,
|
||||||
@ -37,6 +39,7 @@ const formOpen = ref(false);
|
|||||||
const submitLoading = ref(false);
|
const submitLoading = ref(false);
|
||||||
const uploadLoading = ref(false);
|
const uploadLoading = ref(false);
|
||||||
const fileInputRef = ref<HTMLInputElement | null>(null);
|
const fileInputRef = ref<HTMLInputElement | null>(null);
|
||||||
|
const formMode = ref<'create' | 'edit'>('edit');
|
||||||
|
|
||||||
const query = reactive<Record<string, any>>({
|
const query = reactive<Record<string, any>>({
|
||||||
aliasName: '',
|
aliasName: '',
|
||||||
@ -47,7 +50,10 @@ const query = reactive<Record<string, any>>({
|
|||||||
|
|
||||||
const form = reactive<Record<string, any>>({
|
const form = reactive<Record<string, any>>({
|
||||||
aliasName: '',
|
aliasName: '',
|
||||||
|
alphaThree: '',
|
||||||
|
alphaTwo: '',
|
||||||
countryName: '',
|
countryName: '',
|
||||||
|
countryNumeric: '',
|
||||||
countryCodeAliasesInput: '',
|
countryCodeAliasesInput: '',
|
||||||
id: '',
|
id: '',
|
||||||
nationalFlag: '',
|
nationalFlag: '',
|
||||||
@ -59,6 +65,8 @@ const form = reactive<Record<string, any>>({
|
|||||||
|
|
||||||
const columns: any[] = [
|
const columns: any[] = [
|
||||||
{ dataIndex: 'countryName', key: 'countryName', title: '国家名称', width: 200 },
|
{ dataIndex: 'countryName', key: 'countryName', title: '国家名称', width: 200 },
|
||||||
|
{ dataIndex: 'alphaTwo', key: 'alphaTwo', title: '二字码', width: 100 },
|
||||||
|
{ dataIndex: 'alphaThree', key: 'alphaThree', title: '三字码', width: 100 },
|
||||||
{ dataIndex: 'aliasName', key: 'aliasName', title: '别名', width: 160 },
|
{ dataIndex: 'aliasName', key: 'aliasName', title: '别名', width: 160 },
|
||||||
{ dataIndex: 'countryCodeAliases', key: 'countryCodeAliases', title: '国家码集合', width: 220 },
|
{ dataIndex: 'countryCodeAliases', key: 'countryCodeAliases', title: '国家码集合', width: 220 },
|
||||||
{ dataIndex: 'nationalFlag', key: 'nationalFlag', title: '国旗', width: 120 },
|
{ dataIndex: 'nationalFlag', key: 'nationalFlag', title: '国旗', width: 120 },
|
||||||
@ -94,8 +102,12 @@ function handlePageChange(page: number, pageSize: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function openEdit(record: Record<string, any>) {
|
function openEdit(record: Record<string, any>) {
|
||||||
|
formMode.value = 'edit';
|
||||||
form.aliasName = record.aliasName || '';
|
form.aliasName = record.aliasName || '';
|
||||||
|
form.alphaThree = record.alphaThree || '';
|
||||||
|
form.alphaTwo = record.alphaTwo || '';
|
||||||
form.countryName = record.countryName || '';
|
form.countryName = record.countryName || '';
|
||||||
|
form.countryNumeric = record.countryNumeric || '';
|
||||||
form.countryCodeAliasesInput = Array.isArray(record.countryCodeAliases)
|
form.countryCodeAliasesInput = Array.isArray(record.countryCodeAliases)
|
||||||
? record.countryCodeAliases.join(',')
|
? record.countryCodeAliases.join(',')
|
||||||
: '';
|
: '';
|
||||||
@ -108,6 +120,27 @@ function openEdit(record: Record<string, any>) {
|
|||||||
formOpen.value = true;
|
formOpen.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
form.aliasName = '';
|
||||||
|
form.alphaThree = '';
|
||||||
|
form.alphaTwo = '';
|
||||||
|
form.countryName = '';
|
||||||
|
form.countryNumeric = '';
|
||||||
|
form.countryCodeAliasesInput = '';
|
||||||
|
form.id = '';
|
||||||
|
form.nationalFlag = '';
|
||||||
|
form.open = false;
|
||||||
|
form.phonePrefix = '';
|
||||||
|
form.sort = '';
|
||||||
|
form.top = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openCreate() {
|
||||||
|
resetForm();
|
||||||
|
formMode.value = 'create';
|
||||||
|
formOpen.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
function pickFlag() {
|
function pickFlag() {
|
||||||
fileInputRef.value?.click();
|
fileInputRef.value?.click();
|
||||||
}
|
}
|
||||||
@ -128,13 +161,18 @@ async function uploadFlag(event: Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function saveForm() {
|
async function saveForm() {
|
||||||
if (!form.id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!form.nationalFlag) {
|
if (!form.nationalFlag) {
|
||||||
message.warning('请上传国旗');
|
message.warning('请上传国旗');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!String(form.alphaTwo || '').trim()) {
|
||||||
|
message.warning('请填写国家二字码');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!String(form.alphaThree || '').trim()) {
|
||||||
|
message.warning('请填写国家三字码');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!String(form.countryName || '').trim()) {
|
if (!String(form.countryName || '').trim()) {
|
||||||
message.warning('请填写国家名称');
|
message.warning('请填写国家名称');
|
||||||
return;
|
return;
|
||||||
@ -151,15 +189,26 @@ async function saveForm() {
|
|||||||
try {
|
try {
|
||||||
const payload: Record<string, any> = {
|
const payload: Record<string, any> = {
|
||||||
...form,
|
...form,
|
||||||
|
alphaThree: String(form.alphaThree || '').trim().toUpperCase(),
|
||||||
|
alphaTwo: String(form.alphaTwo || '').trim().toUpperCase(),
|
||||||
|
countryNumeric: form.countryNumeric === '' ? undefined : Number(form.countryNumeric),
|
||||||
countryCodeAliases: String(form.countryCodeAliasesInput || '')
|
countryCodeAliases: String(form.countryCodeAliasesInput || '')
|
||||||
.split(',')
|
.split(',')
|
||||||
.map((item) => item.trim().toUpperCase())
|
.map((item) => item.trim().toUpperCase())
|
||||||
.filter(Boolean),
|
.filter(Boolean),
|
||||||
|
phonePrefix: Number(form.phonePrefix),
|
||||||
|
sort: Number(form.sort),
|
||||||
};
|
};
|
||||||
delete payload.countryCodeAliasesInput;
|
delete payload.countryCodeAliasesInput;
|
||||||
await updateSysCountryCode(payload);
|
if (formMode.value === 'create') {
|
||||||
|
delete payload.id;
|
||||||
|
await addSysCountryCode(payload);
|
||||||
|
} else {
|
||||||
|
await updateSysCountryCode(payload);
|
||||||
|
}
|
||||||
message.success('保存成功');
|
message.success('保存成功');
|
||||||
formOpen.value = false;
|
formOpen.value = false;
|
||||||
|
resetForm();
|
||||||
await loadData();
|
await loadData();
|
||||||
} finally {
|
} finally {
|
||||||
submitLoading.value = false;
|
submitLoading.value = false;
|
||||||
@ -192,6 +241,7 @@ loadData(true);
|
|||||||
style="width: 220px"
|
style="width: 220px"
|
||||||
/>
|
/>
|
||||||
<Button :loading="loading" type="primary" @click="handleSearch">搜索</Button>
|
<Button :loading="loading" type="primary" @click="handleSearch">搜索</Button>
|
||||||
|
<Button type="primary" @click="openCreate">新增</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Table
|
<Table
|
||||||
@ -274,7 +324,7 @@ loadData(true);
|
|||||||
:confirm-loading="submitLoading"
|
:confirm-loading="submitLoading"
|
||||||
:open="formOpen"
|
:open="formOpen"
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
title="修改"
|
:title="formMode === 'create' ? '新增' : '修改'"
|
||||||
@cancel="formOpen = false"
|
@cancel="formOpen = false"
|
||||||
@ok="saveForm"
|
@ok="saveForm"
|
||||||
>
|
>
|
||||||
@ -295,6 +345,15 @@ loadData(true);
|
|||||||
<FormItem label="国家名称">
|
<FormItem label="国家名称">
|
||||||
<Input v-model:value="form.countryName" />
|
<Input v-model:value="form.countryName" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
<FormItem label="国家二字码">
|
||||||
|
<Input v-model:value="form.alphaTwo" :maxlength="2" placeholder="例如:SA" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="国家三字码">
|
||||||
|
<Input v-model:value="form.alphaThree" :maxlength="3" placeholder="例如:SAU" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="数字码">
|
||||||
|
<Input v-model:value="form.countryNumeric" placeholder="例如:682" />
|
||||||
|
</FormItem>
|
||||||
<FormItem label="别名">
|
<FormItem label="别名">
|
||||||
<Input v-model:value="form.aliasName" />
|
<Input v-model:value="form.aliasName" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@ -307,6 +366,9 @@ loadData(true);
|
|||||||
<FormItem label="权重序号">
|
<FormItem label="权重序号">
|
||||||
<Input v-model:value="form.sort" />
|
<Input v-model:value="form.sort" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
<FormItem label="开放状态">
|
||||||
|
<Switch v-model:checked="form.open" checked-children="已开放" un-checked-children="未开放" />
|
||||||
|
</FormItem>
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@ -1,10 +1,14 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
CountryDashboardMetric,
|
||||||
CountryDashboardResult,
|
CountryDashboardResult,
|
||||||
} from '#/api/legacy/datav';
|
} from '#/api/legacy/datav';
|
||||||
|
|
||||||
import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
|
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
import { useAccessStore } from '@vben/stores';
|
import { useAccessStore } from '@vben/stores';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -34,6 +38,7 @@ const accessStore = useAccessStore();
|
|||||||
|
|
||||||
const datavFullscreen = ref<HTMLElement>();
|
const datavFullscreen = ref<HTMLElement>();
|
||||||
const fullscreen = ref(false);
|
const fullscreen = 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 dateRange = ref<[string, string] | null>([
|
const dateRange = ref<[string, string] | null>([
|
||||||
@ -47,6 +52,16 @@ const query = reactive({
|
|||||||
sysOrigin: '',
|
sysOrigin: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const userRechargeChartRef = ref<EchartsUIType>();
|
||||||
|
const flowChartRef = ref<EchartsUIType>();
|
||||||
|
const profitChartRef = ref<EchartsUIType>();
|
||||||
|
const luckyChartRef = ref<EchartsUIType>();
|
||||||
|
const { renderEcharts: renderUserRechargeChart } = useEcharts(userRechargeChartRef);
|
||||||
|
const { renderEcharts: renderFlowChart } = useEcharts(flowChartRef);
|
||||||
|
const { renderEcharts: renderProfitChart } = useEcharts(profitChartRef);
|
||||||
|
const { renderEcharts: renderLuckyChart } = useEcharts(luckyChartRef);
|
||||||
|
type ChartOption = Parameters<typeof renderUserRechargeChart>[0];
|
||||||
|
|
||||||
const accessCodes = computed(() => accessStore.accessCodes || []);
|
const accessCodes = computed(() => accessStore.accessCodes || []);
|
||||||
const sysOriginOptions = computed(() => {
|
const sysOriginOptions = computed(() => {
|
||||||
const options = getAllowedSysOrigins(accessCodes.value);
|
const options = getAllowedSysOrigins(accessCodes.value);
|
||||||
@ -100,6 +115,7 @@ const columns: any[] = [
|
|||||||
|
|
||||||
const total = computed(() => dashboard.value?.total || null);
|
const total = computed(() => dashboard.value?.total || null);
|
||||||
const records = computed(() => dashboard.value?.records || []);
|
const records = computed(() => dashboard.value?.records || []);
|
||||||
|
const chartRecords = computed(() => records.value.filter((item) => item.countryCode !== 'ALL'));
|
||||||
const summaryCards = computed(() => {
|
const summaryCards = computed(() => {
|
||||||
const item = total.value;
|
const item = total.value;
|
||||||
return [
|
return [
|
||||||
@ -141,6 +157,11 @@ async function screen() {
|
|||||||
await element.requestFullscreen();
|
await element.requestFullscreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleVisualMode() {
|
||||||
|
visualMode.value = !visualMode.value;
|
||||||
|
renderVisualCharts();
|
||||||
|
}
|
||||||
|
|
||||||
function defaultRange(periodType: string): [string, string] | null {
|
function defaultRange(periodType: string): [string, string] | null {
|
||||||
const now = dayjs();
|
const now = dayjs();
|
||||||
if (periodType === 'ALL') {
|
if (periodType === 'ALL') {
|
||||||
@ -188,6 +209,12 @@ async function loadDashboard() {
|
|||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
renderVisualCharts();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toAmount(value?: null | number | string) {
|
||||||
|
const number = Number(value || 0);
|
||||||
|
return Number.isFinite(number) ? number : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatInteger(value?: null | number | string) {
|
function formatInteger(value?: null | number | string) {
|
||||||
@ -254,6 +281,144 @@ function rowKey(record: Record<string, any>) {
|
|||||||
return `${record.countryCode}-${record.periodKey}`;
|
return `${record.countryCode}-${record.periodKey}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function chartCategory(record: CountryDashboardMetric) {
|
||||||
|
return `${record.periodName || '-'}\n${record.countryName || record.countryCode || '-'}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function amountSeries(name: string, dataIndex: keyof CountryDashboardMetric, list: CountryDashboardMetric[]) {
|
||||||
|
return {
|
||||||
|
data: list.map((item) => toAmount(item[dataIndex] as number | string)),
|
||||||
|
emphasis: { focus: 'series' },
|
||||||
|
name,
|
||||||
|
type: 'bar',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function lineSeries(name: string, dataIndex: keyof CountryDashboardMetric, list: CountryDashboardMetric[]) {
|
||||||
|
return {
|
||||||
|
data: list.map((item) => toAmount(item[dataIndex] as number | string)),
|
||||||
|
emphasis: { focus: 'series' },
|
||||||
|
name,
|
||||||
|
smooth: true,
|
||||||
|
type: 'line',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function valueYAxis(labelFormatter?: string) {
|
||||||
|
return {
|
||||||
|
axisLabel: {
|
||||||
|
color: '#94a3b8',
|
||||||
|
formatter: labelFormatter,
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: 'rgba(148, 163, 184, 0.16)' } },
|
||||||
|
type: 'value' as const,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function baseChartOption(title: string, list: CountryDashboardMetric[], series: any[]): ChartOption {
|
||||||
|
return {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
dataZoom: [
|
||||||
|
{ bottom: 6, height: 18, type: 'slider' },
|
||||||
|
{ type: 'inside' },
|
||||||
|
],
|
||||||
|
grid: {
|
||||||
|
bottom: 58,
|
||||||
|
containLabel: true,
|
||||||
|
left: 16,
|
||||||
|
right: 20,
|
||||||
|
top: 74,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
textStyle: { color: '#cbd5e1' },
|
||||||
|
top: 30,
|
||||||
|
type: 'scroll',
|
||||||
|
},
|
||||||
|
series,
|
||||||
|
textStyle: {
|
||||||
|
color: '#e2e8f0',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
left: 16,
|
||||||
|
text: title,
|
||||||
|
textStyle: {
|
||||||
|
color: '#f8fafc',
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: 700,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
axisLabel: {
|
||||||
|
color: '#94a3b8',
|
||||||
|
interval: 0,
|
||||||
|
},
|
||||||
|
axisLine: { lineStyle: { color: '#334155' } },
|
||||||
|
data: list.map((item) => chartCategory(item)),
|
||||||
|
type: 'category',
|
||||||
|
},
|
||||||
|
yAxis: valueYAxis(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function luckyChartOption(list: CountryDashboardMetric[]): ChartOption {
|
||||||
|
const option = baseChartOption('幸运礼物结构与利润率', list, [
|
||||||
|
amountSeries('幸运礼物流水', 'luckyGiftTotalFlow', list),
|
||||||
|
amountSeries('幸运礼物返奖', 'luckyGiftPayout', list),
|
||||||
|
amountSeries('幸运礼物利润', 'luckyGiftProfit', list),
|
||||||
|
{
|
||||||
|
...lineSeries('幸运礼物利润率', 'luckyGiftProfitRate', list),
|
||||||
|
yAxisIndex: 1,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
return {
|
||||||
|
...option,
|
||||||
|
yAxis: [
|
||||||
|
valueYAxis(),
|
||||||
|
{
|
||||||
|
axisLabel: {
|
||||||
|
color: '#94a3b8',
|
||||||
|
formatter: '{value}%',
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
type: 'value',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderVisualCharts() {
|
||||||
|
if (!visualMode.value || loading.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await nextTick();
|
||||||
|
const list = chartRecords.value;
|
||||||
|
if (list.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
renderUserRechargeChart(baseChartOption('新增用户与充值', list, [
|
||||||
|
amountSeries('新增用户', 'countryNewUser', list),
|
||||||
|
amountSeries('新增充值', 'newUserRecharge', list),
|
||||||
|
amountSeries('币商充值', 'dealerRecharge', list),
|
||||||
|
amountSeries('工资兑换', 'salaryExchange', list),
|
||||||
|
amountSeries('总充值', 'totalRecharge', list),
|
||||||
|
]));
|
||||||
|
renderFlowChart(baseChartOption('核心流水', list, [
|
||||||
|
amountSeries('礼物消耗', 'giftConsume', list),
|
||||||
|
amountSeries('幸运礼物流水', 'luckyGiftTotalFlow', list),
|
||||||
|
amountSeries('游戏流水', 'gameTotalFlow', list),
|
||||||
|
]));
|
||||||
|
renderProfitChart(baseChartOption('返奖与利润', list, [
|
||||||
|
amountSeries('幸运礼物返奖', 'luckyGiftPayout', list),
|
||||||
|
amountSeries('幸运礼物利润', 'luckyGiftProfit', list),
|
||||||
|
amountSeries('游戏返奖', 'gamePayout', list),
|
||||||
|
amountSeries('游戏利润', 'gameProfit', list),
|
||||||
|
]));
|
||||||
|
renderLuckyChart(luckyChartOption(list));
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
sysOriginOptions,
|
sysOriginOptions,
|
||||||
(options) => {
|
(options) => {
|
||||||
@ -264,6 +429,10 @@ watch(
|
|||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch([records, visualMode], () => {
|
||||||
|
renderVisualCharts();
|
||||||
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.addEventListener('fullscreenchange', syncFullscreenState);
|
document.addEventListener('fullscreenchange', syncFullscreenState);
|
||||||
loadDashboard();
|
loadDashboard();
|
||||||
@ -290,13 +459,18 @@ onBeforeUnmount(() => {
|
|||||||
国家维度 · {{ dashboard?.computedAt || '-' }}
|
国家维度 · {{ dashboard?.computedAt || '-' }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="datav-header__center">
|
||||||
|
<Button
|
||||||
|
:type="visualMode ? 'primary' : 'default'"
|
||||||
|
@click="toggleVisualMode"
|
||||||
|
>
|
||||||
|
{{ visualMode ? '表格数据' : '可视化图表' }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<Button ghost @click="screen">
|
<Button ghost @click="screen">
|
||||||
{{ fullscreen ? '退出全屏' : '全屏' }}
|
{{ fullscreen ? '退出全屏' : '全屏' }}
|
||||||
</Button>
|
</Button>
|
||||||
<Button :loading="loading" type="primary" @click="loadDashboard">
|
|
||||||
查询
|
|
||||||
</Button>
|
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -350,32 +524,51 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
<section class="panel dashboard-panel">
|
<section class="panel dashboard-panel">
|
||||||
<Spin :spinning="loading">
|
<Spin :spinning="loading">
|
||||||
<Table
|
<template v-if="visualMode">
|
||||||
v-if="records.length > 0"
|
<div v-if="chartRecords.length > 0" class="chart-grid">
|
||||||
:columns="columns"
|
<section class="chart-card">
|
||||||
:data-source="records"
|
<EchartsUI ref="userRechargeChartRef" class="chart-canvas" />
|
||||||
:pagination="{ pageSize: 20, showSizeChanger: true }"
|
</section>
|
||||||
:row-key="rowKey"
|
<section class="chart-card">
|
||||||
:scroll="{ x: 2060 }"
|
<EchartsUI ref="flowChartRef" class="chart-canvas" />
|
||||||
size="small"
|
</section>
|
||||||
>
|
<section class="chart-card">
|
||||||
<template #bodyCell="{ column, record }">
|
<EchartsUI ref="profitChartRef" class="chart-canvas" />
|
||||||
<template v-if="metricColumns.has(String(column.dataIndex))">
|
</section>
|
||||||
<Tooltip v-if="column.dataIndex === 'luckyGiftProfit'">
|
<section class="chart-card">
|
||||||
<template #title>
|
<EchartsUI ref="luckyChartRef" class="chart-canvas" />
|
||||||
主播分成:{{ formatAmount(record.luckyGiftAnchorShare) }}
|
</section>
|
||||||
</template>
|
</div>
|
||||||
<span class="number-cell">
|
<Empty v-else />
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<Table
|
||||||
|
v-if="records.length > 0"
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="records"
|
||||||
|
:pagination="{ pageSize: 20, showSizeChanger: true }"
|
||||||
|
:row-key="rowKey"
|
||||||
|
:scroll="{ x: 2060 }"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="metricColumns.has(String(column.dataIndex))">
|
||||||
|
<Tooltip v-if="column.dataIndex === 'luckyGiftProfit'">
|
||||||
|
<template #title>
|
||||||
|
主播分成:{{ formatAmount(record.luckyGiftAnchorShare) }}
|
||||||
|
</template>
|
||||||
|
<span class="number-cell">
|
||||||
|
{{ tableCellText(record, String(column.dataIndex)) }}
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
<span v-else class="number-cell">
|
||||||
{{ tableCellText(record, String(column.dataIndex)) }}
|
{{ tableCellText(record, String(column.dataIndex)) }}
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</template>
|
||||||
<span v-else class="number-cell">
|
|
||||||
{{ tableCellText(record, String(column.dataIndex)) }}
|
|
||||||
</span>
|
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</Table>
|
||||||
</Table>
|
<Empty v-else />
|
||||||
<Empty v-else />
|
</template>
|
||||||
</Spin>
|
</Spin>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -422,6 +615,12 @@ onBeforeUnmount(() => {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.datav-header__center {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.datav-title {
|
.datav-title {
|
||||||
color: #f8fafc;
|
color: #f8fafc;
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
@ -483,6 +682,25 @@ onBeforeUnmount(() => {
|
|||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chart-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-card {
|
||||||
|
background: #111827;
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.14);
|
||||||
|
border-radius: 8px;
|
||||||
|
min-height: 360px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-canvas {
|
||||||
|
height: 340px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.number-cell {
|
.number-cell {
|
||||||
color: #e2e8f0;
|
color: #e2e8f0;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
@ -539,11 +757,17 @@ onBeforeUnmount(() => {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.datav-header__center {
|
||||||
|
justify-content: flex-start;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.datav-title {
|
.datav-title {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.summary-grid,
|
.summary-grid,
|
||||||
|
.chart-grid,
|
||||||
.legacy-grid {
|
.legacy-grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user