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

@ -13,8 +13,8 @@
<div v-if="robotInfo" class="robot-info-section"> <div v-if="robotInfo" class="robot-info-section">
<div class="robot-header"> <div class="robot-header">
<div class="robot-name">{{ robotInfo.label }}</div> <div class="robot-name">{{ robotInfo.label }}</div>
<div class="robot-status" :style="{ color: getRobotStatusColor(robotInfo.state as any || 'offline') }"> <div class="robot-status" :style="{ color: getRobotStatusColor((robotInfo.state as any) || 'offline') }">
{{ getRobotStatusText(robotInfo.state as any || 'offline') }} {{ getRobotStatusText((robotInfo.state as any) || 'offline') }}
</div> </div>
</div> </div>
@ -98,6 +98,23 @@
</div> </div>
</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">
<div class="action-group-title">系统管理</div> <div class="action-group-title">系统管理</div>
@ -126,14 +143,17 @@
<script setup lang="ts"> <script setup lang="ts">
import { message } from 'ant-design-vue'; 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 { RobotInfo } from '../../apis/robot';
import type { RobotAction } from '../../services/context-menu/robot-menu.service'; import type { RobotAction } from '../../services/context-menu/robot-menu.service';
import { import {
executeRobotAction, executeRobotAction,
getGlobalFollowState,
getRobotStatusColor, getRobotStatusColor,
getRobotStatusText getRobotStatusText,
startGlobalFollow,
stopGlobalFollow,
} from '../../services/context-menu/robot-menu.service'; } from '../../services/context-menu/robot-menu.service';
import { editorStore } from '../../stores/editor.store'; import { editorStore } from '../../stores/editor.store';
import RobotImageSettingsModal from '../modal/robot-image-settings-modal.vue'; import RobotImageSettingsModal from '../modal/robot-image-settings-modal.vue';
@ -161,6 +181,11 @@ const robotInfo = computed<RobotInfo | null>(() => {
const imageSettingsVisible = ref(false); const imageSettingsVisible = ref(false);
const selectedRobotName = ref(''); const selectedRobotName = ref('');
// - 使
const isFollowing = computed(() => {
const globalState = getGlobalFollowState();
return globalState.isFollowing;
});
// //
defineOptions({ defineOptions({
@ -176,6 +201,25 @@ const getBatteryClass = (batteryLevel: number) => {
return 'battery-low'; 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) => { const handleRobotAction = async (action: RobotAction | 'custom_image', actionName: string) => {
if (!robotInfo.value) return; if (!robotInfo.value) return;
@ -195,11 +239,22 @@ const handleRobotAction = async (action: RobotAction | 'custom_image', actionNam
// //
emit('customImage', { emit('customImage', {
robotInfo: robotInfo.value robotInfo: robotInfo.value,
}); });
return; return;
} }
//
if (action === 'follow_view') {
startFollowView();
return;
}
if (action === 'stop_follow_view') {
stopFollowView();
return;
}
try { try {
console.log(`执行机器人操作: ${action} (${actionName})`, robotInfo.value); console.log(`执行机器人操作: ${action} (${actionName})`, robotInfo.value);
@ -212,7 +267,7 @@ const handleRobotAction = async (action: RobotAction | 'custom_image', actionNam
emit('actionComplete', { emit('actionComplete', {
action, action,
robot: robotInfo.value, robot: robotInfo.value,
success: result.success success: result.success,
}); });
} catch (error) { } catch (error) {
console.error(`机器人${actionName}操作失败:`, error); console.error(`机器人${actionName}操作失败:`, error);
@ -220,7 +275,7 @@ const handleRobotAction = async (action: RobotAction | 'custom_image', actionNam
emit('actionComplete', { emit('actionComplete', {
action, action,
robot: robotInfo.value, robot: robotInfo.value,
success: false success: false,
}); });
} }
}; };
@ -233,6 +288,11 @@ const handleImageSettingsSave = (data: any) => {
// //
// //
}; };
// 使
onUnmounted(() => {
//
});
</script> </script>
<style scoped> <style scoped>
@ -396,6 +456,16 @@ const handleImageSettingsSave = (data: any) => {
background-color: #f0f0f0; 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 { .action-icon {
font-size: 12px; font-size: 12px;
width: 14px; width: 14px;

View File

@ -3,10 +3,19 @@
* *
*/ */
import { reactive } from 'vue';
import * as AmrApi from '../../apis/amr'; import * as AmrApi from '../../apis/amr';
import type { RobotInfo } from '../../apis/robot'; import type { RobotInfo } from '../../apis/robot';
import { RobotState } from '../../apis/robot'; import { RobotState } from '../../apis/robot';
// 全局跟随状态管理 - 使用Vue响应式系统
const globalFollowState = reactive({
isFollowing: false,
robotId: '',
timer: null as NodeJS.Timeout | null,
});
export interface RobotMenuConfig { export interface RobotMenuConfig {
menuType: 'robot' | 'default'; menuType: 'robot' | 'default';
robotInfo?: RobotInfo; robotInfo?: RobotInfo;
@ -27,7 +36,9 @@ export type RobotAction =
| 'reset' // 重置 | 'reset' // 重置
| 'diagnose' // 诊断 | 'diagnose' // 诊断
| 'update' // 更新 | 'update' // 更新
| 'custom_image'; // 自定义图片 | 'custom_image' // 自定义图片
| 'follow_view' // 视角跟随
| 'stop_follow_view'; // 停止视角跟随
/** /**
@ -158,6 +169,22 @@ async function executeAmrAction(
message: '打开自定义图片设置', message: '打开自定义图片设置',
}; };
case 'follow_view':
// 视角跟随 - 前端功能不需要API调用
console.log('视角跟随功能:开始跟随机器人');
return {
success: true,
message: '开始视角跟随',
};
case 'stop_follow_view':
// 停止视角跟随 - 前端功能不需要API调用
console.log('停止视角跟随功能');
return {
success: true,
message: '停止视角跟随',
};
default: default:
// 其他操作 - 没有对应API保持原有打印信息逻辑 // 其他操作 - 没有对应API保持原有打印信息逻辑
console.log(`执行机器人操作: ${action} (${robotInfo.id}) - 仅打印信息`); console.log(`执行机器人操作: ${action} (${robotInfo.id}) - 仅打印信息`);
@ -261,6 +288,8 @@ export function getRobotActionText(action: RobotAction): string {
'diagnose': '诊断', 'diagnose': '诊断',
'update': '更新', 'update': '更新',
'custom_image': '自定义图片', 'custom_image': '自定义图片',
'follow_view': '视角跟随',
'stop_follow_view': '停止跟随',
}; };
return actionMap[action] || action; return actionMap[action] || action;
@ -287,6 +316,8 @@ export function getRobotActionIcon(action: RobotAction): string {
'diagnose': '🔧', 'diagnose': '🔧',
'update': '📱', 'update': '📱',
'custom_image': '🖼️', 'custom_image': '🖼️',
'follow_view': '👁️',
'stop_follow_view': '👁️‍🗨️',
}; };
return iconMap[action] || '⚙️'; return iconMap[action] || '⚙️';
@ -315,3 +346,52 @@ export function getRobotMenuConfig(robotInfo: RobotInfo): RobotMenuConfig {
robotInfo, 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;
}