feat: 优化视图状态管理,新增自动保存与恢复功能,提升用户体验
This commit is contained in:
parent
fbb266f97d
commit
7374ffd6ff
@ -54,8 +54,8 @@ onMounted(async () => {
|
|||||||
await readScene();
|
await readScene();
|
||||||
await editor.value?.initRobots();
|
await editor.value?.initRobots();
|
||||||
await monitorScene();
|
await monitorScene();
|
||||||
// 自动恢复视图状态
|
// 自动保存和恢复视图状态
|
||||||
await handleRestoreViewState();
|
await handleAutoSaveAndRestoreViewState();
|
||||||
});
|
});
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
client.value?.close();
|
client.value?.close();
|
||||||
@ -86,7 +86,7 @@ const selectRobot = (id: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 视图状态管理
|
// 视图状态管理
|
||||||
const { saveViewState, restoreViewState, isSaving } = useViewState();
|
const { saveViewState, autoSaveAndRestoreViewState, isSaving } = useViewState();
|
||||||
|
|
||||||
// 保存当前视图状态
|
// 保存当前视图状态
|
||||||
const handleSaveViewState = async () => {
|
const handleSaveViewState = async () => {
|
||||||
@ -100,15 +100,11 @@ const handleSaveViewState = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 恢复视图状态
|
// 自动保存和恢复视图状态
|
||||||
const handleRestoreViewState = async () => {
|
const handleAutoSaveAndRestoreViewState = async () => {
|
||||||
if (!editor.value) return;
|
if (!editor.value) return;
|
||||||
|
|
||||||
const restored = await restoreViewState(editor.value, props.sid, props.id);
|
await autoSaveAndRestoreViewState(editor.value, props.sid, props.id);
|
||||||
if (!restored) {
|
|
||||||
// 如果没有保存的状态,使用默认的centerView
|
|
||||||
editor.value.centerView();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -55,8 +55,8 @@ watch(
|
|||||||
() => props.id,
|
() => props.id,
|
||||||
async () => {
|
async () => {
|
||||||
await readScene();
|
await readScene();
|
||||||
// 在场景加载完成后恢复视图状态
|
// 在场景加载完成后自动保存和恢复视图状态
|
||||||
await handleRestoreViewState();
|
await handleAutoSaveAndRestoreViewState();
|
||||||
},
|
},
|
||||||
{ immediate: true, flush: 'post' },
|
{ immediate: true, flush: 'post' },
|
||||||
);
|
);
|
||||||
@ -131,7 +131,7 @@ const selectRobot = (id: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 视图状态管理
|
// 视图状态管理
|
||||||
const { saveViewState, restoreViewState, isSaving } = useViewState();
|
const { saveViewState, autoSaveAndRestoreViewState, isSaving } = useViewState();
|
||||||
|
|
||||||
// 保存当前视图状态
|
// 保存当前视图状态
|
||||||
const handleSaveViewState = async () => {
|
const handleSaveViewState = async () => {
|
||||||
@ -145,15 +145,11 @@ const handleSaveViewState = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 恢复视图状态
|
// 自动保存和恢复视图状态
|
||||||
const handleRestoreViewState = async () => {
|
const handleAutoSaveAndRestoreViewState = async () => {
|
||||||
if (!editor.value) return;
|
if (!editor.value) return;
|
||||||
|
|
||||||
const restored = await restoreViewState(editor.value, props.id);
|
await autoSaveAndRestoreViewState(editor.value, props.id);
|
||||||
if (!restored) {
|
|
||||||
// 如果没有保存的状态,使用默认的centerView
|
|
||||||
editor.value.centerView();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -38,78 +38,54 @@ export function useViewState() {
|
|||||||
* @param editor 编辑器服务实例
|
* @param editor 编辑器服务实例
|
||||||
*/
|
*/
|
||||||
const getCurrentViewState = (editor: EditorService): ViewState => {
|
const getCurrentViewState = (editor: EditorService): ViewState => {
|
||||||
console.groupCollapsed('🔍 [getCurrentViewState] - 获取当前视图状态');
|
// 获取当前缩放比例和中心点坐标
|
||||||
|
const scale = editor.store.data.scale || 1;
|
||||||
|
const { centerX, centerY } = calculateCenterPoint(editor);
|
||||||
|
|
||||||
|
const finalState: ViewState = {
|
||||||
|
scale,
|
||||||
|
centerX,
|
||||||
|
centerY,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return finalState;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算画布中心点坐标
|
||||||
|
* @param editor 编辑器服务实例
|
||||||
|
*/
|
||||||
|
const calculateCenterPoint = (editor: EditorService): { centerX: number; centerY: number } => {
|
||||||
// 1. 查找画布上所有的点位
|
// 1. 查找画布上所有的点位
|
||||||
const points = editor.find('point');
|
const points = editor.find('point');
|
||||||
console.log('📍 画布上的点位数量:', points.length);
|
|
||||||
|
|
||||||
// 打印所有点位的原始数据,以便检查
|
// 如果没有点位,返回当前视图中心
|
||||||
console.log(
|
if (!points.length) {
|
||||||
'📊 点位原始数据:',
|
return {
|
||||||
points.map((point) => ({
|
centerX: 0,
|
||||||
id: point.id,
|
centerY: 0,
|
||||||
x: point.x,
|
};
|
||||||
y: point.y,
|
}
|
||||||
name: point.name,
|
|
||||||
text: point.text,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
|
|
||||||
// 2. 计算所有点位的边界
|
// 2. 获取场景数据
|
||||||
|
const sceneData = getSceneData(editor);
|
||||||
|
const { ratio = 1, width: mapWidth = 0, height: mapHeight = 0 } = sceneData;
|
||||||
|
|
||||||
|
// 3. 计算所有点位的边界
|
||||||
let minX = Infinity;
|
let minX = Infinity;
|
||||||
let minY = Infinity;
|
let minY = Infinity;
|
||||||
let maxX = -Infinity;
|
let maxX = -Infinity;
|
||||||
let maxY = -Infinity;
|
let maxY = -Infinity;
|
||||||
|
|
||||||
// 尝试获取编辑器中的原始场景数据
|
|
||||||
// 编辑器中的#originalSceneData是Partial<StandardScene>类型
|
|
||||||
type SceneDataType = { ratio?: number; width?: number; height?: number };
|
|
||||||
let originalSceneData: SceneDataType = { ratio: 1, width: 0, height: 0 };
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 尝试通过编辑器的save方法获取场景数据
|
|
||||||
const sceneJson = editor.save();
|
|
||||||
if (sceneJson) {
|
|
||||||
const sceneData = JSON.parse(sceneJson);
|
|
||||||
// 提取需要的字段
|
|
||||||
originalSceneData = {
|
|
||||||
ratio: sceneData.ratio || 1,
|
|
||||||
width: sceneData.width || 0,
|
|
||||||
height: sceneData.height || 0,
|
|
||||||
};
|
|
||||||
console.log('🗺️ 从场景数据中提取的信息:', originalSceneData);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取编辑器原始数据失败:', error);
|
|
||||||
console.log('⚠️ 使用默认值');
|
|
||||||
}
|
|
||||||
|
|
||||||
const ratio = originalSceneData?.ratio || 1;
|
|
||||||
const mapWidth = originalSceneData?.width || 0;
|
|
||||||
const mapHeight = originalSceneData?.height || 0;
|
|
||||||
|
|
||||||
console.log('🗺️ 地图信息:', { ratio, mapWidth, mapHeight });
|
|
||||||
|
|
||||||
points.forEach((point) => {
|
points.forEach((point) => {
|
||||||
// 获取点位的实际坐标(画布上显示的坐标)
|
// 获取点位的实际坐标(画布上显示的坐标)
|
||||||
const { x: displayX = 0, y: displayY = 0 } = editor.getPointRect(point) ?? {};
|
const { x: displayX = 0, y: displayY = 0 } = editor.getPointRect(point) ?? {};
|
||||||
|
|
||||||
// 将显示坐标转换回原始坐标系统
|
// 将显示坐标转换回原始坐标系统
|
||||||
// 这里我们尝试逆转编辑器中的坐标转换逻辑
|
// 1. 从中心点原点转回左上角原点坐标
|
||||||
// 根据editor.service.ts中的#transformCoordinate方法
|
const topLeftX = (displayX + mapWidth / 2) * ratio;
|
||||||
|
const topLeftY = (mapHeight / 2 - displayY) * ratio;
|
||||||
// 1. 从显示坐标转回中心点原点坐标
|
|
||||||
const centerX = displayX;
|
|
||||||
const centerY = displayY;
|
|
||||||
|
|
||||||
// 2. 从中心点原点转回左上角原点坐标
|
|
||||||
const topLeftX = (centerX + mapWidth / 2) * ratio;
|
|
||||||
const topLeftY = (mapHeight / 2 - centerY) * ratio;
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`点位 ${point.id || point.name || '未命名'}: 显示坐标(${displayX}, ${displayY}), 原始坐标(${topLeftX.toFixed(1)}, ${topLeftY.toFixed(1)})`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 使用原始坐标系统的坐标计算边界
|
// 使用原始坐标系统的坐标计算边界
|
||||||
minX = Math.min(minX, topLeftX);
|
minX = Math.min(minX, topLeftX);
|
||||||
@ -118,22 +94,19 @@ export function useViewState() {
|
|||||||
maxY = Math.max(maxY, topLeftY);
|
maxY = Math.max(maxY, topLeftY);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 如果没有找到任何点位,使用默认值
|
// 如果没有有效的边界,返回原点
|
||||||
if (minX === Infinity || minY === Infinity || maxX === -Infinity || maxY === -Infinity) {
|
if (minX === Infinity || minY === Infinity || maxX === -Infinity || maxY === -Infinity) {
|
||||||
minX = 0;
|
return {
|
||||||
minY = 0;
|
centerX: 0,
|
||||||
maxX = 0;
|
centerY: 0,
|
||||||
maxY = 0;
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 计算边界的中心点(在原始坐标系统中)
|
// 4. 计算边界的中心点(在原始坐标系统中)
|
||||||
const centerX = (minX + maxX) / 2;
|
const centerX = (minX + maxX) / 2;
|
||||||
const centerY = (minY + maxY) / 2;
|
const centerY = (minY + maxY) / 2;
|
||||||
|
|
||||||
console.log('🔍 原始坐标系中的点位边界:', { minX, minY, maxX, maxY });
|
// 5. 将中心点从原始坐标系统转换回编辑器的显示坐标系统
|
||||||
console.log('🎯 原始坐标系中的中心点:', { centerX, centerY });
|
|
||||||
|
|
||||||
// 4. 将中心点从原始坐标系统转换回编辑器的显示坐标系统
|
|
||||||
// 先根据ratio进行缩放
|
// 先根据ratio进行缩放
|
||||||
const scaledCenterX = centerX / ratio;
|
const scaledCenterX = centerX / ratio;
|
||||||
const scaledCenterY = centerY / ratio;
|
const scaledCenterY = centerY / ratio;
|
||||||
@ -142,21 +115,33 @@ export function useViewState() {
|
|||||||
const displayCenterX = scaledCenterX - mapWidth / 2;
|
const displayCenterX = scaledCenterX - mapWidth / 2;
|
||||||
const displayCenterY = mapHeight / 2 - scaledCenterY;
|
const displayCenterY = mapHeight / 2 - scaledCenterY;
|
||||||
|
|
||||||
console.log('🎯 显示坐标系中的中心点:', { x: displayCenterX, y: displayCenterY });
|
return {
|
||||||
console.log('⚙️ 当前画布状态 (origin, scale):', {
|
|
||||||
origin: JSON.parse(JSON.stringify(editor.store.data.origin)),
|
|
||||||
scale: editor.store.data.scale,
|
|
||||||
});
|
|
||||||
|
|
||||||
const finalState: ViewState = {
|
|
||||||
scale: editor.store.data.scale || 1,
|
|
||||||
centerX: displayCenterX,
|
centerX: displayCenterX,
|
||||||
centerY: displayCenterY,
|
centerY: displayCenterY,
|
||||||
timestamp: Date.now(),
|
|
||||||
};
|
};
|
||||||
console.log('💾 保存的最终视图状态:', finalState);
|
};
|
||||||
console.groupEnd();
|
|
||||||
return finalState;
|
/**
|
||||||
|
* 获取场景数据
|
||||||
|
* @param editor 编辑器服务实例
|
||||||
|
*/
|
||||||
|
const getSceneData = (editor: EditorService): { ratio: number; width: number; height: number } => {
|
||||||
|
try {
|
||||||
|
const sceneJson = editor.save();
|
||||||
|
if (sceneJson) {
|
||||||
|
const sceneData = JSON.parse(sceneJson);
|
||||||
|
return {
|
||||||
|
ratio: sceneData.ratio || 1,
|
||||||
|
width: sceneData.width || 0,
|
||||||
|
height: sceneData.height || 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取编辑器原始数据失败:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回默认值
|
||||||
|
return { ratio: 1, width: 0, height: 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -186,78 +171,70 @@ export function useViewState() {
|
|||||||
* @param groupId 群组ID (可选)
|
* @param groupId 群组ID (可选)
|
||||||
*/
|
*/
|
||||||
const restoreViewState = async (editor: EditorService, sceneId: string, groupId?: string): Promise<boolean> => {
|
const restoreViewState = async (editor: EditorService, sceneId: string, groupId?: string): Promise<boolean> => {
|
||||||
console.groupCollapsed('🔄 [restoreViewState] - 恢复视图状态');
|
|
||||||
isRestoring.value = true;
|
isRestoring.value = true;
|
||||||
try {
|
try {
|
||||||
const storageKey = getStorageKey(sceneId, groupId);
|
const storageKey = getStorageKey(sceneId, groupId);
|
||||||
const savedState = localStorage.getItem(storageKey);
|
const savedState = localStorage.getItem(storageKey);
|
||||||
|
|
||||||
if (!savedState) {
|
if (!savedState) {
|
||||||
console.warn('⚠️ 未找到保存的视图状态');
|
|
||||||
console.groupEnd();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewState: ViewState = JSON.parse(savedState);
|
const viewState: ViewState = JSON.parse(savedState);
|
||||||
console.log('💾 读取到的视图状态:', viewState);
|
|
||||||
|
|
||||||
// 1. 设置缩放比例
|
// 1. 设置缩放比例
|
||||||
editor.scale(viewState.scale);
|
editor.scale(viewState.scale);
|
||||||
console.log(`🔎 1. 设置缩放比例为: ${viewState.scale}`);
|
|
||||||
|
|
||||||
// 2. 尝试跳转到画布上的一个随机点位
|
// 2. 如果有中心点坐标,创建临时点位并跳转
|
||||||
if (viewState.centerX !== undefined && viewState.centerY !== undefined) {
|
if (viewState.centerX !== undefined && viewState.centerY !== undefined) {
|
||||||
try {
|
await jumpToPosition(editor, viewState.centerX, viewState.centerY);
|
||||||
// 查找画布上所有的点位
|
|
||||||
const points = editor.find('point');
|
|
||||||
console.log('📍 画布上的点位数量:', points.length);
|
|
||||||
|
|
||||||
// 如果没有找到点位或跳转失败,创建一个临时点位
|
|
||||||
console.log('⚠️ 创建临时点位进行跳转');
|
|
||||||
const centerPointId = 'view-center-point-' + Date.now();
|
|
||||||
|
|
||||||
// 使用 addPoint 方法添加临时点位
|
|
||||||
// 这个方法会处理坐标转换和其他必要的设置
|
|
||||||
await editor.addPoint(
|
|
||||||
{ x: viewState.centerX, y: viewState.centerY }, // 点位坐标
|
|
||||||
1, // 点位类型:普通点
|
|
||||||
centerPointId, // 点位ID
|
|
||||||
);
|
|
||||||
|
|
||||||
// 设置点位为不可见
|
|
||||||
editor.setValue({ id: centerPointId, visible: false }, { render: false, history: false, doEvent: false });
|
|
||||||
|
|
||||||
// 使用 gotoById 方法跳转到临时点
|
|
||||||
editor.gotoById(centerPointId);
|
|
||||||
console.log('🎯 使用 gotoById 跳转到临时点:', { x: viewState.centerX, y: viewState.centerY });
|
|
||||||
|
|
||||||
// 延迟一段时间后移除临时点
|
|
||||||
setTimeout(() => {
|
|
||||||
const tempPen = editor.getPenById(centerPointId);
|
|
||||||
if (tempPen) {
|
|
||||||
editor.delete([tempPen]);
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('使用 gotoById 跳转失败:', error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 触发重绘
|
|
||||||
// editor.render();
|
|
||||||
console.log('🎨 3. 重绘画布完成');
|
|
||||||
|
|
||||||
console.groupEnd();
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('恢复视图状态失败:', error);
|
console.error('恢复视图状态失败:', error);
|
||||||
console.groupEnd();
|
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
isRestoring.value = false;
|
isRestoring.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跳转到指定位置
|
||||||
|
* @param editor 编辑器服务实例
|
||||||
|
* @param x X坐标
|
||||||
|
* @param y Y坐标
|
||||||
|
*/
|
||||||
|
const jumpToPosition = async (editor: EditorService, x: number, y: number): Promise<void> => {
|
||||||
|
try {
|
||||||
|
// 创建临时点位用于跳转
|
||||||
|
const centerPointId = 'view-center-point-' + Date.now();
|
||||||
|
|
||||||
|
// 添加临时点位
|
||||||
|
await editor.addPoint(
|
||||||
|
{ x, y }, // 点位坐标
|
||||||
|
1, // 点位类型:普通点
|
||||||
|
centerPointId, // 点位ID
|
||||||
|
);
|
||||||
|
|
||||||
|
// 设置点位为不可见
|
||||||
|
editor.setValue({ id: centerPointId, visible: false }, { render: false, history: false, doEvent: false });
|
||||||
|
|
||||||
|
// 跳转到临时点
|
||||||
|
editor.gotoById(centerPointId);
|
||||||
|
|
||||||
|
// 延迟移除临时点
|
||||||
|
setTimeout(() => {
|
||||||
|
const tempPen = editor.getPenById(centerPointId);
|
||||||
|
if (tempPen) {
|
||||||
|
editor.delete([tempPen]);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('跳转到指定位置失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查是否存在保存的视图状态
|
* 检查是否存在保存的视图状态
|
||||||
* @param sceneId 场景ID
|
* @param sceneId 场景ID
|
||||||
@ -293,6 +270,47 @@ export function useViewState() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动保存和恢复视图状态
|
||||||
|
* 如果本地没有保存的key,则先保存当前状态(用于定位地图),然后恢复
|
||||||
|
* 如果有key,则直接按原来逻辑恢复
|
||||||
|
* @param editor 编辑器服务实例
|
||||||
|
* @param sceneId 场景ID
|
||||||
|
* @param groupId 群组ID (可选)
|
||||||
|
*/
|
||||||
|
const autoSaveAndRestoreViewState = async (
|
||||||
|
editor: EditorService,
|
||||||
|
sceneId: string,
|
||||||
|
groupId?: string,
|
||||||
|
): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
const hasExistingState = hasViewState(sceneId, groupId);
|
||||||
|
|
||||||
|
if (!hasExistingState) {
|
||||||
|
// 先让编辑器居中显示,这样可以获得一个合适的默认视图状态
|
||||||
|
editor.centerView();
|
||||||
|
|
||||||
|
// 等待一小段时间让centerView完成
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
|
||||||
|
// 保存当前状态作为默认状态
|
||||||
|
await saveViewState(editor, sceneId, groupId);
|
||||||
|
|
||||||
|
// 然后恢复这个刚保存的状态(主要是为了触发定位逻辑)
|
||||||
|
return await restoreViewState(editor, sceneId, groupId);
|
||||||
|
} else {
|
||||||
|
// 直接恢复已有状态
|
||||||
|
return await restoreViewState(editor, sceneId, groupId);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('自动保存和恢复视图状态失败:', error);
|
||||||
|
|
||||||
|
// 如果出错,至少执行centerView作为fallback
|
||||||
|
editor.centerView();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isSaving,
|
isSaving,
|
||||||
isRestoring,
|
isRestoring,
|
||||||
@ -302,5 +320,6 @@ export function useViewState() {
|
|||||||
clearViewState,
|
clearViewState,
|
||||||
getViewStateInfo,
|
getViewStateInfo,
|
||||||
getCurrentViewState,
|
getCurrentViewState,
|
||||||
|
autoSaveAndRestoreViewState,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,8 @@ export default ({ mode }: Record<string, unknown>) =>
|
|||||||
chunkSizeWarningLimit: 2000,
|
chunkSizeWarningLimit: 2000,
|
||||||
},
|
},
|
||||||
esbuild: {
|
esbuild: {
|
||||||
drop: mode === 'production' ? ['console'] : [],
|
drop: mode === 'production' ? [] : [],
|
||||||
|
// drop: mode === 'production' ? [] : [],
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
port: 8888,
|
port: 8888,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user