From 4403b06416a3722b273dcee8c3552a42806bdeaf Mon Sep 17 00:00:00 2001 From: zhongzm Date: Mon, 8 Sep 2025 15:30:40 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E8=AF=AD=E9=9F=B3=E4=BB=AA=E8=A1=A8?= =?UTF-8?q?=E7=9B=98=E5=A2=9E=E5=8A=A0=E6=96=B0=E6=8C=87=E6=A0=87=E6=98=BE?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/perfManage/goldTarget.ts | 8 + src/views/perfManage/overview/index.vue | 492 +++++++++++++++++++++++- 2 files changed, 494 insertions(+), 6 deletions(-) diff --git a/src/api/perfManage/goldTarget.ts b/src/api/perfManage/goldTarget.ts index 7a4d4339..91860abd 100644 --- a/src/api/perfManage/goldTarget.ts +++ b/src/api/perfManage/goldTarget.ts @@ -134,3 +134,11 @@ export function updateKPITitle(data: Record) { data: data, }); } +//忙时呼叫 +export async function getbusyhour(query: Record) { + return request({ + url: '/neData/ims/kpi/busy-hour', + method: 'get', + params: query, + }); +} diff --git a/src/views/perfManage/overview/index.vue b/src/views/perfManage/overview/index.vue index 4bf8c1e5..a22e91b6 100644 --- a/src/views/perfManage/overview/index.vue +++ b/src/views/perfManage/overview/index.vue @@ -88,6 +88,42 @@ + + + +
Call Attempts 📞
+
+
+
+
+
+
+ {{ calculateCallAttemptsValue() }} + {{ calculateCallAttemptsArrow() }} +
+
{{ calculateCallAttemptsChange() }}
+
+
+
+
+ + +
Call Completions 📞
+
+
+
+
+
+
+ {{ calculateCallCompletionsValue() }} + {{ calculateCallCompletionsArrow() }} +
+
{{ calculateCallCompletionsChange() }}
+
+
+
+
+
{{ t('views.perfManage.voiceOverView.registration') }}
@@ -147,7 +183,7 @@ import { GridComponent } from 'echarts/components' import { CanvasRenderer } from 'echarts/renderers' import useNeInfoStore from '@/store/modules/neinfo' import { WS } from '@/plugins/ws-websocket' -import { listKPIData } from '@/api/perfManage/goldTarget' +import { listKPIData ,getbusyhour} from '@/api/perfManage/goldTarget' import { RESULT_CODE_SUCCESS } from '@/constants/result-constants' import useI18n from '@/hooks/useI18n'; const { t } = useI18n(); @@ -156,6 +192,8 @@ echarts.use([LineChart, GridComponent, CanvasRenderer]) const callsChartRef = ref(null) const mosChartRef = ref(null) const failedCallsChartRef = ref(null) +const callAttemptsChartRef = ref(null) +const callCompletionsChartRef = ref(null) const regChartRef = ref(null) const failedRegChartRef = ref(null) @@ -167,6 +205,8 @@ const selectedImsNeId = ref('') const imsWs = ref(null) // IMS实时原始数据(只存储当前选中网元) const imsRealtimeRawData = ref([]) +// Busy Hour数据(用于Call Attempts和Call Completions) +const busyHourData = ref(null) // WebSocket连接状态 const wsStatus = ref('no connection') @@ -185,8 +225,9 @@ onMounted(async () => { selectedImsNeId.value = imsNeList.value[0].neId // console.log('默认选中第一个IMS网元:', selectedImsNeId.value) // 调试信息 - // 先获取历史数据,再订阅实时数据 + // 先获取历史数据和Busy Hour数据,再订阅实时数据 await fetchHistoryData(selectedImsNeId.value) + await fetchBusyHourData(selectedImsNeId.value) subscribeImsRealtime(selectedImsNeId.value) } else { // console.warn('没有找到IMS类型的网元') // 调试信息 @@ -252,6 +293,44 @@ async function fetchHistoryData(neId: string) { } } +// 获取Busy Hour数据 +async function fetchBusyHourData(neId: string) { + if (!neId) return + + try { + // 获取当天日期的时间戳 + const today = new Date() + today.setHours(0, 0, 0, 0) // 设置为当天00:00:00 + const timestamp = today.getTime() + + // 构建请求参数 + const params = { + neId: neId, + timestamp: timestamp + } + + console.log('获取Busy Hour数据参数:', params) + + const res = await getbusyhour(params) + console.log('Busy Hour数据响应:', res) + + if (res.code === 1 && Array.isArray(res.data)) { + busyHourData.value = res.data + console.log('Busy Hour数据加载完成:', busyHourData.value) + + // 更新Call Attempts和Call Completions图表 + updateCallAttemptsChart() + updateCallCompletionsChart() + } else { + console.warn('获取Busy Hour数据失败或数据为空') + busyHourData.value = null + } + } catch (error) { + console.error('获取Busy Hour数据出错:', error) + busyHourData.value = null + } +} + // 切换IMS网元时,重新订阅 async function onImsNeChange() { // console.log('切换IMS网元,新的网元ID:', selectedImsNeId.value) // 调试信息 @@ -268,8 +347,9 @@ async function onImsNeChange() { updateActiveRegistrationsChart() updateFailedRegistrationsChart() - // 先获取历史数据,再订阅实时数据 + // 先获取历史数据和Busy Hour数据,再订阅实时数据 await fetchHistoryData(selectedImsNeId.value) + await fetchBusyHourData(selectedImsNeId.value) subscribeImsRealtime(selectedImsNeId.value) } @@ -998,6 +1078,300 @@ function updateFailedRegistrationsChart() { } } +// 更新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) { + 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()) + } + 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) { + 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()) + } + 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) + } +} + // 处理IMS实时数据(只存储当前选中网元) function handleIMSRealtimeData(res: any) { //console.log('收到实时数据:', res) // 调试信息 @@ -1152,6 +1526,18 @@ onMounted(() => { chart.setOption(defaultChartOption) } + // call attempts + if (callAttemptsChartRef.value) { + const chart = echarts.init(callAttemptsChartRef.value) + chart.setOption(defaultChartOption) + } + + // call completions + if (callCompletionsChartRef.value) { + const chart = echarts.init(callCompletionsChartRef.value) + chart.setOption(defaultChartOption) + } + // active registrations if (regChartRef.value) { const chart = echarts.init(regChartRef.value) @@ -1790,8 +2176,103 @@ function calculateFailedRegistrationsChange() { // 计算时间差 const timeDiff = calculateTimeDifference(latestData, previousData) - return `${changeText} ${+t('views.perfManage.voiceOverView.last')} ${timeDiff}` + return `${changeText} ${t('views.perfManage.voiceOverView.last')} ${timeDiff}` } + +// 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() +} + +function calculateCallAttemptsArrowDirection() { + if (!busyHourData.value || busyHourData.value.length < 2) return 'up' + + 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 'up' + if (change < 0) return 'down' + return 'up' +} + +function calculateCallAttemptsArrow() { + if (!busyHourData.value || busyHourData.value.length < 2) return '→' + + 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 '↗' + if (change < 0) return '↘' + return '→' +} + +function calculateCallAttemptsChange() { + if (!busyHourData.value || busyHourData.value.length < 2) return '±0 ' + t('views.perfManage.voiceOverView.last') + ' 1h' + + 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 '±0 ' + t('views.perfManage.voiceOverView.last') + ' 1h' + + const changeText = change > 0 ? `+${change}` : `${change}` + return `${changeText} ` + t('views.perfManage.voiceOverView.last') + ' 1h' +} + +// 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() +} + +function calculateCallCompletionsArrowDirection() { + if (!busyHourData.value || busyHourData.value.length < 2) return 'up' + + 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 'up' + if (change < 0) return 'down' + return 'up' +} + +function calculateCallCompletionsArrow() { + if (!busyHourData.value || busyHourData.value.length < 2) return '→' + + 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 '↗' + if (change < 0) return '↘' + return '→' +} + +function calculateCallCompletionsChange() { + if (!busyHourData.value || busyHourData.value.length < 2) return '±0 ' + t('views.perfManage.voiceOverView.last') + ' 1h' + + 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 '±0 ' + t('views.perfManage.voiceOverView.last') + ' 1h' + + const changeText = change > 0 ? `+${change}` : `${change}` + return `${changeText} ` + t('views.perfManage.voiceOverView.last') + ' 1h' +} + // 测试数据更新 // function testDataUpdate() { // console.log('测试数据更新') @@ -1854,8 +2335,6 @@ function calculateFailedRegistrationsChange() { .trend-chart { flex: 2; height: 60px; - min-width: 140px; - max-width: 220px; display: flex; flex-direction: column; justify-content: flex-end; @@ -1879,6 +2358,7 @@ function calculateFailedRegistrationsChange() { } .metric-info { flex: 1; + margin-right: 8px; margin-left: 16px; text-align: right; min-width: 90px;