Bladeren bron

feat: sending card

release restrictions and support binding
Casper Dai 3 jaren geleden
bovenliggende
commit
029193ca46

+ 50 - 5
src/api/external.js

@@ -5,6 +5,40 @@ import {
   del,
   messageSend
 } from './base'
+import { ThirdPartDevice } from '@/constant'
+
+function bind (deviceId, deviceType, thirdPartyDeviceId) {
+  return messageSend({
+    url: '/device/bind',
+    method: 'POST',
+    data: {
+      deviceId,
+      deviceType,
+      thirdPartyDeviceId
+    }
+  }, '绑定')
+}
+
+export function unbind (id) {
+  return messageSend({
+    url: `/device/bind/${id}`,
+    method: 'DELETE'
+  }, '解绑')
+}
+
+export function getThirdPartyDeviceTypes () {
+  return request({
+    url: '/device/bind/thirdPartyType',
+    method: 'GET'
+  })
+}
+
+export function getThirdPartyDevices (deviceId) {
+  return request({
+    url: `/device/bind/${deviceId}`,
+    method: 'GET'
+  })
+}
 
 export function getSensors (options) {
   return request({
@@ -56,14 +90,14 @@ export function getReceivingCardTopology (id, options) {
   })
 }
 
-export function getSendCardManufacturers () {
+export function getSendingCardManufacturers () {
   return request({
     url: '/device/thirdPartySendingCard/manufacturer',
     method: 'GET'
   })
 }
 
-export function getSendCards (query) {
+export function getSendingCards (query) {
   const { pageNum: pageIndex, pageSize, ...params } = query
   return request({
     url: '/device/thirdPartySendingCard/list',
@@ -75,7 +109,7 @@ export function getSendCards (query) {
   })
 }
 
-export function addSendCard (data) {
+export function addSendingCard (data) {
   return add({
     url: '/device/thirdPartySendingCard',
     method: 'POST',
@@ -83,7 +117,7 @@ export function addSendCard (data) {
   })
 }
 
-export function updateSendCard (data) {
+export function updateSendingCard (data) {
   return update({
     url: '/device/thirdPartySendingCard',
     method: 'PUT',
@@ -91,13 +125,24 @@ export function updateSendCard (data) {
   })
 }
 
-export function deleteSendCard ({ id, name }) {
+export function deleteSendingCard ({ id, name }) {
   return del({
     url: `/device/thirdPartySendingCard/${id}`,
     method: 'DELETE'
   }, name)
 }
 
+export function getDeviceSendingCard (deviceId) {
+  return request({
+    url: `/device/bind/thirdPartySendingCard/${deviceId}`,
+    method: 'GET'
+  }).then(({ data }) => data?.[0])
+}
+
+export function bindSendingCard (deviceId, thirdPartyDeviceId) {
+  return bind(deviceId, ThirdPartDevice.SENDING_CARD, thirdPartyDeviceId)
+}
+
 export function getContentProtection (deviceId, options) {
   return request({
     url: `/device/contentProtect/${deviceId}`,

+ 1 - 1
src/components/Schedule/ScheduleCalendar/EventEdit.vue

@@ -16,7 +16,7 @@
     <el-dialog
       :visible.sync="choosing"
       title="选择播放内容"
-      custom-class="c-dialog hidden-footer"
+      custom-class="c-dialog"
       append-to-body
     >
       <event-target-choose

+ 1 - 1
src/components/Schedule/ScheduleSwiper/index.vue

@@ -55,7 +55,7 @@
       <el-dialog
         :visible.sync="choosing"
         title="选择节目"
-        custom-class="c-dialog hidden-footer"
+        custom-class="c-dialog"
         append-to-body
       >
         <program-choose

+ 167 - 0
src/components/TableDialog/index.vue

@@ -0,0 +1,167 @@
+<template>
+  <el-dialog
+    title="发送控制设备选择"
+    :visible.sync="choosing"
+    custom-class="c-dialog"
+    :close-on-click-modal="false"
+    v-bind="$attrs"
+  >
+    <c-table
+      v-if="choosing"
+      :options="options"
+      @pagination="getList"
+      @row-dblclick="onChoose"
+    >
+      <template
+        v-if="filters"
+        #header
+      >
+        <div class="l-flex__auto" />
+        <div
+          v-for="filter in filters"
+          :key="filter.key"
+          class="l-flex__none c-sibling-item"
+        >
+          <el-select
+            v-if="filter.type === 'select'"
+            v-model="options.params[filter.key]"
+            class="l-flex__none o-select"
+            :loading="filter.loading"
+            @visible-change="visible => onFocus(visible, filter)"
+            @change="onChange"
+          >
+            <el-option
+              v-for="item in filter.list"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+          <template v-if="filter.type === 'search'">
+            <search-input
+              v-model.trim="options.params[filter.key]"
+              class="l-flex__none c-sibling-item"
+              :placeholder="filter.placeholder"
+              @search="onChange"
+            />
+            <button
+              class="l-flex__none c-sibling-item o-button"
+              @click="onChange"
+            >
+              搜索
+            </button>
+          </template>
+        </div>
+      </template>
+      <el-table-column
+        v-for="col in schema.cols"
+        :key="col.prop"
+        align="center"
+        show-overflow-tooltip
+        v-bind="col"
+      />
+    </c-table>
+  </el-dialog>
+</template>
+
+<script>
+import { createListOptions } from '@/utils'
+
+// const schema = {
+//   condition?: { },
+//   filters?: [
+//     { key: 'xxx', label: 'xxx', type: 'select', value: 'xxx', label: 'xxx' }
+//   ],
+//   list: Function,
+//   transform?: Function
+//   cols: [
+//     {
+//       prop: 'xxx', label: 'xxx'
+//     }
+//   ]
+// }
+export default {
+  name: 'TableDialog',
+  props: {
+    schema: {
+      type: Object,
+      default: null
+    }
+  },
+  data () {
+    return {
+      choosing: false,
+      options: null,
+      filters: null
+    }
+  },
+  methods: {
+    show () {
+      this.options = createListOptions({ ...this.schema.condition })
+      this.filters = this.schema?.filters?.map(filter => {
+        return {
+          ...filter,
+          list: filter.list ? filter.list : filter.placeholder ? [{ value: void 0, label: filter.placeholder }] : [],
+          loading: false,
+          loaded: !!filter.list
+        }
+      })
+      this.getList()
+      this.choosing = true
+    },
+    getList () {
+      const options = this.options
+      if (!options.loading) {
+        options.loading = true
+        const { list, transform } = this.schema
+        list(options.params).then(
+          ({ data, totalCount }) => {
+            options.error = false
+            options.list = transform ? data.map(transform) : data
+            options.totalCount = totalCount
+          },
+          () => {
+            options.error = true
+            options.list = []
+            options.totalCount = 0
+          }
+        ).finally(() => {
+          options.loading = false
+        })
+      }
+    },
+    onChange () {
+      const options = this.options
+      options.list = []
+      options.totalCount = 0
+      options.params.pageNum = 1
+      this.getList()
+    },
+    onChoose (item) {
+      this.$emit('choosen', {
+        value: item,
+        done: () => { this.choosing = false }
+      })
+    },
+    onFocus (visible, filter) {
+      if (visible && !filter.loaded && !filter.loading) {
+        console.log(filter)
+        filter.loading = true
+        filter.invoke().then(
+          ({ data }) => {
+            const { value, label } = filter
+            filter.list = filter.list.concat(data.map(item => {
+              return { value: item[value], label: item[label] }
+            }))
+            filter.loading = false
+            filter.loaded = true
+          },
+          () => {
+            filter.loading = false
+          }
+        )
+      }
+    }
+  }
+}
+</script>

+ 11 - 0
src/constant.js

@@ -45,6 +45,17 @@ export const EventTarget = {
   RECUR: 2
 }
 
+export const ThirdPartDevice = {
+  RECEIVING_CARD: 1,
+  SENDING_CARD: 2
+}
+
+export const Transmitter = {
+  IS_ASYNC: 1,
+  SUPPORT_DETECTION: 1 << 1,
+  SUPPORT_CONTENT_PROTECTION: 1 << 2
+}
+
 export const Access = {
   SUPER_ADMIN: 'frontend:admin',
 

+ 2 - 0
src/main.js

@@ -26,6 +26,7 @@ import SearchInput from './components/SearchInput'
 import Schedule from './components/Schedule'
 import ConfirmDialog from './components/ConfirmDialog'
 import Warning from './components/Warning'
+import TableDialog from './components/TableDialog'
 
 import {
   showLoading,
@@ -48,6 +49,7 @@ function startApp () {
   Vue.component('Schedule', Schedule)
   Vue.component('ConfirmDialog', ConfirmDialog)
   Vue.component('Warning', Warning)
+  Vue.component('TableDialog', TableDialog)
 
   Vue.prototype.$keycloak = keycloak
   Vue.prototype.$showLoading = showLoading

+ 1 - 2
src/router/index.js

@@ -138,7 +138,7 @@ export const asyncRoutes = [
       {
         name: 'schedule-deploy',
         path: 'deploy',
-        component: () => import('@/views/schedule/deploy/complex'),
+        component: () => import('@/views/schedule/deploy/index'),
         access: Access.PUBLISH_CALENDAR,
         meta: { title: '排期发布' }
       },
@@ -218,7 +218,6 @@ export const asyncRoutes = [
     meta: { title: '设备录入', icon: 'em' },
     children: [
       {
-        dev: true,
         path: 'transmitter',
         name: 'transmitter',
         access: Access.MANAGE_DEVICES,

+ 26 - 26
src/scss/bem/_component.scss

@@ -17,16 +17,6 @@
     min-height: 100px;
   }
 
-  &.hidden-footer {
-    .el-dialog__body {
-      padding-bottom: $spacing;
-    }
-
-    .el-dialog__footer {
-      display: none;
-    }
-  }
-
   .el-dialog__header,
   .el-dialog__footer {
     flex: none;
@@ -94,6 +84,7 @@
 .c-grid {
   display: grid;
   grid-template-columns: repeat(4, minmax(100px, 1fr));
+  grid-template-rows: max-content;
   grid-row-gap: $spacing;
   grid-column-gap: $spacing;
   min-height: 0;
@@ -101,6 +92,24 @@
   overflow-y: auto;
 }
 
+
+.c-info-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
+  grid-template-rows: max-content;
+  grid-row-gap: $spacing;
+  grid-column-gap: $spacing;
+  align-items: start;
+
+  &.less {
+    grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
+  }
+
+  &.more {
+    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+  }
+}
+
 .c-form {
   display: flex;
   flex-direction: column;
@@ -244,7 +253,7 @@
 
   .el-tabs__item {
     padding: 0;
-    color: $gray;
+    color: $info--dark;
     font-size: 16px;
     font-weight: bold;
     user-select: none;
@@ -368,20 +377,11 @@
   }
 }
 
-.c-info-grid {
+.c-tags {
   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 $info;
-    border-radius: 2px;
-  }
+  grid-template-columns: repeat(auto-fit, minmax(auto, 80px));
+  grid-template-rows: max-content;
+  grid-row-gap: 8px;
+  grid-column-gap: 10px;
+  justify-content: center;
 }

+ 1 - 1
src/scss/bem/_utility.scss

@@ -59,7 +59,7 @@
 }
 
 .u-color--info {
-  color: $gray--dark;
+  color: $info--dark;
 }
 
 .u-bold {

+ 1 - 1
src/views/basic/profile/index.vue

@@ -32,7 +32,7 @@
       />
       <div
         v-else
-        class="c-form"
+        class="c-form has-padding"
       >
         <div class="c-form__section">
           <span class="c-form__label">手机:</span>

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

@@ -1,11 +1,11 @@
 <template>
-  <div class="c-info-grid">
+  <div class="c-info-grid more">
     <div
       v-loading="rebooting"
-      class="c-info-grid__item l-flex--col center has-padding"
+      class="o-invoke l-flex--col center has-padding"
     >
       <i
-        class="o-invoke-icon reboot u-pointer"
+        class="o-invoke__icon reboot u-pointer"
         @click="reboot"
       />
       <div class="has-padding u-color--black u-bold">重启设备</div>
@@ -67,16 +67,21 @@ export default {
 </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;
+.o-invoke {
+  border: 1px solid $info;
+  border-radius: 2px;
 
-  &.reboot {
-    background-image: url("~@/assets/icon_reboot.png");
+  &__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>

+ 54 - 9
src/views/device/detail/components/DeviceRuntime/index.vue

@@ -1,15 +1,9 @@
 <template>
   <div class="c-info-grid">
-    <running
-      class="c-info-grid__item"
-      v-bind="$attrs"
-    />
+    <running v-bind="$attrs" />
     <template v-if="$attrs.online">
-      <screen-shot
-        class="c-info-grid__item"
-        v-bind="$attrs"
-      />
-      <download class="c-info-grid__item" />
+      <screen-shot v-bind="$attrs" />
+      <download />
     </template>
   </div>
 </template>
@@ -28,3 +22,54 @@ export default {
   }
 }
 </script>
+
+<style lang="scss">
+.c-runtime {
+  height: 180px;
+  padding: 12px 16px 16px;
+  color: $info;
+  font-size: 14px;
+  line-height: 1;
+  border: 1px solid $info;
+  border-radius: 2px;
+
+  .medium {
+    font-size: 18px;
+  }
+
+  .large {
+    font-size: 32px;
+  }
+
+  &__icon {
+    display: inline-block;
+    width: 32px;
+    height: 32px;
+    margin-right: 8px;
+    background-position: 0 0;
+    background-size: 100% 100%;
+    background-repeat: no-repeat;
+
+    &.running {
+      background-image: url("~@/assets/icon_condition.png");
+    }
+
+    &.screenshot {
+      background-image: url("~@/assets/icon_image.png");
+    }
+
+    &.download {
+      background-image: url("~@/assets/icon_download.png");
+    }
+  }
+
+  &__title {
+    color: $info--dark;
+  }
+
+  &__list {
+    color: $blue;
+    font-size: 18px;
+  }
+}
+</style>

+ 0 - 129
src/views/device/detail/components/DeviceStatus.vue

@@ -1,129 +0,0 @@
-<template>
-  <div class="l-flex--col">
-    <tabs
-      v-if="useTabs"
-      :items="tabs"
-      :active.sync="active"
-    />
-    <div class="l-flex__fill u-overflow-y--auto">
-      <component
-        :is="activeComponent"
-        v-bind="$attrs"
-      />
-    </div>
-  </div>
-</template>
-
-<script>
-import {
-  startSensor,
-  stopSensor
-} from '../monitor'
-import Tabs from './Tabs'
-import DeviceRuntime from './DeviceRuntime'
-import DeviceSensor from './DeviceSensor'
-
-export default {
-  name: 'DeviceStatus',
-  components: {
-    Tabs,
-    DeviceRuntime,
-    DeviceSensor
-  },
-  data () {
-    const tabs = [
-      { key: 'runtime', name: '播控器状态' },
-      this.accessSet.has(this.Access.VIEW_SENSORS) ? { key: 'sensor', name: '传感器状态' } : null
-    ].filter(val => val)
-    return {
-      tabs,
-      active: tabs[0].key
-    }
-  },
-  computed: {
-    useTabs () {
-      return this.tabs.length > 1
-    },
-    activeComponent () {
-      switch (this.active) {
-        case 'runtime':
-          return 'DeviceRuntime'
-        case 'sensor':
-          return 'DeviceSensor'
-        default:
-          return null
-      }
-    }
-  },
-  activated () {
-    startSensor()
-  },
-  deactivated () {
-    stopSensor()
-  }
-}
-</script>
-
-<style lang="scss">
-.c-runtime {
-  padding: 12px 16px 16px;
-  color: $info;
-  font-size: 14px;
-  line-height: 1;
-
-  .medium {
-    font-size: 18px;
-  }
-
-  .large {
-    font-size: 32px;
-  }
-
-  &__icon {
-    display: inline-block;
-    width: 32px;
-    height: 32px;
-    margin-right: 8px;
-    background-position: 0 0;
-    background-size: 100% 100%;
-    background-repeat: no-repeat;
-
-    &.running {
-      background-image: url("~@/assets/icon_condition.png");
-    }
-
-    &.screenshot {
-      background-image: url("~@/assets/icon_image.png");
-    }
-
-    &.download {
-      background-image: url("~@/assets/icon_download.png");
-    }
-
-    &.temperature {
-      background-image: url("~@/assets/icon_temperature.png");
-    }
-
-    &.smoke {
-      background-image: url("~@/assets/icon_smoke.png");
-    }
-
-    &.flooding {
-      background-image: url("~@/assets/icon_flooding.png");
-    }
-
-    &.light {
-      background-image: url("~@/assets/icon_light.png");
-    }
-  }
-
-  &__title {
-    color: $info--dark;
-  }
-
-  &__list {
-    color: $blue;
-    font-size: 18px;
-  }
-}
-</style>

+ 11 - 11
src/views/device/detail/components/external/ReceivingCard/ReceivingCardInfo.vue

@@ -12,25 +12,25 @@
     </div>
     <div class="l-flex--row c-info__block">
       <div class="l-flex--row l-flex__fill c-info__item">
-        <div class="l-flex__none c-info__title">屏体型号</div>
+        <div class="l-flex__none c-info__title">设备型号</div>
         <auto-text
           class="l-flex__fill c-info__value"
           :text="screen"
         />
       </div>
       <div class="l-flex--row l-flex__fill c-info__item">
-        <div class="l-flex__none c-info__title">是否支持卡温度监测</div>
-        <div class="l-flex__fill c-info__value">{{ temperature }}</div>
+        <div class="l-flex__none c-info__title">设备类型</div>
+        <div class="l-flex__fill c-info__value">{{ type }}</div>
       </div>
     </div>
     <div class="l-flex--row c-info__block">
       <div class="l-flex--row l-flex__fill c-info__item">
-        <div class="l-flex__none c-info__title">是否支持卡电压监测</div>
-        <div class="l-flex__fill c-info__value">{{ voltage }}</div>
+        <div class="l-flex__none c-info__title">支持温度监测</div>
+        <div class="l-flex__fill c-info__value">{{ temperature }}</div>
       </div>
       <div class="l-flex--row l-flex__fill c-info__item">
-        <div class="l-flex__none c-info__title">是否为异步盒</div>
-        <div class="l-flex__fill c-info__value">{{ async }}</div>
+        <div class="l-flex__none c-info__title">支持电压监测</div>
+        <div class="l-flex__fill c-info__value">{{ voltage }}</div>
       </div>
     </div>
   </div>
@@ -47,7 +47,7 @@ export default {
   },
   computed: {
     manufacturer () {
-      return this.info?.manufacturer
+      return this.info?.manufacturerName
     },
     topology () {
       return this.info?.topology?.version ?? '未上传'
@@ -55,14 +55,14 @@ export default {
     screen () {
       return this.info?.screen
     },
+    type () {
+      return this.info ? this.info.async ? '异步盒' : '非异步盒' : ''
+    },
     temperature () {
       return this.info ? this.info.temperature ? '是' : '否' : ''
     },
     voltage () {
       return this.info ? this.info.voltage ? '是' : '否' : ''
-    },
-    async () {
-      return this.info ? this.info.async ? '是' : '否' : ''
     }
   }
 }

+ 39 - 32
src/views/device/detail/components/external/ReceivingCard/ReceivingCardInfoEdit.vue

@@ -39,7 +39,7 @@
       </div>
       <div class="l-flex--row c-info__block">
         <div class="l-flex--row l-flex__fill c-info__item">
-          <div class="l-flex__none c-info__title">屏体型号</div>
+          <div class="l-flex__none c-info__title">设备型号</div>
           <edit-input
             v-model.trim="defaults.screen"
             class="l-flex__fill c-info__value"
@@ -48,37 +48,37 @@
           />
         </div>
         <div class="l-flex--row l-flex__fill c-info__item">
-          <div class="l-flex__none c-info__title">温度监测</div>
+          <div class="l-flex__none c-info__title">异步盒</div>
           <div class="l-flex__fill">
             <el-switch
-              v-model="defaults.temperature"
+              v-model="defaults.async"
               active-color="#13ce66"
               inactive-color="#ff4949"
-              @change="onChangeProp('temperature')"
+              @change="onChangeProp('async')"
             />
           </div>
         </div>
       </div>
       <div class="l-flex--row c-info__block">
         <div class="l-flex--row l-flex__fill c-info__item">
-          <div class="l-flex__none c-info__title">电压监测</div>
+          <div class="l-flex__none c-info__title">支持温度监测</div>
           <div class="l-flex__fill">
             <el-switch
-              v-model="defaults.voltage"
+              v-model="defaults.temperature"
               active-color="#13ce66"
               inactive-color="#ff4949"
-              @change="onChangeProp('voltage')"
+              @change="onChangeProp('temperature')"
             />
           </div>
         </div>
         <div class="l-flex--row l-flex__fill c-info__item">
-          <div class="l-flex__none c-info__title">异步盒</div>
+          <div class="l-flex__none c-info__title">支持电压监测</div>
           <div class="l-flex__fill">
             <el-switch
-              v-model="defaults.async"
+              v-model="defaults.voltage"
               active-color="#13ce66"
               inactive-color="#ff4949"
-              @change="onChangeProp('async')"
+              @change="onChangeProp('voltage')"
             />
           </div>
         </div>
@@ -90,7 +90,7 @@
         @click="onAdd"
       >
         <i class="o-button__icon el-icon-circle-plus-outline" />
-        新增
+        新增接收卡
       </button>
       <confirm-dialog
         ref="addDialog"
@@ -131,7 +131,7 @@
               :value="manufacturer.value"
             />
           </el-select>
-          <span class="c-grid-form__label">型号:</span>
+          <span class="c-grid-form__label required">设备型号:</span>
           <el-input
             v-model.trim="receivingCard.screen"
             maxlength="50"
@@ -145,14 +145,14 @@
               active-color="#13ce66"
               inactive-color="#ff4949"
             />
-            <span class="c-sibling-item far">温度监测:</span>
+            <span class="c-sibling-item far">支持温度监测:</span>
             <el-switch
               v-model="receivingCard.temperature"
               class="c-sibling-item"
               active-color="#13ce66"
               inactive-color="#ff4949"
             />
-            <span class="c-sibling-item far">电压监测:</span>
+            <span class="c-sibling-item far">支持电压监测:</span>
             <el-switch
               v-model="receivingCard.voltage"
               class="c-sibling-item"
@@ -162,22 +162,22 @@
           </div>
         </div>
       </confirm-dialog>
-      <el-dialog
-        class="c-progress"
-        :visible.sync="showProgress"
-        width="50%"
-        :close-on-click-modal="false"
-        :close-on-press-escape="false"
-        :show-close="false"
-      >
-        <el-progress
-          :percentage="progress"
-          :stroke-width="24"
-          text-inside
-          status="success"
-        />
-      </el-dialog>
     </template>
+    <el-dialog
+      class="c-progress"
+      :visible.sync="showProgress"
+      width="50%"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      :show-close="false"
+    >
+      <el-progress
+        :percentage="progress"
+        :stroke-width="24"
+        text-inside
+        status="success"
+      />
+    </el-dialog>
   </div>
 </template>
 
@@ -224,9 +224,9 @@ export default {
         this.defaults = {
           manufacturerKey: this.info.manufacturerKey,
           screen: this.info.screen,
+          async: this.info.async,
           temperature: this.info.temperature,
           voltage: this.info.voltage,
-          async: this.info.async,
           topology: this.info.topology?.version ?? '未上传'
         }
         if (!this.manufacturers.loaded) {
@@ -253,9 +253,9 @@ export default {
         manufacturerKey: '',
         topologyFile: null,
         screen: '',
+        async: false,
         temperature: false,
-        voltage: false,
-        async: false
+        voltage: false
       }
       this.$refs.addDialog.show()
     },
@@ -274,6 +274,13 @@ export default {
         })
         return
       }
+      if (!this.receivingCard.screen) {
+        this.$message({
+          type: 'warning',
+          message: '请填写设备型号'
+        })
+        return
+      }
       const formData = new FormData()
       formData.append('deviceId', this.$route.params.id)
       formData.append('manufacturerKey', this.receivingCard.manufacturerKey)

+ 12 - 2
src/views/device/detail/components/external/ReceivingCard/index.vue

@@ -8,7 +8,7 @@
         v-if="error"
         @retry="getDefaults"
       />
-      <template v-else>
+      <template v-if="canEdit">
         <tabs
           :items="tabs"
           :active.sync="active"
@@ -22,6 +22,13 @@
           />
         </keep-alive>
       </template>
+      <template v-else-if="!info">
+        <div class="l-flex__fill l-flex--row center u-color--info">
+          <div class="l-flex--col center">
+            <div class="has-padding">未绑定接收卡,请联系管理员</div>
+          </div>
+        </div>
+      </template>
     </template>
   </div>
 </template>
@@ -70,12 +77,15 @@ export default {
     }
   },
   computed: {
+    canEdit () {
+      return this.accessSet.has(this.Access.MANAGE_DEVICES)
+    },
     activeComponent () {
       switch (this.active) {
         case 'topology':
           return 'ReceivingCardTopology'
         case 'info':
-          return this.accessSet.has(this.Access.MANAGE_DEVICES) ? 'ReceivingCardInfoEdit' : 'ReceivingCardInfo'
+          return this.canEdit ? 'ReceivingCardInfoEdit' : 'ReceivingCardInfo'
         default:
           return null
       }

+ 54 - 11
src/views/device/detail/components/DeviceSensor/Sensor.vue → src/views/device/detail/components/external/Sensors/Sensor.vue

@@ -1,14 +1,14 @@
 <template>
-  <div class="l-flex--col c-runtime o-senser">
+  <div class="l-flex--col o-sensor">
     <div class="l-flex--row l-flex__none">
       <i
-        class="l-flex__none c-runtime__icon"
+        class="l-flex__none o-sensor__icon"
         :class="type"
       />
-      <span class="l-flex__fill c-runtime__title u-ellipsis">{{ title }}</span>
+      <span class="l-flex__fill o-sensor__title u-ellipsis">{{ title }}</span>
       <i
         v-if="enough"
-        class="l-flex__none c-runtime__list el-icon-s-operation u-pointer"
+        class="l-flex__none o-sensor__list el-icon-s-operation u-pointer"
         @click="showList"
       />
     </div>
@@ -20,7 +20,7 @@
         {{ tip }}
         <div
           v-if="sensor"
-          class="o-senser__current"
+          class="o-sensor__current"
         >
           {{ sensor }}
         </div>
@@ -29,11 +29,11 @@
     <div
       v-for="item in more"
       :key="item.key"
-      class="l-flex__none l-flex--row o-senser__more"
+      class="l-flex__none l-flex--row o-sensor__more"
     >
       <div class="l-flex__none">{{ item.time }}</div>
-      <div class="l-flex__none o-senser__name">{{ item.name }}</div>
-      <div class="l-flex__fill o-senser__value">{{ item.value }}</div>
+      <div class="l-flex__none o-sensor__name">{{ item.name }}</div>
+      <div class="l-flex__fill o-sensor__value">{{ item.value }}</div>
     </div>
     <el-dialog
       :title="listTitle"
@@ -70,10 +70,10 @@ import {
   Type,
   addListener,
   removeListener
-} from '../../monitor'
+} from '@/views/device/detail/monitor'
 
 export default {
-  name: 'DeviceSensor',
+  name: 'Sensor',
   props: {
     type: {
       type: String,
@@ -156,7 +156,50 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-.o-senser {
+.o-sensor {
+  height: 180px;
+  padding: 12px 16px 16px;
+  color: $info;
+  font-size: 14px;
+  line-height: 1;
+  border: 1px solid $info;
+  border-radius: 2px;
+
+  &__icon {
+    display: inline-block;
+    width: 32px;
+    height: 32px;
+    margin-right: 8px;
+    background-position: 0 0;
+    background-size: 100% 100%;
+    background-repeat: no-repeat;
+
+    &.temperature {
+      background-image: url("~@/assets/icon_temperature.png");
+    }
+
+    &.smoke {
+      background-image: url("~@/assets/icon_smoke.png");
+    }
+
+    &.flooding {
+      background-image: url("~@/assets/icon_flooding.png");
+    }
+
+    &.light {
+      background-image: url("~@/assets/icon_light.png");
+    }
+  }
+
+  &__title {
+    color: $info--dark;
+  }
+
+  &__list {
+    color: $blue;
+    font-size: 18px;
+  }
+
   &__current {
     margin-top: 6px;
     color: $info;

+ 11 - 5
src/views/device/detail/components/DeviceSensor/index.vue → src/views/device/detail/components/external/Sensors/index.vue

@@ -1,24 +1,20 @@
 <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"
@@ -27,12 +23,22 @@
 </template>
 
 <script>
+import {
+  startSensor,
+  stopSensor
+} from '@/views/device/detail/monitor'
 import Sensor from './Sensor'
 
 export default {
-  name: 'DeviceSensor',
+  name: 'Sensors',
   components: {
     Sensor
+  },
+  activated () {
+    startSensor()
+  },
+  deactivated () {
+    stopSensor()
   }
 }
 </script>

+ 173 - 0
src/views/device/detail/components/external/Transmitter/index.vue

@@ -0,0 +1,173 @@
+<template>
+  <div
+    v-loading="loading"
+    class="l-flex--col"
+  >
+    <template v-if="!loading">
+      <warning
+        v-if="error"
+        @retry="getDefaults"
+      />
+      <template v-if="info">
+        <div class="l-flex--row has-bottom-padding u-bold">
+          <span class="c-sibling-item">发送控制设备信息</span>
+          <!-- <span
+            v-if="canEdit"
+            class="c-sibling-item c-info__edit u-pointer"
+            @click="onBind"
+          >
+            <i class="el-icon-edit" />
+            重绑
+          </span> -->
+        </div>
+        <div class="l-flex__fill c-info large">
+          <div class="l-flex--row c-info__block">
+            <div class="l-flex--row l-flex__fill c-info__item">
+              <div class="l-flex__none c-info__title">设备名称</div>
+              <auto-text
+                class="l-flex__fill c-info__value"
+                :text="info.name"
+              />
+            </div>
+            <div class="l-flex--row l-flex__fill c-info__item">
+              <div class="l-flex__none c-info__title">厂家名称</div>
+              <div class="l-flex__fill c-info__value">{{ info.manufacturerName }}</div>
+            </div>
+          </div>
+          <div class="l-flex--row c-info__block">
+            <div class="l-flex--row l-flex__fill c-info__item">
+              <div class="l-flex__none c-info__title">设备型号</div>
+              <auto-text
+                class="l-flex__fill c-info__value"
+                :text="info.type"
+              />
+            </div>
+            <div class="l-flex--row l-flex__fill c-info__item">
+              <div class="l-flex__none c-info__title">设备类型</div>
+              <div class="l-flex__fill c-info__value">{{ type }}</div>
+            </div>
+          </div>
+          <div class="l-flex--row c-info__block">
+            <div class="l-flex--row l-flex__fill c-info__item">
+              <div class="l-flex__none c-info__title">支持设备监测</div>
+              <div class="l-flex__fill c-info__value">{{ supportDetection }}</div>
+            </div>
+            <div class="l-flex--row l-flex__fill c-info__item">
+              <div class="l-flex__none c-info__title">支持内容保护</div>
+              <div class="l-flex__fill c-info__value">{{ supportContentProtection }}</div>
+            </div>
+          </div>
+        </div>
+      </template>
+      <template v-else>
+        <div class="l-flex__fill l-flex--row center u-color--info">
+          <div class="l-flex--col center">
+            <div class="has-padding">未绑定发送控制设备,请联系管理员</div>
+            <button
+              v-if="canEdit"
+              class="o-button"
+              @click="onBind"
+            >
+              绑定设备
+            </button>
+          </div>
+        </div>
+      </template>
+    </template>
+    <table-dialog
+      ref="chooseDialog"
+      title="发送控制设备选择"
+      :schema="schema"
+      @choosen="onChoose"
+    />
+  </div>
+</template>
+
+<script>
+import {
+  getDeviceSendingCard,
+  // getSendingCardManufacturers,
+  getSendingCards,
+  bindSendingCard
+} from '@/api/external'
+import { Transmitter } from '@/constant'
+import { createListOptions } from '@/utils'
+
+export default {
+  name: 'Transmitter',
+  props: {
+    device: {
+      type: Object,
+      default () {
+        return null
+      }
+    }
+  },
+  data () {
+    return {
+      loading: false,
+      error: false,
+      info: null,
+      options: createListOptions({ manufacturerKey: void 0 }),
+      schema: {
+        // condition: { manufacturerKey: void 0 },
+        // filters: [
+        //   { key: 'manufacturerKey', type: 'select', placeholder: '全部厂家', value: 'manufacturerKey', label: 'manufacturerName', invoke: getSendingCardManufacturers }
+        // ],
+        list: getSendingCards,
+        cols: [
+          { prop: 'manufacturerName', label: '厂商' },
+          { prop: 'name', label: '设备名称' },
+          { prop: 'type', label: '设备型号' }
+        ]
+      }
+    }
+  },
+  computed: {
+    canEdit () {
+      return this.accessSet.has(this.Access.MANAGE_DEVICES)
+    },
+    type () {
+      return this.info && this.info.flag & Transmitter.IS_ASYNC ? '异步盒' : '非异步盒'
+    },
+    supportDetection () {
+      return this.info && this.info.flag & Transmitter.SUPPORT_DETECTION ? '是' : '否'
+    },
+    supportContentProtection () {
+      return this.info && this.info.flag & Transmitter.SUPPORT_CONTENT_PROTECTION ? '是' : '否'
+    }
+  },
+  activated () {
+    !this.info && this.getDeviceSendingCard()
+  },
+  methods: {
+    getDeviceSendingCard () {
+      if (this.loading) {
+        return
+      }
+      this.info = null
+      this.loading = true
+      this.error = false
+      getDeviceSendingCard(this.device.id).then(
+        data => {
+          this.info = data
+        },
+        () => {
+          this.error = true
+        }
+      ).finally(() => {
+        this.loading = false
+      })
+    },
+    onBind () {
+      this.$refs.chooseDialog.show()
+    },
+    onChoose ({ value, done }) {
+      bindSendingCard(this.device.id, value.id).then(() => {
+        done()
+        this.getDeviceSendingCard()
+      })
+    }
+  }
+}
+</script>

+ 16 - 26
src/views/device/detail/index.vue

@@ -41,7 +41,8 @@
     >
       <keep-alive>
         <component
-          :is="activeComponent"
+          :is="active"
+          :key="active"
           class="l-flex__fill c-detail__wrapper has-padding u-overflow-y--auto"
           :device="device"
           :online="isOnline"
@@ -60,34 +61,39 @@ import {
   addListener
 } from './monitor'
 import DeviceInfo from './components/DeviceInfo'
-import DeviceStatus from './components/DeviceStatus'
+import DeviceRuntime from './components/DeviceRuntime'
 import DeviceAlarm from './components/DeviceAlarm'
 import DeviceInvoke from './components/DeviceInvoke'
+import Sensors from './components/external/Sensors'
+import Transmitter from './components/external/Transmitter'
 import ReceivingCard from './components/external/ReceivingCard'
 
 export default {
   name: 'DeviceDetail',
   components: {
     DeviceInfo,
-    DeviceStatus,
+    DeviceRuntime,
     DeviceAlarm,
     DeviceInvoke,
+    Sensors,
+    Transmitter,
     ReceivingCard
   },
   data () {
-    const canEdit = this.accessSet.has(this.Access.MANAGE_DEVICES) || this.accessSet.has(this.Access.MANAGE_DEVICE)
     return {
       loading: true,
       device: null,
       isActivated: false,
       isOnline: false,
-      active: 'info',
+      active: 'DeviceInfo',
       tabs: [
-        { key: 'info', label: '设备信息' },
-        { key: 'status', label: '运行状态' },
-        { key: 'alarm', label: '设备告警' },
-        canEdit ? { key: 'invoke', label: '设备操控' } : null,
-        { key: 'screen', label: '屏体状态监测' }
+        { key: 'DeviceInfo', label: '设备信息' },
+        { key: 'DeviceRuntime', label: '运行状态' },
+        { key: 'DeviceAlarm', label: '设备告警' },
+        this.accessSet.has(this.Access.VIEW_SENSORS) ? { key: 'Sensors', label: '传感器' } : null,
+        { key: 'Transmitter', label: '发送控制设备' },
+        { key: 'ReceivingCard', label: '接收卡' },
+        this.accessSet.has(this.Access.MANAGE_DEVICES) || this.accessSet.has(this.Access.MANAGE_DEVICE) ? { key: 'DeviceInvoke', label: '设备操控' } : null
       ].filter(val => val)
     }
   },
@@ -115,22 +121,6 @@ export default {
     },
     deviceName () {
       return this.device?.name
-    },
-    activeComponent () {
-      switch (this.active) {
-        case 'info':
-          return 'DeviceInfo'
-        case 'status':
-          return 'DeviceStatus'
-        case 'alarm':
-          return 'DeviceAlarm'
-        case 'invoke':
-          return 'DeviceInvoke'
-        case 'screen':
-          return 'ReceivingCard'
-        default:
-          return ''
-      }
     }
   },
   watch: {

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

@@ -252,7 +252,7 @@
     <el-dialog
       :visible.sync="choosing"
       title="节目选择"
-      custom-class="c-dialog hidden-footer"
+      custom-class="c-dialog"
     >
       <event-target-choose
         v-if="choosing"

+ 107 - 20
src/views/external/transmitter/index.vue

@@ -53,6 +53,37 @@
         align="center"
         show-overflow-tooltip
       />
+      <el-table-column
+        label="设备类型"
+        align="center"
+      >
+        <template v-slot="scope">
+          {{ scope.row.async ? '异步盒' : '非异步盒' }}
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="特性"
+        align="center"
+      >
+        <template v-slot="scope">
+          <div class="c-tags">
+            <el-tag
+              v-if="scope.row.detection"
+              type="success"
+              size="medium"
+            >
+              设备监测
+            </el-tag>
+            <el-tag
+              v-if="scope.row.contentProtection"
+              type="success"
+              size="medium"
+            >
+              内容保护
+            </el-tag>
+          </div>
+        </template>
+      </el-table-column>
       <el-table-column
         prop="remark"
         label="备注"
@@ -112,6 +143,29 @@
           maxlength="50"
           show-word-limit
         />
+        <span class="c-grid-form__label">异步盒:</span>
+        <div class="l-flex--row">
+          <el-switch
+            v-model="flag.async"
+            class="c-sibling-item"
+            active-color="#13ce66"
+            inactive-color="#ff4949"
+          />
+          <span class="c-sibling-item far">支持设备监测:</span>
+          <el-switch
+            v-model="flag.detection"
+            class="c-sibling-item"
+            active-color="#13ce66"
+            inactive-color="#ff4949"
+          />
+          <span class="c-sibling-item far">支持内容保护:</span>
+          <el-switch
+            v-model="flag.contentProtection"
+            class="c-sibling-item"
+            active-color="#13ce66"
+            inactive-color="#ff4949"
+          />
+        </div>
         <span class="c-grid-form__label">备注:</span>
         <el-input
           v-model="transmitter.remark"
@@ -127,16 +181,17 @@
 
 <script>
 import {
-  getSendCardManufacturers,
-  getSendCards,
-  addSendCard,
-  updateSendCard,
-  deleteSendCard
+  getSendingCardManufacturers,
+  getSendingCards,
+  addSendingCard,
+  updateSendingCard,
+  deleteSendingCard
 } from '@/api/external'
+import { Transmitter } from '@/constant'
 import { createListOptions } from '@/utils'
 
 export default {
-  name: 'Transmitter',
+  name: 'TransmitterList',
   data () {
     return {
       options: createListOptions({ manufacturerKey: void 0 }),
@@ -150,7 +205,8 @@ export default {
         { value: 'sdk', label: 'SDK' }
       ],
       show: false,
-      transmitter: {}
+      transmitter: {},
+      flag: {}
     }
   },
   computed: {
@@ -183,8 +239,14 @@ export default {
       const options = this.options
       options.error = false
       options.loading = true
-      return getSendCards(options.params).then(({ data, totalCount }) => {
-        options.list = data
+      return getSendingCards(options.params).then(({ data, totalCount }) => {
+        options.list = data.map(transmitter => {
+          const { flag } = transmitter
+          transmitter.async = !!(flag & Transmitter.IS_ASYNC)
+          transmitter.detection = !!(flag & Transmitter.SUPPORT_DETECTION)
+          transmitter.contentProtection = !!(flag & Transmitter.SUPPORT_CONTENT_PROTECTION)
+          return transmitter
+        })
         options.totalCount = totalCount
       }, () => {
         options.error = true
@@ -196,7 +258,7 @@ export default {
     getManufacturers (visible) {
       if (visible && !this.manufacturers.loading && !this.manufacturers.loaded) {
         this.manufacturers.loading = true
-        getSendCardManufacturers().then(({ data }) => {
+        getSendingCardManufacturers().then(({ data }) => {
           this.manufacturers.list = data.map(({ manufacturerKey, manufacturerName }) => {
             return { value: manufacturerKey, label: manufacturerName }
           })
@@ -212,14 +274,23 @@ export default {
         manufacturerKey: '',
         name: '',
         type: '',
-        flag: 0,
         remark: ''
       }
+      this.flag = {
+        async: false,
+        detection: false,
+        contentProtection: false
+      }
       this.$refs.editDialog.show()
     },
     onEdit ({ id, manufacturerKey, manufacturerName, name, type, flag, remark }) {
       this.manufacturerName = manufacturerName
-      this.transmitter = { id, manufacturerKey, name, type, flag, remark }
+      this.transmitter = { id, manufacturerKey, name, type, remark }
+      this.flag = {
+        async: !!(flag & Transmitter.IS_ASYNC),
+        detection: !!(flag & Transmitter.SUPPORT_DETECTION),
+        contentProtection: !!(flag & Transmitter.SUPPORT_CONTENT_PROTECTION)
+      }
       this.$refs.editDialog.show()
     },
     onConfirm (done) {
@@ -244,14 +315,30 @@ export default {
         })
         return
       }
-      if (this.transmitter.id) {
-        this.onConfirmEdit(done)
+      const { async: isAsync, detection, contentProtection } = this.flag
+      console.log(this.flag)
+      let flag = 0
+      if (isAsync) {
+        flag |= Transmitter.IS_ASYNC
+      }
+      if (detection) {
+        flag |= Transmitter.SUPPORT_DETECTION
+      }
+      if (contentProtection) {
+        flag |= Transmitter.SUPPORT_CONTENT_PROTECTION
+      }
+      const transmitter = {
+        ...this.transmitter,
+        flag
+      }
+      if (transmitter.id) {
+        this.onConfirmEdit(transmitter, done)
       } else {
-        this.onConfirmAdd(done)
+        this.onConfirmAdd(transmitter, done)
       }
     },
-    onConfirmAdd (done) {
-      addSendCard(this.transmitter).then(() => {
+    onConfirmAdd (transmitter, done) {
+      addSendingCard(transmitter).then(() => {
         done()
         const params = this.options.params
         if (params.manufacturerKey && params.manufacturerKey !== this.transmitter.manufacturerKey) {
@@ -260,14 +347,14 @@ export default {
         this.search()
       })
     },
-    onConfirmEdit (done) {
-      updateSendCard(this.transmitter).then(() => {
+    onConfirmEdit (transmitter, done) {
+      updateSendingCard(transmitter).then(() => {
         done()
         this.getList()
       })
     },
     onDel (item) {
-      deleteSendCard(item).then(() => {
+      deleteSendingCard(item).then(() => {
         const options = this.options
         if (options.list.length === 1 && options.params.pageNum > 1) {
           options.params.pageNum -= 1

+ 1 - 1
src/views/schedule/deploy/complex.vue → src/views/schedule/deploy/index.vue

@@ -136,7 +136,7 @@
     <el-dialog
       :visible.sync="choosingEventTarget"
       title="节目选择"
-      custom-class="c-dialog hidden-footer"
+      custom-class="c-dialog"
       :close-on-click-modal="false"
     >
       <event-target-choose