web-map/src/services/context-menu.service.ts

574 lines
15 KiB
TypeScript
Raw Normal View History

/**
* -
*
*/
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<string, unknown>; // 原始pen对象数据
}
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<ContextMenuState>) {
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<string, unknown>): ParsedEventData {
// 从penData中提取pen数据和事件信息
const pen = penData.pen as Record<string, unknown>;
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<string, unknown> }).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 pointStorageLocations = getStorageLocationsForPoint(data, storageLocationService);
console.log(`找到 ${pointStorageLocations.length} 个库位:`, pointStorageLocations);
return {
menuType: 'storage-background' as const,
storageLocations: pointStorageLocations,
};
}
default:
return {
menuType: 'default' as const,
storageLocations: [],
};
}
}
/**
* 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<typeof createContextMenuManager>
) {
// 阻止默认右键菜单
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<string, unknown>,
manager: ReturnType<typeof createContextMenuManager>,
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();