spring
3 天以前 90ab562f5eac24e0e3b334335f6d76438236f305
完成档案统计
已修改2个文件
已添加2个文件
651 ■■■■■ 文件已修改
src/api/fileManagement/document.js 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/fileManagement/statistics.js 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fileManagement/borrow/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fileManagement/statistics/index.vue 539 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/fileManagement/document.js
@@ -162,3 +162,28 @@
    data: ids,
  });
}
// ç»Ÿè®¡ç›¸å…³æŽ¥å£
// èŽ·å–æ€»ä½“ç»Ÿè®¡æ•°æ®
export function getDocumentationOverview() {
  return request({
    url: "/documentation/overview",
    method: "get",
  });
}
// èŽ·å–åˆ†ç±»ç»Ÿè®¡æ•°æ®
export function getDocumentationCategoryStats() {
  return request({
    url: "/documentation/category",
    method: "get",
  });
}
// èŽ·å–çŠ¶æ€ç»Ÿè®¡æ•°æ®
export function getDocumentationStatusStats() {
  return request({
    url: "/documentation/status",
    method: "get",
  });
}
src/api/fileManagement/statistics.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,75 @@
import request from "@/utils/request";
// èŽ·å–æ¡£æ¡ˆæ€»ä½“ç»Ÿè®¡
export function getDocumentStatistics() {
  return request({
    url: "/fileManagement/statistics/overview",
    method: "get",
  });
}
// èŽ·å–æ¡£æ¡ˆåˆ†ç±»ç»Ÿè®¡
export function getCategoryStatistics() {
  return request({
    url: "/fileManagement/statistics/category",
    method: "get",
  });
}
// èŽ·å–æ¡£æ¡ˆçŠ¶æ€ç»Ÿè®¡
export function getStatusStatistics() {
  return request({
    url: "/fileManagement/statistics/status",
    method: "get",
  });
}
// èŽ·å–æ¡£æ¡ˆå€Ÿé˜…ç»Ÿè®¡
export function getBorrowStatistics() {
  return request({
    url: "/fileManagement/statistics/borrow",
    method: "get",
  });
}
// èŽ·å–æ¡£æ¡ˆå¹´åº¦ç»Ÿè®¡
export function getYearStatistics() {
  return request({
    url: "/fileManagement/statistics/year",
    method: "get",
  });
}
// èŽ·å–æ¡£æ¡ˆä½ç½®ç»Ÿè®¡
export function getLocationStatistics() {
  return request({
    url: "/fileManagement/statistics/location",
    method: "get",
  });
}
// èŽ·å–æ¡£æ¡ˆè¶‹åŠ¿ç»Ÿè®¡
export function getTrendStatistics(params) {
  return request({
    url: "/fileManagement/statistics/trend",
    method: "get",
    params: params,
  });
}
// èŽ·å–æ¡£æ¡ˆå€Ÿé˜…æŽ’è¡Œ
export function getBorrowRanking() {
  return request({
    url: "/fileManagement/statistics/borrowRanking",
    method: "get",
  });
}
// èŽ·å–æ¡£æ¡ˆåˆ†ç±»è¯¦æƒ…ç»Ÿè®¡
export function getCategoryDetailStatistics(categoryId) {
  return request({
    url: `/fileManagement/statistics/categoryDetail/${categoryId}`,
    method: "get",
  });
}
src/views/fileManagement/borrow/index.vue
@@ -233,13 +233,8 @@
const tableColumns = ref([
  { 
    label: '文档名称', 
    prop: 'documentationId',
    prop: 'docName',
    width: '200',
    formatData: (params) => {
      if (!params) return '-';
      const doc = documentList.value.find(item => item.id === params);
      return doc ? (doc.docName || doc.name) : params;
    }
  },
  { label: '借阅人', prop: 'borrower' },
  { label: '借阅目的', prop: 'borrowPurpose' },
@@ -374,7 +369,10 @@
};
// æ‰“开借阅弹框
const openBorrowDia = (type, data) => {
const openBorrowDia = async (type, data) => {
  // å…ˆåˆ·æ–°æ–‡æ¡£åˆ—表
  await loadDocumentList();
  borrowOperationType.value = type;
  borrowDia.value = true;
  
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>