| | |
| | | <div class="title">负载控制</div> |
| | | <div |
| | | class="singleCol" |
| | | v-for="item in list1" |
| | | v-for="item in list1.filter(i => i.show)" |
| | | @click="cameraType(item)" |
| | | :key="item.key" |
| | | :class="{ active: item.key === cameraParams.camera_type }" |
| | |
| | | <div @click="takePictures">拍照</div> |
| | | <div @click="recordFun">{{ isRecording ? '录像中...' : '录像' }}</div> |
| | | </div> |
| | | <div class="multiCol"> |
| | | <div @click="ElMessage.warning('加急开发中...')">喊话</div> |
| | | <div @click="ElMessage.warning('加急开发中...')">广播</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" |
| | |
| | | :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 } from '@/api/payload' |
| | | import { ElMessage } from 'element-plus' |
| | | 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 list1 = ref([ |
| | | { name: '广角', key: 'wide' }, |
| | | { name: '变焦', key: 'zoom' }, |
| | | { name: '红外', key: 'ir' }, |
| | | ]) |
| | | 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', |
| | |
| | | |
| | | // 修改相机参数 |
| | | function cameraParamsChange() { |
| | | const { camera_type, zoom_factor} = cameraParams.value |
| | | const { camera_type, zoom_factor } = cameraParams.value |
| | | cameraParamsChangeApi({ |
| | | sn: dockSn.value, |
| | | ...cameraParams.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"> |
| | |
| | | top: 50%; |
| | | transform: translateY(-60%); |
| | | width: 178px; |
| | | height: 409px; |
| | | padding: 20px 0; |
| | | background: rgba(0, 0, 0, 0.5); |
| | | backdrop-filter: blur(5px); |
| | | border-radius: 20px 20px 20px 20px; |
| | |
| | | |
| | | .cameraZoom { |
| | | position: absolute; |
| | | padding: 16px 0; |
| | | padding: 20px 0 15px 0; |
| | | left: 1600px; |
| | | top: -59px; |
| | | width: 112px; |
| | | width: 90px; |
| | | height: 490px; |
| | | background: rgba(64, 64, 64, 0.15); |
| | | 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 { |
| | | height: 100%; |
| | | flex: 1; |
| | | } |
| | | .cameraZoomText{ |
| | | font-family: Segoe UI, Segoe UI; |
| | | font-weight: 400; |
| | | font-size: 22px; |
| | | color: #ffffff; |
| | | margin-top: 20px; |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | } |
| | | .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> |