携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情
参照最原始的echarts,官网地址:echarts.apache.org/examples/zh…
如果项目中数据可视化图表比较多,若每个图表都重新写一遍,代码又臭又长,修改的时候自己都要找半天才能对应上
所以,图表二次封装为组件,每一次用直接引用就行,代码瞬间减少80% 话不多说,直接上代码:
子组件:
html:
<template>
<div class="chartContent" :style="{ height: height, width: width, position: 'relative' }">
<div :id="id" :class="className" style="height: 100%; width: 100%"></div>
<no-data v-if="noData" class="noDataCls"></no-data>
</div>
</template>
js: NoData、resize是对图表暂无数据和窗口变化的组件方法,后面会把文件放上
<script>
import NoData from '@/components/NoData'
import resize from './mixins/resize'
export default {
mixins: [resize],
components: { NoData },
props: {
className: {
type: String,
default: 'chart'
},
id: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '200px'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null,
noData: null
}
},
beforeDestroy() {
this.destoryChart()
},
methods: {
destoryChart() {
if (this.chart) {
this.chart.clear()
this.chart.dispose()
this.chart = null
}
},
initChart(data) {
this.destoryChart()
if (data.noData) {
this.noData = true
return
}
this.noData = false
let { emitName, seriesData, yAxisData } = data
this.chart = echarts.init(document.getElementById(this.id))
var res = []
if (seriesData && seriesData.length) {
res = seriesData.map(item => {
if (item.actualDays > item.targetDays) {
return {
value: item.actualDays,
itemStyle: {
color: '#F17373',
borderRadius: 5 //圆角
}
}
} else {
return {
value: item.actualDays,
itemStyle: {
color: '#3ed473',
borderRadius: 5 //圆角
}
}
}
})
}
this.chart.setOption(
{
grid: {
top: '30',
left: '20',
right: '40',
bottom: '10',
containLabel: true
},
tooltip: {
trigger: 'axis'
},
// Y轴数据太多时滑动(看自己需求要不要保留)
dataZoom: [
{
start: 0, //默认为0
end: 100, //默认为100
type: 'slider',
maxValueSpan: 4, //显示数据的条数(默认显示10个)
// show: true,
show: yAxisData && yAxisData.length > 5 ? true : false,
yAxisIndex: [0],
handleSize: 20, //滑动条的 左右2个滑动条的大小
width: 12,
height: '76%', //组件高度
// left: 600, //左边的距离
right: 38, //右边的距离
top: 40, //上边边的距离
// borderColor: 'rgba(43,48,67,0.8)',
fillerColor: '#F5F5F5', //滑动块的颜色
// backgroundColor: 'rgba(13,33,117,0.5)', //两边未选中的滑动条区域的颜色
showDataShadow: false, //是否显示数据阴影 默认auto
showDetail: true, //即拖拽时候是否显示详细数值信息 默认true
realtime: true, //是否实时更新
filterMode: 'filter',
yAxisIndex: [0, 1] //控制的y轴
},
//滑块的属性
{
type: 'inside',
show: true,
yAxisIndex: [0, 1],
start: 1, //默认为1
end: 100 //默认为100
}
],
yAxis: {
type: 'category',
data: yAxisData
},
xAxis: {
type: 'value'
},
series: [
{
data: res,
barWidth: 16,
label: {
show: true,
position: 'insideRight',
formatter: '{c}' + ' 天',
color: '#fff',
fontSize: 12
},
type: 'bar'
}
]
},
true
)
this.chart.on('click', param => {
// 点击当前图表 触发父组件方法 并传参
emitName && this.$emit(emitName, param.name)
})
this.chart.off('click', () => {
console.log('解决多次调用接口问题')
})
}
}
}
</script>
css:
<style lang="scss" scoped>
.noDataCls {
position: absolute;
left: 0px;
top: 0px;
right: 0px;
bottom: 0px;
}
</style>
父组件: html:组件使用,id必须,ref必须且唯一
<zhu-left-rights
id="productionId2"
@showProductionDetail2="showProductionDetail2"
width="100%"
height="200px"
ref="zhuLeftRight2"
></zhu-left-rights>
js:获取数据源
// 引入组件,别忘了注册
import ZhuLeftRights from '@/components/Charts/ZhuLeftRights'
// 交付周期偏差
showDetail2(val) {
this.$Api.orderManage.getFinishOrderDetails(val).then(res => {
let { code, data } = res
if (code == 0) {
if (data.length) {
const productionDataY2 = new Array(data.length).fill(null)
const seriesData = []
for (let i = 0; i < data.length; i++) {
productionDataY2[i] = data[i].name
seriesData.push(data[i])
}
this.productionDataY2 = productionDataY2
this.$nextTick(() => {
this.$refs.zhuLeftRight2.initChart({
emitName: 'showProductionDetail2',
yAxisData: this.productionDataY2,
seriesData: seriesData,
noData: !(this.productionDataY2 && this.productionDataY2.length > 0)
})
})
} else {
this.$nextTick(() => {
this.$refs.zhuLeftRight2.initChart({
emitName: 'showProductionDetail2',
yAxisData: [],
seriesData: [],
noData: true
})
})
}
} else {
this.$message.error(res.msg)
}
})
},
效果图:
说明:
子组件中:
循环seriesData的目的就是根据条件,显示不同颜色的柱状图, 如果想要正负轴显示的,及把seriesData里的value的值变成负数即可
父组件中: zhuLeftRight2: 通过ref来渲染调用子组件(单页面名称不可重复) emitName : 控制子组件图表点击后触发父组件事件 yAxisData: Y轴显示数据(内容过多可以加datazoom) seriesData:x轴数据源 noData:图表无数据是显示暂无数据图片
附件代码:
**// 新建一个js文件名为 debounce**
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
}
}
**// 新建一个js文件名为resize 文件 并引入debounce文件**
import { debounce } from '@/common/utils/debounce'
export default {
data() {
return {
$_sidebarElm: null,
$_resizeHandler: null
}
},
mounted() {
this.initListener()
},
activated() {
if (!this.$_resizeHandler) {
// avoid duplication init
this.initListener()
}
// when keep-alive chart activated, auto resize
this.resize()
},
beforeDestroy() {
this.destroyListener()
},
deactivated() {
this.destroyListener()
},
methods: {
// use $_ for mixins properties
// https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
$_sidebarResizeHandler(e) {
if (e.propertyName === 'width') {
this.$_resizeHandler()
}
},
initListener() {
this.$_resizeHandler = debounce(() => {
this.resize()
}, 10)
window.addEventListener('resize', this.$_resizeHandler)
this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0]
this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)
},
destroyListener() {
window.removeEventListener('resize', this.$_resizeHandler)
this.$_resizeHandler = null
this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)
},
resize() {
const { chart, treeGraph } = this
chart && chart.resize()
if (treeGraph) {
const container = document.getElementById('antvOrzTree')
const width = container.clientWidth
const height = container.clientHeight
treeGraph.changeSize(width, height) //修改画布的大小
treeGraph.fitCenter() //将图移动到画布中心位置
treeGraph.fitView() // 让画布内容适应视口
}
}
}
}
// 暂无数据组件样式代码 (图片自己加想要的)
<template>
<div class="noData">
<div class="noData-neir">
<img class="img" src="@/assets/imgs/noData.png" alt="" />
<div class="tit">暂无数据</div>
</div>
</div>
</template>
<script>
export default {
name: 'NoData',
data() {
return {}
},
props: {},
mounted() {},
methods: {}
}
</script>
<style lang="scss" scoped>
.noData {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
.noData-neir {
text-align: center;
.img {
height: 60px;
}
.tit {
font-size: 13px;
font-family: Source Han Sans SC;
font-weight: 400;
color: rgba(0, 0, 0, 0.3);
line-height: 46px;
}
}
}
</style>