SOURCE

<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 ? 10 : 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',
          start: 100 - maxSpan.value,
        },
      ]
    : [],
  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,
      interval: 0,
      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 命令行工具 X clear

                    
>
console