refactor: 优化机器人菜单和上下文菜单逻辑,重构机器人信息获取方式,简化代码结构,提升可读性和维护性

This commit is contained in:
xudan 2025-09-11 14:25:08 +08:00
parent 58c9cad3d3
commit 13c1a689b2
6 changed files with 194 additions and 382 deletions

View File

@ -3,7 +3,7 @@
<!-- 机器人图片设置模态框 -->
<RobotImageSettingsModal
v-model:open="imageSettingsVisible"
:robots="availableRobots"
:robots="robotInfo ? [{ name: robotInfo.label, type: 'robot', id: robotInfo.id }] : []"
:selected-robot-name="selectedRobotName"
@save="handleImageSettingsSave"
/>
@ -13,8 +13,8 @@
<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: getStatusColor(robotInfo.state as any || 'offline') }">
{{ getStatusText(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>
@ -126,7 +126,7 @@
<script setup lang="ts">
import { message } from 'ant-design-vue';
import { computed, ref } from 'vue';
import { ref } from 'vue';
import type { RobotInfo } from '../../apis/robot';
import type { RobotAction } from '../../services/context-menu/robot-menu.service';
@ -139,7 +139,6 @@ import RobotImageSettingsModal from '../modal/robot-image-settings-modal.vue';
interface Props {
robotInfo?: RobotInfo;
availableRobots?: Array<{ name: string; type: string; id: string }>;
}
interface Emits {
@ -147,9 +146,7 @@ interface Emits {
(e: 'customImage', data: { robotInfo: RobotInfo }): void;
}
const props = withDefaults(defineProps<Props>(), {
availableRobots: () => []
});
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
@ -157,39 +154,13 @@ const emit = defineEmits<Emits>();
const imageSettingsVisible = ref(false);
const selectedRobotName = ref('');
//
const availableRobots = computed(() => {
// 使
if (props.availableRobots && props.availableRobots.length > 0) {
return props.availableRobots;
}
//
if (props.robotInfo) {
return [{
name: props.robotInfo.label,
type: 'robot', //
id: props.robotInfo.id
}];
}
return [];
});
//
defineOptions({
name: 'RobotMenu',
});
//
const getStatusText = (status: string): string => {
return getRobotStatusText(status);
};
//
const getStatusColor = (status: string): string => {
return getRobotStatusColor(status);
};
// 使
//
const getBatteryClass = (batteryLevel: number) => {
@ -198,33 +169,27 @@ const getBatteryClass = (batteryLevel: number) => {
return 'battery-low';
};
//
const handleCustomImage = () => {
if (!props.robotInfo?.label) {
message.error('未找到机器人信息');
return;
}
console.log('打开机器人图片设置:', props.robotInfo);
//
selectedRobotName.value = props.robotInfo.label;
imageSettingsVisible.value = true;
console.log('设置模态框可见性:', imageSettingsVisible.value);
//
emit('customImage', {
robotInfo: props.robotInfo
});
};
//
const handleRobotAction = async (action: RobotAction | 'custom_image', actionName: string) => {
if (!props.robotInfo) return;
//
if (action === 'custom_image') {
handleCustomImage();
if (!props.robotInfo?.label) {
message.error('未找到机器人信息');
return;
}
console.log('打开机器人图片设置:', props.robotInfo);
//
selectedRobotName.value = props.robotInfo.label;
imageSettingsVisible.value = true;
//
emit('customImage', {
robotInfo: props.robotInfo
});
return;
}

View File

@ -73,7 +73,7 @@
<script setup lang="ts">
import { UploadOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import { computed, inject, onMounted, ref, watch } from 'vue';
import { computed, onMounted, ref, watch } from 'vue';
import colorConfig from '../../services/color/color-config.service';
import { EditorService } from '../../services/editor.service';
@ -88,6 +88,7 @@ interface Props {
open: boolean;
robots: RobotInfo[];
selectedRobotName?: string;
editor?: EditorService; // editorprops
}
interface Emits {
@ -118,8 +119,7 @@ const formData = ref({
});
//
const EDITOR_KEY = Symbol('editor-key');
const editor = inject<{ value: EditorService }>(EDITOR_KEY);
const editor = computed(() => props.editor);
//
const availableRobots = computed(() => props.robots);
@ -166,6 +166,13 @@ const loadRobotImages = (robotName: string) => {
formData.value.images.normal = colorConfig.getRobotCustomImage(robotName, 'normal') || '';
};
//
const updateRobotImage = () => {
if (editor.value && typeof editor.value.updateRobotImage === 'function') {
editor.value.updateRobotImage(selectedRobot.value);
}
};
//
const handleImageUpload = async (file: File, state: 'normal') => {
if (!selectedRobot.value) {
@ -192,9 +199,7 @@ const handleImageUpload = async (file: File, state: 'normal') => {
formData.value.images[state] = colorConfig.getRobotCustomImage(selectedRobot.value, state) || '';
// pen
if (editor?.value && typeof editor.value.updateRobotImage === 'function') {
editor.value.updateRobotImage(selectedRobot.value);
}
updateRobotImage();
message.success('自定义图片上传成功');
} catch (error) {
@ -213,12 +218,7 @@ const removeImage = (state: 'normal') => {
colorConfig.removeRobotCustomImage(selectedRobot.value, state);
formData.value.images[state] = '';
// pen
if (editor?.value && typeof editor.value.updateRobotImage === 'function') {
editor.value.updateRobotImage(selectedRobot.value);
}
updateRobotImage();
message.success('自定义图片已删除');
};
@ -228,12 +228,7 @@ const resetImages = () => {
colorConfig.removeRobotCustomImage(selectedRobot.value);
formData.value.images.normal = '';
// pen
if (editor?.value && typeof editor.value.updateRobotImage === 'function') {
editor.value.updateRobotImage(selectedRobot.value);
}
updateRobotImage();
message.success('图片已重置');
};
@ -243,12 +238,7 @@ const clearAllImages = () => {
colorConfig.removeRobotCustomImage(selectedRobot.value);
formData.value.images.normal = '';
// pen
if (editor?.value && typeof editor.value.updateRobotImage === 'function') {
editor.value.updateRobotImage(selectedRobot.value);
}
updateRobotImage();
message.success('图片已清除');
};

View File

@ -11,8 +11,7 @@ import { autoDoorSimulationService, type AutoDoorWebSocketData } from '../servic
import {
type ContextMenuState,
createContextMenuManager,
handleContextMenuFromPenData,
isClickInsideMenu} from '../services/context-menu.service';
handleContextMenuFromPenData} from '../services/context-menu.service';
import { EditorService } from '../services/editor.service';
import { StorageLocationService } from '../services/storage-location.service';
import { useViewState } from '../services/useViewState';
@ -232,17 +231,6 @@ onMounted(async () => {
//
if (editor.value) {
autoDoorSimulationService.setEditorService(editor.value);
//
// colorConfig.setEditorService(editor.value);
// 使WebSocket
// autoDoorSimulationService.startSimulation({
// deviceId: 'test01',
// label: 'TestAutoDoor01',
// interval: 3000,
// initialStatus: 0,
// enableLogging: true,
// });
}
//
@ -267,8 +255,6 @@ onUnmounted(() => {
storageLocationService.value?.destroy();
//
autoDoorSimulationService.clearBufferedData();
// WebSocket
// autoDoorSimulationService.stopAllSimulations();
// EditorService
if (editor.value) {
@ -376,14 +362,6 @@ const handleEditorContextMenu = (penData: Record<string, unknown>) => {
});
};
/**
* 处理原生右键菜单事件作为备用
* @param event 鼠标事件
*/
const handleContextMenuEvent = (event: MouseEvent) => {
// handleEditorContextMenu
console.log('原生右键菜单事件(备用):', event);
};
/**
@ -408,16 +386,12 @@ const handleActionComplete = (data: any) => {
* @param event 点击事件
*/
const handleGlobalClick = (event: MouseEvent) => {
//
const closeButton = (event.target as Element)?.closest('.close-button');
if (closeButton) {
//
return;
}
};
/**
@ -460,7 +434,7 @@ const handleGlobalKeydown = (event: KeyboardEvent) => {
</a-tabs>
</a-layout-sider>
<a-layout-content>
<div ref="container" class="editor-container full" @contextmenu="handleContextMenuEvent"></div>
<div ref="container" class="editor-container full"></div>
<!-- 自定义地图工具栏固定右下角最小侵入 -->
<MapToolbar :token="EDITOR_KEY" :container-el="container" />
</a-layout-content>
@ -492,6 +466,7 @@ const handleGlobalKeydown = (event: KeyboardEvent) => {
:menu-type="contextMenuState.menuType"
:storage-locations="contextMenuState.storageLocations"
:robot-info="contextMenuState.robotInfo"
:editor="editor"
@close="handleCloseContextMenu"
@action-complete="handleActionComplete"
/>

View File

@ -30,92 +30,9 @@ export function parsePenData(penData: Record<string, unknown>): ParsedEventData
y: eventInfo?.clientY || 0
};
// 如果有pen数据优先使用pen信息进行解析
// 如果有pen数据使用统一的解析逻辑
if (pen) {
console.log('解析pen数据:', pen);
const { id, name, tags = [], storageLocation } = pen as {
id?: string;
name?: string;
tags?: string[];
storageLocation?: {
pointId: string;
locationName: string;
index: number;
occupied: boolean;
locked: boolean;
};
};
console.log('解析后的数据:', { id, name, tags, storageLocation });
// 根据tags判断类型 - 统一处理库位相关区域
if (tags.includes('storage-background') || tags.includes('storage-location')) {
const isBackground = tags.includes('storage-background');
console.log(`识别为库位相关类型: ${isBackground ? 'storage-background' : 'storage-location'}`);
// 库位背景区域或单个库位区域 - 都查找该点关联的所有库位
const pointId = tags.find((tag: string) => tag.startsWith('point-'))?.replace('point-', '');
return {
type: 'storage-background', // 统一使用storage-background类型
id,
name,
pointId,
storageId: id,
tags,
target: document.elementFromPoint(position.x, position.y) as HTMLElement,
position,
pen,
};
}
if (tags.includes('point')) {
// 点区域
return {
type: 'point',
id,
name,
pointId: id,
tags,
target: document.elementFromPoint(position.x, position.y) as HTMLElement,
position,
pen,
};
}
if (tags.includes('robot')) {
// 机器人区域
// 机器人的真实名称存储在text字段中而不是name字段
const robotName = (pen?.text as string) || name || `机器人-${id}`;
console.log('解析pen数据中的机器人信息:', {
id,
originalName: name,
penText: pen?.text,
finalName: robotName
});
return {
type: 'robot',
id,
name: robotName,
tags,
target: document.elementFromPoint(position.x, position.y) as HTMLElement,
position,
pen,
};
}
if (tags.includes('area')) {
// 区域
return {
type: 'area',
id,
name,
tags,
target: document.elementFromPoint(position.x, position.y) as HTMLElement,
position,
pen,
};
}
return parsePenObject(pen, position);
}
// 默认情况
@ -127,6 +44,110 @@ export function parsePenData(penData: Record<string, unknown>): ParsedEventData
};
}
/**
* pen对象解析逻辑
* @param pen pen对象数据
* @param position
* @returns
*/
function parsePenObject(pen: Record<string, unknown>, position: { x: number; y: number }): ParsedEventData {
console.log('解析pen数据:', pen);
const { id, name, tags = [], storageLocation } = pen as {
id?: string;
name?: string;
tags?: string[];
storageLocation?: {
pointId: string;
locationName: string;
index: number;
occupied: boolean;
locked: boolean;
};
};
console.log('解析后的数据:', { id, name, tags, storageLocation });
const target = document.elementFromPoint(position.x, position.y) as HTMLElement;
// 根据tags判断类型 - 统一处理库位相关区域
if (tags.includes('storage-background') || tags.includes('storage-location')) {
const isBackground = tags.includes('storage-background');
console.log(`识别为库位相关类型: ${isBackground ? 'storage-background' : 'storage-location'}`);
// 库位背景区域或单个库位区域 - 都查找该点关联的所有库位
const pointId = tags.find((tag: string) => tag.startsWith('point-'))?.replace('point-', '');
return {
type: 'storage-background', // 统一使用storage-background类型
id,
name: isBackground ? name : (storageLocation?.locationName || name),
pointId,
storageId: id,
tags,
target,
position,
pen,
};
}
if (tags.includes('point')) {
// 点区域
return {
type: 'point',
id,
name,
pointId: id,
tags,
target,
position,
pen,
};
}
if (tags.includes('robot')) {
// 机器人区域
// 机器人的真实名称存储在text字段中而不是name字段
const robotName = (pen?.text as string) || name || `机器人-${id}`;
console.log('解析pen数据中的机器人信息:', {
id,
originalName: name,
penText: pen?.text,
finalName: robotName
});
return {
type: 'robot',
id,
name: robotName,
tags,
target,
position,
pen,
};
}
if (tags.includes('area')) {
// 区域
return {
type: 'area',
id,
name,
tags,
target,
position,
pen,
};
}
// 默认情况
console.log('未识别到特定类型,使用默认类型');
return {
type: 'default',
target,
position,
pen,
};
}
/**
* -
* @param event
@ -138,105 +159,9 @@ export function parseEventData(event: MouseEvent | PointerEvent): ParsedEventDat
// 从事件对象中获取pen数据如果存在
const pen = (event as MouseEvent & { pen?: Record<string, unknown> }).pen;
// 如果有pen数据优先使用pen信息进行解析
// 如果有pen数据使用统一的解析逻辑
if (pen) {
console.log('解析pen数据:', pen);
const { id, name, tags = [], storageLocation } = pen as {
id?: string;
name?: string;
tags?: string[];
storageLocation?: {
pointId: string;
locationName: string;
index: number;
occupied: boolean;
locked: boolean;
};
};
console.log('解析后的数据:', { id, name, tags, storageLocation });
// 根据tags判断类型
if (tags.includes('storage-background')) {
console.log('识别为storage-background类型');
// 库位背景区域
const pointId = tags.find((tag: string) => tag.startsWith('point-'))?.replace('point-', '');
return {
type: 'storage-background',
id,
name,
pointId,
storageId: id,
tags,
target,
position,
pen,
};
}
if (tags.includes('storage-location')) {
console.log('识别为storage-location类型');
// 库位区域 - 使用storageLocation中的详细信息
const pointId = tags.find((tag: string) => tag.startsWith('point-'))?.replace('point-', '');
return {
type: 'storage',
id,
name: storageLocation?.locationName || name,
pointId,
storageId: id,
tags,
target,
position,
pen,
};
}
if (tags.includes('point')) {
// 点区域
return {
type: 'point',
id,
name,
pointId: id,
tags,
target,
position,
pen,
};
}
if (tags.includes('robot')) {
// 机器人区域
// 机器人的真实名称存储在text字段中而不是name字段
const robotName = (pen?.text as string) || name || `机器人-${id}`;
console.log('解析机器人信息:', {
id,
originalName: name,
penText: pen?.text,
finalName: robotName
});
return {
type: 'robot',
id,
name: robotName,
tags,
target,
position,
pen,
};
}
if (tags.includes('area')) {
// 区域
return {
type: 'area',
id,
name,
tags,
target,
position,
pen,
};
}
return parsePenObject(pen, position);
}
// 回退到DOM元素检查

View File

@ -5,7 +5,7 @@
import type { ParsedEventData } from './event-parser';
import { parseEventData, parsePenData } from './event-parser';
import { getRobotMenuConfig, type RobotMenuConfig } from './robot-menu.service';
import type { RobotMenuConfig } from './robot-menu.service';
import { getStorageMenuConfig, type StorageMenuConfig } from './storage-menu.service';
export interface PointMenuConfig {
@ -51,9 +51,19 @@ export function getMenuConfig(
// 库位相关类型,包括点区域(如果是动作点且有库位信息)
return getStorageMenuConfig(type, data, services?.storageLocationService);
case 'robot':
// 机器人类型 - 只传递机器人ID和EditorService
return getRobotMenuConfig(data.id, services?.robotService);
case 'robot': {
// 机器人类型 - 直接获取机器人信息
const robotInfo = services?.robotService?.getRobotById?.(data.id);
if (robotInfo) {
return {
menuType: 'robot' as const,
robotInfo,
} as RobotMenuConfig;
}
return {
menuType: 'default' as const,
};
}
case 'area':
// 区域类型
@ -90,6 +100,36 @@ function getAreaMenuConfig(data: ParsedEventData): AreaMenuConfig {
};
}
/**
* -
* @param parsedData
* @param manager
* @param services
*/
function processContextMenu(
parsedData: ParsedEventData,
manager: any,
services?: {
storageLocationService?: any;
robotService?: any;
}
) {
// 获取菜单配置
const menuConfig = getMenuConfig(parsedData.type, parsedData, services);
// 更新状态
manager.setState({
visible: true,
x: parsedData.position.x,
y: parsedData.position.y,
eventData: parsedData,
isRightClickActive: true, // 标记右键菜单正在显示
...menuConfig, // 展开具体配置
});
console.log('右键菜单事件数据:', parsedData);
}
/**
* -
* @param event
@ -108,23 +148,9 @@ export function handleContextMenu(
event.preventDefault();
event.stopPropagation();
// 1. 解析事件数据
// 解析事件数据并处理
const parsedData = parseEventData(event);
// 2. 获取菜单配置
const menuConfig = getMenuConfig(parsedData.type, parsedData, services);
// 3. 更新状态
manager.setState({
visible: true,
x: parsedData.position.x,
y: parsedData.position.y,
eventData: parsedData,
isRightClickActive: true, // 标记右键菜单正在显示
...menuConfig, // 展开具体配置
});
console.log('右键菜单事件数据:', parsedData);
processContextMenu(parsedData, manager, services);
}
/**
@ -141,21 +167,7 @@ export function handleContextMenuFromPenData(
robotService?: any;
}
) {
// 1. 解析penData
// 解析penData并处理
const parsedData = parsePenData(penData);
// 2. 获取菜单配置
const menuConfig = getMenuConfig(parsedData.type, parsedData, services);
// 3. 更新状态
manager.setState({
visible: true,
x: parsedData.position.x,
y: parsedData.position.y,
eventData: parsedData,
isRightClickActive: true, // 标记右键菜单正在显示
...menuConfig, // 展开具体配置
});
console.log('右键菜单事件数据:', parsedData);
processContextMenu(parsedData, manager, services);
}

View File

@ -29,61 +29,6 @@ export type RobotAction =
| 'update' // 更新
| 'custom_image'; // 自定义图片
/**
*
* @param robotId ID
* @param robotService EditorService
* @returns
*/
export function getRobotInfo(robotId: string, robotService?: any): RobotInfo | undefined {
if (!robotId) {
console.warn('无法获取机器人ID');
return undefined;
}
// 直接通过EditorService获取完整的机器人数据WebSocket推送的数据
if (robotService && typeof robotService.getRobotById === 'function') {
try {
const robot = robotService.getRobotById(robotId);
if (robot) {
console.log('从EditorService获取到机器人信息:', robot);
// 直接返回EditorService中的RobotInfo数据无需转换
return robot;
}
} catch (error) {
console.error('获取机器人信息失败:', error);
}
}
// 如果无法获取机器人数据返回undefined
console.warn('无法获取机器人数据机器人ID:', robotId);
return undefined;
}
/**
* -
* @param robotId ID
* @param robotService EditorService
* @returns
*/
export function getRobotMenuConfig(robotId: string, robotService?: any): RobotMenuConfig {
console.log(`处理机器人类型机器人ID: ${robotId}`);
const robotInfo = getRobotInfo(robotId, robotService);
if (robotInfo) {
console.log('找到机器人信息:', robotInfo);
return {
menuType: 'robot' as const,
robotInfo,
};
} else {
console.log('未找到机器人信息,显示默认菜单');
return {
menuType: 'default' as const,
};
}
}
/**
*