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;
//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);
});
}
// 确保使用 window 对象挂载
window.detailsVal = this.detailsVal;
},
beforeUnmount() {
// 组件销毁时清理全局函数
delete window.detailsVal;
},
methods: {
getList(title, dataXY) {
@@ -162,45 +168,52 @@
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;
// 构建提示框HTML内容
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 => {
if (dataXY.tooltipFormatter && dataXY.tooltipFormatter.customList) {
if (paramItem.data.content) {
let content = JSON.parse(paramItem.data.content);
content.forEach((contItem, index) => {
html+= `<tr>
<td class="tab_border">${contItem.hopNumber || ''}</td>
<td class="tab_border">${contItem.avgLatency || ''}</td>
<td class="tab_border">${contItem.lossPercent || ''}</td>
<td class="tab_border">${contItem.ipAddress || ''}</td>
<td class="tab_border">${contItem.country || ''}</td>
<td class="tab_border ellipsis" title="${contItem.isp}">${contItem.isp || ''}</td>
<td class="tab_border">${contItem.province || ''}</td>
<td class="tab_border">${contItem.city || ''}</td>
</tr>`;
});
html+= `<div>最新一跳平均延迟: ${content[content.length - 1].avgLatency}(ms)</div>
<div>最新一跳丢包率: ${content[content.length - 1].lossPercent}(%)</div>`;
// 如果不是弹窗这显示按钮,可点击查看弹窗信息
if (dataXY.tooltipFormatter && !dataXY.tooltipFormatter.defaultType) {
html+= `<button style="float: right;background: #409eff; border-color: #409eff;color:#fff;box-shadow:none;" onclick="window.detailsVal('${date}' ,'${paramItem.dataIndex}')">详情</button>`;
}
} else {
let valueparams = paramItem.value;
if (dataXY.tooltipFormatter && dataXY.tooltipFormatter.valueToop) {
let value = dataXY.tooltipFormatter.valueToop;
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;
}
},
@@ -301,13 +314,18 @@
},1000);
}
});
// 2. 关键:拦截默认的鼠标移出事件,不做任何操作(即不取消高亮)
// ​当鼠标在图形元素内部移动时持续触发
lineDataListIntance.on('mousemove', (params) => {
this.$emit('showChartTabList', {value: params.name, tableVal: params.data});
});
// 2. 关键:拦截默认的鼠标移入事件,不做任何操作(即不取消高亮)
lineDataListIntance.on('mouseover', (params) => {
// 这个函数体为空,或者只包含一些不涉及downplay的逻辑
// 这样就阻止了鼠标移出时高亮自动消失
});
// 2. 关键:拦截默认的鼠标移出事件,不做任何操作(即不取消高亮)
lineDataListIntance.on('mouseout', (params) => {
this.$emit('showChartTabList', {});
// 这个函数体为空,或者只包含一些不涉及downplay的逻辑
// 这样就阻止了鼠标移出时高亮自动消失
});
@@ -388,6 +406,10 @@
}
// let lineDataList = days && days.length > 0 ? days: this.lineData.lineXData;
// 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: {
type: Object,
default: {
default: () => ({
total: 0,
pageNum: 1,
pageSize: 10,
layout: 'total, sizes, prev, pager, next, jumper'
}
})
},
config: {
type: Object,
+10 -10
View File
@@ -40,10 +40,10 @@
<!-- </template>-->
<!-- </template>-->
<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 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>
</div>
</div>
@@ -300,9 +300,9 @@
let chartList = [];
res.data.forEach((item, index) => {
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 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 kdContent = `(${item && item['95value'] ? item['95value'] + 'mbit' : '0'} ${this.resourceType === 2 ? 'in+out' : 'out'})`;
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' : '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' : '0mbit'} ${this.resourceType === 2 ? 'in+out' : 'out'})`;
newChartData.title = item && item.name;
newChartData.yAxisName = item && item.other && item.other.recommendedUnit ? '单位' + item.other.recommendedUnit : ' ';
newChartData['unitModel'] = item && item.other && item.other.recommendedUnit || '';
@@ -342,9 +342,9 @@
res.data.forEach((item, index) => {
let newChartData = JSON.parse(JSON.stringify(this.echartData[this.resourceType === 2 ? 1 : 0]));
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 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 kdContent = `(${item && item['95value'] ? item['95value'] + 'mbit' : '0'} ${this.resourceType === 2 ? 'in+out' : 'out'})`;
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' : '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' : '0mbit'} ${this.resourceType === 2 ? 'in+out' : 'out'})`;
newChartData.title = item && item.name;
newChartData.yAxisName = item && item.other && item.other.recommendedUnit ? '单位' + item.other.recommendedUnit : ' ';
newChartData['unitModel'] = item && item.other && item.other.recommendedUnit || '';
@@ -448,8 +448,8 @@
}
});
},
async chartDataEvent(valData, unit, resourceType) {
if (resourceType === 1) {
async chartDataEvent(valData, unit, type) {
if (type === 'day') {
await Promise.all([
this.$modal.loading(),
this.graDailyList({startTime: this.timeList[1][0], endTime: this.timeList[1][1]}, {unit: unit}),
+77 -26
View File
@@ -1,5 +1,5 @@
<template>
<div :class="open ? '' : 'app-container'">
<div :class="open ? 'plr-20' : 'app-container'">
<el-collapse v-model="activeShowList" @change="collapseChange">
<template v-for="(val,key, index) of chartDataLit">
<el-collapse-item :title="`【${val && val.title || ''}】${val && val.jointName || ''}`" :name="val && val.title || ''">
@@ -10,25 +10,37 @@
</div>
<div v-else class="w100 h100" style="position: relative;">
<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>
</el-collapse-item>
</template>
</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;">
<el-button type="primary" @click="goBack">返回</el-button>
</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>
</template>
<script>
import {mtrCToClientIds, lossRateByMtr} from "@/api/disRevenue/resource"
import EchartsLine from "@/components/echartsList/line.vue";
import TableList from '@/components/table/index.vue';
export default {
name: "lossRateChart",
components: {EchartsLine},
components: {EchartsLine, TableList},
props: {
open: {
type: String,
@@ -51,8 +63,9 @@
params[this.dialogRowData.clientId]['jointName'] = '丢包率';
this.activeShowList.push(this.dialogRowData.clientId);
this.chartDataLit = params;
this.timeDefault();
this.$nextTick(() => {
this.getEchartList(params);
this.getEchartList(params, 'dialog');
});
} else if (val === 'type_false') {
}
@@ -85,36 +98,52 @@
paramsData: {},
currTimeList: {},
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() {
this.paramsData = this.$route && this.$route.query;
const now = new Date();
// 获取当前时间
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
// 正确计算前一天(处理跨月情况)
const prevDate = new Date(now);
prevDate.setDate(now.getDate() - 1);
const prevYear = prevDate.getFullYear();
const prevMonth = String(prevDate.getMonth() + 1).padStart(2, '0');
const prevDay = String(prevDate.getDate()).padStart(2, '0');
// 构建时间字符串
const timeString = `${hours}:${minutes}:${seconds}`;
const startData = `${prevYear}-${prevMonth}-${prevDay} ${timeString}`;
const endData = `${year}-${month}-${day} ${timeString}`;
this.currTimeList = {startTime: startData, endTime: endData};
this.defaultTimes = [startData, endData];
this.timeDefault();
if (!this.open) {
this.getLossRateChart();
}
},
methods: {
timeDefault(){
const now = new Date();
// 获取当前时间
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
// 正确计算前一天(处理跨月情况)
const prevDate = new Date(now);
prevDate.setDate(now.getDate() - 1);
const prevYear = prevDate.getFullYear();
const prevMonth = String(prevDate.getMonth() + 1).padStart(2, '0');
const prevDay = String(prevDate.getDate()).padStart(2, '0');
// 构建时间字符串
const timeString = `${hours}:${minutes}:${seconds}`;
const startData = `${prevYear}-${prevMonth}-${prevDay} ${timeString}`;
const endData = `${year}-${month}-${day} ${timeString}`;
this.currTimeList = {startTime: startData, endTime: endData};
this.defaultTimes = [startData, endData];
},
getLossRateChart() {
this.activeShowList = [];
mtrCToClientIds({mtrClientId: this.paramsData.mtrClientId}).then(res => {
@@ -129,7 +158,7 @@
this.getEchartList(dataList);
});
},
getEchartList(resVal) {
getEchartList(resVal, type) {
if (resVal && Object.keys(resVal).length > 0) {
Object.keys(resVal).forEach(item => {
let chartList = JSON.parse(JSON.stringify(this.echartList))[0];
@@ -144,6 +173,7 @@
yDataList = res.data.yData.packetLossRate || [];
}
}
chartList.dataVal.tooltipFormatter['defaultType'] = type ? type : null;
chartList.dataVal.lineXData = res.data && res.data.xData || [];
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;
@@ -168,6 +198,24 @@
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(){
if (this.open) {
this.$emit("dialogResult", {open: false});
@@ -194,4 +242,7 @@
justify-content: center;
align-items: center;
}
::v-deep .el-dialog__wrapper {
z-index: 99999999!important;
}
</style>