<script lang="ts" setup name="videoControl"> import type { FormInstance, FormRules } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus' import type { Ref } from 'vue' import {getCurrentInstance, nextTick, reactive, ref} from 'vue' import dayjs from 'dayjs' import useWebsocketStore from '@/store/modules/websocket' import { getDevInfo, 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' const { proxy } = getCurrentInstance() as any const width = ref(0) const height = ref(0) const player = ref(null) const websocket = useWebsocketStore() const $route = useRoute() const deviceData: Ref<any> = ref({}) const realh = ref('****') const realv = ref('****') const instance = getCurrentInstance() const videoControl = ref('videoControl') const controlSpeed = ref(7) const loading = ref(false) const total = ref(0) const list = ref([]) const divPlugin = ref(null) const lineList: Ref<any[]> = ref([]) const timeRange = ref<[any, any]>(['', '']) const listQuery = ref({ devId: '', startTime: '', endTime: '', offset: 1, limit: 6, }) // socket更新数据 const unwatch = watch(websocket, (newVal) => { }) // 搜索重置 function fetchData() { search() } function search(isNowPage = false) { if (!isNowPage) { // 是否显示当前页,否则跳转第一页 listQuery.value.offset = 1 } loading.value = true getPicListPage(listQuery.value).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 }) } function createPlayer() { player.value = new window.JSPlugin({ szId: 'player', szBasePath: './', iMaxSplit: 1, iCurrentSplit: 2, openDebug: true, iWidth: width.value, iHeight: height.value, oStyle: { borderSelect: '#FFCC00', }, }) } // 调整大小 const resize = () => { width.value = window.innerWidth - 580 height.value = window.innerHeight - 230 if (player.value !== null) { player.value!.JS_Resize() } } // 抓拍 async function takePhoto() { const time = dayjs().format('YYYYMMDDHHmm') player.value!.JS_CapturePicture(0, `${deviceData.value.monitorName}_${time}.jpg`, 'JPEG', (imageData: any) => { // log('视频抓拍', `开始播报时间:${time}`) pictureAdd({ devId: $route.query.id, picture: imageData, }).then((res: any) => { // 本地 const blob = base64ToBlob(imageData) exportFile(blob, `${deviceData.value.monitorName}_${time}.jpg`) ElMessage.success('抓图成功') search() }) }) } /* 预览&对讲 */ async function realplay() { const playUrl = await getPreviewUrl(deviceData.value.cameraIndexCode) player.value!.JS_Play(playUrl, { playURL: playUrl, mode: 0 }, 0).then( () => { console.log('realplay success') }, (e: any) => { console.error(e) }, ) } function stopPlay() { player.value!.JS_Stop().then( () => { console.log('stop realplay success') }, (e: any) => { console.error(e) }, ) } // 放大 function zoomIn(action: string) { const params = { cameraIndexCode: deviceData.value.cameraIndexCode, command: 'ZOOM_IN', action, } devControl(params).then(() => { // ElMessage.success('放大成功') }) } // 缩小 function zoomOut(action: string) { const params = { cameraIndexCode: deviceData.value.cameraIndexCode, command: 'ZOOM_OUT', action, } devControl(params).then(() => { // ElMessage.success('缩小成功') }) } // 上下左右 function controlWithSpeed(event, type, action) { if (event.detail === 1) { const params = { cameraIndexCode: deviceData.value.cameraIndexCode, command: type, action, } devControl(params).then(() => {}) } } function handleSizeChange(val: number) { listQuery.value.limit = val search(true) } // 改变当前页 function handleCurrentChange(val: number) { listQuery.value.offset = val search(true) } watch(timeRange, (val) => { if (val) { listQuery.value.startTime = `${val[0]}` listQuery.value.endTime = `${val[1]}` } else { listQuery.value.startTime = '' listQuery.value.endTime = '' } }) onMounted(() => { console.log($route.query) // 从路由中获取页面类型参数 if ($route.query && $route.query.id) { setTimeout(() => { window.addEventListener('resize', resize) resize() createPlayer() listQuery.value.devId = $route.query.id!.toString() getDevInfo(listQuery.value.devId).then((response) => { deviceData.value = response.data realplay() }) fetchData() }, 500) } }) onBeforeUnmount(() => { unwatch() window.removeEventListener('resize', resize) }) </script> <template> <div class="video-wrap"> <div id="player" :style="`width:${width}px;height:${height}px;`" :class="videoControl"/> <div class="right" :style="`height:${height}px;`"> <el-tabs tab-position="top" style="height: calc(100% - 30px);width: 100%;margin-bottom: 10px;margin-top: -10px"> <el-tab-pane label="历史截图"> <div style="display: flex;margin-bottom: 10px"> <el-date-picker v-model="timeRange" type="daterange" clearable size="small" format="YYYY-MM-DD" value-format="YYYY-MM-DD" start-placeholder="开始时间" end-placeholder="结束时间" range-separator="到" style="margin-right: 20px" /> <el-button type="primary" @click="fetchData" style="width: 60px" size="small">查 询</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', '0')" @mouseup="controlWithSpeed($event, 'UP', '1')" /> <div class="round-btn" style="top: 90px;left: 95px" @mousedown="controlWithSpeed($event, 'DOWN', '0')" @mouseup="controlWithSpeed($event, 'DOWN', '1')" /> <div class="round-btn" style="top: 48px;left: 53px" @mousedown="controlWithSpeed($event, 'LEFT', '0')" @mouseup="controlWithSpeed($event, 'LEFT', '1')" /> <div class="round-btn" style="top: 48px;left: 135px" @mousedown="controlWithSpeed($event, 'RIGHT', '0')" @mouseup="controlWithSpeed($event, 'RIGHT', '1')" /> </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('0')" @mouseup="zoomIn('1')"> <el-icon style="margin-right: 5px"><svg-icon name="icon-zoomin" /></el-icon>放 大 </el-button> <el-button type="primary" class="btn" @mousedown="zoomOut('0')" @mouseup="zoomOut('1')"> <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: rgba(255,255,255,0.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 { margin-left: 10px; margin-top: 10px; position: relative; background-color: #111111; } .right { height: calc(80vh - 90px); 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: 0px; bottom: 5px; display: flex; overflow: hidden; border-radius: 10px; margin: 5px; box-shadow: 0 2px 12px 0 rgba(0,0,0,.1); 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: rgba(61, 125, 254, 0.53); } .icon-button { background-color: #d6e5fc; border-color: #d6e5fc; padding: 6px; border-radius: 6px; color: #4384ff; :deep(.el-icon) { width: 18px; height: 18px; } //.icon-button-icon { // width: 16px; // height: 16px; //} &:hover { background-color: #c3dafd; color: #fff; } } .add-line { width: 250px; display: flex; margin-left: 10px; z-index: 1111111; } </style>