存档代码

This commit is contained in:
2026-06-23 21:25:54 +08:00
parent 56e53977a0
commit df4049ef8d
10 changed files with 573 additions and 752 deletions
@@ -0,0 +1,336 @@
import { ref, reactive, onUnmounted, watch, computed } from 'vue';
import { useStatusStore } from '@/stores/useStatusStore';
import { useLoadingResourceStore } from '@/stores/useLoadingResourceStore';
import { RESOURCE_CONFIGS, AROUND_ANALYSIS_CONSTANTS } from '@/config/aroundAnalysisConfig';
import type { PointResource, PointResourceCategory, AnalysisButtonConfig, AroundAnalysisState } from '@/types/common/useAroundAnalysisType';
import { CesiumUtilsSingleton } from '@/utils/cesium/CesiumUtils';
import { isCategoryVisible, loadAllPointData, calculateDistance } from '@/utils/aroundAnalysisUtils';
import {
ScreenSpaceEventHandler,
ScreenSpaceEventType,
Cartesian2,
Cartographic,
Cartesian3,
} from 'cesium';
import { useCircleDrawer } from './useCircleDrawer';
import { usePulseEffect } from './usePulseEffect';
import { useMarkerManager } from './useMarkerManager';
/**
* 周边分析统一 Hook(合并按钮和搜索逻辑)
*/
export const useAroundAnalysis = (): AroundAnalysisState => {
const statusStore = useStatusStore();
// ==================== 响应式状态 ====================
const selectedButtonIndex = ref<number>(-1);
const showAreaDialog = ref(false);
const radius = ref(10);
const dialogPosition = reactive({ x: 0, y: 0 });
const pulsePoints = ref<PointResource[]>([]);
const showPulsePointList = ref(false);
const searchState = ref('');
const canSearch = computed(() => {
return RESOURCE_CONFIGS.some(config => isCategoryVisible(config.category, config.forcedType));
});
let clickHandler: ScreenSpaceEventHandler | null = null;
let currentCenterPosition: Cartesian3 | null = null;
// ==================== 组合子 Hook ====================
const { drawCircle, clearCircle } = useCircleDrawer();
const { addPulseEffectToPoints, removePulseEffect } = usePulseEffect();
const { addMarker, removeMarker } = useMarkerManager();
// ==================== 核心功能 ====================
const 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 allPoints = loadAllPointData(RESOURCE_CONFIGS);
const radiusMeters = radiusKm * 1000;
return allPoints.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 as PointResourceCategory, point.originalType);
});
};
const refreshPulseEffect = () => {
if (!currentCenterPosition) return;
removePulseEffect();
const pointsInCircle = getPointsInCircle(currentCenterPosition, radius.value);
addPulseEffectToPoints(pointsInCircle);
pulsePoints.value = pointsInCircle;
showPulsePointList.value = true;
};
const clearAllAnalysisResources = () => {
removeMarker();
clearCircle();
removePulseEffect();
currentCenterPosition = null;
pulsePoints.value = [];
showPulsePointList.value = false;
};
const clearVisualEffectsOnly = () => {
clearCircle();
removePulseEffect();
pulsePoints.value = [];
showPulsePointList.value = false;
};
// ==================== 地图事件 ====================
const registerMapClickHandler = () => {
const viewer = CesiumUtilsSingleton.getViewer();
if (!viewer) return;
clickHandler = new ScreenSpaceEventHandler(viewer.scene.canvas);
clickHandler.setInputAction((clickEvent: { position: Cartesian2 }) => {
const cartesian = viewer.camera.pickEllipsoid(clickEvent.position, viewer.scene.globe.ellipsoid);
if (cartesian) {
currentCenterPosition = cartesian;
const cartographic = Cartographic.fromCartesian(cartesian);
console.log('点击位置:', {
longitude: cartographic.longitude * (180 / Math.PI),
latitude: cartographic.latitude * (180 / Math.PI)
});
addMarker(cartesian);
showAreaDialog.value = true;
calculateDialogPosition(clickEvent.position);
}
}, ScreenSpaceEventType.LEFT_CLICK);
};
const removeMapClickHandler = () => {
if (clickHandler) {
clickHandler.destroy();
clickHandler = null;
}
};
const calculateDialogPosition = (clickPosition: Cartesian2) => {
const { innerWidth: screenWidth, innerHeight: screenHeight } = window;
const { DIALOG_WIDTH, DIALOG_HEIGHT, DIALOG_PADDING, DIALOG_OFFSET } = AROUND_ANALYSIS_CONSTANTS;
let x = clickPosition.x + DIALOG_OFFSET;
let y = clickPosition.y + DIALOG_OFFSET;
if (x + DIALOG_WIDTH > screenWidth - DIALOG_PADDING) {
x = clickPosition.x - DIALOG_WIDTH - DIALOG_OFFSET;
}
if (y + DIALOG_HEIGHT > screenHeight - DIALOG_PADDING) {
y = clickPosition.y - DIALOG_HEIGHT - DIALOG_OFFSET;
}
dialogPosition.x = Math.max(DIALOG_PADDING, Math.min(x, screenWidth - DIALOG_WIDTH - DIALOG_PADDING));
dialogPosition.y = Math.max(DIALOG_PADDING, Math.min(y, screenHeight - DIALOG_HEIGHT - DIALOG_PADDING));
};
// ==================== 事件处理 ====================
const handleConfirm = () => {
if (!currentCenterPosition) {
console.error('中心点位置不存在');
return;
}
clearVisualEffectsOnly();
drawCircle(currentCenterPosition, radius.value);
const pointsInCircle = getPointsInCircle(currentCenterPosition, radius.value);
addPulseEffectToPoints(pointsInCircle);
pulsePoints.value = pointsInCircle;
showPulsePointList.value = true;
const cartographic = Cartographic.fromCartesian(currentCenterPosition);
const longitude = cartographic.longitude * (180 / Math.PI);
const latitude = cartographic.latitude * (180 / Math.PI);
const flyHeight = Math.max(radius.value * AROUND_ANALYSIS_CONSTANTS.FLY_HEIGHT_MULTIPLIER, AROUND_ANALYSIS_CONSTANTS.MIN_FLY_HEIGHT);
CesiumUtilsSingleton.flyToTarget([longitude, latitude, flyHeight], AROUND_ANALYSIS_CONSTANTS.FLY_DURATION);
showAreaDialog.value = false;
removeMapClickHandler();
const viewer = CesiumUtilsSingleton.getViewer();
if (viewer?.canvas) {
statusStore.cursorStyle = 'default';
viewer.canvas.style.cursor = 'default';
}
};
const handleCancel = () => {
showAreaDialog.value = false;
clearAllAnalysisResources();
};
const handleButtonClick = (index: number, callback: (status: boolean) => void) => {
const isActive = selectedButtonIndex.value === index;
if (isActive) {
selectedButtonIndex.value = -1;
callback(false);
} else {
if (selectedButtonIndex.value !== -1) {
clearAllAnalysisResources();
showAreaDialog.value = false;
}
selectedButtonIndex.value = index;
callback(true);
}
};
const startAreaAnalysisFromSearch = (point: PointResource) => {
if (point.lon == null || point.lat == null) return;
clearAllAnalysisResources();
currentCenterPosition = Cartesian3.fromDegrees(point.lon, point.lat, 0);
selectedButtonIndex.value = 0;
const viewer = CesiumUtilsSingleton.getViewer();
if (viewer?.canvas) {
statusStore.cursorStyle = 'crosshair';
viewer.canvas.style.cursor = 'crosshair';
}
addMarker(currentCenterPosition);
showAreaDialog.value = true;
const centerX = window.innerWidth / 2;
const centerY = window.innerHeight / 2;
const { DIALOG_WIDTH, DIALOG_HEIGHT, DIALOG_PADDING, DIALOG_OFFSET } = AROUND_ANALYSIS_CONSTANTS;
dialogPosition.x = Math.max(DIALOG_PADDING, Math.min(centerX + DIALOG_OFFSET, window.innerWidth - DIALOG_WIDTH - DIALOG_PADDING));
dialogPosition.y = Math.max(DIALOG_PADDING, Math.min(centerY + DIALOG_OFFSET, window.innerHeight - DIALOG_HEIGHT - DIALOG_PADDING));
};
// ==================== 搜索功能 ====================
const querySearch = (queryString: string, cb: (results: PointResource[]) => void) => {
if (!canSearch.value) {
cb([]);
return;
}
const lowerQuery = queryString.toLowerCase();
const allResources = loadAllPointData(RESOURCE_CONFIGS);
const filteredResults = allResources.filter(item => {
const config = RESOURCE_CONFIGS.find(c => c.category === item.category);
let isVisible = false;
if (config) {
isVisible = isCategoryVisible(config.category, config.forcedType || item.originalType);
}
if (!isVisible) return false;
if (!queryString) return true;
return (item.value || '').toLowerCase().includes(lowerQuery);
});
cb(filteredResults);
};
const handleSelect = async (item: PointResource) => {
if (item.lon == null || item.lat == null) return;
await CesiumUtilsSingleton.flyToTarget([item.lon, item.lat, 6000]);
startAreaAnalysisFromSearch(item);
};
const handleFocus = () => {
// 触发数据刷新(如果需要)
};
// ==================== 监听器 ====================
watch(
() => useLoadingResourceStore().loadingResource,
() => {
if (currentCenterPosition && showPulsePointList.value) {
refreshPulseEffect();
}
},
{ deep: true }
);
const poi = computed(() => statusStore.poiLayers);
const map = computed(() => statusStore.mapLayers);
const infra = computed(() => statusStore.infrastructureLayers);
watch([
() => poi.value.showSchool.show,
() => poi.value.showHospital.show,
() => poi.value.showDangerSource.show,
() => poi.value.showRefugeeShelter.show,
() => poi.value.showFireStation.show,
() => poi.value.showReservePoint.show,
() => poi.value.showSubwayStation.show,
() => poi.value.showLandslideHiddenPoint.show,
() => poi.value.showDebrisFlowHiddenPoint.show,
() => poi.value.showWaterLoggingHiddenPoint.show,
() => poi.value.showFlashFloodHiddenPoint.show,
() => map.value.riskPointShow.show,
() => infra.value.showBridge.show,
() => infra.value.showReservoir.show,
], () => {
if (currentCenterPosition && showPulsePointList.value) {
refreshPulseEffect();
}
});
onUnmounted(() => {
clearAllAnalysisResources();
removeMapClickHandler();
});
// ==================== 按钮配置 ====================
const analysisButtons: AnalysisButtonConfig[] = [
{
name: '标记区域分析',
activeName: '取消区域分析',
callback: (status: boolean) => {
const viewer = CesiumUtilsSingleton.getViewer();
if (!viewer?.canvas) return;
statusStore.cursorStyle = status ? 'crosshair' : 'default';
viewer.canvas.style.cursor = status ? 'crosshair' : 'default';
if (status) {
registerMapClickHandler();
} else {
removeMapClickHandler();
clearAllAnalysisResources();
showAreaDialog.value = false;
}
},
},
{
name: '隐藏行政区划',
callback: (status: boolean) => {
statusStore.mapLayers.showAdministrativeDivision.show = !status;
},
},
];
return {
selectedButtonIndex,
showAreaDialog,
radius,
dialogPosition,
analysisButtons,
searchState,
canSearch,
pulsePoints,
showPulsePointList,
handleButtonClick,
handleConfirm,
handleCancel,
refreshPulseEffect,
startAreaAnalysisFromSearch,
querySearch,
handleSelect,
handleFocus,
};
};