Browse Source

Merge branch 'develop'

xujunjie 1 year ago
parent
commit
08528bfffb
100 changed files with 6784 additions and 2345 deletions
  1. 49 4
      .umirc.ts
  2. 2 1
      src/app.ts
  3. BIN
      src/assets/deviceManager/redLight.png
  4. BIN
      src/assets/eqSelfInspention/tooltip.png
  5. BIN
      src/assets/home/i.png
  6. BIN
      src/assets/keyRemove.png
  7. BIN
      src/assets/message/warning.png
  8. BIN
      src/assets/smart/btn-arr1.png
  9. BIN
      src/assets/smart/btn-arr2.png
  10. BIN
      src/assets/smart/btn1.png
  11. BIN
      src/assets/smart/btn2.png
  12. BIN
      src/assets/smartOps/bg-left-1.png
  13. BIN
      src/assets/smartOps/bg-left-2.png
  14. BIN
      src/assets/smartOps/bg-left-3.png
  15. BIN
      src/assets/smartOps/bg-right-1.png
  16. BIN
      src/assets/smartOps/bg-right-2.png
  17. BIN
      src/assets/smartOps/bg-right-3.png
  18. 26 9
      src/components/ManagementPage/chartModule.js
  19. 1 1
      src/components/ManagementPage/index.less
  20. 14 9
      src/components/PageContent/index.js
  21. 7 2
      src/components/PageContent/index.less
  22. 3 0
      src/components/PageTitle/index.js
  23. 4 3
      src/components/PageTitle/index.less
  24. 67 0
      src/components/PreviewFile/index.js
  25. 20 0
      src/components/PreviewFile/index.less
  26. 15 2
      src/components/TabsContent/index.js
  27. 0 3
      src/components/TabsContent/index.less
  28. 49 15
      src/global.less
  29. 37 1
      src/models/eqSelfInspection.js
  30. 26 0
      src/models/useMandate.js
  31. 1 1
      src/pages/Center/MyTask/List/TaskList.js
  32. 21 3
      src/pages/Center/MyTask/index.js
  33. 13 5
      src/pages/Controller/Hardware.js
  34. 105 0
      src/pages/DeviceManager/Card.less
  35. 362 0
      src/pages/DeviceManager/EquipmentConstructionList.js
  36. 387 0
      src/pages/DeviceManager/EquipmentProcurementList.js
  37. 17 0
      src/pages/DeviceManager/detail.less
  38. 142 0
      src/pages/DeviceManager/deviceMaintainDetail.js
  39. 179 0
      src/pages/DeviceManager/deviceRepairDetail.js
  40. 15 98
      src/pages/DeviceManager/index.js
  41. 31 0
      src/pages/DeviceManager/index.less
  42. 71 0
      src/pages/DeviceManager/models/equipmentConstructionList.js
  43. 76 0
      src/pages/DeviceManager/models/equipmentProcurementList.js
  44. 103 0
      src/pages/DeviceManager/sparePart.js
  45. 103 76
      src/pages/DeviceManager/storage.js
  46. 58 38
      src/pages/EqSelfInspection/List/index.js
  47. 62 65
      src/pages/EqSelfInspection/List/index.less
  48. 11 1
      src/pages/EqSelfInspection/ReportDetail.js
  49. 250 1537
      src/pages/EqSelfInspection/components/Detail.js
  50. 23 5
      src/pages/EqSelfInspection/components/PatrolReportDetail.less
  51. 176 0
      src/pages/EqSelfInspection/components/Table/DeviceTable.js
  52. 213 0
      src/pages/EqSelfInspection/components/Table/DosingFlowCom.js
  53. 11 0
      src/pages/EqSelfInspection/components/Table/Empty.js
  54. 92 0
      src/pages/EqSelfInspection/components/Table/ErrorHandleModal.js
  55. 211 0
      src/pages/EqSelfInspection/components/Table/LiquidLevelCom.js
  56. 22 0
      src/pages/EqSelfInspection/components/Table/MandateBtn.js
  57. 208 0
      src/pages/EqSelfInspection/components/Table/PressureGaugeCom.js
  58. 123 0
      src/pages/EqSelfInspection/components/Table/ReportCom.js
  59. 101 0
      src/pages/EqSelfInspection/components/Table/ReportDumCom.js
  60. 117 0
      src/pages/EqSelfInspection/components/Table/WarningTable.js
  61. 211 0
      src/pages/EqSelfInspection/components/Table/WaterInCom.js
  62. 210 0
      src/pages/EqSelfInspection/components/Table/WaterQualityCom.js
  63. 37 23
      src/pages/EqSelfInspection/index.js
  64. 162 73
      src/pages/Home/ChemCostComparison.js
  65. 134 40
      src/pages/Home/EnergyCostComparison.js
  66. 334 0
      src/pages/Home/EnergyCostDetail.js
  67. 99 30
      src/pages/Home/QualityMng.js
  68. 142 31
      src/pages/Home/WaterAmtMng.js
  69. 68 0
      src/pages/Home/backlog.js
  70. 68 0
      src/pages/Home/backlog.less
  71. 159 54
      src/pages/Home/index.js
  72. 78 2
      src/pages/Home/index.less
  73. 22 3
      src/pages/Home/manage.less
  74. 6 2
      src/pages/Menu/index.js
  75. 44 4
      src/pages/MessageCenter/index.js
  76. 4 10
      src/pages/Projects/index.js
  77. 89 0
      src/pages/SafetyManagement/Command/index.js
  78. 63 0
      src/pages/SafetyManagement/Command/index.less
  79. 12 2
      src/pages/SafetyManagement/index.js
  80. 10 0
      src/pages/SafetyManagement/index.less
  81. 4 3
      src/pages/Smart/ConditionDetection.js
  82. 159 39
      src/pages/Smart/OptimizationTasks.js
  83. 46 1
      src/pages/Smart/OptimizationTasks.less
  84. 137 0
      src/pages/Smart/components/EvaluationReport.js
  85. 36 0
      src/pages/Smart/components/ScrollLoading.js
  86. 167 0
      src/pages/Smart/components/mock.js
  87. 39 45
      src/pages/Smart/index.js
  88. 3 1
      src/pages/SmartOps/Analysis.js
  89. 24 5
      src/pages/SmartOps/HistoryRecord.js
  90. 4 3
      src/pages/SmartOps/OperationRecord.js
  91. 2 0
      src/pages/SmartOps/WorkAnalysisDetail.js
  92. 124 72
      src/pages/SmartOps/components/DeviceAnalysis.js
  93. 26 0
      src/pages/SmartOps/components/SubTitle.js
  94. 3 1
      src/pages/SmartOps/components/VideoAnalysis.js
  95. 38 13
      src/pages/SmartOps/index.js
  96. 37 9
      src/pages/SmartOps/index.less
  97. 202 0
      src/pages/SmartOps/operationManage/CostAnalysis/CostAnalysis.js
  98. 12 0
      src/pages/SmartOps/operationManage/CostAnalysis/CostAnalysis.less
  99. 83 0
      src/pages/SmartOps/operationManage/index.js
  100. 62 0
      src/pages/SmartOps/predictionAnalysis/PredictionAnalysis.js

+ 49 - 4
.umirc.ts

