MTRd丢包趋势图

1、MTRAgent丢包趋势图自定义移入提示框的内容
2、MTR丢包趋势图添加点击提示框按钮,弹窗显示列表详情
3、服务器管理添加MTR探测趋势图,根据点的变化展示不同的table列表信息
This commit is contained in:
康冉冉
2025-12-02 18:41:22 +08:00
parent bf2597a0e4
commit 9ac8cae26f
5 changed files with 141 additions and 65 deletions
+3
View File
@@ -341,3 +341,6 @@ aside {
left: 53% !important; left: 53% !important;
//transform: translate(-50%, -50%) !important; //transform: translate(-50%, -50%) !important;
} }
.el-dialog__wrapper {
z-index: 99999999!important;
}
+49 -27
View File
@@ -84,6 +84,12 @@
this.getList(this.title, this.lineData); this.getList(this.title, this.lineData);
}); });
} }
// 确保使用 window 对象挂载
window.detailsVal = this.detailsVal;
},
beforeUnmount() {
// 组件销毁时清理全局函数
delete window.detailsVal;
}, },
methods: { methods: {
getList(title, dataXY) { getList(title, dataXY) {
@@ -162,45 +168,52 @@
y = y - size.contentSize[1] - 20; y = y - size.contentSize[1] - 20;
} }
return [x + 5, y + 10]; return [x - 10, y + 10];
}, },
formatter: !dataXY.tooltipFormatter ? null : function(params) { formatter: !dataXY.tooltipFormatter ? null : function(params) { // 主要应对与MTRAgent丢包趋势图模块的功能
// 获取当前悬停的日期 // 获取当前悬停的日期
const date = params[0].name; const date = params[0].name;
// 构建提示框HTML内容 // 构建提示框HTML内容
let html = `<div>${date}</div>`; let html = `<div>${date}</div>`;
html+=`<table style="border-collapse: collapse;text-align:center;overflow: auto;height: 180px;display: block;">
<tr style="height: 20px">
<th class="tab_border">路径</th>
<th class="tab_border">平均延迟(ms)</th>
<th class="tab_border">丢包率%</th>
<th class="tab_border">地址</th>
<th class="tab_border">国家</th>
<th class="tab_border">运营商</th>
<th class="tab_border">省</th>
<th class="tab_border">市</th>
</tr>`;
// 遍历所有系列 // 遍历所有系列
params.forEach(paramItem => { params.forEach(paramItem => {
if (dataXY.tooltipFormatter && dataXY.tooltipFormatter.customList) { if (dataXY.tooltipFormatter && dataXY.tooltipFormatter.customList) {
if (paramItem.data.content) { if (paramItem.data.content) {
let content = JSON.parse(paramItem.data.content); let content = JSON.parse(paramItem.data.content);
content.forEach((contItem, index) => { html+= `<div>最新一跳平均延迟: ${content[content.length - 1].avgLatency}(ms)</div>
html+= `<tr> <div>最新一跳丢包率: ${content[content.length - 1].lossPercent}(%)</div>`;
<td class="tab_border">${contItem.hopNumber || ''}</td> // 如果不是弹窗这显示按钮,可点击查看弹窗信息
<td class="tab_border">${contItem.avgLatency || ''}</td> if (dataXY.tooltipFormatter && !dataXY.tooltipFormatter.defaultType) {
<td class="tab_border">${contItem.lossPercent || ''}</td> html+= `<button style="float: right;background: #409eff; border-color: #409eff;color:#fff;box-shadow:none;" onclick="window.detailsVal('${date}' ,'${paramItem.dataIndex}')">详情</button>`;
<td class="tab_border">${contItem.ipAddress || ''}</td> }
<td class="tab_border">${contItem.country || ''}</td> } else {
<td class="tab_border ellipsis" title="${contItem.isp}">${contItem.isp || ''}</td> let valueparams = paramItem.value;
<td class="tab_border">${contItem.province || ''}</td> if (dataXY.tooltipFormatter && dataXY.tooltipFormatter.valueToop) {
<td class="tab_border">${contItem.city || ''}</td> let value = dataXY.tooltipFormatter.valueToop;
</tr>`; valueparams = paramItem.value && paramItem.value === value.num ? value.msg : paramItem.value || paramItem.value === 0 ? paramItem.value : '-';
}); // 为每个系列创建一行
html += `
<div style="display: flex; justify-content: space-between; align-items: center; margin: 4px 0;">
<div style="display: flex; align-items: center;">
<span style="display: inline-block; width: 10px; height: 10px; border-radius: 50%; background-color: ${paramItem.color}; margin-right: 8px;"></span>
<span style="color: #57606f;">${paramItem.seriesName} </span>
</div>
<span style="font-weight: bold; color: #2f3542;margin-left:20px;"> ${valueparams}</span>
</div>`;
} }
} }
} else {
// 为每个系列创建一行
html += `
<div style="display: flex; justify-content: space-between; align-items: center; margin: 4px 0;">
<div style="display: flex; align-items: center;">
<span style="display: inline-block; width: 10px; height: 10px; border-radius: 50%; background-color: ${paramItem.color}; margin-right: 8px;"></span>
<span style="color: #57606f;">${paramItem.seriesName} </span>
</div>
<span style="font-weight: bold; color: #2f3542;margin-left:20px;"> ${paramItem.value}</span>
</div>`;
}
}); });
html+=`</table>`;
return html; return html;
} }
}, },
@@ -301,13 +314,18 @@
},1000); },1000);
} }
}); });
// 2. 关键:拦截默认的鼠标移出事件,不做任何操作(即不取消高亮) // ​当鼠标在图形元素内部移动时持续触发
lineDataListIntance.on('mousemove', (params) => {
this.$emit('showChartTabList', {value: params.name, tableVal: params.data});
});
// 2. 关键:拦截默认的鼠标移入事件,不做任何操作(即不取消高亮)
lineDataListIntance.on('mouseover', (params) => { lineDataListIntance.on('mouseover', (params) => {
// 这个函数体为空,或者只包含一些不涉及downplay的逻辑 // 这个函数体为空,或者只包含一些不涉及downplay的逻辑
// 这样就阻止了鼠标移出时高亮自动消失 // 这样就阻止了鼠标移出时高亮自动消失
}); });
// 2. 关键:拦截默认的鼠标移出事件,不做任何操作(即不取消高亮) // 2. 关键:拦截默认的鼠标移出事件,不做任何操作(即不取消高亮)
lineDataListIntance.on('mouseout', (params) => { lineDataListIntance.on('mouseout', (params) => {
this.$emit('showChartTabList', {});
// 这个函数体为空,或者只包含一些不涉及downplay的逻辑 // 这个函数体为空,或者只包含一些不涉及downplay的逻辑
// 这样就阻止了鼠标移出时高亮自动消失 // 这样就阻止了鼠标移出时高亮自动消失
}); });
@@ -388,6 +406,10 @@
} }
// let lineDataList = days && days.length > 0 ? days: this.lineData.lineXData; // let lineDataList = days && days.length > 0 ? days: this.lineData.lineXData;
// this.getList(this.title, {data: this.lineData.data, lineXData: lineDataList}); // this.getList(this.title, {data: this.lineData.data, lineXData: lineDataList});
},
// tooltip中的按钮点击事件
detailsVal(xTime, content) {
this.$emit('dialogChartTabList', {value: xTime, tableVal: this.lineData.dataList[0].data[content]});
} }
} }
} }
+2 -2
View File
@@ -123,12 +123,12 @@
}, },
queryParams: { queryParams: {
type: Object, type: Object,
default: { default: () => ({
total: 0, total: 0,
pageNum: 1, pageNum: 1,
pageSize: 10, pageSize: 10,
layout: 'total, sizes, prev, pager, next, jumper' layout: 'total, sizes, prev, pager, next, jumper'
} })
}, },
config: { config: {
type: Object, type: Object,
+10 -10
View File
@@ -40,10 +40,10 @@
<!-- </template>--> <!-- </template>-->
<!-- </template>--> <!-- </template>-->
<template v-for="(item,index) of echartListData['day']"> <template v-for="(item,index) of echartListData['day']">
<EchartsLine v-show="switchData[item.indexVal].checkType" :lineData="item" :title="item.title" class="w100 mt20 mb20" style="border: 1.5px solid #878787;height:60%;min-height:350px;" :chartData="(valData, unit) => chartDataEvent(valData, unit, resourceType)"></EchartsLine> <EchartsLine v-show="switchData[item.indexVal].checkType" :lineData="item" :title="item.title" class="w100 mt20 mb20" style="border: 1.5px solid #878787;height:60%;min-height:350px;" :chartData="(valData, unit) => chartDataEvent(valData, unit, 'day')"></EchartsLine>
</template> </template>
<template v-for="(item,index) of echartListData['month']"> <template v-for="(item,index) of echartListData['month']">
<EchartsLine v-show="switchData[item.indexVal].checkType" :lineData="item" :title="item.title " class="w100 mt20 mb20" style="border: 1.5px solid #878787;height:60%;min-height:350px;" :chartData="(valData, unit) => chartDataEvent(valData, unit, resourceType)"></EchartsLine> <EchartsLine v-show="switchData[item.indexVal].checkType" :lineData="item" :title="item.title " class="w100 mt20 mb20" style="border: 1.5px solid #878787;height:60%;min-height:350px;" :chartData="(valData, unit) => chartDataEvent(valData, unit, 'month')"></EchartsLine>
</template> </template>
</div> </div>
</div> </div>
@@ -300,9 +300,9 @@
let chartList = []; let chartList = [];
res.data.forEach((item, index) => { res.data.forEach((item, index) => {
let newChartData = JSON.parse(JSON.stringify(this.echartData[this.resourceType === 2 ? 1 : 0])); let newChartData = JSON.parse(JSON.stringify(this.echartData[this.resourceType === 2 ? 1 : 0]));
let outContent = `最后一个值:${item && item.other && item.other.lastOutSpeedGb ? item.other.lastOutSpeedGb + 'G' : '0'} 平均值:${item && item.other && item.other.avgOutSpeedGb ? item.other.avgOutSpeedGb + 'G' : '0'} 最大值:${item && item.other && item.other.maxOutSpeedGb ? item.other.maxOutSpeedGb + 'G' : '0'}`; let outContent = `最后一个值:${item && item.other && item.other.lastOutSpeedGb ? item.other.lastOutSpeedGb + 'G' : '0G'} 平均值:${item && item.other && item.other.avgOutSpeedGb ? item.other.avgOutSpeedGb + 'G' : '0G'} 最大值:${item && item.other && item.other.maxOutSpeedGb ? item.other.maxOutSpeedGb + 'G' : '0G'}`;
let inContent = `最后一个值:${item && item.other && item.other.lastInSpeedGb ? item.other.lastInSpeedGb + 'G' : '0'} 平均值:${item && item.other && item.other.avgInSpeedGb ? item.other.avgInSpeedGb + 'G' : '0'} 最大值:${item && item.other && item.other.maxInSpeedGb ? item.other.maxInSpeedGb + 'G' : '0'}`; let inContent = `最后一个值:${item && item.other && item.other.lastInSpeedGb ? item.other.lastInSpeedGb + 'G' : '0G'} 平均值:${item && item.other && item.other.avgInSpeedGb ? item.other.avgInSpeedGb + 'G' : '0G'} 最大值:${item && item.other && item.other.maxInSpeedGb ? item.other.maxInSpeedGb + 'G' : '0G'}`;
let kdContent = `(${item && item['95value'] ? item['95value'] + 'mbit' : '0'} ${this.resourceType === 2 ? 'in+out' : 'out'})`; let kdContent = `(${item && item['95value'] ? item['95value'] + 'mbit' : '0mbit'} ${this.resourceType === 2 ? 'in+out' : 'out'})`;
newChartData.title = item && item.name; newChartData.title = item && item.name;
newChartData.yAxisName = item && item.other && item.other.recommendedUnit ? '单位' + item.other.recommendedUnit : ' '; newChartData.yAxisName = item && item.other && item.other.recommendedUnit ? '单位' + item.other.recommendedUnit : ' ';
newChartData['unitModel'] = item && item.other && item.other.recommendedUnit || ''; newChartData['unitModel'] = item && item.other && item.other.recommendedUnit || '';
@@ -342,9 +342,9 @@
res.data.forEach((item, index) => { res.data.forEach((item, index) => {
let newChartData = JSON.parse(JSON.stringify(this.echartData[this.resourceType === 2 ? 1 : 0])); let newChartData = JSON.parse(JSON.stringify(this.echartData[this.resourceType === 2 ? 1 : 0]));
newChartData.dataList[newChartData.dataList.length - 1].name = '95带宽值/月'; newChartData.dataList[newChartData.dataList.length - 1].name = '95带宽值/月';
let outContent = `最后一个值:${item && item.other && item.other.lastOutSpeedGb ? item.other.lastOutSpeedGb + 'G' : '0'} 平均值:${item && item.other && item.other.avgOutSpeedGb ? item.other.avgOutSpeedGb + 'G' : '0'} 最大值:${item && item.other && item.other.maxOutSpeedGb ? item.other.maxOutSpeedGb + 'G' : '0'}`; let outContent = `最后一个值:${item && item.other && item.other.lastOutSpeedGb ? item.other.lastOutSpeedGb + 'G' : '0G'} 平均值:${item && item.other && item.other.avgOutSpeedGb ? item.other.avgOutSpeedGb + 'G' : '0G'} 最大值:${item && item.other && item.other.maxOutSpeedGb ? item.other.maxOutSpeedGb + 'G' : '0G'}`;
let inContent = `最后一个值:${item && item.other && item.other.lastInSpeedGb ? item.other.lastInSpeedGb + 'G' : '0'} 平均值:${item && item.other && item.other.avgInSpeedGb ? item.other.avgInSpeedGb + 'G' : '0'} 最大值:${item && item.other && item.other.maxInSpeedGb ? item.other.maxInSpeedGb + 'G' : '0'}`; let inContent = `最后一个值:${item && item.other && item.other.lastInSpeedGb ? item.other.lastInSpeedGb + 'G' : '0G'} 平均值:${item && item.other && item.other.avgInSpeedGb ? item.other.avgInSpeedGb + 'G' : '0G'} 最大值:${item && item.other && item.other.maxInSpeedGb ? item.other.maxInSpeedGb + 'G' : '0G'}`;
let kdContent = `(${item && item['95value'] ? item['95value'] + 'mbit' : '0'} ${this.resourceType === 2 ? 'in+out' : 'out'})`; let kdContent = `(${item && item['95value'] ? item['95value'] + 'mbit' : '0mbit'} ${this.resourceType === 2 ? 'in+out' : 'out'})`;
newChartData.title = item && item.name; newChartData.title = item && item.name;
newChartData.yAxisName = item && item.other && item.other.recommendedUnit ? '单位' + item.other.recommendedUnit : ' '; newChartData.yAxisName = item && item.other && item.other.recommendedUnit ? '单位' + item.other.recommendedUnit : ' ';
newChartData['unitModel'] = item && item.other && item.other.recommendedUnit || ''; newChartData['unitModel'] = item && item.other && item.other.recommendedUnit || '';
@@ -448,8 +448,8 @@
} }
}); });
}, },
async chartDataEvent(valData, unit, resourceType) { async chartDataEvent(valData, unit, type) {
if (resourceType === 1) { if (type === 'day') {
await Promise.all([ await Promise.all([
this.$modal.loading(), this.$modal.loading(),
this.graDailyList({startTime: this.timeList[1][0], endTime: this.timeList[1][1]}, {unit: unit}), this.graDailyList({startTime: this.timeList[1][0], endTime: this.timeList[1][1]}, {unit: unit}),
+60 -9
View File
@@ -1,5 +1,5 @@
<template> <template>
<div :class="open ? '' : 'app-container'"> <div :class="open ? 'plr-20' : 'app-container'">
<el-collapse v-model="activeShowList" @change="collapseChange"> <el-collapse v-model="activeShowList" @change="collapseChange">
<template v-for="(val,key, index) of chartDataLit"> <template v-for="(val,key, index) of chartDataLit">
<el-collapse-item :title="`【${val && val.title || ''}】${val && val.jointName || ''}`" :name="val && val.title || ''"> <el-collapse-item :title="`【${val && val.title || ''}】${val && val.jointName || ''}`" :name="val && val.title || ''">
@@ -10,25 +10,37 @@
</div> </div>
<div v-else class="w100 h100" style="position: relative;"> <div v-else class="w100 h100" style="position: relative;">
<div style="position: absolute;">最新丢包率{{item.dataVal.lastYDataNum || 'null'}}</div> <div style="position: absolute;">最新丢包率{{item.dataVal.lastYDataNum || 'null'}}</div>
<EchartsLine class="w100 h100" :key="`chart-${val && val.title || ''}-${item && item.title || ''}-${index}`" :lineData="item && item.dataVal || {}" :dateDataTrans="item && item.dateDataTrans || {}" :dateShowType="item && item.dateShowType || 'datetimerange'" :title="item && item.title || '图表数据'" :chartData="(valData) => chartDataEvent(valData,val.title)"></EchartsLine> <EchartsLine class="w100 h100" :key="`chart-${val && val.title || ''}-${item && item.title || ''}-${index}`" :lineData="item && item.dataVal || {}" :dateDataTrans="item && item.dateDataTrans || {}" :dateShowType="item && item.dateShowType || 'datetimerange'" :title="item && item.title || '图表数据'" :chartData="(valData) => chartDataEvent(valData,val.title)" @dialogChartTabList="dialogChartTabList" @showChartTabList="showChartTabList"></EchartsLine>
</div> </div>
</div> </div>
</div> </div>
</el-collapse-item> </el-collapse-item>
</template> </template>
</el-collapse> </el-collapse>
<template v-if="open && roleList.length > 0">
<div style="font-size: 1.2rem;" class="pt5 pb5">{{tagTitle}}</div>
<TableList :columns="columns" :config="{colTopHiddenIcon: true}" :tableList="roleList"></TableList>
</template>
<div style="text-align: right;margin-top: 20px;margin-right: 20px;"> <div style="text-align: right;margin-top: 20px;margin-right: 20px;">
<el-button type="primary" @click="goBack">返回</el-button> <el-button type="primary" @click="goBack">返回</el-button>
</div> </div>
<!-- 弹窗 -->
<el-dialog :title="tagTitle" :visible.sync="tagOpen" width="900px" append-to-body style="padding-bottom: 20px;">
<TableList :columns="columns" :config="{colTopHiddenIcon: true}" :tableList="roleList"></TableList>
<div style="text-align: right;margin-right: 20px;margin-top: 20px;">
<el-button @click="tagOpen = false">返回</el-button>
</div>
</el-dialog>
</div> </div>
</template> </template>
<script> <script>
import {mtrCToClientIds, lossRateByMtr} from "@/api/disRevenue/resource" import {mtrCToClientIds, lossRateByMtr} from "@/api/disRevenue/resource"
import EchartsLine from "@/components/echartsList/line.vue"; import EchartsLine from "@/components/echartsList/line.vue";
import TableList from '@/components/table/index.vue';
export default { export default {
name: "lossRateChart", name: "lossRateChart",
components: {EchartsLine}, components: {EchartsLine, TableList},
props: { props: {
open: { open: {
type: String, type: String,
@@ -51,8 +63,9 @@
params[this.dialogRowData.clientId]['jointName'] = '丢包率'; params[this.dialogRowData.clientId]['jointName'] = '丢包率';
this.activeShowList.push(this.dialogRowData.clientId); this.activeShowList.push(this.dialogRowData.clientId);
this.chartDataLit = params; this.chartDataLit = params;
this.timeDefault();
this.$nextTick(() => { this.$nextTick(() => {
this.getEchartList(params); this.getEchartList(params, 'dialog');
}); });
} else if (val === 'type_false') { } else if (val === 'type_false') {
} }
@@ -85,10 +98,30 @@
paramsData: {}, paramsData: {},
currTimeList: {}, currTimeList: {},
defaultTimes: [], defaultTimes: [],
tagTitle: '',
tagOpen: false,
columns: {
hopNumber: { label: `路径`, minWidth: '50', visible: true},
avgLatency:{ label: `平均延迟(ms)`,minWidth: '100', visible: true},
lossPercent:{ label: `丢包率(%)`,minWidth: '80', visible: true},
ipAddress: { label: `地址`, minWidth: '120', visible: true },
country:{ label: `国家`,minWidth: '80', visible: true},
isp: { label: `运营商`, minWidth: '120', visible: true},
province: { label: ``, minWidth: '80', visible: true },
city: { label: ``, minWidth: '80', visible: true},
},
roleList: [],
} }
}, },
created() { created() {
this.paramsData = this.$route && this.$route.query; this.paramsData = this.$route && this.$route.query;
this.timeDefault();
if (!this.open) {
this.getLossRateChart();
}
},
methods: {
timeDefault(){
const now = new Date(); const now = new Date();
// 获取当前时间 // 获取当前时间
const year = now.getFullYear(); const year = now.getFullYear();
@@ -110,11 +143,7 @@
this.currTimeList = {startTime: startData, endTime: endData}; this.currTimeList = {startTime: startData, endTime: endData};
this.defaultTimes = [startData, endData]; this.defaultTimes = [startData, endData];
if (!this.open) {
this.getLossRateChart();
}
}, },
methods: {
getLossRateChart() { getLossRateChart() {
this.activeShowList = []; this.activeShowList = [];
mtrCToClientIds({mtrClientId: this.paramsData.mtrClientId}).then(res => { mtrCToClientIds({mtrClientId: this.paramsData.mtrClientId}).then(res => {
@@ -129,7 +158,7 @@
this.getEchartList(dataList); this.getEchartList(dataList);
}); });
}, },
getEchartList(resVal) { getEchartList(resVal, type) {
if (resVal && Object.keys(resVal).length > 0) { if (resVal && Object.keys(resVal).length > 0) {
Object.keys(resVal).forEach(item => { Object.keys(resVal).forEach(item => {
let chartList = JSON.parse(JSON.stringify(this.echartList))[0]; let chartList = JSON.parse(JSON.stringify(this.echartList))[0];
@@ -144,6 +173,7 @@
yDataList = res.data.yData.packetLossRate || []; yDataList = res.data.yData.packetLossRate || [];
} }
} }
chartList.dataVal.tooltipFormatter['defaultType'] = type ? type : null;
chartList.dataVal.lineXData = res.data && res.data.xData || []; chartList.dataVal.lineXData = res.data && res.data.xData || [];
chartList.dataVal.dataList[0].data = yDataList || []; chartList.dataVal.dataList[0].data = yDataList || [];
chartList.dataVal.lastYDataNum = chartList.dataVal.dataList[0].data.length > 0 ? chartList.dataVal.dataList[0].data[chartList.dataVal.dataList[0].data.length - 1].value : null; chartList.dataVal.lastYDataNum = chartList.dataVal.dataList[0].data.length > 0 ? chartList.dataVal.dataList[0].data[chartList.dataVal.dataList[0].data.length - 1].value : null;
@@ -168,6 +198,24 @@
this.getEchartList(params); this.getEchartList(params);
} }
}, },
// 点击时
dialogChartTabList(val) {
if (val) {
this.tagTitle = val.value;
this.tagOpen = true;
this.roleList = JSON.parse(val.tableVal.content);
}
},
// 鼠标移动时
showChartTabList(val) {
if(val && Object.keys(val).length > 0) {
this.tagTitle = val.value;
this.roleList = JSON.parse(val.tableVal.content);
} else {
this.tagTitle = '';
this.roleList = [];
}
},
goBack(){ goBack(){
if (this.open) { if (this.open) {
this.$emit("dialogResult", {open: false}); this.$emit("dialogResult", {open: false});
@@ -194,4 +242,7 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
::v-deep .el-dialog__wrapper {
z-index: 99999999!important;
}
</style> </style>