1248 lines
38 KiB
Vue
1248 lines
38 KiB
Vue
<template>
|
||
<!-- 机器人操控面板 -->
|
||
<div class="control-deck child-container">
|
||
<!-- 第一行 -->
|
||
<n-grid :cols="24">
|
||
<n-grid-item :span="9">
|
||
<div class="row-1-item">摄像头</div>
|
||
</n-grid-item>
|
||
<n-grid-item :span="6">
|
||
<!-- <div class="row-1-item">升降杆</div> -->
|
||
<div
|
||
class="row-1-item"
|
||
style="display: flex; align-items: center; justify-content: center"
|
||
>
|
||
<n-icon size="26">
|
||
<!-- 绿色 -->
|
||
<BatteryFullSharp
|
||
color="#16E607"
|
||
v-if="robotBaseInfo.dl >= 80 && robotBaseInfo.type != '充电中'"
|
||
/>
|
||
<BatteryHalfSharp
|
||
color="#E69807"
|
||
v-else-if="robotBaseInfo.dl >= 50 && robotBaseInfo.type != '充电中'"
|
||
/>
|
||
<BatteryDeadSharp
|
||
color="#F90303"
|
||
v-else-if="robotBaseInfo.type != '充电中'"
|
||
/>
|
||
<BatteryChargingSharp
|
||
color="#16E607"
|
||
v-if="robotBaseInfo.type == '充电中'"
|
||
/>
|
||
</n-icon>
|
||
<span style="margin-left: 4px; font-size: 13px"> {{ robotBaseInfo.dl }}%</span>
|
||
</div>
|
||
</n-grid-item>
|
||
<n-grid-item :span="9">
|
||
<div class="row-1-item">底盘</div>
|
||
</n-grid-item>
|
||
</n-grid>
|
||
<!-- 第二行 -->
|
||
<n-grid :cols="24">
|
||
<n-grid-item :span="9">
|
||
<div class="row-2-item">
|
||
<!-- <div>速度</div>
|
||
<div>30</div> -->
|
||
|
||
<!-- <n-button
|
||
type="primary"
|
||
size="small"
|
||
@click="handleStopAndOverRouter('15')"
|
||
v-if="robotBaseInfo.type.includes('巡航')"
|
||
>
|
||
暂停巡航
|
||
</n-button> -->
|
||
</div>
|
||
</n-grid-item>
|
||
<n-grid-item :span="6">
|
||
<div class="row-2-item">
|
||
<!-- <div>高度</div>
|
||
<div>
|
||
{{ currentHeight }}
|
||
</div> -->
|
||
</div>
|
||
|
||
<div class="row-2-item">
|
||
<div></div>
|
||
<div style="width: 70px">
|
||
{{ robotBaseInfo.type }}
|
||
</div>
|
||
</div>
|
||
</n-grid-item>
|
||
<n-grid-item :span="9">
|
||
<div class="row-2-item">
|
||
<!-- <div>速度</div>
|
||
<div>30</div> -->
|
||
<!-- <n-button
|
||
type="primary"
|
||
size="small"
|
||
@click="handleStopAndOverRouter('14')"
|
||
v-if="robotBaseInfo.value.type.includes('巡航')"
|
||
>
|
||
结束巡航
|
||
</n-button> -->
|
||
</div>
|
||
</n-grid-item>
|
||
</n-grid>
|
||
<!-- 第三行 -->
|
||
<n-grid :cols="10">
|
||
<n-grid-item :span="5">
|
||
<div class="row-3-item-1">
|
||
<!-- 上下左右控制按钮 -->
|
||
<img
|
||
class="arrow-top hand-direction"
|
||
@mousedown="handleChangeCamera('up')"
|
||
@mouseup="handleChangeCamera('stop')"
|
||
src="@/assets/home-imgs/control-2-arrow.png"
|
||
/>
|
||
<img
|
||
class="arrow-right hand-direction"
|
||
@mousedown="handleChangeCamera('right')"
|
||
@mouseup="handleChangeCamera('stop')"
|
||
src="@/assets/home-imgs/control-2-arrow.png"
|
||
/>
|
||
<img
|
||
class="arrow-bottom hand-direction"
|
||
@mousedown="handleChangeCamera('down')"
|
||
@mouseup="handleChangeCamera('stop')"
|
||
src="@/assets/home-imgs/control-2-arrow.png"
|
||
/>
|
||
<img
|
||
class="arrow-left hand-direction"
|
||
@mousedown="handleChangeCamera('left')"
|
||
@mouseup="handleChangeCamera('stop')"
|
||
src="@/assets/home-imgs/control-2-arrow.png"
|
||
/>
|
||
|
||
<!-- 中间的按钮 -->
|
||
<div class="row-3-item-1-center">
|
||
<img
|
||
class="center-icon"
|
||
@click="handleChangeCamera('stop')"
|
||
:src="isStopLeft ? stopImg : startImg"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 描述文字 -->
|
||
<div class="text-top description-text">正前</div>
|
||
<div class="text-bottom description-text">正后</div>
|
||
<div class="text-left description-text">正左</div>
|
||
<div class="text-right description-text">正右</div>
|
||
|
||
<!-- 增加减少按钮 -->
|
||
<div class="add-reduce-btn">
|
||
<span>缩放</span>
|
||
<img
|
||
style="margin-bottom: 2px"
|
||
@mousedown="handleChangeZoomCamera('ZoomIn')"
|
||
@mouseup="handleStopDeviceCameraZoom()"
|
||
src="@/assets/home-imgs/control-2-add.png"
|
||
/>
|
||
<img
|
||
@mousedown="handleChangeZoomCamera('ZoomOut')"
|
||
@mouseup="handleStopDeviceCameraZoom()"
|
||
src="@/assets/home-imgs/control-2-reduce.png"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</n-grid-item>
|
||
<n-grid-item :span="3" v-if="false">
|
||
<div class="row-3-item-2" ref="container">
|
||
<!-- 上下滑动调节 -->
|
||
<span
|
||
ref="draggable"
|
||
class="up-down-box"
|
||
@mousedown="startDrag"
|
||
:style="{ top: currentTop + 'px' }"
|
||
/>
|
||
</div>
|
||
</n-grid-item>
|
||
<n-grid-item :span="5">
|
||
<div class="row-3-item-3">
|
||
<!-- 上下左右控制按钮 -->
|
||
<img
|
||
class="arrow-top"
|
||
@mousedown="handleChangeRobot('5')"
|
||
@mouseup="handleChangeRobot(9)"
|
||
src="@/assets/home-imgs/control-2-arrow.png"
|
||
/>
|
||
<img
|
||
class="arrow-right"
|
||
@mousedown="handleChangeRobot('8')"
|
||
@mouseup="handleChangeRobot(9)"
|
||
src="@/assets/home-imgs/control-2-arrow.png"
|
||
/>
|
||
<img
|
||
class="arrow-bottom"
|
||
@mousedown="handleChangeRobot('6')"
|
||
@mouseup="handleChangeRobot(9)"
|
||
src="@/assets/home-imgs/control-2-arrow.png"
|
||
/>
|
||
<img
|
||
class="arrow-left"
|
||
@mousedown="handleChangeRobot('7')"
|
||
@mouseup="handleChangeRobot(9)"
|
||
src="@/assets/home-imgs/control-2-arrow.png"
|
||
/>
|
||
|
||
<!-- 描述文字 -->
|
||
<div class="text-top description-text">前进</div>
|
||
<div class="text-bottom description-text">后退</div>
|
||
<div class="text-left description-text">左转</div>
|
||
<div class="text-right description-text">右转</div>
|
||
|
||
<div class="row-3-item-1-center">
|
||
<img
|
||
class="center-icon"
|
||
@click="handleChangeRobot(9)"
|
||
:src="isStopRight ? startImg : stopImg"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</n-grid-item>
|
||
</n-grid>
|
||
<!-- 第四行 -->
|
||
<n-grid :cols="12" class="row-4">
|
||
<n-grid-item :span="3">
|
||
<div>警灯</div>
|
||
</n-grid-item>
|
||
<n-grid-item :span="3">
|
||
<div>
|
||
<n-radio
|
||
value="关闭"
|
||
name="basic-demo"
|
||
@change="handleChange1"
|
||
:checked="checkedValue === '关闭'"
|
||
>
|
||
关闭
|
||
</n-radio>
|
||
</div>
|
||
</n-grid-item>
|
||
<n-grid-item :span="3">
|
||
<div>
|
||
<n-radio
|
||
value="开启"
|
||
name="basic-demo"
|
||
@change="handleChange2"
|
||
:checked="checkedValue === '开启'"
|
||
>
|
||
开启
|
||
</n-radio>
|
||
</div>
|
||
</n-grid-item>
|
||
<n-grid-item :span="3">
|
||
<div class="row-4-item">
|
||
<n-radio
|
||
value="自动"
|
||
name="basic-demo"
|
||
@change="handleChange3"
|
||
:checked="checkedValue === '自动'"
|
||
>
|
||
自动
|
||
</n-radio>
|
||
</div>
|
||
</n-grid-item>
|
||
</n-grid>
|
||
<!-- 第五行 -->
|
||
<n-grid :cols="12" class="row-5">
|
||
<n-grid-item :span="3">
|
||
<div>录音播放</div>
|
||
</n-grid-item>
|
||
<n-grid-item :span="3">
|
||
<div>
|
||
<div>
|
||
<!-- <n-select
|
||
size="tiny"
|
||
v-model:value="selectValue"
|
||
:options="selectOptions"
|
||
/> -->
|
||
|
||
<!-- <n-select
|
||
size="small"
|
||
v-model:value="selectValue"
|
||
:options="selectOptions"
|
||
/> -->
|
||
|
||
<!-- <n-select
|
||
:options="selectOptions"
|
||
size="small"
|
||
:dropdown-style="{
|
||
borderRadius: '4px',
|
||
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
|
||
color: '#fff',
|
||
}"
|
||
/> -->
|
||
|
||
<!-- <n-button type="info" size="small">请上传</n-button> -->
|
||
|
||
<n-upload
|
||
:accept="audioMimeTypes"
|
||
:show-file-list="false"
|
||
:default-upload="false"
|
||
@before-upload="beforeUpload"
|
||
@change="handleChangeUpload"
|
||
v-if="!audioInfo"
|
||
>
|
||
<n-button type="info" size="small">请上传</n-button>
|
||
</n-upload>
|
||
|
||
<n-popselect
|
||
trigger="click"
|
||
v-model:value="audioValue"
|
||
:options="audioOptions"
|
||
@update:value="handleChangeAudio"
|
||
v-else
|
||
>
|
||
<n-button type="info" size="small">
|
||
{{ audioInfo?.fileName }}
|
||
</n-button>
|
||
</n-popselect>
|
||
</div>
|
||
<!-- <div style="margin-top: 16px">
|
||
<n-radio
|
||
:checked="checkedValue === '循环播放'"
|
||
value="循环播放"
|
||
name="basic-demo"
|
||
@change="handleChange4"
|
||
>
|
||
循环播放
|
||
</n-radio>
|
||
</div> -->
|
||
</div>
|
||
</n-grid-item>
|
||
<n-grid-item :span="6">
|
||
<div class="row-5-item">
|
||
<div class="row-5-item-1">
|
||
<img src="@/assets/home-imgs/control-3-voice.png" alt="" />
|
||
<div style="width: 60%">
|
||
<n-slider
|
||
:step="10"
|
||
:default-value="50"
|
||
:format-tooltip="formatTooltip"
|
||
@update:value="handleChangeVolume"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<!-- <div class="row-5-item-1" style="margin-top: 16px">
|
||
<img src="@/assets/home-imgs/control-3-video.png" alt="" />
|
||
<div style="width: 60%">
|
||
<n-slider
|
||
:default-value="50"
|
||
:step="10"
|
||
:format-tooltip="formatTooltip"
|
||
/>
|
||
</div>
|
||
</div> -->
|
||
</div>
|
||
</n-grid-item>
|
||
</n-grid>
|
||
|
||
<!-- 第六行 -->
|
||
<n-grid :cols="10" class="row-6">
|
||
<n-grid-item :span="4">
|
||
<div class="row-6-item" @click="handleChangeRobot(10)">回桩充电</div>
|
||
</n-grid-item>
|
||
<!-- <n-grid-item :span="3">
|
||
<div class="row-6-item-1">
|
||
|
||
<img
|
||
alt=""
|
||
v-if="!isOpenMK"
|
||
@click="onHandleOpenOrCloseMK(1)"
|
||
src="@/assets/home-imgs/control-3-mk.png"
|
||
/>
|
||
|
||
|
||
<span v-else style="cursor: pointer" @click="onHandleOpenOrCloseMK(2)"
|
||
>结束喊话</span
|
||
>
|
||
</div>
|
||
</n-grid-item> -->
|
||
</n-grid>
|
||
</div>
|
||
|
||
<n-modal v-model:show="selectPlayType">
|
||
<n-card
|
||
size="huge"
|
||
role="dialog"
|
||
aria-modal="true"
|
||
:bordered="false"
|
||
class="modal-container"
|
||
>
|
||
<n-form
|
||
ref="formRef"
|
||
size="small"
|
||
label-placement="left"
|
||
style="margin-top: 10px"
|
||
:model="playParams"
|
||
>
|
||
<n-form-item label="播放类型:">
|
||
<n-radio-group v-model:value="playParams.playType" name="radiogroup">
|
||
<n-space>
|
||
<n-radio :value="1"> 次数播放 </n-radio>
|
||
<n-radio :value="0"> 循环播放 </n-radio>
|
||
</n-space>
|
||
</n-radio-group>
|
||
</n-form-item>
|
||
|
||
<n-form-item label="播放次数:" v-if="playParams.playType === 1">
|
||
<n-input-number
|
||
v-model:value="playParams.playCount"
|
||
placeholder="输入播放次数"
|
||
:show-button="false"
|
||
/>
|
||
</n-form-item>
|
||
|
||
<n-button type="info" @click="handlePlayAudio"> 确定</n-button>
|
||
<n-button color="#6E90A9" style="margin-left: 8px" @click="selectPlayType = false">
|
||
取消
|
||
</n-button>
|
||
</n-form>
|
||
</n-card>
|
||
</n-modal>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted, h } from 'vue'
|
||
|
||
import {
|
||
stopDeviceCameraApi,
|
||
changeDeviceCameraApi,
|
||
stopDeviceCameraZoomApi,
|
||
changeDeviceCameraZoomInApi,
|
||
changeDeviceCameraZoomOutApi,
|
||
} from '@/utils/initLogin'
|
||
import { handleRobotActionApi, uploadAudioApi, getAudioInfoApi, deleteAudioApi } from '@/api/home'
|
||
import { useRobotDataStore } from '@/store/robot'
|
||
import { getRobotTokenFn, getRobotDeviceListFn } from '@/utils/getRobotInfo.js'
|
||
import {
|
||
BatteryHalfSharp,
|
||
BatteryDeadSharp,
|
||
BatteryFullSharp,
|
||
BatteryChargingSharp,
|
||
} from '@vicons/ionicons5'
|
||
import { openDeviceMKApi, stopDeviceMKApi } from '@/utils/initLogin'
|
||
import { useMessage, useDialog, NRadio, NRadioGroup, NInputNumber, NSpace } from 'naive-ui'
|
||
import { debounce } from 'lodash'
|
||
|
||
import stopImg from '@/assets/home-imgs/control-2-stop.png'
|
||
import startImg from '@/assets/home-imgs/control-2-start.png'
|
||
|
||
const dialog = useDialog()
|
||
const message = useMessage()
|
||
const robotData = useRobotDataStore()
|
||
const isStopLeft = ref(true)
|
||
const isStopRight = ref(false)
|
||
const upDownHeight = ref(30)
|
||
const container = ref(null)
|
||
const draggable = ref(null)
|
||
const isDragging = ref(false)
|
||
const currentTop = ref(0)
|
||
const startY = ref(0)
|
||
const startTop = ref(0)
|
||
const checkedValue = ref('关闭')
|
||
const selectValue = ref('')
|
||
const deviceToken = ref('')
|
||
const deviceInfo = ref({})
|
||
const isZoom = ref(false)
|
||
const robotBaseInfo = ref({})
|
||
const selectPlayType = ref(false)
|
||
|
||
const audioValue = ref('')
|
||
const audioInfo = ref(null)
|
||
|
||
const playParams = ref({
|
||
playType: 1,
|
||
playCount: 1,
|
||
})
|
||
|
||
const audioMimeTypes = ref(
|
||
'.pcm,.wav,.aac,audio/wav,audio/x-wav,audio/x-aiff,audio/aac,audio/x-aac',
|
||
)
|
||
|
||
// 音频操作选项
|
||
const audioOptions = ref([
|
||
{
|
||
label: '播放',
|
||
value: '播放',
|
||
},
|
||
{
|
||
label: '删除',
|
||
value: '删除',
|
||
},
|
||
{
|
||
label: '暂停',
|
||
value: '暂停',
|
||
},
|
||
])
|
||
|
||
const beforeUpload = ({ file }) => {
|
||
const allowedExtensions = ['.pcm', '.wav', '.aac']
|
||
const fileName = file.name.toLowerCase()
|
||
const isValid = allowedExtensions.some((ext) => fileName.endsWith(ext))
|
||
|
||
if (!isValid) {
|
||
message.error('只能上传 PCM、WAV、AAC 格式的音频文件')
|
||
return false
|
||
}
|
||
return true
|
||
}
|
||
|
||
// 暂停和停止巡航
|
||
const handleStopAndOverRouter = async (type) => {
|
||
return new Promise((resolve) => {
|
||
dialog.warning({
|
||
title: '提示',
|
||
content: `当前机器人正在巡检中,确定${type == 15 ? '暂停' : '结束'}巡检吗?`,
|
||
positiveText: '确定',
|
||
negativeText: '取消',
|
||
onPositiveClick: async () => {
|
||
try {
|
||
const { data: res } = await handleRobotActionApi({
|
||
puId: robotData.robotInfo?.puId,
|
||
type: type,
|
||
})
|
||
if (res.code == 200) {
|
||
message.success(`已${type == 15 ? '暂停' : '结束'}巡检`)
|
||
getRobotBaseInfo()
|
||
resolve(false) // 已暂停巡检,返回false表示不处于巡检状态
|
||
} else {
|
||
message.error(`${type == 15 ? '暂停' : '结束'}巡检失败` + res.message)
|
||
resolve(true) // 暂停失败,仍然处于巡检状态
|
||
}
|
||
} catch (error) {
|
||
message.error(`${type == 15 ? '暂停' : '结束'}巡检出错`)
|
||
resolve(true) // 出错时,假设仍然处于巡检状态
|
||
}
|
||
},
|
||
onNegativeClick: () => {
|
||
resolve(true) // 用户取消,仍然处于巡检状态
|
||
},
|
||
})
|
||
})
|
||
}
|
||
|
||
// 判断当前机器人是否在 巡检中 如果在巡检中 则不可进行任何操作 可弹框提示客户暂停巡检 然后重新获取机器人状态再进行操作
|
||
const handleInspection = async () => {
|
||
if (robotBaseInfo.value.type.includes('巡航')) {
|
||
return new Promise((resolve) => {
|
||
dialog.warning({
|
||
title: '提示',
|
||
content: '当前机器人正在巡检中,可停止巡检后再进行操作',
|
||
positiveText: '停止巡检',
|
||
negativeText: '取消',
|
||
onPositiveClick: async () => {
|
||
try {
|
||
const { data: res } = await handleRobotActionApi({
|
||
puId: robotData.robotInfo?.puId,
|
||
type: '14',
|
||
})
|
||
if (res.code == 200) {
|
||
message.success('已停止巡检')
|
||
getRobotBaseInfo()
|
||
resolve(false) // 已暂停巡检,返回false表示不处于巡检状态
|
||
} else {
|
||
message.error('停止巡检失败' + res.message)
|
||
resolve(true) // 暂停失败,仍然处于巡检状态
|
||
}
|
||
} catch (error) {
|
||
message.error('停止巡检出错')
|
||
resolve(true) // 出错时,假设仍然处于巡检状态
|
||
}
|
||
},
|
||
onNegativeClick: () => {
|
||
resolve(true) // 用户取消,仍然处于巡检状态
|
||
},
|
||
})
|
||
})
|
||
}
|
||
return false // 不包含"巡检",返回false表示不处于巡检状态
|
||
}
|
||
|
||
// 上传音频
|
||
const handleChangeUpload = async (e) => {
|
||
const file = e.file
|
||
const formData = new FormData()
|
||
formData.append('file', file.file)
|
||
formData.append('puid', deviceInfo.value?.puId)
|
||
|
||
const { data: res } = await uploadAudioApi(formData)
|
||
|
||
if (res.code == 200) {
|
||
message.success('上传音频文件成功')
|
||
|
||
getAudioInfo()
|
||
} else {
|
||
message.error('上传音频文件失败')
|
||
}
|
||
}
|
||
|
||
// 获取音频信息
|
||
const getAudioInfo = async () => {
|
||
const { data: res } = await getAudioInfoApi({
|
||
puid: deviceInfo.value?.puId,
|
||
})
|
||
|
||
if (res.code == 200) {
|
||
audioInfo.value = res?.data
|
||
}
|
||
}
|
||
|
||
const handleChangeAudio = async (e) => {
|
||
// console.log(e, '选择音频操作')
|
||
if (e === '播放') {
|
||
// showPlayTypeDialog()
|
||
|
||
selectPlayType.value = true
|
||
} else if (e === '删除') {
|
||
const { data: res } = await deleteAudioApi({
|
||
puid: robotData.robotInfo?.puId,
|
||
id: audioInfo.value?.id,
|
||
})
|
||
|
||
if (res.code == 200) {
|
||
message.success('删除音频成功')
|
||
getAudioInfo()
|
||
} else {
|
||
message.error('删除音频失败')
|
||
}
|
||
} else {
|
||
const { data: res } = await handleRobotActionApi({
|
||
puId: robotData.robotInfo?.puId,
|
||
type: '17',
|
||
enable: '0',
|
||
videoId: audioInfo.value?.id,
|
||
})
|
||
|
||
if (res.code == 200) {
|
||
message.success('已暂停')
|
||
} else {
|
||
message.error('暂停音频失败')
|
||
}
|
||
}
|
||
}
|
||
|
||
// 计算当前高度百分比(0-100)
|
||
const currentHeight = computed(() => {
|
||
if (!container.value) return 0
|
||
const containerHeight = container.value.offsetHeight
|
||
const draggableHeight = draggable.value?.offsetHeight || 0
|
||
const maxTop = containerHeight - draggableHeight - 20 // 20是底部初始偏移
|
||
|
||
// 计算百分比 (0-100)
|
||
return Math.round(100 - (currentTop.value / maxTop) * 100)
|
||
})
|
||
|
||
const startDrag = (e) => {
|
||
isDragging.value = true
|
||
startY.value = e.clientY
|
||
startTop.value = currentTop.value
|
||
|
||
document.addEventListener('mousemove', onDrag)
|
||
document.addEventListener('mouseup', stopDrag)
|
||
}
|
||
|
||
const onDrag = (e) => {
|
||
if (!isDragging.value) return
|
||
|
||
const deltaY = e.clientY - startY.value
|
||
let newTop = startTop.value + deltaY
|
||
|
||
const containerHeight = container.value.offsetHeight
|
||
const draggableHeight = draggable.value.offsetHeight
|
||
const maxTop = containerHeight - draggableHeight - 20
|
||
|
||
newTop = Math.max(0, Math.min(newTop, maxTop))
|
||
currentTop.value = newTop
|
||
}
|
||
|
||
const stopDrag = () => {
|
||
isDragging.value = false
|
||
document.removeEventListener('mousemove', onDrag)
|
||
document.removeEventListener('mouseup', stopDrag)
|
||
|
||
// 拖动结束时可以在这里使用currentHeight.value
|
||
console.log('当前高度:', currentHeight.value)
|
||
}
|
||
|
||
onMounted(() => {
|
||
// const containerHeight = container.value.offsetHeight
|
||
// const draggableHeight = draggable.value.offsetHeight
|
||
// const maxTop = containerHeight - draggableHeight - 20
|
||
// currentTop.value = maxTop * (1 - upDownHeight.value / 100)
|
||
})
|
||
|
||
const handleChange1 = (e) => {
|
||
checkedValue.value = e.target.value
|
||
handleChangeLight(checkedValue.value)
|
||
}
|
||
const handleChange2 = (e) => {
|
||
checkedValue.value = e.target.value
|
||
handleChangeLight(checkedValue.value)
|
||
}
|
||
const handleChange3 = (e) => {
|
||
checkedValue.value = e.target.value
|
||
handleChangeLight(checkedValue.value)
|
||
}
|
||
|
||
// 警灯闪烁
|
||
|
||
const handleChangeLight = async (value) => {
|
||
const lightMap = {
|
||
关闭: 'OFF',
|
||
开启: 'ON',
|
||
自动: 'AUTO',
|
||
}
|
||
|
||
const { data: res } = await handleRobotActionApi({
|
||
puId: robotData.robotInfo?.puId,
|
||
type: '19',
|
||
value: lightMap[value],
|
||
})
|
||
|
||
if (res.code == 200) {
|
||
message.success('操作成功')
|
||
} else {
|
||
message.error('操作失败')
|
||
checkedValue.value = '关闭'
|
||
}
|
||
}
|
||
|
||
const handleChange4 = (e) => {
|
||
checkedValue.value = e.target.value
|
||
}
|
||
|
||
const formatTooltip = (value) => {
|
||
return `${value}%`
|
||
}
|
||
|
||
// 停止缩放
|
||
const handleStopDeviceCameraZoom = async () => {
|
||
isZoom.value = false
|
||
const res = await stopDeviceCameraZoomApi({
|
||
idx: 0,
|
||
token: deviceToken.value,
|
||
puid: deviceInfo.value?.puId,
|
||
})
|
||
|
||
isStopLeft.value = true
|
||
console.log(res, '停止缩放')
|
||
}
|
||
|
||
// 操控机器人上方的摄像头
|
||
const handleChangeCamera = debounce(async (motion) => {
|
||
const isInspection = await handleInspection()
|
||
if (isInspection) {
|
||
return
|
||
}
|
||
if (motion === 'stop' && isZoom.value) {
|
||
isStopLeft.value = true
|
||
handleStopDeviceCameraZoom()
|
||
return
|
||
}
|
||
if (motion === 'stop') {
|
||
isStopLeft.value = true
|
||
} else {
|
||
isStopLeft.value = false
|
||
}
|
||
if (!deviceInfo.value?.puId) {
|
||
message.error('当前机器人未连接', {
|
||
duration: 1000,
|
||
})
|
||
return
|
||
}
|
||
const res = await changeDeviceCameraApi({
|
||
token: deviceToken.value,
|
||
puid: deviceInfo.value?.puId,
|
||
idx: 0,
|
||
motion,
|
||
})
|
||
}, 0)
|
||
|
||
// 球机缩放
|
||
const handleChangeZoomCamera = async (motion) => {
|
||
const isInspection = await handleInspection()
|
||
if (isInspection) {
|
||
return
|
||
}
|
||
isStopLeft.value = false
|
||
isZoom.value = true
|
||
if (motion === 'ZoomIn') {
|
||
const res = await changeDeviceCameraZoomInApi({
|
||
token: deviceToken.value,
|
||
puid: deviceInfo.value?.puId,
|
||
idx: 0,
|
||
})
|
||
} else {
|
||
const res = await changeDeviceCameraZoomOutApi({
|
||
token: deviceToken.value,
|
||
puid: deviceInfo.value?.puId,
|
||
idx: 0,
|
||
})
|
||
}
|
||
}
|
||
|
||
// 操控机器人
|
||
const handleChangeRobot = debounce(async (type) => {
|
||
const isInspection = await handleInspection()
|
||
if (isInspection) {
|
||
return
|
||
}
|
||
// type 5 前进 6 后退 7 左转 8 右转
|
||
const typeMap = {
|
||
5: '前进', // 前进
|
||
6: '后退', // 后退
|
||
7: '左转', // 左转
|
||
8: '右转', // 右转
|
||
9: '停止', // 停止
|
||
}
|
||
if (!robotData.robotInfo?.puId) {
|
||
message.error('当前机器人未连接', {
|
||
duration: 1000,
|
||
})
|
||
return
|
||
}
|
||
|
||
const params = {
|
||
puId: robotData.robotInfo?.puId,
|
||
type,
|
||
}
|
||
|
||
console.log(params, '机器人操控参数' + typeMap[type])
|
||
|
||
const { data: res } = await handleRobotActionApi(params)
|
||
console.log(res, '机器人操控结果')
|
||
if (res.code == 200 && type != 9) {
|
||
isStopRight.value = true
|
||
}
|
||
if (res.code == 200 && type == 9) {
|
||
isStopRight.value = false
|
||
}
|
||
}, 0)
|
||
|
||
// 停止机器人
|
||
const handleStopRobot = async () => {
|
||
console.log('停止机器人')
|
||
}
|
||
|
||
// 获取机器人的基础信息
|
||
const getRobotBaseInfo = async () => {
|
||
const { data: res } = await handleRobotActionApi({
|
||
puId: deviceInfo.value?.puId,
|
||
type: '1',
|
||
})
|
||
|
||
robotBaseInfo.value = res.data
|
||
}
|
||
|
||
// 确认播放
|
||
const handlePlayAudio = async () => {
|
||
const loop = playParams.value.playType === 0 ? 0 : playParams.value.playCount
|
||
const { data: res } = await handleRobotActionApi({
|
||
puId: robotData.robotInfo?.puId,
|
||
type: '17',
|
||
loop,
|
||
enable: '1',
|
||
videoId: audioInfo.value?.id,
|
||
})
|
||
|
||
if (res.code == 200) {
|
||
message.success('播放音频成功')
|
||
selectPlayType.value = false
|
||
} else {
|
||
message.error('播放音频失败')
|
||
}
|
||
}
|
||
|
||
// 音量选择
|
||
|
||
const handleChangeVolume = debounce(async (value) => {
|
||
const { data: res } = await handleRobotActionApi({
|
||
puId: robotData.robotInfo?.puId,
|
||
type: '18',
|
||
sound: value,
|
||
videoId: audioInfo.value?.id,
|
||
})
|
||
if (res.code == 200) {
|
||
message.success('音量调整成功')
|
||
} else {
|
||
message.error('音量调整失败')
|
||
}
|
||
}, 0)
|
||
|
||
const isOpenMK = ref(false)
|
||
const onHandleOpenOrCloseMK = async (type) => {
|
||
if (type === 1) {
|
||
// 先判断当前设备有没有开启MK
|
||
try {
|
||
// 请求麦克风权限并检查状态
|
||
// const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
|
||
|
||
// // 检查麦克风是否真的在接收声音(可选)
|
||
// const audioTracks = stream.getAudioTracks()
|
||
// if (audioTracks.length > 0) {
|
||
// audioTracks.forEach((track) => track.stop())
|
||
|
||
// // 调用麦克风开启接口
|
||
// const { data: res } = await openDeviceMKApi({
|
||
// token: deviceToken.value,
|
||
// puId: robotData.robotInfo?.puId,
|
||
// idx: 0,
|
||
// })
|
||
|
||
// if (res.code == 200) {
|
||
// message.success('麦克风开启成功')
|
||
// isOpenMK.value = true
|
||
// } else {
|
||
// message.error('麦克风开启失败')
|
||
// }
|
||
// } else {
|
||
// message.error('未检测到音频输入设备')
|
||
// }
|
||
|
||
// 调用麦克风开启接口
|
||
const { data: res } = await openDeviceMKApi({
|
||
token: deviceToken.value,
|
||
puid: robotData.robotInfo?.puId,
|
||
idx: 0,
|
||
})
|
||
|
||
if (res.code == 200) {
|
||
message.success('麦克风开启成功')
|
||
isOpenMK.value = true
|
||
} else {
|
||
message.error('麦克风开启失败')
|
||
}
|
||
} catch (error) {
|
||
message.error('麦克风访问被拒绝或不可用,请检查麦克风权限')
|
||
}
|
||
} else {
|
||
// 调用麦克风开启接口
|
||
const { data: res } = await stopDeviceMKApi({
|
||
token: deviceToken.value,
|
||
puid: deviceInfo.value?.puId,
|
||
idx: 0,
|
||
})
|
||
|
||
if (res.code == 200) {
|
||
message.success('麦克风已关闭')
|
||
isOpenMK.value = false
|
||
} else {
|
||
message.error('麦克风关闭失败')
|
||
}
|
||
}
|
||
}
|
||
|
||
onMounted(async () => {
|
||
const token = await getRobotTokenFn() // 获取设备token
|
||
const device = await getRobotDeviceListFn() // 获取设备信息
|
||
deviceToken.value = token
|
||
deviceInfo.value = device
|
||
getRobotBaseInfo()
|
||
getAudioInfo()
|
||
})
|
||
</script>
|
||
<style lang="scss" scoped>
|
||
.control-deck {
|
||
justify-content: space-between;
|
||
}
|
||
.row-1-item,
|
||
.row-2-item {
|
||
width: 90px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.row-1-item {
|
||
height: 24px;
|
||
background: url('@/assets/home-imgs/control-1-bg.png') no-repeat center center;
|
||
background-size: 100% 100%;
|
||
line-height: 24px;
|
||
text-align: center;
|
||
}
|
||
|
||
.row-2-item {
|
||
// margin-top: 6px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-around;
|
||
|
||
& div:last-child {
|
||
// width: 32px;
|
||
height: 16px;
|
||
background-color: #005899;
|
||
border-radius: 6px;
|
||
font-size: 12px;
|
||
text-align: center;
|
||
line-height: 16px;
|
||
padding: 2px 10px;
|
||
}
|
||
}
|
||
|
||
.row-3-item-1,
|
||
.row-3-item-3 {
|
||
width: 68px;
|
||
height: 68px;
|
||
margin: 20px auto 0;
|
||
position: relative;
|
||
background: url('@/assets/home-imgs/control-2-round.png') no-repeat center center;
|
||
background-size: 100% 100%;
|
||
|
||
> img {
|
||
width: 16px;
|
||
height: 18px;
|
||
cursor: pointer;
|
||
object-fit: contain;
|
||
}
|
||
|
||
// // 增加一个移入时的放大平滑动画效果
|
||
// .hand-direction:hover {
|
||
// transform: scale(1.1);
|
||
// transition: transform 0.3s ease-in-out;
|
||
// }
|
||
|
||
.arrow-top {
|
||
position: absolute;
|
||
top: 1px;
|
||
left: 50%;
|
||
transform: translateX(-50%) rotate(-90deg);
|
||
}
|
||
.arrow-right {
|
||
position: absolute;
|
||
top: 50%;
|
||
right: 1px;
|
||
transform: translateY(-50%);
|
||
}
|
||
.arrow-bottom {
|
||
position: absolute;
|
||
bottom: 1px;
|
||
left: 50%;
|
||
transform: translateX(-50%) rotate(90deg);
|
||
}
|
||
.arrow-left {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 1px;
|
||
transform: translateY(-50%) rotate(-180deg);
|
||
}
|
||
|
||
.row-3-item-1-center {
|
||
width: 16px;
|
||
height: 16px;
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transform: translate(-50%, -50%) rotate(45deg);
|
||
border: 2px solid #7fc0ff;
|
||
border-radius: 4px;
|
||
background-color: #2f78bf;
|
||
cursor: pointer;
|
||
|
||
> img {
|
||
width: 9px;
|
||
height: 9px;
|
||
object-fit: contain;
|
||
transform: rotate(-45deg);
|
||
}
|
||
}
|
||
|
||
// 描述文字
|
||
.description-text {
|
||
position: absolute;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.text-top {
|
||
left: 50%;
|
||
top: -28px;
|
||
transform: translateX(-50%);
|
||
}
|
||
.text-bottom {
|
||
left: 50%;
|
||
bottom: -28px;
|
||
transform: translateX(-50%);
|
||
}
|
||
.text-top,
|
||
.text-bottom {
|
||
width: 36px;
|
||
height: 22px;
|
||
border: 1px solid #7fc0ff;
|
||
border-radius: 4px;
|
||
text-align: center;
|
||
line-height: 22px;
|
||
}
|
||
|
||
.text-left {
|
||
left: -28px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
}
|
||
.text-right {
|
||
right: -28px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
}
|
||
.text-left,
|
||
.text-right {
|
||
height: 36px;
|
||
width: 22px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border: 1px solid #7fc0ff;
|
||
border-radius: 4px;
|
||
// 使文字竖向展示 并垂直居中
|
||
writing-mode: vertical-rl;
|
||
text-orientation: upright;
|
||
}
|
||
|
||
.add-reduce-btn {
|
||
position: absolute;
|
||
right: -58px;
|
||
top: -16px;
|
||
// height: 100px;
|
||
width: 20px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
|
||
span {
|
||
font-size: 13px;
|
||
width: 30px;
|
||
text-align: center;
|
||
color: #7fc0ff;
|
||
}
|
||
|
||
img {
|
||
width: 20px;
|
||
height: 50px;
|
||
object-fit: contain;
|
||
cursor: pointer;
|
||
}
|
||
}
|
||
}
|
||
|
||
.row-3-item-3 {
|
||
width: 68px;
|
||
height: 68px;
|
||
margin: 20px auto 0 !important;
|
||
|
||
> img {
|
||
width: 16px;
|
||
height: 18px;
|
||
cursor: pointer;
|
||
object-fit: contain;
|
||
}
|
||
}
|
||
|
||
.row-3-item-2 {
|
||
width: 60px;
|
||
height: 120px;
|
||
margin: 32px auto 0 !important;
|
||
background: url('@/assets/home-imgs/control-2-up-down.png') no-repeat center center;
|
||
background-size: 100% 100%;
|
||
position: relative;
|
||
user-select: none;
|
||
|
||
.up-down-box {
|
||
position: absolute;
|
||
left: 50%;
|
||
width: 30px;
|
||
height: 10px;
|
||
background: url('@/assets/home-imgs/control-2-up-down-hand.png') no-repeat center center;
|
||
background-size: 100% 100%;
|
||
transform: translateX(-50%);
|
||
cursor: pointer;
|
||
touch-action: none;
|
||
}
|
||
}
|
||
.row-4 {
|
||
margin-top: 10px;
|
||
.row-4-item {
|
||
text-align: center;
|
||
}
|
||
}
|
||
|
||
.row-5-item-1 {
|
||
width: 100%;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
|
||
> img {
|
||
width: 16px;
|
||
height: 16px;
|
||
margin-right: 10px;
|
||
object-fit: contain;
|
||
}
|
||
}
|
||
|
||
.row-6 {
|
||
// margin-top: 20px;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.row-6-item {
|
||
padding: 2px 8px;
|
||
display: inline-block;
|
||
background-color: #005db6;
|
||
border: 1px solid #7fc0ff;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.row-6-item-1 {
|
||
text-align: center;
|
||
img {
|
||
width: 28px;
|
||
height: 40px;
|
||
object-fit: contain;
|
||
cursor: pointer;
|
||
}
|
||
}
|
||
|
||
:deep(.n-radio__label) {
|
||
color: #fff !important;
|
||
}
|
||
|
||
:deep(.n-radio__dot) {
|
||
background: transparent;
|
||
border: 1px solid #7fc0ff;
|
||
box-shadow: none;
|
||
}
|
||
|
||
:deep(.n-radio__dot::before) {
|
||
background: #7fc0ff;
|
||
}
|
||
|
||
// :deep(.n-base-selection .n-base-selection-label) {
|
||
// background-color: #054a92;
|
||
// color: #fff;
|
||
// }
|
||
|
||
:deep(.n-slider .n-slider-rail .n-slider-rail__fill) {
|
||
background-color: #7fc0ff;
|
||
}
|
||
:deep(.n-slider .n-slider-handles .n-slider-handle-wrapper .n-slider-handle) {
|
||
background-color: #7fc0ff;
|
||
}
|
||
|
||
:deep(.n-slider .n-slider-rail) {
|
||
background-color: #2d6098;
|
||
}
|
||
|
||
.modal-container {
|
||
height: 40vh;
|
||
width: 30%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: url('@/assets/home-imgs/modal-bg.png') no-repeat center center;
|
||
background-size: 100% 100%;
|
||
}
|
||
</style>
|