Browse Source

驾驶舱以及limited部分代码

xjj 2 years ago
parent
commit
d109ea915d
38 changed files with 5274 additions and 318 deletions
  1. 2 2
      .umirc.ts
  2. 14 0
      src/Frameworks/SysRouter/index.ts
  3. 1 0
      src/Frameworks/typing.d.ts
  4. 1 1
      src/Project/Functions/FuncMain.ts
  5. 3 1
      src/Project/Functions/LevelAFunctions/FuncDataMeter.ts
  6. 55 0
      src/Project/Functions/LevelAFunctions/FuncLimitedSpace.ts
  7. 3 0
      src/Project/Os.ts
  8. 3 1
      src/Project/constants/index.ts
  9. 88 91
      src/Project/pages/DataMeter/Model/AlarmCenter.tsx
  10. 11 3
      src/Project/pages/DataMeter/Model/ChartBox/index.less
  11. 663 0
      src/Project/pages/DataMeter/Model/DataCenter/chartConfig.ts
  12. 303 0
      src/Project/pages/DataMeter/Model/DataCenter/index.tsx
  13. 649 0
      src/Project/pages/DataMeter/Model/DataCenter/utils.js
  14. 1 9
      src/Project/pages/DataMeter/Model/Map.tsx
  15. 232 0
      src/Project/pages/DataMeter/Model/MessageCenter.tsx
  16. 1 1
      src/Project/pages/DataMeter/Model/Monitor.tsx
  17. 11 7
      src/Project/pages/DataMeter/Model/ProjectInfo.tsx
  18. 0 28
      src/Project/pages/DataMeter/Model/index.less
  19. 9 28
      src/Project/pages/DataMeter/Model/index.tsx
  20. 54 55
      src/Project/pages/DataMeter/index.less
  21. 29 52
      src/Project/pages/DataMeter/index.tsx
  22. 3 1
      src/Project/pages/DataMeter/typings.d.ts
  23. 147 0
      src/Project/pages/LimitedSpace/Data.js
  24. 295 0
      src/Project/pages/LimitedSpace/Detail.js
  25. 130 0
      src/Project/pages/LimitedSpace/EditModal.js
  26. 293 0
      src/Project/pages/LimitedSpace/LimitedSpace.less
  27. 360 0
      src/Project/pages/LimitedSpace/List.js
  28. 230 0
      src/Project/pages/LimitedSpace/Operation.js
  29. 392 0
      src/Project/pages/LimitedSpace/OperationDetail.js
  30. 278 0
      src/Project/pages/LimitedSpace/Save.js
  31. 182 0
      src/Project/pages/LimitedSpace/SaveModal.js
  32. 28 0
      src/Project/pages/LimitedSpace/SaveModal.less
  33. 108 0
      src/Project/pages/LimitedSpace/SearchModal.js
  34. 131 0
      src/Project/pages/LimitedSpace/index.js
  35. 381 0
      src/Project/pages/LimitedSpace/models/limitedSpace.js
  36. 106 1
      src/Project/pages/global.less
  37. 1 0
      src/Project/pages/home.tsx
  38. 76 37
      src/Project/services/DataMeter.ts

+ 2 - 2
.umirc.ts

