2 次代码提交 c874cd15ae ... d0e2c1e56c

作者 SHA1 备注 提交日期
  touchitvoid d0e2c1e56c feat: 升降机列表 3 年之前
  touchitvoid 44dd5bbb57 feat: loading component style 3 年之前

+ 26 - 15
.idea/workspace.xml

@@ -2,18 +2,14 @@
 <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/hard/components/head-mast.tsx" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/src/pages/hard/hooks.ts" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/src/pages/lift/components/bindChanle/index.tsx" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/src/pages/lift/data.d.ts" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/src/pages/lift/hook.ts" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/src/pages/lift/index.tsx" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" 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/config/menuConfig.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/config/menuConfig.tsx" 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" />
+      <change beforePath="$PROJECT_DIR$/src/router/config.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/router/config.tsx" afterDir="false" />
     </list>
     <option name="SHOW_DIALOG" value="false" />
     <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -32,13 +28,13 @@
     </file-type-list>
   </component>
   <component name="HighlightingSettingsPerFile">
-    <setting file="file://$PROJECT_DIR$/src/pages/hard/index.scss" root0="FORCE_HIGHLIGHTING" />
-    <setting file="file://$PROJECT_DIR$/src/pages/hard/components/3d-component.tsx" root0="FORCE_HIGHLIGHTING" />
-    <setting file="file://$PROJECT_DIR$/config/config.js" root0="FORCE_HIGHLIGHTING" />
     <setting file="file://$PROJECT_DIR$/src/index.tsx" root0="FORCE_HIGHLIGHTING" />
     <setting file="file://$PROJECT_DIR$/src/contants.ts" root0="FORCE_HIGHLIGHTING" />
     <setting file="file://$PROJECT_DIR$/src/utils/request.ts" root0="FORCE_HIGHLIGHTING" />
+    <setting file="file://$PROJECT_DIR$/config/config.js" root0="FORCE_HIGHLIGHTING" />
     <setting file="file://$PROJECT_DIR$/src/compontens/button/sendcode.tsx" root0="FORCE_HIGHLIGHTING" />
+    <setting file="file://$PROJECT_DIR$/src/pages/hard/index.scss" root0="FORCE_HIGHLIGHTING" />
+    <setting file="file://$PROJECT_DIR$/src/pages/hard/components/3d-component.tsx" root0="FORCE_HIGHLIGHTING" />
   </component>
   <component name="ProjectId" id="1uFad3rNCKxFq2iO5sZvT112jPT" />
   <component name="ProjectLevelVcsManager">
@@ -52,7 +48,7 @@
     <property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
     <property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
     <property name="WebServerToolWindowFactoryState" value="false" />
-    <property name="last_opened_file_path" value="$PROJECT_DIR$/src/pages/head-mast" />
+    <property name="last_opened_file_path" value="$PROJECT_DIR$/src/pages" />
     <property name="nodejs_package_manager_path" value="yarn" />
     <property name="settings.editor.selected.configurable" value="preferences.pluginManager" />
     <property name="ts.external.directory.path" value="$PROJECT_DIR$/node_modules/typescript/lib" />
@@ -60,9 +56,12 @@
   </component>
   <component name="RecentsManager">
     <key name="CopyFile.RECENT_KEYS">
+      <recent name="$PROJECT_DIR$/src/pages" />
       <recent name="$PROJECT_DIR$/src/pages/head-mast" />
     </key>
     <key name="MoveFile.RECENT_KEYS">
+      <recent name="$PROJECT_DIR$/src/pages/hard/static" />
+      <recent name="$PROJECT_DIR$/src/pages/hard/components/boxContainer/static" />
       <recent name="$PROJECT_DIR$/src/assets/3d" />
       <recent name="$PROJECT_DIR$/src" />
       <recent name="$PROJECT_DIR$/src/pages/hard/components" />
