Pārlūkot izejas kodu

feat: the PeopleCounting component

adjust some styles
Casper Dai 2 gadi atpakaļ
vecāks
revīzija
a2fe062ecb

+ 8 - 254
src/components/service/external/camera/CameraDetail/index.vue

@@ -74,59 +74,17 @@
           @click="onVideoRefresh"
         />
       </div>
-      <div
-        v-if="isTraffic"
+      <people-counting
         class="c-sibling-item--v far c-camera-detail__traffic has-padding"
-      >
-        <div
-          id="main"
-          class="o-canvas"
-        />
-        <div class="c-choose-date">
-          <div class="c-sibling-item c-choose-date__type u-pointer">
-            <span
-              class="c-choose-date__item u-font-size--sm u-text--center"
-              :class="{ active: active === 'hour' }"
-              @click="onTimeTypeChanged('hour')"
-            >
-              1小时
-            </span>
-            <span
-              class="c-choose-date__item u-font-size--sm u-text--center"
-              :class="{ active: active === 'day' }"
-              @click="onTimeTypeChanged('day')"
-            >
-              1天
-            </span>
-          </div>
-          <el-date-picker
-            v-model="dateValue"
-            class="c-sibling-item far u-width--xs u-pointer"
-            type="date"
-            value-format="yyyy-MM-dd"
-            :editable="false"
-            :clearable="false"
-            @change="getStatistic"
-          />
-          <el-time-select
-            v-model="timeValue"
-            class="c-sibling-item u-width--2xs u-pointer"
-            :picker-options="timePickerOptions"
-            :editable="false"
-            :clearable="false"
-            @change="getStatistic"
-          />
-        </div>
-      </div>
+        :camera="camera"
+      />
     </div>
   </div>
 </template>
 
 <script>
 import { mapGetters } from 'vuex'
