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