From 930df2a6f7960609fd8a67784e9e96a3d0be3030 Mon Sep 17 00:00:00 2001 From: xudan Date: Mon, 8 Sep 2025 11:44:28 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=E5=8F=B3=E9=94=AE?= =?UTF-8?q?=E8=8F=9C=E5=8D=95=E7=BB=84=E4=BB=B6=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=8C=96=E5=92=8C=E5=8A=A8=E6=80=81=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E4=BC=A0=E9=80=92=EF=BC=8C=E4=BC=98=E5=8C=96=E4=B8=8A?= =?UTF-8?q?=E4=B8=8B=E6=96=87=E8=8F=9C=E5=8D=95=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/context-menu.vue | 514 +------------- src/components/context-menu/context-menu.vue | 139 ++++ src/components/context-menu/default-menu.vue | 85 +++ src/components/context-menu/index.ts | 9 + src/components/context-menu/robot-menu.vue | 299 +++++++++ src/components/context-menu/storage-menu.vue | 403 +++++++++++ src/pages/movement-supervision.vue | 16 +- src/services/context-menu.service.ts | 632 +----------------- src/services/context-menu/README.md | 164 +++++ src/services/context-menu/event-parser.ts | 298 +++++++++ src/services/context-menu/index.ts | 39 ++ .../context-menu/menu-config.service.ts | 159 +++++ .../context-menu/robot-menu.service.ts | 165 +++++ src/services/context-menu/state-manager.ts | 80 +++ .../context-menu/storage-menu.service.ts | 196 ++++++ src/vite-env.d.ts | 6 + 16 files changed, 2093 insertions(+), 1111 deletions(-) create mode 100644 src/components/context-menu/context-menu.vue create mode 100644 src/components/context-menu/default-menu.vue create mode 100644 src/components/context-menu/index.ts create mode 100644 src/components/context-menu/robot-menu.vue create mode 100644 src/components/context-menu/storage-menu.vue create mode 100644 src/services/context-menu/README.md create mode 100644 src/services/context-menu/event-parser.ts create mode 100644 src/services/context-menu/index.ts create mode 100644 src/services/context-menu/menu-config.service.ts create mode 100644 src/services/context-menu/robot-menu.service.ts create mode 100644 src/services/context-menu/state-manager.ts create mode 100644 src/services/context-menu/storage-menu.service.ts diff --git a/src/components/context-menu.vue b/src/components/context-menu.vue index 02bbd65..6654ecd 100644 --- a/src/components/context-menu.vue +++ b/src/components/context-menu.vue @@ -1,515 +1,45 @@ - + diff --git a/src/components/context-menu/context-menu.vue b/src/components/context-menu/context-menu.vue new file mode 100644 index 0000000..f08fae0 --- /dev/null +++ b/src/components/context-menu/context-menu.vue @@ -0,0 +1,139 @@ + + + + + diff --git a/src/components/context-menu/default-menu.vue b/src/components/context-menu/default-menu.vue new file mode 100644 index 0000000..34250c3 --- /dev/null +++ b/src/components/context-menu/default-menu.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/src/components/context-menu/index.ts b/src/components/context-menu/index.ts new file mode 100644 index 0000000..91ede4a --- /dev/null +++ b/src/components/context-menu/index.ts @@ -0,0 +1,9 @@ +/** + * 右键菜单组件 - 主入口 + * 导出所有相关的组件 + */ + +export { default as ContextMenu } from './context-menu.vue'; +export { default as DefaultMenu } from './default-menu.vue'; +export { default as RobotMenu } from './robot-menu.vue'; +export { default as StorageMenu } from './storage-menu.vue'; diff --git a/src/components/context-menu/robot-menu.vue b/src/components/context-menu/robot-menu.vue new file mode 100644 index 0000000..7f5da72 --- /dev/null +++ b/src/components/context-menu/robot-menu.vue @@ -0,0 +1,299 @@ + + + + + diff --git a/src/components/context-menu/storage-menu.vue b/src/components/context-menu/storage-menu.vue new file mode 100644 index 0000000..76d4e78 --- /dev/null +++ b/src/components/context-menu/storage-menu.vue @@ -0,0 +1,403 @@ + + + + + diff --git a/src/pages/movement-supervision.vue b/src/pages/movement-supervision.vue index 09cc336..c3d3e3e 100644 --- a/src/pages/movement-supervision.vue +++ b/src/pages/movement-supervision.vue @@ -365,7 +365,9 @@ const backToCards = () => { */ const handleEditorContextMenu = (penData: Record) => { console.log('EditorService自定义右键菜单事件:', penData); - handleContextMenuFromPenData(penData, contextMenuManager, storageLocationService.value); + handleContextMenuFromPenData(penData, contextMenuManager, { + storageLocationService: storageLocationService.value + }); }; /** @@ -385,6 +387,16 @@ const handleCloseContextMenu = () => { contextMenuManager.close(); }; +/** + * 处理右键菜单操作完成事件 + * @param data 操作数据 + */ +const handleActionComplete = (data: any) => { + console.log('右键菜单操作完成:', data); + // 可以在这里添加操作完成后的处理逻辑 + // 比如刷新数据、显示消息等 +}; + /** * 处理全局点击事件,用于关闭右键菜单 * @param event 点击事件 @@ -470,7 +482,9 @@ const handleGlobalKeydown = (event: KeyboardEvent) => { :y="contextMenuState.y" :menu-type="contextMenuState.menuType" :storage-locations="contextMenuState.storageLocations" + :robot-info="contextMenuState.robotInfo" @close="handleCloseContextMenu" + @action-complete="handleActionComplete" /> diff --git a/src/services/context-menu.service.ts b/src/services/context-menu.service.ts index bfa81d2..a911697 100644 --- a/src/services/context-menu.service.ts +++ b/src/services/context-menu.service.ts @@ -1,623 +1,19 @@ /** - * 右键菜单服务 - 组合式函数模式 - * 提供纯函数和状态管理工具,避免单例模式的问题 + * 右键菜单服务 - 向后兼容入口 + * 重新导出新的模块化服务,保持向后兼容 */ -import { MapPointType } from '@api/map'; +// 重新导出所有新的模块化服务 +export * from './context-menu'; -export interface ParsedEventData { - type: 'robot' | 'point' | 'area' | 'storage' | 'storage-background' | 'default'; - id?: string; - name?: string; - pointId?: string; // 关联的点ID - storageId?: string; // 库位ID - tags?: string[]; // 标签信息 - target: HTMLElement; - position: { x: number; y: number }; - pen?: Record; // 原始pen对象数据 -} +// 为了向后兼容,保持原有的导出名称 +export { + createContextMenuManager as createContextMenuManager, + defaultContextMenuManager as defaultContextMenuManager, + parseEventData as parseEventData, + parsePenData as parsePenData, + isClickInsideMenu as isClickInsideMenu, + handleContextMenu as handleContextMenu, + handleContextMenuFromPenData as handleContextMenuFromPenData, +} from './context-menu'; -export interface StorageLocationInfo { - id: string; - name: string; - isOccupied: boolean; // 是否占用 - isLocked: boolean; // 是否锁定 - status: 'available' | 'occupied' | 'locked' | 'unknown'; -} - -export interface ContextMenuState { - visible: boolean; - x: number; - y: number; - menuType: 'default' | 'storage' | 'storage-background'; - storageLocations: StorageLocationInfo[]; - eventData?: ParsedEventData; -} - -/** - * 右键菜单状态管理器 - * 使用组合式函数,避免单例模式 - */ -export function createContextMenuManager() { - let state: ContextMenuState = { - visible: false, - x: 0, - y: 0, - menuType: 'default', - storageLocations: [], - }; - - const listeners: Array<(state: ContextMenuState) => void> = []; - - /** - * 订阅状态变化 - */ - function subscribe(listener: (state: ContextMenuState) => void) { - listeners.push(listener); - return () => { - const index = listeners.indexOf(listener); - if (index > -1) { - listeners.splice(index, 1); - } - }; - } - - /** - * 通知状态变化 - */ - function notify() { - listeners.forEach(listener => listener({ ...state })); - } - - /** - * 获取当前状态 - */ - function getState(): ContextMenuState { - return { ...state }; - } - - /** - * 更新状态 - */ - function setState(newState: Partial) { - state = { ...state, ...newState }; - notify(); - } - - /** - * 关闭菜单 - */ - function close() { - setState({ visible: false }); - } - - return { - subscribe, - getState, - setState, - close, - }; -} - -/** - * 解析penData - 纯函数,无副作用 - * @param penData EditorService传递的pen数据 - * @returns 解析后的事件数据 - */ -export function parsePenData(penData: Record): ParsedEventData { - // 从penData中提取pen数据和事件信息 - const pen = penData.pen as Record; - const eventInfo = penData.e as { clientX: number; clientY: number }; - - const position = { - x: eventInfo?.clientX || 0, - y: eventInfo?.clientY || 0 - }; - - // 如果有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')) { - // 机器人区域 - return { - type: 'robot', - id, - name, - 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, - }; - } - } - - // 默认情况 - console.log('未识别到特定类型,使用默认类型'); - return { - type: 'default', - target: document.elementFromPoint(position.x, position.y) as HTMLElement, - position, - }; -} - -/** - * 解析事件数据 - 纯函数,无副作用 - * @param event 鼠标事件或指针事件 - * @returns 解析后的事件数据 - */ -export function parseEventData(event: MouseEvent | PointerEvent): ParsedEventData { - const target = event.target as HTMLElement; - const position = { x: event.clientX, y: event.clientY }; - // 从事件对象中获取pen数据(如果存在) - const pen = (event as MouseEvent & { pen?: Record }).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')) { - // 机器人区域 - return { - type: 'robot', - id, - name, - tags, - target, - position, - pen, - }; - } - - if (tags.includes('area')) { - // 区域 - return { - type: 'area', - id, - name, - tags, - target, - position, - pen, - }; - } - } - - // 回退到DOM元素检查 - if (target?.closest('.robot-item')) { - return { - type: 'robot', - id: target.dataset.robotId || target.id, - name: target.dataset.robotName || target.textContent?.trim(), - target, - position, - }; - } - - if (target?.closest('.point-item')) { - return { - type: 'point', - id: target.dataset.pointId || target.id, - name: target.dataset.pointName || target.textContent?.trim(), - target, - position, - }; - } - - if (target?.closest('.area-item')) { - return { - type: 'area', - id: target.dataset.areaId || target.id, - name: target.dataset.areaName || target.textContent?.trim(), - target, - position, - }; - } - - if (target?.closest('.storage-location')) { - return { - type: 'storage', - id: target.dataset.storageId || target.id, - name: target.dataset.storageName || target.textContent?.trim(), - target, - position, - }; - } - - // 默认情况 - console.log('未识别到特定类型,使用默认类型'); - return { - type: 'default', - target, - position, - }; -} - -/** - * 检查点击是否在菜单区域内 - 纯函数 - * @param event 点击事件 - * @param isMenuVisible 菜单是否可见 - * @returns 是否在菜单区域内 - */ -export function isClickInsideMenu(event: MouseEvent, isMenuVisible: boolean): boolean { - if (!isMenuVisible) return false; - - const menuElement = document.querySelector('.context-menu'); - if (!menuElement) return false; - - const rect = menuElement.getBoundingClientRect(); - const x = event.clientX; - const y = event.clientY; - - return ( - x >= rect.left && - x <= rect.right && - y >= rect.top && - y <= rect.bottom - ); -} - -/** - * 获取菜单配置 - 纯函数 - * @param type 事件类型 - * @param data 事件数据 - * @returns 菜单配置 - */ -export function getMenuConfig(type: string, data: ParsedEventData, storageLocationService?: any) { - switch (type) { - case 'storage-background': { - // 库位背景区域或单个库位区域:显示该点关联的所有库位信息 - console.log(`处理库位相关类型,点ID: ${data.pointId}`); - const allStorageLocations = getStorageLocationsForPoint(data, storageLocationService); - console.log(`找到 ${allStorageLocations.length} 个库位:`, allStorageLocations); - return { - menuType: 'storage-background' as const, - storageLocations: allStorageLocations, - }; - } - case 'storage': { - // 单个库位区域 - 也使用storage-background类型统一处理 - console.log(`处理单个库位类型,点ID: ${data.pointId}`); - const singleStorageLocations = getStorageLocationsForPoint(data, storageLocationService); - console.log(`找到 ${singleStorageLocations.length} 个库位:`, singleStorageLocations); - return { - menuType: 'storage-background' as const, - storageLocations: singleStorageLocations, - }; - } - case 'point': { - // 点区域:只有动作点且有库位信息时才显示库位菜单 - console.log(`处理点区域类型,点ID: ${data.pointId}`); - - // 检查是否为动作点且有库位信息 - const isActionPointWithStorage = checkIfActionPointWithStorage(data); - - if (isActionPointWithStorage) { - const pointStorageLocations = getStorageLocationsForPoint(data, storageLocationService); - console.log(`动作点找到 ${pointStorageLocations.length} 个库位:`, pointStorageLocations); - return { - menuType: 'storage-background' as const, - storageLocations: pointStorageLocations, - }; - } else { - console.log('非动作点或无库位信息,显示默认菜单'); - return { - menuType: 'default' as const, - storageLocations: [], - }; - } - } - default: - return { - menuType: 'default' as const, - storageLocations: [], - }; - } -} - - -/** - * 检查是否为动作点且有库位信息 - * @param data 事件数据 - * @returns 是否为动作点且有库位信息 - */ -function checkIfActionPointWithStorage(data: ParsedEventData): boolean { - // 从pen数据中获取点信息 - const pen = data.pen; - if (!pen) { - console.log('无pen数据,不是动作点'); - return false; - } - - const pointInfo = pen.point as any; - if (!pointInfo) { - console.log('无point信息,不是动作点'); - return false; - } - - // 检查是否为动作点 - const isActionPoint = pointInfo.type === MapPointType.动作点; - if (!isActionPoint) { - console.log(`点类型为 ${pointInfo.type},不是动作点`); - return false; - } - - // 检查是否有库位信息 - const hasStorageLocations = pointInfo.associatedStorageLocations && - Array.isArray(pointInfo.associatedStorageLocations) && - pointInfo.associatedStorageLocations.length > 0; - - console.log(`动作点检查结果: 是动作点=${isActionPoint}, 有库位信息=${hasStorageLocations}`); - - return isActionPoint && hasStorageLocations; -} - -/** - * 查找指定点ID的所有库位信息 - * @param pointId 点ID - * @param storageLocationService StorageLocationService实例(可选) - * @returns 库位信息列表 - */ -function findStorageLocationsByPointId(pointId: string, storageLocationService?: any): StorageLocationInfo[] { - console.log(`查找点 ${pointId} 关联的所有库位`); - - // 如果提供了StorageLocationService,尝试使用它获取真实数据 - if (storageLocationService && typeof storageLocationService.getLocationsByPointId === 'function') { - const locations = storageLocationService.getLocationsByPointId(pointId); - if (locations && locations.length > 0) { - console.log(`从StorageLocationService获取到 ${locations.length} 个库位:`, locations); - // 转换StorageLocationService的数据格式到我们的格式 - const convertedLocations = locations.map((location: any) => { - const converted = { - id: location.id || `storage-${pointId}-${location.layer_index || 0}`, - name: location.layer_name || location.locationName || location.name || '未知库位', - isOccupied: location.is_occupied || location.occupied || false, - isLocked: location.is_locked || location.locked || false, - status: (location.is_locked || location.locked) - ? 'locked' - : (location.is_occupied || location.occupied) - ? 'occupied' - : 'available', - }; - console.log(`转换库位数据: ${location.layer_name} -> ${converted.name}, 占用: ${converted.isOccupied}, 锁定: ${converted.isLocked}, 状态: ${converted.status}`); - return converted; - }); - return convertedLocations; - } - } - - // 回退到模拟数据 - console.log('使用模拟数据'); - const mockStorageLocations: StorageLocationInfo[] = [ - { - id: `storage-${pointId}-0`, - name: 'GSA-1-1-1', // 模拟库位名称 - isOccupied: false, - isLocked: false, - status: 'available', - }, - { - id: `storage-${pointId}-1`, - name: 'GSA-1-1-2', - isOccupied: true, - isLocked: false, - status: 'occupied', - }, - { - id: `storage-${pointId}-2`, - name: 'GSA-1-1-3', - isOccupied: false, - isLocked: true, - status: 'locked', - }, - ]; - - return mockStorageLocations; -} - -/** - * 获取点关联的库位信息列表 - * @param data 事件数据 - * @param storageLocationService StorageLocationService实例(可选) - * @returns 库位信息列表 - */ -function getStorageLocationsForPoint(data: ParsedEventData, storageLocationService?: any): StorageLocationInfo[] { - const pointId = data.pointId || data.id; - - if (!pointId) { - console.warn('无法获取点ID,返回空库位列表'); - return []; - } - - return findStorageLocationsByPointId(pointId, storageLocationService); -} - -/** - * 处理右键事件 - 组合函数 - * @param event 鼠标事件或指针事件 - * @param manager 状态管理器 - */ -export function handleContextMenu( - event: MouseEvent | PointerEvent, - manager: ReturnType -) { - // 阻止默认右键菜单 - event.preventDefault(); - event.stopPropagation(); - - // 1. 解析事件数据 - const parsedData = parseEventData(event); - - // 2. 获取菜单配置 - const menuConfig = getMenuConfig(parsedData.type, parsedData); - - // 3. 更新状态 - manager.setState({ - visible: true, - x: parsedData.position.x, - y: parsedData.position.y, - menuType: menuConfig.menuType, - storageLocations: menuConfig.storageLocations, - eventData: parsedData, - }); - - console.log('右键菜单事件数据:', parsedData); -} - -/** - * 处理EditorService的penData - 组合函数 - * @param penData EditorService传递的pen数据 - * @param manager 状态管理器 - * @param storageLocationService StorageLocationService实例(可选) - */ -export function handleContextMenuFromPenData( - penData: Record, - manager: ReturnType, - storageLocationService?: any -) { - // 1. 解析penData - const parsedData = parsePenData(penData); - - // 2. 获取菜单配置 - const menuConfig = getMenuConfig(parsedData.type, parsedData, storageLocationService); - - // 3. 更新状态 - manager.setState({ - visible: true, - x: parsedData.position.x, - y: parsedData.position.y, - menuType: menuConfig.menuType, - storageLocations: menuConfig.storageLocations, - eventData: parsedData, - }); - - console.log('右键菜单事件数据:', parsedData); -} - -// 为了向后兼容,提供一个默认的状态管理器实例 -// 但建议在组件中创建独立的状态管理器 -export const defaultContextMenuManager = createContextMenuManager(); diff --git a/src/services/context-menu/README.md b/src/services/context-menu/README.md new file mode 100644 index 0000000..b24aa47 --- /dev/null +++ b/src/services/context-menu/README.md @@ -0,0 +1,164 @@ +# 右键菜单服务 - 模块化架构 + +## 概述 + +右键菜单服务已重构为模块化架构,按业务逻辑分离到不同的文件中,提高了代码的可维护性和可扩展性。 + +## 文件夹结构 + +``` +src/services/context-menu/ +├── index.ts # 主入口文件,导出所有服务 +├── state-manager.ts # 核心状态管理 +├── event-parser.ts # 事件解析器 +├── storage-menu.service.ts # 库位相关业务逻辑 +├── robot-menu.service.ts # 机器人相关业务逻辑 +├── menu-config.service.ts # 菜单配置服务 +└── README.md # 说明文档 +``` + +## 组件结构 + +``` +src/components/context-menu/ +├── index.ts # 组件入口文件 +├── context-menu.vue # 主菜单组件 +├── storage-menu.vue # 库位菜单组件 +├── robot-menu.vue # 机器人菜单组件 +└── default-menu.vue # 默认菜单组件 +``` + +## 核心模块说明 + +### 1. 状态管理 (state-manager.ts) + +- 提供纯函数和状态管理工具 +- 避免单例模式的问题 +- 支持订阅/通知机制 + +### 2. 事件解析 (event-parser.ts) + +- 解析鼠标事件和pen数据 +- 提取事件信息 +- 纯函数,无副作用 + +### 3. 库位菜单服务 (storage-menu.service.ts) + +- 处理库位相关的菜单逻辑 +- 库位状态检查 +- 库位操作处理 + +### 4. 机器人菜单服务 (robot-menu.service.ts) + +- 处理机器人相关的菜单逻辑 +- 机器人状态管理 +- 机器人操作处理 + +### 5. 菜单配置服务 (menu-config.service.ts) + +- 统一管理不同业务类型的菜单配置 +- 提供统一的菜单配置入口 + +## 使用方式 + +### 基本使用 + +```typescript +import { createContextMenuManager, handleContextMenu, handleContextMenuFromPenData } from '@/services/context-menu'; + +// 创建状态管理器 +const manager = createContextMenuManager(); + +// 处理右键事件 +handleContextMenu(event, manager, { + storageLocationService: storageService, + robotService: robotService, +}); +``` + +### 组件使用 + +```vue + + + +``` + +## 扩展新业务类型 + +### 1. 添加新的业务服务 + +在 `src/services/context-menu/` 下创建新的服务文件,例如 `point-menu.service.ts`: + +```typescript +export interface PointMenuConfig { + menuType: 'point' | 'default'; + pointInfo?: PointInfo; +} + +export function getPointMenuConfig(data: ParsedEventData): PointMenuConfig { + // 实现点位菜单逻辑 +} +``` + +### 2. 添加新的组件 + +在 `src/components/context-menu/` 下创建新的组件文件,例如 `point-menu.vue`: + +```vue + + + +``` + +### 3. 更新菜单配置服务 + +在 `menu-config.service.ts` 中添加新的业务类型处理: + +```typescript +case 'point': + return getPointMenuConfig(data); +``` + +### 4. 更新主菜单组件 + +在 `context-menu.vue` 中添加新的组件: + +```vue + +``` + +## 向后兼容 + +原有的 `context-menu.service.ts` 文件仍然保留,作为向后兼容的入口,重新导出新的模块化服务。现有代码无需修改即可继续使用。 + +## 优势 + +1. **模块化**: 按业务逻辑分离,代码更清晰 +2. **可维护性**: 每个模块职责单一,易于维护 +3. **可扩展性**: 新增业务类型只需添加对应模块 +4. **可测试性**: 每个模块可独立测试 +5. **向后兼容**: 保持现有API不变 diff --git a/src/services/context-menu/event-parser.ts b/src/services/context-menu/event-parser.ts new file mode 100644 index 0000000..0b6be05 --- /dev/null +++ b/src/services/context-menu/event-parser.ts @@ -0,0 +1,298 @@ +/** + * 事件解析器 - 纯函数,无副作用 + * 负责解析鼠标事件和pen数据,提取事件信息 + */ + +export interface ParsedEventData { + type: 'robot' | 'point' | 'area' | 'storage' | 'storage-background' | 'default'; + id?: string; + name?: string; + pointId?: string; // 关联的点ID + storageId?: string; // 库位ID + tags?: string[]; // 标签信息 + target: HTMLElement; + position: { x: number; y: number }; + pen?: Record; // 原始pen对象数据 +} + +/** + * 解析penData - 纯函数,无副作用 + * @param penData EditorService传递的pen数据 + * @returns 解析后的事件数据 + */ +export function parsePenData(penData: Record): ParsedEventData { + // 从penData中提取pen数据和事件信息 + const pen = penData.pen as Record; + const eventInfo = penData.e as { clientX: number; clientY: number }; + + const position = { + x: eventInfo?.clientX || 0, + y: eventInfo?.clientY || 0 + }; + + // 如果有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')) { + // 机器人区域 + return { + type: 'robot', + id, + name, + 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, + }; + } + } + + // 默认情况 + console.log('未识别到特定类型,使用默认类型'); + return { + type: 'default', + target: document.elementFromPoint(position.x, position.y) as HTMLElement, + position, + }; +} + +/** + * 解析事件数据 - 纯函数,无副作用 + * @param event 鼠标事件或指针事件 + * @returns 解析后的事件数据 + */ +export function parseEventData(event: MouseEvent | PointerEvent): ParsedEventData { + const target = event.target as HTMLElement; + const position = { x: event.clientX, y: event.clientY }; + // 从事件对象中获取pen数据(如果存在) + const pen = (event as MouseEvent & { pen?: Record }).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')) { + // 机器人区域 + return { + type: 'robot', + id, + name, + tags, + target, + position, + pen, + }; + } + + if (tags.includes('area')) { + // 区域 + return { + type: 'area', + id, + name, + tags, + target, + position, + pen, + }; + } + } + + // 回退到DOM元素检查 + if (target?.closest('.robot-item')) { + return { + type: 'robot', + id: target.dataset.robotId || target.id, + name: target.dataset.robotName || target.textContent?.trim(), + target, + position, + }; + } + + if (target?.closest('.point-item')) { + return { + type: 'point', + id: target.dataset.pointId || target.id, + name: target.dataset.pointName || target.textContent?.trim(), + target, + position, + }; + } + + if (target?.closest('.area-item')) { + return { + type: 'area', + id: target.dataset.areaId || target.id, + name: target.dataset.areaName || target.textContent?.trim(), + target, + position, + }; + } + + if (target?.closest('.storage-location')) { + return { + type: 'storage', + id: target.dataset.storageId || target.id, + name: target.dataset.storageName || target.textContent?.trim(), + target, + position, + }; + } + + // 默认情况 + console.log('未识别到特定类型,使用默认类型'); + return { + type: 'default', + target, + position, + }; +} + +/** + * 检查点击是否在菜单区域内 - 纯函数 + * @param event 点击事件 + * @param isMenuVisible 菜单是否可见 + * @returns 是否在菜单区域内 + */ +export function isClickInsideMenu(event: MouseEvent, isMenuVisible: boolean): boolean { + if (!isMenuVisible) return false; + + const menuElement = document.querySelector('.context-menu'); + if (!menuElement) return false; + + const rect = menuElement.getBoundingClientRect(); + const x = event.clientX; + const y = event.clientY; + + return ( + x >= rect.left && + x <= rect.right && + y >= rect.top && + y <= rect.bottom + ); +} diff --git a/src/services/context-menu/index.ts b/src/services/context-menu/index.ts new file mode 100644 index 0000000..53c2362 --- /dev/null +++ b/src/services/context-menu/index.ts @@ -0,0 +1,39 @@ +/** + * 右键菜单服务 - 主入口 + * 导出所有相关的服务和类型 + */ + +// 状态管理 +export type { ContextMenuState } from './state-manager'; +export { createContextMenuManager, defaultContextMenuManager } from './state-manager'; + +// 事件解析 +export type { ParsedEventData } from './event-parser'; +export { isClickInsideMenu, parseEventData, parsePenData } from './event-parser'; + +// 库位菜单服务 +export type { StorageLocationInfo, StorageMenuConfig } from './storage-menu.service'; +export { + checkIfActionPointWithStorage, + findStorageLocationsByPointId, + getStorageLocationsForPoint, + getStorageMenuConfig +} from './storage-menu.service'; + +// 机器人菜单服务 +export type { RobotInfo, RobotMenuConfig } from './robot-menu.service'; +export { + executeRobotAction, + getRobotInfo, + getRobotMenuConfig, + getRobotStatusColor, + getRobotStatusText +} from './robot-menu.service'; + +// 菜单配置服务 +export type { AreaMenuConfig, MenuConfig, PointMenuConfig } from './menu-config.service'; +export { + getMenuConfig, + handleContextMenu, + handleContextMenuFromPenData +} from './menu-config.service'; diff --git a/src/services/context-menu/menu-config.service.ts b/src/services/context-menu/menu-config.service.ts new file mode 100644 index 0000000..809609c --- /dev/null +++ b/src/services/context-menu/menu-config.service.ts @@ -0,0 +1,159 @@ +/** + * 菜单配置服务 + * 统一管理不同业务类型的菜单配置 + */ + +import type { ParsedEventData } from './event-parser'; +import { parseEventData, parsePenData } from './event-parser'; +import { getRobotMenuConfig, 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?: { + id: string; + name: string; + type: string; + }; +} + +export type MenuConfig = StorageMenuConfig | RobotMenuConfig | PointMenuConfig | AreaMenuConfig; + +/** + * 获取菜单配置 - 统一入口 + * @param type 事件类型 + * @param data 事件数据 + * @param services 服务实例集合(可选) + * @returns 菜单配置 + */ +export function getMenuConfig( + type: string, + data: ParsedEventData, + services?: { + storageLocationService?: any; + robotService?: any; + } +): MenuConfig { + switch (type) { + case 'storage-background': + case 'storage': + case 'point': + // 库位相关类型,包括点区域(如果是动作点且有库位信息) + return getStorageMenuConfig(type, data, services?.storageLocationService); + + case 'robot': + // 机器人类型 + return getRobotMenuConfig(data, services?.robotService); + + case 'area': + // 区域类型 + return getAreaMenuConfig(data); + + default: + return { + menuType: 'default' as const, + }; + } +} + +/** + * 获取区域菜单配置 + * @param data 事件数据 + * @returns 区域菜单配置 + */ +function getAreaMenuConfig(data: ParsedEventData): AreaMenuConfig { + console.log(`处理区域类型,区域ID: ${data.id}`); + + if (data.id && data.name) { + return { + menuType: 'area' as const, + areaInfo: { + id: data.id, + name: data.name, + type: '区域', + }, + }; + } + + return { + menuType: 'default' as const, + }; +} + +/** + * 处理右键事件 - 组合函数 + * @param event 鼠标事件或指针事件 + * @param manager 状态管理器 + * @param services 服务实例集合(可选) + */ +export function handleContextMenu( + event: MouseEvent | PointerEvent, + manager: any, + services?: { + storageLocationService?: any; + robotService?: any; + } +) { + // 阻止默认右键菜单 + 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, + ...menuConfig, // 展开具体配置 + }); + + console.log('右键菜单事件数据:', parsedData); +} + +/** + * 处理EditorService的penData - 组合函数 + * @param penData EditorService传递的pen数据 + * @param manager 状态管理器 + * @param services 服务实例集合(可选) + */ +export function handleContextMenuFromPenData( + penData: Record, + manager: any, + services?: { + storageLocationService?: any; + robotService?: any; + } +) { + // 1. 解析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, + ...menuConfig, // 展开具体配置 + }); + + console.log('右键菜单事件数据:', parsedData); +} diff --git a/src/services/context-menu/robot-menu.service.ts b/src/services/context-menu/robot-menu.service.ts new file mode 100644 index 0000000..0240a8a --- /dev/null +++ b/src/services/context-menu/robot-menu.service.ts @@ -0,0 +1,165 @@ +/** + * 机器人右键菜单服务 + * 处理机器人相关的菜单逻辑和操作 + */ + +import type { ParsedEventData } from './event-parser'; + +export interface RobotInfo { + id: string; + name: string; + status: 'online' | 'offline' | 'busy' | 'idle' | 'error'; + batteryLevel?: number; + currentTask?: string; + position?: { x: number; y: number }; +} + +export interface RobotMenuConfig { + menuType: 'robot' | 'default'; + robotInfo?: RobotInfo; +} + +/** + * 获取机器人信息 + * @param data 事件数据 + * @param robotService 机器人服务实例(可选) + * @returns 机器人信息 + */ +export function getRobotInfo(data: ParsedEventData, robotService?: any): RobotInfo | undefined { + const robotId = data.id; + + if (!robotId) { + console.warn('无法获取机器人ID'); + return undefined; + } + + // 如果提供了机器人服务,尝试使用它获取真实数据 + if (robotService && typeof robotService.getRobotById === 'function') { + try { + const robot = robotService.getRobotById(robotId); + if (robot) { + console.log('从RobotService获取到机器人信息:', robot); + return { + id: robot.id || robotId, + name: robot.name || robot.robotName || '未知机器人', + status: robot.status || 'offline', + batteryLevel: robot.batteryLevel || robot.battery, + currentTask: robot.currentTask || robot.task, + position: robot.position || { x: 0, y: 0 }, + }; + } + } catch (error) { + console.error('获取机器人信息失败:', error); + } + } + + // 回退到模拟数据 + console.log('使用模拟机器人数据'); + return { + id: robotId, + name: data.name || `机器人-${robotId}`, + status: 'online', + batteryLevel: 85, + currentTask: '空闲', + position: { x: 0, y: 0 }, + }; +} + +/** + * 获取机器人菜单配置 - 纯函数 + * @param data 事件数据 + * @param robotService 机器人服务实例(可选) + * @returns 菜单配置 + */ +export function getRobotMenuConfig(data: ParsedEventData, robotService?: any): RobotMenuConfig { + console.log(`处理机器人类型,机器人ID: ${data.id}`); + + const robotInfo = getRobotInfo(data, robotService); + + if (robotInfo) { + console.log('找到机器人信息:', robotInfo); + return { + menuType: 'robot' as const, + robotInfo, + }; + } else { + console.log('未找到机器人信息,显示默认菜单'); + return { + menuType: 'default' as const, + }; + } +} + +/** + * 执行机器人操作 + * @param action 操作类型 + * @param robotInfo 机器人信息 + * @param robotService 机器人服务实例(可选) + * @returns 操作结果 + */ +export async function executeRobotAction( + action: string, + robotInfo: RobotInfo, + robotService?: any +): Promise<{ success: boolean; message: string }> { + try { + console.log(`执行机器人操作: ${action}`, robotInfo); + + // 如果提供了机器人服务,使用真实API + if (robotService && typeof robotService.executeAction === 'function') { + const result = await robotService.executeAction(action, robotInfo.id); + return { + success: result.success || true, + message: result.message || `${action}操作成功`, + }; + } + + // 模拟操作 + await new Promise(resolve => setTimeout(resolve, 500)); // 模拟网络延迟 + + return { + success: true, + message: `机器人${robotInfo.name}的${action}操作成功`, + }; + } catch (error) { + console.error(`机器人${action}操作失败:`, error); + return { + success: false, + message: `机器人${action}操作失败: ${error}`, + }; + } +} + +/** + * 获取机器人状态显示文本 + * @param status 机器人状态 + * @returns 状态显示文本 + */ +export function getRobotStatusText(status: string): string { + const statusMap: Record = { + 'online': '在线', + 'offline': '离线', + 'busy': '忙碌', + 'idle': '空闲', + 'error': '错误', + }; + + return statusMap[status] || '未知'; +} + +/** + * 获取机器人状态颜色 + * @param status 机器人状态 + * @returns 状态颜色 + */ +export function getRobotStatusColor(status: string): string { + const colorMap: Record = { + 'online': '#52c41a', + 'offline': '#d9d9d9', + 'busy': '#1890ff', + 'idle': '#faad14', + 'error': '#ff4d4f', + }; + + return colorMap[status] || '#d9d9d9'; +} diff --git a/src/services/context-menu/state-manager.ts b/src/services/context-menu/state-manager.ts new file mode 100644 index 0000000..44059ec --- /dev/null +++ b/src/services/context-menu/state-manager.ts @@ -0,0 +1,80 @@ +/** + * 右键菜单状态管理器 + * 提供纯函数和状态管理工具,避免单例模式的问题 + */ + +export interface ContextMenuState { + visible: boolean; + x: number; + y: number; + menuType: 'default' | 'storage' | 'storage-background' | 'robot' | 'point' | 'area'; + eventData?: any; +} + +/** + * 右键菜单状态管理器 + * 使用组合式函数,避免单例模式 + */ +export function createContextMenuManager() { + let state: ContextMenuState = { + visible: false, + x: 0, + y: 0, + menuType: 'default', + }; + + const listeners: Array<(state: ContextMenuState) => void> = []; + + /** + * 订阅状态变化 + */ + function subscribe(listener: (state: ContextMenuState) => void) { + listeners.push(listener); + return () => { + const index = listeners.indexOf(listener); + if (index > -1) { + listeners.splice(index, 1); + } + }; + } + + /** + * 通知状态变化 + */ + function notify() { + listeners.forEach(listener => listener({ ...state })); + } + + /** + * 获取当前状态 + */ + function getState(): ContextMenuState { + return { ...state }; + } + + /** + * 更新状态 + */ + function setState(newState: Partial) { + state = { ...state, ...newState }; + notify(); + } + + /** + * 关闭菜单 + */ + function close() { + setState({ visible: false }); + } + + return { + subscribe, + getState, + setState, + close, + }; +} + +// 为了向后兼容,提供一个默认的状态管理器实例 +// 但建议在组件中创建独立的状态管理器 +export const defaultContextMenuManager = createContextMenuManager(); diff --git a/src/services/context-menu/storage-menu.service.ts b/src/services/context-menu/storage-menu.service.ts new file mode 100644 index 0000000..b1e99cc --- /dev/null +++ b/src/services/context-menu/storage-menu.service.ts @@ -0,0 +1,196 @@ +/** + * 库位右键菜单服务 + * 处理库位相关的菜单逻辑和操作 + */ + +import { MapPointType } from '@api/map'; + +import type { ParsedEventData } from './event-parser'; + +export interface StorageLocationInfo { + id: string; + name: string; + isOccupied: boolean; // 是否占用 + isLocked: boolean; // 是否锁定 + status: 'available' | 'occupied' | 'locked' | 'unknown'; +} + +export interface StorageMenuConfig { + menuType: 'storage-background' | 'default'; + storageLocations: StorageLocationInfo[]; +} + +/** + * 检查是否为动作点且有库位信息 + * @param data 事件数据 + * @returns 是否为动作点且有库位信息 + */ +export function checkIfActionPointWithStorage(data: ParsedEventData): boolean { + // 从pen数据中获取点信息 + const pen = data.pen; + if (!pen) { + console.log('无pen数据,不是动作点'); + return false; + } + + const pointInfo = pen.point as any; + if (!pointInfo) { + console.log('无point信息,不是动作点'); + return false; + } + + // 检查是否为动作点 + const isActionPoint = pointInfo.type === MapPointType.动作点; + if (!isActionPoint) { + console.log(`点类型为 ${pointInfo.type},不是动作点`); + return false; + } + + // 检查是否有库位信息 + const hasStorageLocations = pointInfo.associatedStorageLocations && + Array.isArray(pointInfo.associatedStorageLocations) && + pointInfo.associatedStorageLocations.length > 0; + + console.log(`动作点检查结果: 是动作点=${isActionPoint}, 有库位信息=${hasStorageLocations}`); + + return isActionPoint && hasStorageLocations; +} + +/** + * 查找指定点ID的所有库位信息 + * @param pointId 点ID + * @param storageLocationService StorageLocationService实例(可选) + * @returns 库位信息列表 + */ +export function findStorageLocationsByPointId(pointId: string, storageLocationService?: any): StorageLocationInfo[] { + console.log(`查找点 ${pointId} 关联的所有库位`); + + // 如果提供了StorageLocationService,尝试使用它获取真实数据 + if (storageLocationService && typeof storageLocationService.getLocationsByPointId === 'function') { + const locations = storageLocationService.getLocationsByPointId(pointId); + if (locations && locations.length > 0) { + console.log(`从StorageLocationService获取到 ${locations.length} 个库位:`, locations); + // 转换StorageLocationService的数据格式到我们的格式 + const convertedLocations = locations.map((location: any) => { + const converted = { + id: location.id || `storage-${pointId}-${location.layer_index || 0}`, + name: location.layer_name || location.locationName || location.name || '未知库位', + isOccupied: location.is_occupied || location.occupied || false, + isLocked: location.is_locked || location.locked || false, + status: (location.is_locked || location.locked) + ? 'locked' + : (location.is_occupied || location.occupied) + ? 'occupied' + : 'available', + }; + console.log(`转换库位数据: ${location.layer_name} -> ${converted.name}, 占用: ${converted.isOccupied}, 锁定: ${converted.isLocked}, 状态: ${converted.status}`); + return converted; + }); + return convertedLocations; + } + } + + // 回退到模拟数据 + console.log('使用模拟数据'); + const mockStorageLocations: StorageLocationInfo[] = [ + { + id: `storage-${pointId}-0`, + name: 'GSA-1-1-1', // 模拟库位名称 + isOccupied: false, + isLocked: false, + status: 'available', + }, + { + id: `storage-${pointId}-1`, + name: 'GSA-1-1-2', + isOccupied: true, + isLocked: false, + status: 'occupied', + }, + { + id: `storage-${pointId}-2`, + name: 'GSA-1-1-3', + isOccupied: false, + isLocked: true, + status: 'locked', + }, + ]; + + return mockStorageLocations; +} + +/** + * 获取点关联的库位信息列表 + * @param data 事件数据 + * @param storageLocationService StorageLocationService实例(可选) + * @returns 库位信息列表 + */ +export function getStorageLocationsForPoint(data: ParsedEventData, storageLocationService?: any): StorageLocationInfo[] { + const pointId = data.pointId || data.id; + + if (!pointId) { + console.warn('无法获取点ID,返回空库位列表'); + return []; + } + + return findStorageLocationsByPointId(pointId, storageLocationService); +} + +/** + * 获取库位菜单配置 - 纯函数 + * @param type 事件类型 + * @param data 事件数据 + * @param storageLocationService StorageLocationService实例(可选) + * @returns 菜单配置 + */ +export function getStorageMenuConfig(type: string, data: ParsedEventData, storageLocationService?: any): StorageMenuConfig { + switch (type) { + case 'storage-background': { + // 库位背景区域或单个库位区域:显示该点关联的所有库位信息 + console.log(`处理库位相关类型,点ID: ${data.pointId}`); + const allStorageLocations = getStorageLocationsForPoint(data, storageLocationService); + console.log(`找到 ${allStorageLocations.length} 个库位:`, allStorageLocations); + return { + menuType: 'storage-background' as const, + storageLocations: allStorageLocations, + }; + } + case 'storage': { + // 单个库位区域 - 也使用storage-background类型统一处理 + console.log(`处理单个库位类型,点ID: ${data.pointId}`); + const singleStorageLocations = getStorageLocationsForPoint(data, storageLocationService); + console.log(`找到 ${singleStorageLocations.length} 个库位:`, singleStorageLocations); + return { + menuType: 'storage-background' as const, + storageLocations: singleStorageLocations, + }; + } + case 'point': { + // 点区域:只有动作点且有库位信息时才显示库位菜单 + console.log(`处理点区域类型,点ID: ${data.pointId}`); + + // 检查是否为动作点且有库位信息 + const isActionPointWithStorage = checkIfActionPointWithStorage(data); + + if (isActionPointWithStorage) { + const pointStorageLocations = getStorageLocationsForPoint(data, storageLocationService); + console.log(`动作点找到 ${pointStorageLocations.length} 个库位:`, pointStorageLocations); + return { + menuType: 'storage-background' as const, + storageLocations: pointStorageLocations, + }; + } else { + console.log('非动作点或无库位信息,显示默认菜单'); + return { + menuType: 'default' as const, + storageLocations: [], + }; + } + } + default: + return { + menuType: 'default' as const, + storageLocations: [], + }; + } +} diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 11f02fe..323c78a 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1 +1,7 @@ /// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +}