Browse Source

refactor: full link

Casper Dai 3 năm trước cách đây
mục cha
commit
0d5fb82f5a

+ 535 - 0
src/components/service/FullLink/index.vue

@@ -0,0 +1,535 @@
+<template>
+  <div
+    ref="wrapper"
+    v-loading="!loaded && loading"
+    element-loading-background="transparent"
+  >
+    <warning
+      v-if="!loaded && !loading"
+      @click="getBoundThirdPartyDevices"
+    />
+    <div
+      v-if="loaded"
+      class="c-link-state l-flex--row"
+      :class="className"
+    >
+      <div
+        class="c-link-state__main"
+        :style="styles"
+      >
+        <div
+          v-for="item in items"
+          :key="item.key"
+          class="o-link-item"
+          :class="item.className"
+          @click="onClick(item)"
+        >
+          <div class="o-link-item__name">{{ item.label }}</div>
+          <device-player
+            v-if="item.key === 'led'"
+            :device="device"
+            autoplay
+          />
+          <svg-icon
+            v-else
+            class="o-link-item__img"
+            :icon-class="item.icon"
+          />
+        </div>
+        <div
+          v-for="line in lines"
+          :key="line.key"
+          class="o-line"
+          :class="line.className"
+        />
+      </div>
+    </div>
+    <slot />
+  </div>
+</template>
+
+<script>
+import { getBoundThirdPartyDevices } from '@/api/external'
+import { ThirdPartyDevice } from '@/constant'
+
+const LinkItems = Object.freeze([
+  {
+    key: 'msr',
+    label: '浪潮屏媒安播云平台'
+  },
+  {
+    key: 'device',
+    label: '浪潮超高清媒体播控器'
+  },
+  {
+    key: 'led',
+    label: 'LED大屏'
+  },
+  {
+    key: ThirdPartyDevice.GATEWAY,
+    alias: 'gateway',
+    label: '浪潮物联网关',
+    iconKey: 'gateway',
+    canClick: true
+  },
+  {
+    key: ThirdPartyDevice.TRAFFIC_CAMERA,
+    alias: 'traffic_camera',
+    label: '人流量监测摄像头',
+    iconKey: 'camera',
+    canClick: true
+  },
+  {
+    key: ThirdPartyDevice.LED_CAMERA,
+    alias: 'led_camera',
+    label: 'LED屏监测摄像头',
+    iconKey: 'camera',
+    canClick: true
+  },
+  {
+    key: ThirdPartyDevice.SENDING_CARD,
+    alias: 'sending_card',
+    label: '发送控制设备',
+    iconKey: 'sending-card',
+    canClick: true
+  },
+  {
+    key: ThirdPartyDevice.RECEIVING_CARD,
+    alias: 'receiving_card',
+    label: '接收卡',
+    iconKey: 'receiving-card',
+    canClick: true
+  }
+])
+
+const LineFromeTo = {
+  1: ['msr', 'device'],
+  2: ['device', ThirdPartyDevice.SENDING_CARD],
+  3: [ThirdPartyDevice.SENDING_CARD, ThirdPartyDevice.RECEIVING_CARD],
+  4: [ThirdPartyDevice.RECEIVING_CARD, 'led'],
+  5: ['msr', ThirdPartyDevice.GATEWAY],
+  6: ['msr', ThirdPartyDevice.LED_CAMERA, ThirdPartyDevice.TRAFFIC_CAMERA],
+  7: ['msr', ThirdPartyDevice.LED_CAMERA],
+  8: ['msr', ThirdPartyDevice.LED_CAMERA],
+  9: ['msr', ThirdPartyDevice.TRAFFIC_CAMERA],
+  10: [ThirdPartyDevice.GATEWAY, 'led'],
+  11: ['msr', ThirdPartyDevice.GATEWAY, ThirdPartyDevice.LED_CAMERA, ThirdPartyDevice.TRAFFIC_CAMERA]
+}
+
+export default {
+  name: 'FullLink',
+  props: {
+    device: {
+      type: Object,
+      required: true
+    },
+    online: {
+      type: [Boolean, String],
+      default: false
+    },
+    theme: {
+      type: String,
+      default: 'dark'
+    },
+    square: {
+      type: [Boolean, String],
+      default: false
+    }
+  },
+  data () {
+    return {
+      linkDeviceMap: null,
+      scale: 1,
+      loading: false,
+      loaded: false
+    }
+  },
+  computed: {
+    targetId () {
+      return this.device.id
+    },
+    className () {
+      return [this.theme, this.square ? 'square' : ''].join(' ')
+    },
+    linkState () {
+      const map = {
+        msr: 1,
+        device: this.online ? 1 : 0,
+        led: this.online ? 1 : 0,
+        [ThirdPartyDevice.GATEWAY]: -1,
+        [ThirdPartyDevice.LED_CAMERA]: -1,
+        [ThirdPartyDevice.TRAFFIC_CAMERA]: -1,
+        [ThirdPartyDevice.SENDING_CARD]: -1,
+        [ThirdPartyDevice.RECEIVING_CARD]: -1
+      }
+      if (this.linkDeviceMap) {
+        for (const item of this.linkDeviceMap) {
+          map[item.deviceType] = item.status
+        }
+      }
+      return map
+    },
+    items () {
+      const map = this.linkState
+      return LinkItems.map(({ key, alias, label, iconKey, canClick }) => {
+        const status = map[key]
+        return {
+          key, label, status, canClick,
+          icon: status === -1
+            ? 'link-unbound'
+            : `link-${iconKey || key}-${status === 1 ? 'online' : 'offline'}`,
+          className: [alias || key, canClick && status === 1 ? 'u-pointer' : ''].join(' ')
+        }
+      })
+    },
+    lines () {
+      const state = this.linkState
+      return Object.keys(LineFromeTo).map(key => {
+        const from = LineFromeTo[key][0]
+        const to = LineFromeTo[key].slice(1)
+        return {
+          key: `line${key}`,
+          className: [`l${key}`, state[from] === 1 && to.some(key => state[key] === 1) ? 'linked' : ''].join(' ')
+        }
+      })
+    },
+    styles () {
+      return {
+        transform: `scale(${this.scale})`
+      }
+    }
+  },
+  created () {
+    this.getBoundThirdPartyDevices()
+    this.$timer = setInterval(this.getBoundThirdPartyDevices, 10000)
+  },
+  mounted () {
+    this.checkScale()
+    window.addEventListener('resize', this.checkScale)
+  },
+  beforeDestroy () {
+    window.removeEventListener('resize', this.checkScale)
+    clearInterval(this.$timer)
+  },
+  methods: {
+    checkScale () {
+      const elm = this.$refs.wrapper
+      const width = elm.clientWidth
+      const height = elm.clientHeight
+      this.scale = this.square
+        ? Math.min(width / 804, height / 640).toFixed(2)
+        : Math.min(width / 966, height / 420).toFixed(2)
+    },
+    getBoundThirdPartyDevices () {
+      if (this.loading) {
+        return
+      }
+      this.loading = true
+      getBoundThirdPartyDevices(this.targetId, true).then(({ data }) => {
+        this.linkDeviceMap = data
+        this.loaded = true
+      }).finally(() => {
+        this.loading = false
+      })
+    },
+    onClick (item) {
+      if (!item.canClick) {
+        return
+      }
+      this.$emit('click', item)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+@mixin getPosition($left, $top, $width, $height) {
+  top: $top;
+  left: $left;
+  width: $width;
+  height: $height;
+}
+
+.c-link-state {
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+
+  &__main {
+    position: relative;
+    width: 966px;
+    height: 420px;
+    transform-origin: left center;
+  }
+
+  &.dark {
+    color: $black;
+  }
+
+  &.light {
+    color: #fff;
+  }
+
+  &.square {
+    .c-link-state__main {
+      width: 804px;
+      height: 640px;
+    }
+
+    .o-link-item {
+      &.msr {
+        top: 0;
+        left: 0;
+      }
+
+      &.device {
+        top: 75px;
+        left: 286px;
+      }
+
+      &.sending_card {
+        top: 75px;
+        left: 472px;
+      }
+
+      &.receiving_card {
+        top: 75px;
+        left: 660px;
+      }
+
+      &.gateway {
+        top: 432px;
+        left: 52px;
+        width: 138px;
+        height: 138px;
+      }
+
+      &.traffic_camera {
+        top: 280px;
+        left: 460px;
+      }
+
+      &.led_camera {
+        top: 400px;
+        left: 290px;
+      }
+
+      &.led {
+        top: 400px;
+        left: 440px;
+      }
+    }
+
+    .o-line {
+      &.l1 {
+        top: 166px;
+        left: 232px;
+        width: 60px;
+        transform: none;
+      }
+
+      &.l2 {
+        top: 166px;
+        left: 418px;
+        width: 60px;
+      }
+
+      &.l3 {
+        top: 166px;
+        left: 604px;
+        width: 60px;
+      }
+
+      &.l4 {
+        top: 202px;
+        left: 728px;
+        width: 198px;
+      }
+
+      &.l5 {
+        top: 334px;
+        left: 122px;
+        width: 108px;
+        transform: rotate(90deg);
+      }
+
+      &.l6 {
+        top: 334px;
+        left: 122px;
+        width: 120px;
+      }
+
+      &.l7 {
+        top: 334px;
+        left: 240px;
+        width: 120px;
+      }
+
+      &.l8 {
+        top: 454px;
+        left: 240px;
+      }
+
+      &.l9 {
+        top: 334px;
+        left: 240px;
+        width: 234px;
+      }
+
+      &.l10 {
+        top: 522px;
+        left: 184px;
+        width: 256px;
+      }
+
+      &.l11 {
+        top: 230px;
+        left: 122px;
+        width: 104px;
+        height: 2px;
+        transform: rotate(90deg);
+        transform-origin: left;
+      }
+    }
+  }
+}
+
+@keyframes move {
+  from {
+    background-position: 0 0;
+  }
+  to {
+    background-position: 3em 0;
+  }
+}
+
+.o-link-item {
+  position: absolute;
+  z-index: 1;
+
+  &__name {
+    position: absolute;
+    top: 100%;
+    left: 50%;
+    font-size: 14px;
+    line-height: 1;
+    white-space: nowrap;
+    transform: translateX(-50%);
+  }
+
+  &__img {
+    width: 100%;
+    height: 100%;
+  }
+
+  &.msr {
+    @include getPosition(0, 42px, 242px, 236px);
+  }
+
+  &.device {
+    @include getPosition(270px, 0, 138px, 138px);
+  }
+
+  &.sending_card {
+    @include getPosition(484px, 0, 138px, 138px);
+  }
+
+  &.receiving_card {
+    @include getPosition(720px, 0, 138px, 138px);
+
+    .o-link-item__name {
+      transform: translateX(8px);
+    }
+  }
+
+  &.gateway {
+    @include getPosition(270px, 250px, 138px, 138px);
+  }
+
+  &.traffic_camera {
+    @include getPosition(480px, 156px, 80px, 80px);
+  }
+
+  &.led_camera {
+    @include getPosition(480px, 252px, 80px, 80px);
+  }
+
+  &.led {
+    @include getPosition(604px, 208px, 352px, 198px);
+
+    .o-link-item__name {
+      top: -8px;
+      left: auto;
+      right: 0;
+      bottom: auto;
+      transform: translateY(-100%);
+    }
+  }
+}
+
+.o-line {
+  position: absolute;
+  background-color: $border;
+
+  &.linked {
+    background-size: 3em 1em;
+    background-image: linear-gradient(
+      -90deg,
+      transparent 0em,
+      transparent 1em,
+      #026af2 1em,
+      #026af2 2em,
+      transparent 3em
+    );
+    animation: move 0.6s linear infinite;
+  }
+
+  &.l1 {
+    transform: skewY(-30deg);
+    transform-origin: left;
+    @include getPosition(180px, 178px, 122px, 2px);
+  }
+
+  &.l2 {
+    @include getPosition(402px, 92px, 86px, 2px);
+  }
+
+  &.l3 {
+    @include getPosition(618px, 92px, 106px, 2px);
+  }
+
+  &.l4 {
+    transform: rotate(90deg);
+    transform-origin: left;
+    @include getPosition(788px, 129px, 80px, 2px);
+  }
+
+  &.l5 {
+    transform: skewY(30deg);
+    transform-origin: left;
+    @include getPosition(170px, 244px, 122px, 2px);
+  }
+
+  &.l6 {
+    @include getPosition(234px, 209px, 196px, 2px);
+  }
+
+  &.l7 {
+    transform: rotate(90deg);
+    transform-origin: left;
+    @include getPosition(428px, 209px, 95px, 2px);
+  }
+
+  &.l8 {
+    @include getPosition(428px, 304px, 66px, 2px);
+  }
+
+  &.l9 {
+    @include getPosition(428px, 209px, 66px, 2px);
+  }
+
+  &.l10 {
+    @include getPosition(376px, 356px, 228px, 2px);
+  }
+}
+</style>

+ 2 - 3
src/icons/index.js

@@ -4,6 +4,5 @@ import SvgIcon from './SvgIcon'// svg component
 // register globally
 Vue.component('SvgIcon', SvgIcon)
 
-const req = require.context('./svg', false, /\.svg$/)
-const requireAll = requireContext => requireContext.keys().map(requireContext)
-requireAll(req)
+const requireContext = require.context('./svg', true, /\.svg$/)
+requireContext.keys().map(requireContext)

+ 0 - 0
src/assets/linkState/icon_camera_offline.svg → src/icons/svg/link/link-camera-offline.svg


+ 0 - 0
src/assets/linkState/icon_camera_online.svg → src/icons/svg/link/link-camera-online.svg


+ 0 - 0
src/assets/linkState/icon_device_offline.svg → src/icons/svg/link/link-device-offline.svg


+ 0 - 0
src/assets/linkState/icon_device_online.svg → src/icons/svg/link/link-device-online.svg


+ 0 - 0
src/assets/linkState/icon_gateway_offline.svg → src/icons/svg/link/link-gateway-offline.svg


+ 0 - 0
src/assets/linkState/icon_gateway_online.svg → src/icons/svg/link/link-gateway-online.svg


+ 0 - 0
src/assets/linkState/icon_msr.svg → src/icons/svg/link/link-msr-online.svg


+ 0 - 0
src/assets/linkState/icon_receive_card_offline.svg → src/icons/svg/link/link-receiving-card-offline.svg


+ 0 - 0
src/assets/linkState/icon_receive_card_online.svg → src/icons/svg/link/link-receiving-card-online.svg


+ 0 - 0
src/assets/linkState/icon_sending_device_offline.svg → src/icons/svg/link/link-sending-card-offline.svg


+ 0 - 0
src/assets/linkState/icon_sending_device_online.svg → src/icons/svg/link/link-sending-card-online.svg


+ 0 - 0
src/assets/linkState/icon_unbound.svg → src/icons/svg/link/link-unbound.svg


+ 4 - 0
src/scss/bem/_ishas.scss

@@ -58,3 +58,7 @@
 .is-hide-now .el-picker-panel__footer .el-button--text {
   display: none;
 }
+
+.has-gap {
+  border: $spacing solid transparent;
+}

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

@@ -1,36 +1,9 @@
 <template>
-  <div v-loading="!loaded && loading">
-    <warning
-      v-if="!loaded && !loading"
-      @click="getBoundThirdPartyDevices"
-    />
-    <div
-      v-if="loaded"
-      class="l-flex__auto u-overflow-x--auto"
-    >
-      <div class="c-link-state">
-        <div
-          v-for="item in items"
-          :key="item.key"
-          class="o-link-device has-bg"
-          v-bind="getAttrs(item.key)"
-          @click="onClick(item)"
-        >
-          <div class="o-link-device__name u-bold">{{ item.label }}</div>
-          <device-player
-            v-if="item.key === 'led'"
-            :device="device"
-            autoplay
-          />
-        </div>
-        <div
-          v-for="line in lines"
-          :key="line.key"
-          class="o-line"
-          :class="line.className"
-        />
-      </div>
-    </div>
+  <full-link
+    :device="device"
+    :online="online"
+    @click="onClick"
+  >
     <el-dialog
       :visible.sync="showDetail"
       custom-class="c-dialog--detail"
@@ -44,11 +17,10 @@
         :device="device"
       />
     </el-dialog>
-  </div>
+  </full-link>
 </template>
 
 <script>
-import { getBoundThirdPartyDevices } from '@/api/external'
 import { ThirdPartyDevice } from '@/constant'
 import SendingCard from './external/SendingCard'
 import ReceivingCard from './external/ReceivingCard'
@@ -56,15 +28,6 @@ import TrafficCamera from './external/Camera/traffic.vue'
 import LedCamera from './external/Camera/led.vue'
 import Gateway from './external/Gateway'
 
-const KeyMap = {
-  [ThirdPartyDevice.RECEIVING_CARD]: 'receiving_card',
-  [ThirdPartyDevice.SENDING_CARD]: 'sending_device',
-  [ThirdPartyDevice.LED_CAMERA]: 'led_camera',
-  [ThirdPartyDevice.TRAFFIC_CAMERA]: 'traffic_camera',
-  [ThirdPartyDevice.GATEWAY]: 'gateway',
-  [ThirdPartyDevice.SCREEN]: 'led'
-}
-
 export default {
   name: 'DeviceExternal',
   components: {
@@ -86,157 +49,28 @@ export default {
   },
   data () {
     return {
-      items: [
-        {
-          label: '浪潮屏媒安播云平台',
-          key: 'msr'
-        },
-        {
-          label: '浪潮超高清媒体播控器',
-          key: 'device'
-        },
-        {
-          label: '浪潮物联网关',
-          key: 'gateway'
-        },
-        {
-          label: '发送控制设备',
-          key: 'sending_device'
-        },
-        {
-          label: '人流量监测摄像头',
-          key: 'traffic_camera'
-        },
-        {
-          label: 'LED屏监测摄像头',
-          key: 'led_camera'
-        },
-        {
-          label: '接收卡',
-          key: 'receiving_card'
-        },
-        {
-          label: 'LED大屏',
-          key: 'led'
-        }
-      ],
-      lineFromeTo: {
-        1: ['msr', 'device'],
-        2: ['device', 'sending_device'],
-        3: ['device', 'receiving_card'],
-        4: ['receiving_card', 'led'],
-        5: ['msr', 'gateway'],
-        6: ['msr', 'led_camera', 'traffic_camera'],
-        7: ['msr', 'traffic_camera'],
-        8: ['msr', 'led_camera'],
-        9: ['msr', 'led_camera'],
-        10: ['gateway', 'led']
-      },
-      linkState: {
-        msr: 0,
-        device: this.online ? 0 : 1,
-        led: this.online ? 0 : 1,
-        gateway: 2,
-        sending_device: 2,
-        traffic_camera: 2,
-        led_camera: 2,
-        receiving_card: 2
-      },
-      loading: false,
-      loaded: false,
       showDetail: false,
       activeComponent: null,
       title: ''
     }
   },
-  computed: {
-    lines () {
-      const lineFromeTo = this.lineFromeTo
-      const state = this.linkState
-      return Object.keys(lineFromeTo).map(key => {
-        const from = lineFromeTo[key][0]
-        const to = lineFromeTo[key].slice(1)
-        return {
-          key: `line${key}`,
-          className: [`l${key}`, state[from] === 0 && to.some(key => state[key] === 0) ? 'linked' : ''].join(' ')
-        }
-      })
-    }
-  },
-  watch: {
-    online () {
-      this.init()
-    }
-  },
-  created () {
-    this.init()
-    this.$timer = setInterval(this.init, 10000)
-  },
-  beforeDestroy () {
-    clearInterval(this.$timer)
-  },
   methods: {
-    init () {
-      this.getBoundThirdPartyDevices()
-    },
-    getBoundThirdPartyDevices () {
-      if (this.loading) {
-        return
-      }
-      this.loading = true
-      getBoundThirdPartyDevices(this.device.id, true).then(({ data }) => {
-        const form = {
-          msr: 0,
-          device: this.online ? 0 : 1,
-          led: this.online ? 0 : 1,
-          gateway: 2,
-          sending_device: 2,
-          receiving_card: 2,
-          traffic_camera: 2,
-          led_camera: 2
-        }
-        for (const item of data) {
-          form[KeyMap[item.deviceType]] = item.status === 1 ? 0 : 1
-        }
-        this.linkState = form
-        this.loaded = true
-      }).finally(() => {
-        this.loading = false
-      })
-    },
-    getAttrs (key) {
-      const value = this.linkState[key]
-      return {
-        'class': [
-          key,
-          value === 0
-            ? 'online u-pointer'
-            : value === 1
-              ? 'offline u-pointer'
-              : 'unbind'
-        ].join(' ')
-      }
-    },
     onClick ({ key, label }) {
-      if (this.linkState[key] > 1) {
-        return
-      }
-
       let activeComponent = null
       switch (key) {
-        case KeyMap[ThirdPartyDevice.SENDING_CARD]:
+        case ThirdPartyDevice.SENDING_CARD:
           activeComponent = 'SendingCard'
           break
-        case KeyMap[ThirdPartyDevice.RECEIVING_CARD]:
+        case ThirdPartyDevice.RECEIVING_CARD:
           activeComponent = 'ReceivingCard'
           break
-        case KeyMap[ThirdPartyDevice.TRAFFIC_CAMERA]:
+        case ThirdPartyDevice.TRAFFIC_CAMERA:
           activeComponent = 'TrafficCamera'
           break
-        case KeyMap[ThirdPartyDevice.LED_CAMERA]:
+        case ThirdPartyDevice.LED_CAMERA:
           activeComponent = 'LedCamera'
           break
-        case KeyMap[ThirdPartyDevice.GATEWAY]:
+        case ThirdPartyDevice.GATEWAY:
           activeComponent = 'Gateway'
           break
         default:
@@ -249,191 +83,3 @@ export default {
   }
 }
 </script>
-
-<style lang="scss" scoped>
-@mixin getPosition($left, $top, $width, $height) {
-  top: $top;
-  left: $left;
-  width: $width;
-  height: $height;
-}
-
-.c-link-state {
-  position: relative;
-  min-width: 978px;
-  min-height: 408px;
-}
-
-.o-line {
-  position: absolute;
-  background-color: $border;
-
-  &.linked {
-    background-color: #026af2;
-  }
-
-  &.l1 {
-    transform: skewY(-30deg);
-    transform-origin: left;
-    @include getPosition(180px, 178px, 122px, 1px);
-  }
-
-  &.l2 {
-    @include getPosition(402px, 92px, 107px, 1px);
-  }
-
-  &.l3 {
-    @include getPosition(638px, 92px, 107px, 1px);
-  }
-
-  &.l4 {
-    @include getPosition(808px, 129px, 1px, 80px);
-  }
-
-  &.l5 {
-    transform: skewY(30deg);
-    transform-origin: left;
-    @include getPosition(170px, 244px, 122px, 1px);
-  }
-
-  &.l6 {
-    @include getPosition(234px, 209px, 216px, 1px);
-  }
-
-  &.l7 {
-    @include getPosition(448px, 209px, 1px, 95px);
-  }
-
-  &.l8 {
-    @include getPosition(448px, 304px, 66px, 1px);
-  }
-
-  &.l9 {
-    @include getPosition(448px, 209px, 66px, 1px);
-  }
-
-  &.l10 {
-    @include getPosition(360px, 366px, 296px, 1px);
-  }
-}
-
-.o-link-device {
-  position: absolute;
-  z-index: 1;
-
-  &__name {
-    position: absolute;
-    top: 100%;
-    left: 50%;
-    color: $black;
-    font-size: 16px;
-    line-height: 1;
-    white-space: nowrap;
-    transform: translateX(-50%);
-  }
-
-  &.unbind {
-    background-image: url("~@/assets/linkState/icon_unbound.svg");
-  }
-
-  &.msr {
-    @include getPosition(0, 42px, 242px, 236px);
-    background-image: url("~@/assets/linkState/icon_msr.svg");
-  }
-
-  &.device {
-    @include getPosition(270px, 0, 138px, 138px);
-
-    &.online {
-      background-image: url("~@/assets/linkState/icon_device_online.svg");
-    }
-
-    &.offline {
-      background-image: url("~@/assets/linkState/icon_device_offline.svg");
-    }
-  }
-
-  &.sending_device {
-    @include getPosition(504px, 0, 138px, 138px);
-
-    &.online {
-      background-image: url("~@/assets/linkState/icon_sending_device_online.svg");
-    }
-
-    &.offline {
-      background-image: url("~@/assets/linkState/icon_sending_device_offline.svg");
-    }
-  }
-
-  &.receiving_card {
-    @include getPosition(740px, 0, 138px, 138px);
-
-    &.online {
-      background-image: url("~@/assets/linkState/icon_receive_card_online.svg");
-    }
-
-    &.offline {
-      background-image: url("~@/assets/linkState/icon_receive_card_offline.svg");
-    }
-
-    .o-link-device__name {
-      transform: translateX(8px);
-    }
-  }
-
-  &.traffic_camera {
-    @include getPosition(500px, 156px, 79px, 79px);
-  }
-
-  &.led_camera {
-    @include getPosition(500px, 252px, 79px, 79px);
-  }
-
-  &.traffic_camera,
-  &.led_camera {
-    &.online {
-      background-image: url("~@/assets/linkState/icon_camera_online.svg");
-    }
-
-    &.offline {
-      background-image: url("~@/assets/linkState/icon_camera_offline.svg");
-    }
-  }
-
-  &.gateway {
-    @include getPosition(270px, 250px, 138px, 138px);
-
-    &.online {
-      background-image: url("~@/assets/linkState/icon_gateway_online.svg");
-    }
-
-    &.offline {
-      background-image: url("~@/assets/linkState/icon_gateway_offline.svg");
-    }
-  }
-
-  &.led {
-    @include getPosition(624px, 208px, 352px, 198px);
-    background-color: rgba(#000, 0.8);
-    background-position: center center;
-    background-size: contain;
-    background-repeat: no-repeat;
-
-    &.online {
-      background-image: url("~@/assets/image_no_program.svg");
-    }
-
-    &.offline {
-      background-image: url("~@/assets/image_offline.svg");
-    }
-
-    .o-link-device__name {
-      top: -8px;
-      left: auto;
-      right: 0;
-      bottom: auto;
-      transform: translateY(-100%);
-    }
-  }
-}
-</style>

+ 262 - 318
src/views/device/detail/dashboard/LinkState.vue

@@ -5,6 +5,8 @@
   >
     <div
       ref="box"
+      v-loading="!loaded && loading"
+      element-loading-background="transparent"
       class="l-flex__fill l-flex--row center jcenter"
     >
       <div
@@ -16,11 +18,11 @@
         <div
           v-for="item in items"
           :key="item.key"
-          :class="['co', item.key]"
-          :style="getImg(item.key)"
+          class="co"
+          :class="item.className"
         >
           <div
-            v-if="Array.isArray(item.label)"
+            v-if="item.multiLines"
             class="ctext"
           >
             <div>{{ item.label[0] }}</div>
@@ -32,37 +34,25 @@
           >
             {{ item.label }}
           </div>
-        </div>
-        <div
-          class="co led"
-          :class="{ mini: !fullscreen }"
-        >
           <device-player
+            v-if="item.key === 'led'"
             :device="device"
             autoplay
           />
-          <div class="ctext">LED大屏</div>
+          <svg-icon
+            v-else
+            class="c-device-dashboard-link-state__img"
+            :icon-class="item.icon"
+          />
         </div>
         <div
           v-for="line in lines"
           :key="line.key"
-          :class="[
-            'line',
-            line.type,
-            line.class,
-            {
-              closed:
-                lineToItem[line.key] &&
-                linkStateForm[lineToItem[line.key]] !== 0,
-              trafficCameraUnbound:
-                line.key === 5 && linkStateForm.trafficCamera === 2,
-            },
-          ]"
+          class="line"
+          :class="line.className"
         >
           <svg
-            v-if="
-              lineToItem[line.key] && linkStateForm[lineToItem[line.key]] === 0
-            "
+            v-if="line.linked"
             class="svg"
           >
             <polyline
@@ -111,11 +101,72 @@
 </template>
 
 <script>
-import videoPoster from '@/assets/image_no_program.svg'
 import { getBoundThirdPartyDevices } from '@/api/external'
-import { ScreenshotCache } from '@/utils/cache'
+import { ThirdPartyDevice } from '@/constant'
 import Box from './Box'
 
+const LinkItems = Object.freeze([
+  {
+    key: 'msr',
+    label: '浪潮屏媒安播云平台'
+  },
+  {
+    key: 'device',
+    label: ['浪潮超高清', '媒体播控器'],
+    multiLines: true
+  },
+  {
+    key: 'led',
+    label: 'LED大屏'
+  },
+  {
+    key: ThirdPartyDevice.GATEWAY,
+    alias: 'gateway',
+    label: '浪潮物联网关',
+    iconKey: 'gateway',
+    canClick: true
+  },
+  {
+    key: ThirdPartyDevice.TRAFFIC_CAMERA,
+    alias: 'traffic_camera',
+    label: '人流量监测摄像头',
+    iconKey: 'camera',
+    canClick: true
+  },
+  {
+    key: ThirdPartyDevice.LED_CAMERA,
+    alias: 'led_camera',
+    label: 'LED屏监测摄像头',
+    iconKey: 'camera',
+    canClick: true
+  },
+  {
+    key: ThirdPartyDevice.SENDING_CARD,
+    alias: 'sending_card',
+    label: '发送控制设备',
+    iconKey: 'sending-card',
+    canClick: true
+  },
+  {
+    key: ThirdPartyDevice.RECEIVING_CARD,
+    alias: 'receiving_card',
+    label: '接收卡',
+    iconKey: 'receiving-card',
+    canClick: true
+  }
+])
+
+const LineFromeTo = {
+  1: ['msr', 'device'],
+  2: ['device', ThirdPartyDevice.SENDING_CARD],
+  3: [ThirdPartyDevice.SENDING_CARD, ThirdPartyDevice.RECEIVING_CARD],
+  4: ['msr', ThirdPartyDevice.GATEWAY, ThirdPartyDevice.LED_CAMERA, ThirdPartyDevice.TRAFFIC_CAMERA],
+  5: ['msr', ThirdPartyDevice.TRAFFIC_CAMERA],
+  6: ['msr', ThirdPartyDevice.LED_CAMERA],
+  7: [ThirdPartyDevice.GATEWAY, 'led'],
+  8: [ThirdPartyDevice.RECEIVING_CARD, 'led']
+}
+
 export default {
   name: 'LinkState',
   components: {
@@ -130,204 +181,181 @@ export default {
   data () {
     return {
       fullscreen: false,
-      poster: videoPoster,
       scale: 1,
-      items: [
-        {
-          label: '浪潮屏媒安播云平台',
-          key: 'msr'
-        },
-        {
-          label: ['浪潮超高清媒体', '播控器'],
-          key: 'device'
-        },
-        {
-          label: '浪潮物联网关',
-          key: 'gateway'
-        },
-        {
-          label: '发送控制设备',
-          key: 'sending_device'
-        },
-        {
-          label: '人流量监测',
-          key: 'trafficCamera'
-        },
-        {
-          label: 'LED屏画面监测',
-          key: 'ledCamera'
-        },
-        {
-          label: '接收卡',
-          key: 'receive_card'
-        }
-        // {
-        //   label: 'LED大屏',
-        //   key: 'led'
-        // }
-      ],
-      lineToItem: {
-        1: 'msr',
-        2: 'device',
-        3: 'sending_device',
-        8: 'receive_card',
-        4: 'msr',
-        7: 'gateway',
-        5: 'trafficCamera',
-        6: 'ledCamera'
-      },
-      linkStateForm: {
-        // 0 开启 1 关闭 2未绑定
-        msr: 0,
-        device: 0,
-        gateway: 0,
-        sending_device: 0,
-        ledCamera: 0,
-        trafficCamera: 0,
-        receive_card: 0,
-        led: 0
-      },
+      loading: false,
       loaded: false,
-      base64: null
+      linkDeviceMap: null
     }
   },
-  // created () {
-  //   ScreenshotCache.watch(this.device, this.onScreenshotUpdate, 10000)
-  // },
   computed: {
     scaleStyle () {
       return this.fullscreen
         ? null
         : { transform: `scale(${this.scale})` }
     },
-    lines () {
-      return Array.from({ length: 8 }, (v, index) => {
-        // M4,0 L4,95
-        let map
-        if (this.fullscreen) {
-          map = [
-            {
-              points: '0,4 150,4',
-              path: 'M0,4 L150,4',
-              length: 150,
-              animation: this.getAnimation(150)
-            },
-            {
-              points: '0,4 150,4',
-              path: 'M0,4 L150,4',
-              length: 150,
-              animation: this.getAnimation(150)
-            },
-            {
-              points: '0,4 150,4',
-              path: 'M0,4 L150,4',
-              length: 150,
-              animation: this.getAnimation(150)
-            },
-            {
-              points: '4,0 4,137',
-              path: 'M4,0 L4,137',
-              type: 'col',
-              length: 137,
-              animation: this.getAnimation(137)
-            },
-            {
-              points: '0,50 350,50 350,0 400,0',
-              path: 'M0,50 L350,50 L350 0 L400,0',
-              type: 'polygon',
-              length: 450,
-              animation: this.getAnimation(450)
-            },
-            {
-              points: '0,51 350,51 350,100 400,100',
-              path: 'M0,51 L350,51 L350,100 L400,100',
-              type: 'polygon',
-              length: 449,
-              animation: this.getAnimation(449)
-            },
-            {
-              points: '0,4 494,4',
-              path: 'M0,4 L494,4',
-              length: 494,
-              animation: this.getAnimation(494)
-            },
-            {
-              points: '4,0 4,78',
-              path: 'M4,0 L4,78',
-              type: 'col',
-              length: 78,
-              animation: this.getAnimation(78)
-            }
-          ]
-        } else {
-          map = [
-            {
-              points: '0,4 22,4',
-              path: 'M0,4 L22,4',
-              length: 22,
-              animation: this.getAnimation(22)
-            },
-            {
-              points: '0,4 22,4',
-              path: 'M0,4 L22,4',
-              length: 22,
-              animation: this.getAnimation(22)
-            },
-            {
-              points: '0,4 22,4',
-              path: 'M0,4 L22,4',
-              length: 22,
-              animation: this.getAnimation(22)
-            },
-            {
-              points: '4,0 4,95',
-              path: 'M4,0 L4,95',
-              type: 'col',
-              length: 95,
-              animation: this.getAnimation(95)
-            },
-            {
-              points: '0,18 80,18 80 0 105 0',
-              path: 'M0,18 L80,18 L80 0 L105 0',
-              type: 'polygon',
-              length: 123,
-              animation: this.getAnimation(123)
-            },
-            {
-              points: '0,19 80,19 80 36 105 36',
-              path: 'M0,19 L80,19 L80 36 L105 36',
-              type: 'polygon',
-              length: 122,
-              animation: this.getAnimation(122)
-            },
-            {
-              points: '0,4 119,4',
-              path: 'M0,4 L119,4',
-              length: 119,
-              animation: this.getAnimation(119)
-            },
-            {
-              points: '4,0 4,92',
-              path: 'M4,0 L4,92',
-              type: 'col',
-              length: 92,
-              animation: this.getAnimation(92)
-            }
-          ]
+    online () {
+      return this.device.activate === 2 && this.device.onlineStatus === 1
+    },
+    linkState () {
+      const map = {
+        msr: 1,
+        device: this.online ? 1 : 0,
+        led: this.online ? 1 : 0,
+        [ThirdPartyDevice.GATEWAY]: -1,
+        [ThirdPartyDevice.LED_CAMERA]: -1,
+        [ThirdPartyDevice.TRAFFIC_CAMERA]: -1,
+        [ThirdPartyDevice.SENDING_CARD]: -1,
+        [ThirdPartyDevice.RECEIVING_CARD]: -1
+      }
+      if (this.linkDeviceMap) {
+        for (const item of this.linkDeviceMap) {
+          map[item.deviceType] = item.status
         }
-
+      }
+      return map
+    },
+    items () {
+      const map = this.linkState
+      return LinkItems.map(({ key, alias, label, iconKey, multiLines }) => {
+        const status = map[key]
         return {
-          key: index + 1,
-          class: `l${index + 1}`,
-          ...map[index]
+          key, label, status, multiLines,
+          icon: status === -1
+            ? 'link-unbound'
+            : `link-${iconKey || key}-${status === 1 ? 'online' : 'offline'}`,
+          className: alias || key
+        }
+      })
+    },
+    lines () {
+      const map = this.fullscreen
+        ? [
+          null,
+          {
+            points: '0,4 150,4',
+            path: 'M0,4 L150,4',
+            length: 150,
+            animation: this.getAnimation(150)
+          },
+          {
+            points: '0,4 150,4',
+            path: 'M0,4 L150,4',
+            length: 150,
+            animation: this.getAnimation(150)
+          },
+          {
+            points: '0,4 150,4',
+            path: 'M0,4 L150,4',
+            length: 150,
+            animation: this.getAnimation(150)
+          },
+          {
+            points: '4,0 4,137',
+            path: 'M4,0 L4,137',
+            type: 'col',
+            length: 137,
+            animation: this.getAnimation(137)
+          },
+          {
+            points: '0,50 350,50 350,0 400,0',
+            path: 'M0,50 L350,50 L350 0 L400,0',
+            type: 'polygon',
+            length: 450,
+            animation: this.getAnimation(450)
+          },
+          {
+            points: '0,51 350,51 350,100 400,100',
+            path: 'M0,51 L350,51 L350,100 L400,100',
+            type: 'polygon',
+            length: 449,
+            animation: this.getAnimation(449)
+          },
+          {
+            points: '0,4 494,4',
+            path: 'M0,4 L494,4',
+            length: 494,
+            animation: this.getAnimation(494)
+          },
+          {
+            points: '4,0 4,78',
+            path: 'M4,0 L4,78',
+            type: 'col',
+            length: 78,
+            animation: this.getAnimation(78)
+          }
+        ]
+        : [
+          null,
+          {
+            points: '0,4 22,4',
+            path: 'M0,4 L22,4',
+            length: 22,
+            animation: this.getAnimation(22)
+          },
+          {
+            points: '0,4 22,4',
+            path: 'M0,4 L22,4',
+            length: 22,
+            animation: this.getAnimation(22)
+          },
+          {
+            points: '0,4 22,4',
+            path: 'M0,4 L22,4',
+            length: 22,
+            animation: this.getAnimation(22)
+          },
+          {
+            points: '4,0 4,95',
+            path: 'M4,0 L4,95',
+            type: 'col',
+            length: 95,
+            animation: this.getAnimation(95)
+          },
+          {
+            points: '0,18 80,18 80 0 105 0',
+            path: 'M0,18 L80,18 L80 0 L105 0',
+            type: 'polygon',
+            length: 123,
+            animation: this.getAnimation(123)
+          },
+          {
+            points: '0,19 80,19 80 36 105 36',
+            path: 'M0,19 L80,19 L80 36 L105 36',
+            type: 'polygon',
+            length: 122,
+            animation: this.getAnimation(122)
+          },
+          {
+            points: '0,4 119,4',
+            path: 'M0,4 L119,4',
+            length: 119,
+            animation: this.getAnimation(119)
+          },
+          {
+            points: '4,0 4,92',
+            path: 'M4,0 L4,92',
+            type: 'col',
+            length: 92,
+            animation: this.getAnimation(92)
+          }
+        ]
+      const state = this.linkState
+      return Object.keys(LineFromeTo).map(key => {
+        const from = LineFromeTo[key][0]
+        const to = LineFromeTo[key].slice(1)
+        const linked = state[from] === 1 && to.some(key => state[key] === 1)
+        return {
+          ...map[key],
+          linked,
+          key: `line${key}`,
+          className: [`l${key}`, linked ? '' : 'closed'].join(' ')
         }
       })
     }
   },
   created () {
-    // this.getBoundThirdPartyDevices();
-    this.loaded = true
-    ScreenshotCache.watch(this.device, this.onScreenshotUpdate, 60 * 60 * 1000)
+    this.getBoundThirdPartyDevices()
+    this.$timer = setInterval(this.getBoundThirdPartyDevices, 10000)
   },
   mounted () {
     this.onFullscreen(this.fullscreen)
@@ -336,107 +364,26 @@ export default {
     this.scale = Math.min(278 / width, 244 / height)
   },
   beforeDestroy () {
-    ScreenshotCache.unwatch(this.device.id)
+    clearInterval(this.$timer)
   },
   methods: {
     onFullscreen (fullscreen) {
       this.fullscreen = fullscreen
     },
     getAnimation (length, gap, dur = 2, mode = 'linear') {
-      // let style = {
-      //   strokeDasharray: undefined,
-      //   strokeDashoffset: undefined,
-      //   animation: undefined,
-      // };
-      // let str = "",
-      //   i = 0;
-      // gap = 4;
-      // while (i + gap <= length) {
-      //   i = i + gap;
-      //   str = str + gap + "px ";
-      // }
-      // str = str + length + "px";
-      // style.strokeDasharray = str;
-      // style.strokeDashoffset = length + "px";
-      // style.animation = `${dur}s linkLength${length} ${mode} infinite`;
-      // return style;
       return `${dur}s linkLength${length} ${mode} infinite`
     },
-    onScreenshotUpdate ({ waiting, base64 }) {
-      if (!waiting) {
-        this.base64 = base64
-      }
-    },
     getBoundThirdPartyDevices () {
-      getBoundThirdPartyDevices(this.device.id, true)
-        .then(({ data, success }) => {
-          if (!success) {
-            return
-          }
-          const map = {
-            0: 'gateway',
-            1: 'receive_card',
-            2: 'sending_device',
-            // 3: 'led'
-            4: 'ledCamera',
-            5: 'trafficCamera'
-          }
-          const form = {
-            device:
-              this.device.onlineStatus === 1 && this.device.activate === 2
-                ? 0
-                : 1,
-            gateway: 2,
-            sending_device: 2,
-            ledCamera: 2,
-            trafficCamera: 2,
-            receive_card: 2,
-            led:
-              this.device.onlineStatus === 1 && this.device.activate === 2
-                ? 0
-                : 1,
-            msr: 0
-          }
-          for (const item of data) {
-            form[map[item.deviceType]] = item.status === 1 ? 0 : 1
-          }
-          this.linkStateForm = form
-        })
-        .catch(err => {
-          console.log('err', err)
-        })
-        .finally(() => {
-          this.loaded = true
-        })
-    },
-    getImg (key) {
-      const value = this.linkStateForm[key]
-      let background = ''
-      let params = {}
-      switch (key) {
-        case 'msr':
-          background = `url("${require('@/assets/linkState/icon_msr.svg')}")`
-          break
-        case 'led':
-          value === 0
-            ? (background = `url("${
-              this.base64
-                ? this.base64
-                : require('@/assets/image_no_program.svg')
-            }")`)
-            : (params = { backgroundColor: '#333333' })
-          break
-        default:
-          if (value === 2) {
-            background = `url("${require('@/assets/linkState/icon_unbound.svg')}")`
-          } else {
-            background = `url("${require(`@/assets/linkState/icon_${
-              key === 'trafficCamera' || key === 'ledCamera' ? 'camera' : key
-            }_${value === 0 ? 'online' : 'offline'}.svg`)}")`
-          }
-          break
+      if (this.loading) {
+        return
       }
-      return { backgroundImage: background, ...params }
+      this.loading = true
+      getBoundThirdPartyDevices(this.device.id, true).then(({ data }) => {
+        this.linkDeviceMap = data
+        this.loaded = true
+      }).finally(() => {
+        this.loading = false
+      })
     }
   }
 }
@@ -456,6 +403,11 @@ export default {
   height: 244px;
   transform-origin: center;
 
+  &__img {
+    width: 100%;
+    height: 100%;
+  }
+
   .co {
     position: absolute;
     background-position: center;
@@ -520,22 +472,22 @@ export default {
   }
 
   .msr {
-    @include getPosition(0, 0, 90px, 90px);
+    @include getPosition(0, 0, 92px, 92px);
   }
 
   .device {
-    @include getPosition(108px, 37px, 40px, 40px);
+    @include getPosition(108px, 32px, 44px, 48px);
   }
 
-  .sending_device {
-    @include getPosition(176px, 37px, 40px, 40px);
+  .sending_card {
+    @include getPosition(176px, 32px, 44px, 48px);
   }
 
-  .receive_card {
-    @include getPosition(234px, 33px, 45px, 45px);
+  .receiving_card {
+    @include getPosition(234px, 32px, 44px, 48px);
   }
 
-  .trafficCamera {
+  .traffic_camera {
     @include getPosition(141px, 107px, 40px, 40px);
 
     .ctext {
@@ -544,7 +496,7 @@ export default {
     }
   }
 
-  .ledCamera {
+  .led_camera {
     @include getPosition(141px, 145px, 40px, 40px);
 
     .ctext {
@@ -559,14 +511,6 @@ export default {
 
   .led {
     @include getPosition(182px, 168px, 96px, 54px);
-
-    &.mini {
-      ::v-deep .o-video__btn {
-        width: 32px;
-        height: 32px;
-        font-size: 24px;
-      }
-    }
   }
 
   .l1 {
@@ -641,15 +585,15 @@ export default {
       @include getPosition(389px, 118px, 90px, 90px);
     }
 
-    .sending_device {
+    .sending_card {
       @include getPosition(622px, 118px, 90px, 90px);
     }
 
-    .receive_card {
+    .receiving_card {
       @include getPosition(854px, 118px, 90px, 90px);
     }
 
-    .trafficCamera {
+    .traffic_camera {
       @include getPosition(511px, 213px, 90px, 90px);
 
       .ctext {
@@ -657,7 +601,7 @@ export default {
       }
     }
 
-    .ledCamera {
+    .led_camera {
       @include getPosition(511px, 308px, 90px, 90px);
 
       .ctext {

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

@@ -53,7 +53,7 @@
         <component
           :is="active"
           :key="active"
-          class="l-flex__fill c-detail__wrapper has-padding u-overflow-y--auto"
+          class="l-flex__fill c-detail__wrapper has-gap u-overflow-y--auto"
           :device="device"
           :online="isOnline"
         />