Files
monitor/src/App.vue
2026-01-14 16:54:50 +08:00

916 lines
24 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="monitor-container" :class="{ 'is-top2': currentStyle === 'top2' }">
<!-- 样式切换按钮 -->
<div class="style-switcher" :class="{ 'is-top2': currentStyle === 'top2' }">
<button
:class="['style-btn', { active: currentStyle === 'circle' }]"
@click="switchStyle('circle')"
>
原型-1
</button>
<button
:class="['style-btn', { active: currentStyle === 'top2' }]"
@click="switchStyle('top2')"
>
原型-2
</button>
</div>
<template v-if="currentStyle === 'top2'">
<div class="top2-stage" ref="top2Stage" :style="{ '--top2-scale': top2Scale }">
<div class="top2-scale">
<div class="top2-layout">
<div class="top2-header" data-node-id="153:2111">
<div class="top2-title" data-node-id="153:2112">火情综合监控系统</div>
</div>
<div class="top2-content">
<div class="top2-left-col">
<div class="panel-outline panel-left-top" data-node-id="153:2087">
<div class="panel-label label-metric-11" data-node-id="153:2098">视联拉流质量统计</div>
</div>
<div class="panel-outline panel-left-middle" data-node-id="153:2089">
<div class="panel-label label-metric-21" data-node-id="153:2099">AI分析质量监控</div>
</div>
<div class="panel-outline panel-left-bottom" data-node-id="271:27">
<div class="panel-label label-metric-31" data-node-id="271:28">网络质量分析</div>
</div>
</div>
<div class="top2-center-col">
<div class="top2-cards" data-node-id="270:19">
<div class="top2-card" data-node-id="277:79">
<div class="top2-card-icon">
<img :src="top2CardIcons.device" alt="" />
</div>
<div class="top2-card-text">
<div class="top2-card-label">火情设备数</div>
<div class="top2-card-value">54781</div>
<div class="top2-card-sub">截止今日数量</div>
</div>
</div>
<div class="top2-card" data-node-id="279:46">
<div class="top2-card-icon">
<img :src="top2CardIcons.stream" alt="" />
</div>
<div class="top2-card-text">
<div class="top2-card-label">拉流质量</div>
<div class="top2-card-value">85.2%</div>
<div class="top2-card-sub">拉流成功率指标</div>
</div>
</div>
<div class="top2-card" data-node-id="279:54">
<div class="top2-card-icon">
<img :src="top2CardIcons.ai" alt="" />
</div>
<div class="top2-card-text">
<div class="top2-card-label">AI分析质量</div>
<div class="top2-card-value">99.5%</div>
<div class="top2-card-sub">AI请求成功率</div>
</div>
</div>
<div class="top2-card" data-node-id="279:62">
<div class="top2-card-icon">
<img :src="top2CardIcons.cut" alt="" />
</div>
<div class="top2-card-text">
<div class="top2-card-label">切图质量</div>
<div class="top2-card-value">99.5%</div>
<div class="top2-card-sub">抽帧切图成功率</div>
</div>
</div>
</div>
<div class="top2-subheader" data-node-id="271:25">业务系统分层整体架构展示</div>
<div class="top2-center" data-node-id="162:5">
<div class="svg-wrapper is-top2" ref="svgWrapper" v-html="svgContent"></div>
</div>
<div class="top2-bottom-row">
<div class="panel-outline panel-center-bottom-left" data-node-id="153:2091">
<div class="panel-label label-metric-22" data-node-id="153:2100">监控指标区22</div>
</div>
<div class="panel-outline panel-center-bottom-right" data-node-id="153:2092">
<div class="panel-label label-metric-23" data-node-id="153:2102">监控指标区23</div>
</div>
</div>
</div>
<div class="top2-right-col">
<div class="panel-outline panel-right-top" data-node-id="153:2088">
<div class="panel-label label-alarm" data-node-id="153:2095">告警看板</div>
</div>
<div class="panel-outline panel-right-bottom" data-node-id="153:2090">
<div class="panel-label label-fault" data-node-id="153:2096">故障看板</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<template v-else>
<h1 class="title">火情监控全链路业务监控视图</h1>
<div class="svg-wrapper" ref="svgWrapper" v-html="svgContent"></div>
</template>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'
type StyleType = 'circle' | 'top2'
const svgWrapper = ref<HTMLElement | null>(null)
const svgContent = ref('')
const currentStyle = ref<StyleType>('circle')
const top2Stage = ref<HTMLElement | null>(null)
const top2Scale = ref(1)
const TOP2_WIDTH = 1438
const TOP2_HEIGHT = 832
let top2ResizeObserver: ResizeObserver | null = null
const top2CardIcons = {
device: 'https://www.figma.com/api/mcp/asset/d0315e83-c91a-4df2-aa1c-c68bc334548a',
stream: 'https://www.figma.com/api/mcp/asset/05d50c71-8671-4e90-beb7-46c7186a2c1f',
ai: 'https://www.figma.com/api/mcp/asset/8e943e5a-ebbb-44d4-9caa-9d32a8329ab7',
cut: 'https://www.figma.com/api/mcp/asset/c1ba83a9-ec62-49b0-9b07-925faae32277',
}
const updateTop2Scale = () => {
const stage = top2Stage.value
if (!stage) return
const { width, height } = stage.getBoundingClientRect()
if (!width || !height) return
const scaleX = width / TOP2_WIDTH
const scaleY = height / TOP2_HEIGHT
top2Scale.value = Math.min(scaleX, scaleY)
}
const attachTop2Observer = () => {
const stage = top2Stage.value
if (!stage) return
if (!top2ResizeObserver) {
top2ResizeObserver = new ResizeObserver(() => {
updateTop2Scale()
})
} else {
top2ResizeObserver.disconnect()
}
top2ResizeObserver.observe(stage)
}
const detachTop2Observer = () => {
if (!top2ResizeObserver) return
top2ResizeObserver.disconnect()
}
const loadSvg = async (style: StyleType) => {
try {
const fileName = style === 'circle' ? 'monitor.svg' : 'top2.svg'
const response = await fetch(`/${fileName}`)
const content = await response.text()
svgContent.value = content
// 等待 DOM 更新后添加动画
setTimeout(() => {
if (style === 'circle') {
addFlowingAnimation()
} else {
addTop2Animation()
}
}, 100)
} catch (error) {
console.error('加载 SVG 失败:', error)
}
}
const switchStyle = (style: StyleType) => {
currentStyle.value = style
loadSvg(style)
}
onMounted(() => {
loadSvg('circle')
})
onBeforeUnmount(() => {
detachTop2Observer()
})
watch(currentStyle, async (style) => {
if (style === 'top2') {
await nextTick()
attachTop2Observer()
updateTop2Scale()
} else {
detachTop2Observer()
}
})
const addFlowingAnimation = () => {
if (!svgWrapper.value) return
const svg = svgWrapper.value.querySelector('svg')
if (!svg) {
console.log('SVG not found')
return
}
// 查找所有可能的连线元素
const allPaths = svg.querySelectorAll('path, line, polyline')
console.log('Found elements:', allPaths.length)
let animatedCount = 0
allPaths.forEach((element, index) => {
const el = element as SVGPathElement | SVGLineElement | SVGPolylineElement
// 检查是否有描边
const hasStroke = el.getAttribute('stroke') && el.getAttribute('stroke') !== 'none'
const hasFill = el.getAttribute('fill') && el.getAttribute('fill') !== 'none'
// 只处理有描边且填充为none或没有填充的元素这些是连线
if (!hasStroke || (hasFill && el.getAttribute('fill') !== 'transparent')) {
return
}
// 跳过顶部第一条虚线shape582
const elementId = el.getAttribute('id')
if (elementId === 'shape582') {
return
}
const strokeWidth = parseFloat(el.getAttribute('stroke-width') || '1')
if (strokeWidth < 0.5) return
animatedCount++
// 保存原始描边颜色和宽度
const originalStroke = el.getAttribute('stroke') || '#595959'
const originalWidth = strokeWidth
// 判断是否为红色线条
const isRed = originalStroke.toLowerCase().includes('red') ||
originalStroke.toLowerCase().includes('#eb') ||
originalStroke.toLowerCase().includes('#ff') ||
originalStroke.toLowerCase().includes('#f') ||
originalStroke.toLowerCase().includes('rgb(235') ||
originalStroke.toLowerCase().includes('rgb(255')
// 根据线条颜色选择流动颜色
const flowColor = isRed ? '#EB5017' : '#00ff88'
// 创建流动层(克隆原线条)
const flowLine = el.cloneNode(true) as SVGElement
flowLine.setAttribute('stroke', flowColor)
flowLine.setAttribute('stroke-width', String(originalWidth * 1.2))
flowLine.setAttribute('stroke-linecap', 'round')
flowLine.setAttribute('stroke-opacity', '0.7')
flowLine.setAttribute('fill', 'none')
flowLine.classList.add('flowing-line')
// 计算路径长度
let pathLength = 0
if (flowLine instanceof SVGPathElement) {
pathLength = flowLine.getTotalLength()
} else if (flowLine instanceof SVGLineElement) {
const x1 = parseFloat(flowLine.getAttribute('x1') || '0')
const y1 = parseFloat(flowLine.getAttribute('y1') || '0')
const x2 = parseFloat(flowLine.getAttribute('x2') || '0')
const y2 = parseFloat(flowLine.getAttribute('y2') || '0')
pathLength = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2))
}
// 设置虚线动画 - 更明显的流动效果
const dashLength = 40
const gapLength = 80
flowLine.style.strokeDasharray = `${dashLength} ${gapLength}`
flowLine.style.strokeDashoffset = String(pathLength)
// 插入流动层到原线条之后
el.parentNode?.insertBefore(flowLine, el.nextSibling)
// 添加 CSS 动画 - 减慢速度到 8-12 秒
const duration = 8 + (index % 5)
flowLine.style.animation = `flowLine ${duration}s linear infinite`
flowLine.style.animationDelay = `${(index * 0.5) % 3}s`
})
console.log('Animated lines:', animatedCount)
// 添加 CSS 动画样式
if (!document.getElementById('flowing-animation-style')) {
const style = document.createElement('style')
style.id = 'flowing-animation-style'
style.textContent = `
@keyframes flowLine {
from {
stroke-dashoffset: 1000;
}
to {
stroke-dashoffset: -1000;
}
}
.flowing-line {
filter: drop-shadow(0 0 6px currentColor);
pointer-events: none;
}
`
document.head.appendChild(style)
}
}
// Top2 样式的动画效果
const addTop2Animation = () => {
if (!svgWrapper.value) return
const svg = svgWrapper.value.querySelector('svg')
if (!svg) {
console.log('SVG not found')
return
}
// 查找所有可能的连线元素
const allPaths = svg.querySelectorAll('path, line, polyline')
console.log('Found elements for top2:', allPaths.length)
let animatedCount = 0
allPaths.forEach((element, index) => {
const el = element as SVGPathElement | SVGLineElement | SVGPolylineElement
// 检查是否有描边
const hasStroke = el.getAttribute('stroke') && el.getAttribute('stroke') !== 'none'
const hasFill = el.getAttribute('fill') && el.getAttribute('fill') !== 'none'
// 只处理有描边且填充为none或没有填充的元素这些是连线
if (!hasStroke || (hasFill && el.getAttribute('fill') !== 'transparent')) {
return
}
const strokeWidth = parseFloat(el.getAttribute('stroke-width') || '1')
if (strokeWidth < 0.5) return
animatedCount++
// 保存原始描边颜色和宽度
const originalStroke = el.getAttribute('stroke') || '#595959'
const originalWidth = strokeWidth
// 判断是否为特殊颜色线条
const isBlue = originalStroke.toLowerCase().includes('blue') ||
originalStroke.toLowerCase().includes('#878fd3') ||
originalStroke.toLowerCase().includes('#3469f1')
const isRed = originalStroke.toLowerCase().includes('red') ||
originalStroke.toLowerCase().includes('#eb') ||
originalStroke.toLowerCase().includes('#cf0e30')
// 使用更鲜艳的科技感配色方案
let flowColor = '#00F5FF' // 亮青色(强科技感)
let glowColor = '#00F5FF'
let shadowColor = 'rgba(0, 245, 255, 0.6)'
if (isBlue) {
flowColor = '#00D4FF' // 明亮蓝
glowColor = '#4D9FFF'
shadowColor = 'rgba(0, 212, 255, 0.6)'
}
if (isRed) {
flowColor = '#FF1744' // 鲜艳红(警告色)
glowColor = '#FF4081'
shadowColor = 'rgba(255, 23, 68, 0.6)'
}
// 创建流动层(克隆原线条)
const flowLine = el.cloneNode(true) as SVGElement
flowLine.setAttribute('stroke', flowColor)
flowLine.setAttribute('stroke-width', String(originalWidth * 2.5))
flowLine.setAttribute('stroke-linecap', 'round')
flowLine.setAttribute('stroke-opacity', '1')
flowLine.setAttribute('fill', 'none')
flowLine.classList.add('flowing-line-top2')
flowLine.style.setProperty('--glow-color', glowColor)
flowLine.style.setProperty('--shadow-color', shadowColor)
// 计算路径长度
let pathLength = 0
if (flowLine instanceof SVGPathElement) {
pathLength = flowLine.getTotalLength()
} else if (flowLine instanceof SVGLineElement) {
const x1 = parseFloat(flowLine.getAttribute('x1') || '0')
const y1 = parseFloat(flowLine.getAttribute('y1') || '0')
const x2 = parseFloat(flowLine.getAttribute('x2') || '0')
const y2 = parseFloat(flowLine.getAttribute('y2') || '0')
pathLength = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2))
}
// 设置虚线动画 - 更明显的流动效果
const dashLength = 60
const gapLength = 120
flowLine.style.strokeDasharray = `${dashLength} ${gapLength}`
flowLine.style.strokeDashoffset = String(pathLength)
// 插入流动层到原线条之后
el.parentNode?.insertBefore(flowLine, el.nextSibling)
// 添加 CSS 动画 - 流畅快速的速度
const duration = 4 + (index % 3)
const pulseSpeed = duration * 0.4
flowLine.style.animation = `flowLineTop2 ${duration}s linear infinite, pulseGlow ${pulseSpeed}s ease-in-out infinite`
flowLine.style.animationDelay = `${(index * 0.3) % 2}s`
})
console.log('Animated top2 lines:', animatedCount)
// 添加 Top2 CSS 动画样式
if (!document.getElementById('flowing-animation-top2-style')) {
const style = document.createElement('style')
style.id = 'flowing-animation-top2-style'
style.textContent = `
@keyframes flowLineTop2 {
0% {
stroke-dashoffset: 1000;
}
100% {
stroke-dashoffset: -1000;
}
}
@keyframes pulseGlow {
0%, 100% {
filter: drop-shadow(0 0 6px var(--glow-color))
drop-shadow(0 0 12px var(--glow-color))
drop-shadow(0 0 18px var(--shadow-color));
stroke-opacity: 1;
}
50% {
filter: drop-shadow(0 0 12px var(--glow-color))
drop-shadow(0 0 24px var(--glow-color))
drop-shadow(0 0 36px var(--shadow-color))
drop-shadow(0 0 48px var(--shadow-color));
stroke-opacity: 1;
}
}
.flowing-line-top2 {
pointer-events: none;
mix-blend-mode: screen;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.flowing-line-top2:hover {
filter: drop-shadow(0 0 20px var(--glow-color))
drop-shadow(0 0 40px var(--shadow-color)) !important;
}
`
document.head.appendChild(style)
}
}
</script>
<style scoped>
.monitor-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
padding: 10px;
position: relative;
}
.style-switcher {
display: flex;
gap: 10px;
margin-top: 10px;
}
.style-switcher.is-top2 {
margin-top: 0;
position: absolute;
top: 24px;
right: 24px;
z-index: 5;
}
.style-btn {
padding: 10px 24px;
font-size: 16px;
font-weight: 600;
color: rgba(255, 255, 255, 0.7);
background: rgba(255, 255, 255, 0.1);
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
}
.style-btn:hover {
color: rgba(255, 255, 255, 0.9);
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.5);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(255, 255, 255, 0.2);
}
.style-btn.active {
color: white;
background: linear-gradient(135deg, rgba(0, 245, 255, 0.25), rgba(77, 159, 255, 0.25));
border-color: #00F5FF;
box-shadow: 0 0 20px rgba(0, 245, 255, 0.4),
0 0 40px rgba(0, 245, 255, 0.2),
inset 0 0 20px rgba(0, 245, 255, 0.1);
}
.style-btn.active:hover {
background: linear-gradient(135deg, rgba(0, 245, 255, 0.35), rgba(77, 159, 255, 0.35));
box-shadow: 0 0 25px rgba(0, 245, 255, 0.5),
0 0 50px rgba(0, 245, 255, 0.3),
inset 0 0 25px rgba(0, 245, 255, 0.15);
}
.title {
font-size: 2.5rem;
font-weight: 700;
color: white;
text-align: center;
text-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
letter-spacing: 2px;
padding: 20px 30px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
border: 2px solid rgba(255, 255, 255, 0.2);
animation: titleGlow 3s ease-in-out infinite;
width: 100%;
max-width: 1400px;
box-sizing: border-box;
}
@keyframes titleGlow {
0%, 100% {
box-shadow: 0 0 20px rgba(255, 255, 255, 0.3);
}
50% {
box-shadow: 0 0 40px rgba(255, 255, 255, 0.5);
}
}
.svg-wrapper {
flex: 1;
width: 100%;
max-width: 1400px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
overflow: auto;
min-height: 0;
box-sizing: border-box;
-webkit-overflow-scrolling: touch;
}
.monitor-container.is-top2 {
padding: 0;
gap: 0;
align-items: stretch;
justify-content: stretch;
background: #2543a5;
}
.top2-stage {
flex: 1;
width: 100%;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
background: #2543a5;
}
.top2-scale {
position: relative;
width: calc(1438px * var(--top2-scale));
height: calc(832px * var(--top2-scale));
flex-shrink: 0;
}
.top2-layout {
position: absolute;
left: 0;
top: 0;
width: 1438px;
height: 832px;
background: #2543a5;
border-radius: 0;
overflow: hidden;
transform: scale(var(--top2-scale));
transform-origin: top left;
display: flex;
flex-direction: column;
gap: 16px;
padding: 16px;
box-sizing: border-box;
}
.top2-header {
height: 50px;
border-radius: 16px;
background: rgba(229, 229, 229, 0);
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
}
.top2-title {
font-family: 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
font-size: 32px;
font-weight: 700;
color: #f9d1b0;
letter-spacing: 0.5px;
text-align: center;
}
.top2-content {
flex: 1;
display: flex;
gap: 16px;
}
.top2-left-col {
width: 314.937px;
height: 734px;
display: flex;
flex-direction: column;
gap: 16px;
}
.top2-center-col {
width: 744.126px;
height: 734px;
display: flex;
flex-direction: column;
gap: 16px;
}
.top2-right-col {
width: 314.937px;
height: 734px;
display: flex;
flex-direction: column;
gap: 16px;
}
.panel-outline {
position: relative;
border: 0.297px solid #f0e2e2;
border-radius: 10.684px;
background: rgba(217, 217, 217, 0);
box-sizing: border-box;
}
.panel-left-top {
width: 314.937px;
height: 234px;
}
.panel-left-middle {
width: 314.937px;
height: 234px;
}
.panel-left-bottom {
width: 314.937px;
height: 234px;
}
.panel-right-top {
width: 314.937px;
height: 359px;
}
.panel-right-bottom {
width: 314.937px;
height: 359px;
}
.panel-center-bottom-left {
width: 364.063px;
height: 187.992px;
}
.panel-center-bottom-right {
width: 364.063px;
height: 187.992px;
}
.top2-center {
width: 743.928px;
height: 333.004px;
position: relative;
}
.top2-bottom-row {
width: 744.126px;
height: 187.992px;
display: flex;
gap: 16px;
}
.top2-cards {
height: 114px;
display: flex;
gap: 8px;
align-items: center;
padding: 8px 0;
box-sizing: border-box;
}
.top2-card {
width: 180.032px;
height: 98px;
border-radius: 16px;
background: rgba(37, 67, 165, 0.51);
display: flex;
gap: 12px;
align-items: center;
padding: 8px;
box-sizing: border-box;
}
.top2-card-icon {
width: 51px;
height: 49px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.top2-card-icon img {
width: 100%;
height: 100%;
display: block;
}
.top2-card-text {
display: flex;
flex-direction: column;
gap: 4px;
}
.top2-card-label {
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
font-size: 18px;
font-weight: 600;
color: #ffffff;
}
.top2-card-value {
font-family: 'Inter', 'PingFang SC', 'Microsoft YaHei', sans-serif;
font-size: 24px;
font-weight: 700;
color: #ffffff;
line-height: 1;
}
.top2-card-sub {
font-family: 'Inter', 'PingFang SC', 'Microsoft YaHei', sans-serif;
font-size: 12px;
font-weight: 700;
color: rgba(191, 191, 191, 0.61);
}
.top2-subheader {
height: 51px;
display: flex;
align-items: center;
justify-content: center;
font-family: 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
font-size: 28px;
font-weight: 700;
color: #ffffff;
}
.svg-wrapper.is-top2 {
flex: none;
width: 100%;
height: 100%;
max-width: none;
padding: 0;
background: transparent;
border-radius: 0;
box-shadow: none;
overflow: hidden;
}
.panel-label {
position: absolute;
left: 50%;
top: 20px;
transform: translate(-50%, -50%);
font-family: 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
font-size: 24px;
font-weight: 700;
color: #ffffff;
letter-spacing: 0.2px;
text-align: center;
width: 220px;
}
.label-alarm {
color: #f02525;
}
.svg-wrapper :deep(svg) {
width: 100%;
height: auto;
max-height: 100%;
filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.1));
touch-action: pan-x pan-y;
}
.svg-wrapper.is-top2 :deep(svg) {
height: 100%;
max-height: none;
filter: none;
}
.svg-wrapper.is-top2 :deep(#shape1) {
fill: transparent;
stroke: none;
}
:deep(.flowing-line) {
transition: all 0.3s ease;
}
:deep(.flowing-line:hover) {
stroke-width: 6 !important;
filter: drop-shadow(0 0 8px #00ff88);
}
/* 平板适配 */
@media (max-width: 1024px) {
.title {
font-size: 2rem;
padding: 15px 20px;
letter-spacing: 1px;
}
.monitor-container:not(.is-top2) .svg-wrapper {
padding: 20px;
}
}
/* 手机适配 */
@media (max-width: 768px) {
.monitor-container:not(.is-top2) {
gap: 15px;
padding: 10px;
}
.title {
font-size: 1.5rem;
padding: 12px 16px;
letter-spacing: 0.5px;
border-radius: 15px;
}
.monitor-container:not(.is-top2) .svg-wrapper {
padding: 15px;
border-radius: 15px;
}
}
/* 小屏手机适配 */
@media (max-width: 480px) {
.monitor-container:not(.is-top2) {
gap: 10px;
padding: 8px;
}
.title {
font-size: 1.2rem;
padding: 10px 12px;
letter-spacing: 0.3px;
border-radius: 12px;
}
.monitor-container:not(.is-top2) .svg-wrapper {
padding: 10px;
border-radius: 12px;
}
}
</style>