<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 html2canvas from 'html2canvas' import { getCurrentInstance, reactive, ref } from 'vue' import dayjs from 'dayjs' import useWebsocketStore from '@/store/modules/websocket' import { initPlugin, login, logout, onlyLogin, preview } from '@/utils/HKVideo' import { getDataHisList, getDevInfo, restartDev } from '@/api/ptz/dev' import type { DevInfo } from '@/views/device/dev-interface' import controlImg from '@/assets/images/control.png' import type { lineDataI } from '@/components/Echart/echart-interface' import type { DateReturn, TypeReturn } from '@/views/statistics/statistics-interface' import type { LineListInfo } from '@/views/line/line-interface' import { delAlarm, getAlarmListPage } from '@/api/ptz/alarm' import redImg from '@/assets/images/中心.png' import { devCancelAlarm, devControl, devControlWithSpeed, devToPosition, setPreset, specialControl, } from '@/api/ptz/control' import type { AlarmListInfo } from '@/views/alarm/alarm-interface' import { isInRange, isInRangeNum } from '@/utils/validate' import { HCCruiseRoute, HCPreset, getLineList, getSerialListPage, handleCruise } from '@/api/ptz/line' import { exportFile } from '@/utils/exportUtils' import devJson from '/public/config/dev.json' import { loginMedia, playBack } from '@/api/ptz/media' const { proxy } = getCurrentInstance() const websocket = useWebsocketStore() const $route = useRoute() const deviceData: Ref<DevInfo> = ref({ id: '', stationId: '', stationName: '', monitorName: '', deviceIp: '', devicePort: '', deviceUser: '', devicePassword: '', nvrIp: '', nvrPort: '', nvrUser: '', nvrPassword: '', nvrChannel: '', deviceType: '', deviceTypeName: '', doorIp: '', doorSn: '', longitude: '', latitude: '', description: '', location: '', // speed: 0, // stopTime: 0, // high: 0, // angle: 0, deviceStatus: '', deviceStatusName: '', }) const wiper = ref(true) const video = ref() let baseUrl = '' const playUrl = ref('') const realppm = ref('****') const realh = ref('****') const realv = ref('****') const devCH4Loading = ref(false) const devCH4XData: Ref<string[]> = ref([]) const devCH4Data: Ref<lineDataI[]> = ref([]) const devCH4YDataMax = ref() const tableData = ref([]) const instance = getCurrentInstance() const loading = ref(false) const videoControl = ref('videoControl') const resumeTime = ref('') const detailTxt = ref('') const inputh = ref('') const inputv = ref('') const selectLine = ref('') const selectPoint = ref('') const controlSpeed = ref(7) const maxSpeed = ref(63) const exeData = ref({ inputh: '', inputv: '', }) const divPlugin = ref(null) const lineList: Ref<LineListInfo[]> = ref([]) const pointList: Ref<any[]> = ref([]) const addData = ref({ speed: '30', time: '3', alarm: '5000', }) const pointName = ref('') const showAdd = ref(false) const isInRangeV = function (min: number, max: number) { return function (rule: any, value: any, callback: any) { if (value === '' || !isInRange(value, min, max)) { callback(new Error(`请输入${min.toString()}-${max.toString()}整数`)) } else { callback() } callback() } } const isInRangeNumV = function (min: number, max: number, length: number) { return function (rule: any, value: any, callback: any) { if (isNaN(value) || value === '') { callback(new Error(`请输入${min.toString()}-${max.toString()}范围${length}位小数`)) } else if (isInRangeNum(Number(value), min, max)) { if (value.toString().includes('.') && value.toString().split('.')[1].length > length) { callback(new Error(`请输入${min.toString()}-${max.toString()}范围${length}位小数`)) } callback() } else { callback(new Error(`请输入${min.toString()}-${max.toString()}范围${length}位小数`)) } callback() } } const exeFormRef = ref<FormInstance>() const addFormRef = ref<FormInstance>() // 校验规则 const exeRules = reactive<FormRules>({ inputh: [{ required: true, message: '0~360整数/一位小数', trigger: [], validator: isInRangeNumV(0, 360, 1) }], inputv: [{ required: true, message: '-90~90整数/一位小数', trigger: [], validator: isInRangeNumV(-90, 90, 1) }], }) const rules = reactive<FormRules>({ presetName: [{ required: true, message: '预置点名不能为空', trigger: ['blur', 'change'] }], cruiseRoute: [{ required: true, message: '巡航路径不能为空', trigger: ['blur', 'change'] }], speed: [{ type: 'number', required: true, message: '1~63整数', trigger: [], validator: isInRangeV(1, 63) }], time: [{ type: 'number', required: true, message: '0~255整数', trigger: [], validator: isInRangeV(0, 255) }], alarm: [{ type: 'number', required: true, message: '0~10000整数', trigger: [], validator: isInRangeV(0, 10000) }], }) // socket更新数据 const unwatch = watch(websocket, (newVal) => { if (newVal.wsData && newVal.wsData.monitorId === deviceData.value.id && (newVal.wsData.type === 'gasAlarm' || newVal.wsData.type === 'AIAlarm')) { getAlarm() } else if (newVal.wsData && newVal.wsData.deviceIp === deviceData.value.deviceIp) { // if (newVal.wsData.type === 'gasAlarm' || newVal.wsData.type === 'AIAlarm') { // else if (newVal.wsData.type === undefined) { realppm.value = newVal.wsData.gasData realh.value = newVal.wsData.horizontal realv.value = newVal.wsData.verticalAngle devCH4Loading.value = true // 判断折现数据长度 if (devCH4XData.value.length > 300) { devCH4XData.value.splice(0, 100) devCH4Data.value[0].data.splice(0, 100) } devCH4XData.value = [...devCH4XData.value, dayjs(newVal.wsData.time).format('HH:mm:ss')] devCH4Data.value = [{ name: 'CH4', data: devCH4Data.value[0].data.concat(newVal.wsData.gasData) }] devCH4Loading.value = false // } } }) function formatTooltip(val) { return `转动速度:${val}` } function tableRowClassName({ row, rowIndex }) { if (rowIndex % 2 === 1) { return 'e-row' } return '' } // 刷新报警列表 function getAlarm() { loading.value = true const parans = { monitorId: deviceData.value.id, alarmStatus: '0', offset: 1, limit: 100, } getAlarmListPage(parans).then((res: { data: { rows: []; total: number } }) => { tableData.value = res.data.rows.map((item: any) => { return { ...item, position: `${item.alarmDirection},${item.alarmPitch}`, } }) loading.value = false }) } // 获取历史甲烷浓度 function getCH4() { devCH4Loading.value = true const param = { monitorId: $route.query.id, startTime: dayjs(Date.now() - 1 * 60 * 1000).format('YYYY-MM-DD HH:mm:ss'), endTime: dayjs().format('YYYY-MM-DD HH:mm:ss'), } // 历史浓度 getDataHisList(param).then((res) => { const yValue = res.data.reverse().map((item: DateReturn) => { if (Object.keys(item).toString().includes('num')) { Number(item.num) } else { Number(item.concentration) } }) devCH4YDataMax.value = Math.max(yValue) > 10 ? Math.max(yValue) : 10 devCH4XData.value = res.data.map((item: TypeReturn) => item.logTime ? item.logTime.split(' ')[1] : '') devCH4Data.value = [{ name: 'CH4', data: yValue }] devCH4Loading.value = false }) } // 设备消警 function deviceCancelAlarm() { ElMessageBox.confirm(`确定要将${deviceData.value.monitorName}设备消警?`, '设备消警', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', }).then(() => { devCancelAlarm(deviceData.value.id).then((res) => { if (res.code.toString() === '200') { ElMessage.success('设备消警成功') } getAlarm() }) }) } // 全部消警 function cancelAllAlarm() { ElMessageBox.confirm('确定要将所有设备消警?', '全部消警', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', }).then(() => { devCancelAlarm('').then((res) => { if (res.code.toString() === '200') { ElMessage.success('全部消警成功') } getAlarm() }) }) } // 消警单条 function cancelRowAlarm(row: AlarmListInfo) { resumeTime.value = '' detailTxt.value = '' if (row.alarmType !== '0' && row.alarmType !== '1') { ElMessageBox.confirm( h('div', [ h('div', { style: { fontSize: '17px', fontWeight: 'bold', }, }, '处置结果:'), h('textarea', { style: { padding: '4px', width: '100%', marginTop: '4px', lineHeight: '20px', }, props: { value: detailTxt.value, }, value: detailTxt.value, onInput: (e) => { detailTxt.value = e.target.value }, }), ]), '确定要取消该条报警吗?', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', }).then(() => { delAlarm({ id: row.id, detail: detailTxt.value }).then((res) => { if (res.code.toString() === '200') { ElMessage.success('取消报警成功') } getAlarm() }) }) } else { ElMessageBox.confirm( h('div', [ h('i', { style: { fontSize: '17px', fontStyle: 'normal', fontWeight: 'bold', }, }, '恢复报警时间:'), h('input', { style: { padding: '4px', lineHeight: '20px', marginBottom: '2px', float: 'right', rows: '2', }, props: { value: resumeTime.value, }, value: resumeTime.value, onInput: (e) => { resumeTime.value = e.target.value }, }), h('div', { style: { fontSize: '17px', fontWeight: 'bold', }, }, '处置结果:'), h('textarea', { style: { padding: '4px', width: '100%', marginTop: '4px', lineHeight: '20px', }, props: { value: detailTxt.value, }, value: detailTxt.value, onInput: (e) => { detailTxt.value = e.target.value }, }), ]), '确定要取消该条报警吗?', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', beforeClose: (action, instance, done) => { if (action === 'confirm') { if (isNaN(resumeTime.value)) { ElMessage.error('请输入整数') } else if (!Number.isInteger(Number(resumeTime.value))) { ElMessage.error('请输入整数') } else { done() } } else { done() } }, }).then(() => { delAlarm({ id: row.id, resumeTime: resumeTime.value, detail: detailTxt.value }).then((res) => { if (res.code.toString() === '200') { ElMessage.success('取消报警成功') } getAlarm() }) }) } } // 定位 function toPosition(row: AlarmListInfo) { ElMessage.info('已下发定位指令请等待') const params = { deviceIp: deviceData.value.deviceIp, horizontalAngle: row.alarmDirection, verticalAngle: row.alarmPitch, } devToPosition(params).then((res) => { if (res.code === 200) { ElMessage.success('定位成功') } }) } // 录像 function playBackVideo(row: AlarmListInfo) { ElMessage.info('已下发录像指令请等待') loginMedia().then((response) => { const accessToken = response.data.accessToken const startTime = dayjs(row.alarmTime).subtract(5, 'second').format('YYYY-MM-DD HH:mm:ss') const endTime = dayjs(row.alarmTime).add(5, 'second').format('YYYY-MM-DD HH:mm:ss') playBack(devJson[row.monitorName], startTime, endTime, accessToken).then((res) => { playUrl.value = res.data.fmp4 video.value.play() setTimeout(() => { playUrl.value = baseUrl video.value.play() }, 11000) }) }) } const canvas = ref(null) function takePhoto() { const szPicName = `${deviceData.value.monitorName}_${new Date().getTime()}.png` const w = video.value.clientWidth const h = video.value.clientHeight canvas.value.width = w canvas.value.height = h // 设置宽高 const ctx = canvas.value.getContext('2d') ctx?.drawImage(video.value, 0, 0, w, h) // video写入到canvas clientHeight const plShareImg = new Image() plShareImg.crossOrigin = 'anonymous' // 添加此行anonymous必须小写 plShareImg.src = canvas.value.toDataURL('image/png') // 最终海报 // downloadFileByBase64(plShareImg, szPicName) const aLink = document.createElement('a') aLink.id = 'qrcodeimg' aLink.href = plShareImg.src aLink.download = szPicName document.body.appendChild(aLink) // 模拟a标签点击事件 aLink.click() // 事件已经执行,删除本次操作创建的a标签对象 document.body.removeChild(aLink) } // 放大 function zoomIn(isStop: string) { console.log(isStop) const params = { deviceIp: deviceData.value.deviceIp, command: 'zoomIn', isStop, } devControl(params).then(() => { // ElMessage.success('放大成功') }) } // 缩小 function zoomOut(isStop: string) { const params = { deviceIp: deviceData.value.deviceIp, command: 'zoomOut', isStop, } devControl(params).then(() => { // ElMessage.success('缩小成功') }) } // 上下左右 function controlWithSpeed(event, type, isStop) { // console.log(event.detail, type, isStop) if (event.detail === 1) { const params = { deviceIp: deviceData.value.deviceIp, command: type, isStop, speed: controlSpeed.value.toString(), } devControlWithSpeed(params).then(() => {}) } else if (event.detail === 2) { // 小角度转动 let horizontalAngle = Number(realh.value) let verticalAngle = Number(realv.value) switch (type) { case 'up': verticalAngle += deviceData.value.deviceType === '1' ? -1 : 1 break case 'down': verticalAngle += deviceData.value.deviceType === '1' ? 1 : -1 break case 'left': horizontalAngle += -1 break case 'right': horizontalAngle += 1 break } if (horizontalAngle < 0 || horizontalAngle > 360) { return } if (verticalAngle < -90 || verticalAngle > 90) { return } const params = { deviceIp: deviceData.value.deviceIp, horizontalAngle, verticalAngle, } devToPosition(params).then(() => {}) } } // 雨刷 function wiperChange() { if (wiper.value) { const params = { deviceIp: deviceData.value.deviceIp, command: 'clean', isStop: '0', } devControl(params).then((res) => { if (res.code.toString() === '200') { ElMessage.success('雨刷打开') } }) } else { const params = { deviceIp: deviceData.value.deviceIp, command: 'clean', isStop: '1', } devControl(params).then((res) => { if (res.code.toString() === '200') { ElMessage.success('雨刷关闭') } }) } } // 断点重启 function cq() { restartDev(deviceData.value.doorIp, deviceData.value.doorSn).then(() => { ElMessage.success('重启成功') }) } // 复位 function fw() { // const params = { // deviceIp: deviceData.value.deviceIp, // command: 'restart', // value: '', // } // specialControl(params).then((res) => { // if (res.code.toString() === '200') { // ElMessage.success('复位成功') // } // }) restartDev(deviceData.value.doorIp, deviceData.value.doorSn).then(() => { ElMessage.success('复位成功') }) } // 自检 function zj() { const params = { deviceIp: deviceData.value.deviceIp, command: '79', } setPreset(params).then((res) => { if (res.code.toString() === '200') { ElMessage.success('自检成功') } }) } // 角度执行 function exeAngle() { if (exeFormRef.value) { exeFormRef.value?.validate((valid: boolean) => { if (valid) { const params = { deviceIp: deviceData.value.deviceIp, horizontalAngle: exeData.value.inputh, verticalAngle: exeData.value.inputv, } devToPosition(params).then((res) => { if (res.code.toString() === '200') { ElMessage.success('角度执行成功') } }) } }) } setTimeout(() => { exeFormRef.value?.clearValidate() }, 5000) } // 获取设备巡航线 function fetchLineList() { selectLine.value = '' getLineList('', deviceData.value.id).then((res) => { lineList.value = res.data }) } // 获取巡航点 function fetchPoint() { const arr = lineList.value.filter(item => item.id === selectLine.value) if (arr.length === 0) { return } const params = { monitorId: deviceData.value.id, lineNum: selectLine.value, offset: 1, limit: 200, } getSerialListPage(params).then( (res: { data: { rows: []; total: number } }) => { pointList.value = res.data.rows }, ) selectPoint.value = '' } // 开启巡航线 function startLine() { if (selectLine.value === '') { ElMessage.warning('请选择巡航路径进行开启!') } else { const params = { deviceIp: deviceData.value.deviceIp, command: 'start', cruiseRoute: selectLine.value, interval: '750', } handleCruise(params).then((res) => { console.log(res) }) } } // 暂停巡航线 function pauseLine() { if (selectLine.value === '') { ElMessage.warning('请选择巡航路径进行暂停!') } else { const params = { deviceIp: deviceData.value.deviceIp, command: 'stop', interval: '750', } handleCruise(params).then((res) => { console.log(res) }) } } // 添加巡航线 function addLine() { const arr = lineList.value.filter(item => item.id === selectLine.value) if (arr.length > 0 || selectLine.value === '') { // 不是新填写 ElMessage.warning('请填写新巡航线名称!') return } const param = { deviceIp: deviceData.value.deviceIp, command: 'cruiseAdd', cruiseName: selectLine.value, cruiseValue: '', } HCCruiseRoute(param).then((res) => { if (res.code.toString() === '200') { ElMessage.success('巡航线添加成功') } fetchLineList() }) } // 删除巡航线 function delLine() { const arr = lineList.value.filter(item => item.id === selectLine.value) if (arr.length === 0 || selectLine.value === '') { // 不是新填写 ElMessage.warning('请选择要删除的巡航线!') return } ElMessageBox.confirm('确定要删除所选巡航线吗?', '确认删除', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', }).then(() => { const param = { deviceIp: deviceData.value.deviceIp, command: 'cruiseDelete', cruiseName: arr[0].lineName, cruiseValue: selectLine.value, } HCCruiseRoute(param).then((res) => { if (res.code.toString() === '200') { ElMessage.success('巡航线删除成功') } fetchLineList() }) }) } // 删除预置点 function delPoint() { if (selectLine.value === '' || selectPoint.value === '') { ElMessage.warning('请选择预置点!') } else { const point = pointList.value.filter(item => item.id === selectPoint.value)[0] ElMessageBox.confirm(`确定要删除${point.serialName}预置点吗?`, '确认删除', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', }).then(() => { const params = { deviceIp: deviceData.value.deviceIp, command: 'presetDel', presetIndex: point.serialNum, cruiseRoute: point.lineNum, presetName: point.serialName, cruiseName: point.lineName, } HCPreset(params).then((res) => { if (res.code.toString() === '200') { ElMessage.success('删除成功') } fetchPoint() }) }) } } // 转至预置点 function toPoint() { if (selectLine.value === '' || selectPoint.value === '') { ElMessage.warning('请选择预置点!') } else { const point = pointList.value.filter(item => item.id === selectPoint.value)[0] const params = { deviceIp: deviceData.value.deviceIp, horizontalAngle: point.direction, verticalAngle: point.pitch, } devToPosition(params).then((res) => { if (res.code.toString() === '200') { ElMessage.success('定位成功') } }) } } // 插入弹窗 function addShow() { pointName.value = '' showAdd.value = true } // 添加预置点 function addPoint() { if (selectLine.value === '') { ElMessage.warning('请选择巡航线!') return } const arr = pointList.value.filter(item => item.id === selectPoint.value) if (arr.length > 0 || selectPoint.value === '') { // 不是新填写 ElMessage.warning('请填写新预置点名称!') return } // 判断新填写 if (addFormRef.value) { addFormRef.value?.validate((valid: boolean) => { if (valid) { const params = { deviceIp: deviceData.value.deviceIp, command: 'presetAdd', presetIndex: '', cruiseRoute: selectLine.value, presetName: selectPoint.value, cruiseName: lineList.value.filter(item => item.id === selectLine.value)[0].lineName, direction: realh.value, pitch: realv.value, speed: addData.value.speed, stopTime: addData.value.time, alarmValue: addData.value.alarm, } HCPreset(params).then((res) => { if (res.code.toString() === '200') { ElMessage.success('预置点添加成功') } }) } }) } setTimeout(() => { addFormRef.value?.clearValidate() }, 5000) } // 插入预置点 function insertPoint() { if (selectLine.value === '') { ElMessage.warning('请选择巡航线!') return } const arr = pointList.value.filter(item => item.id === selectPoint.value) if (arr.length === 0) { ElMessage.warning('请选择被插入预置点!') return } if (pointName.value === '') { ElMessage.warning('请选择插入预置点名称!') return } // 判断新填写 if (addFormRef.value) { addFormRef.value?.validate((valid: boolean) => { if (valid) { const params = { deviceIp: deviceData.value.deviceIp, command: 'presetInsert', presetIndex: selectPoint.value, cruiseRoute: selectLine.value, presetName: pointName.value, cruiseName: lineList.value.filter(item => item.id === selectLine.value)[0].lineName, direction: realh.value, pitch: realv.value, speed: addData.value.speed, stopTime: addData.value.time, alarmValue: addData.value.alarm, } HCPreset(params).then((res) => { if (res.code.toString() === '200') { ElMessage.success('预置点添加成功') } }) } }) } setTimeout(() => { addFormRef.value?.clearValidate() }, 5000) } const handleDoubleClick = (index, fullSceen, event) => { console.log(event.point) const x = (event.point[0] - 0.5) * 59.8 const y = (event.point[1] - 0.5) * 30 // (29.9 * divPlugin.value.clientHeight / divPlugin.value.clientWidth) console.log(x, y) const params = { deviceIp: deviceData.value.deviceIp, horizontalAngle: Number(realh.value) + x, verticalAngle: Number(realv.value) - y, } devToPosition(params).then(() => { // ElMessage.success('定位成功') }) } // 登录设备 function loginDevice() { login( deviceData.value.deviceIp, deviceData.value.id === '1655767080138518529' ? '81' : '80', deviceData.value.deviceUser, deviceData.value.devicePassword, 0) if (proxy.NVR) { login( deviceData.value.nvrIp, deviceData.value.nvrPort, deviceData.value.nvrUser, deviceData.value.nvrPassword, 0, true, deviceData.value.nvrChannel) // onlyLogin( // deviceData.value.nvrIp, // deviceData.value.nvrPort, // deviceData.value.nvrUser, // deviceData.value.nvrPassword, // 0, // true, // deviceData.value.nvrChannel) } } onMounted(() => { console.log($route.query) // 从路由中获取页面类型参数 if ($route.query && $route.query.id) { getDevInfo($route.query.id.toString()).then((response) => { deviceData.value = response.data playUrl.value = `http://192.168.2.4:8091/rtp/11010820001110020000_${devJson[deviceData.value.monitorName]}.live.mp4` baseUrl = `http://192.168.2.4:8091/rtp/11010820001110020000_${devJson[deviceData.value.monitorName]}.live.mp4` video.value.play() setTimeout(() => { // 甲烷历史浓度 getCH4() // 设备报警 getAlarm() // 获取巡航线 fetchLineList() }, 200) }) } }) onBeforeUnmount(() => { unwatch() window.location.reload() }) </script> <template> <div class="video-wrap"> <div class="left-box"> <div class="box-card" style="display: flex;height: 42%;"> <div style="width: 110px;overflow: hidden;"> <div class="control-title"> 实时监控 </div> <div style="color: #0a0a0a;background-color: #dcdcdc;margin: 6px 2px;text-align: center;"> {{ deviceData.monitorName }} </div> <div class="real-text"> 气体浓度: </div> <div class="real-text" style="margin-left: 10px;font-weight: bold;"> {{ realppm }}ppm·m </div> <div class="real-text"> 水平,垂直: </div> <div class="real-text" style="margin-left: 10px;font-weight: bold;"> {{ realh }}°,{{ realv }}° </div> <!-- <div class="real-text"> --> <!-- 垂直角度: --> <!-- </div> --> <!-- <div class="real-text" style="margin-left: 20px"> --> <!-- {{ realv }}° --> <!-- </div> --> </div> <div v-loading="devCH4Loading" class="ppm-line"> <line-chart :x-axis-data="devCH4XData" width="100%" :data="devCH4Data" unit="" :grid="{ top: 10, left: 10, right: 10, bottom: 20, containLabel: true }" /> </div> </div> <div class="box-card" style="height: calc(58% - 10px);"> <div class="control-title"> 告警列表 </div> <div style="top: -30px;position: relative;float: right;margin-right: 10px;"> <el-button type="primary" round style="height: 26px;" @click="deviceCancelAlarm"> 设备消警 </el-button> <el-button type="primary" round style="height: 26px;" @click="cancelAllAlarm"> 全部消警 </el-button> </div> <el-table v-loading="loading" :data="tableData" class="alarm-table" :row-class-name="tableRowClassName" border size="small" > <el-table-column prop="alarmTime" label="时 间" align="center" /> <el-table-column prop="alarmValue" label="告警值" align="center" /> <el-table-column prop="position" label="方 位" align="center" /> <el-table-column prop="alarmTypeName" label="告警事件" align="center" /> <el-table-column label="操 作" align="center" width="110"> <template #default="scope"> <el-button type="primary" link size="small" class="table-text-button" @click="cancelRowAlarm(scope.row)"> 消警 </el-button> <el-button type="primary" link size="small" class="table-text-button" @click="toPosition(scope.row)"> 定位 </el-button> <el-button type="primary" link size="small" class="table-text-button" @click="playBackVideo(scope.row)"> 录像 </el-button> </template> </el-table-column> </el-table> </div> </div> <div class="bottom"> <div class="box-card" style="height: 100%;width: calc(80% - 5px);"> <div class="control-title"> 云台控制 </div> <div name="four"> <el-image style="width: 130px; height: 130px;position: absolute;top: 10px;left: 110px;" :src="controlImg" fit="fill" /> <div> <div class="round-btn" style="top: 13px;left: 155px;" @mousedown="controlWithSpeed($event, 'up', '0')" @mouseup="controlWithSpeed($event, 'up', '1')" /> <div class="round-btn" style="top: 95px;left: 155px;" @mousedown="controlWithSpeed($event, 'down', '0')" @mouseup="controlWithSpeed($event, 'down', '1')" /> <div class="round-btn" style="top: 53px;left: 113px;" @mousedown="controlWithSpeed($event, 'left', '0')" @mouseup="controlWithSpeed($event, 'left', '1')" /> <div class="round-btn" style="top: 53px;left: 195px;" @mousedown="controlWithSpeed($event, 'right', '0')" @mouseup="controlWithSpeed($event, 'right', '1')" /> </div> <el-slider v-model="controlSpeed" style="width: 140px;position: absolute;bottom: 5px;left: 110px;" :max="maxSpeed" :format-tooltip="formatTooltip" /> </div> <div class="control-row" style="top: 10px;"> <span style="margin: 0 10px;">巡航号</span> <el-select v-model="selectLine" placeholder="巡航号" clearable filterable allow-create default-first-option style="width: 200px;margin-right: 10px;" @change="fetchPoint" > <el-option v-for="item in lineList" :key="item.id" :label="item.lineName" :value="item.id" /> </el-select> <icon-button v-show="lineList.length < 5" icon="icon-add" title="新增巡航线" @click="addLine" /> <icon-button icon="icon-delete" title="删除巡航线" style="margin-left: 10px;" @click="delLine" /> <icon-button icon="icon-play" title="开启巡航" @click="startLine" /> <icon-button icon="icon-pause" title="停止巡航" style="margin-left: 10px;" @click="pauseLine" /> </div> <div class="control-row" style="top: 70px;"> <span style="margin: 0 10px;">预置点</span> <el-select v-model="selectPoint" placeholder="预置点" filterable allow-create default-first-option style="width: 200px;margin-right: 10px;" > <el-option v-for="item in pointList" :key="item.id" :label="item.serialName" :value="item.id" /> </el-select> <icon-button v-show="pointList.length < 200" icon="icon-add" title="新增预置点" @click="addPoint" /> <icon-button icon="icon-delete" title="删除预置点" style="margin-left: 10px;" @click="delPoint" /> <icon-button icon="icon-to-position" title="转至预置点" @click="toPoint" /> <icon-button v-show="deviceData.deviceType !== '2' && pointList.length < 200" id="addLineBtn" icon="icon-insert" title="插入预置点" style="margin-left: 10px;" @click="addShow" /> <div v-show="showAdd" id="addLineDiv" class="add-line"> <el-input v-model="pointName" placeholder="预置点名称" style="width: 150px;" /> <icon-button icon="search-up" title="提交" style="margin-left: 5px;" @click="insertPoint" /> <div style="margin-left: 5px;color: #4788fe;margin-top: -5px;font-weight: 800;cursor: pointer;" @click="showAdd = false"> × </div> </div> </div> <div class="control-row" style="top: 130px;"> <el-form ref="addFormRef" :model="addData" :rules="rules" label-position="right" label-width="110px" size="default" @submit.prevent> <el-row> <el-form-item label="巡航速度(°/s)" prop="speed"> <el-input v-model="addData.speed" placeholder="例如:30" style="width: 120px;" /> </el-form-item> <el-form-item label="停留时间(s)" prop="time"> <el-input v-model="addData.time" placeholder="例如:3" style="width: 120px;" /> </el-form-item> <el-form-item label="阈值(ppm·m)" prop="alarm"> <el-input v-model="addData.alarm" placeholder="例如:5000" style="width: 120px;" /> </el-form-item> </el-row> </el-form> </div> <div class="ptz-control"> <el-icon style="font-size: 20px;margin-right: 5px;margin-top: 5px;"> <svg-icon name="icon-clean" /> </el-icon> <el-tooltip :content="`雨刷:${wiper ? '开' : '关'} `" placement="bottom"> <el-switch v-model="wiper" @change="wiperChange" /> </el-tooltip> <icon-button icon="icon-fw" title="复 位" style="margin-left: 15px;" @click="fw" /> <icon-button icon="icon-zj" title="自 检" size="26" @click="zj" /> <!-- <icon-button icon="icon-cq" title="重 启" size="26" @click="cq" /> --> <el-form ref="exeFormRef" :model="exeData" :rules="exeRules" label-position="left" label-width="10px" size="default" @submit.prevent> <el-form-item label="" prop="inputh"> <el-input v-model="exeData.inputh" placeholder="水平角度" style="width: 120px;" /> </el-form-item> <el-form-item label="" prop="inputv"> <el-input v-model="exeData.inputv" placeholder="垂直角度" style="width: 120px;" /> </el-form-item> <el-button type="primary" round style="margin-left: 25px;margin-top: -5px;height: 30px;" @click="exeAngle"> 角度执行 </el-button> </el-form> </div> </div> <div class="box-card b2"> <div class="control-title"> 视频控制 </div> <el-button type="primary" round class="btn" @click="takePhoto"> <el-icon style="margin-right: 5px;"> <svg-icon name="icon-camera" /> </el-icon> 抓 拍 </el-button> <div> <el-button type="primary" round class="btn" @mousedown="zoomIn('0')" @mouseup="zoomIn('1')"> <el-icon style="margin-right: 5px;"> <svg-icon name="icon-zoomin" /> </el-icon> 放 大 </el-button> </div> <div> <el-button type="primary" round class="btn" @mousedown="zoomOut('0')" @mouseup="zoomOut('1')"> <el-icon style="margin-right: 5px;"> <svg-icon name="icon-zoomout" /> </el-icon> 缩 小 </el-button> </div> </div> </div> <video id="video" ref="video" crossOrigin="anonymous" :src="playUrl" controls autoPlay class="plugin" :class="videoControl" /> <canvas v-show="false" id="canvas" ref="canvas" /> <el-image :src="redImg" class="red-img-control" /> </div> </template> <style lang="scss"> .video-wrap { padding: 0 5px; width: calc(100% - 10px); height: 100%; margin-left: 5px; } .videoControl { float: right; height: calc(76vh - 70px); width: 65%; position: relative; } .red-img-control { position: absolute; top: calc((76vh - 70px) / 2 - 20px); right: calc(34.125% - 20px); width: 40px; height: 40px; z-index: 999999; } .left-box { width: 35%; height: calc(76vh - 60px); position: absolute; left: 0; top: 0; padding: 5px; } .bottom { width: 100%; height: calc(24vh + 5px); position: absolute; left: 0; bottom: 5px; display: flex; justify-content: space-between; padding: 0 5px; overflow: hidden; } .box-card { width: 100%; height: calc(50% - 5px); margin-bottom: 5px; padding: 5px !important; box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%); overflow: hidden; } .control-title { color: white; background-color: #ff9f11; border-radius: 10px; width: 100px; height: 30px; 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; } .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; } .alarm-table { width: calc(100% - 20px); height: calc(58% - 60px); position: absolute; margin-top: 5px; overflow-y: scroll; } .el-table .e-row { background: #9ed7ff; } .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>