1329 lines
43 KiB
Vue
1329 lines
43 KiB
Vue
<template>
|
||
<div class="dashboard-cards">
|
||
<div style="font-size:32px; font-weight: bold; margin-bottom: 20px; text-align: center;">
|
||
Dashboard
|
||
</div>
|
||
<div style="margin-bottom: 24px; text-align: right;">
|
||
<label style="margin-right: 8px; font-weight: 600;">NE:</label>
|
||
<a-select
|
||
v-model:value="selectedImsNeId"
|
||
:options="imsNeList.map(ne => ({ label: ne.neName || ne.neId, value: ne.neId }))"
|
||
@change="onImsNeChange"
|
||
style="width: 100px; font-size: 15px;"
|
||
:dropdown-style="{ borderRadius: '8px' }"
|
||
placeholder='No NE'
|
||
:bordered="true"
|
||
class="ims-select"
|
||
:allow-clear="false"
|
||
/>
|
||
<!-- <a-button-->
|
||
<!-- type="primary"-->
|
||
<!-- style="margin-left: 16px;"-->
|
||
<!-- @click="testDataUpdate"-->
|
||
<!-- >-->
|
||
<!-- 测试数据更新-->
|
||
<!-- </a-button>-->
|
||
<!-- <div style="margin-top: 8px; font-size: 12px; color: #666;">-->
|
||
<!-- WebSocket状态: {{ wsStatus }} | 数据点数量: {{ imsRealtimeRawData.length }}-->
|
||
<!-- </div>-->
|
||
</div>
|
||
<div class="row-title">Calls</div>
|
||
<a-row :gutter="[48, 48]">
|
||
<a-col :xs="24" :sm="24" :lg="8">
|
||
<a-card bordered class="metric-card">
|
||
<div class="card-title">active calls <span class="main-icon phone">📞</span></div>
|
||
<div class="card-content">
|
||
<div class="trend-chart">
|
||
<div class="mini-chart" ref="callsChartRef"></div>
|
||
<!-- <div class="card-subtext">60m <span class="card-sub-sep">now</span></div>-->
|
||
</div>
|
||
<div class="metric-info">
|
||
<div class="metric-value">
|
||
{{ calculateActiveCallsValue() }}
|
||
<span class="main-arrow" :class="calculateActiveCallsArrowDirection()">{{ calculateActiveCallsArrow() }}</span>
|
||
</div>
|
||
<div class="metric-change">{{ calculateActiveCallsChange() }}</div>
|
||
</div>
|
||
</div>
|
||
</a-card>
|
||
</a-col>
|
||
<!-- <a-col :xs="24" :sm="24" :lg="6">-->
|
||
<!-- <a-card bordered class="metric-card">-->
|
||
<!-- <div class="card-title">MOS <span class="main-icon phone">📞</span></div>-->
|
||
<!-- <div class="card-content">-->
|
||
<!-- <div class="trend-chart">-->
|
||
<!-- <div class="mini-chart" ref="mosChartRef"></div>-->
|
||
<!--<!– <div class="card-subtext">60m <span class="card-sub-sep">now</span></div>–>-->
|
||
<!-- </div>-->
|
||
<!-- <div class="metric-info">-->
|
||
<!-- <div class="metric-value">-->
|
||
<!-- 4.30-->
|
||
<!-- <span class="main-arrow up">↗</span>-->
|
||
<!-- </div>-->
|
||
<!-- <div class="metric-change">+0.08 last 5m</div>-->
|
||
<!-- </div>-->
|
||
<!-- </div>-->
|
||
<!-- </a-card>-->
|
||
<!-- </a-col>-->
|
||
<a-col :xs="24" :sm="24" :lg="8">
|
||
<a-card bordered class="metric-card">
|
||
<div class="card-title">calls MO / MT</div>
|
||
<div class="card-content full-width">
|
||
<div class="metric-info" style="width:100%;text-align:center;">
|
||
<div class="metric-value" style="font-size:38px;justify-content:center;">
|
||
{{ calculateMOValue() }}% / {{ calculateMTValue() }}%
|
||
</div>
|
||
<div class="metric-change">{{ calculateMOChange() }} / {{ calculateMTChange() }}</div>
|
||
</div>
|
||
</div>
|
||
</a-card>
|
||
</a-col>
|
||
<a-col :xs="24" :sm="24" :lg="8">
|
||
<a-card bordered class="metric-card">
|
||
<div class="card-title">failed calls <span class="main-icon phone">📞</span></div>
|
||
<div class="card-content">
|
||
<div class="trend-chart">
|
||
<div class="mini-chart" ref="failedCallsChartRef"></div>
|
||
<!-- <div class="card-subtext">60m <span class="card-sub-sep">now</span></div>-->
|
||
</div>
|
||
<div class="metric-info">
|
||
<div class="metric-value">
|
||
{{ calculateFailedCallsValue() }}
|
||
<span class="main-arrow" :class="calculateFailedCallsArrowDirection()">{{ calculateFailedCallsArrow() }}</span>
|
||
</div>
|
||
<div class="metric-change">{{ calculateFailedCallsChange() }}</div>
|
||
</div>
|
||
</div>
|
||
</a-card>
|
||
</a-col>
|
||
</a-row>
|
||
<div class="row-title" style="margin-top: 48px;">Registrations</div>
|
||
<a-row :gutter="[48, 48]">
|
||
<a-col :xs="24" :sm="24" :lg="8">
|
||
<a-card bordered class="metric-card">
|
||
<div class="card-title">active registrations <span class="main-icon calendar">📅</span></div>
|
||
<div class="card-content">
|
||
<div class="trend-chart">
|
||
<div class="mini-chart" ref="regChartRef"></div>
|
||
<!-- <div class="card-subtext">24h <span class="card-sub-sep">now</span></div>-->
|
||
</div>
|
||
<div class="metric-info">
|
||
<div class="metric-value">
|
||
{{ calculateActiveRegistrationsValue() }}
|
||
<span class="main-arrow" :class="calculateActiveRegistrationsArrowDirection()">{{ calculateActiveRegistrationsArrow() }}</span>
|
||
</div>
|
||
<div class="metric-change">{{ calculateActiveRegistrationsChange() }}</div>
|
||
</div>
|
||
</div>
|
||
</a-card>
|
||
</a-col>
|
||
<a-col :xs="24" :sm="24" :lg="8">
|
||
<a-card bordered class="metric-card">
|
||
<div class="card-title">registration success</div>
|
||
<div class="card-content full-width">
|
||
<div class="metric-info" style="width:100%;text-align:center;">
|
||
<div class="metric-value" style="font-size:38px;justify-content:center;">{{ calculateRegSuccessValue() }}%</div>
|
||
<div class="metric-change">{{ calculateRegSuccessChange() }}</div>
|
||
</div>
|
||
</div>
|
||
</a-card>
|
||
</a-col>
|
||
<a-col :xs="24" :sm="24" :lg="8">
|
||
<a-card bordered class="metric-card">
|
||
<div class="card-title">failed registrations total <span class="main-icon calendar">📅</span></div>
|
||
<div class="card-content">
|
||
<div class="trend-chart">
|
||
<div class="mini-chart" ref="failedRegChartRef"></div>
|
||
<!-- <div class="card-subtext">24h <span class="card-sub-sep">now</span></div>-->
|
||
</div>
|
||
<div class="metric-info">
|
||
<div class="metric-value">
|
||
{{ calculateFailedRegistrationsValue() }}
|
||
<span class="main-arrow" :class="calculateFailedRegistrationsArrowDirection()">{{ calculateFailedRegistrationsArrow() }}</span>
|
||
</div>
|
||
<div class="metric-change">{{ calculateFailedRegistrationsChange() }}</div>
|
||
</div>
|
||
</div>
|
||
</a-card>
|
||
</a-col>
|
||
</a-row>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
|
||
import * as echarts from 'echarts/core'
|
||
import { LineChart } from 'echarts/charts'
|
||
import { GridComponent } from 'echarts/components'
|
||
import { CanvasRenderer } from 'echarts/renderers'
|
||
import useNeListStore from '@/store/modules/ne_list';
|
||
import { WS } from '@/plugins/ws-websocket'
|
||
import { listKPIData } from '@/api/perfManage/goldTarget'
|
||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'
|
||
|
||
echarts.use([LineChart, GridComponent, CanvasRenderer])
|
||
|
||
const callsChartRef = ref<HTMLDivElement | null>(null)
|
||
const mosChartRef = ref<HTMLDivElement | null>(null)
|
||
const failedCallsChartRef = ref<HTMLDivElement | null>(null)
|
||
const regChartRef = ref<HTMLDivElement | null>(null)
|
||
const failedRegChartRef = ref<HTMLDivElement | null>(null)
|
||
|
||
// IMS网元列表
|
||
const imsNeList = ref<{ neId: string, neName: string }[]>([])
|
||
// 当前选中的IMS网元ID
|
||
const selectedImsNeId = ref('')
|
||
// WebSocket实例
|
||
const imsWs = ref<any>(null)
|
||
// IMS实时原始数据(只存储当前选中网元)
|
||
const imsRealtimeRawData = ref<any[]>([])
|
||
// WebSocket连接状态
|
||
const wsStatus = ref('未连接')
|
||
|
||
// 获取IMS网元列表
|
||
onMounted(async () => {
|
||
// console.log('组件挂载,开始获取IMS网元列表') // 调试信息
|
||
|
||
const res = await useNeListStore().fnNelist()
|
||
// console.log('获取到的网元列表响应:', res) // 调试信息
|
||
|
||
if (res && Array.isArray(res.data)) {
|
||
imsNeList.value = res.data.filter((ne: any) => ne.neType === 'IMS')
|
||
// console.log('过滤后的IMS网元列表:', imsNeList.value) // 调试信息
|
||
|
||
if (imsNeList.value.length > 0) {
|
||
selectedImsNeId.value = imsNeList.value[0].neId
|
||
// console.log('默认选中第一个IMS网元:', selectedImsNeId.value) // 调试信息
|
||
|
||
// 先获取历史数据,再订阅实时数据
|
||
await fetchHistoryData(selectedImsNeId.value)
|
||
subscribeImsRealtime(selectedImsNeId.value)
|
||
} else {
|
||
// console.warn('没有找到IMS类型的网元') // 调试信息
|
||
}
|
||
} else {
|
||
// console.error('获取网元列表失败或数据格式不正确') // 调试信息
|
||
}
|
||
})
|
||
|
||
// 获取历史数据
|
||
async function fetchHistoryData(neId: string) {
|
||
if (!neId) return
|
||
|
||
try {
|
||
// 计算30分钟前的时间
|
||
const endTime = Date.now()
|
||
const beginTime = endTime - (30 * 60 * 1000) // 30分钟前
|
||
|
||
// 构建查询参数,与黄金指标界面保持一致
|
||
const params = {
|
||
neType: 'IMS',
|
||
neId: neId,
|
||
interval: 60, // 1分钟颗粒度
|
||
beginTime: beginTime.toString(),
|
||
endTime: endTime.toString(),
|
||
sortField: 'timeGroup',
|
||
sortOrder: 'desc',
|
||
pageSize: 30, // 最多获取30条数据
|
||
pageNum: 1
|
||
}
|
||
|
||
console.log('获取历史数据参数:', params)
|
||
|
||
const res = await listKPIData(params)
|
||
console.log('历史数据响应:', res)
|
||
|
||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||
// 将历史数据转换为与实时数据相同的格式
|
||
const historyData = res.data.map((item: any) => ({
|
||
timestamp: item.timeGroup || Date.now(),
|
||
data: item
|
||
}))
|
||
|
||
console.log('转换后的历史数据:', historyData)
|
||
|
||
// 将历史数据添加到实时数据数组中(追加而不是覆盖)
|
||
// 注意:这里直接赋值,因为这是初始加载历史数据
|
||
imsRealtimeRawData.value = historyData
|
||
|
||
// 更新所有图表
|
||
updateActiveCallsChart()
|
||
updateFailedCallsChart()
|
||
updateActiveRegistrationsChart()
|
||
updateFailedRegistrationsChart()
|
||
} else {
|
||
console.warn('获取历史数据失败或数据为空')
|
||
}
|
||
} catch (error) {
|
||
console.error('获取历史数据出错:', error)
|
||
}
|
||
}
|
||
|
||
// 切换IMS网元时,重新订阅
|
||
async function onImsNeChange() {
|
||
// console.log('切换IMS网元,新的网元ID:', selectedImsNeId.value) // 调试信息
|
||
|
||
// 切换网元时,先清空旧数据
|
||
imsRealtimeRawData.value = []
|
||
|
||
// 先获取历史数据,再订阅实时数据
|
||
await fetchHistoryData(selectedImsNeId.value)
|
||
subscribeImsRealtime(selectedImsNeId.value)
|
||
}
|
||
|
||
// 订阅指定IMS网元实时数据
|
||
function subscribeImsRealtime(neId: string) {
|
||
// console.log('开始订阅IMS网元数据,网元ID:', neId) // 调试信息
|
||
wsStatus.value = '连接中...' // 更新状态
|
||
|
||
// 关闭旧WebSocket
|
||
if (imsWs.value) {
|
||
// console.log('关闭旧WebSocket连接') // 调试信息
|
||
imsWs.value.close()
|
||
imsWs.value = null
|
||
}
|
||
|
||
// 注意:不要清空历史数据,让历史数据保留
|
||
// imsRealtimeRawData.value = [] // 移除这行,避免清空历史数据
|
||
|
||
if (!neId) return
|
||
|
||
imsWs.value = new WS()
|
||
// console.log('创建新的WebSocket连接') // 调试信息
|
||
|
||
imsWs.value.connect({
|
||
url: '/ws',
|
||
params: {
|
||
subGroupID: `10_IMS_${neId}`,
|
||
},
|
||
onmessage: handleIMSRealtimeData,
|
||
onerror: (error: any) => {
|
||
// console.error('WebSocket连接错误:', error) // 调试信息
|
||
wsStatus.value = '连接错误' // 更新状态
|
||
},
|
||
onopen: () => {
|
||
// console.log('WebSocket连接已建立') // 调试信息
|
||
wsStatus.value = '已连接' // 更新状态
|
||
},
|
||
onclose: () => {
|
||
// console.log('WebSocket连接已关闭') // 调试信息
|
||
wsStatus.value = '已断开' // 更新状态
|
||
}
|
||
})
|
||
}
|
||
|
||
// 组件卸载时关闭WebSocket
|
||
onBeforeUnmount(() => {
|
||
if (imsWs.value) {
|
||
imsWs.value.close()
|
||
imsWs.value = null
|
||
}
|
||
wsStatus.value = '已断开' // 更新状态
|
||
})
|
||
|
||
// 更新active calls图表
|
||
function updateActiveCallsChart() {
|
||
if (!callsChartRef.value) return
|
||
|
||
// 获取图表实例
|
||
let chart = echarts.getInstanceByDom(callsChartRef.value)
|
||
if (!chart) {
|
||
chart = echarts.init(callsChartRef.value)
|
||
}
|
||
|
||
// 准备图表数据
|
||
const chartData = imsRealtimeRawData.value.map((item, index) => {
|
||
const kpiEvent = item.data
|
||
const scscf07 = Number(kpiEvent['SCSCF.07']) || 0
|
||
return scscf07
|
||
})
|
||
|
||
console.log('updateActiveCallsChart - 原始数据点数量:', imsRealtimeRawData.value.length)
|
||
console.log('updateActiveCallsChart - 图表数据:', chartData)
|
||
|
||
// 如果没有数据,显示默认的平直线
|
||
if (chartData.length === 0) {
|
||
const defaultData = [0, 0, 0, 0, 0] // 5个默认数据点,值为0
|
||
const xAxisData = [1, 2, 3, 4, 5]
|
||
|
||
chart.setOption({
|
||
grid: { left: 0, right: 0, top: 10, bottom: 10 },
|
||
xAxis: { type: 'category', show: false, data: xAxisData },
|
||
yAxis: { type: 'value', show: false },
|
||
series: [{
|
||
data: defaultData,
|
||
type: 'line', symbol: 'none',
|
||
lineStyle: { width: 2, color: '#d9d9d9' }, // 灰色线条表示无数据
|
||
areaStyle: { color: 'rgba(217,217,217,0.1)' } // 淡灰色填充
|
||
}]
|
||
})
|
||
return
|
||
}
|
||
|
||
// 如果数据不足,补充默认数据
|
||
while (chartData.length < 5) {
|
||
chartData.unshift(0) // 在开头补充默认值0
|
||
}
|
||
|
||
// 限制数据点数量为最近30个
|
||
if (chartData.length > 30) {
|
||
chartData.splice(0, chartData.length - 30)
|
||
}
|
||
|
||
console.log('updateActiveCallsChart - 最终图表数据点数量:', chartData.length)
|
||
|
||
// 生成时间轴数据
|
||
const xAxisData = Array.from({ length: chartData.length }, (_, i) => i + 1)
|
||
|
||
chart.setOption({
|
||
grid: { left: 0, right: 0, top: 10, bottom: 10 },
|
||
xAxis: { type: 'category', show: false, data: xAxisData },
|
||
yAxis: { type: 'value', show: false },
|
||
series: [{
|
||
data: chartData,
|
||
type: 'line', symbol: 'none',
|
||
lineStyle: { width: 2, color: '#1890ff' }, // 蓝色线条表示有数据
|
||
areaStyle: { color: 'rgba(24,144,255,0.1)' } // 淡蓝色填充
|
||
}]
|
||
})
|
||
}
|
||
|
||
// 更新failed calls图表
|
||
function updateFailedCallsChart() {
|
||
if (!failedCallsChartRef.value) return
|
||
|
||
// 获取图表实例
|
||
let chart = echarts.getInstanceByDom(failedCallsChartRef.value)
|
||
if (!chart) {
|
||
chart = echarts.init(failedCallsChartRef.value)
|
||
}
|
||
|
||
// 准备图表数据
|
||
const chartData = imsRealtimeRawData.value.map((item, index) => {
|
||
const kpiEvent = item.data
|
||
const scscf06 = Number(kpiEvent['SCSCF.06']) || 0
|
||
const scscf07 = Number(kpiEvent['SCSCF.07']) || 0
|
||
const failedCalls = scscf06 - scscf07
|
||
return failedCalls
|
||
})
|
||
|
||
// 如果没有数据,显示默认的平直线
|
||
if (chartData.length === 0) {
|
||
const defaultData = [0, 0, 0, 0, 0] // 5个默认数据点,值为0
|
||
const xAxisData = [1, 2, 3, 4, 5]
|
||
|
||
chart.setOption({
|
||
grid: { left: 0, right: 0, top: 10, bottom: 10 },
|
||
xAxis: { type: 'category', show: false, data: xAxisData },
|
||
yAxis: { type: 'value', show: false },
|
||
series: [{
|
||
data: defaultData,
|
||
type: 'line', symbol: 'none',
|
||
lineStyle: { width: 2, color: '#d9d9d9' }, // 灰色线条表示无数据
|
||
areaStyle: { color: 'rgba(217,217,217,0.1)' } // 淡灰色填充
|
||
}]
|
||
})
|
||
return
|
||
}
|
||
|
||
// 如果数据不足,补充默认数据
|
||
while (chartData.length < 5) {
|
||
chartData.unshift(0) // 在开头补充默认值0
|
||
}
|
||
|
||
// 限制数据点数量为最近30个
|
||
if (chartData.length > 30) {
|
||
chartData.splice(0, chartData.length - 30)
|
||
}
|
||
|
||
// 生成时间轴数据
|
||
const xAxisData = Array.from({ length: chartData.length }, (_, i) => i + 1)
|
||
|
||
chart.setOption({
|
||
grid: { left: 0, right: 0, top: 10, bottom: 10 },
|
||
xAxis: { type: 'category', show: false, data: xAxisData },
|
||
yAxis: { type: 'value', show: false },
|
||
series: [{
|
||
data: chartData,
|
||
type: 'line', symbol: 'none',
|
||
lineStyle: { width: 2, color: '#faad14' }, // 橙色线条表示failed calls
|
||
areaStyle: { color: 'rgba(250,173,20,0.1)' } // 淡橙色填充
|
||
}]
|
||
})
|
||
}
|
||
|
||
// 更新active registrations图表
|
||
function updateActiveRegistrationsChart() {
|
||
if (!regChartRef.value) return
|
||
|
||
// 获取图表实例
|
||
let chart = echarts.getInstanceByDom(regChartRef.value)
|
||
if (!chart) {
|
||
chart = echarts.init(regChartRef.value)
|
||
}
|
||
|
||
// 准备图表数据
|
||
const chartData = imsRealtimeRawData.value.map((item, index) => {
|
||
const kpiEvent = item.data
|
||
const scscf03 = Number(kpiEvent['SCSCF.03']) || 0
|
||
return scscf03
|
||
})
|
||
|
||
// 如果没有数据,显示默认的平直线
|
||
if (chartData.length === 0) {
|
||
const defaultData = [0, 0, 0, 0, 0] // 5个默认数据点,值为0
|
||
const xAxisData = [1, 2, 3, 4, 5]
|
||
|
||
chart.setOption({
|
||
grid: { left: 0, right: 0, top: 10, bottom: 10 },
|
||
xAxis: { type: 'category', show: false, data: xAxisData },
|
||
yAxis: { type: 'value', show: false },
|
||
series: [{
|
||
data: defaultData,
|
||
type: 'line', symbol: 'none',
|
||
lineStyle: { width: 2, color: '#d9d9d9' }, // 灰色线条表示无数据
|
||
areaStyle: { color: 'rgba(217,217,217,0.1)' } // 淡灰色填充
|
||
}]
|
||
})
|
||
return
|
||
}
|
||
|
||
// 如果数据不足,补充默认数据
|
||
while (chartData.length < 5) {
|
||
chartData.unshift(0) // 在开头补充默认值0
|
||
}
|
||
|
||
// 限制数据点数量为最近30个
|
||
if (chartData.length > 30) {
|
||
chartData.splice(0, chartData.length - 30)
|
||
}
|
||
|
||
// 生成时间轴数据
|
||
const xAxisData = Array.from({ length: chartData.length }, (_, i) => i + 1)
|
||
|
||
chart.setOption({
|
||
grid: { left: 0, right: 0, top: 10, bottom: 10 },
|
||
xAxis: { type: 'category', show: false, data: xAxisData },
|
||
yAxis: { type: 'value', show: false },
|
||
series: [{
|
||
data: chartData,
|
||
type: 'line', symbol: 'none',
|
||
lineStyle: { width: 2, color: '#1890ff' }, // 蓝色线条表示active registrations
|
||
areaStyle: { color: 'rgba(24,144,255,0.1)' } // 淡蓝色填充
|
||
}]
|
||
})
|
||
}
|
||
|
||
// 更新failed registrations图表
|
||
function updateFailedRegistrationsChart() {
|
||
if (!failedRegChartRef.value) return
|
||
|
||
// 获取图表实例
|
||
let chart = echarts.getInstanceByDom(failedRegChartRef.value)
|
||
if (!chart) {
|
||
chart = echarts.init(failedRegChartRef.value)
|
||
}
|
||
|
||
// 准备图表数据
|
||
const chartData = imsRealtimeRawData.value.map((item, index) => {
|
||
const kpiEvent = item.data
|
||
const scscf04 = Number(kpiEvent['SCSCF.04']) || 0
|
||
const scscf03 = Number(kpiEvent['SCSCF.03']) || 0
|
||
const failedRegistrations = scscf04 - scscf03
|
||
return failedRegistrations
|
||
})
|
||
|
||
// 如果没有数据,显示默认的平直线
|
||
if (chartData.length === 0) {
|
||
const defaultData = [0, 0, 0, 0, 0] // 5个默认数据点,值为0
|
||
const xAxisData = [1, 2, 3, 4, 5]
|
||
|
||
chart.setOption({
|
||
grid: { left: 0, right: 0, top: 10, bottom: 10 },
|
||
xAxis: { type: 'category', show: false, data: xAxisData },
|
||
yAxis: { type: 'value', show: false },
|
||
series: [{
|
||
data: defaultData,
|
||
type: 'line', symbol: 'none',
|
||
lineStyle: { width: 2, color: '#d9d9d9' }, // 灰色线条表示无数据
|
||
areaStyle: { color: 'rgba(217,217,217,0.1)' } // 淡灰色填充
|
||
}]
|
||
})
|
||
return
|
||
}
|
||
|
||
// 如果数据不足,补充默认数据
|
||
while (chartData.length < 5) {
|
||
chartData.unshift(0) // 在开头补充默认值0
|
||
}
|
||
|
||
// 限制数据点数量为最近30个
|
||
if (chartData.length > 30) {
|
||
chartData.splice(0, chartData.length - 30)
|
||
}
|
||
|
||
// 生成时间轴数据
|
||
const xAxisData = Array.from({ length: chartData.length }, (_, i) => i + 1)
|
||
|
||
chart.setOption({
|
||
grid: { left: 0, right: 0, top: 10, bottom: 10 },
|
||
xAxis: { type: 'category', show: false, data: xAxisData },
|
||
yAxis: { type: 'value', show: false },
|
||
series: [{
|
||
data: chartData,
|
||
type: 'line', symbol: 'none',
|
||
lineStyle: { width: 2, color: '#f5222d' }, // 红色线条表示failed registrations
|
||
areaStyle: { color: 'rgba(245,34,45,0.1)' } // 淡红色填充
|
||
}]
|
||
})
|
||
}
|
||
|
||
// 处理IMS实时数据(只存储当前选中网元)
|
||
function handleIMSRealtimeData(res: any) {
|
||
// console.log('收到实时数据:', res) // 调试信息
|
||
|
||
// 检查数据结构:后端实际格式是 {code: 1, data: {...}, msg: 'success'}
|
||
const { code, data, msg } = res
|
||
|
||
// 检查是否是错误响应
|
||
if (code !== 1 || !data) {
|
||
// console.warn('收到错误响应或数据格式不正确:', res) // 调试信息
|
||
return
|
||
}
|
||
|
||
// 检查是否是连接确认消息(只包含clientId)
|
||
if (data.clientId) {
|
||
// console.log('收到WebSocket连接确认消息:', data.clientId) // 调试信息
|
||
return
|
||
}
|
||
|
||
// 检查是否是KPI数据消息(包含data和groupId)
|
||
if (data.data && data.groupId) {
|
||
// console.log('收到KPI数据消息,groupId:', data.groupId) // 调试信息
|
||
|
||
// 解析订阅组ID,确认是我们订阅的IMS网元
|
||
const [_, neType, neId] = data.groupId.split('_')
|
||
if (neType !== 'IMS' || neId !== selectedImsNeId.value) {
|
||
// console.log('收到其他网元数据,忽略:', data.groupId) // 调试信息
|
||
return
|
||
}
|
||
|
||
const kpiEvent = data.data
|
||
if (!kpiEvent) {
|
||
// console.warn('KPI事件数据为空') // 调试信息
|
||
return
|
||
}
|
||
|
||
// console.log('处理IMS网元KPI数据:', kpiEvent) // 调试信息
|
||
|
||
// 确保数据结构正确
|
||
const dataToStore = {
|
||
timestamp: Date.now(),
|
||
data: kpiEvent
|
||
}
|
||
|
||
// 将新数据添加到数组末尾(最新的数据在最后)
|
||
imsRealtimeRawData.value.push(dataToStore)
|
||
|
||
// 保持数据点数量在合理范围内(最多200个点)
|
||
if (imsRealtimeRawData.value.length > 200) {
|
||
imsRealtimeRawData.value.shift() // 移除最早的数据点
|
||
}
|
||
|
||
console.log('实时数据已添加,当前数据点数量:', imsRealtimeRawData.value.length)
|
||
console.log('最新数据:', dataToStore)
|
||
|
||
// 更新active calls图表
|
||
updateActiveCallsChart()
|
||
// 更新failed calls图表
|
||
updateFailedCallsChart()
|
||
// 更新active registrations图表
|
||
updateActiveRegistrationsChart()
|
||
// 更新failed registrations图表
|
||
updateFailedRegistrationsChart()
|
||
} else {
|
||
// console.log('收到未知格式的数据:', data) // 调试信息
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
// 初始化所有图表为0值线
|
||
const defaultChartOption = {
|
||
grid: { left: 0, right: 0, top: 10, bottom: 10 },
|
||
xAxis: { type: 'category', show: false, data: [1, 2, 3, 4, 5] },
|
||
yAxis: { type: 'value', show: false },
|
||
series: [{
|
||
data: [0, 0, 0, 0, 0], // 默认显示0值
|
||
type: 'line', symbol: 'none',
|
||
lineStyle: { width: 2, color: '#d9d9d9' }, // 灰色线条表示无数据
|
||
areaStyle: { color: 'rgba(217,217,217,0.1)' } // 淡灰色填充
|
||
}]
|
||
}
|
||
|
||
// active calls
|
||
if (callsChartRef.value) {
|
||
const chart = echarts.init(callsChartRef.value)
|
||
chart.setOption(defaultChartOption)
|
||
}
|
||
|
||
// MOS - 保持原有的模拟数据
|
||
if (mosChartRef.value) {
|
||
const chart = echarts.init(mosChartRef.value)
|
||
chart.setOption({
|
||
grid: { left: 0, right: 0, top: 10, bottom: 10 },
|
||
xAxis: { type: 'category', show: false, data: [1,2,3,4,5] },
|
||
yAxis: { type: 'value', show: false },
|
||
series: [{
|
||
data: [4.62, 4.50, 4.40, 4.35, 4.30],
|
||
type: 'line', symbol: 'none',
|
||
lineStyle: { width: 2, color: '#52c41a' },
|
||
areaStyle: { color: 'rgba(82,196,26,0.1)' }
|
||
}]
|
||
})
|
||
}
|
||
|
||
// failed calls
|
||
if (failedCallsChartRef.value) {
|
||
const chart = echarts.init(failedCallsChartRef.value)
|
||
chart.setOption(defaultChartOption)
|
||
}
|
||
|
||
// active registrations
|
||
if (regChartRef.value) {
|
||
const chart = echarts.init(regChartRef.value)
|
||
chart.setOption(defaultChartOption)
|
||
}
|
||
|
||
// failed registrations
|
||
if (failedRegChartRef.value) {
|
||
const chart = echarts.init(failedRegChartRef.value)
|
||
chart.setOption(defaultChartOption)
|
||
}
|
||
})
|
||
|
||
// 计算MO值 (SCSCF.05/SCSCF.06)*100
|
||
function calculateMOValue() {
|
||
if (imsRealtimeRawData.value.length === 0) return '-'
|
||
|
||
// 获取最新的数据
|
||
const latestData = imsRealtimeRawData.value[imsRealtimeRawData.value.length - 1]
|
||
if (!latestData || !latestData.data) return '-'
|
||
|
||
const kpiEvent = latestData.data
|
||
const scscf05 = Number(kpiEvent['SCSCF.05']) || 0
|
||
const scscf06 = Number(kpiEvent['SCSCF.06']) || 0
|
||
|
||
if (scscf06 === 0) return '-'
|
||
|
||
const moValue = (scscf05 / scscf06) * 100
|
||
return moValue.toFixed(1)
|
||
}
|
||
|
||
// 计算MT值 (SCSCF.06/SCSCF.07)*100
|
||
function calculateMTValue() {
|
||
if (imsRealtimeRawData.value.length === 0) return '-'
|
||
|
||
// 获取最新的数据
|
||
const latestData = imsRealtimeRawData.value[imsRealtimeRawData.value.length - 1]
|
||
if (!latestData || !latestData.data) return '-'
|
||
|
||
const kpiEvent = latestData.data
|
||
const scscf06 = Number(kpiEvent['SCSCF.06']) || 0
|
||
const scscf07 = Number(kpiEvent['SCSCF.07']) || 0
|
||
|
||
if (scscf07 === 0) return '-'
|
||
|
||
const mtValue = (scscf06 / scscf07) * 100
|
||
return mtValue.toFixed(1)
|
||
}
|
||
|
||
// 计算MO变化值(完善版本)
|
||
function calculateMOChange() {
|
||
if (imsRealtimeRawData.value.length < 2) return '±0.00% last 1m'
|
||
|
||
// 获取最新和上一个数据点
|
||
const latestData = imsRealtimeRawData.value[imsRealtimeRawData.value.length - 1]
|
||
const previousData = imsRealtimeRawData.value[imsRealtimeRawData.value.length - 2]
|
||
|
||
if (!latestData?.data || !previousData?.data) return '±0.00% last 1m'
|
||
|
||
const latestKpi = latestData.data
|
||
const previousKpi = previousData.data
|
||
|
||
const latestMO = calculateMOValueFromData(latestKpi)
|
||
const previousMO = calculateMOValueFromData(previousKpi)
|
||
|
||
// 检查MO是否有有效值
|
||
if (latestMO === '-' || previousMO === '-') return '±0.00% last 5s'
|
||
|
||
// 计算变化幅度
|
||
const change = latestMO - previousMO
|
||
|
||
// 检查是否有变化
|
||
if (change === 0) return '±0.00% last 5s'
|
||
|
||
const changeText = change > 0 ? `+${change.toFixed(2)}%` : `${change.toFixed(2)}%`
|
||
|
||
// 计算时间差
|
||
const timeDiff = calculateTimeDifference(latestData, previousData)
|
||
|
||
return `${changeText} last ${timeDiff}`
|
||
}
|
||
|
||
// 计算MT变化值(完善版本)
|
||
function calculateMTChange() {
|
||
if (imsRealtimeRawData.value.length < 2) return '±0.00% last 5s'
|
||
|
||
// 获取最新和上一个数据点
|
||
const latestData = imsRealtimeRawData.value[imsRealtimeRawData.value.length - 1]
|
||
const previousData = imsRealtimeRawData.value[imsRealtimeRawData.value.length - 2]
|
||
|
||
if (!latestData?.data || !previousData?.data) return '±0.00% last 5s'
|
||
|
||
const latestKpi = latestData.data
|
||
const previousKpi = previousData.data
|
||
|
||
const latestMT = calculateMTValueFromData(latestKpi)
|
||
const previousMT = calculateMTValueFromData(previousKpi)
|
||
|
||
// 检查MT是否有有效值
|
||
if (latestMT === '-' || previousMT === '-') return '±0.00% last 5s'
|
||
|
||
// 计算变化幅度
|
||
const change = latestMT - previousMT
|
||
|
||
// 检查是否有变化
|
||
if (change === 0) return '±0.00% last 5s'
|
||
|
||
const changeText = change > 0 ? `+${change.toFixed(2)}%` : `${change.toFixed(2)}%`
|
||
|
||
// 计算时间差
|
||
const timeDiff = calculateTimeDifference(latestData, previousData)
|
||
|
||
return `${changeText} last ${timeDiff}`
|
||
}
|
||
|
||
// 计算时间差函数
|
||
function calculateTimeDifference(latestData: any, previousData: any) {
|
||
// 获取时间戳
|
||
const latestTime = latestData.timestamp || latestData.time || Date.now()
|
||
const previousTime = previousData.timestamp || previousData.time || Date.now()
|
||
|
||
// 计算时间差(毫秒)
|
||
const diffMs = Math.abs(latestTime - previousTime)
|
||
|
||
// 转换为秒
|
||
const diffSeconds = Math.floor(diffMs / 1000)
|
||
|
||
// 转换为分钟
|
||
const diffMinutes = Math.floor(diffSeconds / 60)
|
||
|
||
// 根据时间差返回合适的格式
|
||
if (diffMinutes > 0) {
|
||
return `${diffMinutes}m`
|
||
} else {
|
||
return `${diffSeconds}s`
|
||
}
|
||
}
|
||
|
||
// 辅助函数:从数据中计算MO值
|
||
function calculateMOValueFromData(kpiEvent: any) {
|
||
const scscf05 = Number(kpiEvent['SCSCF.05']) || 0
|
||
const scscf06 = Number(kpiEvent['SCSCF.06']) || 0
|
||
return scscf06 === 0 ? '-' : (scscf05 / scscf06) * 100
|
||
}
|
||
|
||
// 辅助函数:从数据中计算MT值
|
||
function calculateMTValueFromData(kpiEvent: any) {
|
||
const scscf06 = Number(kpiEvent['SCSCF.06']) || 0
|
||
const scscf07 = Number(kpiEvent['SCSCF.07']) || 0
|
||
return scscf07 === 0 ? '-' : (scscf06 / scscf07) * 100
|
||
}
|
||
|
||
// 计算registration success值
|
||
function calculateRegSuccessValue() {
|
||
if (imsRealtimeRawData.value.length === 0) return '-'
|
||
|
||
// 获取最新的数据
|
||
const latestData = imsRealtimeRawData.value[imsRealtimeRawData.value.length - 1]
|
||
if (!latestData || !latestData.data) return '-'
|
||
|
||
const kpiEvent = latestData.data
|
||
const scscf03 = Number(kpiEvent['SCSCF.03']) || 0
|
||
const scscf04 = Number(kpiEvent['SCSCF.04']) || 0
|
||
|
||
if (scscf04 === 0) return '-'
|
||
|
||
const regSuccessValue = (scscf03 / scscf04) * 100
|
||
return regSuccessValue.toFixed(1)
|
||
}
|
||
|
||
// 计算registration success变化值
|
||
function calculateRegSuccessChange() {
|
||
if (imsRealtimeRawData.value.length < 2) return '±0.00% last 5s'
|
||
|
||
// 获取最新和上一个数据点
|
||
const latestData = imsRealtimeRawData.value[imsRealtimeRawData.value.length - 1]
|
||
const previousData = imsRealtimeRawData.value[imsRealtimeRawData.value.length - 2]
|
||
|
||
if (!latestData?.data || !previousData?.data) return '±0.00% last 5s'
|
||
|
||
const latestKpi = latestData.data
|
||
const previousKpi = previousData.data
|
||
|
||
const latestRegSuccess = calculateRegSuccessValueFromData(latestKpi)
|
||
const previousRegSuccess = calculateRegSuccessValueFromData(previousKpi)
|
||
|
||
// 检查registration success是否有有效值
|
||
if (latestRegSuccess === '-' || previousRegSuccess === '-') return '±0.00% last 5s'
|
||
|
||
// 计算变化幅度
|
||
const change = latestRegSuccess - previousRegSuccess
|
||
|
||
// 检查是否有变化
|
||
if (change === 0) return '±0.00% last 5s'
|
||
|
||
const changeText = change > 0 ? `+${change.toFixed(2)}%` : `${change.toFixed(2)}%`
|
||
|
||
// 计算时间差
|
||
const timeDiff = calculateTimeDifference(latestData, previousData)
|
||
|
||
return `${changeText} last ${timeDiff}`
|
||
}
|
||
|
||
// 辅助函数:从数据中计算registration success值
|
||
function calculateRegSuccessValueFromData(kpiEvent: any) {
|
||
const scscf03 = Number(kpiEvent['SCSCF.03']) || 0
|
||
const scscf04 = Number(kpiEvent['SCSCF.04']) || 0
|
||
return scscf04 === 0 ? '-' : (scscf03 / scscf04) * 100
|
||
}
|
||
|
||
// 计算active calls值
|
||
function calculateActiveCallsValue() {
|
||
if (imsRealtimeRawData.value.length === 0) return '-'
|
||
|
||
// 获取最新的数据
|
||
const latestData = imsRealtimeRawData.value[imsRealtimeRawData.value.length - 1]
|
||
if (!latestData || !latestData.data) return '-'
|
||
|
||
const kpiEvent = latestData.data
|
||
const scscf07 = Number(kpiEvent['SCSCF.07']) || 0
|
||
|
||
if (scscf07 === 0) return '-'
|
||
|
||
const activeCallsValue = scscf07
|
||
return activeCallsValue.toFixed(0)
|
||
}
|
||
|
||
// 计算active calls箭头方向
|
||
function calculateActiveCallsArrowDirection() {
|
||
const changeText = calculateActiveCallsChange()
|
||
|
||
if (changeText.startsWith('+')) return 'up'
|
||
if (changeText.startsWith('-')) return 'down'
|
||
return 'up' // ±0 或无变化时默认向上
|
||
}
|
||
|
||
// 计算active calls箭头
|
||
function calculateActiveCallsArrow() {
|
||
const changeText = calculateActiveCallsChange()
|
||
|
||
if (changeText.startsWith('+')) return '↗'
|
||
if (changeText.startsWith('-')) return '↘'
|
||
return '→' // ±0 或无变化时显示水平箭头
|
||
}
|
||
|
||
// 计算active calls变化值
|
||
function calculateActiveCallsChange() {
|
||
if (imsRealtimeRawData.value.length < 2) return '±0 last 5s'
|
||
|
||
// 获取最新和上一个数据点
|
||
const latestData = imsRealtimeRawData.value[imsRealtimeRawData.value.length - 1]
|
||
const previousData = imsRealtimeRawData.value[imsRealtimeRawData.value.length - 2]
|
||
|
||
if (!latestData?.data || !previousData?.data) return '±0 last 5s'
|
||
|
||
const latestKpi = latestData.data
|
||
const previousKpi = previousData.data
|
||
|
||
const latestActiveCalls = Number(latestKpi['SCSCF.07']) || 0
|
||
const previousActiveCalls = Number(previousKpi['SCSCF.07']) || 0
|
||
|
||
// 检查active calls是否有有效值
|
||
if (latestActiveCalls === 0 || previousActiveCalls === 0) return '±0 last 5s'
|
||
|
||
// 计算变化幅度
|
||
const change = latestActiveCalls - previousActiveCalls
|
||
|
||
// 检查是否有变化
|
||
if (change === 0) return '±0 last 5s'
|
||
|
||
const changeText = change > 0 ? `+${change.toFixed(0)}` : `${change.toFixed(0)}`
|
||
|
||
// 计算时间差
|
||
const timeDiff = calculateTimeDifference(latestData, previousData)
|
||
|
||
return `${changeText} last ${timeDiff}`
|
||
}
|
||
|
||
// 计算failed calls值
|
||
function calculateFailedCallsValue() {
|
||
if (imsRealtimeRawData.value.length === 0) return '-'
|
||
|
||
// 获取最新的数据
|
||
const latestData = imsRealtimeRawData.value[imsRealtimeRawData.value.length - 1]
|
||
if (!latestData || !latestData.data) return '-'
|
||
|
||
const kpiEvent = latestData.data
|
||
const scscf06 = Number(kpiEvent['SCSCF.06']) || 0
|
||
const scscf07 = Number(kpiEvent['SCSCF.07']) || 0
|
||
|
||
const failedCallsValue = scscf06 - scscf07
|
||
return failedCallsValue.toFixed(0)
|
||
}
|
||
|
||
// 计算failed calls箭头方向
|
||
function calculateFailedCallsArrowDirection() {
|
||
const changeText = calculateFailedCallsChange()
|
||
|
||
if (changeText.startsWith('+')) return 'up'
|
||
if (changeText.startsWith('-')) return 'down'
|
||
return 'up' // ±0 或无变化时默认向上
|
||
}
|
||
|
||
// 计算failed calls箭头
|
||
function calculateFailedCallsArrow() {
|
||
const changeText = calculateFailedCallsChange()
|
||
|
||
if (changeText.startsWith('+')) return '↗'
|
||
if (changeText.startsWith('-')) return '↘'
|
||
return '→' // ±0 或无变化时显示水平箭头
|
||
}
|
||
|
||
// 计算failed calls变化值
|
||
function calculateFailedCallsChange() {
|
||
if (imsRealtimeRawData.value.length < 2) return '±0 last 5s'
|
||
|
||
// 获取最新和上一个数据点
|
||
const latestData = imsRealtimeRawData.value[imsRealtimeRawData.value.length - 1]
|
||
const previousData = imsRealtimeRawData.value[imsRealtimeRawData.value.length - 2]
|
||
|
||
if (!latestData?.data || !previousData?.data) return '±0 last 5s'
|
||
|
||
const latestKpi = latestData.data
|
||
const previousKpi = previousData.data
|
||
|
||
const latestScscf06 = Number(latestKpi['SCSCF.06']) || 0
|
||
const latestScscf07 = Number(latestKpi['SCSCF.07']) || 0
|
||
const previousScscf06 = Number(previousKpi['SCSCF.06']) || 0
|
||
const previousScscf07 = Number(previousKpi['SCSCF.07']) || 0
|
||
|
||
const latestFailedCalls = latestScscf06 - latestScscf07
|
||
const previousFailedCalls = previousScscf06 - previousScscf07
|
||
|
||
// 计算变化幅度
|
||
const change = latestFailedCalls - previousFailedCalls
|
||
|
||
// 检查是否有变化
|
||
if (change === 0) return '±0 last 5s'
|
||
|
||
const changeText = change > 0 ? `+${change.toFixed(0)}` : `${change.toFixed(0)}`
|
||
|
||
// 计算时间差
|
||
const timeDiff = calculateTimeDifference(latestData, previousData)
|
||
|
||
return `${changeText} last ${timeDiff}`
|
||
}
|
||
|
||
// 计算active registrations值
|
||
function calculateActiveRegistrationsValue() {
|
||
if (imsRealtimeRawData.value.length === 0) return '-'
|
||
|
||
// 获取最新的数据
|
||
const latestData = imsRealtimeRawData.value[imsRealtimeRawData.value.length - 1]
|
||
if (!latestData || !latestData.data) return '-'
|
||
|
||
const kpiEvent = latestData.data
|
||
const scscf03 = Number(kpiEvent['SCSCF.03']) || 0
|
||
|
||
if (scscf03 === 0) return '-'
|
||
|
||
const activeRegistrationsValue = scscf03
|
||
return activeRegistrationsValue.toFixed(0)
|
||
}
|
||
|
||
// 计算active registrations箭头方向
|
||
function calculateActiveRegistrationsArrowDirection() {
|
||
const changeText = calculateActiveRegistrationsChange()
|
||
|
||
if (changeText.startsWith('+')) return 'up'
|
||
if (changeText.startsWith('-')) return 'down'
|
||
return 'up' // ±0 或无变化时默认向上
|
||
}
|
||
|
||
// 计算active registrations箭头
|
||
function calculateActiveRegistrationsArrow() {
|
||
const changeText = calculateActiveRegistrationsChange()
|
||
|
||
if (changeText.startsWith('+')) return '↗'
|
||
if (changeText.startsWith('-')) return '↘'
|
||
return '→' // ±0 或无变化时显示水平箭头
|
||
}
|
||
|
||
// 计算active registrations变化值
|
||
function calculateActiveRegistrationsChange() {
|
||
if (imsRealtimeRawData.value.length < 2) return '±0 last 5s'
|
||
|
||
// 获取最新和上一个数据点
|
||
const latestData = imsRealtimeRawData.value[imsRealtimeRawData.value.length - 1]
|
||
const previousData = imsRealtimeRawData.value[imsRealtimeRawData.value.length - 2]
|
||
|
||
if (!latestData?.data || !previousData?.data) return '±0 last 5s'
|
||
|
||
const latestKpi = latestData.data
|
||
const previousKpi = previousData.data
|
||
|
||
const latestActiveRegistrations = Number(latestKpi['SCSCF.03']) || 0
|
||
const previousActiveRegistrations = Number(previousKpi['SCSCF.03']) || 0
|
||
|
||
// 检查active registrations是否有有效值
|
||
if (latestActiveRegistrations === 0 || previousActiveRegistrations === 0) return '±0 last 5s'
|
||
|
||
// 计算变化幅度
|
||
const change = latestActiveRegistrations - previousActiveRegistrations
|
||
|
||
// 检查是否有变化
|
||
if (change === 0) return '±0 last 5s'
|
||
|
||
const changeText = change > 0 ? `+${change.toFixed(0)}` : `${change.toFixed(0)}`
|
||
|
||
// 计算时间差
|
||
const timeDiff = calculateTimeDifference(latestData, previousData)
|
||
|
||
return `${changeText} last ${timeDiff}`
|
||
}
|
||
|
||
// 计算failed registrations值
|
||
function calculateFailedRegistrationsValue() {
|
||
if (imsRealtimeRawData.value.length === 0) return '-'
|
||
|
||
// 获取最新的数据
|
||
const latestData = imsRealtimeRawData.value[imsRealtimeRawData.value.length - 1]
|
||
if (!latestData || !latestData.data) return '-'
|
||
|
||
const kpiEvent = latestData.data
|
||
const scscf04 = Number(kpiEvent['SCSCF.04']) || 0
|
||
const scscf03 = Number(kpiEvent['SCSCF.03']) || 0
|
||
|
||
const failedRegistrationsValue = scscf04 - scscf03
|
||
return failedRegistrationsValue.toFixed(0)
|
||
}
|
||
|
||
// 计算failed registrations箭头方向
|
||
function calculateFailedRegistrationsArrowDirection() {
|
||
const changeText = calculateFailedRegistrationsChange()
|
||
|
||
if (changeText.startsWith('+')) return 'up'
|
||
if (changeText.startsWith('-')) return 'down'
|
||
return 'up' // ±0 或无变化时默认向上
|
||
}
|
||
|
||
// 计算failed registrations箭头
|
||
function calculateFailedRegistrationsArrow() {
|
||
const changeText = calculateFailedRegistrationsChange()
|
||
|
||
if (changeText.startsWith('+')) return '↗'
|
||
if (changeText.startsWith('-')) return '↘'
|
||
return '→' // ±0 或无变化时显示水平箭头
|
||
}
|
||
|
||
// 计算failed registrations变化值
|
||
function calculateFailedRegistrationsChange() {
|
||
if (imsRealtimeRawData.value.length < 2) return '±0 last 5s'
|
||
|
||
// 获取最新和上一个数据点
|
||
const latestData = imsRealtimeRawData.value[imsRealtimeRawData.value.length - 1]
|
||
const previousData = imsRealtimeRawData.value[imsRealtimeRawData.value.length - 2]
|
||
|
||
if (!latestData?.data || !previousData?.data) return '±0 last 5s'
|
||
|
||
const latestKpi = latestData.data
|
||
const previousKpi = previousData.data
|
||
|
||
const latestScscf04 = Number(latestKpi['SCSCF.04']) || 0
|
||
const latestScscf03 = Number(latestKpi['SCSCF.03']) || 0
|
||
const previousScscf04 = Number(previousKpi['SCSCF.04']) || 0
|
||
const previousScscf03 = Number(previousKpi['SCSCF.03']) || 0
|
||
|
||
const latestFailedRegistrations = latestScscf04 - latestScscf03
|
||
const previousFailedRegistrations = previousScscf04 - previousScscf03
|
||
|
||
// 计算变化幅度
|
||
const change = latestFailedRegistrations - previousFailedRegistrations
|
||
|
||
// 检查是否有变化
|
||
if (change === 0) return '±0 last 5s'
|
||
|
||
const changeText = change > 0 ? `+${change.toFixed(0)}` : `${change.toFixed(0)}`
|
||
|
||
// 计算时间差
|
||
const timeDiff = calculateTimeDifference(latestData, previousData)
|
||
|
||
return `${changeText} last ${timeDiff}`
|
||
}
|
||
|
||
// 测试数据更新
|
||
function testDataUpdate() {
|
||
// console.log('测试数据更新')
|
||
|
||
// 创建模拟的后端KPI数据消息格式
|
||
const mockWebSocketMessage = {
|
||
code: 1,
|
||
data: {
|
||
data: {
|
||
'SCSCF.03': Math.floor(Math.random() * 300000) + 200000, // active registrations
|
||
'SCSCF.04': Math.floor(Math.random() * 310000) + 200000, // total registrations
|
||
'SCSCF.05': Math.floor(Math.random() * 15000) + 10000, // MO calls
|
||
'SCSCF.06': Math.floor(Math.random() * 16000) + 10000, // total calls
|
||
'SCSCF.07': Math.floor(Math.random() * 15000) + 10000, // successful calls
|
||
},
|
||
groupId: `10_IMS_${selectedImsNeId.value}`
|
||
},
|
||
msg: 'success'
|
||
}
|
||
|
||
// console.log('模拟WebSocket消息:', mockWebSocketMessage)
|
||
|
||
// 直接调用handleIMSRealtimeData函数
|
||
handleIMSRealtimeData(mockWebSocketMessage)
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.row-title {
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
margin-bottom: 16px;
|
||
padding-left: 8px;
|
||
border-left: 3px solid #1890ff;
|
||
}
|
||
.dashboard-cards {
|
||
padding: 16px;
|
||
}
|
||
.metric-card {
|
||
border-radius: 16px;
|
||
height: 100%;
|
||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.09);
|
||
min-height: 180px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
}
|
||
.card-title {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
margin-bottom: 16px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
.card-content {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.trend-chart {
|
||
flex: 2;
|
||
height: 60px;
|
||
min-width: 140px;
|
||
max-width: 220px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: flex-end;
|
||
}
|
||
.mini-chart {
|
||
width: 100%;
|
||
height: 100%;
|
||
margin-bottom: 0;
|
||
display: block;
|
||
}
|
||
.card-subtext {
|
||
font-size: 12px;
|
||
color: #b0b0b0;
|
||
margin-top: 2px;
|
||
letter-spacing: 0.1em;
|
||
}
|
||
.card-sub-sep {
|
||
margin-left: 18px;
|
||
}
|
||
.metric-info {
|
||
flex: 1;
|
||
margin-left: 16px;
|
||
text-align: right;
|
||
min-width: 90px;
|
||
}
|
||
.metric-value {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
}
|
||
.metric-change {
|
||
font-size: 12px;
|
||
color: #999;
|
||
margin-top: 4px;
|
||
}
|
||
.main-arrow.up { color: #4CAF50; margin-left: 8px; }
|
||
.main-arrow.down { color: #F44336; margin-left: 8px; }
|
||
.main-icon.phone { color: #1890ff; font-size: 22px; margin-left: 8px; }
|
||
.main-icon.calendar { color: #52c41a; font-size: 22px; margin-left: 8px; }
|
||
.full-width {
|
||
width: 100%;
|
||
text-align: center;
|
||
}
|
||
|
||
/* IMS网元选择框样式 */
|
||
.ims-select :deep(.ant-select-selector) {
|
||
border-radius: 8px !important;
|
||
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.08);
|
||
min-height: 36px;
|
||
font-size: 15px;
|
||
border: 1px solid #d9d9d9;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.ims-select :deep(.ant-select-selector:hover) {
|
||
border-color: #1890ff;
|
||
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.15);
|
||
}
|
||
|
||
.ims-select :deep(.ant-select-focused .ant-select-selector) {
|
||
border-color: #1890ff;
|
||
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||
}
|
||
|
||
.ims-select :deep(.ant-select-selection-item) {
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
|
||
.ims-select :deep(.ant-select-arrow) {
|
||
color: #666;
|
||
}
|
||
|
||
/* 暗色主题适配 */
|
||
[data-theme='dark'] .ims-select :deep(.ant-select-selector) {
|
||
background-color: #1f1f1f;
|
||
border-color: #434343;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
[data-theme='dark'] .ims-select :deep(.ant-select-selection-item) {
|
||
color: #cacada;
|
||
}
|
||
|
||
[data-theme='dark'] .ims-select :deep(.ant-select-arrow) {
|
||
color: #cacada;
|
||
}
|
||
</style>
|