From 90ab562f5eac24e0e3b334335f6d76438236f305 Mon Sep 17 00:00:00 2001
From: spring <2396852758@qq.com>
Date: 星期四, 21 八月 2025 10:04:30 +0800
Subject: [PATCH] 完成档案统计

---
 src/api/fileManagement/statistics.js          |   75 ++++++
 src/views/fileManagement/borrow/index.vue     |   12 
 src/views/fileManagement/statistics/index.vue |  539 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/api/fileManagement/document.js            |   25 ++
 4 files changed, 644 insertions(+), 7 deletions(-)

diff --git a/src/api/fileManagement/document.js b/src/api/fileManagement/document.js
index 49b156e..f3d5f4f 100644
--- a/src/api/fileManagement/document.js
+++ b/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",
+  });
+}
diff --git a/src/api/fileManagement/statistics.js b/src/api/fileManagement/statistics.js
new file mode 100644
index 0000000..d77375c
--- /dev/null
+++ b/src/api/fileManagement/statistics.js
@@ -0,0 +1,75 @@
+import request from "@/utils/request";
+
+// 鑾峰彇妗f鎬讳綋缁熻
+export function getDocumentStatistics() {
+  return request({
+    url: "/fileManagement/statistics/overview",
+    method: "get",
+  });
+}
+
+// 鑾峰彇妗f鍒嗙被缁熻
+export function getCategoryStatistics() {
+  return request({
+    url: "/fileManagement/statistics/category",
+    method: "get",
+  });
+}
+
+// 鑾峰彇妗f鐘舵�佺粺璁�
+export function getStatusStatistics() {
+  return request({
+    url: "/fileManagement/statistics/status",
+    method: "get",
+  });
+}
+
+// 鑾峰彇妗f鍊熼槄缁熻
+export function getBorrowStatistics() {
+  return request({
+    url: "/fileManagement/statistics/borrow",
+    method: "get",
+  });
+}
+
+// 鑾峰彇妗f骞村害缁熻
+export function getYearStatistics() {
+  return request({
+    url: "/fileManagement/statistics/year",
+    method: "get",
+  });
+}
+
+// 鑾峰彇妗f浣嶇疆缁熻
+export function getLocationStatistics() {
+  return request({
+    url: "/fileManagement/statistics/location",
+    method: "get",
+  });
+}
+
+// 鑾峰彇妗f瓒嬪娍缁熻
+export function getTrendStatistics(params) {
+  return request({
+    url: "/fileManagement/statistics/trend",
+    method: "get",
+    params: params,
+  });
+}
+
+// 鑾峰彇妗f鍊熼槄鎺掕
+export function getBorrowRanking() {
+  return request({
+    url: "/fileManagement/statistics/borrowRanking",
+    method: "get",
+  });
+}
+
+// 鑾峰彇妗f鍒嗙被璇︽儏缁熻
+export function getCategoryDetailStatistics(categoryId) {
+  return request({
+    url: `/fileManagement/statistics/categoryDetail/${categoryId}`,
+    method: "get",
+  });
+}
+
diff --git a/src/views/fileManagement/borrow/index.vue b/src/views/fileManagement/borrow/index.vue
index 9a9ee81..6875571 100644
--- a/src/views/fileManagement/borrow/index.vue
+++ b/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) => {
+  // 鍏堝埛鏂版枃妗e垪琛�
+  await loadDocumentList();
+  
   borrowOperationType.value = type;
   borrowDia.value = true;
   
diff --git a/src/views/fileManagement/statistics/index.vue b/src/views/fileManagement/statistics/index.vue
new file mode 100644
index 0000000..42b81e4
--- /dev/null
+++ b/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>妗f鍒嗙被缁熻</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>妗f鐘舵�佺粺璁�</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: "鍊熷嚭妗f",
+    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: "妗f鍒嗙被鍒嗗竷",
+      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: "妗f鏁伴噺",
+        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: "妗f鐘舵�佸垎甯�",
+      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: "妗f鏁伴噺",
+        type: "pie",
+        radius: ["40%", "70%"],
+        center: ["60%", "50%"],
+        roseType: false,
+        data: newData || [
+          { name: "姝e父", 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>

--
Gitblit v1.9.3