vue项目中使用echarts

2,815 阅读3分钟

最近项目中用到较多的echarts图表,所以考虑把它们做成组件,以便日后的复用。

一、引入echarts

在package.json中写入echarts依赖,运行npm install

"dependencies": {
    ...
    "echarts": "4.2.1",
    ...
  }

二、按需引入模块

整个echarts文件还是挺大的,我们可以根据自身的需求,按需引入模块。比如我新建了一个js,然后根据项目需求引入了折线图、柱状图等组件。

// 引入主模块
import echarts from 'echarts/lib/echarts'
// 按需引入需要使用的图表类型,标题,提示信息等
import 'echarts/lib/chart/bar'
import 'echarts/lib/chart/line'
import 'echarts/lib/chart/pie'
import 'echarts/lib/component/legend'
import 'echarts/lib/component/title'
import 'echarts/lib/component/tooltip'
export default echarts

三、创建组件

需要考虑以下几点:

  • 1、图表创建
  • 2、图表需根据数据变化而变化(使用watch监听传入的数据)
  • 3、窗口发生变化时,图表能自适应(echarts的resize方法)
  • 4、无数据的时候需要做一些提示说明

最终代码如下:

<template>
  <div class="module-box" :style="{height:height,width:width}">
    <div v-if="showChart" :id="id" class="chart-box" />
    <div v-else class="no-data">暂无数据</div>
  </div>
</template>
<script>
import echarts from '@/config/echarts'
import resize from '@/views/dashboard/mixins/resize'
export default {
  mixins: [resize],
  props: {
    data: {
      default() {
        return {
          tradeLineData: [
            {
              name: "折线1",
              color: "#3186FF",
              data: [120, 132, 101, 134, 90, 230, 210]
            },
            {
              name: "折线2",
              color: "#FF9609",
              data: [108, 125, 148, 136, 99, 87, 148]
            }
          ],
          tradeLineAxis: ['01/01','01/02','01/03','01/04','01/05','01/06','01/07'],
        }
      }
    },    
    width: {
      type: String,
      default: '100%'
    },
    height: {
      type: String,
      default: '350px'
    },
    yAxisName: {
      type: String,
      default: '人次'
    },
  },
  data() {
    return {
      chart: null,
      showChart:true,
      option: {
        color: ['#589DFF', '#FC7D6A', '#30C28D', '#FAC000'],
        title: {
          text: '',
          textStyle: {
            color: '#222222',
            fontSize: 14,
            lineHeight: 20
          },
          left: 24,
          top: 0
        },
        tooltip: {
          trigger: 'axis',
          backgroundColor: 'rgba(0,0,0,0.8)',
          textStyle: {
            fontSize: 12,
            color: '#fff',
            lineHeight: 20
          },
          padding: 12
        },
        legend: {
          show: true,
          right: 26,
          // icon: 'circle',
          itemWidth: 6,
          itemHeight: 6,
          top: 0,
          itemHeight: '5',
          itemGap: 26,
          textStyle: {
            fontSize: 14,
            color: '#757575',
            fontWeight: 400,
            lineHeight: 20
          },
          data: []
        },
        grid: {
          containLabel: true,
          left: 26,
          right: 30,
          top: 64,
          bottom: 10
        },
        xAxis: {
          type: 'category',
          boundaryGap: false,
          axisLine: {
            show: true,
            lineStyle: {
              width: 0.5,
              color: '#BDC8D3'
            }
          },
          nameGap: 8,
          axisTick: {
            show: true,
            lineStyle: {
              width: 1,
              height: 4,
              color: '#BDC8D3'
            }
          },
          axisLabel: {
            show: true,
            textStyle: {
              color: '#666',
              fontSize: 14,
              lineHeight: 14
            }
          },
          data: []
        },
        yAxis: {
          type: 'value',
          name: this.yAxisName,
          splitLine: {
            show: true,
            lineStyle: {
              color: '#F0F3F5',
              height: 1
            }
          },
          axisLine: {
            show: false
          },
          axisTick: {
            show: false
          },
          nameTextStyle: {
            color: '#222',
            fontSize: '16',
            lineHeight: 22,
            fontWeight:500,
            padding: [0, 0, 20, 0]
          },
          axisLabel: {
            show: true,
            textStyle: {
              color: '#666',
              fontSize: 14,
              lineHeight: 14,
              fontFamily: 'Helvetica'
            }
          }
        },
        series: []
      }
    }
  },
  computed: {
    id() {
      return (
        'pieChart' + new Date().getTime() + Math.round(Math.random() * 100)
      )
    },
  },
  created() {
    
  },
  watch: {
    data: {
      deep: true,
      handler(newVal, oldVal) {
        console.log("newVal:",newVal)
        console.log("reload line chart")
        if(newVal.tradeLineAxis.length){
          this.showChart = true
          this.initChart()
        }else{
          this.showChart = false
        }
      }
    }
  },
  mounted() {
    // this.$nextTick(() => {
    //   this.initChart()
    // })
  },
  beforeDestroy() {
    if (!this.chart) {
      return
    }
    this.chart.dispose()
    this.chart = null
  },
  methods: {
    initChart() {
      this.option.series = this.keyParams().series
      this.option.legend.data = this.keyParams().legend
      this.option.xAxis.data = this.data.tradeLineAxis
      if (this.option.legend.data.length > 1) {
        this.option.legend.show = true
      }
      this.chart = echarts.init(document.getElementById(this.id))
      this.chart.setOption(this.option, true)
    },
    
    keyParams() {
      const p = {
        legend: [],
        series: [],
        color: []
      }
      this.data.tradeLineData.forEach(el => {
        p.color.push(el.color),
        p.legend.push({
          name: el.name,
          textStyle: {
            color: '#666666'
          }
        })
        p.series.push({
          type:'line',
          name: el.name,
          // stack: "总量",
          barMaxWidth: 25,
          symbol: 'circle',
          // symbolSize: 8,
          showAllSymbol: true,
          itemStyle: {
            normal: {
              color: el.color,
              borderWidth: '3'
            }
          },
          lineStyle: {
            normal: {
              color: el.color,
              width: '2'
            }
          },
          // areaStyle: {
          //   normal: {
          //     color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
          //       {
          //         offset: 0,
          //         color: el.color
          //       },
          //       {
          //         offset: 1,
          //         color: '#ffffff'
          //       }
          //     ]),
          //     opacity: '0.1'
          //   }
          // },
          data: el.data
        })
      })
      return p
    },
    delPositiveLable(val) {
      return val.toString().replace(/\-/, '')
    }
  }
}
</script>
<style scoped>
* {
  margin: 0;
}
.module-box {
  position: relative;
}
.chart-box {
  position: relative;
  height: 100%;
}
.no-data {
  font-size:14px;
  font-family:PingFangSC-Regular,PingFang SC;
  font-weight:400;
  color:rgba(111,111,111,1);
  line-height:20px;
}
</style>

