import { defineStore, storeToRefs } from 'pinia'; import { ref } from 'vue'; import dayjs from 'dayjs'; import { MapClass } from '@/components/gis/map.class'; import { getMapConfig } from '@/components/gis/gisUtils'; import { applyLayerMutualExclusionRules } from '@/modules/map/domain/map-layer-rules'; import { applyEnvFacilityLegendRule, buildLegendCheckedState, buildLegendTree } from '@/modules/map/domain/legend-deriver'; import { attachNearbyPointRuntimeMeta } from '@/modules/map/domain/nearby-point-rules'; 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 request from '@/utils/request'; const mapClass = MapClass.getInstance(); const ENG_POINT_LAYER_KEY = 'eng_point'; const YLFB_POINT_LAYER_KEY = 'ylfb_point'; const ENG_POINT_MEDIUM_VISIBLE_ZOOM = 7.5; const ENG_POINT_LARGE_STATES = [ 'large_eng_built', 'large_eng_ubuilt', 'large_eng_nbuilt' ]; const ENG_POINT_MEDIUM_STATES = [ 'mid_eng_built', 'mid_eng_ubuilt', 'mid_eng_nbuilt' ]; const normalizeCachePayload = (value: any): any => { if (Array.isArray(value)) { return value.map(item => normalizeCachePayload(item)); } if (value && typeof value === 'object') { return Object.keys(value) .sort() .reduce>((acc, key) => { acc[key] = normalizeCachePayload(value[key]); return acc; }, {}); } return value; }; const isCanceledRequestError = (error: unknown) => { const message = error instanceof Error ? error.message : String(error || ''); return ( message.includes('canceled') || message.includes('aborted') || message.includes('AbortError') ); }; const buildFiltersFromParamsObject = ( paramsObject: Record = {} ) => { const filtersArray: any[] = []; Object.keys(paramsObject).forEach(paramKey => { const value = paramsObject[paramKey]; if (Array.isArray(value)) { value.forEach((filter: any) => { filtersArray.push({ ...filter, dataType: filter?.dataType || 'string' }); }); return; } filtersArray.push({ field: paramKey, operator: 'eq', dataType: 'string', value }); }); return filtersArray; }; const normalizeRequestParams = (rawParams: any) => { if (!rawParams || !Object.keys(rawParams).length) { return { logic: 'and', filters: [] }; } if (Array.isArray(rawParams.filters)) { return { logic: rawParams.logic || 'and', filters: rawParams.filters.map((filter: any) => ({ ...filter, dataType: filter?.dataType || 'string' })) }; } return { logic: 'and', filters: buildFiltersFromParamsObject(rawParams) }; }; const parseAnchorParamFilters = (anchorParamJson?: string) => { if (!anchorParamJson) return []; try { const parsedParams = JSON.parse(anchorParamJson); if (!parsedParams || typeof parsedParams !== 'object') { return []; } return buildFiltersFromParamsObject(parsedParams); } catch (error) { console.error('解析 anchorParamJson 失败:', error); return []; } }; export const useMapStore = defineStore('map', () => { const mapConfigStore = useMapConfigStore(); const mapDataStore = useMapDataStore(); const mapViewStore = useMapViewStore(); const { getLayerConfigByKey, getLegendConfigByLayerCode, getLegendConfigByNameEn } = mapConfigStore; const { hasPointLayerData, getPointLayerData } = mapDataStore; const { layerConfigTree: layerData, legendConfigOriginal: legendDataOriginal, pageLegendConfig } = storeToRefs(mapConfigStore); const { checkedLayerKeys, legendCheckedState, searchTimeRange } = storeToRefs(mapViewStore); const { pointData, pointDataCache, layerLoadState, loading } = storeToRefs(mapDataStore); // 图例数据(经过筛选和排序的) const legendData = ref([]); // 选中的图例数据 const legendDataSelected = ref([]); const inFlightRequestMap = new Map< string, { promise: Promise; controller: AbortController; layerKey: string; sessionId: number; } >(); const activeLayerRequestKeyMap = new Map(); const backgroundPointLayerCache = new Map< string, { checked: boolean; data: any[]; cacheKey?: string; } >(); let loadSessionSeed = 0; let activePageRenderToken = 0; let activePageKey = ''; let activePageLayerKeys = new Set(); let activeLoadingPageToken: number | null = null; let pendingPageNavigationKey = ''; const normalizeLegendNameEn = mapConfigStore.normalizeLegendNameEn; const filterPointLayerDataForDisplay = ( layerKey: string, sourceData: any[] = [], currentZoom: number = mapViewStore.currentZoomLevel ): any[] => { if (layerKey !== ENG_POINT_LAYER_KEY || !Array.isArray(sourceData)) { return sourceData; } const allowMedium = currentZoom > ENG_POINT_MEDIUM_VISIBLE_ZOOM; return sourceData.filter((item: any) => { const state = String(item?.anchoPointState || ''); if (ENG_POINT_LARGE_STATES.includes(state)) { return true; } if (ENG_POINT_MEDIUM_STATES.includes(state)) { return allowMedium; } return true; }); }; const refreshPointLayerDisplayData = (layerKey: string) => { if (!layerKey) return; const layerItem = findLayerByKey(layerData.value, layerKey); syncBackgroundCacheToReactiveStore(layerKey); const rawData = Array.isArray(layerItem?.data) && layerItem.data.length > 0 ? layerItem.data : getEffectivePointLayerData(layerKey); const displayData = filterPointLayerDataForDisplay(layerKey, rawData); const shouldRestoreVisible = layerItem?.checked === 1 || checkedLayerKeys.value.includes(layerKey); mapClass.addInitDataLayer(displayData, layerKey); if (!shouldRestoreVisible) { mapClass.mdLayerTreeShowOrHidden(layerKey, false); return; } restoreLayerLegendVisibility(layerKey); mapClass.mdLayerTreeShowOrHidden(layerKey, true); }; const getLegendItemsByLayerCode = (layerCode: string): any[] => { return getLegendConfigByLayerCode(layerCode); }; const getLegendChecked = (nameEn?: string): number => { const normalizedNameEn = normalizeLegendNameEn(nameEn); if (!normalizedNameEn) return 0; return mapViewStore.getLegendChecked(normalizedNameEn); }; const restoreLayerLegendVisibility = (layerKey: string): boolean => { const layerLegendItems = getLegendItemsByLayerCode(layerKey); if ( legendDataOriginal.value.length === 0 || layerLegendItems.length === 0 ) { return false; } mapClass.setLegendPointVisible(layerKey, '', false); layerLegendItems.forEach((legendItem: any) => { if (getLegendChecked(legendItem.nameEn) === 1) { applyLegendItemVisibility(legendItem, 1); } }); return true; }; const buildRuntimeLegendTree = ( items: any[] = [], selectedLayerCodes?: Set ): any[] => { return buildLegendTree({ items, selectedLayerCodes, getLegendChecked, normalizeLegendNameEn }); }; const deriveSelectedLegendData = (layerKeys: string[]): any[] => { const selectedLayerCodes = new Set( layerKeys.filter( key => key && key !== '-' && key !== 'customBaseLayer' && key !== 'powerBaseStation' ) ); let derivedLegend = buildRuntimeLegendTree( legendDataOriginal.value, selectedLayerCodes ); derivedLegend = applyEnvFacilityLegendRule( derivedLegend, Array.from(selectedLayerCodes), legendDataOriginal.value, items => buildRuntimeLegendTree(items) ); return sortByOrderIndex(derivedLegend); }; const rebuildLegendRuntimeData = ( layerKeys: string[] = checkedLayerKeys.value ) => { legendData.value = sortByOrderIndex( buildRuntimeLegendTree(legendDataOriginal.value) ); legendDataSelected.value = deriveSelectedLegendData(layerKeys); }; const getLayerBranchKeys = (rootKey: string): string[] => { const rootLayer = getLayerConfigByKey(rootKey); if (!rootLayer) return []; const branchKeys: string[] = []; const walk = (node: any) => { if (node?.key) { branchKeys.push(node.key); } if (node?.children?.length > 0) { node.children.forEach((child: any) => walk(child)); } }; walk(rootLayer); return branchKeys; }; const normalizeCheckedLayerKeys = ( rawKeys: string[] = [], triggerKey?: string, isChecked?: boolean ): string[] => { return applyLayerMutualExclusionRules({ rawKeys, previousKeys: checkedLayerKeys.value, triggerKey, checked: isChecked, getLayerBranchKeys }); }; const getRuntimeCheckedLayerKeys = (): string[] => { return mapViewStore.getCheckedLayerKeys(); }; const shouldSyncMergedPointData = (layerKey: string, pageToken?: number) => { if (!layerKey) return false; return ( shouldApplyToActivePage(layerKey, pageToken) || getRuntimeCheckedLayerKeys().includes(layerKey) ); }; const getEffectivePointLayerCache = (layerKey: string) => { const reactiveCache = mapDataStore.getPointLayerCache(layerKey); if (reactiveCache) { return reactiveCache; } return backgroundPointLayerCache.get(layerKey); }; const getEffectivePointLayerData = (layerKey: string): any[] => { const reactiveCache = mapDataStore.getPointLayerCache(layerKey); if (reactiveCache?.data) { return reactiveCache.data; } return getEffectivePointLayerCache(layerKey)?.data || []; }; const hasEffectivePointLayerCache = (layerKey: string, cacheKey: string) => { const cache = getEffectivePointLayerCache(layerKey); if (!cache || !cacheKey) return false; return cache.cacheKey === cacheKey && Array.isArray(cache.data); }; const setBackgroundPointLayerCache = ( layerKey: string, cache: { checked: boolean; data: any[]; cacheKey?: string; } ) => { if (!layerKey) return; backgroundPointLayerCache.set(layerKey, cache); }; const syncBackgroundCacheToReactiveStore = (layerKey: string) => { const cache = backgroundPointLayerCache.get(layerKey); if (!cache) return; mapDataStore.setPointLayerCache(layerKey, cache); backgroundPointLayerCache.delete(layerKey); }; const syncPointDataForFilter = (layerKeys: string[] = []) => { mapDataStore.rebuildPointDataFromCache(layerKeys); }; const normalizePointLayerItems = (list: any[] = []) => { return list .map((item: any) => { const iconType = item.anchoPointState; const legendConfig = getLegendConfigByNameEn(iconType); return { ...item, iconCode: item.iconCode || legendConfig?.icon || '', code: item.code || legendConfig?.code || '', tm: item.sttpMap === 'WQ_ALARM' ? item?.warnDataList?.[0]?.tm : item?.tm, _id: item._id || `${item.sttpMap || iconType || 'point'}_${item.stcd}`, layerKey: item.layerKey || YLFB_POINT_LAYER_KEY }; }) .filter( (item: any) => (item.iconCode || item.code == 'colorLayer') && !(item?.baseId == 'all' && item?.anchoPointState?.endsWith('_nbuilt')) ); }; const ensureGISLayerConfig = (layerItem: any) => { if (!layerItem) return null; if (!layerItem.config && layerItem.paramJson) { try { const jsonObj = JSON.parse(layerItem.paramJson); layerItem.config = getMapConfig(jsonObj); } catch { layerItem.config = null; } } if (!layerItem.config) { return null; } if (layerItem.config.key !== layerItem.key) { layerItem.config = { ...layerItem.config, key: layerItem.key }; } return layerItem.config; }; /** * 设置图层数据 */ const setLayerData = (data: any[]) => { mapConfigStore.setLayerConfigTree(data); const nextCheckedLayerKeys = mapConfigStore.extractCheckedLayerKeys(data); mapViewStore.setCheckedLayerKeys(nextCheckedLayerKeys); rebuildLegendRuntimeData(nextCheckedLayerKeys); }; /** * 按照 orderIndex 排序图例数据(递归) */ const sortByOrderIndex = (items: any[]): any[] => { return items .map(item => { if (item.childrenList && item.childrenList.length > 0) { return { ...item, childrenList: sortByOrderIndex(item.childrenList) }; } return item; }) .sort((a, b) => (a.orderIndex || 0) - (b.orderIndex || 0)); }; /** * 启动新的图层加载会话,供需要严格按最新查询条件收口的场景复用。 */ const startLoadSession = () => { loadSessionSeed += 1; return loadSessionSeed; }; /** * 判断当前请求是否仍属于最新加载会话,避免旧请求结果回写到最新页面。 */ const isCurrentLoadSession = (sessionId: number) => { return loadSessionSeed === sessionId; }; const activatePageContext = (pageKey: string, items: any[] = []) => { activePageKey = pageKey; pendingPageNavigationKey = ''; activePageRenderToken += 1; activePageLayerKeys = new Set(getAllLayerKeys(items)); return activePageRenderToken; }; const markPendingPageNavigation = (targetPageKey: string) => { pendingPageNavigationKey = targetPageKey || '__route-change__'; }; const hasPendingPageNavigationAwayFrom = (pageKey: string) => { return !!pendingPageNavigationKey && pendingPageNavigationKey !== pageKey; }; const shouldApplyToActivePage = (layerKey: string, pageToken?: number) => { if (!layerKey) return false; if (activePageKey && hasPendingPageNavigationAwayFrom(activePageKey)) { return false; } if (pageToken === undefined) return true; return ( activePageRenderToken === pageToken && activePageLayerKeys.has(layerKey) ); }; const isActivePageToken = (pageToken?: number) => { if (pageToken === undefined) return true; return activePageRenderToken === pageToken; }; const beginPageLoading = (pageToken?: number) => { if (typeof pageToken === 'number') { activeLoadingPageToken = pageToken; } mapDataStore.setLoading(true); }; const finishPageLoading = (pageToken?: number) => { if (typeof pageToken !== 'number') { mapDataStore.setLoading(false); return; } if ( activeLoadingPageToken === pageToken && activePageRenderToken === pageToken ) { activeLoadingPageToken = null; mapDataStore.setLoading(false); } }; /** * 设置图例数据(过滤掉 name == '地图' 的项,ifShow == 0 时不显示) * @param data - 全部图例数据 * @param selectedData - 当前页面选中的图例数据(用于标记选中状态) */ const setLegendData = (data: any[], selectedData: any[] = []) => { mapConfigStore.setLegendConfigOriginal(data); mapConfigStore.setPageLegendConfig(selectedData); mapViewStore.setLegendCheckedState( buildLegendCheckedState(legendDataOriginal.value, normalizeLegendNameEn) ); rebuildLegendRuntimeData(checkedLayerKeys.value); }; /** * 设置选中的图例数据(在图层加载完成后调用) */ const setSelectedLegendData = () => { rebuildLegendRuntimeData(checkedLayerKeys.value); }; /** * 设置描点数据 */ const setPointData = (data: any[]) => { mapDataStore.setPointData(data); }; const replacePointLayerData = ( layerKey: string, data: any[] = [], visible = true ) => { if (!layerKey) return; const layerItem = findLayerByKey(layerData.value, layerKey); const normalizedData = normalizePointLayerItems( data.map((item: any) => ({ ...item, layerKey })) ); mapDataStore.setPointLayerCache(layerKey, { checked: visible, data: normalizedData, cacheKey: `custom:${layerKey}:${normalizedData.length}` }); mapDataStore.rebuildPointDataFromCache(); if (layerItem) { layerItem.data = normalizedData; layerItem.checked = visible ? 1 : 0; } if (!visible || normalizedData.length === 0) { if (mapClass.hasLayer(layerKey)) { mapClass.mdLayerTreeShowOrHidden(layerKey, false); } return; } refreshPointLayerDisplayData(layerKey); }; const applyLoadedPointLayerToMap = ( layer: any, layerKey: string, list: any[] = [], pageToken?: number ) => { if (!Array.isArray(list)) { return; } layer.data = list; const isLayerChecked = layer.checked === 1; if (list.length === 0 || !shouldApplyToActivePage(layerKey, pageToken)) { return; } const displayData = filterPointLayerDataForDisplay(layerKey, list); mapClass.addInitDataLayer(displayData, layerKey); if (isLayerChecked) { restoreLayerLegendVisibility(layerKey); } mapClass.mdLayerTreeShowOrHidden(layerKey, isLayerChecked); }; /** * 图例数据转对象 */ const legendData2Obj = (data: any[]): Record => { const _tempData: Record = {}; const f = (_data: any[]) => { _data.forEach(item => { if (item?.childrenList && item.childrenList?.length > 0) { f(item.childrenList); } else { const normalizedNameEn = normalizeLegendNameEn(item.nameEn); if (normalizedNameEn) { _tempData[normalizedNameEn] = item; } } }); }; f(data); return _tempData; }; /** * 处理单个 pointMap 图层的显示/隐藏 * 数据已在初始化时通过 loadLayerData 加载并存入 layer.data */ const handlePointMapLayer = async (layer: any, isChecked: boolean) => { const { key } = layer; // 如果没有勾选,隐藏图层 if (!isChecked) { if (mapClass.hasLayer(key)) { mapClass.mdLayerTreeShowOrHidden(key, false); } if (mapDataStore.getPointLayerCache(key)) { mapDataStore.setPointLayerCacheChecked(key, false); } if (layer) { layer.checked = 0; } return; } // 使用初始化时已加载的数据(存入 layer.data) const cachedData = Array.isArray(layer?.data) && layer.data.length > 0 ? layer.data : getPointLayerData(key); if (cachedData && cachedData.length > 0) { layer.checked = 1; const displayData = filterPointLayerDataForDisplay(key, cachedData); mapClass.addInitDataLayer(displayData, key); restoreLayerLegendVisibility(key); if (mapDataStore.getPointLayerCache(key)) { mapDataStore.setPointLayerCacheChecked(key, true); } } else { console.warn(`图层 ${key} 没有缓存数据`); } }; /** * 更新图层数据(联动图例和描点) * @param checkKeys - 选中的图层 key 列表 * @param isInit - 是否是初始化阶段(初始化时遍历所有图层) */ const updateLayerData = async (checkKeys: string[], isInit = false) => { if (!isInit) { checkKeys = normalizeCheckedLayerKeys(checkKeys); } mapViewStore.setCheckedLayerKeys(checkKeys); // 收集变化的图层 key(新增选中 / 取消选中) const newlyChecked: string[] = []; const newlyUnchecked: string[] = []; // 更新图层的 checked 状态,并记录变化 const updateCheckedStatus = (items: any[]) => { items.forEach(item => { if (item.key) { const wasChecked = item.checked === 1; const isChecked = checkKeys.includes(item.key); item.checked = isChecked ? 1 : 0; if (wasChecked !== isChecked) { if (isChecked) { newlyChecked.push(item.key); } else { newlyUnchecked.push(item.key); } } } if (item.children && item.children.length > 0) { updateCheckedStatus(item.children); } }); }; updateCheckedStatus(layerData.value); rebuildLegendRuntimeData(checkKeys); // 控制锚点显示隐藏:初始化时处理全部,否则只处理变化的图层 const keysToProcess = isInit ? getAllLayerKeys(layerData.value) : [...newlyChecked, ...newlyUnchecked]; for (const key of keysToProcess) { const layerItem = findLayerByKey(layerData.value, key); if (layerItem && layerItem.type === 'pointMap' && layerItem.url) { const shouldBeVisible = checkKeys.includes(key); await handlePointMapLayer(layerItem, shouldBeVisible); if (shouldBeVisible) { restoreLayerLegendVisibility(key); mapClass.mdLayerTreeShowOrHidden(key, true); } } else if (layerItem && layerItem.type === 'GISMap') { // 处理 GISMap 类型的图层:勾选时若地图实例中不存在,则先重新加载再控制显隐。 if (key && key !== '-' && key !== 'powerBaseStation') { const shouldBeVisible = checkKeys.includes(key); const layerConfig = ensureGISLayerConfig(layerItem); if ( shouldBeVisible && layerConfig && !mapClass.hasBaseLayer(layerItem.key) ) { mapClass.addBaseDataLayer(layerConfig, true); } mapClass.controlBaseLayerTreeShowAndHidden( key, layerItem.key, shouldBeVisible ); } } } }; /** * 加载所有包含URL的图层数据 * @param items 图层数据 * @param checkedKeys 选中的图层 keys(用于优先加载) */ const loadAllLayerData = async ( items: any[], checkedKeys: string[] = [], options: { pageToken?: number; skipSessionCheck?: boolean; pageKey?: string; } = {} ) => { beginPageLoading(options.pageToken); const currentSessionId = options.skipSessionCheck ? undefined : startLoadSession(); const checkedTasks: Array<() => Promise> = []; const uncheckedTasks: Array<() => Promise> = []; const processedKeys = new Set(); const debugStart = Date.now(); const processItems = (itemList: any[]) => { itemList.forEach(item => { if ( (item.type === 'pointMap' || item.url) && item.key && item.url && !processedKeys.has(item.key) ) { processedKeys.add(item.key); const task = () => loadLayerData(item, { sessionId: currentSessionId, pageToken: options.pageToken, skipSessionCheck: options.skipSessionCheck, pageKey: options.pageKey }); if (checkedKeys.includes(item.key)) { checkedTasks.push(task); } else { uncheckedTasks.push(task); } } if (item.children && item.children.length > 0) { processItems(item.children); } }); }; processItems(items); try { // 首页初始化:所有图层接口一次性并发下发,避免分批串行等待。 const loadResults = await Promise.allSettled( [...checkedTasks, ...uncheckedTasks].map(task => task()) ); const failedResults = loadResults.filter( result => result.status === 'rejected' ); if (failedResults.length > 0) { console.warn( '部分图层数据加载失败,但不会阻断其他图层:', failedResults ); } if ( (typeof currentSessionId === 'number' && !isCurrentLoadSession(currentSessionId)) || !isActivePageToken(options.pageToken) ) { return; } if ( options.pageKey && hasPendingPageNavigationAwayFrom(options.pageKey) ) { return; } // 确保所有图层的数据都已经缓存到 pointDataCache 和 layer.data const allLayerKeys = getAllLayerKeys(items); for (const key of allLayerKeys) { const layer = findLayerByKey(items, key); if (layer && getEffectivePointLayerData(key).length > 0) { syncBackgroundCacheToReactiveStore(key); layer.data = getEffectivePointLayerData(key); } } const allPointData = mapDataStore.rebuildPointDataFromCache(allLayerKeys); attachNearbyPointRuntimeMeta(allPointData); const runtimeCheckedKeys = getRuntimeCheckedLayerKeys(); const treeCheckedKeys = mapConfigStore.extractCheckedLayerKeys( layerData.value.length > 0 ? layerData.value : items ); const finalCheckedKeys = runtimeCheckedKeys.length > 0 ? runtimeCheckedKeys : treeCheckedKeys; // 备注:近邻点元数据需要基于全量点位统一识别,识别完成后同步刷新各图层 Feature。 for (const key of allLayerKeys) { const layer = findLayerByKey(items, key); if (!layer || !hasPointLayerData(key)) { continue; } const layerPoints = getPointLayerData(key); const displayData = filterPointLayerDataForDisplay(key, layerPoints); const cacheChecked = mapDataStore.getPointLayerCache(key)?.checked; const shouldRestoreVisible = typeof cacheChecked === 'boolean' ? cacheChecked : layer.checked === 1 || finalCheckedKeys.includes(key); layer.data = layerPoints; mapClass.addInitDataLayer(displayData, key); if (shouldRestoreVisible) { restoreLayerLegendVisibility(key); } mapClass.mdLayerTreeShowOrHidden(key, shouldRestoreVisible); } // 设置选中的图例数据(确保图例在所有锚点加载完成后才显示) setSelectedLegendData(); // 备注:初始化 loading 期间用户可能已经手动改过图层勾选,这里必须以最新运行态收尾, // 不能再回放 load 启动瞬间的默认 checked 快照。 await updateLayerData(finalCheckedKeys, true); } finally { const shouldFinishLoading = (typeof currentSessionId !== 'number' || isCurrentLoadSession(currentSessionId)) && (options.pageToken === undefined || activePageRenderToken === options.pageToken); if (shouldFinishLoading) { finishPageLoading(options.pageToken); } } }; const loadCurrentPageLayerData = async ( items: any[], checkedKeys: string[] = [], options: { pageToken?: number; skipSessionCheck?: boolean; } = {} ) => { const checkedLayerSet = new Set(checkedKeys); const tasks: Array<() => Promise> = []; const processedKeys = new Set(); const processItems = (itemList: any[]) => { itemList.forEach(item => { if ( item.type === 'pointMap' && item.key && item.url && checkedLayerSet.has(item.key) && !processedKeys.has(item.key) ) { processedKeys.add(item.key); tasks.push(() => loadLayerData(item, { sessionId: options.skipSessionCheck ? undefined : loadSessionSeed, pageToken: options.pageToken, skipSessionCheck: options.skipSessionCheck ?? true }) ); } if (item.children && item.children.length > 0) { processItems(item.children); } }); }; processItems(items); if (tasks.length === 0) { finishPageLoading(options.pageToken); return; } beginPageLoading(options.pageToken); try { // 菜单切换:仅当前页的勾选图层一次性并发下发,旧请求在后台继续运行。 await Promise.allSettled(tasks.map(task => task())); } finally { if (isActivePageToken(options.pageToken)) { finishPageLoading(options.pageToken); } } }; /** * 根据 key 查找图层 */ const findLayerByKey = (items: any[], key: string): any | null => { void items; if (!key) return null; return getLayerConfigByKey(key) || null; }; /** * 获取所有图层 keys */ const getAllLayerKeys = (data: any[]): string[] => { const keys: string[] = []; const f = (arr: any[] = []) => { arr.forEach((item: any) => { if (item.key) { keys.push(item.key); } if (item.children && item.children.length > 0) { f(item.children); } }); }; f(data); return keys; }; const getRuntimeLegendNames = ( layerKey: string, nameEn: string ): string[] => { void layerKey; const normalizedNameEn = normalizeLegendNameEn(nameEn); if (!normalizedNameEn) { return []; } if (normalizedNameEn.includes('alarm_range_')) { return [`large_eng_built_${normalizedNameEn}`]; } return [normalizedNameEn]; }; const syncLegendCheckedInTree = ( items: any[], nameEn: string, checked: number ) => { items.forEach((item: any) => { if (item.nameEn === nameEn) { item.checked = checked; } if (item.childrenList && item.childrenList.length > 0) { syncLegendCheckedInTree(item.childrenList, nameEn, checked); } }); }; const applyLegendItemVisibility = (legendItem: any, checked: number) => { if (!legendItem?.nameEn || !legendItem?.layerCode) { return; } const layerKey = legendItem.layerCode; const layerItem = getLayerConfigByKey(layerKey); const shouldBeVisible = checked === 1; const runtimeLegendNames = getRuntimeLegendNames( layerKey, legendItem.nameEn ); if (layerItem?.type === 'GISMap') { mapClass.controlBaseLayerTreeShowAndHidden( layerKey, layerItem.config?.id || layerKey, shouldBeVisible ); return; } runtimeLegendNames.forEach(runtimeLegendName => { mapClass.setLegendPointVisible( layerKey, runtimeLegendName, shouldBeVisible ); }); }; /** * 更新图例选中状态 */ const updateLegendChecked = (nameEn: string, checked: number) => { const normalizedNameEn = normalizeLegendNameEn(nameEn); const legendItem = getLegendConfigByNameEn(normalizedNameEn); if (!legendItem) { return; } mapViewStore.setLegendChecked(normalizedNameEn, checked); syncLegendCheckedInTree(legendData.value, normalizedNameEn, checked); syncLegendCheckedInTree( legendDataSelected.value, normalizedNameEn, checked ); rebuildLegendRuntimeData(checkedLayerKeys.value); applyLegendItemVisibility(legendItem, checked); }; const updateLegendCheckedBatch = (nameEns: string[], checked: number) => { const nextState = { ...legendCheckedState.value }; const changedLegendItems: any[] = []; nameEns.forEach(nameEn => { const normalizedNameEn = normalizeLegendNameEn(nameEn); const legendItem = getLegendConfigByNameEn(normalizedNameEn); if (!legendItem) return; nextState[normalizedNameEn] = checked; changedLegendItems.push(legendItem); }); mapViewStore.setLegendCheckedState(nextState); rebuildLegendRuntimeData(checkedLayerKeys.value); changedLegendItems.forEach(legendItem => applyLegendItemVisibility(legendItem, checked) ); }; /** * 生成请求标识符,用于避免重复请求 */ const getRequestIdentifier = (url: string, params: any): string => { const paramString = JSON.stringify(normalizeCachePayload(params)); return `${url}?${encodeURIComponent(paramString)}`; }; /** * 加载单个图层数据 */ const loadLayerData = async ( layer: any, options: { sessionId?: number; pageToken?: number; skipSessionCheck?: boolean; pageKey?: string; } = {} ) => { const { sessionId = loadSessionSeed, pageToken, skipSessionCheck = false, pageKey } = options; const { key, url, params = {}, paramJson, anchorParamJson } = layer; // 没有URL,跳过 if (!url) { return []; } const requestUrl = url; let requestParams: any = params; let requestOrders: any = null; const layerKey = layer?.key || ''; const ylfbKeys = ['ylfb_point']; const timeRangeLayerKeys = ['ef_point']; const yearTime = dayjs().subtract(1, 'years'); requestParams = normalizeRequestParams(requestParams); const anchorParamFilters = parseAnchorParamFilters(anchorParamJson); if (anchorParamFilters.length) { requestParams.filters.push(...anchorParamFilters); } if (timeRangeLayerKeys.includes(layerKey)) { requestParams.filters.push({ field: 'tm', operator: 'gte', dataType: 'date', value: dayjs(searchTimeRange.value[0]).format('YYYY-MM-DD HH:mm:ss') }); requestParams.filters.push({ field: 'tm', operator: 'lte', dataType: 'date', value: dayjs(searchTimeRange.value[1]).format('YYYY-MM-DD 23:59:59') }); } if (ylfbKeys.includes(layerKey)) { if (yearTime) { requestParams.filters.push({ field: 'startTime', operator: 'eq', dataType: 'date', value: dayjs(yearTime).startOf('year').format('YYYY-MM-DD 00:00:00') }); requestParams.filters.push({ field: 'endTime', operator: 'eq', dataType: 'date', value: dayjs(yearTime).endOf('year').format('YYYY-MM-DD 23:59:59') }); } } if (layer?.orders) { try { const ordersObj = JSON.parse(layer.orders); const ordersArray = Object.entries(ordersObj).map(([field, dir]) => ({ field, dir })); requestOrders = ordersArray; } catch (e) { console.error('解析 orders 失败:', e); } } if (!Object.keys(requestParams).length && paramJson) { try { const jsonObj = JSON.parse(paramJson); if (jsonObj.params) { requestParams = jsonObj.params; } } catch (e) { console.error('解析 paramJson 失败:', e); } } const requestData: any = { filter: requestParams }; if (requestOrders) { requestData.sort = requestOrders; } const requestIdentifier = getRequestIdentifier(requestUrl, { layerKey: key, requestData }); if (hasEffectivePointLayerCache(key, requestIdentifier)) { syncBackgroundCacheToReactiveStore(key); const cachedList = getEffectivePointLayerData(key); layer.data = cachedList; if (shouldSyncMergedPointData(key, pageToken)) { syncPointDataForFilter(getRuntimeCheckedLayerKeys()); } applyLoadedPointLayerToMap(layer, key, cachedList, pageToken); return cachedList; } activeLayerRequestKeyMap.set(key, requestIdentifier); const existingRequest = inFlightRequestMap.get(requestIdentifier); if (existingRequest) { mapDataStore.setLayerLoading(key); return existingRequest.promise.then(list => { if (Array.isArray(list) && list.length > 0) { if (shouldSyncMergedPointData(key, pageToken)) { syncPointDataForFilter(getRuntimeCheckedLayerKeys()); } applyLoadedPointLayerToMap(layer, key, list, pageToken); } return list; }); } mapDataStore.setLayerLoading(key); const controller = new AbortController(); const requestPromise = (async () => { try { const response = await request({ url: requestUrl, method: 'post', data: requestData, signal: controller.signal }); const resData = response?.data || response?.data?.data || []; if ( controller.signal.aborted || (!skipSessionCheck && !isCurrentLoadSession(sessionId)) ) { return []; } let list: any[] = []; if (resData) { if (Array.isArray(resData)) { list = resData; } else if (Array.isArray(resData.data)) { list = resData.data; } else if (resData.data && Array.isArray(resData.data.data)) { list = resData.data.data; } else if ( resData.data && resData.data.data && Array.isArray(resData.data.data.data) ) { list = resData.data.data.data; } } list = normalizePointLayerItems( list.map((item: any) => ({ ...item, layerKey: item.layerKey || key })) ); if (activeLayerRequestKeyMap.get(key) !== requestIdentifier) { return []; } const isLayerChecked = layer.checked === 1; const cachePayload = { checked: isLayerChecked, data: list, cacheKey: requestIdentifier }; const shouldDeferToBackgroundCache = !!pageKey && hasPendingPageNavigationAwayFrom(pageKey); if (shouldDeferToBackgroundCache) { setBackgroundPointLayerCache(key, cachePayload); layer.data = list; return list; } mapDataStore.setPointLayerCache(key, cachePayload); if (shouldSyncMergedPointData(key, pageToken)) { syncPointDataForFilter(getRuntimeCheckedLayerKeys()); } applyLoadedPointLayerToMap(layer, key, list, pageToken); mapDataStore.setLayerLoaded(key); return list; } catch (error) { if ( controller.signal.aborted || isCanceledRequestError(error) || (!skipSessionCheck && !isCurrentLoadSession(sessionId)) || activeLayerRequestKeyMap.get(key) !== requestIdentifier ) { return []; } console.error(`加载描点数据失败 [${key}]:`, error); mapDataStore.setLayerLoadError(key, error); throw error; } finally { const activeRequest = inFlightRequestMap.get(requestIdentifier); if (activeRequest?.controller === controller) { inFlightRequestMap.delete(requestIdentifier); } if (activeLayerRequestKeyMap.get(key) === requestIdentifier) { activeLayerRequestKeyMap.delete(key); } } })(); inFlightRequestMap.set(requestIdentifier, { promise: requestPromise, controller, layerKey: key, sessionId }); return requestPromise; }; /** * 获取选中的图层 key */ const getCheckedKeys = (): string[] => { return mapViewStore.getCheckedLayerKeys(); }; /** * 根据当前搜索时间范围重新加载描点数据 */ const reloadBySearchTimeRange = async () => { mapDataStore.setLoading(true); const currentSessionId = startLoadSession(); // 只处理和 searchTimeRange 相关的图层 const timeRangeLayerKeys = ['ef_point']; try { for (const key of timeRangeLayerKeys) { // 如果描点图层存在于地图中,先删除旧图层 mapClass.removePointLayer(key); // 清空缓存 if (mapDataStore.getPointLayerCache(key)) { mapDataStore.removePointLayerCache(key); mapDataStore.clearLayerLoadState(key); } } // 重新加载需要时间搜索的图层数据 if (layerData.value.length > 0) { const currentCheckedKeys = getCheckedKeys(); for (const key of timeRangeLayerKeys) { const layerItem = findLayerByKey(layerData.value, key); if (layerItem && currentCheckedKeys.includes(key)) { await loadLayerData(layerItem, { sessionId: currentSessionId }); } } if (!isCurrentLoadSession(currentSessionId)) { return; } const allPointData = mapDataStore.rebuildPointDataFromCache(); // 更新地图锚点显示 await updateLayerData(currentCheckedKeys, false); } } finally { mapDataStore.setLoading(false); } }; /** * 更新搜索时间范围并重新加载描点数据 */ const updateSearchTimeRange = async (newRange: [any, any]) => { mapViewStore.setSearchTimeRange(newRange); await reloadBySearchTimeRange(); }; return { layerData, setLayerData, loadAllLayerData, loadCurrentPageLayerData, loadLayerData, setSelectedLegendData, legendData, setLegendData, legendDataSelected, pointData, setPointData, checkedLayerKeys, updateLayerData, updateLegendChecked, updateLegendCheckedBatch, replacePointLayerData, getCheckedKeys, legendData2Obj, loading, pointDataCache, layerLoadState, findLayerByKey, getLegendItemsByLayerCode, getLayerBranchKeys, normalizeCheckedLayerKeys, refreshPointLayerDisplayData, activatePageContext, markPendingPageNavigation, searchTimeRange, reloadBySearchTimeRange, updateSearchTimeRange }; });