<template>
<div class="trend-chart">
<div class="trend-chart-instance" ref="chartRef"></div>
</div>
</template>
<script setup>
import { ref, shallowRef, toRefs, onMounted, onBeforeUnmount, nextTick, computed } from 'vue';
import * as echarts from 'echarts';
import { addThousandSeparator } from '@/utils/index';
import handleIcon from '@/assets/images/handler_flip.png';
const props = defineProps({
option: {
type: Array,
default: () => {},
},
color: {
type: Array,
default: ['#1CD1AA', '#2367F1', '#9F51E7'],
},
});
const { option, color } = toRefs(props);
const chartRef = ref(null);
const maxVisableXAxis = ref(0);
const chartInstance = shallowRef(null);
const getHexToRgba = (hex, opacity = 1) => {
const newHex = hex.match(/^#[0-9a-f]{3}$/gi)
? hex.replace(/^(#)([0-9a-f])([0-9a-f])([0-9a-f])$/gi, '$1$2$2$3$3$4$4')
: hex;
if ((!opacity && opacity !== 0) || opacity === 1) {
return newHex;
}
return `rgba(${[1, 3, 5]
.map(h => parseInt(newHex.substring(h, h + 2), 16))
.join(',')},${opacity})`;
};
const getIsDataZoom = computed(
() => (option.value?.xAxis?.data || []).length > maxVisableXAxis.value
);
const maxSpan = computed(() =>
Math.floor((maxVisableXAxis.value / (option.value?.xAxis?.data || []).length) * 100)
);
const getSeries = computed(() => option.value?.series || []);
const getOption = computed(() => ({
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(255, 255, 255)', // 通过设置rgba调节背景颜色与透明度
extraCssText: 'box-shadow : 0px 2px 6px rgba( 10,80,200,0.18 );', // 边框阴影
color: '#6e6e6e',
borderColor: '#fff',
axisPointer: {
lineStyle: {
type: 'dashed',
color: '#b1b8cc',
},
},
confine: true,
textStyle: {
fontSize: 14,
color: '#6e6e6e',
},
formatter: params => {
let title = `<span style="font-family: Arial;">${params[0].axisValue}</span><br/>`;
let str = `<span style="font-family: Microsoft YaHei;">${params[0].seriesName}: </span>
<span style="font-family: Arial;color: #333;font-weight: Bold;">
${
params[0].value || params[0].value === 0 ? addThousandSeparator(params[0].value) : '--'
}</span>`;
return title + str;
},
},
grid: {
top: 60,
left: 10,
right: getIsDataZoom.value ? 15 : 0, // 存在zoom时:需要增大间距,保证zoom图标显示完整
bottom: getIsDataZoom.value ? 24 : 0, // 存在zoom时:需要增大间距,保证x轴文本和zoom控件的间距
containLabel: true,
},
dataZoom: getIsDataZoom.value
? [
{
show: true,
type: 'slider',
zoomLock: true,
showDetail: false,
showDataShadow: false,
detail: false,
filterMode: 'weakFilter',
handleIcon: 'image://' + handleIcon,
handleSize: 30,
handleStyle: {
color: 'rgba(43, 101, 240, 0.08)',
},
fillerColor: 'rgba(0,0,0,.05)',
borderColor: 'rgba(0,0,0,.075)',
moveHandleSize: 0,
moveHandleIcon: 'none',
brushSelect: false,
maxSpan: maxSpan.value,
throttle: 0,
height: 16,
bottom: '0',
},
]
: [],
xAxis: {
type: 'category',
boundaryGap: true,
data: option.value?.xAxis?.data || [],
axisLine: {
lineStyle: {
color: '#d8dde0',
width: 1,
type: 'solid',
},
},
axisLabel: {
color: '#999',
fontSize: 14,
margin: 10,
overflow: 'truncate',
},
axisTick: {
show: false,
},
splitLine: {
show: false,
interval: '0',
lineStyle: {
color: ['#d8dde0'],
width: 1,
type: 'solid',
},
},
},
yAxis: {
type: 'value',
name: getSeries.value.length && option.value?.xAxis?.data.length ? '(个)' : '',
nameGap: 20,
nameTextStyle: {
color: '#999',
fontSize: 14,
align: 'right',
padding: [0, 0, 0, 0],
},
axisLine: {
show: false,
},
axisTick: {
show: false,
},
splitLine: {
show: true,
lineStyle: {
color: ['#d8dde0'],
width: 1,
type: 'dashed',
},
},
axisLabel: {
show: true,
color: '#999',
fontSize: 14,
margin: 4,
},
},
color: color.value,
series: getSeries.value.map((item, index) => {
return {
type: 'line',
smooth: true,
showSymbol: true,
symbol: 'circle',
symbolSize: 6,
lineStyle: {
width: 2,
shadowColor: 'rgba(0, 0, 0, 0.0625)',
shadowBlur: 2,
shadowOffsetY: 10,
},
emphasis: {
scale: false,
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: getHexToRgba(color.value[index], 0.25),
},
{
offset: 1,
color: getHexToRgba(color.value[index], 0),
},
],
},
},
...item,
};
}),
}));
const updateChart = async () => {
await nextTick();
if (!chartRef.value) {
return;
}
if (chartInstance.value) {
chartInstance.value.clear();
} else {
chartInstance.value = echarts.init(chartRef.value);
chartInstance.value.on('highlight', params => {
chartInstance.value.setOption({
series: [
{
symbol: (value, data) => {
if (data.dataIndex === params.batch[0].dataIndex) {
return 'image://' + getSeries.value[0].highlightSymbol;
} else {
return 'circle';
}
},
symbolSize: (value, data) => {
if (data.dataIndex === params.batch[0].dataIndex) {
return 18;
} else {
return 6;
}
},
},
],
});
});
chartInstance.value.on('downplay', () => {
chartInstance.value.setOption({
series: [
{
symbol: 'circle',
symbolSize: 6,
},
],
});
});
}
try {
maxVisableXAxis.value = Math.floor(chartRef.value.offsetWidth / 114); // 最大可显示x轴label个数
chartInstance.value.setOption(getOption.value, true);
handleResize();
} catch (error) {}
};
const handleResize = async e => {
if (chartInstance.value) {
if (e) {
await nextTick();
updateChart();
}
chartInstance.value.resize();
}
};
onMounted(() => {
top.window.addEventListener('resize', handleResize);
});
onBeforeUnmount(() => {
if (chartInstance.value) {
chartInstance.value.dispose();
chartInstance.value = null;
}
top.window.removeEventListener('resize', handleResize);
});
defineExpose({ chartRef, updateChart });
</script>
<style lang="scss">
.trend-chart {
width: 100%;
height: 332px;
position: relative;
.trend-chart-instance {
width: 100%;
height: 100%;
}
.no-data {
text-align: center;
font-size: 14px;
color: #999;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
</style>
console