<template>
|
<div class="taskDetailsLeft">
|
<div class="title">负载控制</div>
|
<div
|
class="singleCol"
|
v-for="item in list1.filter(i => i.show)"
|
@click="cameraType(item)"
|
:key="item.key"
|
:class="{ active: item.key === cameraParams.camera_type }"
|
>
|
{{ item.name }}相机
|
</div>
|
<div class="multiCol">
|
<div @click="ptzResetMode(0)">云台回中</div>
|
<div @click="ptzResetMode(1)">云台朝下</div>
|
</div>
|
<el-select class="qualityChange" v-model="lineQuality" @change="qualityChange">
|
<el-option v-for="item in qualityList" :key="item.id" :label="item.name" :value="item.id" />
|
</el-select>
|
<div class="multiCol">
|
<div @click="takePictures">拍照</div>
|
<div @click="recordFun">{{ isRecording ? '录像中...' : '录像' }}</div>
|
</div>
|
<div class="multiCol" v-if="wsInfo?.psdk_widget_values">
|
<div @click="shoutFun">{{ isRecordShouting ? '喊话' : '停止喊话' }}</div>
|
<div @click="broadcastFun">广播</div>
|
</div>
|
<div class="percent" v-if="isRecordShouting && percent > 0">
|
<el-progress :text-inside="true" :stroke-width="10" :percentage="percent"></el-progress>
|
</div>
|
<div class="cameraZoom">
|
<el-slider
|
v-model="cameraParams.zoom_factor"
|
vertical
|
:min="cameraParams.camera_type === 'ir' ? 2 : 0"
|
:max="cameraParams.camera_type === 'ir' ? 200 : 200"
|
@change="sliderChange"
|
/>
|
<div class="cameraZoomText">{{cameraParams.zoom_factor}}X</div>
|
</div>
|
</div>
|
<!-- 广播列表 -->
|
<div class="broadcast" v-if="isBroadcast">
|
<div class="title">
|
<span>语音内容</span>
|
<img src="@/assets/images/taskManagement/taskIntermediateContent/broadcast-list.png" alt="" />
|
<img @click="isBroadcast = !isBroadcast" class="close" src="@/assets/images/taskManagement/taskIntermediateContent/close.png" alt="">
|
</div>
|
<div class="search-btn">
|
<div class="input-set">
|
<label>文件名称:</label><el-input v-model="searchParams.name" placeholder="请输入广播名称"></el-input>
|
</div>
|
<div class="btn-set">
|
<div class="btn" @click="clearData">清空</div>
|
<div class="btn" @click="getFileList">搜索</div>
|
<el-upload
|
class="btn"
|
action="/upload"
|
:show-file-list="false"
|
:before-upload="handleUpload">
|
音频上传
|
</el-upload>
|
</div>
|
</div>
|
<div class="video-table">
|
<div class="table-header">
|
<div class="name">音频名称</div>
|
<div class="operate">操作</div>
|
</div>
|
<div class="table-body" v-for="item in tableList">
|
<div class="name">{{ item.name }}</div>
|
<div class="operate"><span class="btn" @click="playVoice(item)">播放</span></div>
|
</div>
|
</div>
|
<div class="pagination">
|
<el-pagination
|
background
|
:page-sizes="[10, 20, 30, 50]"
|
v-model:current-page="searchParams.page"
|
v-model:page-size="searchParams.page_size"
|
layout=" prev, pager, next"
|
:total="total"
|
@change="pageChange"
|
/>
|
</div>
|
</div>
|
</template>
|
|
<script setup>
|
import EventBus from '@/event-bus'
|
import {
|
callPhotoAndVideoCmd,
|
cameraParamsChangeApi,
|
ptzResetModeApi,
|
startVoice,
|
stayAwayRiver,
|
getVoiceFile,
|
playAudio,
|
uploadSpeak } from '@/api/payload'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { throttle } from 'lodash'
|
import Recorder from 'js-audio-recorder';
|
import dayjs from 'dayjs'
|
|
const wsInfo = inject('wsInfo')
|
// 初始化喊话
|
let globalShout = null
|
const isRecording = ref(false)
|
const hasIr = inject('hasIr')
|
const list1 = computed(() => {
|
return [
|
{ name: '广角', key: 'wide',show:true },
|
{ name: '变焦', key: 'zoom', show: true },
|
{ name: '红外', key: 'ir',show: hasIr.value },
|
]
|
})
|
const cameraParams = ref({
|
zoom_factor: 0,
|
camera_type: 'wide',
|
})
|
const droneSn = inject('droneSn')
|
const dockSn = inject('dockSn')
|
const lineQuality = inject('lineQuality')
|
const qualityList = [
|
{ name: '自适应', id: 0 },
|
{ name: '流畅', id: 1 },
|
{ name: '标清', id: 2 },
|
{ name: '高清', id: 3 },
|
{ name: '超清', id: 4 },
|
]
|
|
function ptzResetMode(resetMode) {
|
ptzResetModeApi({ sn: dockSn.value, resetMode })
|
}
|
|
// 倍率调整
|
function sliderChange(val) {
|
if (cameraParams.value.camera_type !== 'ir') {
|
cameraParams.value.camera_type = val > 1 ? 'zoom' : 'wide'
|
}
|
cameraParamsChangeThrottle()
|
}
|
|
// 修改相机参数-节流版
|
const cameraParamsChangeThrottle = throttle(cameraParamsChange, 500)
|
|
// 修改相机参数
|
function cameraParamsChange() {
|
const { camera_type, zoom_factor } = cameraParams.value
|
cameraParamsChangeApi({
|
sn: dockSn.value,
|
...cameraParams.value,
|
zoom_factor: camera_type === 'wide' ? undefined : zoom_factor,
|
})
|
}
|
|
function cameraType(row) {
|
cameraParams.value.camera_type = row.key
|
if (row.key === 'wide') {
|
cameraParams.value.zoom_factor = 0
|
}
|
if (row.key === 'zoom') {
|
cameraParams.value.zoom_factor = 5
|
}
|
if (row.key === 'ir') {
|
cameraParams.value.zoom_factor = 2
|
}
|
cameraParamsChange()
|
}
|
|
// 画质切换
|
function qualityChange() {
|
EventBus.emit('CurrentTaskDetails-timeStop')
|
}
|
|
// 录像
|
function recordFun() {
|
const type = isRecording.value ? 'video_stop' : 'video_start'
|
const msg = isRecording.value ? '停止录像' : '开始录像'
|
const emitType = isRecording.value ? 'controlPanel-timeStop' : 'controlPanel-timeStart'
|
|
callPhotoAndVideoCmd(dockSn.value, type).then(res => {
|
ElMessage.success(msg)
|
EventBus.emit(emitType)
|
isRecording.value = !isRecording.value
|
})
|
}
|
|
// 拍照
|
function takePictures() {
|
if (isRecording.value) {
|
return ElMessage.warning('请先结束录像')
|
}
|
// photo拍照,video_start开始录像,video_stop结束录像
|
callPhotoAndVideoCmd(dockSn.value, 'photo').then(res => {
|
ElMessage.success('拍照成功')
|
})
|
}
|
|
// 记录喊话是否开始阶段
|
const isRecordShouting = ref(true);
|
const isFlag = ref(false);
|
const percent = ref(0); // 滚动条滚动百分比
|
// 初始化喊话
|
function initShout() {
|
destroyShout();
|
globalShout = new Recorder({
|
sampleBits: 16,
|
sampleRate: 16000,
|
numChannels: 1,
|
})
|
}
|
|
// 销毁喊话
|
function destroyShout() {
|
if (globalShout) {
|
globalShout.pause()
|
globalShout.destroy() // 销毁实例
|
globalShout = null
|
}
|
}
|
|
// 喊话
|
function shoutFun() {
|
// 是否需要判断有控制权限
|
if (isFlag.value) return
|
isFlag.value = true;
|
if (isRecordShouting.value) {
|
// 初始化喊话
|
initShout();
|
globalShout.start().then(() => {
|
ElMessage.success('开始喊话')
|
isRecordShouting.value = false
|
isFlag.value = false
|
}).catch((err) => {
|
isFlag.value = false
|
})
|
} else {
|
let formData = new FormData();
|
const wavBlob = globalShout.getWAVBlob()
|
const wavform = new File([wavBlob], new Date().getTime() + '.wav')
|
formData.append('file', wavform)
|
formData.append('sn', '8UUXN2A00A00MM')
|
formData.append('psdk_index', 2)
|
formData.append('name', '喊话' + dayjs().format('YYYY.MM.DD HH:mm:ss'))
|
formData.append('volumn', 100)
|
startVoice(formData).then(res => {
|
ElMessage.success('喊话成功');
|
isRecordShouting.value = true
|
isFlag.value = false
|
destroyShout();
|
}).catch(err => {
|
isRecordShouting.value = true
|
isFlag.value = false
|
ElMessage.error('喊话失败')
|
destroyShout();
|
});
|
}
|
}
|
const tableList = ref([]);
|
// 分页相关
|
const searchParams = ref({
|
sn: droneSn.value,
|
name: '',
|
page: 1,
|
page_size: 1
|
});
|
const isBroadcast = ref(false);
|
const total = ref(0);
|
// 广播
|
function broadcastFun() {
|
// 是否需要判断有控制权限
|
// 弹出框选择文件
|
isBroadcast.value = true;
|
// 获取文件接口
|
getFileList();
|
}
|
|
// 获取音频文件列表
|
function getFileList() {
|
getVoiceFile(searchParams.value).then((res) => {
|
if (res.data.code !== 0) return ElMessage.error(res.data.msg);
|
tableList.value = res.data.data.records;
|
total.value = res.data.data.total;
|
});
|
}
|
|
// 清除数据
|
function clearData() {
|
searchParams.value.name = '';
|
searchParams.value.page = 1;
|
getFileList();
|
}
|
|
function pageChange(val) {
|
searchParams.value.page = val;
|
getFileList();
|
}
|
// 上传
|
function handleUpload(file) {
|
let formData = new FormData();
|
formData.append('file', file)
|
formData.append('sn', droneSn.value)
|
uploadSpeak(formData).then(res => {
|
if (res.data.code !== 0) return ElMessage.error('上传失败');
|
ElMessage.success('上传成功');
|
// 获取文件接口
|
getFileList();
|
}).catch(err => {
|
ElMessage.error('上传失败')
|
})
|
}
|
|
// 播放操作
|
function playVoice(item) {
|
ElMessageBox.confirm('是否确认播放该音频?', '提示', {
|
confirmButtonText: '确定',
|
cancelButtonText: '取消',
|
type: 'warning'
|
}).then(() => {
|
const { sn, name, url, md5,voice } = item
|
playAudio({sn, name, url, md5,voice}).then((res) => {
|
if (res.code !== 0) return ElMessage.error("播放失败");
|
ElMessage.success("播放成功");
|
});
|
}).catch(() => {
|
ElMessage({
|
type: 'info',
|
message: '已取消'
|
});
|
});
|
}
|
onMounted(() => {
|
// 初始化喊话
|
initShout();
|
});
|
onUnmounted(() => {
|
// 销毁喊话
|
destroyShout();
|
});
|
</script>
|
|
<style scoped lang="scss">
|
.taskDetailsLeft {
|
z-index: 2;
|
position: absolute;
|
left: 18px;
|
top: 50%;
|
transform: translateY(-60%);
|
width: 178px;
|
padding: 20px 0;
|
background: rgba(0, 0, 0, 0.5);
|
backdrop-filter: blur(5px);
|
border-radius: 20px 20px 20px 20px;
|
display: flex;
|
flex-direction: column;
|
justify-content: center;
|
font-family: Segoe UI, Segoe UI;
|
font-weight: 400;
|
font-size: 14px;
|
color: #ffffff;
|
line-height: 18px;
|
text-align: center;
|
align-items: center;
|
gap: 12px;
|
|
.cameraZoom {
|
position: absolute;
|
padding: 20px 0 15px 0;
|
left: 1600px;
|
top: -59px;
|
width: 90px;
|
height: 490px;
|
background: rgba(0, 0, 0, 0.4);
|
backdrop-filter: blur(5rem);
|
border-radius: 20px 20px 20px 20px;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
|
.el-slider {
|
flex: 1;
|
}
|
.cameraZoomText{
|
font-family: Segoe UI, Segoe UI;
|
font-weight: 400;
|
font-size: 22px;
|
color: #ffffff;
|
margin-top: 20px;
|
}
|
}
|
|
.qualityChange {
|
width: 146px;
|
height: 40px;
|
background: rgba(74, 72, 72, 0.67);
|
border: none !important;
|
display: flex;
|
justify-content: center;
|
box-shadow: 0 0.4rem 7.2rem 0 rgba(0, 0, 0, 0.25);
|
border-radius: 0.8rem 0.8rem 0.8rem 0.8rem;
|
|
:deep() {
|
.el-select__wrapper {
|
width: 100px;
|
height: 100%;
|
background: transparent;
|
color: #3d3b3b;
|
border: none;
|
box-shadow: none;
|
}
|
|
.el-select__selected-item {
|
font-family: Segoe UI, Segoe UI;
|
color: #ffffff;
|
font-size: 14px;
|
}
|
}
|
}
|
|
.singleCol {
|
width: 146px;
|
height: 40px;
|
box-shadow: 0px 4px 72px 0px rgba(0, 0, 0, 0.25);
|
border-radius: 8px 8px 8px 8px;
|
|
line-height: 40px;
|
cursor: pointer;
|
background: rgba(74, 72, 72, 0.67);
|
|
&.active {
|
background: rgba(255, 255, 255, 0.78);
|
color: #3d3b3b;
|
border: 1px solid rgba(255, 255, 255, 0.99);
|
}
|
|
&:hover {
|
background: rgba(255, 255, 255, 0.78);
|
color: #3d3b3b;
|
border: 1px solid rgba(255, 255, 255, 0.99);
|
}
|
}
|
|
.multiCol {
|
display: flex;
|
gap: 12px;
|
|
> div {
|
cursor: pointer;
|
width: 67px;
|
height: 40px;
|
background: rgba(74, 72, 72, 0.67);
|
box-shadow: 0px 4px 72px 0px rgba(0, 0, 0, 0.25);
|
border-radius: 8px 8px 8px 8px;
|
line-height: 40px;
|
|
&:hover {
|
background: rgba(255, 255, 255, 0.78);
|
color: #3d3b3b;
|
border: 1px solid rgba(255, 255, 255, 0.99);
|
}
|
}
|
}
|
.percent {
|
width: 146px;
|
:deep(.el-progress-bar__outer) {
|
height: 6px;
|
background-color: rgba(255, 255, 255, 0.1);
|
}
|
:deep(.el-progress-bar__inner) {
|
height: 6px;
|
background: #FFFFFF;
|
border-radius: 10px 10px 10px 10px;
|
transition: all 0.3s;
|
}
|
:deep(.el-progress-bar__innerText) {
|
// color: red;
|
}
|
}
|
}
|
|
.broadcast {
|
z-index: 2;
|
position: absolute;
|
left: 204px;
|
top: 60%;
|
transform: translateY(-60%);
|
backdrop-filter: blur(5px);
|
width: 608px;
|
height: 256px;
|
// background: rgba(64,64,64,0.25);
|
background: rgba(0, 0, 0, 0.5);
|
border-radius: 20px 20px 20px 20px;
|
.title {
|
margin: 18px 8px;
|
span {
|
width: 66px;
|
height: 18px;
|
font-family: Segoe UI, Segoe UI;
|
font-weight: 400;
|
font-size: 14px;
|
color: #EDEDED;
|
line-height: 18px;
|
text-align: left;
|
font-style: normal;
|
text-transform: none;
|
margin-left: 16px;
|
}
|
img {
|
width: 584px;
|
height: 14px;
|
}
|
.close {
|
width: 16px;
|
height: 16px;
|
text-align: right;
|
position: absolute;
|
right: 16px;
|
top: 16px;
|
cursor: pointer;
|
}
|
}
|
.search-btn {
|
display: flex;
|
justify-content: space-between;
|
color: #EDEDED;
|
margin: 0 14px;
|
.input-set {
|
display: flex;
|
align-items: center;
|
width: 214px;
|
height: 32px;
|
label {width: 90px;}
|
:deep(.el-input__wrapper) {
|
background: rgba(58,58,58,0.75);
|
border-radius: 8px 8px 8px 8px;
|
}
|
:deep(.el-input__inner) {
|
color: #EDEDED;
|
}
|
}
|
.btn-set {
|
display: flex;
|
gap: 12px;
|
.btn {
|
width: 66px;
|
height: 32px;
|
line-height: 32px;
|
text-align: center;
|
background: rgba(58,58,58,0.75);
|
box-shadow: 0px 4px 72px 0px rgba(0,0,0,0.25);
|
border-radius: 8px 8px 8px 8px;
|
color: #EDEDED;
|
cursor: pointer;
|
}
|
}
|
}
|
.video-table {
|
margin: 14px 10px;
|
.table-header {
|
height: 44px;
|
line-height: 44px;
|
background: rgba(66,66,66,0.85);
|
border-radius: 8px 8px 0px 0px;
|
display: flex;
|
color: #BDBDBD;
|
}
|
.table-body {
|
height: 44px;
|
line-height: 44px;
|
background: rgba(74,72,72,0.6);
|
border-radius: 0px 0px 8px 8px;
|
display: flex;
|
color: #EDEDED;
|
}
|
.name {
|
width: 60%;
|
text-align: center
|
}
|
.operate {
|
width:40%;
|
text-align: center;
|
.btn {
|
display: inline-block;
|
cursor: pointer;
|
width: 66px;
|
height: 32px;
|
line-height: 32px;
|
text-align: center;
|
background: rgba(58,58,58,0.75);
|
box-shadow: 0px 4px 72px 0px rgba(0,0,0,0.25);
|
border-radius: 8px 8px 8px 8px;
|
color: #EDEDED;
|
}
|
}
|
}
|
.pagination {
|
position: absolute;
|
right: 10px;
|
bottom: 10px;
|
height: 32px;
|
display: flex;
|
|
:deep(.number) {
|
color: #EDEDED;
|
}
|
:deep(.btn-prev),
|
:deep(.btn-next) {
|
width: 26px;
|
height: 26px;
|
background: rgba(58,58,58,0.75);
|
box-shadow: 0px 4px 72px 0px rgba(0,0,0,0.25);
|
border-radius: 8px 8px 8px 8px;
|
cursor: pointer;
|
}
|
:deep(.el-pager li) {
|
cursor: pointer;
|
background: rgba(58,58,58,0.75);
|
box-shadow: 0px 4px 72px 0px rgba(0,0,0,0.25);
|
border-radius: 8px 8px 8px 8px;
|
border: 1px solid rgba(255,255,255,0.99);
|
margin: 0 4px;
|
}
|
}
|
}
|
</style>
|