From 3e4bb654929496c141e43747fa5892a650819376 Mon Sep 17 00:00:00 2001
From: spring <2396852758@qq.com>
Date: 星期三, 13 八月 2025 16:20:02 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_7004' into dev_7004

---
 src/views/equipmentManagement/iotMonitor/index.vue        |  317 +++++++++++++++
 src/views/productionManagement/safetyMonitoring/index.vue |  873 +++++++++++++++++++++++++++++++++++++++++
 src/router/index.js                                       |   13 
 3 files changed, 1,203 insertions(+), 0 deletions(-)

diff --git a/src/router/index.js b/src/router/index.js
index 10adf72..4b4f154 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -72,6 +72,19 @@
     ]
   },
   {
+    path: '/equipment',
+    component: Layout,
+    redirect: '/equipment/iot-monitor',
+    children: [
+      {
+        path: 'iot-monitor',
+        component: () => import('@/views/equipmentManagement/iotMonitor/index.vue'),
+        name: 'IoTMonitor',
+        meta: { title: 'IoT鐩戞帶', icon: 'monitor', noCache: true }
+      }
+    ]
+  },
+  {
     path: '/main/MobileChat',
     component: Layout,
     redirect: '',
diff --git a/src/views/equipmentManagement/iotMonitor/index.vue b/src/views/equipmentManagement/iotMonitor/index.vue
new file mode 100644
index 0000000..b0cd818
--- /dev/null
+++ b/src/views/equipmentManagement/iotMonitor/index.vue
@@ -0,0 +1,317 @@
+<template>
+  <div class="app-container iot-monitor">
+    <div class="header">
+      <div class="title">瀹炴椂宸ュ喌鐩戞帶锛圛oT锛�</div>
+      <div class="actions">
+        <el-button type="primary" @click="toggleCollecting">{{ collecting ? '鏆傚仠閲囬泦' : '鍚姩閲囬泦' }}</el-button>
+        <el-button @click="resetAll">閲嶇疆</el-button>
+        <span class="ts">涓婃鏇存柊鏃堕棿锛歿{ lastUpdatedDisplay }}</span>
+      </div>
+    </div>
+
+<!--    <el-alert-->
+<!--      title="杈圭紭棰勮瑙勫垯锛氳酱鎵跨(鎹�-鎸姩鍊煎亸绂诲熀绾柯�5%瑙﹀彂鍛婅锛涙俯搴�/鍘嬪姏瓒婄晫瑙﹀彂鎻愰啋"-->
+<!--      type="info"-->
+<!--      :closable="false"-->
+<!--      show-icon-->
+<!--      class="rule-alert"-->
+<!--    />-->
+
+    <el-row :gutter="16">
+      <el-col v-for="dev in devices" :key="dev.id" :span="12">
+        <el-card :class="['device-card', dev.hasAlert ? 'is-alert' : '']">
+          <template #header>
+            <div class="card-header">
+              <div class="card-title">
+                <span class="device-name">{{ dev.name }}</span>
+                <el-tag :type="dev.hasAlert ? 'danger' : 'success'" size="small">{{ dev.hasAlert ? '鍛婅' : '姝e父' }}</el-tag>
+              </div>
+              <div class="meta">绫诲瀷锛歿{ dev.type }}锝滃熀绾挎尟鍔細{{ dev.baseline.vibration.toFixed(2) }} mm/s</div>
+            </div>
+          </template>
+
+          <div class="metrics">
+            <div class="metric" :class="{ 'metric-alert': dev.alerts.vibration }">
+              <div class="metric-head">
+                <span>鎸姩(mm/s)</span>
+                <el-tag :type="dev.alerts.vibration ? 'danger' : 'info'" size="small">{{ dev.alerts.vibration ? '卤5%瓒婄晫' : '鍩虹嚎卤5%' }}</el-tag>
+              </div>
+              <div class="metric-value">{{ currentValue(dev.series.vibration).toFixed(2) }}</div>
+              <Echarts
+                :xAxis="[{ type: 'category', data: xAxisLabels }]"
+                :yAxis="[{ type: 'value', name: 'mm/s' }]"
+                :series="[{ type: 'line', smooth: true, showSymbol: false, data: dev.series.vibration }]"
+                :tooltip="{ trigger: 'axis' }"
+                :grid="{ left: 40, right: 10, top: 10, bottom: 20 }"
+                :chartStyle="{ height: '160px', width: '100%' }"
+                :lineColors="['#409EFF']"
+              />
+            </div>
+
+            <div class="metric" :class="{ 'metric-alert': dev.alerts.temperature }">
+              <div class="metric-head">
+                <span>娓╁害(掳C)</span>
+                <el-tag :type="dev.alerts.temperature ? 'warning' : 'info'" size="small">{{ dev.alerts.temperature ? '瓒婄晫' : '20~80' }}</el-tag>
+              </div>
+              <div class="metric-value">{{ currentValue(dev.series.temperature).toFixed(1) }}</div>
+              <Echarts
+                :xAxis="[{ type: 'category', data: xAxisLabels }]"
+                :yAxis="[{ type: 'value', name: '掳C' }]"
+                :series="[{ type: 'line', smooth: true, showSymbol: false, data: dev.series.temperature }]"
+                :tooltip="{ trigger: 'axis' }"
+                :grid="{ left: 40, right: 10, top: 10, bottom: 20 }"
+                :chartStyle="{ height: '160px', width: '100%' }"
+                :lineColors="['#E6A23C']"
+              />
+            </div>
+
+            <div class="metric" :class="{ 'metric-alert': dev.alerts.pressure }">
+              <div class="metric-head">
+                <span>鍘嬪姏(MPa)</span>
+                <el-tag :type="dev.alerts.pressure ? 'warning' : 'info'" size="small">{{ dev.alerts.pressure ? '瓒婄晫' : '0.2~1.5' }}</el-tag>
+              </div>
+              <div class="metric-value">{{ currentValue(dev.series.pressure).toFixed(2) }}</div>
+              <Echarts
+                :xAxis="[{ type: 'category', data: xAxisLabels }]"
+                :yAxis="[{ type: 'value', name: 'MPa' }]"
+                :series="[{ type: 'line', smooth: true, showSymbol: false, data: dev.series.pressure }]"
+                :tooltip="{ trigger: 'axis' }"
+                :grid="{ left: 40, right: 10, top: 10, bottom: 20 }"
+                :chartStyle="{ height: '160px', width: '100%' }"
+                :lineColors="['#67C23A']"
+              />
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+  </div>
+  
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, onBeforeUnmount, computed } from 'vue'
+import { ElNotification } from 'element-plus'
+import Echarts from '@/components/Echarts/echarts.vue'
+
+defineOptions({ name: 'IoTMonitor' })
+
+const windowSize = 30
+const collecting = ref(true)
+const lastUpdated = ref(Date.now())
+const lastUpdatedDisplay = computed(() => new Date(lastUpdated.value).toLocaleTimeString())
+
+const xAxisLabels = ref(Array.from({ length: windowSize }, (_, i) => i - (windowSize - 1)).map(n => `${n}s`))
+
+function makeSeries(fill, decimals = 2) {
+  return Array.from({ length: windowSize }, () => Number(fill.toFixed(decimals)))
+}
+
+const devices = reactive([
+  {
+    id: 'water-pump',
+    name: '娉ㄦ按娉�1',
+    type: '绉诲姩瑁呭',
+    baseline: { vibration: 9 },
+    initial: { temperature: 40, pressure: 0.70 },
+    alerts: { vibration: false, temperature: false, pressure: false },
+    hasAlert: false,
+    series: {
+      vibration: makeSeries(9),
+      temperature: makeSeries(40, 1),
+      pressure: makeSeries(0.7, 2),
+    },
+  },
+  {
+    id: 'fluid-supply-truck',
+    name: '娉ㄦ按娉�2',
+    type: '绉诲姩瑁呭',
+    baseline: { vibration: 7 },
+    initial: { temperature: 30, pressure: 0.60 },
+    alerts: { vibration: false, temperature: false, pressure: false },
+    hasAlert: false,
+    series: {
+      vibration: makeSeries(7),
+      temperature: makeSeries(30, 1),
+      pressure: makeSeries(0.6, 2),
+    },
+  },
+  {
+    id: 'fracturing-truck',
+    name: '娉ㄦ按娉�3',
+    type: '绉诲姩瑁呭',
+    baseline: { vibration: 12 },
+    initial: { temperature: 65, pressure: 1.40 },
+    alerts: { vibration: false, temperature: false, pressure: false },
+    hasAlert: false,
+    series: {
+      vibration: makeSeries(12),
+      temperature: makeSeries(65, 1),
+      pressure: makeSeries(1.4, 2),
+    },
+  },
+  {
+    id: 'oil-tank-truck',
+    name: '娉ㄦ按娉�4',
+    type: '绉诲姩瑁呭',
+    baseline: { vibration: 6 },
+    initial: { temperature: 28, pressure: 0.50 },
+    alerts: { vibration: false, temperature: false, pressure: false },
+    hasAlert: false,
+    series: {
+      vibration: makeSeries(6),
+      temperature: makeSeries(28, 1),
+      pressure: makeSeries(0.5, 2),
+    },
+  },
+])
+
+function currentValue(arr) {
+  return arr[arr.length - 1] ?? 0
+}
+
+function pushWindow(arr, val) {
+  if (arr.length >= windowSize) arr.shift()
+  arr.push(val)
+}
+
+function clamp(val, min, max) { return Math.max(min, Math.min(max, val)) }
+
+function tickDevice(dev) {
+  const vibBase = dev.baseline.vibration
+  // 鎸姩锛氬熀绾柯�2%闅忔満娉㈠姩锛�5%姒傜巼瑙﹀彂8%~12%灏栧嘲妯℃嫙鍛婅
+  const spike = Math.random() < 0.05
+  const vibNoise = vibBase * (spike ? (1 + (Math.random() * 0.08 + 0.04) * (Math.random() < 0.5 ? -1 : 1)) : (1 + (Math.random() - 0.5) * 0.04))
+  const vibVal = Number(vibNoise.toFixed(2))
+  pushWindow(dev.series.vibration, vibVal)
+
+  // 娓╁害锛氱紦鎱㈤殢鏈烘父璧帮紝骞舵坊鍔犲伓鍙戦珮娓╁亸绉�
+  const tPrev = currentValue(dev.series.temperature)
+  const tDrift = tPrev + (Math.random() - 0.5) * 0.8 + (Math.random() < 0.02 ? 6 : 0)
+  const tVal = Number(clamp(tDrift, 15, 95).toFixed(1))
+  pushWindow(dev.series.temperature, tVal)
+
+  // 鍘嬪姏锛氬皬骞呮尝鍔紝鍋跺彂浣庡帇/楂樺帇
+  const pPrev = currentValue(dev.series.pressure)
+  const pDrift = pPrev + (Math.random() - 0.5) * 0.05 + (Math.random() < 0.02 ? (Math.random() < 0.5 ? -0.3 : 0.3) : 0)
+  const pVal = Number(clamp(pDrift, 0.05, 2.0).toFixed(2))
+  pushWindow(dev.series.pressure, pVal)
+
+  // 杈圭紭璁$畻闃堝�煎垽鏂�
+  const vibDelta = Math.abs(vibVal - vibBase) / vibBase
+  const vibAlert = vibDelta > 0.05
+  const tAlert = tVal < 20 || tVal > 80
+  const pAlert = pVal < 0.2 || pVal > 1.5
+
+  const prevHasAlert = dev.hasAlert
+  dev.alerts.vibration = vibAlert
+  dev.alerts.temperature = tAlert
+  dev.alerts.pressure = pAlert
+  dev.hasAlert = vibAlert || tAlert || pAlert
+
+  if (dev.hasAlert && !prevHasAlert) {
+    const reasons = []
+    if (vibAlert) reasons.push(`鎸姩鍋忕卤5% (褰撳墠 ${vibVal} / 鍩虹嚎 ${vibBase})`)
+    if (tAlert) reasons.push(`娓╁害瓒婄晫 (褰撳墠 ${tVal}掳C, 鏈熸湜 20~80掳C) `)
+    if (pAlert) reasons.push(`鍘嬪姏瓒婄晫 (褰撳墠 ${pVal}MPa, 鏈熸湜 0.2~1.5MPa) `)
+    ElNotification({
+      title: `${dev.name} 鍛婅`,
+      message: reasons.join('锛�'),
+      type: vibAlert ? 'error' : 'warning',
+      duration: 5000,
+    })
+  }
+}
+
+let timer = null
+function start() {
+  if (timer) return
+  timer = setInterval(() => {
+    if (!collecting.value) return
+    devices.forEach(tickDevice)
+    lastUpdated.value = Date.now()
+  }, 10000)
+}
+
+function stop() {
+  if (timer) {
+    clearInterval(timer)
+    timer = null
+  }
+}
+
+function toggleCollecting() { collecting.value = !collecting.value }
+
+function resetAll() {
+  devices.forEach(dev => {
+    dev.series.vibration = makeSeries(dev.baseline.vibration)
+    const t0 = dev.initial?.temperature ?? 45
+    const p0 = dev.initial?.pressure ?? 0.8
+    dev.series.temperature = makeSeries(t0, 1)
+    dev.series.pressure = makeSeries(p0, 2)
+    dev.alerts.vibration = false
+    dev.alerts.temperature = false
+    dev.alerts.pressure = false
+    dev.hasAlert = false
+  })
+  lastUpdated.value = Date.now()
+}
+
+onMounted(() => {
+  start()
+})
+
+onBeforeUnmount(() => {
+  stop()
+})
+</script>
+
+<style lang="scss" scoped>
+.iot-monitor {
+  .header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-bottom: 12px;
+    .title { font-size: 18px; font-weight: 600; }
+    .actions { display: flex; align-items: center; gap: 8px; }
+    .ts { color: #909399; font-size: 12px; }
+  }
+  .rule-alert { margin-bottom: 12px; }
+}
+
+.device-card {
+  margin-bottom: 16px;
+  transition: border-color 0.2s ease, box-shadow 0.2s ease;
+  &.is-alert { border-color: #F56C6C; box-shadow: 0 0 0 2px rgba(245,108,108,0.2) inset; }
+  .card-header {
+    display: flex; flex-direction: column; gap: 4px;
+    .card-title { display: flex; align-items: center; gap: 8px; font-weight: 600; }
+    .meta { color: #909399; font-size: 12px; }
+  }
+  .metrics {
+    display: grid;
+    grid-template-columns: 1fr;
+    gap: 12px;
+  }
+}
+
+.metric {
+  border: 1px solid #ebeef5;
+  border-radius: 6px;
+  padding: 8px 8px 0 8px;
+  &-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px; font-size: 13px; color: #606266; }
+  &-value { font-size: 20px; font-weight: 600; margin: 2px 0 6px 0; }
+}
+
+.metric-alert {
+  border-color: #F56C6C;
+  background: #FFF6F6;
+}
+
+@media (min-width: 1200px) {
+  .device-card .metrics { grid-template-columns: 1fr 1fr 1fr; }
+}
+</style>
+
+
diff --git a/src/views/productionManagement/safetyMonitoring/index.vue b/src/views/productionManagement/safetyMonitoring/index.vue
new file mode 100644
index 0000000..8416914
--- /dev/null
+++ b/src/views/productionManagement/safetyMonitoring/index.vue
@@ -0,0 +1,873 @@
+<template>
+	<div class="safety-monitoring">
+		<el-row :gutter="20">
+			<!-- 宸︿晶锛氬疄鏃剁洃鎺у尯鍩� -->
+			<el-col :span="16">
+				<el-card class="monitoring-card">
+					<div slot="header" class="card-header">
+						<span>瀹炴椂姘斾綋娴撳害鐩戞帶</span>
+						<el-tag :type="systemStatus === 'normal' ? 'success' : 'danger'">
+							{{ systemStatus === 'normal' ? '绯荤粺姝e父' : '绯荤粺鍛婅' }}
+						</el-tag>
+					</div>
+					
+					<!-- 鍌ㄧ綈鍖虹洃鎺� -->
+					<div class="monitoring-section">
+						<h3>鍌ㄧ綈鍖虹洃鎺�</h3>
+						<div class="sensor-grid">
+							<div class="sensor-item" v-for="sensor in tankSensors" :key="sensor.id">
+								<div class="sensor-header">
+									<span>{{ sensor.name }}</span>
+									<el-tag :type="sensor.status === 'normal' ? 'success' : 'danger'" size="small">
+										{{ sensor.status === 'normal' ? '姝e父' : '瓒呮爣' }}
+									</el-tag>
+								</div>
+								<div class="sensor-data">
+									<div class="data-item">
+										<span>鐢茬兎: {{ sensor.methane.toFixed(2) }}%</span>
+										<el-progress
+											:percentage="Math.min(Math.round(sensor.methane * 40 * 100) / 100, 100)"
+											:color="getProgressColor(Math.min(Math.round(sensor.methane * 40 * 100) / 100, 100), 80)"
+											:format="formatProgress"
+											:stroke-width="8"
+										/>
+									</div>
+									<div class="data-item">
+										<span>纭寲姘�: {{ sensor.h2s.toFixed(2) }}ppm</span>
+										<el-progress
+											:percentage="Math.min(Math.round((sensor.h2s / 20) * 100 * 100) / 100, 100)"
+											:color="getProgressColor(Math.min(Math.round((sensor.h2s / 20) * 100 * 100) / 100, 100), 80)"
+											:format="formatProgress"
+											:stroke-width="8"
+										/>
+									</div>
+								</div>
+							</div>
+						</div>
+					</div>
+					
+					<!-- 浜曞彛鍘嬬缉鏈虹洃鎺� -->
+					<div class="monitoring-section">
+						<h3>浜曞彛鍘嬬缉鏈虹洃鎺�</h3>
+						<div class="sensor-grid">
+							<div class="sensor-item" v-for="sensor in compressorSensors" :key="sensor.id">
+								<div class="sensor-header">
+									<span>{{ sensor.name }}</span>
+									<el-tag :type="sensor.status === 'normal' ? 'success' : 'danger'" size="small">
+										{{ sensor.status === 'normal' ? '姝e父' : '瓒呮爣' }}
+									</el-tag>
+								</div>
+								<div class="sensor-data">
+									<div class="data-item">
+										<span>鐢茬兎: {{ sensor.methane.toFixed(2) }}%</span>
+										<el-progress
+											:percentage="Math.min(Math.round(sensor.methane * 40 * 100) / 100, 100)"
+											:color="getProgressColor(sensor.methane, 2.5)"
+											:format="formatProgress"
+											:stroke-width="8"
+										/>
+									</div>
+									<div class="data-item">
+										<span>纭寲姘�: {{ sensor.h2s.toFixed(2) }}ppm</span>
+										<el-progress
+											:percentage="Math.min(Math.round((sensor.h2s / 20) * 100 * 100) / 100, 100)"
+											:color="getProgressColor(sensor.h2s, 10)"
+											:format="formatProgress"
+											:stroke-width="8"
+										/>
+									</div>
+								</div>
+							</div>
+						</div>
+					</div>
+					
+					<!-- 瀹炴椂鏇茬嚎鍥� -->
+					<div class="chart-section">
+						<h3>瀹炴椂娴撳害鏇茬嚎</h3>
+						<div class="chart-container">
+							<div ref="chart" class="chart"></div>
+						</div>
+					</div>
+				</el-card>
+			</el-col>
+			
+			<!-- 鍙充晶锛氭帶鍒堕潰鏉� -->
+			<el-col :span="8">
+				<el-card class="control-card">
+					<div slot="header" class="card-header">
+						<span>搴旀�ユ帶鍒堕潰鏉�</span>
+					</div>
+					
+					<!-- 鍠锋穻鐘舵�� -->
+					<div class="control-section">
+						<h4>鍠锋穻绯荤粺鐘舵��</h4>
+						<div class="status-grid">
+							<div class="status-item" v-for="sprinkler in sprinklerSystems" :key="sprinkler.id">
+								<div class="status-indicator" :class="sprinkler.status">
+									<i class="el-icon-circle-check" v-if="sprinkler.status === 'active'"></i>
+									<i class="el-icon-circle-close" v-else></i>
+								</div>
+								<span>{{ sprinkler.name }}</span>
+								<el-tag :type="sprinkler.status === 'active' ? 'success' : 'info'" size="small">
+									{{ sprinkler.status === 'active' ? '杩愯涓�' : '寰呮満' }}
+								</el-tag>
+							</div>
+						</div>
+					</div>
+					
+					<!-- 搴旀�ヨ褰曟寜閽� -->
+					<h4>搴旀�ョ鐞�</h4>
+					
+					<div class="control-section1">
+						<el-button type="primary" @click="showEmergencyRecords" style="margin-bottom: 10px;">
+							搴旀�ヨ褰�
+						</el-button>
+						<el-button type="warning" @click="triggerEmergency" :disabled="!hasEmergency">
+							瑙﹀彂搴旀�ュ搷搴�
+						</el-button>
+					</div>
+					
+					<!-- 绯荤粺鏃ュ織 -->
+					<div class="control-section">
+						<h4>绯荤粺鏃ュ織</h4>
+						<div class="log-container">
+							<div class="log-item" v-for="log in systemLogs" :key="log.id">
+								<span class="log-time">{{ log.time }}</span>
+								<span class="log-content">{{ log.content }}</span>
+							</div>
+						</div>
+					</div>
+				</el-card>
+			</el-col>
+		</el-row>
+		
+		<!-- 娉勬紡棰勮寮圭獥 -->
+		<el-dialog
+			title="鈿狅笍 娉勬紡棰勮"
+			:visible.sync="leakWarningVisible"
+			width="500px"
+			:close-on-click-modal="false"
+			:close-on-press-escape="false"
+			class="leak-warning-dialog"
+		>
+			<div class="warning-content">
+				<div class="warning-icon">
+					<i class="el-icon-warning"></i>
+				</div>
+				<div class="warning-text">
+					<h3>妫�娴嬪埌姘斾綋娴撳害瓒呮爣锛�</h3>
+					<p>浣嶇疆锛歿{ currentWarning.location }}</p>
+					<p>瓒呮爣姘斾綋锛歿{ currentWarning.gas }}</p>
+					<p>褰撳墠娴撳害锛歿{ currentWarning.value }}</p>
+				</div>
+			</div>
+			<div slot="footer" class="dialog-footer">
+				<el-button type="danger" @click="acknowledgeWarning">纭鍛婅</el-button>
+				<el-button type="primary" @click="viewDetails">鏌ョ湅璇︽儏</el-button>
+			</div>
+		</el-dialog>
+		
+		<!-- 搴旀�ヨ褰曞脊绐� -->
+		<el-dialog
+			title="搴旀�ヨ褰�"
+			:visible.sync="emergencyRecordsVisible"
+			width="800px"
+		>
+			<el-table :data="emergencyRecords" style="width: 100%">
+				<el-table-column prop="time" label="鏃堕棿" width="180"></el-table-column>
+				<el-table-column prop="location" label="浣嶇疆" width="150"></el-table-column>
+				<el-table-column prop="type" label="绫诲瀷" width="120"></el-table-column>
+				<el-table-column prop="status" label="鐘舵��" width="100">
+					<template slot-scope="scope">
+						<el-tag :type="scope.row.status === 'resolved' ? 'success' : 'warning'">
+							{{ scope.row.status === 'resolved' ? '宸茶В鍐�' : '澶勭悊涓�' }}
+						</el-tag>
+					</template>
+				</el-table-column>
+				<el-table-column prop="description" label="鎻忚堪"></el-table-column>
+				<el-table-column label="鎿嶄綔" width="120">
+					<template slot-scope="scope">
+						<el-button type="text" @click="viewBlockchainDetails(scope.row)">
+							鍖哄潡閾捐鎯�
+						</el-button>
+					</template>
+				</el-table-column>
+			</el-table>
+		</el-dialog>
+		
+		<!-- 鍖哄潡閾惧瓨璇佽鎯呭脊绐� -->
+		<el-dialog
+			title="鍖哄潡閾惧瓨璇佽鎯�"
+			:visible.sync="blockchainDetailsVisible"
+			width="900px"
+		>
+			<div class="blockchain-details">
+				<el-descriptions :column="2" border>
+					<el-descriptions-item label="浜嬩欢ID">{{ currentEvent.id }}</el-descriptions-item>
+					<el-descriptions-item label="鏃堕棿鎴�">{{ currentEvent.timestamp }}</el-descriptions-item>
+					<el-descriptions-item label="浣嶇疆">{{ currentEvent.location }}</el-descriptions-item>
+					<el-descriptions-item label="浜嬩欢绫诲瀷">{{ currentEvent.type }}</el-descriptions-item>
+				</el-descriptions>
+				
+				<div class="sensor-data-section">
+					<h4>浼犳劅鍣ㄦ暟鎹�</h4>
+					<el-table :data="currentEvent.sensorData" style="width: 100%">
+						<el-table-column prop="sensor" label="浼犳劅鍣�"></el-table-column>
+						<el-table-column prop="methane" label="鐢茬兎娴撳害"></el-table-column>
+						<el-table-column prop="h2s" label="纭寲姘㈡祿搴�"></el-table-column>
+						<el-table-column prop="timestamp" label="璁板綍鏃堕棿"></el-table-column>
+					</el-table>
+				</div>
+				
+				<div class="action-log-section">
+					<h4>澶勭疆鍔ㄤ綔璁板綍</h4>
+					<el-timeline>
+						<el-timeline-item
+							v-for="action in currentEvent.actions"
+							:key="action.id"
+							:timestamp="action.timestamp"
+							:type="action.type === 'emergency' ? 'danger' : 'primary'"
+						>
+							{{ action.description }}
+						</el-timeline-item>
+					</el-timeline>
+				</div>
+				
+				<div class="blockchain-info">
+					<h4>鍖哄潡閾句俊鎭�</h4>
+					<el-descriptions :column="1" border>
+						<el-descriptions-item label="鍖哄潡鍝堝笇">{{ currentEvent.blockHash }}</el-descriptions-item>
+						<el-descriptions-item label="浜ゆ槗鍝堝笇">{{ currentEvent.txHash }}</el-descriptions-item>
+						<el-descriptions-item label="纭鏁�">{{ currentEvent.confirmations }}</el-descriptions-item>
+					</el-descriptions>
+				</div>
+			</div>
+		</el-dialog>
+	</div>
+</template>
+
+<script>
+import * as echarts from 'echarts'
+
+export default {
+	name: 'SafetyMonitoring',
+	data() {
+		return {
+			systemStatus: 'normal',
+			leakWarningVisible: false,
+			emergencyRecordsVisible: false,
+			blockchainDetailsVisible: false,
+			currentWarning: {},
+			currentEvent: {},
+			hasEmergency: false,
+			
+			// 鍌ㄧ綈鍖轰紶鎰熷櫒鏁版嵁
+			tankSensors: [
+				{ id: 1, name: '鍌ㄧ綈T-001', methane: 1.20, h2s: 2.10, status: 'normal' },
+				{ id: 2, name: '鍌ㄧ綈T-002', methane: 0.80, h2s: 1.50, status: 'normal' },
+				{ id: 3, name: '鍌ㄧ綈T-003', methane: 3.20, h2s: 8.50, status: 'warning' },
+				{ id: 4, name: '鍌ㄧ綈T-004', methane: 0.60, h2s: 0.80, status: 'normal' }
+			],
+			
+			// 浜曞彛鍘嬬缉鏈轰紶鎰熷櫒鏁版嵁
+			compressorSensors: [
+				{ id: 5, name: '鍘嬬缉鏈篊-001', methane: 2.10, h2s: 3.20, status: 'normal' },
+				{ id: 6, name: '鍘嬬缉鏈篊-002', methane: 4.80, h2s: 12.50, status: 'warning' },
+				{ id: 7, name: '鍘嬬缉鏈篊-003', methane: 1.80, h2s: 2.80, status: 'normal' }
+			],
+			
+			// 鍠锋穻绯荤粺鐘舵��
+			sprinklerSystems: [
+				{ id: 1, name: '鍌ㄧ綈鍖哄柗娣�', status: 'active' },
+				{ id: 2, name: '鍘嬬缉鏈哄尯鍠锋穻', status: 'standby' },
+				{ id: 3, name: '绱ф�ュ柗娣�', status: 'standby' }
+			],
+			
+			// 绯荤粺鏃ュ織
+			systemLogs: [
+				{ id: 1, time: '14:30:25', content: '绯荤粺鍚姩瀹屾垚锛屾墍鏈変紶鎰熷櫒姝e父' },
+				{ id: 2, time: '14:35:12', content: '鍌ㄧ綈T-003鐢茬兎娴撳害瓒呮爣锛岃Е鍙戦璀�' },
+				{ id: 3, time: '14:35:15', content: '鍚姩鍌ㄧ綈鍖哄柗娣嬬郴缁�' },
+				{ id: 4, time: '14:35:20', content: '鍙戦�佺揣鎬ョ枏鏁e箍鎾�' }
+			],
+			
+			// 搴旀�ヨ褰�
+			emergencyRecords: [
+				{
+					id: 'EM001',
+					time: '2024-01-15 14:35:12',
+					location: '鍌ㄧ綈T-003',
+					type: '鐢茬兎瓒呮爣',
+					status: 'resolved',
+					description: '鍌ㄧ綈T-003鐢茬兎娴撳害杈惧埌3.2%锛岃秴杩囧畨鍏ㄩ槇鍊�2.5%'
+				},
+				{
+					id: 'EM002',
+					time: '2024-01-15 14:35:15',
+					location: '鍘嬬缉鏈篊-002',
+					type: '纭寲姘㈣秴鏍�',
+					status: 'processing',
+					description: '鍘嬬缉鏈篊-002纭寲姘㈡祿搴﹁揪鍒�12.5ppm锛岃秴杩囧畨鍏ㄩ槇鍊�10ppm'
+				}
+			],
+			
+			// 鍥捐〃瀹炰緥
+			chart: null,
+			
+			// 瀹氭椂鍣�
+			timer: null
+		}
+	},
+	
+	mounted() {
+		this.initChart()
+		this.startDataRefresh()
+		this.checkEmergencyStatus()
+	},
+	
+	beforeDestroy() {
+		if (this.timer) {
+			clearInterval(this.timer)
+		}
+		if (this.chart) {
+			this.chart.dispose()
+		}
+	},
+	
+	methods: {
+		// 缁熶竴杩涘害鏉℃牸寮忓寲涓轰袱浣嶅皬鏁帮紝閬垮厤娴偣璇樊鏄剧ず
+		formatProgress(percentage) {
+			if (percentage == null || isNaN(percentage)) return '0.00%'
+			const val = Math.round(Number(percentage) * 100) / 100
+			return `${val.toFixed(2)}%`
+		},
+		// 鍒濆鍖栧浘琛�
+		initChart() {
+			this.chart = echarts.init(this.$refs.chart)
+			this.updateChart()
+		},
+		
+		// 鏇存柊鍥捐〃鏁版嵁
+		updateChart() {
+			const option = {
+				title: {
+					text: '瀹炴椂姘斾綋娴撳害鐩戞帶',
+					left: 'center'
+				},
+				tooltip: {
+					trigger: 'axis',
+					axisPointer: {
+						type: 'cross'
+					}
+				},
+				legend: {
+					data: ['鍌ㄧ綈鍖虹敳鐑�', '鍌ㄧ綈鍖虹~鍖栨阿', '鍘嬬缉鏈虹敳鐑�', '鍘嬬缉鏈虹~鍖栨阿'],
+					top: 30
+				},
+				grid: {
+					left: '3%',
+					right: '4%',
+					bottom: '3%',
+					top: '15%',
+					containLabel: true
+				},
+				xAxis: {
+					type: 'category',
+					data: this.generateTimeData()
+				},
+				yAxis: [
+					{
+						type: 'value',
+						name: '鐢茬兎娴撳害(%)',
+						position: 'left'
+					},
+					{
+						type: 'value',
+						name: '纭寲姘㈡祿搴�(ppm)',
+						position: 'right'
+					}
+				],
+				series: [
+					{
+						name: '鍌ㄧ綈鍖虹敳鐑�',
+						type: 'line',
+						data: this.generateRandomData(20, 0.5, 3.5),
+						smooth: true,
+						yAxisIndex: 0
+					},
+					{
+						name: '鍌ㄧ綈鍖虹~鍖栨阿',
+						type: 'line',
+						data: this.generateRandomData(20, 0.5, 12),
+						smooth: true,
+						yAxisIndex: 1
+					},
+					{
+						name: '鍘嬬缉鏈虹敳鐑�',
+						type: 'line',
+						data: this.generateRandomData(20, 1.0, 5.0),
+						smooth: true,
+						yAxisIndex: 0
+					},
+					{
+						name: '鍘嬬缉鏈虹~鍖栨阿',
+						type: 'line',
+						data: this.generateRandomData(20, 1.0, 15),
+						smooth: true,
+						yAxisIndex: 1
+					}
+				]
+			}
+			
+			this.chart.setOption(option)
+		},
+		
+		// 鐢熸垚鏃堕棿鏁版嵁
+		generateTimeData() {
+			const times = []
+			const now = new Date()
+			for (let i = 19; i >= 0; i--) {
+				const time = new Date(now.getTime() - i * 5 * 60 * 1000)
+				times.push(time.toLocaleTimeString('zh-CN', { hour12: false }))
+			}
+			return times
+		},
+		
+		// 鐢熸垚闅忔満鏁版嵁
+		generateRandomData(count, min, max) {
+			const data = []
+			for (let i = 0; i < count; i++) {
+				data.push(+(Math.random() * (max - min) + min).toFixed(2))
+			}
+			return data
+		},
+		
+		// 寮�濮嬫暟鎹埛鏂�
+		startDataRefresh() {
+			this.timer = setInterval(() => {
+				this.refreshSensorData()
+				this.updateChart()
+				this.checkEmergencyStatus()
+			}, 5000) // 姣�5绉掑埛鏂颁竴娆�
+		},
+		
+		// 鍒锋柊浼犳劅鍣ㄦ暟鎹�
+		refreshSensorData() {
+			// 鏇存柊鍌ㄧ綈鍖轰紶鎰熷櫒鏁版嵁
+			this.tankSensors.forEach(sensor => {
+				sensor.methane = +(Math.random() * 4).toFixed(2)
+				sensor.h2s = +(Math.random() * 15).toFixed(2)
+				sensor.status = this.getSensorStatus(sensor.methane, sensor.h2s)
+			})
+			
+			// 鏇存柊鍘嬬缉鏈轰紶鎰熷櫒鏁版嵁
+			this.compressorSensors.forEach(sensor => {
+				sensor.methane = +(Math.random() * 6).toFixed(2)
+				sensor.h2s = +(Math.random() * 20).toFixed(2)
+				sensor.status = this.getSensorStatus(sensor.methane, sensor.h2s)
+			})
+			
+			// 妫�鏌ユ槸鍚﹂渶瑕佽Е鍙戦璀�
+			this.checkLeakWarning()
+		},
+		
+		// 鑾峰彇浼犳劅鍣ㄧ姸鎬�
+		getSensorStatus(methane, h2s) {
+			const methanePct = Math.min(Math.round(methane * 40 * 100) / 100, 100)
+			const h2sPct = Math.min(Math.round((h2s / 20) * 100 * 100) / 100, 100)
+			if (methanePct >= 80 || h2sPct >= 80) {
+				return 'warning'
+			}
+			return 'normal'
+		},
+		
+		// 妫�鏌ユ硠婕忛璀�
+		checkLeakWarning() {
+			const allSensors = [...this.tankSensors, ...this.compressorSensors]
+			const warningSensor = allSensors.find(sensor => this.getSensorStatus(sensor.methane, sensor.h2s) === 'warning')
+			
+			if (warningSensor && !this.leakWarningVisible) {
+				this.triggerLeakWarning(warningSensor)
+			}
+		},
+		
+		// 瑙﹀彂娉勬紡棰勮
+		triggerLeakWarning(sensor) {
+			const methanePct = Math.min(Math.round(sensor.methane * 40 * 100) / 100, 100)
+			const h2sPct = Math.min(Math.round((sensor.h2s / 20) * 100 * 100) / 100, 100)
+			const isMethaneMajor = methanePct >= h2sPct
+			const overGas = isMethaneMajor ? '鐢茬兎' : '纭寲姘�'
+			const percent = (isMethaneMajor ? methanePct : h2sPct).toFixed(2)
+			this.currentWarning = {
+				location: sensor.name,
+				gas: overGas,
+				value: `${percent}%`
+			}
+			
+			this.leakWarningVisible = true
+			this.hasEmergency = true
+			
+			// 鑷姩瑙﹀彂搴旀�ュ搷搴�
+			this.autoEmergencyResponse(sensor)
+			
+			// 娣诲姞绯荤粺鏃ュ織
+			this.addSystemLog(`妫�娴嬪埌${sensor.name}姘斾綋娴撳害瓒呮爣锛岃Е鍙戞硠婕忛璀)
+		},
+		
+		// 鑷姩搴旀�ュ搷搴�
+		autoEmergencyResponse(sensor) {
+			// 鍚姩鍠锋穻绯荤粺
+			if (sensor.name.includes('鍌ㄧ綈')) {
+				this.sprinklerSystems[0].status = 'active'
+			} else if (sensor.name.includes('鍘嬬缉鏈�')) {
+				this.sprinklerSystems[1].status = 'active'
+			}
+			
+			// 娣诲姞绯荤粺鏃ュ織
+			this.addSystemLog(`鍚姩${sensor.name}鍖哄煙鍠锋穻绯荤粺`)
+			this.addSystemLog(`鍙戦�佺揣鎬ョ枏鏁e箍鎾璥)
+			
+			// 鍒涘缓搴旀�ヨ褰�
+			this.createEmergencyRecord(sensor)
+		},
+		
+		// 娣诲姞绯荤粺鏃ュ織
+		addSystemLog(content) {
+			const now = new Date()
+			const time = now.toLocaleTimeString('zh-CN', { hour12: false })
+			
+			this.systemLogs.unshift({
+				id: Date.now(),
+				time: time,
+				content: content
+			})
+			
+			// 淇濇寔鏈�澶�20鏉℃棩蹇�
+			if (this.systemLogs.length > 20) {
+				this.systemLogs = this.systemLogs.slice(0, 20)
+			}
+		},
+		
+		// 鍒涘缓搴旀�ヨ褰�
+		createEmergencyRecord(sensor) {
+			const now = new Date()
+			const record = {
+				id: `EM${Date.now()}`,
+				time: now.toLocaleString('zh-CN'),
+				location: sensor.name,
+				type: sensor.methane > 2.5 ? '鐢茬兎瓒呮爣' : '纭寲姘㈣秴鏍�',
+				status: 'processing',
+				description: `${sensor.name}妫�娴嬪埌${sensor.methane > 2.5 ? '鐢茬兎' : '纭寲姘�'}娴撳害瓒呮爣`
+			}
+			
+			this.emergencyRecords.unshift(record)
+		},
+		
+		// 鑾峰彇杩涘害鏉¢鑹�
+		getProgressColor(value, threshold) {
+			if (value > threshold) {
+				return '#F56C6C'
+			} else if (value > threshold * 0.8) {
+				return '#E6A23C'
+			}
+			return '#67C23A'
+		},
+		
+		// 妫�鏌ュ簲鎬ョ姸鎬�
+		checkEmergencyStatus() {
+			const allSensors = [...this.tankSensors, ...this.compressorSensors]
+			const has = allSensors.some(sensor => this.getSensorStatus(sensor.methane, sensor.h2s) === 'warning')
+			this.hasEmergency = has
+			this.systemStatus = has ? 'warning' : 'normal'
+		},
+		
+		// 纭鍛婅
+		acknowledgeWarning() {
+			this.leakWarningVisible = false
+			this.addSystemLog('娉勬紡棰勮宸茬‘璁�')
+		},
+		
+		// 鏌ョ湅璇︽儏
+		viewDetails() {
+			this.leakWarningVisible = false
+			// 杩欓噷鍙互璺宠浆鍒拌缁嗛〉闈㈡垨鏄剧ず鏇村淇℃伅
+		},
+		
+		// 鏄剧ず搴旀�ヨ褰�
+		showEmergencyRecords() {
+			this.emergencyRecordsVisible = true
+		},
+		
+		// 鏌ョ湅鍖哄潡閾捐鎯�
+		viewBlockchainDetails(record) {
+			this.currentEvent = {
+				id: record.id,
+				timestamp: record.time,
+				location: record.location,
+				type: record.type,
+				sensorData: [
+					{
+						sensor: '鐢茬兎浼犳劅鍣�',
+						methane: '3.2%',
+						h2s: '8.5ppm',
+						timestamp: record.time
+					},
+					{
+						sensor: '纭寲姘紶鎰熷櫒',
+						methane: '2.8%',
+						h2s: '12.5ppm',
+						timestamp: record.time
+					}
+				],
+				actions: [
+					{
+						id: 1,
+						timestamp: record.time,
+						type: 'emergency',
+						description: '妫�娴嬪埌姘斾綋娴撳害瓒呮爣锛岃Е鍙戦璀�'
+					},
+					{
+						id: 2,
+						timestamp: new Date(new Date(record.time).getTime() + 3000).toLocaleString('zh-CN'),
+						type: 'action',
+						description: '鍚姩鍠锋穻绯荤粺闄嶆俯'
+					},
+					{
+						id: 3,
+						timestamp: new Date(new Date(record.time).getTime() + 5000).toLocaleString('zh-CN'),
+						type: 'action',
+						description: '鍙戦�佺揣鎬ョ枏鏁e箍鎾�'
+					}
+				],
+				blockHash: '0x1234567890abcdef...',
+				txHash: '0xabcdef1234567890...',
+				confirmations: 12
+			}
+			
+			this.emergencyRecordsVisible = false
+			this.blockchainDetailsVisible = true
+		},
+		
+		// 瑙﹀彂搴旀�ュ搷搴�
+		triggerEmergency() {
+			this.$message.success('搴旀�ュ搷搴斿凡瑙﹀彂')
+			this.addSystemLog('鎵嬪姩瑙﹀彂搴旀�ュ搷搴�')
+		}
+	}
+}
+</script>
+
+<style scoped>
+.safety-monitoring {
+	padding: 20px;
+	background-color: #f5f7fa;
+	min-height: calc(100vh - 84px);
+}
+
+.monitoring-card, .control-card {
+	margin-bottom: 20px;
+}
+
+.card-header {
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+}
+
+.monitoring-section {
+	margin-bottom: 30px;
+}
+
+.monitoring-section h3 {
+	color: #303133;
+	margin-bottom: 15px;
+	padding-bottom: 8px;
+	border-bottom: 2px solid #409EFF;
+}
+
+.sensor-grid {
+	display: grid;
+	grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+	gap: 15px;
+}
+
+.sensor-item {
+	background: #fff;
+	border: 1px solid #e4e7ed;
+	border-radius: 8px;
+	padding: 15px;
+	box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+}
+
+.sensor-header {
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	margin-bottom: 15px;
+	font-weight: bold;
+}
+
+.sensor-data .data-item {
+	margin-bottom: 12px;
+}
+
+.sensor-data .data-item span {
+	display: block;
+	margin-bottom: 5px;
+	font-size: 14px;
+	color: #606266;
+}
+
+.chart-section {
+	margin-top: 30px;
+}
+
+.chart-section h3 {
+	color: #303133;
+	margin-bottom: 15px;
+	padding-bottom: 8px;
+	border-bottom: 2px solid #409EFF;
+}
+
+.chart-container {
+	background: #fff;
+	border-radius: 8px;
+	padding: 20px;
+}
+
+.chart {
+	width: 100%;
+	height: 400px;
+}
+
+.control-section {
+	margin-bottom: 25px;
+}
+.control-section1 {
+	display: flex;
+}
+
+.control-section h4 {
+	color: #303133;
+	margin-bottom: 15px;
+	font-size: 16px;
+}
+
+.status-grid {
+	display: grid;
+	gap: 10px;
+}
+
+.status-item {
+	display: flex;
+	align-items: center;
+	gap: 10px;
+	padding: 10px;
+	background: #f8f9fa;
+	border-radius: 6px;
+}
+
+.status-indicator {
+	width: 20px;
+	height: 20px;
+	border-radius: 50%;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.status-indicator.active {
+	color: #67C23A;
+}
+
+.status-indicator.standby {
+	color: #909399;
+}
+
+.log-container {
+	max-height: 200px;
+	overflow-y: auto;
+	background: #f8f9fa;
+	border-radius: 6px;
+	padding: 10px;
+}
+
+.log-item {
+	display: flex;
+	gap: 10px;
+	margin-bottom: 8px;
+	font-size: 12px;
+}
+
+.log-time {
+	color: #909399;
+	min-width: 60px;
+}
+
+.log-content {
+	color: #606266;
+}
+
+/* 娉勬紡棰勮寮圭獥鏍峰紡 */
+.leak-warning-dialog {
+	background: #fff5f5;
+}
+
+.warning-content {
+	text-align: center;
+	padding: 20px 0;
+}
+
+.warning-icon {
+	font-size: 60px;
+	color: #F56C6C;
+	margin-bottom: 20px;
+}
+
+.warning-text h3 {
+	color: #F56C6C;
+	margin-bottom: 15px;
+}
+
+.warning-text p {
+	margin: 8px 0;
+	color: #606266;
+}
+
+/* 鍖哄潡閾捐鎯呮牱寮� */
+.blockchain-details {
+	padding: 20px 0;
+}
+
+.sensor-data-section, .action-log-section, .blockchain-info {
+	margin-top: 25px;
+}
+
+.sensor-data-section h4, .action-log-section h4, .blockchain-info h4 {
+	color: #303133;
+	margin-bottom: 15px;
+	padding-bottom: 8px;
+	border-bottom: 1px solid #e4e7ed;
+}
+
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 1200px) {
+	.sensor-grid {
+		grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+	}
+}
+
+@media (max-width: 768px) {
+	.safety-monitoring {
+		padding: 10px;
+	}
+	
+	.sensor-grid {
+		grid-template-columns: 1fr;
+	}
+	
+	.chart {
+		height: 300px;
+	}
+}
+</style>

--
Gitblit v1.9.3