update at 2026-01-14 16:47:43
This commit is contained in:
408
src/App.vue
408
src/App.vue
@@ -1,40 +1,170 @@
|
||||
<template>
|
||||
<div class="monitor-container" :class="{ 'is-top2': currentStyle === 'top2' }">
|
||||
<h1 class="title">火情监控全链路业务监控视图</h1>
|
||||
|
||||
<!-- 样式切换按钮 -->
|
||||
<div class="style-switcher">
|
||||
<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')"
|
||||
>
|
||||
Top2样式
|
||||
原型-2
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="svg-wrapper"
|
||||
:class="{ 'is-top2': currentStyle === 'top2' }"
|
||||
ref="svgWrapper"
|
||||
v-html="svgContent"
|
||||
></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 } from 'vue'
|
||||
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 {
|
||||
@@ -66,6 +196,20 @@ onMounted(() => {
|
||||
loadSvg('circle')
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
detachTop2Observer()
|
||||
})
|
||||
|
||||
watch(currentStyle, async (style) => {
|
||||
if (style === 'top2') {
|
||||
await nextTick()
|
||||
attachTop2Observer()
|
||||
updateTop2Scale()
|
||||
} else {
|
||||
detachTop2Observer()
|
||||
}
|
||||
})
|
||||
|
||||
const addFlowingAnimation = () => {
|
||||
if (!svgWrapper.value) return
|
||||
|
||||
@@ -337,6 +481,7 @@ const addTop2Animation = () => {
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
padding: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.style-switcher {
|
||||
@@ -345,6 +490,14 @@ const addTop2Animation = () => {
|
||||
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;
|
||||
@@ -427,31 +580,215 @@ const addTop2Animation = () => {
|
||||
}
|
||||
|
||||
.monitor-container.is-top2 {
|
||||
padding: 8px;
|
||||
gap: 12px;
|
||||
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(1296px * var(--top2-scale));
|
||||
height: calc(692px * var(--top2-scale));
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.top2-layout {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 1296px;
|
||||
height: 692px;
|
||||
background: #2543a5;
|
||||
border-radius: 0;
|
||||
overflow: hidden;
|
||||
transform: scale(var(--top2-scale));
|
||||
transform-origin: top left;
|
||||
}
|
||||
|
||||
.top2-header {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 16px;
|
||||
width: 1264px;
|
||||
height: 50px;
|
||||
border-radius: 16px;
|
||||
background: #e5e5e5;
|
||||
border: 0.297px solid #9fbaff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.top2-title {
|
||||
position: absolute;
|
||||
left: 516.1px;
|
||||
top: 7.78px;
|
||||
font-family: 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||||
font-size: 28.491px;
|
||||
font-weight: 700;
|
||||
color: #2543a5;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.top2-content {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 82px;
|
||||
width: 1264px;
|
||||
height: 594px;
|
||||
}
|
||||
|
||||
.top2-left-col {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 26.055px;
|
||||
width: 232.377px;
|
||||
height: 541.889px;
|
||||
}
|
||||
|
||||
.top2-center-col {
|
||||
position: absolute;
|
||||
left: 248.377px;
|
||||
top: 25.15px;
|
||||
width: 744.126px;
|
||||
height: 543.699px;
|
||||
}
|
||||
|
||||
.top2-right-col {
|
||||
position: absolute;
|
||||
left: 1008.503px;
|
||||
top: 25.907px;
|
||||
width: 232.377px;
|
||||
height: 542.186px;
|
||||
}
|
||||
|
||||
.panel-outline {
|
||||
position: absolute;
|
||||
border: 0.297px solid #f0e2e2;
|
||||
border-radius: 10.684px;
|
||||
background: rgba(217, 217, 217, 0);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.panel-left-top {
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 232.377px;
|
||||
height: 259.977px;
|
||||
}
|
||||
|
||||
.panel-left-bottom {
|
||||
left: 0;
|
||||
top: 275.977px;
|
||||
width: 232.377px;
|
||||
height: 265.912px;
|
||||
}
|
||||
|
||||
.panel-right-top {
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 232.377px;
|
||||
height: 259.977px;
|
||||
}
|
||||
|
||||
.panel-right-bottom {
|
||||
left: 0;
|
||||
top: 275.977px;
|
||||
width: 232.377px;
|
||||
height: 266.209px;
|
||||
}
|
||||
|
||||
.panel-center-bottom-left {
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 360.668px;
|
||||
height: 175.692px;
|
||||
}
|
||||
|
||||
.panel-center-bottom-right {
|
||||
left: 386.668px;
|
||||
top: 0;
|
||||
width: 357.216px;
|
||||
height: 175.692px;
|
||||
}
|
||||
|
||||
.top2-center {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 743.928px;
|
||||
height: 352.007px;
|
||||
}
|
||||
|
||||
.top2-bottom-row {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 368.007px;
|
||||
width: 744.126px;
|
||||
height: 175.692px;
|
||||
}
|
||||
|
||||
.svg-wrapper.is-top2 {
|
||||
max-width: none;
|
||||
flex: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: none;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border-radius: 55px;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.svg-wrapper.is-top2 :deep(svg) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: none;
|
||||
filter: none;
|
||||
.panel-label {
|
||||
position: absolute;
|
||||
font-family: 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||||
font-size: 18.994px;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.svg-wrapper.is-top2 :deep(#shape1) {
|
||||
fill: #211677;
|
||||
stroke: none;
|
||||
.label-metric-11 {
|
||||
left: 61.73px;
|
||||
top: 91.7px;
|
||||
}
|
||||
|
||||
.label-metric-21 {
|
||||
left: 61.73px;
|
||||
top: 121.68px;
|
||||
}
|
||||
|
||||
.label-metric-22 {
|
||||
left: 132.11px;
|
||||
top: 76.27px;
|
||||
}
|
||||
|
||||
.label-metric-23 {
|
||||
left: 126.11px;
|
||||
top: 76.27px;
|
||||
}
|
||||
|
||||
.label-alarm {
|
||||
left: 78.35px;
|
||||
top: 18.7px;
|
||||
color: #f02525;
|
||||
}
|
||||
|
||||
.label-fault {
|
||||
left: 78.35px;
|
||||
top: 22.85px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.svg-wrapper :deep(svg) {
|
||||
@@ -462,6 +799,17 @@ const addTop2Animation = () => {
|
||||
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;
|
||||
}
|
||||
@@ -479,14 +827,14 @@ const addTop2Animation = () => {
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.svg-wrapper {
|
||||
.monitor-container:not(.is-top2) .svg-wrapper {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 手机适配 */
|
||||
@media (max-width: 768px) {
|
||||
.monitor-container {
|
||||
.monitor-container:not(.is-top2) {
|
||||
gap: 15px;
|
||||
padding: 10px;
|
||||
}
|
||||
@@ -498,7 +846,7 @@ const addTop2Animation = () => {
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.svg-wrapper {
|
||||
.monitor-container:not(.is-top2) .svg-wrapper {
|
||||
padding: 15px;
|
||||
border-radius: 15px;
|
||||
}
|
||||
@@ -506,7 +854,7 @@ const addTop2Animation = () => {
|
||||
|
||||
/* 小屏手机适配 */
|
||||
@media (max-width: 480px) {
|
||||
.monitor-container {
|
||||
.monitor-container:not(.is-top2) {
|
||||
gap: 10px;
|
||||
padding: 8px;
|
||||
}
|
||||
@@ -518,7 +866,7 @@ const addTop2Animation = () => {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.svg-wrapper {
|
||||
.monitor-container:not(.is-top2) .svg-wrapper {
|
||||
padding: 10px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
@@ -8,9 +8,6 @@ body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -19,7 +16,7 @@ body {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
align-items: stretch;
|
||||
justify-content: stretch;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user