95 lines
2.9 KiB
Vue
95 lines
2.9 KiB
Vue
<template>
|
|
<div class="page-shell">
|
|
<div class="toolbar">
|
|
<div>
|
|
<h2 class="section-title">{{ '\u4eea\u8868\u76d8' }}</h2>
|
|
<div class="tag">{{ '\u5b9e\u65f6\u6982\u89c8' }}</div>
|
|
</div>
|
|
<el-button type="primary" plain @click="loadData">{{ '\u5237\u65b0' }}</el-button>
|
|
</div>
|
|
|
|
<div class="stat-grid">
|
|
<div class="stat-card" v-for="item in overviewCards" :key="item.label">
|
|
<div class="stat-label">{{ item.label }}</div>
|
|
<div class="stat-value">{{ item.value }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid-two">
|
|
<div class="glass-card panel">
|
|
<div class="chart-header">
|
|
<h3 class="section-title">{{ '\u6d3b\u8dc3\u8d8b\u52bf' }}</h3>
|
|
<span class="tag">{{ '\u6700\u8fd130\u5929' }}</span>
|
|
</div>
|
|
<TrendChart :trend="trend" />
|
|
</div>
|
|
<div class="glass-card panel">
|
|
<div class="chart-header">
|
|
<h3 class="section-title">{{ '\u9879\u76ee\u5206\u5e03' }}</h3>
|
|
<span class="tag">{{ '\u6d3b\u8dc3\u5361\u5bc6' }}</span>
|
|
</div>
|
|
<el-table :data="projectDistribution" height="320">
|
|
<el-table-column prop="project" :label="'\u9879\u76ee'" min-width="120" />
|
|
<el-table-column prop="count" :label="'\u5361\u5bc6'" width="120" />
|
|
</el-table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, onMounted, ref } from 'vue';
|
|
import { ElMessage } from 'element-plus';
|
|
import TrendChart from '@/components/TrendChart.vue';
|
|
import { statsDashboard } from '@/api/admin';
|
|
|
|
const data = ref({ overview: {}, trend: {}, projectDistribution: [] });
|
|
|
|
const overviewCards = computed(() => {
|
|
const overview = data.value.overview || {};
|
|
return [
|
|
{ label: '\u9879\u76ee\u603b\u6570', value: overview.totalProjects ?? 0 },
|
|
{ label: '\u5361\u5bc6\u603b\u6570', value: overview.totalCards ?? 0 },
|
|
{ label: '\u6d3b\u8dc3\u5361\u5bc6', value: overview.activeCards ?? 0 },
|
|
{ label: '\u5728\u7ebf\u8bbe\u5907', value: overview.activeDevices ?? 0 },
|
|
{ label: '\u4eca\u65e5\u6536\u5165', value: overview.todayRevenue ?? 0 },
|
|
{ label: '\u672c\u6708\u6536\u5165', value: overview.monthRevenue ?? 0 }
|
|
];
|
|
});
|
|
|
|
const trend = computed(() => data.value.trend || {});
|
|
const projectDistribution = computed(() => data.value.projectDistribution || []);
|
|
|
|
const loadData = async () => {
|
|
try {
|
|
data.value = await statsDashboard();
|
|
} catch (err) {
|
|
ElMessage.error(err?.message || '\u52a0\u8f7d\u4eea\u8868\u76d8\u5931\u8d25');
|
|
}
|
|
};
|
|
|
|
onMounted(loadData);
|
|
</script>
|
|
|
|
<style scoped>
|
|
.grid-two {
|
|
display: grid;
|
|
grid-template-columns: 2fr 1fr;
|
|
gap: 16px;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.chart-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
@media (max-width: 1024px) {
|
|
.grid-two {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|