fix:增加并发请求,优化性能和逻辑
This commit is contained in:
@@ -1,9 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted, onUnmounted, nextTick, computed } from 'vue';
|
import { ref, onMounted, onUnmounted, nextTick, computed } from 'vue';
|
||||||
import * as echarts from 'echarts/core';
|
import * as echarts from 'echarts/core';
|
||||||
import { LegendComponent } from 'echarts/components';
|
import { GridComponent, TooltipComponent, TitleComponent,LegendComponent } from 'echarts/components';
|
||||||
import { LineChart, BarChart } from 'echarts/charts';
|
import { LineChart, BarChart } from 'echarts/charts';
|
||||||
import { GridComponent, TooltipComponent, TitleComponent, } from 'echarts/components';
|
|
||||||
import { CanvasRenderer } from 'echarts/renderers';
|
import { CanvasRenderer } from 'echarts/renderers';
|
||||||
import { getKPITitle, listKPIData } from '@/api/perfManage/goldTarget';
|
import { getKPITitle, listKPIData } from '@/api/perfManage/goldTarget';
|
||||||
import useI18n from '@/hooks/useI18n';
|
import useI18n from '@/hooks/useI18n';
|
||||||
@@ -15,12 +14,27 @@ import { OptionsType, WS } from '@/plugins/ws-websocket';
|
|||||||
import { generateColorRGBA } from '@/utils/generate-utils';
|
import { generateColorRGBA } from '@/utils/generate-utils';
|
||||||
import { BarChartOutlined, LineChartOutlined, UnorderedListOutlined, DownOutlined, MoreOutlined } from '@ant-design/icons-vue';
|
import { BarChartOutlined, LineChartOutlined, UnorderedListOutlined, DownOutlined, MoreOutlined } from '@ant-design/icons-vue';
|
||||||
|
|
||||||
|
//定义KPI接口
|
||||||
|
interface KPIBase{
|
||||||
|
kpiId: string;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface KPIColumn extends KPIBase{
|
||||||
|
dataIndex: string;
|
||||||
|
key: string;
|
||||||
|
neType: string;
|
||||||
|
}
|
||||||
// 在这里定义 ChartDataItem 接口
|
// 在这里定义 ChartDataItem 接口
|
||||||
interface ChartDataItem {
|
interface ChartDataItem {
|
||||||
date: string; // 将存储完整的时间字符串,包含时分秒
|
date: string; // 将存储完整的时间字符串,包含时分秒
|
||||||
[kpiId: string]: string | number; // 动态指标
|
[kpiId: string]: string | number; // 动态指标
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//网元类型定义
|
||||||
|
const ALL_NE_TYPES = ['AMF','SMF','UPF','MME','IMS','SMSC'] as const;
|
||||||
|
type NeType= typeof ALL_NE_TYPES[number];
|
||||||
|
|
||||||
echarts.use([
|
echarts.use([
|
||||||
LineChart,
|
LineChart,
|
||||||
BarChart,
|
BarChart,
|
||||||
@@ -71,10 +85,6 @@ const toggleRealtime = () => {
|
|||||||
fnRealTimeSwitch(isRealtime.value);
|
fnRealTimeSwitch(isRealtime.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 定义所有网元类型
|
|
||||||
const ALL_NE_TYPES = ['AMF', 'SMF', 'UPF', 'MME', 'IMS', 'SMSC'] as const;
|
|
||||||
type NeType = (typeof ALL_NE_TYPES)[number];
|
|
||||||
|
|
||||||
// 定义要筛选的指标 ID,按网元类型组织
|
// 定义要筛选的指标 ID,按网元类型组织
|
||||||
const TARGET_KPI_IDS: Record<NeType, string[]> = {
|
const TARGET_KPI_IDS: Record<NeType, string[]> = {
|
||||||
AMF: ['AMF.02', 'AMF.03', 'AMF.A.07', 'AMF.A.08'],
|
AMF: ['AMF.02', 'AMF.03', 'AMF.A.07', 'AMF.A.08'],
|
||||||
@@ -113,63 +123,65 @@ const wsError = () => {
|
|||||||
message.error(t('common.websocketError'));
|
message.error(t('common.websocketError'));
|
||||||
};
|
};
|
||||||
|
|
||||||
// 接收数据后回调
|
const handleWebSocketMessage = (kpiEvent:any)=>{
|
||||||
const wsMessage = (res: Record<string, any>) => {
|
if(!kpiEvent)return;
|
||||||
const { code, data } = res;
|
|
||||||
if (code === RESULT_CODE_ERROR) {
|
|
||||||
console.warn(res.msg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data?.groupId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const kpiEvent = data.data;
|
|
||||||
if (!kpiEvent) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 构造新的数据点
|
// 构造新的数据点
|
||||||
const newData: ChartDataItem = {
|
const newData: ChartDataItem = {
|
||||||
date: kpiEvent.timeGroup
|
date: kpiEvent.timeGroup?.toString() || Date.now().toString(),
|
||||||
? kpiEvent.timeGroup.toString()
|
|
||||||
: Date.now().toString(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 只添加已选中的指标的数据
|
// 只添加已选中的指标的数据
|
||||||
selectedKPIs.value.forEach(kpiId => {
|
selectedKPIs.value.forEach(kpiId => {
|
||||||
if (kpiEvent[kpiId] !== undefined) {
|
newData[kpiId] = Number(kpiEvent[kpiId])||0;
|
||||||
newData[kpiId] = Number(kpiEvent[kpiId]);
|
|
||||||
} else {
|
|
||||||
newData[kpiId] = 0;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 更新数据
|
// 更新数据
|
||||||
updateChartData(newData);
|
updateChartData(newData);
|
||||||
};
|
};
|
||||||
|
//成功回调
|
||||||
|
const wsMessage = (res:Record<string,any>)=>{
|
||||||
|
const{code,data}=res;
|
||||||
|
if(code===RESULT_CODE_ERROR||!data?.gropId)return;
|
||||||
|
handleWebSocketMessage(data.data);
|
||||||
|
};
|
||||||
|
// 添加数据处理函数
|
||||||
|
const processChartData = (rawData: any[]) => {
|
||||||
|
const groupedData = new Map<string, any>();//数据按时间分组
|
||||||
|
|
||||||
|
rawData.forEach(item => {//合并相同时间点的数据
|
||||||
|
const timeKey = item.timeGroup;
|
||||||
|
if (!groupedData.has(timeKey)) {//按时间排序
|
||||||
|
groupedData.set(timeKey, { timeGroup: timeKey });
|
||||||
|
}
|
||||||
|
Object.assign(groupedData.get(timeKey), item);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(groupedData.values())
|
||||||
|
.sort((a, b) => Number(a.timeGroup) - Number(b.timeGroup))
|
||||||
|
.map(item => {//转换成图表需要的格式
|
||||||
|
const dataItem: ChartDataItem = { date: item.timeGroup.toString() };
|
||||||
|
selectedKPIs.value.forEach(kpiId => {
|
||||||
|
dataItem[kpiId] = Number(item[kpiId]) || 0;
|
||||||
|
});
|
||||||
|
return dataItem;
|
||||||
|
});
|
||||||
|
};
|
||||||
// 获取图表数据方法
|
// 获取图表数据方法
|
||||||
const fetchChartData = async () => {
|
const fetchChartData = async () => {
|
||||||
if(kpiColumns.value.length===0){
|
if(kpiColumns.value.length===0){
|
||||||
console.warn('No KPI columns available');
|
|
||||||
updateChart();
|
updateChart();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const[startTime,endTime]=dateRange.value;
|
const[startTime,endTime]=dateRange.value;
|
||||||
|
|
||||||
if (!startTime || !endTime) {
|
if (!startTime || !endTime) {
|
||||||
console.warn('Invalid date range:', dateRange.value);
|
console.warn('Invalid date range:', dateRange.value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const allData: any[] = [];
|
// 创建并行请求数组
|
||||||
|
const requests = ALL_NE_TYPES.map(async (neType) => {
|
||||||
// 使用 ALL_NE_TYPES 遍历网元类型
|
|
||||||
for (const neType of ALL_NE_TYPES) {
|
|
||||||
const params = {
|
const params = {
|
||||||
neType,
|
neType,
|
||||||
neId: '001',
|
neId: '001',
|
||||||
@@ -181,36 +193,23 @@ const fetchChartData = async () => {
|
|||||||
kpiIds: TARGET_KPI_IDS[neType].join(','),
|
kpiIds: TARGET_KPI_IDS[neType].join(','),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
const res = await listKPIData(params);
|
const res = await listKPIData(params);
|
||||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||||
allData.push(...res.data);
|
return res.data;
|
||||||
}
|
}
|
||||||
|
return [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to fetch data for ${neType}:`, error);
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按时间分组合数据
|
|
||||||
const groupedData = new Map<string, any>();
|
|
||||||
allData.forEach(item => {
|
|
||||||
const timeKey = item.timeGroup;
|
|
||||||
if (!groupedData.has(timeKey)) {
|
|
||||||
groupedData.set(timeKey, { timeGroup: timeKey });
|
|
||||||
}
|
|
||||||
const existingData = groupedData.get(timeKey);
|
|
||||||
Object.assign(existingData, item);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 直接将处理后的数据赋值给 chartData.value
|
// 并行执行所有请求
|
||||||
chartData.value = Array.from(groupedData.values())
|
const results = await Promise.all(requests);
|
||||||
.sort((a, b) => Number(a.timeGroup) - Number(b.timeGroup))
|
const allData = results.flat();
|
||||||
.map(item => {
|
|
||||||
const dataItem: ChartDataItem = {
|
|
||||||
date: item.timeGroup.toString(),
|
|
||||||
};
|
|
||||||
kpiColumns.value.forEach(kpi => {
|
|
||||||
dataItem[kpi.kpiId] = Number(item[kpi.kpiId]) || 0;
|
|
||||||
});
|
|
||||||
return dataItem;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
chartData.value = processChartData(allData);
|
||||||
updateChart();
|
updateChart();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch chart data:', error);
|
console.error('Failed to fetch chart data:', error);
|
||||||
@@ -218,47 +217,49 @@ const fetchChartData = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 添加一个 Map 来存储每个指标的临时固定颜色
|
// 存储每个指标的临时固定颜色
|
||||||
const kpiColors = new Map<string, string>();
|
const kpiColors = new Map<string, string>();
|
||||||
|
|
||||||
// 定义图表类型的响应式变量
|
// 定义图表类型的响应式变量
|
||||||
const chartType = ref<'line' | 'bar'>('line');
|
const chartType = ref<'line' | 'bar'>('line');
|
||||||
|
|
||||||
// 添加切换图表类型的方法
|
// 切换图表类型的方法
|
||||||
const toggleChartType = () => {
|
const toggleChartType = () => {
|
||||||
chartType.value = chartType.value === 'line' ? 'bar' : 'line';
|
chartType.value = chartType.value === 'line' ? 'bar' : 'line';
|
||||||
updateChart();
|
updateChart();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 更新图表
|
// 更新图表类型
|
||||||
|
const getCommonSeriesConfig=(isLine:boolean)=>({
|
||||||
|
symbol: isLine ? 'circle' : undefined,
|
||||||
|
symbolSize: isLine ? 6 : undefined,
|
||||||
|
showSymbol: isLine,
|
||||||
|
barWidth: !isLine ? 20 : undefined,
|
||||||
|
barGap: !isLine ? '30%' : undefined,
|
||||||
|
animation: !isLine,
|
||||||
|
animationDuration: 300,
|
||||||
|
animationEasing: 'cubicOut',
|
||||||
|
});
|
||||||
const updateChart = () => {
|
const updateChart = () => {
|
||||||
if (!chart || !kpiColumns.value.length) return; //首先检查图表实例和指标是否存在
|
if (!chart || !kpiColumns.value.length) return; //首先检查图表实例和指标是否存在
|
||||||
//过滤出已选择的指标列
|
|
||||||
const filteredColumns = kpiColumns.value.filter(col =>
|
const isLine = chartType.value==='line';
|
||||||
selectedKPIs.value.includes(col.kpiId)
|
const commonConfig = getCommonSeriesConfig(isLine);
|
||||||
);
|
|
||||||
const legendData = filteredColumns.map(item => item.title); //创建图例数据数组,包含所有选中的指标的标题
|
const series = selectedKPIs.value.map(kpiId => {
|
||||||
//为每个选中的指标创建一个系列配置
|
const kpi = kpiColumns.value.find(col=>col.kpiId ===kpiId);
|
||||||
const series = filteredColumns.map(item => {
|
if (!kpi) return null;
|
||||||
const color = kpiColors.get(item.kpiId) || generateColorRGBA();
|
const color = kpiColors.get(kpiId)||generateColorRGBA();
|
||||||
if (!kpiColors.has(item.kpiId)) {
|
kpiColors.set(kpiId, color);
|
||||||
kpiColors.set(item.kpiId, color); //保持指标颜色的临时一致性
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: item.title,
|
name: kpi.title,
|
||||||
type: chartType.value, // 使用当前选择的图表类型
|
type: chartType.value, // 使用当前选择的图表类型
|
||||||
data:
|
data:chartData.value.map(item=>item[kpiId]||0),
|
||||||
chartData.value.length > 0
|
|
||||||
? chartData.value.map(dataItem => dataItem[item.kpiId] || 0)
|
|
||||||
: [0],
|
|
||||||
smooth: chartType.value === 'line', // 只在折线图时使用平滑
|
|
||||||
symbol: chartType.value === 'line' ? 'circle' : undefined, // 只在折线图时显示标记
|
|
||||||
symbolSize: chartType.value === 'line' ? 6 : undefined,
|
|
||||||
showSymbol: chartType.value === 'line',
|
|
||||||
itemStyle: { color },
|
itemStyle: { color },
|
||||||
|
...commonConfig,
|
||||||
};
|
};
|
||||||
});
|
}).filter(Boolean);
|
||||||
//图表配置对象
|
//图表配置对象
|
||||||
const option = {
|
const option = {
|
||||||
title: {
|
title: {
|
||||||
@@ -267,20 +268,26 @@ const updateChart = () => {
|
|||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
position: function (pt: any) {
|
formatter: function (params: any[]) {
|
||||||
return [pt[0], '10%'];
|
const time = params[0].axisValue;
|
||||||
},
|
let result = `${time}<br/>`;
|
||||||
|
params.forEach(param => {
|
||||||
|
const value = param.value === 0 ? '0' : param.value;
|
||||||
|
result += `${param.marker}${param.seriesName}: ${value}<br/>`;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
//图例配置
|
//图例配置
|
||||||
data: legendData,
|
data: selectedKPIs.value.map(kpiId=>kpiColumns.value.find(col=>col.kpiId===kpiId)?.title),
|
||||||
type: 'scroll',
|
type: 'scroll',
|
||||||
orient: 'horizontal',
|
orient: 'horizontal',
|
||||||
top: 25,
|
top: 25,
|
||||||
textStyle: {
|
textStyle: {
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
},
|
},
|
||||||
selected: Object.fromEntries(legendData.map(name => [name, true])),
|
selected:Object.fromEntries(selectedKPIs.value.map(kpiId=>[kpiId,true])),
|
||||||
show: true,
|
show: true,
|
||||||
left: 'center',
|
left: 'center',
|
||||||
width: '80%',
|
width: '80%',
|
||||||
@@ -296,23 +303,26 @@ const updateChart = () => {
|
|||||||
containLabel: true,
|
containLabel: true,
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
//x轴配置
|
// 指定x轴类型为类目轴,适用于离散的类目数据
|
||||||
type: 'category',
|
type: 'category',
|
||||||
boundaryGap: false,
|
// boundaryGap 控制坐标轴两边留白
|
||||||
data:
|
// 当为折线图时(isLine为true)时不留白,柱状图时留白
|
||||||
chartData.value.length > 0
|
// 这样可以让折线图从原点开始,柱状图有合适的间距
|
||||||
? chartData.value.map(item => {
|
boundaryGap: isLine,
|
||||||
// 将时间戳转换为包含时分秒的格式
|
// 设置x轴的数据
|
||||||
return dayjs(Number(item.date)).format('YYYY-MM-DD HH:mm:ss');
|
// 将时间戳转换为格式化的时间字符串
|
||||||
})
|
data:chartData.value.map(item=>
|
||||||
: [''],
|
dayjs(Number(item.date)).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
),
|
||||||
|
// 设置坐标轴刻度标签的样式
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
formatter: (value: string) => {
|
// 格式化函数,这里直接返回原值
|
||||||
// 自定义 x 轴标签的显示格式
|
formatter: (value: string) => value,
|
||||||
return dayjs(value).format('YYYY-MM-DD HH:mm:ss');
|
// 刻度标签旋转角度,0表示不旋转
|
||||||
},
|
|
||||||
rotate: 0,
|
rotate: 0,
|
||||||
interval: 'auto', // 自动计算显示<E698BE><E7A4BA>隔
|
// 自动计算标签显示的间隔,防止标签重叠
|
||||||
|
interval: 'auto', // 自动计算显示间隔
|
||||||
|
// 刻度标签对齐方式,右对齐
|
||||||
align: 'right',
|
align: 'right',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -329,10 +339,12 @@ const updateChart = () => {
|
|||||||
},
|
},
|
||||||
series: series, //配置数据
|
series: series, //配置数据
|
||||||
};
|
};
|
||||||
|
if(chart) {
|
||||||
chart.setOption(option); //使用新的配置更新图表
|
requestAnimationFrame(() => {
|
||||||
chart.resize(); //调整图表大小以适应容器
|
chart!.setOption(option, true); //使用新的配置更新图表
|
||||||
|
chart!.resize(); //调整图表大小以适应容器
|
||||||
|
});
|
||||||
|
}
|
||||||
// 如果已经有 observer,先断开连接
|
// 如果已经有 observer,先断开连接
|
||||||
if (observer) {
|
if (observer) {
|
||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
@@ -351,6 +363,8 @@ const updateChart = () => {
|
|||||||
observer.observe(container);
|
observer.observe(container);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
//chart可能为null判断
|
||||||
|
|
||||||
|
|
||||||
//钩子函数
|
//钩子函数
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@@ -380,15 +394,6 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 定义指列类型
|
|
||||||
interface KPIColumn {
|
|
||||||
title: string;
|
|
||||||
dataIndex: string;
|
|
||||||
key: string;
|
|
||||||
kpiId: string;
|
|
||||||
neType: string; // 添加网元类型字段
|
|
||||||
}
|
|
||||||
|
|
||||||
// 存储指标列信
|
// 存储指标列信
|
||||||
const kpiColumns = ref<KPIColumn[]>([]);
|
const kpiColumns = ref<KPIColumn[]>([]);
|
||||||
// 添加选中指标的的状态
|
// 添加选中指标的的状态
|
||||||
@@ -587,7 +592,6 @@ const secondaryKPIs = computed(() => {
|
|||||||
ALL_NE_TYPES.forEach(neType => {
|
ALL_NE_TYPES.forEach(neType => {
|
||||||
// 获取当前网元类型的主要指标 ID
|
// 获取当前网元类型的主要指标 ID
|
||||||
const primaryIds = TARGET_KPI_IDS[neType];
|
const primaryIds = TARGET_KPI_IDS[neType];
|
||||||
|
|
||||||
// 从所有指标中筛选出当前网元其他指标
|
// 从所有指标中筛选出当前网元其他指标
|
||||||
groups[neType] = kpiColumns.value.filter(kpi => {
|
groups[neType] = kpiColumns.value.filter(kpi => {
|
||||||
// 检查是否不在主要指标列表中
|
// 检查是否不在主要指标列表中
|
||||||
@@ -719,7 +723,6 @@ const handleSecondaryKPIChange = (kpiId: string, checked: boolean) => {
|
|||||||
</a-modal>
|
</a-modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.kpi-overview {
|
.kpi-overview {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|||||||
Reference in New Issue
Block a user