You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
194 lines
4.7 KiB
194 lines
4.7 KiB
<template>
|
|
<div ref="chartContainer" class="comparison-bar-chart-container"></div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
|
|
import * as echarts from 'echarts'
|
|
|
|
// 定义组件属性
|
|
interface Props {
|
|
chartData?: Array<{
|
|
name: string
|
|
currentValue: number
|
|
previousValue: number
|
|
growthRate?: number
|
|
currentPeriod?: string
|
|
previousPeriod?: string
|
|
}>
|
|
title?: string
|
|
currentColor?: string
|
|
previousColor?: string
|
|
showYAxis?: boolean
|
|
yAxisName?: string
|
|
currentLabel?: string
|
|
previousLabel?: string
|
|
chartType?: 'default' | 'percentage'
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
chartData: () => [],
|
|
title: '',
|
|
currentColor: '#1890FF',
|
|
previousColor: '#A9A9A9',
|
|
showYAxis: true,
|
|
yAxisName: '数值',
|
|
currentLabel: '本期',
|
|
previousLabel: '上期',
|
|
chartType: 'default'
|
|
})
|
|
|
|
// 图表容器引用和实例
|
|
const chartContainer = ref<HTMLDivElement | null>(null)
|
|
let chartInstance: echarts.ECharts | null = null
|
|
|
|
// 创建图表配置
|
|
const createBarOption = (currentLabel: string, previousLabel: string, currentValue: number, previousValue: number) => {
|
|
// 根据图表类型决定格式化方式
|
|
const isPercentage = props.chartType === 'percentage';
|
|
|
|
return {
|
|
tooltip: {
|
|
trigger: 'axis',
|
|
axisPointer: { type: 'shadow' },
|
|
formatter: (params: any) => {
|
|
const value = params[0].value;
|
|
const formattedValue = isPercentage ? `${(value * 100).toFixed(1)}%` : value.toFixed(2);
|
|
return `${params[0].name}: ${formattedValue}`;
|
|
}
|
|
},
|
|
grid: {
|
|
left: '15%',
|
|
right: '10%',
|
|
bottom: '20%',
|
|
top: '15%'
|
|
},
|
|
xAxis: {
|
|
type: 'category',
|
|
data: [previousLabel, currentLabel],
|
|
axisTick: { show: false },
|
|
axisLine: { show: false },
|
|
axisLabel: {
|
|
color: 'rgba(255, 255, 255, 0.7)',
|
|
fontSize: 22
|
|
}
|
|
},
|
|
yAxis: {
|
|
type: 'value',
|
|
axisLabel: {
|
|
formatter: isPercentage ? (value: number) => `${(value * 100).toFixed(0)}%` : '{value}',
|
|
color: 'rgba(255, 255, 255, 0.7)'
|
|
},
|
|
splitLine: {
|
|
lineStyle: {
|
|
color: 'rgba(255, 255, 255, 0.1)'
|
|
}
|
|
},
|
|
axisLine: {
|
|
lineStyle: {
|
|
color: 'rgba(255, 255, 255, 0.3)'
|
|
}
|
|
},
|
|
max: isPercentage ? 1 : undefined
|
|
},
|
|
series: [{
|
|
name: isPercentage ? '百分比' : '用量',
|
|
type: 'bar',
|
|
barWidth: '60%',
|
|
data: [
|
|
{ value: previousValue, itemStyle: { color: props.previousColor } },
|
|
{ value: currentValue, itemStyle: { color: props.currentColor } }
|
|
],
|
|
label: {
|
|
show: true,
|
|
position: 'top',
|
|
fontWeight: 'bold',
|
|
color: '#fff',
|
|
formatter: (params: any) => {
|
|
if (params.value <= 0) return '';
|
|
return isPercentage ? `${(params.value * 100).toFixed(1)}%` : params.value.toFixed(2);
|
|
}
|
|
}
|
|
}],
|
|
// 添加标题显示增长率
|
|
title: props.chartData[0]?.growthRate !== undefined ? {
|
|
text: `${props.chartData[0].growthRate >= 0 ? '↑' : '↓'} ${(props.chartData[0].growthRate * 100).toFixed(1)}%`,
|
|
left: 'center',
|
|
top: '5%',
|
|
textStyle: {
|
|
fontSize: 32,
|
|
fontWeight: 'bold',
|
|
color: props.chartData[0].growthRate >= 0 ? '#52c41a' : '#f5222d',
|
|
fontFamily: 'Arial, sans-serif'
|
|
}
|
|
} : undefined
|
|
};
|
|
}
|
|
|
|
// 初始化图表
|
|
const initChart = () => {
|
|
if (chartContainer.value) {
|
|
chartInstance = echarts.init(chartContainer.value)
|
|
updateChart()
|
|
}
|
|
}
|
|
|
|
// 更新图表
|
|
const updateChart = () => {
|
|
if (!chartInstance || !props.chartData.length) return
|
|
|
|
const dataItem = props.chartData[0]
|
|
|
|
// 优先使用period字段作为标签,如果没有则使用默认标签
|
|
const currentLabel = dataItem.currentPeriod || props.currentLabel
|
|
const previousLabel = dataItem.previousPeriod || props.previousLabel
|
|
|
|
const option = createBarOption(
|
|
currentLabel,
|
|
previousLabel,
|
|
dataItem.currentValue,
|
|
dataItem.previousValue
|
|
)
|
|
|
|
chartInstance.setOption(option)
|
|
}
|
|
|
|
// 监听数据变化
|
|
watch(
|
|
() => props.chartData,
|
|
() => {
|
|
updateChart()
|
|
},
|
|
{ deep: true }
|
|
)
|
|
|
|
// 窗口大小改变时重绘图表
|
|
const handleResize = () => {
|
|
if (chartInstance) {
|
|
chartInstance.resize()
|
|
}
|
|
}
|
|
|
|
// 组件挂载时初始化图表
|
|
onMounted(() => {
|
|
initChart()
|
|
|
|
window.addEventListener('resize', handleResize)
|
|
})
|
|
|
|
// 组件销毁前清理资源
|
|
onBeforeUnmount(() => {
|
|
window.removeEventListener('resize', handleResize)
|
|
if (chartInstance) {
|
|
chartInstance.dispose()
|
|
chartInstance = null
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.comparison-bar-chart-container {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
</style>
|