diff --git a/config/routes.js b/config/routes.js index b2e1d63..dc7441b 100644 --- a/config/routes.js +++ b/config/routes.js @@ -30,6 +30,12 @@ export default [ name: 'basic', component: './business_basic/basic', }, + // 消防重点部位管理 + { + path: '/topnavbar00/business/firekeynotearea', + name: 'firekeynotearea', + component: './business_firekeynotearea/FireKeynoteArea', + }, // 安全管理基础信息 { path: '/topnavbar00/business/basicinformation', diff --git a/src/assets/business_firekeynotearea/eqicon1.png b/src/assets/business_firekeynotearea/eqicon1.png new file mode 100644 index 0000000..03c5873 Binary files /dev/null and b/src/assets/business_firekeynotearea/eqicon1.png differ diff --git a/src/assets/business_firekeynotearea/eqicon2.png b/src/assets/business_firekeynotearea/eqicon2.png new file mode 100644 index 0000000..2231524 Binary files /dev/null and b/src/assets/business_firekeynotearea/eqicon2.png differ diff --git a/src/assets/business_firekeynotearea/eqicon3.png b/src/assets/business_firekeynotearea/eqicon3.png new file mode 100644 index 0000000..d642214 Binary files /dev/null and b/src/assets/business_firekeynotearea/eqicon3.png differ diff --git a/src/assets/business_firekeynotearea/eqicon4.png b/src/assets/business_firekeynotearea/eqicon4.png new file mode 100644 index 0000000..e161b78 Binary files /dev/null and b/src/assets/business_firekeynotearea/eqicon4.png differ diff --git a/src/assets/business_firekeynotearea/eqicon5.png b/src/assets/business_firekeynotearea/eqicon5.png new file mode 100644 index 0000000..9f5c131 Binary files /dev/null and b/src/assets/business_firekeynotearea/eqicon5.png differ diff --git a/src/assets/business_firekeynotearea/eqicon6.png b/src/assets/business_firekeynotearea/eqicon6.png new file mode 100644 index 0000000..122d7ad Binary files /dev/null and b/src/assets/business_firekeynotearea/eqicon6.png differ diff --git a/src/assets/business_firekeynotearea/eqicon7.png b/src/assets/business_firekeynotearea/eqicon7.png new file mode 100644 index 0000000..d178645 Binary files /dev/null and b/src/assets/business_firekeynotearea/eqicon7.png differ diff --git a/src/assets/business_firekeynotearea/eqicon8.png b/src/assets/business_firekeynotearea/eqicon8.png new file mode 100644 index 0000000..68a2821 Binary files /dev/null and b/src/assets/business_firekeynotearea/eqicon8.png differ diff --git a/src/assets/business_firekeynotearea/keyparts_bg.png b/src/assets/business_firekeynotearea/keyparts_bg.png new file mode 100644 index 0000000..4e95bf2 Binary files /dev/null and b/src/assets/business_firekeynotearea/keyparts_bg.png differ diff --git a/src/assets/business_firekeynotearea/map.png b/src/assets/business_firekeynotearea/map.png new file mode 100644 index 0000000..cfdf182 Binary files /dev/null and b/src/assets/business_firekeynotearea/map.png differ diff --git a/src/assets/business_firekeynotearea/tree-child-node-selected.svg b/src/assets/business_firekeynotearea/tree-child-node-selected.svg new file mode 100644 index 0000000..9f3b982 --- /dev/null +++ b/src/assets/business_firekeynotearea/tree-child-node-selected.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/business_firekeynotearea/tree-child-node.svg b/src/assets/business_firekeynotearea/tree-child-node.svg new file mode 100644 index 0000000..614d428 --- /dev/null +++ b/src/assets/business_firekeynotearea/tree-child-node.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/assets/business_firekeynotearea/tree-parent-node.svg b/src/assets/business_firekeynotearea/tree-parent-node.svg new file mode 100644 index 0000000..6b6f47c --- /dev/null +++ b/src/assets/business_firekeynotearea/tree-parent-node.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/assets/img/fire_keynote_area.svg b/src/assets/img/fire_keynote_area.svg new file mode 100644 index 0000000..d8b1c5c --- /dev/null +++ b/src/assets/img/fire_keynote_area.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/pages/business_firekeynotearea/FireKeynoteArea.js b/src/pages/business_firekeynotearea/FireKeynoteArea.js new file mode 100644 index 0000000..e4ac73b --- /dev/null +++ b/src/pages/business_firekeynotearea/FireKeynoteArea.js @@ -0,0 +1,56 @@ +import React, { useState } from 'react'; +import { Card, Row, Col, Statistic, Progress, Button, Space } from 'antd'; +import styles from './FireKeynoteArea.less'; +import KeypartsBasicInformation from './components/KeypartsBasicInformation'; //重点部位基础信息管理 +import EmergencyPlanAssociation from './components/EmergencyPlanAssociation'; //应急预案关联管理 +import EmergencyDrillRecordAssociation from './components/EmergencyDrillRecordAssociation'; //应急演练记录关联管理 + +const FireKeynoteArea = () => { + const [activeModule, setActiveModule] = useState('1'); + + const handleModuleClick = (module) => { + setActiveModule(module) + } + + + const renderModule = () => { + switch (activeModule) { + case '1': + return ; + case '2': + return ; + case '3': + return ; + default: + return ; + } + }; + + + return ( +
+
+ + + +
+
+ {renderModule()} +
+
+ ); +}; + +export default FireKeynoteArea; diff --git a/src/pages/business_firekeynotearea/FireKeynoteArea.less b/src/pages/business_firekeynotearea/FireKeynoteArea.less new file mode 100644 index 0000000..b479169 --- /dev/null +++ b/src/pages/business_firekeynotearea/FireKeynoteArea.less @@ -0,0 +1,66 @@ +.container { + background-color: transparent; + width: 100%; + height: 89vh; + overflow: hidden; + display: flex; + flex-direction: column; + + .TopButton { + background-color: white; + width: 100%; + padding: 10px 30px; + display: flex; + gap: 24px; + margin-left: 6px; + + .TopButtonItem { + background-color: transparent !important; + color: #333333 !important; + font-family: 'PingFang SC', sans-serif !important; + font-weight: 500 !important; + font-size: 14px !important; + line-height: 100% !important; + border-radius: 8px !important; + padding: 6px 10px !important; + height: auto !important; + border: none !important; + box-shadow: none !important; + position: relative !important; + + &:hover { + color: #333333 !important; + border: none !important; + } + + &:focus { + color: #2E4CD4 !important; + border: none !important; + } + + &.active { + color: #2E4CD4 !important; + + &::after { + content: ''; + position: absolute; + bottom: -10px; + left: 0; + right: 0; + width: 100%; + height: 4px; + background-color: #2E4CD4; + border-radius: 0; + opacity: 1; + } + } + } + } + + .content { + // ======== 内容区域样式 ======== + flex: 1; // ======== 占据剩余空间 ======== + overflow-y: auto; // ======== 允许垂直滚动 ======== + padding: 0; // ======== 无内边距 ======== + } +} \ No newline at end of file diff --git a/src/pages/business_firekeynotearea/components/EmergencyDrillRecordAssociation.js b/src/pages/business_firekeynotearea/components/EmergencyDrillRecordAssociation.js new file mode 100644 index 0000000..e963cc2 --- /dev/null +++ b/src/pages/business_firekeynotearea/components/EmergencyDrillRecordAssociation.js @@ -0,0 +1,749 @@ + +import React, { useEffect, useRef, useState } from 'react'; +import { Card, Result, Select, Button, Segmented, Input } from 'antd'; +import { PlusOutlined } from '@ant-design/icons'; +import * as echarts from 'echarts'; +import StandardTable from '@/components/StandardTable'; +import styles from './EmergencyDrillRecordAssociation.less'; +import eqicon1 from '@/assets/business_firekeynotearea/eqicon1.png'; +import eqicon6 from '@/assets/business_firekeynotearea/eqicon6.png'; +import eqicon7 from '@/assets/business_firekeynotearea/eqicon7.png'; +import eqicon8 from '@/assets/business_firekeynotearea/eqicon8.png'; + +const EmergencyDrillRecordAssociation = () => { + 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: 13, + itemHeight: 4, + 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: 'drillId', + key: 'drillId', + width: 140, + }, + { + title: '演练时间', + dataIndex: 'drillTime', + key: 'drillTime', + width: 180, + }, + { + title: '演练类型', + dataIndex: 'drillType', + key: 'drillType', + width: 120, + }, + { + title: '关联部位', + dataIndex: 'relatedPart', + key: 'relatedPart', + width: 120, + }, + { + title: '部位名称', + dataIndex: 'partName', + key: 'partName', + width: 160, + }, + { + title: '演练次数', + dataIndex: 'drillCount', + key: 'drillCount', + width: 100, + }, + { + title: '参与人数', + dataIndex: 'participantCount', + key: 'participantCount', + width: 100, + }, + { + title: '效果评估', + dataIndex: 'effect', + key: 'effect', + width: 120, + render: (text) => { + let color = '#FFF3E9', fontColor = '#FF8800', label = text; + if (text === '优秀') { + color = '#D8F7DE'; + fontColor = '#44BB5F'; + } + return ( + {label} + ); + } + }, + { + title: '操作', + key: 'action', + width: 120, + render: (_, record) => ( + <> + 编辑 + 删除 + + ), + }, + ]; + + // 表格数据(同步图片) + const mockData = [ + { + key: '1', + drillId: 'YL202310001', + drillTime: '2025-09-10 14:23:45', + drillType: '实战演练', + relatedPart: '三楼东侧', + partName: '数据机房 A区', + drillCount: 5, + participantCount: 25, + effect: '良好', + }, + { + key: '2', + drillId: 'YL202310002', + drillTime: '2025-09-10 14:23:45', + drillType: '桌面推演', + relatedPart: '二楼西侧', + partName: 'B区厨房', + drillCount: 6, + participantCount: 18, + effect: '良好', + }, + { + key: '3', + drillId: 'YL202310003', + drillTime: '2025-09-10 14:23:45', + drillType: '模拟演练', + relatedPart: '地下一层', + partName: '数据中心机房', + drillCount: 3, + participantCount: 32, + effect: '优秀', + }, + ]; + + // 初始化数据 + 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 keypartsData = [ + { + name: '配电房', + location: '1楼 - 东区', + risk: '高风险', + riskColor: '#FFE0E2', + riskTextColor: '#FF3E48', + }, + { + name: '消防控制室', + location: '1楼 - 西区', + risk: '中风险', + riskColor: '#FFF8E2', + riskTextColor: '#FFC403', + }, + { + name: '档案室', + location: '2楼 - 中区', + risk: '低风险', + riskColor: '#DAF3FF', + riskTextColor: '#00AAFA', + } + ]; + const [selectedKeypartIdx, setSelectedKeypartIdx] = useState(0); + + return ( +
+ {/* 第一个div - 高度20% */} +
+
+
+ {/* 块1 */} +
+
+
演练记录总数
+
120
+
+
+ 演练记录总数 +
+
+ + {/* 块2 */} +
+
+
重点部位数量
+
32
+
+
+ 重点部位数量 +
+
+ + {/* 块3 */} +
+
+
参与演练人员
+
69
+
+
+ 参与演练人员 +
+
+ + {/* 块4 */} +
+
+
平均演练达标率
+
72%
+
+
+ 平均演练达标率 +
+
+ +
+
+
+ + +
+
+
+
+
+
+
重点部位分布图
+
+
+ 实时定位 +
+
+
+ {/* 地图图片 */} + 重点部位分布图 +
+
+
+
+
+
+ 重点部位类型分布 +
+ { + console.log(value); + }} + /> +
+ {/* 设备状态饼图 */} +
+
+ +
+ +
+
+
+
+ 重点部位列表 +
+
共 3 项
+
+ {/* 重点部位列表 */} +
+ {keypartsData.map((item, idx) => ( +
setSelectedKeypartIdx(idx)} + style={{ cursor: 'pointer' }} + > +
+
+ {item.name} +
+
{item.location}
+
+
+ {item.risk} +
+
+ ))} +
+ +
+ +
+
+ + {/* 第三个div - 占满剩余位置 */} +
+
+ +
+ {/* 表格 */} +
+
+
+
演练记录录入
+
+ +
+
+ {/* 搜索输入框 */} + +
+
+ {/* 下拉选择 */} + + {/* 新增按钮 */} + +
+
+
+ + {/* 表格 */} +
+ + `共 ${total} 条`, + }} + // scroll={{ x: 1200 }} + /> +
+
+
+
+
+ ); +}; + +export default EmergencyPlanAssociation; \ No newline at end of file diff --git a/src/pages/business_firekeynotearea/components/EmergencyPlanAssociation.less b/src/pages/business_firekeynotearea/components/EmergencyPlanAssociation.less new file mode 100644 index 0000000..053e850 --- /dev/null +++ b/src/pages/business_firekeynotearea/components/EmergencyPlanAssociation.less @@ -0,0 +1,500 @@ +// 重点部位列表样式 +.keypartsList { + display: flex; + flex-direction: column; + gap: 10px; + height: 200px; + margin: 35px 0 0 0; + overflow: auto; +} + +.keypartsCard { + background: #f9fbff; + border: 1px solid #ECEDFC; + border-radius: 4px; + padding: 8px 12px 6px 12px; + box-shadow: 0 1px 4px 0 rgba(46, 76, 212, 0.03); + transition: border-color 0.2s, box-shadow 0.2s; + position: relative; + // min-height: 44px; + max-width: 300px; + width: 100%; + margin: 0 auto; + display: flex; + justify-content: space-between; + align-items: center; +} + +.keypartsCardActive { + border-color: #75A7FF; + background: #F6F7FF; + box-shadow: 0 2px 8px 0 rgba(46, 76, 212, 0.10); +} + +.keypartsCardHeader { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 3px; +} + +.keypartsCardTitle { + font-size: 12px; + font-weight: 600; + color: #333; + margin-bottom: 8px; +} + +.keypartsRiskTag { + display: inline-block; + min-width: 40px; + height: 30px; + line-height: 30px; + text-align: center; + font-size: 13px; + font-weight: 500; + border-radius: 5px; + padding: 0 8px; + box-shadow: 0 1px 3px 0 rgba(46, 76, 212, 0.05); +} + +.keypartsCardSub { + font-size: 12px; + color: #666; + font-weight: 400; + letter-spacing: 0.5px; +} + +.Rcontainer { + padding: 8px 6px 0px 6px; + height: 100%; + display: flex; + flex-direction: column; + gap: 10px; + + // 第一个div - 高度20% + .RcontainerTop { + height: 16%; + // background-color: #fff; + border-radius: 4px; + display: flex; + flex-direction: column; + + .sectionContent { + height: 100%; + display: flex; + flex-direction: column; + // padding: 15px; + + .blocksContainer { + flex: 1; + display: flex; + gap: 10px; + height: 100%; + + .blockItem { + flex: 1; + height: 100%; + display: flex; + background: linear-gradient(170.5deg, #F5F7FF 6.87%, #FFFFFF 47.65%); + border-radius: 2px; + // border: 2px solid #FFFFFF; + + &.bgBlock1 { + background: url('@/assets/business_firekeynotearea/keyparts_bg.png') no-repeat center center, linear-gradient(170.5deg, #F5F7FF 6.87%, #FFFFFF 47.65%); + background-size: cover; + } + + &.bgBlock2 { + background: url('@/assets/business_firekeynotearea/keyparts_bg.png') no-repeat center center, linear-gradient(170.5deg, #F5F7FF 6.87%, #FFFFFF 47.65%); + background-size: cover; + } + + &.bgBlock3 { + background: url('@/assets/business_firekeynotearea/keyparts_bg.png') no-repeat center center, linear-gradient(170.5deg, #F5F7FF 6.87%, #FFFFFF 47.65%); + background-size: cover; + } + + &.bgBlock4 { + background: url('@/assets/business_firekeynotearea/keyparts_bg.png') no-repeat center center, linear-gradient(170.5deg, #F5F7FF 6.87%, #FFFFFF 47.65%); + background-size: cover; + } + + &.bgBlock5 { + background: url('@/assets/business_firekeynotearea/keyparts_bg.png') no-repeat center center, linear-gradient(170.5deg, #F5F7FF 6.87%, #FFFFFF 47.65%); + background-size: cover; + } + + .blockLeft { + width: 60%; + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + padding: 15px; + padding-left: 20px; + gap: 15px; + + .blockTitle { + font-family: PingFang SC; + font-weight: 400; + font-size: 12px; + color: #333333; + line-height: 1.2; + } + + .blockNumber { + font-family: PingFang SC; + font-weight: 700; + font-size: 24px; + color: #333333; + line-height: 1.2; + } + + .blockChange { + font-family: PingFang SC; + font-weight: 400; + font-size: 12px; + color: #1269FF; + line-height: 1.2; + display: flex; + align-items: center; + gap: 4px; + + .arrow { + font-size: 14px; + font-weight: bold; + } + + .checkIcon { + font-size: 16px; + color: #1269FF; + } + } + } + + .blockRight { + flex: 1; + height: 100%; + background-color: transparent; + border-radius: 0 4px 4px 0; + display: flex; + align-items: center; + justify-content: center; + + .blockImage { + // width: 80%; + height: 65%; + // height: 80%; + object-fit: contain; + margin-right: -5px; + } + } + } + } + } + } + + // 第二个div - 高度39% + .RcontainerMiddle { + height: 100%; + border-radius: 4px; + display: flex; + flex-direction: column; + + .sectionContent { + height: 100%; + display: flex; + display: flex; + gap: 10px; + height: 100%; + + + + .middleBlock1 { + // flex: 3; + width: 28%; + height: 100%; + background: #fff; + + border: 2px solid #fff; + // border-radius: 4px; + position: relative; + padding: 0px 10px 10px 2px; + font-family: PingFang SC; + font-size: 14px; + color: #333333; + + .block1Header { + position: absolute; + top: 5px; + left: 10px; + right: 10px; + display: flex; + justify-content: space-between; + align-items: center; + z-index: 10; + height: 28px; + line-height: 28px; + + .block1Title { + display: flex; + align-items: center; + gap: 8px; + font-weight: 500; + font-size: 14px; + color: #333333; + + .titleIcon { + width: 3px; + height: 14px; + background-color: #2E4CD4; + } + } + + .block1Segmented { + padding: 0; + margin: 0; + border: 1px solid #E3E3E3; + border-radius: 4px; + height: 28px; + + :global(.ant-segmented) { + padding: 0; + margin: 0; + height: 28px; + } + + :global(.ant-segmented-item) { + font-size: 12px; + // padding: 2px 8px; + height: 26px; + line-height: 26px; + display: flex; + align-items: center; + justify-content: center; + } + + :global(.ant-segmented-item-selected) { + background-color: #2E4CD4; + color: #fff; + } + } + } + + .deviceStatusChart { + position: absolute; + top: 35px; + left: 10px; + right: 10px; + bottom: 10px; + z-index: 10; + } + + // .block1Chart { + // width: 100%; + // height: 100%; + // margin-top: 20px; + + // .mapImage { + // margin-top: 7%; + // width: 90%; + // height: 77%; + // object-fit: cover; + // border-radius: 4px; + // display: block; + // margin-left: auto; + // margin-right: auto; + // } + // } + } + + .middleBlock2 { + flex: 6; + height: 100%; + // background: linear-gradient(170.5deg, #EBEFF4 6.87%, #FFFFFF 53.01%); + // border: 2px solid #fff; + background-color: #fff; + // border-radius: 4px; + display: flex; + flex-direction: column; + font-family: PingFang SC; + font-size: 14px; + color: #333333; + padding: 5px 10px 5px 10px; + + .middleBlock2Title { + display: flex; + justify-content: space-between; + align-items: center; + // margin-bottom: 10px; + + .titleLeft { + display: flex; + align-items: center; + gap: 8px; + font-weight: 500; + font-size: 14px; + color: #333333; + + .titleIcon { + width: 3px; + height: 14px; + background-color: #2E4CD4; + } + } + + .titleRight { + font-size: 12px; + width: 60px; + height: 20px; + line-height: 20px; + background-color: #E6E9FB; + color: #2E4CD4; + text-align: center; + border-radius: 2px; + } + } + + .middleBlock2Chart { + width: 100%; + height: 100%; + padding: 5px; + // min-height: 200px; + } + } + + } + } + + // 第三个div - 高度不超过45% + .RcontainerBottom { + height: 45%; // 限制高度不超过45% + max-height: 45%; // 确保最大高度不超过45% + // display: flex; + // flex-direction: column; + + .sectionContent { + // display: flex; + // flex-direction: row; + // gap: 10px; + padding: 0; + + .tableBlock { + width: 100%; + height: 100%; + background-color: #fff; + padding: 0; + display: flex; + flex-direction: column; + + .tableHeader { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 15px 5px 15px; + + .tableTitle { + display: flex; + align-items: center; + gap: 8px; + font-family: PingFang SC; + font-weight: 500; + font-size: 14px; + color: #333333; + + .titleIcon { + width: 3px; + height: 16px; + background-color: #2E4CD4; + } + } + + .tableActions { + display: flex; + gap: 8px; + margin-top: 5px; + + .searchInput { + // flex: 1; + // height: 40px; + // border: 1px solid #E3E6EB; + // border-radius: 6px; + // padding: 0 16px; + // font-size: 14px; + // color: #333; + // background: #fff; + // outline: none; + // box-shadow: none; + // transition: border-color 0.2s; + } + + .searchInput:focus { + border-color: #2E4CD4; + } + + .selectAll { + // height: 40px; + // border: 1px solid #E3E6EB; + // border-radius: 6px; + // background: #fff; + // font-size: 14px; + // color: #333; + // padding: 0 32px 0 16px; + // margin-left: 12px; + } + + .selectAll:focus { + border-color: #2E4CD4; + } + + .addBtn { + background: #2E4CD4; + margin-left: 15px; + } + + .addBtn:hover { + background: #1d3bb3; + } + } + } + + .tableContainer { + flex: 1; + overflow: hidden; + margin: 10px 15px 0 15px; // 上边距10px,左右边距15px + + :global(.ant-table) { + font-size: 12px; + } + + :global(.ant-table-thead > tr > th) { + background-color: #f5f5fa; + font-weight: 500; + font-size: 14px; + color: #333333; + border-bottom: 1px solid #f0f0f0; + padding: 8px 12px; + text-align: center; + } + + :global(.ant-table-tbody > tr > td) { + padding: 8px 12px; + border-bottom: 1px solid #f0f0f0; + text-align: center; + color: #666666; + } + + :global(.ant-table-tbody > tr:hover > td) { + background-color: #f5f5f5; + } + + :global(.ant-pagination) { + margin-top: 16px; + text-align: right; + } + } + } + } + } +} \ No newline at end of file diff --git a/src/pages/business_firekeynotearea/components/EvaluationReport.js b/src/pages/business_firekeynotearea/components/EvaluationReport.js new file mode 100644 index 0000000..1d61812 --- /dev/null +++ b/src/pages/business_firekeynotearea/components/EvaluationReport.js @@ -0,0 +1,922 @@ +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 './EvaluationReport.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 EvaluationReport = () => { + 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: 8, + 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: '20%', + containLabel: true + }, + xAxis: { + type: 'category', + data: ['灭火器', '消火栓', '报警器', '疏散灯', '排烟设备'], + axisLabel: { + fontSize: 12, + color: '#333', + interval: 0, + rotate: 0 + }, + axisLine: { + show: false + }, + axisTick: { + show: false + } + }, + yAxis: { + type: 'value', + min: 0, + max: 50, + interval: 10, + axisLabel: { + fontSize: 12, + color: '#666', + formatter: '{value}' + }, + axisLine: { + show: false + }, + axisTick: { + show: false + }, + splitLine: { + lineStyle: { + color: '#00001A26', + type: 'dashed' + } + } + }, + series: [{ + name: '使用次数', + type: 'bar', + barWidth: 27, + data: [35, 28, 42, 31, 38], + itemStyle: { + color: { + type: 'linear', + x: 0, + y: 0, + x2: 0, + y2: 1, + colorStops: [ + { offset: 0, color: '#199BFB' }, + { offset: 1, color: '#1373FA' } + ] + } + }, + emphasis: { + itemStyle: { + color: { + type: 'linear', + x: 0, + y: 0, + x2: 0, + y2: 1, + colorStops: [ + { offset: 0, color: '#0D7AE8' }, + { offset: 1, color: '#0F5BC7' } + ] + } + } + } + }], + legend: { + show: true, + top: '5%', + left: 'center', + itemWidth: 15, + itemHeight: 3, + textStyle: { + fontSize: 12, + color: '#333' + }, + data: [{ + name: '使用次数', + icon: 'rect', + itemStyle: { + color: '#4B69F1' + } + }] + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow' + }, + formatter: function (params) { + return `${params[0].name}
使用次数: ${params[0].value}`; + } + } + }; + + 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: 20000, + max: 30000, + interval: 2000, + axisLine: { + show: false + }, + axisTick: { + show: false + }, + axisLabel: { + color: '#666', + fontSize: 12, + formatter: '¥{value}' + }, + splitLine: { + lineStyle: { + color: '#00001A26', + type: 'dashed' + } + } + }, + series: [{ + name: '费用', + type: 'line', + data: [29000, 21000, 27500, 21900, 26000, 25000, 27000, 24000, 22300, 28000, 29000, 27000], + smooth: false, + symbol: 'circle', + symbolSize: 6, + lineStyle: { + color: '#1269FF', + width: 1 + }, + itemStyle: { + color: '#FFFFFF', + borderColor: '#1269FF', + borderWidth: 1 + }, + areaStyle: { + color: { + type: 'linear', + x: 0, + y: 0, + x2: 0, + y2: 1, + colorStops: [{ + offset: 0, + color: 'rgba(18, 105, 255, 0.3)' + }, { + offset: 1, + color: 'rgba(18, 105, 255, 0.05)' + }] + } + } + }] + }; + + 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: ['#3C7EFF', '#FF8800', '#FFC403', '#31BCFF'], + legend: { + orient: 'vertical', + right: '2%', + top: 'middle', + itemWidth: 14, + itemHeight: 5, + textStyle: { + fontSize: 10, + color: '#666' + } + }, + tooltip: { + trigger: 'item', + formatter: '{b}
{d}%' + }, + series: [ + { + name: '设备类型占比', + type: 'pie', + radius: '70%', + center: ['40%', '55%'], + avoidLabelOverlap: false, + itemStyle: { + borderRadius: 0, + borderColor: '#fff', + borderWidth: 1 + }, + label: { + show: false, + position: 'center' + }, + emphasis: { + label: { + show: false + } + }, + labelLine: { + show: false + }, + data: [ + { value: 25, name: '灭火器' }, + { value: 30, name: '消防栓' }, + { value: 20, name: '报警器' }, + { value: 25, name: '烟雾探测器' } + ] + } + ] + }; + + 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 || 8; + 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: 120, + }, + { + title: '安装位置', + dataIndex: 'installLocation', + key: 'installLocation', + width: 100, + }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + width: 100, + render: (text) => { + const statusMap = { + '报废': { color: '#FF3E48', bg: '#FFE0E2' }, + '待维修': { color: '#FF8800', bg: '#FFF3E9' }, + '已使用': { color: '#00AAFA', bg: '#DAF3FF' }, + '正常': { color: '#44BB5F', bg: '#D8F7DE' } + }; + const status = statusMap[text] || { color: '#333', bg: '#F5F5F5' }; + return ( + + {text} + + ); + } + }, + { + title: '最后维护时间', + dataIndex: 'lastMaintenance', + key: 'lastMaintenance', + width: 150, + }, + { + title: '操作', + key: 'action', + width: 140, + render: (_, record) => ( +
+ + +
+ ), + }, + ]; + + // 模拟数据 + const mockData = [ + { + key: '1', + id: '001', + deviceId: 'HQ-XF-01-001', + deviceName: '干粉灭火器', + modelSpec: '灭火设备', + installLocation: '1层大厅', + status: '报废', + lastMaintenance: '2025-09-10', + }, + { + key: '2', + id: '002', + deviceId: 'HQ-XF-02-015', + deviceName: '室内消火栓', + modelSpec: '灭火设备', + installLocation: '3层东区', + status: '已使用', + lastMaintenance: '2025-09-10', + }, + { + key: '3', + id: '003', + deviceId: 'HQ-XF-03-007', + deviceName: '火警报警器', + modelSpec: '报警设备', + installLocation: '地下一层', + status: '正常', + lastMaintenance: '2025-09-10', + }, + { + key: '4', + id: '004', + deviceId: 'HQ-XF-03-008', + deviceName: '火警报警器', + modelSpec: '报警设备', + installLocation: '地下一层', + status: '待维修', + lastMaintenance: '2025-09-10', + }, + { + key: '5', + id: '005', + deviceId: 'HQ-XF-01-009', + deviceName: '干粉灭火器', + modelSpec: '灭火设备', + installLocation: '地下一层', + status: '报废', + lastMaintenance: '2025-09-10', + }, + { + key: '6', + id: '006', + deviceId: 'HQ-XF-01-010', + deviceName: '室内消火栓', + modelSpec: '灭火设备', + installLocation: '地下一层', + status: '已使用', + lastMaintenance: '2025-09-10', + }, + { + key: '7', + id: '007', + deviceId: 'HQ-XF-01-011', + deviceName: '火警报警器', + modelSpec: '报警设备', + installLocation: '地下一层', + status: '待维修', + lastMaintenance: '2025-09-10', + }, + { + key: '8', + id: '008', + deviceId: 'HQ-XF-01-012', + deviceName: '火警报警器', + modelSpec: '报警设备', + installLocation: '地下一层', + status: '正常', + lastMaintenance: '2025-09-10', + }, + { + key: '9', + id: '009', + deviceId: 'HQ-XF-01-013', + deviceName: '干粉灭火器', + modelSpec: '灭火设备', + installLocation: '地下一层', + status: '已使用', + lastMaintenance: '2025-09-10', + }, + { + key: '10', + id: '010', + deviceId: 'HQ-XF-01-014', + deviceName: '室内消火栓', + modelSpec: '灭火设备', + installLocation: '地下一层', + status: '待维修', + lastMaintenance: '2025-09-10', + }, + { + key: '11', + id: '011', + deviceId: 'HQ-XF-01-015', + deviceName: '火警报警器', + modelSpec: '报警设备', + installLocation: '地下一层', + status: '正常', + lastMaintenance: '2025-09-10', + }, + { + key: '12', + id: '012', + deviceId: 'HQ-XF-01-016', + deviceName: '火警报警器', + modelSpec: '报警设备', + installLocation: '地下一层', + status: '已使用', + lastMaintenance: '2025-09-10', + }, + { + key: '13', + id: '013', + deviceId: 'HQ-XF-01-017', + deviceName: '干粉灭火器', + modelSpec: '灭火设备', + installLocation: '2层西区', + status: '报废', + lastMaintenance: '2024-08-15', + }, + { + key: '14', + id: '014', + deviceId: 'HQ-XF-02-018', + deviceName: '室内消火栓', + modelSpec: '灭火设备', + installLocation: '4层南区', + status: '报废', + lastMaintenance: '2024-07-20', + }, + { + key: '15', + id: '015', + deviceId: 'HQ-XF-03-019', + deviceName: '火警报警器', + modelSpec: '报警设备', + installLocation: '地下二层', + status: '报废', + lastMaintenance: '2024-06-10', + }, + { + key: '16', + id: '016', + deviceId: 'HQ-XF-01-020', + deviceName: '干粉灭火器', + modelSpec: '灭火设备', + installLocation: '5层北区', + status: '报废', + lastMaintenance: '2024-05-05', + }, + ]; + + // 初始化数据 + 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 ( +
+ {/* 第1个div - 高度39% */} +
+
+
+
+
+
+ 设备使用频率分析 +
+
+ {/* 设备状态饼图 */} +
+
+
+ +
+
+
+
+ 近一年维护费用趋势 +
+
+ + {/* 维护费用趋势折线图 */} +
+
+
+ +
+
+
+
+
设备类型占比
+
+
+
+
+
+
+
+ + {/* 第2个div - 占满剩余位置 */} +
+
+
+
+
+
+
+
近期维护提醒
+
+
+
+
+
SH-MHQ-023-C 干粉灭火器
+
位置: 4楼办公区丨维护类型: 季度检查
+
负责人: 张三
+
+
+
3天后到期
+
+
+
+
+
SH-XHS-045-D 室内消火栓
+
位置: 2楼东侧走廊丨维护类型: 水压测试
+
负责人: 李四
+
+
+
8天后到期
+
+
+
+
+
+
+
+
维护任务进度
+
+
+ {/* 进度条区域 */} +
+ +
月度维护计划
+ + +
季度维护计划
+ + + +
年度维护计划
+ + + + {/* 警告提示框 */} +
+ + 本月有5项维护任务即将到期 +
+ +
+
+
+
+
+ +
+ {/* 表格 */} +
+
+
+
消防设施与器材列表
+
+
+ + + {/* 操作按钮 */} +
+
+ } + /> +
+
+ + +
+
+ + {/* 表格 */} +
+ + `共 ${total} 条`, + }} + /> +
+
+
+
+
+ ); +}; + +export default EvaluationReport; diff --git a/src/pages/business_firekeynotearea/components/EvaluationReport.less b/src/pages/business_firekeynotearea/components/EvaluationReport.less new file mode 100644 index 0000000..e62182a --- /dev/null +++ b/src/pages/business_firekeynotearea/components/EvaluationReport.less @@ -0,0 +1,558 @@ +.Econtainer { + padding: 8px 6px 0px 6px; + height: 100%; + display: flex; + flex-direction: column; + gap: 10px; + + // 第二个div - 高度35% + .EcontainerMiddle { + // height: 400px; + min-height: 35%; + border-radius: 4px; + display: flex; + flex-direction: column; + + .sectionContent { + height: 100%; + display: flex; + display: flex; + gap: 10px; + height: 100%; + + .middleBlock1 { + width: 30%; + height: 100%; + background: #fff; + border: 2px solid #fff; + position: relative; + padding: 0px 10px 10px 2px; + font-family: PingFang SC; + font-size: 14px; + color: #333333; + + .block1Header { + position: absolute; + top: 5px; + left: 10px; + right: 10px; + display: flex; + justify-content: space-between; + align-items: center; + z-index: 10; + + .block1Title { + display: flex; + align-items: center; + gap: 8px; + font-weight: 500; + font-size: 14px; + margin-top: 5px; + color: #333333; + + .titleIcon { + width: 3px; + height: 14px; + background-color: #2E4CD4; + } + } + + } + + .deviceStatusChart { + position: absolute; + top: 10px; + left: 10px; + right: 10px; + z-index: 10; + min-height: 100%; + } + } + + .middleBlock12 { + flex: 1; + height: 100%; + background-color: #fff; + display: flex; + flex-direction: column; + font-family: PingFang SC; + font-size: 14px; + color: #333333; + padding: 5px 10px 5px 10px; + position: relative; + + .block1Header { + position: absolute; + top: 5px; + left: 10px; + right: 10px; + display: flex; + justify-content: space-between; + align-items: center; + z-index: 10; + + .block1Title { + display: flex; + align-items: center; + gap: 8px; + font-weight: 500; + font-size: 14px; + margin-top: 5px; + color: #333333; + + .titleIcon { + width: 3px; + height: 14px; + background-color: #2E4CD4; + } + } + + } + + .deviceStatusChart { + position: absolute; + top: 10px; + left: 10px; + right: 10px; + // bottom: 10px; + z-index: 10; + } + } + + .middleBlock12 { + width: 45%; + height: 100%; + background-color: #fff; + display: flex; + flex-direction: column; + font-family: PingFang SC; + font-size: 14px; + color: #333333; + padding: 5px 10px 5px 10px; + position: relative; + + .block1Header { + position: absolute; + top: 5px; + left: 10px; + right: 10px; + display: flex; + justify-content: space-between; + align-items: center; + z-index: 10; + + .block1Title { + display: flex; + align-items: center; + gap: 8px; + font-weight: 500; + font-size: 14px; + color: #333333; + + .titleIcon { + width: 3px; + height: 14px; + background-color: #2E4CD4; + } + } + } + + .deviceStatusChart { + position: absolute; + top: 10px; + left: 10px; + right: 10px; + // bottom: 10px; + min-height: 100%; + z-index: 10; + } + } + + .middleBlock2 { + // flex: 1; + width: calc(100% - 75% - 15px); + height: 100%; + // background: linear-gradient(170.5deg, #EBEFF4 6.87%, #FFFFFF 53.01%); + // border: 2px solid #fff; + background-color: #fff; + // border-radius: 4px; + display: flex; + flex-direction: column; + font-family: PingFang SC; + font-size: 14px; + color: #333333; + padding: 5px 10px 5px 10px; + + .middleBlock2Title { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 5px; + + .titleLeft { + display: flex; + align-items: center; + gap: 8px; + font-weight: 500; + font-size: 14px; + color: #333333; + + .titleIcon { + width: 3px; + height: 14px; + background-color: #2E4CD4; + } + } + + } + + .middleBlock2Chart { + width: 100%; + height: 100%; + } + } + } + } + + // 第三个div - 占满剩余位置 + .EcontainerBottom { + display: flex; + flex-direction: column; + flex-shrink: 0; + + .sectionContent { + display: flex; + flex-direction: row; + flex: 1; + gap: 10px; + padding: 0; + + .leftBlock { + width: 30%; + flex-shrink: 0; + height: 100%; + padding: 0; + display: flex; + flex-direction: column; + gap: 10px; + + .leftBlockTitle { + display: flex; + align-items: center; + gap: 8px; + font-family: PingFang SC; + font-weight: 500; + font-size: 14px; + color: #333333; + margin-bottom: 10px; + + .titleIcon { + width: 3px; + height: 16px; + background-color: #2E4CD4; + } + } + + .maintenanceStack { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + gap: 10px; + + .maintenanceSection { + width: 100%; + height: 50%; + background: #FFF; + border-radius: 4px; + display: flex; + flex-direction: column; + padding: 12px 14px; + + .maintenanceTitle { + display: flex; + align-items: center; + gap: 8px; + font-family: PingFang SC; + font-weight: 500; + font-size: 14px; + color: #333333; + margin-bottom: 8px; + } + + .titleIcon { + width: 3px; + height: 16px; + background-color: #2E4CD4; + } + + .maintenanceContent { + flex: 1; + width: 100%; + } + + .maintenanceContent1 { + flex: 1; + width: 100%; + display: flex; + flex-direction: column; + gap: 8px; + margin-top: 8px; + .maintenanceItem { + display: flex; + align-items: center; + justify-content: space-between; + background-color: #F1F7FF; + border-radius: 4px; + padding: 16px 16px; + + .maintenanceLeft { + flex: 1; + display: flex; + flex-direction: column; + gap: 4px; + + .maintenanceText1 { + font-size: 14px; + font-weight: 500; + color: #333333; + font-family: PingFang SC; + } + + .maintenanceText2 { + font-size: 12px; + color: #666666; + font-family: PingFang SC; + } + + .maintenanceText3 { + font-size: 12px; + color: #666666; + font-family: PingFang SC; + } + } + + .maintenanceRight { + flex: 0 0 auto; + display: flex; + align-items: center; + justify-content: center; + + .maintenanceStatus { + font-size: 12px; + color: #FF3E48; + font-weight: 500; + font-family: PingFang SC; + background-color: #FFE0E2; + padding: 4px 8px; + border-radius: 4px; + // border: 1px solid #FFE0E2; + } + } + + .maintenanceRight2 { + flex: 0 0 auto; + display: flex; + align-items: center; + justify-content: center; + + .maintenanceStatus { + font-size: 12px; + color: #FF8800; + font-weight: 500; + font-family: PingFang SC; + background-color: #FFF3E9; + padding: 4px 8px; + border-radius: 4px; + // padding-right: 2px; + } + } + } + } + + .maintenanceContent2 { + flex: 1; + width: 100%; + display: flex; + flex-direction: column; + gap: 15px; + padding: 8px 0; + + .warningBox { + display: flex; + align-items: center; + gap: 8px; + background-color: #FFF3CD; + border: 1px solid #F4E3AE; + border-radius: 4px; + padding: 8px 12px; + // margin-bottom: 8px; + // margin-top: 10px; + + .warningIcon { + color: #8C6C0B; + font-size: 14px; + } + + .warningText { + color: #8C6C0B; + font-size: 12px; + font-family: PingFang SC; + font-weight: 400; + } + } + + .progressSection { + width: 100%; + display: flex; + flex-direction: column; + // gap: 12px; + padding: 0px 12px 12px 12px; + + + .progressLabel { + font-size: 12px; + color: #666666; + font-family: PingFang SC; + font-weight: 400; + + } + + // 自定义进度条样式 + :global(.ant-progress) { + .ant-progress-bg { + background: linear-gradient(90deg, #2E4CD4 0%, #4B69F1 100%); + } + + .ant-progress-text { + color: #2E4CD4; + font-weight: 500; + } + } + + } + } + } + } + } + + .rightBlock { + width: calc(100% - 28% - 10px); + height: 100%; + background-color: #fff; + padding: 0; + display: flex; + flex-direction: column; + + .tableHeader { + display: flex; + justify-content: space-between; + align-items: center; + padding: 11px 15px 5px 15px; + + .tableTitle { + display: flex; + align-items: center; + gap: 8px; + font-family: PingFang SC; + font-weight: 500; + font-size: 14px; + color: #333333; + + .titleIcon { + width: 3px; + height: 16px; + background-color: #2E4CD4; + } + } + } + + .tableActions { + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; + margin-top: 5px; + padding: 0px 15px; + + .leftActions { + display: flex; + align-items: center; + } + + .rightActions { + display: flex; + gap: 8px; + align-items: center; + } + + .actionButton { + display: flex; + align-items: center; + gap: 4px; + height: 28px; + border: 1px solid #DFE4F6; + border-radius: 4px; + color: #2E4CD4; + font-weight: 500; + font-size: 12px; + padding: 0px 8px; + background: transparent; + cursor: pointer; + transition: all 0.2s ease; + + &:hover { + background-color: #f0f2ff; + border-color: #2E4CD4; + } + + &:active { + background-color: #e6ebff; + } + + .buttonIcon { + font-size: 14px; + font-weight: bold; + } + } + } + + .tableContainer { + flex: 1; + overflow: hidden; + margin: 10px 15px 0 15px; // 上边距10px,左右边距15px + + :global(.ant-table) { + font-size: 12px; + } + + :global(.ant-table-thead > tr > th) { + background-color: #f5f5fa; + font-weight: 500; + font-size: 14px; + color: #333333; + border-bottom: 1px solid #f0f0f0; + padding: 8px 12px; + text-align: center; + } + + :global(.ant-table-tbody > tr > td) { + padding: 8px 12px; + border-bottom: 1px solid #f0f0f0; + text-align: center; + color: #666666; + } + + :global(.ant-table-tbody > tr:hover > td) { + background-color: #f5f5f5; + } + + :global(.ant-pagination) { + margin-top: 16px; + text-align: right; + } + } + } + } + } +} \ No newline at end of file diff --git a/src/pages/business_firekeynotearea/components/KeypartsBasicInformation.js b/src/pages/business_firekeynotearea/components/KeypartsBasicInformation.js new file mode 100644 index 0000000..07779ce --- /dev/null +++ b/src/pages/business_firekeynotearea/components/KeypartsBasicInformation.js @@ -0,0 +1,429 @@ + +import React, { useEffect, useRef, useState } from 'react'; +import { Button, Input, Select, Tree } from 'antd'; +import { ExportOutlined, PlusOutlined } from '@ant-design/icons'; +import StandardTable from '@/components/StandardTable'; +import styles from './KeypartsBasicInformation.less'; +import eqicon1 from '@/assets/business_firekeynotearea/eqicon1.png'; +import eqicon2 from '@/assets/business_firekeynotearea/eqicon2.png'; +import eqicon3 from '@/assets/business_firekeynotearea/eqicon3.png'; +import eqicon4 from '@/assets/business_firekeynotearea/eqicon4.png'; +import eqicon5 from '@/assets/business_firekeynotearea/eqicon5.png'; + +const KeypartsBasicInformation = () => { + 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 columns = [ + { + title: '编号', + dataIndex: 'id', + key: 'id', + width: 60, + fixed: 'left', + 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: 'name', + key: 'name', + width: 120, + }, + { + title: '所属建筑', + dataIndex: 'building', + key: 'building', + width: 120, + }, + { + title: '类型', + dataIndex: 'type', + key: 'type', + width: 100, + }, + { + title: '标签', + dataIndex: 'tags', + key: 'tags', + width: 200, + render: (tags) => ( + <> + {tags.map((tag, idx) => { + const tagColorMap = { + '电气风险': { color: '#F9EBBC', fontColor: '#333333' }, + '重要设备': { color: '#CDDFFF', fontColor: '#333333' }, + '易燃易爆': { color: '#F8C6C6', fontColor: '#333333' }, + '人员密集': { color: '#B6E1F6', fontColor: '#333333' }, + '关键设施': { color: '#FDDBB5', fontColor: '#333333' }, + '高风险区': { color: '#F8C6C6', fontColor: '#333333' }, + }; + const style = tagColorMap[tag] || { color: '#eee', fontColor: '#666' }; + return ( + {tag} + ); + })} + + ) + }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + width: 80, + render: (text) => { + const statusMap = { + '故障': { color: '#FF3E48', bg: '#FFE0E2' }, + '预警': { color: '#FF8800', bg: '#FFF3E9' }, + '正常': { color: '#44BB5F', bg: '#D8F7DE' } + }; + const status = statusMap[text] || { color: '#333', bg: '#F5F5F5' }; + return ( + + {text} + + ); + } + }, + { + title: '最后更新', + dataIndex: 'lastUpdate', + key: 'lastUpdate', + width: 120, + }, + { + title: '操作', + key: 'action', + width: 100, + align: 'center', + fixed: 'right', + render: (_, record) => ( + <> + + + + ), + }, + ]; + + // 模拟数据 + const mockData = [ + { + key: '1', + id: '001', + name: '1号办公楼', + building: '1号办公楼', + type: '配电室', + tags: ['电气风险', '重要设备'], + status: '故障', + lastUpdate: '2025-09-10', + }, + { + key: '2', + id: '002', + name: 'B区厨房', + building: '员工餐厅', + type: '厨房', + tags: ['易燃易爆', '人员密集'], + status: '预警', + lastUpdate: '2025-09-10', + }, + { + key: '3', + id: '003', + name: '数据中心机房', + building: '信息中心', + type: '数据机房', + tags: ['关键设施', '重要设备'], + status: '正常', + lastUpdate: '2025-09-10', + }, + { + key: '4', + id: '004', + name: '化学品仓库', + building: '实验楼', + type: '仓库', + tags: ['易燃易爆', '高风险区'], + status: '正常', + lastUpdate: '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 - 高度20% */} +
+
+
+ {/* 块1 */} +
+
+
总重点部位数
+
1820
+
+
+ 总重点部位数 +
+
+ + {/* 块2 */} +
+
+
易燃易爆场所
+
386
+
+
+ 易燃易爆场所 +
+
+ + {/* 块3 */} +
+
+
人员密集区域
+
269
+
+
+ 人员密集区域 +
+
+ + {/* 块4 */} +
+
+
重要设备房
+
412
+
+
+ 重要设备房 +
+
+ + {/* 块5 */} +
+
+
物资储存区
+
290
+
+
+ 物资储存区 +
+
+ +
+
+
+ + {/* 第二个div - 占满剩余位置 */} +
+
+
+ + {/* 表格头部 */} +
+
+
+
消防设施与器材列表
+
+
+ + {/* 搜索与操作栏 */} +
+
+ {/* 搜索输入框 */} + +
+
+ {/* 下拉选择 */} +