|
@@ -0,0 +1,876 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="resource-statistics">
|
|
|
|
|
+ <div class="container">
|
|
|
|
|
+ <h1>资源库统计分析</h1>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 摘要信息 -->
|
|
|
|
|
+ <div class="summary-row">
|
|
|
|
|
+ <div class="summary-box">
|
|
|
|
|
+ <h3>总资源数</h3>
|
|
|
|
|
+ <p>{{ summaryData.totalResources }}</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="summary-box">
|
|
|
|
|
+ <h3>总存储空间</h3>
|
|
|
|
|
+ <p>{{ summaryData.totalStorage }}</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="summary-box">
|
|
|
|
|
+ <h3>总观看人数</h3>
|
|
|
|
|
+ <p>{{ summaryData.totalViews }}</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="summary-box">
|
|
|
|
|
+ <h3>总收藏人数</h3>
|
|
|
|
|
+ <p>{{ summaryData.totalFavorites }}</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 存储空间信息 -->
|
|
|
|
|
+ <div class="storage-info">
|
|
|
|
|
+ <h3>资源库存储空间使用情况</h3>
|
|
|
|
|
+ <p>
|
|
|
|
|
+ 当前资源库总容量:{{ summaryData.totalCapacity }} | 已使用:{{ summaryData.totalStorage }} | 使用率:{{
|
|
|
|
|
+ summaryData.usageRate
|
|
|
|
|
+ }}
|
|
|
|
|
+ </p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 筛选器 -->
|
|
|
|
|
+ <div class="filters">
|
|
|
|
|
+ <label>筛选院系:</label>
|
|
|
|
|
+ <a-select
|
|
|
|
|
+ v-model:value="filters.department"
|
|
|
|
|
+ style="width: 150px; margin-right: 20px"
|
|
|
|
|
+ @change="handleDepartmentChange"
|
|
|
|
|
+ >
|
|
|
|
|
+ <a-select-option value="all">全部院系</a-select-option>
|
|
|
|
|
+ <a-select-option value="aviation">航空学院</a-select-option>
|
|
|
|
|
+ <a-select-option value="military">军事管理系</a-select-option>
|
|
|
|
|
+ <a-select-option value="politics">政治工作部</a-select-option>
|
|
|
|
|
+ <a-select-option value="maintenance">地面维修中心</a-select-option>
|
|
|
|
|
+ <a-select-option value="other">其他部门</a-select-option>
|
|
|
|
|
+ </a-select>
|
|
|
|
|
+
|
|
|
|
|
+ <label>时间范围:</label>
|
|
|
|
|
+ <a-select v-model:value="filters.timeRange" style="width: 120px" @change="handleTimeChange">
|
|
|
|
|
+ <a-select-option value="all">全部时间</a-select-option>
|
|
|
|
|
+ <a-select-option value="30">最近30天</a-select-option>
|
|
|
|
|
+ <a-select-option value="90">最近90天</a-select-option>
|
|
|
|
|
+ <a-select-option value="180">最近180天</a-select-option>
|
|
|
|
|
+ <a-select-option value="365">最近1年</a-select-option>
|
|
|
|
|
+ </a-select>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 资源类型分布 -->
|
|
|
|
|
+ <div class="chart-container">
|
|
|
|
|
+ <h2>资源类型分布</h2>
|
|
|
|
|
+ <div class="chart-row">
|
|
|
|
|
+ <div class="chart-col">
|
|
|
|
|
+ <div ref="typeChartRef" class="chart"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="chart-col">
|
|
|
|
|
+ <div ref="departmentChartRef" class="chart"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 资源可见性与热度 -->
|
|
|
|
|
+ <div class="chart-container">
|
|
|
|
|
+ <h2>资源可见性与热度</h2>
|
|
|
|
|
+ <div class="chart-row">
|
|
|
|
|
+ <div class="chart-col">
|
|
|
|
|
+ <div ref="visibilityChartRef" class="chart"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="chart-col">
|
|
|
|
|
+ <div ref="hotnessChartRef" class="chart"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 文件格式分布 -->
|
|
|
|
|
+ <div class="chart-container">
|
|
|
|
|
+ <h2>文件格式分布</h2>
|
|
|
|
|
+ <div class="chart-row">
|
|
|
|
|
+ <div class="chart-col">
|
|
|
|
|
+ <div ref="formatChartRef" class="chart"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="chart-col">
|
|
|
|
|
+ <div ref="storageChartRef" class="chart"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 资源使用情况分析 -->
|
|
|
|
|
+ <div class="chart-container">
|
|
|
|
|
+ <h2>资源使用情况分析</h2>
|
|
|
|
|
+ <div class="chart-row">
|
|
|
|
|
+ <div class="chart-col">
|
|
|
|
|
+ <div ref="viewsChartRef" class="chart"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="chart-col">
|
|
|
|
|
+ <div ref="engagementChartRef" class="chart"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 时间趋势分析 -->
|
|
|
|
|
+ <div class="chart-container">
|
|
|
|
|
+ <h2>上传与访问时间趋势</h2>
|
|
|
|
|
+ <div ref="trendChartRef" class="chart-full"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <footer>
|
|
|
|
|
+ <p>数据更新时间: {{ updateTime }} | 资源库统计分析系统</p>
|
|
|
|
|
+ </footer>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup>
|
|
|
|
|
+ import { ref, onMounted, onUnmounted, nextTick } from 'vue'
|
|
|
|
|
+ import * as echarts from 'echarts'
|
|
|
|
|
+ import {
|
|
|
|
|
+ getSummaryData,
|
|
|
|
|
+ getResourceTypeData,
|
|
|
|
|
+ getDepartmentData,
|
|
|
|
|
+ getVisibilityData,
|
|
|
|
|
+ getFormatData,
|
|
|
|
|
+ getEngagementData,
|
|
|
|
|
+ getTrendData
|
|
|
|
|
+ } from '@/api/statisticalAnalysis/statisticalAnalysisResourceLibrary'
|
|
|
|
|
+
|
|
|
|
|
+ // 响应式数据
|
|
|
|
|
+ const summaryData = ref({
|
|
|
|
|
+ totalResources: '0',
|
|
|
|
|
+ totalStorage: '0 TB',
|
|
|
|
|
+ totalCapacity: '10 TB',
|
|
|
|
|
+ usageRate: '0%',
|
|
|
|
|
+ totalViews: '0',
|
|
|
|
|
+ totalFavorites: '0'
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const filters = ref({
|
|
|
|
|
+ department: 'all',
|
|
|
|
|
+ timeRange: 'all'
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const updateTime = ref('2025年8月20日')
|
|
|
|
|
+
|
|
|
|
|
+ // 图表引用
|
|
|
|
|
+ const typeChartRef = ref()
|
|
|
|
|
+ const departmentChartRef = ref()
|
|
|
|
|
+ const visibilityChartRef = ref()
|
|
|
|
|
+ const hotnessChartRef = ref()
|
|
|
|
|
+ const formatChartRef = ref()
|
|
|
|
|
+ const storageChartRef = ref()
|
|
|
|
|
+ const viewsChartRef = ref()
|
|
|
|
|
+ const engagementChartRef = ref()
|
|
|
|
|
+ const trendChartRef = ref()
|
|
|
|
|
+
|
|
|
|
|
+ // 图表实例存储
|
|
|
|
|
+ const chartInstances = ref({
|
|
|
|
|
+ typeChart: null,
|
|
|
|
|
+ departmentChart: null,
|
|
|
|
|
+ visibilityChart: null,
|
|
|
|
|
+ hotnessChart: null,
|
|
|
|
|
+ formatChart: null,
|
|
|
|
|
+ storageChart: null,
|
|
|
|
|
+ viewsChart: null,
|
|
|
|
|
+ engagementChart: null,
|
|
|
|
|
+ trendChart: null
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 数据存储
|
|
|
|
|
+ const chartData = ref({
|
|
|
|
|
+ types: [],
|
|
|
|
|
+ typeCounts: [],
|
|
|
|
|
+ typeStorage: [],
|
|
|
|
|
+ departments: [],
|
|
|
|
|
+ departmentCounts: [],
|
|
|
|
|
+ departmentStorage: [],
|
|
|
|
|
+ visibility: [],
|
|
|
|
|
+ visibilityCounts: [],
|
|
|
|
|
+ hotness: [],
|
|
|
|
|
+ hotnessCounts: [],
|
|
|
|
|
+ recommended: [],
|
|
|
|
|
+ recommendedCounts: [],
|
|
|
|
|
+ formats: [],
|
|
|
|
|
+ formatCounts: [],
|
|
|
|
|
+ formatStorage: [],
|
|
|
|
|
+ formatAvgSize: [],
|
|
|
|
|
+ engagement: [],
|
|
|
|
|
+ engagementCounts: [],
|
|
|
|
|
+ typeViewCounts: [],
|
|
|
|
|
+ trendLabels: [],
|
|
|
|
|
+ uploadTrend: [],
|
|
|
|
|
+ viewTrend: []
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 加载数据
|
|
|
|
|
+ const loadData = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 并行加载所有数据
|
|
|
|
|
+ const [
|
|
|
|
|
+ summaryResponse,
|
|
|
|
|
+ typeResponse,
|
|
|
|
|
+ departmentResponse,
|
|
|
|
|
+ visibilityResponse,
|
|
|
|
|
+ formatResponse,
|
|
|
|
|
+ engagementResponse,
|
|
|
|
|
+ trendResponse
|
|
|
|
|
+ ] = await Promise.all([
|
|
|
|
|
+ getSummaryData(getFilterParams()),
|
|
|
|
|
+ getResourceTypeData(getFilterParams()),
|
|
|
|
|
+ getDepartmentData(getFilterParams()),
|
|
|
|
|
+ getVisibilityData(getFilterParams()),
|
|
|
|
|
+ getFormatData(getFilterParams()),
|
|
|
|
|
+ getEngagementData(getFilterParams()),
|
|
|
|
|
+ getTrendData(getFilterParams())
|
|
|
|
|
+ ])
|
|
|
|
|
+
|
|
|
|
|
+ // 更新摘要数据
|
|
|
|
|
+ if (summaryResponse.code === 200) {
|
|
|
|
|
+ summaryData.value = summaryResponse.data
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 更新图表数据
|
|
|
|
|
+ if (typeResponse.code === 200) {
|
|
|
|
|
+ const { types, typeCounts, typeStorage } = typeResponse.data
|
|
|
|
|
+ chartData.value.types = types
|
|
|
|
|
+ chartData.value.typeCounts = typeCounts
|
|
|
|
|
+ chartData.value.typeStorage = typeStorage
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (departmentResponse.code === 200) {
|
|
|
|
|
+ const { departments, departmentCounts, departmentStorage } = departmentResponse.data
|
|
|
|
|
+ chartData.value.departments = departments
|
|
|
|
|
+ chartData.value.departmentCounts = departmentCounts
|
|
|
|
|
+ chartData.value.departmentStorage = departmentStorage
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (visibilityResponse.code === 200) {
|
|
|
|
|
+ const { visibility, visibilityCounts, hotness, hotnessCounts, recommended, recommendedCounts } =
|
|
|
|
|
+ visibilityResponse.data
|
|
|
|
|
+ chartData.value.visibility = visibility
|
|
|
|
|
+ chartData.value.visibilityCounts = visibilityCounts
|
|
|
|
|
+ chartData.value.hotness = hotness
|
|
|
|
|
+ chartData.value.hotnessCounts = hotnessCounts
|
|
|
|
|
+ chartData.value.recommended = recommended
|
|
|
|
|
+ chartData.value.recommendedCounts = recommendedCounts
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (formatResponse.code === 200) {
|
|
|
|
|
+ const { formats, formatCounts, formatStorage, formatAvgSize } = formatResponse.data
|
|
|
|
|
+ chartData.value.formats = formats
|
|
|
|
|
+ chartData.value.formatCounts = formatCounts
|
|
|
|
|
+ chartData.value.formatStorage = formatStorage
|
|
|
|
|
+ chartData.value.formatAvgSize = formatAvgSize
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (engagementResponse.code === 200) {
|
|
|
|
|
+ const { engagement, engagementCounts, typeViewCounts } = engagementResponse.data
|
|
|
|
|
+ chartData.value.engagement = engagement
|
|
|
|
|
+ chartData.value.engagementCounts = engagementCounts
|
|
|
|
|
+ chartData.value.typeViewCounts = typeViewCounts
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (trendResponse.code === 200) {
|
|
|
|
|
+ const { trendLabels, uploadTrend, viewTrend } = trendResponse.data
|
|
|
|
|
+ chartData.value.trendLabels = trendLabels
|
|
|
|
|
+ chartData.value.uploadTrend = uploadTrend
|
|
|
|
|
+ chartData.value.viewTrend = viewTrend
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('加载数据失败:', error)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取筛选参数
|
|
|
|
|
+ const getFilterParams = () => {
|
|
|
|
|
+ return {
|
|
|
|
|
+ department: filters.value.department,
|
|
|
|
|
+ timeRange: filters.value.timeRange
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取或创建图表实例
|
|
|
|
|
+ const getOrCreateChart = (ref, instanceKey) => {
|
|
|
|
|
+ if (!ref) return null
|
|
|
|
|
+
|
|
|
|
|
+ if (chartInstances.value[instanceKey]) {
|
|
|
|
|
+ return chartInstances.value[instanceKey]
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const chart = echarts.init(ref)
|
|
|
|
|
+ chartInstances.value[instanceKey] = chart
|
|
|
|
|
+ return chart
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 初始化图表
|
|
|
|
|
+ const initCharts = () => {
|
|
|
|
|
+ // 资源类型分布图
|
|
|
|
|
+ if (typeChartRef.value && chartData.value.types.length > 0) {
|
|
|
|
|
+ const typeChart = getOrCreateChart(typeChartRef.value, 'typeChart')
|
|
|
|
|
+ if (typeChart) {
|
|
|
|
|
+ typeChart.setOption({
|
|
|
|
|
+ title: {
|
|
|
|
|
+ text: '按资源类型分类 (数量)',
|
|
|
|
|
+ left: 'center'
|
|
|
|
|
+ },
|
|
|
|
|
+ tooltip: {
|
|
|
|
|
+ trigger: 'item'
|
|
|
|
|
+ },
|
|
|
|
|
+ legend: {
|
|
|
|
|
+ bottom: '5%',
|
|
|
|
|
+ left: 'center'
|
|
|
|
|
+ },
|
|
|
|
|
+ series: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: 'pie',
|
|
|
|
|
+ radius: ['40%', '70%'],
|
|
|
|
|
+ data: chartData.value.types.map((name, index) => ({
|
|
|
|
|
+ value: chartData.value.typeCounts[index],
|
|
|
|
|
+ name
|
|
|
|
|
+ })),
|
|
|
|
|
+ emphasis: {
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ shadowBlur: 10,
|
|
|
|
|
+ shadowOffsetX: 0,
|
|
|
|
|
+ shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 院系分布图
|
|
|
|
|
+ if (departmentChartRef.value && chartData.value.departments.length > 0) {
|
|
|
|
|
+ const departmentChart = getOrCreateChart(departmentChartRef.value, 'departmentChart')
|
|
|
|
|
+ if (departmentChart) {
|
|
|
|
|
+ departmentChart.setOption({
|
|
|
|
|
+ title: {
|
|
|
|
|
+ text: '按院系分布 (数量)',
|
|
|
|
|
+ left: 'center'
|
|
|
|
|
+ },
|
|
|
|
|
+ tooltip: {
|
|
|
|
|
+ trigger: 'item'
|
|
|
|
|
+ },
|
|
|
|
|
+ legend: {
|
|
|
|
|
+ bottom: '5%',
|
|
|
|
|
+ left: 'center'
|
|
|
|
|
+ },
|
|
|
|
|
+ series: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: 'pie',
|
|
|
|
|
+ radius: ['50%', '70%'],
|
|
|
|
|
+ avoidLabelOverlap: false,
|
|
|
|
|
+ data: chartData.value.departments.map((name, index) => ({
|
|
|
|
|
+ value: chartData.value.departmentCounts[index],
|
|
|
|
|
+ name
|
|
|
|
|
+ }))
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 可见性分析
|
|
|
|
|
+ if (visibilityChartRef.value && chartData.value.visibility.length > 0) {
|
|
|
|
|
+ const visibilityChart = getOrCreateChart(visibilityChartRef.value, 'visibilityChart')
|
|
|
|
|
+ if (visibilityChart) {
|
|
|
|
|
+ visibilityChart.setOption({
|
|
|
|
|
+ title: {
|
|
|
|
|
+ text: '资源公开情况',
|
|
|
|
|
+ left: 'center'
|
|
|
|
|
+ },
|
|
|
|
|
+ tooltip: {
|
|
|
|
|
+ trigger: 'axis',
|
|
|
|
|
+ axisPointer: {
|
|
|
|
|
+ type: 'shadow'
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ xAxis: {
|
|
|
|
|
+ type: 'category',
|
|
|
|
|
+ data: chartData.value.visibility
|
|
|
|
|
+ },
|
|
|
|
|
+ yAxis: {
|
|
|
|
|
+ type: 'value',
|
|
|
|
|
+ name: '资源数量'
|
|
|
|
|
+ },
|
|
|
|
|
+ series: [
|
|
|
|
|
+ {
|
|
|
|
|
+ name: '资源数量',
|
|
|
|
|
+ type: 'bar',
|
|
|
|
|
+ data: chartData.value.visibilityCounts,
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ color: function (params) {
|
|
|
|
|
+ const colors = ['#2ecc71', '#e74c3c']
|
|
|
|
|
+ return colors[params.dataIndex]
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 热度分析
|
|
|
|
|
+ if (hotnessChartRef.value && chartData.value.hotnessCounts.length > 0) {
|
|
|
|
|
+ const hotnessChart = getOrCreateChart(hotnessChartRef.value, 'hotnessChart')
|
|
|
|
|
+ if (hotnessChart) {
|
|
|
|
|
+ hotnessChart.setOption({
|
|
|
|
|
+ title: {
|
|
|
|
|
+ text: '资源热度分析',
|
|
|
|
|
+ left: 'center'
|
|
|
|
|
+ },
|
|
|
|
|
+ tooltip: {
|
|
|
|
|
+ trigger: 'item'
|
|
|
|
|
+ },
|
|
|
|
|
+ angleAxis: {
|
|
|
|
|
+ type: 'category',
|
|
|
|
|
+ data: ['热门资源', '推荐资源', '热门且推荐']
|
|
|
|
|
+ },
|
|
|
|
|
+ radiusAxis: {},
|
|
|
|
|
+ polar: {},
|
|
|
|
|
+ series: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: 'bar',
|
|
|
|
|
+ data: [chartData.value.hotnessCounts[0], chartData.value.recommendedCounts[0], 89],
|
|
|
|
|
+ coordinateSystem: 'polar',
|
|
|
|
|
+ name: '资源数量',
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ color: function (params) {
|
|
|
|
|
+ const colors = ['#e74c3c', '#3498db', '#9b59b6']
|
|
|
|
|
+ return colors[params.dataIndex]
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 文件格式数量分布
|
|
|
|
|
+ if (formatChartRef.value && chartData.value.formats.length > 0) {
|
|
|
|
|
+ const formatChart = getOrCreateChart(formatChartRef.value, 'formatChart')
|
|
|
|
|
+ if (formatChart) {
|
|
|
|
|
+ formatChart.setOption({
|
|
|
|
|
+ title: {
|
|
|
|
|
+ text: '不同文件格式的数量分布',
|
|
|
|
|
+ left: 'center'
|
|
|
|
|
+ },
|
|
|
|
|
+ tooltip: {
|
|
|
|
|
+ trigger: 'axis',
|
|
|
|
|
+ axisPointer: {
|
|
|
|
|
+ type: 'shadow'
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ grid: {
|
|
|
|
|
+ left: '3%',
|
|
|
|
|
+ right: '4%',
|
|
|
|
|
+ bottom: '3%',
|
|
|
|
|
+ containLabel: true
|
|
|
|
|
+ },
|
|
|
|
|
+ xAxis: {
|
|
|
|
|
+ type: 'value',
|
|
|
|
|
+ name: '文件数量'
|
|
|
|
|
+ },
|
|
|
|
|
+ yAxis: {
|
|
|
|
|
+ type: 'category',
|
|
|
|
|
+ data: chartData.value.formats
|
|
|
|
|
+ },
|
|
|
|
|
+ series: [
|
|
|
|
|
+ {
|
|
|
|
|
+ name: '文件数量',
|
|
|
|
|
+ type: 'bar',
|
|
|
|
|
+ data: chartData.value.formatCounts,
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ color: '#34495e'
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 存储空间占用分布
|
|
|
|
|
+ if (storageChartRef.value && chartData.value.formats.length > 0) {
|
|
|
|
|
+ const storageChart = getOrCreateChart(storageChartRef.value, 'storageChart')
|
|
|
|
|
+ if (storageChart) {
|
|
|
|
|
+ storageChart.setOption({
|
|
|
|
|
+ title: {
|
|
|
|
|
+ text: '不同文件格式的存储空间占用',
|
|
|
|
|
+ left: 'center'
|
|
|
|
|
+ },
|
|
|
|
|
+ tooltip: {
|
|
|
|
|
+ trigger: 'item',
|
|
|
|
|
+ formatter: function (params) {
|
|
|
|
|
+ const index = params.dataIndex
|
|
|
|
|
+ return `${params.name}: ${params.value} TB<br/>文件数量: ${chartData.value.formatCounts[index]}<br/>平均大小: ${chartData.value.formatAvgSize[index]}`
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ legend: {
|
|
|
|
|
+ bottom: '5%',
|
|
|
|
|
+ left: 'center'
|
|
|
|
|
+ },
|
|
|
|
|
+ series: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: 'pie',
|
|
|
|
|
+ radius: '50%',
|
|
|
|
|
+ data: chartData.value.formats.map((name, index) => ({
|
|
|
|
|
+ value: chartData.value.formatStorage[index],
|
|
|
|
|
+ name
|
|
|
|
|
+ })),
|
|
|
|
|
+ emphasis: {
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ shadowBlur: 10,
|
|
|
|
|
+ shadowOffsetX: 0,
|
|
|
|
|
+ shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 观看情况分析
|
|
|
|
|
+ if (viewsChartRef.value && chartData.value.types.length > 0) {
|
|
|
|
|
+ const viewsChart = getOrCreateChart(viewsChartRef.value, 'viewsChart')
|
|
|
|
|
+ if (viewsChart) {
|
|
|
|
|
+ viewsChart.setOption({
|
|
|
|
|
+ title: {
|
|
|
|
|
+ text: '各类资源平均观看人数',
|
|
|
|
|
+ left: 'center'
|
|
|
|
|
+ },
|
|
|
|
|
+ tooltip: {
|
|
|
|
|
+ trigger: 'axis',
|
|
|
|
|
+ axisPointer: {
|
|
|
|
|
+ type: 'shadow'
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ xAxis: {
|
|
|
|
|
+ type: 'category',
|
|
|
|
|
+ data: chartData.value.types
|
|
|
|
|
+ },
|
|
|
|
|
+ yAxis: {
|
|
|
|
|
+ type: 'value',
|
|
|
|
|
+ name: '平均观看人数'
|
|
|
|
|
+ },
|
|
|
|
|
+ series: [
|
|
|
|
|
+ {
|
|
|
|
|
+ name: '平均观看人数',
|
|
|
|
|
+ type: 'bar',
|
|
|
|
|
+ data: chartData.value.typeViewCounts,
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ color: '#2980b9'
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 用户参与度分析
|
|
|
|
|
+ if (engagementChartRef.value && chartData.value.engagement.length > 0) {
|
|
|
|
|
+ const engagementChart = getOrCreateChart(engagementChartRef.value, 'engagementChart')
|
|
|
|
|
+ if (engagementChart) {
|
|
|
|
|
+ engagementChart.setOption({
|
|
|
|
|
+ title: {
|
|
|
|
|
+ text: '资源使用情况统计',
|
|
|
|
|
+ left: 'center'
|
|
|
|
|
+ },
|
|
|
|
|
+ tooltip: {
|
|
|
|
|
+ trigger: 'axis',
|
|
|
|
|
+ axisPointer: {
|
|
|
|
|
+ type: 'shadow'
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ xAxis: {
|
|
|
|
|
+ type: 'category',
|
|
|
|
|
+ data: chartData.value.engagement
|
|
|
|
|
+ },
|
|
|
|
|
+ yAxis: {
|
|
|
|
|
+ type: 'value',
|
|
|
|
|
+ name: '数量'
|
|
|
|
|
+ },
|
|
|
|
|
+ series: [
|
|
|
|
|
+ {
|
|
|
|
|
+ name: '用户参与度',
|
|
|
|
|
+ type: 'bar',
|
|
|
|
|
+ data: chartData.value.engagementCounts,
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ color: '#9b59b6'
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 时间趋势分析
|
|
|
|
|
+ if (trendChartRef.value && chartData.value.trendLabels.length > 0) {
|
|
|
|
|
+ const trendChart = getOrCreateChart(trendChartRef.value, 'trendChart')
|
|
|
|
|
+ if (trendChart) {
|
|
|
|
|
+ trendChart.setOption({
|
|
|
|
|
+ title: {
|
|
|
|
|
+ text: '资源上传与访问趋势 (2025年)',
|
|
|
|
|
+ left: 'center'
|
|
|
|
|
+ },
|
|
|
|
|
+ tooltip: {
|
|
|
|
|
+ trigger: 'axis'
|
|
|
|
|
+ },
|
|
|
|
|
+ legend: {
|
|
|
|
|
+ data: ['资源上传数量', '资源观看人数'],
|
|
|
|
|
+ top: '10%'
|
|
|
|
|
+ },
|
|
|
|
|
+ grid: {
|
|
|
|
|
+ left: '3%',
|
|
|
|
|
+ right: '4%',
|
|
|
|
|
+ bottom: '3%',
|
|
|
|
|
+ containLabel: true
|
|
|
|
|
+ },
|
|
|
|
|
+ xAxis: {
|
|
|
|
|
+ type: 'category',
|
|
|
|
|
+ boundaryGap: false,
|
|
|
|
|
+ data: chartData.value.trendLabels
|
|
|
|
|
+ },
|
|
|
|
|
+ yAxis: {
|
|
|
|
|
+ type: 'value',
|
|
|
|
|
+ name: '数量'
|
|
|
|
|
+ },
|
|
|
|
|
+ series: [
|
|
|
|
|
+ {
|
|
|
|
|
+ name: '资源上传数量',
|
|
|
|
|
+ type: 'line',
|
|
|
|
|
+ data: chartData.value.uploadTrend,
|
|
|
|
|
+ smooth: true,
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ color: '#3498db'
|
|
|
|
|
+ },
|
|
|
|
|
+ areaStyle: {
|
|
|
|
|
+ color: 'rgba(52, 152, 219, 0.1)'
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: '资源观看人数',
|
|
|
|
|
+ type: 'line',
|
|
|
|
|
+ data: chartData.value.viewTrend,
|
|
|
|
|
+ smooth: true,
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ color: '#e74c3c'
|
|
|
|
|
+ },
|
|
|
|
|
+ areaStyle: {
|
|
|
|
|
+ color: 'rgba(231, 76, 60, 0.1)'
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 筛选器处理函数
|
|
|
|
|
+ const handleDepartmentChange = async (value) => {
|
|
|
|
|
+ filters.value.department = value
|
|
|
|
|
+ disposeCharts()
|
|
|
|
|
+ await loadData()
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ initCharts()
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const handleTimeChange = async (value) => {
|
|
|
|
|
+ filters.value.timeRange = value
|
|
|
|
|
+ disposeCharts()
|
|
|
|
|
+ await loadData()
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ initCharts()
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 清理图表实例
|
|
|
|
|
+ const disposeCharts = () => {
|
|
|
|
|
+ Object.values(chartInstances.value).forEach((chart) => {
|
|
|
|
|
+ if (chart) {
|
|
|
|
|
+ chart.dispose()
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ chartInstances.value = {
|
|
|
|
|
+ typeChart: null,
|
|
|
|
|
+ departmentChart: null,
|
|
|
|
|
+ visibilityChart: null,
|
|
|
|
|
+ hotnessChart: null,
|
|
|
|
|
+ formatChart: null,
|
|
|
|
|
+ storageChart: null,
|
|
|
|
|
+ viewsChart: null,
|
|
|
|
|
+ engagementChart: null,
|
|
|
|
|
+ trendChart: null
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 组件挂载后加载数据并初始化图表
|
|
|
|
|
+ onMounted(async () => {
|
|
|
|
|
+ await loadData()
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ initCharts()
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 组件卸载时清理图表实例
|
|
|
|
|
+ onUnmounted(() => {
|
|
|
|
|
+ disposeCharts()
|
|
|
|
|
+ })
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+ .resource-statistics {
|
|
|
|
|
+ font-family: 'Arial', sans-serif;
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+ background-color: #f5f5f5;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .container {
|
|
|
|
|
+ margin: 0 auto;
|
|
|
|
|
+ background-color: white;
|
|
|
|
|
+ padding: 30px;
|
|
|
|
|
+ border-radius: 10px;
|
|
|
|
|
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ h1,
|
|
|
|
|
+ h2,
|
|
|
|
|
+ h3 {
|
|
|
|
|
+ color: #2c3e50;
|
|
|
|
|
+ border-bottom: 2px solid #3498db;
|
|
|
|
|
+ padding-bottom: 10px;
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .summary-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ gap: 20px;
|
|
|
|
|
+ margin-bottom: 30px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .summary-box {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-width: 200px;
|
|
|
|
|
+ background-color: #ecf0f1;
|
|
|
|
|
+ padding: 15px;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .summary-box h3 {
|
|
|
|
|
+ margin: 0 0 10px 0;
|
|
|
|
|
+ color: #2c3e50;
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ padding: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .summary-box p {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ font-size: 24px;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ color: #3498db;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .storage-info {
|
|
|
|
|
+ background-color: #e8f4fc;
|
|
|
|
|
+ padding: 15px;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .storage-info h3 {
|
|
|
|
|
+ margin: 0 0 10px 0;
|
|
|
|
|
+ color: #2980b9;
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ padding: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .storage-info p {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ font-size: 18px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .filters {
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+ padding: 15px;
|
|
|
|
|
+ background-color: #f1f8ff;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .filters label {
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ margin-right: 10px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .chart-container {
|
|
|
|
|
+ margin-bottom: 40px;
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+ background-color: #f9f9f9;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .chart-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ gap: 20px;
|
|
|
|
|
+ margin-bottom: 30px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .chart-col {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-width: 300px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .chart {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 400px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .chart-full {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 400px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ footer {
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ margin-top: 40px;
|
|
|
|
|
+ color: #7f8c8d;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ footer p {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @media (max-width: 768px) {
|
|
|
|
|
+ .summary-row {
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .chart-row {
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .chart-col {
|
|
|
|
|
+ min-width: 100%;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .filters {
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+</style>
|