|
|
@@ -2,7 +2,12 @@
|
|
|
import { ref, reactive, onMounted, onUnmounted } from 'vue'
|
|
|
import { Graph } from '@antv/x6'
|
|
|
import air from './air.vue'
|
|
|
+import { useFullscreen } from '@vueuse/core'
|
|
|
+import { Crop, FullScreen } from '@element-plus/icons-vue'
|
|
|
|
|
|
+// 新增外层容器 Ref
|
|
|
+const wrapperRef = ref<HTMLDivElement>()
|
|
|
+const targetArea = ref<HTMLDivElement>()
|
|
|
const containerRef = ref<HTMLDivElement>()
|
|
|
const graph = ref<Graph | null>(null)
|
|
|
let timer: number | null = null
|
|
|
@@ -35,8 +40,6 @@ const gasComposition = reactive([
|
|
|
{ name: '其他', value: 0.5, color: 'bg-slate-500' }
|
|
|
])
|
|
|
|
|
|
-// === 核心改造:所有设备统一数据源 (驱动面板、X6节点、Tooltip) ===
|
|
|
-// 我们额外存储一个 defaultParams 用于设备恢复启动时参考
|
|
|
const deviceData = reactive([
|
|
|
{
|
|
|
id: 'ac5',
|
|
|
@@ -299,16 +302,14 @@ const updateDeviceBaseStats = () => {
|
|
|
deviceData.forEach((dev) => {
|
|
|
if (dev.status === 'running') {
|
|
|
dev.params.forEach((p) => {
|
|
|
- // 排除掉由工艺链计算的“处理量”、“压力”等核心参数,只对排温、负载等进行基础波动
|
|
|
if (['排温', '负载率', '冲程', '冲次', '液位'].includes(p.label)) {
|
|
|
const range = Math.max(0.1, p.value * 0.01)
|
|
|
p.value = Number((p.value + (Math.random() - 0.5) * range).toFixed(1))
|
|
|
}
|
|
|
})
|
|
|
} else {
|
|
|
- // 停机状态下参数缓慢归零/降至环境值
|
|
|
dev.params.forEach((p) => {
|
|
|
- const target = p.label === '排温' ? 25 : 0 // 温度降至25度,其余归0
|
|
|
+ const target = p.label === '排温' ? 25 : 0
|
|
|
if (p.value > target) {
|
|
|
p.value = Number((p.value * 0.8 + target * 0.2).toFixed(1))
|
|
|
if (Math.abs(p.value - target) < 0.2) p.value = target
|
|
|
@@ -320,39 +321,32 @@ const updateDeviceBaseStats = () => {
|
|
|
|
|
|
const startRealtimeSimulation = () => {
|
|
|
timer = window.setInterval(() => {
|
|
|
- // === 第一步:计算空压机源头输出 ===
|
|
|
const runningACs = deviceData.filter((d) => d.id.startsWith('ac') && d.status === 'running')
|
|
|
- const totalACFlow = runningACs.length * 250 // 每台提供250单位流量
|
|
|
+ const totalACFlow = runningACs.length * 250
|
|
|
|
|
|
- // === 第二步:空气处理橇联动 (依赖空压机) ===
|
|
|
const airDevice = deviceData.find((d) => d.id === 'air')!
|
|
|
const airInflow = totalACFlow
|
|
|
- // 处理量:如果自身开启,则等于进气量;如果自身关闭,处理量为0
|
|
|
const airOutflow = airDevice.status === 'running' ? airInflow : 0
|
|
|
- airDevice.params[0].value = airOutflow // 处理量
|
|
|
+ airDevice.params[0].value = airOutflow
|
|
|
|
|
|
- // === 第三步:制氮橇联动 (依赖空气处理) ===
|
|
|
const nitDevice = deviceData.find((d) => d.id === 'nit')!
|
|
|
const nitParam_Flow = nitDevice.params.find((p) => p.label === '产氮量')!
|
|
|
const nitParam_Purity = nitDevice.params.find((p) => p.label === '纯度')!
|
|
|
|
|
|
if (nitDevice.status === 'running' && airOutflow > 100) {
|
|
|
- // 正常产氮,纯度稳定
|
|
|
nitParam_Flow.value = Number((airOutflow * 0.85).toFixed(1))
|
|
|
nitParam_Purity.value = Number((95.5 + (Math.random() - 0.5) * 0.2).toFixed(1))
|
|
|
} else {
|
|
|
- // 没气或者关机,产氮量归零,纯度下降
|
|
|
nitParam_Flow.value = Number((nitParam_Flow.value * 0.7).toFixed(1))
|
|
|
nitParam_Purity.value = Math.max(0, Number((nitParam_Purity.value * 0.9).toFixed(1)))
|
|
|
}
|
|
|
|
|
|
- // === 第四步:注水泵系统 (独立分支) ===
|
|
|
const waterPumps = deviceData.filter((d) => d.id.startsWith('fillwater'))
|
|
|
let totalWaterFlow = 0
|
|
|
waterPumps.forEach((pump) => {
|
|
|
if (pump.status === 'running') {
|
|
|
- pump.params[0].value = Number((15.2 + (Math.random() - 0.5)).toFixed(1)) // 泵压
|
|
|
- pump.params[1].value = Number((45 + (Math.random() - 0.5)).toFixed(1)) // 流量
|
|
|
+ pump.params[0].value = Number((15.2 + (Math.random() - 0.5)).toFixed(1))
|
|
|
+ pump.params[1].value = Number((45 + (Math.random() - 0.5)).toFixed(1))
|
|
|
totalWaterFlow += pump.params[1].value
|
|
|
} else {
|
|
|
pump.params[0].value = Number((pump.params[0].value * 0.5).toFixed(1))
|
|
|
@@ -360,43 +354,34 @@ const startRealtimeSimulation = () => {
|
|
|
}
|
|
|
})
|
|
|
|
|
|
- // === 第五步:增压橇 (综合气+水压力) ===
|
|
|
const superDevice = deviceData.find((d) => d.id === 'supercharge')!
|
|
|
const gasIn = nitParam_Flow.value
|
|
|
const superInPress = superDevice.params.find((p) => p.label === '入口压')!
|
|
|
const superOutPress = superDevice.params.find((p) => p.label === '出口压')!
|
|
|
|
|
|
- // 入口压由气源决定
|
|
|
superInPress.value = Number(((gasIn / 1200) * 0.8).toFixed(2))
|
|
|
if (superDevice.status === 'running' && gasIn > 10) {
|
|
|
- // 增压至25MPa左右
|
|
|
superOutPress.value = Number((25.4 + (Math.random() - 0.5) * 0.5).toFixed(1))
|
|
|
} else {
|
|
|
superOutPress.value = Math.max(0, Number((superOutPress.value * 0.8).toFixed(1)))
|
|
|
}
|
|
|
|
|
|
- // === 第六步:大屏看板 & 井口数据联动 ===
|
|
|
- // 1. 场站总览
|
|
|
overviewData.totalFlow = Math.floor(gasIn)
|
|
|
overviewData.pressure = superOutPress.value
|
|
|
overviewData.efficiency = gasIn > 0 ? Number((90 + Math.random() * 5).toFixed(1)) : 0
|
|
|
overviewData.temperature = gasIn > 0 ? Number((35 + Math.random() * 5).toFixed(1)) : 25
|
|
|
|
|
|
- // 2. 井口数据
|
|
|
wellData.wellheadPressure = Number((overviewData.pressure * 0.98).toFixed(1))
|
|
|
if (wellData.wellheadPressure > 2) {
|
|
|
- // 只有压力够才算注气成功
|
|
|
wellData.dailyInjection += Math.floor(gasIn / 3600)
|
|
|
wellData.cumulative = Number((wellData.cumulative + 0.0001).toFixed(4))
|
|
|
}
|
|
|
|
|
|
- // 3. 气体成分随产量微调 (模拟)
|
|
|
if (gasIn > 10) {
|
|
|
gasComposition[0].value = Number((95 + (Math.random() - 0.5) * 0.5).toFixed(1))
|
|
|
gasComposition[1].value = Number((100 - gasComposition[0].value - 2).toFixed(1))
|
|
|
}
|
|
|
|
|
|
- // 处理排温等基础属性波动
|
|
|
updateDeviceBaseStats()
|
|
|
}, 1000)
|
|
|
}
|
|
|
@@ -688,260 +673,326 @@ const renderNodes = () => {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+// === 大屏全局等比缩放计算逻辑 ===
|
|
|
+const updateScale = () => {
|
|
|
+ if (!wrapperRef.value || !targetArea.value) return
|
|
|
+
|
|
|
+ const currentWidth = wrapperRef.value.clientWidth
|
|
|
+ const currentHeight = wrapperRef.value.clientHeight
|
|
|
+
|
|
|
+ // 固定你的设计稿标准尺寸
|
|
|
+ const designWidth = 2560
|
|
|
+ const designHeight = 1440
|
|
|
+
|
|
|
+ const scaleX = currentWidth / designWidth
|
|
|
+ const scaleY = currentHeight / designHeight
|
|
|
+ // 取最小比例,确保内容完整显示(类似 object-fit: contain)
|
|
|
+ const scale = Math.min(scaleX, scaleY)
|
|
|
+
|
|
|
+ targetArea.value.style.transform = `scale(${scale})`
|
|
|
+}
|
|
|
+
|
|
|
onMounted(() => {
|
|
|
initGraph()
|
|
|
renderGroundAndFence()
|
|
|
renderEdges()
|
|
|
renderNodes()
|
|
|
|
|
|
+ // 你完美调试出的硬编码参数被成功复用
|
|
|
if (graph.value) {
|
|
|
- graph.value.zoomTo(0.35)
|
|
|
- graph.value.translate(437, 530.65)
|
|
|
+ graph.value.zoomTo(0.4)
|
|
|
+ graph.value.translate(521.71, 704.89)
|
|
|
}
|
|
|
+
|
|
|
startRealtimeSimulation()
|
|
|
+
|
|
|
+ // 初始化缩放并监听窗口调整
|
|
|
+ updateScale()
|
|
|
+ window.addEventListener('resize', updateScale)
|
|
|
})
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
graph.value?.dispose()
|
|
|
if (timer) clearInterval(timer)
|
|
|
+ window.removeEventListener('resize', updateScale)
|
|
|
})
|
|
|
+
|
|
|
+const { toggle, isFullscreen } = useFullscreen(wrapperRef)
|
|
|
</script>
|
|
|
|
|
|
<template>
|
|
|
<div
|
|
|
- class="relative flex flex-col w-full overflow-hidden bg-[#0B1120] font-sans"
|
|
|
+ ref="wrapperRef"
|
|
|
+ id="screen-wrapper"
|
|
|
+ class="flex justify-center items-center w-full overflow-hidden bg-[#0B1120]"
|
|
|
style="
|
|
|
height: calc(
|
|
|
100vh - 20px - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height)
|
|
|
);
|
|
|
"
|
|
|
>
|
|
|
- <header
|
|
|
- class="relative z-20 flex items-center justify-center h-16 shrink-0 bg-[#0B1120] border-b border-[#00E5FF]/40 shadow-[0_4px_20px_rgba(0,229,255,0.15)]"
|
|
|
+ <div
|
|
|
+ ref="targetArea"
|
|
|
+ class="relative flex flex-col shrink-0 overflow-hidden bg-[#0B1120] font-sans"
|
|
|
+ style="width: 2560px; height: 1440px; transform-origin: center center"
|
|
|
>
|
|
|
- <div
|
|
|
- class="absolute left-10 right-10 bottom-0 h-[2px] bg-gradient-to-r from-transparent via-[#00E5FF] to-transparent opacity-80"
|
|
|
- ></div>
|
|
|
- <h1
|
|
|
- class="text-2xl font-bold tracking-[0.2em] text-[#00E5FF] drop-shadow-[0_0_10px_rgba(0,229,255,0.8)]"
|
|
|
+ <!-- style="width: 2300px; height: 1147px; transform-origin: center center" -->
|
|
|
+ <header
|
|
|
+ class="relative z-20 flex items-center justify-center h-16 shrink-0 bg-[#0B1120] border-b border-[#00E5FF]/40 shadow-[0_4px_20px_rgba(0,229,255,0.15)]"
|
|
|
>
|
|
|
- XX注气场站注气工艺组态图
|
|
|
- </h1>
|
|
|
- </header>
|
|
|
-
|
|
|
- <div class="relative flex-1 w-full h-full overflow-hidden">
|
|
|
- <main class="absolute inset-0 z-0" ref="containerRef"></main>
|
|
|
-
|
|
|
- <div class="absolute inset-0 z-10 pointer-events-none flex justify-between p-6">
|
|
|
- <div class="flex flex-col gap-6 w-[380px] pointer-events-auto h-full pb-2">
|
|
|
- <div
|
|
|
- class="bg-[#0A1628]/95 border border-[#00E5FF]/60 shadow-[0_0_15px_rgba(0,229,255,0.15)_inset] rounded-lg p-5 flex flex-col backdrop-blur-md transition-all shrink-0"
|
|
|
- >
|
|
|
- <h2
|
|
|
- class="text-lg font-bold text-[#00E5FF] border-b border-[#00E5FF]/30 pb-2 mb-4 drop-shadow-[0_0_5px_rgba(0,229,255,0.8)]"
|
|
|
- >场站实时总览</h2
|
|
|
+ <div
|
|
|
+ class="absolute left-10 right-10 bottom-0 h-[2px] bg-gradient-to-r from-transparent via-[#00E5FF] to-transparent opacity-80"
|
|
|
+ ></div>
|
|
|
+ <h1
|
|
|
+ class="text-2xl font-bold tracking-[0.2em] text-[#00E5FF] drop-shadow-[0_0_10px_rgba(0,229,255,0.8)]"
|
|
|
+ >
|
|
|
+ XX注气场站注气工艺组态图
|
|
|
+ </h1>
|
|
|
+ <el-button
|
|
|
+ size="default"
|
|
|
+ class="absolute right-6"
|
|
|
+ :type="isFullscreen ? 'info' : 'primary'"
|
|
|
+ :icon="isFullscreen ? Crop : FullScreen"
|
|
|
+ @click="toggle"
|
|
|
+ >
|
|
|
+ {{ isFullscreen ? '退出全屏' : '全屏' }}
|
|
|
+ </el-button>
|
|
|
+ </header>
|
|
|
+
|
|
|
+ <div class="relative flex-1 w-full h-full overflow-hidden">
|
|
|
+ <main class="absolute inset-0 z-0" ref="containerRef"></main>
|
|
|
+
|
|
|
+ <div class="absolute inset-0 z-10 pointer-events-none flex justify-between p-6">
|
|
|
+ <div class="flex flex-col gap-6 w-[380px] pointer-events-auto h-full pb-2">
|
|
|
+ <div
|
|
|
+ class="bg-[#0A1628]/95 border border-[#00E5FF]/60 shadow-[0_0_15px_rgba(0,229,255,0.15)_inset] rounded-lg p-5 flex flex-col backdrop-blur-md transition-all shrink-0"
|
|
|
>
|
|
|
- <div class="grid grid-cols-2 gap-4">
|
|
|
- <div class="bg-[#112240] border border-[#1E3A8A] rounded p-3 flex flex-col">
|
|
|
- <span class="text-xs text-slate-300 font-medium mb-1">注气总流量 (Nm³/h)</span>
|
|
|
- <span
|
|
|
- class="text-xl font-bold font-mono text-[#00FF7F] drop-shadow-[0_0_5px_rgba(0,255,127,0.5)] transition-all"
|
|
|
- >{{ overviewData.totalFlow.toLocaleString() }}</span
|
|
|
- >
|
|
|
- </div>
|
|
|
- <div class="bg-[#112240] border border-[#1E3A8A] rounded p-3 flex flex-col">
|
|
|
- <span class="text-xs text-slate-300 font-medium mb-1">管网压力 (MPa)</span>
|
|
|
- <span
|
|
|
- class="text-xl font-bold font-mono text-[#38BDF8] drop-shadow-[0_0_5px_rgba(56,189,248,0.5)] transition-all"
|
|
|
- >{{ overviewData.pressure }}</span
|
|
|
- >
|
|
|
- </div>
|
|
|
- <div class="bg-[#112240] border border-[#1E3A8A] rounded p-3 flex flex-col">
|
|
|
- <span class="text-xs text-slate-300 font-medium mb-1">排气温度 (°C)</span>
|
|
|
- <span
|
|
|
- class="text-xl font-bold font-mono text-[#FBBF24] drop-shadow-[0_0_5px_rgba(251,191,36,0.5)] transition-all"
|
|
|
- >{{ overviewData.temperature }}</span
|
|
|
- >
|
|
|
- </div>
|
|
|
- <div class="bg-[#112240] border border-[#1E3A8A] rounded p-3 flex flex-col">
|
|
|
- <span class="text-xs text-slate-300 font-medium mb-1">系统能效 (%)</span>
|
|
|
- <span
|
|
|
- class="text-xl font-bold font-mono text-[#00E5FF] drop-shadow-[0_0_5px_rgba(0,229,255,0.5)] transition-all"
|
|
|
- >{{ overviewData.efficiency }}</span
|
|
|
- >
|
|
|
+ <h2
|
|
|
+ class="text-lg font-bold text-[#00E5FF] border-b border-[#00E5FF]/30 pb-2 mb-4 drop-shadow-[0_0_5px_rgba(0,229,255,0.8)]"
|
|
|
+ >场站实时总览</h2
|
|
|
+ >
|
|
|
+ <div class="grid grid-cols-2 gap-4">
|
|
|
+ <div class="bg-[#112240] border border-[#1E3A8A] rounded p-3 flex flex-col">
|
|
|
+ <span class="text-xs text-slate-300 font-medium mb-1">注气总流量 (Nm³/h)</span>
|
|
|
+ <span
|
|
|
+ class="text-xl font-bold font-mono text-[#00FF7F] drop-shadow-[0_0_5px_rgba(0,255,127,0.5)] transition-all"
|
|
|
+ >{{ overviewData.totalFlow.toLocaleString() }}</span
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ <div class="bg-[#112240] border border-[#1E3A8A] rounded p-3 flex flex-col">
|
|
|
+ <span class="text-xs text-slate-300 font-medium mb-1">管网压力 (MPa)</span>
|
|
|
+ <span
|
|
|
+ class="text-xl font-bold font-mono text-[#38BDF8] drop-shadow-[0_0_5px_rgba(56,189,248,0.5)] transition-all"
|
|
|
+ >{{ overviewData.pressure }}</span
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ <div class="bg-[#112240] border border-[#1E3A8A] rounded p-3 flex flex-col">
|
|
|
+ <span class="text-xs text-slate-300 font-medium mb-1">排气温度 (°C)</span>
|
|
|
+ <span
|
|
|
+ class="text-xl font-bold font-mono text-[#FBBF24] drop-shadow-[0_0_5px_rgba(251,191,36,0.5)] transition-all"
|
|
|
+ >{{ overviewData.temperature }}</span
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ <div class="bg-[#112240] border border-[#1E3A8A] rounded p-3 flex flex-col">
|
|
|
+ <span class="text-xs text-slate-300 font-medium mb-1">系统能效 (%)</span>
|
|
|
+ <span
|
|
|
+ class="text-xl font-bold font-mono text-[#00E5FF] drop-shadow-[0_0_5px_rgba(0,229,255,0.5)] transition-all"
|
|
|
+ >{{ overviewData.efficiency }}</span
|
|
|
+ >
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
|
- <div
|
|
|
- class="bg-[#0A1628]/95 border border-[#00E5FF]/60 shadow-[0_0_15px_rgba(0,229,255,0.15)_inset] rounded-lg p-5 flex flex-col flex-1 min-h-0 backdrop-blur-md"
|
|
|
- >
|
|
|
- <h2
|
|
|
- class="text-lg font-bold text-[#00E5FF] border-b border-[#00E5FF]/30 pb-2 mb-4 drop-shadow-[0_0_5px_rgba(0,229,255,0.8)] shrink-0"
|
|
|
- >全场设备状态监控</h2
|
|
|
+ <div
|
|
|
+ class="bg-[#0A1628]/95 border border-[#00E5FF]/60 shadow-[0_0_15px_rgba(0,229,255,0.15)_inset] rounded-lg p-5 flex flex-col flex-1 min-h-0 backdrop-blur-md"
|
|
|
>
|
|
|
-
|
|
|
- <div class="flex-1 flex flex-col gap-3 overflow-y-auto pr-2 custom-scrollbar">
|
|
|
- <div
|
|
|
- v-for="dev in deviceData"
|
|
|
- :key="dev.id"
|
|
|
- class="flex items-center justify-between p-3 bg-[#112240] border border-[#1E3A8A] rounded hover:border-[#00E5FF]/60 transition-colors"
|
|
|
+ <h2
|
|
|
+ class="text-lg font-bold text-[#00E5FF] border-b border-[#00E5FF]/30 pb-2 mb-4 drop-shadow-[0_0_5px_rgba(0,229,255,0.8)] shrink-0"
|
|
|
+ >全场设备状态监控</h2
|
|
|
>
|
|
|
- <div class="flex items-center gap-3">
|
|
|
- <div
|
|
|
- :class="[
|
|
|
- 'w-3 h-3 rounded-full shadow-[0_0_8px] transition-colors shrink-0',
|
|
|
- dev.status === 'running'
|
|
|
- ? 'bg-[#00FF7F] shadow-[#00FF7F]'
|
|
|
- : 'bg-[#EF4444] shadow-[#EF4444]'
|
|
|
- ]"
|
|
|
- ></div>
|
|
|
- <span class="text-sm font-bold text-white w-[75px] truncate" :title="dev.name">{{
|
|
|
- dev.name
|
|
|
- }}</span>
|
|
|
- </div>
|
|
|
|
|
|
- <div class="flex gap-3 text-sm font-mono min-w-[120px] justify-end items-center">
|
|
|
- <div class="flex gap-3 min-w-[90px] justify-end">
|
|
|
+ <div class="flex-1 flex flex-col gap-3 overflow-y-auto pr-2 custom-scrollbar">
|
|
|
+ <div
|
|
|
+ v-for="dev in deviceData"
|
|
|
+ :key="dev.id"
|
|
|
+ class="flex items-center justify-between p-3 bg-[#112240] border border-[#1E3A8A] rounded hover:border-[#00E5FF]/60 transition-colors"
|
|
|
+ >
|
|
|
+ <div class="flex items-center gap-3">
|
|
|
<div
|
|
|
- v-for="(param, idx) in dev.params.slice(0, 2)"
|
|
|
- :key="idx"
|
|
|
- class="flex flex-col items-end"
|
|
|
+ :class="[
|
|
|
+ 'w-3 h-3 rounded-full shadow-[0_0_8px] transition-colors shrink-0',
|
|
|
+ dev.status === 'running'
|
|
|
+ ? 'bg-[#00FF7F] shadow-[#00FF7F]'
|
|
|
+ : 'bg-[#EF4444] shadow-[#EF4444]'
|
|
|
+ ]"
|
|
|
+ ></div>
|
|
|
+ <span
|
|
|
+ class="text-sm font-bold text-white w-[75px] truncate"
|
|
|
+ :title="dev.name"
|
|
|
+ >{{ dev.name }}</span
|
|
|
>
|
|
|
- <span class="text-slate-400 text-[10px]">{{ param.label }}</span>
|
|
|
- <span
|
|
|
- :class="
|
|
|
- dev.status === 'running'
|
|
|
- ? 'text-[#00E5FF] font-bold transition-all'
|
|
|
- : 'text-slate-500'
|
|
|
- "
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="flex gap-3 text-sm font-mono min-w-[120px] justify-end items-center">
|
|
|
+ <div class="flex gap-3 min-w-[90px] justify-end">
|
|
|
+ <div
|
|
|
+ v-for="(param, idx) in dev.params.slice(0, 2)"
|
|
|
+ :key="idx"
|
|
|
+ class="flex flex-col items-end"
|
|
|
>
|
|
|
- {{ param.value }}<span class="text-[10px] ml-0.5">{{ param.unit }}</span>
|
|
|
- </span>
|
|
|
+ <span class="text-slate-400 text-[10px]">{{ param.label }}</span>
|
|
|
+ <span
|
|
|
+ :class="
|
|
|
+ dev.status === 'running'
|
|
|
+ ? 'text-[#00E5FF] font-bold transition-all'
|
|
|
+ : 'text-slate-500'
|
|
|
+ "
|
|
|
+ >
|
|
|
+ {{ param.value }}<span class="text-[10px] ml-0.5">{{ param.unit }}</span>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- <button
|
|
|
- @click="toggleDevice(dev)"
|
|
|
- class="ml-2 p-1.5 rounded flex items-center justify-center transition-all duration-300 border focus:outline-none active:scale-90"
|
|
|
- :class="
|
|
|
- dev.status === 'running'
|
|
|
- ? 'border-[#EF4444]/30 text-[#EF4444] hover:bg-[#EF4444]/20 hover:border-[#EF4444] hover:shadow-[0_0_10px_rgba(239,68,68,0.5)]'
|
|
|
- : 'border-[#00FF7F]/30 text-[#00FF7F] hover:bg-[#00FF7F]/20 hover:border-[#00FF7F] hover:shadow-[0_0_10px_rgba(0,255,127,0.5)]'
|
|
|
- "
|
|
|
- :title="dev.status === 'running' ? '停止设备' : '启动设备'"
|
|
|
- >
|
|
|
- <div
|
|
|
- :class="dev.status === 'running' ? 'i-mdi-pause' : 'i-mdi-play'"
|
|
|
- class="w-4 h-4"
|
|
|
+ <!-- <button
|
|
|
+ @click="toggleDevice(dev)"
|
|
|
+ class="ml-2 p-1.5 rounded flex items-center justify-center transition-all duration-300 border focus:outline-none active:scale-90"
|
|
|
+ :class="
|
|
|
+ dev.status === 'running'
|
|
|
+ ? 'border-[#EF4444]/30 text-[#EF4444] hover:bg-[#EF4444]/20 hover:border-[#EF4444] hover:shadow-[0_0_10px_rgba(239,68,68,0.5)]'
|
|
|
+ : 'border-[#00FF7F]/30 text-[#00FF7F] hover:bg-[#00FF7F]/20 hover:border-[#00FF7F] hover:shadow-[0_0_10px_rgba(0,255,127,0.5)]'
|
|
|
+ "
|
|
|
+ :title="dev.status === 'running' ? '停止设备' : '启动设备'"
|
|
|
>
|
|
|
+ <div
|
|
|
+ :class="dev.status === 'running' ? 'i-mdi-pause' : 'i-mdi-play'"
|
|
|
+ class="w-4 h-4"
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </button> -->
|
|
|
+ <div class="switch-container">
|
|
|
+ <label class="switch">
|
|
|
+ <input
|
|
|
+ type="checkbox"
|
|
|
+ :checked="dev.status === 'running'"
|
|
|
+ @change="toggleDevice(dev)"
|
|
|
+ />
|
|
|
+ <div class="button">
|
|
|
+ <div class="light"></div>
|
|
|
+ <div class="dots"></div>
|
|
|
+ <div class="characters"></div>
|
|
|
+ <div class="shine"></div>
|
|
|
+ <div class="shadow"></div>
|
|
|
+ </div>
|
|
|
+ </label>
|
|
|
</div>
|
|
|
- </button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
|
- <div class="flex flex-col gap-6 w-[380px] pointer-events-auto h-full pb-2">
|
|
|
- <div
|
|
|
- class="bg-[#0A1628]/95 border border-[#00E5FF]/60 shadow-[0_0_15px_rgba(0,229,255,0.15)_inset] rounded-lg p-5 flex flex-col backdrop-blur-md shrink-0"
|
|
|
- >
|
|
|
- <h2
|
|
|
- class="text-lg font-bold text-[#00E5FF] border-b border-[#00E5FF]/30 pb-2 mb-4 drop-shadow-[0_0_5px_rgba(0,229,255,0.8)]"
|
|
|
- >注气井实时数据</h2
|
|
|
+ <div class="flex flex-col gap-6 w-[380px] pointer-events-auto h-full pb-2">
|
|
|
+ <div
|
|
|
+ class="bg-[#0A1628]/95 border border-[#00E5FF]/60 shadow-[0_0_15px_rgba(0,229,255,0.15)_inset] rounded-lg p-5 flex flex-col backdrop-blur-md shrink-0"
|
|
|
>
|
|
|
- <div class="flex flex-col gap-4">
|
|
|
- <div
|
|
|
- class="flex justify-between items-end bg-[#112240] p-3 rounded border border-[#1E3A8A]"
|
|
|
+ <h2
|
|
|
+ class="text-lg font-bold text-[#00E5FF] border-b border-[#00E5FF]/30 pb-2 mb-4 drop-shadow-[0_0_5px_rgba(0,229,255,0.8)]"
|
|
|
+ >注气井实时数据</h2
|
|
|
>
|
|
|
- <span class="text-white text-sm font-medium">井口压力</span>
|
|
|
- <span class="text-2xl font-mono text-[#00E5FF] font-bold transition-all"
|
|
|
- >{{ wellData.wellheadPressure
|
|
|
- }}<span class="text-sm text-slate-400">MPa</span></span
|
|
|
+ <div class="flex flex-col gap-4">
|
|
|
+ <div
|
|
|
+ class="flex justify-between items-end bg-[#112240] p-3 rounded border border-[#1E3A8A]"
|
|
|
>
|
|
|
- </div>
|
|
|
- <div
|
|
|
- class="flex justify-between items-end bg-[#112240] p-3 rounded border border-[#1E3A8A]"
|
|
|
- >
|
|
|
- <span class="text-white text-sm font-medium">日注气量</span>
|
|
|
- <span class="text-2xl font-mono text-[#00FF7F] font-bold transition-all"
|
|
|
- >{{ wellData.dailyInjection.toLocaleString()
|
|
|
- }}<span class="text-sm text-slate-400">Nm³</span></span
|
|
|
+ <span class="text-white text-sm font-medium">井口压力</span>
|
|
|
+ <span class="text-2xl font-mono text-[#00E5FF] font-bold transition-all"
|
|
|
+ >{{ wellData.wellheadPressure
|
|
|
+ }}<span class="text-sm text-slate-400">MPa</span></span
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="flex justify-between items-end bg-[#112240] p-3 rounded border border-[#1E3A8A]"
|
|
|
>
|
|
|
- </div>
|
|
|
- <div
|
|
|
- class="flex justify-between items-end bg-[#112240] p-3 rounded border border-[#1E3A8A]"
|
|
|
- >
|
|
|
- <span class="text-white text-sm font-medium">累计注气</span>
|
|
|
- <span class="text-2xl font-mono text-[#FBBF24] font-bold transition-all"
|
|
|
- >{{ wellData.cumulative }} <span class="text-sm text-slate-400">万Nm³</span></span
|
|
|
+ <span class="text-white text-sm font-medium">日注气量</span>
|
|
|
+ <span class="text-2xl font-mono text-[#00FF7F] font-bold transition-all"
|
|
|
+ >{{ wellData.dailyInjection.toLocaleString()
|
|
|
+ }}<span class="text-sm text-slate-400">Nm³</span></span
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="flex justify-between items-end bg-[#112240] p-3 rounded border border-[#1E3A8A]"
|
|
|
>
|
|
|
+ <span class="text-white text-sm font-medium">累计注气</span>
|
|
|
+ <span class="text-2xl font-mono text-[#FBBF24] font-bold transition-all"
|
|
|
+ >{{ wellData.cumulative }}
|
|
|
+ <span class="text-sm text-slate-400">万Nm³</span></span
|
|
|
+ >
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
|
- <div
|
|
|
- class="bg-[#0A1628]/95 border border-[#00E5FF]/60 shadow-[0_0_15px_rgba(0,229,255,0.15)_inset] rounded-lg p-5 flex flex-col backdrop-blur-md shrink-0"
|
|
|
- >
|
|
|
- <h2
|
|
|
- class="text-lg font-bold text-[#00E5FF] border-b border-[#00E5FF]/30 pb-2 mb-4 drop-shadow-[0_0_5px_rgba(0,229,255,0.8)]"
|
|
|
- >气体成分占比</h2
|
|
|
+ <div
|
|
|
+ class="bg-[#0A1628]/95 border border-[#00E5FF]/60 shadow-[0_0_15px_rgba(0,229,255,0.15)_inset] rounded-lg p-5 flex flex-col backdrop-blur-md shrink-0"
|
|
|
>
|
|
|
- <div class="flex flex-col gap-4">
|
|
|
- <div v-for="gas in gasComposition" :key="gas.name" class="flex flex-col gap-2">
|
|
|
- <div class="flex justify-between text-sm">
|
|
|
- <span class="text-white font-medium">{{ gas.name }}</span>
|
|
|
- <span class="text-[#00E5FF] font-mono font-bold transition-all"
|
|
|
- >{{ gas.value }}%</span
|
|
|
- >
|
|
|
- </div>
|
|
|
- <div class="w-full h-2 bg-[#1E3A8A] rounded-full overflow-hidden">
|
|
|
- <div
|
|
|
- :class="[
|
|
|
- 'h-full rounded-full transition-all duration-500 ease-in-out',
|
|
|
- gas.color
|
|
|
- ]"
|
|
|
- :style="{ width: `${gas.value}%` }"
|
|
|
- ></div>
|
|
|
+ <h2
|
|
|
+ class="text-lg font-bold text-[#00E5FF] border-b border-[#00E5FF]/30 pb-2 mb-4 drop-shadow-[0_0_5px_rgba(0,229,255,0.8)]"
|
|
|
+ >气体成分占比</h2
|
|
|
+ >
|
|
|
+ <div class="flex flex-col gap-4">
|
|
|
+ <div v-for="gas in gasComposition" :key="gas.name" class="flex flex-col gap-2">
|
|
|
+ <div class="flex justify-between text-sm">
|
|
|
+ <span class="text-white font-medium">{{ gas.name }}</span>
|
|
|
+ <span class="text-[#00E5FF] font-mono font-bold transition-all"
|
|
|
+ >{{ gas.value }}%</span
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ <div class="w-full h-2 bg-[#1E3A8A] rounded-full overflow-hidden">
|
|
|
+ <div
|
|
|
+ :class="[
|
|
|
+ 'h-full rounded-full transition-all duration-500 ease-in-out',
|
|
|
+ gas.color
|
|
|
+ ]"
|
|
|
+ :style="{ width: `${gas.value}%` }"
|
|
|
+ ></div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
|
- <div
|
|
|
- class="bg-[#0A1628]/95 border border-[#00E5FF]/60 shadow-[0_0_15px_rgba(0,229,255,0.15)_inset] rounded-lg p-5 flex flex-col flex-1 min-h-0 backdrop-blur-md"
|
|
|
- >
|
|
|
- <h2
|
|
|
- class="text-lg font-bold text-[#00E5FF] border-b border-[#00E5FF]/30 pb-2 mb-4 drop-shadow-[0_0_5px_rgba(0,229,255,0.8)] shrink-0"
|
|
|
- >系统操作/告警日志</h2
|
|
|
+ <div
|
|
|
+ class="bg-[#0A1628]/95 border border-[#00E5FF]/60 shadow-[0_0_15px_rgba(0,229,255,0.15)_inset] rounded-lg p-5 flex flex-col flex-1 min-h-0 backdrop-blur-md"
|
|
|
>
|
|
|
- <div class="flex-1 flex flex-col gap-3 overflow-y-auto pr-2 custom-scrollbar">
|
|
|
- <div
|
|
|
- v-for="(log, idx) in alarmLogs"
|
|
|
- :key="idx"
|
|
|
- class="flex flex-col p-3 bg-[#112240] rounded border-l-4"
|
|
|
- :class="log.level === 'warn' ? 'border-[#FBBF24]' : 'border-[#00E5FF]'"
|
|
|
+ <h2
|
|
|
+ class="text-lg font-bold text-[#00E5FF] border-b border-[#00E5FF]/30 pb-2 mb-4 drop-shadow-[0_0_5px_rgba(0,229,255,0.8)] shrink-0"
|
|
|
+ >系统操作/告警日志</h2
|
|
|
>
|
|
|
- <div class="flex justify-between items-center mb-2">
|
|
|
- <span class="text-xs text-slate-400 font-mono">{{ log.time }}</span>
|
|
|
- <span
|
|
|
- :class="[
|
|
|
- 'text-xs px-2 py-0.5 rounded font-bold',
|
|
|
- log.level === 'warn'
|
|
|
- ? 'bg-[#FBBF24]/20 text-[#FBBF24]'
|
|
|
- : 'bg-[#00E5FF]/20 text-[#00E5FF]'
|
|
|
- ]"
|
|
|
- >
|
|
|
- {{ log.level === 'warn' ? '警告' : '信息' }}
|
|
|
- </span>
|
|
|
+ <div class="flex-1 flex flex-col gap-3 overflow-y-auto pr-2 custom-scrollbar">
|
|
|
+ <div
|
|
|
+ v-for="(log, idx) in alarmLogs"
|
|
|
+ :key="idx"
|
|
|
+ class="flex flex-col p-3 bg-[#112240] rounded border-l-4"
|
|
|
+ :class="log.level === 'warn' ? 'border-[#FBBF24]' : 'border-[#00E5FF]'"
|
|
|
+ >
|
|
|
+ <div class="flex justify-between items-center mb-2">
|
|
|
+ <span class="text-xs text-slate-400 font-mono">{{ log.time }}</span>
|
|
|
+ <span
|
|
|
+ :class="[
|
|
|
+ 'text-xs px-2 py-0.5 rounded font-bold',
|
|
|
+ log.level === 'warn'
|
|
|
+ ? 'bg-[#FBBF24]/20 text-[#FBBF24]'
|
|
|
+ : 'bg-[#00E5FF]/20 text-[#00E5FF]'
|
|
|
+ ]"
|
|
|
+ >
|
|
|
+ {{ log.level === 'warn' ? '警告' : '信息' }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <span class="text-sm text-white">{{ log.msg }}</span>
|
|
|
</div>
|
|
|
- <span class="text-sm text-white">{{ log.msg }}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
+ <air ref="airDialogRef" />
|
|
|
</div>
|
|
|
|
|
|
<template v-if="hoveredNodeId">
|
|
|
@@ -976,8 +1027,6 @@ onUnmounted(() => {
|
|
|
</div>
|
|
|
</template>
|
|
|
</template>
|
|
|
-
|
|
|
- <air ref="airDialogRef" />
|
|
|
</template>
|
|
|
|
|
|
<style scoped>
|
|
|
@@ -1034,6 +1083,174 @@ onUnmounted(() => {
|
|
|
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
|
|
background: rgb(0 229 255 / 100%);
|
|
|
}
|
|
|
+
|
|
|
+.switch-container {
|
|
|
+ display: inline-block;
|
|
|
+ width: 37.5px; /* 约 150 * 0.12 */
|
|
|
+ height: 48.75px; /* 约 195 * 0.12 */
|
|
|
+ overflow: visible;
|
|
|
+}
|
|
|
+
|
|
|
+.switch {
|
|
|
+ display: block;
|
|
|
+ width: 150px;
|
|
|
+ height: 195px;
|
|
|
+ padding: 20px;
|
|
|
+ background-color: black;
|
|
|
+ border-radius: 5px;
|
|
|
+ transform: scale(0.25);
|
|
|
+ box-shadow:
|
|
|
+ 0 0 10px 2px rgb(0 0 0 / 20%),
|
|
|
+ 0 0 1px 2px black,
|
|
|
+ inset 0 2px 2px -2px white,
|
|
|
+ inset 0 0 2px 15px #47434c,
|
|
|
+ inset 0 0 2px 22px black;
|
|
|
+ perspective: 700px;
|
|
|
+ transform-origin: 0 0; /* 锁定左上角缩放 */
|
|
|
+}
|
|
|
+
|
|
|
+.switch input {
|
|
|
+ display: none;
|
|
|
+}
|
|
|
+
|
|
|
+.switch input:checked + .button {
|
|
|
+ transform: translateZ(20px) rotateX(25deg);
|
|
|
+ box-shadow: 0 -10px 20px #ff1818;
|
|
|
+}
|
|
|
+
|
|
|
+.switch input:checked + .button .light {
|
|
|
+ animation: flicker 0.2s infinite 0.3s;
|
|
|
+}
|
|
|
+
|
|
|
+.switch input:checked + .button .shine {
|
|
|
+ opacity: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.switch input:checked + .button .shadow {
|
|
|
+ opacity: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.switch .button {
|
|
|
+ position: relative;
|
|
|
+ display: block;
|
|
|
+ height: 100%;
|
|
|
+ cursor: pointer;
|
|
|
+ background: linear-gradient(#980000 0%, #6f0000 30%, #6f0000 70%, #980000 100%);
|
|
|
+ background-color: #9b0621;
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ transform: translateZ(20px) rotateX(-25deg);
|
|
|
+ transition: all 0.3s cubic-bezier(1, 0, 1, 1);
|
|
|
+ transform-origin: center center -20px;
|
|
|
+ transform-style: preserve-3d;
|
|
|
+}
|
|
|
+
|
|
|
+.switch .button::before {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 50px;
|
|
|
+ background:
|
|
|
+ linear-gradient(rgb(255 255 255 / 80%) 10%, rgb(255 255 255 / 30%) 30%, #650000 75%, #320000)
|
|
|
+ 50% 50%/97% 97%,
|
|
|
+ #b10000;
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ content: '';
|
|
|
+ transform: rotateX(-90deg);
|
|
|
+ transform-origin: top;
|
|
|
+}
|
|
|
+
|
|
|
+.switch .button::after {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 50px;
|
|
|
+ background-image: linear-gradient(#650000, #320000);
|
|
|
+ content: '';
|
|
|
+ transform: translateY(50px) rotateX(-90deg);
|
|
|
+ box-shadow:
|
|
|
+ 0 50px 8px 0 black,
|
|
|
+ 0 80px 20px 0 rgb(0 0 0 / 50%);
|
|
|
+ transform-origin: top;
|
|
|
+}
|
|
|
+
|
|
|
+.switch .light {
|
|
|
+ position: absolute;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background-image: radial-gradient(#ffc97e, #ff1818 40%, transparent 70%);
|
|
|
+ opacity: 0;
|
|
|
+ animation: light-off 1s;
|
|
|
+}
|
|
|
+
|
|
|
+.switch .dots {
|
|
|
+ position: absolute;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background-image: radial-gradient(transparent 30%, rgb(101 0 0 / 70%) 70%);
|
|
|
+ background-size: 10px 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.switch .characters {
|
|
|
+ position: absolute;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background:
|
|
|
+ linear-gradient(white, white) 50% 20%/5% 20%,
|
|
|
+ radial-gradient(circle, transparent 50%, white 52%, white 70%, transparent 72%) 50% 80%/33% 25%;
|
|
|
+ background-repeat: no-repeat;
|
|
|
+}
|
|
|
+
|
|
|
+.switch .shine {
|
|
|
+ position: absolute;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background:
|
|
|
+ linear-gradient(white, transparent 3%) 50% 50%/97% 97%,
|
|
|
+ linear-gradient(
|
|
|
+ rgb(255 255 255 / 50%),
|
|
|
+ transparent 50%,
|
|
|
+ transparent 80%,
|
|
|
+ rgb(255 255 255 / 50%)
|
|
|
+ )
|
|
|
+ 50% 50%/97% 97%;
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ opacity: 0.3;
|
|
|
+ transition: all 0.3s cubic-bezier(1, 0, 1, 1);
|
|
|
+}
|
|
|
+
|
|
|
+.switch .shadow {
|
|
|
+ position: absolute;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background: linear-gradient(transparent 70%, rgb(0 0 0 / 80%));
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ opacity: 1;
|
|
|
+ transition: all 0.3s cubic-bezier(1, 0, 1, 1);
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes flicker {
|
|
|
+ 0% {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ 80% {
|
|
|
+ opacity: 0.8;
|
|
|
+ }
|
|
|
+
|
|
|
+ 100% {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes light-off {
|
|
|
+ 0% {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ 80% {
|
|
|
+ opacity: 0;
|
|
|
+ }
|
|
|
+}
|
|
|
</style>
|
|
|
|
|
|
<style>
|