增加仪表盘累加及UPF下拉框

This commit is contained in:
lai
2024-12-20 15:47:17 +08:00
parent 71f2e596fe
commit 2b69b8d72b
3 changed files with 284 additions and 148 deletions

View File

@@ -79,7 +79,7 @@
.upfFlow .inner .chart { .upfFlow .inner .chart {
width: 100%; width: 100%;
height: 100%; height: 100%;
margin-top: 1rem; margin-top: 0rem;
} }
/* 网络拓扑 */ /* 网络拓扑 */

View File

@@ -1,6 +1,6 @@
import { RESULT_CODE_ERROR } from '@/constants/result-constants'; import { RESULT_CODE_ERROR } from '@/constants/result-constants';
import { OptionsType, WS } from '@/plugins/ws-websocket'; import { OptionsType, WS } from '@/plugins/ws-websocket';
import { onBeforeUnmount, onMounted } from 'vue'; import { onBeforeUnmount, onMounted, ref } from 'vue';
import { import {
eventListParse, eventListParse,
eventItemParseAndPush, eventItemParseAndPush,
@@ -12,9 +12,15 @@ import {
upfFlowParse, upfFlowParse,
upfTotalFlowReset, upfTotalFlowReset,
} from './useUPFTotalFlow'; } from './useUPFTotalFlow';
import { topologyReset, neStateParse } from './useTopology'; import { topologyReset, neStateParse, neStateRequestMap } from './useTopology';
import PQueue from 'p-queue'; import PQueue from 'p-queue';
/**UPF-的Id */
export const upfWhoId = ref<any>('');
/**UPF-的RmUid */
export const upfWhoRmUid = ref<any>('');
/**websocket连接 */ /**websocket连接 */
export default function useWS() { export default function useWS() {
const ws = new WS(); const ws = new WS();
@@ -27,13 +33,12 @@ export default function useWS() {
/**接收数据后回调 */ /**接收数据后回调 */
function wsMessage(res: Record<string, any>) { function wsMessage(res: Record<string, any>) {
// console.log(res); //console.log(res);
const { code, requestId, data } = res; const { code, requestId, data } = res;
if (code === RESULT_CODE_ERROR) { if (code === RESULT_CODE_ERROR) {
console.warn(res.msg); console.warn(res.msg);
return; return;
} }
// 网元状态 // 网元状态
if (requestId && requestId.startsWith('neState')) { if (requestId && requestId.startsWith('neState')) {
const neType = requestId.split('_')[1]; const neType = requestId.split('_')[1];
@@ -72,14 +77,13 @@ export default function useWS() {
upfTFParse('30', data); upfTFParse('30', data);
break; break;
} }
// 订阅组信息 // 订阅组信息
if (!data?.groupId) { if (!data?.groupId) {
return; return;
} }
switch (data.groupId) { switch (data.groupId) {
// kpiEvent 指标UPF // kpiEvent 指标UPF
case '12_001': case '12_' + upfWhoRmUid.value:
if (data.data) { if (data.data) {
upfFlowParse(data.data); upfFlowParse(data.data);
} }
@@ -168,7 +172,17 @@ export default function useWS() {
}); });
} }
onMounted(() => { /**重新发送至UPF 12_rmUid */
function reSendUPF(rmUid: string) {
upfWhoRmUid.value = rmUid;
//初始时时无需还原全部属性以及关闭
if (ws.state() === WebSocket.OPEN) {
ws.close();
userActivityReset();
upfTotalFlowReset();
neStateRequestMap.value = new Map();
//topologyReset();
}
const options: OptionsType = { const options: OptionsType = {
url: '/ws', url: '/ws',
params: { params: {
@@ -179,7 +193,7 @@ export default function useWS() {
* MME_UE会话事件(GroupID:1011_neId) * MME_UE会话事件(GroupID:1011_neId)
* IMS_CDR会话事件(GroupID:1005_neId) * IMS_CDR会话事件(GroupID:1005_neId)
*/ */
subGroupID: '12_001,1010,1011_001,1005_001', subGroupID: '12_' + rmUid + ',1010,1011_001,1005_001',
}, },
onmessage: wsMessage, onmessage: wsMessage,
onerror: (ev: any) => { onerror: (ev: any) => {
@@ -187,18 +201,20 @@ export default function useWS() {
}, },
}; };
ws.connect(options); ws.connect(options);
}); }
onBeforeUnmount(() => { onBeforeUnmount(() => {
ws.close(); ws.close();
userActivityReset(); userActivityReset();
upfTotalFlowReset(); upfTotalFlowReset();
topologyReset(); topologyReset();
upfWhoRmUid.value = '';
}); });
return { return {
wsSend, wsSend,
userActivitySend, userActivitySend,
upfTFSend, upfTFSend,
reSendUPF,
}; };
} }

View File

@@ -26,10 +26,15 @@ import useWS from './hooks/useWS';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import useNeInfoStore from '@/store/modules/neinfo';
import { message } from 'ant-design-vue';
import { upfWhoId } from './hooks/useWS';
const neInfoStore = useNeInfoStore();
const router = useRouter(); const router = useRouter();
const appStore = useAppStore(); const appStore = useAppStore();
const { t } = useI18n(); const { t } = useI18n();
const { wsSend, userActivitySend, upfTFSend } = useWS(); const { wsSend, userActivitySend, upfTFSend, reSendUPF } = useWS();
/**概览状态类型 */ /**概览状态类型 */
type SkimStateType = { type SkimStateType = {
@@ -60,6 +65,9 @@ let skimState: SkimStateType = reactive({
enbUeNum: 0, enbUeNum: 0,
}); });
/**网元参数 */
let neCascaderOptions = ref<Record<string, any>[]>([]);
/**总览节点 */ /**总览节点 */
const viewportDom = ref<HTMLElement | null>(null); const viewportDom = ref<HTMLElement | null>(null);
const { isFullscreen, toggle } = useFullscreen(viewportDom); const { isFullscreen, toggle } = useFullscreen(viewportDom);
@@ -94,62 +102,126 @@ function fnGetNeState() {
/**获取概览信息 */ /**获取概览信息 */
async function fnGetSkim() { async function fnGetSkim() {
const resArr = await Promise.allSettled([ console.log(neCascaderOptions.value);
listUDMSub({ // const resArr = await Promise.allSettled([
neid: '001', // listUDMSub({
pageNum: 1, // neid: '001',
pageSize: 1, // pageNum: 1,
}), // pageSize: 1,
listUENumBySMF('001'), // }),
listUENumByIMS('001'), // listUENumBySMF('001'),
listBase5G({ // listUENumByIMS('001'),
neType: 'AMF', // listBase5G({
neId: '001', // neType: 'AMF',
}), // neId: '001',
listBase5G({ // }),
neType: 'MME', // listBase5G({
neId: '001', // neType: 'MME',
}), // neId: '001',
// }),
// ]);
// if (resArr[0].status === 'fulfilled') {
// const res0 = resArr[0].value;
// if (res0.code === RESULT_CODE_SUCCESS) {
// skimState.udmSubNum = res0.total;
// }
// }
// if (resArr[1].status === 'fulfilled') {
// const res1 = resArr[1].value;
// if (res1.code === RESULT_CODE_SUCCESS) {
// skimState.smfUeNum = res1.data;
// }
// }
// if (resArr[2].status === 'fulfilled') {
// const res2 = resArr[2].value;
// if (res2.code === RESULT_CODE_SUCCESS) {
// skimState.imsUeNum = res2.data;
// }
// }
// if (resArr[3].status === 'fulfilled') {
// const res3 = resArr[3].value;
// if (res3.code === RESULT_CODE_SUCCESS) {
// skimState.gnbNum = res3.total;
// skimState.gnbUeNum = 0;
// res3.rows.map((item: any) => {
// skimState.gnbUeNum += item.ueNum;
// });
// }
// }
// if (resArr[4].status === 'fulfilled') {
// const res4 = resArr[4].value;
// if (res4.code === RESULT_CODE_SUCCESS) {
// skimState.enbNum = res4.total;
// skimState.enbUeNum = 0;
// res4.rows.map((item: any) => {
// skimState.enbUeNum += item.ueNum;
// });
// }
// }
const neHandlers = new Map([
['UDM', {
request: (neId: string) => listUDMSub({ neid: neId, pageNum: 1, pageSize: 1 }),
process: (res: any) => res.code === RESULT_CODE_SUCCESS && (skimState.udmSubNum += res.total)
}],
['SMF', {
request: (neId: string) => listUENumBySMF(neId),
process: (res: any) => res.code === RESULT_CODE_SUCCESS && (skimState.smfUeNum += res.data)
}],
['IMS', {
request: (neId: string) => listUENumByIMS(neId),
process: (res: any) => res.code === RESULT_CODE_SUCCESS && (skimState.imsUeNum += res.data)
}],
['AMF', {
request: (neId: string) => listBase5G({ neType: 'AMF', neId }),
process: (res: any) => {
if (res.code === RESULT_CODE_SUCCESS) {
skimState.gnbNum += res.total;
skimState.gnbUeNum += res.rows.reduce((sum: number, item: any) => sum + item.ueNum, 0);
}
}
}],
['MME', {
request: (neId: string) => listBase5G({ neType: 'MME', neId }),
process: (res: any) => {
if (res.code === RESULT_CODE_SUCCESS) {
skimState.enbNum += res.total;
skimState.enbUeNum += res.rows.reduce((sum: number, item: any) => sum + item.ueNum, 0);
}
}
}]
]); ]);
if (resArr[0].status === 'fulfilled') { const requests = neCascaderOptions.value.flatMap((ne: any) =>
const res0 = resArr[0].value; ne.children?.map((child: any) => {
if (res0.code === RESULT_CODE_SUCCESS) { const handler = neHandlers.get(child.neType);
skimState.udmSubNum = res0.total; return handler ? {
promise: handler.request(child.neId),
process: handler.process
} : null;
}).filter(Boolean) || []
);
const results = await Promise.allSettled(requests.map(r => r.promise));
// 重置
Object.assign(skimState, {
udmSubNum: 0,
smfUeNum: 0,
imsUeNum: 0,
gnbNum: 0,
gnbUeNum: 0,
enbNum: 0,
enbUeNum: 0
});
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
requests[index].process(result.value);
} }
} });
if (resArr[1].status === 'fulfilled') {
const res1 = resArr[1].value;
if (res1.code === RESULT_CODE_SUCCESS) {
skimState.smfUeNum = res1.data;
}
}
if (resArr[2].status === 'fulfilled') {
const res2 = resArr[2].value;
if (res2.code === RESULT_CODE_SUCCESS) {
skimState.imsUeNum = res2.data;
}
}
if (resArr[3].status === 'fulfilled') {
const res3 = resArr[3].value;
if (res3.code === RESULT_CODE_SUCCESS) {
skimState.gnbNum = res3.total;
skimState.gnbUeNum = 0;
res3.rows.map((item: any) => {
skimState.gnbUeNum += item.ueNum;
});
}
}
if (resArr[4].status === 'fulfilled') {
const res4 = resArr[4].value;
if (res4.code === RESULT_CODE_SUCCESS) {
skimState.enbNum = res4.total;
skimState.enbUeNum = 0;
res4.rows.map((item: any) => {
skimState.enbUeNum += item.ueNum;
});
}
}
} }
/**初始数据函数 */ /**初始数据函数 */
@@ -188,10 +260,85 @@ function fnToRouter(name: string, query?: any) {
router.push({ name, query }); router.push({ name, query });
} }
/**网元参数 */
let neOtions = ref<Record<string, any>[]>([]);
/**UPF网元Id */
let queryParams = reactive({
/**45G类型 */
neRealId: '',
});
// UPF实时流量下拉框选择
function fnSelectNe(value: any, option: any) {
queryParams.neRealId = value;
upfWhoId.value = value;
reSendUPF(option.rmUid);
// upfTotalFlow.value.map((item: any) => {
// item.requestFlag = false;
// });
for (var key in upfTotalFlow.value) {
upfTotalFlow.value[key].requestFlag = false;
}
loadData();
}
// 定义一个方法返回 views 容器
const getPopupContainer = () => {
// 使用 ref 或其他方式来引用你的 views 容器
// 如果 views 容器直接在这个组件内部,你可以使用 ref
// 但在这个例子中,我们假设它是通过类名来获取的
return document.querySelector('.viewport');
};
onMounted(() => { onMounted(() => {
fnGetSkim().then(() => {
loadData(); neInfoStore.fnNelist().then(res => {
}); if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
if (res.data.length > 0) {
let arr: Record<string, any>[] = [];
res.data.forEach(i => {
if (i.neType === 'UPF') {
arr.push({ value: i.neId, label: i.neName, rmUid: i.rmUid });
}
});
neOtions.value = arr;
if (arr.length > 0) {
//queryParams.neRealId = arr[0].value;
fnSelectNe(arr[0].value, arr[0]);
}
// 过滤不可用的网元
neCascaderOptions.value = neInfoStore.getNeCascaderOptions.filter(
(item: any) => {
return ['UDM', 'SMF', 'IMS', 'AMF', 'MME'].includes(
item.value
);
}
);
if (neCascaderOptions.value.length === 0) {
message.warning({
content: t('common.noData'),
duration: 2,
});
return;
}
}
} else {
message.warning({
content: t('common.noData'),
duration: 2,
});
}
})
.finally(() => {
fnGetSkim().then(() => {
loadData();
})
})
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
@@ -205,11 +352,7 @@ onBeforeUnmount(() => {
<template> <template>
<div class="viewport" ref="viewportDom"> <div class="viewport" ref="viewportDom">
<div class="brand"> <div class="brand">
<div <div class="brand-title" @click="toggle" :title="t('views.dashboard.overview.fullscreen')">
class="brand-title"
@click="toggle"
:title="t('views.dashboard.overview.fullscreen')"
>
{{ t('views.dashboard.overview.title') }} {{ t('views.dashboard.overview.title') }}
<FullscreenExitOutlined v-if="isFullscreen" /> <FullscreenExitOutlined v-if="isFullscreen" />
<FullscreenOutlined v-else /> <FullscreenOutlined v-else />
@@ -226,27 +369,17 @@ onBeforeUnmount(() => {
{{ t('views.dashboard.overview.skim.userTitle') }} {{ t('views.dashboard.overview.skim.userTitle') }}
</h3> </h3>
<div class="data"> <div class="data">
<div <div class="item toRouter" @click="fnToRouter('Sub_2010')" :title="t('views.dashboard.overview.toRouter')">
class="item toRouter"
@click="fnToRouter('Sub_2010')"
:title="t('views.dashboard.overview.toRouter')"
>
<div> <div>
<UserOutlined <UserOutlined style="color: #4096ff; margin-right: 8px; font-size: 1.1rem" />
style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
/>
{{ skimState.udmSubNum }} {{ skimState.udmSubNum }}
</div> </div>
<span> <span>
{{ t('views.dashboard.overview.skim.users') }} {{ t('views.dashboard.overview.skim.users') }}
</span> </span>
</div> </div>
<div <div class="item toRouter" @click="fnToRouter('Ims_2080')" :title="t('views.dashboard.overview.toRouter')"
class="item toRouter" style="margin: 0 12px">
@click="fnToRouter('Ims_2080')"
:title="t('views.dashboard.overview.toRouter')"
style="margin: 0 12px"
>
<div> <div>
<img :src="svgUserIMS" style="width: 18px; margin-right: 8px" /> <img :src="svgUserIMS" style="width: 18px; margin-right: 8px" />
{{ skimState.imsUeNum }} {{ skimState.imsUeNum }}
@@ -255,11 +388,7 @@ onBeforeUnmount(() => {
{{ t('views.dashboard.overview.skim.imsUeNum') }} {{ t('views.dashboard.overview.skim.imsUeNum') }}
</span> </span>
</div> </div>
<div <div class="item toRouter" @click="fnToRouter('Ue_2081')" :title="t('views.dashboard.overview.toRouter')">
class="item toRouter"
@click="fnToRouter('Ue_2081')"
:title="t('views.dashboard.overview.toRouter')"
>
<div> <div>
<img :src="svgUserSMF" style="width: 18px; margin-right: 8px" /> <img :src="svgUserSMF" style="width: 18px; margin-right: 8px" />
{{ skimState.smfUeNum }} {{ skimState.smfUeNum }}
@@ -278,29 +407,18 @@ onBeforeUnmount(() => {
{{ t('views.dashboard.overview.skim.baseTitle') }} {{ t('views.dashboard.overview.skim.baseTitle') }}
</h3> </h3>
<div class="data"> <div class="data">
<div <div class="item toRouter" @click="fnToRouter('Base5G_2082', { neType: 'AMF' })"
class="item toRouter" :title="t('views.dashboard.overview.toRouter')">
@click="fnToRouter('Base5G_2082', { neType: 'AMF' })"
:title="t('views.dashboard.overview.toRouter')"
>
<div style="align-items: flex-start"> <div style="align-items: flex-start">
<img <img :src="svgBase" style="width: 18px; margin-right: 8px; height: 2rem" />
:src="svgBase"
style="width: 18px; margin-right: 8px; height: 2rem"
/>
{{ skimState.gnbNum }} {{ skimState.gnbNum }}
</div> </div>
<span>{{ t('views.dashboard.overview.skim.gnbBase') }}</span> <span>{{ t('views.dashboard.overview.skim.gnbBase') }}</span>
</div> </div>
<div <div class="item toRouter" @click="fnToRouter('Base5G_2082', { neType: 'AMF' })"
class="item toRouter" :title="t('views.dashboard.overview.toRouter')">
@click="fnToRouter('Base5G_2082', { neType: 'AMF' })"
:title="t('views.dashboard.overview.toRouter')"
>
<div style="align-items: flex-start"> <div style="align-items: flex-start">
<UserOutlined <UserOutlined style="color: #4096ff; margin-right: 8px; font-size: 1.1rem" />
style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
/>
{{ skimState.gnbUeNum }} {{ skimState.gnbUeNum }}
</div> </div>
<span>{{ t('views.dashboard.overview.skim.gnbUeNum') }}</span> <span>{{ t('views.dashboard.overview.skim.gnbUeNum') }}</span>
@@ -315,29 +433,18 @@ onBeforeUnmount(() => {
{{ t('views.dashboard.overview.skim.baseTitle') }} {{ t('views.dashboard.overview.skim.baseTitle') }}
</h3> </h3>
<div class="data"> <div class="data">
<div <div class="item toRouter" @click="fnToRouter('Base5G_2082', { neType: 'MME' })"
class="item toRouter" :title="t('views.dashboard.overview.toRouter')">
@click="fnToRouter('Base5G_2082', { neType: 'MME' })"
:title="t('views.dashboard.overview.toRouter')"
>
<div style="align-items: flex-start"> <div style="align-items: flex-start">
<img <img :src="svgBase" style="width: 18px; margin-right: 8px; height: 2rem" />
:src="svgBase"
style="width: 18px; margin-right: 8px; height: 2rem"
/>
{{ skimState.enbNum }} {{ skimState.enbNum }}
</div> </div>
<span>{{ t('views.dashboard.overview.skim.enbBase') }}</span> <span>{{ t('views.dashboard.overview.skim.enbBase') }}</span>
</div> </div>
<div <div class="item toRouter" @click="fnToRouter('Base5G_2082', { neType: 'MME' })"
class="item toRouter" :title="t('views.dashboard.overview.toRouter')">
@click="fnToRouter('Base5G_2082', { neType: 'MME' })"
:title="t('views.dashboard.overview.toRouter')"
>
<div style="align-items: flex-start"> <div style="align-items: flex-start">
<UserOutlined <UserOutlined style="color: #4096ff; margin-right: 8px; font-size: 1.1rem" />
style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
/>
{{ skimState.enbUeNum }} {{ skimState.enbUeNum }}
</div> </div>
<span>{{ t('views.dashboard.overview.skim.enbUeNum') }}</span> <span>{{ t('views.dashboard.overview.skim.enbUeNum') }}</span>
@@ -365,12 +472,23 @@ onBeforeUnmount(() => {
<div class="inner"> <div class="inner">
<h3 <h3
class="toRouter" class="toRouter"
@click="fnToRouter('GoldTarget_2104')"
:title="t('views.dashboard.overview.toRouter')" :title="t('views.dashboard.overview.toRouter')"
style="display: flex; align-items: center"
> >
<AreaChartOutlined style="color: #68d8fe" />&nbsp;&nbsp; <AreaChartOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.upfFlow.title') }} <span @click="fnToRouter('GoldTarget_2104')">{{
t('views.dashboard.overview.upfFlow.title')
}}</span>
<a-select
v-model:value="queryParams.neRealId"
:options="neOtions"
:get-Popup-Container="getPopupContainer"
class="toDeep"
style="width: 100px; color: #fff; margin-left: auto"
@change="fnSelectNe"
/>
</h3> </h3>
<div class="chart"> <div class="chart">
<UPFFlow /> <UPFFlow />
</div> </div>
@@ -379,11 +497,8 @@ onBeforeUnmount(() => {
<!-- 网络拓扑 --> <!-- 网络拓扑 -->
<div class="topology panel"> <div class="topology panel">
<div class="inner"> <div class="inner">
<h3 <h3 class="toRouter" @click="fnToRouter('TopologyArchitecture_2128')"
class="toRouter" :title="t('views.dashboard.overview.toRouter')">
@click="fnToRouter('TopologyArchitecture_2128')"
:title="t('views.dashboard.overview.toRouter')"
>
<span> <span>
<ApartmentOutlined style="color: #68d8fe" />&nbsp;&nbsp; <ApartmentOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.topology.title') }} {{ t('views.dashboard.overview.topology.title') }}
@@ -413,17 +528,10 @@ onBeforeUnmount(() => {
<!-- 筛选 --> <!-- 筛选 -->
<div class="filter"> <div class="filter">
<span <span :data-key="v" :class="{ active: upfTFActive === v }" v-for="v in ['0', '7', '30']" :key="v" @click="() => {
:data-key="v" upfTFActive = v;
:class="{ active: upfTFActive === v }" }
v-for="v in ['0', '7', '30']" ">
:key="v"
@click="
() => {
upfTFActive = v;
}
"
>
{{ {{
v === '0' v === '0'
? '24' + t('common.units.hour') ? '24' + t('common.units.hour')
@@ -456,11 +564,7 @@ onBeforeUnmount(() => {
<!-- 告警统计 --> <!-- 告警统计 -->
<div class="alarmType panel"> <div class="alarmType panel">
<div class="inner"> <div class="inner">
<h3 <h3 class="toRouter" @click="fnToRouter('HistoryAlarm_2097')" :title="t('views.dashboard.overview.toRouter')">
class="toRouter"
@click="fnToRouter('HistoryAlarm_2097')"
:title="t('views.dashboard.overview.toRouter')"
>
<PieChartOutlined style="color: #68d8fe" />&nbsp;&nbsp; <PieChartOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.alarmTypeBar.alarmSum') }} {{ t('views.dashboard.overview.alarmTypeBar.alarmSum') }}
</h3> </h3>
@@ -488,4 +592,20 @@ onBeforeUnmount(() => {
<style lang="less" scoped> <style lang="less" scoped>
@import url('./css/index.css'); @import url('./css/index.css');
.toDeep {
--editor-background-color: blue;
}
.toDeep :deep(.ant-select-selector) {
background-color: #101129;
border: none;
}
.toDeep :deep(.ant-select-arrow) {
color: #fff;
}
.toDeep :deep(.ant-select-selection-item) {
color: #fff;
}
</style> </style>