diff --git a/src/views/perfManage/overview/index.vue b/src/views/perfManage/overview/index.vue index 9d637d5e..20ae1819 100644 --- a/src/views/perfManage/overview/index.vue +++ b/src/views/perfManage/overview/index.vue @@ -393,8 +393,6 @@ async function fetchMosData(neId: string) { timestamp: timestamp } - // console.log('获取MOS数据参数:', params) - const res = await getMosHour(params) // console.log('MOS数据响应:', res) @@ -542,1270 +540,182 @@ onBeforeUnmount(() => { // 更新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: 30, 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)' } // 淡灰色填充 - }] - }) - - // 清除所有旧标注,即使在无数据时也要清理 - const chartContainer = callsChartRef.value - if (chartContainer) { - const existingLabels = chartContainer.querySelectorAll('.chart-label') - existingLabels.forEach(label => label.remove()) + updateChart({ + chartRef: callsChartRef, + data: imsRealtimeRawData.value, + dataExtractor: (item) => Number(item.data['SCSCF.07']) || 0, + lineColor: '#1890ff', + areaColor: 'rgba(24,144,255,0.1)', + labelColor: '#1890ff', + dataType: 'realtime', + formatValue: (value: number) => value.toString(), + timeCalculator: (data) => { + const displayDataLength = Math.min(data.length, 30) + const oldestDisplayIndex = Math.max(0, data.length - displayDataLength) + const oldestDisplayData = data[oldestDisplayIndex] + if (oldestDisplayData && oldestDisplayData.timestamp) { + return calculateRelativeTime(oldestDisplayData.timestamp) + } + return '--' } - 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) - - // 计算最大值、最小值、最新值 - const maxValue = Math.max(...chartData) - const minValue = Math.min(...chartData) - const latestValue = chartData[chartData.length - 1] - - chart.setOption({ - grid: { left: 0, right: 30, 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)' } // 淡蓝色填充 - }] }) - - // 在图表容器中添加数值标注 - const chartContainer = callsChartRef.value - if (chartContainer) { - // 清除之前的标注 - const existingLabels = chartContainer.querySelectorAll('.chart-label') - existingLabels.forEach(label => label.remove()) - - // 添加右侧数值标注 - const maxLabel = document.createElement('div') - maxLabel.className = 'chart-label' - maxLabel.style.cssText = ` - position: absolute; - right: 8px; - top: 8px; - font-size: 12px; - font-weight: bold; - color: #666; - pointer-events: none; - z-index: 10; - ` - maxLabel.textContent = maxValue.toString() - - const latestLabel = document.createElement('div') - latestLabel.className = 'chart-label' - latestLabel.style.cssText = ` - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - font-size: 12px; - font-weight: bold; - color: #1890ff; - pointer-events: none; - z-index: 10; - ` - latestLabel.textContent = latestValue.toString() - - const minLabel = document.createElement('div') - minLabel.className = 'chart-label' - minLabel.style.cssText = ` - position: absolute; - right: 8px; - bottom: 8px; - font-size: 12px; - font-weight: bold; - color: #666; - pointer-events: none; - z-index: 10; - ` - minLabel.textContent = minValue.toString() - - // 添加底部时间标注 - const oldestTimeLabel = document.createElement('div') - oldestTimeLabel.className = 'chart-label' - oldestTimeLabel.style.cssText = ` - position: absolute; - left: 8px; - bottom: -20px; - font-size: 11px; - color: #999; - pointer-events: none; - z-index: 10; - ` - // 计算图表实际显示的最旧数据时间(基于图表数据点数量) - const displayDataLength = Math.min(imsRealtimeRawData.value.length, 30) - const oldestDisplayIndex = Math.max(0, imsRealtimeRawData.value.length - displayDataLength) - const oldestDisplayData = imsRealtimeRawData.value[oldestDisplayIndex] - if (oldestDisplayData && oldestDisplayData.timestamp) { - oldestTimeLabel.textContent = calculateRelativeTime(oldestDisplayData.timestamp) - } else { - oldestTimeLabel.textContent = '--' - } - - const nowTimeLabel = document.createElement('div') - nowTimeLabel.className = 'chart-label' - nowTimeLabel.style.cssText = ` - position: absolute; - right: 8px; - bottom: -20px; - font-size: 11px; - color: #999; - pointer-events: none; - z-index: 10; - ` - nowTimeLabel.textContent = t('views.perfManage.voiceOverView.now') - - chartContainer.appendChild(maxLabel) - chartContainer.appendChild(latestLabel) - chartContainer.appendChild(minLabel) - chartContainer.appendChild(oldestTimeLabel) - chartContainer.appendChild(nowTimeLabel) - } } // 更新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: 30, 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)' } // 淡灰色填充 - }] - }) - - // 清除所有旧标注,即使在无数据时也要清理 - const chartContainer = failedCallsChartRef.value - if (chartContainer) { - const existingLabels = chartContainer.querySelectorAll('.chart-label') - existingLabels.forEach(label => label.remove()) + updateChart({ + chartRef: failedCallsChartRef, + data: imsRealtimeRawData.value, + dataExtractor: (item) => { + const kpiEvent = item.data + const scscf06 = Number(kpiEvent['SCSCF.06']) || 0 + const scscf07 = Number(kpiEvent['SCSCF.07']) || 0 + return scscf06 - scscf07 + }, + lineColor: '#faad14', + areaColor: 'rgba(250,173,20,0.1)', + labelColor: '#faad14', + dataType: 'realtime', + formatValue: (value: number) => value.toString(), + timeCalculator: (data) => { + const displayDataLength = Math.min(data.length, 30) + const oldestDisplayIndex = Math.max(0, data.length - displayDataLength) + const oldestDisplayData = data[oldestDisplayIndex] + if (oldestDisplayData && oldestDisplayData.timestamp) { + return calculateRelativeTime(oldestDisplayData.timestamp) + } + return '--' } - 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) - - // 计算最大值、最小值、最新值 - const maxValue = Math.max(...chartData) - const minValue = Math.min(...chartData) - const latestValue = chartData[chartData.length - 1] - - chart.setOption({ - grid: { left: 0, right: 30, 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)' } // 淡橙色填充 - }] }) - - // 在图表容器中添加数值标注 - const chartContainer = failedCallsChartRef.value - if (chartContainer) { - // 清除之前的标注 - const existingLabels = chartContainer.querySelectorAll('.chart-label') - existingLabels.forEach(label => label.remove()) - - // 添加右侧数值标注 - const maxLabel = document.createElement('div') - maxLabel.className = 'chart-label' - maxLabel.style.cssText = ` - position: absolute; - right: 8px; - top: 8px; - font-size: 12px; - font-weight: bold; - color: #666; - pointer-events: none; - z-index: 10; - ` - maxLabel.textContent = maxValue.toString() - - const latestLabel = document.createElement('div') - latestLabel.className = 'chart-label' - latestLabel.style.cssText = ` - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - font-size: 12px; - font-weight: bold; - color: #faad14; - pointer-events: none; - z-index: 10; - ` - latestLabel.textContent = latestValue.toString() - - const minLabel = document.createElement('div') - minLabel.className = 'chart-label' - minLabel.style.cssText = ` - position: absolute; - right: 8px; - bottom: 8px; - font-size: 12px; - font-weight: bold; - color: #666; - pointer-events: none; - z-index: 10; - ` - minLabel.textContent = minValue.toString() - - // 添加底部时间标注 - const oldestTimeLabel = document.createElement('div') - oldestTimeLabel.className = 'chart-label' - oldestTimeLabel.style.cssText = ` - position: absolute; - left: 8px; - bottom: -20px; - font-size: 11px; - color: #999; - pointer-events: none; - z-index: 10; - ` - // 计算图表实际显示的最旧数据时间(基于图表数据点数量) - const displayDataLength = Math.min(imsRealtimeRawData.value.length, 30) - const oldestDisplayIndex = Math.max(0, imsRealtimeRawData.value.length - displayDataLength) - const oldestDisplayData = imsRealtimeRawData.value[oldestDisplayIndex] - if (oldestDisplayData && oldestDisplayData.timestamp) { - oldestTimeLabel.textContent = calculateRelativeTime(oldestDisplayData.timestamp) - } else { - oldestTimeLabel.textContent = '--' - } - - const nowTimeLabel = document.createElement('div') - nowTimeLabel.className = 'chart-label' - nowTimeLabel.style.cssText = ` - position: absolute; - right: 8px; - bottom: -20px; - font-size: 11px; - color: #999; - pointer-events: none; - z-index: 10; - ` - nowTimeLabel.textContent = t('views.perfManage.voiceOverView.now') - - chartContainer.appendChild(maxLabel) - chartContainer.appendChild(latestLabel) - chartContainer.appendChild(minLabel) - chartContainer.appendChild(oldestTimeLabel) - chartContainer.appendChild(nowTimeLabel) - } } // 更新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: 30, 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)' } // 淡灰色填充 - }] - }) - - // 清除所有旧标注,即使在无数据时也要清理 - const chartContainer = regChartRef.value - if (chartContainer) { - const existingLabels = chartContainer.querySelectorAll('.chart-label') - existingLabels.forEach(label => label.remove()) + updateChart({ + chartRef: regChartRef, + data: imsRealtimeRawData.value, + dataExtractor: (item) => Number(item.data['SCSCF.03']) || 0, + lineColor: '#1890ff', + areaColor: 'rgba(24,144,255,0.1)', + labelColor: '#1890ff', + dataType: 'realtime', + formatValue: (value: number) => value.toString(), + timeCalculator: (data) => { + const displayDataLength = Math.min(data.length, 30) + const oldestDisplayIndex = Math.max(0, data.length - displayDataLength) + const oldestDisplayData = data[oldestDisplayIndex] + if (oldestDisplayData && oldestDisplayData.timestamp) { + return calculateRelativeTime(oldestDisplayData.timestamp) + } + return '--' } - 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) - - // 计算最大值、最小值、最新值 - const maxValue = Math.max(...chartData) - const minValue = Math.min(...chartData) - const latestValue = chartData[chartData.length - 1] - - chart.setOption({ - grid: { left: 0, right: 30, 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)' } // 淡蓝色填充 - }] }) - - // 在图表容器中添加数值标注 - const chartContainer = regChartRef.value - if (chartContainer) { - // 清除之前的标注 - const existingLabels = chartContainer.querySelectorAll('.chart-label') - existingLabels.forEach(label => label.remove()) - - // 添加右侧数值标注 - const maxLabel = document.createElement('div') - maxLabel.className = 'chart-label' - maxLabel.style.cssText = ` - position: absolute; - right: 8px; - top: 8px; - font-size: 12px; - font-weight: bold; - color: #666; - pointer-events: none; - z-index: 10; - ` - maxLabel.textContent = maxValue.toString() - - const latestLabel = document.createElement('div') - latestLabel.className = 'chart-label' - latestLabel.style.cssText = ` - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - font-size: 12px; - font-weight: bold; - color: #1890ff; - pointer-events: none; - z-index: 10; - ` - latestLabel.textContent = latestValue.toString() - - const minLabel = document.createElement('div') - minLabel.className = 'chart-label' - minLabel.style.cssText = ` - position: absolute; - right: 8px; - bottom: 8px; - font-size: 12px; - font-weight: bold; - color: #666; - pointer-events: none; - z-index: 10; - ` - minLabel.textContent = minValue.toString() - - // 添加底部时间标注 - const oldestTimeLabel = document.createElement('div') - oldestTimeLabel.className = 'chart-label' - oldestTimeLabel.style.cssText = ` - position: absolute; - left: 8px; - bottom: -20px; - font-size: 11px; - color: #999; - pointer-events: none; - z-index: 10; - ` - // 计算图表实际显示的最旧数据时间(基于图表数据点数量) - const displayDataLength = Math.min(imsRealtimeRawData.value.length, 30) - const oldestDisplayIndex = Math.max(0, imsRealtimeRawData.value.length - displayDataLength) - const oldestDisplayData = imsRealtimeRawData.value[oldestDisplayIndex] - if (oldestDisplayData && oldestDisplayData.timestamp) { - oldestTimeLabel.textContent = calculateRelativeTime(oldestDisplayData.timestamp) - } else { - oldestTimeLabel.textContent = '--' - } - - const nowTimeLabel = document.createElement('div') - nowTimeLabel.className = 'chart-label' - nowTimeLabel.style.cssText = ` - position: absolute; - right: 8px; - bottom: -20px; - font-size: 11px; - color: #999; - pointer-events: none; - z-index: 10; - ` - nowTimeLabel.textContent = t('views.perfManage.voiceOverView.now') - - chartContainer.appendChild(maxLabel) - chartContainer.appendChild(latestLabel) - chartContainer.appendChild(minLabel) - chartContainer.appendChild(oldestTimeLabel) - chartContainer.appendChild(nowTimeLabel) - } } // 更新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: 30, 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)' } // 淡灰色填充 - }] - }) - - // 清除所有旧标注,即使在无数据时也要清理 - const chartContainer = failedRegChartRef.value - if (chartContainer) { - const existingLabels = chartContainer.querySelectorAll('.chart-label') - existingLabels.forEach(label => label.remove()) + updateChart({ + chartRef: failedRegChartRef, + data: imsRealtimeRawData.value, + dataExtractor: (item) => { + const kpiEvent = item.data + const scscf04 = Number(kpiEvent['SCSCF.04']) || 0 + const scscf03 = Number(kpiEvent['SCSCF.03']) || 0 + return scscf04 - scscf03 + }, + lineColor: '#f5222d', + areaColor: 'rgba(245,34,45,0.1)', + labelColor: '#f5222d', + dataType: 'realtime', + formatValue: (value: number) => value.toString(), + timeCalculator: (data) => { + const displayDataLength = Math.min(data.length, 30) + const oldestDisplayIndex = Math.max(0, data.length - displayDataLength) + const oldestDisplayData = data[oldestDisplayIndex] + if (oldestDisplayData && oldestDisplayData.timestamp) { + return calculateRelativeTime(oldestDisplayData.timestamp) + } + return '--' } - 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) - - // 计算最大值、最小值、最新值 - const maxValue = Math.max(...chartData) - const minValue = Math.min(...chartData) - const latestValue = chartData[chartData.length - 1] - - chart.setOption({ - grid: { left: 0, right: 30, 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)' } // 淡红色填充 - }] }) - - // 在图表容器中添加数值标注 - const chartContainer = failedRegChartRef.value - if (chartContainer) { - // 清除之前的标注 - const existingLabels = chartContainer.querySelectorAll('.chart-label') - existingLabels.forEach(label => label.remove()) - - // 添加右侧数值标注 - const maxLabel = document.createElement('div') - maxLabel.className = 'chart-label' - maxLabel.style.cssText = ` - position: absolute; - right: 8px; - top: 8px; - font-size: 12px; - font-weight: bold; - color: #666; - pointer-events: none; - z-index: 10; - ` - maxLabel.textContent = maxValue.toString() - - const latestLabel = document.createElement('div') - latestLabel.className = 'chart-label' - latestLabel.style.cssText = ` - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - font-size: 12px; - font-weight: bold; - color: #f5222d; - pointer-events: none; - z-index: 10; - ` - latestLabel.textContent = latestValue.toString() - - const minLabel = document.createElement('div') - minLabel.className = 'chart-label' - minLabel.style.cssText = ` - position: absolute; - right: 8px; - bottom: 8px; - font-size: 12px; - font-weight: bold; - color: #666; - pointer-events: none; - z-index: 10; - ` - minLabel.textContent = minValue.toString() - - // 添加底部时间标注 - const oldestTimeLabel = document.createElement('div') - oldestTimeLabel.className = 'chart-label' - oldestTimeLabel.style.cssText = ` - position: absolute; - left: 8px; - bottom: -20px; - font-size: 11px; - color: #999; - pointer-events: none; - z-index: 10; - ` - // 计算图表实际显示的最旧数据时间(基于图表数据点数量) - const displayDataLength = Math.min(imsRealtimeRawData.value.length, 30) - const oldestDisplayIndex = Math.max(0, imsRealtimeRawData.value.length - displayDataLength) - const oldestDisplayData = imsRealtimeRawData.value[oldestDisplayIndex] - if (oldestDisplayData && oldestDisplayData.timestamp) { - oldestTimeLabel.textContent = calculateRelativeTime(oldestDisplayData.timestamp) - } else { - oldestTimeLabel.textContent = '--' - } - - const nowTimeLabel = document.createElement('div') - nowTimeLabel.className = 'chart-label' - nowTimeLabel.style.cssText = ` - position: absolute; - right: 8px; - bottom: -20px; - font-size: 11px; - color: #999; - pointer-events: none; - z-index: 10; - ` - nowTimeLabel.textContent = t('views.perfManage.voiceOverView.now') - - chartContainer.appendChild(maxLabel) - chartContainer.appendChild(latestLabel) - chartContainer.appendChild(minLabel) - chartContainer.appendChild(oldestTimeLabel) - chartContainer.appendChild(nowTimeLabel) - } } // 更新Call Attempts图表 function updateCallAttemptsChart() { - if (!callAttemptsChartRef.value) return - - // 获取图表实例 - let chart = echarts.getInstanceByDom(callAttemptsChartRef.value) - if (!chart) { - chart = echarts.init(callAttemptsChartRef.value) - } - - // 如果没有Busy Hour数据或数据为空数组,显示默认的平直线 - if (!busyHourData.value || busyHourData.value.length === 0) { - const defaultData = [0, 0, 0, 0, 0] // 5个默认数据点,值为0 - const xAxisData = [1, 2, 3, 4, 5] - - chart.setOption({ - grid: { left: 0, right: 30, 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)' } - }] - }) - - // 清除所有旧标注 - const chartContainer = callAttemptsChartRef.value - if (chartContainer) { - const existingLabels = chartContainer.querySelectorAll('.chart-label') - existingLabels.forEach(label => label.remove()) + updateChart({ + chartRef: callAttemptsChartRef, + data: busyHourData.value || [], + dataExtractor: (item: any) => Number(item.callAttempts) || 0, + lineColor: '#52c41a', + areaColor: 'rgba(82,196,26,0.1)', + labelColor: '#52c41a', + dataType: 'hourly', + formatValue: (value: number) => value.toString(), + timeCalculator: (data) => { + if (data.length === 0) return '--' + const oldestTime = Number(data[0]?.timeGroup) || Date.now() + return calculateRelativeTime(oldestTime) } - return - } - - // 从Busy Hour数据中提取Call Attempts数据 - const chartData = busyHourData.value.map((item: any) => Number(item.callAttempts) || 0) - - // 生成时间轴数据 - const xAxisData = Array.from({ length: chartData.length }, (_, i) => i + 1) - - // 计算最大值、最小值、最新值 - const maxValue = Math.max(...chartData) - const minValue = Math.min(...chartData) - const latestValue = chartData[chartData.length - 1] - - chart.setOption({ - grid: { left: 0, right: 30, 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: '#52c41a' }, // 绿色线条 - areaStyle: { color: 'rgba(82,196,26,0.1)' } // 淡绿色填充 - }] }) - - // 在图表容器中添加数值标注 - const chartContainer = callAttemptsChartRef.value - if (chartContainer) { - // 清除之前的标注 - const existingLabels = chartContainer.querySelectorAll('.chart-label') - existingLabels.forEach(label => label.remove()) - - // 添加右侧数值标注 - const maxLabel = document.createElement('div') - maxLabel.className = 'chart-label' - maxLabel.style.cssText = ` - position: absolute; - right: 8px; - top: 8px; - font-size: 12px; - font-weight: bold; - color: #666; - pointer-events: none; - z-index: 10; - ` - maxLabel.textContent = maxValue.toString() - - const latestLabel = document.createElement('div') - latestLabel.className = 'chart-label' - latestLabel.style.cssText = ` - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - font-size: 12px; - font-weight: bold; - color: #52c41a; - pointer-events: none; - z-index: 10; - ` - latestLabel.textContent = latestValue.toString() - - const minLabel = document.createElement('div') - minLabel.className = 'chart-label' - minLabel.style.cssText = ` - position: absolute; - right: 8px; - bottom: 8px; - font-size: 12px; - font-weight: bold; - color: #666; - pointer-events: none; - z-index: 10; - ` - minLabel.textContent = minValue.toString() - - // 添加底部时间标注 - const oldestTimeLabel = document.createElement('div') - oldestTimeLabel.className = 'chart-label' - oldestTimeLabel.style.cssText = ` - position: absolute; - left: 8px; - bottom: -20px; - font-size: 11px; - color: #999; - pointer-events: none; - z-index: 10; - ` - // 使用第一个数据点的时间戳计算相对时间 - const oldestTime = Number(busyHourData.value[0]?.timeGroup) || Date.now() - oldestTimeLabel.textContent = calculateRelativeTime(oldestTime) - - const nowTimeLabel = document.createElement('div') - nowTimeLabel.className = 'chart-label' - nowTimeLabel.style.cssText = ` - position: absolute; - right: 8px; - bottom: -20px; - font-size: 11px; - color: #999; - pointer-events: none; - z-index: 10; - ` - nowTimeLabel.textContent = t('views.perfManage.voiceOverView.now') - - chartContainer.appendChild(maxLabel) - chartContainer.appendChild(latestLabel) - chartContainer.appendChild(minLabel) - chartContainer.appendChild(oldestTimeLabel) - chartContainer.appendChild(nowTimeLabel) - } } // 更新Call Completions图表 function updateCallCompletionsChart() { - if (!callCompletionsChartRef.value) return - - // 获取图表实例 - let chart = echarts.getInstanceByDom(callCompletionsChartRef.value) - if (!chart) { - chart = echarts.init(callCompletionsChartRef.value) - } - - // 如果没有Busy Hour数据或数据为空数组,显示默认的平直线 - if (!busyHourData.value || busyHourData.value.length === 0) { - const defaultData = [0, 0, 0, 0, 0] // 5个默认数据点,值为0 - const xAxisData = [1, 2, 3, 4, 5] - - chart.setOption({ - grid: { left: 0, right: 30, 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)' } - }] - }) - - // 清除所有旧标注 - const chartContainer = callCompletionsChartRef.value - if (chartContainer) { - const existingLabels = chartContainer.querySelectorAll('.chart-label') - existingLabels.forEach(label => label.remove()) + updateChart({ + chartRef: callCompletionsChartRef, + data: busyHourData.value || [], + dataExtractor: (item: any) => Number(item.callCompletions) || 0, + lineColor: '#722ed1', + areaColor: 'rgba(114,46,209,0.1)', + labelColor: '#722ed1', + dataType: 'hourly', + formatValue: (value: number) => value.toString(), + timeCalculator: (data) => { + if (data.length === 0) return '--' + const oldestTime = Number(data[0]?.timeGroup) || Date.now() + return calculateRelativeTime(oldestTime) } - return - } - - // 从Busy Hour数据中提取Call Completions数据 - const chartData = busyHourData.value.map((item: any) => Number(item.callCompletions) || 0) - - // 生成时间轴数据 - const xAxisData = Array.from({ length: chartData.length }, (_, i) => i + 1) - - // 计算最大值、最小值、最新值 - const maxValue = Math.max(...chartData) - const minValue = Math.min(...chartData) - const latestValue = chartData[chartData.length - 1] - - chart.setOption({ - grid: { left: 0, right: 30, 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: '#722ed1' }, // 紫色线条 - areaStyle: { color: 'rgba(114,46,209,0.1)' } // 淡紫色填充 - }] }) - - // 在图表容器中添加数值标注 - const chartContainer = callCompletionsChartRef.value - if (chartContainer) { - // 清除之前的标注 - const existingLabels = chartContainer.querySelectorAll('.chart-label') - existingLabels.forEach(label => label.remove()) - - // 添加右侧数值标注 - const maxLabel = document.createElement('div') - maxLabel.className = 'chart-label' - maxLabel.style.cssText = ` - position: absolute; - right: 8px; - top: 8px; - font-size: 12px; - font-weight: bold; - color: #666; - pointer-events: none; - z-index: 10; - ` - maxLabel.textContent = maxValue.toString() - - const latestLabel = document.createElement('div') - latestLabel.className = 'chart-label' - latestLabel.style.cssText = ` - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - font-size: 12px; - font-weight: bold; - color: #722ed1; - pointer-events: none; - z-index: 10; - ` - latestLabel.textContent = latestValue.toString() - - const minLabel = document.createElement('div') - minLabel.className = 'chart-label' - minLabel.style.cssText = ` - position: absolute; - right: 8px; - bottom: 8px; - font-size: 12px; - font-weight: bold; - color: #666; - pointer-events: none; - z-index: 10; - ` - minLabel.textContent = minValue.toString() - - // 添加底部时间标注 - const oldestTimeLabel = document.createElement('div') - oldestTimeLabel.className = 'chart-label' - oldestTimeLabel.style.cssText = ` - position: absolute; - left: 8px; - bottom: -20px; - font-size: 11px; - color: #999; - pointer-events: none; - z-index: 10; - ` - // 使用第一个数据点的时间戳计算相对时间 - const oldestTime = Number(busyHourData.value[0]?.timeGroup) || Date.now() - oldestTimeLabel.textContent = calculateRelativeTime(oldestTime) - - const nowTimeLabel = document.createElement('div') - nowTimeLabel.className = 'chart-label' - nowTimeLabel.style.cssText = ` - position: absolute; - right: 8px; - bottom: -20px; - font-size: 11px; - color: #999; - pointer-events: none; - z-index: 10; - ` - nowTimeLabel.textContent = t('views.perfManage.voiceOverView.now') - - chartContainer.appendChild(maxLabel) - chartContainer.appendChild(latestLabel) - chartContainer.appendChild(minLabel) - chartContainer.appendChild(oldestTimeLabel) - chartContainer.appendChild(nowTimeLabel) - } } // 更新MOS图表 function updateMosChart() { - if (!mosChartRef.value) return - - // 获取图表实例 - let chart = echarts.getInstanceByDom(mosChartRef.value) - if (!chart) { - chart = echarts.init(mosChartRef.value) - } - - // 如果没有MOS数据或数据为空数组,显示默认的平直线 - if (!mosData.value || mosData.value.length === 0) { - const defaultData = [0, 0, 0, 0, 0] // 5个默认数据点,值为0 - const xAxisData = [1, 2, 3, 4, 5] - - chart.setOption({ - grid: { left: 0, right: 30, 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)' } - }], - graphic: [] // 清除任何ECharts图形元素 - }) - - // 清除所有旧标注 - const chartContainer = mosChartRef.value - if (chartContainer) { - const existingLabels = chartContainer.querySelectorAll('.chart-label') - existingLabels.forEach(label => label.remove()) + updateChart({ + chartRef: mosChartRef, + data: mosData.value || [], + dataExtractor: (item: any) => Number(item.mosAvg) || 0, + lineColor: '#52c41a', + areaColor: 'rgba(82,196,26,0.1)', + labelColor: '#52c41a', + dataType: 'hourly', + formatValue: (value: number) => value.toFixed(2), + timeCalculator: (data) => { + if (data.length === 0) return '--' + // MOS时间戳是秒级,需要转换为毫秒级 + const oldestTime = (Number(data[0]?.timeGroup) || 0) * 1000 + return calculateRelativeTime(oldestTime) } - return - } - - // 从MOS数据中提取MOS值 - const chartData = mosData.value.map((item: any) => Number(item.mosAvg) || 0) - - // 生成时间轴数据 - const xAxisData = Array.from({ length: chartData.length }, (_, i) => i + 1) - - // 计算最大值、最小值、最新值 - const maxValue = Math.max(...chartData) - const minValue = Math.min(...chartData) - const latestValue = chartData[chartData.length - 1] - - chart.setOption({ - grid: { left: 0, right: 30, 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: '#52c41a' }, // 绿色线条 - areaStyle: { color: 'rgba(82,196,26,0.1)' } // 淡绿色填充 - }] }) - - // 在图表容器中添加数值标注 - const chartContainer = mosChartRef.value - if (chartContainer) { - // 清除之前的标注 - const existingLabels = chartContainer.querySelectorAll('.chart-label') - existingLabels.forEach(label => label.remove()) - - // 添加右侧数值标注 - const maxLabel = document.createElement('div') - maxLabel.className = 'chart-label' - maxLabel.style.cssText = ` - position: absolute; - right: 8px; - top: 8px; - font-size: 12px; - font-weight: bold; - color: #666; - pointer-events: none; - z-index: 10; - ` - maxLabel.textContent = maxValue.toFixed(2) - - const latestLabel = document.createElement('div') - latestLabel.className = 'chart-label' - latestLabel.style.cssText = ` - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - font-size: 12px; - font-weight: bold; - color: #52c41a; - pointer-events: none; - z-index: 10; - ` - latestLabel.textContent = latestValue.toFixed(2) - - const minLabel = document.createElement('div') - minLabel.className = 'chart-label' - minLabel.style.cssText = ` - position: absolute; - right: 8px; - bottom: 8px; - font-size: 12px; - font-weight: bold; - color: #666; - pointer-events: none; - z-index: 10; - ` - minLabel.textContent = minValue.toFixed(2) - - // 添加底部时间标注 - const oldestTimeLabel = document.createElement('div') - oldestTimeLabel.className = 'chart-label' - oldestTimeLabel.style.cssText = ` - position: absolute; - left: 8px; - bottom: -20px; - font-size: 11px; - color: #999; - pointer-events: none; - z-index: 10; - ` - // 使用第一个数据点的时间戳计算相对时间 - // MOS时间戳是秒级,需要转换为毫秒级 - const oldestTime = (Number(mosData.value[0]?.timeGroup) || 0) * 1000 || Date.now() - oldestTimeLabel.textContent = calculateRelativeTime(oldestTime) - - const nowTimeLabel = document.createElement('div') - nowTimeLabel.className = 'chart-label' - nowTimeLabel.style.cssText = ` - position: absolute; - right: 8px; - bottom: -20px; - font-size: 11px; - color: #999; - pointer-events: none; - z-index: 10; - ` - nowTimeLabel.textContent = t('views.perfManage.voiceOverView.now') - - chartContainer.appendChild(maxLabel) - chartContainer.appendChild(latestLabel) - chartContainer.appendChild(minLabel) - chartContainer.appendChild(oldestTimeLabel) - chartContainer.appendChild(nowTimeLabel) - } } // 更新CCT图表 function updateCctChart() { - if (!cctChartRef.value) return - - // 获取图表实例 - let chart = echarts.getInstanceByDom(cctChartRef.value) - if (!chart) { - chart = echarts.init(cctChartRef.value) - } - - // 如果没有CCT数据或数据为空数组,显示默认的平直线 - if (!cctData.value || cctData.value.length === 0) { - const defaultData = [0, 0, 0, 0, 0] // 5个默认数据点,值为0 - const xAxisData = [1, 2, 3, 4, 5] - - chart.setOption({ - grid: { left: 0, right: 30, 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)' } - }] - }) - - // 清除所有旧标注 - const chartContainer = cctChartRef.value - if (chartContainer) { - const existingLabels = chartContainer.querySelectorAll('.chart-label') - existingLabels.forEach(label => label.remove()) + updateChart({ + chartRef: cctChartRef, + data: cctData.value || [], + dataExtractor: (item: any) => Number(item.cctAvg) || 0, + lineColor: '#fa8c16', + areaColor: 'rgba(250,140,22,0.1)', + labelColor: '#fa8c16', + dataType: 'hourly', + formatValue: (value: number) => value.toFixed(2), + timeCalculator: (data) => { + if (data.length === 0) return '--' + // CCT时间戳是秒级,需要转换为毫秒级 + const oldestTime = (Number(data[0]?.timeGroup) || 0) * 1000 + return calculateRelativeTime(oldestTime) } - return - } - - // 从CCT数据中提取CCT值 - const chartData = cctData.value.map((item: any) => Number(item.cctAvg) || 0) - - // 生成时间轴数据 - const xAxisData = Array.from({ length: chartData.length }, (_, i) => i + 1) - - // 计算最大值、最小值、最新值 - const maxValue = Math.max(...chartData) - const minValue = Math.min(...chartData) - const latestValue = chartData[chartData.length - 1] - - chart.setOption({ - grid: { left: 0, right: 30, 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: '#fa8c16' }, // 橙色线条 - areaStyle: { color: 'rgba(250,140,22,0.1)' } // 淡橙色填充 - }] }) - - // 在图表容器中添加数值标注 - const chartContainer = cctChartRef.value - if (chartContainer) { - // 清除之前的标注 - const existingLabels = chartContainer.querySelectorAll('.chart-label') - existingLabels.forEach(label => label.remove()) - - // 添加右侧数值标注 - const maxLabel = document.createElement('div') - maxLabel.className = 'chart-label' - maxLabel.style.cssText = ` - position: absolute; - right: 8px; - top: 8px; - font-size: 12px; - font-weight: bold; - color: #666; - pointer-events: none; - z-index: 10; - ` - maxLabel.textContent = maxValue.toFixed(2) - - const latestLabel = document.createElement('div') - latestLabel.className = 'chart-label' - latestLabel.style.cssText = ` - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - font-size: 12px; - font-weight: bold; - color: #fa8c16; - pointer-events: none; - z-index: 10; - ` - latestLabel.textContent = latestValue.toFixed(2) - - const minLabel = document.createElement('div') - minLabel.className = 'chart-label' - minLabel.style.cssText = ` - position: absolute; - right: 8px; - bottom: 8px; - font-size: 12px; - font-weight: bold; - color: #666; - pointer-events: none; - z-index: 10; - ` - minLabel.textContent = minValue.toFixed(2) - - // 添加底部时间标注 - const oldestTimeLabel = document.createElement('div') - oldestTimeLabel.className = 'chart-label' - oldestTimeLabel.style.cssText = ` - position: absolute; - left: 8px; - bottom: -20px; - font-size: 11px; - color: #999; - pointer-events: none; - z-index: 10; - ` - // 使用第一个数据点的时间戳计算相对时间 - // CCT时间戳是秒级,需要转换为毫秒级 - const oldestTime = (Number(cctData.value[0]?.timeGroup) || 0) * 1000 || Date.now() - oldestTimeLabel.textContent = calculateRelativeTime(oldestTime) - - const nowTimeLabel = document.createElement('div') - nowTimeLabel.className = 'chart-label' - nowTimeLabel.style.cssText = ` - position: absolute; - right: 8px; - bottom: -20px; - font-size: 11px; - color: #999; - pointer-events: none; - z-index: 10; - ` - nowTimeLabel.textContent = t('views.perfManage.voiceOverView.now') - - chartContainer.appendChild(maxLabel) - chartContainer.appendChild(latestLabel) - chartContainer.appendChild(minLabel) - chartContainer.appendChild(oldestTimeLabel) - chartContainer.appendChild(nowTimeLabel) - } } // 处理IMS实时数据(只存储当前选中网元) @@ -2057,9 +967,6 @@ function calculateTimeDifference(latestData: any, previousData: any) { const latestTime = latestData.timestamp || latestData.time || Date.now() const previousTime = previousData.timestamp || previousData.time || Date.now() - // console.log('计算时间差 - 最新数据时间戳:', latestTime) - // console.log('计算时间差 - 上一个数据时间戳:', previousTime) - // 计算时间差(毫秒) const diffMs = Math.abs(latestTime - previousTime) @@ -2069,10 +976,6 @@ function calculateTimeDifference(latestData: any, previousData: any) { // 转换为分钟 const diffMinutes = Math.floor(diffSeconds / 60) - // console.log('计算时间差 - 时间差(毫秒):', diffMs) - // console.log('计算时间差 - 时间差(秒):', diffSeconds) - // console.log('计算时间差 - 时间差(分钟):', diffMinutes) - // 根据时间差返回合适的格式 if (diffMinutes > 0) { return `${diffMinutes} Min` @@ -2125,6 +1028,282 @@ function calculateMosCctTimeDifference(latestData: any, previousData: any) { } } +// ========== 通用工具函数 ========== + +// 通用图表更新函数 +function updateChart(config: { + chartRef: any, + data: any[], + dataExtractor: (item: any, index?: number) => number, + lineColor: string, + areaColor: string, + labelColor: string, + dataType?: 'realtime' | 'hourly', + formatValue?: (value: number) => string, + timeCalculator?: (data: any[]) => string +}) { + if (!config.chartRef.value) return + + // 获取图表实例 + let chart = echarts.getInstanceByDom(config.chartRef.value) + if (!chart) { + chart = echarts.init(config.chartRef.value) + } + + // 准备图表数据 + const chartData = config.data.map(config.dataExtractor) + + // 如果没有数据,显示默认的平直线 + if (chartData.length === 0) { + const defaultData = [0, 0, 0, 0, 0] + const xAxisData = [1, 2, 3, 4, 5] + + chart.setOption({ + grid: { left: 0, right: 30, 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)' } + }], + graphic: [] + }) + + // 清除所有旧标注 + const chartContainer = config.chartRef.value + if (chartContainer) { + const existingLabels = chartContainer.querySelectorAll('.chart-label') + existingLabels.forEach((label: any) => label.remove()) + } + return + } + + // 实时数据需要补充和限制数据点 + let processedData = [...chartData] + if (config.dataType === 'realtime') { + // 如果数据不足,补充默认数据 + while (processedData.length < 5) { + processedData.unshift(0) + } + // 限制数据点数量为最近30个 + if (processedData.length > 30) { + processedData.splice(0, processedData.length - 30) + } + } + + // 生成时间轴数据 + const xAxisData = Array.from({ length: processedData.length }, (_, i) => i + 1) + + // 计算最大值、最小值、最新值 + const maxValue = Math.max(...processedData) + const minValue = Math.min(...processedData) + const latestValue = processedData[processedData.length - 1] + + chart.setOption({ + grid: { left: 0, right: 30, top: 10, bottom: 10 }, + xAxis: { type: 'category', show: false, data: xAxisData }, + yAxis: { type: 'value', show: false }, + series: [{ + data: processedData, + type: 'line', symbol: 'none', + lineStyle: { width: 2, color: config.lineColor }, + areaStyle: { color: config.areaColor } + }], + graphic: [] + }) + + // 添加数值标签 + addChartLabels({ + container: config.chartRef.value, + maxValue, + minValue, + latestValue, + labelColor: config.labelColor, + formatValue: config.formatValue + }) + + // 添加时间标签(如果有时间计算器) + if (config.timeCalculator && config.data.length > 0) { + addTimeLabels({ + container: config.chartRef.value, + timeText: config.timeCalculator(config.data) + }) + } +} + +// 通用标签添加函数 +function addChartLabels(config: { + container: any, + maxValue: number, + minValue: number, + latestValue: number, + labelColor: string, + formatValue?: (value: number) => string +}) { + if (!config.container) return + + // 清除之前的标注 + const existingLabels = config.container.querySelectorAll('.chart-label') + existingLabels.forEach((label: any) => label.remove()) + + const formatValue = config.formatValue || ((val: number) => val.toString()) + + // 添加右侧数值标注 + const maxLabel = document.createElement('div') + maxLabel.className = 'chart-label' + maxLabel.style.cssText = ` + position: absolute; + right: 8px; + top: 8px; + font-size: 12px; + font-weight: bold; + color: #666; + pointer-events: none; + z-index: 10; + ` + maxLabel.textContent = formatValue(config.maxValue) + + const latestLabel = document.createElement('div') + latestLabel.className = 'chart-label' + latestLabel.style.cssText = ` + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + font-size: 12px; + font-weight: bold; + color: ${config.labelColor}; + pointer-events: none; + z-index: 10; + ` + latestLabel.textContent = formatValue(config.latestValue) + + const minLabel = document.createElement('div') + minLabel.className = 'chart-label' + minLabel.style.cssText = ` + position: absolute; + right: 8px; + bottom: 8px; + font-size: 12px; + font-weight: bold; + color: #666; + pointer-events: none; + z-index: 10; + ` + minLabel.textContent = formatValue(config.minValue) + + config.container.appendChild(maxLabel) + config.container.appendChild(latestLabel) + config.container.appendChild(minLabel) +} + +// 通用时间标签添加函数 +function addTimeLabels(config: { + container: any, + timeText: string +}) { + if (!config.container) return + + // 添加底部时间标注 + const oldestTimeLabel = document.createElement('div') + oldestTimeLabel.className = 'chart-label' + oldestTimeLabel.style.cssText = ` + position: absolute; + left: 8px; + bottom: -20px; + font-size: 11px; + color: #999; + pointer-events: none; + z-index: 10; + ` + oldestTimeLabel.textContent = config.timeText + + const nowTimeLabel = document.createElement('div') + nowTimeLabel.className = 'chart-label' + nowTimeLabel.style.cssText = ` + position: absolute; + right: 8px; + bottom: -20px; + font-size: 11px; + color: #999; + pointer-events: none; + z-index: 10; + ` + nowTimeLabel.textContent = 'Now' + + config.container.appendChild(oldestTimeLabel) + config.container.appendChild(nowTimeLabel) +} + +// 通用计算值函数 +function calculateMetricValue(config: { + data: any[] | null, + valueExtractor: (item: any) => number, + formatValue?: (value: number) => string +}) { + if (!config.data || config.data.length === 0) return '-' + + const latestData = config.data[config.data.length - 1] + if (!latestData) return '-' + + const value = config.valueExtractor(latestData) + const formatValue = config.formatValue || ((val: number) => val.toString()) + + return formatValue(value) +} + +// 通用计算变化值函数 +function calculateMetricChange(config: { + data: any[], + valueExtractor: (item: any) => number | string, + timeDiffCalculator?: (latest: any, previous: any) => string, + defaultTimeText?: string, + formatValue?: (value: number) => string, + isPercentage?: boolean +}) { + if (!config.data || config.data.length < 2) { + const defaultTime = config.defaultTimeText || '1 Min' + return `${t('views.perfManage.voiceOverView.last')} ${defaultTime} +0${config.isPercentage ? '%' : ''}` + } + + const latestData = config.data[config.data.length - 1] + const previousData = config.data[config.data.length - 2] + + const latestValue = config.valueExtractor(latestData) + const previousValue = config.valueExtractor(previousData) + + // 检查值是否有效 + if (latestValue === '-' || previousValue === '-' || + (typeof latestValue === 'number' && typeof previousValue === 'number')) { + + if (typeof latestValue !== 'number' || typeof previousValue !== 'number') { + const defaultTime = config.defaultTimeText || '1 Min' + return `${t('views.perfManage.voiceOverView.last')} ${defaultTime} +0${config.isPercentage ? '%' : ''}` + } + + const change = latestValue - previousValue + if (change === 0) { + const defaultTime = config.defaultTimeText || '1 Min' + return `${t('views.perfManage.voiceOverView.last')} ${defaultTime} +0${config.isPercentage ? '%' : ''}` + } + + const formatValue = config.formatValue || ((val: number) => val.toFixed(config.isPercentage ? 2 : 0)) + const changeText = change > 0 ? `+${formatValue(Math.abs(change))}` : `-${formatValue(Math.abs(change))}` + const suffix = config.isPercentage ? '%' : '' + + const timeDiff = config.timeDiffCalculator ? + config.timeDiffCalculator(latestData, previousData) : + (config.defaultTimeText || '1 Min') + + return `${t('views.perfManage.voiceOverView.last')} ${timeDiff} ${changeText}${suffix}` + } + + const defaultTime = config.defaultTimeText || '1 Min' + return `${t('views.perfManage.voiceOverView.last')} ${defaultTime} +0${config.isPercentage ? '%' : ''}` +} + // 生成当天0点到现在的完整时间序列数据(用于MOS/CCT数据补全) function generateTodayTimeSeries(rawData: any[], dataField: string) { if (!rawData || rawData.length === 0) { @@ -2258,18 +1437,14 @@ function calculateRegSuccessValueFromData(kpiEvent: any) { // 计算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 - - // 修改:即使值为0也显示"0",因为这是有效的后端数据 - const activeCallsValue = scscf07 - return activeCallsValue.toFixed(0) + return calculateMetricValue({ + data: imsRealtimeRawData.value, + valueExtractor: (item: any) => { + if (!item || !item.data) return 0 + return Number(item.data['SCSCF.07']) || 0 + }, + formatValue: (value: number) => value.toFixed(0) + }) } // 计算active calls箭头方向 @@ -2322,49 +1497,31 @@ function calculateActiveCallsArrow() { // 计算active calls变化值 function calculateActiveCallsChange() { - if (imsRealtimeRawData.value.length < 2) return t('views.perfManage.voiceOverView.last') +' 1 Min'+' +0' - - // 获取最新和上一个数据点 - const latestData = imsRealtimeRawData.value[imsRealtimeRawData.value.length - 1] - const previousData = imsRealtimeRawData.value[imsRealtimeRawData.value.length - 2] - - if (!latestData?.data || !previousData?.data) return t('views.perfManage.voiceOverView.last') +' 1 Min'+' +0' - - const latestKpi = latestData.data - const previousKpi = previousData.data - - const latestActiveCalls = Number(latestKpi['SCSCF.07']) || 0 - const previousActiveCalls = Number(previousKpi['SCSCF.07']) || 0 - - // 修改:即使值为0也参与计算,因为这是有效的后端数据 - // 计算变化幅度 - const change = latestActiveCalls - previousActiveCalls - - // 检查是否有变化 - if (change === 0) return t('views.perfManage.voiceOverView.last') +' 1 Min'+' +0' - - const changeText = change > 0 ? `+${change.toFixed(0)}` : `${change.toFixed(0)}` - - // 计算时间差 - const timeDiff = calculateTimeDifference(latestData, previousData) - - return ` ${t('views.perfManage.voiceOverView.last')} ${timeDiff} ${changeText}` + return calculateMetricChange({ + data: imsRealtimeRawData.value || [], + valueExtractor: (item: any) => { + if (!item || !item.data) return 0 + return Number(item.data['SCSCF.07']) || 0 + }, + timeDiffCalculator: calculateTimeDifference, + defaultTimeText: '1 Min', + formatValue: (value: number) => value.toFixed(0), + isPercentage: false + }) } // 计算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) + return calculateMetricValue({ + data: imsRealtimeRawData.value, + valueExtractor: (item: any) => { + if (!item || !item.data) return 0 + const scscf06 = Number(item.data['SCSCF.06']) || 0 + const scscf07 = Number(item.data['SCSCF.07']) || 0 + return scscf06 - scscf07 + }, + formatValue: (value: number) => value.toFixed(0) + }) } // 计算failed calls箭头方向 @@ -2666,12 +1823,11 @@ function calculateFailedRegistrationsChange() { // Call Attempts相关计算函数 function calculateCallAttemptsValue() { - if (!busyHourData.value || busyHourData.value.length === 0) return '-' - - // 获取最新的Call Attempts值(数组最后一个元素) - const latestData = busyHourData.value[busyHourData.value.length - 1] - const callAttempts = Number(latestData.callAttempts) || 0 - return callAttempts.toString() + return calculateMetricValue({ + data: busyHourData.value, + valueExtractor: (item: any) => Number(item.callAttempts) || 0, + formatValue: (value: number) => value.toString() + }) } function calculateCallAttemptsArrowDirection() { @@ -2699,26 +1855,22 @@ function calculateCallAttemptsArrow() { } function calculateCallAttemptsChange() { - if (!busyHourData.value || busyHourData.value.length < 2) return t('views.perfManage.voiceOverView.last') +' 1 Hour'+' +0' - - const latestValue = Number(busyHourData.value[busyHourData.value.length - 1].callAttempts) || 0 - const previousValue = Number(busyHourData.value[busyHourData.value.length - 2].callAttempts) || 0 - - const change = latestValue - previousValue - if (change === 0) return t('views.perfManage.voiceOverView.last') +' 1 Hour'+' +0' - - const changeText = change > 0 ? `+${change}` : `${change}` - return t('views.perfManage.voiceOverView.last') + ' 1 Hour'+ ` ${changeText}` + return calculateMetricChange({ + data: busyHourData.value || [], + valueExtractor: (item: any) => Number(item.callAttempts) || 0, + defaultTimeText: '1 Hour', + formatValue: (value: number) => value.toString(), + isPercentage: false + }) } // Call Completions相关计算函数 function calculateCallCompletionsValue() { - if (!busyHourData.value || busyHourData.value.length === 0) return '-' - - // 获取最新的Call Completions值(数组最后一个元素) - const latestData = busyHourData.value[busyHourData.value.length - 1] - const callCompletions = Number(latestData.callCompletions) || 0 - return callCompletions.toString() + return calculateMetricValue({ + data: busyHourData.value, + valueExtractor: (item: any) => Number(item.callCompletions) || 0, + formatValue: (value: number) => value.toString() + }) } function calculateCallCompletionsArrowDirection() { @@ -2746,26 +1898,22 @@ function calculateCallCompletionsArrow() { } function calculateCallCompletionsChange() { - if (!busyHourData.value || busyHourData.value.length < 2) return t('views.perfManage.voiceOverView.last') +' 1 Hour'+' +0' - - const latestValue = Number(busyHourData.value[busyHourData.value.length - 1].callCompletions) || 0 - const previousValue = Number(busyHourData.value[busyHourData.value.length - 2].callCompletions) || 0 - - const change = latestValue - previousValue - if (change === 0) return t('views.perfManage.voiceOverView.last') +' 1 Hour'+' +0' - - const changeText = change > 0 ? `+${change}` : `${change}` - return t('views.perfManage.voiceOverView.last') + ' 1 Hour' +` ${changeText}` + return calculateMetricChange({ + data: busyHourData.value || [], + valueExtractor: (item: any) => Number(item.callCompletions) || 0, + defaultTimeText: '1 Hour', + formatValue: (value: number) => value.toString(), + isPercentage: false + }) } // MOS相关计算函数 function calculateMosValue() { - if (!mosData.value || mosData.value.length === 0) return '-' - - // 获取最新的MOS值(数组最后一个元素) - const latestData = mosData.value[mosData.value.length - 1] - const mosValue = Number(latestData.mosAvg) || 0 - return mosValue.toFixed(2) + return calculateMetricValue({ + data: mosData.value, + valueExtractor: (item: any) => Number(item.mosAvg) || 0, + formatValue: (value: number) => value.toFixed(2) + }) } function calculateMosArrowDirection() { @@ -2793,33 +1941,23 @@ function calculateMosArrow() { } function calculateMosChange() { - if (!mosData.value || mosData.value.length < 2) return t('views.perfManage.voiceOverView.last') +' 1 Hour'+' +0' - - const latestData = mosData.value[mosData.value.length - 1] - const previousData = mosData.value[mosData.value.length - 2] - - const latestValue = Number(latestData.mosAvg) || 0 - const previousValue = Number(previousData.mosAvg) || 0 - - const change = latestValue - previousValue - if (change === 0) return t('views.perfManage.voiceOverView.last') +' 1 Hour'+' +0' - - const changeText = change > 0 ? `+${change.toFixed(2)}` : `${change.toFixed(2)}` - - // 计算实际时间差 - const timeDiff = calculateMosCctTimeDifference(latestData, previousData) - - return t('views.perfManage.voiceOverView.last') + ' ' + timeDiff +` ${changeText}` + return calculateMetricChange({ + data: mosData.value || [], + valueExtractor: (item: any) => Number(item.mosAvg) || 0, + timeDiffCalculator: calculateMosCctTimeDifference, + defaultTimeText: '1 Hour', + formatValue: (value: number) => value.toFixed(2), + isPercentage: false + }) } // CCT相关计算函数 function calculateCctValue() { - if (!cctData.value || cctData.value.length === 0) return '-' - - // 获取最新的CCT值(数组最后一个元素) - const latestData = cctData.value[cctData.value.length - 1] - const cctValue = Number(latestData.cctAvg) || 0 - return cctValue.toFixed(2) + return calculateMetricValue({ + data: cctData.value, + valueExtractor: (item: any) => Number(item.cctAvg) || 0, + formatValue: (value: number) => value.toFixed(2) + }) } function calculateCctArrowDirection() { @@ -2847,50 +1985,16 @@ function calculateCctArrow() { } function calculateCctChange() { - if (!cctData.value || cctData.value.length < 2) return t('views.perfManage.voiceOverView.last') +' 1 Hour'+' +0' - - const latestData = cctData.value[cctData.value.length - 1] - const previousData = cctData.value[cctData.value.length - 2] - - const latestValue = Number(latestData.cctAvg) || 0 - const previousValue = Number(previousData.cctAvg) || 0 - - const change = latestValue - previousValue - if (change === 0) return t('views.perfManage.voiceOverView.last') +' 1 Hour'+' +0' - - const changeText = change > 0 ? `+${change.toFixed(2)}` : `${change.toFixed(2)}` - - // 计算实际时间差 - const timeDiff = calculateMosCctTimeDifference(latestData, previousData) - - return t('views.perfManage.voiceOverView.last') + ' ' + timeDiff + ` ${changeText}` + return calculateMetricChange({ + data: cctData.value || [], + valueExtractor: (item: any) => Number(item.cctAvg) || 0, + timeDiffCalculator: calculateMosCctTimeDifference, + defaultTimeText: '1 Hour', + formatValue: (value: number) => value.toFixed(2), + isPercentage: false + }) } -// 测试数据更新 -// function testDataUpdate() { -// console.log('测试数据更新') -// -// // 创建模拟的后端KPI数据消息格式 -// const mockWebSocketMessage = { -// code: RESULT_CODE_SUCCESS, -// 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) -// }