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.

256 lines
5.4 KiB

3 weeks ago
<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
}>
title?: string
currentColor?: string
previousColor?: string
showYAxis?: boolean
yAxisName?: string
}
const props = withDefaults(defineProps<Props>(), {
chartData: () => [],
title: '',
currentColor: '#1296db',
previousColor: '#ff6b6b',
showYAxis: true,
yAxisName: '数值'
})
// 图表容器引用和实例
const chartContainer = ref<HTMLDivElement | null>(null)
let chartInstance: echarts.ECharts | null = null
// 初始化图表
const initChart = () => {
if (chartContainer.value) {
chartInstance = echarts.init(chartContainer.value)
updateChart()
}
}
// 更新图表
const updateChart = () => {
if (!chartInstance || !props.chartData.length) return
const option = {
title: {
text: props.title,
textStyle: {
color: '#fff',
fontSize: 14
},
left: 'center'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
backgroundColor: 'rgba(0, 0, 0, 0.7)',
borderColor: 'rgba(30, 120, 255, 0.4)',
borderWidth: 1,
textStyle: {
color: '#fff'
},
formatter: function (params: any) {
const currentData = params[0]
const previousData = params[1]
const growthRate = previousData.value > 0
? ((currentData.value - previousData.value) / previousData.value * 100).toFixed(2)
: '∞'
return `${currentData.name}<br/>
${currentData.seriesName}: ${currentData.value}<br/>
${previousData.seriesName}: ${previousData.value}<br/>
增长率: ${growthRate}%`
}
},
legend: {
data: ['本期', '上期'],
textStyle: {
color: '#fff'
},
top: 30
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '25%',
containLabel: true
},
xAxis: {
type: 'category',
data: props.chartData.map(item => item.name),
axisLine: {
lineStyle: {
color: 'rgba(255, 255, 255, 0.3)'
}
},
axisLabel: {
color: 'rgba(255, 255, 255, 0.7)',
rotate: 0,
interval: 0,
margin: 15
}
},
yAxis: {
type: 'value',
show: props.showYAxis,
name: props.yAxisName,
nameTextStyle: {
color: 'rgba(255, 255, 255, 0.7)'
},
axisLine: {
show: props.showYAxis,
lineStyle: {
color: 'rgba(255, 255, 255, 0.3)'
}
},
splitLine: {
show: props.showYAxis,
lineStyle: {
color: 'rgba(255, 255, 255, 0.1)'
}
},
axisLabel: {
show: props.showYAxis,
color: 'rgba(255, 255, 255, 0.7)'
}
},
series: [
{
name: '当前期',
type: 'bar',
data: props.chartData.map(item => item.currentValue),
barWidth: '35%',
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: props.currentColor
},
{
offset: 1,
color: props.currentColor + '80'
}
]
},
borderRadius: [4, 4, 0, 0]
},
emphasis: {
itemStyle: {
color: props.currentColor
}
},
label: {
show: true,
position: 'top',
color: '#fff',
formatter: '{c}',
fontSize: 12
}
},
{
name: '对比期',
type: 'bar',
data: props.chartData.map(item => item.previousValue),
barWidth: '35%',
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: props.previousColor
},
{
offset: 1,
color: props.previousColor + '80'
}
]
},
borderRadius: [4, 4, 0, 0]
},
emphasis: {
itemStyle: {
color: props.previousColor
}
},
label: {
show: true,
position: 'top',
color: '#fff',
formatter: '{c}',
fontSize: 12
}
}
]
}
chartInstance.setOption(option)
}
// 监听数据变化
watch(
() => props.chartData,
() => {
updateChart()
},
{ deep: true }
)
// 窗口大小改变时重绘图表
const handleResize = () => {
if (chartInstance) {
chartInstance.resize()
}
}
// 组件挂载时初始化图表
onMounted(() => {
3 weeks ago
initChart()
3 weeks ago
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>