From 2800fa4f32f3900509cb4d6eefaf2bfaf54efdd7 Mon Sep 17 00:00:00 2001
From: 罗广辉 <guanghui.luo@foxmail.com>
Date: Mon, 21 Apr 2025 18:29:09 +0800
Subject: [PATCH] fix: 天气显示

---
 src/components/CurrentTaskDetails/TaskDetailsLeft.vue |  418 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 399 insertions(+), 19 deletions(-)

diff --git a/src/components/CurrentTaskDetails/TaskDetailsLeft.vue b/src/components/CurrentTaskDetails/TaskDetailsLeft.vue
index 89b9174..336d9f1 100644
--- a/src/components/CurrentTaskDetails/TaskDetailsLeft.vue
+++ b/src/components/CurrentTaskDetails/TaskDetailsLeft.vue
@@ -3,7 +3,7 @@
 		<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 }"
@@ -21,11 +21,13 @@
 			<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"
@@ -34,23 +36,84 @@
 				: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',
@@ -83,7 +146,7 @@
 
 // 修改相机参数
 function cameraParamsChange() {
-	const { camera_type, zoom_factor} = cameraParams.value
+	const { camera_type, zoom_factor } = cameraParams.value
 	cameraParamsChangeApi({
 		sn: dockSn.value,
 		...cameraParams.value,
@@ -125,11 +188,156 @@
 
 // 拍照
 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">
@@ -140,7 +348,7 @@
 	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;
@@ -158,16 +366,27 @@
 
 	.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;
 		}
 	}
 
@@ -242,5 +461,166 @@
 			}
 		}
 	}
+	.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>

--
Gitblit v1.9.3