Files
profitManage1.2/src/components/echartsList/line.vue
T
康冉冉 9ac8cae26f MTRd丢包趋势图
1、MTRAgent丢包趋势图自定义移入提示框的内容
2、MTR丢包趋势图添加点击提示框按钮,弹窗显示列表详情
3、服务器管理添加MTR探测趋势图,根据点的变化展示不同的table列表信息
2025-12-02 18:41:22 +08:00

451 lines
19 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div>
<DateTimeSelect ref="changeDateTime" v-if="!(lineData && lineData.hiddenTime)" :sideIcon="sideIcon" :dateDataTrans="dateDataTrans" :dateShowType="dateShowType" @dataChange="dataChangeValue"></DateTimeSelect>
<!-- <div :id="`lineChart` + num" style="width:100%;height: 100%;" />-->
<!-- 新增包裹图表和自定义下拉框的容器设置为相对定位 -->
<div class="chart-wrapper">
<!-- 自定义的下拉选择框 -->
<template v-if="this.lineData && this.lineData.unitSelList && this.lineData.unitSelList.length > 0">
<select :class="'yaxis-selector yaxis-selector' + num" v-model="selectedYAxisName">
<option v-for="item of this.lineData.unitSelList" :value="item.value">{{item.label}}</option>
</select>
</template>
<!-- 图表容器id 用于初始化 ECharts -->
<div :id="`lineChart` + num" class="chart-container"></div>
</div>
</div>
</template>
<script>
import * as echarts from 'echarts'
import DateTimeSelect from './dateTimeSelect.vue'
export default {
name: 'echartsLine',
components: {DateTimeSelect},
props: {
lineData: {
type: Object,
default: () => {}
},
title: {
type: String,
default: null
},
dateShowType: {
type: String,
default: null
},
dateDataTrans: {
type: Object,
default: () => ({})
},
chartData: {
type: Function,
default: () => {
}
},
sideIcon: {
type: Object,
default: () => {}
}
},
watch: {
lineData: {
handler(val) {
this.$nextTick(() => {
this.selectedYAxisName = this.lineData && this.lineData.unitModel || '';
this.getList(this.title, this.lineData);
});
},
deep: true,
immediate: true
},
// 新增:监听下拉框选择的变化
selectedYAxisName(newVal) {
if (newVal != (this.lineData && this.lineData.unitModel)) {
let times = this.lineData && this.lineData.hiddenTime ? [] : this.$refs.changeDateTime.dateRange;
this.chartData({timeArr: times}, newVal);
}
}
},
data() {
return {
lineDataList: '',
num: '',
selectedYAxisName: '' // 新增:用于绑定下拉框选中的值
}
},
created() {
this.num = Math.floor(Math.random() * 9000);
},
mounted() {
if (this.lineData && this.lineData.hiddenTime) {
this.$nextTick(() => {
this.getList(this.title, this.lineData);
});
}
// 确保使用 window 对象挂载
window.detailsVal = this.detailsVal;
},
beforeUnmount() {
// 组件销毁时清理全局函数
delete window.detailsVal;
},
methods: {
getList(title, dataXY) {
let that = this;
const lineDataListIntance = echarts.init(document.getElementById('lineChart' + that.num));
let titleList = [{text: title, x: '50%', y: '3%', textAlign: 'center',textStyle: {fontSize: 16}}];
if (dataXY && dataXY.titleSubtext) {
titleList.push(dataXY.titleSubtext);
} else if (dataXY && dataXY.titleVal) {
titleList[0] = Object.assign({},titleList[0], dataXY.titleVal);
}
let legendData = dataXY && dataXY.legend ? dataXY.legend : {};
if (dataXY && dataXY.legend && dataXY.legend.formatter) {
let formatData = {
formatter: function(params) {
const customSuffix = dataXY && dataXY.content && dataXY.content[params] || ' ';
return `${params} ${customSuffix}`;
},
};
legendData = Object.assign({}, legendData, formatData);
}
let series = [];
if (dataXY && dataXY.dataList && dataXY.dataList.length > 0) {
dataXY.dataList.forEach(item => {
let str = {
name: item && item.name || '',
type: 'line',
// showSymbol: false,
symbol: item && item.symbol || 'circle',
data: item && item.data || []
};
if (!(item && item.areaStyleNone)) {
str['areaStyle'] = {
opacity: 0.6
};
}
series.push(str);
});
} else {
series = [{
data: dataXY && dataXY.data || [],
type: 'line',
smooth: true,
// areaStyle: { // 阴影
// color: 'rgba(140, 158, 217, 1)'
// }
}];
}
let option = {
title: titleList,
legend: legendData,
tooltip: {
trigger: 'axis',
confine: true, // 将tooltip限制在图表的区域内,防止超出屏幕[1,7](@ref)
enterable: true, // 关键配置:允许鼠标移入tooltip内部[1,2,7](@ref)
hideDelay: 300, // 建议设置隐藏延迟,方便鼠标有足够时间移动到tooltip上[1](@ref)
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
},
// 自定义位置 - 跟随鼠标
position: !dataXY.tooltipFormatter ? null : function (point, params, dom, rect, size) {
// 确保提示框在视图范围内
let x = point[0];
let y = point[1];
// 如果太靠右,向左偏移
if (x > size.viewSize[0] - size.contentSize[0]) {
x = x - size.contentSize[0] - 20;
}
// 如果太靠下,向上偏移
if (y > size.viewSize[1] - size.contentSize[1]) {
y = y - size.contentSize[1] - 20;
}
return [x - 10, y + 10];
},
formatter: !dataXY.tooltipFormatter ? null : function(params) { // 主要应对与MTRAgent丢包趋势图模块的功能
// 获取当前悬停的日期
const date = params[0].name;
// 构建提示框HTML内容
let html = `<div>${date}</div>`;
// 遍历所有系列
params.forEach(paramItem => {
if (dataXY.tooltipFormatter && dataXY.tooltipFormatter.customList) {
if (paramItem.data.content) {
let content = JSON.parse(paramItem.data.content);
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>`;
}
});
return html;
}
},
grid: {
top: dataXY && dataXY.gridTop || '80px',
left: dataXY && dataXY.gridLeft || '2%',
right: '4%',
bottom: dataXY && dataXY.gridBotm || '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
formatter: function(value) {
if (value) {
// 每4个字符换一行(根据实际文本长度调整)
const maxLength = 11;
let result = '';
let count = 0;
for (let i = 0; i < value.length; i++) {
count++;
result += value[i];
if (count % maxLength === 0 && i !== value.length - 1) {
result += '\n'; // 换行
}
}
return result;
}
},
// lineHeight: 10, // 行高(确保换行后不重叠)
},
data: dataXY && dataXY.lineXData || []
},
yAxis: {
type: 'value',
name: dataXY?.yAxisName || ' ',
nameTextStyle: dataXY && dataXY.unitSelList && dataXY.unitSelList.length > 0 ? {color: 'transparent'} : {}, // 将名称文本颜色设为透明,使其不可见
min: 0, // 自定义最小刻度(根据业务需求调整,如 0)
max: dataXY && dataXY.data && dataXY.data.length > 0 ? null : dataXY && dataXY.dataList && dataXY.dataList[0] && dataXY.dataList[0].data.length > 0 ? null : 100,
splitLine: {
show: true,
},
},
// 配置高亮状态下的样式
emphasis: dataXY && dataXY.highlight ? {
itemStyle: {
borderWidth: 2, // 边框加粗
shadowBlur: 5, // 添加阴影光晕
},
label: {
show: false, // 高亮时显示标签
}
} : {},
series: series
};
lineDataListIntance.setOption(option);
if (dataXY && dataXY.highlight) {
// 然后高亮当前点击的数据点
lineDataListIntance.dispatchAction({
type: 'highlight', // 高亮类型
seriesIndex: 0, // 系列索引,从点击事件参数中获取
dataIndex: 0 // 数据索引,从点击事件参数中获取
});
}
if (dataXY && dataXY.unitSelList && dataXY.unitSelList.length > 0) {
// 初始化图表后,调用方法定位下拉框
this.positionYAxisSelector(lineDataListIntance);
}
window.addEventListener("resize", () => {
lineDataListIntance.resize();
if (dataXY && dataXY.unitSelList && dataXY.unitSelList.length > 0) {
this.positionYAxisSelector(lineDataListIntance);
}
});
lineDataListIntance.off('click');
lineDataListIntance.on("click", function(params) {
that.$emit("chartClickModel", {params: params});
if (dataXY && dataXY.highlight) {
// 首先取消之前可能存在的高亮状态
lineDataListIntance.dispatchAction({
type: 'downplay' // 取消所有数据点的高亮
});
// 然后高亮当前点击的数据点
setTimeout(() => {
lineDataListIntance.dispatchAction({
type: 'highlight', // 高亮类型
seriesIndex: params.seriesIndex, // 系列索引,从点击事件参数中获取
dataIndex: params.dataIndex // 数据索引,从点击事件参数中获取
});
},1000);
}
});
// ​当鼠标在图形元素内部移动时持续触发
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的逻辑
// 这样就阻止了鼠标移出时高亮自动消失
});
},
positionYAxisSelector(chartInstance) {
const selector = document.querySelector('.yaxis-selector' + this.num);
if (!selector || !chartInstance) return;
const chartDom = chartInstance.getDom();
const chartRect = chartDom.getBoundingClientRect();
// 获取图表配置项中的网格设置
const option = chartInstance.getOption();
const grid = option.grid && option.grid[0] ? option.grid[0] : {};
// 使用网格的 left 和 top 作为基准点
const gridLeft = this.parseGridValue(grid.left || '2%', chartRect.width);
const gridTop = this.parseGridValue(grid.top || '80px', chartRect.height);
// const labelAreaLeft = gridLeft - 100; // 向左偏移
// const labelAreaTop = gridTop + (chartRect.height / 2) - 10; // 垂直居中
// selector.style.left = `${chartRect.left + window.pageXOffset + labelAreaLeft}px`;
// selector.style.top = `${chartRect.top + window.pageYOffset + labelAreaTop}px`;
const labelAreaLeft = gridLeft - 5; // 向左偏移
const labelAreaTop = gridTop - 35; // 垂直居中
selector.style.left = `${labelAreaLeft}px`;
selector.style.top = `${labelAreaTop}px`;
},
// 辅助方法:解析网格值(百分比、像素等)
parseGridValue(value, containerSize) {
if (typeof value === 'number') return value;
if (typeof value === 'string') {
if (value.includes('%')) {
return (parseFloat(value) / 100) * containerSize;
} else if (value.includes('px')) {
return parseFloat(value);
}
}
return 0;
},
// updateChartOnYAxisChange(selectedValue) {
// // 根据选择的值,动态更新图表配置
// // 例如,可以更新 Y 轴的单位名称、最大值、最小值,甚至切换整个 series 的数据[2](@ref)
// const chartDom = document.getElementById('lineChart' + this.num);
// const chartInstance = echarts.getInstanceByDom(chartDom);
//
// if (chartInstance) {
// // 示例:更新 Y 轴的名称显示(虽然它是隐藏的,但可能用于 tooltip 等)
// const option = chartInstance.getOption();
// option.yAxis[0].name = this.getYAxisNameByValue(selectedValue); // 假设的方法,根据值返回要显示的名称文本
//
// // 示例:可以在这里根据 selectedValue 重新设置 series 的 data[5](@ref)
// // option.series[0].data = ... 从某个数据源获取新数据
//
// chartInstance.setOption(option);
// }
// },
// getYAxisNameByValue(value) {
// // 一个简单映射,将下拉框的值转换为实际要显示的轴名称
// const map = {
// 'value1': '温度 (°C)',
// 'value2': '湿度 (%)',
// 'value3': '压力 (Pa)'
// };
// return map[value] || '';
// },
dataChangeValue(val, timeArr, iconVal) {
// 调用父级页面传过来的事件,在父级中处理该方法的逻辑--
if (timeArr) {
this.chartData(timeArr);
}
if (iconVal) {
this.chartData({iconVal: iconVal})
}
// 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]});
}
}
}
</script>
<style scoped>
/* 新增样式 */
.chart-wrapper {
position: relative; /* 为绝对定位的下拉框提供参考 */
width: 100%;
height: 100%;
}
.chart-container {
width: 100%;
height: 100%;
}
.yaxis-selector {
position: absolute; /* 绝对定位,相对于 .chart-wrapper */
z-index: 10; /* 确保下拉框显示在图表上方 */
/* 初始位置,后续由 js 方法精细调整 */
top: 0;
left: 0;
background: white;
border: 1px solid #ccc;
border-radius: 3px;
padding: 2px 5px;
font-size: 12px;
}
::v-deep .tab_border {
padding: 2px 10px;
border-bottom: 1px solid #ddd;
}
::v-deep .ellipsis {
max-width: 300px;
white-space: nowrap; /* 防止文本换行 */
overflow: hidden; /* 隐藏溢出的内容 */
text-overflow: ellipsis; /* 显示省略号来代表被裁剪的文本 */
}
</style>