2025-10-05 21:45:39 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<a-card title="知识库使用情况" :loading="loading" class="dashboard-card">
|
|
|
|
|
|
<!-- 知识库概览 -->
|
|
|
|
|
|
<div class="stats-overview">
|
|
|
|
|
|
<a-row :gutter="16">
|
2025-10-07 00:15:14 +08:00
|
|
|
|
<a-col :span="8">
|
2025-10-05 21:45:39 +08:00
|
|
|
|
<a-statistic
|
|
|
|
|
|
title="知识库总数"
|
|
|
|
|
|
:value="knowledgeStats?.total_databases || 0"
|
2025-11-25 22:34:44 +08:00
|
|
|
|
:value-style="{ color: 'var(--color-info-500)' }"
|
2025-10-05 21:45:39 +08:00
|
|
|
|
suffix="个"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</a-col>
|
2025-10-07 00:15:14 +08:00
|
|
|
|
<a-col :span="8">
|
2025-10-05 21:45:39 +08:00
|
|
|
|
<a-statistic
|
|
|
|
|
|
title="文件总数"
|
|
|
|
|
|
:value="knowledgeStats?.total_files || 0"
|
2025-11-25 22:34:44 +08:00
|
|
|
|
:value-style="{ color: 'var(--color-success-500)' }"
|
2025-10-05 21:45:39 +08:00
|
|
|
|
suffix="个"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</a-col>
|
2025-10-07 00:15:14 +08:00
|
|
|
|
<a-col :span="8">
|
2025-10-05 21:45:39 +08:00
|
|
|
|
<a-statistic
|
|
|
|
|
|
title="存储容量"
|
|
|
|
|
|
:value="formattedStorageSize"
|
2025-11-25 22:34:44 +08:00
|
|
|
|
:value-style="{ color: 'var(--color-warning-500)' }"
|
2025-10-05 21:45:39 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</a-col>
|
|
|
|
|
|
</a-row>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<a-divider />
|
|
|
|
|
|
|
2025-10-07 00:15:14 +08:00
|
|
|
|
<!-- 图表区域:拆分为两行 -->
|
|
|
|
|
|
<a-row :gutter="24" style="margin-bottom: 16px;">
|
2025-10-05 21:45:39 +08:00
|
|
|
|
<!-- 数据库类型分布 -->
|
2025-10-07 00:15:14 +08:00
|
|
|
|
<a-col :span="24">
|
2025-10-05 21:45:39 +08:00
|
|
|
|
<div class="chart-container">
|
2025-10-07 00:15:14 +08:00
|
|
|
|
<div class="chart-header">
|
2025-10-07 17:14:58 +08:00
|
|
|
|
<h4>类型分布</h4>
|
2025-10-07 00:15:14 +08:00
|
|
|
|
<div class="legend" v-if="dbTypeLegend.length">
|
|
|
|
|
|
<div class="legend-item" v-for="(item, idx) in dbTypeLegend" :key="item.name">
|
|
|
|
|
|
<span class="legend-color" :style="{ backgroundColor: getLegendColorByIndex(idx) }"></span>
|
|
|
|
|
|
<span class="legend-label">{{ item.name }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div ref="dbTypeChartRef" class="chart chart--thin"></div>
|
2025-10-05 21:45:39 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</a-col>
|
2025-10-07 00:15:14 +08:00
|
|
|
|
</a-row>
|
|
|
|
|
|
|
|
|
|
|
|
<a-row :gutter="24">
|
2025-10-05 21:45:39 +08:00
|
|
|
|
<!-- 文件类型分布 -->
|
2025-10-07 00:15:14 +08:00
|
|
|
|
<a-col :span="24">
|
2025-10-05 21:45:39 +08:00
|
|
|
|
<div class="chart-container">
|
|
|
|
|
|
<h4>文件类型分布</h4>
|
2025-11-07 02:55:35 +08:00
|
|
|
|
<div ref="fileTypeChartRef" class="chart donut-chart-container">
|
|
|
|
|
|
<div class="carousel-info" v-if="fileTypeData.length > 0">
|
|
|
|
|
|
<div class="carousel-item" :class="{ active: currentCarouselIndex === index }"
|
|
|
|
|
|
v-for="(item, index) in fileTypeData" :key="item.name">
|
|
|
|
|
|
<div class="carousel-label">{{ item.name }}</div>
|
|
|
|
|
|
<div class="carousel-value">{{ item.value }}</div>
|
|
|
|
|
|
<div class="carousel-percent">{{ ((item.value / totalFiles) * 100).toFixed(1) }}%</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-10-05 21:45:39 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</a-col>
|
|
|
|
|
|
</a-row>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 详细统计信息 -->
|
2025-10-07 00:15:14 +08:00
|
|
|
|
<!-- <a-divider />
|
2025-10-05 21:45:39 +08:00
|
|
|
|
<a-row :gutter="16">
|
|
|
|
|
|
<a-col :span="8">
|
|
|
|
|
|
<a-statistic
|
|
|
|
|
|
title="平均每库文件数"
|
|
|
|
|
|
:value="averageFilesPerDatabase"
|
|
|
|
|
|
suffix="个"
|
|
|
|
|
|
:precision="1"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</a-col>
|
|
|
|
|
|
<a-col :span="8">
|
|
|
|
|
|
<a-statistic
|
|
|
|
|
|
title="平均每文件节点数"
|
|
|
|
|
|
:value="averageNodesPerFile"
|
|
|
|
|
|
suffix="个"
|
|
|
|
|
|
:precision="1"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</a-col>
|
|
|
|
|
|
<a-col :span="8">
|
|
|
|
|
|
<a-statistic
|
|
|
|
|
|
title="平均节点大小"
|
|
|
|
|
|
:value="averageNodeSize"
|
|
|
|
|
|
suffix="KB"
|
|
|
|
|
|
:precision="2"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</a-col>
|
2025-10-07 00:15:14 +08:00
|
|
|
|
</a-row> -->
|
2025-10-05 21:45:39 +08:00
|
|
|
|
</a-card>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
|
import { ref, onMounted, watch, nextTick, computed } from 'vue'
|
|
|
|
|
|
import * as echarts from 'echarts'
|
2025-11-25 22:34:44 +08:00
|
|
|
|
import { getColorByIndex, getColorPalette } from '@/utils/chartColors'
|
2025-11-23 01:39:44 +08:00
|
|
|
|
import { useThemeStore } from '@/stores/theme'
|
|
|
|
|
|
|
|
|
|
|
|
// CSS 变量解析工具函数
|
|
|
|
|
|
function getCSSVariable(variableName, element = document.documentElement) {
|
|
|
|
|
|
return getComputedStyle(element).getPropertyValue(variableName).trim()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// theme store
|
|
|
|
|
|
const themeStore = useThemeStore()
|
2025-10-05 21:45:39 +08:00
|
|
|
|
|
|
|
|
|
|
// Props
|
|
|
|
|
|
const props = defineProps({
|
|
|
|
|
|
knowledgeStats: {
|
|
|
|
|
|
type: Object,
|
|
|
|
|
|
default: () => ({})
|
|
|
|
|
|
},
|
|
|
|
|
|
loading: {
|
|
|
|
|
|
type: Boolean,
|
|
|
|
|
|
default: false
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// Chart refs
|
|
|
|
|
|
const dbTypeChartRef = ref(null)
|
|
|
|
|
|
const fileTypeChartRef = ref(null)
|
|
|
|
|
|
let dbTypeChart = null
|
|
|
|
|
|
let fileTypeChart = null
|
|
|
|
|
|
|
2025-11-07 02:55:35 +08:00
|
|
|
|
// File type chart data for carousel
|
|
|
|
|
|
const fileTypeData = ref([])
|
|
|
|
|
|
const totalFiles = ref(0)
|
|
|
|
|
|
const currentCarouselIndex = ref(0)
|
|
|
|
|
|
let carouselTimer = null
|
|
|
|
|
|
|
2025-10-05 21:45:39 +08:00
|
|
|
|
// 计算属性
|
|
|
|
|
|
const formattedStorageSize = computed(() => {
|
|
|
|
|
|
const size = props.knowledgeStats?.total_storage_size || 0
|
|
|
|
|
|
if (size < 1024) return `${size} B`
|
|
|
|
|
|
if (size < 1024 * 1024) return `${(size / 1024).toFixed(2)} KB`
|
|
|
|
|
|
if (size < 1024 * 1024 * 1024) return `${(size / (1024 * 1024)).toFixed(2)} MB`
|
|
|
|
|
|
return `${(size / (1024 * 1024 * 1024)).toFixed(2)} GB`
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-10-07 00:15:14 +08:00
|
|
|
|
// const averageFilesPerDatabase = computed(() => {
|
|
|
|
|
|
// const databases = props.knowledgeStats?.total_databases || 0
|
|
|
|
|
|
// const files = props.knowledgeStats?.total_files || 0
|
|
|
|
|
|
// return databases > 0 ? files / databases : 0
|
|
|
|
|
|
// })
|
2025-10-05 21:45:39 +08:00
|
|
|
|
|
2025-10-07 00:15:14 +08:00
|
|
|
|
// const averageNodeSize = computed(() => {
|
|
|
|
|
|
// const nodes = props.knowledgeStats?.total_nodes || 0
|
|
|
|
|
|
// const size = props.knowledgeStats?.total_storage_size || 0
|
|
|
|
|
|
// return nodes > 0 ? size / (nodes * 1024) : 0 // 转换为KB
|
|
|
|
|
|
// })
|
2025-10-05 21:45:39 +08:00
|
|
|
|
|
2025-10-07 13:04:14 +08:00
|
|
|
|
// 使用统一的调色盘
|
|
|
|
|
|
const getLegendColorByIndex = (index) => getColorByIndex(index)
|
2025-10-07 00:15:14 +08:00
|
|
|
|
|
2025-10-05 21:45:39 +08:00
|
|
|
|
|
2025-10-07 00:15:14 +08:00
|
|
|
|
// 初始化数据库类型分布图 - 横向分段条
|
|
|
|
|
|
const dbTypeLegend = ref([])
|
2025-10-05 21:45:39 +08:00
|
|
|
|
const initDbTypeChart = () => {
|
|
|
|
|
|
if (!dbTypeChartRef.value || !props.knowledgeStats?.databases_by_type) return
|
|
|
|
|
|
|
2025-10-07 00:15:14 +08:00
|
|
|
|
const entries = Object.entries(props.knowledgeStats.databases_by_type)
|
|
|
|
|
|
.map(([type, count]) => ({ name: type || '未知', value: count }))
|
|
|
|
|
|
.filter(item => item.value > 0)
|
2025-10-05 21:45:39 +08:00
|
|
|
|
|
2025-10-07 00:15:14 +08:00
|
|
|
|
// update legend data
|
|
|
|
|
|
dbTypeLegend.value = entries
|
|
|
|
|
|
|
|
|
|
|
|
if (!dbTypeChart) {
|
|
|
|
|
|
dbTypeChart = echarts.init(dbTypeChartRef.value)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const total = entries.reduce((sum, item) => sum + item.value, 0)
|
|
|
|
|
|
|
|
|
|
|
|
// Build stacked bar series, but render with neutral color and separators
|
|
|
|
|
|
const series = entries.map((item, idx) => ({
|
|
|
|
|
|
name: item.name,
|
|
|
|
|
|
type: 'bar',
|
|
|
|
|
|
stack: 'total',
|
|
|
|
|
|
barWidth: 28,
|
|
|
|
|
|
data: [item.value],
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
color: getLegendColorByIndex(idx),
|
2025-11-23 01:39:44 +08:00
|
|
|
|
borderColor: getCSSVariable('--gray-0'),
|
2025-10-07 00:15:14 +08:00
|
|
|
|
borderWidth: 2,
|
|
|
|
|
|
borderJoin: 'miter'
|
|
|
|
|
|
},
|
|
|
|
|
|
emphasis: {
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
color: getLegendColorByIndex(idx)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-05 21:45:39 +08:00
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
|
|
const option = {
|
2025-10-07 00:15:14 +08:00
|
|
|
|
animation: false,
|
2025-10-05 21:45:39 +08:00
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: 'item',
|
2025-11-23 01:39:44 +08:00
|
|
|
|
backgroundColor: getCSSVariable('--gray-0'),
|
|
|
|
|
|
borderColor: getCSSVariable('--gray-200'),
|
2025-10-05 21:45:39 +08:00
|
|
|
|
borderWidth: 1,
|
2025-11-23 01:39:44 +08:00
|
|
|
|
textStyle: { color: getCSSVariable('--gray-600') },
|
2025-10-07 00:15:14 +08:00
|
|
|
|
formatter: (params) => {
|
|
|
|
|
|
const value = params.value || 0
|
|
|
|
|
|
return `${params.seriesName}: ${value}/${total}`
|
|
|
|
|
|
}
|
2025-10-05 21:45:39 +08:00
|
|
|
|
},
|
2025-10-07 00:15:14 +08:00
|
|
|
|
grid: { left: 0, right: 0, top: 10, bottom: 10, containLabel: false },
|
|
|
|
|
|
xAxis: {
|
|
|
|
|
|
type: 'value',
|
|
|
|
|
|
show: false,
|
|
|
|
|
|
max: total > 0 ? total : undefined
|
|
|
|
|
|
},
|
|
|
|
|
|
yAxis: {
|
|
|
|
|
|
type: 'category',
|
|
|
|
|
|
show: false,
|
|
|
|
|
|
data: ['all']
|
|
|
|
|
|
},
|
|
|
|
|
|
series
|
2025-10-05 21:45:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-07 00:15:14 +08:00
|
|
|
|
dbTypeChart.setOption(option, true)
|
2025-10-05 21:45:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化文件类型分布图
|
|
|
|
|
|
const initFileTypeChart = () => {
|
|
|
|
|
|
if (!fileTypeChartRef.value) return
|
|
|
|
|
|
|
2025-11-23 01:39:44 +08:00
|
|
|
|
// 如果已存在图表实例,先销毁
|
|
|
|
|
|
if (fileTypeChart) {
|
|
|
|
|
|
fileTypeChart.dispose()
|
|
|
|
|
|
fileTypeChart = null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-05 21:45:39 +08:00
|
|
|
|
fileTypeChart = echarts.init(fileTypeChartRef.value)
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否有文件类型数据 - 兼容旧字段名和新字段名
|
|
|
|
|
|
const fileTypesData = props.knowledgeStats?.files_by_type || props.knowledgeStats?.file_type_distribution || {}
|
|
|
|
|
|
if (Object.keys(fileTypesData).length > 0) {
|
|
|
|
|
|
const data = Object.entries(fileTypesData).map(([type, count]) => ({
|
|
|
|
|
|
name: type || '未知',
|
|
|
|
|
|
value: count
|
2025-11-07 02:55:35 +08:00
|
|
|
|
})).sort((a, b) => b.value - a.value) // 按数量排序
|
|
|
|
|
|
|
|
|
|
|
|
// 设置轮播数据
|
|
|
|
|
|
fileTypeData.value = data
|
|
|
|
|
|
totalFiles.value = data.reduce((sum, item) => sum + item.value, 0)
|
|
|
|
|
|
|
|
|
|
|
|
// 启动轮播
|
|
|
|
|
|
startCarousel()
|
2025-10-05 21:45:39 +08:00
|
|
|
|
|
|
|
|
|
|
const option = {
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: 'item',
|
2025-11-23 01:39:44 +08:00
|
|
|
|
backgroundColor: getCSSVariable('--gray-0'),
|
|
|
|
|
|
borderColor: getCSSVariable('--gray-200'),
|
2025-10-05 21:45:39 +08:00
|
|
|
|
borderWidth: 1,
|
|
|
|
|
|
textStyle: {
|
2025-11-23 01:39:44 +08:00
|
|
|
|
color: getCSSVariable('--gray-600')
|
2025-10-05 21:45:39 +08:00
|
|
|
|
},
|
|
|
|
|
|
formatter: '{a} <br/>{b}: {c} ({d}%)'
|
|
|
|
|
|
},
|
2025-11-07 02:55:35 +08:00
|
|
|
|
legend: {
|
|
|
|
|
|
orient: 'horizontal',
|
|
|
|
|
|
bottom: '5%',
|
|
|
|
|
|
left: 'center',
|
|
|
|
|
|
itemGap: 16,
|
|
|
|
|
|
itemWidth: 10,
|
|
|
|
|
|
itemHeight: 10,
|
|
|
|
|
|
textStyle: {
|
|
|
|
|
|
fontSize: 11,
|
2025-11-23 01:39:44 +08:00
|
|
|
|
color: getCSSVariable('--gray-600')
|
2025-11-07 02:55:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-10-05 21:45:39 +08:00
|
|
|
|
series: [{
|
|
|
|
|
|
name: '文件类型',
|
|
|
|
|
|
type: 'pie',
|
2025-11-07 02:55:35 +08:00
|
|
|
|
radius: ['45%', '75%'], // 调整为更大的环,为中心信息留出更多空间
|
|
|
|
|
|
center: ['50%', '45%'], // 向上移动,为中心和底部图例留出空间
|
|
|
|
|
|
avoidLabelOverlap: true, // 避免标签重叠
|
2025-10-05 21:45:39 +08:00
|
|
|
|
itemStyle: {
|
2025-11-07 02:55:35 +08:00
|
|
|
|
borderRadius: 8,
|
2025-11-23 01:39:44 +08:00
|
|
|
|
borderColor: getCSSVariable('--gray-0'),
|
2025-10-05 21:45:39 +08:00
|
|
|
|
borderWidth: 2
|
|
|
|
|
|
},
|
|
|
|
|
|
label: {
|
2025-11-07 02:55:35 +08:00
|
|
|
|
show: false // 隐藏饼图上的标签,使用图例代替
|
2025-10-05 21:45:39 +08:00
|
|
|
|
},
|
|
|
|
|
|
emphasis: {
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
shadowBlur: 10,
|
|
|
|
|
|
shadowOffsetX: 0,
|
2025-11-23 01:39:44 +08:00
|
|
|
|
shadowColor: getCSSVariable('--shadow-3')
|
2025-10-05 21:45:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
labelLine: {
|
2025-11-07 02:55:35 +08:00
|
|
|
|
show: false // 隐藏标签线
|
2025-10-05 21:45:39 +08:00
|
|
|
|
},
|
|
|
|
|
|
data: data,
|
2025-10-07 13:04:14 +08:00
|
|
|
|
color: getColorPalette()
|
2025-10-05 21:45:39 +08:00
|
|
|
|
}]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fileTypeChart.setOption(option)
|
|
|
|
|
|
} else {
|
2025-11-07 02:55:35 +08:00
|
|
|
|
// 清空轮播数据
|
|
|
|
|
|
fileTypeData.value = []
|
|
|
|
|
|
totalFiles.value = 0
|
|
|
|
|
|
stopCarousel()
|
|
|
|
|
|
|
2025-10-05 21:45:39 +08:00
|
|
|
|
// 如果没有文件类型数据,显示一个占位图表
|
|
|
|
|
|
const option = {
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: 'item',
|
2025-11-23 01:39:44 +08:00
|
|
|
|
backgroundColor: getCSSVariable('--gray-0'),
|
|
|
|
|
|
borderColor: getCSSVariable('--gray-200'),
|
2025-10-05 21:45:39 +08:00
|
|
|
|
borderWidth: 1,
|
|
|
|
|
|
textStyle: {
|
2025-11-23 01:39:44 +08:00
|
|
|
|
color: getCSSVariable('--gray-600')
|
2025-10-05 21:45:39 +08:00
|
|
|
|
},
|
|
|
|
|
|
formatter: '{a} <br/>{b}: {c} ({d}%)'
|
|
|
|
|
|
},
|
|
|
|
|
|
series: [{
|
|
|
|
|
|
name: '文件类型',
|
|
|
|
|
|
type: 'pie',
|
2025-11-07 02:55:35 +08:00
|
|
|
|
radius: ['45%', '75%'],
|
|
|
|
|
|
center: ['50%', '45%'],
|
|
|
|
|
|
avoidLabelOverlap: true,
|
2025-10-05 21:45:39 +08:00
|
|
|
|
itemStyle: {
|
2025-11-07 02:55:35 +08:00
|
|
|
|
borderRadius: 8,
|
2025-11-23 01:39:44 +08:00
|
|
|
|
borderColor: getCSSVariable('--gray-0'),
|
2025-10-05 21:45:39 +08:00
|
|
|
|
borderWidth: 2
|
|
|
|
|
|
},
|
|
|
|
|
|
label: {
|
2025-11-07 02:55:35 +08:00
|
|
|
|
show: false
|
2025-10-05 21:45:39 +08:00
|
|
|
|
},
|
|
|
|
|
|
emphasis: {
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
shadowBlur: 10,
|
|
|
|
|
|
shadowOffsetX: 0,
|
2025-11-23 01:39:44 +08:00
|
|
|
|
shadowColor: getCSSVariable('--shadow-3')
|
2025-10-05 21:45:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
labelLine: {
|
2025-11-07 02:55:35 +08:00
|
|
|
|
show: false
|
2025-10-05 21:45:39 +08:00
|
|
|
|
},
|
|
|
|
|
|
data: [
|
|
|
|
|
|
{ name: '暂无数据', value: 1 }
|
|
|
|
|
|
],
|
2025-11-25 22:34:44 +08:00
|
|
|
|
color: [getCSSVariable('--color-info-500')]
|
2025-10-05 21:45:39 +08:00
|
|
|
|
}]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fileTypeChart.setOption(option)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 02:55:35 +08:00
|
|
|
|
// 轮播功能
|
|
|
|
|
|
const startCarousel = () => {
|
|
|
|
|
|
stopCarousel() // 先停止之前的轮播
|
|
|
|
|
|
if (fileTypeData.value.length <= 1) return
|
|
|
|
|
|
|
|
|
|
|
|
// 重置索引
|
|
|
|
|
|
currentCarouselIndex.value = 0
|
|
|
|
|
|
|
|
|
|
|
|
// 启动新的轮播,每3秒切换一次
|
|
|
|
|
|
carouselTimer = setInterval(() => {
|
|
|
|
|
|
currentCarouselIndex.value = (currentCarouselIndex.value + 1) % fileTypeData.value.length
|
|
|
|
|
|
}, 3000)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const stopCarousel = () => {
|
|
|
|
|
|
if (carouselTimer) {
|
|
|
|
|
|
clearInterval(carouselTimer)
|
|
|
|
|
|
carouselTimer = null
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-05 21:45:39 +08:00
|
|
|
|
// 更新图表
|
|
|
|
|
|
const updateCharts = () => {
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
initDbTypeChart()
|
|
|
|
|
|
initFileTypeChart()
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 监听数据变化
|
|
|
|
|
|
watch(() => props.knowledgeStats, () => {
|
|
|
|
|
|
updateCharts()
|
|
|
|
|
|
}, { deep: true })
|
|
|
|
|
|
|
|
|
|
|
|
// 窗口大小变化时重新调整图表
|
|
|
|
|
|
const handleResize = () => {
|
|
|
|
|
|
if (dbTypeChart) dbTypeChart.resize()
|
|
|
|
|
|
if (fileTypeChart) fileTypeChart.resize()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
updateCharts()
|
|
|
|
|
|
window.addEventListener('resize', handleResize)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-11-23 01:39:44 +08:00
|
|
|
|
// 监听主题变化,重新渲染图表
|
|
|
|
|
|
watch(() => themeStore.isDark, () => {
|
|
|
|
|
|
if (props.knowledgeStats && (dbTypeChart || fileTypeChart)) {
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
updateCharts()
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-10-05 21:45:39 +08:00
|
|
|
|
// 组件卸载时清理
|
|
|
|
|
|
const cleanup = () => {
|
|
|
|
|
|
window.removeEventListener('resize', handleResize)
|
2025-11-07 02:55:35 +08:00
|
|
|
|
stopCarousel() // 停止轮播
|
2025-10-05 21:45:39 +08:00
|
|
|
|
if (dbTypeChart) {
|
|
|
|
|
|
dbTypeChart.dispose()
|
|
|
|
|
|
dbTypeChart = null
|
|
|
|
|
|
}
|
|
|
|
|
|
if (fileTypeChart) {
|
|
|
|
|
|
fileTypeChart.dispose()
|
|
|
|
|
|
fileTypeChart = null
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 导出清理函数供父组件调用
|
|
|
|
|
|
defineExpose({
|
|
|
|
|
|
cleanup
|
|
|
|
|
|
})
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="less">
|
|
|
|
|
|
// KnowledgeStatsComponent 特有的样式
|
|
|
|
|
|
.chart-container {
|
2025-10-07 00:15:14 +08:00
|
|
|
|
.chart-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
|
|
|
|
|
|
h4 {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.legend {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
|
|
|
|
.legend-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
font-size: 12px;
|
2025-11-23 01:39:44 +08:00
|
|
|
|
color: var(--gray-500);
|
2025-10-07 00:15:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.legend-color {
|
|
|
|
|
|
width: 10px;
|
|
|
|
|
|
height: 10px;
|
|
|
|
|
|
border-radius: 2px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-05 21:45:39 +08:00
|
|
|
|
.chart {
|
|
|
|
|
|
height: 300px;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
2025-10-07 00:15:14 +08:00
|
|
|
|
|
|
|
|
|
|
.chart--thin {
|
|
|
|
|
|
height: 80px;
|
|
|
|
|
|
}
|
2025-11-07 02:55:35 +08:00
|
|
|
|
|
|
|
|
|
|
// 环形图容器样式
|
|
|
|
|
|
.donut-chart-container {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
|
|
|
|
|
|
.carousel-info {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 50%;
|
|
|
|
|
|
left: 50%;
|
|
|
|
|
|
transform: translate(-50%, -50%);
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
|
z-index: 10;
|
|
|
|
|
|
|
|
|
|
|
|
.carousel-item {
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
transition: opacity 0.5s ease-in-out;
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 50%;
|
|
|
|
|
|
left: 50%;
|
|
|
|
|
|
transform: translate(-50%, -50%);
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
|
|
|
|
|
|
&.active {
|
|
|
|
|
|
opacity: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.carousel-label {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 600;
|
2025-11-23 01:39:44 +08:00
|
|
|
|
color: var(--gray-500);
|
2025-11-07 02:55:35 +08:00
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.carousel-value {
|
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
|
font-weight: 700;
|
2025-11-23 01:39:44 +08:00
|
|
|
|
color: var(--gray-800);
|
2025-11-07 02:55:35 +08:00
|
|
|
|
margin-bottom: 2px;
|
|
|
|
|
|
line-height: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.carousel-percent {
|
|
|
|
|
|
font-size: 12px;
|
2025-11-23 01:39:44 +08:00
|
|
|
|
color: var(--gray-400);
|
2025-11-07 02:55:35 +08:00
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-05 21:45:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
</style>
|