xjj 2 жил өмнө
parent
commit
4fbbcb2d08
56 өөрчлөгдсөн 3959 нэмэгдсэн , 24 устгасан
  1. 2 1
      .umirc.ts
  2. 4 0
      package.json
  3. 29 0
      src/Project/Functions/LevelAFunctions/FuncDataMeter.ts
  4. 7 1
      src/Project/Functions/LevelAFunctions/FuncProjectSelection.ts
  5. BIN
      src/Project/assets/dataMeter/ProjectList/arrow.png
  6. BIN
      src/Project/assets/dataMeter/ProjectList/enterBtnBG.png
  7. BIN
      src/Project/assets/dataMeter/ProjectList/icons8-search-48.png
  8. BIN
      src/Project/assets/dataMeter/ProjectList/listBG.png
  9. BIN
      src/Project/assets/dataMeter/ProjectList/nodata.png
  10. BIN
      src/Project/assets/dataMeter/ProjectList/search.png
  11. BIN
      src/Project/assets/dataMeter/ProjectList/search_Icon.png
  12. BIN
      src/Project/assets/dataMeter/ProjectList/search_bg.png
  13. BIN
      src/Project/assets/dataMeter/ProjectList/selected.png
  14. BIN
      src/Project/assets/dataMeter/arrowIcon.png
  15. BIN
      src/Project/assets/dataMeter/conCenter.png
  16. BIN
      src/Project/assets/dataMeter/conLeft.png
  17. BIN
      src/Project/assets/dataMeter/conRight.png
  18. BIN
      src/Project/assets/dataMeter/contentBg.png
  19. BIN
      src/Project/assets/dataMeter/dataMeterBg.png
  20. BIN
      src/Project/assets/dataMeter/drawLeftBg.png
  21. BIN
      src/Project/assets/dataMeter/full-screen.png
  22. BIN
      src/Project/assets/dataMeter/headerLeft.png
  23. BIN
      src/Project/assets/dataMeter/headerRight.png
  24. BIN
      src/Project/assets/dataMeter/icon-close.png
  25. BIN
      src/Project/assets/dataMeter/icon-edit.png
  26. BIN
      src/Project/assets/dataMeter/icon-upload.png
  27. BIN
      src/Project/assets/dataMeter/itemBg.png
  28. BIN
      src/Project/assets/dataMeter/map-bg.png
  29. BIN
      src/Project/assets/dataMeter/map-icon1.png
  30. BIN
      src/Project/assets/dataMeter/map-icon2.png
  31. BIN
      src/Project/assets/dataMeter/map-icon3.png
  32. BIN
      src/Project/assets/dataMeter/map-icon4.png
  33. BIN
      src/Project/assets/dataMeter/mouldListBG.png
  34. BIN
      src/Project/assets/dataMeter/page-bg.png
  35. BIN
      src/Project/assets/dataMeter/personnel.png
  36. BIN
      src/Project/assets/dataMeter/rotateSwitch.png
  37. BIN
      src/Project/assets/dataMeter/time.png
  38. BIN
      src/Project/assets/dataMeter/title-icon.png
  39. BIN
      src/Project/assets/dataMeter/title-icon2.png
  40. 2 1
      src/Project/constants/index.ts
  41. 125 0
      src/Project/pages/DataMeter/Model/ChartBox/index.less
  42. 87 0
      src/Project/pages/DataMeter/Model/ChartBox/index.tsx
  43. 464 0
      src/Project/pages/DataMeter/Model/Map.tsx
  44. 133 0
      src/Project/pages/DataMeter/Model/ProjectInfo.tsx
  45. 219 0
      src/Project/pages/DataMeter/Model/ProjectList.less
  46. 253 0
      src/Project/pages/DataMeter/Model/ProjectList.tsx
  47. 53 0
      src/Project/pages/DataMeter/Model/index.tsx
  48. 977 0
      src/Project/pages/DataMeter/config.ts
  49. 1089 0
      src/Project/pages/DataMeter/index.less
  50. 132 0
      src/Project/pages/DataMeter/index.tsx
  51. 53 0
      src/Project/pages/DataMeter/typings.d.ts
  52. 8 20
      src/Project/pages/ProjectSelect/index.js
  53. 234 0
      src/Project/services/DataMeter.js
  54. 4 0
      src/Project/services/typings.d.ts
  55. 12 0
      src/models/project.ts
  56. 72 1
      yarn.lock

+ 2 - 1
.umirc.ts

@@ -24,6 +24,7 @@ export default defineConfig({
       target: 'http://47.96.12.136:8788/',
       // target: 'http://47.96.12.136:8888/',
       // target: 'http://120.55.44.4:8900/',
+      changeOrigin: true,
     },
   },
-});
+});

+ 4 - 0
package.json