@@ -29,8 +29,8 @@ export default defineConfig({
     '/api': {
       // target: 'http://47.96.12.136:8888/',
       target: 'http://47.96.12.136:8788/',
-      // target: 'http://120.55.44.4:8903/',
-      // target: 'https://work.greentech.com.cn/',
+      // target: 'http://120.55.44.4:8900/',
+      //target: 'https://metawant.greentech.com.cn/',
       changeOrigin: true,
     },
   },
@@ -69,11 +69,21 @@ export default defineConfig({
       path: '/home/energy/:projectId',
       component: './Home/EnergyCostComparison',
     },
+    {
+      name: '能耗详情',
+      path: '/home/energy/detail/:projectId',
+      component: './Home/EnergyCostDetail',
+    },
     {
       name: '药耗监测',
       path: '/home/chem-cost/:projectId',
       component: './Home/ChemCostComparison',
     },
+    {
+      name: '待办事项',
+      path: '/home/backlog/:projectId',
+      component: './Home/backlog',
+    },
     {
       name: '工况管理',
       path: '/smart/work/:projectId',
@@ -191,14 +201,19 @@ export default defineConfig({
     },
     {
       name: '备品管理总览',
-      path: '/device/storage/:projectId',
-      component: './DeviceManager/storage',
+      path: '/device/spare-part/:projectId',
+      component: './DeviceManager/sparePart',
     },
     {
       name: '安全管理',
       path: '/safety/:projectId', ///safety management
       component: './SafetyManagement',
     },
+    {
+      name: '安全管理-口令',
+      path: '/safety/command/:projectId', ///safety management
+      component: './SafetyManagement/Command',
+    },
     // 视频图片预览
     {
       path: '/alarm/preview',
@@ -240,6 +255,12 @@ export default defineConfig({
       path: '/smart-ops/chart-page/:projectId',
       component: './SmartOps/ChartPage',
     },
+    // 预测分析图表页面
+    {
+      name: '',
+      path: '/smart-ops/prediction/:projectId',
+      component: './SmartOps/predictionAnalysis/PredictionDetail',
+    },
     // 系统工作日报
     {
       name: '',
@@ -283,6 +304,30 @@ export default defineConfig({
       path: '/smart-report/:projectId',
       component: './SmartReport/index',
     },
+    //设备采购文件列表
+    {
+      path: '/device/equipment-procurement-list/:projectId/:billId/:deviceCode/:fileType/:ops',
+      name: '',
+      component: './DeviceManager/EquipmentProcurementList',
+    },
+    //设备施工文件列表
+    {
+      path: '/device/equipment-construction-list/:projectId/:code/:fileType/:ops',
+      name: '',
+      component: './DeviceManager/EquipmentConstructionList',
+    },
+    //设备保养详情
+    {
+      path: '/device/maintain-detail',
+      name: '',
+      component: './DeviceManager/deviceMaintainDetail',
+    },
+    //设备维修详情
+    {
+      path: '/device/repair-detail',
+      name: '',
+      component: './DeviceManager/deviceRepairDetail',
+    },
   ],
   npmClient: 'yarn',
 });

+ 2 - 1
src/app.ts

@@ -1,7 +1,7 @@
 // 运行时配置
 
 // import { RequestConfig, history } from '@umijs/max';
-import { getToken, GetTokenFromUrl } from '@/utils/utils';
+import { getToken, GetTokenFromUrl, UnityAction } from '@/utils/utils';
 import { RequestConfig } from '@umijs/max';
 import { message } from 'antd';
 import dayjs from 'dayjs';
@@ -95,6 +95,7 @@ const tokenExpiredHandle = (code: number) => {
       tokenFlag = false;
     }, 50000);
     message.error('token失效,请重新登录');
+    UnityAction.sendMsg('sessionTimeout', {});
 
     // history.push('/login');
     return true;

BIN
src/assets/deviceManager/redLight.png


BIN
src/assets/eqSelfInspention/tooltip.png


BIN
src/assets/home/i.png


BIN
src/assets/keyRemove.png


BIN
src/assets/message/warning.png


BIN
src/assets/smart/btn-arr1.png


BIN
src/assets/smart/btn-arr2.png


BIN
src/assets/smart/btn1.png


BIN
src/assets/smart/btn2.png


BIN
src/assets/smartOps/bg-left-1.png


BIN
src/assets/smartOps/bg-left-2.png


BIN
src/assets/smartOps/bg-left-3.png


BIN
src/assets/smartOps/bg-right-1.png


BIN
src/assets/smartOps/bg-right-2.png


BIN
src/assets/smartOps/bg-right-3.png


+ 26 - 9
src/components/ManagementPage/chartModule.js

@@ -69,6 +69,9 @@ const ChartModule = (props) => {
         ...option.legend.textStyle,
         ...legend.textStyle,
       };
+      if (legend.right) {
+        option.legend.right = legend.right;
+      }
     }
     let series = [];
     switch (chartType) {
@@ -102,7 +105,7 @@ const ChartModule = (props) => {
 
         if (Array.isArray(yName)) {
           if (yName.length > 2) {
-            option.grid.right = 120;
+            option.grid.right = yName.length * 60;
           }
 
           option.yAxis = yName.map((item, index) => {
@@ -119,8 +122,15 @@ const ChartModule = (props) => {
           });
         } else {
           option.grid.right = 30;
+          // 重置grid left 用于比对计算合适宽度
+          option.grid.left = 70;
           option.yAxis = { ...option.yAxis[0], name: yName };
           option.series = dataList.map((item) => {
+            item.data.forEach((dataItem) => {
+              if (String(dataItem).length * 10 + 40 > option.grid.left) {
+                option.grid.left = String(dataItem).length * 10 + 45;
+              }
+            });
             return {
               ...option.series[item.type],
               name: item.name,
@@ -202,7 +212,8 @@ const defaultOption = {
   tooltip: {
     trigger: 'axis',
     textStyle: {
-      fontSize: '0.24rem',
+      fontSize: 24,
+      // color:
     },
   },
   grid: {
@@ -239,10 +250,10 @@ const defaultOption = {
       nameTextStyle: {
         fontSize: '0.24rem',
         // align: 'left',
-        padding: [0, 0, 20, 0],
+        padding: [0, 0, 15, 0],
       },
       axisLabel: {
-        fontSize: '0.24rem',
+        fontSize: '0.2rem',
       },
       axisLine: {
         show: false,
@@ -263,8 +274,11 @@ const defaultOption = {
       position: 'right',
       nameTextStyle: {
         fontSize: '0.24rem',
-        // align: 'left',
-        padding: [0, 0, 20, 0],
+        align: 'center',
+        padding: [0, 0, 15, 0],
+      },
+      axisLabel: {
+        fontSize: '0.2rem',
       },
       axisLine: {
         show: true,
@@ -286,8 +300,11 @@ const defaultOption = {
       offset: 80,
       nameTextStyle: {
         fontSize: '0.24rem',
-        // align: 'left',
-        padding: [0, 0, 20, 0],
+        align: 'left',
+        padding: [0, 0, 15, 0],
+      },
+      axisLabel: {
+        fontSize: '0.2rem',
       },
       axisLine: {
         show: true,
@@ -423,7 +440,7 @@ const defaultOption = {
     // icon:'arrow',
     // width:'2',
     itemHeight: 0, // 远点宽度为0不显示原点
-    // right: '10%',
+    // right: '30%',
     data: ['进水水量', '预测出水量', '实际出水量'],
     lineStyle: {},
     textStyle: {

+ 1 - 1
src/components/ManagementPage/index.less

@@ -11,7 +11,7 @@
   padding-left: 0.12rem;
   line-height: 1;
 
-  font-size: 0.28rem;
+  font-size: 0.32rem;
   font-family: Source Han Sans, Source Han Sans;
   color: #615d5d;
 }

+ 14 - 9
src/components/PageContent/index.js

@@ -1,8 +1,8 @@
 import { UnityAction } from '@/utils/utils';
-import { CloseCircleFilled, LeftOutlined } from '@ant-design/icons';
-import { history } from '@umijs/max';
+import { CloseCircleFilled } from '@ant-design/icons';
 import { ConfigProvider } from 'antd';
 import locale from 'antd/es/locale/zh_CN';
+import { useEffect } from 'react';
 import styles from './index.less';
 
 export default (props) => {
@@ -10,27 +10,32 @@ export default (props) => {
     children,
     style,
     closeable = true,
-    returnable = false,
     tabs = false,
+    title = null,
   } = props;
 
   const handleClose = () => {
     UnityAction.sendMsg('closePage');
   };
-  const handleReturn = () => {
-    history.back();
-  };
+  // const handleReturn = () => {
+  //   history.back();
+  // };
+
+  useEffect(() => {
+    document.body.style.backgroundColor = 'transparent';
+  }, []);
 
   return (
     <ConfigProvider locale={locale}>
       <div className={styles.page} style={style}>
-        {returnable && (
+        {title}
+        {/* {returnable && (
           <LeftOutlined
             onClick={handleReturn}
             className={styles.return}
             style={{ top: tabs ? '0.4rem' : '' }}
           />
-        )}
+        )} */}
         {closeable && (
           <CloseCircleFilled
             onClick={handleClose}
@@ -39,7 +44,7 @@ export default (props) => {
           />
         )}
 
-        {children}
+        <div className={styles.pageContent}>{children}</div>
       </div>
     </ConfigProvider>
   );

+ 7 - 2
src/components/PageContent/index.less

@@ -1,16 +1,18 @@
 .page {
   // background: #fff;
-  padding: 0.2rem 0.5rem;
+  padding: 0.2rem 0;
+  // padding-right: 0;
   min-height: 100vh;
   position: relative;
 
   .close {
     position: absolute;
     top: 0.175rem;
-    right: 0.2rem;
+    right: 0.5rem;
     font-size: 0.34rem;
     color: #bcbaba;
     cursor: pointer;
+    z-index: 999;
   }
   .return {
     position: absolute;
@@ -20,4 +22,7 @@
     color: #bcbaba;
     cursor: pointer;
   }
+  .pageContent {
+    padding: 0 0.5rem;
+  }
 }

+ 3 - 0
src/components/PageTitle/index.js

@@ -28,6 +28,9 @@ export default (props) => {
             cursor: 'pointer',
             marginRight: '0.15rem',
             color: '#0139F1',
+            position: 'absolute',
+            left: '-0.34rem',
+            top: '0.09rem',
           }}
           onClick={handleOnClick}
         />

+ 4 - 3
src/components/PageTitle/index.less

@@ -1,10 +1,11 @@
 .titleBox {
   position: relative;
-  left: -0.2rem;
+  // left: -0.2rem;
   display: flex;
   align-items: center;
-  padding-bottom: 0.2rem;
-  border-bottom: 1px solid rgba(188, 186, 186, 1);
+  margin-bottom: 0.2rem;
+  // padding: 0.2rem 0.5rem 0;
+  // border-bottom: 1px solid rgba(188, 186, 186, 1);
 }
 .subPage {
   border-bottom: 0 none;

+ 67 - 0
src/components/PreviewFile/index.js

@@ -0,0 +1,67 @@
+import React from 'react';
+import ReactZmage from 'react-zmage';
+import './index.less';
+
+export default function PreviewFile(props) {
+  const { src, name, showName, download = false } = props;
+  var reg = /\.(png|jpg|gif|jpeg|webp)$/;
+  const controller = {
+    // 关闭按钮
+    close: true,
+    // 旋转按钮
+    rotate: true,
+    // 缩放按钮
+    zoom: false,
+    // 下载按钮
+    download: false,
+    // 翻页按钮
+    flip: false,
+    // 多页指示
+    pagination: false,
+  };
+
+  const downloadFile = () => {
+    if (window.InvokeUnityFileOpener) {
+      window.InvokeUnityFileOpener(src);
+    } else {
+      window.location.href = `${src}`;
+    }
+  };
+
+  if (reg.test(name)) {
+    if (showName) {
+      return (
+        <div style={{ display: 'flex', alignItems: 'center' }}>
+          <ReactZmage
+            controller={controller}
+            backdrop="rgba(255,255,255,0.5)"
+            style={{ height: '90px' }}
+            src={src}
+          />
+          <div style={{ marginLeft: 20 }}>{name}</div>
+        </div>
+      );
+    } else {
+      return (
+        <ReactZmage
+          controller={controller}
+          backdrop="rgba(255,255,255,0.5)"
+          style={{ height: '90px' }}
+          src={src}
+        />
+      );
+    }
+  } else if (download) {
+    return (
+      <div style={{ height: '90px', display: 'flex', alignItems: 'center' }}>
+        <a onClick={downloadFile}>{name}</a>
+      </div>
+    );
+  } else {
+    return (
+      <div style={{ height: '90px', display: 'flex', alignItems: 'center' }}>
+        <div>{name}</div>
+      </div>
+    );
+  }
+}

+ 20 - 0
src/components/PreviewFile/index.less

@@ -0,0 +1,20 @@
+:global {
+  #zmageControlZoom {
+    display: none;
+  }
+  #zmageControl {
+    transform: scale(0.3);
+    transform-origin: top right;
+  }
+  #zmageControlFlipRight {
+    transform: scale(0.3) !important;
+    transform-origin: top right;
+  }
+  #zmageControlFlipLeft {
+    transform: scale(0.3) !important;
+    transform-origin: top left;
+  }
+  #zmageControlPagination {
+    transform: translate(-50%, 0) scale(0.2);
+  }
+}

+ 15 - 2
src/components/TabsContent/index.js

@@ -1,19 +1,28 @@
 import { Divider } from 'antd';
-import { useMemo, useState } from 'react';
+import { useEffect, useMemo, useState } from 'react';
 import styles from './index.less';
 
 const TabsContent = (props) => {
   const {
     defaultActiveKey = '1',
+    active: parentActive,
     center = true,
     small = false,
+    spacing = 4,
     items = {},
+    bold = true,
     onChange,
   } = props;
-  const [active, setActive] = useState(defaultActiveKey);
+  const [active, setActive] = useState(parentActive || defaultActiveKey);
   const renderContent = useMemo(() => {
     return items.find((item) => item.key == active)?.children;
   }, [active, items]);
+
+  useEffect(() => {
+    if (parentActive) {
+      setActive(parentActive);
+    }
+  }, [parentActive]);
   return (
     <div>
       <div
@@ -24,6 +33,10 @@ const TabsContent = (props) => {
           <>
             <div
               key={item.key}
+              style={{
+                padding: `0 ${small ? (spacing - 1) / 10 : spacing / 10}rem`,
+                fontWeight: `${bold ? 'bold' : 'normal'}`,
+              }}
               className={`${styles.tabsItem} ${
                 active == item.key ? styles.active : ''
               }`}

+ 0 - 3
src/components/TabsContent/index.less

@@ -20,11 +20,9 @@
   // }
 }
 .tabsItem {
-  padding: 0 0.4rem;
   color: #3f3f40;
   font-size: 0.36rem;
   white-space: nowrap;
-  font-weight: 600;
 }
 .active {
   color: #1755ff;
@@ -36,7 +34,6 @@
   border-bottom: none;
   .tabsItem {
     font-size: 0.28rem;
-    padding: 0 0.3rem;
     font-weight: 600;
     &:last-child {
       padding-right: 0;

+ 49 - 15
src/global.less

@@ -116,7 +116,6 @@ body {
   }
 
   .ant-modal-root .ant-modal-mask {
-    border-radius: 0.5rem;
     background-color: rgba(0, 0, 0, 0.2);
   }
 
@@ -210,26 +209,26 @@ input[type='reset'] {
 .content-tab {
   padding-left: 0.05rem;
   padding-top: 0.05rem;
-  padding-right: 0.05rem;
+  padding-right: 0.25rem;
   height: calc(100vh - 1.3rem);
   overflow-x: hidden;
   overflow-y: auto;
 }
 .content-title {
-  padding-left: 0.05rem;
-  padding-right: 0.05rem;
-  height: calc(100vh - 1.15rem);
-  overflow-x: hidden;
-  overflow-y: auto;
+  // padding-left: 0.05rem;
+  // padding-right: 0.5rem;
+  // height: calc(100vh - 1.15rem);
+  // overflow-x: hidden;
+  // overflow-y: auto;
 }
 
 .password-eye {
   display: inline-block;
   vertical-align: middle;
-  width: 0.3rem;
-  height: 0.3rem;
+  width: 0.6rem;
+  height: 0.35rem;
   background: url('@/assets/icon-eye1.png') no-repeat center;
-  background-size: 0.3rem;
+  background-size: 0.4rem;
   &.open {
     background-image: url('@/assets/icon-eye2.png');
   }
@@ -292,12 +291,14 @@ input[type='reset'] {
 *::-webkit-scrollbar-track {
   background-color: #eee;
 }
-
+.ant-btn {
+  padding: 0.08rem 0.24rem;
+  font-size: 0.28rem;
+  line-height: 1;
+  height: auto;
+}
 .ant-btn-primary {
   background: #4a90e2;
-  font-size: 0.22rem;
-  padding-top: 0;
-  padding-bottom: 0;
 }
 
 .ant-table {
@@ -325,7 +326,7 @@ input[type='reset'] {
 .ant-table-wrapper .ant-table-thead > tr > th,
 .ant-table-wrapper .ant-table-tbody > tr > td {
   border-radius: 0;
-  padding: 0.12rem 0.18rem;
+  padding: 0.16rem 0.18rem;
   font-size: 0.28rem;
   color: rgba(97, 93, 93, 1);
   line-height: 0.32rem;
@@ -375,3 +376,36 @@ input[type='reset'] {
 .ant-select-single .ant-select-selector {
   font-size: 0.26rem;
 }
+.ant-checkbox-wrapper {
+  font-size: 0.32rem;
+  color: rgba(29, 131, 255, 1);
+}
+.ant-checkbox {
+  .ant-checkbox-inner {
+    width: 0.32rem;
+    height: 0.32rem;
+    &::after {
+      width: 0.1rem;
+      height: 0.15rem;
+      left: 30%;
+    }
+  }
+  // font-size: 0.32rem;
+}
+.ant-empty {
+  margin-top: 20px;
+  .ant-empty-image {
+    height: 150px;
+  }
+  .ant-empty-description {
+    font-size: 24px;
+  }
+}
+.ant-tabs .ant-tabs-tab {
+  font-size: 26px;
+}
+// react-zmage样式修改
+#zmageControl {
+  transform: scale(0.4);
+  transform-origin: 100% 0;
+}

+ 37 - 1
src/models/eqSelfInspection.js

@@ -1,9 +1,12 @@
+import dayjs from 'dayjs';
+
 import {
   analysisResultList,
   getAutoPatrolByRouteId,
   getPatrolDumuList,
   getPatrolRecordMandateInfo,
   getRecentAutoPatrolByRouteId,
+  getRecentGaugeRecord,
   getRouteList,
   patrolRelationList,
   queryAnalysisDict,
@@ -45,6 +48,10 @@ export default {
       const { data } = yield call(queryPatrolRecord, {
         recordId: payload.routeId,
       });
+      const recentGaugeRecord = yield call(getRecentGaugeRecord, {
+        project_id: payload.projectId,
+        e_time: payload.eTime || dayjs().format('YYYY-MM-DD HH:mm:ss'),
+      });
       if (data) {
         const cacheData = yield select(
           (state) => state.eqSelfInspection.autoReport,
@@ -162,6 +169,7 @@ export default {
                 console.log(FixPlan);
               }
               FaultAnalysis.push({
+                ...item,
                 device_name: item.DeviceName,
                 device_code: item.DeviceCode,
                 reason: item.Reason,
@@ -242,12 +250,40 @@ export default {
           sensorStatus = 1;
         }
         secureChild.push({ label: '环境检测', status: sensorStatus });
-        secureChild.push({ label: '电气检测', status: 0 });
+        // secureChild.push({ label: '电气检测', status: 0 });
         secureChild.push({ label: '密闭空间检测', status: 0 });
 
         data.secureChild = secureChild;
 
         callback?.(data);
+
+        if (recentGaugeRecord.code === 200) {
+          data.DrugFlowList = [
+            ...data.DrugFlowList,
+            ...recentGaugeRecord.data.DrugFlowList.map((item) => {
+              return { ...item, history: true };
+            }),
+          ];
+          data.FluidLevelList = [
+            ...data.FluidLevelList,
+            ...recentGaugeRecord.data.FluidLevelList.map((item) => {
+              return { ...item, history: true };
+            }),
+          ];
+          data.PressureCompareList = [
+            ...data.PressureCompareList,
+            ...recentGaugeRecord.data.PressureCompareList.map((item) => {
+              return { ...item, history: true };
+            }),
+          ];
+          data.WaterQualityCompareList = [
+            ...data.WaterQualityCompareList,
+            ...recentGaugeRecord.data.WaterQualityCompareList.map((item) => {
+              return { ...item, history: true };
+            }),
+          ];
+        }
+
         yield put({
           type: 'save',
           payload: { autoReport: data },

+ 26 - 0
src/models/useMandate.js

@@ -0,0 +1,26 @@
+import { getMandateIDs } from '@/services/eqSelfInspection';
+import { useRequest } from '@umijs/max';
+import { useState } from 'react';
+
+const useMandate = () => {
+  const [mandate, setMandate] = useState([]);
+  const { run } = useRequest(getMandateIDs, {
+    manual: true,
+    formatResult(res) {
+      setMandate(res);
+    },
+    initialData: [],
+  });
+
+  const getMandateId = (id) => {
+    return mandate.find((item) => item.relation_id == id)?.mandate_id;
+  };
+
+  return {
+    mandate,
+    queryMandate: run,
+    getMandateId,
+  };
+};
+
+export default useMandate;

+ 1 - 1
src/pages/Center/MyTask/List/TaskList.js

@@ -238,7 +238,7 @@ const MyTaskList = (props) => {
           <span
             style={{
               width: '0.2rem',
-              height: '2.05rem',
+              height: '2rem',
               position: 'absolute',
               background: 'rgba(23, 85, 255, 0.20)',
             }}

+ 21 - 3
src/pages/Center/MyTask/index.js

@@ -2,8 +2,14 @@ import PageContent from '@/components/PageContent';
 import TabsContent from '@/components/TabsContent';
 import { MandateType, OrderType } from '@/pages/TaskManage/constent';
 import { getMandateList } from '@/services/TaskManage';
-import { CaretRightFilled } from '@ant-design/icons';
-import { connect, useLocation, useNavigate, useParams } from '@umijs/max';
+import { CaretLeftFilled, CaretRightFilled } from '@ant-design/icons';
+import {
+  connect,
+  history,
+  useLocation,
+  useNavigate,
+  useParams,
+} from '@umijs/max';
 import { List, Spin } from 'antd';
 import { useEffect, useState } from 'react';
 import styles from './index.less';
@@ -124,7 +130,19 @@ const MyTask = (props) => {
   };
 
   return (
-    <PageContent tabs closeable={false} returnable>
+    <PageContent closeable={false}>
+      <CaretLeftFilled
+        style={{
+          fontSize: '0.3rem',
+          cursor: 'pointer',
+          marginRight: '0.15rem',
+          color: '#0139F1',
+          position: 'absolute',
+          left: '0.5rem',
+          top: '0.5rem',
+        }}
+        onClick={() => history.back()}
+      />
       <TabsContent
         defaultActiveKey={tab}
         onChange={onTabChange}

+ 13 - 5
src/pages/Controller/Hardware.js

@@ -1,9 +1,9 @@
 import PageContent from '@/components/PageContent';
-import PageTitle from '@/components/PageTitle';
 import TabsContent from '@/components/TabsContent';
 import { UnityAction } from '@/utils/utils';
 import AirConditioner from './components/AirConditioner';
 import Light from './components/Light';
+import { CaretLeftFilled } from '@ant-design/icons';
 
 function Hardware() {
   const handleTabsChange = (tab) => {
@@ -16,10 +16,18 @@ function Hardware() {
 
   return (
     <PageContent closeable={false}>
-      <PageTitle
-        tabs
-        onReturn={() => UnityAction.sendMsg('menuItem', '智能管控')}
-      ></PageTitle>
+      <CaretLeftFilled
+        style={{
+          fontSize: '0.3rem',
+          cursor: 'pointer',
+          marginRight: '0.15rem',
+          color: '#0139F1',
+          position: 'absolute',
+          left: '0.5rem',
+          top: '0.5rem',
+        }}
+        onClick={() => UnityAction.sendMsg('menuItem', '智能管控')}
+      />
       <TabsContent
         defaultActiveKey="1"
         onChange={handleTabsChange}

+ 105 - 0
src/pages/DeviceManager/Card.less

@@ -0,0 +1,105 @@
+.CardCon {
+  background: transparent;
+
+  :global {
+    .ant-card-head {
+      padding: 0;
+    }
+
+    .ant-card-body {
+      padding: 0;
+
+      // .ant-table-bordered .ant-table-body > table {
+      //   border: 1px solid #e1e1e1;
+      // }
+
+      .ant-table-wrapper {
+        //.ant-table table .ant-table-thead > tr > th {
+        //  background: #E7E7E7;
+        //  border-bottom: 1px solid #e1e1e1;
+        //  border-right: 1px solid #e1e1e1;
+        //}
+        //.ant-table table .ant-table-tbody > tr > td {
+        //  background: #Fff;
+        //  border-bottom: 1px solid #e1e1e1;
+        //  border-right: 1px solid #e1e1e1;
+        //}
+        //.ant-pagination-prev a, .ant-pagination-next a {
+        //  background: #F0F0F0;
+        //  border: 1px solid #BABABA;
+        //}
+        //.ant-pagination-item-active {
+        //  background: #46A9FC;
+        //  border: 1px solid #46A9FC;
+        //}
+        //.ant-pagination-item-active a {
+        //  color: white;
+        //}
+      }
+
+      //.ant-select-selection, .ant-calendar-picker-input, .ant-input {
+      //  background: #F0F0F0!important;
+      //  border: 1px solid #e1e1e1!important;
+      //}
+      //.ant-table-bordered.ant-table-empty .ant-table-placeholder {
+      //  background: #F0F0F0;
+      //  border: 1px solid #e1e1e1;
+      //}
+
+      .ant-form-item {
+        padding-bottom: 0;
+        margin-bottom: 15px;
+      }
+
+      .ant-form-vertical .ant-form-item-label {
+        line-height: 2.2;
+      }
+    }
+
+    .ant-calendar-picker {
+      min-width: 100% !important;
+    }
+  }
+}
+
+.bgCon {
+  padding: 20px 0 0;
+  background: transparent;
+  height: 100%;
+}
+
+.hrStyle {
+  border: 0.5px solid #e1e1e1;
+  padding: 0;
+  margin: 0;
+}
+
+.pageCon {
+  padding: 20px 20px;
+  height: 100%;
+  min-height: 100vh;
+}
+
+.devicesModal {
+  width: 774px !important;
+
+  :global {
+    .ant-modal-body {
+      height: 500px !important;
+      overflow-y: auto;
+    }
+  }
+}
+
+.formBox {
+  :global {
+    .ant-form-item-label {
+      text-align: right;
+      padding-right: 10px;
+    }
+  }
+  // .ant-form-item-label,
+  // .ant-col-24.ant-form-item-label,
+  // .ant-col-xl-24.ant-form-item-label {
+  // }
+}

+ 362 - 0
src/pages/DeviceManager/EquipmentConstructionList.js

@@ -0,0 +1,362 @@
+import PageContent from '@/components/PageContent';
+import PreviewFile from '@/components/PreviewFile';
+import { constructionSubtype } from '@/utils/constants';
+import {
+  GetTokenFromUrl,
+  connectUserModel,
+  downloadFile,
+  getToken,
+} from '@/utils/utils';
+import { Button, Card, Modal, Row, Table, Tabs, Upload, message } from 'antd';
+// import { connect } from 'dva';
+import { connect } from '@umijs/max';
+import dayjs from 'dayjs';
+import React, { Fragment } from 'react';
+import cardStyle from './Card.less';
+import styles from './index.less';
+
+const { TabPane } = Tabs;
+const { confirm } = Modal;
+
+const opsPermissionTabsMap = new Map([
+  ['合同资料', 'func-01-ops-DeviceList-00-00'],
+  ['监造资料', 'func-01-ops-DeviceList-01-00'],
+  ['出厂检验', 'func-01-ops-DeviceList-01-01'],
+  ['包装', 'func-01-ops-DeviceList-02-00'],
+  ['装箱单', 'func-01-ops-DeviceList-02-01'],
+  ['质量证书', 'func-01-ops-DeviceList-02-02'],
+  ['原产地证明', 'func-01-ops-DeviceList-02-03'],
+  ['安装手册', 'func-01-ops-DeviceList-02-04'],
+  ['到货签收单', 'func-01-ops-DeviceList-03-00'],
+  ['开箱报告', 'func-01-ops-DeviceList-03-01'],
+  ['随机资料', 'func-01-ops-DeviceList-03-02'],
+  ['安装指导资料', 'func-01-ops-DeviceList-04-00'],
+  ['安装过程资料', 'func-01-ops-DeviceList-04-01'],
+  ['调试指导资料', 'func-01-ops-DeviceList-05-00'],
+  ['单体调试记录表', 'func-01-ops-DeviceList-05-01'],
+  ['其它资料', 'func-01-ops-DeviceList-05-02'],
+]);
+
+const buildPermissionTabsMap = new Map([
+  ['合同资料', 'func-01-build-5-dev-00-00'],
+  ['监造资料', 'func-01-build-5-dev-01-00'],
+  ['出厂检验', 'func-01-build-5-dev-01-01'],
+  ['包装', 'func-01-build-5-dev-02-00'],
+  ['装箱单', 'func-01-build-5-dev-02-01'],
+  ['质量证书', 'func-01-build-5-dev-02-02'],
+  ['原产地证明', 'func-01-build-5-dev-02-03'],
+  ['安装手册', 'func-01-build-5-dev-02-04'],
+  ['到货签收单', 'func-01-build-5-dev-03-00'],
+  ['开箱报告', 'func-01-build-5-dev-03-01'],
+  ['随机资料', 'func-01-build-5-dev-03-02'],
+  ['安装指导资料', 'func-01-build-5-dev-04-00'],
+  ['安装过程资料', 'func-01-build-5-dev-04-01'],
+  ['调试指导资料', 'func-01-build-5-dev-05-00'],
+  ['单体调试记录表', 'func-01-build-5-dev-05-01'],
+  ['其它资料', 'func-01-build-5-dev-05-02'],
+]);
+
+class Index extends React.Component {
+  constructor(props) {
+    super(props);
+    const { fileType, list } = props;
+    this.state = {
+      typeNum:
+        constructionSubtype[fileType] && constructionSubtype[fileType][0].type,
+      subtypeMap: [],
+      list,
+    };
+  }
+
+  componentDidMount() {
+    console.log('-------------------', this.props);
+    const { projectId, fileType, ops, dispatch } = this.props;
+    this.getFileList();
+    if (ops == '1') {
+      dispatch({
+        type: 'equipmentConstructionList/getOpsFileType',
+        projectId,
+        fileType,
+        callback: (res) => {
+          let arr = [];
+          res &&
+            res.forEach((folder) => {
+              constructionSubtype[fileType].forEach((element) => {
+                if (
+                  folder.Type != 0 &&
+                  folder.Type == fileType &&
+                  element.type == folder.SubType
+                )
+                  arr.push(element);
+              });
+            });
+          // console.log(arr);
+          this.setState({
+            subtypeMap: arr,
+            typeNum: arr[0].type || this.state.typeNum,
+          });
+        },
+      });
+    }
+  }
+
+  getFileList = () => {
+    const { dispatch } = this.props;
+    const { projectId, code, fileType, ops } = this.props;
+    dispatch({
+      type: 'equipmentConstructionList/getFileList',
+      payload: {
+        projectId,
+        code,
+        fileType,
+        ops: ops == '1' ? 1 : undefined,
+      },
+    });
+  };
+
+  componentWillReceiveProps(nextProps, prevState) {
+    const { list } = nextProps;
+    this.setState({
+      list,
+    });
+  }
+
+  OnDeleteFile = (fileId) => {
+    const { dispatch } = this.props;
+    const { projectId, code, fileType, ops } = this.props;
+    confirm({
+      title: '提醒',
+      content: '确认删除该文件,删除后无法复原',
+      okText: '确认',
+      cancelText: '取消',
+      onOk() {
+        dispatch({
+          type: 'equipmentConstructionList/removeProjectFile',
+          payload: {
+            fileId,
+            projectId,
+            code,
+            fileType,
+            ops,
+          },
+        });
+      },
+    });
+  };
+
+  columns = [
+    {
+      title: '文件名称',
+      dataIndex: 'Name',
+      render: (text, item) => {
+        return <PreviewFile name={item.Name} src={item.Url} />;
+      },
+    },
+    {
+      title: '上传人',
+      dataIndex: 'Creator',
+      render: (text) => {
+        return text && text.CName;
+      },
+    },
+    {
+      title: '上传时间',
+      dataIndex: 'CreatedTime',
+      render: (text) => {
+        return text ? dayjs(text).format('YYYY年MM月DD日  HH:mm:ss') : null;
+      },
+    },
+    {
+      title: '操作',
+      width: '20%',
+      render: (record) => (
+        <Fragment>
+          <a
+            style={{ color: '#7BFFFB' }}
+            onClick={() => this.checkFile(record)}
+          >
+            查看
+          </a>
+          {this.showJurisdiction('func-01-build-5-1-06') && (
+            <>
+              &nbsp;&nbsp;
+              <a
+                style={{ color: '#7BFFFB' }}
+                onClick={() => this.OnDeleteFile(record.ID)}
+              >
+                删除
+              </a>
+            </>
+          )}
+        </Fragment>
+      ),
+    },
+  ];
+
+  checkFile = (record) => {
+    downloadFile(record?.Url, record?.Name);
+  };
+  GetOpsTabs = (subtype, fileType, folders) => {
+    const { list, loading } = this.props;
+    const { subtypeMap } = this.state;
+    const typeNum = this.state.typeNum || -1;
+    // console.log('typeNum', typeNum);
+    const data =
+      list &&
+      list.Files &&
+      list.Files.filter((each) => each.Subtype == typeNum);
+    // console.log(data);
+    return subtypeMap.map((item) => {
+      if (!this.showJurisdiction(opsPermissionTabsMap.get(item.value)))
+        return null;
+      const num =
+        list &&
+        list.Files &&
+        list.Files.filter((each) => each.Subtype == item.type);
+      return (
+        <TabPane
+          tab={`${item.value} (${(data && data.length) || 0})`}
+          key={item.type}
+        >
+          <Table
+            bordered
+            rowKey="ID"
+            columns={this.columns}
+            loading={loading}
+            dataSource={num}
+            pagination={false}
+          />
+        </TabPane>
+      );
+    });
+  };
+
+  showJurisdiction = (item) => {
+    console.log(item);
+    if (item === undefined) return false;
+    return this.props.user?.Permission[item];
+  };
+
+  callback = (key) => {
+    this.setState({
+      typeNum: key,
+    });
+  };
+
+  render() {
+    const { loading, folders } = this.props;
+    const { dispatch } = this.props;
+    const { projectId, code, fileType, ops } = this.props;
+    const { list, subtypeMap } = this.state;
+    const token = getToken() || GetTokenFromUrl();
+    const typeNum = this.state.typeNum || -1;
+    // const data = list && list.Files && list.Files.filter(each => each.Subtype == typeNum);
+    const uploadProps = {
+      name: 'files',
+      action: `/api/v1/project-file/${projectId}/${fileType}/${code}/${typeNum}?${
+        ops == '1' ? 'ops=1' : undefined
+      }`,
+      showUploadList: false,
+      headers: {
+        'JWT-TOKEN': token,
+      },
+      onChange(info) {
+        if (info.file.status !== 'uploading') {
+          // console.log(info.file, info.fileList);
+        }
+        if (info.file.status === 'done') {
+          var res = info.file.response;
+          if (res.code !== 200) return message.error(res.msg);
+          message.success(`${info.file.name} 文件上传成功`);
+          dispatch({
+            type: 'equipmentConstructionList/getFileList',
+            payload: {
+              projectId,
+              code,
+              fileType,
+              ops: ops == '1' ? 1 : undefined,
+            },
+          });
+        } else if (info.file.status === 'error') {
+          message.error(`${info.file.name} 文件上传失败`);
+        }
+      },
+    };
+
+    return (
+      <PageContent>
+        <Card bordered={false} className={cardStyle.CardCon}>
+          {(this.showJurisdiction('func-01-ops-1-5-06') ||
+            this.showJurisdiction('func-01-build-5-1-05')) && (
+            <Row style={{ margiTop: '-10px' }}>
+              <Upload {...uploadProps} className={styles.upload}>
+                <Button type="primary">上传</Button>
+              </Upload>
+            </Row>
+          )}
+          {constructionSubtype[fileType] ? (
+            <Tabs
+              animated={false}
+              // activeKey={typeNum + ''}
+              onChange={this.callback}
+              style={{ paddingTop: -20 }}
+            >
+              {ops == '1'
+                ? this.GetOpsTabs(subtypeMap, fileType)
+                : constructionSubtype[fileType].map((item) => {
+                    if (
+                      !this.showJurisdiction(
+                        buildPermissionTabsMap.get(item.value),
+                      )
+                    )
+                      return null;
+                    const num =
+                      list &&
+                      list.Files &&
+                      list.Files.filter((each) => each.Subtype == item.type);
+                    return (
+                      <TabPane
+                        tab={`${item.value} (${(num && num.length) || 0})`}
+                        key={item.type}
+                      >
+                        <Table
+                          bordered
+                          rowKey="ID"
+                          columns={this.columns}
+                          loading={loading}
+                          dataSource={num}
+                          pagination={false}
+                        />
+                      </TabPane>
+                    );
+                  })}
+            </Tabs>
+          ) : (
+            <Table
+              bordered
+              rowKey="ID"
+              columns={this.columns}
+              loading={loading}
+              dataSource={
+                list &&
+                list.Files &&
+                list.Files.filter((item) => item.Type == fileType)
+              }
+              pagination={false}
+              style={{ paddingTop: 20 }}
+            />
+          )}
+        </Card>
+      </PageContent>
+    );
+  }
+}
+const HOCIndex = connect(({ equipmentConstructionList, loading }) => {
+  return {
+    list: equipmentConstructionList.list,
+    folders: equipmentConstructionList.folders,
+    loading: loading.models.equipmentConstructionList,
+  };
+})(Index);
+
+export default connectUserModel('user', '@@initialState')(HOCIndex);

+ 387 - 0
src/pages/DeviceManager/EquipmentProcurementList.js

@@ -0,0 +1,387 @@
+import PageContent from '@/components/PageContent';
+import PreviewFile from '@/components/PreviewFile';
+import { subtypeDictionaries } from '@/utils/constants';
+import { GetTokenFromUrl, connectUserModel, getToken } from '@/utils/utils';
+import { connect } from '@umijs/max';
+import { Button, Card, Modal, Row, Table, Tabs, Upload, message } from 'antd';
+import dayjs from 'dayjs';
+import React, { Fragment } from 'react';
+import cardStyle from './Card.less';
+import styles from './index.less';
+
+const { TabPane } = Tabs;
+const { confirm } = Modal;
+
+const opsPermissionTabsMap = new Map([
+  ['合同资料', 'func-01-ops-DeviceList-00-00'],
+  ['监造资料', 'func-01-ops-DeviceList-01-00'],
+  ['出厂检验', 'func-01-ops-DeviceList-01-01'],
+  ['包装', 'func-01-ops-DeviceList-02-00'],
+  ['装箱单', 'func-01-ops-DeviceList-02-01'],
+  ['质量证书', 'func-01-ops-DeviceList-02-02'],
+  ['原产地证明', 'func-01-ops-DeviceList-02-03'],
+  ['安装手册', 'func-01-ops-DeviceList-02-04'],
+  ['到货签收单', 'func-01-ops-DeviceList-03-00'],
+  ['开箱报告', 'func-01-ops-DeviceList-03-01'],
+  ['随机资料', 'func-01-ops-DeviceList-03-02'],
+  ['安装指导资料', 'func-01-ops-DeviceList-04-00'],
+  ['安装过程资料', 'func-01-ops-DeviceList-04-01'],
+  ['调试指导资料', 'func-01-ops-DeviceList-05-00'],
+  ['单体调试记录表', 'func-01-ops-DeviceList-05-01'],
+  ['其它资料', 'func-01-ops-DeviceList-05-02'],
+]);
+
+const buildPermissionTabsMap = new Map([
+  ['合同资料', 'func-01-build-5-dev-00-00'],
+  ['监造资料', 'func-01-build-5-dev-01-00'],
+  ['出厂检验', 'func-01-build-5-dev-01-01'],
+  ['包装', 'func-01-build-5-dev-02-00'],
+  ['装箱单', 'func-01-build-5-dev-02-01'],
+  ['质量证书', 'func-01-build-5-dev-02-02'],
+  ['原产地证明', 'func-01-build-5-dev-02-03'],
+  ['安装手册', 'func-01-build-5-dev-02-04'],
+  ['到货签收单', 'func-01-build-5-dev-03-00'],
+  ['开箱报告', 'func-01-build-5-dev-03-01'],
+  ['随机资料', 'func-01-build-5-dev-03-02'],
+  ['安装指导资料', 'func-01-build-5-dev-04-00'],
+  ['安装过程资料', 'func-01-build-5-dev-04-01'],
+  ['调试指导资料', 'func-01-build-5-dev-05-00'],
+  ['单体调试记录表', 'func-01-build-5-dev-05-01'],
+  ['其它资料', 'func-01-build-5-dev-05-02'],
+]);
+
+// @connect(({ equipmentProcurementList, loading, user }) => ({
+//   list: equipmentProcurementList.list,
+//   folders: equipmentProcurementList.folders,
+//   loading: loading.models.equipmentProcurementList,
+//   permission: user.currentUser.Permission,
+// }))
+class Index extends React.Component {
+  constructor(props) {
+    super(props);
+    const { fileType, list } = props;
+    this.state = {
+      typeNum:
+        subtypeDictionaries[fileType] && subtypeDictionaries[fileType][0].type,
+      subtypeMap: [],
+      list,
+    };
+  }
+
+  componentDidMount() {
+    console.log(this.props);
+
+    const { dispatch } = this.props;
+    const { billId, projectId, deviceCode, fileType, ops } = this.props;
+    if (ops == '1') {
+      dispatch({
+        type: 'equipmentProcurementList/getOpsFileType',
+        projectId,
+        fileType,
+        callback: (res) => {
+          let arr = [];
+          res &&
+            res.forEach((folder) => {
+              subtypeDictionaries[fileType].forEach((element) => {
+                if (
+                  folder.Type != 0 &&
+                  folder.Type == fileType &&
+                  element.type == folder.SubType
+                )
+                  arr.push(element);
+              });
+            });
+          // console.log(arr);
+          this.setState({
+            subtypeMap: arr,
+            typeNum: arr[0].type || this.state.typeNum,
+          });
+        },
+      });
+    }
+
+    dispatch({
+      type: 'equipmentProcurementList/getFileList',
+      payload: {
+        billId: 0,
+        deviceCode,
+        fileType,
+        projectId,
+        ops: ops == '1' ? 1 : undefined,
+      },
+    });
+  }
+
+  componentWillReceiveProps(nextProps, prevState) {
+    const { list } = nextProps;
+    this.setState({
+      list,
+    });
+  }
+
+  OnDeleteFile = (fileId) => {
+    const { dispatch } = this.props;
+    const {
+      match: {
+        params: { billId, deviceCode, fileType, projectId, ops },
+      },
+    } = this.props;
+    confirm({
+      title: '提醒',
+      content: '确认删除该文件,删除后无法复原',
+      okText: '确认',
+      cancelText: '取消',
+      onOk() {
+        dispatch({
+          type: `equipmentProcurementList/removeFile`,
+          payload: {
+            FileId: fileId,
+            billId: 0,
+            deviceCode,
+            fileType,
+            projectId,
+            ops: ops == '1' ? 1 : undefined,
+          },
+        });
+      },
+    });
+  };
+
+  OnDeletePurchaseBillFile = (fileId) => {
+    const {
+      dispatch,
+      match: {
+        params: { billId },
+      },
+    } = this.props;
+    dispatch({
+      type: 'purchaseBill/removeById',
+      payload: {
+        // ID: billId,
+        ID: 0,
+        FileId: fileId,
+      },
+    });
+  };
+
+  columns = [
+    {
+      title: '文件名称',
+      dataIndex: 'Name',
+      render: (text, item) => <PreviewFile name={item.Name} src={item.Url} />,
+    },
+    {
+      title: '上传人',
+      dataIndex: 'Creator',
+      render: (text) => {
+        return text && text.CName;
+      },
+    },
+    {
+      title: '上传时间',
+      dataIndex: 'CreatedTime',
+      render: (text) => {
+        return text ? dayjs(text).format('YYYY年MM月DD日  HH:mm:ss') : null;
+      },
+    },
+    {
+      title: '操作',
+      width: '20%',
+      render: (record) => (
+        <Fragment>
+          <a
+            style={{ color: '#7BFFFB' }}
+            onClick={() => this.checkFile(record)}
+          >
+            下载
+          </a>
+          {this.showJurisdiction('func-01-build-5-1-06') && (
+            <>
+              &nbsp;&nbsp;
+              <a
+                style={{ color: '#7BFFFB' }}
+                onClick={() => this.OnDeleteFile(record.ID)}
+              >
+                删除
+              </a>
+            </>
+          )}
+        </Fragment>
+      ),
+    },
+  ];
+
+  checkFile = (record) => {
+    if (window.InvokeUnityFileOpener) {
+      window.InvokeUnityFileOpener(record.Url);
+    } else {
+      window.location.href = `${record.Url}`;
+    }
+  };
+
+  showJurisdiction = (item) => {
+    if (item === undefined) {
+      return false;
+    }
+    return this.props.user?.Permission[item];
+  };
+
+  callback = (key) => {
+    this.setState({
+      typeNum: key,
+    });
+  };
+
+  GetOpsTabs = (subtype, fileType, folders) => {
+    const { list, loading } = this.props;
+    const { subtypeMap } = this.state;
+    const typeNum = this.state.typeNum || -1;
+    // const data = list && list.BillFiles && list.BillFiles.filter(each => each.Subtype == typeNum);
+    return subtypeMap.map((item) => {
+      if (!this.showJurisdiction(opsPermissionTabsMap.get(item.value)))
+        return null;
+      const num =
+        list &&
+        list.BillFiles &&
+        list.BillFiles.filter((each) => each.Subtype == item.type);
+      return (
+        <TabPane
+          tab={`${item.value} (${(num && num.length) || 0})`}
+          key={item.type}
+        >
+          <Table
+            bordered
+            rowKey="ID"
+            columns={this.columns}
+            loading={loading}
+            dataSource={num}
+            pagination={false}
+          />
+        </TabPane>
+      );
+    });
+  };
+
+  render() {
+    console.log('=--------', this.props);
+    const { loading } = this.props;
+    const { dispatch, billId, deviceCode, fileType, projectId, ops } =
+      this.props;
+    const { list, folders } = this.props;
+
+    const token = getToken() || GetTokenFromUrl();
+    const typeNum = this.state.typeNum || -1;
+    // const data = list && list.BillFiles && list.BillFiles.filter(each => each.Subtype == typeNum);
+    const uploadProps = {
+      name: 'files',
+      action: `/api/v1/purchase_bill/device_file/0/${deviceCode}/${fileType}/${typeNum}?projectId=${projectId}&${
+        ops == '1' ? 'ops=1' : undefined
+      }`,
+      // action: `/api/v1/purchase_bill/device_file/${billId}/${deviceCode}/${fileType}/${typeNum}?projectId=${projectId}`,
+      showUploadList: false,
+      headers: {
+        'JWT-TOKEN': token,
+      },
+      onChange(info) {
+        if (info.file.status !== 'uploading') {
+          // console.log(info.file, info.fileList);
+        }
+        if (info.file.status === 'done') {
+          var res = info.file.response;
+          if (res.code !== 200) return message.error(res.msg);
+          message.success(`${info.file.name} 文件上传成功`);
+          dispatch({
+            type: 'equipmentProcurementList/getFileList',
+            payload: {
+              billId: 0,
+              deviceCode,
+              fileType,
+              projectId,
+              ops: ops == '1' ? 1 : undefined,
+            },
+          });
+        } else if (info.file.status === 'error') {
+          message.error(`${info.file.name} 文件上传失败`);
+        }
+      },
+    };
+    return (
+      <PageContent>
+        <Card bordered={false} className={cardStyle.CardCon}>
+          {(this.showJurisdiction('func-01-ops-1-5-06') ||
+            this.showJurisdiction('func-01-build-5-1-05')) && (
+            <Row>
+              <Upload {...uploadProps} className={styles.upload}>
+                <Button type="primary">上传</Button>
+              </Upload>
+            </Row>
+          )}
+          {subtypeDictionaries[fileType] ? (
+            <Tabs
+              animated={false}
+              // activeKey={typeNum + ''}
+              onChange={this.callback}
+              style={{ paddingTop: -20 }}
+            >
+              {ops == '1'
+                ? this.GetOpsTabs(subtypeDictionaries, fileType, folders)
+                : subtypeDictionaries[fileType].map((item) => {
+                    if (
+                      !this.showJurisdiction(
+                        buildPermissionTabsMap.get(item.value),
+                      )
+                    )
+                      return null;
+
+                    const num =
+                      list &&
+                      list.BillFiles &&
+                      list.BillFiles.filter(
+                        (each) => each.Subtype == item.type,
+                      );
+                    return (
+                      <TabPane
+                        tab={`${item.value} (${(num && num.length) || 0})`}
+                        key={item.type}
+                      >
+                        <Table
+                          bordered
+                          rowKey="ID"
+                          columns={this.columns}
+                          loading={loading}
+                          dataSource={num}
+                          pagination={false}
+                        />
+                      </TabPane>
+                    );
+                  })}
+            </Tabs>
+          ) : (
+            <Table
+              bordered
+              rowKey="ID"
+              columns={this.columns}
+              loading={loading}
+              dataSource={
+                list &&
+                list.BillFiles &&
+                list.BillFiles.filter((item) => item.Type == fileType)
+              }
+              pagination={false}
+              style={{ paddingTop: 20 }}
+            />
+          )}
+        </Card>
+      </PageContent>
+    );
+  }
+}
+const HOCIndex = connect(({ equipmentProcurementList, loading }) => {
+  return {
+    list: equipmentProcurementList.list,
+    folders: equipmentProcurementList.folders,
+    loading: loading.models.equipmentProcurementList,
+  };
+})(Index);
+
+export default connectUserModel('user', '@@initialState')(HOCIndex);

+ 17 - 0
src/pages/DeviceManager/detail.less

@@ -0,0 +1,17 @@
+.detailBox {
+  display: flex;
+  flex-wrap: wrap;
+  margin-top: 20px;
+  span {
+    width: 50%;
+    line-height: 1.8;
+    font-size: 0.3rem;
+    display: flex;
+  }
+  label {
+    display: block;
+    width: 2.5rem;
+    text-align: right;
+    padding-right: 0.4rem;
+  }
+}

+ 142 - 0
src/pages/DeviceManager/deviceMaintainDetail.js

@@ -0,0 +1,142 @@
+import PageContent from '@/components/PageContent';
+import PageTitle from '@/components/PageTitle';
+import { Table } from 'antd';
+import dayjs from 'dayjs';
+import ReactZmage from 'react-zmage';
+import styles from './detail.less';
+
+const {
+  DeviceCode,
+  DeviceName,
+  MaintainTime,
+  EvaluationScore,
+  Operators,
+  RustRemoval,
+  AntiCorrosive,
+  MaterialConsumption,
+  Fasten,
+  Clean,
+  Lubrication,
+  Check,
+  Note,
+  Files = [],
+} = JSON.parse(localStorage.maintain || '{}');
+
+export default function DeviceMaintainDetail() {
+  const columns = [
+    {
+      title: '保养资料',
+      dataIndex: 'Name',
+      render: (text, item) => {
+        return (
+          <ReactZmage
+            controller={{
+              // 关闭按钮
+              close: true,
+              // 缩放按钮
+              zoom: false,
+              // 下载按钮
+              download: false,
+              // 翻页按钮
+              flip: false,
+              // 多页指示
+              pagination: false,
+            }}
+            backdrop="rgba(255,255,255,0.5)"
+            style={{ width: '3.5rem' }}
+            src={item.Url}
+          />
+        );
+      },
+    },
+    {
+      title: '创建时间',
+      dataIndex: 'CreatedTime',
+      render: (text) => {
+        return text ? dayjs(text).format('YYYY年MM月DD日  HH:mm:ss') : null;
+      },
+    },
+  ];
+
+  function getUser(params) {
+    let arr = [];
+    if (!params) {
+      return;
+    } else {
+      return (arr = params
+        .map((item) => {
+          return item.Operator?.CName;
+        })
+        .join(','));
+    }
+  }
+
+  return (
+    <PageContent style={{ background: '#fff', borderRadius: 12 }}>
+      <PageTitle>设备保养</PageTitle>
+      <div className={styles.detailBox}>
+        <span>
+          <label>设备位号:</label>
+          {DeviceCode}
+        </span>
+        <span>
+          <label>设备名称:</label>
+          {DeviceName}
+        </span>
+        <span>
+          <label>保养人:</label>
+          {getUser(Operators)}
+        </span>
+        <span>
+          <label>物料消耗:</label>
+          {MaterialConsumption}
+        </span>
+        <span>
+          <label>保养日期:</label>
+          {MaintainTime}
+        </span>
+        <span>
+          <label>是否润滑加油:</label>
+          {Lubrication === 1 ? '是' : '否'}
+        </span>
+        <span>
+          <label>是否拆检:</label>
+          {Check === 1 ? '是' : '否'}
+        </span>
+        <span>
+          <label>是否清洁:</label>
+          {Clean === 1 ? '是' : '否'}
+        </span>
+        <span>
+          <label>是否紧固:</label>
+          {Fasten === 1 ? '是' : '否'}
+        </span>
+        <span>
+          <label>是否除锈:</label>
+          {RustRemoval === 1 ? '是' : '否'}
+        </span>
+        <span>
+          <label>是否防腐:</label>
+          {AntiCorrosive === 1 ? '是' : '否'}
+        </span>
+
+        <span>
+          <label>评估分数:</label>
+          {EvaluationScore * 100}
+        </span>
+        <span>
+          <label>保养备注:</label>
+          {Note?.split('|').map((item) => (
+            <div>{item}</div>
+          ))}
+        </span>
+      </div>
+      <Table
+        style={{ marginTop: '0.1rem' }}
+        dataSource={Files}
+        columns={columns}
+        pagination={false}
+      />
+    </PageContent>
+  );
+}

+ 179 - 0
src/pages/DeviceManager/deviceRepairDetail.js

@@ -0,0 +1,179 @@
+import PageContent from '@/components/PageContent';
+import PageTitle from '@/components/PageTitle';
+import { useParams } from '@umijs/max';
+import { Table } from 'antd';
+import dayjs from 'dayjs';
+import ReactZmage from 'react-zmage';
+import styles from './detail.less';
+
+const {
+  DeviceCode,
+  DeviceName,
+  Reason,
+  PlanTime,
+  RepairType,
+  IsPlan,
+  DifficultyLevel,
+  AcceptanceStatus,
+  EvaluationScore,
+  Operators,
+  RepairTime,
+  Desc,
+  MaterialConsumption,
+  AcceptorUser,
+  AcceptanceTime,
+  Level,
+  AcceptanceOpinion,
+  Note,
+  Files = [],
+} = JSON.parse(localStorage.repair || '{}');
+
+export default function DeviceMaintainDetail() {
+  const { projectId, type } = useParams();
+
+  const columns = [
+    {
+      title: type == 1 ? '维修资料' : '保养资料',
+      dataIndex: 'Name',
+      render: (text, item) => {
+        return (
+          <ReactZmage
+            controller={{
+              // 关闭按钮
+              close: true,
+              // 缩放按钮
+              zoom: false,
+              // 下载按钮
+              download: false,
+              // 翻页按钮
+              flip: false,
+              // 多页指示
+              pagination: false,
+            }}
+            backdrop="rgba(255,255,255,0.5)"
+            style={{ width: '3.5rem' }}
+            src={item.Url}
+          />
+        );
+      },
+    },
+    {
+      title: '创建时间',
+      dataIndex: 'CreatedTime',
+      render: (text) => {
+        return text ? dayjs(text).format('YYYY年MM月DD日  HH:mm:ss') : null;
+      },
+    },
+  ];
+
+  function getUser(params) {
+    let arr = [];
+    if (!params) {
+      return;
+    } else {
+      return (arr = params
+        .map((item) => {
+          return item.Operator?.CName;
+        })
+        .join(','));
+    }
+  }
+  const getStatus = (time, acStatus) => {
+    if (time && acStatus == 0) return '不通过';
+    if (time && acStatus == 2) return '通过';
+    return '';
+  };
+
+  return (
+    <PageContent style={{ background: '#fff', borderRadius: 12 }}>
+      <PageTitle>设备维修</PageTitle>
+      <div className={styles.detailBox}>
+        <span>
+          <label>设备位号:</label>
+          {DeviceCode}
+        </span>
+        <span>
+          <label>维修人:</label>
+          {getUser(Operators)}
+        </span>
+        <span>
+          <label>设备名称:</label>
+          {DeviceName}
+        </span>
+        <span>
+          <label>维修日期:</label>
+          {RepairTime}
+        </span>
+        <span>
+          <label>维修原因:</label>
+          {Reason}
+        </span>
+        <span>
+          <label>维修内容简述:</label>
+          {Desc}
+        </span>
+
+        <span>
+          <label>计划日期:</label>
+          {PlanTime}
+        </span>
+        <span>
+          <label>物料消耗:</label>
+          {MaterialConsumption}
+        </span>
+
+        <span>
+          <label>维修方式:</label>
+          {RepairType}
+        </span>
+        <span>
+          <label>验收人:</label>
+          {AcceptorUser?.CName}
+        </span>
+
+        <span>
+          <label>是否计划内:</label>
+          {String(IsPlan) == 0 ? '否' : '是'}
+        </span>
+        <span>
+          <label>验收状态:</label>
+          {getStatus(AcceptanceTime, AcceptanceStatus)}
+        </span>
+
+        <span>
+          <label>难度级别:</label>
+          {DifficultyLevel}
+        </span>
+        <span>
+          <label>级别:</label>
+          {Level}
+        </span>
+        <span>
+          <label>维修状态:</label>
+          {AcceptanceStatus}
+        </span>
+        <span>
+          <label>验收意见:</label>
+          {AcceptanceOpinion}
+        </span>
+
+        <span>
+          <label>评估分数:</label>
+          {EvaluationScore * 100}
+        </span>
+        <span>
+          <label>保养备注:</label>
+          {Note?.split('|').map((item) => (
+            <div>{item}</div>
+          ))}
+        </span>
+      </div>
+      <Table
+        style={{ marginTop: '0.1rem' }}
+        dataSource={Files}
+        columns={columns}
+        pagination={false}
+      />
+    </PageContent>
+  );
+}

+ 15 - 98
src/pages/DeviceManager/index.js

@@ -1,22 +1,19 @@
 import PageContent from '@/components/PageContent';
 import TabsContent from '@/components/TabsContent';
-import { queryMainChartList } from '@/services/StorageManagement';
 import {
   queryDeviceList,
   queryMaintainRecord,
   queryRepairRecord,
 } from '@/services/device';
 import { UnityAction } from '@/utils/utils';
-import { RightOutlined } from '@ant-design/icons';
-import { useNavigate, useParams, useRequest } from '@umijs/max';
+import { useParams, useRequest } from '@umijs/max';
 import { Collapse, List, Space, Spin, Table } from 'antd';
 import dayjs from 'dayjs';
 import { useEffect, useMemo, useState } from 'react';
 import styles from './index.less';
+import StorageOverview from './storage';
 
 const deviceIcon = require('@/assets/deviceManager/deviceIcon.png');
-const spareIcon = require('@/assets/deviceManager/spareIcon.png');
-// const chartIcon = require('@/assets/deviceManager/chartIcon.png');
 
 export const PageType = {
   in: 0, //入库管理
@@ -56,7 +53,7 @@ const DeviceManager = () => {
           {
             label: `备品管理`,
             key: '2',
-            children: <SparePart projectId={projectId} />,
+            children: <StorageOverview projectId={projectId} />, //<SparePart projectId={projectId} />,
           },
         ]}
       />
@@ -245,13 +242,6 @@ const Device = ({ projectId }) => {
   return (
     <div className={`content-tab ${styles.sparePart}`}>
       <Spin spinning={loadingDevice}>
-        {/* <div className={styles.titleContent}>
-          <img className={styles.img} src={deviceIcon} />
-          <div>
-            <div className="value-number">{allData?.total}</div>
-            <div className={styles.text}>设备总数(个)</div>
-          </div>
-        </div> */}
         <div className={styles.top}>
           <div className={styles.left}>
             <img
@@ -301,6 +291,12 @@ const Device = ({ projectId }) => {
           dataSource={repairData?.filter((item) => item.DeviceCode)}
           columns={columnsRepair}
           pagination={false}
+          onRow={(record, index) => ({
+            onClick: () => {
+              localStorage.repair = JSON.stringify(record)
+              UnityAction.sendMsg("RepairDetail")
+            },
+          })}
         />
       )}
       {type == 2 && (
@@ -310,95 +306,16 @@ const Device = ({ projectId }) => {
           dataSource={maintainData?.filter((item) => item.DeviceCode)}
           columns={columns}
           pagination={false}
+          onRow={(record, index) => ({
+            onClick: () => {
+              localStorage.maintain = JSON.stringify(record)
+              UnityAction.sendMsg("MaintainDetail")
+            },
+          })}
         />
       )}
     </div>
   );
 };
-const SparePart = ({ projectId }) => {
-  const navigate = useNavigate();
-  const year = dayjs().format('YYYY');
-  const currentMonth = dayjs().format('MM');
-  const defaultParams = {
-    project_id: Number(projectId),
-    month: Number(currentMonth),
-    year: Number(year),
-  };
-  //请求备品列表
-  const { data, loading } = useRequest(() => queryMainChartList(defaultParams));
-
-  const changePage = (type) => {
-    navigate(`/device/detail/${projectId}/${type}`);
-    // 设置默认显示tab为备品管理
-    localStorage.deviceTab = '2';
-  };
-
-  const handleTotalPage = () => {
-    navigate(`/device/storage/${projectId}`);
-    // 设置默认显示tab为备品管理
-    localStorage.deviceTab = '2';
-  };
-
-  const list = useMemo(() => {
-    const result = [
-      {
-        title: '基础库存',
-        type: PageType.base,
-      },
-      {
-        title: '入库数量',
-        type: PageType.in,
-        num: data?.in_amount || 0,
-      },
-      {
-        title: '出库数量',
-        type: PageType.out,
-        num: data?.out_amount || 0,
-      },
-      {
-        title: '报废数量',
-        type: PageType.scrap,
-        num: data?.scrap_amount || 0,
-      },
-      {
-        title: '报警数量',
-        type: PageType.warning,
-        num: data?.warning_stat || 0,
-      },
-    ];
-    return result;
-  }, [data]);
-  return (
-    <Spin spinning={loading}>
-      <div className="content-tab">
-        <Space direction="vertical" size={16} className={styles.sparePart}>
-          <div className={styles.titleContent}>
-            <img className={styles.img} src={spareIcon} />
-            <div>
-              <div className="value-number">{data?.on_amount || 0}</div>
-              <div className={styles.text}>在库数量(个)</div>
-            </div>
-            <div
-              onClick={handleTotalPage}
-              className={styles.iconFundFilled}
-            ></div>
-          </div>
-          {list.map((item) => (
-            <div
-              className={`card-box ${styles.cardItem}`}
-              onClick={() => changePage(item.type)}
-            >
-              <span className={styles.spareText}>{item.title}</span>
-              <Space size={30}>
-                {/* <span className={styles.spareText}>{item.num}个</span> */}
-                <RightOutlined style={{ fontSize: '0.28rem' }} />
-              </Space>
-            </div>
-          ))}
-        </Space>
-      </div>
-    </Spin>
-  );
-};
 
 export default DeviceManager;

+ 31 - 0
src/pages/DeviceManager/index.less

@@ -92,6 +92,21 @@
   padding: 0.24rem;
   margin-bottom: 0.2rem;
 }
+.tipContent {
+  .itemContent;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  font-size: 0.28rem;
+  font-family: Source Han Sans, Source Han Sans;
+  color: #615d5d;
+}
+.tipContent2 {
+  .itemContent;
+  font-size: 0.28rem;
+  font-family: Source Han San;
+  color: #615d5d;
+}
 
 .lineContent {
   display: flex;
@@ -176,3 +191,19 @@
 .main {
   padding: 0.2rem;
 }
+.upload {
+  :global {
+    .ant-upload {
+      float: right;
+    }
+  }
+}
+
+.imgCon {
+  width: 100%;
+  padding-top: 20px;
+  img {
+    display: block;
+    width: 100%;
+  }
+}

+ 71 - 0
src/pages/DeviceManager/models/equipmentConstructionList.js

@@ -0,0 +1,71 @@
+import {
+  getConFileList,
+  getOpsFileType,
+  removeProjectFile,
+} from '@/services/device';
+import { message } from 'antd';
+
+export default {
+  namespace: 'equipmentConstructionList',
+  state: {
+    list: [],
+    folders: [],
+  },
+
+  effects: {
+    *getFileList({ payload }, { call, put }) {
+      const response = yield call(getConFileList, payload);
+      if (response) {
+        yield put({
+          type: 'save',
+          payload: { list: response.data },
+        });
+      }
+    },
+    *getOpsFileType({ projectId, fileType, callback }, { call, put }) {
+      try {
+        const response = yield call(getOpsFileType, projectId);
+        // console.log(response.data);
+        if (response) {
+          yield put({
+            type: 'save',
+            payload: {
+              folders: response.data.filter((each) => each.Type == fileType),
+            },
+          });
+          // console.log(response.data.filter(each => each.Type == fileType));
+          callback &&
+            callback(response.data.filter((each) => each.Type == fileType));
+        }
+      } catch (error) {
+        console.error(error);
+      }
+    },
+
+    *removeProjectFile({ payload, callback }, { call, put }) {
+      const response = yield call(removeProjectFile, payload);
+      if (response) {
+        if (callback) callback();
+        message.success('删除成功');
+        yield put({
+          type: 'getFileList',
+          payload: {
+            projectId: payload.projectId,
+            code: payload.code,
+            fileType: payload.fileType,
+            ops: payload.ops == '1' ? 1 : undefined,
+          },
+        });
+      }
+    },
+  },
+
+  reducers: {
+    save(state, action) {
+      return {
+        ...state,
+        ...action.payload,
+      };
+    },
+  },
+};

+ 76 - 0
src/pages/DeviceManager/models/equipmentProcurementList.js

@@ -0,0 +1,76 @@
+import {
+  getFileList,
+  getOpsFileType,
+  removePurchaseRequestBillFileById,
+} from '@/services/device';
+import { message } from 'antd';
+
+export default {
+  namespace: 'equipmentProcurementList',
+  state: {
+    list: {},
+    folders: [],
+  },
+
+  effects: {
+    *getFileList({ payload }, { call, put }) {
+      try {
+        const response = yield call(getFileList, payload);
+        if (response) {
+          yield put({
+            type: 'save',
+            payload: { list: response.data },
+          });
+        }
+      } catch (error) {
+        console.error(error);
+      }
+    },
+
+    *getOpsFileType({ projectId, fileType, callback }, { call, put }) {
+      try {
+        const response = yield call(getOpsFileType, projectId);
+        // console.log(response.data);
+        if (response) {
+          yield put({
+            type: 'save',
+            payload: {
+              folders: response.data.filter((each) => each.Type == fileType),
+            },
+          });
+          // console.log(response.data.filter(each => each.Type == fileType));
+          callback &&
+            callback(response.data.filter((each) => each.Type == fileType));
+        }
+      } catch (error) {
+        console.error(error);
+      }
+    },
+
+    *removeFile({ payload }, { call, put }) {
+      const response = yield call(removePurchaseRequestBillFileById, payload);
+      if (response) {
+        message.success('删除成功');
+        yield put({
+          type: 'getFileList',
+          payload: {
+            billId: payload.billId,
+            deviceCode: payload.deviceCode,
+            fileType: payload.fileType,
+            projectId: payload.projectId,
+            ops: payload.ops == '1' ? 1 : undefined,
+          },
+        });
+      }
+    },
+  },
+
+  reducers: {
+    save(state, action) {
+      return {
+        ...state,
+        ...action.payload,
+      };
+    },
+  },
+};

+ 103 - 0
src/pages/DeviceManager/sparePart.js

@@ -0,0 +1,103 @@
+import PageContent from '@/components/PageContent';
+import PageTitle from '@/components/PageTitle';
+import { queryMainChartList } from '@/services/StorageManagement';
+import { RightOutlined } from '@ant-design/icons';
+import { useNavigate, useParams, useRequest } from '@umijs/max';
+import { Space, Spin } from 'antd';
+import dayjs from 'dayjs';
+import { useMemo } from 'react';
+import { PageType } from './index';
+import styles from './index.less';
+const spareIcon = require('@/assets/deviceManager/spareIcon.png');
+
+const SparePart = () => {
+  const { projectId } = useParams();
+  const navigate = useNavigate();
+  const year = dayjs().format('YYYY');
+  const currentMonth = dayjs().format('MM');
+  const defaultParams = {
+    project_id: Number(projectId),
+    month: Number(currentMonth),
+    year: Number(year),
+  };
+  //请求备品列表
+  const { data, loading } = useRequest(() => queryMainChartList(defaultParams));
+
+  const changePage = (type) => {
+    navigate(`/device/detail/${projectId}/${type}`);
+    // 设置默认显示tab为备品管理
+    localStorage.deviceTab = '2';
+  };
+
+  const handleTotalPage = () => {
+    navigate(`/device/storage/${projectId}`);
+    // 设置默认显示tab为备品管理
+    localStorage.deviceTab = '2';
+  };
+
+  const list = useMemo(() => {
+    const result = [
+      {
+        title: '基础库存',
+        type: PageType.base,
+      },
+      {
+        title: '入库数量',
+        type: PageType.in,
+        num: data?.in_amount || 0,
+      },
+      {
+        title: '出库数量',
+        type: PageType.out,
+        num: data?.out_amount || 0,
+      },
+      {
+        title: '报废数量',
+        type: PageType.scrap,
+        num: data?.scrap_amount || 0,
+      },
+      {
+        title: '报警数量',
+        type: PageType.warning,
+        num: data?.warning_stat || 0,
+      },
+    ];
+    return result;
+  }, [data]);
+  return (
+    <PageContent closeable={false}>
+      <PageTitle returnable>备品详情</PageTitle>
+      <Spin spinning={loading}>
+        <div className="content-tab">
+          <Space direction="vertical" size={16} className={styles.sparePart}>
+            <div className={styles.titleContent}>
+              <img className={styles.img} src={spareIcon} />
+              <div>
+                <div className="value-number">{data?.on_amount || 0}</div>
+                <div className={styles.text}>在库数量(个)</div>
+              </div>
+              {/* <div
+                onClick={handleTotalPage}
+                className={styles.iconFundFilled}
+              ></div> */}
+            </div>
+            {list.map((item) => (
+              <div
+                className={`card-box ${styles.cardItem}`}
+                onClick={() => changePage(item.type)}
+              >
+                <span className={styles.spareText}>{item.title}</span>
+                <Space size={30}>
+                  {/* <span className={styles.spareText}>{item.num}个</span> */}
+                  <RightOutlined style={{ fontSize: '0.28rem' }} />
+                </Space>
+              </div>
+            ))}
+          </Space>
+        </div>
+      </Spin>
+    </PageContent>
+  );
+};
+
+export default SparePart;

+ 103 - 76
src/pages/DeviceManager/storage.js

@@ -2,17 +2,18 @@ import BarChartModule from '@/components/ManagementPage/BarChartModule';
 import PieChartModule from '@/components/ManagementPage/PieChartModule';
 import RadarChartModule from '@/components/ManagementPage/RadarChartModule';
 import ModuleTitle from '@/components/ManagementPage/moduleTitle';
-import PageContent from '@/components/PageContent';
-import PageTitle from '@/components/PageTitle';
-import { queryMainChartList } from '@/services/StorageManagement';
-import { useParams, useRequest } from '@umijs/max';
-import { Empty, Spin } from 'antd';
+import {
+  queryMainChartList,
+  queryStoreGetWarning,
+} from '@/services/StorageManagement';
+import { useNavigate, useRequest } from '@umijs/max';
+import { Button, Empty, Spin } from 'antd';
 import dayjs from 'dayjs';
 import { useRef } from 'react';
 import styles from './index.less';
 
-const StorageOverview = () => {
-  const { projectId } = useParams();
+const StorageOverview = ({ projectId }) => {
+  const navigate = useNavigate();
   const yearRef = useRef(Number(dayjs().format('YYYY')));
   const monthRef = useRef(0);
   const statistics = [
@@ -38,6 +39,18 @@ const StorageOverview = () => {
     },
   ];
 
+  const { data: warningData, loading: loadingWarn } = useRequest(
+    queryStoreGetWarning,
+    {
+      defaultParams: [{ project_id: projectId * 1 }],
+      formatResult: (res) => {
+        const data = res.data;
+        const { check_notice, inventory_notice } = res.data;
+        return check_notice + inventory_notice;
+      },
+    },
+  );
+
   const { data, loading } = useRequest(
     () =>
       queryMainChartList({
@@ -121,78 +134,92 @@ const StorageOverview = () => {
     },
   );
 
+  const goDetail = () => {
+    navigate(`/device/spare-part/${projectId}`);
+    // 设置默认显示tab为备品管理
+    localStorage.deviceTab = '2';
+  };
+
   return (
-    <PageContent closeable={false}>
-      <PageTitle children="备品统计" returnable />
-      <div className="content-title">
-        <div className={styles.main}>
-          <Spin spinning={loading}>
-            <div className={`card-box ${styles.itemContent}`}>
-              <ModuleTitle title="物料种类库存占比" />
-              <div style={{ height: '3.3rem' }}>
-                {data?.pieData && <PieChartModule data={data.pieData} />}
-              </div>
-            </div>
-            <div className={`card-box ${styles.itemContent}`}>
-              <ModuleTitle title="物料库存排名前十统计" />
-              <ul
-                style={{
-                  height: '3.3rem',
-                  display: 'flex',
-                  flexDirection: 'column',
-                }}
-              >
-                {data?.kind && data.kind.length != 0 ? (
-                  data.kind
-                    ?.sort((a, b) => b.value - a.value)
-                    .map((item) => {
-                      return (
-                        <li
-                          className={styles.li}
-                          key={`kind_${item.item}`}
-                          style={{ flexGrow: 1 }}
-                        >
-                          <div
-                            style={{
-                              width: '0.8rem',
-                              textAlign: 'right',
-                              fontSize: '0.18rem',
-                              whiteSpace: 'nowrap',
-                            }}
-                          >
-                            {item.item}
-                          </div>
-                          <div
-                            className={styles.line}
-                            style={{
-                              width: `${(70 * item.value) / data?.maxKind}%`,
-                            }}
-                          />
-                          {item.value}
-                        </li>
-                      );
-                    })
-                ) : (
-                  <Empty />
-                )}
-              </ul>
-            </div>
-            <div className={`card-box ${styles.itemContent}`}>
-              <ModuleTitle title="物料报废统计" />
-              <div style={{ height: '4.4rem' }}>
-                {data?.radarData && <RadarChartModule {...data.radarData} />}
-              </div>
-            </div>
-            <div className={`card-box ${styles.itemContent}`}>
-              <ModuleTitle title="出入库统计" />
-              <div style={{ height: '4rem' }}>
-                {data?.barData && <BarChartModule {...data.barData} />}
-              </div>
-            </div>
-          </Spin>
+    <Spin className="content-title" spinning={loading}>
+      <div className={`card-box  ${styles.tipContent2}`}>
+        <div
+          style={{
+            display: 'flex',
+            justifyContent: 'space-between',
+            alignItems: 'center',
+          }}
+        >
+          <ModuleTitle title="备品报告:" />
+          <Button type="primary" onClick={goDetail}>
+            详情
+          </Button>
+        </div>
+        <div style={{ marginTop: '0.2rem' }}>{warningData}</div>
+      </div>
+      <div className={`card-box ${styles.itemContent}`}>
+        <ModuleTitle title="物料种类库存占比" />
+        <div style={{ height: '3.3rem' }}>
+          {data?.pieData && <PieChartModule data={data.pieData} />}
+        </div>
+      </div>
+      <div className={`card-box ${styles.itemContent}`}>
+        <ModuleTitle title="物料库存排名前十统计" />
+        <ul
+          style={{
+            height: '3.3rem',
+            display: 'flex',
+            flexDirection: 'column',
+          }}
+        >
+          {data?.kind && data.kind.length != 0 ? (
+            data.kind
+              ?.sort((a, b) => b.value - a.value)
+              .map((item) => {
+                return (
+                  <li
+                    className={styles.li}
+                    key={`kind_${item.item}`}
+                    style={{ flexGrow: 1 }}
+                  >
+                    <div
+                      style={{
+                        width: '0.8rem',
+                        textAlign: 'right',
+                        fontSize: '0.18rem',
+                        whiteSpace: 'nowrap',
+                      }}
+                    >
+                      {item.item}
+                    </div>
+                    <div
+                      className={styles.line}
+                      style={{
+                        width: `${(70 * item.value) / data?.maxKind}%`,
+                      }}
+                    />
+                    {item.value}
+                  </li>
+                );
+              })
+          ) : (
+            <Empty />
+          )}
+        </ul>
+      </div>
+      <div className={`card-box ${styles.itemContent}`}>
+        <ModuleTitle title="物料报废统计" />
+        <div style={{ height: '4.4rem' }}>
+          {data?.radarData && <RadarChartModule {...data.radarData} />}
+        </div>
+      </div>
+      <div className={`card-box ${styles.itemContent}`}>
+        <ModuleTitle title="出入库统计" />
+        <div style={{ height: '4rem' }}>
+          {data?.barData && <BarChartModule {...data.barData} />}
         </div>
       </div>
-    </PageContent>
+    </Spin>
   );
 };
 

+ 58 - 38
src/pages/EqSelfInspection/List/index.js

@@ -1,74 +1,94 @@
 import PageContent from '@/components/PageContent';
 import PageTitle from '@/components/PageTitle';
+import ScrollLoading from '@/components/ScrollLoading';
 import { GetTokenFromUrl, UnityAction } from '@/utils/utils';
 import { connect, history, useParams } from '@umijs/max';
 import dayjs from 'dayjs';
-import { useEffect } from 'react';
+import { useEffect, useState } from 'react';
 import styles from './index.less';
 
 function List(props) {
-  const { loading, list, processList = [], dispatch } = props;
-
+  const { loading, processList = [], dispatch } = props;
   const { projectId } = useParams();
+  const [list, setList] = useState([]);
+  const [curPagination, setCurPagination] = useState({
+    pageSize: 50,
+    currentPage: 1,
+  });
+
+  const defaultParam = {
+    projectId: projectId,
+    auto: 1,
+    startDate: dayjs().subtract(10, 'day').format('YYYY-MM-DD 00:00:00'),
+    endDate: dayjs().format('YYYY-MM-DD 23:59:59'),
+    pageSize: 50,
+    currentPage: 1,
+  };
 
   const goToDetail = (record) => {
     UnityAction.sendMsg('reportDetail', '');
     history.push(
       `/self-inspection/detail/${projectId}/${
         record.Id
-      }?JWT-TOKEN=${GetTokenFromUrl()}`,
+      }?JWT-TOKEN=${GetTokenFromUrl()}&eTime=${record.CreatedTime}`,
     );
   };
 
   useEffect(() => {
-    let params = {};
-    params.projectId = projectId;
-    params.auto = 1;
-    params.startDate = dayjs()
-      .subtract(10, 'day')
-      .format('YYYY-MM-DD 00:00:00');
-    params.endDate = dayjs().format('YYYY-MM-DD 23:59:59');
-    params.pageSize = 100;
-    params.currentPage = 1;
+    queryData(1);
+  }, []);
+
+  const queryData = (currentPage) => {
+    const params = { ...defaultParam, currentPage };
     dispatch({
       type: 'patrolArtificialRecord/queryPatrol',
       payload: params,
+      callback: (data) => {
+        setList([...list, ...data.list]);
+        setCurPagination(data.pagination);
+      },
     });
-  }, []);
+  };
   return (
     <PageContent closeable={false}>
       <PageTitle returnable>自检记录</PageTitle>
-      <div className={`content-title ${styles.list}`}>
-        {list.map((item) => (
-          <div className={styles.item} onClick={() => goToDetail(item)}>
-            {item.Status == 0 ? (
-              <div className={`${styles.status}`}>正常</div>
-            ) : (
-              <div className={`${styles.status} ${styles.statusError}`}>
-                异常
-              </div>
-            )}
-            <div className={styles.info}>
-              <div className={styles.time}>
-                <i></i>自检时间:{dayjs(item.CreatedTime).format('MM-DD HH:mm')}
-              </div>
-              <div className={styles.desc}>
-                <i></i>
-                发现异常
-                <span className={styles.number}>{item.Status}</span>
-                <span>项</span>
+      <ScrollLoading
+        loading={loading}
+        pagination={curPagination}
+        handleLoadData={queryData}
+      >
+        <div>
+          {list.map((item) => (
+            <div className={styles.item} onClick={() => goToDetail(item)}>
+              {item.Status == 0 ? (
+                <div className={`${styles.status}`}>正常</div>
+              ) : (
+                <div className={`${styles.status} ${styles.statusError}`}>
+                  异常
+                </div>
+              )}
+              <div className={styles.info}>
+                <div className={styles.time}>
+                  <i></i>自检时间:
+                  {dayjs(item.CreatedTime).format('MM-DD HH:mm')}
+                </div>
+                <div className={styles.desc}>
+                  <i></i>
+                  发现异常
+                  <span className={styles.number}>{item.Status}</span>
+                  <span>项</span>
+                </div>
               </div>
+              <div className={styles.btn}></div>
             </div>
-            <div className={styles.btn}></div>
-          </div>
-        ))}
-      </div>
+          ))}
+        </div>
+      </ScrollLoading>
     </PageContent>
   );
 }
 
 export default connect(({ patrolArtificialRecord, loading }) => ({
-  list: patrolArtificialRecord.list,
   routeInfoList: patrolArtificialRecord.routeInfoList,
   loading: loading.models.patrolArtificialRecord,
   processList: patrolArtificialRecord.processList,

+ 62 - 65
src/pages/EqSelfInspection/List/index.less

@@ -1,70 +1,67 @@
-.list {
-  margin-top: 0.4rem;
-  .item {
-    padding: 0.28rem 0.75rem 0.28rem 1.24rem;
-    display: flex;
-    justify-content: space-between;
-    margin-bottom: 0.18rem;
-    background: #ffffff;
-    box-shadow: 0rem 0rem 0.08rem 0.14rem rgba(0, 0, 0, 0.03);
-    border-radius: 0.12rem;
-    align-items: center;
-  }
-  .status {
-    width: 1.34rem;
-    height: 1.34rem;
-    font-size: 0.32rem;
-    font-weight: 400;
-    color: #000000;
-    text-align: center;
-    line-height: 1.34rem;
-    background: url('@/assets/circle-success.png') no-repeat center;
-    background-size: 100% 100%;
-  }
-  .statusError {
-    background-image: url('@/assets/circle-error.png');
-  }
-  .info {
-    flex: 1;
-    padding-left: 1rem;
-    i {
-      width: 0.14rem;
-      height: 0.14rem;
-      display: inline-block;
-      vertical-align: middle;
-      border-radius: 50%;
-      margin-right: 0.14rem;
-      background-color: rgba(74, 144, 226, 1);
-    }
+.item {
+  padding: 0.28rem 0.75rem 0.28rem 1.24rem;
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 0.18rem;
+  background: #ffffff;
+  box-shadow: 0rem 0rem 0.08rem 0.14rem rgba(0, 0, 0, 0.03);
+  border-radius: 0.12rem;
+  align-items: center;
+}
+.status {
+  width: 1.34rem;
+  height: 1.34rem;
+  font-size: 0.32rem;
+  font-weight: 400;
+  color: #000000;
+  text-align: center;
+  line-height: 1.34rem;
+  background: url('@/assets/circle-success.png') no-repeat center;
+  background-size: 100% 100%;
+}
+.statusError {
+  background-image: url('@/assets/circle-error.png');
+}
+.info {
+  flex: 1;
+  padding-left: 1rem;
+  i {
+    width: 0.14rem;
+    height: 0.14rem;
+    display: inline-block;
+    vertical-align: middle;
+    border-radius: 50%;
+    margin-right: 0.14rem;
+    background-color: rgba(74, 144, 226, 1);
   }
-  .time {
-    font-size: 0.24rem;
-    font-weight: 400;
-    color: #4a4a4a;
-    line-height: 0.34rem;
+}
+.time {
+  font-size: 0.24rem;
+  font-weight: 400;
+  color: #4a4a4a;
+  line-height: 0.34rem;
+}
+.desc {
+  font-size: 0.32rem;
+  font-weight: 400;
+  color: #4a4a4a;
+  line-height: 0.44rem;
+  .number {
+    font-size: 0.54rem;
+    font-weight: 500;
+    color: #f5a623;
+    line-height: 0.76rem;
+    margin-left: 0.12rem;
   }
-  .desc {
-    font-size: 0.32rem;
-    font-weight: 400;
-    color: #4a4a4a;
-    line-height: 0.44rem;
-    .number {
-      font-size: 0.54rem;
-      font-weight: 500;
-      color: #f5a623;
-      line-height: 0.76rem;
-      margin-left: 0.12rem;
-    }
 
-    span {
-      font-size: 0.28rem;
-      color: #000000;
-    }
-  }
-  .btn {
-    width: 0.23rem;
-    height: 0.35rem;
-    background: url('@/assets/arr-right.png') no-repeat center;
-    background-size: 100% 100%;
+  span {
+    font-size: 0.28rem;
+    color: #000000;
   }
 }
+.btn {
+  width: 0.23rem;
+  height: 0.35rem;
+  background: url('@/assets/arr-right.png') no-repeat center;
+  background-size: 100% 100%;
+}

+ 11 - 1
src/pages/EqSelfInspection/ReportDetail.js

@@ -1,19 +1,29 @@
 import PageContent from '@/components/PageContent';
 import PageTitle from '@/components/PageTitle';
-import { connect, useParams } from '@umijs/max';
+import { connect, useLocation, useParams, useSearchParams } from '@umijs/max';
 import { useEffect } from 'react';
 import Detail from './components/Detail';
 
 const ReportDetail = (props) => {
   const { data, dispatch, loading } = props;
+  const [searchParams, setSearchParams] = useSearchParams();
 
   const { projectId, routeId } = useParams();
+  const eTime = new URLSearchParams(useLocation()?.search)?.get('eTime');
+
   useEffect(() => {
     if (routeId) {
       dispatch({
         type: 'eqSelfInspection/getPatrolDataById',
         payload: {
           routeId,
+          projectId,
+          eTime,
+        },
+        callback: () => {
+          if (searchParams.get('model')) {
+            document.getElementById(searchParams.get('model')).scrollIntoView();
+          }
         },
       });
     }

File diff suppressed because it is too large
+ 250 - 1537
src/pages/EqSelfInspection/components/Detail.js


+ 23 - 5
src/pages/EqSelfInspection/components/PatrolReportDetail.less

@@ -22,6 +22,24 @@
   border-radius: 50%;
   display: inline-block;
 }
+.tipTitle {
+  display: flex;
+  i {
+    width: 0.28rem;
+    height: 0.28rem;
+    margin-left: 0.1rem;
+    display: block;
+    background: url('@/assets/eqSelfInspention/tooltip.png') no-repeat center;
+    background-size: 100% 100%;
+  }
+}
+.popoverContent {
+  font-size: 20px;
+  p {
+    margin-bottom: 0;
+    line-height: 36px;
+  }
+}
 .detailCard {
   // margin: 0.2rem 0;
   margin-bottom: 0.3rem;
@@ -35,6 +53,7 @@
   // margin: 0.1rem 0 0.1rem 0;
   height: 0.51rem;
   width: 50%;
+  font-weight: 600;
   .text {
     color: #1677ff;
     font-size: 0.28rem;
@@ -62,15 +81,13 @@
 .card {
   background: #fff;
   // padding: 0.1rem 0.2rem;
-  padding-top: 0.28rem;
-  padding-bottom: 0.4rem;
-  padding-left: 0.6rem;
+  padding: 0.28rem 0 0.2rem;
   position: relative;
   overflow: hidden;
   border-bottom: 2px solid #4577ff;
-  margin-bottom: 0.4rem;
+  margin-bottom: 0.2rem;
   .cardText {
-    font-size: 0.32rem;
+    font-size: 0.3rem;
     font-family: Source Han Sans, Source Han Sans;
     color: #615d5d;
     line-height: 1;
@@ -130,6 +147,7 @@
   right: 0.1rem;
   top: 0;
   line-height: 1;
+  font-weight: 600;
 }
 .content {
   margin-top: 0.5rem;

+ 176 - 0
src/pages/EqSelfInspection/components/Table/DeviceTable.js

@@ -0,0 +1,176 @@
+import ThresholdDetail from '@/components/ThresholdDetail';
+import ThresholdModal from '@/components/ThresholdDetail/ThresholdModal';
+import { changeRecordStatus } from '@/services/eqSelfInspection';
+import { UnityAction } from '@/utils/utils';
+import { Table, message } from 'antd';
+import { useState } from 'react';
+import styles from '../PatrolReportDetail.less';
+import ErrorHandleModal from './ErrorHandleModal';
+import Empty from './Empty';
+
+export default function DeviceTable(props) {
+  const {
+    onClickItem,
+    data = {},
+    items,
+    userList,
+    type,
+  } = props;
+  console.log(items);
+  const [loading, setLoading] = useState(false);
+  const [visible, setVisible] = useState(false);
+  const [errVisible, setErrVisible] = useState(false);
+  const [currentItem, setCurrentItem] = useState({});
+  const isSensor = type == 'sensor';
+
+  const onClickThreshold = (record) => {
+    setCurrentItem(record);
+    setVisible(true);
+  };
+  const onClickError = (record) => {
+    setCurrentItem(record);
+    setErrVisible(true);
+  };
+  const handleError = async (values) => {
+    setLoading(true);
+    let res = await changeRecordStatus({
+      ...values,
+      Id: currentItem.Id,
+      DeviceCode: currentItem.DeviceCode,
+      DeviceName: currentItem.DeviceName,
+      RecordId: data.Id,
+      RepairMan: values.RepairMan * 1,
+    });
+    setLoading(false);
+    if (res) {
+      message.success('操作成功');
+      setErrVisible(false);
+    }
+  };
+  const columns = [
+    {
+      title: '设备名称',
+      width: '20%',
+      dataIndex: 'DeviceName',
+    },
+    {
+      title: '巡检项',
+      key: 'TemplateItem.Name',
+      dataIndex: ['TemplateItem', 'Name'],
+    },
+    // {
+    //   title: '设备位号',
+    //   width: '16%',
+    //   dataIndex: 'DeviceCode',
+    // },
+    {
+      title: '设定值范围',
+      render: (record) => (
+        <ThresholdDetail
+          current={record.Value || 0}
+          data={record || {}}
+          // onClick={() => onClickThreshold(record)}
+        />
+      ),
+    },
+    {
+      title: '状态',
+      dataIndex: 'Status',
+      width: '1.25rem',
+      render: (Status) => {
+        switch (Status) {
+          case -1:
+          case 0:
+            return (
+              <div>
+                <i
+                  className={styles.iconStatus}
+                  style={{ background: '#12CEB3' }}
+                ></i>
+                正常
+              </div>
+            );
+          case 1:
+            return (
+              <div>
+                <i
+                  className={styles.iconStatus}
+                  style={{ background: '#FE5850' }}
+                ></i>
+                异常
+              </div>
+            );
+          case 2:
+            return (
+              <div>
+                <i
+                  className={styles.iconStatus}
+                  style={{ background: '#FFE26D' }}
+                ></i>
+                警告
+              </div>
+            );
+        }
+      },
+    },
+  ];
+  const handleClickItem = (data) => {
+    if (!isSensor) {
+      onClickItem(`DeviceTable-${data.Id}`, {
+        type: data.Status,
+        deviceCode: data.DeviceCode,
+      });
+    } else {
+      onClickItem(`DeviceTable-${data.Id}`, {
+        // type: data.Status,
+        deviceCode: data.DeviceCode,
+        value: Number(data.Value || 0),
+        threshold: data.JsonNumThreshold,
+      });
+      UnityAction.sendMsg('SinglePowerEnvironFromWeb', JSON.stringify(data));
+    }
+  };
+
+  // useEffect(() => {
+  //   console.log('温控', items);
+  //   if (isSensor)
+  //     UnityAction.sendMsg('PowerEnvironsFromWeb', JSON.stringify(items));
+  // }, [items]);
+
+  // if (!isSensor) {
+  //   columns.push({
+  //     title: '操作',
+  //     render: (record) =>
+  //       record.Status == 1 && (
+  //         <a style={{ color: '#FE5850' }} onClick={() => onClickError(record)}>
+  //           异常处理
+  //         </a>
+  //       ),
+  //   });
+  // }
+
+  return (
+    <div>
+      <Table
+        columns={columns}
+        dataSource={items}
+        rowKey="Id"
+        locale={{
+          emptyText: <Empty />,
+        }}
+        pagination={false}
+      />
+      <ThresholdModal
+        open={visible}
+        data={currentItem.JsonNumThreshold}
+        onClose={() => setVisible(false)}
+      />
+      <ErrorHandleModal
+        open={errVisible}
+        userList={userList}
+        onCancel={() => setErrVisible(false)}
+        onOk={handleError}
+      />
+    </div>
+  );
+}

+ 213 - 0
src/pages/EqSelfInspection/components/Table/DosingFlowCom.js

@@ -0,0 +1,213 @@
+import TabsContent from '@/components/TabsContent';
+import ThresholdDetail from '@/components/ThresholdDetail';
+import { Popover, Table } from 'antd';
+import dayjs from 'dayjs';
+import { useEffect, useMemo, useState } from 'react';
+import styles from '../PatrolReportDetail.less';
+import Empty from './Empty';
+import { useModel } from '@umijs/max';
+import MandateBtn from './MandateBtn';
+
+export default function DosingFlowCom(props) {
+  const {
+    sendMessageToUnity,
+    select,
+    allData = [],
+    statusCheck,
+    changeStatus,
+  } = props;
+  const [activeKey, setActiveKey] = useState('1');
+
+  const handleTabsChange = (key) => {
+    setActiveKey(key);
+  };
+
+  const { errorTableData, normalData } = useMemo(() => {
+    let data = { errorTableData: [], normalData: [] };
+    allData?.forEach((item) => {
+      if (!item.history && item.status == 1 && statusCheck.includes(1)) {
+        data.errorTableData.push(item);
+      } else if (item.history && item.status == 0 && statusCheck.includes(0)) {
+        data.normalData.push(item);
+      }
+    });
+    return data;
+  }, [statusCheck, allData]);
+
+  const items = useMemo(() => {
+    let items = [];
+    let showAllTabs = statusCheck.length == 3;
+    if (showAllTabs || errorTableData.length > 0) {
+      items.push({
+        key: '1',
+        label: `异常(${errorTableData.length || 0})`,
+        children: <div></div>,
+      });
+    }
+    if (showAllTabs || normalData.length > 0) {
+      items.push({
+        key: '0',
+        label: `历史记录(${normalData.length || 0})`,
+        children: <div></div>,
+      });
+    }
+    return items;
+  }, [statusCheck, allData]);
+
+  useEffect(() => {
+    if (items.length == 0) {
+      changeStatus(0);
+    } else {
+      setActiveKey(items[0].key);
+      changeStatus(1);
+    }
+  }, [items]);
+
+  const content = (
+    <div className={styles.popoverContent}>
+      <p>差值:液位计流量与流量计流量的差值</p>
+      <p>比值:液位计流量与流量计流量的比值</p>
+    </div>
+  );
+  if (items.length == 0) return null;
+
+  return (
+    <div className={styles.detailCard}>
+      <div className={styles.tableTop}>
+        <div className={styles.tipTitle}>
+          加药流量校验
+          <Popover content={content}>
+            <i></i>
+          </Popover>
+        </div>
+        <TabsContent
+          active={activeKey}
+          onChange={handleTabsChange}
+          small
+          items={items}
+        ></TabsContent>
+      </div>
+
+      {activeKey === '1' && (
+        <DosingFlowTable
+          onClickItem={sendMessageToUnity}
+          select={select}
+          items={errorTableData}
+        />
+      )}
+      {activeKey === '0' && (
+        <DosingFlowTable
+          onClickItem={sendMessageToUnity}
+          select={select}
+          items={normalData}
+        />
+      )}
+    </div>
+  );
+}
+function DosingFlowTable(props) {
+  const { items } = props;
+  const { getMandateId } = useModel('useMandate');
+  const columns = [
+    {
+      title: '设备名称',
+      width: '12%',
+      dataIndex: 'device_name',
+    },
+    {
+      title: '时间',
+      dataIndex: 'record_time',
+      render: (text) => {
+        if (text) {
+          return dayjs(text).format('YYYY.MM.DD HH:mm');
+        }
+        return '-';
+      },
+    },
+    {
+      title: '类型',
+      width: '15%',
+      key: 'template_item_name',
+      dataIndex: 'template_item_name',
+    },
+    {
+      title: '实际流量',
+      dataIndex: 'origin_value',
+    },
+    {
+      title: '计量流量',
+      dataIndex: 'value',
+    },
+    {
+      title: '差值/比值',
+      dataIndex: 'value',
+    },
+    {
+      title: '设定值范围',
+      width: '18%',
+      render: (record) => (
+        <ThresholdDetail
+          current={record.value || 0}
+          data={{
+            JsonNumThreshold: record?.json_num_threshold,
+            Type: record.type || 2,
+          }}
+        />
+      ),
+    },
+    {
+      title: '状态',
+      dataIndex: 'status',
+      render: (status, record) => {
+        switch (status) {
+          case -1:
+          case 0:
+            return (
+              <div>
+                <i
+                  className={styles.iconStatus}
+                  style={{ background: '#12CEB3' }}
+                />
+                正常
+              </div>
+            );
+          case 1:
+            return (
+              <div>
+                <i
+                  className={styles.iconStatus}
+                  style={{ background: '#FF8600' }}
+                />
+                异常
+                <MandateBtn relationId={record.id} />
+              </div>
+            );
+          case 2:
+            return (
+              <div>
+                <i
+                  className={styles.iconStatus}
+                  style={{ background: '#FFE26D' }}
+                />
+                警告
+              </div>
+            );
+          default:
+            return null;
+        }
+      },
+    },
+  ];
+
+  return (
+    <Table
+      columns={columns}
+      dataSource={items}
+      rowKey="device_code"
+      locale={{
+        emptyText: <Empty />,
+      }}
+      pagination={false}
+    />
+  );
+}

+ 11 - 0
src/pages/EqSelfInspection/components/Table/Empty.js

@@ -0,0 +1,11 @@
+export default function Empty() {
+  return (
+    <div>
+      <img
+        src={require('@/assets/self-empty.png')}
+        style={{ margin: '0.2rem 0', width: '1rem' }}
+      />
+      <p style={{ textAlign: 'center', color: '#555' }}>自检正常</p>
+    </div>
+  );
+}

+ 92 - 0
src/pages/EqSelfInspection/components/Table/ErrorHandleModal.js

@@ -0,0 +1,92 @@
+import { DatePicker, Form, Input, Modal, Select } from 'antd';
+
+export default function ErrorHandleModal(props) {
+  const { visible, onCancel, onOk, userList } = props;
+  const [form] = Form.useForm();
+  const status = form.getFieldValue('Status');
+  const handleOk = () => {
+    form.validateFields((error, values) => {
+      if (error) return;
+      onOk({
+        ...values,
+        PlanTime: values?.PlanTime?.format('YYYY-MM-DD HH:mm:ss'),
+      });
+    });
+  };
+
+  return (
+    <Modal
+      title="异常处理"
+      open={visible}
+      onCancel={onCancel}
+      onOk={handleOk}
+      destroyOnClose
+    >
+      <Form labelCol={{ span: 7 }} wrapperCol={{ span: 16 }}>
+        <Form.Item label="异常处理备注" name="ExceptionHandling">
+          <Input.TextArea />
+        </Form.Item>
+        <Form.Item
+          label="审核状态"
+          name="Status"
+          rules={[{ required: true, message: '请选择验收状态' }]}
+        >
+          <Select style={{ width: '100%' }} placeholder="请选择验收状态">
+            <Select.Option value={1}>已派遣</Select.Option>
+            <Select.Option value={2}>已通过</Select.Option>
+          </Select>
+        </Form.Item>
+        <Form.Item
+          label="维修人"
+          name="RepairMan"
+          rules={[{ required: true, message: '请选择维修人' }]}
+        >
+          <Select
+            showSearch
+            placeholder="请选择维修人"
+            filterOption={(input, option) =>
+              option.props.children.indexOf(input) >= 0
+            }
+            style={{ width: '100%' }}
+          >
+            {userList?.map((item) => (
+              <Select.Option key={item.ID}>{item.CName}</Select.Option>
+            ))}
+          </Select>
+        </Form.Item>
+        {status == 1 && (
+          <>
+            <Form.Item
+              label="难度级别"
+              name="DifficultyLevel"
+              rules={[{ required: true, message: '请选择难度级别' }]}
+            >
+              <Select placeholder="请选择难度级别" style={{ width: '100%' }}>
+                <Select.Option value={0}>大修</Select.Option>
+                <Select.Option value={1}>项目维修</Select.Option>
+                <Select.Option value={2}>小修</Select.Option>
+              </Select>
+            </Form.Item>
+            <Form.Item
+              label="维修方式"
+              name="RepairType"
+              rules={[{ required: true, message: '请选择维修方式' }]}
+            >
+              <Select placeholder="请选择维修方式" style={{ width: '100%' }}>
+                <Select.Option value={0}>自维</Select.Option>
+                <Select.Option value={1}>外委</Select.Option>
+              </Select>
+            </Form.Item>
+            <Form.Item
+              label="计划完成日期"
+              name="PlanTime"
+              rules={[{ required: true, message: '请选择计划完成日期' }]}
+            >
+              <DatePicker inputReadOnly />
+            </Form.Item>
+          </>
+        )}
+      </Form>
+    </Modal>
+  );
+}

+ 211 - 0
src/pages/EqSelfInspection/components/Table/LiquidLevelCom.js

@@ -0,0 +1,211 @@
+import TabsContent from '@/components/TabsContent';
+import ThresholdDetail from '@/components/ThresholdDetail';
+import { Popover, Table } from 'antd';
+import dayjs from 'dayjs';
+import { useEffect, useMemo, useState } from 'react';
+import styles from '../PatrolReportDetail.less';
+import Empty from './Empty';
+import MandateBtn from './MandateBtn';
+
+export default function LiquidLevelCom(props) {
+  const {
+    sendMessageToUnity,
+    select,
+    allData,
+    type,
+    statusCheck,
+    changeStatus,
+  } = props;
+  const [activeKey, setActiveKey] = useState('1');
+
+  const handleTabsChange = (activeKey) => {
+    setActiveKey(activeKey);
+  };
+
+  const { errorTableData, normalData } = useMemo(() => {
+    let data = { errorTableData: [], normalData: [] };
+    allData?.forEach((item) => {
+      if (!item.history && item.status == 1 && statusCheck.includes(1)) {
+        data.errorTableData.push(item);
+      } else if (item.history && item.status == 0 && statusCheck.includes(0)) {
+        data.normalData.push(item);
+      }
+    });
+    return data;
+  }, [statusCheck, allData]);
+
+  const items = useMemo(() => {
+    let items = [];
+    let showAllTabs = statusCheck.length == 3;
+    if (showAllTabs || errorTableData.length > 0) {
+      items.push({
+        key: '1',
+        label: `异常(${errorTableData.length || 0})`,
+        children: <div></div>,
+      });
+    }
+    if (showAllTabs || normalData.length > 0) {
+      items.push({
+        key: '0',
+        label: `历史记录(${normalData.length || 0})`,
+        children: <div></div>,
+      });
+    }
+    return items;
+  }, [statusCheck, allData]);
+
+  useEffect(() => {
+    if (items.length == 0) {
+      changeStatus(0);
+    } else {
+      setActiveKey(items[0].key);
+      changeStatus(1);
+    }
+  }, [items]);
+  const content = (
+    <div className={styles.popoverContent}>
+      <p>固定液位1为H1,固定液位2为H2,实际液位为H实</p>
+      <p>液位差值:液位标准值(H1)与液位实际值(H2)的差值</p>
+      <p>液位差比值:H1标准值与实际值的差值与H2的标准值与实际值的差值的比值</p>
+    </div>
+  );
+  if (items.length == 0) return null;
+
+  return (
+    <div className={styles.detailCard}>
+      <div className={styles.tableTop}>
+        <div className={styles.tipTitle}>
+          液位校验
+          <Popover content={content}>
+            <i></i>
+          </Popover>
+        </div>
+        <TabsContent
+          active={activeKey}
+          onChange={handleTabsChange}
+          small
+          items={items}
+        ></TabsContent>
+      </div>
+
+      {activeKey === '1' && (
+        <LiquidTable
+          onClickItem={sendMessageToUnity}
+          select={select}
+          items={errorTableData}
+          key={type}
+          type={type}
+        />
+      )}
+      {activeKey === '0' && (
+        <LiquidTable
+          onClickItem={sendMessageToUnity}
+          select={select}
+          items={normalData}
+          key={type}
+          type={type}
+        />
+      )}
+    </div>
+  );
+}
+
+function LiquidTable(props) {
+  const { items } = props;
+
+  const columns = [
+    {
+      title: '设备名称',
+      width: '12%',
+      dataIndex: 'device_name',
+    },
+    {
+      title: '时间',
+      dataIndex: 'record_time',
+      render: (text) => {
+        if (text) {
+          return dayjs(text).format('YYYY.MM.DD HH:mm');
+        }
+        return '-';
+      },
+    },
+    {
+      title: '类型',
+      key: 'template_item_name',
+      dataIndex: 'template_item_name',
+    },
+    {
+      title: '液位数',
+      dataIndex: 'origin_value',
+    },
+    {
+      title: '差值/比值',
+      dataIndex: 'value',
+    },
+    {
+      title: '设定值范围',
+      width: '18%',
+      render: (record) => (
+        <ThresholdDetail
+          current={record.value || 0}
+          data={{
+            JsonNumThreshold: record?.json_num_threshold,
+            Type: record.type || 2,
+          }}
+        />
+      ),
+    },
+    {
+      title: '状态',
+      dataIndex: 'status',
+      render: (status, record) => {
+        switch (status) {
+          case -1:
+          case 0:
+            return (
+              <div>
+                <i
+                  className={styles.iconStatus}
+                  style={{ background: '#12CEB3' }}
+                ></i>
+                正常
+              </div>
+            );
+          case 1:
+            return (
+              <div>
+                <i
+                  className={styles.iconStatus}
+                  style={{ background: '#FFE26D' }}
+                ></i>
+                异常
+                <MandateBtn relationId={record.id} />
+              </div>
+            );
+          case 2:
+            return (
+              <div>
+                <i
+                  className={styles.iconStatus}
+                  style={{ background: '#FFE26D' }}
+                ></i>
+                警告
+              </div>
+            );
+        }
+      },
+    },
+  ];
+
+  return (
+    <Table
+      columns={columns}
+      dataSource={items}
+      rowKey="device_code"
+      locale={{
+        emptyText: <Empty />,
+      }}
+      pagination={false}
+    />
+  );
+}

+ 22 - 0
src/pages/EqSelfInspection/components/Table/MandateBtn.js

@@ -0,0 +1,22 @@
+import { UnityAction } from '@/utils/utils';
+import { useModel } from '@umijs/max';
+import { useMemo } from 'react';
+
+export default function ({ relationId }) {
+  const { getMandateId } = useModel('useMandate');
+  let mandateId = useMemo(() => {
+    return getMandateId(relationId);
+  }, [relationId, getMandateId]);
+  if (!mandateId) return null;
+  return (
+    <a
+      style={{ marginLeft: 10 }}
+      onClick={() =>
+        // task
+        UnityAction.sendMsg('OpenTaskModal', `mandate_id=${mandateId}`)
+      }
+    >
+      查看任务
+    </a>
+  );
+}

+ 208 - 0
src/pages/EqSelfInspection/components/Table/PressureGaugeCom.js

@@ -0,0 +1,208 @@
+import TabsContent from '@/components/TabsContent';
+import { Table } from 'antd';
+import dayjs from 'dayjs';
+import { useEffect, useMemo, useState } from 'react';
+import styles from '../PatrolReportDetail.less';
+import Empty from './Empty';
+import MandateBtn from './MandateBtn';
+
+function PressureGaugeTable(props) {
+  const { items } = props;
+  const columns = [
+    {
+      title: '设备名称',
+      width: '20%',
+      dataIndex: 'item_alias',
+    },
+    {
+      title: '时间范围',
+      key: 'dataRange',
+      width: '1.5rem',
+      render: (record) => {
+        if (record.query_start && record.query_end) {
+          return (
+            <>
+              {dayjs(record.query_start).format('HH:mm:ss')}
+              <br />
+              {dayjs(record.query_end).format('HH:mm:ss')}
+            </>
+          );
+        }
+        return '-';
+      },
+    },
+    {
+      title: (
+        <>
+          仪表
+          <br />
+          最小数
+        </>
+      ),
+      width: '1.25rem',
+      dataIndex: 'data_min',
+    },
+    {
+      title: (
+        <>
+          仪表
+          <br />
+          最大数
+        </>
+      ),
+      width: '1.25rem',
+      dataIndex: 'data_max',
+    },
+    {
+      title: '可能原因',
+      dataIndex: 'content',
+    },
+    {
+      title: '状态',
+      dataIndex: 'status',
+      render: (status) => {
+        switch (status) {
+          case -1:
+          case 0:
+            return (
+              <div>
+                <i
+                  className={styles.iconStatus}
+                  style={{ background: '#12CEB3' }}
+                />
+                正常
+              </div>
+            );
+          case 1:
+            return (
+              <div>
+                <i
+                  className={styles.iconStatus}
+                  style={{ background: '#FF8600' }}
+                />
+                异常
+                <MandateBtn relationId={record.id} />
+              </div>
+            );
+          case 2:
+            return (
+              <div>
+                <i
+                  className={styles.iconStatus}
+                  style={{ background: '#FFE26D' }}
+                />
+                警告
+              </div>
+            );
+          default:
+            return (
+              <div>
+                <i
+                  className={styles.iconStatus}
+                  style={{ background: '#FF8600' }}
+                />
+                异常
+              </div>
+            );
+        }
+      },
+    },
+  ];
+  return (
+    <Table
+      columns={columns}
+      dataSource={items}
+      rowKey="device_code"
+      locale={{
+        emptyText: <Empty />,
+      }}
+      pagination={false}
+    />
+  );
+}
+
+export default function PressureGaugeCom(props) {
+  const {
+    sendMessageToUnity,
+    select,
+    allData = [],
+    title,
+    statusCheck,
+    changeStatus,
+  } = props;
+  const [activeKey, setActiveKey] = useState('1');
+
+  const handleTabsChange = (key) => {
+    setActiveKey(key);
+  };
+
+  const { errorTableData, normalData } = useMemo(() => {
+    let data = { errorTableData: [], normalData: [] };
+    allData?.forEach((item) => {
+      if (!item.history && item.status == 1 && statusCheck.includes(1)) {
+        data.errorTableData.push(item);
+      } else if (item.history && item.status == 0 && statusCheck.includes(0)) {
+        data.normalData.push(item);
+      }
+    });
+    return data;
+  }, [statusCheck, allData]);
+
+  const items = useMemo(() => {
+    let items = [];
+    let showAllTabs = statusCheck.length == 3;
+    if (showAllTabs || errorTableData.length > 0) {
+      items.push({
+        key: '1',
+        label: `异常(${errorTableData.length || 0})`,
+        children: <div></div>,
+      });
+    }
+    if (showAllTabs || normalData.length > 0) {
+      items.push({
+        key: '0',
+        label: `正常(${normalData.length || 0})`,
+        children: <div></div>,
+      });
+    }
+    return items;
+  }, [statusCheck, allData]);
+
+  useEffect(() => {
+    if (items.length == 0) {
+      changeStatus(0);
+    } else {
+      setActiveKey(items[0].key);
+      changeStatus(1);
+    }
+  }, [items]);
+
+  if (items.length == 0) return null;
+  return (
+    <div className={styles.detailCard}>
+      <div className={styles.tableTop}>
+        {title}
+        <TabsContent
+          active={activeKey}
+          onChange={handleTabsChange}
+          small
+          items={items}
+        ></TabsContent>
+      </div>
+      {activeKey === '1' && (
+        <PressureGaugeTable
+          onClickItem={sendMessageToUnity}
+          select={select}
+          items={errorTableData}
+        />
+      )}
+      {activeKey === '0' && (
+        <PressureGaugeTable
+          onClickItem={sendMessageToUnity}
+          select={select}
+          items={normalData}
+        />
+      )}
+    </div>
+  );
+}

+ 123 - 0
src/pages/EqSelfInspection/components/Table/ReportCom.js

@@ -0,0 +1,123 @@
+import TabsContent from '@/components/TabsContent';
+import { useEffect, useMemo, useState } from 'react';
+import styles from '../PatrolReportDetail.less';
+import DeviceTable from './DeviceTable';
+import WarningTable from './WarningTable';
+
+export default function ReportCom(props) {
+  const {
+    sendMessageToUnity,
+    select,
+    allData,
+    userList,
+    type,
+    title,
+    data,
+    statusCheck,
+    changeStatus,
+  } = props;
+  const [activeKey, setActiveKey] = useState('1');
+  const handleTabsChange = (activeKey) => {
+    setActiveKey(activeKey);
+  };
+
+  const { warningTableData, errorTableData, normalData } = useMemo(() => {
+    let data = { warningTableData: [], errorTableData: [], normalData: [] };
+    allData?.forEach((item) => {
+      if (item.Status == 1 && statusCheck.includes(1)) {
+        data.errorTableData.push(item);
+      } else if (item.Status == 2 && statusCheck.includes(2)) {
+        data.warningTableData.push(item);
+      } else if (statusCheck.includes(0)) {
+        data.normalData.push(item);
+      }
+    });
+    return data;
+  }, [statusCheck, allData]);
+
+  const items = useMemo(() => {
+    let items = [];
+    let showAllTabs = statusCheck.length == 3;
+    if (showAllTabs || errorTableData.length > 0) {
+      items.push({
+        key: '1',
+        label: `异常(${errorTableData.length || 0})`,
+        children: <div></div>,
+      });
+    }
+    if (showAllTabs || warningTableData.length > 0) {
+      items.push({
+        key: '2',
+        label: `警告(${warningTableData.length || 0})`,
+        children: <div></div>,
+      });
+    }
+    if (showAllTabs || normalData.length > 0) {
+      items.push({
+        key: '0',
+        label: `正常(${normalData.length || 0})`,
+        children: <div></div>,
+      });
+    }
+    return items;
+  }, [statusCheck, allData]);
+
+  useEffect(() => {
+    if (items.length == 0) {
+      changeStatus(0);
+    } else {
+      setActiveKey(items[0].key);
+      changeStatus(1);
+    }
+  }, [items]);
+
+  if (items.length == 0) return null;
+
+  return (
+    <div className={styles.detailCard}>
+      <div className={styles.tableTop}>
+        {title}
+        <TabsContent
+          active={activeKey}
+          onChange={handleTabsChange}
+          small={true}
+          items={items}
+        ></TabsContent>
+      </div>
+
+      {activeKey == '1' && (
+        <WarningTable
+          onClickItem={sendMessageToUnity}
+          select={select}
+          items={errorTableData}
+          key={type}
+          data={data}
+          type={type}
+          userList={userList}
+        />
+      )}
+      {activeKey == '2' && (
+        <WarningTable
+          onClickItem={sendMessageToUnity}
+          select={select}
+          items={warningTableData}
+          key={type}
+          data={data}
+          type={type}
+          userList={userList}
+        />
+      )}
+      {activeKey == '0' && (
+        <DeviceTable
+          onClickItem={sendMessageToUnity}
+          select={select}
+          items={allData}
+          data={data}
+          key={type}
+          type={type}
+          userList={userList}
+        />
+      )}
+    </div>
+  );
+}

+ 101 - 0
src/pages/EqSelfInspection/components/Table/ReportDumCom.js

@@ -0,0 +1,101 @@
+import { Table } from 'antd';
+import dayjs from 'dayjs';
+import { useEffect, useMemo } from 'react';
+import ReactZmage from 'react-zmage';
+import styles from '../PatrolReportDetail.less';
+import Empty from './Empty';
+import MandateBtn from './MandateBtn';
+
+export default function ReportDumCom(props) {
+  const { data = [], title, statusCheck, changeStatus } = props;
+  const errorCount = data?.length || 0;
+  const columns = [
+    {
+      title: '报警时间',
+      dataIndex: 'event_time',
+      render: (time) => dayjs(time).format('YYYY-MM-DD HH:mm:ss'),
+    },
+    {
+      title: '设备名称',
+      dataIndex: 'device_name',
+    },
+    {
+      title: '报警类型',
+      dataIndex: 'event_type',
+      // render: type => alarmType[type],
+    },
+    {
+      title: '报警图片',
+      render: (item) => (
+        <ReactZmage
+          controller={{
+            // 关闭按钮
+            close: true,
+            // 旋转按钮
+            rotate: true,
+            // 缩放按钮
+            zoom: false,
+            // 下载按钮
+            download: false,
+            // 翻页按钮
+            flip: false,
+            // 多页指示
+            pagination: false,
+          }}
+          backdrop="rgba(255,255,255,0.5)"
+          style={{ height: '0.9rem' }}
+          src={item.path}
+        />
+      ),
+    },
+    {
+      title: '关联任务',
+      dataIndex: 'id',
+      render: (id) => <MandateBtn relationId={id} />,
+    },
+  ];
+
+  const show = useMemo(() => {
+    if (statusCheck.length != 3) {
+      // 不显示异常数据时,隐藏次模块
+      if (!statusCheck.includes(1)) return null;
+      // 过滤异常并且此模块没有异常数据时,不显示此模块
+      if (statusCheck.includes(1) && errorCount == 0) return null;
+    }
+    return true;
+  }, [statusCheck, errorCount]);
+
+  useEffect(() => {
+    if (show) {
+      changeStatus(1);
+    } else {
+      changeStatus(0);
+    }
+  }, [show]);
+
+  if (!show) return null;
+  return (
+    <div style={{ marginBottom: '0.3rem' }}>
+      <div className={styles.tabBarExtraContent}>
+        <div className={styles.text} style={{ width: '60%' }}>
+          {title}
+        </div>
+        <div className={styles.abnormal}>
+          <div className={styles.text} style={{ float: 'right' }}>
+            异常({errorCount})
+          </div>
+        </div>
+      </div>
+      <Table
+        bordered
+        rowKey="event_time"
+        columns={columns}
+        dataSource={data}
+        locale={{
+          emptyText: <Empty />,
+        }}
+        pagination={false}
+      />
+    </div>
+  );
+}

+ 117 - 0
src/pages/EqSelfInspection/components/Table/WarningTable.js

@@ -0,0 +1,117 @@
+import ThresholdDetail from '@/components/ThresholdDetail';
+import ThresholdModal from '@/components/ThresholdDetail/ThresholdModal';
+import { changeRecordStatus } from '@/services/eqSelfInspection';
+import { Table, message } from 'antd';
+import { useState } from 'react';
+import styles from '../PatrolReportDetail.less';
+import Empty from './Empty';
+import ErrorHandleModal from './ErrorHandleModal';
+import MandateBtn from './MandateBtn';
+
+export default function WarningTable(props) {
+  const { data, userList, items } = props;
+  const [loading, setLoading] = useState(false);
+  const [visible, setVisible] = useState(false);
+  const [errVisible, setErrVisible] = useState(false);
+  const [currentItem, setCurrentItem] = useState({});
+
+  const handleError = async (values) => {
+    setLoading(true);
+    var res = await changeRecordStatus({
+      ...values,
+      Id: currentItem.Id,
+      DeviceCode: currentItem.DeviceCode,
+      DeviceName: currentItem.DeviceName,
+      RecordId: data.Id,
+      RepairMan: values.RepairMan * 1,
+    });
+    setLoading(false);
+    if (res) {
+      message.success('操作成功');
+      setErrVisible(false);
+    }
+  };
+  const columns = [
+    {
+      title: '设备名称',
+      width: '20%',
+      dataIndex: 'DeviceName',
+    },
+    {
+      title: '巡检项',
+      dataIndex: ['TemplateItem', 'Name'],
+    },
+    {
+      title: '设定值范围',
+      width: '30%',
+      render: (record) => (
+        <ThresholdDetail current={record.Value || 0} data={record || {}} />
+      ),
+    },
+    {
+      title: '状态',
+      dataIndex: 'Status',
+      render: (Status, record) => {
+        switch (Status) {
+          case -1:
+          case 0:
+            return (
+              <div>
+                <i
+                  className={styles.iconStatus}
+                  style={{ background: '#12CEB3' }}
+                ></i>
+                正常
+              </div>
+            );
+          case 1:
+            return (
+              <div>
+                <i
+                  className={styles.iconStatus}
+                  style={{ background: '#FE5850' }}
+                ></i>
+                异常
+                <MandateBtn relationId={record.Id} />
+              </div>
+            );
+          case 2:
+            return (
+              <div>
+                <i
+                  className={styles.iconStatus}
+                  style={{ background: '#FFE26D' }}
+                ></i>
+                警告
+              </div>
+            );
+        }
+      },
+    },
+  ];
+
+  return (
+    <div>
+      <Table
+        columns={columns}
+        dataSource={items}
+        rowKey="Id"
+        locale={{
+          emptyText: <Empty />,
+        }}
+        pagination={false}
+      />
+      <ThresholdModal
+        open={visible}
+        data={currentItem.JsonNumThreshold}
+        onClose={() => setVisible(false)}
+      />
+      <ErrorHandleModal
+        open={errVisible}
+        userList={userList}
+        onCancel={() => setErrVisible(false)}
+        onOk={handleError}
+      />
+    </div>
+  );
+}

+ 211 - 0
src/pages/EqSelfInspection/components/Table/WaterInCom.js

@@ -0,0 +1,211 @@
+import TabsContent from '@/components/TabsContent';
+import ThresholdDetail from '@/components/ThresholdDetail';
+import { Popover, Table } from 'antd';
+import dayjs from 'dayjs';
+import { useEffect, useMemo, useState } from 'react';
+import styles from '../PatrolReportDetail.less';
+import Empty from './Empty';
+import MandateBtn from './MandateBtn';
+
+export default function WaterInCom(props) {
+  const {
+    sendMessageToUnity,
+    select,
+    allData = [],
+    statusCheck,
+    changeStatus,
+  } = props;
+  const [activeKey, setActiveKey] = useState('1');
+
+  const handleTabsChange = (key) => {
+    setActiveKey(key);
+  };
+
+  const { errorTableData, normalData } = useMemo(() => {
+    let data = { errorTableData: [], normalData: [] };
+    allData?.forEach((item) => {
+      if (!item.history && item.status == 1 && statusCheck.includes(1)) {
+        data.errorTableData.push(item);
+      } else if (item.history && item.status == 0 && statusCheck.includes(0)) {
+        data.normalData.push(item);
+      }
+    });
+    return data;
+  }, [statusCheck, allData]);
+
+  const items = useMemo(() => {
+    let items = [];
+    let showAllTabs = statusCheck.length == 3;
+    if (showAllTabs || errorTableData.length > 0) {
+      items.push({
+        key: '1',
+        label: `异常(${errorTableData.length || 0})`,
+        children: <div></div>,
+      });
+    }
+    if (showAllTabs || normalData.length > 0) {
+      items.push({
+        key: '0',
+        label: `正常(${normalData.length || 0})`,
+        children: <div></div>,
+      });
+    }
+    return items;
+  }, [statusCheck, allData]);
+
+  useEffect(() => {
+    if (items.length == 0) {
+      changeStatus(0);
+    } else {
+      setActiveKey(items[0].key);
+      changeStatus(1);
+    }
+  }, [items]);
+  const content = (
+    <div className={styles.popoverContent}>
+      <p>进出水实际流量=超滤总进水流量-反洗水池的补水流量-反渗透总进水流量</p>
+      <p>差值:进出水实际流量与流量计流量的差值</p>
+    </div>
+  );
+  if (items.length == 0) return null;
+  return (
+    <div className={styles.detailCard}>
+      <div className={styles.tableTop}>
+        <div className={styles.tipTitle}>
+          进出水流量校验
+          <Popover content={content}>
+            <i></i>
+          </Popover>
+        </div>
+        <TabsContent
+          active={activeKey}
+          onChange={handleTabsChange}
+          small
+          items={items}
+        ></TabsContent>
+      </div>
+      {activeKey === '1' && (
+        <WaterInTable
+          onClickItem={sendMessageToUnity}
+          select={select}
+          items={errorTableData}
+        />
+      )}
+      {activeKey === '0' && (
+        <WaterInTable
+          onClickItem={sendMessageToUnity}
+          select={select}
+          items={normalData}
+        />
+      )}
+    </div>
+  );
+}
+function WaterInTable(props) {
+  const { items } = props;
+  const columns = [
+    {
+      title: '设备名称',
+      width: '20%',
+      dataIndex: 'item_alias',
+    },
+    {
+      title: '差值',
+      // width: '15%',
+      dataIndex: 'current_val',
+    },
+    {
+      title: '时间',
+      // width: '15%',
+      dataIndex: 'create_time',
+      render: (text) => {
+        if (text) {
+          return dayjs(text).format('HH:mm:ss');
+        }
+        return '-';
+      },
+    },
+    {
+      title: '设定值范围',
+      width: '30%',
+      render: (record) => (
+        <ThresholdDetail
+          current={record.current_val || 0}
+          data={{
+            JsonNumThreshold: {
+              exception: [
+                { ThresholdValue: record?.thresholds, ThresholdType: 1 },
+              ],
+            },
+            Type: 2,
+          }}
+        />
+      ),
+    },
+    {
+      title: '状态',
+      dataIndex: 'status',
+      render: (status) => {
+        switch (status) {
+          case -1:
+          case 0:
+            return (
+              <div>
+                <i
+                  className={styles.iconStatus}
+                  style={{ background: '#12CEB3' }}
+                />
+                正常
+              </div>
+            );
+          case 1:
+            return (
+              <div>
+                <i
+                  className={styles.iconStatus}
+                  style={{ background: '#FF8600' }}
+                />
+                异常
+                <a style={{ marginLeft: 20 }}>
+                  关联任务
+                  <MandateBtn relationId={record.id} />
+                </a>
+              </div>
+            );
+          case 2:
+            return (
+              <div>
+                <i
+                  className={styles.iconStatus}
+                  style={{ background: '#FFE26D' }}
+                />
+                警告
+              </div>
+            );
+          default:
+            return (
+              <div>
+                <i
+                  className={styles.iconStatus}
+                  style={{ background: '#FF8600' }}
+                />
+                异常
+              </div>
+            );
+        }
+      },
+    },
+  ];
+
+  return (
+    <Table
+      columns={columns}
+      dataSource={items}
+      rowKey="device_code"
+      locale={{
+        emptyText: <Empty />,
+      }}
+      pagination={false}
+    />
+  );
+}

+ 210 - 0
src/pages/EqSelfInspection/components/Table/WaterQualityCom.js

@@ -0,0 +1,210 @@
+import TabsContent from '@/components/TabsContent';
+import { Table } from 'antd';
+import dayjs from 'dayjs';
+import { useEffect, useMemo, useState } from 'react';
+import styles from '../PatrolReportDetail.less';
+import Empty from './Empty';
+import MandateBtn from './MandateBtn';
+
+function WaterQualityTable(props) {
+  const { items } = props;
+  const columns = [
+    {
+      title: '设备名称',
+      width: '20%',
+      dataIndex: 'item_alias',
+    },
+    {
+      title: '时间范围',
+      key: 'dataRange',
+      width: '1.5rem',
+      render: (record) => {
+        if (record.query_start && record.query_end) {
+          return (
+            <>
+              {dayjs(record.query_start).format('HH:mm:ss')}
+              <br />
+              {dayjs(record.query_end).format('HH:mm:ss')}
+            </>
+          );
+        }
+        return '-';
+      },
+    },
+    {
+      title: (
+        <>
+          仪表
+          <br />
+          最小数
+        </>
+      ),
+      width: '1.25rem',
+      dataIndex: 'data_min',
+    },
+    {
+      title: (
+        <>
+          仪表
+          <br />
+          最大数
+        </>
+      ),
+      width: '1.25rem',
+      dataIndex: 'data_max',
+    },
+    {
+      title: '可能原因',
+      dataIndex: 'content',
+    },
+    {
+      title: '状态',
+      dataIndex: 'status',
+      render: (status) => {
+        switch (status) {
+          case -1:
+          case 0:
+            return (
+              <div>
+                <i
+                  className={styles.iconStatus}
+                  style={{ background: '#12CEB3' }}
+                />
+                正常
+              </div>
+            );
+          case 1:
+            return (
+              <div>
+                <i
+                  className={styles.iconStatus}
+                  style={{ background: '#FF8600' }}
+                />
+                异常
+                <MandateBtn relationId={record.id} />
+              </div>
+            );
+          case 2:
+            return (
+              <div>
+                <i
+                  className={styles.iconStatus}
+                  style={{ background: '#FFE26D' }}
+                />
+                警告
+              </div>
+            );
+          default:
+            return (
+              <div>
+                <i
+                  className={styles.iconStatus}
+                  style={{ background: '#FF8600' }}
+                />
+                异常
+              </div>
+            );
+        }
+      },
+    },
+  ];
+
+  return (
+    <Table
+      columns={columns}
+      dataSource={items}
+      rowKey="device_code"
+      locale={{
+        emptyText: <Empty />,
+      }}
+      pagination={false}
+    />
+  );
+}
+
+export default function WaterQualityCom(props) {
+  const {
+    sendMessageToUnity,
+    select,
+    allData = [],
+    title,
+    statusCheck,
+    changeStatus,
+  } = props;
+  const [activeKey, setActiveKey] = useState('1');
+
+  const handleTabsChange = (key) => {
+    setActiveKey(key);
+  };
+
+  const { errorTableData, normalData } = useMemo(() => {
+    let data = { errorTableData: [], normalData: [] };
+    allData?.forEach((item) => {
+      if (item.status == 1 && statusCheck.includes(1)) {
+        data.errorTableData.push(item);
+      } else if (item.status == 0 && statusCheck.includes(0)) {
+        data.normalData.push(item);
+      }
+    });
+    return data;
+  }, [statusCheck, allData]);
+
+  const items = useMemo(() => {
+    let items = [];
+    let showAllTabs = statusCheck.length == 3;
+    if (showAllTabs || errorTableData.length > 0) {
+      items.push({
+        key: '1',
+        label: `异常(${errorTableData.length || 0})`,
+        children: <div></div>,
+      });
+    }
+    if (showAllTabs || normalData.length > 0) {
+      items.push({
+        key: '0',
+        label: `正常(${normalData.length || 0})`,
+        children: <div></div>,
+      });
+    }
+    return items;
+  }, [statusCheck, allData]);
+
+  useEffect(() => {
+    if (items.length == 0) {
+      changeStatus(0);
+    } else {
+      setActiveKey(items[0].key);
+      changeStatus(1);
+    }
+  }, [items]);
+
+  if (items.length == 0) return null;
+
+  return (
+    <div className={styles.detailCard}>
+      <div className={styles.tableTop}>
+        {title}
+        <TabsContent
+          active={activeKey}
+          onChange={handleTabsChange}
+          small
+          items={items}
+        ></TabsContent>
+      </div>
+      {activeKey === '1' && (
+        <WaterQualityTable
+          onClickItem={sendMessageToUnity}
+          select={select}
+          items={errorTableData}
+        />
+      )}
+      {activeKey === '0' && (
+        <WaterQualityTable
+          onClickItem={sendMessageToUnity}
+          select={select}
+          items={normalData}
+        />
+      )}
+    </div>
+  );
+}

+ 37 - 23
src/pages/EqSelfInspection/index.js

@@ -78,12 +78,13 @@ const EqSelfInspection = (props) => {
     if (!autoReport.Id) {
       return;
     }
-    let mandateIDs = await getMandateIDs({
+    let res = await getMandateIDs({
       project_id: projectId,
       id: autoReport.Id,
     }).catch((err) => {
       console.log(err);
     });
+    let mandateIDs = res.map((item) => item.id);
     if (mandateIDs?.length) {
       mandateIDs = [...new Set(mandateIDs)];
       UnityAction.sendMsg('OpenTaskModal', `mandate_id=${mandateIDs.join()}`);
@@ -98,14 +99,20 @@ const EqSelfInspection = (props) => {
     );
   };
 
-  // useEffect(() => {
-  //   UnityAction.on('notiZiJian', (id) => {
-  //     handleJumpDetail(id);
-  //   });
-  //   //自检页面加载完毕
-  //   UnityAction.sendMsg('pageInited');
-  //   return () => UnityAction.off('notiZiJian');
-  // }, []);
+  const gotoDetail = (model = '') => {
+    history.push(
+      `/self-inspection/detail/${projectId}/${autoReport?.Id}?model=${model}`,
+    );
+  };
+
+  useEffect(() => {
+    UnityAction.on('notiZiJian', (id) => {
+      if (id) handleJumpDetail(id);
+    });
+    //自检页面加载完毕
+    UnityAction.sendMsg('pageInited');
+    return () => UnityAction.off('notiZiJian');
+  }, []);
 
   useEffect(() => {
     if (routeId) {
@@ -113,6 +120,7 @@ const EqSelfInspection = (props) => {
         type: 'eqSelfInspection/getPatrolDataById',
         payload: {
           routeId: routeId,
+          projectId,
         },
       });
     } else {
@@ -143,7 +151,7 @@ const EqSelfInspection = (props) => {
             className={`${styles.itemMain} card-box`}
             style={{ padding: '0.2rem 0.24rem', position: 'relative' }}
           >
-            <div style={{ fontSize: '0.28rem', color: 'rgb(110, 110, 110)' }}>
+            <div style={{ fontSize: '0.3rem', color: 'rgb(110, 110, 110)' }}>
               自检间隔:{autoReport?.RouteInfo?.PlanDur}分钟
             </div>
 
@@ -185,12 +193,24 @@ const EqSelfInspection = (props) => {
                   一键自检
                 </Button>
               </div>
-              <div className={styles.logoFont}>系统自检中</div>
+              {/* <div className={styles.logoFont}>系统自检中</div> */}
             </div>
           </div>
-          <Item name="设备自检" status={patrolStatus}></Item>
-          <Item name="工艺自检" status={faultAnalysisStatus}></Item>
-          <Item name="安全自检" status={secureStatus}>
+          <Item
+            name="设备自检"
+            status={patrolStatus}
+            onClick={() => gotoDetail('device')}
+          ></Item>
+          <Item
+            name="工艺自检"
+            status={faultAnalysisStatus}
+            onClick={() => gotoDetail('technology')}
+          ></Item>
+          <Item
+            name="安全自检"
+            status={secureStatus}
+            onClick={() => gotoDetail('secure')}
+          >
             {secureChildren?.map((item, index) => (
               <WarningItem
                 key={index}
@@ -220,13 +240,7 @@ const EqSelfInspection = (props) => {
             <Button
               className={styles.reportBtn}
               type="primary"
-              onClick={() => {
-                history.push(
-                  `/self-inspection/detail/${projectId}/${
-                    autoReport?.Id
-                  }?JWT-TOKEN=${GetTokenFromUrl()}`,
-                );
-              }}
+              onClick={() => gotoDetail()}
             >
               查看自检报告
             </Button>
@@ -275,7 +289,7 @@ export default connect(({ eqSelfInspection, loading }) => ({
 }))(EqSelfInspection);
 
 const Item = (props) => {
-  const { name, children, warningText = '', status = 0 } = props;
+  const { name, children, warningText = '', status = 0, onClick } = props;
   const renderRight = (status) => {
     switch (status) {
       case 0:
@@ -298,7 +312,7 @@ const Item = (props) => {
     }
   };
   return (
-    <div className={`${styles.itemMain} card-box`}>
+    <div className={`${styles.itemMain} card-box`} onClick={onClick}>
       <div className={styles.item} style={{ height: children ? '0.8rem' : '' }}>
         <span className={styles.itemName}>{name}</span>
         {/* <span className={styles.warningText}>{warningText}</span> */}

+ 162 - 73
src/pages/Home/ChemCostComparison.js

@@ -6,8 +6,9 @@ import {
   getChemicalAgents,
   getComparisonData,
 } from '@/services/OperationManagement';
+import { queryConditionSnapshot } from '@/services/SmartOps';
 import { UnityAction } from '@/utils/utils';
-import { useParams } from '@umijs/max';
+import { useParams, useRequest } from '@umijs/max';
 import { Tabs, message } from 'antd';
 import dayjs from 'dayjs';
 import { useEffect, useState } from 'react';
@@ -15,6 +16,15 @@ import styles from './manage.less';
 
 const { TabPane } = Tabs;
 
+const referencePriceTable = {
+  阻垢剂: '30.088元',
+  盐酸: '0.531元',
+  非氧化杀菌剂: '26.549元',
+  次氯酸钠: '1.001元',
+  氢氧化纳: '1.527元',
+  还原剂: '4.956元',
+};
+
 const typeParams = [
   {
     // 计划吨水药耗
@@ -38,10 +48,32 @@ const typeParams = [
   },
 ];
 
-const CostComparison = (props) => {
+const CostComparison = () => {
+  const [open, setOpen] = useState(false);
+
+  return (
+    <PageContent closeable={false}>
+      <PageTitle onReturn={() => UnityAction.sendMsg('menuItem', '首页')}>
+        药耗监测
+        <div
+          onClick={(e) => {
+            e.stopPropagation();
+            setOpen(!open);
+          }}
+          style={{ marginLeft: 10 }}
+          className={`password-eye ${open ? 'open' : ''}`}
+        ></div>
+      </PageTitle>
+      <ChemCost open={open} showTip />
+    </PageContent>
+  );
+};
+
+export default CostComparison;
+
+export const ChemCost = ({ open, showTip = false }) => {
   const { projectId } = useParams();
 
-  const [open, setOpen] = useState(false);
   const [chartData, setChartData] = useState([]);
   const [chemList, setChemList] = useState([]);
   const [currentChem, setCurrentChem] = useState();
@@ -63,6 +95,49 @@ const CostComparison = (props) => {
     end: defaultTime.e_time,
   };
 
+  const { data } = useRequest(getComparisonData, {
+    defaultParams: [
+      {
+        project_id: projectId,
+        start: curMonth,
+        end: curMonth,
+        type: 1,
+        flag: 1,
+      },
+    ],
+    formatResult(res) {
+      return res[0];
+    },
+  });
+  const getValue = (str) => {
+    const result = str?.match(/.*?(\d+(?:\.\d+)?)\D*$/);
+    if (result && result[1]) return result[1];
+    return 0;
+  };
+
+  const { data: snapshot } = useRequest(queryConditionSnapshot, {
+    defaultParams: [{ project_id: projectId }],
+    formatResult: (result) => {
+      const otcCost = Number(getValue(result.data.otc_cost_unit));
+      let resultText = `当前药耗持平理论值 (${
+        result?.data?.otc_unit_theory || 0
+      }kg/m³)`;
+      if (otcCost) {
+        if (otcCost > result.data.otc_unit_theory) {
+          resultText = `当前药耗高于理论值 (${result.data.otc_unit_theory}kg/m³)`;
+        }
+        if (otcCost === result.data.otc_unit_theory) {
+          resultText = `当前药耗持平理论值 (${result.data.otc_unit_theory}kg/m³)`;
+        }
+        if (otcCost < result.data.otc_unit_theory) {
+          resultText = `当前药耗低于理论值 (${result.data.otc_unit_theory}kg/m³)`;
+        }
+      }
+      console.log({ ...result.data, resultText });
+      return { ...result.data, resultText };
+    },
+  });
+
   const getChartData = () => {
     // 构建请求列表
     const queryList = [];
@@ -84,17 +159,15 @@ const CostComparison = (props) => {
     // 大于100,保留一位
     // 大于1000,不保留
     let fixed = 0;
+    if (maxValue === 0) {
+      return 2;
+    }
+    if (maxValue === 0) return fixed;
     if (maxValue < 1) {
-      const decimal = maxValue.toFixed(100).toString().split('.')[1];
-      const decimalArr = decimal.split('');
-      for (let index = 0; index < decimalArr.length; index++) {
-        if (decimalArr[index] === '0') {
-          fixed++;
-        } else {
-          break;
-        }
-      }
-      fixed += 2;
+      //maxValue + 1 防止maxValue过小自动转科学计数法
+      const decimal = (maxValue + 1).toString().split('.')[1];
+      const num = decimal.split('').findIndex((num) => num > 0);
+      fixed = num + 3;
     } else if (maxValue < 10) {
       fixed = 3;
     } else if (maxValue < 100) {
@@ -111,7 +184,7 @@ const CostComparison = (props) => {
     });
     if (result && result.length) {
       const [planChemPerCost, actualChemPerCost, planChem, actualChem] = result;
-      const chemPerCost = { yName: 'kg/t' };
+      const chemPerCost = { yName: 'kg/' };
       const chemUsed = { yName: 'kg' };
       chemPerCost.xData = [
         ...new Set(
@@ -135,7 +208,6 @@ const CostComparison = (props) => {
         .map((item) => item.value)
         .reduce((a, b) => Math.max(a, b));
       const chemPerCostFixed = getFixed(chemPerCostMaxValue);
-      console.log(chemPerCostFixed);
       chemPerCost.dataList = [
         {
           type: 0,
@@ -250,72 +322,89 @@ const CostComparison = (props) => {
   }, []);
 
   return (
-    <PageContent closeable={false}>
-      <PageTitle onReturn={() => UnityAction.sendMsg('menuItem', '首页')}>
-        药耗监测
-        <div
-          onClick={(e) => {
-            e.stopPropagation();
-            setOpen(!open);
-          }}
-          style={{ marginLeft: 10 }}
-          className={`password-eye ${open ? 'open' : ''}`}
-        ></div>
-      </PageTitle>
-
-      <div className="card-box" style={{ padding: '0.2rem' }}>
-        {/* 使用Tabs来展示所有药的标签 */}
-        <div className="tabs">
-          {chemList?.map((item) => (
-            <div
-              onClick={() => {
-                setCurrentChem(item);
-                handleChemChange(item);
-              }}
-              className={`tabs-item ${currentChem == item ? 'active' : ''}`}
-            >
-              {item}
+    <div style={{ position: 'relative' }}>
+      {showTip && <div className={styles.pageTip}>{snapshot?.resultText}</div>}
+      <div style={{ padding: '0.2rem 0rem' }}>
+        <div className="card-box">
+          <div className={styles.curEnergyCost}>
+            <div className={styles.item}>
+              <div className={styles.value}>
+                {open ? getValue(snapshot?.otc_unit || '') : '***'}
+                <span className={styles.unit}>元/m³</span>
+              </div>
+              <div className={styles.name}>近一小时吨水药成本</div>
             </div>
-          ))}
-        </div>
-        <div className={styles.curEnergyCost}>
-          <div className={styles.item}>
-            <div className={styles.value}>
-              {open ? topValues.chemPer : '***'}
-              <span className={styles.unit}>kg/t</span>
+            <div className={styles.item}>
+              <div className={styles.value}>
+                {open ? data?.value.toFixed(2) : '***'}
+                <span className={styles.unit}>元/m³</span>
+              </div>
+              <div className={styles.name}>当月吨水药成本</div>
             </div>
-            <div className={styles.name}>当月吨水药耗</div>
           </div>
-          <div className={styles.item}>
-            <div className={styles.value}>
-              {open ? topValues.chemUser : '***'}
-              <span className={styles.unit}>kg</span>
+        </div>
+
+        <div className="card-box" style={{ marginTop: '0.4rem' }}>
+          {/* 使用Tabs来展示所有药的标签 */}
+          <div style={{ padding: '0.2rem 0.1rem' }}>
+            <div className="tabs">
+              {chemList?.map((item) => (
+                <div
+                  key={item}
+                  onClick={() => {
+                    setCurrentChem(item);
+                    handleChemChange(item);
+                  }}
+                  className={`tabs-item ${currentChem == item ? 'active' : ''}`}
+                >
+                  {item}
+                </div>
+              ))}
             </div>
-            <div className={styles.name}>当月药量</div>
           </div>
-        </div>
-        {chartData.length !== 0 && (
-          <div
-            style={{
-              height: '8.8rem',
-              display: 'flex',
-              flexDirection: 'column',
-              justifyContent: 'space-between',
-              padding: '0.4rem 0',
-            }}
-          >
-            <div style={{ height: '3.5rem' }}>
-              <ChartModule {...chartData[0]} />
+          <div className={styles.curEnergyCost}>
+            <div className={styles.item}>
+              <div style={{ fontSize: '0.22rem', color: 'gray' }}>
+                药剂参考价格:
+                {open ? referencePriceTable[`${currentChem}`] : '***元'}
+              </div>
+              <div className={styles.value}>
+                {open ? topValues.chemPer : '***'}
+                <span className={styles.unit}>kg/m³</span>
+              </div>
+              <div className={styles.name}>当月吨水药耗</div>
             </div>
-
-            <div style={{ height: '3.5rem' }}>
-              <ChartModule {...chartData[1]} />
+            <div className={styles.item}>
+              <div className={styles.value}>
+                {open ? topValues.chemUser : '***'}
+                <span className={styles.unit}>kg</span>
+              </div>
+              <div className={styles.name}>当月药量</div>
             </div>
           </div>
-        )}
+
+          {chartData.length !== 0 && (
+            <div
+              style={{
+                marginTop: '0.2rem',
+                height: '8.8rem',
+                display: 'flex',
+                flexDirection: 'column',
+                justifyContent: 'space-between',
+                padding: '0.4rem 0',
+              }}
+            >
+              <div style={{ height: '3.5rem' }}>
+                <ChartModule {...chartData[0]} />
+              </div>
+
+              <div style={{ height: '3.5rem' }}>
+                <ChartModule {...chartData[1]} />
+              </div>
+            </div>
+          )}
+        </div>
       </div>
-    </PageContent>
+    </div>
   );
 };
-
-export default CostComparison;

+ 134 - 40
src/pages/Home/EnergyCostComparison.js

@@ -3,8 +3,10 @@ import ChartModule from '@/components/ManagementPage/chartModule';
 import PageContent from '@/components/PageContent';
 import PageTitle from '@/components/PageTitle';
 import { getComparisonData } from '@/services/OperationManagement';
+import { queryConditionSnapshot } from '@/services/SmartOps';
 import { UnityAction } from '@/utils/utils';
-import { useParams } from '@umijs/max';
+import { LineChartOutlined } from '@ant-design/icons';
+import { history, useParams, useRequest } from '@umijs/max';
 import { message } from 'antd';
 import dayjs from 'dayjs';
 import { useEffect, useState } from 'react';
@@ -34,9 +36,31 @@ const typeParams = [
 ];
 
 const CostComparison = () => {
+  const [open, setOpen] = useState(false);
+
+  return (
+    <PageContent closeable={false}>
+      <PageTitle onReturn={() => UnityAction.sendMsg('menuItem', '首页')}>
+        能耗数据
+        <div
+          onClick={(e) => {
+            e.stopPropagation();
+            setOpen(!open);
+          }}
+          style={{ marginLeft: '0.1rem' }}
+          className={`password-eye ${open ? 'open' : ''}`}
+        />
+      </PageTitle>
+      <EnergyCost open={open} showTip />
+    </PageContent>
+  );
+};
+
+export default CostComparison;
+
+export const EnergyCost = ({ open, detailClick, showTip }) => {
   const { projectId } = useParams();
 
-  const [open, setOpen] = useState(false);
   const [chartData, setChartData] = useState([]);
   const [curElecPerCost, setElecPerCost] = useState(0); // 当前月实际吨水电耗
   const [curElecUsed, setCurElecUsed] = useState(0); // 当前月实际用电量
@@ -52,6 +76,34 @@ const CostComparison = () => {
     end: defaultTime.e_time,
   };
 
+  const getValue = (str) => {
+    const result = str?.match(/.*?(\d+(?:\.\d+)?)\D*$/);
+    if (result && result[1]) return result[1];
+    return 0;
+  };
+
+  const { data: snapshot } = useRequest(queryConditionSnapshot, {
+    defaultParams: [{ project_id: projectId }],
+    formatResult: (result) => {
+      const elec = Number(getValue(result.data.elec_unit));
+      let resultText = `当前电耗持平理论值 ${
+        result?.data?.elec_unit_theory || 0
+      }KWh/m³`;
+      if (elec) {
+        if (elec > result.data.elec_unit_theory) {
+          resultText = `当前电耗高于理论值 ${result.data.elec_unit_theory}KWh/m³`;
+        }
+        if (elec === result.data.elec_unit_theory) {
+          resultText = `当前电耗持平理论值 ${result.data.elec_unit_theory}KWh/m³`;
+        }
+        if (elec < result.data.elec_unit_theory) {
+          resultText = `当前电耗低于理论值 ${result.data.elec_unit_theory}KWh/m³`;
+        }
+      }
+      return { ...result.data, resultText };
+    },
+  });
+
   const getChartData = () => {
     // 构建请求列表
     const queryList = [];
@@ -72,6 +124,9 @@ const CostComparison = () => {
     // 大于10,保留两位
     // 大于100,保留一位
     // 大于1000,不保留
+    if (maxValue === 0) {
+      return 2;
+    }
     let fixed = 0;
     if (maxValue < 1) {
       const decimal = maxValue.toFixed(100).toString().split('.')[1];
@@ -101,7 +156,7 @@ const CostComparison = () => {
     if (result && result.length) {
       const [planElecPerCost, actualElecPerCost, planElecUsed, actualElecUsed] =
         result;
-      const elecPerCost = { yName: 'kWh/t' };
+      const elecPerCost = { yName: 'kwh/m³' };
       const elecUsed = { yName: 'kWh' };
       elecPerCost.xData = [
         ...new Set(
@@ -201,13 +256,13 @@ const CostComparison = () => {
       elecUsed.chartType = 'bar';
       setChartData([elecPerCost, elecUsed]);
 
-      var curElecPerCost = actualElecPerCost?.find((item) =>
+      let curElecPerCost = actualElecPerCost?.find((item) =>
         dayjs().isSame(item?.month, 'month'),
       );
       if (curElecPerCost)
         setElecPerCost(curElecPerCost?.value.toFixed(elecPerCostFixed));
 
-      var curElecUsed = actualElecUsed?.find((item) =>
+      let curElecUsed = actualElecUsed?.find((item) =>
         dayjs().isSame(item?.month, 'month'),
       );
       if (curElecUsed)
@@ -221,59 +276,98 @@ const CostComparison = () => {
     createChartData();
   }, []);
 
+  const goEnergyDetail = () => {
+    if (detailClick) {
+      detailClick();
+    } else {
+      history.push(`/home/energy/detail/${projectId}`);
+    }
+  };
+
   return (
-    <PageContent closeable={false}>
-      <PageTitle onReturn={() => UnityAction.sendMsg('menuItem', '首页')}>
-        能耗数据
-        <div
-          onClick={(e) => {
-            e.stopPropagation();
-            setOpen(!open);
+    <div
+      style={{ padding: '0.2rem', position: 'relative', marginTop: '0.1rem' }}
+    >
+      {showTip && <div className={styles.pageTip}>{snapshot?.resultText}</div>}
+      <div className="card-box" style={{ padding: '0.1rem 0' }}>
+        <LineChartOutlined
+          style={{
+            fontSize: '0.4rem',
+            position: 'absolute',
+            right: '0.3rem',
+            color: '#0139f1',
           }}
-          style={{ marginLeft: 10 }}
-          className={`password-eye ${open ? 'open' : ''}`}
-        ></div>
-      </PageTitle>
-
-      <div className="card-box" style={{ padding: '0.2rem' }}>
+          onClick={goEnergyDetail}
+        />
         <div className={styles.curEnergyCost}>
-          <div className={styles.item}>
+          <div
+            className={styles.item}
+            style={{
+              borderRight: '1px solid #eaeaea',
+              borderBottom: '1px solid #eaeaea',
+            }}
+          >
             <div className={styles.value}>
-              {open ? curElecPerCost : '***'}
+              {open ? getValue(snapshot?.elec_unit || '') : '***'}
               <span className={styles.unit}>kWh/t</span>
             </div>
-            <div className={styles.name}>当月吨水电耗</div>
+            <div className={styles.name}>近一小时吨水电耗</div>
           </div>
-          <div className={styles.item}>
+          <div
+            className={styles.item}
+            style={{
+              borderBottom: '1px solid #eaeaea',
+            }}
+          >
             <div className={styles.value}>
-              {open ? curElecUsed : '***'}
+              {open ? getValue(snapshot?.elec || '') : '***'}
               <span className={styles.unit}>kWh</span>
             </div>
-            <div className={styles.name}>当月实际用电量</div>
+            <div className={styles.name}>近一小时用电量</div>
           </div>
-        </div>
-        {chartData.length !== 0 && (
           <div
+            className={styles.item}
             style={{
-              height: '9.2rem',
-              display: 'flex',
-              flexDirection: 'column',
-              justifyContent: 'space-between',
-              padding: '0.6rem 0 0.4rem',
+              borderRight: '1px solid #eaeaea',
             }}
           >
-            <div style={{ height: '3.5rem' }}>
-              <ChartModule {...chartData[0]} />
+            <div className={styles.value}>
+              {open ? curElecPerCost : '***'}
+              <span className={styles.unit}>kWh/t</span>
             </div>
-
-            <div style={{ height: '3.5rem' }}>
-              <ChartModule {...chartData[1]} />
+            <div className={styles.name}>当月吨水电耗</div>
+          </div>
+          <div className={styles.item}>
+            <div className={styles.value}>
+              {open ? curElecUsed : '***'}
+              <span className={styles.unit}>kWh</span>
             </div>
+            <div className={styles.name}>当月用电量</div>
           </div>
-        )}
+        </div>
       </div>
-    </PageContent>
+
+      {chartData.length !== 0 && (
+        <div
+          className="card-box"
+          style={{
+            height: '9.2rem',
+            display: 'flex',
+            flexDirection: 'column',
+            justifyContent: 'space-between',
+            padding: '0.6rem 0 0.4rem',
+            marginTop: '0.4rem',
+          }}
+        >
+          <div style={{ height: '3.5rem' }}>
+            <ChartModule {...chartData[0]} />
+          </div>
+
+          <div style={{ height: '3.5rem' }}>
+            <ChartModule {...chartData[1]} />
+          </div>
+        </div>
+      )}
+    </div>
   );
 };
-
-export default CostComparison;

+ 334 - 0
src/pages/Home/EnergyCostDetail.js

@@ -0,0 +1,334 @@
+// 能耗详情
+
+import ChartModule from '@/components/ManagementPage/chartModule';
+import PageContent from '@/components/PageContent';
+import PageTitle from '@/components/PageTitle';
+import {
+  queryAccumulativeEnergy,
+  queryChartList,
+  queryEnergyConfig,
+  queryEnergyWaterChart,
+} from '@/services/OperationManagement';
+import { ArrowDownOutlined, ArrowUpOutlined } from '@ant-design/icons';
+import { useParams, useRequest } from '@umijs/max';
+import { Spin } from 'antd';
+import dayjs from 'dayjs';
+import { useMemo } from 'react';
+import styles from './manage.less';
+
+const EnergyCostDetail = () => {
+  return (
+    <PageContent closeable={false}>
+      <PageTitle returnable>能耗数据</PageTitle>
+      <EnergyDetail />
+    </PageContent>
+  );
+};
+
+export default EnergyCostDetail;
+
+export const EnergyDetail = () => {
+  const { projectId } = useParams();
+
+  const TIMER = 3600000;
+
+  // 全场概况
+  const { data: allFactoryData, loading: allFacLoading } = useRequest(
+    queryEnergyConfig,
+    {
+      defaultParams: [projectId],
+      pollingInterval: TIMER,
+    },
+  );
+
+  // 当日累计能耗、吨水电耗、环比
+  const { data: energyData, loading: energyLoading } = useRequest(
+    queryAccumulativeEnergy,
+    {
+      defaultParams: [projectId],
+      pollingInterval: TIMER,
+    },
+  );
+
+  // 吨水电耗折线图
+  const { data: chartData, loading: chartLosding } = useRequest(
+    queryEnergyWaterChart,
+    {
+      defaultParams: [
+        {
+          project_id: Number(projectId),
+          start_time: dayjs().startOf('day').format('YYYY-MM-DD 00:00:00'),
+          end_time: dayjs().format('YYYY-MM-DD 23:59:59'),
+        },
+      ],
+      pollingInterval: TIMER,
+      formatResult(data) {
+        const tempData = data.data;
+        if (!tempData) {
+          return null;
+        }
+        return {
+          xData: tempData?.map((item) => item.data_time) || [],
+          dataList: [
+            {
+              type: 0,
+              name: '吨水电耗',
+              data: tempData?.map((item) => item.value.toFixed(2)) || [],
+            },
+          ],
+        };
+      },
+    },
+  );
+
+  // 电量折线图
+  const { data: electricChartData, loading: electricLosding } = useRequest(
+    () =>
+      queryChartList({
+        project_id: Number(projectId),
+        metric_code: 'plant_electricity',
+        start_time: dayjs()
+          .subtract(2, 'day')
+          .startOf('day')
+          .format('YYYY-MM-DD 00:00:00'),
+        end_time: dayjs().subtract(1, 'day').format('YYYY-MM-DD 23:59:59'),
+      }),
+    {
+      pollingInterval: TIMER,
+      formatResult(data) {
+        if (!data?.data) return null;
+        const tempData = data.data;
+        const reversedData = tempData.reverse();
+        return {
+          xData: reversedData?.map((item) => item.data_time) || [],
+          dataList: [
+            {
+              type: 2,
+              name: '实际',
+              data: reversedData?.map((item) => item.value) || [],
+            },
+          ],
+        };
+      },
+    },
+  );
+
+  // 全场概况  包括两个折线图的y 轴名称
+  const allFactory = useMemo(() => {
+    const data = allFactoryData || {
+      voltage: '-',
+      transformer: '-',
+      capacity: '-',
+      runtime_capacity: '-',
+      voltage_unit: 'kV',
+      transformer_unit: '台',
+      capacity_unit: 'kVA',
+      runtime_capacity_unit: 'kVA',
+      energy_unit: 'kWh/t',
+      energy_water_unit: 'kWh',
+    };
+    return [
+      {
+        title: '电量等级',
+        data: data.voltage,
+        unit: data.voltage_unit,
+        color: '#eb0ce2',
+      },
+      {
+        title: '变压器台数',
+        data: data.transformer,
+        unit: data.transformer_unit,
+        color: '#eb8c0c',
+      },
+      {
+        title: '装机容器',
+        data: data.capacity,
+        unit: data.capacity_unit,
+        color: '#0cafeb',
+      },
+      {
+        title: '运行容器',
+        data: data.runtime_capacity,
+        unit: data.runtime_capacity_unit,
+        color: '#50bb0a',
+      },
+    ];
+  }, [allFactoryData]);
+
+  const powerData = useMemo(() => {
+    const power = energyData || {
+      energy_change: '-',
+      energy_water_change: '-',
+      today_energy: '-',
+      today_energy_water: '-',
+      yesterday_energy: '-',
+      yesterday_energy_water: '-',
+    };
+
+    return [
+      {
+        title: '当日吨水电耗',
+        data:
+          typeof power.today_energy_water == 'string'
+            ? power.today_energy_water
+            : power.today_energy_water?.toFixed(2),
+        unit: 'kWh/t',
+        color: '#eb0ce2',
+      },
+      {
+        title: '昨日吨水电耗',
+        data:
+          typeof power.yesterday_energy_water == 'string'
+            ? power.yesterday_energy_water
+            : power.yesterday_energy_water?.toFixed(2),
+        unit: 'kWh/t',
+        color: '#eb8c0c',
+      },
+      {
+        title: '环比增长',
+        data:
+          typeof power.energy_water_change == 'string'
+            ? power.energy_water_change
+            : power.energy_water_change?.toFixed(2),
+        unit: '%',
+        icon: power.energy_water_change >= 0 ? 1 : 2,
+      },
+      {
+        title: '当日用电',
+        data: power.today_energy,
+        unit: 'kWh',
+        color: '#eb0ce2',
+      },
+      {
+        title: '昨日用电',
+        data: power.yesterday_energy,
+        unit: 'kWh',
+        color: '#eb8c0c',
+      },
+      {
+        title: '环比增长',
+        data:
+          typeof power.energy_change == 'string'
+            ? power.energy_change
+            : power.energy_change?.toFixed(2),
+        unit: '%',
+        icon: power.energy_change >= 0 ? 1 : 2,
+      },
+    ];
+  }, [energyData]);
+
+  const loading = useMemo(() => allFacLoading, [allFacLoading]);
+  return (
+    <Spin spinning={loading}>
+      <div className={`${styles.infoContainer} card-box`}>
+        <div>
+          <SubTitle title="全厂概览" />
+          <div style={{ display: 'flex' }}>
+            {allFactory.map((item, index) => (
+              <DataCard key={`all_${index}`} {...item} type={4} />
+            ))}
+          </div>
+        </div>
+        <div>
+          <SubTitle title="用电概况" />
+          <div style={{ display: 'flex', flexWrap: 'wrap' }}>
+            {powerData.map((item, index) => (
+              <DataCard key={`power_${index}`} {...item} type={3} />
+            ))}
+          </div>
+        </div>
+        <div>
+          <SubTitle title="吨水电耗" />
+          <div style={{ height: '3rem' }}>
+            {chartData && allFactoryData && (
+              <ChartModule
+                yName={allFactoryData?.energy_water_unit || 'kWh'}
+                xData={chartData.xData}
+                dataList={chartData.dataList}
+              />
+            )}
+          </div>
+        </div>
+        <div>
+          <SubTitle title="电量" />
+          <div style={{ height: '3rem' }}>
+            {electricChartData && allFactoryData && (
+              <ChartModule
+                yName={allFactoryData?.energy_unit || 'kWh/t'}
+                xData={electricChartData.xData}
+                dataList={electricChartData.dataList}
+              />
+            )}
+          </div>
+        </div>
+      </div>
+    </Spin>
+  );
+};
+
+const DataCard = ({ title, data, unit, icon, color, type }) => {
+  const width = Math.floor(100 / type - 2) + '%';
+  return (
+    <div style={{ width }} className={`${styles.itemContent} card-box`}>
+      <div style={{ fontSize: '0.26rem' }}>{title}</div>
+      <div style={{ position: 'relative' }}>
+        <span
+          style={{ color: !icon ? color : icon === 1 ? 'red' : '#50bb0a' }}
+          className={styles.data}
+        >
+          {data}
+        </span>
+        {unit}
+        {icon === 1 && (
+          <ArrowUpOutlined
+            style={{
+              height: '0.2rem',
+              color: 'red',
+              position: 'absolute',
+              fontSize: '0.34rem',
+              right: '0.4rem',
+            }}
+          />
+        )}
+        {icon === 2 && (
+          <ArrowDownOutlined
+            style={{
+              height: '0.2rem',
+              color: '#50bb0a',
+              position: 'absolute',
+              fontSize: '0.36rem',
+              right: '0.4rem',
+            }}
+          />
+        )}
+      </div>
+    </div>
+  );
+};
+
+const SubTitle = ({ title }) => {
+  return (
+    <div
+      style={{
+        display: 'flex',
+        justifyContent: 'flex-start',
+        alignItems: 'center',
+        marginBottom: '0.2rem',
+        fontSize: '0.28rem',
+        fontWeight: '600',
+        width: '80%',
+      }}
+    >
+      <div
+        style={{
+          width: '0.15rem',
+          height: '0.15rem',
+          background: '#0139f1',
+          marginRight: '0.1rem',
+          borderRadius: '0.16rem',
+        }}
+      />
+      {title}
+    </div>
+  );
+};

+ 99 - 30
src/pages/Home/QualityMng.js

@@ -3,21 +3,39 @@ import ChartModule from '@/components/ManagementPage/chartModule';
 // import SearchModule from '@/components/ManagementPage/searchModule';
 import PageContent from '@/components/PageContent';
 import PageTitle from '@/components/PageTitle';
+import TabsContent from '@/components/TabsContent';
 import {
   queryChartListByCode,
   queryProcessSection,
   querySectionCode,
 } from '@/services/OperationManagement';
+import { queryConditionSnapshot } from '@/services/SmartOps';
 import { UnityAction } from '@/utils/utils';
 import { useParams, useRequest } from '@umijs/max';
 import { Empty, Spin, Table } from 'antd';
 import dayjs from 'dayjs';
 import { useEffect, useMemo, useRef, useState } from 'react';
+import SubTitle from '../SmartOps/components/SubTitle';
+import { getValue } from './index';
+import styles from './index.less';
 
 function Quality() {
+  return (
+    <PageContent closeable={false}>
+      <PageTitle onReturn={() => UnityAction.sendMsg('menuItem', '首页')}>
+        水质监测
+      </PageTitle>
+      <WaterQuality showTip />
+    </PageContent>
+  );
+}
+
+export default Quality;
+
+export const WaterQuality = ({ showTip = false }) => {
   const { projectId } = useParams();
   const [currentCode, setCode] = useState(null);
-  // const [processId, setProcessId] = useState(null);
+  const [processList, setProcessList] = useState([]);
 
   const timerRef = useRef({
     s_time: dayjs().subtract(1, 'day').format('YYYY-MM-DD HH:mm:ss'),
@@ -31,16 +49,24 @@ function Quality() {
         setCode(res[0]);
       }
     },
+    formatResult: (result) => {
+      if (result?.data) {
+        return result.data.reverse();
+      }
+      return [];
+    },
   });
 
   // 获取工艺段列表
   useRequest(queryProcessSection, {
     defaultParams: [projectId],
     onSuccess(res) {
-      // setProcessId(res.data[0].id);
+      setProcessList(res);
       queryCodeList(res[0].id, 2, projectId * 1);
+      return res;
     },
   });
+
   const mainRes = useRequest(
     () => {
       return queryChartListByCode(
@@ -57,6 +83,21 @@ function Quality() {
       manual: true,
     },
   );
+
+  const { data: snapshot } = useRequest(queryConditionSnapshot, {
+    defaultParams: [{ project_id: projectId }],
+    pollingInterval: 10 * 1000,
+  });
+
+  const status = useMemo(() => {
+    switch (snapshot?.water_quality_status) {
+      case 1:
+        return '当前水质良好';
+      case 2:
+        return '当前水质较好';
+    }
+  }, [snapshot?.water_quality_status]);
+
   const column = useMemo(() => {
     if (!currentCode) return [];
     return [
@@ -101,29 +142,62 @@ function Quality() {
     if (code) setCode(code);
   };
 
-  return (
-    <PageContent closeable={false}>
-      <PageTitle onReturn={() => UnityAction.sendMsg('menuItem', '首页')}>
-        水质监测
-      </PageTitle>
+  const handleProcessChange = (val) => {
+    queryCodeList(val, 2, projectId);
+  };
 
-      <div className="card-box" style={{ padding: '0.2rem' }}>
-        <div className="tabs" style={{ marginBottom: '0.2rem' }}>
-          {codeList?.map((item) => (
-            <div
-              onClick={() => {
-                setCode(item);
-              }}
-              className={`tabs-item ${item == currentCode ? 'active' : ''}`}
-            >
-              {item.metric}
+  return (
+    <div style={{ marginTop: '0.1rem', position: 'relative' }}>
+      {showTip && <div className={styles.pageTip}>{status}</div>}
+      <div className="card-box">
+        <ul className={styles.pageTop}>
+          <li>
+            <div className={styles.pageTopValue}>
+              {getValue(snapshot?.dtds)}
             </div>
-          ))}
-        </div>
-        <div className="section-title">
-          <div className="section-line"></div>
-          数据曲线
+            <div>外供水电导率(µs/cm)</div>
+          </li>
+          <li>
+            <div className={styles.pageTopValue}>{getValue(snapshot?.dph)}</div>
+            <div>外供水(PH)</div>
+          </li>
+        </ul>
+      </div>
+      <div className="card-box" style={{ padding: '0.2rem' }}>
+        <div style={{ padding: '0.2rem 0' }}>
+          <TabsContent
+            defaultActiveKey="19"
+            center={false}
+            small
+            spacing={2.5}
+            items={processList
+              ?.map((item) => {
+                return {
+                  key: item.id,
+                  label: item.name,
+                  children: null,
+                };
+              })
+              .filter((item) => item.label !== '膜车间全景')}
+            onChange={handleProcessChange}
+          />
         </div>
+        {codeList?.length > 0 && (
+          <div className="tabs" style={{ marginBottom: '0.2rem' }}>
+            {codeList?.map((item) => (
+              <div
+                key={item.metric_code}
+                onClick={() => {
+                  setCode(item);
+                }}
+                className={`tabs-item ${item === currentCode ? 'active' : ''}`}
+              >
+                {item.metric}
+              </div>
+            ))}
+          </div>
+        )}
+        <SubTitle title="数据曲线" />
         <Spin spinning={mainRes.loading}>
           <div style={{ height: '5rem', marginTop: '0.2rem' }}>
             {mainRes?.data ? (
@@ -134,10 +208,7 @@ function Quality() {
           </div>
         </Spin>
         <div style={{ marginTop: '0.3rem' }}>
-          <div className="section-title">
-            <div className="section-line"></div>
-            数据列表
-          </div>
+          <SubTitle title="数据列表" />
           <Table
             columns={column}
             style={{ marginTop: '0.2rem' }}
@@ -146,8 +217,6 @@ function Quality() {
           />
         </div>
       </div>
-    </PageContent>
+    </div>
   );
-}
-
-export default Quality;
+};

+ 142 - 31
src/pages/Home/WaterAmtMng.js

@@ -4,21 +4,50 @@ import ChartModule from '@/components/ManagementPage/chartModule';
 import PageContent from '@/components/PageContent';
 import PageTitle from '@/components/PageTitle';
 import { queryChartListByCode } from '@/services/OperationManagement';
+import { queryConditionSnapshot } from '@/services/SmartOps';
 import { UnityAction } from '@/utils/utils';
 import { useParams, useRequest } from '@umijs/max';
-import { Spin, Table } from 'antd';
+import { Button, DatePicker, Spin, Table } from 'antd';
 import dayjs from 'dayjs';
-import { useMemo } from 'react';
+import { useMemo, useState } from 'react';
+import SubTitle from '../SmartOps/components/SubTitle';
+import { getValue } from './index';
+import styles from './index.less';
+
+const { RangePicker } = DatePicker;
 
 const WaterAmtMng = () => {
+  return (
+    <PageContent closeable={false}>
+      <PageTitle onReturn={() => UnityAction.sendMsg('menuItem', '首页')}>
+        水量监测
+      </PageTitle>
+      <WaterAmt showTip />
+    </PageContent>
+  );
+};
+
+export default WaterAmtMng;
+
+export const WaterAmt = ({ showTip }) => {
   const { projectId } = useParams();
 
+  const [filter, setFilter] = useState([dayjs().subtract(1, 'day'), dayjs()]);
+
+  const { data: snapshot } = useRequest(queryConditionSnapshot, {
+    defaultParams: [{ project_id: projectId }],
+    pollingInterval: 10 * 1000,
+    formatResult: (result) => {
+      return result?.data;
+    },
+  });
+
   const { data, loading, run } = useRequest(
     (date) =>
       queryChartListByCode(
         {
-          start_time: date.s_time,
-          end_time: date.e_time,
+          start_time: date?.s_time,
+          end_time: date?.e_time,
           project_id: Number(projectId),
           order: 1,
         },
@@ -34,6 +63,17 @@ const WaterAmtMng = () => {
     },
   );
 
+  const status = useMemo(() => {
+    switch (snapshot?.dwa_status) {
+      case 1:
+        return '当前处于外供水高峰期';
+      case 2:
+        return '当前处于外供水平时期';
+      case 3:
+        return '当前处于外供水低峰期';
+    }
+  }, [snapshot?.dwa_status]);
+
   const columns = [
     {
       title: '时间',
@@ -49,6 +89,7 @@ const WaterAmtMng = () => {
     },
     {
       title: '预测出水水量(t)',
+      key: '-',
       // dataIndex: '',
       render: () => '-',
     },
@@ -74,40 +115,110 @@ const WaterAmtMng = () => {
   }, [data]);
 
   const onSearch = (date) => {
-    run(date);
+    if (date === null) {
+      setFilter([]);
+      return;
+    }
+    if ('string' === typeof date) {
+      setFilter([]);
+      run({
+        s_time: dayjs().subtract(1, date).format('YYYY-MM-DD 00:00:00'),
+        e_time: dayjs().format('YYYY-MM-DD 23:59:59'),
+      });
+    } else {
+      const [s_time, e_time] = date.map((time) =>
+        dayjs(time).format('YYYY-MM-DD'),
+      );
+      setFilter(date);
+      run({
+        s_time: dayjs(s_time).format('YYYY-MM-DD 00:00:00'),
+        e_time: dayjs(e_time).format('YYYY-MM-DD 23:59:59'),
+      });
+    }
   };
 
   return (
-    <PageContent closeable={false}>
-      <PageTitle onReturn={() => UnityAction.sendMsg('menuItem', '首页')}>
-        水量监测
-      </PageTitle>
-      <div className="card-box" style={{ padding: '0.2rem' }}>
-        <div className="section-title">
-          <div className="section-line"></div>
-          数据曲线
+    <Spin spinning={loading}>
+      <div style={{ padding: '0.2rem' }}>
+        {showTip && <div className={styles.pageTip}>{status}</div>}
+        <div className="card-box">
+          <ul className={styles.pageTop}>
+            <li>
+              <div className={styles.pageTopValue}>
+                {getValue(snapshot?.fwa)}
+              </div>
+              <div>进水量(m³/h)</div>
+            </li>
+            <li>
+              <div className={styles.pageTopValue}>
+                {getValue(snapshot?.dwa)}
+              </div>
+              <div>产水量(m³/h)</div>
+            </li>
+          </ul>
         </div>
-        <Spin spinning={loading}>
-          <div style={{ height: '5rem', marginTop: 20 }}>
-            <ChartModule yName="水量(t)" xData={xData} dataList={dataList} />
+        <div className="card-box" style={{ padding: '0.2rem ' }}>
+          <SubTitle title="数据曲线" />
+          <div className={styles.timeSelectBox}>
+            <div style={{ fontSize: '0.3rem' }}>
+              <RangePicker
+                style={{ width: '4rem' }}
+                allowClear
+                value={filter}
+                inputReadOnly
+                onChange={(time) => {
+                  onSearch(time);
+                }}
+              />
+            </div>
+            <div className={styles.timeBtn}>
+              <Button
+                type="primary"
+                shape="round"
+                onClick={() => {
+                  onSearch('day');
+                }}
+              >
+                近一天
+              </Button>
+              <Button
+                type="primary"
+                shape="round"
+                onClick={() => {
+                  onSearch('week');
+                }}
+              >
+                近一周
+              </Button>
+              <Button
+                type="primary"
+                shape="round"
+                onClick={() => {
+                  onSearch('month');
+                }}
+              >
+                近一个月
+              </Button>
+            </div>
           </div>
-        </Spin>
-        <div style={{ marginTop: 30 }}>
-          <div className="section-title">
-            <div className="section-line"></div>
-            数据列表
+          <Spin spinning={loading}>
+            <div style={{ height: '5rem', marginTop: 20 }}>
+              <ChartModule yName="水量(t)" xData={xData} dataList={dataList} />
+            </div>
+          </Spin>
+          <div style={{ marginTop: 30 }}>
+            <SubTitle title="数据列表" />
+            <Table
+              loading={loading}
+              columns={columns}
+              rowKey="time"
+              style={{ marginTop: 20 }}
+              pagination={false}
+              dataSource={data?.sort((a, b) => b?.time?.localeCompare(a?.time))}
+            />
           </div>
-          <Table
-            loading={loading}
-            columns={columns}
-            style={{ marginTop: 20 }}
-            pagination={false}
-            dataSource={data?.sort((a, b) => b?.time?.localeCompare(a?.time))}
-          />
         </div>
       </div>
-    </PageContent>
+    </Spin>
   );
 };
-
-export default WaterAmtMng;

+ 68 - 0
src/pages/Home/backlog.js

@@ -0,0 +1,68 @@
+// 药耗监测
+import PageContent from '@/components/PageContent';
+import PageTitle from '@/components/PageTitle';
+import { getPendingList } from '@/services/message';
+import { UnityAction } from '@/utils/utils';
+import { useParams, useRequest } from '@umijs/max';
+import dayjs from 'dayjs';
+import styles from './backlog.less';
+
+const CostComparison = () => {
+  const { projectId } = useParams();
+  const { data, loading } = useRequest(getPendingList, {
+    defaultParams: [{ project_id: projectId }],
+  });
+  const handleClick = (item) => {
+    if (item.type === 0) {
+      // task
+      UnityAction.sendMsg('OpenTaskModal', `mandate_id=${item.origin_id}`);
+    } else {
+      // order
+      UnityAction.sendMsg(
+        'OpenWorkOrderModal',
+        `order_id=${item.origin_id}&order_type=${item.origin_type}`,
+      );
+    }
+  };
+  return (
+    <PageContent closeable={false}>
+      <PageTitle onReturn={() => UnityAction.sendMsg('menuItem', '首页')}>
+        待办事项
+      </PageTitle>
+
+      <div className={styles.backlog}>
+        <div>
+          {data?.map((item) => (
+            <div
+              key={item.connect}
+              className={`${styles.item} ${
+                item.type == 0 ? styles.task : styles.order
+              }`}
+              onClick={(e) => {
+                e.stopPropagation();
+                handleClick(item);
+              }}
+            >
+              <div className={styles.createTime}>
+                {dayjs(item.time).format('MM-DD HH:mm')}
+              </div>
+              <div className={styles.point} />
+              <div className={styles.titleCon}>
+                <div className={styles.titleText}>{item.title}</div>
+              </div>
+
+              <div className={styles.bottomCon}>
+                {item.content}
+                <div className={styles.btn}>
+                  {item.type == 0 ? '任务' : '工单'}详情
+                </div>
+              </div>
+            </div>
+          ))}
+        </div>
+      </div>
+    </PageContent>
+  );
+};
+
+export default CostComparison;

+ 68 - 0
src/pages/Home/backlog.less

@@ -0,0 +1,68 @@
+.backlog {
+  margin-right: 6px;
+  margin-bottom: 40px;
+  overflow-y: auto;
+  height: 100%;
+  color: #3b3b3b;
+  .item {
+    padding: 8px 0;
+    width: 100%;
+    font-size: 24px;
+    margin-bottom: 20px;
+    line-height: 50px;
+    position: relative;
+    background: #fff;
+    padding-right: 24px;
+    &.order {
+      .point,
+      .btn {
+        background-color: #2576f5;
+      }
+    }
+    &.task {
+      .point,
+      .btn {
+        background-color: #ff8400;
+      }
+    }
+  }
+  .titleCon {
+    display: flex;
+    align-items: center;
+  }
+  .point {
+    display: inline-block;
+    width: 12px;
+    position: absolute;
+    left: 0;
+    top: 0;
+    height: 100%;
+  }
+  .createTime {
+    float: right;
+  }
+  .titleText {
+    font-size: 24px;
+    margin: 0 20px;
+    margin-left: 24px;
+    font-family: Source Han Sans, Source Han Sans;
+  }
+  .bottomCon {
+    margin-left: 24px;
+    font-size: 20px;
+    line-height: 36px;
+    word-wrap: break-word;
+    font-family: Source Han Sans, Source Han Sans;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    .btn {
+      flex-shrink: 0;
+      padding: 6px 20px;
+      border-radius: 12px;
+      font-size: 24px;
+      color: #fff;
+      margin-left: 20px;
+    }
+  }
+}

+ 159 - 54
src/pages/Home/index.js

@@ -1,11 +1,11 @@
 import { getPendingList } from '@/services/message';
-import { getComparisonData } from '@/services/OperationManagement';
 import { queryConditionSnapshot } from '@/services/SmartOps';
 import { getToken, UnityAction } from '@/utils/utils';
 import { LoadingOutlined } from '@ant-design/icons';
 import { connect, useParams, useRequest } from '@umijs/max';
+import { Popover } from 'antd';
 import dayjs from 'dayjs';
-import { useEffect } from 'react';
+import { useEffect, useMemo, useState } from 'react';
 import { getScadaPage } from '../../services/OperationManagement';
 import styles from './index.less';
 
@@ -13,6 +13,7 @@ const HomePage = (props) => {
   const { projectId } = useParams();
   const { data } = useRequest(queryConditionSnapshot, {
     defaultParams: [{ project_id: projectId }],
+    pollingInterval: 10 * 1000,
   });
 
   const getPositionPst = (e) => {
@@ -35,6 +36,7 @@ const HomePage = (props) => {
   useEffect(() => {
     localStorage.width = document.documentElement.getBoundingClientRect().width;
     window.refreshRem();
+    document.body.style.backgroundColor = 'transparent';
   }, []);
   return (
     <div className={styles.content} {...webMouseEvent}>
@@ -74,13 +76,19 @@ const RightContent = (props) => {
     <div className={styles.right}>
       {/* <SelfInspection /> */}
       <Electric data={data} />
-      <Medicine />
+      <Medicine data={data} />
       <Scada />
       {/* <Scada /> */}
     </div>
   );
 };
 
+export const getValue = (str) => {
+  const result = str?.match(/.*?(\d+(?:\.\d+)?)\D*$/);
+  if (result && result[1]) return result[1];
+  return 0;
+};
+
 // 水厂工况
 const SmartWork = (props) => {
   const { data } = props;
@@ -110,21 +118,32 @@ const SmartWork = (props) => {
 // 水量监测
 const WaterAmt = (props) => {
   const { data } = props;
-  const { projectId } = useParams();
+  const status = useMemo(() => {
+    switch (data?.dwa_status) {
+      case 1:
+        return '当前处于外供水高峰期';
+      case 2:
+        return '当前处于外供水平时期';
+      case 3:
+        return '当前处于外供水低峰期';
+    }
+  }, [data?.dwa_status]);
+
   return (
     <div
       className={styles.waterAmt}
       onClick={() => UnityAction.sendMsg('menuItem', '水量监测')}
     >
       <Title title="水量监测" />
+      <div className={styles.boxTip}>{status}</div>
       <ul>
         <li>
-          <div className={styles.value}>{data?.fwa}</div>
-          <div className={styles.btn1}>进水量</div>
+          <div className={styles.value}>{getValue(data?.fwa)}</div>
+          <div className={styles.btn1}>进水量(m³/h)</div>
         </li>
         <li>
-          <div className={styles.value}>{data?.dwa}</div>
-          <div className={styles.btn2}>产水量</div>
+          <div className={styles.value}>{getValue(data?.dwa)}</div>
+          <div className={styles.btn2}>产水量(m³/h)</div>
         </li>
       </ul>
     </div>
@@ -134,21 +153,30 @@ const WaterAmt = (props) => {
 // 水质监测
 const WaterQuality = (props) => {
   const { data } = props;
-  const { projectId } = useParams();
+  const status = useMemo(() => {
+    switch (data?.water_quality_status) {
+      case 1:
+        return '当前水质良好';
+      case 2:
+        return '当前水质较好';
+    }
+  }, [data?.water_quality_status]);
   return (
     <div
       className={styles.waterQuality}
       onClick={() => UnityAction.sendMsg('menuItem', '水质监测')}
     >
       <Title title="水质监测" />
+      <div className={styles.boxTip}>{status}</div>
       <ul>
-        <li>
-          <div className={styles.valueLong}>{data?.dsan || 0}</div>
-          <div className={styles.btn1}>出水余氯</div>
+        <li style={{ width: '60%' }}>
+          <div className={styles.valueLong}>{getValue(data?.dtds)}</div>
+          <div className={styles.btn1}>外供水电导率(µs/cm)</div>
         </li>
-        <li>
-          <div className={styles.valueLong}>{data?.dtur || 0}</div>
-          <div className={styles.btn2}>出水浊度</div>
+
+        <li style={{ width: '40%' }}>
+          <div className={styles.valueLong}>{data?.dph || 0}</div>
+          <div className={styles.btn2}>外供水(PH)</div>
         </li>
       </ul>
     </div>
@@ -214,54 +242,130 @@ const SelfInspection = connect(({ eqSelfInspection, loading }) => ({
 // 能耗监测
 const Electric = (props) => {
   const { data } = props;
-  const { projectId } = useParams();
+  const [open, setOpen] = useState(false);
+
+  const elec = useMemo(() => {
+    return getValue(data?.elec_unit);
+  }, [data?.elec_unit]);
+
+  const status = useMemo(() => {
+    if (!data) return '';
+    if (elec > data.elec_unit_theory) {
+      return '当前电耗高于理论值';
+    }
+    if (elec == data.elec_unit_theory) {
+      return '当前电耗持平理论值';
+    }
+    if (elec < data.elec_unit_theory) {
+      return '当前电耗低于理论值';
+    }
+  }, [data]);
+  const content = (
+    <div className={styles.popoverContent}>
+      <p>理论值规则:</p>
+      <p>分为高/中/低温3档。则高温为≥25℃,低温为<20℃,中温为≥20且<25℃;</p>
+      <p>
+        吨水电耗理论值:高温阶段理论值暂定为0.77Kwh/m3;高温阶段理论值暂定为0.83Kwh/m3;低温阶段理论值暂定为0.89Kwh/m3。
+      </p>
+    </div>
+  );
+
   return (
     <div
       className={styles.electric}
       onClick={() => UnityAction.sendMsg('menuItem', '能耗监测')}
     >
-      <Title title="能耗监测" />
+      <Title title={'能耗监测'} />
+      <div className={styles.boxTip}>{status}</div>
+
+      <div
+        className={`password-eye ${styles.eye} ${open ? 'open' : ''}`}
+        onClick={(e) => {
+          e.stopPropagation();
+          setOpen(!open);
+        }}
+      ></div>
       <ul>
         <li>
-          <div className={styles.value}>{data?.elec}</div>
-          <div className={styles.btn1}>用电量</div>
+          <div className={styles.value}>{open ? elec : '*****'}</div>
+          <div className={styles.btn1}>吨水电耗(KWh/m³)</div>
+        </li>
+        <li>
+          <div className={styles.value}>
+            {/* {open ? getValue(data?.elec) : '*****'} */}
+            {open ? data?.elec_unit_theory || '-' : '*****'}
+          </div>
+          <Popover title={content}>
+            <div className={styles.btn1} onClick={(e) => e.stopPropagation()}>
+              理论值(KWh/m³)
+              <i className={styles.iconAlert}></i>
+            </div>
+          </Popover>
         </li>
       </ul>
     </div>
   );
 };
 // 药耗监测
-const Medicine = () => {
-  const { projectId } = useParams();
-  const time = dayjs().format('YYYY-MM');
-
-  const { data } = useRequest(getComparisonData, {
-    defaultParams: [
-      {
-        project_id: projectId,
-        start: time,
-        end: time,
-        type: 1,
-        flag: 1,
-      },
-    ],
-    formatResult(res) {
-      return res[0];
-    },
-  });
+const Medicine = (props) => {
+  const { data } = props;
+  const [open, setOpen] = useState(false);
 
+  const otc_cost = useMemo(() => {
+    return getValue(data?.otc_cost_unit);
+  }, [data?.otc_cost_unit]);
+
+  const status = useMemo(() => {
+    if (!data) return '';
+    if (otc_cost > data.otc_unit_theory) {
+      return '当前药耗高于理论值';
+    }
+    if (otc_cost == data.otc_unit_theory) {
+      return '当前药耗持平理论值';
+    }
+    if (otc_cost < data.otc_unit_theory) {
+      return '当前药耗低于理论值';
+    }
+  }, [data]);
+  const content = (
+    <div className={styles.popoverContent}>
+      <p>理论值规则:</p>
+      <p>分为高/中/低温3档。则高温为≥25℃,低温为<20℃,中温为≥20且<25℃;</p>
+      <p>
+        吨水药耗理论值:高温阶段理论值暂定为0.165元/m3;中温阶段理论值暂定为0.177元/m3;低温阶段理论值暂定为0.189元/m3。
+      </p>
+    </div>
+  );
   return (
     <div
       className={styles.medicine}
       onClick={() => UnityAction.sendMsg('menuItem', '药耗监测')}
     >
-      <Title title="药耗监测" />
+      <Title title={'药耗监测'} />
+      <div className={styles.boxTip}>{status}</div>
+
+      <div
+        className={`password-eye ${styles.eye} ${open ? 'open' : ''}`}
+        onClick={(e) => {
+          e.stopPropagation();
+          setOpen(!open);
+        }}
+      ></div>
       <ul>
+        <li>
+          <div className={styles.valueLong}>{open ? otc_cost : '*****'}</div>
+          <div className={styles.btn1}>吨水药成本(元/m³)</div>
+        </li>
         <li>
           <div className={styles.valueLong}>
-            {data?.value?.toFixed(2) || '-'}元
+            {open ? data?.otc_unit_theory || '-' : '*****'}
           </div>
-          <div className={styles.btn1}>当月吨水药成本</div>
+          <Popover title={content}>
+            <div className={styles.btn1} onClick={(e) => e.stopPropagation()}>
+              理论值(元/m³)
+              <i className={styles.iconAlert}></i>
+            </div>
+          </Popover>
         </li>
       </ul>
     </div>
@@ -303,6 +407,7 @@ const Scada = () => {
               height: '279px',
               borderRadius: '0 0 44px 0',
             }}
+            frameBorder="0"
             src={url}
           />
         ))}
@@ -320,16 +425,16 @@ const Backlog = (props) => {
     defaultParams: [{ project_id: projectId }],
   });
   const handleClick = (item) => {
-    if (item.type === 0) {
-      // task
-      UnityAction.sendMsg('OpenTaskModal', `mandate_id=${item.origin_id}`);
-    } else {
-      // order
-      UnityAction.sendMsg(
-        'OpenWorkOrderModal',
-        `order_id=${item.origin_id}&order_type=${item.origin_type}`,
-      );
-    }
+    // if (item.type === 0) {
+    //   // task
+    //   UnityAction.sendMsg('OpenTaskModal', `mandate_id=${item.origin_id}`);
+    // } else {
+    //   // order
+    //   UnityAction.sendMsg(
+    //     'OpenWorkOrderModal',
+    //     `order_id=${item.origin_id}&order_type=${item.origin_type}`,
+    //   );
+    // }
   };
   return (
     <div
@@ -344,10 +449,10 @@ const Backlog = (props) => {
             <div
               key={item.connect}
               className={styles.item}
-              onClick={(e) => {
-                e.stopPropagation();
-                handleClick(item);
-              }}
+              // onClick={(e) => {
+              //   e.stopPropagation();
+              //   handleClick(item);
+              // }}
             >
               <div className={styles.createTime}>
                 {dayjs(item.time).format('MM-DD HH:mm')}

+ 78 - 2
src/pages/Home/index.less

@@ -35,6 +35,30 @@
     }
   }
 }
+
+.eye {
+  position: absolute;
+  top: 20px;
+  left: 190px;
+}
+.popoverContent {
+  max-width: 5rem;
+  font-size: 20px;
+  p {
+    margin-bottom: 0;
+    line-height: 36px;
+  }
+}
+.iconAlert {
+  width: 28px;
+  height: 28px;
+  display: block;
+  float: right;
+  margin-top: 5px;
+  margin-left: 6px;
+  background: url('@/assets/home/i.png') no-repeat center;
+  background-size: 100% 100%;
+}
 .smartWork {
   .box;
   width: 604px;
@@ -110,6 +134,15 @@
   line-height: 34px;
   z-index: 1;
 }
+.boxTip {
+  float: right;
+  top: 26px;
+  right: 20px;
+  position: inherit;
+  font-size: 24px;
+  font-family: Source Han Sans, Source Han Sans;
+  color: #3b3b3b;
+}
 .line {
   position: absolute;
   bottom: 0;
@@ -172,7 +205,7 @@
   display: flex;
   justify-content: space-around;
   .score {
-    font-size: 50px;
+    font-size: 48px;
     font-weight: 600;
     font-family: PangMenZhengDao-3, PangMenZhengDao-3;
     color: #615d5d;
@@ -290,7 +323,7 @@
 .btn1 {
   margin-bottom: 20px;
   padding: 14px 28px;
-  font-size: 32px;
+  font-size: 28px;
   font-family: Source Han Sans, Source Han Sans;
   font-weight: 500;
   color: #ffffff;
@@ -330,3 +363,46 @@
     color: #3b3b3b;
   }
 }
+
+.timeSelectBox {
+  padding: 0.1rem 0;
+  display: flex;
+  justify-content: flex-start;
+  align-items: center;
+
+  .timeBtn > * {
+    margin-right: 0.1rem;
+  }
+  .timeBtn {
+    margin-left: 0.1rem;
+  }
+
+  :global {
+    .ant-btn {
+      height: 0.5rem;
+    }
+  }
+}
+.pageTip {
+  position: absolute;
+  top: -0.6rem;
+  right: 0.3rem;
+  font-size: 0.28rem;
+}
+.pageTop {
+  display: flex;
+  padding-top: 0.4rem;
+  padding-bottom: 0.4rem;
+  margin-bottom: 0.4rem;
+  // border-bottom: 1px solid #e8e8e8;
+  li {
+    width: 50%;
+    text-align: center;
+    font-size: 0.22rem;
+    .pageTopValue {
+      color: #f5a623;
+      font-size: 0.48rem;
+      margin-bottom: 0.1rem;
+    }
+  }
+}

+ 22 - 3
src/pages/Home/manage.less

@@ -4,16 +4,24 @@
   font-size: 0.24rem;
   color: #02a7f0;
 }
+
+.infoContainer {
+  padding: 0.2rem;
+}
+
+.infoContainer > * {
+  margin-bottom: 0.2rem;
+}
+
 .itemContent {
   margin: 0.06rem 0.06rem;
   text-align: center;
   flex-grow: 1;
-  background-color: #2f62ae;
   width: 20%;
   padding: 0.1rem 0;
   .data {
     color: palevioletred;
-    font-size: 0.24rem;
+    font-size: 0.26rem;
     margin-right: 0.06rem;
   }
 }
@@ -52,13 +60,24 @@
     // }
   }
 }
+
+.pageTip {
+  position: absolute;
+  top: -0.6rem;
+  right: 0.3rem;
+  font-size: 0.28rem;
+}
+
 .curEnergyCost {
   display: flex;
+  flex-wrap: wrap;
   justify-content: center;
   align-items: center;
-  margin: 0.4rem 0.4rem 0 0.4rem;
+  margin: 0rem 0.4rem 0 0.4rem;
+  padding: 0.3rem 0;
   .item {
     width: 50%;
+    padding: 0.1rem;
     letter-spacing: 0.02rem;
     position: relative;
     font-size: 0.24rem;

+ 6 - 2
src/pages/Menu/index.js

@@ -1,5 +1,5 @@
 import { UnityAction } from '@/utils/utils';
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
 import styles from './index.less';
 
 const menuList = [
@@ -24,7 +24,7 @@ const menuList = [
     // path: (projectId) => `/hardware-controller/${projectId}`,
   },
   {
-    name: '数字孪生',
+    name: 'AR水厂',
     icon: require('@/assets/menu/number.png'),
   },
   {
@@ -61,6 +61,10 @@ function Menu() {
     UnityAction.sendMsg('HideMenu');
   };
 
+  useEffect(() => {
+    document.body.style.backgroundColor = 'transparent';
+  }, []);
+
   return (
     <div className={styles.main}>
       <div className={styles.menu}>

+ 44 - 4
src/pages/MessageCenter/index.js

@@ -9,15 +9,24 @@ import { useState } from 'react';
 import styles from './index.less';
 const icon1 = require('@/assets/message/work.png');
 const icon2 = require('@/assets/message/check.png');
+const icon3 = require('@/assets/message/warning.png');
 
 const MessageCenter = () => {
   const { projectId } = useParams();
   const [tab, setTab] = useState('2');
-  //, msgType: 工况:11, 自检:12
+  //, msgType: 工况:11, 自检:12,  预警:13
   const { data, run, loading } = useRequest(() =>
     getNotificationList({ projectId, msgType: 11 }, { manual: true }),
   );
 
+  const {
+    data: dataWarning,
+    run: runWarning,
+    loading: loadingWarning,
+  } = useRequest(() =>
+    getNotificationList({ projectId, msgType: 13 }, { manual: true }),
+  );
+
   const {
     data: dataSelf,
     run: runSelf,
@@ -26,7 +35,17 @@ const MessageCenter = () => {
 
   const handleTabsChange = (tab) => {
     setTab(tab);
-    tab == '1' ? run() : runSelf();
+    switch (tab) {
+      case '1':
+        run();
+        break;
+      case '2':
+        runSelf();
+        break;
+      case '3':
+        runWarning();
+        break;
+    }
   };
 
   const handleReadClick = () => {
@@ -39,7 +58,7 @@ const MessageCenter = () => {
       UnityAction.sendMsg('OpenTaskModal', `mandate_id=${item.MandateId}`);
       // }
     } else {
-      UnityAction.sendMsg('notiZiJian', item.ID);
+      UnityAction.sendMsg('notiZiJian', item.PatrolId);
     }
   };
 
@@ -47,10 +66,22 @@ const MessageCenter = () => {
     const time = item?.CreatedOn
       ? dayjs(item.CreatedOn).format('YYYY-MM-DD HH:mm')
       : '';
+    let icon = '';
+    switch (tab) {
+      case '1':
+        icon = icon1;
+        break;
+      case '2':
+        icon = icon2;
+        break;
+      case '3':
+        icon = icon3;
+        break;
+    }
     return (
       <div className={`card-box ${styles.itemContent}`}>
         <div className={styles.left}>
-          <img className={styles.img} src={tab == '1' ? icon1 : icon2} />
+          <img className={styles.img} src={icon} />
           <div>
             <div className={styles.text}>{item.MsgBody}</div>
             <div className={styles.time}>{time}</div>
@@ -88,6 +119,15 @@ const MessageCenter = () => {
               </Spin>
             ),
           },
+          {
+            label: `预警数据`,
+            key: '3',
+            children: (
+              <Spin spinning={loadingWarning}>
+                {dataWarning?.list?.map((item) => renderItem(item))}
+              </Spin>
+            ),
+          },
           {
             label: `水厂工况`,
             key: '1',

+ 4 - 10
src/pages/Projects/index.js

@@ -70,21 +70,15 @@ const Projects = () => {
                   <div className={styles.itemCon}>
                     <div className={styles.line} />
                     <div className={styles.valueCon}>
-                      <span className={styles.value}>
-                        {item.WaterIn}
-                        {/* <div className={styles.valueCon}>m3/h</div> */}
-                      </span>
-                      <div>进水流量(m3/h)</div>
+                      <span className={styles.value}>{item.WaterIn}</span>
+                      <div>进水流量(m³/h)</div>
                     </div>
                   </div>
                   <div className={styles.itemCon}>
                     <div className={styles.line} />
                     <div className={styles.valueCon}>
-                      <span className={styles.value}>
-                        {item.WaterOut}
-                        {/* <div className={styles.valueCon}>m3/h</div> */}
-                      </span>
-                      <div>出水流量(m3/h)</div>
+                      <span className={styles.value}>{item.WaterOut}</span>
+                      <div>出水流量(m³/h)</div>
                     </div>
                   </div>
                 </div>

+ 89 - 0
src/pages/SafetyManagement/Command/index.js

@@ -0,0 +1,89 @@
+import { checkPW } from '@/services/safety';
+import { UnityAction } from '@/utils/utils';
+import { Modal } from 'antd';
+import md5 from 'md5';
+import { useState } from 'react';
+import styles from './index.less';
+const backIcon = require('@/assets/keyRemove.png');
+
+const CommandModal = () => {
+  const keys = [1, 2, 3, 4, 5, 6, 7, 8, 9, '清空', 0, 12];
+  const [values, setValues] = useState([]);
+
+  const handlerClick = (key) => {
+    if (key == 12) {
+      const newValues = [...values];
+      setValues(newValues.splice(0, values.length - 1));
+    } else if (key == '清空') {
+      setValues([]);
+    } else {
+      const newValues = [...values, key];
+      setValues(newValues);
+      if (newValues.length == 8) handleCheckPw(newValues.join(''));
+    }
+  };
+  const handleCheckPw = async (value) => {
+    const pw = md5(value);
+    const res = await checkPW({ pw });
+    if (res) {
+      UnityAction.sendMsg('CommandSuccess');
+      // message.success('已切换成【控制模式】');
+    }
+  };
+  const handleClose = () => {
+    UnityAction.sendMsg('CommandClose');
+  };
+  return (
+    <div className={styles.modelMain}>
+      <Modal
+        title="请输入口令"
+        cancelText="取消"
+        okText="确定"
+        width={640}
+        open={true}
+        mask={false}
+        footer={null}
+        centered={true}
+        maskClosable={false}
+        wrapClassName={styles.modal}
+        // closable={false}
+        onCancel={handleClose}
+      >
+        <div className={styles.upContent}>
+          {new Array(8).fill('').map((item, idx) => (
+            <div className={styles.upItem}>{values[idx]}</div>
+          ))}
+        </div>
+        <div className={styles.numContent}>
+          {keys.map((item) => {
+            let comment = <div></div>;
+            if (item == 12) {
+              comment = (
+                <div
+                  className={styles.numberItem}
+                  onClick={() => handlerClick(item)}
+                >
+                  <img src={backIcon} />
+                </div>
+              );
+            } else {
+              comment = (
+                <div
+                  className={`${styles.numberItem} ${
+                    item == '清空' ? styles.remove : ''
+                  }`}
+                  onClick={() => handlerClick(item)}
+                >
+                  {item}
+                </div>
+              );
+            }
+            return comment;
+          })}
+        </div>
+      </Modal>
+    </div>
+  );
+};
+
+export default CommandModal;

+ 63 - 0
src/pages/SafetyManagement/Command/index.less

@@ -0,0 +1,63 @@
+.upContent {
+  display: flex;
+  margin-left: 4px;
+  margin-bottom: 20px;
+  width: 100%;
+  margin-top: 40px;
+  .upItem {
+    font-size: 26px;
+    margin-right: 10px;
+    width: 72px;
+    height: 72px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background: #ffffff;
+    box-shadow: 0px 0px 8px 3px rgba(204, 204, 204, 0.5);
+  }
+}
+.modal {
+  :global {
+    .ant-modal {
+      // transform: scale(1.3);
+    }
+    .ant-modal-title {
+      font-size: 30px;
+    }
+    .ant-modal-close {
+      top: 24px;
+      inset-inline-end: 24px;
+    }
+    .ant-modal-close-x {
+      font-size: 30px;
+    }
+  }
+}
+.numContent {
+  display: flex;
+  flex-wrap: wrap;
+}
+.numberItem {
+  width: 33%;
+  height: 100px;
+  font-size: 32px;
+  user-select: none;
+  font-weight: bold;
+  line-height: 100px;
+  text-align: center;
+  border: 1px solid rgba(128, 128, 128, 0.592);
+}
+.remove {
+  .numberItem;
+  font-size: 24px;
+}
+
+:global {
+  .ant-modal-title {
+    font-size: 32px;
+    line-height: 32px;
+  }
+  .ant-modal-close-x {
+    font-size: 32px;
+  }
+}

+ 12 - 2
src/pages/SafetyManagement/index.js

@@ -130,6 +130,13 @@ const DeviceManager = () => {
   );
 };
 const Video = ({ data, dataOnline, loading, selected, setSelected }) => {
+
+  useEffect(() => {
+    let current = data?.find(item => item.Name == selected)
+    if(current) {
+      localStorage.preview = current.EventPath;
+    }
+  }, [selected])
   const renderRed = (item) => {
     return (
       <div className={styles.cardItem}>
@@ -137,6 +144,7 @@ const Video = ({ data, dataOnline, loading, selected, setSelected }) => {
           className={item.Online ? styles.onlinePoint : styles.outlinePoint}
         />
         <span className={styles.name}>{item.Name}</span>
+        {item.EventPath && <div className={styles.redLight}></div>}
       </div>
     );
   };
@@ -149,7 +157,7 @@ const Video = ({ data, dataOnline, loading, selected, setSelected }) => {
               <img className={styles.img} src={videoIcon} />
               <div className={styles.textCon}>
                 <div className="value-number">{dataOnline?.total || 0}</div>
-                <div className={styles.text}>在库数量(个)</div>
+                <div className={styles.text}>视频数量(个)</div>
               </div>
             </div>
             <div>
@@ -172,7 +180,9 @@ const Video = ({ data, dataOnline, loading, selected, setSelected }) => {
               <div
                 id={item.Name}
                 key={`video_${idx}`}
-                onClick={() => setSelected(idx, item.Name)}
+                onClick={() => {
+                  setSelected(idx, item.Name);
+                }}
                 className={`card-box ${styles.listItem} ${
                   selected == item.Name || selected == idx ? 'card-select' : ''
                 }`}

+ 10 - 0
src/pages/SafetyManagement/index.less

@@ -43,6 +43,7 @@
   margin-bottom: 0.29rem;
 }
 .cardItem {
+  position: relative;
   display: flex;
   padding: 0.4rem 0.44rem 0.32rem;
   align-items: center;
@@ -50,6 +51,15 @@
     font-size: 0.32rem;
     color: #000000;
   }
+  .redLight {
+    position: absolute;
+    top: 0.2rem;
+    right: 0.2rem;
+    width: 0.34rem;
+    height: 0.34rem;
+    background: url('@/assets/deviceManager/redLight.png') no-repeat;
+    background-size: 100% 100%;
+  }
 }
 .lTextCon2 {
   display: flex;

+ 4 - 3
src/pages/Smart/ConditionDetection.js

@@ -8,7 +8,7 @@ import { useEffect, useMemo, useRef } from 'react';
 import PageContent from '@/components/PageContent';
 import PageTitle from '@/components/PageTitle';
 import styles from './ConditionDetection.less';
-import CircleScore from './components/CircleScore';
+import EvaluationReport from './components/EvaluationReport';
 
 const ConditionDetection = (props) => {
   const { projectId } = useParams();
@@ -89,7 +89,7 @@ const ConditionDetection = (props) => {
             <Col span={12} style={{ padding: '0.2rem' }}>
               <div className={`${styles.card2} card-box`}>
                 <h3>
-                  目标工况 <span>{best.score}分</span>
+                  预测工况 <span>{best.score}分</span>
                 </h3>
                 <ul>
                   <li>
@@ -114,7 +114,8 @@ const ConditionDetection = (props) => {
           <div className={styles.img}></div>
         </div>
 
-        <ChartContent projectId={pid} />
+        <EvaluationReport dataKey="allProject" />
+        {/* <ChartContent projectId={pid} /> */}
       </div>
     </PageContent>
   );

+ 159 - 39
src/pages/Smart/OptimizationTasks.js

@@ -2,7 +2,7 @@
 import PageContent from '@/components/PageContent';
 import PageTitle from '@/components/PageTitle';
 import {
-  queryMandate,
+  queryHistory,
   queryMandateChildList,
   querySimulationProfit,
 } from '@/services/SmartOps';
@@ -12,14 +12,11 @@ import { Table } from 'antd';
 import dayjs from 'dayjs';
 import { useState } from 'react';
 import styles from './OptimizationTasks.less';
+import ScrollLoading from './components/ScrollLoading';
 
 const OptimizationTasks = (props) => {
   const { projectId } = useParams();
 
-  const { data, run } = useRequest(queryMandate, {
-    manual: true,
-  });
-
   return (
     <PageContent closeable={false}>
       <PageTitle returnable>
@@ -50,8 +47,30 @@ const Produce = ({ projectId }) => {
     },
   ];
 
+  const historyColumns = [
+    {
+      title: '时间',
+      dataIndex: 'CreateTime',
+      render: (time) => dayjs(time).format('YYYY-MM-DD HH:mm:ss'),
+    },
+    {
+      title: '参数',
+      width: '50%',
+      dataIndex: 'Title',
+    },
+    {
+      title: '任务内容',
+      width: '24%',
+      dataIndex: 'Content',
+    },
+  ];
+
   const [mandateID, setMandateID] = useState();
+  const [active, setActive] = useState(false);
+  const [list, setList] = useState([]);
+  const [curPagination, setCurPagination] = useState({});
 
+  // 当前记录
   const { data } = useRequest(queryMandateChildList, {
     defaultParams: [
       {
@@ -60,7 +79,20 @@ const Produce = ({ projectId }) => {
       },
     ],
     onSuccess: (data) => {
-      setMandateID(data[0].MandateId);
+      setMandateID(data[0]?.MandateId);
+    },
+  });
+
+  // 历史记录
+  const {
+    run,
+    loading,
+    data: historyData,
+  } = useRequest(queryHistory, {
+    defaultParams: [{ project_id: projectId, class: 1, currentPage: 1 }],
+    onSuccess: (data) => {
+      setList([...list, ...data.list]);
+      setCurPagination(data.pagination);
     },
   });
 
@@ -73,52 +105,86 @@ const Produce = ({ projectId }) => {
   };
 
   return (
-    <div className={styles.pageCard}>
-      <h3 className={styles.title} style={{ justifyContent: 'space-between' }}>
-        <div>
-          <i />
-          生产调度类
-        </div>
-        {mandateID && (
-          <div className={styles.btnBlue} onClick={openDetail}>
-            查看任务
+    <>
+      <div className={styles.pageCard}>
+        <h3
+          className={styles.title}
+          style={{ justifyContent: 'space-between' }}
+        >
+          <div>
+            <i />
+            生产调度类
           </div>
+          {mandateID && (
+            <div className={styles.btnBlue} onClick={openDetail}>
+              查看任务
+            </div>
+          )}
+        </h3>
+        {data?.length > 0 && (
+          <>
+            <div
+              className={styles.content}
+              style={{ backgroundColor: '#B1D2F3' }}
+            >
+              <h3 className={styles.left}>任务总结</h3>
+              <div className={styles.desc}>
+                根据水质相关数据.建议您调节以下参数,水厂运行可达较优状态
+              </div>
+            </div>
+            <div
+              className={styles.content}
+              style={{ backgroundColor: '#EDF5FC' }}
+            >
+              <h3 className={styles.left}>任务内容</h3>
+              <Table
+                className={styles.taskTable}
+                style={{ width: '100%' }}
+                columns={columns}
+                dataSource={data}
+                pagination={false}
+              />
+            </div>
+          </>
         )}
-      </h3>
-      {data?.length > 0 && (
-        <>
+        {!data?.length && (
           <div
             className={styles.content}
             style={{ backgroundColor: '#B1D2F3' }}
           >
-            <h3 className={styles.left}>任务总结</h3>
             <div className={styles.desc}>
-              根据水质相关数据.建议您调节以下参数,水厂运行可达较优状态
+              当前进水数据稳定,产水数据稳定,暂无调节任务,继续保持哦~
             </div>
           </div>
-          <div
-            className={styles.content}
-            style={{ backgroundColor: '#EDF5FC' }}
+        )}
+        <div
+          className={`${styles.bottomBtn} ${active ? styles.active : ''}`}
+          onClick={() => setActive(!active)}
+        >
+          历史优化记录
+          <i></i>
+        </div>
+        <div style={{ display: active ? 'block' : 'none' }}>
+          <ScrollLoading
+            loading={loading}
+            pagination={curPagination}
+            handleLoadData={(current) =>
+              run({ project_id: projectId, currentPage: current, class: 1 })
+            }
+            height="4rem"
           >
-            <h3 className={styles.left}>任务内容</h3>
             <Table
-              className={styles.taskTable}
-              style={{ width: '100%' }}
-              columns={columns}
-              dataSource={data}
+              rowKey={'id'}
+              loading={loading}
+              dataSource={list}
+              columns={historyColumns}
               pagination={false}
+              className={styles.table1}
             />
-          </div>
-        </>
-      )}
-      {!data?.length && (
-        <div className={styles.content} style={{ backgroundColor: '#B1D2F3' }}>
-          <div className={styles.desc}>
-            当前进水数据稳定,产水数据稳定,暂无调节任务,继续保持哦~
-          </div>
+          </ScrollLoading>
         </div>
-      )}
-    </div>
+      </div>
+    </>
   );
 };
 
@@ -133,8 +199,27 @@ const Cost = ({ projectId }) => {
       dataIndex: 'Content',
     },
   ];
+  const historyColumns = [
+    {
+      title: '时间',
+      dataIndex: 'CreateTime',
+      render: (time) => dayjs(time).format('YYYY-MM-DD HH:mm:ss'),
+    },
+    {
+      title: '参数',
+      width: '24%',
+      dataIndex: 'Title',
+    },
+    {
+      title: '任务内容',
+      dataIndex: 'Content',
+    },
+  ];
 
   const [mandateID, setMandateID] = useState();
+  const [active, setActive] = useState(false);
+  const [list, setList] = useState([]);
+  const [curPagination, setCurPagination] = useState({});
 
   const { data: profit } = useRequest(querySimulationProfit, {
     defaultParams: [
@@ -161,7 +246,16 @@ const Cost = ({ projectId }) => {
       },
     ],
     onSuccess: (data) => {
-      setMandateID(data[0].MandateId);
+      setMandateID(data[0]?.MandateId);
+    },
+  });
+
+  // 历史记录
+  const { run, loading } = useRequest(queryHistory, {
+    defaultParams: [{ project_id: projectId, class: 2, currentPage: 1 }],
+    onSuccess: (data) => {
+      setList([...list, ...data.list]);
+      setCurPagination(data.pagination);
     },
   });
 
@@ -218,6 +312,32 @@ const Cost = ({ projectId }) => {
           <div className={styles.desc}>暂无可降低成本,继续保持哦~</div>
         </div>
       )}
+      <div
+        className={`${styles.bottomBtn2} ${active ? styles.active : ''}`}
+        onClick={() => setActive(!active)}
+      >
+        历史优化记录
+        <i></i>
+      </div>
+      <div style={{ display: active ? 'block' : 'none' }}>
+        <ScrollLoading
+          loading={loading}
+          pagination={curPagination}
+          handleLoadData={(current) =>
+            run({ project_id: projectId, currentPage: current, class: 2 })
+          }
+          height="4rem"
+        >
+          <Table
+            rowKey={'id'}
+            loading={loading}
+            dataSource={list}
+            columns={historyColumns}
+            pagination={false}
+            className={styles.table2}
+          />
+        </ScrollLoading>
+      </div>
     </div>
   );
 };

+ 46 - 1
src/pages/Smart/OptimizationTasks.less

@@ -69,6 +69,7 @@
 }
 .pageCard {
   padding: 0.2rem;
+  padding-bottom: 0;
   margin-top: 0.3rem;
   overflow: hidden;
   border-radius: 0.26rem;
@@ -112,10 +113,54 @@
   text-align: center;
 }
 
+.bottomBtn {
+  margin: 0.2rem auto 0;
+  width: 2.6rem;
+  height: 0.46rem;
+  background: url('@/assets/smart/btn1.png') no-repeat;
+  background-size: 100% 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+
+  font-size: 0.26rem;
+  color: #ffffff;
+
+  &.active {
+    i {
+      background-image: url('@/assets/smart/btn-arr2.png');
+    }
+  }
+  i {
+    flex-shrink: 1;
+    width: 0.2rem;
+    height: 0.1rem;
+    margin-left: 4px;
+    background: url('@/assets/smart/btn-arr1.png') no-repeat center;
+    background-size: 100% 100%;
+  }
+}
+.bottomBtn2 {
+  .bottomBtn;
+  background-image: url('@/assets/smart/btn2.png');
+}
+.table1 {
+  :global {
+    .ant-table-thead {
+      background: rgb(177, 210, 243);
+    }
+    .ant-table-thead > tr > th {
+      color: #4a4a4a;
+    }
+  }
+}
 .table2 {
   :global {
     .ant-table-thead {
-      background: rgba(245, 166, 35, 0.14);
+      background: rgb(251, 222, 174);
+    }
+    .ant-table-thead > tr > th {
+      color: #4a4a4a;
     }
   }
 }

+ 137 - 0
src/pages/Smart/components/EvaluationReport.js

@@ -0,0 +1,137 @@
+import { Table, Tooltip } from 'antd';
+import { useMemo } from 'react';
+import { current, indicators } from './mock';
+import styles from '../OptimizationTasks.less';
+// import styles from './WorkConditionAssessment.less';
+
+/**
+ * 评估报告组件
+ * @param {boolean} isAbnormal - 是否存在异常情况
+ * @param {object} compareData - 同比环比数据
+ * @param {number} compareData.lastPeriod - 上期同比数据(百分比)
+ * @param {number} compareData.lastYear - 去年同期同比数据(百分比)
+ * @param {string} optimizationSuggestions - 优化建议
+ */
+const EvaluationReport = (props) => {
+  const { dataKey } = props;
+
+  const dataSource = useMemo(() => {
+    let data = current[dataKey];
+    let dataSource = indicators[dataKey]
+      .filter((item) => item.delta !== false)
+      .map((item) => {
+        let key = item.value;
+        let realTimeData = data['realTimeData'][key];
+        let forecastData = data['forecastData'][key];
+
+        return {
+          indicator: item.label,
+          delta1: (
+            ((forecastData - realTimeData) / forecastData) *
+            100
+          ).toFixed(2),
+        };
+      });
+    let error = { color: '#f5222d' },
+      success = { color: '#7cb305' };
+    if (dataKey == 'allProject') {
+      dataSource.push({
+        indicator: '总结',
+        delta1: (
+          <div>
+            【2024年1月】 <span style={error}>异常</span>
+            <br />
+            (1)生产安全评估: <span style={success}>正常</span>
+            <br />
+            达标率、产水量实时工况与仿真、最优和标准工况对比皆在偏差范围内,正常
+            <br />
+            <br />
+            (2)经济评估:<span style={error}>异常</span>
+            <br />
+            电耗实时工况与仿真工况对比,偏差为1.89,预计比仿真多消耗21269kWh,系统判断预计下一阶段会持续增加
+          </div>
+        ),
+      });
+      dataSource.push({
+        indicator: '优化建议',
+        delta1: <div>根据上述结论,为减少电耗,建议检查膜车间工艺段</div>,
+      });
+    } else {
+      dataSource.push({
+        indicator: '总结',
+        delta1: (
+          <div>
+            (1)生产安全评估: <span style={error}>异常</span>
+            <br />
+            膜阻力、TMP实时工况与标准工况对比偏差偏大;与最优工况对比膜阻力有偏离;与预测工况对比,膜阻力、TMP会持续增加
+            <br />
+            <br />
+            (2)经济评估:<span style={success}>正常</span>
+            <br />
+            处理水量此时为正常,系统判断下一阶段随着膜阻力、TMP增加会影响产水量
+          </div>
+        ),
+      });
+      dataSource.push({
+        indicator: '优化建议',
+        delta1: (
+          <div>
+            根据上述结论,为保证产水量,需要增加膜的反冲洗频次和清洗强度。
+          </div>
+        ),
+      });
+    }
+
+    return dataSource;
+  }, []);
+
+  const columns = [
+    {
+      title: '指标',
+      dataIndex: 'indicator',
+      key: 'indicator',
+      width: 200,
+    },
+    {
+      title: (
+        <Tooltip
+          title="(预测 - 实际) / 预测 * 100"
+          // overlayClassName={styles.toolTip}
+        >
+          Δ1
+          {/* <Icon type="info-circle" /> */}
+        </Tooltip>
+      ),
+      dataIndex: 'delta1',
+      key: 'delta1',
+      render: (text, row, index) => {
+        if (index < dataSource.length - 2) {
+          return text;
+        }
+        return {
+          children: text,
+          props: {
+            colSpan: 3,
+          },
+        };
+      },
+    },
+  ];
+  // return null
+  return (
+    <div className={styles.pageCard}>
+      <h3 className={styles.title} style={{ justifyContent: 'space-between' }}>
+        <div>
+          <i />
+          评估报告
+        </div>
+      </h3>
+
+      <div style={{ fontSize: 18, color: '#fff' }}>
+        <Table columns={columns} dataSource={dataSource} pagination={false} />
+      </div>
+    </div>
+  );
+};
+
+export default EvaluationReport;

+ 36 - 0
src/pages/Smart/components/ScrollLoading.js

@@ -0,0 +1,36 @@
+import { Spin } from 'antd';
+import { useRef } from 'react';
+
+export default function ScrollLoading({
+  loading,
+  children,
+  pagination = { current: 1, total: 0, pageSize: 20 },
+  handleLoadData, //请求数据方法
+  height, //除滚动区域外其他高度(只是通用标题就是90)
+}) {
+  const scrollContent = useRef();
+
+  const handleScroll = () => {
+    const { current, total, pageSize } = pagination;
+    if (current * pageSize >= total) return;
+    const { scrollHeight, clientHeight, scrollTop } = scrollContent.current;
+    if (scrollHeight - clientHeight <= scrollTop + 1 && !loading) {
+      handleLoadData(current + 1);
+    }
+  };
+
+  return (
+    <Spin spinning={loading}>
+      <div
+        ref={scrollContent}
+        onScroll={handleScroll}
+        style={{
+          overflowY: 'scroll',
+          height: height,
+        }}
+      >
+        {children}
+      </div>
+    </Spin>
+  );
+}

+ 167 - 0
src/pages/Smart/components/mock.js

@@ -0,0 +1,167 @@
+export const indicators = {
+  allProject: [
+    { label: '产水量', value: 'waterProduction', max: 20 },
+    { label: '达标率', value: 'complianceRate', max: 100 },
+    { label: '电耗', value: 'electricityConsumption', max: 610000 },
+    { label: '维修保养完成率', value: 'maintenanceCompletionRate', max: 100, delta: false },
+    { label: '设备故障率', value: 'equipmentFailureRate', max: 0, delta: false },
+    { label: '药耗', value: 'chemicalConsumption', max: 40 },
+  ],
+};
+export const MockData = [
+  {
+    time: '1月',
+    allProject: {
+      realTimeData: {
+        complianceRate: 100,
+        waterProduction: 16,
+        electricityConsumption: 606360,
+        chemicalConsumption: 37.51,
+
+        equipmentFailureRate: 0,
+        maintenanceCompletionRate: 100,
+      },
+      forecastData: {
+        complianceRate: 100,
+        waterProduction: 16,
+        electricityConsumption: 595091,
+        chemicalConsumption: 37.44,
+        equipmentFailureRate: 0,
+        maintenanceCompletionRate: 100,
+      },
+      optimalData: {
+        complianceRate: 100,
+        waterProduction: 18,
+        electricityConsumption: 595267,
+        chemicalConsumption: 35.58,
+        equipmentFailureRate: 0,
+        maintenanceCompletionRate: 100,
+      },
+      standardData: {
+        complianceRate: 100,
+        waterProduction: 20,
+        electricityConsumption: 610000,
+        chemicalConsumption: 40,
+        equipmentFailureRate: 0,
+        maintenanceCompletionRate: 100,
+      },
+    },
+  },
+  {
+    time: '2月',
+    allProject: {
+      realTimeData: {
+        complianceRate: 100,
+        waterProduction: 16,
+        electricityConsumption: 617346,
+        chemicalConsumption: 37.51,
+
+        equipmentFailureRate: 0,
+        maintenanceCompletionRate: 100,
+      },
+      forecastData: {
+        complianceRate: 100,
+        waterProduction: 17,
+        electricityConsumption: 605346,
+        chemicalConsumption: 37.44,
+        equipmentFailureRate: 0,
+        maintenanceCompletionRate: 100,
+      },
+      optimalData: {
+        complianceRate: 100,
+        waterProduction: 18,
+        electricityConsumption: 604782,
+        chemicalConsumption: 35.58,
+        equipmentFailureRate: 0,
+        maintenanceCompletionRate: 100,
+      },
+      standardData: {
+        complianceRate: 100,
+        waterProduction: 20,
+        electricityConsumption: 610000,
+        chemicalConsumption: 40,
+        equipmentFailureRate: 0,
+        maintenanceCompletionRate: 100,
+      },
+    },
+  },
+  {
+    time: '3月',
+    allProject: {
+      realTimeData: {
+        complianceRate: 100,
+        waterProduction: 16,
+        electricityConsumption: 605785,
+        chemicalConsumption: 37.69,
+
+        equipmentFailureRate: 0,
+        maintenanceCompletionRate: 100,
+      },
+      forecastData: {
+        complianceRate: 100,
+        waterProduction: 16,
+        electricityConsumption: 604471,
+        chemicalConsumption: 37.72,
+        equipmentFailureRate: 0,
+        maintenanceCompletionRate: 100,
+      },
+      optimalData: {
+        complianceRate: 100,
+        waterProduction: 20,
+        electricityConsumption: 603782,
+        chemicalConsumption: 36.66,
+        equipmentFailureRate: 0,
+        maintenanceCompletionRate: 100,
+      },
+      standardData: {
+        complianceRate: 100,
+        waterProduction: 20,
+        electricityConsumption: 610000,
+        chemicalConsumption: 40,
+        equipmentFailureRate: 0,
+        maintenanceCompletionRate: 100,
+      },
+    },
+  },
+  {
+    time: '4月',
+    allProject: {
+      realTimeData: {
+        complianceRate: 100,
+        waterProduction: 16,
+        electricityConsumption: 606360,
+        chemicalConsumption: 37.51,
+
+        equipmentFailureRate: 0,
+        maintenanceCompletionRate: 100,
+      },
+      forecastData: {
+        complianceRate: 100,
+        waterProduction: 16,
+        electricityConsumption: 595091,
+        chemicalConsumption: 37.44,
+        equipmentFailureRate: 0,
+        maintenanceCompletionRate: 100,
+      },
+      optimalData: {
+        complianceRate: 100,
+        waterProduction: 20,
+        electricityConsumption: 595267,
+        chemicalConsumption: 35.58,
+        equipmentFailureRate: 0,
+        maintenanceCompletionRate: 100,
+      },
+      standardData: {
+        complianceRate: 100,
+        waterProduction: 20,
+        electricityConsumption: 610000,
+        chemicalConsumption: 40,
+        equipmentFailureRate: 0,
+        maintenanceCompletionRate: 100,
+      },
+    },
+  },
+];
+
+// 工况评估
+export const current = MockData[3];

+ 39 - 45
src/pages/Smart/index.js

@@ -54,111 +54,105 @@ const Work = (props) => {
               history.push('/smart/condition-detection/' + projectId)
             }
           >
-            工况
+            工况
           </Button>
         </div>
       </div>
       <Row gutter={30}>
         <Col span={12}>
           <div className={styles.card}>
-            <h3>进水数据</h3>
+            <h3>
+              成本数据
+              <div
+                style={{ marginLeft: 14 }}
+                onClick={() => setOpen(!open)}
+                className={`password-eye ${open ? 'open' : ''}`}
+              ></div>
+            </h3>
             <ul>
               <li>
-                <i></i>进水量:{data?.fwa}
-              </li>
-              <li>
-                <i></i>进水温度:{data?.ft}
-              </li>
-              <li>
-                <i></i>进水浊度:{data?.ftur}
+                <i></i>吨水药成本:{open ? data?.otc_cost_unit : '*******'}
               </li>
               <li>
-                <i></i>进水压力:{data?.fpp}
-              </li>
-              {/* 与产水数据对其 */}
-              <li style={{ visibility: 'hidden' }}>
-                <i></i>
+                <i></i>吨水电成本:{open ? data?.elec_cost_unit : '*******'}
               </li>
             </ul>
           </div>
         </Col>
         <Col span={12}>
           <div className={styles.card}>
-            <h3>水数据</h3>
+            <h3>水厂负荷数据</h3>
             <ul>
               <li>
-                <i></i>外供水流量:{data?.dwa}
-              </li>
-              <li>
-                <i></i>外供水pH:{data?.dph}
-              </li>
-              <li>
-                <i></i>外供水电导率:{data?.dtds}
-              </li>
-              <li>
-                <i></i>外供水浊度:{data?.dtur}
+                <i></i>实际处理水量:{data?.fact_water}
               </li>
               <li>
-                <i></i>外供水余氯:{data?.dsan}
+                <i></i>设计处理水量:{data?.devise_water}
               </li>
             </ul>
           </div>
         </Col>
         <Col span={12}>
           <div className={styles.card}>
-            <h3>水厂负荷数据</h3>
+            <h3>能耗数据</h3>
             <ul>
               <li>
-                <i></i>实际处理水量:{data?.fact_water}
+                <i></i>吨水电耗:{data?.elec_unit}
               </li>
               <li>
-                <i></i>设计处理水量:{data?.devise_water}
+                <i></i>用电量:{data?.elec}
               </li>
             </ul>
           </div>
         </Col>
         <Col span={12}>
           <div className={styles.card2}>
-            <h3>
-              成本数据
-              <div
-                style={{ marginLeft: 14 }}
-                onClick={() => setOpen(!open)}
-                className={`password-eye ${open ? 'open' : ''}`}
-              ></div>
-            </h3>
+            <h3>药耗数据</h3>
             <ul>
               <li>
-                <i></i>吨水药成本:{open ? data?.otc_cost_unit : '*******'}
+                <i></i>吨水药耗:{data?.otc_unit}
               </li>
               <li>
-                <i></i>吨水电成本:{open ? data?.elec_cost_unit : '*******'}
+                <i></i>药量:{data?.otc}
               </li>
             </ul>
           </div>
         </Col>
         <Col span={12}>
           <div className={styles.card2} style={{ marginBottom: 0 }}>
-            <h3>能耗数据</h3>
+            <h3>进水数据</h3>
             <ul>
               <li>
-                <i></i>吨水电耗:{data?.elec_unit}
+                <i></i>进水量:{data?.fwa}
               </li>
               <li>
-                <i></i>用电量:{data?.elec}
+                <i></i>进水温度:{data?.ft}
+              </li>
+              <li>
+                <i></i>进水浊度:{data?.ftur}
+              </li>
+              <li>
+                <i></i>进水压力:{data?.fpp}
               </li>
             </ul>
           </div>
         </Col>
         <Col span={12}>
           <div className={styles.card2} style={{ marginBottom: 0 }}>
-            <h3>药耗数据</h3>
+            <h3>产水数据</h3>
             <ul>
               <li>
-                <i></i>吨水药耗:{data?.otc_unit}
+                <i></i>外供水流量:{data?.dwa}
               </li>
               <li>
-                <i></i>药量:{data?.otc}
+                <i></i>外供水pH:{data?.dph}
+              </li>
+              <li>
+                <i></i>外供水电导率:{data?.dtds}
+              </li>
+              {/* 与进水数据对齐 */}
+              <li style={{ visibility: 'hidden' }}>
+                <i></i>
               </li>
             </ul>
           </div>

+ 3 - 1
src/pages/SmartOps/Analysis.js

@@ -167,7 +167,9 @@ const Analysis = (props) => {
     <Spin spinning={loading}>
       <div style={{ height: 'calc(100vh - 5.6rem)', overflow: 'auto' }}>
         <TabsContent
-          small={true}
+          small
+          bold={false}
+          spacing={2.5}
           center={false}
           defaultActiveKey="1"
           items={data?.map((item) => {

+ 24 - 5
src/pages/SmartOps/HistoryRecord.js

@@ -19,7 +19,7 @@ const HistoryRecord = (props) => {
   const convertObject2FormData = (params) => {
     const formData = new FormData();
     Object.entries(params).forEach(([key, value]) => {
-      if (value !== null && value !== undefined && value !== NaN) {
+      if (value !== null && value !== undefined) {
         formData.append(key, value);
       }
     });
@@ -45,10 +45,11 @@ const HistoryRecord = (props) => {
     (params = formData) => getHistoryRecord(params),
     {
       onSuccess: (res) => {
+        let list = res?.list || []
         if (res.pagenation?.current == 1) {
-          setData(res?.list);
+          setData(list);
         } else {
-          setData([...data, ...res?.list]);
+          setData([...data, ...list]);
         }
         setPagination(res.pagenation);
       },
@@ -60,8 +61,9 @@ const HistoryRecord = (props) => {
       title: '时间',
       dataIndex: 'CTime',
       key: 'CTime',
+      width: '20%',
       render: (text) => {
-        return dayjs(text).format('YYYY-MM-DD HH:mm') || '--';
+        return dayjs(text).format('MM-DD HH:mm') || '--';
       },
     },
     {
@@ -109,9 +111,23 @@ const HistoryRecord = (props) => {
         return text;
       },
     },
-
+    {
+      title: '预测分析',
+      key: 'Num4',
+      render: (text) => {
+        return 0;
+      },
+    },
+    {
+      title: '经营分析',
+      key: 'Num5',
+      render: (text) => {
+        return 0;
+      },
+    },
     {
       title: '操作',
+      width: '10%',
       render: (record) => (
         <a
           onClick={() => {
@@ -153,6 +169,7 @@ const HistoryRecord = (props) => {
       default:
         break;
     }
+    // console.log(tempParams)
     setQueryParams(tempParams);
   };
 
@@ -161,11 +178,13 @@ const HistoryRecord = (props) => {
       getList(params);
       return;
     }
+    console.log(formData);
     getList(formData);
   };
 
   useEffect(() => {
     const tempFormData = convertObject2FormData(queryParams);
+    console.log(queryParams);
     // page变更自动请求接口
     setFormData(tempFormData);
   }, [queryParams]);

+ 4 - 3
src/pages/SmartOps/OperationRecord.js

@@ -18,7 +18,7 @@ const OperationRecord = (props) => {
   const convertObject2FormData = (params) => {
     const formData = new FormData();
     Object.entries(params).forEach(([key, value]) => {
-      if (value !== null && value !== undefined && value !== NaN) {
+      if (value !== null && value !== undefined) {
         formData.append(key, value);
       }
     });
@@ -45,10 +45,11 @@ const OperationRecord = (props) => {
     (params = formData) => getVarValues(params),
     {
       onSuccess: (res) => {
+        let list = res?.list || [];
         if (res.pagination?.current == 1) {
-          setData(res?.list);
+          setData(list);
         } else {
-          setData([...data, ...res?.list]);
+          setData([...data, ...list]);
         }
         setPagination(res.pagination);
       },

+ 2 - 0
src/pages/SmartOps/WorkAnalysisDetail.js

@@ -200,6 +200,8 @@ function WorkAnalysisDetail(props) {
         <TabsContent
           center={false}
           small
+          bold={false}
+          spacing={2.5}
           defaultActiveKey={active}
           items={technologys.map((item) => ({
             label: TYPE[item]?.name,

+ 124 - 72
src/pages/SmartOps/components/DeviceAnalysis.js

@@ -3,7 +3,7 @@ import TabsContent from '@/components/TabsContent';
 import ThresholdDetail from '@/components/ThresholdDetail';
 import { UnityAction } from '@/utils/utils';
 import { connect } from '@umijs/max';
-import { Spin, Table, Tabs } from 'antd';
+import { Table, Tabs } from 'antd';
 import dayjs from 'dayjs';
 import { useEffect, useMemo, useState } from 'react';
 import styles from './DeviceAnalysis.less';
@@ -114,14 +114,20 @@ const DeviceAnalysis = (props) => {
         type: '1',
         data: autoReport?.extendWarningData,
         FluidLevelList: autoReport?.FluidLevelList?.filter(
-          (item) => item.status,
+          (item) => item.status && !item.history,
+        ),
+        DrugFlowList: autoReport?.DrugFlowList?.filter(
+          (item) => item.status && !item.history,
         ),
-        DrugFlowList: autoReport?.DrugFlowList?.filter((item) => item.status),
         WaterInCheckList: autoReport?.WaterInCheckList?.filter(
           (item) => item.status,
         ),
-        PressureCompareList: autoReport?.PressureCompareList,
-        WaterQualityCompareList: autoReport?.WaterQualityCompareList,
+        PressureCompareList: autoReport?.PressureCompareList?.filter(
+          (item) => !item.history,
+        ),
+        WaterQualityCompareList: autoReport?.WaterQualityCompareList?.filter(
+          (item) => !item.history,
+        ),
       },
       {
         name: '全部',
@@ -166,62 +172,90 @@ const DeviceAnalysis = (props) => {
     }
   };
 
+  const calculateLength = (params) => {
+    let length = 0;
+    Object.keys(params).forEach((key) => {
+      if (key !== 'name' && key !== 'type') {
+        length += params[key]?.length;
+      }
+    });
+    return length;
+  };
+
   return (
-    <Spin spinning={loading}>
-      <div style={{ height: 'calc(100vh - 5.6rem)', overflow: 'auto' }}>
-        <TabsContent
-          small={true}
-          center={false}
-          defaultActiveKey="1"
-          items={data?.map((item) => {
-            return {
-              label: `${item.name}(${item.data?.length || 0})`,
-              key: item.type,
-              children: (
-                <>
-                  {(item.type === '1' ? item?.data?.length > 0 : true) && (
-                    <>
-                      <ModuleTitle title="设备检测" />
-                      <Table
-                        dataSource={item.data}
-                        columns={columns}
-                        rowKey="Id"
-                        rowClassName={setRowClassName}
-                        onRow={(record, index) => ({
-                          onClick: () => onSelectRow(record, index),
-                        })}
-                        pagination={false}
-                        // scroll={{ y: document.body.clientHeight - 730 }}
-                      />
-                    </>
-                  )}
-                  {(item.type === '1'
-                    ? item?.FluidLevelList.length > 0
-                    : true) && <LiquidLevel allData={item?.FluidLevelList} />}
-                  {(item.type === '1'
-                    ? item?.DrugFlowList.length > 0
-                    : true) && <DosingFlow allData={item?.DrugFlowList} />}
-                  {(item.type === '1'
-                    ? item?.WaterInCheckList.length > 0
-                    : true) && <WaterFlow allData={item?.WaterInCheckList} />}
-                  {(item.type === '1'
-                    ? item?.PressureCompareList.length > 0
-                    : true) && (
-                    <PressureGauge allData={item?.PressureCompareList} />
-                  )}
-                  {(item.type === '1'
-                    ? item?.WaterQualityCompareList.length > 0
-                    : true) && (
-                    <WaterQuality allData={item?.WaterQualityCompareList} />
-                  )}
-                </>
-              ),
-            };
-          })}
-          onChange={onTabChange}
-        />
-      </div>
-    </Spin>
+    <div style={{ height: 'calc(100vh - 5.6rem)', overflow: 'auto' }}>
+      <TabsContent
+        small
+        bold={false}
+        spacing={2.5}
+        center={false}
+        defaultActiveKey="1"
+        items={data?.map((item) => {
+          return {
+            label: `${item.name}(${calculateLength(item) || 0})`,
+            key: item.type,
+            children: (
+              <>
+                {(item.type === '1' ? item?.data?.length > 0 : true) && (
+                  <>
+                    <ModuleTitle title="设备检测" />
+                    <Table
+                      dataSource={item.data}
+                      columns={columns}
+                      rowKey="Id"
+                      rowClassName={setRowClassName}
+                      onRow={(record, index) => ({
+                        onClick: () => onSelectRow(record, index),
+                      })}
+                      pagination={false}
+                      // scroll={{ y: document.body.clientHeight - 730 }}
+                    />
+                  </>
+                )}
+                {(item.type === '1'
+                  ? item?.FluidLevelList?.length > 0
+                  : true) && (
+                  <LiquidLevel
+                    allData={item?.FluidLevelList}
+                    type={item.type}
+                  />
+                )}
+                {(item.type === '1'
+                  ? item?.DrugFlowList?.length > 0
+                  : true) && (
+                  <DosingFlow allData={item?.DrugFlowList} type={item.type} />
+                )}
+                {(item.type === '1'
+                  ? item?.WaterInCheckList?.length > 0
+                  : true) && (
+                  <WaterFlow
+                    allData={item?.WaterInCheckList}
+                    type={item.type}
+                  />
+                )}
+                {(item.type === '1'
+                  ? item?.PressureCompareList?.length > 0
+                  : true) && (
+                  <PressureGauge
+                    allData={item?.PressureCompareList}
+                    type={item.type}
+                  />
+                )}
+                {(item.type === '1'
+                  ? item?.WaterQualityCompareList?.length > 0
+                  : true) && (
+                  <WaterQuality
+                    allData={item?.WaterQualityCompareList}
+                    type={item.type}
+                  />
+                )}
+              </>
+            ),
+          };
+        })}
+        onChange={onTabChange}
+      />
+    </div>
   );
 };
 export default connect(({ eqSelfInspection, loading }) => ({
@@ -230,14 +264,23 @@ export default connect(({ eqSelfInspection, loading }) => ({
 }))(DeviceAnalysis);
 
 const LiquidLevel = (props) => {
-  const { allData } = props;
+  const { allData, type } = props;
 
   const columns = [
     {
       title: '设备名称',
-      width: '20%',
+      width: '14%',
       dataIndex: 'device_name',
     },
+    {
+      title: '时间',
+      dataIndex: 'record_time',
+      render: (text) => {
+        if (text) {
+          return dayjs(text).format('YYYY.MM.DD HH:mm');
+        }
+      },
+    },
     {
       title: '类型',
       key: 'template_item_name',
@@ -253,7 +296,7 @@ const LiquidLevel = (props) => {
     },
     {
       title: '设定值范围',
-      width: '25%',
+      width: '20%',
       render: (record) => (
         <ThresholdDetail
           current={record.value || 0}
@@ -318,21 +361,30 @@ const LiquidLevel = (props) => {
 
   return (
     <div>
-      <ModuleTitle title="液位检测" />
+      <ModuleTitle title={`液位检测${type === '1' ? '' : '(历史记录)'}`} />
       <Table dataSource={allData} columns={columns} />
     </div>
   );
 };
 
 const DosingFlow = (props) => {
-  const { allData } = props;
+  const { allData, type } = props;
 
   const columns = [
     {
       title: '设备名称',
-      width: '15%',
+      width: '14%',
       dataIndex: 'device_name',
     },
+    {
+      title: '时间',
+      dataIndex: 'record_time',
+      render: (text) => {
+        if (text) {
+          return dayjs(text).format('YYYY.MM.DD HH:mm');
+        }
+      },
+    },
     {
       title: '类型',
       key: 'template_item_name',
@@ -416,14 +468,14 @@ const DosingFlow = (props) => {
   ];
   return (
     <div>
-      <ModuleTitle title="加药流量校验" />
+      <ModuleTitle title={`加药流量校验${type === '1' ? '' : '(历史记录)'}`} />
       <Table dataSource={allData} columns={columns} />
     </div>
   );
 };
 
 const WaterFlow = (props) => {
-  const { allData } = props;
+  const { allData, type } = props;
   const columns = [
     {
       title: '设备名称',
@@ -534,7 +586,7 @@ const WaterFlow = (props) => {
 };
 
 const PressureGauge = (props) => {
-  const { allData } = props;
+  const { allData, type } = props;
 
   const columns = [
     {
@@ -644,14 +696,14 @@ const PressureGauge = (props) => {
 
   return (
     <div>
-      <ModuleTitle title="压力仪表校验" />
+      <ModuleTitle title={`压力仪表校验${type === '1' ? '' : '(历史记录)'}`} />
       <Table dataSource={allData} columns={columns} />
     </div>
   );
 };
 
 const WaterQuality = (props) => {
-  const { allData } = props;
+  const { allData, type } = props;
 
   const columns = [
     {
@@ -761,7 +813,7 @@ const WaterQuality = (props) => {
 
   return (
     <div>
-      <ModuleTitle title="水质仪表校验" />
+      <ModuleTitle title={`水质仪表校验${type === '1' ? '' : '(历史记录)'}`} />
       <Table dataSource={allData} columns={columns} />
     </div>
   );

+ 26 - 0
src/pages/SmartOps/components/SubTitle.js

@@ -0,0 +1,26 @@
+const SubTitle = ({ title, fontSize = '0.28rem' }) => {
+  return (
+    <div
+      style={{
+        display: 'flex',
+        justifyContent: 'flex-start',
+        alignItems: 'center',
+        marginBottom: '0.1rem',
+        fontSize,
+        fontWeight: '600',
+      }}
+    >
+      <div
+        style={{
+          width: '0.1rem',
+          height: '0.1rem',
+          background: '#1755ff',
+          marginRight: '0.1rem',
+        }}
+      />
+      {title}
+    </div>
+  );
+};
+
+export default SubTitle;

+ 3 - 1
src/pages/SmartOps/components/VideoAnalysis.js

@@ -74,7 +74,9 @@ function VideoAnalysis(props) {
     <Spin spinning={loading}>
       <div style={{ height: 'calc(100vh - 5.6rem)', overflow: 'auto' }}>
         <TabsContent
-          small={true}
+          small
+          bold={false}
+          spacing={2.5}
           center={false}
           defaultActiveKey="1"
           items={[

+ 38 - 13
src/pages/SmartOps/index.js

@@ -23,6 +23,8 @@ import DeviceAnalysis from './components/DeviceAnalysis';
 import VideoAnalysis from './components/VideoAnalysis';
 import WorkAnalysis from './components/WorkAnalysis';
 import styles from './index.less';
+import OperationManage from './operationManage';
+import PredictionAnalysis from './predictionAnalysis/PredictionAnalysis';
 
 const { TabPane } = Tabs;
 const icon06 = require('@/assets/smartOps/icon06.png');
@@ -99,6 +101,7 @@ function SmartOps(props) {
       type: 'eqSelfInspection/getPatrolDataById',
       payload: {
         routeId: patrolId || reportData.PatrolId,
+        projectId,
       },
       callback: (data) => {
         // console.log(data);
@@ -265,11 +268,14 @@ function SmartOps(props) {
   const lengthCalculate = () => {
     return (
       autoReport?.extendWarningData?.length +
-      autoReport?.FluidLevelList?.filter((item) => item.status).length +
-      autoReport?.DrugFlowList?.filter((item) => item.status)?.length +
+      autoReport?.FluidLevelList?.filter((item) => item.status && !item.history)
+        .length +
+      autoReport?.DrugFlowList?.filter((item) => item.status && !item.history)
+        ?.length +
       autoReport?.WaterInCheckList?.filter((item) => item.status)?.length +
-      autoReport?.WaterQualityCompareList?.length +
-      autoReport?.PressureCompareList?.length
+      autoReport?.WaterQualityCompareList?.filter((item) => !item.history)
+        ?.length +
+      autoReport?.PressureCompareList?.filter((item) => !item.history)?.length
     );
   };
 
@@ -286,33 +292,40 @@ function SmartOps(props) {
             </div>
           )}
         </div>
-        <div className={styles.middle}>
+        <div className={styles.overview}>
           <div className={styles.left}>
-            <div className={styles.in} />
-            <div className={styles.out} />
-          </div>
-          <div className={styles.right}>
-            <div className={styles.item1}>
+            <div className={styles.item}>
               工况分析:
               {optimizationNumber > 0
                 ? `${optimizationNumber}项可优化`
                 : '暂无优化'}
             </div>
+            <div className={styles.item1}>预测分析:{'暂无优化'}</div>
             <div className={styles.item2}>
               设备分析:
               {lengthCalculate() > 0
                 ? `${lengthCalculate()}项可优化`
                 : '暂无优化'}
             </div>
-            <div className={styles.item3}>
+          </div>
+          <div className={styles.middle}>
+            <div className={styles.in} />
+            <div className={styles.out} />
+          </div>
+          <div className={styles.right}>
+            <div className={styles.item}>
               工艺分析:
               {list?.pagenation?.total > 0
                 ? `${list?.pagenation?.total}项可优化`
                 : '暂无优化'}
             </div>
-            <div className={styles.item4}>
+            <div className={styles.item1}>
               感知分析:{videoNum > 0 ? `${videoNum}项可优化` : '暂无优化'}
             </div>
+            <div className={styles.item2}>
+              经营分析:
+              {'暂无优化'}
+            </div>
           </div>
         </div>
         <div className={styles.text}>通过智慧分析预计可省{profitData}元</div>
@@ -320,6 +333,8 @@ function SmartOps(props) {
       <div className={styles.tabContent}>
         <TabsContent
           defaultActiveKey="1"
+          center={false}
+          spacing={2.5}
           small
           items={[
             {
@@ -334,6 +349,11 @@ function SmartOps(props) {
                 />
               ),
             },
+            {
+              label: `预测分析`,
+              key: '6',
+              children: <PredictionAnalysis />,
+            },
             {
               label: `设备分析(${loadingDev ? '-' : lengthCalculate()})`,
               key: '4',
@@ -355,6 +375,11 @@ function SmartOps(props) {
                 />
               ),
             },
+            {
+              label: `经营分析`,
+              key: '5',
+              children: <OperationManage />,
+            },
           ]}
           onChange={onChangeTab}
         />
@@ -366,7 +391,7 @@ function SmartOps(props) {
 const convertObject2FormData = (params) => {
   const formData = new FormData();
   Object.entries(params).forEach(([key, value]) => {
-    if (value !== null && value !== undefined && value !== NaN) {
+    if (value !== null && value !== undefined) {
       formData.append(key, value);
     }
   });

+ 37 - 9
src/pages/SmartOps/index.less

@@ -2,7 +2,7 @@
   padding: 0.08rem 0.12rem;
   margin: 0.16rem 0;
   color: #1c50b3;
-  font-size: 0.28rem;
+  font-size: 0.26rem;
   .titleContent {
     display: flex;
     align-items: center;
@@ -24,12 +24,37 @@
       background-size: 100% 100%;
     }
   }
-  .middle {
+  .overview {
     margin: 0.1rem 0;
     display: flex;
     align-items: center;
     justify-content: center;
     .left {
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      align-items: flex-start;
+      .item {
+        width: 3.5rem;
+        height: 0.52rem;
+        white-space: nowrap;
+        margin-bottom: 0.1rem;
+        background: url('@/assets/smartOps/bg-left-1.png') no-repeat center;
+        background-size: 100% 100%;
+        padding-left: 0.1rem;
+        line-height: 0.52rem;
+      }
+      .item1 {
+        .item;
+        width: 3.15rem;
+        background-image: url('@/assets/smartOps/bg-left-2.png');
+      }
+      .item2 {
+        .item;
+        background-image: url('@/assets/smartOps/bg-left-3.png');
+      }
+    }
+    .middle {
       position: relative;
       .in {
         width: 2.12rem;
@@ -58,29 +83,32 @@
     .right {
       display: flex;
       flex-direction: column;
+      justify-content: center;
       align-items: flex-end;
       .item {
-        width: 5.85rem;
+        width: 3.5rem;
         height: 0.52rem;
         white-space: nowrap;
         margin-bottom: 0.1rem;
-        background: url('@/assets/smartOps/icon01.png') no-repeat center;
+        background: url('@/assets/smartOps/bg-right-1.png') no-repeat center;
         background-size: 100% 100%;
+        padding-left: 0.45rem;
         line-height: 0.52rem;
       }
       .item1 {
         .item;
-        padding-left: 0.96rem;
+        width: 3.15rem;
+        background-image: url('@/assets/smartOps/bg-right-2.png');
+        padding-left: 0.2rem;
       }
       .item2 {
         .item;
-        width: 5.27rem;
-        padding-left: 0.4rem;
-        background-image: url('@/assets/smartOps/icon02.png');
+        padding-left: 0.45rem;
+        background-image: url('@/assets/smartOps/bg-right-3.png');
       }
       .item3 {
         .item;
-        width: 5.27rem;
+        // width: 5.27rem;
         padding-left: 0.4rem;
         background-image: url('@/assets/smartOps/icon03.png');
       }

+ 202 - 0
src/pages/SmartOps/operationManage/CostAnalysis/CostAnalysis.js

@@ -0,0 +1,202 @@
+import ChartModule from '@/components/ManagementPage/chartModule';
+import {
+  queryEnergyChartList,
+  queryProcessSection,
+} from '@/services/OperationManagement';
+import { useParams, useRequest } from '@umijs/max';
+import { Button, DatePicker, Table } from 'antd';
+import dayjs from 'dayjs';
+import SubTitle from '../../components/SubTitle';
+import styles from './CostAnalysis.less';
+
+const { RangePicker } = DatePicker;
+
+const CostAnalysis = () => {
+  const { projectId } = useParams();
+
+  const defaultTime = {
+    s_time: dayjs().subtract(7, 'day').format('YYYY-MM-DD 00:00:00'),
+    e_time: dayjs().format('YYYY-MM-DD 23:59:59'),
+  };
+
+  const columns = [
+    {
+      title: '时间',
+      dataIndex: 'data_time',
+    },
+    {
+      title: '能耗(kWh)',
+      dataIndex: 'electric',
+    },
+    {
+      title: '药耗(kg)',
+      dataIndex: 'medicine',
+    },
+    {
+      title: '进水水量(t)',
+      dataIndex: 'waterIn',
+    },
+    {
+      title: '出水水量(t)',
+      dataIndex: 'waterOut',
+    },
+  ];
+
+  const { data, loading, run } = useRequest(
+    (date) =>
+      queryEnergyChartList({
+        project_id: Number(projectId),
+        start_time: date.s_time,
+        end_time: date.e_time,
+        order: 1,
+      }),
+    {
+      defaultParams: [{ ...defaultTime }],
+      formatResult: (data) => {
+        const tempData = data.data;
+        const arr = Object.values(tempData).find((arr) => arr);
+
+        return {
+          chartData: {
+            yName: ['药耗(kg)', '能耗(kWh)', '水量(t)'],
+            xData: arr?.map((item) => item.data_time),
+            dataList: [
+              {
+                type: 0,
+                name: '能耗',
+                yIndex: 1,
+                data: tempData.electric?.map((item) => item.value),
+              },
+              {
+                type: 0,
+                name: '药耗',
+                data: tempData.medicine?.map((item) => item.value),
+              },
+              {
+                type: 0,
+                name: '进水水量',
+                yIndex: 2,
+                data: tempData.waterIn?.map((item) => item.value),
+              },
+              {
+                type: 2,
+                name: '出水水量',
+                yIndex: 2,
+                data: tempData.waterOut?.map((item) => item.value),
+              },
+            ],
+          },
+          tableData: tempData?.electric?.map((item, idx) => {
+            return {
+              key: `analy_table_${idx}`,
+              // value: item.value?.toFixed(2) | '-',
+              data_time: item.data_time,
+              electric: formatElcData(tempData?.electric, item?.data_time),
+              medicine:
+                tempData.medicine
+                  ?.find((cur) => cur.data_time === item.data_time)
+                  ?.value?.toFixed(2) || '-',
+              waterIn:
+                tempData.waterIn
+                  ?.find((cur) => cur.data_time === item.data_time)
+                  ?.value?.toFixed(2) || '-',
+              waterOut:
+                tempData.waterOut
+                  ?.find((cur) => cur.data_time === item.data_time)
+                  ?.value?.toFixed(2) || '-',
+            };
+          }),
+        };
+      },
+    },
+  );
+
+  const ProcessSectionRequest = useRequest(queryProcessSection, {
+    manual: true,
+    onSuccess: (data) => {
+      if (data.length > 0) {
+        let section;
+        section = data[0];
+        setTitle(section.name);
+      }
+    },
+  });
+
+  const formatElcData = (data, data_time) => {
+    const value = data?.find((cur) => cur.data_time == data_time)?.value;
+    return value === 0 ? '-' : value.toFixed(2);
+  };
+
+  const handleTimeRangeChange = (range) => {
+    if (range?.length === 2) {
+      run({
+        s_time: dayjs(range[0]).format('YYYY-MM-DD 00:00:00'),
+        e_time: dayjs(range[1]).format('YYYY-MM-DD HH:mm:ss'),
+      });
+    } else {
+      run(defaultTime);
+    }
+  };
+
+  const handleTimeBTNClick = (unit) => {
+    run({
+      s_time: dayjs().subtract(1, unit).format('YYYY-MM-DD 00:00:00'),
+      e_time: dayjs().format('YYYY-MM-DD HH:mm:ss'),
+    });
+  };
+
+  return (
+    <div className="card-box" style={{ padding: '0.2rem' }}>
+      <div>
+        <SubTitle title={'数据曲线'} fontSize="0.26rem" />
+        <div className={styles.timeBtn}>
+          <RangePicker inputReadOnly onChange={handleTimeRangeChange} />
+          <Button
+            type="primary"
+            shape="round"
+            onClick={() => {
+              handleTimeBTNClick('day');
+            }}
+          >
+            近一天
+          </Button>
+          <Button
+            type="primary"
+            shape="round"
+            onClick={() => {
+              handleTimeBTNClick('week');
+            }}
+          >
+            近一周
+          </Button>
+          <Button
+            type="primary"
+            shape="round"
+            onClick={() => {
+              handleTimeBTNClick('month');
+            }}
+          >
+            近一个月
+          </Button>
+        </div>
+        <div style={{ height: '3.5rem', marginBottom: '0.1rem' }}>
+          {data?.chartData && (
+            <ChartModule {...data.chartData} legend={{ right: '30%' }} />
+          )}
+        </div>
+      </div>
+      <div style={{ marginTop: '0.2rem' }}>
+        <SubTitle title={'数据列表'} fontSize="0.26rem" />
+        <Table
+          loading={loading}
+          columns={columns}
+          dataSource={data?.tableData?.sort((a, b) =>
+            b?.data_time?.localeCompare(a?.data_time),
+          )}
+        />
+      </div>
+    </div>
+  );
+};
+
+export default CostAnalysis;

+ 12 - 0
src/pages/SmartOps/operationManage/CostAnalysis/CostAnalysis.less

@@ -0,0 +1,12 @@
+.timeBtn > * {
+  margin-right: 0.1rem;
+}
+.timeBtn {
+  margin-bottom: 0.1rem;
+  margin-right: 0.1rem;
+  :global {
+    .ant-btn {
+      height: 0.5rem;
+    }
+  }
+}

+ 83 - 0
src/pages/SmartOps/operationManage/index.js

@@ -0,0 +1,83 @@
+import TabsContent from '@/components/TabsContent';
+import { ChemCost } from '@/pages/Home/ChemCostComparison';
+import { EnergyCost } from '@/pages/Home/EnergyCostComparison';
+import { EnergyDetail } from '@/pages/Home/EnergyCostDetail';
+import { WaterQuality } from '@/pages/Home/QualityMng';
+import { WaterAmt } from '@/pages/Home/WaterAmtMng';
+import { Button } from 'antd';
+import { useState } from 'react';
+import CostAnalysis from './CostAnalysis/CostAnalysis';
+
+const OperationManage = () => {
+  const [showEnergyDetail, setShowEnergyDetail] = useState(false);
+
+  const items = [
+    {
+      label: '成本分析',
+      key: '1',
+      children: <CostAnalysis />,
+    },
+    {
+      label: '水质分析',
+      key: '2',
+      children: <WaterQuality />,
+    },
+    {
+      label: '水量分析',
+      key: '3',
+      children: <WaterAmt />,
+    },
+    {
+      label: '药耗分析',
+      key: '4',
+      children: <ChemCost open />,
+    },
+    {
+      label: '能耗分析',
+      key: '5',
+      children: showEnergyDetail ? (
+        <div style={{ padding: '0.2rem', position: 'relative' }}>
+          <Button
+            style={{
+              position: 'absolute',
+              top: '0.3rem',
+              right: '0.3rem',
+              fontSize: '0.26rem',
+              height: '0.5rem',
+              zIndex: 1,
+            }}
+            onClick={() => {
+              setShowEnergyDetail(false);
+            }}
+          >
+            返回
+          </Button>
+          <EnergyDetail />
+        </div>
+      ) : (
+        <EnergyCost
+          open
+          detailClick={() => {
+            setShowEnergyDetail(!showEnergyDetail);
+          }}
+        />
+      ),
+    },
+  ];
+
+  const onTabsChange = () => {};
+
+  return (
+    <TabsContent
+      small
+      bold={false}
+      spacing={2.5}
+      center={false}
+      defaultActiveKey="1"
+      items={items}
+      onChange={onTabsChange}
+    />
+  );
+};
+
+export default OperationManage;

+ 62 - 0
src/pages/SmartOps/predictionAnalysis/PredictionAnalysis.js

@@ -0,0 +1,62 @@
+import { queryMembraneList } from '@/services/SmartOps';
+import { UnityAction } from '@/utils/utils';
+import { RightOutlined } from '@ant-design/icons';
+import { history, useParams, useRequest } from '@umijs/max';
+import styles from './PredictionAnalysis.less';
+
+const PredictionAnalysis = () => {
+  const { projectId } = useParams();
+
+  const { data: deviceList } = useRequest(queryMembraneList, {
+    defaultParams: [{ project_id: projectId, type: 'uf' }],
+    formatResult: (result) => {
+      console.log(result.data.list);
+      if (result?.data?.list) {
+        return result.data.list;
+      }
+    },
+  });
+
+  const toDetail = (code) => {
+    const devs = {};
+    deviceList.forEach((item) => {
+      devs[item.device_code] = 0;
+    });
+    const msg = {
+      SysName: '超滤工艺单元',
+      SysDevs: devs,
+    };
+    UnityAction.sendMsg('ProcessAnalysisDetail', JSON.stringify(msg));
+
+    history.push(`/smart-ops/prediction/${projectId}?code=${code}`);
+  };
+
+  return (
+    <>
+      <div className="card-box" style={{ padding: '0.2rem' }}>
+        <div className={styles.title}>超滤工艺单元</div>
+        {deviceList?.map((item, index) => {
+          return (
+            <div
+              key={item.device_code}
+              className={styles.itemContainer}
+              onClick={() => {
+                toDetail(item.device_code);
+              }}
+            >
+              <div>{`${item.device_name}(${item.device_code})`}</div>
+              <div className={styles.toDetail}>
+                预测分析
+                <RightOutlined
+                  style={{ marginLeft: '0.1rem', color: 'gray' }}
+                />
+              </div>
+            </div>
+          );
+        })}
+      </div>
+    </>
+  );
+};
+
+export default PredictionAnalysis;

Some files were not shown because too many files changed in this diff