feat(view-state): 重构多楼层视图状态管理,提取场景加载逻辑并支持楼层独立的视图状态保存与恢复
This commit is contained in:
parent
b1d77ae3c8
commit
972a63e738
@ -64,6 +64,37 @@ const normalizeSceneJson = (raw: unknown): unknown => {
|
|||||||
return result;
|
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
|
//#region Playback Mode
|
||||||
const mode = computed(() => (route.path.includes('/playback') ? 'playback' : 'live'));
|
const mode = computed(() => (route.path.includes('/playback') ? 'playback' : 'live'));
|
||||||
const isPlaybackMode = computed(() => mode.value === 'playback');
|
const isPlaybackMode = computed(() => mode.value === 'playback');
|
||||||
@ -158,13 +189,16 @@ watch(playback.sceneJson, async (newJson) => {
|
|||||||
if (newJson) {
|
if (newJson) {
|
||||||
isSceneLoading.value = true;
|
isSceneLoading.value = true;
|
||||||
try {
|
try {
|
||||||
await editor.value?.load(newJson);
|
await loadSceneFromJson(newJson);
|
||||||
// [移除] 不再需要在这里初始化机器人,交由 onFirstAmrData 回调处理
|
// [移除] 不再需要在这里初始化机器人,交由 onFirstAmrData 回调处理
|
||||||
// await editor.value?.initRobots();
|
// await editor.value?.initRobots();
|
||||||
|
|
||||||
// [电梯映射] 回放模式场景加载完成后构建电梯映射
|
// [电梯映射] 回放模式场景加载完成后构建电梯映射
|
||||||
console.log('[回放模式] 构建电梯设备映射');
|
console.log('[回放模式] 构建电梯设备映射');
|
||||||
elevatorStore.refreshMapping();
|
elevatorStore.refreshMapping();
|
||||||
|
|
||||||
|
// 回放模式下 load 会重置缩放/视角,场景加载完成后再恢复,避免覆盖“保存比例”
|
||||||
|
await handleAutoSaveAndRestoreViewState();
|
||||||
} finally {
|
} finally {
|
||||||
isSceneLoading.value = false;
|
isSceneLoading.value = false;
|
||||||
}
|
}
|
||||||
@ -260,34 +294,7 @@ provide(EDITOR_KEY, editor);
|
|||||||
const readScene = async () => {
|
const readScene = async () => {
|
||||||
const res = props.id ? await getSceneByGroupId(props.id, props.sid) : await getSceneById(props.sid);
|
const res = props.id ? await getSceneByGroupId(props.id, props.sid) : await getSceneById(props.sid);
|
||||||
title.value = res?.label ?? '';
|
title.value = res?.label ?? '';
|
||||||
const sceneJson = normalizeSceneJson(res?.json);
|
await loadSceneFromJson(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('{}'); // 加载空场景
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -500,6 +507,9 @@ onMounted(async () => {
|
|||||||
elevatorStore.refreshMapping();
|
elevatorStore.refreshMapping();
|
||||||
|
|
||||||
await monitorScene();
|
await monitorScene();
|
||||||
|
|
||||||
|
// 自动保存和恢复视图状态(实时模式:readScene 已完成加载)
|
||||||
|
await handleAutoSaveAndRestoreViewState();
|
||||||
} else {
|
} else {
|
||||||
console.log('[回放模式] 初始化回放模式');
|
console.log('[回放模式] 初始化回放模式');
|
||||||
playback.connect(props.sid);
|
playback.connect(props.sid);
|
||||||
@ -515,8 +525,6 @@ onMounted(async () => {
|
|||||||
// await editor.value?.initRobots();
|
// await editor.value?.initRobots();
|
||||||
|
|
||||||
storageLocationService.value?.startMonitoring({ interval: 1 });
|
storageLocationService.value?.startMonitoring({ interval: 1 });
|
||||||
// 自动保存和恢复视图状态
|
|
||||||
await handleAutoSaveAndRestoreViewState();
|
|
||||||
|
|
||||||
// 设置编辑器服务
|
// 设置编辑器服务
|
||||||
if (editor.value) {
|
if (editor.value) {
|
||||||
@ -705,7 +713,44 @@ const selectRobot = (id: string) => {
|
|||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region 视图状态管理
|
//#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;
|
if (!editor.value) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await saveViewState(editor.value, props.sid, props.id);
|
migrateLegacyViewStateIfNeeded();
|
||||||
|
await saveViewState(editor.value, getViewStateSceneId(), props.id);
|
||||||
message.success('视图状态保存成功');
|
message.success('视图状态保存成功');
|
||||||
} catch {
|
} catch {
|
||||||
message.error('视图状态保存失败');
|
message.error('视图状态保存失败');
|
||||||
@ -727,7 +773,8 @@ const handleSaveViewState = async () => {
|
|||||||
const handleAutoSaveAndRestoreViewState = async () => {
|
const handleAutoSaveAndRestoreViewState = async () => {
|
||||||
if (!editor.value) return;
|
if (!editor.value) return;
|
||||||
|
|
||||||
await autoSaveAndRestoreViewState(editor.value, props.sid, props.id);
|
migrateLegacyViewStateIfNeeded();
|
||||||
|
await autoSaveAndRestoreViewState(editor.value, getViewStateSceneId(), props.id);
|
||||||
};
|
};
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
@ -735,13 +782,18 @@ const handleAutoSaveAndRestoreViewState = async () => {
|
|||||||
const handleFloorChange = async (value: any) => {
|
const handleFloorChange = async (value: any) => {
|
||||||
const newFloorIndex = value as number;
|
const newFloorIndex = value as number;
|
||||||
if (editor.value && floorScenes.value[newFloorIndex]) {
|
if (editor.value && floorScenes.value[newFloorIndex]) {
|
||||||
|
currentFloorIndex.value = newFloorIndex;
|
||||||
// 1) 先加载新楼层
|
// 1) 先加载新楼层
|
||||||
await editor.value.load(floorScenes.value[newFloorIndex]);
|
await editor.value.load(floorScenes.value[newFloorIndex]);
|
||||||
|
|
||||||
// 2) 立即定位到新楼层中心,提升交互反馈(不要等待后续WS/机器人初始化)
|
// 2) 立即定位到新楼层中心,提升交互反馈(不要等待后续WS/机器人初始化)
|
||||||
try {
|
try {
|
||||||
|
migrateLegacyViewStateIfNeeded();
|
||||||
|
const restored = await restoreViewState(editor.value, getViewStateSceneId(), props.id);
|
||||||
|
if (!restored) {
|
||||||
const { centerX, centerY } = calculateCenterPoint(editor.value);
|
const { centerX, centerY } = calculateCenterPoint(editor.value);
|
||||||
await jumpToPosition(editor.value, centerX, centerY, false, 0.05);
|
await jumpToPosition(editor.value, centerX, centerY, false, 0.05);
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user