最近项目中用到较多的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>