Pārlūkot izejas kodu

feat(dashboard): v2.0

Casper Dai 2 gadi atpakaļ
vecāks
revīzija
2a42683de0

+ 1 - 1
src/views/dashboard/v1/AlarmInfo.vue

@@ -144,7 +144,7 @@ export default {
     }
     &__type {
       font-size: 32px;
-
+      padding: 0 10px;
       line-height: 48px;
       font-weight: bold;
       margin: 15px 0 15px 0;

+ 1 - 1
src/views/dashboard/v1/AlarmType.vue

@@ -31,7 +31,7 @@ export default {
     getDeviceExceptionTypeStatistics () {
       getDeviceExceptionTypeStatistics()
         .then(({ data: typeList }) => {
-          typeList = typeList.map(i => {
+          this.typeList = typeList = typeList.map(i => {
             return { name: i.typeName, value: Number(i.count) }
           })
           typeList.sort((a, b) => b.value - a.value)

+ 19 - 5
src/views/dashboard/v1/CameraScreen.vue

@@ -46,11 +46,26 @@ export default {
   methods: {
     getCameras () {
       getCameras(this.options.params).then(({ data }) => {
-        const cameras = data.filter(i => i.onlineStatus === 1)
-        if (cameras.length >= 4) {
-          this.options.list = cameras
+        if (data.length <= 4) {
+          this.options.list = data
         } else {
-          this.options.list = cameras.concat(data).slice(0, 4)
+          const cameras = []
+          const camerasOffline = []
+          const length = data.length
+          for (let i = 0; i < length; i++) {
+            if (data[i].onlineStatus === 1) {
+              cameras.push(data[i])
+            } else {
+              camerasOffline.push(data[i])
+            }
+            if (cameras.length === 4) {
+              break
+            }
+          }
+          if (cameras.length < 4) {
+            cameras.push(...camerasOffline.slice(0, 4 - cameras.length))
+          }
+          this.options.list = cameras
         }
       })
     }
@@ -61,7 +76,6 @@ export default {
 <style lang="scss" scoped>
 .c-record-grid {
   display: grid;
-  //   grid-template-rows: 1fr 1fr 1fr;
   grid-template-rows: 1fr 1fr;
   grid-template-columns: 1fr 1fr;
   grid-row-gap: 20px;

+ 1 - 0
src/views/dashboard/v1/Header.vue

@@ -1,5 +1,6 @@
 <template>
   <div class="c-device-dashboard-header">
+    <slot name="content" />
     <div class="c-device-dashboard-header__name u-bold">{{ title }}</div>
     <div class="c-device-dashboard-header__time">
       <span>{{ date }}</span>

+ 179 - 136
src/views/dashboard/v1/Map.vue

@@ -15,6 +15,13 @@
           :style="alarmPositionStyle"
           class="c-alarm"
         />
+        <AlarmInfo
+          v-for="(item, index) in alarmList.slice(1,5)"
+          :key="item.id"
+          :alarm="item"
+          :style="alarmStyleMap[index]"
+          class="c-alarm--sub"
+        />
       </div>
     </div>
   </box>
@@ -22,6 +29,7 @@
 
 <script>
 import Box from './Box'
+import { Bounds } from './config'
 import DeviceStatus from './DeviceStatus'
 import AlarmInfo from './AlarmInfo'
 import AMapLoader from '@amap/amap-jsapi-loader'
@@ -46,17 +54,36 @@ export default {
     },
     city: {
       type: String,
-      default: '中国'
+      default: '深圳市'
     }
   },
   data () {
     this.curMarker = null
     this.polylines = null
+    this.alarmStyleMap = [
+      {
+        top: 0,
+        left: 0
+      },
+      {
+        top: 0,
+        left: '720px'
+      },
+      {
+        bottom: 0,
+        left: 0
+      },
+      {
+        bottom: 0,
+        left: '720px'
+      }
+    ]
     return {
       marks: [],
       alarmPosition: [],
       isShowAlarm: false,
-      alarm: {}
+      alarm: {},
+      alarmList: []
     }
   },
   computed: {
@@ -91,46 +118,47 @@ export default {
         plugins: ['AMap.DistrictSearch']
       }).then(AMap => {
         this.$AMap = AMap
-
-        const options = {
-          subdistrict: 2,
-          extensions: 'all',
-          level: 'province'
+        /*
+        bounds 里存多个边界区域  配合map 的mask 属性 可只显示该区域
+        边界区域获取方式
+        1. new AMap.DistrictSearch  获取省市区  街道无
+        2. 自定义  手动捡
+        高德地图 F12 接口获取
+        点击目标点 出现 蓝色轮廓时 过滤 detail接口  数据  data.spec.mining_shape.shape 获取分号相隔的经纬度字符串
+        辅助转化函数 str.split(';').map(i=>i.split(',').map(i=>Number))
+        后续 可以  new AMap.Polyline画轮廓  setFiew 定位轮廓
+        */
+        const bounds = [Bounds.inspur]
+        const mask = []
+        for (let i = 0; i < bounds.length; i++) {
+          mask.push([bounds[i]])
         }
 
-        const district = new AMap.DistrictSearch(options)
-        // 查询区域
-        district.search(this.city, (status, result) => {
-          const bounds = result.districtList[0]['boundaries']
-          const mask = []
-          console.log(result)
-          for (let i = 0; i < bounds.length; i++) {
-            mask.push([bounds[i]])
-          }
-
-          // 实例化地图
-          const map = new AMap.Map(this.$refs.map, {
-            mask,
-            // expandZoomRange: true, // 开启显示范围设置
-            // zooms: [7, 20], // 最小显示级别为7,最大显示级别为20
-            // viewMode: '3D', // 这里特别注意,设置为3D则其它地区不显示
-            zoomEnable: true, // 是否可以缩放地图
-            // resizeEnable: true
-            mapStyle: 'amap://styles/darkblue'
-          })
-          this.map = map
+        // 实例化地图
+        const map = new AMap.Map(this.$refs.map, {
+          mask,
+          // expandZoomRange: true, // 开启显示范围设置
+          // zooms: [7, 20], // 最小显示级别为7,最大显示级别为20
+          // viewMode: '3D', // 这里特别注意,设置为3D则其它地区不显示
+          zoomEnable: true, // 是否可以缩放地图
+          // resizeEnable: true
+          mapStyle: 'amap://styles/darkblue'
+        })
+        this.map = map
 
-          this.marks = []
-          this.$deviceMap = {}
+        this.marks = []
+        this.$deviceMap = {} // 当前地图 有标记点的设备  (可能切换部门变少)
+        this.$alldeviceMap = {} // 警告是全部设备的  可能因为切换部门 地图上没有警告对应设备的标记点无法定位 所以需要一个全部的坐标map
 
-          this.$onlineIcon = new AMap.Icon({
-            image: onlineIcon
-          })
-          this.$offlineIcon = new AMap.Icon({
-            image: offlineIcon
-          })
+        this.$onlineIcon = new AMap.Icon({
+          image: onlineIcon
+        })
+        this.$offlineIcon = new AMap.Icon({
+          image: offlineIcon
+        })
 
-          this.deviceList.forEach(({ id, longitude, latitude, name, onlineStatus, address, mac }) => {
+        this.deviceList.forEach(
+          ({ id, longitude, latitude, name, onlineStatus, address, mac }) => {
             if (longitude && latitude) {
               const markObj = new AMap.Marker({
                 position: [Number(longitude), Number(latitude)],
@@ -140,61 +168,55 @@ export default {
               this.marks.push(markObj)
               this.$deviceMap[id] = {
                 onlineStatus,
-                markObj, address, mac
+                markObj,
+                address,
+                mac
+              }
+              this.$allDeviceMap[id] = {
+                onlineStatus,
+                markObj,
+                address,
+                mac
               }
             }
-          })
-          map.add(this.marks)
-
-          const polylines = []
-          // 添加描边
-          for (let i = 0; i < bounds.length; i += 1) {
-            polylines.push(
-              new AMap.Polyline({
-                path: bounds[i],
-                strokeColor: '#182C65',
-                strokeWeight: 0,
-                map
-              })
-            )
           }
-          this.polylines = polylines
-          this.resetView()
-          map.on('complete', () => {
-            map.on('zoomstart', () => {
-              this.isShowAlarm = false
-            })
-            map.on('movestart', () => {
-              this.isShowAlarm = false
+        )
+        map.add(this.marks)
+        const polylines = []
+        // 添加描边
+        for (let i = 0; i < bounds.length; i += 1) {
+          polylines.push(
+            new AMap.Polyline({
+              path: bounds[i],
+              strokeColor: '#182C65',
+              strokeWeight: 0,
+              map
             })
+          )
+        }
+        this.polylines = polylines
+        this.resetView()
+        map.on('complete', () => {
+          map.on('zoomstart', () => {
+            this.isShowAlarm = false
+          })
+          map.on('movestart', () => {
+            this.isShowAlarm = false
           })
-
-          // 区级线条
-          for (const item of result.districtList[0].districtList) {
-            district.search(item.adcode, (status, result) => {
-              const bounds = result.districtList[0]['boundaries']
-              for (let i = 0; i < bounds.length; i++) {
-                new AMap.Polyline({
-                  path: bounds[i],
-                  strokeColor: '#182C65',
-                  strokeWeight: 4,
-                  map
-                })
-              }
-            })
-          }
         })
       })
     },
     setNewAlarm (alarm) {
-      const { markObj: marker, mac, address } = this.$deviceMap[alarm.deviceId]
+      const {
+        markObj: marker,
+        mac,
+        address
+      } = this.$alldeviceMap[alarm.deviceId] || {}
+      this.alarm = { ...alarm, mac, address, status: alarm.status.label }
+      this.alarmList.unshift(this.alarm)
       if (!marker) {
         return
       }
-      this.alarm = { ...alarm, mac, address, status: alarm.status.label }
-      alarm.status = alarm.status?.label
-      alarm.mac = marker._originOpts.mac
-      alarm.address = marker._originOpts.address
       //
       this.map.on('zoomend', this.onSetNewAlarmFitView)
       this.map.on('moveend', this.onSetNewAlarmFitView)
@@ -212,70 +234,87 @@ export default {
     showAlarm (position) {
       this.map.off('zoomend', this.onSetNewAlarmFitView)
       this.map.off('moveend', this.onSetNewAlarmFitView)
-      const x = this.$refs.map.offsetWidth
+
       // const y = this.$refs.map.offsetHeight
-      const padding = 40
+
       const width = 700 / 2
       const height = 420
       let left = position[0]
       let top = position[1]
-      if (left < width) {
-        left = 0
-      } else if (left + width > x) {
-        left = x - width * 2
-      } else {
-        left -= width
-      }
-      if (left < padding) {
-        left = padding
-      }
-      if (top - height - padding > 0) {
-        top = top - height - padding
-      } else {
-        top += padding
-      }
+      // 弹出框在标记点上方的逻辑
+      // const x = this.$refs.map.offsetWidth
+      // const padding = 40
+      // if (left < width) {
+      //   left = 0
+      // } else if (left + width > x) {
+      //   left = x - width * 2
+      // } else {
+      //   left -= width
+      // }
+      // if (left < padding) {
+      //   left = padding
+      // }
+      // if (top - height - padding > 0) {
+      //   top = top - height - padding
+      // } else {
+      //   top += padding
+      // }
+
+      // 弹出框在标记点中间的逻辑
+      left -= width
+      top -= height / 2
 
+      //
       this.alarmPosition = [`${left}px`, `${top}px`]
       this.isShowAlarm = true
     },
     refreshMarkers () {
       const map = {}
       const arr = []
-      this.deviceList.forEach(({ id, longitude, latitude, name, onlineStatus, address, mac }) => {
-        if (longitude && latitude) {
-          const device = this.$deviceMap[id]
-          if (device) {
-            device.onlineStatus = onlineStatus
-            device.markObj.setPosition([Number(longitude), Number(latitude)])
-            device.markObj.setIcon(onlineStatus === 1 ? this.$onlineIcon : this.$offlineIcon)
-            map[id] = device
-            delete this.$deviceMap[id]
-          } else {
-            const markObj = new this.$AMap.Marker({
-              position: [Number(longitude), Number(latitude)],
-              title: name,
-              icon: onlineStatus === 1 ? this.$onlineIcon : this.$offlineIcon
-            })
-            map[id] = {
-              onlineStatus,
-              markObj,
-              address, mac
+      this.deviceList.forEach(
+        ({ id, longitude, latitude, name, onlineStatus, address, mac }) => {
+          if (longitude && latitude) {
+            const device = this.$deviceMap[id]
+            if (device) {
+              device.onlineStatus = onlineStatus
+              device.markObj.setPosition([Number(longitude), Number(latitude)])
+              device.markObj.setIcon(
+                onlineStatus === 1 ? this.$onlineIcon : this.$offlineIcon
+              )
+              map[id] = device
+              delete this.$deviceMap[id]
+            } else {
+              const markObj = new this.$AMap.Marker({
+                position: [Number(longitude), Number(latitude)],
+                title: name,
+                icon: onlineStatus === 1 ? this.$onlineIcon : this.$offlineIcon
+              })
+              map[id] = {
+                onlineStatus,
+                markObj,
+                address,
+                mac
+              }
+              arr.push(markObj)
             }
-            arr.push(markObj)
           }
         }
-      })
+      )
       if (arr.length) {
         this.map.add(arr)
       }
-      const marks = Object.values(this.$deviceMap).filter(Boolean).map(({ markObj }) => markObj)
+      const marks = Object.values(this.$deviceMap)
+        .filter(Boolean)
+        .map(({ markObj }) => markObj)
       if (marks.length) {
         this.map.remove(marks)
       }
       // 重构 $deviceMap   包含 delete 和  add 的
+      this.$deviceMap = {} // 只包含当前地图上的设备(按部门)
       for (const key in map) {
         if (Object.hasOwnProperty.call(map, key)) {
           this.$deviceMap[key] = map[key]
+          this.$alldeviceMap[key] = map[key]
         }
       }
     }
@@ -290,24 +329,28 @@ export default {
   }
   .c-alarm {
     position: absolute;
-    z-index: 9;
+    z-index: 99;
+    &--sub {
+      position: absolute;
+      z-index: 9;
+    }
   }
 }
-  @keyframes sparkle {
-    0% {
-      opacity: 1;
-    }
-    40% {
-      opacity: 1;
-    }
-    50% {
-      opacity: 0;
-    }
-    60% {
-      opacity: 1;
-    }
-    100% {
-      opacity: 1;
-    }
+@keyframes sparkle {
+  0% {
+    opacity: 1;
+  }
+  40% {
+    opacity: 1;
+  }
+  50% {
+    opacity: 0;
   }
+  60% {
+    opacity: 1;
+  }
+  100% {
+    opacity: 1;
+  }
+}
 </style>

+ 0 - 13
src/views/dashboard/v1/MessageNotice.vue

@@ -122,10 +122,6 @@ export default {
     VueSeamlessScroll
   },
   props: {
-    deviceList: {
-      type: Array,
-      required: true
-    }
   },
   data () {
     return {
@@ -152,15 +148,6 @@ export default {
         payload = JSON.parse(payload)
       }
       payload.deviceId += ''
-      // const demo = {
-      //   deviceId: '',
-      //   deviceName: 'TB60',
-      //   level: 0,
-      //   warnLevel: '提示',
-      //   bugName: '播控器上线',
-      //   happenTime: '2023-2-23 19:00:01',
-      //   tenant: '/msr'
-      // }
       console.log(`%c大屏dashboard/event`, 'color: red')
       console.log(payload)
       const alarm = {

+ 13 - 2
src/views/dashboard/v1/NewNotice.vue

@@ -1,9 +1,12 @@
 <template>
   <div class="c-newNoctice l-flex--col">
-    <div class="c-newNoctice--header l-flex--row">消息通知</div>
+    <div class="c-newNoctice--header l-flex--row">
+      <div class="l-flex__fill">消息通知</div>
+      <div class="l-flex__none c-count">{{ count }}s</div>
+    </div>
     <div class="c-block l-flex--col jcenter center l-flex__fill">
       <div class="c-block--time">{{ notice.happenTime }}</div>
-      <div class="c-block--info u-bold">{{ notice.type }}</div>
+      <div class="c-block--info u-bold">{{ notice.deviceName }}{{ notice.type }}</div>
     </div>
   </div>
 </template>
@@ -15,6 +18,10 @@ export default {
     notice: {
       type: Object,
       required: true
+    },
+    count: {
+      type: Number,
+      default: 0
     }
   },
   data () {
@@ -41,7 +48,11 @@ export default {
     margin-bottom: 30px;
   }
   &--info {
+    padding: 0 10px;
     color: #000000;
   }
 }
+.c-count{
+  // color: #fc5531;
+}
 </style>

+ 29 - 2
src/views/dashboard/v1/ProgramRate.vue

@@ -40,21 +40,48 @@ export default {
   methods: {
     getAssetTagSummary () {
       getAssetTagSummary().then(({ data }) => {
+        const quantile = 10 // 保留一位小数
+        // 策略 非0则最小0.1 最大的为100-其它项和
         const map = {}
         let total = 0
+        const tagsNum = 4 // 1~4
+        const max = {
+          value: 0,
+          index: 1 // tag
+        }
         for (const item of data) {
           map[item.tag] = +item.count
           total += +item.count
+          if (map[item.tag] > max.value) {
+            max.value = map[item.tag]
+            max.index = item.tag
+          }
         }
         const result = []
-        for (let index = 1; index <= 4; index++) {
+        let minSum = 0
+        for (let index = 1; index <= tagsNum; index++) {
+          let calValue = 0
+          if (map[index]) {
+            calValue = Math.max(Math.floor((map[index] * 100 * quantile) / total), 1)
+          } else {
+            calValue = 0
+            map[index] = 0
+          }
+          if (index !== max.index) {
+            minSum += calValue
+          }
           result.push({
             name: AssetTagInfo[index],
             key: index,
             color: this.colorMap[index],
-            value: ((map[index] * 100 || 0) / total).toFixed(1)
+            calValue,
+            value: calValue / quantile
           })
         }
+        if (data.length) {
+          result[max.index - 1].value = (100 * quantile - minSum) / quantile
+        }
+
         this.list = result
       })
     }

+ 76 - 90
src/views/dashboard/v1/ProgramTop.vue

@@ -1,59 +1,79 @@
 <template>
-  <Box title="节目播放TOP5">
-    <div class="l-flex__fill l-flex--col c-top">
-      <status-wrapper
-        v-if="list.length === 0"
-        class="l-flex__fill l-flex--row center"
-      />
-      <template v-else>
-        <div
-          v-for="item in list"
-          :key="item.key"
-          :item="item"
-          class="l-flex__fill block"
-        >
-          <div class="block__intro">
-            <div class="block__name">{{ item.name }}</div>
-            <div class="block__value">{{ item.value }}</div>
-          </div>
-          <div class="block__progress--outer">
-            <div
-              class="block__progress--inner"
-              :style="{ width: item.rate }"
-            />
-          </div>
-        </div>
-      </template>
-    </div>
+  <Box :title="methodsMap[type]">
+    <TopRankChart
+      :list="list"
+      :is-time="['getPageDurationRank','getTopOnlineSeconds'].includes(type)"
+      :is-money="['getPagePriceRank'].includes(type)"
+    />
   </Box>
 </template>
 
 <script>
 import Box from './Box'
-import { getResources } from './api'
+import TopRankChart from './TopRankChart'
+import { createListOptions } from '@/utils'
 import { ProgramTop } from './config'
+import {
+  getResources,
+  getPageDurationRank,
+  getPagePriceRank,
+  getPageCountRank,
+  getTopOnlineSeconds
+} from './api'
+const request = {
+  getResources,
+  getPageDurationRank,
+  getPagePriceRank,
+  getPageCountRank,
+  getTopOnlineSeconds
+}
+
 export default {
   name: 'ProgramTop',
   components: {
-    Box
+    Box,
+    TopRankChart
+  },
+  props: {
+    type: {
+      type: String,
+      default: 'getResources'
+    }
   },
   data () {
+    this.methodsMap = {
+      getResources: '节目播放TOP5',
+      getPageDurationRank: '下单时长TOP5',
+      getPagePriceRank: '下单金额TOP5',
+      getPageCountRank: '下单次数TOP5',
+      getTopOnlineSeconds: '设备在线TOP5'
+    }
     return {
-      list: [
-      ]
+      list: [],
+      options: createListOptions({ pageSize: 100 })
     }
   },
-  mounted () {
-    this.getRate()
-    this.$timer = setInterval(this.getRate, ProgramTop.timer)
+  watch: {
+    type () {
+      this.init()
+    }
+  },
+  created () {
+    this.init()
   },
   beforeDestroy () {
     clearInterval(this.$timer)
   },
   methods: {
-    getRate () {
+    init () {
+      this.list = []
+      clearInterval(this.$timer)
+      this[this.type]()
+      this.$timer = setInterval(this[this.type], ProgramTop.timer)
+    },
+    getResources () {
       getResources().then(({ data }) => {
-        data = data
+        this.list = data
           .slice(1, -1)
           .split(',')
           .map(i => {
@@ -62,70 +82,36 @@ export default {
               value: +i.split('=')[1]
             }
           })
-        this.list = data
           .sort((a, b) => b.value - a.value)
           .slice(0, 5)
+      })
+    },
+    getPageDurationRank () {
+      this.getDeviceRank('getPageDurationRank')
+    },
+    getPagePriceRank () {
+      this.getDeviceRank('getPagePriceRank')
+    },
+    getPageCountRank () {
+      this.getDeviceRank('getPageCountRank')
+    },
+    getTopOnlineSeconds () {
+      this.getDeviceRank('getTopOnlineSeconds', 5, 'deviceName', 'onlineSeconds')
+    },
+    getDeviceRank (method, params = this.options.params, nameKey = 'deviceName', valueKey = 'value') {
+      request[method](params).then(({ data }) => {
+        this.list = data
           .map(i => {
             return {
-              name: i.name,
-              value: i.value
+              name: i[nameKey],
+              value: +i[valueKey]
             }
           })
-
-        if (this.list.length === 1) {
-          this.list[0].rate = '100%'
-          return
-        }
-        let total = this.list.reduce((total, cur) => total + cur.value, 0)
-        total *= 0.6
-        this.list = this.list.map(i => {
-          return {
-            ...i,
-            rate: `${(i.value * 100) / total}%`
-          }
-        })
+          .sort((a, b) => b.value - a.value)
+          .slice(0, 5)
       })
     }
   }
 }
 </script>
 
-<style lang="scss" scoped>
-.block {
-  height: 20%;
-  padding-top: 20px;
-  &:nth-child(1) {
-    padding-top: 0;
-  }
-  &__intro {
-    display: flex;
-    justify-content: space-between;
-    margin-bottom: 10px;
-  }
-  &__name {
-    font-size: 18px;
-    color: #9ea9cd;
-    line-height: 27px;
-    height: 18px;
-  }
-  &__value {
-    font-size: 18px;
-    line-height: 21px;
-  }
-  &__progress--outer {
-    height: 20px;
-    position: relative;
-    background-color: #060920;
-    width: 100%;
-    .block__progress--inner {
-      position: absolute;
-      width: 0;
-      top: 0;
-      left: 0;
-      bottom: 0;
-      background-color: #00d1ff;
-      transition: all 1s;
-    }
-  }
-}
-</style>

+ 29 - 9
src/views/dashboard/v1/Record.vue

@@ -40,14 +40,15 @@
 </template>
 
 <script>
-import { getDevices } from '@/api/device'
+import { getDeviceAttentionList } from '@/api/device'
 import { createListOptions } from '@/utils'
-import Box from './Box'
+import { Record } from './config'
 import DevicePlayer from './components/DevicePlayer'
+import Box from './Box'
+
 const offlineIcon = require('@/assets/v1/icon_offline.svg')
 const onlineIcon = require('@/assets/v1/icon_online.svg')
 const waitIcon = require('@/assets/v1/icon_wait.svg')
-import { Record } from './config'
 
 export default {
   name: 'Record',
@@ -57,8 +58,7 @@ export default {
   },
   data () {
     return {
-      // options: createListOptions({ activate: 1, onlineStatus: 1, pageSize: 9 })
-      options: createListOptions({ activate: 1, pageSize: 9 })
+      options: createListOptions({ activate: 1, pageSize: 4 })
     }
   },
   created () {
@@ -82,8 +82,28 @@ export default {
       return offlineIcon
     },
     getDevices () {
-      getDevices(this.options.params, { custom: true }).then(({ data }) => {
-        this.options.list = data
+      getDeviceAttentionList(this.options.params, { custom: true }).then(({ data }) => {
+        if (data.length <= 4) {
+          this.options.list = data
+        } else {
+          const cameras = []
+          const camerasOffline = []
+          const length = data.length
+          for (let i = 0; i < length; i++) {
+            if (data[i].onlineStatus === 1) {
+              cameras.push(data[i])
+            } else {
+              camerasOffline.push(data[i])
+            }
+            if (cameras.length === 4) {
+              break
+            }
+          }
+          if (cameras.length < 4) {
+            cameras.push(...camerasOffline.slice(0, 4 - cameras.length))
+          }
+          this.options.list = cameras
+        }
       })
     }
   }
@@ -93,8 +113,8 @@ export default {
 <style lang="scss" scoped>
 .c-record-grid {
   display: grid;
-  grid-template-rows: 1fr 1fr 1fr;
-  grid-template-columns: 1fr 1fr 1fr;
+  grid-template-rows: 1fr 1fr;
+  grid-template-columns: 1fr 1fr;
   grid-row-gap: 20px;
   grid-column-gap: 20px;
 }

+ 168 - 0
src/views/dashboard/v1/TopRankChart.vue

@@ -0,0 +1,168 @@
+<template>
+  <div class="l-flex__fill l-flex--col">
+    <status-wrapper
+      v-if="list.length === 0"
+      class="l-flex__fill l-flex--row center"
+    />
+    <template v-else>
+      <div
+        v-for="item in progressData"
+        :key="item.key"
+        class="l-flex__fill block"
+      >
+        <div class="block__intro">
+          <div class="block__name">{{ item.name }}</div>
+          <div class="block__value">{{ item.value }}</div>
+        </div>
+        <div class="block__progress--outer">
+          <div
+            class="block__progress--inner"
+            :style="{ width: item.rate }"
+          />
+        </div>
+      </div>
+    </template>
+  </div>
+</template>
+
+<script>
+// 秒数转化为时分秒
+function formatSeconds (value) {
+  let secondTime = parseInt(value)// 秒
+  let minuteTime = 0// 分
+  let hourTime = 0// 小时
+  if (secondTime > 60) { // 如果秒数大于60,将秒数转换成整数
+    // 获取分钟,除以60取整数,得到整数分钟
+    minuteTime = parseInt(secondTime / 60)
+    // 获取秒数,秒数取余,得到整数秒数
+    secondTime = parseInt(secondTime % 60)
+    // 如果分钟大于60,将分钟转换成小时
+    if (minuteTime > 60) {
+      // 获取小时,获取分钟除以60,得到整数小时
+      hourTime = parseInt(minuteTime / 60)
+      // 获取小时后取余的分,获取分钟除以60取余的分
+      minuteTime = parseInt(minuteTime % 60)
+    }
+  }
+  let result = `${parseInt(secondTime)}秒`
+
+  if (minuteTime > 0) {
+    result = `${parseInt(minuteTime)}分${result}`
+  }
+  if (hourTime > 0) {
+    result = `${parseInt(hourTime)}小时${result}`
+  }
+  console.log('result', result)
+  return result
+}
+export default {
+  name: 'TopRankChart',
+  props: {
+    list: {
+      type: Array,
+      default: () => []
+    },
+    isTime: {
+      type: Boolean,
+      default: false
+    },
+    isMoney: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data () {
+    return {
+      progressData: []
+    }
+  },
+  watch: {
+    list: {
+      handler () {
+        console.log('transform')
+        this.transform()
+      },
+      immediate: true
+    }
+  },
+  methods: {
+    transform () {
+      if (!this.list) {
+        this.progressData = []
+        return
+      }
+      this.progressData = this.list.slice()
+      // if (this.progressData.length === 1) {
+      //   this.progressData[0].rate = '100%'
+      //   return
+      // }
+      this.progressData = this.progressData
+        .sort((a, b) => b.value - a.value)
+        .slice(0, 5)
+      let max = 0
+      let total = this.progressData.reduce(
+        (total, cur) => {
+          max = Math.max(max, cur.value)
+          return total + cur.value
+        },
+        0
+      )
+      total = Math.max(max, total *= 0.6)
+      this.progressData = this.progressData.map(i => {
+        const item = {
+          ...i,
+          rate: `${(i.value * 100) / total}%`
+        }
+        if (this.isTime) {
+          item.value = formatSeconds(item.value)
+        } else if (this.isMoney) {
+          item.value = `${(item.value / 100).toFixed(2)}元`
+        } else {
+          item.value += '次'
+        }
+        return item
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.block {
+  height: 20%;
+  padding-top: 20px;
+  &:nth-child(1) {
+    padding-top: 0;
+  }
+  &__intro {
+    display: flex;
+    justify-content: space-between;
+    margin-bottom: 10px;
+  }
+  &__name {
+    font-size: 18px;
+    color: #9ea9cd;
+    line-height: 27px;
+    height: 18px;
+  }
+  &__value {
+    font-size: 18px;
+    line-height: 21px;
+  }
+  &__progress--outer {
+    height: 20px;
+    position: relative;
+    background-color: #060920;
+    width: 100%;
+    .block__progress--inner {
+      position: absolute;
+      width: 0;
+      top: 0;
+      left: 0;
+      bottom: 0;
+      background-color: #00d1ff;
+      transition: all 1s;
+    }
+  }
+}
+</style>

+ 46 - 0
src/views/dashboard/v1/api.js

@@ -124,3 +124,49 @@ export function getResources () {
     custom: true
   })
 }
+
+export function getPageDurationRank (query) {
+  const { pageNum: pageIndex, pageSize, ...params } = query
+  return tenantRequest({
+    url: '/ad/statistic/pageDurationRank',
+    method: 'GET',
+    params: addTenant({
+      pageIndex, pageSize,
+      ...params
+    })
+  })
+}
+
+export function getPagePriceRank (query) {
+  const { pageNum: pageIndex, pageSize, ...params } = query
+  return tenantRequest({
+    url: '/ad/statistic/pagePriceRank',
+    method: 'GET',
+    params: addTenant({
+      pageIndex, pageSize,
+      ...params
+    })
+  })
+}
+
+export function getPageCountRank (query) {
+  const { pageNum: pageIndex, pageSize, ...params } = query
+  return tenantRequest({
+    url: '/ad/statistic/pageCountRank',
+    method: 'GET',
+    params: addTenant({
+      pageIndex, pageSize,
+      ...params
+    })
+  })
+}
+
+export function getTopOnlineSeconds (count) {
+  return tenantRequest({
+    url: '/deviceOnlineInfoDetail/topOnlineSeconds',
+    method: 'GET',
+    params: addTenant({
+      count
+    })
+  })
+}

+ 9 - 2
src/views/dashboard/v1/components/CameraDetail.vue

@@ -13,9 +13,16 @@
       @error="onVideoError"
       @ended="onVideoEnded"
     />
-    <div class="c-camera-detail__footer ">
+    <div class="c-camera-detail__footer">
       <div class="l-flex--row c-sibling-item--v c-video-controls">
-        {{ camera.model }}
+        <div class="l-flex__auto">
+          {{ camera.model }}
+        </div>
+        <i
+          v-if="online"
+          class=" c-video-controls__btn el-icon-full-screen has-active"
+          @click="onFullScreen"
+        />
       </div>
     </div>
   </div>

+ 37 - 4
src/views/dashboard/v1/config.js

@@ -1,10 +1,9 @@
-
 // 首页 Index
 export const Index = {
   // 普通警告 右下角弹出消失时间
-  noticeTimer: 60 * 1000,
+  noticeTimer: 10 * 1000,
   // 设备 轮询时间
-  deviceTimer: 60 * 1000
+  deviceTimer: 10 * 1000
 }
 
 // 摄像头监控画面
@@ -20,7 +19,7 @@ export const Record = {
 
 // 预警等级情况
 export const AlarmLevel = {
-  timer: 10 * 1000
+  timer: 30 * 1000
 }
 
 // 各大屏预警率占比
@@ -52,3 +51,37 @@ export const ProgramTop = {
   timer: 30 * 1000
 }
 
+export const Bounds = {
+  inspur: [
+    [117.126789, 36.660564],
+    [117.128488, 36.660393],
+    [117.128695, 36.660365],
+    [117.129627, 36.660197],
+    [117.129648, 36.660197],
+    [117.129682, 36.66022],
+    [117.129703, 36.660254],
+    [117.130412, 36.663341],
+    [117.130404, 36.663376],
+    [117.13037, 36.66341],
+    [117.130191, 36.6635],
+    [117.130083, 36.663575],
+    [117.129937, 36.663707],
+    [117.129826, 36.663826],
+    [117.129678, 36.664048],
+    [117.129544, 36.664413],
+    [117.129469, 36.664997],
+    [117.129246, 36.66522],
+    [117.129031, 36.665279],
+    [117.127167, 36.665765],
+    [117.127141, 36.665771],
+    [117.127115, 36.665775],
+    [117.126812, 36.665781],
+    [117.126797, 36.665776],
+    [117.126782, 36.665766],
+    [117.126772, 36.665751],
+    [117.126764, 36.660593],
+    [117.126765, 36.660577],
+    [117.126773, 36.660568],
+    [117.126789, 36.660564]
+  ]
+}

+ 70 - 13
src/views/dashboard/v1/index.vue

@@ -4,11 +4,23 @@
       class="c-monitor-dashboard__main"
       :style="style"
     >
-      <Header />
+      <department-drawer
+        ref="departmentDrawer"
+        @change="onGroupChanged"
+      />
+      <Header>
+        <template #content>
+          <div
+            class="c-department-button u-pointer"
+            @click="$refs.departmentDrawer.show"
+          >{{ group.name }}</div>
+        </template>
+      </Header>
       <NewNotice
         class="new-notice"
         :class="{ active: showNotice }"
         :notice="curNotice"
+        :count="noticeCount"
       />
       <div class="l-flex--col center">
         <div class="l-flex--row">
@@ -18,7 +30,6 @@
               style="width: 1040px; height: 450px"
             >
               <MessageNotice
-                :device-list="mapDeviceList"
                 @new-alarm="onNewAlarm"
               />
             </div>
@@ -81,11 +92,32 @@
         <div class="l-flex--row">
           <div class="l-flex--col l-flex__none">
             <div
+              v-if="false"
               class="l-flex__none dashboard-block"
               style="width: 1040px; height: 450px"
             >
               <DeviceCalender :device-list="deviceList" />
             </div>
+            <div
+              v-else
+              class="l-flex__none dashboard-block l-flex--row"
+            >
+              <div
+                class="l-flex__none l-flex--col block-item"
+                style="width: 510px; height: 450px"
+              >
+                <ProgramTop type="getTopOnlineSeconds" />
+              </div>
+              <div
+                class="l-flex__none l-flex--col block-item"
+                style="width: 510px; height: 450px"
+              >
+                <ProgramTop
+                  :type="topMap[topIndex]"
+                  @click.native="topIndex=(topIndex+1)%topMap.length"
+                />
+              </div>
+            </div>
           </div>
 
           <div
@@ -117,7 +149,7 @@
 <script>
 import { Index } from './config'
 import {
-  getDevices, getDeviceStatistics
+  getDevicesByQuery, getDeviceStatisticsByPath
 } from '@/api/device'
 import DeviceCalender from './DeviceCalender'
 import Map from './Map'
@@ -154,7 +186,9 @@ export default {
     NewNotice
   },
   data () {
+    this.topMap = ['getPagePriceRank', 'getPageDurationRank', 'getPageCountRank']
     return {
+      topIndex: 0,
       style: null,
       statusData: [
         { label: '设备总数', value: '-' },
@@ -164,7 +198,9 @@ export default {
       ],
       deviceList: [],
       showNotice: false,
-      curNotice: {}
+      curNotice: {},
+      noticeCount: Index.noticeTimer,
+      group: {}
     }
   },
   computed: {
@@ -173,7 +209,6 @@ export default {
     }
   },
   created () {
-    this.getDeviceStatistics()
     this.$timer = setInterval(this.getDeviceStatistics, Index.deviceTimer)
   },
   mounted () {
@@ -182,10 +217,16 @@ export default {
   },
   beforeDestroy () {
     clearInterval(this.$timer)
-    clearTimeout(this.$newAlarmTimer)
+    clearInterval(this.$newAlarmTimer)
     window.removeEventListener('resize', this.checkScale)
   },
   methods: {
+    onGroupChanged ({ path, name }) {
+      if (!this.group || this.group.path !== path) {
+        this.group = { path, name }
+        this.getDeviceStatistics()
+      }
+    },
     checkScale () {
       this.style = {
         transform: `scale(${window.innerWidth / 3840}, ${
@@ -199,14 +240,21 @@ export default {
       } else {
         this.showNotice = true
         this.curNotice = alarm
-        clearTimeout(this.$newAlarmTimer)
-        this.$newAlarmTimer = setTimeout(() => {
-          this.showNotice = false
-        }, Index.noticeTimer)
+        clearInterval(this.$newAlarmTimer)
+
+        this.noticeCount = Index.noticeTimer / 1000
+        this.$newAlarmTimer = setInterval(() => {
+          this.noticeCount--
+          if (this.noticeCount <= 0) {
+            this.showNotice = false
+            this.noticeCount = 0
+            clearInterval(this.$newAlarmTimer)
+          }
+        }, 1000)
       }
     },
     getDeviceStatistics () {
-      getDeviceStatistics().then(({ data }) => {
+      getDeviceStatisticsByPath(this.group.path).then(({ data }) => {
         const {
           deactivatedTotal,
           notConnectedTotal,
@@ -224,11 +272,12 @@ export default {
       })
     },
     getDevices (total) {
-      getDevices(
+      getDevicesByQuery(
         {
           pageNum: 1,
           pageSize: total,
-          activate: 1
+          activate: 1,
+          org: this.group.path
         },
         { custom: true }
       ).then(({ data }) => {
@@ -278,4 +327,12 @@ export default {
     transform: translateY(0);
   }
 }
+.c-department-button{
+  position: absolute;
+  left: 0;
+  z-index: 999;
+  bottom:10px;
+  font-size: 32px;
+  left: 135px;
+}
 </style>