2025-02-17 18:44:53 +08:00
|
|
|
|
<template>
|
|
|
|
|
<div class="control-panel">
|
|
|
|
|
<!-- 顶部状态栏 -->
|
|
|
|
|
<div class="status-bar">
|
|
|
|
|
<div class="placeholder-div"></div>
|
|
|
|
|
<div class="title-container">
|
|
|
|
|
<div class="title-box">
|
|
|
|
|
遥控车当前状态
|
2025-02-19 07:46:51 +08:00
|
|
|
|
<img src="../assets/img/shout.png" alt="voice" class="voice-icon-img"
|
|
|
|
|
:class="{ 'show-voice': isControlPressed }" style="width: 20px; height: 20px; margin-left: 20px;">
|
2025-02-17 18:44:53 +08:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="top-right-icons">
|
2025-02-19 07:46:51 +08:00
|
|
|
|
<img :src="connectionImage" alt="link" class="top-icon">
|
2025-03-05 11:40:40 +08:00
|
|
|
|
<!-- 注释掉电池图标 -->
|
|
|
|
|
<!-- <img :src="batteryImage" alt="battery" class="top-icon"> -->
|
2025-02-17 18:44:53 +08:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 主要内容区域 -->
|
|
|
|
|
<div class="main-content">
|
|
|
|
|
<!-- 左侧控制开关 -->
|
|
|
|
|
<div class="left-controls">
|
|
|
|
|
<div class="switch-item">
|
|
|
|
|
<span>屏幕</span>
|
|
|
|
|
<el-switch v-model="screenStatus" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="switch-item">
|
|
|
|
|
<span>警灯</span>
|
|
|
|
|
<el-switch v-model="warningLightStatus" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="switch-item">
|
|
|
|
|
<span>跟随</span>
|
|
|
|
|
<el-switch v-model="followStatus" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="switch-item">
|
|
|
|
|
<span>避障</span>
|
2025-02-22 21:04:37 +08:00
|
|
|
|
<el-switch v-model="obstacleAvoidEnabled" />
|
2025-02-17 18:44:53 +08:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 中间车辆状态显示区域 -->
|
|
|
|
|
<div class="center-display">
|
|
|
|
|
<div class="status-text">云台状态</div>
|
|
|
|
|
<div class="car-display">
|
2025-02-19 07:46:51 +08:00
|
|
|
|
<div class="boom-container">
|
|
|
|
|
<img src="../assets/img/boom.png" alt="boom" class="boom-image top-boom" v-show="obstacleStatus.top">
|
|
|
|
|
<img src="../assets/img/car.png" alt="car" class="car-image" />
|
|
|
|
|
<img src="../assets/img/boom.png" alt="boom" class="boom-image bottom-boom" v-show="obstacleStatus.bottom">
|
|
|
|
|
</div>
|
2025-02-17 18:44:53 +08:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 右侧状态图标 -->
|
|
|
|
|
<div class="right-status">
|
|
|
|
|
<div class="status-icons">
|
|
|
|
|
<div class="icons-row">
|
|
|
|
|
<div class="icon-item">
|
2025-02-19 07:46:51 +08:00
|
|
|
|
<img src="../assets/img/refresh.png" alt="refresh" @mousedown="handleRefresh"
|
|
|
|
|
style="width: 40px; height: 40px;margin-right: 20px;">
|
2025-02-17 18:44:53 +08:00
|
|
|
|
</div>
|
|
|
|
|
<div class="icon-item">
|
2025-02-19 07:46:51 +08:00
|
|
|
|
<img src="../assets/img/mp3.png" alt="play" @click="goto('voiceset')"
|
|
|
|
|
style="width: 40px; height: 40px;margin-left: 20px;">
|
2025-02-17 18:44:53 +08:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="control-button">
|
2025-02-19 07:46:51 +08:00
|
|
|
|
<div class="circle-border" :class="{ 'pressed': isControlPressed }" @mousedown="handleControlPress"
|
|
|
|
|
@mouseup="handleControlRelease" @mouseleave="handleControlRelease" @touchstart="handleControlPress"
|
|
|
|
|
@touchend="handleControlRelease" @touchcancel="handleControlRelease">
|
|
|
|
|
<img src="../assets/img/stop.png" alt="control" class="control-button-img"
|
|
|
|
|
style="width: 80px; height: 80px;">
|
|
|
|
|
</div>
|
2025-02-17 18:44:53 +08:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script>
|
2025-02-19 07:46:51 +08:00
|
|
|
|
import { useRouter } from 'vue-router';
|
|
|
|
|
import GamepadController from '../assets/js/GamepadController';
|
2025-02-22 21:04:37 +08:00
|
|
|
|
import audioTransmitter from '../assets/js/audio-transmitter';
|
2025-02-19 07:46:51 +08:00
|
|
|
|
|
2025-02-17 18:44:53 +08:00
|
|
|
|
export default {
|
|
|
|
|
name: 'CarControl',
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
screenStatus: false,
|
|
|
|
|
warningLightStatus: false,
|
|
|
|
|
followStatus: false,
|
2025-02-19 07:46:51 +08:00
|
|
|
|
obstacleStatus: {
|
|
|
|
|
top: false,
|
|
|
|
|
bottom: false
|
|
|
|
|
},
|
2025-02-22 21:04:37 +08:00
|
|
|
|
obstacleAvoidEnabled: false,
|
2025-02-19 07:46:51 +08:00
|
|
|
|
gamepadController: null,
|
|
|
|
|
directionAxis0_1: "",
|
|
|
|
|
directionAxis9: "",
|
|
|
|
|
isGamepadConnected: false,
|
|
|
|
|
isControlPressed: false,
|
|
|
|
|
ws: null,
|
|
|
|
|
wsConnected: false,
|
2025-03-05 11:40:40 +08:00
|
|
|
|
// batteryLevel: 0, // 注释掉电池电量
|
2025-02-19 07:46:51 +08:00
|
|
|
|
lastResponseTime: 0,
|
|
|
|
|
connectionCheckInterval: null,
|
|
|
|
|
sendInterval: null,
|
|
|
|
|
lastDirection: 0,
|
2025-02-19 21:33:57 +08:00
|
|
|
|
lastSpeed: 0,
|
2025-02-22 21:04:37 +08:00
|
|
|
|
platform_fun: 0,
|
|
|
|
|
button: "",
|
|
|
|
|
audioConfig: audioTransmitter.data(),
|
|
|
|
|
isRecording: false,
|
2025-03-05 11:40:40 +08:00
|
|
|
|
audioHandler: null,
|
|
|
|
|
reconnectTimer: null, // 添加重连定时器引用
|
|
|
|
|
isComponentUnmounted: false // 添加组件卸载标志
|
2025-02-19 07:46:51 +08:00
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
created() {
|
|
|
|
|
// 初始化 GamepadController,启用调试模式
|
|
|
|
|
this.gamepadController = new GamepadController({
|
|
|
|
|
debug: true,
|
|
|
|
|
deadZone: 0.1,
|
|
|
|
|
updateInterval: 50
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 添加全局事件监听
|
|
|
|
|
window.addEventListener("gamepadconnected", this.handleGamepadConnected);
|
|
|
|
|
window.addEventListener("gamepaddisconnected", this.handleGamepadDisconnected);
|
|
|
|
|
|
2025-03-05 11:40:40 +08:00
|
|
|
|
this.isComponentUnmounted = false; // 添加组件卸载标志
|
2025-02-19 07:46:51 +08:00
|
|
|
|
this.initWebSocket();
|
|
|
|
|
|
|
|
|
|
// 启动连接状态检查
|
|
|
|
|
this.connectionCheckInterval = setInterval(() => {
|
|
|
|
|
this.checkConnectionStatus();
|
|
|
|
|
}, 3000); // 每3秒检查一次
|
|
|
|
|
|
|
|
|
|
// 启动定时发送数据
|
|
|
|
|
this.sendInterval = setInterval(() => {
|
|
|
|
|
this.sendControlData();
|
|
|
|
|
}, 1000); // 每100ms发送一次
|
2025-02-22 21:04:37 +08:00
|
|
|
|
|
|
|
|
|
// 初始化音频处理器
|
|
|
|
|
this.audioHandler = {
|
|
|
|
|
...audioTransmitter.data(),
|
|
|
|
|
...audioTransmitter.methods
|
|
|
|
|
};
|
2025-02-19 07:46:51 +08:00
|
|
|
|
},
|
|
|
|
|
beforeDestroy() {
|
2025-03-05 11:40:40 +08:00
|
|
|
|
this.isComponentUnmounted = true; // 标记组件已卸载
|
|
|
|
|
|
|
|
|
|
// 清除重连定时器
|
|
|
|
|
if (this.reconnectTimer) {
|
|
|
|
|
clearTimeout(this.reconnectTimer);
|
|
|
|
|
this.reconnectTimer = null;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-19 07:46:51 +08:00
|
|
|
|
// 组件销毁前清理资源
|
|
|
|
|
if (this.gamepadController) {
|
|
|
|
|
this.gamepadController.destroy();
|
|
|
|
|
}
|
|
|
|
|
// 移除全局事件监听
|
|
|
|
|
window.removeEventListener("gamepadconnected", this.handleGamepadConnected);
|
|
|
|
|
window.removeEventListener("gamepaddisconnected", this.handleGamepadDisconnected);
|
|
|
|
|
|
|
|
|
|
if (this.ws) {
|
|
|
|
|
this.ws.close();
|
2025-03-05 11:40:40 +08:00
|
|
|
|
this.ws = null;
|
2025-02-19 07:46:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.connectionCheckInterval) {
|
|
|
|
|
clearInterval(this.connectionCheckInterval);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.sendInterval) {
|
|
|
|
|
clearInterval(this.sendInterval);
|
|
|
|
|
}
|
2025-02-22 21:04:37 +08:00
|
|
|
|
|
|
|
|
|
// 确保停止音频传输
|
|
|
|
|
if (this.audioHandler && this.audioHandler.isRecording) {
|
|
|
|
|
this.audioHandler.stopRecording();
|
|
|
|
|
}
|
2025-02-19 07:46:51 +08:00
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
goto(path) {
|
|
|
|
|
this.$router.push({ name: path });
|
|
|
|
|
},
|
|
|
|
|
handleGamepadConnected(e) {
|
|
|
|
|
console.log('Gamepad connected:', e.gamepad);
|
|
|
|
|
this.isGamepadConnected = true;
|
|
|
|
|
},
|
|
|
|
|
handleGamepadDisconnected() {
|
|
|
|
|
console.log('Gamepad disconnected');
|
|
|
|
|
this.isGamepadConnected = false;
|
|
|
|
|
// 可以在这里添加断开连接的处理逻辑
|
|
|
|
|
},
|
|
|
|
|
// 获取手柄数据的方法
|
|
|
|
|
getGamepadData() {
|
|
|
|
|
if (this.gamepadController) {
|
|
|
|
|
this.directionAxis0_1 = this.gamepadController.directionAxis0_1;
|
|
|
|
|
this.directionAxis9 = this.gamepadController.directionAxis9;
|
|
|
|
|
// 可以在这里添加其他数据的获取和处理
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
handleControlPress() {
|
|
|
|
|
this.isControlPressed = true;
|
2025-02-22 21:04:37 +08:00
|
|
|
|
// 开始音频传输
|
|
|
|
|
console.log('开始音频传输...');
|
|
|
|
|
if (this.audioHandler) {
|
|
|
|
|
this.audioHandler.startRecording();
|
|
|
|
|
}
|
2025-02-19 07:46:51 +08:00
|
|
|
|
},
|
|
|
|
|
handleControlRelease() {
|
|
|
|
|
this.isControlPressed = false;
|
2025-02-22 21:04:37 +08:00
|
|
|
|
// 停止音频传输
|
|
|
|
|
console.log('停止音频传输...');
|
|
|
|
|
if (this.audioHandler) {
|
|
|
|
|
this.audioHandler.stopRecording();
|
|
|
|
|
}
|
2025-02-19 07:46:51 +08:00
|
|
|
|
},
|
|
|
|
|
initWebSocket() {
|
2025-02-22 21:04:37 +08:00
|
|
|
|
try {
|
|
|
|
|
console.log('正在连接 WebSocket...');
|
|
|
|
|
this.ws = new WebSocket('ws://192.168.4.120/ws');
|
|
|
|
|
// this.ws = new WebSocket('ws://192.168.1.60:81');
|
2025-02-19 07:46:51 +08:00
|
|
|
|
|
|
|
|
|
|
2025-02-22 21:04:37 +08:00
|
|
|
|
this.ws.onopen = () => {
|
|
|
|
|
console.log('WebSocket 已连接');
|
|
|
|
|
this.wsConnected = true;
|
|
|
|
|
this.lastResponseTime = Date.now();
|
|
|
|
|
this.sendCarInfo();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.ws.onmessage = (event) => {
|
|
|
|
|
this.lastResponseTime = Date.now();
|
|
|
|
|
try {
|
|
|
|
|
const response = JSON.parse(event.data);
|
|
|
|
|
console.log('收到数据:', JSON.stringify(response));
|
|
|
|
|
if (response.JSON_id === 1 && response.rc_car_card) {
|
|
|
|
|
this.updateCarStatus(response.rc_car_card);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('解析响应数据失败:', error);
|
2025-02-19 07:46:51 +08:00
|
|
|
|
}
|
2025-02-22 21:04:37 +08:00
|
|
|
|
};
|
2025-02-19 07:46:51 +08:00
|
|
|
|
|
2025-03-05 11:40:40 +08:00
|
|
|
|
this.ws.onclose = () => {
|
|
|
|
|
console.log('WebSocket连接关闭');
|
|
|
|
|
// 只有在组件没有被卸载的情况下才重连
|
|
|
|
|
if (!this.isComponentUnmounted) {
|
|
|
|
|
this.reconnectTimer = setTimeout(() => {
|
|
|
|
|
this.initWebSocket();
|
|
|
|
|
}, 3000);
|
|
|
|
|
}
|
2025-02-22 21:04:37 +08:00
|
|
|
|
};
|
2025-02-19 07:46:51 +08:00
|
|
|
|
|
2025-02-22 21:04:37 +08:00
|
|
|
|
this.ws.onerror = (error) => {
|
|
|
|
|
console.error('WebSocket 错误:', error);
|
|
|
|
|
this.wsConnected = false;
|
|
|
|
|
// 打印更详细的错误信息
|
|
|
|
|
if (error.message) {
|
|
|
|
|
console.error('错误信息:', error.message);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('WebSocket 初始化失败:', error);
|
|
|
|
|
// 尝试重新连接
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
console.log('尝试重新连接...');
|
|
|
|
|
this.initWebSocket();
|
|
|
|
|
}, 3000);
|
|
|
|
|
}
|
2025-02-19 07:46:51 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 更新手柄数据
|
|
|
|
|
updateGamepadData() {
|
|
|
|
|
if (this.gamepadController) {
|
2025-02-19 21:33:57 +08:00
|
|
|
|
// 获取手柄数据
|
2025-02-19 07:46:51 +08:00
|
|
|
|
const angle = this.gamepadController.angle;
|
2025-02-19 21:33:57 +08:00
|
|
|
|
const directionAxis9 = this.gamepadController.directionAxis9;
|
2025-02-22 21:04:37 +08:00
|
|
|
|
const speed = this.gamepadController.speed; // 获取速度值
|
|
|
|
|
|
2025-02-19 21:33:57 +08:00
|
|
|
|
// 更新控制按钮状态
|
2025-02-22 21:04:37 +08:00
|
|
|
|
const wasPressed = this.isControlPressed;
|
2025-02-19 21:33:57 +08:00
|
|
|
|
this.isControlPressed = this.gamepadController.rightJoystickPressed;
|
2025-02-22 21:04:37 +08:00
|
|
|
|
|
|
|
|
|
// 检测按钮状态变化并处理音频传输
|
|
|
|
|
if (!wasPressed && this.isControlPressed) {
|
|
|
|
|
// 按钮刚被按下
|
|
|
|
|
console.log('手柄按钮按下,开始音频传输...');
|
|
|
|
|
if (this.audioHandler) {
|
|
|
|
|
this.audioHandler.startRecording();
|
|
|
|
|
}
|
|
|
|
|
} else if (wasPressed && !this.isControlPressed) {
|
|
|
|
|
// 按钮刚被释放
|
|
|
|
|
console.log('手柄按钮释放,停止音频传输...');
|
|
|
|
|
if (this.audioHandler) {
|
|
|
|
|
this.audioHandler.stopRecording();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-19 21:33:57 +08:00
|
|
|
|
// 更新其他数据
|
2025-02-19 07:46:51 +08:00
|
|
|
|
this.lastDirection = angle;
|
2025-02-19 21:33:57 +08:00
|
|
|
|
this.platform_fun = directionAxis9;
|
2025-02-22 21:04:37 +08:00
|
|
|
|
this.lastSpeed = speed; // 更新速度值
|
2025-02-19 07:46:51 +08:00
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
sendCarInfo() {
|
|
|
|
|
const carData = {
|
|
|
|
|
"JSON_id": 1,
|
|
|
|
|
"rc_car_card": {
|
|
|
|
|
"get_info": 1,
|
|
|
|
|
"get_attribute": 1,
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
|
|
|
this.ws.send(JSON.stringify(carData));
|
|
|
|
|
// 在控制台打印发送的数据
|
|
|
|
|
console.log('发送的数据:', JSON.stringify(carData));
|
|
|
|
|
} else {
|
|
|
|
|
console.error('WebSocket 未连接');
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 修改 refresh 图标的点击事件
|
|
|
|
|
handleRefresh() {
|
|
|
|
|
this.sendCarInfo();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
updateCarStatus(data) {
|
|
|
|
|
if (data.attribute) {
|
2025-03-05 11:40:40 +08:00
|
|
|
|
// 注释掉电池状态更新
|
|
|
|
|
// this.batteryLevel = data.attribute.bat_quantity;
|
2025-02-22 21:04:37 +08:00
|
|
|
|
// 更新障碍物显示状态
|
2025-02-19 07:46:51 +08:00
|
|
|
|
this.obstacleStatus = {
|
|
|
|
|
top: data.attribute.obstacle_sta === 0,
|
|
|
|
|
bottom: data.attribute.obstacle_sta === 1
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 更新开关状态
|
2025-02-22 21:04:37 +08:00
|
|
|
|
this.platform_fun = data.attribute.platform;
|
|
|
|
|
this.screenStatus = data.attribute.screen_en === 1;
|
|
|
|
|
this.warningLightStatus = data.attribute.warn_light_en === 1;
|
|
|
|
|
this.followStatus = data.attribute.follow_en === 1;
|
|
|
|
|
this.obstacleAvoidEnabled = data.attribute.obstacle_avoid_en === 1;
|
|
|
|
|
|
|
|
|
|
// 更新车辆状态
|
|
|
|
|
this.lastSpeed = data.attribute.car_speed;
|
|
|
|
|
this.lastDirection = data.attribute.car_direction;
|
2025-02-19 07:46:51 +08:00
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
checkConnectionStatus() {
|
|
|
|
|
const now = Date.now();
|
|
|
|
|
// 如果超过5秒没有收到响应,认为连接已断开
|
|
|
|
|
if (now - this.lastResponseTime > 5000) {
|
|
|
|
|
this.wsConnected = false;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
sendControlData() {
|
|
|
|
|
// 先更新手柄数据
|
|
|
|
|
this.updateGamepadData();
|
|
|
|
|
|
|
|
|
|
const controlData = {
|
|
|
|
|
"JSON_id": 1,
|
|
|
|
|
"rc_car_card": {
|
2025-02-22 21:04:37 +08:00
|
|
|
|
"attribute": {
|
|
|
|
|
"platform": this.platform_fun,
|
2025-02-19 07:46:51 +08:00
|
|
|
|
"screen_en": this.screenStatus ? 1 : 0,
|
|
|
|
|
"warn_light_en": this.warningLightStatus ? 1 : 0,
|
|
|
|
|
"follow_en": this.followStatus ? 1 : 0,
|
2025-02-22 21:04:37 +08:00
|
|
|
|
"obstacle_avoid_en": this.obstacleAvoidEnabled ? 1 : 0,
|
|
|
|
|
"car_speed": this.lastSpeed,
|
|
|
|
|
"car_direction": this.lastDirection
|
2025-02-19 07:46:51 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
|
|
|
this.ws.send(JSON.stringify(controlData));
|
|
|
|
|
console.log('发送控制数据:', JSON.stringify(controlData));
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
computed: {
|
2025-03-05 11:40:40 +08:00
|
|
|
|
// 注释掉电池图片计算属性
|
|
|
|
|
/*
|
2025-02-19 07:46:51 +08:00
|
|
|
|
batteryImage() {
|
|
|
|
|
if (this.batteryLevel >= 80) return require('../assets/img/bat_full.png');
|
|
|
|
|
if (this.batteryLevel >= 60) return require('../assets/img/bat_high.png');
|
|
|
|
|
if (this.batteryLevel >= 40) return require('../assets/img/bat_medium.png');
|
|
|
|
|
if (this.batteryLevel >= 20) return require('../assets/img/bat_low.png');
|
|
|
|
|
return require('../assets/img/bat_empty.png');
|
|
|
|
|
},
|
2025-03-05 11:40:40 +08:00
|
|
|
|
*/
|
2025-02-19 07:46:51 +08:00
|
|
|
|
connectionImage() {
|
|
|
|
|
return this.wsConnected ?
|
|
|
|
|
require('../assets/img/connect.png') :
|
|
|
|
|
require('../assets/img/no_connect.png');
|
2025-02-17 18:44:53 +08:00
|
|
|
|
}
|
2025-03-05 11:40:40 +08:00
|
|
|
|
},
|
|
|
|
|
setup() {
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
|
|
|
|
|
// 添加返回按钮处理函数
|
|
|
|
|
const onClickLeft = () => {
|
|
|
|
|
router.back();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
onClickLeft,
|
|
|
|
|
};
|
2025-02-17 18:44:53 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.control-panel {
|
|
|
|
|
position: fixed;
|
|
|
|
|
top: 0;
|
|
|
|
|
left: 0;
|
|
|
|
|
width: 100vw;
|
|
|
|
|
height: 100vh;
|
|
|
|
|
background-color: #000033;
|
|
|
|
|
color: #fff;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.status-bar {
|
|
|
|
|
height: 60px;
|
|
|
|
|
background-color: #000033;
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 0 20px;
|
|
|
|
|
position: relative;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.placeholder-div {
|
|
|
|
|
width: 63px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.title-container {
|
|
|
|
|
position: absolute;
|
|
|
|
|
left: 50%;
|
|
|
|
|
transform: translateX(-50%);
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.title-box {
|
|
|
|
|
border: 2px solid #00ffff;
|
2025-02-19 07:46:51 +08:00
|
|
|
|
padding: 2px;
|
2025-02-17 18:44:53 +08:00
|
|
|
|
border-radius: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.main-content {
|
|
|
|
|
flex: 1;
|
|
|
|
|
display: flex;
|
2025-02-19 07:46:51 +08:00
|
|
|
|
padding: 10px;
|
2025-02-17 18:44:53 +08:00
|
|
|
|
gap: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.left-controls {
|
|
|
|
|
width: 120px;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 15px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.switch-item {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 10px;
|
|
|
|
|
border: 1px solid #00ffff;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.center-display {
|
|
|
|
|
flex: 1;
|
|
|
|
|
border: 1px solid #00ffff;
|
|
|
|
|
border-radius: 8px;
|
2025-02-19 07:46:51 +08:00
|
|
|
|
padding: 5px;
|
2025-02-17 18:44:53 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.status-text {
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
color: #00ffff;
|
|
|
|
|
align-self: flex-start;
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.car-display {
|
|
|
|
|
flex: 1;
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
align-items: center;
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-19 07:46:51 +08:00
|
|
|
|
.boom-container {
|
|
|
|
|
position: relative;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
/* gap: 10px; */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.boom-image {
|
|
|
|
|
width: 60px;
|
|
|
|
|
height: 60px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.top-boom {
|
|
|
|
|
transform: rotate(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.bottom-boom {
|
|
|
|
|
transform: rotate(180deg);
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-17 18:44:53 +08:00
|
|
|
|
.car-image {
|
|
|
|
|
width: 160px;
|
|
|
|
|
height: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.right-status {
|
|
|
|
|
width: 150px;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.status-icons {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
width: 100%;
|
|
|
|
|
margin-top: 50px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.icons-row {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: row;
|
|
|
|
|
gap: 15px;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.icon-item {
|
|
|
|
|
width: 40px;
|
|
|
|
|
height: 40px;
|
|
|
|
|
/* border: 1px solid #00ffff; */
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
align-items: center;
|
|
|
|
|
color: #00ffff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.battery {
|
|
|
|
|
background-color: #00ffff;
|
|
|
|
|
color: #000033;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.el-switch__core) {
|
|
|
|
|
border-color: #00ffff;
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.el-switch.is-checked .el-switch__core) {
|
|
|
|
|
background-color: #00ffff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.top-right-icons {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 15px;
|
|
|
|
|
align-items: center;
|
|
|
|
|
width: 63px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.top-icon {
|
|
|
|
|
width: 24px;
|
|
|
|
|
height: 24px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
2025-02-19 07:46:51 +08:00
|
|
|
|
|
|
|
|
|
.circle-border {
|
|
|
|
|
width: 100px;
|
|
|
|
|
height: 100px;
|
|
|
|
|
border: 2px solid white;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
align-items: center;
|
|
|
|
|
transition: border-color 0.3s ease;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.circle-border.pressed {
|
|
|
|
|
border-color: red;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.control-button-img {
|
|
|
|
|
width: 80px;
|
|
|
|
|
height: 80px;
|
|
|
|
|
user-select: none;
|
|
|
|
|
/* 防止拖动图片 */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-icon-img {
|
|
|
|
|
opacity: 0;
|
|
|
|
|
transition: opacity 0.3s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-icon-img.show-voice {
|
|
|
|
|
opacity: 1;
|
|
|
|
|
}
|
2025-03-05 11:40:40 +08:00
|
|
|
|
|
|
|
|
|
/* 确保标题栏在最上层 */
|
|
|
|
|
:deep(.van-nav-bar) {
|
|
|
|
|
z-index: 999;
|
|
|
|
|
}
|
2025-02-22 21:04:37 +08:00
|
|
|
|
</style>
|