Przeglądaj źródła

feat: support device control (reboot)

daigang 3 lat temu
rodzic
commit
7b96e44e11

+ 8 - 106
src/views/dashboard/components/Device.vue

@@ -1,8 +1,7 @@
 <template>
   <div
-    class="o-device has-padding"
-    :class="{ 'u-pointer': isOnline }"
-    @click="askStatus"
+    class="o-device has-padding u-pointer"
+    @click="onClick"
   >
     <div class="l-flex__none l-flex--row o-device__block o-device__header">
       <i
@@ -79,39 +78,6 @@
       class="l-flex__none o-device__block o-device__footer"
       :text="address"
     />
-    <el-dialog
-      :title="name"
-      :visible.sync="show"
-      custom-class="c-dialog"
-      :before-close="handleClose"
-      append-to-body
-    >
-      <div class="u-text-center">
-        <i
-          v-if="loading"
-          class="el-icon-loading"
-        />
-        <template v-else>
-          <div class="has-bottom-padding">
-            <button
-              class="c-sibling-item o-button"
-              @click="ask"
-            >
-              检测状态
-            </button>
-            <button
-              class="c-sibling-item o-button"
-              @click="restart"
-            >
-              重启
-            </button>
-          </div>
-          <template v-if="message">
-            <div v-if="message.type === 's'">{{ message.value }}</div>
-          </template>
-        </template>
-      </div>
-    </el-dialog>
   </div>
 </template>
 