-import * as echarts from 'echarts'
 import {
-  getStatistic,
   getVideoInfo,
   getAvailableParams,
   setCameraParams,
@@ -136,7 +94,6 @@ import {
   GATEWAY_CAMERA,
   Camera
 } from '@/constant'
-import { parseTime } from '@/utils'
 import playerMixin from '../../player'
 
 export default {
@@ -149,27 +106,17 @@ export default {
     }
   },
   data () {
-    const now = new Date()
-
     return {
-      active: 'hour', // hour是小时,day是天
-      settingActive: 'items',
       tabs: [
         { value: 'items', label: '分辨率' },
         { value: 'fps', label: '帧率' },
         { value: 'bitRates', label: '码率' }
       ],
-      settingTab: true,
+      settingActive: 'items',
+      settingTab: false,
       settingsShow: false,
       videoSettings: null,
-      infoData: null,
-      dateValue: parseTime(now, '{y}-{m}-{d}'),
-      timeValue: parseTime(now, '{h}:00'),
-      timePickerOptions: {
-        start: '00:00',
-        step: '01:00',
-        end: '23:00'
-      }
+      infoData: null
     }
   },
   computed: {
@@ -186,16 +133,9 @@ export default {
   },
   mounted () {
     this.createPlayer()
-    if (this.isTraffic) {
-      this.getStatistic()
-      window.addEventListener('resize', this.onResize)
-    }
   },
   beforeDestroy () {
-    if (this.isTraffic) {
-      this.hideSettingsMenu()
-      window.removeEventListener('resize', this.onResize)
-    }
+    this.hideSettingsMenu()
   },
   methods: {
     close () {
@@ -412,147 +352,6 @@ export default {
           break
       }
       this.setCameraParams()
-    },
-    onTimeTypeChanged (type) {
-      if (this.active === type) {
-        return
-      }
-      this.active = type
-      this.getStatistic()
-    },
-    getStatistic () {
-      const endTime = `${this.dateValue} ${this.timeValue}:00`
-      const date = new Date(endTime)
-      switch (this.active) {
-        case 'hour':
-          date.setHours(date.getHours() - 1)
-          break
-        default:
-          date.setDate(date.getDate() - 1)
-          break
-      }
-      const startTime = parseTime(date, '{y}-{m}-{d} {h}:{i}:{s}')
-      this.refreshEchart([])
-      getStatistic({
-        deviceId: this.camera.identifier,
-        startTime,
-        endTime,
-        pageIndex: 1,
-        pageSize: 10000
-      }).then(({ data }) => {
-        this.refreshEchart(data)
-      })
-    },
-    getEchartData (data) {
-      const date = new Date(`${this.dateValue} ${this.timeValue}:00`)
-      const xdata = []
-      const ydata = []
-      if (this.active === 'hour') {
-        date.setHours(date.getHours() - 1)
-        for (let i = 0; i <= 60; i++) {
-          const value = parseTime(date, '{y}-{m}-{d} {h}:{i}')
-          xdata.push(value.split(' ')[1])
-          ydata.push(data[value] || 0)
-          date.setMinutes(date.getMinutes() + 1)
-        }
-      } else {
-        date.setDate(date.getDate() - 1)
-        for (let i = 0; i <= 24; i++) {
-          const value = parseTime(date, '{y}-{m}-{d} {h}')
-          xdata.push(`${value.split(' ')[1]}:00`)
-          ydata.push(data[value] || 0)
-          date.setHours(date.getHours() + 1)
-        }
-      }
-      return { xdata, ydata }
-    },
-    transformEchartData (data) {
-      const ydata = {}
-      const isHour = this.active === 'hour'
-      data.forEach(({ eventTime, insidePeopleNum }) => {
-        const key = eventTime.slice(0, isHour ? 16 : 13)
-        if (ydata[key]) {
-          ydata[key] = Math.max(ydata[key], insidePeopleNum)
-        } else {
-          ydata[key] = insidePeopleNum
-        }
-      })
-      return this.getEchartData(ydata)
-    },
-    onResize () {
-      this.$echarts?.resize()
-    },
-    refreshEchart (echartsData) {
-      const { xdata, ydata } = this.transformEchartData(echartsData)
-      if (!this.$echarts) {
-        this.$echarts = echarts.init(document.getElementById('main'))
-      }
-      this.$echarts?.setOption({
-        title: {
-          text: '区域内人数',
-          textStyle: {
-            color: '#fff',
-            fontWeight: 'bold'
-          }
-        },
-        xAxis: {
-          type: 'category',
-          data: xdata,
-          axisLine: {
-            lineStyle: {
-              color: '#4779BC'
-            }
-          },
-          axisLabel: {
-            color: '#A9CEFF'
-          }
-        },
-        yAxis: {
-          type: 'value',
-          minInterval: 1,
-          splitLine: {
-            lineStyle: {
-              color: '#4779BC',
-              type: 'dashed'
-            }
-          },
-          axisLine: {
-            lineStyle: {
-              color: '#4779BC'
-            }
-          },
-          axisLabel: {
-            color: '#A9CEFF'
-          }
-        },
-        grid: {
-          left: '30',
-          right: '20',
-          top: '40',
-          bottom: '20'
-        },
-        series: [
-          {
-            data: ydata,
-            type: 'bar',
-            showBackground: true,
-            backgroundStyle: {
-              color: 'transparent'
-            },
-            itemStyle: {
-              color: 'rgba(0, 191, 208, 0.5)'
-            },
-            select: {
-              itemStyle: {
-                color: 'rgb(0, 234, 255)'
-              }
-            }
-          }
-        ],
-        tooltip: {
-          formatter: '时间:{b}<br />人流量:{c}'
-        }
-      })
     }
   }
 }
@@ -605,8 +404,7 @@ $theme-blue: #003e90;
   }
 
   &__traffic {
-    position: relative;
-    line-height: 1;
+    height: 240px;
     border-radius: $radius--sm;
     background-color: rgba($theme-blue, 0.8);
   }
@@ -664,48 +462,4 @@ $theme-blue: #003e90;
     }
   }
 }
-
-.c-choose-date {
-  position: absolute;
-  right: $spacing;
-  top: $spacing;
-  color: #fff;
-
-  &__type {
-    display: inline-block;
-    height: 24px;
-    line-height: 24px;
-    border-radius: $radius--sm;
-    background-color: #4478bc;
-  }
-
-  &__item {
-    display: inline-block;
-    width: 50px;
-    border-radius: $radius--sm;
-    background-color: #4478bc;
-
-    &.active {
-      background-color: #0096ff;
-    }
-  }
-
-  ::v-deep input {
-    height: 24px;
-    padding-right: $padding;
-    color: #fff;
-    font-size: $font-size--sm;
-    line-height: 24px;
-    background-color: transparent;
-  }
-
-  ::v-deep .el-input__icon {
-    line-height: 24px;
-  }
-}
-
-.o-canvas {
-  width: 100%;
-  height: 200px;
-}
 </style>

+ 271 - 0
src/components/service/external/camera/PeopleCounting/index.vue

