gaoluyang
2025-08-13 440f11bade4901ea4bb6e935019e01748c193ad0
安全监控页面添加
已添加1个文件
873 ■■■■■ 文件已修改
src/views/productionManagement/safetyMonitoring/index.vue 873 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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' ? '系统正常' : '系统告警' }}
                        </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' ? '正常' : '超标' }}
                                    </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' ? '正常' : '超标' }}
                                    </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: '压缩机C-001', methane: 2.10, h2s: 3.20, status: 'normal' },
                { id: 6, name: '压缩机C-002', methane: 4.80, h2s: 12.50, status: 'warning' },
                { id: 7, name: '压缩机C-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: '系统启动完成,所有传感器正常' },
                { id: 2, time: '14:35:12', content: '储罐T-003甲烷浓度超标,触发预警' },
                { id: 3, time: '14:35:15', content: '启动储罐区喷淋系统' },
                { id: 4, time: '14:35:20', content: '发送紧急疏散广播' }
            ],
            // åº”急记录
            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: '压缩机C-002',
                    type: '硫化氢超标',
                    status: 'processing',
                    description: '压缩机C-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(`发送紧急疏散广播`)
            // åˆ›å»ºåº”急记录
            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: '发送紧急疏散广播'
                    }
                ],
                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>