{b}: {c} ({d}%)' }, @@ -263,7 +278,7 @@ const initFileTypeChart = () => { itemHeight: 10, textStyle: { fontSize: 11, - color: '#666' + color: getCSSVariable('--gray-600') } }, series: [{ @@ -274,7 +289,7 @@ const initFileTypeChart = () => { avoidLabelOverlap: true, // 避免标签重叠 itemStyle: { borderRadius: 8, - borderColor: '#fff', + borderColor: getCSSVariable('--gray-0'), borderWidth: 2 }, label: { @@ -284,7 +299,7 @@ const initFileTypeChart = () => { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, - shadowColor: 'rgba(0, 0, 0, 0.5)' + shadowColor: getCSSVariable('--shadow-3') } }, labelLine: { @@ -306,11 +321,11 @@ const initFileTypeChart = () => { const option = { tooltip: { trigger: 'item', - backgroundColor: 'rgba(255, 255, 255, 0.95)', - borderColor: '#e8e8e8', + backgroundColor: getCSSVariable('--gray-0'), + borderColor: getCSSVariable('--gray-200'), borderWidth: 1, textStyle: { - color: '#666' + color: getCSSVariable('--gray-600') }, formatter: '{a}
{b}: {c} ({d}%)' }, @@ -322,7 +337,7 @@ const initFileTypeChart = () => { avoidLabelOverlap: true, itemStyle: { borderRadius: 8, - borderColor: '#fff', + borderColor: getCSSVariable('--gray-0'), borderWidth: 2 }, label: { @@ -332,7 +347,7 @@ const initFileTypeChart = () => { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, - shadowColor: 'rgba(0, 0, 0, 0.5)' + shadowColor: getCSSVariable('--shadow-3') } }, labelLine: { @@ -341,7 +356,7 @@ const initFileTypeChart = () => { data: [ { name: '暂无数据', value: 1 } ], - color: ['#e1f6fb'] + color: [getCSSVariable('--chart-info-lighter')] }] } @@ -394,6 +409,15 @@ onMounted(() => { window.addEventListener('resize', handleResize) }) +// 监听主题变化,重新渲染图表 +watch(() => themeStore.isDark, () => { + if (props.knowledgeStats && (dbTypeChart || fileTypeChart)) { + nextTick(() => { + updateCharts() + }) + } +}) + // 组件卸载时清理 const cleanup = () => { window.removeEventListener('resize', handleResize) @@ -437,7 +461,7 @@ defineExpose({ align-items: center; gap: 6px; font-size: 12px; - color: #666; + color: var(--gray-500); } .legend-color { @@ -486,21 +510,21 @@ defineExpose({ .carousel-label { font-size: 14px; font-weight: 600; - color: #666; + color: var(--gray-500); margin-bottom: 4px; } .carousel-value { font-size: 24px; font-weight: 700; - color: #333; + color: var(--gray-800); margin-bottom: 2px; line-height: 1; } .carousel-percent { font-size: 12px; - color: #999; + color: var(--gray-400); font-weight: 500; } } diff --git a/web/src/components/dashboard/StatsOverviewComponent.vue b/web/src/components/dashboard/StatsOverviewComponent.vue index 794e2a26..2fcfb179 100644 --- a/web/src/components/dashboard/StatsOverviewComponent.vue +++ b/web/src/components/dashboard/StatsOverviewComponent.vue @@ -92,4 +92,201 @@ const getSatisfactionClass = () => { diff --git a/web/src/components/dashboard/ToolStatsComponent.vue b/web/src/components/dashboard/ToolStatsComponent.vue index 813ba517..0970cd76 100644 --- a/web/src/components/dashboard/ToolStatsComponent.vue +++ b/web/src/components/dashboard/ToolStatsComponent.vue @@ -79,6 +79,15 @@ import { ref, onMounted, watch, nextTick, computed } from 'vue' import * as echarts from 'echarts' import { getColorByIndex, getChartColor, getColorPalette } from '@/utils/chartColors' +import { useThemeStore } from '@/stores/theme' + +// CSS 变量解析工具函数 +function getCSSVariable(variableName, element = document.documentElement) { + return getComputedStyle(element).getPropertyValue(variableName).trim() +} + +// theme store +const themeStore = useThemeStore() // Props const props = defineProps({ @@ -132,6 +141,12 @@ const errorData = computed(() => { const initToolsChart = () => { if (!toolsChartRef.value || !props.toolStats?.most_used_tools?.length) return + // 如果已存在图表实例,先销毁 + if (toolsChart) { + toolsChart.dispose() + toolsChart = null + } + toolsChart = echarts.init(toolsChartRef.value) const data = props.toolStats.most_used_tools.slice(0, 10) // 只显示前10个 @@ -142,11 +157,11 @@ const initToolsChart = () => { axisPointer: { type: 'shadow' }, - backgroundColor: 'rgba(255, 255, 255, 0.95)', - borderColor: '#e8e8e8', + backgroundColor: getCSSVariable('--gray-0'), + borderColor: getCSSVariable('--gray-200'), borderWidth: 1, textStyle: { - color: '#666' + color: getCSSVariable('--gray-600') } }, grid: { @@ -160,15 +175,15 @@ const initToolsChart = () => { type: 'value', axisLine: { lineStyle: { - color: '#e8e8e8' + color: getCSSVariable('--gray-200') } }, axisLabel: { - color: '#666' + color: getCSSVariable('--gray-500') }, splitLine: { lineStyle: { - color: '#f0f0f0' + color: getCSSVariable('--gray-150') } } }, @@ -177,11 +192,11 @@ const initToolsChart = () => { data: data.map(item => item.tool_name), axisLine: { lineStyle: { - color: '#e8e8e8' + color: getCSSVariable('--gray-200') } }, axisLabel: { - color: '#666', + color: getCSSVariable('--gray-500'), interval: 0 } }, @@ -197,7 +212,7 @@ const initToolsChart = () => { itemStyle: { color: getChartColor('primary'), shadowBlur: 10, - shadowColor: 'rgba(2, 142, 160, 0.3)' + shadowColor: getCSSVariable('--chart-info-shadow') } } }] @@ -210,6 +225,12 @@ const initToolsChart = () => { const initErrorChart = () => { if (!errorChartRef.value || !hasErrorData.value) return + // 如果已存在图表实例,先销毁 + if (errorChart) { + errorChart.dispose() + errorChart = null + } + errorChart = echarts.init(errorChartRef.value) const data = errorData.value.slice(0, 5) // 只显示前5个 @@ -217,11 +238,11 @@ const initErrorChart = () => { const option = { tooltip: { trigger: 'item', - backgroundColor: 'rgba(255, 255, 255, 0.95)', - borderColor: '#e8e8e8', + backgroundColor: getCSSVariable('--gray-0'), + borderColor: getCSSVariable('--gray-200'), borderWidth: 1, textStyle: { - color: '#666' + color: getCSSVariable('--gray-600') }, formatter: '{a}
{b}: {c} ({d}%)' }, @@ -236,7 +257,7 @@ const initErrorChart = () => { })), itemStyle: { borderRadius: 6, - borderColor: '#fff', + borderColor: getCSSVariable('--gray-0'), borderWidth: 2 }, label: { @@ -247,7 +268,7 @@ const initErrorChart = () => { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, - shadowColor: 'rgba(0, 0, 0, 0.5)' + shadowColor: getCSSVariable('--shadow-300') } }, color: getColorPalette() @@ -283,6 +304,15 @@ onMounted(() => { window.addEventListener('resize', handleResize) }) +// 监听主题变化,重新渲染图表 +watch(() => themeStore.isDark, () => { + if (props.toolStats && (toolsChart || errorChart)) { + nextTick(() => { + updateCharts() + }) + } +}) + // 组件卸载时清理 const cleanup = () => { window.removeEventListener('resize', handleResize) diff --git a/web/src/components/dashboard/UserStatsComponent.vue b/web/src/components/dashboard/UserStatsComponent.vue index 58ec0129..5eef0673 100644 --- a/web/src/components/dashboard/UserStatsComponent.vue +++ b/web/src/components/dashboard/UserStatsComponent.vue @@ -32,6 +32,15 @@ import { ref, onMounted, watch, nextTick } from 'vue' import * as echarts from 'echarts' import { getChartColor } from '@/utils/chartColors' import { dashboardApi } from '@/apis/dashboard_api' +import { useThemeStore } from '@/stores/theme' + +// CSS 变量解析工具函数 +function getCSSVariable(variableName, element = document.documentElement) { + return getComputedStyle(element).getPropertyValue(variableName).trim() +} + +// theme store +const themeStore = useThemeStore() // Props const props = defineProps({ @@ -53,16 +62,22 @@ let activityChart = null const initActivityChart = () => { if (!activityChartRef.value || !props.userStats?.daily_active_users) return + // 如果已存在图表实例,先销毁 + if (activityChart) { + activityChart.dispose() + activityChart = null + } + activityChart = echarts.init(activityChartRef.value) const option = { tooltip: { trigger: 'axis', - backgroundColor: 'rgba(255, 255, 255, 0.95)', - borderColor: '#e8e8e8', + backgroundColor: getCSSVariable('--gray-0'), + borderColor: getCSSVariable('--gray-200'), borderWidth: 1, textStyle: { - color: '#666' + color: getCSSVariable('--gray-600') } }, grid: { @@ -77,26 +92,26 @@ const initActivityChart = () => { data: props.userStats.daily_active_users.map(item => item.date), axisLine: { lineStyle: { - color: '#e8e8e8' + color: getCSSVariable('--gray-200') } }, axisLabel: { - color: '#666' + color: getCSSVariable('--gray-500') } }, yAxis: { type: 'value', axisLine: { lineStyle: { - color: '#e8e8e8' + color: getCSSVariable('--gray-200') } }, axisLabel: { - color: '#666' + color: getCSSVariable('--gray-500') }, splitLine: { lineStyle: { - color: '#f0f0f0' + color: getCSSVariable('--gray-100') } } }, @@ -117,24 +132,24 @@ const initActivityChart = () => { x2: 0, y2: 1, colorStops: [{ - offset: 0, color: 'rgba(57, 150, 174, 0.3)' + offset: 0, color: getCSSVariable('--chart-primary-light') }, { - offset: 1, color: 'rgba(57, 150, 174, 0.05)' + offset: 1, color: getCSSVariable('--chart-primary-lighter') }] } }, itemStyle: { color: getChartColor('primary'), borderWidth: 2, - borderColor: '#fff' + borderColor: getCSSVariable('--gray-0') }, emphasis: { itemStyle: { color: getChartColor('primary'), borderWidth: 3, - borderColor: '#fff', + borderColor: getCSSVariable('--gray-0'), shadowBlur: 10, - shadowColor: 'rgba(57, 150, 174, 0.5)' + shadowColor: getCSSVariable('--chart-primary-shadow') } } }] @@ -166,6 +181,15 @@ onMounted(() => { window.addEventListener('resize', handleResize) }) +// 监听主题变化,重新渲染图表 +watch(() => themeStore.isDark, () => { + if (props.userStats?.daily_active_users && activityChart) { + nextTick(() => { + initActivityChart() + }) + } +}) + // 组件卸载时清理 const cleanup = () => { window.removeEventListener('resize', handleResize) @@ -183,12 +207,132 @@ defineExpose({ \ No newline at end of file diff --git a/web/src/layouts/AppLayout.vue b/web/src/layouts/AppLayout.vue index 22358cf2..fb137001 100644 --- a/web/src/layouts/AppLayout.vue +++ b/web/src/layouts/AppLayout.vue @@ -3,9 +3,8 @@ import { ref, reactive, onMounted, useTemplateRef, computed } from 'vue' import { RouterLink, RouterView, useRoute } from 'vue-router' import { GithubOutlined, - ExclamationCircleOutlined, } from '@ant-design/icons-vue' -import { Bot, Waypoints, LibraryBig, Settings, BarChart3, BookOpen, CircleCheck } from 'lucide-vue-next'; +import { Bot, Waypoints, LibraryBig, Settings, BarChart3, CircleCheck } from 'lucide-vue-next'; import { onLongPress } from '@vueuse/core' import { useConfigStore } from '@/stores/config' @@ -169,31 +168,20 @@ const mainList = [{