@@ -96,7 +95,19 @@
       <workItem from="1625625423352" duration="4922000" />
       <workItem from="1625644149171" duration="1921000" />
       <workItem from="1625727905329" duration="22558000" />
-      <workItem from="1626052480143" duration="4167000" />
+      <workItem from="1626052480143" duration="5719000" />
+      <workItem from="1626062355736" duration="1053000" />
+      <workItem from="1626070560378" duration="1502000" />
+      <workItem from="1626142671663" duration="8474000" />
+      <workItem from="1626232872900" duration="7551000" />
+      <workItem from="1626312737875" duration="9610000" />
+      <workItem from="1626491883422" duration="73000" />
+      <workItem from="1626609758940" duration="2000" />
+      <workItem from="1626675361566" duration="5472000" />
+      <workItem from="1626749325308" duration="15672000" />
+      <workItem from="1626862210324" duration="128000" />
+      <workItem from="1626862703158" duration="507000" />
+      <workItem from="1626934065732" duration="4486000" />
     </task>
     <servers />
   </component>

+ 6 - 0
src/config/menuConfig.tsx

@@ -246,6 +246,12 @@ const adminMenuConfig = [
         path: '/admin/device/headMast',
         icon: '',
       },
+      {
+        title: '升降机设备',
+        key: '/device/lift',
+        path: '/admin/device/lift',
+        icon: '',
+      },
       {
         title: '视频监控',
         key: '/device/videos',

+ 27 - 39
src/pages/hard/components/3d-component.tsx

@@ -3,8 +3,8 @@ 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 { HEADMAST_TYPE } from '../../../contants'
 
+// 创建文字
 const createCanvasText = (text, color = '#ffffff') => {
   const canvas = document.createElement('canvas')
   const ctx: any = canvas.getContext('2d')
@@ -14,13 +14,10 @@ const createCanvasText = (text, color = '#ffffff') => {
   return canvas
 }
 
-const initThree:any = (spriteList: Array<any> = [], rootEle, setLoadProcess) => {
-  // const renderer = new THREE.WebGLRenderer({antialias: true, alpha: true});
+const initThree: any = (spriteList: Array<any> = [], rootEle, setLoadProcess) => {
   // 已经存在场景
   if (initThree.scene) {
-    initThree.scene.children = initThree.scene.children.filter(item => {
-      return ['AmbientLight', 'Group'].includes(item.type)
-    })
+    initThree.scene.children = initThree.scene.children.filter(item => ['AmbientLight', 'Group'].includes(item.type))
     // reload
     AddSpriteList(initThree.scene, spriteList)
     initThree.renderer.render(initThree.scene, initThree.camera);
@@ -32,31 +29,20 @@ const initThree:any = (spriteList: Array<any> = [], rootEle, setLoadProcess) =>
   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)
-
-  // default xyz
+  // default camera position
   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)
-  }
+  rootEle.appendChild(renderer.domElement)
   renderer.setSize(rootEle.clientWidth, rootEle.clientHeight);
-  // 加载fbx
+  // load fbx files
   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)
@@ -66,7 +52,6 @@ const initThree:any = (spriteList: Array<any> = [], rootEle, setLoadProcess) =>
     return setLoadProcess(process)
   })
   AddSpriteList(scene, spriteList)
