<template>
|
<div class="app-container statistics-container">
|
|
<!-- 总体统计卡片 -->
|
<el-row :gutter="20" class="statistics-cards">
|
<el-col :span="6" v-for="(item, index) in overviewData" :key="index">
|
<el-card class="statistics-card" :class="item.type">
|
<div class="card-content">
|
<div class="card-icon">
|
<el-icon :size="32">
|
<component :is="item.icon" />
|
</el-icon>
|
</div>
|
<div class="card-info">
|
<div class="card-number">
|
<el-skeleton-item v-if="loading" variant="text" style="width: 60px; height: 32px;" />
|
<span v-else>{{ item.value }}</span>
|
</div>
|
<div class="card-label">{{ item.label }}</div>
|
</div>
|
</div>
|
</el-card>
|
</el-col>
|
</el-row>
|
|
<!-- 图表区域 -->
|
<el-row :gutter="20" class="charts-section">
|
<el-col :span="12">
|
<el-card class="chart-card">
|
<template #header>
|
<div class="card-header">
|
<span>档案分类统计</span>
|
</div>
|
</template>
|
<div class="chart-container">
|
<div ref="categoryChartRef" class="chart"></div>
|
</div>
|
</el-card>
|
</el-col>
|
|
<el-col :span="12">
|
<el-card class="chart-card">
|
<template #header>
|
<div class="card-header">
|
<span>档案状态统计</span>
|
</div>
|
</template>
|
<div class="chart-container">
|
<div ref="statusChartRef" class="chart"></div>
|
</div>
|
</el-card>
|
</el-col>
|
</el-row>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, onMounted, nextTick, onUnmounted } from "vue";
|
import { ElMessage } from "element-plus";
|
import { Refresh } from "@element-plus/icons-vue";
|
import * as echarts from "echarts";
|
import {
|
getDocumentationOverview,
|
getDocumentationCategoryStats,
|
getDocumentationStatusStats
|
} from "@/api/fileManagement/document";
|
import {
|
Document,
|
Folder,
|
Tickets,
|
Calendar
|
} from "@element-plus/icons-vue";
|
|
// 响应式数据
|
const overviewData = ref([
|
{
|
label: "总档案数",
|
value: 0,
|
icon: "Document",
|
type: "primary",
|
},
|
{
|
label: "分类数量",
|
value: 0,
|
icon: "Folder",
|
type: "success",
|
},
|
{
|
label: "借出档案",
|
value: 0,
|
icon: "Tickets",
|
type: "warning",
|
},
|
{
|
label: "本月新增",
|
value: 0,
|
icon: "Calendar",
|
type: "info",
|
},
|
]);
|
|
const categoryChartRef = ref(null);
|
const statusChartRef = ref(null);
|
|
// 图表实例
|
let categoryChart = null;
|
let statusChart = null;
|
|
// 加载状态
|
const loading = ref(false);
|
const autoRefreshInterval = ref(null);
|
|
// 自动刷新开关
|
const autoRefreshEnabled = ref(true);
|
|
// 自动刷新间隔(5分钟)
|
const AUTO_REFRESH_INTERVAL = 5 * 60 * 1000;
|
|
// 启动自动刷新
|
const startAutoRefresh = () => {
|
if (autoRefreshInterval.value) {
|
clearInterval(autoRefreshInterval.value);
|
}
|
if (autoRefreshEnabled.value) {
|
autoRefreshInterval.value = setInterval(() => {
|
refreshData();
|
}, AUTO_REFRESH_INTERVAL);
|
}
|
};
|
|
// 停止自动刷新
|
const stopAutoRefresh = () => {
|
if (autoRefreshInterval.value) {
|
clearInterval(autoRefreshInterval.value);
|
autoRefreshInterval.value = null;
|
}
|
};
|
|
// 切换自动刷新状态
|
const toggleAutoRefresh = (value) => {
|
if (value) {
|
startAutoRefresh();
|
} else {
|
stopAutoRefresh();
|
}
|
};
|
|
// 加载总体统计数据
|
const loadOverviewData = async () => {
|
try {
|
const response = await getDocumentationOverview();
|
if (response.code === 200) {
|
const data = response.data;
|
overviewData.value[0].value = data.totalDocsCount || 0;
|
overviewData.value[1].value = data.categoryNumCount || 0;
|
overviewData.value[2].value = data.borrowedDocsCount || 0;
|
overviewData.value[3].value = data.monthlyAddedDocsCount || 0;
|
}
|
} catch (error) {
|
console.error('加载总体统计数据失败:', error);
|
ElMessage.error('加载总体统计数据失败');
|
}
|
};
|
|
// 加载分类统计数据
|
const loadCategoryData = async () => {
|
try {
|
const response = await getDocumentationCategoryStats();
|
if (response.code === 200) {
|
renderCategoryChart(response.data);
|
}
|
} catch (error) {
|
console.error('加载分类统计数据失败:', error);
|
ElMessage.error('加载分类统计数据失败');
|
}
|
};
|
|
// 加载状态统计数据
|
const loadStatusData = async () => {
|
try {
|
const response = await getDocumentationStatusStats();
|
if (response.code === 200) {
|
renderStatusChart(response.data);
|
}
|
} catch (error) {
|
console.error('加载状态统计数据失败:', error);
|
ElMessage.error('加载状态统计数据失败');
|
}
|
};
|
|
// 刷新数据
|
const refreshData = async () => {
|
loading.value = true;
|
try {
|
await Promise.all([
|
loadOverviewData(),
|
loadCategoryData(),
|
loadStatusData()
|
]);
|
ElMessage.success('数据刷新成功');
|
} catch (error) {
|
console.error('刷新数据失败:', error);
|
ElMessage.error('刷新数据失败');
|
} finally {
|
loading.value = false;
|
}
|
};
|
|
// 初始化图表
|
const initCharts = () => {
|
// 延迟初始化,确保DOM元素已经渲染
|
setTimeout(() => {
|
if (categoryChartRef.value) {
|
categoryChart = echarts.init(categoryChartRef.value);
|
}
|
|
if (statusChartRef.value) {
|
statusChart = echarts.init(statusChartRef.value);
|
}
|
|
// 初始化完成后加载数据
|
loadCategoryData();
|
loadStatusData();
|
}, 300);
|
};
|
|
// 渲染分类统计图表
|
const renderCategoryChart = (data) => {
|
if (!categoryChart) return;
|
let newData = data.map(item => {
|
return {
|
name: item.category,
|
value: item.count
|
}
|
})
|
|
const option = {
|
title: {
|
text: "档案分类分布",
|
left: "center",
|
textStyle: {
|
fontSize: 16,
|
fontWeight: "normal",
|
},
|
},
|
tooltip: {
|
trigger: "item",
|
formatter: "{a} <br/>{b}: {c} ({d}%)",
|
},
|
legend: {
|
orient: "vertical",
|
left: "left",
|
top: "middle",
|
},
|
series: [
|
{
|
name: "档案数量",
|
type: "pie",
|
radius: ["40%", "70%"],
|
center: ["60%", "50%"],
|
data: newData || [
|
{ name: "技术文档", value: 450 },
|
{ name: "管理文档", value: 320 },
|
{ name: "财务文档", value: 280 },
|
{ name: "人事文档", value: 200 },
|
],
|
emphasis: {
|
itemStyle: {
|
shadowBlur: 10,
|
shadowOffsetX: 0,
|
shadowColor: "rgba(0, 0, 0, 0.5)",
|
},
|
},
|
},
|
],
|
};
|
|
try {
|
categoryChart.setOption(option);
|
} catch (error) {
|
console.error('分类图表渲染失败:', error);
|
}
|
};
|
|
// 渲染状态统计图表
|
const renderStatusChart = (data) => {
|
if (!statusChart) return;
|
let newData = data.map(item => {
|
return {
|
name: item.docStatus,
|
value: item.count
|
}
|
})
|
const option = {
|
title: {
|
text: "档案状态分布",
|
left: "center",
|
textStyle: {
|
fontSize: 16,
|
fontWeight: "normal",
|
},
|
},
|
tooltip: {
|
trigger: "item",
|
formatter: "{a} <br/>{b}: {c} ({d}%)",
|
},
|
legend: {
|
orient: "vertical",
|
left: "left",
|
top: "middle",
|
},
|
series: [
|
{
|
name: "档案数量",
|
type: "pie",
|
radius: ["40%", "70%"],
|
center: ["60%", "50%"],
|
roseType: false,
|
data: newData || [
|
{ name: "正常", value: 1150, itemStyle: { color: "#67C23A" } },
|
{ name: "借出", value: 89, itemStyle: { color: "#E6A23C" } },
|
{ name: "丢失", value: 8, itemStyle: { color: "#F56C6C" } },
|
{ name: "损坏", value: 4, itemStyle: { color: "#909399" } },
|
],
|
emphasis: {
|
itemStyle: {
|
shadowBlur: 10,
|
shadowOffsetX: 0,
|
shadowColor: "rgba(0, 0, 0, 0.5)",
|
},
|
},
|
},
|
],
|
};
|
|
try {
|
statusChart.setOption(option);
|
} catch (error) {
|
console.error('状态图表渲染失败:', error);
|
}
|
};
|
|
onMounted(() => {
|
loadOverviewData();
|
initCharts();
|
startAutoRefresh();
|
});
|
|
// 组件卸载时清理定时器
|
onUnmounted(() => {
|
stopAutoRefresh();
|
});
|
</script>
|
|
<style scoped>
|
.statistics-container {
|
padding: 20px;
|
background-color: #f5f7fa;
|
min-height: 100vh;
|
}
|
|
.page-header {
|
text-align: center;
|
margin-bottom: 30px;
|
padding: 20px;
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
border-radius: 12px;
|
color: white;
|
}
|
|
.page-header h2 {
|
color: white;
|
margin-bottom: 10px;
|
font-size: 28px;
|
font-weight: 600;
|
}
|
|
.page-header p {
|
color: rgba(255, 255, 255, 0.9);
|
font-size: 14px;
|
margin: 0 0 15px 0;
|
}
|
|
.header-controls {
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
margin-top: 10px;
|
gap: 20px;
|
}
|
|
.refresh-btn {
|
margin-left: 20px;
|
}
|
|
.statistics-cards {
|
margin-bottom: 30px;
|
}
|
|
.statistics-card {
|
border-radius: 12px;
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
transition: all 0.3s ease;
|
border: none;
|
overflow: hidden;
|
}
|
|
.statistics-card:hover {
|
transform: translateY(-5px);
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
}
|
|
.statistics-card.primary {
|
border-left: 4px solid #409EFF;
|
background: linear-gradient(135deg, #409EFF 0%, #36a3f7 100%);
|
}
|
|
.statistics-card.success {
|
border-left: 4px solid #67C23A;
|
background: linear-gradient(135deg, #67C23A 0%, #85ce61 100%);
|
}
|
|
.statistics-card.warning {
|
border-left: 4px solid #E6A23C;
|
background: linear-gradient(135deg, #E6A23C 0%, #ebb563 100%);
|
}
|
|
.statistics-card.info {
|
border-left: 4px solid #909399;
|
background: linear-gradient(135deg, #909399 0%, #a6a9ad 100%);
|
}
|
|
.card-content {
|
display: flex;
|
align-items: center;
|
padding: 20px;
|
}
|
|
.card-icon {
|
margin-right: 20px;
|
color: white;
|
}
|
|
.card-info {
|
flex: 1;
|
}
|
|
.card-number {
|
font-size: 32px;
|
font-weight: 600;
|
color: white;
|
margin-bottom: 5px;
|
}
|
|
.card-label {
|
font-size: 14px;
|
color: rgba(255, 255, 255, 0.9);
|
}
|
|
.charts-section {
|
margin-bottom: 30px;
|
}
|
|
.chart-card {
|
border-radius: 12px;
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
border: none;
|
}
|
|
.card-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
font-weight: 600;
|
color: #303133;
|
padding: 15px 20px;
|
border-bottom: 1px solid #ebeef5;
|
}
|
|
.chart-container {
|
height: 400px;
|
padding: 20px;
|
}
|
|
.chart {
|
width: 100%;
|
height: 100%;
|
}
|
|
/* 响应式设计 */
|
@media (max-width: 768px) {
|
.statistics-container {
|
padding: 10px;
|
}
|
|
.page-header {
|
padding: 15px;
|
}
|
|
.page-header h2 {
|
font-size: 24px;
|
}
|
|
.header-controls {
|
flex-direction: column;
|
gap: 15px;
|
}
|
|
.refresh-btn {
|
margin-left: 0;
|
}
|
|
.statistics-cards .el-col {
|
margin-bottom: 15px;
|
}
|
|
.charts-section .el-col {
|
margin-bottom: 20px;
|
}
|
|
.chart-container {
|
height: 300px;
|
}
|
}
|
|
@media (max-width: 480px) {
|
.page-header h2 {
|
font-size: 20px;
|
}
|
|
.card-number {
|
font-size: 24px;
|
}
|
|
.chart-container {
|
height: 250px;
|
}
|
}
|
</style>
|