style(dashboard): 更新图表调色盘和样式优化

- 修改 base.css 中的图表调色盘颜色,提升视觉效果
- 在多个组件中引入统一的颜色工具函数,确保颜色一致性
- 优化仪表板组件的样式,提升用户体验和可读性
- 移除冗余的颜色定义,简化代码结构
This commit is contained in:
Wenjie Zhang 2025-10-07 13:04:14 +08:00
parent e13f98f16f
commit e592c75662
8 changed files with 175 additions and 106 deletions

View File

@ -82,7 +82,7 @@
/* Chart Palette - 图表调色板 */
--chart-palette-1: var(--chart-primary);
--chart-palette-2: #028ea0; /* 浅一点的青蓝 */
--chart-palette-2: #11cbe3; /* 浅一点的青蓝 */
--chart-palette-3: #00b8a9; /* 明亮青绿(邻近色) */
--chart-palette-4: #f2c94c; /* 柔和金黄(对比色) */
--chart-palette-5: #eb5757; /* 温暖红橙(强调) */

View File

@ -341,11 +341,12 @@
/* 使用更具体的选择器来覆盖 Ant Design 的默认样式 */
.dashboard-card .ant-card-head {
border-bottom: 1px solid var(--gray-200) !important;
/* border-bottom: 1px solid var(--gray-200) !important; */
min-height: 56px !important;
padding: 0 20px !important;
flex-shrink: 0;
background-color: var(--gray-0) !important;
/* border: 1px solo; */
}
.dashboard-card .ant-card-head-title {
@ -356,11 +357,16 @@
.dashboard-card .ant-card-body {
padding: 20px !important;
padding-top: 0px !important;
flex: 1;
overflow: auto;
background-color: var(--gray-0) !important;
}
.dashboard-card .ant-card-extra {
padding-top: 0px !important;
}
/* 统计概览样式 */
.stats-overview {
margin-bottom: 16px;

View File

@ -98,6 +98,7 @@
<script setup>
import { ref, onMounted, watch, nextTick, computed } from 'vue'
import * as echarts from 'echarts'
import { getColorByIndex, getChartColor } from '@/utils/chartColors'
// Props
const props = defineProps({
@ -164,9 +165,9 @@ const topPerformers = computed(() => {
//
const getScoreColor = (score) => {
if (score >= 80) return '#3996ae'
if (score >= 60) return '#82c3d6'
return '#a3d8e8'
if (score >= 80) return getChartColor('primary')
if (score >= 60) return getChartColor('warning')
return getChartColor('accent')
}
@ -252,12 +253,12 @@ const initConversationToolChart = () => {
return item ? item.conversation_count : 0
}),
itemStyle: {
color: '#3996ae',
color: getChartColor('primary'),
borderRadius: [4, 4, 0, 0]
},
emphasis: {
itemStyle: {
color: '#028ea0',
color: getChartColor('primary'),
shadowBlur: 10,
shadowColor: 'rgba(2, 142, 160, 0.3)'
}
@ -271,12 +272,12 @@ const initConversationToolChart = () => {
return item ? item.tool_usage_count : 0
}),
itemStyle: {
color: '#00b8a9',
color: getChartColor('accent'),
borderRadius: [4, 4, 0, 0]
},
emphasis: {
itemStyle: {
color: '#028ea0',
color: getChartColor('primary'),
shadowBlur: 10,
shadowColor: 'rgba(2, 142, 160, 0.3)'
}

View File

@ -42,6 +42,7 @@
import { ref, computed, onMounted, onUnmounted, nextTick, defineExpose, watch } from 'vue'
import * as echarts from 'echarts'
import { dashboardApi } from '@/apis/dashboard_api'
import { getColorByIndex, truncateLegend } from '@/utils/chartColors'
const props = defineProps({
loading: { type: Boolean, default: false },
@ -81,62 +82,6 @@ let retryTimer = null
const retryCount = ref(0)
const maxRetry = 20
// Build color palette from CSS variables in base.css
let colorPalette = []
const buildColorPalette = () => {
try {
const root = document.documentElement
const styles = getComputedStyle(root)
const pick = (name, fallback) => {
const v = styles.getPropertyValue(name)
return v && v.trim() ? v.trim() : fallback
}
const baseVars = [
['--chart-primary', '#3996ae'],
['--chart-success', '#52c41a'],
['--chart-warning', '#faad14'],
['--chart-error', '#f5222d'],
['--chart-secondary', '#722ed1'],
['--chart-accent', '#13c2c2'],
]
const paletteVars = [
['--chart-palette-1', '#3996ae'],
['--chart-palette-2', '#028ea0'],
['--chart-palette-3', '#00b8a9'],
['--chart-palette-4', '#f2c94c'],
['--chart-palette-5', '#eb5757'],
['--chart-palette-6', '#2f80ed'],
['--chart-palette-7', '#9b51e0'],
['--chart-palette-8', '#56ccf2'],
['--chart-palette-9', '#6fcf97'],
['--chart-palette-10', '#333333'],
]
const baseColors = baseVars.map(([n, f]) => pick(n, f))
const paletteColors = paletteVars.map(([n, f]) => pick(n, f))
// PRIORITY: palette first, then base
const merged = [...paletteColors, ...baseColors]
.filter(Boolean)
.filter((c, idx, arr) => arr.indexOf(c) === idx)
colorPalette = merged
} catch (e) {
// Fallback
colorPalette = [
'#3996ae', '#52c41a', '#faad14', '#f5222d', '#722ed1', '#13c2c2',
'#fa8c16', '#1890ff', '#95de64', '#69c0ff'
]
}
}
const getColorByIndex = (index) => {
if (!colorPalette || colorPalette.length === 0) buildColorPalette()
return colorPalette[index % colorPalette.length]
}
const truncateLegend = (name) => {
if (!name) return ''
return name.length > 20 ? name.slice(0, 20) + '…' : name
}
const loadCallStats = async () => {
callStatsLoading.value = true
try {
@ -178,7 +123,6 @@ const renderCallStatsChart = () => {
callStatsChart.dispose()
}
if (!colorPalette || colorPalette.length === 0) buildColorPalette()
callStatsChart = echarts.init(container)
@ -295,7 +239,6 @@ const cleanup = () => {
defineExpose({ cleanup })
onMounted(() => {
buildColorPalette()
loadCallStats()
})

View File

@ -94,6 +94,7 @@
<script setup>
import { ref, onMounted, watch, nextTick, computed } from 'vue'
import * as echarts from 'echarts'
import { getColorByIndex, getChartColor, getColorPalette } from '@/utils/chartColors'
// Props
const props = defineProps({
@ -134,38 +135,8 @@ const formattedStorageSize = computed(() => {
// return nodes > 0 ? size / (nodes * 1024) : 0 // KB
// })
// -
const colorPalette = [
'#3996ae', //
'#5faec2', //
'#82c3d6', //
'#a3d8e8', //
'#24839a', //
'#046a82', //
'#035065', //
'#c4eaf5', //
'#e1f6fb', //
'#f2fbfd' //
]
const getColorByIndex = (index) => {
return colorPalette[index % colorPalette.length]
}
// / base.css --chart-palette-* 使 HEX
const legendPalette = [
'#3996ae', // --chart-palette-1 -> var(--chart-primary)
'#028ea0', // --chart-palette-2
'#00b8a9', // --chart-palette-3
'#f2c94c', // --chart-palette-4
'#eb5757', // --chart-palette-5
'#2f80ed', // --chart-palette-6
'#9b51e0', // --chart-palette-7
'#56ccf2', // --chart-palette-8
'#6fcf97', // --chart-palette-9
'#333333' // --chart-palette-10
]
const getLegendColorByIndex = (index) => legendPalette[index % legendPalette.length]
// 使
const getLegendColorByIndex = (index) => getColorByIndex(index)
// -
@ -293,7 +264,7 @@ const initFileTypeChart = () => {
show: true
},
data: data,
color: legendPalette
color: getColorPalette()
}]
}

View File

@ -86,6 +86,7 @@
<script setup>
import { ref, onMounted, watch, nextTick, computed } from 'vue'
import * as echarts from 'echarts'
import { getColorByIndex, getChartColor, getColorPalette } from '@/utils/chartColors'
// Props
const props = defineProps({
@ -197,12 +198,12 @@ const initToolsChart = () => {
type: 'bar',
data: data.map(item => item.count),
itemStyle: {
color: '#3996ae',
color: getChartColor('primary'),
borderRadius: [0, 4, 4, 0]
},
emphasis: {
itemStyle: {
color: '#028ea0',
color: getChartColor('primary'),
shadowBlur: 10,
shadowColor: 'rgba(2, 142, 160, 0.3)'
}
@ -257,7 +258,7 @@ const initErrorChart = () => {
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
},
color: ['#3996ae', '#028ea0', '#00b8a9', '#f2c94c', '#eb5757', '#2f80ed', '#9b51e0', '#56ccf2', '#6fcf97', '#333333']
color: getColorPalette()
}]
}

View File

@ -30,6 +30,7 @@
<script setup>
import { ref, onMounted, watch, nextTick } from 'vue'
import * as echarts from 'echarts'
import { getChartColor } from '@/utils/chartColors'
import { dashboardApi } from '@/apis/dashboard_api'
// Props
@ -105,7 +106,7 @@ const initActivityChart = () => {
data: props.userStats.daily_active_users.map(item => item.active_users),
smooth: true,
lineStyle: {
color: '#3996ae',
color: getChartColor('primary'),
width: 3
},
areaStyle: {
@ -123,13 +124,13 @@ const initActivityChart = () => {
}
},
itemStyle: {
color: '#3996ae',
color: getChartColor('primary'),
borderWidth: 2,
borderColor: '#fff'
},
emphasis: {
itemStyle: {
color: '#24839a',
color: getChartColor('primary'),
borderWidth: 3,
borderColor: '#fff',
shadowBlur: 10,

View File

@ -0,0 +1,146 @@
/**
* Chart Color Palette Utility
* 统一的图表调色盘工具函数
* CSS 变量中动态获取颜色确保与主题保持一致
*/
let colorPalette = []
let isInitialized = false
/**
* Build color palette from CSS variables in base.css
* base.css 中的 CSS 变量构建调色盘
*/
const buildColorPalette = () => {
try {
const root = document.documentElement
const styles = getComputedStyle(root)
const pick = (name, fallback) => {
const v = styles.getPropertyValue(name)
return v && v.trim() ? v.trim() : fallback
}
// Base chart colors
const baseVars = [
['--chart-primary', '#3996ae'],
['--chart-success', '#52c41a'],
['--chart-warning', '#faad14'],
['--chart-error', '#f5222d'],
['--chart-secondary', '#722ed1'],
['--chart-accent', '#13c2c2'],
]
// Extended palette colors
const paletteVars = [
['--chart-palette-1', '#3996ae'],
['--chart-palette-2', '#028ea0'],
['--chart-palette-3', '#00b8a9'],
['--chart-palette-4', '#f2c94c'],
['--chart-palette-5', '#eb5757'],
['--chart-palette-6', '#2f80ed'],
['--chart-palette-7', '#9b51e0'],
['--chart-palette-8', '#56ccf2'],
['--chart-palette-9', '#6fcf97'],
['--chart-palette-10', '#333333'],
]
const baseColors = baseVars.map(([n, f]) => pick(n, f))
const paletteColors = paletteVars.map(([n, f]) => pick(n, f))
// Priority: palette first, then base colors
const merged = [...paletteColors, ...baseColors]
.filter(Boolean)
.filter((c, idx, arr) => arr.indexOf(c) === idx) // Remove duplicates
colorPalette = merged
isInitialized = true
} catch (e) {
console.warn('Failed to build color palette from CSS variables, using fallback:', e)
// Fallback palette
colorPalette = [
'#3996ae', '#52c41a', '#faad14', '#f5222d', '#722ed1', '#13c2c2',
'#fa8c16', '#1890ff', '#95de64', '#69c0ff'
]
isInitialized = true
}
}
/**
* Get color by index from the palette
* 根据索引从调色盘中获取颜色
* @param {number} index - Color index
* @returns {string} Color value
*/
export const getColorByIndex = (index) => {
if (!isInitialized || colorPalette.length === 0) {
buildColorPalette()
}
return colorPalette[index % colorPalette.length]
}
/**
* Get the entire color palette
* 获取完整的调色盘
* @returns {Array<string>} Color palette array
*/
export const getColorPalette = () => {
if (!isInitialized || colorPalette.length === 0) {
buildColorPalette()
}
return [...colorPalette] // Return a copy
}
/**
* Get specific chart colors by name
* 根据名称获取特定的图表颜色
* @param {string} colorName - Color name (primary, success, warning, error, secondary, accent)
* @returns {string} Color value
*/
export const getChartColor = (colorName) => {
const colorMap = {
primary: '#3996ae',
success: '#52c41a',
warning: '#faad14',
error: '#f5222d',
secondary: '#722ed1',
accent: '#13c2c2'
}
try {
const root = document.documentElement
const styles = getComputedStyle(root)
const cssVarName = `--chart-${colorName}`
const value = styles.getPropertyValue(cssVarName)
return value && value.trim() ? value.trim() : colorMap[colorName] || colorMap.primary
} catch (e) {
return colorMap[colorName] || colorMap.primary
}
}
/**
* Truncate legend text for better display
* 截断图例文本以便更好地显示
* @param {string} name - Legend name
* @param {number} maxLength - Maximum length (default: 20)
* @returns {string} Truncated name
*/
export const truncateLegend = (name, maxLength = 20) => {
if (!name) return ''
return name.length > maxLength ? name.slice(0, maxLength) + '…' : name
}
/**
* Initialize the color palette (call this when DOM is ready)
* 初始化调色盘 DOM 准备好时调用
*/
export const initColorPalette = () => {
buildColorPalette()
}
// Auto-initialize when module is loaded
if (typeof window !== 'undefined' && document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initColorPalette)
} else if (typeof window !== 'undefined') {
initColorPalette()
}