@@ -13,10 +13,14 @@
   "dependencies": {
     "@ant-design/icons": "^4.7.0",
     "@ant-design/pro-components": "^2.0.1",
+    "@types/react-grid-layout": "^1.3.2",
     "@umijs/max": "^4.0.41",
     "antd": "^5.0.0",
+    "echarts": "^5.4.1",
     "pinyin-match": "^1.2.2",
+    "react-grid-layout": "^1.2.5",
     "react-id-swiper": "^2.4.0",
+    "react-zmage": "^0.8.5-beta.37",
     "swiper": "^5.4.5"
   },
   "devDependencies": {

+ 29 - 0
src/Project/Functions/LevelAFunctions/FuncDataMeter.ts

@@ -0,0 +1,29 @@
+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 FuncDataMeterState {
+  idle,
+}
+
+export default class FuncDataMeter extends Func<FuncDataMeterState> {
+  constructor(name: string) {
+    super(name);
+    super.initStates((sm) => {
+      sm.addState(
+        FuncDataMeterState.idle,
+        this.onIdleStateIn,
+        null,
+        this.onIdleStateExit,
+      );
+    });
+  }
+  onIdleStateIn(): void {
+    SysPage.add(PAGE_KEY.DataMeter);
+  }
+  onIdleStateExit(): void {
+    SysPage.removeByKey(PAGE_KEY.DataMeter);
+  }
+}

+ 7 - 1
src/Project/Functions/LevelAFunctions/FuncProjectSelection.ts

@@ -18,7 +18,13 @@ export default class FuncProjectSelection extends Func<FuncProjectSelectionState
     });
   }
   onIdleStateIn(): void {
-    SysPage.add(PAGE_KEY.ProjectSelection);
+    const selectProject = (projcet: Api.IProject) => {
+      console.log(projcet);
+    };
+    SysPage.add(PAGE_KEY.ProjectSelection, {
+      selectProject,
+      subModule: 1,
+    });
   }
   onIdleStateExit(): void {
     SysPage.removeByKey(PAGE_KEY.ProjectSelection);

BIN
src/Project/assets/dataMeter/ProjectList/arrow.png


BIN
src/Project/assets/dataMeter/ProjectList/enterBtnBG.png


BIN
src/Project/assets/dataMeter/ProjectList/icons8-search-48.png


BIN
src/Project/assets/dataMeter/ProjectList/listBG.png


BIN
src/Project/assets/dataMeter/ProjectList/nodata.png


BIN
src/Project/assets/dataMeter/ProjectList/search.png


BIN
src/Project/assets/dataMeter/ProjectList/search_Icon.png


BIN
src/Project/assets/dataMeter/ProjectList/search_bg.png


BIN
src/Project/assets/dataMeter/ProjectList/selected.png


BIN
src/Project/assets/dataMeter/arrowIcon.png


BIN
src/Project/assets/dataMeter/conCenter.png


BIN
src/Project/assets/dataMeter/conLeft.png


BIN
src/Project/assets/dataMeter/conRight.png


BIN
src/Project/assets/dataMeter/contentBg.png


BIN
src/Project/assets/dataMeter/dataMeterBg.png


BIN
src/Project/assets/dataMeter/drawLeftBg.png


BIN
src/Project/assets/dataMeter/full-screen.png


BIN
src/Project/assets/dataMeter/headerLeft.png


BIN
src/Project/assets/dataMeter/headerRight.png


BIN
src/Project/assets/dataMeter/icon-close.png


BIN
src/Project/assets/dataMeter/icon-edit.png


BIN
src/Project/assets/dataMeter/icon-upload.png


BIN
src/Project/assets/dataMeter/itemBg.png


BIN
src/Project/assets/dataMeter/map-bg.png


BIN
src/Project/assets/dataMeter/map-icon1.png


BIN
src/Project/assets/dataMeter/map-icon2.png


BIN
src/Project/assets/dataMeter/map-icon3.png


BIN
src/Project/assets/dataMeter/map-icon4.png


BIN
src/Project/assets/dataMeter/mouldListBG.png


BIN
src/Project/assets/dataMeter/page-bg.png


BIN
src/Project/assets/dataMeter/personnel.png


BIN
src/Project/assets/dataMeter/rotateSwitch.png


BIN
src/Project/assets/dataMeter/time.png


BIN
src/Project/assets/dataMeter/title-icon.png


BIN
src/Project/assets/dataMeter/title-icon2.png


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

@@ -3,7 +3,8 @@ export enum PAGE_KEY {
   Access,
   Login,
   PlatformMenu,
-  ProjectSelection
+  ProjectSelection,
+  DataMeter
 }
 
 export const BuildNodeCode = 'func-01-build'

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

@@ -0,0 +1,125 @@
+.main {
+  padding: 10px 14px 0;
+  width: 100%;
+  height: 40px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  position: relative;
+
+  .titleImg {
+    width: 4px;
+    height: 30px;
+    margin-right: 10px;
+  }
+  .titleBg {
+    width: 100%;
+    height: 46.5px;
+  }
+
+  .title {
+    font-size: 20px;
+    // font-family: Source Han Sans CN-Medium, Source Han Sans CN;
+    font-weight: 500;
+    color: #C9EFFF;
+    line-height: 20px;
+    letter-spacing: 2px;
+  }
+  .icon{
+    width: 20px;
+    height: 20px;
+    pointer-events: initial;
+  }
+
+  .titleBorder{
+    position: absolute;
+    top: 40px;
+    left: 13px;
+    width: 100%;
+    height: 1px;
+  }
+
+  .arrow {
+    position: absolute;
+    right: 14px;
+    top: 29px;
+    width: 26px;
+    height: 26px;
+    pointer-events: initial;
+    transition: transform 0.2s;
+  }
+
+  .titleBg {
+    height: 47px;
+    width: 100%;
+    background: url('@/assets/chartBox/title-bg.png');
+    background-size: 100% 100%;
+  }
+  .titleBgMini {
+    background-image: url('@/assets/chartBox/title-bg-mini.png');
+  }
+
+  // .titleImg {
+  //   display: block;
+  //   float: left;
+  //   height: 100%;
+  // }
+
+  // .titleBar02 {
+  //   // flex: 132 1 auto;
+  //   float: left;
+  //   height: 100%;
+  //   --shf: 4.9406564584124654e-322;
+  //   width: calc(((100% - 214px) / 479) * 132);
+  //   background: url('@/assets/chartBox/titleBar02.png') no-repeat center;
+  //   background-size: 100% 100%;
+  // }
+
+  // .titleBar04 {
+  //   float: left;
+  //   height: 100%;
+  //   // flex: 215 1 auto;
+  //   width: calc(((100% - 214px) / 479) * 215);
+  //   background: url('@/assets/chartBox/titleBar04.png') no-repeat center;
+  //   background-size: 100% 100%;
+  // }
+
+  // .titleBar06 {
+  //   float: left;
+  //   height: 100%;
+  //   // flex: 132 1 auto;
+  //   width: calc(((100% - 214px) / 479) * 132);
+  //   background: url('@/assets/chartBox/titleBar06.png') no-repeat center;
+  //   background-size: 100% 100%;
+  // }
+}
+
+.jiao {
+  position: absolute;
+  width: 10px;
+  height: 11.11px
+}
+
+.jiao1 {
+  .jiao;
+  top: -1px;
+  left: -1px;
+}
+
+.jiao2 {
+  .jiao;
+  top: -1px;
+  right: -1px;
+}
+
+.jiao3 {
+  .jiao;
+  bottom: -1px;
+  left: -1px;
+}
+
+.jiao4 {
+  .jiao;
+  bottom: -1px;
+  right: -1px;
+}

+ 87 - 0
src/Project/pages/DataMeter/Model/ChartBox/index.tsx

@@ -0,0 +1,87 @@
+import React, { useMemo, useState } from 'react';
+import Styles from './index.less';
+
+export const ChartBoxTitle1 = (props:DataMeter.ITitleProps) => {
+  const { title, showTabs, setShowTabs, width = 0 } = props;
+  const [rotate, setRotate] = useState('');
+  const isMini = useMemo(() => {
+    return width < 10;
+  }, [width]);
+  return (
+    <div className={Styles.main}>
+      <div className={`${Styles.titleBg} ${isMini ? Styles.titleBgMini : ''}`}>
+        {/* <img className={Styles.titleImg} src={require('@/assets/chartBox/titleBar01.png')}/>
+        <div className={Styles.titleBar02}/>
+        <img className={Styles.titleImg} src={require('@/assets/chartBox/titleBar03.png')}/>
+        <div className={Styles.titleBar04}/>
+        <img className={Styles.titleImg} src={require('@/assets/chartBox/titleBar05.png')}/>
+        <div className={Styles.titleBar06}/>
+        <img className={Styles.titleImg} src={require('@/assets/chartBox/titleBar07.png')}/> */}
+      </div>
+      <img className={Styles.jiao1} src={require('@/assets/chartBox/jiao1.png')} />
+      <img className={Styles.jiao2} src={require('@/assets/chartBox/jiao2.png')} />
+      {/* <img className={Styles.jiao3} src={require('@/assets/chartBox/jiao4.png')}/>
+      <img className={Styles.jiao4} src={require('@/assets/chartBox/jiao3.png')}/> */}
+      <div className={Styles.title} style={{ top: isMini ? '7px' : '' }}>
+        {title}
+      </div>
+      {setShowTabs && (
+        <img
+          style={{ transform: rotate }}
+          className={Styles.arrow}
+          src={require('@/assets/chartBox/arrow.png')}
+          onClick={() => {
+            setRotate(showTabs ? '' : 'rotate(180deg)');
+            setShowTabs(!showTabs);
+          }}
+        />
+      )}
+    </div>
+  );
+};
+
+export const ChartBoxBottom = () => {
+  return (
+    <>
+      <img className={Styles.jiao3} src={require('@/assets/chartBox/jiao4.png')} />
+      <img className={Styles.jiao4} src={require('@/assets/chartBox/jiao3.png')} />
+    </>
+  );
+};
+
+export const ChartBoxThreeAngle = () => {
+  return (
+    <div className={Styles.main}>
+      <img className={Styles.jiao1} src={require('@/assets/chartBox/jiao1.png')} />
+      <img className={Styles.jiao2} src={require('@/assets/chartBox/jiao2.png')} />
+    </div>
+  );
+};
+
+export const ChartBoxTitle = (props:DataMeter.ITitleProps) => {
+  const { title, showTabs, setShowTabs, width = 0 } = props;
+  const [rotate, setRotate] = useState('');
+  // const isMini = useMemo(() => {
+  //   return width < 10;
+  // }, [width]);
+  return (
+    <div className={Styles.main}>
+      <div className={Styles.titleLeft}>
+        <img className={Styles.titleImg} src={require('@/assets/chartBox/titleBefor.png')} />
+        <span className={Styles.title}>{title}</span>
+      </div>
+      {setShowTabs && (
+        <img
+          // style={{ transform: rotate }}
+          className={Styles.icon}
+          src={require('@/assets/chartBox/titleIcon.png')}
+          onClick={() => {
+            // setRotate(showTabs ? '' : 'rotate(180deg)');
+            setShowTabs(!showTabs);
+          }}
+        />
+      )}
+      <img className={Styles.titleBorder} src={require('@/assets/chartBox/titleBorder.png')} />
+    </div>
+  );
+};

+ 464 - 0
src/Project/pages/DataMeter/Model/Map.tsx

@@ -0,0 +1,464 @@
+import { useModel } from '@umijs/max';
+import React, { useEffect, useRef, useState, MouseEvent } from 'react';
+import ReactZmage from 'react-zmage';
+import { ChartBoxTitle } from './ChartBox';
+import ProjectList from './ProjectList';
+import style from '../index.less';
+import { LocalService, STORAGE_TYPE } from '@/Frameworks/SysStorage';
+import {
+  Alert,
+  Button,
+  Carousel,
+  message,
+  Modal,
+  Result,
+  Tabs,
+  Upload,
+} from 'antd';
+import { UploadOutlined } from '@ant-design/icons';
+import type { RcFile, UploadProps } from 'antd/es/upload';
+import type { UploadFile } from 'antd/es/upload/interface';
+
+const nodata = require('@/Project/assets/dataMeter/ProjectList/nodata.png');
+const token = LocalService.getItem(STORAGE_TYPE.token);
+const { TabPane } = Tabs;
+
+function Map(props: DataMeter.IModelsProps) {
+  const { subModule, isNew, edit, layout, hasModel } = props;
+  if (isNew) {
+    return <ProjectList {...props} />;
+  } else if (subModule != 0 && hasModel) {
+    return <NoModelMap {...props} />;
+  } else {
+    return (
+      <ModelMap
+        {...props}
+        // type={type}
+        // setType={setType}
+      >
+        <div className={style.mapWapper}>
+          <div
+            // ref={mapEle}
+            className={style.mapBox}
+          ></div>
+        </div>
+      </ModelMap>
+    );
+  }
+}
+
+function NoModelMap(props: DataMeter.IModelsProps) {
+  const { layout, edit } = props;
+  const { project: projectDetail } = useModel('project');
+  return (
+    <div
+      style={{
+        display: 'flex',
+        flexDirection: 'column',
+        height: '100%',
+        border: '#2866B2 solid 1px',
+      }}
+    >
+      <ChartBoxTitle title={'数字实景'} width={layout.w} />
+      <div
+        style={{
+          width: '100%',
+          height: '100%',
+          marginTop: 28,
+
+          justifyContent: 'space-between',
+        }}
+      >
+        <div
+          onClick={() => {
+            if (edit) return;
+            // document.getElementsByClassName('reactZmage')[0].click();
+          }}
+          style={{
+            backgroundImage: `url(${projectDetail?.ScreenShot || nodata})`,
+            cursor: 'pointer',
+            width: '100%',
+            height: '100%',
+            // backgroundSize: '100% 100%',
+            backgroundSize: 'contain',
+            backgroundPosition: 'center',
+            backgroundRepeat: 'no-repeat',
+          }}
+        ></div>
+        <ReactZmage
+          controller={{
+            close: true,
+            rotate: true,
+            zoom: false,
+            download: false,
+            flip: false,
+            pagination: false,
+          }}
+          backdrop="rgba(255,255,255,0.5)"
+          src={projectDetail?.ScreenShot || nodata}
+        />
+      </div>
+    </div>
+  );
+}
+
+function ModelMap(props: DataMeter.IModelsProps) {
+  const [visible, setVisible] = useState(false);
+  const [activeKey, setActiveKey] = useState('1');
+  const [imgList, setImgList] = useState<UploadFile[]>([]);
+  // const [videoList, setVideoList] = useState([]);
+  const [projectList, setProjectList] = useState([]);
+  const [type, setType] = useState<number>(1);
+  const [uploadLoading, setUploadLoading] = useState(false);
+  const mapEle = useRef<HTMLDivElement>(null);
+  const videoEle = useRef<HTMLVideoElement>(null);
+  // const token = getToken();
+  const mediaImgList: any[] = [];
+  const mediaVideo = { Url: '' };
+  const loading = false;
+  const {
+    children,
+    // type,
+    // setType,
+    edit,
+    layout,
+  } = props;
+  const {
+    initialState: { projectId },
+  } = useModel('@@initialState');
+
+  const imgProps: UploadProps = {
+    name: 'file',
+    action: `/api/v1/project-cabin-file/${projectId}/0`,
+    headers: {
+      'JWT-TOKEN': token,
+    },
+    fileList: imgList,
+    listType: 'picture',
+    className: style.uploadList,
+    accept: 'image/*',
+    onPreview() {
+      return false;
+    },
+    beforeUpload: (file: RcFile) => {
+      const isLt = file.size / 1024 / 1024 < 1;
+      if (!isLt) {
+        message.error('图片必须小于1 M');
+      } 
+      // else {
+      //   setImgList([...imgList, file]);
+      // }
+      return isLt;
+    },
+    onChange(info) {
+      // if (info.file.status !== 'uploading') {
+      // }
+      // if (info.file.status === 'done') {
+      //   message.success(`文件上传成功`);
+      //   delete info.file.status;
+      //   getMediaList();
+      // } else if (info.file.status === 'error') {
+      //   message.error(`文件上传失败`);
+      // }
+      let newFileList = [...info.fileList];
+
+      // Read from response and show file link
+      newFileList = newFileList.map((file) => {
+        if (file.response) {
+          // Component will show file.url as link
+          file.url = file.response.url;
+        }
+        return file;
+      });
+
+      setImgList(newFileList);
+    },
+    onRemove(file) {
+      // dispatch({
+      //   type: 'dataMeterNew/deleteFile',
+      //   payload: {
+      //     id: file.uid,
+      //     projectId,
+      //   },
+      // }).then(() => {
+      //   message.success(`文件删除成功`);
+      // });
+    },
+  };
+
+  const deleteVideo = () => {
+    Modal.confirm({
+      title: '提醒',
+      content: `确认删除视频?`,
+      okText: '确认',
+      cancelText: '取消',
+      onOk: () => {
+        // dispatch({
+        //   type: 'dataMeterNew/deleteFile',
+        //   payload: {
+        //     id: mediaVideo.ID,
+        //     projectId,
+        //   },
+        // }).then(() => {
+        //   message.success(`文件删除成功`);
+        // });
+      },
+    });
+  };
+
+  const onHandleCancel = () => {
+    if (videoEle.current) {
+      videoEle.current.pause();
+    }
+    setVisible(false);
+    // UnityAction.emit('mouseHandle', 1);
+  };
+
+  const getMediaList = () => {
+    // dispatch({
+    //   type: 'dataMeterNew/getMediaList',
+    //   payload: {
+    //     projectId,
+    //   },
+    // });
+  };
+
+  const sendUpload = (e: MouseEvent<HTMLElement>, info: string) => {
+    // e.stopPropagation();
+    if (info == 'image' && imgList.length >= 10) {
+      message.error('图片不能超过10张');
+      return;
+    }
+    if (info == 'video' && mediaVideo) {
+      message.error('视频只能上传一个');
+      return;
+    }
+    // if (!window.vuplex) return;
+    // window.vuplex.postMessage({
+    //   type: 'uploadFile',
+    //   message: JSON.stringify(info),
+    // });
+    setUploadLoading(true);
+  };
+
+  useEffect(() => {
+    // UnityAction.on('provinceList', (e) => setProjectList(e));
+    // UnityAction.on('uploadState', (e) => {
+    //   // console.log('============================upload response unity========================');
+    //   // console.log(e);
+    //   if (e == 'error') {
+    //     message.error('上传失败');
+    //     getMediaList();
+    //   } else if (e == 'success') {
+    //     message.success('上传成功');
+    //     getMediaList();
+    //   } else {
+    //     message.info('取消上传');
+    //   }
+    //   setUploadLoading(false);
+    // });
+    // return () => {
+    //   UnityAction.off('uploadState');
+    //   UnityAction.off('provinceList');
+    // };
+  }, []);
+
+  useEffect(() => {
+    getMediaList();
+  }, [projectId]);
+
+  // useEffect(() => {
+  //   setImgList(
+  //     (mediaImgList || []).map((item) => ({
+  //       uid: item.ID,
+  //       url: item.Url,
+  //       name: item.Name,
+  //     })),
+  //   );
+  // }, [mediaImgList]);
+
+  let content;
+  if (edit) {
+    const selectList = [
+      {
+        title: '三维模型',
+        key: 1,
+      },
+      {
+        title: '图片展示',
+        key: 3,
+      },
+      {
+        title: '视频展示',
+        key: 2,
+      },
+    ];
+    content = (
+      <>
+        <div
+          className={style.mapControl}
+          onMouseDown={(e) => {
+            e.stopPropagation();
+          }}
+        >
+          <ul className={style.selectList}>
+            {selectList.map((item) => (
+              <li
+                key={item.key}
+                className={`${type == item.key ? style.active : ''}`}
+                onClick={() => {
+                  setActiveKey(item.key == 2 ? '2' : '1');
+                  setType(item.key);
+                }}
+              >
+                {item.title}
+              </li>
+            ))}
+          </ul>
+          <div
+            className={style.iconUpload}
+            style={{ marginRight: 20 }}
+            onClick={() => {
+              setVisible(true);
+              // UnityAction.emit('mouseHandle', 0);
+            }}
+          ></div>
+        </div>
+        <Result
+          status="warning"
+          title={<div style={{ color: '#fff' }}>模型将在编辑完成后显示</div>}
+        />
+      </>
+    );
+  } else {
+    content = (
+      <>
+        {type == 1 && (
+          <>
+            <div
+              style={{
+                display: 'flex',
+                flexDirection: 'column',
+                height: '100%',
+              }}
+            >
+              <ChartBoxTitle title={'数字实景'} width={layout.w} />
+              {children}
+            </div>
+          </>
+        )}
+        {type == 2 && mediaVideo && (
+          <div
+            style={{ width: '100%', height: '100%', backgroundColor: '#000' }}
+          >
+            <video
+              style={{ width: '100%', height: '100%' }}
+              src={mediaVideo.Url}
+              loop
+              autoPlay={true}
+            ></video>
+          </div>
+        )}
+        {type == 3 && (
+          <Carousel autoplay>
+            {(imgList || []).map((item) => (
+              <div key={item.uid} className={style.carousel}>
+                <div
+                  style={{
+                    backgroundImage: `url('${item.url}')`,
+                    height: mapEle.current?.clientHeight,
+                  }}
+                  className={style.carouselItem}
+                ></div>
+              </div>
+            ))}
+          </Carousel>
+        )}
+      </>
+    );
+  }
+
+  return (
+    <div
+      className={`${style.mapContent} ${style.mapBg}`}
+      style={{ overflow: 'hidden' }}
+      ref={mapEle}
+    >
+      {content}
+      <Modal
+        title="上传文件"
+        visible={visible}
+        onOk={onHandleCancel}
+        onCancel={onHandleCancel}
+        cancelButtonProps={{ style: { display: 'none' } }}
+        style={{ minHeight: 400 }}
+        wrapClassName={style.uploadModal}
+      >
+        <Tabs activeKey={activeKey} tabPosition="left" onChange={setActiveKey}>
+          <TabPane tab="图片" key="1">
+            <Upload
+              {...imgProps}
+              // defaultFileList={[...defaultFileList]}
+            >
+              <Button
+                loading={uploadLoading}
+                onClick={(e: MouseEvent<HTMLElement>) => {
+                  sendUpload(e, 'image');
+                }}
+              >
+                {!uploadLoading && <UploadOutlined />} 上传
+              </Button>
+              <div
+                style={{ marginTop: 10 }}
+                onClick={(e) => e.stopPropagation()}
+              >
+                <Alert
+                  message={`图片大小限制1M    已上传${imgList.length}/10张`}
+                  type="warning"
+                  showIcon
+                />
+              </div>
+            </Upload>
+          </TabPane>
+          <TabPane tab="视频" key="2">
+            <Button
+              loading={uploadLoading}
+              onClick={(e) => {
+                sendUpload(e, 'video');
+              }}
+            >
+              {!uploadLoading && <UploadOutlined />} 上传
+            </Button>
+            {mediaVideo && (
+              <Button
+                loading={loading}
+                onClick={deleteVideo}
+                style={{ marginLeft: 20 }}
+                danger
+              >
+                删除视频
+              </Button>
+            )}
+
+            {/* </Upload> */}
+            <div style={{ marginTop: 10, marginBottom: 20 }}>
+              <Alert
+                message={`已上传${mediaVideo ? 1 : 0}/1个视频`}
+                type="warning"
+                showIcon
+              />
+            </div>
+            {mediaVideo && (
+              <video
+                ref={videoEle}
+                src={mediaVideo.Url}
+                controls
+                style={{ width: '100%' }}
+              ></video>
+            )}
+          </TabPane>
+        </Tabs>
+      </Modal>
+    </div>
+  );
+}

+ 133 - 0
src/Project/pages/DataMeter/Model/ProjectInfo.tsx

@@ -0,0 +1,133 @@
+import React, { useMemo } from 'react';
+import styles from './index.less';
+import moment from 'moment';
+import { ChartBoxTitle } from './ChartBox';
+import { useModel } from '@umijs/max';
+
+function ProjectInfo(props: DataMeter.IModelsProps) {
+  const { child, layout } = props;
+  const { project: projectDetail } = useModel('project');
+  const getMainLeader = () => {
+    let user = [];
+    if (!projectDetail) return;
+    if (projectDetail.Leader) {
+      user.push(projectDetail.Leader.CName);
+    }
+    if (projectDetail.SiteManagerUser) {
+      user.push(projectDetail.SiteManagerUser.CName);
+    }
+    if (projectDetail.TechnicalDesignerUser) {
+      user.push(projectDetail.TechnicalDesignerUser.CName);
+    }
+    if (projectDetail.MechanicalDesignerUser) {
+      user.push(projectDetail.MechanicalDesignerUser.CName);
+    }
+    if (projectDetail.ElectricalDesignerUser) {
+      user.push(projectDetail.ElectricalDesignerUser.CName);
+    }
+    return Array.from(new Set(user)).join(',');
+  };
+  const getUser = () => {
+    if (!projectDetail || !projectDetail.User) return;
+    let user = (projectDetail.User || []).map((item) => item.CName);
+    user = [...new Set(user)];
+    return user.join(',');
+  };
+
+  const data = useMemo(() => {
+    let halfItem: React.ReactElement[] = [];
+    let fullItem: React.ReactElement[] = [];
+    let dom: React.ReactElement[] = [];
+    if (!projectDetail) return null;
+    (child || []).forEach((item) => {
+      if (!item.show) return '';
+      switch (item.key) {
+        case 'Code':
+        case 'ConstructionUnit':
+        case 'UndertakenUnit':
+        case 'Position':
+        case 'Scale':
+        case 'MainProcess':
+        case 'CooperateMode':
+        case 'WaterStandard':
+        case 'ServiceScope':
+        case 'Duration':
+        case 'ServiceTime':
+        case 'CompanyName':
+        case 'CompanyNumber':
+        case 'Remark':
+          halfItem.push(
+            <p className={styles.detailItem} key={item.key}>
+              <span>{item.title}:</span>
+              <span className={styles.data}>
+                {projectDetail && projectDetail[item.key]}
+              </span>
+            </p>,
+          );
+          break;
+        case 'ContractTime':
+          halfItem.push(
+            <p className={styles.detailItem} key={item.key}>
+              <span>{item.title}:</span>
+              <span className={styles.data}>
+                {projectDetail[item.key] &&
+                  moment(projectDetail[item.key]).format('YYYY-MM-DD')}
+              </span>
+            </p>,
+          );
+          break;
+        case 'Admin':
+          fullItem.push(
+            <div className={styles.detailRow}>
+              <p
+                className={`${styles.detailItem} ${styles.detailItemLine}`}
+                key={item.key}
+              >
+                <span>{item.title}:</span>
+                <span className={styles.data}>{getMainLeader()}</span>
+              </p>
+            </div>,
+          );
+          break;
+        case 'User':
+          fullItem.push(
+            <div className={styles.detailRow}>
+              <p
+                className={`${styles.detailItem} ${styles.detailItemLine}`}
+                key={item.key}
+              >
+                <span>{item.title}:</span>
+                <span className={styles.data}>{getUser()}</span>
+              </p>
+            </div>,
+          );
+          break;
+        default:
+          return '';
+      }
+    });
+    for (let i = 0; i < halfItem.length; i += 2) {
+      dom.push(
+        <div className={styles.detailRow}>
+          {halfItem[i]}
+          {halfItem[i + 1] || null}
+        </div>,
+      );
+    }
+    dom = dom.concat(fullItem);
+
+    return dom;
+  }, [child, projectDetail]);
+
+  if (!projectDetail) return 'loading....';
+  return (
+    <div className={styles.modelBox}>
+      <ChartBoxTitle title={'项目概况'} width={layout.w} />
+      <div className={`${styles.modelContent} ${styles.projectInfoContent}`}>
+        {data}
+      </div>
+    </div>
+  );
+}
+
+export default ProjectInfo;

+ 219 - 0
src/Project/pages/DataMeter/Model/ProjectList.less

@@ -0,0 +1,219 @@
+.main{
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  // background: url('@/assets/chartBox/bg.png') no-repeat center;
+  // background-size: 100% 100%;
+  background: linear-gradient(rgba(29, 56, 104, 0.72), rgba(4, 18, 51, 0.76) );
+  border: 1px solid;
+  border-image: linear-gradient(45deg, rgba(74, 196, 253, 0.45), rgba(173, 219, 240, 0.34), rgba(146, 213, 249, 0.28)) 1;
+}
+.container {
+  display: flex;
+  width: 100%;
+  padding: 24px;
+  // padding-left: 0.16rem;
+  // padding-right: 0.16rem;
+  height: calc(100% - 40px);//calc(100% - 98px);
+  // margin-top: 28px;
+  justify-content: space-between;
+}
+.line{
+  width: 1px;
+  height: 100%;
+  background: #266985;
+}
+.left {
+  display: flex;
+  width: 27.65%;
+  height: 100%;
+  flex-direction: column;
+  position: relative;
+  // border: #2866B2 solid 1px;
+  // background: linear-gradient(180deg, rgba(2, 37, 69, 0) 4%, rgba(3, 39, 84, 0.21) 100%);
+  // box-shadow: 0px 8px 32px 0px rgba(0, 0, 0, 0.16);
+  // border-radius: 0px 0px 0px 0px;
+  opacity: 1;
+}
+.search {
+  position: relative;
+  margin-bottom: 16px;
+  input {
+    background-image: url('@/assets/dataMeter/ProjectList/search_bg.png');
+    background-size: 100% 100%;
+    width: 100%;
+    height: 35px;
+    border: none;
+    outline: none;
+    padding-left: 10px;
+    padding-right: 30px;
+    padding-top: 2px;
+    padding-bottom: 2px;
+    font-size: 14px;
+    font-family: 'Microsoft YaHei UI';// Microsoft YaHei UI;
+  }
+  input::-webkit-input-placeholder {
+    // color:rgba(255, 255, 255, 128);
+    // font-style: oblique;
+    text-align: center;
+    font-size: 18px;
+    font-weight: 400;
+    color: #366CDA;
+    letter-spacing: 3px;
+  }
+  img {
+    position: absolute;
+    width: 16px;
+    height: 16px;
+    top: 9px;
+    right: 14px;
+    z-index: 100;
+    cursor: pointer;
+    pointer-events: auto;
+  }
+}
+.list {
+  width: 100%;
+  // height: 60%;
+  overflow: auto;
+  // padding-bottom: 9px;
+  // padding-right: 2px;
+  // padding-top: 16px;//28px;
+  padding-left: 0;
+  margin-bottom: 0;
+  // background-image: url('@/assets/dataMeter/ProjectList/listBG.png');
+  // background-size: 100% 100%;
+  li {
+    width: 100%;
+    // padding-left: 16px;//31px;
+    margin-bottom: 6px;
+    line-height: 30px;
+    height: 31px;
+    font-size: 16px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    &.active {
+      // background-image: url('@/assets/dataMeter/ProjectList/selected.png');
+      // background-size: 100% 100%;
+      // font-family: Microsoft YaHei UI;
+      font-weight: bold;
+      color: #03DDFF;
+    }
+    // font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei,
+    //   Source Han Sans SC, Noto Sans CJK SC, WenQuanYi Micro Hei, sans-serif;
+    // font-weight: 300;
+    color: #D7E4E8;
+  }
+}
+.bottom{
+  display: flex;
+  height: 114px;
+}
+.context {
+  margin-right: 35px;
+  width: 45%;
+  // height: 25%;
+  font-size: 14px;
+  font-family: 'Microsoft YaHei UI';
+  overflow-y: auto;
+  padding: 16px;
+  // border: #009CCB solid 0.75px;
+  color: #D7E4E8;
+  // width: 100%;
+  // height: 40%;
+  // font-size: 16px;
+  // font-family:  'Microsoft YaHei UI';//Microsoft YaHei UI;
+  background: url('@/assets/chartBox/ai_bg1.png') no-repeat center;
+  background-size: 100% 100%;
+  // overflow-y: auto;
+  // padding: 24px 33px 23px 31px;
+}
+.right {
+  display: flex;
+  flex-direction: column;
+  width: 64.4%;
+  height: 100%;
+  position: relative;
+  opacity: 1;
+}
+.image {
+  width: 100%;
+  flex-grow: 1;
+  // border: 1px solid #ffffff;
+  background-size: 100% 100%;
+  background-size: contain;
+  background-position: center;
+  background-repeat: no-repeat;
+  margin-bottom: 12px;
+}
+.right_bottom {
+  // height: 50px;
+  flex-grow: 0;
+  position: relative;
+}
+.enterBtn {
+  display: flex;
+  justify-content: center;
+  cursor: pointer;
+  border: none;
+  align-items: center;
+  width: 142px;
+  height: 46px;
+  font-size: 17px;
+  line-height: 26px;
+  // font-family: 'Microsoft YaHei UI';//Microsoft YaHei UI;
+  // background-image: url('@/assets/dataMeter/ProjectList/enterBtnBG.png');
+  // background-size: 100% 100%;
+  position: absolute;
+  left: 0px;
+  bottom: 0px;
+  background: #366CDA;
+  border-radius: 8px;
+  img {
+    padding-left: 10px;
+    height: 40%;
+  }
+}
+.noModelEnterBtn {
+  display: flex;
+  justify-content: center;
+  cursor: pointer;
+  border: none;
+  align-items: center;
+  width: 142px;
+  height: 46px;
+  font-size: 17px;
+  line-height: 26px;
+  // font-family: 'Microsoft YaHei UI';// Microsoft YaHei UI;
+  // background-image: url('@/assets/dataMeter/ProjectList/enterBtnBG.png');
+  // background-size: 100% 100%;
+  position: absolute;
+  left: 100px;
+  bottom: 0px;
+  background: #366CDA;
+  border-radius: 8px;
+  img {
+    padding-left: 10px;
+    height: 40%;
+  }
+}
+.province {
+  // max-width: 60%;
+  // position: absolute;
+  // overflow: auto;
+  font-size: 17px;
+  font-weight: bold;
+  // font-family:  'Microsoft YaHei UI';//Microsoft YaHei UI;
+  // right: 0px;
+  // overflow: hidden;
+  // text-overflow: ellipsis;
+  // white-space: nowrap;
+  // bottom: 0px;
+}
+.name {
+  font-size: 17px;
+  font-weight: 400;
+  font-family: 'Microsoft YaHei UI';// Microsoft YaHei UI;
+  // margin-left: 30px;
+}

+ 253 - 0
src/Project/pages/DataMeter/Model/ProjectList.tsx

@@ -0,0 +1,253 @@
+import React, {
+  useState,
+  useEffect,
+  useRef,
+  useMemo,
+  ChangeEvent,
+} from 'react';
+import { Input, Tooltip } from 'antd';
+import style from './ProjectList.less';
+import ReactZmage from 'react-zmage';
+import PinyinMatch from 'pinyin-match';
+import { ChartBoxTitle } from './ChartBox';
+import { useRequest } from '@umijs/max';
+
+let LoopTime: NodeJS.Timer;
+let searchTimer: NodeJS.Timer;
+const nodata = require('@/Project/assets/dataMeter/ProjectList/nodata.png');
+const clickTimer = 20000;
+const normalTimer = 15000;
+function ProjectList(props: DataMeter.IProjectListProps) {
+  const { edit, subModule, layout } = props;
+  const [currentProject, setCurrentProject] = useState<Api.IProject>();
+  const [currentIndex, setCurrentIndex] = useState(0);
+  // const [queryList, setQueryList] = useState<Api.IProject[]>([]);
+  const [isClick, setIsClick] = useState(false);
+  const [isPlay, setIsPlay] = useState(false);
+  const [value, setValue] = useState<string>('');
+  const ulRef = useRef<HTMLUListElement>(null);
+  const { data: projectList } = useRequest(() => {});
+
+  const list = useMemo<Api.IProject[]>(() => {
+    const arr = projectList.filter((item: Api.IProject) => {
+      let flag = subModule == 0 || item?.Stage == subModule || item?.Stage == 3;
+      if (!flag) return;
+      if (value) {
+        return PinyinMatch.match(item.Name, value);
+      }
+      return flag;
+    });
+    return arr;
+  }, [projectList]);
+
+  // const getProjectList = (params, callback) => {
+  //   dispatch({
+  //     type: 'dataMeterNew/getProjectList',
+  //     payload: params,
+  //     callback: (res) => {
+  //       res = res.filter((item) => {
+  //         if (isPlatform != undefined) return true;
+  //         if (subModule == 0) return true;
+  //         else {
+  //           if (item?.Stage == subModule || item?.Stage == 3) return true;
+  //         }
+  //       });
+  //       // console.log(subModule, res);
+  //       setQueryList(res);
+  //       setCurrentIndex(0);
+  //       setCurrentProject(res[0] || {});
+  //       callback && callback(res);
+  //     },
+  //   });
+  // };
+  // useEffect(() => {
+  //   getProjectList();
+  //   return () => {
+  //     clearInterval(LoopTime);
+  //   };
+  // }, []);
+  // useEffect(() => {
+  //   if (isPlay) {
+  //     getProjectList({ carousel: 1 }, (res) => {
+  //       res = res.filter((item) => {
+  //         if (isPlatform != undefined) return true;
+  //         if (subModule == 0) return true;
+  //         else {
+  //           if (item?.Stage == subModule || item?.Stage == 3) return true;
+  //         }
+  //       });
+  //       setQueryList(res);
+  //       inputRef.current.value = '';
+  //       setCurrentProject(res[0] || {});
+  //       setProjectId(res[0].ID || 0);
+  //       ChangedProject(res[0] || {});
+  //       onPlayCallback && onPlayCallback();
+  //       ulRef.current.scrollTop = 0;
+  //     });
+  //   } else {
+  //     getProjectList({}, (res) => {
+  //       clearInterval(LoopTime);
+  //       inputRef.current.value = '';
+  //       setProjectId(0);
+  //     });
+  //   }
+
+  //   // // console.log(isPlay);
+  //   return () => {};
+  // }, [isPlay]);
+  // useEffect(() => {
+  //   UnityAction.on('projectCarousel', (isPlay) => {
+  //     setIsPlay(() => isPlay);
+  //     if (!isPlay) {
+  //       // else
+  //       OnPauseCallback && OnPauseCallback();
+  //     }
+  //   });
+  //   return () => {
+  //     UnityAction.off('projectCarousel');
+  //     clearInterval(LoopTime);
+  //   };
+  // }, []);
+
+  // const StartTimeout = (time:number) => {
+  //   LoopTime = setInterval(() => {
+  //     if (queryList.length > 0) {
+  //       var count = queryList.length;
+  //       var index;
+  //       if (currentIndex < count - 1) {
+  //         index = currentIndex + 1;
+  //       } else {
+  //         index = 0;
+  //       }
+
+  //       // console.log(currentIndex, queryList[index].Name);
+  //       cleanProjectData && cleanProjectData();
+  //       setCurrentIndex(index);
+  //       setCurrentProject(queryList[index]);
+  //       setProjectId(queryList[index].ID);
+  //       ChangedProject(queryList[index]);
+  //       // console.log(33333);
+  //       ulRef.current.scrollTop =
+  //         ulRef.current?.children &&
+  //         ulRef.current.children[index].clientHeight * index -
+  //           ulRef.current.clientHeight / 2 +
+  //           ulRef.current.children[index].clientHeight * 0.5 +
+  //           6 * index +
+  //           28;
+  //     }
+  //   }, time);
+  // };
+  // const sendProjectToUnity = () => {
+  //   UnityAction.sendMsg('project', JSON.stringify(currentProject));
+  // };
+  // const sendNoModelToUnity = () => {
+  //   UnityAction.sendMsg('noModel', JSON.stringify(currentProject));
+  // };
+
+  const onChangeInput = (e: ChangeEvent<HTMLInputElement>) => {
+    setValue(e.target.value);
+  };
+
+  // useEffect(() => {
+  //   const timer = isClick ? clickTimer : normalTimer;
+  //   if (isClick) setIsClick(false);
+  //   if (isPlay) StartTimeout(timer);
+  //   return () => {
+  //     clearInterval(LoopTime);
+  //   };
+  // }, [currentIndex, queryList]);
+
+  return (
+    <div className={style.main}>
+      <ChartBoxTitle title="数字实景" width={layout.w} />
+      <div className={style.container}>
+        <div className={style.left}>
+          <div className={style.search}>
+            <input
+              onChange={onChangeInput}
+              type="text"
+              placeholder="请输入项目"
+              placeholder-style={{ color: '#366CDA', fondSize: '18px' }}
+            ></input>
+            <img
+              src={require('@/Project/assets/dataMeter/ProjectList/search_Icon.png')}
+            ></img>
+          </div>
+          <ul ref={ulRef} className={style.list}>
+            {list.map((item, index) => {
+              return (
+                <li
+                  className={currentIndex == index ? style.active : ''}
+                  key={item.ID}
+                  onClick={() => {
+                    // clearInterval(LoopTime);
+                    // setIsClick(true);
+                    setCurrentProject(item);
+                    setCurrentIndex(index);
+                    // if (isPlay) {
+                    //   setProjectId(item.ID);
+                    //   ChangedProject(item);
+                    // }
+
+                    // if (currentIndex == index) {
+                    //   if (isClick) setIsClick(false);
+                    //   if (isPlay) StartTimeout(clickTimer);
+                    // }
+                  }}
+                >
+                  {item.Name}
+                </li>
+              );
+            })}
+          </ul>
+        </div>
+        <div className={style.line}></div>
+        <div className={style.right}>
+          <div
+            className={style.image}
+            onClick={() => {
+              if (edit) return;
+              // document.getElementsByClassName('reactZmage')[0]?.click();
+            }}
+            style={{
+              backgroundImage: `url('${currentProject?.ScreenShot || nodata}')`,
+              cursor: 'pointer',
+            }}
+          ></div>
+          <ReactZmage
+            controller={{
+              close: true,
+              rotate: true,
+              zoom: false,
+              download: false,
+              flip: false,
+              pagination: false,
+            }}
+            backdrop="rgba(255,255,255,0.5)"
+            src={currentProject?.ScreenShot || nodata}
+            onBrowsing={(state) => {
+              // if (state) {
+              //   clearInterval(LoopTime);
+              // } else {
+              //   if (isPlay) StartTimeout(normalTimer);
+              // }
+            }}
+          />
+          <div className={style.bottom}>
+            <div className={style.context}>{currentProject?.Remark}</div>
+
+            <div className={style.right_bottom}>
+              <div className={style.province}>
+                {currentProject?.Province}
+                <Tooltip title={currentProject?.Name}>
+                  <div className={style.name}>{currentProject?.Name}</div>
+                </Tooltip>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}
+export default ProjectList;

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

@@ -0,0 +1,53 @@
+import style from '../index.less';
+import { CHILD_MAP } from '../config';
+import { Tooltip } from 'antd';
+
+function Model(props: DataMeter.IModelsProps) {
+  const { layout } = props;
+  let content;
+
+  return (
+    <div
+      className={style.item}
+      key={layout.i}
+    >
+      {content}
+      <Control {...props} />
+    </div>
+  );
+}
+
+function Control(props: DataMeter.IModelsProps) {
+  const { edit, layout, showEditModel, removeModel } = props;
+  return (
+    <div
+      className={style.controlBox}
+      onMouseDown={(e) => {
+        e.stopPropagation();
+      }}
+    >
+      {edit && CHILD_MAP[layout.key] && (
+        <Tooltip title="编辑该模块">
+          <div
+            className={style.edit}
+            onClick={() => {
+              showEditModel(layout);
+            }}
+          ></div>
+        </Tooltip>
+      )}
+      {edit && (
+        <Tooltip title="删除该模块">
+          <div
+            className={style.close}
+            onClick={() => {
+              removeModel(layout.key, layout.active);
+            }}
+          ></div>
+        </Tooltip>
+      )}
+    </div>
+  );
+}
+
+export default Model;

+ 977 - 0
src/Project/pages/DataMeter/config.ts

@@ -0,0 +1,977 @@
+export const CHILD_MAP: { [key: string]: DataMeter.ILayoutChild[] } = {
+  ProjectInfo: [
+    {
+      title: '项目编号',
+      key: 'Code',
+      show: true,
+    },
+    {
+      title: '公司名称',
+      key: 'CompanyName',
+      show: true,
+    },
+    {
+      title: '项目工期',
+      key: 'Duration',
+      show: true,
+    },
+    {
+      title: '建设单位',
+      key: 'ConstructionUnit',
+      show: true,
+    },
+    {
+      title: '承建单位',
+      key: 'UndertakenUnit',
+      show: true,
+    },
+    {
+      title: '所在地',
+      key: 'Position',
+      show: true,
+    },
+
+    {
+      title: '项目规模',
+      key: 'Scale',
+      show: true,
+    },
+    {
+      title: '主体工艺',
+      key: 'MainProcess',
+      show: true,
+    },
+    {
+      title: '员工人数',
+      key: 'CompanyNumber',
+      show: true,
+    },
+    {
+      title: '服务范围',
+      key: 'ServiceScope',
+      show: true,
+    },
+    {
+      title: '签约时间',
+      key: 'ContractTime',
+      show: true,
+    },
+    {
+      title: '运营服务时间',
+      key: 'ServiceTime',
+      show: true,
+    },
+    {
+      title: '合作模式',
+      key: 'CooperateMode',
+      show: true,
+    },
+    {
+      title: '出水执行标准',
+      key: 'WaterStandard',
+      show: true,
+    },
+    {
+      title: '核心管理人员',
+      key: 'Admin',
+      show: true,
+    },
+    {
+      title: '项目成员',
+      key: 'User',
+      show: true,
+    },
+    {
+      title: '项目描述',
+      key: 'Remark',
+      show: true,
+    },
+  ],
+  AlarmCenter: [
+    { title: '全部', key: 4, show: true },
+    {
+      title: '待办事项',
+      key: 3,
+      show: true,
+    },
+    {
+      title: '异常报警',
+      key: 1,
+      show: true,
+    },
+    {
+      title: '逾期预警',
+      key: 2,
+      show: true,
+    },
+  ],
+  MessageCenter: [
+    {
+      title: '消息推送',
+      key: 1,
+      show: true,
+    },
+    {
+      title: '新闻动态',
+      key: 2,
+      show: true,
+    },
+    {
+      title: '大事记',
+      key: 3,
+      show: true,
+    },
+    {
+      title: '所获奖项',
+      key: 4,
+      show: true,
+    },
+  ],
+  PlanManagement: [
+    // {
+    //   title: '设计',
+    //   key: 1,
+    //   show: true,
+    // },
+    // {
+    //   title: '采购',
+    //   key: 2,
+    //   show: true,
+    // },
+    {
+      title: '到货',
+      key: 1,
+      show: true,
+    },
+    {
+      title: '施工',
+      key: 2,
+      show: true,
+    },
+    {
+      title: '单机调试',
+      key: 3,
+      show: true,
+    },
+    {
+      title: '系统调试',
+      key: 4,
+      show: true,
+    },
+    {
+      title: '试运行',
+      key: 5,
+      show: true,
+    },
+    // {
+    //   title: '维修',
+    //   key: 6,
+    //   show: true,
+    // },
+    {
+      title: '项目动态',
+      key: 7,
+      show: true,
+    },
+    // {
+    //   title: '巡检',
+    //   key: 7,
+    //   show: true,
+    // },
+    // {
+    //   title: '其它',
+    //   key: 8,
+    //   show: true,
+    // },
+  ],
+  Other: [
+    {
+      title: '安全操作规范',
+      key: 1,
+      show: true,
+    },
+    {
+      title: '项目日志',
+      key: 2,
+      show: true,
+    },
+  ],
+  Monitor: [],
+  DebugData: [],
+  DataCenter: [
+    {
+      title: '水质管理',
+      key: 1,
+      show: true,
+    },
+    {
+      title: '水质指标',
+      key: 2,
+      show: true,
+    },
+    {
+      title: '客户满意度',
+      key: 3,
+      show: true,
+    },
+    {
+      title: '设备完好率',
+      key: 4,
+      show: true,
+    },
+    {
+      title: 'KPI',
+      key: 5,
+      show: true,
+      children: [
+        {
+          title: '累计EBITDA',
+          key: 11,
+          show: true,
+        },
+        {
+          title: '累计经营性净现金流',
+          key: 12,
+          show: true,
+        },
+      ],
+    },
+    {
+      title: '水量计费',
+      key: 6,
+      show: true,
+    },
+    {
+      title: '预算成本',
+      key: 7,
+      show: true,
+      children: [
+        {
+          title: '累计成本支出',
+          key: 13,
+          show: true,
+        },
+        {
+          title: '累计月度吨水成本',
+          key: 14,
+          show: true,
+        },
+      ],
+    },
+    {
+      title: '季度绩效',
+      key: 8,
+      show: true,
+    },
+    {
+      title: '化学实验室数据',
+      key: 9,
+      show: true,
+    },
+    {
+      title: '安全生产天',
+      key: 10,
+      show: true,
+    },
+    // {
+    //   title: '调试数据',
+    //   key: 11,
+    //   show: true,
+    // },
+  ],
+};
+
+export const DEFAULT_LAYOUT: DataMeter.ILayout[] = [
+  {
+    i: '41',
+    key: 'AlarmCenter',
+    w: 8,
+    h: 5,
+    x: 28,
+    y: 14,
+    child: [
+      { title: '全部', key: 4, show: true },
+      { title: '待办事项', key: 3, show: true },
+      { title: '异常报警', key: 1, show: true },
+      { title: '逾期预警', key: 2, show: true },
+    ],
+    moved: false,
+    static: false,
+    active: 2,
+  },
+  {
+    i: '40',
+    key: 'DataCenter',
+    active: 11,
+    w: 6,
+    h: 5,
+    x: 42,
+    y: 14,
+    child: [
+      { title: '水质管理', key: 1, show: true },
+      { title: '水质指标', key: 2, show: true },
+      { title: '客户满意度', key: 3, show: true },
+      { title: '设备完好率', key: 4, show: true },
+      { title: '累计EBITDA', key: 11, show: true },
+      { title: '累计经营性净现金流', key: 12, show: true },
+      { title: '水量计费', key: 6, show: true },
+      { title: '累计成本支出', key: 13, show: true },
+      { title: '累计月度吨水成本', key: 14, show: true },
+      { title: '季度绩效', key: 8, show: true },
+      { title: '化学实验室数据', key: 9, show: true },
+      { title: '安全生产天', key: 10, show: true },
+    ],
+    moved: false,
+    static: false,
+  },
+  {
+    i: '39',
+    key: 'DataCenter',
+    active: '4',
+    w: 6,
+    h: 5,
+    x: 42,
+    y: 9,
+    child: [
+      { title: '水质管理', key: 1, show: true },
+      { title: '水质指标', key: 2, show: true },
+      { title: '客户满意度', key: 3, show: true },
+      { title: '设备完好率', key: 4, show: true },
+      { title: '累计EBITDA', key: 11, show: true },
+      { title: '累计经营性净现金流', key: 12, show: true },
+      { title: '水量计费', key: 6, show: true },
+      { title: '累计成本支出', key: 13, show: true },
+      { title: '累计月度吨水成本', key: 14, show: true },
+      { title: '季度绩效', key: 8, show: true },
+      { title: '化学实验室数据', key: 9, show: true },
+      { title: '安全生产天', key: 10, show: true },
+    ],
+    moved: false,
+    static: false,
+  },
+  {
+    i: '38',
+    key: 'DataCenter',
+    active: '2',
+    w: 6,
+    h: 5,
+    x: 42,
+    y: 4,
+    child: [
+      { title: '水质管理', key: 1, show: true },
+      { title: '水质指标', key: 2, show: true },
+      { title: '客户满意度', key: 3, show: true },
+      { title: '设备完好率', key: 4, show: true },
+      { title: '累计EBITDA', key: 11, show: true },
+      { title: '累计经营性净现金流', key: 12, show: true },
+      { title: '水量计费', key: 6, show: true },
+      { title: '累计成本支出', key: 13, show: true },
+      { title: '累计月度吨水成本', key: 14, show: true },
+      { title: '季度绩效', key: 8, show: true },
+      { title: '化学实验室数据', key: 9, show: true },
+      { title: '安全生产天', key: 10, show: true },
+    ],
+    moved: false,
+    static: false,
+  },
+  {
+    i: '37',
+    key: 'DataCenter',
+    active: 10,
+    w: 6,
+    h: 4,
+    x: 42,
+    y: 0,
+    child: [
+      { title: '水质管理', key: 1, show: true },
+      { title: '水质指标', key: 2, show: true },
+      { title: '客户满意度', key: 3, show: true },
+      { title: '设备完好率', key: 4, show: true },
+      { title: '累计EBITDA', key: 11, show: true },
+      { title: '累计经营性净现金流', key: 12, show: true },
+      { title: '水量计费', key: 6, show: true },
+      { title: '累计成本支出', key: 13, show: true },
+      { title: '累计月度吨水成本', key: 14, show: true },
+      { title: '季度绩效', key: 8, show: true },
+      { title: '化学实验室数据', key: 9, show: true },
+      { title: '安全生产天', key: 10, show: true },
+    ],
+    moved: false,
+    static: false,
+  },
+  {
+    i: '36',
+    key: 'DataCenter',
+    active: '1',
+    w: 6,
+    h: 5,
+    x: 36,
+    y: 4,
+    child: [
+      { title: '水质管理', key: 1, show: true },
+      { title: '水质指标', key: 2, show: true },
+      { title: '客户满意度', key: 3, show: true },
+      { title: '设备完好率', key: 4, show: true },
+      { title: '累计EBITDA', key: 11, show: true },
+      { title: '累计经营性净现金流', key: 12, show: true },
+      { title: '水量计费', key: 6, show: true },
+      { title: '累计成本支出', key: 13, show: true },
+      { title: '累计月度吨水成本', key: 14, show: true },
+      { title: '季度绩效', key: 8, show: true },
+      { title: '化学实验室数据', key: 9, show: true },
+      { title: '安全生产天', key: 10, show: true },
+    ],
+    moved: false,
+    static: false,
+  },
+  {
+    i: '35',
+    key: 'DataCenter',
+    active: 13,
+    w: 7,
+    h: 5,
+    x: 7,
+    y: 14,
+    child: [
+      { title: '水质管理', key: 1, show: true },
+      { title: '水质指标', key: 2, show: true },
+      { title: '客户满意度', key: 3, show: true },
+      { title: '设备完好率', key: 4, show: true },
+      { title: '累计EBITDA', key: 11, show: true },
+      { title: '累计经营性净现金流', key: 12, show: true },
+      { title: '水量计费', key: 6, show: true },
+      { title: '累计成本支出', key: 13, show: true },
+      { title: '累计月度吨水成本', key: 14, show: true },
+      { title: '季度绩效', key: 8, show: true },
+      { title: '化学实验室数据', key: 9, show: true },
+      { title: '安全生产天', key: 10, show: true },
+    ],
+    moved: false,
+    static: false,
+  },
+  {
+    i: '34',
+    key: 'DataCenter',
+    active: '8',
+    w: 7,
+    h: 5,
+    x: 0,
+    y: 14,
+    child: [
+      { title: '水质管理', key: 1, show: true },
+      { title: '水质指标', key: 2, show: true },
+      { title: '客户满意度', key: 3, show: true },
+      { title: '设备完好率', key: 4, show: true },
+      { title: '累计EBITDA', key: 11, show: true },
+      { title: '累计经营性净现金流', key: 12, show: true },
+      { title: '水量计费', key: 6, show: true },
+      { title: '累计成本支出', key: 13, show: true },
+      { title: '累计月度吨水成本', key: 14, show: true },
+      { title: '季度绩效', key: 8, show: true },
+      { title: '化学实验室数据', key: 9, show: true },
+      { title: '安全生产天', key: 10, show: true },
+    ],
+    moved: false,
+    static: false,
+  },
+  {
+    i: '33',
+    key: 'DataCenter',
+    active: 11,
+    w: 7,
+    h: 4,
+    x: 7,
+    y: 10,
+    child: [
+      { title: '水质管理', key: 1, show: true },
+      { title: '水质指标', key: 2, show: true },
+      { title: '客户满意度', key: 3, show: true },
+      { title: '设备完好率', key: 4, show: true },
+      { title: '累计EBITDA', key: 11, show: true },
+      { title: '累计经营性净现金流', key: 12, show: true },
+      { title: '水量计费', key: 6, show: true },
+      { title: '累计成本支出', key: 13, show: true },
+      { title: '累计月度吨水成本', key: 14, show: true },
+      { title: '季度绩效', key: 8, show: true },
+      { title: '化学实验室数据', key: 9, show: true },
+      { title: '安全生产天', key: 10, show: true },
+    ],
+    moved: false,
+    static: false,
+  },
+  {
+    i: '32',
+    key: 'DataCenter',
+    active: '2',
+    w: 7,
+    h: 5,
+    x: 7,
+    y: 5,
+    child: [
+      { title: '水质管理', key: 1, show: true },
+      { title: '水质指标', key: 2, show: true },
+      { title: '客户满意度', key: 3, show: true },
+      { title: '设备完好率', key: 4, show: true },
+      { title: '累计EBITDA', key: 11, show: true },
+      { title: '累计经营性净现金流', key: 12, show: true },
+      { title: '水量计费', key: 6, show: true },
+      { title: '累计成本支出', key: 13, show: true },
+      { title: '累计月度吨水成本', key: 14, show: true },
+      { title: '季度绩效', key: 8, show: true },
+      { title: '化学实验室数据', key: 9, show: true },
+      { title: '安全生产天', key: 10, show: true },
+    ],
+    moved: false,
+    static: false,
+  },
+  {
+    i: '31',
+    key: 'DataCenter',
+    active: '1',
+    w: 7,
+    h: 5,
+    x: 0,
+    y: 0,
+    child: [
+      { title: '水质管理', key: 1, show: true },
+      { title: '水质指标', key: 2, show: true },
+      { title: '客户满意度', key: 3, show: true },
+      { title: '设备完好率', key: 4, show: true },
+      { title: '累计EBITDA', key: 11, show: true },
+      { title: '累计经营性净现金流', key: 12, show: true },
+      { title: '水量计费', key: 6, show: true },
+      { title: '累计成本支出', key: 13, show: true },
+      { title: '累计月度吨水成本', key: 14, show: true },
+      { title: '季度绩效', key: 8, show: true },
+      { title: '化学实验室数据', key: 9, show: true },
+      { title: '安全生产天', key: 10, show: true },
+    ],
+    moved: false,
+    static: false,
+  },
+  {
+    i: '30',
+    key: 'DataCenter',
+    w: 7,
+    h: 5,
+    x: 7,
+    y: 0,
+    child: [
+      { title: '水质管理', key: 1, show: true },
+      { title: '水质指标', key: 2, show: true },
+      { title: '客户满意度', key: 3, show: true },
+      { title: '设备完好率', key: 4, show: true },
+      { title: '累计EBITDA', key: 11, show: true },
+      { title: '累计经营性净现金流', key: 12, show: true },
+      { title: '水量计费', key: 6, show: true },
+      { title: '累计成本支出', key: 13, show: true },
+      { title: '累计月度吨水成本', key: 14, show: true },
+      { title: '季度绩效', key: 8, show: true },
+      { title: '化学实验室数据', key: 9, show: true },
+      { title: '安全生产天', key: 10, show: true },
+    ],
+    moved: false,
+    static: false,
+    active: 12,
+  },
+  {
+    i: '29',
+    key: 'DataCenter',
+    w: 6,
+    h: 5,
+    x: 36,
+    y: 14,
+    child: [
+      { title: '水质管理', key: 1, show: true },
+      { title: '水质指标', key: 2, show: true },
+      { title: '客户满意度', key: 3, show: true },
+      { title: '设备完好率', key: 4, show: true },
+      { title: 'KPI', key: 5, show: true },
+      { title: '水量计费', key: 6, show: true },
+      { title: '预算成本', key: 7, show: true },
+      { title: '季度绩效', key: 8, show: true },
+      { title: '化学实验室数据', key: 9, show: true },
+      { title: '安全生产天', key: 10, show: true },
+    ],
+    moved: false,
+    static: false,
+  },
+  {
+    i: '20',
+    key: 'DataCenter',
+    w: 6,
+    h: 5,
+    x: 36,
+    y: 9,
+    child: [
+      { title: '水质管理', key: 1, show: true },
+      { title: '水质指标', key: 2, show: true },
+      { title: '客户满意度', key: 3, show: true },
+      { title: '设备完好率', key: 4, show: true },
+      { title: 'KPI', key: 5, show: true },
+      { title: '水量计费', key: 6, show: true },
+      { title: '预算成本', key: 7, show: true },
+      { title: '季度绩效', key: 8, show: true },
+      { title: '化学实验室数据', key: 9, show: true },
+      { title: '安全生产天', key: 10, show: true },
+    ],
+    moved: false,
+    static: false,
+    active: 3,
+  },
+  {
+    i: '14',
+    key: 'DataCenter',
+    w: 7,
+    h: 5,
+    x: 0,
+    y: 5,
+    child: [
+      { title: '水质管理', key: 1, show: true },
+      { title: '水质指标', key: 2, show: true },
+      { title: '客户满意度', key: 3, show: true },
+      { title: '设备完好率', key: 4, show: true },
+      { title: 'KPI', key: 5, show: true },
+      { title: '水量计费', key: 6, show: true },
+      { title: '预算成本', key: 7, show: true },
+      { title: '季度绩效', key: 8, show: true },
+      { title: '化学实验室数据', key: 9, show: true },
+      { title: '安全生产天', key: 10, show: true },
+    ],
+    moved: false,
+    static: false,
+    active: 3,
+  },
+  {
+    i: '12',
+    key: 'PlanManagement',
+    w: 6,
+    h: 4,
+    x: 36,
+    y: 0,
+    child: [
+      { title: '设计', key: 1, show: true },
+      { title: '采购', key: 2, show: true },
+      { title: '施工', key: 3, show: true },
+      { title: '调试', key: 4, show: true },
+      { title: '保养', key: 5, show: true },
+      { title: '维修', key: 6, show: true },
+      { title: '项目动态', key: 7, show: true },
+    ],
+    moved: false,
+    static: false,
+    active: 1,
+  },
+  {
+    i: '10',
+    key: 'PlanManagement',
+    w: 7,
+    h: 4,
+    x: 0,
+    y: 10,
+    child: [
+      { title: '设计', key: 1, show: true },
+      { title: '采购', key: 2, show: true },
+      { title: '施工', key: 3, show: true },
+      { title: '调试', key: 4, show: true },
+      { title: '保养', key: 5, show: true },
+      { title: '维修', key: 6, show: true },
+      { title: '项目动态', key: 7, show: true },
+    ],
+    moved: false,
+    static: false,
+    active: 2,
+  },
+  {
+    i: '8',
+    key: 'ProjectInfo',
+    w: 14,
+    h: 5,
+    x: 14,
+    y: 14,
+    active: 1,
+    child: [
+      { title: '项目编号', key: 'Code', show: true },
+      { title: '项目工期', key: 'Duration', show: true },
+      { title: '建设单位', key: 'ConstructionUnit', show: true },
+      { title: '承建单位', key: 'UndertakenUnit', show: true },
+      { title: '所在地', key: 'Position', show: true },
+      { title: '项目规模', key: 'Scale', show: true },
+      { title: '主体工艺', key: 'MainProcess', show: true },
+      { title: '服务范围', key: 'ServiceScope', show: true },
+      { title: '签约时间', key: 'ContractTime', show: true },
+      { title: '运营服务时间', key: 'ServiceTime', show: true },
+      { title: '合作模式', key: 'CooperateMode', show: true },
+      { title: '出水执行标准', key: 'WaterStandard', show: true },
+      { title: '核心管理人员', key: 'Admin', show: true },
+      { title: '项目成员', key: 'User', show: true },
+    ],
+    moved: false,
+    static: false,
+  },
+  {
+    i: '7',
+    key: 'Map',
+    w: 22,
+    h: 14,
+    x: 14,
+    y: 0,
+    moved: false,
+    static: false,
+  },
+];
+export const DEFAULT_MAP_LAYOUT: DataMeter.ILayout[] = [
+  {
+    i: '20',
+    key: 'DataCenter',
+    w: 14,
+    h: 7,
+    x: 0,
+    y: 0,
+    child: [
+      { title: '水质管理', key: 1, show: true },
+      { title: '水质指标', key: 2, show: true },
+      { title: '客户满意度', key: 3, show: true },
+      { title: '设备完好率', key: 4, show: true },
+      { title: 'KPI', key: 5, show: true },
+      { title: '水量计费', key: 6, show: true },
+      { title: '预算成本', key: 7, show: true },
+      { title: '季度绩效', key: 8, show: true },
+      { title: '化学实验室数据', key: 9, show: true },
+      { title: '安全生产天', key: 10, show: true },
+    ],
+    moved: false,
+    static: false,
+  },
+  {
+    i: '19',
+    key: 'DataCenter',
+    w: 7,
+    h: 7,
+    x: 7,
+    y: 13,
+    child: [
+      { title: '水质管理', key: 1, show: true },
+      { title: '水质指标', key: 2, show: true },
+      { title: '客户满意度', key: 3, show: true },
+      { title: '设备完好率', key: 4, show: true },
+      { title: 'KPI', key: 5, show: true },
+      { title: '水量计费', key: 6, show: true },
+      { title: '预算成本', key: 7, show: true },
+      { title: '季度绩效', key: 8, show: true },
+      { title: '化学实验室数据', key: 9, show: true },
+      { title: '安全生产天', key: 10, show: true },
+    ],
+    moved: false,
+    static: false,
+    active: 9,
+  },
+  {
+    i: '18',
+    key: 'DataCenter',
+    w: 7,
+    h: 6,
+    x: 0,
+    y: 13,
+    child: [
+      { title: '水质管理', key: 1, show: true },
+      { title: '水质指标', key: 2, show: true },
+      { title: '客户满意度', key: 3, show: true },
+      { title: '设备完好率', key: 4, show: true },
+      { title: 'KPI', key: 5, show: true },
+      { title: '水量计费', key: 6, show: true },
+      { title: '预算成本', key: 7, show: true },
+      { title: '季度绩效', key: 8, show: true },
+      { title: '化学实验室数据', key: 9, show: true },
+      { title: '安全生产天', key: 10, show: true },
+    ],
+    moved: false,
+    static: false,
+    active: 3,
+  },
+  {
+    i: '17',
+    key: 'DataCenter',
+    w: 7,
+    h: 6,
+    x: 7,
+    y: 7,
+    child: [
+      { title: '水质管理', key: 1, show: true },
+      { title: '水质指标', key: 2, show: true },
+      { title: '客户满意度', key: 3, show: true },
+      { title: '设备完好率', key: 4, show: true },
+      { title: 'KPI', key: 5, show: true },
+      { title: '水量计费', key: 6, show: true },
+      { title: '预算成本', key: 7, show: true },
+      { title: '季度绩效', key: 8, show: true },
+      { title: '化学实验室数据', key: 9, show: true },
+      { title: '安全生产天', key: 10, show: true },
+    ],
+    moved: false,
+    static: false,
+    active: 7,
+  },
+  {
+    i: '15',
+    key: 'DataCenter',
+    w: 13,
+    h: 6,
+    x: 35,
+    y: 6,
+    child: [
+      { title: '水质管理', key: 1, show: true },
+      { title: '水质指标', key: 2, show: true },
+      { title: '客户满意度', key: 3, show: true },
+      { title: '设备完好率', key: 4, show: true },
+      { title: 'KPI', key: 5, show: true },
+      { title: '水量计费', key: 6, show: true },
+      { title: '预算成本', key: 7, show: true },
+      { title: '季度绩效', key: 8, show: true },
+      { title: '化学实验室数据', key: 9, show: true },
+      { title: '安全生产天', key: 10, show: true },
+    ],
+    moved: false,
+    static: false,
+    active: 6,
+  },
+  {
+    i: '12',
+    key: 'DataCenter',
+    w: 7,
+    h: 6,
+    x: 0,
+    y: 7,
+    child: [
+      { title: '水质管理', key: 1, show: true },
+      { title: '水质指标', key: 2, show: true },
+      { title: '客户满意度', key: 3, show: true },
+      { title: '设备完好率', key: 4, show: true },
+      { title: 'KPI', key: 5, show: true },
+      { title: '水量计费', key: 6, show: true },
+      { title: '预算成本', key: 7, show: true },
+      { title: '季度绩效', key: 8, show: true },
+      { title: '化学实验室数据', key: 9, show: true },
+      { title: '安全生产天', key: 10, show: true },
+    ],
+    moved: false,
+    static: false,
+    active: 5,
+  },
+  {
+    i: '11',
+    key: 'DataCenter',
+    w: 13,
+    h: 7,
+    x: 35,
+    y: 12,
+    child: [
+      { title: '水质管理', key: 1, show: true },
+      { title: '水质指标', key: 2, show: true },
+      { title: '客户满意度', key: 3, show: true },
+      { title: '设备完好率', key: 4, show: true },
+      { title: 'KPI', key: 5, show: true },
+      { title: '水量计费', key: 6, show: true },
+      { title: '预算成本', key: 7, show: true },
+      { title: '季度绩效', key: 8, show: true },
+      { title: '化学实验室数据', key: 9, show: true },
+      { title: '安全生产天', key: 10, show: true },
+    ],
+    moved: false,
+    static: false,
+    active: 4,
+  },
+  {
+    i: '7',
+    key: 'Map',
+    w: 21,
+    h: 12,
+    x: 14,
+    y: 0,
+    moved: false,
+    static: false,
+  },
+  {
+    i: '5',
+    key: 'AlarmCenter',
+    w: 10,
+    h: 6,
+    x: 25,
+    y: 12,
+    active: 3,
+    child: [
+      { title: '待办事项', key: 3, show: true },
+      { title: '异常报警', key: 1, show: true },
+      { title: '项目预警', key: 2, show: true },
+    ],
+    moved: false,
+    static: false,
+  },
+  {
+    i: '3',
+    key: 'DataCenter',
+    w: 13,
+    h: 6,
+    x: 35,
+    y: 0,
+    active: 10,
+    child: [
+      { title: '水质管理', key: 1, show: true },
+      { title: '水质指标', key: 2, show: true },
+      { title: '客户满意度', key: 3, show: true },
+      { title: '设备完好率', key: 4, show: true },
+      { title: 'KPI', key: 5, show: true },
+      { title: '水量计费', key: 6, show: true },
+      { title: '预算成本', key: 7, show: true },
+      { title: '季度绩效', key: 8, show: true },
+      { title: '化学实验室数据', key: 9, show: true },
+      { title: '安全生产天', key: 10, show: true },
+    ],
+    moved: false,
+    static: false,
+  },
+  {
+    i: '2',
+    key: 'MessageCenter',
+    w: 11,
+    h: 6,
+    x: 14,
+    y: 12,
+    active: 1,
+    child: [
+      { title: '消息推送', key: 1, show: true },
+      { title: '新闻动态', key: 2, show: true },
+      { title: '大事记', key: 3, show: true },
+      { title: '所获奖项', key: 4, show: true },
+    ],
+    moved: false,
+    static: false,
+  },
+];
+
+export const U3D_PATH_STATE = {
+  ProjectInfo: 1,
+  AlarmCenter: {
+    3: 53,
+    1: 39,
+    2: 54,
+  },
+  MessageCenter: {
+    1: 54,
+  },
+  FileManagement: [27, 44],
+  Monitor: 100,
+  PlanManagement: {
+    1: 48,
+    2: 49,
+    3: 50,
+    4: 50,
+    5: 51,
+  },
+  Other: {
+    2: 35,
+  },
+};
+
+export const COL_COLS: number = 48;
+export const ROW_COLS: number = 24;

+ 1089 - 0
src/Project/pages/DataMeter/index.less

@@ -0,0 +1,1089 @@
+// body,
+// html {
+//   background: transparent;
+// }
+
+.dataMeter {
+  width: 100vw; 
+  margin: 0 auto;
+  min-height: 100%;
+  background: url('@/Project/assets/newBackground.jpg') no-repeat center;
+  background-size: 100% 100%;
+  box-sizing: border-box;
+  font-family: 'Microsoft YaHei'; //'Microsoft YaHei';
+  color: #fff;
+  user-select: none;
+}
+
+.gridBox {
+  width: 100%;
+  position: relative;
+}
+
+.noScroll {
+  height: 100vh;
+  overflow: hidden;
+}
+
+.btnToUnity {
+  position: absolute;
+  top: 27px;
+  left: 16px;
+  font-size: 20px;
+  z-index: 999;
+}
+
+.item {
+  // background: url('@/assets/dataMeter/conLeft.png') no-repeat 100% 100%;
+  background-size: 100% 100%;
+  position: relative;
+
+  .controlBox {
+    position: absolute;
+    top: 0.1rem;
+    right: 0.3rem;
+    display: flex;
+  }
+
+  .edit {
+    width: 0.16rem;
+    height: 0.16rem;
+    // font-size: 0.2rem;
+    background: url('@/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('@/assets/dataMeter/icon-close.png') no-repeat center;
+    background-size: 100% 100%;
+    cursor: pointer;
+    margin-right: 0.1rem;
+    cursor: pointer;
+  }
+}
+
+.addMask {
+  width: 3rem;
+  height: 3rem;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  color: #fff;
+  top: 160px;
+  left: 50%;
+  transform: translateX(-50%);
+  position: fixed;
+  background: #000;
+  z-index: 10001;
+  font-size: 0.16rem;
+
+  &.active {
+    color: #000;
+    font-weight: bold;
+  }
+}
+
+.mapContent {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  border: 1px solid #2866B2;
+
+  .mapControl {
+    top: 10px;
+    right: 10px;
+    position: absolute;
+    z-index: 100;
+    display: flex;
+    align-items: center;
+  }
+
+  .uploadModal {
+    :global {
+      .ant-tabs-tabpane-active {
+        min-height: 400px;
+      }
+    }
+  }
+
+  .carousel {
+    width: 100%;
+
+    // height: 400px;
+    .carouselItem {
+      width: 100%;
+      background-position: center;
+      background-repeat: no-repeat;
+      background-size: contain;
+      background-color: #000;
+    }
+
+    img {
+      // width: 100%;
+      max-width: 100%;
+      display: block;
+      margin: 0 auto;
+    }
+  }
+
+  .selectList {
+    display: flex;
+    color: #fff;
+    margin-bottom: 0;
+    margin-right: 0.1rem;
+
+    li {
+      padding: 0.04rem 0;
+      margin-right: 0.26rem;
+      cursor: pointer;
+      border-bottom: 1px solid transparent;
+      font-size: 0.12rem;
+
+      &.active {
+        border-bottom: 1px solid #fff;
+      }
+    }
+  }
+
+  .iconUpload {
+    background: url('@/assets/dataMeter/icon-upload.png') no-repeat center;
+    background-size: 100% 100%;
+    cursor: pointer;
+    width: 0.16rem;
+    height: 0.16rem;
+  }
+}
+
+.modelTitleTabs {
+  display: flex;
+  flex-direction: column;
+}
+
+.contentBg {
+  // background: rgba(0, 0, 0, 0.6);
+}
+
+.modelBox {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+  height: 100%;
+  line-height: 1;
+  font-size: 0.14rem;
+  // background: url('@/assets/chartBox/bg.png') no-repeat center;
+  // background-size: 100% 100%;
+  // border: #2866B2 solid 1px;
+  background: linear-gradient(rgba(29, 56, 104, 0.72), rgba(4, 18, 51, 0.76) );
+  border: 1px solid;
+  border-image: linear-gradient(45deg, rgba(74, 196, 253, 0.45), rgba(173, 219, 240, 0.34), rgba(146, 213, 249, 0.28)) 1;
+  // box-shadow: 0px 8px 32px 0px rgba(0, 0, 0, 0.16);
+  opacity: 1;
+
+  .tabBox {
+    width: 100%;
+    padding: 0.16rem;
+    padding-bottom: 0;
+  }
+
+  :global {
+    ::-webkit-scrollbar {
+      background: transparent;
+    }
+
+    .ant-spin-nested-loading {
+      height: auto;
+    }
+
+    .ant-radio-group {
+      display: flex;
+      flex-wrap: wrap;
+      justify-content: flex-start;
+      border-left: none;
+      width: 100%;
+      background: #121921;
+
+      .ant-radio-button-wrapper {
+        border: none !important;
+        background: transparent;
+        font-size: 0.14rem;
+        height: 0.32rem;
+        color: rgba(255, 255, 255, 0.6);
+
+        &::before {
+          display: none;
+        }
+
+        &.ant-radio-button-wrapper-checked {
+          background: #055dff;
+          box-shadow: none;
+          color: #fff;
+        }
+
+        &:first-child,
+        &:last-child {
+          border-radius: 0;
+        }
+      }
+    }
+
+    .ant-progress-inner {
+      // background: #7A96BF;
+    }
+
+    .ant-progress-success-bg,
+    .ant-progress-bg {
+      background: #284d86;
+    }
+
+    .ant-empty {
+      margin-top: 40px;
+    }
+
+    .oc-fm--file-manager {
+      width: 100%;
+    }
+
+    .oc-fm--file-navigator__toolbar {
+      display: none !important;
+    }
+
+    .oc-fm--file-navigator {
+      background: transparent;
+    }
+
+    .oc-fm--list-view__table {
+      color: #fff;
+    }
+
+    .oc-fm--name-cell__icon,
+    .oc-fm--no-files-found-stub {
+      background: transparent;
+    }
+
+    .oc-fm--list-view__row:hover:not(.oc-fm--list-view__row--selected):not(.oc-fm--list-view__row--loading) {
+      // background: rgba(255, 255, 255, 0.3);
+      background: transparent;
+    }
+
+    .oc-fm--location-bar__item {
+      color: rgba(255, 255, 255, 0.6);
+    }
+
+    .oc-fm--location-bar__item:hover {
+      color: #fff;
+      // background: rgba(255, 255, 255, 0.3);
+      background: transparent;
+    }
+
+    .oc-fm--no-files-found-stub__icon {
+      fill: rgba(255, 255, 255, 0.72);
+    }
+
+    .oc-fm--list-view__row--selected {
+      background: #448bf1;
+    }
+  }
+
+  // 项目详情
+  .projectInfoContent {
+    display: flex;
+    justify-content: flex-start;
+    align-items: flex-start;
+    align-content: flex-start;
+    flex-wrap: wrap;
+    padding-top: 0.1rem;
+
+    .detailItem {
+      width: 100%;
+      display: flex;
+      justify-content: flex-start;
+      align-items: flex-start;
+
+      span:first-child {
+        color: #C0F0FF;
+        font-size: 18px;
+        margin-right: 10px;
+        text-align: right;
+        flex-shrink: 0;
+        line-height: 20px;
+      }
+
+      .data {
+        color: #D7E4E8;
+        font-size: 18px;
+        line-height: 24px;
+        letter-spacing: 1px;
+      }
+    }
+
+    .detailRow {
+      display: flex;
+      justify-content: space-between;
+      align-items: flex-start;
+      padding: 14px 0;
+      width: 100%;
+      margin: 0 35px;
+      border-bottom: 1px solid rgba(153, 231, 255, 0.40);
+
+      &:last-child {
+        border-bottom: none;
+      }
+
+      p {
+        margin-bottom: 0;
+      }
+    }
+  }
+
+  // 报警中心
+  .alarmTable {
+    width: 100%;
+    table-layout: fixed;
+    padding-left: 0.14rem;
+
+    // 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;
+        font-weight: 400;
+        color: #183266;
+      }
+
+      td {
+        color: #D7E4E8;
+        ;
+        vertical-align: top;
+        line-height: 1.5;
+        font-weight: 400;
+        font-size: 0.14rem;
+      }
+    }
+
+    .alarmTitle {
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+    }
+  }
+
+  // 消息中心
+  .messageList {
+    padding: 0.16rem;
+    padding-top: 0;
+    padding-bottom: 0;
+    margin: 0;
+
+    li {
+      margin-bottom: 0.06rem;
+    }
+
+    .messageTitle {
+      font-size: 0.12rem;
+      margin-bottom: 0.02rem;
+      color: #13EDF5;
+    }
+
+    .messageContent {
+      color: #D7E4E8;
+      line-height: 1.5;
+    }
+  }
+
+  // 计划管理类
+  .tabsList {
+    display: flex;
+    flex-wrap: wrap;
+    margin: 0;
+    width: -webkit-fill-available;
+    margin: 0.1rem 0.16rem 0 0.16rem;
+    border-bottom: 0.25px solid rgba(216, 216, 216, 0.34);
+    padding: inherit;
+    padding: 0 0 0.06rem 0;
+
+    li {
+      color: #D7E4E8;
+      font-weight: 300;
+      margin-right: 0.1rem;
+      cursor: pointer;
+      margin-bottom: 2px;
+
+      &.active {
+        color: #D7E4E8;
+        font-weight: 400;
+      }
+    }
+  }
+
+  .activeSelect {
+    margin-left: 0.16rem;
+    margin-top: 10px;
+    width: 149px;
+    height: 24px;
+    background: url('@/assets/dataMeter/ProjectList/search_bg.png') no-repeat center;
+    background-size: 100% 100%;
+    cursor: pointer;
+  }
+
+  .other {
+    width: 100%;
+    // height: 100%;
+    table-layout: fixed;
+    padding-left: 0.14rem;
+
+    .messageTitle {
+      font-size: 0.12rem;
+      margin-bottom: 0.02rem;
+      color: rgba(255, 255, 255, 0.6);
+    }
+
+    .messageContent {
+      td {
+        color: #fff;
+        vertical-align: top;
+        line-height: 1.5;
+        font-size: 0.14rem;
+        word-break: break-all;
+        padding-right: 0.1rem;
+      }
+    }
+
+    tr {
+      th {
+        color: #dd7777;
+        padding: 0.04rem 0.02rem;
+        font-size: 0.16rem;
+      }
+    }
+
+    .alarmTitle {
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+    }
+  }
+
+  .chartBox {
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+
+    .planChart {
+      flex: 1 1 auto;
+      width: 100%;
+      height: 100%;
+    }
+
+    :global {
+      .ant-progress-text {
+        color: #fff;
+      }
+    }
+  }
+}
+
+.modelContent {
+  overflow-y: auto;
+  overflow-x: hidden;
+  flex: 1;
+  width: 100%;
+  padding-top: 10px;
+}
+
+// 项目列表
+.projectList {
+  position: absolute;
+  min-width: 100px;
+  top: 0.6rem;
+  right: 0.35rem;
+  background: #121921;
+  margin: 0;
+  padding: 0;
+
+  li {
+    padding: 5px 10px;
+    cursor: pointer;
+
+    &:hover,
+    &:active,
+    &:focus {
+      background: #055dff;
+    }
+  }
+}
+
+// 地图
+.mapBg {
+  // background: url('@/assets/dataMeter/map-bg.png') no-repeat center;
+  background-size: 100% 100%;
+  // background: #010522;
+}
+
+.mapWapper {
+  height: 100%;
+  position: relative;
+  padding-top: 0.14rem;
+}
+
+.mapBox {
+  height: 100%;
+  position: relative;
+
+  .mapIcon {
+    position: absolute;
+    width: 0.12rem;
+    height: 0.12rem;
+    background-position: center;
+    background-repeat: no-repeat;
+    background-size: 100% 100%;
+
+    &.icon1 {
+      top: 0;
+      left: 0;
+      background-image: url('@/assets/dataMeter/map-icon1.png');
+    }
+
+    &.icon2 {
+      top: 0;
+      right: 0;
+      background-image: url('@/assets/dataMeter/map-icon2.png');
+    }
+
+    &.icon3 {
+      bottom: 0;
+      left: 0;
+      background-image: url('@/assets/dataMeter/map-icon4.png');
+    }
+
+    &.icon4 {
+      bottom: 0;
+      right: 0;
+      background-image: url('@/assets/dataMeter/map-icon3.png');
+    }
+  }
+}
+
+.listItem {
+  color: #fff;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 10px;
+  padding-right: 10px;
+
+  .left {
+    display: flex;
+    align-items: center;
+    flex: 1 1 auto;
+    margin-right: 20px;
+    width: 100%;
+    min-width: 0;
+
+    :global {
+      .ant-avatar {
+        flex-shrink: 0;
+      }
+    }
+  }
+
+  .right {
+    flex-shrink: 0;
+  }
+
+  .listInfo {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    margin-left: 10px;
+  }
+}
+
+.videoList {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: flex-start;
+  align-items: flex-start;
+  width: 100%;
+  height: 100%;
+  margin: 0;
+  padding: 0;
+
+  li {
+    position: relative;
+    padding: 10px;
+    width: 50%;
+    height: 50%;
+
+    .bg {
+      background: #000;
+      height: 100%;
+    }
+
+    video {
+      width: 100%;
+      height: 100%;
+    }
+  }
+
+  .fullscreen {
+    position: absolute;
+    cursor: pointer;
+    bottom: 20px;
+    right: 20px;
+    z-index: 100;
+  }
+}
+
+.uploadList {
+  :global {
+    .ant-upload-list {
+      height: 200px;
+      overflow: auto;
+    }
+  }
+}
+
+// 模块列表
+.drawer {
+  width: 502px;    
+  padding: 43px 86px;
+  // box-shadow: -2px 0 8px rgb(0, 0, 0, 0.15);
+  position: fixed;
+  top: 0;
+  right: 0;
+  // background: #0d1a2b;
+  background: url('@/Project/assets/dataMeter/drawLeftBg.png') no-repeat center;
+  background-size: 100% 100%;
+  z-index: 10000;
+
+  .header {
+    position: relative;
+    padding: 16px 24px;
+    height: 45px;
+    // border-bottom: 1px solid #e8e8e8;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    cursor: all-scroll;
+    font-size: 22px;
+    font-weight: 400;
+    color: #183266;
+    line-height: 24px;
+    letter-spacing: 2px;
+    background: linear-gradient(to right,rgba(153, 231, 255, 0.80), rgba(96, 168, 255, 1));
+
+    .title {
+      margin: 0;
+      color: #fff;
+      font-weight: 500;
+      font-size: 0.2rem;
+      line-height: 1;
+      font-family: 'dataMeter';
+    }
+    
+    .closeIcon{
+      width: 26px;
+      height: 26px;
+      background: url('@/Project/assets/newUI/modalClose.png');
+      background-size: 100% 100%;
+      background-repeat: no-repeat;
+      background-position: center;
+    }
+  }
+  .itemContent{
+    width: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+  }
+  .titleTip{
+    width: 304px;
+    height: 36px;
+    display: flex;
+    justify-content: flex-end;
+    align-items: center;
+    margin-bottom: 14px;
+    padding-right: 14px;
+    background: linear-gradient(to right,rgba(153, 231, 255, 0.80), rgba(96, 168, 255, 1));
+    .titleTipIcon{
+      width: 26px;
+      height: 18px;
+      margin-right: 8px;
+    }
+    .iconIn{
+      .titleTipIcon;
+      background: url('@/Project/assets/newUI/icon_in.png');
+      background-size: 100% 100%;
+    }
+    .iconOut{
+      .titleTipIcon;
+      background: url('@/Project/assets/newUI/icon_out.png');
+      background-size: 100% 100%;
+    }
+    .titleTipText{
+      font-size: 14px;
+      font-weight: 400;
+      color: #183266;
+      line-height: 36px;
+    }
+    .titleTipLine{
+      margin: 0 16px;
+      width: 1px;
+      height: 18px;
+      background: #183266;
+    }
+  }
+  .itemIconIn{
+    width: 26px;
+    height: 18px;
+    background: url('@/Project/assets/newUI/icon1_in.png');
+    background-size: 100% 100%;
+  }
+  .itemIconOut{
+    width: 26px;
+    height: 18px;
+    background: url('@/Project/assets/newUI/icon1_out.png');
+    background-size: 100% 100%;
+  }
+  .childIconIn{
+    margin-top: 8px;
+    width: 16px;
+    height: 12px;
+    float: right;
+    background: url('@/Project/assets/newUI/icon1_in.png');
+    background-size: 100% 100%;
+  }
+  .childIconOut{
+    margin-top: 8px;
+    width: 16px;
+    height: 12px;
+    float: right;
+    background: url('@/Project/assets/newUI/icon1_out.png');
+    background-size: 100% 100%;
+  }
+
+  .icon{
+    width: 7px;
+    height: 10px;
+    background: url('@/Project/assets/dataMeter/arrowIcon.png') no-repeat center;
+    background-size: 100% 100%;
+    margin-right: 12px;
+    transition: transform 0.2s;
+  }
+  .itemDiv{
+    color: #C0F0FF;
+    font-size: 16px;
+    line-height: 32px;
+    height: 32px;
+    align-items: center;
+    border-bottom: 1px solid #3a6481;
+    // paddingLeft: 26px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    letter-spacing: 1px;
+  }
+
+  .content {
+    max-height: 80vh;
+    padding: 26px 13px;
+    font-size: 16px;
+    word-wrap: break-word;
+    overflow-y: auto;
+    background: url('@/Project/assets/dataMeter/contentBg.png') no-repeat center;
+    background-size: 100% 100%;
+    .title{
+      font-size: 22px;
+      font-weight: 400;
+      color: #C0F0FF;
+      line-height: 24px;
+      letter-spacing: 2px;
+    }
+
+    :global {
+      .ant-list-bordered{
+        border: none;
+      }
+      .ant-list-bordered .ant-list-item{
+        background: url('~@/Project/assets/dataMeter/itemBg.png') no-repeat center;
+        background-size: 100% 100%;
+        margin-bottom: 14px;
+        border-bottom: 1px solid #6597BA;
+      }
+      
+      .ant-collapse-borderless > .ant-collapse-item > .ant-collapse-content > .ant-collapse-content-box{
+        padding-left: 26px;
+      }
+      .ant-collapse {
+        border: none;
+  
+        >.ant-collapse-item>.ant-collapse-header {
+          color: #183266;
+          background: #C0F0FF;
+          height: auto;
+          font-size: 18px;
+          font-weight: 500;
+          line-height: 22px;
+        }
+        
+      }
+    }
+  }
+}
+
+//模板列表
+.mouldList {
+  width: 598px;
+  position: fixed;
+  top: 0;
+  right: 50px;
+  background: linear-gradient(to right,rgba(9, 20, 64, 0.85), rgba(48, 85, 148, 0.77));
+  z-index: 999;
+  height: 1000px;
+  .header {
+    position: relative;
+    padding: 10px 20px 10px 18px;
+    height: 45px;
+    width: 100%;
+    background: url('@/Project/assets/newUI/theadBg.png') no-repeat center;
+    background-size: 100% 100%;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    cursor: pointer;
+
+    .title {
+      margin: 0;
+      font-size: 22px;
+      font-weight: 400;
+      color: #183266;
+      line-height: 24px;
+      letter-spacing: 2px;
+    }
+
+    .closeIcon{
+      width: 26px;
+      height: 26px;
+      background: url('@/Project/assets/newUI/modalClose.png');
+      background-size: 100% 100%;
+      background-repeat: no-repeat;
+      background-position: center;
+      cursor: pointer;
+    }
+  }
+
+  .content {
+    padding: 20px;
+    min-height: 470px;
+    max-height: 950px;
+    word-wrap: break-word;
+    overflow-y: auto;
+    display: flex;
+    flex-direction: column;
+  }
+
+  .item {
+    width: 100%;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    margin: 20px 0 20px 0;
+
+    .img {
+      background-size: auto;
+      background-position: center;
+      background-repeat: no-repeat;
+      width: 558px;
+      height: 286px;
+      // border: 1px solid #ada1a1;
+      cursor: pointer;
+    }
+
+    .name {
+      font-size: 22px;
+      font-weight: 400;
+      color: #C0F0FF;
+      line-height: 24px;
+      letter-spacing: 2px;
+      width: 100%;
+    }
+
+    .btnGroup {
+      display: flex;
+      width: 100%;
+      flex-direction: row;
+      justify-content: center;
+      align-items: center;
+      border-top: 2px solid #99E7FF;
+      margin-top: 40px;
+      :global {
+        .ant-btn {
+          height: 46px;
+          width: 96px;
+          font-size: 22px;
+          border-radius: 4px;
+          line-height: 1;
+          font-weight: 400;
+          color: #D7E4E8;
+          line-height: 24px;
+          letter-spacing: 2px;
+          margin-top: 20px;
+        }
+      }
+    }
+  }
+}
+
+// pfd模块
+.pfdBox {
+  height: 100%;
+  width: 100%;
+  // margin: 0 auto;
+  position: relative;
+  background-position: left center;
+  background-size: cover;
+
+  // img {
+  //   height: 100%;
+  // }
+  .pfdFont {
+    padding: 5px;
+    position: absolute;
+    white-space: nowrap;
+    user-select: none;
+    transform: translate(-50%, -50%);
+  }
+}
+
+:global {
+  .dataMeter {
+    .react-grid-item>.react-resizable-handle.react-resizable-handle-se {
+      width: 30px;
+      height: 30px;
+      z-index: 100;
+      bottom: 5px;
+      right: 5px;
+    }
+
+    .ant-tooltip-arrow::before {
+      background-color: #fff;
+    }
+
+    .ant-tooltip-inner {
+      background-color: #fff;
+      color: #333;
+    }
+
+    .react-grid-item>.react-resizable-handle::after {
+      right: 0 !important;
+      bottom: 0 !important;
+      width: 10px !important;
+      height: 10px !important;
+      border-right: 4px solid rgb(255 0 0) !important;
+      border-bottom: 4px solid rgb(255 0 0) !important;
+    }
+
+    .ant-collapse>.ant-collapse-item>.ant-collapse-header {
+      padding: 0;
+      padding-left: 5px;
+
+      .ant-collapse-arrow {
+        left: -9px;
+      }
+    }
+
+    .ant-collapse-content>.ant-collapse-content-box {
+      padding: 0;
+    }
+
+    .ant-list,
+    .ant-collapse>.ant-collapse-item>.ant-collapse-header {
+      color: #fff;
+      font-size: 0.18rem;
+    }
+
+    .ant-collapse-content {
+      color: #fff;
+      font-size: 0.16rem;
+    }
+
+    .ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow {
+      font-size: 0.14rem;
+      left: -0.1rem;
+    }
+
+    .ant-collapse-borderless {
+      background: transparent;
+    }
+
+    .ant-collapse-borderless>.ant-collapse-item {
+      border-bottom: none;
+    }
+
+    .dataMeter .ant-collapse>.ant-collapse-item>.ant-collapse-header {
+      padding-left: 0.08rem;
+    }
+
+    .ant-collapse-borderless>.ant-collapse-item>.ant-collapse-content>.ant-collapse-content-box {
+      padding-left: 10px;
+    }
+
+    .ant-select-selection {
+      background-color: transparent;
+      // border: 1px solid #fff;
+      color: #fff;
+    }
+
+    .ant-select-arrow {
+      color: #fff;
+    }
+
+    .ant-select-selection {
+      height: 24px;
+      font-size: 14px;
+    }
+
+    .ant-select-selection__rendered {
+      line-height: 24px;
+    }
+
+    .ant-select-arrow {
+      font-size: 14px;
+      margin-top: -7px;
+      right: 10px;
+    }
+
+    .ant-select-dropdown-menu .ant-select-dropdown-menu-item {
+      font-size: 14px;
+      line-height: 20px;
+    }
+    .ant-col-8{
+      margin-bottom: 18px;
+    }
+    .ant-checkbox + span{
+      font-size: 18px;
+      color: #D7E4E8;
+    }
+  }
+ 
+}
+//遮罩
+.mask {
+  width: 780px;
+  height: 100vh;
+  background: url('@/Project/assets/dataMeter/drawLeftBg.png') no-repeat center;
+  background-size: 100% 100%;
+  z-index: 998;
+  position: fixed;
+  top: 0;
+  right: -80px;
+}

+ 132 - 0
src/Project/pages/DataMeter/index.tsx

@@ -0,0 +1,132 @@
+import GridLayout, { ReactGridLayoutProps } from 'react-grid-layout';
+import 'react-grid-layout/css/styles.css';
+import 'react-resizable/css/styles.css';
+import style from './index.less';
+import {
+  CHILD_MAP,
+  DEFAULT_LAYOUT,
+  COL_COLS,
+  ROW_COLS,
+  DEFAULT_MAP_LAYOUT,
+  U3D_PATH_STATE,
+} from './config';
+import React, { useMemo, useRef, useState } from 'react';
+
+const gridWidth = document.documentElement.clientWidth;
+
+function DataMeter(props: DataMeter.IProps) {
+  // 编辑模块
+  const [editVisible, setEditVisible] = useState(false);
+  // 子模块列表
+  const [currentModel, setCurrentModel] = useState(null);
+  // 抽屉栏显示状态
+  const [visible, setVisible] = useState(false);
+  // 布局信息
+  const [layout, setLayout] = useState<DataMeter.ILayout[]>([]);
+  // 编辑状态
+  const [edit, setEdit] = useState(false);
+  // 子模块多选
+  const [modelCheck, setModelCheck] = useState([]);
+
+  const [dataCenterChild, setDataCenterChild] = useState([]);
+  // map 类型
+  const [type, setType] = useState('1');
+  // 根据col算出高度
+  const [rowHeight, setRowHeight] = useState(
+    (document.documentElement.clientHeight - 70) / ROW_COLS,
+  );
+  const mapEle = useRef();
+  const [hasSwitchSubModule, setHasSwitchSubModule] = useState(false);
+  //模块列表抽屉显示状态
+  const [mouldVisible, setMouldVisible] = useState(false);
+  //创建模板弹窗状态
+  const [createMouldVisible, setCreateMouldVisible] = useState(false);
+
+  // 布局发生改变回调函数
+  const onLayoutChange = (newLayout: DataMeter.ILayout[]) => {
+    // console.log(newLayout);
+    let newArr = newLayout.map((newModel) => {
+      let oldModel = layout.find((item) => item.i == newModel.i) || {};
+      return {
+        // 新属性覆盖旧属性
+        ...oldModel,
+        ...newModel,
+      };
+    });
+    setLayout(newArr);
+    if (!edit && hasSwitchSubModule) {
+      // getMapPosition();
+      setHasSwitchSubModule(false);
+    }
+  };
+
+  const models = useMemo(() => {
+
+  },[layout])
+
+  return (
+    <div
+      className={`dataMeter ${style.dataMeter} ${!edit ? style.noScroll : ''}`}
+      // onDragEnd={onDragEnd}
+      style={{ minHeight: '100vh' }}
+    >
+      {/* <CreateMould
+          visible={createMouldVisible}
+          handleCancel={() => setCreateMouldVisible(false)}
+          handleOk={createMouldHandleOk}
+        ></CreateMould>
+        <DrawerLeft
+          onClose={() => {
+            setVisible(false);
+          }}
+          layout={layout}
+          dragActive={dragActive}
+          addModel={addModel}
+          removeModel={removeModel}
+          // onDrag={onHandleDrag}
+          visible={visible}
+          subModule={subModule}
+          dataCenterChild={dataCenterChild}
+        />
+        {mouldVisible && <div className={style.mask}></div>}
+        <MouldDrawerLeft
+          mouldList={mouldList}
+          onClose={() => {
+            setMouldVisible(false);
+          }}
+          visible={mouldVisible}
+          mouldSave={saveMould}
+        ></MouldDrawerLeft> */}
+      {/*         
+        <EditMould
+          title="编辑子模块"
+          visible={editVisible}
+          onOk={handleOk}
+          onCancel={() => {
+            setEditVisible(false);
+          }}
+          setModelCheck={setModelCheck}
+          modelCheck={modelCheck}
+          setCurrentModel={setCurrentModel}
+          currentModel={currentModel}
+        ></EditMould> */}
+      <div className={style.gridBox}>
+        <GridLayout
+          className="layout"
+          isDraggable={edit}
+          isResizable={edit}
+          layout={layout}
+          onLayoutChange={onLayoutChange}
+          cols={COL_COLS}
+          rowHeight={rowHeight}
+          width={gridWidth}
+          margin={[15, 10]}
+        >
+          {/* {models} */}
+        </GridLayout>
+      </div>
+    </div>
+  );
+}
+
+

+ 53 - 0
src/Project/pages/DataMeter/typings.d.ts

@@ -0,0 +1,53 @@
+declare namespace DataMeter {
+  interface ILayout {
+    i: string;
+    key: string;
+    w: number;
+    h: number;
+    x: number;
+    y: number;
+    child?: ILayoutChild[];
+    moved: boolean;
+    static: boolean;
+    active?: number | string;
+  }
+
+  interface ILayoutChild {
+    title: string;
+    key: number | string;
+    show: boolean;
+    children?: ILayoutChild[];
+  }
+  interface IProps {
+    subModule: number;
+    onClickProject: number;
+    switchModule: number;
+    isNew: boolean;
+    hasModel: boolean;
+  }
+  interface IModelsProps {
+    key: number | string;
+    edit: boolean;
+    hasModel: boolean;
+    child: ILayoutChild[];
+    layout: ILayout;
+    projectId: number;
+    setActive: Function;
+    subModule: number;
+    isNew: boolean;
+    showEditModel: Function;
+    removeModel: Function;
+    children?: React.ReactElement
+  }
+  interface ITitleProps {
+    title: string;
+    showTabs?: boolean;
+    setShowTabs?: Function;
+    width: number;
+  }
+  interface IProjectListProps {
+    edit:boolean,
+    subModule: number,
+    layout: ILayout,
+  }
+}

+ 8 - 20
src/Project/pages/ProjectSelect/index.js

@@ -2,43 +2,32 @@ import React, { useState, useEffect, useRef, useMemo } from 'react';
 import { Switch } from 'antd';
 import style from './index.less';
 import PinyinMatch from 'pinyin-match';
-import { useRequest } from 'umi';
+import { useRequest, useModel } from 'umi';
 import { getProjectList } from '@/Project/services/project';
 import Swiper from 'react-id-swiper';
-import 'swiper/css/swiper.min.css';
+import 'swiper/css/swiper.css';
 
-// console.log(Swiper);
-
-let searchTimer;
 const nodata = require('@/Project/assets/projectSelect/nodata.png');
 
 function ProjectSelect(props) {
+  const { setProject } = useModel('project');
   const {
-    params: { subModule = 1, forbiddenModel },
+    params: { subModule = 1, selectProject },
   } = props;
   const [swiper, updateSwiper] = useState(null);
   const [value, setValue] = useState('');
   // const [curProjectList, SetCurProjectList] = useState([]);
-  const {
-    data: projectRes,
-    error,
-    loading,
-  } = useRequest(() => {
+  const { data: projectRes } = useRequest(() => {
     return getProjectList();
   });
   const projectList = projectRes?.list || [];
   // const inputRef = useRef();
 
-  const sendProjectToUnity = (item) => {
-    // UnityAction.sendMsg('project', item);
+  const handleClickProject = (item) => {
+    setProject(item);
   };
   const onChangeInput = (e) => {
-    // console.log(e)
     setValue(e.target.value);
-    // clearTimeout(searchTimer);
-    // searchTimer = setTimeout(() => {
-    //   onSearch();
-    // }, 700);
   };
   const getPageList = (data) => {
     let list = [];
@@ -92,7 +81,6 @@ function ProjectSelect(props) {
 
   useEffect(() => {
     swiper?.update();
-    console.log(swiper);
   }, [curProjectList]);
   return (
     <div className={style.background}>
@@ -104,7 +92,7 @@ function ProjectSelect(props) {
                 <div key={index}>
                   <Page
                     data={item}
-                    sendProjectToUnity={sendProjectToUnity}
+                    sendProjectToUnity={handleClickProject}
                   ></Page>
                 </div>
               );

+ 234 - 0
src/Project/services/DataMeter.js

@@ -0,0 +1,234 @@
+import request from '@/utils/request';
+import { stringify } from 'qs';
+
+// 获得项目详情
+export async function getProject(params) {
+  return request(`/project/${params.projectId}`);
+}
+
+// 异常报警通知列表
+export async function getFaultAnalysis(params) {
+  return request(`/fault_analysis/result/${params.projectId}`);
+}
+
+// 项目预警
+export async function getProjectAlarm(params) {
+  return request(`/notification/list?${stringify(params)}`);
+}
+
+
+/**
+ * 问题追踪列表
+ * @param {object} params
+ * @param {number} params.projectId
+ * @param {number} params.currentPage
+ * @param {number} params.pageSize
+ * @returns
+ */
+export async function getIssueList(params) {
+  // return request(`/issue/list/${params.projectId}?${stringify(params)}`);
+  return request(`/issue/ticket/list/${params.projectId}?${stringify(params)}`);
+}
+
+/**
+ * 消息推送
+ * @param {object} params
+ * @param {number} params.currentPage
+ * @param {number} params.pageSize
+ * @returns
+ */
+export async function getNotificationList(params) {
+  return request(`/notification/list?${stringify(params)}`);
+}
+
+/**
+ * 上传多媒体资源
+ * @param {object} params
+ * @param {array}  params.files
+ * @param {number} params.projectId
+ * @param {number} params.type  0-图片,1- 视频
+ * @returns
+ */
+
+export async function uploadMedia(params) {
+  return request(`/project-cabin-file/${params.projectId}/${type}`, {
+    method: 'POST',
+    body: {
+      ...params,
+    },
+  });
+}
+
+/**
+ * 多媒体资源查询
+ * @param {object} params
+ * @param {number} params.projectId
+ * @returns
+ */
+
+export async function getMediaList(params) {
+  return request(`/project-file/${params.projectId}/26/-1`);
+}
+
+/**
+ * 视频监控查询
+ * @param {object} params
+ * @param {string} params.currentPage
+ * @param {string} params.pageSize
+ * @param {number} params.projectId
+ * @returns
+ */
+
+export async function getMonitorList(params) {
+  // return request(`/monitor/list/${params.projectId}?${stringify(params)}`);
+  return request(`/monitor_config/query/${params.projectId}`);
+}
+
+/**
+ * 项目动态
+ * @param {object} params
+ * @param {string} params.currentPage
+ * @param {string} params.pageSize
+ * @param {number} params.projectId
+ * @param {number} params.type 动态类型:-1-所有类型, 0-项目动态 1-新闻动态 2-大事记 3-所获奖项
+ * @returns
+ */
+
+export async function getProjectActive(params) {
+  return request(`/project-active/list/${params.projectId}/${params.type}?${stringify(params)}`);
+}
+
+/**
+ * 设计|采购|施工详细进度
+ * @param {object} params
+ * @param {number} params.projectId
+ * @returns
+ */
+
+export async function getProjectProgress(params) {
+  return request(`/project-progress/${params.projectId}`);
+}
+
+/**
+ * 设计|采购|施工总进度
+ * @param {object} params
+ * @param {number} params.projectId
+ * @returns
+ */
+
+export async function getProjectTotalProgress(params) {
+  return request(`/project-plan-progress/${params.projectId}`);
+}
+
+/**
+ * 安全操作规范列表查询
+ * @param {object} params
+ * @param {number} params.projectId
+ * @param {string} params.currentPage
+ * @param {string} params.pageSize
+ * @returns
+ */
+
+export async function getUserGuide(params) {
+  return request(`/user_guide/list/${params.projectId}?${stringify(params)}`);
+}
+
+/**
+ * 项目日志列表查询
+ * @param {object} params
+ * @param {number} params.projectId
+ * @param {string} params.currentPage
+ * @param {string} params.pageSize
+ * @returns
+ */
+
+export async function getDailyList(params) {
+  return request(`/daily/list/${params.projectId}?${stringify(params)}`);
+}
+
+/**
+ * 删除资源
+ * @param {object} params
+ * @param {number} params.id
+ * @returns
+ */
+
+export async function deleteFile(params) {
+  return request(`/delete/project-cabin-file/${params.id}`, {
+    method: 'POST',
+  });
+}
+
+/**
+ * 保存布局配置
+ * @param {object} params
+ * @param {number} params.projectId 项目id,地图页为0
+ * @param {number} params.id 配置id,大于0,则更新
+ * @param {number} params.module 所属模块:1-驾驶舱 2-模型分层 3-管道流向
+ * @param {number} params.sub_module 子模块: (驾驶舱)0-地图驾驶舱 1-建设驾驶舱 2-运营驾驶舱
+ * @param {string} params.config_json 配置json数据
+ * @param {number} params.is_default 是否默认配置,大于1时会校验调用者权限
+ * @returns
+ */
+
+export async function saveLayoutOptions(params) {
+  return request(`/project_config/save/${params.projectId || 0}`, {
+    method: 'POST',
+    body: {
+      ...params,
+    },
+  });
+}
+
+/**
+ * 读取配置
+ * @param {object} params
+ * @param {number} params.projectId 项目id,地图页为0
+ * @param {number} params.module 所属模块:1-驾驶舱 2-模型分层 3-管道流向
+ * @param {number} params.sub_module 子模块: (驾驶舱)0-地图驾驶舱 1-建设驾驶舱 2-运营驾驶舱
+ * @param {number} params.is_default 是否默认配置,大于1时会校验调用者权限
+ * @returns
+ */
+
+export async function getLayoutOptions(params) {
+  return request(`/project_config/query/${params.projectId || 0}?${stringify(params)}`);
+}
+
+// 获取故障列表
+export async function getBreakdownRecord(params) {
+  return request(`/breakdown-record/unhandled/list/${params.projectId}`);
+}
+
+//获取项目列表
+export async function getProjectList(params) {
+  return request(`/api/v2/project?${stringify(params)}`)
+}
+
+//获取巡检结果
+export async function getAutoPatrol(params) {
+  return request(`/patrol/auto/data/${params.projectId}`)
+}
+
+export async function getBreakdownList(params) {
+  return request(`/breakdown-record/list/${params.projectId}?${stringify(params)}`);
+}
+export async function getPatrolRecord(params) {
+  return request(`/patrol/data/${params.projectId}?${stringify(params)}`);
+}
+
+export async function getMouldList(params) {
+  return request(`/project_config/template-list/${params.project_id || 0}?${stringify(params)}`);
+}
+
+export async function createMouldList(params) {
+  return request(`/project_config/save-template/${params.projectId || 0}`, {
+    method: 'POST',
+    headers: { ContentType: 'application/x-www-form-urlencoded' },
+    body: params.formData,
+  });
+}
+export async function getAutoPatrolByRouteId(params) {
+  return request(`/patrol/data/${params.projectId}/${params.routeId}`)
+}
+
+

+ 4 - 0
src/Project/services/typings.d.ts

@@ -42,6 +42,9 @@ declare namespace Api {
     MainProcess: string;
     ImageUrl: string;
     ConstructionUnit: string;
+    Scale: string;
+    CooperateMode: string;
+    ContractTime: string;
     CompanyName: string;
     Accountant: number;
     CarouselFlag: number;
@@ -65,6 +68,7 @@ declare namespace Api {
     TechnicalDesigner: number;
     WtyManager: number;
     Leader: IUser | null;
+    ElectricalDesignerUser: IUser | null;
     WtyManagerUser: IUser | null;
     TechnicalDesignerUser: IUser | null;
     SiteManagerUser: IUser | null;

+ 12 - 0
src/models/project.ts

@@ -0,0 +1,12 @@
+// 全局共享数据示例
+import { useState } from 'react';
+
+const useProject = () => {
+  const [project, setProject] = useState<Api.IProject>();
+  return {
+    project,
+    setProject,
+  };
+};
+
+export default useProject;

+ 72 - 1
yarn.lock

@@ -2087,6 +2087,13 @@
   dependencies:
     "@types/react" "*"
 
+"@types/react-grid-layout@^1.3.2":
+  version "1.3.2"
+  resolved "https://registry.npmmirror.com/@types/react-grid-layout/-/react-grid-layout-1.3.2.tgz#9f195666a018a5ae2b773887e3b552cb4378d67f"
+  integrity sha512-ZzpBEOC1JTQ7MGe1h1cPKSLP4jSWuxc+yvT4TsAlEW9+EFPzAf8nxQfFd7ea9gL17Em7PbwJZAsiwfQQBUklZQ==
+  dependencies:
+    "@types/react" "*"
+
 "@types/react@*", "@types/react@^18.0.0":
   version "18.0.26"
   resolved "https://registry.npmmirror.com/@types/react/-/react-18.0.26.tgz#8ad59fc01fef8eaf5c74f4ea392621749f0b7917"
@@ -3315,6 +3322,11 @@ clone-regexp@^2.1.0:
   dependencies:
     is-regexp "^2.0.0"
 
+clsx@^1.1.1:
+  version "1.2.1"
+  resolved "https://registry.npmmirror.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
+  integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
+
 color-convert@^1.9.0:
   version "1.9.3"
   resolved "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
@@ -3877,6 +3889,14 @@ eastasianwidth@^0.2.0:
   resolved "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
   integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
 
+echarts@^5.4.1:
+  version "5.4.1"
+  resolved "https://registry.npmmirror.com/echarts/-/echarts-5.4.1.tgz#d7f65a584d78beff62568d878b16151b3381811c"
+  integrity sha512-9ltS3M2JB0w2EhcYjCdmtrJ+6haZcW6acBolMGIuf01Hql1yrIV01L1aRj7jsaaIULJslEP9Z3vKlEmnJaWJVQ==
+  dependencies:
+    tslib "2.3.0"
+    zrender "5.4.1"
+
 electron-to-chromium@^1.4.251:
   version "1.4.284"
   resolved "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592"
@@ -5705,6 +5725,11 @@ lodash.debounce@^4.0.8:
   resolved "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
   integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
 
+lodash.isequal@^4.0.0:
+  version "4.5.0"
+  resolved "https://registry.npmmirror.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
+  integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
+
 lodash.merge@^4.6.2:
   version "4.6.2"
   resolved "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
@@ -6801,7 +6826,7 @@ process@^0.11.10:
   resolved "https://registry.npmmirror.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
   integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
 
-prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.7.2, prop-types@^15.8.1:
+prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.7.2, prop-types@^15.8.1:
   version "15.8.1"
   resolved "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
   integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -7399,6 +7424,14 @@ react-dom@18.1.0:
     loose-envify "^1.1.0"
     scheduler "^0.22.0"
 
+react-draggable@^4.0.0, react-draggable@^4.0.3:
+  version "4.4.5"
+  resolved "https://registry.npmmirror.com/react-draggable/-/react-draggable-4.4.5.tgz#9e37fe7ce1a4cf843030f521a0a4cc41886d7e7c"
+  integrity sha512-OMHzJdyJbYTZo4uQE393fHcqqPYsEtkjfMgvCHr6rejT+Ezn4OZbNyGH50vv+SunC1RMvwOTSWkEODQLzw1M9g==
+  dependencies:
+    clsx "^1.1.1"
+    prop-types "^15.8.1"
+
 react-error-overlay@6.0.9:
   version "6.0.9"
   resolved "https://registry.npmmirror.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a"
@@ -7409,6 +7442,17 @@ react-fast-compare@^3.2.0:
   resolved "https://registry.npmmirror.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
   integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==
 
+react-grid-layout@^1.2.5:
+  version "1.3.4"
+  resolved "https://registry.npmmirror.com/react-grid-layout/-/react-grid-layout-1.3.4.tgz#4fa819be24a1ba9268aa11b82d63afc4762a32ff"
+  integrity sha512-sB3rNhorW77HUdOjB4JkelZTdJGQKuXLl3gNg+BI8gJkTScspL1myfZzW/EM0dLEn+1eH+xW+wNqk0oIM9o7cw==
+  dependencies:
+    clsx "^1.1.1"
+    lodash.isequal "^4.0.0"
+    prop-types "^15.8.1"
+    react-draggable "^4.0.0"
+    react-resizable "^3.0.4"
+
 react-helmet-async@1.3.0:
   version "1.3.0"
   resolved "https://registry.npmmirror.com/react-helmet-async/-/react-helmet-async-1.3.0.tgz#7bd5bf8c5c69ea9f02f6083f14ce33ef545c222e"
@@ -7482,6 +7526,14 @@ react-refresh@0.14.0, react-refresh@^0.14.0:
   resolved "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e"
   integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==
 
+react-resizable@^3.0.4:
+  version "3.0.4"
+  resolved "https://registry.npmmirror.com/react-resizable/-/react-resizable-3.0.4.tgz#aa20108eff28c52c6fddaa49abfbef8abf5e581b"
+  integrity sha512-StnwmiESiamNzdRHbSSvA65b0ZQJ7eVQpPusrSmcpyGKzC0gojhtO62xxH6YOBmepk9dQTBi9yxidL3W4s3EBA==
+  dependencies:
+    prop-types "15.x"
+    react-draggable "^4.0.3"
+
 react-router-dom@6.3.0:
   version "6.3.0"
   resolved "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-6.3.0.tgz#a0216da813454e521905b5fa55e0e5176123f43d"
@@ -7506,6 +7558,13 @@ react-sortable-hoc@^2.0.0:
     invariant "^2.2.4"
     prop-types "^15.5.7"
 
+react-zmage@^0.8.5-beta.37:
+  version "0.8.5-beta.37"
+  resolved "https://registry.npmmirror.com/react-zmage/-/react-zmage-0.8.5-beta.37.tgz#a310a2ea273685021e098edd10505011b3f3b778"
+  integrity sha512-6US4DVozc2IdqxHCrMAejDy3H08B95Rc/LeFMnHBblE0wagKbqbQlHfwgLRUcdPBBKCtm1KuWNKiN3MXurrcOg==
+  dependencies:
+    classnames "^2.3.1"
+
 react@18.1.0:
   version "18.1.0"
   resolved "https://registry.npmmirror.com/react/-/react-18.1.0.tgz#6f8620382decb17fdc5cc223a115e2adbf104890"
@@ -8520,6 +8579,11 @@ trim-newlines@^3.0.0:
   resolved "https://registry.npmmirror.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144"
   integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==
 
+tslib@2.3.0:
+  version "2.3.0"
+  resolved "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
+  integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
+
 tslib@^1.8.1:
   version "1.14.1"
   resolved "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
@@ -8984,3 +9048,10 @@ yocto-queue@^0.1.0:
   version "0.1.0"
   resolved "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
   integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
+
+zrender@5.4.1:
+  version "5.4.1"
+  resolved "https://registry.npmmirror.com/zrender/-/zrender-5.4.1.tgz#892f864b885c71e1dc25dcb3c7a4ba42678d3f11"
+  integrity sha512-M4Z05BHWtajY2241EmMPHglDQAJ1UyHQcYsxDNzD9XLSkPDqMq4bB28v9Pb4mvHnVQ0GxyTklZ/69xCFP6RXBA==
+  dependencies:
+    tslib "2.3.0"