From 972a63e73819501ca8856d1ab9479a3bfcdab877 Mon Sep 17 00:00:00 2001 From: xudan Date: Wed, 17 Dec 2025 11:01:15 +0800 Subject: [PATCH] =?UTF-8?q?feat(view-state):=20=E9=87=8D=E6=9E=84=E5=A4=9A?= =?UTF-8?q?=E6=A5=BC=E5=B1=82=E8=A7=86=E5=9B=BE=E7=8A=B6=E6=80=81=E7=AE=A1?= =?UTF-8?q?=E7=90=86=EF=BC=8C=E6=8F=90=E5=8F=96=E5=9C=BA=E6=99=AF=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E9=80=BB=E8=BE=91=E5=B9=B6=E6=94=AF=E6=8C=81=E6=A5=BC?= =?UTF-8?q?=E5=B1=82=E7=8B=AC=E7=AB=8B=E7=9A=84=E8=A7=86=E5=9B=BE=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E4=BF=9D=E5=AD=98=E4=B8=8E=E6=81=A2=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/movement-supervision.vue | 124 ++++++++++++++++++++--------- 1 file changed, 88 insertions(+), 36 deletions(-) diff --git a/src/pages/movement-supervision.vue b/src/pages/movement-supervision.vue index e182122..0c88056 100644 --- a/src/pages/movement-supervision.vue +++ b/src/pages/movement-supervision.vue @@ -64,6 +64,37 @@ const normalizeSceneJson = (raw: unknown): unknown => { return result; }; +const loadSceneFromJson = async (raw: unknown) => { + const sceneJson = normalizeSceneJson(raw); + + // 检查返回的json是数组(多楼层)还是对象(单楼层) + if (Array.isArray(sceneJson)) { + if (sceneJson.length > 0) { + // 多楼层 + floorScenes.value = sceneJson as any[]; + currentFloorIndex.value = 0; // 默认显示第一层 + await editor.value?.load(floorScenes.value[0]); + } else { + // 空数组,当作空场景处理 + message.warn('场景文件为空数组'); + floorScenes.value = [{}]; + currentFloorIndex.value = 0; + await editor.value?.load('{}'); // 加载空场景 + } + } else if (sceneJson && typeof sceneJson === 'object' && Object.keys(sceneJson as any).length > 0) { + // 单楼层,包装成数组以统一处理 + floorScenes.value = [sceneJson]; + currentFloorIndex.value = 0; + await editor.value?.load(sceneJson as any); + } else { + // 空对象或null/undefined,也当作空场景处理 + message.warn('场景文件为空或格式不正确'); + floorScenes.value = [{}]; + currentFloorIndex.value = 0; + await editor.value?.load('{}'); // 加载空场景 + } +}; + //#region Playback Mode const mode = computed(() => (route.path.includes('/playback') ? 'playback' : 'live')); const isPlaybackMode = computed(() => mode.value === 'playback'); @@ -158,13 +189,16 @@ watch(playback.sceneJson, async (newJson) => { if (newJson) { isSceneLoading.value = true; try { - await editor.value?.load(newJson); + await loadSceneFromJson(newJson); // [移除] 不再需要在这里初始化机器人,交由 onFirstAmrData 回调处理 // await editor.value?.initRobots(); // [电梯映射] 回放模式场景加载完成后构建电梯映射 console.log('[回放模式] 构建电梯设备映射'); elevatorStore.refreshMapping(); + + // 回放模式下 load 会重置缩放/视角,场景加载完成后再恢复,避免覆盖“保存比例” + await handleAutoSaveAndRestoreViewState(); } finally { isSceneLoading.value = false; } @@ -260,34 +294,7 @@ provide(EDITOR_KEY, editor); const readScene = async () => { const res = props.id ? await getSceneByGroupId(props.id, props.sid) : await getSceneById(props.sid); title.value = res?.label ?? ''; - const sceneJson = normalizeSceneJson(res?.json); - - // 检查返回的json是数组(多楼层)还是对象(单楼层) - if (Array.isArray(sceneJson)) { - if (sceneJson.length > 0) { - // 多楼层 - floorScenes.value = sceneJson as any[]; - currentFloorIndex.value = 0; // 默认显示第一层 - await editor.value?.load(floorScenes.value[0]); - } else { - // 空数组,当作空场景处理 - message.warn('场景文件为空数组'); - floorScenes.value = [{}]; - currentFloorIndex.value = 0; - await editor.value?.load('{}'); // 加载空场景 - } - } else if (sceneJson && typeof sceneJson === 'object' && Object.keys(sceneJson as any).length > 0) { - // 单楼层,包装成数组以统一处理 - floorScenes.value = [sceneJson]; - currentFloorIndex.value = 0; - await editor.value?.load(sceneJson as any); - } else { - // 空对象或null/undefined,也当作空场景处理 - message.warn('场景文件为空或格式不正确'); - floorScenes.value = [{}]; - currentFloorIndex.value = 0; - await editor.value?.load('{}'); // 加载空场景 - } + await loadSceneFromJson(res?.json); }; /** @@ -500,6 +507,9 @@ onMounted(async () => { elevatorStore.refreshMapping(); await monitorScene(); + + // 自动保存和恢复视图状态(实时模式:readScene 已完成加载) + await handleAutoSaveAndRestoreViewState(); } else { console.log('[回放模式] 初始化回放模式'); playback.connect(props.sid); @@ -515,8 +525,6 @@ onMounted(async () => { // await editor.value?.initRobots(); storageLocationService.value?.startMonitoring({ interval: 1 }); - // 自动保存和恢复视图状态 - await handleAutoSaveAndRestoreViewState(); // 设置编辑器服务 if (editor.value) { @@ -705,7 +713,44 @@ const selectRobot = (id: string) => { //#endregion //#region 视图状态管理 -const { saveViewState, autoSaveAndRestoreViewState, isSaving, jumpToPosition, calculateCenterPoint } = useViewState(); +const { + saveViewState, + restoreViewState, + autoSaveAndRestoreViewState, + isSaving, + jumpToPosition, + calculateCenterPoint, +} = useViewState(); + +const getViewStateSceneId = (): string => { + if (isMultiFloor.value) { + return `${props.sid}/${currentFloorIndex.value + 1}`; + } + return props.sid; +}; + +const getViewStateStorageKey = (sceneId: string, groupId?: string): string => { + return groupId ? `view-state-${groupId}-${sceneId}` : `view-state-${sceneId}`; +}; + +const migrateLegacyViewStateIfNeeded = () => { + if (!isMultiFloor.value) return; + + try { + const groupId = props.id; + const legacyKey = getViewStateStorageKey(props.sid, groupId); + const floorKey = getViewStateStorageKey(getViewStateSceneId(), groupId); + if (legacyKey === floorKey) return; + + const legacy = localStorage.getItem(legacyKey); + if (!legacy) return; + if (localStorage.getItem(floorKey)) return; + + localStorage.setItem(floorKey, legacy); + } catch { + // ignore + } +}; /** * 保存当前视图状态 @@ -714,7 +759,8 @@ const handleSaveViewState = async () => { if (!editor.value) return; try { - await saveViewState(editor.value, props.sid, props.id); + migrateLegacyViewStateIfNeeded(); + await saveViewState(editor.value, getViewStateSceneId(), props.id); message.success('视图状态保存成功'); } catch { message.error('视图状态保存失败'); @@ -727,7 +773,8 @@ const handleSaveViewState = async () => { const handleAutoSaveAndRestoreViewState = async () => { if (!editor.value) return; - await autoSaveAndRestoreViewState(editor.value, props.sid, props.id); + migrateLegacyViewStateIfNeeded(); + await autoSaveAndRestoreViewState(editor.value, getViewStateSceneId(), props.id); }; //#endregion @@ -735,13 +782,18 @@ const handleAutoSaveAndRestoreViewState = async () => { const handleFloorChange = async (value: any) => { const newFloorIndex = value as number; if (editor.value && floorScenes.value[newFloorIndex]) { + currentFloorIndex.value = newFloorIndex; // 1) 先加载新楼层 await editor.value.load(floorScenes.value[newFloorIndex]); // 2) 立即定位到新楼层中心,提升交互反馈(不要等待后续WS/机器人初始化) try { - const { centerX, centerY } = calculateCenterPoint(editor.value); - await jumpToPosition(editor.value, centerX, centerY, false, 0.05); + migrateLegacyViewStateIfNeeded(); + const restored = await restoreViewState(editor.value, getViewStateSceneId(), props.id); + if (!restored) { + const { centerX, centerY } = calculateCenterPoint(editor.value); + await jumpToPosition(editor.value, centerX, centerY, false, 0.05); + } } catch { // ignore }