-  // renderer.render(scene, camera)
   // add camera controller
   const controls = new OrbitControls(camera, renderer.domElement);
   controls.addEventListener("change", () => {
@@ -75,7 +60,7 @@ const initThree:any = (spriteList: Array<any> = [], rootEle, setLoadProcess) =>
 }
 
 const AddSpriteList = (scene, spriteList) => {
-  spriteList.forEach((info, index) => {
+  spriteList.forEach((info) => {
     const texture = new THREE.Texture(createCanvasText(info.text))
     texture.needsUpdate = true
     const spriteMaterial = new THREE.SpriteMaterial({map: texture})
@@ -98,9 +83,9 @@ interface ThreeComponentProps {
 }
 
 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(() => {
@@ -150,23 +135,26 @@ const ThreeComponent: React.FC<ThreeComponentProps> = (props) => {
     })
     initThree(spriteList, rootEle, setLoadProcess)
   }, [props.baseData.base])
-
-  const LoadComponent = () => (
-    <div className='load-process'>
-      <div className='load-process-bar'>
-        <div className='bar' style={{
-          transform: `translate(${loadProcess}%, 0)`
-        }}/>
+  // 进度条
+  const LoadComponent = () => {
+    // 计算刻度
+    const sign = [5, 25, 50, 75, 100].find(p => p >= loadProcess)
+    return (
+      <div className='load-process'>
+        <div className='load-process-bar'>
+          <div className={`bar process-${sign}`} style={{
+            width: `${loadProcess}%`
+          }}/>
+        </div>
       </div>
-    </div>
+    )
+  }
+  return (
+    <React.Fragment>
+      { (loadProcess < 100) && LoadComponent() }
+      <div id="three-box"/>
+    </React.Fragment>
   )
-  return <React.Fragment>
-    { (loadProcess < 100) && LoadComponent() }
-    {/*<div>*/}
-    {/*  x: { modelPosition.x } y: { modelPosition.y } z: { modelPosition.z }*/}
-    {/*</div>*/}
-    <div id="three-box"/>
-  </React.Fragment>
 }
 
 export default ThreeComponent

+ 1 - 1
src/pages/hard/hooks.ts

@@ -45,7 +45,7 @@ export const useHeadMastHistory = () => {
         setState((preState) => {
           data.list = data.list.map(obj => {
             Object.keys(obj).map(key => {
-              obj[key] = obj[key] + HEADMAST_COMPANY[key]
+              obj[key] = obj[key] + (HEADMAST_COMPANY[key] || '')
             })
             return obj
           })

+ 50 - 13
src/pages/hard/index.scss

@@ -77,7 +77,7 @@
         flex:1;
         padding-left: 20px;
         box-sizing: border-box;
-        .hard-device-content{
+        .hard-device-content {
             width: 100%;
             height: 100%;
             background-image: url("./static/device-info-bg.png");
@@ -85,15 +85,14 @@
             background-repeat: no-repeat;
             background-position: left bottom;
             position: relative;
-            .hard-device-header{
+            .hard-device-header {
                 position: absolute;
                 left: 0;
                 top: 6px;
                 display: flex;
                 color: #ffffff;
-               
                 font-size: 14px;
-                &>div{
+                & > div {
                     background-image: url("./static/tab-unchecked.png");
                     background-size: 100% auto;
                     background-repeat: no-repeat;
@@ -103,7 +102,7 @@
                     line-height: 33px;
                     text-align: center;
                     cursor: pointer;
-                    &[data-checked="true"]{
+                    &[data-checked="true"] {
                         opacity: 1;
                         background-image: url("./static/tab-checked.png");
                     }
@@ -126,28 +125,31 @@
                     display: -webkit-flex;
                     position: absolute;
                     box-sizing: border-box;
-                    padding: 0 20px;
+                    padding: 0 14px;
                     .load-process-bar {
                       width: 100%;
-                      height: 32px;
-                      background-color: white;
+                      height: 26px;
+                      background: rgba(0, 0, 0, 0.25);
+                      border-radius: 6px;
+                      box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.25), 0 1px rgba(255, 255, 255, 0.08);
                       position: relative;
                       top: 180px;
                       display: -webkit-flex;
                       align-items: center;
                       overflow-x: hidden;
                       box-sizing: border-box;
+                      padding: 1px 8px 0 8px;
                       &::after {
                         content: 'LOADING FILES...';
-                        font-family: 'lcd';
-                        font-size: 17px;
+                        font-family: 'lcd', serif;
+                        font-size: 12px;
                         letter-spacing: 3px;
                         color: white;
                         width: 100%;
                         height: 100%;
                         display: -webkit-flex;
                         align-items: center;
-                        padding-bottom: 5px;
+                        padding-bottom: 2px;
                         box-sizing: border-box;
                         justify-content: center;
                         position: absolute;
@@ -159,11 +161,46 @@
                       .bar {
                         width: 100%;
                         height: calc(100% - 10px);
-                        background-color: #1BC794;
+                        background-color: #f63a0f;
                         position: relative;
-                        left: -100%;
+                        border-radius: inherit;
+                        //left: -100%;
                         transition: all 1s;
                         will-change: auto;
+                        &.process-5 {
+                          background-color: #f63a0f;
+                        }
+                        &.process-25 {
+                          background-color: #f27011;
+                        }
+                        &.process-50 {
+                          background-color: #f2b01e;
+                        }
+                        &.process-75 {
+                          background-color: #f2d31b;
+                        }
+                        &.process-100 {
+                          background-color: #86e01e;
+                        }
+                        &::before, &::after {
+                          content: '';
+                          position: absolute;
+                          top: 0;
+                          left: 0;
+                          right: 0;
+                          bottom: 0;
+                        }
+                        &::before {
+                          background: url("./static/stripes.png") 0 0 repeat;
+                        }
+                        &::after {
+                          bottom: 45%;
+                          border-radius: 4px;
+                          background-image: -webkit-linear-gradient(top, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.05));
+                          background-image: -moz-linear-gradient(top, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.05));
+                          background-image: -o-linear-gradient(top, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.05));
+                          background-image: linear-gradient(to bottom, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.05));
+                        }
                       }
                     }
                   }

+ 9 - 2
src/pages/hard/index.tsx

@@ -38,7 +38,7 @@ const hard: React.FC = (props) => {
   // 获取塔吊历史数据
   const { state: headMastHistory, request: getHeadMastHistory } = useHeadMastHistory()
 
-  const headMastColumns = [
+  const [headMastColumns, setHeadMastColumns] = React.useState([
     { title: '日期', dataIndex: 'date', datakey: 'date' },
     { title: HEADMAST_TYPE["angle"], dataIndex: 'angle', datakey: 'angle' },
     { title: HEADMAST_TYPE["back_turn"], dataIndex: 'back_turn', datakey: 'back_turn' },
@@ -47,7 +47,7 @@ const hard: React.FC = (props) => {
     { 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(() => {
@@ -85,6 +85,13 @@ const hard: React.FC = (props) => {
   useEffect(() => {
     const checked: any = tabsState.list.find(item => item.checked)
     if (checked.key !== 'real') {
+      setHeadMastColumns((prevState => {
+        if (checked.key !== 'history') {
+          return [...prevState, { title: '报警原因', dataIndex: 'alarm_reason', datakey: 'alarm_reason' }]
+        }
+        prevState.pop()
+        return prevState
+      }))
       getHeadMastHistory({
         sn: state.checkedSn,
         is_alarm: checked.key !== 'history'

二进制
src/pages/hard/static/stripes.png


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

@@ -165,7 +165,7 @@ const Index: React.FC<RouteComponentProps> = ({ history }) => {
 											history.push('/person');
 										}}>
 										<span>更多</span>
-										<RightOutlined></RightOutlined>
+										<RightOutlined/>
 									</div>
 								}
 								className='attendance-box-wrapper'>
@@ -216,7 +216,7 @@ const Index: React.FC<RouteComponentProps> = ({ history }) => {
 											backgroundImage: `url("${
 												value.projectInfo.project_plan || emptyPlanImg
 											}")`,
-										}}></div>
+										}}/>
 								) : (
 									<div className='project-plan-imgError'></div>
 								)}

+ 65 - 0
src/pages/lift/components/bindChanle/index.tsx

@@ -0,0 +1,65 @@
+import { useEffect, useCallback } from 'react';
+import { Modal, Form, Select } from 'antd';
+import { useUnBindChanles } from '../../../../hooks/video';
+
+interface Iprops {
+	onOk?: (values: { channel_id: number }) => void;
+	visible?: boolean;
+	onCancle?: () => void;
+	loading?: boolean;
+}
+const bindChanleModal: React.FC<Iprops> = ({
+	onOk,
+	visible,
+	onCancle,
+	loading,
+}) => {
+	const [form] = Form.useForm();
+	const {
+		state: chanleListState,
+		request: requestChanleList,
+	} = useUnBindChanles();
+	useEffect(() => {
+		requestChanleList();
+	}, []);
+	useEffect(() => {
+		form && form.resetFields();
+	}, [visible]);
+	const handleOK = useCallback(() => {
+		form
+			.validateFields()
+			.then((data: any) => {
+				onOk && onOk(data);
+			})
+			.catch((error) => {});
+	}, [onOk, form]);
+	return (
+		<Modal
+			title='绑定摄像头'
+			visible={visible}
+			onOk={handleOK}
+			okButtonProps={{ loading }}
+			onCancel={onCancle}>
+			<Form form={form}>
+				<Form.Item
+					name='channel_id'
+					required
+					rules={[{ required: true, message: '请选择摄像头' }]}
+					label='摄像头'
+					children={
+						<Select
+							loading={chanleListState.loading}
+							showSearch={false}
+							placeholder='请输入摄像头'
+							options={chanleListState.dataSource.map((element) => {
+								return {
+									label: element.name,
+									value: element.id,
+								};
+							})}></Select>
+					}></Form.Item>
+			</Form>
+		</Modal>
+	);
+};
+export default bindChanleModal;

+ 0 - 0
src/pages/lift/data.d.ts


+ 65 - 0
src/pages/lift/hook.ts

@@ -0,0 +1,65 @@
+import { useState } from 'react';
+import Request from '../../utils/request';
+export interface DustRecordProps {
+	id: number;
+	name: string;
+	batch: string;
+	provider_name: string;
+	created_time: string;
+	approve_time: string;
+	state: keyof typeof DustState;
+	channel_id: number;
+	status: keyof typeof StatusConfig;
+	type_code: number;
+}
+export const DustState = {
+	0: '离线',
+	1: '在线',
+};
+
+export enum DustStatus {
+	'wait',
+	'agree',
+	'Refuse',
+}
+export const StatusConfig = {
+	[DustStatus.wait]: '待确认',
+	[DustStatus.agree]: '已确认',
+	[DustStatus.Refuse]: '非本项目',
+};
+/**
+ * @description 扬尘设备列表
+ */
+export const useLiftList = () => {
+	const [state, setState] = useState<{
+		loading: boolean;
+		dataSource: DustRecordProps[];
+		total: number;
+		current: number;
+	}>({
+		loading: false,
+		dataSource: [],
+		total: 0,
+		current: 1,
+	});
+	const request = (params: { page: number }) => {
+		setState((preState) => ({ ...preState, loading: true }));
+		return Request.sendRequest({
+			url: '/v1/device/lift_list',
+			params,
+		})
+			.then((data: any) => {
+				setState((preState) => ({
+					...preState,
+					...data,
+					loading: false,
+					dataSource: data.list,
+				}));
+			})
+			.catch((error) => {
+				setState((preState) => ({ ...preState, loading: false }));
+				return Promise.reject(error);
+			});
+	};
+	return { state, request } as const;
+};

+ 279 - 0
src/pages/lift/index.tsx

@@ -0,0 +1,279 @@
+import { useEffect, useCallback, useState, useRef } from 'react';
+import {
+	Button,
+	Table,
+	Modal,
+	Radio,
+	Space,
+	PageHeader,
+	message,
+	Badge,
+	Typography,
+} from 'antd';
+import { ColumnProps } from 'antd/lib/table';
+import {
+	useLiftList,
+	DustRecordProps,
+	DustState,
+	DustStatus,
+  StatusConfig,
+} from './hook';
+import {
+	useBindChanle,
+	useUnBindChanle,
+	usPutDevice,
+} from '../../hooks/device';
+
+const { Text } = Typography;
+
+import BindChanleModal from './components/bindChanle';
+const device: React.FC = () => {
+	const ApplyStatus = useRef(true);
+	const [modalState, setModalState] = useState<{
+		visible: boolean;
+		values?: DustRecordProps;
+	}>({
+		visible: false,
+	});
+	const [searchState, changeSearchState] = useState({ page: 1 });
+	const { state: dustListState, request: requestDustList } = useLiftList();
+	const { state: bindState, request: requestBindChanle } = useBindChanle();
+	const { state: revokeState, request: requestRevoke } = useUnBindChanle();
+	const { state: putState, request: requestPutDevice } = usPutDevice();
+	const [state, setState] = useState<{ reviewStatue: boolean }>({
+		reviewStatue: true,
+	});
+	useEffect(() => {
+		requestDustList(searchState);
+	}, [searchState]);
+
+	const colums: ColumnProps<DustRecordProps>[] = [
+		{
+			title: '供应商',
+			dataIndex: 'provider_name',
+			key: 'provider_name',
+		},
+		{
+			title: '设备名',
+			dataIndex: 'name',
+			key: 'name',
+		},
+		{
+			title: '批次',
+			dataIndex: 'batch',
+			key: 'batch',
+		},
+		{
+			title: '设备唯一编码',
+			dataIndex: 'sn',
+			key: 'sn',
+		},
+		/*{
+			title: '设备密钥',
+			width: 130,
+			dataIndex: 'key',
+			key: 'key',
+		},*/
+		{
+			title: '申请时间',
+			dataIndex: 'created_time',
+			key: 'created_time',
+		},
+		{
+			title: '审批时间',
+			dataIndex: 'approve_time',
+			key: 'approve_time',
+		},
+		{
+			title: '状态',
+			dataIndex: 'state',
+			key: 'state',
+			render: (dataIndex) => {
+				const online = dataIndex === 1;
+				return (
+					<>
+						<Badge status={online ? 'success' : 'error'}/>
+						<Text type={online ? 'success' : 'secondary'}>
+							{DustState[dataIndex as 0 | 1]}
+						</Text>
+					</>
+				);
+			},
+			//render: (_, record) => DustState[record.state],
+		},
+		{
+			title: '确认状态',
+			dataIndex: 'status',
+			key: 'status',
+			render: (_, record) => StatusConfig[record.status],
+		},
+		{
+			title: '操作',
+			render: (record) => {
+				return (
+					<Space>
+						<Button
+							type={record.status === DustStatus.wait ? 'primary' : 'default'}
+							disabled={record.status !== DustStatus.wait}
+							size='small'
+							onClick={() => handleConfirm(record)}>
+							审核
+						</Button>
+						{/*{!!record.channel_id ? (*/}
+						{/*	<Button*/}
+						{/*		size='small'*/}
+						{/*		danger*/}
+						{/*		onClick={() => handleRevokeChanle(record)}>*/}
+						{/*		解绑摄像头*/}
+						{/*	</Button>*/}
+						{/*) : (*/}
+						{/*	<Button*/}
+						{/*		size='small'*/}
+						{/*		onClick={() => {*/}
+						{/*			setModalState((preState) => ({*/}
+						{/*				...preState,*/}
+						{/*				visible: true,*/}
+						{/*				values: record,*/}
+						{/*			}));*/}
+						{/*		}}>*/}
+						{/*		绑定摄像头*/}
+						{/*	</Button>*/}
+						{/*)}*/}
+					</Space>
+				);
+			},
+		},
+	];
+	/**
+	 * @description 审批设备
+	 * @param record
+	 */
+	const handleConfirm = useCallback(
+		(record: DustRecordProps) => {
+			Modal.confirm({
+				title: '审核',
+				content: (
+					<div>
+						<Space>
+							确认是否为当前项目设备:
+							<Radio.Group
+								defaultValue={true}
+								onChange={(event) => {
+									console.log(event.target.value);
+									ApplyStatus.current = event.target.value;
+								}}>
+								<Radio value={true}>通过</Radio>
+								<Radio value={false}>拒绝</Radio>
+							</Radio.Group>
+						</Space>
+					</div>
+				),
+				okText: '确认',
+				cancelText: '取消',
+				onOk: () => {
+					return requestPutDevice({
+						id: record.id,
+						device_code: record.type_code,
+						status: ApplyStatus.current,
+					})
+						.then(() => {
+							requestDustList(searchState);
+							message.success('操作成功');
+						})
+						.catch((error) => {
+							message.error(error.message);
+						})
+						.finally(() => {
+							ApplyStatus.current = true;
+						});
+				},
+				onCancel() {
+					ApplyStatus.current = true;
+				},
+			});
+		},
+		[Modal]
+	);
+
+	/**
+	 * @description 绑定摄像头
+	 */
+	const handleBindChanle = useCallback(
+		(data) => {
+			requestBindChanle({
+				...data,
+				device_id: modalState.values?.id,
+				device_code: modalState.values?.type_code,
+			})
+				.then(() => {
+					message.success('绑定摄像头成功');
+					requestDustList(searchState);
+					setModalState((preState) => ({
+						...preState,
+						visible: false,
+						values: undefined,
+					}));
+				})
+				.catch((error) => {
+					message.error(error.message);
+				});
+		},
+		[modalState.values, message, requestDustList]
+	);
+	/**
+	 * @description 解绑摄像头
+	 */
+	const handleRevokeChanle = useCallback(
+		(record: DustRecordProps) => {
+			Modal.confirm({
+				title: '确认解绑摄像头',
+				okText: '确认',
+				cancelText: '取消',
+				okButtonProps: {
+					loading: revokeState.loading,
+				},
+				onOk: () => {
+					return requestRevoke({
+						device_id: record.id,
+						device_code: record.type_code,
+					})
+						.then(() => {
+							requestDustList(searchState);
+							message.success('解绑成功');
+						})
+						.catch((error) => {
+							message.error(error.message);
+						});
+				},
+			});
+		},
+		[requestDustList, message]
+	);
+	return (
+		<div>
+			<PageHeader title='升降机设备'/>
+			<Table
+				rowKey={(record) => record.id}
+				columns={colums}
+				pagination={{
+					current: dustListState.current,
+					total: dustListState.total,
+					showQuickJumper: false,
+					showSizeChanger: false,
+					hideOnSinglePage: true,
+					onChange: (page) =>
+						changeSearchState((preState) => ({ ...preState, page })),
+				}}
+				loading={dustListState.loading}
+				dataSource={dustListState.dataSource}/>
+			<BindChanleModal
+				loading={bindState.loading}
+				visible={modalState.visible}
+				onCancle={() => {
+					setModalState((preState) => ({ ...preState, visible: false }));
+				}}
+				onOk={handleBindChanle}/>
+		</div>
+	);
+};
+export default device;

+ 6 - 0
src/router/config.tsx

@@ -18,6 +18,7 @@ const projectPlan = lazy(() => import('../pages/projectPlan'));
 const projectRender = lazy(() => import('../pages/admin-renderings'));
 
 const dust = lazy(() => import('../pages/dust'));
+const lift = lazy(() => import('../pages/lift'));
 const headMast = lazy(() => import('../pages/head-mast'));
 
 const videoDevice = lazy(() => import('../pages/device-video'));
@@ -133,6 +134,11 @@ export const routerConfig = [
 				exact: true,
 				component: wrapperLoading(headMast),
 			},
+      {
+				path: '/admin/device/lift',
+				exact: true,
+				component: wrapperLoading(lift),
+			},
 			{
 				path: '/admin/device/accessController',
 				exact: true,