Parcourir la source

feat(thirdparty): data is obtained through mesh

Casper Dai il y a 2 ans
Parent
commit
0e720a1f92
66 fichiers modifiés avec 1253 ajouts et 1777 suppressions
  1. 4 3
      src/api/camera.js
  2. 51 158
      src/api/external.js
  3. 22 10
      src/api/mesh.js
  4. 0 59
      src/components/dialog/DeviceDialog/index.vue
  5. 1 1
      src/components/layout/Wrapper/index.vue
  6. 31 11
      src/components/service/FullLink/index.vue
  7. 1 1
      src/components/service/external/DevicePlayer/index.vue
  8. 33 5
      src/components/service/external/camera/CameraDetail/index.vue
  9. 35 15
      src/components/service/external/camera/CameraPlayer/index.vue
  10. 32 27
      src/router/index.js
  11. 1 1
      src/views/dashboard/v0/DeviceInfo.vue
  12. 19 11
      src/views/dashboard/v0/Record.vue
  13. 81 0
      src/views/device/detail/components/DeviceExternal/components/Gateway.vue
  14. 10 13
      src/views/device/detail/components/DeviceExternal/components/LED.vue
  15. 0 0
      src/views/device/detail/components/DeviceExternal/components/ReceivingCard/ReceivingCardInfo.vue
  16. 0 0
      src/views/device/detail/components/DeviceExternal/components/ReceivingCard/ReceivingCardTopology.vue
  17. 0 0
      src/views/device/detail/components/DeviceExternal/components/ReceivingCard/index.vue
  18. 120 0
      src/views/device/detail/components/DeviceExternal/components/SendingCard.vue
  19. 9 12
      src/views/device/detail/components/DeviceExternal/components/Traffic.vue
  20. 0 200
      src/views/device/detail/components/DeviceExternal/external/Gateway/index.vue
  21. 0 123
      src/views/device/detail/components/DeviceExternal/external/SendingCard/index.vue
  22. 8 8
      src/views/device/detail/components/DeviceExternal/index.vue
  23. 32 12
      src/views/device/detail/components/DeviceInvoke/PLCSwitch.vue
  24. 1 1
      src/views/device/detail/dashboard/DeviceInfo.vue
  25. 28 10
      src/views/device/detail/dashboard/LinkState.vue
  26. 10 15
      src/views/device/detail/dashboard/index.vue
  27. 14 11
      src/views/device/detail/index.vue
  28. 19 10
      src/views/external/box/components/Device.vue
  29. 0 0
      src/views/external/box/components/Product.vue
  30. 0 0
      src/views/external/box/components/ProductType.vue
  31. 55 0
      src/views/external/box/index.vue
  32. 0 0
      src/views/external/box/settings/api.js
  33. 0 1
      src/views/external/box/settings/components/AdConfigDialog.vue
  34. 0 0
      src/views/external/box/settings/components/AttributeConfigDialog.vue
  35. 0 0
      src/views/external/box/settings/components/ContentProtectionConfigDialog.vue
  36. 0 0
      src/views/external/box/settings/components/DeviceNormalConfig.vue
  37. 0 0
      src/views/external/box/settings/components/DeviceShadow.vue
  38. 0 0
      src/views/external/box/settings/components/RecordConfigDialog.vue
  39. 70 0
      src/views/external/box/settings/components/external/Camera.vue
  40. 77 0
      src/views/external/box/settings/components/external/Camera/index.vue
  41. 63 0
      src/views/external/box/settings/components/external/Gateway.vue
  42. 3 3
      src/views/external/box/settings/components/external/MultifunctionCard.vue
  43. 3 3
      src/views/external/box/settings/components/external/PLC.vue
  44. 0 0
      src/views/external/box/settings/components/external/ReceivingCard/ReceivingCardTopology.vue
  45. 0 0
      src/views/external/box/settings/components/external/ReceivingCard/index.vue
  46. 0 0
      src/views/external/box/settings/components/external/Screen.vue
  47. 0 0
      src/views/external/box/settings/components/external/SendingCard.vue
  48. 4 4
      src/views/external/box/settings/components/external/Sensor.vue
  49. 25 3
      src/views/external/box/settings/components/external/index.vue
  50. 10 16
      src/views/external/box/settings/index.vue
  51. 50 101
      src/views/external/camera/index.vue
  52. 54 0
      src/views/external/components/MeshDialog.vue
  53. 0 126
      src/views/external/gateway/components/PLCDialog.vue
  54. 0 168
      src/views/external/gateway/components/SensorDialog.vue
  55. 129 86
      src/views/external/gateway/index.vue
  56. 50 11
      src/views/external/mesh/index.vue
  57. 19 8
      src/views/external/multifunction-card/index.vue
  58. 17 6
      src/views/external/plc/index.vue
  59. 17 6
      src/views/external/screen/index.vue
  60. 19 8
      src/views/external/sending-card/index.vue
  61. 24 7
      src/views/external/sensor/index.vue
  62. 0 57
      src/views/platform/tenant/device/index.vue
  63. 0 177
      src/views/platform/tenant/device/settings/components/external/Camera/index.vue
  64. 0 264
      src/views/platform/tenant/device/settings/components/external/Gateway/index.vue
  65. 1 1
      src/views/realm/device/index.vue
  66. 1 4
      src/views/screen/review/workflow/audit/components/ReviewPublish.vue

+ 4 - 3
src/api/camera.js

@@ -81,10 +81,11 @@ export function setCamera (data) {
 }
 
 // 摄像头在线状态
