|
|
@@ -30,7 +30,35 @@
|
|
|
</template>
|
|
|
<el-sub-menu v-for="(data,index) in server_config" :key="data.id" :index="data.id" popper-append-to-body="false">
|
|
|
<template #title>{{data.name}}</template>
|
|
|
- <div class="box" :index="index">
|
|
|
+
|
|
|
+ <!-- 基础地理实体使用文字列表方式 -->
|
|
|
+ <template v-if="data.id === 'vectorData'">
|
|
|
+ <el-menu-item
|
|
|
+ v-for="(data2, idx) in data.children"
|
|
|
+ :key="data2.name"
|
|
|
+ :index="data.id + '-' + idx"
|
|
|
+ @dblclick="addDatas(data.id, data2)"
|
|
|
+ class="vector-data-item"
|
|
|
+ >
|
|
|
+ <span class="vector-data-name" :class="{ 'vector-data-loaded': data2.state !== 0 }">{{ data2.name }}</span>
|
|
|
+ <el-popconfirm
|
|
|
+ v-if="data2.state !== 0"
|
|
|
+ title="确定卸载吗?"
|
|
|
+ confirmButtonText="确认"
|
|
|
+ cancelButtonText="取消"
|
|
|
+ @confirm="DeleteDates(data.id, data2)"
|
|
|
+ >
|
|
|
+ <template #reference>
|
|
|
+ <el-icon class="delete-vector-data-icon" title="卸载">
|
|
|
+ <CircleClose />
|
|
|
+ </el-icon>
|
|
|
+ </template>
|
|
|
+ </el-popconfirm>
|
|
|
+ </el-menu-item>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <!-- 其他数据服务使用图片方式 -->
|
|
|
+ <div v-else class="box" :index="index">
|
|
|
<div
|
|
|
class="imgbox"
|
|
|
v-for="data2 in data.children"
|
|
|
@@ -202,18 +230,24 @@
|
|
|
<i class="iconfont iconxingcheng iconfont2"></i>
|
|
|
<span>业务场景</span>
|
|
|
</template>
|
|
|
- <el-menu-item index="business-scenario-1" @click="loadBusinessScenario('flood-control')">
|
|
|
- <span>防洪排涝</span>
|
|
|
- </el-menu-item>
|
|
|
- <el-menu-item index="business-scenario-2" @click="loadBusinessScenario('water-resource')">
|
|
|
- <span>水资源管理</span>
|
|
|
- </el-menu-item>
|
|
|
- <el-menu-item index="business-scenario-3" @click="loadBusinessScenario('project-supervision')">
|
|
|
- <span>水利工程监管</span>
|
|
|
- </el-menu-item>
|
|
|
- <el-menu-item index="business-scenario-4" @click="loadBusinessScenario('eco-environment')">
|
|
|
- <span>生态环境保护</span>
|
|
|
- </el-menu-item>
|
|
|
+ <template v-for="scene in businessScenes" :key="scene.sceneId">
|
|
|
+ <el-menu-item
|
|
|
+ :index="'business-scenario-' + scene.sceneId"
|
|
|
+ >
|
|
|
+ <template #default>
|
|
|
+ <div style="display: flex; align-items: center; width: 100%;">
|
|
|
+ <span
|
|
|
+ style="flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
|
|
|
+ @click="loadBusinessScenarioFromDB(scene)"
|
|
|
+ >{{ scene.sceneName }}</span>
|
|
|
+ <span
|
|
|
+ style="margin-left: 8px; font-size: 14px; cursor: pointer; color: #999;"
|
|
|
+ @click.stop="showDeleteConfirm(scene)"
|
|
|
+ >✕</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-menu-item>
|
|
|
+ </template>
|
|
|
<el-menu-item index="business-scenario-create" @click="handleCreateScenario" class="no-active-style">
|
|
|
<el-icon @click.stop="handleCreateScenario" class="square-plus-icon"><Plus /></el-icon>
|
|
|
<span>创建场景</span>
|
|
|
@@ -309,11 +343,12 @@ import loadingBar from "../../components/loading.vue"; //加载动画
|
|
|
import TyphoonVisualization from "../../components/typhoon-visualization/typhoon-visualization.vue"; //台风可视化组件
|
|
|
import CesiumThreeFusion from "@/components/ThreeCesiumIntegration/CesiumThreeFusion.vue"; //Three.js与Cesium融合组件
|
|
|
import ThreeModelViewer from "@/components/ThreeModelViewer/ModelViewer.vue"; //模型查看器组件
|
|
|
-import { CircleClose, Plus, CirclePlus, Setting } from '@element-plus/icons-vue'; //删除图标
|
|
|
+import { CircleClose, Plus, CirclePlus, Setting, Delete } from '@element-plus/icons-vue'; //删除图标
|
|
|
import { listModel, getModel } from '@/api/watershed/model'; //模型API
|
|
|
import { getDefaultMapConfig, saveMapConfig } from '@/api/cesium/mapConfig'; //地图配置API
|
|
|
import serviceApi from '@/api/watershed/service'; //自定义服务API
|
|
|
-import { ElMessage } from 'element-plus';
|
|
|
+import { listBusinessScene, addBusinessScene, delBusinessScene } from '@/api/cesium/businessScene'; //业务场景API
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus';
|
|
|
import { getToken } from '@/utils/auth'
|
|
|
import axios from 'axios'
|
|
|
import { onMounted, watch, computed } from 'vue'
|
|
|
@@ -368,6 +403,7 @@ export default {
|
|
|
typhoonDataUrl: '', //台风数据URL
|
|
|
loadedTyphoonId: null, //已加载的台风ID
|
|
|
showModelDialog: false, //是否显示模型弹框
|
|
|
+ businessScenes: [], //业务场景列表(从数据库读取)
|
|
|
// MVT属性弹窗相关
|
|
|
mvtPopupVisible: false, //弹窗是否可见
|
|
|
mvtPopupPosition: { x: 0, y: 0 }, //弹窗位置
|
|
|
@@ -984,6 +1020,216 @@ export default {
|
|
|
this.$emit('open-create-scenario')
|
|
|
},
|
|
|
|
|
|
+ // 从数据库刷新业务场景列表
|
|
|
+ refreshBusinessScenes() {
|
|
|
+ console.log('从数据库加载业务场景列表...')
|
|
|
+ listBusinessScene({}).then(response => {
|
|
|
+ if (response.code === 200) {
|
|
|
+ this.businessScenes = response.rows || []
|
|
|
+ console.log('业务场景列表加载成功:', this.businessScenes)
|
|
|
+ } else {
|
|
|
+ console.error('加载业务场景列表失败:', response.msg)
|
|
|
+ ElMessage.error('加载业务场景列表失败')
|
|
|
+ }
|
|
|
+ }).catch(error => {
|
|
|
+ console.error('加载业务场景列表出错:', error)
|
|
|
+ ElMessage.error('加载业务场景列表出错')
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 显示删除确认对话框
|
|
|
+ showDeleteConfirm(scene) {
|
|
|
+ console.log('显示删除确认:', scene)
|
|
|
+ ElMessageBox.confirm(
|
|
|
+ '确定要删除场景 "' + scene.sceneName + '" 吗?',
|
|
|
+ '提示',
|
|
|
+ {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ }
|
|
|
+ ).then(() => {
|
|
|
+ this.handleDeleteBusinessScene(scene.sceneId)
|
|
|
+ }).catch(() => {
|
|
|
+ ElMessage.info('已取消删除')
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 删除业务场景
|
|
|
+ handleDeleteBusinessScene(sceneId) {
|
|
|
+ console.log('删除业务场景:', sceneId)
|
|
|
+ delBusinessScene(sceneId).then(response => {
|
|
|
+ if (response.code === 200) {
|
|
|
+ ElMessage.success('业务场景删除成功')
|
|
|
+ // 刷新场景列表
|
|
|
+ this.refreshBusinessScenes()
|
|
|
+ } else {
|
|
|
+ ElMessage.error('删除业务场景失败: ' + response.msg)
|
|
|
+ }
|
|
|
+ }).catch(error => {
|
|
|
+ console.error('删除业务场景出错:', error)
|
|
|
+ ElMessage.error('删除业务场景出错')
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 从数据库加载业务场景
|
|
|
+ loadBusinessScenarioFromDB(scene) {
|
|
|
+ console.log('从数据库加载业务场景:', scene)
|
|
|
+
|
|
|
+ if (!window.viewer) {
|
|
|
+ ElMessage.error('Cesium viewer 未初始化')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!window.Cesium) {
|
|
|
+ ElMessage.error('Cesium 库未加载')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 加载第三人称相机位置(视口跳转)
|
|
|
+ if (scene.thirdPersonCameraPos && scene.thirdPersonCameraTarget) {
|
|
|
+ console.log('thirdPersonCameraPos:', scene.thirdPersonCameraPos)
|
|
|
+ console.log('thirdPersonCameraTarget:', scene.thirdPersonCameraTarget)
|
|
|
+
|
|
|
+ const cameraPos = JSON.parse(scene.thirdPersonCameraPos)
|
|
|
+ const cameraTarget = JSON.parse(scene.thirdPersonCameraTarget)
|
|
|
+ console.log('解析后的相机位置:', cameraPos, '类型:', Array.isArray(cameraPos))
|
|
|
+ console.log('解析后的目标位置:', cameraTarget, '类型:', Array.isArray(cameraTarget))
|
|
|
+
|
|
|
+ // 检查数组长度是否正确
|
|
|
+ if (!Array.isArray(cameraPos) || cameraPos.length !== 3) {
|
|
|
+ console.error('相机位置格式不正确:', cameraPos)
|
|
|
+ ElMessage.error('相机位置数据格式错误')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!Array.isArray(cameraTarget) || cameraTarget.length !== 3) {
|
|
|
+ console.error('目标位置格式不正确:', cameraTarget)
|
|
|
+ ElMessage.error('目标位置数据格式错误')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 使用 Cesium 的 flyTo 方法跳转到指定视角
|
|
|
+ const Cesium = window.Cesium
|
|
|
+ console.log('Cesium.Cartesian3:', Cesium.Cartesian3)
|
|
|
+
|
|
|
+ const posCartesian = Cesium.Cartesian3.fromArray(cameraPos)
|
|
|
+ console.log('posCartesian:', posCartesian)
|
|
|
+
|
|
|
+ const targetCartesian = Cesium.Cartesian3.fromArray(cameraTarget)
|
|
|
+ console.log('targetCartesian:', targetCartesian)
|
|
|
+
|
|
|
+ if (!posCartesian || !targetCartesian) {
|
|
|
+ console.error('Cartesian3.fromArray 返回 undefined')
|
|
|
+ ElMessage.error('坐标转换失败')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const direction = Cesium.Cartesian3.subtract(
|
|
|
+ posCartesian,
|
|
|
+ targetCartesian,
|
|
|
+ new Cesium.Cartesian3()
|
|
|
+ )
|
|
|
+ console.log('direction before normalize:', direction)
|
|
|
+
|
|
|
+ Cesium.Cartesian3.normalize(direction, direction)
|
|
|
+ console.log('direction after normalize:', direction)
|
|
|
+
|
|
|
+ // 优先使用方向向量和上向量精确恢复相机状态
|
|
|
+ if (scene.thirdPersonCameraDirection && scene.thirdPersonCameraUp) {
|
|
|
+ try {
|
|
|
+ const directionArray = JSON.parse(scene.thirdPersonCameraDirection)
|
|
|
+ const upArray = JSON.parse(scene.thirdPersonCameraUp)
|
|
|
+
|
|
|
+ if (directionArray && directionArray.length === 3 && upArray && upArray.length === 3) {
|
|
|
+ const direction = new Cesium.Cartesian3(
|
|
|
+ directionArray[0],
|
|
|
+ directionArray[1],
|
|
|
+ directionArray[2]
|
|
|
+ )
|
|
|
+ const up = new Cesium.Cartesian3(
|
|
|
+ upArray[0],
|
|
|
+ upArray[1],
|
|
|
+ upArray[2]
|
|
|
+ )
|
|
|
+
|
|
|
+ console.log('使用方向向量和上向量恢复相机状态')
|
|
|
+ console.log('方向向量:', direction)
|
|
|
+ console.log('上向量:', up)
|
|
|
+
|
|
|
+ // 使用 setView 方法精确恢复相机状态
|
|
|
+ window.viewer.camera.setView({
|
|
|
+ destination: posCartesian,
|
|
|
+ orientation: {
|
|
|
+ direction: direction,
|
|
|
+ up: up
|
|
|
+ }
|
|
|
+ })
|
|
|
+ console.log('使用方向向量和上向量恢复相机完成')
|
|
|
+ return // 使用方向向量恢复后继续后续逻辑
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('解析方向向量或上向量失败:', e)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果没有方向向量,使用 HPR 角度(数据库中已存储为弧度值)
|
|
|
+ const heading = scene.thirdPersonCameraHeading || 0
|
|
|
+ const pitch = scene.thirdPersonCameraPitch || Cesium.Math.toRadians(-90) // 默认俯视90度
|
|
|
+ const roll = scene.thirdPersonCameraRoll || 0
|
|
|
+ console.log('保存的相机朝向(弧度):', heading, pitch, roll)
|
|
|
+ console.log('保存的相机朝向(角度):', Cesium.Math.toDegrees(heading), Cesium.Math.toDegrees(pitch), Cesium.Math.toDegrees(roll))
|
|
|
+
|
|
|
+ // 使用 setView 方法设置相机位置和朝向
|
|
|
+ // 注意:数据库中保存的已经是弧度值,不需要再转换
|
|
|
+ window.viewer.camera.setView({
|
|
|
+ destination: posCartesian,
|
|
|
+ orientation: {
|
|
|
+ heading: heading,
|
|
|
+ pitch: pitch,
|
|
|
+ roll: roll
|
|
|
+ }
|
|
|
+ })
|
|
|
+ console.log('使用HPR角度恢复相机完成')
|
|
|
+ }
|
|
|
+
|
|
|
+ // 加载第一人称相机设置
|
|
|
+ if (scene.firstPersonCameraPos && scene.firstPersonCameraTarget) {
|
|
|
+ const firstPersonPos = JSON.parse(scene.firstPersonCameraPos)
|
|
|
+ const firstPersonTarget = JSON.parse(scene.firstPersonCameraTarget)
|
|
|
+ // 可以根据需要应用第一人称相机设置
|
|
|
+ console.log('第一人称相机位置:', firstPersonPos, firstPersonTarget)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 加载模型
|
|
|
+ if (scene.loadedModels) {
|
|
|
+ const models = JSON.parse(scene.loadedModels)
|
|
|
+ console.log('需要加载的模型:', models)
|
|
|
+ // 可以根据需要加载模型
|
|
|
+ }
|
|
|
+
|
|
|
+ // 加载数据服务
|
|
|
+ if (scene.dataServices) {
|
|
|
+ const services = JSON.parse(scene.dataServices)
|
|
|
+ console.log('需要加载的数据服务:', services)
|
|
|
+ // 可以根据需要加载数据服务
|
|
|
+ }
|
|
|
+
|
|
|
+ // 加载POI点
|
|
|
+ if (scene.poiPoints) {
|
|
|
+ const poiPoints = JSON.parse(scene.poiPoints)
|
|
|
+ console.log('需要加载的POI点:', poiPoints)
|
|
|
+ // 可以根据需要加载POI点
|
|
|
+ }
|
|
|
+
|
|
|
+ ElMessage.success(`已加载业务场景:${scene.sceneName}`)
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载业务场景失败:', error)
|
|
|
+ ElMessage.error('加载业务场景失败')
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
|
|
|
|
|
|
// 区分地质体组件(需销毁)和其他组件
|
|
|
@@ -1292,6 +1538,12 @@ export default {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
+ // 检查URL是否为空
|
|
|
+ if (!obj.proxiedUrl || obj.proxiedUrl.trim() === '') {
|
|
|
+ ElMessage.warning(`${obj.name} 暂无数据`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
if (obj.state === 0) {
|
|
|
// 根据数据类型选择加载方式
|
|
|
if (obj.type === 'MVT') {
|
|
|
@@ -2491,6 +2743,8 @@ export default {
|
|
|
this.fetchModels();
|
|
|
// 从数据库加载自定义服务
|
|
|
this.fetchCustomServices();
|
|
|
+ // 从数据库加载业务场景列表
|
|
|
+ this.refreshBusinessScenes();
|
|
|
});
|
|
|
|
|
|
// 延迟初始化全局点击处理器,等待 viewer 准备好
|
|
|
@@ -2501,6 +2755,16 @@ export default {
|
|
|
this.initGlobalClickHandler();
|
|
|
}
|
|
|
}, 3000);
|
|
|
+
|
|
|
+ // 监听创建场景成功事件,刷新业务场景列表
|
|
|
+ this.refreshScenesHandler = () => {
|
|
|
+ this.refreshBusinessScenes()
|
|
|
+ }
|
|
|
+ document.addEventListener('refreshBusinessScenes', this.refreshScenesHandler)
|
|
|
+ },
|
|
|
+ beforeUnmount() {
|
|
|
+ // 移除事件监听
|
|
|
+ document.removeEventListener('refreshBusinessScenes', this.refreshScenesHandler)
|
|
|
}
|
|
|
};
|
|
|
</script>
|
|
|
@@ -2852,6 +3116,41 @@ export default {
|
|
|
background-color: transparent !important;
|
|
|
}
|
|
|
|
|
|
+/* 基础地理实体菜单项样式 */
|
|
|
+.vector-data-item {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding-left: 50px !important;
|
|
|
+}
|
|
|
+
|
|
|
+.vector-data-item.is-active {
|
|
|
+ background-color: transparent !important;
|
|
|
+}
|
|
|
+
|
|
|
+.vector-data-item .vector-data-name {
|
|
|
+ flex: 1;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.vector-data-item .vector-data-name.vector-data-loaded {
|
|
|
+ color: #409eff;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.vector-data-item .delete-vector-data-icon {
|
|
|
+ margin-left: 8px;
|
|
|
+ cursor: pointer;
|
|
|
+ color: #909399;
|
|
|
+ transition: color 0.3s;
|
|
|
+}
|
|
|
+
|
|
|
+.vector-data-item .delete-vector-data-icon:hover {
|
|
|
+ color: #f56c6c;
|
|
|
+}
|
|
|
+
|
|
|
/* 模型菜单项样式 */
|
|
|
.model-menu-item {
|
|
|
display: flex;
|
|
|
@@ -3028,6 +3327,34 @@ export default {
|
|
|
margin-left: 10px;
|
|
|
}
|
|
|
|
|
|
+/* 场景名称 */
|
|
|
+.scene-name {
|
|
|
+ flex: 1;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+/* 删除图标 */
|
|
|
+.delete-icon {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ font-size: 16px;
|
|
|
+ margin-left: 8px;
|
|
|
+ cursor: pointer;
|
|
|
+ opacity: 0.6;
|
|
|
+ transition: opacity 0.2s;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.delete-icon:hover {
|
|
|
+ opacity: 1;
|
|
|
+ color: #f56c6c;
|
|
|
+}
|
|
|
+
|
|
|
// ########### 关键修复:折叠状态下 二级弹框保留原位置 + 三级强制弹出 ###########
|
|
|
// 1. 折叠状态二级弹框:强制保留原有正确位置,不做任何改动
|
|
|
:deep(.el-menu--collapse .el-sub-menu > .el-sub-menu__popper) {
|