zxl
2026-03-25 27c7661cae945f65f8d2752cae41801e3c2b1485
src/views/dataAnalysis/components/DataReLineChart.vue
@@ -22,13 +22,204 @@
      type: Boolean,
      default: false,
    },
    enablePointClick: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      chartEntity: null,
      zrClickHandler: null,
      zrMoveHandler: null,
      zrMouseOutHandler: null,
      lastHoverIndex: -1,
      lastEmitName: '',
      lastEmitAt: 0,
      lastLegendToggleAt: 0,
    }
  },
  methods: {
    getOffsetPoint(event) {
      if (!event) {
        return null
      }
      if (event.offsetX !== undefined && event.offsetY !== undefined) {
        return [event.offsetX, event.offsetY]
      }
      if (event.event && event.event.offsetX !== undefined && event.event.offsetY !== undefined) {
        return [event.event.offsetX, event.event.offsetY]
      }
      return null
    },
    isPointInGrid(point) {
      if (!this.chartEntity || !this.chartEntity.containPixel || !point) {
        return false
      }
      return this.chartEntity.containPixel({ gridIndex: 0 }, point)
    },
    getXData() {
      return this.chartData && this.chartData.xData ? this.chartData.xData : []
    },
    resolveNearestIndexByOffsetX(offsetX) {
      let xData = this.getXData()
      if (!this.chartEntity || !this.chartEntity.convertToPixel || !xData.length) {
        return -1
      }
      let nearestIndex = -1
      let minDistance = Number.MAX_VALUE
      for (let i = 0; i < xData.length; i++) {
        let xPixel = this.chartEntity.convertToPixel({ xAxisIndex: 0 }, xData[i])
        if (Array.isArray(xPixel)) {
          xPixel = xPixel[0]
        }
        if (xPixel === undefined || xPixel === null || Number.isNaN(Number(xPixel))) {
          continue
        }
        let distance = Math.abs(Number(offsetX) - Number(xPixel))
        if (distance < minDistance) {
          minDistance = distance
          nearestIndex = i
        }
      }
      if (nearestIndex === -1) {
        return -1
      }
      return nearestIndex
    },
    resolveClickIndex(event, params) {
      let xData = this.getXData()
      if (!xData.length) {
        return -1
      }
      let point = this.getOffsetPoint(event)
      if (!this.isPointInGrid(point)) {
        return -1
      }
      if (params && typeof params.dataIndex === 'number' && xData[params.dataIndex] !== undefined) {
        return params.dataIndex
      }
      if (params && params.name) {
        let nameIndex = xData.findIndex((item) => item === params.name)
        if (nameIndex > -1) {
          return nameIndex
        }
      }
      if (this.chartEntity && this.chartEntity.convertFromPixel && event) {
        let xAxisValue = this.chartEntity.convertFromPixel({ xAxisIndex: 0 }, [event.offsetX, event.offsetY])
        if (xAxisValue !== undefined && xAxisValue !== null && !Number.isNaN(Number(xAxisValue))) {
          let xIndex = Math.round(Number(xAxisValue))
          if (xIndex >= 0 && xIndex < xData.length) {
            return xIndex
          }
        }
      }
      if (event && event.offsetX !== undefined && event.offsetX !== null) {
        return this.resolveNearestIndexByOffsetX(event.offsetX)
      }
      return -1
    },
    emitChartClickByIndex(index, seriesName) {
      let xData = this.getXData()
      if (index < 0 || index >= xData.length) {
        return
      }
      let clickName = xData[index]
      let now = Date.now()
      if (this.lastEmitName === clickName && now - this.lastEmitAt < 120) {
        return
      }
      this.lastEmitName = clickName
      this.lastEmitAt = now
      this.$emit('chart-click', {
        name: clickName,
        seriesName: seriesName || null,
      })
    },
    hideTooltip() {
      if (!this.chartEntity) {
        return
      }
      this.chartEntity.dispatchAction({
        type: 'hideTip',
      })
    },
    bindChartClick() {
      if (!this.chartEntity) {
        return
      }
      this.chartEntity.off('click')
      if (this.chartEntity.getZr) {
        let zr = this.chartEntity.getZr()
        if (this.zrClickHandler) {
          zr.off('click', this.zrClickHandler)
        }
        if (this.zrMoveHandler) {
          zr.off('mousemove', this.zrMoveHandler)
        }
        if (this.zrMouseOutHandler) {
          zr.off('globalout', this.zrMouseOutHandler)
        }
      }
      if (!this.enablePointClick) {
        let chartDom = document.getElementById(this.domId)
        if (chartDom) {
          chartDom.style.cursor = 'default'
        }
        return
      }
      let chartDom = document.getElementById(this.domId)
      if (chartDom) {
        chartDom.style.cursor = 'pointer'
      }
      this.chartEntity.on('legendselectchanged', () => {
        this.lastLegendToggleAt = Date.now()
      })
      this.chartEntity.on('click', (params) => {
        if (params && params.componentType === 'legend') {
          return
        }
        if (Date.now() - this.lastLegendToggleAt < 180) {
          return
        }
        let index = this.resolveClickIndex(params && params.event ? params.event : null, params)
        this.emitChartClickByIndex(index, params ? params.seriesName : null)
      })
      this.zrMoveHandler = (event) => {
        let xData = this.getXData()
        if (!xData.length) {
          return
        }
        let xIndex = this.resolveClickIndex(event, null)
        if (xIndex < 0 || xIndex >= xData.length) {
          return
        }
        if (this.lastHoverIndex === xIndex) {
          return
        }
        this.lastHoverIndex = xIndex
        this.chartEntity.dispatchAction({
          type: 'showTip',
          seriesIndex: 0,
          dataIndex: xIndex,
        })
      }
      this.zrMouseOutHandler = () => {
        this.lastHoverIndex = -1
        this.hideTooltip()
      }
      this.zrClickHandler = (event) => {
        if (Date.now() - this.lastLegendToggleAt < 180) {
          return
        }
        let index = this.resolveClickIndex(event, null)
        this.emitChartClickByIndex(index, null)
      }
      let zr = this.chartEntity.getZr()
      zr.on('mousemove', this.zrMoveHandler)
      zr.on('globalout', this.zrMouseOutHandler)
      zr.on('click', this.zrClickHandler)
    },
    setChart() {
      if (!this.chartEntity) {
        let chartDom = document.getElementById(this.domId)
@@ -38,6 +229,8 @@
      var option = {
        tooltip: {
          trigger: 'axis',
          confine: true,
          extraCssText: 'pointer-events:none;',
          axisPointer: {
            // 坐标轴指示器,坐标轴触发有效
            type: 'line', // 默认为直线,可选为:'line' | 'shadow'
@@ -54,23 +247,29 @@
          shadowBlur: 10,
          formatter: (params) => {
            let strName1 = params[0].name
            let value1 = params[0] ? params[0].value : 0
            let value2 = params[1] ? params[1].value : 0
            let value3 = params[2] ? params[2].value : 0
            let value4 = params[3] ? params[3].value : 0
            return `<div style="color:#fff;font-size:16px;">${strName1}</div>
              <div><span style="color:#fff;display: inline-block;width: 86px;">${
                this.chartData.barName
              }</span><span style="color:#5DB6FB">${value1}
              </span></div><div><span style="color:#fff;display: inline-block;width: 86px;">${
                this.chartData.lineName
              }</span><span style="color:#5DB6FB">${value2}
              </span></div><div><span style="color:#fff;display: inline-block;width: 86px;">${
                this.chartData.barName2
              }</span><span style="color:#FF6B6B">${value3}
              </span></div><div><span style="color:#fff;display: inline-block;width: 86px;">${
                this.chartData.lineName2
              }</span><span style="color:#FFD93D">${value4}</span>`
            let result = `<div style="color:#fff;font-size:16px;">${strName1}</div>`
            if (params[0] && this.chartData.barName) {
              let value1 = params[0].value !== undefined ? params[0].value : 0
              result += `<div><span style="color:#fff;display: inline-block;width: 86px;">${this.chartData.barName}</span><span style="color:#5DB6FB">${value1}</span></div>`
            }
            if (params[1] && this.chartData.lineName) {
              let value2 = params[1].value !== undefined ? params[1].value : 0
              result += `<div><span style="color:#fff;display: inline-block;width: 86px;">${this.chartData.lineName}</span><span style="color:#5DB6FB">${value2}</span></div>`
            }
            if (params[2] && this.chartData.barName2) {
              let value3 = params[2].value !== undefined ? params[2].value : 0
              result += `<div><span style="color:#fff;display: inline-block;width: 86px;">${this.chartData.barName2}</span><span style="color:#FF6B6B">${value3}</span></div>`
            }
            if (params[3] && this.chartData.lineName2) {
              let value4 = params[3].value !== undefined ? params[3].value : 0
              result += `<div><span style="color:#fff;display: inline-block;width: 86px;">${this.chartData.lineName2}</span><span style="color:#FFD93D">${value4}</span></div>`
            }
            return result
          },
          textStyle: {
            rich: {
@@ -129,93 +328,93 @@
          },
        ],
        series: [
          {
            name: this.chartData.barName,
            type: 'bar',
            barWidth: '12px',
            itemStyle: {
              normal: {
                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                  {
                    offset: 0,
                    color: '#5B81F9',
                  },
                  {
                    offset: 1,
                    color: '#151A22',
                  },
                ]),
              },
            },
            data: this.chartData.barData,
          },
          {
            name: this.chartData.lineName,
            type: 'bar',
            barWidth: '12px',
            itemStyle: {
              normal: {
                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                  {
                    offset: 0,
                    color: '#16B777',
                  },
                  {
                    offset: 1,
                    color: '#0D6E4A',
                  },
                ]),
              },
            },
            data: this.chartData.lineData,
          },
          {
            name: this.chartData.barName2,
            type: 'bar',
            barWidth: '12px',
            itemStyle: {
              normal: {
                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                  {
                    offset: 0,
                    color: '#FF6B6B',
                  },
                  {
                    offset: 1,
                    color: '#8B0000',
                  },
                ]),
              },
            },
            data: this.chartData.barData2,
          },
          {
            name: this.chartData.lineName2,
            type: 'bar',
            barWidth: '12px',
            itemStyle: {
              normal: {
                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                  {
                    offset: 0,
                    color: '#FFD93D',
                  },
                  {
                    offset: 1,
                    color: '#B8860B',
                  },
                ]),
              },
            },
            data: this.chartData.lineData2,
          },
        ],
        series: [],
      }
      if (this.chartData.barData) {
        option.series.push({
          name: this.chartData.barName,
          type: 'bar',
          barWidth: '12px',
          itemStyle: {
            normal: {
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                {
                  offset: 0,
                  color: '#5B81F9',
                },
                {
                  offset: 1,
                  color: '#151A22',
                },
              ]),
            },
          },
          data: this.chartData.barData,
        })
      }
      if (this.chartData.lineData) {
        option.series.push({
          name: this.chartData.lineName,
          type: 'line',
          smooth: true,
          symbol: 'circle',
          symbolSize: 6,
          lineStyle: {
            width: 2,
            color: '#16B777',
          },
          itemStyle: {
            color: '#16B777',
          },
          data: this.chartData.lineData,
        })
      }
      if (this.chartData.barData2) {
        option.series.push({
          name: this.chartData.barName2,
          type: 'bar',
          barWidth: '12px',
          itemStyle: {
            normal: {
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                {
                  offset: 0,
                  color: '#FF6B6B',
                },
                {
                  offset: 1,
                  color: '#8B0000',
                },
              ]),
            },
          },
          data: this.chartData.barData2,
        })
      }
      if (this.chartData.lineData2) {
        option.series.push({
          name: this.chartData.lineName2,
          type: 'line',
          smooth: true,
          symbol: 'circle',
          symbolSize: 6,
          lineStyle: {
            width: 2,
            color: '#FFD93D',
          },
          itemStyle: {
            color: '#FFD93D',
          },
          data: this.chartData.lineData2,
        })
      }
      option && this.chartEntity.setOption(option, true)
      this.bindChartClick()
    },
    setPieUpChart() {
      if (!this.chartEntity) {
@@ -241,6 +440,8 @@
      var option = {
        tooltip: {
          trigger: 'axis',
          confine: true,
          extraCssText: 'pointer-events:none;',
          axisPointer: {
            // 坐标轴指示器,坐标轴触发有效
            type: 'line', // 默认为直线,可选为:'line' | 'shadow'
@@ -299,6 +500,7 @@
        series: [...data],
      }
      option && this.chartEntity.setOption(option, true)
      this.bindChartClick()
    },
    setOnlyLineChart() {
      if (!this.chartEntity) {
@@ -309,6 +511,8 @@
      var option = {
        tooltip: {
          trigger: 'axis',
          confine: true,
          extraCssText: 'pointer-events:none;',
          axisPointer: {
            // 坐标轴指示器,坐标轴触发有效
            type: 'line', // 默认为直线,可选为:'line' | 'shadow'
@@ -442,6 +646,7 @@
        ],
      }
      option && this.chartEntity.setOption(option, true)
      this.bindChartClick()
    },
  },
}
@@ -449,5 +654,7 @@
<style lang="less" scoped>
.line-chart {
  height: 20vh;
  position: relative;
  overflow: hidden;
}
</style>