WholeProcessPlatform/frontend/src/components/mapController/index.vue

487 lines
12 KiB
Vue
Raw Normal View History

2026-03-31 14:17:30 +08:00
<template>
2026-07-31 11:13:49 +08:00
<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>
2026-03-31 14:17:30 +08:00
</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';
2026-04-22 17:53:20 +08:00
const props = defineProps<{
map: any;
onClick: (key: any, mapType: any) => void;
2026-04-22 17:53:20 +08:00
}>();
const map = toRef(props, 'map');
2026-04-02 08:56:49 +08:00
// 使用 Pinia store
const uiStore = useUiStore();
const drawerOpen = ref(uiStore.drawerOpen);
const tjVisible = ref(false);
2026-04-02 08:56:49 +08:00
// 监听 store 中的 drawerOpen 变化
2026-04-02 09:27:56 +08:00
watch(
() => uiStore.drawerOpen,
newVal => {
2026-04-02 09:27:56 +08:00
drawerOpen.value = newVal;
}
);
2026-04-02 08:56:49 +08:00
watch(
() => uiStore.mapType,
newVal => {
if (newVal !== '2D') {
tjVisible.value = false;
}
}
);
2026-03-31 14:17:30 +08:00
const isFullScreen = ref(false);
2026-04-02 08:56:49 +08:00
// 倾斜摄影数据
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();
});
2026-04-02 08:56:49 +08:00
// 响应式的控制器配置
2026-04-02 13:51:36 +08:00
const controllers: any = computed(() => [
2026-03-31 14:17:30 +08:00
{
2026-04-02 09:27:56 +08:00
children: [
{
name: '全屏',
key: 'fullScreen',
icon: isFullScreen.value ? 'exitFullScreen' : 'fullScreen'
}
]
2026-03-31 14:17:30 +08:00
},
2026-04-02 09:27:56 +08:00
!uiStore.isRoaming
? {
children: [
// {
// name: "定位",
// key: "positioning",
// icon: "iconGlobal",
// },
{
name: '放大',
key: 'zoomIn',
icon: 'zoomIn'
},
{
name: '缩小',
key: 'zoomOut',
icon: 'zoomOut'
}
]
}
: {},
2026-03-31 14:17:30 +08:00
{
2026-04-02 13:51:36 +08:00
children: [
{
name: '3D',
key: 'dim',
icon: uiStore.mapType === '2D' ? 'a-3D' : 'a-2D'
}
]
2026-03-31 14:17:30 +08:00
},
{
2026-04-02 13:51:36 +08:00
children: [
{
name: '图层管理',
key: 'layerController',
component: LayerController
}
]
2026-03-31 14:17:30 +08:00
},
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'
}
]
}
: {},
2026-03-31 14:17:30 +08:00
{
2026-04-02 13:51:36 +08:00
children: [
{
name: '下载',
key: 'screenShot',
icon: 'downLoad'
}
]
}
2026-03-31 14:17:30 +08:00
]);
2026-04-02 13:51:36 +08:00
// 添加全屏切换功能
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;
});
2026-04-02 13:51:36 +08:00
// 控制器点击事件处理
const handleControllerClick = (item: any) => {
switch (item.key) {
case 'fullScreen':
isFullScreen.value = !isFullScreen.value;
toggleFullScreen();
2026-04-22 17:53:20 +08:00
break;
case 'zoomIn':
map.value.zoomToggle('in');
2026-04-22 17:53:20 +08:00
break;
case 'zoomOut':
map.value.zoomToggle('out');
2026-04-22 17:53:20 +08:00
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);
}
2026-04-02 13:51:36 +08:00
break;
case 'threedRoam': // 流域三维漫游
if (is3DTransition.value) break;
// 互斥选中threedRoam时取消其他选中
activeKey.value = activeKey.value === 'threedRoam' ? null : 'threedRoam';
uiStore.isRoaming = !uiStore.isRoaming;
2026-04-02 13:51:36 +08:00
break;
// 可以在这里添加其他控制器的处理逻辑
case 'screenShot':
map.value.mapOutPut();
2026-04-22 17:53:20 +08:00
break;
case 'TJ':
tjVisible.value = !tjVisible.value;
props.onClick(4, null);
2026-04-22 17:53:20 +08:00
break;
2026-04-02 13:51:36 +08:00
default:
console.log(`点击了控制器: ${item.name}`);
break;
}
};
2026-03-31 14:17:30 +08:00
</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;
}
2026-03-31 14:17:30 +08:00
}
2026-04-02 13:51:36 +08:00
}
.map-controller-group:not(:first-child) {
margin-top: 10px;
2026-03-31 14:17:30 +08:00
}
}
2026-04-02 09:27:56 +08:00
</style>