2 changed files with 411 additions and 18 deletions
@ -0,0 +1,367 @@
@@ -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