first commit
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Deploy Website on push / Rerun on failure (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Deploy Website on push / Rerun on failure (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
This commit is contained in:
110
playground/src/views/dashboard/analytics/agent-ranking-table.vue
Normal file
110
playground/src/views/dashboard/analytics/agent-ranking-table.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { getAgentRanking } from '#/api/agent';
|
||||
import type { AgentApi } from '#/api/agent';
|
||||
|
||||
const loading = ref(false);
|
||||
const rankingType = ref<'commission' | 'orders'>('commission');
|
||||
const rankingList = ref<AgentApi.AgentRankingItem[]>([]);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
dataIndex: 'rank',
|
||||
key: 'rank',
|
||||
title: '排名',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
dataIndex: 'agent_mobile',
|
||||
key: 'agent_mobile',
|
||||
title: '代理手机号',
|
||||
},
|
||||
{
|
||||
dataIndex: 'region',
|
||||
key: 'region',
|
||||
title: '区域',
|
||||
},
|
||||
{
|
||||
dataIndex: 'value',
|
||||
key: 'value',
|
||||
title: '佣金(元)',
|
||||
width: 150,
|
||||
},
|
||||
];
|
||||
|
||||
async function loadRanking() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await getAgentRanking({
|
||||
type: rankingType.value,
|
||||
limit: 10,
|
||||
});
|
||||
rankingList.value = data.items.map((item, index) => ({
|
||||
...item,
|
||||
rank: index + 1,
|
||||
}));
|
||||
|
||||
// 更新列标题
|
||||
columns[3].title = rankingType.value === 'commission' ? '佣金(元)' : '订单数';
|
||||
} catch (error) {
|
||||
console.error('Failed to load agent ranking:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleTypeChange() {
|
||||
loadRanking();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadRanking();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="agent-ranking-table">
|
||||
<div class="ranking-header">
|
||||
<a-radio-group v-model:value="rankingType" button-style="solid" @change="handleTypeChange">
|
||||
<a-radio-button value="commission">佣金排行</a-radio-button>
|
||||
<a-radio-button value="orders">订单量排行</a-radio-button>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="rankingList"
|
||||
:loading="loading"
|
||||
:pagination="false"
|
||||
row-key="agent_id"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'rank'">
|
||||
<a-tag v-if="record.rank === 1" color="gold">1</a-tag>
|
||||
<a-tag v-else-if="record.rank === 2" color="silver">2</a-tag>
|
||||
<a-tag v-else-if="record.rank === 3" color="#cd7f32">3</a-tag>
|
||||
<span v-else>{{ record.rank }}</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'value'">
|
||||
{{
|
||||
rankingType === 'commission'
|
||||
? `¥${record.value.toFixed(2)}`
|
||||
: record.value
|
||||
}}
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.agent-ranking-table {
|
||||
.ranking-header {
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,77 @@
|
||||
<script lang="ts" setup>
|
||||
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||
import { getAgentTrends } from '#/api/agent';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
const loading = ref(false);
|
||||
|
||||
async function loadChartData() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await getAgentTrends();
|
||||
|
||||
renderEcharts({
|
||||
grid: {
|
||||
bottom: 40,
|
||||
containLabel: true,
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
top: '10%',
|
||||
},
|
||||
legend: {
|
||||
data: ['新增代理'],
|
||||
top: 0,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
barMaxWidth: 80,
|
||||
data: data.counts,
|
||||
itemStyle: {
|
||||
color: '#4f69fd',
|
||||
},
|
||||
name: '新增代理',
|
||||
type: 'bar',
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
axisPointer: {
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
},
|
||||
},
|
||||
trigger: 'axis',
|
||||
},
|
||||
xAxis: {
|
||||
data: data.dates,
|
||||
type: 'category',
|
||||
},
|
||||
yAxis: {
|
||||
name: '新增代理数',
|
||||
splitNumber: 4,
|
||||
type: 'value',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to load agent trends:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadChartData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="agent-trends-chart">
|
||||
<a-spin :spinning="loading">
|
||||
<EchartsUI ref="chartRef" />
|
||||
</a-spin>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,98 @@
|
||||
<script lang="ts" setup>
|
||||
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
|
||||
onMounted(() => {
|
||||
renderEcharts({
|
||||
grid: {
|
||||
bottom: 0,
|
||||
containLabel: true,
|
||||
left: '1%',
|
||||
right: '1%',
|
||||
top: '2 %',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
areaStyle: {},
|
||||
data: [
|
||||
111, 2000, 6000, 16_000, 33_333, 55_555, 64_000, 33_333, 18_000,
|
||||
36_000, 70_000, 42_444, 23_222, 13_000, 8000, 4000, 1200, 333, 222,
|
||||
111,
|
||||
],
|
||||
itemStyle: {
|
||||
color: '#5ab1ef',
|
||||
},
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
},
|
||||
{
|
||||
areaStyle: {},
|
||||
data: [
|
||||
33, 66, 88, 333, 3333, 6200, 20_000, 3000, 1200, 13_000, 22_000,
|
||||
11_000, 2221, 1201, 390, 198, 60, 30, 22, 11,
|
||||
],
|
||||
itemStyle: {
|
||||
color: '#019680',
|
||||
},
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
axisPointer: {
|
||||
lineStyle: {
|
||||
color: '#019680',
|
||||
width: 1,
|
||||
},
|
||||
},
|
||||
trigger: 'axis',
|
||||
},
|
||||
// xAxis: {
|
||||
// axisTick: {
|
||||
// show: false,
|
||||
// },
|
||||
// boundaryGap: false,
|
||||
// data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
||||
// type: 'category',
|
||||
// },
|
||||
xAxis: {
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
boundaryGap: false,
|
||||
data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'solid',
|
||||
width: 1,
|
||||
},
|
||||
show: true,
|
||||
},
|
||||
type: 'category',
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
max: 80_000,
|
||||
splitArea: {
|
||||
show: true,
|
||||
},
|
||||
splitNumber: 4,
|
||||
type: 'value',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EchartsUI ref="chartRef" />
|
||||
</template>
|
||||
@@ -0,0 +1,82 @@
|
||||
<script lang="ts" setup>
|
||||
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
|
||||
onMounted(() => {
|
||||
renderEcharts({
|
||||
legend: {
|
||||
bottom: 0,
|
||||
data: ['访问', '趋势'],
|
||||
},
|
||||
radar: {
|
||||
indicator: [
|
||||
{
|
||||
name: '网页',
|
||||
},
|
||||
{
|
||||
name: '移动端',
|
||||
},
|
||||
{
|
||||
name: 'Ipad',
|
||||
},
|
||||
{
|
||||
name: '客户端',
|
||||
},
|
||||
{
|
||||
name: '第三方',
|
||||
},
|
||||
{
|
||||
name: '其它',
|
||||
},
|
||||
],
|
||||
radius: '60%',
|
||||
splitNumber: 8,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
areaStyle: {
|
||||
opacity: 1,
|
||||
shadowBlur: 0,
|
||||
shadowColor: 'rgba(0,0,0,.2)',
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 10,
|
||||
},
|
||||
data: [
|
||||
{
|
||||
itemStyle: {
|
||||
color: '#b6a2de',
|
||||
},
|
||||
name: '访问',
|
||||
value: [90, 50, 86, 40, 50, 20],
|
||||
},
|
||||
{
|
||||
itemStyle: {
|
||||
color: '#5ab1ef',
|
||||
},
|
||||
name: '趋势',
|
||||
value: [70, 75, 70, 76, 20, 85],
|
||||
},
|
||||
],
|
||||
itemStyle: {
|
||||
// borderColor: '#fff',
|
||||
borderRadius: 10,
|
||||
borderWidth: 2,
|
||||
},
|
||||
symbolSize: 0,
|
||||
type: 'radar',
|
||||
},
|
||||
],
|
||||
tooltip: {},
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EchartsUI ref="chartRef" />
|
||||
</template>
|
||||
@@ -0,0 +1,46 @@
|
||||
<script lang="ts" setup>
|
||||
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
|
||||
onMounted(() => {
|
||||
renderEcharts({
|
||||
series: [
|
||||
{
|
||||
animationDelay() {
|
||||
return Math.random() * 400;
|
||||
},
|
||||
animationEasing: 'exponentialInOut',
|
||||
animationType: 'scale',
|
||||
center: ['50%', '50%'],
|
||||
color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],
|
||||
data: [
|
||||
{ name: '外包', value: 500 },
|
||||
{ name: '定制', value: 310 },
|
||||
{ name: '技术支持', value: 274 },
|
||||
{ name: '远程', value: 400 },
|
||||
].sort((a, b) => {
|
||||
return a.value - b.value;
|
||||
}),
|
||||
name: '商业占比',
|
||||
radius: '80%',
|
||||
roseType: 'radius',
|
||||
type: 'pie',
|
||||
},
|
||||
],
|
||||
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EchartsUI ref="chartRef" />
|
||||
</template>
|
||||
@@ -0,0 +1,65 @@
|
||||
<script lang="ts" setup>
|
||||
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
|
||||
onMounted(() => {
|
||||
renderEcharts({
|
||||
legend: {
|
||||
bottom: '2%',
|
||||
left: 'center',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
animationDelay() {
|
||||
return Math.random() * 100;
|
||||
},
|
||||
animationEasing: 'exponentialInOut',
|
||||
animationType: 'scale',
|
||||
avoidLabelOverlap: false,
|
||||
color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],
|
||||
data: [
|
||||
{ name: '搜索引擎', value: 1048 },
|
||||
{ name: '直接访问', value: 735 },
|
||||
{ name: '邮件营销', value: 580 },
|
||||
{ name: '联盟广告', value: 484 },
|
||||
],
|
||||
emphasis: {
|
||||
label: {
|
||||
fontSize: '12',
|
||||
fontWeight: 'bold',
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
itemStyle: {
|
||||
// borderColor: '#fff',
|
||||
borderRadius: 10,
|
||||
borderWidth: 2,
|
||||
},
|
||||
label: {
|
||||
position: 'center',
|
||||
show: false,
|
||||
},
|
||||
labelLine: {
|
||||
show: false,
|
||||
},
|
||||
name: '访问来源',
|
||||
radius: ['40%', '65%'],
|
||||
type: 'pie',
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EchartsUI ref="chartRef" />
|
||||
</template>
|
||||
@@ -0,0 +1,55 @@
|
||||
<script lang="ts" setup>
|
||||
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
|
||||
onMounted(() => {
|
||||
renderEcharts({
|
||||
grid: {
|
||||
bottom: 0,
|
||||
containLabel: true,
|
||||
left: '1%',
|
||||
right: '1%',
|
||||
top: '2 %',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
barMaxWidth: 80,
|
||||
// color: '#4f69fd',
|
||||
data: [
|
||||
3000, 2000, 3333, 5000, 3200, 4200, 3200, 2100, 3000, 5100, 6000,
|
||||
3200, 4800,
|
||||
],
|
||||
type: 'bar',
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
axisPointer: {
|
||||
lineStyle: {
|
||||
// color: '#4f69fd',
|
||||
width: 1,
|
||||
},
|
||||
},
|
||||
trigger: 'axis',
|
||||
},
|
||||
xAxis: {
|
||||
data: Array.from({ length: 12 }).map((_item, index) => `${index + 1}月`),
|
||||
type: 'category',
|
||||
},
|
||||
yAxis: {
|
||||
max: 8000,
|
||||
splitNumber: 4,
|
||||
type: 'value',
|
||||
},
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EchartsUI ref="chartRef" />
|
||||
</template>
|
||||
56
playground/src/views/dashboard/analytics/index.vue
Normal file
56
playground/src/views/dashboard/analytics/index.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<script lang="ts" setup>
|
||||
import type { TabOption } from '@vben/types';
|
||||
|
||||
import {
|
||||
AnalysisChartCard,
|
||||
AnalysisChartsTabs,
|
||||
} from '@vben/common-ui';
|
||||
|
||||
import StatisticsOverview from './statistics-overview.vue';
|
||||
import AgentRankingTable from './agent-ranking-table.vue';
|
||||
import AgentTrendsChart from './agent-trends-chart.vue';
|
||||
import OrderTrendsChart from './order-trends-chart.vue';
|
||||
import ProductDistributionChart from './product-distribution-chart.vue';
|
||||
import RegionDistributionChart from './region-distribution-chart.vue';
|
||||
|
||||
const chartTabs: TabOption[] = [
|
||||
{
|
||||
label: '订单趋势',
|
||||
value: 'order-trends',
|
||||
},
|
||||
{
|
||||
label: '代理注册趋势',
|
||||
value: 'agent-trends',
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-5">
|
||||
<!-- 统计概览 -->
|
||||
<StatisticsOverview />
|
||||
|
||||
<!-- 趋势图表 -->
|
||||
<AnalysisChartsTabs :tabs="chartTabs" class="mt-5">
|
||||
<template #order-trends>
|
||||
<OrderTrendsChart />
|
||||
</template>
|
||||
<template #agent-trends>
|
||||
<AgentTrendsChart />
|
||||
</template>
|
||||
</AnalysisChartsTabs>
|
||||
|
||||
<!-- 分布图表和排行榜 -->
|
||||
<div class="mt-5 w-full md:flex">
|
||||
<AnalysisChartCard class="mt-5 md:mr-4 md:mt-0 md:w-1/3" title="产品订单分布">
|
||||
<ProductDistributionChart />
|
||||
</AnalysisChartCard>
|
||||
<AnalysisChartCard class="mt-5 md:mr-4 md:mt-0 md:w-1/3" title="区域代理分布">
|
||||
<RegionDistributionChart />
|
||||
</AnalysisChartCard>
|
||||
<AnalysisChartCard class="mt-5 md:mt-0 md:w-1/3" title="代理排行榜">
|
||||
<AgentRankingTable />
|
||||
</AnalysisChartCard>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
116
playground/src/views/dashboard/analytics/order-trends-chart.vue
Normal file
116
playground/src/views/dashboard/analytics/order-trends-chart.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<script lang="ts" setup>
|
||||
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||
import { getOrderTrends } from '#/api/agent';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
const loading = ref(false);
|
||||
|
||||
async function loadChartData() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await getOrderTrends();
|
||||
|
||||
renderEcharts({
|
||||
grid: {
|
||||
bottom: 40,
|
||||
containLabel: true,
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
top: '10%',
|
||||
},
|
||||
legend: {
|
||||
data: ['订单金额', '订单数量'],
|
||||
top: 0,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
areaStyle: {},
|
||||
data: data.amounts,
|
||||
itemStyle: {
|
||||
color: '#5ab1ef',
|
||||
},
|
||||
name: '订单金额',
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
},
|
||||
{
|
||||
areaStyle: {},
|
||||
data: data.counts,
|
||||
itemStyle: {
|
||||
color: '#019680',
|
||||
},
|
||||
name: '订单数量',
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
axisPointer: {
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
},
|
||||
},
|
||||
trigger: 'axis',
|
||||
},
|
||||
xAxis: {
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
boundaryGap: false,
|
||||
data: data.dates,
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'solid',
|
||||
width: 1,
|
||||
},
|
||||
show: true,
|
||||
},
|
||||
type: 'category',
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
name: '金额(元)',
|
||||
splitArea: {
|
||||
show: true,
|
||||
},
|
||||
splitNumber: 4,
|
||||
type: 'value',
|
||||
},
|
||||
{
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
name: '订单数',
|
||||
splitNumber: 4,
|
||||
type: 'value',
|
||||
},
|
||||
],
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to load order trends:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadChartData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="order-trends-chart">
|
||||
<a-spin :spinning="loading">
|
||||
<EchartsUI ref="chartRef" />
|
||||
</a-spin>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,66 @@
|
||||
<script lang="ts" setup>
|
||||
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||
import { getProductDistribution } from '#/api/agent';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
const loading = ref(false);
|
||||
|
||||
async function loadChartData() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await getProductDistribution();
|
||||
|
||||
renderEcharts({
|
||||
legend: {
|
||||
bottom: 0,
|
||||
orient: 'horizontal',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: data.products.map((product, index) => ({
|
||||
name: product,
|
||||
value: data.counts[index],
|
||||
})),
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
},
|
||||
label: {
|
||||
formatter: '{b}: {c} ({d}%)',
|
||||
},
|
||||
radius: '70%',
|
||||
type: 'pie',
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
formatter: '{a} <br/>{b}: {c} ({d}%)',
|
||||
trigger: 'item',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to load product distribution:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadChartData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="product-distribution-chart">
|
||||
<a-spin :spinning="loading">
|
||||
<EchartsUI ref="chartRef" />
|
||||
</a-spin>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,97 @@
|
||||
<script lang="ts" setup>
|
||||
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||
import { getRegionDistribution } from '#/api/agent';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
const loading = ref(false);
|
||||
|
||||
async function loadChartData() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await getRegionDistribution();
|
||||
|
||||
renderEcharts({
|
||||
grid: {
|
||||
bottom: 40,
|
||||
containLabel: true,
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
top: '10%',
|
||||
},
|
||||
legend: {
|
||||
data: ['代理数量'],
|
||||
top: 0,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
barMaxWidth: 80,
|
||||
data: data.counts,
|
||||
itemStyle: {
|
||||
color: {
|
||||
colorStops: [
|
||||
{
|
||||
color: '#4f69fd',
|
||||
offset: 0,
|
||||
},
|
||||
{
|
||||
color: '#7c85ff',
|
||||
offset: 1,
|
||||
},
|
||||
],
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
},
|
||||
},
|
||||
name: '代理数量',
|
||||
type: 'bar',
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
axisPointer: {
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
},
|
||||
},
|
||||
trigger: 'axis',
|
||||
},
|
||||
xAxis: {
|
||||
axisLabel: {
|
||||
interval: 0,
|
||||
rotate: 30,
|
||||
},
|
||||
data: data.regions,
|
||||
type: 'category',
|
||||
},
|
||||
yAxis: {
|
||||
name: '代理数量',
|
||||
splitNumber: 4,
|
||||
type: 'value',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to load region distribution:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadChartData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="region-distribution-chart">
|
||||
<a-spin :spinning="loading">
|
||||
<EchartsUI ref="chartRef" />
|
||||
</a-spin>
|
||||
</div>
|
||||
</template>
|
||||
121
playground/src/views/dashboard/analytics/statistics-overview.vue
Normal file
121
playground/src/views/dashboard/analytics/statistics-overview.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import {
|
||||
getStatisticsOverview,
|
||||
} from '#/api/agent';
|
||||
import type { AgentApi } from '#/api/agent';
|
||||
|
||||
interface OverviewItem {
|
||||
title: string;
|
||||
value: number;
|
||||
prefix?: string;
|
||||
suffix?: string;
|
||||
}
|
||||
|
||||
const loading = ref(false);
|
||||
const overviews = ref<OverviewItem[]>([]);
|
||||
|
||||
async function loadOverview() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await getStatisticsOverview();
|
||||
overviews.value = [
|
||||
{
|
||||
title: '代理总数',
|
||||
value: data.total_agents,
|
||||
},
|
||||
{
|
||||
title: '今日新增代理',
|
||||
value: data.today_new_agents,
|
||||
},
|
||||
{
|
||||
title: '总订单数',
|
||||
value: data.total_orders,
|
||||
},
|
||||
{
|
||||
title: '今日订单数',
|
||||
value: data.today_orders,
|
||||
},
|
||||
{
|
||||
title: '总订单金额',
|
||||
value: data.total_order_amount,
|
||||
prefix: '¥',
|
||||
},
|
||||
{
|
||||
title: '今日订单金额',
|
||||
value: data.today_order_amount,
|
||||
prefix: '¥',
|
||||
},
|
||||
{
|
||||
title: '总佣金支出',
|
||||
value: data.total_commission,
|
||||
prefix: '¥',
|
||||
},
|
||||
{
|
||||
title: '今日佣金支出',
|
||||
value: data.today_commission,
|
||||
prefix: '¥',
|
||||
},
|
||||
{
|
||||
title: '本月订单金额',
|
||||
value: data.month_order_amount,
|
||||
prefix: '¥',
|
||||
},
|
||||
{
|
||||
title: '本月佣金支出',
|
||||
value: data.month_commission,
|
||||
prefix: '¥',
|
||||
},
|
||||
{
|
||||
title: '待审核提现',
|
||||
value: data.pending_withdraw,
|
||||
prefix: '¥',
|
||||
},
|
||||
];
|
||||
} catch (error) {
|
||||
console.error('Failed to load statistics overview:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadOverview();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="statistics-overview">
|
||||
<a-spin :spinning="loading">
|
||||
<a-row :gutter="[16, 16]">
|
||||
<a-col
|
||||
v-for="(item, index) in overviews"
|
||||
:key="index"
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:md="8"
|
||||
:lg="6"
|
||||
>
|
||||
<a-card class="overview-card">
|
||||
<a-statistic
|
||||
:title="item.title"
|
||||
:value="item.value"
|
||||
:prefix="item.prefix"
|
||||
:precision="item.prefix ? 2 : 0"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.statistics-overview {
|
||||
.overview-card {
|
||||
:deep(.ant-card-body) {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user