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
+}