diff --git a/src/hooks/usePlaybackWebSocket.ts b/src/hooks/usePlaybackWebSocket.ts index 00c1460..c3a3c8a 100644 --- a/src/hooks/usePlaybackWebSocket.ts +++ b/src/hooks/usePlaybackWebSocket.ts @@ -8,7 +8,7 @@ import ws from '../services/ws'; // Define the structure of WebSocket messages for playback type PlaybackMessage = { - type: 'AMR' | 'SCENE'; + type: 'AMR' | 'SCENE' | 'LOCATION'; timestamp: number; data: any; }; @@ -18,6 +18,7 @@ type PlaybackSceneData = { totalDuration: number; }; +// Hook's return type definition // Hook's return type definition export interface UsePlaybackWebSocketReturn { // Connection status @@ -31,6 +32,7 @@ export interface UsePlaybackWebSocketReturn { // Data for rendering currentSceneId: Ref; sceneJson: Ref; + locationData: Ref; // 新增:用于存储库位数据 // Methods connect: (historySceneId: string) => void; @@ -50,6 +52,7 @@ export function usePlaybackWebSocket(editorService: ShallowRef(null); const sceneJson = ref(null); + const locationData = ref([]); // 新增:库位数据的响应式引用 const { calculateCenterPoint, jumpToPosition } = useViewState(); @@ -100,6 +103,9 @@ export function usePlaybackWebSocket(editorService: ShallowRef { latestRobotData.set(robot.id, robot); }); + } else if (msg.type === 'LOCATION') { + // 新增:处理库位数据 + locationData.value = msg.data; } }; @@ -230,6 +236,7 @@ export function usePlaybackWebSocket(editorService: ShallowRef { if (newMode === 'live') { playback.disconnect(); + storageLocationService.value?.setPlaybackMode(false); await monitorScene(); + storageLocationService.value?.startMonitoring({ interval: 1 }); } else { client.value?.close(); + storageLocationService.value?.setPlaybackMode(true); playback.connect(props.sid); // Connect once using props.sid } }); @@ -127,6 +130,44 @@ watch(playback.sceneJson, async (newJson) => { } }); +watch(playback.locationData, (newData) => { + if (newData) { + storageLocationService.value?.handlePlaybackData(newData); + } +}); + +let mockLocationInterval: NodeJS.Timeout; +onMounted(() => { + // 仅在回放模式下启动模拟数据 + if (isPlaybackMode.value) { + let occupied = false; + mockLocationInterval = setInterval(() => { + const mockData = { + type: 'LOCATION', + timestamp: playback.currentTime.value, + data: [ + { + id: '666', + label: '1-1', + stationName: 'AP190', + occupied: occupied, + locked: false, + disabled: false, + emptyTray: false, + }, + ], + }; + // 模拟数据推送 + storageLocationService.value?.handlePlaybackData(mockData.data); + occupied = !occupied; // 切换状态 + }, 2000); // 每2秒更新一次 + } +}); + +onUnmounted(() => { + clearInterval(mockLocationInterval); +}); + watch(selectedDate, (date) => { if (date) { const startOfDayTimestamp = date.startOf('day').valueOf(); diff --git a/src/services/storage-location.service.ts b/src/services/storage-location.service.ts index d6d21f8..becdc5e 100644 --- a/src/services/storage-location.service.ts +++ b/src/services/storage-location.service.ts @@ -115,6 +115,7 @@ export class StorageLocationService { private config: StorageLocationConfig; // 渲染调度标记,避免在高频事件中重复 render private renderScheduled = false; + private isPlaybackMode = false; constructor(editor: EditorService, sceneId: string, config?: Partial) { this.editor = editor; @@ -472,6 +473,9 @@ export class StorageLocationService { * @param options 监控选项 */ async startMonitoring(options: { interval?: number } = {}) { + if (this.isPlaybackMode) { + return; + } this.stopMonitoring(); // 监控库位状态 @@ -765,6 +769,70 @@ export class StorageLocationService { this.storageLocations.value.clear(); this.editor = null; } + + setPlaybackMode(isPlayback: boolean) { + this.isPlaybackMode = isPlayback; + } + + handlePlaybackData(data: any[]) { + const stationToPointIdMap = this.buildStationToPointIdMap(); + + const locationsByPointId = new Map(); + data.forEach((location) => { + const pointId = stationToPointIdMap.get(location.stationName); + if (pointId) { + if (!locationsByPointId.has(pointId)) { + locationsByPointId.set(pointId, []); + } + locationsByPointId.get(pointId)!.push({ + ...location, + is_occupied: location.occupied, + is_locked: location.locked, + is_disabled: location.disabled, + is_empty_tray: location.emptyTray, + layer_name: location.label, + station_name: location.stationName, + }); + } + }); + + const changedPointIds = this.getChangedPointIds(locationsByPointId); + if (changedPointIds.size === 0) { + return; + } + + this.storageLocations.value = locationsByPointId; + + storageStateMap.clear(); + for (const [pointId, list] of locationsByPointId.entries()) { + const inner = new Map(); + list.forEach((loc) => { + inner.set(loc.layer_name, { + occupied: loc.is_occupied, + locked: loc.is_locked, + disabled: loc.is_disabled, + isEmptyTray: loc.is_empty_tray, + }); + }); + storageStateMap.set(pointId, inner); + } + + this.updatePointBorderColorsOptimized(changedPointIds); + + for (const pointId of changedPointIds) { + const pointPen = this.editor?.getPenById(pointId); + const storageNames = pointPen?.point?.associatedStorageLocations; + if (pointPen && Array.isArray(storageNames) && storageNames.length > 0) { + const existing = this.getStorageLocationPens(pointId); + if (existing.length > 0) { + this.update(pointId, storageNames); + } else { + this.create(pointId, storageNames); + } + } + } + this.scheduleRender(); + } } // ==================== 新增:简化的库位更新API ====================