存档代码
This commit is contained in:
@@ -81,7 +81,7 @@ const isCategoryVisible = (category: PointResourceCategory, originalType?: strin
|
||||
};
|
||||
return hiddenMap[originalType || '']?.() ?? false;
|
||||
},
|
||||
};
|
||||
};
|
||||
return visibilityMap[category]?.() ?? false;
|
||||
};
|
||||
|
||||
@@ -106,15 +106,15 @@ export const useAroundButton = (): AnalysisButtonState => {
|
||||
|
||||
const loadAllPointData = (): PointResource[] => {
|
||||
const resources: PointResource[] = [];
|
||||
|
||||
|
||||
RESOURCE_CONFIGS.forEach(config => {
|
||||
const data = loadingResourceStore.getLoadingResource(config.key).info;
|
||||
if (Array.isArray(data)) {
|
||||
const convertedData = data.map((item: Record<string, unknown>) => {
|
||||
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)
|
||||
const value = (item.name && String(item.name).trim() !== '')
|
||||
? String(item.name)
|
||||
: String(safeId);
|
||||
|
||||
return {
|
||||
@@ -142,14 +142,14 @@ export const useAroundButton = (): AnalysisButtonState => {
|
||||
};
|
||||
|
||||
const calculateDistance = (
|
||||
centerLon: number,
|
||||
centerLat: number,
|
||||
pointLon: unknown,
|
||||
centerLon: number,
|
||||
centerLat: number,
|
||||
pointLon: unknown,
|
||||
pointLat: unknown
|
||||
): number => {
|
||||
const pLon = Number(pointLon);
|
||||
const pLat = Number(pointLat);
|
||||
|
||||
|
||||
if (isNaN(pLon) || isNaN(pLat)) return Infinity;
|
||||
|
||||
const dLat = (pLat - centerLat) * Math.PI / 180;
|
||||
@@ -164,13 +164,13 @@ export const useAroundButton = (): AnalysisButtonState => {
|
||||
const cartographic = Cartographic.fromCartesian(centerPosition);
|
||||
const centerLon = cartographic.longitude * (180 / Math.PI);
|
||||
const centerLat = cartographic.latitude * (180 / Math.PI);
|
||||
|
||||
|
||||
const allPoints = loadAllPointData();
|
||||
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);
|
||||
});
|
||||
@@ -181,10 +181,10 @@ export const useAroundButton = (): AnalysisButtonState => {
|
||||
*/
|
||||
const refreshPulseEffect = () => {
|
||||
if (!currentCenterPosition) return;
|
||||
|
||||
|
||||
console.log('刷新脉冲效果...');
|
||||
removePulseEffect();
|
||||
|
||||
|
||||
const pointsInCircle = getPointsInCircle(currentCenterPosition, radius.value);
|
||||
addPulseEffectToPoints(pointsInCircle);
|
||||
pulsePoints.value = pointsInCircle;
|
||||
@@ -224,18 +224,18 @@ export const useAroundButton = (): AnalysisButtonState => {
|
||||
const calculateDialogPosition = (clickPosition: Cartesian2) => {
|
||||
const screenWidth = window.innerWidth;
|
||||
const screenHeight = window.innerHeight;
|
||||
|
||||
|
||||
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));
|
||||
};
|
||||
@@ -287,16 +287,16 @@ export const useAroundButton = (): AnalysisButtonState => {
|
||||
|
||||
const flyHeight = Math.max(radius.value * FLY_HEIGHT_MULTIPLIER, MIN_FLY_HEIGHT);
|
||||
CesiumUtilsSingleton.flyToTarget([longitude, latitude, flyHeight], FLY_DURATION);
|
||||
|
||||
|
||||
// 关闭对话框并清理资源(包括鼠标样式)
|
||||
showAreaDialog.value = false;
|
||||
|
||||
|
||||
// 移除地图点击事件监听器,恢复鼠标默认样式
|
||||
if (clickHandler) {
|
||||
clickHandler.destroy();
|
||||
clickHandler = null;
|
||||
}
|
||||
|
||||
|
||||
// 恢复鼠标默认样式
|
||||
const viewer = CesiumUtilsSingleton.getViewer();
|
||||
if (viewer?.canvas) {
|
||||
@@ -313,7 +313,7 @@ export const useAroundButton = (): AnalysisButtonState => {
|
||||
|
||||
const handleButtonClick = (index: number, callback: (status: boolean) => void) => {
|
||||
const isActive = selectedButtonIndex.value === index;
|
||||
|
||||
|
||||
if (isActive) {
|
||||
selectedButtonIndex.value = -1;
|
||||
callback(false);
|
||||
@@ -327,6 +327,43 @@ export const useAroundButton = (): AnalysisButtonState => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 从搜索结果启动区域分析
|
||||
* 自动激活按钮(变为"取消区域分析"),显示区域选择对话框
|
||||
* @param point - 搜索选中的点资源
|
||||
*/
|
||||
const startAreaAnalysisFromSearch = (point: PointResource) => {
|
||||
if (point.lon == null || point.lat == null) return;
|
||||
|
||||
// 清除之前的分析
|
||||
clearAllAnalysisResources();
|
||||
|
||||
// 设置中心点
|
||||
currentCenterPosition = Cartesian3.fromDegrees(point.lon, point.lat, 0);
|
||||
|
||||
// 激活按钮(索引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;
|
||||
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));
|
||||
};
|
||||
|
||||
// ==================== 监听器 ====================
|
||||
|
||||
watch(
|
||||
@@ -377,10 +414,10 @@ export const useAroundButton = (): AnalysisButtonState => {
|
||||
console.log('标记区域分析', status);
|
||||
const viewer = CesiumUtilsSingleton.getViewer();
|
||||
if (!viewer?.canvas) return;
|
||||
|
||||
|
||||
statusStore.cursorStyle = status ? 'crosshair' : 'default';
|
||||
viewer.canvas.style.cursor = status ? 'crosshair' : 'default';
|
||||
|
||||
|
||||
if (status) {
|
||||
registerMapClickHandler();
|
||||
} else {
|
||||
@@ -411,5 +448,6 @@ export const useAroundButton = (): AnalysisButtonState => {
|
||||
refreshPulseEffect,
|
||||
pulsePoints,
|
||||
showPulsePointList,
|
||||
startAreaAnalysisFromSearch,
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -1,16 +1,12 @@
|
||||
import { computed, ref, watch } from 'vue';
|
||||
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';
|
||||
import { Cartesian3, Cartographic } from 'cesium';
|
||||
import { useCircleDrawer } from './useCircleDrawer';
|
||||
import { usePulseEffect } from './usePulseEffect';
|
||||
import { useMarkerManager } from './useMarkerManager';
|
||||
|
||||
/**
|
||||
* 周边分析搜索组件钩子函数
|
||||
* 搜索组件钩子函数
|
||||
* @returns 搜索相关的状态和方法
|
||||
*/
|
||||
export const useAroundSearch = () => {
|
||||
@@ -21,21 +17,6 @@ export const useAroundSearch = () => {
|
||||
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<PointResource | null>(null);
|
||||
const isAreaAnalysisActive = ref(false);
|
||||
const currentAnalysisCenter = ref<Cartesian3 | null>(null);
|
||||
const showPulsePointListFromSearch = ref(false);
|
||||
const pulsePointsFromSearch = ref<PointResource[]>([]);
|
||||
|
||||
// 组合子 Hook
|
||||
const { drawCircle, clearCircle } = useCircleDrawer();
|
||||
const { addPulseEffectToPoints, removePulseEffect } = usePulseEffect();
|
||||
const { addMarker, removeMarker } = useMarkerManager();
|
||||
|
||||
/**
|
||||
* 资源配置列表
|
||||
*/
|
||||
@@ -102,187 +83,19 @@ export const useAroundSearch = () => {
|
||||
/**
|
||||
* 选择建议回调
|
||||
* @param item - 选中的点资源
|
||||
* @param onAnalysisStart - 搜索后触发区域分析的回调(由父组件传入)
|
||||
*/
|
||||
async function handleSelect(item: PointResource) {
|
||||
async function handleSelect(item: PointResource, onAnalysisStart?: (point: PointResource) => void) {
|
||||
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);
|
||||
|
||||
// 飞行完成后,触发区域分析回调
|
||||
if (onAnalysisStart) {
|
||||
onAnalysisStart(item);
|
||||
}
|
||||
// 计算并添加脉冲效果
|
||||
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 }
|
||||
);
|
||||
|
||||
/**
|
||||
* 处理聚焦事件,重新加载数据
|
||||
*/
|
||||
@@ -342,16 +155,5 @@ export const useAroundSearch = () => {
|
||||
handleSelect,
|
||||
handleFocus,
|
||||
loadAllPointData,
|
||||
// 区域分析相关
|
||||
showAreaDialog,
|
||||
areaRadius,
|
||||
dialogPosition,
|
||||
isAreaAnalysisActive,
|
||||
showPulsePointListFromSearch,
|
||||
pulsePointsFromSearch,
|
||||
handleAreaConfirm,
|
||||
handleAreaCancel,
|
||||
refreshSearchPulseEffect,
|
||||
clearSearchAreaAnalysis,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user