You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

857 lines
31 KiB
JavaScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import React, { useEffect, useRef, useState } from 'react';
import { Card, Result, Select, Button, Segmented, Progress, Input } from 'antd';
import { CheckCircleOutlined, ExportOutlined, HeartFilled, LineHeightOutlined, ExclamationCircleOutlined, SearchOutlined } from '@ant-design/icons';
import * as echarts from 'echarts';
import StandardTable from '@/components/StandardTable';
import styles from './DataAnalysisWarning.less';
import img1 from '@/assets/safe_majorHazard/online_monitoring/img1.png';
import img2 from '@/assets/safe_majorHazard/online_monitoring/img2.png';
import img3 from '@/assets/safe_majorHazard/online_monitoring/img3.png';
import map1 from '@/assets/safe_majorHazard/online_monitoring/map.png';
import risk1 from '@/assets/safe_majorHazard/online_monitoring/risk1.png';
import risk2 from '@/assets/safe_majorHazard/online_monitoring/risk2.png';
import risk3 from '@/assets/safe_majorHazard/online_monitoring/risk3.png';
import eqicon1 from '@/assets/business_basic/eqicon1.png';
import eqicon2 from '@/assets/business_basic/eqicon2.png';
import eqicon3 from '@/assets/business_basic/eqicon3.png';
import eqicon4 from '@/assets/business_basic/eqicon4.png';
const DataAnalysisWarning = () => {
const chartRef = useRef(null);
const pieChartRef = useRef(null);
const faultPieChartRef = useRef(null);
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const [selectedRows, setSelectedRows] = useState([]);
const [loading, setLoading] = useState(false);
const [dataSource, setDataSource] = useState([]);
const [pagination, setPagination] = useState({
current: 1,
pageSize: 5,
total: 0,
});
const [searchText, setSearchText] = useState('');
// 柱状图初始化
useEffect(() => {
if (pieChartRef.current) {
const barChart = echarts.init(pieChartRef.current);
const barOption = {
grid: {
left: '5%',
right: '5%',
bottom: '10%',
top: '18%',
containLabel: true
},
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
axisLabel: {
fontSize: 12,
color: '#333',
interval: 0,
rotate: 0
},
axisLine: {
show: false
},
axisTick: {
show: false
}
},
yAxis: {
type: 'value',
min: 0,
max: 35,
interval: 5,
axisLabel: {
fontSize: 12,
color: '#666',
formatter: '{value}'
},
axisLine: {
show: false
},
axisTick: {
show: false
},
splitLine: {
lineStyle: {
color: '#00001A26',
type: 'dashed'
}
}
},
series: [
{
name: '火灾报警',
type: 'bar',
stack: 'total',
barWidth: 20,
data: [12, 8, 15, 10, 18, 14, 16, 13, 11, 17, 19, 15],
itemStyle: {
color: '#8979FF'
}
},
{
name: '故障报警',
type: 'bar',
stack: 'total',
barWidth: 20,
data: [6, 9, 7, 12, 8, 11, 9, 14, 10, 7, 8, 6],
itemStyle: {
color: '#FF928A'
}
},
{
name: '误报',
type: 'bar',
stack: 'total',
barWidth: 20,
data: [3, 5, 4, 7, 6, 8, 5, 9, 7, 4, 6, 5],
itemStyle: {
color: '#3CC3DF'
}
}
],
legend: {
show: true,
top: '8%',
left: 'center',
itemWidth: 8,
itemHeight: 8,
textStyle: {
fontSize: 12,
color: '#333'
},
data: [
{
name: '火灾报警',
icon: 'rect',
itemStyle: {
color: '#8979FF'
}
},
{
name: '故障报警',
icon: 'rect',
itemStyle: {
color: '#FF928A'
}
},
{
name: '误报',
icon: 'rect',
itemStyle: {
color: '#3CC3DF'
}
}
]
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
formatter: function (params) {
let result = `${params[0].name}<br/>`;
params.forEach(param => {
result += `${param.seriesName}: ${param.value}<br/>`;
});
return result;
}
}
};
barChart.setOption(barOption);
// 响应式调整
const handleBarResize = () => {
if (barChart && !barChart.isDisposed()) {
barChart.resize();
}
};
window.addEventListener('resize', handleBarResize);
return () => {
window.removeEventListener('resize', handleBarResize);
if (barChart && !barChart.isDisposed()) {
barChart.dispose();
}
};
}
}, []);
// 设备运行状态趋势折线图初始化
useEffect(() => {
if (faultPieChartRef.current) {
const faultPieChart = echarts.init(faultPieChartRef.current);
const faultPieOption = {
legend: {
show: true,
top: '5%',
left: 'center',
itemWidth: 20,
itemHeight: 8,
textStyle: {
color: '#333',
fontSize: 12
}
},
grid: {
left: '5%',
right: '5%',
bottom: '10%',
top: '20%',
containLabel: true
},
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
axisLine: {
lineStyle: {
color: '#E5E5E5'
}
},
axisTick: {
show: false
},
axisLabel: {
color: '#666',
fontSize: 12,
interval: 0
}
},
yAxis: {
type: 'value',
min: 0,
max: 100,
interval: 20,
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
color: '#666',
fontSize: 12,
formatter: '{value}%'
},
splitLine: {
lineStyle: {
color: '#00001A26',
type: 'dashed'
}
}
},
series: [
{
name: '正常运行率',
type: 'line',
data: [85, 78, 92, 88, 95, 90, 87, 93, 89, 91, 86, 88],
smooth: false,
symbol: 'circle',
symbolSize: 6,
lineStyle: {
color: '#3CC3DF',
width: 1
},
itemStyle: {
color: '#FFF',
borderColor: '#3CC3DF',
borderWidth: 1
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0,
color: 'rgba(60, 195, 223, 0.3)'
}, {
offset: 1,
color: 'rgba(60, 195, 223, 0.05)'
}]
}
}
},
{
name: '故障率',
type: 'line',
data: [15, 22, 8, 12, 5, 10, 13, 7, 11, 9, 14, 12],
smooth: false,
symbol: 'circle',
symbolSize: 6,
lineStyle: {
color: '#8979FF',
width: 1
},
itemStyle: {
color: '#fff',
borderColor: '#8979FF',
borderWidth: 1
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0,
color: 'rgba(137, 121, 255, 0.3)'
}, {
offset: 1,
color: 'rgba(137, 121, 255, 0.05)'
}]
}
}
}
],
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
},
formatter: function (params) {
let result = `${params[0].name}<br/>`;
params.forEach(param => {
result += `${param.seriesName}: ${param.value}%<br/>`;
});
return result;
}
}
};
faultPieChart.setOption(faultPieOption);
// 响应式调整
const handleFaultPieResize = () => {
if (faultPieChart && !faultPieChart.isDisposed()) {
faultPieChart.resize();
}
};
window.addEventListener('resize', handleFaultPieResize);
return () => {
window.removeEventListener('resize', handleFaultPieResize);
if (faultPieChart && !faultPieChart.isDisposed()) {
faultPieChart.dispose();
}
};
}
}, []);
useEffect(() => {
if (chartRef.current) {
const chart = echarts.init(chartRef.current);
// 强制初始化时调整大小
setTimeout(() => {
if (chart && !chart.isDisposed()) {
chart.resize();
}
}, 100);
const option = {
tooltip: {
trigger: 'item'
},
legend: {
bottom: '4%',
left: 'center',
itemWidth: 16,
itemHeight: 5,
textStyle: {
fontSize: 12,
}
},
series: [
{
name: '设备故障原因',
type: 'pie',
radius: ['20%', '65%'],
center: ['50%', '40%'],
avoidLabelOverlap: false,
padAngle: 5,
itemStyle: {
borderRadius: 8,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
// emphasis: {
// label: {
// show: true,
// fontSize: 40,
// fontWeight: 'bold'
// }
// },
labelLine: {
show: false
},
data: [
{ value: 1048, name: '环境因素(粉尘)', itemStyle: { color: '#44BB5F' } },
{ value: 735, name: '环境因素(湿度)', itemStyle: { color: '#F8C541' } },
{ value: 580, name: '设备故障', itemStyle: { color: '#A493FB' } },
{ value: 484, name: '施工干扰', itemStyle: { color: '#4B69F1' } },
{ value: 300, name: '其他', itemStyle: { color: '#949FD0' } }
]
}
]
};
chart.setOption(option);
// 响应式调整 - 使用多种方式监听容器尺寸变化
let resizeTimer = null;
const handleResize = () => {
// 防抖处理避免频繁调用resize
if (resizeTimer) {
clearTimeout(resizeTimer);
}
resizeTimer = setTimeout(() => {
if (chart && !chart.isDisposed()) {
chart.resize();
}
}, 50); // 减少延迟时间
};
// 监听窗口大小变化
window.addEventListener('resize', handleResize);
// 监听容器尺寸变化(解决菜单栏伸缩时的自适应问题)
let resizeObserver = null;
if (window.ResizeObserver) {
resizeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
// 使用requestAnimationFrame确保在下一帧执行
requestAnimationFrame(() => {
handleResize();
});
}
});
resizeObserver.observe(chartRef.current);
}
// 额外监听父容器的尺寸变化
const parentContainer = chartRef.current?.parentElement;
let parentObserver = null;
if (parentContainer && window.ResizeObserver) {
parentObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
requestAnimationFrame(() => {
handleResize();
});
}
});
parentObserver.observe(parentContainer);
}
// 使用MutationObserver监听DOM结构变化菜单展开收起时
const mutationObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' &&
(mutation.attributeName === 'class' || mutation.attributeName === 'style')) {
// 延迟执行确保DOM更新完成
setTimeout(() => {
handleResize();
}, 200);
}
});
});
// 监听整个页面的class和style变化
mutationObserver.observe(document.body, {
attributes: true,
attributeFilter: ['class', 'style'],
subtree: true
});
return () => {
window.removeEventListener('resize', handleResize);
if (resizeObserver) {
resizeObserver.disconnect();
}
if (parentObserver) {
parentObserver.disconnect();
}
if (mutationObserver) {
mutationObserver.disconnect();
}
if (resizeTimer) {
clearTimeout(resizeTimer);
}
if (chart && !chart.isDisposed()) {
chart.dispose();
}
};
}
}, []);
// 表格列定义
const columns = [
{
title: '序号',
dataIndex: 'id',
key: 'id',
width: 60,
render: (text, record, index) => {
const page = pagination.current || 1;
const pageSize = pagination.pageSize || 5;
const number = (page - 1) * pageSize + index + 1;
return number;
}
},
{
title: '区域',
dataIndex: 'area',
key: 'area',
width: 120,
},
{
title: '火灾报警',
dataIndex: 'fireAlarm',
key: 'fireAlarm',
width: 100,
},
{
title: '故障报警',
dataIndex: 'faultAlarm',
key: 'faultAlarm',
width: 100,
},
{
title: '误报',
dataIndex: 'falseAlarm',
key: 'falseAlarm',
width: 100,
},
{
title: '总计',
dataIndex: 'total',
key: 'total',
width: 100,
},
{
title: '同比变化',
dataIndex: 'yearOverYear',
key: 'yearOverYear',
width: 120,
render: (text) => {
const isPositive = text.includes('↑');
const color = isPositive ? '#FF3E48' : '#44BB5F';
const icon = isPositive ? '↑' : '↓';
return (
<span style={{ color }}>
{text}
</span>
);
}
},
];
// 模拟数据
const mockData = [
{
key: '1',
id: '1',
area: 'A栋',
fireAlarm: 3,
faultAlarm: 7,
falseAlarm: 10,
total: 23,
yearOverYear: '↓ 12%',
},
{
key: '2',
id: '2',
area: 'B栋',
fireAlarm: 2,
faultAlarm: 9,
falseAlarm: 6,
total: 18,
yearOverYear: '↑ 8%',
},
{
key: '3',
id: '3',
area: 'C栋',
fireAlarm: 1,
faultAlarm: 5,
falseAlarm: 8,
total: 16,
yearOverYear: '↓ 15%',
},
{
key: '4',
id: '4',
area: 'D栋',
fireAlarm: 0,
faultAlarm: 2,
falseAlarm: 7,
total: 11,
yearOverYear: '↓ 16%',
},
{
key: '5',
id: '5',
area: 'E栋',
fireAlarm: 4,
faultAlarm: 6,
falseAlarm: 5,
total: 15,
yearOverYear: '↓ 5%',
},
{
key: '6',
id: '6',
area: 'F栋',
fireAlarm: 2,
faultAlarm: 8,
falseAlarm: 9,
total: 19,
yearOverYear: '↑ 3%',
},
{
key: '7',
id: '7',
area: 'G栋',
fireAlarm: 1,
faultAlarm: 4,
falseAlarm: 6,
total: 11,
yearOverYear: '↓ 8%',
},
{
key: '8',
id: '8',
area: 'H栋',
fireAlarm: 3,
faultAlarm: 3,
falseAlarm: 4,
total: 10,
yearOverYear: '↓ 20%',
},
{
key: '9',
id: '9',
area: 'I栋',
fireAlarm: 0,
faultAlarm: 1,
falseAlarm: 3,
total: 4,
yearOverYear: '↓ 25%',
},
{
key: '10',
id: '10',
area: 'J栋',
fireAlarm: 2,
faultAlarm: 5,
falseAlarm: 7,
total: 14,
yearOverYear: '↑ 2%',
},
];
// 初始化数据
useEffect(() => {
setPagination(prev => ({ ...prev, total: mockData.length }));
}, []);
// 根据分页获取当前页数据
const getCurrentPageData = () => {
const { current, pageSize } = pagination;
const startIndex = (current - 1) * pageSize;
const endIndex = startIndex + pageSize;
return mockData.slice(startIndex, endIndex);
};
// 表格选择变化
const onSelectChange = (newSelectedRowKeys, newSelectedRows) => {
setSelectedRowKeys(newSelectedRowKeys);
setSelectedRows(newSelectedRows);
};
// 新增设备按钮点击事件
const handleAddDevice = () => {
console.log('新增设备');
// TODO: 实现新增设备逻辑
};
// 导出数据按钮点击事件
const handleExportData = () => {
console.log('导出数据');
// TODO: 实现导出数据逻辑
};
// 分页变化处理
const handleTableChange = (pagination) => {
setPagination(prev => ({
...prev,
current: pagination.current,
pageSize: pagination.pageSize,
}));
};
// 搜索处理
const handleSearchChange = (e) => {
setSearchText(e.target.value);
console.log('搜索:', e.target.value);
// TODO: 实现搜索逻辑,根据设备名称、编号等筛选数据
};
return (
<div className={styles.analysisContainer}>
<div className={styles.analysisContainerMiddle}>
<div className={styles.analysisSectionContent}>
<div className={styles.analysisMiddleBlock2}>
<div className={styles.analysisMiddleBlock2Title}>
<div className={styles.analysisTitleLeft}>
<div className={styles.analysisTitleIcon}></div>
<div>误报原因分析</div>
</div>
</div>
<div className={styles.analysisMiddleBlock2Chart} ref={chartRef}>
</div>
</div>
<div className={styles.analysisMiddleBlock1}>
<div className={styles.analysisBlock1Header}>
<div className={styles.analysisBlock1Title}>
<div className={styles.analysisTitleIcon}></div>
报表统计生成
</div>
</div>
<div className={styles.analysisDeviceStatusChart} ref={pieChartRef}>
</div>
</div>
<div className={styles.analysisMiddleBlock12}>
<div className={styles.analysisBlock1Header}>
<div className={styles.analysisBlock1Title}>
<div className={styles.analysisTitleIcon}></div>
设备运行状态趋势
</div>
</div>
<div className={styles.analysisDeviceStatusChart} ref={faultPieChartRef}>
</div>
</div>
</div>
</div>
{/* 底部区域 */}
<div className={styles.analysisBottom}>
{/* 左侧维护提醒 */}
<div className={styles.analysisMaintenanceSection}>
<div className={styles.analysisMaintenanceTitle}>
<div className={styles.analysisTitleIcon}></div>
<div>实时预警信息</div>
</div>
<div className={styles.analysisMaintenanceContent}>
<div className={styles.analysisMaintenanceItem1}>
<div className={styles.analysisMaintenanceLeft}>
<div className={styles.analysisMaintenanceText1}>电路线路过载预警</div>
<div className={styles.analysisMaintenanceText2}>B栋3层配电室丨15分钟前</div>
<div className={styles.analysisMaintenanceText3}>电流持续上升已超过正常阈值</div>
</div>
<div className={styles.analysisMaintenanceRight}>
<div className={styles.analysisMaintenanceStatus}>紧急</div>
</div>
</div>
<div className={styles.analysisMaintenanceItem2}>
<div className={styles.analysisMaintenanceLeft}>
<div className={styles.analysisMaintenanceText1}>2号防排烟风机异常</div>
<div className={styles.analysisMaintenanceText2}>地下车库丨2小时前</div>
<div className={styles.analysisMaintenanceText3}>震动频率异常, 建议尽快检修</div>
</div>
<div className={styles.analysisMaintenanceRight2}>
<div className={styles.analysisMaintenanceStatus}>警告</div>
</div>
</div>
<div className={styles.analysisMaintenanceItem3}>
<div className={styles.analysisMaintenanceLeft}>
<div className={styles.analysisMaintenanceText1}>消防水泵预测维护</div>
<div className={styles.analysisMaintenanceText2}>A栋水泵房丨今天</div>
<div className={styles.analysisMaintenanceText3}>预计15天后需要维护请提前安排</div>
</div>
<div className={styles.analysisMaintenanceRight2}>
<div className={styles.analysisMaintenanceStatus2}>提示</div>
</div>
</div>
<div className={styles.analysisMaintenanceItem4}>
<div className={styles.analysisMaintenanceLeft}>
<div className={styles.analysisMaintenanceText1}>消防水泵预测维护</div>
<div className={styles.analysisMaintenanceText2}>A栋水泵房丨今天</div>
<div className={styles.analysisMaintenanceText3}>预计15天后需要维护请提前安排</div>
</div>
<div className={styles.analysisMaintenanceRight2}>
<div className={styles.analysisMaintenanceStatus2}>提示</div>
</div>
</div>
</div>
</div>
{/* 右侧表格 */}
<div className={styles.analysisTableSection}>
<div className={styles.analysisTableHeader}>
<div className={styles.analysisTableTitle}>
<div className={styles.analysisTitleIcon}></div>
<div>月度报警统计</div>
</div>
<div className={styles.analysisTableActions}>
<button className={styles.analysisActionButton} onClick={handleExportData}>
<span className={styles.analysisButtonIcon}><ExportOutlined /></span>
<span>导出数据</span>
</button>
</div>
</div>
<div className={styles.analysisTableContainer}>
<StandardTable
columns={columns}
data={{
list: getCurrentPageData(),
pagination: pagination
}}
loading={loading}
selectionType="checkbox"
onSelectRow={onSelectChange}
onChange={handleTableChange}
pagination={{
...pagination,
showSizeChanger: false,
showQuickJumper: true,
showTotal: (total, range) =>
`${total}`,
}}
/>
</div>
</div>
</div>
</div>
);
};
export default DataAnalysisWarning;