gaoluyang
2026-06-04 b65aa66293688dcaf888072dbfa23e7d50136c36
src/views/fileManagement/statistics/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,539 @@
<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>