Newer
Older
safe_production_front / src / views / ycjg / sjhf / index-media.vue
dutingting on 31 Oct 15 KB 网关集成完成
<script lang="ts" setup name="ResourceList">
import type { Ref } from 'vue'
import { ElLoading } from 'element-plus'
import { getCurrentInstance, nextTick, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import dayjs from 'dayjs'
import { getPlaybackUrl, getPreviewUrl } from '@/utils/hik'
import { getMediaStream, getMediaToken, querySceneReplayList, startReplay, endReplay } from '@/api/ycjg/media'
import { videoTree } from '@/api/ycjg/aqbb'
const router = useRouter()
const mediaToken = ref('') // 流媒体token
const treeRef = ref(null) as any
const filterText = ref('')
const deviceData: Ref<any> = ref({})
const timeRange = ref<[any, any]>(['', ''])
const lastCurrentStream = ref({ // 上一个正在播放的流的信息
  deviceId: '', // 设备国标编号
  channelId: '', // 通道国标编号
  stream: '', // 开始播放时返回的stream标识
})
const currentLeafId = ref('')
const listQuery = ref({
  id: '',
  startTime: '',
  endTime: '',
})
const leafLoading = ref(false)
const mediaUrl = ref('') // 流媒体播放地址
const data = ref([])
const currentKey = ref(null) as any // 存储当前高亮的节点
const mediaRecordList = ref([]) as any
const defaultProps = ref({
  children: 'children',
  label: 'name',
  isDisabled: 'disabled',
})
const currentTime = ref('')
const player = ref(null) as any

const { proxy } = getCurrentInstance() as any
const width = ref(0)
const height = ref(0)
const loading = ref(false)
const src = ref('')
const backPlayTitle = ref('') // 回放列表的title
const resize = () => {
  const divPlugin = document.getElementById('home')
  console.log(divPlugin)
  width.value = divPlugin!.clientWidth - 20
  height.value = divPlugin!.clientHeight - 60
}

// 筛选节点
function filterNode(value: any, data: { name: string | any[] }) {
  if (value === '' || value === null) {
    return true
  }
  return data.name.includes(value)
}

let treeClickCount = 0

// 双击树节点
function handleNodeClick(data: any, node: any, self: any) {
  deviceData.value = data
  currentLeafId.value = deviceData.value.device.id
  const now = new Date().getTime()
  if (now - treeClickCount < 300) { // 双击事件的判断,300毫秒内重复点击
    console.log('Double click on:', data)
    console.log('节点信息', node)
    if (data.device.deviceStatusName === '离线') {
      ElMessage.warning(`设备 ${data.device.monitorName} 离线`)
      return false
    }
    if (data.device === '') {
      treeRef.value.setCurrentKey(null)
    }
    else {
      search()
      treeRef.value.setCurrentKey(node.id)
    }
  }
  else { // 单击
    treeRef.value.setCurrentKey(null)
  }
  treeClickCount = now
}

const unwatch = watch(filterText, (newVal) => {
  treeRef.value.filter(newVal)
})

/**
 * 查询回放录像列表
 * @param type tableClick点击查询按钮
 */
function search(type = '') {
  if (deviceData.value && deviceData.value.device) {
    const params = {
      startTime: listQuery.value.startTime, // 开始时间
      endTime: listQuery.value.endTime, // 结束时间
      deviceId: deviceData.value.device.cameraIndexCode, // 监控点唯一标识
      channelId: deviceData.value.device.nvrIndexCode,
      accessToken: mediaToken.value,
    }
    const loading = ElLoading.service({
      lock: true,
      text: '正在查询回放列表,请稍等',
      background: 'rgba(255, 255, 255, 0.8)',
    })
    mediaUrl.value = ''
    currentTime.value = ''
    querySceneReplayList(params).then((res: any) => {
      if (res && res.data && res.data.recordList && res.data.recordList.length) {
        mediaRecordList.value = res.data.recordList.reverse()
        mediaRecordList.value = mediaRecordList.value.map((item: any) => {
          return {
            ...item,
            isActive: false,
          }
        })
        clickTag(mediaRecordList.value[0], 0)
        backPlayTitle.value = deviceData.value.fullName
      }
      else {
        ElMessage.warning(`【${deviceData.value.fullName}】此时间段无场景回放!`)
        mediaRecordList.value = []
      }
      loading.close()
    }).catch(() => {
      ElMessage.warning('获取流媒体场景回放列表失败!')
      loading.close()
    })
  }
  else {
    if (type === 'tableClick') {
      ElMessage.warning('请先选中一个设备')
    }
  }
}

// 排他思想(点击的改变背景色,其他默认背景色)
const clickDivSolveBack = (indexParams: number) => {
  if (!mediaRecordList.value.length) { return false }
  mediaRecordList.value.forEach((item: any, index: number) => {
    if (indexParams === index) {
      item.isActive = !item.isActive
    }
    else {
      item.isActive = false
    }
  })
}

// 点击标签
function clickTag(item: any, index: number) {
  const now = new Date().getTime()
  if (now - treeClickCount < 300) { // 双击事件的判断,300毫秒内重复点击
    console.log('双击事件的判断,300毫秒内重复点击')
    return
  }
  // console.log(currentTime.value, currentTime.value === `${item.startTime - item.endTime}`)

  if (currentTime.value && currentTime.value === item.startTime + ' - ' + item.endTime) {
    ElMessage.warning(`设备${deviceData.value.fullName}【${currentTime.value}】时间段的流正在播放, 请换一个时间段`)
    return
  }
  treeClickCount = now
  const startParams = {
    startTime: item.startTime, // 开始时间
    endTime: item.endTime, // 结束时间
    deviceId: deviceData.value.device.cameraIndexCode, // 监控点唯一标识
    channelId: deviceData.value.device.nvrIndexCode,
    accessToken: mediaToken.value,
  }
  // const loading = ElLoading.service({
  //   lock: true,
  //   text: '正在开启回放,请稍等',
  //   background: 'rgba(255, 255, 255, 0.8)',
  // })
  clickDivSolveBack(index)
  leafLoading.value = true
  if (lastCurrentStream.value && lastCurrentStream.value.stream) { // 有正在播的流,就先停止他
    const stopParams = {
      deviceId: lastCurrentStream.value.deviceId, // 设备国标编号
      channelId: lastCurrentStream.value.channelId, // 通道国标编号
      stream: lastCurrentStream.value.stream, // 开始播放时返回的stream标识
      accessToken: mediaToken.value,
    }
    endReplay(stopParams).then(() => {
      console.log('上一个流停播成功')
    }).catch(() => console.log('上一个流停播失败'))
  }
  startReplay(startParams).then((res) => {
    console.log('取流成功!')
    if (res && res.data) {
      lastCurrentStream.value = {
        deviceId: startParams.deviceId, // 设备国标编号
        channelId: startParams.channelId, // 通道国标编号
        stream: res.data.mediaInfo.stream, // 开始播放时返回的stream标识
      }
      mediaUrl.value = res.data.fmp4
      currentTime.value = startParams.startTime + ' - ' + startParams.endTime
      console.log('-----', currentTime.value)
    } else {
      ElMessage.warning('取流失败!')
    }
    // loading.close()
    leafLoading.value = false
  }).catch(() => {
    ElMessage.warning('未获取到流!')
    leafLoading.value = false
  })
}

// 搜索重置
function reset() {
  listQuery.value = {
    startTime: dayjs(Date.now() - 1 * 24 * 60 * 60 * 1000).format('YYYY-MM-DD HH:mm:ss'),
    endTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
    id: '',
  }
  timeRange.value = [listQuery.value.startTime, listQuery.value.endTime]
  search()
}

onBeforeUnmount(() => {
  unwatch()
  window.removeEventListener('resize', resize)
})

function solveData(data: any) {
  data.forEach((item: any) => {
    if (item.device) {
      item.name = `${item.name} (${item.device.deviceStatusName})`
      console.log('修改后的name', item.name)
    }
    if (item.children && item.children.length) {
      solveData(item.children)
    }
  })
  return data
}

watch(timeRange, (val) => {
  if (val) {
    listQuery.value.startTime = `${val[0]}`
    listQuery.value.endTime = `${val[1]}`
  }
  else {
    listQuery.value.startTime = ''
    listQuery.value.endTime = ''
  }
})

onMounted(() => {
  videoTree().then((response) => {
    if (response.code === 200) {
      data.value = response.data
      // data.value = solveData(data.value)
    }
  })
  reset() // 重置
  setTimeout(() => {
    // 获取流媒体token
    getMediaToken().then((res: any) => {
      mediaToken.value = res.data
    })
    // handleResize()
    // window.addEventListener('resize', handleResize)
  }, 200)
})
</script>

<template>
  <app-container style="height: calc(100vh - 110px)">
    <div style="display: flex;height: 100%">
      <el-card class="left">
        <el-input
          v-model="filterText"
          placeholder="设备名称过滤"
          style="margin-bottom: 10px;"
        />
        <el-tree
          ref="treeRef"
          class="filter-tree"
          style="width: 100%;height: 100%;"
          :data="data"
          :filter-node-method="filterNode"
          node-key="id"
          :default-expand-all="true"
          :props="defaultProps"
          highlight-current
          :current-node-key="currentKey"
          @node-click="handleNodeClick"
        >
          <template #default="{ node, data }">
            <span style="display: flex;align-items: center;">
              <el-icon class="loading-rotate" style="margin-right: 5px" v-if="data.device.id === currentLeafId && leafLoading">
                <svg-icon name="icon-loading" />
              </el-icon>
              <el-icon v-if="data.device.deviceStatusName === '在线'" style="margin-right: 5px">
                <svg-icon name="icon-online" />
              </el-icon>
              <el-icon v-if="data.device.deviceStatusName === '离线'" style="margin-right: 5px">
                <svg-icon name="icon-offline" />
              </el-icon>
              <el-tooltip
                class="box-item"
                effect="dark"
                :content="node.label"
                placement="right"
              >
                <template #content>
                  <span>{{ node.label }}</span>
                  <span v-if="data.device.deviceTypeName">({{ data.device.deviceTypeName }})</span>
                </template>
                <span v-if="data.device.deviceTypeName" :style="{ 'color': data.device.deviceStatusName === '在线' ? '#0e932e' : '#606266', 'font-weight': data.device.deviceStatusName === '在线' ? 600 : 500 }">{{ node.label }}</span>
                <span v-else>{{ node.label }}</span>
              </el-tooltip>
              <el-icon v-if="data.device.deviceType === '0'" style="margin-left: 5px">
                <svg-icon name="icon-qiang" :color="data.device.deviceStatusName === '在线' ? '#0e932e' : '#979797'"/>
              </el-icon>
              <el-icon v-if="data.device.deviceType === '1' || data.device.deviceType === '2'" style="margin-left: 5px;">
                <svg-icon name="icon-ball" :color="data.device.deviceStatusName === '在线' ? '#0e932e' : '#979797'" />
              </el-icon>
              <el-icon v-if="data.device.deviceType === '3'" style="margin-left: 5px;">
                <svg-icon name="icon-ball" :color="data.device.deviceStatusName === '在线' ? '#0e932e' : '#979797'" />
              </el-icon>
              <el-icon v-if="data.device.deviceType === '4'" style="margin-left: 5px;">
                <svg-icon name="icon-ytqiang" :color="data.device.deviceStatusName === '在线' ? '#0e932e' : '#979797'" />
              </el-icon>
              <!-- <span v-if="data.device.deviceStatusName === '在线' || node.label.slice(node.label.indexOf('(')) === '(离线)'" :style="{ color: data.device.deviceStatusName === '在线' ? 'green' : 'red' ,  'font-weight': 600 }">{{ node.label.slice(node.label.indexOf('(')) }}</span> -->
            </span>
          </template>
        </el-tree>
      </el-card>
      <div id="home" class="right">
        <div style="display:flex;">
          <search-area :need-clear="true" @search="search('tableClick')" @clear="reset">
            <search-item>
              <el-date-picker
                v-model="timeRange"
                type="datetimerange"
                range-separator="到"
                format="YYYY-MM-DD HH:mm:ss"
                value-format="YYYY-MM-DD HH:mm:ss"
                start-placeholder="开始时间"
                end-placeholder="结束时间"
                style="width: 450px;"
                :clearable="false"
              />
            </search-item>
          </search-area>
          <!-- <div style="position: absolute;right: 20px;top: 22px">
            <el-button type="primary" @click="recordStart('MP4')" plain>录制MP4</el-button>
            <el-button @click="recordStop" type="info" plain>停止录制并保存文件</el-button>
          </div> -->
        </div>
        <!-- <video id="video0" :src="mediaUrl" autoPlay class="videoControl" /> -->
        <video id="video0" :src="mediaUrl" autoPlay style="width: 100%; height: calc(100% - 58px)" />
        <div v-if="leafLoading && mediaRecordList.length" style="font-size: 28px;color: #fff;font-weight: 600;position: absolute;
          z-index: 1000;top: 50%;left: 35%;letter-spacing: 4px;">视频加载中,请稍等...</div>
        <div v-if="!mediaRecordList.length && leafLoading" style="font-size: 28px;color: #fff;font-weight: 600;position: absolute;
        z-index: 1000;top: 50%;left: 50%;letter-spacing: 4px;">视频加载中,请稍等...</div>
      </div>
      <el-card v-if="mediaRecordList.length" class="last">
        <span style="font-size: 13px;font-weight: 600;margin-bottom: 16px;display: block;">
          <span style="color: #0e932e;">【{{ backPlayTitle }}】</span>
          <span>回放时间列表</span>
        </span>
        <div>
          <div
            v-for="(item, index) in mediaRecordList"
            :key="index"
            :style="item.isActive === true ? { backgroundColor: '#589aff', color: '#fff', borderColor: '#589aff' } : { backgroundColor: 'white' }"
            :class="index % 2 === 0 ? 'normal-div even-div' : 'normal-div odd-div'"
            @click="clickTag(item, index)"
          >
            <span>{{ item.startTime }} - {{ item.endTime }}</span>
          </div>
        </div>
      </el-card>
    </div>
  </app-container>
</template>

<style lang="scss" scoped>
.left {
  width: 300px;
  height: 100%;
  padding: 10px;
  overflow: auto;
}
.right {
  width: calc(100% - 310px);
  margin-left: 10px;
  background-color: white;
}
.last {
  width: 390px;
  height: 100%;
  // padding: 10px;
  overflow-y: scroll;
  margin-left: 10px;
}
.tag {
  margin-bottom: 8px;
  cursor: pointer;
}
// .click-tag-title {
//   color: #000;
//   font-weight: 600;
// }
.video-container {
  display: flex;
  flex: 1;
  flex-direction: row;
  justify-content: center;
  align-items: center;
  margin-bottom: 5px;
}
video {
  position: relative;
  object-fit: fill;
  overflow: hidden;
  background: #000;
}

/* 定义一个名为rotate的动画 */
@keyframes rotate {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

/* 应用动画到具体的元素上 */
.loading-rotate {
  animation: rotate 2s linear infinite;
}

.normal-div {
  margin-bottom: 8px;
  cursor: pointer;
  display: inline-flex;
  justify-content: center;
  align-items: center;
  vertical-align: middle;
  height: 24px;
  line-height: 1;
  border-width: 1px;
  border-style: solid;
  border-radius: 5px;
  box-sizing: border-box;
  white-space: nowrap;
  padding: 0 9px;
  font-size: 10px;
}
.even-div {
  border-color: #68c23b;
  color: #68c23b;
}
.odd-div {
  border-color: #f56c6c;
  color: #f56c6c;
}
</style>