|
|
|
|
@ -0,0 +1,485 @@
|
|
|
|
|
import React, { useState, useEffect, useRef } from 'react';
|
|
|
|
|
import { Row, Col, Select, Table, Pagination } from 'antd';
|
|
|
|
|
import styles from './EnvironmentalMonitoring.less';
|
|
|
|
|
import * as echarts from 'echarts';
|
|
|
|
|
|
|
|
|
|
// EChartsGauge组件定义 - 支持动态大小
|
|
|
|
|
const EChartsGauge = (props) => {
|
|
|
|
|
const {
|
|
|
|
|
width = '100%',
|
|
|
|
|
height = '100%',
|
|
|
|
|
minWidth = 250,
|
|
|
|
|
maxWidth = 500,
|
|
|
|
|
minHeight = 180,
|
|
|
|
|
maxHeight = 350,
|
|
|
|
|
option,
|
|
|
|
|
loading = false
|
|
|
|
|
} = props;
|
|
|
|
|
const chartRef = useRef(null);
|
|
|
|
|
const chartInstance = useRef(null);
|
|
|
|
|
|
|
|
|
|
// 计算响应式尺寸
|
|
|
|
|
const calculateResponsiveSize = () => {
|
|
|
|
|
if (!chartRef.current) return;
|
|
|
|
|
|
|
|
|
|
const parentElement = chartRef.current.parentElement;
|
|
|
|
|
if (!parentElement) return;
|
|
|
|
|
|
|
|
|
|
// 获取父容器的可用空间,考虑padding和border
|
|
|
|
|
const parentComputedStyle = window.getComputedStyle(parentElement);
|
|
|
|
|
const parentWidth = parentElement.clientWidth -
|
|
|
|
|
parseInt(parentComputedStyle.paddingLeft) -
|
|
|
|
|
parseInt(parentComputedStyle.paddingRight);
|
|
|
|
|
const parentHeight = parentElement.clientHeight -
|
|
|
|
|
parseInt(parentComputedStyle.paddingTop) -
|
|
|
|
|
parseInt(parentComputedStyle.paddingBottom);
|
|
|
|
|
|
|
|
|
|
// 计算响应式宽度,确保在最小和最大尺寸之间
|
|
|
|
|
let responsiveWidth = typeof width === 'number' ? width : parentWidth;
|
|
|
|
|
responsiveWidth = Math.max(minWidth, Math.min(maxWidth, responsiveWidth));
|
|
|
|
|
|
|
|
|
|
// 计算响应式高度,确保在最小和最大尺寸之间
|
|
|
|
|
let responsiveHeight = typeof height === 'number' ? height : parentHeight;
|
|
|
|
|
responsiveHeight = Math.max(minHeight, Math.min(maxHeight, responsiveHeight));
|
|
|
|
|
|
|
|
|
|
// 为了更好的响应式体验,当宽度小于350px时调整高度
|
|
|
|
|
if (responsiveWidth < 350) {
|
|
|
|
|
responsiveHeight = Math.max(minHeight, responsiveWidth * 0.6);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 设置容器尺寸
|
|
|
|
|
chartRef.current.style.width = `${responsiveWidth}px`;
|
|
|
|
|
chartRef.current.style.height = `${responsiveHeight}px`;
|
|
|
|
|
|
|
|
|
|
// 调整图表大小
|
|
|
|
|
if (chartInstance.current) {
|
|
|
|
|
chartInstance.current.resize();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
// 初始化图表
|
|
|
|
|
if (chartRef.current && !chartInstance.current) {
|
|
|
|
|
chartInstance.current = echarts.init(chartRef.current);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 设置option
|
|
|
|
|
if (chartInstance.current && option) {
|
|
|
|
|
chartInstance.current.setOption(option, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 计算初始响应式尺寸
|
|
|
|
|
calculateResponsiveSize();
|
|
|
|
|
|
|
|
|
|
// 窗口大小改变时,图表自适应
|
|
|
|
|
const handleResize = () => {
|
|
|
|
|
calculateResponsiveSize();
|
|
|
|
|
};
|
|
|
|
|
window.addEventListener('resize', handleResize);
|
|
|
|
|
|
|
|
|
|
// 清理函数
|
|
|
|
|
return () => {
|
|
|
|
|
window.removeEventListener('resize', handleResize);
|
|
|
|
|
if (chartInstance.current) {
|
|
|
|
|
chartInstance.current.dispose();
|
|
|
|
|
chartInstance.current = null;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}, [option]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (chartInstance.current) {
|
|
|
|
|
if (loading) {
|
|
|
|
|
chartInstance.current.showLoading();
|
|
|
|
|
} else {
|
|
|
|
|
chartInstance.current.hideLoading();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, [loading]);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
ref={chartRef}
|
|
|
|
|
style={{
|
|
|
|
|
width: '100%',
|
|
|
|
|
height: '100%',
|
|
|
|
|
minWidth: `${minWidth}px`,
|
|
|
|
|
maxWidth: `${maxWidth}px`,
|
|
|
|
|
minHeight: `${minHeight}px`,
|
|
|
|
|
maxHeight: `${maxHeight}px`,
|
|
|
|
|
margin: '0 auto'
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 获取仪表盘配置选项
|
|
|
|
|
const getGaugeOption = (title, value, unit, max = 100) => {
|
|
|
|
|
return {
|
|
|
|
|
series: [
|
|
|
|
|
{
|
|
|
|
|
type: 'gauge',
|
|
|
|
|
startAngle: 180,
|
|
|
|
|
endAngle: 0,
|
|
|
|
|
min: 0,
|
|
|
|
|
max: 120,
|
|
|
|
|
splitNumber: 6,
|
|
|
|
|
animation: true,
|
|
|
|
|
animationDuration: 2000,
|
|
|
|
|
animationEasing: 'cubicOut',
|
|
|
|
|
animationDelay: 0,
|
|
|
|
|
animationDurationUpdate: 1000,
|
|
|
|
|
itemStyle: {
|
|
|
|
|
color: 'rgba(4, 95, 94, 0.5)',
|
|
|
|
|
shadowColor: 'rgba(4, 95, 94, 0.7)',
|
|
|
|
|
shadowBlur: 10,
|
|
|
|
|
shadowOffsetX: 2,
|
|
|
|
|
shadowOffsetY: 2
|
|
|
|
|
},
|
|
|
|
|
progress: {
|
|
|
|
|
show: true,
|
|
|
|
|
roundCap: true,
|
|
|
|
|
width: 18,
|
|
|
|
|
itemStyle: {
|
|
|
|
|
color: 'rgba(4, 95, 94, 0.5)'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
pointer: {
|
|
|
|
|
icon: 'path://M2090.36389,615.30999 L2090.36389,615.30999 C2091.48372,615.30999 2092.40383,616.194028 2092.44859,617.312956 L2096.90698,728.755929 C2097.05155,732.369577 2094.2393,735.416212 2090.62566,735.56078 C2090.53845,735.564269 2090.45117,735.566014 2090.36389,735.566014 L2090.36389,735.566014 C2086.74736,735.566014 2083.81557,732.63423 2083.81557,729.017692 C2083.81557,728.930412 2083.81732,728.84314 2083.82081,728.755929 L2088.2792,617.312956 C2088.32396,616.194028 2089.24407,615.30999 2090.36389,615.30999 Z',
|
|
|
|
|
length: '75%',
|
|
|
|
|
width: 16,
|
|
|
|
|
offsetCenter: [0, '5%'],
|
|
|
|
|
itemStyle: {
|
|
|
|
|
color: 'rgba(4, 95, 94, 1)'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
axisLine: {
|
|
|
|
|
roundCap: true,
|
|
|
|
|
lineStyle: {
|
|
|
|
|
width: 18,
|
|
|
|
|
color: [
|
|
|
|
|
[1, 'rgba(4, 95, 94, 0.5)']
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
axisTick: {
|
|
|
|
|
splitNumber: 2,
|
|
|
|
|
lineStyle: {
|
|
|
|
|
width: 2,
|
|
|
|
|
color: '#999'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
splitLine: {
|
|
|
|
|
length: 12,
|
|
|
|
|
lineStyle: {
|
|
|
|
|
width: 3,
|
|
|
|
|
color: 'rgba(4, 95, 94, 0.8)'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
axisLabel: {
|
|
|
|
|
distance: 30,
|
|
|
|
|
color: 'rgba(4, 95, 94, 0.8)',
|
|
|
|
|
fontSize: 20
|
|
|
|
|
},
|
|
|
|
|
title: {
|
|
|
|
|
show: true,
|
|
|
|
|
offsetCenter: [0, '10%'],
|
|
|
|
|
fontSize: 18,
|
|
|
|
|
color: 'rgba(4, 95, 94, 0.8)'
|
|
|
|
|
},
|
|
|
|
|
detail: {
|
|
|
|
|
fontSize: 20,
|
|
|
|
|
fontWeight: '400',
|
|
|
|
|
color: 'rgba(4, 95, 94, 1)',
|
|
|
|
|
width: '60%',
|
|
|
|
|
lineHeight: 40,
|
|
|
|
|
height: 40,
|
|
|
|
|
borderRadius: 8,
|
|
|
|
|
offsetCenter: [0, '35%'],
|
|
|
|
|
valueAnimation: true,
|
|
|
|
|
formatter: function (value) {
|
|
|
|
|
return `${title}:${value.toFixed(0)}${unit}`;
|
|
|
|
|
},
|
|
|
|
|
rich: {
|
|
|
|
|
value: {
|
|
|
|
|
fontSize: 20,
|
|
|
|
|
fontWeight: '400',
|
|
|
|
|
color: 'rgba(4, 95, 94, 1)'
|
|
|
|
|
},
|
|
|
|
|
unit: {
|
|
|
|
|
fontSize: 20,
|
|
|
|
|
color: 'rgba(4, 95, 94, 0.8)',
|
|
|
|
|
padding: [0, 0, -20, 10]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
data: [
|
|
|
|
|
{
|
|
|
|
|
value: value,
|
|
|
|
|
name: ''
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 传感器仪表盘组件
|
|
|
|
|
const SensorDashboard = () => {
|
|
|
|
|
// 模拟传感器数据,与图片完全一致
|
|
|
|
|
const sensorData = [
|
|
|
|
|
{ id: 1, type: '温度', value: 26, unit: '°C', maxValue: 100 },
|
|
|
|
|
{ id: 2, type: '一氧化碳', value: 0, unit: 'ppm', maxValue: 100 },
|
|
|
|
|
{ id: 3, type: '湿度', value: 55, unit: '%RH', maxValue: 100 },
|
|
|
|
|
{ id: 4, type: '二氧化碳', value: 0, unit: 'ppm', maxValue: 100 },
|
|
|
|
|
{ id: 5, type: '甲烷', value: 0, unit: 'ppm', maxValue: 100 },
|
|
|
|
|
{ id: 6, type: '烟雾', value: 0, unit: '无', maxValue: 100 },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div style={{ padding: '20px' }}>
|
|
|
|
|
<h4 style={{ marginBottom: '20px', color: '#006665', fontWeight: 'bold' }}>• 传感器仪表盘</h4>
|
|
|
|
|
<div style={{
|
|
|
|
|
display: 'grid',
|
|
|
|
|
gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 450px), 1fr))',
|
|
|
|
|
gap: '20px',
|
|
|
|
|
width: '100%',
|
|
|
|
|
boxSizing: 'border-box'
|
|
|
|
|
}}>
|
|
|
|
|
{sensorData.map((sensor) => {
|
|
|
|
|
// 为每个传感器创建对应的仪表盘配置选项
|
|
|
|
|
const gaugeOption = getGaugeOption(
|
|
|
|
|
sensor.type,
|
|
|
|
|
sensor.value,
|
|
|
|
|
sensor.unit,
|
|
|
|
|
sensor.maxValue
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div key={sensor.id} style={{
|
|
|
|
|
textAlign: 'center',
|
|
|
|
|
padding: '10px',
|
|
|
|
|
width: '100%',
|
|
|
|
|
boxSizing: 'border-box'
|
|
|
|
|
}}>
|
|
|
|
|
<div className={styles.dashboardContainer} style={{
|
|
|
|
|
width: '100%',
|
|
|
|
|
height: 'auto',
|
|
|
|
|
display: 'flex',
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
overflow: 'hidden'
|
|
|
|
|
}}>
|
|
|
|
|
{/* 使用EChartsGauge组件,采用动态响应式大小 */}
|
|
|
|
|
{/* 这里可以直接传入尺寸参数来自定义仪表盘大小 */}
|
|
|
|
|
<EChartsGauge
|
|
|
|
|
option={gaugeOption}
|
|
|
|
|
// 可以根据需要调整以下参数来自定义仪表盘大小:
|
|
|
|
|
// minWidth={250} // 最小宽度
|
|
|
|
|
// maxWidth={500} // 最大宽度
|
|
|
|
|
// minHeight={180} // 最小高度
|
|
|
|
|
// maxHeight={350} // 最大高度
|
|
|
|
|
width={450} // 固定宽度(如果不需要响应式)
|
|
|
|
|
height={350} // 固定高度(如果不需要响应式)
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 传感器配置区组件
|
|
|
|
|
const SensorConfig = () => {
|
|
|
|
|
// 根据图片显示,默认选中甲烷、一氧化碳、二氧化碳
|
|
|
|
|
const [selectedGases, setSelectedGases] = useState(['甲烷', '一氧化碳', '二氧化碳']);
|
|
|
|
|
|
|
|
|
|
const handleGasChange = (value) => {
|
|
|
|
|
setSelectedGases(value);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div style={{ padding: '20px', backgroundColor: '#fafafa', borderRadius: '4px' }}>
|
|
|
|
|
<h4 style={{ marginBottom: '20px', color: '#006665', fontWeight: 'bold', fontSize: '16px' }}>• 传感器配置区</h4>
|
|
|
|
|
<div style={{ marginBottom: '20px' }}>
|
|
|
|
|
<label style={{ marginRight: '10px', fontSize: '14px', color: '#333', fontWeight: '500' }}>气体传感器选配:</label>
|
|
|
|
|
<Select
|
|
|
|
|
mode="multiple"
|
|
|
|
|
style={{
|
|
|
|
|
width: '300px',
|
|
|
|
|
borderRadius: '4px',
|
|
|
|
|
borderColor: '#d9d9d9',
|
|
|
|
|
}}
|
|
|
|
|
placeholder="请选择气体传感器"
|
|
|
|
|
value={selectedGases}
|
|
|
|
|
onChange={handleGasChange}
|
|
|
|
|
options={[
|
|
|
|
|
{ label: '甲烷', value: '甲烷' },
|
|
|
|
|
{ label: '一氧化碳', value: '一氧化碳' },
|
|
|
|
|
{ label: '二氧化碳', value: '二氧化碳' },
|
|
|
|
|
{ label: '硫化氢', value: '硫化氢' },
|
|
|
|
|
{ label: '氧气', value: '氧气' },
|
|
|
|
|
]}
|
|
|
|
|
tokenSeparators={[',']} // 支持逗号分隔输入
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 环境告警记录组件
|
|
|
|
|
const AlertRecords = () => {
|
|
|
|
|
// 与图片完全一致的分页设置:第6页,共85条记录,每页10条
|
|
|
|
|
const [currentPage, setCurrentPage] = useState(6);
|
|
|
|
|
const totalRecords = 85;
|
|
|
|
|
const pageSize = 10;
|
|
|
|
|
|
|
|
|
|
// 模拟告警数据,与图片完全一致
|
|
|
|
|
const alertData = [
|
|
|
|
|
{
|
|
|
|
|
key: '1',
|
|
|
|
|
id: 1,
|
|
|
|
|
time: '2025-10-12 16:40',
|
|
|
|
|
type: '甲烷浓度超标',
|
|
|
|
|
value: '15ppm',
|
|
|
|
|
status: '已处理',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: '2',
|
|
|
|
|
id: 2,
|
|
|
|
|
time: '2025-10-10 09:10',
|
|
|
|
|
type: '烟雾告警',
|
|
|
|
|
value: '检测到烟雾',
|
|
|
|
|
status: '误报',
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const columns = [
|
|
|
|
|
{
|
|
|
|
|
title: '序号',
|
|
|
|
|
dataIndex: 'id',
|
|
|
|
|
key: 'id',
|
|
|
|
|
align: 'center',
|
|
|
|
|
width: 80,
|
|
|
|
|
render: (text) => <span style={{ fontSize: '14px', color: '#333' }}>{text}</span>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '告警时间',
|
|
|
|
|
dataIndex: 'time',
|
|
|
|
|
key: 'time',
|
|
|
|
|
align: 'center',
|
|
|
|
|
width: 150,
|
|
|
|
|
render: (text) => <span style={{ fontSize: '14px', color: '#333' }}>{text}</span>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '告警类型',
|
|
|
|
|
dataIndex: 'type',
|
|
|
|
|
key: 'type',
|
|
|
|
|
align: 'center',
|
|
|
|
|
render: (text) => <span style={{ fontSize: '14px', color: '#333' }}>{text}</span>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '超标值',
|
|
|
|
|
dataIndex: 'value',
|
|
|
|
|
key: 'value',
|
|
|
|
|
align: 'center',
|
|
|
|
|
render: (text) => <span style={{ fontSize: '14px', color: '#333' }}>{text}</span>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '处理状态',
|
|
|
|
|
dataIndex: 'status',
|
|
|
|
|
key: 'status',
|
|
|
|
|
align: 'center',
|
|
|
|
|
render: (status) => {
|
|
|
|
|
let color = '#52c41a'; // 已处理 - 绿色
|
|
|
|
|
if (status === '误报') color = '#faad14'; // 误报 - 黄色
|
|
|
|
|
if (status === '待处理') color = '#ff4d4f'; // 待处理 - 红色
|
|
|
|
|
return <span style={{ color, fontSize: '14px', fontWeight: '500' }}>{status}</span>;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const handlePageChange = (page) => {
|
|
|
|
|
setCurrentPage(page);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div style={{ padding: '20px', backgroundColor: '#fff', marginTop: '20px', borderRadius: '4px', display: 'flex', flexDirection: 'column', height: '100%' }}>
|
|
|
|
|
<h4 style={{ marginBottom: '20px', color: '#006665', fontWeight: 'bold', fontSize: '16px' }}>• 环境告警记录</h4>
|
|
|
|
|
<Table
|
|
|
|
|
columns={columns}
|
|
|
|
|
dataSource={alertData}
|
|
|
|
|
pagination={false}
|
|
|
|
|
rowKey="key"
|
|
|
|
|
style={{ marginBottom: '10px' }}
|
|
|
|
|
// 设置表格样式以匹配图片
|
|
|
|
|
bordered
|
|
|
|
|
size="middle"
|
|
|
|
|
locale={{
|
|
|
|
|
emptyText: '暂无数据',
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 'auto' }}>
|
|
|
|
|
<div style={{ color: '#666', fontSize: '14px' }}>共 {totalRecords} 条 / 10 / page</div>
|
|
|
|
|
<Pagination
|
|
|
|
|
current={currentPage}
|
|
|
|
|
total={totalRecords}
|
|
|
|
|
pageSize={pageSize}
|
|
|
|
|
onChange={handlePageChange}
|
|
|
|
|
showSizeChanger={false}
|
|
|
|
|
showQuickJumper={false}
|
|
|
|
|
showTotal={false}
|
|
|
|
|
simple
|
|
|
|
|
// 自定义分页样式
|
|
|
|
|
itemRender={(current, type, originalElement) => {
|
|
|
|
|
if (type === 'page') {
|
|
|
|
|
return (
|
|
|
|
|
<a
|
|
|
|
|
href="#"
|
|
|
|
|
style={{
|
|
|
|
|
padding: '4px 11px',
|
|
|
|
|
margin: '0 4px',
|
|
|
|
|
borderRadius: '4px',
|
|
|
|
|
border: current === currentPage ? '1px solid #006665' : '1px solid #d9d9d9',
|
|
|
|
|
backgroundColor: current === currentPage ? '#006665' : '#fff',
|
|
|
|
|
color: current === currentPage ? '#fff' : '#333',
|
|
|
|
|
textDecoration: 'none',
|
|
|
|
|
fontSize: '14px',
|
|
|
|
|
}}
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
setCurrentPage(current);
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{current}
|
|
|
|
|
</a>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return originalElement;
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 主环境监测组件
|
|
|
|
|
const EnvironmentalMonitoring = () => {
|
|
|
|
|
return (
|
|
|
|
|
<div style={{ backgroundColor: '#fff', minHeight: '100%', padding: '20px' }}>
|
|
|
|
|
<Row gutter={[20, 0]}>
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
<SensorDashboard />
|
|
|
|
|
</Col>
|
|
|
|
|
<Col span={12} style={{ display: 'flex', flexDirection: 'column' }}>
|
|
|
|
|
<SensorConfig />
|
|
|
|
|
<AlertRecords />
|
|
|
|
|
</Col>
|
|
|
|
|
</Row>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default EnvironmentalMonitoring;
|