效果预览
组件调用示例:
<el-row class="block-margin--top" :gutter="20">
<el-col :span="9">
<div class="col-block col-block--card" style="height: 300px">
<div class="col-block__title">告警类型分布</div>
<div class="col-block__content col-block__content--full">
<PieEcharts height="100%" :center-pie="['25%', '50%']" label-type="center" label-method="grossValue" :chartData="chartData"></PieEcharts>
</div>
</div>
</el-col>
</el-row>
组件示例数据:
/** 饼图数据示例 */
private chartData = [
{value: 22600, name: '侦察', suffix: '2.26万次 (7.05%)'},
{value: 10400, name: '可疑', suffix: '1.04万次 (7.05%)'},
{value: 43200, name: '拒绝服务', suffix: '2.26万次 (7.05%)'},
{value: 5567, name: '恶意文件', suffix: '5567次 (7.05%)'},
{value: 6918, name: 'WEB攻击', suffix: '6918次 (7.05%)'},
{value: 6918, name: '远程控制', suffix: '2.26万次 (7.05%)'},
{value: 6918, name: '恶意利用', suffix: '2.26万次 (7.05%)'},
{value: 6918, name: 'APT攻击', suffix: '2.26万次 (7.05%)'},
{value: 6918, name: '自定义威胁', suffix: '5401次 (7.05%)'},
]
具体代码实现:
Template 代码
<template>
<div :style="{ height: height, width: width }">
<div v-show="!showEmpty" v-loading="loading" :class="className" ref="myChart" :style="{ height: height, width: width }"></div>
<el-empty v-show="showEmpty" style="padding: 0;height: calc(100% - 40px);" :description="emptyDesc" :image-size="100"></el-empty>
</div>
</template>
Script 代码
<script lang="ts">
import Vue from 'vue';
import * as echarts from 'echarts/core';
import {Component, Emit, Prop, Watch} from 'vue-property-decorator';
import {common} from '@/utils/common';
import {nextTick} from 'vue/types/umd';
import {NdrModule} from '@/store/modules/Ndr';
@Component({})
export default class PieEcharts extends Vue {
// 图表数据
@Prop({
default: () => {
}
}) readonly chartData?: any;
// 图表样式名称
@Prop({default: 'class-name'}) readonly className?: String;
// 图表颜色
@Prop({default: () => ['#3761ee', '#ff905d', '#f94565', '#e7bcf3', '#aa6dfe', '#8378ea', '#1c9dff', '#31c5e9', '#90c577']})
readonly color?: any;
// 图表内外半径
@Prop({default: () => ['50%', '70%']}) readonly radius?: any;
// 图表位置
@Prop({default: () => ['36%', '50%']}) readonly centerPie?: any;
// 图表宽
@Prop({default: '100%'}) readonly width?: any;
// 图表高
@Prop({default: '80%'}) readonly height?: any;
// 图表标题
@Prop({default: '数据图表'}) readonly name?: any;
// 右边子栏是否显示
@Prop({default: true}) readonly legendShow?: any;
// 右边子栏是否能点击
@Prop({default: true}) readonly electedModeShow?: any;
// 右边子栏位置
@Prop({default: 'center'}) readonly legendTop?: any;
// label描述显示位置 显示类型1.outside 2.
@Prop({default: 'outside'}) readonly labelType?: any;
// label描述是否显示
@Prop({default: true}) readonly labelShow?: any;
// label鼠标移入是否显示信息
@Prop({default: true}) readonly emphasisEabelShow?: any;
// label描述的几种显示形式
@Prop({default: 'default'}) readonly labelMethod?: any;
// 整体图表类型 类型1 default //类型2progress
@Prop({default: 'default'}) readonly typePie?: any;
// 最小的扇区角度(0 ~ 360),用于防止某个值过小导致扇区太小影响交互
@Prop({default: 0}) readonly minAngle?: any;
// 是否显示组件自动计算的占比
@Prop({default: false}) readonly tipsAppendAutoPercent?: any;
// 是否显示数量单位
@Prop({default: ''}) readonly tipsAppendAutoUnit?: string
// 是否显示悬浮信息
@Prop({default: true}) readonly showTooltip?: boolean
/** Loading */
protected loading: any = false;
/** 图表是否显示暂无数据 */
protected showEmpty = false
/** 中间状态的描述文字 */
protected emptyDesc: string = '';
protected chartInstance: any = null;
protected options: any = {};
@Emit('typeClick')
protected typeClick(data: any) {
return data;
}
/***********************************************************************************************
* 方法
* *******************************************************************************************/
@Watch('chartData')
onChartDataChanged(newVal: any, oldVal: any) {
this.showEmpty = Object.keys(this.chartData).length == 0
this.initChart()
}
@Watch('loading')
protected loadingChange(val: any) {
this.loading = val;
this.emptyDesc = val ? '数据加载中' : '暂无数据';
}
//初始化图标
private initChart = () => {
this.options = {
color: this.color,
tooltip: {
show: this.showTooltip,
formatter: (params: any) => {
let ext = '';
let unit = '';
if (params.data.unit) {
unit += params.data.unit;
}
if (this.tipsAppendAutoPercent) {
ext += ` (${params.percent}%)`;
}
if (this.tipsAppendAutoUnit) {
unit += this.tipsAppendAutoUnit;
}
let returnData = `
<div style="padding: 5px 10px;">
<span style="display:inline-block; width:10px; height:10px; margin-right:5px; border-radius:10px; background-color:${
params.color
}"></span>
<span style="color:${params.color}">${params.name}</span>
<span style="color:${params.color}">${common.FormatNumberUnit(params.data.value)}${unit}${ext}</span>
</div>`;
this.$emit('changeData', params);
return returnData;
}
},
legend: {
right: '0%',
top: this.legendTop,
orient: '',
show: this.legendShow,
selectedMode: this.electedModeShow,
formatter: (params: any) => {
let newChartData = this.chartData.map((item: any) => ({name: item.name, suffix: item.suffix}));
let name = '';
for (let i = 0; i < newChartData.length; i++) {
if (newChartData[i].suffix && newChartData[i].name == params) {
return params + '——' + newChartData[i].suffix;
} else {
name = params;
}
}
return params;
}
},
series: [
{
name: this.name,
type: 'pie',
minAngle: this.minAngle,//最小角度
radius: this.radius,
center: this.centerPie,
avoidLabelOverlap: true,
labelLine: {
show: this.labelShow,
length: 10,
length2: 15
},
label: {
show: this.labelShow,
position: this.labelType,
formatter: (params: any) => {
let newChartData = this.chartData.map((item: any) => ({
value: item.value,
name: item.name,
suffix: item.suffix
}));
let num = 0;
let ext = '';
if (params.data.ext) {
ext += params.data.ext;
}
for (let i in newChartData) {
num += parseInt(newChartData[i].value);
}
let title = this.labelType === 'center' ? 'b' : 'c';
let context = this.labelType === 'center' ? 'a' : 'd';
// 固定总值显示,正常显示,自我后缀显示
if (this.labelMethod == 'grossValue') {
return `{${title}|${common.FormatNumberUnit(num)}}\n{${context}|总数}`;
} else if (this.labelMethod == 'percent') {
return `{${title}|${params.data.percent} %}`;
} else if (this.labelMethod == 'default') {
return `{${title}|${params.data.name}}\n{${context}|${common.FormatNumberUnit(params.data.value)}${ext}(${params.data.percent})}`;
}
},
rich: {
a: {
color: '#666',
lineHeight: 10
},
b: {
fontSize: 20,
fontWeight: 700,
color: '#333',
display: 'block',
lineHeight: 20
},
c: {
fontSize: 14,
fontWeight: 700,
color: '#333',
display: 'block',
lineHeight: 20
},
d: {
fontSize: 12,
fontWeight: 500,
color: '#333',
display: 'block',
lineHeight: 12
}
}
},
emphasis: {
label: {
show: this.emphasisEabelShow,
fontSize: '10',
fontWeight: 'bold'
}
},
z: 2,
data: this.chartData
}
]
}
let that = this;
if (this.$refs.myChart) {
this.chartInstance = echarts.init(<any>this.$refs.myChart);
}
this.chartInstance.setOption(this.options);
that.chartInstance.on('click', function (params: any) {
let typeData: any = params.data;
if (typeData) {
that.$emit('typeClick', typeData);
}
});
};
//更改图标样式
private updateCharts = () => {
if (this.typePie == 'progress') {
let round = <any>{
type: 'pie',
radius: this.radius,
center: this.centerPie,
hoverAnimation: false,
animation: false,
itemStyle: {
color: '#ff905d'
},
z: 0,
label: {
show: false
},
stillShowZeroSum: true,
data: [0]
};
this.options.series.unshift(round);
}
this.initChart();
};
private screenAdapter = () => {
this.initChart();
this.chartInstance.resize();
};
protected async initData() {
await this.initChart()
await this.updateCharts();
}
async mounted() {
await this.initData()
this.showEmpty = Object.keys(this.chartData).length == 0
window.addEventListener('resize', this.screenAdapter);
}
}
</script>
SCSS 代码
.col-block {
padding: 15px 25px;
border-radius: 15px;
overflow: hidden;
&--inner-padding {
padding: 15px 20px;
}
&.normal-gray-border {
border: 1px solid $gray-dash-border-color;
//box-shadow: none;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.03);
}
&--card {
background-color: #fff;
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.1);
.text-hover {
cursor: pointer;
&:hover {
color: $blue-color;
}
}
}
&--no-padding {
padding: 0;
border-radius: 0;
}
&__title {
margin-bottom: 5px;
align-items: center;
font-size: 16px;
font-weight: bolder;
display: flex;
justify-content: space-between;
&--normal-font {
font-weight: normal;
}
}
&__content {
height: fit-content;
margin-bottom: 5px;
&--full {
height: calc(100% - 10px);
}
&--full-b20 {
height: calc(100% - 20px);
}
&--full-b30 {
height: calc(100% - 30px);
}
&--left {
float: left;
}
&--right {
float: right;
}
&--between {
display: flex;
justify-content: space-between;
}
&__text {
line-height: 55px;
width: calc(100% - 55px);
overflow: hidden;
height: 55px;
&__append-text {
margin-left: 5px;
}
}
&__icon {
height: 55px;
width: 55px;
&--align-center {
align-self: center;
}
}
}
&__content--pie-center {
height: 100%; position: relative;
.pie-center-title {
position: absolute; text-align: center; top: 20px; width: 100%; z-index: 99;
.big-font-count {
font-size: 18px;
font-weight: bold;
}
}
}
&__footer {
margin-top: 10px;
display: flex;
justify-content: space-between;
span {
&:first-child {
//margin-right: 15%;
}
display: inline-block;
}
}
}
总结:
至此,我们的组件编码就已经完成了,小伙伴们可以尝试着跑起来了。
本专栏会定期更新 Vue_ElementUI_Echarts_Typescript 有关的实例教程,希望感兴趣的小伙伴们多多关注~ 有问题在评论区留言吧~ 谢谢