diff --git a/src/apis/amr/api.ts b/src/apis/amr/api.ts index c09886f..f0a4646 100644 --- a/src/apis/amr/api.ts +++ b/src/apis/amr/api.ts @@ -8,6 +8,7 @@ const enum API { 手动发布充电任务 = '/amr/manuallSetCharging', 暂停AMR = '/amr', 恢复AMR = '/amr', + 手动移动任务 = '/task/manual/move', } // 只保留右键菜单需要的API函数 @@ -127,3 +128,58 @@ export async function resumeAmr(amrId: string, sceneId: string): Promise { + type B = { + sceneId: string; + amrId: string; + taskName: string; + targetStationName: string; + description?: string; + operator?: string; + priority: number; + }; + type D = { + success: boolean; + message: string; + code: number; + result?: { + taskId: string; + taskBlockId: string; + vwedTaskId: string; + amrId: string; + amrName: string; + currentStationName: string; + targetStationName: string; + }; + timestamp: number; + }; + + try { + const response = await http.post(API.手动移动任务, params); + return { + success: (response as any).success, + message: (response as any).message, + data: (response as any).result, + }; + } catch (error) { + console.error('手动下发移动任务失败:', error); + return { + success: false, + message: '', // 让全局错误处理显示具体错误 + }; + } +} diff --git a/src/components/context-menu/context-menu.vue b/src/components/context-menu/context-menu.vue index cfcbd22..bc66878 100644 --- a/src/components/context-menu/context-menu.vue +++ b/src/components/context-menu/context-menu.vue @@ -26,6 +26,14 @@ @custom-image="handleCustomImage" /> + + + @@ -38,6 +46,7 @@ import { computed, ref, watch } from 'vue'; import type { StorageLocationInfo } from '../../services/context-menu'; import DefaultMenu from './default-menu.vue'; +import PointMenu from './point-menu.vue'; import RobotMenu from './robot-menu.vue'; import StorageMenu from './storage-menu.vue'; @@ -48,6 +57,11 @@ interface Props { menuType?: 'default' | 'storage' | 'storage-background' | 'robot' | 'point' | 'area'; storageLocations?: StorageLocationInfo[]; robotId?: string; // 改为传递机器人ID + pointInfo?: { // 添加站点信息 + id: string; + name: string; + type: string; + }; apiBaseUrl?: string; isPlaybackMode?: boolean; } @@ -67,6 +81,8 @@ const props = withDefaults(defineProps(), { }); const emit = defineEmits(); + + // 拖拽功能 const menuRef = ref(null); const handleRef = ref(null); @@ -125,27 +141,8 @@ defineOptions({ name: 'ContextMenu', }); -// 触发器引用 -const triggerRef = ref(null); const mainMenuWidth = ref(200); // 默认宽度 -// 触发器样式 - 创建一个不可见的定位点 -const triggerStyle = computed(() => ({ - position: 'fixed' as const, - left: `${props.x}px`, - top: `${props.y}px`, - width: '1px', - height: '1px', - pointerEvents: 'none' as const, - zIndex: 9999, -})); - -// 使用 Ant Design 的自动边界检测,无需手动计算 -const dropdownPlacement = 'bottomLeft' as const; - -// 获取弹出层容器 -const getPopupContainer = () => document.body; - // 动态标题 const headerTitle = computed(() => { if (props.menuType === 'storage-background') return '库位状态'; @@ -156,12 +153,6 @@ const headerTitle = computed(() => { return '右键菜单'; }); -// 处理下拉菜单开关变化 -const handleOpenChange = (open: boolean) => { - if (!open) { - emit('close'); - } -}; // 处理操作完成事件 const handleActionComplete = (data: { success: boolean; action: string; message?: string }) => { diff --git a/src/components/context-menu/point-menu.vue b/src/components/context-menu/point-menu.vue new file mode 100644 index 0000000..702cfec --- /dev/null +++ b/src/components/context-menu/point-menu.vue @@ -0,0 +1,256 @@ + + + + + \ No newline at end of file diff --git a/src/components/modal/robot-selector-modal.vue b/src/components/modal/robot-selector-modal.vue new file mode 100644 index 0000000..86582ea --- /dev/null +++ b/src/components/modal/robot-selector-modal.vue @@ -0,0 +1,280 @@ + + + + + \ No newline at end of file diff --git a/src/pages/movement-supervision.vue b/src/pages/movement-supervision.vue index b3cb6a8..6ff6421 100644 --- a/src/pages/movement-supervision.vue +++ b/src/pages/movement-supervision.vue @@ -855,6 +855,7 @@ const handleGlobalKeydown = (event: KeyboardEvent) => { :menu-type="contextMenuState.menuType" :storage-locations="contextMenuState.storageLocations" :robot-id="contextMenuState.robotInfo?.id" + :point-info="contextMenuState.pointInfo" :token="EDITOR_KEY" :is-playback-mode="isPlaybackMode" @close="handleCloseContextMenu" diff --git a/src/services/context-menu/event-parser.ts b/src/services/context-menu/event-parser.ts index 39bdba8..c476614 100644 --- a/src/services/context-menu/event-parser.ts +++ b/src/services/context-menu/event-parser.ts @@ -64,9 +64,7 @@ function parsePenObject(pen: Record, position: { x: number; y: locked: boolean; }; }; - - console.log('解析后的数据:', { id, name, tags, storageLocation }); - + const target = document.elementFromPoint(position.x, position.y) as HTMLElement; // 根据tags判断类型 - 统一处理库位相关区域 diff --git a/src/services/context-menu/menu-config.service.ts b/src/services/context-menu/menu-config.service.ts index e722359..9e3b87c 100644 --- a/src/services/context-menu/menu-config.service.ts +++ b/src/services/context-menu/menu-config.service.ts @@ -5,19 +5,10 @@ import type { ParsedEventData } from './event-parser'; import { parseEventData, parsePenData } from './event-parser'; +import { getPointMenuConfig, isStationPoint, type PointMenuConfig } from './point-menu.service'; import type { RobotMenuConfig } from './robot-menu.service'; import { getStorageMenuConfig, type StorageMenuConfig } from './storage-menu.service'; -export interface PointMenuConfig { - menuType: 'point' | 'default'; - pointInfo?: { - id: string; - name: string; - type: string; - hasStorage: boolean; - }; -} - export interface AreaMenuConfig { menuType: 'area' | 'default'; areaInfo?: { @@ -27,7 +18,7 @@ export interface AreaMenuConfig { }; } -export type MenuConfig = StorageMenuConfig | RobotMenuConfig | PointMenuConfig | AreaMenuConfig; +export type MenuConfig = StorageMenuConfig | RobotMenuConfig | AreaMenuConfig | PointMenuConfig; /** * 获取菜单配置 - 统一入口 @@ -47,10 +38,27 @@ export function getMenuConfig( switch (type) { case 'storage-background': case 'storage': - case 'point': - // 库位相关类型,包括点区域(如果是动作点且有库位信息) + // 库位相关类型 return getStorageMenuConfig(type, data, services?.storageLocationService); + case 'point': + // 检查是否为站点(动作点)而不是库位点 + if (isStationPoint(data.tags || [], data.pen)) { + // 是站点,显示站点菜单 + // 尝试从 pen 对象中获取真实的站点名称 + const pen = data.pen as any; + const stationName = pen?.label || pen?.text || pen?.title || data.name || `站点-${data.id}`; + + return getPointMenuConfig({ + id: data.id || '', + name: stationName, + type: data.type || 'point', + }); + } else { + // 是库位点,显示库位菜单 + return getStorageMenuConfig(type, data, services?.storageLocationService); + } + case 'robot': { // 机器人类型 - 直接获取机器人信息 const robotInfo = services?.robotService?.getRobotById?.(data.id); @@ -104,16 +112,16 @@ function processContextMenu( } // 更新状态 - manager.setState({ + const stateToSet = { visible: true, x: parsedData.position.x, y: parsedData.position.y, eventData: parsedData, isRightClickActive: true, // 标记右键菜单正在显示 ...menuConfig, // 展开具体配置 - }); + }; - console.log('右键菜单事件数据:', parsedData); + manager.setState(stateToSet); } /** diff --git a/src/services/context-menu/point-menu.service.ts b/src/services/context-menu/point-menu.service.ts new file mode 100644 index 0000000..53eee3e --- /dev/null +++ b/src/services/context-menu/point-menu.service.ts @@ -0,0 +1,108 @@ +/** + * 站点右键菜单服务 + * 处理站点导航功能 + */ + +import * as AmrApi from '../../apis/amr'; +import { editorStore } from '../../stores/editor.store'; + +export interface PointMenuConfig { + menuType: 'point' | 'default'; + pointInfo?: { + id: string; + name: string; + type: string; + }; +} + +/** + * 执行路径导航任务 + * @param pointId 目标站点ID + * @param pointName 目标站点名称 + * @param amrId 选择的机器人ID + * @param amrName 选择的机器人名称 + * @returns 操作结果 + */ +export async function executeNavigateToPoint( + pointId: string, + pointName: string, + amrId: string, + amrName: string +): Promise<{ success: boolean; message: string; result?: any }> { + try { + console.log(`执行路径导航: 机器人 ${amrName} 到站点 ${pointName}`); + + // 获取场景ID + const sceneId = editorStore.getEditorValue()?.getSceneId(); + if (!sceneId) { + console.error('路径导航失败: 场景ID未提供'); + return { + success: false, + message: '路径导航失败: 场景ID未提供', + }; + } + + // 调用手动下发小车移动任务接口 + const result = await AmrApi.manualMoveTask({ + sceneId, + amrId, // 注意:根据接口文档,这里应该传递AMR的ID + taskName: '手动移动任务', + targetStationName: pointName, // 使用站点名称作为目标站点 + description: `手动下发的小车移动任务:从当前位置到 ${pointName}`, + operator: 'system', + priority: 500, + }); + + if (result.success) { + return { + success: true, + message: '路径导航任务下发成功', + result: result.data, + }; + } else { + return { + success: false, + message: result.message || '路径导航任务下发失败', + }; + } + + } catch (error) { + console.error('路径导航失败:', error); + return { + success: false, + message: '', // 让全局错误处理显示具体错误 + }; + } +} + +/** + * 获取站点菜单配置 + * @param pointInfo 站点信息 + * @returns 站点菜单配置 + */ +export function getPointMenuConfig(pointInfo: { + id: string; + name: string; + type: string; +}): PointMenuConfig { + return { + menuType: 'point', + pointInfo, + }; +} + +/** + * 判断是否为站点(动作点) + * @param tags 元素标签 + * @param pen 元素数据 + * @returns 是否为站点 + */ +export function isStationPoint(tags: string[], pen?: Record): boolean { + // 如果标签包含 point 且不包含 storage 相关标签,则认为是站点 + // 注意:不依赖 name 字段,因为 name 可能是 'point' + return tags.includes('point') && + !tags.includes('storage') && + !tags.includes('storage-background') && + !tags.includes('storage-location') && + !(pen as any)?.storageLocation; // 检查是否有库位关联 +} \ No newline at end of file diff --git a/src/services/context-menu/state-manager.ts b/src/services/context-menu/state-manager.ts index f9669c1..af653ef 100644 --- a/src/services/context-menu/state-manager.ts +++ b/src/services/context-menu/state-manager.ts @@ -11,6 +11,11 @@ export interface ContextMenuState { eventData?: any; storageLocations?: any[]; robotInfo?: any; + pointInfo?: { + id: string; + name: string; + type: string; + }; isRightClickActive?: boolean; // 新增:标记右键菜单是否正在显示,用于阻止选中状态更新 }