混入的resize方法:

import { debounce } from '@/utils'
export default {
  data() {
    return {
      $_sidebarElm: null
    }
  },
  mounted() {
    this.$_initResizeEvent()
    this.$_initSidebarResizeEvent()
  },
  beforeDestroy() {
    this.$_destroyResizeEvent()
    this.$_destroySidebarResizeEvent()
  },
  activated() {
    this.$_initResizeEvent()
    this.$_initSidebarResizeEvent()
  },
  deactivated() {
    this.$_destroyResizeEvent()
    this.$_destroySidebarResizeEvent()
  },
  methods: {
    $_resizeHandler() {
      return debounce(() => {
        if (this.chart) {
          this.chart.resize()
        }
      }, 100)()
    },
    $_initResizeEvent() {
      window.addEventListener('resize', this.$_resizeHandler)
    },
    $_destroyResizeEvent() {
      window.removeEventListener('resize', this.$_resizeHandler)
    },
    $_sidebarResizeHandler(e) {
      if (e.propertyName === 'width') {
        this.$_resizeHandler()
      }
    },
    $_initSidebarResizeEvent() {
      this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0]
      this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)
    },
    $_destroySidebarResizeEvent() {
      this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)
    }
  }
}

以及工具类的防抖函数debounce

/**
 * @param {Function} func
 * @param {number} wait
 * @param {boolean} immediate
 * @return {*}
 */
export function debounce(func, wait, immediate) {
  let timeout, args, context, timestamp, result
  const later = function() {
    // 据上一次触发时间间隔
    const last = +new Date() - timestamp
    // 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
    if (last < wait && last > 0) {
      timeout = setTimeout(later, wait - last)
    } else {
      timeout = null
      // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
      if (!immediate) {
        result = func.apply(context, args)
        if (!timeout) context = args = null
      }
    }
  }
  return function(...args) {
    context = this
    timestamp = +new Date()
    const callNow = immediate && !timeout
    // 如果延时不存在,重新设定延时
    if (!timeout) timeout = setTimeout(later, wait)
    if (callNow) {
      result = func.apply(context, args)
      context = args = null
    }
    return result
  }
}

四、组件调用

<template>
  <TradeLine
    :data="data"
    height="390px"
  />
  ...
</template>
<script>
import TradeLine from "@/views/operation/components/TradeLine"
export default {
    data() {
        return {
            data:{
                tradeLineData: [
                  {
                    name: "折线1",
                    color: "#3186FF",
                    data: []
                  },
                  {
                    name: "折线2",
                    color: "#FF9609",
                    data: []
                  }
                ],
                tradeLineAxis: [],
            },
            ...
        }
    },
    created() {
        this.initLineChartData();
    },
    methods:{
        initLineChartData() {
          consultTreeData(this.param).then(res => {
            // 清空数据
            this.data.tradeLineData.forEach(element => {
              element.data = [];
            });
            this.data.tradeLineAxis = []
            res.param.forEach(element => {
              this.data.tradeLineData[0].data.push(element.wzCount);
              this.data.tradeLineData[1].data.push(element.fzCount);
              this.data.tradeLineAxis.push(this.formatYearMonth(element.dateTime));
            });
          });
        },
        ...
    },
    ...
}
</script>