Files
schoolNews/schoolNewsWeb/src/views/admin/overview/SystemOverviewView.vue

304 lines
6.2 KiB
Vue
Raw Normal View History

2025-10-16 18:03:46 +08:00
<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>