import { watch, type WatchStopHandle, ref } from 'vue'; import { useRoute } from 'vue-router'; import { unByKey } from 'ol/Observable'; import dayjs from 'dayjs'; import { MapClass } from '@/components/gis/map.class'; import { getMapConfig, layerConfig2Flat } from '@/components/gis/gisUtils'; import { useMapConfigStore } from '@/modules/map/stores/map-config.store'; import { useMapDataStore } from '@/modules/map/stores/map-data.store'; import { useMapViewStore } from '@/modules/map/stores/map-view.store'; import { useMapStore } from '@/store/modules/map'; import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent'; type InitializeOptions = { container: HTMLElement; popupContainer?: HTMLDivElement | null; pageKey: string; }; type LoadPageOptions = { pageKey: string; isInitialLoad?: boolean; }; type ApplyLayerSelectionOptions = { checkedKeys: string[]; triggerKey?: string; checked?: boolean; }; const SYSTEM_ID = 'qgc'; const HYDRO_DYNAMIC_LAYER_KEYS = [ 'dw_point', 'stinfo_video_point', 'stinfo_gjllz_point', 'wt_point', 'wq_ownWq_point', 'wq_countryWq_point', 'fp_point', 'eq_point', 'fb_point', 'vp_point', 'va_point', 'sg_point' ]; const ENG_POINT_LAYER_KEY = 'eng_point'; const ENG_POINT_MEDIUM_VISIBLE_ZOOM = 7.5; const LARGE_ENG_LEGEND_PREFIX = 'large_eng_'; const MID_ENG_LEGEND_PREFIX = 'mid_eng_'; export const useMapOrchestrator = () => { const route = useRoute(); const mapClass = MapClass.getInstance(); const mapStore = useMapStore(); const mapConfigStore = useMapConfigStore(); const mapDataStore = useMapDataStore(); const mapViewStore = useMapViewStore(); const jidiSelectEventStore = useJidiSelectEventStore(); let zoomListenerKey: any = null; let stopBaseSelectionWatch: WatchStopHandle | null = null; const initializedBaseLayerKeys = new Set(); const baseSelectionDebounceTimer = ref | null>( null ); // 备注:统一获取当前地图视图对象,避免在多个组件里重复兼容 2D/3D view 访问方式。 const getCurrentView = () => { return mapClass.view?.getView ? mapClass.view.getView() : mapClass.view; }; // 备注:统一获取当前地图缩放级别,供页面切换和缩放监听复用。 const getCurrentZoom = (): number | undefined => { return getCurrentView()?.getZoom?.(); }; // 备注:统一把当前页面中的 GIS 基础图层初始化到地图实例,避免在图层树组件里做配置解析和加层编排。 const ensureBaseLayersInitialized = (configs: any[] = []) => { const flatLayerConfigs = layerConfig2Flat(configs); flatLayerConfigs.forEach((item: any) => { if (item.type !== 'GISMap' || !item.key) { return; } if (!item.config && item.paramJson) { try { const jsonObj = JSON.parse(item.paramJson); item.config = getMapConfig(jsonObj); } catch { item.config = null; } } if (!item.config) { return; } if (item.key === 'customBaseLayer') { sessionStorage.setItem('customBaseLayer', JSON.stringify(item.config)); } if (!initializedBaseLayerKeys.has(item.key)) { mapClass.addBaseDataLayer(item.config, item.checked === 1); initializedBaseLayerKeys.add(item.key); } mapClass.controlBaseLayerTreeShowAndHidden( item.key, item.config?.id || item.key, item.checked === 1 ); }); }; // 备注:统一加载当前页面所需的图层配置、图例配置以及首批锚点数据。 const loadPage = async ({ pageKey, isInitialLoad = false }: LoadPageOptions) => { mapDataStore.setLoading(true); try { const hasGlobalLegendConfig = Array.isArray(mapConfigStore.legendConfigOriginal) && mapConfigStore.legendConfigOriginal.length > 0; const hasPointLayerCache = Object.keys(mapDataStore.pointDataCache || {}).length > 0; const shouldLoadGlobalLegend = isInitialLoad && !hasGlobalLegendConfig; const shouldPreloadLayerData = isInitialLoad && !hasPointLayerCache; const previousPageKey = mapConfigStore.lastLoadOptions?.pageKey || ''; const previousCheckedKeys = mapViewStore.getCheckedLayerKeys(); const moduleId = (route.meta?.moduleId as string) || ''; const loadOptions = { systemId: SYSTEM_ID, moduleId, pageKey, description: 'true' }; const layerConfigPromise = mapConfigStore.loadPageLayerConfig(loadOptions); const legendConfigPromise = mapConfigStore.loadPageLegendConfig( moduleId, { includeGlobal: shouldLoadGlobalLegend } ); // 同时等待两个 promise const [{ layerConfig }, { legendOriginal, pageLegend }] = await Promise.all([layerConfigPromise, legendConfigPromise]); // 先设置图层数据(更新 checkedLayerKeys),再设置图例数据 if (layerConfig.length > 0) { mapStore.setLayerData(layerConfig); ensureBaseLayersInitialized(layerConfig); let checkedKeys = mapViewStore.getCheckedLayerKeys(); if (previousPageKey === pageKey && previousCheckedKeys.length > 0) { const getAllLayerKeys = (items: any[]): string[] => { const keys: string[] = []; const walk = (nodes: any[]) => { nodes.forEach(item => { if (item?.key) { keys.push(item.key); } if (item?.children?.length > 0) { walk(item.children); } }); }; walk(items); return keys; }; const currentLayerKeys = new Set(getAllLayerKeys(layerConfig)); const runtimeCheckedKeys = previousCheckedKeys.filter(key => currentLayerKeys.has(key) ); if (runtimeCheckedKeys.length > 0) { mapViewStore.setCheckedLayerKeys(runtimeCheckedKeys); checkedKeys = runtimeCheckedKeys; } } // 设置图例数据(此时 checkedLayerKeys 已正确设置) if (legendOriginal.length > 0) { mapStore.setLegendData(legendOriginal, pageLegend); } if (shouldPreloadLayerData) { mapStore.setSelectedLegendData(); await mapStore.loadAllLayerData(layerConfig, checkedKeys); } else { await mapStore.updateLayerData(checkedKeys, true); mapStore.setSelectedLegendData(); } } } finally { // mapDataStore.setLoading(false); } }; // 备注:统一完成地图实例初始化和弹窗挂载,确保后续事件监听可以在图层数据加载前生效。 const initializeMapShell = async ({ container, popupContainer }: InitializeOptions) => { await mapClass.init(container); if (popupContainer) { mapClass.initPopupOverlay(popupContainer); } }; // 备注:统一完成首屏页面配置加载,供挂载阶段和后续页面重载复用。 const initialize = async ({ container, popupContainer, pageKey }: InitializeOptions) => { await initializeMapShell({ container, popupContainer, pageKey }); await loadPage({ pageKey, isInitialLoad: true }); }; // 备注:统一绑定地图缩放监听,让页面组件只负责触发初始化,不再直接处理缩放联动细节。 const bindZoomListener = (getIsHydroMenu: () => boolean) => { if (zoomListenerKey) { unByKey(zoomListenerKey); zoomListenerKey = null; } const view = getCurrentView(); if (!view?.on) return; const currentZoom = getCurrentZoom(); if (currentZoom !== undefined) { mapViewStore.setCurrentZoomLevel(currentZoom); } zoomListenerKey = view.on('change:resolution', async () => { const zoom = getCurrentZoom(); if (zoom === undefined) return; await handleZoomLevelChange( mapViewStore.currentZoomLevel, zoom, getIsHydroMenu() ); }); }; // 备注:统一绑定基地切换事件,让页面组件不再直接 watch 外部基地选择源。 const bindBaseSelection = () => { if (stopBaseSelectionWatch) { stopBaseSelectionWatch(); stopBaseSelectionWatch = null; } stopBaseSelectionWatch = watch( () => jidiSelectEventStore.selectedItem?.wbsCode, newWbsCode => { if (!newWbsCode) { changeBaseId(''); return; } if (baseSelectionDebounceTimer.value) { clearTimeout(baseSelectionDebounceTimer.value); } baseSelectionDebounceTimer.value = setTimeout(() => { changeBaseId(newWbsCode); baseSelectionDebounceTimer.value = null; }, 100); }, { immediate: true } ); }; // 备注:统一挂载地图页面生命周期,初始化后自动接管缩放监听和基地切换监听。 const mountView = async ( options: InitializeOptions & { getIsHydroMenu: () => boolean; } ) => { await initializeMapShell(options); bindBaseSelection(); bindZoomListener(options.getIsHydroMenu); await loadPage({ pageKey: options.pageKey, isInitialLoad: true }); }; // 备注:统一处理菜单切换后的页面配置重载,供页面 watch 直接复用。 const reloadPage = async (pageKey: string) => { if (!pageKey) return; await loadPage({ pageKey, isInitialLoad: false }); }; // 备注:菜单切换前先隐藏旧页面当前可见的点图层,避免旧锚点残留到新页面接口返回之后才消失。 const hideCurrentVisiblePointLayers = () => { const currentCheckedKeys = mapViewStore.getCheckedLayerKeys(); currentCheckedKeys.forEach(layerKey => { const layerItem = mapStore.findLayerByKey(mapStore.layerData, layerKey); if ( !layerItem?.key || layerItem.type !== 'pointMap' || !mapClass.hasLayer(layerItem.key) ) { return; } mapClass.mdLayerTreeShowOrHidden(layerItem.key, false); }); }; // 备注:统一处理图层树勾选结果归一化和地图联动,供图层树组件复用。 const applyLayerSelection = async ({ checkedKeys, triggerKey, checked }: ApplyLayerSelectionOptions) => { const normalizedKeys = mapStore.normalizeCheckedLayerKeys( checkedKeys, triggerKey, checked ); await mapStore.updateLayerData(normalizedKeys); return normalizedKeys; }; // 备注:统一确保指定动态图层完成数据预加载,避免缩放切换时重复在页面组件里拼装加载逻辑。 const ensureDynamicLayersLoaded = async (layerKeys: string[] = []) => { const layersToLoad = layerKeys .map(layerKey => mapStore.findLayerByKey(mapStore.layerData, layerKey)) .filter( (layer: any) => layer?.url && !mapDataStore.hasPointLayerData(layer.key) ); for (const layer of layersToLoad) { await mapStore.loadLayerData(layer); } }; // 备注:统一控制水电开发菜单下的动态图层增删,避免页面组件直接操作加载和勾选细节。 const syncHydroDynamicLayers = async (visible: boolean) => { const currentCheckedKeys = mapViewStore.getCheckedLayerKeys(); const pageDefaultCheckedKeys = new Set( mapConfigStore.extractCheckedLayerKeys(mapStore.layerData || []) ); if (visible) { const layersToAdd = HYDRO_DYNAMIC_LAYER_KEYS.filter( key => !currentCheckedKeys.includes(key) ); if (layersToAdd.length === 0) { return currentCheckedKeys; } await ensureDynamicLayersLoaded(layersToAdd); return applyLayerSelection({ checkedKeys: [...currentCheckedKeys, ...layersToAdd] }); } const nextCheckedKeys = currentCheckedKeys.filter(key => { if (!HYDRO_DYNAMIC_LAYER_KEYS.includes(key)) { return true; } return pageDefaultCheckedKeys.has(key); }); if (nextCheckedKeys.length === currentCheckedKeys.length) { return currentCheckedKeys; } return applyLayerSelection({ checkedKeys: nextCheckedKeys }); }; // 备注:统一处理地图缩放联动,收口水电开发菜单下的动态图层阈值切换逻辑。 const handleZoomLevelChange = async ( previousZoom: number, currentZoom: number, isHydroMenu: boolean ) => { mapViewStore.setCurrentZoomLevel(currentZoom); const crossedEngPointMediumThreshold = (previousZoom <= ENG_POINT_MEDIUM_VISIBLE_ZOOM && currentZoom > ENG_POINT_MEDIUM_VISIBLE_ZOOM) || (previousZoom > ENG_POINT_MEDIUM_VISIBLE_ZOOM && currentZoom <= ENG_POINT_MEDIUM_VISIBLE_ZOOM); if ( crossedEngPointMediumThreshold && mapViewStore.getCheckedLayerKeys().includes(ENG_POINT_LAYER_KEY) ) { mapStore.refreshPointLayerDisplayData(ENG_POINT_LAYER_KEY); } if (!isHydroMenu) { return; } const oldZoomLevel = Math.floor(previousZoom); const newZoomLevel = Math.floor(currentZoom); if (oldZoomLevel < 12 && newZoomLevel >= 12) { await syncHydroDynamicLayers(true); return; } if (oldZoomLevel >= 12 && newZoomLevel < 12) { await syncHydroDynamicLayers(false); } }; // 备注:统一处理菜单切换后的页面重载和动态图层收口,减少页面组件里的分支编排。 const handlePageChange = async (pageKey: string, isHydroMenu: boolean) => { if (!pageKey) return; const previousPageKey = mapConfigStore.lastLoadOptions?.pageKey || ''; if (previousPageKey && previousPageKey !== pageKey) { hideCurrentVisiblePointLayers(); } await reloadPage(pageKey); const currentZoom = getCurrentZoom(); if (isHydroMenu && currentZoom !== undefined && currentZoom >= 12) { await syncHydroDynamicLayers(true); return; } await syncHydroDynamicLayers(false); }; // 备注:统一处理单个图层切换命令,供后续其他入口复用。 const toggleLayer = async (layerKey: string, checked?: boolean) => { const currentKeys = mapViewStore.getCheckedLayerKeys(); const currentSet = new Set(currentKeys); const nextChecked = checked === undefined ? !currentSet.has(layerKey) : checked; if (nextChecked) { currentSet.add(layerKey); } else { currentSet.delete(layerKey); } return applyLayerSelection({ checkedKeys: Array.from(currentSet), triggerKey: layerKey, checked: nextChecked }); }; // 备注:统一处理单个图例切换命令,供图例组件复用。 const toggleLegend = (nameEn: string, checked?: number) => { if (!nameEn) return; const nextChecked = checked === undefined ? mapViewStore.getLegendChecked(nameEn) === 1 ? 0 : 1 : checked; mapStore.updateLegendChecked(nameEn, nextChecked); }; // 备注:统一处理批量图例切换命令,供容量筛选和分组图例复用。 const toggleLegendBatch = (nameEns: string[] = [], checked: number) => { const targetNameEns = nameEns.filter(Boolean); if (targetNameEns.length === 0) return; mapStore.updateLegendCheckedBatch(targetNameEns, checked); }; // 备注:统一处理基地切换命令,同时驱动地图基地裁切联动。 const changeBaseId = (baseId: string) => { const nextBaseId = !baseId || baseId === 'all' ? '' : baseId; mapViewStore.setSelectedBaseId(nextBaseId); mapClass.jdPanelControlShowAndHidden( baseId || 'all', baseId !== 'all' && !!baseId ); }; // 备注:统一处理时间范围变更,并触发相关图层数据重载。 const changeTimeRange = async (range: [any, any]) => { mapViewStore.setSearchTimeRange(range); await mapStore.reloadBySearchTimeRange(); }; // 备注:统一处理装机容量筛选,让筛选器切换时同步更新 eng_point 图例运行态和点位显示。 const changeEngPointCapacity = (capacityType?: string | null) => { const legendItems = mapStore.getLegendItemsByLayerCode(ENG_POINT_LAYER_KEY); if (!legendItems.length) return; const allEngLegendNames = legendItems .map((item: any) => item?.nameEn) .filter(Boolean); if (!allEngLegendNames.length) return; const normalizedCapacityType = capacityType || 'all'; let activePrefixes = [LARGE_ENG_LEGEND_PREFIX, MID_ENG_LEGEND_PREFIX]; if (normalizedCapacityType === 'large_eng_built') { activePrefixes = [LARGE_ENG_LEGEND_PREFIX]; } else if (normalizedCapacityType === 'mid_eng_built') { activePrefixes = [MID_ENG_LEGEND_PREFIX]; } const visibleLegendNames = allEngLegendNames.filter((nameEn: string) => activePrefixes.some(prefix => nameEn.startsWith(prefix)) ); const hiddenLegendNames = allEngLegendNames.filter( (nameEn: string) => !visibleLegendNames.includes(nameEn) ); mapStore.updateLegendCheckedBatch(hiddenLegendNames, 0); mapStore.updateLegendCheckedBatch(visibleLegendNames, 1); mapStore.refreshPointLayerDisplayData(ENG_POINT_LAYER_KEY); }; // 备注:统一重置地图筛选表单依赖的时间范围,供筛选组件在切页时复位默认输入。 const resetFilterState = () => { mapViewStore.setSearchTimeRange([dayjs().subtract(1, 'M'), dayjs()]); }; // 备注:统一处理搜索定位,按点位编码从缓存数据中查找并飞行到目标位置。 const focusPoint = (pointId: string, zoom = 15) => { if (!pointId) return; const targetPoint = mapDataStore.pointData.find((item: any) => { return item.stcd === pointId || item._id === pointId; }); if (!targetPoint?.lgtd || !targetPoint?.lttd) return; mapClass.flyTopanto([targetPoint.lgtd, targetPoint.lttd], zoom); }; // 备注:统一释放地图页面生命周期里注册的监听,避免页面卸载后残留联动。 const unmountView = () => { if (zoomListenerKey) { unByKey(zoomListenerKey); zoomListenerKey = null; } if (stopBaseSelectionWatch) { stopBaseSelectionWatch(); stopBaseSelectionWatch = null; } if (baseSelectionDebounceTimer.value) { clearTimeout(baseSelectionDebounceTimer.value); baseSelectionDebounceTimer.value = null; } }; return { initialize, mountView, unmountView, loadPage, reloadPage, applyLayerSelection, ensureDynamicLayersLoaded, syncHydroDynamicLayers, getCurrentZoom, handleZoomLevelChange, handlePageChange, toggleLayer, toggleLegend, toggleLegendBatch, changeBaseId, changeTimeRange, changeEngPointCapacity, resetFilterState, focusPoint }; };