This commit is contained in:
lai
2025-08-01 15:54:34 +08:00

View File

@@ -1,7 +1,8 @@
<template>
<div class="dashboard-cards">
<div style="font-size:32px; font-weight: bold; margin-bottom: 20px; text-align: center;">
<div style="font-size:32px; font-weight: bold; margin-bottom: 20px; text-align: center; position: relative;">
Dashboard
<span class="control-tower-badge">IMS Control Tower</span>
</div>
<div style="margin-bottom: 24px; text-align: right;">
<label style="margin-right: 8px; font-weight: 600;">NE</label>
@@ -17,16 +18,16 @@
:allow-clear="false"
/>
<!-- 调试信息 -->
<div style="margin-top: 8px; font-size: 12px; color: #666;">
WebSocket状态: {{ wsStatus }} | 数据点数量: {{ imsRealtimeRawData.length }}
</div>
<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>-->
<!-- <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>-->
@@ -354,7 +355,7 @@ function updateActiveCallsChart() {
const xAxisData = [1, 2, 3, 4, 5]
chart.setOption({
grid: { left: 0, right: 0, top: 10, bottom: 10 },
grid: { left: 0, right: 30, top: 10, bottom: 10 }, // 增加右侧边距
xAxis: { type: 'category', show: false, data: xAxisData },
yAxis: { type: 'value', show: false },
series: [{
@@ -388,7 +389,7 @@ function updateActiveCallsChart() {
const latestValue = chartData[chartData.length - 1]
chart.setOption({
grid: { left: 0, right: 0, top: 10, bottom: 10 },
grid: { left: 0, right: 30, top: 10, bottom: 10 }, // 增加右侧边距
xAxis: { type: 'category', show: false, data: xAxisData },
yAxis: { type: 'value', show: false },
series: [{
@@ -396,45 +397,101 @@ function updateActiveCallsChart() {
type: 'line', symbol: 'none',
lineStyle: { width: 2, color: '#1890ff' }, // 蓝色线条表示有数据
areaStyle: { color: 'rgba(24,144,255,0.1)' } // 淡蓝色填充
}],
graphic: [
{
type: 'text',
right: 8,
top: 8,
style: {
text: maxValue.toString(),
fontSize: 12,
fill: '#666',
fontWeight: 'bold'
}
},
{
type: 'text',
right: 8,
top: '50%',
style: {
text: latestValue.toString(),
fontSize: 12,
fill: '#1890ff',
fontWeight: 'bold'
}
},
{
type: 'text',
right: 8,
bottom: 8,
style: {
text: minValue.toString(),
fontSize: 12,
fill: '#666',
fontWeight: 'bold'
}
}
]
}]
})
console.log('updateActiveCallsChart - 图表更新完成')
// 在图表容器中添加数值标注
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 = 'now'
chartContainer.appendChild(maxLabel)
chartContainer.appendChild(latestLabel)
chartContainer.appendChild(minLabel)
chartContainer.appendChild(oldestTimeLabel)
chartContainer.appendChild(nowTimeLabel)
}
}
// 更新failed calls图表
@@ -462,7 +519,7 @@ function updateFailedCallsChart() {
const xAxisData = [1, 2, 3, 4, 5]
chart.setOption({
grid: { left: 0, right: 0, top: 10, bottom: 10 },
grid: { left: 0, right: 30, top: 10, bottom: 10 }, // 增加右侧边距
xAxis: { type: 'category', show: false, data: xAxisData },
yAxis: { type: 'value', show: false },
series: [{
@@ -494,7 +551,7 @@ function updateFailedCallsChart() {
const latestValue = chartData[chartData.length - 1]
chart.setOption({
grid: { left: 0, right: 0, top: 10, bottom: 10 },
grid: { left: 0, right: 30, top: 10, bottom: 10 }, // 增加右侧边距
xAxis: { type: 'category', show: false, data: xAxisData },
yAxis: { type: 'value', show: false },
series: [{
@@ -502,43 +559,101 @@ function updateFailedCallsChart() {
type: 'line', symbol: 'none',
lineStyle: { width: 2, color: '#faad14' }, // 橙色线条表示failed calls
areaStyle: { color: 'rgba(250,173,20,0.1)' } // 淡橙色填充
}],
graphic: [
{
type: 'text',
right: 8,
top: 8,
style: {
text: maxValue.toString(),
fontSize: 12,
fill: '#666',
fontWeight: 'bold'
}
},
{
type: 'text',
right: 8,
top: '50%',
style: {
text: latestValue.toString(),
fontSize: 12,
fill: '#faad14',
fontWeight: 'bold'
}
},
{
type: 'text',
right: 8,
bottom: 8,
style: {
text: minValue.toString(),
fontSize: 12,
fill: '#666',
fontWeight: 'bold'
}
}
]
}]
})
// 在图表容器中添加数值标注
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 = 'now'
chartContainer.appendChild(maxLabel)
chartContainer.appendChild(latestLabel)
chartContainer.appendChild(minLabel)
chartContainer.appendChild(oldestTimeLabel)
chartContainer.appendChild(nowTimeLabel)
}
}
// 更新active registrations图表
@@ -564,7 +679,7 @@ function updateActiveRegistrationsChart() {
const xAxisData = [1, 2, 3, 4, 5]
chart.setOption({
grid: { left: 0, right: 0, top: 10, bottom: 10 },
grid: { left: 0, right: 30, top: 10, bottom: 10 }, // 增加右侧边距
xAxis: { type: 'category', show: false, data: xAxisData },
yAxis: { type: 'value', show: false },
series: [{
@@ -596,7 +711,7 @@ function updateActiveRegistrationsChart() {
const latestValue = chartData[chartData.length - 1]
chart.setOption({
grid: { left: 0, right: 0, top: 10, bottom: 10 },
grid: { left: 0, right: 30, top: 10, bottom: 10 }, // 增加右侧边距
xAxis: { type: 'category', show: false, data: xAxisData },
yAxis: { type: 'value', show: false },
series: [{
@@ -604,43 +719,101 @@ function updateActiveRegistrationsChart() {
type: 'line', symbol: 'none',
lineStyle: { width: 2, color: '#1890ff' }, // 蓝色线条表示active registrations
areaStyle: { color: 'rgba(24,144,255,0.1)' } // 淡蓝色填充
}],
graphic: [
{
type: 'text',
right: 8,
top: 8,
style: {
text: maxValue.toString(),
fontSize: 12,
fill: '#666',
fontWeight: 'bold'
}
},
{
type: 'text',
right: 8,
top: '50%',
style: {
text: latestValue.toString(),
fontSize: 12,
fill: '#1890ff',
fontWeight: 'bold'
}
},
{
type: 'text',
right: 8,
bottom: 8,
style: {
text: minValue.toString(),
fontSize: 12,
fill: '#666',
fontWeight: 'bold'
}
}
]
}]
})
// 在图表容器中添加数值标注
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 = 'now'
chartContainer.appendChild(maxLabel)
chartContainer.appendChild(latestLabel)
chartContainer.appendChild(minLabel)
chartContainer.appendChild(oldestTimeLabel)
chartContainer.appendChild(nowTimeLabel)
}
}
// 更新failed registrations图表
@@ -668,7 +841,7 @@ function updateFailedRegistrationsChart() {
const xAxisData = [1, 2, 3, 4, 5]
chart.setOption({
grid: { left: 0, right: 0, top: 10, bottom: 10 },
grid: { left: 0, right: 30, top: 10, bottom: 10 }, // 增加右侧边距
xAxis: { type: 'category', show: false, data: xAxisData },
yAxis: { type: 'value', show: false },
series: [{
@@ -700,7 +873,7 @@ function updateFailedRegistrationsChart() {
const latestValue = chartData[chartData.length - 1]
chart.setOption({
grid: { left: 0, right: 0, top: 10, bottom: 10 },
grid: { left: 0, right: 30, top: 10, bottom: 10 }, // 增加右侧边距
xAxis: { type: 'category', show: false, data: xAxisData },
yAxis: { type: 'value', show: false },
series: [{
@@ -708,43 +881,101 @@ function updateFailedRegistrationsChart() {
type: 'line', symbol: 'none',
lineStyle: { width: 2, color: '#f5222d' }, // 红色线条表示failed registrations
areaStyle: { color: 'rgba(245,34,45,0.1)' } // 淡红色填充
}],
graphic: [
{
type: 'text',
right: 8,
top: 8,
style: {
text: maxValue.toString(),
fontSize: 12,
fill: '#666',
fontWeight: 'bold'
}
},
{
type: 'text',
right: 8,
top: '50%',
style: {
text: latestValue.toString(),
fontSize: 12,
fill: '#f5222d',
fontWeight: 'bold'
}
},
{
type: 'text',
right: 8,
bottom: 8,
style: {
text: minValue.toString(),
fontSize: 12,
fill: '#666',
fontWeight: 'bold'
}
}
]
}]
})
// 在图表容器中添加数值标注
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 = 'now'
chartContainer.appendChild(maxLabel)
chartContainer.appendChild(latestLabel)
chartContainer.appendChild(minLabel)
chartContainer.appendChild(oldestTimeLabel)
chartContainer.appendChild(nowTimeLabel)
}
}
// 处理IMS实时数据只存储当前选中网元
@@ -822,7 +1053,7 @@ function handleIMSRealtimeData(res: any) {
onMounted(() => {
// 初始化所有图表为0值线
const defaultChartOption = {
grid: { left: 0, right: 0, top: 10, bottom: 10 },
grid: { left: 0, right: 30, top: 10, bottom: 10 }, // 增加右侧边距
xAxis: { type: 'category', show: false, data: [1, 2, 3, 4, 5] },
yAxis: { type: 'value', show: false },
series: [{
@@ -848,7 +1079,7 @@ onMounted(() => {
const latestValue = mosData[mosData.length - 1]
chart.setOption({
grid: { left: 0, right: 0, top: 10, bottom: 10 },
grid: { left: 0, right: 30, top: 10, bottom: 10 }, // 增加右侧边距
xAxis: { type: 'category', show: false, data: [1,2,3,4,5] },
yAxis: { type: 'value', show: false },
series: [{
@@ -932,7 +1163,7 @@ function calculateMOValue() {
return moValue.toFixed(1)
}
// 计算MT值 (SCSCF.06/SCSCF.07)*100
// 计算MT值 (SCSCF.07/SCSCF.08)*100
function calculateMTValue() {
if (imsRealtimeRawData.value.length === 0) return '-'
@@ -941,12 +1172,12 @@ function calculateMTValue() {
if (!latestData || !latestData.data) return '-'
const kpiEvent = latestData.data
const scscf06 = Number(kpiEvent['SCSCF.06']) || 0
const scscf07 = Number(kpiEvent['SCSCF.07']) || 0
const scscf08 = Number(kpiEvent['SCSCF.08']) || 0
if (scscf07 === 0) return '-'
if (scscf08 === 0) return '-'
const mtValue = (scscf06 / scscf07) * 100
const mtValue = (scscf07 / scscf08) * 100
return mtValue.toFixed(1)
}
@@ -1046,6 +1277,22 @@ function calculateTimeDifference(latestData: any, previousData: any) {
}
}
// 计算相对时间(距离现在多久)
function calculateRelativeTime(timestamp: number) {
const now = Date.now()
const diffMs = now - timestamp
const diffMinutes = Math.floor(diffMs / (1000 * 60))
const diffHours = Math.floor(diffMinutes / 60)
if (diffHours >= 1) {
return `${diffHours}h`
} else if (diffMinutes >= 1) {
return `${diffMinutes}m`
} else {
return '1m'
}
}
// 辅助函数从数据中计算MO值
function calculateMOValueFromData(kpiEvent: any) {
const scscf05 = Number(kpiEvent['SCSCF.05']) || 0
@@ -1055,9 +1302,9 @@ function calculateMOValueFromData(kpiEvent: any) {
// 辅助函数从数据中计算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
const scscf08 = Number(kpiEvent['SCSCF.08']) || 0
return scscf08 === 0 ? '-' : (scscf07 / scscf08) * 100
}
// 计算registration success值
@@ -1527,30 +1774,30 @@ function calculateFailedRegistrationsChange() {
}
// 测试数据更新
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)
}
// 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>
@@ -1593,12 +1840,14 @@ function testDataUpdate() {
display: flex;
flex-direction: column;
justify-content: flex-end;
margin-bottom: 25px; /* 为底部时间标注留出空间 */
}
.mini-chart {
width: 100%;
height: 100%;
margin-bottom: 0;
display: block;
position: relative;
}
.card-subtext {
font-size: 12px;
@@ -1679,4 +1928,30 @@ function testDataUpdate() {
[data-theme='dark'] .ims-select :deep(.ant-select-arrow) {
color: #cacada;
}
/* IMS Control Tower 角标样式 */
.control-tower-badge {
position: absolute;
left: 50%;
bottom: 0;
margin-left: 100px;
font-size: 12px;
font-weight: 500;
color: #1890ff;
background: linear-gradient(135deg, #e6f7ff 0%, #bae7ff 100%);
padding: 4px 12px;
border-radius: 12px;
border: 1px solid #91d5ff;
box-shadow: 0 2px 4px rgba(24, 144, 255, 0.1);
letter-spacing: 0.5px;
transform: translateY(4px);
}
/* 暗色主题适配 */
[data-theme='dark'] .control-tower-badge {
color: #69c0ff;
background: linear-gradient(135deg, #001529 0%, #002766 100%);
border-color: #1890ff;
box-shadow: 0 2px 4px rgba(105, 192, 255, 0.1);
}
</style>