feat: 添加视角跟随功能,优化机器人菜单组件,支持全局跟随状态管理和相应操作反馈

This commit is contained in:
xudan 2025-09-12 10:31:33 +08:00
parent b237ba834e
commit 53075dbb39
2 changed files with 176 additions and 26 deletions

View File

@ -7,24 +7,24 @@
:selected-robot-name="selectedRobotName"
@save="handleImageSettingsSave"
/>
<div class="menu-container">
<!-- 左侧机器人信息区域 -->
<div v-if="robotInfo" class="robot-info-section">
<div class="robot-header">
<div class="robot-name">{{ robotInfo.label }}</div>
<div class="robot-status" :style="{ color: getRobotStatusColor(robotInfo.state as any || 'offline') }">
{{ getRobotStatusText(robotInfo.state as any || 'offline') }}
<div class="robot-status" :style="{ color: getRobotStatusColor((robotInfo.state as any) || 'offline') }">
{{ getRobotStatusText((robotInfo.state as any) || 'offline') }}
</div>
</div>
<div class="robot-details">
<div v-if="robotInfo.battery !== undefined" class="detail-item">
<span class="detail-label">电量:</span>
<div class="battery-container">
<div class="battery-bar">
<div
class="battery-fill"
<div
class="battery-fill"
:style="{ width: `${robotInfo.battery}%` }"
:class="getBatteryClass(robotInfo.battery)"
></div>
@ -32,7 +32,7 @@
<span class="battery-text">{{ robotInfo.battery }}%</span>
</div>
</div>
<div v-if="robotInfo.targetPoint" class="detail-item">
<span class="detail-label">目标点位:</span>
<span class="detail-value">{{ robotInfo.targetPoint }}</span>
@ -98,6 +98,23 @@
</div>
</div>
<!-- 视角控制 -->
<div class="action-group">
<div class="action-group-title">视角控制</div>
<div v-if="!isFollowing" class="action-item" @click="handleRobotAction('follow_view', '视角跟随')">
<span class="action-icon">👁</span>
<span>视角跟随</span>
</div>
<div
v-if="isFollowing"
class="action-item follow-active"
@click="handleRobotAction('stop_follow_view', '停止跟随')"
>
<span class="action-icon">👁🗨</span>
<span>停止跟随</span>
</div>
</div>
<!-- 系统管理 -->
<div class="action-group">
<div class="action-group-title">系统管理</div>
@ -126,14 +143,17 @@
<script setup lang="ts">
import { message } from 'ant-design-vue';
import { computed, ref } from 'vue';
import { computed, onUnmounted, ref } from 'vue';
import type { RobotInfo } from '../../apis/robot';
import type { RobotAction } from '../../services/context-menu/robot-menu.service';
import {
import {
executeRobotAction,
getGlobalFollowState,
getRobotStatusColor,
getRobotStatusText
getRobotStatusText,
startGlobalFollow,
stopGlobalFollow,
} from '../../services/context-menu/robot-menu.service';
import { editorStore } from '../../stores/editor.store';
import RobotImageSettingsModal from '../modal/robot-image-settings-modal.vue';
@ -161,6 +181,11 @@ const robotInfo = computed<RobotInfo | null>(() => {
const imageSettingsVisible = ref(false);
const selectedRobotName = ref('');
// - 使
const isFollowing = computed(() => {
const globalState = getGlobalFollowState();
return globalState.isFollowing;
});
//
defineOptions({
@ -176,51 +201,81 @@ const getBatteryClass = (batteryLevel: number) => {
return 'battery-low';
};
//
const startFollowView = () => {
if (!robotInfo.value || !editorStore.hasEditor()) return;
const editor = editorStore.getEditorValue();
if (!editor) return;
// 使
startGlobalFollow(robotInfo.value.id, editor);
message.success('开始视角跟随');
};
//
const stopFollowView = () => {
// 使
stopGlobalFollow();
message.success('停止视角跟随');
};
//
const handleRobotAction = async (action: RobotAction | 'custom_image', actionName: string) => {
if (!robotInfo.value) return;
//
if (action === 'custom_image') {
if (!robotInfo.value?.label) {
message.error('未找到机器人信息');
return;
}
console.log('打开机器人图片设置:', robotInfo.value);
//
selectedRobotName.value = robotInfo.value.label;
imageSettingsVisible.value = true;
//
emit('customImage', {
robotInfo: robotInfo.value
robotInfo: robotInfo.value,
});
return;
}
//
if (action === 'follow_view') {
startFollowView();
return;
}
if (action === 'stop_follow_view') {
stopFollowView();
return;
}
try {
console.log(`执行机器人操作: ${action} (${actionName})`, robotInfo.value);
// 使
const result = await executeRobotAction(action, robotInfo.value);
console.log('机器人操作结果:', result);
//
emit('actionComplete', {
action,
robot: robotInfo.value,
success: result.success
success: result.success,
});
} catch (error) {
console.error(`机器人${actionName}操作失败:`, error);
emit('actionComplete', {
action,
robot: robotInfo.value,
success: false
success: false,
});
}
};
@ -229,10 +284,15 @@ const handleRobotAction = async (action: RobotAction | 'custom_image', actionNam
const handleImageSettingsSave = (data: any) => {
console.log('机器人图片设置保存:', data);
message.success('机器人图片设置保存成功');
//
//
};
// 使
onUnmounted(() => {
//
});
</script>
<style scoped>
@ -396,6 +456,16 @@ const handleImageSettingsSave = (data: any) => {
background-color: #f0f0f0;
}
.action-item.follow-active {
background-color: #e6f7ff;
border: 1px solid #91d5ff;
color: #1890ff;
}
.action-item.follow-active:hover {
background-color: #bae7ff;
}
.action-icon {
font-size: 12px;
width: 14px;
@ -408,13 +478,13 @@ const handleImageSettingsSave = (data: any) => {
.menu-container {
flex-direction: column;
}
.robot-info-section {
width: 100%;
border-right: none;
border-bottom: 1px solid #e9ecef;
}
.actions-grid {
grid-template-columns: 1fr;
}

View File

@ -3,10 +3,19 @@
*
*/
import { reactive } from 'vue';
import * as AmrApi from '../../apis/amr';
import type { RobotInfo } from '../../apis/robot';
import { RobotState } from '../../apis/robot';
// 全局跟随状态管理 - 使用Vue响应式系统
const globalFollowState = reactive({
isFollowing: false,
robotId: '',
timer: null as NodeJS.Timeout | null,
});
export interface RobotMenuConfig {
menuType: 'robot' | 'default';
robotInfo?: RobotInfo;
@ -27,7 +36,9 @@ export type RobotAction =
| 'reset' // 重置
| 'diagnose' // 诊断
| 'update' // 更新
| 'custom_image'; // 自定义图片
| 'custom_image' // 自定义图片
| 'follow_view' // 视角跟随
| 'stop_follow_view'; // 停止视角跟随
/**
@ -158,6 +169,22 @@ async function executeAmrAction(
message: '打开自定义图片设置',
};
case 'follow_view':
// 视角跟随 - 前端功能不需要API调用
console.log('视角跟随功能:开始跟随机器人');
return {
success: true,
message: '开始视角跟随',
};
case 'stop_follow_view':
// 停止视角跟随 - 前端功能不需要API调用
console.log('停止视角跟随功能');
return {
success: true,
message: '停止视角跟随',
};
default:
// 其他操作 - 没有对应API保持原有打印信息逻辑
console.log(`执行机器人操作: ${action} (${robotInfo.id}) - 仅打印信息`);
@ -261,6 +288,8 @@ export function getRobotActionText(action: RobotAction): string {
'diagnose': '诊断',
'update': '更新',
'custom_image': '自定义图片',
'follow_view': '视角跟随',
'stop_follow_view': '停止跟随',
};
return actionMap[action] || action;
@ -287,6 +316,8 @@ export function getRobotActionIcon(action: RobotAction): string {
'diagnose': '🔧',
'update': '📱',
'custom_image': '🖼️',
'follow_view': '👁️',
'stop_follow_view': '👁️‍🗨️',
};
return iconMap[action] || '⚙️';
@ -314,4 +345,53 @@ export function getRobotMenuConfig(robotInfo: RobotInfo): RobotMenuConfig {
menuType: 'robot',
robotInfo,
};
}
/**
*
* @param robotId ID
* @param editor
*/
export function startGlobalFollow(robotId: string, editor: any): void {
// 如果已经在跟随其他机器人,先停止
if (globalFollowState.isFollowing && globalFollowState.robotId !== robotId) {
stopGlobalFollow();
}
globalFollowState.isFollowing = true;
globalFollowState.robotId = robotId;
// 立即执行一次聚焦
editor.gotoById(robotId);
// 设置定时器每36毫秒执行一次约27.8fps
globalFollowState.timer = setInterval(() => {
if (globalFollowState.isFollowing) {
editor.gotoById(robotId);
}
}, 10);
console.log('开始全局视角跟随:', robotId);
}
/**
*
*/
export function stopGlobalFollow(): void {
globalFollowState.isFollowing = false;
globalFollowState.robotId = '';
if (globalFollowState.timer) {
clearInterval(globalFollowState.timer);
globalFollowState.timer = null;
}
console.log('停止全局视角跟随');
}
/**
*
*/
export function getGlobalFollowState() {
return globalFollowState;
}