<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>