Ver Fonte

refactor: device information

Casper Dai há 2 anos atrás
pai
commit
eabba31744

+ 20 - 18
src/components/dialog/RadioTableDialog/index.vue

@@ -5,25 +5,27 @@
     v-bind="$attrs"
     @confirm="onConfirm"
   >
-    <schema-table
-      ref="table"
-      :schema="tableSchema"
-      :row-key="rowKey"
-      :current-row-key="selectedKey"
-      highlight-current-row
-      @row-click="onClickRow"
-    >
-      <template
-        v-if="hasHeader"
-        #header="scope"
+    <template #default>
+      <schema-table
+        ref="table"
+        :schema="tableSchema"
+        :row-key="rowKey"
+        :current-row-key="selectedKey"
+        highlight-current-row
+        @row-click="onClickRow"
       >
-        <slot
-          name="header"
-          :item="selectedRow"
-          v-bind="scope"
-        />
-      </template>
-    </schema-table>
+        <template
+          v-if="hasHeader"
+          #header="scope"
+        >
+          <slot
+            name="header"
+            :item="selectedRow"
+            v-bind="scope"
+          />
+        </template>
+      </schema-table>
+    </template>
   </confirm-dialog>
 </template>
 

+ 33 - 65
src/views/ad/automation/task/AssetTask.vue

@@ -74,50 +74,36 @@
       @confirm="onChangeDevices"
       @closed="onClosedDeviceDialog"
     >
-      <template #default>
-        <div class="l-flex__auto l-flex c-device-toggle">
-          <div class="l-flex__fill l-flex--col">
-            <schema-table
-              ref="deviceTable"
-              :schema="deviceSchema"
-              @row-click="onDeviceRowClick"
-              @selection-change="onDeviceSelectionChange"
-            />
-          </div>
-          <div class="l-flex__none l-flex--row c-device-toggle__button">
-            <el-button
-              type="primary"
-              :disabled="!selectedDevices.length"
-              @click="onAddDevice"
-            >
-              <i class="el-icon-arrow-right" />
-            </el-button>
-          </div>
-          <el-empty
-            v-if="isEmpty"
-            class="l-flex__none l-flex--row center c-device-toggle__list"
-            description="请添加设备"
-          />
+      <c-transfer
+        class="l-flex__auto"
+        :source-schema="deviceSchema"
+        :selectable="deviceSelectable"
+        :add="onAddDevices"
+      >
+        <el-empty
+          v-if="isEmpty"
+          class="l-flex__auto l-flex--row center"
+          description="请添加设备"
+        />
+        <div
+          v-else
+          class="l-flex__auto l-flex--col u-font-size--sm u-overflow-y--auto"
+        >
           <div
-            v-else
-            class="l-flex__none l-flex--col c-device-toggle__list u-font-size--sm u-overflow-y--auto"
+            v-for="(device, index) in devices"
+            :key="device.id"
+            class="l-flex--row c-sibling-item--v c-device-toggle__item"
           >
-            <div
-              v-for="(device, index) in devices"
-              :key="device.id"
-              class="l-flex--row c-sibling-item--v c-device-toggle__item"
-            >
-              <div class="l-flex__auto u-ellipsis">
-                {{ device.name }}
-              </div>
-              <i
-                class="l-flex__none c-device-toggle__del el-icon-delete has-padding--h has-active"
-                @click="onDelDevice(index)"
-              />
+            <div class="l-flex__auto u-ellipsis">
+              {{ device.name }}
             </div>
+            <i
+              class="l-flex__none c-device-toggle__del el-icon-delete has-padding--h has-active"
+              @click="onDelDevice(index)"
+            />
           </div>
         </div>
-      </template>
+      </c-transfer>
     </confirm-dialog>
   </wrapper>
 </template>
@@ -312,13 +298,11 @@ export default {
           { key: 'name', type: 'search', placeholder: '设备名称' }
         ],
         cols: [
-          { type: 'selection' },
-          { prop: 'name', label: '名称' }
-        ],
-        pagination: { small: true, layout: 'prev,pager,next' }
+          { prop: 'name', label: '名称' },
+          { prop: 'address', label: '地址' }
+        ]
       },
