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