diff --git a/src/component/rain-earthquake/function-child/AroundAnalysis.vue b/src/component/rain-earthquake/function-child/AroundAnalysis.vue index a806d49..00bca4c 100644 --- a/src/component/rain-earthquake/function-child/AroundAnalysis.vue +++ b/src/component/rain-earthquake/function-child/AroundAnalysis.vue @@ -15,12 +15,35 @@ + + +
+
选择区域
+
+
+ 半径: + + 公里 +
+
+ +
- + \ No newline at end of file diff --git a/src/component/rain-earthquake/function-child/around-analysis/SearchComponent.vue b/src/component/rain-earthquake/function-child/around-analysis/SearchComponent.vue index 0559b96..b7098ab 100644 --- a/src/component/rain-earthquake/function-child/around-analysis/SearchComponent.vue +++ b/src/component/rain-earthquake/function-child/around-analysis/SearchComponent.vue @@ -22,24 +22,52 @@ + + +
+
选择区域
+
+
+ 半径: + + 公里 +
+
+ +
@@ -76,12 +104,12 @@ onMounted(() => { } .search-component-box :deep(.my-autocomplete li) { - padding: 2px 6px; + padding: 2px 6px; display: flex; justify-content: space-between; cursor: pointer; color: #e6edf3; - font-size: 13px; + font-size: 13px; } .search-component-box :deep(.my-autocomplete li .value) { @@ -92,7 +120,7 @@ onMounted(() => { } .search-component-box :deep(.my-autocomplete li .link) { - font-size: 12px; + font-size: 12px; margin-left: 8px; flex-shrink: 0; } @@ -102,4 +130,106 @@ onMounted(() => { background: rgba(58, 112, 169, 0.7); color: #fff; } - + +/* 搜索触发的区域选择对话框样式(fixed定位,相对于视口) */ +.search-area-dialog { + position: fixed; + z-index: 100001; + min-width: 200px; + padding: 0; + background: linear-gradient(180deg, rgba(0, 60, 120, 0.95), rgba(0, 40, 80, 0.95)); + border: 2px solid #00b4ff; + border-radius: 8px; + box-shadow: 0 4px 20px rgba(0, 180, 255, 0.3); + color: white; + overflow: hidden; +} + +.search-area-dialog .dialog-header { + padding: 8px 12px; + font-size: 16px; + font-weight: bold; + text-align: center; + color: white; + background: linear-gradient(90deg, #00b4ff, #0080cc); +} + +.search-area-dialog .dialog-content { + padding: 10px 6px; + display: flex; + justify-content: center; + align-items: center; +} + +.search-area-dialog .radius-input-group { + display: flex; + align-items: center; + gap: 6px; +} + +.search-area-dialog .radius-input-group .label, +.search-area-dialog .radius-input-group .unit { + font-size: 14px; + color: white; +} + +.search-area-dialog .radius-input { + width: 50px; + height: 30px; + padding: 2px 5px; + font-size: 14px; + text-align: center; + color: white; + background: rgba(0, 100, 180, 0.6); + border: 1px solid #00b4ff; + border-radius: 3px; + outline: none; +} + +.search-area-dialog .radius-input::-webkit-inner-spin-button, +.search-area-dialog .radius-input::-webkit-outer-spin-button { + opacity: 0; + -webkit-appearance: none; + appearance: none; + margin: 0; +} + +.search-area-dialog .dialog-footer { + display: flex; + justify-content: center; + gap: 8px; + padding: 10px 10px 8px; +} + +.search-area-dialog .confirm-btn, +.search-area-dialog .cancel-btn { + padding: 6px 15px; + font-size: 14px; + font-weight: bold; + color: white; + border: none; + border-radius: 3px; + cursor: pointer; + transition: background 0.3s, box-shadow 0.3s; +} + +.search-area-dialog .confirm-btn { + background: linear-gradient(180deg, #2d8a4e, #1e6b3a); + border: 1px solid #3da862; +} + +.search-area-dialog .confirm-btn:hover { + background: linear-gradient(180deg, #3da862, #2d8a4e); + box-shadow: 0 2px 8px rgba(45, 138, 78, 0.5); +} + +.search-area-dialog .cancel-btn { + background: linear-gradient(180deg, #c0392b, #96281b); + border: 1px solid #e74c3c; +} + +.search-area-dialog .cancel-btn:hover { + background: linear-gradient(180deg, #e74c3c, #c0392b); + box-shadow: 0 2px 8px rgba(192, 57, 43, 0.5); +} + \ No newline at end of file diff --git a/src/hooks/rain-earthquake/useAroundAnalysis.ts b/src/hooks/rain-earthquake/useAroundAnalysis.ts deleted file mode 100644 index 83e9906..0000000 --- a/src/hooks/rain-earthquake/useAroundAnalysis.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { computed, ref } from 'vue'; -import { CesiumUtilsSingleton } from '@/utils/cesium/CesiumUtils'; -import { useStatusStore } from '@/stores/useStatusStore'; -import { useLoadingResourceStore } from '@/stores/useLoadingResourceStore'; -import { LoadingResource } from '@/types/common/LoadingResourceType'; -import type { PointResource, ResourceConfig } from '@/types/common/useAroundAnalysisType'; - -/** - * 周边分析搜索组件钩子函数 - * @returns 搜索相关的状态和方法 - */ -export const useAroundAnalysis = () => { - const statusStore = useStatusStore();//用于访问图层显示状态 - const loadingResourceStore = useLoadingResourceStore();//用于访问各类点位数据 - // 计算属性:获取图层的显示状态 - const poi = computed(() => statusStore.poiLayers); - const map = computed(() => statusStore.mapLayers); - const infra = computed(() => statusStore.infrastructureLayers); - - /** - * 资源配置列表 - */ - const RESOURCE_CONFIGS: ResourceConfig[] = [ - { key: LoadingResource.SCHOOL, category: 'school', isVisible: () => poi.value.showSchool.show }, - { key: LoadingResource.HOSPITAL, category: 'hospital', isVisible: () => poi.value.showHospital.show }, - { key: LoadingResource.DANGEROUS_SOURCE, category: 'danger', isVisible: () => poi.value.showDangerSource.show }, - { key: LoadingResource.EMERGENCY_SHELTER, category: 'shelter', isVisible: () => poi.value.showRefugeeShelter.show }, - { key: LoadingResource.FIRE_STATION, category: 'fire', isVisible: () => poi.value.showFireStation.show }, - { key: LoadingResource.STORE_POINTS, category: 'store', isVisible: () => poi.value.showReservePoint.show }, - { key: LoadingResource.SUBWAY_STATION, category: 'subway', isVisible: () => poi.value.showSubwayStation.show }, - { key: LoadingResource.LANDSLIDE_HIDDEN_POINT, category: 'hidden-danger', forcedType: 'landslide', isVisible: () => poi.value.showLandslideHiddenPoint.show }, - { key: LoadingResource.DEBRIS_FLOW_HIDDEN_POINT, category: 'hidden-danger', forcedType: 'debris_flow', isVisible: () => poi.value.showDebrisFlowHiddenPoint.show }, - { key: LoadingResource.WATER_LOGGING_HIDDEN_POINT, category: 'hidden-danger', forcedType: 'water_logging', isVisible: () => poi.value.showWaterLoggingHiddenPoint.show }, - { key: LoadingResource.FLASH_FLOOD_HIDDEN_POINT, category: 'hidden-danger', forcedType: 'flash_flood', isVisible: () => poi.value.showFlashFloodHiddenPoint.show }, - { key: LoadingResource.COLLAPSE_HIDDEN_POINT, category: 'hidden-danger', forcedType: 'collapse', isVisible: () => poi.value.showCollapseHiddenPoint.show }, - { key: LoadingResource.RISK_POINT, category: 'risk-point', isVisible: () => map.value.riskPointShow.show }, - { key: LoadingResource.BRIDGE, category: 'bridge', isVisible: () => infra.value.showBridge.show }, - { key: LoadingResource.RESERVOIR, category: 'reservoir', isVisible: () => infra.value.showReservoir.show }, - ]; - - /** - * 所有资源数据 - */ - const allResources = ref([]); - - /** - * 计算属性:判断是否允许搜索 - */ - const canSearch = computed(() => { - return RESOURCE_CONFIGS.some(config => config.isVisible()); - }); - - /** - * 查询建议回调 - * @param queryString - 搜索字符串 - * @param cb - 回调函数 - */ - function querySearch(queryString: string, cb: (results: PointResource[]) => void) { - if (!canSearch.value) { - cb([]); - return; - } - - const lowerQuery = queryString.toLowerCase(); - const filteredResults = allResources.value.filter(item => { - const config = RESOURCE_CONFIGS.find(c => c.category === item.category); - let isVisible = false; - if (config) { - if (item.category === 'hidden-danger') { - const type = (item.originalType as string)?.toLowerCase(); - isVisible = (type === config.forcedType?.toLowerCase()) && config.isVisible(); - } else { - isVisible = config.isVisible(); - } - } - - if (!isVisible) return false; - if (!queryString) return true; - const matchStr = (item.value || '').toLowerCase(); - return matchStr.includes(lowerQuery); - }); - - cb(filteredResults); - } - - /** - * 选择建议回调 - * @param item - 选中的点资源 - */ - function handleSelect(item: PointResource) { - if (item.lon != null && item.lat != null) { - CesiumUtilsSingleton.flyToTarget([item.lon, item.lat, 6000]); - } - } - - /** - * 处理聚焦事件,重新加载数据 - */ - function handleFocus() { - loadAllPointData(); - } - - /** - * 数据处理:将 Store 数据转换为资源格式 - * @param infoList - 原始数据列表 - * @param category - 资源分类 - * @param forcedType - 强制类型 - * @returns 转换后的点资源数组 - */ - function convertStoreDataToResources( - infoList: Record[], - category: PointResource['category'], - forcedType?: string, - ): PointResource[] { - if (!Array.isArray(infoList)) return []; - - return infoList.map((item: Record) => { - const id = item.id || item._id || item.uuid || 'unknown_id'; - const safeId = typeof id === 'string' ? id : typeof id === 'number' ? id : 'unknown_id'; - - const value = (item.name && String(item.name).trim() !== '') - ? String(item.name) - : String(safeId); - - return { - ...item, - id: safeId, - value: value, - category: category, - originalType: (forcedType || (item.type as string) || (item.disasterType as string))?.toLowerCase(), - }; - }); - } - - /** - * 加载所有点类数据 - */ - function loadAllPointData() { - const resources: PointResource[] = []; - - RESOURCE_CONFIGS.forEach(config => { - const data = loadingResourceStore.getLoadingResource(config.key).info; - resources.push(...convertStoreDataToResources(data, config.category, config.forcedType)); - }); - - const seenIds = new Map(); - const uniqueResources: PointResource[] = []; - for (const item of resources) { - if (!seenIds.has(item.id)) { - seenIds.set(item.id, item); - uniqueResources.push(item); - } - } - - allResources.value = uniqueResources; - } - - /** - * 搜索框的值 - */ - const state = ref(''); - - return { state, allResources, canSearch, querySearch, handleSelect, handleFocus, loadAllPointData }; -}; diff --git a/src/hooks/rain-earthquake/useAnalysisButton.ts b/src/hooks/rain-earthquake/useAroundButton.ts similarity index 99% rename from src/hooks/rain-earthquake/useAnalysisButton.ts rename to src/hooks/rain-earthquake/useAroundButton.ts index a72299f..af22aa5 100644 --- a/src/hooks/rain-earthquake/useAnalysisButton.ts +++ b/src/hooks/rain-earthquake/useAroundButton.ts @@ -86,7 +86,7 @@ const isCategoryVisible = (category: PointResourceCategory, originalType?: strin }; // ==================== 响应式状态 ==================== -export const useAnalysisButton = (): AnalysisButtonState => { +export const useAroundButton = (): AnalysisButtonState => { const selectedButtonIndex = ref(-1); const showAreaDialog = ref(false); const radius = ref(10); diff --git a/src/hooks/rain-earthquake/useAroundSearch.ts b/src/hooks/rain-earthquake/useAroundSearch.ts new file mode 100644 index 0000000..825b6b5 --- /dev/null +++ b/src/hooks/rain-earthquake/useAroundSearch.ts @@ -0,0 +1,357 @@ +import { computed, ref, watch } from 'vue'; +import { CesiumUtilsSingleton } from '@/utils/cesium/CesiumUtils'; +import { useStatusStore } from '@/stores/useStatusStore'; +import { useLoadingResourceStore } from '@/stores/useLoadingResourceStore'; +import { LoadingResource } from '@/types/common/LoadingResourceType'; +import type { PointResource, ResourceConfig } from '@/types/common/useAroundAnalysisType'; +import { Cartesian3, Cartographic } from 'cesium'; +import { useCircleDrawer } from './useCircleDrawer'; +import { usePulseEffect } from './usePulseEffect'; +import { useMarkerManager } from './useMarkerManager'; + +/** + * 周边分析搜索组件钩子函数 + * @returns 搜索相关的状态和方法 + */ +export const useAroundSearch = () => { + const statusStore = useStatusStore();//用于访问图层显示状态 + const loadingResourceStore = useLoadingResourceStore();//用于访问各类点位数据 + // 计算属性:获取图层的显示状态 + const poi = computed(() => statusStore.poiLayers); + const map = computed(() => statusStore.mapLayers); + const infra = computed(() => statusStore.infrastructureLayers); + + // 区域分析相关状态 + const showAreaDialog = ref(false); + const areaRadius = ref(10); + const dialogPosition = ref({ x: 0, y: 0 }); + const pendingAnalysisPoint = ref(null); + const isAreaAnalysisActive = ref(false); + const currentAnalysisCenter = ref(null); + const showPulsePointListFromSearch = ref(false); + const pulsePointsFromSearch = ref([]); + + // 组合子 Hook + const { drawCircle, clearCircle } = useCircleDrawer(); + const { addPulseEffectToPoints, removePulseEffect } = usePulseEffect(); + const { addMarker, removeMarker } = useMarkerManager(); + + /** + * 资源配置列表 + */ + const RESOURCE_CONFIGS: ResourceConfig[] = [ + { key: LoadingResource.SCHOOL, category: 'school', isVisible: () => poi.value.showSchool.show }, + { key: LoadingResource.HOSPITAL, category: 'hospital', isVisible: () => poi.value.showHospital.show }, + { key: LoadingResource.DANGEROUS_SOURCE, category: 'danger', isVisible: () => poi.value.showDangerSource.show }, + { key: LoadingResource.EMERGENCY_SHELTER, category: 'shelter', isVisible: () => poi.value.showRefugeeShelter.show }, + { key: LoadingResource.FIRE_STATION, category: 'fire', isVisible: () => poi.value.showFireStation.show }, + { key: LoadingResource.STORE_POINTS, category: 'store', isVisible: () => poi.value.showReservePoint.show }, + { key: LoadingResource.SUBWAY_STATION, category: 'subway', isVisible: () => poi.value.showSubwayStation.show }, + { key: LoadingResource.LANDSLIDE_HIDDEN_POINT, category: 'hidden-danger', forcedType: 'landslide', isVisible: () => poi.value.showLandslideHiddenPoint.show }, + { key: LoadingResource.DEBRIS_FLOW_HIDDEN_POINT, category: 'hidden-danger', forcedType: 'debris_flow', isVisible: () => poi.value.showDebrisFlowHiddenPoint.show }, + { key: LoadingResource.WATER_LOGGING_HIDDEN_POINT, category: 'hidden-danger', forcedType: 'water_logging', isVisible: () => poi.value.showWaterLoggingHiddenPoint.show }, + { key: LoadingResource.FLASH_FLOOD_HIDDEN_POINT, category: 'hidden-danger', forcedType: 'flash_flood', isVisible: () => poi.value.showFlashFloodHiddenPoint.show }, + { key: LoadingResource.COLLAPSE_HIDDEN_POINT, category: 'hidden-danger', forcedType: 'collapse', isVisible: () => poi.value.showCollapseHiddenPoint.show }, + { key: LoadingResource.RISK_POINT, category: 'risk-point', isVisible: () => map.value.riskPointShow.show }, + { key: LoadingResource.BRIDGE, category: 'bridge', isVisible: () => infra.value.showBridge.show }, + { key: LoadingResource.RESERVOIR, category: 'reservoir', isVisible: () => infra.value.showReservoir.show }, + ]; + + /** + * 所有资源数据 + */ + const allResources = ref([]); + + /** + * 计算属性:判断是否允许搜索 + */ + const canSearch = computed(() => { + return RESOURCE_CONFIGS.some(config => config.isVisible()); + }); + + /** + * 查询建议回调 + * @param queryString - 搜索字符串 + * @param cb - 回调函数 + */ + function querySearch(queryString: string, cb: (results: PointResource[]) => void) { + if (!canSearch.value) { + cb([]); + return; + } + const lowerQuery = queryString.toLowerCase(); + const filteredResults = allResources.value.filter(item => { + const config = RESOURCE_CONFIGS.find(c => c.category === item.category); + let isVisible = false; + if (config) { + if (item.category === 'hidden-danger') { + const type = (item.originalType as string)?.toLowerCase(); + isVisible = (type === config.forcedType?.toLowerCase()) && config.isVisible(); + } else { + isVisible = config.isVisible(); + } + } + if (!isVisible) return false; + if (!queryString) return true; + const matchStr = (item.value || '').toLowerCase(); + return matchStr.includes(lowerQuery); + }); + cb(filteredResults); + } + + /** + * 选择建议回调 + * @param item - 选中的点资源 + */ + async function handleSelect(item: PointResource) { + if (item.lon == null || item.lat == null) return; + + await CesiumUtilsSingleton.flyToTarget([item.lon, item.lat, 6000]); + startAreaAnalysis(item); + } + + /** + * 开始区域分析 + * @param centerPoint - 中心点资源 + */ + function startAreaAnalysis(centerPoint: PointResource) { + if (centerPoint.lon == null || centerPoint.lat == null) return; + // 清除之前的分析 + clearSearchAreaAnalysis(); + // 保存待分析的点 + pendingAnalysisPoint.value = centerPoint; + // 设置中心点 + const centerPosition = Cartesian3.fromDegrees(centerPoint.lon, centerPoint.lat, 0); + currentAnalysisCenter.value = centerPosition; + isAreaAnalysisActive.value = true; + // 添加标记(红点+四个绿色角) + addMarker(centerPosition); + // 显示区域选择对话框 + showAreaDialog.value = true; + // 计算对话框位置(屏幕中心右下20px) + calculateDialogPosition(); + } + + /** + * 计算对话框位置(在屏幕中心点右下20px,确保在界面内) + */ + function calculateDialogPosition() { + const W = 280, H = 150, P = 10, O = 20; + const cx = window.innerWidth / 2, cy = window.innerHeight / 2; + let x = cx + O, y = cy + O; + x = x + W > window.innerWidth - P ? cx - W - O : x; + y = y + H > window.innerHeight - P ? cy - H - O : y; + dialogPosition.value = { + x: Math.max(P, Math.min(x, window.innerWidth - W - P)), + y: Math.max(P, Math.min(y, window.innerHeight - H - P)), + }; + } + + /** + * 确认区域分析 + */ + function handleAreaConfirm() { + if (!pendingAnalysisPoint.value) return; + const { lon, lat } = pendingAnalysisPoint.value; + if (lon == null || lat == null) return; + // 关闭对话框 + showAreaDialog.value = false; + // 绘制圆形 + if (currentAnalysisCenter.value) { + drawCircle(currentAnalysisCenter.value, areaRadius.value); + } + // 计算并添加脉冲效果 + refreshSearchPulseEffect(); + // 飞行到合适的高度以显示整个圆形区域 + const flyHeight = Math.max(areaRadius.value * 6000, 10000); + CesiumUtilsSingleton.flyToTarget([lon, lat, flyHeight], 2); + } + + /** + * 取消区域分析 + */ + function handleAreaCancel() { + showAreaDialog.value = false; + clearSearchAreaAnalysis(); + } + + /** + * 清除搜索触发的区域分析 + */ + function clearSearchAreaAnalysis() { + removeMarker(); + clearCircle(); + removePulseEffect(); + currentAnalysisCenter.value = null; + isAreaAnalysisActive.value = false; + showPulsePointListFromSearch.value = false; + pulsePointsFromSearch.value = []; + pendingAnalysisPoint.value = null; + } + + /** + * 刷新搜索触发的脉冲效果 + */ + function refreshSearchPulseEffect() { + if (!currentAnalysisCenter.value) { + console.warn('refreshSearchPulseEffect: currentAnalysisCenter.value 为空'); + return; + } + removePulseEffect(); + const pointsInCircle = getPointsInCircle(currentAnalysisCenter.value, areaRadius.value); + addPulseEffectToPoints(pointsInCircle); + pulsePointsFromSearch.value = pointsInCircle; + showPulsePointListFromSearch.value = true; + } + + /** + * 获取圆形范围内的点 + * @param centerPosition - 中心位置 + * @param radiusKm - 半径(公里) + * @returns 圆形范围内的点资源数组 + */ + function getPointsInCircle(centerPosition: Cartesian3, radiusKm: number): PointResource[] { + const cartographic = Cartographic.fromCartesian(centerPosition); + const centerLon = cartographic.longitude * (180 / Math.PI); + const centerLat = cartographic.latitude * (180 / Math.PI); + const radiusMeters = radiusKm * 1000; + return allResources.value.filter(point => { + if (point.lon === undefined || point.lat === undefined) return false; + const distance = calculateDistance(centerLon, centerLat, point.lon, point.lat); + return distance <= radiusMeters && isCategoryVisible(point.category, point.originalType); + }); + } + + /** + * 计算两点间距离(米) + */ + function calculateDistance( + centerLon: number, + centerLat: number, + pointLon: unknown, + pointLat: unknown + ): number { + const EARTH_RADIUS = 6371000; + const pLon = Number(pointLon); + const pLat = Number(pointLat); + if (isNaN(pLon) || isNaN(pLat)) return Infinity; + const dLat = (pLat - centerLat) * Math.PI / 180; + const dLon = (pLon - centerLon) * Math.PI / 180; + const a = Math.sin(dLat / 2) ** 2 + + Math.cos(centerLat * Math.PI / 180) * Math.cos(pLat * Math.PI / 180) * + Math.sin(dLon / 2) ** 2; + return EARTH_RADIUS * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + } + + /** + * 判断分类是否可见(复用 RESOURCE_CONFIGS 配置) + */ + function isCategoryVisible(category?: string, originalType?: string): boolean { + if (!category) return false; + + const config = RESOURCE_CONFIGS.find(c => c.category === category); + if (!config) return false; + + // 隐患点需要额外判断子类型 + if (category === 'hidden-danger') { + return config.forcedType === originalType?.toLowerCase() && config.isVisible(); + } + + return config.isVisible(); + } + + // 监听图层可见性变化,自动刷新脉冲效果(复用 RESOURCE_CONFIGS) + const layerVisibilityWatchers = RESOURCE_CONFIGS.map(config => config.isVisible); + + // 监听图层可见性变化 + watch(layerVisibilityWatchers, () => { + if (currentAnalysisCenter.value && showPulsePointListFromSearch.value) { + loadAllPointData(); // ① 重新加载所有数据 + refreshSearchPulseEffect(); // ② 刷新脉冲 + } + }); + + // 监听资源数据变化 + watch( + () => loadingResourceStore.loadingResource, + () => { + if (currentAnalysisCenter.value && showPulsePointListFromSearch.value) { + loadAllPointData(); + refreshSearchPulseEffect(); + } + }, + { deep: true } + ); + + /** + * 处理聚焦事件,重新加载数据 + */ + function handleFocus() { + loadAllPointData(); + } + + /** + * 数据处理:将 Store 数据转换为资源格式 + * @param infoList - 原始数据列表 + * @param category - 资源分类 + * @param forcedType - 强制类型 + * @returns 转换后的点资源数组 + */ + function convertStoreDataToResources( + infoList: Record[], + category: PointResource['category'], + forcedType?: string, + ): PointResource[] { + if (!Array.isArray(infoList)) return []; + return infoList.map(item => { + const id = item.id || item._id || item.uuid || 'unknown_id'; + const safeId = typeof id === 'string' || typeof id === 'number' ? id : 'unknown_id'; + const name = item.name && String(item.name).trim() !== '' ? String(item.name) : String(safeId); + return { + ...item, + id: safeId, + value: name, + category, + originalType: (forcedType || item.type || item.disasterType)?.toString().toLowerCase(), + }; + }); + } + + /** + * 加载所有点类数据 + */ + function loadAllPointData() { + const resources = RESOURCE_CONFIGS.flatMap(config => + convertStoreDataToResources(loadingResourceStore.getLoadingResource(config.key).info, config.category, config.forcedType) + ); + const uniqueMap = new Map(); + resources.forEach(item => uniqueMap.set(item.id, item)); + allResources.value = Array.from(uniqueMap.values()); + } + + /** + * 搜索框的值 + */ + const state = ref(''); + + return { + state, + allResources, + canSearch, + querySearch, + handleSelect, + handleFocus, + loadAllPointData, + // 区域分析相关 + showAreaDialog, + areaRadius, + dialogPosition, + isAreaAnalysisActive, + showPulsePointListFromSearch, + pulsePointsFromSearch, + handleAreaConfirm, + handleAreaCancel, + refreshSearchPulseEffect, + clearSearchAreaAnalysis, + }; +}; \ No newline at end of file diff --git a/src/types/common/useAroundAnalysisType.ts b/src/types/common/useAroundAnalysisType.ts index a0e554a..f8f3483 100644 --- a/src/types/common/useAroundAnalysisType.ts +++ b/src/types/common/useAroundAnalysisType.ts @@ -116,3 +116,29 @@ export interface AnalysisButtonState { /** 是否显示脉冲点列表 */ showPulsePointList: Ref; } + +/** + * 搜索区域分析状态接口(用于 provide/inject 共享) + */ +export interface SearchAreaAnalysisState { + /** 是否显示区域选择对话框 */ + showAreaDialog: Ref; + /** 区域半径(公里) */ + areaRadius: Ref; + /** 对话框位置 */ + dialogPosition: Ref; + /** 确认区域分析 */ + handleAreaConfirm: () => void; + /** 取消区域分析 */ + handleAreaCancel: () => void; + /** 搜索框状态 */ + state: Ref; + /** 是否允许搜索 */ + canSearch: Ref; + /** 查询建议 */ + querySearch: (queryString: string, cb: (results: PointResource[]) => void) => void; + /** 选择建议回调 */ + handleSelect: (item: PointResource) => void; + /** 聚焦事件处理 */ + handleFocus: () => void; +} \ No newline at end of file