@@ -0,0 +1,271 @@
+<template>
+  <div class="l-flex inline c-people-counting u-relative">
+    <div
+      id="main"
+      class="l-flex__fill"
+    />
+    <div class="c-people-counting__filter">
+      <div class="c-sibling-item c-people-counting__type u-pointer">
+        <span
+          class="c-people-counting__item u-font-size--sm u-text--center"
+          :class="{ active: active === 'hour' }"
+          @click="onTimeTypeChanged('hour')"
+        >
+          1小时
+        </span>
+        <span
+          class="c-people-counting__item u-font-size--sm u-text--center"
+          :class="{ active: active === 'day' }"
+          @click="onTimeTypeChanged('day')"
+        >
+          1天
+        </span>
+      </div>
+      <el-date-picker
+        v-model="dateValue"
+        class="c-sibling-item far u-width--xs u-pointer"
+        type="date"
+        value-format="yyyy-MM-dd"
+        :editable="false"
+        :clearable="false"
+        @change="getStatistic"
+      />
+      <el-time-select
+        v-model="timeValue"
+        class="c-sibling-item u-width--2xs u-pointer"
+        :picker-options="timePickerOptions"
+        :editable="false"
+        :clearable="false"
+        @change="getStatistic"
+      />
+    </div>
+  </div>
+</template>
+
+<script>
+import * as echarts from 'echarts'
+import { getStatistic } from '@/api/camera'
+import { parseTime } from '@/utils'
+
+export default {
+  name: 'PeopleCounting',
+  props: {
+    camera: {
+      type: Object,
+      required: true
+    }
+  },
+  data () {
+    const date = new Date()
+    if (date.getMinutes() < 30) {
+      date.setHours(date.getHours() - 1)
+    }
+
+    return {
+      active: 'hour', // hour是小时,day是天
+      dateValue: parseTime(date, '{y}-{m}-{d}'),
+      timeValue: parseTime(date, '{h}:00'),
+      timePickerOptions: {
+        start: '00:00',
+        step: '01:00',
+        end: '23:00'
+      }
+    }
+  },
+  mounted () {
+    this.getStatistic()
+    window.addEventListener('resize', this.onResize)
+  },
+  beforeDestroy () {
+    window.removeEventListener('resize', this.onResize)
+  },
+  methods: {
+    onResize () {
+      this.$echarts?.resize()
+    },
+    onTimeTypeChanged (type) {
+      if (this.active === type) {
+        return
+      }
+      this.active = type
+      this.getStatistic()
+    },
+    getStatistic () {
+      const startTime = `${this.dateValue} ${this.timeValue}:00`
+      const date = new Date(startTime)
+      let pageSize = 1
+      switch (this.active) {
+        case 'hour':
+          date.setHours(date.getHours() + 1)
+          pageSize = 3600
+          break
+        default:
+          date.setDate(date.getDate() + 1)
+          // 数据量太大会造成卡死
+          pageSize = 10000
+          break
+      }
+      const endTime = parseTime(date, '{y}-{m}-{d} {h}:{i}:{s}')
+      this.refreshEchart([])
+      getStatistic({
+        deviceId: this.camera.identifier,
+        startTime,
+        endTime,
+        pageIndex: 1,
+        pageSize
+      }).then(({ data }) => {
+        this.refreshEchart(data)
+      })
+    },
+    getEchartData (data) {
+      const date = new Date(`${this.dateValue} ${this.timeValue}:00`)
+      const xdata = []
+      const ydata = []
+      if (this.active === 'hour') {
+        for (let i = 0; i <= 60; i++) {
+          const value = parseTime(date, '{y}-{m}-{d} {h}:{i}')
+          xdata.push(value.split(' ')[1])
+          ydata.push(data[value] || 0)
+          date.setMinutes(date.getMinutes() + 1)
+        }
+      } else {
+        for (let i = 0; i <= 24; i++) {
+          const value = parseTime(date, '{y}-{m}-{d} {h}')
+          xdata.push(`${value.split(' ')[1]}:00`)
+          ydata.push(data[value] || 0)
+          date.setHours(date.getHours() + 1)
+        }
+      }
+      return { xdata, ydata }
+    },
+    transformEchartData (data) {
+      const ydata = {}
+      const isHour = this.active === 'hour'
+      data.forEach(({ eventTime, insidePeopleNum }) => {
+        const key = eventTime.slice(0, isHour ? 16 : 13)
+        if (ydata[key]) {
+          ydata[key] = Math.max(ydata[key], insidePeopleNum)
+        } else {
+          ydata[key] = insidePeopleNum
+        }
+      })
+      return this.getEchartData(ydata)
+    },
+    refreshEchart (echartsData) {
+      const { xdata, ydata } = this.transformEchartData(echartsData)
+      if (!this.$echarts) {
+        this.$echarts = echarts.init(document.getElementById('main'))
+      }
+      this.$echarts?.setOption({
+        title: {
+          text: '人流统计',
+          textStyle: {
+            color: '#fff',
+            fontWeight: 'bold'
+          }
+        },
+        xAxis: {
+          type: 'category',
+          data: xdata,
+          axisLine: {
+            lineStyle: {
+              color: '#4779BC'
+            }
+          },
+          axisLabel: {
+            color: '#A9CEFF'
+          }
+        },
+        yAxis: {
+          type: 'value',
+          minInterval: 1,
+          splitLine: {
+            lineStyle: {
+              color: '#4779BC',
+              type: 'dashed'
+            }
+          },
+          axisLine: {
+            lineStyle: {
+              color: '#4779BC'
+            }
+          },
+          axisLabel: {
+            color: '#A9CEFF'
+          }
+        },
+        grid: {
+          left: '64',
+          right: '10',
+          top: '40',
+          bottom: '20'
+        },
+        series: [
+          {
+            data: ydata,
+            type: 'bar',
+            showBackground: true,
+            backgroundStyle: {
+              color: 'transparent'
+            },
+            itemStyle: {
+              color: 'rgba(0, 191, 208, 0.5)'
+            },
+            select: {
+              itemStyle: {
+                color: 'rgb(0, 234, 255)'
+              }
+            }
+          }
+        ],
+        tooltip: {
+          formatter: '时间:{b}<br />人流量:{c}'
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.c-people-counting {
+  &__filter {
+    position: absolute;
+    right: $spacing;
+    top: $spacing;
+    color: #fff;
+  }
+
+  &__type {
+    display: inline-block;
+    height: 24px;
+    line-height: 24px;
+    border-radius: $radius--sm;
+    background-color: #4478bc;
+  }
+
+  &__item {
+    display: inline-block;
+    width: 50px;
+    border-radius: $radius--sm;
+    background-color: #4478bc;
+
+    &.active {
+      background-color: #0096ff;
+    }
+  }
+
+  ::v-deep input {
+    height: 24px;
+    padding-right: $padding;
+    color: #fff;
+    font-size: $font-size--sm;
+    line-height: 24px;
+    background-color: transparent;
+  }
+
+  ::v-deep .el-input__icon {
+    line-height: 24px;
+  }
+}
+</style>

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

@@ -46,7 +46,7 @@ export default {
             : { type: 'warning', label: '未激活' }, 'width': 120 },
           { label: '运行时间', render: ({ openTime, closeTime }) => openTime && closeTime
             ? `${openTime} - ${closeTime}`
-            : '-', 'min-width': 100 },
+            : '-', width: 140 },
           { prop: 'address', label: '地址', 'min-width': 160 },
           { type: 'invoke', render: [
             { label: '详情', on: this.onViewDevice }

+ 4 - 5
src/views/realm/device/index.vue

@@ -142,14 +142,13 @@ export default {
               }
             }
           ) },
-          { prop: 'name', 'min-width': 120 },
-          { prop: 'serialNumber', label: '序列号' },
-          { prop: 'mac', label: 'MAC' },
+          { prop: 'name', label: '设备名称' },
           { label: '运行时间', render: ({ empty, openTime, closeTime }) => empty
             ? ''
             : openTime && closeTime
               ? `${openTime} - ${closeTime}`
-              : '-' },
+              : '-', width: 148 },
+          { prop: 'address', label: '地址' },
           { label: '坐标', type: 'tag', render: ({ empty, longitude, latitude }) => empty
             ? null
             : longitude && latitude
@@ -175,7 +174,7 @@ export default {
             { label: '编辑', render ({ empty }) { return !empty }, on: this.onEditDevice },
             { label: '详情', render ({ empty }) { return !empty }, on: this.onViewDevice },
             { label: '配置', render ({ isMaster }) { return isMaster }, on: this.onSettingDevice }
-          ], width: 160 }
+          ], width: 140 }
         ]
       },
       info: {}

