feat(view-state): 重构多楼层视图状态管理,提取场景加载逻辑并支持楼层独立的视图状态保存与恢复
This commit is contained in:
parent
b1d77ae3c8
commit
972a63e738
@ -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
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user