feat: mt版本移动过来的的告警/仪表/性能大屏页面
@@ -194,33 +194,13 @@ export async function exportAll(query: Record<string, any>) {
|
|||||||
* @returns bolb
|
* @returns bolb
|
||||||
*/
|
*/
|
||||||
export async function origGet() {
|
export async function origGet() {
|
||||||
let totalSQL = `select count(*) as value,orig_severity as name from alarm WHERE alarm_status='1' and orig_severity!='Event' group by orig_severity`;
|
return await request({
|
||||||
|
url: `/neData/alarm/count/severity`,
|
||||||
// 发起请求
|
|
||||||
const result = await request({
|
|
||||||
url: `/api/rest/databaseManagement/v1/select/omc_db/alarm`,
|
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
SQL: totalSQL,
|
alarmStatus: 'Active',
|
||||||
},
|
},
|
||||||
timeout: 30_000,
|
|
||||||
});
|
});
|
||||||
////
|
|
||||||
|
|
||||||
// 解析数据
|
|
||||||
if (result.code === RESULT_CODE_SUCCESS) {
|
|
||||||
const itemData = result.data.data;
|
|
||||||
if (Array.isArray(itemData)) {
|
|
||||||
const v = itemData[0]['alarm'];
|
|
||||||
if (Array.isArray(v)) {
|
|
||||||
result.data = v;
|
|
||||||
}
|
|
||||||
if (v === null) {
|
|
||||||
result.data = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -228,34 +208,13 @@ export async function origGet() {
|
|||||||
* @param filterFlag 查询参数
|
* @param filterFlag 查询参数
|
||||||
* @returns object
|
* @returns object
|
||||||
*/
|
*/
|
||||||
export async function top3Sel(filterFlag?: string) {
|
export async function top3Sel() {
|
||||||
let filter = ` WHERE alarm_status='1'and orig_severity='${filterFlag}'`;
|
return await request({
|
||||||
if (!filterFlag) filter = "WHERE alarm_status='1'";
|
url: `/neData/alarm/count/ne`,
|
||||||
|
|
||||||
let top3SQL = `select count(*) as value,ne_type as name from alarm ${filter} and orig_severity!='Event' group by ne_type ORDER BY value desc limit 0,3 `;
|
|
||||||
|
|
||||||
// 发起请求
|
|
||||||
const result = await request({
|
|
||||||
url: `/api/rest/databaseManagement/v1/select/omc_db/alarm`,
|
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
SQL: top3SQL,
|
alarmStatus: 'Active',
|
||||||
|
top: 3,
|
||||||
},
|
},
|
||||||
timeout: 30_000,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 解析数据
|
|
||||||
if (result.code === RESULT_CODE_SUCCESS) {
|
|
||||||
const itemData = result.data.data;
|
|
||||||
if (Array.isArray(itemData)) {
|
|
||||||
const v = itemData[0]['alarm'];
|
|
||||||
if (Array.isArray(v)) {
|
|
||||||
result.data = v;
|
|
||||||
}
|
|
||||||
if (v === null) {
|
|
||||||
result.data = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,9 +78,9 @@ const alarmTypeType = ref<any>([
|
|||||||
|
|
||||||
/**告警类型Top数据 */
|
/**告警类型Top数据 */
|
||||||
const alarmTypeTypeTop = ref<any>([
|
const alarmTypeTypeTop = ref<any>([
|
||||||
{ name: 'AMF', value: 0 },
|
{ neType: 'AMF', total: 0 },
|
||||||
{ name: 'UDM', value: 0 },
|
{ neType: 'UDM', total: 0 },
|
||||||
{ name: 'SMF', value: 0 },
|
{ neType: 'SMF', total: 0 },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -92,7 +92,7 @@ function initPicture() {
|
|||||||
if (res0.code === RESULT_CODE_SUCCESS && Array.isArray(res0.data)) {
|
if (res0.code === RESULT_CODE_SUCCESS && Array.isArray(res0.data)) {
|
||||||
for (const item of res0.data) {
|
for (const item of res0.data) {
|
||||||
let index = 0;
|
let index = 0;
|
||||||
switch (item.name) {
|
switch (item.severity) {
|
||||||
case 'Critical':
|
case 'Critical':
|
||||||
index = 0;
|
index = 0;
|
||||||
break;
|
break;
|
||||||
@@ -109,7 +109,7 @@ function initPicture() {
|
|||||||
// index = 4;
|
// index = 4;
|
||||||
// break;
|
// break;
|
||||||
}
|
}
|
||||||
alarmTypeType.value[index].value = Number(item.value);
|
alarmTypeType.value[index].value = Number(item.total);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,7 +119,7 @@ function initPicture() {
|
|||||||
alarmTypeTypeTop.value = alarmTypeTypeTop.value
|
alarmTypeTypeTop.value = alarmTypeTypeTop.value
|
||||||
.concat(res1.data)
|
.concat(res1.data)
|
||||||
.sort((a: any, b: any) => {
|
.sort((a: any, b: any) => {
|
||||||
return b.value - a.value;
|
return b.total - a.total;
|
||||||
})
|
})
|
||||||
.slice(0, 3);
|
.slice(0, 3);
|
||||||
}
|
}
|
||||||
@@ -210,7 +210,7 @@ function initPicture() {
|
|||||||
{ offset: 1, color: '#2f54eb' },
|
{ offset: 1, color: '#2f54eb' },
|
||||||
]), // 渐变
|
]), // 渐变
|
||||||
},
|
},
|
||||||
data: alarmTypeTypeTop.value,
|
data: alarmTypeTypeTop.value.map((item: any) => item.total),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
// 柱状图设置
|
// 柱状图设置
|
||||||
@@ -237,7 +237,7 @@ function initPicture() {
|
|||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
inverse: true,
|
inverse: true,
|
||||||
data: alarmTypeTypeTop.value.map((item: any) => item.name),
|
data: alarmTypeTypeTop.value.map((item: any) => item.neType),
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
color: '#A7D6F4',
|
color: '#A7D6F4',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
|||||||
284
src/views/dashboard/overview2/components/AlarnTypeBar/index.vue
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import * as echarts from 'echarts/core';
|
||||||
|
import {
|
||||||
|
TitleComponent,
|
||||||
|
TitleComponentOption,
|
||||||
|
TooltipComponent,
|
||||||
|
TooltipComponentOption,
|
||||||
|
GridComponent,
|
||||||
|
GridComponentOption,
|
||||||
|
LegendComponent,
|
||||||
|
LegendComponentOption,
|
||||||
|
} from 'echarts/components';
|
||||||
|
import {
|
||||||
|
PieChart,
|
||||||
|
PieSeriesOption,
|
||||||
|
BarChart,
|
||||||
|
BarSeriesOption,
|
||||||
|
} from 'echarts/charts';
|
||||||
|
import { LabelLayout } from 'echarts/features';
|
||||||
|
import { CanvasRenderer } from 'echarts/renderers';
|
||||||
|
|
||||||
|
import { markRaw, onMounted, ref } from 'vue';
|
||||||
|
import { origGet, top3Sel } from '@/api/faultManage/actAlarm';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
echarts.use([
|
||||||
|
TitleComponent,
|
||||||
|
TooltipComponent,
|
||||||
|
GridComponent,
|
||||||
|
LegendComponent,
|
||||||
|
PieChart,
|
||||||
|
BarChart,
|
||||||
|
CanvasRenderer,
|
||||||
|
LabelLayout,
|
||||||
|
]);
|
||||||
|
|
||||||
|
type EChartsOption = echarts.ComposeOption<
|
||||||
|
| TitleComponentOption
|
||||||
|
| TooltipComponentOption
|
||||||
|
| GridComponentOption
|
||||||
|
| LegendComponentOption
|
||||||
|
| PieSeriesOption
|
||||||
|
| BarSeriesOption
|
||||||
|
>;
|
||||||
|
|
||||||
|
/**图DOM节点实例对象 */
|
||||||
|
const alarmTypeBar = ref<HTMLElement | undefined>(undefined);
|
||||||
|
|
||||||
|
/**图实例对象 */
|
||||||
|
const alarmTypeBarChart = ref<any>(null);
|
||||||
|
|
||||||
|
/**告警类型数据 */
|
||||||
|
const alarmTypeType = ref<any>([
|
||||||
|
{
|
||||||
|
value: 0,
|
||||||
|
name: t('views.index.Critical'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 0,
|
||||||
|
name: t('views.index.Major'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 0,
|
||||||
|
name: t('views.index.Minor'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 0,
|
||||||
|
name: t('views.index.Warning'),
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// value: 0,
|
||||||
|
// name: t('views.index.Event'),
|
||||||
|
// },
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**告警类型Top数据 */
|
||||||
|
const alarmTypeTypeTop = ref<any>([
|
||||||
|
{ neType: 'AMF', total: 0 },
|
||||||
|
{ neType: 'UDM', total: 0 },
|
||||||
|
{ neType: 'SMF', total: 0 },
|
||||||
|
]);
|
||||||
|
|
||||||
|
//
|
||||||
|
function initPicture() {
|
||||||
|
Promise.allSettled([origGet(), top3Sel()])
|
||||||
|
.then(resArr => {
|
||||||
|
if (resArr[0].status === 'fulfilled') {
|
||||||
|
const res0 = resArr[0].value;
|
||||||
|
if (res0.code === RESULT_CODE_SUCCESS && Array.isArray(res0.data)) {
|
||||||
|
for (const item of res0.data) {
|
||||||
|
let index = 0;
|
||||||
|
switch (item.severity) {
|
||||||
|
case 'Critical':
|
||||||
|
index = 0;
|
||||||
|
break;
|
||||||
|
case 'Major':
|
||||||
|
index = 1;
|
||||||
|
break;
|
||||||
|
case 'Minor':
|
||||||
|
index = 2;
|
||||||
|
break;
|
||||||
|
case 'Warning':
|
||||||
|
index = 3;
|
||||||
|
break;
|
||||||
|
// case 'Event':
|
||||||
|
// index = 4;
|
||||||
|
// break;
|
||||||
|
}
|
||||||
|
alarmTypeType.value[index].value = Number(item.total);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (resArr[1].status === 'fulfilled') {
|
||||||
|
const res1 = resArr[1].value;
|
||||||
|
if (res1.code === RESULT_CODE_SUCCESS && Array.isArray(res1.data)) {
|
||||||
|
alarmTypeTypeTop.value = alarmTypeTypeTop.value
|
||||||
|
.concat(res1.data)
|
||||||
|
.sort((a: any, b: any) => {
|
||||||
|
return b.total - a.total;
|
||||||
|
})
|
||||||
|
.slice(0, 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
const optionData: EChartsOption = {
|
||||||
|
title: [
|
||||||
|
{
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: t('views.dashboard.overview.alarmTypeBar.topTitle'),
|
||||||
|
textStyle: {
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: '14',
|
||||||
|
fontWeight: 400,
|
||||||
|
},
|
||||||
|
top: '50%',
|
||||||
|
left: '0%',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: '{b} : {c}',
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
orient: 'vertical',
|
||||||
|
right: '2%',
|
||||||
|
top: '12%',
|
||||||
|
data: alarmTypeType.value.map((item: any) => item.name), //label数组
|
||||||
|
textStyle: {
|
||||||
|
color: '#A7D6F4', // 设置图例文字颜色
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: [
|
||||||
|
{
|
||||||
|
top: '60%',
|
||||||
|
left: '15%',
|
||||||
|
right: '25%',
|
||||||
|
bottom: '10%',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
//饼图:
|
||||||
|
{
|
||||||
|
type: 'pie',
|
||||||
|
radius: '35%',
|
||||||
|
color: ['#f5222d', '#fa8c16', '#fadb14', '#1677ff', '#13c2c2'],
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'inner',
|
||||||
|
formatter: (params: any) => {
|
||||||
|
if (!params.value) return '';
|
||||||
|
return `${params.value}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
labelLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
center: ['35%', '25%'],
|
||||||
|
data: alarmTypeType.value,
|
||||||
|
zlevel: 2, // 设置zlevel为1,使得柱状图在下层显示
|
||||||
|
itemStyle: {
|
||||||
|
shadowBlur: 10,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
//柱状
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
barWidth: 12, // 柱子宽度
|
||||||
|
barCategoryGap: '30%', // 控制同一系列的柱间距离
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'right', // 位置
|
||||||
|
color: '#A7D6F4', //淡蓝色
|
||||||
|
fontSize: 14,
|
||||||
|
distance: 14, // label与柱子距离
|
||||||
|
formatter: '{c}',
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: [0, 20, 20, 0], // 圆角(左上、右上、右下、左下)
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
|
{ offset: 0, color: '#f0f5ff' },
|
||||||
|
{ offset: 0.5, color: '#adc6ff' },
|
||||||
|
{ offset: 1, color: '#2f54eb' },
|
||||||
|
]), // 渐变
|
||||||
|
},
|
||||||
|
data: alarmTypeTypeTop.value.map((item: any) => item.total),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// 柱状图设置
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
splitLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
type: 'value',
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
splitLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
//y轴
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
type: 'category',
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
inverse: true,
|
||||||
|
data: alarmTypeTypeTop.value.map((item: any) => item.neType),
|
||||||
|
axisLabel: {
|
||||||
|
color: '#A7D6F4',
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
fnDesign(alarmTypeBar.value, optionData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fnDesign(container: HTMLElement | undefined, option: any) {
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
alarmTypeBarChart.value = markRaw(echarts.init(container, 'light'));
|
||||||
|
option && alarmTypeBarChart.value.setOption(option);
|
||||||
|
|
||||||
|
// 创建 ResizeObserver 实例
|
||||||
|
var observer = new ResizeObserver(entries => {
|
||||||
|
if (alarmTypeBarChart.value) {
|
||||||
|
alarmTypeBarChart.value.resize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 监听元素大小变化
|
||||||
|
observer.observe(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initPicture();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="alarmTypeBar" class="chart-container"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.chart-container {
|
||||||
|
/* 设置图表容器大小和位置 */
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
268
src/views/dashboard/overview2/components/IMSActivity/index.vue
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { parseDuration, parseDateToStr } from '@/utils/date-utils';
|
||||||
|
import { eventData, eventId } from '../../hooks/useUserActivity';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
import useDictStore from '@/store/modules/dict';
|
||||||
|
import { onMounted, reactive } from 'vue';
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { getDict } = useDictStore();
|
||||||
|
|
||||||
|
/**字典数据 */
|
||||||
|
let dict: {
|
||||||
|
/**CDR SIP响应代码类别类型 */
|
||||||
|
cdrSipCode: DictType[];
|
||||||
|
/**CDR 呼叫类型 */
|
||||||
|
cdrCallType: DictType[];
|
||||||
|
/**UE 事件认证代码类型 */
|
||||||
|
ueAauthCode: DictType[];
|
||||||
|
/**UE 事件类型 */
|
||||||
|
ueEventType: DictType[];
|
||||||
|
/**UE 事件CM状态 */
|
||||||
|
ueEventCmState: DictType[];
|
||||||
|
/**CDR SIP响应代码类别类型 */
|
||||||
|
cdrSipCodeCause: DictType[];
|
||||||
|
} = reactive({
|
||||||
|
cdrSipCode: [],
|
||||||
|
cdrCallType: [],
|
||||||
|
ueAauthCode: [],
|
||||||
|
ueEventType: [],
|
||||||
|
ueEventCmState: [],
|
||||||
|
cdrSipCodeCause: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 初始字典数据
|
||||||
|
Promise.allSettled([
|
||||||
|
getDict('cdr_sip_code'),
|
||||||
|
getDict('cdr_call_type'),
|
||||||
|
getDict('ue_auth_code'),
|
||||||
|
getDict('ue_event_type'),
|
||||||
|
getDict('ue_event_cm_state'),
|
||||||
|
getDict('cdr_sip_code_cause'),
|
||||||
|
]).then(resArr => {
|
||||||
|
if (resArr[0].status === 'fulfilled') {
|
||||||
|
dict.cdrSipCode = resArr[0].value;
|
||||||
|
}
|
||||||
|
if (resArr[1].status === 'fulfilled') {
|
||||||
|
dict.cdrCallType = resArr[1].value;
|
||||||
|
}
|
||||||
|
if (resArr[2].status === 'fulfilled') {
|
||||||
|
dict.ueAauthCode = resArr[2].value;
|
||||||
|
}
|
||||||
|
if (resArr[3].status === 'fulfilled') {
|
||||||
|
dict.ueEventType = resArr[3].value;
|
||||||
|
}
|
||||||
|
if (resArr[4].status === 'fulfilled') {
|
||||||
|
dict.ueEventCmState = resArr[4].value;
|
||||||
|
}
|
||||||
|
if (resArr[5].status === 'fulfilled') {
|
||||||
|
dict.cdrSipCodeCause = resArr[5].value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="activty">
|
||||||
|
<template v-for="item in eventData" :key="item.eId">
|
||||||
|
<!-- CDR事件IMS -->
|
||||||
|
<div class="card-cdr" :class="{ active: item.eId === eventId }" v-if="item.eType === 'ims_cdr'">
|
||||||
|
<div class="card-cdr-item">
|
||||||
|
<div>
|
||||||
|
{{ t('views.dashboard.overview.userActivity.type') }}:
|
||||||
|
<span>
|
||||||
|
<DictTag :options="dict.cdrCallType" :value="item.data.callType" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
<div class="card-cdr-item">
|
||||||
|
<div>
|
||||||
|
{{ t('views.dashboard.overview.userActivity.caller') }}:
|
||||||
|
<span :title="item.data.callerParty">
|
||||||
|
{{ item.data.callerParty }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ t('views.dashboard.overview.userActivity.called') }}:
|
||||||
|
<span :title="item.data.calledParty">
|
||||||
|
{{ item.data.calledParty }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="item.data.callType !== 'sms'">
|
||||||
|
{{ t('views.dashboard.overview.userActivity.duration') }}:
|
||||||
|
<span>{{ parseDuration(item.data.callDuration) }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-else></div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ t('views.dashboard.overview.userActivity.time') }}:
|
||||||
|
<span :title="item.data.releaseTime">
|
||||||
|
{{
|
||||||
|
typeof item.data.releaseTime === 'number'
|
||||||
|
? parseDateToStr(+item.data.releaseTime * 1000)
|
||||||
|
: parseDateToStr(item.data.releaseTime)
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||||
|
<span v-if="item.data.callType !== 'sms'">
|
||||||
|
<DictTag :options="dict.cdrSipCode" :value="item.data.cause" value-default="0" />
|
||||||
|
<DictTag :options="dict.cdrSipCodeCause" :value="item.data.cause" value-default="0" />
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
{{ t('views.dashboard.overview.userActivity.resultOK') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.activty {
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
height: 94%;
|
||||||
|
color: #61a8ff;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
|
||||||
|
& .card-ue {
|
||||||
|
border: 1px #61a8ff solid;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
margin-bottom: 0.3rem;
|
||||||
|
line-height: 1rem;
|
||||||
|
|
||||||
|
& span {
|
||||||
|
color: #68d8fe;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
&>div {
|
||||||
|
width: 50%;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-align: start;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-w33 {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
&>div {
|
||||||
|
width: 33%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .card-cdr {
|
||||||
|
border: 1px #61a8ff solid;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
margin-bottom: 0.3rem;
|
||||||
|
line-height: 1rem;
|
||||||
|
|
||||||
|
& span {
|
||||||
|
color: #68d8fe;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
&>div {
|
||||||
|
flex: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-align: start;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .active {
|
||||||
|
color: #faad14;
|
||||||
|
border: 1px #faad14 solid;
|
||||||
|
animation: backInRight 0.3s alternate;
|
||||||
|
|
||||||
|
& span {
|
||||||
|
color: #faad14;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 兼容当行显示字内容 */
|
||||||
|
@media (max-width: 1720px) {
|
||||||
|
& .card-cdr {
|
||||||
|
&-item {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
&>div {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .card-ue {
|
||||||
|
&-item {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
&>div {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 修改滚动条的样式 */
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
/* 设置滚动条宽度 */
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background-color: #101129;
|
||||||
|
/* 设置滚动条轨道背景颜色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #28293f;
|
||||||
|
/* 设置滚动条滑块颜色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: #68d8fe;
|
||||||
|
/* 设置鼠标悬停时滚动条滑块颜色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes backInRight {
|
||||||
|
0% {
|
||||||
|
opacity: 0.7;
|
||||||
|
-webkit-transform: translateX(2000px) scale(0.7);
|
||||||
|
transform: translateX(2000px) scale(0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
80% {
|
||||||
|
opacity: 0.7;
|
||||||
|
-webkit-transform: translateX(0) scale(0.7);
|
||||||
|
transform: translateX(0) scale(0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-transform: scale(1);
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
352
src/views/dashboard/overview2/components/NeResources/index.vue
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref, nextTick, watch } from 'vue';
|
||||||
|
import * as echarts from 'echarts/core';
|
||||||
|
import { GridComponent, GridComponentOption, TooltipComponent } from 'echarts/components';
|
||||||
|
import { CanvasRenderer } from 'echarts/renderers';
|
||||||
|
import { graphNodeClickID, graphNodeState } from '../../hooks/useTopology';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
import { markRaw } from 'vue';
|
||||||
|
// 引入液体填充图表
|
||||||
|
import 'echarts-liquidfill';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
echarts.use([GridComponent, TooltipComponent, CanvasRenderer]);
|
||||||
|
|
||||||
|
type EChartsOption = echarts.ComposeOption<GridComponentOption>;
|
||||||
|
|
||||||
|
/**图DOM节点实例对象 */
|
||||||
|
const neResourcesDom = ref<HTMLElement | undefined>(undefined);
|
||||||
|
|
||||||
|
/**图实例对象 */
|
||||||
|
const neResourcesChart = ref<any>(null);
|
||||||
|
|
||||||
|
// 当前选中的网元ID
|
||||||
|
const currentNeId = ref('');
|
||||||
|
|
||||||
|
// 资源数据
|
||||||
|
const resourceData = ref({
|
||||||
|
neCpu: 1,
|
||||||
|
sysCpu: 1,
|
||||||
|
sysMem: 1,
|
||||||
|
sysDisk: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取颜色
|
||||||
|
function getColorByValue(value: number) {
|
||||||
|
if (value >= 70) {
|
||||||
|
return ['#f5222d', '#ff7875']; // 红色
|
||||||
|
} else if (value >= 30) {
|
||||||
|
return ['#2f54eb', '#597ef7']; // 蓝色
|
||||||
|
} else {
|
||||||
|
return ['#52c41a', '#95de64']; // 绿色
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**图数据 */
|
||||||
|
const optionData: any = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: '{b}: {c}%'
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: '10%',
|
||||||
|
bottom: '5%',
|
||||||
|
left: '5%',
|
||||||
|
right: '5%',
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'liquidFill',
|
||||||
|
radius: '50%',
|
||||||
|
center: ['15%', '35%'],
|
||||||
|
data: [resourceData.value.neCpu / 100],
|
||||||
|
name: t('views.dashboard.overview.resources.neCpu'),
|
||||||
|
color: getColorByValue(resourceData.value.neCpu),
|
||||||
|
backgroundStyle: {
|
||||||
|
color: 'rgba(10, 60, 160, 0.1)'
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
normal: {
|
||||||
|
formatter: () => {
|
||||||
|
return `${t('views.dashboard.overview.resources.neCpu')}\n${resourceData.value.neCpu}%`;
|
||||||
|
},
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#fff'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
outline: {
|
||||||
|
show: true,
|
||||||
|
borderDistance: 2,
|
||||||
|
itemStyle: {
|
||||||
|
borderColor: '#0a3ca0',
|
||||||
|
borderWidth: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'liquidFill',
|
||||||
|
radius: '50%',
|
||||||
|
center: ['85%', '35%'],
|
||||||
|
data: [resourceData.value.sysCpu / 100],
|
||||||
|
name: t('views.dashboard.overview.resources.sysCpu'),
|
||||||
|
color: getColorByValue(resourceData.value.sysCpu),
|
||||||
|
backgroundStyle: {
|
||||||
|
color: 'rgba(10, 60, 160, 0.1)'
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
normal: {
|
||||||
|
formatter: () => {
|
||||||
|
return `${t('views.dashboard.overview.resources.sysCpu')}\n${resourceData.value.sysCpu}%`;
|
||||||
|
},
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#fff'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
outline: {
|
||||||
|
show: true,
|
||||||
|
borderDistance: 2,
|
||||||
|
itemStyle: {
|
||||||
|
borderColor: '#0a3ca0',
|
||||||
|
borderWidth: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'liquidFill',
|
||||||
|
radius: '50%',
|
||||||
|
center: ['35%', '65%'],
|
||||||
|
data: [resourceData.value.sysMem / 100],
|
||||||
|
name: t('views.dashboard.overview.resources.sysMem'),
|
||||||
|
color: getColorByValue(resourceData.value.sysMem),
|
||||||
|
backgroundStyle: {
|
||||||
|
color: 'rgba(10, 60, 160, 0.1)'
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
normal: {
|
||||||
|
formatter: () => {
|
||||||
|
return `${t('views.dashboard.overview.resources.sysMem')}\n${resourceData.value.sysMem}%`;
|
||||||
|
},
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#fff'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
outline: {
|
||||||
|
show: true,
|
||||||
|
borderDistance: 2,
|
||||||
|
itemStyle: {
|
||||||
|
borderColor: '#0a3ca0',
|
||||||
|
borderWidth: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'liquidFill',
|
||||||
|
radius: '50%',
|
||||||
|
center: ['65%', '65%'],
|
||||||
|
data: [resourceData.value.sysDisk / 100],
|
||||||
|
name: t('views.dashboard.overview.resources.sysDisk'),
|
||||||
|
color: getColorByValue(resourceData.value.sysDisk),
|
||||||
|
backgroundStyle: {
|
||||||
|
color: 'rgba(10, 60, 160, 0.1)'
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
normal: {
|
||||||
|
formatter: () => {
|
||||||
|
return `${t('views.dashboard.overview.resources.sysDisk')}\n${resourceData.value.sysDisk}%`;
|
||||||
|
},
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#fff'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
outline: {
|
||||||
|
show: true,
|
||||||
|
borderDistance: 2,
|
||||||
|
itemStyle: {
|
||||||
|
borderColor: '#0a3ca0',
|
||||||
|
borderWidth: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
/**图数据渲染 */
|
||||||
|
function handleRanderChart(
|
||||||
|
container: HTMLElement | undefined,
|
||||||
|
option: any
|
||||||
|
) {
|
||||||
|
if (!container) return;
|
||||||
|
neResourcesChart.value = markRaw(echarts.init(container));
|
||||||
|
option && neResourcesChart.value.setOption(option);
|
||||||
|
|
||||||
|
// 创建 ResizeObserver 实例
|
||||||
|
var observer = new ResizeObserver(entries => {
|
||||||
|
if (neResourcesChart.value) {
|
||||||
|
neResourcesChart.value.resize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 监听元素大小变化
|
||||||
|
observer.observe(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fnChangeData(data: any[], itemID: string) {
|
||||||
|
const neType = itemID.split('_')[0];
|
||||||
|
const neID = itemID.split('_')[1];
|
||||||
|
currentNeId.value = neID;
|
||||||
|
|
||||||
|
let info = data.find((item: any) => item.id === neType);
|
||||||
|
if (!info || !info.neStateMap[neID]?.online) return;
|
||||||
|
|
||||||
|
let sysCpuUsage = 0;
|
||||||
|
let nfCpuUsage = 0;
|
||||||
|
if (info.neStateMap[neID].cpu) {
|
||||||
|
nfCpuUsage = info.neStateMap[neID].cpu.nfCpuUsage;
|
||||||
|
const nfCpu = +(info.neStateMap[neID].cpu.nfCpuUsage / 100);
|
||||||
|
nfCpuUsage = +nfCpu.toFixed(2);
|
||||||
|
if (nfCpuUsage > 100) {
|
||||||
|
nfCpuUsage = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
sysCpuUsage = info.neStateMap[neID].cpu.sysCpuUsage;
|
||||||
|
let sysCpu = +(info.neStateMap[neID].cpu.sysCpuUsage / 100);
|
||||||
|
sysCpuUsage = +sysCpu.toFixed(2);
|
||||||
|
if (sysCpuUsage > 100) {
|
||||||
|
sysCpuUsage = 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let sysMemUsage = 0;
|
||||||
|
if (info.neStateMap[neID].mem) {
|
||||||
|
const men = info.neStateMap[neID].mem.sysMemUsage;
|
||||||
|
sysMemUsage = +(men / 100).toFixed(2);
|
||||||
|
if (sysMemUsage > 100) {
|
||||||
|
sysMemUsage = 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let sysDiskUsage = 0;
|
||||||
|
if (info.neStateMap[neID].disk && Array.isArray(info.neStateMap[neID].disk.partitionInfo)) {
|
||||||
|
let disks: any[] = info.neStateMap[neID].disk.partitionInfo;
|
||||||
|
disks = disks.sort((a, b) => +b.used - +a.used);
|
||||||
|
if (disks.length > 0) {
|
||||||
|
const { total, used } = disks[0];
|
||||||
|
sysDiskUsage = +((used / total) * 100).toFixed(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceData.value = {
|
||||||
|
neCpu: nfCpuUsage,
|
||||||
|
sysCpu: sysCpuUsage,
|
||||||
|
sysMem: sysMemUsage,
|
||||||
|
sysDisk: sysDiskUsage
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新图表数据
|
||||||
|
neResourcesChart.value.setOption({
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
data: [resourceData.value.neCpu / 100],
|
||||||
|
color: getColorByValue(resourceData.value.neCpu),
|
||||||
|
label: {
|
||||||
|
normal: {
|
||||||
|
formatter: () => {
|
||||||
|
return `${t('views.dashboard.overview.resources.neCpu')}\n${resourceData.value.neCpu}%`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: [resourceData.value.sysCpu / 100],
|
||||||
|
color: getColorByValue(resourceData.value.sysCpu),
|
||||||
|
label: {
|
||||||
|
normal: {
|
||||||
|
formatter: () => {
|
||||||
|
return `${t('views.dashboard.overview.resources.sysCpu')}\n${resourceData.value.sysCpu}%`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: [resourceData.value.sysMem / 100],
|
||||||
|
color: getColorByValue(resourceData.value.sysMem),
|
||||||
|
label: {
|
||||||
|
normal: {
|
||||||
|
formatter: () => {
|
||||||
|
return `${t('views.dashboard.overview.resources.sysMem')}\n${resourceData.value.sysMem}%`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: [resourceData.value.sysDisk / 100],
|
||||||
|
color: getColorByValue(resourceData.value.sysDisk),
|
||||||
|
label: {
|
||||||
|
normal: {
|
||||||
|
formatter: () => {
|
||||||
|
return `${t('views.dashboard.overview.resources.sysDisk')}\n${resourceData.value.sysDisk}%`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
graphNodeState,
|
||||||
|
v => {
|
||||||
|
fnChangeData(v, graphNodeClickID.value);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(graphNodeClickID, v => {
|
||||||
|
fnChangeData(graphNodeState.value, v);
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
handleRanderChart(neResourcesDom.value, optionData);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="resource-panel">
|
||||||
|
<div ref="neResourcesDom" class="chart"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.resource-panel {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin-top: -32px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.panel-title {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #00fcff;
|
||||||
|
padding: 8px;
|
||||||
|
border-bottom: 1px solid rgba(10, 115, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
337
src/views/dashboard/overview2/components/Topology/index.vue
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||||
|
import { listAllNeInfo } from '@/api/ne/neInfo';
|
||||||
|
import { message } from 'ant-design-vue/es';
|
||||||
|
import { getGraphData } from '@/api/monitor/topology';
|
||||||
|
import { Graph, GraphData, Tooltip } from '@antv/g6';
|
||||||
|
import { parseBasePath } from '@/plugins/file-static-url';
|
||||||
|
import { edgeLineAnimateState } from '@/views/monitor/topologyBuild/hooks/registerEdge';
|
||||||
|
import { nodeImageAnimateState } from '@/views/monitor/topologyBuild/hooks/registerNode';
|
||||||
|
import {
|
||||||
|
graphG6,
|
||||||
|
graphState,
|
||||||
|
graphNodeClickID,
|
||||||
|
notNeNodes,
|
||||||
|
} from '../../hooks/useTopology';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
/**图DOM节点实例对象 */
|
||||||
|
const graphG6Dom = ref<HTMLElement | undefined>(undefined);
|
||||||
|
|
||||||
|
/**图节点展示 */
|
||||||
|
const graphNodeTooltip = new Tooltip({
|
||||||
|
offsetX: 10,
|
||||||
|
offsetY: 20,
|
||||||
|
getContent(evt) {
|
||||||
|
if (!evt) return t('views.monitor.topologyBuild.graphNotInfo');
|
||||||
|
const { id, label, neState, neInfoList, neStateMap }: any = evt.item?.getModel();
|
||||||
|
//console.log(neInfoList,neState,neInfoList);
|
||||||
|
if (notNeNodes.includes(id)) {
|
||||||
|
return `<div><span>${label || id}</span></div>`;
|
||||||
|
}
|
||||||
|
if (!neState) {
|
||||||
|
return `<div><span>${label || id}</span></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取同类型网元列表
|
||||||
|
const sameTypeNes = neInfoList || [];
|
||||||
|
|
||||||
|
// 如果没有网元或只有一个网元,显示原来的信息
|
||||||
|
if (sameTypeNes.length <= 1) {
|
||||||
|
return `
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 200px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div><strong>${t('views.monitor.topology.state')}:</strong><span>
|
||||||
|
${
|
||||||
|
neState.online
|
||||||
|
? t('views.monitor.topology.normalcy')
|
||||||
|
: t('views.monitor.topology.exceptions')
|
||||||
|
}
|
||||||
|
</span></div>
|
||||||
|
<div><strong>${t('views.monitor.topology.refreshTime')}:</strong><span>
|
||||||
|
${neState.refreshTime ?? '--'}
|
||||||
|
</span></div>
|
||||||
|
<div>========================</div>
|
||||||
|
<div><strong>ID:</strong><span>${neState.neId}</span></div>
|
||||||
|
<div><strong>${t('views.monitor.topology.name')}:</strong><span>
|
||||||
|
${neState.neName ?? '--'}
|
||||||
|
</span></div>
|
||||||
|
<div><strong>IP:</strong><span>${neState.neIP ?? '--'}</span></div>
|
||||||
|
<div><strong>${t('views.monitor.topology.version')}:</strong><span>
|
||||||
|
${neState.version ?? '--'}
|
||||||
|
</span></div>
|
||||||
|
<div><strong>${t('views.monitor.topology.serialNum')}:</strong><span>
|
||||||
|
${neState.sn ?? '--'}
|
||||||
|
</span></div>
|
||||||
|
<div><strong>${t('views.monitor.topology.expiryDate')}:</strong><span>
|
||||||
|
${neState.expire ?? '--'}
|
||||||
|
</span></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有多个网元,聚合显示
|
||||||
|
let content = `
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 300px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div><strong>${t('views.monitor.topology.state')}:</strong><span>
|
||||||
|
${
|
||||||
|
neState.online
|
||||||
|
? t('views.monitor.topology.normalcy')
|
||||||
|
: t('views.monitor.topology.exceptions')
|
||||||
|
}
|
||||||
|
</span></div>
|
||||||
|
<div><strong>${t('views.monitor.topology.refreshTime')}:</strong><span>
|
||||||
|
${neState.refreshTime ?? '--'}
|
||||||
|
</span></div>
|
||||||
|
<div>========================</div>`;
|
||||||
|
|
||||||
|
// 为每个同类型网元添加信息
|
||||||
|
sameTypeNes.forEach((ne: any, index: number) => {
|
||||||
|
// 获取该网元的状态信息
|
||||||
|
const neStateInfo = neStateMap?.[ne.neId] ||
|
||||||
|
(ne.neId === neState.neId ? neState : {});
|
||||||
|
|
||||||
|
content += `
|
||||||
|
<div style="margin-top: 8px;"><strong>${t('views.monitor.topology.name')}:${ne.neName || id + '_' + ne.neId}</strong></div>
|
||||||
|
<div><strong>ID:</strong><span>${ne.neId || '--'}</span></div>
|
||||||
|
<div><strong>IP:</strong><span>${neStateInfo.neIP || ne.neIP || '--'}</span></div>
|
||||||
|
<div><strong>${t('views.monitor.topology.version')}:</strong><span>
|
||||||
|
${neStateInfo.version || ne.version || '--'}
|
||||||
|
</span></div>
|
||||||
|
<div><strong>${t('views.monitor.topology.serialNum')}:</strong><span>
|
||||||
|
${neStateInfo.sn || ne.sn || '--'}
|
||||||
|
</span></div>
|
||||||
|
<div><strong>${t('views.monitor.topology.expiryDate')}:</strong><span>
|
||||||
|
${neStateInfo.expire || ne.expire || '--'}
|
||||||
|
</span></div>
|
||||||
|
<div><strong>${t('views.monitor.topology.state')}:</strong><span>
|
||||||
|
${
|
||||||
|
neStateInfo.online !== undefined
|
||||||
|
? (neStateInfo.online
|
||||||
|
? t('views.monitor.topology.normalcy')
|
||||||
|
: t('views.monitor.topology.exceptions'))
|
||||||
|
: 'undefined'
|
||||||
|
}
|
||||||
|
</span></div>
|
||||||
|
${index < sameTypeNes.length - 1 ? '<div>------------------------</div>' : ''}
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
content += '</div>';
|
||||||
|
return content;
|
||||||
|
},
|
||||||
|
itemTypes: ['node'],
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**图数据渲染 */
|
||||||
|
function handleRanderGraph(
|
||||||
|
container: HTMLElement | undefined,
|
||||||
|
data: GraphData
|
||||||
|
) {
|
||||||
|
if (!container) return;
|
||||||
|
const { clientHeight, clientWidth } = container;
|
||||||
|
|
||||||
|
edgeLineAnimateState();
|
||||||
|
nodeImageAnimateState();
|
||||||
|
|
||||||
|
const graph = new Graph({
|
||||||
|
container: container,
|
||||||
|
width: clientWidth,
|
||||||
|
height: clientHeight - 36,
|
||||||
|
fitCenter: true,
|
||||||
|
fitView: true,
|
||||||
|
fitViewPadding: [20],
|
||||||
|
autoPaint: true,
|
||||||
|
modes: {
|
||||||
|
// default: ['drag-canvas', 'zoom-canvas'],
|
||||||
|
},
|
||||||
|
groupByTypes: false,
|
||||||
|
nodeStateStyles: {
|
||||||
|
selected: {
|
||||||
|
fill: 'transparent',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [graphNodeTooltip],
|
||||||
|
animate: true, // 是否使用动画过度,默认为 false
|
||||||
|
animateCfg: {
|
||||||
|
duration: 500, // Number,一次动画的时长
|
||||||
|
easing: 'linearEasing', // String,动画函数
|
||||||
|
},
|
||||||
|
});
|
||||||
|
graph.data(data);
|
||||||
|
graph.render();
|
||||||
|
|
||||||
|
|
||||||
|
graphG6.value = graph;
|
||||||
|
|
||||||
|
// 创建 ResizeObserver 实例
|
||||||
|
var observer = new ResizeObserver(function (entries) {
|
||||||
|
// 当元素大小发生变化时触发回调函数
|
||||||
|
entries.forEach(function (entry) {
|
||||||
|
if (!graphG6.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
graphG6.value.changeSize(
|
||||||
|
entry.contentRect.width,
|
||||||
|
entry.contentRect.height - 20
|
||||||
|
);
|
||||||
|
graphG6.value.fitCenter();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// 监听元素大小变化
|
||||||
|
observer.observe(container);
|
||||||
|
|
||||||
|
return graph;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取图组数据渲染到画布
|
||||||
|
* @param reload 是否重载数据
|
||||||
|
*/
|
||||||
|
function fnGraphDataLoad(reload: boolean = false) {
|
||||||
|
Promise.all([
|
||||||
|
getGraphData(graphState.group),
|
||||||
|
listAllNeInfo({
|
||||||
|
bandStatus: false,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
.then(resArr => {
|
||||||
|
const graphRes = resArr[0];
|
||||||
|
const neRes = resArr[1];
|
||||||
|
if (
|
||||||
|
graphRes.code === RESULT_CODE_SUCCESS &&
|
||||||
|
Array.isArray(graphRes.data.nodes) &&
|
||||||
|
graphRes.data.nodes.length > 0 &&
|
||||||
|
neRes.code === RESULT_CODE_SUCCESS &&
|
||||||
|
Array.isArray(neRes.data) &&
|
||||||
|
neRes.data.length > 0
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
graphData: graphRes.data,
|
||||||
|
neList: neRes.data,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
message.warning({
|
||||||
|
content: t('views.monitor.topology.noData'),
|
||||||
|
duration: 5,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if (!res) return;
|
||||||
|
const { combos, edges, nodes } = res.graphData;
|
||||||
|
|
||||||
|
// 按网元类型分组
|
||||||
|
const neTypeMap = new Map();
|
||||||
|
res.neList.forEach(ne => {
|
||||||
|
if (!ne.neType) return;
|
||||||
|
if (!neTypeMap.has(ne.neType)) {
|
||||||
|
neTypeMap.set(ne.neType, []);
|
||||||
|
}
|
||||||
|
neTypeMap.get(ne.neType).push(ne);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 节点过滤
|
||||||
|
const nf: Record<string, any>[] = nodes.filter(
|
||||||
|
(node: Record<string, any>) => {
|
||||||
|
Reflect.set(node, 'neState', { online: false });
|
||||||
|
Reflect.set(node, 'neStateMap', {}); // 初始化状态映射
|
||||||
|
|
||||||
|
// 图片路径处理
|
||||||
|
if (node.img) node.img = parseBasePath(node.img);
|
||||||
|
if (node.icon.show && node.icon?.img){
|
||||||
|
node.icon.img = parseBasePath(node.icon.img);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历是否有网元数据
|
||||||
|
const nodeID: string = node.id;
|
||||||
|
|
||||||
|
// 处理非网元节点
|
||||||
|
if (notNeNodes.includes(nodeID)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
//(neTypeMap.get(nodeID),nodeID,node.neState)
|
||||||
|
// 处理网元节点
|
||||||
|
if (neTypeMap.has(nodeID)) {
|
||||||
|
// all NeInfo
|
||||||
|
Reflect.set(node, 'neInfoList', neTypeMap.get(nodeID));
|
||||||
|
|
||||||
|
Reflect.set(node, 'neInfo', neTypeMap.get(nodeID)[0] || {});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 边过滤
|
||||||
|
const ef: Record<string, any>[] = edges.filter(
|
||||||
|
(edge: Record<string, any>) => {
|
||||||
|
const edgeSource: string = edge.source;
|
||||||
|
const edgeTarget: string = edge.target;
|
||||||
|
const hasNeS = nf.some(n => n.id === edgeSource);
|
||||||
|
const hasNeT = nf.some(n => n.id === edgeTarget);
|
||||||
|
if (hasNeS && hasNeT) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (hasNeS && notNeNodes.includes(edgeTarget)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (hasNeT && notNeNodes.includes(edgeSource)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 分组过滤
|
||||||
|
combos.forEach((combo: Record<string, any>) => {
|
||||||
|
const comboChildren: Record<string, any>[] = combo.children;
|
||||||
|
combo.children = comboChildren.filter(c => nf.some(n => n.id === c.id));
|
||||||
|
return combo;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 图数据
|
||||||
|
graphState.data = { combos, edges: ef, nodes: nf };
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (graphState.data.length < 0) return;
|
||||||
|
// 重载数据
|
||||||
|
if (reload) {
|
||||||
|
graphG6.value.read(graphState.data);
|
||||||
|
} else {
|
||||||
|
handleRanderGraph(graphG6Dom.value, graphState.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fnGraphDataLoad(false);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="graphG6Dom" class="chart"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
291
src/views/dashboard/overview2/components/UPFFlow/index.vue
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref, watch } from 'vue';
|
||||||
|
import { listKPIData } from '@/api/perfManage/goldTarget';
|
||||||
|
import * as echarts from 'echarts/core';
|
||||||
|
import {
|
||||||
|
TooltipComponent,
|
||||||
|
TooltipComponentOption,
|
||||||
|
GridComponent,
|
||||||
|
GridComponentOption,
|
||||||
|
LegendComponent,
|
||||||
|
LegendComponentOption,
|
||||||
|
} from 'echarts/components';
|
||||||
|
import { LineChart, LineSeriesOption } from 'echarts/charts';
|
||||||
|
import { UniversalTransition } from 'echarts/features';
|
||||||
|
import { CanvasRenderer } from 'echarts/renderers';
|
||||||
|
import { markRaw } from 'vue';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||||
|
import { upfFlowData, upfFlowParse } from '../../hooks/useUPFTotalFlow';
|
||||||
|
import { upfWhoId } from '../../hooks/useWS';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
echarts.use([
|
||||||
|
TooltipComponent,
|
||||||
|
GridComponent,
|
||||||
|
LegendComponent,
|
||||||
|
LineChart,
|
||||||
|
CanvasRenderer,
|
||||||
|
UniversalTransition,
|
||||||
|
]);
|
||||||
|
|
||||||
|
type EChartsOption = echarts.ComposeOption<
|
||||||
|
| TooltipComponentOption
|
||||||
|
| GridComponentOption
|
||||||
|
| LegendComponentOption
|
||||||
|
| LineSeriesOption
|
||||||
|
>;
|
||||||
|
|
||||||
|
/**图DOM节点实例对象 */
|
||||||
|
const upfFlow = ref<HTMLElement | undefined>(undefined);
|
||||||
|
|
||||||
|
/**图实例对象 */
|
||||||
|
const upfFlowChart = ref<any>(null);
|
||||||
|
|
||||||
|
function fnDesign(container: HTMLElement | undefined, option: EChartsOption) {
|
||||||
|
if (!container) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!upfFlowChart.value) {
|
||||||
|
upfFlowChart.value = markRaw(echarts.init(container, 'light'));
|
||||||
|
}
|
||||||
|
|
||||||
|
option && upfFlowChart.value.setOption(option);
|
||||||
|
|
||||||
|
// 创建 ResizeObserver 实例
|
||||||
|
var observer = new ResizeObserver(entries => {
|
||||||
|
if (upfFlowChart.value) {
|
||||||
|
upfFlowChart.value.resize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 监听元素大小变化
|
||||||
|
observer.observe(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
//渲染速率图
|
||||||
|
function handleRanderChart() {
|
||||||
|
const { lineXTime, lineYUp, lineYDown } = upfFlowData.value;
|
||||||
|
|
||||||
|
var yAxisSeries: any = [
|
||||||
|
{
|
||||||
|
name: t('views.dashboard.overview.upfFlow.up'),
|
||||||
|
type: 'line',
|
||||||
|
color: 'rgba(250, 219, 20)',
|
||||||
|
smooth: true,
|
||||||
|
areaStyle: {
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: 'rgba(250, 219, 20, .5)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: 'rgba(250, 219, 20, 0.5)',
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
shadowBlur: 10,
|
||||||
|
},
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 5,
|
||||||
|
formatter: '{b}',
|
||||||
|
data: lineYUp,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t('views.dashboard.overview.upfFlow.down'),
|
||||||
|
type: 'line',
|
||||||
|
color: 'rgba(92, 123, 217)',
|
||||||
|
smooth: true,
|
||||||
|
areaStyle: {
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: 'rgba(92, 123, 217, .5)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: 'rgba(92, 123, 217, 0.5)',
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
shadowBlur: 10,
|
||||||
|
},
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 5,
|
||||||
|
formatter: '{b}',
|
||||||
|
data: lineYDown,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const optionData: EChartsOption = {
|
||||||
|
tooltip: {
|
||||||
|
show: true, //是否显示提示框组件
|
||||||
|
trigger: 'axis',
|
||||||
|
//formatter:'{a0}:{c0}<br>{a1}:{c1}'
|
||||||
|
formatter: function (param: any) {
|
||||||
|
var tip = '';
|
||||||
|
if (param !== null && param.length > 0) {
|
||||||
|
tip += param[0].name + '<br />';
|
||||||
|
for (var i = 0; i < param.length; i++) {
|
||||||
|
tip +=
|
||||||
|
param[i].marker +
|
||||||
|
param[i].seriesName +
|
||||||
|
': ' +
|
||||||
|
param[i].value +
|
||||||
|
'<br />';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tip;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: yAxisSeries.map((s: any) => s.name),
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: 'rgb(0,253,255,0.6)',
|
||||||
|
},
|
||||||
|
left: 'center', // 设置图例居中
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: '14%',
|
||||||
|
left: '4%',
|
||||||
|
right: '4%',
|
||||||
|
bottom: '16%',
|
||||||
|
containLabel: true,
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: false,
|
||||||
|
data: lineXTime,
|
||||||
|
axisLabel: {
|
||||||
|
formatter: function (params: any) {
|
||||||
|
return params;
|
||||||
|
},
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: 'rgb(0,253,255,0.6)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
name: '(Mbps)',
|
||||||
|
nameTextStyle: {
|
||||||
|
fontSize: 12, // 设置文字距离x轴的距离
|
||||||
|
padding: [0, -10, 0, 0], // 设置名称在x轴方向上的偏移
|
||||||
|
},
|
||||||
|
type: 'value',
|
||||||
|
// splitNumber: 4,
|
||||||
|
min: 0,
|
||||||
|
//max: 300,
|
||||||
|
axisLabel: {
|
||||||
|
formatter: '{value}',
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: 'rgb(23,255,243,0.3)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: 'rgb(0,253,255,0.6)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
series: yAxisSeries,
|
||||||
|
};
|
||||||
|
fnDesign(upfFlow.value, optionData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**查询初始UPF数据 */
|
||||||
|
function fnGetInitData() {
|
||||||
|
// 查询5分钟前的
|
||||||
|
const nowDate = new Date().getTime();
|
||||||
|
|
||||||
|
listKPIData({
|
||||||
|
neType: 'UPF',
|
||||||
|
neId: upfWhoId.value,
|
||||||
|
startTime: nowDate - 5 * 60 * 1000,
|
||||||
|
endTime: nowDate,
|
||||||
|
|
||||||
|
interval: 5, // 5秒
|
||||||
|
sortField: 'timeGroup',
|
||||||
|
sortOrder: 'asc',
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||||
|
for (const item of res.data) {
|
||||||
|
upfFlowParse(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
handleRanderChart();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => upfFlowData.value,
|
||||||
|
v => {
|
||||||
|
if (upfFlowChart.value == null) return;
|
||||||
|
upfFlowChart.value.setOption({
|
||||||
|
xAxis: {
|
||||||
|
data: v.lineXTime,
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
data: v.lineYUp,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: v.lineYDown,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fnGetInitData();
|
||||||
|
|
||||||
|
// setInterval(() => {
|
||||||
|
// upfFlowData.value.lineXTime.push(parseDateToStr(new Date()));
|
||||||
|
// const upN3 = parseSizeFromKbs(+145452, 5);
|
||||||
|
// upfFlowData.value.lineYUp.push(upN3[0]);
|
||||||
|
// const downN6 = parseSizeFromKbs(+232343, 5);
|
||||||
|
// upfFlowData.value.lineYDown.push(downN6[0]);
|
||||||
|
|
||||||
|
// upfFlowChart.value.setOption({
|
||||||
|
// xAxis: {
|
||||||
|
// data: upfFlowData.value.lineXTime,
|
||||||
|
// },
|
||||||
|
// series: [
|
||||||
|
// {
|
||||||
|
// data: upfFlowData.value.lineYUp,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// data: upfFlowData.value.lineYDown,
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// });
|
||||||
|
// }, 5000);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="upfFlow" class="chart-container"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.chart-container {
|
||||||
|
/* 设置图表容器大小和位置 */
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
324
src/views/dashboard/overview2/components/UserActivity/index.vue
Normal file
@@ -0,0 +1,324 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { parseDuration, parseDateToStr } from '@/utils/date-utils';
|
||||||
|
import { eventData, eventId } from '../../hooks/useUserActivity';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
import useDictStore from '@/store/modules/dict';
|
||||||
|
import { onMounted, reactive } from 'vue';
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { getDict } = useDictStore();
|
||||||
|
|
||||||
|
/**字典数据 */
|
||||||
|
let dict: {
|
||||||
|
/**CDR SIP响应代码类别类型 */
|
||||||
|
cdrSipCode: DictType[];
|
||||||
|
/**CDR 呼叫类型 */
|
||||||
|
cdrCallType: DictType[];
|
||||||
|
/**UE 事件认证代码类型 */
|
||||||
|
ueAauthCode: DictType[];
|
||||||
|
/**UE 事件类型 */
|
||||||
|
ueEventType: DictType[];
|
||||||
|
/**UE 事件CM状态 */
|
||||||
|
ueEventCmState: DictType[];
|
||||||
|
/**CDR SIP响应代码类别类型 */
|
||||||
|
cdrSipCodeCause: DictType[];
|
||||||
|
} = reactive({
|
||||||
|
cdrSipCode: [],
|
||||||
|
cdrCallType: [],
|
||||||
|
ueAauthCode: [],
|
||||||
|
ueEventType: [],
|
||||||
|
ueEventCmState: [],
|
||||||
|
cdrSipCodeCause: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 初始字典数据
|
||||||
|
Promise.allSettled([
|
||||||
|
getDict('cdr_sip_code'),
|
||||||
|
getDict('cdr_call_type'),
|
||||||
|
getDict('ue_auth_code'),
|
||||||
|
getDict('ue_event_type'),
|
||||||
|
getDict('ue_event_cm_state'),
|
||||||
|
getDict('cdr_sip_code_cause'),
|
||||||
|
]).then(resArr => {
|
||||||
|
if (resArr[0].status === 'fulfilled') {
|
||||||
|
dict.cdrSipCode = resArr[0].value;
|
||||||
|
}
|
||||||
|
if (resArr[1].status === 'fulfilled') {
|
||||||
|
dict.cdrCallType = resArr[1].value;
|
||||||
|
}
|
||||||
|
if (resArr[2].status === 'fulfilled') {
|
||||||
|
dict.ueAauthCode = resArr[2].value;
|
||||||
|
}
|
||||||
|
if (resArr[3].status === 'fulfilled') {
|
||||||
|
dict.ueEventType = resArr[3].value;
|
||||||
|
}
|
||||||
|
if (resArr[4].status === 'fulfilled') {
|
||||||
|
dict.ueEventCmState = resArr[4].value;
|
||||||
|
}
|
||||||
|
if (resArr[5].status === 'fulfilled') {
|
||||||
|
dict.cdrSipCodeCause = resArr[5].value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="activty">
|
||||||
|
<template v-for="item in eventData" :key="item.eId">
|
||||||
|
<!-- UE事件AMF -->
|
||||||
|
<div
|
||||||
|
class="card-ue"
|
||||||
|
:class="{ active: item.eId === eventId }"
|
||||||
|
v-if="item.eType === 'amf_ue'"
|
||||||
|
>
|
||||||
|
<div class="card-ue-item">
|
||||||
|
<div>
|
||||||
|
{{ t('views.dashboard.overview.userActivity.type') }}:
|
||||||
|
<span>
|
||||||
|
<DictTag :options="dict.ueEventType" :value="item.type" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
IMSI: <span :title="item.data.imsi">{{ item.data.imsi }}</span>
|
||||||
|
</div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-ue-w33" v-if="item.type === 'auth-result'">
|
||||||
|
<div>
|
||||||
|
GNB ID: <span>{{ item.data.gNBID }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Cell ID: <span>{{ item.data.cellID }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
TAC ID: <span>{{ item.data.tacID }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ t('views.dashboard.overview.userActivity.time') }}:
|
||||||
|
<template v-if="item.data?.time">
|
||||||
|
{{ parseDateToStr(item.data.time) }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="item.data?.timestamp">
|
||||||
|
{{ parseDateToStr(+item.data.timestamp * 1000) }}
|
||||||
|
</template>
|
||||||
|
<template v-else> - </template>
|
||||||
|
</div>
|
||||||
|
<div v-if="item.type === 'auth-result'">
|
||||||
|
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||||
|
<span>
|
||||||
|
<DictTag :options="dict.ueAauthCode" :value="item.data.result" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="item.type === 'detach'">
|
||||||
|
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||||
|
<span>{{ t('views.dashboard.overview.userActivity.resultOK') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-ue-w33" v-if="item.type === 'cm-state'">
|
||||||
|
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||||
|
<span>
|
||||||
|
<DictTag :options="dict.ueEventCmState" :value="item.data.result" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- UE事件MME -->
|
||||||
|
<div
|
||||||
|
class="card-ue"
|
||||||
|
:class="{ active: item.eId === eventId }"
|
||||||
|
v-if="item.eType === 'mme_ue'"
|
||||||
|
>
|
||||||
|
<div class="card-ue-item">
|
||||||
|
<div>
|
||||||
|
{{ t('views.dashboard.overview.userActivity.type') }}:
|
||||||
|
<span v-if="item.type === 'cm-state'">
|
||||||
|
{{
|
||||||
|
dict.ueEventType
|
||||||
|
.find(s => s.value === item.type)
|
||||||
|
?.label.replace('CM', 'ECM')
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
<DictTag :options="dict.ueEventType" :value="item.type" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
IMSI: <span :title="item.data?.imsi">{{ item.data?.imsi }}</span>
|
||||||
|
</div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-ue-w33" v-if="item.type === 'auth-result'">
|
||||||
|
<div>
|
||||||
|
ENB ID: <span>{{ item.data.eNBID }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Cell ID: <span>{{ item.data.cellID }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
TAC ID: <span>{{ item.data.tacID }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ t('views.dashboard.overview.userActivity.time') }}:
|
||||||
|
<template v-if="item.data?.time">
|
||||||
|
{{ parseDateToStr(item.data.time) }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="item.data?.timestamp">
|
||||||
|
{{
|
||||||
|
typeof item.data?.timestamp === 'number'
|
||||||
|
? parseDateToStr(+item.data?.timestamp * 1000)
|
||||||
|
: parseDateToStr(item.data?.timestamp)
|
||||||
|
}}
|
||||||
|
</template>
|
||||||
|
<template v-else> - </template>
|
||||||
|
</div>
|
||||||
|
<div v-if="item.type === 'auth-result'">
|
||||||
|
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||||
|
<span>
|
||||||
|
<DictTag :options="dict.ueAauthCode" :value="item.data.result" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="item.type === 'detach'">
|
||||||
|
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||||
|
<span>{{ t('views.dashboard.overview.userActivity.resultOK') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-ue-w33" v-if="item.type === 'cm-state'">
|
||||||
|
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||||
|
<span>
|
||||||
|
<DictTag :options="dict.ueEventCmState" :value="item.data.result" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.activty {
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
height: 94%;
|
||||||
|
color: #61a8ff;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
& .card-ue {
|
||||||
|
border: 1px #61a8ff solid;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
margin-bottom: 0.3rem;
|
||||||
|
line-height: 1rem;
|
||||||
|
& span {
|
||||||
|
color: #68d8fe;
|
||||||
|
}
|
||||||
|
&-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
& > div {
|
||||||
|
width: 50%;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-align: start;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&-w33 {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
& > div {
|
||||||
|
width: 33%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .card-cdr {
|
||||||
|
border: 1px #61a8ff solid;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
margin-bottom: 0.3rem;
|
||||||
|
line-height: 1rem;
|
||||||
|
& span {
|
||||||
|
color: #68d8fe;
|
||||||
|
}
|
||||||
|
&-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
& > div {
|
||||||
|
flex: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-align: start;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .active {
|
||||||
|
color: #faad14;
|
||||||
|
border: 1px #faad14 solid;
|
||||||
|
animation: backInRight 0.3s alternate;
|
||||||
|
& span {
|
||||||
|
color: #faad14;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 兼容当行显示字内容 */
|
||||||
|
@media (max-width: 1720px) {
|
||||||
|
& .card-cdr {
|
||||||
|
&-item {
|
||||||
|
display: block;
|
||||||
|
& > div {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
& .card-ue {
|
||||||
|
&-item {
|
||||||
|
display: block;
|
||||||
|
& > div {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 修改滚动条的样式 */
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 8px; /* 设置滚动条宽度 */
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background-color: #101129; /* 设置滚动条轨道背景颜色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #28293f; /* 设置滚动条滑块颜色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: #68d8fe; /* 设置鼠标悬停时滚动条滑块颜色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes backInRight {
|
||||||
|
0% {
|
||||||
|
opacity: 0.7;
|
||||||
|
-webkit-transform: translateX(2000px) scale(0.7);
|
||||||
|
transform: translateX(2000px) scale(0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
80% {
|
||||||
|
opacity: 0.7;
|
||||||
|
-webkit-transform: translateX(0) scale(0.7);
|
||||||
|
transform: translateX(0) scale(0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-transform: scale(1);
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
399
src/views/dashboard/overview2/css/index.css
Normal file
@@ -0,0 +1,399 @@
|
|||||||
|
.viewport {
|
||||||
|
/* 限定大小 */
|
||||||
|
min-width: 1024px;
|
||||||
|
max-width: 1920px;
|
||||||
|
min-height: 780px;
|
||||||
|
margin: 0 auto;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
padding: 5rem 0.833rem 0;
|
||||||
|
line-height: 1.15;
|
||||||
|
background-image: url(../images/bj.png);
|
||||||
|
height: 100vh;
|
||||||
|
margin-bottom: -20px;
|
||||||
|
background-size:80% 80%;
|
||||||
|
background-attachment:fixed;
|
||||||
|
-webkit-background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.column {
|
||||||
|
flex: 3;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 边框 */
|
||||||
|
.panel {
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 2px solid rgba(252, 252, 252, 0);
|
||||||
|
border-width: 2.125rem 1.583rem 0.875rem 5.5rem;
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 0.833rem;
|
||||||
|
}
|
||||||
|
.panel .inner {
|
||||||
|
/* 装内容 */
|
||||||
|
/* height: 60px; */
|
||||||
|
position: absolute;
|
||||||
|
top: -2.125rem;
|
||||||
|
right: -1.583rem;
|
||||||
|
bottom: -0.875rem;
|
||||||
|
left: -5.5rem;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
}
|
||||||
|
.panel h3 {
|
||||||
|
font-size: 0.833rem;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leftright {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 2.5rem;
|
||||||
|
background: url(../images/title.png) no-repeat center center;
|
||||||
|
background-size: 100%;
|
||||||
|
color: #4c9bfd;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 0.5rem 1.2rem;
|
||||||
|
border-radius: 0.7rem 0.7rem 0 0;
|
||||||
|
margin: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-shadow: 0 1px 4px #000a;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
/* 保证内容不换行且居中 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.leftright .title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 1rem;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.centerStyle {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 2.5rem;
|
||||||
|
background: url(../images/title.png) no-repeat center center;
|
||||||
|
background-size: 90%;
|
||||||
|
color: #4c9bfd;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 0.5rem 1.2rem;
|
||||||
|
border-radius: 0.7rem 0.7rem 0 0;
|
||||||
|
margin: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-shadow: 0 1px 4px #000a;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
/* 保证内容不换行且居中 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.centerStyle .title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 1rem;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 总览标题 */
|
||||||
|
.brand {
|
||||||
|
background-image: url(../images/newBrand.png);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center center;
|
||||||
|
position: absolute;
|
||||||
|
top: 0.833rem;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.brand .brand-title {
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
font-weight: 600;
|
||||||
|
padding-top: 1rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.brand .brand-desc {
|
||||||
|
color: #d9d9d9;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 实时流量 */
|
||||||
|
.upfFlow {
|
||||||
|
/* min-height: 16rem; */
|
||||||
|
height: 40%;
|
||||||
|
}
|
||||||
|
.upfFlow .inner .chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin-top: 0rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 网络拓扑 */
|
||||||
|
.topology {
|
||||||
|
/* min-height: 27.8rem; */
|
||||||
|
height: 46.4%;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.topology .inner h3 {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
.topology .inner h3 .normal {
|
||||||
|
color: #52c41a;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
.topology .inner h3 .abnormal {
|
||||||
|
color: #f5222d;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
.topology .inner .chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 概览区域 */
|
||||||
|
.skim {
|
||||||
|
/* min-height: 7.78rem; */
|
||||||
|
height: 14.4%;
|
||||||
|
}
|
||||||
|
.skim .inner .data {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
height: 90%;
|
||||||
|
}
|
||||||
|
.skim .inner .data .item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: baseline;
|
||||||
|
width: 33%;
|
||||||
|
}
|
||||||
|
.skim .inner .data .item div {
|
||||||
|
font-size: 1.467rem;
|
||||||
|
color: #fff;
|
||||||
|
margin-bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
line-height: 2rem;
|
||||||
|
}
|
||||||
|
.skim .inner .data .item span {
|
||||||
|
color: #4c9bfd;
|
||||||
|
font-size: 0.833rem;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
line-height: 2rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-align: start;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.skim .inner .data .item span::before {
|
||||||
|
content: ' ';
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 0;
|
||||||
|
background-image: linear-gradient(to right, #fff, #fff0);
|
||||||
|
height: 1px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 概览区域 衍生基站信息 */
|
||||||
|
.skim.base {
|
||||||
|
height: 11.25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skim.base .inner .data {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
height: 75%;
|
||||||
|
}
|
||||||
|
.skim.base .inner .data .item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: baseline;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 用户行为 */
|
||||||
|
.userActivity {
|
||||||
|
/* min-height: 35.8rem; */
|
||||||
|
height: 54.6%;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.userActivity .inner .chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 流量统计 */
|
||||||
|
.upfFlowTotal1 {
|
||||||
|
/* min-height: 7.5rem; */
|
||||||
|
height: 14.4%;
|
||||||
|
}
|
||||||
|
.upfFlowTotal1 .inner h3 {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.upfFlowTotal1 .inner h3 .filter {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.upfFlowTotal1 .inner h3 .filter span {
|
||||||
|
display: block;
|
||||||
|
height: 0.75rem;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 0 0.75rem;
|
||||||
|
color: #1950c4;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
border-right: 0.083rem solid #00f2f1;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.upfFlowTotal1 .inner h3 .filter span:first-child {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
.upfFlowTotal1 .inner h3 .filter span:last-child {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
.upfFlowTotal1 .inner h3 .filter span.active {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.833rem;
|
||||||
|
}
|
||||||
|
.upfFlowTotal1 .inner .chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin-top: 0.1rem;
|
||||||
|
}
|
||||||
|
.upfFlowTotal1 .inner .chart .data {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
height: 60%;
|
||||||
|
}
|
||||||
|
.upfFlowTotal1 .inner .chart .data .item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
.upfFlowTotal1 .inner .chart .data .item h4 {
|
||||||
|
font-size: 1.467rem;
|
||||||
|
color: #fff;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.upfFlowTotal1 .inner .chart .data .item span {
|
||||||
|
color: #4c9bfd;
|
||||||
|
font-size: 0.867rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 流量统计 */
|
||||||
|
.upfFlowTotal {
|
||||||
|
/* min-height: 7.5rem; */
|
||||||
|
height: 14.4%;
|
||||||
|
}
|
||||||
|
.upfFlowTotal .inner h3 {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.upfFlowTotal .inner h3 .filter {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.upfFlowTotal .inner h3 .filter span {
|
||||||
|
display: block;
|
||||||
|
height: 0.75rem;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 0 0.75rem;
|
||||||
|
color: #1950c4;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
border-right: 0.083rem solid #00f2f1;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.upfFlowTotal .inner h3 .filter span:first-child {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
.upfFlowTotal .inner h3 .filter span:last-child {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
.upfFlowTotal .inner h3 .filter span.active {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.833rem;
|
||||||
|
}
|
||||||
|
.upfFlowTotal .inner .chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin-top: 0.1rem;
|
||||||
|
}
|
||||||
|
.upfFlowTotal .inner .chart .data {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
height: 60%;
|
||||||
|
}
|
||||||
|
.upfFlowTotal .inner .chart .data .item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
.upfFlowTotal .inner .chart .data .item h4 {
|
||||||
|
font-size: 1.467rem;
|
||||||
|
color: #fff;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.upfFlowTotal .inner .chart .data .item span {
|
||||||
|
color: #4c9bfd;
|
||||||
|
font-size: 0.867rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 资源情况 */
|
||||||
|
.resources {
|
||||||
|
/* min-height: 18rem; */
|
||||||
|
height: 24.4%;
|
||||||
|
}
|
||||||
|
.resources .inner .chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 告警统计 */
|
||||||
|
.alarmType {
|
||||||
|
/* min-height: 25rem; */
|
||||||
|
height: 35%;
|
||||||
|
}
|
||||||
|
.alarmType .inner .chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 跳转鼠标悬浮 */
|
||||||
|
.toRouter:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
.toRouter:hover > *,
|
||||||
|
.toRouter:hover > * > * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
197
src/views/dashboard/overview2/hooks/useTopology.ts
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
import { parseDateToStr } from '@/utils/date-utils';
|
||||||
|
import { computed, reactive, ref } from 'vue';
|
||||||
|
|
||||||
|
/**非网元元素 */
|
||||||
|
export const notNeNodes = [
|
||||||
|
'5GC',
|
||||||
|
'DN',
|
||||||
|
'UE',
|
||||||
|
'Base',
|
||||||
|
'lan',
|
||||||
|
'lan1',
|
||||||
|
'lan2',
|
||||||
|
'lan3',
|
||||||
|
'lan4',
|
||||||
|
'lan5',
|
||||||
|
'lan6',
|
||||||
|
'lan7',
|
||||||
|
'LAN',
|
||||||
|
'NR',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**图状态 */
|
||||||
|
export const graphState = reactive<Record<string, any>>({
|
||||||
|
/**当前图组名 */
|
||||||
|
group: '5GC System Architecture',
|
||||||
|
/**图数据 */
|
||||||
|
data: {
|
||||||
|
combos: [],
|
||||||
|
edges: [],
|
||||||
|
nodes: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**图实例对象 */
|
||||||
|
export const graphG6 = ref<any>(null);
|
||||||
|
|
||||||
|
/**图点击选择 */
|
||||||
|
export const graphNodeClickID = ref<string>('UPF_001');
|
||||||
|
|
||||||
|
/**图节点网元信息状态 */
|
||||||
|
export const graphNodeState = computed(() =>{
|
||||||
|
return graphState.data.nodes.map((item: any) => ({
|
||||||
|
id: item.id,
|
||||||
|
label: item.label,
|
||||||
|
neInfo: item.neInfo,
|
||||||
|
neState: item.neState,
|
||||||
|
neInfoList:item.neInfoList,
|
||||||
|
neStateMap: item.neStateMap,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
/**图节点网元状态数量 */
|
||||||
|
export const graphNodeStateNum = computed(() => {
|
||||||
|
let normal = 0;
|
||||||
|
let abnormal = 0;
|
||||||
|
for (const item of graphState.data.nodes) {
|
||||||
|
const neId = item.neState.neId;
|
||||||
|
if (neId) {
|
||||||
|
if (item.neState.online) {
|
||||||
|
normal += 1;
|
||||||
|
} else {
|
||||||
|
abnormal += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [normal, abnormal];
|
||||||
|
});
|
||||||
|
|
||||||
|
/**网元状态请求标记 */
|
||||||
|
export const neStateRequestMap = ref<Map<string, boolean>>(new Map());
|
||||||
|
|
||||||
|
/**neStateParse 网元状态 数据解析 */
|
||||||
|
export function neStateParse(neType: string, data: Record<string, any>,neId: string) {
|
||||||
|
// console.log('neStateParse',neType, data, neId);
|
||||||
|
|
||||||
|
const { combos, edges, nodes } = graphState.data;
|
||||||
|
|
||||||
|
const node = nodes.find((item: Record<string, any>) => item.id === neType);
|
||||||
|
//console.log('neStateParse',node);
|
||||||
|
|
||||||
|
if (!node) return;
|
||||||
|
|
||||||
|
// 初始化状态映射
|
||||||
|
if (!node.neStateMap) node.neStateMap = {};
|
||||||
|
|
||||||
|
// 更新网元状态
|
||||||
|
const newNeState :any = {
|
||||||
|
...data, // 先展开data对象
|
||||||
|
refreshTime: parseDateToStr(data.refreshTime, 'HH:mm:ss'),
|
||||||
|
online: !!data.cpu,
|
||||||
|
neId: neId
|
||||||
|
};
|
||||||
|
// 如果是001,更新节点状态。neInfo为主要的网元信息
|
||||||
|
if (node.neInfo && node.neInfo.neId === neId) {
|
||||||
|
Object.assign(node.neState, newNeState);
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log(node.neState)
|
||||||
|
// 无论是否为主要网元,都更新状态映射
|
||||||
|
node.neStateMap[neId] = {...newNeState};
|
||||||
|
// 通过 ID 查询节点实例
|
||||||
|
const item = graphG6.value.findById(node.id);
|
||||||
|
if (item) {
|
||||||
|
// 检查当前节点下所有网元的状态
|
||||||
|
const allStates = Object.values(node.neStateMap);
|
||||||
|
// 判断状态颜色
|
||||||
|
let stateColor = '#52c41a'; // 默认绿色(所有网元都正常)
|
||||||
|
if (allStates.some((state: any) => !state.online)) {
|
||||||
|
// 如果有任何一个网元不正常
|
||||||
|
stateColor = allStates.every((state: any) => !state.online) ? '#f5222d' : '#faad14'; // 红色(全部不正常)或黄色(部分不正常)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片类型不能填充
|
||||||
|
if (node.type && node.type.startsWith('image')) {
|
||||||
|
// 更新节点
|
||||||
|
if (node.label !== newNeState.neType) {
|
||||||
|
graphG6.value.updateItem(item, {
|
||||||
|
label: newNeState.neType,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 设置状态
|
||||||
|
graphG6.value.setItemState(item, 'top-right-dot', stateColor);
|
||||||
|
} else {
|
||||||
|
// 更新节点
|
||||||
|
graphG6.value.updateItem(item, {
|
||||||
|
label: newNeState.neType,
|
||||||
|
style: {
|
||||||
|
fill: stateColor, // 填充色
|
||||||
|
stroke: stateColor, // 填充色
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// 设置状态
|
||||||
|
graphG6.value.setItemState(item, 'stroke', newNeState.online);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置边状态
|
||||||
|
for (const edge of edges) {
|
||||||
|
const edgeSource: string = edge.source;
|
||||||
|
const edgeTarget: string = edge.target;
|
||||||
|
const neS = nodes.find((n: any) => n.id === edgeSource);
|
||||||
|
const neT = nodes.find((n: any) => n.id === edgeTarget);
|
||||||
|
// console.log(neS, edgeSource, neT, edgeTarget);
|
||||||
|
|
||||||
|
if (neS && neT) {
|
||||||
|
// 通过 ID 查询节点实例
|
||||||
|
// const item = graphG6.value.findById(edge.id);
|
||||||
|
// console.log(
|
||||||
|
// `${edgeSource} - ${edgeTarget}`,
|
||||||
|
// neS.neState.online && neT.neState.online
|
||||||
|
// );
|
||||||
|
// const stateColor = neS.neState.online && neT.neState.online ? '#000000' : '#ff4d4f'; // 状态颜色
|
||||||
|
// 更新边
|
||||||
|
// graphG6.value.updateItem(item, {
|
||||||
|
// label: `${edgeSource} - ${edgeTarget}`,
|
||||||
|
// style: {
|
||||||
|
// stroke: stateColor, // 填充色
|
||||||
|
// },
|
||||||
|
// labelCfg: {
|
||||||
|
// style: {
|
||||||
|
// fill: '#ffffff', // 标签文本色
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// 设置状态
|
||||||
|
graphG6.value.setItemState(
|
||||||
|
edge.id,
|
||||||
|
'circle-move',
|
||||||
|
neS.neState.online && neT.neState.online
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (neS && notNeNodes.includes(edgeTarget)) {
|
||||||
|
graphG6.value.setItemState(edge.id, 'line-dash', neS.neState.online);
|
||||||
|
}
|
||||||
|
if (neT && notNeNodes.includes(edgeSource)) {
|
||||||
|
graphG6.value.setItemState(edge.id, 'line-dash', neT.neState.online);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 请求标记复位
|
||||||
|
neStateRequestMap.value.set(neType, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**属性复位 */
|
||||||
|
export function topologyReset() {
|
||||||
|
graphState.data = {
|
||||||
|
combos: [],
|
||||||
|
edges: [],
|
||||||
|
nodes: [],
|
||||||
|
};
|
||||||
|
graphG6.value = null;
|
||||||
|
graphNodeClickID.value = 'UPF_001';
|
||||||
|
neStateRequestMap.value = new Map();
|
||||||
|
}
|
||||||
110
src/views/dashboard/overview2/hooks/useUPFTotalFlow.ts
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import { parseDateToStr } from '@/utils/date-utils';
|
||||||
|
import { parseSizeFromBits, parseSizeFromKbs } from '@/utils/parse-utils';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
type FDType = {
|
||||||
|
/**时间 */
|
||||||
|
lineXTime: string[];
|
||||||
|
/**上行 N3 */
|
||||||
|
lineYUp: number[];
|
||||||
|
/**下行 N6 */
|
||||||
|
lineYDown: number[];
|
||||||
|
/**容量 */
|
||||||
|
cap: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**UPF-流量数据 */
|
||||||
|
export const upfFlowData = ref<FDType>({
|
||||||
|
lineXTime: [],
|
||||||
|
lineYUp: [],
|
||||||
|
lineYDown: [],
|
||||||
|
cap: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**UPF-流量数据 数据解析 */
|
||||||
|
export function upfFlowParse(data: Record<string, string>) {
|
||||||
|
upfFlowData.value.lineXTime.push(parseDateToStr(+data['timeGroup'], 'HH:mm:ss'));
|
||||||
|
const upN3 = parseSizeFromKbs(+data['UPF.03'], 5);
|
||||||
|
upfFlowData.value.lineYUp.push(upN3[0]);
|
||||||
|
const downN6 = parseSizeFromKbs(+data['UPF.06'], 5);
|
||||||
|
upfFlowData.value.lineYDown.push(downN6[0]);
|
||||||
|
upfFlowData.value.cap += 1;
|
||||||
|
// 超过 25 弹出
|
||||||
|
if (upfFlowData.value.cap > 25) {
|
||||||
|
upfFlowData.value.lineXTime.shift();
|
||||||
|
upfFlowData.value.lineYUp.shift();
|
||||||
|
upfFlowData.value.lineYDown.shift();
|
||||||
|
upfFlowData.value.cap -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TFType = {
|
||||||
|
/**上行 N3 */
|
||||||
|
up: number;
|
||||||
|
upFrom: string;
|
||||||
|
/**下行 N6 */
|
||||||
|
down: number;
|
||||||
|
downFrom: string;
|
||||||
|
/**请求标记 */
|
||||||
|
requestFlag: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**UPF-总流量数 */
|
||||||
|
export const upfTotalFlow = ref<Record<string, TFType>>({
|
||||||
|
'0': {
|
||||||
|
up: 0,
|
||||||
|
upFrom: '0 B',
|
||||||
|
down: 0,
|
||||||
|
downFrom: '0 B',
|
||||||
|
requestFlag: false,
|
||||||
|
},
|
||||||
|
'7': {
|
||||||
|
up: 0,
|
||||||
|
upFrom: '0 B',
|
||||||
|
down: 0,
|
||||||
|
downFrom: '0 B',
|
||||||
|
requestFlag: false,
|
||||||
|
},
|
||||||
|
'30': {
|
||||||
|
up: 0,
|
||||||
|
upFrom: '0 B',
|
||||||
|
down: 0,
|
||||||
|
downFrom: '0 B',
|
||||||
|
requestFlag: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**UPF-总流量数 数据解析 */
|
||||||
|
export function upfTFParse(day: string, data: Record<string, number>) {
|
||||||
|
let { up, down } = data;
|
||||||
|
upfTotalFlow.value[day] = {
|
||||||
|
up: up,
|
||||||
|
upFrom: parseSizeFromBits(up),
|
||||||
|
down: down,
|
||||||
|
downFrom: parseSizeFromBits(down),
|
||||||
|
requestFlag: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**UPF-总流量数 选中 */
|
||||||
|
export const upfTFActive = ref<string>('0');
|
||||||
|
|
||||||
|
/**属性复位 */
|
||||||
|
export function upfTotalFlowReset() {
|
||||||
|
upfFlowData.value = {
|
||||||
|
lineXTime: [],
|
||||||
|
lineYUp: [],
|
||||||
|
lineYDown: [],
|
||||||
|
cap: 0,
|
||||||
|
};
|
||||||
|
for (const key of Object.keys(upfTotalFlow.value)) {
|
||||||
|
upfTotalFlow.value[key] = {
|
||||||
|
up: 0,
|
||||||
|
upFrom: '0 B',
|
||||||
|
down: 0,
|
||||||
|
downFrom: '0 B',
|
||||||
|
requestFlag: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
upfTFActive.value = '0';
|
||||||
|
}
|
||||||
144
src/views/dashboard/overview2/hooks/useUserActivity.ts
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
/**ueEventAMFParse UE会话事件AMF 数据解析 */
|
||||||
|
function ueEventAMFParse(
|
||||||
|
item: Record<string, any>
|
||||||
|
): false | Record<string, any> {
|
||||||
|
let evData: Record<string, any> = item.eventJSON;
|
||||||
|
if (typeof evData === 'string') {
|
||||||
|
try {
|
||||||
|
evData = JSON.parse(evData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
eType: 'amf_ue',
|
||||||
|
eId: `amf_ue_${item.id}_${Date.now()}`,
|
||||||
|
eTime: +item.timestamp,
|
||||||
|
id: item.id,
|
||||||
|
type: item.eventType,
|
||||||
|
data: evData,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**ueEventMMEParse UE会话事件MME 数据解析 */
|
||||||
|
function ueEventMMEParse(
|
||||||
|
item: Record<string, any>
|
||||||
|
): false | Record<string, any> {
|
||||||
|
let evData: Record<string, any> = item.eventJSON;
|
||||||
|
if (typeof evData === 'string') {
|
||||||
|
try {
|
||||||
|
evData = JSON.parse(evData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
eType: 'mme_ue',
|
||||||
|
eId: `mme_ue_${item.id}_${Date.now()}`,
|
||||||
|
eTime: +item.timestamp,
|
||||||
|
id: item.id,
|
||||||
|
type: item.eventType,
|
||||||
|
data: evData,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**cdrEventIMSParse CDR会话事件IMS 数据解析 */
|
||||||
|
function cdrEventIMSParse(
|
||||||
|
item: Record<string, any>
|
||||||
|
): false | Record<string, any> {
|
||||||
|
let evData: Record<string, any> = item.cdrJSON || item.CDR;
|
||||||
|
if (typeof evData === 'string') {
|
||||||
|
try {
|
||||||
|
evData = JSON.parse(evData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 指定显示CDR类型MOC/MTSM
|
||||||
|
if (!['MOC', 'MTSM'].includes(evData.recordType)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
eType: 'ims_cdr',
|
||||||
|
eId: `ims_cdr_${item.id}_${Date.now()}`,
|
||||||
|
eTime: +item.timestamp,
|
||||||
|
id: item.id,
|
||||||
|
data: evData,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**eventListParse 事件列表解析 */
|
||||||
|
export function eventListParse(
|
||||||
|
type: 'ims_cdr' | 'amf_ue' | 'mme_ue',
|
||||||
|
data: any
|
||||||
|
) {
|
||||||
|
eventTotal.value += data.total;
|
||||||
|
for (const item of data.rows) {
|
||||||
|
let v: false | Record<string, any> = false;
|
||||||
|
if (type === 'ims_cdr') {
|
||||||
|
v = cdrEventIMSParse(item);
|
||||||
|
}
|
||||||
|
if (type === 'amf_ue') {
|
||||||
|
v = ueEventAMFParse(item);
|
||||||
|
}
|
||||||
|
if (type === 'mme_ue') {
|
||||||
|
v = ueEventMMEParse(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v) {
|
||||||
|
eventData.value.push(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 激活选中
|
||||||
|
if (eventData.value.length > 0) {
|
||||||
|
eventId.value = eventData.value[0].eId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**eventItemParseAndPush 事件项解析并添加 */
|
||||||
|
export async function eventItemParseAndPush(
|
||||||
|
type: 'ims_cdr' | 'amf_ue' | 'mme_ue',
|
||||||
|
item: any
|
||||||
|
) {
|
||||||
|
let v: false | Record<string, any> = false;
|
||||||
|
if (type === 'ims_cdr') {
|
||||||
|
v = cdrEventIMSParse(item);
|
||||||
|
}
|
||||||
|
if (type === 'amf_ue') {
|
||||||
|
v = ueEventAMFParse(item);
|
||||||
|
}
|
||||||
|
if (type === 'mme_ue') {
|
||||||
|
v = ueEventMMEParse(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v) {
|
||||||
|
eventData.value.unshift(v);
|
||||||
|
eventTotal.value += 1;
|
||||||
|
eventId.value = v.eId;
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 800));
|
||||||
|
if (eventData.value.length > 20) {
|
||||||
|
eventData.value.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**CDR+UE事件数据 */
|
||||||
|
export const eventData = ref<Record<string, any>[]>([]);
|
||||||
|
/**CDR+UE事件总量 */
|
||||||
|
export const eventTotal = ref<number>(0);
|
||||||
|
/**CDR/UE事件推送id */
|
||||||
|
export const eventId = ref<string>('');
|
||||||
|
|
||||||
|
/**属性复位 */
|
||||||
|
export function userActivityReset() {
|
||||||
|
eventData.value = [];
|
||||||
|
eventTotal.value = 0;
|
||||||
|
eventId.value = '';
|
||||||
|
}
|
||||||
224
src/views/dashboard/overview2/hooks/useWS.ts
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
import { RESULT_CODE_ERROR } from '@/constants/result-constants';
|
||||||
|
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||||
|
import { onBeforeUnmount, ref } from 'vue';
|
||||||
|
import {
|
||||||
|
eventData,
|
||||||
|
eventListParse,
|
||||||
|
eventItemParseAndPush,
|
||||||
|
userActivityReset,
|
||||||
|
} from './useUserActivity';
|
||||||
|
import {
|
||||||
|
upfTotalFlow,
|
||||||
|
upfTFParse,
|
||||||
|
upfFlowParse,
|
||||||
|
upfTotalFlowReset,
|
||||||
|
} from './useUPFTotalFlow';
|
||||||
|
import { topologyReset, neStateParse, neStateRequestMap } from './useTopology';
|
||||||
|
import PQueue from 'p-queue';
|
||||||
|
|
||||||
|
/**UPF-的Id */
|
||||||
|
export const upfWhoId = ref<any>('');
|
||||||
|
|
||||||
|
/**UPF-的RmUid */
|
||||||
|
export const upfWhoRmUid = ref<any>('');
|
||||||
|
|
||||||
|
/**websocket连接 */
|
||||||
|
export default function useWS() {
|
||||||
|
const ws = new WS();
|
||||||
|
const queue = new PQueue({ concurrency: 1, autoStart: true });
|
||||||
|
|
||||||
|
/**发消息 */
|
||||||
|
function wsSend(data: Record<string, any>) {
|
||||||
|
ws.send(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**接收数据后回调 */
|
||||||
|
function wsMessage(res: Record<string, any>) {
|
||||||
|
const { code, requestId, data } = res;
|
||||||
|
if (code === RESULT_CODE_ERROR) {
|
||||||
|
console.warn(res.msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 网元状态
|
||||||
|
if (requestId && requestId.startsWith('neState')) {
|
||||||
|
const neType = requestId.split('_')[1];
|
||||||
|
const neId = requestId.split('_')[2];
|
||||||
|
neStateParse(neType, data,neId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 普通信息
|
||||||
|
switch (requestId) {
|
||||||
|
// AMF_UE会话事件
|
||||||
|
case 'amf_1010_001':
|
||||||
|
if (Array.isArray(data.rows)) {
|
||||||
|
eventListParse('amf_ue', data);
|
||||||
|
eventData.value.sort((a, b) => b.eTime - a.eTime);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// MME_UE会话事件
|
||||||
|
case 'mme_1011_001':
|
||||||
|
if (Array.isArray(data.rows)) {
|
||||||
|
eventListParse('mme_ue', data);
|
||||||
|
eventData.value.sort((a, b) => b.eTime - a.eTime);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// IMS_CDR会话事件
|
||||||
|
case 'ims_1005_001':
|
||||||
|
if (Array.isArray(data.rows)) {
|
||||||
|
eventListParse('ims_cdr', data);
|
||||||
|
eventData.value.sort((a, b) => b.eTime - a.eTime);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
//UPF-总流量数
|
||||||
|
case 'upf_001_0':
|
||||||
|
upfTFParse('0', data);
|
||||||
|
break;
|
||||||
|
case 'upf_001_7':
|
||||||
|
upfTFParse('7', data);
|
||||||
|
break;
|
||||||
|
case 'upf_001_30':
|
||||||
|
upfTFParse('30', data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// 订阅组信息
|
||||||
|
if (!data?.groupId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (data.groupId) {
|
||||||
|
// kpiEvent 指标UPF
|
||||||
|
case '10_UPF_' + upfWhoId.value:
|
||||||
|
if (data.data) {
|
||||||
|
upfFlowParse(data.data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// AMF_UE会话事件
|
||||||
|
case '1010_001':
|
||||||
|
if (data.data) {
|
||||||
|
queue.add(() => eventItemParseAndPush('amf_ue', data.data));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// MME_UE会话事件
|
||||||
|
case '1011_001':
|
||||||
|
if (data.data) {
|
||||||
|
queue.add(() => eventItemParseAndPush('mme_ue', data.data));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// IMS_CDR会话事件
|
||||||
|
case '1005_001':
|
||||||
|
if (data.data) {
|
||||||
|
queue.add(() => eventItemParseAndPush('ims_cdr', data.data));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**UPF-总流量数 发消息*/
|
||||||
|
function upfTFSend(day: '0' | '7' | '30') {
|
||||||
|
// 请求标记检查避免重复发送
|
||||||
|
if (upfTotalFlow.value[day].requestFlag) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
upfTotalFlow.value[day].requestFlag = true;
|
||||||
|
|
||||||
|
ws.send({
|
||||||
|
requestId: `upf_001_${day}`,
|
||||||
|
type: 'upf_tf',
|
||||||
|
data: {
|
||||||
|
neType: 'UPF',
|
||||||
|
neId: '001',
|
||||||
|
day: Number(day),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**userActivitySend 用户行为事件基础列表数据 发消息*/
|
||||||
|
function userActivitySend() {
|
||||||
|
// AMF_UE会话事件
|
||||||
|
ws.send({
|
||||||
|
requestId: 'amf_1010_001',
|
||||||
|
type: 'amf_ue',
|
||||||
|
data: {
|
||||||
|
neType: 'AMF',
|
||||||
|
neId: '001',
|
||||||
|
sortField: 'timestamp',
|
||||||
|
sortOrder: 'desc',
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// MME_UE会话事件
|
||||||
|
ws.send({
|
||||||
|
requestId: 'mme_1011_001',
|
||||||
|
type: 'mme_ue',
|
||||||
|
data: {
|
||||||
|
neType: 'MME',
|
||||||
|
neId: '001',
|
||||||
|
sortField: 'timestamp',
|
||||||
|
sortOrder: 'desc',
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// IMS_CDR会话事件
|
||||||
|
ws.send({
|
||||||
|
requestId: 'ims_1005_001',
|
||||||
|
type: 'ims_cdr',
|
||||||
|
data: {
|
||||||
|
neType: 'IMS',
|
||||||
|
neId: '001',
|
||||||
|
recordType: 'MOC',
|
||||||
|
sortField: 'timestamp',
|
||||||
|
sortOrder: 'desc',
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**重新发送至UPF 10_UPF_neId */
|
||||||
|
function reSendUPF(neId: string) {
|
||||||
|
upfWhoId.value = neId;
|
||||||
|
//初始时时无需还原全部属性以及关闭
|
||||||
|
if (ws.state() === WebSocket.OPEN) {
|
||||||
|
ws.close();
|
||||||
|
// userActivityReset();
|
||||||
|
upfTotalFlowReset();
|
||||||
|
neStateRequestMap.value = new Map();
|
||||||
|
//topologyReset();
|
||||||
|
}
|
||||||
|
const options: OptionsType = {
|
||||||
|
url: '/ws',
|
||||||
|
params: {
|
||||||
|
/**订阅通道组
|
||||||
|
*
|
||||||
|
* 指标UPF (GroupID:10_neType_neId)
|
||||||
|
* AMF_UE会话事件(GroupID:1010_neId)
|
||||||
|
* MME_UE会话事件(GroupID:1011_neId)
|
||||||
|
* IMS_CDR会话事件(GroupID:1005_neId)
|
||||||
|
*/
|
||||||
|
subGroupID: '10_UPF_' + neId + ',1010_001,1011_001,1005_001',
|
||||||
|
},
|
||||||
|
onmessage: wsMessage,
|
||||||
|
onerror: (ev: any) => {
|
||||||
|
console.error(ev);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
ws.connect(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
ws.close();
|
||||||
|
userActivityReset();
|
||||||
|
upfTotalFlowReset();
|
||||||
|
topologyReset();
|
||||||
|
upfWhoRmUid.value = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
wsSend,
|
||||||
|
userActivitySend,
|
||||||
|
upfTFSend,
|
||||||
|
reSendUPF,
|
||||||
|
};
|
||||||
|
}
|
||||||
BIN
src/views/dashboard/overview2/images/bj.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
src/views/dashboard/overview2/images/border.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src/views/dashboard/overview2/images/brand.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
src/views/dashboard/overview2/images/line.png
Normal file
|
After Width: | Height: | Size: 237 B |
BIN
src/views/dashboard/overview2/images/newBrand.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
src/views/dashboard/overview2/images/rect.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/views/dashboard/overview2/images/title.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
783
src/views/dashboard/overview2/index.vue
Normal file
@@ -0,0 +1,783 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onBeforeUnmount, onMounted, reactive, ref } from 'vue';
|
||||||
|
import svgBase from '@/assets/svg/base.svg';
|
||||||
|
import svgUserIMS from '@/assets/svg/userIMS.svg';
|
||||||
|
import svgUserSMF from '@/assets/svg/userSMF.svg';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
import Topology from './components/Topology/index.vue';
|
||||||
|
import NeResources from './components/NeResources/index.vue';
|
||||||
|
import UserActivity from './components/UserActivity/index.vue';
|
||||||
|
import IMSActivity from './components/IMSActivity/index.vue';
|
||||||
|
import AlarnTypeBar from './components/AlarnTypeBar/index.vue';
|
||||||
|
import UPFFlow from './components/UPFFlow/index.vue';
|
||||||
|
import { listIMSSessionNum } from '@/api/neData/ims';
|
||||||
|
import { listUDMSub } from '@/api/neData/udm_sub';
|
||||||
|
import { listAMFNblist } from '@/api/neData/amf';
|
||||||
|
import { listMMENblist } from '@/api/neData/mme';
|
||||||
|
import { listSMFSubNum } from '@/api/neData/smf';
|
||||||
|
import { graphNodeClickID, graphState, notNeNodes } from './hooks/useTopology';
|
||||||
|
import { upfTotalFlow, upfTFActive } from './hooks/useUPFTotalFlow';
|
||||||
|
import { useFullscreen } from '@vueuse/core';
|
||||||
|
import useWS from './hooks/useWS';
|
||||||
|
import useAppStore from '@/store/modules/app';
|
||||||
|
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import useNeListStore from '@/store/modules/ne_list';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import { upfWhoId } from './hooks/useWS';
|
||||||
|
import { listAMFNbStatelist } from '@/api/neData/amf';
|
||||||
|
import { listMMENbStatelist } from '@/api/neData/mme';
|
||||||
|
|
||||||
|
const neInfoStore = useNeListStore();
|
||||||
|
const router = useRouter();
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { wsSend, userActivitySend, upfTFSend, reSendUPF } = useWS();
|
||||||
|
|
||||||
|
/**概览状态类型 */
|
||||||
|
type SkimStateType = {
|
||||||
|
/**UDM签约用户数量 */
|
||||||
|
udmSubNum: number;
|
||||||
|
/**SMF在线用户数 */
|
||||||
|
smfUeNum: number;
|
||||||
|
/**IMS在线用户数 */
|
||||||
|
imsUeNum: number;
|
||||||
|
/**5G基站数量 */
|
||||||
|
gnbNum: number;
|
||||||
|
/**5G在线用户数量 */
|
||||||
|
gnbUeNum: number;
|
||||||
|
/**4G基站数量 */
|
||||||
|
enbNum: number;
|
||||||
|
/**4G在线用户数量 */
|
||||||
|
enbUeNum: number;
|
||||||
|
/**5G用户总数量 */
|
||||||
|
gNbSumNum: number;
|
||||||
|
/**4G用户总数量 */
|
||||||
|
eNbSumNum: number;
|
||||||
|
};
|
||||||
|
/**概览状态信息 */
|
||||||
|
let skimState: SkimStateType = reactive({
|
||||||
|
udmSubNum: 0,
|
||||||
|
smfUeNum: 0,
|
||||||
|
imsUeNum: 0,
|
||||||
|
gnbNum: 0,
|
||||||
|
gnbUeNum: 0,
|
||||||
|
enbNum: 0,
|
||||||
|
enbUeNum: 0,
|
||||||
|
gNbSumNum: 0,
|
||||||
|
eNbSumNum: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**网元参数 */
|
||||||
|
let neCascaderOptions = ref<Record<string, any>[]>([]);
|
||||||
|
|
||||||
|
/**总览节点 */
|
||||||
|
const viewportDom = ref<HTMLElement | null>(null);
|
||||||
|
const { isFullscreen, toggle } = useFullscreen(viewportDom);
|
||||||
|
|
||||||
|
let initFlag = false;
|
||||||
|
/**10s调度器 */
|
||||||
|
const interval10s = ref<any>(null);
|
||||||
|
|
||||||
|
/**5s调度器 */
|
||||||
|
const interval5s = ref<any>(null);
|
||||||
|
|
||||||
|
/**查询网元状态 */
|
||||||
|
function fnGetNeState() {
|
||||||
|
// 获取节点状态
|
||||||
|
for (const node of graphState.data.nodes) {
|
||||||
|
if (notNeNodes.includes(node.id)) continue;
|
||||||
|
|
||||||
|
const neInfoList = node.neInfoList || [];
|
||||||
|
if (neInfoList.length === 0) continue;
|
||||||
|
|
||||||
|
for (const neInfo of neInfoList) {
|
||||||
|
if (!neInfo.neType || !neInfo.neId) continue;
|
||||||
|
|
||||||
|
wsSend({
|
||||||
|
requestId: `neState_${neInfo.neType}_${neInfo.neId}`,
|
||||||
|
type: 'ne_state',
|
||||||
|
data: {
|
||||||
|
neType: neInfo.neType,
|
||||||
|
neId: neInfo.neId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**获取概览信息 */
|
||||||
|
async function fnGetSkim() {
|
||||||
|
let tempGnbSumNum = 0;
|
||||||
|
let tempEnbSumNum = 0;
|
||||||
|
|
||||||
|
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) => listSMFSubNum(neId),
|
||||||
|
process: (res: any) =>
|
||||||
|
res.code === RESULT_CODE_SUCCESS && (skimState.smfUeNum += res.data),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'IMS',
|
||||||
|
{
|
||||||
|
request: (neId: string) => listIMSSessionNum(neId),
|
||||||
|
process: (res: any) =>
|
||||||
|
res.code === RESULT_CODE_SUCCESS && (skimState.imsUeNum += res.data),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'AMF',
|
||||||
|
{
|
||||||
|
request: (neId: string) => listAMFNblist({ neId }),
|
||||||
|
process: async (res: any, neId: any) => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
skimState.gnbNum += res.total;
|
||||||
|
skimState.gnbUeNum += res.rows.reduce(
|
||||||
|
(sum: number, item: any) => sum + item.ueNum,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const amfNbRes = await listAMFNbStatelist({ neId });
|
||||||
|
if (
|
||||||
|
amfNbRes.code === RESULT_CODE_SUCCESS &&
|
||||||
|
Array.isArray(amfNbRes.data)
|
||||||
|
) {
|
||||||
|
// skimState.gNbSumNum += amfNbRes.data.length;
|
||||||
|
tempGnbSumNum += amfNbRes.data.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'MME',
|
||||||
|
{
|
||||||
|
request: (neId: string) => listMMENblist({ neId }),
|
||||||
|
process: async (res: any, neId: any) => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
skimState.enbNum += res.total;
|
||||||
|
skimState.enbUeNum += res.rows.reduce(
|
||||||
|
(sum: number, item: any) => sum + item.ueNum,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
const mmeNbRes = await listMMENbStatelist({ neId });
|
||||||
|
if (
|
||||||
|
mmeNbRes.code === RESULT_CODE_SUCCESS &&
|
||||||
|
Array.isArray(mmeNbRes.data)
|
||||||
|
) {
|
||||||
|
// skimState.eNbSumNum += mmeNbRes.data.length;
|
||||||
|
tempEnbSumNum += mmeNbRes.data.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
const requests = neCascaderOptions.value.flatMap(
|
||||||
|
(ne: any) =>
|
||||||
|
ne.children
|
||||||
|
?.map((child: any) => {
|
||||||
|
const handler = neHandlers.get(child.neType);
|
||||||
|
return handler
|
||||||
|
? {
|
||||||
|
promise: handler.request(child.neId),
|
||||||
|
process: handler.process,
|
||||||
|
neId: child.neId, // 这里加上neId
|
||||||
|
}
|
||||||
|
: 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,
|
||||||
|
});
|
||||||
|
const processPromises = results.map((result, index) => {
|
||||||
|
const req = requests[index];
|
||||||
|
if (result.status === 'fulfilled') {
|
||||||
|
return req.process(result.value, req.neId);
|
||||||
|
} else {
|
||||||
|
return req.process(0, req.neId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 等待所有 process 执行完再赋值gNbSumNum等
|
||||||
|
await Promise.all(processPromises);
|
||||||
|
|
||||||
|
skimState.gNbSumNum = tempGnbSumNum;
|
||||||
|
skimState.eNbSumNum = tempEnbSumNum;
|
||||||
|
|
||||||
|
// UDM
|
||||||
|
listUDMSub({ neId: udmNeId.value, pageNum: 1, pageSize: 1 }).then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
skimState.udmSubNum = res.total;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**初始数据函数 */
|
||||||
|
function loadData() {
|
||||||
|
fnGetNeState(); // 获取网元状态
|
||||||
|
userActivitySend();
|
||||||
|
upfTFSend('0');
|
||||||
|
upfTFSend('7');
|
||||||
|
upfTFSend('30');
|
||||||
|
|
||||||
|
clearInterval(interval10s.value);
|
||||||
|
interval10s.value = setInterval(() => {
|
||||||
|
if (!interval10s.value) return;
|
||||||
|
if (upfTFActive.value === '0') {
|
||||||
|
upfTFSend('7');
|
||||||
|
upfTFActive.value = '7';
|
||||||
|
} else if (upfTFActive.value === '7') {
|
||||||
|
upfTFSend('30');
|
||||||
|
upfTFActive.value = '30';
|
||||||
|
} else if (upfTFActive.value === '30') {
|
||||||
|
upfTFSend('0');
|
||||||
|
upfTFActive.value = '0';
|
||||||
|
}
|
||||||
|
}, 10_000);
|
||||||
|
|
||||||
|
clearInterval(interval5s.value);
|
||||||
|
interval5s.value = setInterval(() => {
|
||||||
|
if (!interval5s.value || !initFlag) return;
|
||||||
|
fnGetSkim(); // 获取概览信息
|
||||||
|
fnGetNeState(); // 获取网元状态
|
||||||
|
}, 10_000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**栏目信息跳转 */
|
||||||
|
function fnToRouter(name: string, query?: any) {
|
||||||
|
router.push({ name, query });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**网元参数 */
|
||||||
|
let neOtions = ref<Record<string, any>[]>([]);
|
||||||
|
|
||||||
|
// UPF实时流量下拉框选择
|
||||||
|
function fnSelectNe(value: any, option: any) {
|
||||||
|
upfWhoId.value = value;
|
||||||
|
reSendUPF(value);
|
||||||
|
// upfTotalFlow.value.map((item: any) => {
|
||||||
|
// item.requestFlag = false;
|
||||||
|
// });
|
||||||
|
|
||||||
|
for (var key in upfTotalFlow.value) {
|
||||||
|
upfTotalFlow.value[key].requestFlag = false;
|
||||||
|
}
|
||||||
|
// loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
let udmNeId = ref<string>('001');
|
||||||
|
let udmOtions = ref<Record<string, any>[]>([]);
|
||||||
|
let onlineOtions = ref<Record<string, any>[]>([]);
|
||||||
|
|
||||||
|
/**用户数量-选择UDM */
|
||||||
|
function fnSelectUDM(e: any) {
|
||||||
|
udmNeId.value = e.key;
|
||||||
|
listUDMSub({ neId: udmNeId.value, pageNum: 1, pageSize: 1 }).then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
skimState.udmSubNum = res.total;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**资源控制-选择NE */
|
||||||
|
function fnSelectNeRe(e: any) {
|
||||||
|
graphNodeClickID.value = e.key;
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// 定义一个方法返回 views 容器
|
||||||
|
const getPopupContainer = () => {
|
||||||
|
// 使用 ref 或其他方式来引用你的 views 容器
|
||||||
|
// 如果 views 容器直接在这个组件内部,你可以使用 ref
|
||||||
|
// 但在这个例子中,我们假设它是通过类名来获取的
|
||||||
|
return document.querySelector('.viewport');
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
neInfoStore
|
||||||
|
.fnNelist()
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||||
|
if (res.data.length > 0) {
|
||||||
|
// UPF
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
//online Ne
|
||||||
|
let onlineArr: Record<string, any>[] = [];
|
||||||
|
|
||||||
|
// UDM
|
||||||
|
let arr1: Record<string, any>[] = [];
|
||||||
|
res.data.forEach((v: any) => {
|
||||||
|
if (
|
||||||
|
v.status &&
|
||||||
|
[
|
||||||
|
'UDM',
|
||||||
|
'UPF',
|
||||||
|
'AUSF',
|
||||||
|
'PCF',
|
||||||
|
'SMF',
|
||||||
|
'AMF',
|
||||||
|
'OMC',
|
||||||
|
'SMSC',
|
||||||
|
'IMS',
|
||||||
|
'MME',
|
||||||
|
].includes(v.neType)
|
||||||
|
) {
|
||||||
|
onlineArr.push({
|
||||||
|
value: v.neType + '_' + v.neId,
|
||||||
|
label: v.neName,
|
||||||
|
rmUid: v.rmUid,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (v.neType === 'UDM') {
|
||||||
|
arr1.push({ value: v.neId, label: v.neName, rmUid: v.rmUid });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
udmOtions.value = arr1;
|
||||||
|
onlineOtions.value = onlineArr;
|
||||||
|
if (arr1.length > 0) {
|
||||||
|
fnSelectUDM({ key: arr1[0].value });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onlineArr.length > 0) {
|
||||||
|
fnSelectNeRe({ key: onlineArr[0].value });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤不可用的网元
|
||||||
|
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(() => {
|
||||||
|
initFlag = true;
|
||||||
|
fnGetSkim().then(() => {
|
||||||
|
loadData();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
clearInterval(interval10s.value);
|
||||||
|
interval10s.value = null;
|
||||||
|
clearInterval(interval5s.value);
|
||||||
|
interval5s.value = null;
|
||||||
|
initFlag = false;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="viewport" ref="viewportDom">
|
||||||
|
<div class="brand">
|
||||||
|
<div
|
||||||
|
class="brand-title"
|
||||||
|
@click="toggle"
|
||||||
|
:title="t('views.dashboard.overview.fullscreen')"
|
||||||
|
>
|
||||||
|
{{ t('views.dashboard.overview.title') }}
|
||||||
|
<FullscreenExitOutlined v-if="isFullscreen" />
|
||||||
|
<FullscreenOutlined v-else />
|
||||||
|
</div>
|
||||||
|
<div class="brand-desc">{{ appStore.appName }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column">
|
||||||
|
<div class="skim panel">
|
||||||
|
<div class="inner">
|
||||||
|
<h3 class="leftright">
|
||||||
|
<span class="title">
|
||||||
|
<IdcardOutlined style="color: #68d8fe" />
|
||||||
|
{{ t('views.dashboard.overview.skim.userTitle') }}
|
||||||
|
</span>
|
||||||
|
</h3>
|
||||||
|
<div class="data">
|
||||||
|
<div
|
||||||
|
class="item toRouter"
|
||||||
|
:title="t('views.dashboard.overview.toRouter')"
|
||||||
|
>
|
||||||
|
<div @click="fnToRouter('Sub_2010')">
|
||||||
|
<UserOutlined
|
||||||
|
style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
|
||||||
|
/>
|
||||||
|
{{ skimState.udmSubNum }}
|
||||||
|
</div>
|
||||||
|
<span>
|
||||||
|
<a-dropdown
|
||||||
|
:trigger="['click']"
|
||||||
|
:get-Popup-Container="getPopupContainer"
|
||||||
|
>
|
||||||
|
<div class="toDeep-text">
|
||||||
|
{{ t('views.dashboard.overview.skim.users') }}
|
||||||
|
<DownOutlined style="margin-left: 12px; font-size: 12px" />
|
||||||
|
</div>
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu @click="fnSelectUDM">
|
||||||
|
<a-menu-item
|
||||||
|
v-for="v in udmOtions"
|
||||||
|
:key="v.value"
|
||||||
|
:disabled="udmNeId === v.value"
|
||||||
|
>
|
||||||
|
{{ v.label }}
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="item toRouter"
|
||||||
|
@click="fnToRouter('Ims_2080')"
|
||||||
|
:title="t('views.dashboard.overview.toRouter')"
|
||||||
|
style="margin: 0 12px"
|
||||||
|
v-perms:has="['dashboard:overview:imsUeNum']"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<img :src="svgUserIMS" style="width: 18px; margin-right: 8px" />
|
||||||
|
{{ skimState.imsUeNum }}
|
||||||
|
</div>
|
||||||
|
<span>
|
||||||
|
{{ t('views.dashboard.overview.skim.imsUeNum') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="item toRouter"
|
||||||
|
@click="fnToRouter('Ue_2081')"
|
||||||
|
:title="t('views.dashboard.overview.toRouter')"
|
||||||
|
v-perms:has="['dashboard:overview:smfUeNum']"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<img :src="svgUserSMF" style="width: 18px; margin-right: 8px" />
|
||||||
|
{{ skimState.smfUeNum }}
|
||||||
|
</div>
|
||||||
|
<span>
|
||||||
|
{{ t('views.dashboard.overview.skim.smfUeNum') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--告警统计-->
|
||||||
|
<div class="alarmType panel">
|
||||||
|
<div class="inner">
|
||||||
|
<h3
|
||||||
|
class="toRouter leftright"
|
||||||
|
:title="t('views.dashboard.overview.toRouter')"
|
||||||
|
>
|
||||||
|
<span class="title" @click="fnToRouter('HistoryAlarm_2097')">
|
||||||
|
<PieChartOutlined style="color: #68d8fe" />
|
||||||
|
{{ t('views.dashboard.overview.alarmTypeBar.alarmSum') }}
|
||||||
|
</span>
|
||||||
|
</h3>
|
||||||
|
<div class="chart">
|
||||||
|
<AlarnTypeBar />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 用户行为 -->
|
||||||
|
<div class="userActivity panel">
|
||||||
|
<div class="inner">
|
||||||
|
<h3 class="leftright">
|
||||||
|
<span class="title">
|
||||||
|
<WhatsAppOutlined style="color: #68d8fe" />
|
||||||
|
{{ t('views.dashboard.overview.userActivity.title') }}
|
||||||
|
</span>
|
||||||
|
</h3>
|
||||||
|
<div class="chart">
|
||||||
|
<UserActivity />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column" style="flex: 4; margin: 1.333rem 0.833rem 0">
|
||||||
|
<!-- 实时流量 -->
|
||||||
|
<div class="upfFlow panel">
|
||||||
|
<div class="inner">
|
||||||
|
<h3
|
||||||
|
class="toRouter centerStyle"
|
||||||
|
:title="t('views.dashboard.overview.toRouter')"
|
||||||
|
>
|
||||||
|
<span class="title">
|
||||||
|
<div @click="fnToRouter('GoldTarget_2104')">
|
||||||
|
<AreaChartOutlined style="color: #68d8fe" />
|
||||||
|
{{ t('views.dashboard.overview.upfFlow.title') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-select
|
||||||
|
v-model:value="upfWhoId"
|
||||||
|
:options="neOtions"
|
||||||
|
:get-Popup-Container="getPopupContainer"
|
||||||
|
class="toDeep"
|
||||||
|
style="color: #fff"
|
||||||
|
@change="fnSelectNe"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="chart">
|
||||||
|
<UPFFlow />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 网络拓扑 -->
|
||||||
|
<div class="topology panel">
|
||||||
|
<div class="inner">
|
||||||
|
<h3
|
||||||
|
class="toRouter centerStyle"
|
||||||
|
@click="fnToRouter('TopologyArchitecture_2128')"
|
||||||
|
:title="t('views.dashboard.overview.toRouter')"
|
||||||
|
>
|
||||||
|
<span class="title">
|
||||||
|
<ApartmentOutlined style="color: #68d8fe" />
|
||||||
|
{{ t('views.dashboard.overview.topology.title') }}
|
||||||
|
</span>
|
||||||
|
</h3>
|
||||||
|
<div class="chart">
|
||||||
|
<Topology />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column">
|
||||||
|
<!-- 基站信息 -->
|
||||||
|
<div class="skim panel base" v-perms:has="['dashboard:overview:gnbBase']">
|
||||||
|
<div class="inner">
|
||||||
|
<h3 class="leftright">
|
||||||
|
<span class="title">
|
||||||
|
<GlobalOutlined style="color: #68d8fe" />
|
||||||
|
{{ t('views.dashboard.overview.skim.nodeBInfo') }}
|
||||||
|
</span>
|
||||||
|
</h3>
|
||||||
|
<div class="data" style="margin-top: 20px">
|
||||||
|
<div
|
||||||
|
class="item toRouter"
|
||||||
|
@click="fnToRouter('BaseStation_2096', { neType: 'AMF' })"
|
||||||
|
:title="t('views.dashboard.overview.toRouter')"
|
||||||
|
>
|
||||||
|
<div style="align-items: flex-start">
|
||||||
|
<img
|
||||||
|
:src="svgBase"
|
||||||
|
style="width: 18px; margin-right: 8px; height: 2rem"
|
||||||
|
/>
|
||||||
|
{{ skimState.gNbSumNum }}
|
||||||
|
</div>
|
||||||
|
<span>{{ t('views.dashboard.overview.skim.gnbSumBase') }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="item toRouter"
|
||||||
|
@click="fnToRouter('BaseStation_2096', { neType: 'AMF' })"
|
||||||
|
:title="t('views.dashboard.overview.toRouter')"
|
||||||
|
>
|
||||||
|
<div style="align-items: flex-start">
|
||||||
|
<img
|
||||||
|
:src="svgBase"
|
||||||
|
style="width: 18px; margin-right: 8px; height: 2rem"
|
||||||
|
/>
|
||||||
|
{{ skimState.gnbNum }}
|
||||||
|
</div>
|
||||||
|
<span>{{ t('views.dashboard.overview.skim.gnbBase') }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="item toRouter"
|
||||||
|
@click="fnToRouter('BaseStation_2096', { neType: 'AMF' })"
|
||||||
|
:title="t('views.dashboard.overview.toRouter')"
|
||||||
|
>
|
||||||
|
<div style="align-items: flex-start">
|
||||||
|
<UserOutlined
|
||||||
|
style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
|
||||||
|
/>
|
||||||
|
{{ skimState.gnbUeNum }}
|
||||||
|
</div>
|
||||||
|
<span>{{ t('views.dashboard.overview.skim.gnbUeNum') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="skim panel base" v-perms:has="['dashboard:overview:enbBase']">
|
||||||
|
<div class="inner">
|
||||||
|
<h3></h3>
|
||||||
|
<div class="data" style="margin-top: 40px">
|
||||||
|
<div
|
||||||
|
class="item toRouter"
|
||||||
|
@click="fnToRouter('BaseStation_2096', { neType: 'MME' })"
|
||||||
|
:title="t('views.dashboard.overview.toRouter')"
|
||||||
|
>
|
||||||
|
<div style="align-items: flex-start">
|
||||||
|
<img
|
||||||
|
:src="svgBase"
|
||||||
|
style="width: 18px; margin-right: 8px; height: 2rem"
|
||||||
|
/>
|
||||||
|
{{ skimState.eNbSumNum }}
|
||||||
|
</div>
|
||||||
|
<span>{{ t('views.dashboard.overview.skim.enbSumBase') }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="item toRouter"
|
||||||
|
@click="fnToRouter('BaseStation_2096', { neType: 'MME' })"
|
||||||
|
:title="t('views.dashboard.overview.toRouter')"
|
||||||
|
>
|
||||||
|
<div style="align-items: flex-start">
|
||||||
|
<img
|
||||||
|
:src="svgBase"
|
||||||
|
style="width: 18px; margin-right: 8px; height: 2rem"
|
||||||
|
/>
|
||||||
|
{{ skimState.enbNum }}
|
||||||
|
</div>
|
||||||
|
<span>{{ t('views.dashboard.overview.skim.enbBase') }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="item toRouter"
|
||||||
|
@click="fnToRouter('BaseStation_2096', { neType: 'MME' })"
|
||||||
|
:title="t('views.dashboard.overview.toRouter')"
|
||||||
|
>
|
||||||
|
<div style="align-items: flex-start">
|
||||||
|
<UserOutlined
|
||||||
|
style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
|
||||||
|
/>
|
||||||
|
{{ skimState.enbUeNum }}
|
||||||
|
</div>
|
||||||
|
<span>{{ t('views.dashboard.overview.skim.enbUeNum') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 资源情况 -->
|
||||||
|
<div class="resources panel">
|
||||||
|
<div class="inner">
|
||||||
|
<h3 class="resources leftright">
|
||||||
|
<span class="title">
|
||||||
|
<DashboardOutlined
|
||||||
|
style="color: #68d8fe; font-size: 20px"
|
||||||
|
/>
|
||||||
|
<div style="margin-left: -3px">
|
||||||
|
{{ t('views.dashboard.overview.resources.title') }}:
|
||||||
|
</div>
|
||||||
|
<a-dropdown
|
||||||
|
:trigger="['click']"
|
||||||
|
:get-Popup-Container="getPopupContainer"
|
||||||
|
>
|
||||||
|
<div class="toDeep-text">
|
||||||
|
{{ graphNodeClickID }}
|
||||||
|
<DownOutlined style="margin-left: -2px; font-size: 12px" />
|
||||||
|
</div>
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu @click="fnSelectNeRe">
|
||||||
|
<a-menu-item v-for="v in onlineOtions" :key="v.value">
|
||||||
|
{{ v.label }}
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
</span>
|
||||||
|
</h3>
|
||||||
|
<div class="chart">
|
||||||
|
<NeResources />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- IMS用户行为 -->
|
||||||
|
<div class="userActivity panel">
|
||||||
|
<div class="inner">
|
||||||
|
<h3 class="leftright">
|
||||||
|
<span class="title">
|
||||||
|
<WhatsAppOutlined
|
||||||
|
style="color: #68d8fe; font-size: 20px"
|
||||||
|
/>
|
||||||
|
{{ t('views.dashboard.overview.userActivity.imsTitle') }}
|
||||||
|
</span>
|
||||||
|
</h3>
|
||||||
|
<div class="chart">
|
||||||
|
<IMSActivity />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
@import url('./css/index.css');
|
||||||
|
|
||||||
|
.toDeep {
|
||||||
|
--editor-background-color: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toDeep :deep(.ant-select-selector) {
|
||||||
|
background-color: #050f23;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toDeep :deep(.ant-select-arrow) {
|
||||||
|
color: #4c9bfd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toDeep :deep(.ant-select-selection-item) {
|
||||||
|
color: #4c9bfd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toDeep-text {
|
||||||
|
color: #4c9bfd !important;
|
||||||
|
font-size: 0.844rem !important;
|
||||||
|
position: relative !important;
|
||||||
|
line-height: 2rem !important;
|
||||||
|
white-space: nowrap !important;
|
||||||
|
text-align: start !important;
|
||||||
|
text-overflow: ellipsis !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
BIN
src/views/faultManage/alarm-overview/images/bg.png
Normal file
|
After Width: | Height: | Size: 257 KiB |