@@ -21,8 +21,8 @@ export default defineConfig({
   npmClient: 'yarn',
   proxy: {
     '/api': {
-      target: 'http://47.96.12.136:8788/',
-      // target: 'http://47.96.12.136:8888/',
+      // target: 'http://47.96.12.136:8788/',
+      target: 'http://47.96.12.136:8888/',
       // target: 'http://120.55.44.4:8900/',
       changeOrigin: true,
     },

+ 14 - 0
src/Frameworks/SysRouter/index.ts

@@ -72,6 +72,20 @@ export const route: GT.IRouterOptions[] = [
       header: false,
     },
   },
+  {
+    key: PAGE_KEY.LimitedIndex,
+    //@ts-ignore
+    component: () => import('@/Project/pages/LimitedSpace/index'),
+    //@ts-ignore
+    // models: () => import('@/Project/pages/LimitedSpace/models/limitedSpace'),
+    options: {
+      // header: false,
+      top: 0,
+      right: 0,
+      height: '100%',
+      width: 800,
+    },
+  },
 ];
 
 let Pages: { [key: string]: any } = {};

+ 1 - 0
src/Frameworks/typing.d.ts

@@ -3,5 +3,6 @@ import Os from "@/Project/Os";
 declare global {
   interface Window {
     GT_APP: Os;
+    render: any;
   }
 }

+ 1 - 1
src/Project/Functions/FuncMain.ts

@@ -22,8 +22,8 @@ export default class FuncMain extends Func<FuncMainState> {
       //   () => null,
       //   () => window.GT_APP.funcProjectSelection.setActive(false),
       // );
-      sm.addState(FuncMainState.Login, new LoginHandle());
       sm.addState(FuncMainState.DataMeter, new DataMeterHandle());
+      sm.addState(FuncMainState.Login, new LoginHandle());
       sm.addState(FuncMainState.PlatformMenu, new PlatformMenuHandle());
 
       sm.addState(

+ 3 - 1
src/Project/Functions/LevelAFunctions/FuncDataMeter.ts

@@ -21,7 +21,9 @@ export default class FuncDataMeter extends Func<FuncDataMeterState> {
     });
   }
   onIdleStateIn(): void {
-    SysPage.add(PAGE_KEY.DataMeter);
+    SysPage.add(PAGE_KEY.DataMeter, {
+      subModule: 2,
+    });
   }
   onIdleStateExit(): void {
     SysPage.removeByKey(PAGE_KEY.DataMeter);

+ 55 - 0
src/Project/Functions/LevelAFunctions/FuncLimitedSpace.ts

@@ -0,0 +1,55 @@
+import Func from '@/Engine/ECS/Function';
+import SysPage from '@/Frameworks/SysPage';
+import { STORAGE_TYPE, LocalService } from '@/Frameworks/SysStorage';
+import { PAGE_KEY } from '@/Project/constants';
+import { FuncMainState } from '../FuncMain';
+
+export enum FuncLimitedSpaceState {
+  idle,
+  index,
+  data
+}
+
+export default class FuncLimitedSpace extends Func<FuncLimitedSpaceState> {
+  constructor(name: string) {
+    super(name);
+    super.initStates((sm) => {
+      sm.addState(
+        FuncLimitedSpaceState.idle,
+        this.onIdleStateIn,
+        null,
+        this.onIdleStateExit,
+      );
+      sm.addState(
+        FuncLimitedSpaceState.index,
+        this.onIndexStateIn,
+        null,
+        this.onIndexStateExit,
+      );
+      sm.addState(
+        FuncLimitedSpaceState.data,
+        this.onIndexStateIn,
+        null,
+        this.onIndexStateExit,
+      );
+    });
+  }
+  onIdleStateIn(): void {
+    window.GT_APP.funcLimitedSpace.changeState(FuncLimitedSpaceState.index)
+    // window.GT_APP.funcLimitedSpace.changeState(FuncLimitedSpaceState.data)
+  }
+  onIdleStateExit(): void {
+  }
+  onIndexStateIn(): void {
+    SysPage.add(PAGE_KEY.LimitedIndex)
+  }
+  onIndexStateExit(): void {
+    SysPage.removeByKey(PAGE_KEY.LimitedIndex)
+  }
+  onDataStateIn(): void {
+    SysPage.add(PAGE_KEY.LimitedData)
+  }
+  onDataStateExit(): void {
+    SysPage.removeByKey(PAGE_KEY.LimitedData)
+  }
+}

+ 3 - 0
src/Project/Os.ts

@@ -1,5 +1,6 @@
 import FuncMain from './Functions/FuncMain';
 import FuncDataMeter from './Functions/LevelAFunctions/FuncDataMeter';
+import FuncLimitedSpace from './Functions/LevelAFunctions/FuncLimitedSpace';
 import FuncLogin from './Functions/LevelAFunctions/FuncLogin';
 import FuncMap from './Functions/LevelAFunctions/FuncMap';
 import FuncPlatformMenu from './Functions/LevelAFunctions/FuncPlatformMenu';
@@ -12,6 +13,7 @@ class Os {
   funcMap: FuncMap;
   funcProjectSelection: FuncProjectSelection;
   funcDataMeter: FuncDataMeter;
+  funcLimitedSpace: FuncLimitedSpace;
   constructor() {
     this.funcMain = new FuncMain('FuncMain');
     this.funcLogin = new FuncLogin('FuncLogin');
@@ -21,6 +23,7 @@ class Os {
       'FuncProjectSelection',
     );
     this.funcDataMeter = new FuncDataMeter('FuncDataMeter');
+    this.funcLimitedSpace = new FuncLimitedSpace('FuncLimitedSpace');
   }
 }
 

+ 3 - 1
src/Project/constants/index.ts

@@ -5,7 +5,9 @@ export enum PAGE_KEY {
   PlatformMenu,
   ProjectSelection,
   Map,
-  DataMeter
+  DataMeter,
+  LimitedIndex,
+  LimitedData,
 }
 
 export const BuildNodeCode = 'func-01-build';

+ 88 - 91
src/Project/pages/DataMeter/Model/AlarmCenter.tsx

@@ -1,6 +1,6 @@
 import React, { useState, useEffect } from 'react';
 import { Popover, Radio, Avatar, Tooltip, Empty, Select } from 'antd';
-import style from './index.less';
+import style from '../index.less';
 import moment from 'moment';
 import { ChartBoxTitle } from './ChartBox';
 import { useRequest } from '@umijs/max';
@@ -17,8 +17,6 @@ const { Option } = Select;
 
 function AlarmCenter(props: DataMeter.IModelsProps) {
   const { child, setActive, layout, subModule, projectId } = props;
-  // const [active, setActive] = useState();
-  // const active = layout.active || child.find(item => item.show).key;
   const [active, setSelfActive] = useState(
     layout.active || child.find((item) => item.show)?.key,
   );
@@ -70,10 +68,11 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
   const projectRequest = useRequest(getProjectList, {
     cacheKey: 'projectList',
     staleTime: -1,
-  });
+  },
+);
   const projectAlarmList = projectAlarmRequest.data?.list || [];
   const issueList = IssueListRequest.data?.list || [];
-  const faultAnalysis = faultAnalysisRequest.data?.list || [];
+  const faultAnalysis = faultAnalysisRequest.data || [];
   const breakdownList = breakdownRecordRequest.data?.list || [];
   const patrolList = patrolRecordRequest.data?.list || [];
   const projectList = projectRequest.data?.list || [];
@@ -82,7 +81,7 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
     return (
       <Popover
         placement="topLeft"
-        content={<div style={{ maxWidth: '2rem' }}>{title}</div>}
+        content={<div style={{ maxWidth: '200px' }}>{title}</div>}
       >
         {title}
       </Popover>
@@ -125,8 +124,8 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                 <td
                   style={
                     subModule == 0
-                      ? { width: '33%', paddingRight: '0.1rem' }
-                      : { width: '50%', paddingRight: '0.1rem' }
+                      ? { width: '33%', paddingRight: '10px' }
+                      : { width: '50%', paddingRight: '10px' }
                   }
                   className={style.alarmTitle}
                 >
@@ -137,12 +136,12 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                     subModule == 0
                       ? {
                           width: '33%',
-                          paddingRight: '0.1rem',
+                          paddingRight: '10px',
                           whiteSpace: 'nowrap',
                         }
                       : {
                           width: '50%',
-                          paddingRight: '0.1rem',
+                          paddingRight: '10px',
                           whiteSpace: 'nowrap',
                         }
                   }
@@ -155,8 +154,8 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
             content = (
               <div
                 style={{
-                  paddingTop: '0.12rem',
-                  paddingLeft: '0.14rem',
+                  paddingTop: '12px',
+                  paddingLeft: '14px',
                   flex: '1',
                   height: 0,
                 }}
@@ -174,8 +173,8 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                       <th
                         style={
                           subModule == 0
-                            ? { width: '33%', paddingRight: '0.1rem' }
-                            : { width: '50%', paddingRight: '0.1rem' }
+                            ? { width: '33%', paddingRight: '10px' }
+                            : { width: '50%', paddingRight: '10px' }
                         }
                       >
                         设备
@@ -183,8 +182,8 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                       <th
                         style={
                           subModule == 0
-                            ? { width: '33%', paddingRight: '0.1rem' }
-                            : { width: '50%', paddingRight: '0.1rem' }
+                            ? { width: '33%', paddingRight: '10px' }
+                            : { width: '50%', paddingRight: '10px' }
                         }
                       >
                         故障类型
@@ -195,7 +194,7 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                 <div
                   style={{
                     overflowY: 'scroll',
-                    height: 'calc(100% - 0.24rem)',
+                    height: 'calc(100% - 22px)',
                   }}
                 >
                   <table className={style.alarmTable}>
@@ -227,8 +226,8 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                 <td
                   style={
                     subModule == 0
-                      ? { width: '40%', paddingRight: '0.1rem' }
-                      : { width: '50%', paddingRight: '0.1rem' }
+                      ? { width: '40%', paddingRight: '10px' }
+                      : { width: '50%', paddingRight: '10px' }
                   }
                   className={style.alarmTitle}
                 >
@@ -239,12 +238,12 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                     subModule == 0
                       ? {
                           width: '20%',
-                          paddingRight: '0.1rem',
+                          paddingRight: '10px',
                           whiteSpace: 'nowrap',
                         }
                       : {
                           width: '50%',
-                          paddingRight: '0.1rem',
+                          paddingRight: '10px',
                           whiteSpace: 'nowrap',
                         }
                   }
@@ -255,7 +254,7 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                   <td
                     style={{
                       width: '40%',
-                      paddingRight: '0.1rem',
+                      paddingRight: '10px',
                       whiteSpace: 'nowrap',
                     }}
                     className={style.alarmTitle}
@@ -272,8 +271,8 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
             content = (
               <div
                 style={{
-                  paddingTop: '0.12rem',
-                  paddingLeft: '0.14rem',
+                  paddingTop: '12px',
+                  paddingLeft: '14px',
                   flex: '1',
                   height: 0,
                 }}
@@ -291,8 +290,8 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                       <th
                         style={
                           subModule == 0
-                            ? { width: '40%', paddingRight: '0.1rem' }
-                            : { width: '50%', paddingRight: '0.1rem' }
+                            ? { width: '40%', paddingRight: '10px' }
+                            : { width: '50%', paddingRight: '10px' }
                         }
                       >
                         设备
@@ -300,14 +299,14 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                       <th
                         style={
                           subModule == 0
-                            ? { width: '20%', paddingRight: '0.1rem' }
-                            : { width: '50%', paddingRight: '0.1rem' }
+                            ? { width: '20%', paddingRight: '10px' }
+                            : { width: '50%', paddingRight: '10px' }
                         }
                       >
                         日期
                       </th>
                       {subModule == 0 && (
-                        <th style={{ width: '40%', paddingRight: '0.1rem' }}>
+                        <th style={{ width: '40%', paddingRight: '10px' }}>
                           项目名称
                         </th>
                       )}
@@ -317,7 +316,7 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                 <div
                   style={{
                     overflowY: 'scroll',
-                    height: 'calc(100% - 0.24rem)',
+                    height: 'calc(100% - 22px)',
                   }}
                 >
                   <table className={style.alarmTable}>
@@ -349,8 +348,8 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                 <td
                   style={
                     subModule == 0
-                      ? { width: '40%', paddingRight: '0.1rem' }
-                      : { width: '50%', paddingRight: '0.1rem' }
+                      ? { width: '40%', paddingRight: '10px' }
+                      : { width: '50%', paddingRight: '10px' }
                   }
                   className={style.alarmTitle}
                 >
@@ -361,12 +360,12 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                     subModule == 0
                       ? {
                           width: '20%',
-                          paddingRight: '0.1rem',
+                          paddingRight: '10px',
                           whiteSpace: 'nowrap',
                         }
                       : {
                           width: '50%',
-                          paddingRight: '0.1rem',
+                          paddingRight: '10px',
                           whiteSpace: 'nowrap',
                         }
                   }
@@ -376,7 +375,7 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                 </td>
                 {subModule == 0 && (
                   <td
-                    style={{ width: '40%', paddingRight: '0.1rem' }}
+                    style={{ width: '40%', paddingRight: '10px' }}
                     className={style.alarmTitle}
                   >
                     {getTitle(
@@ -391,8 +390,8 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
             content = (
               <div
                 style={{
-                  paddingTop: '0.12rem',
-                  paddingLeft: '0.14rem',
+                  paddingTop: '12px',
+                  paddingLeft: '14px',
                   flex: '1',
                   height: 0,
                 }}
@@ -410,8 +409,8 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                       <th
                         style={
                           subModule == 0
-                            ? { width: '40%', paddingRight: '0.1rem' }
-                            : { width: '50%', paddingRight: '0.1rem' }
+                            ? { width: '40%', paddingRight: '10px' }
+                            : { width: '50%', paddingRight: '10px' }
                         }
                       >
                         路线名称
@@ -419,14 +418,14 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                       <th
                         style={
                           subModule == 0
-                            ? { width: '20%', paddingRight: '0.1rem' }
-                            : { width: '50%', paddingRight: '0.1rem' }
+                            ? { width: '20%', paddingRight: '10px' }
+                            : { width: '50%', paddingRight: '10px' }
                         }
                       >
                         日期
                       </th>
                       {subModule == 0 && (
-                        <th style={{ width: '40%', paddingRight: '0.1rem' }}>
+                        <th style={{ width: '40%', paddingRight: '10px' }}>
                           项目名称
                         </th>
                       )}
@@ -436,7 +435,7 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                 <div
                   style={{
                     overflowY: 'scroll',
-                    height: 'calc(100% - 0.24rem)',
+                    height: 'calc(100% - 22px)',
                   }}
                 >
                   <table className={style.alarmTable}>
@@ -469,7 +468,7 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
             >
               {/* <td>{item.MsgBody}</td> */}
               <td
-                style={{ width: '40%', paddingRight: '0.1rem' }}
+                style={{ width: '40%', paddingRight: '10px' }}
                 className={style.alarmTitle}
               >
                 {getTitle(item.MsgBody)}
@@ -480,12 +479,12 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                   subModule == 0
                     ? {
                         width: '20%',
-                        paddingRight: '0.1rem',
+                        paddingRight: '10px',
                         whiteSpace: 'nowrap',
                       }
                     : {
                         width: '50%',
-                        paddingRight: '0.1rem',
+                        paddingRight: '10px',
                         whiteSpace: 'nowrap',
                       }
                 }
@@ -494,7 +493,7 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
               </td>
               {subModule == 0 && (
                 <td
-                  style={{ width: '40%', paddingRight: '0.1rem' }}
+                  style={{ width: '40%', paddingRight: '10px' }}
                   className={style.alarmTitle}
                 >
                   {getTitle(
@@ -509,8 +508,8 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
           content = (
             <div
               style={{
-                paddingTop: '0.12rem',
-                paddingLeft: '0.14rem',
+                paddingTop: '12px',
+                paddingLeft: '14px',
                 flex: '1',
                 height: 0,
               }}
@@ -528,8 +527,8 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                     <th
                       style={
                         subModule == 0
-                          ? { width: '40%', paddingRight: '0.1rem' }
-                          : { width: '50%', paddingRight: '0.1rem' }
+                          ? { width: '40%', paddingRight: '10px' }
+                          : { width: '50%', paddingRight: '10px' }
                       }
                     >
                       预警内容
@@ -538,14 +537,14 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                     <th
                       style={
                         subModule == 0
-                          ? { width: '20%', paddingRight: '0.1rem' }
-                          : { width: '50%', paddingRight: '0.1rem' }
+                          ? { width: '20%', paddingRight: '10px' }
+                          : { width: '50%', paddingRight: '10px' }
                       }
                     >
                       日期
                     </th>
                     {subModule == 0 && (
-                      <th style={{ width: '40%', paddingRight: '0.1rem' }}>
+                      <th style={{ width: '40%', paddingRight: '10px' }}>
                         项目名称
                       </th>
                     )}
@@ -555,7 +554,7 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
               <div
                 style={{
                   overflowY: 'scroll',
-                  height: 'calc(100% - 0.24rem)',
+                  height: 'calc(100% - 22px)',
                 }}
               >
                 <table className={style.alarmTable}>
@@ -589,8 +588,8 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
               <td
                 style={
                   subModule == 0
-                    ? { width: '25%', paddingRight: '0.1rem' }
-                    : { width: '33%', paddingRight: '0.1rem' }
+                    ? { width: '25%', paddingRight: '10px' }
+                    : { width: '33%', paddingRight: '10px' }
                 }
                 className={style.alarmTitle}
               >
@@ -600,8 +599,8 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
               <td
                 style={
                   subModule == 0
-                    ? { width: '25%', paddingRight: '0.1rem' }
-                    : { width: '33%', paddingRight: '0.1rem' }
+                    ? { width: '25%', paddingRight: '10px' }
+                    : { width: '33%', paddingRight: '10px' }
                 }
                 className={style.alarmTitle}
               >
@@ -612,12 +611,12 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                   subModule == 0
                     ? {
                         width: '20%',
-                        paddingRight: '0.1rem',
+                        paddingRight: '10px',
                         whiteSpace: 'nowrap',
                       }
                     : {
                         width: '33%',
-                        paddingRight: '0.1rem',
+                        paddingRight: '10px',
                         whiteSpace: 'nowrap',
                       }
                 }
@@ -626,7 +625,7 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
               </td>
               {subModule == 0 && (
                 <td
-                  style={{ width: '30%', paddingRight: '0.1rem' }}
+                  style={{ width: '30%', paddingRight: '10px' }}
                   className={style.alarmTitle}
                 >
                   {getTitle(
@@ -641,8 +640,8 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
           content = (
             <div
               style={{
-                paddingTop: '0.12rem',
-                paddingLeft: '0.14rem',
+                paddingTop: '12px',
+                paddingLeft: '14px',
                 flex: '1',
                 height: 0,
               }}
@@ -660,8 +659,8 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                     <th
                       style={
                         subModule == 0
-                          ? { width: '25%', paddingRight: '0.1rem' }
-                          : { width: '33%', paddingRight: '0.1rem' }
+                          ? { width: '25%', paddingRight: '10px' }
+                          : { width: '33%', paddingRight: '10px' }
                       }
                     >
                       标题
@@ -669,8 +668,8 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                     <th
                       style={
                         subModule == 0
-                          ? { width: '25%', paddingRight: '0.1rem' }
-                          : { width: '33%', paddingRight: '0.1rem' }
+                          ? { width: '25%', paddingRight: '10px' }
+                          : { width: '33%', paddingRight: '10px' }
                       }
                     >
                       状态
@@ -678,14 +677,14 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                     <th
                       style={
                         subModule == 0
-                          ? { width: '20%', paddingRight: '0.1rem' }
-                          : { width: '33%', paddingRight: '0.1rem' }
+                          ? { width: '20%', paddingRight: '10px' }
+                          : { width: '33%', paddingRight: '10px' }
                       }
                     >
                       日期
                     </th>
                     {subModule == 0 && (
-                      <th style={{ width: '30%', paddingRight: '0.1rem' }}>
+                      <th style={{ width: '30%', paddingRight: '10px' }}>
                         项目名称
                       </th>
                     )}
@@ -695,7 +694,7 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
               <div
                 style={{
                   overflowY: 'scroll',
-                  height: 'calc(100% - 0.24rem)',
+                  height: 'calc(100% - 22px)',
                 }}
               >
                 <table className={style.alarmTable}>
@@ -781,8 +780,8 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
               <td
                 style={
                   subModule == 0
-                    ? { width: '25%', paddingRight: '0.1rem' }
-                    : { width: '33%', paddingRight: '0.1rem' }
+                    ? { width: '25%', paddingRight: '10px' }
+                    : { width: '33%', paddingRight: '10px' }
                 }
                 className={style.alarmTitle}
               >
@@ -791,8 +790,8 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
               <td
                 style={
                   subModule == 0
-                    ? { width: '15%', paddingRight: '0.1rem' }
-                    : { width: '33%', paddingRight: '0.1rem' }
+                    ? { width: '15%', paddingRight: '10px' }
+                    : { width: '33%', paddingRight: '10px' }
                 }
                 className={style.alarmTitle}
               >
@@ -803,12 +802,12 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                   subModule == 0
                     ? {
                         width: '20%',
-                        paddingRight: '0.1rem',
+                        paddingRight: '10px',
                         whiteSpace: 'nowrap',
                       }
                     : {
                         width: '33%',
-                        paddingRight: '0.1rem',
+                        paddingRight: '10px',
                         whiteSpace: 'nowrap',
                       }
                 }
@@ -817,7 +816,7 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
               </td>
               {subModule == 0 && (
                 <td
-                  style={{ width: '40%', paddingRight: '0.1rem' }}
+                  style={{ width: '40%', paddingRight: '10px' }}
                   className={style.alarmTitle}
                 >
                   {getTitle(
@@ -832,8 +831,8 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
           content = (
             <div
               style={{
-                paddingTop: '0.12rem',
-                paddingLeft: '0.14rem',
+                paddingTop: '12px',
+                paddingLeft: '14px',
                 flex: '1',
                 height: 0,
               }}
@@ -851,8 +850,8 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                     <th
                       style={
                         subModule == 0
-                          ? { width: '25%', paddingRight: '0.1rem' }
-                          : { width: '33%', paddingRight: '0.1rem' }
+                          ? { width: '25%', paddingRight: '10px' }
+                          : { width: '33%', paddingRight: '10px' }
                       }
                     >
                       预警内容
@@ -860,8 +859,8 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                     <th
                       style={
                         subModule == 0
-                          ? { width: '15%', paddingRight: '0.1rem' }
-                          : { width: '33%', paddingRight: '0.1rem' }
+                          ? { width: '15%', paddingRight: '10px' }
+                          : { width: '33%', paddingRight: '10px' }
                       }
                     >
                       状态
@@ -869,14 +868,14 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
                     <th
                       style={
                         subModule == 0
-                          ? { width: '20%', paddingRight: '0.1rem' }
-                          : { width: '33%', paddingRight: '0.1rem' }
+                          ? { width: '20%', paddingRight: '10px' }
+                          : { width: '33%', paddingRight: '10px' }
                       }
                     >
                       日期
                     </th>
                     {subModule == 0 && (
-                      <th style={{ width: '40%', paddingRight: '0.1rem' }}>
+                      <th style={{ width: '40%', paddingRight: '10px' }}>
                         项目名称
                       </th>
                     )}
@@ -886,7 +885,7 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
               <div
                 style={{
                   overflowY: 'scroll',
-                  height: 'calc(100% - 0.24rem)',
+                  height: 'calc(100% - 22px)',
                 }}
               >
                 <table className={style.alarmTable}>
@@ -964,7 +963,6 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
           className={style.activeSelect}
           onChange={onChange}
           dropdownClassName="dataMeter"
-          // style={{ width: 200, marginLeft: '0.16rem', marginTop: 10 }}
           defaultValue="4,5,6"
           options={[
             { value: '4,5,6', label: '全部' },
@@ -979,7 +977,6 @@ function AlarmCenter(props: DataMeter.IModelsProps) {
           className={style.activeSelect}
           onChange={onChangeFault}
           dropdownClassName="dataMeter"
-          // style={{ width: 200, marginLeft: '0.16rem', marginTop: 10 }}
           defaultValue={faultActive}
         >
           <Option value={1}>工艺诊断异常</Option>

+ 11 - 3
src/Project/pages/DataMeter/Model/ChartBox/index.less

@@ -7,11 +7,17 @@
   align-items: center;
   position: relative;
 
+  .titleLeft {
+    display: flex;
+    align-items: flex-start;
+  }
+
   .titleImg {
     width: 4px;
     height: 30px;
     margin-right: 10px;
   }
+
   .titleBg {
     width: 100%;
     height: 46.5px;
@@ -25,13 +31,14 @@
     line-height: 20px;
     letter-spacing: 2px;
   }
-  .icon{
+
+  .icon {
     width: 20px;
     height: 20px;
     pointer-events: initial;
   }
 
-  .titleBorder{
+  .titleBorder {
     position: absolute;
     top: 40px;
     left: 13px;
@@ -55,6 +62,7 @@
     background: url('@/Project/assets/chartBox/title-bg.png');
     background-size: 100% 100%;
   }
+
   .titleBgMini {
     background-image: url('@/Project/assets/chartBox/title-bg-mini.png');
   }
@@ -122,4 +130,4 @@
   .jiao;
   bottom: -1px;
   right: -1px;
-}
+}

+ 663 - 0
src/Project/pages/DataMeter/Model/DataCenter/chartConfig.ts

@@ -0,0 +1,663 @@
+// 固定图表
+export const defaultOptions = {
+  color: ['#079FFD', '#13EDF5', '#3815BB', '#366CDA', '#00FFAE'],
+  legend: {
+    textStyle: {
+      color: '#D7E4E8',
+    },
+  },
+  grid: {
+    top: '3%',
+    left: '2%',
+    right: '2%',
+    bottom: '2%',
+    containLabel: true,
+    borderColor: 'rgba(216, 216, 216, 0.6)',
+    borderWidth: '0.25px',
+  },
+  xAxis: {
+    axisLabel: {
+      color: '#D8E4E8',
+    },
+    axisLine: {
+      lineStyle: {
+        color: '#D8D8D8',
+      },
+    },
+    splitLine: {
+      lineStyle: {
+        color: '#fff',
+        opacity: 0.4,
+      },
+    },
+    nameTextStyle: {
+      fontSize: 10,
+    },
+  },
+  yAxis: {
+    axisLabel: {
+      color: '#D8E4E8',
+    },
+    axisLine: {
+      lineStyle: {
+        color: '#D8D8D8',
+      },
+    },
+    nameTextStyle: {
+      fontSize: 10,
+    },
+    splitLine: {
+      lineStyle: {
+        color: '#fff',
+        opacity: 0.4,
+      },
+    },
+  },
+};
+export const getOptions = () => {
+  return {
+    ...defaultOptions,
+    color: [
+      'rgba(0, 200, 255, 0.6)',
+      'rgba(0, 200, 255, 0.5)',
+      'rgba(0, 200, 255, 0.4)',
+      'rgba(0, 200, 255, 0.3)',
+      'rgba(0, 200, 255, 0.2)',
+    ],
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'cross',
+        label: {
+          backgroundColor: '#6a7985',
+        },
+      },
+    },
+    legend: {
+      data: ['1', '2', '3', '4', '5'],
+      textStyle: {
+        color: '#D7E4E8',
+      },
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true,
+      show: true,
+      borderColor: 'rgba(216, 216, 216, 0.6)',
+      borderWidth: '0.25px',
+    },
+    xAxis: [
+      {
+        type: 'category',
+        boundaryGap: false,
+        data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
+        axisLine: {
+          lineStyle: {
+            color: '#D8D8D8',
+          },
+        },
+        axisLabel: {
+          color: '#D8E4E8',
+        },
+      },
+    ],
+    yAxis: [
+      {
+        type: 'value',
+        axisLine: {
+          lineStyle: {
+            color: '#D8D8D8',
+          },
+        },
+        axisLabel: {
+          // formatter: '{value}%',
+          color: '#D8E4E8',
+        },
+      },
+    ],
+    series: [
+      {
+        name: '1',
+        type: 'line',
+        stack: '总量',
+        areaStyle: {},
+        emphasis: {
+          focus: 'series',
+        },
+        data: [120, 132, 101, 134, 90, 230, 210],
+        lineStyle: {
+          color: '#03DDFF',
+        },
+      },
+      {
+        name: '2',
+        type: 'line',
+        stack: '总量',
+        areaStyle: {},
+        emphasis: {
+          focus: 'series',
+        },
+        data: [220, 182, 191, 234, 290, 330, 310],
+        lineStyle: {
+          color: '#03DDFF',
+        },
+      },
+      {
+        name: '3',
+        type: 'line',
+        stack: '总量',
+        areaStyle: {},
+        emphasis: {
+          focus: 'series',
+        },
+        data: [150, 232, 201, 154, 190, 330, 410],
+        lineStyle: {
+          color: '#03DDFF',
+        },
+      },
+      {
+        name: '4',
+        type: 'line',
+        stack: '总量',
+        areaStyle: {},
+        emphasis: {
+          focus: 'series',
+        },
+        data: [320, 332, 301, 334, 390, 330, 320],
+        lineStyle: {
+          color: '#03DDFF',
+        },
+      },
+      {
+        name: '5',
+        type: 'line',
+        stack: '总量',
+        areaStyle: {},
+        emphasis: {
+          focus: 'series',
+        },
+        data: [820, 932, 901, 934, 1290, 1330, 1320],
+        lineStyle: {
+          color: '#03DDFF',
+        },
+      },
+    ],
+  };
+};
+
+export const getOptions2 = () => {
+  return {
+    ...defaultOptions,
+    // color: [
+    //   {
+    //     type: 'linear',
+    //     x: 0,
+    //     y: 0,
+    //     x2: 0,
+    //     y2: 1,
+    //     colorStops: [
+    //       {
+    //         offset: 0,
+    //         color: 'rgba(153, 255, 232, 0.30)', // 0% 处的颜色
+    //       },
+    //       {
+    //         offset: 1,
+    //         color: 'rgba(96, 209, 255, 0.5)', // 100% 处的颜色
+    //       },
+    //     ],
+    //     global: false, // 缺省为 false
+    //   },
+    //   {
+    //     type: 'linear',
+    //     x: 0,
+    //     y: 0,
+    //     x2: 0,
+    //     y2: 1,
+    //     colorStops: [
+    //       {
+    //         offset: 0,
+    //         color: 'rgba(153, 255, 232, 0.80)', // 0% 处的颜色
+    //       },
+    //       {
+    //         offset: 1,
+    //         color: 'rgba(96, 209, 255, 1)', // 100% 处的颜色
+    //       },
+    //     ],
+    //     global: false, // 缺省为 false
+    //   },
+    //   '#03DDFF',
+    // ],
+    grid: {
+      top: 34,
+      left: '2%',
+      right: '2%',
+      bottom: 10,
+      containLabel: true,
+    },
+    legend: {
+      data: ['1', '2', '3'],
+      ...defaultOptions.legend,
+    },
+    xAxis: [
+      {
+        type: 'category',
+        data: [
+          '1月',
+          '2月',
+          '3月',
+          '4月',
+          '5月',
+          '6月',
+          '7月',
+          '8月',
+          '9月',
+          '10月',
+          '11月',
+          '12月',
+        ],
+        axisPointer: {
+          type: 'shadow',
+        },
+        ...defaultOptions.xAxis,
+      },
+    ],
+    yAxis: [
+      {
+        type: 'value',
+        name: '水量',
+        min: 0,
+        max: 250,
+        interval: 50,
+        ...defaultOptions.yAxis,
+        axisLabel: {
+          formatter: '{value} ml',
+        },
+      },
+      {
+        type: 'value',
+        name: '温度',
+        min: 0,
+        max: 25,
+        interval: 5,
+        ...defaultOptions.yAxis,
+        axisLabel: {
+          formatter: '{value} °C',
+        },
+      },
+    ],
+    series: [
+      {
+        name: '1',
+        type: 'bar',
+        data: [
+          2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3,
+        ],
+      },
+      {
+        name: '2',
+        type: 'bar',
+        data: [
+          2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3,
+        ],
+      },
+      {
+        name: '3',
+        type: 'line',
+        yAxisIndex: 1,
+        data: [
+          2.0, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3, 23.4, 23.0, 16.5, 12.0, 6.2,
+        ],
+      },
+    ],
+  };
+};
+
+export const getOptions3 = () => {
+  return {
+    color: defaultOptions.color,
+    tooltip: {
+      trigger: 'item',
+    },
+    legend: {
+      top: '3%',
+      left: 'center',
+      ...defaultOptions.legend,
+    },
+    series: [
+      {
+        name: '来源',
+        type: 'pie',
+        radius: ['40%', '60%'],
+        center: ['50%', '60%'],
+        avoidLabelOverlap: false,
+        label: {
+          show: false,
+          position: 'center',
+        },
+        emphasis: {
+          label: {
+            show: true,
+            fontSize: '14',
+            fontWeight: 'bold',
+            color: '#fff',
+          },
+        },
+        labelLine: {
+          show: false,
+        },
+        data: [
+          { value: 1048, name: '1' },
+          { value: 735, name: '2' },
+          { value: 580, name: '3' },
+          { value: 484, name: '4' },
+          { value: 300, name: '5' },
+        ],
+      },
+    ],
+  };
+};
+
+export const getOptions4 = () => {
+  return {
+    // color: defaultOptions.color,
+    // grid: defaultOptions.grid,
+    ...defaultOptions,
+    color: ['#079FFD', '#079FFD'], // , '#FFAA00'
+    xAxis: {
+      scale: true,
+      ...defaultOptions.xAxis,
+    },
+    yAxis: {
+      scale: true,
+      ...defaultOptions.yAxis,
+    },
+    series: [
+      {
+        type: 'effectScatter',
+        // symbolSize: 20,
+        data: [
+          [172.7, 105.2],
+          [153.4, 42],
+        ],
+      },
+      {
+        type: 'scatter',
+        data: [
+          [161.2, 51.6],
+          [167.5, 59.0],
+          [176.2, 66.8],
+          [160.2, 75.2],
+          [175.0, 82.5],
+          [166.8, 57.2],
+          [176.5, 87.8],
+          [170.2, 72.8],
+          [174.0, 54.5],
+          [173.0, 59.8],
+          [179.9, 67.3],
+          [170.5, 67.8],
+          [160.0, 47.0],
+          [154.4, 46.2],
+          [162.0, 55.0],
+          [167.6, 61.0],
+          [160.7, 69.1],
+          [163.2, 55.9],
+          [152.4, 46.5],
+          [157.5, 54.3],
+          [168.3, 54.8],
+          [180.3, 60.7],
+          [165.5, 60.0],
+          [165.0, 62.0],
+          [164.5, 60.3],
+          [156.0, 52.7],
+          [160.0, 74.3],
+          [163.0, 62.0],
+          [165.7, 73.1],
+          [161.0, 80.0],
+          [162.0, 54.7],
+          [166.0, 53.2],
+          [174.0, 75.7],
+          [161.2, 54.8],
+          [155.0, 45.9],
+          [170.0, 70.6],
+          [176.2, 67.2],
+          [170.0, 69.4],
+          [162.5, 58.2],
+          [170.3, 64.8],
+          [164.1, 71.6],
+          [169.5, 52.8],
+          [163.2, 59.8],
+          [164.3, 59.8],
+          [163.0, 72.0],
+          [168.5, 65.2],
+          [166.8, 56.6],
+          [172.7, 105.2],
+          [155.0, 49.2],
+          [156.5, 67.2],
+          [164.0, 53.8],
+          [160.9, 54.4],
+          [162.8, 58.0],
+          [167.0, 59.8],
+          [160.0, 54.8],
+          [160.0, 43.2],
+          [168.9, 60.5],
+          [158.2, 46.4],
+          [156.0, 64.4],
+          [160.0, 48.8],
+          [167.1, 62.2],
+          [158.0, 55.5],
+          [167.6, 57.8],
+          [172.7, 69.5],
+          [167.6, 76.4],
+          [162.6, 61.4],
+          [167.6, 65.9],
+          [156.2, 58.6],
+          [175.2, 66.8],
+          [172.1, 56.6],
+          [162.6, 58.6],
+          [160.0, 55.9],
+          [165.1, 59.1],
+          [182.9, 81.8],
+          [166.4, 70.7],
+          [165.1, 56.8],
+          [175.3, 65.5],
+          [157.5, 48.6],
+          [163.8, 58.6],
+          [167.6, 63.6],
+          [165.1, 55.2],
+          [165.1, 62.7],
+          [168.9, 56.6],
+          [174.0, 55.5],
+          [176.5, 71.8],
+          [164.4, 55.5],
+          [160.7, 48.6],
+          [174.0, 66.4],
+          [163.8, 67.3],
+        ],
+      },
+    ],
+  };
+};
+
+export const getOptions5 = () => {
+  return {
+    ...defaultOptions,
+    color: ['#079FFD', '#366CDA', '#13EDF5'],
+    dataset: {
+      source: [
+        ['product', '2015', '2016', '2017'],
+        ['1号', 43.3, 85.8, 93.7],
+        ['8号', 83.1, 73.4, 55.1],
+        ['16号', 86.4, 65.2, 82.5],
+        ['24号', 72.4, 53.9, 39.1],
+      ],
+    },
+    grid: {
+      top: 34,
+      left: '2%',
+      right: '2%',
+      bottom: 10,
+      containLabel: true,
+    },
+    xAxis: { type: 'category', ...defaultOptions.xAxis },
+    series: [{ type: 'bar' }, { type: 'bar' }, { type: 'bar' }],
+  };
+};
+
+export const getOptions6 = () => {
+  return {
+    ...defaultOptions,
+    // color: [
+    //   {
+    //     type: 'linear',
+    //     x: 0,
+    //     y: 0,
+    //     x2: 0,
+    //     y2: 1,
+    //     colorStops: [
+    //       {
+    //         offset: 0,
+    //         color: 'rgba(0, 199, 255, 1)', // 0% 处的颜色
+    //       },
+    //       {
+    //         offset: 1,
+    //         color: 'rgba(71, 194, 251, 0)', // 100% 处的颜色
+    //       },
+    //     ],
+    //     global: false, // 缺省为 false
+    //   },
+    // ],
+    xAxis: {
+      type: 'category',
+      boundaryGap: false,
+      ...defaultOptions.xAxis,
+    },
+    yAxis: {
+      type: 'value',
+      boundaryGap: [0, '30%'],
+      ...defaultOptions.yAxis,
+    },
+    // visualMap: {
+    //   type: 'piecewise',
+    //   show: false,
+    //   dimension: 0,
+    //   seriesIndex: 0,
+    //   pieces: [
+    //     {
+    //       gt: 1,
+    //       lt: 3,
+    //       color: 'rgba(177,225,255, 0.4)',
+    //     },
+    //     {
+    //       gt: 5,
+    //       lt: 7,
+    //       color: 'rgba(177,225,255, 0.4)',
+    //     },
+    //   ],
+    // },
+    series: [
+      {
+        type: 'line',
+        smooth: 0.6,
+        symbol: 'none',
+        // markLine: {
+        //   symbol: ['none', 'none'],
+        //   label: { show: false },
+        //   data: [{ xAxis: 1 }, { xAxis: 3 }, { xAxis: 5 }, { xAxis: 7 }],
+        // },
+        areaStyle: {},
+        data: [
+          ['2019-10-10', 90],
+          ['2019-10-11', 105],
+          ['2019-10-12', 115],
+          ['2019-10-13', 124],
+          ['2019-10-14', 138],
+          ['2019-10-15', 145],
+          ['2019-10-16', 150],
+          ['2019-10-17', 167],
+          ['2019-10-18', 175],
+        ],
+        lineStyle: {
+          color: '#03DDFF',
+          width: 3,
+        },
+      },
+    ],
+  };
+};
+
+export const getOptions7 = () => {
+  return {
+    ...defaultOptions,
+    color: ['#13EDF5', '#366CDA', '#079FFD'],
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow', // 默认为直线,可选为:'line' | 'shadow'
+      },
+    },
+    grid: {
+      top: 34,
+      left: '2%',
+      right: '2%',
+      bottom: 10,
+      containLabel: true,
+    },
+    xAxis: [
+      {
+        type: 'value',
+        ...defaultOptions.xAxis,
+      },
+    ],
+    yAxis: [
+      {
+        type: 'category',
+        axisTick: {
+          show: false,
+        },
+        data: ['周一', '周二', '周三', '周四'],
+        ...defaultOptions.yAxis,
+      },
+    ],
+    series: [
+      {
+        name: '利润',
+        type: 'bar',
+        label: {
+          show: true,
+          position: 'inside',
+        },
+        emphasis: {
+          focus: 'series',
+        },
+        data: [200, 170, 240, 244],
+      },
+      {
+        name: '收入',
+        type: 'bar',
+        stack: '总量',
+        label: {
+          show: true,
+        },
+        emphasis: {
+          focus: 'series',
+        },
+        data: [320, 302, 341, 374],
+      },
+      {
+        name: '支出',
+        type: 'bar',
+        stack: '总量',
+        label: {
+          show: true,
+          position: 'left',
+        },
+        emphasis: {
+          focus: 'series',
+        },
+        data: [-120, -132, -101, -134],
+      },
+    ],
+  };
+};

+ 303 - 0
src/Project/pages/DataMeter/Model/DataCenter/index.tsx

@@ -0,0 +1,303 @@
+import React, { useState, useEffect, useRef } from 'react';
+import { Button, Empty, Spin } from 'antd';
+import echarts from 'echarts';
+import style from './index.less';
+import { ChartBoxTitle } from '../ChartBox';
+import {
+  defaultOptions,
+  getOptions,
+  getOptions2,
+  getOptions3,
+  getOptions4,
+  getOptions5,
+  getOptions6,
+  getOptions7,
+} from './chartConfig';
+import { useRequest } from '@umijs/max';
+import { queryConfigList } from '@/Project/services/DataMeter';
+
+const { getOptions2: queryOptions } = require('./utils');
+
+function DataCenter(props: DataMeter.IModelsProps) {
+  const [chart, setChart] = useState<echarts.ECharts>();
+  const [timer, setTimer] = useState<NodeJS.Timer>();
+  const [showTabs, setShowTabs] = useState(false);
+  const [title, setTitle] = useState('');
+  // const [pfdOptions, setPfdOptions] = useState<any>(false);
+  const [chartOptions, setChartOptions] = useState<any>(false);
+  const chartEle = useRef<HTMLDivElement | null>(null);
+  const iframeRef = useRef<HTMLIFrameElement | null>(null);
+  const signalRef = useRef<any>();
+  // const [loading, setLoading] = useState(false);
+  const { child, setActive, projectId, layout } = props;
+  const { h, w } = layout;
+  const [active, setSelfActive] = useState(
+    layout.active ||
+      child.find((item: DataMeter.ILayoutChild) => item.show)?.key,
+  );
+  const configRequest = useRequest(queryConfigList, {
+    manual: true,
+    onSuccess(data, params) {
+      // if (data.type != 'pfd') {
+      const { options } = data;
+      // setPfdOptions(false);
+      if (options.series[0].type != 'pie') {
+        // 添加默认设置
+        options.xAxis = {
+          ...defaultOptions.xAxis,
+          ...options.xAxis,
+        };
+        options.yAxis = {
+          ...defaultOptions.yAxis,
+          ...options.yAxis,
+        };
+      } else {
+        options.legend = {
+          ...defaultOptions.legend,
+          ...options.legend,
+        };
+      }
+      chart?.clear();
+      chart?.setOption(data.options);
+      // } else {
+      //   setPfdOptions(data);
+      // }
+    },
+  });
+
+  const queryOptionsRequest = useRequest(queryOptions, {
+    manual: true,
+    onSuccess(data, params) {
+      console.log('=================renderChart=======', data);
+      iframeRef.current?.contentWindow?.render(data);
+    },
+  });
+
+  // 旧版图表
+  const getOptionsForConfig = (item: DataMeter.ILayoutChild) => {
+    console.log(item);
+    clearTimeout(timer);
+    configRequest.run({
+      projectId,
+      chartId: item.key,
+    });
+
+    const t = setTimeout(() => {
+      getOptionsForConfig(item);
+    }, 1000 * 60);
+    setTimer(t);
+  };
+  // 新版图表
+  const getOptionsForConfig2 = (item: DataMeter.ILayoutChild) => {
+    setChartOptions(item);
+    // setPfdOptions(false);
+    const t = setTimeout(() => {
+      getOptionsForConfig2({ ...item });
+    }, 1000 * 60);
+    setTimer(t);
+  };
+
+  const handleClickTabs = (key: number | string) => {
+    const item = child.find((c) => c.key == key);
+    if (!item) return;
+    if (item.type != 'chartConfig') {
+      // setPfdOptions(false);
+      setChartOptions(false);
+      let option;
+      switch (Number(key) % 7) {
+        case 0:
+          option = getOptions2();
+          break;
+        case 1:
+          option = getOptions();
+          break;
+        case 2:
+          option = getOptions3();
+          break;
+        case 3:
+          option = getOptions4();
+          break;
+        case 4:
+          option = getOptions5();
+          break;
+        case 5:
+          option = getOptions6();
+          break;
+        case 6:
+          option = getOptions7();
+          break;
+        default:
+          option = getOptions();
+          break;
+      }
+      chart?.clear();
+      chart?.setOption(option);
+    } else {
+      // setLoading(true);
+      if (item.is_new) {
+        getOptionsForConfig2(item);
+      } else {
+        setChartOptions(false);
+        getOptionsForConfig(item);
+      }
+    }
+  };
+
+  const renderChart = async () => {
+    const datas = chartOptions.configs;
+    const values = chartOptions.options;
+    const formula = JSON.parse(chartOptions.formula || '[]');
+    // setLoading(true);
+    try {
+      queryOptionsRequest.run(values, datas, formula, projectId);
+      // const options = await queryOptions(values, datas, formula, projectId);
+      // iframeRef.current?.contentWindow?.render(options);
+    } catch (error) {
+      // console.log(error);
+    }
+    // setLoading(false);
+  };
+
+  useEffect(() => {
+    if (!chartEle.current) return;
+    const chart = echarts.init(chartEle.current);
+    setChart(chart);
+    const controller = new AbortController();
+    signalRef.current = controller;
+    // handleClickTabs(active)
+    // let option = getOptions({});
+    // chart.setOption(option);
+    return () => {
+      clearTimeout(timer);
+      signalRef.current.abort();
+    };
+  }, []);
+
+  useEffect(() => {
+    if (chart) {
+      chart.resize();
+    }
+  }, [h, w, showTabs]);
+
+  useEffect(() => {
+    if (!chart || child.length == 0) return;
+    let { active } = layout;
+    if (!active) active = child[0].key;
+    handleClickTabs(active);
+    let current = child.find(
+      (item: DataMeter.ILayoutChild) => item.key == active,
+    );
+    if (current) {
+      setTitle(current.title);
+    }
+  }, [layout.active, chart, child.length]);
+
+  useEffect(() => {
+    const chartWindow = iframeRef.current?.contentWindow;
+    if (!chartWindow) return;
+    if (chartWindow.render) {
+      renderChart();
+    } else {
+      chartWindow.onload = () => {
+        renderChart();
+      };
+    }
+  }, [chartOptions]);
+
+  return (
+    <div className={style.modelBox}>
+      <ChartBoxTitle
+        title={title}
+        showTabs={showTabs}
+        setShowTabs={setShowTabs}
+        width={layout.w}
+      />
+      {showTabs && (
+        <Spin spinning={configRequest.loading || queryOptionsRequest.loading}>
+          <ul className={style.tabsList}>
+            {(child || [])
+              .filter((item) => item.show)
+              .map((item) => (
+                <li
+                  key={item.key}
+                  className={`${active == item.key ? style.active : ''}`}
+                  onClick={() => {
+                    handleClickTabs(item.key);
+                    setActive(item.key);
+                    setSelfActive(item.key);
+                  }}
+                >
+                  {item.title}
+                </li>
+              ))}
+          </ul>
+        </Spin>
+      )}
+      <div
+        style={{ paddingTop: '0.1rem', overflow: 'hidden' }}
+        className={style.modelContent}
+      >
+        {/* {pfdOptions && <Pfd data={pfdOptions} />} */}
+        {chartOptions && chartOptions.template && (
+          <iframe
+            ref={iframeRef}
+            style={{ width: '100%', height: '98%', border: 'none' }}
+            srcDoc={chartOptions.template.Content}
+          />
+        )}
+        <div
+          ref={chartEle}
+          style={{
+            // display: pfdOptions || chartOptions,
+            height: '100%',
+          }}
+        />
+      </div>
+    </div>
+  );
+}
+
+export default DataCenter;
+
+// const Pfd = (props:any) => {
+//   const {
+//     data: { configs, options },
+//   } = props;
+//   const { series } = options;
+//   const getStyle = (item) => {
+//     return {
+//       top: `${item.topPercent * 100}%`,
+//       left: `${item.leftPercent * 100}%`,
+//       color: item.color,
+//     };
+//   };
+
+//   return (
+//     <div
+//       key={key}
+//       className={style.pfdBox}
+//       style={{ backgroundImage: `url(${options.fileUrl})` }}
+//     >
+//       {series.map((item, index) => (
+//         <>
+//           <div
+//             key={`name-${index}`}
+//             style={getStyle(item.name)}
+//             className={style.pfdFont}
+//           >
+//             {item.paramsName}
+//           </div>
+//           <div
+//             key={`data-${index}`}
+//             style={getStyle(item.data)}
+//             className={style.pfdFont}
+//           >
+//             {configs[index].value}
+//           </div>
+//         </>
+//       ))}
+//       {/* <img src={options.fileUrl} /> */}
+//     </div>
+//   );
+// };
+

+ 649 - 0
src/Project/pages/DataMeter/Model/DataCenter/utils.js

@@ -0,0 +1,649 @@
+import moment from 'moment';
+import { computePrefixExpression } from './compute';
+
+import {
+  getDeviceRealData,
+  getDeviceRealDataByTime,
+  getDeviceRealData2,
+  getExcelUrl,
+  queryFormCurrentData,
+  queryFormHistoryData,
+  queryFormHistoryData2,
+} from '@/services/ProjectAdmin';
+export async function getOptions(values, datas, formula, projectId) {
+  let plcDatas = [],
+    formDatas = [];
+  datas.forEach((item, index) => {
+    item.index = index;
+    if (item.data_type == 0) {
+      plcDatas.push(item);
+    } else {
+      formDatas.push(item);
+    }
+  });
+
+  var plcData = await getPlcOptions(values, plcDatas, formula, projectId);
+  var formData = await getFormOptions(values, formDatas, projectId);
+
+  if (values.timeType) {
+    // 获得时间轴全集
+    let times = getTimes(plcData[0]?.data, formData[0]?.data);
+
+    // 根据时间全集补填数据
+    formatData(plcData, times);
+    formatData(formData, times);
+  }
+  // console.log(formData, plcData);
+  return {
+    ...values,
+    // 合并data
+    data: plcData.concat(formData),
+  };
+}
+
+function getTimes(plcTimes = [], formTimes = []) {
+  let times = {};
+
+  plcTimes.forEach(item => {
+    times[item.htime] = true;
+  });
+  formTimes.forEach(item => {
+    times[item.htime] = true;
+  });
+  return Object.keys(times).sort((a, b) => new Date(a) - new Date(b));
+}
+
+// 根据时间进行格式化数据
+function formatData(datas, times) {
+  datas.forEach(item => {
+    // 默认数据
+    let newItemData = [];
+    let i = 0;
+    times.forEach(t => {
+      let curData = item.data.find(item => item.htime == t);
+      if (!curData) {
+        // 空缺时间要补0
+        newItemData.push({
+          htime: t,
+          val: 0,
+        });
+      } else {
+        newItemData.push(curData);
+      }
+    });
+    // 使用新值
+    item.data = newItemData;
+  });
+}
+
+/**
+ * 获取图表的options
+ * @param {object} values 表单数据
+ * @param {array} datas 数据项
+ * @param {array} formula 数据公式
+ * @returns
+ */
+export async function getPlcOptions(values, datas, formula, project_id) {
+  let arrtData = values.data || [];
+  let params = getSingleData(datas, project_id);
+  // let multiParams = getFormula(formula);
+  if (!values.timeType) {
+    let res = await getData(params, values);
+    // for (let i = 0; i < multiParams.length; i++) {
+    //   let device = multiParams[i].paramsDevice;
+    //   let indexArr = multiParams[i].indexArr;
+    //   let tempExpression = [...multiParams[i].expression];
+    //   let formulaRes = await getData(device, values);
+    //   formulaRes.data.map(res => {
+    //     let temp = device.find(child => child.deviceName === res.alias);
+    //     // console.log(temp);
+    //     if (temp) {
+    //       var indexObj = indexArr.find(item => item.deviceName === temp.deviceName);
+    //       if (indexObj) {
+    //         tempExpression[indexObj.index] = res.val;
+    //       }
+    //     }
+    //   });
+    //   let tempValue = computePrefixExpression([...tempExpression]);
+    //   multiParams[i].formatExpression = tempExpression;
+    //   multiParams[i].value = tempValue;
+    // }
+    let resData = res.data.map((item, index) => {
+      let attrDataItem = arrtData[index + formula.length] || {};
+      return {
+        ...attrDataItem,
+        name: item.alias,
+        value: item.val * 1,
+      };
+    });
+    // let formulaData = multiParams.map((item, index) => {
+    //   let attrDataItem = arrtData[index] || {};
+    //   return {
+    //     ...attrDataItem,
+    //     name: item.FormulaName,
+    //     value: item.value * 1,
+    //   };
+    // });
+
+    // return [...resData, ...formulaData];
+    return resData;
+  } else {
+    let singleData = [];
+    for (let i = 0; i < params.length; i++) {
+      const item = params[i];
+      let res = await getData(item, values);
+      // let attrDataItem = arrtData[i + multiParams.length] || {};
+      let attrDataItem = arrtData[i + formula.length] || {};
+      singleData.push({
+        ...attrDataItem,
+        name: item.deviceName,
+        data: (res.data || []).map(item => {
+          return {
+            val: item.val,
+            htime: moment(item.htime_at).format('YYYY-MM-DD HH:mm:ss'),
+          };
+        }),
+      });
+    }
+
+    // let firstHTimeArr = [];
+    // let formulaData = [];
+    // for (let i = 0; i < multiParams.length; i++) {
+    //   const element = multiParams[i];
+    //   let indexArr = element.indexArr;
+    //   let tempExpression = [...element.expression];
+    //   let device = element.paramsDevice;
+    //   let child = {};
+    //   child.tempExpression = tempExpression;
+    //   child.name = element.FormulaName;
+    //   child.data = [];
+    //   child.indexArr = indexArr;
+    //   for (let j = 0; j < device.length; j++) {
+    //     const tempDevice = device[j];
+    //     let res = await getData(tempDevice, values);
+    //     child.data.push({
+    //       data: res.data,
+    //       device: tempDevice,
+    //     });
+    //   }
+    //   formulaData.push(child);
+    // }
+
+    // let data = [];
+
+    // formulaData.map((item, index) => {
+    //   let attrDataItem = arrtData[index] || {};
+    //   data.push({ ...attrDataItem, ...getFunctionValue(item) });
+    // });
+
+    // return [...singleData, ...data];
+    return singleData;
+  }
+}
+// 根据数据项获取请求参数
+function getSingleData(datas, project_id) {
+  let params = [];
+  datas.forEach(({ device_id, device_items, seq }) => {
+    if (device_id && device_items) {
+      params.push({
+        deviceName: seq,
+        deviceId: device_id,
+        deviceItems: device_items,
+        project_id: Number(project_id),
+      });
+    }
+  });
+  return params;
+}
+// 根据公式获得请求参数
+function getFormula(formula) {
+  let params = [];
+  formula.forEach(item => {
+    let tempItem = item;
+    let tempDeviceArr = [];
+    let indexArr = [];
+    item.params.map(item => {
+      if (item.data_type == 1) {
+      } else if (item.Id && item.ItemAlias && item.ItemName) {
+        tempDeviceArr.push({
+          deviceName: item.ItemAlias,
+          deviceId: String(item.PlcDeviceId),
+          deviceItems: item.ItemName,
+        });
+
+        indexArr.push({ index: item.index, deviceName: item.ItemAlias });
+      }
+    });
+    tempItem.paramsDevice = tempDeviceArr;
+    tempItem.indexArr = indexArr;
+    params.push(tempItem);
+  });
+  return params;
+}
+
+function getFunctionValue(child) {
+  let data = {};
+  data.data = [];
+  let expression = child.tempExpression;
+  let first = child.data[0];
+  let indexArr = child.indexArr;
+  first.data.forEach(item => {
+    // firstDevice.device
+    let resObj = {
+      [first.device.deviceName]: item,
+    };
+    let htime = item.htime;
+    for (let i = 1; i < child.data.length; i++) {
+      let element = child.data[i];
+      // deviceName = element.name
+      let result = element.data.find(item => item.htime === htime);
+      if (!result) return;
+      resObj[element.device.deviceName] = result;
+    }
+    Object.keys(resObj).forEach(key => {
+      var result = indexArr.find(item => key === item.deviceName);
+      if (result) expression[result.index] = resObj[key].val;
+    });
+    // console.log(expression);
+    data.data.push({
+      htime: moment(htime).format('YYYY-MM-DD HH:mm:ss'),
+      val: computePrefixExpression([...expression]),
+    });
+    // console.log(indexArr, resObj, expression);
+    //  indexArr   resObj     expression
+  });
+  data.name = child.name;
+  return data;
+}
+
+var DATA_CACHE = {};
+// 请求plc数据
+async function getData(params, values) {
+  const { timeType, date, size, interval, aggregator } = values;
+  let key, etime, stime;
+  if (!timeType) {
+    // key = `${params.map(item => item.deviceItems).join(',')}-${timeType}`;
+  } else if (timeType != -1) {
+    // key = `${params.deviceItems}-${timeType}-${size}-${interval}-${aggregator}`;
+  } else {
+    let clear = { hour: 0, minute: 0, second: 0, millisecond: 0 };
+    etime = moment(date[1]).set(clear) * 1;
+    stime = moment(date[0]).set(clear) * 1;
+    // key = `${params.deviceItems}-${stime}-${etime}-${size}-${interval}-${aggregator}`;
+  }
+  // if (!DATA_CACHE[key]) {
+  if (!timeType) {
+    // DATA_CACHE[key] = await getDeviceRealData(params);
+    return await getDeviceRealData(params);
+  } else {
+    if (timeType != -1) {
+      let currentDate = moment();
+      etime = currentDate * 1;
+      stime = currentDate.add(-1 * timeType, 'hour') * 1;
+    }
+    return await getDeviceRealDataByTime({
+      deviceid: params.deviceId * 1,
+      dataitemid: params.deviceItems,
+      project_id: Number(params.project_id),
+      stime,
+      etime,
+      size,
+      interval,
+      aggregator,
+    });
+  }
+  // }
+  // return DATA_CACHE[key];
+}
+
+// 请求plc历史数据
+async function getDataHistory(params, values) {
+  const { timeType, size, interval, aggregator, date } = values;
+  let currentDate = moment();
+  let etime;
+  let stime;
+  let res = [];
+  if (timeType != -1) {
+    let currentDate = moment();
+    etime = currentDate * 1;
+    stime = currentDate.add(-1 * timeType, 'hour') * 1;
+  } else {
+    let clear = { hour: 0, minute: 0, second: 0, millisecond: 0 };
+    etime = moment(date[1]).set(clear) * 1;
+    stime = moment(date[0]).set(clear) * 1;
+  }
+  for (var i = 0; i < params.length; i++) {
+    const item = params[i];
+    const { data } = await getDeviceRealDataByTime({
+      deviceid: item.deviceId * 1,
+      dataitemid: item.deviceItems,
+      project_id: Number(item.project_id),
+      stime,
+      etime,
+      size,
+      interval,
+      aggregator,
+    });
+    res.push({
+      paramsInfo: item,
+      name: item.deviceName,
+      data: (data || [])
+        .filter(item => item.htime_at)
+        .map(item => {
+          return {
+            val: item.val * 1,
+            htime: moment(item.htime_at).format('YYYY-MM-DD HH:mm:ss'),
+          };
+        }),
+    });
+  }
+  return res;
+}
+
+// 获取表单的options
+export async function getFormOptions(values, datas, projectId) {
+  let arrtData = values.data || [];
+  const params = getFormParams(datas, projectId);
+  if (!values.timeType) {
+    // 请求最新数据
+    let data = await getFormCurrentData(params, values);
+    let resData = data.map((item, index) => {
+      let attrDataItem = arrtData[index] || {};
+      return {
+        ...attrDataItem,
+        name: item.title,
+        value: item.value * 1,
+      };
+    });
+    return resData;
+  } else {
+    // 请求历史数据
+    let data = await getFormHistoryData(params, values);
+    return data.map((item, i) => {
+      let attrDataItem = arrtData[i] || {};
+      return {
+        ...attrDataItem,
+        ...item,
+      };
+    });
+  }
+}
+
+// 根据表单的数据项获取请求参数
+function getFormParams(datas, projectId) {
+  let params = {};
+  datas.forEach(item => {
+    if (!params[item.data_name]) params[item.data_name] = [];
+    params[item.data_name].push(item.data_title);
+  });
+  return Object.keys(params).map(data_name => ({
+    formName: data_name,
+    titles: params[data_name],
+    projectId,
+  }));
+}
+
+// 请求表单最新数据
+async function getFormCurrentData(params, values) {
+  const { timeType, date } = values;
+  let data = [];
+  for (let i = 0; i < params.length; i++) {
+    const resData = await queryFormCurrentData(params[i]);
+    data = [...data, ...resData];
+  }
+  return data;
+}
+
+// 请求表单历史数据
+async function getFormHistoryData(params, values) {
+  const { timeType, date, size, interval, aggregator } = values;
+  let sTime, eTime;
+  let data = [];
+  // -1为自选日期  从date内获取时间
+  if (timeType == -1) {
+    let clear = { hour: 0, minute: 0, second: 0, millisecond: 0 };
+    eTime = moment(date[1])
+      .set(clear)
+      .format('YYYY-MM-DD HH:mm');
+    sTime = moment(date[0])
+      .set(clear)
+      .format('YYYY-MM-DD HH:mm');
+  } else {
+    let currentDate = moment();
+    eTime = currentDate.format('YYYY-MM-DD HH:mm');
+    sTime = currentDate.add(-1 * timeType, 'hour').format('YYYY-MM-DD HH:mm');
+  }
+
+  // for (let i = 0; i < params.length; i++) {
+  //   const resData = await queryFormHistoryData({ ...params[i], eTime, sTime });
+  //   data = [...data, ...resData];
+  // }
+  const item_info = params.map(item => ({
+    table: item.formName,
+    pro: item.titles.map(title => ({ key: title })),
+  }));
+  let queryParams = {
+    project_id: Number(params[0].projectId),
+    item_info,
+    stime: sTime,
+    etime: eTime,
+    interval,
+    size: Number(size),
+    aggregator,
+  };
+  const resData = await queryFormHistoryData2(queryParams);
+
+  return resData;
+}
+
+async function queryData(values, datas, projectId) {
+  let plcDatas = [],
+    formDatas = [];
+  datas.forEach(item => {
+    if (item.data_type == 0) {
+      plcDatas.push(item);
+    } else {
+      formDatas.push(item);
+    }
+  });
+
+  // 根据params获取form的数据
+  var formData = await getFormData(values, formDatas, projectId);
+  var plcData = await getPlcData(values, plcDatas, projectId);
+  if (values.timeType) {
+    // 获得时间轴全集
+    let times = getTimes(plcData[0]?.data, formData[0]?.data);
+
+    // 根据时间全集补填数据
+    formatData(plcData, times);
+    formatData(formData, times);
+  }
+  return {
+    plcData,
+    formData,
+  };
+}
+
+async function getFormData(values, datas, projectId) {
+  let arrtData = values.data || [];
+  const params = getFormParams(datas, projectId);
+  if (params.length == 0) return [];
+  if (!values.timeType) {
+    // 请求最新数据
+    return await getFormCurrentData(params, values);
+  } else {
+    // 请求历史数据
+    return await getFormHistoryData(params, values);
+  }
+}
+async function getPlcData(values, datas, project_id) {
+  let arrtData = values.data || [];
+  let params = getSingleData(datas, project_id);
+  if (!values.timeType) {
+    let res = await getData(params, values);
+    return res.data;
+  } else {
+    // let singleData = [];
+    // for (let i = 0; i < params.length; i++) {
+    //   const item = params[i];
+    //   let res = await getData(item, values);
+    //   singleData.push({
+    //     paramsInfo: item,
+    //     name: item.deviceName,
+    //     data: (res.data || [])
+    //       .filter(item => item.htime_at)
+    //       .map(item => {
+    //         return {
+    //           val: item.val * 1,
+    //           htime: moment(item.htime_at).format('YYYY-MM-DD HH:mm:ss'),
+    //         };
+    //       }),
+    //   });
+    // }
+    // return singleData;
+    return await getDataHistory(params, values);
+  }
+}
+
+export async function getOptions2(values, datas, formula, projectId) {
+  let allDatas = getAllParams(datas, formula);
+  if (allDatas.length == 0) return;
+
+  let optionsData = getOptionsData(datas, formula, values);
+
+  // 请求接口
+  const { plcData, formData } = await queryData(values, allDatas, projectId);
+
+  optionsData = optionsData.map(item => {
+    const paramsInfo = item.paramsInfo;
+    let res;
+    if (paramsInfo.data_type == 0) {
+      // plc数据
+      res = findPlcData(paramsInfo, plcData, values.timeType);
+    } else if (paramsInfo.data_type == 1) {
+      // form数据
+      res = findFormData(paramsInfo, formData, values.timeType);
+    } else if (paramsInfo.data_type == 2) {
+      // 获取公式的值
+      res = getFormulaData(item.paramsInfo, plcData, formData, values.timeType);
+    }
+    return { ...item, ...res };
+  });
+  return {
+    ...values,
+    data: optionsData,
+  };
+}
+
+function findPlcData(paramsInfo, plcData, timeType) {
+  // 实时数据与历史数据结构不一致  需判断
+  if (timeType) {
+    return plcData.find(resItem => resItem.paramsInfo.deviceName == paramsInfo.seq);
+  } else {
+    let res = plcData.find(
+      resItem =>
+        resItem.itemname == paramsInfo.device_items && resItem.devid == paramsInfo.device_id
+    );
+    return {
+      name: res.alias,
+      value: res.val,
+    };
+  }
+}
+function findFormData(paramsInfo, formData, timeType) {
+  // 实时数据与历史数据结构不一致  需判断
+  if (timeType) {
+    return formData.find(resItem => resItem.name == paramsInfo.data_title);
+  } else {
+    let res = formData.find(resItem => resItem.title == paramsInfo.data_title);
+    return {
+      name: res.title,
+      value: res.value,
+    };
+  }
+}
+
+function getAllParams(datas, formula) {
+  let allDatas = [...datas];
+  formula.forEach(f => {
+    f.params.forEach(params => {
+      if (params.data_type == 0) {
+        if (!datas.find(item => item.seq == params.seq)) {
+          allDatas.push(params);
+        }
+      } else {
+        if (!datas.find(item => item.data_title == params.data_title)) {
+          allDatas.push(params);
+        }
+      }
+    });
+  });
+  return allDatas;
+}
+
+function getOptionsData(datas, formula, values) {
+  let valuesData = values.data || [];
+
+  let optionsData = [];
+  formula.forEach((item, index) => {
+    var arrData = valuesData[index] || {};
+    item.data_type = 2;
+    arrData.paramsInfo = item;
+    optionsData.push(arrData);
+  });
+  datas.forEach((data, index) => {
+    var arrData = valuesData[index + formula.length] || {};
+    arrData.paramsInfo = data;
+    optionsData.push(arrData);
+  });
+  return optionsData;
+}
+
+function getFormulaData(formula, plcData, formData, timeType) {
+  let expression = [...formula.expression];
+  let resDatas = formula.params.map(params => {
+    let res;
+    if (params.data_type == 0) {
+      res = findPlcData(params, plcData, timeType);
+      return {
+        ...params,
+        ...res,
+      };
+    } else {
+      res = findFormData(params, formData, timeType);
+      return {
+        ...params,
+        ...res,
+      };
+    }
+  });
+
+  if (timeType) {
+    let optionsData = [];
+    // 获取时间
+    let time = resDatas[0].data.map(item => item.htime);
+    time.forEach((htime, index) => {
+      resDatas.forEach(params => {
+        // 根据index去替换表达式中对应的值
+        expression[params.index] = params.data[index].val || 0;
+      });
+      optionsData.push({
+        htime: moment(htime).format('YYYY-MM-DD HH:mm:ss'),
+        val: computePrefixExpression([...expression]),
+      });
+    });
+    return {
+      data: optionsData,
+      name: formula.FormulaName,
+    };
+  } else {
+    resDatas.forEach(params => {
+      // 根据index去替换表达式中对应的值
+      expression[params.index] = params.value || 0;
+    });
+    return {
+      value: computePrefixExpression([...expression]),
+      name: formula.FormulaName,
+    };
+  }
+}

+ 1 - 9
src/Project/pages/DataMeter/Model/Map.tsx

@@ -54,15 +54,7 @@ function Map(props: DataMeter.IModelsProps) {
   //     UnityAction.emit('mouseHandle', 0);
   //   },
   // };
-  return (
-    <div
-      // {...mouseEvent}
-      key={layout.i}
-      // className={`${edit || type != 1 ? style.modelContent : ''}`}
-    >
-      {content}
-    </div>
-  );
+  return content;
 }
 
 function NoModelMap(props: DataMeter.IModelsProps) {

+ 232 - 0
src/Project/pages/DataMeter/Model/MessageCenter.tsx

@@ -0,0 +1,232 @@
+import React, { useState, useEffect, useRef } from 'react';
+import { Radio, Empty } from 'antd';
+import style from '../index.less';
+import moment from 'moment';
+import { ChartBoxTitle } from './ChartBox';
+import { useRequest } from '@umijs/max';
+import {
+  getNotificationList,
+  getProjectActive,
+} from '@/Project/services/DataMeter';
+let LoopTime: NodeJS.Timer;
+const normalTimer = 10000;
+
+function MessageCenter(props: DataMeter.IModelsProps) {
+  const {
+    child,
+
+    setActive,
+    layout,
+    projectId,
+    // news,
+    // memorabilia,
+    // awards,
+    // notificationList,
+  } = props;
+  const [active, setSelfActive] = useState(
+    layout.active || child.find((item) => item.show)?.key,
+  );
+  const [showTabs, setShowTabs] = useState(false);
+  const [isPlay, setIsPlay] = useState(false);
+  const [currentIndex, setCurrentIndex] = useState(0);
+  const [title, setTitle] = useState('');
+  const ulRef = useRef<HTMLUListElement | null>(null);
+  const id = projectId || -1;
+
+  const notificationRequest = useRequest(getNotificationList, {
+    defaultParams: [
+      {
+        projectId: id,
+        msgType: 6,
+      },
+    ],
+  });
+  const newsRequest = useRequest(getProjectActive, {
+    defaultParams: [
+      {
+        projectId: id,
+        type: 1,
+      },
+    ],
+  });
+  const memorabiliaRequest = useRequest(getProjectActive, {
+    defaultParams: [
+      {
+        projectId: id,
+        type: 2,
+      },
+    ],
+  });
+  const awardsRequest = useRequest(getProjectActive, {
+    defaultParams: [
+      {
+        projectId: id,
+        type: 3,
+      },
+    ],
+  });
+  const news: any = notificationRequest.data?.list || [];
+  const memorabilia: any = newsRequest.data?.list || [];
+  const awards: any = memorabiliaRequest.data?.list || [];
+  const notificationList: any = awardsRequest.data?.list || [];
+
+  const StartTimeout = (time: number) => {
+    LoopTime = setInterval(() => {
+      if (news.length > 0) {
+        let scroll: any = document.getElementById('scroll')!;
+        if (!scroll) return;
+        if (scroll.scrollTop + scroll.clientHeight >= scroll.scrollHeight)
+          scroll.scrollTop = 0;
+        else scroll.scrollTop += ulRef.current?.children[0].clientHeight;
+      }
+    }, time);
+  };
+
+  const Loop = (key: string | number) => {
+    if (key == 2) {
+      setIsPlay(true);
+      document.getElementById('scroll')!.scrollTop = 0;
+      StartTimeout(normalTimer);
+    } else {
+      setIsPlay(false);
+      setCurrentIndex(0);
+      clearInterval(LoopTime);
+    }
+  };
+  
+  useEffect(() => {
+    let current = child.find((item) => item.key == active);
+    if (current) {
+      setTitle(current.title);
+    }
+    if (active) {
+      Loop(active);
+    }
+  }, [active]);
+  useEffect(() => {
+    if (isPlay) StartTimeout(normalTimer);
+    return () => {
+      clearInterval(LoopTime);
+    };
+  }, [currentIndex, news]);
+
+  var content;
+
+  switch (active) {
+    case 1:
+      if (notificationList.length > 0) {
+        content = (
+          <ul className={style.messageList}>
+            {notificationList.map((item: any) => (
+              <li key={item.ID}>
+                <div className={style.messageTitle}>
+                  {moment(item.CreatedOn).format('YYYY-MM-DD')}
+                </div>
+                <div className={style.messageContent}>{item.MsgBody}</div>
+              </li>
+            ))}
+          </ul>
+        );
+      } else {
+        content = <Empty />;
+      }
+      break;
+    case 2:
+      if (news.length > 0) {
+        content = (
+          <ul ref={ulRef} className={style.messageList}>
+            {news.map((item: any) => (
+              <li key={item.id}>
+                <div className={style.messageTitle}>
+                  {moment(item.c_time).format('YYYY-MM-DD')}
+                </div>
+                <div className={style.messageContent}>
+                  <a
+                    style={{ color: 'white' }}
+                    // onClick={() => {
+                    //   sendNewDetailToUnity(item.detail);
+                    // }}
+                  >
+                    {item.content}
+                  </a>
+                </div>
+              </li>
+            ))}
+          </ul>
+        );
+      } else {
+        content = <Empty />;
+      }
+      break;
+    case 3:
+      if (memorabilia.length > 0) {
+        content = (
+          <ul className={style.messageList}>
+            {memorabilia.map((item: any) => (
+              <li key={item.id}>
+                <div className={style.messageTitle}>
+                  {moment(item.c_time).format('YYYY-MM-DD')}
+                </div>
+                <div className={style.messageContent}>{item.content}</div>
+              </li>
+            ))}
+          </ul>
+        );
+      } else {
+        content = <Empty />;
+      }
+      break;
+    case 4:
+      if (awards.length > 0) {
+        content = (
+          <ul className={style.messageList}>
+            {awards.map((item: any) => (
+              <li key={item.id}>
+                <div className={style.messageTitle}>
+                  {moment(item.c_time).format('YYYY-MM-DD')}
+                </div>
+                <div className={style.messageContent}>{item.content}</div>
+              </li>
+            ))}
+          </ul>
+        );
+      } else {
+        content = <Empty />;
+      }
+      break;
+  }
+  return (
+    <div className={style.modelBox}>
+      
+      <ChartBoxTitle
+        title={title == '全部' ? '报警中心' : title}
+        showTabs={showTabs}
+        setShowTabs={setShowTabs}
+        width={layout.w}
+      />
+      {showTabs && (
+        <ul className={style.tabsList}>
+          {(child || [])
+            .filter((item) => item.show)
+            .map((item) => (
+              <li
+                key={item.key}
+                className={`${active == item.key ? style.active : ''}`}
+                onClick={() => {
+                  setActive(item.key);
+                  setSelfActive(item.key);
+                }}
+              >
+                {item.title}
+              </li>
+            ))}
+        </ul>
+      )}
+      <div id="scroll" className={style.modelContent}>
+        {content}
+      </div>
+    </div>
+  );
+}
+
+export default MessageCenter;

+ 1 - 1
src/Project/pages/DataMeter/Model/Monitor.tsx

@@ -2,7 +2,7 @@ import React from 'react';
 import { Empty, Tooltip } from 'antd';
 import { FullscreenOutlined } from '@ant-design/icons';
 import flvjs from 'flv.js';
-import style from './index.less';
+import style from '../index.less';
 import { ChartBoxTitle } from './ChartBox';
 import { getMonitorList } from '@/Project/services/DataMeter';
 import { useRequest } from '@umijs/max';

+ 11 - 7
src/Project/pages/DataMeter/Model/ProjectInfo.tsx

@@ -1,12 +1,16 @@
 import React, { useMemo } from 'react';
-import styles from './index.less';
+import styles from '../index.less';
 import moment from 'moment';
 import { ChartBoxTitle } from './ChartBox';
-import { useModel } from '@umijs/max';
+import { useModel, useRequest } from '@umijs/max';
+import { getProject } from '@/Project/services/DataMeter';
 
 function ProjectInfo(props: DataMeter.IModelsProps) {
-  const { child, layout } = props;
-  const { project: projectDetail } = useModel('project');
+  const { child, layout, projectId } = props;
+  // const { project: projectDetail } = useModel('project');
+  const { data: projectDetail } = useRequest(getProject, {
+    defaultParams: [projectId],
+  });
   const getMainLeader = () => {
     let user = [];
     if (!projectDetail) return;
@@ -29,7 +33,7 @@ function ProjectInfo(props: DataMeter.IModelsProps) {
   };
   const getUser = () => {
     if (!projectDetail || !projectDetail.User) return;
-    let user = (projectDetail.User || []).map((item) => item.CName);
+    let user = (projectDetail.User || []).map((item: Api.IUser) => item.CName);
     user = [...new Set(user)];
     return user.join(',');
   };
@@ -108,7 +112,7 @@ function ProjectInfo(props: DataMeter.IModelsProps) {
     });
     for (let i = 0; i < halfItem.length; i += 2) {
       dom.push(
-        <div className={styles.detailRow}>
+        <div key={dom.length + 1} className={styles.detailRow}>
           {halfItem[i]}
           {halfItem[i + 1] || null}
         </div>,
@@ -118,8 +122,8 @@ function ProjectInfo(props: DataMeter.IModelsProps) {
 
     return dom;
   }, [child, projectDetail]);
+  if (!projectDetail) return <div>loading projectDetail....</div>;
 
-  if (!projectDetail) return <div>loading....</div>;
   return (
     <div className={styles.modelBox}>
       <ChartBoxTitle title={'项目概况'} width={layout.w} />

+ 0 - 28
src/Project/pages/DataMeter/Model/index.less

@@ -1,28 +0,0 @@
-.controlBox {
-  position: absolute;
-  top: 0.1rem;
-  right: 0.3rem;
-  display: flex;
-}
-
-.edit {
-  width: 0.16rem;
-  height: 0.16rem;
-  // font-size: 0.2rem;
-  background: url('@/Project/assets/dataMeter/icon-edit.png') no-repeat center;
-  background-size: 100% 100%;
-  cursor: pointer;
-  margin-right: 0.1rem;
-  cursor: pointer;
-}
-
-.close {
-  width: 0.16rem;
-  height: 0.16rem;
-  // font-size: 0.2rem;
-  background: url('@/Project/assets/dataMeter/icon-close.png') no-repeat center;
-  background-size: 100% 100%;
-  cursor: pointer;
-  margin-right: 0.1rem;
-  cursor: pointer;
-}

+ 9 - 28
src/Project/pages/DataMeter/Model/index.tsx

@@ -3,8 +3,9 @@ import { Tooltip } from 'antd';
 import ProjectInfo from './ProjectInfo';
 import Map from './Map';
 import Monitor from './Monitor';
-import style from "./index.less"
-
+import style from '../index.less';
+import AlarmCenter from './AlarmCenter';
+import MessageCenter from './MessageCenter';
 
 function Model(props: DataMeter.IModelsProps) {
   const { layout, edit, removeModel } = props;
@@ -19,19 +20,8 @@ function Model(props: DataMeter.IModelsProps) {
       return <Map {...props} />;
     case 'Monitor':
       return <Monitor {...props} />;
-    // case 'AlarmCenter': //报警中心
-    //   content = (
-    //     <AlarmCenter
-    //       key={layout.i}
-    //       projectId={projectId}
-    //       {...props}
-    //       child={layout.child}
-    //       layout={layout}
-    //       setActive={setActive}
-    //       subModule={subModule}
-    //     />
-    //   );
-    //   break;
+    case 'AlarmCenter': //报警中心
+      return <AlarmCenter {...props} />;
     // case 'FileManagement':
     //   content = (
     //     <FileManagement
@@ -77,18 +67,8 @@ function Model(props: DataMeter.IModelsProps) {
     //     />
     //   );
     //   break;
-    // case 'MessageCenter': //新闻动态
-    //   content = (
-    //     <MessageCenter
-    //       key={layout.i}
-    //       projectId={projectId}
-    //       {...props}
-    //       child={layout.child}
-    //       layout={layout}
-    //       setActive={setActive}
-    //     />
-    //   );
-    //   break;
+    case 'MessageCenter': //新闻动态
+      return <MessageCenter {...props} />;
     // case 'Other':
     //   content = (
     //     <Other
@@ -118,8 +98,9 @@ function Model(props: DataMeter.IModelsProps) {
       content = <div>loading...</div>;
       break;
   }
+
   return (
-    <div key={layout.i}>
+    <div className={style.item} key={layout.i}>
       {content}
       <Control {...props} />
     </div>

+ 54 - 55
src/Project/pages/DataMeter/index.less

@@ -41,37 +41,37 @@
 
   .controlBox {
     position: absolute;
-    top: 0.1rem;
-    right: 0.3rem;
+    top: 10px;
+    right: 30px;
     display: flex;
   }
 
   .edit {
-    width: 0.16rem;
-    height: 0.16rem;
-    // font-size: 0.2rem;
+    width: 16px;
+    height: 16px;
+    // font-size: 0.2px;
     background: url('@/Project/assets/dataMeter/icon-edit.png') no-repeat center;
     background-size: 100% 100%;
     cursor: pointer;
-    margin-right: 0.1rem;
+    margin-right: 10px;
     cursor: pointer;
   }
 
   .close {
-    width: 0.16rem;
-    height: 0.16rem;
-    // font-size: 0.2rem;
+    width: 16px;
+    height: 16px;
+    // font-size: 0.2px;
     background: url('@/Project/assets/dataMeter/icon-close.png') no-repeat center;
     background-size: 100% 100%;
     cursor: pointer;
-    margin-right: 0.1rem;
+    margin-right: 0.1px;
     cursor: pointer;
   }
 }
 
 .addMask {
-  width: 3rem;
-  height: 3rem;
+  width: 300px;
+  height: 300px;
   display: flex;
   flex-direction: column;
   justify-content: center;
@@ -83,7 +83,7 @@
   position: fixed;
   background: #000;
   z-index: 10001;
-  font-size: 0.16rem;
+  font-size: 16px;
 
   &.active {
     color: #000;
@@ -138,14 +138,14 @@
     display: flex;
     color: #fff;
     margin-bottom: 0;
-    margin-right: 0.1rem;
+    margin-right: 10px;
 
     li {
-      padding: 0.04rem 0;
-      margin-right: 0.26rem;
+      padding: 4px 0;
+      margin-right: 26px;
       cursor: pointer;
       border-bottom: 1px solid transparent;
-      font-size: 0.12rem;
+      font-size: 12px;
 
       &.active {
         border-bottom: 1px solid #fff;
@@ -157,8 +157,8 @@
     background: url('@/Project/assets/dataMeter/icon-upload.png') no-repeat center;
     background-size: 100% 100%;
     cursor: pointer;
-    width: 0.16rem;
-    height: 0.16rem;
+    width: 16px;
+    height: 16px;
   }
 }
 
@@ -177,7 +177,7 @@
   align-items: flex-start;
   height: 100%;
   line-height: 1;
-  font-size: 0.14rem;
+  font-size: 14px;
   // background: url('@/Project/assets/chartBox/bg.png') no-repeat center;
   // background-size: 100% 100%;
   // border: #2866B2 solid 1px;
@@ -189,7 +189,7 @@
 
   .tabBox {
     width: 100%;
-    padding: 0.16rem;
+    padding: 16px;
     padding-bottom: 0;
   }
 
@@ -213,8 +213,8 @@
       .ant-radio-button-wrapper {
         border: none !important;
         background: transparent;
-        font-size: 0.14rem;
-        height: 0.32rem;
+        font-size: 14px;
+        height: 32px;
         color: rgba(255, 255, 255, 0.6);
 
         &::before {
@@ -299,7 +299,7 @@
     align-items: flex-start;
     align-content: flex-start;
     flex-wrap: wrap;
-    padding-top: 0.1rem;
+    padding-top: 1px;
 
     .detailItem {
       width: 100%;
@@ -347,26 +347,25 @@
   .alarmTable {
     width: 100%;
     table-layout: fixed;
-    padding-left: 0.14rem;
+    padding-left: 14px;
 
     // background: linear-gradient(270deg, rgba(153, 231, 255, 0.8) 0%, #60A8FF 100%);
     tr {
 
       td,
       th {
-        padding: 0.04rem 0.02rem;
-        font-size: 0.14rem;
+        padding: 4px 2px;
+        font-size: 14px;
         font-weight: 400;
         color: #183266;
       }
 
       td {
         color: #D7E4E8;
-        ;
         vertical-align: top;
         line-height: 1.5;
         font-weight: 400;
-        font-size: 0.14rem;
+        font-size: 14px;
       }
     }
 
@@ -379,18 +378,18 @@
 
   // 消息中心
   .messageList {
-    padding: 0.16rem;
+    padding: 16px;
     padding-top: 0;
     padding-bottom: 0;
     margin: 0;
 
     li {
-      margin-bottom: 0.06rem;
+      margin-bottom: 6px;
     }
 
     .messageTitle {
-      font-size: 0.12rem;
-      margin-bottom: 0.02rem;
+      font-size: 12px;
+      margin-bottom: 2px;
       color: #13EDF5;
     }
 
@@ -406,15 +405,15 @@
     flex-wrap: wrap;
     margin: 0;
     width: -webkit-fill-available;
-    margin: 0.1rem 0.16rem 0 0.16rem;
+    margin: 10px 16px 0 16px;
     border-bottom: 0.25px solid rgba(216, 216, 216, 0.34);
     padding: inherit;
-    padding: 0 0 0.06rem 0;
+    padding: 0 0 6px 0;
 
     li {
       color: #D7E4E8;
       font-weight: 300;
-      margin-right: 0.1rem;
+      margin-right: 10px;
       cursor: pointer;
       margin-bottom: 2px;
 
@@ -426,7 +425,7 @@
   }
 
   .activeSelect {
-    margin-left: 0.16rem;
+    margin-left:16px;
     margin-top: 10px;
     width: 149px;
     height: 24px;
@@ -439,11 +438,11 @@
     width: 100%;
     // height: 100%;
     table-layout: fixed;
-    padding-left: 0.14rem;
+    padding-left: 14px;
 
     .messageTitle {
-      font-size: 0.12rem;
-      margin-bottom: 0.02rem;
+      font-size: 12px;
+      margin-bottom: 2px;
       color: rgba(255, 255, 255, 0.6);
     }
 
@@ -452,17 +451,17 @@
         color: #fff;
         vertical-align: top;
         line-height: 1.5;
-        font-size: 0.14rem;
+        font-size: 14px;
         word-break: break-all;
-        padding-right: 0.1rem;
+        padding-right: 1px;
       }
     }
 
     tr {
       th {
         color: #dd7777;
-        padding: 0.04rem 0.02rem;
-        font-size: 0.16rem;
+        padding: 4px 2px;
+        font-size: 16px;
       }
     }
 
@@ -504,8 +503,8 @@
 .projectList {
   position: absolute;
   min-width: 100px;
-  top: 0.6rem;
-  right: 0.35rem;
+  top: 60px;
+  right: 35px;
   background: #121921;
   margin: 0;
   padding: 0;
@@ -532,7 +531,7 @@
 .mapWapper {
   height: 100%;
   position: relative;
-  padding-top: 0.14rem;
+  padding-top: 14px;
 }
 
 .mapBox {
@@ -541,8 +540,8 @@
 
   .mapIcon {
     position: absolute;
-    width: 0.12rem;
-    height: 0.12rem;
+    width: 12px;
+    height: 12px;
     background-position: center;
     background-repeat: no-repeat;
     background-size: 100% 100%;
@@ -686,7 +685,7 @@
       margin: 0;
       color: #fff;
       font-weight: 500;
-      font-size: 0.2rem;
+      font-size: 20px;
       line-height: 1;
       font-family: 'dataMeter';
     }
@@ -1026,17 +1025,17 @@
     .ant-list,
     .ant-collapse>.ant-collapse-item>.ant-collapse-header {
       color: #fff;
-      font-size: 0.18rem;
+      font-size: 18px;
     }
 
     .ant-collapse-content {
       color: #fff;
-      font-size: 0.16rem;
+      font-size: 16px;
     }
 
     .ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow {
-      font-size: 0.14rem;
-      left: -0.1rem;
+      font-size: 14px;
+      left: -10px;
     }
 
     .ant-collapse-borderless {
@@ -1048,7 +1047,7 @@
     }
 
     .dataMeter .ant-collapse>.ant-collapse-item>.ant-collapse-header {
-      padding-left: 0.08rem;
+      padding-left: 8px;
     }
 
     .ant-collapse-borderless>.ant-collapse-item>.ant-collapse-content>.ant-collapse-content-box {

+ 29 - 52
src/Project/pages/DataMeter/index.tsx

@@ -22,11 +22,13 @@ import {
 const gridWidth = document.documentElement.clientWidth;
 
 function DataMeter(props: DataMeter.IProps) {
+  // const data = useMo
   const {
     initialState: { projectId },
   } = useModel('@@initialState');
 
-  const { hasModel, subModule, isNew } = props;
+  const { hasModel, isNew, params } = props;
+  const { subModule } = params;
   // 编辑模块
   const [editVisible, setEditVisible] = useState(false);
   // 子模块列表
@@ -54,11 +56,11 @@ function DataMeter(props: DataMeter.IProps) {
   //创建模板弹窗状态
   const [createMouldVisible, setCreateMouldVisible] = useState(false);
 
-  const layoutRequest = useRequest(getLayoutOptions, {
-    defaultParams: [
-      {
-        projectId: projectId || 0,
-        module: 1,
+    const layoutRequest = useRequest(getLayoutOptions, {
+      defaultParams: [
+        {
+          projectId: projectId || 0,
+          module: 1,
         sub_module: subModule,
         is_default: 0,
       },
@@ -151,28 +153,29 @@ function DataMeter(props: DataMeter.IProps) {
     return layout.map((item) => {
       const setActive = (key: number | string) => {
         item.active = key;
-        saveLayout(layout);
+        // saveLayout(layout);
+        setLayout([...layout]);
       };
       return (
-        <Model
-          key={item.i}
-          hasModel={hasModel}
-          child={item.child || []}
-          projectId={projectId}
-          removeModel={removeModel}
-          edit={edit}
-          layout={item}
-          setActive={setActive}
-          subModule={subModule}
-          isNew={isNew}
-          showEditModel={showEditModel}
-        />
+        <div key={item.i}>
+          <Model
+            key={item.i}
+            hasModel={hasModel}
+            child={item.child || []}
+            projectId={projectId}
+            removeModel={removeModel}
+            edit={edit}
+            layout={item}
+            setActive={setActive}
+            subModule={subModule}
+            isNew={isNew}
+            showEditModel={showEditModel}
+          />
+        </div>
       );
     });
   }, [layout]);
 
-  console.log(layoutRequest);
-
   return (
     <div
       className={`dataMeter ${style.dataMeter} ${!edit ? style.noScroll : ''}`}
@@ -222,42 +225,16 @@ function DataMeter(props: DataMeter.IProps) {
       <div className={style.gridBox}>
         <GridLayout
           className="layout"
-          isDraggable={edit}
-          isResizable={edit}
-          // layout={layout}
-          layout={[
-            {
-              i: '1',
-              w: 1,
-              h: 2,
-              x: 0,
-              y: 0,
-            },
-            {
-              i: '2',
-              w: 1,
-              h: 2,
-              x: 0,
-              y: 0,
-            },
-            {
-              i: '3',
-              w: 1,
-              h: 2,
-              x: 0,
-              y: 0,
-            },
-          ]}
+          // isDraggable={edit}
+          // isResizable={edit}
+          layout={layout}
           onLayoutChange={onLayoutChange}
           cols={COL_COLS}
           rowHeight={rowHeight}
           width={gridWidth}
           margin={[15, 10]}
         >
-          {/* {models} */}
-          <div>1</div>
-          <div>2</div>
-          <div>3</div>
+          {models}
         </GridLayout>
       </div>
     </div>

+ 3 - 1
src/Project/pages/DataMeter/typings.d.ts

@@ -16,14 +16,16 @@ declare namespace DataMeter {
     title: string;
     key: number | string;
     show: boolean;
+    type?: string; // 图表中心特有属性
+    is_new?: string; // 图表中心特有属性
     children?: ILayoutChild[];
   }
   interface IProps {
-    subModule: number;
     onClickProject: number;
     switchModule: number;
     isNew: boolean;
     hasModel: boolean;
+    params: any;
   }
   interface IModelsProps {
     key: number | string;

+ 147 - 0
src/Project/pages/LimitedSpace/Data.js

@@ -0,0 +1,147 @@
+//空间监测
+
+import React, { useEffect } from 'react';
+import { connect } from 'dva';
+import { Table, Form, Input, Button, AutoComplete } from 'antd';
+import styles from './LimitedSpace.less';
+
+function Data(props) {
+  const { dispatch, currentUser, loading, form, dataConfig, data, pagination, spaceList } = props;
+  const { projectId, spaceId } = props.match.params;
+  const { getFieldDecorator, getFieldsValue } = form;
+  let timer = null;
+
+  const onSearch = () => {
+    const { name } = getFieldsValue();
+    dispatch({
+      type: 'limitedSpace/queryData',
+      payload: { project_id: projectId, name: name },
+    });
+  };
+
+  useEffect(() => {
+    dispatch({
+      type: 'limitedSpace/querySpace',
+      payload: { project_id: projectId },
+    });
+
+    dispatch({
+      type: 'limitedSpace/queryDataConfig',
+      payload: { project_id: projectId },
+    });
+
+    dispatch({
+      type: 'limitedSpace/queryData',
+      payload: { project_id: projectId },
+    });
+
+    timer = setInterval(() => {
+      onSearch();
+    }, 20 * 1000);
+
+    return () => {
+      clearInterval(timer);
+    };
+  }, []);
+
+  const getColorAndStrByStatus = status => {
+    let str = '安全';
+    let color = 'white';
+    switch (status) {
+      case 0:
+        str = '-';
+        color = 'white';
+        break;
+      case 1:
+        str = '安全';
+        color = 'white';
+        break;
+      case 2:
+        str = '预警';
+        color = 'yellow';
+        break;
+      case 3:
+        str = '报警';
+        color = 'red';
+        break;
+    }
+    return { str, color };
+  };
+  const columns = [
+    // { title: '序号', dataIndex: 'spaceId' },
+    { title: '空间名称', dataIndex: 'name' },
+    {
+      title: '风险提示',
+      render: item => {
+        //风险提示按改行中最高风险状态显示
+        const maxStatus = Math.max(...Object.values(item.statusObj));
+        const { str, color } = getColorAndStrByStatus(maxStatus);
+        return <div style={{ color: color }}>{str}</div>;
+      },
+    },
+    ...dataConfig.map(config => ({
+      title: (
+        <div>
+          {config.item_alias}
+          <br />({config.item_unit})
+        </div>
+      ),
+      // dataIndex: item.item_name,
+      render: item => {
+        const status = item.statusObj[config.item_name];
+        const { color } = getColorAndStrByStatus(status);
+        return <div style={{ color: color }}>{item[config.item_name]}</div>;
+      },
+    })),
+  ];
+
+  const nameFilter = (value, option) => {
+    return option.key.indexOf(value) != -1;
+  };
+
+  return (
+    <div className={styles.page}>
+      <Form
+        layout="inline"
+        labelAlign="left"
+        labelCol={{ span: 8 }}
+        wrapperCol={{ span: 15 }}
+        style={{ marginBottom: 20 }}
+      >
+        <Form.Item className={styles.searchItem} label="空间名称:">
+          {getFieldDecorator('name')(
+            <AutoComplete
+              style={{ display: 'block' }}
+              dataSource={spaceList.map(item => item.name)}
+              filterOption={(value, option) => nameFilter(value, option)}
+              children={<Input style={{ borderRadius: '8px' }} />}
+            />
+          )}
+        </Form.Item>
+        <Form.Item className="btn-item">
+          <Button onClick={() => onSearch()} type="primary">
+            搜索
+          </Button>
+        </Form.Item>
+      </Form>
+      <Table
+        // scroll={{ x: 1500, y: 'false' }}
+        columns={columns}
+        rowKey="id"
+        loading={loading}
+        // dataSource={temp}
+        dataSource={data}
+        pagination={pagination}
+      />
+    </div>
+  );
+}
+
+export default connect(({ limitedSpace, user, loading }) => ({
+  currentUser: user.currentUser,
+  loading: loading.models.limitedSpace,
+  dataConfig: limitedSpace.dataConfig,
+  data: limitedSpace.spaceData.list,
+  pagination: limitedSpace.spaceData.pagination,
+  spaceList: limitedSpace.space.list,
+}))(Form.create()(Data));

+ 295 - 0
src/Project/pages/LimitedSpace/Detail.js

@@ -0,0 +1,295 @@
+//空间详情
+import React, { useEffect, useMemo, useRef, useState } from 'react';
+import { connect } from 'dva';
+import { Table, Button, Descriptions, Carousel, Icon, Spin, List } from 'antd';
+import router from 'umi/router';
+import styles from './LimitedSpace.less';
+import ReactZmage from 'react-zmage';
+import { UnityAction } from '@/utils/utils';
+let tempType = localStorage.tempType;
+let timer = null;
+
+function Detail(props) {
+  const { dispatch, currentUser, loading, config, spaceList, spaceData, dataConfig } = props;
+  const { projectId, spaceId } = props.match.params;
+  const { group_id } = props.location.query;
+  const carouselRef = useRef();
+  const [space, setSpace] = useState(spaceList.find(item => item.id == spaceId));
+  const [tempType, setTempType] = useState(localStorage.tempType || 1);
+
+  //图片列表
+  const { picList, imageList } = useMemo(() => {
+    let picList = [];
+    let imageList = [];
+    config.map(item => {
+      if (item.type == 'file') {
+        let i = item.en_name.indexOf('_pic');
+        picList.push({ en_name: item.en_name.slice(0, i) });
+      }
+    });
+    picList = picList.map(item => {
+      let pic = JSON.parse(space[item.en_name + '_pic']);
+      imageList = imageList.concat(pic);
+      return { ...item, pic };
+    });
+    return { picList, imageList };
+  }, [config, space]);
+
+  const onBack = () => {
+    router.go(-1);
+    if (!group_id) UnityAction.sendMsg('BackToOverallFromWeb');
+  };
+
+  const getTempImg = () => {
+    if (tempType == 1) {
+      return `url(${require('@/assets/LimitedSpace/圆柱形2.gif')})`;
+    } else if (tempType == 2) {
+      return `url(${require('@/assets/LimitedSpace/池子形2.gif')})`;
+    } else {
+      return `url(${require('@/assets/LimitedSpace/方井形2.gif')})`;
+    }
+  };
+
+  useEffect(() => {
+    dispatch({
+      type: 'limitedSpace/queryDataConfig',
+      payload: { project_id: projectId },
+    });
+
+    //在unity中点击点位修改页面数据
+    UnityAction.addEventListener('SelectSpaceFromUnity', e => {
+      setSpace(spaceList.find(item => item.id == e));
+    });
+
+    // 使用tempType切换类型,展示所有GIF
+    let type = tempType;
+    if (tempType == 3) type = 1;
+    else type++;
+    localStorage.tempType = type;
+
+    return () => {
+      UnityAction.off('SelectSpaceFromUnity');
+    };
+  }, []);
+
+  //随空间变动调用空间监测接口
+  useEffect(() => {
+    clearInterval(timer);
+    timer = setInterval(() => {
+      dispatch({
+        type: 'limitedSpace/queryData',
+        payload: { project_id: projectId, id: space.id },
+      });
+    }, 20 * 1000);
+
+    dispatch({
+      type: 'limitedSpace/queryData',
+      payload: { project_id: projectId, id: space.id },
+    });
+    return () => {
+      clearInterval(timer);
+    };
+  }, [space]);
+
+  const getColorAndStrByStatus = status => {
+    let str = '安全';
+    let color = 'white';
+    switch (status) {
+      case 0:
+        str = '-';
+        color = 'white';
+        break;
+      case 1:
+        str = '安全';
+        color = 'white';
+        break;
+      case 2:
+        str = '预警';
+        color = 'yellow';
+        break;
+      case 3:
+        str = '报警';
+        color = 'red';
+        break;
+    }
+    return { str, color };
+  };
+
+  //空间监测数据
+  const tableData = useMemo(() => {
+    let temp = spaceData.list[0];
+    if (!temp) return [];
+    return dataConfig.map(item => {
+      const status = temp.statusObj[item.item_name];
+      const { color } = getColorAndStrByStatus(status);
+      return {
+        text: `${item.item_alias}(${item.item_unit})`,
+        value: temp[item.item_name],
+        color,
+      };
+    });
+  }, [dataConfig, spaceData]);
+
+  //空间监测总提示
+  const threshold = useMemo(() => {
+    let temp = spaceData.list[0];
+    if (!temp) return [];
+    const maxStatus = Math.max(...Object.values(temp.statusObj));
+    return getColorAndStrByStatus(maxStatus);
+  }, [spaceData]);
+
+  const goToPic = name => {
+    let p = picList.find(item => item.en_name == name);
+    carouselRef.current.goTo(imageList.findIndex(item => item == p.pic[0]));
+  };
+
+  return (
+    <div className={styles.page}>
+      {/* <div style={{height:'50vh', overflow:'auto'}}>
+        {config.map(item => {
+          if (item.type != 'file') {
+            let p = picList.find(p => p.en_name == item.en_name);
+            if (p && p.pic.length != 0) {
+              return (
+                <List.Item className={styles.listItem} style={{background: 'linear-gradient(to right, rgba(153, 231, 255, 0.1), rgba(96, 168, 255, 0.1))'}} >
+                    {item.cn_name}:
+                    {space[item.en_name]}
+                    <a onClick={() => goToPic(item.en_name)}> 查看图片</a>
+                </List.Item>
+              );
+            } else {
+              return (
+                <List.Item className={styles.listItem} style={{background: 'linear-gradient(to right, rgba(153, 231, 255, 0.1), rgba(96, 168, 255, 0.1))'}} >{item.cn_name}:{space[item.en_name]}</List.Item>
+              );
+            }
+          }
+          return <></>
+        })}
+      </div>
+      <div style={{ display: 'flex', margin: '10px 0 23px', justifyContent:'space-around'}}>
+        <div className={styles.detailBlock}>
+          {imageList.length == 0 ? (
+            <div className={styles.carousel}>暂无图片</div>
+          ) : (
+            <>
+              <div
+                className={styles.prev}
+                onClick={() => carouselRef.current.prev()}
+              />
+              <div
+                className={styles.next}
+                onClick={() => carouselRef.current.next()}
+              />
+              <Carousel className={styles.carousel} ref={carouselRef}>
+                {imageList.map(item => (
+                  <ReactZmage src={item} />
+                ))}
+              </Carousel>
+            </>
+          )}
+        </div>
+        <div
+          className={styles.detailBlock}
+          style={{
+            // backgroundImage: getTempImg(),
+            width:'30vw',
+            backgroundImage: `url(${space.gif_url})`,
+            backgroundSize: '30vw, 30vw',
+            backgroundRepeat: 'no-repeat',
+          }}
+        />
+      </div>
+      <div className={styles.message}>空间监测信息</div>
+      <div className={styles.messageText}>
+        <div className={styles.DataRowItem}>
+          <span className={styles.DataItemLeft}>风险提示:</span>
+          <span style={{ color: '#5ae416' }}>正常</span>
+        </div>
+        {tableData.map(item => (
+          <div className={styles.DataRowItem}>
+            <span className={styles.DataItemLeft}>{item.text}:{item.value}</span>
+          </div>
+        ))}
+      </div>
+      <div style={{width:'100%', marginTop:'12px', borderBottom:'1px solid #2866B2'}}></div> */}
+
+      <Button className={styles.back} type="primary" onClick={() => onBack()}>
+        返回
+      </Button>
+      <div className={styles.detail}>
+        <div className={styles.detailBlock}>
+          {imageList.length == 0 ? (
+            <div className={styles.carousel}>暂无图片</div>
+          ) : (
+            <>
+              <div className={styles.prev} onClick={() => carouselRef.current.prev()} />
+              <div className={styles.next} onClick={() => carouselRef.current.next()} />
+              <Carousel className={styles.carousel} ref={carouselRef}>
+                {imageList.map(item => (
+                  <ReactZmage src={item} />
+                ))}
+              </Carousel>
+            </>
+          )}
+        </div>
+        <div
+          className={styles.detailBlock}
+          style={{
+            backgroundImage: getTempImg(),
+            backgroundImage: `url(${space.gif_url})`,
+            backgroundSize: '30vw, 30vw',
+            backgroundRepeat: 'no-repeat',
+          }}
+        />
+        <div className={styles.DataBlock}>
+          <Spin spinning={loading}>
+            <div className={styles.DataItem}>
+              <div className={styles.DataTitle}>空间监测信息</div>
+              <div className={styles.DataRowItem}>
+                <div className={styles.DataItemLeft}>风险提示</div>
+                <div style={{ width: '25%', color: threshold.color }}>{threshold.str}</div>
+              </div>
+              {tableData.map(item => (
+                <div className={styles.DataRowItem}>
+                  <div className={styles.DataItemLeft}>{item.text}</div>
+                  <div style={{ width: '25%', color: item.color }}>{item.value}</div>
+                </div>
+              ))}
+            </div>
+          </Spin>
+        </div>
+      </div>
+      <Descriptions className={styles.descriptions} title="空间详情" column={1} bordered>
+        {config.map(item => {
+          if (item.type != 'file') {
+            let p = picList.find(p => p.en_name == item.en_name);
+            if (p && p.pic.length != 0) {
+              return (
+                <Descriptions.Item label={item.cn_name}>
+                  <div>
+                    {space[item.en_name]}
+                    {space[item.en_name] && <br />}
+                    <a onClick={() => goToPic(item.en_name)}>查看图片</a>
+                  </div>
+                </Descriptions.Item>
+              );
+            } else {
+              return (
+                <Descriptions.Item label={item.cn_name}>{space[item.en_name]}</Descriptions.Item>
+              );
+            }
+          }
+        })}
+      </Descriptions>
+    </div>
+  );
+}
+
+export default connect(({ limitedSpace, user, loading }) => ({
+  currentUser: user.currentUser,
+  loading: loading.models.limitedSpace,
+  config: limitedSpace.config,
+  dataConfig: limitedSpace.dataConfig,
+  spaceData: limitedSpace.spaceData,
+  spaceList: limitedSpace.space.list,
+}))(Detail);

+ 130 - 0
src/Project/pages/LimitedSpace/EditModal.js

@@ -0,0 +1,130 @@
+//空间台账编辑
+
+import React, { useEffect } from 'react';
+import { connect } from 'dva';
+import { Modal, Form, Input, Select, Upload } from 'antd';
+import styles from './LimitedSpace.less';
+const { Option } = Select;
+
+function EditModal(props) {
+  const {
+    form,
+    loading,
+    config,
+    currentItem,
+    visible,
+    onOk,
+    onCancel,
+    uploadProps,
+    groupId,
+  } = props;
+  const { getFieldDecorator } = form;
+
+  const handleOk = () => {
+    form.validateFields((error, values) => {
+      if (error) return;
+      let data = Object.keys(values)
+        .map(k => {
+          let value = values[k].v;
+          //处理上传图片的数据
+          if (typeof values[k].v != 'string') {
+            const datas = values[k].v?.fileList
+              ?.map(cur => {
+                return cur.response?.data || cur.url;
+              })
+              .filter(item => item);
+            value = JSON.stringify(datas);
+          }
+          let item = {
+            key: k,
+            val: value,
+          };
+          return item;
+        })
+        .filter(item => item.val);
+      onOk(data);
+    });
+  };
+
+  useEffect(() => {
+    form.resetFields();
+  }, [currentItem]);
+
+  const renderFormItem = item => {
+    let dom,
+      option = [];
+    switch (item.query_type) {
+      case 'select':
+        option = item.query_option.split(',');
+        dom = (
+          <Select allowClear>
+            {option.map(item => (
+              <Option key={item}>{item}</Option>
+            ))}
+          </Select>
+        );
+        break;
+      case 'multi-select':
+        option = item.query_option.split(',');
+        dom = (
+          <Select allowClear mode="multiple">
+            {option.map(item => (
+              <Option key={item}>{item}</Option>
+            ))}
+          </Select>
+        );
+        break;
+      case 'input':
+        if (item.type == 'string') dom = <Input />;
+        else {
+          //将图片数据转换成默认已上传的图片列表数据
+          const defaultFileList = currentItem ? JSON.parse(currentItem[item.en_name])?.map((cur, idx) => {
+            let index = cur.lastIndexOf("\/");
+            let fileName = cur.substring(index + 1,cur.length);
+            return {
+              uid: idx,
+              name: fileName,
+              status: 'done',
+              url: cur,
+            }
+          }):[]
+          dom = <Upload {...uploadProps} defaultFileList={defaultFileList}>
+            <a style={{ color: "#7BFFFB", fontSize:'22px' }}>上传</a>
+          </Upload>;
+        }
+        break;
+    }
+
+    return form.getFieldDecorator(`${item.en_name}.v`, {
+      initialValue: currentItem ? currentItem[item.en_name] : null,
+    })(dom);
+  };
+
+  return (
+    <Modal
+      title="编辑台账"
+      width="95%"
+      visible={visible}
+      onOk={handleOk}
+      onCancel={onCancel}
+      confirmLoading={loading}
+      destroyOnClose
+    >
+      <Form
+        labelCol={groupId ? { span: 8 } : { span: 6 }}
+        wrapperCol={groupId ? { span: 16 } : { span: 16 }}
+        layout="horizontal"
+      >
+        {config
+          .filter(item => item.is_edit)
+          .map(item => (
+            <Form.Item key={item.en_name} label={item.cn_name}>
+              {renderFormItem(item)}
+            </Form.Item>
+          ))}
+      </Form>
+    </Modal>
+  );
+}
+
+export default Form.create()(EditModal);

+ 293 - 0
src/Project/pages/LimitedSpace/LimitedSpace.less

@@ -0,0 +1,293 @@
+.page {
+  padding: 20px 25px;
+
+  :global {
+    .ant-carousel .slick-list .slick-slide {
+      text-align: center;
+      color: #fff;
+      justify-content: center;
+      line-height: 30vw;
+      font-size: 24px;
+    }
+
+    .ant-table-header .ant-table-hide-scrollbar {
+      min-width: 0;
+    }
+
+    .ant-table-fixed-header .ant-table-scroll .ant-table-header::-webkit-scrollbar {
+      border: 0;
+    }
+
+    .ant-table-header::-webkit-scrollbar-thumb {
+      background-color: transparent;
+    }
+
+    // body ::-webkit-scrollbar {
+    //   width: 0;
+    //   background-color: transparent;
+    // }
+  }
+}
+
+.list {
+  height: 50%;
+  overflow: auto;
+}
+
+.listItem {
+  display: block !important;
+  padding: 12px !important;
+  // text-align: center;
+  margin-bottom: 5px;
+  color: #fff;
+  font-size: 20px;
+  border-bottom: 1px solid rgba(153, 231, 255, 0.4);
+}
+
+.title {
+  text-align: center;
+  font-size: 32px;
+  letter-spacing: 10px;
+  color: #c2e6ff;
+  width: 100%;
+  background-size: 100% 100%;
+}
+
+.searchBtn {
+  padding: 10px 0 !important;
+}
+
+// .searchItem {
+//   padding: 10px 0 !important;
+// }
+
+.select {
+  background: #5f696e;
+}
+
+.areaBtn {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-between;
+}
+
+.area {
+  width: 27vw;
+  height: 27vw;
+  margin-top: 50px;
+  // border: solid #fff 2px;
+  border: 0;
+  border-radius: 20px;
+  background-size: 100% 100%;
+}
+
+.selectedArea {
+  width: 27vw;
+  height: 27vw;
+  margin-top: 50px;
+  // border: solid #fff 2px;
+  border: 0;
+  border-radius: 10px;
+  // background: #5f696e;
+  background-size: 100% 100%;
+}
+
+.nameBackground {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: flex-end;
+  background-size: 100% 100%;
+}
+
+.areaName {
+  color: #fff;
+  font-size: 24px;
+  margin-bottom: 20px;
+  letter-spacing: 5px;
+}
+
+.back {
+  position: fixed;
+}
+
+.detail {
+  position: fixed;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  width: 30vw;
+  margin-top: 70px;
+  margin-right: 20px;
+}
+
+.detailBlock {
+  display: flex;
+  align-items: center;
+  width: 30vw;
+  height: 30vw;
+  border: solid #99e7ff 2px;
+  border-radius: 10px;
+  margin-bottom: 20px;
+  position: relative;
+}
+.message {
+  width: 100%;
+  height: 36px;
+  line-height: 36px;
+  padding-left: 21px;
+  background: linear-gradient(270deg, #99e7ff 0%, #60a8ff 100%);
+}
+
+.messageText {
+  display: flex;
+  flex-wrap: wrap;
+}
+
+.DataBlock {
+  border: solid #99e7ff 2px;
+  border-radius: 10px;
+  margin-bottom: 20px;
+  color: #fff;
+
+  .DataItem {
+    padding: 0 20px;
+    font-size: 20px;
+    color: '#fff';
+    width: 100%;
+  }
+
+  .DataTitle {
+    font-size: 24px;
+    padding: 20px 0 10px;
+    border-bottom: 1px solid rgba(232, 232, 232, 0.6);
+  }
+
+  .DataRowItem {
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    padding: 10px;
+    // width: 50%;
+    // padding: 10px 35px;
+  }
+
+  .DataItemLeft {
+    width: 70%;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    color: #d7e4e8;
+  }
+}
+
+.descriptions {
+  margin-left: 35vw;
+}
+
+.carousel {
+  width: calc(30vw - 4px);
+  height: 30vw;
+
+  :global img {
+    max-height: 28vw;
+    object-fit: contain;
+  }
+}
+
+.prev {
+  width: 22px;
+  height: 129px;
+  color: #fff;
+  position: absolute;
+  z-index: 999;
+  left: 0;
+  background: url('@/assets/LimitedSpace/arrow-left.png') no-repeat center;
+  background-size: 100% 100%;
+}
+
+.next {
+  width: 22px;
+  height: 129px;
+  color: #fff;
+  position: absolute;
+  z-index: 999;
+  right: 0;
+  background: url('@/assets/LimitedSpace/arrow-right.png') no-repeat center;
+  background-size: 100% 100%;
+}
+
+.selectModal {
+  // .selectBlock {
+  //   height: 94px;
+  // }
+
+  :global {
+    .ant-input-number {
+      width: 100%;
+      border: 0;
+      border-radius: 0;
+    }
+
+    .ant-tabs-tab {
+      width: 14.5vw;
+      text-align: center;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      margin-right: 5% !important;
+
+      &:last-child {
+        margin-right: 0 !important;
+      }
+    }
+  }
+}
+
+.operationDetail {
+  padding: 10px 20px 0px;
+}
+.opDetailBlock {
+  padding: 20px 0px;
+  border-bottom: 1px solid #99e7ff;
+  &:last-child{
+    padding-bottom: 0px;
+    border: 0px;
+  }
+}
+.opPicBlock {
+  padding: 20px 0px;
+  border-bottom: 1px solid #99e7ff;
+  display: flex;
+  justify-content: space-between;
+}
+.opPicDes {
+  padding-right: 40px;
+}
+.opTitle {
+  font-size: 20px;
+  color: #c0f0ff;
+  line-height: 23px;
+  letter-spacing: 2px;
+}
+.opContent {
+  font-size: 18px;
+  color: #d8e4e8;
+  line-height: 20px;
+  letter-spacing: 2px;
+  padding-top: 24px;
+}
+.opPic {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding-right: 20px;
+  img{
+    max-width: 300px;
+    max-height: 300px;
+    margin-bottom: 20px;
+    &:last-child{
+      margin-bottom: 0px;
+    }
+  }
+}

+ 360 - 0
src/Project/pages/LimitedSpace/List.js

@@ -0,0 +1,360 @@
+//空间台账
+
+import React, { useState, useEffect, useRef } from 'react';
+import { connect } from 'dva';
+import {
+  Table,
+  Form,
+  Input,
+  Button,
+  Upload,
+  Modal,
+  Select,
+  AutoComplete,
+  Alert,
+  Divider,
+  message,
+} from 'antd';
+import router from 'umi/router';
+import { UnityAction, getToken, GetTokenFromUrl } from '@/utils/utils';
+import styles from './LimitedSpace.less';
+import project from '@/models/project';
+import SearchModal from './SearchModal';
+import EditModal from './EditModal';
+import { downloadFile } from '@/utils/utils';
+import { downloadQRCode } from '@/services/limitedSpace';
+
+function List(props) {
+  const { dispatch, loading, currentUser, form, config, space, pagination, areaList } = props;
+  const { projectId } = props.match.params;
+  const { group_id } = props.location.query;
+  const { getFieldDecorator, getFieldsValue } = form;
+  const [currentItem, setCurrentItem] = useState(null);
+  const [multiQuery, setMultiQuery] = useState([]);
+  const [searchVisible, setSearchVisible] = useState(false);
+  const [editVisible, setEditVisible] = useState(false);
+  const multiQueryRef = useRef([]);
+
+  const clickDetail = record => {
+    setCurrentItem(record);
+    let url = `/unity/limitedSpace/detail/${projectId}/${record.id}?JWT-TOKEN=${GetTokenFromUrl()}`;
+
+    if (!group_id) UnityAction.sendMsg('SpaceDatasFromWeb', JSON.stringify([record]));
+    else url = url.concat(`&group_id=${group_id}`);
+
+    UnityAction.sendMsg('SelectSpaceFromWeb', record.id);
+    router.push(url);
+  };
+
+  const token = getToken();
+  const uploadProps = {
+    name: 'file',
+    action: `/api/limitedspace/v1/space/upload`,
+    headers: {
+      'JWT-TOKEN': token,
+    },
+    data: {
+      project_id: projectId,
+    },
+    onChange(info) {
+      if (info.file.status !== 'uploading') {
+        console.log(info.file, info.fileList);
+      }
+      if (info.file.status === 'done') {
+        message.success(`${info.file.name} 文件上传成功`);
+        dispatch({
+          type: 'limitedSpace/querySpace',
+          payload: { project_id: projectId },
+        });
+      } else if (info.file.status === 'error') {
+        message.error(`${info.file.name} 文件上传失败`);
+      }
+    },
+    onRemove() {
+      return new Promise((resolve, reject) => {
+        Modal.confirm({
+          title: '提醒',
+          content: `确认删除图片?`,
+          okText: '确认',
+          cancelText: '取消',
+          onOk: resolve,
+          onCancel: reject,
+        });
+      });
+    },
+  };
+
+  const editUploadProps = {
+    name: 'files',
+    action: `/api/v1/common/file-upload?path=${projectId}/运营平台/设计文档/其他资料/&project_id=${projectId}`,
+    headers: {
+      'JWT-TOKEN': token,
+    },
+    onChange(info) {
+      if (info.file.status !== 'uploading') {
+        console.log(info.file, info.fileList);
+      }
+      if (info.file.status === 'done') {
+        message.success(` 文件上传成功`);
+      } else if (info.file.status === 'error') {
+        message.error(`${info.file.name} 文件上传失败`);
+      }
+    },
+    onRemove() {
+      return new Promise((resolve, reject) => {
+        Modal.confirm({
+          title: '提醒',
+          content: `确认删除图片?`,
+          okText: '确认',
+          cancelText: '取消',
+          onOk: () => resolve(),
+          onCancel: () => reject(),
+        });
+      });
+    },
+  };
+  const onBack = () => {
+    router.go(-1);
+    if (group_id) UnityAction.sendMsg('BackToOverallFromWeb');
+    UnityAction.sendMsg('BackToIndex');
+  };
+
+  const onQueryList = (pagination = {}) => {
+    const { name, area } = getFieldsValue();
+    let query = [...multiQueryRef.current];
+    let filter = { name: name, group_id, ...pagination };
+    if (area) {
+      query.push({ k: 'data1', v: area });
+    }
+    filter = { ...filter, query: JSON.stringify(query) };
+    dispatch({
+      type: 'limitedSpace/querySpace',
+      payload: { project_id: projectId, ...filter },
+    });
+  };
+
+  const onEdit = record => {
+    setCurrentItem(record);
+    setEditVisible(true);
+  };
+
+  const onCancel = () => {
+    setCurrentItem(null);
+    setSearchVisible(false);
+    setEditVisible(false);
+  };
+
+  const onOk = (query = []) => {
+    multiQueryRef.current = query;
+    onQueryList();
+    setSearchVisible(false);
+  };
+
+  const onEditOk = data => {
+    dispatch({
+      type: 'limitedSpace/updateSpace',
+      payload: {
+        x: currentItem.x,
+        y: currentItem.y,
+        z: currentItem.z,
+        gif_id: currentItem.gif_id,
+        group_id: currentItem.group_id,
+        type: currentItem.type,
+        name: currentItem.name,
+        id: currentItem.id,
+        project_id: Number(projectId),
+        extend: data,
+      },
+      callback: () => {
+        onQueryList();
+        setCurrentItem(null);
+        setEditVisible(false);
+      },
+    });
+  };
+
+  useEffect(() => {
+    dispatch({
+      type: 'limitedSpace/queryArea',
+      payload: { project_id: projectId },
+    });
+
+    dispatch({
+      type: 'limitedSpace/queryConfig',
+      payload: { project_id: projectId },
+    });
+
+    if (group_id) {
+      dispatch({
+        type: 'limitedSpace/querySpace',
+        payload: { project_id: projectId, group_id: group_id },
+        callback: list => {
+          UnityAction.sendMsg(
+            'SpaceDatasFromWeb',
+            JSON.stringify(list.filter(item => item.group_id == group_id))
+          );
+
+          UnityAction.addEventListener('SelectSpaceFromUnity', e => {
+            let record = list.find(item => item.id == e);
+            setCurrentItem(record);
+            let url = `/unity/limitedSpace/detail/${projectId}/${
+              record.id
+            }?JWT-TOKEN=${GetTokenFromUrl()}`;
+            if (group_id) url = url.concat(`&group_id=${group_id}`);
+            router.push(url);
+          });
+        },
+      });
+      return () => {
+        UnityAction.off('SelectSpaceFromUnity');
+      };
+    } else {
+      dispatch({
+        type: 'limitedSpace/querySpace',
+        payload: { project_id: projectId },
+      });
+    }
+  }, []);
+
+  const columns = [
+    { title: '序号', dataIndex: 'spaceId' },
+    { title: '存在区域', dataIndex: 'data1' },
+    { title: '空间名称', dataIndex: 'name' },
+    {
+      title: '深度',
+      render: record => {
+        return `${record.data8 || '-'} 米`;
+      },
+    },
+    {
+      title: '操作',
+      width: 200,
+      render: record => (
+        <>
+          <a onClick={() => onEdit(record)}>编辑</a>
+          <Divider type="vertical" />
+          <a onClick={() => clickDetail(record)}>详情</a>
+        </>
+      ),
+    },
+  ];
+
+  const nameFilter = (value, option) => {
+    return option.key.indexOf(value) != -1;
+  };
+
+  const areaFilter = (value, option) => {
+    return option.key.indexOf(value) != -1;
+  };
+
+  return (
+    <div className={styles.page}>
+      <div style={{ display: 'flex' }}>
+        <Form layout="inline" labelAlign="left" labelCol={{ span: 8 }} wrapperCol={{ span: 15 }}>
+          <Form.Item className={styles.searchItem} label="空间名称:">
+            {getFieldDecorator('name')(
+              <AutoComplete
+                style={{ display: 'block' }}
+                dataSource={space.list.map(item => item.name)}
+                filterOption={(value, option) => nameFilter(value, option)}
+                children={
+                  <Input placeholder="请选择空间名称" style={{ borderRadius: '8px', width: 180 }} />
+                }
+              />
+            )}
+          </Form.Item>
+          {!group_id && (
+            <Form.Item className={styles.searchItem} label="存在区域:">
+              {getFieldDecorator('area')(
+                <AutoComplete
+                  style={{ display: 'block' }}
+                  dataSource={areaList.map(item => item.name)}
+                  filterOption={(value, option) => areaFilter(value, option)}
+                  children={
+                    <Input
+                      placeholder="请选择存在区域"
+                      style={{ borderRadius: '8px', width: 180 }}
+                    />
+                  }
+                />
+              )}
+            </Form.Item>
+          )}
+        </Form>
+        <div style={{ display: 'flex' }}>
+          <Button onClick={() => onQueryList()} type="primary" style={{ marginRight: 20 }}>
+            搜索
+          </Button>
+          <Button onClick={() => setSearchVisible(true)} type="primary">
+            高级搜索
+          </Button>
+        </div>
+      </div>
+
+      <div style={{ marginBottom: 20, marginTop: 10 }}>
+        {group_id && (
+          <Button onClick={() => onBack()} type="primary" style={{ marginRight: 20 }}>
+            返回
+          </Button>
+        )}
+        <Button onClick={() => downloadQRCode(projectId, group_id)} type="primary">
+          下载全部二维码
+        </Button>
+      </div>
+      <div>
+        <Alert
+          message={`共有空间${space.statistics.total || 0}个,其中,地上${space.statistics.num1 ||
+            0}个,地下${space.statistics.num2 || 0}个,密闭容器${space.statistics.num3 || 0}个`}
+          type="info"
+        />
+      </div>
+      <Table
+        dataSource={space.list}
+        loading={loading}
+        columns={columns.filter(item => (group_id ? item.title != '存在区域' : item))}
+        rowKey="id"
+        pagination={space.pagination}
+        onChange={pagination => onQueryList(pagination)}
+        onRow={record => {
+          return {
+            onClick: () => {
+              setCurrentItem(record);
+              UnityAction.sendMsg('SelectSpaceFromWeb', record.id);
+            },
+          };
+        }}
+        rowClassName={record => (record == currentItem ? 'table-row-active' : null)}
+      />
+      {!group_id && (
+        <Upload {...uploadProps}>
+          <Button type="primary">导入空间信息</Button>
+        </Upload>
+      )}
+      <SearchModal
+        config={config}
+        visible={searchVisible}
+        onCancel={() => onCancel()}
+        onOk={onOk}
+      />
+      <EditModal
+        groupId={group_id}
+        config={config}
+        currentItem={currentItem}
+        loading={loading}
+        visible={editVisible}
+        onCancel={onCancel}
+        onOk={onEditOk}
+        uploadProps={editUploadProps}
+      />
+    </div>
+  );
+}
+
+export default connect(({ limitedSpace, user, loading }) => ({
+  currentUser: user.currentUser,
+  loading: loading.models.limitedSpace,
+  config: limitedSpace.config,
+  space: limitedSpace.space,
+  pagination: limitedSpace.space.pagination,
+  areaList: limitedSpace.areaList,
+}))(Form.create()(List));

+ 230 - 0
src/Project/pages/LimitedSpace/Operation.js

@@ -0,0 +1,230 @@
+//空间作业
+
+import React, { useEffect, useState } from 'react';
+import { connect } from 'dva';
+import {
+  Table,
+  Form,
+  Input,
+  Button,
+  Divider,
+  Modal,
+  Select,
+  DatePicker,
+  AutoComplete,
+  message,
+} from 'antd';
+import moment from 'moment';
+import { downloadFile, UnityAction } from '@/utils/utils';
+import ReactZmage from 'react-zmage';
+import styles from './LimitedSpace.less';
+import OperationDetail from './OperationDetail.js';
+
+function Operation(props) {
+  const { dispatch, currentUser, loading, form, operation, spaceList } = props;
+  const { projectId } = props.match.params;
+  const { RangePicker } = DatePicker;
+  const { getFieldsValue, getFieldDecorator } = form;
+  const [selectVisible, setSelectVisible] = useState(false);
+  const [createVisible, setCreateVisible] = useState(false);
+  const [currentRecord, setCurrentRecord] = useState(null);
+  const [selectedSpace, setSelectedSpace] = useState([]);
+
+  const columns = [
+    { title: '序号', dataIndex: 'opId' },
+    { title: '空间名称', dataIndex: 'name' },
+    { title: '作业内容', dataIndex: 'op_content' },
+    { title: '时间', render: record => moment(record.op_time).format('YYYY-MM-DD HH:mm') },
+    {
+      title: '操作',
+      render: record => (
+        <>
+          <a onClick={() => onDetail(record)}>详情</a>
+          <Divider type="vertical" />
+          <a onClick={() => onDownload(record)}>下载</a>
+          <Divider type="vertical" />
+          <a onClick={() => onDelete(record)}>删除</a>
+        </>
+      ),
+    },
+  ];
+
+  useEffect(() => {
+    dispatch({
+      type: 'limitedSpace/querySpace',
+      payload: { project_id: projectId },
+    });
+
+    dispatch({
+      type: 'limitedSpace/queryOperation',
+      payload: { project_id: projectId },
+      callback: data => {
+        setTimeout(() => {
+          UnityAction.sendMsg('SpaceDatasFromWeb', data);
+        }, 800);
+      },
+    });
+  }, []);
+
+  const onQueryList = (pagination = {}) => {
+    const { date, name } = getFieldsValue();
+    let s_time, e_time;
+    if (date) {
+      s_time = date[0].format('yyyy-MM-DD');
+      e_time = date[1].format('yyyy-MM-DD');
+    }
+    let filter = { s_time, e_time, space_name: name, ...pagination };
+    dispatch({
+      type: 'limitedSpace/queryOperation',
+      payload: { project_id: projectId, ...filter },
+      callback: data => {
+        setTimeout(() => {
+          UnityAction.sendMsg('SpaceDatasFromWeb', data);
+        }, 800);
+      },
+    });
+  };
+
+  const onDetail = record => {
+    setCurrentRecord(record);
+    setCreateVisible(true);
+  };
+
+  const onDownload = record => {
+    dispatch({
+      type: 'limitedSpace/queryDownloadOperation',
+      payload: {
+        project_id: projectId,
+        id: record.id,
+      },
+      callback: url => downloadFile(url, `空间作业_${record.name}`),
+    });
+  };
+
+  const onDelete = record => {
+    Modal.confirm({
+      title: '提醒',
+      content: `确认删除作业?`,
+      okText: '确认',
+      cancelText: '取消',
+      onOk: () => {
+        dispatch({
+          type: 'limitedSpace/deleteOperation',
+          payload: {
+            id: record.id,
+            project_id: projectId,
+          },
+        });
+      },
+    });
+  };
+
+  const onCancel = () => {
+    setCreateVisible(false);
+    setSelectVisible(false);
+    setSelectedSpace([]);
+    setCurrentRecord(null);
+  };
+
+  const onSelectOk = () => {
+    setSelectVisible(false);
+    setCreateVisible(true);
+  };
+
+  const onOk = () => {
+    setCreateVisible(false);
+    setSelectedSpace([]);
+    setCurrentRecord(null);
+  };
+
+  const onChange = value => {
+    if (value.length <= 5) setSelectedSpace([...value]);
+    else message.error('最多选择5个作业空间');
+  };
+
+  const nameFilter = (value, option) => {
+    return option.key.indexOf(value) != -1;
+  };
+
+  return (
+    <div className={styles.page}>
+      <Form layout="inline" labelAlign="right">
+        <Form.Item className={styles.searchItem} label="作业时间:">
+          {getFieldDecorator('date')(<RangePicker style={{ width: '35vw' }} />)}
+        </Form.Item>
+        <Form.Item className={styles.searchItem} label="空间名称:">
+          {getFieldDecorator('name')(
+            <AutoComplete
+              style={{ width: '25vw', display: 'block' }}
+              dataSource={spaceList.map(item => item.name)}
+              filterOption={(value, option) => nameFilter(value, option)}
+              children={<Input style={{ borderRadius: '8px' }} />}
+            />
+          )}
+        </Form.Item>
+      </Form>
+
+      <div style={{ marginBottom: 20, marginTop: 10 }}>
+        <Button style={{ marginRight: 20 }} onClick={() => onQueryList()} type="primary">
+          搜索
+        </Button>
+        <Button onClick={() => setSelectVisible(true)} type="primary">
+          创建空间作业
+        </Button>
+      </div>
+
+      <Table
+        columns={columns}
+        loading={loading}
+        dataSource={operation.list}
+        pagination={operation.pagination}
+        onChange={pagination => onQueryList(pagination)}
+      />
+      <Modal
+        className={styles.selectModal}
+        title="选择空间"
+        visible={selectVisible}
+        onCancel={() => onCancel()}
+        onOk={() => onSelectOk()}
+      >
+        <Form labelCol={{ span: 5 }} wrapperCol={{ span: 18 }} layout="horizontal">
+          <Form.Item label="作业空间:">
+            <Select
+              mode="multiple"
+              onChange={value => onChange(value)}
+              placeholder="请选择空间"
+              loading={loading}
+              style={{ width: '100%' }}
+              value={selectedSpace}
+              optionFilterProp="label"
+            >
+              {spaceList?.map(item => (
+                <Select.Option key={item.id} label={`${item.data1}-${item.name}`}>
+                  {`${item.data1}-${item.name}`}
+                </Select.Option>
+              ))}
+            </Select>
+          </Form.Item>
+        </Form>
+      </Modal>
+      <OperationDetail
+        visible={createVisible}
+        selectedSpace={selectedSpace}
+        currentRecord={currentRecord}
+        projectId={projectId}
+        onOk={onOk}
+        onCancel={onCancel}
+        loading={loading}
+      />
+    </div>
+  );
+}
+
+export default connect(({ limitedSpace, user, loading }) => ({
+  currentUser: user.currentUser,
+  loading: loading.models.limitedSpace,
+  spaceList: limitedSpace.space.list,
+  operation: limitedSpace.operation,
+  operationList: limitedSpace.operation.list,
+  pagination: limitedSpace.operation.pagination,
+}))(Form.create()(Operation));

+ 392 - 0
src/Project/pages/LimitedSpace/OperationDetail.js

@@ -0,0 +1,392 @@
+import React, { useState, useEffect } from 'react';
+import { Form, Input, Modal, Row, Col, Select, InputNumber, Spin, Tabs, DatePicker } from 'antd';
+import { connect } from 'dva';
+import moment from 'moment';
+import ReactZmage from 'react-zmage';
+import styles from './LimitedSpace.less';
+
+function OperationDetail(props) {
+  const {
+    loading,
+    form,
+    dispatch,
+    visible,
+    selectedSpace,
+    currentRecord,
+    projectId,
+    onOk,
+    onCancel,
+  } = props;
+  const { getFieldDecorator, setFieldsValue, resetFields, validateFields } = form;
+  const [spaceDetailList, setSpaceDetailList] = useState([]);
+  const [isEdit, setIsEdit] = useState(false);
+  const [isEpiboly, setIsEpiboly] = useState(false);
+  const [opCode, setOpCode] = useState('');
+  const [opTime, setOpTime] = useState('');
+
+  const TIMER_FORMAT = 'YYYY-MM-DD HH:mm';
+
+  const handleOk = () => {
+    if (isEdit) {
+      validateFields((err, fieldValues) => {
+        if (!err) {
+          const { op_total, op_time } = fieldValues;
+          let payload = {
+            ...fieldValues,
+            project_id: Number(projectId),
+            op_time: opTime,
+            op_total: Number(op_total),
+            op_time: moment(op_time).format('YYYY-MM-DD HH:mm:ss'),
+          };
+          //编辑
+          if (currentRecord) {
+            onEdit(payload);
+          }
+          //新建
+          else {
+            onCreate(payload);
+          }
+          setSpaceDetailList([]);
+          setIsEdit(false);
+          setIsEpiboly(false);
+          setOpCode('');
+          setOpTime('');
+          resetFields();
+          onOk();
+        }
+      });
+    }
+    //编辑按钮
+    else {
+      setIsEdit(true);
+    }
+  };
+
+  const handleCancel = () => {
+    setSpaceDetailList([]);
+    setIsEdit(false);
+    setIsEpiboly(false);
+    setOpCode('');
+    setOpTime('');
+    resetFields();
+    onCancel();
+  };
+
+  const onCreate = payload => {
+    payload = { ...payload, space_ids: selectedSpace.join(',') };
+    dispatch({
+      type: 'limitedSpace/createOperation',
+      payload,
+    });
+  };
+
+  const onEdit = payload => {
+    payload = { ...payload, space_id: Number(spaceDetailList[0].space_id), id: currentRecord.id };
+    dispatch({
+      type: 'limitedSpace/updateOperation',
+      payload,
+    });
+  };
+
+  const onChangeForm = value => {
+    if (value == 3) setIsEpiboly(true);
+    else setIsEpiboly(false);
+  };
+
+  useEffect(() => {
+    if (!visible) return;
+    //详情
+    if (currentRecord) {
+      setIsEdit(false);
+      dispatch({
+        type: 'limitedSpace/queryOpDetail',
+        payload: {
+          id: currentRecord.id,
+        },
+        callback: data => {
+          setSpaceDetailList([{ ...data.space, id: data.op.id, space_id: data.op.space_id }]);
+          setOpCode(data.op.code);
+          setOpTime(data.op.op_time);
+          const {
+            dep,
+            op_content,
+            op_form,
+            charge_person,
+            guardian,
+            check_person,
+            op_person,
+            other_person,
+            op_total,
+            other_info,
+          } = data.op;
+          setFieldsValue({
+            dep,
+            op_content,
+            op_form,
+            charge_person,
+            guardian,
+            check_person,
+            op_person,
+            other_person,
+            op_total,
+            other_info,
+          });
+          if (data.op.epiboly) {
+            setIsEpiboly(true);
+            setFieldsValue({
+              epiboly: data.op.epiboly,
+            });
+          }
+        },
+      });
+    }
+    //新建
+    else {
+      setIsEdit(true);
+      setOpTime(moment().format(TIMER_FORMAT));
+      dispatch({
+        type: 'limitedSpace/querySpacesDetail',
+        payload: { selectedSpace },
+        callback: list => setSpaceDetailList(list),
+      });
+    }
+  }, [selectedSpace, currentRecord, visible]);
+
+  const renderForm = () => (
+    <Form layout="horizontal" labelCol={{ span: 10 }} wrapperCol={{ span: 12 }}>
+      <Row gutter={[20, 20]} justify="space-between">
+        <Col span={12}>
+          <Form.Item label="制定部门">
+            {getFieldDecorator('dep', {
+              rules: [{ required: true, message: '请填写部门' }],
+            })(<Input disabled={!isEdit} required />)}
+          </Form.Item>
+        </Col>
+        <Col span={12}>
+          <Form.Item label="作业内容">
+            {getFieldDecorator('op_content', {
+              rules: [{ required: true, message: '请填写作业内容' }],
+            })(<Input disabled={!isEdit} required />)}
+          </Form.Item>
+        </Col>
+        <Col span={12} className={styles.selectBlock}>
+          <Form.Item label="作业形式">
+            {getFieldDecorator('op_form', {
+              rules: [{ required: true, message: '请选择作业形式' }],
+            })(
+              <Select disabled={!isEdit} onChange={value => onChangeForm(value)} required>
+                <Select.Option key="1"> 自主作业(无外请劳务人员) </Select.Option>
+                <Select.Option key="2"> 自主作业(有外请劳务人员) </Select.Option>
+                <Select.Option key="3"> 外包作业 </Select.Option>
+              </Select>
+            )}
+          </Form.Item>
+        </Col>
+        {isEpiboly && (
+          <Col span={12}>
+            <Form.Item label="外包作业单位">
+              {getFieldDecorator('epiboly', {
+                rules: [{ required: true, message: '请输入外包单位' }],
+              })(<Input disabled={!isEdit} required />)}
+            </Form.Item>
+          </Col>
+        )}
+        <Col span={12}>
+          <Form.Item label="作业负责人">
+            {getFieldDecorator('charge_person', {
+              rules: [{ required: true, message: '请填写负责人' }],
+            })(<Input disabled={!isEdit} required />)}
+          </Form.Item>
+        </Col>
+        <Col span={12}>
+          <Form.Item label="作业监护人">
+            {getFieldDecorator('guardian', {
+              rules: [{ required: true, message: '请填写监护人' }],
+            })(<Input disabled={!isEdit} required />)}
+          </Form.Item>
+        </Col>
+        <Col span={12}>
+          <Form.Item label="气体检测人">
+            {getFieldDecorator('check_person', {
+              rules: [{ required: true, message: '请填写气体检测人' }],
+            })(<Input disabled={!isEdit} required />)}
+          </Form.Item>
+        </Col>
+        <Col span={12}>
+          <Form.Item label="作业人">
+            {getFieldDecorator('op_person', {
+              rules: [{ required: true, message: '请填写作业人' }],
+            })(<Input disabled={!isEdit} required />)}
+          </Form.Item>
+        </Col>
+        <Col span={12}>
+          <Form.Item label="其他作业人员">
+            {getFieldDecorator('other_person')(<Input disabled={!isEdit} />)}
+          </Form.Item>
+        </Col>
+        <Col span={12}>
+          <Form.Item label="作业人数">
+            {getFieldDecorator('op_total', {
+              rules: [{ required: true, message: '请填写作业人数' }],
+            })(<InputNumber precision={0} min={0} disabled={!isEdit} required />)}
+          </Form.Item>
+        </Col>
+        <Col span={12}>
+          <Form.Item label="其他情况">
+            {getFieldDecorator('other_info')(<Input disabled={!isEdit} />)}
+          </Form.Item>
+        </Col>
+        <Col span={12}>
+          <Form.Item label="作业时间">
+            {getFieldDecorator('op_time', {
+              initialValue: moment(opTime || moment(new Date()), TIMER_FORMAT),
+            })(
+              <DatePicker
+                style={{ minWidth: '230px' }}
+                disabled={!isEdit}
+                format={TIMER_FORMAT}
+                placeholder="请选择作业时间"
+                showTime={{ format: 'HH:mm' }}
+              />
+            )}
+          </Form.Item>
+        </Col>
+      </Row>
+    </Form>
+  );
+
+  //这部分可以重构
+  //利用array.map
+  //结构[{pic: bool, title: string, value: string}]
+  const renderDetail = (space, op_total) => (
+    <div className={styles.operationDetail}>
+      <div className={styles.opDetailBlock}>
+        <div className={styles.opTitle}>作业地点: </div>
+        <div className={styles.opContent}>{`石景山水厂${space.data1}${space.name}`}</div>
+      </div>
+      <div className={styles.opDetailBlock}>
+        <div className={styles.opTitle}>作业安全: </div>
+        <div className={styles.opContent}>{`本次作业电压不大于${space.data20 || '-'}`}</div>
+      </div>
+      <div className={styles.opPicBlock}>
+        <div className={styles.opPicDes}>
+          <div className={styles.opTitle}>有限空间基本情况: </div>
+          <div className={styles.opContent}>
+            {`为${space.type || '-'}, 深度${space.data8 || '-'}米, 出入口共有${space.data9 ||
+              '-'}个`}
+          </div>
+        </div>
+        {space?.data9_pic?.length != 0 && (
+          <div className={styles.opPic}>
+            {space.data9_pic.map((item, index) => (
+              <ReactZmage key={index} src={item} />
+            ))}
+          </div>
+        )}
+      </div>
+      <div className={styles.opPicBlock}>
+        <div className={styles.opPicDes}>
+          <div className={styles.opTitle}>周围情况: </div>
+          <div className={styles.opContent}>
+            {`${space.data11 || '-'}, 此有限空间为${
+              space.data12 ? (space.data12 == '是' ? '道路作业' : '非道路作业') : '-'
+            }`}
+          </div>
+        </div>
+        {space?.data11_pic?.length != 0 && (
+          <div className={styles.opPic}>
+            {space.data11_pic.map((item, index) => (
+              <ReactZmage key={index} src={item} />
+            ))}
+          </div>
+        )}
+      </div>
+      <div className={styles.opPicBlock}>
+        <div className={styles.opPicDes}>
+          <div className={styles.opTitle}>危险源情况: </div>
+          <div className={styles.opContent}>{space.data10}</div>
+        </div>
+        {space?.data10_pic?.length != 0 && (
+          <div className={styles.opPic}>
+            {space.data10_pic.map((item, index) => (
+              <ReactZmage key={index} src={item} />
+            ))}
+          </div>
+        )}
+      </div>
+      <div className={styles.opPicBlock}>
+        <div className={styles.opPicDes}>
+          <div className={styles.opTitle}>内部情况: </div>
+          <div className={styles.opContent}>{space.data14}</div>
+        </div>
+        {space?.data14_pic?.length != 0 && (
+          <div className={styles.opPic}>
+            {space.data14_pic.map((item, index) => (
+              <ReactZmage key={index} src={item} />
+            ))}
+          </div>
+        )}
+      </div>
+      <div className={styles.opDetailBlock}>
+        <div className={styles.opTitle}>照明灯具数量: </div>
+        <div className={styles.opContent}>{op_total}</div>
+      </div>
+      <div className={styles.opDetailBlock}>
+        <div className={styles.opTitle}>安全带数量: </div>
+        <div className={styles.opContent}>{op_total}</div>
+      </div>
+      <div className={styles.opDetailBlock}>
+        <div className={styles.opTitle}>安全帽数量: </div>
+        <div className={styles.opContent}>{op_total ? Number(op_total) + 2 : null}</div>
+      </div>
+      <div className={styles.opDetailBlock}>
+        <div className={styles.opTitle}>安全绳数量: </div>
+        <div className={styles.opContent}>{op_total}</div>
+      </div>
+      <div className={styles.opDetailBlock}>
+        <div className={styles.opTitle}>呼吸防护数量: </div>
+        <div className={styles.opContent}>{`每人应携带${space.data19 < 10 ? 1 : 2}个`}</div>
+      </div>
+      {opCode && (
+        <div className={styles.opDetailBlock}>
+          <div className={styles.opTitle}>审批表编号: </div>
+          <div className={styles.opContent}>{opCode}</div>
+        </div>
+      )}
+      <div className={styles.opDetailBlock}>
+        <div className={styles.opTitle}>审批人: </div>
+        <div className={styles.opContent}>{space.data6}</div>
+      </div>
+    </div>
+  );
+
+  return (
+    <Modal
+      className={styles.selectModal}
+      title={`作业详情`}
+      visible={visible}
+      onCancel={handleCancel}
+      onOk={handleOk}
+      width={'95%'}
+      okText={isEdit ? '提交' : '编辑'}
+    >
+      {renderForm()}
+      <br />
+      <Spin spinning={loading}>
+        <Tabs>
+          {spaceDetailList?.map((space, index) => {
+            let id = selectedSpace[index] || space.space_id;
+            const { op_total } = form.getFieldsValue();
+            return (
+              <Tabs.TabPane tab={space.name} key={id}>
+                {renderDetail(space, op_total)}
+              </Tabs.TabPane>
+            );
+          })}
+        </Tabs>
+      </Spin>
+    </Modal>
+  );
+}
+
+export default connect()(Form.create()(OperationDetail));

+ 278 - 0
src/Project/pages/LimitedSpace/Save.js

@@ -0,0 +1,278 @@
+//空间作业
+
+import React, { useEffect, useState } from 'react';
+import { connect } from 'dva';
+import {
+  Table,
+  Form,
+  Input,
+  Button,
+  Divider,
+  Modal,
+  Tabs,
+  Select,
+  Empty,
+  DatePicker,
+  AutoComplete,
+} from 'antd';
+import moment from 'moment';
+import { downloadFile, UnityAction } from '@/utils/utils';
+import styles from './LimitedSpace.less';
+import SaveModal from './SaveModal';
+
+function Operation(props) {
+  const {
+    dispatch,
+    currentUser,
+    loading,
+    form,
+    rescue,
+    config,
+    spaceList,
+    rescueDetail,
+    spaceDetail,
+  } = props;
+  const { projectId } = props.match.params;
+  const { RangePicker } = DatePicker;
+  const { getFieldsValue, getFieldDecorator } = form;
+  const [selectVisible, setSelectVisible] = useState(false);
+  const [createVisible, setCreateVisible] = useState(false);
+  const [isDetail, setIsDetail] = useState(false);
+  const [selectedSpace, setSelectedSpace] = useState();
+  const [selectedRecord, setSelectedRecord] = useState({});
+
+  const columns = [
+    { title: '序号', dataIndex: 'saveId' },
+    { title: '时间', dataIndex: 'r_time' },
+    { title: '空间名称', dataIndex: 'name' },
+    {
+      title: '操作',
+      render: record => (
+        <>
+          <a onClick={() => onDetail(record)}>详情</a>
+          <Divider type="vertical" />
+          {/* <a onClick={() => onDownload(record)}>下载</a> */}
+          {/* <a>下载应急处置卡</a> */}
+          {/* <Divider type="vertical" /> */}
+          <a onClick={() => onDownload(record)}>下载应急预案</a>
+          <Divider type="vertical" />
+          <a onClick={() => onDelete(record)}>删除</a>
+        </>
+      ),
+    },
+  ];
+  const temp = [
+    { id: 1, operation: '测试1' },
+    { id: 2, operation: '测试2' },
+  ];
+
+  useEffect(() => {
+    // dispatch({
+    //   type: 'limitedSpace/queryConfig',
+    //   payload: { project_id: projectId },
+    // });
+
+    dispatch({
+      type: 'limitedSpace/querySpace',
+      payload: { project_id: projectId },
+    });
+
+    dispatch({
+      type: 'limitedSpace/queryRescue',
+      payload: { project_id: projectId },
+      callback: data => {
+        setTimeout(() => {
+          UnityAction.sendMsg('SpaceDatasFromWeb', data);
+        }, 800);
+      }
+    });
+  }, []);
+
+  const onQueryList = (pagination = {}) => {
+    const { date, name } = getFieldsValue();
+    let s_time, e_time;
+    if (date) {
+      s_time = date[0].format('yyyy-MM-DD');
+      e_time = date[1].format('yyyy-MM-DD');
+    }
+    let filter = { s_time, e_time, space_name: name, ...pagination };
+    dispatch({
+      type: 'limitedSpace/queryRescue',
+      payload: { project_id: projectId, ...filter },
+      callback: data => {
+        setTimeout(() => {
+          UnityAction.sendMsg('SpaceDatasFromWeb', data);
+        }, 800);
+      }
+    });
+  };
+
+  const onDetail = record => {
+    console.log(record);
+    dispatch({
+      type: 'limitedSpace/queryRescueDetail',
+      payload: {
+        id: record.id,
+      },
+    });
+    setIsDetail(true);
+    setSelectedRecord(record);
+    setCreateVisible(true);
+  };
+  const onDownload = record => {
+    console.log(record);
+    dispatch({
+      type: 'limitedSpace/queryDownloadRescue',
+      payload: {
+        project_id: projectId,
+        id: record.id,
+      },
+      callback: url => downloadFile(url, `应急处置预案_${record.name}`),
+    });
+  };
+
+  const onDownloadCard = () => {
+    dispatch({
+      type: 'limitedSpace/queryDownloadCard',
+      payload: {
+        project_id: projectId,
+        id: selectedRecord.id,
+      },
+      callback: url => downloadFile(url, `应急处理卡_${selectedRecord.name}`),
+    });
+  };
+  const onDelete = record => {
+    Modal.confirm({
+      title: '提醒',
+      content: `确认删除作业?`,
+      okText: '确认',
+      cancelText: '取消',
+      onOk: () => {
+        dispatch({
+          type: 'limitedSpace/deleteRescue',
+          payload: {
+            id: record.id,
+            project_id: projectId,
+          },
+        });
+      },
+    });
+  };
+
+  const onCancel = () => {
+    setCreateVisible(false);
+    setSelectVisible(false);
+    setIsDetail(false);
+    setSelectedSpace();
+    setSelectedRecord({});
+  };
+  const onSelectOk = () => {
+    setSelectVisible(false);
+    setCreateVisible(true);
+    dispatch({
+      type: 'limitedSpace/querySpaceDetail',
+      payload: { id: selectedSpace },
+    });
+  };
+  const onCreateOk = () => {
+    dispatch({
+      type: 'limitedSpace/createRescue',
+      payload: { space_id: Number(selectedSpace), project_id: Number(projectId) },
+    });
+
+    setCreateVisible(false);
+    setSelectedSpace();
+    form.resetFields();
+  };
+
+  const onChange = value => {
+    setSelectedSpace(value);
+  };
+
+  const createRescue = () => {
+    setSelectVisible(true);
+  };
+
+  const nameFilter = (value, option) => {
+    return option.key.indexOf(value) != -1;
+  };
+
+  return (
+    <div className={styles.page}>
+      <Form layout="inline" labelAlign="right">
+        <Form.Item className={styles.searchItem} label="救援时间:">
+          {getFieldDecorator('date')(<RangePicker style={{ width: '35vw' }} />)}
+        </Form.Item>
+        <Form.Item className={styles.searchItem} label="空间名称:">
+          {getFieldDecorator('name')(
+            <AutoComplete
+              style={{ width: '25vw', display: 'block' }}
+              dataSource={spaceList.map(item => item.name)}
+              filterOption={(value, option) => nameFilter(value, option)}
+              children={<Input style={{ borderRadius: '8px' }} />}
+            />
+          )}
+        </Form.Item>
+      </Form>
+
+      <div style={{ marginBottom: 20, marginTop: 10 }}>
+        <Button style={{ marginRight: 20 }} onClick={() => onQueryList()} type="primary">
+          搜索
+        </Button>
+        <Button onClick={() => createRescue()} type="primary">
+          创建应急处置卡
+        </Button>
+      </div>
+      <Table
+        loading={loading}
+        columns={columns}
+        dataSource={rescue.list}
+        pagination={rescue.pagination}
+        onChange={pagination => onQueryList(pagination)}
+      />
+      <Modal
+        title="选择空间"
+        visible={selectVisible}
+        onCancel={() => onCancel()}
+        onOk={() => onSelectOk()}
+      >
+        <Form labelCol={{ span: 5 }} wrapperCol={{ span: 18 }} layout="horizontal">
+          <Form.Item label="救援空间:">
+            <Select
+              showSearch
+              onChange={value => onChange(value)}
+              placeholder="请选择空间"
+              loading={loading}
+              style={{ width: '100%' }}
+              value={selectedSpace}
+              optionFilterProp="label"
+            >
+              {spaceList.map(item => (
+                <Select.Option key={item.id} label={`${item.data1}-${item.name}`}>
+                  {`${item.data1}-${item.name}`}
+                </Select.Option>
+              ))}
+            </Select>
+          </Form.Item>
+        </Form>
+      </Modal>
+      <SaveModal
+        visible={createVisible}
+        spaceDetail={isDetail ? rescueDetail : spaceDetail}
+        onOk={() => (isDetail ? onDownloadCard() : onCreateOk())}
+        onCancel={() => onCancel()}
+        okText={isDetail ? '下载' : '确定'}
+      />
+    </div>
+  );
+}
+
+export default connect(({ limitedSpace, user, loading }) => ({
+  currentUser: user.currentUser,
+  loading: loading.models.limitedSpace,
+  config: limitedSpace.config,
+  spaceList: limitedSpace.space.list,
+  rescue: limitedSpace.rescue,
+  rescueDetail: limitedSpace.rescueDetail,
+  spaceDetail: limitedSpace.spaceDetail,
+}))(Form.create()(Operation));

+ 182 - 0
src/Project/pages/LimitedSpace/SaveModal.js

@@ -0,0 +1,182 @@
+import React, { useEffect, useState, useRef } from 'react';
+import { Modal } from 'antd';
+import style from './SaveModal.less';
+import ReactZmage from 'react-zmage';
+
+function SaveModal(props) {
+  const { onCancel, onOk, okText, spaceDetail, visible } = props;
+
+  const handleOk = () => {
+    onOk();
+  };
+
+  const handleCancel = () => {
+    onCancel && onCancel();
+  };
+  console.log(spaceDetail);
+
+  return (
+    <div>
+      <Modal
+        destroyOnClose
+        width={'90%'}
+        title="有限空间事故应急处置卡"
+        visible={visible}
+        onOk={handleOk}
+        onCancel={handleCancel}
+        okText={okText}
+      >
+        <h3 className={style.title}>
+          原则:在确保自身安全的前提下,科学救援,挽救生命、降低损失,严禁盲目施救!
+        </h3>
+        <table className={style.table} border="1">
+          <tr>
+            <th width="4%" rowSpan={2}>
+              1
+            </th>
+            <th width="15%" rowSpan={2}>
+              信息上报
+            </th>
+            <th width="27%">作业审批人</th>
+            <th width="27%">公司值班电话</th>
+            <th width="27%">社会救援</th>
+          </tr>
+          <tr>
+            <td>{spaceDetail.data6 || '-'}</td>
+            <td>68840911</td>
+            <td>68886208</td>
+          </tr>
+          <tr>
+            <td rowSpan={2}>2</td>
+            <td rowSpan={2}>现场警戒</td>
+            <td>事故有限空间名称</td>
+            <td>事故有限空间位置</td>
+            <td>有限空间出入口是否在道路作业</td>
+          </tr>
+          <tr>
+            <td>{spaceDetail.name || '-'}</td>
+            <td>{spaceDetail.data1 || '-'}</td>
+            <td>{spaceDetail.data12 || '-'}</td>
+          </tr>
+          <tr>
+            <td rowSpan={2}>3</td>
+            <td rowSpan={2}>救援装备</td>
+            <td>安全帽、安全绳、安全带、照明</td>
+            <td>三脚架、速差器、安全警示</td>
+            <td>气体检测仪、风机、呼吸防护</td>
+          </tr>
+          <tr>
+            <td colSpan={3}>
+              石景山水厂救援装备联系人,栗猛,13661035551;安全科救援装备联系人,张来德,13641077820
+            </td>
+          </tr>
+          <tr>
+            <td rowSpan={2}>4</td>
+            <td rowSpan={2}>现场环境情况</td>
+            <td>现场环境照片</td>
+            <td>救援锚点位置</td>
+            <td>附近危险源</td>
+          </tr>
+          {/* 以下3部分需要照片 */}
+          <tr>
+            <td>
+              {/* 现场环境照片 */}
+              {/* {spaceDetail.data11 || '-'}
+              <br /> */}
+              <div className={style.img}>
+                <ReactZmage src={spaceDetail.data11_pic?.[0]} />
+              </div>
+            </td>
+            <td>
+              {/* 救援锚点位置 */}
+              {/* {spaceDetail.data15 || '-'}
+              <br /> */}
+              <div className={style.img}>
+                <ReactZmage src={spaceDetail.data15_pic} />
+              </div>
+            </td>
+            <td>
+              {/* 附近危险源 */}
+              {/* {spaceDetail.data10 || '-'}
+              <br /> */}
+              <div className={style.img}>
+                <ReactZmage src={spaceDetail.data10_pic} />
+              </div>
+            </td>
+          </tr>
+          <tr>
+            <td rowSpan={2}>5</td>
+            <td rowSpan={2}>有限空间内情况</td>
+            <td>内部结构</td>
+            <td>危险因素</td>
+            <td>踏步形式</td>
+          </tr>
+          <tr>
+            <td>
+              {/* 内部结构 */}
+              <div className={style.img}>
+                <ReactZmage src={spaceDetail.data14_pic} />
+              </div>
+            </td>
+            <td>
+              {/* 危险因素 */}
+              {spaceDetail.data2 || '-'}
+            </td>
+            <td>
+              {/* 踏步形式 */}
+              {spaceDetail.data21 || '-'}
+            </td>
+          </tr>
+          <tr>
+            <td rowSpan={2}>6</td>
+            <td rowSpan={2}>被救人员情况</td>
+            <td>作业负责人姓名</td>
+            <td>作业监护人姓名</td>
+            <td>作业人员名单</td>
+          </tr>
+          <tr>
+            <td>
+              {/* 作业负责人姓名 */}
+              {spaceDetail.charge_person || '-'}
+            </td>
+            <td>
+              {/* 作业监护人姓名 */}
+              {spaceDetail.guardian || '-'}
+            </td>
+            <td>
+              {/* 作业人员名单 */}
+              {spaceDetail.op_person || '-'}
+            </td>
+          </tr>
+          <tr>
+            <td rowSpan={2}>7</td>
+            <td rowSpan={2}>救援形式</td>
+            <td>自救</td>
+            <td>非进入式救援</td>
+            <td>进入式救援</td>
+          </tr>
+          <tr>
+            <td colSpan={2}>
+              被困人员所处位置、身体状态、个体防护装备穿戴等情况,具备从有限空间外直接施救或自救的,救援人员在外部通过安全绳等装备将被困人员救出。是推荐的救援方式。
+            </td>
+            <td>不具备从有限空间外直接施救条件,救援人员进入内部施救。是危险的救援方式</td>
+          </tr>
+          <tr>
+            <td rowSpan={2}>8</td>
+            <td rowSpan={2}>后续(持续)工作</td>
+            <td>信息持续上报</td>
+            <td>医疗救护</td>
+            <td>清理现场</td>
+          </tr>
+          <tr>
+            <td>持续上报事故现场情况、救援进展和采取的措施</td>
+            <td>人员救出后立即移至通风良好且安全处,采取正确的院前急救,迅速送医治疗</td>
+            <td>清点人员、装备;清理事故现场残留的有毒有害物质;人员、装备洗消</td>
+          </tr>
+        </table>
+      </Modal>
+    </div>
+  );
+}
+
+export default SaveModal;

+ 28 - 0
src/Project/pages/LimitedSpace/SaveModal.less

@@ -0,0 +1,28 @@
+.title {
+  text-align: left;
+  color: #f5222d;
+  font-size: 24px;
+}
+
+.table {
+  border: 1px solid #fff;
+
+  th {
+    font-weight: normal;
+    padding: 8px 12px;
+  }
+
+  td {
+    padding: 8px 12px;
+  }
+}
+
+.img {
+  width: 200;
+
+  :global {
+    img {
+      width: 100%;
+    }
+  }
+}

+ 108 - 0
src/Project/pages/LimitedSpace/SearchModal.js

@@ -0,0 +1,108 @@
+import React, { useEffect, useState, useRef } from 'react';
+import { Modal, Form, Select, InputNumber, Input } from 'antd';
+const { Option } = Select;
+
+function SearchModal(props) {
+  const { onCancel, onOk, config = [], visible, form } = props;
+
+  const handleOk = () => {
+    form.validateFields((error, values) => {
+      if (error) return;
+      let data = Object.keys(values)
+        .map(k => {
+          let item = {
+            k,
+            v: values[k].v,
+            op: values[k].op,
+          };
+          if (!item.op) delete item.op;
+          return item;
+        })
+        .filter(item => item.v);
+      onOk(data);
+    });
+  };
+
+  const handleCancel = () => {
+    onCancel && onCancel();
+  };
+
+  const renderFormItem = item => {
+    let dom,
+      option = [],
+      rules = [];
+
+    switch (item.query_type) {
+      case 'select':
+        option = item.query_option.split(',');
+        dom = (
+          <Select allowClear>
+            {option.map(item => (
+              <Option key={item}>{item}</Option>
+            ))}
+          </Select>
+        );
+        break;
+      case 'multi-select':
+        option = item.query_option.split(',');
+        dom = (
+          <Select allowClear mode="multiple">
+            {option.map(item => (
+              <Option key={item}>{item}</Option>
+            ))}
+          </Select>
+        );
+      case 'input':
+        if (item.type == 'string') dom = <Input />;
+        else {
+          let addonBefore = form.getFieldDecorator(`${item.en_name}.op`, {
+            initialValue: '=',
+          })(
+            <Select style={{ width: 200 }}>
+              <Option value="=">等于</Option>
+              <Option value=">">大于</Option>
+              <Option value=">=">大于等于</Option>
+              <Option value="<">小于</Option>
+              <Option value="<=">小于等于</Option>
+            </Select>
+          );
+          rules = [
+            {
+              validator: (rule, value, callback) => {
+                if (value && isNaN(value)) callback('请输入数字!');
+                callback();
+              },
+            },
+          ];
+          dom = <Input addonBefore={addonBefore} />;
+        }
+        break;
+    }
+
+    return form.getFieldDecorator(`${item.en_name}.v`, {
+      rules,
+    })(dom);
+  };
+
+  return (
+    <Modal
+      title={'高级搜索'}
+      width={'95%'}
+      visible={visible}
+      onCancel={handleCancel}
+      onOk={handleOk}
+    >
+      <Form labelCol={{ span: 6 }} wrapperCol={{ span: 16 }} layout="horizontal">
+        {config
+          .filter(item => item.is_query)
+          .map(item => (
+            <Form.Item key={item.en_name} label={item.cn_name}>
+              {renderFormItem(item)}
+            </Form.Item>
+          ))}
+      </Form>
+    </Modal>
+  );
+}
+
+export default Form.create()(SearchModal);

+ 131 - 0
src/Project/pages/LimitedSpace/index.js

@@ -0,0 +1,131 @@
+// //总览
+
+// import React, { useState, useEffect } from 'react';
+// import { connect } from 'dva';
+// import { Row, Col, Button } from 'antd';
+// import router from 'umi/router';
+// import { UnityAction, GetTokenFromUrl } from '@/utils/utils';
+// import styles from './LimitedSpace.less';
+// import limitedSpace from './models/limitedSpace';
+
+// const areaList1 = [
+//   { id: 1, project_id: 65, name: '进水组团', group_id: '1_2' },
+//   { id: 2, project_id: 65, name: '预处理车间', group_id: '00_4' },
+//   { id: 3, project_id: 65, name: '机加池', group_id: '1_3' },
+//   { id: 4, project_id: 65, name: '滤池', group_id: '1_1' },
+//   { id: 5, project_id: 65, name: '清水池', group_id: '00_5' },
+//   { id: 6, project_id: 65, name: '脱水机房', group_id: '00_6' },
+//   { id: 7, project_id: 65, name: '污泥车间', group_id: '00_7' },
+//   { id: 8, project_id: 65, name: '膜车间', group_id: '1_0' },
+//   { id: 9, project_id: 65, name: '臭氧接触池', group_id: '00_9' },
+//   { id: 10, project_id: 65, name: '次氯酸钠投加间', group_id: '00_0' },
+//   { id: 11, project_id: 65, name: '厂区雨水', group_id: '02_0' },
+//   { id: 12, project_id: 65, name: '厂区污水', group_id: '03_0' },
+//   { id: 13, project_id: 65, name: '厂区工艺', group_id: '04_0' },
+//   { id: 14, project_id: 65, name: '厂区自用水', group_id: '05_0' },
+//   { id: 15, project_id: 65, name: '厂区电力', group_id: '06_0' },
+//   { id: 16, project_id: 65, name: '厂区加药', group_id: '07_0' },
+// ];
+
+// function Overview(props) {
+//   const { dispatch, loading, currentUser, areaList } = props;
+//   const { projectId } = props.match.params;
+//   const [currentArea, setCurrentArea] = useState(null);
+
+//   useEffect(() => {
+//     document.body.style.background = 'rgba(13, 26, 43, 0.6)';
+
+//     dispatch({
+//       type: 'limitedSpace/queryArea',
+//       payload: { project_id: projectId },
+//     });
+
+//     return () => {
+//       document.body.style.background = '#0d1a2b';
+//     };
+//   }, []);
+
+//   const onClick = item => {
+//     setCurrentArea(item);
+//     UnityAction.sendMsg('AreaSelectFromWeb', JSON.stringify(item));
+//     router.push(
+//       `/unity/limitedSpace/list/${projectId}?JWT-TOKEN=${GetTokenFromUrl()}&group_id=${
+//         item.group_id
+//       }`
+//     );
+//   };
+
+//   const onDoubleClick = item => {
+//     // UnityAction.sendMsg('xxx', xxx);
+//   };
+
+//   const getAreaBackground = id => {
+//     switch (id) {
+//       case 1:
+//         return require('@/assets/LimitedSpace/进水组团.png');
+//       case 2:
+//         return require('@/assets/LimitedSpace/预处理.png');
+//       case 3:
+//         return require('@/assets/LimitedSpace/机加池.png');
+//       case 4:
+//         return require('@/assets/LimitedSpace/滤池.png');
+//       case 5:
+//         return require('@/assets/LimitedSpace/清水池.png');
+//       case 6:
+//         return require('@/assets/LimitedSpace/脱水机房.png');
+//       case 7:
+//         return require('@/assets/LimitedSpace/污泥车间.png');
+//       case 8:
+//         return require('@/assets/LimitedSpace/膜车间.png');
+//       case 9:
+//         return require('@/assets/LimitedSpace/配水泵房.png');
+//       case 10:
+//         return require('@/assets/LimitedSpace/次氯酸钠投加间.png');
+//       default:
+//         return require('@/assets/LimitedSpace/厂区.png');
+//     }
+//   };
+
+//   return (
+//     <div
+//       className={styles.page}
+//       style={{
+//         backgroundSize: '100% 100%',
+//         backgroundImage: `url(${require('@/assets/LimitedSpace/页面背景.png')}`,
+//       }}
+//     >
+//       <div
+//         className={styles.title}
+//         style={{ backgroundImage: `url(${require('@/assets/LimitedSpace/标题背景.png')})` }}
+//       >
+//         总览
+//       </div>
+//       <div className={styles.areaBtn}>
+//         {/* {areaList1.map(item => ( */}
+//         {areaList.map(item => (
+//           <div
+//             className={item == currentArea ? styles.selectedArea : styles.area}
+//             onClick={() => onClick(item)}
+//             onDoubleClick={() => onDoubleClick(item)}
+//             // style={{ backgroundImage: `url(${getAreaBackground(item.id)})` }}
+//             style = {{backgroundImage: `url(${item.image})`}}
+//           >
+//             <div
+//               className={styles.nameBackground}
+//               style={{ backgroundImage: `url(${require('@/assets/LimitedSpace/资源2_去边.png')})` }}
+//             >
+//               <div className={styles.areaName}>{item.name}</div>
+//             </div>
+//           </div>
+//         ))}
+//       </div>
+//     </div>
+//   );
+// }
+
+// export default connect(({ limitedSpace, user, loading }) => ({
+//   currentUser: user.currentUser,
+//   loading: loading.models.limitedSpace,
+//   areaList: limitedSpace.areaList,
+// }))(Overview);
+export default () => <div>哈哈哈哈</div>

+ 381 - 0
src/Project/pages/LimitedSpace/models/limitedSpace.js

@@ -0,0 +1,381 @@
+import {
+  queryArea,
+  queryConfig,
+  querySpace,
+  updateSpace,
+  createSpace,
+  queryDataConfig,
+  queryData,
+  queryPlcData,
+  createOperation,
+  updateOperation,
+  deleteOperation,
+  queryOperation,
+  queryOperationDetail,
+  querySpaceDetail,
+  queryDownloadOperation,
+  queryRescue,
+  queryRescueDetail,
+  createRescue,
+  deleteRescue,
+  queryDownloadRescue,
+  queryDownloadCard,
+} from '@/services/limitedSpace';
+import { getRealtimeData } from '@/services/DeviceInfo';
+import { message } from 'antd';
+import res from '@/pages/SysAdmin/AuthorityAdmin/models/res';
+
+export default {
+  namespace: 'limitedSpace',
+  state: {
+    areaList: [],
+    space: { list: [], pagination: {}, statistics: {} },
+    config: [],
+    dataConfig: [],
+    spaceData: { list: [], pagination: {} },
+    operation: { list: [], pagination: {} },
+    opDetail: { op: {}, space: {} },
+    spaceDetail: {},
+    rescue: { list: [], pagination: {} },
+    rescueDetail: {},
+  },
+
+  effects: {
+    *queryArea({ payload, callback }, { call, put }) {
+      const res = yield call(queryArea, payload);
+      if (!res) return;
+      yield put({ type: 'save', payload: { areaList: res.data } });
+    },
+
+    *queryConfig({ payload, callback }, { call, put }) {
+      const res = yield call(queryConfig, payload);
+      if (!res) return;
+      yield put({
+        type: 'save',
+        payload: { config: res.data },
+      });
+    },
+
+    *querySpace({ payload, callback }, { call, put }) {
+      const res = yield call(querySpace, payload);
+      if (!res) return;
+      let spaceList = res.data.list;
+      const { current, pageSize } = res.data.pagination;
+      for (let i = 0; i < spaceList.length; ++i) {
+        // spaceList[i] = { ...spaceList[i], spaceId: i * res.data.pagination.current + 1 };
+        spaceList[i] = { ...spaceList[i], spaceId: i + 1 + (current - 1) * pageSize };
+      }
+      callback && callback(spaceList);
+      yield put({
+        type: 'save',
+        payload: { space: { ...res.data, list: spaceList } },
+      });
+    },
+
+    *createSpace({ payload, callback }, { call, put }) {
+      const { id } = yield call(createSpace, payload);
+      if (!id) return;
+      callback && callback();
+      message.success('添加成功');
+      const { project_id } = payload;
+      yield put({
+        type: 'queryList',
+        payload: { project_id: project_id },
+      });
+    },
+
+    *updateSpace({ payload, callback }, { call, put }) {
+      const { id } = yield call(updateSpace, payload);
+      if (!id) return;
+      callback && callback();
+      message.success('编辑成功');
+      const { project_id } = payload;
+      yield put({
+        type: 'queryList',
+        payload: { project_id: project_id },
+      });
+    },
+
+    *queryDataConfig({ payload, callback }, { call, put }) {
+      const res = yield call(queryDataConfig, payload);
+      if (!res) return;
+      yield put({
+        type: 'save',
+        payload: { dataConfig: res.data },
+      });
+    },
+
+    *queryData({ payload, callback }, { call, put }) {
+      let res = yield call(queryDataConfig, payload);
+      if (!res) return;
+      let configList = res.data; //判断条件
+      yield put({
+        type: 'save',
+        payload: { dataConfig: configList },
+      });
+
+      res = yield call(queryData, payload);
+      if (!res) return;
+      let dataList = res.data.list; //plc数据
+
+      let realDataParams = [];
+      let dataKeys = configList.map(item => item.item_name);
+      dataList.forEach(item => {
+        dataKeys.forEach(key => {
+          if (item[key] && item[key] != '-') {
+            let [deviceId, deviceItems] = item[key].split(',');
+            item[key] = {
+              deviceId,
+              deviceItems,
+              project_id: Number(payload.project_id),
+            };
+            realDataParams.push(item[key]);
+          } else {
+            // 没有数据的置为空
+            item[key] = '';
+          }
+        });
+      });
+      const { data: realData } = yield call(getRealtimeData, realDataParams);
+      dataList.forEach(item => {
+        item.statusObj = {}; // 状态对象 {data1: 3, data2: 3, data3: 2, data4: 1}
+        dataKeys.forEach(keys => {
+          if (item[keys]) {
+            let config = configList.find(t => t.item_name == keys);
+            let { deviceId, deviceItems } = item[keys];
+            let realValue = realData.find(
+              item => item.devid == deviceId && item.itemname == deviceItems
+            );
+            let singleStatus = 0;
+            let x = realValue.val;
+            if (config.threshold && eval(config.threshold)) singleStatus = 1; //安全
+            if (config.notice_threshold && eval(config.notice_threshold)) singleStatus = 2; //预警
+            if (config.warning_threshold && eval(config.warning_threshold)) singleStatus = 3; //报警
+            item.statusObj[keys] = singleStatus;
+            item[keys] = realValue.val;
+            // item[keys] = `${realValue.val},${status}`;
+          } else {
+            item[keys] = 0;
+            //无数据
+            item.statusObj[keys] = 0;
+          }
+        });
+      });
+
+      const { current, pageSize } = res.data.pagination;
+      for (let i = 0; i < dataList.length; ++i) {
+        dataList[i] = { ...dataList[i], spaceId: i + 1 + (current - 1) * pageSize };
+      }
+      yield put({
+        type: 'save',
+        payload: { spaceData: { list: dataList, pagination: res.data.pagination } },
+      });
+    },
+
+    *createOperation({ payload, callback }, { call, put }) {
+      const res = yield call(createOperation, payload);
+      if (!res) return;
+      callback && callback();
+      message.success('提交成功');
+      const project_id = payload.project_id;
+      yield put({
+        type: 'queryOperation',
+        payload: { project_id: project_id },
+      });
+    },
+
+    *updateOperation({ payload, callback }, { call, put }) {
+      const res = yield call(updateOperation, payload);
+      if (!res) return;
+      callback && callback();
+      message.success('提交成功');
+      const project_id = payload.project_id;
+      yield put({
+        type: 'queryOperation',
+        payload: { project_id: project_id },
+      });
+    },
+
+    *deleteOperation({ payload, callback }, { call, put }) {
+      const { id, project_id } = payload;
+      const res = yield call(deleteOperation, { id: id });
+      if (!res) return;
+      callback && callback();
+      message.success('删除成功');
+      yield put({
+        type: 'queryOperation',
+        payload: { project_id: project_id },
+      });
+    },
+
+    *queryOperation({ payload, callback }, { call, put }) {
+      const res = yield call(queryOperation, payload);
+      if (!res) return;
+      let opList = res.data.list;
+      const { current, pageSize } = res.data.pagination;
+      for (let i = 0; i < opList.length; ++i) {
+        opList[i] = { ...opList[i], opId: i + 1 + (current - 1) * pageSize };
+      }
+      callback?.(opList);
+      yield put({
+        type: 'save',
+        payload: { operation: res.data },
+      });
+    },
+
+    *queryOpDetail({ payload, callback }, { call, put }) {
+      const res = yield call(queryOperationDetail, payload);
+      if (!res) return;
+      let data = res.data;
+      let space = data.space;
+      Object.keys(space).forEach(key => {
+        if (key.includes('_pic')) {
+          try {
+            space[key] = JSON.parse(space[key] || '[]');
+          } catch (error) {
+            space[key] = [];
+            console.error(error);
+          }
+        }
+      });
+      callback && callback(data);
+      yield put({
+        type: 'save',
+        payload: { opDetail: data },
+      });
+    },
+
+    *querySpaceDetail({ payload, callback }, { call, put }) {
+      const res = yield call(querySpaceDetail, payload);
+      if (!res) return;
+      // let data = res.data;
+      // let space = data.space;
+      let space = res.data;
+      Object.keys(space).forEach(key => {
+        if (key.includes('_pic')) {
+          try {
+            space[key] = JSON.parse(space[key] || '[]');
+          } catch (error) {
+            space[key] = [];
+            console.error(error);
+          }
+        }
+      });
+      // callback && callback(data);
+      callback && callback(space);
+      yield put({
+        type: 'save',
+        payload: { spaceDetail: space },
+      });
+    },
+
+    *querySpacesDetail({ payload, callback }, { call, put }) {
+      let res,
+        list = [];
+      for (let i = 0; i < payload.selectedSpace.length; i++) {
+        res = yield call(querySpaceDetail, { id: payload.selectedSpace[i] });
+        if (!res) return;
+        let space = res.data;
+        Object.keys(space).forEach(key => {
+          if (key.includes('_pic')) {
+            try {
+              space[key] = JSON.parse(space[key] || '[]');
+            } catch (error) {
+              space[key] = [];
+              console.error(error);
+            }
+          }
+        });
+        list.push(space);
+      }
+      callback && callback(list);
+    },
+
+    *queryDownloadOperation({ payload, callback }, { call, put }) {
+      const res = yield call(queryDownloadOperation, payload);
+      if (!res) return;
+      callback && callback(res.data);
+    },
+
+    *queryRescue({ payload, callback }, { call, put }) {
+      const res = yield call(queryRescue, payload);
+      if (!res) return;
+      let saveList = res.data.list;
+      const { current, pageSize } = res.data.pagination;
+      for (let i = 0; i < saveList.length; ++i) {
+        saveList[i] = { ...saveList[i], saveId: i + 1 + (current - 1) * pageSize };
+      }
+      callback?.(saveList);
+      yield put({
+        type: 'save',
+        payload: { rescue: res.data },
+      });
+    },
+
+    *queryRescueDetail({ payload, callback }, { call, put }) {
+      const res = yield call(queryRescueDetail, payload);
+      if (!res) return;
+      let space = res.data;
+      Object.keys(space).forEach(key => {
+        if (key.includes('_pic')) {
+          try {
+            space[key] = JSON.parse(space[key] || '[]');
+          } catch (error) {
+            space[key] = [];
+            console.error(error);
+          }
+        }
+      });
+      callback && callback(res.data);
+      yield put({
+        type: 'save',
+        payload: { rescueDetail: res.data },
+      });
+    },
+
+    *createRescue({ payload, callback }, { call, put }) {
+      const res = yield call(createRescue, payload);
+      if (!res) return;
+      callback && callback();
+      message.success('提交成功');
+      const project_id = payload.project_id;
+      yield put({
+        type: 'queryRescue',
+        payload: { project_id: project_id },
+      });
+    },
+
+    *deleteRescue({ payload, callback }, { call, put }) {
+      const { id, project_id } = payload;
+      const res = yield call(deleteRescue, { id: id });
+      if (!res) return;
+      callback && callback();
+      message.success('删除成功');
+      yield put({
+        type: 'queryRescue',
+        payload: { project_id: project_id },
+      });
+    },
+
+    *queryDownloadRescue({ payload, callback }, { call, put }) {
+      const res = yield call(queryDownloadRescue, payload);
+      if (!res) return;
+      callback && callback(res.data);
+    },
+
+    *queryDownloadCard({ payload, callback }, { call, put }) {
+      const res = yield call(queryDownloadCard, payload);
+      if (!res) return;
+      callback && callback(res.data);
+    },
+  },
+
+  reducers: {
+    save(state, action) {
+      return {
+        ...state,
+        ...action.payload,
+      };
+    },
+  },
+};

+ 106 - 1
src/Project/pages/global.less

@@ -2,7 +2,112 @@
   box-sizing: border-box;
 }
 
-// @import '~antd/lib/style/themes/default.less';
+body,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+hr,
+p,
+blockquote,
+dl,
+dt,
+dd,
+ul,
+ol,
+li,
+pre,
+form,
+fieldset,
+legend,
+button,
+input,
+textarea,
+th,
+td {
+  margin: 0;
+  padding: 0;
+}
+
+body,
+button,
+input,
+select,
+textarea {
+  font: 12px/1.5tahoma, arial, \5b8b\4f53;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+  font-size: 100%;
+}
+
+address,
+cite,
+dfn,
+em,
+var {
+  font-style: normal;
+}
+
+code,
+kbd,
+pre,
+samp {
+  font-family: couriernew, courier, monospace;
+}
+
+small {
+  font-size: 12px;
+}
+
+ul,
+ol {
+  list-style: none;
+}
+
+a {
+  text-decoration: none;
+}
+
+a:hover {
+  text-decoration: underline;
+}
+
+sup {
+  vertical-align: text-top;
+}
+
+sub {
+  vertical-align: text-bottom;
+}
+
+legend {
+  color: #000;
+}
+
+fieldset,
+img {
+  border: 0;
+}
+
+button,
+input,
+select,
+textarea {
+  font-size: 100%;
+}
+
+table {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
 
 .content {
   padding-top: 38px;

+ 1 - 0
src/Project/pages/home.tsx

@@ -6,6 +6,7 @@ import Os from '../Os';
 
 window.GT_APP = new Os();
 window.GT_APP.funcMain.setActive(true);
+// window.GT_APP.funcLimitedSpace.setActive(true);
 
 const Home: React.FC = (props: any) => {
   const {

+ 76 - 37
src/Project/services/DataMeter.ts

@@ -6,12 +6,25 @@ import {
 
 // 获得项目详情
 export async function getProject(projectId: string | number) {
-  return request(`/project/${projectId}`);
+  return request(`/api/v1/project/${projectId}`);
 }
 
 // 异常报警通知列表
 export async function getFaultAnalysis(projectId: string | number) {
-  return request(`/fault_analysis/result/${projectId}`);
+  const response: any = await request(
+    `/api/v1/fault_analysis/result/${projectId}`,
+  );
+  var list: any = [];
+  response.data.forEach((item: any) => {
+    item.Details.forEach((err: any) => {
+      list.push({
+        ...item,
+        ...err,
+      });
+    });
+  });
+  response.data = list;
+  return response;
 }
 
 // 项目预警
@@ -22,7 +35,7 @@ interface IProjectAlarmParams {
   projectId: number | string;
 }
 export async function getProjectAlarm(params: IProjectAlarmParams) {
-  return request(`/notification/list`, {
+  return request(`/api/v1/notification/list`, {
     params: params,
   });
 }
@@ -36,8 +49,8 @@ interface IIssueListParams {
   projectId: number | string;
 }
 export async function getIssueList(params: IIssueListParams) {
-  // return request(`/issue/list/${params.projectId}`);
-  return request(`/issue/ticket/list/${params.projectId}`, {
+  // return request(`/api/v1/issue/list/${params.projectId}`);
+  return request(`/api/v1/issue/ticket/list/${params.projectId}`, {
     params: params,
   });
 }
@@ -46,11 +59,13 @@ export async function getIssueList(params: IIssueListParams) {
  * 消息推送
  */
 interface INotificationListParams {
-  currentPage: number;
-  pageSize: number;
+  currentPage?: number;
+  pageSize?: number;
+  projectId: number | string;
+  msgType: number;
 }
 export async function getNotificationList(params: INotificationListParams) {
-  return request(`/notification/list?`, {
+  return request(`/api/v1/notification/list?`, {
     params: params,
   });
 }
@@ -64,12 +79,15 @@ interface IuploadMediaParams {
   files: any[];
 }
 export async function uploadMedia(params: IuploadMediaParams) {
-  return request(`/project-cabin-file/${params.projectId}/${params.type}`, {
-    method: 'POST',
-    body: {
-      ...params,
+  return request(
+    `/api/v1/project-cabin-file/${params.projectId}/${params.type}`,
+    {
+      method: 'POST',
+      body: {
+        ...params,
+      },
     },
-  });
+  );
 }
 
 /**
@@ -79,7 +97,7 @@ export async function uploadMedia(params: IuploadMediaParams) {
  * @returns
  */
 export async function getMediaList(params: any) {
-  return request(`/project-file/${params.projectId}/26/-1`);
+  return request(`/api/v1/project-file/${params.projectId}/26/-1`);
 }
 
 /**
@@ -92,8 +110,10 @@ export async function getMediaList(params: any) {
  */
 
 export async function getMonitorList(projectId: string | number): Promise<any> {
-  // return request(`/monitor/list/${params.projectId}`);
-  let data: Api.IMonitor = await request(`/monitor_config/query/${projectId}`);
+  // return request(`/api/v1/monitor/list/${params.projectId}`);
+  let data: Api.IMonitor = await request(
+    `/api/v1/monitor_config/query/${projectId}`,
+  );
   let obj: any;
   try {
     obj = JSON.parse(data.config_json) || {};
@@ -114,9 +134,12 @@ export async function getMonitorList(projectId: string | number): Promise<any> {
  */
 
 export async function getProjectActive(params: any) {
-  return request(`/project-active/list/${params.projectId}/${params.type}}`, {
-    params: params,
-  });
+  return request(
+    `/api/v1/project-active/list/${params.projectId}/${params.type}`,
+    {
+      params: params,
+    },
+  );
 }
 
 /**
@@ -127,7 +150,7 @@ export async function getProjectActive(params: any) {
  */
 
 export async function getProjectProgress(params: any) {
-  return request(`/project-progress/${params.projectId}`);
+  return request(`/api/v1/project-progress/${params.projectId}`);
 }
 
 /**
@@ -138,7 +161,7 @@ export async function getProjectProgress(params: any) {
  */
 
 export async function getProjectTotalProgress(params: any) {
-  return request(`/project-plan-progress/${params.projectId}`);
+  return request(`/api/v1/project-plan-progress/${params.projectId}`);
 }
 
 /**
@@ -151,7 +174,7 @@ export async function getProjectTotalProgress(params: any) {
  */
 
 export async function getUserGuide(params: any) {
-  return request(`/user_guide/list/${params.projectId}`, {
+  return request(`/api/v1/user_guide/list/${params.projectId}`, {
     params: params,
   });
 }
@@ -166,7 +189,7 @@ export async function getUserGuide(params: any) {
  */
 
 export async function getDailyList(params: any) {
-  return request(`/daily/list/${params.projectId}`, {
+  return request(`/api/v1/daily/list/${params.projectId}`, {
     params: params,
   });
 }
@@ -179,7 +202,7 @@ export async function getDailyList(params: any) {
  */
 
 export async function deleteFile(params: any) {
-  return request(`/delete/project-cabin-file/${params.id}`, {
+  return request(`/api/v1/delete/project-cabin-file/${params.id}`, {
     method: 'POST',
   });
 }
@@ -237,7 +260,7 @@ export async function getLayoutOptions(params: any) {
 
 // 获取故障列表
 export async function getBreakdownRecord(params: any) {
-  return request(`/breakdown-record/unhandled/list/${params.projectId}`);
+  return request(`/api/v1/breakdown-record/unhandled/list/${params.projectId}`);
 }
 
 //获取项目列表
@@ -260,33 +283,49 @@ export async function getProjectList(params: any) {
 
 //获取巡检结果
 export async function getAutoPatrol(params: any) {
-  return request(`/patrol/auto/data/${params.projectId}`);
+  return request(`/api/v1/patrol/auto/data/${params.projectId}`);
 }
 
 export async function getBreakdownList(params: any) {
-  return request(`/breakdown-record/list/${params.projectId}`, {
+  return request(`/api/v1/breakdown-record/list/${params.projectId}`, {
     params: params,
   });
 }
 export async function getPatrolRecord(params: any) {
-  return request(`/patrol/data/${params.projectId}`, {
+  return request(`/api/v1/patrol/data/${params.projectId}`, {
     params: params,
   });
 }
 
 export async function getMouldList(params: any) {
-  return request(`/project_config/template-list/${params.project_id || 0}`, {
-    params: params,
-  });
+  return request(
+    `/api/v1/project_config/template-list/${params.project_id || 0}`,
+    {
+      params: params,
+    },
+  );
 }
 
 export async function createMouldList(params: any) {
-  return request(`/project_config/save-template/${params.projectId || 0}`, {
-    method: 'POST',
-    headers: { ContentType: 'application/x-www-form-urlencoded' },
-    body: params.formData,
-  });
+  return request(
+    `/api/v1/project_config/save-template/${params.projectId || 0}`,
+    {
+      method: 'POST',
+      headers: { ContentType: 'application/x-www-form-urlencoded' },
+      body: params.formData,
+    },
+  );
 }
 export async function getAutoPatrolByRouteId(params: any) {
-  return request(`/patrol/data/${params.projectId}/${params.routeId}`);
+  return request(`/api/v1/patrol/data/${params.projectId}/${params.routeId}`);
+}
+
+export async function queryConfigList(params: any) {
+  return request(`/chart/sync/info/${params.projectId}`, {
+    params: params,
+  });
 }
+
+export async function queryTemplateList() {
+  return request(`/chart/list?pageSize=999`);
+}