数据采集页面

main
wangyunfei888 4 days ago
parent 290733b67c
commit 4f05da26e7

@ -1,3 +1,4 @@
# dq-enms
能源管理系统

@ -24,19 +24,19 @@ export default [
name: 'business',
component: './nav_system_content/SystemContentList',
routes: [
// 基础信息管理
// 基础数据管理
{
path: '/topnavbar00/business/basic',
name: 'basic',
component: './business_basic/basic',
path: '/topnavbar00/business/data',
name: 'basic_data',
component: './business_data/basic',
},
// 消防重点部位管理
// 数据采集
{
path: '/topnavbar00/business/firekeynotearea',
name: 'firekeynotearea',
component: './business_firekeynotearea/FireKeynoteArea',
path: '/topnavbar00/business/dataCollection',
name: 'dataCollection',
component: './business_dataCollection/basic',
},
// 消防检测报警
// 能源监测
{
path: '/topnavbar00/business/fireWarning',
name: 'fireWarning',

@ -0,0 +1,4 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 0C8.02219 0 6.08879 0.58649 4.4443 1.6853C2.79981 2.78412 1.51809 4.3459 0.761209 6.17317C0.00433281 8.00043 -0.193701 10.0111 0.192152 11.9509C0.578004 13.8907 1.53041 15.6725 2.92894 17.0711C4.32746 18.4696 6.10929 19.422 8.0491 19.8079C9.98891 20.1937 11.9996 19.9957 13.8268 19.2388C15.6541 18.4819 17.2159 17.2002 18.3147 15.5557C19.4135 13.9112 20 11.9778 20 10C20 7.34784 18.9464 4.8043 17.0711 2.92893C15.1957 1.05357 12.6522 0 10 0ZM10 18C8.41775 18 6.87104 17.5308 5.55544 16.6518C4.23985 15.7727 3.21447 14.5233 2.60897 13.0615C2.00347 11.5997 1.84504 9.99113 2.15372 8.43928C2.4624 6.88743 3.22433 5.46197 4.34315 4.34315C5.46197 3.22433 6.88743 2.4624 8.43928 2.15372C9.99113 1.84504 11.5997 2.00346 13.0615 2.60896C14.5233 3.21446 15.7727 4.23985 16.6518 5.55544C17.5308 6.87103 18 8.41775 18 10C18 12.1217 17.1572 14.1566 15.6569 15.6569C14.1566 17.1571 12.1217 18 10 18Z" fill="white"/>
<path d="M12.8692 4H7.78922L6.19922 10.63H8.98922L7.04922 16H8.12922L13.7992 8.83H10.4692L12.8692 4Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.7551 1.98145C8.73746 1.83952 8.50412 1.83952 8.48646 1.98145C7.90193 6.69329 4.91346 7.66393 4.91346 10.9383C4.91346 12.9617 6.61055 14.6019 8.62117 14.6019C10.631 14.6019 12.3281 12.9617 12.3281 10.9383C12.3281 7.66393 9.33963 6.69329 8.7551 1.98145ZM9.38656 13.7406C9.17605 13.7406 9.00648 13.5706 9.00648 13.3605C9.00648 13.1504 9.17686 12.9804 9.38656 12.9804C9.91018 12.9804 10.336 12.5562 10.336 12.0338C10.336 11.8237 10.5059 11.6537 10.7161 11.6537C10.7907 11.6536 10.8637 11.6754 10.926 11.7164C10.9883 11.7575 11.0372 11.816 11.0664 11.8846C11.0866 11.9318 11.097 11.9825 11.0969 12.0338C11.0969 12.9752 10.3295 13.7406 9.38656 13.7406ZM13.4113 3.5676C13.4051 3.51721 13.3222 3.51721 13.316 3.5676C13.1084 5.24053 12.0474 5.58514 12.0474 6.74768C12.0474 7.4661 12.6499 8.04844 13.3638 8.04844C14.0774 8.04844 14.6799 7.4661 14.6799 6.74768C14.6799 5.58514 13.6189 5.24051 13.4113 3.5676ZM13.6355 7.74262C13.6178 7.74265 13.6003 7.73917 13.5839 7.7324C13.5675 7.72562 13.5526 7.71568 13.5401 7.70314C13.5275 7.69061 13.5176 7.67572 13.5108 7.65933C13.5041 7.64295 13.5006 7.62539 13.5006 7.60766C13.5006 7.57187 13.5148 7.53755 13.5401 7.51225C13.5654 7.48694 13.5998 7.47272 13.6355 7.47272C13.8214 7.47272 13.9726 7.32211 13.9726 7.13665C13.9726 7.11892 13.9761 7.10137 13.9829 7.08499C13.9897 7.06862 13.9996 7.05374 14.0121 7.0412C14.0247 7.02867 14.0395 7.01873 14.0559 7.01195C14.0723 7.00518 14.0899 7.00169 14.1076 7.0017C14.1253 7.00167 14.1429 7.00513 14.1593 7.0119C14.1757 7.01866 14.1906 7.0286 14.2032 7.04113C14.2157 7.05366 14.2257 7.06855 14.2325 7.08494C14.2393 7.10133 14.2428 7.1189 14.2428 7.13665C14.2428 7.47086 13.9704 7.74262 13.6355 7.74262ZM17.5454 15.434C17.2585 15.3126 16.9275 15.4467 16.8061 15.7336C16.4846 16.4931 15.7385 16.9969 14.8996 16.9969C14.0498 16.9969 13.2962 16.4801 12.9822 15.7064C12.8651 15.4177 12.5362 15.2787 12.2475 15.3958C12.1422 15.4384 12.0523 15.5121 11.9898 15.6069C11.9596 15.6454 11.9324 15.6867 11.9124 15.7338C11.5915 16.4931 10.8455 16.9969 10.0066 16.9969C9.17674 16.9969 8.44148 16.5025 8.11463 15.759C8.0691 15.5958 7.95297 15.4542 7.78363 15.3861C7.49459 15.2699 7.16613 15.4101 7.04998 15.6991C6.73742 16.4769 5.98197 16.9969 5.12936 16.9969C4.27059 16.9969 3.51057 16.4689 3.20266 15.6829C3.08904 15.3928 2.7618 15.2498 2.47176 15.3634C2.1817 15.477 2.03867 15.8043 2.15229 16.0943C2.62818 17.3091 3.80252 18.125 5.12936 18.125C6.09225 18.125 6.97381 17.6942 7.56771 16.9937C8.16168 17.6944 9.04367 18.125 10.0066 18.125C10.973 18.125 11.8594 17.6917 12.4536 16.9851C13.0475 17.6909 13.9328 18.125 14.8996 18.125C16.1956 18.125 17.3484 17.3465 17.8449 16.1733C17.9664 15.8864 17.8322 15.5555 17.5454 15.434Z" fill="#DADCFF"/>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

@ -0,0 +1,4 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 10C0 12.6522 1.05357 15.1957 2.92893 17.0711C4.8043 18.9464 7.34784 20 10 20C12.6522 20 15.1957 18.9464 17.0711 17.0711C18.9464 15.1957 20 12.6522 20 10C20 7.34784 18.9464 4.8043 17.0711 2.92893C15.1957 1.05357 12.6522 0 10 0C7.34784 0 4.8043 1.05357 2.92893 2.92893C1.05357 4.8043 0 7.34784 0 10Z" fill="#DADCFF"/>
<path d="M10.8926 16C10.8926 16 14.4406 15.7055 14.9996 12C14.9996 9.5035 12.9461 8.089 12.9461 8.089C12.9461 8.089 12.5236 8.844 12.0531 9.0665C12.0571 9.1005 13.3241 5.688 9.10705 4C9.10205 4.0635 9.29705 4.896 9.28555 5.2445C9.27405 5.593 8.98105 7.1665 7.41055 8.3555C5.84055 9.5445 5.02555 10.401 5.00055 12C4.97455 13.5985 5.85655 15.322 8.92855 16C8.92605 16.0185 7.68055 14.6275 7.67855 13.0665C7.67655 11.5055 9.17755 10.4725 9.91055 10.0445C9.89455 10.0395 9.83055 10.3155 9.82105 10.7555C9.81205 11.1955 10.0896 11.9785 10.6246 12.4445C11.1601 12.91 11.6146 13.6645 11.6071 14.133C11.5996 14.602 11.2321 15.55 10.8926 16Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,10 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_5176_22848)">
<path d="M6.38984 6.59905C10.0064 6.59905 12.5287 5.63793 12.5287 4.45329V3.16545C12.5287 1.98057 10.0064 1.01953 6.38984 1.01953C2.77328 1.01953 0.660156 1.98057 0.660156 3.16545V4.45329C0.660156 5.63793 2.77328 6.59905 6.38984 6.59905ZM0.950316 10.4473C0.754876 10.6516 0.660156 10.869 0.660156 11.0944V12.8203C0.660156 14.0118 2.78056 14.9772 6.41 14.9772C6.5912 14.9772 6.76976 14.9741 6.94632 14.9688V11.9482C6.76304 11.9539 6.57792 11.9572 6.3904 11.9572C3.44648 11.9572 1.78736 11.3224 0.950316 10.4473ZM0.950316 6.25385C0.754876 6.45913 0.660156 6.67681 0.660156 6.90281V8.63281C0.660156 9.82649 2.78056 10.7953 6.41 10.7953C6.59096 10.7953 6.76968 10.7925 6.94632 10.7872V7.76393C6.76616 7.76641 6.58112 7.76769 6.3904 7.76769C3.44648 7.76769 1.78736 7.13129 0.950316 6.25385ZM12.6365 8.33257C12.6365 9.22169 11.916 9.94225 11.0271 9.94225C10.1382 9.94225 9.4176 9.22169 9.4176 8.33257C9.4176 7.44385 10.1382 6.72329 11.0271 6.72329C11.9162 6.72329 12.6365 7.44385 12.6365 8.33257ZM7.2624 13.0051C7.2624 13.2079 7.30235 13.4087 7.37997 13.5961C7.45759 13.7835 7.57136 13.9538 7.71479 14.0972C7.85821 14.2406 8.02848 14.3544 8.21588 14.432C8.40327 14.5097 8.60412 14.5496 8.80696 14.5496C9.00979 14.5496 9.21064 14.5097 9.39803 14.432C9.58543 14.3544 9.7557 14.2406 9.89913 14.0972C10.0426 13.9538 10.1563 13.7835 10.2339 13.5961C10.3116 13.4087 10.3515 13.2079 10.3515 13.0051C10.3515 12.5954 10.1888 12.2025 9.89913 11.9129C9.60946 11.6232 9.2166 11.4605 8.80696 11.4605C8.39731 11.4605 8.00445 11.6232 7.71479 11.9129C7.42513 12.2025 7.2624 12.5954 7.2624 13.0051ZM15.7484 12.2399C15.7484 13.0379 15.1015 13.6843 14.3038 13.6843C13.5059 13.6843 12.859 13.0379 12.859 12.2399C12.859 11.4419 13.5059 10.7953 14.3038 10.7953C15.1015 10.7953 15.7484 11.4419 15.7484 12.2399ZM10.6817 8.33481L11.0345 8.55481L8.25952 13.0054L7.90672 12.7854L10.6817 8.33481ZM15.3175 12.4931L10.6559 8.21681L10.8928 7.95857L15.5544 12.2349L15.3175 12.4931Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_5176_22848">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.4361 3.02605C1.4361 3.23442 1.60581 3.44075 1.93553 3.63326C2.26526 3.82577 2.74854 4.00069 3.35779 4.14803C3.96704 4.29537 4.69033 4.41224 5.48636 4.49198C6.28238 4.57172 7.13556 4.61276 7.99717 4.61276C8.85878 4.61276 9.71195 4.57172 10.508 4.49198C11.304 4.41224 12.0273 4.29537 12.6365 4.14803C13.2458 4.00069 13.7291 3.82577 14.0588 3.63326C14.3885 3.44075 14.5582 3.23442 14.5582 3.02605C14.5582 2.81768 14.3885 2.61135 14.0588 2.41884C13.7291 2.22633 13.2458 2.05141 12.6365 1.90407C12.0273 1.75673 11.304 1.63986 10.508 1.56012C9.71195 1.48038 8.85878 1.43934 7.99717 1.43934C7.13556 1.43934 6.28238 1.48038 5.48636 1.56012C4.69033 1.63986 3.96704 1.75673 3.35779 1.90407C2.74854 2.05141 2.26526 2.22633 1.93553 2.41884C1.60581 2.61135 1.4361 2.81768 1.4361 3.02605ZM7.99717 4.62174C6.2437 4.62174 4.5967 4.45627 3.35632 4.15612C2.11337 3.85468 1.42969 3.45448 1.42969 3.02605C1.42969 2.59762 2.11337 2.19742 3.35632 1.89726C4.5967 1.59839 6.2437 1.43164 7.99717 1.43164C9.75063 1.43164 11.3976 1.59711 12.638 1.89726C13.881 2.1987 14.5646 2.59891 14.5646 3.02605C14.5646 3.45319 13.881 3.85468 12.638 4.15484C11.3976 4.45499 9.75063 4.62174 7.99717 4.62174ZM7.99717 1.44575C4.38249 1.44575 1.44251 2.15509 1.44251 3.02605C1.44251 3.89829 4.38249 4.60763 7.99717 4.60763C11.6118 4.60763 14.5531 3.89829 14.5531 3.02605C14.5531 2.15509 11.6118 1.44575 7.99717 1.44575ZM13.1639 5.42472C11.962 5.76592 10.0944 5.98526 7.99717 5.98526C5.89994 5.98526 4.03103 5.76592 2.83041 5.42472C1.95816 5.67228 1.43738 5.98526 1.43738 6.32518C1.43738 7.13329 4.37479 7.78619 7.99845 7.78619C11.6221 7.78619 14.5608 7.13201 14.5608 6.32518C14.5595 5.98526 14.0387 5.67228 13.1639 5.42472ZM7.99717 7.79389C6.2437 7.79389 4.5967 7.64124 3.35632 7.36546C2.11337 7.0884 1.42969 6.71898 1.42969 6.32518C1.42969 5.99296 1.91327 5.6787 2.82913 5.41831H2.83169C4.0849 5.7749 5.96792 5.97885 7.99717 5.97885C10.0264 5.97885 11.9082 5.7749 13.1626 5.41831H13.1652C14.0798 5.6787 14.5646 5.99296 14.5646 6.32518C14.5646 6.71898 13.881 7.08711 12.638 7.36418C11.3976 7.64124 9.75063 7.79389 7.99717 7.79389ZM2.83041 5.43113C2.38916 5.55684 2.04539 5.69665 1.80937 5.84673C1.56565 6.00066 1.44251 6.16228 1.44251 6.32518C1.44251 7.12688 4.38377 7.78106 7.99845 7.78106C11.6131 7.78106 14.5531 7.12816 14.5531 6.32518C14.5531 6.161 14.43 6.00066 14.1862 5.84545C13.9502 5.69537 13.6065 5.55684 13.1652 5.42985C11.9094 5.78644 10.0277 5.9904 7.99717 5.9904C5.96664 5.9904 4.0849 5.78644 2.83041 5.43113ZM13.1639 8.81877C11.962 9.15997 10.0944 9.37932 7.99717 9.37932C5.89994 9.37932 4.03103 9.15997 2.83041 8.81877C1.95688 9.06762 1.4361 9.3806 1.4361 9.71924C1.4361 10.5273 4.37351 11.1802 7.99717 11.1802C11.6208 11.1802 14.5595 10.5261 14.5595 9.71924C14.5595 9.3806 14.0387 9.06762 13.1639 8.81877ZM7.99717 11.1892C6.2437 11.1892 4.5967 11.0366 3.35632 10.7608C2.11337 10.4837 1.42969 10.113 1.42969 9.71924C1.42969 9.38701 1.91327 9.07275 2.82913 8.81236H2.83169C4.0849 9.16895 5.96664 9.3729 7.99717 9.3729C10.0277 9.3729 11.9082 9.16895 13.1626 8.81236H13.1652C14.0798 9.07275 14.5646 9.38701 14.5646 9.71924C14.5646 10.113 13.881 10.4812 12.638 10.7582C11.3976 11.0366 9.75063 11.1892 7.99717 11.1892ZM2.83041 8.82519C2.38787 8.95217 2.04539 9.09071 1.80937 9.24078C1.56565 9.39471 1.44251 9.55633 1.44251 9.71924C1.44251 10.5209 4.38377 11.1751 7.99845 11.1751C11.6131 11.1751 14.5531 10.5222 14.5531 9.71924C14.5531 9.55505 14.43 9.39471 14.1862 9.2395C13.9502 9.08942 13.6065 8.95089 13.1652 8.8239C11.9094 9.18178 10.0277 9.38573 7.99717 9.38573C5.96664 9.38573 4.0849 9.18178 2.83041 8.82519ZM13.1639 12.1987C11.962 12.5399 10.0944 12.7593 7.99717 12.7593C5.89994 12.7593 4.03103 12.5412 2.83041 12.1987C1.95816 12.4463 1.43738 12.7593 1.43738 13.0992C1.43738 13.9073 4.37479 14.5602 7.99845 14.5602C11.6221 14.5602 14.5608 13.906 14.5608 13.0992C14.5595 12.7605 14.0387 12.4476 13.1639 12.1987ZM7.99717 14.5692C6.2437 14.5692 4.5967 14.4165 3.35632 14.1407C2.11337 13.8637 1.42969 13.493 1.42969 13.0992C1.42969 12.767 1.91327 12.4527 2.82913 12.1923H2.83169C4.0849 12.5489 5.96792 12.7528 7.99717 12.7528C10.0264 12.7528 11.9082 12.5489 13.1626 12.1923H13.1652C14.0798 12.4527 14.5646 12.767 14.5646 13.0992C14.5646 13.493 13.881 13.8611 12.638 14.1382C11.3976 14.4165 9.75063 14.5692 7.99717 14.5692ZM2.83041 12.2051C2.38916 12.3308 2.04539 12.4707 1.80937 12.6207C1.56565 12.7747 1.44251 12.9363 1.44251 13.0992C1.44251 13.9022 4.38377 14.5551 7.99845 14.5551C11.6131 14.5551 14.5531 13.9022 14.5531 13.0992C14.5531 12.935 14.43 12.7747 14.1862 12.6194C13.9502 12.4694 13.6065 12.3308 13.1652 12.2038C11.9094 12.5604 10.0277 12.7657 7.99717 12.7657C5.96664 12.7657 4.0849 12.5604 2.83041 12.2051Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.6 MiB

@ -0,0 +1,74 @@
import React, { useState } from 'react';
import { Card, Row, Col, Statistic, Progress, Button, Space } from 'antd';
import styles from './basic.less';
import Nyfl from './components/nyfl';
import Fgpsdsz from './components/fgpsdsz';
import Jgfx from './components/jgfx';
import Bmzstx from './components/bmzstx';
import Nhtjcs from './components/nhtjcs';
const SafeMajorHazardList = () => {
const [activeModule, setActiveModule] = useState('nyfl');
const handleModuleClick = (module) => {
setActiveModule(module)
}
const renderModule = () => {
switch (activeModule) {
case 'nyfl':
return <Nyfl />;
case 'fgpsdsz':
return <Fgpsdsz />;
case 'jgtx':
return <Jgfx />;
case 'bmzstx':
return <Bmzstx />;
case 'nhtjcs':
return <Nhtjcs />;
default:
return <Nyfl />;
}
};
return (
<div className={styles.container}>
<div className={styles.TopButton}>
<Button
className={`${styles.TopButtonItem} ${activeModule === "nyfl" ? styles.active : ""}`}
onClick={() => handleModuleClick("nyfl")}
>能源分类
</Button>
<Button
className={`${styles.TopButtonItem} ${activeModule === "fgpsdsz" ? styles.active : ""}`}
onClick={() => handleModuleClick("fgpsdsz")}
>峰谷平时段设置
</Button>
<Button
className={`${styles.TopButtonItem} ${activeModule === "jgtx" ? styles.active : ""}`}
onClick={() => handleModuleClick("jgtx")}
>价格体系
</Button>
<Button
className={`${styles.TopButtonItem} ${activeModule === "bmzstx" ? styles.active : ""}`}
onClick={() => handleModuleClick("bmzstx")}
>标煤折算体系
</Button>
<Button
className={`${styles.TopButtonItem} ${activeModule === "nhtjcs" ? styles.active : ""}`}
onClick={() => handleModuleClick("nhtjcs")}
>能耗统计参数
</Button>
</div>
<div className={styles.content}>
{renderModule()}
</div>
</div>
);
};
export default SafeMajorHazardList;

@ -0,0 +1,69 @@
.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 {
background-color: rgba(72, 81, 255, 1) !important;
color: #fff !important;
border-radius: 20px !important;
padding: 6px 10px 10px !important;
&::after {
content: '';
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 3px;
width: 16.573974609375px;
height: 2.615234375px;
background-color: rgba(255, 255, 255, 0.52);
border-radius: 15px;
opacity: 1;
}
}
}
}
.content {
// ======== 内容区域样式 ========
flex: 1; // ======== 占据剩余空间 ========
overflow-y: auto; // ======== 允许垂直滚动 ========
padding: 0; // ======== 无内边距 ========
}
}

@ -0,0 +1,716 @@
import React, { useEffect, useRef, useState } from 'react';
import { Card, Result, Select, Button } from 'antd';
import * as echarts from 'echarts';
import StandardTable from '@/components/StandardTable';
import styles from './OnlineMonitoring.less';
import alarm0 from '@/assets/safe_majorHazard/online_monitoring/alarm0.png';
import alarm1 from '@/assets/safe_majorHazard/online_monitoring/alarm1.png';
import alarm2 from '@/assets/safe_majorHazard/online_monitoring/alarm2.png';
import alarm3 from '@/assets/safe_majorHazard/online_monitoring/alarm3.png';
import exportIcon from '@/assets/safe_majorHazard/online_monitoring/export.png';
import deleteIcon from '@/assets/safe_majorHazard/online_monitoring/delete.png';
const OnlineMonitoring = () => {
const chartRef = 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 (chartRef.current) {
const chart = echarts.init(chartRef.current);
const option = {
color: ['#04A7F3', '#E7C42C', '#EC6941'],
legend: {
data: ['液位', '温度', '压力'],
top: "-3px",
left: "center",
itemGap: 40, // 图例间距
textStyle: {
fontSize: 10
}
},
grid: {
left: '2%',
right: '4%',
bottom: '2%',
top: '12%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['0:00', '2:00', '4:00', '6:00', '8:00', '10:00', '12:00', '14:00', '16:00', '18:00', '20:00', '22:00', '24:00'],
axisLabel: {
fontSize: 10
}
},
yAxis: {
type: 'value',
min: 0,
max: 500,
axisLabel: {
formatter: '{value}',
fontSize: 10
}
},
series: [
{
name: '液位',
type: 'line',
smooth: true,
lineStyle: {
width: 1.5,
color: '#04A7F3'
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(4, 167, 243, 0.3)' },
{ offset: 1, color: 'rgba(4, 167, 243, 0)' }
]
}
},
symbol: 'none', // 不显示数据点
data: [120, 200, 150, 300, 250, 400, 350, 280, 320, 180, 220, 160, 140]
},
{
name: '温度',
type: 'line',
smooth: true,
lineStyle: {
width: 1.5,
color: '#E7C42C'
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(231, 196, 44, 0.3)' },
{ offset: 1, color: 'rgba(231, 196, 44, 0)' }
]
}
},
symbol: 'none',
data: [80, 120, 100, 180, 160, 220, 200, 150, 170, 90, 110, 85, 75]
},
{
name: '压力',
type: 'line',
smooth: true,
lineStyle: {
width: 1.5,
color: '#EC6941'
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 1,
x2: 0,
y2: 0,
colorStops: [
{ offset: 0, color: 'rgba(236, 105, 65, 0)' },
{ offset: 1, color: 'rgba(236, 105, 65, 0.3)' }
]
}
},
symbol: 'none',
data: [200, 300, 250, 450, 400, 430, 480, 420, 480, 280, 320, 260, 240]
}
]
};
chart.setOption(option);
// 响应式调整 - 使用ResizeObserver监听容器尺寸变化
let resizeTimer = null;
const handleResize = () => {
// 防抖处理避免频繁调用resize
if (resizeTimer) {
clearTimeout(resizeTimer);
}
resizeTimer = setTimeout(() => {
chart.resize();
}, 100);
};
// 监听窗口大小变化
window.addEventListener('resize', handleResize);
// 监听容器尺寸变化(解决菜单栏伸缩时的自适应问题)
let resizeObserver = null;
if (window.ResizeObserver) {
resizeObserver = new ResizeObserver(() => {
// 使用setTimeout确保DOM更新完成后再调整图表
setTimeout(() => {
handleResize();
}, 0);
});
resizeObserver.observe(chartRef.current);
}
return () => {
window.removeEventListener('resize', handleResize);
if (resizeObserver) {
resizeObserver.disconnect();
}
if (resizeTimer) {
clearTimeout(resizeTimer);
}
chart.dispose();
};
}
}, []);
// 表格列定义
const columns = [
{
title: '编号',
dataIndex: 'id',
key: 'id',
width: 80,
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: 'alarmTime',
key: 'alarmTime',
width: 150,
},
{
title: '报警传感器名称',
dataIndex: 'sensorName',
key: 'sensorName',
width: 150,
},
{
title: '报警类型',
dataIndex: 'alarmType',
key: 'alarmType',
width: 120,
},
{
title: '报警内容',
dataIndex: 'alarmContent',
key: 'alarmContent',
width: 200,
},
{
title: '优先级',
dataIndex: 'priority',
key: 'priority',
width: 80,
render: (text) => {
const colorMap = {
'高': '#FF4D4F',
'中': '#FAAD14',
'低': '#52C41A'
};
return <span style={{ color: colorMap[text] || '#333' }}>{text}</span>;
}
},
{
title: '处理状态',
dataIndex: 'status',
key: 'status',
width: 100,
render: (text) => {
const statusMap = {
'未处理': { color: '#FF4D4F', bg: '#FFF2F0' },
'处理中': { color: '#FAAD14', bg: '#FFFBE6' },
'已处理': { color: '#52C41A', bg: '#F6FFED' }
};
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: 'processTime',
key: 'processTime',
width: 150,
},
{
title: '处理人',
dataIndex: 'processor',
key: 'processor',
width: 100,
},
{
title: '操作',
key: 'action',
width: 120,
render: (_, record) => (
<div>
<Button type="link" size="small" style={{ padding: 0, marginRight: 8 }}>
查看
</Button>
</div>
),
},
];
// 模拟数据
const mockData = [
{
key: '1',
id: '001',
alarmTime: '2024-01-15 08:30:25',
sensorName: 'LNG储罐',
alarmType: '温度超限',
alarmContent: '储罐温度超过安全阈值',
priority: '高',
status: '未处理',
processTime: '-',
processor: '-',
},
{
key: '2',
id: '002',
alarmTime: '2024-01-15 09:15:10',
sensorName: 'LNG储罐',
alarmType: '压力异常',
alarmContent: '管道压力异常波动',
priority: '中',
status: '处理中',
processTime: '2024-01-15 09:20:00',
processor: '张三',
},
{
key: '3',
id: '003',
alarmTime: '2024-01-15 10:45:30',
sensorName: 'LNG储罐',
alarmType: '液位异常',
alarmContent: '储罐液位低于警戒线',
priority: '高',
status: '已处理',
processTime: '2024-01-15 11:00:15',
processor: '李四',
},
{
key: '4',
id: '004',
alarmTime: '2024-01-15 11:20:45',
sensorName: 'LNG储罐',
alarmType: '气体泄漏',
alarmContent: '检测到可燃气体泄漏',
priority: '高',
status: '未处理',
processTime: '-',
processor: '-',
},
{
key: '5',
id: '005',
alarmTime: '2024-01-15 12:10:20',
sensorName: 'LNG储罐',
alarmType: '设备振动',
alarmContent: '设备异常振动',
priority: '低',
status: '已处理',
processTime: '2024-01-15 12:30:00',
processor: '王五',
},
{
key: '6',
id: '006',
alarmTime: '2024-01-15 13:25:15',
sensorName: 'LNG管道',
alarmType: '流量异常',
alarmContent: '管道流量异常波动',
priority: '中',
status: '未处理',
processTime: '-',
processor: '-',
},
{
key: '7',
id: '007',
alarmTime: '2024-01-15 14:10:30',
sensorName: 'LNG储罐',
alarmType: '温度异常',
alarmContent: '储罐温度异常升高',
priority: '高',
status: '处理中',
processTime: '2024-01-15 14:15:00',
processor: '赵六',
},
{
key: '8',
id: '008',
alarmTime: '2024-01-15 15:45:20',
sensorName: 'LNG管道',
alarmType: '压力超限',
alarmContent: '管道压力超过安全阈值',
priority: '高',
status: '已处理',
processTime: '2024-01-15 16:00:00',
processor: '孙七',
},
{
key: '9',
id: '009',
alarmTime: '2024-01-15 16:30:45',
sensorName: 'LNG储罐',
alarmType: '液位超限',
alarmContent: '储罐液位超过警戒线',
priority: '中',
status: '未处理',
processTime: '-',
processor: '-',
},
{
key: '10',
id: '010',
alarmTime: '2024-01-15 17:15:10',
sensorName: 'LNG管道',
alarmType: '泄漏检测',
alarmContent: '检测到轻微气体泄漏',
priority: '低',
status: '已处理',
processTime: '2024-01-15 17:30:00',
processor: '周八',
},
{
key: '11',
id: '011',
alarmTime: '2024-01-15 18:20:35',
sensorName: 'LNG储罐',
alarmType: '设备故障',
alarmContent: '储罐阀门异常关闭',
priority: '高',
status: '处理中',
processTime: '2024-01-15 18:25:00',
processor: '吴九',
},
{
key: '12',
id: '012',
alarmTime: '2024-01-15 19:05:50',
sensorName: 'LNG管道',
alarmType: '温度异常',
alarmContent: '管道温度异常下降',
priority: '中',
status: '未处理',
processTime: '-',
processor: '-',
},
];
// 初始化数据
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 handleTableChange = (pagination) => {
setPagination(prev => ({
...prev,
current: pagination.current,
pageSize: pagination.pageSize,
}));
};
// 导出功能
const handleExport = () => {
console.log('导出数据');
// 这里可以添加导出逻辑
};
// 批量删除功能
const handleBatchDelete = () => {
if (selectedRowKeys.length === 0) {
console.log('没有选中任何行');
// 可以在这里添加提示用户选择行的逻辑
return;
}
console.log('批量删除', selectedRowKeys);
// 这里可以添加批量删除逻辑
};
return (
<div className={styles.Ocontainer}>
<div className={styles.OcontainerTop}>
<div className={styles.OcontainerTopLeft}>
<div className={styles.OcontainerTopLeftTop}>
<div className={styles.alarmO}>
<div className={styles.alarmOLeft}>
<img style={{ width: 58, height: 47 }} src={alarm0} alt='alarm0' />
</div>
<div className={styles.alarmORight}>
<div className={styles.alarmORightText1}>总报警</div>
<div className={styles.alarmORightText2}>1456</div>
<div className={styles.alarmORightText3}>
<div>
未处理 <text style={{ color: '#FF4D4F' }}>6</text>
</div>
<div>
处理中 <text style={{ color: '#2e4cd4' }}>10</text>
</div>
</div>
</div>
</div>
<div className={styles.alarmTw}>
<div className={styles.alarmTwLeft}>
<img style={{ width: 58, height: 47 }} src={alarm1} alt='alarm1' />
</div>
<div className={styles.alarmTwRight}>
<div className={styles.alarmTwRightText1}>一级报警</div>
<div className={styles.alarmTwRightText2}>357</div>
<div className={styles.alarmTwRightText3}>
<div>
未处理 <text style={{ color: '#FF4D4F' }}>6</text>
</div>
<div>
处理中 <text style={{ color: '#2e4cd4' }}>10</text>
</div>
</div>
</div>
</div>
<div className={styles.alarmTh}>
<div className={styles.alarmThLeft}>
<img style={{ width: 58, height: 47 }} src={alarm2} alt='alarm2' />
</div>
<div className={styles.alarmThRight}>
<div className={styles.alarmThRightText1}>二级报警</div>
<div className={styles.alarmThRightText2}>401</div>
<div className={styles.alarmThRightText3}>
<div>
未处理 <text style={{ color: '#FF4D4F' }}>6</text>
</div>
<div>
处理中 <text style={{ color: '#2e4cd4' }}>10</text>
</div>
</div>
</div>
</div>
<div className={styles.alarmF}>
<div className={styles.alarmFLeft}>
<img style={{ width: 58, height: 47 }} src={alarm3} alt='alarm3' />
</div>
<div className={styles.alarmFRight}>
<div className={styles.alarmFRightText1}>三级报警</div>
<div className={styles.alarmFRightText2}>556</div>
<div className={styles.alarmFRightText3}>
<div>
未处理 <text style={{ color: '#FF4D4F' }}>6</text>
</div>
<div>
处理中 <text style={{ color: '#2e4cd4' }}>10</text>
</div>
</div>
</div>
</div>
</div>
<div className={styles.OcontainerTopLeftBottom}>
<div className={styles.OcontainerTopLeftBottomTitle}>
<div className={styles.titleLeft}>
<div className={styles.titleIcon}></div>
<div>预警看板</div>
</div>
<div className={styles.titleRight}>
<div>检测对象</div>
<Select
style={{ width: 80 }}
defaultValue="储罐"
options={[
{ value: '储罐', label: '储罐' },
{ value: '管道', label: '管道' },
{ value: '设备', label: '设备' }
]}
/>
</div>
</div>
<div className={styles.OcontainerTopLeftBottomChart} ref={chartRef}>
</div>
</div>
</div>
<div className={styles.OcontainerTopRight}>
<div className={styles.realTimeDataHeader}>
<div className={styles.titleLeft}>
<div className={styles.titleIcon}></div>
<div>实时数据采集</div>
</div>
<div className={styles.totalCount}>
总数 <text style={{ color: '#2e4cd4' }}>1378</text>
</div>
</div>
<div className={styles.dataItem1}>
<div className={styles.dataItemLeft}>
<div className={styles.areaName}>储罐液化装置区</div>
<div className={styles.rValue}>R值: 1765</div>
<div className={styles.codeNumber}>编号:XXXXXXXX</div>
</div>
<div className={styles.dataItemRight}>
<div className={styles.circleContainer}>
<div className={styles.outerCircle}>
<div className={styles.innerCircle}>
<div className={styles.levelText}>三级</div>
<div className={styles.riskText}>危险等级</div>
</div>
</div>
</div>
</div>
</div>
<div className={styles.dataItem2}>
<div className={styles.dataItemLeft}>
<div className={styles.areaName}>储罐液化装置区</div>
<div className={styles.rValue}>R值: 1765</div>
<div className={styles.codeNumber}>编号:XXXXXXXX</div>
</div>
<div className={styles.dataItemRight}>
<div className={styles.circleContainer}>
<div className={styles.outerCircle}>
<div className={styles.innerCircle}>
<div className={styles.levelText}>一级</div>
<div className={styles.riskText}>危险等级</div>
</div>
</div>
</div>
</div>
</div>
<div className={styles.dataItem3}>
<div className={styles.dataItemLeft}>
<div className={styles.areaName}>储罐液化装置区</div>
<div className={styles.rValue}>R值: 1765</div>
<div className={styles.codeNumber}>编号:XXXXXXXX</div>
</div>
<div className={styles.dataItemRight}>
<div className={styles.circleContainer}>
<div className={styles.outerCircle}>
<div className={styles.innerCircle}>
<div className={styles.levelText}>二级</div>
<div className={styles.riskText}>危险等级</div>
</div>
</div>
</div>
</div>
</div>
<div className={styles.dataItem4}>
<div className={styles.dataItemLeft}>
<div className={styles.areaName}>储罐液化装置区</div>
<div className={styles.rValue}>R值: 1765</div>
<div className={styles.codeNumber}>编号:XXXXXXXX</div>
</div>
<div className={styles.dataItemRight}>
<div className={styles.circleContainer}>
<div className={styles.outerCircle}>
<div className={styles.innerCircle}>
<div className={styles.levelText}>三级</div>
<div className={styles.riskText}>危险等级</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{/* 表格 */}
<div className={styles.OcontainerBottom}>
{/* 首行 左侧标题左对齐 右侧按钮右对齐 */}
<div className={styles.tableHeader}>
<div className={styles.tableTitle}>
<div className={styles.titleIcon}></div>
<div>报警信息列表</div>
</div>
<div className={styles.tableActions}>
<Button
type="primary"
onClick={handleExport}
style={{ marginRight: 8 }}
>
<img src={exportIcon} alt="导出" style={{ width: 16, height: 16, margin: '-2px 6px 0 0px'}} />
导出word 报告
</Button>
<Button
type="primary"
onClick={handleBatchDelete}
>
<img src={deleteIcon} alt="删除" style={{ width: 16, height: 16, margin: '-2px 6px 0 0px' }} />
批量删除
</Button>
</div>
</div>
{/* 表格 5行10列 带页码 每页5条数据 */}
<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>
);
};
export default OnlineMonitoring;

@ -0,0 +1,919 @@
.Ocontainer {
padding: 8px 6px 0px 6px;
height: 100%;
display: flex;
flex-direction: column;
.OcontainerTop {
display: flex;
height: 50%;
margin-bottom: 5px;
.OcontainerTopLeft {
width: 72%;
height: 100%;
// background-color: pink;
margin-right: 10px;
// display: flex;
.OcontainerTopLeftTop {
width: 100%;
height: 35%;
display: flex;
gap: 12px;
.alarmO {
flex: 1;
height: 100%;
background-color: #F4F7FF;
border: 1px solid #AED3FF;
border-bottom: 0px solid #AED3FF;
border-radius: 4px;
box-shadow: 0px 2px 31px 0px #5382FE33 inset;
display: flex;
.alarmOLeft {
width: 35%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.alarmORight {
flex: 1;
width: 35%;
height: 100%;
display: flex;
flex-direction: column;
margin-left: 2px;
gap: 18px;
font-family: PingFang SC;
font-weight: 400;
font-style: Regular;
font-size: 12px;
line-height: 100%;
letter-spacing: 0%;
color: #333333;
.alarmORightText1 {
margin-top: 15px;
}
.alarmORightText2 {
font-weight: 700;
font-size: 16px;
}
.alarmORightText3 {
display: flex;
gap: 22px;
}
}
}
.alarmTw {
flex: 1;
height: 100%;
background-color: #FFF5f4;
border: 1px solid #FFC5BC;
border-bottom: 0px solid #FFC5BC;
border-radius: 4px;
box-shadow: 0px 2px 31px 0px #FE5F4C33 inset;
display: flex;
.alarmTwLeft {
width: 35%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.alarmTwRight {
flex: 1;
width: 35%;
height: 100%;
display: flex;
flex-direction: column;
margin-left: 2px;
gap: 18px;
font-family: PingFang SC;
font-weight: 400;
font-style: Regular;
font-size: 12px;
line-height: 100%;
letter-spacing: 0%;
color: #333333;
.alarmTwRightText1 {
margin-top: 15px;
}
.alarmTwRightText2 {
font-weight: 700;
font-size: 16px;
}
.alarmTwRightText3 {
display: flex;
gap: 22px;
}
}
}
.alarmTh {
flex: 1;
height: 100%;
background-color: #FFF7F2;
border: 1px solid #FFD9B2;
border-bottom: 0px solid #FFD9B2;
border-radius: 4px;
box-shadow: 0px 2px 31px 0px #FD883C33 inset;
display: flex;
.alarmThLeft {
width: 35%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.alarmThRight {
flex: 1;
width: 35%;
height: 100%;
display: flex;
flex-direction: column;
margin-left: 2px;
gap: 18px;
font-family: PingFang SC;
font-weight: 400;
font-style: Regular;
font-size: 12px;
line-height: 100%;
letter-spacing: 0%;
color: #333333;
.alarmThRightText1 {
margin-top: 15px;
}
.alarmThRightText2 {
font-weight: 700;
font-size: 16px;
}
.alarmThRightText3 {
display: flex;
gap: 22px;
}
}
}
.alarmF {
flex: 1;
height: 100%;
background-color: #EFF9FF;
border: 1px solid #89E1FF;
border-bottom: 0px solid #89E1FF;
border-radius: 4px;
box-shadow: 0px 2px 31px 0px #22A4FD33 inset;
display: flex;
.alarmFLeft {
width: 35%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.alarmFRight {
flex: 1;
width: 35%;
height: 100%;
display: flex;
flex-direction: column;
margin-left: 2px;
gap: 18px;
font-family: PingFang SC;
font-weight: 400;
font-style: Regular;
font-size: 12px;
line-height: 100%;
letter-spacing: 0%;
color: #333333;
.alarmFRightText1 {
margin-top: 15px;
}
.alarmFRightText2 {
font-weight: 700;
font-size: 16px;
}
.alarmFRightText3 {
display: flex;
gap: 22px;
}
}
}
}
.OcontainerTopLeftBottom {
margin-top: 12px;
background-color: #fff;
width: 100%;
height: 60%;
.OcontainerTopLeftBottomTitle {
display: flex;
justify-content: space-between;
align-items: center;
// padding: 8px 15px;
padding: 8px 15px 0px 15px;
.titleLeft {
display: flex;
align-items: center;
gap: 8px;
font-family: PingFang SC;
font-weight: 500;
font-style: Medium;
font-size: 14px;
line-height: 100%;
letter-spacing: 0%;
.titleIcon {
width: 3px;
height: 16px;
background-color: #2E4CD4;
}
}
.titleRight {
display: flex;
align-items: center;
gap: 8px;
font-family: PingFang SC;
font-style: Medium;
font-size: 13px;
line-height: 100%;
letter-spacing: 0%;
.selectBox {
padding: 4px 8px;
border: 1px solid #d9d9d9;
border-radius: 4px;
background-color: #fff;
font-size: 12px;
color: #333;
outline: none;
&:focus {
border-color: #2E4CD4;
}
}
}
}
.OcontainerTopLeftBottomChart {
flex: 1;
width: 100%;
height: 75%;
}
}
}
.OcontainerTopRight {
flex: 1;
height: calc(100% - 3.3px);
background-color: #fff;
background-image: url('@/assets/safe_majorHazard/online_monitoring/backTopRight.png');
background-size: 100% auto;
display: flex;
flex-direction: column;
overflow-y: auto;
.realTimeDataHeader {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 15px;
margin-bottom: 10px;
.titleLeft {
display: flex;
align-items: center;
gap: 8px;
font-family: PingFang SC;
font-weight: 500;
font-style: Medium;
font-size: 14px;
line-height: 100%;
letter-spacing: 0%;
.titleIcon {
width: 3px;
height: 16px;
background-color: #2E4CD4;
}
}
.totalCount {
font-family: PingFang SC;
font-weight: 400;
font-size: 13px;
color: #333333;
}
}
.dataItem {
height: 23%;
flex-shrink: 0;
border: 1px solid #89E1FF;
border-radius: 2px;
margin: 0 15px;
margin-bottom: 6px;
display: flex;
align-items: center;
justify-content: center;
font-family: PingFang SC;
font-size: 14px;
// color: #666;
background-color: #EFF9FF;
&:last-child {
// margin-bottom: 1px;
}
}
.dataItem1 {
height: 25%;
flex-shrink: 0;
border: 1px solid #89E1FF;
border-radius: 4px;
margin: 0 15px;
margin-bottom: 6px;
display: flex;
align-items: center;
padding: 0px 15px;
background-color: #EFF9FF;
.dataItemLeft {
width: 65%;
display: flex;
flex-direction: column;
gap: 8px;
.areaName {
font-family: PingFang SC;
font-weight: 400;
font-size: 13px;
color: #333333;
line-height: 2.2;
}
.rValue {
font-family: PingFang SC;
font-weight: 400;
font-size: 14px;
color: #666666;
line-height: 0.2;
}
.codeNumber {
font-family: PingFang SC;
font-weight: 400;
font-size: 12px;
color: #666666;
}
}
.dataItemRight {
width: 35%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
.circleContainer {
position: relative;
height: 80%;
aspect-ratio: 1; // 强制宽高比1:1
.outerCircle {
width: 100%;
height: 100%;
background-color: rgba(51, 176, 253, 0.3);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
.innerCircle {
width: 70%;
height: 70%;
background-color: rgba(4, 128, 251, 0.8);
border-radius: 50%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.levelText {
font-family: PingFang SC;
font-weight: 500;
font-size: 11px;
color: #FFFFFF;
line-height: 1.4;
margin-top: -4px;
}
.riskText {
font-family: PingFang SC;
font-weight: 300;
font-size: 8px;
color: #FFFFFF;
line-height: 1;
}
}
}
}
}
}
.dataItem2 {
height: 25%;
flex-shrink: 0;
border: 1px solid rgba(255, 197, 188, 1);
border-radius: 4px;
margin: 0 15px;
margin-bottom: 6px;
display: flex;
align-items: center;
padding: 0px 15px;
background-color: #fff5f4;
.dataItemLeft {
width: 65%;
display: flex;
flex-direction: column;
gap: 8px;
.areaName {
font-family: PingFang SC;
font-weight: 400;
font-size: 13px;
color: #333333;
line-height: 2.2;
}
.rValue {
font-family: PingFang SC;
font-weight: 400;
font-size: 14px;
color: #666666;
line-height: 0.2;
}
.codeNumber {
font-family: PingFang SC;
font-weight: 400;
font-size: 12px;
color: #666666;
}
}
.dataItemRight {
width: 35%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
.circleContainer {
position: relative;
height: 80%;
aspect-ratio: 1;
.outerCircle {
width: 100%;
height: 100%;
background-color: rgba(254, 214, 209, 1);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
.innerCircle {
width: 70%;
height: 70%;
background-color: rgba(253, 41, 14, 1);
border-radius: 50%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.levelText {
font-family: PingFang SC;
font-weight: 500;
font-size: 11px;
color: #FFFFFF;
line-height: 1.4;
margin-top: -4px;
}
.riskText {
font-family: PingFang SC;
font-weight: 300;
font-size: 8px;
color: #FFFFFF;
line-height: 1;
}
}
}
}
}
}
.dataItem3 {
height: 25%;
flex-shrink: 0;
border: 1px solid rgba(255, 217, 178, 1);
border-radius: 4px;
margin: 0 15px;
margin-bottom: 6px;
display: flex;
align-items: center;
padding: 0px 15px;
background-color: #fef6f1;
.dataItemLeft {
width: 65%;
display: flex;
flex-direction: column;
gap: 8px;
.areaName {
font-family: PingFang SC;
font-weight: 400;
font-size: 13px;
color: #333333;
line-height: 2.2;
}
.rValue {
font-family: PingFang SC;
font-weight: 400;
font-size: 14px;
color: #666666;
line-height: 0.2;
}
.codeNumber {
font-family: PingFang SC;
font-weight: 400;
font-size: 12px;
color: #666666;
}
}
.dataItemRight {
width: 35%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
.circleContainer {
position: relative;
height: 80%;
aspect-ratio: 1;
.outerCircle {
width: 100%;
height: 100%;
background-color: rgba(255, 234, 218, 1);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
.innerCircle {
width: 70%;
height: 70%;
background-color: rgba(252, 103, 18, 1);
border-radius: 50%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.levelText {
font-family: PingFang SC;
font-weight: 500;
font-size: 11px;
color: #FFFFFF;
line-height: 1.4;
margin-top: -4px;
}
.riskText {
font-family: PingFang SC;
font-weight: 300;
font-size: 8px;
color: #FFFFFF;
line-height: 1;
}
}
}
}
}
}
.dataItem4 {
height: 25%;
flex-shrink: 0;
border: 1px solid #89E1FF;
border-radius: 4px;
margin: 0 15px;
margin-bottom: 6px;
display: flex;
align-items: center;
padding: 0px 15px;
background-color: #EFF9FF;
.dataItemLeft {
width: 65%;
display: flex;
flex-direction: column;
gap: 8px;
.areaName {
font-family: PingFang SC;
font-weight: 400;
font-size: 13px;
color: #333333;
line-height: 2.2;
}
.rValue {
font-family: PingFang SC;
font-weight: 400;
font-size: 14px;
color: #666666;
line-height: 0.2;
}
.codeNumber {
font-family: PingFang SC;
font-weight: 400;
font-size: 12px;
color: #666666;
}
}
.dataItemRight {
width: 35%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
.circleContainer {
position: relative;
height: 80%;
aspect-ratio: 1;
.outerCircle {
width: 100%;
height: 100%;
background-color: rgba(51, 176, 253, 0.3);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
.innerCircle {
width: 70%;
height: 70%;
background-color: rgba(4, 128, 251, 0.8);
border-radius: 50%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.levelText {
font-family: PingFang SC;
font-weight: 500;
font-size: 11px;
color: #FFFFFF;
line-height: 1.4;
margin-top: -4px;
}
.riskText {
font-family: PingFang SC;
font-weight: 300;
font-size: 8px;
color: #FFFFFF;
line-height: 1;
}
}
}
}
}
}
}
}
.OcontainerBottom {
background-color: #fff;
flex: 1;
padding: 8px 15px 5px 15px;
display: flex;
flex-direction: column;
.tableHeader {
display: flex;
justify-content: space-between;
align-items: center;
// margin-bottom: 15px;
padding-bottom: 5px;
// border-bottom: 1px solid #f0f0f0;
.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;
// 自定义按钮样式
:global(.ant-btn) {
background-color: #ffffff !important;
border-color: #DFE4F6 !important;
color: #333333 !important;
box-shadow: none !important;
&:hover {
background-color: #f5f5f5 !important;
border-color: #DFE4F6 !important;
color: #333333 !important;
box-shadow: none !important;
}
&:focus {
background-color: #ffffff !important;
border-color: #DFE4F6 !important;
color: #333333 !important;
box-shadow: none !important;
}
&:active {
background-color: #e6e6e6 !important;
border-color: #DFE4F6 !important;
color: #333333 !important;
box-shadow: none !important;
}
// 主要按钮样式
&.ant-btn-primary {
background-color: #ffffff !important;
border-color: #DFE4F6 !important;
color: #333333 !important;
box-shadow: none !important;
&:hover {
background-color: #f5f5f5 !important;
border-color: #DFE4F6 !important;
color: #333333 !important;
box-shadow: none !important;
}
&:focus {
background-color: #ffffff !important;
border-color: #DFE4F6 !important;
color: #333333 !important;
box-shadow: none !important;
}
&:active {
background-color: #e6e6e6 !important;
border-color: #DFE4F6 !important;
color: #333333 !important;
box-shadow: none !important;
}
}
// 危险按钮样式
&.ant-btn-dangerous {
background-color: #ffffff !important;
border-color: #DFE4F6 !important;
color: #333333 !important;
box-shadow: none !important;
&:hover {
background-color: #f5f5f5 !important;
border-color: #DFE4F6 !important;
color: #333333 !important;
box-shadow: none !important;
}
&:focus {
background-color: #ffffff !important;
border-color: #DFE4F6 !important;
color: #333333 !important;
box-shadow: none !important;
}
&:active {
background-color: #e6e6e6 !important;
border-color: #DFE4F6 !important;
color: #333333 !important;
box-shadow: none !important;
}
}
// 禁用状态
&:disabled {
background-color: #f5f5f5 !important;
border-color: #d9d9d9 !important;
color: #bfbfbf !important;
box-shadow: none !important;
}
}
}
}
.tableContainer {
flex: 1;
overflow: hidden;
: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;
}
:global(.ant-table-tbody > tr:hover > td) {
background-color: #f5f5f5;
}
:global(.ant-pagination) {
margin-top: 16px;
text-align: right;
}
}
}
}

@ -0,0 +1,581 @@
import React from 'react';
import { Card, Statistic, Table,Row, Input,Button,Col, Select} from 'antd';
import { PhoneOutlined, IdcardOutlined, PlusOutlined } from '@ant-design/icons';
import StandardTable from '@/components/StandardTable';
import styles from './ResponsibilityImplementation.less';
import upload from '@/assets/business_basic/upload.png';
import download from '@/assets/business_basic/download.png';
import import1 from '@/assets/business_basic/import1.png';
import fire_fighting1 from '@/assets/business_basic/fire_fighting1.png';
import fire_fighting2 from '@/assets/business_basic/fire_fighting2.png';
import fire_fighting3 from '@/assets/business_basic/fire_fighting3.png';
import frameIcon from '@/assets/business_basic/Frame.png';
import background1 from '@/assets/business_basic/background1.png';
import export1 from '@/assets/business_basic/export1.png';
const ResponsibilityImplementation = () => {
// 搜索处理函数
const onSearch = (value) => {
console.log('搜索内容:', value);
// 这里可以添加实际的搜索逻辑
};
const columns = [
{
title:"编号",
dataIndex:"id",
key:"id",
width:80,
},
{
title:"组织代码",
dataIndex:"orgCode",
key:"orgCode",
width:120,
},
{
title:"组织类型",
dataIndex:"orgType",
key:"orgType",
width:120,
},
{
title:"负责人",
dataIndex:"manager",
key:"manager",
width:100,
},
{
title:"所属部门",
dataIndex:"department",
key:"department",
width:120,
},
{
title:"创建时间",
dataIndex:"createTime",
key:"createTime",
width:120,
},
{
title:"人员规模",
dataIndex:"staffCount",
key:"staffCount",
width:100,
},
{
title:"状态",
dataIndex:"status",
key:"status",
width:80,
render: (text, record) => {
const getStatusStyle = (status) => {
if (status === '正常') {
return {
color: '#44BB5F',
backgroundColor: '#D8F7DE',
padding: '4px 8px',
borderRadius: '4px',
fontSize: '12px',
display: 'inline-block'
};
} else if (status === '信息不全') {
return {
color: '#FF8800',
backgroundColor: '#FFF3E9',
padding: '4px 8px',
borderRadius: '4px',
fontSize: '12px',
display: 'inline-block'
};
}
return {};
};
return (
<span style={getStatusStyle(text)}>
{text}
</span>
);
}
},
{
title:"操作",
dataIndex:"action",
key:"action",
width:120,
render: (text, record) => {
const handleEdit = (record) => {
console.log('编辑记录:', record);
};
const handleDelete = (record) => {
console.log('删除记录:', record);
};
return (
<div style={{
display: 'flex',
gap: '8px',
justifyContent: 'center',
alignItems: 'center'
}}>
<Button
onClick={() => handleEdit(record)}
style={{
color: '#2E4CD4',
backgroundColor: 'transparent',
borderColor: '#E6E9FB',
fontSize: '12px',
height: '28px',
padding: '0 12px'
}}
>
编辑
</Button>
<Button
onClick={() => handleDelete(record)}
style={{
color: '#FF2526',
backgroundColor: 'transparent',
borderColor: '#FFE0E2',
fontSize: '12px',
height: '28px',
padding: '0 12px'
}}
>
删除
</Button>
</div>
);
}
}
];
// 固定的假数据
const tableData = [
{
key: '1',
id: '01',
orgCode: 'DH002',
orgType: '义务消防队',
manager: '张明',
department: '安全管理部',
createTime: '2024-12-19 14:32:15',
staffCount: '15人',
status: '正常'
},
{
key: '2',
id: '02',
orgCode: 'DH002',
orgType: '义务消防队',
manager: '张明',
department: '安全管理部',
createTime: '2024-12-18 09:25:43',
staffCount: '20人',
status: '正常'
},
{
key: '3',
id: '03',
orgCode: 'DH002',
orgType: '义务消防队',
manager: '张明',
department: '安全管理部',
createTime: '2024-12-17 16:48:22',
staffCount: '25人',
status: '信息不全'
},
{
key: '4',
id: '04',
orgCode: 'DH002',
orgType: '义务消防队',
manager: '张明',
department: '安全管理部',
createTime: '2024-12-16 11:15:37',
staffCount: '18人',
status: '正常'
},
{
key: '5',
id: '05',
orgCode: 'DH002',
orgType: '义务消防队',
manager: '张明',
department: '安全管理部',
createTime: '2024-12-15 08:42:56',
staffCount: '22人',
status: '正常'
},
{
key: '6',
id: '06',
orgCode: 'DH002',
orgType: '义务消防队',
manager: '张明',
department: '安全管理部',
createTime: '2024-12-14 13:27:18',
staffCount: '16人',
status: '信息不全'
},
{
key: '7',
id: '07',
orgCode: 'DH002',
orgType: '义务消防队',
manager: '张明',
department: '安全管理部',
createTime: '2024-12-13 15:33:29',
staffCount: '19人',
status: '正常'
},
{
key: '8',
id: '08',
orgCode: 'DH002',
orgType: '义务消防队',
manager: '张明',
department: '安全管理部',
createTime: '2024-12-12 10:56:44',
staffCount: '21人',
status: '正常'
},
{
key: '9',
id: '09',
orgCode: 'DH002',
orgType: '义务消防队',
manager: '张明',
department: '安全管理部',
createTime: '2024-12-11 17:19:52',
staffCount: '17人',
status: '信息不全'
},
{
key: '10',
id: '10',
orgCode: 'DH002',
orgType: '义务消防队',
manager: '张明',
department: '安全管理部',
createTime: '2024-12-10 12:41:07',
staffCount: '23人',
status: '正常'
},
{
key: '11',
id: '11',
orgCode: 'DH002',
orgType: '义务消防队',
manager: '张明',
department: '安全管理部',
createTime: '2024-12-09 14:08:33',
staffCount: '24人',
status: '正常'
},
{
key: '12',
id: '12',
orgCode: 'DH002',
orgType: '义务消防队',
manager: '张明',
department: '安全管理部',
createTime: '2024-12-08 16:52:14',
staffCount: '26人',
status: '信息不全'
}
];
return (
<div className={styles.XcontainerR}>
{/* 警告提示框 */}
<div className={styles.warningBox}>
<img src={frameIcon} alt="警告" className={styles.warningIcon} />
<span className={styles.warningText}>
有5个消防设备需要维护3个资质证书即将到期请及时处理
</span>
</div>
<div className={styles.containerOne}>
<div className={styles.containerOneLeft}>
{/* 第一行:标题和按钮 */}
<div className={styles.leftTopSection}>
<div className={styles.titleLeft}>
<div className={styles.titleIcon}></div>
<div>组织架构图预览</div>
</div>
<div className={styles.buttonGroup}>
<Button className={styles.actionBtn}>
<img src={upload} alt="上传图表" className={styles.btnIcon} />
上传图表
</Button>
<Button className={styles.actionBtn}>
<img src={download} alt="下载" className={styles.btnIcon} />
下载
</Button>
</div>
</div>
{/* 第二行:图片占位 */}
<div className={styles.leftBottomSection}>
<div className={styles.imagePlaceholder}>
<img src={fire_fighting1} alt="消防1" className={styles.imageIcon1} />
<div className={styles.imageRow}>
<img src={fire_fighting2} alt="消防2" className={styles.imageIcon2} />
<img src={fire_fighting3} alt="消防3" className={styles.imageIcon3} />
</div>
</div>
</div>
</div>
<div className={styles.containerOneRight}>
{/* 第一行:标题 + 搜索栏 + 下拉选择框 */}
<div className={styles.rightTopSection}>
<div className={styles.rightTopLeft}>
<div className={styles.titleLeft}>
<div className={styles.titleIcon}></div>
<div>成员信息管理</div>
</div>
</div>
<div className={styles.rightTopRight}>
<div className={styles.searchGroup}>
<Input.Search placeholder="搜索成员..." onSearch={onSearch} style={{ width: 200}} />
<Select
defaultValue="全部组织"
className={styles.organizationSelect}
options={[
{ value: '全部组织', label: '全部组织' },
{ value: '技术部', label: '技术部' },
{ value: '生产部', label: '生产部' },
{ value: '安全部', label: '安全部' },
]}
/>
</div>
</div>
</div>
{/* 第二行:三个小块 */}
<div className={styles.rightBottomSection}>
<div className={styles.threeBlocksContainer}>
<div className={styles.blockItem}>
<div className={styles.blockContent}>
<div className={styles.backgroundContainer}>
{/* 第一个块:姓名和单位 */}
<div className={styles.infoBlock}>
<div className={styles.nameText}>张明</div>
<div className={styles.unitText}>东义区消防队</div>
</div>
{/* 第二个块:电话 */}
<div className={styles.infoBlock}>
<PhoneOutlined className={styles.infoIcon} />
<span className={styles.infoText}>132****3847</span>
</div>
{/* 第三个块:身份证 */}
<div className={styles.infoBlock}>
<IdcardOutlined className={styles.infoIcon} />
<span className={styles.infoText}>1304************10</span>
</div>
{/* 第四个块:职位标签 */}
<div className={styles.infoBlock}>
<div className={styles.tagContainer}>
<div className={styles.tagBlue1}>队长</div>
<div className={styles.tagBlue2}>消防工程师</div>
</div>
</div>
{/* 第五个块:证书状态 */}
<div className={styles.infoBlock}>
<div className={styles.tagContainer}>
<div className={styles.tagBlue3}>消防工程师</div>
<div className={styles.tagYellow}>证书7天后到期</div>
</div>
</div>
{/* 第六个块:操作按钮 */}
<div className={styles.actionBlock}>
<div className={styles.buttonContainer}>
<Button className={styles.editBtn}>编辑</Button>
<Button className={styles.deleteBtn}>删除</Button>
</div>
</div>
</div>
</div>
</div>
<div className={styles.blockItem}>
<div className={styles.blockContent}>
<div className={styles.backgroundContainer2}>
{/* 第一个块:姓名和单位 */}
<div className={styles.infoBlock2}>
<span className={styles.nameText2}>李小明</span>
<span className={styles.unitText2}>消防支队</span>
</div>
{/* 第二个块:电话 */}
<div className={styles.infoBlock2}>
<PhoneOutlined className={styles.infoIcon2} />
<span className={styles.infoText2}>138****5678</span>
</div>
{/* 第三个块:身份证 */}
<div className={styles.infoBlock2}>
<IdcardOutlined className={styles.infoIcon2} />
<span className={styles.infoText2}>1304************20</span>
</div>
{/* 第四个块:职位标签 */}
<div className={styles.infoBlock2}>
<div className={styles.tagContainer2}>
<div className={styles.tagBlue4}>副队长</div>
<div className={styles.tagBlue5}>安全员</div>
</div>
</div>
{/* 第五个块:证书状态 */}
<div className={styles.infoBlock2}>
<div className={styles.tagContainer2}>
<div className={styles.tagBlue6}>安全员证</div>
<div className={styles.tagGreen}>证书正常</div>
</div>
</div>
{/* 第六个块:操作按钮 */}
<div className={styles.actionBlock2}>
<div className={styles.buttonContainer2}>
<Button className={styles.editBtn2}>编辑</Button>
<Button className={styles.deleteBtn2}>删除</Button>
</div>
</div>
</div>
</div>
</div>
<div className={styles.blockItem}>
<div className={styles.blockContent}>
<div className={styles.backgroundContainer3}>
{/* 第一个块:姓名和单位 */}
<div className={styles.infoBlock3}>
<span className={styles.nameText3}>王小红</span>
<span className={styles.unitText3}>消防中队</span>
</div>
{/* 第二个块:电话 */}
<div className={styles.infoBlock3}>
<PhoneOutlined className={styles.infoIcon3} />
<span className={styles.infoText3}>139****9012</span>
</div>
{/* 第三个块:身份证 */}
<div className={styles.infoBlock3}>
<IdcardOutlined className={styles.infoIcon3} />
<span className={styles.infoText3}>1304************30</span>
</div>
{/* 第四个块:职位标签 */}
<div className={styles.infoBlock3}>
<div className={styles.tagContainer3}>
<div className={styles.tagBlue7}>队员</div>
<div className={styles.tagBlue8}>技术员</div>
</div>
</div>
{/* 第五个块:证书状态 */}
<div className={styles.infoBlock3}>
<div className={styles.tagContainer3}>
<div className={styles.tagBlue9}>技术员证</div>
<div className={styles.tagOrange}>证书3天后到期</div>
</div>
</div>
{/* 第六个块:操作按钮 */}
<div className={styles.actionBlock3}>
<div className={styles.buttonContainer3}>
<Button className={styles.editBtn3}>编辑</Button>
<Button className={styles.deleteBtn3}>删除</Button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div className={styles.containerTwo}>
{/* 第一块:标题 */}
<div className={styles.containerTwoTitle}>
<div className={styles.titleLeft}>
<div className={styles.titleIcon}></div>
<div>组织架构管理</div>
</div>
</div>
{/* 第二个大块:搜索和按钮 */}
<div className={styles.containerTwoActions}>
<div className={styles.searchSection}>
<Input.Search placeholder="搜索姓名、工号..." onSearch={onSearch} style={{ width: 180 }} />
</div>
<div className={styles.buttonSection}>
<Button className={styles.addBtn}>
<PlusOutlined className={styles.addIcon} />
新增组织
</Button>
<Button className={styles.importBtn}>
<img src={import1} alt="批量导入" className={styles.importIcon} />
批量导入
</Button>
<Button className={styles.exportBtn}>
<img src={export1} alt="批量导出" className={styles.exportIcon} />
批量导出
</Button>
</div>
</div>
{/* 第三个大块:表格 */}
<div className={styles.containerTwoTable}>
<StandardTable
columns={columns}
data={{
list: tableData, // ======== 表格数据列表 ========
pagination: { // ======== 分页配置 ========
currentPage: 1, // ======== 当前页码 ========
pageSize: 5, // ======== 每页显示10条数据 ========
total: tableData.length, // ======== 总数据条数 ========
} // ======== 分页配置结束 ========
}} // ======== 数据对象结束 ========
selectedRows={[]} // ======== 选中的行数据,初始为空数组 ========
onSelectRow={() => {}} // ======== 行选择事件处理函数 ========
onChange={() => {}} // ======== 表格变化事件处理函数 ========
pagination={{
currentPage: 1,
pageSize: 5,
total: tableData.length,
showSizeChanger: false,
showQuickJumper: true,
showTotal: (total, range) =>
`${total}`,
locale: {
jump_to: '前往',
page: '页',
items_per_page: '条/页',
}
}}
/>
</div>
</div>
</div>
);
};
export default ResponsibilityImplementation;

@ -0,0 +1,865 @@
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;

@ -0,0 +1,594 @@
.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: 4px;
border: 2px solid #FFFFFF;
.blockLeft {
width: 60%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
padding: 15px;
padding-left: 20px;
gap: 8px;
.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: 33%;
border-radius: 4px;
display: flex;
flex-direction: column;
.sectionContent {
height: 100%;
display: flex;
display: flex;
gap: 10px;
height: 100%;
.middleBlock1 {
// flex: 1;
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;
.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: #1890ff;
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;
// }
// }
}
.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;
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: #1890ff;
color: #fff;
}
}
.customSelect {
:global(.ant-select-single:not(.ant-select-customize-input) .ant-select-selector) {
height: 26px !important;
display: flex !important;
align-items: center !important;
}
:global(.ant-select-selection-item) {
line-height: 24px !important;
// height: 24px !important;
display: flex !important;
align-items: center !important;
}
}
}
.deviceStatusChart {
position: absolute;
top: 35px;
left: 10px;
right: 10px;
bottom: 10px;
z-index: 10;
}
}
.middleBlock2 {
flex: 1;
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 {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
color: #666;
}
}
.middleBlock2Chart {
width: 100%;
height: 100%;
// 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;
.leftBlock {
width: 28%;
flex-shrink: 0;
height: 100%;
background: #fff;
// background-size: cover;
padding: 0;
display: flex;
flex-direction: column;
gap: 10px;
padding: 15px;
.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;
}
}
.developmentContainer {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
gap: 8px;
.developmentBlock1 {
flex: 1;
background-color: #F1F7FF;
border-radius: 4px;
padding: 15px 20px;
display: flex;
align-items: center;
width: 100%;
.leftContent {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
min-width: 0;
.mainText {
color: #333333;
font-size: 14px;
font-weight: 500;
font-family: PingFang SC;
width: 100%;
max-width: 500px;
}
.subText {
color: #666666;
font-size: 12px;
font-weight: 400;
font-family: PingFang SC;
width: 100%;
max-width: 400px;
}
}
.rightContent {
flex: 0 0 auto;
display: flex;
justify-content: flex-end;
align-items: center;
padding-right: 10px;
min-width: 80px;
.importantTag {
background-color: #FFE0E2;
color: #FF3E48;
font-size: 14px;
font-weight: 500;
font-family: PingFang SC;
width: 45px;
height: 25px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
}
.normalTag {
background-color: #DAF3FF;
color: #00AAFA;
font-size: 14px;
font-weight: 500;
font-family: PingFang SC;
width: 45px;
height: 25px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
}
}
}
}
}
.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: 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;
.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;
}
}
}
}
}
}

@ -0,0 +1,265 @@
import React, { useMemo, useState } from 'react';
import { Button, message, Popconfirm, Select, Space, Table } from 'antd';
import { DeleteOutlined, DownloadOutlined, EditOutlined, FileTextOutlined, PlusOutlined, UploadOutlined } from '@ant-design/icons';
import styles from './jgfx.less';
const Bmzstx = () => {
const [filterA, setFilterA] = useState(undefined);
const [filterB, setFilterB] = useState(undefined);
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const [pagination, setPagination] = useState({ current: 1, pageSize: 10 });
const selectOptionsA = useMemo(
() => [
{ label: '千瓦时(kWh)', value: '千瓦时(kWh)' },
{ label: '立方米(m³)', value: '立方米(m³)' },
{ label: '吨(t)', value: '吨(t)' },
{ label: '千克(kg)', value: '千克(kg)' },
],
[],
);
const selectOptionsB = useMemo(
() => [
{ label: '电能', value: '电能' },
{ label: '燃气', value: '燃气' },
{ label: '水', value: '水' },
{ label: '汽油', value: '汽油' },
],
[],
);
const rows = useMemo(
() => [
{
id: 1,
energyType: '电能',
unit: '千瓦时(kWh)',
unitPrice: 0.5,
priceUnit: 'kgce/kwh',
scope: '国标(2024 GB-282287)',
pricingUnit: '—',
latestUpdateTime: '2025-12-02 03:56:02',
},
{
id: 2,
energyType: '燃气',
unit: '立方米(m³)',
unitPrice: 2.5,
priceUnit: 'kgce/m³',
scope: '国标(2024 GB-282287)',
pricingUnit: '—',
latestUpdateTime: '2025-11-22 11:56:50',
},
{
id: 3,
energyType: '水',
unit: '吨(t)',
unitPrice: 1.2,
priceUnit: 'kgce/t',
scope: '行业标准(2024 GB-282287)',
pricingUnit: '—',
latestUpdateTime: '2025-12-04 21:12:20',
},
{
id: 4,
energyType: '汽油',
unit: '千克(kg)',
unitPrice: 1.5,
priceUnit: 'kgce/kg',
scope: '行业标准(2024 GB-282287)',
pricingUnit: '—',
latestUpdateTime: '2025-11-26 10:28:20',
},
],
[],
);
const tableData = useMemo(() => {
return rows
.filter((r) => (filterA ? r.unit === filterA : true))
.filter((r) => (filterB ? r.energyType === filterB : true));
}, [filterA, filterB, rows]);
const pagedData = useMemo(() => {
const start = (pagination.current - 1) * pagination.pageSize;
return tableData.slice(start, start + pagination.pageSize);
}, [pagination.current, pagination.pageSize, tableData]);
const handleAdd = () => message.info('新增');
const handleUpload = () => message.info('上传');
const handleBatchDownload = () => message.info('批量下载');
const handleQuery = () => message.info('查询');
const handleEdit = (record) => message.info(`编辑:${record.energyType}`);
const handleView = (record) => message.info(`详情:${record.energyType}`);
const handleDelete = (record) => message.success(`已删除:${record.energyType}`);
const columns = useMemo(
() => [
{
title: '序号',
dataIndex: 'id',
key: 'id',
width: 70,
align: 'center',
},
{
title: '能源类型',
dataIndex: 'energyType',
key: 'energyType',
width: 120,
},
{
title: '单位',
dataIndex: 'unit',
key: 'unit',
width: 160,
},
{
title: '单价',
dataIndex: 'unitPrice',
key: 'unitPrice',
width: 110,
},
{
title: '价格单位',
dataIndex: 'priceUnit',
key: 'priceUnit',
width: 120,
},
{
title: '适用范围',
dataIndex: 'scope',
key: 'scope',
width: 120,
},
{
title: '定价单位',
dataIndex: 'pricingUnit',
key: 'pricingUnit',
width: 140,
},
{
title: '最后更新时间',
dataIndex: 'latestUpdateTime',
key: 'latestUpdateTime',
width: 210,
},
{
title: '操作',
key: 'action',
width: 140,
align: 'center',
render: (_, record) => (
<Space size={14}>
<Button
className={styles.actionIconBtn}
type="text"
icon={<EditOutlined />}
onClick={() => handleEdit(record)}
/>
<Button
className={styles.actionIconBtnInfo}
type="text"
icon={<FileTextOutlined />}
onClick={() => handleView(record)}
/>
<Popconfirm
title="删除确认"
okText="确定"
cancelText="取消"
onConfirm={() => handleDelete(record)}
>
<Button className={styles.actionIconBtnDanger} type="text" icon={<DeleteOutlined />} />
</Popconfirm>
</Space>
),
},
],
[],
);
return (
<div className={styles.container}>
<div style={{
marginBottom: 15,
padding: "8px 13px",
color: 'rgba(72, 81, 255, 1)',
background: "rgba(72, 81, 255, 0.12)",
fontSize: 12,
border: '1px solid rgba(162, 166, 255, 1)',
borderRadius: 8,
}}>
说明标准煤折算系数用于将不同能源消耗统一换算为标准煤当量方便进行能耗对比和分析
</div>
<div className={styles.toolbar}>
<div className={styles.toolbarLeft}>
<Space size={12}>
<Button className={styles.primaryBtn} type="primary" icon={<PlusOutlined />} onClick={handleAdd}>
新增
</Button>
<Button className={styles.ghostBtn} icon={<UploadOutlined />} onClick={handleUpload}>
上传
</Button>
<Button className={styles.ghostBtn} icon={<DownloadOutlined />} onClick={handleBatchDownload}>
批量下载
</Button>
</Space>
</div>
<div className={styles.toolbarRight}>
<div className={styles.filterLabel}>筛选条件</div>
<Space size={10}>
<Select
className={styles.filterSelect}
placeholder="请选择"
options={selectOptionsA}
value={filterA}
allowClear
onChange={setFilterA}
/>
<Select
className={styles.filterSelect}
placeholder="请选择"
options={selectOptionsB}
value={filterB}
allowClear
onChange={setFilterB}
/>
<Button className={styles.queryBtn} onClick={handleQuery}>
查询
</Button>
</Space>
</div>
</div>
<div className={styles.tableWrap}>
<Table
rowKey="id"
columns={columns}
dataSource={pagedData}
size="middle"
pagination={{
current: pagination.current,
pageSize: pagination.pageSize,
total: tableData.length,
showSizeChanger: true,
showQuickJumper: false,
showTotal: (total) => `${total}`,
position: ['bottomRight'],
onChange: (current, pageSize) => setPagination({ current, pageSize }),
}}
rowSelection={{
selectedRowKeys,
onChange: (keys) => setSelectedRowKeys(keys),
columnWidth: 44,
}}
/>
</div>
</div>
);
};
export default Bmzstx;

@ -0,0 +1,160 @@
.container {
width: 100%;
height: 100%;
min-height: 560px;
background: #fff;
border-radius: 14px;
padding: 14px 16px 12px;
overflow: hidden;
}
.toolbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 2px 0 14px;
}
.toolbarLeft {
display: flex;
align-items: center;
}
.toolbarRight {
display: flex;
align-items: center;
}
.filterLabel {
font-size: 12px;
color: #8d93a3;
margin-right: 10px;
}
.filterSelect {
width: 170px;
:global {
.ant-select-selector {
height: 32px !important;
border-radius: 999px !important;
border-color: #e7eaf2 !important;
display: flex !important;
align-items: center !important;
box-shadow: none !important;
}
.ant-select-selection-placeholder {
color: #b1b7c4;
}
}
}
.primaryBtn {
height: 32px !important;
border-radius: 999px !important;
padding: 0 16px !important;
box-shadow: none !important;
background-color: rgba(72, 81, 255, 1);
:global {
.ant-btn-icon {
margin-right: 6px;
}
}
}
.ghostBtn {
height: 32px !important;
border-radius: 999px !important;
padding: 0 16px !important;
border: 1px solid #e7eaf2 !important;
background: #fff !important;
color: #5b6070 !important;
box-shadow: none !important;
}
.queryBtn {
height: 32px !important;
border-radius: 999px !important;
padding: 0 16px !important;
border: 1px solid #e7eaf2 !important;
background: #fff !important;
color: #5b6070 !important;
box-shadow: none !important;
}
.tableWrap {
width: 100%;
:global {
.ant-table {
border-radius: 10px;
}
.ant-table-thead > tr > th {
background: #f7f8fb;
color: #6d7383;
font-weight: 500;
height: 44px;
}
.ant-table-tbody > tr > td {
color: #2b2f3a;
height: 48px;
}
.ant-table-tbody > tr:hover > td {
background: #fafbff;
}
.ant-table-pagination {
margin: 14px 0 0;
}
.ant-pagination-total-text {
color: #8d93a3;
margin-right: 10px;
}
.ant-pagination-options {
margin-left: 10px;
}
}
}
.actionIconBtn {
padding: 0 !important;
height: 24px !important;
color: rgba(72, 81, 255, 1) !important;
:global {
.anticon {
font-size: 16px;
}
}
}
.actionIconBtnInfo {
padding: 0 !important;
height: 24px !important;
color: #13c2c2 !important;
:global {
.anticon {
font-size: 16px;
}
}
}
.actionIconBtnDanger {
padding: 0 !important;
height: 24px !important;
color: #ff4d4f !important;
:global {
.anticon {
font-size: 16px;
}
}
}

@ -0,0 +1,290 @@
import React, { useMemo, useState } from 'react';
import { Button, Radio, Space, Tag } from 'antd';
import { LeftOutlined, PlusOutlined, RightOutlined, SettingOutlined } from '@ant-design/icons';
import styles from './fgpsdsz.less';
import Component1 from '@/assets/basic_data/Component1.svg';
import Component2 from '@/assets/basic_data/Component2.svg';
import Component3 from '@/assets/basic_data/Component3.svg';
const TIME_LABELS = [
'09:00',
'11:00',
'13:00',
'15:00',
'17:00',
'19:00',
'21:00',
'23:00',
'01:00',
'03:00',
'05:00',
'07:00',
'09:00',
];
const MONTH_LABEL = '2025年8月';
const Fgpsdsz = () => {
const [activeEnergyKey, setActiveEnergyKey] = useState('electric');
const [viewMode, setViewMode] = useState('month');
const [periodType, setPeriodType] = useState('peak');
const energyTypes = useMemo(
() => [
{ key: 'electric', label: '电能', icon: Component1 },
{ key: 'water', label: '水', icon: Component2 },
{ key: 'gas', label: '天然气', icon: Component3 },
],
[],
);
const cards = useMemo(() => {
const byEnergyKey = {
electric: [
{
key: 'peak',
title: '高峰时段',
price: '1.58',
unit: '(元/度)',
tags: ['工作日', '周末', '节假日'],
periods: ['起止时刻109:00:0012:00:00', '起止时刻209:00:0012:00:00'],
},
{
key: 'flat',
title: '平时时段',
price: '0.58',
unit: '(元/度)',
tags: ['工作日', '周末', '节假日'],
periods: ['起止时刻109:00:0012:00:00', '起止时刻209:00:0012:00:00'],
},
{
key: 'valley',
title: '低谷时段',
price: '0.28',
unit: '(元/度)',
tags: ['工作日', '周末', '节假日'],
periods: ['起止时刻109:00:0012:00:00', '起止时刻209:00:0012:00:00'],
},
],
water: [
{
key: 'peak',
title: '高峰时段',
price: '2.30',
unit: '(元/m³)',
tags: ['工作日', '周末', '节假日'],
periods: ['起止时刻109:00:0012:00:00', '起止时刻209:00:0012:00:00'],
},
{
key: 'flat',
title: '平时时段',
price: '1.30',
unit: '(元/m³)',
tags: ['工作日', '周末', '节假日'],
periods: ['起止时刻109:00:0012:00:00', '起止时刻209:00:0012:00:00'],
},
{
key: 'valley',
title: '低谷时段',
price: '0.80',
unit: '(元/m³)',
tags: ['工作日', '周末', '节假日'],
periods: ['起止时刻109:00:0012:00:00', '起止时刻209:00:0012:00:00'],
},
],
gas: [
{
key: 'peak',
title: '高峰时段',
price: '3.80',
unit: '(元/m³)',
tags: ['工作日', '周末', '节假日'],
periods: ['起止时刻109:00:0012:00:00', '起止时刻209:00:0012:00:00'],
},
{
key: 'flat',
title: '平时时段',
price: '2.80',
unit: '(元/m³)',
tags: ['工作日', '周末', '节假日'],
periods: ['起止时刻109:00:0012:00:00', '起止时刻209:00:0012:00:00'],
},
{
key: 'valley',
title: '低谷时段',
price: '1.60',
unit: '(元/m³)',
tags: ['工作日', '周末', '节假日'],
periods: ['起止时刻109:00:0012:00:00', '起止时刻209:00:0012:00:00'],
},
],
};
return byEnergyKey[activeEnergyKey] ?? byEnergyKey.electric;
}, [activeEnergyKey]);
const scheduleRows = useMemo(
() => [
{ day: 1, weekday: '一', bars: [{ start: 0, end: 2, type: 'peak' }, { start: 9, end: 13, type: 'valley' }] },
{ day: 2, weekday: '二', bars: [{ start: 0, end: 5, type: 'peak' }, { start: 11, end: 13, type: 'valley' }] },
{ day: 3, weekday: '三', bars: [{ start: 0, end: 4, type: 'peak' }, { start: 10, end: 13, type: 'valley' }] },
{ day: 4, weekday: '四', bars: [{ start: 0, end: 4, type: 'peak' }, { start: 9, end: 13, type: 'valley' }] },
{ day: 5, weekday: '五', bars: [{ start: 0, end: 4, type: 'peak' }, { start: 8, end: 13, type: 'valley' }] },
{ day: 6, weekday: '六', bars: [{ start: 0, end: 5, type: 'peak' }, { start: 11, end: 13, type: 'valley' }] },
{ day: 7, weekday: '日', bars: [{ start: 0, end: 4, type: 'peak' }, { start: 9, end: 13, type: 'valley' }] },
{ day: 8, weekday: '一', bars: [{ start: 0, end: 3, type: 'peak' }, { start: 8, end: 13, type: 'valley' }] },
{ day: 9, weekday: '二', bars: [{ start: 0, end: 5, type: 'peak' }, { start: 10, end: 13, type: 'valley' }] },
{ day: 10, weekday: '三', bars: [{ start: 0, end: 2, type: 'peak' }, { start: 8, end: 13, type: 'valley' }] },
{ day: 11, weekday: '四', bars: [{ start: 0, end: 2, type: 'peak' }, { start: 8, end: 13, type: 'valley' }] },
{ day: 12, weekday: '五', bars: [{ start: 0, end: 2, type: 'peak' }, { start: 8, end: 13, type: 'valley' }] },
],
[],
);
const renderBarStyle = (bar) => {
const left = (bar.start / TIME_LABELS.length) * 100;
const width = ((bar.end - bar.start) / TIME_LABELS.length) * 100;
return { left: `${left}%`, width: `${width}%` };
};
return (
<div className={styles.container}>
<div className={styles.leftPanel}>
<div className={styles.leftTitle}>能源类型</div>
<div className={styles.leftList}>
{energyTypes.map((item) => {
const isActive = item.key === activeEnergyKey;
return (
<Button
key={item.key}
className={`${styles.leftItemBtn} ${isActive ? styles.leftItemActive : ''}`}
onClick={() => setActiveEnergyKey(item.key)}
>
<span className={styles.leftIconCircle}>
<img className={styles.leftIconImg} src={item.icon} alt={item.label} />
</span>
<span className={styles.leftItemText}>{item.label}</span>
</Button>
);
})}
<Button className={styles.leftAddBtn} disabled icon={<PlusOutlined />}>
添加分类
</Button>
</div>
</div>
<div className={styles.rightPanel}>
<div className={styles.cardsRow}>
{cards.map((card) => (
<div key={card.key} className={`${styles.priceCard} ${styles[`priceCard_${card.key}`]}`}>
<div className={styles.priceCardCorner}>
<SettingOutlined />
</div>
<div className={styles.priceCardTop}>
<div className={styles.priceCardTitle}>{card.title}</div>
<div className={styles.priceCardTags}>
{card.tags.map((tag) => (
<Tag key={tag}>{tag}</Tag>
))}
</div>
</div>
<div className={styles.priceCardBody}>
<div className={styles.priceLeft}>
<div className={styles.priceLabel}>单价</div>
<div className={styles.priceValue}>{card.price}</div>
<div className={styles.priceUnit}>{card.unit}</div>
</div>
<div className={styles.priceRight}>
<div className={styles.pricePeriodBox}>
{card.periods.map((text) => (
<div key={text} className={styles.pricePeriodLine}>
{text}
</div>
))}
</div>
</div>
</div>
</div>
))}
</div>
<div className={styles.scheduleSection}>
<div className={styles.scheduleTopBar}>
<div className={styles.scheduleTopLeft} />
<Space className={styles.monthSwitcher} size={8}>
<Button type="text" size="small" icon={<LeftOutlined />} />
<span className={styles.monthText}>{MONTH_LABEL}</span>
<Button type="text" size="small" icon={<RightOutlined />} />
</Space>
<Radio.Group
className={styles.periodRadios}
value={periodType}
onChange={(e) => setPeriodType(e.target.value)}
>
<Radio value="peak">高峰时段</Radio>
<Radio value="flat">平时时段</Radio>
<Radio value="valley">低谷时段</Radio>
</Radio.Group>
</div>
<div className={styles.scheduleGrid}>
<div className={styles.scheduleHeaderRow}>
<div className={styles.scheduleHeaderLeft}>
<Radio.Group
size="small"
value={viewMode}
buttonStyle="solid"
onChange={(e) => setViewMode(e.target.value)}
>
<Radio.Button value="month"></Radio.Button>
<Radio.Button value="week"></Radio.Button>
</Radio.Group>
</div>
<div className={styles.timeHeader}>
{TIME_LABELS.map((t) => (
<div key={t} className={styles.timeHeaderCell}>
{t}
</div>
))}
</div>
</div>
<div className={styles.scheduleBody}>
{scheduleRows.map((row) => (
<div key={row.day} className={styles.scheduleRow}>
<div className={styles.dayCell}>{row.day}</div>
<div className={styles.weekCell}>{row.weekday}</div>
<div className={styles.timeArea}>
{row.bars.map((bar, idx) => {
const isDimmed = periodType && bar.type !== periodType;
return (
<div
key={`${bar.type}-${idx}`}
className={`${styles.timeBar} ${styles[`timeBar_${bar.type}`]} ${
isDimmed ? styles.timeBarDim : ''
}`}
style={renderBarStyle(bar)}
/>
);
})}
</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
);
};
export default Fgpsdsz;

@ -0,0 +1,458 @@
.container {
display: flex;
gap: 15px;
width: 100%;
height: 100%;
min-height: 560px;
padding: 15px 10px;
}
.leftPanel {
width: 200px;
background: #fff;
border-radius: 12px;
padding: 15px 10px;
}
.leftTitle {
font-size: 15px;
font-weight: 500;
color: #2b2f3a;
padding: 2px 10px 14px;
}
.leftList {
display: flex;
flex-direction: column;
gap: 12px;
padding: 0 8px;
}
.leftItemBtn {
width: 100%;
height: 40px !important;
padding: 0 14px !important;
border-radius: 999px !important;
border: 1px solid #e8ecf3 !important;
background: #fff !important;
box-shadow: none !important;
display: flex !important;
align-items: center !important;
justify-content: flex-start !important;
color: #a5adbb !important;
&:hover {
border-color: rgba(72, 81, 255, 0.35) !important;
color: rgba(72, 81, 255, 1) !important;
}
}
.leftItemActive {
background: rgba(72, 81, 255, 1) !important;
border-color: rgba(72, 81, 255, 1) !important;
color: #fff !important;
box-shadow: 0 10px 20px rgba(72, 81, 255, 0.18) !important;
&:hover {
color: #fff !important;
}
.leftIconCircle {
background: rgba(255, 255, 255, 0.26);
color: #fff;
}
}
.leftIconCircle {
width: 18px;
height: 18px;
border-radius: 999px;
display: inline-flex;
align-items: center;
justify-content: center;
background: rgba(72, 81, 255, 0.12);
color: rgba(72, 81, 255, 1);
flex: none;
:global {
.anticon {
font-size: 12px;
line-height: 1;
}
}
}
.leftIconImg {
width: 18px;
height: 18px;
display: block;
object-fit: contain;
}
.leftItemText {
margin-left: 10px;
font-size: 14px;
font-weight: 500;
}
.leftAddBtn {
width: 100%;
height: 40px !important;
border-radius: 999px !important;
border: none !important;
background: #f2f4f8 !important;
color: #b7bdc9 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
box-shadow: none !important;
}
.rightPanel {
flex: 1;
background: #fff;
border-radius: 12px;
padding: 14px 16px 12px;
overflow: hidden;
display: flex;
flex-direction: column;
gap: 14px;
}
.cardsRow {
display: flex;
gap: 16px;
}
.priceCard {
flex: 1;
min-height: 118px;
padding: 14px 16px 12px;
border-radius: 14px;
color: #fff;
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.priceCard_peak {
background: linear-gradient(135deg, #ff5e57 0%, #ffb1ae 100%);
}
.priceCard_flat {
background: linear-gradient(135deg, #ff9c3c 0%, #ffd8a8 100%);
}
.priceCard_valley {
background: linear-gradient(135deg, #4cc9ff 0%, #a7e9ff 100%);
}
.priceCardCorner {
position: absolute;
top: 12px;
right: 12px;
width: 26px;
height: 26px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.35);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
:global {
.anticon {
font-size: 14px;
}
}
}
.priceCardTop {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
}
.priceCardTitle {
font-size: 16px;
font-weight: 600;
line-height: 1;
padding-top: 2px;
}
.priceCardTags {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-right: 28px;
:global {
.ant-tag {
margin: 0;
// margin-right: 30px;
border: none;
border-radius: 999px;
background: rgba(255, 255, 255, 0.26);
color: #fff;
font-size: 12px;
padding: 0 10px;
height: 22px;
line-height: 22px;
}
}
}
.priceCardBody {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 12px;
margin-top: 8px;
}
.priceLeft {
min-width: 96px;
}
.priceLabel {
font-size: 12px;
opacity: 0.92;
}
.priceValue {
font-size: 36px;
font-weight: 700;
line-height: 1.05;
margin-top: 6px;
}
.priceUnit {
font-size: 12px;
opacity: 0.92;
margin-top: 4px;
}
.priceRight {
flex: 1;
display: flex;
justify-content: flex-end;
}
.pricePeriodBox {
width: 220px;
padding: 10px 12px;
border-radius: 12px;
background: rgba(255, 255, 255, 0.22);
backdrop-filter: blur(6px);
}
.pricePeriodLine {
font-size: 12px;
line-height: 18px;
opacity: 0.96;
}
.scheduleSection {
flex: 1;
background: #f7f9ff;
border-radius: 12px;
padding: 12px;
overflow: hidden;
display: flex;
flex-direction: column;
}
.scheduleTopBar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 0 4px 10px;
}
.scheduleTopLeft {
width: 88px;
flex: none;
}
.monthSwitcher {
flex: 1;
justify-content: center;
color: #3757ff;
:global {
.ant-btn {
padding: 0 6px;
}
}
}
.monthText {
font-size: 14px;
font-weight: 600;
}
.periodRadios {
:global {
.ant-radio-wrapper {
color: #6b7280;
font-size: 12px;
margin-inline-end: 10px;
}
.ant-radio-wrapper-checked {
color: #3757ff;
}
}
}
.scheduleGrid {
background: #fff;
border-radius: 12px;
padding: 10px 10px 12px;
overflow: auto;
min-height: 360px;
}
.scheduleHeaderRow {
display: flex;
align-items: center;
gap: 10px;
padding: 0 0 10px;
}
.scheduleHeaderLeft {
width: 88px;
flex: none;
display: flex;
align-items: center;
:global {
.ant-radio-group {
background: #eef3ff;
border-radius: 999px;
padding: 2px;
}
.ant-radio-button-wrapper {
border: none;
height: 24px;
line-height: 22px;
border-radius: 999px !important;
padding: 0 12px;
font-size: 12px;
color: #6b7280;
background: transparent;
box-shadow: none;
}
.ant-radio-button-wrapper-checked {
background: #3a66ff;
color: #fff;
box-shadow: none;
}
}
}
.timeHeader {
flex: 1;
min-width: 780px;
display: flex;
align-items: center;
height: 24px;
border-radius: 999px;
background: #f7f9ff;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
inset: 0;
background-image: linear-gradient(to right, rgba(232, 236, 243, 0.9) 1px, transparent 1px);
background-size: calc(100% / 13) 100%;
pointer-events: none;
}
}
.timeHeaderCell {
flex: 1;
text-align: center;
font-size: 12px;
color: #6b7280;
position: relative;
z-index: 1;
}
.scheduleBody {
display: flex;
flex-direction: column;
gap: 12px;
padding-top: 6px;
}
.scheduleRow {
display: flex;
align-items: center;
gap: 10px;
}
.dayCell {
width: 44px;
text-align: center;
color: #2b2f3a;
font-size: 13px;
}
.weekCell {
width: 44px;
text-align: center;
color: #8d93a3;
font-size: 12px;
}
.timeArea {
flex: 1;
min-width: 780px;
height: 28px;
border-radius: 12px;
background: #f7f9ff;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
inset: 0;
background-image: linear-gradient(to right, rgba(232, 236, 243, 0.9) 1px, transparent 1px);
background-size: calc(100% / 13) 100%;
pointer-events: none;
}
}
.timeBar {
position: absolute;
top: 5px;
height: 18px;
border-radius: 999px;
z-index: 1;
}
.timeBar_peak {
background: rgba(255, 111, 111, 0.92);
}
.timeBar_flat {
background: rgba(255, 177, 61, 0.92);
}
.timeBar_valley {
background: rgba(120, 205, 255, 0.92);
}
.timeBarDim {
opacity: 0.25;
}

@ -0,0 +1,253 @@
import React, { useMemo, useState } from 'react';
import { Button, message, Popconfirm, Select, Space, Table } from 'antd';
import { DeleteOutlined, DownloadOutlined, EditOutlined, FileTextOutlined, PlusOutlined, UploadOutlined } from '@ant-design/icons';
import styles from './jgfx.less';
const Jgfx = () => {
const [filterA, setFilterA] = useState(undefined);
const [filterB, setFilterB] = useState(undefined);
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const [pagination, setPagination] = useState({ current: 1, pageSize: 10 });
const selectOptionsA = useMemo(
() => [
{ label: '千瓦时(kWh)', value: '千瓦时(kWh)' },
{ label: '立方米(m³)', value: '立方米(m³)' },
{ label: '吨(t)', value: '吨(t)' },
{ label: 'kWh', value: 'kWh' },
],
[],
);
const selectOptionsB = useMemo(
() => [
{ label: '电能', value: '电能' },
{ label: '燃气', value: '燃气' },
{ label: '水', value: '水' },
{ label: '汽油', value: '汽油' },
],
[],
);
const rows = useMemo(
() => [
{
id: 1,
energyType: '电能',
unit: '千瓦时(kWh)',
unitPrice: 0.5,
priceUnit: '元',
scope: 'xxxx',
pricingUnit: 'xxxxxx',
latestUpdateTime: '2025-12-02 03:56:02',
},
{
id: 2,
energyType: '燃气',
unit: '立方米(m³)',
unitPrice: 2.5,
priceUnit: '元',
scope: 'xxxx',
pricingUnit: 'xxxxxx',
latestUpdateTime: '2025-11-22 11:56:50',
},
{
id: 3,
energyType: '水',
unit: '吨(t)',
unitPrice: 1.2,
priceUnit: '元',
scope: 'xxxx',
pricingUnit: 'xxxxxx',
latestUpdateTime: '2025-12-04 21:12:20',
},
{
id: 4,
energyType: '汽油',
unit: 'kWh',
unitPrice: 1.5,
priceUnit: '元',
scope: 'xxxx',
pricingUnit: 'xxxxxx',
latestUpdateTime: '2025-11-26 10:28:20',
},
],
[],
);
const tableData = useMemo(() => {
return rows
.filter((r) => (filterA ? r.unit === filterA : true))
.filter((r) => (filterB ? r.energyType === filterB : true));
}, [filterA, filterB, rows]);
const pagedData = useMemo(() => {
const start = (pagination.current - 1) * pagination.pageSize;
return tableData.slice(start, start + pagination.pageSize);
}, [pagination.current, pagination.pageSize, tableData]);
const handleAdd = () => message.info('新增');
const handleUpload = () => message.info('上传');
const handleBatchDownload = () => message.info('批量下载');
const handleQuery = () => message.info('查询');
const handleEdit = (record) => message.info(`编辑:${record.energyType}`);
const handleView = (record) => message.info(`详情:${record.energyType}`);
const handleDelete = (record) => message.success(`已删除:${record.energyType}`);
const columns = useMemo(
() => [
{
title: '序号',
dataIndex: 'id',
key: 'id',
width: 70,
align: 'center',
},
{
title: '能源类型',
dataIndex: 'energyType',
key: 'energyType',
width: 120,
},
{
title: '单位',
dataIndex: 'unit',
key: 'unit',
width: 160,
},
{
title: '单价',
dataIndex: 'unitPrice',
key: 'unitPrice',
width: 110,
},
{
title: '价格单位',
dataIndex: 'priceUnit',
key: 'priceUnit',
width: 120,
},
{
title: '适用范围',
dataIndex: 'scope',
key: 'scope',
width: 120,
},
{
title: '定价单位',
dataIndex: 'pricingUnit',
key: 'pricingUnit',
width: 140,
},
{
title: '最后更新时间',
dataIndex: 'latestUpdateTime',
key: 'latestUpdateTime',
width: 210,
},
{
title: '操作',
key: 'action',
width: 140,
align: 'center',
render: (_, record) => (
<Space size={14}>
<Button
className={styles.actionIconBtn}
type="text"
icon={<EditOutlined />}
onClick={() => handleEdit(record)}
/>
<Button
className={styles.actionIconBtnInfo}
type="text"
icon={<FileTextOutlined />}
onClick={() => handleView(record)}
/>
<Popconfirm
title="删除确认"
okText="确定"
cancelText="取消"
onConfirm={() => handleDelete(record)}
>
<Button className={styles.actionIconBtnDanger} type="text" icon={<DeleteOutlined />} />
</Popconfirm>
</Space>
),
},
],
[],
);
return (
<div className={styles.container}>
<div className={styles.toolbar}>
<div className={styles.toolbarLeft}>
<Space size={12}>
<Button className={styles.primaryBtn} type="primary" icon={<PlusOutlined />} onClick={handleAdd}>
新增
</Button>
<Button className={styles.ghostBtn} icon={<UploadOutlined />} onClick={handleUpload}>
上传
</Button>
<Button className={styles.ghostBtn} icon={<DownloadOutlined />} onClick={handleBatchDownload}>
批量下载
</Button>
</Space>
</div>
<div className={styles.toolbarRight}>
<div className={styles.filterLabel}>筛选条件</div>
<Space size={10}>
<Select
className={styles.filterSelect}
placeholder="请选择"
options={selectOptionsA}
value={filterA}
allowClear
onChange={setFilterA}
/>
<Select
className={styles.filterSelect}
placeholder="请选择"
options={selectOptionsB}
value={filterB}
allowClear
onChange={setFilterB}
/>
<Button className={styles.queryBtn} onClick={handleQuery}>
查询
</Button>
</Space>
</div>
</div>
<div className={styles.tableWrap}>
<Table
rowKey="id"
columns={columns}
dataSource={pagedData}
size="middle"
pagination={{
current: pagination.current,
pageSize: pagination.pageSize,
total: tableData.length,
showSizeChanger: true,
showQuickJumper: false,
showTotal: (total) => `${total}`,
position: ['bottomRight'],
onChange: (current, pageSize) => setPagination({ current, pageSize }),
}}
rowSelection={{
selectedRowKeys,
onChange: (keys) => setSelectedRowKeys(keys),
columnWidth: 44,
}}
/>
</div>
</div>
);
};
export default Jgfx;

@ -0,0 +1,161 @@
.container {
width: 100%;
height: 100%;
min-height: 560px;
background: #fff;
border-radius: 14px;
padding: 14px 16px 12px;
margin:15px 10px;
overflow: hidden;
}
.toolbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 2px 0 14px;
}
.toolbarLeft {
display: flex;
align-items: center;
}
.toolbarRight {
display: flex;
align-items: center;
}
.filterLabel {
font-size: 12px;
color: #8d93a3;
margin-right: 10px;
}
.filterSelect {
width: 170px;
:global {
.ant-select-selector {
height: 32px !important;
border-radius: 999px !important;
border-color: #e7eaf2 !important;
display: flex !important;
align-items: center !important;
box-shadow: none !important;
}
.ant-select-selection-placeholder {
color: #b1b7c4;
}
}
}
.primaryBtn {
height: 32px !important;
border-radius: 999px !important;
padding: 0 16px !important;
box-shadow: none !important;
background-color: rgba(72, 81, 255, 1);
:global {
.ant-btn-icon {
margin-right: 6px;
}
}
}
.ghostBtn {
height: 32px !important;
border-radius: 999px !important;
padding: 0 16px !important;
border: 1px solid #e7eaf2 !important;
background: #fff !important;
color: #5b6070 !important;
box-shadow: none !important;
}
.queryBtn {
height: 32px !important;
border-radius: 999px !important;
padding: 0 16px !important;
border: 1px solid #e7eaf2 !important;
background: #fff !important;
color: #5b6070 !important;
box-shadow: none !important;
}
.tableWrap {
width: 100%;
:global {
.ant-table {
border-radius: 10px;
}
.ant-table-thead > tr > th {
background: #f7f8fb;
color: #6d7383;
font-weight: 500;
height: 44px;
}
.ant-table-tbody > tr > td {
color: #2b2f3a;
height: 48px;
}
.ant-table-tbody > tr:hover > td {
background: #fafbff;
}
.ant-table-pagination {
margin: 14px 0 0;
}
.ant-pagination-total-text {
color: #8d93a3;
margin-right: 10px;
}
.ant-pagination-options {
margin-left: 10px;
}
}
}
.actionIconBtn {
padding: 0 !important;
height: 24px !important;
color: rgba(72, 81, 255, 1) !important;
:global {
.anticon {
font-size: 16px;
}
}
}
.actionIconBtnInfo {
padding: 0 !important;
height: 24px !important;
color: #13c2c2 !important;
:global {
.anticon {
font-size: 16px;
}
}
}
.actionIconBtnDanger {
padding: 0 !important;
height: 24px !important;
color: #ff4d4f !important;
:global {
.anticon {
font-size: 16px;
}
}
}

@ -0,0 +1,10 @@
import React from 'react';
import styles from './nhtj.less';
const Nhtj = () => {
return (
<div className={styles.placeholder}>待开发</div>
);
};
export default Nhtj;

@ -0,0 +1,4 @@
.placeholder {
padding: 20px;
color: #999;
}

@ -0,0 +1,260 @@
import React, { useMemo, useState } from 'react';
import { Button, message, Popconfirm, Select, Space, Table } from 'antd';
import {
DeleteOutlined,
DownloadOutlined,
EditOutlined,
PlusOutlined,
UploadOutlined,
} from '@ant-design/icons';
import styles from './nyfl.less';
import Component1 from '@/assets/basic_data/Component1.svg';
import Component2 from '@/assets/basic_data/Component2.svg';
import Component3 from '@/assets/basic_data/Component3.svg';
const Nhtjcs = () => {
const [activeEnergyKey, setActiveEnergyKey] = useState('electric');
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const [filterA, setFilterA] = useState(undefined);
const [filterB, setFilterB] = useState(undefined);
const energyTypes = useMemo(
() => [
{ key: 'electric', label: '电能', icon: Component1 },
{ key: 'water', label: '水', icon: Component2 },
{ key: 'gas', label: '天然气', icon: Component3 },
],
[],
);
const [rows, setRows] = useState(() => [
{
id: 1,
energyKey: 'electric',
powerType: '泵类系统',
deviceCount: 28,
statisticMethod: '智能电表',
usageDesc: '用于油品输送的各类泵设备的耗电量。',
},
{
id: 2,
energyKey: 'electric',
powerType: '加热保温系统',
deviceCount: 23,
statisticMethod: '智能电表',
usageDesc: '用于油罐加热和管道保温的电加热设备的耗电量。',
},
{
id: 3,
energyKey: 'electric',
powerType: '照明系统',
deviceCount: 55,
statisticMethod: '手动抄表',
usageDesc: '油库区域内照明设备的耗电量,包括室内照明和室外照明。',
},
{
id: 4,
energyKey: 'electric',
powerType: '生活用电系统',
deviceCount: 21,
statisticMethod: '手动抄表',
usageDesc: '油库工作人员生活区的用电,如宿舍、办公区等的耗电量。',
},
]);
const scopedRows = useMemo(() => {
return rows.filter((row) => row.energyKey === activeEnergyKey);
}, [activeEnergyKey, rows]);
const selectOptionsA = useMemo(() => {
const values = Array.from(new Set(scopedRows.map((r) => r.powerType).filter(Boolean)));
return values.map((value) => ({ label: value, value }));
}, [scopedRows]);
const selectOptionsB = useMemo(() => {
const values = Array.from(new Set(scopedRows.map((r) => r.statisticMethod).filter(Boolean)));
return values.map((value) => ({ label: value, value }));
}, [scopedRows]);
const tableData = useMemo(() => {
return scopedRows
.filter((row) => (filterA ? row.powerType === filterA : true))
.filter((row) => (filterB ? row.statisticMethod === filterB : true));
}, [filterA, filterB, scopedRows]);
const handleAdd = () => message.info('新增');
const handleUpload = () => message.info('上传');
const handleBatchDownload = () => message.info('批量下载');
const handleQuery = () => message.info('查询');
const handleEdit = (record) => message.info(`编辑:${record.powerType}`);
const handleDelete = (record) => message.success(`已删除:${record.powerType}`);
const columns = useMemo(
() => [
{
title: '序号',
dataIndex: 'id',
key: 'id',
width: 70,
align: 'center',
},
{
title: '耗电类型',
dataIndex: 'powerType',
key: 'powerType',
width: 180,
},
{
title: '设备数量',
dataIndex: 'deviceCount',
key: 'deviceCount',
width: 120,
align: 'center',
},
{
title: '统计方式',
dataIndex: 'statisticMethod',
key: 'statisticMethod',
width: 140,
},
{
title: '用途描述',
dataIndex: 'usageDesc',
key: 'usageDesc',
ellipsis: true,
},
{
title: '操作',
key: 'action',
width: 120,
align: 'center',
render: (_, record) => (
<Space size={14}>
<Button
className={styles.actionIconBtn}
type="text"
icon={<EditOutlined />}
onClick={() => handleEdit(record)}
/>
<Popconfirm
title="删除确认"
okText="确定"
cancelText="取消"
onConfirm={() => handleDelete(record)}
>
<Button className={styles.actionIconBtnDanger} type="text" icon={<DeleteOutlined />} />
</Popconfirm>
</Space>
),
},
],
[],
);
return (
<div className={styles.container}>
<div className={styles.leftPanel}>
<div className={styles.leftTitle}>能源类型</div>
<div className={styles.leftList}>
{energyTypes.map((item) => {
const isActive = item.key === activeEnergyKey;
return (
<Button
key={item.key}
className={`${styles.leftItemBtn} ${isActive ? styles.leftItemActive : ''}`}
onClick={() => {
setActiveEnergyKey(item.key);
setFilterA(undefined);
setFilterB(undefined);
}}
>
<span className={styles.leftIconCircle}>
<img className={styles.leftIconImg} src={item.icon} alt={item.label} />
</span>
<span className={styles.leftItemText}>{item.label}</span>
</Button>
);
})}
<Button className={styles.leftAddBtn} disabled icon={<PlusOutlined />}>
添加分类
</Button>
</div>
</div>
<div className={styles.rightPanel}>
<div className={styles.toolbar}>
<div className={styles.toolbarLeft}>
<Space size={12}>
<Button
className={styles.primaryBtn}
type="primary"
icon={<PlusOutlined />}
onClick={handleAdd}
>
新增
</Button>
<Button className={styles.ghostBtn} icon={<UploadOutlined />} onClick={handleUpload}>
上传
</Button>
<Button className={styles.ghostBtn} icon={<DownloadOutlined />} onClick={handleBatchDownload}>
批量下载
</Button>
</Space>
</div>
<div className={styles.toolbarRight}>
<div className={styles.filterLabel}>筛选条件</div>
<Space size={10}>
<Select
className={styles.filterSelect}
placeholder="请选择"
options={selectOptionsA}
value={filterA}
allowClear
onChange={setFilterA}
/>
<Select
className={styles.filterSelect}
placeholder="请选择"
options={selectOptionsB}
value={filterB}
allowClear
onChange={setFilterB}
/>
<Button className={styles.queryBtn} onClick={handleQuery}>
查询
</Button>
</Space>
</div>
</div>
<div className={styles.tableWrap}>
<Table
rowKey="id"
columns={columns}
dataSource={tableData}
size="middle"
pagination={{
current: 1,
pageSize: 10,
total: tableData.length,
showSizeChanger: true,
showQuickJumper: false,
}}
rowSelection={{
selectedRowKeys,
onChange: (keys) => setSelectedRowKeys(keys),
columnWidth: 44,
}}
/>
</div>
</div>
</div>
);
};
export default Nhtjcs;

@ -0,0 +1,245 @@
.container {
display: flex;
gap: 15px;
width: 100%;
height: 100%;
min-height: 560px;
padding: 15px 10px
}
.leftPanel {
width: 200px;
background: #fff;
border-radius: 12px;
padding: 15px 10px;
}
.leftTitle {
font-size: 15px;
font-weight: 500;
color: #2b2f3a;
padding: 2px 10px 14px;
}
.leftList {
display: flex;
flex-direction: column;
gap: 12px;
padding: 0 8px;
}
.leftItemBtn {
width: 100%;
height: 40px !important;
padding: 0 14px !important;
border-radius: 999px !important;
border: 1px solid #e8ecf3 !important;
background: #fff !important;
box-shadow: none !important;
display: flex !important;
align-items: center !important;
justify-content: flex-start !important;
color: #a5adbb !important;
&:hover {
border-color: rgba(72, 81, 255, 0.35) !important;
color: rgba(72, 81, 255, 1) !important;
}
}
.leftItemActive {
background: rgba(72, 81, 255, 1) !important;
border-color: rgba(72, 81, 255, 1) !important;
color: #fff !important;
box-shadow: 0 10px 20px rgba(72, 81, 255, 0.18) !important;
&:hover {
color: #fff !important;
}
.leftIconCircle {
background: rgba(255, 255, 255, 0.26);
color: #fff;
}
}
.leftIconCircle {
width: 18px;
height: 18px;
border-radius: 999px;
display: inline-flex;
align-items: center;
justify-content: center;
background: rgba(72, 81, 255, 0.12);
color: rgba(72, 81, 255, 1);
flex: none;
:global {
.anticon {
font-size: 12px;
line-height: 1;
}
}
}
.leftIconImg {
width: 18px;
height: 18px;
display: block;
object-fit: contain;
}
.leftItemText {
margin-left: 10px;
font-size: 14px;
font-weight: 500;
}
.leftAddBtn {
width: 100%;
height: 40px !important;
border-radius: 999px !important;
border: none !important;
background: #f2f4f8 !important;
color: #b7bdc9 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
box-shadow: none !important;
}
.rightPanel {
flex: 1;
background: #fff;
border-radius: 12px;
padding: 14px 16px 12px;
overflow: hidden;
}
.toolbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 2px 0 14px;
}
.toolbarLeft {
display: flex;
align-items: center;
}
.toolbarRight {
display: flex;
align-items: center;
}
.filterLabel {
font-size: 12px;
color: #8d93a3;
margin-right: 10px;
}
.filterSelect {
width: 150px;
:global {
.ant-select-selector {
height: 32px !important;
border-radius: 999px !important;
border-color: #e7eaf2 !important;
display: flex !important;
align-items: center !important;
box-shadow: none !important;
}
.ant-select-selection-placeholder {
color: #b1b7c4;
}
}
}
.primaryBtn {
height: 32px !important;
border-radius: 999px !important;
padding: 0 16px !important;
box-shadow: none !important;
background-color: rgba(72, 81, 255, 1);
:global {
.ant-btn-icon {
margin-right: 6px;
}
}
}
.ghostBtn {
height: 32px !important;
border-radius: 999px !important;
padding: 0 16px !important;
border: 1px solid #e7eaf2 !important;
background: #fff !important;
color: #5b6070 !important;
box-shadow: none !important;
}
.queryBtn {
height: 32px !important;
border-radius: 999px !important;
padding: 0 16px !important;
border: 1px solid #e7eaf2 !important;
background: #fff !important;
color: #5b6070 !important;
box-shadow: none !important;
}
.tableWrap {
width: 100%;
:global {
.ant-table {
border-radius: 10px;
}
.ant-table-thead > tr > th {
background: #f7f8fb;
color: #2b2f3a;
// font-weight: 500;
}
.ant-table-tbody > tr > td {
color: #2b2f3a;
}
.ant-table-tbody > tr:hover > td {
background: #fafbff;
}
.ant-table-pagination {
margin: 12px 0 0;
}
}
}
.actionIconBtn {
padding: 0 !important;
height: 24px !important;
color: rgba(72, 81, 255, 1) !important;
:global {
.anticon {
font-size: 16px;
}
}
}
.actionIconBtnDanger {
padding: 0 !important;
height: 24px !important;
color: #ff4d4f !important;
:global {
.anticon {
font-size: 16px;
}
}
}

@ -0,0 +1,295 @@
import React, { useMemo, useState } from 'react';
import { Button, message, Popconfirm, Select, Space, Switch, Table } from 'antd';
import {
DeleteOutlined,
DownloadOutlined,
EditOutlined,
PlusOutlined,
UploadOutlined,
} from '@ant-design/icons';
import styles from './nyfl.less';
import Component1 from '@/assets/basic_data/Component1.svg';
import Component2 from '@/assets/basic_data/Component2.svg';
import Component3 from '@/assets/basic_data/Component3.svg';
const Nyfl = () => {
const [activeEnergyKey, setActiveEnergyKey] = useState('electric');
const [filterUnit, setFilterUnit] = useState(undefined);
const [filterCollection, setFilterCollection] = useState(undefined);
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const energyTypes = useMemo(
() => [
{ key: 'electric', label: '电能', icon: Component1 },
{ key: 'water', label: '水', icon: Component2 },
{ key: 'gas', label: '天然气', icon: Component3 },
],
[],
);
const unitOptions = useMemo(
() => [
{ label: 'kWh', value: 'kWh' },
{ label: 'm³', value: 'm³' },
{ label: 't', value: 't' },
],
[],
);
const collectionOptions = useMemo(
() => [
{ label: '市电', value: '市电' },
{ label: '光伏发电', value: '光伏发电' },
{ label: '自备发电', value: '自备发电' },
],
[],
);
const [rows, setRows] = useState(() => [
{
id: 1,
energyKey: 'electric',
energyType: '电能',
unit: 'kWh',
collectionType: '市电',
enabled: true,
latestUpdateTime: '2025-12-02 03:56:02',
},
{
id: 2,
energyKey: 'electric',
energyType: '电能',
unit: 'kWh',
collectionType: '光伏发电',
enabled: false,
latestUpdateTime: '2025-11-22 11:56:50',
},
{
id: 3,
energyKey: 'electric',
energyType: '电能',
unit: 'kWh',
collectionType: '自备发电',
enabled: true,
latestUpdateTime: '2025-12-04 21:12:20',
},
{
id: 4,
energyKey: 'electric',
energyType: '电能',
unit: 'kWh',
collectionType: 'xxxxxx',
enabled: false,
latestUpdateTime: '2025-11-26 10:28:20',
},
{
id: 5,
energyKey: 'water',
energyType: '水',
unit: 'm³',
collectionType: '市政供水',
enabled: true,
latestUpdateTime: '2025-12-06 09:12:10',
},
{
id: 6,
energyKey: 'gas',
energyType: '天然气',
unit: 'm³',
collectionType: '管网供气',
enabled: true,
latestUpdateTime: '2025-12-01 16:08:45',
},
]);
const tableData = useMemo(() => {
return rows
.filter((row) => row.energyKey === activeEnergyKey)
.filter((row) => (filterUnit ? row.unit === filterUnit : true))
.filter((row) => (filterCollection ? row.collectionType === filterCollection : true));
}, [activeEnergyKey, filterCollection, filterUnit, rows]);
const handleAdd = () => message.info('新增');
const handleUpload = () => message.info('上传');
const handleBatchDownload = () => message.info('批量下载');
const handleQuery = () => message.info('查询');
const handleEdit = (record) => message.info(`编辑:${record.energyType}`);
const handleDelete = (record) => message.success(`已删除:${record.energyType}`);
const columns = useMemo(
() => [
{
title: '序号',
dataIndex: 'id',
key: 'id',
width: 70,
align: 'center',
},
{
title: '能源类型',
dataIndex: 'energyType',
key: 'energyType',
width: 120,
},
{
title: '单位',
dataIndex: 'unit',
key: 'unit',
width: 120,
},
{
title: '采集类型',
dataIndex: 'collectionType',
key: 'collectionType',
width: 180,
},
{
title: '状态',
dataIndex: 'enabled',
key: 'enabled',
width: 120,
align: 'center',
render: (value, record) => (
<Switch
size="small"
checked={Boolean(value)}
onChange={(checked) => {
setRows((prev) =>
prev.map((item) => (item.id === record.id ? { ...item, enabled: checked } : item)),
);
message.success(checked ? '已启用' : '已停用');
}}
/>
),
},
{
title: '最后更新时间',
dataIndex: 'latestUpdateTime',
key: 'latestUpdateTime',
width: 200,
},
{
title: '操作',
key: 'action',
width: 120,
align: 'center',
render: (_, record) => (
<Space size={14}>
<Button
className={styles.actionIconBtn}
type="text"
icon={<EditOutlined />}
onClick={() => handleEdit(record)}
/>
<Popconfirm
title="删除确认"
okText="确定"
cancelText="取消"
onConfirm={() => handleDelete(record)}
>
<Button className={styles.actionIconBtnDanger} type="text" icon={<DeleteOutlined />} />
</Popconfirm>
</Space>
),
},
],
[],
);
return (
<div className={styles.container}>
<div className={styles.leftPanel}>
<div className={styles.leftTitle}>能源类型</div>
<div className={styles.leftList}>
{energyTypes.map((item) => {
const isActive = item.key === activeEnergyKey;
return (
<Button
key={item.key}
className={`${styles.leftItemBtn} ${isActive ? styles.leftItemActive : ''}`}
onClick={() => setActiveEnergyKey(item.key)}
>
<span className={styles.leftIconCircle}>
<img className={styles.leftIconImg} src={item.icon} alt={item.label} />
</span>
<span className={styles.leftItemText}>{item.label}</span>
</Button>
);
})}
<Button className={styles.leftAddBtn} disabled icon={<PlusOutlined />}>
添加分类
</Button>
</div>
</div>
<div className={styles.rightPanel}>
<div className={styles.toolbar}>
<div className={styles.toolbarLeft}>
<Space size={12}>
<Button className={styles.primaryBtn} type="primary" icon={<PlusOutlined />} onClick={handleAdd}>
新增
</Button>
<Button className={styles.ghostBtn} icon={<UploadOutlined />} onClick={handleUpload}>
上传
</Button>
<Button className={styles.ghostBtn} icon={<DownloadOutlined />} onClick={handleBatchDownload}>
批量下载
</Button>
</Space>
</div>
<div className={styles.toolbarRight}>
<div className={styles.filterLabel}>筛选条件</div>
<Space size={10}>
<Select
className={styles.filterSelect}
placeholder="请选择"
options={unitOptions}
value={filterUnit}
allowClear
onChange={setFilterUnit}
/>
<Select
className={styles.filterSelect}
placeholder="请选择"
options={collectionOptions}
value={filterCollection}
allowClear
onChange={setFilterCollection}
/>
<Button className={styles.queryBtn} onClick={handleQuery}>
查询
</Button>
</Space>
</div>
</div>
<div className={styles.tableWrap}>
<Table
rowKey="id"
columns={columns}
dataSource={tableData}
size="middle"
pagination={{
current: 1,
pageSize: 10,
total: tableData.length,
showSizeChanger: true,
showQuickJumper: false,
}}
rowSelection={{
selectedRowKeys,
onChange: (keys) => setSelectedRowKeys(keys),
columnWidth: 44,
}}
/>
</div>
</div>
</div>
);
};
export default Nyfl;

@ -0,0 +1,245 @@
.container {
display: flex;
gap: 15px;
width: 100%;
height: 100%;
min-height: 560px;
padding: 15px 10px
}
.leftPanel {
width: 200px;
background: #fff;
border-radius: 12px;
padding: 15px 10px;
}
.leftTitle {
font-size: 15px;
font-weight: 500;
color: #2b2f3a;
padding: 2px 10px 14px;
}
.leftList {
display: flex;
flex-direction: column;
gap: 12px;
padding: 0 8px;
}
.leftItemBtn {
width: 100%;
height: 40px !important;
padding: 0 14px !important;
border-radius: 999px !important;
border: 1px solid #e8ecf3 !important;
background: #fff !important;
box-shadow: none !important;
display: flex !important;
align-items: center !important;
justify-content: flex-start !important;
color: #a5adbb !important;
&:hover {
border-color: rgba(72, 81, 255, 0.35) !important;
color: rgba(72, 81, 255, 1) !important;
}
}
.leftItemActive {
background: rgba(72, 81, 255, 1) !important;
border-color: rgba(72, 81, 255, 1) !important;
color: #fff !important;
box-shadow: 0 10px 20px rgba(72, 81, 255, 0.18) !important;
&:hover {
color: #fff !important;
}
.leftIconCircle {
background: rgba(255, 255, 255, 0.26);
color: #fff;
}
}
.leftIconCircle {
width: 18px;
height: 18px;
border-radius: 999px;
display: inline-flex;
align-items: center;
justify-content: center;
background: rgba(72, 81, 255, 0.12);
color: rgba(72, 81, 255, 1);
flex: none;
:global {
.anticon {
font-size: 12px;
line-height: 1;
}
}
}
.leftIconImg {
width: 18px;
height: 18px;
display: block;
object-fit: contain;
}
.leftItemText {
margin-left: 10px;
font-size: 14px;
font-weight: 500;
}
.leftAddBtn {
width: 100%;
height: 40px !important;
border-radius: 999px !important;
border: none !important;
background: #f2f4f8 !important;
color: #b7bdc9 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
box-shadow: none !important;
}
.rightPanel {
flex: 1;
background: #fff;
border-radius: 12px;
padding: 14px 16px 12px;
overflow: hidden;
}
.toolbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 2px 0 14px;
}
.toolbarLeft {
display: flex;
align-items: center;
}
.toolbarRight {
display: flex;
align-items: center;
}
.filterLabel {
font-size: 12px;
color: #8d93a3;
margin-right: 10px;
}
.filterSelect {
width: 150px;
:global {
.ant-select-selector {
height: 32px !important;
border-radius: 999px !important;
border-color: #e7eaf2 !important;
display: flex !important;
align-items: center !important;
box-shadow: none !important;
}
.ant-select-selection-placeholder {
color: #b1b7c4;
}
}
}
.primaryBtn {
height: 32px !important;
border-radius: 999px !important;
padding: 0 16px !important;
box-shadow: none !important;
background-color: rgba(72, 81, 255, 1);
:global {
.ant-btn-icon {
margin-right: 6px;
}
}
}
.ghostBtn {
height: 32px !important;
border-radius: 999px !important;
padding: 0 16px !important;
border: 1px solid #e7eaf2 !important;
background: #fff !important;
color: #5b6070 !important;
box-shadow: none !important;
}
.queryBtn {
height: 32px !important;
border-radius: 999px !important;
padding: 0 16px !important;
border: 1px solid #e7eaf2 !important;
background: #fff !important;
color: #5b6070 !important;
box-shadow: none !important;
}
.tableWrap {
width: 100%;
:global {
.ant-table {
border-radius: 10px;
}
.ant-table-thead > tr > th {
background: #f7f8fb;
color: #6d7383;
font-weight: 500;
}
.ant-table-tbody > tr > td {
color: #2b2f3a;
}
.ant-table-tbody > tr:hover > td {
background: #fafbff;
}
.ant-table-pagination {
margin: 12px 0 0;
}
}
}
.actionIconBtn {
padding: 0 !important;
height: 24px !important;
color: rgba(72, 81, 255, 1) !important;
:global {
.anticon {
font-size: 16px;
}
}
}
.actionIconBtnDanger {
padding: 0 !important;
height: 24px !important;
color: #ff4d4f !important;
:global {
.anticon {
font-size: 16px;
}
}
}

@ -0,0 +1,271 @@
import { useState, useEffect } from 'react'
import { Col, DatePicker, Form, Input, Modal, Row, Select } from 'antd'
import SelectDeptTree from '@/components/SelectDeptTree'
import SelectOrganTree from '@/components/SelectOrganTree'
import datadictionary from '@/utils/dataDictionary'
import { formatDictOptions, verifyPhone } from '@/utils/globalCommon'
import { NumberInput } from '@/components/NumberInput'
import styles from '../StaffSheetList.less'
import style from '@/global.less'
import dayjs from 'dayjs'
import { formatDate } from '@/utils/formatUtils'
const { Item: FormItem } = Form
const { TextArea } = Input
const dictData = datadictionary
//新增表单
let getDeptTreeBySelectTree
let getOrganTreeBySelectTree
const StaffSheetCreateForm = (props => {
const [form] = Form.useForm()
const [jobStatus, setJobStatus] = useState('1')
const {
modalVisible,
handleAdd,
handleModalVisible,
loading,
dispatch,
selectDeptTree,
selectOrganTree
} = props
useEffect(() => {
form.setFieldsValue({
user_type: 'employee',
job_status: '1',
mgr_type: '0'
})
}, [])
const selectedDeptTreeValue = (deptRecord) => {
getDeptTreeBySelectTree = deptRecord
}
const selectedOrganTreeValue = (orgRecord) => {
getOrganTreeBySelectTree = orgRecord
}
const parentDeptTreeMethod = {
dispatch: dispatch,
selectDeptTree: selectDeptTree,
selectedDeptTreeValue: selectedDeptTreeValue,
}
const parentOrganTreeMethod = {
dispatch: dispatch,
selectOrganTree: selectOrganTree,
selectedOrganTreeValue: selectedOrganTreeValue
}
const okHandle = () => {
form.validateFields()
.then(fieldsValue => {
form.resetFields()
fieldsValue.birthday = formatDate(fieldsValue.birthday, 'YYYY-MM-DD')
fieldsValue.hiredate = formatDate(fieldsValue.hiredate, 'YYYY-MM-DD')
fieldsValue.departure_time = formatDate(fieldsValue.departure_time, 'YYYY-MM-DD')
fieldsValue.posts = fieldsValue.posts ? JSON.stringify(fieldsValue.posts) : null
// if (getDeptTreeBySelectTree) {
// fieldsValue.dept_code = getDeptTreeBySelectTree.dept_code
// fieldsValue.dept_name = getDeptTreeBySelectTree.title
// }
if (getOrganTreeBySelectTree) {
fieldsValue.org_code = getOrganTreeBySelectTree.org_code
fieldsValue.org_name = getOrganTreeBySelectTree.title
}
handleAdd(fieldsValue)
})
.catch(errInfo => {})
}
const afterClose = () =>{
form.resetFields();
}
const handleJobStatusChange = (value) => {
setJobStatus(value)
}
return (
<Modal
width={800}
height={550}
bodyStyle={{ height: '500px', overflowY: 'auto' }}
className={style.createForm}
centered
destroyOnClose
title='新增'
open={modalVisible}
onOk={okHandle}
onCancel={() => handleModalVisible()}
afterClose={() => afterClose()}
confirmLoading={loading}
>
<Form form={form} layout='vertical' requiredMark={false}>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='登录账号' name='user_name' rules={[{ required: true, message: '请输入至少2个字符的用户名', min: 2 }]}>
<Input placeholder='请输入' />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='用户名称' name='user_name_cn' rules={[{ required: true, message: '请输入至少2个字符的用户名称', min: 2 }]}>
<Input placeholder='请输入' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='用户性别' name='sex'>
<Select options={formatDictOptions(dictData.sys_user_sex, 'dict_label', 'dict_value')} placeholder='请选择' />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='用户生日' name='birthday'>
<DatePicker format='YYYY-MM-DD' placeholder='请选择' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='手机号码' name='phone' rules={[{ required: false, min: 1, validator: verifyPhone }]}>
<NumberInput placeholder='请输入' style={{width: '100%'}} maxLength={11} />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='电子邮箱' name='email' rules={[{ type: 'email' }]}>
<Input placeholder='请输入' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='机构名称' name='org_code'>
<SelectOrganTree {...parentOrganTreeMethod} />
</FormItem>
</Col>
{/*<Col md={12} sm={24}>*/}
{/* <FormItem label='部门名称' name='dept_code'>*/}
{/* <SelectDeptTree {...parentDeptTreeMethod} placeholder={'请选择部门'} />*/}
{/* </FormItem>*/}
{/*</Col>*/}
<Col md={12} sm={24}>
<FormItem label='所属岗位' name='posts'>
<Select
mode='multiple'
allowClear
placeholder='请选择'
options={formatDictOptions(dictData.sys_user_post, 'dict_label', 'dict_value')}
/>
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
{/*<Col md={12} sm={24}>
<FormItem
label={
<span>
密码
<em className={styles.optional}>
<Tooltip title='默认密码123456'>
<InfoCircleOutlined style={{ marginLeft: 4 }} />
</Tooltip>
</em>
</span>
}
name='password'
initialValue={'123456'}
rules={[{required: true, message: '请输入至少6个字符的密码', min: 6}]}>
<Input placeholder='请输入' type='password'/>
</FormItem>
</Col>*/}
<Col md={12} sm={24}>
<FormItem label='在职状态' name='job_status'>
<Select
placeholder='请选择'
options={formatDictOptions(dictData.sys_job_status, 'dict_label', 'dict_value')}
onChange={handleJobStatusChange}
/>
</FormItem>
</Col>
{jobStatus === '1' ?
<Col md={12} sm={24}>
<FormItem label='入职时间' name='hiredate' initialValue={dayjs().endOf('day')}>
<DatePicker format='YYYY-MM-DD' placeholder='请选择' />
</FormItem>
</Col>
:
<Col md={12} sm={24}>
<FormItem label='离职时间' name='departure_time'>
<DatePicker format='YYYY-MM-DD' placeholder='请选择' />
</FormItem>
</Col>
}
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='员工类型' name='user_type'>
<Select
placeholder='请选择'
options={formatDictOptions(dictData.sys_user_type, 'dict_label', 'dict_value')}
/>
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='员工工号' name='emp_no'>
<Input placeholder='请输入' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='门禁卡号' name='access_card_no'>
<Input placeholder='请输入' />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='是否是管理员' name='mgr_type'>
<Select
style={{width: '100%'}}
placeholder='请选择'
options={formatDictOptions(dictData.sys_mgr_type, 'dict_label', 'dict_value')}
/>
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={24} sm={24}>
<FormItem label='备注' name='remarks'>
<TextArea rows={4} />
</FormItem>
</Col>
</Row>
</Form>
</Modal>
)
})
export default StaffSheetCreateForm

@ -0,0 +1,113 @@
import { useEffect } from 'react'
import { Button, Col, Form, Input, Row } from 'antd'
import { UpOutlined, SearchOutlined, RedoOutlined } from '@ant-design/icons'
import SelectDeptTree from '@/components/SelectDeptTree'
import SelectOrganTree from '@/components/SelectOrganTree'
import style from '@/global.less'
const { Item: FormItem } = Form
let getDeptTreeBySelectTree
let getOrganTreeBySelectTree
const StaffSheetRenderAdvancedForm = (props) => {
const [form] = Form.useForm()
const { dispatch, handleSearch, handleFormReset, toggleForm, selectDeptTree, selectOrganTree, params } = props
useEffect(() => {
form.setFieldsValue({
user_name: params?.user_name,
user_name_cn: params?.user_name_cn,
deptname: params?.deptname,
orgname: params?.orgname,
})
}, [params])
const onFinish = values => {
// if (getDeptTreeBySelectTree) {
// values.dept_code = getDeptTreeBySelectTree.dept_code
// values.deptname = getDeptTreeBySelectTree.title
// }
if (getOrganTreeBySelectTree) {
values.org_code = getOrganTreeBySelectTree.org_code
values.orgname = getOrganTreeBySelectTree.title
}
handleSearch(values)
}
const myHandleFormReset = () => {
form.resetFields()
handleFormReset()
}
const selectedDeptTreeValue = (deptRecord) => {
getDeptTreeBySelectTree = deptRecord
}
const selectedOrganTreeValue = (orgRecord) => {
getOrganTreeBySelectTree = orgRecord
}
const parentDeptTreeMethod = {
dispatch: dispatch,
selectDeptTree: selectDeptTree,
selectedDeptTreeValue: selectedDeptTreeValue
}
const parentOrganTreeMethod = {
dispatch: dispatch,
selectOrganTree: selectOrganTree,
selectedOrganTreeValue: selectedOrganTreeValue
}
return (
<Form form={form} onFinish={onFinish} layout='inline'>
<Row gutter={{ md: 8, lg: 24, xl: 48 }} className={style.searchInput}>
<Col md={8} sm={24}>
<FormItem label='用户名' name='user_name'>
<Input placeholder='请输入' />
</FormItem>
</Col>
<Col md={8} sm={24}>
<FormItem label='用户名称' name='user_name_cn'>
<Input placeholder='请输入' />
</FormItem>
</Col>
<Col md={8} sm={24}>
<FormItem label='机构代码' name='orgname'>
<SelectOrganTree {...parentOrganTreeMethod} />
</FormItem>
</Col>
</Row>
<Row gutter={{md: 8, lg: 24, xl: 48}} className={style.searchBox}>
{/*<Col md={8} sm={24}>*/}
{/* <FormItem label='部门名称' name='deptname'>*/}
{/* <SelectDeptTree placeholder={'请选择部门'} {...parentDeptTreeMethod} />*/}
{/* </FormItem>*/}
{/*</Col>*/}
<Col md={24} sm={24}>
<div className={style.searchBtn}>
<Button type='primary' htmlType='submit'>
查询
</Button>
<Button onClick={myHandleFormReset}>
重置
</Button>
<a onClick={() => toggleForm(form)}>
收起 <UpOutlined />
</a>
</div>
</Col>
</Row>
</Form>
)
}
export default StaffSheetRenderAdvancedForm

@ -0,0 +1,81 @@
import { useEffect } from 'react'
import {Button, Col, Form, Input, Row, DatePicker, Select} from 'antd'
import {DownOutlined, RedoOutlined, SearchOutlined} from '@ant-design/icons'
import style from '@/global.less'
import dayjs from 'dayjs'
const { Item: FormItem } = Form
const StaffSheetRenderSimpleForm = (props) => {
const [form] = Form.useForm()
const { handleSearch, handleFormReset, toggleForm, params } = props
useEffect(() => {
form.setFieldsValue({
user_name: params?.user_name,
user_name_cn: params?.user_name_cn,
})
}, [params])
const onFinish = values => {
handleSearch(values)
}
const myHandleFormReset = () => {
form.resetFields()
handleFormReset()
}
return (
<Form form={form} onFinish={onFinish} layout='inline'>
<Row gutter={{ md: 8, lg: 24, xl: 48 }} className={style.searchInput}>
<Col md={4} sm={24}>
<FormItem label='我的查询条件' name='wdcxtj'>
<Select
placeholder='请选择'
options={[]}
/>
</FormItem>
</Col>
<Col md={4} sm={24}>
<FormItem label='日期' name='rq' rules={[{ required: true, message: '请选择日期!' }]}>
<DatePicker defaultValue={dayjs('2025-04-10', 'YYYY-MM-DD')} format='YYYY-MM-DD' />
</FormItem>
</Col>
<Col md={4} sm={24}>
<FormItem label='工作地点' name='gzdd'>
<Input placeholder='请输入' />
</FormItem>
</Col>
<Col md={4} sm={24}>
<FormItem label='工号' name='gh'>
<Input placeholder='请输入' defaultValue="123456"/>
</FormItem>
</Col>
<Col md={4} sm={24}>
<FormItem label='名称' name='gh'>
<Input placeholder='请输入'/>
</FormItem>
</Col>
<Col md={4} sm={24}>
<div className={style.searchBtn}>
<Button type='primary' htmlType='submit'>
查询
</Button>
<Button onClick={myHandleFormReset}>
重置
</Button>
</div>
</Col>
</Row>
</Form>
)
}
export default StaffSheetRenderSimpleForm

@ -0,0 +1,362 @@
import { useState, useEffect } from 'react'
import { Col, DatePicker, Form, Input, Modal, Row, Select } from 'antd'
import SelectOrganTree from '@/components/SelectOrganTree'
import datadictionary from '@/utils/dataDictionary'
import { formatDictOptions, verifyPhone } from '@/utils/globalCommon'
import { NumberInput } from '@/components/NumberInput'
import styles from '../StaffSheetList.less'
import style from '@/global.less'
import dayjs from 'dayjs'
import { formatDate, formatDateObject } from '@/utils/formatUtils'
const { Item: FormItem } = Form
const { TextArea } = Input
const dictData = datadictionary
//新增表单
let getDeptTreeBySelectTree
let getOrganTreeBySelectTree
const StaffSheetUpdateForm = (props) => {
const [form] = Form.useForm()
const [jobStatus, setJobStatus] = useState('1')
const [userStatus, setUserStatus] = useState('0')
const {
handleUpdate,
updateModalVisible,
handleUpdateModalVisible,
values,
loading,
dispatch,
selectDeptTree,
selectOrganTree
} = props
const selectedDeptTreeValue = (deptRecord) => {
getDeptTreeBySelectTree = deptRecord
}
const selectedOrganTreeValue = (orgRecord) => {
getOrganTreeBySelectTree = orgRecord
}
const parentDeptTreeMethod = {
dispatch: dispatch,
selectDeptTree: selectDeptTree,
selectedDeptTreeValue: selectedDeptTreeValue,
}
const parentOrganTreeMethod = {
dispatch: dispatch,
selectOrganTree: selectOrganTree,
selectedOrganTreeValue: selectedOrganTreeValue
}
useEffect(() => {
setJobStatus(values.job_status)
setUserStatus(values.status)
form.setFieldsValue({
user_id: values.user_id,
user_name: values.user_name,
user_name_cn: values.user_name_cn,
user_name_en: values.user_name_en,
password: values.password,
email: values.email,
phone: values.phone,
landline: values.landline,
sex: values.sex,
avatar: values.avatar,
sign: values.sign,
tags: values.tags,
id_card: values.id_card,
birthday: formatDateObject(values.birthday, 'YYYY-MM-DD'),
job_status: values.job_status,
hiredate: formatDateObject(values.hiredate, 'YYYY-MM-DD'),
departure_time: formatDateObject(values.departure_time, 'YYYY-MM-DD'),
user_type: values.user_type,
emp_no: values.emp_no,
access_card_no: values.access_card_no,
country: values.country,
province: values.province,
city: values.city,
address: values.address,
work_addr: values.work_addr,
floor: values.floor,
inprovince: values.inprovince,
// dept_code: values.dept_code,
// dept_name: values.dept_name,
inner_dept_code: values.inner_dept_code,
org_code: values.org_code,
org_name: values.org_name,
inner_org_code: values.inner_org_code,
posts: values.posts ? JSON.parse(values.posts) : [],
wx_openid: values.wx_openid,
wx_mpopenid: values.wx_mpopenid,
wx_miniopenid: values.wx_miniopenid,
wx_unionid: values.wx_unionid,
mobile_imei: values.mobile_imei,
device_num: values.device_num,
al_taobao: values.al_taobao,
al_alipay: values.al_alipay,
al_dingding: values.al_dingding,
is_system_user: values.is_system_user,
mgr_type: values.mgr_type,
pwd_security_level: values.pwd_security_level,
pwd_update_date: values.pwd_update_date,
last_login_ip: values.last_login_ip,
last_login_date: values.last_login_date,
freeze_date: values.freeze_date,
freeze_cause: values.freeze_cause,
zindex: values.zindex,
wx_msg: values.wx_msg,
email_msg: values.email_msg,
system_msg: values.system_msg,
remarks: values.remarks,
status: values.status,
creator: values.creator,
create_date: values.create_date,
updater: values.updater,
update_date: values.update_date
})
}, [])
const handleLocalUpdate = () => {
form
.validateFields()
.then(fieldsValue => {
const formVals = {...values, ...fieldsValue}
formVals.birthday = formatDate(formVals.birthday, 'YYYY-MM-DD')
formVals.hiredate = formatDate(formVals.hiredate, 'YYYY-MM-DD')
formVals.departure_time = formatDate(formVals.departure_time, 'YYYY-MM-DD')
formVals.posts = formVals.posts ? JSON.stringify(formVals.posts) : null
formVals.freeze_date = '3' === formVals.status ? formatDate(dayjs().endOf('day'), 'YYYY-MM-DD') : null
formVals.freeze_cause = '3' === formVals.status ? formVals.freeze_cause : null
// if (getDeptTreeBySelectTree) {
// formVals.dept_code = getDeptTreeBySelectTree.dept_code
// formVals.dept_name = getDeptTreeBySelectTree.title
// }
if (getOrganTreeBySelectTree) {
formVals.org_code = getOrganTreeBySelectTree.org_code
formVals.org_name = getOrganTreeBySelectTree.title
}
handleUpdate(formVals)
})
.catch(errInfo => {})
}
const afterClose = () =>{
form.resetFields();
}
const handleJobStatusChange = (value) => {
setJobStatus(value)
}
const handleUserStatusChange = (value) => {
setUserStatus(value)
}
return (
<Modal
width={800}
height={550}
bodyStyle={{ height: '500px', overflowY: 'auto' }}
className={style.updateForm}
centered
destroyOnClose
title='修改'
open={updateModalVisible}
onOk={() => handleLocalUpdate()}
onCancel={() => handleUpdateModalVisible()}
afterClose={() => afterClose()}
confirmLoading={loading}
>
<Form form={form} layout='vertical' requiredMark={false}>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='登录账号' name='user_name' rules={[{ required: true, message: '请输入至少2个字符的用户名', min: 2 }]}>
<Input placeholder='请输入' />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='用户名称' name='user_name_cn' rules={[{ required: true, message: '请输入至少2个字符的用户名称', min: 2 }]}>
<Input placeholder='请输入' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='用户性别' name='sex'>
<Select options={formatDictOptions(dictData.sys_user_sex, 'dict_label', 'dict_value')} placeholder='请选择' />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='用户生日' name='birthday'>
<DatePicker format='YYYY-MM-DD' placeholder='请选择' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='手机号码' name='phone' rules={[{ required: false, min: 1, validator: verifyPhone }]}>
<NumberInput placeholder='请输入' style={{width: '100%'}} maxLength={11} />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='电子邮箱' name='email' rules={[{ type: 'email' }]}>
<Input placeholder='请输入' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='机构名称' name='org_code'>
<SelectOrganTree {...parentOrganTreeMethod} />
</FormItem>
</Col>
{/*<Col md={12} sm={24}>*/}
{/* <FormItem label='部门名称' name='dept_code'>*/}
{/* <SelectDeptTree {...parentDeptTreeMethod} placeholder={'请选择部门'} />*/}
{/* </FormItem>*/}
{/*</Col>*/}
<Col md={12} sm={24}>
<FormItem label='所属岗位' name='posts'>
<Select
mode='multiple'
allowClear
placeholder='请选择'
options={formatDictOptions(dictData.sys_user_post, 'dict_label', 'dict_value')}
/>
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
{/*<Col md={12} sm={24}>
<FormItem
label={
<span>
密码
<em className={styles.optional}>
<Tooltip title='默认密码123456'>
<InfoCircleOutlined style={{ marginLeft: 4 }} />
</Tooltip>
</em>
</span>
}
name='password'
initialValue={'123456'}
rules={[{required: true, message: '请输入至少6个字符的密码', min: 6}]}>
<Input placeholder='请输入' type='password'/>
</FormItem>
</Col>*/}
<Col md={12} sm={24}>
<FormItem label='在职状态' name='job_status'>
<Select
placeholder='请选择'
options={formatDictOptions(dictData.sys_job_status, 'dict_label', 'dict_value')}
onChange={handleJobStatusChange}
/>
</FormItem>
</Col>
{jobStatus === '1' ?
<Col md={12} sm={24}>
<FormItem label='入职时间' name='hiredate' initialValue={dayjs().endOf('day')}>
<DatePicker format='YYYY-MM-DD' placeholder='请选择' />
</FormItem>
</Col>
:
<Col md={12} sm={24}>
<FormItem label='离职时间' name='departure_time'>
<DatePicker format='YYYY-MM-DD' placeholder='请选择' />
</FormItem>
</Col>
}
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='员工类型' name='user_type'>
<Select
placeholder='请选择'
options={formatDictOptions(dictData.sys_user_type, 'dict_label', 'dict_value')}
/>
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='员工工号' name='emp_no'>
<Input placeholder='请输入' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='门禁卡号' name='access_card_no'>
<Input placeholder='请输入' />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='是否是管理员' name='mgr_type'>
<Select
style={{width: '100%'}}
placeholder='请选择'
options={formatDictOptions(dictData.sys_mgr_type, 'dict_label', 'dict_value')}
/>
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='状态' name='status'>
<Select
placeholder='请选择'
options={formatDictOptions(dictData.user_status, 'dict_label', 'dict_value')}
onChange={handleUserStatusChange}
/>
</FormItem>
</Col>
{ userStatus === '3' &&
<Col md={12} sm={24}>
<FormItem label='冻结原因' name='freeze_cause'>
<Input placeholder='请输入' />
</FormItem>
</Col>
}
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={24} sm={24}>
<FormItem label='备注' name='remarks'>
<TextArea rows={4} />
</FormItem>
</Col>
</Row>
</Form>
</Modal>
)
}
export default StaffSheetUpdateForm

@ -0,0 +1,299 @@
import { useState, useEffect } from 'react'
import {Col, DatePicker, Form, Input, Modal, Row, Select} from 'antd'
import datadictionary from '@/utils/dataDictionary'
import style from "@/global.less";
import {formatDictOptions, verifyPhone} from "@/utils/globalCommon";
import {NumberInput} from "@/components/NumberInput";
import dayjs from "dayjs";
import SelectOrganTree from "@/components/SelectOrganTree";
import {formatDateObject} from "@/utils/formatUtils";
const { Item: FormItem } = Form
const { TextArea } = Input
const dictData = datadictionary
const StaffSheetViewForm = (props) => {
const [form] = Form.useForm()
const [jobStatus, setJobStatus] = useState('1')
const [userStatus, setUserStatus] = useState('0')
const { viewModalVisible, handleViewModalVisible, values } = props
useEffect(() => {
setJobStatus(values.job_status)
setUserStatus(values.status)
form.setFieldsValue({
user_id: values.user_id,
user_name: values.user_name,
user_name_cn: values.user_name_cn,
user_name_en: values.user_name_en,
password: values.password,
email: values.email,
phone: values.phone,
landline: values.landline,
sex: values.sex,
avatar: values.avatar,
sign: values.sign,
tags: values.tags,
id_card: values.id_card,
birthday: formatDateObject(values.birthday, 'YYYY-MM-DD'),
job_status: values.job_status,
hiredate: formatDateObject(values.hiredate, 'YYYY-MM-DD'),
departure_time: formatDateObject(values.departure_time, 'YYYY-MM-DD'),
user_type: values.user_type,
emp_no: values.emp_no,
access_card_no: values.access_card_no,
country: values.country,
province: values.province,
city: values.city,
address: values.address,
work_addr: values.work_addr,
floor: values.floor,
inprovince: values.inprovince,
// dept_code: values.dept_code,
// dept_name: values.dept_name,
inner_dept_code: values.inner_dept_code,
org_code: values.org_code,
org_name: values.org_name,
inner_org_code: values.inner_org_code,
posts: values.posts ? JSON.parse(values.posts) : [],
wx_openid: values.wx_openid,
wx_mpopenid: values.wx_mpopenid,
wx_miniopenid: values.wx_miniopenid,
wx_unionid: values.wx_unionid,
mobile_imei: values.mobile_imei,
device_num: values.device_num,
al_taobao: values.al_taobao,
al_alipay: values.al_alipay,
al_dingding: values.al_dingding,
is_system_user: values.is_system_user,
mgr_type: values.mgr_type,
pwd_security_level: values.pwd_security_level,
pwd_update_date: values.pwd_update_date,
last_login_ip: values.last_login_ip,
last_login_date: values.last_login_date,
freeze_date: values.freeze_date,
freeze_cause: values.freeze_cause,
zindex: values.zindex,
wx_msg: values.wx_msg,
email_msg: values.email_msg,
system_msg: values.system_msg,
remarks: values.remarks,
status: values.status,
creator: values.creator,
create_date: values.create_date,
updater: values.updater,
update_date: values.update_date
})
}, [])
const afterClose = () =>{
form.resetFields();
}
const handleJobStatusChange = (value) => {
setJobStatus(value)
}
const handleUserStatusChange = (value) => {
setUserStatus(value)
}
return (
<Modal
width={800}
height={550}
bodyStyle={{ height: '500px', overflowY: 'auto' }}
className={style.viewForm}
centered
destroyOnClose
title='查看'
open={viewModalVisible}
onOk={() => handleViewModalVisible()}
onCancel={() => handleViewModalVisible()}
afterClose={() => afterClose()}
>
<Form form={form} layout='vertical' requiredMark={false} style={{pointerEvents: 'none'}}>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='登录账号' name='user_name' rules={[{ required: true, message: '请输入至少2个字符的用户名', min: 2 }]}>
<Input placeholder='请输入' />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='用户名称' name='user_name_cn' rules={[{ required: true, message: '请输入至少2个字符的用户名称', min: 2 }]}>
<Input placeholder='请输入' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='用户性别' name='sex'>
<Select options={formatDictOptions(dictData.sys_user_sex, 'dict_label', 'dict_value')} placeholder='请选择' />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='用户生日' name='birthday'>
<DatePicker format='YYYY-MM-DD' placeholder='请选择' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='手机号码' name='phone' rules={[{ required: false, min: 1, validator: verifyPhone }]}>
<NumberInput placeholder='请输入' style={{width: '100%'}} maxLength={11} />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='电子邮箱' name='email' rules={[{ type: 'email' }]}>
<Input placeholder='请输入' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='机构名称' name='org_name'>
<Input placeholder='请输入' />
</FormItem>
</Col>
{/*<Col md={12} sm={24}>*/}
{/* <FormItem label='部门名称' name='dept_code'>*/}
{/* <SelectDeptTree {...parentDeptTreeMethod} placeholder={'请选择部门'} />*/}
{/* </FormItem>*/}
{/*</Col>*/}
<Col md={12} sm={24}>
<FormItem label='所属岗位' name='posts'>
<Select
mode='multiple'
allowClear
placeholder='请选择'
options={formatDictOptions(dictData.sys_user_post, 'dict_label', 'dict_value')}
/>
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
{/*<Col md={12} sm={24}>
<FormItem
label={
<span>
密码
<em className={styles.optional}>
<Tooltip title='默认密码123456'>
<InfoCircleOutlined style={{ marginLeft: 4 }} />
</Tooltip>
</em>
</span>
}
name='password'
initialValue={'123456'}
rules={[{required: true, message: '请输入至少6个字符的密码', min: 6}]}>
<Input placeholder='请输入' type='password'/>
</FormItem>
</Col>*/}
<Col md={12} sm={24}>
<FormItem label='在职状态' name='job_status'>
<Select
placeholder='请选择'
options={formatDictOptions(dictData.sys_job_status, 'dict_label', 'dict_value')}
onChange={handleJobStatusChange}
/>
</FormItem>
</Col>
{jobStatus === '1' ?
<Col md={12} sm={24}>
<FormItem label='入职时间' name='hiredate' initialValue={dayjs().endOf('day')}>
<DatePicker format='YYYY-MM-DD' placeholder='请选择' />
</FormItem>
</Col>
:
<Col md={12} sm={24}>
<FormItem label='离职时间' name='departure_time'>
<DatePicker format='YYYY-MM-DD' placeholder='请选择' />
</FormItem>
</Col>
}
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='员工类型' name='user_type'>
<Select
placeholder='请选择'
options={formatDictOptions(dictData.sys_user_type, 'dict_label', 'dict_value')}
/>
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='员工工号' name='emp_no'>
<Input placeholder='请输入' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='门禁卡号' name='access_card_no'>
<Input placeholder='请输入' />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='是否是管理员' name='mgr_type'>
<Select
style={{width: '100%'}}
placeholder='请选择'
options={formatDictOptions(dictData.sys_mgr_type, 'dict_label', 'dict_value')}
/>
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='状态' name='status'>
<Select
placeholder='请选择'
options={formatDictOptions(dictData.user_status, 'dict_label', 'dict_value')}
onChange={handleUserStatusChange}
/>
</FormItem>
</Col>
{ userStatus === '3' &&
<Col md={12} sm={24}>
<FormItem label='冻结原因' name='freeze_cause'>
<Input placeholder='请输入' />
</FormItem>
</Col>
}
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={24} sm={24}>
<FormItem label='备注' name='remarks'>
<TextArea rows={4} />
</FormItem>
</Col>
</Row>
</Form>
</Modal>
)
}
export default StaffSheetViewForm

@ -0,0 +1,319 @@
import { deleteByPrimaryKeyForProUser, selectByPrimaryKeyForProUser, insertForProUser, updateForProUser, deleteByMapForProUser,updateByMapForProUser, getOneForProUser,getAllForProUser,queryPageForProUser, countForProUser, insertBatchForProUser, deleteBatchForProUser,updateBatchForProUser, resetPwdForProUser } from '@/services/system/api_prouser';
export default {
namespace: 'safemajorha33zard',
state: {
params: {},
data: {
list: [],
pagination: {},
},
},
effects: {
*delete_by_primarykey_for_prouser({ payload, callback }, { select, call, put }) {
const response = yield call(deleteByPrimaryKeyForProUser, payload)
yield put({
type: 'deleteByPrimaryKeyForProUser',
payload: response
})
if (!response.success) {
callback && callback(response)
return
}
const params = yield select(state => state.prouser.params)
const responseData = yield call(queryPageForProUser, params)
yield put({
type: 'queryPageForProUser',
payload: responseData
})
if (callback) callback(response)
},
*select_by_primarykey_for_prouser({ payload, callback }, { call, put }) {
const response = yield call(selectByPrimaryKeyForProUser, payload)
yield put({
type: 'selectByPrimaryKeyForProUser',
payload: response
})
if (callback) callback(response)
},
*insert_for_prouser({ payload, callback }, { select, call, put }) {
const response = yield call(insertForProUser, payload)
yield put({
type: 'insertForProUser',
payload: response
})
if (!response.success) {
callback && callback(response)
return
}
const params = yield select(state => state.prouser.params)
const responseData = yield call(queryPageForProUser, params)
yield put({
type: 'queryPageForProUser',
payload: responseData
})
if (callback) callback(response)
},
*update_for_prouser({ payload, callback }, { select, call, put }) {
const response = yield call(updateForProUser, payload)
yield put({
type: 'updateForProUser',
payload: response
})
if (!response.success) {
callback && callback(response)
return
}
const params = yield select(state => state.prouser.params)
const responseData = yield call(queryPageForProUser, params)
yield put({
type: 'queryPageForProUser',
payload: responseData
})
if (callback) callback(response)
},
*delete_by_map_for_prouser({ payload, callback }, { select, call, put }) {
const response = yield call(deleteByMapForProUser, payload);
yield put({
type: 'deleteByMapForProUser',
payload: response,
});
const params = yield select(state => state.prouser.params);
const responsedata = yield call(queryPageForProUser, params);
yield put({
type: 'queryPageForProUser',
payload: responsedata,
});
if (callback) callback(response);
},
*update_by_map_for_prouser({ payload, callback }, { select, call, put }) {
const response = yield call(updateByMapForProUser, payload);
yield put({
type: 'updateByMapForProUser',
payload: response,
});
const params = yield select(state => state.prouser.params);
const responsedata = yield call(queryPageForProUser, params);
yield put({
type: 'queryPageForProUser',
payload: responsedata,
});
if (callback) callback(response);
},
*get_one_for_prouser({ payload, callback }, { call, put }) {
const response = yield call(getOneForProUser, payload);
yield put({
type: 'getOneForProUser',
payload: response,
});
if (callback) callback(response);
},
*get_all_for_prouser({ payload, callback }, { call, put }) {
const response = yield call(getAllForProUser, payload);
yield put({
type: 'getAllForProUser',
payload: response,
});
if (callback) callback(response);
},
*query_page_for_prouser({ payload, callback }, { select, call, put }) {
const params = yield select(state => state.prouser.params);
const newParams = payload?.resetFlag ? payload : {...params, ...payload};
yield put({
type: 'setQueryPageByParams',
payload: newParams,
});
const response = yield call(queryPageForProUser, newParams);
yield put({
type: 'queryPageForProUser',
payload: response,
});
if (callback) callback(response);
},
*count_for_prouser({ payload, callback }, { call, put }) {
const response = yield call(countForProUser, payload);
yield put({
type: 'countForProUser',
payload: response,
});
if (callback) callback(response);
},
*insert_batch_for_prouser({ payload, callback }, { select, call, put }) {
const response = yield call(insertBatchForProUser, payload);
yield put({
type: 'insertBatchForProUser',
payload: response,
});
const params = yield select(state => state.prouser.params);
const responsedata = yield call(queryPageForProUser, params);
yield put({
type: 'queryPageForProUser',
payload: responsedata,
});
if (callback) callback(response);
},
*delete_batch_for_prouser({ payload, callback }, { select, call, put }) {
const response = yield call(deleteBatchForProUser, payload);
yield put({
type: 'deleteBatchForProUser',
payload: response,
});
const params = yield select(state => state.prouser.params);
const responsedata = yield call(queryPageForProUser, params);
yield put({
type: 'queryPageForProUser',
payload: responsedata,
});
if (callback) callback(response);
},
*update_batch_for_prouser({ payload, callback }, { select, call, put }) {
const response = yield call(updateBatchForProUser, payload);
yield put({
type: 'updateBatchForProUser',
payload: response,
});
const params = yield select(state => state.prouser.params);
const responsedata = yield call(queryPageForProUser, params);
yield put({
type: 'queryPageForProUser',
payload: responsedata,
});
if (callback) callback(response);
},
*resetpwd_for_prouser({ payload, callback }, { select, call, put }) {
const response = yield call(resetPwdForProUser, payload);
yield put({
type: 'resetPwdForProUser',
payload: response,
});
const params = yield select(state => state.prouser.params);
const responsedata = yield call(queryPageForProUser, params);
yield put({
type: 'queryPageForProUser',
payload: responsedata,
});
if (callback) callback(response);
},
},
reducers: {
setQueryPageByParams(state, { payload }) {
return {
...state,
params: {...payload},
};
},
deleteByPrimaryKeyForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
selectByPrimaryKeyForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
insertForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
updateForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
deleteByMapForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
updateByMapForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
getOneForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
getAllForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
queryPageForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
countForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
insertBatchForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
deleteBatchForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
updateBatchForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
resetPwdForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
},
};

@ -0,0 +1,74 @@
import React, { useState } from 'react';
import { Card, Row, Col, Statistic, Progress, Button, Space } from 'antd';
import styles from './basic.less';
import Cjgl from './components/Cjgl';
import Zdcj from './components/Zdcj';
import Sglr from './components/Sglr';
import Sjjy from './components/Sjjy';
import Cjrz from './components/Cjrz';
const SafeMajorHazardList = () => {
const [activeModule, setActiveModule] = useState('Cjgl');
const handleModuleClick = (module) => {
setActiveModule(module)
}
const renderModule = () => {
switch (activeModule) {
case 'Cjgl':
return <Cjgl />;
case 'Zdcj':
return <Zdcj />;
case 'Sglr':
return <Sglr />;
case 'Sjjy':
return <Sjjy />;
case 'Cjrz':
return <Cjrz />;
default:
return <Cjgl />;
}
};
return (
<div className={styles.container}>
<div className={styles.TopButton}>
<Button
className={`${styles.TopButtonItem} ${activeModule === "Cjgl" ? styles.active : ""}`}
onClick={() => handleModuleClick("Cjgl")}
>采集概览
</Button>
<Button
className={`${styles.TopButtonItem} ${activeModule === "Zdcj" ? styles.active : ""}`}
onClick={() => handleModuleClick("Zdcj")}
>自动采集
</Button>
<Button
className={`${styles.TopButtonItem} ${activeModule === "Sglr" ? styles.active : ""}`}
onClick={() => handleModuleClick("Sglr")}
>手工录入
</Button>
<Button
className={`${styles.TopButtonItem} ${activeModule === "Sjjy" ? styles.active : ""}`}
onClick={() => handleModuleClick("Sjjy")}
>数据校验
</Button>
<Button
className={`${styles.TopButtonItem} ${activeModule === "Cjrz" ? styles.active : ""}`}
onClick={() => handleModuleClick("Cjrz")}
>采集日志
</Button>
</div>
<div className={styles.content}>
{renderModule()}
</div>
</div>
);
};
export default SafeMajorHazardList;

@ -0,0 +1,69 @@
.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 {
background-color: rgba(72, 81, 255, 1) !important;
color: #fff !important;
border-radius: 20px !important;
padding: 6px 10px 10px !important;
&::after {
content: '';
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 3px;
width: 16.573974609375px;
height: 2.615234375px;
background-color: rgba(255, 255, 255, 0.52);
border-radius: 15px;
opacity: 1;
}
}
}
}
.content {
// ======== 内容区域样式 ========
flex: 1; // ======== 占据剩余空间 ========
overflow-y: auto; // ======== 允许垂直滚动 ========
padding: 0; // ======== 无内边距 ========
}
}

@ -0,0 +1,443 @@
import React, { useMemo, useState } from 'react';
import {
Badge,
Button,
Card,
Col,
List,
Progress,
Row,
Space,
Statistic,
Tag,
Typography,
} from 'antd';
import {
CloudSyncOutlined,
DatabaseOutlined,
ExclamationCircleOutlined,
HddOutlined,
ReloadOutlined,
} from '@ant-design/icons';
import styles from './Cjgl.less';
const { Text } = Typography;
const formatNumber = (value) => {
if (value === null || value === undefined) return '--';
const str = String(value);
return str.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
};
const buildConicGradient = (segments) => {
const total = segments.reduce((sum, s) => sum + (Number(s.value) || 0), 0) || 1;
let start = 0;
const stops = segments
.map((s) => {
const ratio = (Number(s.value) || 0) / total;
const end = start + ratio;
const result = `${s.color} ${Math.round(start * 360)}deg ${Math.round(end * 360)}deg`;
start = end;
return result;
})
.join(', ');
return `conic-gradient(${stops})`;
};
const Cjgl = () => {
const [activeAnomaly, setActiveAnomaly] = useState('missing');
const overviewMetrics = useMemo(
() => [
{
key: 'deviceTotal',
label: '采集设备',
value: 52,
suffix: '台',
tone: 'primary',
icon: <DatabaseOutlined />,
},
{
key: 'deviceOnline',
label: '在线设备',
value: 45,
suffix: '台',
tone: 'primary',
icon: <CloudSyncOutlined />,
},
{
key: 'todayCount',
label: '今日采集量',
value: 151235,
suffix: '',
tone: 'primary',
icon: <DatabaseOutlined />,
},
{
key: 'manualCount',
label: '手工录入',
value: 87,
suffix: '条',
tone: 'primary',
icon: <DatabaseOutlined />,
},
{
key: 'successRate',
label: '采集成功率',
value: 68,
suffix: '%',
tone: 'success',
icon: <CloudSyncOutlined />,
},
{
key: 'storageRate',
label: '存储使用率',
value: 89,
suffix: '%',
tone: 'warning',
icon: <HddOutlined />,
},
],
[],
);
const realtimeSummary = useMemo(
() => [
{ label: '运行中', value: 52, unit: '台', tone: 'primary' },
{ label: '设备类型', value: 5, unit: '种', tone: 'primary' },
{ label: '警告数', value: 3, unit: '条', tone: 'danger' },
],
[],
);
const devices = useMemo(
() =>
new Array(8).fill(0).map((_, idx) => ({
id: `SDO292938387-${idx + 1}`,
name: `变压器#${(idx % 3) + 1}`, // 仅作演示
frequency: '1分钟',
lastTime: '2024-06-10 14:29:30',
status: idx === 5 ? '异常' : '正常',
})),
[],
);
const anomalyTypes = useMemo(
() => [
{ key: 'missing', title: '数据缺失', desc: '范围超限', badge: 5 },
{ key: 'logic', title: '逻辑矛盾', desc: '', badge: 0 },
{ key: 'time', title: '时间异常', desc: '', badge: 0 },
],
[],
);
const anomalyCards = useMemo(
() =>
new Array(3).fill(0).map((_, idx) => ({
id: `anomaly-${activeAnomaly}-${idx}`,
deviceName: '变压器#1 (TR-001)',
missingTime: '14:20:00 至 14:25:00',
duration: '5分钟',
frequency: '10秒/次',
missingCount: '30条记录',
status: idx === 2 ? '待处理' : '已自动恢复',
})),
[activeAnomaly],
);
const sourceSegments = useMemo(
() => [
{ name: '自动采集', value: 23, color: '#3b82f6' },
{ name: '手工录入', value: 39, color: '#22c55e' },
{ name: '手工补录', value: 48, color: '#f59e0b' },
],
[],
);
const donutBg = useMemo(() => buildConicGradient(sourceSegments), [sourceSegments]);
const updateTime = useMemo(() => '2024-05-10 14:00:00', []);
return (
<div className={styles.container}>
<div className={styles.overview}>
<div className={styles.overviewHeader}>
<div className={styles.overviewTitle}>
<div className={styles.overviewIcon}>
<DatabaseOutlined />
</div>
<div className={styles.overviewText}>
<div className={styles.overviewMainTitle}>数据概览</div>
<div className={styles.overviewSubTitle}>采集与存储整体状态</div>
</div>
</div>
<div className={styles.overviewTime}>
<Text type="secondary">更新时间{updateTime}</Text>
</div>
</div>
<Row gutter={[12, 12]} className={styles.overviewCards}>
{overviewMetrics.map((m) => (
<Col key={m.key} xs={12} sm={8} md={8} lg={4} xl={4}>
<div className={`${styles.metricCard} ${styles[`metricCard_${m.tone}`]}`}>
<div className={styles.metricInner}>
<div className={styles.metricIcon}>{m.icon}</div>
<div className={styles.metricContent}>
<div className={styles.metricValue}>
{formatNumber(m.value)}
<span className={styles.metricSuffix}>{m.suffix}</span>
</div>
<div className={styles.metricLabel}>{m.label}</div>
</div>
</div>
</div>
</Col>
))}
</Row>
</div>
<Row gutter={[12, 12]} className={styles.middle}>
<Col xs={24} lg={8}>
<Card
className={styles.panelCard}
title={<span className={styles.panelTitle}>实时采集状态</span>}
extra={
<Button size="small" type="text" icon={<ReloadOutlined />} className={styles.iconBtn} />
}
>
<div className={styles.realtimeSummary}>
{realtimeSummary.map((s) => (
<div
key={s.label}
className={`${styles.summaryItem} ${styles[`summaryItem_${s.tone}`]}`}
>
<div className={styles.summaryValue}>
{s.value}
<span className={styles.summaryUnit}>{s.unit}</span>
</div>
<div className={styles.summaryLabel}>{s.label}</div>
</div>
))}
</div>
<div className={styles.deviceListWrap}>
<List
className={styles.deviceList}
dataSource={devices}
renderItem={(item) => (
<List.Item className={styles.deviceItem}>
<div className={styles.deviceRow}>
<div className={styles.deviceMain}>
<div className={styles.deviceTitle}>
<span className={styles.deviceName}>{item.name}</span>
<Tag className={styles.deviceId} color="cyan">
#{item.id}
</Tag>
</div>
<div className={styles.deviceMeta}>
<span>
<Text type="secondary">采集频率</Text>
{item.frequency}
</span>
<span>
<Text type="secondary">最后采集时间</Text>
{item.lastTime}
</span>
</div>
</div>
<div className={styles.deviceStatus}>
<Tag color={item.status === '正常' ? 'success' : 'error'}>
{item.status}
</Tag>
</div>
</div>
</List.Item>
)}
/>
</div>
</Card>
</Col>
<Col xs={24} lg={16}>
<Card
className={styles.panelCard}
title={<span className={styles.panelTitle}>今日数据异常统计</span>}
extra={
<Space size={8}>
<Button size="small">执行校验</Button>
<Button size="small">查看详情</Button>
<Button size="small">导出报告</Button>
</Space>
}
>
<div className={styles.anomalyBody}>
<div className={styles.anomalyTabs}>
{anomalyTypes.map((t) => (
<div
key={t.key}
className={`${styles.anomalyTab} ${
activeAnomaly === t.key ? styles.anomalyTabActive : ''
}`}
onClick={() => setActiveAnomaly(t.key)}
role="button"
tabIndex={0}
>
<div className={styles.anomalyTabTitleRow}>
<span className={styles.anomalyTabTitle}>{t.title}</span>
{t.badge ? (
<Badge count={t.badge} size="small" className={styles.anomalyBadge} />
) : null}
</div>
{t.desc ? <div className={styles.anomalyTabDesc}>{t.desc}</div> : null}
</div>
))}
</div>
<div className={styles.anomalyContent}>
<div className={styles.anomalyCards}>
{anomalyCards.map((c) => (
<div key={c.id} className={styles.anomalyCard}>
<div className={styles.anomalyCardHeader}>
<div className={styles.anomalyCardHeaderLeft}>
<ExclamationCircleOutlined className={styles.anomalyCardHeaderIcon} />
<span className={styles.anomalyCardHeaderTitle}>设备{c.deviceName}</span>
</div>
</div>
<div className={styles.anomalyCardBody}>
<div className={styles.anomalyField}>
<span className={styles.anomalyLabel}>缺失时间</span>
<span className={styles.anomalyValue}>{c.missingTime}</span>
</div>
<div className={styles.anomalyField}>
<span className={styles.anomalyLabel}>持续时间</span>
<span className={styles.anomalyValue}>{c.duration}</span>
</div>
<div className={styles.anomalyField}>
<span className={styles.anomalyLabel}>采集频率</span>
<span className={styles.anomalyValue}>{c.frequency}</span>
</div>
<div className={styles.anomalyField}>
<span className={styles.anomalyLabel}>缺失数据量</span>
<span className={styles.anomalyValue}>{c.missingCount}</span>
</div>
<div className={styles.anomalyField}>
<span className={styles.anomalyLabel}>处理状态</span>
<span className={styles.anomalyValue}>
<Tag color={c.status === '已自动恢复' ? 'success' : 'warning'}>
{c.status}
</Tag>
</span>
</div>
</div>
<div className={styles.anomalyCardFooter}>
<Button size="small" type="link">
查看详情
</Button>
</div>
</div>
))}
</div>
</div>
</div>
</Card>
</Col>
</Row>
<Row gutter={[12, 12]} className={styles.bottom}>
<Col xs={24} lg={14}>
<Card className={styles.panelCard} title={<span className={styles.panelTitle}>数据来源分布</span>}>
<div className={styles.donutSection}>
<div
className={styles.donut}
style={{
'--donut-bg': donutBg,
}}
>
<div className={styles.donutInner}>
<div className={styles.donutCenterTitle}>来源</div>
<div className={styles.donutCenterValue}>分布</div>
</div>
</div>
<div className={styles.donutLegends}>
{sourceSegments.map((s) => (
<div key={s.name} className={styles.legendItem}>
<span className={styles.legendDot} style={{ background: s.color }} />
<span className={styles.legendName}>{s.name}</span>
<span className={styles.legendValue}>{s.value}%</span>
</div>
))}
</div>
</div>
</Card>
</Col>
<Col xs={24} lg={10}>
<Card
className={styles.panelCard}
title={<span className={styles.panelTitle}>存储空间使用预测</span>}
extra={<Text type="secondary">已用总量</Text>}
>
<div className={styles.storageSection}>
<div className={styles.storageLegend}>
<div className={styles.storageLegendItem}>
<span className={styles.legendDot} style={{ background: '#8b5cf6' }} />
<span className={styles.legendName}>已用总量</span>
<span className={styles.legendValue}>54TB</span>
</div>
<div className={styles.storageLegendItem}>
<span className={styles.legendDot} style={{ background: '#60a5fa' }} />
<span className={styles.legendName}>近30天预测</span>
<span className={styles.legendValue}>54TB</span>
</div>
<div className={styles.storageLegendItem}>
<span className={styles.legendDot} style={{ background: '#4ade80' }} />
<span className={styles.legendName}>近60天预测</span>
<span className={styles.legendValue}>54TB</span>
</div>
</div>
<div className={styles.storageRings}>
<div className={styles.storageRingLayer}>
<Progress
type="circle"
percent={82}
size={200}
strokeWidth={10}
strokeColor="#8b5cf6"
trailColor="#f1f5f9"
format={() => '54TB'}
/>
</div>
<div className={`${styles.storageRingLayer} ${styles.storageRingLayer_mid}`}>
<Progress
type="circle"
percent={70}
size={160}
strokeWidth={10}
strokeColor="#60a5fa"
trailColor="transparent"
format={() => ''}
/>
</div>
<div className={`${styles.storageRingLayer} ${styles.storageRingLayer_inner}`}>
<Progress
type="circle"
percent={55}
size={120}
strokeWidth={10}
strokeColor="#4ade80"
trailColor="transparent"
format={() => ''}
/>
</div>
</div>
</div>
</Card>
</Col>
</Row>
</div>
);
};
export default Cjgl;

@ -0,0 +1,603 @@
.container {
padding: 12px;
background: linear-gradient(180deg, rgba(241, 248, 255, 1) 0%, rgba(246, 250, 255, 1) 100%);
min-height: 100%;
.overview {
background: rgba(255, 255, 255, 0.75);
border-radius: 14px;
padding: 14px;
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.06);
backdrop-filter: blur(8px);
border: 1px solid rgba(226, 232, 240, 0.7);
margin-bottom: 12px;
.overviewHeader {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
.overviewTitle {
display: flex;
align-items: center;
gap: 10px;
.overviewIcon {
width: 44px;
height: 44px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, rgba(59, 130, 246, 0.16), rgba(99, 102, 241, 0.10));
color: #2f66ff;
font-size: 20px;
}
.overviewText {
.overviewMainTitle {
font-size: 16px;
font-weight: 700;
color: #0f172a;
line-height: 20px;
}
.overviewSubTitle {
font-size: 12px;
color: rgba(15, 23, 42, 0.55);
line-height: 16px;
margin-top: 2px;
}
}
}
.overviewTime {
font-size: 12px;
color: rgba(15, 23, 42, 0.55);
}
}
.overviewCards {
margin-top: 2px;
}
.metricCard {
border-radius: 14px;
padding: 12px;
background: rgba(255, 255, 255, 0.85);
border: 1px solid rgba(226, 232, 240, 0.7);
box-shadow: 0 10px 20px rgba(15, 23, 42, 0.05);
transition: transform 150ms ease, box-shadow 150ms ease;
height: 78px;
&:hover {
transform: translateY(-1px);
box-shadow: 0 14px 30px rgba(15, 23, 42, 0.08);
}
.metricInner {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.metricIcon {
width: 38px;
height: 38px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
flex: 0 0 auto;
}
.metricContent {
flex: 1 1 auto;
min-width: 0;
.metricValue {
font-size: 22px;
font-weight: 800;
letter-spacing: 0.3px;
color: #0f172a;
line-height: 26px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
.metricSuffix {
margin-left: 2px;
font-size: 12px;
font-weight: 700;
color: rgba(15, 23, 42, 0.55);
}
}
.metricLabel {
margin-top: 4px;
font-size: 12px;
color: rgba(15, 23, 42, 0.6);
}
}
}
.metricCard_primary {
.metricIcon {
background: linear-gradient(135deg, rgba(59, 130, 246, 0.18), rgba(37, 99, 235, 0.10));
color: #2563eb;
}
}
.metricCard_success {
background: linear-gradient(135deg, rgba(34, 197, 94, 0.12), rgba(255, 255, 255, 0.85));
.metricIcon {
background: linear-gradient(135deg, rgba(34, 197, 94, 0.18), rgba(34, 197, 94, 0.08));
color: #16a34a;
}
}
.metricCard_warning {
background: linear-gradient(135deg, rgba(245, 158, 11, 0.12), rgba(255, 255, 255, 0.85));
.metricIcon {
background: linear-gradient(135deg, rgba(245, 158, 11, 0.18), rgba(245, 158, 11, 0.08));
color: #d97706;
}
}
}
.panelCard {
border-radius: 14px;
overflow: hidden;
border: 1px solid rgba(226, 232, 240, 0.7);
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.06);
:global(.ant-card-head) {
border-bottom: 1px solid rgba(226, 232, 240, 0.6);
background: rgba(255, 255, 255, 0.75);
backdrop-filter: blur(8px);
}
:global(.ant-card-body) {
background: rgba(255, 255, 255, 0.9);
}
.panelTitle {
font-weight: 700;
color: #0f172a;
}
.iconBtn {
color: rgba(15, 23, 42, 0.55);
}
}
.middle {
.realtimeSummary {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin-bottom: 10px;
.summaryItem {
border-radius: 12px;
padding: 12px;
border: 1px solid rgba(226, 232, 240, 0.7);
background: rgba(248, 250, 252, 0.8);
.summaryValue {
font-size: 22px;
font-weight: 800;
color: #0f172a;
line-height: 26px;
.summaryUnit {
margin-left: 2px;
font-size: 12px;
color: rgba(15, 23, 42, 0.55);
font-weight: 700;
}
}
.summaryLabel {
margin-top: 4px;
font-size: 12px;
color: rgba(15, 23, 42, 0.6);
}
}
.summaryItem_primary {
background: linear-gradient(135deg, rgba(59, 130, 246, 0.10), rgba(248, 250, 252, 0.85));
}
.summaryItem_danger {
background: linear-gradient(135deg, rgba(239, 68, 68, 0.10), rgba(248, 250, 252, 0.85));
.summaryValue {
color: #ef4444;
}
}
}
.deviceListWrap {
max-height: 420px;
overflow: auto;
padding-right: 4px;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: rgba(148, 163, 184, 0.5);
border-radius: 8px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
}
.deviceList {
:global(.ant-list-item) {
padding: 10px 0;
border-bottom: 1px dashed rgba(226, 232, 240, 0.9);
}
}
.deviceItem {
.deviceRow {
width: 100%;
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 10px;
}
.deviceMain {
flex: 1 1 auto;
min-width: 0;
}
.deviceTitle {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 4px;
.deviceName {
font-weight: 700;
color: #0f172a;
}
.deviceId {
border-radius: 999px;
margin: 0;
}
}
.deviceMeta {
display: flex;
gap: 12px;
flex-wrap: wrap;
color: rgba(15, 23, 42, 0.72);
font-size: 12px;
}
.deviceStatus {
flex: 0 0 auto;
margin-top: 2px;
}
}
.anomalyBody {
display: grid;
grid-template-columns: 150px 1fr;
gap: 12px;
min-height: 360px;
.anomalyTabs {
display: flex;
flex-direction: column;
gap: 10px;
}
.anomalyTab {
border-radius: 12px;
padding: 12px 12px;
border: 1px solid rgba(226, 232, 240, 0.7);
background: rgba(248, 250, 252, 0.7);
cursor: pointer;
transition: background 150ms ease, transform 150ms ease, box-shadow 150ms ease;
&:hover {
transform: translateY(-1px);
box-shadow: 0 10px 20px rgba(15, 23, 42, 0.06);
}
.anomalyTabTitleRow {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.anomalyTabTitle {
font-weight: 800;
color: #0f172a;
}
.anomalyTabDesc {
margin-top: 6px;
font-size: 12px;
color: rgba(15, 23, 42, 0.55);
}
.anomalyBadge {
:global(.ant-badge-count) {
background: #2563eb;
}
}
}
.anomalyTabActive {
background: linear-gradient(135deg, rgba(59, 130, 246, 0.18), rgba(255, 255, 255, 0.7));
border-color: rgba(59, 130, 246, 0.25);
}
.anomalyContent {
overflow: hidden;
}
.anomalyCards {
display: flex;
gap: 12px;
overflow-x: auto;
padding-bottom: 6px;
&::-webkit-scrollbar {
height: 6px;
}
&::-webkit-scrollbar-thumb {
background: rgba(148, 163, 184, 0.5);
border-radius: 8px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
}
.anomalyCard {
min-width: 260px;
max-width: 300px;
border-radius: 14px;
border: 1px solid rgba(226, 232, 240, 0.8);
background: rgba(255, 255, 255, 0.9);
box-shadow: 0 10px 22px rgba(15, 23, 42, 0.06);
overflow: hidden;
.anomalyCardHeader {
padding: 10px 12px;
background: linear-gradient(135deg, rgba(59, 130, 246, 0.18), rgba(255, 255, 255, 0.5));
border-bottom: 1px solid rgba(226, 232, 240, 0.7);
.anomalyCardHeaderLeft {
display: flex;
align-items: center;
gap: 8px;
}
.anomalyCardHeaderIcon {
color: #2563eb;
}
.anomalyCardHeaderTitle {
font-weight: 700;
color: #0f172a;
font-size: 12px;
}
}
.anomalyCardBody {
padding: 10px 12px;
.anomalyField {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 10px;
padding: 6px 0;
border-bottom: 1px dashed rgba(226, 232, 240, 0.8);
&:last-child {
border-bottom: none;
}
}
.anomalyLabel {
color: rgba(15, 23, 42, 0.55);
font-size: 12px;
white-space: nowrap;
}
.anomalyValue {
color: rgba(15, 23, 42, 0.82);
font-size: 12px;
text-align: right;
flex: 1 1 auto;
}
}
.anomalyCardFooter {
padding: 6px 12px 10px;
display: flex;
justify-content: center;
}
}
}
}
.bottom {
.donutSection {
display: grid;
grid-template-columns: 220px 1fr;
gap: 12px;
align-items: center;
.donut {
width: 200px;
height: 200px;
border-radius: 999px;
background: var(--donut-bg);
position: relative;
margin: 4px 0;
box-shadow: inset 0 0 0 10px rgba(255, 255, 255, 0.85);
&::after {
content: '';
position: absolute;
inset: -10px;
border-radius: 999px;
background: radial-gradient(circle at 30% 20%, rgba(59, 130, 246, 0.22), transparent 55%),
radial-gradient(circle at 70% 80%, rgba(34, 197, 94, 0.18), transparent 55%);
filter: blur(10px);
z-index: 0;
}
.donutInner {
position: absolute;
inset: 42px;
background: rgba(255, 255, 255, 0.95);
border-radius: 999px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border: 1px solid rgba(226, 232, 240, 0.8);
z-index: 1;
.donutCenterTitle {
font-size: 12px;
color: rgba(15, 23, 42, 0.55);
}
.donutCenterValue {
margin-top: 2px;
font-size: 18px;
font-weight: 800;
color: #0f172a;
}
}
}
.donutLegends {
display: flex;
flex-direction: column;
gap: 10px;
.legendItem {
display: grid;
grid-template-columns: 10px 1fr auto;
gap: 10px;
align-items: center;
padding: 10px;
border-radius: 12px;
border: 1px solid rgba(226, 232, 240, 0.7);
background: rgba(248, 250, 252, 0.7);
}
.legendDot {
width: 10px;
height: 10px;
border-radius: 999px;
}
.legendName {
color: rgba(15, 23, 42, 0.75);
font-weight: 700;
}
.legendValue {
color: rgba(15, 23, 42, 0.85);
font-weight: 800;
}
}
}
.storageSection {
display: grid;
grid-template-columns: 1fr 220px;
gap: 12px;
align-items: center;
.storageLegend {
display: flex;
flex-direction: column;
gap: 10px;
.storageLegendItem {
display: grid;
grid-template-columns: 10px 1fr auto;
gap: 10px;
align-items: center;
padding: 10px;
border-radius: 12px;
border: 1px solid rgba(226, 232, 240, 0.7);
background: rgba(248, 250, 252, 0.7);
}
}
.storageRings {
position: relative;
width: 220px;
height: 220px;
display: flex;
align-items: center;
justify-content: center;
.storageRingLayer {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
:global(.ant-progress-text) {
font-weight: 900;
color: rgba(15, 23, 42, 0.75);
}
}
.storageRingLayer_mid {
transform: translateY(0);
}
.storageRingLayer_inner {
transform: translateY(0);
}
}
}
}
@media (max-width: 992px) {
.middle {
.anomalyBody {
grid-template-columns: 1fr;
}
}
.bottom {
.donutSection {
grid-template-columns: 1fr;
justify-items: center;
}
.storageSection {
grid-template-columns: 1fr;
justify-items: center;
}
}
}
}

@ -0,0 +1,325 @@
import React, { useMemo, useState } from 'react';
import { Button, Checkbox, DatePicker, message, Popconfirm, Select, Space, Table } from 'antd';
import {
DeleteOutlined,
DownloadOutlined,
EyeOutlined,
PlusOutlined,
ReloadOutlined,
SearchOutlined,
UploadOutlined,
} from '@ant-design/icons';
import styles from './Cjrz.less';
const { RangePicker } = DatePicker;
const Cjrz = () => {
const [levelAll, setLevelAll] = useState(true);
const [levels, setLevels] = useState(['信息', '警告']);
const [deviceType, setDeviceType] = useState('全部');
const [meterAll, setMeterAll] = useState(true);
const [meterTypes, setMeterTypes] = useState(['电表']);
const [taskAll, setTaskAll] = useState(true);
const [taskTypes, setTaskTypes] = useState(['自动']);
const [timeRangePreset, setTimeRangePreset] = useState('本月');
const [timeRange, setTimeRange] = useState(null);
const [pagination, setPagination] = useState({ current: 1, pageSize: 10 });
const levelOptions = useMemo(() => ['信息', '警告', '错误', '严重'], []);
const meterOptions = useMemo(() => ['电表', '水表', '燃气表'], []);
const taskOptions = useMemo(() => ['自动', '手工'], []);
const rows = useMemo(
() => [
{
id: 1,
time: '2025-11-25 01:07:46',
level: '信息',
device: '变压器1#',
opType: '自动采集',
detail: 'xxxxxxxxxxxxxxxxxxxx',
},
{
id: 2,
time: '2025-11-29 01:15:57',
level: '信息',
device: 'SCADA',
opType: '自动采集',
detail: 'xxxxxxxxxxxxxxxxxxxx',
},
{
id: 3,
time: '2025-11-30 02:21:41',
level: '信息',
device: '燃气表B',
opType: '自动采集',
detail: 'xxxxxxxxxxxxxxxxxxxx',
},
{
id: 4,
time: '2025-12-04 20:10:27',
level: '警告',
device: '水表A',
opType: '手动录入',
detail: 'xxxxxxxxxxxxxxxxxxxx',
},
{
id: 5,
time: '2025-12-12 04:35:20',
level: '警告',
device: '数据校验',
opType: '手动录入',
detail: 'xxxxxxxxxxxxxxxxxxxx',
},
],
[],
);
const filteredRows = useMemo(() => {
return rows
.filter((r) => (levelAll ? true : levels.includes(r.level)))
.filter((r) => (deviceType === '全部' ? true : r.device.includes(deviceType)))
.filter((r) => (meterAll ? true : meterTypes.some((m) => r.device.includes(m))))
.filter((r) => (taskAll ? true : taskTypes.some((t) => r.opType.includes(t))));
}, [deviceType, levelAll, levels, meterAll, meterTypes, rows, taskAll, taskTypes]);
const handleQuery = () => message.info('查询');
const handleReset = () => {
setLevelAll(true);
setLevels(['信息', '警告']);
setDeviceType('全部');
setMeterAll(true);
setMeterTypes(['电表']);
setTaskAll(true);
setTaskTypes(['自动']);
setTimeRangePreset('本月');
setTimeRange(null);
setPagination({ current: 1, pageSize: pagination.pageSize });
message.success('已重置');
};
const handleAdd = () => message.info('新增');
const handleUpload = () => message.info('上传');
const handleBatchDownload = () => message.info('批量下载');
const handleView = (record) => message.info(`查看:${record.device}`);
const handleDelete = (record) => message.success(`已删除:${record.device}`);
const columns = useMemo(
() => [
{
title: '序号',
dataIndex: 'id',
key: 'id',
width: 70,
align: 'center',
},
{
title: '时间',
dataIndex: 'time',
key: 'time',
sorter: (a, b) => String(a.time).localeCompare(String(b.time)),
width: 180,
},
{
title: '级别',
dataIndex: 'level',
key: 'level',
width: 90,
},
{
title: '设备',
dataIndex: 'device',
key: 'device',
width: 160,
},
{
title: '操作类型',
dataIndex: 'opType',
key: 'opType',
width: 130,
filters: [
{ text: '自动采集', value: '自动采集' },
{ text: '手动录入', value: '手动录入' },
],
onFilter: (value, record) => String(record.opType).includes(String(value)),
},
{
title: '详情',
dataIndex: 'detail',
key: 'detail',
},
{
title: '操作',
key: 'action',
width: 110,
align: 'center',
render: (_, record) => (
<Space size={12} className={styles.actionCell}>
<Button
type="text"
size="small"
className={styles.actionBtnView}
icon={<EyeOutlined />}
onClick={() => handleView(record)}
/>
<Popconfirm
title="确认删除这条记录?"
okText="删除"
cancelText="取消"
onConfirm={() => handleDelete(record)}
>
<Button
type="text"
size="small"
danger
className={styles.actionBtnDelete}
icon={<DeleteOutlined />}
/>
</Popconfirm>
</Space>
),
},
],
[],
);
return (
<div className={styles.container}>
<div className={styles.panel}>
<div className={styles.rowTop}>
<div className={styles.filters}>
<div className={styles.filterItem}>
<div className={styles.filterLabel}>日志级别</div>
<div className={styles.checkGroup}>
<Checkbox checked={levelAll} onChange={(e) => setLevelAll(e.target.checked)}>
全部
</Checkbox>
<Checkbox.Group
options={levelOptions}
value={levels}
disabled={levelAll}
onChange={(next) => setLevels(next)}
/>
</div>
</div>
<div className={styles.filterItem}>
<div className={styles.filterLabel}>设备类型</div>
<Select
className={styles.pillSelect}
value={deviceType}
onChange={setDeviceType}
options={[
{ label: '全部', value: '全部' },
{ label: '电表', value: '电表' },
{ label: '水表', value: '水表' },
{ label: '燃气表', value: '燃气表' },
]}
/>
</div>
<div className={styles.filterItem}>
<div className={styles.filterLabel}>电表</div>
<div className={styles.checkGroup}>
<Checkbox checked={meterAll} onChange={(e) => setMeterAll(e.target.checked)}>
全部
</Checkbox>
<Checkbox.Group
options={meterOptions}
value={meterTypes}
disabled={meterAll}
onChange={(next) => setMeterTypes(next)}
/>
</div>
</div>
<div className={styles.filterItem}>
<div className={styles.filterLabel}>任务状态</div>
<div className={styles.checkGroup}>
<Checkbox checked={taskAll} onChange={(e) => setTaskAll(e.target.checked)}>
全部
</Checkbox>
<Checkbox.Group
options={taskOptions}
value={taskTypes}
disabled={taskAll}
onChange={(next) => setTaskTypes(next)}
/>
</div>
</div>
</div>
<div className={styles.topActions}>
<Button
type="primary"
icon={<SearchOutlined />}
className={styles.queryBtn}
onClick={handleQuery}
>
查询
</Button>
<Button icon={<ReloadOutlined />} className={styles.resetBtn} onClick={handleReset}>
重置
</Button>
</div>
</div>
<div className={styles.rowSecond}>
<div className={styles.toolbarLeft}>
<Button type="primary" icon={<PlusOutlined />} className={styles.primaryBtn} onClick={handleAdd}>
新增
</Button>
<Button icon={<UploadOutlined />} className={styles.ghostBtn} onClick={handleUpload}>
上传
</Button>
<Button icon={<DownloadOutlined />} className={styles.ghostBtn} onClick={handleBatchDownload}>
批量下载
</Button>
</div>
<div className={styles.timeRange}>
<div className={styles.filterLabel}>时间范围</div>
<Select
className={styles.presetSelect}
value={timeRangePreset}
onChange={setTimeRangePreset}
options={[
{ label: '本月', value: '本月' },
{ label: '本周', value: '本周' },
{ label: '今日', value: '今日' },
]}
/>
<RangePicker
className={styles.pillRange}
value={timeRange}
onChange={setTimeRange}
placeholder={['开始日期', '结束日期']}
/>
</div>
</div>
</div>
<div className={styles.tableWrap}>
<Table
rowKey="id"
columns={columns}
dataSource={filteredRows}
pagination={{
current: pagination.current,
pageSize: pagination.pageSize,
total: filteredRows.length,
showSizeChanger: true,
showQuickJumper: false,
showTotal: (total) => `${total}`,
onChange: (current, pageSize) => setPagination({ current, pageSize }),
}}
/>
</div>
</div>
);
};
export default Cjrz;

@ -0,0 +1,277 @@
.container {
width: 100%;
height: 100%;
min-height: 560px;
background: #fff;
border-radius: 14px;
padding: 14px 16px 16px;
margin: 15px 10px;
overflow: hidden;
}
.panel {
background: #f6f8fc;
border-radius: 12px;
padding: 10px 12px;
}
.rowTop {
display: flex;
align-items: center;
justify-content: space-between;
gap: 14px;
}
.filters {
flex: 1 1 auto;
min-width: 0;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px 22px;
}
.filterItem {
display: flex;
align-items: center;
gap: 10px;
}
.filterLabel {
font-size: 12px;
color: #6b7280;
white-space: nowrap;
}
.checkGroup {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
:global {
.ant-checkbox-wrapper {
margin-inline-start: 0 !important;
font-size: 12px;
color: #4b5563;
}
.ant-checkbox-group {
display: inline-flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
}
}
.topActions {
flex: 0 0 auto;
display: flex;
align-items: center;
gap: 12px;
}
.rowSecond {
margin-top: 10px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.toolbarLeft {
display: flex;
align-items: center;
gap: 12px;
}
.timeRange {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
justify-content: flex-end;
}
.pillSelect {
width: 120px;
:global {
.ant-select-selector {
height: 32px !important;
border-radius: 999px !important;
border-color: #dfe5f2 !important;
display: flex !important;
align-items: center !important;
box-shadow: none !important;
background: #fff !important;
}
.ant-select-selection-item {
font-size: 12px;
color: #111827;
}
}
}
.presetSelect {
width: 92px;
:global {
.ant-select-selector {
height: 32px !important;
border-radius: 999px !important;
border-color: #dfe5f2 !important;
display: flex !important;
align-items: center !important;
box-shadow: none !important;
background: #fff !important;
}
.ant-select-selection-item {
font-size: 12px;
color: #111827;
}
}
}
.pillRange {
width: 300px;
:global {
.ant-picker {
height: 32px !important;
border-radius: 999px !important;
border-color: #dfe5f2 !important;
box-shadow: none !important;
background: #fff !important;
}
.ant-picker-input > input {
font-size: 12px;
color: #111827;
}
.ant-picker-separator {
color: #9ca3af;
}
}
}
.queryBtn {
height: 32px !important;
border-radius: 999px !important;
padding: 0 18px !important;
box-shadow: none !important;
background: #3b5bfd;
border-color: #3b5bfd;
:global {
.ant-btn-icon {
margin-right: 6px;
}
}
}
.resetBtn {
height: 32px !important;
border-radius: 999px !important;
padding: 0 18px !important;
border: 1px solid #dfe5f2 !important;
background: #fff !important;
color: #4b5563 !important;
box-shadow: none !important;
:global {
.ant-btn-icon {
margin-right: 6px;
}
}
}
.primaryBtn {
height: 32px !important;
border-radius: 999px !important;
padding: 0 16px !important;
box-shadow: none !important;
background: #3b5bfd;
border-color: #3b5bfd;
:global {
.ant-btn-icon {
margin-right: 6px;
}
}
}
.ghostBtn {
height: 32px !important;
border-radius: 999px !important;
padding: 0 16px !important;
border: 1px solid #dfe5f2 !important;
background: #fff !important;
color: #374151 !important;
box-shadow: none !important;
:global {
.ant-btn-icon {
margin-right: 6px;
}
}
}
.tableWrap {
width: 100%;
margin-top: 12px;
:global {
.ant-table {
border-radius: 10px;
}
.ant-table-thead > tr > th {
background: #f3f5f9;
color: #6b7280;
font-weight: 500;
height: 44px;
}
.ant-table-tbody > tr > td {
color: #111827;
height: 48px;
}
.ant-table-tbody > tr:hover > td {
background: #f7f9ff;
}
.ant-table-pagination {
margin: 14px 0 0;
display: flex;
justify-content: flex-end;
align-items: center;
}
.ant-pagination-total-text {
color: #6b7280;
margin-right: auto;
}
}
}
.actionCell {
:global {
.ant-btn {
padding: 0 4px;
}
}
}
.actionBtnView {
color: #22c55e;
}
.actionBtnDelete {
color: #ef4444;
}

@ -0,0 +1,318 @@
import React, { useMemo, useState } from 'react';
import {
Button,
Card,
Col,
DatePicker,
Form,
Input,
InputNumber,
Pagination,
Radio,
Row,
Select,
Space,
Table,
Tag,
TimePicker,
Upload,
} from 'antd';
import { CloudUploadOutlined, FileTextOutlined, PlusOutlined } from '@ant-design/icons';
import styles from './Sglr.less';
const { TextArea } = Input;
const energyTypeOptions = [
{ label: '电表', value: 'electric' },
{ label: '燃气表', value: 'gas' },
{ label: '水表', value: 'water' },
{ label: '蒸汽表', value: 'steam' },
{ label: '其他', value: 'other' },
];
const templateList = [
{ key: 'tpl-1', title: '电表日常', icon: <FileTextOutlined /> },
{ key: 'tpl-2', title: '水表日常', icon: <FileTextOutlined /> },
{ key: 'tpl-3', title: '电表日常', icon: <FileTextOutlined /> },
{ key: 'tpl-4', title: '电表日常', icon: <FileTextOutlined /> },
{ key: 'tpl-5', title: '电表日常', icon: <FileTextOutlined /> },
{ key: 'tpl-6', title: '电表日常', icon: <FileTextOutlined /> },
{ key: 'tpl-7', title: '电表日常', icon: <FileTextOutlined /> },
{ key: 'tpl-8', title: '电表日常', icon: <FileTextOutlined /> },
];
const pendingCardsSeed = [
{
id: 'TR-001',
deviceName: '变压器1#',
submitTime: '14:20:00',
energyType: '天然气',
submitter: '张三',
status: '待审核',
},
{
id: 'TR-001-2',
deviceName: '变压器1#',
submitTime: '14:20:00',
energyType: '天然气',
submitter: '张三',
status: '待审核',
},
{
id: 'TR-001-3',
deviceName: '变压器1#',
submitTime: '14:20:00',
energyType: '天然气',
submitter: '张三',
status: '待审核',
},
];
const todayRowsSeed = Array.from({ length: 10 }).map((_, idx) => ({
key: String(idx + 1),
time: '14:30:00',
device: '水表A',
energyType: '水表',
reading: 123.5,
status: idx === 1 ? '未审核' : '已审核',
}));
const Sglr = () => {
const [form] = Form.useForm();
const [todayCount] = useState(15);
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
const pendingCards = useMemo(() => pendingCardsSeed, []);
const todayRows = useMemo(() => todayRowsSeed, []);
const tableColumns = useMemo(
() => [
{ title: '时间', dataIndex: 'time', key: 'time', width: 120 },
{ title: '设备', dataIndex: 'device', key: 'device', width: 160 },
{ title: '能源类型', dataIndex: 'energyType', key: 'energyType', width: 140 },
{ title: '读数', dataIndex: 'reading', key: 'reading', width: 120 },
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 120,
render: (value) =>
value === '已审核' ? (
<Tag color="success">已审核</Tag>
) : (
<Tag color="warning">未审核</Tag>
),
},
],
[],
);
const handleSubmit = async () => {
await form.validateFields();
};
return (
<div className={styles.container}>
<Row gutter={16} className={styles.grid}>
<Col xs={24} lg={13} className={styles.leftCol}>
<div className={styles.panel}>
<div className={styles.panelHeader}>
<div className={styles.panelTitle}>新增数据</div>
<Button className={styles.headerBtn}>批量导入</Button>
</div>
<div className={styles.leftBody}>
<div className={styles.formWrap}>
<Form form={form} layout="vertical" className={styles.form}>
<Form.Item
label="能源类型"
name="energyType"
rules={[{ required: true, message: '请选择能源类型' }]}
>
<Radio.Group options={energyTypeOptions} optionType="button" />
</Form.Item>
<Form.Item
label="设备选择"
name="device"
rules={[{ required: true, message: '请选择设备' }]}
>
<Select
placeholder="请选择"
options={[
{ label: '变压器1#TR-001', value: 'TR-001' },
{ label: '水表A', value: 'WATER-A' },
]}
/>
</Form.Item>
<Form.Item
label="读数日期"
name="recordDate"
rules={[{ required: true, message: '请选择读数日期' }]}
>
<DatePicker className={styles.fullWidth} placeholder="选择日期" />
</Form.Item>
<Form.Item
label="起止时刻"
name="timeRange"
rules={[{ required: true, message: '请选择起止时刻' }]}
>
<TimePicker.RangePicker className={styles.fullWidth} format="HH:mm:ss" />
</Form.Item>
<Form.Item label="读书数值1" required>
<Space.Compact className={styles.valueRow} block>
<Select
defaultValue="读数"
options={[
{ label: '读数', value: 'reading' },
{ label: '差值', value: 'delta' },
]}
className={styles.valueSelect}
/>
<Form.Item
name="readingValue"
noStyle
rules={[{ required: true, message: '请输入读数' }]}
>
<InputNumber placeholder="请输入" className={styles.valueInput} />
</Form.Item>
<Button className={styles.addBtn} icon={<PlusOutlined />} />
</Space.Compact>
</Form.Item>
<Form.Item label="备注信息" name="remark">
<TextArea maxLength={100} showCount autoSize={{ minRows: 4, maxRows: 4 }} />
</Form.Item>
<Form.Item label="上传附件" name="files">
<Upload.Dragger
className={styles.uploader}
multiple
beforeUpload={() => false}
accept=".doc,.docx,.pdf,.png,.jpg,.jpeg"
showUploadList={false}
>
<div className={styles.uploadInner}>
<div className={styles.uploadIcon}>
<CloudUploadOutlined />
</div>
<Button className={styles.uploadBtn}>上传</Button>
<div className={styles.uploadHint}>支持 wordpdfpngjpegjpg 格式文件</div>
</div>
</Upload.Dragger>
</Form.Item>
<div className={styles.formActions}>
<Button type="primary" onClick={handleSubmit} className={styles.primaryAction}>
提交
</Button>
<Button onClick={() => form.resetFields()} className={styles.secondaryAction}>
取消
</Button>
</div>
</Form>
</div>
<div className={styles.templateWrap}>
<div className={styles.templateTitle}>日常抄表数据</div>
<div className={styles.templateList}>
{templateList.map((item) => (
<div key={item.key} className={styles.templateItem}>
<div className={styles.templateIcon}>{item.icon}</div>
<div className={styles.templateText}>{item.title}</div>
</div>
))}
</div>
</div>
</div>
</div>
</Col>
<Col xs={24} lg={11} className={styles.rightCol}>
<div className={styles.panel}>
<div className={styles.panelHeader}>
<div className={styles.panelTitle}>待审核数据</div>
</div>
<div className={styles.pendingRow}>
{pendingCards.map((item) => (
<Card key={item.id} className={styles.pendingCard} bordered={false}>
<div className={styles.pendingTop}>
<div className={styles.pendingDevice}>
设备<a className={styles.deviceLink}>{item.deviceName}{item.id}</a>
</div>
</div>
<div className={styles.pendingMeta}>
<div className={styles.metaLine}>
<span className={styles.metaKey}>提交时间</span>
<span className={styles.metaVal}>{item.submitTime}</span>
</div>
<div className={styles.metaLine}>
<span className={styles.metaKey}>能源类型</span>
<span className={styles.metaVal}>{item.energyType}</span>
</div>
<div className={styles.metaLine}>
<span className={styles.metaKey}>提交人</span>
<span className={styles.metaVal}>{item.submitter}</span>
</div>
<div className={styles.metaLine}>
<span className={styles.metaKey}>处理状态</span>
<span className={styles.statusDot} />
<span className={styles.statusText}>{item.status}</span>
</div>
</div>
<div className={styles.pendingActions}>
<Button type="primary" className={styles.detailBtn}>
查看详情
</Button>
</div>
</Card>
))}
</div>
<div className={styles.todaySection}>
<div className={styles.todayHeader}>
<div className={styles.todayTitle}>今日录入</div>
<div className={styles.todayRight}>
<span className={styles.todayCount}>已录入{todayCount}</span>
<Button type="primary" className={styles.auditBtn}>
提交审核
</Button>
</div>
</div>
<Table
className={styles.table}
columns={tableColumns}
dataSource={todayRows.slice((page - 1) * pageSize, page * pageSize)}
pagination={false}
size="small"
rowKey="key"
/>
<div className={styles.paginationWrap}>
<Pagination
current={page}
pageSize={pageSize}
total={todayRows.length}
onChange={(nextPage, nextSize) => {
setPage(nextPage);
setPageSize(nextSize);
}}
showSizeChanger
size="small"
/>
</div>
</div>
</div>
</Col>
</Row>
</div>
);
};
export default Sglr;

@ -0,0 +1,362 @@
.container {
padding: 16px;
background: #f6f8fc;
min-height: calc(89vh - 0px);
.grid {
height: 100%;
}
.panel {
background: #ffffff;
border-radius: 14px;
box-shadow: 0 6px 18px rgba(12, 24, 52, 0.06);
overflow: hidden;
}
.panelHeader {
padding: 14px 18px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #eef2f7;
.panelTitle {
font-size: 16px;
font-weight: 600;
color: #1f2a37;
}
.headerBtn {
border-radius: 18px;
height: 32px;
padding: 0 12px;
}
}
.leftBody {
display: flex;
gap: 14px;
padding: 14px;
.formWrap {
flex: 1;
min-width: 0;
padding-right: 6px;
.form {
:global {
.ant-form-item {
margin-bottom: 12px;
}
.ant-radio-group {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.ant-radio-button-wrapper {
border-radius: 16px;
overflow: hidden;
}
}
.fullWidth {
width: 100%;
}
.valueRow {
width: 100%;
.valueSelect {
width: 110px;
}
.valueInput {
width: 100%;
}
.addBtn {
width: 36px;
border-radius: 10px;
border-color: #c7d2fe;
color: #2f54eb;
}
}
.uploader {
border-radius: 12px;
:global {
.ant-upload-drag {
border-radius: 12px;
border: 1px dashed #cbd5e1;
background: #fbfcfe;
}
.ant-upload-drag:hover {
border-color: #5b7cff;
}
}
.uploadInner {
padding: 14px 12px;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
.uploadIcon {
width: 44px;
height: 44px;
border-radius: 12px;
background: rgba(91, 124, 255, 0.12);
color: #2f54eb;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
}
.uploadBtn {
border-radius: 18px;
height: 30px;
padding: 0 14px;
}
.uploadHint {
font-size: 12px;
color: #94a3b8;
line-height: 18px;
}
}
}
.formActions {
display: flex;
gap: 10px;
padding-top: 6px;
.primaryAction {
border-radius: 18px;
height: 34px;
padding: 0 22px;
background: #4b63ff;
}
.secondaryAction {
border-radius: 18px;
height: 34px;
padding: 0 22px;
}
}
}
}
.templateWrap {
width: 200px;
border-left: 1px solid #eef2f7;
padding-left: 12px;
.templateTitle {
font-size: 13px;
font-weight: 600;
color: #6b7280;
padding: 2px 2px 10px;
}
.templateList {
display: flex;
flex-direction: column;
gap: 10px;
max-height: 560px;
overflow: auto;
padding-right: 6px;
}
.templateItem {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 10px;
border-radius: 12px;
background: #f7faff;
border: 1px solid #eef2f7;
cursor: pointer;
&:hover {
border-color: #c7d2fe;
background: #f3f6ff;
}
.templateIcon {
width: 30px;
height: 30px;
border-radius: 10px;
background: rgba(47, 84, 235, 0.12);
color: #2f54eb;
display: flex;
align-items: center;
justify-content: center;
}
.templateText {
font-size: 13px;
color: #334155;
font-weight: 500;
}
}
}
}
.pendingRow {
padding: 14px;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
.pendingCard {
border-radius: 14px;
background: #f5f9ff;
border: 1px solid #cfe0ff;
box-shadow: none;
:global {
.ant-card-body {
padding: 12px;
}
}
.pendingDevice {
font-size: 12px;
color: #475569;
.deviceLink {
color: #2f54eb;
}
}
.pendingMeta {
padding-top: 8px;
display: flex;
flex-direction: column;
gap: 6px;
.metaLine {
font-size: 12px;
color: #64748b;
display: flex;
align-items: center;
.metaKey {
width: 66px;
color: #94a3b8;
flex: 0 0 auto;
}
.metaVal {
color: #475569;
}
.statusDot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #22c55e;
margin-right: 6px;
}
.statusText {
color: #16a34a;
font-weight: 600;
}
}
}
.pendingActions {
padding-top: 10px;
display: flex;
justify-content: center;
.detailBtn {
width: 100%;
border-radius: 18px;
height: 32px;
background: #4b63ff;
}
}
}
}
.todaySection {
padding: 0 14px 14px;
.todayHeader {
padding: 8px 0 10px;
display: flex;
align-items: center;
justify-content: space-between;
.todayTitle {
font-size: 14px;
font-weight: 600;
color: #1f2a37;
}
.todayRight {
display: flex;
align-items: center;
gap: 10px;
.todayCount {
color: #64748b;
font-size: 12px;
}
.auditBtn {
border-radius: 18px;
height: 32px;
background: #4b63ff;
}
}
}
.table {
:global {
.ant-table {
border-radius: 12px;
overflow: hidden;
}
.ant-table-thead > tr > th {
background: #f2f6ff;
color: #475569;
font-weight: 600;
}
}
}
.paginationWrap {
padding-top: 10px;
display: flex;
justify-content: flex-end;
}
}
}
@media (max-width: 1200px) {
.container {
.leftBody {
flex-direction: column;
.templateWrap {
width: 100%;
border-left: none;
border-top: 1px solid #eef2f7;
padding-left: 0;
padding-top: 12px;
}
}
.pendingRow {
grid-template-columns: 1fr;
}
}
}

@ -0,0 +1,290 @@
import React, { useEffect, useMemo, useState } from 'react';
import {
Button,
Divider,
Form,
Input,
InputNumber,
Pagination,
Radio,
Select,
Space,
Switch,
Table,
Tabs,
Typography,
message,
} from 'antd';
import { EditOutlined, PlusOutlined } from '@ant-design/icons';
import styles from './Sjjy.less';
const { TextArea } = Input;
const ruleTypeOptions = [
{ label: '一致性', value: '一致性' },
{ label: '完整性', value: '完整性' },
{ label: '准确性', value: '准确性' },
{ label: '其他', value: '其他' },
];
const deviceOptions = [
{ label: 'Lucy', value: 'Lucy' },
{ label: '变压器1#', value: '变压器1#' },
{ label: '水表A', value: '水表A' },
];
const paramKeyOptions = [
{ label: '容错率', value: '容错率' },
{ label: '检查周期', value: '检查周期' },
{ label: '异常级别', value: '异常级别' },
];
const seedRules = Array.from({ length: 23 }).map((_, idx) => ({
id: idx + 1,
name: '电表巡检检查',
type: '一致性',
enabled: true,
lastRun: '2025-10-15 14:30:00',
}));
const Sjjy = () => {
const [activeTab, setActiveTab] = useState('rules');
const [editing, setEditing] = useState(false);
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(8);
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const [rules, setRules] = useState(() => seedRules);
const [activeRuleId, setActiveRuleId] = useState(() => seedRules[0]?.id);
const [form] = Form.useForm();
const activeRule = useMemo(() => rules.find((r) => r.id === activeRuleId) || rules[0], [activeRuleId, rules]);
const pageData = useMemo(() => {
const start = (page - 1) * pageSize;
return rules.slice(start, start + pageSize);
}, [page, pageSize, rules]);
useEffect(() => {
if (!activeRule) return;
form.setFieldsValue({
ruleName: activeRule.name,
ruleType: activeRule.type,
device: 'Lucy',
ruleLogic: '内容信息',
params: [
{ key: '容错率', value: 3 },
{ key: '检查周期', value: 3 },
{ key: '异常级别', value: 3 },
],
autoHandle: 'useLast',
notify: 'system',
});
}, [activeRule, form]);
const handleAdd = () => message.info('新增');
const handleBatchToggle = () => message.info('批量启用/禁用');
const handleTemplate = () => message.info('规则模板');
const columns = useMemo(
() => [
{
title: '规则名称',
dataIndex: 'name',
key: 'name',
width: 220,
render: (val) => <span className={styles.ruleNameCell}>{val}</span>,
},
{
title: '类型',
dataIndex: 'type',
key: 'type',
width: 110,
},
{
title: '状态',
dataIndex: 'enabled',
key: 'enabled',
width: 110,
render: (val, record) => (
<Switch
checked={val}
onChange={(checked) => {
setRules((prev) => prev.map((r) => (r.id === record.id ? { ...r, enabled: checked } : r)));
}}
/>
),
},
{
title: '最后执行',
dataIndex: 'lastRun',
key: 'lastRun',
width: 180,
},
],
[],
);
return (
<div className={styles.container}>
<div className={styles.leftPanel}>
<Tabs
className={styles.topTabs}
activeKey={activeTab}
onChange={setActiveTab}
items={[
{ key: 'rules', label: '校验规则' },
{ key: 'test', label: '测试规则' },
{ key: 'template', label: '规则模板' },
]}
/>
{activeTab !== 'rules' ? (
<div className={styles.placeholder}>模块待开发</div>
) : (
<>
<div className={styles.leftToolbar}>
<div className={styles.leftToolbarLeft}>
<Space size={10}>
<Button className={styles.primaryBtn} type="primary" icon={<PlusOutlined />} onClick={handleAdd}>
新增
</Button>
<Button className={styles.ghostBtn} onClick={handleBatchToggle}>
批量启用/禁用
</Button>
<Button className={styles.ghostBtn} onClick={handleTemplate}>
规则模板
</Button>
</Space>
</div>
<div className={styles.leftToolbarRight}>
<Typography.Text className={styles.counterText}>当前生效规则(): 12</Typography.Text>
</div>
</div>
<div className={styles.listWrap}>
<Table
rowKey="id"
size="middle"
columns={columns}
dataSource={pageData}
pagination={false}
rowSelection={{
selectedRowKeys,
onChange: (keys) => setSelectedRowKeys(keys),
columnWidth: 44,
}}
rowClassName={(record) => (record.id === activeRuleId ? styles.rowActive : '')}
onRow={(record) => ({
onClick: () => setActiveRuleId(record.id),
})}
/>
<div className={styles.paginationBar}>
<Pagination
current={page}
pageSize={pageSize}
total={rules.length}
showSizeChanger={false}
onChange={(nextPage, nextPageSize) => {
setPage(nextPage);
if (nextPageSize && nextPageSize !== pageSize) setPageSize(nextPageSize);
}}
/>
</div>
</div>
</>
)}
</div>
<div className={styles.rightPanel}>
<div className={styles.rightHeader}>
<div className={styles.rightTitle}>详情</div>
<Button
type="link"
icon={<EditOutlined />}
className={styles.editBtn}
onClick={() => setEditing((v) => !v)}
>
编辑
</Button>
</div>
<div className={styles.detailBody}>
<Form
form={form}
className={styles.detailForm}
layout="horizontal"
labelCol={{ flex: '92px' }}
wrapperCol={{ flex: 'auto' }}
colon={false}
disabled={!editing}
>
<Form.Item label="规则名称" name="ruleName">
<Input placeholder="请输入" />
</Form.Item>
<Form.Item label="规则类型" name="ruleType">
<Radio.Group options={ruleTypeOptions} />
</Form.Item>
<Form.Item label="适用设备" name="device">
<Select placeholder="请选择" options={deviceOptions} />
</Form.Item>
<Form.Item label="校验逻辑" name="ruleLogic">
<TextArea rows={3} placeholder="请输入" />
</Form.Item>
<Form.Item label="参数设置">
<Form.List name="params">
{(fields) => (
<div className={styles.paramList}>
{fields.map((field, index) => (
<div key={field.key} className={styles.paramRow}>
<div className={styles.paramLabel}>{`参数${index + 1}`}</div>
<Space.Compact className={styles.paramCompact}>
<Form.Item name={[field.name, 'key']} noStyle>
<Select options={paramKeyOptions} className={styles.paramSelect} />
</Form.Item>
<Form.Item name={[field.name, 'value']} noStyle>
<InputNumber min={0} className={styles.paramNumber} />
</Form.Item>
</Space.Compact>
</div>
))}
</div>
)}
</Form.List>
</Form.Item>
<Divider className={styles.divider} />
<div className={styles.sectionTitle}>异常处理</div>
<Form.Item label="自动处理" name="autoHandle">
<Radio.Group
options={[
{ label: '使用上次有效值', value: 'useLast' },
{ label: '重新设置', value: 'reset' },
]}
/>
</Form.Item>
<Form.Item label="报警方式" name="notify">
<Radio.Group
options={[
{ label: '系统提示', value: 'system' },
{ label: '邮件', value: 'email' },
{ label: '短信', value: 'sms' },
{ label: 'APP推送', value: 'app' },
]}
/>
</Form.Item>
</Form>
</div>
</div>
</div>
);
};
export default Sjjy;

@ -0,0 +1,248 @@
.container {
display: flex;
gap: 14px;
width: 100%;
height: 100%;
min-height: 560px;
padding: 12px;
background: linear-gradient(180deg, rgba(241, 248, 255, 1) 0%, rgba(246, 250, 255, 1) 100%);
}
.leftPanel {
flex: 1.15;
min-width: 560px;
background: rgba(255, 255, 255, 0.92);
border-radius: 14px;
border: 1px solid rgba(226, 232, 240, 0.7);
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.06);
overflow: hidden;
display: flex;
flex-direction: column;
}
.rightPanel {
flex: 1;
min-width: 420px;
background: rgba(255, 255, 255, 0.92);
border-radius: 14px;
border: 1px solid rgba(226, 232, 240, 0.7);
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.06);
overflow: hidden;
display: flex;
flex-direction: column;
}
.topTabs {
padding: 6px 14px 0;
:global {
.ant-tabs-nav {
margin: 0;
}
.ant-tabs-tab {
padding: 12px 2px 10px;
font-weight: 600;
color: rgba(15, 23, 42, 0.65);
}
.ant-tabs-tab-active {
.ant-tabs-tab-btn {
color: rgba(15, 23, 42, 0.95);
}
}
}
}
.placeholder {
padding: 18px 16px;
color: rgba(15, 23, 42, 0.55);
}
.leftToolbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 14px 10px;
}
.leftToolbarLeft {
display: flex;
align-items: center;
}
.leftToolbarRight {
display: flex;
align-items: center;
}
.counterText {
font-size: 12px;
color: rgba(15, 23, 42, 0.55);
}
.primaryBtn {
height: 32px !important;
border-radius: 999px !important;
padding: 0 16px !important;
box-shadow: none !important;
background-color: rgba(72, 81, 255, 1);
:global {
.ant-btn-icon {
margin-right: 6px;
}
}
}
.ghostBtn {
height: 32px !important;
border-radius: 999px !important;
padding: 0 16px !important;
border: 1px solid #e7eaf2 !important;
background: #fff !important;
color: #5b6070 !important;
box-shadow: none !important;
}
.listWrap {
padding: 0 14px 12px;
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.paginationBar {
display: flex;
justify-content: flex-end;
padding: 12px 0 0;
}
.rowActive {
:global {
td {
background: rgba(72, 81, 255, 0.06) !important;
}
}
}
.ruleNameCell {
color: rgba(15, 23, 42, 0.9);
}
:global {
.ant-table {
border-radius: 10px;
overflow: hidden;
}
.ant-table-thead > tr > th {
background: #f7f8fb;
color: #6d7383;
font-weight: 500;
}
.ant-table-tbody > tr > td {
color: #2b2f3a;
}
.ant-table-tbody > tr:hover > td {
background: #fafbff;
}
}
.rightHeader {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 16px;
border-bottom: 1px solid rgba(226, 232, 240, 0.6);
background: rgba(255, 255, 255, 0.78);
backdrop-filter: blur(8px);
}
.rightTitle {
font-size: 16px;
font-weight: 700;
color: rgba(15, 23, 42, 0.95);
}
.editBtn {
padding: 0 4px !important;
}
.detailBody {
padding: 12px 16px 16px;
overflow: auto;
}
.detailForm {
:global {
.ant-form-item {
margin-bottom: 14px;
}
.ant-form-item-label > label {
color: rgba(15, 23, 42, 0.65);
font-weight: 500;
}
.ant-input,
.ant-input-affix-wrapper,
.ant-select-selector {
border-radius: 8px !important;
}
}
}
.divider {
margin: 8px 0 12px !important;
}
.sectionTitle {
font-size: 13px;
font-weight: 700;
color: rgba(15, 23, 42, 0.85);
margin: 0 0 10px;
}
.paramList {
display: flex;
flex-direction: column;
gap: 10px;
}
.paramRow {
display: flex;
align-items: center;
gap: 10px;
}
.paramLabel {
width: 52px;
color: rgba(15, 23, 42, 0.55);
}
.paramCompact {
flex: 1;
}
.paramSelect {
width: 160px;
}
.paramNumber {
width: 90px;
}
@media (max-width: 1200px) {
.container {
flex-direction: column;
}
.leftPanel,
.rightPanel {
min-width: 0;
}
}

@ -0,0 +1,272 @@
import React, { useEffect, useMemo, useState } from 'react';
import {
Button,
Card,
Checkbox,
Form,
Input,
InputNumber,
Radio,
Select,
Space,
Switch,
Table,
Tabs,
Typography,
} from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import styles from './Zdcj.less';
const { Text } = Typography;
const ruleTypeLabel = {
consistency: '一致性',
completeness: '完整性',
accuracy: '准确性',
other: '其他',
};
const Zdcj = () => {
const [activeTab, setActiveTab] = useState('rules');
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const [selectedRuleId, setSelectedRuleId] = useState('r-4');
const [form] = Form.useForm();
const ruleList = useMemo(
() =>
new Array(9).fill(0).map((_, idx) => {
const id = `r-${idx + 1}`;
return {
id,
name: '电表漏电增值查',
type: 'consistency',
enabled: idx !== 1,
lastRun: '2025-10-15 14:30:00',
device: 'Lucy',
logic: '内容信息',
params: [
{ key: '告警阈值', value: 3 },
{ key: '检查周期', value: 3 },
{ key: '异常级别', value: 3 },
],
autoAction: 'lastValid',
notify: ['system'],
};
}),
[],
);
const currentRule = useMemo(
() => ruleList.find((r) => r.id === selectedRuleId) || ruleList[0],
[ruleList, selectedRuleId],
);
useEffect(() => {
if (!currentRule) return;
form.setFieldsValue({
ruleName: currentRule.name,
ruleType: currentRule.type,
applyDevice: currentRule.device,
checkLogic: currentRule.logic,
params: currentRule.params,
autoAction: currentRule.autoAction,
notify: currentRule.notify,
});
}, [currentRule, form]);
const columns = useMemo(
() => [
{
title: '规则名称',
dataIndex: 'name',
key: 'name',
ellipsis: true,
},
{
title: '类型',
dataIndex: 'type',
key: 'type',
width: 100,
render: (v) => ruleTypeLabel[v] || '--',
},
{
title: '状态',
dataIndex: 'enabled',
key: 'enabled',
width: 90,
align: 'center',
render: (v) => <Switch size="small" checked={!!v} onClick={(e) => e?.stopPropagation?.()} />,
},
{
title: '最后执行',
dataIndex: 'lastRun',
key: 'lastRun',
width: 170,
},
],
[],
);
const leftContent = useMemo(() => {
if (activeTab !== 'rules') {
return <div className={styles.placeholder}>该功能待接入接口/页面</div>;
}
return (
<>
<div className={styles.leftToolbar}>
<Space size={10} wrap>
<Button type="primary" icon={<PlusOutlined />}>
新增
</Button>
<Button>批量启用/禁用</Button>
<Button>规则模板</Button>
</Space>
<Text type="secondary">当前生效规则/总计12</Text>
</div>
<div className={styles.tableWrap}>
<Table
rowKey="id"
size="middle"
columns={columns}
dataSource={ruleList}
pagination={{ pageSize: 8, showSizeChanger: false }}
rowSelection={{
selectedRowKeys,
onChange: (keys) => setSelectedRowKeys(keys),
columnWidth: 44,
}}
rowClassName={(record) =>
record.id === selectedRuleId ? styles.selectedRow : ''
}
onRow={(record) => ({
onClick: () => setSelectedRuleId(record.id),
})}
/>
</div>
</>
);
}, [activeTab, columns, ruleList, selectedRowKeys, selectedRuleId]);
return (
<div className={styles.container}>
<div className={styles.layout}>
<div className={styles.leftPane}>
<Card
className={styles.panelCard}
bodyStyle={{ padding: 12 }}
title={
<Tabs
activeKey={activeTab}
onChange={setActiveTab}
items={[
{ key: 'rules', label: '校验规则' },
{ key: 'test', label: '测试规则' },
{ key: 'template', label: '规则模板' },
]}
/>
}
>
{leftContent}
</Card>
</div>
<div className={styles.rightPane}>
<Card
className={styles.panelCard}
title={<span className={styles.panelTitle}>详情</span>}
extra={
<Button type="link" className={styles.editLink}>
编辑
</Button>
}
bodyStyle={{ padding: 16 }}
>
<Form
form={form}
layout="horizontal"
labelCol={{ flex: '86px' }}
wrapperCol={{ flex: 1 }}
colon={false}
className={styles.detailForm}
>
<Form.Item label="规则名称" name="ruleName">
<Input placeholder="请输入" />
</Form.Item>
<Form.Item label="规则类型" name="ruleType">
<Radio.Group>
<Radio value="consistency">一致性</Radio>
<Radio value="completeness">完整性</Radio>
<Radio value="accuracy">准确性</Radio>
<Radio value="other">其他</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="适用设备" name="applyDevice">
<Select
options={[
{ value: 'Lucy', label: 'Lucy' },
{ value: 'Tom', label: 'Tom' },
{ value: 'A-01', label: 'A-01' },
]}
/>
</Form.Item>
<Form.Item label="校验逻辑" name="checkLogic">
<Input placeholder="内容信息" />
</Form.Item>
<div className={styles.sectionTitle}>参数设置</div>
{[0, 1, 2].map((idx) => (
<div className={styles.paramRow} key={idx}>
<div className={styles.paramLabel}>{`参数${idx + 1}`}</div>
<div className={styles.paramControl}>
<Space size={10}>
<Form.Item name={['params', idx, 'key']} noStyle>
<Select
style={{ width: 120 }}
placeholder="请选择"
options={[
{ value: '告警阈值', label: '告警阈值' },
{ value: '检查周期', label: '检查周期' },
{ value: '异常级别', label: '异常级别' },
]}
/>
</Form.Item>
<Form.Item name={['params', idx, 'value']} noStyle>
<InputNumber style={{ width: 72 }} min={0} />
</Form.Item>
</Space>
</div>
</div>
))}
<div className={styles.sectionTitle}>异常处理</div>
<Form.Item label="自动处理" name="autoAction">
<Radio.Group>
<Radio value="lastValid">使用上次有效值</Radio>
<Radio value="reset">重新设置</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="报警方式" name="notify">
<Checkbox.Group
options={[
{ label: '系统提示', value: 'system' },
{ label: '邮件', value: 'email' },
{ label: '短信', value: 'sms' },
{ label: 'APP推送', value: 'app' },
]}
/>
</Form.Item>
</Form>
</Card>
</div>
</div>
</div>
);
};
export default Zdcj;

@ -0,0 +1,271 @@
import { useState, useEffect } from 'react'
import { Col, DatePicker, Form, Input, Modal, Row, Select } from 'antd'
import SelectDeptTree from '@/components/SelectDeptTree'
import SelectOrganTree from '@/components/SelectOrganTree'
import datadictionary from '@/utils/dataDictionary'
import { formatDictOptions, verifyPhone } from '@/utils/globalCommon'
import { NumberInput } from '@/components/NumberInput'
import styles from '../StaffSheetList.less'
import style from '@/global.less'
import dayjs from 'dayjs'
import { formatDate } from '@/utils/formatUtils'
const { Item: FormItem } = Form
const { TextArea } = Input
const dictData = datadictionary
//新增表单
let getDeptTreeBySelectTree
let getOrganTreeBySelectTree
const StaffSheetCreateForm = (props => {
const [form] = Form.useForm()
const [jobStatus, setJobStatus] = useState('1')
const {
modalVisible,
handleAdd,
handleModalVisible,
loading,
dispatch,
selectDeptTree,
selectOrganTree
} = props
useEffect(() => {
form.setFieldsValue({
user_type: 'employee',
job_status: '1',
mgr_type: '0'
})
}, [])
const selectedDeptTreeValue = (deptRecord) => {
getDeptTreeBySelectTree = deptRecord
}
const selectedOrganTreeValue = (orgRecord) => {
getOrganTreeBySelectTree = orgRecord
}
const parentDeptTreeMethod = {
dispatch: dispatch,
selectDeptTree: selectDeptTree,
selectedDeptTreeValue: selectedDeptTreeValue,
}
const parentOrganTreeMethod = {
dispatch: dispatch,
selectOrganTree: selectOrganTree,
selectedOrganTreeValue: selectedOrganTreeValue
}
const okHandle = () => {
form.validateFields()
.then(fieldsValue => {
form.resetFields()
fieldsValue.birthday = formatDate(fieldsValue.birthday, 'YYYY-MM-DD')
fieldsValue.hiredate = formatDate(fieldsValue.hiredate, 'YYYY-MM-DD')
fieldsValue.departure_time = formatDate(fieldsValue.departure_time, 'YYYY-MM-DD')
fieldsValue.posts = fieldsValue.posts ? JSON.stringify(fieldsValue.posts) : null
// if (getDeptTreeBySelectTree) {
// fieldsValue.dept_code = getDeptTreeBySelectTree.dept_code
// fieldsValue.dept_name = getDeptTreeBySelectTree.title
// }
if (getOrganTreeBySelectTree) {
fieldsValue.org_code = getOrganTreeBySelectTree.org_code
fieldsValue.org_name = getOrganTreeBySelectTree.title
}
handleAdd(fieldsValue)
})
.catch(errInfo => {})
}
const afterClose = () =>{
form.resetFields();
}
const handleJobStatusChange = (value) => {
setJobStatus(value)
}
return (
<Modal
width={800}
height={550}
bodyStyle={{ height: '500px', overflowY: 'auto' }}
className={style.createForm}
centered
destroyOnClose
title='新增'
open={modalVisible}
onOk={okHandle}
onCancel={() => handleModalVisible()}
afterClose={() => afterClose()}
confirmLoading={loading}
>
<Form form={form} layout='vertical' requiredMark={false}>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='登录账号' name='user_name' rules={[{ required: true, message: '请输入至少2个字符的用户名', min: 2 }]}>
<Input placeholder='请输入' />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='用户名称' name='user_name_cn' rules={[{ required: true, message: '请输入至少2个字符的用户名称', min: 2 }]}>
<Input placeholder='请输入' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='用户性别' name='sex'>
<Select options={formatDictOptions(dictData.sys_user_sex, 'dict_label', 'dict_value')} placeholder='请选择' />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='用户生日' name='birthday'>
<DatePicker format='YYYY-MM-DD' placeholder='请选择' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='手机号码' name='phone' rules={[{ required: false, min: 1, validator: verifyPhone }]}>
<NumberInput placeholder='请输入' style={{width: '100%'}} maxLength={11} />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='电子邮箱' name='email' rules={[{ type: 'email' }]}>
<Input placeholder='请输入' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='机构名称' name='org_code'>
<SelectOrganTree {...parentOrganTreeMethod} />
</FormItem>
</Col>
{/*<Col md={12} sm={24}>*/}
{/* <FormItem label='部门名称' name='dept_code'>*/}
{/* <SelectDeptTree {...parentDeptTreeMethod} placeholder={'请选择部门'} />*/}
{/* </FormItem>*/}
{/*</Col>*/}
<Col md={12} sm={24}>
<FormItem label='所属岗位' name='posts'>
<Select
mode='multiple'
allowClear
placeholder='请选择'
options={formatDictOptions(dictData.sys_user_post, 'dict_label', 'dict_value')}
/>
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
{/*<Col md={12} sm={24}>
<FormItem
label={
<span>
密码
<em className={styles.optional}>
<Tooltip title='默认密码123456'>
<InfoCircleOutlined style={{ marginLeft: 4 }} />
</Tooltip>
</em>
</span>
}
name='password'
initialValue={'123456'}
rules={[{required: true, message: '请输入至少6个字符的密码', min: 6}]}>
<Input placeholder='请输入' type='password'/>
</FormItem>
</Col>*/}
<Col md={12} sm={24}>
<FormItem label='在职状态' name='job_status'>
<Select
placeholder='请选择'
options={formatDictOptions(dictData.sys_job_status, 'dict_label', 'dict_value')}
onChange={handleJobStatusChange}
/>
</FormItem>
</Col>
{jobStatus === '1' ?
<Col md={12} sm={24}>
<FormItem label='入职时间' name='hiredate' initialValue={dayjs().endOf('day')}>
<DatePicker format='YYYY-MM-DD' placeholder='请选择' />
</FormItem>
</Col>
:
<Col md={12} sm={24}>
<FormItem label='离职时间' name='departure_time'>
<DatePicker format='YYYY-MM-DD' placeholder='请选择' />
</FormItem>
</Col>
}
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='员工类型' name='user_type'>
<Select
placeholder='请选择'
options={formatDictOptions(dictData.sys_user_type, 'dict_label', 'dict_value')}
/>
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='员工工号' name='emp_no'>
<Input placeholder='请输入' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='门禁卡号' name='access_card_no'>
<Input placeholder='请输入' />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='是否是管理员' name='mgr_type'>
<Select
style={{width: '100%'}}
placeholder='请选择'
options={formatDictOptions(dictData.sys_mgr_type, 'dict_label', 'dict_value')}
/>
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={24} sm={24}>
<FormItem label='备注' name='remarks'>
<TextArea rows={4} />
</FormItem>
</Col>
</Row>
</Form>
</Modal>
)
})
export default StaffSheetCreateForm

@ -0,0 +1,113 @@
import { useEffect } from 'react'
import { Button, Col, Form, Input, Row } from 'antd'
import { UpOutlined, SearchOutlined, RedoOutlined } from '@ant-design/icons'
import SelectDeptTree from '@/components/SelectDeptTree'
import SelectOrganTree from '@/components/SelectOrganTree'
import style from '@/global.less'
const { Item: FormItem } = Form
let getDeptTreeBySelectTree
let getOrganTreeBySelectTree
const StaffSheetRenderAdvancedForm = (props) => {
const [form] = Form.useForm()
const { dispatch, handleSearch, handleFormReset, toggleForm, selectDeptTree, selectOrganTree, params } = props
useEffect(() => {
form.setFieldsValue({
user_name: params?.user_name,
user_name_cn: params?.user_name_cn,
deptname: params?.deptname,
orgname: params?.orgname,
})
}, [params])
const onFinish = values => {
// if (getDeptTreeBySelectTree) {
// values.dept_code = getDeptTreeBySelectTree.dept_code
// values.deptname = getDeptTreeBySelectTree.title
// }
if (getOrganTreeBySelectTree) {
values.org_code = getOrganTreeBySelectTree.org_code
values.orgname = getOrganTreeBySelectTree.title
}
handleSearch(values)
}
const myHandleFormReset = () => {
form.resetFields()
handleFormReset()
}
const selectedDeptTreeValue = (deptRecord) => {
getDeptTreeBySelectTree = deptRecord
}
const selectedOrganTreeValue = (orgRecord) => {
getOrganTreeBySelectTree = orgRecord
}
const parentDeptTreeMethod = {
dispatch: dispatch,
selectDeptTree: selectDeptTree,
selectedDeptTreeValue: selectedDeptTreeValue
}
const parentOrganTreeMethod = {
dispatch: dispatch,
selectOrganTree: selectOrganTree,
selectedOrganTreeValue: selectedOrganTreeValue
}
return (
<Form form={form} onFinish={onFinish} layout='inline'>
<Row gutter={{ md: 8, lg: 24, xl: 48 }} className={style.searchInput}>
<Col md={8} sm={24}>
<FormItem label='用户名' name='user_name'>
<Input placeholder='请输入' />
</FormItem>
</Col>
<Col md={8} sm={24}>
<FormItem label='用户名称' name='user_name_cn'>
<Input placeholder='请输入' />
</FormItem>
</Col>
<Col md={8} sm={24}>
<FormItem label='机构代码' name='orgname'>
<SelectOrganTree {...parentOrganTreeMethod} />
</FormItem>
</Col>
</Row>
<Row gutter={{md: 8, lg: 24, xl: 48}} className={style.searchBox}>
{/*<Col md={8} sm={24}>*/}
{/* <FormItem label='部门名称' name='deptname'>*/}
{/* <SelectDeptTree placeholder={'请选择部门'} {...parentDeptTreeMethod} />*/}
{/* </FormItem>*/}
{/*</Col>*/}
<Col md={24} sm={24}>
<div className={style.searchBtn}>
<Button type='primary' htmlType='submit'>
查询
</Button>
<Button onClick={myHandleFormReset}>
重置
</Button>
<a onClick={() => toggleForm(form)}>
收起 <UpOutlined />
</a>
</div>
</Col>
</Row>
</Form>
)
}
export default StaffSheetRenderAdvancedForm

@ -0,0 +1,81 @@
import { useEffect } from 'react'
import {Button, Col, Form, Input, Row, DatePicker, Select} from 'antd'
import {DownOutlined, RedoOutlined, SearchOutlined} from '@ant-design/icons'
import style from '@/global.less'
import dayjs from 'dayjs'
const { Item: FormItem } = Form
const StaffSheetRenderSimpleForm = (props) => {
const [form] = Form.useForm()
const { handleSearch, handleFormReset, toggleForm, params } = props
useEffect(() => {
form.setFieldsValue({
user_name: params?.user_name,
user_name_cn: params?.user_name_cn,
})
}, [params])
const onFinish = values => {
handleSearch(values)
}
const myHandleFormReset = () => {
form.resetFields()
handleFormReset()
}
return (
<Form form={form} onFinish={onFinish} layout='inline'>
<Row gutter={{ md: 8, lg: 24, xl: 48 }} className={style.searchInput}>
<Col md={4} sm={24}>
<FormItem label='我的查询条件' name='wdcxtj'>
<Select
placeholder='请选择'
options={[]}
/>
</FormItem>
</Col>
<Col md={4} sm={24}>
<FormItem label='日期' name='rq' rules={[{ required: true, message: '请选择日期!' }]}>
<DatePicker defaultValue={dayjs('2025-04-10', 'YYYY-MM-DD')} format='YYYY-MM-DD' />
</FormItem>
</Col>
<Col md={4} sm={24}>
<FormItem label='工作地点' name='gzdd'>
<Input placeholder='请输入' />
</FormItem>
</Col>
<Col md={4} sm={24}>
<FormItem label='工号' name='gh'>
<Input placeholder='请输入' defaultValue="123456"/>
</FormItem>
</Col>
<Col md={4} sm={24}>
<FormItem label='名称' name='gh'>
<Input placeholder='请输入'/>
</FormItem>
</Col>
<Col md={4} sm={24}>
<div className={style.searchBtn}>
<Button type='primary' htmlType='submit'>
查询
</Button>
<Button onClick={myHandleFormReset}>
重置
</Button>
</div>
</Col>
</Row>
</Form>
)
}
export default StaffSheetRenderSimpleForm

@ -0,0 +1,362 @@
import { useState, useEffect } from 'react'
import { Col, DatePicker, Form, Input, Modal, Row, Select } from 'antd'
import SelectOrganTree from '@/components/SelectOrganTree'
import datadictionary from '@/utils/dataDictionary'
import { formatDictOptions, verifyPhone } from '@/utils/globalCommon'
import { NumberInput } from '@/components/NumberInput'
import styles from '../StaffSheetList.less'
import style from '@/global.less'
import dayjs from 'dayjs'
import { formatDate, formatDateObject } from '@/utils/formatUtils'
const { Item: FormItem } = Form
const { TextArea } = Input
const dictData = datadictionary
//新增表单
let getDeptTreeBySelectTree
let getOrganTreeBySelectTree
const StaffSheetUpdateForm = (props) => {
const [form] = Form.useForm()
const [jobStatus, setJobStatus] = useState('1')
const [userStatus, setUserStatus] = useState('0')
const {
handleUpdate,
updateModalVisible,
handleUpdateModalVisible,
values,
loading,
dispatch,
selectDeptTree,
selectOrganTree
} = props
const selectedDeptTreeValue = (deptRecord) => {
getDeptTreeBySelectTree = deptRecord
}
const selectedOrganTreeValue = (orgRecord) => {
getOrganTreeBySelectTree = orgRecord
}
const parentDeptTreeMethod = {
dispatch: dispatch,
selectDeptTree: selectDeptTree,
selectedDeptTreeValue: selectedDeptTreeValue,
}
const parentOrganTreeMethod = {
dispatch: dispatch,
selectOrganTree: selectOrganTree,
selectedOrganTreeValue: selectedOrganTreeValue
}
useEffect(() => {
setJobStatus(values.job_status)
setUserStatus(values.status)
form.setFieldsValue({
user_id: values.user_id,
user_name: values.user_name,
user_name_cn: values.user_name_cn,
user_name_en: values.user_name_en,
password: values.password,
email: values.email,
phone: values.phone,
landline: values.landline,
sex: values.sex,
avatar: values.avatar,
sign: values.sign,
tags: values.tags,
id_card: values.id_card,
birthday: formatDateObject(values.birthday, 'YYYY-MM-DD'),
job_status: values.job_status,
hiredate: formatDateObject(values.hiredate, 'YYYY-MM-DD'),
departure_time: formatDateObject(values.departure_time, 'YYYY-MM-DD'),
user_type: values.user_type,
emp_no: values.emp_no,
access_card_no: values.access_card_no,
country: values.country,
province: values.province,
city: values.city,
address: values.address,
work_addr: values.work_addr,
floor: values.floor,
inprovince: values.inprovince,
// dept_code: values.dept_code,
// dept_name: values.dept_name,
inner_dept_code: values.inner_dept_code,
org_code: values.org_code,
org_name: values.org_name,
inner_org_code: values.inner_org_code,
posts: values.posts ? JSON.parse(values.posts) : [],
wx_openid: values.wx_openid,
wx_mpopenid: values.wx_mpopenid,
wx_miniopenid: values.wx_miniopenid,
wx_unionid: values.wx_unionid,
mobile_imei: values.mobile_imei,
device_num: values.device_num,
al_taobao: values.al_taobao,
al_alipay: values.al_alipay,
al_dingding: values.al_dingding,
is_system_user: values.is_system_user,
mgr_type: values.mgr_type,
pwd_security_level: values.pwd_security_level,
pwd_update_date: values.pwd_update_date,
last_login_ip: values.last_login_ip,
last_login_date: values.last_login_date,
freeze_date: values.freeze_date,
freeze_cause: values.freeze_cause,
zindex: values.zindex,
wx_msg: values.wx_msg,
email_msg: values.email_msg,
system_msg: values.system_msg,
remarks: values.remarks,
status: values.status,
creator: values.creator,
create_date: values.create_date,
updater: values.updater,
update_date: values.update_date
})
}, [])
const handleLocalUpdate = () => {
form
.validateFields()
.then(fieldsValue => {
const formVals = {...values, ...fieldsValue}
formVals.birthday = formatDate(formVals.birthday, 'YYYY-MM-DD')
formVals.hiredate = formatDate(formVals.hiredate, 'YYYY-MM-DD')
formVals.departure_time = formatDate(formVals.departure_time, 'YYYY-MM-DD')
formVals.posts = formVals.posts ? JSON.stringify(formVals.posts) : null
formVals.freeze_date = '3' === formVals.status ? formatDate(dayjs().endOf('day'), 'YYYY-MM-DD') : null
formVals.freeze_cause = '3' === formVals.status ? formVals.freeze_cause : null
// if (getDeptTreeBySelectTree) {
// formVals.dept_code = getDeptTreeBySelectTree.dept_code
// formVals.dept_name = getDeptTreeBySelectTree.title
// }
if (getOrganTreeBySelectTree) {
formVals.org_code = getOrganTreeBySelectTree.org_code
formVals.org_name = getOrganTreeBySelectTree.title
}
handleUpdate(formVals)
})
.catch(errInfo => {})
}
const afterClose = () =>{
form.resetFields();
}
const handleJobStatusChange = (value) => {
setJobStatus(value)
}
const handleUserStatusChange = (value) => {
setUserStatus(value)
}
return (
<Modal
width={800}
height={550}
bodyStyle={{ height: '500px', overflowY: 'auto' }}
className={style.updateForm}
centered
destroyOnClose
title='修改'
open={updateModalVisible}
onOk={() => handleLocalUpdate()}
onCancel={() => handleUpdateModalVisible()}
afterClose={() => afterClose()}
confirmLoading={loading}
>
<Form form={form} layout='vertical' requiredMark={false}>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='登录账号' name='user_name' rules={[{ required: true, message: '请输入至少2个字符的用户名', min: 2 }]}>
<Input placeholder='请输入' />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='用户名称' name='user_name_cn' rules={[{ required: true, message: '请输入至少2个字符的用户名称', min: 2 }]}>
<Input placeholder='请输入' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='用户性别' name='sex'>
<Select options={formatDictOptions(dictData.sys_user_sex, 'dict_label', 'dict_value')} placeholder='请选择' />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='用户生日' name='birthday'>
<DatePicker format='YYYY-MM-DD' placeholder='请选择' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='手机号码' name='phone' rules={[{ required: false, min: 1, validator: verifyPhone }]}>
<NumberInput placeholder='请输入' style={{width: '100%'}} maxLength={11} />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='电子邮箱' name='email' rules={[{ type: 'email' }]}>
<Input placeholder='请输入' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='机构名称' name='org_code'>
<SelectOrganTree {...parentOrganTreeMethod} />
</FormItem>
</Col>
{/*<Col md={12} sm={24}>*/}
{/* <FormItem label='部门名称' name='dept_code'>*/}
{/* <SelectDeptTree {...parentDeptTreeMethod} placeholder={'请选择部门'} />*/}
{/* </FormItem>*/}
{/*</Col>*/}
<Col md={12} sm={24}>
<FormItem label='所属岗位' name='posts'>
<Select
mode='multiple'
allowClear
placeholder='请选择'
options={formatDictOptions(dictData.sys_user_post, 'dict_label', 'dict_value')}
/>
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
{/*<Col md={12} sm={24}>
<FormItem
label={
<span>
密码
<em className={styles.optional}>
<Tooltip title='默认密码123456'>
<InfoCircleOutlined style={{ marginLeft: 4 }} />
</Tooltip>
</em>
</span>
}
name='password'
initialValue={'123456'}
rules={[{required: true, message: '请输入至少6个字符的密码', min: 6}]}>
<Input placeholder='请输入' type='password'/>
</FormItem>
</Col>*/}
<Col md={12} sm={24}>
<FormItem label='在职状态' name='job_status'>
<Select
placeholder='请选择'
options={formatDictOptions(dictData.sys_job_status, 'dict_label', 'dict_value')}
onChange={handleJobStatusChange}
/>
</FormItem>
</Col>
{jobStatus === '1' ?
<Col md={12} sm={24}>
<FormItem label='入职时间' name='hiredate' initialValue={dayjs().endOf('day')}>
<DatePicker format='YYYY-MM-DD' placeholder='请选择' />
</FormItem>
</Col>
:
<Col md={12} sm={24}>
<FormItem label='离职时间' name='departure_time'>
<DatePicker format='YYYY-MM-DD' placeholder='请选择' />
</FormItem>
</Col>
}
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='员工类型' name='user_type'>
<Select
placeholder='请选择'
options={formatDictOptions(dictData.sys_user_type, 'dict_label', 'dict_value')}
/>
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='员工工号' name='emp_no'>
<Input placeholder='请输入' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='门禁卡号' name='access_card_no'>
<Input placeholder='请输入' />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='是否是管理员' name='mgr_type'>
<Select
style={{width: '100%'}}
placeholder='请选择'
options={formatDictOptions(dictData.sys_mgr_type, 'dict_label', 'dict_value')}
/>
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='状态' name='status'>
<Select
placeholder='请选择'
options={formatDictOptions(dictData.user_status, 'dict_label', 'dict_value')}
onChange={handleUserStatusChange}
/>
</FormItem>
</Col>
{ userStatus === '3' &&
<Col md={12} sm={24}>
<FormItem label='冻结原因' name='freeze_cause'>
<Input placeholder='请输入' />
</FormItem>
</Col>
}
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={24} sm={24}>
<FormItem label='备注' name='remarks'>
<TextArea rows={4} />
</FormItem>
</Col>
</Row>
</Form>
</Modal>
)
}
export default StaffSheetUpdateForm

@ -0,0 +1,299 @@
import { useState, useEffect } from 'react'
import {Col, DatePicker, Form, Input, Modal, Row, Select} from 'antd'
import datadictionary from '@/utils/dataDictionary'
import style from "@/global.less";
import {formatDictOptions, verifyPhone} from "@/utils/globalCommon";
import {NumberInput} from "@/components/NumberInput";
import dayjs from "dayjs";
import SelectOrganTree from "@/components/SelectOrganTree";
import {formatDateObject} from "@/utils/formatUtils";
const { Item: FormItem } = Form
const { TextArea } = Input
const dictData = datadictionary
const StaffSheetViewForm = (props) => {
const [form] = Form.useForm()
const [jobStatus, setJobStatus] = useState('1')
const [userStatus, setUserStatus] = useState('0')
const { viewModalVisible, handleViewModalVisible, values } = props
useEffect(() => {
setJobStatus(values.job_status)
setUserStatus(values.status)
form.setFieldsValue({
user_id: values.user_id,
user_name: values.user_name,
user_name_cn: values.user_name_cn,
user_name_en: values.user_name_en,
password: values.password,
email: values.email,
phone: values.phone,
landline: values.landline,
sex: values.sex,
avatar: values.avatar,
sign: values.sign,
tags: values.tags,
id_card: values.id_card,
birthday: formatDateObject(values.birthday, 'YYYY-MM-DD'),
job_status: values.job_status,
hiredate: formatDateObject(values.hiredate, 'YYYY-MM-DD'),
departure_time: formatDateObject(values.departure_time, 'YYYY-MM-DD'),
user_type: values.user_type,
emp_no: values.emp_no,
access_card_no: values.access_card_no,
country: values.country,
province: values.province,
city: values.city,
address: values.address,
work_addr: values.work_addr,
floor: values.floor,
inprovince: values.inprovince,
// dept_code: values.dept_code,
// dept_name: values.dept_name,
inner_dept_code: values.inner_dept_code,
org_code: values.org_code,
org_name: values.org_name,
inner_org_code: values.inner_org_code,
posts: values.posts ? JSON.parse(values.posts) : [],
wx_openid: values.wx_openid,
wx_mpopenid: values.wx_mpopenid,
wx_miniopenid: values.wx_miniopenid,
wx_unionid: values.wx_unionid,
mobile_imei: values.mobile_imei,
device_num: values.device_num,
al_taobao: values.al_taobao,
al_alipay: values.al_alipay,
al_dingding: values.al_dingding,
is_system_user: values.is_system_user,
mgr_type: values.mgr_type,
pwd_security_level: values.pwd_security_level,
pwd_update_date: values.pwd_update_date,
last_login_ip: values.last_login_ip,
last_login_date: values.last_login_date,
freeze_date: values.freeze_date,
freeze_cause: values.freeze_cause,
zindex: values.zindex,
wx_msg: values.wx_msg,
email_msg: values.email_msg,
system_msg: values.system_msg,
remarks: values.remarks,
status: values.status,
creator: values.creator,
create_date: values.create_date,
updater: values.updater,
update_date: values.update_date
})
}, [])
const afterClose = () =>{
form.resetFields();
}
const handleJobStatusChange = (value) => {
setJobStatus(value)
}
const handleUserStatusChange = (value) => {
setUserStatus(value)
}
return (
<Modal
width={800}
height={550}
bodyStyle={{ height: '500px', overflowY: 'auto' }}
className={style.viewForm}
centered
destroyOnClose
title='查看'
open={viewModalVisible}
onOk={() => handleViewModalVisible()}
onCancel={() => handleViewModalVisible()}
afterClose={() => afterClose()}
>
<Form form={form} layout='vertical' requiredMark={false} style={{pointerEvents: 'none'}}>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='登录账号' name='user_name' rules={[{ required: true, message: '请输入至少2个字符的用户名', min: 2 }]}>
<Input placeholder='请输入' />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='用户名称' name='user_name_cn' rules={[{ required: true, message: '请输入至少2个字符的用户名称', min: 2 }]}>
<Input placeholder='请输入' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='用户性别' name='sex'>
<Select options={formatDictOptions(dictData.sys_user_sex, 'dict_label', 'dict_value')} placeholder='请选择' />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='用户生日' name='birthday'>
<DatePicker format='YYYY-MM-DD' placeholder='请选择' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='手机号码' name='phone' rules={[{ required: false, min: 1, validator: verifyPhone }]}>
<NumberInput placeholder='请输入' style={{width: '100%'}} maxLength={11} />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='电子邮箱' name='email' rules={[{ type: 'email' }]}>
<Input placeholder='请输入' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='机构名称' name='org_name'>
<Input placeholder='请输入' />
</FormItem>
</Col>
{/*<Col md={12} sm={24}>*/}
{/* <FormItem label='部门名称' name='dept_code'>*/}
{/* <SelectDeptTree {...parentDeptTreeMethod} placeholder={'请选择部门'} />*/}
{/* </FormItem>*/}
{/*</Col>*/}
<Col md={12} sm={24}>
<FormItem label='所属岗位' name='posts'>
<Select
mode='multiple'
allowClear
placeholder='请选择'
options={formatDictOptions(dictData.sys_user_post, 'dict_label', 'dict_value')}
/>
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
{/*<Col md={12} sm={24}>
<FormItem
label={
<span>
密码
<em className={styles.optional}>
<Tooltip title='默认密码123456'>
<InfoCircleOutlined style={{ marginLeft: 4 }} />
</Tooltip>
</em>
</span>
}
name='password'
initialValue={'123456'}
rules={[{required: true, message: '请输入至少6个字符的密码', min: 6}]}>
<Input placeholder='请输入' type='password'/>
</FormItem>
</Col>*/}
<Col md={12} sm={24}>
<FormItem label='在职状态' name='job_status'>
<Select
placeholder='请选择'
options={formatDictOptions(dictData.sys_job_status, 'dict_label', 'dict_value')}
onChange={handleJobStatusChange}
/>
</FormItem>
</Col>
{jobStatus === '1' ?
<Col md={12} sm={24}>
<FormItem label='入职时间' name='hiredate' initialValue={dayjs().endOf('day')}>
<DatePicker format='YYYY-MM-DD' placeholder='请选择' />
</FormItem>
</Col>
:
<Col md={12} sm={24}>
<FormItem label='离职时间' name='departure_time'>
<DatePicker format='YYYY-MM-DD' placeholder='请选择' />
</FormItem>
</Col>
}
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='员工类型' name='user_type'>
<Select
placeholder='请选择'
options={formatDictOptions(dictData.sys_user_type, 'dict_label', 'dict_value')}
/>
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='员工工号' name='emp_no'>
<Input placeholder='请输入' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='门禁卡号' name='access_card_no'>
<Input placeholder='请输入' />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='是否是管理员' name='mgr_type'>
<Select
style={{width: '100%'}}
placeholder='请选择'
options={formatDictOptions(dictData.sys_mgr_type, 'dict_label', 'dict_value')}
/>
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='状态' name='status'>
<Select
placeholder='请选择'
options={formatDictOptions(dictData.user_status, 'dict_label', 'dict_value')}
onChange={handleUserStatusChange}
/>
</FormItem>
</Col>
{ userStatus === '3' &&
<Col md={12} sm={24}>
<FormItem label='冻结原因' name='freeze_cause'>
<Input placeholder='请输入' />
</FormItem>
</Col>
}
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={24} sm={24}>
<FormItem label='备注' name='remarks'>
<TextArea rows={4} />
</FormItem>
</Col>
</Row>
</Form>
</Modal>
)
}
export default StaffSheetViewForm

@ -0,0 +1,319 @@
import { deleteByPrimaryKeyForProUser, selectByPrimaryKeyForProUser, insertForProUser, updateForProUser, deleteByMapForProUser,updateByMapForProUser, getOneForProUser,getAllForProUser,queryPageForProUser, countForProUser, insertBatchForProUser, deleteBatchForProUser,updateBatchForProUser, resetPwdForProUser } from '@/services/system/api_prouser';
export default {
namespace: 'dataCollection',
state: {
params: {},
data: {
list: [],
pagination: {},
},
},
effects: {
*delete_by_primarykey_for_prouser({ payload, callback }, { select, call, put }) {
const response = yield call(deleteByPrimaryKeyForProUser, payload)
yield put({
type: 'deleteByPrimaryKeyForProUser',
payload: response
})
if (!response.success) {
callback && callback(response)
return
}
const params = yield select(state => state.prouser.params)
const responseData = yield call(queryPageForProUser, params)
yield put({
type: 'queryPageForProUser',
payload: responseData
})
if (callback) callback(response)
},
*select_by_primarykey_for_prouser({ payload, callback }, { call, put }) {
const response = yield call(selectByPrimaryKeyForProUser, payload)
yield put({
type: 'selectByPrimaryKeyForProUser',
payload: response
})
if (callback) callback(response)
},
*insert_for_prouser({ payload, callback }, { select, call, put }) {
const response = yield call(insertForProUser, payload)
yield put({
type: 'insertForProUser',
payload: response
})
if (!response.success) {
callback && callback(response)
return
}
const params = yield select(state => state.prouser.params)
const responseData = yield call(queryPageForProUser, params)
yield put({
type: 'queryPageForProUser',
payload: responseData
})
if (callback) callback(response)
},
*update_for_prouser({ payload, callback }, { select, call, put }) {
const response = yield call(updateForProUser, payload)
yield put({
type: 'updateForProUser',
payload: response
})
if (!response.success) {
callback && callback(response)
return
}
const params = yield select(state => state.prouser.params)
const responseData = yield call(queryPageForProUser, params)
yield put({
type: 'queryPageForProUser',
payload: responseData
})
if (callback) callback(response)
},
*delete_by_map_for_prouser({ payload, callback }, { select, call, put }) {
const response = yield call(deleteByMapForProUser, payload);
yield put({
type: 'deleteByMapForProUser',
payload: response,
});
const params = yield select(state => state.prouser.params);
const responsedata = yield call(queryPageForProUser, params);
yield put({
type: 'queryPageForProUser',
payload: responsedata,
});
if (callback) callback(response);
},
*update_by_map_for_prouser({ payload, callback }, { select, call, put }) {
const response = yield call(updateByMapForProUser, payload);
yield put({
type: 'updateByMapForProUser',
payload: response,
});
const params = yield select(state => state.prouser.params);
const responsedata = yield call(queryPageForProUser, params);
yield put({
type: 'queryPageForProUser',
payload: responsedata,
});
if (callback) callback(response);
},
*get_one_for_prouser({ payload, callback }, { call, put }) {
const response = yield call(getOneForProUser, payload);
yield put({
type: 'getOneForProUser',
payload: response,
});
if (callback) callback(response);
},
*get_all_for_prouser({ payload, callback }, { call, put }) {
const response = yield call(getAllForProUser, payload);
yield put({
type: 'getAllForProUser',
payload: response,
});
if (callback) callback(response);
},
*query_page_for_prouser({ payload, callback }, { select, call, put }) {
const params = yield select(state => state.prouser.params);
const newParams = payload?.resetFlag ? payload : {...params, ...payload};
yield put({
type: 'setQueryPageByParams',
payload: newParams,
});
const response = yield call(queryPageForProUser, newParams);
yield put({
type: 'queryPageForProUser',
payload: response,
});
if (callback) callback(response);
},
*count_for_prouser({ payload, callback }, { call, put }) {
const response = yield call(countForProUser, payload);
yield put({
type: 'countForProUser',
payload: response,
});
if (callback) callback(response);
},
*insert_batch_for_prouser({ payload, callback }, { select, call, put }) {
const response = yield call(insertBatchForProUser, payload);
yield put({
type: 'insertBatchForProUser',
payload: response,
});
const params = yield select(state => state.prouser.params);
const responsedata = yield call(queryPageForProUser, params);
yield put({
type: 'queryPageForProUser',
payload: responsedata,
});
if (callback) callback(response);
},
*delete_batch_for_prouser({ payload, callback }, { select, call, put }) {
const response = yield call(deleteBatchForProUser, payload);
yield put({
type: 'deleteBatchForProUser',
payload: response,
});
const params = yield select(state => state.prouser.params);
const responsedata = yield call(queryPageForProUser, params);
yield put({
type: 'queryPageForProUser',
payload: responsedata,
});
if (callback) callback(response);
},
*update_batch_for_prouser({ payload, callback }, { select, call, put }) {
const response = yield call(updateBatchForProUser, payload);
yield put({
type: 'updateBatchForProUser',
payload: response,
});
const params = yield select(state => state.prouser.params);
const responsedata = yield call(queryPageForProUser, params);
yield put({
type: 'queryPageForProUser',
payload: responsedata,
});
if (callback) callback(response);
},
*resetpwd_for_prouser({ payload, callback }, { select, call, put }) {
const response = yield call(resetPwdForProUser, payload);
yield put({
type: 'resetPwdForProUser',
payload: response,
});
const params = yield select(state => state.prouser.params);
const responsedata = yield call(queryPageForProUser, params);
yield put({
type: 'queryPageForProUser',
payload: responsedata,
});
if (callback) callback(response);
},
},
reducers: {
setQueryPageByParams(state, { payload }) {
return {
...state,
params: {...payload},
};
},
deleteByPrimaryKeyForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
selectByPrimaryKeyForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
insertForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
updateForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
deleteByMapForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
updateByMapForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
getOneForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
getAllForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
queryPageForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
countForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
insertBatchForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
deleteBatchForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
updateBatchForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
resetPwdForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
},
};

@ -1,54 +1,10 @@
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'; //应急演练记录关联管理
import React from 'react';
const FireKeynoteArea = () => {
const [activeModule, setActiveModule] = useState('1');
const handleModuleClick = (module) => {
setActiveModule(module)
}
const renderModule = () => {
switch (activeModule) {
case '1':
return <KeypartsBasicInformation />;
case '2':
return <EmergencyPlanAssociation />;
case '3':
return <EmergencyDrillRecordAssociation />;
default:
return <KeypartsBasicInformation />;
}
};
return (
<div className={styles.container}>
<div className={styles.TopButton}>
<Button
className={`${styles.TopButtonItem} ${activeModule === "1" ? styles.active : ""}`}
onClick={() => handleModuleClick("1")}
>重点部位基础信息管理
</Button>
<Button
className={`${styles.TopButtonItem} ${activeModule === "2" ? styles.active : ""}`}
onClick={() => handleModuleClick("2")}
>应急预案关联管理
</Button>
<Button
className={`${styles.TopButtonItem} ${activeModule === "3" ? styles.active : ""}`}
onClick={() => handleModuleClick("3")}
>应急演练记录关联管理
</Button>
</div>
<div className={styles.content}>
{renderModule()}
</div>
<div style={{ padding: 24 }}>
<h3 style={{ margin: 0 }}>重点防火区域</h3>
<div style={{ marginTop: 12, color: 'rgba(0,0,0,0.65)' }}>页面未实现占位页</div>
</div>
);
};

@ -1,48 +1,12 @@
import React, { useState } from 'react';
import { Button } from 'antd';
import styles from './FireWarning.less';
import RealtimeMonitoring from './components/RealtimeMonitoring';
import DataAnalysisWarning from './components/DataAnalysisWarning';
const Firewarning = () => {
const [activeModule, setActiveModule] = useState('realtime');
const handleModuleClick = (module) => {
setActiveModule(module);
};
const renderModule = () => {
switch (activeModule) {
case 'realtime':
return <RealtimeMonitoring />;
case 'analysis':
return <DataAnalysisWarning />;
default:
return <RealtimeMonitoring />;
}
};
import React from 'react';
const FireWarning = () => {
return (
<div className={styles.firewarningContainer}>
<div className={styles.firewarningTopButton}>
<Button
className={`${styles.firewarningTopButtonItem} ${activeModule === "realtime" ? styles.active : ""}`}
onClick={() => handleModuleClick("realtime")}
>
实时状态监测
</Button>
<Button
className={`${styles.firewarningTopButtonItem} ${activeModule === "analysis" ? styles.active : ""}`}
onClick={() => handleModuleClick("analysis")}
>
数据分析与预警
</Button>
</div>
<div className={styles.firewarningContent}>
{renderModule()}
</div>
<div style={{ padding: 24 }}>
<h3 style={{ margin: 0 }}>消防预警</h3>
<div style={{ marginTop: 12, color: 'rgba(0,0,0,0.65)' }}>页面未实现占位页</div>
</div>
);
};
export default Firewarning;
export default FireWarning;

@ -0,0 +1,69 @@
import React, { useState } from 'react';
import { Card, Row, Col, Statistic, Progress, Button, Space } from 'antd';
import styles from './basic.less';
import ResponsibilityImplementation from './components/ResponsibilityImplementation';
import OnlineMonitoring from './components/OnlineMonitoring';
import RiskAssessment from './components/RiskAssessment';
import EvaluationReport from './components/EvaluationReport';
import LicenseManagement from './components/LicenseManagement';
const SafeMajorHazardList = () => {
const [activeModule, setActiveModule] = useState('organization');
const handleModuleClick = (module) => {
setActiveModule(module)
}
const renderModule = () => {
switch (activeModule) {
case 'organization':
return <ResponsibilityImplementation />;
case 'license':
return <LicenseManagement />;
case 'equipment':
return <OnlineMonitoring />;
case 'firefighting':
return <RiskAssessment />;
case 'other':
return <EvaluationReport />;
default:
return <ResponsibilityImplementation />;
}
};
return (
<div className={styles.container}>
<div className={styles.TopButton}>
<Button
className={`${styles.TopButtonItem} ${activeModule === "organization" ? styles.active : ""}`}
onClick={() => handleModuleClick("organization")}
>组织机构管理
</Button>
<Button
className={`${styles.TopButtonItem} ${activeModule === "license" ? styles.active : ""}`}
onClick={() => handleModuleClick("license")}
>资质证照管理
</Button>
<Button
className={`${styles.TopButtonItem} ${activeModule === "firefighting" ? styles.active : ""}`}
onClick={() => handleModuleClick("firefighting")}
>设备设施管理
</Button>
<Button
className={`${styles.TopButtonItem} ${activeModule === "other" ? styles.active : ""}`}
onClick={() => handleModuleClick("other")}
>建筑消防与器材管理
</Button>
</div>
<div className={styles.content}>
{renderModule()}
</div>
</div>
);
};
export default SafeMajorHazardList;

@ -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; // ======== 无内边距 ========
}
}

@ -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}<br/>使用次数: ${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: '#fff',
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}<br/>{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 (
<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,
color: '#FF2526',
border: '1px solid #FFE0E2',
backgroundColor: 'transparent',
borderRadius: '4px'
}}>
删除
</Button>
</div>
),
},
];
// 模拟数据
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 (
<div className={styles.Econtainer}>
{/* 第1个div - 高度39% */}
<div className={styles.EcontainerMiddle}>
<div className={styles.sectionContent}>
<div className={styles.middleBlock1}>
<div className={styles.block1Header}>
<div className={styles.block1Title}>
<div className={styles.titleIcon}></div>
设备使用频率分析
</div>
</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>
</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>
<div className={styles.middleBlock2Chart} ref={chartRef}>
</div>
</div>
</div>
</div>
{/* 第2个div - 占满剩余位置 */}
<div className={styles.EcontainerBottom}>
<div className={styles.sectionContent}>
<div className={styles.leftBlock}>
<div className={styles.maintenanceStack}>
<div className={styles.maintenanceSection}>
<div className={styles.maintenanceTitle}>
<div className={styles.titleIcon}></div>
<div>近期维护提醒</div>
</div>
<div className={styles.maintenanceContent1}>
<div className={styles.maintenanceItem}>
<div className={styles.maintenanceLeft}>
<div className={styles.maintenanceText1}>SH-MHQ-023-C 干粉灭火器</div>
<div className={styles.maintenanceText2}>位置: 4楼办公区丨维护类型: 季度检查</div>
<div className={styles.maintenanceText3}>负责人: 张三</div>
</div>
<div className={styles.maintenanceRight}>
<div className={styles.maintenanceStatus}>3天后到期</div>
</div>
</div>
<div className={styles.maintenanceItem}>
<div className={styles.maintenanceLeft}>
<div className={styles.maintenanceText1}>SH-XHS-045-D 室内消火栓</div>
<div className={styles.maintenanceText2}>位置: 2楼东侧走廊丨维护类型: 水压测试</div>
<div className={styles.maintenanceText3}>负责人: 李四</div>
</div>
<div className={styles.maintenanceRight2}>
<div className={styles.maintenanceStatus}>8天后到期</div>
</div>
</div>
</div>
</div>
<div className={styles.maintenanceSection}>
<div className={styles.maintenanceTitle}>
<div className={styles.titleIcon}></div>
<div>维护任务进度</div>
</div>
<div className={styles.maintenanceContent2}>
{/* 进度条区域 */}
<div className={styles.progressSection}>
<div className={styles.progressLabel}>月度维护计划</div>
<Progress percent={75} status="active" />
<div className={styles.progressLabel}>季度维护计划</div>
<Progress percent={60} status="active" />
<div className={styles.progressLabel}>年度维护计划</div>
<Progress percent={85} status="active" />
{/* 警告提示框 */}
<div className={styles.warningBox}>
<ExclamationCircleOutlined className={styles.warningIcon} />
<span className={styles.warningText}>本月有5项维护任务即将到期</span>
</div>
</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>
{/* 操作按钮 */}
<div className={styles.tableActions}>
<div className={styles.leftActions}>
<Input
placeholder="搜索设备名称、编号..."
onChange={handleSearchChange}
value={searchText}
style={{ width: 250, fontSize: 12 }}
allowClear
suffix={<SearchOutlined />}
/>
</div>
<div className={styles.rightActions}>
<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}`,
}}
/>
</div>
</div>
</div>
</div>
</div>
);
};
export default EvaluationReport;

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

@ -0,0 +1,604 @@
import React, { useEffect, useRef, useState } from 'react';
import { Card, Table, Tag, Space, Typography, Progress, Row, Col, Button, Input, Select } from 'antd';
import * as echarts from 'echarts';
import StandardTable from '@/components/StandardTable';
import styles from './LicenseManagement.less';
import icon_echart from '@/assets/business_basic/icon_echart.svg';
const { Title } = Typography;
const { Search } = Input;
const { Option } = Select;
const LicenseManagement = () => {
const chartRef = useRef(null);
const [searchValue, setSearchValue] = useState('');
const [selectedType, setSelectedType] = useState('all');
// 图表数据
const chartData = [
{ name: '安全生产许可证', value: 35, itemStyle: { color: '#3C7DFF' } },
{ name: '安全评估报告', value: 25, itemStyle: { color: '#FF8800' } },
{ name: '安全三同时材料', value: 20, itemStyle: { color: '#FF3E48' } },
{ name: '施工资质证书', value: 15, itemStyle: { color: '#FFC403' } },
{ name: '应急预案', value: 10, itemStyle: { color: '#22C55E' } },
{ name: '其他', value: 5, itemStyle: { color: '#31BCFF' } }
];
// 初始化图表
useEffect(() => {
if (chartRef.current) {
const chart = echarts.init(chartRef.current);
const option = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'horizontal',
bottom: 0,
left: 'center',
itemWidth: 14,
itemHeight: 4,
itemGap: 10,
textStyle: {
fontSize: 12,
color: '#333',
width: 100
},
formatter: function (name) {
return name;
},
data: (() => {
// 找到最长的名称长度
const maxLength = Math.max(...chartData.map(item => item.name.length));
// 将所有名称填充到相同长度
return chartData.map(item => {
const paddingLength = maxLength - item.name.length;
return item.name + ' '.repeat(paddingLength);
});
})()
},
series: [
{
name: '证件类型分布',
type: 'pie',
radius: ['20%', '65%'],
center: ['50%', '38%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 5,
// color:"red",/
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '16',
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: (() => {
// 找到最长的名称长度
const maxLength = Math.max(...chartData.map(item => item.name.length));
// 将所有名称填充到相同长度
return chartData.map(item => ({
...item,
name: item.name + ' '.repeat(maxLength - item.name.length)
}));
})()
}
]
};
chart.setOption(option);
// 响应式处理
const handleResize = () => {
chart.resize();
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
chart.dispose();
};
}
}, []);
// 表格数据
const tableData = [
{
key: '1',
no: '01',
name: '安全生产许可证',
type: '资质证书',
id: 'HQ-XF-01-001',
authority: '应急管理部',
validUntil: '2025-09-10',
status: '已过期',
statusType: 'error'
},
{
key: '2',
no: '02',
name: '安全预评估报告',
type: '安全三同时',
id: 'HQ-XF-02-015',
authority: '第三方评估机构',
validUntil: '2025-09-10',
status: '有效',
statusType: 'warning'
},
{
key: '3',
no: '03',
name: '施工资质证书',
type: '资质证书',
id: 'HQ-XF-03-007',
authority: '3设计院',
validUntil: '2025-09-10',
status: '有效',
statusType: 'success'
},
{
key: '4',
no: '04',
name: '安全标准化证书',
type: '资质证书',
id: 'HQ-XF-03-007',
authority: '第三方评估机构',
validUntil: '2025-09-10',
status: '有效',
statusType: 'success'
},
{
key: '5',
no: '05',
name: '消防验收合格证',
type: '消防证书',
id: 'HQ-XF-05-012',
authority: '消防局',
validUntil: '2026-03-15',
status: '有效',
statusType: 'success'
},
{
key: '6',
no: '06',
name: '职业健康安全管理体系认证',
type: '管理体系认证',
id: 'HQ-XF-06-008',
authority: '认证机构',
validUntil: '2026-06-20',
status: '有效',
statusType: 'success'
},
{
key: '7',
no: '07',
name: '环境管理体系认证',
type: '管理体系认证',
id: 'HQ-XF-07-009',
authority: '认证机构',
validUntil: '2026-08-25',
status: '有效',
statusType: 'success'
},
{
key: '8',
no: '08',
name: '特种设备使用登记证',
type: '特种设备证书',
id: 'HQ-XF-08-011',
authority: '质量技术监督局',
validUntil: '2026-12-10',
status: '有效',
statusType: 'success'
},
{
key: '9',
no: '09',
name: '危险化学品经营许可证',
type: '经营许可证',
id: 'HQ-XF-09-013',
authority: '应急管理局',
validUntil: '2027-01-30',
status: '有效',
statusType: 'success'
},
{
key: '10',
no: '10',
name: '辐射安全许可证',
type: '辐射安全证书',
id: 'HQ-XF-10-014',
authority: '生态环境部',
validUntil: '2027-04-18',
status: '有效',
statusType: 'success'
}
];
// 表格列定义
const columns = [
{
title: '编号',
dataIndex: 'no',
key: 'no',
width: 80,
},
{
title: '证照名称',
dataIndex: 'name',
key: 'name',
width: 150,
},
{
title: '类型',
dataIndex: 'type',
key: 'type',
width: 120,
},
{
title: '编号',
dataIndex: 'id',
key: 'id',
width: 150,
},
{
title: '发证机关',
dataIndex: 'authority',
key: 'authority',
width: 150,
},
{
title: '有效期至',
dataIndex: 'validUntil',
key: 'validUntil',
width: 120,
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 120,
render: (text, record) => {
const getStatusStyle = (status) => {
if (status === '有效') {
return {
color: '#44BB5F',
backgroundColor: '#D8F7DE',
padding: '4px 8px',
borderRadius: '4px',
fontSize: '12px',
display: 'inline-block'
};
} else if (status === '即将到期') {
return {
color: '#FF8800',
backgroundColor: '#FFF3E9',
padding: '4px 8px',
borderRadius: '4px',
fontSize: '12px',
display: 'inline-block'
};
} else if (status === '已过期') {
return {
color: '#FF3E48',
backgroundColor: '#FFE0E2',
padding: '4px 8px',
borderRadius: '4px',
fontSize: '12px',
display: 'inline-block'
};
}
return {};
};
return (
<span style={getStatusStyle(text)}>
{text}
</span>
);
}
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
width: 120,
render: (text, record) => {
const handleEdit = (record) => {
console.log('编辑记录:', record);
};
const handleDelete = (record) => {
console.log('删除记录:', record);
};
return (
<div style={{
display: 'flex',
gap: '8px',
justifyContent: 'center',
alignItems: 'center'
}}>
<Button
onClick={() => handleEdit(record)}
style={{
color: '#2E4CD4',
backgroundColor: 'transparent',
// borderColor: '#E6E9FB',
fontSize: '12px',
height: '28px',
padding: '0 12px'
}}
>
更新
</Button>
<Button
onClick={() => handleDelete(record)}
style={{
color: '#2E4CD4',
backgroundColor: 'transparent',
// borderColor: '#E6E9FB',
fontSize: '12px',
height: '28px',
padding: '0 12px'
}}
>
查看
</Button>
</div>
);
}
},
];
return (
<div className={styles.licenseManagementContainer}>
<div className={styles.topSectionContainer}>
<div className={styles.firstBlock}>
<div className={styles.chartHeader}>
<div className={styles.colorBlock}></div>
<span className={styles.chartTitle}>证件类型分布</span>
</div>
<div className={styles.chartContainer}>
<div ref={chartRef} className={styles.chart}></div>
</div>
</div>
<div className={styles.secondBlock}>
<div className={styles.chartHeader}>
<div className={styles.colorBlock}></div>
<span className={styles.chartTitle}>证件状态概览</span>
</div>
<div className={styles.chartContainer}>
{/* 上半部分:进度条和百分比 */}
<div className={styles.progressSection}>
<div className={styles.progressItem}>
<div className={styles.progressLabel}>有效证照</div>
<div className={styles.progressWrapper}>
<Progress
percent={50}
strokeColor="#3C7DFF"
trailColor="#F0F0F0"
showInfo={false}
className={styles.customProgress}
/>
<span className={styles.progressPercent}>50%</span>
</div>
</div>
<div className={styles.progressItem}>
<div className={styles.progressLabel}>即将到期</div>
<div className={styles.progressWrapper}>
<Progress
percent={15}
strokeColor="#FFC403"
trailColor="#F0F0F0"
showInfo={false}
className={styles.customProgress}
/>
<span className={styles.progressPercent}>15%</span>
</div>
</div>
<div className={styles.progressItem}>
<div className={styles.progressLabel}>已过期</div>
<div className={styles.progressWrapper}>
<Progress
percent={20}
strokeColor="#FF3E48"
trailColor="#F0F0F0"
showInfo={false}
className={styles.customProgress}
/>
<span className={styles.progressPercent}>20%</span>
</div>
</div>
<div className={styles.progressItem}>
<div className={styles.progressLabel}>待审核材料</div>
<div className={styles.progressWrapper}>
<Progress
percent={15}
strokeColor="#FF8800"
trailColor="#F0F0F0"
showInfo={false}
className={styles.customProgress}
/>
<span className={styles.progressPercent}>15%</span>
</div>
</div>
</div>
{/* 下半部分:数字统计 */}
<div className={styles.statsSection}>
<Row gutter={[16, 16]}>
<Col span={6}>
<div className={styles.statItem}>
<div className={styles.statNumber} style={{ color: '#3C7DFF' }}>42</div>
<div className={styles.statLabel}>总证照数</div>
</div>
</Col>
<Col span={6}>
<div className={styles.statItem}>
<div className={styles.statNumber} style={{ color: '#FFC403' }}>8</div>
<div className={styles.statLabel}>即将过期</div>
</div>
</Col>
<Col span={6}>
<div className={styles.statItem}>
<div className={styles.statNumber} style={{ color: '#FF3E48' }}>6</div>
<div className={styles.statLabel}>已过期</div>
</div>
</Col>
<Col span={6}>
<div className={styles.statItem}>
<div className={styles.statNumber} style={{ color: '#FF8800' }}>6</div>
<div className={styles.statLabel}>待审核材料</div>
</div>
</Col>
</Row>
</div>
</div>
</div>
<div className={styles.thirdBlock}>
<div className={styles.chartHeader}>
<div className={styles.colorBlock}></div>
<span className={styles.chartTitle}>临期预警</span>
</div>
<div className={styles.chartContainer}>
{/* 透明块容器 */}
<div className={styles.transparentBlock}>
{/* 四个垂直分布的卡片 */}
<div className={styles.licenseCard}>
<div className={styles.cardContent}>
<div className={styles.licenseName}>安全生产许可证</div>
<div className={styles.licenseNumber}>编号: AQXK-2023-0582</div>
</div>
<div className={styles.expiryTag}>
<span className={styles.expiryText}>15天后到期</span>
</div>
</div>
<div className={styles.licenseCard}>
<div className={styles.cardContent}>
<div className={styles.licenseName}>安全评估报告</div>
<div className={styles.licenseNumber}>编号: AQPG-2023-0125</div>
</div>
<div className={styles.expiryTag}>
<span className={styles.expiryText}>30天后到期</span>
</div>
</div>
<div className={styles.licenseCard}>
<div className={styles.cardContent}>
<div className={styles.licenseName}>施工资质证书</div>
<div className={styles.licenseNumber}>编号: SGZZ-2023-0089</div>
</div>
<div className={styles.expiryTag} style={{ backgroundColor: '#FFE0E2' }}>
<span className={styles.expiryText} style={{ color: '#FF2526' }}>7天后到期</span>
</div>
</div>
<div className={styles.licenseCard}>
<div className={styles.cardContent}>
<div className={styles.licenseName}>应急预案</div>
<div className={styles.licenseNumber}>编号: YJYA-2023-0045</div>
</div>
<div className={styles.expiryTag} style={{ backgroundColor: '#FFE0E2' }}>
<span className={styles.expiryText} style={{ color: '#FF2526' }}>4天后到期</span>
</div>
</div>
</div>
</div>
</div>
</div>
{/* 证照列表区域 */}
<div className={styles.listCard}>
<div className={styles.chartHeader}>
<div className={styles.headerLeft}>
<div className={styles.colorBlock}></div>
<span className={styles.chartTitle}>证照列表</span>
</div>
<div className={styles.headerRight}>
<Search
placeholder="搜索证照名称或编号..."
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
onSearch={(value) => console.log('搜索:', value)}
className={styles.searchInput}
/>
<Select
value={selectedType}
onChange={setSelectedType}
className={styles.typeSelector}
>
<Option value="all">全部类型</Option>
<Option value="safety">安全生产许可证</Option>
<Option value="assessment">安全评估报告</Option>
<Option value="construction">施工资质证书</Option>
<Option value="emergency">应急预案</Option>
<Option value="other">其他</Option>
</Select>
<Button
type="primary"
className={styles.addButton}
onClick={() => console.log('新增证照')}
>
新增证照
</Button>
</div>
</div>
<StandardTable
columns={columns}
data={{
list: tableData, // ======== 表格数据列表 ========
pagination: { // ======== 分页配置 ========
currentPage: 1, // ======== 当前页码 ========
pageSize: 5, // ======== 每页显示5条数据 ========
total: tableData.length, // ======== 总数据条数 ========
} // ======== 分页配置结束 ========
}} // ======== 数据对象结束 ========
selectedRows={[]} // ======== 选中的行数据,初始为空数组 ========
onSelectRow={() => { }} // ======== 行选择事件处理函数 ========
onChange={() => { }} // ======== 表格变化事件处理函数 ========
pagination={{
currentPage: 1,
pageSize: 5,
total: tableData.length,
showSizeChanger: false,
showQuickJumper: true,
showTotal: (total, range) =>
`${total}`,
locale: {
jump_to: '前往',
page: '页',
items_per_page: '条/页',
}
}}
/>
</div>
</div>
);
};
export default LicenseManagement;

@ -0,0 +1,498 @@
.licenseManagementContainer {
height: 90vh;
.topSectionContainer {
padding: 0;
margin: 15px 0px 15px 5px;
height: 40%;
display: flex;
gap: 15px;
align-items: stretch;
.firstBlock {
width: 30%;
background-color: #fff;
display: flex;
flex-direction: column;
padding: 10px 16px;
border-radius: 2px;
.chartHeader {
display: flex;
align-items: center;
margin-bottom: 16px;
.colorBlock {
width: 2px;
height: 18px;
background-color: #2E4CD4;
margin-right: 8px;
border-radius: 1px;
}
.chartTitle {
font-size: 14px;
font-weight: 500;
color: #333333;
line-height: 18px;
}
}
.chartContainer {
flex: 1;
width: 100%;
position: relative;
.chart {
width: 100%;
height: 100%;
min-height: 200px;
}
// 进度条区域样式
.progressSection {
margin-bottom: 20px;
.progressItem {
margin-bottom: 16px;
.progressLabel {
font-size: 12px;
color: #666;
margin-bottom: 8px;
font-weight: 400;
}
.progressWrapper {
display: flex;
align-items: center;
gap: 12px;
.customProgress {
flex: 1;
:global(.ant-progress-bg) {
height: 8px !important;
border-radius: 4px;
}
:global(.ant-progress-outer) {
.ant-progress-inner {
background-color: #F0F0F0;
border-radius: 4px;
}
}
}
.progressPercent {
font-size: 12px;
color: #333;
font-weight: 500;
min-width: 30px;
text-align: right;
}
}
}
}
// 数字统计区域样式
.statsSection {
.statItem {
text-align: center;
padding: 8px;
.statNumber {
font-size: 24px;
font-weight: 600;
line-height: 1.2;
margin-bottom: 4px;
}
.statLabel {
font-size: 12px;
color: #666;
font-weight: 400;
}
}
}
}
}
.secondBlock {
width: 30%;
background-color: #fff;
display: flex;
flex-direction: column;
padding: 10px 16px;
border-radius: 2px;
.chartHeader {
display: flex;
align-items: center;
margin-bottom: 8px;
.colorBlock {
width: 2px;
height: 18px;
background-color: #2E4CD4;
margin-right: 8px;
border-radius: 1px;
}
.chartTitle {
font-size: 14px;
font-weight: 500;
color: #333333;
// line-height: 18px;
}
}
.chartContainer {
flex: 1;
width: 100%;
position: relative;
// 进度条区域样式
.progressSection {
// margin-bottom: 20px;
.progressItem {
// margin-bottom: 16px;
.progressLabel {
font-size: 10px;
color: #666;
// margin-bottom: 8px;
font-weight: 400;
}
.progressWrapper {
display: flex;
align-items: center;
gap: 5px;
.customProgress {
flex: 1;
:global(.ant-progress-bg) {
height: 8px !important;
border-radius: 4px;
}
:global(.ant-progress-outer) {
.ant-progress-inner {
background-color: #F0F0F0;
border-radius: 4px;
}
}
}
.progressPercent {
font-size: 12px;
color: #333;
font-weight: 500;
min-width: 30px;
text-align: right;
}
}
}
}
// 数字统计区域样式
.statsSection {
.statItem {
text-align: center;
padding: 0px 2px 2px 2px;
.statNumber {
font-size: 22px;
font-weight: 600;
line-height: 1.2;
margin-bottom: 4px;
}
.statLabel {
font-size: 12px;
color: #666;
font-weight: 400;
}
}
}
}
}
.thirdBlock {
flex: 1;
background-image: url('@/assets/business_basic/background_lqyj.svg');
background-color: #fff;
background-repeat: no-repeat;
background-size: cover;
background-position: center;
display: flex;
flex-direction: column;
padding: 10px 16px;
border-radius: 2px;
.chartHeader {
display: flex;
align-items: center;
margin-bottom: 8px;
.colorBlock {
width: 2px;
height: 18px;
background-color: #2E4CD4;
margin-right: 8px;
border-radius: 1px;
}
.chartTitle {
font-size: 14px;
font-weight: 500;
color: #333333;
}
}
.chartContainer {
flex: 1;
width: 100%;
position: relative;
// 透明块容器样式
.transparentBlock {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
gap: 8px;
padding: 4px 8px;
.licenseCard {
width: 60%;
height: auto;
background-color: #FFF9F4;
border: 1px solid #FFD7BB;
border-radius: 2px;
padding: 5px 8px;
display: flex;
justify-content: space-between;
align-items: center;
// box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
.cardContent {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
.licenseName {
font-size: 12px;
font-weight: 500;
color: #333;
line-height: 1.2;
}
.licenseNumber {
font-size: 12px;
color: #666;
font-weight: 400;
}
}
.expiryTag {
width: 38%;
background-color: #FFEDDE;
border-radius: 2px;
padding: 5px 12px;
margin-left: 12px;
.expiryText {
font-size: 12px;
font-weight: 500;
display: flex;
align-items: center;
color: #D46B08;
}
}
}
}
}
}
}
.listCard {
padding: 0;
padding: 15px 5px 15px 20px;
flex: 1;
// display: flex;
gap: 15px;
background-color: #fff;
// align-items: stretch;
.chartHeader {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
.headerLeft {
display: flex;
align-items: center;
.colorBlock {
width: 2px;
height: 18px;
background-color: #2E4CD4;
margin-right: 8px;
border-radius: 1px;
}
.chartTitle {
font-size: 14px;
font-weight: 500;
color: #333333;
line-height: 18px;
}
}
.headerRight {
display: flex;
align-items: center;
gap: 12px;
.searchInput {
width: 280px;
.ant-input {
border-radius: 2px;
border: 1px solid #d9d9d9;
&:hover {
border-color: #40a9ff;
}
&:focus {
border-color: #40a9ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
}
}
.typeSelector {
width: 120px;
.ant-select-selector {
border-radius: 2px;
border: 1px solid #d9d9d9;
&:hover {
border-color: #40a9ff;
}
}
&.ant-select-focused .ant-select-selector {
border-color: #40a9ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
}
.addButton {
border-radius: 4px;
background-color: #2E4CD4;
// border-color: #1890ff;
height: 32px;
padding: 4px 15px;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
&:hover {
background-color: #2E4CD4;
// border-color: #40a9ff;
}
&:focus {
background-color: #2E4CD4;
// border-color: #40a9ff;
}
}
}
}
// StandardTable 组件样式
:global(.ant-table) {
font-size: 12px;
}
:global(.ant-pagination-options-quick-jumper input) {
text-align: center !important;
}
: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-pagination) {
margin-top: 16px;
text-align: right;
}
}
}
// 覆盖Ant Design默认样式
.licenseManagementContainer {
.ant-card {
box-shadow: none;
}
.ant-card-body {
padding: 20px;
}
.ant-table {
font-size: 14px;
}
.ant-tag {
border-radius: 4px;
font-size: 12px;
padding: 2px 8px;
}
.ant-btn-link {
padding: 0;
height: auto;
font-size: 14px;
}
.ant-input-search {
.ant-input {
border-radius: 6px;
}
}
.ant-select {
.ant-select-selector {
border-radius: 6px;
}
}
.ant-btn-primary {
border-radius: 6px;
}
}

@ -0,0 +1,716 @@
import React, { useEffect, useRef, useState } from 'react';
import { Card, Result, Select, Button } from 'antd';
import * as echarts from 'echarts';
import StandardTable from '@/components/StandardTable';
import styles from './OnlineMonitoring.less';
import alarm0 from '@/assets/safe_majorHazard/online_monitoring/alarm0.png';
import alarm1 from '@/assets/safe_majorHazard/online_monitoring/alarm1.png';
import alarm2 from '@/assets/safe_majorHazard/online_monitoring/alarm2.png';
import alarm3 from '@/assets/safe_majorHazard/online_monitoring/alarm3.png';
import exportIcon from '@/assets/safe_majorHazard/online_monitoring/export.png';
import deleteIcon from '@/assets/safe_majorHazard/online_monitoring/delete.png';
const OnlineMonitoring = () => {
const chartRef = 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 (chartRef.current) {
const chart = echarts.init(chartRef.current);
const option = {
color: ['#04A7F3', '#E7C42C', '#EC6941'],
legend: {
data: ['液位', '温度', '压力'],
top: "-3px",
left: "center",
itemGap: 40, // 图例间距
textStyle: {
fontSize: 10
}
},
grid: {
left: '2%',
right: '4%',
bottom: '2%',
top: '12%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['0:00', '2:00', '4:00', '6:00', '8:00', '10:00', '12:00', '14:00', '16:00', '18:00', '20:00', '22:00', '24:00'],
axisLabel: {
fontSize: 10
}
},
yAxis: {
type: 'value',
min: 0,
max: 500,
axisLabel: {
formatter: '{value}',
fontSize: 10
}
},
series: [
{
name: '液位',
type: 'line',
smooth: true,
lineStyle: {
width: 1.5,
color: '#04A7F3'
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(4, 167, 243, 0.3)' },
{ offset: 1, color: 'rgba(4, 167, 243, 0)' }
]
}
},
symbol: 'none', // 不显示数据点
data: [120, 200, 150, 300, 250, 400, 350, 280, 320, 180, 220, 160, 140]
},
{
name: '温度',
type: 'line',
smooth: true,
lineStyle: {
width: 1.5,
color: '#E7C42C'
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(231, 196, 44, 0.3)' },
{ offset: 1, color: 'rgba(231, 196, 44, 0)' }
]
}
},
symbol: 'none',
data: [80, 120, 100, 180, 160, 220, 200, 150, 170, 90, 110, 85, 75]
},
{
name: '压力',
type: 'line',
smooth: true,
lineStyle: {
width: 1.5,
color: '#EC6941'
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 1,
x2: 0,
y2: 0,
colorStops: [
{ offset: 0, color: 'rgba(236, 105, 65, 0)' },
{ offset: 1, color: 'rgba(236, 105, 65, 0.3)' }
]
}
},
symbol: 'none',
data: [200, 300, 250, 450, 400, 430, 480, 420, 480, 280, 320, 260, 240]
}
]
};
chart.setOption(option);
// 响应式调整 - 使用ResizeObserver监听容器尺寸变化
let resizeTimer = null;
const handleResize = () => {
// 防抖处理避免频繁调用resize
if (resizeTimer) {
clearTimeout(resizeTimer);
}
resizeTimer = setTimeout(() => {
chart.resize();
}, 100);
};
// 监听窗口大小变化
window.addEventListener('resize', handleResize);
// 监听容器尺寸变化(解决菜单栏伸缩时的自适应问题)
let resizeObserver = null;
if (window.ResizeObserver) {
resizeObserver = new ResizeObserver(() => {
// 使用setTimeout确保DOM更新完成后再调整图表
setTimeout(() => {
handleResize();
}, 0);
});
resizeObserver.observe(chartRef.current);
}
return () => {
window.removeEventListener('resize', handleResize);
if (resizeObserver) {
resizeObserver.disconnect();
}
if (resizeTimer) {
clearTimeout(resizeTimer);
}
chart.dispose();
};
}
}, []);
// 表格列定义
const columns = [
{
title: '编号',
dataIndex: 'id',
key: 'id',
width: 80,
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: 'alarmTime',
key: 'alarmTime',
width: 150,
},
{
title: '报警传感器名称',
dataIndex: 'sensorName',
key: 'sensorName',
width: 150,
},
{
title: '报警类型',
dataIndex: 'alarmType',
key: 'alarmType',
width: 120,
},
{
title: '报警内容',
dataIndex: 'alarmContent',
key: 'alarmContent',
width: 200,
},
{
title: '优先级',
dataIndex: 'priority',
key: 'priority',
width: 80,
render: (text) => {
const colorMap = {
'高': '#FF4D4F',
'中': '#FAAD14',
'低': '#52C41A'
};
return <span style={{ color: colorMap[text] || '#333' }}>{text}</span>;
}
},
{
title: '处理状态',
dataIndex: 'status',
key: 'status',
width: 100,
render: (text) => {
const statusMap = {
'未处理': { color: '#FF4D4F', bg: '#FFF2F0' },
'处理中': { color: '#FAAD14', bg: '#FFFBE6' },
'已处理': { color: '#52C41A', bg: '#F6FFED' }
};
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: 'processTime',
key: 'processTime',
width: 150,
},
{
title: '处理人',
dataIndex: 'processor',
key: 'processor',
width: 100,
},
{
title: '操作',
key: 'action',
width: 120,
render: (_, record) => (
<div>
<Button type="link" size="small" style={{ padding: 0, marginRight: 8 }}>
查看
</Button>
</div>
),
},
];
// 模拟数据
const mockData = [
{
key: '1',
id: '001',
alarmTime: '2024-01-15 08:30:25',
sensorName: 'LNG储罐',
alarmType: '温度超限',
alarmContent: '储罐温度超过安全阈值',
priority: '高',
status: '未处理',
processTime: '-',
processor: '-',
},
{
key: '2',
id: '002',
alarmTime: '2024-01-15 09:15:10',
sensorName: 'LNG储罐',
alarmType: '压力异常',
alarmContent: '管道压力异常波动',
priority: '中',
status: '处理中',
processTime: '2024-01-15 09:20:00',
processor: '张三',
},
{
key: '3',
id: '003',
alarmTime: '2024-01-15 10:45:30',
sensorName: 'LNG储罐',
alarmType: '液位异常',
alarmContent: '储罐液位低于警戒线',
priority: '高',
status: '已处理',
processTime: '2024-01-15 11:00:15',
processor: '李四',
},
{
key: '4',
id: '004',
alarmTime: '2024-01-15 11:20:45',
sensorName: 'LNG储罐',
alarmType: '气体泄漏',
alarmContent: '检测到可燃气体泄漏',
priority: '高',
status: '未处理',
processTime: '-',
processor: '-',
},
{
key: '5',
id: '005',
alarmTime: '2024-01-15 12:10:20',
sensorName: 'LNG储罐',
alarmType: '设备振动',
alarmContent: '设备异常振动',
priority: '低',
status: '已处理',
processTime: '2024-01-15 12:30:00',
processor: '王五',
},
{
key: '6',
id: '006',
alarmTime: '2024-01-15 13:25:15',
sensorName: 'LNG管道',
alarmType: '流量异常',
alarmContent: '管道流量异常波动',
priority: '中',
status: '未处理',
processTime: '-',
processor: '-',
},
{
key: '7',
id: '007',
alarmTime: '2024-01-15 14:10:30',
sensorName: 'LNG储罐',
alarmType: '温度异常',
alarmContent: '储罐温度异常升高',
priority: '高',
status: '处理中',
processTime: '2024-01-15 14:15:00',
processor: '赵六',
},
{
key: '8',
id: '008',
alarmTime: '2024-01-15 15:45:20',
sensorName: 'LNG管道',
alarmType: '压力超限',
alarmContent: '管道压力超过安全阈值',
priority: '高',
status: '已处理',
processTime: '2024-01-15 16:00:00',
processor: '孙七',
},
{
key: '9',
id: '009',
alarmTime: '2024-01-15 16:30:45',
sensorName: 'LNG储罐',
alarmType: '液位超限',
alarmContent: '储罐液位超过警戒线',
priority: '中',
status: '未处理',
processTime: '-',
processor: '-',
},
{
key: '10',
id: '010',
alarmTime: '2024-01-15 17:15:10',
sensorName: 'LNG管道',
alarmType: '泄漏检测',
alarmContent: '检测到轻微气体泄漏',
priority: '低',
status: '已处理',
processTime: '2024-01-15 17:30:00',
processor: '周八',
},
{
key: '11',
id: '011',
alarmTime: '2024-01-15 18:20:35',
sensorName: 'LNG储罐',
alarmType: '设备故障',
alarmContent: '储罐阀门异常关闭',
priority: '高',
status: '处理中',
processTime: '2024-01-15 18:25:00',
processor: '吴九',
},
{
key: '12',
id: '012',
alarmTime: '2024-01-15 19:05:50',
sensorName: 'LNG管道',
alarmType: '温度异常',
alarmContent: '管道温度异常下降',
priority: '中',
status: '未处理',
processTime: '-',
processor: '-',
},
];
// 初始化数据
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 handleTableChange = (pagination) => {
setPagination(prev => ({
...prev,
current: pagination.current,
pageSize: pagination.pageSize,
}));
};
// 导出功能
const handleExport = () => {
console.log('导出数据');
// 这里可以添加导出逻辑
};
// 批量删除功能
const handleBatchDelete = () => {
if (selectedRowKeys.length === 0) {
console.log('没有选中任何行');
// 可以在这里添加提示用户选择行的逻辑
return;
}
console.log('批量删除', selectedRowKeys);
// 这里可以添加批量删除逻辑
};
return (
<div className={styles.Ocontainer}>
<div className={styles.OcontainerTop}>
<div className={styles.OcontainerTopLeft}>
<div className={styles.OcontainerTopLeftTop}>
<div className={styles.alarmO}>
<div className={styles.alarmOLeft}>
<img style={{ width: 58, height: 47 }} src={alarm0} alt='alarm0' />
</div>
<div className={styles.alarmORight}>
<div className={styles.alarmORightText1}>总报警</div>
<div className={styles.alarmORightText2}>1456</div>
<div className={styles.alarmORightText3}>
<div>
未处理 <text style={{ color: '#FF4D4F' }}>6</text>
</div>
<div>
处理中 <text style={{ color: '#2e4cd4' }}>10</text>
</div>
</div>
</div>
</div>
<div className={styles.alarmTw}>
<div className={styles.alarmTwLeft}>
<img style={{ width: 58, height: 47 }} src={alarm1} alt='alarm1' />
</div>
<div className={styles.alarmTwRight}>
<div className={styles.alarmTwRightText1}>一级报警</div>
<div className={styles.alarmTwRightText2}>357</div>
<div className={styles.alarmTwRightText3}>
<div>
未处理 <text style={{ color: '#FF4D4F' }}>6</text>
</div>
<div>
处理中 <text style={{ color: '#2e4cd4' }}>10</text>
</div>
</div>
</div>
</div>
<div className={styles.alarmTh}>
<div className={styles.alarmThLeft}>
<img style={{ width: 58, height: 47 }} src={alarm2} alt='alarm2' />
</div>
<div className={styles.alarmThRight}>
<div className={styles.alarmThRightText1}>二级报警</div>
<div className={styles.alarmThRightText2}>401</div>
<div className={styles.alarmThRightText3}>
<div>
未处理 <text style={{ color: '#FF4D4F' }}>6</text>
</div>
<div>
处理中 <text style={{ color: '#2e4cd4' }}>10</text>
</div>
</div>
</div>
</div>
<div className={styles.alarmF}>
<div className={styles.alarmFLeft}>
<img style={{ width: 58, height: 47 }} src={alarm3} alt='alarm3' />
</div>
<div className={styles.alarmFRight}>
<div className={styles.alarmFRightText1}>三级报警</div>
<div className={styles.alarmFRightText2}>556</div>
<div className={styles.alarmFRightText3}>
<div>
未处理 <text style={{ color: '#FF4D4F' }}>6</text>
</div>
<div>
处理中 <text style={{ color: '#2e4cd4' }}>10</text>
</div>
</div>
</div>
</div>
</div>
<div className={styles.OcontainerTopLeftBottom}>
<div className={styles.OcontainerTopLeftBottomTitle}>
<div className={styles.titleLeft}>
<div className={styles.titleIcon}></div>
<div>预警看板</div>
</div>
<div className={styles.titleRight}>
<div>检测对象</div>
<Select
style={{ width: 80 }}
defaultValue="储罐"
options={[
{ value: '储罐', label: '储罐' },
{ value: '管道', label: '管道' },
{ value: '设备', label: '设备' }
]}
/>
</div>
</div>
<div className={styles.OcontainerTopLeftBottomChart} ref={chartRef}>
</div>
</div>
</div>
<div className={styles.OcontainerTopRight}>
<div className={styles.realTimeDataHeader}>
<div className={styles.titleLeft}>
<div className={styles.titleIcon}></div>
<div>实时数据采集</div>
</div>
<div className={styles.totalCount}>
总数 <text style={{ color: '#2e4cd4' }}>1378</text>
</div>
</div>
<div className={styles.dataItem1}>
<div className={styles.dataItemLeft}>
<div className={styles.areaName}>储罐液化装置区</div>
<div className={styles.rValue}>R值: 1765</div>
<div className={styles.codeNumber}>编号:XXXXXXXX</div>
</div>
<div className={styles.dataItemRight}>
<div className={styles.circleContainer}>
<div className={styles.outerCircle}>
<div className={styles.innerCircle}>
<div className={styles.levelText}>三级</div>
<div className={styles.riskText}>危险等级</div>
</div>
</div>
</div>
</div>
</div>
<div className={styles.dataItem2}>
<div className={styles.dataItemLeft}>
<div className={styles.areaName}>储罐液化装置区</div>
<div className={styles.rValue}>R值: 1765</div>
<div className={styles.codeNumber}>编号:XXXXXXXX</div>
</div>
<div className={styles.dataItemRight}>
<div className={styles.circleContainer}>
<div className={styles.outerCircle}>
<div className={styles.innerCircle}>
<div className={styles.levelText}>一级</div>
<div className={styles.riskText}>危险等级</div>
</div>
</div>
</div>
</div>
</div>
<div className={styles.dataItem3}>
<div className={styles.dataItemLeft}>
<div className={styles.areaName}>储罐液化装置区</div>
<div className={styles.rValue}>R值: 1765</div>
<div className={styles.codeNumber}>编号:XXXXXXXX</div>
</div>
<div className={styles.dataItemRight}>
<div className={styles.circleContainer}>
<div className={styles.outerCircle}>
<div className={styles.innerCircle}>
<div className={styles.levelText}>二级</div>
<div className={styles.riskText}>危险等级</div>
</div>
</div>
</div>
</div>
</div>
<div className={styles.dataItem4}>
<div className={styles.dataItemLeft}>
<div className={styles.areaName}>储罐液化装置区</div>
<div className={styles.rValue}>R值: 1765</div>
<div className={styles.codeNumber}>编号:XXXXXXXX</div>
</div>
<div className={styles.dataItemRight}>
<div className={styles.circleContainer}>
<div className={styles.outerCircle}>
<div className={styles.innerCircle}>
<div className={styles.levelText}>三级</div>
<div className={styles.riskText}>危险等级</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{/* 表格 */}
<div className={styles.OcontainerBottom}>
{/* 首行 左侧标题左对齐 右侧按钮右对齐 */}
<div className={styles.tableHeader}>
<div className={styles.tableTitle}>
<div className={styles.titleIcon}></div>
<div>报警信息列表</div>
</div>
<div className={styles.tableActions}>
<Button
type="primary"
onClick={handleExport}
style={{ marginRight: 8 }}
>
<img src={exportIcon} alt="导出" style={{ width: 16, height: 16, margin: '-2px 6px 0 0px'}} />
导出word 报告
</Button>
<Button
type="primary"
onClick={handleBatchDelete}
>
<img src={deleteIcon} alt="删除" style={{ width: 16, height: 16, margin: '-2px 6px 0 0px' }} />
批量删除
</Button>
</div>
</div>
{/* 表格 5行10列 带页码 每页5条数据 */}
<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>
);
};
export default OnlineMonitoring;

@ -0,0 +1,919 @@
.Ocontainer {
padding: 8px 6px 0px 6px;
height: 100%;
display: flex;
flex-direction: column;
.OcontainerTop {
display: flex;
height: 50%;
margin-bottom: 5px;
.OcontainerTopLeft {
width: 72%;
height: 100%;
// background-color: pink;
margin-right: 10px;
// display: flex;
.OcontainerTopLeftTop {
width: 100%;
height: 35%;
display: flex;
gap: 12px;
.alarmO {
flex: 1;
height: 100%;
background-color: #F4F7FF;
border: 1px solid #AED3FF;
border-bottom: 0px solid #AED3FF;
border-radius: 4px;
box-shadow: 0px 2px 31px 0px #5382FE33 inset;
display: flex;
.alarmOLeft {
width: 35%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.alarmORight {
flex: 1;
width: 35%;
height: 100%;
display: flex;
flex-direction: column;
margin-left: 2px;
gap: 18px;
font-family: PingFang SC;
font-weight: 400;
font-style: Regular;
font-size: 12px;
line-height: 100%;
letter-spacing: 0%;
color: #333333;
.alarmORightText1 {
margin-top: 15px;
}
.alarmORightText2 {
font-weight: 700;
font-size: 16px;
}
.alarmORightText3 {
display: flex;
gap: 22px;
}
}
}
.alarmTw {
flex: 1;
height: 100%;
background-color: #FFF5f4;
border: 1px solid #FFC5BC;
border-bottom: 0px solid #FFC5BC;
border-radius: 4px;
box-shadow: 0px 2px 31px 0px #FE5F4C33 inset;
display: flex;
.alarmTwLeft {
width: 35%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.alarmTwRight {
flex: 1;
width: 35%;
height: 100%;
display: flex;
flex-direction: column;
margin-left: 2px;
gap: 18px;
font-family: PingFang SC;
font-weight: 400;
font-style: Regular;
font-size: 12px;
line-height: 100%;
letter-spacing: 0%;
color: #333333;
.alarmTwRightText1 {
margin-top: 15px;
}
.alarmTwRightText2 {
font-weight: 700;
font-size: 16px;
}
.alarmTwRightText3 {
display: flex;
gap: 22px;
}
}
}
.alarmTh {
flex: 1;
height: 100%;
background-color: #FFF7F2;
border: 1px solid #FFD9B2;
border-bottom: 0px solid #FFD9B2;
border-radius: 4px;
box-shadow: 0px 2px 31px 0px #FD883C33 inset;
display: flex;
.alarmThLeft {
width: 35%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.alarmThRight {
flex: 1;
width: 35%;
height: 100%;
display: flex;
flex-direction: column;
margin-left: 2px;
gap: 18px;
font-family: PingFang SC;
font-weight: 400;
font-style: Regular;
font-size: 12px;
line-height: 100%;
letter-spacing: 0%;
color: #333333;
.alarmThRightText1 {
margin-top: 15px;
}
.alarmThRightText2 {
font-weight: 700;
font-size: 16px;
}
.alarmThRightText3 {
display: flex;
gap: 22px;
}
}
}
.alarmF {
flex: 1;
height: 100%;
background-color: #EFF9FF;
border: 1px solid #89E1FF;
border-bottom: 0px solid #89E1FF;
border-radius: 4px;
box-shadow: 0px 2px 31px 0px #22A4FD33 inset;
display: flex;
.alarmFLeft {
width: 35%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.alarmFRight {
flex: 1;
width: 35%;
height: 100%;
display: flex;
flex-direction: column;
margin-left: 2px;
gap: 18px;
font-family: PingFang SC;
font-weight: 400;
font-style: Regular;
font-size: 12px;
line-height: 100%;
letter-spacing: 0%;
color: #333333;
.alarmFRightText1 {
margin-top: 15px;
}
.alarmFRightText2 {
font-weight: 700;
font-size: 16px;
}
.alarmFRightText3 {
display: flex;
gap: 22px;
}
}
}
}
.OcontainerTopLeftBottom {
margin-top: 12px;
background-color: #fff;
width: 100%;
height: 60%;
.OcontainerTopLeftBottomTitle {
display: flex;
justify-content: space-between;
align-items: center;
// padding: 8px 15px;
padding: 8px 15px 0px 15px;
.titleLeft {
display: flex;
align-items: center;
gap: 8px;
font-family: PingFang SC;
font-weight: 500;
font-style: Medium;
font-size: 14px;
line-height: 100%;
letter-spacing: 0%;
.titleIcon {
width: 3px;
height: 16px;
background-color: #2E4CD4;
}
}
.titleRight {
display: flex;
align-items: center;
gap: 8px;
font-family: PingFang SC;
font-style: Medium;
font-size: 13px;
line-height: 100%;
letter-spacing: 0%;
.selectBox {
padding: 4px 8px;
border: 1px solid #d9d9d9;
border-radius: 4px;
background-color: #fff;
font-size: 12px;
color: #333;
outline: none;
&:focus {
border-color: #2E4CD4;
}
}
}
}
.OcontainerTopLeftBottomChart {
flex: 1;
width: 100%;
height: 75%;
}
}
}
.OcontainerTopRight {
flex: 1;
height: calc(100% - 3.3px);
background-color: #fff;
background-image: url('@/assets/safe_majorHazard/online_monitoring/backTopRight.png');
background-size: 100% auto;
display: flex;
flex-direction: column;
overflow-y: auto;
.realTimeDataHeader {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 15px;
margin-bottom: 10px;
.titleLeft {
display: flex;
align-items: center;
gap: 8px;
font-family: PingFang SC;
font-weight: 500;
font-style: Medium;
font-size: 14px;
line-height: 100%;
letter-spacing: 0%;
.titleIcon {
width: 3px;
height: 16px;
background-color: #2E4CD4;
}
}
.totalCount {
font-family: PingFang SC;
font-weight: 400;
font-size: 13px;
color: #333333;
}
}
.dataItem {
height: 23%;
flex-shrink: 0;
border: 1px solid #89E1FF;
border-radius: 2px;
margin: 0 15px;
margin-bottom: 6px;
display: flex;
align-items: center;
justify-content: center;
font-family: PingFang SC;
font-size: 14px;
// color: #666;
background-color: #EFF9FF;
&:last-child {
// margin-bottom: 1px;
}
}
.dataItem1 {
height: 25%;
flex-shrink: 0;
border: 1px solid #89E1FF;
border-radius: 4px;
margin: 0 15px;
margin-bottom: 6px;
display: flex;
align-items: center;
padding: 0px 15px;
background-color: #EFF9FF;
.dataItemLeft {
width: 65%;
display: flex;
flex-direction: column;
gap: 8px;
.areaName {
font-family: PingFang SC;
font-weight: 400;
font-size: 13px;
color: #333333;
line-height: 2.2;
}
.rValue {
font-family: PingFang SC;
font-weight: 400;
font-size: 14px;
color: #666666;
line-height: 0.2;
}
.codeNumber {
font-family: PingFang SC;
font-weight: 400;
font-size: 12px;
color: #666666;
}
}
.dataItemRight {
width: 35%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
.circleContainer {
position: relative;
height: 80%;
aspect-ratio: 1; // 强制宽高比1:1
.outerCircle {
width: 100%;
height: 100%;
background-color: rgba(51, 176, 253, 0.3);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
.innerCircle {
width: 70%;
height: 70%;
background-color: rgba(4, 128, 251, 0.8);
border-radius: 50%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.levelText {
font-family: PingFang SC;
font-weight: 500;
font-size: 11px;
color: #FFFFFF;
line-height: 1.4;
margin-top: -4px;
}
.riskText {
font-family: PingFang SC;
font-weight: 300;
font-size: 8px;
color: #FFFFFF;
line-height: 1;
}
}
}
}
}
}
.dataItem2 {
height: 25%;
flex-shrink: 0;
border: 1px solid rgba(255, 197, 188, 1);
border-radius: 4px;
margin: 0 15px;
margin-bottom: 6px;
display: flex;
align-items: center;
padding: 0px 15px;
background-color: #fff5f4;
.dataItemLeft {
width: 65%;
display: flex;
flex-direction: column;
gap: 8px;
.areaName {
font-family: PingFang SC;
font-weight: 400;
font-size: 13px;
color: #333333;
line-height: 2.2;
}
.rValue {
font-family: PingFang SC;
font-weight: 400;
font-size: 14px;
color: #666666;
line-height: 0.2;
}
.codeNumber {
font-family: PingFang SC;
font-weight: 400;
font-size: 12px;
color: #666666;
}
}
.dataItemRight {
width: 35%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
.circleContainer {
position: relative;
height: 80%;
aspect-ratio: 1;
.outerCircle {
width: 100%;
height: 100%;
background-color: rgba(254, 214, 209, 1);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
.innerCircle {
width: 70%;
height: 70%;
background-color: rgba(253, 41, 14, 1);
border-radius: 50%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.levelText {
font-family: PingFang SC;
font-weight: 500;
font-size: 11px;
color: #FFFFFF;
line-height: 1.4;
margin-top: -4px;
}
.riskText {
font-family: PingFang SC;
font-weight: 300;
font-size: 8px;
color: #FFFFFF;
line-height: 1;
}
}
}
}
}
}
.dataItem3 {
height: 25%;
flex-shrink: 0;
border: 1px solid rgba(255, 217, 178, 1);
border-radius: 4px;
margin: 0 15px;
margin-bottom: 6px;
display: flex;
align-items: center;
padding: 0px 15px;
background-color: #fef6f1;
.dataItemLeft {
width: 65%;
display: flex;
flex-direction: column;
gap: 8px;
.areaName {
font-family: PingFang SC;
font-weight: 400;
font-size: 13px;
color: #333333;
line-height: 2.2;
}
.rValue {
font-family: PingFang SC;
font-weight: 400;
font-size: 14px;
color: #666666;
line-height: 0.2;
}
.codeNumber {
font-family: PingFang SC;
font-weight: 400;
font-size: 12px;
color: #666666;
}
}
.dataItemRight {
width: 35%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
.circleContainer {
position: relative;
height: 80%;
aspect-ratio: 1;
.outerCircle {
width: 100%;
height: 100%;
background-color: rgba(255, 234, 218, 1);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
.innerCircle {
width: 70%;
height: 70%;
background-color: rgba(252, 103, 18, 1);
border-radius: 50%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.levelText {
font-family: PingFang SC;
font-weight: 500;
font-size: 11px;
color: #FFFFFF;
line-height: 1.4;
margin-top: -4px;
}
.riskText {
font-family: PingFang SC;
font-weight: 300;
font-size: 8px;
color: #FFFFFF;
line-height: 1;
}
}
}
}
}
}
.dataItem4 {
height: 25%;
flex-shrink: 0;
border: 1px solid #89E1FF;
border-radius: 4px;
margin: 0 15px;
margin-bottom: 6px;
display: flex;
align-items: center;
padding: 0px 15px;
background-color: #EFF9FF;
.dataItemLeft {
width: 65%;
display: flex;
flex-direction: column;
gap: 8px;
.areaName {
font-family: PingFang SC;
font-weight: 400;
font-size: 13px;
color: #333333;
line-height: 2.2;
}
.rValue {
font-family: PingFang SC;
font-weight: 400;
font-size: 14px;
color: #666666;
line-height: 0.2;
}
.codeNumber {
font-family: PingFang SC;
font-weight: 400;
font-size: 12px;
color: #666666;
}
}
.dataItemRight {
width: 35%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
.circleContainer {
position: relative;
height: 80%;
aspect-ratio: 1;
.outerCircle {
width: 100%;
height: 100%;
background-color: rgba(51, 176, 253, 0.3);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
.innerCircle {
width: 70%;
height: 70%;
background-color: rgba(4, 128, 251, 0.8);
border-radius: 50%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.levelText {
font-family: PingFang SC;
font-weight: 500;
font-size: 11px;
color: #FFFFFF;
line-height: 1.4;
margin-top: -4px;
}
.riskText {
font-family: PingFang SC;
font-weight: 300;
font-size: 8px;
color: #FFFFFF;
line-height: 1;
}
}
}
}
}
}
}
}
.OcontainerBottom {
background-color: #fff;
flex: 1;
padding: 8px 15px 5px 15px;
display: flex;
flex-direction: column;
.tableHeader {
display: flex;
justify-content: space-between;
align-items: center;
// margin-bottom: 15px;
padding-bottom: 5px;
// border-bottom: 1px solid #f0f0f0;
.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;
// 自定义按钮样式
:global(.ant-btn) {
background-color: #ffffff !important;
border-color: #DFE4F6 !important;
color: #333333 !important;
box-shadow: none !important;
&:hover {
background-color: #f5f5f5 !important;
border-color: #DFE4F6 !important;
color: #333333 !important;
box-shadow: none !important;
}
&:focus {
background-color: #ffffff !important;
border-color: #DFE4F6 !important;
color: #333333 !important;
box-shadow: none !important;
}
&:active {
background-color: #e6e6e6 !important;
border-color: #DFE4F6 !important;
color: #333333 !important;
box-shadow: none !important;
}
// 主要按钮样式
&.ant-btn-primary {
background-color: #ffffff !important;
border-color: #DFE4F6 !important;
color: #333333 !important;
box-shadow: none !important;
&:hover {
background-color: #f5f5f5 !important;
border-color: #DFE4F6 !important;
color: #333333 !important;
box-shadow: none !important;
}
&:focus {
background-color: #ffffff !important;
border-color: #DFE4F6 !important;
color: #333333 !important;
box-shadow: none !important;
}
&:active {
background-color: #e6e6e6 !important;
border-color: #DFE4F6 !important;
color: #333333 !important;
box-shadow: none !important;
}
}
// 危险按钮样式
&.ant-btn-dangerous {
background-color: #ffffff !important;
border-color: #DFE4F6 !important;
color: #333333 !important;
box-shadow: none !important;
&:hover {
background-color: #f5f5f5 !important;
border-color: #DFE4F6 !important;
color: #333333 !important;
box-shadow: none !important;
}
&:focus {
background-color: #ffffff !important;
border-color: #DFE4F6 !important;
color: #333333 !important;
box-shadow: none !important;
}
&:active {
background-color: #e6e6e6 !important;
border-color: #DFE4F6 !important;
color: #333333 !important;
box-shadow: none !important;
}
}
// 禁用状态
&:disabled {
background-color: #f5f5f5 !important;
border-color: #d9d9d9 !important;
color: #bfbfbf !important;
box-shadow: none !important;
}
}
}
}
.tableContainer {
flex: 1;
overflow: hidden;
: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;
}
:global(.ant-table-tbody > tr:hover > td) {
background-color: #f5f5f5;
}
:global(.ant-pagination) {
margin-top: 16px;
text-align: right;
}
}
}
}

@ -0,0 +1,581 @@
import React from 'react';
import { Card, Statistic, Table,Row, Input,Button,Col, Select} from 'antd';
import { PhoneOutlined, IdcardOutlined, PlusOutlined } from '@ant-design/icons';
import StandardTable from '@/components/StandardTable';
import styles from './ResponsibilityImplementation.less';
import upload from '@/assets/business_basic/upload.png';
import download from '@/assets/business_basic/download.png';
import import1 from '@/assets/business_basic/import1.png';
import fire_fighting1 from '@/assets/business_basic/fire_fighting1.png';
import fire_fighting2 from '@/assets/business_basic/fire_fighting2.png';
import fire_fighting3 from '@/assets/business_basic/fire_fighting3.png';
import frameIcon from '@/assets/business_basic/Frame.png';
import background1 from '@/assets/business_basic/background1.png';
import export1 from '@/assets/business_basic/export1.png';
const ResponsibilityImplementation = () => {
// 搜索处理函数
const onSearch = (value) => {
console.log('搜索内容:', value);
// 这里可以添加实际的搜索逻辑
};
const columns = [
{
title:"编号",
dataIndex:"id",
key:"id",
width:80,
},
{
title:"组织代码",
dataIndex:"orgCode",
key:"orgCode",
width:120,
},
{
title:"组织类型",
dataIndex:"orgType",
key:"orgType",
width:120,
},
{
title:"负责人",
dataIndex:"manager",
key:"manager",
width:100,
},
{
title:"所属部门",
dataIndex:"department",
key:"department",
width:120,
},
{
title:"创建时间",
dataIndex:"createTime",
key:"createTime",
width:120,
},
{
title:"人员规模",
dataIndex:"staffCount",
key:"staffCount",
width:100,
},
{
title:"状态",
dataIndex:"status",
key:"status",
width:80,
render: (text, record) => {
const getStatusStyle = (status) => {
if (status === '正常') {
return {
color: '#44BB5F',
backgroundColor: '#D8F7DE',
padding: '4px 8px',
borderRadius: '4px',
fontSize: '12px',
display: 'inline-block'
};
} else if (status === '信息不全') {
return {
color: '#FF8800',
backgroundColor: '#FFF3E9',
padding: '4px 8px',
borderRadius: '4px',
fontSize: '12px',
display: 'inline-block'
};
}
return {};
};
return (
<span style={getStatusStyle(text)}>
{text}
</span>
);
}
},
{
title:"操作",
dataIndex:"action",
key:"action",
width:120,
render: (text, record) => {
const handleEdit = (record) => {
console.log('编辑记录:', record);
};
const handleDelete = (record) => {
console.log('删除记录:', record);
};
return (
<div style={{
display: 'flex',
gap: '8px',
justifyContent: 'center',
alignItems: 'center'
}}>
<Button
onClick={() => handleEdit(record)}
style={{
color: '#2E4CD4',
backgroundColor: 'transparent',
borderColor: '#E6E9FB',
fontSize: '12px',
height: '28px',
padding: '0 12px'
}}
>
编辑
</Button>
<Button
onClick={() => handleDelete(record)}
style={{
color: '#FF2526',
backgroundColor: 'transparent',
borderColor: '#FFE0E2',
fontSize: '12px',
height: '28px',
padding: '0 12px'
}}
>
删除
</Button>
</div>
);
}
}
];
// 固定的假数据
const tableData = [
{
key: '1',
id: '01',
orgCode: 'DH002',
orgType: '义务消防队',
manager: '张明',
department: '安全管理部',
createTime: '2024-12-19 14:32:15',
staffCount: '15人',
status: '正常'
},
{
key: '2',
id: '02',
orgCode: 'DH002',
orgType: '义务消防队',
manager: '张明',
department: '安全管理部',
createTime: '2024-12-18 09:25:43',
staffCount: '20人',
status: '正常'
},
{
key: '3',
id: '03',
orgCode: 'DH002',
orgType: '义务消防队',
manager: '张明',
department: '安全管理部',
createTime: '2024-12-17 16:48:22',
staffCount: '25人',
status: '信息不全'
},
{
key: '4',
id: '04',
orgCode: 'DH002',
orgType: '义务消防队',
manager: '张明',
department: '安全管理部',
createTime: '2024-12-16 11:15:37',
staffCount: '18人',
status: '正常'
},
{
key: '5',
id: '05',
orgCode: 'DH002',
orgType: '义务消防队',
manager: '张明',
department: '安全管理部',
createTime: '2024-12-15 08:42:56',
staffCount: '22人',
status: '正常'
},
{
key: '6',
id: '06',
orgCode: 'DH002',
orgType: '义务消防队',
manager: '张明',
department: '安全管理部',
createTime: '2024-12-14 13:27:18',
staffCount: '16人',
status: '信息不全'
},
{
key: '7',
id: '07',
orgCode: 'DH002',
orgType: '义务消防队',
manager: '张明',
department: '安全管理部',
createTime: '2024-12-13 15:33:29',
staffCount: '19人',
status: '正常'
},
{
key: '8',
id: '08',
orgCode: 'DH002',
orgType: '义务消防队',
manager: '张明',
department: '安全管理部',
createTime: '2024-12-12 10:56:44',
staffCount: '21人',
status: '正常'
},
{
key: '9',
id: '09',
orgCode: 'DH002',
orgType: '义务消防队',
manager: '张明',
department: '安全管理部',
createTime: '2024-12-11 17:19:52',
staffCount: '17人',
status: '信息不全'
},
{
key: '10',
id: '10',
orgCode: 'DH002',
orgType: '义务消防队',
manager: '张明',
department: '安全管理部',
createTime: '2024-12-10 12:41:07',
staffCount: '23人',
status: '正常'
},
{
key: '11',
id: '11',
orgCode: 'DH002',
orgType: '义务消防队',
manager: '张明',
department: '安全管理部',
createTime: '2024-12-09 14:08:33',
staffCount: '24人',
status: '正常'
},
{
key: '12',
id: '12',
orgCode: 'DH002',
orgType: '义务消防队',
manager: '张明',
department: '安全管理部',
createTime: '2024-12-08 16:52:14',
staffCount: '26人',
status: '信息不全'
}
];
return (
<div className={styles.XcontainerR}>
{/* 警告提示框 */}
<div className={styles.warningBox}>
<img src={frameIcon} alt="警告" className={styles.warningIcon} />
<span className={styles.warningText}>
有5个消防设备需要维护3个资质证书即将到期请及时处理
</span>
</div>
<div className={styles.containerOne}>
<div className={styles.containerOneLeft}>
{/* 第一行:标题和按钮 */}
<div className={styles.leftTopSection}>
<div className={styles.titleLeft}>
<div className={styles.titleIcon}></div>
<div>组织架构图预览</div>
</div>
<div className={styles.buttonGroup}>
<Button className={styles.actionBtn}>
<img src={upload} alt="上传图表" className={styles.btnIcon} />
上传图表
</Button>
<Button className={styles.actionBtn}>
<img src={download} alt="下载" className={styles.btnIcon} />
下载
</Button>
</div>
</div>
{/* 第二行:图片占位 */}
<div className={styles.leftBottomSection}>
<div className={styles.imagePlaceholder}>
<img src={fire_fighting1} alt="消防1" className={styles.imageIcon1} />
<div className={styles.imageRow}>
<img src={fire_fighting2} alt="消防2" className={styles.imageIcon2} />
<img src={fire_fighting3} alt="消防3" className={styles.imageIcon3} />
</div>
</div>
</div>
</div>
<div className={styles.containerOneRight}>
{/* 第一行:标题 + 搜索栏 + 下拉选择框 */}
<div className={styles.rightTopSection}>
<div className={styles.rightTopLeft}>
<div className={styles.titleLeft}>
<div className={styles.titleIcon}></div>
<div>成员信息管理</div>
</div>
</div>
<div className={styles.rightTopRight}>
<div className={styles.searchGroup}>
<Input.Search placeholder="搜索成员..." onSearch={onSearch} style={{ width: 200}} />
<Select
defaultValue="全部组织"
className={styles.organizationSelect}
options={[
{ value: '全部组织', label: '全部组织' },
{ value: '技术部', label: '技术部' },
{ value: '生产部', label: '生产部' },
{ value: '安全部', label: '安全部' },
]}
/>
</div>
</div>
</div>
{/* 第二行:三个小块 */}
<div className={styles.rightBottomSection}>
<div className={styles.threeBlocksContainer}>
<div className={styles.blockItem}>
<div className={styles.blockContent}>
<div className={styles.backgroundContainer}>
{/* 第一个块:姓名和单位 */}
<div className={styles.infoBlock}>
<div className={styles.nameText}>张明</div>
<div className={styles.unitText}>东义区消防队</div>
</div>
{/* 第二个块:电话 */}
<div className={styles.infoBlock}>
<PhoneOutlined className={styles.infoIcon} />
<span className={styles.infoText}>132****3847</span>
</div>
{/* 第三个块:身份证 */}
<div className={styles.infoBlock}>
<IdcardOutlined className={styles.infoIcon} />
<span className={styles.infoText}>1304************10</span>
</div>
{/* 第四个块:职位标签 */}
<div className={styles.infoBlock}>
<div className={styles.tagContainer}>
<div className={styles.tagBlue1}>队长</div>
<div className={styles.tagBlue2}>消防工程师</div>
</div>
</div>
{/* 第五个块:证书状态 */}
<div className={styles.infoBlock}>
<div className={styles.tagContainer}>
<div className={styles.tagBlue3}>消防工程师</div>
<div className={styles.tagYellow}>证书7天后到期</div>
</div>
</div>
{/* 第六个块:操作按钮 */}
<div className={styles.actionBlock}>
<div className={styles.buttonContainer}>
<Button className={styles.editBtn}>编辑</Button>
<Button className={styles.deleteBtn}>删除</Button>
</div>
</div>
</div>
</div>
</div>
<div className={styles.blockItem}>
<div className={styles.blockContent}>
<div className={styles.backgroundContainer2}>
{/* 第一个块:姓名和单位 */}
<div className={styles.infoBlock2}>
<span className={styles.nameText2}>李小明</span>
<span className={styles.unitText2}>消防支队</span>
</div>
{/* 第二个块:电话 */}
<div className={styles.infoBlock2}>
<PhoneOutlined className={styles.infoIcon2} />
<span className={styles.infoText2}>138****5678</span>
</div>
{/* 第三个块:身份证 */}
<div className={styles.infoBlock2}>
<IdcardOutlined className={styles.infoIcon2} />
<span className={styles.infoText2}>1304************20</span>
</div>
{/* 第四个块:职位标签 */}
<div className={styles.infoBlock2}>
<div className={styles.tagContainer2}>
<div className={styles.tagBlue4}>副队长</div>
<div className={styles.tagBlue5}>安全员</div>
</div>
</div>
{/* 第五个块:证书状态 */}
<div className={styles.infoBlock2}>
<div className={styles.tagContainer2}>
<div className={styles.tagBlue6}>安全员证</div>
<div className={styles.tagGreen}>证书正常</div>
</div>
</div>
{/* 第六个块:操作按钮 */}
<div className={styles.actionBlock2}>
<div className={styles.buttonContainer2}>
<Button className={styles.editBtn2}>编辑</Button>
<Button className={styles.deleteBtn2}>删除</Button>
</div>
</div>
</div>
</div>
</div>
<div className={styles.blockItem}>
<div className={styles.blockContent}>
<div className={styles.backgroundContainer3}>
{/* 第一个块:姓名和单位 */}
<div className={styles.infoBlock3}>
<span className={styles.nameText3}>王小红</span>
<span className={styles.unitText3}>消防中队</span>
</div>
{/* 第二个块:电话 */}
<div className={styles.infoBlock3}>
<PhoneOutlined className={styles.infoIcon3} />
<span className={styles.infoText3}>139****9012</span>
</div>
{/* 第三个块:身份证 */}
<div className={styles.infoBlock3}>
<IdcardOutlined className={styles.infoIcon3} />
<span className={styles.infoText3}>1304************30</span>
</div>
{/* 第四个块:职位标签 */}
<div className={styles.infoBlock3}>
<div className={styles.tagContainer3}>
<div className={styles.tagBlue7}>队员</div>
<div className={styles.tagBlue8}>技术员</div>
</div>
</div>
{/* 第五个块:证书状态 */}
<div className={styles.infoBlock3}>
<div className={styles.tagContainer3}>
<div className={styles.tagBlue9}>技术员证</div>
<div className={styles.tagOrange}>证书3天后到期</div>
</div>
</div>
{/* 第六个块:操作按钮 */}
<div className={styles.actionBlock3}>
<div className={styles.buttonContainer3}>
<Button className={styles.editBtn3}>编辑</Button>
<Button className={styles.deleteBtn3}>删除</Button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div className={styles.containerTwo}>
{/* 第一块:标题 */}
<div className={styles.containerTwoTitle}>
<div className={styles.titleLeft}>
<div className={styles.titleIcon}></div>
<div>组织架构管理</div>
</div>
</div>
{/* 第二个大块:搜索和按钮 */}
<div className={styles.containerTwoActions}>
<div className={styles.searchSection}>
<Input.Search placeholder="搜索姓名、工号..." onSearch={onSearch} style={{ width: 180 }} />
</div>
<div className={styles.buttonSection}>
<Button className={styles.addBtn}>
<PlusOutlined className={styles.addIcon} />
新增组织
</Button>
<Button className={styles.importBtn}>
<img src={import1} alt="批量导入" className={styles.importIcon} />
批量导入
</Button>
<Button className={styles.exportBtn}>
<img src={export1} alt="批量导出" className={styles.exportIcon} />
批量导出
</Button>
</div>
</div>
{/* 第三个大块:表格 */}
<div className={styles.containerTwoTable}>
<StandardTable
columns={columns}
data={{
list: tableData, // ======== 表格数据列表 ========
pagination: { // ======== 分页配置 ========
currentPage: 1, // ======== 当前页码 ========
pageSize: 5, // ======== 每页显示10条数据 ========
total: tableData.length, // ======== 总数据条数 ========
} // ======== 分页配置结束 ========
}} // ======== 数据对象结束 ========
selectedRows={[]} // ======== 选中的行数据,初始为空数组 ========
onSelectRow={() => {}} // ======== 行选择事件处理函数 ========
onChange={() => {}} // ======== 表格变化事件处理函数 ========
pagination={{
currentPage: 1,
pageSize: 5,
total: tableData.length,
showSizeChanger: false,
showQuickJumper: true,
showTotal: (total, range) =>
`${total}`,
locale: {
jump_to: '前往',
page: '页',
items_per_page: '条/页',
}
}}
/>
</div>
</div>
</div>
);
};
export default ResponsibilityImplementation;

@ -0,0 +1,865 @@
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;

@ -0,0 +1,594 @@
.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: 4px;
border: 2px solid #FFFFFF;
.blockLeft {
width: 60%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
padding: 15px;
padding-left: 20px;
gap: 8px;
.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: 33%;
border-radius: 4px;
display: flex;
flex-direction: column;
.sectionContent {
height: 100%;
display: flex;
display: flex;
gap: 10px;
height: 100%;
.middleBlock1 {
// flex: 1;
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;
.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: #1890ff;
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;
// }
// }
}
.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;
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: #1890ff;
color: #fff;
}
}
.customSelect {
:global(.ant-select-single:not(.ant-select-customize-input) .ant-select-selector) {
height: 26px !important;
display: flex !important;
align-items: center !important;
}
:global(.ant-select-selection-item) {
line-height: 24px !important;
// height: 24px !important;
display: flex !important;
align-items: center !important;
}
}
}
.deviceStatusChart {
position: absolute;
top: 35px;
left: 10px;
right: 10px;
bottom: 10px;
z-index: 10;
}
}
.middleBlock2 {
flex: 1;
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 {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
color: #666;
}
}
.middleBlock2Chart {
width: 100%;
height: 100%;
// 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;
.leftBlock {
width: 28%;
flex-shrink: 0;
height: 100%;
background: #fff;
// background-size: cover;
padding: 0;
display: flex;
flex-direction: column;
gap: 10px;
padding: 15px;
.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;
}
}
.developmentContainer {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
gap: 8px;
.developmentBlock1 {
flex: 1;
background-color: #F1F7FF;
border-radius: 4px;
padding: 15px 20px;
display: flex;
align-items: center;
width: 100%;
.leftContent {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
min-width: 0;
.mainText {
color: #333333;
font-size: 14px;
font-weight: 500;
font-family: PingFang SC;
width: 100%;
max-width: 500px;
}
.subText {
color: #666666;
font-size: 12px;
font-weight: 400;
font-family: PingFang SC;
width: 100%;
max-width: 400px;
}
}
.rightContent {
flex: 0 0 auto;
display: flex;
justify-content: flex-end;
align-items: center;
padding-right: 10px;
min-width: 80px;
.importantTag {
background-color: #FFE0E2;
color: #FF3E48;
font-size: 14px;
font-weight: 500;
font-family: PingFang SC;
width: 45px;
height: 25px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
}
.normalTag {
background-color: #DAF3FF;
color: #00AAFA;
font-size: 14px;
font-weight: 500;
font-family: PingFang SC;
width: 45px;
height: 25px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
}
}
}
}
}
.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: 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;
.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;
}
}
}
}
}
}

@ -0,0 +1,271 @@
import { useState, useEffect } from 'react'
import { Col, DatePicker, Form, Input, Modal, Row, Select } from 'antd'
import SelectDeptTree from '@/components/SelectDeptTree'
import SelectOrganTree from '@/components/SelectOrganTree'
import datadictionary from '@/utils/dataDictionary'
import { formatDictOptions, verifyPhone } from '@/utils/globalCommon'
import { NumberInput } from '@/components/NumberInput'
import styles from '../StaffSheetList.less'
import style from '@/global.less'
import dayjs from 'dayjs'
import { formatDate } from '@/utils/formatUtils'
const { Item: FormItem } = Form
const { TextArea } = Input
const dictData = datadictionary
//新增表单
let getDeptTreeBySelectTree
let getOrganTreeBySelectTree
const StaffSheetCreateForm = (props => {
const [form] = Form.useForm()
const [jobStatus, setJobStatus] = useState('1')
const {
modalVisible,
handleAdd,
handleModalVisible,
loading,
dispatch,
selectDeptTree,
selectOrganTree
} = props
useEffect(() => {
form.setFieldsValue({
user_type: 'employee',
job_status: '1',
mgr_type: '0'
})
}, [])
const selectedDeptTreeValue = (deptRecord) => {
getDeptTreeBySelectTree = deptRecord
}
const selectedOrganTreeValue = (orgRecord) => {
getOrganTreeBySelectTree = orgRecord
}
const parentDeptTreeMethod = {
dispatch: dispatch,
selectDeptTree: selectDeptTree,
selectedDeptTreeValue: selectedDeptTreeValue,
}
const parentOrganTreeMethod = {
dispatch: dispatch,
selectOrganTree: selectOrganTree,
selectedOrganTreeValue: selectedOrganTreeValue
}
const okHandle = () => {
form.validateFields()
.then(fieldsValue => {
form.resetFields()
fieldsValue.birthday = formatDate(fieldsValue.birthday, 'YYYY-MM-DD')
fieldsValue.hiredate = formatDate(fieldsValue.hiredate, 'YYYY-MM-DD')
fieldsValue.departure_time = formatDate(fieldsValue.departure_time, 'YYYY-MM-DD')
fieldsValue.posts = fieldsValue.posts ? JSON.stringify(fieldsValue.posts) : null
// if (getDeptTreeBySelectTree) {
// fieldsValue.dept_code = getDeptTreeBySelectTree.dept_code
// fieldsValue.dept_name = getDeptTreeBySelectTree.title
// }
if (getOrganTreeBySelectTree) {
fieldsValue.org_code = getOrganTreeBySelectTree.org_code
fieldsValue.org_name = getOrganTreeBySelectTree.title
}
handleAdd(fieldsValue)
})
.catch(errInfo => {})
}
const afterClose = () =>{
form.resetFields();
}
const handleJobStatusChange = (value) => {
setJobStatus(value)
}
return (
<Modal
width={800}
height={550}
bodyStyle={{ height: '500px', overflowY: 'auto' }}
className={style.createForm}
centered
destroyOnClose
title='新增'
open={modalVisible}
onOk={okHandle}
onCancel={() => handleModalVisible()}
afterClose={() => afterClose()}
confirmLoading={loading}
>
<Form form={form} layout='vertical' requiredMark={false}>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='登录账号' name='user_name' rules={[{ required: true, message: '请输入至少2个字符的用户名', min: 2 }]}>
<Input placeholder='请输入' />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='用户名称' name='user_name_cn' rules={[{ required: true, message: '请输入至少2个字符的用户名称', min: 2 }]}>
<Input placeholder='请输入' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='用户性别' name='sex'>
<Select options={formatDictOptions(dictData.sys_user_sex, 'dict_label', 'dict_value')} placeholder='请选择' />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='用户生日' name='birthday'>
<DatePicker format='YYYY-MM-DD' placeholder='请选择' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='手机号码' name='phone' rules={[{ required: false, min: 1, validator: verifyPhone }]}>
<NumberInput placeholder='请输入' style={{width: '100%'}} maxLength={11} />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='电子邮箱' name='email' rules={[{ type: 'email' }]}>
<Input placeholder='请输入' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='机构名称' name='org_code'>
<SelectOrganTree {...parentOrganTreeMethod} />
</FormItem>
</Col>
{/*<Col md={12} sm={24}>*/}
{/* <FormItem label='部门名称' name='dept_code'>*/}
{/* <SelectDeptTree {...parentDeptTreeMethod} placeholder={'请选择部门'} />*/}
{/* </FormItem>*/}
{/*</Col>*/}
<Col md={12} sm={24}>
<FormItem label='所属岗位' name='posts'>
<Select
mode='multiple'
allowClear
placeholder='请选择'
options={formatDictOptions(dictData.sys_user_post, 'dict_label', 'dict_value')}
/>
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
{/*<Col md={12} sm={24}>
<FormItem
label={
<span>
密码
<em className={styles.optional}>
<Tooltip title='默认密码123456'>
<InfoCircleOutlined style={{ marginLeft: 4 }} />
</Tooltip>
</em>
</span>
}
name='password'
initialValue={'123456'}
rules={[{required: true, message: '请输入至少6个字符的密码', min: 6}]}>
<Input placeholder='请输入' type='password'/>
</FormItem>
</Col>*/}
<Col md={12} sm={24}>
<FormItem label='在职状态' name='job_status'>
<Select
placeholder='请选择'
options={formatDictOptions(dictData.sys_job_status, 'dict_label', 'dict_value')}
onChange={handleJobStatusChange}
/>
</FormItem>
</Col>
{jobStatus === '1' ?
<Col md={12} sm={24}>
<FormItem label='入职时间' name='hiredate' initialValue={dayjs().endOf('day')}>
<DatePicker format='YYYY-MM-DD' placeholder='请选择' />
</FormItem>
</Col>
:
<Col md={12} sm={24}>
<FormItem label='离职时间' name='departure_time'>
<DatePicker format='YYYY-MM-DD' placeholder='请选择' />
</FormItem>
</Col>
}
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='员工类型' name='user_type'>
<Select
placeholder='请选择'
options={formatDictOptions(dictData.sys_user_type, 'dict_label', 'dict_value')}
/>
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='员工工号' name='emp_no'>
<Input placeholder='请输入' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='门禁卡号' name='access_card_no'>
<Input placeholder='请输入' />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='是否是管理员' name='mgr_type'>
<Select
style={{width: '100%'}}
placeholder='请选择'
options={formatDictOptions(dictData.sys_mgr_type, 'dict_label', 'dict_value')}
/>
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={24} sm={24}>
<FormItem label='备注' name='remarks'>
<TextArea rows={4} />
</FormItem>
</Col>
</Row>
</Form>
</Modal>
)
})
export default StaffSheetCreateForm

@ -0,0 +1,113 @@
import { useEffect } from 'react'
import { Button, Col, Form, Input, Row } from 'antd'
import { UpOutlined, SearchOutlined, RedoOutlined } from '@ant-design/icons'
import SelectDeptTree from '@/components/SelectDeptTree'
import SelectOrganTree from '@/components/SelectOrganTree'
import style from '@/global.less'
const { Item: FormItem } = Form
let getDeptTreeBySelectTree
let getOrganTreeBySelectTree
const StaffSheetRenderAdvancedForm = (props) => {
const [form] = Form.useForm()
const { dispatch, handleSearch, handleFormReset, toggleForm, selectDeptTree, selectOrganTree, params } = props
useEffect(() => {
form.setFieldsValue({
user_name: params?.user_name,
user_name_cn: params?.user_name_cn,
deptname: params?.deptname,
orgname: params?.orgname,
})
}, [params])
const onFinish = values => {
// if (getDeptTreeBySelectTree) {
// values.dept_code = getDeptTreeBySelectTree.dept_code
// values.deptname = getDeptTreeBySelectTree.title
// }
if (getOrganTreeBySelectTree) {
values.org_code = getOrganTreeBySelectTree.org_code
values.orgname = getOrganTreeBySelectTree.title
}
handleSearch(values)
}
const myHandleFormReset = () => {
form.resetFields()
handleFormReset()
}
const selectedDeptTreeValue = (deptRecord) => {
getDeptTreeBySelectTree = deptRecord
}
const selectedOrganTreeValue = (orgRecord) => {
getOrganTreeBySelectTree = orgRecord
}
const parentDeptTreeMethod = {
dispatch: dispatch,
selectDeptTree: selectDeptTree,
selectedDeptTreeValue: selectedDeptTreeValue
}
const parentOrganTreeMethod = {
dispatch: dispatch,
selectOrganTree: selectOrganTree,
selectedOrganTreeValue: selectedOrganTreeValue
}
return (
<Form form={form} onFinish={onFinish} layout='inline'>
<Row gutter={{ md: 8, lg: 24, xl: 48 }} className={style.searchInput}>
<Col md={8} sm={24}>
<FormItem label='用户名' name='user_name'>
<Input placeholder='请输入' />
</FormItem>
</Col>
<Col md={8} sm={24}>
<FormItem label='用户名称' name='user_name_cn'>
<Input placeholder='请输入' />
</FormItem>
</Col>
<Col md={8} sm={24}>
<FormItem label='机构代码' name='orgname'>
<SelectOrganTree {...parentOrganTreeMethod} />
</FormItem>
</Col>
</Row>
<Row gutter={{md: 8, lg: 24, xl: 48}} className={style.searchBox}>
{/*<Col md={8} sm={24}>*/}
{/* <FormItem label='部门名称' name='deptname'>*/}
{/* <SelectDeptTree placeholder={'请选择部门'} {...parentDeptTreeMethod} />*/}
{/* </FormItem>*/}
{/*</Col>*/}
<Col md={24} sm={24}>
<div className={style.searchBtn}>
<Button type='primary' htmlType='submit'>
查询
</Button>
<Button onClick={myHandleFormReset}>
重置
</Button>
<a onClick={() => toggleForm(form)}>
收起 <UpOutlined />
</a>
</div>
</Col>
</Row>
</Form>
)
}
export default StaffSheetRenderAdvancedForm

@ -0,0 +1,81 @@
import { useEffect } from 'react'
import {Button, Col, Form, Input, Row, DatePicker, Select} from 'antd'
import {DownOutlined, RedoOutlined, SearchOutlined} from '@ant-design/icons'
import style from '@/global.less'
import dayjs from 'dayjs'
const { Item: FormItem } = Form
const StaffSheetRenderSimpleForm = (props) => {
const [form] = Form.useForm()
const { handleSearch, handleFormReset, toggleForm, params } = props
useEffect(() => {
form.setFieldsValue({
user_name: params?.user_name,
user_name_cn: params?.user_name_cn,
})
}, [params])
const onFinish = values => {
handleSearch(values)
}
const myHandleFormReset = () => {
form.resetFields()
handleFormReset()
}
return (
<Form form={form} onFinish={onFinish} layout='inline'>
<Row gutter={{ md: 8, lg: 24, xl: 48 }} className={style.searchInput}>
<Col md={4} sm={24}>
<FormItem label='我的查询条件' name='wdcxtj'>
<Select
placeholder='请选择'
options={[]}
/>
</FormItem>
</Col>
<Col md={4} sm={24}>
<FormItem label='日期' name='rq' rules={[{ required: true, message: '请选择日期!' }]}>
<DatePicker defaultValue={dayjs('2025-04-10', 'YYYY-MM-DD')} format='YYYY-MM-DD' />
</FormItem>
</Col>
<Col md={4} sm={24}>
<FormItem label='工作地点' name='gzdd'>
<Input placeholder='请输入' />
</FormItem>
</Col>
<Col md={4} sm={24}>
<FormItem label='工号' name='gh'>
<Input placeholder='请输入' defaultValue="123456"/>
</FormItem>
</Col>
<Col md={4} sm={24}>
<FormItem label='名称' name='gh'>
<Input placeholder='请输入'/>
</FormItem>
</Col>
<Col md={4} sm={24}>
<div className={style.searchBtn}>
<Button type='primary' htmlType='submit'>
查询
</Button>
<Button onClick={myHandleFormReset}>
重置
</Button>
</div>
</Col>
</Row>
</Form>
)
}
export default StaffSheetRenderSimpleForm

@ -0,0 +1,362 @@
import { useState, useEffect } from 'react'
import { Col, DatePicker, Form, Input, Modal, Row, Select } from 'antd'
import SelectOrganTree from '@/components/SelectOrganTree'
import datadictionary from '@/utils/dataDictionary'
import { formatDictOptions, verifyPhone } from '@/utils/globalCommon'
import { NumberInput } from '@/components/NumberInput'
import styles from '../StaffSheetList.less'
import style from '@/global.less'
import dayjs from 'dayjs'
import { formatDate, formatDateObject } from '@/utils/formatUtils'
const { Item: FormItem } = Form
const { TextArea } = Input
const dictData = datadictionary
//新增表单
let getDeptTreeBySelectTree
let getOrganTreeBySelectTree
const StaffSheetUpdateForm = (props) => {
const [form] = Form.useForm()
const [jobStatus, setJobStatus] = useState('1')
const [userStatus, setUserStatus] = useState('0')
const {
handleUpdate,
updateModalVisible,
handleUpdateModalVisible,
values,
loading,
dispatch,
selectDeptTree,
selectOrganTree
} = props
const selectedDeptTreeValue = (deptRecord) => {
getDeptTreeBySelectTree = deptRecord
}
const selectedOrganTreeValue = (orgRecord) => {
getOrganTreeBySelectTree = orgRecord
}
const parentDeptTreeMethod = {
dispatch: dispatch,
selectDeptTree: selectDeptTree,
selectedDeptTreeValue: selectedDeptTreeValue,
}
const parentOrganTreeMethod = {
dispatch: dispatch,
selectOrganTree: selectOrganTree,
selectedOrganTreeValue: selectedOrganTreeValue
}
useEffect(() => {
setJobStatus(values.job_status)
setUserStatus(values.status)
form.setFieldsValue({
user_id: values.user_id,
user_name: values.user_name,
user_name_cn: values.user_name_cn,
user_name_en: values.user_name_en,
password: values.password,
email: values.email,
phone: values.phone,
landline: values.landline,
sex: values.sex,
avatar: values.avatar,
sign: values.sign,
tags: values.tags,
id_card: values.id_card,
birthday: formatDateObject(values.birthday, 'YYYY-MM-DD'),
job_status: values.job_status,
hiredate: formatDateObject(values.hiredate, 'YYYY-MM-DD'),
departure_time: formatDateObject(values.departure_time, 'YYYY-MM-DD'),
user_type: values.user_type,
emp_no: values.emp_no,
access_card_no: values.access_card_no,
country: values.country,
province: values.province,
city: values.city,
address: values.address,
work_addr: values.work_addr,
floor: values.floor,
inprovince: values.inprovince,
// dept_code: values.dept_code,
// dept_name: values.dept_name,
inner_dept_code: values.inner_dept_code,
org_code: values.org_code,
org_name: values.org_name,
inner_org_code: values.inner_org_code,
posts: values.posts ? JSON.parse(values.posts) : [],
wx_openid: values.wx_openid,
wx_mpopenid: values.wx_mpopenid,
wx_miniopenid: values.wx_miniopenid,
wx_unionid: values.wx_unionid,
mobile_imei: values.mobile_imei,
device_num: values.device_num,
al_taobao: values.al_taobao,
al_alipay: values.al_alipay,
al_dingding: values.al_dingding,
is_system_user: values.is_system_user,
mgr_type: values.mgr_type,
pwd_security_level: values.pwd_security_level,
pwd_update_date: values.pwd_update_date,
last_login_ip: values.last_login_ip,
last_login_date: values.last_login_date,
freeze_date: values.freeze_date,
freeze_cause: values.freeze_cause,
zindex: values.zindex,
wx_msg: values.wx_msg,
email_msg: values.email_msg,
system_msg: values.system_msg,
remarks: values.remarks,
status: values.status,
creator: values.creator,
create_date: values.create_date,
updater: values.updater,
update_date: values.update_date
})
}, [])
const handleLocalUpdate = () => {
form
.validateFields()
.then(fieldsValue => {
const formVals = {...values, ...fieldsValue}
formVals.birthday = formatDate(formVals.birthday, 'YYYY-MM-DD')
formVals.hiredate = formatDate(formVals.hiredate, 'YYYY-MM-DD')
formVals.departure_time = formatDate(formVals.departure_time, 'YYYY-MM-DD')
formVals.posts = formVals.posts ? JSON.stringify(formVals.posts) : null
formVals.freeze_date = '3' === formVals.status ? formatDate(dayjs().endOf('day'), 'YYYY-MM-DD') : null
formVals.freeze_cause = '3' === formVals.status ? formVals.freeze_cause : null
// if (getDeptTreeBySelectTree) {
// formVals.dept_code = getDeptTreeBySelectTree.dept_code
// formVals.dept_name = getDeptTreeBySelectTree.title
// }
if (getOrganTreeBySelectTree) {
formVals.org_code = getOrganTreeBySelectTree.org_code
formVals.org_name = getOrganTreeBySelectTree.title
}
handleUpdate(formVals)
})
.catch(errInfo => {})
}
const afterClose = () =>{
form.resetFields();
}
const handleJobStatusChange = (value) => {
setJobStatus(value)
}
const handleUserStatusChange = (value) => {
setUserStatus(value)
}
return (
<Modal
width={800}
height={550}
bodyStyle={{ height: '500px', overflowY: 'auto' }}
className={style.updateForm}
centered
destroyOnClose
title='修改'
open={updateModalVisible}
onOk={() => handleLocalUpdate()}
onCancel={() => handleUpdateModalVisible()}
afterClose={() => afterClose()}
confirmLoading={loading}
>
<Form form={form} layout='vertical' requiredMark={false}>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='登录账号' name='user_name' rules={[{ required: true, message: '请输入至少2个字符的用户名', min: 2 }]}>
<Input placeholder='请输入' />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='用户名称' name='user_name_cn' rules={[{ required: true, message: '请输入至少2个字符的用户名称', min: 2 }]}>
<Input placeholder='请输入' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='用户性别' name='sex'>
<Select options={formatDictOptions(dictData.sys_user_sex, 'dict_label', 'dict_value')} placeholder='请选择' />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='用户生日' name='birthday'>
<DatePicker format='YYYY-MM-DD' placeholder='请选择' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='手机号码' name='phone' rules={[{ required: false, min: 1, validator: verifyPhone }]}>
<NumberInput placeholder='请输入' style={{width: '100%'}} maxLength={11} />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='电子邮箱' name='email' rules={[{ type: 'email' }]}>
<Input placeholder='请输入' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='机构名称' name='org_code'>
<SelectOrganTree {...parentOrganTreeMethod} />
</FormItem>
</Col>
{/*<Col md={12} sm={24}>*/}
{/* <FormItem label='部门名称' name='dept_code'>*/}
{/* <SelectDeptTree {...parentDeptTreeMethod} placeholder={'请选择部门'} />*/}
{/* </FormItem>*/}
{/*</Col>*/}
<Col md={12} sm={24}>
<FormItem label='所属岗位' name='posts'>
<Select
mode='multiple'
allowClear
placeholder='请选择'
options={formatDictOptions(dictData.sys_user_post, 'dict_label', 'dict_value')}
/>
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
{/*<Col md={12} sm={24}>
<FormItem
label={
<span>
密码
<em className={styles.optional}>
<Tooltip title='默认密码123456'>
<InfoCircleOutlined style={{ marginLeft: 4 }} />
</Tooltip>
</em>
</span>
}
name='password'
initialValue={'123456'}
rules={[{required: true, message: '请输入至少6个字符的密码', min: 6}]}>
<Input placeholder='请输入' type='password'/>
</FormItem>
</Col>*/}
<Col md={12} sm={24}>
<FormItem label='在职状态' name='job_status'>
<Select
placeholder='请选择'
options={formatDictOptions(dictData.sys_job_status, 'dict_label', 'dict_value')}
onChange={handleJobStatusChange}
/>
</FormItem>
</Col>
{jobStatus === '1' ?
<Col md={12} sm={24}>
<FormItem label='入职时间' name='hiredate' initialValue={dayjs().endOf('day')}>
<DatePicker format='YYYY-MM-DD' placeholder='请选择' />
</FormItem>
</Col>
:
<Col md={12} sm={24}>
<FormItem label='离职时间' name='departure_time'>
<DatePicker format='YYYY-MM-DD' placeholder='请选择' />
</FormItem>
</Col>
}
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='员工类型' name='user_type'>
<Select
placeholder='请选择'
options={formatDictOptions(dictData.sys_user_type, 'dict_label', 'dict_value')}
/>
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='员工工号' name='emp_no'>
<Input placeholder='请输入' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='门禁卡号' name='access_card_no'>
<Input placeholder='请输入' />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='是否是管理员' name='mgr_type'>
<Select
style={{width: '100%'}}
placeholder='请选择'
options={formatDictOptions(dictData.sys_mgr_type, 'dict_label', 'dict_value')}
/>
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='状态' name='status'>
<Select
placeholder='请选择'
options={formatDictOptions(dictData.user_status, 'dict_label', 'dict_value')}
onChange={handleUserStatusChange}
/>
</FormItem>
</Col>
{ userStatus === '3' &&
<Col md={12} sm={24}>
<FormItem label='冻结原因' name='freeze_cause'>
<Input placeholder='请输入' />
</FormItem>
</Col>
}
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={24} sm={24}>
<FormItem label='备注' name='remarks'>
<TextArea rows={4} />
</FormItem>
</Col>
</Row>
</Form>
</Modal>
)
}
export default StaffSheetUpdateForm

@ -0,0 +1,299 @@
import { useState, useEffect } from 'react'
import {Col, DatePicker, Form, Input, Modal, Row, Select} from 'antd'
import datadictionary from '@/utils/dataDictionary'
import style from "@/global.less";
import {formatDictOptions, verifyPhone} from "@/utils/globalCommon";
import {NumberInput} from "@/components/NumberInput";
import dayjs from "dayjs";
import SelectOrganTree from "@/components/SelectOrganTree";
import {formatDateObject} from "@/utils/formatUtils";
const { Item: FormItem } = Form
const { TextArea } = Input
const dictData = datadictionary
const StaffSheetViewForm = (props) => {
const [form] = Form.useForm()
const [jobStatus, setJobStatus] = useState('1')
const [userStatus, setUserStatus] = useState('0')
const { viewModalVisible, handleViewModalVisible, values } = props
useEffect(() => {
setJobStatus(values.job_status)
setUserStatus(values.status)
form.setFieldsValue({
user_id: values.user_id,
user_name: values.user_name,
user_name_cn: values.user_name_cn,
user_name_en: values.user_name_en,
password: values.password,
email: values.email,
phone: values.phone,
landline: values.landline,
sex: values.sex,
avatar: values.avatar,
sign: values.sign,
tags: values.tags,
id_card: values.id_card,
birthday: formatDateObject(values.birthday, 'YYYY-MM-DD'),
job_status: values.job_status,
hiredate: formatDateObject(values.hiredate, 'YYYY-MM-DD'),
departure_time: formatDateObject(values.departure_time, 'YYYY-MM-DD'),
user_type: values.user_type,
emp_no: values.emp_no,
access_card_no: values.access_card_no,
country: values.country,
province: values.province,
city: values.city,
address: values.address,
work_addr: values.work_addr,
floor: values.floor,
inprovince: values.inprovince,
// dept_code: values.dept_code,
// dept_name: values.dept_name,
inner_dept_code: values.inner_dept_code,
org_code: values.org_code,
org_name: values.org_name,
inner_org_code: values.inner_org_code,
posts: values.posts ? JSON.parse(values.posts) : [],
wx_openid: values.wx_openid,
wx_mpopenid: values.wx_mpopenid,
wx_miniopenid: values.wx_miniopenid,
wx_unionid: values.wx_unionid,
mobile_imei: values.mobile_imei,
device_num: values.device_num,
al_taobao: values.al_taobao,
al_alipay: values.al_alipay,
al_dingding: values.al_dingding,
is_system_user: values.is_system_user,
mgr_type: values.mgr_type,
pwd_security_level: values.pwd_security_level,
pwd_update_date: values.pwd_update_date,
last_login_ip: values.last_login_ip,
last_login_date: values.last_login_date,
freeze_date: values.freeze_date,
freeze_cause: values.freeze_cause,
zindex: values.zindex,
wx_msg: values.wx_msg,
email_msg: values.email_msg,
system_msg: values.system_msg,
remarks: values.remarks,
status: values.status,
creator: values.creator,
create_date: values.create_date,
updater: values.updater,
update_date: values.update_date
})
}, [])
const afterClose = () =>{
form.resetFields();
}
const handleJobStatusChange = (value) => {
setJobStatus(value)
}
const handleUserStatusChange = (value) => {
setUserStatus(value)
}
return (
<Modal
width={800}
height={550}
bodyStyle={{ height: '500px', overflowY: 'auto' }}
className={style.viewForm}
centered
destroyOnClose
title='查看'
open={viewModalVisible}
onOk={() => handleViewModalVisible()}
onCancel={() => handleViewModalVisible()}
afterClose={() => afterClose()}
>
<Form form={form} layout='vertical' requiredMark={false} style={{pointerEvents: 'none'}}>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='登录账号' name='user_name' rules={[{ required: true, message: '请输入至少2个字符的用户名', min: 2 }]}>
<Input placeholder='请输入' />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='用户名称' name='user_name_cn' rules={[{ required: true, message: '请输入至少2个字符的用户名称', min: 2 }]}>
<Input placeholder='请输入' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='用户性别' name='sex'>
<Select options={formatDictOptions(dictData.sys_user_sex, 'dict_label', 'dict_value')} placeholder='请选择' />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='用户生日' name='birthday'>
<DatePicker format='YYYY-MM-DD' placeholder='请选择' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='手机号码' name='phone' rules={[{ required: false, min: 1, validator: verifyPhone }]}>
<NumberInput placeholder='请输入' style={{width: '100%'}} maxLength={11} />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='电子邮箱' name='email' rules={[{ type: 'email' }]}>
<Input placeholder='请输入' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='机构名称' name='org_name'>
<Input placeholder='请输入' />
</FormItem>
</Col>
{/*<Col md={12} sm={24}>*/}
{/* <FormItem label='部门名称' name='dept_code'>*/}
{/* <SelectDeptTree {...parentDeptTreeMethod} placeholder={'请选择部门'} />*/}
{/* </FormItem>*/}
{/*</Col>*/}
<Col md={12} sm={24}>
<FormItem label='所属岗位' name='posts'>
<Select
mode='multiple'
allowClear
placeholder='请选择'
options={formatDictOptions(dictData.sys_user_post, 'dict_label', 'dict_value')}
/>
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
{/*<Col md={12} sm={24}>
<FormItem
label={
<span>
密码
<em className={styles.optional}>
<Tooltip title='默认密码123456'>
<InfoCircleOutlined style={{ marginLeft: 4 }} />
</Tooltip>
</em>
</span>
}
name='password'
initialValue={'123456'}
rules={[{required: true, message: '请输入至少6个字符的密码', min: 6}]}>
<Input placeholder='请输入' type='password'/>
</FormItem>
</Col>*/}
<Col md={12} sm={24}>
<FormItem label='在职状态' name='job_status'>
<Select
placeholder='请选择'
options={formatDictOptions(dictData.sys_job_status, 'dict_label', 'dict_value')}
onChange={handleJobStatusChange}
/>
</FormItem>
</Col>
{jobStatus === '1' ?
<Col md={12} sm={24}>
<FormItem label='入职时间' name='hiredate' initialValue={dayjs().endOf('day')}>
<DatePicker format='YYYY-MM-DD' placeholder='请选择' />
</FormItem>
</Col>
:
<Col md={12} sm={24}>
<FormItem label='离职时间' name='departure_time'>
<DatePicker format='YYYY-MM-DD' placeholder='请选择' />
</FormItem>
</Col>
}
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='员工类型' name='user_type'>
<Select
placeholder='请选择'
options={formatDictOptions(dictData.sys_user_type, 'dict_label', 'dict_value')}
/>
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='员工工号' name='emp_no'>
<Input placeholder='请输入' />
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='门禁卡号' name='access_card_no'>
<Input placeholder='请输入' />
</FormItem>
</Col>
<Col md={12} sm={24}>
<FormItem label='是否是管理员' name='mgr_type'>
<Select
style={{width: '100%'}}
placeholder='请选择'
options={formatDictOptions(dictData.sys_mgr_type, 'dict_label', 'dict_value')}
/>
</FormItem>
</Col>
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={12} sm={24}>
<FormItem label='状态' name='status'>
<Select
placeholder='请选择'
options={formatDictOptions(dictData.user_status, 'dict_label', 'dict_value')}
onChange={handleUserStatusChange}
/>
</FormItem>
</Col>
{ userStatus === '3' &&
<Col md={12} sm={24}>
<FormItem label='冻结原因' name='freeze_cause'>
<Input placeholder='请输入' />
</FormItem>
</Col>
}
</Row>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={24} sm={24}>
<FormItem label='备注' name='remarks'>
<TextArea rows={4} />
</FormItem>
</Col>
</Row>
</Form>
</Modal>
)
}
export default StaffSheetViewForm

@ -0,0 +1,319 @@
import { deleteByPrimaryKeyForProUser, selectByPrimaryKeyForProUser, insertForProUser, updateForProUser, deleteByMapForProUser,updateByMapForProUser, getOneForProUser,getAllForProUser,queryPageForProUser, countForProUser, insertBatchForProUser, deleteBatchForProUser,updateBatchForProUser, resetPwdForProUser } from '@/services/system/api_prouser';
export default {
namespace: 'safemajo333hazard',
state: {
params: {},
data: {
list: [],
pagination: {},
},
},
effects: {
*delete_by_primarykey_for_prouser({ payload, callback }, { select, call, put }) {
const response = yield call(deleteByPrimaryKeyForProUser, payload)
yield put({
type: 'deleteByPrimaryKeyForProUser',
payload: response
})
if (!response.success) {
callback && callback(response)
return
}
const params = yield select(state => state.prouser.params)
const responseData = yield call(queryPageForProUser, params)
yield put({
type: 'queryPageForProUser',
payload: responseData
})
if (callback) callback(response)
},
*select_by_primarykey_for_prouser({ payload, callback }, { call, put }) {
const response = yield call(selectByPrimaryKeyForProUser, payload)
yield put({
type: 'selectByPrimaryKeyForProUser',
payload: response
})
if (callback) callback(response)
},
*insert_for_prouser({ payload, callback }, { select, call, put }) {
const response = yield call(insertForProUser, payload)
yield put({
type: 'insertForProUser',
payload: response
})
if (!response.success) {
callback && callback(response)
return
}
const params = yield select(state => state.prouser.params)
const responseData = yield call(queryPageForProUser, params)
yield put({
type: 'queryPageForProUser',
payload: responseData
})
if (callback) callback(response)
},
*update_for_prouser({ payload, callback }, { select, call, put }) {
const response = yield call(updateForProUser, payload)
yield put({
type: 'updateForProUser',
payload: response
})
if (!response.success) {
callback && callback(response)
return
}
const params = yield select(state => state.prouser.params)
const responseData = yield call(queryPageForProUser, params)
yield put({
type: 'queryPageForProUser',
payload: responseData
})
if (callback) callback(response)
},
*delete_by_map_for_prouser({ payload, callback }, { select, call, put }) {
const response = yield call(deleteByMapForProUser, payload);
yield put({
type: 'deleteByMapForProUser',
payload: response,
});
const params = yield select(state => state.prouser.params);
const responsedata = yield call(queryPageForProUser, params);
yield put({
type: 'queryPageForProUser',
payload: responsedata,
});
if (callback) callback(response);
},
*update_by_map_for_prouser({ payload, callback }, { select, call, put }) {
const response = yield call(updateByMapForProUser, payload);
yield put({
type: 'updateByMapForProUser',
payload: response,
});
const params = yield select(state => state.prouser.params);
const responsedata = yield call(queryPageForProUser, params);
yield put({
type: 'queryPageForProUser',
payload: responsedata,
});
if (callback) callback(response);
},
*get_one_for_prouser({ payload, callback }, { call, put }) {
const response = yield call(getOneForProUser, payload);
yield put({
type: 'getOneForProUser',
payload: response,
});
if (callback) callback(response);
},
*get_all_for_prouser({ payload, callback }, { call, put }) {
const response = yield call(getAllForProUser, payload);
yield put({
type: 'getAllForProUser',
payload: response,
});
if (callback) callback(response);
},
*query_page_for_prouser({ payload, callback }, { select, call, put }) {
const params = yield select(state => state.prouser.params);
const newParams = payload?.resetFlag ? payload : {...params, ...payload};
yield put({
type: 'setQueryPageByParams',
payload: newParams,
});
const response = yield call(queryPageForProUser, newParams);
yield put({
type: 'queryPageForProUser',
payload: response,
});
if (callback) callback(response);
},
*count_for_prouser({ payload, callback }, { call, put }) {
const response = yield call(countForProUser, payload);
yield put({
type: 'countForProUser',
payload: response,
});
if (callback) callback(response);
},
*insert_batch_for_prouser({ payload, callback }, { select, call, put }) {
const response = yield call(insertBatchForProUser, payload);
yield put({
type: 'insertBatchForProUser',
payload: response,
});
const params = yield select(state => state.prouser.params);
const responsedata = yield call(queryPageForProUser, params);
yield put({
type: 'queryPageForProUser',
payload: responsedata,
});
if (callback) callback(response);
},
*delete_batch_for_prouser({ payload, callback }, { select, call, put }) {
const response = yield call(deleteBatchForProUser, payload);
yield put({
type: 'deleteBatchForProUser',
payload: response,
});
const params = yield select(state => state.prouser.params);
const responsedata = yield call(queryPageForProUser, params);
yield put({
type: 'queryPageForProUser',
payload: responsedata,
});
if (callback) callback(response);
},
*update_batch_for_prouser({ payload, callback }, { select, call, put }) {
const response = yield call(updateBatchForProUser, payload);
yield put({
type: 'updateBatchForProUser',
payload: response,
});
const params = yield select(state => state.prouser.params);
const responsedata = yield call(queryPageForProUser, params);
yield put({
type: 'queryPageForProUser',
payload: responsedata,
});
if (callback) callback(response);
},
*resetpwd_for_prouser({ payload, callback }, { select, call, put }) {
const response = yield call(resetPwdForProUser, payload);
yield put({
type: 'resetPwdForProUser',
payload: response,
});
const params = yield select(state => state.prouser.params);
const responsedata = yield call(queryPageForProUser, params);
yield put({
type: 'queryPageForProUser',
payload: responsedata,
});
if (callback) callback(response);
},
},
reducers: {
setQueryPageByParams(state, { payload }) {
return {
...state,
params: {...payload},
};
},
deleteByPrimaryKeyForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
selectByPrimaryKeyForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
insertForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
updateForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
deleteByMapForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
updateByMapForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
getOneForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
getAllForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
queryPageForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
countForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
insertBatchForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
deleteBatchForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
updateBatchForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
resetPwdForProUser(state, action) {
return {
...state,
data: action.payload,
};
},
},
};

@ -10,22 +10,13 @@ import { HomeOutlined, LogoutOutlined, AppstoreOutlined, UserOutlined, SettingOu
import { getPageQuery } from '@/utils/utils'
import menuTitle from '@/assets/img/智能管控平台.svg'
import menuTitle1 from '@/assets/img/智能管控平台-1.svg'
import dataIcon from '@/assets/data_icon.svg'
import fireHydrant from '@/assets/img/fireHydrant.svg'
import fireHydrant1 from '@/assets/img/fireHydrant1.svg'
import fireKeynoteArea from '@/assets/img/fire_keynote_area.svg'
import firewarning from '@/assets/img/icon_firewarning.svg'
import trouble from '@/assets/img/trouble.svg'
import book from '@/assets/img/book.svg'
import danger from '@/assets/img/danger.svg'
import danger1 from '@/assets/img/danger1.svg'
import license from '@/assets/img/license.svg'
import people from '@/assets/img/people.svg'
import risk from '@/assets/img/risk.svg'
import icon1 from '@/assets/img/icon1.svg'
import iconfont from "@/assets/basic_data/Iconfont.svg"
import { CustomBreadcrumb } from '@/components/GlobalComponent'
// 自定义菜单项渲染组件,支持根据激活状态显示不同图片
const CustomMenuItem = ({ item, selectedKeys }) => {
const isActive = selectedKeys.includes(item.key);
@ -93,19 +84,33 @@ const SystemContentList = (props) => {
const fixedMenuItems = [
{
path: '/topnavbar00/business/basic',
path: '/topnavbar00/business/data',
icon: <img
src={icon1}
alt="基础信息管理"
src={dataIcon}
alt="基础数据管理"
style={{
width: '16px',
height: '16px',
opacity: selectedKey.includes('/topnavbar00/business/basic') ? 1 : 0.6
opacity: selectedKey.includes('/topnavbar00/business/data') ? 1 : 0.6
}}
/>,
key: "/topnavbar00/business/basic",
"label": "基础信息管理"
key: "/topnavbar00/business/data",
"label": "基础数据管理"
},
{
path: '/topnavbar00/business/dataCollection',
icon: <img
src={iconfont}
alt="数据采集"
style={{
width: '16px',
height: '16px',
opacity: selectedKey.includes('/topnavbar00/business/data') ? 1 : 0.6
}}
/>,
key: '/topnavbar00/business/dataCollection',
"label": "数据采集",
}
]
setMenuItems(fixedMenuItems)
// 初始化默认路由

@ -77,11 +77,17 @@
width: 230px;
overflow-y: auto;
overflow-x: hidden;
background-color: #2E4CD4;
background-color: #3900E4;
background-image: linear-gradient(180deg, rgba(54, 87, 255, 0.5) 0%, rgba(78, 0, 188, 0.5) 100%),
url('@/assets/menuBack.svg');
background-repeat: no-repeat, no-repeat;
background-position: 0 0, center bottom;
background-size: auto, 100% auto;
.ant-menu-inline,
.ant-menu-vertical {
background: #2E4CD4 !important;
background: transparent !important;
}
// 默认情况字体样式和右边距

@ -3,25 +3,15 @@ import {
DeleteOutlined,
EditOutlined,
PlusOutlined,
SearchOutlined,
RedoOutlined,
DownOutlined,
ExclamationCircleFilled,
UpOutlined,
InfoCircleFilled,
QuestionCircleFilled,
DownloadOutlined
} from '@ant-design/icons';
import {connect, history} from '@umijs/max';
import {Button, Card, Divider, Dropdown, message, Modal, Popconfirm, Space, Switch, Tag, Row, Col, Tree} from 'antd';
import StandardTable from '@/components/StandardTable';
import { MyIcon } from "@/components/Icon"
import style from "@/global.less";
import styles from './SystemMenuList.less';
import datadictionary from "@/utils/dataDictionary";
import {formatDate} from "@/utils/formatUtils";
import { formatDictText, checkButtonAuthority } from "@/utils/globalCommon";
import { formatDictText, } from "@/utils/globalCommon";
const { confirm } = Modal;

@ -9,16 +9,12 @@ import { userInfo } from '@/utils/globalCommon'
const menuItem = [
{
label: '基础信息管理',
key: '/topnavbar00/business/basic',
label: '基础数据管理',
key: '/topnavbar00/business/data',
},
{
label: '消防重点部位管理',
key: '/topnavbar00/business/firekeynotearea',
},
{
label: '消防监测报警',
key: '/topnavbar00/business/fireWarning',
label: "数据采集",
key: '/topnavbar00/business/dataCollection',
},
]

Loading…
Cancel
Save