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.

865 lines
33 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 } from 'antd';
import { CheckCircleOutlined, ExportOutlined } from '@ant-design/icons';
import * as echarts from 'echarts';
import StandardTable from '@/components/StandardTable';
import styles from './RiskAssessment.less';
// import './RiskAssessment.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 RiskAssessment = () => {
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,
});
// 饼图初始化
useEffect(() => {
if (pieChartRef.current) {
const pieChart = echarts.init(pieChartRef.current);
const pieOption = {
color: ['#44BB5F', '#F8C541', '#A493FB', '#4B69F1', '#949FD0'],
legend: {
orient: 'vertical',
right: '10%',
top: 'center',
itemWidth: 8,
itemHeight: 8,
textStyle: {
fontSize: 12,
color: '#333'
}
},
series: [{
name: '设备状态',
type: 'pie',
radius: ['40%', '70%'],
center: ['35%', '50%'],
avoidLabelOverlap: false,
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '14',
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{ value: 480, name: '正常' },
{ value: 289, name: '故障' },
{ value: 200, name: '维修中' },
{ value: 150, name: '待验收' },
{ value: 161, name: '停用' }
]
}]
};
pieChart.setOption(pieOption);
// 响应式调整
const handlePieResize = () => {
if (pieChart && !pieChart.isDisposed()) {
pieChart.resize();
}
};
window.addEventListener('resize', handlePieResize);
return () => {
window.removeEventListener('resize', handlePieResize);
if (pieChart && !pieChart.isDisposed()) {
pieChart.dispose();
}
};
}
}, []);
// 故障类型饼图初始化
useEffect(() => {
if (faultPieChartRef.current) {
const faultPieChart = echarts.init(faultPieChartRef.current);
const faultPieOption = {
color: ['#FF3E48', '#FF8800', '#FFC403'],
legend: {
orient: 'vertical',
right: '10%',
top: 'center',
itemWidth: 8,
itemHeight: 8,
textStyle: {
fontSize: 12,
color: '#333'
}
},
series: [{
name: '设备故障类型',
type: 'pie',
radius: '70%',
center: ['35%', '50%'],
avoidLabelOverlap: false,
label: {
show: true,
position: 'outside',
formatter: '{b}: {c}',
fontSize: 12
},
emphasis: {
label: {
show: true,
fontSize: '14',
fontWeight: 'bold'
}
},
labelLine: {
show: true
},
data: [
{ value: 120, name: '紧急' },
{ value: 80, name: '重要' },
{ value: 60, name: '一般' }
]
}]
};
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 = {
color: ['#8979FF', '#3CC3DF'],
legend: {
// data: ['消防水泵1', '消防水泵2'],
top: "-3px",
// left: "center",
// itemGap: 40,
itemWidth: 20,
itemHeight: 8,
// icon: 'path://M902 472.7H747.9c-19.1-113.3-117.7-200-236.4-200s-217.3 86.7-236.4 200H119.7c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h155.5c19.1 113.3 117.7 200 236.4 200S728.9 666 748 552.7h154c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8z m-390.5 200c-88.2 0-160-71.8-160-160s71.8-160 160-160 160 71.8 160 160-71.8 160-160 160z',
textStyle: {
fontSize: 10
}
},
grid: {
left: '2%',
right: '4%',
bottom: '2%',
top: '12%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['9:00', '10:00', '11:00', '12:00', '13:00', '14:00', '15:00', '16:00', '17:00', '18:00', '19:00', '20:00'],
axisLabel: {
fontSize: 10
}
},
yAxis: {
type: 'value',
min: 0,
max: 30,
axisLabel: {
formatter: '{value}',
fontSize: 10
}
},
series: [
{
name: '消防水泵1',
type: 'line',
smooth: false,
lineStyle: {
width: 2,
color: '#8979FF'
},
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)' }
]
}
},
symbol: 'circle',
symbolSize: 4,
itemStyle: {
color: '#fff',
borderColor: '#8979FF',
borderWidth: 1
},
data: [12, 15, 18, 14, 16, 20, 22, 19, 17, 21, 23, 25]
},
{
name: '消防水泵2',
type: 'line',
smooth: false,
lineStyle: {
width: 2,
color: '#3CC3DF'
},
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)' }
]
}
},
symbol: 'circle',
symbolSize: 4,
itemStyle: {
color: '#fff',
borderColor: '#3CC3DF',
borderWidth: 1
},
data: [8, 11, 14, 10, 13, 17, 19, 16, 14, 18, 20, 22]
}
]
};
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 `0${number}`.slice(-2);
}
},
{
title: '设备编号',
dataIndex: 'deviceId',
key: 'deviceId',
width: 140,
},
{
title: '设备名称',
dataIndex: 'deviceName',
key: 'deviceName',
width: 110,
},
{
title: '型号规格',
dataIndex: 'modelSpec',
key: 'modelSpec',
width: 140,
},
{
title: '安装位置',
dataIndex: 'installLocation',
key: 'installLocation',
width: 200,
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 80,
render: (text) => {
const statusMap = {
'故障': { color: '#FF4D4F', bg: '#FFF2F0' },
'预警': { color: '#FAAD14', bg: '#FFF3E9' },
'正常': { color: '#44BB5F', bg: '#D8F7DE' }
};
const status = statusMap[text] || { color: '#333', bg: '#F5F5F5' };
return (
<span style={{
color: status.color,
backgroundColor: status.bg,
padding: '2px 8px',
borderRadius: '4px',
fontSize: '12px'
}}>
{text}
</span>
);
}
},
{
title: '最后维护',
dataIndex: 'lastMaintenance',
key: 'lastMaintenance',
width: 150,
},
{
title: '操作',
key: 'action',
width: 140,
render: (_, record) => (
<div>
<Button type="link" size="small" style={{
padding: '2px 8px',
fontSize: 12,
marginRight: 8,
border: '1px solid #E6E9FB',
backgroundColor: 'transparent',
borderRadius: '4px'
}}>
编辑
</Button>
<Button type="link" size="small" style={{
padding: '2px 8px',
fontSize: 12,
border: '1px solid #E6E9FB',
backgroundColor: 'transparent',
borderRadius: '4px'
}}>
详情
</Button>
</div>
),
},
];
// 模拟数据
const mockData = [
{
key: '1',
id: '001',
deviceId: 'HQ-XF-01-001',
deviceName: '消防水泵',
modelSpec: 'XBD5.0/30-125',
installLocation: '总部大楼1层大厅',
status: '故障',
lastMaintenance: '2025-09-10',
},
{
key: '2',
id: '002',
deviceId: 'HQ-XF-01-001',
deviceName: '消防水泵',
modelSpec: 'XBD5.0/30-125',
installLocation: '总部大楼3层 东区',
status: '预警',
lastMaintenance: '2025-09-10',
},
{
key: '3',
id: '003',
deviceId: 'HQ-XF-01-001',
deviceName: '消防水泵',
modelSpec: 'XBD5.0/30-125',
installLocation: '总部大楼地下一层',
status: '正常',
lastMaintenance: '2025-09-10',
},
{
key: '4',
id: '004',
deviceId: 'HQ-XF-01-001',
deviceName: '消防水泵',
modelSpec: 'XBD5.0/30-125',
installLocation: '总部大楼地下一层',
status: '故障',
lastMaintenance: '2025-09-10',
},
{
key: '5',
id: '005',
deviceId: 'HQ-XF-01-001',
deviceName: '消防水泵',
modelSpec: 'XBD5.0/30-125',
installLocation: '总部大楼地下一层',
status: '正常',
lastMaintenance: '2025-09-10',
},
{
key: '6',
id: '006',
deviceId: 'HQ-XF-01-001',
deviceName: '消防水泵',
modelSpec: 'XBD5.0/30-125',
installLocation: '总部大楼地下一层',
status: '预警',
lastMaintenance: '2025-09-10',
},
{
key: '7',
id: '007',
deviceId: 'HQ-XF-01-001',
deviceName: '消防水泵',
modelSpec: 'XBD5.0/30-125',
installLocation: '总部大楼地下一层',
status: '故障',
lastMaintenance: '2025-09-10',
},
{
key: '8',
id: '008',
deviceId: 'HQ-XF-01-001',
deviceName: '消防水泵',
modelSpec: 'XBD5.0/30-125',
installLocation: '总部大楼地下一层',
status: '正常',
lastMaintenance: '2025-09-10',
},
{
key: '9',
id: '009',
deviceId: 'HQ-XF-01-001',
deviceName: '消防水泵',
modelSpec: 'XBD5.0/30-125',
installLocation: '总部大楼地下一层',
status: '预警',
lastMaintenance: '2025-09-10',
},
{
key: '10',
id: '010',
deviceId: 'HQ-XF-01-001',
deviceName: '消防水泵',
modelSpec: 'XBD5.0/30-125',
installLocation: '总部大楼地下一层',
status: '故障',
lastMaintenance: '2025-09-10',
},
{
key: '11',
id: '011',
deviceId: 'HQ-XF-01-001',
deviceName: '消防水泵',
modelSpec: 'XBD5.0/30-125',
installLocation: '总部大楼地下一层',
status: '正常',
lastMaintenance: '2025-09-10',
},
{
key: '12',
id: '012',
deviceId: 'HQ-XF-01-001',
deviceName: '消防水泵',
modelSpec: 'XBD5.0/30-125',
installLocation: '总部大楼地下一层',
status: '预警',
lastMaintenance: '2025-09-10',
},
];
// 初始化数据
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,
}));
};
return (
<div className={styles.Rcontainer}>
{/* 第一个div - 高度20% */}
<div className={styles.RcontainerTop}>
<div className={styles.sectionContent}>
<div className={styles.blocksContainer}>
{/* 块1 */}
<div className={styles.blockItem}>
<div className={styles.blockLeft}>
<div className={styles.blockTitle}>设备总数</div>
<div className={styles.blockNumber}>1280</div>
</div>
<div className={styles.blockRight}>
<img src={eqicon1} alt="设备总数" className={styles.blockImage} />
</div>
</div>
{/* 块2 */}
<div className={styles.blockItem}>
<div className={styles.blockLeft}>
<div className={styles.blockTitle}>正常运行</div>
<div className={styles.blockNumber}>480</div>
</div>
<div className={styles.blockRight}>
<img src={eqicon2} alt="高风险设备" className={styles.blockImage} />
</div>
</div>
{/* 块3 */}
<div className={styles.blockItem}>
<div className={styles.blockLeft}>
<div className={styles.blockTitle}>需要维护</div>
<div className={styles.blockNumber}>347</div>
</div>
<div className={styles.blockRight}>
<img src={eqicon3} alt="今日预警次数" className={styles.blockImage} />
</div>
</div>
{/* 块4 */}
<div className={styles.blockItem}>
<div className={styles.blockLeft}>
<div className={styles.blockTitle}>故障设备</div>
<div className={styles.blockNumber}>289</div>
</div>
<div className={styles.blockRight}>
<img src={eqicon4} alt="未处理预警" className={styles.blockImage} />
</div>
</div>
</div>
</div>
</div>
<div className={styles.RcontainerMiddle}>
<div className={styles.sectionContent}>
<div className={styles.middleBlock1}>
<div className={styles.block1Header}>
<div className={styles.block1Title}>
<div className={styles.titleIcon}></div>
设备状态分布
</div>
<Segmented
className={styles.block1Segmented}
options={['月', '季', '年']}
onChange={(value) => {
console.log(value);
}}
/>
</div>
{/* 设备状态饼图 */}
<div className={styles.deviceStatusChart} ref={pieChartRef}>
</div>
</div>
<div className={styles.middleBlock12}>
<div className={styles.block1Header}>
<div className={styles.block1Title}>
<div className={styles.titleIcon}></div>
设备故障类型分布
</div>
<Select
className={styles.customSelect}
style={{
width: 120,
display: 'flex',
alignItems: 'center'
}}
defaultValue="全部区域"
options={[
{ value: '全部区域', label: '全部区域' },
{ value: '部分区域', label: '部分区域' },
]}
/>
</div>
{/* 设备故障类型饼图 */}
<div className={styles.deviceStatusChart} ref={faultPieChartRef}>
</div>
</div>
<div className={styles.middleBlock2}>
<div className={styles.middleBlock2Title}>
<div className={styles.titleLeft}>
<div className={styles.titleIcon}></div>
<div>设备运行参数</div>
</div>
<div className={styles.titleRight}>
<Select
style={{ width: 80 }}
defaultValue="今日"
options={[
{ value: '近3天', label: '近3天' },
{ value: '近7天', label: '近7天' },
]}
/>
</div>
</div>
<div className={styles.middleBlock2Chart} ref={chartRef}>
</div>
</div>
</div>
</div>
{/* 第三个div - 占满剩余位置 */}
<div className={styles.RcontainerBottom}>
<div className={styles.sectionContent}>
<div className={styles.leftBlock}>
{/* 第一行块 - 蓝色方块加标题 */}
<div className={styles.leftBlockTitle}>
<div className={styles.titleIcon}></div>
<div>预警信息</div>
</div>
<div className={styles.developmentContainer}>
<div className={styles.developmentBlock1}>
<div className={styles.leftContent}>
<div className={styles.mainText}>灭火器压力不足</div>
<div className={styles.subText}>2号楼3层 15分钟前</div>
</div>
<div className={styles.rightContent}>
<div className={styles.importantTag}>重要</div>
</div>
</div>
<div className={styles.developmentBlock1}>
<div className={styles.leftContent}>
<div className={styles.mainText}>烟雾探测器电池低电量</div>
<div className={styles.subText}>1号楼5层 1小时前</div>
</div>
<div className={styles.rightContent}>
<div className={styles.importantTag}>重要</div>
</div>
</div>
<div className={styles.developmentBlock1}>
<div className={styles.leftContent}>
<div className={styles.mainText}>消防栓维护到期</div>
<div className={styles.subText}>3号楼1层 2小时前</div>
</div>
<div className={styles.rightContent}>
<div className={styles.normalTag}>一般</div>
</div>
</div>
<div className={styles.developmentBlock1}>
<div className={styles.leftContent}>
<div className={styles.mainText}>应急照明故障</div>
<div className={styles.subText}>地下停车场 3小时前</div>
</div>
<div className={styles.rightContent}>
<div className={styles.normalTag}>一般</div>
</div>
</div>
</div>
</div>
<div className={styles.rightBlock}>
{/* 表格 */}
<div className={styles.tableHeader}>
<div className={styles.tableTitle}>
<div className={styles.titleIcon}></div>
<div>消防设备台账</div>
</div>
<div className={styles.tableActions}>
<button className={styles.actionButton} onClick={handleAddDevice}>
<span className={styles.buttonIcon}>+</span>
<span>新增设备</span>
</button>
<button className={styles.actionButton} onClick={handleExportData}>
<span className={styles.buttonIcon}><ExportOutlined /></span>
<span>导出数据</span>
</button>
</div>
</div>
{/* 表格 */}
<div className={styles.tableContainer}>
<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}`,
}}
// scroll={{ x: 1200 }}
/>
</div>
</div>
</div>
</div>
</div>
);
};
export default RiskAssessment;