2 changed files with 411 additions and 18 deletions
@ -0,0 +1,367 @@ |
|||||||
|
<template> |
||||||
|
<div ref="ChartsBarLineRef" style="height:100%;width: 100%;" class="chart-container"></div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import * as echarts from "echarts"; |
||||||
|
import dayjs from 'dayjs'; |
||||||
|
import { onMounted, nextTick, ref, onUnmounted, watch } from "vue"; |
||||||
|
|
||||||
|
const props = defineProps({ |
||||||
|
legendData: { |
||||||
|
type: Array, |
||||||
|
default: () => [] |
||||||
|
}, |
||||||
|
xAxisData: { |
||||||
|
type: Array, |
||||||
|
default: () => [] |
||||||
|
}, |
||||||
|
seriesData: { |
||||||
|
type: Array, |
||||||
|
default: () => [] |
||||||
|
}, |
||||||
|
baseOptionData: { |
||||||
|
type: Array, |
||||||
|
default: () => [] |
||||||
|
}, |
||||||
|
textTitle: { |
||||||
|
type: String, |
||||||
|
default: '' |
||||||
|
}, |
||||||
|
fixed: { |
||||||
|
type: Number, |
||||||
|
default: 2 |
||||||
|
}, |
||||||
|
unit: { |
||||||
|
type: String, |
||||||
|
default: 'mm' |
||||||
|
}, |
||||||
|
echartType: { |
||||||
|
type: String, |
||||||
|
default: 'line' |
||||||
|
}, |
||||||
|
grid: { |
||||||
|
type: Object, |
||||||
|
default: () => { |
||||||
|
return { |
||||||
|
left: "5%", |
||||||
|
right: "5%", |
||||||
|
bottom: "20%", |
||||||
|
top: "16%", |
||||||
|
containLabel: true, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
let ChartsBarLineRef = ref(null); |
||||||
|
let echartsBar = null; |
||||||
|
let resizeObserver = null; |
||||||
|
|
||||||
|
// 监听props变化,确保数据更新后重新初始化图表 |
||||||
|
watch([() => props.legendData, () => props.xAxisData, () => props.seriesData], () => { |
||||||
|
if (echartsBar) { |
||||||
|
updateChart(); |
||||||
|
} |
||||||
|
}, { deep: true }); |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
initEcharts(); |
||||||
|
window.addEventListener('resize', handleResize); |
||||||
|
// 监听容器本身尺寸变化 |
||||||
|
if (ChartsBarLineRef.value) { |
||||||
|
resizeObserver = new ResizeObserver(handleResize); |
||||||
|
resizeObserver.observe(ChartsBarLineRef.value); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
onUnmounted(() => { |
||||||
|
window.removeEventListener('resize', handleResize); |
||||||
|
if (resizeObserver && ChartsBarLineRef.value) { |
||||||
|
resizeObserver.unobserve(ChartsBarLineRef.value); |
||||||
|
} |
||||||
|
if (echartsBar) { |
||||||
|
echartsBar.dispose(); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
const echartsLoading = ref(true); |
||||||
|
|
||||||
|
const initEcharts = () => { |
||||||
|
nextTick(() => { |
||||||
|
if (ChartsBarLineRef.value) { |
||||||
|
echartsBar = echarts.init(ChartsBarLineRef.value, "macarons"); |
||||||
|
let option = getChartOption(); |
||||||
|
echartsBar.setOption(option); |
||||||
|
|
||||||
|
// 确保图表尺寸适应容器 |
||||||
|
const resizeChart = () => { |
||||||
|
if (echartsBar && ChartsBarLineRef.value) { |
||||||
|
echartsBar.resize({ |
||||||
|
width: ChartsBarLineRef.value.clientWidth, |
||||||
|
height: ChartsBarLineRef.value.clientHeight |
||||||
|
}); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
// 初始化时调整一次大小 |
||||||
|
resizeChart(); |
||||||
|
} |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
const updateChart = () => { |
||||||
|
nextTick(() => { |
||||||
|
if (echartsBar) { |
||||||
|
let option = getChartOption(); |
||||||
|
echartsBar.setOption(option, true); // 使用notMerge:true来完全替换旧选项 |
||||||
|
resizeChart(); |
||||||
|
} |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
const resizeChart = () => { |
||||||
|
if (echartsBar && ChartsBarLineRef.value) { |
||||||
|
echartsBar.resize({ |
||||||
|
width: ChartsBarLineRef.value.clientWidth, |
||||||
|
height: ChartsBarLineRef.value.clientHeight |
||||||
|
}); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const handleResize = () => { |
||||||
|
if (echartsBar && ChartsBarLineRef.value) { |
||||||
|
// 添加防抖机制,避免频繁触发 |
||||||
|
clearTimeout(window.resizeTimer); |
||||||
|
window.resizeTimer = setTimeout(() => { |
||||||
|
echartsBar.resize({ |
||||||
|
width: ChartsBarLineRef.value.clientWidth, |
||||||
|
height: ChartsBarLineRef.value.clientHeight, |
||||||
|
animation: { |
||||||
|
duration: 300 |
||||||
|
} |
||||||
|
}); |
||||||
|
}, 100); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
// 使用 dayjs 格式化时间轴标签 |
||||||
|
const formatTimelineLabel = (s) => { |
||||||
|
return dayjs(s).format('MM-DD'); |
||||||
|
}; |
||||||
|
|
||||||
|
// 使用 dayjs 格式化 X 轴标签 |
||||||
|
const formatXAxisLabel = (value) => { |
||||||
|
return dayjs(value).format('HH:mm'); |
||||||
|
}; |
||||||
|
|
||||||
|
// 使用 dayjs 格式化 tooltip 中的时间 |
||||||
|
const formatTooltipDate = (dateValue) => { |
||||||
|
return dayjs(dateValue).format('YYYY-MM-DD HH:mm'); |
||||||
|
}; |
||||||
|
|
||||||
|
// 使用 dayjs 格式化 tooltip 中的数据时间 |
||||||
|
const formatTooltipDataDate = (dateValue) => { |
||||||
|
const date = dayjs(dateValue); |
||||||
|
return `${date.format('YYYY-MM-DD')} ${date.format('HH:mm')}`; |
||||||
|
}; |
||||||
|
|
||||||
|
const getChartOption = () => { |
||||||
|
let xAxisType = 'time'; |
||||||
|
let yAxisBoundaryGap = ['5%', '5%']; |
||||||
|
if (props.echartType == 'bar') { |
||||||
|
xAxisType = 'category'; |
||||||
|
yAxisBoundaryGap = [0, '5%']; |
||||||
|
} |
||||||
|
|
||||||
|
// 颜色配置提取为常量 |
||||||
|
const CHART_COLORS = [ |
||||||
|
["#FA8072", "#B22222"], |
||||||
|
["#0fec7d", "#04793e"], |
||||||
|
["#ab36fc", "#7705a4"], |
||||||
|
["#ffbd89", "#8a5c08"], |
||||||
|
["#CD9B1D", "#8B6914"], |
||||||
|
["#ff7f50", "#a93f07"], |
||||||
|
["#EE1289", "#8B0A50"], |
||||||
|
["#ee9c9c", "#755252"], |
||||||
|
["#06e8d6", "#078075"], |
||||||
|
["#de79c2", "#cd5c5c"], |
||||||
|
["#00F5FF", "#6b8e23"], |
||||||
|
["#f50000", "#590404"], |
||||||
|
["#00CD66", "#3cb371"], |
||||||
|
["#c400fa", "#48005d"], |
||||||
|
["#eca86c", "#5e4310"], |
||||||
|
["#bebe0f", "#8B8B00"] |
||||||
|
]; |
||||||
|
|
||||||
|
let optionsData = props.baseOptionData.map((base, idx) => ({ |
||||||
|
series: props.seriesData.map((item, index) => { |
||||||
|
const color = CHART_COLORS[index % CHART_COLORS.length]; |
||||||
|
return { |
||||||
|
name: props.legendData[index], |
||||||
|
type: 'line', |
||||||
|
symbolSize: 0, |
||||||
|
smooth: false, |
||||||
|
lineStyle: { |
||||||
|
width: 2, |
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
||||||
|
{ offset: 0, color: color[0] }, |
||||||
|
{ offset: 1, color: color[1] } |
||||||
|
]) |
||||||
|
}, |
||||||
|
itemStyle: { |
||||||
|
opacity: 0 |
||||||
|
}, |
||||||
|
markPoint: { |
||||||
|
data: [ |
||||||
|
{ type: 'max', name: '最高值', label: { formatter: '{c}' } }, |
||||||
|
{ type: 'min', name: '最低值', label: { formatter: '{c}' } } |
||||||
|
], |
||||||
|
itemStyle: { |
||||||
|
color: color[0] |
||||||
|
}, |
||||||
|
label: { |
||||||
|
color: '#fff', |
||||||
|
} |
||||||
|
}, |
||||||
|
data: item[idx] |
||||||
|
}; |
||||||
|
}) |
||||||
|
})); |
||||||
|
console.log(optionsData, 'optionsData') |
||||||
|
let option = { |
||||||
|
baseOption: { |
||||||
|
color: CHART_COLORS, |
||||||
|
timeline: { |
||||||
|
axisType: 'category', |
||||||
|
autoPlay: false, |
||||||
|
playInterval: 2000, |
||||||
|
data: props.baseOptionData, |
||||||
|
label: { |
||||||
|
formatter: formatTimelineLabel |
||||||
|
}, |
||||||
|
lineStyle: { |
||||||
|
color: "#464b50" |
||||||
|
}, |
||||||
|
controlStyle: { |
||||||
|
color: "#464b50", |
||||||
|
borderColor: "#464b50" |
||||||
|
}, |
||||||
|
bottom:'10%' |
||||||
|
}, |
||||||
|
title: { |
||||||
|
text: props.textTitle, |
||||||
|
x: 'center', |
||||||
|
textStyle: { |
||||||
|
color: '#464b50', |
||||||
|
fontSize: 16 |
||||||
|
} |
||||||
|
}, |
||||||
|
tooltip: { |
||||||
|
trigger: 'axis', |
||||||
|
axisPointer: { |
||||||
|
type: 'shadow', |
||||||
|
shadowStyle: { |
||||||
|
color: "rgba(133,131,131,0.3)" |
||||||
|
} |
||||||
|
}, |
||||||
|
backgroundColor: 'rgba(255,255,255,1)', |
||||||
|
padding: [5, 10], |
||||||
|
textStyle: { |
||||||
|
color: '#7588E4', |
||||||
|
align: 'left' |
||||||
|
}, |
||||||
|
extraCssText: 'box-shadow: 0 0 5px rgba(0,0,0,0.3)', |
||||||
|
formatter: function (params) { |
||||||
|
const dateValue = params[0].data[0]; |
||||||
|
const formattedDate = formatTooltipDataDate(dateValue); |
||||||
|
let text = formattedDate + '<br/>'; |
||||||
|
for (let i = 0; i < params.length; i++) { |
||||||
|
text += `<span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${params[i].color}"></span>` |
||||||
|
text += params[i].seriesName + " : " + params[i].data[1] + '<br/>'; |
||||||
|
} |
||||||
|
return text; |
||||||
|
} |
||||||
|
}, |
||||||
|
legend: { |
||||||
|
data: props.legendData, |
||||||
|
right: 10, |
||||||
|
top: 20, |
||||||
|
textStyle: { |
||||||
|
color: "#333" |
||||||
|
}, |
||||||
|
itemWidth: 15, |
||||||
|
itemHeight: 8, |
||||||
|
}, |
||||||
|
grid: props.grid, |
||||||
|
toolbox: { |
||||||
|
show: true, |
||||||
|
feature: { |
||||||
|
mark: { show: true }, |
||||||
|
restore: { iconStyle: { borderColor: '#000' } }, |
||||||
|
saveAsImage: { iconStyle: { borderColor: '#000' } } |
||||||
|
}, |
||||||
|
y: 'center', |
||||||
|
orient: 'vertical' |
||||||
|
}, |
||||||
|
xAxis: [ |
||||||
|
{ |
||||||
|
type: 'time', |
||||||
|
boundaryGap: true, |
||||||
|
axisLabel: { |
||||||
|
formatter: formatXAxisLabel, |
||||||
|
textStyle: { |
||||||
|
color: '#606e7a' |
||||||
|
} |
||||||
|
}, |
||||||
|
axisLine: { |
||||||
|
lineStyle: { |
||||||
|
color: '#b3b6b9' |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
], |
||||||
|
yAxis: [ |
||||||
|
{ |
||||||
|
type: 'value', |
||||||
|
axisLabel: { |
||||||
|
textStyle: { |
||||||
|
color: "#606e7a", |
||||||
|
}, |
||||||
|
formatter: function (value, index) { |
||||||
|
return value.toFixed(props.fixed); |
||||||
|
} |
||||||
|
}, |
||||||
|
name: props.unit, |
||||||
|
nameLocation: 'middle', |
||||||
|
nameGap: 50, |
||||||
|
nameTextStyle: { |
||||||
|
color: '#464b50' |
||||||
|
}, |
||||||
|
scale: true, |
||||||
|
boundaryGap: ['5%', '5%'], |
||||||
|
axisLine: { |
||||||
|
show: true, |
||||||
|
lineStyle: { |
||||||
|
color: '#b3b6b9' |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
], |
||||||
|
series: [] |
||||||
|
}, |
||||||
|
options: optionsData |
||||||
|
}; |
||||||
|
|
||||||
|
return option; |
||||||
|
}; |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.chart-container { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
min-height: 300px; |
||||||
|
position: relative; |
||||||
|
} |
||||||
|
</style> |
||||||
Loading…
Reference in new issue