Эх сурвалжийг харах

refactor(camera): operation

Casper Dai 3 жил өмнө
parent
commit
4e9b63a198

+ 0 - 9
src/api/camera.js

@@ -104,12 +104,3 @@ export function detail (stream) {
     method: 'GET'
   })
 }
-
-export function getBindCameras (query) {
-  const { deviceId, ...params } = query
-  return request({
-    url: `device/bind/thirdPartyCamera/${deviceId}`,
-    method: 'GET',
-    params
-  })
-}

+ 24 - 3
src/api/external.js

@@ -5,7 +5,7 @@ import {
   del,
   messageSend
 } from './base'
-import { ThirdPartDevice } from '@/constant'
+import { ThirdPartyDevice } from '@/constant'
 
 export function bind (deviceId, deviceType, thirdPartyDeviceId) {
   return messageSend({
@@ -33,7 +33,19 @@ export function getThirdPartyDeviceTypes () {
   })
 }
 
-export function getThirdPartyDevices (deviceId) {
+export function getThirdPartyDevices (query) {
+  const { pageNum: pageIndex, pageSize, ...params } = query
+  return request({
+    url: '/device/bind/thirdPartyDevice',
+    method: 'GET',
+    params: {
+      pageIndex, pageSize,
+      ...params
+    }
+  })
+}
+
+export function getBoundThirdPartyDevices (deviceId) {
   return request({
     url: `/device/bind/${deviceId}`,
     method: 'GET'
@@ -140,7 +152,7 @@ export function getSendingCard (deviceId) {
 }
 
 export function bindSendingCard (deviceId, thirdPartyDeviceId) {
-  return bind(deviceId, ThirdPartDevice.SENDING_CARD, thirdPartyDeviceId)
+  return bind(deviceId, ThirdPartyDevice.SENDING_CARD, thirdPartyDeviceId)
 }
 
 export function getContentProtection (deviceId, options) {
@@ -176,3 +188,12 @@ export function updateContentProtection (deviceId, protect) {
     }
   }, protect ? '开启' : '关闭')
 }
+
+export function getCameras (query) {
+  const { deviceId, ...params } = query
+  return request({
+    url: `/device/bind/thirdPartyCamera/${deviceId}`,
+    method: 'GET',
+    params
+  })
+}

+ 0 - 91
src/components/dialog/BindDeviceDialog/index.vue

@@ -1,91 +0,0 @@
-<template>
-  <table-dialog
-    ref="tableDialog"
-    title="绑定设备"
-    size="medium"
-    :schema="schema"
-    append-to-body
-    v-bind="$attrs"
-    @choosen="onChoosen"
-  />
-</template>
-
-<script>
-import { getCameras } from '@/api/camera'
-import { ThirdPartDevice } from '@/constant'
-const cameraType2thirdPartDevice = {
-  1: ThirdPartDevice.LED_CAMERA,
-  2: ThirdPartDevice.TRAFFIC_CAMERA
-}
-export default {
-  name: 'BindDeviceDialog',
-  props: {
-    ratio: {
-      type: String,
-      default: ''
-    }
-  },
-  data () {
-    return {
-      device: {},
-      schema: {
-        list: this.getCameras,
-        condition: {},
-        filters: [
-          { key: 'name', type: 'search', placeholder: '设备名称' }
-        ],
-        cols: [
-          { prop: 'name', label: '设备名称' },
-          {
-            label: '产品',
-            render ({ empty, cameraType }) {
-              return empty
-                ? null
-                : {
-                  0: '通用摄像头',
-                  1: 'LED屏画面监测摄像头',
-                  2: '人流量监测摄像头'
-                }[cameraType] || ''
-            }
-          },
-          {
-            label: '状态',
-            type: 'tag',
-            width: 100,
-            render ({ empty, onlineStatus }) {
-              return empty
-                ? null
-                : {
-                  type: onlineStatus === 1 ? 'success' : 'danger',
-                  label: onlineStatus === 1 ? '在线' : '离线'
-                }
-            }
-          }
-        ]
-      }
-    }
-  },
-  methods: {
-    show (device) {
-      device && (this.device = device)
-      this.$refs.tableDialog.show()
-    },
-    onChoosen ({ value, done }) {
-      this.$emit('choosen', {
-        value: this.getValue(
-          this.$refs.tableDialog.getTable().getCondition().type,
-          value
-        ),
-        done
-      })
-    },
-    getCameras,
-    getValue (type, item) {
-      switch (type) {
-        default:
-          return { ...item, deviceType: cameraType2thirdPartDevice[item.cameraType] }
-      }
-    }
-  }
-}
-</script>

+ 276 - 291
src/views/external/camera/components/Detail.vue → src/components/external/camera/CameraDetail/index.vue

@@ -1,126 +1,122 @@
 <template>
   <div
     v-loading="videoLoading"
-    class="detail"
+    class="c-detail"
   >
-    <i
-      class="el-icon-close closeDetail"
-      @click="close"
-    />
-    <div class="detail-top">
-      <div class="detail_text">
-        {{ camera.name }}人流量监测
-      </div>
-      <div class="detail_border" />
+    <div class="c-detail-header">
+      <div class="c-detail-header__name u-ellipsis"> {{ camera.name }}</div>
+      <i
+        class="c-detail-header__close el-icon-close u-pointer"
+        @click="close"
+      />
     </div>
     <video
       ref="player"
-      class="video"
+      class="o-simple-video"
       muted
       autoplay
+      controls
       :poster="poster"
     />
-    <div class="detail-buttom">
-      <el-row :gutter="16">
-        <el-col :span="24">
-          <div class="video-controls l-flex--row">
+    <div class="c-detail-footer has-padding">
+      <div class="l-flex--row c-sibling-item--v c-video-controls">
+        <div
+          v-show="settingBshow"
+          class="settingB"
+        >
+          <div v-show="settingTab">
             <div
-              v-show="settingBshow"
-              class="settingB"
+              v-for="(item, index) in setData"
+              :key="index"
+              class="settingT"
+              @click="setClick(index)"
             >
-              <div v-show="settingTab">
-                <div
-                  v-for="(item, index) in setData"
-                  :key="index"
-                  class="settingT"
-                  @click="setClick(index)"
-                >
-                  {{ item }} <i class="el-icon-arrow-right" />
-                </div>
-              </div>
-              <div v-show="!settingTab">
-                <div
-                  class="settingT settingsub"
-                  @click="setBack"
-                >
-                  <i class="el-icon-arrow-left" />{{ setData[settingActive] }}
-                </div>
-                <div class="settingHeight">
-                  <div
-                    v-for="(item, index) in settingData[settingActive]"
-                    :key="index"
-                    class="settingT settingsub"
-                    @click="settingClick(index)"
-                  >
-                    <i :class="{ 'el-icon-check': item.active }" />
-                    {{ item.value }}
-                  </div>
-                </div>
-              </div>
+              {{ item }} <i class="el-icon-arrow-right" />
             </div>
-            <div class="l-flex__auto">
-              <i
-                class="status u-pointer"
-                :class="{ stop: !playing }"
-                @click="onPlayOrPause"
-              />
-            </div>
-            <i
-              class="setting u-pointer"
-              @click="onSettings"
-            />
-            <i
-              class="refresh u-pointer"
-              @click="onRefresh"
-            />
           </div>
-        </el-col>
-        <el-col :span="8">
-          <div class="o-detail">
-            <div>
-              <span class="o-detail_title">设备名称:</span><span>{{ camera.name }}</span>
-            </div>
-            <div>
-              <span class="o-detail_title">ID:</span><span>{{ camera.identifier }}</span>
-            </div>
-            <div>
-              <span class="o-detail_title">用户名:</span><span>{{ camera.username }}</span>
+          <div v-show="!settingTab">
+            <div
+              class="settingT settingsub"
+              @click="setBack"
+            >
+              <i class="el-icon-arrow-left" />{{ setData[settingActive] }}
             </div>
-            <div>
-              <span class="o-detail_title">备注:</span><span>{{ camera.remark }}</span>
+            <div class="settingHeight">
+              <div
+                v-for="(item, index) in settingData[settingActive]"
+                :key="index"
+                class="settingT settingsub"
+                @click="settingClick(index)"
+              >
+                <i :class="{ 'el-icon-check': item.active }" />
+                {{ item.value }}
+              </div>
             </div>
           </div>
-        </el-col>
-        <el-col :span="16">
-          <div class="o-detail">
-            <div
-              id="main"
-              style="width: 100%; height: 200px"
-            />
-            <div class="choosedate">
-              <div class="timeBtn">
-                <span
-                  :class="{ active: active === 'hour' }"
-                  @click="timeType('hour')"
-                >1小时</span>
-                <span
-                  :class="{ active: active === 'day' }"
-                  @click="timeType('day')"
-                >1天</span>
-              </div>
-              <el-date-picker
-                v-model="datevalue"
-                type="datetime"
-                placeholder="选择日期时间"
-                prefix-icon="el-icon-date"
-                format="yyyy-MM-dd HH:mm"
-                value-format="yyyy-MM-dd HH:mm:ss"
-                @change="onDateTimeChange()"
-              />
+        </div>
+        <div class="l-flex__auto">
+          <i
+            class="status u-pointer"
+            :class="{ stop: !playing }"
+            @click="onPlayOrPause"
+          />
+        </div>
+        <i
+          class="setting u-pointer"
+          @click="onSettings"
+        />
+        <i
+          class="refresh u-pointer"
+          @click="onRefresh"
+        />
+      </div>
+      <div class="l-flex c-sibling-item--v far">
+        <div class="l-flex__none c-sibling-item c-block c-camera-info">
+          <div class="c-sibling-item--v">
+            <span class="c-camera-info__title">ID:</span><span>{{ camera.identifier }}</span>
+          </div>
+          <div class="c-sibling-item--v">
+            <span class="c-camera-info__title">用户名:</span><span>{{ camera.username }}</span>
+          </div>
+          <div class="c-sibling-item--v">
+            <span class="c-camera-info__title">备注:</span><span>{{ camera.remark }}</span>
+          </div>
+        </div>
+        <div class="l-flex__auto c-sibling-item far c-block">
+          <div
+            id="main"
+            class="o-canvas"
+          />
+          <div class="c-choose-date">
+            <div class="c-sibling-item c-choose-date__time u-pointer">
+              <span
+                class="c-choose-date__item"
+                :class="{ active: active === 'hour' }"
+                @click="onTimeTypeChanged('hour')"
+              >
+                1小时
+              </span>
+              <span
+                class="c-choose-date__item"
+                :class="{ active: active === 'day' }"
+                @click="onTimeTypeChanged('day')"
+              >
+                1天
+              </span>
             </div>
+            <el-date-picker
+              v-model="datevalue"
+              class="c-sibling-item far"
+              type="datetime"
+              placeholder="选择日期时间"
+              prefix-icon="el-icon-date"
+              format="yyyy-MM-dd HH:mm"
+              value-format="yyyy-MM-dd HH:mm:ss"
+              @change="onDateTimeChange()"
+            />
           </div>
-        </el-col>
-      </el-row>
+        </div>
+      </div>
     </div>
   </div>
 </template>
@@ -169,10 +165,10 @@ export default {
     this.getAvailableParam()
   },
   mounted () {
-    this.getflv()
+    this.createPlayer()
     this.getStatistic(
-      this.getStarttime(new Date(), 'now'),
-      this.getStarttime(new Date())
+      this.getStartTime(new Date(), 'now'),
+      this.getStartTime(new Date())
     )
   },
   methods: {
@@ -180,7 +176,7 @@ export default {
       this.destroyPlayer()
       this.$emit('close')
     },
-    getflv () {
+    createPlayer () {
       if (flvjs.isSupported()) {
         // 创建一个flvjs实例
         this.player = flvjs.createPlayer({
@@ -215,7 +211,7 @@ export default {
         frameRate: this.infoData.frameRate.slice(0, -3)
       }).then(() => {
         this.destroyPlayer()
-        this.getflv()
+        this.createPlayer()
       })
     },
     getAvailableParam () {
@@ -341,25 +337,25 @@ export default {
     },
     onRefresh () {
       this.destroyPlayer()
-      this.getflv()
+      this.createPlayer()
     },
-    timeType (type) {
+    onTimeTypeChanged (type) {
       this.active = type
       let startTime
       if (this.datevalue.length) {
         startTime = this.datevalue
       } else {
         this.datevalue = new Date()
-        startTime = this.getStarttime(new Date(), 'now')
+        startTime = this.getStartTime(new Date(), 'now')
       }
-      this.getStatistic(startTime, this.getStarttime(startTime, type))
+      this.getStatistic(startTime, this.getStartTime(startTime, type))
     },
     onDateTimeChange () {
       if (this.datevalue) {
-        this.getStatistic(this.datevalue, this.getStarttime(this.datevalue, this.active))
+        this.getStatistic(this.datevalue, this.getStartTime(this.datevalue, this.active))
       }
     },
-    getStarttime (time, type) {
+    getStartTime (time, type) {
       let onehour = 60 * 60 * 1000
       if (type === 'day') {
         onehour = 60 * 60 * 1000 * 24
@@ -390,7 +386,7 @@ export default {
         this.initEchart()
       })
     },
-    getxdata (data) {
+    getXData (data) {
       const arr = []
       data.forEach(item => {
         const time = item.eventTime.slice(11, 16)
@@ -398,7 +394,7 @@ export default {
       })
       return arr
     },
-    getydata (data) {
+    getYData (data) {
       const arr = []
       data.forEach(item => {
         arr.push(item.insidePeopleNum)
@@ -407,8 +403,8 @@ export default {
     },
     initEchart () {
       const data = this.echartsData.filter(item => item.insidePeopleNum !== 0)
-      const xdata = this.getxdata(data)
-      const ydata = this.getydata(data)
+      const xdata = this.getXData(data)
+      const ydata = this.getYData(data)
       const chartDom = document.getElementById('main')
       const myChart = echarts.init(chartDom)
       myChart.setOption({
@@ -488,217 +484,206 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-.detail {
+$theme-blue: #003e90;
+
+.o-simple-video {
+  width: 100%;
+  height: 100%;
+}
+
+.c-detail {
   width: 100%;
   height: 100%;
   position: relative;
   overflow: hidden;
+}
+
+.c-detail-header {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  padding-top: $spacing;
+  color: #fff;
+  font-size: 24px;
+  text-align: center;
+  background-color: rgba($theme-blue, 0.8);
+  z-index: 9;
 
-  .closeDetail {
+  &::after {
+    content: "";
     position: absolute;
-    top: 5px;
-    right: 20px;
-    color: #000;
-    font-size: 42px;
-    z-index: 3;
-    cursor: pointer;
+    top: 100%;
+    left: 50%;
+    width: 700px;
+    height: 0;
+    border-top: 16px solid rgba($theme-blue, 0.8);
+    border-right: 16px solid transparent;
+    border-bottom: 16px solid transparent;
+    border-left: 16px solid transparent;
+    transform: translateX(-50%);
   }
 
-  .video {
-    width: 100%;
-    height: 100vh;
-    object-fit: contain;
+  &__name {
+    display: inline-block;
+    width: 600px;
   }
 
-  .detail-buttom {
+  &__close {
     position: absolute;
-    left: 0;
-    bottom: 0;
-    width: 100%;
-    padding: 16px;
-
-    .o-detail {
-      padding: 24px 24px 70px;
-      background-color: rgba(0, 62, 144, 0.8);
-      color: #fff;
-      line-height: 36px;
-      height: 238px;
-      position: relative;
-      border-radius: $radius--mini;
-
-      &_title {
-        width: 72px;
-        display: inline-block;
-        border-radius: $radius--mini;
-      }
-
-      .choosedate {
-        position: absolute;
-        right: 20px;
-        top: 10px;
-        .timeBtn {
-          display: inline-block;
-          border-radius: $radius--mini;
-          background: #4478bc;
-          height: 24px;
-          line-height: 24px;
-          margin-right: 16px;
-
-          span {
-            font-size: 14px;
-            background: #4478bc;
-            border-radius: $radius--mini;
-            padding: 0 7px;
-            display: inline-block;
-            cursor: pointer;
-            width: 50px;
-            text-align: center;
-          }
+    top: 50%;
+    right: 0;
+    padding: $spacing;
+    color: #fff;
+    font-size: 42px;
+    transform: translateY(-50%);
+  }
+}
 
-          .active {
-            background: #0096ff;
-          }
-        }
-      }
-    }
+.c-detail-footer {
+  position: absolute;
+  left: 0;
+  bottom: 0;
+  width: 100%;
+}
 
-    .video-controls {
-      background-color: rgba(0, 6, 13, 0.75);
-      height: 48px;
-      border-radius: $radius--mini;
-      margin-bottom: 16px;
-      position: relative;
+.c-block {
+  position: relative;
+  height: 238px;
+  padding: $spacing $spacing 0;
+  color: #fff;
+  line-height: 1;
+  background-color: rgba($theme-blue, 0.8);
+}
 
-      .status {
-        display: inline-block;
-        width: 30px;
-        height: 30px;
-        margin-left: 12px;
-        background-image: url("~@/assets/icon_stop.png");
+.c-camera-info {
+  width: 25%;
 
-        &.stop {
-          background-image: url("~@/assets/icon_start.png");
-        }
-      }
+  &__title {
+    display: inline-block;
+    width: 72px;
+  }
+}
 
-      .setting {
-        display: inline-block;
-        width: 30px;
-        height: 30px;
-        margin-right: 50px;
-        background-image: url("~@/assets/icon_setting.png");
-      }
+.c-video-controls {
+  position: relative;
+  height: 48px;
+  border-radius: $radius--mini;
+  background-color: rgba(0, 6, 13, 0.75);
 
-      .refresh {
-        display: inline-block;
-        width: 30px;
-        height: 30px;
-        margin-right: 38px;
-        background-image: url("~@/assets/icon_refresh.png");
-      }
+  .status {
+    display: inline-block;
+    width: 30px;
+    height: 30px;
+    margin-left: 12px;
+    background-image: url("~@/assets/icon_stop.png");
 
-      .settingB {
-        position: absolute;
-        padding: 10px 0px;
-        background-color: rgba($color: #000000, $alpha: 0.85);
-        right: 134px;
-        transform: translateX(50%);
-        bottom: 48px;
-        color: #fff;
-
-        .settingT {
-          position: relative;
-          padding: 0px 16px;
-          width: 180px;
-          height: 40px;
-          line-height: 40px;
-          cursor: pointer;
-
-          &:hover {
-            background-color: rgba(69, 69, 69, 0.85);
-          }
-
-          i {
-            position: absolute;
-            top: 13px;
-            right: 16px;
-          }
-        }
+    &.stop {
+      background-image: url("~@/assets/icon_start.png");
+    }
+  }
 
-        .settingHeight {
-          max-height: 250px;
-          overflow: auto;
-        }
+  .setting {
+    display: inline-block;
+    width: 30px;
+    height: 30px;
+    margin-right: 50px;
+    background-image: url("~@/assets/icon_setting.png");
+  }
 
-        .settingsub {
-          width: 140px;
-          padding-left: 42px;
-          i {
-            position: absolute;
-            left: 16px;
-          }
-        }
-      }
-    }
+  .refresh {
+    display: inline-block;
+    width: 30px;
+    height: 30px;
+    margin-right: 38px;
+    background-image: url("~@/assets/icon_refresh.png");
   }
 
-  .detail-top {
+  .settingB {
     position: absolute;
-    top: 0;
-    left: 0;
-    width: 100%;
-    padding-top: 16px;
+    padding: 10px 0px;
+    background-color: rgba($color: #000000, $alpha: 0.85);
+    right: 134px;
+    transform: translateX(50%);
+    bottom: 48px;
     color: #fff;
-    font-size: 24px;
-    text-align: center;
-    background-color: rgba(0, 62, 144, 0.8);
-    z-index: 2;
 
-    .detail {
-      &_border {
+    .settingT {
+      position: relative;
+      padding: 0px 16px;
+      width: 180px;
+      height: 40px;
+      line-height: 40px;
+      cursor: pointer;
+
+      &:hover {
+        background-color: rgba(69, 69, 69, 0.85);
+      }
+
+      i {
         position: absolute;
-        left: 50%;
-        width: 700px;
-        height: 0;
-        border-top: 16px solid rgba(0, 62, 144, 0.8);
-        border-right: 16px solid transparent;
-        border-bottom: 16px solid transparent;
-        border-left: 16px solid transparent;
-        transform: translateX(-50%);
+        top: 13px;
+        right: 16px;
       }
+    }
 
-      &_text {
-        height: 32px;
-        width: 600px;
-        margin: auto;
-        white-space: nowrap;
-        text-overflow: ellipsis;
-        overflow: hidden;
+    .settingHeight {
+      max-height: 250px;
+      overflow: auto;
+    }
+
+    .settingsub {
+      width: 140px;
+      padding-left: 42px;
+      i {
+        position: absolute;
+        left: 16px;
       }
     }
   }
 }
 
-video::-webkit-media-controls-fullscreen-button {
-  display: none;
-}
-//所有控件
-video::-webkit-media-controls-enclosure {
-  display: none;
-}
-</style>
-<style lang="scss">
-.choosedate {
-  input {
-    background-color: transparent;
+.c-choose-date {
+  position: absolute;
+  right: $spacing;
+  top: $spacing;
+
+  &__time {
+    display: inline-block;
+    border-radius: $radius--mini;
+    background: #4478bc;
     height: 24px;
     line-height: 24px;
+  }
+
+  &__item {
+    display: inline-block;
+    width: 50px;
     font-size: 14px;
+    text-align: center;
+    border-radius: $radius--mini;
+    background-color: #4478bc;
+
+    &.active {
+      background: #0096ff;
+    }
+  }
+
+  ::v-deep input {
+    height: 24px;
     color: #fff;
-    // padding: 0 10px !important;
+    font-size: 14px;
+    line-height: 24px;
+    background-color: transparent;
   }
-  .el-input__prefix {
-    top: -2px;
+
+  ::v-deep .el-input__icon {
+    line-height: 24px;
   }
 }
+
+.o-canvas {
+  width: 100%;
+  height: 200px;
+}
 </style>

+ 1 - 9
src/views/external/camera/components/CameraPlayer.vue → src/components/external/camera/CameraPlayer/index.vue

@@ -6,7 +6,7 @@
     <video
       v-if="online"
       ref="video"
-      class="o-video__player"
+      class="o-video__player o-simple-video"
       muted
       autoplay
       controls
@@ -101,14 +101,6 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-video::-webkit-media-controls-fullscreen-button {
-  display: none;
-}
-
-video::-webkit-media-controls-enclosure {
-  display: none;
-}
-
 .o-video {
   position: relative;
   padding-top: 56.25%;

+ 3 - 1
src/constant.js

@@ -48,9 +48,11 @@ export const EventTarget = {
   RECUR: 2
 }
 
-export const ThirdPartDevice = {
+export const ThirdPartyDevice = {
+  GATEWAY: 0,
   RECEIVING_CARD: 1,
   SENDING_CARD: 2,
+  SCREEN: 3,
   LED_CAMERA: 4,
   TRAFFIC_CAMERA: 5
 }

+ 31 - 0
src/scss/bem/_component.scss

@@ -55,6 +55,17 @@
   }
 }
 
+.c-dialog--full {
+  .el-dialog__header {
+    display: none;
+  }
+
+  .el-dialog__body {
+    padding: 0;
+    height: 100%;
+  }
+}
+
 .c-lock {
   .el-loading-spinner > i {
     font-size: 48px;
@@ -241,6 +252,10 @@
     & + & {
       margin-top: 10px !important;
     }
+
+    & + &.far {
+      margin-top: $spacing  !important;
+    }
   }
 }
 
@@ -454,3 +469,19 @@
     text-align: center;
   }
 }
+
+.c-footer {
+  position: absolute;
+  width: 100%;
+  bottom: 0;
+  padding: 24px 16px 16px;
+  color: #fff;
+  background-image: linear-gradient(to bottom,
+      rgba(#000, 0) 0%,
+      rgba(#000, 0.6) 100%);
+
+  .o-icon--active {
+    margin-left: $spacing;
+    font-size: 18px;
+  }
+}

+ 8 - 0
src/scss/bem/_object.scss

@@ -271,3 +271,11 @@
     text-decoration: underline;
   }
 }
+
+.o-simple-video {
+  object-fit: contain;
+
+  &::-webkit-media-controls-enclosure {
+    display: none;
+  }
+}

+ 2 - 23
src/views/device/back/components/Video.vue

@@ -8,7 +8,7 @@
     <video
       v-if="online"
       ref="player"
-      class="o-video__player"
+      class="o-video__player o-simple-video"
       muted
       autoplay
       controls
@@ -25,7 +25,7 @@
       class="o-video__tip"
       :class="{ online }"
     />
-    <div class="o-video__name u-ellipsis">{{ device.name }}</div>
+    <div class="c-footer u-ellipsis">{{ device.name }}</div>
     <div
       v-show="maskShow"
       class="o-video__mask"
@@ -187,14 +187,6 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-video::-webkit-media-controls-fullscreen-button {
-  display: none;
-}
-
-video::-webkit-media-controls-enclosure {
-  display: none;
-}
-
 .o-video {
   position: relative;
   padding-top: 56.25%;
@@ -249,19 +241,6 @@ video::-webkit-media-controls-enclosure {
     }
   }
 
-  &__name {
-    position: absolute;
-    width: 100%;
-    bottom: 0;
-    padding: 24px 16px 16px;
-    color: #fff;
-    background-image: linear-gradient(
-      to bottom,
-      rgba(#000, 0) 0%,
-      rgba(#000, 0.6) 100%
-    );
-  }
-
   &__mask {
     position: absolute;
     top: 0;

+ 208 - 0
src/views/device/detail/components/external/Camera/index.vue

@@ -0,0 +1,208 @@
+<template>
+  <div class="l-flex--col">
+    <el-tabs
+      :value="active"
+      class="c-tabs has-bottom-padding"
+      @tab-click="onTabClick"
+    >
+      <template v-if="isSuperAdmin">
+        <el-tab-pane name="TRAFFIC_CAMERA">
+          <template #label>
+            <div class="o-tab">
+              <i
+                class="o-tab__icon el-icon-circle-plus-outline"
+                @click.stop="onAdd('TRAFFIC_CAMERA')"
+              />
+              人流量监测
+            </div>
+          </template>
+        </el-tab-pane>
+        <el-tab-pane name="LED_CAMERA">
+          <template #label>
+            <div class="o-tab">
+              <i
+                class="o-tab__icon el-icon-circle-plus-outline"
+                @click.stop="onAdd('LED_CAMERA')"
+              />
+              LED屏监测
+            </div>
+          </template>
+        </el-tab-pane>
+      </template>
+      <template v-else>
+        <el-tab-pane
+          label="人流量监测"
+          name="TRAFFIC_CAMERA"
+        />
+        <el-tab-pane
+          label="LED屏监测"
+          name="LED_CAMERA"
+        />
+      </template>
+    </el-tabs>
+    <grid-table
+      ref="table"
+      :schema="schema"
+      size="large"
+    >
+      <grid-table-item v-slot="item">
+        <camera-player
+          v-if="isActivated"
+          :key="item.identifier"
+          :class="{ 'u-pointer': canClick && item.onlineStatus }"
+          :camera="item"
+          @click.native="onClick(item)"
+        >
+          <div
+            class="c-footer l-flex--row"
+            @click.stop
+          >
+            <div class="l-flex__auto c-sibling-item u-ellipsis">{{ item.name }}</div>
+            <template v-if="isSuperAdmin">
+              <i
+                class="o-icon--active el-icon-delete u-pointer"
+                @click.stop="onDel(item)"
+              />
+            </template>
+          </div>
+        </camera-player>
+      </grid-table-item>
+    </grid-table>
+    <el-dialog
+      class="c-dialog--full"
+      :visible="detailing"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      fullscreen
+    >
+      <camera-detail
+        v-if="detailing"
+        :camera="targetCamera"
+        @close="onClose"
+      />
+    </el-dialog>
+    <table-dialog
+      ref="cameraDialog"
+      :title="title"
+      :schema="cameraSchema"
+      @choosen="onCameraChoosen"
+    />
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import {
+  getCameras,
+  getThirdPartyDevices,
+  bind,
+  unbind
+} from '@/api/external'
+import { ThirdPartyDevice } from '@/constant'
+
+export default {
+  name: 'DeviceCamera',
+  props: {
+    device: {
+      type: Object,
+      required: true
+    }
+  },
+  data () {
+    return {
+      active: 'TRAFFIC_CAMERA',
+      deviceType: ThirdPartyDevice.TRAFFIC_CAMERA,
+      schema: {
+        pageSize: 999,
+        list: this.getCameras
+      },
+      detailing: false,
+      targetCamera: null
+    }
+  },
+  computed: {
+    ...mapGetters(['isSuperAdmin']),
+    canClick () {
+      return this.active === 'TRAFFIC_CAMERA'
+    },
+    cameraType () {
+      return this.canClick ? 2 : 1
+    },
+    isActivated () {
+      return !this.detailing
+    },
+    title () {
+      return this.deviceType === ThirdPartyDevice.TRAFFIC_CAMERA ? '绑定人流量监测摄像头' : '绑定LED屏监测摄像头'
+    },
+    cameraSchema () {
+      return {
+        condition: { deviceType: this.deviceType },
+        list: getThirdPartyDevices,
+        cols: [
+          { prop: 'name', label: '名称', align: 'center' }
+        ]
+      }
+    }
+  },
+  methods: {
+    onTabClick ({ name: active }) {
+      if (this.active !== active) {
+        this.active = active
+        this.$refs.table.pageTo(1)
+      }
+    },
+    getCameras () {
+      return getCameras({
+        deviceId: this.device.id,
+        cameraType: this.cameraType
+      }).then(({ data }) => {
+        data = data.map(({ id, thirdPartyDevice }) => {
+          return {
+            ...thirdPartyDevice,
+            id
+          }
+        })
+        return { data }
+      })
+    },
+    onClick (camera) {
+      if (this.canClick && camera.onlineStatus) {
+        this.targetCamera = camera
+        this.detailing = true
+      }
+    },
+    onClose () {
+      this.detailing = false
+    },
+    onAdd (deviceType) {
+      this.deviceType = ThirdPartyDevice[deviceType]
+      this.$refs.cameraDialog.show()
+    },
+    onCameraChoosen ({ value, done }) {
+      this.$confirm(
+        `绑定摄像头 ${value.name} ?`,
+        { type: 'warning' }
+      ).then(() => {
+        bind(this.device.id, this.deviceType, value.id).then(() => {
+          done()
+          if (this.deviceType === ThirdPartyDevice[this.active]) {
+            this.$refs.table.pageTo(1)
+          } else {
+            this.onTabClick({ name: this.canClick ? 'LED_CAMERA' : 'TRAFFIC_CAMERA' })
+          }
+        })
+      })
+    },
+    onDel (item) {
+      this.$confirm(
+        `解绑摄像头 ${item.name} ?`,
+        { type: 'warning' }
+      ).then(() => {
+        unbind(item.id).then(() => {
+          this.$refs.table.decrease(1)
+        })
+      })
+    }
+  }
+}
+</script>

+ 4 - 4
src/views/device/detail/index.vue

@@ -39,7 +39,7 @@
       v-if="device"
       class="l-flex--col l-flex__fill has-padding"
     >
-      <keep-alive exclude="Cameras">
+      <keep-alive exclude="DeviceCamera">
         <component
           :is="active"
           :key="active"
@@ -69,7 +69,7 @@ import LinkState from './components/LinkState'
 import Sensors from './components/external/Sensors'
 import Transmitter from './components/external/Transmitter'
 import ReceivingCard from './components/external/ReceivingCard'
-import Cameras from '@/views/external/camera'
+import Camera from './components/external/Camera'
 
 export default {
   name: 'DeviceDetail',
@@ -82,7 +82,7 @@ export default {
     Sensors,
     Transmitter,
     ReceivingCard,
-    Cameras
+    Camera
   },
   data () {
     return {
@@ -100,7 +100,7 @@ export default {
         __STAGING__ ? { key: 'Transmitter', label: '发送控制设备' } : null,
         __STAGING__ ? { key: 'ReceivingCard', label: '接收卡' } : null,
         __STAGING__ ? { key: 'LinkState', label: '全链路监测状态' } : null,
-        __STAGING__ ? { key: 'Cameras', label: '摄像头' } : null
+        __STAGING__ ? { key: 'Camera', label: '摄像头' } : null
       ].filter(val => val)
     }
   },

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

@@ -504,7 +504,7 @@ export default {
 <style lang="scss" scoped>
 .c-device-detail {
   &__screen {
-    width: 354px;
+    width: 352px;
     margin-right: 24px;
   }
 
@@ -718,6 +718,7 @@ export default {
 
 .o-program {
   display: inline-block;
+  font-size: 0;
   border-radius: $radius--mini;
   background: rgba(#000, 0.8) url("~@/assets/program_bg.png") center center /
     100% 100% no-repeat;

+ 0 - 4
src/views/external/camera/components/Fullscreen.vue

@@ -20,13 +20,9 @@
 
 <script>
 import { getCameras } from '@/api/camera'
-import CameraPlayer from './CameraPlayer'
 
 export default {
   name: 'CameraWall',
-  components: {
-    CameraPlayer
-  },
   props: {
     info: {
       type: Object,

+ 17 - 80
src/views/external/camera/index.vue

@@ -2,7 +2,7 @@
   <wrapper
     fill
     margin
-    :padding="isAll"
+    padding
     background
   >
     <el-tabs
@@ -15,7 +15,7 @@
         name="first"
       />
       <el-tab-pane
-        label="LED屏画面监测"
+        label="LED屏监测"
         name="second"
       />
     </el-tabs>
@@ -26,7 +26,7 @@
     >
       <template #header="{ totalCount }">
         <button
-          v-if="isSuperAdmin && isAll"
+          v-if="isSuperAdmin"
           class="c-sibling-item o-button"
           @click="onAdd"
         >
@@ -49,11 +49,11 @@
           @click.native="onClick(item)"
         >
           <div
-            class="o-video-footer l-flex--row"
+            class="c-footer l-flex--row"
             @click.stop
           >
             <div class="l-flex__auto c-sibling-item u-ellipsis">{{ item.name }}</div>
-            <template v-if="isSuperAdmin && isAll">
+            <template v-if="isSuperAdmin">
               <i
                 class="o-icon--active el-icon-edit u-pointer"
                 @click.stop="onEdit(item)"
@@ -153,7 +153,7 @@
       :close-on-press-escape="false"
       fullscreen
     >
-      <detail
+      <camera-detail
         v-if="detailing"
         :camera="targetCamera"
         @close="onClose"
@@ -181,40 +181,25 @@ import {
   getCameras,
   addCamera,
   updateCamera,
-  deleteCamera,
-  getBindCameras
+  deleteCamera
 } from '@/api/camera'
-import Detail from './components/Detail'
 import Fullscreen from './components/Fullscreen'
-import CameraPlayer from './components/CameraPlayer'
 
 export default {
   name: 'Cameras',
   components: {
-    Detail,
-    Fullscreen,
-    CameraPlayer
-  },
-  props: {
-    device: {
-      type: Object,
-      default: null
-    }
+    Fullscreen
   },
   data () {
     return {
       active: 'first',
       schema: {
-        pageSize: this.device ? 999 : 6,
-        condition: this.device
-          ? null
-          : { name: '' },
+        pageSize: 6,
+        condition: { name: '' },
         list: this.getCameras,
-        filters: this.device
-          ? null
-          : [
-            { key: 'searchname', type: 'search', placeholder: '设备名称' }
-          ]
+        filters: [
+          { key: 'searchname', type: 'search', placeholder: '设备名称' }
+        ]
       },
       dialogTitle: '新增',
       isEdit: true,
@@ -239,9 +224,6 @@ export default {
   },
   computed: {
     ...mapGetters(['isSuperAdmin']),
-    isAll () {
-      return !this.device
-    },
     cameraType () {
       return this.active === 'first' ? 2 : 1
     },
@@ -255,29 +237,14 @@ export default {
   methods: {
     onTabClick ({ name: active }) {
       if (this.active !== active) {
-        this.totalCount = 0
         this.active = active
         this.$refs.table.pageTo(1)
       }
     },
     getCameras (params) {
-      const key = Date.now()
-      this.$key = key
-      return (this.isAll
-        ? getCameras({
-          ...params,
-          cameraType: this.cameraType
-        })
-        : getBindCameras({
-          deviceId: this.device.id,
-          cameraType: this.cameraType
-        })
-      ).then(({ data, totalCount }) => {
-        if (key === this.$key) {
-          data = this.isAll ? data : data.map(({ thirdPartyDevice }) => thirdPartyDevice)
-          totalCount = totalCount || data.length
-        }
-        return { data, totalCount }
+      return getCameras({
+        ...params,
+        cameraType: this.cameraType
       })
     },
     onAdd () {
@@ -350,7 +317,7 @@ export default {
     },
     onFull () {
       const { totalCount, params } = this.$refs.table.info()
-      if (this.isAll && totalCount > params.pageSize) {
+      if (totalCount > params.pageSize) {
         this.fullscreenOptions = {
           params: {
             ...params,
@@ -382,24 +349,6 @@ export default {
   background-color: #fff;
 }
 
-.o-video-footer {
-  position: absolute;
-  width: 100%;
-  bottom: 0;
-  padding: 24px 16px 16px;
-  color: #fff;
-  background-image: linear-gradient(
-    to bottom,
-    rgba(#000, 0) 0%,
-    rgba(#000, 0.6) 100%
-  );
-
-  .o-icon--active {
-    margin-left: $spacing;
-    font-size: 18px;
-  }
-}
-
 .c-form {
   display: flex;
   flex-direction: column;
@@ -410,15 +359,3 @@ export default {
   }
 }
 </style>
-<style lang="scss">
-.c-dialog--full {
-  .el-dialog__header {
-    display: none;
-  }
-
-  .el-dialog__body {
-    padding: 0;
-    height: 100%;
-  }
-}
-</style>

+ 1 - 22
src/views/realm/device/Device.vue

@@ -72,10 +72,6 @@
         />
       </div>
     </confirm-dialog>
-    <bind-device-dialog
-      ref="bindDeviceDialog"
-      @choosen="onBindDeviceChoosen"
-    />
   </schema-table>
 </template>
 
@@ -95,7 +91,6 @@ import {
   validLongitude,
   validLatitude
 } from '@/utils/validate'
-import { bind as bindDevice } from '@/api/external'
 
 export default {
   name: 'Device',
@@ -166,10 +161,9 @@ export default {
                   : '未激活'
               }
           }, on: this.onTagClick },
-          { type: 'invoke', width: 280, render: [
+          { type: 'invoke', width: 180, render: [
             { label: '查看', render ({ empty }) { return !empty }, on: this.onViewDevice },
             { label: '添加备份', render ({ isMaster }) { return isMaster }, on: this.onAddSubDevice },
-            { label: '绑定摄像头', render ({ empty }) { return !empty }, on: this.onBindDevice },
             { label: '删除', render ({ empty }) { return !empty }, on: this.onDelDevice }
           ] }
         ]
@@ -373,21 +367,6 @@ export default {
           this.reloadSubDevices(data.parent)
         }
       })
-    },
-    onBindDevice (device) {
-      this.$device = {
-        id: device.id,
-        name: device.name
-      }
-      this.$refs.bindDeviceDialog.show()
-    },
-    onBindDeviceChoosen ({ value, done }) {
-      this.$confirm(
-        `绑定 ${[null, 'LED屏画面监测摄像头', '人流量监测摄像头'][value.cameraType]} ${value.name} ?`,
-        { type: 'warning' }
-      ).then(() => {
-        bindDevice(this.$device.id, value.deviceType, value.id).then(done)
-      })
     }
   }
 }