Parcourir la source

feat: hard 3d show

touchitvoid il y a 3 ans
Parent
commit
c874cd15ae

+ 25 - 9
.idea/workspace.xml

@@ -2,14 +2,18 @@
 <project version="4">
   <component name="ChangeListManager">
     <list default="true" id="e134edf7-cd39-4d8a-9fa6-b64c2a732b8a" name="Default Changelist" comment="">
-      <change afterPath="$PROJECT_DIR$/src/pages/head-mast/components/bindChanle/index.tsx" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/src/pages/head-mast/data.d.ts" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/src/pages/head-mast/hook.ts" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/src/pages/head-mast/index.tsx" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/src/pages/hard/components/head-mast.tsx" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/src/pages/hard/hooks.ts" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/src/config/menuConfig.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/config/menuConfig.tsx" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/src/router/config.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/router/config.tsx" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/src/router/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/router/index.tsx" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/compontens/table/index.scss" beforeDir="false" afterPath="$PROJECT_DIR$/src/compontens/table/index.scss" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/compontens/table/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/compontens/table/index.tsx" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/contants.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/contants.ts" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/hooks/iot.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/hooks/iot.ts" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/pages/hard/components/3d-component.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/hard/components/3d-component.tsx" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/pages/hard/components/boxContainer/index.scss" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/hard/components/boxContainer/index.scss" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/pages/hard/index.scss" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/hard/index.scss" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/pages/hard/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/hard/index.tsx" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/pages/index/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/index/index.tsx" afterDir="false" />
     </list>
     <option name="SHOW_DIALOG" value="false" />
     <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -37,6 +41,9 @@
     <setting file="file://$PROJECT_DIR$/src/compontens/button/sendcode.tsx" root0="FORCE_HIGHLIGHTING" />
   </component>
   <component name="ProjectId" id="1uFad3rNCKxFq2iO5sZvT112jPT" />
+  <component name="ProjectLevelVcsManager">
+    <ConfirmationsSetting value="2" id="Add" />
+  </component>
   <component name="ProjectViewState">
     <option name="hideEmptyMiddlePackages" value="true" />
     <option name="showLibraryContents" value="true" />
@@ -47,7 +54,7 @@
     <property name="WebServerToolWindowFactoryState" value="false" />
     <property name="last_opened_file_path" value="$PROJECT_DIR$/src/pages/head-mast" />
     <property name="nodejs_package_manager_path" value="yarn" />
-    <property name="settings.editor.selected.configurable" value="MTHome" />
+    <property name="settings.editor.selected.configurable" value="preferences.pluginManager" />
     <property name="ts.external.directory.path" value="$PROJECT_DIR$/node_modules/typescript/lib" />
     <property name="vue.rearranger.settings.migration" value="true" />
   </component>
@@ -80,7 +87,16 @@
       <workItem from="1624518277506" duration="7545000" />
       <workItem from="1624585683521" duration="9000" />
       <workItem from="1624956776578" duration="1834000" />
-      <workItem from="1625035060479" duration="4147000" />
+      <workItem from="1625035060479" duration="20224000" />
+      <workItem from="1625190188707" duration="21016000" />
+      <workItem from="1625448641258" duration="29757000" />
+      <workItem from="1625556842676" duration="5972000" />
+      <workItem from="1625622159648" duration="164000" />
+      <workItem from="1625622541817" duration="44000" />
+      <workItem from="1625625423352" duration="4922000" />
+      <workItem from="1625644149171" duration="1921000" />
+      <workItem from="1625727905329" duration="22558000" />
+      <workItem from="1626052480143" duration="4167000" />
     </task>
     <servers />
   </component>

+ 26 - 0
src/compontens/table/index.scss

