From 3b6f4294426602a0dae270ba48c45e55fa04fd5b Mon Sep 17 00:00:00 2001 From: xudan Date: Tue, 1 Jul 2025 19:18:00 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E7=94=BB=E5=B8=83?= =?UTF-8?q?=E8=A7=86=E5=9B=BE=E7=8A=B6=E6=80=81=E7=AE=A1=E7=90=86Hook?= =?UTF-8?q?=EF=BC=8C=E6=94=AF=E6=8C=81=E4=BF=9D=E5=AD=98=E3=80=81=E6=81=A2?= =?UTF-8?q?=E5=A4=8D=E8=A7=86=E5=9B=BE=E7=8A=B6=E6=80=81=E5=8F=8A=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E8=A7=86=E5=9B=BE=E7=8A=B6=E6=80=81=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/services/useViewState.ts | 306 +++++++++++++++++++++++++++++++++++ 1 file changed, 306 insertions(+) create mode 100644 src/services/useViewState.ts diff --git a/src/services/useViewState.ts b/src/services/useViewState.ts new file mode 100644 index 0000000..ca084ea --- /dev/null +++ b/src/services/useViewState.ts @@ -0,0 +1,306 @@ +import { ref } from 'vue'; + +import type { EditorService } from './editor.service'; + +/** + * 画布视图状态接口 + */ +interface ViewState { + /** 缩放比例 */ + scale: number; + /** 中心点X坐标 */ + centerX: number; + /** 中心点Y坐标 */ + centerY: number; + /** 保存时间戳 */ + timestamp: number; +} + +/** + * 画布视图状态管理Hook + * 用于保存和恢复画布的缩放比例和中心坐标 + */ +export function useViewState() { + const isSaving = ref(false); + const isRestoring = ref(false); + + /** + * 生成localStorage的key + * @param sceneId 场景ID + * @param groupId 群组ID (可选) + */ + const getStorageKey = (sceneId: string, groupId?: string): string => { + return groupId ? `view-state-${groupId}-${sceneId}` : `view-state-${sceneId}`; + }; + + /** + * 获取当前画布的视图状态 + * @param editor 编辑器服务实例 + */ + const getCurrentViewState = (editor: EditorService): ViewState => { + console.groupCollapsed('🔍 [getCurrentViewState] - 获取当前视图状态'); + + // 1. 查找画布上所有的点位 + const points = editor.find('point'); + console.log('📍 画布上的点位数量:', points.length); + + // 打印所有点位的原始数据,以便检查 + console.log( + '📊 点位原始数据:', + points.map((point) => ({ + id: point.id, + x: point.x, + y: point.y, + name: point.name, + text: point.text, + })), + ); + + // 2. 计算所有点位的边界 + let minX = Infinity; + let minY = Infinity; + let maxX = -Infinity; + let maxY = -Infinity; + + // 尝试获取编辑器中的原始场景数据 + // 编辑器中的#originalSceneData是Partial类型 + 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) => { + // 获取点位的实际坐标(画布上显示的坐标) + const { x: displayX = 0, y: displayY = 0 } = editor.getPointRect(point) ?? {}; + + // 将显示坐标转换回原始坐标系统 + // 这里我们尝试逆转编辑器中的坐标转换逻辑 + // 根据editor.service.ts中的#transformCoordinate方法 + + // 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); + minY = Math.min(minY, topLeftY); + maxX = Math.max(maxX, topLeftX); + maxY = Math.max(maxY, topLeftY); + }); + + // 如果没有找到任何点位,使用默认值 + if (minX === Infinity || minY === Infinity || maxX === -Infinity || maxY === -Infinity) { + minX = 0; + minY = 0; + maxX = 0; + maxY = 0; + } + + // 3. 计算边界的中心点(在原始坐标系统中) + const centerX = (minX + maxX) / 2; + const centerY = (minY + maxY) / 2; + + console.log('🔍 原始坐标系中的点位边界:', { minX, minY, maxX, maxY }); + console.log('🎯 原始坐标系中的中心点:', { centerX, centerY }); + + // 4. 将中心点从原始坐标系统转换回编辑器的显示坐标系统 + // 先根据ratio进行缩放 + const scaledCenterX = centerX / ratio; + const scaledCenterY = centerY / ratio; + + // 再进行坐标系转换:左上角原点 -> 中心点原点 + const displayCenterX = scaledCenterX - mapWidth / 2; + const displayCenterY = mapHeight / 2 - scaledCenterY; + + console.log('🎯 显示坐标系中的中心点:', { x: displayCenterX, y: displayCenterY }); + 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, + centerY: displayCenterY, + timestamp: Date.now(), + }; + console.log('💾 保存的最终视图状态:', finalState); + console.groupEnd(); + return finalState; + }; + + /** + * 保存当前视图状态到localStorage + * @param editor 编辑器服务实例 + * @param sceneId 场景ID + * @param groupId 群组ID (可选) + */ + const saveViewState = async (editor: EditorService, sceneId: string, groupId?: string): Promise => { + isSaving.value = true; + try { + const viewState = getCurrentViewState(editor); + const storageKey = getStorageKey(sceneId, groupId); + localStorage.setItem(storageKey, JSON.stringify(viewState)); + } catch (error) { + console.error('保存视图状态失败:', error); + throw error; + } finally { + isSaving.value = false; + } + }; + + /** + * 从localStorage恢复视图状态 + * @param editor 编辑器服务实例 + * @param sceneId 场景ID + * @param groupId 群组ID (可选) + */ + const restoreViewState = async (editor: EditorService, sceneId: string, groupId?: string): Promise => { + console.groupCollapsed('🔄 [restoreViewState] - 恢复视图状态'); + isRestoring.value = true; + try { + const storageKey = getStorageKey(sceneId, groupId); + const savedState = localStorage.getItem(storageKey); + + if (!savedState) { + console.warn('⚠️ 未找到保存的视图状态'); + console.groupEnd(); + return false; + } + + const viewState: ViewState = JSON.parse(savedState); + console.log('💾 读取到的视图状态:', viewState); + + // 1. 设置缩放比例 + editor.scale(viewState.scale); + console.log(`🔎 1. 设置缩放比例为: ${viewState.scale}`); + + // 2. 尝试跳转到画布上的一个随机点位 + if (viewState.centerX !== undefined && viewState.centerY !== undefined) { + try { + // 查找画布上所有的点位 + 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; + } catch (error) { + console.error('恢复视图状态失败:', error); + console.groupEnd(); + return false; + } finally { + isRestoring.value = false; + } + }; + + /** + * 检查是否存在保存的视图状态 + * @param sceneId 场景ID + * @param groupId 群组ID (可选) + */ + const hasViewState = (sceneId: string, groupId?: string): boolean => { + const storageKey = getStorageKey(sceneId, groupId); + return localStorage.getItem(storageKey) !== null; + }; + + /** + * 清除保存的视图状态 + * @param sceneId 场景ID + * @param groupId 群组ID (可选) + */ + const clearViewState = (sceneId: string, groupId?: string): void => { + const storageKey = getStorageKey(sceneId, groupId); + localStorage.removeItem(storageKey); + }; + + /** + * 获取保存的视图状态信息 + * @param sceneId 场景ID + * @param groupId 群组ID (可选) + */ + const getViewStateInfo = (sceneId: string, groupId?: string): ViewState | null => { + try { + const storageKey = getStorageKey(sceneId, groupId); + const savedState = localStorage.getItem(storageKey); + return savedState ? JSON.parse(savedState) : null; + } catch { + return null; + } + }; + + return { + isSaving, + isRestoring, + saveViewState, + restoreViewState, + hasViewState, + clearViewState, + getViewStateInfo, + getCurrentViewState, + }; +}