304 lines
6.2 KiB
Vue
304 lines
6.2 KiB
Vue
|
|
<template>
|
||
|
|
<div class="system-overview">
|
||
|
|
<h1 class="page-title">系统总览</h1>
|
||
|
|
|
||
|
|
<!-- 统计卡片 -->
|
||
|
|
<div class="stats-grid">
|
||
|
|
<div class="stat-card" v-for="stat in statistics" :key="stat.label">
|
||
|
|
<div class="stat-icon" :style="{ background: stat.color }">
|
||
|
|
<i>{{ stat.icon }}</i>
|
||
|
|
</div>
|
||
|
|
<div class="stat-content">
|
||
|
|
<h3>{{ stat.value }}</h3>
|
||
|
|
<p>{{ stat.label }}</p>
|
||
|
|
<span class="stat-change" :class="stat.trend">
|
||
|
|
{{ stat.change }}
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- 图表区域 -->
|
||
|
|
<el-row :gutter="20" class="charts-row">
|
||
|
|
<el-col :span="16">
|
||
|
|
<el-card class="chart-card">
|
||
|
|
<template #header>
|
||
|
|
<div class="card-header">
|
||
|
|
<span>用户活跃度折线图</span>
|
||
|
|
<el-date-picker
|
||
|
|
v-model="dateRange"
|
||
|
|
type="daterange"
|
||
|
|
range-separator="至"
|
||
|
|
start-placeholder="开始日期"
|
||
|
|
end-placeholder="结束日期"
|
||
|
|
size="small"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
<div class="chart-container" ref="activityChart"></div>
|
||
|
|
</el-card>
|
||
|
|
</el-col>
|
||
|
|
|
||
|
|
<el-col :span="8">
|
||
|
|
<el-card class="chart-card">
|
||
|
|
<template #header>
|
||
|
|
<span>资源分类统计(饼图)</span>
|
||
|
|
</template>
|
||
|
|
<div class="chart-container" ref="resourcePieChart"></div>
|
||
|
|
</el-card>
|
||
|
|
</el-col>
|
||
|
|
</el-row>
|
||
|
|
|
||
|
|
<!-- 今日访问量详情 -->
|
||
|
|
<el-card class="visit-card">
|
||
|
|
<template #header>
|
||
|
|
<span>今日访问量</span>
|
||
|
|
</template>
|
||
|
|
<div class="visit-stats">
|
||
|
|
<div class="visit-item" v-for="item in visitStats" :key="item.label">
|
||
|
|
<div class="visit-label">{{ item.label }}</div>
|
||
|
|
<div class="visit-value">{{ item.value }}</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</el-card>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script setup lang="ts">
|
||
|
|
import { ref, onMounted, onUnmounted } from 'vue';
|
||
|
|
import { ElRow, ElCol, ElCard, ElDatePicker } from 'element-plus';
|
||
|
|
import * as echarts from 'echarts';
|
||
|
|
|
||
|
|
const dateRange = ref<[Date, Date] | null>(null);
|
||
|
|
const activityChart = ref<HTMLElement | null>(null);
|
||
|
|
const resourcePieChart = ref<HTMLElement | null>(null);
|
||
|
|
let activityChartInstance: echarts.ECharts | null = null;
|
||
|
|
let pieChartInstance: echarts.ECharts | null = null;
|
||
|
|
|
||
|
|
const statistics = ref([
|
||
|
|
{
|
||
|
|
icon: '👥',
|
||
|
|
label: '总用户数',
|
||
|
|
value: '1,234',
|
||
|
|
change: '+12%',
|
||
|
|
trend: 'up',
|
||
|
|
color: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'
|
||
|
|
},
|
||
|
|
{
|
||
|
|
icon: '📚',
|
||
|
|
label: '总资源数',
|
||
|
|
value: '5,678',
|
||
|
|
change: '+8%',
|
||
|
|
trend: 'up',
|
||
|
|
color: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)'
|
||
|
|
},
|
||
|
|
{
|
||
|
|
icon: '👁',
|
||
|
|
label: '今日访问量',
|
||
|
|
value: '892',
|
||
|
|
change: '+15%',
|
||
|
|
trend: 'up',
|
||
|
|
color: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)'
|
||
|
|
},
|
||
|
|
{
|
||
|
|
icon: '✅',
|
||
|
|
label: '活跃用户',
|
||
|
|
value: '456',
|
||
|
|
change: '+5%',
|
||
|
|
trend: 'up',
|
||
|
|
color: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)'
|
||
|
|
}
|
||
|
|
]);
|
||
|
|
|
||
|
|
const visitStats = ref([
|
||
|
|
{ label: 'UV(独立访客)', value: '892' },
|
||
|
|
{ label: 'PV(页面浏览量)', value: '3,456' },
|
||
|
|
{ label: '平均访问时长', value: '5分32秒' },
|
||
|
|
{ label: '跳出率', value: '35.6%' }
|
||
|
|
]);
|
||
|
|
|
||
|
|
onMounted(() => {
|
||
|
|
initCharts();
|
||
|
|
// TODO: 加载实际数据
|
||
|
|
});
|
||
|
|
|
||
|
|
onUnmounted(() => {
|
||
|
|
if (activityChartInstance) {
|
||
|
|
activityChartInstance.dispose();
|
||
|
|
}
|
||
|
|
if (pieChartInstance) {
|
||
|
|
pieChartInstance.dispose();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
function initCharts() {
|
||
|
|
if (activityChart.value) {
|
||
|
|
activityChartInstance = echarts.init(activityChart.value);
|
||
|
|
const activityOption = {
|
||
|
|
tooltip: {
|
||
|
|
trigger: 'axis'
|
||
|
|
},
|
||
|
|
xAxis: {
|
||
|
|
type: 'category',
|
||
|
|
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
|
||
|
|
},
|
||
|
|
yAxis: {
|
||
|
|
type: 'value'
|
||
|
|
},
|
||
|
|
series: [{
|
||
|
|
data: [120, 200, 150, 80, 70, 110, 130],
|
||
|
|
type: 'line',
|
||
|
|
smooth: true,
|
||
|
|
areaStyle: {}
|
||
|
|
}]
|
||
|
|
};
|
||
|
|
activityChartInstance.setOption(activityOption);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (resourcePieChart.value) {
|
||
|
|
pieChartInstance = echarts.init(resourcePieChart.value);
|
||
|
|
const pieOption = {
|
||
|
|
tooltip: {
|
||
|
|
trigger: 'item'
|
||
|
|
},
|
||
|
|
legend: {
|
||
|
|
orient: 'vertical',
|
||
|
|
left: 'left'
|
||
|
|
},
|
||
|
|
series: [{
|
||
|
|
type: 'pie',
|
||
|
|
radius: '50%',
|
||
|
|
data: [
|
||
|
|
{ value: 1048, name: '文章' },
|
||
|
|
{ value: 735, name: '视频' },
|
||
|
|
{ value: 580, name: '音频' },
|
||
|
|
{ value: 484, name: '课程' },
|
||
|
|
{ value: 300, name: '其他' }
|
||
|
|
]
|
||
|
|
}]
|
||
|
|
};
|
||
|
|
pieChartInstance.setOption(pieOption);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style lang="scss" scoped>
|
||
|
|
.system-overview {
|
||
|
|
}
|
||
|
|
|
||
|
|
.page-title {
|
||
|
|
font-size: 28px;
|
||
|
|
font-weight: 600;
|
||
|
|
color: #141F38;
|
||
|
|
margin-bottom: 24px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.stats-grid {
|
||
|
|
display: grid;
|
||
|
|
grid-template-columns: repeat(4, 1fr);
|
||
|
|
gap: 20px;
|
||
|
|
margin-bottom: 20px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.stat-card {
|
||
|
|
background: white;
|
||
|
|
padding: 24px;
|
||
|
|
border-radius: 8px;
|
||
|
|
display: flex;
|
||
|
|
gap: 16px;
|
||
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
|
|
}
|
||
|
|
|
||
|
|
.stat-icon {
|
||
|
|
width: 64px;
|
||
|
|
height: 64px;
|
||
|
|
border-radius: 12px;
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: center;
|
||
|
|
font-size: 32px;
|
||
|
|
flex-shrink: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.stat-content {
|
||
|
|
flex: 1;
|
||
|
|
|
||
|
|
h3 {
|
||
|
|
font-size: 28px;
|
||
|
|
font-weight: 600;
|
||
|
|
color: #141F38;
|
||
|
|
margin-bottom: 4px;
|
||
|
|
}
|
||
|
|
|
||
|
|
p {
|
||
|
|
font-size: 14px;
|
||
|
|
color: #666;
|
||
|
|
margin-bottom: 4px;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
.stat-change {
|
||
|
|
font-size: 13px;
|
||
|
|
|
||
|
|
&.up {
|
||
|
|
color: #4caf50;
|
||
|
|
}
|
||
|
|
|
||
|
|
&.down {
|
||
|
|
color: #f44336;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
.charts-row {
|
||
|
|
margin-bottom: 20px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.chart-card {
|
||
|
|
:deep(.el-card__body) {
|
||
|
|
padding: 20px;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
.card-header {
|
||
|
|
display: flex;
|
||
|
|
justify-content: space-between;
|
||
|
|
align-items: center;
|
||
|
|
}
|
||
|
|
|
||
|
|
.chart-container {
|
||
|
|
height: 300px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.visit-card {
|
||
|
|
:deep(.el-card__body) {
|
||
|
|
padding: 20px;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
.visit-stats {
|
||
|
|
display: grid;
|
||
|
|
grid-template-columns: repeat(4, 1fr);
|
||
|
|
gap: 20px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.visit-item {
|
||
|
|
text-align: center;
|
||
|
|
padding: 20px;
|
||
|
|
background: #f9f9f9;
|
||
|
|
border-radius: 8px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.visit-label {
|
||
|
|
font-size: 14px;
|
||
|
|
color: #666;
|
||
|
|
margin-bottom: 8px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.visit-value {
|
||
|
|
font-size: 24px;
|
||
|
|
font-weight: 600;
|
||
|
|
color: #C62828;
|
||
|
|
}
|
||
|
|
</style>
|
||
|
|
|