@@ -1,3 +1,29 @@
+.table-box {
+  width: 100%;
+  .table-pagination {
+    display: -webkit-flex;
+    padding: 10px;
+    box-sizing: border-box;
+    font-size: 13px;
+    color: hsla(0, 0%, 100%, .85);
+    line-height: 20px;
+    div {
+      padding: 0 10px;
+      cursor: pointer;
+      user-select: none;
+      &.current {
+        font-size: 15px;
+        font-weight: bold;
+        color: white;
+      }
+    }
+    span {
+      display: -webkit-flex;
+      align-items: center;
+      user-select: none;
+    }
+  }
+}
 .table-wrapper {
   width: 100%;
   thead{

+ 108 - 27
src/compontens/table/index.tsx

@@ -1,5 +1,7 @@
 import * as React from 'react';
 import './index.scss';
+import {useState} from "react";
+import { LeftOutlined, RightOutlined } from "@ant-design/icons"
 export interface columsProps {
   title: string;
   datakey: string | number;
@@ -10,38 +12,117 @@ interface Iprops {
   colums: columsProps[];
   dataSource: any[];
   rowKey: (any) => any;
+  onRowClick: (any) => any;
+  total?: any,
+  onPagination?: (any) => any,
+  showPagination?: boolean
 }
 const Table: React.FC<Iprops> = (props) => {
-  const { colums, dataSource, rowKey } = props;
+  const { colums, dataSource, rowKey, total, onPagination, showPagination = true } = props;
+
+  const [pagination, setPagination] = useState({
+    total: 100,
+    pageSize: 10,
+    page: [],
+    current: 1
+  })
+  React.useEffect(() => {
+    const pages = []
+    const page = Math.ceil(total / pagination.pageSize)
+    for (let i = 0; i < page; i++) {
+      // @ts-ignore
+      pages.push(i+1)
+    }
+    setPagination(prevState => ({
+      ...prevState,
+      page: pages
+    }))
+    setPagination(prevState =>({
+      ...prevState,
+      current: 1
+    }))
+  }, [total])
+
+  React.useEffect(() => {
+    if (onPagination && total) {
+      onPagination(pagination.current)
+    }
+  }, [pagination.current])
+
+  const flip = (type) => {
+    const { current, page } = pagination
+    if (type === 'next') {
+      if (current < page.length) {
+        setPagination(prevState => ({
+          ...prevState,
+          current: prevState.current + 1
+        }))
+      }
+      return
+    }
+    if (current > 1) {
+      setPagination(prevState => ({
+        ...prevState,
+        current: prevState.current - 1
+      }))
+    }
+  }
+
   return (
-    <table cellPadding={0} cellSpacing={0} className='table-wrapper'>
-      <thead>
-        <tr>
-          {colums.map((element) => {
+      <div className="table-box">
+        <table cellPadding={0} cellSpacing={0} className='table-wrapper'>
+          <thead>
+          <tr>
+            {colums.map((element) => {
+              return (
+                <th align='left' key={element.datakey}>
+                  {element.title}
+                </th>
+              );
+            })}
+          </tr>
+          </thead>
+          <tbody>
+          {dataSource.map((element) => {
             return (
-              <th align='left' key={element.datakey}>
-                {element.title}
-              </th>
+              <tr onClick={() => {
+                props.onRowClick(element)
+              }} key={rowKey(element)}>
+                {colums.map((item) => {
+                  return (
+                    <td align='left' key={item.datakey}>
+                      {item.render ? item.render(element) : element[item.datakey]}
+                    </td>
+                  );
+                })}
+              </tr>
             );
           })}
-        </tr>
-      </thead>
-      <tbody>
-        {dataSource.map((element) => {
-          return (
-            <tr key={rowKey(element)}>
-              {colums.map((item) => {
+          </tbody>
+        </table>
+        {
+          showPagination && <div className="table-pagination">
+            <LeftOutlined onClick={() => flip('prev')} />
+            {
+              pagination.page.map(page => {
                 return (
-                  <td align='left' key={item.datakey}>
-                    {item.render ? item.render(element) : element[item.datakey]}
-                  </td>
-                );
-              })}
-            </tr>
-          );
-        })}
-      </tbody>
-    </table>
-  );
-};
+                  <div
+                    className={(pagination.current === page) ? 'current' : ''}
+                    key={'index'+page}
+                    onClick={() => {
+                      setPagination(prevState => ({
+                        ...prevState,
+                        current: page
+                      }))
+                    }}
+                  >{ page }</div>
+                )
+              })
+            }
+            <RightOutlined onClick={() => flip('next')}/>
+          </div>
+        }
+      </div>
+  )
+}
 export default Table;

+ 33 - 1
src/contants.ts

@@ -139,4 +139,36 @@ export const PERSON_TYPE = {
 export const CERTIFICATE_TYPE = {
 	1: '居民身份证',
 	2: '护照',
-};
+}
+
+
+/*
+ angle 倾角 (°)
+back turn 回转 (°)
+hight 高度 (m)
+moment 力矩 (%)
+scope 幅度(m)
+weight 载重  (t)
+wind_speed 风速 (m/s)
+* */
+export const HEADMAST_TYPE = {
+  alarm_reason: '',
+  angle: '倾角',
+  back_turn: '回转',
+  date: '日期',
+  hight: '高度',
+  moment: '力矩',
+  scope: '幅度',
+  weight: '载重',
+  wind_speed: '风速'
+}
+export const HEADMAST_COMPANY = {
+  date: '',
+  angle: '°',
+  back_turn: '°',
+  hight: 'm',
+  moment: '%',
+  scope: 'm',
+  weight: 't',
+  wind_speed: 'm/s'
+}

+ 2 - 0
src/hooks/iot.ts

@@ -2,6 +2,8 @@ import {useState, useEffect} from 'react'
 import Request from "../utils/request"
 
 interface IotCopumns {
+  device_code: number | undefined;
+  device_name: any;
   id: number
   type_code: number
   type_name: string

+ 108 - 61
src/pages/hard/components/3d-component.tsx

@@ -3,7 +3,7 @@ import * as THREE from "three"
 import FbxFile from '../../../assets/3d/head-mast.fbx'
 import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
 import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader'
-import button from "../../../compontens/button/sendcode";
+import { HEADMAST_TYPE } from '../../../contants'
 
 const createCanvasText = (text, color = '#ffffff') => {
   const canvas = document.createElement('canvas')
@@ -14,40 +14,98 @@ const createCanvasText = (text, color = '#ffffff') => {
   return canvas
 }
 
-const ThreeComponent = () => {
-  const [modelPosition, setModelPosition] = React.useState({ x: -200,y: -115,z: 320 })
-  const [loadProcess, setLoadProcess] = React.useState('0%')
-  React.useEffect(() => {
-    const rootEle:any = document.getElementById('three-box')
-    const scene = new THREE.Scene()
-    const camera = new THREE.PerspectiveCamera(50, rootEle.clientWidth / rootEle.clientHeight, 1, 2000)
-    // default xyz
-    const { x, y, z } = modelPosition
-    camera.position.set(x, y, z)
-    // const helper = new THREE.CameraHelper( camera )
-    // scene.add( helper )
-    // 迪迦
-    const hemisphereLight = new THREE.AmbientLight(0xffffff, 1)
-    // hemisphereLight.position.set(0, 50, 60);
-    scene.add(hemisphereLight)
+const initThree:any = (spriteList: Array<any> = [], rootEle, setLoadProcess) => {
+  // const renderer = new THREE.WebGLRenderer({antialias: true, alpha: true});
+  // 已经存在场景
+  if (initThree.scene) {
+    initThree.scene.children = initThree.scene.children.filter(item => {
+      return ['AmbientLight', 'Group'].includes(item.type)
+    })
+    // reload
+    AddSpriteList(initThree.scene, spriteList)
+    initThree.renderer.render(initThree.scene, initThree.camera);
+    return
+  }
+  // cache
+  initThree.scene =  new THREE.Scene()
+  initThree.camera = new THREE.PerspectiveCamera(50, rootEle.clientWidth / rootEle.clientHeight, 1, 2000)
+  initThree.renderer = new THREE.WebGLRenderer({antialias: true, alpha: true});
+
+  const { scene, camera, renderer } = initThree
+  // const scene = new THREE.Scene()
+  // const camera = new THREE.PerspectiveCamera(50, rootEle.clientWidth / rootEle.clientHeight, 1, 2000)
 
-    const renderer = new THREE.WebGLRenderer({antialias: true, alpha: true});
-    renderer.setClearColor(0x000000, 0);
+  // default xyz
+  const [x, y, z] = [-200, -115, 320]
+  camera.position.set(x, y, z)
+  // const helper = new THREE.CameraHelper( camera )
+  // scene.add( helper )
+  // 迪迦
+  const hemisphereLight = new THREE.AmbientLight(0xffffff, 1)
+  // hemisphereLight.position.set(0, 50, 60);
+  scene.add(hemisphereLight)
+
+  renderer.setClearColor(0x000000, 0);
+  if (rootEle.childNodes[0]) {
+    rootEle.replaceChild(renderer.domElement, rootEle.childNodes[0])
+  } else {
     rootEle.appendChild(renderer.domElement)
-    renderer.setSize(rootEle.clientWidth, rootEle.clientHeight);
+  }
+  renderer.setSize(rootEle.clientWidth, rootEle.clientHeight);
+  // 加载fbx
+  const loader = new FBXLoader()
+  loader.load(FbxFile, (fbx) => {
+    // const axesHelper = new THREE.AxesHelper( 200 );
+    // scene.add( axesHelper );
+    fbx.scale.set(0.05, 0.05, 0.05)
+    scene.add(fbx)
+    renderer.render(scene, camera)
+    console.log(scene)
+  }, ({loaded, total}) => {
+    const process:any = (loaded / total * 100).toFixed(2)
+    return setLoadProcess(process)
+  })
+  AddSpriteList(scene, spriteList)
+  // renderer.render(scene, camera)
+  // add camera controller
+  const controls = new OrbitControls(camera, renderer.domElement);
+  controls.addEventListener("change", () => {
     renderer.render(scene, camera);
-    // 加载fbx
-    const loader = new FBXLoader()
-    loader.load(FbxFile, (fbx) => {
-      // const axesHelper = new THREE.AxesHelper( 200 );
-      // scene.add( axesHelper );
-      fbx.scale.set(0.05, 0.05, 0.05)
-      scene.add(fbx)
-      renderer.render(scene, camera)
-    }, ({loaded, total}) => {
-      setLoadProcess(`${(loaded / total * 100).toFixed(2)}%`)
-    })
+  })
+}
+
+const AddSpriteList = (scene, spriteList) => {
+  spriteList.forEach((info, index) => {
+    const texture = new THREE.Texture(createCanvasText(info.text))
+    texture.needsUpdate = true
+    const spriteMaterial = new THREE.SpriteMaterial({map: texture})
+    const sprite = new THREE.Sprite(spriteMaterial)
+    sprite.scale.set(60, 30, 1)
+    sprite.position.set(info.position[0], info.position[1], info.position[2])
+    scene.add(sprite)
+    // create 3d object line
+    const lineMaterial = new THREE.LineBasicMaterial({color: 0xffffff})
+    const geometry = new THREE.BufferGeometry().setFromPoints(info.points)
+    const line = new THREE.Line(geometry, lineMaterial)
+    line.scale.set(4, 4, 1)
+    line.position.set(info.line[0], info.line[1], info.line[2])
+    scene.add(line)
+  })
+}
+
+interface ThreeComponentProps {
+  baseData: any
+}
+
+const ThreeComponent: React.FC<ThreeComponentProps> = (props) => {
+  const [modelPosition, setModelPosition] = React.useState({ x: -200,y: -115,z: 320 })
+  const [loadProcess, setLoadProcess] = React.useState(0)
+
+  const rootEle:any = document.getElementById('three-box')
 
+  React.useEffect(() => {
+    const textArr = ['塔臂高', '后臂长', '前臂长', '塔帽高']
+    if (!props.baseData.base) return
     const spriteList = [
       {
         text: 'Block_信息展示1',
@@ -85,41 +143,30 @@ const ThreeComponent = () => {
           new THREE.Vector3(0, 0, 30)
         ]
       }
-    ]
-    spriteList.forEach((info, index) => {
-      const texture = new THREE.Texture(createCanvasText(info.text))
-      texture.needsUpdate = true
-      const spriteMaterial = new THREE.SpriteMaterial({map: texture})
-      const sprite = new THREE.Sprite(spriteMaterial)
-      sprite.scale.set(60, 30, 1)
-      sprite.position.set(info.position[0], info.position[1], info.position[2])
-      scene.add(sprite)
-      // create 3d object line
-      const lineMaterial = new THREE.LineBasicMaterial({color: 0xffffff})
-      const geometry = new THREE.BufferGeometry().setFromPoints(info.points)
-      const line = new THREE.Line(geometry, lineMaterial)
-      line.scale.set(4, 4, 1)
-      line.position.set(info.line[0], info.line[1], info.line[2])
-      scene.add(line)
-    })
-    // add camera controller
-    const controls = new OrbitControls(camera, renderer.domElement);
-    controls.addEventListener("change", () => {
-      renderer.render(scene, camera);
-      const { x, y ,z } = camera.position
-      setModelPosition({
-        x, y, z
-      })
+    ].map((item, index) => {
+      const key = Object.keys(props.baseData.base)[index]
+      item.text = `${textArr[index]}:${(props.baseData.base[key])}`
+      return item
     })
-  }, [])
+    initThree(spriteList, rootEle, setLoadProcess)
+  }, [props.baseData.base])
 
-  return <div>
-    { loadProcess !== '100.00%' && loadProcess }
+  const LoadComponent = () => (
+    <div className='load-process'>
+      <div className='load-process-bar'>
+        <div className='bar' style={{
+          transform: `translate(${loadProcess}%, 0)`
+        }}/>
+      </div>
+    </div>
+  )
+  return <React.Fragment>
+    { (loadProcess < 100) && LoadComponent() }
     {/*<div>*/}
     {/*  x: { modelPosition.x } y: { modelPosition.y } z: { modelPosition.z }*/}
     {/*</div>*/}
     <div id="three-box"/>
-  </div>
+  </React.Fragment>
 }
 
-export default ThreeComponent
+export default ThreeComponent

+ 1 - 1
src/pages/hard/components/boxContainer/index.scss

@@ -19,7 +19,7 @@
         }
         
     }
-    .hadware-box-body{
+    .hadware-box-body {
         padding: 0 12px;
         box-sizing: border-box;
     }

+ 34 - 0
src/pages/hard/components/head-mast.tsx

@@ -0,0 +1,34 @@
+import * as React from 'react'
+import ThreeComponent from "./3d-component";
+import { HEADMAST_TYPE, HEADMAST_COMPANY } from "../../../contants";
+
+interface ComponentProps {
+  baseState: any
+}
+
+const HeadMast: React.FC<ComponentProps> = (props:any) => {
+  const headMastState = props.baseState
+  return (
+    <React.Fragment>
+      <div className="hard-head-mast">
+        <ThreeComponent baseData={headMastState} />
+      </div>
+      <div className="info-record-box">
+        {Object.keys(headMastState.data).filter(key => HEADMAST_TYPE[key]).map((key,index) => {
+          const value = typeof headMastState.data[key] === 'number' ? headMastState.data[key].toFixed(2) : headMastState.data[key]
+          return (
+            <div className='info-record' key={index}>
+              <span className='info-record-param' style={{ color: '#0976B6' }}>{HEADMAST_TYPE[key]}</span>
+              <span className='info-record-value'>
+                { value }&nbsp;
+                <span className='info-record-company'>{ HEADMAST_COMPANY[key] }</span>
+              </span>
+            </div>
+          );
+        })}
+      </div>
+    </React.Fragment>
+  )
+}
+
+export default HeadMast

+ 103 - 0
src/pages/hard/hooks.ts

@@ -0,0 +1,103 @@
+import * as React from 'react'
+import Request from '../../utils/request';
+import { HEADMAST_COMPANY } from '../../contants'
+
+
+export const useHeadMastInfo = () => {
+  const [state, setState] = React.useState({
+    base: null,
+    data: {
+      "alarm_reason": "string",
+      "angle": 0,
+      "back_turn": 0,
+      "date": "string",
+      "hight": 0,
+      "moment": "string",
+      "scope": 0,
+      "weight": 0,
+      "wind_speed": 0
+    }
+  })
+  const request = (params) => {
+    return Request.sendRequest({
+      url: '/v1/tower/last',
+      params
+    })
+      .then((data: any) => {
+        setState((preState) => Object.assign({}, preState, data))
+      })
+      .catch((error) => Promise.reject(error))
+  };
+  return { state, request } as const;
+}
+
+export const useHeadMastHistory = () => {
+  const [state, setState] = React.useState({
+    list: [],
+    total: 0
+  })
+  const request = (params) => {
+    return Request.sendRequest({
+      url: '/v1/tower/history',
+      params
+    })
+      .then((data: any) => {
+        setState((preState) => {
+          data.list = data.list.map(obj => {
+            Object.keys(obj).map(key => {
+              obj[key] = obj[key] + HEADMAST_COMPANY[key]
+            })
+            return obj
+          })
+          return data
+        });
+      })
+      .catch((error) => {
+        return Promise.reject(error);
+      });
+  };
+  return { state, request } as const;
+}
+
+export const useHeadMastList = () => {
+  const [state, setState] = React.useState({
+    list: [],
+    total: 0
+  })
+  const request = (params?: any) => {
+    return Request.sendRequest({
+      url: '/v1/device/tower_list',
+      params: {
+        status: 1,
+        page_size: 99999,
+        ...params
+      }
+    })
+      .then((data: any) => {
+        setState((preState) => (data))
+      })
+      .catch((error) => {
+        return Promise.reject(error);
+      });
+  };
+  return { state, request } as const;
+}
+export const useDeviceStateTotal = () => {
+  const [state, setState] = React.useState({
+    offline: 0,
+    online: 0
+  })
+  const request = (params) => {
+    return Request.sendRequest({
+      url: '/v1/device/state_count',
+      params
+    })
+      .then((data: any) => {
+        setState((preState) => (data));
+      })
+      .catch((error) => {
+        return Promise.reject(error);
+      });
+  };
+  return { state, request } as const;
+}

+ 90 - 14
src/pages/hard/index.scss

@@ -1,11 +1,7 @@
-#three-box {
-  width: 900px;
-  height: 650px;
-}
 .hard-wrapper{
     display: flex;
     .hard-left{
-        width: 400px;
+        min-width: 400px;
         .iot-wrapper{
             min-height: 356px;
             margin-bottom: 20px;
@@ -39,8 +35,19 @@
             }
         }
         .iot-device-wrapper{
-            min-height: 380px;
+            height: 380px;
             background-image: url("./static/checked-iot-bg.png");
+            display: -webkit-flex;
+            flex-direction: column;
+            padding-bottom: 10px;
+            box-sizing: border-box;
+            .hadware-box-header {
+              flex-basis: 56px;
+            }
+            .hadware-box-body {
+              overflow-y: auto;
+              flex: 1;
+            }
             .iot-device-moreInfo{
                 font-size: 12px;
                 .iot-online{
@@ -103,7 +110,7 @@
                 }
             }
             .hard-info-wrapper{
-                padding: 60px 20px 0 20px;
+                padding: 60px 0 0 20px;
                 box-sizing: border-box;
                 display: flex;
                 justify-content: space-between;
@@ -111,6 +118,59 @@
                 font-size: 14px;
                 .hard-head-mast {
                   position: relative;
+                  flex: 1;
+                  .load-process {
+                    width: calc(100% - 20px);
+                    height: 100%;
+                    background-color: rgba(0, 0, 0, .1);
+                    display: -webkit-flex;
+                    position: absolute;
+                    box-sizing: border-box;
+                    padding: 0 20px;
+                    .load-process-bar {
+                      width: 100%;
+                      height: 32px;
+                      background-color: white;
+                      position: relative;
+                      top: 180px;
+                      display: -webkit-flex;
+                      align-items: center;
+                      overflow-x: hidden;
+                      box-sizing: border-box;
+                      &::after {
+                        content: 'LOADING FILES...';
+                        font-family: 'lcd';
+                        font-size: 17px;
+                        letter-spacing: 3px;
+                        color: white;
+                        width: 100%;
+                        height: 100%;
+                        display: -webkit-flex;
+                        align-items: center;
+                        padding-bottom: 5px;
+                        box-sizing: border-box;
+                        justify-content: center;
+                        position: absolute;
+                        top: 0;
+                        left: 0;
+                        z-index: 2;
+                        text-align: center;
+                      }
+                      .bar {
+                        width: 100%;
+                        height: calc(100% - 10px);
+                        background-color: #1BC794;
+                        position: relative;
+                        left: -100%;
+                        transition: all 1s;
+                        will-change: auto;
+                      }
+                    }
+                  }
+                  #three-box {
+                    max-width: 100%;
+                    height: 620px;
+                  }
                   img {
                     width: 520px;
                     display: block;
@@ -127,19 +187,35 @@
                     }
                   }
                 }
-                .info-record{
-                    width: 222px;
-                    height: 40px;
-                    margin-bottom: 10px;
-                    padding: 0 10px;
+                .info-record-box {
+                  box-sizing: border-box;
+                  padding-right: 16px;
+                }
+                .info-record {
+                    width: 250px;
+                    height: 52px;
+                    margin-bottom: 16px;
+                    padding: 0 12px;
+                    padding-bottom: 12px;
                     box-sizing: border-box;
                     display: flex;
                     justify-content: space-between;
-                    align-items: center;
+                    align-items: flex-end;
                     background-image: url("./static/box-title-bg.png");
                     background-repeat: no-repeat;
                     background-size: 100% 100%;
-
+                    .info-record-param {
+                      font-size: 15px;
+                    }
+                    .info-record-value {
+                      font-family: "lcd", serif;
+                      font-size: 25px;
+                      box-sizing: border-box;
+                      padding-bottom: 2px;
+                      .info-record-company {
+                        font-size: 15px;
+                      }
+                    }
                 }
             }
            

+ 116 - 53
src/pages/hard/index.tsx

@@ -4,18 +4,22 @@ import { useIotList, useDeviceList, useDeviceInfo } from '../../hooks/iot';
 import BoxContainer from './components/boxContainer';
 import ThreeComponent from "./components/3d-component";
 import './index.scss';
-// import HeadMast from './static/head-mast.png'
-// import HeadMastArrow from './static/head-mast-arrow.png'
-// import HeadMastComponent from './static/head-mast-component.png'
+import { useHeadMastInfo, useDeviceStateTotal, useHeadMastList, useHeadMastHistory } from "./hooks"
+import { HEADMAST_TYPE } from '../../contants'
+import HeadMastDataComponent from './components/head-mast'
 
 const { useEffect, useState } = React;
 const hard: React.FC = (props) => {
 	const [state, setstate] = useState<{
 		checkedIot: number | undefined;
 		checkedDeviceId: number | undefined;
+    checkedSn: string | null,
+    checkedTab: string | null
 	}>({
 		checkedIot: undefined,
 		checkedDeviceId: undefined,
+    checkedSn: null,
+    checkedTab: 'real'
 	});
 	const [tabsState, handleChangeTabState] = useState({
 		list: [
@@ -25,38 +29,69 @@ const hard: React.FC = (props) => {
 		]
 	});
 	const { state: iotListState, request: requestIotList } = useIotList();
-	const { state: deviceState, request: requestDeviceList } = useDeviceList();
+	// const { state: deviceState, request: requestDeviceList } = useDeviceList();
 	const { state: infoState, request: requestInfo } = useDeviceInfo();
+  const { state: headMastState, request: getHeadMastInfo } = useHeadMastInfo()
+  const { state: headMastList, request: getHeadMastList } = useHeadMastList()
+  // 获取设备在线离线数量
+  const { state: deviceTotalState, request: getDeviceStateTotal } = useDeviceStateTotal()
+  // 获取塔吊历史数据
+  const { state: headMastHistory, request: getHeadMastHistory } = useHeadMastHistory()
+
+  const headMastColumns = [
+    { title: '日期', dataIndex: 'date', datakey: 'date' },
+    { title: HEADMAST_TYPE["angle"], dataIndex: 'angle', datakey: 'angle' },
+    { title: HEADMAST_TYPE["back_turn"], dataIndex: 'back_turn', datakey: 'back_turn' },
+    { title: HEADMAST_TYPE["hight"], dataIndex: 'hight', datakey: 'hight' },
+    { title: HEADMAST_TYPE["moment"], dataIndex: 'moment', datakey: 'moment' },
+    { title: HEADMAST_TYPE["scope"], dataIndex: 'scope', datakey: 'scope' },
+    { title: HEADMAST_TYPE["weight"], dataIndex: 'weight', datakey: 'weight' },
+    { title: HEADMAST_TYPE["wind_speed"], dataIndex: 'wind_speed', datakey: 'wind_speed' }
+  ]
+
+  // 获取iot列表
 	useEffect(() => {
-		requestIotList();
-	}, []);
+		requestIotList()
+	}, [])
+
+  useEffect(() => {
+    if (typeof state.checkedIot === 'number') {
+      getDeviceStateTotal({ device_code: state.checkedIot })
+      // 获取设备列表
+      getHeadMastList();
+    }
+  }, [state.checkedIot])
+
+  // 默认获取塔吊列表第一条数据基本信息
+  useEffect(() => {
+    if (!headMastList.list.length) return
+    const list:any = headMastList.list
+    getHeadMastInfo({ sn: list[0].sn })
+    setstate((pre)=> ({
+      ...pre,
+      checkedSn: list[0].sn
+    }))
+  }, [headMastList])
+
 	useEffect(() => {
 		if (iotListState.dataSource.length) {
 			setstate((preState) => ({
 				...preState,
-				checkedIot: iotListState.dataSource[0].id,
+				checkedIot: iotListState.dataSource[0].device_code,
 			}));
 		}
 	}, [iotListState.dataSource]);
-	useEffect(() => {
-		if (deviceState.dataSource.length) {
-			setstate((preState) => ({
-				...preState,
-				checkedDeviceId: deviceState.dataSource[0].id,
-			}));
-		}
-	}, [deviceState.dataSource]);
-	useEffect(() => {
-		if (state.checkedIot) {
-			requestDeviceList({
-				page: 1,
-				page_size: 20,
-				device_code: iotListState.dataSource.find(
-					(element) => element.id === state.checkedIot
-				)?.type_code as number,
-			});
-		}
-	}, [state.checkedIot]);
+
+  useEffect(() => {
+    const checked: any = tabsState.list.find(item => item.checked)
+    if (checked.key !== 'real') {
+      getHeadMastHistory({
+        sn: state.checkedSn,
+        is_alarm: checked.key !== 'history'
+      })
+    }
+  }, [tabsState])
+
 	useEffect(() => {
 		if (state.checkedDeviceId) {
 			const currentTab = tabsState.list.find((element) => element.checked);
@@ -73,7 +108,7 @@ const hard: React.FC = (props) => {
 		}
 	}, [state.checkedDeviceId, tabsState.list]);
 
-	return (
+  return (
 		<div className='hard-wrapper'>
 			<div className='hard-left'>
 				<BoxContainer title='Iot设备' className='iot-wrapper'>
@@ -81,16 +116,16 @@ const hard: React.FC = (props) => {
 						{iotListState.dataSource.map((iot) => {
 							return (
 								<div
-									key={iot.id}
+									key={iot.device_code}
 									className='iot-box'
 									onClick={() => {
 										setstate((preState) => ({
 											...preState,
-											checkedIot: iot.id,
+											checkedIot: iot.device_code,
 										}));
 									}}
-									data-checked={state.checkedIot === iot.id}>
-									{iot.type_name}
+									data-checked={state.checkedIot === iot.device_code}>
+									{iot.device_name}
 								</div>
 							);
 						})}
@@ -102,10 +137,10 @@ const hard: React.FC = (props) => {
 					renderMore={
 						<div className='iot-device-moreInfo'>
 							<span className='iot-online'>
-								在线设备:<span>{deviceState.online}</span>台
+								在线设备:<span>{deviceTotalState.online}</span>台
 							</span>
 							<span className='iot-offline'>
-								离线设备:<span>{deviceState.offline}</span> 台
+								离线设备:<span>{deviceTotalState.offline}</span> 台
 							</span>
 						</div>
 					}>
@@ -128,7 +163,20 @@ const hard: React.FC = (props) => {
 								},
 							},
 						]}
-						dataSource={deviceState.dataSource}/>
+            showPagination={false}
+            total={headMastList.total}
+						dataSource={headMastList.list}
+            onRowClick={async (record) => {
+              await getHeadMastInfo({ sn: record.sn })
+              setstate((pre)=> ({
+                ...pre,
+                checkedSn: record.sn
+              }))
+            }}
+            onPagination={(page) => {
+              getHeadMastList({ page })
+            }}
+          />
 				</BoxContainer>
 			</div>
 			<div className='hard-right'>
@@ -140,6 +188,10 @@ const hard: React.FC = (props) => {
 									data-checked={tabs.checked}
 									key={tabs.key}
 									onClick={() => {
+									  setstate(preState => ({
+                      ...preState,
+                      checkedTab: tabs.key
+                    }))
 										handleChangeTabState((preState) => ({
 											...preState,
 											list: preState.list.map((element) => {
@@ -155,26 +207,37 @@ const hard: React.FC = (props) => {
 							);
 						})}
 					</div>
-					<div className='hard-info-wrapper'>
-						<div className="hard-head-mast">
-              <ThreeComponent/>
-              {/*<img className="arrow" src={HeadMastArrow} alt='' />*/}
-              {/*<img src={HeadMast} alt='' />*/}
-              {/*<img className="component" src={HeadMastComponent} alt='' />*/}
-						</div>
-						<div>
-							{infoState.list.map((element) => {
-								return (
-									<div className='info-record'>
-										<span>{element.name}</span>
-										<span>
-											{element.value}&nbsp;{element.unit}
-										</span>
-									</div>
-								);
-							})}
-						</div>
+					<div
+            className='hard-info-wrapper'
+            style={{ display: state.checkedTab === 'real' ? 'flex' : 'none' }}>
+            <HeadMastDataComponent baseState={headMastState} />
 					</div>
+          <div
+            className='hard-info-wrapper'
+            style={{ display: state.checkedTab !== 'real' ? 'flex' : 'none' }}>
+            <Table
+              rowKey={(item) => item.id }
+              colums={headMastColumns}
+              dataSource={headMastHistory.list}
+              total={headMastHistory.total}
+              onRowClick={(record) => {
+                getHeadMastInfo({ sn: record.sn })
+                // checked sn
+                setstate((pre)=> ({
+                  ...pre,
+                  checkedSn: record.sn
+                }))
+              }}
+              onPagination={(page) => {
+                const checked: any = tabsState.list.find(item => item.checked)
+                getHeadMastHistory({
+                  sn: state.checkedSn,
+                  is_alarm: checked.key !== 'history',
+                  page
+                })
+              }}
+            />
+          </div>
 				</div>
 			</div>
 		</div>

+ 1 - 1
src/pages/index/index.tsx

@@ -186,7 +186,7 @@ const Index: React.FC<RouteComponentProps> = ({ history }) => {
 													className='environmental-icon'
 													style={{
 														backgroundImage: `URL(${environmental.icon})`,
-													}}></div>
+													}}/>
 												<div className='environmental-desc'>
 													<p className='environmental-name'>
 														{environmental.name}