487 lines
12 KiB
Vue
487 lines
12 KiB
Vue
<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>
|