diff --git a/frontend/.env.development b/frontend/.env.development
index b560df26..d29767aa 100644
--- a/frontend/.env.development
+++ b/frontend/.env.development
@@ -7,7 +7,7 @@ VITE_APP_TITLE = '水电水利建设项目全过程环境管理信息平台'
VITE_APP_PORT = 3000
VITE_APP_BASE_API = '/dev-api'
# 本地环境
-VITE_APP_BASE_URL = 'http://localhost:8093'
+# VITE_APP_BASE_URL = 'http://localhost:8093'
# 测试环境
# VITE_APP_BASE_URL = 'http://172.16.21.142:8093'
# 汤伟
diff --git a/frontend/public/glb/map-air.glb b/frontend/public/glb/map-air.glb
new file mode 100644
index 00000000..f4b70f77
Binary files /dev/null and b/frontend/public/glb/map-air.glb differ
diff --git a/frontend/src/api/system/disposeManage/index.ts b/frontend/src/api/system/disposeManage/index.ts
index 3b3d9a01..a226e358 100644
--- a/frontend/src/api/system/disposeManage/index.ts
+++ b/frontend/src/api/system/disposeManage/index.ts
@@ -8,6 +8,14 @@ export function threedroambGetKendoListCust(data: any) {
data: data
});
}
+export function getThreedRoamData(data: any) {
+ return request({
+ url: '/threedroamb/getThreedRoamData',
+ method: 'post',
+ data: data
+ });
+}
+
export function threedroambsave(data: any) {
return request({
url: '/threedroamb/save',
@@ -47,4 +55,4 @@ export function threedroambexport(data: any) {
data: data,
responseType: 'blob'
});
-}
\ No newline at end of file
+}
diff --git a/frontend/src/components/gis/GisView.vue b/frontend/src/components/gis/GisView.vue
index 57f2525f..c52c139b 100644
--- a/frontend/src/components/gis/GisView.vue
+++ b/frontend/src/components/gis/GisView.vue
@@ -18,6 +18,9 @@
+
+
+
+
+
diff --git a/frontend/src/components/gis/roam/data/riverline.ts b/frontend/src/components/gis/roam/data/riverline.ts
new file mode 100644
index 00000000..b9f4dbbd
--- /dev/null
+++ b/frontend/src/components/gis/roam/data/riverline.ts
@@ -0,0 +1,50 @@
+/**
+ * CZML 飞行路径模板
+ * 运行时由 ThreeDRoamManager 动态替换 position.cartographicDegrees 和 clock.interval
+ */
+export const czml_huanghe = [
+ {
+ id: 'document',
+ name: '黄河八电站漫游',
+ version: '1.0',
+ clock: {
+ interval: '2021-03-16T10:00:01Z/2021-03-16T11:12:18Z',
+ currentTime: '2021-03-16T10:00:01Z',
+ multiplier: 1
+ }
+ },
+ {
+ id: 'hhriverRoamingModel',
+ name: '黄河八电站漫游飞机',
+ description: '从上游电站开始往下游电站漫游飞行模型',
+ availability: '2021-03-16T10:00:01Z/2021-03-16T11:12:18Z',
+ model: {
+ gltf: '/glb/map-air.glb',
+ scale: 10
+ },
+ orientation: {
+ velocityReference: '#position'
+ },
+ viewFrom: {
+ cartesian: [-1480, -1315, 1079]
+ },
+ path: {
+ resolution: 1,
+ material: {
+ polylineGlow: {
+ color: {
+ rgba: [0, 0, 200, 255]
+ },
+ glowPower: 0.4
+ }
+ },
+ width: 3
+ },
+ position: {
+ interpolationAlgorithm: 'HERMITE',
+ interpolationDegree: 7,
+ epoch: '2021-03-16T10:00:01Z',
+ cartographicDegrees: []
+ }
+ }
+]
diff --git a/frontend/src/components/mapController/index.vue b/frontend/src/components/mapController/index.vue
index 28f2fd3d..4c0b1b1d 100644
--- a/frontend/src/components/mapController/index.vue
+++ b/frontend/src/components/mapController/index.vue
@@ -43,7 +43,7 @@
{
case 'dim': // 切换3D/2D
{
const switchingTo3D = uiStore.mapType === '2D';
+ // 先关闭漫游(如果有),标记跳过相机还原(由地图切换自行处理)
+ if (uiStore.isRoaming) {
+ uiStore.skipRoamCameraRestore = true;
+ uiStore.isRoaming = false;
+ }
uiStore.mapType = switchingTo3D ? '3D' : '2D';
if (switchingTo3D) {
// 2D→3D:关闭抽屉,请求倾斜摄影数据,开始过渡
@@ -405,7 +410,6 @@ const handleControllerClick = (item: any) => {
} else {
// 3D→2D:打开抽屉,清理过渡,卸载所有倾斜摄影模型
uiStore.drawerOpen = true;
- uiStore.isRoaming = false;
cleanup3DTransition();
activeKey.value = null;
// 卸载所有倾斜摄影
diff --git a/frontend/src/components/mapFilter/index.vue b/frontend/src/components/mapFilter/index.vue
index 9e495377..93af03ad 100644
--- a/frontend/src/components/mapFilter/index.vue
+++ b/frontend/src/components/mapFilter/index.vue
@@ -647,7 +647,7 @@ const focusSelectedAnchorPoint = () => {
mapOrchestrator.focusPoint(
selectedValue,
- uiStore.mapType === '3D' ? 9 : 14,
+ uiStore.mapType === '3D' ? 8 : 14,
isFishSurveyMode.value ? anchorPointOptions.value : undefined
);
};
diff --git a/frontend/src/modules/map/application/map-orchestrator.ts b/frontend/src/modules/map/application/map-orchestrator.ts
index 4c0ff19e..93785339 100644
--- a/frontend/src/modules/map/application/map-orchestrator.ts
+++ b/frontend/src/modules/map/application/map-orchestrator.ts
@@ -227,20 +227,18 @@ export const useMapOrchestrator = () => {
);
await mapStore.updateLayerData(checkedKeys, true);
mapStore.setSelectedLegendData();
- const backgroundLoadPromise = isInitialLoad
- ? mapStore.loadAllLayerData(layerConfig, checkedKeys, {
- pageToken: activePageToken,
- skipSessionCheck: true,
- pageKey
- })
- : mapStore.loadCurrentPageLayerData(layerConfig, checkedKeys, {
- pageToken: activePageToken,
- skipSessionCheck: true
- });
-
- void backgroundLoadPromise.catch(error => {
- console.error(`页面锚点后台加载失败 [${pageKey}]`, error);
- });
+ if (isInitialLoad) {
+ mapStore.loadAllLayerData(layerConfig, checkedKeys, {
+ pageToken: activePageToken,
+ skipSessionCheck: true,
+ pageKey
+ });
+ } else {
+ mapStore.loadCurrentPageLayerData(layerConfig, checkedKeys, {
+ pageToken: activePageToken,
+ skipSessionCheck: true
+ });
+ }
backgroundLoadStarted = true;
}
} finally {
@@ -297,6 +295,10 @@ export const useMapOrchestrator = () => {
const checkedKeys = mapViewStore.getCheckedLayerKeys();
await mapStore.updateLayerData(checkedKeys, true);
mapStore.setSelectedLegendData();
+ // 切换到 3D 时:先恢复图层,再停留 + 飞入中国视角(与 mountView 一致)
+ if (mapClass.flyToDefaultView) {
+ await mapClass.flyToDefaultView();
+ }
await syncZoomSensitiveState({
isHydroMenu: options.getIsHydroMenu(),
refreshEngPoint: true
@@ -419,6 +421,10 @@ export const useMapOrchestrator = () => {
bindBaseSelection();
bindZoomListener(options.getIsHydroMenu);
await loadPage({ pageKey: options.pageKey, isInitialLoad: true });
+ // 加载完边界线等动态图层后,再从地球视图飞入中国视角
+ if (mapClass.flyToDefaultView) {
+ await mapClass.flyToDefaultView();
+ }
await syncZoomSensitiveState({
isHydroMenu: options.getIsHydroMenu(),
refreshEngPoint: true
@@ -622,6 +628,8 @@ export const useMapOrchestrator = () => {
const previousPageKey = mapConfigStore.lastLoadOptions?.pageKey || '';
if (previousPageKey && previousPageKey !== pageKey) {
+ // 取消旧页面正在请求的图层数据,释放 HTTP 连接
+ mapStore.abortAllInFlightRequests();
hideCurrentVisibleLayers();
mapViewStore.setCheckedLayerKeys([]);
mapStore.setSelectedLegendData();
diff --git a/frontend/src/store/modules/map.ts b/frontend/src/store/modules/map.ts
index 99cbd6c0..7a99a910 100644
--- a/frontend/src/store/modules/map.ts
+++ b/frontend/src/store/modules/map.ts
@@ -808,7 +808,7 @@ export const useMapStore = defineStore('map', () => {
* @param items 图层数据
* @param checkedKeys 选中的图层 keys(用于优先加载)
*/
- const loadAllLayerData = async (
+ const loadAllLayerData = (
items: any[],
checkedKeys: string[] = [],
options: {
@@ -824,7 +824,6 @@ export const useMapStore = defineStore('map', () => {
const checkedTasks: Array<() => Promise> = [];
const uncheckedTasks: Array<() => Promise> = [];
const processedKeys = new Set();
- const debugStart = Date.now();
const processItems = (itemList: any[]) => {
itemList.forEach(item => {
@@ -857,97 +856,101 @@ export const useMapStore = defineStore('map', () => {
processItems(items);
- try {
- // 首页初始化:所有图层接口一次性并发下发,避免分批串行等待。
- const loadResults = await Promise.allSettled(
- [...checkedTasks, ...uncheckedTasks].map(task => task())
- );
- const failedResults = loadResults.filter(
- result => result.status === 'rejected'
- );
+ const allTasks = [...checkedTasks, ...uncheckedTasks];
- if (failedResults.length > 0) {
- console.warn(
- '部分图层数据加载失败,但不会阻断其他图层:',
- failedResults
+ // 方案三:逐个处理 —— 内部不 await 批量完成,每个请求独立释放 HTTP 连接
+ void (async () => {
+ try {
+ const loadResults = await Promise.allSettled(
+ allTasks.map(task => task())
+ );
+ const failedResults = loadResults.filter(
+ result => result.status === 'rejected'
);
- }
- 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;
+ if (failedResults.length > 0) {
+ console.warn(
+ '部分图层数据加载失败,但不会阻断其他图层:',
+ failedResults
+ );
}
- 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);
+ if (
+ (typeof currentSessionId === 'number' &&
+ !isCurrentLoadSession(currentSessionId)) ||
+ !isActivePageToken(options.pageToken)
+ ) {
+ return;
}
- mapClass.mdLayerTreeShowOrHidden(key, shouldRestoreVisible);
- }
- // 设置选中的图例数据(确保图例在所有锚点加载完成后才显示)
- setSelectedLegendData();
+ if (
+ options.pageKey &&
+ hasPendingPageNavigationAwayFrom(options.pageKey)
+ ) {
+ return;
+ }
- // 备注:初始化 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);
+ // 确保所有图层的数据都已经缓存到 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 (
+ const loadCurrentPageLayerData = (
items: any[],
checkedKeys: string[] = [],
options: {
@@ -993,14 +996,12 @@ export const useMapStore = defineStore('map', () => {
beginPageLoading(options.pageToken);
- try {
- // 菜单切换:仅当前页的勾选图层一次性并发下发,旧请求在后台继续运行。
- await Promise.allSettled(tasks.map(task => task()));
- } finally {
+ // 方案三:逐个处理 —— 不 await 批量完成,每个请求独立释放 HTTP 连接
+ void Promise.allSettled(tasks.map(task => task())).then(() => {
if (isActivePageToken(options.pageToken)) {
finishPageLoading(options.pageToken);
}
- }
+ });
};
/**
@@ -1446,6 +1447,18 @@ export const useMapStore = defineStore('map', () => {
}
};
+ /**
+ * 取消所有正在请求的图层数据,释放 HTTP 连接
+ */
+ const abortAllInFlightRequests = () => {
+ for (const [identifier, entry] of inFlightRequestMap.entries()) {
+ entry.controller.abort();
+ inFlightRequestMap.delete(identifier);
+ activeLayerRequestKeyMap.delete(entry.layerKey);
+ }
+ backgroundPointLayerCache.clear();
+ };
+
/**
* 更新搜索时间范围并重新加载描点数据
*/
@@ -1461,6 +1474,7 @@ export const useMapStore = defineStore('map', () => {
loadCurrentPageLayerData,
loadLayerData,
setSelectedLegendData,
+ abortAllInFlightRequests,
legendData,
setLegendData,
legendDataSelected,
diff --git a/frontend/src/store/modules/ui.ts b/frontend/src/store/modules/ui.ts
index d629759e..31992ca2 100644
--- a/frontend/src/store/modules/ui.ts
+++ b/frontend/src/store/modules/ui.ts
@@ -8,6 +8,8 @@ export const useUiStore = defineStore('ui', () => {
const mapSwitchCompletedTick = ref(0);
// 3d 流域三维漫游
const isRoaming = ref(false);
+ // dim 切换时跳过漫游相机还原(由地图模式切换自行处理)
+ const skipRoamCameraRestore = ref(false);
// 切换抽屉状态
const toggleDrawer = () => {
@@ -26,6 +28,7 @@ export const useUiStore = defineStore('ui', () => {
return {
drawerOpen,
isRoaming,
+ skipRoamCameraRestore,
toggleDrawer,
setDrawerOpen,
mapType,
diff --git a/frontend/src/utils/request.ts b/frontend/src/utils/request.ts
index c8e1b177..91849a70 100644
--- a/frontend/src/utils/request.ts
+++ b/frontend/src/utils/request.ts
@@ -56,10 +56,9 @@ service.interceptors.response.use(
if (status === 200) {
if (response.data.code == 401) {
message.error(response.data.msg || '请求失败');
- setTimeout(() => {
- localStorage.clear();
- window.location.href = '/login';
- }, 1000);
+ localStorage.clear();
+ useUserStoreHook().resetToken();
+ router.push('/login');
return;
} else if (response.data.code == 1) {
message.error(response.data.msg);
diff --git a/frontend/src/views/system/TiltPhotoManagement/index.vue b/frontend/src/views/system/TiltPhotoManagement/index.vue
index 98aa474e..d40ecce4 100644
--- a/frontend/src/views/system/TiltPhotoManagement/index.vue
+++ b/frontend/src/views/system/TiltPhotoManagement/index.vue
@@ -148,7 +148,7 @@ const handleDelete = (record: any) => {
zIndex: 2002,
onOk: async () => {
try {
- let res = await deleteTiltPhoto({ id: record.id });
+ let res = await deleteTiltPhoto({ stcd: record.stcd });
message.success('删除成功');
basicTable.value.refresh();
} catch (error) {