-export function isOnline (deviceId) {
+export function isOnline (identifier, options) {
   return request({
-    url: `/camera/${deviceId}/online`,
-    method: 'GET'
+    url: `/camera/${identifier}/online`,
+    method: 'GET',
+    ...options
   })
 }
 

+ 51 - 158
src/api/external.js

@@ -7,7 +7,6 @@ import {
   addTenantAndOrg,
   addTenant
 } from './base'
-import { ThirdPartyDevice } from '@/constant'
 
 export function bind (deviceId, deviceType, thirdPartyDeviceId) {
   return messageSend({
@@ -40,15 +39,7 @@ export function getThirdPartyDevices (query) {
   })
 }
 
-export function getBoundThirdPartyDevices (deviceId, chain = false) {
-  return request({
-    url: `/device/bind/${deviceId}`,
-    method: 'GET',
-    params: { chain },
-    custom: true
-  })
-}
-
+// 接收卡
 export function getReceivingCardManufacturers () {
   return request({
     url: '/device/manufacturer/list',
@@ -58,7 +49,7 @@ export function getReceivingCardManufacturers () {
 
 export function addReceivingCard (fromData, onUploadProgress) {
   return request({
-    url: `/device/information`,
+    url: '/device/information',
     method: 'POST',
     timeout: 0,
     data: fromData,
@@ -68,7 +59,7 @@ export function addReceivingCard (fromData, onUploadProgress) {
 
 export function updateReceivingCard (fromData, onUploadProgress) {
   return update({
-    url: `/device/information`,
+    url: '/device/information',
     method: 'PUT',
     timeout: 0,
     data: fromData,
@@ -76,10 +67,11 @@ export function updateReceivingCard (fromData, onUploadProgress) {
   })
 }
 
-export function getReceivingCard (id) {
+export function getReceivingCard (id, options) {
   return request({
     url: `/device/information/${id}`,
-    method: 'GET'
+    method: 'GET',
+    ...options
   })
 }
 
@@ -91,6 +83,7 @@ export function getReceivingCardTopology (id, options) {
   })
 }
 
+// 摄像头
 export function getCamerasByDevice (deviceId, params) {
   return request({
     url: `/device/bind/thirdPartyCamera/${deviceId}`,
@@ -99,143 +92,6 @@ export function getCamerasByDevice (deviceId, params) {
   })
 }
 
-export function getGateways (query) {
-  const { pageNum: pageIndex, pageSize, ...params } = query
-  return request({
-    url: '/device/thirdPartyGateway/list',
-    method: 'GET',
-    params: {
-      pageIndex, pageSize,
-      ...params
-    }
-  })
-}
-
-export function addGateway (data) {
-  return add({
-    url: '/device/thirdPartyGateway',
-    method: 'POST',
-    data
-  })
-}
-
-export function updateGateway (data) {
-  return update({
-    url: '/device/thirdPartyGateway',
-    method: 'PUT',
-    data
-  })
-}
-
-export function deleteGateway ({ id, name }) {
-  return del({
-    url: `/device/thirdPartyGateway/${id}`,
-    method: 'DELETE'
-  }, name)
-}
-
-export function getGateway (deviceId) {
-  return request({
-    url: `/device/bind/thirdPartyGateway/${deviceId}`,
-    method: 'GET'
-  }).then(({ data }) => data?.[0])
-}
-
-export function bindGateway (deviceId, thirdPartyDeviceId) {
-  return bind(deviceId, ThirdPartyDevice.GATEWAY, thirdPartyDeviceId)
-}
-
-export function plcCommand (deviceId, status) {
-  return messageSend({
-    url: '/device/thirdplc/command',
-    method: 'POST',
-    data: { deviceId, status }
-  }, '执行')
-}
-
-export function getPLCsByDevice (deviceId) {
-  return request({
-    url: `/device/bind/thirdPartyPlc/${deviceId}`,
-    method: 'GET'
-  })
-}
-
-export function addPLCToGateway (gatewayId, plc) {
-  return add({
-    url: '/device/thirdplc/add',
-    method: 'POST',
-    data: {
-      gatewayId,
-      ...plc
-    }
-  })
-}
-
-export function updateGatewayPLC (data) {
-  return update({
-    url: '/device/thirdplc/modify',
-    method: 'PUT',
-    data
-  })
-}
-
-export function deleteGatewayPLC ({ id, name }) {
-  return del({
-    url: `/device/thirdplc/delete/${id}`,
-    method: 'DELETE'
-  }, name)
-}
-
-export function getPLCsByGateway (gatewayId) {
-  return request({
-    url: '/device/thirdPartyGateway/listPlc',
-    method: 'GET',
-    params: { gatewayId }
-  })
-}
-
-export function getSensorsByGateway (gatewayId) {
-  return request({
-    url: '/device/thirdPartyGateway/listSensor',
-    method: 'GET',
-    params: { gatewayId }
-  })
-}
-
-export function addSensorToGateway (gatewayId, sensor) {
-  return add({
-    url: '/device/thirdPartyGateway/addSensor',
-    method: 'POST',
-    data: {
-      gatewayId,
-      ...sensor
-    }
-  })
-}
-
-export function updateGatewaySensor (data) {
-  return update({
-    url: '/device/thirdPartyGateway/updateSensor',
-    method: 'PUT',
-    data
-  })
-}
-
-export function deleteGatewaySensor ({ id, name }) {
-  return del({
-    url: '/device/thirdPartyGateway/deleteSensor',
-    method: 'DELETE',
-    params: { sensorId: id }
-  }, name)
-}
-
-export function getDevicesByThirdPartyDevice (thirdPartyDeviceId) {
-  return request({
-    url: `/device/bind/getDevice/${thirdPartyDeviceId}`,
-    method: 'GET'
-  })
-}
-
 // 厂商
 export function getManufacturers (query) {
   const { pageNum: pageIndex, pageSize, ...params } = query
@@ -354,13 +210,6 @@ export function deleteSendingCard ({ id }) {
   }, '该发送控制设备')
 }
 
-export function getSendingCard (deviceId) {
-  return request({
-    url: `/device/bind/thirdPartySendingCard/${deviceId}`,
-    method: 'GET'
-  }).then(({ data }) => data?.[0])
-}
-
 // 内容保护
 export function getContentProtection (deviceId, options) {
   return request({
@@ -513,3 +362,47 @@ export function deletePLC ({ id }) {
     params: { id }
   }, '该PLC')
 }
+
+// 网关
+export function getGateways (query) {
+  const { pageNum: pageIndex, pageSize, ...params } = query
+  return request({
+    url: '/device/thirdPartyGateway/list',
+    method: 'GET',
+    params: addTenant({
+      pageIndex, pageSize,
+      ...params
+    })
+  })
+}
+
+export function addGateway (data) {
+  return add({
+    url: '/device/thirdPartyGateway',
+    method: 'POST',
+    data: addTenantAndOrg(data)
+  })
+}
+
+export function updateGateway (data) {
+  return update({
+    url: '/device/thirdPartyGateway',
+    method: 'PUT',
+    data
+  })
+}
+
+export function deleteGateway ({ id }) {
+  return del({
+    url: `/device/thirdPartyGateway/${id}`,
+    method: 'DELETE'
+  }, '该网关')
+}
+
+export function plcCommand (deviceId, status) {
+  return messageSend({
+    url: '/device/thirdplc/command',
+    method: 'POST',
+    data: { deviceId, status }
+  }, '执行')
+}

+ 22 - 10
src/api/mesh.js

@@ -4,8 +4,7 @@ import {
   update,
   del,
   addTenant,
-  messageSend,
-  send
+  messageSend
 } from './base'
 
 export function getMeshes (query) {
@@ -211,20 +210,33 @@ export function getMeshByBox (deviceId) {
 }
 
 export function getMeshByInstance (instanceId) {
-  return send({
+  return request({
     url: '/device/mesh/getMeshByInstanceId',
     method: 'GET',
     params: { instanceId }
   })
 }
 
-export function getThirdPartyDevicesByBox (deviceId, type) {
+export function getThirdPartyDevicesByThirdPartyDevice (instanceId, typeList, options) {
   return request({
-    url: '/device/bind/listAssignedInstance',
-    method: 'GET',
-    params: {
-      deviceId,
-      type
-    }
+    url: '/device/bind/listAssignedNode',
+    method: 'POST',
+    data: {
+      instanceId,
+      typeList
+    },
+    ...options
+  })
+}
+
+export function getFollowThirdPartyDevicesByThirdPartyDevice (instanceId, typeList, options) {
+  return request({
+    url: '/device/bind/listAssignedBindNode',
+    method: 'POST',
+    data: {
+      instanceId,
+      typeList
+    },
+    ...options
   })
 }

+ 0 - 59
src/components/dialog/DeviceDialog/index.vue

@@ -1,59 +0,0 @@
-<template>
-  <table-dialog
-    ref="tableDialg"
-    title="相关设备"
-    :schema="schema"
-    v-bind="$attrs"
-    v-on="$listeners"
-  />
-</template>
-
-<script>
-import { getDevicesByThirdPartyDevice } from '@/api/external'
-
-export default {
-  name: 'DeviceDialog',
-  data () {
-    return {
-      thirdPartyDeviceId: null
-    }
-  },
-  computed: {
-    schema () {
-      return {
-        nonPagination: true,
-        list: this.getDevicesByThirdPartyDevice,
-        cols: [
-          { prop: 'name', label: '设备名称' },
-          { prop: 'address', label: '地址' },
-          { type: 'invoke', render: [
-            { label: '详情', on: this.onView },
-            { label: '配置', on: this.onConfig }
-          ], width: 160 }
-        ]
-      }
-    }
-  },
-  methods: {
-    getDevicesByThirdPartyDevice () {
-      return getDevicesByThirdPartyDevice(this.thirdPartyDeviceId)
-    },
-    show (id) {
-      this.thirdPartyDeviceId = id
-      this.$refs.tableDialg.show()
-    },
-    onView ({ id }) {
-      this.$router.push({
-        name: 'device-detail',
-        params: { id }
-      })
-    },
-    onConfig ({ id }) {
-      this.$router.push({
-        name: 'device-management-settings',
-        params: { id }
-      })
-    }
-  }
-}
-</script>

+ 1 - 1
src/components/layout/Wrapper/index.vue

@@ -58,7 +58,7 @@ export default {
 
   .c-page__content {
     flex: none;
-    min-width: 1048px;
+    min-width: 1096px;
     min-height: 500px;
     overflow: hidden;
 

+ 31 - 11
src/components/service/FullLink/index.vue

@@ -6,7 +6,7 @@
   >
     <warning
       v-if="!loaded && !loading"
-      @click="getBoundThirdPartyDevices"
+      @click="getThirdPartyDevicesByThirdPartyDevice"
     />
     <div
       v-if="loaded"
@@ -50,8 +50,9 @@
 </template>
 
 <script>
-import { getBoundThirdPartyDevices } from '@/api/external'
 import { ThirdPartyDevice } from '@/constant'
+import { getThirdPartyDevicesByThirdPartyDevice } from '@/api/mesh'
+import { getReceivingCard } from '@/api/external'
 
 const LinkItems = Object.freeze([
   {
@@ -160,12 +161,15 @@ export default {
         [ThirdPartyDevice.LED_CAMERA]: -1,
         [ThirdPartyDevice.TRAFFIC_CAMERA]: -1,
         [ThirdPartyDevice.SENDING_CARD]: -1,
-        [ThirdPartyDevice.RECEIVING_CARD]: -1
+        [ThirdPartyDevice.RECEIVING_CARD]: -1,
+        [ThirdPartyDevice.BOX]: -1
       }
       if (this.linkDeviceMap) {
-        for (const item of this.linkDeviceMap) {
-          map[item.deviceType] = item.status
-        }
+        this.linkDeviceMap.forEach(({ nodeType, instance }) => {
+          if (instance && (map[nodeType] === 1 || map[nodeType] === -1)) {
+            map[nodeType] = instance.onlineStatus === 0 ? 0 : 1
+          }
+        })
       }
       return map
     },
@@ -200,8 +204,8 @@ export default {
     }
   },
   created () {
-    this.getBoundThirdPartyDevices()
-    this.$timer = setInterval(this.getBoundThirdPartyDevices, 10000)
+    this.getThirdPartyDevicesByThirdPartyDevice()
+    this.$timer = setInterval(this.getThirdPartyDevicesByThirdPartyDevice, 10000)
   },
   mounted () {
     this.checkScale()
@@ -230,13 +234,29 @@ export default {
           : Math.min(width / 966, height / 420).toFixed(2)
       }
     },
-    getBoundThirdPartyDevices () {
+    getThirdPartyDevicesByThirdPartyDevice () {
       if (this.loading) {
         return
       }
       this.loading = true
-      getBoundThirdPartyDevices(this.targetId, true).then(({ data }) => {
-        this.linkDeviceMap = data
+      Promise.all([
+        getThirdPartyDevicesByThirdPartyDevice(this.targetId, [
+          ThirdPartyDevice.GATEWAY,
+          ThirdPartyDevice.RECEIVING_CARD,
+          ThirdPartyDevice.SENDING_CARD,
+          ThirdPartyDevice.LED_CAMERA,
+          ThirdPartyDevice.TRAFFIC_CAMERA,
+          ThirdPartyDevice.BOX
+        ], { custom: true }),
+        getReceivingCard(this.device.id, { custom: true })
+      ]).then(([{ data: nodes }, { data: receivingCard }]) => {
+        this.linkDeviceMap = [
+          ...nodes,
+          {
+            nodeType: ThirdPartyDevice.RECEIVING_CARD,
+            instance: receivingCard
+          }
+        ]
         this.loaded = true
       }).finally(() => {
         this.loading = false

+ 1 - 1
src/components/service/external/DevicePlayer/index.vue

@@ -127,7 +127,7 @@ export default {
   },
   computed: {
     online () {
-      return this.device.activate && this.device.onlineStatus === 1
+      return this.device.onlineStatus === 1
     },
     canPlay () {
       return !this.loading && !this.needReset

+ 33 - 5
src/components/service/external/camera/CameraDetail/index.vue

@@ -129,7 +129,8 @@ import {
   getStatistic,
   getVideoInfo,
   getAvailableParam,
-  setCamera
+  setCamera,
+  isOnline
 } from '@/api/camera'
 import {
   GATEWAY_CAMERA,
@@ -202,7 +203,27 @@ export default {
       this.$emit('close')
     },
     createPlayer () {
-      this.playUrl(`${GATEWAY_CAMERA}/${this.camera.identifier}?authorization=${this.token}`)
+      this.loading = true
+      isOnline(this.camera.identifier, { custom: true }).then(
+        ({ data }) => {
+          if (data) {
+            this.playUrl(`${GATEWAY_CAMERA}/${this.camera.identifier}?authorization=${this.token}`)
+          } else {
+            this.loading = false
+            this.$message({
+              type: 'warning',
+              message: '设备未上线,请稍后重试'
+            })
+          }
+        },
+        () => {
+          this.loading = false
+          this.$message({
+            type: 'warning',
+            message: '暂未检测到设备状态,请稍后重试'
+          })
+        }
+      )
     },
     setCamera () {
       this.hideSettingsMenu()
@@ -299,9 +320,16 @@ export default {
       getVideoInfo(this.camera.identifier).finally(() => {
         this.$videoInfoLoading = false
       }).then(({ data }) => {
-        const { width, hight, frameRate, bitRate } = data
-        this.infoData = { width, hight, frameRate, bitRate }
-        this.dataInit()
+        if (data) {
+          const { width, hight, frameRate, bitRate } = data
+          this.infoData = { width, hight, frameRate, bitRate }
+          this.dataInit()
+        } else {
+          this.$message({
+            type: 'warning',
+            message: '未获取到摄像头配置,请联系管理员'
+          })
+        }
       })
     },
     dataInit () {

+ 35 - 15
src/components/service/external/camera/CameraPlayer/index.vue

@@ -33,7 +33,7 @@
       <div class="l-flex__auto c-sibling-item u-ellipsis">{{ camera.name }}</div>
       <slot />
       <i
-        v-if="online"
+        v-if="online && !loading"
         class="c-sibling-item el-icon-full-screen has-active"
         @click="onFullScreen"
       />
@@ -44,6 +44,7 @@
 <script>
 import { mapGetters } from 'vuex'
 import { GATEWAY_CAMERA } from '@/constant'
+import { isOnline } from '@/api/camera'
 import playerMixin from '../../player'
 
 export default {
@@ -55,27 +56,46 @@ export default {
       required: true
     }
   },
-  computed: {
-    ...mapGetters(['token']),
-    online () {
-      return this.camera.onlineStatus === 1
+  data () {
+    return {
+      online: true
     }
   },
-  watch: {
-    online (val) {
-      if (val) {
-        this.$nextTick(this.createPlayer)
-      } else {
-        this.destroyPlayer()
-      }
-    }
+  computed: {
+    ...mapGetters(['token'])
   },
   mounted () {
-    this.online && this.createPlayer()
+    this.createPlayer()
   },
   methods: {
     createPlayer () {
-      this.playUrl(`${GATEWAY_CAMERA}/${this.camera.identifier}?authorization=${this.token}`)
+      this.loading = true
+      isOnline(this.camera.identifier, { custom: true }).then(
+        ({ data }) => {
+          if (!this.loading) {
+            return
+          }
+          this.online = data
+          if (data) {
+            this.$nextTick(() => {
+              this.playUrl(`${GATEWAY_CAMERA}/${this.camera.identifier}?authorization=${this.token}`)
+            })
+          } else {
+            this.destroyPlayer()
+            this.checkOnline(5000)
+          }
+        },
+        () => {
+          if (!this.loading) {
+            return
+          }
+          this.checkOnline(2000)
+        }
+      )
+    },
+    checkOnline (delay = 5000) {
+      clearTimeout(this.$timer)
+      this.$timer = setTimeout(this.createPlayer, delay + Math.random() * delay | 0)
     }
   }
 }

+ 32 - 27
src/router/index.js

@@ -3,6 +3,8 @@ import Router from 'vue-router'
 import {
   ScheduleType,
   Access,
+  Camera,
+  CameraInfo,
   Sensor,
   SensorInfo
 } from '@/constant'
@@ -317,7 +319,7 @@ export const asyncRoutes = [
             hidden: true,
             name: 'device-management-settings',
             path: 'settings/:id',
-            component: () => import('@/views/platform/tenant/device/settings/index'),
+            component: () => import('@/views/external/box/settings/index'),
             meta: { title: '配置', cache: 'DeviceManagement' },
             props: { redirect: 'device-management' }
           }
@@ -408,27 +410,6 @@ export const asyncRoutes = [
         component: () => import('@/views/platform/tenant/index'),
         meta: { title: '租户管理' }
       },
-      {
-        path: 'device',
-        component: Solo,
-        meta: { title: '设备管理' },
-        children: [
-          {
-            name: 'tenant-device-management',
-            path: '',
-            component: () => import('@/views/platform/tenant/device/index'),
-            meta: { cache: 'TenantDeviceManagement' }
-          },
-          {
-            hidden: true,
-            name: 'tenant-device-management-settings',
-            path: 'settings/:id',
-            component: () => import('@/views/platform/tenant/device/settings/index'),
-            meta: { title: '配置', cache: 'TenantDeviceManagement' },
-            props: { redirect: 'tenant-device-management' }
-          }
-        ]
-      },
       {
         path: 'template',
         component: () => import('@/views/broadcast/template/index'),
@@ -487,6 +468,27 @@ export const asyncRoutes = [
           }
         ]
       },
+      {
+        path: 'box',
+        component: Solo,
+        meta: { title: '终端设备' },
+        children: [
+          {
+            name: 'box-management',
+            path: '',
+            component: () => import('@/views/external/box/index'),
+            meta: { cache: 'BoxManagement' }
+          },
+          {
+            hidden: true,
+            name: 'box-settings',
+            path: 'settings/:id',
+            component: () => import('@/views/external/box/settings/index'),
+            meta: { title: '配置', cache: 'BoxManagement' },
+            props: { redirect: 'box-management' }
+          }
+        ]
+      },
       {
         path: 'manufacturer',
         component: () => import('@/views/external/manufacturer/index'),
@@ -502,11 +504,14 @@ export const asyncRoutes = [
         component: () => import('@/views/external/sending-card/index'),
         meta: { title: '发送控制设备' }
       },
-      {
-        path: 'camera',
-        component: () => import('@/views/external/camera/index'),
-        meta: { title: '摄像头' }
-      },
+      ...Object.keys(Camera).map(key => {
+        return {
+          path: `camera/${Camera[key]}`,
+          component: () => import('@/views/external/camera/index'),
+          meta: { title: CameraInfo[Camera[key]] },
+          props: { type: Camera[key] }
+        }
+      }),
       {
         path: 'multi',
         component: () => import('@/views/external/multifunction-card/index'),

+ 1 - 1
src/views/dashboard/v0/DeviceInfo.vue

@@ -61,7 +61,7 @@ export default {
   },
   data () {
     return {
-      online: this.device.activate && this.device.onlineStatus === 1,
+      online: this.device.onlineStatus === 1,
       rebooting: false,
       rows: [
         [

+ 19 - 11
src/views/dashboard/v0/Record.vue

@@ -5,7 +5,12 @@
       element-loading-background="transparent"
       class="l-flex__auto l-flex--col has-top-padding"
     >
+      <status-wrapper
+        v-if="isAbnormal"
+        class="l-flex__fill"
+      />
       <div
+        v-else
         class="l-flex__fill c-sibling-item--v c-record-grid"
         :class="gridClass"
       >
@@ -14,8 +19,6 @@
           :key="item.identifier"
           :device="item"
           controls
-          autoplay
-          retry
         >
           <template #controls="{canPlay, onFullScreen}">
             <svg-icon
@@ -38,10 +41,6 @@
           </template>
         </device-player>
       </div>
-      <status-wrapper
-        v-if="isAbnormal"
-        class="l-flex__fill"
-      />
       <div class="l-flex__none l-flex--row c-sibling-item--v far">
         <div class="l-flex__none c-sibling-item l-flex--row">
           <div
@@ -94,9 +93,10 @@
 </template>
 
 <script>
-import { getDevices } from '@/api/device'
+import { ThirdPartyDevice } from '@/constant'
 import { createListOptions } from '@/utils'
-import { getBoundThirdPartyDevices } from '@/api/external'
+import { getDevices } from '@/api/device'
+import { getThirdPartyDevicesByThirdPartyDevice } from '@/api/mesh'
 import Box from './Box'
 
 export default {
@@ -151,8 +151,14 @@ export default {
           options.list.forEach(item => {
             this.$cache[item.id] = item.status
             if (item.status !== 2) {
-              getBoundThirdPartyDevices(item.id, true).then(({ data: linkList }) => {
-                const status = Math.max(this.$cache[item.id] || 0, linkList && linkList.some(({ status }) => status !== 1) ? 2 : 1)
+              getThirdPartyDevicesByThirdPartyDevice(item.id, [
+                ThirdPartyDevice.GATEWAY,
+                ThirdPartyDevice.RECEIVING_CARD,
+                ThirdPartyDevice.SENDING_CARD,
+                ThirdPartyDevice.LED_CAMERA,
+                ThirdPartyDevice.TRAFFIC_CAMERA
+              ], { custom: true }).then(({ data }) => {
+                const status = Math.max(this.$cache[item.id] || 0, data.some(({ instance }) => instance && instance.onlineStatus === 0) ? 2 : 1)
                 item.status = status
                 item.statusClass = `status${status}`
                 this.$cache[item.id] = status
@@ -212,19 +218,21 @@ export default {
 
 .c-record-grid {
   display: grid;
-  grid-template-rows: max-content;
   grid-row-gap: 4px;
   grid-column-gap: 4px;
 
   &.one {
+    grid-template-rows: 1fr;
     grid-template-columns: 1fr;
   }
 
   &.four {
+    grid-template-rows: 1fr 1fr;
     grid-template-columns: 1fr 1fr;
   }
 
   &.nine {
+    grid-template-rows: 1fr 1fr 1fr;
     grid-template-columns: 1fr 1fr 1fr;
   }
 }

+ 81 - 0
src/views/device/detail/components/DeviceExternal/components/Gateway.vue

@@ -0,0 +1,81 @@
+<template>
+  <div
+    v-loading="loading"
+    class="c-info"
+  >
+    <warning
+      v-if="error"
+      @click="getThirdPartyDevicesByThirdPartyDevice"
+    />
+    <div
+      v-for="info in items"
+      :key="info.id"
+      class="c-sibling-item--v"
+    >
+      <div class="c-sibling-item--v u-bold">物联网关({{ info.name }})</div>
+      <div class="c-sibling-item--v">
+        <div class="l-flex--row c-info__block">
+          <div class="l-flex--row l-flex__fill c-sibling-item">
+            <div class="l-flex__none c-info__title">厂商</div>
+            <div class="l-flex__fill c-info__value">{{ info.instance.manufacturerName }}</div>
+          </div>
+          <div class="l-flex--row l-flex__fill c-sibling-item">
+            <div class="l-flex__none c-info__title">型号</div>
+            <div class="l-flex__fill c-info__value">{{ info.instance.model }}</div>
+          </div>
+          <div class="l-flex--row l-flex__fill c-sibling-item">
+            <div class="l-flex__none c-info__title">唯一标识</div>
+            <div class="l-flex__fill c-info__value">{{ info.instance.identifier }}</div>
+          </div>
+        </div>
+        <div class="l-flex--row c-info__block">
+          <div class="l-flex--row l-flex__fill c-sibling-item">
+            <div class="l-flex__none c-info__title">服务器</div>
+            <div class="l-flex__fill c-info__value u-color--blue">{{ info.instance.ip }}</div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { ThirdPartyDevice } from '@/constant'
+import { getThirdPartyDevicesByThirdPartyDevice } from '@/api/mesh'
+
+export default {
+  name: 'DeviceGateway',
+  props: {
+    device: {
+      type: Object,
+      required: true
+    }
+  },
+  data () {
+    return {
+      loading: true,
+      error: false,
+      items: []
+    }
+  },
+  created () {
+    this.getThirdPartyDevicesByThirdPartyDevice()
+  },
+  methods: {
+    getThirdPartyDevicesByThirdPartyDevice () {
+      this.loading = true
+      this.error = false
+      getThirdPartyDevicesByThirdPartyDevice(this.device.id, [ThirdPartyDevice.GATEWAY]).then(
+        ({ data }) => {
+          this.items = data.filter(({ instance }) => instance)
+        },
+        () => {
+          this.error = true
+        }
+      ).finally(() => {
+        this.loading = false
+      })
+    }
+  }
+}
+</script>

+ 10 - 13
src/views/device/detail/components/DeviceExternal/external/Camera/LED.vue → src/views/device/detail/components/DeviceExternal/components/LED.vue

@@ -25,11 +25,11 @@
 </template>
 
 <script>
-import { getCamerasByDevice } from '@/api/external'
-import { Camera } from '@/constant'
+import { ThirdPartyDevice } from '@/constant'
+import { getThirdPartyDevicesByThirdPartyDevice } from '@/api/mesh'
 
 export default {
-  name: 'DeviceTrafficCamera',
+  name: 'DeviceLEDCamera',
   props: {
     device: {
       type: Object,
@@ -38,20 +38,17 @@ export default {
   },
   data () {
     return {
-      schema: { list: this.getCamerasByDevice },
+      schema: {
+        nonPagination: true,
+        list: this.getThirdPartyDevicesByThirdPartyDevice
+      },
       isActivated: true
     }
   },
   methods: {
-    getCamerasByDevice () {
-      return getCamerasByDevice(this.device.id, { cameraType: Camera.LED }).then(({ data }) => {
-        data = data.map(({ id, thirdPartyDevice }) => {
-          return {
-            ...thirdPartyDevice,
-            id
-          }
-        })
-        return { data }
+    getThirdPartyDevicesByThirdPartyDevice () {
+      return getThirdPartyDevicesByThirdPartyDevice(this.device.id, [ThirdPartyDevice.LED_CAMERA]).then(({ data }) => {
+        return { data: data.filter(({ instance }) => instance).map(({ instance }) => instance) }
       })
     },
     onFullScreen (camera) {

+ 0 - 0
src/views/device/detail/components/DeviceExternal/external/ReceivingCard/ReceivingCardInfo.vue → src/views/device/detail/components/DeviceExternal/components/ReceivingCard/ReceivingCardInfo.vue


+ 0 - 0
src/views/device/detail/components/DeviceExternal/external/ReceivingCard/ReceivingCardTopology.vue → src/views/device/detail/components/DeviceExternal/components/ReceivingCard/ReceivingCardTopology.vue


+ 0 - 0
src/views/device/detail/components/DeviceExternal/external/ReceivingCard/index.vue → src/views/device/detail/components/DeviceExternal/components/ReceivingCard/index.vue


+ 120 - 0
src/views/device/detail/components/DeviceExternal/components/SendingCard.vue

@@ -0,0 +1,120 @@
+<template>
+  <div
+    v-loading="loading"
+    class="c-info"
+  >
+    <warning
+      v-if="error"
+      @click="getThirdPartyDevicesByThirdPartyDevice"
+    />
+    <template v-else-if="info">
+      <div class="l-flex--row c-info__block">
+        <div class="l-flex--row l-flex__fill c-sibling-item">
+          <div class="l-flex__none c-info__title">厂商</div>
+          <div class="l-flex__fill c-info__value">{{ info.manufacturerName }}</div>
+        </div>
+        <div class="l-flex--row l-flex__fill c-sibling-item">
+          <div class="l-flex__none c-info__title">型号</div>
+          <div class="l-flex__fill c-info__value">{{ info.model }}</div>
+        </div>
+        <div class="l-flex--row l-flex__fill c-sibling-item">
+          <div class="l-flex__none c-info__title">唯一标识</div>
+          <div class="l-flex__fill c-info__value">{{ info.identifier }}</div>
+        </div>
+      </div>
+      <div class="l-flex--row c-info__block">
+        <div class="l-flex--row l-flex__fill c-sibling-item">
+          <div class="l-flex__none c-info__title">特性</div>
+          <div class="l-flex__fill l-flex--row">
+            <el-tag
+              class="c-sibling-item o-tag"
+              size="medium"
+              :type="type"
+            >
+              异步盒
+            </el-tag>
+            <el-tag
+              class="c-sibling-item o-tag"
+              size="medium"
+              :type="supportDetection"
+            >
+              设备监测
+            </el-tag>
+            <el-tag
+              class="c-sibling-item o-tag"
+              size="medium"
+              :type="supportContentProtection"
+            >
+              内容保护
+            </el-tag>
+            <el-tag
+              class="c-sibling-item o-tag"
+              size="medium"
+              :type="recoveryCard"
+            >
+              回采卡
+            </el-tag>
+          </div>
+        </div>
+      </div>
+    </template>
+  </div>
+</template>
+
+<script>
+import {
+  ThirdPartyDevice,
+  Transmitter
+} from '@/constant'
+import { getThirdPartyDevicesByThirdPartyDevice } from '@/api/mesh'
+
+export default {
+  name: 'DeviceSendingCard',
+  props: {
+    device: {
+      type: Object,
+      required: true
+    }
+  },
+  data () {
+    return {
+      loading: true,
+      error: false,
+      info: null
+    }
+  },
+  computed: {
+    type () {
+      return this.info && this.info.flag & Transmitter.IS_ASYNC ? 'success' : 'danger'
+    },
+    supportDetection () {
+      return this.info && this.info.flag & Transmitter.SUPPORT_DETECTION ? 'success' : 'danger'
+    },
+    supportContentProtection () {
+      return this.info && this.info.flag & Transmitter.SUPPORT_CONTENT_PROTECTION ? 'success' : 'danger'
+    },
+    recoveryCard () {
+      return this.info && this.info.flag & Transmitter.RECOVERY_CARD ? 'success' : 'danger'
+    }
+  },
+  created () {
+    this.getThirdPartyDevicesByThirdPartyDevice()
+  },
+  methods: {
+    getThirdPartyDevicesByThirdPartyDevice () {
+      this.loading = true
+      this.error = false
+      getThirdPartyDevicesByThirdPartyDevice(this.device.id, [ThirdPartyDevice.SENDING_CARD]).then(
+        ({ data }) => {
+          this.info = data[0].instance
+        },
+        () => {
+          this.error = true
+        }
+      ).finally(() => {
+        this.loading = false
+      })
+    }
+  }
+}
+</script>

+ 9 - 12
src/views/device/detail/components/DeviceExternal/external/Camera/Traffic.vue → src/views/device/detail/components/DeviceExternal/components/Traffic.vue

@@ -25,8 +25,8 @@
 </template>
 
 <script>
-import { getCamerasByDevice } from '@/api/external'
-import { Camera } from '@/constant'
+import { ThirdPartyDevice } from '@/constant'
+import { getThirdPartyDevicesByThirdPartyDevice } from '@/api/mesh'
 
 export default {
   name: 'DeviceTrafficCamera',
@@ -38,20 +38,17 @@ export default {
   },
   data () {
     return {
-      schema: { list: this.getCamerasByDevice },
+      schema: {
+        nonPagination: true,
+        list: this.getThirdPartyDevicesByThirdPartyDevice
+      },
       isActivated: true
     }
   },
   methods: {
-    getCamerasByDevice () {
-      return getCamerasByDevice(this.device.id, { cameraType: Camera.TRAFFIC }).then(({ data }) => {
-        data = data.map(({ id, thirdPartyDevice }) => {
-          return {
-            ...thirdPartyDevice,
-            id
-          }
-        })
-        return { data }
+    getThirdPartyDevicesByThirdPartyDevice () {
+      return getThirdPartyDevicesByThirdPartyDevice(this.device.id, [ThirdPartyDevice.TRAFFIC_CAMERA]).then(({ data }) => {
+        return { data: data.filter(({ instance }) => instance).map(({ instance }) => instance) }
       })
     },
     onFullScreen (camera) {

+ 0 - 200
src/views/device/detail/components/DeviceExternal/external/Gateway/index.vue

@@ -1,200 +0,0 @@
-<template>
-  <div
-    v-loading="loading"
-    class="l-flex--col u-overflow-y--auto"
-  >
-    <template v-if="!loading">
-      <warning
-        v-if="error"
-        @click="getGateway"
-      />
-      <template v-else>
-        <template v-if="gateway">
-          <div class="c-sibling-item--v c-info">
-            <div class="l-flex--row c-sibling-item--v u-bold">网关信息</div>
-            <div class="l-flex--row c-sibling-item--v  c-info__block">
-              <div class="l-flex--row l-flex__fill c-sibling-item">
-                <div class="l-flex__none c-info__title">名称</div>
-                <auto-text
-                  class="l-flex__fill c-info__value"
-                  :text="gateway.name"
-                />
-              </div>
-              <div class="l-flex--row l-flex__fill c-sibling-item">
-                <div class="l-flex__none c-info__title">地址</div>
-                <div class="l-flex__fill c-info__value">{{ gateway.ip }}</div>
-              </div>
-            </div>
-          </div>
-          <div class="c-sibling-item--v far c-info">
-            <div class="l-flex--row c-sibling-item--v u-bold">
-              <span class="c-sibling-item">PLC信息</span>
-              <i
-                v-if="plcOptions.loading"
-                class="c-sibling-item el-icon-loading"
-              />
-            </div>
-            <warning
-              v-if="plcOptions.error"
-              @click="getPLCs"
-            />
-            <div
-              v-else-if="!plcOptions.loading && plcOptions.list.length === 0"
-              class="c-sibling-item--v u-font-size--sm u-color--info"
-            >
-              暂未绑定
-            </div>
-            <div
-              v-for="plc in plcOptions.list"
-              :key="plc.id"
-              class="l-flex--row c-sibling-item--v c-info__block"
-            >
-              <div class="l-flex--row l-flex__fill c-sibling-item">
-                <div class="l-flex__none c-info__title">名称</div>
-                <auto-text
-                  class="l-flex__fill c-info__value"
-                  :text="plc.name"
-                />
-              </div>
-              <div class="l-flex--row l-flex__fill c-sibling-item">
-                <div class="l-flex__none c-info__title">地址</div>
-                <div class="l-flex__fill c-info__value">{{ plc.identifier }}</div>
-              </div>
-            </div>
-          </div>
-          <div class="c-sibling-item--v far c-info">
-            <div class="l-flex--row c-sibling-item--v u-bold">
-              <span class="c-sibling-item">传感器信息</span>
-              <i
-                v-if="sensorOptions.loading"
-                class="c-sibling-item el-icon-loading"
-              />
-            </div>
-            <warning
-              v-if="sensorOptions.error"
-              @click="getSensors"
-            />
-            <div
-              v-else-if="!sensorOptions.loading && sensorOptions.list.length === 0"
-              class="c-sibling-item--v u-font-size--sm u-color--info"
-            >
-              暂未绑定
-            </div>
-            <div
-              v-for="sensor in sensorOptions.list"
-              :key="sensor.id"
-              class="l-flex--row c-sibling-item--v c-info__block"
-            >
-              <div class="l-flex--row l-flex__fill c-sibling-item">
-                <div class="l-flex__none c-info__title">类型</div>
-                <div class="l-flex__fill c-info__value">{{ sensor.type }}</div>
-              </div>
-              <div class="l-flex--row l-flex__fill c-sibling-item">
-                <div class="l-flex__none c-info__title">标识</div>
-                <div class="l-flex__fill c-info__value">{{ sensor.identifier }}</div>
-              </div>
-            </div>
-          </div>
-        </template>
-      </template>
-    </template>
-  </div>
-</template>
-
-<script>
-import {
-  getGateway,
-  getPLCsByGateway,
-  getSensorsByGateway
-} from '@/api/external'
-
-export default {
-  name: 'DeviceGateway',
-  props: {
-    device: {
-      type: Object,
-      required: true
-    }
-  },
-  data () {
-    return {
-      loading: false,
-      error: false,
-      gatewayProxy: null,
-      deviceType: 'GATEWAY',
-      plcOptions: {
-        list: [],
-        loading: false,
-        error: false
-      },
-      sensorOptions: {
-        list: [],
-        loading: false,
-        error: false
-      }
-    }
-  },
-  computed: {
-    gateway () {
-      return this.gatewayProxy?.thirdPartyDevice
-    }
-  },
-  created () {
-    this.getGateway()
-  },
-  methods: {
-    getGateway () {
-      if (this.loading) {
-        return
-      }
-      this.loading = true
-      this.error = false
-      getGateway(this.device.id).then(
-        gateway => {
-          this.gatewayProxy = gateway
-          if (gateway) {
-            this.getPLCs()
-            this.getSensors()
-          }
-        },
-        () => {
-          this.error = true
-        }
-      ).finally(() => {
-        this.loading = false
-      })
-    },
-    getPLCs () {
-      this.plcOptions.loading = true
-      this.plcOptions.error = false
-      getPLCsByGateway(this.gateway.id).then(
-        ({ data }) => {
-          this.plcOptions.list = data
-        },
-        () => {
-          this.plcOptions.error = true
-        }
-      ).finally(() => {
-        this.plcOptions.loading = false
-      })
-    },
-    getSensors () {
-      this.sensorOptions.loading = true
-      this.sensorOptions.error = false
-      getSensorsByGateway(this.gateway.id).then(
-        ({ data }) => {
-          this.sensorOptions.list = data.map(sensor => {
-            sensor.type = ['烟雾', '温度', '光照', '水浸'][sensor.sensorType]
-            return sensor
-          })
-        },
-        () => {
-          this.sensorOptions.error = true
-        }
-      ).finally(() => {
-        this.sensorOptions.loading = false
-      })
-    }
-  }
-}
-</script>

+ 0 - 123
src/views/device/detail/components/DeviceExternal/external/SendingCard/index.vue

@@ -1,123 +0,0 @@
-<template>
-  <div
-    v-loading="loading"
-    class="l-flex--col"
-  >
-    <template v-if="!loading">
-      <warning
-        v-if="error"
-        @click="getSendingCard"
-      />
-      <div
-        v-else
-        class="c-info"
-      >
-        <div class="l-flex--row c-info__block">
-          <div class="l-flex--row l-flex__fill c-sibling-item">
-            <div class="l-flex__none c-info__title">名称</div>
-            <auto-text
-              class="l-flex__fill c-info__value"
-              :text="transmitter.name"
-            />
-          </div>
-          <div class="l-flex--row l-flex__fill c-sibling-item">
-            <div class="l-flex__none c-info__title">厂商</div>
-            <div class="l-flex__fill c-info__value">{{ transmitter.manufacturerName }}</div>
-          </div>
-        </div>
-        <div class="l-flex--row c-info__block">
-          <div class="l-flex--row l-flex__fill c-sibling-item">
-            <div class="l-flex__none c-info__title">型号</div>
-            <auto-text
-              class="l-flex__fill c-info__value"
-              :text="transmitter.type"
-            />
-          </div>
-          <div class="l-flex--row l-flex__fill c-sibling-item">
-            <div class="l-flex__none c-info__title">类型</div>
-            <div class="l-flex__fill c-info__value u-color--blue">{{ type }}</div>
-          </div>
-        </div>
-        <div class="l-flex--row c-info__block">
-          <div class="l-flex--row l-flex__fill c-sibling-item">
-            <div class="l-flex__none c-info__title">设备监测</div>
-            <div class="l-flex__fill c-info__value u-color--blue">{{ supportDetection }}</div>
-          </div>
-          <div class="l-flex--row l-flex__fill c-sibling-item">
-            <div class="l-flex__none c-info__title">内容保护</div>
-            <div class="l-flex__fill c-info__value u-color--blue">{{ supportContentProtection }}</div>
-          </div>
-        </div>
-        <div class="l-flex--row c-info__block">
-          <div class="l-flex--row l-flex__fill c-sibling-item">
-            <div class="l-flex__none c-info__title">回采卡</div>
-            <div class="l-flex__fill c-info__value u-color--blue">{{ recoveryCard }}</div>
-          </div>
-          <div class="l-flex--row l-flex__fill c-sibling-item" />
-        </div>
-      </div>
-    </template>
-  </div>
-</template>
-
-<script>
-import { getSendingCard } from '@/api/external'
-import { Transmitter } from '@/constant'
-
-export default {
-  name: 'DeviceSendingCard',
-  props: {
-    device: {
-      type: Object,
-      required: true
-    }
-  },
-  data () {
-    return {
-      loading: false,
-      error: false,
-      info: null
-    }
-  },
-  computed: {
-    transmitter () {
-      return this.info?.thirdPartyDevice
-    },
-    type () {
-      return this.transmitter && this.transmitter.flag & Transmitter.IS_ASYNC ? '异步盒' : '非异步盒'
-    },
-    supportDetection () {
-      return this.transmitter && this.transmitter.flag & Transmitter.SUPPORT_DETECTION ? '支持' : '不支持'
-    },
-    supportContentProtection () {
-      return this.transmitter && this.transmitter.flag & Transmitter.SUPPORT_CONTENT_PROTECTION ? '支持' : '不支持'
-    },
-    recoveryCard () {
-      return this.transmitter && this.transmitter.flag & Transmitter.RECOVERY_CARD ? '包含' : '不包含'
-    }
-  },
-  created () {
-    this.getSendingCard()
-  },
-  methods: {
-    getSendingCard () {
-      if (this.loading) {
-        return
-      }
-      this.info = null
-      this.loading = true
-      this.error = false
-      getSendingCard(this.device.id).then(
-        data => {
-          this.info = data
-        },
-        () => {
-          this.error = true
-        }
-      ).finally(() => {
-        this.loading = false
-      })
-    }
-  }
-}
-</script>

+ 8 - 8
src/views/device/detail/components/DeviceExternal/index.vue

@@ -22,11 +22,11 @@
 
 <script>
 import { ThirdPartyDevice } from '@/constant'
-import SendingCard from './external/SendingCard'
-import ReceivingCard from './external/ReceivingCard'
-import TrafficCamera from './external/Camera/Traffic.vue'
-import LedCamera from './external/Camera/LED.vue'
-import Gateway from './external/Gateway'
+import SendingCard from './components/SendingCard.vue'
+import ReceivingCard from './components/ReceivingCard'
+import LedCamera from './components/LED.vue'
+import TrafficCamera from './components/Traffic.vue'
+import Gateway from './components/Gateway.vue'
 
 export default {
   name: 'DeviceExternal',
@@ -62,12 +62,12 @@ export default {
         case ThirdPartyDevice.RECEIVING_CARD:
           activeComponent = 'ReceivingCard'
           break
-        case ThirdPartyDevice.TRAFFIC_CAMERA:
-          activeComponent = 'TrafficCamera'
-          break
         case ThirdPartyDevice.LED_CAMERA:
           activeComponent = 'LedCamera'
           break
+        case ThirdPartyDevice.TRAFFIC_CAMERA:
+          activeComponent = 'TrafficCamera'
+          break
         case ThirdPartyDevice.GATEWAY:
           activeComponent = 'Gateway'
           break

+ 32 - 12
src/views/device/detail/components/DeviceInvoke/PLCSwitch.vue

@@ -32,7 +32,10 @@
 
 <script>
 import { ThirdPartyDevice } from '@/constant'
-import { getThirdPartyDevicesByBox } from '@/api/mesh'
+import {
+  getThirdPartyDevicesByThirdPartyDevice,
+  getFollowThirdPartyDevicesByThirdPartyDevice
+} from '@/api/mesh'
 import { plcCommand } from '@/api/external'
 
 export default {
@@ -46,18 +49,35 @@ export default {
   methods: {
     onInvoke () {
       const loading = this.$showLoading()
-      getThirdPartyDevicesByBox(this.device.id, ThirdPartyDevice.PLC).finally(() => {
-        this.$closeLoading(loading)
-      }).then(({ data }) => {
-        if (data.length) {
+      getThirdPartyDevicesByThirdPartyDevice(this.device.id, [ThirdPartyDevice.GATEWAY])
+        .then(({ data }) => {
+          const gateway = data.find(({ instance }) => instance)
+          if (!gateway) {
+            this.$message({
+              type: 'warning',
+              message: '暂未绑定网关,请联系管理员'
+            })
+            return Promise.reject()
+          }
+          return getFollowThirdPartyDevicesByThirdPartyDevice(gateway.instance.id, [ThirdPartyDevice.PLC])
+        })
+        .then(({ data }) => {
+          const plc = data.find(({ instance }) => instance)
+          if (!plc) {
+            this.$message({
+              type: 'warning',
+              message: '网关暂未绑定PLC,请联系管理员'
+            })
+            return Promise.reject()
+          }
+          return Promise.resolve()
+        })
+        .finally(() => {
+          this.$closeLoading(loading)
+        })
+        .then(() => {
           this.$refs.dialog.show()
-        } else {
-          this.$message({
-            type: 'warning',
-            message: '暂未绑定PLC,请联系管理员'
-          })
-        }
-      })
+        })
     },
     onSwitch (open) {
       this.$confirm(

+ 1 - 1
src/views/device/detail/dashboard/DeviceInfo.vue

@@ -42,7 +42,7 @@ export default {
   },
   data () {
     return {
-      online: this.device.activate && this.device.onlineStatus === 1,
+      online: this.device.onlineStatus === 1,
       rebooting: false,
       items: [
         {

+ 28 - 10
src/views/device/detail/dashboard/LinkState.vue

@@ -86,8 +86,9 @@
 </template>
 
 <script>
-import { getBoundThirdPartyDevices } from '@/api/external'
 import { ThirdPartyDevice } from '@/constant'
+import { getThirdPartyDevicesByThirdPartyDevice } from '@/api/mesh'
+import { getReceivingCard } from '@/api/external'
 import Box from './Box'
 
 const LinkItems = Object.freeze([
@@ -179,7 +180,7 @@ export default {
         : { transform: `scale(${this.scale})` }
     },
     online () {
-      return this.device.activate && this.device.onlineStatus === 1
+      return this.device.onlineStatus === 1
     },
     linkState () {
       const map = {
@@ -193,9 +194,11 @@ export default {
         [ThirdPartyDevice.RECEIVING_CARD]: -1
       }
       if (this.linkDeviceMap) {
-        for (const item of this.linkDeviceMap) {
-          map[item.deviceType] = item.status
-        }
+        this.linkDeviceMap.forEach(({ nodeType, instance }) => {
+          if (instance && (map[nodeType] === 1 || map[nodeType] === -1)) {
+            map[nodeType] = instance.onlineStatus === 0 ? 0 : 1
+          }
+        })
       }
       return map
     },
@@ -339,8 +342,8 @@ export default {
     }
   },
   created () {
-    this.getBoundThirdPartyDevices()
-    this.$timer = setInterval(this.getBoundThirdPartyDevices, 5000)
+    this.getThirdPartyDevicesByThirdPartyDevice()
+    this.$timer = setInterval(this.getThirdPartyDevicesByThirdPartyDevice, 10000)
   },
   mounted () {
     const width = this.$refs.box.clientWidth
@@ -354,9 +357,24 @@ export default {
     getAnimation (length, gap, dur = 2, mode = 'linear') {
       return `${dur}s linkLength${length} ${mode} infinite`
     },
-    getBoundThirdPartyDevices () {
-      getBoundThirdPartyDevices(this.device.id, true).then(({ data }) => {
-        this.linkDeviceMap = data
+    getThirdPartyDevicesByThirdPartyDevice () {
+      Promise.all([
+        getThirdPartyDevicesByThirdPartyDevice(this.targetId, [
+          ThirdPartyDevice.GATEWAY,
+          ThirdPartyDevice.RECEIVING_CARD,
+          ThirdPartyDevice.SENDING_CARD,
+          ThirdPartyDevice.LED_CAMERA,
+          ThirdPartyDevice.TRAFFIC_CAMERA
+        ], { custom: true }),
+        getReceivingCard(this.device.id, { custom: true })
+      ]).then(([{ data: nodes }, { data: receivingCard }]) => {
+        this.linkDeviceMap = [
+          ...nodes,
+          {
+            nodeType: ThirdPartyDevice.RECEIVING_CARD,
+            instance: receivingCard
+          }
+        ]
         this.loaded = true
       })
     }

+ 10 - 15
src/views/device/detail/dashboard/index.vue

@@ -80,12 +80,9 @@
 </template>
 
 <script>
-import {
-  ThirdPartyDevice,
-  Camera
-} from '@/constant'
+import { ThirdPartyDevice } from '@/constant'
 import { getDevice } from '@/api/device'
-import { getCamerasByDevice } from '@/api/external'
+import { getThirdPartyDevicesByThirdPartyDevice } from '@/api/mesh'
 import {
   start,
   stop
@@ -184,17 +181,15 @@ export default {
       })
     },
     getCamera () {
-      if (!this.device) {
-        return
-      }
-      getCamerasByDevice(this.device.id, { cameraType: Camera.TRAFFIC }).then(({ data }) => {
-        data = data.map(({ id, thirdPartyDevice }) => {
-          return {
-            ...thirdPartyDevice,
-            id
+      getThirdPartyDevicesByThirdPartyDevice(this.id, [ThirdPartyDevice.TRAFFIC_CAMERA], { custom: true }).then(({ data }) => {
+        if (this.camera) {
+          const id = this.camera.id
+          const camera = data.find(({ instance }) => instance?.id === id)?.instance
+          if (camera) {
+            return
           }
-        })
-        this.camera = data[0]
+        }
+        this.camera = data.find(({ instance }) => instance)?.instance
       })
     }
   }

+ 14 - 11
src/views/device/detail/index.vue

@@ -94,6 +94,7 @@ export default {
       device: null,
       isActivated: false,
       isOnline: false,
+      isWaiting: false,
       active: 'DeviceInfo',
       tabs: [
         { key: 'DeviceInfo', label: '设备信息' },
@@ -112,21 +113,21 @@ export default {
     },
     statusType () {
       return this.isActivated
-        ? this.isOnline
-          ? 'success'
-          : 'error'
-        : this.device.activate
+        ? this.isWaiting
           ? 'primary'
-          : 'warning'
+          : this.isOnline
+            ? 'success'
+            : 'error'
+        : 'warning'
     },
     statusTip () {
       return this.isActivated
-        ? this.isOnline
-          ? '在线'
-          : '离线'
-        : this.device.activate
-          ? '已激活'
-          : '未激活'
+        ? this.isWaiting
+          ? '待接入'
+          : this.isOnline
+            ? '在线'
+            : '离线'
+        : '未激活'
     },
     deviceName () {
       return this.device?.name
@@ -169,6 +170,7 @@ export default {
           if (data) {
             this.device = data
             this.isActivated = data.activate
+            this.isWaiting = this.isActivated && data.onlineStatus === 0
             this.isOnline = this.isActivated && data.onlineStatus === 1
             start(this.device)
             addListener('online', this.onUpdateOnline)
@@ -192,6 +194,7 @@ export default {
     },
     onUpdateOnline (online) {
       this.isActivated = true
+      this.isWaiting = false
       this.isOnline = online
       this.device.onlineStatus = online ? 1 : 2
       if (this.isOnline) {

+ 19 - 10
src/views/platform/tenant/device/components/Device.vue → src/views/external/box/components/Device.vue

@@ -84,10 +84,16 @@
         </div>
       </div>
     </confirm-dialog>
+    <mesh-dialog ref="meshDialog" />
   </schema-table>
 </template>
 
 <script>
+import {
+  validMAC,
+  validLongitude,
+  validLatitude
+} from '@/utils/validate'
 import {
   getDevicesByTenant,
   addDevice,
@@ -98,14 +104,13 @@ import {
   deactivateDevice,
   getProducts
 } from '@/api/device'
-import {
-  validMAC,
-  validLongitude,
-  validLatitude
-} from '@/utils/validate'
+import MeshDialog from '../../components/MeshDialog.vue'
 
 export default {
   name: 'Device',
+  components: {
+    MeshDialog
+  },
   props: {
     tenant: {
       type: String,
@@ -156,17 +161,18 @@ export default {
             ? null
             : activate
               ? onlineStatus === 0
-                ? { type: 'primary', label: '已启用' }
+                ? { type: 'primary', label: '待接入' }
                 : onlineStatus === 1
                   ? { type: 'success', label: '在线' }
                   : { type: 'danger', label: '离线' }
               : { type: 'warning', label: '未激活' },
             on: this.onTagClick },
-          { type: 'invoke', width: __SUB_DEVICE__ ? 180 : 120, render: [
+          { type: 'invoke', render: [
             { label: '配置', render: ({ isMaster }) => isMaster, on: this.onSettingDevice },
-            __SUB_DEVICE__ ? { label: '添加备份', render: ({ isMaster }) => isMaster, on: this.onAddSubDevice } : null,
+            !__SUB_DEVICE__ && { label: '添加备份', render: ({ isMaster }) => isMaster, on: this.onAddSubDevice },
+            { label: '所属网点', render: ({ isMaster }) => isMaster, on: this.onViewMesh },
             { label: '删除', render: ({ empty }) => !empty, on: this.onDelDevice }
-          ].filter(Boolean) }
+          ].filter(Boolean), width: 240 }
         ]
       }
     }
@@ -417,9 +423,12 @@ export default {
     },
     onSettingDevice ({ id }) {
       this.$router.push({
-        name: 'tenant-device-management-settings',
+        name: 'box-settings',
         params: { id }
       })
+    },
+    onViewMesh ({ id }) {
+      this.$refs.meshDialog.show(id)
     }
   }
 }

+ 0 - 0
src/views/platform/tenant/device/components/Product.vue → src/views/external/box/components/Product.vue


+ 0 - 0
src/views/platform/tenant/device/components/ProductType.vue → src/views/external/box/components/ProductType.vue


+ 55 - 0
src/views/external/box/index.vue

@@ -0,0 +1,55 @@
+<template>
+  <wrapper
+    fill
+    margin
+    padding
+    background
+  >
+    <el-tabs
+      v-model="active"
+      class="c-tabs has-bottom-padding"
+    >
+      <el-tab-pane
+        label="设备"
+        name="Device"
+      />
+      <el-tab-pane
+        label="屏幕配置"
+        name="Product"
+      />
+      <el-tab-pane
+        label="屏幕类型"
+        name="ProductType"
+      />
+    </el-tabs>
+    <component
+      :is="active"
+      :key="`${tenant.id}_${active}`"
+      :tenant="tenant"
+    />
+  </wrapper>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import Device from './components/Device.vue'
+import Product from './components/Product.vue'
+import ProductType from './components/ProductType.vue'
+
+export default {
+  name: 'BoxManagement',
+  components: {
+    Device,
+    Product,
+    ProductType
+  },
+  data () {
+    return {
+      active: 'Device'
+    }
+  },
+  computed: {
+    ...mapGetters(['tenant'])
+  }
+}
+</script>

+ 0 - 0
src/views/platform/tenant/device/settings/api.js → src/views/external/box/settings/api.js


+ 0 - 1
src/views/platform/tenant/device/settings/components/AdConfigDialog.vue → src/views/external/box/settings/components/AdConfigDialog.vue

@@ -79,7 +79,6 @@ export default {
           price: 1000, // 单位分
           ...data
         }
-        console.log(this.attributes)
         this.$refs.configDialog.show()
       }).finally(() => {
         this.$closeLoading(loading)

+ 0 - 0
src/views/platform/tenant/device/settings/components/AttributeConfigDialog.vue → src/views/external/box/settings/components/AttributeConfigDialog.vue


+ 0 - 0
src/views/platform/tenant/device/settings/components/ContentProtectionConfigDialog.vue → src/views/external/box/settings/components/ContentProtectionConfigDialog.vue


+ 0 - 0
src/views/platform/tenant/device/settings/components/DeviceNormalConfig.vue → src/views/external/box/settings/components/DeviceNormalConfig.vue


+ 0 - 0
src/views/platform/tenant/device/settings/components/DeviceShadow.vue → src/views/external/box/settings/components/DeviceShadow.vue


+ 0 - 0
src/views/platform/tenant/device/settings/components/RecordConfigDialog.vue → src/views/external/box/settings/components/RecordConfigDialog.vue


+ 70 - 0
src/views/external/box/settings/components/external/Camera.vue

@@ -0,0 +1,70 @@
+<template>
+  <div
+    v-if="items.length"
+    class="c-info"
+  >
+    <div
+      v-for="info in items"
+      :key="info.id"
+      class="c-sibling-item--v"
+    >
+      <div class="c-sibling-item--v u-bold">{{ info.title }}({{ info.name }})</div>
+      <div class="c-sibling-item--v">
+        <template v-if="info.instance">
+          <div class="l-flex--row c-info__block">
+            <!-- <div class="l-flex--row l-flex__fill c-sibling-item">
+              <div class="l-flex__none c-info__title">厂商</div>
+              <div class="l-flex__fill c-info__value">{{ info.instance.manufacturerName }}</div>
+            </div>
+            <div class="l-flex--row l-flex__fill c-sibling-item">
+              <div class="l-flex__none c-info__title">型号</div>
+              <div class="l-flex__fill c-info__value">{{ info.instance.model }}</div>
+            </div> -->
+            <div class="l-flex--row l-flex__fill c-sibling-item">
+              <div class="l-flex__none c-info__title">唯一标识</div>
+              <div class="l-flex__fill c-info__value">{{ info.instance.identifier }}</div>
+            </div>
+            <div class="l-flex--row l-flex__fill c-sibling-item">
+              <div class="l-flex__none l-flex--row c-info__title">状态</div>
+              <div class="l-flex__fill c-info__value u-color--blue">{{ info.instance.status ? '在线' : '离线' }}</div>
+            </div>
+            <div class="l-flex--row l-flex__fill c-sibling-item" />
+          </div>
+        </template>
+        <div
+          v-else
+          class="u-color--info u-font-size--sm"
+        >
+          暂未绑定
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import {
+  CameraToThirdPartyMap,
+  ThirdPartyDeviceInfo
+} from '@/constant'
+
+const Cameras = Object.values(CameraToThirdPartyMap)
+
+export default {
+  name: 'Camera',
+  props: {
+    devices: {
+      type: Array,
+      required: true
+    }
+  },
+  computed: {
+    items () {
+      return this.devices.filter(({ nodeType }) => Cameras.includes(nodeType)).sort((a, b) => a.nodeType - b.nodeType).map(val => {
+        val.title = ThirdPartyDeviceInfo[val.nodeType]
+        return val
+      })
+    }
+  }
+}
+</script>

+ 77 - 0
src/views/external/box/settings/components/external/Camera/index.vue

@@ -0,0 +1,77 @@
+<template>
+  <div class="l-flex--col">
+    <grid-table
+      ref="table"
+      :schema="schema"
+      size="large"
+    >
+      <grid-table-item v-slot="item">
+        <camera-player
+          v-if="isActivated"
+          :key="item.identifier"
+          :camera="item"
+          controls
+          @fullscreen="onFullScreen(item)"
+          @click.native="onFullScreen(item)"
+        />
+      </grid-table-item>
+    </grid-table>
+    <camera-dialog
+      ref="cameraDialog"
+      @open="onOpen"
+      @closed="onClosed"
+    />
+  </div>
+</template>
+
+<script>
+import {
+  ThirdPartyDeviceInfo,
+  CameraToThirdPartyMap
+} from '@/constant'
+import { getThirdPartyDevicesByThirdPartyDevice } from '@/api/mesh'
+
+const Cameras = Object.values(CameraToThirdPartyMap)
+
+export default {
+  name: 'DeviceCamera',
+  props: {
+    device: {
+      type: Object,
+      required: true
+    }
+  },
+  data () {
+    return {
+      schema: {
+        nonPagination: true,
+        list: this.getThirdPartyDevicesByThirdPartyDevice
+      },
+      isActivated: true
+    }
+  },
+  methods: {
+    getThirdPartyDevicesByThirdPartyDevice () {
+      return getThirdPartyDevicesByThirdPartyDevice(this.device.id, Cameras).then(({ data }) => {
+        return {
+          data: data.filter(({ instance }) => instance).map(({ name, nodeType, instance }) => {
+            return {
+              ...instance,
+              name: `${ThirdPartyDeviceInfo[nodeType]}(${name})`
+            }
+          })
+        }
+      })
+    },
+    onFullScreen (camera) {
+      this.$refs.cameraDialog.show(camera)
+    },
+    onOpen () {
+      this.isActivated = false
+    },
+    onClosed () {
+      this.isActivated = true
+    }
+  }
+}
+</script>

+ 63 - 0
src/views/external/box/settings/components/external/Gateway.vue

@@ -0,0 +1,63 @@
+<template>
+  <div
+    v-if="items.length"
+    class="c-info"
+  >
+    <div
+      v-for="info in items"
+      :key="info.id"
+      class="c-sibling-item--v"
+    >
+      <div class="c-sibling-item--v u-bold">物联网关({{ info.name }})</div>
+      <div class="c-sibling-item--v">
+        <template v-if="info.instance">
+          <div class="l-flex--row c-info__block">
+            <div class="l-flex--row l-flex__fill c-sibling-item">
+              <div class="l-flex__none c-info__title">厂商</div>
+              <div class="l-flex__fill c-info__value">{{ info.instance.manufacturerName }}</div>
+            </div>
+            <div class="l-flex--row l-flex__fill c-sibling-item">
+              <div class="l-flex__none c-info__title">型号</div>
+              <div class="l-flex__fill c-info__value">{{ info.instance.model }}</div>
+            </div>
+            <div class="l-flex--row l-flex__fill c-sibling-item">
+              <div class="l-flex__none c-info__title">唯一标识</div>
+              <div class="l-flex__fill c-info__value">{{ info.instance.identifier }}</div>
+            </div>
+          </div>
+          <div class="l-flex--row c-info__block">
+            <div class="l-flex--row l-flex__fill c-sibling-item">
+              <div class="l-flex__none c-info__title">服务器</div>
+              <div class="l-flex__fill c-info__value u-color--blue">{{ info.instance.ip }}</div>
+            </div>
+          </div>
+        </template>
+        <div
+          v-else
+          class="u-color--info u-font-size--sm"
+        >
+          暂未绑定
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { ThirdPartyDevice } from '@/constant'
+
+export default {
+  name: 'Gateway',
+  props: {
+    devices: {
+      type: Array,
+      required: true
+    }
+  },
+  computed: {
+    items () {
+      return this.devices.filter(({ nodeType }) => nodeType === ThirdPartyDevice.GATEWAY)
+    }
+  }
+}
+</script>

+ 3 - 3
src/views/platform/tenant/device/settings/components/external/MultifunctionCard.vue → src/views/external/box/settings/components/external/MultifunctionCard.vue

@@ -1,10 +1,10 @@
 <template>
   <div
-    v-if="item.length"
+    v-if="items.length"
     class="c-info"
   >
     <div
-      v-for="info in item"
+      v-for="info in items"
       :key="info.id"
       class="c-sibling-item--v"
     >
@@ -60,7 +60,7 @@ export default {
     }
   },
   computed: {
-    item () {
+    items () {
       return this.devices.filter(({ nodeType }) => nodeType === ThirdPartyDevice.MULTI_FUNCTION_CARD)
     }
   }

+ 3 - 3
src/views/platform/tenant/device/settings/components/external/PLC.vue → src/views/external/box/settings/components/external/PLC.vue

@@ -1,10 +1,10 @@
 <template>
   <div
-    v-if="item.length"
+    v-if="items.length"
     class="c-info"
   >
     <div
-      v-for="info in item"
+      v-for="info in items"
       :key="info.id"
       class="c-sibling-item--v"
     >
@@ -55,7 +55,7 @@ export default {
     }
   },
   computed: {
-    item () {
+    items () {
       return this.devices.filter(({ nodeType }) => nodeType === ThirdPartyDevice.PLC)
     }
   }

+ 0 - 0
src/views/platform/tenant/device/settings/components/external/ReceivingCard/ReceivingCardTopology.vue → src/views/external/box/settings/components/external/ReceivingCard/ReceivingCardTopology.vue


+ 0 - 0
src/views/platform/tenant/device/settings/components/external/ReceivingCard/index.vue → src/views/external/box/settings/components/external/ReceivingCard/index.vue


+ 0 - 0
src/views/platform/tenant/device/settings/components/external/Screen.vue → src/views/external/box/settings/components/external/Screen.vue


+ 0 - 0
src/views/platform/tenant/device/settings/components/external/SendingCard.vue → src/views/external/box/settings/components/external/SendingCard.vue


+ 4 - 4
src/views/platform/tenant/device/settings/components/external/Sensor.vue → src/views/external/box/settings/components/external/Sensor.vue

@@ -1,10 +1,10 @@
 <template>
   <div
-    v-if="item.length"
+    v-if="items.length"
     class="c-info"
   >
     <div
-      v-for="info in item"
+      v-for="info in items"
       :key="info.id"
       class="c-sibling-item--v"
     >
@@ -46,7 +46,7 @@ import {
 const Sensors = Object.values(SensorToThirdPartyMap)
 
 export default {
-  name: 'MultifunctionCard',
+  name: 'Sensor',
   props: {
     devices: {
       type: Array,
@@ -54,7 +54,7 @@ export default {
     }
   },
   computed: {
-    item () {
+    items () {
       return this.devices.filter(({ nodeType }) => Sensors.includes(nodeType)).sort((a, b) => a.nodeType - b.nodeType).map(val => {
         val.title = ThirdPartyDeviceInfo[val.nodeType]
         return val

+ 25 - 3
src/views/platform/tenant/device/settings/components/external/index.vue → src/views/external/box/settings/components/external/index.vue

@@ -11,18 +11,32 @@
       <template v-else>
         <template v-if="info">
           <div class="c-sibling-item--v c-info">
-            <div class="c-sibling-item--v u-bold">网点</div>
+            <div class="l-flex--row c-sibling-item--v u-bold">
+              网点
+              <i
+                class="o-icon el-icon-refresh has-active"
+                @click="getMeshByBox"
+              />
+            </div>
             <div class="l-flex--row c-sibling-item--v c-info__block">
               <div class="l-flex--row l-flex__fill c-sibling-item">
                 <div class="l-flex__none c-info__title">名称</div>
                 <div class="l-flex__fill c-info__value">{{ info.name }}</div>
               </div>
-              <div class="l-flex--row l-flex__fill c-sibling-item far">
+              <div
+                class="l-flex--row l-flex__fill c-sibling-item"
+                style="flex-grow: 2;"
+              >
                 <div class="l-flex__none c-info__title">地址</div>
                 <div class="l-flex__fill c-info__value">{{ info.address }}</div>
               </div>
+              <div class="l-flex__none c-sibling-item" />
             </div>
           </div>
+          <camera
+            class="c-sibling-item--v far"
+            :devices="thirdPartyDevices"
+          />
           <screen
             class="c-sibling-item--v far"
             :devices="thirdPartyDevices"
@@ -39,6 +53,10 @@
             class="c-sibling-item--v far"
             :devices="thirdPartyDevices"
           />
+          <gateway
+            class="c-sibling-item--v far"
+            :devices="thirdPartyDevices"
+          />
           <plc
             class="c-sibling-item--v far"
             :devices="thirdPartyDevices"
@@ -64,6 +82,8 @@ import SendingCard from './SendingCard.vue'
 import MultifunctionCard from './MultifunctionCard.vue'
 import Sensor from './Sensor.vue'
 import PLC from './PLC.vue'
+import Gateway from './Gateway.vue'
+import Camera from './Camera.vue'
 
 export default {
   name: 'DeviceMesh',
@@ -72,7 +92,9 @@ export default {
     SendingCard,
     MultifunctionCard,
     Sensor,
-    plc: PLC
+    Gateway,
+    plc: PLC,
+    Camera
   },
   props: {
     device: {

+ 10 - 16
src/views/platform/tenant/device/settings/index.vue → src/views/external/box/settings/index.vue

@@ -54,17 +54,17 @@ import { getDevice } from '@/api/device'
 import DeviceNormalConfig from './components/DeviceNormalConfig'
 import DeviceMesh from './components/external'
 import ReceivingCard from './components/external/ReceivingCard'
-import Camera from './components/external/Camera'
+import DeviceCamera from './components/external/Camera'
 import Gateway from './components/external/Gateway'
 import DeviceShadow from './components/DeviceShadow'
 
 export default {
-  name: 'DeviceSettings',
+  name: 'BoxSettings',
   components: {
     DeviceNormalConfig,
     DeviceMesh,
     ReceivingCard,
-    Camera,
+    DeviceCamera,
     Gateway,
     DeviceShadow
   },
@@ -84,19 +84,13 @@ export default {
   computed: {
     ...mapGetters(['isSuperAdmin']),
     tabs () {
-      return this.isSuperAdmin
-        ? [
-          { key: 'DeviceNormalConfig', label: '基础配置' },
-          { key: 'DeviceMesh', label: '第三方设备' },
-          { key: 'ReceivingCard', label: '接收卡' },
-          { key: 'Camera', label: '摄像头' },
-          { key: 'Gateway', label: '网关' },
-          { key: 'DeviceShadow', label: '影子配置' }
-        ]
-        : [
-          { key: 'DeviceNormalConfig', label: '基础配置' },
-          { key: 'DeviceMesh', label: '第三方设备' }
-        ]
+      return [
+        { key: 'DeviceNormalConfig', label: '基础配置' },
+        { key: 'DeviceMesh', label: '第三方设备' },
+        this.isSuperAdmin && { key: 'ReceivingCard', label: '接收卡' },
+        { key: 'DeviceCamera', label: '摄像头' },
+        this.isSuperAdmin && { key: 'DeviceShadow', label: '影子配置' }
+      ].filter(Boolean)
     },
     deviceId () {
       return this.$route.params.id

+ 50 - 101
src/views/external/camera/index.vue

@@ -11,82 +11,67 @@
     />
     <confirm-dialog
       ref="editDialog"
-      :title="dialogTitle"
+      title="新增摄像头"
       @confirm="onConfirm"
     >
       <div class="c-grid-form u-align-self--center">
         <span class="c-grid-form__label u-required">名称</span>
         <el-input
-          v-model.trim="camera.name"
+          v-model.trim="item.name"
           placeholder="最多30个字符"
           maxlength="30"
           clearable
         />
-        <template v-if="!isEdit">
-          <span class="c-grid-form__label">类型</span>
-          <schema-select
-            v-model="camera.cameraType"
-            class="u-width--sm"
-            :schema="cameraTypeSelectSchema"
-          />
-          <span class="c-grid-form__label u-required">标识</span>
-          <el-input
-            v-model.trim="camera.identifier"
-            placeholder="仅支持数字和字母,最多30个字符"
-            maxlength="30"
-            clearable
-          />
-          <span class="c-grid-form__label u-required">账号</span>
-          <el-input v-model.trim="camera.username" />
-          <span class="c-grid-form__label u-required">密码</span>
-          <el-input
-            v-model.trim="camera.password"
-            class="u-password"
-          />
-        </template>
+        <span class="c-grid-form__label u-required">标识</span>
+        <el-input
+          v-model.trim="item.identifier"
+          placeholder="仅支持数字和字母,最多30个字符"
+          maxlength="30"
+          clearable
+        />
+        <span class="c-grid-form__label u-required">账号</span>
+        <el-input v-model.trim="item.username" />
+        <span class="c-grid-form__label u-required">密码</span>
+        <el-input
+          v-model.trim="item.password"
+          class="u-password"
+        />
       </div>
     </confirm-dialog>
     <camera-dialog ref="cameraDialog" />
-    <device-dialog ref="deviceDialog" />
+    <mesh-dialog ref="meshDialog" />
   </wrapper>
 </template>
 
 <script>
-import {
-  ThirdPartyDevice,
-  Camera,
-  ThirdPartyToCameraMap
-} from '@/constant'
+import { CameraToThirdPartyMap } from '@/constant'
+import { getThirdPartyDevices } from '@/api/external'
 import {
   addCamera,
-  updateCamera,
   deleteCamera
 } from '@/api/camera'
-import { getThirdPartyDevices } from '@/api/external'
+import MeshDialog from '../components/MeshDialog.vue'
 
 export default {
   name: 'Cameras',
-  data () {
-    const cameraTypeSelectSchema = {
-      options: [
-        { value: Camera.LED, label: 'LED屏监测' },
-        { value: Camera.TRAFFIC, label: '人流量监测' }
-      ]
+  components: {
+    MeshDialog
+  },
+  props: {
+    type: {
+      type: Number,
+      required: true
     }
-
+  },
+  data () {
     return {
-      cameraTypeSelectSchema,
+      item: {},
       schema: {
         list: this.getThirdPartyDevices,
-        condition: { deviceType: ThirdPartyDevice.LED_CAMERA },
         buttons: [
           { type: 'add', on: this.onAdd }
         ],
         filters: [
-          { key: 'deviceType', type: 'select', options: [
-            { value: ThirdPartyDevice.LED_CAMERA, label: 'LED屏监测' },
-            { value: ThirdPartyDevice.TRAFFIC_CAMERA, label: '人流量监测' }
-          ] },
           { key: 'boundFlag', type: 'select', placeholder: '使用情况', options: [
             { value: 0, label: '未使用' },
             { value: 1, label: '已使用' }
@@ -96,47 +81,33 @@ export default {
         cols: [
           { type: 'refresh' },
           { prop: 'name', label: '名称' },
-          { prop: 'identifier', label: '标识' },
+          { prop: 'identifier', label: '唯一标识' },
           { type: 'tag', render: ({ onlineStatus }) => onlineStatus === 1
             ? { type: 'success', label: '在线' }
             : { type: 'danger', label: '离线' } },
           { label: '使用情况', type: 'tag', render: ({ bound }) => bound
             ? { type: 'success', label: '已使用' }
             : { type: 'primary', label: '未使用' } },
-          { type: 'invoke', width: 240, render: [
-            { label: '编辑', on: this.onEdit },
-            { label: '查看', allow: ({ onlineStatus }) => onlineStatus, on: this.onFullScreen },
-            { label: '相关设备', allow: ({ bound }) => bound, on: this.onViewDevice },
-            { label: '删除', on: this.onDel }
-          ] }
+          { type: 'invoke', render: [
+            { label: '查看', allow: ({ onlineStatus }) => onlineStatus, on: this.onView },
+            { label: '所属网点', allow: ({ bound }) => bound, on: this.onViewMesh },
+            { label: '删除', allow: ({ bound }) => !bound, on: this.onDel }
+          ], width: 200 }
         ]
-      },
-      camera: {}
-    }
-  },
-  computed: {
-    isEdit () {
-      return !!this.camera.id
-    },
-    dialogTitle () {
-      if (this.isEdit) {
-        return `编辑${this.camera.type === Camera.LED ? 'LED屏监测摄像头' : '人流量监测摄像头'}`
       }
-      return '新增摄像头'
     }
   },
   methods: {
     getThirdPartyDevices (params) {
       return getThirdPartyDevices({
-        cameraType: ThirdPartyToCameraMap[params.deviceType],
+        cameraType: this.type,
+        deviceType: CameraToThirdPartyMap[this.type],
         ...params
       })
     },
     onAdd () {
-      this.camera = {
-        cameraType: this.$refs.table.info().deviceType === ThirdPartyDevice.LED_CAMERA
-          ? Camera.LED
-          : Camera.TRAFFIC,
+      this.item = {
+        cameraType: this.type,
         name: '',
         identifier: '',
         username: '',
@@ -144,66 +115,44 @@ export default {
       }
       this.$refs.editDialog.show()
     },
-    onEdit (camera) {
-      this.camera = {
-        id: camera.id,
-        name: camera.name,
-        identifier: camera.identifier,
-        username: camera.username
-      }
-      this.$refs.editDialog.show()
-    },
     onConfirm (done) {
-      if (!this.camera.name) {
+      if (!this.item.name) {
         this.$message({
           type: 'warning',
           message: '请填写设备名称'
         })
         return
       }
-      if (this.isEdit) {
-        this.editCamera(done)
-      } else {
-        this.addCamera(done)
-      }
-    },
-    addCamera (done) {
-      if (!this.camera.identifier) {
+      if (!this.item.identifier) {
         this.$message({
           type: 'warning',
           message: '请填写设备标识'
         })
         return
       }
-      if (!/^[0-9a-zA-Z]+$/.test(this.camera.identifier)) {
+      if (!/^[0-9a-zA-Z]+$/.test(this.item.identifier)) {
         this.$message({
           type: 'warning',
           message: '设备标识格式错误仅支持数字和字母'
         })
         return
       }
-      if (!this.camera.username) {
+      if (!this.item.username) {
         this.$message({
           type: 'warning',
           message: '请填写账号'
         })
         return
       }
-      if (!this.camera.password) {
+      if (!this.item.password) {
         this.$message({
           type: 'warning',
           message: '请填写密码'
         })
         return
       }
-      addCamera(this.camera).then(() => {
-        this.$refs.table.resetCondition({ cameraType: this.camera.cameraType, name: this.camera.name })
-        done()
-      })
-    },
-    editCamera (done) {
-      updateCamera(this.camera).then(() => {
-        this.$refs.table.pageTo()
+      addCamera(this.item).then(() => {
+        this.$refs.table.resetCondition({ name: this.camera.name })
         done()
       })
     },
@@ -212,11 +161,11 @@ export default {
         this.$refs.table.decrease(1)
       })
     },
-    onFullScreen (camera) {
+    onView (camera) {
       this.$refs.cameraDialog.show(camera)
     },
-    onViewDevice ({ id }) {
-      this.$refs.deviceDialog.show(id)
+    onViewMesh ({ id }) {
+      this.$refs.meshDialog.show(id)
     }
   }
 }

+ 54 - 0
src/views/external/components/MeshDialog.vue

@@ -0,0 +1,54 @@
+<template>
+  <table-dialog
+    ref="tableDialg"
+    title="所属网点"
+    :schema="schema"
+    v-bind="$attrs"
+    v-on="$listeners"
+  />
+</template>
+
+<script>
+import { getMeshByInstance } from '@/api/mesh'
+
+export default {
+  name: 'MeshDialog',
+  data () {
+    return {
+      thirdPartyDeviceId: null
+    }
+  },
+  computed: {
+    schema () {
+      return {
+        nonPagination: true,
+        list: this.getMeshByInstance,
+        cols: [
+          { prop: 'name', label: '网点' },
+          { prop: 'address', label: '地址' },
+          { type: 'invoke', render: [
+            { label: '详情', on: this.onView }
+          ] }
+        ]
+      }
+    }
+  },
+  methods: {
+    getMeshByInstance () {
+      return getMeshByInstance(this.thirdPartyDeviceId).then(({ data }) => {
+        return { data: data ? [data] : [] }
+      })
+    },
+    show (id) {
+      this.thirdPartyDeviceId = id
+      this.$refs.tableDialg.show()
+    },
+    onView ({ meshId }) {
+      this.$router.push({
+        name: 'mesh',
+        params: { id: meshId }
+      })
+    }
+  }
+}
+</script>

+ 0 - 126
src/views/external/gateway/components/PLCDialog.vue

@@ -1,126 +0,0 @@
-<template>
-  <table-dialog
-    ref="tableDialog"
-    title="PLC"
-    :schema="schema"
-  >
-    <confirm-dialog
-      ref="editDialog"
-      :title="dialogTitle"
-      append-to-body
-      @confirm="onConfirm"
-    >
-      <div class="c-grid-form u-align-self--center">
-        <span class="c-grid-form__label u-required">名称</span>
-        <el-input
-          v-model.trim="plc.name"
-          placeholder="最多30个字符"
-          maxlength="30"
-          clearable
-        />
-        <span class="c-grid-form__label u-required">地址</span>
-        <el-input
-          v-model.trim="plc.identifier"
-          placeholder="PLC网络地址"
-          clearable
-        />
-      </div>
-    </confirm-dialog>
-  </table-dialog>
-</template>
-
-<script>
-import {
-  getPLCsByGateway,
-  addPLCToGateway,
-  updateGatewayPLC,
-  deleteGatewayPLC
-} from '@/api/external'
-
-export default {
-  name: 'PLCList',
-  data () {
-    return {
-      plc: {},
-      schema: {
-        nonPagination: true,
-        list: this.getPLCs,
-        buttons: [
-          { type: 'add', on: this.onAdd }
-        ],
-        cols: [
-          { prop: 'name', label: '名称' },
-          { prop: 'identifier', label: '标识' },
-          { type: 'invoke', render: [
-            { label: '编辑', on: this.onEdit },
-            { label: '删除', on: this.onDel }
-          ] }
-        ]
-      }
-    }
-  },
-  computed: {
-    dialogTitle () {
-      return this.plc.id ? '编辑PLC' : '新增PLC'
-    }
-  },
-  methods: {
-    show (gatewayId) {
-      this.$gatewayId = gatewayId
-      this.$refs.tableDialog.show()
-    },
-    getPLCs () {
-      return getPLCsByGateway(this.$gatewayId)
-    },
-    onAdd () {
-      this.plc = {
-        name: '',
-        identifier: ''
-      }
-      this.$refs.editDialog.show()
-    },
-    onEdit ({ id, name, identifier }) {
-      this.plc = { id, name, identifier }
-      this.$refs.editDialog.show()
-    },
-    onConfirm (done) {
-      if (!this.plc.name) {
-        this.$message({
-          type: 'warning',
-          message: '请填写PLC名称'
-        })
-        return
-      }
-      if (!this.plc.identifier) {
-        this.$message({
-          type: 'warning',
-          message: '请填写PLC标识'
-        })
-        return
-      }
-      if (this.plc.id) {
-        this.onConfirmEdit(this.plc, done)
-      } else {
-        this.onConfirmAdd(this.plc, done)
-      }
-    },
-    onConfirmAdd (plc, done) {
-      addPLCToGateway(this.$gatewayId, { ...plc }).then(() => {
-        done()
-        this.$refs.table.resetCondition()
-      })
-    },
-    onConfirmEdit (plc, done) {
-      updateGatewayPLC(plc).then(() => {
-        done()
-        this.$refs.table.pageTo()
-      })
-    },
-    onDel (plc) {
-      deleteGatewayPLC(plc).then(() => {
-        this.$refs.table.decrease(1)
-      })
-    }
-  }
-}
-</script>

+ 0 - 168
src/views/external/gateway/components/SensorDialog.vue

@@ -1,168 +0,0 @@
-<template>
-  <table-dialog
-    ref="tableDialog"
-    title="传感器"
-    :schema="schema"
-  >
-    <confirm-dialog
-      ref="editDialog"
-      :title="dialogTitle"
-      append-to-body
-      @confirm="onConfirm"
-    >
-      <div class="c-grid-form u-align-self--center">
-        <span class="c-grid-form__label u-required">类型</span>
-        <schema-select
-          v-model="sensor.sensorType"
-          class="u-width--xs"
-          :schema="sensorTypeSelectchema"
-          :disabled="!isAdd"
-        />
-        <span class="c-grid-form__label u-required">名称</span>
-        <el-input
-          v-model.trim="sensor.name"
-          placeholder="最多30个字符"
-          maxlength="30"
-          clearable
-        />
-        <span class="c-grid-form__label u-required">标识</span>
-        <el-input
-          v-model.trim="sensor.identifier"
-          placeholder="最多30个字符"
-          maxlength="30"
-          :disabled="!isAdd"
-          clearable
-        />
-      </div>
-    </confirm-dialog>
-  </table-dialog>
-</template>
-
-<script>
-import {
-  Sensor,
-  SensorInfo
-} from '@/constant'
-import {
-  getSensorsByGateway,
-  addSensorToGateway,
-  updateGatewaySensor,
-  deleteGatewaySensor
-} from '@/api/external'
-
-export default {
-  name: 'SensorList',
-  data () {
-    return {
-      sensor: {},
-      sensorTypeSelectchema: {
-        options: Object.keys(Sensor).map(key => {
-          return { value: Sensor[key], label: SensorInfo[Sensor[key]] }
-        })
-      },
-      schema: {
-        nonPagination: true,
-        list: this.getSensors,
-        buttons: [
-          { type: 'add', on: this.onAdd }
-        ],
-        cols: [
-          { label: '类型', render: ({ sensorType }) => SensorInfo[sensorType], width: 100, align: 'center' },
-          { label: '名称', render: (data, h) => h('edit-input', {
-            props: {
-              value: `${data.name}`
-            },
-            on: { edit: val => this.onEditName(data, val) }
-          }), 'class-name': 'c-edit-column' },
-          { prop: 'identifier', label: '标识' },
-          { type: 'invoke', render: [
-            { label: '删除', on: this.onDel }
-          ] }
-        ]
-      }
-    }
-  },
-  computed: {
-    isAdd () {
-      return !this.sensor.id
-    },
-    dialogTitle () {
-      return this.isAdd ? '新增传感器' : '编辑传感器'
-    }
-  },
-  methods: {
-    show (gatewayId) {
-      this.$gatewayId = gatewayId
-      this.$refs.tableDialog.show()
-    },
-    getSensors () {
-      return getSensorsByGateway(this.$gatewayId)
-    },
-    onAdd () {
-      this.sensor = {
-        name: '',
-        identifier: '',
-        sensorType: Sensor.TEMPERATURE
-      }
-      this.$refs.editDialog.show()
-    },
-    onEditName (sensor, { newVal, oldVal }) {
-      if (newVal === oldVal) {
-        return
-      }
-      if (!newVal) {
-        this.$message({
-          type: 'warning',
-          message: '请填写传感器名称'
-        })
-        return
-      }
-      sensor.name = newVal
-      updateGatewaySensor({
-        id: sensor.id,
-        name: newVal
-      }).catch(() => {
-        sensor.name = oldVal
-      })
-    },
-    onConfirm (done) {
-      if (!this.sensor.name) {
-        this.$message({
-          type: 'warning',
-          message: '请填写传感器名称'
-        })
-        return
-      }
-      if (!this.sensor.identifier) {
-        this.$message({
-          type: 'warning',
-          message: '请填写传感器标识'
-        })
-        return
-      }
-      if (this.isAdd) {
-        this.onConfirmAdd(this.sensor, done)
-      } else {
-        this.onConfirmEdit(this.sensor, done)
-      }
-    },
-    onConfirmAdd (sensor, done) {
-      addSensorToGateway(this.$gatewayId, { ...sensor }).then(() => {
-        done()
-        this.$refs.table.resetCondition()
-      })
-    },
-    onConfirmEdit (sensor, done) {
-      updateGatewaySensor(sensor).then(() => {
-        done()
-        this.$refs.table.pageTo()
-      })
-    },
-    onDel (sensor) {
-      deleteGatewaySensor(sensor).then(() => {
-        this.$refs.table.decrease(1)
-      })
-    }
-  }
-}
-</script>

+ 129 - 86
src/views/external/gateway/index.vue

@@ -11,187 +11,230 @@
     />
     <confirm-dialog
       ref="editDialog"
-      :title="dialogTitle"
+      title="新增网关"
       @confirm="onConfirm"
     >
       <div class="c-grid-form u-align-self--center">
-        <span class="c-grid-form__label u-required">名称</span>
-        <el-input
-          v-model.trim="gateway.name"
-          placeholder="最多30个字符"
-          maxlength="30"
-          clearable
+        <span class="c-grid-form__label u-required">厂商</span>
+        <schema-select
+          ref="manufacturer"
+          v-model="item.manufacturerKey"
+          class="u-width"
+          placeholder="请选择厂商"
+          :schema="manufacturerSelectSchema"
         />
-        <span class="c-grid-form__label u-required">地址</span>
+        <span class="c-grid-form__label u-required">型号</span>
         <el-input
-          v-model.trim="gateway.ip"
-          placeholder="网关网络地址"
+          v-model.trim="item.model"
+          placeholder="最多50个字符"
+          maxlength="50"
           clearable
         />
-        <span class="c-grid-form__label u-required">MAC</span>
+        <span class="c-grid-form__label u-required">服务器地址</span>
         <el-input
-          v-model.trim="gateway.mac"
-          class="u-width--sm"
-          placeholder="ff:ff:ff:ff:ff:ff"
-          maxlength="17"
+          v-model.trim="item.ip"
+          placeholder="10.180.88.84"
           clearable
         />
-        <span class="c-grid-form__label">账号</span>
-        <el-input v-model.trim="gateway.account" />
-        <span class="c-grid-form__label">密码</span>
+        <span class="c-grid-form__label u-required">服务器账号</span>
+        <el-input v-model.trim="item.account" />
+        <span class="c-grid-form__label u-required">服务器密码</span>
         <el-input
-          v-model.trim="gateway.password"
+          v-model.trim="item.password"
           class="u-password"
         />
+        <span class="c-grid-form__label u-required">唯一标识</span>
+        <el-input
+          v-model.trim="item.identifier"
+          placeholder="最多50个字符"
+          maxlength="50"
+          clearable
+        />
       </div>
     </confirm-dialog>
-    <device-dialog ref="deviceDialog" />
-    <plc-dialog ref="plcDialog" />
-    <sensor-dialog ref="sensorDialog" />
+    <table-dialog
+      ref="tableDialog"
+      title="关联设备"
+      :schema="deviceSchema"
+    />
+    <mesh-dialog ref="meshDialog" />
   </wrapper>
 </template>
 
 <script>
-import { validMAC } from '@/utils/validate'
 import {
+  ThirdPartyDevice,
+  ThirdPartyDeviceInfo,
+  SensorToThirdPartyMap
+} from '@/constant'
+import {
+  getManufacturersByType,
   getGateways,
   addGateway,
-  updateGateway,
   deleteGateway
 } from '@/api/external'
-import PLCDialog from './components/PLCDialog.vue'
-import SensorDialog from './components/SensorDialog.vue'
+import { getFollowThirdPartyDevicesByThirdPartyDevice } from '@/api/mesh'
+import MeshDialog from '../components/MeshDialog.vue'
+
+const sensors = Object.values(SensorToThirdPartyMap)
 
 export default {
   name: 'GatewayList',
   components: {
-    plcDialog: PLCDialog,
-    SensorDialog
+    MeshDialog
   },
   data () {
+    const manufacturerSelectSchema = {
+      remote: this.getManufacturersByType,
+      value: 'manufacturerKey',
+      label: 'manufacturerName'
+    }
+
     return {
-      gateway: {},
+      item: {},
+      manufacturerSelectSchema,
       schema: {
         list: getGateways,
         buttons: [
           { type: 'add', on: this.onAdd }
         ],
         filters: [
+          { key: 'manufacturerKey', type: 'select', placeholder: '厂商', ...manufacturerSelectSchema },
           { key: 'boundFlag', type: 'select', placeholder: '使用情况', options: [
-            { value: 0, label: '使用' },
-            { value: 1, label: '使用' }
+            { value: 0, label: '使用' },
+            { value: 1, label: '使用' }
           ] },
-          { key: 'name', type: 'search', placeholder: '网关名称' }
+          { key: 'identifier', type: 'search', placeholder: '唯一标识' }
         ],
         cols: [
-          { prop: 'name', label: '名称' },
-          { prop: 'ip', label: '地址' },
-          { type: 'tag', render: ({ status }) => status === 1
-            ? { type: 'success', label: '在线' }
-            : { type: 'danger', label: '离线' } },
+          { prop: 'manufacturerName', label: '厂商' },
+          { prop: 'model', label: '型号' },
+          { prop: 'ip', label: '服务器地址' },
+          { prop: 'identifier', label: '唯一标识' },
           { label: '使用情况', type: 'tag', render: ({ bound }) => bound
             ? { type: 'success', label: '已使用' }
             : { type: 'primary', label: '未使用' } },
-          { type: 'invoke', width: 280, render: [
-            { label: '编辑', on: this.onEdit },
-            { label: 'PLC', on: this.onEditPLC },
-            { label: '传感器', on: this.onEditSensor },
-            { label: '相关设备', allow: ({ bound }) => bound, on: this.onViewDevice },
-            { label: '删除', on: this.onDel }
-          ] }
+          { type: 'invoke', render: [
+            { label: '关联设备', on: this.onViewDevices },
+            { label: '所属网点', allow: ({ bound }) => bound, on: this.onViewMesh },
+            { label: '删除', allow: ({ bound }) => !bound, on: this.onDel }
+          ], width: 200 }
+        ]
+      },
+      deviceSchema: {
+        nonPagination: true,
+        list: this.getFollowThirdPartyDevicesByThirdPartyDevice,
+        cols: [
+          { prop: 'type', label: '设备类型' },
+          { prop: 'manufacturerName', label: '厂商' },
+          { prop: 'model', label: '型号' },
+          { prop: 'identifier', label: '唯一标识' }
         ]
       }
     }
   },
-  computed: {
-    dialogTitle () {
-      return this.gateway.id ? '编辑网关' : '新增网关'
-    }
-  },
   methods: {
+    getManufacturersByType () {
+      return getManufacturersByType(ThirdPartyDevice.GATEWAY)
+    },
     onAdd () {
-      this.gateway = {
-        name: '',
+      this.item = {
+        identifier: '',
+        manufacturerKey: '',
+        model: '',
         ip: '',
-        mac: '',
         account: '',
         password: ''
       }
       this.$refs.editDialog.show()
     },
-    onEdit ({ id, name, ip, mac, account, password }) {
-      this.$gateway = { id, name, ip, mac, account, password }
-      this.gateway = { id, name, ip, mac, account, password }
-      this.$refs.editDialog.show()
-    },
     onConfirm (done) {
-      const gateway = this.gateway
-      if (!gateway.name) {
+      if (!this.item.manufacturerKey) {
         this.$message({
           type: 'warning',
-          message: '请填写网关名称'
+          message: '请选择厂商'
         })
         return
       }
-      if (!gateway.ip) {
+      if (!this.item.model) {
         this.$message({
           type: 'warning',
-          message: '请填写网关地址'
+          message: '请填写型号'
         })
         return
       }
-      if (!gateway.mac) {
+      if (!this.item.identifier) {
         this.$message({
           type: 'warning',
-          message: '请填写MAC'
+          message: '请填写唯一标识'
         })
         return
       }
-      if (!validMAC(gateway.mac)) {
+      if (!this.item.ip) {
         this.$message({
           type: 'warning',
-          message: 'MAC格式不正确,例 ff:ff:ff:ff:ff:ff'
+          message: '请填写网关服务器地址'
         })
         return
       }
-      if (!gateway.account !== !gateway.password) {
+      if (!this.item.account) {
         this.$message({
           type: 'warning',
-          message: '账号密码必须同时填写'
+          message: '请填写网关服务器账号'
         })
         return
       }
-      if (gateway.id) {
-        this.onConfirmEdit(gateway, done)
-      } else {
-        this.onConfirmAdd(gateway, done)
+      if (!this.item.password) {
+        this.$message({
+          type: 'warning',
+          message: '请填写网关服务器密码'
+        })
+        return
       }
-    },
-    onConfirmAdd (gateway, done) {
-      addGateway(gateway).then(() => {
+      const key = this.item.manufacturerKey
+      addGateway({
+        manufacturerName: this.$refs.manufacturer.getOptions().find(({ value }) => value === key).label,
+        ...this.item
+      }).then(() => {
         done()
-        this.$refs.table.resetCondition()
+        this.$refs.table.resetCondition({
+          manufacturerKey: key,
+          identifier: this.item.identifier
+        })
       })
     },
-    onConfirmEdit (gateway, done) {
-      updateGateway(gateway).then(() => {
-        done()
-        this.$refs.table.pageTo()
+    onDel (item) {
+      deleteGateway(item).then(() => {
+        this.$refs.table.decrease(1)
       })
     },
-    onDel (gateway) {
-      deleteGateway(gateway).then(() => {
-        this.$refs.table.decrease(1)
+    onViewDevices ({ id }) {
+      this.$gatewayId = id
+      this.$refs.tableDialog.show()
+    },
+    getFollowThirdPartyDevicesByThirdPartyDevice () {
+      return getFollowThirdPartyDevicesByThirdPartyDevice(this.$gatewayId, [
+        ...sensors,
+        ThirdPartyDevice.PLC
+      ]).then(({ data }) => {
+        return { data: data.filter(({ instance }) => instance).map(this.transformThirdPartyDevice) }
       })
     },
+    transformThirdPartyDevice ({ nodeType, instance }) {
+      const { manufacturerName, model, identifier } = instance
+      return {
+        type: ThirdPartyDeviceInfo[nodeType],
+        manufacturerName, model, identifier
+      }
+    },
+    onViewMesh ({ id }) {
+      this.$refs.meshDialog.show(id)
+    },
     onEditPLC ({ id }) {
       this.$refs.plcDialog.show(id)
     },
     onEditSensor ({ id }) {
       this.$refs.sensorDialog.show(id)
-    },
-    onViewDevice ({ id }) {
-      this.$refs.deviceDialog.show(id)
     }
   }
 }

+ 50 - 11
src/views/external/mesh/index.vue

@@ -201,7 +201,8 @@ import {
   ThirdPartyDeviceInfo,
   Transmitter,
   ThirdPartyToSensorMap,
-  SensorToThirdPartyMap
+  SensorToThirdPartyMap,
+  ThirdPartyToCameraMap
 } from '@/constant'
 import {
   getMesh,
@@ -219,11 +220,13 @@ import {
 } from '@/api/mesh'
 import {
   getManufacturersByType,
+  getGateways,
   getScreens,
   getSendingCards,
   getMultifunctionCards,
   getSensors,
-  getPLCs
+  getPLCs,
+  getThirdPartyDevices
 } from '@/api/external'
 import { getDevices } from '@/api/device'
 import * as echarts from 'echarts'
@@ -406,9 +409,25 @@ export default {
     },
     schema () {
       switch (this.type) {
+        case ThirdPartyDevice.GATEWAY:
+          return {
+            list: getGateways,
+            condition: { boundFlag: 0 },
+            filters: [
+              { key: 'manufacturerKey', type: 'select', placeholder: '厂商', remote: this.getManufacturersByType, value: 'manufacturerKey', label: 'manufacturerName' },
+              { key: 'identifier', type: 'search', placeholder: '唯一标识' }
+            ],
+            cols: [
+              { prop: 'manufacturerName', label: '厂商' },
+              { prop: 'model', label: '型号' },
+              { prop: 'ip', label: '服务器地址' },
+              { prop: 'identifier', label: '唯一标识' }
+            ]
+          }
         case ThirdPartyDevice.SCREEN:
           return {
             list: getScreens,
+            condition: { boundFlag: 0 },
             filters: [
               { key: 'manufacturerKey', type: 'select', placeholder: '厂商', remote: this.getManufacturersByType, value: 'manufacturerKey', label: 'manufacturerName' },
               { key: 'identifier', type: 'search', placeholder: '唯一标识' }
@@ -425,6 +444,7 @@ export default {
         case ThirdPartyDevice.SENDING_CARD:
           return {
             list: getSendingCards,
+            condition: { boundFlag: 0 },
             filters: [
               { key: 'manufacturerKey', type: 'select', placeholder: '厂商', remote: this.getManufacturersByType, value: 'manufacturerKey', label: 'manufacturerName' },
               { key: 'identifier', type: 'search', placeholder: '唯一标识' }
@@ -440,12 +460,13 @@ export default {
                   { type: flag & Transmitter.SUPPORT_CONTENT_PROTECTION ? 'success' : 'danger', label: '内容保护' },
                   { type: flag & Transmitter.RECOVERY_CARD ? 'success' : 'danger', label: '回采卡' }
                 ]
-              }, width: 364, align: 'left' }
+              }, width: 364 }
             ]
           }
         case ThirdPartyDevice.PLC:
           return {
             list: getPLCs,
+            condition: { boundFlag: 0 },
             filters: [
               { key: 'manufacturerKey', type: 'select', placeholder: '厂商', remote: this.getManufacturersByType, value: 'manufacturerKey', label: 'manufacturerName' },
               { key: 'identifier', type: 'search', placeholder: '唯一标识' }
@@ -471,6 +492,7 @@ export default {
         case ThirdPartyDevice.MULTI_FUNCTION_CARD:
           return {
             list: getMultifunctionCards,
+            condition: { boundFlag: 0 },
             filters: [
               { key: 'manufacturerKey', type: 'select', placeholder: '厂商', remote: this.getManufacturersByType, value: 'manufacturerKey', label: 'manufacturerName' },
               { key: 'identifier', type: 'search', placeholder: '唯一标识' }
@@ -490,7 +512,7 @@ export default {
         case ThirdPartyDevice.TRANSLOCATION_SENSOR:
           return {
             list: getSensors,
-            condition: { sensorType: ThirdPartyToSensorMap[this.type] },
+            condition: { sensorType: ThirdPartyToSensorMap[this.type], boundFlag: 0 },
             filters: [
               { key: 'manufacturerKey', type: 'select', placeholder: '厂商', remote: this.getManufacturersByType, value: 'manufacturerKey', label: 'manufacturerName' },
               { key: 'identifier', type: 'search', placeholder: '唯一标识' }
@@ -501,8 +523,26 @@ export default {
               { prop: 'identifier', label: '唯一标识' }
             ]
           }
+        case ThirdPartyDevice.LED_CAMERA:
+        case ThirdPartyDevice.TRAFFIC_CAMERA:
+          return {
+            list: getThirdPartyDevices,
+            condition: { boundFlag: 0, cameraType: ThirdPartyToCameraMap[this.type], deviceType: this.type },
+            filters: [
+              { key: 'name', type: 'search', placeholder: '名称' }
+            ],
+            cols: [
+              { prop: 'name', label: '名称' },
+              { prop: 'identifier', label: '唯一标识' }
+            ]
+          }
         default:
-          return { cols: [] }
+          return {
+            list: () => Promise.resolve({ data: [] }),
+            cols: [
+              { label: '唯一标识' }
+            ]
+          }
       }
     }
   },
@@ -655,6 +695,7 @@ export default {
     onClick (eventProxy) {
       console.log('click', eventProxy)
       if (this.$waitTarget) {
+        this.onChangeLinkStatus(false)
         if (eventProxy.dataType === 'node') {
           const targetNode = this.nodes[eventProxy.dataIndex]
           if (eventProxy.data.canClick) {
@@ -671,12 +712,8 @@ export default {
               message: '无法连接至该节点'
             })
           }
-          this.$waitTarget = false
-          this.onDraw()
           return
         }
-        this.$waitTarget = false
-        this.onDraw()
       }
       this.clickTargetType = eventProxy.dataType
       this.$clickTargetIndex = eventProxy.dataIndex
@@ -885,11 +922,13 @@ export default {
         type: 'success',
         message: '源节点已记录,请选择目标节点'
       })
-      this.$waitTarget = true
+      this.onChangeLinkStatus(true)
+    },
+    onChangeLinkStatus (bool) {
+      this.$waitTarget = bool
       this.onDraw()
     },
     addEdge (source, target) {
-      this.$waitTarget = false
       const sourceId = source.id
       const targetId = target.id
       if (this.edges.some(({ preNodeId, nextNodeId }) => sourceId === preNodeId && targetId === nextNodeId)) {

+ 19 - 8
src/views/external/multifunction-card/index.vue

@@ -53,7 +53,7 @@
         />
       </div>
     </confirm-dialog>
-    <device-dialog ref="deviceDialog" />
+    <mesh-dialog ref="meshDialog" />
   </wrapper>
 </template>
 
@@ -65,9 +65,13 @@ import {
   addMultifunctionCard,
   deleteMultifunctionCard
 } from '@/api/external'
+import MeshDialog from '../components/MeshDialog.vue'
 
 export default {
   name: 'MultifunctionCardList',
+  components: {
+    MeshDialog
+  },
   data () {
     const manufacturerSelectSchema = {
       remote: this.getManufacturersByType,
@@ -85,6 +89,10 @@ export default {
         ],
         filters: [
           { key: 'manufacturerKey', type: 'select', placeholder: '厂商', ...manufacturerSelectSchema },
+          { key: 'boundFlag', type: 'select', placeholder: '使用情况', options: [
+            { value: 0, label: '已使用' },
+            { value: 1, label: '未使用' }
+          ] },
           { key: 'identifier', type: 'search', placeholder: '唯一标识' }
         ],
         cols: [
@@ -93,10 +101,13 @@ export default {
           { prop: 'identifier', label: '唯一标识' },
           { prop: 'portCount', label: '端口数', align: 'center' },
           { prop: 'rs485Count', label: '485接口数', align: 'center' },
+          { label: '使用情况', type: 'tag', render: ({ bound }) => bound
+            ? { type: 'success', label: '已使用' }
+            : { type: 'primary', label: '未使用' } },
           { type: 'invoke', render: [
-            { label: '相关设备', on: this.onViewDevice },
-            { label: '删除', on: this.onDel }
-          ] }
+            { label: '所属网点', allow: ({ bound }) => bound, on: this.onViewMesh },
+            { label: '删除', allow: ({ bound }) => !bound, on: this.onDel }
+          ], width: 200 }
         ]
       }
     }
@@ -110,8 +121,8 @@ export default {
         identifier: '',
         manufacturerKey: '',
         model: '',
-        portCount: '',
-        rs485Count: ''
+        portCount: 0,
+        rs485Count: 0
       }
       this.$refs.editDialog.show()
     },
@@ -154,8 +165,8 @@ export default {
         this.$refs.table.decrease(1)
       })
     },
-    onViewDevice ({ id }) {
-      this.$refs.deviceDialog.show(id)
+    onViewMesh ({ id }) {
+      this.$refs.meshDialog.show(id)
     }
   }
 }

+ 17 - 6
src/views/external/plc/index.vue

@@ -47,7 +47,7 @@
         />
       </div>
     </confirm-dialog>
-    <device-dialog ref="deviceDialog" />
+    <mesh-dialog ref="meshDialog" />
   </wrapper>
 </template>
 
@@ -59,9 +59,13 @@ import {
   addPLC,
   deletePLC
 } from '@/api/external'
+import MeshDialog from '../components/MeshDialog.vue'
 
 export default {
   name: 'PLCList',
+  components: {
+    MeshDialog
+  },
   data () {
     const manufacturerSelectSchema = {
       remote: this.getManufacturersByType,
@@ -79,6 +83,10 @@ export default {
         ],
         filters: [
           { key: 'manufacturerKey', type: 'select', placeholder: '厂商', ...manufacturerSelectSchema },
+          { key: 'boundFlag', type: 'select', placeholder: '使用情况', options: [
+            { value: 0, label: '已使用' },
+            { value: 1, label: '未使用' }
+          ] },
           { key: 'identifier', type: 'search', placeholder: '唯一标识' }
         ],
         cols: [
@@ -86,10 +94,13 @@ export default {
           { prop: 'model', label: '型号' },
           { prop: 'identifier', label: '唯一标识' },
           { prop: 'portCount', label: '端口数' },
+          { label: '使用情况', type: 'tag', render: ({ bound }) => bound
+            ? { type: 'success', label: '已使用' }
+            : { type: 'primary', label: '未使用' } },
           { type: 'invoke', render: [
-            { label: '相关设备', on: this.onViewDevice },
-            { label: '删除', on: this.onDel }
-          ] }
+            { label: '所属网点', allow: ({ bound }) => bound, on: this.onViewMesh },
+            { label: '删除', allow: ({ bound }) => !bound, on: this.onDel }
+          ], width: 200 }
         ]
       }
     }
@@ -146,8 +157,8 @@ export default {
         this.$refs.table.decrease(1)
       })
     },
-    onViewDevice ({ id }) {
-      this.$refs.deviceDialog.show(id)
+    onViewMesh ({ id }) {
+      this.$refs.meshDialog.show(id)
     }
   }
 }

+ 17 - 6
src/views/external/screen/index.vue

@@ -80,7 +80,7 @@
         />
       </div>
     </confirm-dialog>
-    <device-dialog ref="deviceDialog" />
+    <mesh-dialog ref="meshDialog" />
   </wrapper>
 </template>
 
@@ -92,9 +92,13 @@ import {
   addScreen,
   deleteScreen
 } from '@/api/external'
+import MeshDialog from '../components/MeshDialog.vue'
 
 export default {
   name: 'ScreenList',
+  components: {
+    MeshDialog
+  },
   data () {
     const manufacturerSelectSchema = {
       remote: this.getManufacturersByType,
@@ -112,6 +116,10 @@ export default {
         ],
         filters: [
           { key: 'manufacturerKey', type: 'select', placeholder: '厂商', ...manufacturerSelectSchema },
+          { key: 'boundFlag', type: 'select', placeholder: '使用情况', options: [
+            { value: 0, label: '已使用' },
+            { value: 1, label: '未使用' }
+          ] },
           { key: 'identifier', type: 'search', placeholder: '唯一标识' }
         ],
         cols: [
@@ -121,10 +129,13 @@ export default {
           { prop: 'measure', label: '屏宽高(m²)' },
           { prop: 'pitch', label: '间距(mm)' },
           { prop: 'resolutionRatio', label: '分辨率' },
+          { label: '使用情况', type: 'tag', render: ({ bound }) => bound
+            ? { type: 'success', label: '已使用' }
+            : { type: 'primary', label: '未使用' } },
           { type: 'invoke', render: [
-            { label: '相关设备', on: this.onViewDevice },
-            { label: '删除', on: this.onDel }
-          ] }
+            { label: '所属网点', allow: ({ bound }) => bound, on: this.onViewMesh },
+            { label: '删除', allow: ({ bound }) => !bound, on: this.onDel }
+          ], width: 200 }
         ]
       }
     }
@@ -226,8 +237,8 @@ export default {
         this.$refs.table.decrease(1)
       })
     },
-    onViewDevice ({ id }) {
-      this.$refs.deviceDialog.show(id)
+    onViewMesh ({ id }) {
+      this.$refs.meshDialog.show(id)
     }
   }
 }

+ 19 - 8
src/views/external/sending-card/index.vue

@@ -46,7 +46,7 @@
         />
       </div>
     </confirm-dialog>
-    <device-dialog ref="deviceDialog" />
+    <mesh-dialog ref="meshDialog" />
   </wrapper>
 </template>
 
@@ -61,9 +61,13 @@ import {
   addSendingCard,
   deleteSendingCard
 } from '@/api/external'
+import MeshDialog from '../components/MeshDialog.vue'
 
 export default {
   name: 'SendingCardList',
+  components: {
+    MeshDialog
+  },
   data () {
     const manufacturerSelectSchema = {
       remote: this.getManufacturersByType,
@@ -82,6 +86,10 @@ export default {
         ],
         filters: [
           { key: 'manufacturerKey', type: 'select', placeholder: '厂商', ...manufacturerSelectSchema },
+          { key: 'boundFlag', type: 'select', placeholder: '使用情况', options: [
+            { value: 0, label: '已使用' },
+            { value: 1, label: '未使用' }
+          ] },
           { key: 'identifier', type: 'search', placeholder: '唯一标识' }
         ],
         cols: [
@@ -95,11 +103,14 @@ export default {
               { type: flag & Transmitter.SUPPORT_CONTENT_PROTECTION ? 'success' : 'danger', label: '内容保护' },
               { type: flag & Transmitter.RECOVERY_CARD ? 'success' : 'danger', label: '回采卡' }
             ]
-          }, width: 364, align: 'left' },
+          }, width: 364 },
+          { label: '使用情况', type: 'tag', render: ({ bound }) => bound
+            ? { type: 'success', label: '已使用' }
+            : { type: 'primary', label: '未使用' } },
           { type: 'invoke', render: [
-            { label: '相关设备', on: this.onViewDevice },
-            { label: '删除', on: this.onDel }
-          ] }
+            { label: '所属网点', allow: ({ bound }) => bound, on: this.onViewMesh },
+            { label: '删除', allow: ({ bound }) => !bound, on: this.onDel }
+          ], width: 200 }
         ]
       }
     }
@@ -109,7 +120,7 @@ export default {
       return getManufacturersByType(ThirdPartyDevice.SENDING_CARD)
     },
     onAdd () {
-      this.transmitter = {
+      this.item = {
         identifier: '',
         manufacturerKey: '',
         model: ''
@@ -176,8 +187,8 @@ export default {
         this.$refs.table.decrease(1)
       })
     },
-    onViewDevice ({ id }) {
-      this.$refs.deviceDialog.show(id)
+    onViewMesh ({ id }) {
+      this.$refs.meshDialog.show(id)
     }
   }
 }

+ 24 - 7
src/views/external/sensor/index.vue

@@ -40,7 +40,7 @@
         />
       </div>
     </confirm-dialog>
-    <device-dialog ref="deviceDialog" />
+    <mesh-dialog ref="meshDialog" />
   </wrapper>
 </template>
 
@@ -52,9 +52,13 @@ import {
   addSensor,
   deleteSensor
 } from '@/api/external'
+import MeshDialog from '../components/MeshDialog.vue'
 
 export default {
   name: 'SensorList',
+  components: {
+    MeshDialog
+  },
   props: {
     type: {
       type: Number,
@@ -72,27 +76,40 @@ export default {
       item: {},
       manufacturerSelectSchema,
       schema: {
-        list: getSensors,
+        list: this.getSensors,
         buttons: [
           { type: 'add', on: this.onAdd }
         ],
         filters: [
           { key: 'manufacturerKey', type: 'select', placeholder: '厂商', ...manufacturerSelectSchema },
+          { key: 'boundFlag', type: 'select', placeholder: '使用情况', options: [
+            { value: 0, label: '已使用' },
+            { value: 1, label: '未使用' }
+          ] },
           { key: 'identifier', type: 'search', placeholder: '唯一标识' }
         ],
         cols: [
           { prop: 'manufacturerName', label: '厂商' },
           { prop: 'model', label: '型号' },
           { prop: 'identifier', label: '唯一标识' },
+          { label: '使用情况', type: 'tag', render: ({ bound }) => bound
+            ? { type: 'success', label: '已使用' }
+            : { type: 'primary', label: '未使用' } },
           { type: 'invoke', render: [
-            { label: '相关设备', on: this.onViewDevice },
-            { label: '删除', on: this.onDel }
-          ] }
+            { label: '所属网点', allow: ({ bound }) => bound, on: this.onViewMesh },
+            { label: '删除', allow: ({ bound }) => !bound, on: this.onDel }
+          ], width: 200 }
         ]
       }
     }
   },
   methods: {
+    getSensors (params) {
+      return getSensors({
+        sensorType: this.type,
+        ...params
+      })
+    },
     getManufacturersByType () {
       return getManufacturersByType(SensorToThirdPartyMap[this.type])
     },
@@ -147,8 +164,8 @@ export default {
         this.$refs.table.decrease(1)
       })
     },
-    onViewDevice ({ id }) {
-      this.$refs.deviceDialog.show(id)
+    onViewMesh ({ id }) {
+      this.$refs.meshDialog.show(id)
     }
   }
 }

+ 0 - 57
src/views/platform/tenant/device/index.vue

@@ -1,57 +0,0 @@
-<template>
-  <wrapper
-    fill
-    margin
-    padding
-    background
-  >
-    <platform-page class="l-flex__fill">
-      <template #default="{ tenant }">
-        <div class="l-flex__auto l-flex--col">
-          <el-tabs
-            v-model="active"
-            class="c-tabs has-bottom-padding"
-          >
-            <el-tab-pane
-              label="设备"
-              name="Device"
-            />
-            <el-tab-pane
-              label="屏幕配置"
-              name="Product"
-            />
-            <el-tab-pane
-              label="屏幕类型"
-              name="ProductType"
-            />
-          </el-tabs>
-          <component
-            :is="active"
-            :key="`${tenant.id}_${active}`"
-            :tenant="tenant.path"
-          />
-        </div>
-      </template>
-    </platform-page>
-  </wrapper>
-</template>
-
-<script>
-import ProductType from './components/ProductType'
-import Product from './components/Product'
-import Device from './components/Device'
-
-export default {
-  name: 'TenantDeviceManagement',
-  components: {
-    ProductType,
-    Product,
-    Device
-  },
-  data () {
-    return {
-      active: 'Device'
-    }
-  }
-}
-</script>

+ 0 - 177
src/views/platform/tenant/device/settings/components/external/Camera/index.vue

@@ -1,177 +0,0 @@
-<template>
-  <div class="l-flex--col">
-    <el-tabs
-      :value="active"
-      class="c-tabs has-bottom-padding"
-      @tab-click="onTabClick"
-    >
-      <el-tab-pane name="LED_CAMERA">
-        <template #label>
-          <div class="o-tab">
-            <i
-              class="o-tab__icon el-icon-circle-plus-outline has-active"
-              @click.stop="onAdd('LED_CAMERA')"
-            />
-            LED屏监测
-          </div>
-        </template>
-      </el-tab-pane>
-      <el-tab-pane name="TRAFFIC_CAMERA">
-        <template #label>
-          <div class="o-tab">
-            <i
-              class="o-tab__icon el-icon-circle-plus-outline has-active"
-              @click.stop="onAdd('TRAFFIC_CAMERA')"
-            />
-            人流量监测
-          </div>
-        </template>
-      </el-tab-pane>
-    </el-tabs>
-    <grid-table
-      ref="table"
-      :schema="schema"
-      size="large"
-    >
-      <grid-table-item v-slot="item">
-        <camera-player
-          v-if="isActivated"
-          :key="item.identifier"
-          :camera="item"
-          controls
-          @fullscreen="onFullScreen(item)"
-          @click.native="onFullScreen(item)"
-        >
-          <i
-            class="c-sibling-item el-icon-delete has-active"
-            @click.stop="onDel(item)"
-          />
-        </camera-player>
-      </grid-table-item>
-    </grid-table>
-    <radio-table-dialog
-      ref="tableDialog"
-      :title="title"
-      message="请选择需绑定的摄像头"
-      :schema="cameraSchema"
-      @confirm="onCameraChoosen"
-    />
-    <camera-dialog
-      ref="cameraDialog"
-      @open="onOpen"
-      @closed="onClosed"
-    />
-  </div>
-</template>
-
-<script>
-import {
-  getCamerasByDevice,
-  getThirdPartyDevices,
-  bind,
-  unbind
-} from '@/api/external'
-import {
-  ThirdPartyDevice,
-  Camera,
-  ThirdPartyToCameraMap
-} from '@/constant'
-
-export default {
-  name: 'DeviceCamera',
-  props: {
-    device: {
-      type: Object,
-      required: true
-    }
-  },
-  data () {
-    return {
-      active: 'LED_CAMERA',
-      deviceType: ThirdPartyDevice.LED_CAMERA,
-      schema: { list: this.getCamerasByDevice },
-      isActivated: true
-    }
-  },
-  computed: {
-    isTraffic () {
-      return this.active === 'TRAFFIC_CAMERA'
-    },
-    cameraType () {
-      return this.isTraffic ? Camera.TRAFFIC : Camera.LED
-    },
-    title () {
-      return this.deviceType === ThirdPartyDevice.TRAFFIC_CAMERA ? '绑定人流量监测摄像头' : '绑定LED屏监测摄像头'
-    },
-    cameraSchema () {
-      return {
-        list: this.getThirdPartyDevices,
-        condition: { deviceType: this.deviceType, boundFlag: 0 },
-        cols: [
-          { prop: 'name', label: '名称' },
-          { prop: 'remark', label: '备注' }
-        ]
-      }
-    }
-  },
-  methods: {
-    onTabClick ({ name: active }) {
-      if (this.active !== active) {
-        this.active = active
-        this.$refs.table.pageTo(1)
-      }
-    },
-    getThirdPartyDevices (params) {
-      return getThirdPartyDevices({
-        cameraType: ThirdPartyToCameraMap[params.deviceType],
-        ...params
-      })
-    },
-    getCamerasByDevice () {
-      return getCamerasByDevice(this.device.id, { cameraType: this.cameraType }).then(({ data }) => {
-        data = data.map(({ id, thirdPartyDevice }) => {
-          return {
-            ...thirdPartyDevice,
-            id
-          }
-        })
-        return { data }
-      })
-    },
-    onFullScreen (camera) {
-      this.$refs.cameraDialog.show(camera)
-    },
-    onOpen () {
-      this.isActivated = false
-    },
-    onClosed () {
-      this.isActivated = true
-    },
-    onAdd (deviceType) {
-      this.deviceType = ThirdPartyDevice[deviceType]
-      this.$refs.tableDialog.show()
-    },
-    onCameraChoosen ({ value, done }) {
-      bind(this.device.id, this.deviceType, value.id).then(() => {
-        done()
-        if (this.deviceType === ThirdPartyDevice[this.active]) {
-          this.$refs.table.pageTo(1)
-        } else {
-          this.onTabClick({ name: this.isTraffic ? 'LED_CAMERA' : 'TRAFFIC_CAMERA' })
-        }
-      })
-    },
-    onDel (item) {
-      this.$confirm(
-        `解绑摄像头 ${item.name} ?`,
-        '操作确认',
-        { type: 'warning' }
-      ).then(() => {
-        unbind(item.id).then(() => {
-          this.$refs.table.decrease(1)
-        })
-      })
-    }
-  }
-}
-</script>

+ 0 - 264
src/views/platform/tenant/device/settings/components/external/Gateway/index.vue

@@ -1,264 +0,0 @@
-<template>
-  <div
-    v-loading="loading"
-    class="l-flex--col"
-  >
-    <template v-if="!loading">
-      <warning
-        v-if="error"
-        @click="getGateway"
-      />
-      <template v-else>
-        <template v-if="gateway">
-          <div class="c-sibling-item--v c-info">
-            <div class="l-flex--row c-sibling-item--v u-bold">
-              <span class="c-sibling-item">网关信息</span>
-              <span
-                class="c-sibling-item u-color--blue u-font-size--sm has-active"
-                @click="onUnbindGateway"
-              >
-                <i class="el-icon-edit" />
-                解绑
-              </span>
-            </div>
-            <div class="l-flex--row c-sibling-item--v  c-info__block">
-              <div class="l-flex--row l-flex__fill c-sibling-item">
-                <div class="l-flex__none c-info__title">名称</div>
-                <auto-text
-                  class="l-flex__fill c-info__value"
-                  :text="gateway.name"
-                />
-              </div>
-              <div class="l-flex--row l-flex__fill c-sibling-item">
-                <div class="l-flex__none c-info__title">地址</div>
-                <div class="l-flex__fill c-info__value">{{ gateway.ip }}</div>
-              </div>
-            </div>
-          </div>
-          <div class="c-sibling-item--v far c-info">
-            <div class="l-flex--row c-sibling-item--v u-bold">
-              <span class="c-sibling-item">PLC信息</span>
-              <i
-                v-if="plcOptions.loading"
-                class="c-sibling-item el-icon-loading"
-              />
-            </div>
-            <warning
-              v-if="plcOptions.error"
-              @click="getPLCs"
-            />
-            <div
-              v-else-if="!plcOptions.loading && plcOptions.list.length === 0"
-              class="c-sibling-item--v u-font-size--sm u-color--info"
-            >
-              暂未绑定
-            </div>
-            <div
-              v-for="plc in plcOptions.list"
-              :key="plc.id"
-              class="l-flex--row c-sibling-item--v c-info__block"
-            >
-              <div class="l-flex--row l-flex__fill c-sibling-item">
-                <div class="l-flex__none c-info__title">名称</div>
-                <auto-text
-                  class="l-flex__fill c-info__value"
-                  :text="plc.name"
-                />
-              </div>
-              <div class="l-flex--row l-flex__fill c-sibling-item">
-                <div class="l-flex__none c-info__title">地址</div>
-                <div class="l-flex__fill c-info__value">{{ plc.identifier }}</div>
-              </div>
-            </div>
-          </div>
-          <div class="c-sibling-item--v far c-info">
-            <div class="l-flex--row c-sibling-item--v u-bold">
-              <span class="c-sibling-item">传感器信息</span>
-              <i
-                v-if="sensorOptions.loading"
-                class="c-sibling-item el-icon-loading"
-              />
-            </div>
-            <warning
-              v-if="sensorOptions.error"
-              @click="getSensors"
-            />
-            <div
-              v-else-if="!sensorOptions.loading && sensorOptions.list.length === 0"
-              class="c-sibling-item--v u-font-size--sm u-color--info"
-            >
-              暂未绑定
-            </div>
-            <div
-              v-for="sensor in sensorOptions.list"
-              :key="sensor.id"
-              class="l-flex--row c-sibling-item--v c-info__block"
-            >
-              <div class="l-flex--row l-flex__fill c-sibling-item">
-                <div class="l-flex__none c-info__title">类型</div>
-                <div class="l-flex__fill c-info__value">{{ sensor.type }}</div>
-              </div>
-              <div class="l-flex--row l-flex__fill c-sibling-item">
-                <div class="l-flex__none c-info__title">标识</div>
-                <div class="l-flex__fill c-info__value">{{ sensor.identifier }}</div>
-              </div>
-            </div>
-          </div>
-        </template>
-        <div
-          v-else
-          class="l-flex__fill l-flex--row center"
-        >
-          <div class="l-flex--col center">
-            <button
-              class="o-button"
-              @click="onBindGateway"
-            >
-              绑定网关
-            </button>
-          </div>
-        </div>
-      </template>
-    </template>
-    <radio-table-dialog
-      ref="chooseDialog"
-      title="网关选择"
-      message="请选择需要绑定的网关"
-      :schema="schema"
-      @confirm="onChoosen"
-    />
-  </div>
-</template>
-
-<script>
-import { ThirdPartyDevice } from '@/constant'
-import {
-  getGateway,
-  bindGateway,
-  unbind,
-  getPLCsByGateway,
-  getSensorsByGateway,
-  getThirdPartyDevices
-} from '@/api/external'
-
-export default {
-  name: 'Gateway',
-  props: {
-    device: {
-      type: Object,
-      required: true
-    }
-  },
-  data () {
-    return {
-      loading: false,
-      error: false,
-      gatewayProxy: null,
-      schema: {
-        list: getThirdPartyDevices,
-        condition: { deviceType: ThirdPartyDevice.GATEWAY },
-        filters: [
-          { key: 'name', type: 'search', placeholder: '网关名称' }
-        ],
-        cols: [
-          { prop: 'name', label: '名称' },
-          { prop: 'ip', label: '地址' },
-          { prop: 'remark', label: '备注' }
-        ]
-      },
-      plcOptions: {
-        list: [],
-        loading: false,
-        error: false
-      },
-      sensorOptions: {
-        list: [],
-        loading: false,
-        error: false
-      }
-    }
-  },
-  computed: {
-    gateway () {
-      return this.gatewayProxy?.thirdPartyDevice
-    }
-  },
-  created () {
-    this.getGateway()
-  },
-  methods: {
-    getGateway () {
-      if (this.loading) {
-        return
-      }
-      this.loading = true
-      this.error = false
-      getGateway(this.device.id).then(
-        gateway => {
-          this.gatewayProxy = gateway
-          if (gateway) {
-            this.getPLCs()
-            this.getSensors()
-          }
-        },
-        () => {
-          this.error = true
-        }
-      ).finally(() => {
-        this.loading = false
-      })
-    },
-    onBindGateway () {
-      this.$refs.chooseDialog.show()
-    },
-    onChoosen ({ value, done }) {
-      bindGateway(this.device.id, value.id).then(() => {
-        done()
-        this.getGateway()
-      })
-    },
-    onUnbindGateway () {
-      this.$confirm(
-        `解绑网关 ${this.gateway.name}?`,
-        '操作确认',
-        { type: 'warning' }
-      ).then(() => {
-        unbind(this.gatewayProxy.id).then(() => {
-          this.gatewayProxy = null
-        })
-      })
-    },
-    getPLCs () {
-      this.plcOptions.loading = true
-      this.plcOptions.error = false
-      getPLCsByGateway(this.gateway.id).then(
-        ({ data }) => {
-          this.plcOptions.list = data
-        },
-        () => {
-          this.plcOptions.error = true
-        }
-      ).finally(() => {
-        this.plcOptions.loading = false
-      })
-    },
-    getSensors () {
-      this.sensorOptions.loading = true
-      this.sensorOptions.error = false
-      getSensorsByGateway(this.gateway.id).then(
-        ({ data }) => {
-          this.sensorOptions.list = data.map(sensor => {
-            sensor.type = ['烟雾', '温度', '光照', '水浸'][sensor.sensorType]
-            return sensor
-          })
-        },
-        () => {
-          this.sensorOptions.error = true
-        }
-      ).finally(() => {
-        this.sensorOptions.loading = false
-      })
-    }
-  }
-}
-</script>

+ 1 - 1
src/views/realm/device/index.vue

@@ -137,7 +137,7 @@ export default {
             ? null
             : activate
               ? onlineStatus === 0
-                ? { type: 'primary', label: '已启用' }
+                ? { type: 'primary', label: '待接入' }
                 : onlineStatus === 1
                   ? { type: 'success', label: '在线' }
                   : { type: 'danger', label: '离线' }

+ 1 - 4
src/views/screen/review/workflow/audit/components/ReviewPublish.vue

@@ -6,10 +6,7 @@
       :schema="schema"
     />
     <div class="l-flex__fill l-flex c-sibling-item--v">
-      <div
-        class="l-flex__none l-flex--col c-sibling-item"
-        style="width: 300px;"
-      >
+      <div class="l-flex__none l-flex--col c-sibling-item u-width--lg">
         <div class="c-sibling-item--v u-font-size--sm u-color--black u-bold">目标设备</div>
         <schema-table
           class="c-sibling-item--v near"