<!-- SDK --> <script lang="ts" setup name="videoControl"> import type { FormInstance, FormRules } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus' import html2canvas from 'html2canvas' import type { Ref } from 'vue' import { getCurrentInstance, nextTick, reactive, ref } from 'vue' import dayjs from 'dayjs' import { debounce } from 'lodash-es' import useWebsocketStore from '@/store/modules/websocket' import { getDevInfo, ptzControl, restartDev } from '@/api/ptz/dev' import controlImg from '@/assets/images/control.png' import { devControl, devControlWithSpeed, devToPosition, } from '@/api/ptz/control' import { getPicListPage, pictureAdd } from '@/api/ycjg/ssjk' import { base64ToBlob, exportFile } from '@/utils/exportUtils' import { log } from '@/utils/log' import { getPreviewUrl } from '@/utils/hik' import { initPlugin, login, logout, preview, stop } from '@/utils/HKVideo' import { loginDH } from '@/utils/DHVideo' const websocket = useWebsocketStore() const $route = useRoute() const deviceData: Ref<any> = ref({}) const instance = getCurrentInstance() const videoControl = ref('videoControl') const loading = ref(false) const total = ref(0) const list = ref([]) const refreshHK = ref(true) const hkPlugin: any = ref(null) const dhVideo: any = ref(null) const lineList: Ref<any[]> = ref([]) const timeRange = ref<[any, any]>(['', '']) const defaultQuery = { devId: '', startTime: '', endTime: '', offset: 1, limit: 6, } const listQuery = reactive({ ...defaultQuery }) // socket更新数据 const unwatch = watch(websocket, (newVal) => { }) // 搜索重置 function fetchData() { search() } function search(isNowPage = false) { // if (timeRange.value) { // listQuery.startTime = timeRange.value[0] as string || '' // listQuery.endTime = timeRange.value[1] as string || '' // } if (!isNowPage) { // 是否显示当前页,否则跳转第一页 listQuery.offset = 1 } loading.value = true getPicListPage(listQuery).then((res: any) => { list.value = res.data.rows.map((item: any) => { item.url = `${window.localStorage.getItem('baseurl-safe')}/picture/download/${item.url}?token=${window.localStorage.getItem('token')}` return item }) total.value = res.data.total loading.value = false }).catch(() => { loading.value = false }) } // 调整大小 const resize = debounce(() => { if (deviceData.value.nvrManufacture === 'HIKVISION') { stop(0) refreshHK.value = true nextTick(() => { initPlugin(hkPlugin.value.clientWidth, hkPlugin.value.clientHeight, 1, '', false, () => { setTimeout(() => { preview(deviceData.value.nvrIp, '80', deviceData.value.nvrUser, deviceData.value.nvrPassword, 0, true, deviceData.value.nvrChannel) }, 1000) }, () => { }) }) } }, 1000) const handleResize = () => { if (deviceData.value.nvrManufacture === 'HIKVISION') { refreshHK.value = false } resize() } function dataURLtoBlob(dataurl: any) { const arr = dataurl.split(',') const mime = arr[0].match(/:(.*?);/)[1] const bstr = atob(arr[1]) let n = bstr.length const u8arr = new Uint8Array(n) while (n--) { u8arr[n] = bstr.charCodeAt(n) } return new Blob([u8arr], { type: mime }) } // 抓拍 async function takePhoto() { const time = dayjs() log('视频抓拍', `${deviceData.value.monitorName}_抓拍时间:${time.format('YYYY-MM-DD HH:mm:ss')}`) const name = `${deviceData.value.monitorName}_${time.format('YYYY-MM-DD HH_mm_ss')}` if (deviceData.value.nvrManufacture === 'HIKVISION') { WebVideoCtrl.I2_CapturePic(`${name}.jpeg`, { bDateDir: true, }).then((e: any) => { var blob = new Blob([e], { type: 'image/jpeg' }) var reader = new FileReader() reader.readAsDataURL(blob) reader.onload = function (base64: any) { pictureAdd({ devId: $route.query.id, picture: base64.target.result, }).then((res: any) => { ElMessage.success('抓图成功') search() }) } }, () => {}) } else { window.player.capture(`${name}.png`) pictureAdd({ devId: $route.query.id, picture: window.player.getCapture(), }).then((res: any) => { ElMessage.success('抓图成功') search() }) // domToPic(dhVideo.value, time) } } function domToPic(dom: any, time: any) { html2canvas(dom).then((res) => { // 下载到本地 const imgUrl = res.toDataURL('image/png') const aLink = document.createElement('a') aLink.id = 'qrcodeimg' aLink.href = imgUrl aLink.download = `${deviceData.value.monitorName}_${time.format('YYYY-MM-DD HH_mm_ss')}.png` // 导出文件名 document.body.appendChild(aLink) // 模拟a标签点击事件 aLink.click() // 事件已经执行,删除本次操作创建的a标签对象 document.body.removeChild(aLink) // 上传到服务器 const jpg = res.toDataURL('image/jpeg') pictureAdd({ devId: $route.query.id, picture: jpg, }).then((res: any) => { ElMessage.success('抓图成功') search() }) }) } /* 预览&对讲 */ async function realplay() { if (deviceData.value.nvrManufacture === 'HIKVISION') { const canvas = document.getElementById('canvas1') canvas!.id = 'canvas0' } else { window.player.play() } } function stopPlay() { if (deviceData.value.nvrManufacture === 'HIKVISION') { const canvas = document.getElementById('canvas0') canvas!.id = 'canvas1' } else { window.player.pause() } } // 放大 function zoomIn(action: string) { const params = { command: action, horizonSpeed: '30', verticalSpeed: '30', zoomSpeed: '30', } ptzControl(deviceData.value.nvrIndexCode, deviceData.value.cameraIndexCode, params).then((res) => {}) } // 上下左右 function controlWithSpeed(event: any, type: any) { if (event.detail === 1) { const params = { command: type, zoomSpeed: '30', horizonSpeed: '30', verticalSpeed: '30', } ptzControl(deviceData.value.nvrIndexCode, deviceData.value.cameraIndexCode, params).then((res) => {}) } } function handleSizeChange(val: number) { // emit('change', { size: val }) listQuery.limit = val search(true) } // 改变当前页 function handleCurrentChange(val: number) { // emit('change', { page: val }) listQuery.offset = val search(true) } function loginDevice() { login( deviceData.value.nvrIp, 80, deviceData.value.nvrUser, deviceData.value.nvrPassword, 0, true, deviceData.value.nvrChannel) } const disabledPastDateStart = (date: any) => { if (listQuery.endTime !== '') { return date.getTime() > new Date(`${listQuery.endTime} 23:59:59`).getTime() } else { return null } } const disabledPastDateEnd = (date: any) => { if (listQuery.startTime) { return date.getTime() < new Date(`${listQuery.startTime} 00:00:00`).getTime() } else { return null } } onMounted(() => { if ($route.query && $route.query.id) { listQuery.devId = $route.query.id!.toString() getDevInfo(listQuery.devId).then((response) => { deviceData.value = response.data setTimeout(() => { if (response.data.nvrManufacture === 'HIKVISION') { initPlugin(hkPlugin.value.clientWidth, hkPlugin.value.clientHeight, 1, '', false, loginDevice, null) } else { nextTick(() => { loginDH(deviceData.value.nvrIp, '80', deviceData.value.nvrUser, deviceData.value.nvrPassword, deviceData.value.nvrChannel, 0) }) } handleResize() window.addEventListener('resize', handleResize) }, 200) }) fetchData() } }) onBeforeUnmount(() => { try { window.play = null } catch (e) {} unwatch() try { logout(deviceData.value.nvrIp) stop(0) } catch (e) {} window.removeEventListener('resize', handleResize) }) </script> <template> <div class="video-wrap"> <video id="dhVideo0" ref="dhVideo" class="videoControl" /> <canvas id="dhCanvas0" class="dhCanvas" /> <div v-if="deviceData?.nvrManufacture === 'HIKVISION' && refreshHK" id="hkPlugin" ref="hkPlugin" class="plugin videoControl" /> <div class="right"> <el-tabs tab-position="top" style="height: calc(100% - 30px);width: 100%;margin-bottom: 10px;margin-top: -10px;"> <el-tab-pane label="历史截图"> <span style="color: #2f3742;font-size: 15px;margin-top: 2px;">开始时间:</span> <el-date-picker v-model="listQuery.startTime" type="date" clearable size="small" format="YYYY-MM-DD" value-format="YYYY-MM-DD" placeholder="请选择" :disabled-date="disabledPastDateStart" style="width: 160px;z-index: 1111111111;" /> <div> <span style="color: #2f3742;font-size: 15px;margin-top: 2px;">结束时间:</span> <el-date-picker v-model="listQuery.endTime" type="date" clearable size="small" format="YYYY-MM-DD" value-format="YYYY-MM-DD" placeholder="请选择" :disabled-date="disabledPastDateEnd" style="margin: 5px 20px 10px 0;width: 160px;z-index: 1111111111;" /> <el-button type="primary" style="width: 60px;margin-top: -5px;" size="small" @click="fetchData"> 查 询 </el-button> </div> <div style="background-color: white;overflow-y: hidden;overflow-x: scroll;width: 100%;display: flex;flex-wrap: wrap;"> <div v-for="(item, index) in list" :key="index" style="height: 100px;width: 160px;margin: 5px;"> <el-image style="width: 160px; height: 100px;" :src="item.url" fit="fill" /> </div> </div> </el-tab-pane> </el-tabs> <div style="z-index: 99999999;margin: 10px 10px 0;"> <el-pagination small :current-page="listQuery.offset" :page-sizes="[6]" :page-size="listQuery.limit" :total="total" layout="total,prev,pager,next" @size-change="handleSizeChange" @current-change="handleCurrentChange" /> </div> </div> <div class="bottom"> <div class="control-title"> 设备控制 </div> <div name="four" style="width: 150px; height: 130px;"> <el-image style="width: 130px; height: 130px;position: absolute;top: 5px;left: 50px;" :src="controlImg" fit="fill" /> <div> <div class="round-btn" style="top: 8px;left: 95px;" @mousedown="controlWithSpeed($event, 'up')" @mouseup="controlWithSpeed($event, 'stop')" /> <div class="round-btn" style="top: 90px;left: 95px;" @mousedown="controlWithSpeed($event, 'down')" @mouseup="controlWithSpeed($event, 'stop')" /> <div class="round-btn" style="top: 48px;left: 53px;" @mousedown="controlWithSpeed($event, 'left')" @mouseup="controlWithSpeed($event, 'stop')" /> <div class="round-btn" style="top: 48px;left: 135px;" @mousedown="controlWithSpeed($event, 'right')" @mouseup="controlWithSpeed($event, 'stop')" /> </div> </div> <div style="display: flex;max-width: 250px;flex-wrap: wrap;margin: 0 20px;"> <el-button type="primary" class="btn" @click="stopPlay"> <el-icon style="margin-right: 5px;"> <svg-icon name="icon-stop" /> </el-icon>暂 停 </el-button> <el-button type="primary" class="btn" @click="realplay"> <el-icon style="margin-right: 5px;"> <svg-icon name="icon-play" /> </el-icon>恢 复 </el-button> <el-button type="primary" class="btn" @mousedown="zoomIn('zoomin')" @mouseup="zoomIn('stop')"> <el-icon style="margin-right: 5px;"> <svg-icon name="icon-zoomin" /> </el-icon>放 大 </el-button> <el-button type="primary" class="btn" @mousedown="zoomIn('zoomout')" @mouseup="zoomIn('stop')"> <el-icon style="margin-right: 5px;"> <svg-icon name="icon-zoomout" /> </el-icon>缩 小 </el-button> <el-button type="primary" class="btn" @click="takePhoto"> <el-icon style="margin-right: 5px;"> <svg-icon name="icon-camera" /> </el-icon>抓 拍 </el-button> </div> <div style="width: calc(100% - 350px);height: 100%;display: flex;background-color: rgb(255 255 255 / 65%);border-radius: 10px;"> <el-tabs v-if="deviceData !== {}" tab-position="left" style="height: 100%;width: 100%;"> <el-tab-pane label="设备信息"> <el-descriptions style="flex: 1;" class="margin-top" :column="3" border> <el-descriptions-item label="设备名称"> {{ deviceData.monitorName }} </el-descriptions-item> <el-descriptions-item label="设备类型"> {{ deviceData.deviceTypeName }} </el-descriptions-item> <el-descriptions-item label="所属单位"> {{ deviceData.deptName }} </el-descriptions-item> <el-descriptions-item label="所属区域"> {{ deviceData.areaName }} </el-descriptions-item> <el-descriptions-item label="经度"> {{ deviceData.longitude }} </el-descriptions-item> <el-descriptions-item label="纬度"> {{ deviceData.latitude }} </el-descriptions-item> <el-descriptions-item label="位置"> {{ deviceData.location }} </el-descriptions-item> <el-descriptions-item label="备注"> {{ deviceData.description }} </el-descriptions-item> </el-descriptions> </el-tab-pane> </el-tabs> </div> </div> </div> </template> <style lang="scss" scoped> .video-wrap { display: flex; flex-wrap: wrap; } .videoControl { height: calc(100% - 165px); width: calc(100% - 390px); margin-left: 10px; margin-top: 10px; position: absolute; background-color: #111; } .dhCanvas { height: calc(100% - 165px); width: calc(100% - 390px); margin-left: 10px; margin-top: 10px; position: absolute; } .right { height: calc(100% - 165px); width: 360px; background-color: white; border-radius: 10px; padding: 10px; position: absolute; top: 10px; right: 10px; } .bottom { width: calc(100% - 10px); height: 140px; position: absolute; left: 0; bottom: 5px; display: flex; border-radius: 10px; margin: 5px; box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%); overflow: hidden; padding: 5px; justify-content: space-between; } .control-title { color: white; background-color: #ff9f11; border-bottom-right-radius: 10px; border-top-right-radius: 10px; line-height: 30px; font-weight: bold; text-align: center; letter-spacing: 1px; font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimSun, sans-serif; writing-mode: vertical-rl; text-orientation: upright; width: 30px; height: 100px; } .b2 { height: 100%; width: 20%; text-align: center; overflow: hidden; } .btn { margin: 3px auto; width: 100px; } .control-row { display: flex; position: absolute; left: 260px; width: 60%; overflow: hidden; justify-content: flex-start; align-items: center; } .real-text { margin: 6px 10px; color: #6e6e6e; } .ppm-line { padding: 0; height: 100%; flex: 1; } .el-message-box { position: absolute !important; top: calc(50% - 68px) !important; left: 100px !important; } .ptz-control { width: 400px; position: absolute; right: 18%; top: 5px; display: flex; justify-content: center; align-items: flex-start; flex-wrap: wrap; } .round-btn { position: absolute; top: 14px; left: 129px; width: 42px; height: 42px; z-index: 111111; border-radius: 21px; cursor: pointer; } .round-btn:hover { background-color: rgb(61 125 254 / 53%); } .icon-button { background-color: #d6e5fc; border-color: #d6e5fc; padding: 6px; border-radius: 6px; color: #4384ff; :deep(.el-icon) { width: 18px; height: 18px; } &:hover { background-color: #c3dafd; color: #fff; } } .add-line { width: 250px; display: flex; margin-left: 10px; z-index: 1111111; } </style>