WholeProcessPlatform/frontend/src/components/mapController/index.vue
2026-07-31 11:13:49 +08:00

487 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div id="map-controller" class="map-controller" :style="{ right: drawerOpen ? '480px' : '12px' }">
<div
class="map-controller-group"
v-for="(item, index) in controllers"
:key="index"
>
<template v-for="child in item.children" :key="child.key">
<!-- 组件项:直接渲染,自身已有交互逻辑 -->
<component
v-if="child.component"
:is="child.component"
:map="map"
:visible="activeKey === child.key"
:active="activeKey === child.key"
:treeData="tiltPhotoTree"
:disabled="is3DTransition && child.key === 'OSBGController'"
:countdown="countdown"
:inTransition="is3DTransition && child.key === 'OSBGController'"
@update:visible="(v: boolean) => handleComponentVisibleToggle(child.key, v)"
@loadModel="handleLoadModel"
@unloadModel="handleUnloadModel"
@flyToSite="handleFlyToSite"
/>
<!-- 非组件项:包裹 tooltip 显示名称 -->
<a-tooltip
v-else
:title="getTooltipTitle(child)"
placement="left"
:color="getTooltipColor(child)"
:open="getTooltipOpen(child)"
>
<div
class="map-controller-item"
:class="{
'is-active':
(child.key === 'TJ' && tjVisible) ||
(activeKey === child.key && child.key !== 'threedRoam'),
'is-disabled': isItemDisabled(child)
}"
@click="handleControllerClick(child)"
>
<i
class="icon iconfont"
:class="
child.key === 'threedRoam' && uiStore.isRoaming
? 'icon-closeCircle'
: 'icon-' + child.icon
"
></i>
</div>
</a-tooltip>
</template>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch, toRef, computed, onUnmounted } from 'vue';
import { useUiStore } from '@/store/modules/ui';
import Calculate from './Calculate.vue';
import LayerController from './LayerController.vue';
import OSBGController from './OSBGController.vue';
import { getAllTiltPhotoTree } from '@/api/system/map/TiltPhotoManagement';
const props = defineProps<{
map: any;
onClick: (key: any, mapType: any) => void;
}>();
const map = toRef(props, 'map');
// 使用 Pinia store
const uiStore = useUiStore();
const drawerOpen = ref(uiStore.drawerOpen);
const tjVisible = ref(false);
// 监听 store 中的 drawerOpen 变化
watch(
() => uiStore.drawerOpen,
newVal => {
drawerOpen.value = newVal;
}
);
watch(
() => uiStore.mapType,
newVal => {
if (newVal !== '2D') {
tjVisible.value = false;
}
}
);
const isFullScreen = ref(false);
// 倾斜摄影数据
const tiltPhotoTree = ref<any[]>([]);
// 将 API 返回的扁平 list 转换为树结构
const buildTiltPhotoTree = (list: any[]) => {
if (!list || list.length === 0) return [];
const baseMap = new Map<string, { baseName: string; children: any[] }>();
for (const item of list) {
const id = String(item.baseId ?? '');
if (!baseMap.has(id)) {
baseMap.set(id, { baseName: item.baseName || '', children: [] });
}
}
for (const item of list) {
const id = String(item.baseId ?? '');
const group = baseMap.get(id);
if (group) {
// 直接 push 原始对象(不用展开),确保与 addQxsyLayer 中的 item 是同一引用
// OSGB 状态osgbtitles, cartesian3, eventListener 等)都挂在这个对象上
item.checked = true;
group.children.push(item);
}
}
return Array.from(baseMap.entries())
.map(([baseId, group]) => ({
baseId,
baseName: group.baseName,
children: group.children
}))
.sort((a, b) => String(a.baseId).localeCompare(String(b.baseId)));
};
// 请求倾斜摄影树数据
const fetchTiltPhotoTree = async () => {
try {
const res = await getAllTiltPhotoTree({
filter: {
logic: 'and',
filters: [
{
field: 'url',
operator: 'contains',
dataType: 'string',
value: 'http'
},
{
field: 'sttpCode',
operator: 'eq',
dataType: 'string',
value: 'OSGB'
}
]
},
sort: [
{ field: 'rstcdStepSort', dir: 'asc' },
{ field: 'ttpwr', dir: 'desc' }
]
});
const list = res.data.data;
tiltPhotoTree.value = buildTiltPhotoTree(list);
// 全部加载倾斜摄影模型dynamicSetVisible 按 12km 距离自动控制显隐)
list.forEach((item: any) => {
map.value.addQxsyLayer?.(item);
});
} catch (err) {
console.error('获取倾斜摄影数据失败:', err);
}
};
// 倾斜摄影模型相关事件处理
const handleLoadModel = (item: any) => {
// 开关打开:加载/显示模型
map.value.qxsyChangeClick?.(item, true);
};
const handleUnloadModel = (item: any) => {
// 开关关闭:卸载/隐藏模型
map.value.qxsyChangeClick?.(item, false);
};
const handleFlyToSite = (item: any) => {
// 先加载模型(如果还没加载),再飞行定位
map.value.addQxsyLayer?.(item);
map.value.qxsyToPosition?.(item);
};
// 3D过渡动画状态
// layerController / OSBGController / threedRoam 三者互斥,最多一个被选中
const activeKey = ref<string | null>(null);
const is3DTransition = ref(false);
const countdown = ref(0);
let transitionTimer: ReturnType<typeof setTimeout> | null = null;
const start3DTransition = () => {
cleanup3DTransition();
is3DTransition.value = true;
countdown.value = 5;
const tick = () => {
countdown.value--;
if (countdown.value <= 0) {
// 过渡完成
is3DTransition.value = false;
countdown.value = 0;
// 自动选中OSBGController
activeKey.value = 'OSBGController';
transitionTimer = null;
} else {
transitionTimer = setTimeout(tick, 1000);
}
};
transitionTimer = setTimeout(tick, 1000);
};
const cleanup3DTransition = () => {
if (transitionTimer) {
clearTimeout(transitionTimer);
transitionTimer = null;
}
is3DTransition.value = false;
countdown.value = 0;
};
// Tooltip 辅助函数(仅用于非组件项,如 threedRoam
const getTooltipTitle = (child: any) => {
if (is3DTransition.value && child.key === 'threedRoam') {
return child.name + ' ' + countdown.value + 's';
}
return child.name;
};
const getTooltipColor = (child: any) => {
if (is3DTransition.value && child.key === 'threedRoam') {
return '#008BFF';
}
return undefined;
};
const getTooltipOpen = (child: any) => {
if (is3DTransition.value && child.key === 'threedRoam') {
return true;
}
return undefined;
};
const isItemDisabled = (child: any) => {
return is3DTransition.value && child.key === 'threedRoam';
};
// 组件切换回调(插槽面板 open/close互斥
const handleComponentVisibleToggle = (key: string, visible: boolean) => {
activeKey.value = visible ? key : null;
};
onUnmounted(() => {
cleanup3DTransition();
});
// 响应式的控制器配置
const controllers: any = computed(() => [
{
children: [
{
name: '全屏',
key: 'fullScreen',
icon: isFullScreen.value ? 'exitFullScreen' : 'fullScreen'
}
]
},
!uiStore.isRoaming
? {
children: [
// {
// name: "定位",
// key: "positioning",
// icon: "iconGlobal",
// },
{
name: '放大',
key: 'zoomIn',
icon: 'zoomIn'
},
{
name: '缩小',
key: 'zoomOut',
icon: 'zoomOut'
}
]
}
: {},
{
children: [
{
name: '3D',
key: 'dim',
icon: uiStore.mapType === '2D' ? 'a-3D' : 'a-2D'
}
]
},
{
children: [
{
name: '图层管理',
key: 'layerController',
component: LayerController
}
]
},
uiStore.mapType === '2D'
? {
children: [
{
name: '测量工具',
key: 'Calculate',
component: Calculate
}
]
}
: {},
uiStore.mapType === '2D'
? {
children: [
{
name: '梯级',
key: 'TJ',
icon: 'tiji'
}
]
}
: {},
uiStore.mapType === '3D'
? {
children: [
{
name: '电站倾斜摄影',
key: 'OSBGController',
component: OSBGController
}
]
}
: {},
uiStore.mapType === '3D'
? {
children: [
{
name: '流域三维漫游',
key: 'threedRoam',
icon: 'roaming'
}
]
}
: {},
{
children: [
{
name: '下载',
key: 'screenShot',
icon: 'downLoad'
}
]
}
]);
// 添加全屏切换功能
const toggleFullScreen = () => {
if (!document.fullscreenElement) {
// 进入全屏
document.documentElement.requestFullscreen().catch(err => {
console.error('进入全屏失败:', err);
isFullScreen.value = false;
});
} else {
// 退出全屏
document.exitFullscreen().catch(err => {
console.error('退出全屏失败:', err);
isFullScreen.value = true;
});
}
};
// 监听全屏状态变化
document.addEventListener('fullscreenchange', () => {
isFullScreen.value = !!document.fullscreenElement;
});
// 控制器点击事件处理
const handleControllerClick = (item: any) => {
switch (item.key) {
case 'fullScreen':
isFullScreen.value = !isFullScreen.value;
toggleFullScreen();
break;
case 'zoomIn':
map.value.zoomToggle('in');
break;
case 'zoomOut':
map.value.zoomToggle('out');
break;
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关闭抽屉请求倾斜摄影数据开始过渡
uiStore.drawerOpen = false;
fetchTiltPhotoTree();
start3DTransition();
} else {
// 3D→2D打开抽屉清理过渡卸载所有倾斜摄影模型
uiStore.drawerOpen = true;
cleanup3DTransition();
activeKey.value = null;
// 卸载所有倾斜摄影
tiltPhotoTree.value.forEach((group: any) => {
group.children?.forEach((item: any) => {
map.value.removeQxsyLayer?.(item);
});
});
tiltPhotoTree.value = [];
}
props.onClick('dim', uiStore.mapType);
}
break;
case 'threedRoam': // 流域三维漫游
if (is3DTransition.value) break;
// 互斥选中threedRoam时取消其他选中
activeKey.value = activeKey.value === 'threedRoam' ? null : 'threedRoam';
uiStore.isRoaming = !uiStore.isRoaming;
break;
// 可以在这里添加其他控制器的处理逻辑
case 'screenShot':
map.value.mapOutPut();
break;
case 'TJ':
tjVisible.value = !tjVisible.value;
props.onClick(4, null);
break;
default:
console.log(`点击了控制器: ${item.name}`);
break;
}
};
</script>
<style lang="scss" scoped>
.map-controller {
position: absolute;
right: 480px;
bottom: 114px;
z-index: 10;
.map-controller-group {
box-shadow: 0 1px 2px #00000026;
background-color: #fff;
border: none;
.map-controller-item {
height: 40px;
width: 40px;
color: #000;
line-height: 40px;
text-align: center;
position: relative;
cursor: pointer;
.iconfont {
font-size: 20px;
}
&:hover {
background-color: #005292;
color: #ffffff;
}
&.is-active {
background-color: #005292;
color: #ffffff;
}
&.is-disabled {
pointer-events: none;
opacity: 0.5;
}
}
}
.map-controller-group:not(:first-child) {
margin-top: 10px;
}
}
</style>