-      devices: [],
-      selectedDevices: []
+      devices: []
     }
   },
   computed: {
@@ -688,20 +672,16 @@ export default {
       this.devices = deviceArray
       this.$refs.deviceDialog.show()
     },
-    onDeviceRowClick (row) {
-      this.$refs.deviceTable.getInst().toggleRowSelection(row)
+    deviceSelectable ({ id }) {
+      return !this.$deviceMap[id]
     },
-    onDeviceSelectionChange (devices) {
-      this.selectedDevices = devices
-    },
-    onAddDevice () {
-      this.selectedDevices.forEach(({ id, name }) => {
+    onAddDevices (value) {
+      value.forEach(({ id, name }) => {
         if (!this.$deviceMap[id]) {
           this.$deviceMap[id] = 1
           this.devices.push({ id, name })
         }
       })
-      this.$refs.deviceTable.getInst().clearSelection()
     },
     onDelDevice (index) {
       const { id } = this.devices.splice(index, 1)[0]
@@ -746,7 +726,6 @@ export default {
       this.$task = null
       this.$deviceMap = null
       this.devices = []
-      this.selectedDevices = []
     }
   }
 }
@@ -809,17 +788,6 @@ export default {
 }
 
 .c-device-toggle {
-  &__button {
-    padding: 0 $spacing $spacing;
-    margin: 0 $spacing;
-    border-left: 1px solid $border;
-    border-right: 1px solid $border;
-  }
-
-  &__list {
-    width: 400px;
-  }
-
   &__item {
     padding: 0 0 0 $padding--lg;
     border: 1px solid $gray;

+ 0 - 76
src/views/device/detail/components/DeviceInfo.vue

@@ -1,76 +0,0 @@
-<template>
-  <div
-    class="l-flex--col c-info"
-    :class="{ 'l-flex__fill': hasMap }"
-  >
-    <div class="l-flex--row has-bottom-padding u-bold">设备信息</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">{{ device.serialNumber }}</div>
-      </div>
-      <div class="l-flex--row l-flex__fill c-sibling-item">
-        <div class="l-flex__none c-info__title">MAC</div>
-        <div class="l-flex__fill c-info__value">{{ device.mac }}</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">{{ device.productName }}</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">{{ device.openTime }} - {{ device.closeTime }}</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">{{ device.resolutionRatio }}</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">
-          <content-protection :device="device" />
-        </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">{{ device.address }}</div>
-      </div>
-    </div>
-    <device-map
-      v-if="hasMap"
-      class="l-flex__auto"
-      :device="device"
-    />
-  </div>
-</template>
-
-<script>
-import DeviceMap from './DeviceMap'
-import ContentProtection from './external/ContentProtection'
-
-export default {
-  name: 'DeviceInfo',
-  components: {
-    DeviceMap,
-    ContentProtection
-  },
-  props: {
-    device: {
-      type: Object,
-      required: true
-    }
-  },
-  computed: {
-    hasMap () {
-      const { longitude, latitude } = this.device
-      return longitude && latitude
-    }
-  }
-}
-</script>

+ 348 - 0
src/views/device/detail/components/DeviceInfo/components/Power.vue

@@ -0,0 +1,348 @@
+<template>
+  <div>
+    <div class="l-flex--row c-sibling-item--v u-color--black u-font-size--sm u-bold">
+      <div class="c-sibling-item">状态</div>
+      <i
+        class="c-sibling-item el-icon-refresh u-font-size has-active"
+        @click="onRefresh"
+      />
+    </div>
+    <schema-table
+      ref="powerTable"
+      class="c-sibling-item--v near"
+      :schema="powerSchema"
+    />
+    <template v-if="hasMulti">
+      <div class="c-sibling-item--v u-color--black u-font-size--sm u-bold">多功能卡定时任务</div>
+      <schema-table
+        ref="multiTable"
+        class="c-sibling-item--v near"
+        :schema="multiTaskSchema"
+      />
+    </template>
+    <template v-if="hasRelay">
+      <div class="c-sibling-item--v u-color--black u-font-size--sm u-bold">播控盒定时任务</div>
+      <schema-table
+        ref="relayTable"
+        class="c-sibling-item--v near"
+        :schema="relayTaskSchema"
+      />
+    </template>
+  </div>
+</template>
+
+<script>
+import { toDate } from '@/utils/event'
+import {
+  send,
+  addListener,
+  removeListener
+} from '../../../monitor'
+import { Freq } from '../../DeviceInvoke/mixins/TaskDialog'
+
+const GET_POWER_STATUS = 'GetRealtimePowerSwitchStatusAsync' // 9.2.2.1、获取电源实时状态获取
+const GET_MULTI_POWER_TIMING = 'GetPowerInfoPolicyAsync' // 9.29.1.2、获取多功能卡电源定时控制任务
+const GET_RELAY_POWER_TIMING = 'GetRelayPowerPolicyAsync' // 9.29.1.6、获取本板电源定时控制任务
+
+const ErrorMessage = {
+  TIMEOUT: '暂未获取到操作反馈,请稍后重试',
+  BUSY: '终端被他人占用',
+  PASSWORD: '登录密码错误,请联系管理员',
+  [GET_POWER_STATUS]: '获取电源状态异常,请稍后重试',
+  [GET_MULTI_POWER_TIMING]: '获取多功能卡定时数据异常,请回读查看',
+  [GET_RELAY_POWER_TIMING]: '获取播控盒定时数据异常,请回读查看'
+}
+const RELAY_KEY = -1
+const FOREVER = '4016-06-06'
+
+export default {
+  name: 'Power',
+  props: {
+    device: {
+      type: Object,
+      required: true
+    }
+  },
+  data () {
+    return {
+      actionInfo: ['开启', '关闭'],
+      hasMulti: false,
+      hasRelay: false,
+      powerSchema: {
+        singlePage: true,
+        list: this.getPowers,
+        cols: [
+          { label: '设备', render: ({ portIndex }) => portIndex === RELAY_KEY ? '播控盒' : `多功能卡${portIndex}` },
+          { prop: 'powerIndex', label: '电源端口' },
+          { prop: 'type', label: '电源类型' },
+          { type: 'tag', render: ({ action }) => action
+            ? { type: 'danger', label: this.actionInfo[action] }
+            : { type: 'success', label: this.actionInfo[action] } }
+        ]
+      },
+      multiTaskSchema: {
+        singlePage: true,
+        list: this.getMultiTasks,
+        cols: [
+          { label: '电源类型', render: ({ typeKey, type }) => this.$typeMap[typeKey]?.label || type },
+          { label: '执行动作', render: ({ action }) => this.actionInfo[action] },
+          { prop: 'executeTime', label: '执行时间' },
+          { prop: 'freqInfo', label: '执行方式' },
+          { label: '生效日期', render: ({ freq, startTime, endTime }) => {
+            switch (freq) {
+              case Freq.ONCE:
+                return startTime
+              default:
+                return endTime === FOREVER ? '永久有效' : `${startTime} 至 ${endTime}`
+            }
+          }, 'min-width': 120 },
+          { type: 'tag', render: task => this.isExpired(task)
+            ? { type: 'warning', label: '已过期' }
+            : task.enable
+              ? { type: 'success', label: '启用' }
+              : { type: 'danger', label: '停用' } }
+        ]
+      },
+      relayTaskSchema: {
+        singlePage: true,
+        list: this.getRelayTasks,
+        cols: [
+          { label: '电源类型', render: ({ typeKey, type }) => this.$typeMap[typeKey]?.label || type },
+          { label: '执行动作', render: ({ action }) => this.actionInfo[action] },
+          { prop: 'executeTime', label: '执行时间' },
+          { prop: 'freqInfo', label: '执行方式' },
+          { label: '生效日期', render: ({ freq, startTime, endTime }) => {
+            switch (freq) {
+              case Freq.ONCE:
+                return startTime
+              default:
+                return endTime === FOREVER ? '永久有效' : `${startTime} 至 ${endTime}`
+            }
+          }, 'min-width': 120 },
+          { type: 'tag', render: task => this.isExpired(task)
+            ? { type: 'warning', label: '已过期' }
+            : task.enable
+              ? { type: 'success', label: '启用' }
+              : { type: 'danger', label: '停用' } }
+        ]
+      }
+    }
+  },
+  created () {
+    addListener('power', this.onMessage)
+    this.$tableData = {}
+    this.$tableStatus = {}
+  },
+  beforeDestroy () {
+    removeListener('power', this.onMessage)
+    Object.keys(this.$tableStatus).forEach(key => {
+      clearTimeout(this.$tableStatus[key].timer)
+    })
+  },
+  methods: {
+    onRefresh () {
+      this.$tableData = {}
+      this.$tableStatus = {}
+      this.hasMulti = false
+      this.hasRelay = false
+      this.$refs.powerTable.pageTo()
+    },
+    sendTopic (invoke, inputs) {
+      console.log('invoke', invoke, inputs)
+      const timestamp = `${Date.now()}`
+      const messageId = `${invoke}_${timestamp}`
+      return send(
+        '/multifunctionCard/invoke',
+        {
+          messageId,
+          timestamp,
+          'function': invoke,
+          inputs: inputs || []
+        }
+      ).then(
+        () => messageId,
+        () => {
+          this.$message({
+            type: 'warning',
+            message: '正在连接,请稍后再试'
+          })
+          return Promise.reject()
+        }
+      )
+    },
+    getData (key) {
+      if (this.$tableData[key]) {
+        return Promise.resolve({
+          data: this.$tableData[key] || []
+        })
+      }
+      return new Promise((resolve, reject) => {
+        this.sendTopic(
+          key,
+          JSON.stringify({ sn: this.device.serialNumber }),
+          ErrorMessage[key]
+        ).then(
+          messageId => {
+            const timer = setTimeout(() => {
+              this.$message({
+                type: 'warning',
+                message: ErrorMessage.TIMEOUT
+              })
+              this.$tableStatus[messageId].reject()
+              delete this.$tableStatus[messageId]
+            }, 10000)
+            this.$tableStatus[messageId] = { key, timer, resolve, reject }
+          },
+          reject
+        )
+      })
+    },
+    onMessage (message) {
+      if (!this.$tableStatus[message.messageId]) {
+        return
+      }
+
+      const status = this.$tableStatus[message.messageId]
+      delete this.$tableStatus[message.messageId]
+      clearTimeout(status.timer)
+
+      if (message.code !== 0) {
+        this.$message({
+          type: 'warning',
+          message: `${ErrorMessage[status.key]}【${message.data}】`
+        })
+        status.reject()
+        return
+      }
+      const data = message.data
+        ? JSON.parse(message.data.replaceAll("'", '"'))
+        : {}
+      if (data.logined === false) {
+        this.$message({
+          type: 'warning',
+          message: data.validition ? ErrorMessage.BUSY : ErrorMessage.PASSWORD
+        })
+        status.reject()
+        return
+      }
+      let tableData = []
+      switch (message.function) {
+        case GET_POWER_STATUS:
+          tableData = this.setPowerStatus(data)
+          break
+        case GET_MULTI_POWER_TIMING:
+          tableData = this.setMultiPowerTasks(data.data)
+          break
+        case GET_RELAY_POWER_TIMING:
+          tableData = this.setRelayPowerTasks(data)
+          break
+        default:
+          break
+      }
+      this.$tableData[status.key] = tableData
+      status.resolve({ data: tableData })
+    },
+    getPowers () {
+      return this.getData(GET_POWER_STATUS)
+    },
+    setPowerStatus ({ current_status_info }) {
+      const map = {}
+      const powers = []
+      let hasMulti = false
+      let hasRelay = false
+      current_status_info.forEach(({ portIndex, connectIndex, updatePowerIndexStates }) => {
+        updatePowerIndexStates.forEach(({ powerIndex, type, action }) => {
+          const key = portIndex === RELAY_KEY ? RELAY_KEY : `${portIndex}_${connectIndex}_${type}`
+          if (!map[key]) {
+            map[key] = {
+              portIndex,
+              connectIndex,
+              type,
+              label: portIndex === RELAY_KEY ? '' : `${portIndex} ${type}`
+            }
+            if (portIndex === RELAY_KEY) {
+              hasRelay = true
+            } else {
+              hasMulti = true
+            }
+          }
+          powers.push({
+            portIndex, connectIndex, powerIndex, type, action,
+            powerKey: `${portIndex}_${powerIndex}`
+          })
+        })
+      })
+      this.$typeMap = map
+      this.hasMulti = hasMulti
+      this.hasRelay = !hasMulti && hasRelay
+      this.$hasRelay = hasRelay
+      return powers
+    },
+    getMultiTasks () {
+      return this.getData(GET_MULTI_POWER_TIMING)
+    },
+    setMultiPowerTasks (data) {
+      console.log('GET_MULTI_POWER_TIMING', data)
+      const tasks = []
+      data.forEach(({ portIndex, connectIndex, enable, conditions }) => {
+        if (enable) {
+          conditions.forEach(task => {
+            tasks.push({
+              from: { portIndex, connectIndex },
+              typeKey: `${portIndex}_${connectIndex}_${task.type}`,
+              action: task.action,
+              ...this.transfromDataToTask(task)
+            })
+          })
+        }
+      })
+      if (this.$hasRelay) {
+        this.hasRelay = true
+      }
+      return tasks
+    },
+    getRelayTasks () {
+      return this.getData(GET_RELAY_POWER_TIMING)
+    },
+    setRelayPowerTasks (data) {
+      console.log('GET_RELAY_POWER_TIMING', data)
+      const tasks = []
+      data.relayPolicyTask.forEach(task => {
+        tasks.push({
+          action: task.status ^ 1,
+          ...this.transfromDataToTask(task)
+        })
+      })
+      return tasks
+    },
+    transfromDataToTask ({ type, flag, powerIndex, enable, startTime, endTime, cron }) {
+      const strArr = cron[0].split(' ')
+      const freq = strArr[3] === '*' && strArr[5] === '?'
+        ? Freq.DAILY
+        : strArr[5] === '?'
+          ? Freq.ONCE
+          : Freq.WEEKLY
+      return {
+        type, flag, powerIndex, enable, startTime, endTime,
+        freq,
+        freqInfo: this.getFreqInfo(freq, strArr[5]),
+        dayOfWeek: freq === Freq.WEEKLY ? strArr[5] : '',
+        executeTime: `${strArr[2].padStart(2, '0')}:${strArr[1].padStart(2, '0')}:${strArr[0].padStart(2, '0')}`
+      }
+    },
+    getFreqInfo (freq, val = '') {
+      switch (freq) {
+        case Freq.ONCE:
+          return '单次'
+        case Freq.DAILY:
+          return '每天'
+        default:
+          return `每周${val.split(',').map(val => ['', '一', '二', '三', '四', '五', '六', '日'][val])}`
+      }
+    },
+    isExpired ({ endTime, executeTime }) {
+      return endTime !== FOREVER && toDate(`${endTime} ${executeTime}`) <= Date.now()
+    }
+  }
+}
+</script>

+ 24 - 0
src/views/device/detail/components/DeviceInfo/components/VolumeInfo.vue

@@ -0,0 +1,24 @@
+<script>
+export default {
+  name: 'VolumeInfo',
+  props: {
+    device: {
+      type: Object,
+      required: true
+    }
+  },
+  computed: {
+    volume () {
+      return this.device.volume
+    }
+  },
+  render (h) {
+    if (isNaN(this.volume)) {
+      return h('i', {
+        class: 'el-icon-loading u-font-size'
+      })
+    }
+    return h('div', null, `${this.volume}%`)
+  }
+}
+</script>

+ 149 - 0
src/views/device/detail/components/DeviceInfo/index.vue

@@ -0,0 +1,149 @@
+<template>
+  <div class="l-flex--col 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>
+        <div class="l-flex__fill c-info__value">{{ device.serialNumber }}</div>
+      </div>
+      <div class="l-flex--row l-flex__fill c-sibling-item">
+        <div class="l-flex__none c-info__title">MAC</div>
+        <div class="l-flex__fill c-info__value">{{ device.mac }}</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">{{ device.productName }}</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">{{ device.resolutionRatio }}</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">{{ device.openTime }} - {{ device.closeTime }}</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">
+          <content-protection :device="device" />
+        </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">
+          <i
+            v-if="hasMap"
+            class="c-sibling-item el-icon-map-location u-font-size--lg has-active u-pointer"
+            @click="showMap"
+          />
+          <div class="c-sibling-item">地址</div>
+        </div>
+        <div class="l-flex__fill c-info__value u-color--blue">{{ device.address }}</div>
+      </div>
+      <div class="l-flex--row l-flex__fill c-sibling-item">
+        <div class="l-flex__none c-info__title">
+          <screen-volume
+            v-if="online"
+            ref="screenVolume"
+            class="c-sibling-item"
+            :device="device"
+            :online="online"
+          >
+            <template #trigger="{invoke}">
+              <i
+                class="el-icon-s-tools u-font-size--lg has-active u-pointer"
+                @click="invoke"
+              />
+            </template>
+          </screen-volume>
+          <div class="c-sibling-item">音量</div>
+        </div>
+        <div class="l-flex__fill c-info__value u-color--blue">
+          <volume-info
+            v-if="online"
+            :device="device"
+          />
+          <div
+            v-else
+            class="u-font-size--sm u-color--info"
+          >
+            设备当前未上线
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="l-flex--row c-info__block">
+      <div class="l-flex__none c-info__title">电源</div>
+      <power
+        v-if="online"
+        class="l-flex__fill"
+        :device="device"
+      />
+      <div
+        v-else
+        class="l-flex__fill c-info__value u-font-size--sm u-color--info"
+      >
+        设备当前未上线
+      </div>
+    </div>
+    <c-dialog
+      ref="mapDialog"
+      title="地址详情"
+      size="lg fixed"
+    >
+      <template #default>
+        <device-map
+          class="l-flex__auto"
+          :device="device"
+        />
+      </template>
+    </c-dialog>
+  </div>
+</template>
+
+<script>
+import ContentProtection from '../external/ContentProtection'
+import ScreenVolume from '../DeviceInvoke/ScreenVolume.vue'
+import DeviceMap from '../DeviceMap'
+import Power from './components/Power'
+import VolumeInfo from './components/VolumeInfo'
+
+export default {
+  name: 'DeviceInfo',
+  components: {
+    DeviceMap,
+    ContentProtection,
+    Power,
+    VolumeInfo,
+    ScreenVolume
+  },
+  props: {
+    device: {
+      type: Object,
+      required: true
+    },
+    online: {
+      type: [Boolean, String],
+      default: false
+    }
+  },
+  computed: {
+    hasMap () {
+      const { longitude, latitude } = this.device
+      return longitude && latitude
+    }
+  },
+  methods: {
+    showMap () {
+      if (!this.hasMap) {
+        return
+      }
+      this.$refs.mapDialog.show()
+    }
+  }
+}
+</script>

+ 3 - 0
src/views/device/detail/components/DeviceInvoke/DeviceNetwork/index.vue

@@ -72,6 +72,9 @@ export default {
     },
     isWireless () {
       return this.active === 'wireless'
+    },
+    replyTopic () {
+      return `${this.device.productId}/${this.device.id}/function/invoke/reply`
     }
   },
   created () {

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

@@ -12,12 +12,10 @@
 </template>
 
 <script>
-import baseMixin from './mixins/base'
 import { send } from '../../monitor'
 
 export default {
   name: 'DeviceReboot',
-  mixins: [baseMixin],
   data () {
     return {
       rebooting: false

+ 10 - 16
src/views/device/detail/components/DeviceInvoke/MultifunctionCardPowerSwitch.vue

@@ -88,11 +88,11 @@
 
 <script>
 import { parseTime } from '@/utils'
-import {
-  subscribe,
-  unsubscribe
-} from '@/utils/mqtt'
 import { toDate } from '@/utils/event'
+import {
+  addListener,
+  removeListener
+} from '../../monitor'
 import baseMixin from './mixins/base'
 import TaskDialog, { Freq } from './mixins/TaskDialog'
 
@@ -139,6 +139,7 @@ export default {
   mixins: [baseMixin],
   data () {
     return {
+      topic: '/multifunctionCard/invoke',
       active: GET_POWER_STATUS,
       tabs: [
         { key: GET_POWER_STATUS, name: '电源状态' }
@@ -212,12 +213,6 @@ export default {
     }
   },
   computed: {
-    topic () {
-      return `${this.device.productId}/${this.deviceId}/multifunctionCard/invoke`
-    },
-    replyTopic () {
-      return `${this.device.productId}/${this.deviceId}/multifunctionCard/invoke/reply`
-    },
     loading () {
       return this.active !== GET_POWER_STATUS && !this.timingStatus
     },
@@ -294,10 +289,10 @@ export default {
     }
   },
   created () {
-    subscribe([this.replyTopic], this.onMessage)
+    addListener('power', this.onMessage)
   },
   beforeDestroy () {
-    unsubscribe([this.replyTopic], this.onMessage)
+    removeListener('power', this.onMessage)
   },
   methods: {
     isDisableDate (date) {
@@ -316,11 +311,10 @@ export default {
       this.active = GET_POWER_STATUS
       this.getPowerStatus()
     },
-    onMessage (topic, message) {
-      if (!this.$messageId || topic !== this.replyTopic) {
+    onMessage (message) {
+      if (!this.$messageId) {
         return
       }
-      message = JSON.parse(message)
       if (message.messageId !== this.$messageId) {
         return
       }
@@ -328,7 +322,7 @@ export default {
       if (message.code !== 0) {
         this.$message({
           type: 'warning',
-          message: ErrorMessage[message.function] || ErrorMessage.DEFAULT
+          message: `${ErrorMessage[message.function] || ErrorMessage.DEFAULT}【${message.data}】`
         })
         return
       }

+ 19 - 7
src/views/device/detail/components/DeviceInvoke/ScreenVolume.vue

@@ -1,10 +1,15 @@
 <template>
-  <div class="l-flex--col center has-border radius has-padding ">
-    <i
-      class="o-icon lg u-pointer"
-      @click="invoke"
-    />
-    <div class="has-padding u-color--black u-bold">{{ type }}</div>
+  <div :class="defaultClass">
+    <slot
+      name="trigger"
+      :invoke="invoke"
+    >
+      <i
+        class="o-icon lg u-pointer"
+        @click="invoke"
+      />
+      <div class="has-padding u-color--black u-bold">{{ type }}</div>
+    </slot>
     <c-dialog
       ref="dialog"
       size="medium"
@@ -66,7 +71,7 @@
 import simpleTaskMixin from './mixins/simple-task'
 
 export default {
-  name: 'ScreenLight',
+  name: 'ScreenVolumn',
   mixins: [simpleTaskMixin],
   data () {
     return {
@@ -77,6 +82,13 @@ export default {
         { label: '音量值(%)', render ({ inputs }) { return inputs[0]?.value } }
       ])
     }
+  },
+  computed: {
+    defaultClass () {
+      return this.$scopedSlots.trigger
+        ? ''
+        : 'l-flex--col center has-border radius has-padding'
+    }
   }
 }
 </script>

+ 10 - 16
src/views/device/detail/components/DeviceInvoke/mixins/base.js

@@ -1,4 +1,4 @@
-import { publish } from '@/utils/mqtt'
+import { send } from '../../../monitor'
 
 export default {
   props: {
@@ -7,15 +7,9 @@ export default {
       required: true
     }
   },
-  computed: {
-    deviceId () {
-      return this.device.id
-    },
-    topic () {
-      return `${this.device.productId}/${this.deviceId}/function/invoke`
-    },
-    replyTopic () {
-      return `${this.device.productId}/${this.deviceId}/function/invoke/reply`
+  data () {
+    return {
+      topic: '/function/invoke'
     }
   },
   created () {
@@ -28,16 +22,16 @@ export default {
     sendTopic (invoke, inputs, aysncFailMessage, delay = 5000) {
       console.log('invoke', invoke, inputs)
       this.closeAsyncLoading()
-      const messageId = `${Date.now()}`
-      publish(
+      const timestamp = `${Date.now()}`
+      const messageId = `${timestamp}`
+      return send(
         this.topic,
-        JSON.stringify({
+        {
           messageId,
-          timestamp: messageId,
+          timestamp,
           'function': invoke,
           inputs: inputs || []
-        }),
-        true
+        }
       ).then(
         () => {
           if (aysncFailMessage) {

+ 4 - 2
src/views/device/detail/components/DeviceInvoke/mixins/simple-task.js

@@ -23,7 +23,7 @@ export default {
   methods: {
     invoke () {
       this.active = 'immediate'
-      this.taskValue = 50
+      this.taskValue = isNaN(this.device[this.valueKey]) ? 50 : this.device[this.valueKey]
       this.options = createListOptions({ functionKey: this.functionKey })
       this.$refs.dialog.show()
     },
@@ -33,7 +33,9 @@ export default {
         '操作确认',
         { type: 'warning' }
       ).then(() => {
-        this.sendTopic(this.functionKey, this.createProps(this.taskValue))
+        this.sendTopic(this.functionKey, this.createProps(this.taskValue)).then(() => {
+          this.$set(this.device, this.valueKey, this.taskValue)
+        })
       })
     },
     createProps (val) {

+ 3 - 0
src/views/device/detail/components/DeviceInvoke/mixins/task.js

@@ -23,6 +23,9 @@ export default {
     }
   },
   computed: {
+    deviceId () {
+      return this.device.id
+    },
     isImmediate () {
       return this.active === 'immediate'
     },

+ 8 - 5
src/views/device/detail/components/external/ContentProtection.vue

@@ -12,12 +12,15 @@ export default {
   data () {
     return {
       loading: true,
-      error: false,
-      open: false
+      error: false
     }
   },
   created () {
-    this.getContentProtection()
+    if (this.device.contentProtection === true || this.device.contentProtection === false) {
+      this.loading = false
+    } else {
+      this.getContentProtection()
+    }
   },
   methods: {
     getContentProtection () {
@@ -25,7 +28,7 @@ export default {
       this.error = false
       getContentProtection(this.device.id, { custom: true }).then(
         ({ data }) => {
-          this.open = data ? data.protect : false
+          this.device.contentProtection = data ? data.protect : false
         },
         () => {
           this.error = true
@@ -51,7 +54,7 @@ export default {
         }
       }, ['获取失败,点击重试'])
     }
-    return h('div', null, this.open ? '已启用' : '未启用')
+    return h('div', null, this.device.contentProtection ? '已启用' : '未启用')
   }
 }
 </script>

+ 1 - 1
src/views/device/detail/components/external/Sensors/Sensor.vue

@@ -48,7 +48,7 @@ import {
   Type,
   addListener,
   removeListener
-} from '@/views/device/detail/monitor'
+} from '../../../monitor'
 
 export default {
   name: 'Sensor',

+ 1 - 1
src/views/device/detail/components/external/Sensors/index.vue

@@ -26,7 +26,7 @@
 import {
   startSensor,
   stopSensor
-} from '@/views/device/detail/monitor'
+} from '../../../monitor'
 import Sensor from './Sensor'
 
 export default {

+ 22 - 17
src/views/device/detail/index.vue

@@ -49,25 +49,23 @@
       v-if="device"
       class="l-flex--col l-flex__fill has-padding"
     >
-      <keep-alive include="DeviceInfo">
-        <component
-          :is="active"
-          :key="active"
-          class="c-detail__wrapper has-gap u-overflow-y--auto"
-          :device="device"
-          :online="isOnline"
-        />
-      </keep-alive>
+      <component
+        :is="active"
+        :key="active"
+        class="c-detail__wrapper has-gap u-overflow-y--auto"
+        :device="device"
+        :online="isOnline"
+      />
     </div>
   </wrapper>
 </template>
 
 <script>
 import { getDevice } from '@/api/device'
-import { ScreenshotCache } from '@/utils/cache'
 import {
   start,
   stop,
+  send,
   addListener
 } from './monitor'
 import DeviceInfo from './components/DeviceInfo'
@@ -172,11 +170,12 @@ export default {
             this.device = data
             this.isActivated = data.activate
             this.isOnline = this.isActivated && data.onlineStatus === 1
-            if (!this.isOnline) {
-              ScreenshotCache.remove(id)
-            }
             start(this.device)
-            addListener('online', this.onUpdate)
+            addListener('online', this.onUpdateOnline)
+            addListener('screen', this.onUpdateScreen)
+            if (this.isOnline) {
+              send('/screen/ask')
+            }
           } else {
             this.$message({
               type: 'warning',
@@ -191,12 +190,18 @@ export default {
         }
       })
     },
-    onUpdate (online) {
+    onUpdateOnline (online) {
       this.isActivated = true
       this.isOnline = online
       this.device.onlineStatus = online ? 1 : 2
-      if (!this.isOnline) {
-        ScreenshotCache.remove(this.deviceId)
+      if (this.isOnline) {
+        send('/screen/ask')
+      }
+    },
+    onUpdateScreen (screen) {
+      if (screen) {
+        const { volume } = screen
+        this.$set(this.device, 'volume', volume)
       }
     },
     onDashboard () {

+ 15 - 3
src/views/device/detail/monitor.js

@@ -22,6 +22,8 @@ const RESET_MESSAGE = '__reset__'
 
 const types = new Map()
 
+const parser = (inst, message) => JSON.parse(message)
+
 export function start (device) {
   if (productId) {
     stop()
@@ -33,12 +35,15 @@ export function start (device) {
     `${productId}/${deviceId}/offline`,
     `${productId}/${deviceId}/status/reply`,
     `${productId}/${deviceId}/resource/progress`,
-    `${productId}/${deviceId}/screen`
+    `${productId}/${deviceId}/screen`,
+    `${productId}/${deviceId}/screen`,
+    `${productId}/${deviceId}/multifunctionCard/invoke/reply`
   ], onMessage)
   createType('online', { parser: onlineParser })
   createType('status', { type: Type.LOAD, parser: statusParser, reset: true })
   createType('download', { type: Type.CACHE, parser: downloadParser, value: [], reset: true, mark: {} })
   createType('screen', { type: Type.CACHE, parser: screenParser, reset: true })
+  createType('power', { parser })
 }
 
 export function stop () {
@@ -48,7 +53,8 @@ export function stop () {
       `${productId}/${deviceId}/offline`,
       `${productId}/${deviceId}/status/reply`,
       `${productId}/${deviceId}/resource/progress`,
-      `${productId}/${deviceId}/screen`
+      `${productId}/${deviceId}/screen`,
+      `${productId}/${deviceId}/multifunctionCard/invoke/reply`
     ], onMessage)
     productId = null
     deviceId = null
@@ -83,13 +89,17 @@ function getTypeBySend (topic) {
   switch (topic) {
     case '/status/ask':
       return 'status'
+    case '/screen/ask':
+      return 'screen'
+    case '/multifunctionCard/invoke':
+      return 'power'
     default:
       return null
   }
 }
 
 export function send (topic, message) {
-  return publish(`${productId}/${deviceId}${topic}`, JSON.stringify(message || { timestamp: Date.now() })).then(() => {
+  return publish(`${productId}/${deviceId}${topic}`, JSON.stringify(message || { timestamp: `${Date.now()}` }), true).then(() => {
     const inst = types.get(getTypeBySend(topic))
     if (inst && inst.type === Type.LOAD) {
       inst.loading = true
@@ -118,6 +128,8 @@ function getType (topic) {
       return 'download'
     case 'screen':
       return 'screen'
+    case 'multifunctionCard/invoke/reply':
+      return 'power'
     default:
       return null
   }

+ 1 - 1
src/views/external/camera/index.vue

@@ -91,7 +91,7 @@ export default {
         ],
         cols: [
           { type: 'refresh' },
-          { label: '类型', render: ({ cameraType }) => ['', '人流量监测', 'LED屏监测'][cameraType] },
+          { label: '类型', render: ({ cameraType }) => ['', 'LED屏监测', '人流量监测'][cameraType] },
           { prop: 'name', label: '名称' },
           { type: 'tag', render: ({ onlineStatus }) => onlineStatus === 1
             ? { type: 'success', label: '在线' }