新增:环境监测

main
yupeng 1 month ago
parent 94e38d0b7e
commit 99443b6d1e

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

@ -0,0 +1,50 @@
.dashboardContainer {
background-color: #f5f5f5;
border-radius: 8px;
padding: 15px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
// 传感器仪表盘样式增强
.dashboardContainer svg {
width: 100%;
max-width: 120px;
margin: 0 auto;
}
// 环境监测页面整体样式
.environmentalMonitoring {
padding: 20px;
background-color: #fff;
}
// 标题样式
.sectionTitle {
color: #006665;
font-weight: bold;
margin-bottom: 20px;
font-size: 16px;
}
// 表格样式增强
.alertTable {
margin-top: 10px;
}
// 分页样式增强
.pagination {
margin-top: 15px;
display: flex;
justify-content: flex-end;
}
// 响应式调整
@media (max-width: 768px) {
.dashboardContainer {
margin-bottom: 15px;
}
.sectionTitle {
font-size: 14px;
}
}

@ -2,6 +2,7 @@ import { Button, Col, Drawer, Form, Input, Menu, Pagination, Row, Select, Space,
import { useEffect, useRef, useState } from "react";
import styles from './InspectionTaskPlan.less'
import { Title } from "@/pages/inspectiontasks/InspectionTasks";
import EnvironmentalMonitoring from "../EnvironmentalMonitoring/EnvironmentalMonitoring";
import btnImg1 from '@/assets/img/planBtn1.png'
import btnImg2 from '@/assets/img/planBtn2.png'
import btnImg3 from '@/assets/img/planBtn3.png'
@ -2060,8 +2061,8 @@ const SmartInspectionContent = () => {
);
case '环境监测':
return (
<div style={{ padding: 40, textAlign: 'center', color: '#666' }}>
<h3>该模块正在开发中...</h3>
<div style={{ padding: 0 }}>
<EnvironmentalMonitoring />
</div>
);
default:

Loading…
Cancel
Save