|
|
@@ -1,641 +0,0 @@
|
|
|
-<template>
|
|
|
- <div class="c-camera-detail">
|
|
|
- <video
|
|
|
- ref="video"
|
|
|
- class="c-camera-detail__video o-simple-video"
|
|
|
- :poster="poster"
|
|
|
- autoplay
|
|
|
- muted
|
|
|
- @play="onVideoPlay"
|
|
|
- @pause="onVideoPause"
|
|
|
- @waiting="onVideoWaiting"
|
|
|
- @playing="onVideoPlaying"
|
|
|
- @error="onVideoError"
|
|
|
- @ended="onVideoEnded"
|
|
|
- />
|
|
|
- <div class="c-camera-detail__footer">
|
|
|
- <div class="l-flex--row c-sibling-item--v c-video-controls">
|
|
|
- <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>
|
|
|
-</template>
|
|
|
-
|
|
|
-<script>
|
|
|
-import { mapGetters } from 'vuex'
|
|
|
-import * as echarts from 'echarts'
|
|
|
-import {
|
|
|
- getStatistic,
|
|
|
- getVideoInfo,
|
|
|
- getAvailableParams,
|
|
|
- setCameraParams,
|
|
|
- isOnline
|
|
|
-} from '@/api/camera'
|
|
|
-import {
|
|
|
- GATEWAY_CAMERA, Camera
|
|
|
-} from '@/constant'
|
|
|
-import { parseTime } from '@/utils'
|
|
|
-import playerMixin from '@/components/service/external/player.js'
|
|
|
-
|
|
|
-export default {
|
|
|
- name: 'CameraDetail',
|
|
|
- mixins: [playerMixin],
|
|
|
- props: {
|
|
|
- camera: {
|
|
|
- type: Object,
|
|
|
- required: true
|
|
|
- }
|
|
|
- },
|
|
|
- data () {
|
|
|
- const now = new Date()
|
|
|
-
|
|
|
- return {
|
|
|
- active: 'hour', // hour是小时,day是天
|
|
|
- settingActive: 'items',
|
|
|
- tabs: [
|
|
|
- { value: 'items', label: '分辨率' },
|
|
|
- { value: 'fps', label: '帧率' },
|
|
|
- { value: 'bitRates', label: '码率' }
|
|
|
- ],
|
|
|
- settingTab: true,
|
|
|
- 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'
|
|
|
- }
|
|
|
- }
|
|
|
- },
|
|
|
- computed: {
|
|
|
- ...mapGetters(['token']),
|
|
|
- online () {
|
|
|
- return this.camera.onlineStatus === 1
|
|
|
- },
|
|
|
- isTraffic () {
|
|
|
- return this.camera.cameraType === Camera.TRAFFIC
|
|
|
- },
|
|
|
- iconClass () {
|
|
|
- return this.paused || this.needReset
|
|
|
- ? 'el-icon-video-play'
|
|
|
- : 'el-icon-video-pause'
|
|
|
- }
|
|
|
- },
|
|
|
- created () {
|
|
|
- // this.getAvailableParams()
|
|
|
- },
|
|
|
- mounted () {
|
|
|
- this.createPlayer()
|
|
|
- // if (this.isTraffic) {
|
|
|
- // this.getStatistic()
|
|
|
- // window.addEventListener('resize', this.onResize)
|
|
|
- // }
|
|
|
- },
|
|
|
- beforeDestroy () {
|
|
|
- if (this.isTraffic) {
|
|
|
- this.hideSettingsMenu()
|
|
|
- window.removeEventListener('resize', this.onResize)
|
|
|
- }
|
|
|
- },
|
|
|
- methods: {
|
|
|
- close () {
|
|
|
- this.destroyPlayer()
|
|
|
- this.$emit('close')
|
|
|
- },
|
|
|
- createPlayer () {
|
|
|
- this.loading = true
|
|
|
- isOnline(this.camera.identifier, { custom: true }).then(
|
|
|
- ({ data }) => {
|
|
|
- if (data) {
|
|
|
- this.playUrl(
|
|
|
- `${GATEWAY_CAMERA}/${this.camera.identifier}?authorization=${this.token}`
|
|
|
- )
|
|
|
- } else {
|
|
|
- this.loading = false
|
|
|
- this.$message({
|
|
|
- type: 'warning',
|
|
|
- message: '设备未上线,请稍后重试'
|
|
|
- })
|
|
|
- }
|
|
|
- },
|
|
|
- () => {
|
|
|
- this.loading = false
|
|
|
- this.$message({
|
|
|
- type: 'warning',
|
|
|
- message: '暂未检测到设备状态,请稍后重试'
|
|
|
- })
|
|
|
- }
|
|
|
- )
|
|
|
- },
|
|
|
- setCameraParams () {
|
|
|
- this.hideSettingsMenu()
|
|
|
- setCameraParams({
|
|
|
- deviceId: this.camera.identifier,
|
|
|
- ...this.infoData
|
|
|
- }).then(
|
|
|
- () => {
|
|
|
- this.dataInit()
|
|
|
- this.onRefresh()
|
|
|
- },
|
|
|
- () => {
|
|
|
- this.infoData = { ...this.$infoData }
|
|
|
- }
|
|
|
- )
|
|
|
- },
|
|
|
- getAvailableParams () {
|
|
|
- if (this.$videoPramsLoading) {
|
|
|
- this.$message({
|
|
|
- type: 'warning',
|
|
|
- message: '配置获取中,请稍后重试'
|
|
|
- })
|
|
|
- return
|
|
|
- }
|
|
|
- this.$videoPramsLoading = true
|
|
|
- getAvailableParams()
|
|
|
- .finally(() => {
|
|
|
- this.$videoPramsLoading = false
|
|
|
- })
|
|
|
- .then(({ data }) => {
|
|
|
- this.videoSettings = {
|
|
|
- items: data.itemList.map(item => {
|
|
|
- // maxBitRateOptions: 20480
|
|
|
- // minBitRateOptions: 3
|
|
|
- // resolutionFpsMax: 20
|
|
|
- // snHight: 1944
|
|
|
- // snWidth: 2592
|
|
|
- return {
|
|
|
- value: `${item.snWidth}*${item.snHight}`,
|
|
|
- label: `${item.snWidth}*${item.snHight}`,
|
|
|
- active: false,
|
|
|
- ...item
|
|
|
- }
|
|
|
- }),
|
|
|
- fps: [],
|
|
|
- bitRates: [],
|
|
|
- streamRateType: data.streamRateTypeList
|
|
|
- }
|
|
|
- this.getVideoInfo()
|
|
|
- })
|
|
|
- },
|
|
|
- onSettings () {
|
|
|
- if (!this.videoSettings) {
|
|
|
- this.getAvailableParams()
|
|
|
- return
|
|
|
- }
|
|
|
- if (!this.infoData) {
|
|
|
- this.getVideoInfo()
|
|
|
- return
|
|
|
- }
|
|
|
- this.settingTab = false
|
|
|
- if (this.settingsShow) {
|
|
|
- this.hideSettingsMenu()
|
|
|
- } else {
|
|
|
- document.addEventListener('click', this.hideSettingsMenu)
|
|
|
- this.settingsShow = true
|
|
|
- }
|
|
|
- },
|
|
|
- hideSettingsMenu () {
|
|
|
- if (this.settingsShow) {
|
|
|
- this.settingsShow = false
|
|
|
- document.addEventListener('click', this.hideSettingsMenu)
|
|
|
- }
|
|
|
- },
|
|
|
- onRefresh () {
|
|
|
- this.destroyPlayer()
|
|
|
- this.createPlayer()
|
|
|
- },
|
|
|
- onClickSettings (tab) {
|
|
|
- this.settingActive = tab
|
|
|
- this.settingTab = true
|
|
|
- },
|
|
|
- onCloseSettingTab () {
|
|
|
- this.settingTab = false
|
|
|
- },
|
|
|
- getVideoInfo () {
|
|
|
- console.log('getVideoInfo', this.$videoInfoLoading)
|
|
|
- if (this.$videoInfoLoading) {
|
|
|
- this.$message({
|
|
|
- type: 'warning',
|
|
|
- message: '配置获取中,请稍后重试'
|
|
|
- })
|
|
|
- return
|
|
|
- }
|
|
|
- this.$videoInfoLoading = true
|
|
|
- getVideoInfo(this.camera.identifier)
|
|
|
- .finally(() => {
|
|
|
- this.$videoInfoLoading = false
|
|
|
- })
|
|
|
- .then(({ data }) => {
|
|
|
- if (data) {
|
|
|
- const { width, hight, frameRate, bitRate } = data
|
|
|
- this.infoData = { width, hight, frameRate, bitRate }
|
|
|
- this.dataInit()
|
|
|
- } else {
|
|
|
- this.$message({
|
|
|
- type: 'warning',
|
|
|
- message: '未获取到摄像头配置,请联系管理员'
|
|
|
- })
|
|
|
- }
|
|
|
- })
|
|
|
- },
|
|
|
- dataInit () {
|
|
|
- const { width, hight, frameRate, bitRate } = this.infoData
|
|
|
- const value = `${width}*${hight}`
|
|
|
- let target = null
|
|
|
-
|
|
|
- this.videoSettings.items.forEach(item => {
|
|
|
- if (item.value === value) {
|
|
|
- item.active = true
|
|
|
- target = item
|
|
|
- } else {
|
|
|
- item.active = false
|
|
|
- }
|
|
|
- })
|
|
|
-
|
|
|
- if (target) {
|
|
|
- const fps = []
|
|
|
- const max = target.resolutionFpsMax
|
|
|
- for (let i = 1; i <= max; i++) {
|
|
|
- fps.push({ value: i, label: `${i}fps`, active: i === frameRate })
|
|
|
- }
|
|
|
- this.videoSettings.fps = fps
|
|
|
-
|
|
|
- const bitRates = []
|
|
|
- const minBitRate = target.minBitRateOptions
|
|
|
- const maxBitRate = target.maxBitRateOptions
|
|
|
- const streamRateType = this.videoSettings.streamRateType
|
|
|
- for (let i = 0; i < streamRateType.length; i++) {
|
|
|
- const rateType = streamRateType[i]
|
|
|
- if (rateType >= minBitRate && rateType <= maxBitRate) {
|
|
|
- bitRates.push({
|
|
|
- value: rateType,
|
|
|
- label: `${rateType}kb/s`,
|
|
|
- active: rateType === bitRate
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
- this.videoSettings.bitRates = bitRates
|
|
|
- } else {
|
|
|
- this.videoSettings.items = [
|
|
|
- { label: `${width}*${hight}`, active: true }
|
|
|
- ]
|
|
|
- this.videoSettings.fps = [
|
|
|
- { value: frameRate, label: `${frameRate}fps`, active: true }
|
|
|
- ]
|
|
|
- this.videoSettings.bitRates = [
|
|
|
- { value: bitRate, label: `${bitRate}kb/s`, active: true }
|
|
|
- ]
|
|
|
- }
|
|
|
- },
|
|
|
- settingClick (item) {
|
|
|
- let value = null
|
|
|
- switch (this.settingActive.value) {
|
|
|
- case 'items':
|
|
|
- value = `${this.infoData.width}*${this.infoData.hight}`
|
|
|
- break
|
|
|
- case 'fps':
|
|
|
- value = this.infoData.frameRate
|
|
|
- break
|
|
|
- case 'bitRates':
|
|
|
- value = this.infoData.bitRate
|
|
|
- break
|
|
|
- default:
|
|
|
- return
|
|
|
- }
|
|
|
- if (value === item.value) {
|
|
|
- return
|
|
|
- }
|
|
|
- this.$infoData = { ...this.infoData }
|
|
|
- switch (this.settingActive.value) {
|
|
|
- case 'items':
|
|
|
- this.infoData.width = item.snWidth
|
|
|
- this.infoData.hight = item.snHight
|
|
|
- this.infoData.frameRate = Math.min(
|
|
|
- item.resolutionFpsMax,
|
|
|
- this.infoData.frameRate
|
|
|
- )
|
|
|
- this.infoData.bitRate = Math.max(
|
|
|
- item.minBitRateOptions,
|
|
|
- Math.min(item.maxBitRateOptions, this.infoData.bitRate)
|
|
|
- )
|
|
|
- break
|
|
|
- case 'fps':
|
|
|
- this.infoData.frameRate = item.value
|
|
|
- break
|
|
|
- case 'bitRates':
|
|
|
- this.infoData.bitRate = item.value
|
|
|
- break
|
|
|
- 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}'
|
|
|
- }
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-</script>
|
|
|
-
|
|
|
-<style lang="scss" scoped>
|
|
|
-$theme-blue: #003e90;
|
|
|
-
|
|
|
-.c-camera-detail {
|
|
|
- position: relative;
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- background-color: #000;
|
|
|
- overflow: hidden;
|
|
|
-
|
|
|
- &__video {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- }
|
|
|
-
|
|
|
- &__header {
|
|
|
- position: absolute;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
- width: 100%;
|
|
|
- height: $height;
|
|
|
- background-color: rgba($theme-blue, 0.8);
|
|
|
- z-index: 9;
|
|
|
-
|
|
|
- &::after {
|
|
|
- content: "";
|
|
|
- position: absolute;
|
|
|
- top: 100%;
|
|
|
- left: 50%;
|
|
|
- width: 700px;
|
|
|
- height: 0;
|
|
|
- border-top: $padding--lg solid rgba($theme-blue, 0.8);
|
|
|
- border-right: $padding--lg solid transparent;
|
|
|
- border-bottom: $padding--lg solid transparent;
|
|
|
- border-left: $padding--lg solid transparent;
|
|
|
- transform: translateX(-50%);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- &__footer {
|
|
|
- position: absolute;
|
|
|
- left: 0;
|
|
|
- bottom: 0;
|
|
|
- width: 100%;
|
|
|
- }
|
|
|
-
|
|
|
- &__traffic {
|
|
|
- position: relative;
|
|
|
- line-height: 1;
|
|
|
- border-radius: $radius--sm;
|
|
|
- background-color: rgba($theme-blue, 0.8);
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-.c-video-controls {
|
|
|
- position: relative;
|
|
|
- padding: $padding $padding--lg;
|
|
|
- border-radius: $radius--sm;
|
|
|
- background-color: rgba(#00060d, 0.75);
|
|
|
-
|
|
|
- &__btn {
|
|
|
- color: #fff;
|
|
|
- font-size: $font-size--3xl;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-.settingB {
|
|
|
- position: absolute;
|
|
|
- right: 134px;
|
|
|
- bottom: 48px;
|
|
|
- padding: 10px 0px;
|
|
|
- color: #fff;
|
|
|
- background-color: rgba(#000, 0.85);
|
|
|
- transform: translateX(50%);
|
|
|
-
|
|
|
- .settingT {
|
|
|
- position: relative;
|
|
|
- padding: 0px 16px;
|
|
|
- width: 180px;
|
|
|
- height: 40px;
|
|
|
- line-height: 40px;
|
|
|
- cursor: pointer;
|
|
|
-
|
|
|
- &:hover {
|
|
|
- background-color: rgba(#454545, 0.85);
|
|
|
- }
|
|
|
-
|
|
|
- i {
|
|
|
- position: absolute;
|
|
|
- top: 13px;
|
|
|
- right: 16px;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .settingHeight {
|
|
|
- max-height: 250px;
|
|
|
- overflow: auto;
|
|
|
- }
|
|
|
-
|
|
|
- .settingsub {
|
|
|
- width: 140px;
|
|
|
- padding-left: 42px;
|
|
|
-
|
|
|
- i {
|
|
|
- position: absolute;
|
|
|
- left: 16px;
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-.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>
|