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