@@ -135,9 +101,6 @@ export default {
   },
   data () {
     return {
-      show: false,
-      loading: false,
-      message: null,
       isShotting: false,
       shot: null,
       timeline: [],
@@ -196,32 +159,21 @@ export default {
   beforeDestroy () {
     if (this.isActivated) {
       unlisten(this.onMessage)
-      if (this.show) {
-        this.handleClose()
-      }
     }
     clearTimeout(this.$timer)
   },
   methods: {
-    askStatus () {
-      if (!this.isOnline) {
-        return
-      }
-      this.show = true
-    },
-    handleClose () {
-      this.loading = false
-      this.message = null
-      this.show = false
+    onClick () {
+      this.$router.push({
+        name: 'device-detail',
+        params: { id: this.device.id }
+      })
     },
     onMessage (topic, message) {
       if (message) {
         const result = new RegExp(`${this.device.productId}/${this.device.id}/(.+)`).exec(topic)
         if (result) {
           switch (result[1]) {
-            case 'status/reply':
-              this.onAskReply(message)
-              break
             case 'screenshot/reply':
               this.onScreenshotReply(message)
               break
@@ -234,44 +186,8 @@ export default {
         }
       }
     },
-    publish (invoke) {
-      return publish(`${this.device.productId}/${this.device.id}/${invoke}/ask`, JSON.stringify({ timestamp: Date.now() }))
-    },
-    ask () {
-      this.publish('status').then(() => {
-        this.loading = true
-        this.message = null
-      }, () => {
-        this.$message({
-          type: 'warning',
-          message: '正在连接,请稍后再试'
-        })
-      })
-    },
-    onAskReply (message) {
-      this.loading = false
-      try {
-        message = JSON.parse(message)
-        switch (message.status) {
-          case 1:
-            this.message = { type: 's', value: '未播放节目,处于默认状态' }
-            break
-          case 2:
-            this.message = { type: 's', value: '正在播放节目' }
-            break
-          case 3:
-            this.message = { type: 's', value: '解析节目异常,请重新发布' }
-            break
-          default:
-            this.message = { type: 's', value: '未知' }
-            break
-        }
-      } catch {
-        this.message = { type: 's', value: '解析异常,请重试' }
-      }
-    },
     screenshot () {
-      this.publish('screenshot').then(() => {
+      publish(`${this.device.productId}/${this.device.id}/screenshot/ask`, JSON.stringify({ timestamp: Date.now() }))('screenshot').then(() => {
         this.isShotting = true
         this.shot = null
       }, () => {
@@ -285,20 +201,6 @@ export default {
       this.isShotting = false
       this.shot = `data:image/jpeg;base64,${message.replace(/\s/g, '')}`
     },
-    restart () {
-      this.publish('restart').then(() => {
-        this.loading = true
-        this.message = null
-      }, () => {
-        this.$message({
-          type: 'warning',
-          message: '正在连接,请稍后再试'
-        })
-      })
-    },
-    onRestartReply () {
-      this.loading = false
-    },
     onCalendarUpdate (message) {
       clearTimeout(this.$timer)
       try {

+ 0 - 2
src/views/dashboard/index.vue

@@ -160,7 +160,6 @@ export default {
       '+/+/online',
       '+/+/offline',
       '+/+/calendar/update',
-      '+/+/status/reply',
       '+/+/screenshot/reply'
     ], this.onMessage)
   },
@@ -169,7 +168,6 @@ export default {
       '+/+/online',
       '+/+/offline',
       '+/+/calendar/update',
-      '+/+/status/reply',
       '+/+/screenshot/reply'
     ], this.onMessage)
   },

+ 0 - 32
src/views/device/detail/components/DeviceAlarm.vue

@@ -174,36 +174,4 @@ export default {
     border-radius: 2px;
   }
 }
-
-.o-status {
-  color: #d5d9e4;
-  font-size: 14px;
-
-  .large {
-    font-size: 32px;
-  }
-
-  &__icon {
-    display: inline-block;
-    width: 32px;
-    height: 32px;
-    margin-right: 8px;
-    background-origin: 0 0;
-    background-size: 100% 100%;
-    background-repeat: no-repeat;
-
-    &.download {
-      background-image: url("~@/assets/icon_download.png");
-    }
-  }
-
-  &__title {
-    color: #8e929c;
-  }
-
-  &__list {
-    color: $blue;
-    font-size: 18px;
-  }
-}
 </style>

+ 82 - 0
src/views/device/detail/components/DeviceInvoke.vue

@@ -0,0 +1,82 @@
+<template>
+  <div class="c-info-grid">
+    <div
+      v-loading="rebooting"
+      class="c-info-grid__item l-flex--col center has-padding"
+    >
+      <i
+        class="o-invoke-icon reboot u-pointer"
+        @click="reboot"
+      />
+      <div class="has-padding u-color--black u-bold">重启设备</div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { send } from '../monitor'
+
+export default {
+  name: 'DeviceInvoke',
+  props: {
+    device: {
+      type: Object,
+      default: null
+    },
+    online: {
+      type: [Boolean, String],
+      default: false
+    }
+  },
+  data () {
+    return {
+      rebooting: false
+    }
+  },
+  watch: {
+    online () {
+      this.rebooting = false
+    }
+  },
+  methods: {
+    reboot () {
+      if (this.rebooting) {
+        return
+      }
+      if (!this.online) {
+        this.$message({
+          type: 'warning',
+          message: '设备未上线,请稍后再试'
+        })
+        return
+      }
+      send('/restart/ask').then(
+        () => {
+          this.rebooting = true
+        },
+        () => {
+          this.$message({
+            type: 'warning',
+            message: '正在连接,请稍后再试'
+          })
+        }
+      )
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.o-invoke-icon {
+  display: inline-block;
+  width: 64px;
+  height: 64px;
+  background-position: 0 0;
+  background-size: 100% 100%;
+  background-repeat: no-repeat;
+
+  &.reboot {
+    background-image: url("~@/assets/icon_reboot.png");
+  }
+}
+</style>

+ 19 - 0
src/views/device/detail/components/DeviceRuntime.vue

@@ -0,0 +1,19 @@
+<template>
+  <div class="c-info-grid">
+    <screen-shot class="c-info-grid__item" />
+    <download class="c-info-grid__item" />
+  </div>
+</template>
+
+<script>
+import ScreenShot from './ScreenShot'
+import Download from './Download'
+
+export default {
+  name: 'DeviceRuntime',
+  components: {
+    ScreenShot,
+    Download
+  }
+}
+</script>

+ 38 - 0
src/views/device/detail/components/DeviceSensor.vue

@@ -0,0 +1,38 @@
+<template>
+  <div class="c-info-grid less">
+    <sensor
+      class="c-info-grid__item"
+      type="temperature"
+      title="温度"
+      color="#ff0000"
+    />
+    <sensor
+      class="c-info-grid__item"
+      type="smoke"
+      title="烟雾"
+      color="#8400ff"
+    />
+    <sensor
+      class="c-info-grid__item"
+      type="flooding"
+      title="水浸"
+    />
+    <sensor
+      class="c-info-grid__item"
+      type="light"
+      title="光照"
+      color="#ffa200"
+    />
+  </div>
+</template>
+
+<script>
+import Sensor from './Sensor'
+
+export default {
+  name: 'DeviceSensor',
+  components: {
+    Sensor
+  }
+}
+</script>

+ 24 - 62
src/views/device/detail/components/DeviceStatus.vue

@@ -12,62 +12,40 @@
       </div>
     </div>
     <div class="l-flex__fill u-overflow-y--auto">
-      <div
-        class="c-status-grid"
-        :class="{ less: active === 'sensor' }"
-      >
-        <template v-if="active === 'box'">
-          <screen-shot class="c-status-grid__item" />
-          <download class="c-status-grid__item" />
-        </template>
-        <template v-if="active === 'sensor'">
-          <sensor
-            class="c-status-grid__item"
-            type="temperature"
-            title="温度"
-            color="#ff0000"
-          />
-          <sensor
-            class="c-status-grid__item"
-            type="smoke"
-            title="烟雾"
-            color="#8400ff"
-          />
-          <sensor
-            class="c-status-grid__item"
-            type="flooding"
-            title="水浸"
-          />
-          <sensor
-            class="c-status-grid__item"
-            type="light"
-            title="光照"
-            color="#ffa200"
-          />
-        </template>
-      </div>
+      <component :is="activeComponent" />
     </div>
   </div>
 </template>
 
 <script>
-import ScreenShot from './ScreenShot'
-import Download from './Download'
-import Sensor from './Sensor'
+import DeviceRuntime from './DeviceRuntime'
+import DeviceSensor from './DeviceSensor'
 
 export default {
   name: 'DeviceStatus',
   components: {
-    ScreenShot,
-    Download,
-    Sensor
+    DeviceRuntime,
+    DeviceSensor
   },
   data () {
     return {
-      active: 'box',
+      active: 'runtime',
       tabs: [
-        { key: 'box', name: '播控器状态' }
-      ].concat(__SENSOR__ ? [{ key: 'sensor', name: '传感器状态' }] : [])
+        { key: 'runtime', name: '播控器状态' },
+        __SENSOR__ ? { key: 'sensor', name: '传感器状态' } : null
+      ].filter(val => val)
+    }
+  },
+  computed: {
+    activeComponent () {
+      switch (this.active) {
+        case 'runtime':
+          return 'DeviceRuntime'
+        case 'sensor':
+          return 'DeviceSensor'
+        default:
+          return null
+      }
     }
   }
 }
@@ -96,30 +74,14 @@ export default {
     }
   }
 }
-
-.c-status-grid {
-  display: grid;
-  grid-template-columns: repeat(auto-fill, minmax(248px, 1fr));
-  grid-row-gap: $spacing;
-  grid-column-gap: $spacing;
-  align-items: start;
-
-  &.less {
-    grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
-  }
-
-  &__item {
-    height: 180px;
-    border: 1px solid #d5d9e4;
-    border-radius: 2px;
-  }
-}
 </style>
 
 <style lang="scss">
-.o-status {
+.c-runtime {
+  padding: 12px 16px 16px;
   color: #d5d9e4;
   font-size: 14px;
+  line-height: 1;
 
   .large {
     font-size: 32px;

+ 4 - 4
src/views/device/detail/components/Download.vue

@@ -1,11 +1,11 @@
 <template>
-  <div class="l-flex--col o-status has-padding">
+  <div class="l-flex--col c-runtime">
     <div class="l-flex--row l-flex__none has-bottom-padding">
-      <i class="l-flex__none o-status__icon download" />
-      <span class="l-flex__fill o-status__title u-ellipsis">文件下载</span>
+      <i class="l-flex__none c-runtime__icon download" />
+      <span class="l-flex__fill c-runtime__title u-ellipsis">文件下载</span>
       <i
         v-if="count"
-        class="l-flex__none o-status__list el-icon-s-operation u-pointer"
+        class="l-flex__none c-runtime__list el-icon-s-operation u-pointer"
         @click="showDownload"
       />
     </div>

+ 3 - 3
src/views/device/detail/components/ScreenShot.vue

@@ -1,8 +1,8 @@
 <template>
-  <div class="l-flex--col o-status has-padding">
+  <div class="l-flex--col c-runtime">
     <div class="l-flex--row l-flex__none has-bottom-padding">
-      <i class="l-flex__none o-status__icon screenshot" />
-      <span class="l-flex__fill o-status__title u-ellipsis">截屏</span>
+      <i class="l-flex__none c-runtime__icon screenshot" />
+      <span class="l-flex__fill c-runtime__title u-ellipsis">截屏</span>
       <i
         v-if="isShotting"
         class="l-flex__none el-icon-loading"

+ 4 - 7
src/views/device/detail/components/Sensor.vue

@@ -1,14 +1,14 @@
 <template>
-  <div class="l-flex--col o-status o-senser">
+  <div class="l-flex--col c-runtime o-senser">
     <div class="l-flex--row l-flex__none">
       <i
-        class="l-flex__none o-status__icon"
+        class="l-flex__none c-runtime__icon"
         :class="type"
       />
-      <span class="l-flex__fill o-status__title u-ellipsis">{{ title }}</span>
+      <span class="l-flex__fill c-runtime__title u-ellipsis">{{ title }}</span>
       <i
         v-if="enough"
-        class="l-flex__none o-status__list el-icon-s-operation u-pointer"
+        class="l-flex__none c-runtime__list el-icon-s-operation u-pointer"
         @click="showList"
       />
     </div>
@@ -155,9 +155,6 @@ export default {
 
 <style lang="scss" scoped>
 .o-senser {
-  padding: 12px 16px 16px;
-  line-height: 1;
-
   &__current {
     margin-top: 6px;
     color: #d5d9e4;

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

@@ -23,16 +23,10 @@
           class="c-tabs"
         >
           <el-tab-pane
-            label="设备信息"
-            name="info"
-          />
-          <el-tab-pane
-            label="运行状态"
-            name="status"
-          />
-          <el-tab-pane
-            label="设备告警"
-            name="alarm"
+            v-for="tab in tabs"
+            :key="tab.key"
+            :label="tab.label"
+            :name="tab.key"
           />
         </el-tabs>
       </template>
@@ -60,6 +54,7 @@
           :is="activeComponent"
           class="l-flex__fill c-detail__wrapper has-padding u-overflow-y--auto"
           :device="device"
+          :online="isOnline"
         />
       </keep-alive>
     </div>
@@ -76,13 +71,15 @@ import {
 import DeviceInfo from './components/DeviceInfo'
 import DeviceStatus from './components/DeviceStatus'
 import DeviceAlarm from './components/DeviceAlarm'
+import DeviceInvoke from './components/DeviceInvoke'
 
 export default {
   name: 'DeviceDetail',
   components: {
     DeviceInfo,
     DeviceStatus,
-    DeviceAlarm
+    DeviceAlarm,
+    DeviceInvoke
   },
   data () {
     return {
@@ -90,7 +87,13 @@ export default {
       device: null,
       isActivated: false,
       isOnline: false,
-      active: 'info'
+      active: 'info',
+      tabs: [
+        { key: 'info', label: '设备信息' },
+        { key: 'status', label: '运行状态' },
+        { key: 'alarm', label: '设备告警' },
+        { key: 'invoke', label: '设备操控' }
+      ]
     }
   },
   computed: {
@@ -126,6 +129,8 @@ export default {
           return 'DeviceStatus'
         case 'alarm':
           return 'DeviceAlarm'
+        case 'invoke':
+          return 'DeviceInvoke'
         default:
           return ''
       }
@@ -133,7 +138,6 @@ export default {
   },
   watch: {
     deviceId () {
-      console.log(this.deviceId)
       stop()
       this.active = 'info'
       this.device = null
@@ -154,7 +158,7 @@ export default {
         if (this.deviceId === id) {
           this.device = data
           this.isActivated = data.activate === 2
-          this.isOnline = data.onlineStatus === 1
+          this.isOnline = this.isActivated && data.onlineStatus === 1
           start(this.device)
           addListener('online', this.onUpdate)
         }
@@ -212,3 +216,23 @@ export default {
   }
 }
 </style>
+
+<style lang="scss">
+.c-info-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(248px, 1fr));
+  grid-row-gap: $spacing;
+  grid-column-gap: $spacing;
+  align-items: start;
+
+  &.less {
+    grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
+  }
+
+  &__item {
+    height: 180px;
+    border: 1px solid #d5d9e4;
+    border-radius: 2px;
+  }
+}
+</style>

+ 4 - 0
src/views/device/detail/monitor.js

@@ -76,6 +76,10 @@ export function screenshot () {
   return publish(`${productId}/${deviceId}/screenshot/ask`, JSON.stringify({ timestamp: Date.now() }))
 }
 
+export function send (topic, message) {
+  return publish(`${productId}/${deviceId}${topic}`, JSON.stringify(message || { timestamp: Date.now() }))
+}
+
 function invoke (type, cb) {
   switch (type) {
     case 'download':