feat: 添加视角跟随功能,优化机器人菜单组件,支持全局跟随状态管理和相应操作反馈
This commit is contained in:
parent
b237ba834e
commit
53075dbb39
@ -7,24 +7,24 @@
|
|||||||
:selected-robot-name="selectedRobotName"
|
:selected-robot-name="selectedRobotName"
|
||||||
@save="handleImageSettingsSave"
|
@save="handleImageSettingsSave"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="menu-container">
|
<div class="menu-container">
|
||||||
<!-- 左侧:机器人信息区域 -->
|
<!-- 左侧:机器人信息区域 -->
|
||||||
<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>
|
||||||
|
|
||||||
<div class="robot-details">
|
<div class="robot-details">
|
||||||
<div v-if="robotInfo.battery !== undefined" class="detail-item">
|
<div v-if="robotInfo.battery !== undefined" class="detail-item">
|
||||||
<span class="detail-label">电量:</span>
|
<span class="detail-label">电量:</span>
|
||||||
<div class="battery-container">
|
<div class="battery-container">
|
||||||
<div class="battery-bar">
|
<div class="battery-bar">
|
||||||
<div
|
<div
|
||||||
class="battery-fill"
|
class="battery-fill"
|
||||||
:style="{ width: `${robotInfo.battery}%` }"
|
:style="{ width: `${robotInfo.battery}%` }"
|
||||||
:class="getBatteryClass(robotInfo.battery)"
|
:class="getBatteryClass(robotInfo.battery)"
|
||||||
></div>
|
></div>
|
||||||
@ -32,7 +32,7 @@
|
|||||||
<span class="battery-text">{{ robotInfo.battery }}%</span>
|
<span class="battery-text">{{ robotInfo.battery }}%</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="robotInfo.targetPoint" class="detail-item">
|
<div v-if="robotInfo.targetPoint" class="detail-item">
|
||||||
<span class="detail-label">目标点位:</span>
|
<span class="detail-label">目标点位:</span>
|
||||||
<span class="detail-value">{{ robotInfo.targetPoint }}</span>
|
<span class="detail-value">{{ robotInfo.targetPoint }}</span>
|
||||||
@ -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,51 +201,81 @@ 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;
|
||||||
|
|
||||||
// 处理自定义图片操作
|
// 处理自定义图片操作
|
||||||
if (action === 'custom_image') {
|
if (action === 'custom_image') {
|
||||||
if (!robotInfo.value?.label) {
|
if (!robotInfo.value?.label) {
|
||||||
message.error('未找到机器人信息');
|
message.error('未找到机器人信息');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('打开机器人图片设置:', robotInfo.value);
|
console.log('打开机器人图片设置:', robotInfo.value);
|
||||||
|
|
||||||
// 打开模态框,不关闭右键菜单
|
// 打开模态框,不关闭右键菜单
|
||||||
selectedRobotName.value = robotInfo.value.label;
|
selectedRobotName.value = robotInfo.value.label;
|
||||||
imageSettingsVisible.value = true;
|
imageSettingsVisible.value = true;
|
||||||
|
|
||||||
// 只触发自定义图片事件,不触发操作完成事件
|
// 只触发自定义图片事件,不触发操作完成事件
|
||||||
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);
|
||||||
|
|
||||||
// 使用服务函数执行操作
|
// 使用服务函数执行操作
|
||||||
const result = await executeRobotAction(action, robotInfo.value);
|
const result = await executeRobotAction(action, robotInfo.value);
|
||||||
|
|
||||||
console.log('机器人操作结果:', result);
|
console.log('机器人操作结果:', result);
|
||||||
|
|
||||||
// 发送操作完成事件
|
// 发送操作完成事件
|
||||||
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);
|
||||||
|
|
||||||
emit('actionComplete', {
|
emit('actionComplete', {
|
||||||
action,
|
action,
|
||||||
robot: robotInfo.value,
|
robot: robotInfo.value,
|
||||||
success: false
|
success: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -229,10 +284,15 @@ const handleRobotAction = async (action: RobotAction | 'custom_image', actionNam
|
|||||||
const handleImageSettingsSave = (data: any) => {
|
const handleImageSettingsSave = (data: any) => {
|
||||||
console.log('机器人图片设置保存:', data);
|
console.log('机器人图片设置保存:', data);
|
||||||
message.success('机器人图片设置保存成功');
|
message.success('机器人图片设置保存成功');
|
||||||
|
|
||||||
// 可以在这里添加额外的保存后处理逻辑
|
// 可以在这里添加额外的保存后处理逻辑
|
||||||
// 比如通知父组件更新机器人显示等
|
// 比如通知父组件更新机器人显示等
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 组件卸载时不需要清理,因为使用全局状态管理
|
||||||
|
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;
|
||||||
@ -408,13 +478,13 @@ const handleImageSettingsSave = (data: any) => {
|
|||||||
.menu-container {
|
.menu-container {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.robot-info-section {
|
.robot-info-section {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-right: none;
|
border-right: none;
|
||||||
border-bottom: 1px solid #e9ecef;
|
border-bottom: 1px solid #e9ecef;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions-grid {
|
.actions-grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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] || '⚙️';
|
||||||
@ -314,4 +345,53 @@ export function getRobotMenuConfig(robotInfo: RobotInfo): RobotMenuConfig {
|
|||||||
menuType: 'robot',
|
menuType: 'robot',
|
||||||
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;
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user