<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>
|