+ 1 - 1
src/views/realm/org/index.vue

@@ -212,7 +212,7 @@ export default {
             } else {
               this.$refs.treeRef.append({ id: data.id, path: data.path, children: [], ...this.node })
             }
-            if (!this.$selectedVNode.expanded) {
+            if (this.$selectedVNode && !this.$selectedVNode.expanded) {
               this.$selectedComponent.handleExpandIconClick()
             }
           } else {

+ 5 - 2
src/views/test-api.vue

@@ -13,7 +13,7 @@
         {{ tip }}
       </div>
     </div>
-    <div class="l-flex c-sibling-item--v">
+    <div class="l-flex__fill l-flex c-sibling-item--v">
       <div class="l-flex__fill l-flex--col c-sibling-item">
         <span class="c-sibling-item--v far u-font-size--sm u-bold">在线时长统计</span>
         <div class="c-sibling-item--v l-grid--info mini">
@@ -83,7 +83,10 @@
         </div>
       </div>
       <div class="l-flex__none c-sibling-item far u-width--lg u-font-size--sm u-overflow-y--auto">
-        <pre style="white-space: break-spaces;"><code>{{ responseData }}</code></pre>
+        <pre
+          class="l-flex__fill"
+          style="white-space: break-spaces;"
+        ><code>{{ responseData }}</code></pre>
       </div>
     </div>
     <single-device-dialog