Browse Source

解决水面打包不显示的问题

WQQ 11 hours ago
parent
commit
325ae944f9
37 changed files with 1672 additions and 284 deletions
  1. 4 0
      RuoYi-Vue3/.env.development
  2. 4 0
      RuoYi-Vue3/.env.production
  3. 10 0
      RuoYi-Vue3/.env.supermap
  4. 2 0
      RuoYi-Vue3/index.html
  5. 14 0
      RuoYi-Vue3/public/supermap.config.js
  6. 131 41
      RuoYi-Vue3/src/supermap-cesium-module/components/custom-service/custom-service.vue
  7. 121 154
      RuoYi-Vue3/src/supermap-cesium-module/components/layer/custom-service/custom-service.vue
  8. 37 67
      RuoYi-Vue3/src/supermap-cesium-module/config/server_config.js
  9. 132 1
      RuoYi-Vue3/src/supermap-cesium-module/js/common/layerManagement.js
  10. 23 3
      RuoYi-Vue3/src/supermap-cesium-module/js/common/waterLayer.js
  11. 5 0
      RuoYi-Vue3/src/supermap-cesium-module/js/tool/tool.js
  12. 36 7
      RuoYi-Vue3/src/supermap-cesium-module/views/layout/aside.vue
  13. 16 0
      RuoYi-Vue3/vite.config.js
  14. 33 0
      copy-dist.bat
  15. 70 0
      deploy.bat
  16. 263 0
      docs/GeoJSON加载修复说明.md
  17. 199 0
      docs/莒口水闸河道范围加载问题诊断.md
  18. 193 0
      docs/超图服务外部配置使用说明.md
  19. 123 0
      docs/超图服务配置说明.md
  20. 41 0
      nginx-production.conf
  21. 40 0
      nginx.conf.example
  22. 4 1
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/cesium/CesiumGeojsonController.java
  23. 6 0
      ruoyi-admin/src/main/resources/application-deploy.yml
  24. 1 1
      ruoyi-admin/src/main/resources/application-druid.yml
  25. 3 3
      ruoyi-admin/src/main/resources/application-prod.yml
  26. 2 2
      ruoyi-admin/src/main/resources/application.yml
  27. 36 0
      ruoyi-admin/src/main/resources/sql/export_config_data.sql
  28. 1 1
      ruoyi-admin/target/classes/application-druid.yml
  29. 2 2
      ruoyi-admin/target/classes/application.yml
  30. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysProfileController.class
  31. 0 0
      ruoyi-admin/uploads/models/geojson/12c9fe6888244d22957a13e7999cf982.geojson
  32. BIN
      ruoyi-admin/uploads/models/geojson/12c9fe6888244d22957a13e7999cf982/福建省_全省水体(河流、人工湖等)分布矢量图(Shp格式).dbf
  33. 0 1
      ruoyi-admin/uploads/models/geojson/12c9fe6888244d22957a13e7999cf982/福建省_全省水体(河流、人工湖等)分布矢量图(Shp格式).prj
  34. BIN
      ruoyi-admin/uploads/models/geojson/12c9fe6888244d22957a13e7999cf982/福建省_全省水体(河流、人工湖等)分布矢量图(Shp格式).shp
  35. BIN
      ruoyi-admin/uploads/models/geojson/12c9fe6888244d22957a13e7999cf982/福建省_全省水体(河流、人工湖等)分布矢量图(Shp格式).shx
  36. 20 0
      start-frontend-server.bat
  37. 100 0
      start-test-server.js

+ 4 - 0
RuoYi-Vue3/.env.development

@@ -6,3 +6,7 @@ VITE_APP_ENV = 'development'
 
 # 若依管理系统/开发环境
 VITE_APP_BASE_API = '/dev-api'
+
+# 超图服务配置(开发环境)
+VITE_SUPERMAP_HOST=localhost
+VITE_SUPERMAP_PORT=8090

+ 4 - 0
RuoYi-Vue3/.env.production

@@ -8,5 +8,9 @@ VITE_APP_ENV = 'production'
 VITE_APP_BASE_API = '/prod-api'
 # VITE_APP_BASE_API = 'http://127.0.0.1:8080'
 
+# 超图服务配置(生产环境)- 修改为实际服务器地址
+VITE_SUPERMAP_HOST=106.15.124.118
+VITE_SUPERMAP_PORT=8090
+
 # 是否在打包时开启压缩,支持 gzip 和 brotli
 VITE_BUILD_COMPRESS = gzip

+ 10 - 0
RuoYi-Vue3/.env.supermap

@@ -0,0 +1,10 @@
+# 超图服务配置
+# 超图服务器地址(修改为实际服务器地址)
+VITE_SUPERMAP_HOST=localhost
+VITE_SUPERMAP_PORT=8090
+
+# 完整的服务地址(如果需要自定义完整地址,取消注释)
+# VITE_SUPERMAP_BASE_URL=http://192.168.1.100:8090
+
+# 天地图Key(如果使用天地图服务)
+VITE_TIANDITU_KEY=your_tianditu_key_here

+ 2 - 0
RuoYi-Vue3/index.html

@@ -217,6 +217,8 @@
     console.log('Cesium 对象:', typeof Cesium);
   </script>
   <link rel="stylesheet" href="/Cesium/Widgets/widgets.css">
+  <!-- 引入超图服务外部配置文件(打包后可修改) -->
+  <script src="/supermap.config.js"></script>
   <script type="module" src="/src/main.js"></script>
 </body>
 

+ 14 - 0
RuoYi-Vue3/public/supermap.config.js

@@ -0,0 +1,14 @@
+// 超图服务外部配置文件
+// 打包后可以修改此文件来改变超图服务地址,无需重新打包
+window.SUPERMAP_CONFIG = {
+  // 超图服务器地址
+  host: '106.15.124.118',
+  port: '8090',
+
+  // 完整地址(可选,如果设置了则使用此地址)
+  // baseUrl: 'http://106.15.124.118:8090',
+
+  // 其他配置
+  // 天地图Key
+  tiandituKey: 'your_tianditu_key_here'
+}

+ 131 - 41
RuoYi-Vue3/src/supermap-cesium-module/components/custom-service/custom-service.vue

@@ -25,15 +25,28 @@
         />
 
         <div v-show="layersType === 'SHP'" class="shp-upload-container">
-          <input
-            type="file"
-            ref="shpFileInput"
-            accept=".zip,.shp"
-            @change="handleShpFileChange"
-            class="sm-input"
-            style="width:100%"
-          />
-          <div class="upload-tip">支持 .zip(包含.shp,.shx,.dbf等) 或单独.shp文件</div>
+          <el-upload
+            ref="shpUploadRef"
+            class="shp-upload"
+            :auto-upload="false"
+            :limit="5"
+            accept=".zip,.shp,.shx,.dbf,.prj"
+            :on-change="handleShpFileChange"
+            :on-exceed="handleShpExceed"
+            :on-remove="handleShpRemove"
+            multiple
+            drag
+          >
+            <el-icon class="el-icon--upload"><UploadFilled /></el-icon>
+            <div class="el-upload__text">
+              拖拽文件到此处或 <em>点击上传</em>
+            </div>
+            <template #tip>
+              <div class="el-upload__tip">
+                支持 .zip(包含.shp,.shx,.dbf,.prj) 或单独上传 .shp + .shx + .dbf + .prj 文件
+              </div>
+            </template>
+          </el-upload>
         </div>
 
         <input
@@ -74,9 +87,12 @@
 import layerManagement from "../../js/common/layerManagement.js";
 import tool from "../../js/tool/tool.js";
 import { watch, ref, reactive, toRefs, onBeforeUnmount, onMounted, getCurrentInstance } from "vue";
-import { uploadShp, getGeoJsonList, getGeoJsonUrl } from "../../api/cesium/geojson.js";
+import { uploadShp, getGeoJsonList, getGeoJsonUrl } from "../../../api/cesium/geojson.js";
+import { UploadFilled } from '@element-plus/icons-vue';
+import { ElMessage } from 'element-plus';
 export default {
   name: "Sm3dCustomService",
+  components: { UploadFilled },
   props: {
     //点击确定时回调
     addCallback: {
@@ -101,8 +117,8 @@ export default {
       }, 50);
     });
     
-    const shpFileInput = ref(null);
-    const shpFile = ref(null);
+    const shpUploadRef = ref(null);
+    const uploadedFiles = ref([]);
     const uploadProgress = ref(0);
     const isUploading = ref(false);
 
@@ -117,31 +133,103 @@ export default {
       isSct: false
     });
 
-    async function handleShpFileChange(event) {
-      const file = event.target.files[0];
-      if (!file) return;
+    function handleShpFileChange(file, fileList) {
+      // 过滤出有效的文件类型
+      const validFiles = fileList.filter(f => {
+        const fileName = f.name.toLowerCase();
+        return fileName.endsWith('.shp') || fileName.endsWith('.shx') || fileName.endsWith('.dbf') || fileName.endsWith('.prj') || fileName.endsWith('.zip');
+      });
       
-      const fileName = file.name.toLowerCase();
-      if (!fileName.endsWith('.zip') && !fileName.endsWith('.shp')) {
-        tool.Message.warnMsg("请上传 .zip 或 .shp 格式的文件");
-        return;
+      uploadedFiles.value = validFiles.map(f => f.raw);
+      
+      // 自动从第一个文件中推断服务名称
+      if (validFiles.length > 0 && !state.layerName) {
+        const fileName = validFiles[0].name;
+        state.layerName = fileName.replace(/\.(zip|shp|shx|dbf|prj)$/i, '');
       }
       
-      shpFile.value = file;
-      tool.Message.infoMsg("已选择文件: " + file.name);
+      // 检查是否上传了所有必要的文件
+      if (validFiles.length > 1) {
+        const fileTypes = validFiles.map(f => f.name.toLowerCase().split('.').pop());
+        const hasShp = fileTypes.includes('shp');
+        const hasShx = fileTypes.includes('shx');
+        const hasDbf = fileTypes.includes('dbf');
+        const hasPrj = fileTypes.includes('prj');
+        
+        if (!hasShp) {
+          ElMessage.warning('请上传 .shp 主文件');
+        } else if (!hasShx) {
+          ElMessage.warning('请上传 .shx 索引文件');
+        } else if (!hasDbf) {
+          ElMessage.warning('请上传 .dbf 属性文件');
+        } else if (!hasPrj) {
+          ElMessage.warning('请上传 .prj 坐标文件');
+        }
+      }
+    }
+
+    function handleShpExceed() {
+      ElMessage.warning('最多上传5个文件(.shp, .shx, .dbf, .prj 和可选的 .zip)');
+    }
+
+    function handleShpRemove() {
+      uploadedFiles.value = [];
     }
 
     async function uploadShpFile() {
-      if (!shpFile.value) {
-        tool.Message.warnMsg("请先选择SHP文件");
+      const files = uploadedFiles.value;
+      if (files.length === 0) {
+        ElMessage.warning('请先选择SHP文件');
+        return null;
+      }
+      
+      // 检查是否上传了所有必要的文件(非zip情况)
+      const fileTypes = files.map(f => f.name.toLowerCase().split('.').pop());
+      const hasZip = fileTypes.includes('zip');
+      const hasShp = fileTypes.includes('shp');
+      
+      if (!hasZip && !hasShp) {
+        ElMessage.error('请上传 .shp 主文件');
         return null;
       }
       
+      if (!hasZip) {
+        const hasShx = fileTypes.includes('shx');
+        const hasDbf = fileTypes.includes('dbf');
+        const hasPrj = fileTypes.includes('prj');
+        if (!hasShx) {
+          ElMessage.error('请上传 .shx 索引文件');
+          return null;
+        }
+        if (!hasDbf) {
+          ElMessage.error('请上传 .dbf 属性文件');
+          return null;
+        }
+        if (!hasPrj) {
+          ElMessage.error('请上传 .prj 坐标文件');
+          return null;
+        }
+      }
+      
       isUploading.value = true;
       uploadProgress.value = 0;
       
       const formData = new FormData();
-      formData.append('file', shpFile.value);
+      // 根据文件类型添加到对应的参数
+      files.forEach(file => {
+        const fileName = file.name.toLowerCase();
+        if (fileName.endsWith('.shp')) {
+          formData.append('file', file);
+        } else if (fileName.endsWith('.shx')) {
+          formData.append('shxFile', file);
+        } else if (fileName.endsWith('.dbf')) {
+          formData.append('dbfFile', file);
+        } else if (fileName.endsWith('.prj')) {
+          formData.append('prjFile', file);
+        } else if (fileName.endsWith('.zip')) {
+          formData.append('file', file);
+        }
+      });
       if (state.layerName) {
         formData.append('layerName', state.layerName);
       }
@@ -152,15 +240,18 @@ export default {
         uploadProgress.value = 100;
         
         if (result.code === 200 && result.data) {
-          tool.Message.successMsg("文件上传成功");
+          ElMessage.success("文件上传成功");
+          uploadedFiles.value = [];
+          if (shpUploadRef.value) shpUploadRef.value.clearFiles();
           return result.data;
         } else {
-          tool.Message.errorMsg(result.msg || "上传失败");
+          ElMessage.error(result.msg || "上传失败");
           return null;
         }
       } catch (error) {
         isUploading.value = false;
-        tool.Message.errorMsg("上传失败: " + error.message);
+        const errorMsg = error.response?.data?.msg || error.response?.data?.message || error.message || '未知错误';
+        ElMessage.error("上传失败: " + errorMsg);
         return null;
       }
     }
@@ -183,12 +274,17 @@ export default {
     }
 
     async function addShpLayer() {
-      const geoJsonUrl = await uploadShpFile();
-      if (!geoJsonUrl) return;
+      const uploadResult = await uploadShpFile();
+      if (!uploadResult) return;
+      
+      const geoJsonUrl = typeof uploadResult === 'string' ? uploadResult : uploadResult.url;
+      const serviceId = typeof uploadResult === 'object' ? uploadResult.id : null;
       
       const serviceInfo = {
         url: geoJsonUrl,
-        type: 'GEOJSON'
+        id: serviceId,
+        type: 'GEOJSON',
+        layerName: state.layerName || 'SHP_Data'
       };
       
       layerManagement.addGeoJsonLayer(geoJsonUrl, state.layerName || 'SHP_Data', (geoJsonLayer) => {
@@ -212,7 +308,7 @@ export default {
 
     function check() {
       if (state.layersType === 'SHP') {
-        if (!shpFile.value) {
+        if (uploadedFiles.value.length === 0) {
           tool.Message.warnMsg("请先选择SHP文件");
           return false;
         }
@@ -352,7 +448,10 @@ export default {
       addLayer,
       clear,
       handleShpFileChange,
-      shpFileInput,
+      handleShpExceed,
+      handleShpRemove,
+      shpUploadRef,
+      uploadedFiles,
       uploadProgress,
       isUploading
     };
@@ -365,13 +464,4 @@ export default {
   width: 100%;
   margin-bottom: 10px;
 }
-
-.upload-tip {
-  font-size: 12px;
-  color: #999;
-  margin-top: 5px;
-}
 </style>
-
-
-

+ 121 - 154
RuoYi-Vue3/src/supermap-cesium-module/components/layer/custom-service/custom-service.vue

@@ -25,51 +25,28 @@
         />
 
         <div v-show="layersType === 'SHP'" class="shp-upload-container">
-          <div class="shp-file-item">
-            <span class="file-label">主文件(.shp):</span>
-            <input
-              type="file"
-              ref="shpFileInput"
-              accept=".shp"
-              @change="handleShpFileChange"
-              class="sm-input"
-              style="width:100%"
-            />
-          </div>
-          <div class="shp-file-item">
-            <span class="file-label">索引文件(.shx):</span>
-            <input
-              type="file"
-              ref="shxFileInput"
-              accept=".shx"
-              @change="handleShxFileChange"
-              class="sm-input"
-              style="width:100%"
-            />
-          </div>
-          <div class="shp-file-item">
-            <span class="file-label">属性文件(.dbf):</span>
-            <input
-              type="file"
-              ref="dbfFileInput"
-              accept=".dbf"
-              @change="handleDbfFileChange"
-              class="sm-input"
-              style="width:100%"
-            />
-          </div>
-          <div class="shp-file-item">
-            <span class="file-label">坐标文件(.prj):</span>
-            <input
-              type="file"
-              ref="prjFileInput"
-              accept=".prj"
-              @change="handlePrjFileChange"
-              class="sm-input"
-              style="width:100%"
-            />
-          </div>
-          <div class="upload-tip">必须同时上传 .shp、.shx、.dbf、.prj 四个文件</div>
+          <el-upload
+            ref="shpUploadRef"
+            class="shp-upload"
+            :auto-upload="false"
+            :limit="5"
+            accept=".zip,.shp,.shx,.dbf,.prj"
+            :on-change="handleShpFileChange"
+            :on-exceed="handleShpExceed"
+            :on-remove="handleShpRemove"
+            multiple
+            drag
+          >
+            <el-icon class="el-icon--upload"><UploadFilled /></el-icon>
+            <div class="el-upload__text">
+              拖拽文件到此处或 <em>点击上传</em>
+            </div>
+            <template #tip>
+              <div class="el-upload__tip">
+                支持 .zip(包含.shp,.shx,.dbf,.prj) 或单独上传 .shp + .shx + .dbf + .prj 文件
+              </div>
+            </template>
+          </el-upload>
         </div>
 
         <input
@@ -111,8 +88,11 @@ import layerManagement from "../../../js/common/layerManagement.js";
 import tool from "../../../js/tool/tool.js";
 import { watch, ref, reactive, toRefs, onBeforeUnmount, onMounted, getCurrentInstance } from "vue";
 import { uploadShp, getGeoJsonList, getGeoJsonUrl } from "@/api/cesium/geojson.js";
+import { UploadFilled } from '@element-plus/icons-vue';
+import { ElMessage } from 'element-plus';
 export default {
   name: "Sm3dCustomService",
+  components: { UploadFilled },
   props: {
     //点击确定时回调
     addCallback: {
@@ -137,16 +117,8 @@ export default {
       }, 50);
     });
     
-    const shpFileInput = ref(null);
-    const shxFileInput = ref(null);
-    const dbfFileInput = ref(null);
-    const prjFileInput = ref(null);
-    
-    const shpFile = ref(null);
-    const shxFile = ref(null);
-    const dbfFile = ref(null);
-    const prjFile = ref(null);
-    
+    const shpUploadRef = ref(null);
+    const uploadedFiles = ref([]);
     const uploadProgress = ref(0);
     const isUploading = ref(false);
 
@@ -161,91 +133,103 @@ export default {
       isSct: false
     });
 
-    async function handleShpFileChange(event) {
-      const file = event.target.files[0];
-      if (!file) return;
-      
-      const fileName = file.name.toLowerCase();
-      if (!fileName.endsWith('.shp')) {
-        tool.Message.warnMsg("请上传 .shp 格式的文件");
-        return;
-      }
+    function handleShpFileChange(file, fileList) {
+      // 过滤出有效的文件类型
+      const validFiles = fileList.filter(f => {
+        const fileName = f.name.toLowerCase();
+        return fileName.endsWith('.shp') || fileName.endsWith('.shx') || fileName.endsWith('.dbf') || fileName.endsWith('.prj') || fileName.endsWith('.zip');
+      });
       
-      shpFile.value = file;
-      tool.Message.infoMsg("已选择主文件: " + file.name);
-    }
-    
-    async function handleShxFileChange(event) {
-      const file = event.target.files[0];
-      if (!file) return;
+      uploadedFiles.value = validFiles.map(f => f.raw);
       
-      const fileName = file.name.toLowerCase();
-      if (!fileName.endsWith('.shx')) {
-        tool.Message.warnMsg("请上传 .shx 格式的文件");
-        return;
+      // 自动从第一个文件中推断服务名称
+      if (validFiles.length > 0 && !state.layerName) {
+        const fileName = validFiles[0].name;
+        state.layerName = fileName.replace(/\.(zip|shp|shx|dbf|prj)$/i, '');
       }
       
-      shxFile.value = file;
-      tool.Message.infoMsg("已选择索引文件: " + file.name);
-    }
-    
-    async function handleDbfFileChange(event) {
-      const file = event.target.files[0];
-      if (!file) return;
-      
-      const fileName = file.name.toLowerCase();
-      if (!fileName.endsWith('.dbf')) {
-        tool.Message.warnMsg("请上传 .dbf 格式的文件");
-        return;
+      // 检查是否上传了所有必要的文件
+      if (validFiles.length > 1) {
+        const fileTypes = validFiles.map(f => f.name.toLowerCase().split('.').pop());
+        const hasShp = fileTypes.includes('shp');
+        const hasShx = fileTypes.includes('shx');
+        const hasDbf = fileTypes.includes('dbf');
+        const hasPrj = fileTypes.includes('prj');
+        
+        if (!hasShp) {
+          ElMessage.warning('请上传 .shp 主文件');
+        } else if (!hasShx) {
+          ElMessage.warning('请上传 .shx 索引文件');
+        } else if (!hasDbf) {
+          ElMessage.warning('请上传 .dbf 属性文件');
+        } else if (!hasPrj) {
+          ElMessage.warning('请上传 .prj 坐标文件');
+        }
       }
-      
-      dbfFile.value = file;
-      tool.Message.infoMsg("已选择属性文件: " + file.name);
     }
-    
-    async function handlePrjFileChange(event) {
-      const file = event.target.files[0];
-      if (!file) return;
-      
-      const fileName = file.name.toLowerCase();
-      if (!fileName.endsWith('.prj')) {
-        tool.Message.warnMsg("请上传 .prj 格式的文件");
-        return;
-      }
-      
-      prjFile.value = file;
-      tool.Message.infoMsg("已选择坐标文件: " + file.name);
+
+    function handleShpExceed() {
+      ElMessage.warning('最多上传5个文件(.shp, .shx, .dbf, .prj 和可选的 .zip)');
+    }
+
+    function handleShpRemove() {
+      uploadedFiles.value = [];
     }
 
     async function uploadShpFile() {
-      if (!shpFile.value) {
-        tool.Message.warnMsg("请先选择SHP主文件");
+      const files = uploadedFiles.value;
+      if (files.length === 0) {
+        ElMessage.warning('请先选择SHP文件');
         return null;
       }
       
-      if (!shxFile.value) {
-        tool.Message.warnMsg("请选择SHX索引文件");
-        return null;
-      }
+      // 检查是否上传了所有必要的文件(非zip情况)
+      const fileTypes = files.map(f => f.name.toLowerCase().split('.').pop());
+      const hasZip = fileTypes.includes('zip');
+      const hasShp = fileTypes.includes('shp');
       
-      if (!dbfFile.value) {
-        tool.Message.warnMsg("请选择DBF属性文件");
+      if (!hasZip && !hasShp) {
+        ElMessage.error('请上传 .shp 主文件');
         return null;
       }
       
-      if (!prjFile.value) {
-        tool.Message.warnMsg("请选择PRJ坐标文件");
-        return null;
+      if (!hasZip) {
+        const hasShx = fileTypes.includes('shx');
+        const hasDbf = fileTypes.includes('dbf');
+        const hasPrj = fileTypes.includes('prj');
+        if (!hasShx) {
+          ElMessage.error('请上传 .shx 索引文件');
+          return null;
+        }
+        if (!hasDbf) {
+          ElMessage.error('请上传 .dbf 属性文件');
+          return null;
+        }
+        if (!hasPrj) {
+          ElMessage.error('请上传 .prj 坐标文件');
+          return null;
+        }
       }
       
       isUploading.value = true;
       uploadProgress.value = 0;
       
       const formData = new FormData();
-      formData.append('file', shpFile.value);
-      formData.append('shxFile', shxFile.value);
-      formData.append('dbfFile', dbfFile.value);
-      formData.append('prjFile', prjFile.value);
+      // 根据文件类型添加到对应的参数
+      files.forEach(file => {
+        const fileName = file.name.toLowerCase();
+        if (fileName.endsWith('.shp')) {
+          formData.append('file', file);
+        } else if (fileName.endsWith('.shx')) {
+          formData.append('shxFile', file);
+        } else if (fileName.endsWith('.dbf')) {
+          formData.append('dbfFile', file);
+        } else if (fileName.endsWith('.prj')) {
+          formData.append('prjFile', file);
+        } else if (fileName.endsWith('.zip')) {
+          formData.append('file', file);
+        }
+      });
       if (state.layerName) {
         formData.append('layerName', state.layerName);
       }
@@ -256,23 +240,18 @@ export default {
         uploadProgress.value = 100;
         
         if (result.code === 200 && result.data) {
-          tool.Message.successMsg("文件上传成功");
-          shpFile.value = null;
-          shxFile.value = null;
-          dbfFile.value = null;
-          prjFile.value = null;
-          if (shpFileInput.value) shpFileInput.value.value = '';
-          if (shxFileInput.value) shxFileInput.value.value = '';
-          if (dbfFileInput.value) dbfFileInput.value.value = '';
-          if (prjFileInput.value) prjFileInput.value.value = '';
+          ElMessage.success("文件上传成功");
+          uploadedFiles.value = [];
+          if (shpUploadRef.value) shpUploadRef.value.clearFiles();
           return result.data;
         } else {
-          tool.Message.errorMsg(result.msg || "上传失败");
+          ElMessage.error(result.msg || "上传失败");
           return null;
         }
       } catch (error) {
         isUploading.value = false;
-        tool.Message.errorMsg("上传失败: " + error.message);
+        const errorMsg = error.response?.data?.msg || error.response?.data?.message || error.message || '未知错误';
+        ElMessage.error("上传失败: " + errorMsg);
         return null;
       }
     }
@@ -295,12 +274,17 @@ export default {
     }
 
     async function addShpLayer() {
-      const geoJsonUrl = await uploadShpFile();
-      if (!geoJsonUrl) return;
+      const uploadResult = await uploadShpFile();
+      if (!uploadResult) return;
+      
+      const geoJsonUrl = typeof uploadResult === 'string' ? uploadResult : uploadResult.url;
+      const serviceId = typeof uploadResult === 'object' ? uploadResult.id : null;
       
       const serviceInfo = {
         url: geoJsonUrl,
-        type: 'GEOJSON'
+        id: serviceId,
+        type: 'GEOJSON',
+        layerName: state.layerName || 'SHP_Data'
       };
       
       layerManagement.addGeoJsonLayer(geoJsonUrl, state.layerName || 'SHP_Data', (geoJsonLayer) => {
@@ -324,7 +308,7 @@ export default {
 
     function check() {
       if (state.layersType === 'SHP') {
-        if (!shpFile.value) {
+        if (uploadedFiles.value.length === 0) {
           tool.Message.warnMsg("请先选择SHP文件");
           return false;
         }
@@ -464,7 +448,10 @@ export default {
       addLayer,
       clear,
       handleShpFileChange,
-      shpFileInput,
+      handleShpExceed,
+      handleShpRemove,
+      shpUploadRef,
+      uploadedFiles,
       uploadProgress,
       isUploading
     };
@@ -477,24 +464,4 @@ export default {
   width: 100%;
   margin-bottom: 10px;
 }
-
-.shp-file-item {
-  margin-bottom: 8px;
-}
-
-.file-label {
-  display: block;
-  font-size: 12px;
-  color: #666;
-  margin-bottom: 4px;
-}
-
-.upload-tip {
-  font-size: 12px;
-  color: #e6a23c;
-  margin-top: 5px;
-}
 </style>
-
-
-

+ 37 - 67
RuoYi-Vue3/src/supermap-cesium-module/config/server_config.js

@@ -1,3 +1,24 @@
+// 超图服务地址配置
+// 支持外部配置文件动态修改,优先级:外部配置 > 环境变量 > 默认值
+
+// 从外部配置文件获取配置(打包后可修改)
+let externalConfig = {}
+if (typeof window !== 'undefined' && window.SUPERMAP_CONFIG) {
+  externalConfig = window.SUPERMAP_CONFIG
+}
+
+// 配置优先级:外部配置 > 环境变量 > 默认值
+const SUPERMAP_HOST = externalConfig.host || import.meta.env.VITE_SUPERMAP_HOST || 'localhost'
+const SUPERMAP_PORT = externalConfig.port || import.meta.env.VITE_SUPERMAP_PORT || '8090'
+const SUPERMAP_BASE_URL = externalConfig.baseUrl || import.meta.env.VITE_SUPERMAP_BASE_URL || `http://${SUPERMAP_HOST}:${SUPERMAP_PORT}`
+
+console.log('超图服务配置:', {
+  source: externalConfig.host ? '外部配置文件' : '环境变量',
+  host: SUPERMAP_HOST,
+  port: SUPERMAP_PORT,
+  baseUrl: SUPERMAP_BASE_URL
+})
+
 export default [
   {
     id: "vectorData",
@@ -6,8 +27,7 @@ export default [
       {
         type: "MVT",
         thumbnail: "/img/componentsImg/mvt.png",
-        proxiedUrl:
-          "http://localhost:8090/iserver/services/fj_building_vector_tile/restjsr/v1/vectortile/maps/building",
+        proxiedUrl: `${SUPERMAP_BASE_URL}/iserver/services/fj_building_vector_tile/restjsr/v1/vectortile/maps/building`,
         name: "建筑",
         layers: [{ type: "MVT", layerName: "建筑" }],
         state: 0,
@@ -15,20 +35,15 @@ export default [
       {
         type: "MVT",
         thumbnail: "/img/webServeImg/MVT.jpg",
-        proxiedUrl:
-          "http://localhost:8090/iserver/services/fj_soli_vector_tile/restjsr/v1/vectortile/maps/soil",
+        proxiedUrl: `${SUPERMAP_BASE_URL}/iserver/services/fj_soli_vector_tile/restjsr/v1/vectortile/maps/soil`,
         name: "土壤",
         layers: [{ type: "MVT", layerName: "土壤" }],
-        // 新增:绑定该MVT对应的RESTFEATURE数据服务地址
-        // dataSourceUrl:
-        //   "http://192.168.0.103:8090/iserver/services/data-shapefile-turang2/rest/data/datasources/fujian-turang/datasets/fujian-turang",
         state: 0,
       },
       {
         type: "MVT",
         thumbnail: "/img/componentsImg/mvt.png",
-        proxiedUrl:
-          "http://localhost:8090/iserver/services/fj_river_vector_tile/restjsr/v1/vectortile/maps/river",
+        proxiedUrl: `${SUPERMAP_BASE_URL}/iserver/services/fj_river_vector_tile/restjsr/v1/vectortile/maps/river`,
         name: "河流",
         layers: [{ type: "MVT", layerName: "水系" }],
         state: 0,
@@ -42,8 +57,7 @@ export default [
       {
         type: "MVT",
         thumbnail: "/img/componentsImg/mvt.png",
-        proxiedUrl:
-          "http://localhost:8090/iserver/services/fj_liuyu_vector_tile/restjsr/v1/vectortile/maps/liuyu",
+        proxiedUrl: `${SUPERMAP_BASE_URL}/iserver/services/fj_liuyu_vector_tile/restjsr/v1/vectortile/maps/liuyu`,
         name: "流域分区",
         layers: [{ type: "MVT", layerName: "流域" }],
         state: 0,
@@ -142,13 +156,10 @@ export default [
     id: "webServe",
     name: "公共服务",
     children: [
-      // layers:删除图层配置
-      // style:图层风格配置
       {
         type: "REALSPACE",
         thumbnail: "/img/webServeImg/CBD.png",
-        proxiedUrl:
-          "https://www.supermapol.com/realspace/services/3D-CBD/rest/realspace",
+        proxiedUrl: "https://www.supermapol.com/realspace/services/3D-CBD/rest/realspace",
         name: "CBD",
         layers: [
           { type: "S3M", layerName: "Building@CBD" },
@@ -164,8 +175,7 @@ export default [
       {
         type: "REALSPACE",
         thumbnail: "/img/webServeImg/铁岭.png",
-        proxiedUrl:
-          "https://www.supermapol.com/realspace/services/3D-QingXieSheYingMoXing/rest/realspace",
+        proxiedUrl: "https://www.supermapol.com/realspace/services/3D-QingXieSheYingMoXing/rest/realspace",
         name: "倾斜摄影模型",
         layers: [{ type: "S3M", layerName: "qingxie" }],
         state: 0,
@@ -173,8 +183,7 @@ export default [
       {
         type: "REALSPACE",
         thumbnail: "/img/webServeImg/白模.png",
-        proxiedUrl:
-          "https://www.supermapol.com/realspace/services/3D-CQmodel_wireframe_2000/rest/realspace",
+        proxiedUrl: "https://www.supermapol.com/realspace/services/3D-CQmodel_wireframe_2000/rest/realspace",
         name: "白模",
         layers: [{ type: "S3M", layerName: "CQmodel" }],
         style: { fillStyle: "Fill_And_WireFrame" },
@@ -183,25 +192,15 @@ export default [
       {
         type: "REALSPACE",
         thumbnail: "/img/webServeImg/BIM.png",
-        proxiedUrl:
-          "https://www.supermapol.com/realspace/services/3D-wireFrame/rest/realspace",
+        proxiedUrl: "https://www.supermapol.com/realspace/services/3D-wireFrame/rest/realspace",
         name: "BIM建筑",
         layers: [{ type: "S3M", layerName: "wireFrame" }],
         state: 0,
       },
-      // {
-      //     type: "REALSPACE",
-      //     thumbnail: "/img/webServeImg/四姑娘山.png",
-      //     proxiedUrl: 'http://www.supermapol.com/realspace/services/3D-dixingyingxiang/rest/realspace',
-      //     name: "四姑娘山",
-      //     layers: [{ type: 'TERRAIN', layerName: 'DatasetDEM' }, { type: 'IMG', layerName: 'MosaicResult' }],
-      //     state: 0
-      // },
       {
         type: "REALSPACE",
         thumbnail: "/img/webServeImg/珠峰.png",
-        proxiedUrl:
-          "https://www.supermapol.com/realspace/services/3D-ZF_normal/rest/realspace",
+        proxiedUrl: "https://www.supermapol.com/realspace/services/3D-ZF_normal/rest/realspace",
         name: "珠峰地形影像",
         layers: [
           { type: "IMG", layerName: "image" },
@@ -209,17 +208,10 @@ export default [
         ],
         state: 0,
       },
-      // 水下地形(超图iServer本地发布的3D-SXDX_DEM服务),
-      // 沿用珠峰"在已有地形上叠加另一层"的做法:作为 REALSPACE 场景加载,
-      // 内部会调用 addScene -> viewer.scene.open(),以 S3M 图层形式叠加到现有地形之上
-      // useServerView: true 表示使用超图服务端默认视口
-      // transparentColor: '#000000' 表示将黑色设为透明色
-      // transparentColorTolerance: 0.2 增大容差值以处理接近黑色的区域
       {
         type: "REALSPACE",
         thumbnail: "/img/webServeImg/珠峰.png",
-        proxiedUrl:
-          "http://localhost:8090/iserver/services/SXDX-yx/rest/realspace",
+        proxiedUrl: `${SUPERMAP_BASE_URL}/iserver/services/SXDX-yx/rest/realspace`,
         name: "水下地形",
         layers: [{ type: "S3M", layerName: "SXDX@fujian" }],
         state: 0,
@@ -230,8 +222,7 @@ export default [
       {
         type: "REALSPACE",
         thumbnail: "/img/webServeImg/点云.png",
-        proxiedUrl:
-          "https://www.supermapol.com/realspace/services/3D-cloud/rest/realspace",
+        proxiedUrl: "https://www.supermapol.com/realspace/services/3D-cloud/rest/realspace",
         name: "点云",
         layers: [{ type: "S3M", layerName: "POINTCLOUD23" }],
         state: 0,
@@ -239,8 +230,7 @@ export default [
       {
         type: "MVT",
         thumbnail: "/img/webServeImg/MVT.jpg",
-        proxiedUrl:
-          "https://www.supermapol.com/realspace/services/map-mvt-JingJinDiQuDiTu/restjsr/v1/vectortile/maps/%E4%BA%AC%E6%B4%A5%E5%9C%B0%E5%8C%BA%E5%9C%B0%E5%9B%BE",
+        proxiedUrl: "https://www.supermapol.com/realspace/services/map-mvt-JingJinDiQuDiTu/restjsr/v1/vectortile/maps/%E4%BA%AC%E6%B4%A5%E5%9C%B0%E5%8C%BA%E5%9C%B0%E5%9B%BE",
         name: "京津地区MVT",
         layers: [{ type: "MVT", layerName: "京津地区MVT" }],
         state: 0,
@@ -282,33 +272,15 @@ export default [
         title: "Open Street Map",
         type: "OSM",
         state: 0,
-        // serviceType : Resource.osmServiceType
       },
-      // {
-      //     proxiedUrl : 'https://www.supermapol.com/iserver/services/map_China/rest/maps/China_Dark',
-      //     name : 'SuperMap China_Dark Map',
-      //     thumbnail : '/img/baseLayer/SuperMapDark.png',
-      //     title : 'SuperMap China_Dark Map',
-      //     type : 'SUPERMAPDARK',
-      //     state: 0
-      // },
       {
-        proxiedUrl:
-          "https://www.supermapol.com/iserver/services/map_China/rest/maps/China_Light",
+        proxiedUrl: "https://www.supermapol.com/iserver/services/map_China/rest/maps/China_Light",
         name: "Grid Image Map",
         thumbnail: "/img/baseLayer/grad.png",
         title: "Grid Image Map",
         type: "GRIDIMAGERY",
         state: 0,
       },
-      // {
-      //     proxiedUrl : 'https://www.supermapol.com/iserver/services/map_China/rest/maps/China_Light',
-      //     name : 'SuperMap China_Light Map',
-      //     thumbnail : '/img/baseLayer/SuperMapLight.png',
-      //     title : 'SuperMap China_Light Map',
-      //     type : 'SUPERMAPLIGHT',
-      //     state: 0
-      // }
     ],
   },
   {
@@ -316,8 +288,7 @@ export default [
     name: "在线地形",
     children: [
       {
-        proxiedUrl:
-          "https://maptiles.supermapol.com/iserver/services/3D-local3DCache-GlobalTIN30M/rest/realspace/datas/Global_TIN_30M",
+        proxiedUrl: "https://maptiles.supermapol.com/iserver/services/3D-local3DCache-GlobalTIN30M/rest/realspace/datas/Global_TIN_30M",
         name: "SuperMapOnline 地形",
         thumbnail: "/img/baseLayer/SuperMapOnline.png",
         title: "Online地形",
@@ -333,8 +304,7 @@ export default [
         state: 0,
       },
       {
-        proxiedUrl:
-          "https://www.supermapol.com/realspace/services/3D-stk_terrain/rest/realspace/datas/info/data/path",
+        proxiedUrl: "https://www.supermapol.com/realspace/services/3D-stk_terrain/rest/realspace/datas/info/data/path",
         name: "STK地形",
         thumbnail: "/img/baseLayer/STK地形.png",
         title: "STK地形",
@@ -343,4 +313,4 @@ export default [
       },
     ],
   },
-];
+]

+ 132 - 1
RuoYi-Vue3/src/supermap-cesium-module/js/common/layerManagement.js

@@ -1,5 +1,6 @@
 import { actions, storeDate } from '../store/store.js'   //局部状态管理
 import { fetchFeaturesFromSuperMap, loadDatasetFeatures, loadSuperMapRestDataService } from './vectorDataLoader.js';
+import { getToken } from '@/utils/auth';
 
 // 添加s3m
 function addS3mLayers(scps, callback) {
@@ -497,6 +498,10 @@ function validateGeoJsonCoordinates(coordinates, geometryType) {
 
 // 加载GeoJson图层
 function addGeoJsonLayer(url, name, callback) {
+    console.log('========== addGeoJsonLayer 开始执行 ==========');
+    console.log('URL:', url);
+    console.log('Name:', name);
+
     try {
         // 判断是否为超图REST数据服务
         if (url.includes('/iserver/services/') && url.includes('/rest/data/datasources')) {
@@ -504,6 +509,17 @@ function addGeoJsonLayer(url, name, callback) {
             return;
         }
 
+        // 检查是否为本地文件路径(需要通过API加载)
+        const isLocalPath = url.startsWith('/uploads/') || url.startsWith('uploads/') || url.startsWith('./uploads/');
+
+        if (isLocalPath) {
+            console.log('检测到本地GeoJSON文件,通过API加载:', url);
+            loadGeoJsonViaApi(url, name, null, callback);
+            return;
+        }
+
+        // 外部URL直接加载
+        console.log('外部URL直接加载:', url);
         const loadPromise = Cesium.GeoJsonDataSource.load(url, {
             stroke: Cesium.Color.fromCssColorString('#0055FF').withAlpha(1),
             fill: Cesium.Color.fromCssColorString('#00FF00').withAlpha(0.4),
@@ -514,9 +530,11 @@ function addGeoJsonLayer(url, name, callback) {
         Cesium.when(loadPromise, function (dataSource) {
             try {
                 viewer.dataSources.add(dataSource);
+                dataSource.name = name;
                 processGeoJsonEntities(dataSource, name);
                 viewer.flyTo(dataSource, { duration: 2 });
                 actions.setChangeLayers();
+                console.log('GeoJSON图层加载成功:', name);
                 if (callback) callback(dataSource);
             } catch (e) {
                 console.error("添加GeoJSON数据源失败:", e);
@@ -534,9 +552,14 @@ function addGeoJsonLayer(url, name, callback) {
 
 // 加载GeoJson图层(支持自定义样式)
 function addGeoJsonLayerWithStyle(url, name, style, callback) {
+    console.log('========== addGeoJsonLayerWithStyle 开始执行 ==========');
+    console.log('URL:', url);
+    console.log('Name:', name);
+    console.log('Style:', style);
+
     // 存储当前样式配置供processGeoJsonEntities使用
     window.currentGeoJsonStyle = style;
-    
+
     try {
         // 判断是否为超图REST数据服务
         if (url.includes('/iserver/services/') && url.includes('/rest/data/datasources')) {
@@ -544,6 +567,17 @@ function addGeoJsonLayerWithStyle(url, name, style, callback) {
             return;
         }
 
+        // 检查是否为本地文件路径(需要通过API加载)
+        const isLocalPath = url.startsWith('/uploads/') || url.startsWith('uploads/') || url.startsWith('./uploads/');
+
+        if (isLocalPath) {
+            console.log('检测到本地GeoJSON文件,通过API加载:', url);
+            loadGeoJsonViaApi(url, name, style, callback);
+            return;
+        }
+
+        // 外部URL直接加载
+        console.log('外部URL直接加载:', url);
         const fillColor = Cesium.Color.fromCssColorString(style.fillColor || '#00FF00').withAlpha(style.fillOpacity || 0.4);
         const strokeColor = Cesium.Color.fromCssColorString(style.strokeColor || '#0055FF');
 
@@ -560,6 +594,7 @@ function addGeoJsonLayerWithStyle(url, name, style, callback) {
                 processGeoJsonEntities(dataSource, name, style);
                 viewer.flyTo(dataSource, { duration: 2 });
                 actions.setChangeLayers();
+                console.log('GeoJSON图层加载成功:', name);
                 if (callback) callback(dataSource);
             } catch (e) {
                 console.error("添加GeoJSON数据源失败:", e);
@@ -575,8 +610,104 @@ function addGeoJsonLayerWithStyle(url, name, style, callback) {
     }
 };
 
+// 通过API加载本地GeoJSON文件
+async function loadGeoJsonViaApi(url, name, style, callback) {
+    console.log('========== loadGeoJsonViaApi 开始执行 ==========');
+    console.log('原始URL:', url);
+
+    try {
+        // 规范化URL
+        let normalizedUrl = url.replace(/^\/+/, '').replace(/^\.\/+/, '');
+
+        // 获取API基础路径
+        const baseURL = window.globalRequestConfig?.baseURL || import.meta.env.VITE_APP_BASE_API || '';
+        const apiUrl = `${baseURL}/common/download/resource?resource=${encodeURIComponent(normalizedUrl)}`;
+
+        console.log('API URL:', apiUrl);
+        console.log('完整请求URL:', new URL(apiUrl, window.location.origin).href);
+
+        // 获取认证token
+        const token = window.globalRequestConfig?.headers?.Authorization ||
+            (typeof getToken === 'function' ? 'Bearer ' + getToken() : '');
+
+        console.log('开始通过API下载GeoJSON文件...');
+
+        // 通过API下载文件
+        const response = await fetch(apiUrl, {
+            method: 'GET',
+            headers: {
+                'Authorization': token
+            }
+        });
+
+        console.log('API响应状态:', response.status);
+        console.log('API响应URL:', response.url);
+
+        if (!response.ok) {
+            const errorText = await response.text();
+            console.error('API响应内容:', errorText);
+            throw new Error(`API请求失败: ${response.status} ${response.statusText}`);
+        }
+
+        // 获取文件内容
+        const blob = await response.blob();
+        console.log('文件大小:', blob.size, 'bytes');
+
+        if (blob.size === 0) {
+            throw new Error('下载的文件为空');
+        }
+
+        // 创建Blob URL
+        const blobUrl = URL.createObjectURL(blob);
+        console.log('创建Blob URL:', blobUrl);
+
+        // 使用Cesium加载GeoJSON
+        console.log('开始使用Cesium加载GeoJSON...');
+
+        const fillColor = style ? Cesium.Color.fromCssColorString(style.fillColor || '#00FF00').withAlpha(style.fillOpacity || 0.4) : Cesium.Color.fromCssColorString('#00FF00').withAlpha(0.4);
+        const strokeColor = style ? Cesium.Color.fromCssColorString(style.strokeColor || '#0055FF') : Cesium.Color.fromCssColorString('#0055FF');
+        const strokeWidth = style?.strokeWidth || 3;
+
+        const loadPromise = Cesium.GeoJsonDataSource.load(blobUrl, {
+            stroke: strokeColor.withAlpha(1),
+            fill: fillColor,
+            strokeWidth: strokeWidth,
+            clampToGround: true
+        });
+
+        Cesium.when(loadPromise, function (dataSource) {
+            try {
+                viewer.dataSources.add(dataSource);
+                processGeoJsonEntities(dataSource, name, style);
+                viewer.flyTo(dataSource, { duration: 2 });
+                actions.setChangeLayers();
 
+                console.log('========== GeoJSON通过API加载成功 ==========');
+                console.log('图层名称:', name);
+                dataSource.name = name;
 
+                // 释放Blob URL
+                URL.revokeObjectURL(blobUrl);
+
+                if (callback) callback(dataSource);
+            } catch (e) {
+                console.error("添加GeoJSON数据源失败:", e);
+                URL.revokeObjectURL(blobUrl);
+                if (callback) callback(null, e);
+            }
+        }, function (error) {
+            console.error("加载GeoJSON失败:", error);
+            URL.revokeObjectURL(blobUrl);
+            if (callback) callback(null, error);
+        });
+
+    } catch (error) {
+        console.error('========== loadGeoJsonViaApi 失败 ==========');
+        console.error('URL:', url);
+        console.error('错误详情:', error);
+        if (callback) callback(null, error);
+    }
+}
 
 
 

+ 23 - 3
RuoYi-Vue3/src/supermap-cesium-module/js/common/waterLayer.js

@@ -68,10 +68,21 @@ function validateAndCleanCoordinates(coords) {
 }
 
 function loadWaterLayer(url, name, callback) {
+    console.log('========== loadWaterLayer 开始执行 ==========');
+    console.log('URL:', url);
+    console.log('Name:', name);
+    
     try {
         fetch(url)
-            .then(response => response.json())
+            .then(response => {
+                console.log('Fetch response:', response);
+                if (!response.ok) {
+                    throw new Error(`HTTP error! status: ${response.status}`);
+                }
+                return response.json();
+            })
             .then(geoJson => {
+                console.log('GeoJSON 加载成功:', geoJson);
                 const waterPrimitives = [];
                 
                 async function processFeature(feature) {
@@ -399,10 +410,19 @@ function loadWaterLayer(url, name, callback) {
                 processAllFeatures();
             })
             .catch(error => {
-                console.error("加载水体GeoJSON失败:", error);
+                console.error("========== 加载水体GeoJSON失败 ==========");
+                console.error("URL:", url);
+                console.error("错误详情:", error);
+                console.error("请检查:");
+                console.error("1. 文件是否存在:", url);
+                console.error("2. 文件路径是否正确");
+                console.error("3. 是否有访问权限");
+                console.error("4. 网络请求是否成功");
             });
     } catch (e) {
-        console.error("加载水体GeoJSON异常:", e);
+        console.error("========== 加载水体GeoJSON异常 ==========");
+        console.error("URL:", url);
+        console.error("异常详情:", e);
     }
 }
 

+ 5 - 0
RuoYi-Vue3/src/supermap-cesium-module/js/tool/tool.js

@@ -20,6 +20,11 @@ const Message = {
     console.log('success', msg)
     // alert('success:' + msg)
     return
+  },
+  infoMsg: (msg) => {
+    console.log('info', msg)
+    // alert('info:' + msg)
+    return
   }
 };
 

+ 36 - 7
RuoYi-Vue3/src/supermap-cesium-module/views/layout/aside.vue

@@ -2327,14 +2327,22 @@ export default {
     
     // 加载水面图层
     loadWaterLayers() {
-      if (!window.viewer) return;
+      console.log('========== loadWaterLayers 开始执行 ==========');
+      console.log('window.viewer:', window.viewer);
+      
+      if (!window.viewer) {
+        console.error('window.viewer 不存在,无法加载水面图层');
+        return;
+      }
+      
+      console.log('开始加载莒口水闸河道范围水面...');
       
       // 加载莒口水闸河道范围水面
       waterLayer.loadWaterLayer(
         '/js/莒口水闸河道范围.geojson',
         '莒口水闸河道',
         (dataSource) => {
-          console.log('水面图层加载完成');
+          console.log('水面图层加载完成', dataSource);
         }
       );
     },
@@ -2716,12 +2724,33 @@ export default {
       this.varName = undefined;
       
       // 存储服务到数据库
-      console.log('准备调用saveServiceToDatabase (非MVT):', serviceInfo, add_type, obj.name);
-      if (serviceInfo && serviceUrl) {
-        console.log('调用saveServiceToDatabase (非MVT)');
-        this.saveServiceToDatabase(serviceInfo, add_type, obj.name);
+      // SHP上传后端已自动保存,跳过前端重复保存
+      const isShpUpload = serviceUrl && serviceUrl.startsWith('/profile/geojson/');
+      if (!isShpUpload) {
+        console.log('准备调用saveServiceToDatabase (非MVT):', serviceInfo, add_type, obj.name);
+        if (serviceInfo && serviceUrl) {
+          console.log('调用saveServiceToDatabase (非MVT)');
+          this.saveServiceToDatabase(serviceInfo, add_type, obj.name);
+        } else {
+          console.log('serviceInfo或serviceUrl为空 (非MVT):', serviceInfo, serviceUrl);
+        }
       } else {
-        console.log('serviceInfo或serviceUrl为空 (非MVT):', serviceInfo, serviceUrl);
+        console.log('SHP上传后端已自动保存,直接标记为已加载');
+        this.addService = "";
+        this.varName = undefined;
+        // 使用上传响应中返回的id更新服务对象并标记为已加载
+        const serviceId = serviceInfo && serviceInfo.id;
+        if (serviceId) {
+          // 更新已在列表中的obj,使isServiceLoaded能匹配
+          obj.id = serviceId;
+          obj.url = serviceUrl;
+          obj.type = add_type || 'GEOJSON';
+          obj.layers = [{ type: 'GEOJSON', layerName: serviceInfo.layerName || obj.name }];
+          if (!this.loadedCustomServiceIds.includes(serviceId)) {
+            this.loadedCustomServiceIds.push(serviceId);
+          }
+        }
+        return;
       }
       
       if ((add_type && add_type === "SCENE") || add_type === "TERRAIN") return;

+ 16 - 0
RuoYi-Vue3/vite.config.js

@@ -118,6 +118,22 @@ export default defineConfig(({ mode, command }) => {
         }
       }
     },
+    // 预览服务器配置(生产环境预览)
+    preview: {
+      port: 4173,
+      host: true,
+      proxy: {
+        '/prod-api': {
+          target: 'http://localhost:8080',
+          changeOrigin: true,
+          rewrite: (p) => p.replace(/^\/prod-api/, '/prod-api')
+        },
+        '/uploads': {
+          target: 'http://localhost:8080',
+          changeOrigin: true
+        }
+      }
+    },
     css: {
       postcss: {
         plugins: [

+ 33 - 0
copy-dist.bat

@@ -0,0 +1,33 @@
+@echo off
+echo ========================================
+echo 复制前端文件到Nginx目录
+echo ========================================
+echo.
+
+:: 设置Nginx目录(请修改为您的Nginx安装路径)
+set NGINX_DIR=C:\nginx\html
+
+:: 检查Nginx目录是否存在
+if not exist "%NGINX_DIR%" (
+    echo 错误:Nginx目录不存在: %NGINX_DIR%
+    echo 请修改脚本中的NGINX_DIR变量
+    pause
+    exit /b 1
+)
+
+:: 复制文件
+echo 正在复制前端文件...
+xcopy d:\Web\PlatformModel\RuoYi-Vue3\dist "%NGINX_DIR%" /E /I /H /Y
+
+echo.
+echo ========================================
+echo 复制完成!
+echo ========================================
+echo 前端文件已复制到: %NGINX_DIR%
+echo.
+echo 下一步:
+echo 1. 配置Nginx(参考 nginx.conf.example)
+echo 2. 启动Nginx
+echo 3. 访问: http://localhost
+echo.
+pause

+ 70 - 0
deploy.bat

@@ -0,0 +1,70 @@
+@echo off
+chcp 65001 >nul
+echo ========================================
+echo 若依前后端部署脚本
+echo ========================================
+echo.
+
+:: 设置部署目录
+set DEPLOY_DIR=D:\WebApp\ruoyi
+set FRONTEND_DIR=%DEPLOY_DIR%-ui
+
+:: 1. 前端打包
+echo [步骤1] 正在打包前端...
+cd d:\Web\PlatformModel\RuoYi-Vue3
+call npm run build:prod
+if errorlevel 1 (
+    echo 前端打包失败!
+    pause
+    exit /b 1
+)
+echo 前端打包完成!
+echo.
+
+:: 2. 创建部署目录
+echo [步骤2] 创建部署目录...
+if not exist "%DEPLOY_DIR%" mkdir "%DEPLOY_DIR%"
+if not exist "%FRONTEND_DIR%" mkdir "%FRONTEND_DIR%"
+echo 部署目录创建完成!
+echo.
+
+:: 3. 复制前端文件
+echo [步骤3] 复制前端文件...
+xcopy d:\Web\PlatformModel\RuoYi-Vue3\dist "%FRONTEND_DIR%" /E /I /H /Y
+echo 前端文件复制完成!
+echo.
+
+:: 4. 复制后端文件
+echo [步骤4] 复制后端文件...
+copy d:\Web\PlatformModel\ruoyi-admin\target\ruoyi-admin.jar "%DEPLOY_DIR%\" /Y
+echo 后端文件复制完成!
+echo.
+
+:: 5. 复制uploads目录(如果不存在)
+echo [步骤5] 检查uploads目录...
+if not exist "%DEPLOY_DIR%\uploads" (
+    echo 复制uploads目录...
+    xcopy d:\Web\PlatformModel\ruoyi-admin\uploads "%DEPLOY_DIR%\uploads" /E /I /H /Y
+    echo uploads目录复制完成!
+) else (
+    echo uploads目录已存在,跳过复制
+)
+echo.
+
+:: 6. 提示配置文件修改
+echo [步骤6] 请手动修改配置文件:
+echo 前端配置:%FRONTEND_DIR%\supermap.config.js
+echo 后端配置:数据库连接已在jar包中
+echo.
+
+echo ========================================
+echo 部署完成!
+echo ========================================
+echo 前端目录:%FRONTEND_DIR%
+echo 后端目录:%DEPLOY_DIR%
+echo.
+echo 启动后端:
+echo cd %DEPLOY_DIR%
+echo java -jar ruoyi-admin.jar
+echo.
+pause

+ 263 - 0
docs/GeoJSON加载修复说明.md

@@ -0,0 +1,263 @@
+# GeoJSON加载问题修复说明
+
+## 问题描述
+
+用户上传的自定义数据(SHP文件)转换为GeoJSON后,存放在 `uploads/models/geojson/` 目录中。在开发环境下可以正常加载,但打包后无法加载,报错"加载GeoJSON失败"。
+
+而同样在 `uploads/models/` 目录下的模型文件(.glb)却可以正常加载。
+
+## 问题原因
+
+### 模型文件 vs GeoJSON文件加载方式对比
+
+#### 模型文件(可以正常加载)
+
+**加载方式:** 通过后端API下载
+
+**代码位置:** `aside.vue` 第734-735行
+
+```javascript
+const baseURL = import.meta.env.VITE_APP_BASE_API || ''
+const apiUrl = baseURL + "/common/download/resource?resource=" + encodeURIComponent(modelUrl)
+```
+
+**实际请求路径:**
+```
+/prod-api/common/download/resource?resource=uploads/models/xxx.glb
+```
+
+#### GeoJSON文件(打包后加载失败)
+
+**原加载方式:** 直接使用文件路径
+
+**代码位置:** `layerManagement.js` 第507行
+
+```javascript
+const loadPromise = Cesium.GeoJsonDataSource.load(url, { ... });
+```
+
+**实际请求路径:**
+```
+/uploads/models/geojson/xxx.geojson
+```
+
+### 为什么打包后失败?
+
+**开发环境:**
+- Vite开发服务器会代理所有请求到后端
+- 相对路径 `/uploads/...` 可以通过代理访问到后端文件
+
+**生产环境(打包后):**
+- 前端和后端分离部署
+- 前端静态文件服务器(Nginx/IIS)找不到 `/uploads/...` 路径的文件
+- 需要通过后端API访问:`/prod-api/common/download/resource?resource=uploads/...`
+
+## 解决方案
+
+### 修改内容
+
+修改了 [layerManagement.js](file:///d:/Web/PlatformModel/RuoYi-Vue3/src/supermap-cesium-module/js/common/layerManagement.js) 文件:
+
+#### 1. 导入认证函数
+
+```javascript
+import { getToken } from '@/utils/auth';
+```
+
+#### 2. 修改 `addGeoJsonLayer` 函数
+
+添加本地文件检测,自动使用API加载:
+
+```javascript
+function addGeoJsonLayer(url, name, callback) {
+    // ... 省略部分代码
+
+    // 检查是否为本地文件路径(需要通过API加载)
+    const isLocalPath = url.startsWith('/uploads/') || url.startsWith('uploads/') || url.startsWith('./uploads/');
+
+    if (isLocalPath) {
+        console.log('检测到本地GeoJSON文件,通过API加载:', url);
+        loadGeoJsonViaApi(url, name, null, callback);
+        return;
+    }
+
+    // 外部URL直接加载
+    // ... 省略部分代码
+}
+```
+
+#### 3. 新增 `loadGeoJsonViaApi` 函数
+
+专门处理本地GeoJSON文件的加载:
+
+```javascript
+async function loadGeoJsonViaApi(url, name, style, callback) {
+    // 1. 规范化URL
+    let normalizedUrl = url.replace(/^\/+/, '').replace(/^\.\/+/, '');
+
+    // 2. 构建API URL
+    const baseURL = window.globalRequestConfig?.baseURL || import.meta.env.VITE_APP_BASE_API || '';
+    const apiUrl = `${baseURL}/common/download/resource?resource=${encodeURIComponent(normalizedUrl)}`;
+
+    // 3. 获取认证token
+    const token = typeof getToken === 'function' ? 'Bearer ' + getToken() : '';
+
+    // 4. 通过API下载文件
+    const response = await fetch(apiUrl, {
+        method: 'GET',
+        headers: { 'Authorization': token }
+    });
+
+    // 5. 创建Blob URL
+    const blob = await response.blob();
+    const blobUrl = URL.createObjectURL(blob);
+
+    // 6. 使用Cesium加载
+    const loadPromise = Cesium.GeoJsonDataSource.load(blobUrl, { ... });
+
+    // 7. 加载完成后释放Blob URL
+    URL.revokeObjectURL(blobUrl);
+}
+```
+
+#### 4. 同样修改 `addGeoJsonLayerWithStyle` 函数
+
+添加了相同的本地文件检测和API加载逻辑。
+
+## 工作原理
+
+### 加载流程对比
+
+#### 修改前(打包后失败)
+
+```
+GeoJSON URL: /uploads/models/geojson/xxx.geojson
+      ↓
+直接请求前端服务器
+      ↓
+404 Not Found(文件不存在)
+```
+
+#### 修改后(打包后成功)
+
+```
+GeoJSON URL: /uploads/models/geojson/xxx.geojson
+      ↓
+检测为本地文件
+      ↓
+转换为API URL: /prod-api/common/download/resource?resource=uploads/models/geojson/xxx.geojson
+      ↓
+请求后端API下载文件
+      ↓
+创建Blob URL
+      ↓
+Cesium加载Blob URL
+      ↓
+加载成功
+```
+
+### 关键技术点
+
+1. **路径检测**:自动识别本地文件路径(`/uploads/` 开头)
+2. **API调用**:使用后端 `/common/download/resource` 接口下载文件
+3. **认证集成**:自动添加 `Authorization` header
+4. **Blob处理**:将下载的文件转换为 Blob URL 供 Cesium 使用
+5. **内存管理**:加载完成后自动释放 Blob URL
+
+## 验证步骤
+
+### 1. 重新打包
+
+```bash
+cd d:\Web\PlatformModel\RuoYi-Vue3
+npm run build:prod
+```
+
+### 2. 查看控制台日志
+
+成功加载时应该看到:
+
+```
+========== addGeoJsonLayer 开始执行 ==========
+URL: uploads/models/geojson/xxx.geojson
+Name: 自定义数据
+检测到本地GeoJSON文件,通过API加载: uploads/models/geojson/xxx.geojson
+========== loadGeoJsonViaApi 开始执行 ==========
+原始URL: uploads/models/geojson/xxx.geojson
+API URL: /prod-api/common/download/resource?resource=uploads%2Fmodels%2Fgeojson%2Fxxx.geojson
+开始通过API下载GeoJSON文件...
+API响应状态: 200
+文件大小: 12345 bytes
+创建Blob URL: blob:http://localhost/xxx-xxx-xxx
+开始使用Cesium加载GeoJSON...
+========== GeoJSON通过API加载成功 ==========
+图层名称: 自定义数据
+```
+
+### 3. 检查网络请求
+
+打开浏览器开发者工具 → Network 标签:
+
+应该看到:
+- 请求URL:`/prod-api/common/download/resource?resource=uploads%2Fmodels%2Fgeojson%2Fxxx.geojson`
+- 状态:`200 OK`
+- 类型:`application/octet-stream`
+
+## 支持的文件路径格式
+
+修改后的代码支持以下格式的本地路径:
+
+- ✅ `/uploads/models/geojson/xxx.geojson`
+- ✅ `uploads/models/geojson/xxx.geojson`
+- ✅ `./uploads/models/geojson/xxx.geojson`
+
+## 注意事项
+
+### 1. 后端API要求
+
+确保后端提供了 `/common/download/resource` 接口,且:
+- 支持GeoJSON文件下载
+- 返回正确的文件内容
+- 支持认证
+
+### 2. 文件权限
+
+确保:
+- uploads 目录存在且可访问
+- geojson 文件有正确的读取权限
+
+### 3. 外部URL不受影响
+
+外部URL(如超图服务、在线GeoJSON)仍然直接加载,不通过API。
+
+### 4. 内存管理
+
+代码会自动释放Blob URL,无需手动管理。
+
+## 相关文件
+
+- [layerManagement.js](file:///d:/Web/PlatformModel/RuoYi-Vue3/src/supermap-cesium-module/js/common/layerManagement.js) - 主要修改文件
+- [aside.vue](file:///d:/Web/PlatformModel/RuoYi-Vue3/src/supermap-cesium-module/views/layout/aside.vue) - 参考模型加载方式
+- [auth.js](file:///d:/Web/PlatformModel/RuoYi-Vue3/src/utils/auth.js) - 认证函数
+
+## 常见问题
+
+### Q: 为什么模型文件可以加载,GeoJSON不行?
+
+A: 模型文件通过后端API下载,GeoJSON直接使用文件路径。打包后前端静态服务器找不到文件。
+
+### Q: 修改后会影响性能吗?
+
+A: 影响很小。多了一次API请求,但文件下载速度与直接访问相同。
+
+### Q: 外部URL也会通过API加载吗?
+
+A: 不会。只有以 `/uploads/` 开头的本地路径才会通过API加载。
+
+### Q: 需要修改数据库中的路径吗?
+
+A: 不需要。数据库中的路径保持不变,代码会自动处理。
+
+## 总结
+
+通过让GeoJSON文件和模型文件使用相同的加载方式(通过后端API),解决了打包后无法加载本地GeoJSON文件的问题。代码会自动识别本地路径并进行相应处理,对用户透明。

+ 199 - 0
docs/莒口水闸河道范围加载问题诊断.md

@@ -0,0 +1,199 @@
+# 莒口水闸河道范围加载问题诊断指南
+
+## 问题描述
+打包后,莒口水闸河道范围水面图层无法加载。
+
+## 诊断步骤
+
+### 1. 检查浏览器控制台日志
+
+启动应用后,打开浏览器控制台(F12),查看以下日志:
+
+#### 正常情况应该看到:
+```
+========== loadWaterLayers 开始执行 ==========
+window.viewer: Viewer {...}
+开始加载莒口水闸河道范围水面...
+========== loadWaterLayer 开始执行 ==========
+URL: /js/莒口水闸河道范围.geojson
+Name: 莒口水闸河道
+Fetch response: Response {ok: true, status: 200, ...}
+GeoJSON 加载成功: {type: "FeatureCollection", ...}
+水面图层加载完成
+```
+
+#### 错误情况可能看到:
+```
+window.viewer 不存在,无法加载水面图层
+```
+或
+```
+加载水体GeoJSON失败
+URL: /js/莒口水闸河道范围.geojson
+错误详情: TypeError: Failed to fetch
+```
+
+### 2. 检查网络请求
+
+1. 打开浏览器开发者工具(F12)
+2. 切换到 **Network** 标签
+3. 查找请求:`莒口水闸河道范围.geojson`
+4. 检查状态:
+   - ✅ `200 OK` - 文件加载成功
+   - ❌ `404 Not Found` - 文件不存在
+   - ❌ `Failed to fetch` - 路径问题或跨域问题
+
+### 3. 检查打包后的文件
+
+#### Windows:
+```powershell
+# 检查文件是否存在
+Test-Path dist\js\莒口水闸河道范围.geojson
+
+# 或使用 dir
+dir dist\js\莒口水闸河道范围.geojson
+```
+
+#### Linux/Mac:
+```bash
+# 检查文件是否存在
+ls -la dist/js/莒口水闸河道范围.geojson
+```
+
+文件应该存在于 `dist/js/` 目录下。
+
+## 解决方案
+
+### 方案1:确保文件正确打包
+
+**步骤:**
+1. 确认文件在正确位置:
+   ```
+   RuoYi-Vue3/public/js/莒口水闸河道范围.geojson
+   ```
+
+2. 重新打包:
+   ```bash
+   cd RuoYi-Vue3
+   npm run build:prod
+   ```
+
+3. 验证文件已复制:
+   ```bash
+   dir dist\js\莒口水闸河道范围.geojson
+   ```
+
+### 方案2:解决中文路径问题(推荐)
+
+**问题:** 某些服务器对中文路径支持不好
+
+**解决方案:**
+1. 重命名文件:
+   ```bash
+   # 将文件重命名为英文
+   move public\js\莒口水闸河道范围.geojson public\js\jukou-water.geojson
+   ```
+
+2. 修改代码引用(需要手动修改):
+   ```javascript
+   // 修改 aside.vue
+   waterLayer.loadWaterLayer(
+     '/js/jukou-water.geojson',  // 改为英文路径
+     '莒口水闸河道',
+     (dataSource) => {
+       console.log('水面图层加载完成', dataSource);
+     }
+   );
+   ```
+
+### 方案3:检查 Web 服务器配置
+
+#### Nginx 配置
+确保 Nginx 正确配置静态文件访问:
+```nginx
+location /js/ {
+    alias /path/to/dist/js/;
+    charset utf-8;
+    index index.html;
+}
+```
+
+#### IIS 配置
+1. 确保应用程序池集成模式
+2. 添加 MIME 类型:
+   - 扩展名:`.geojson`
+   - MIME类型:`application/json`
+
+### 方案4:检查文件权限
+
+**Linux/Mac:**
+```bash
+# 设置正确的文件权限
+chmod 644 dist/js/莒口水闸河道范围.geojson
+```
+
+**Windows:**
+确保 IIS_IUSRS 或 IIS_WPG 用户对文件有读取权限。
+
+## 调试技巧
+
+### 1. 手动测试文件访问
+
+在浏览器地址栏直接访问:
+```
+http://localhost/js/莒口水闸河道范围.geojson
+```
+
+如果能看到 JSON 数据,说明文件可访问。
+
+### 2. 使用 curl 测试
+
+```bash
+# 测试文件是否可访问
+curl -I http://localhost/js/莒口水闸河道范围.geojson
+
+# 应该返回 200 OK
+```
+
+### 3. 添加调试代码
+
+在 `waterLayer.js` 的 `loadWaterLayer` 函数开始处添加:
+```javascript
+console.log('当前 URL:', window.location.href);
+console.log('完整路径:', new URL(url, window.location.href).href);
+```
+
+## 已知问题
+
+### 问题1:开发环境正常,生产环境失败
+
+**原因:** Vite 开发服务器和静态文件服务器行为不同
+
+**解决:**
+- 检查打包后的文件路径
+- 检查 Web 服务器配置
+
+### 问题2:HTTPS 环境加载 HTTP 文件
+
+**症状:** 控制台显示 Mixed Content 错误
+
+**解决:**
+- 使用相对路径 `/js/...`
+- 或使用 HTTPS 协议访问所有资源
+
+## 验证修复
+
+修复后,检查以下内容:
+
+1. ✅ 控制台无错误日志
+2. ✅ 网络请求返回 200
+3. ✅ 地图上可以看到水面图层
+4. ✅ 水面有动态效果
+
+## 联系支持
+
+如果以上方法都无法解决问题,请提供:
+1. 浏览器控制台完整日志
+2. 网络请求截图
+3. 打包后的文件列表
+4. Web 服务器配置

+ 193 - 0
docs/超图服务外部配置使用说明.md

@@ -0,0 +1,193 @@
+# 超图服务地址配置 - 打包后动态修改
+
+## 概述
+
+现在超图服务地址支持**打包后通过外部配置文件修改**,无需重新打包即可适配不同的部署环境。
+
+## 配置文件位置
+
+打包后的配置文件位于:`dist/supermap.config.js`
+
+## 使用方法
+
+### 1. 打包前端
+```bash
+npm run build:prod
+```
+
+打包后会在 `dist` 目录生成 `supermap.config.js` 文件。
+
+### 2. 部署到不同环境
+
+**环境A部署:**
+编辑 `dist/supermap.config.js`:
+```javascript
+window.SUPERMAP_CONFIG = {
+  host: '192.168.1.100',
+  port: '8090'
+}
+```
+
+**环境B部署:**
+编辑 `dist/supermap.config.js`:
+```javascript
+window.SUPERMAP_CONFIG = {
+  host: '192.168.2.200',
+  port: '8090'
+}
+```
+
+### 3. 验证配置
+
+启动应用后,浏览器控制台会显示:
+```
+超图服务配置: { source: "外部配置文件", host: "192.168.1.100", port: "8090", baseUrl: "http://192.168.1.100:8090" }
+```
+
+## 配置项说明
+
+| 配置项 | 说明 | 示例 |
+|--------|------|------|
+| `host` | 超图服务器地址 | `'192.168.1.100'` |
+| `port` | 超图服务器端口 | `'8090'` |
+| `baseUrl` | 完整地址(可选) | `'http://192.168.1.100:8090'` |
+| `tiandituKey` | 天地图Key(可选) | `'your_key'` |
+
+## 配置优先级
+
+1. **外部配置文件** (`supermap.config.js`) - 最高优先级
+2. **环境变量** (`.env.production`)
+3. **默认值** (`localhost:8090`)
+
+## 不同部署场景示例
+
+### 场景1:局域网部署
+```javascript
+window.SUPERMAP_CONFIG = {
+  host: '192.168.1.100',
+  port: '8090'
+}
+```
+
+### 场景2:公网部署
+```javascript
+window.SUPERMAP_CONFIG = {
+  baseUrl: 'http://supermap.example.com:8090'
+}
+```
+
+### 场景3:HTTPS部署
+```javascript
+window.SUPERMAP_CONFIG = {
+  baseUrl: 'https://supermap.example.com'
+}
+```
+
+## 注意事项
+
+### 1. 文件位置
+- 开发环境:`public/supermap.config.js`
+- 打包后:`dist/supermap.config.js`
+- 必须与 `index.html` 在同一目录
+
+### 2. 修改时机
+- ✅ 可以在打包后、部署前修改
+- ✅ 可以在已部署的服务器上修改(需要重启Web服务器)
+- ❌ 不能在应用运行时修改(需要刷新页面)
+
+### 3. 缓存问题
+如果修改配置后未生效,可能是浏览器缓存:
+- 清除浏览器缓存
+- 或在URL后添加 `?v=时间戳`
+
+### 4. 文件权限
+确保Web服务器有读取 `supermap.config.js` 的权限:
+- Linux: `chmod 644 supermap.config.js`
+- Windows: 确保IIS用户有读取权限
+
+## 部署工作流
+
+### 标准流程
+```bash
+# 1. 打包
+npm run build:prod
+
+# 2. 部署到服务器
+scp -r dist/* user@server:/var/www/html/
+
+# 3. 登录服务器
+ssh user@server
+
+# 4. 修改配置
+vi /var/www/html/supermap.config.js
+
+# 5. 重启Web服务器(可选)
+systemctl restart nginx
+```
+
+### 多环境部署
+
+**部署脚本示例:**
+```bash
+#!/bin/bash
+# deploy.sh
+
+ENV=$1
+
+if [ "$ENV" == "prod" ]; then
+    HOST="192.168.1.100"
+elif [ "$ENV" == "test" ]; then
+    HOST="192.168.2.200"
+else
+    HOST="localhost"
+fi
+
+# 修改配置文件
+sed -i "s/host: '.*'/host: '$HOST'/" dist/supermap.config.js
+
+# 部署
+rsync -avz dist/ user@server:/var/www/html/
+```
+
+使用:
+```bash
+./deploy.sh prod   # 部署到生产环境
+./deploy.sh test   # 部署到测试环境
+```
+
+## 故障排查
+
+### 问题1:配置不生效
+**检查步骤:**
+1. 确认文件路径正确
+2. 检查文件内容是否正确
+3. 清除浏览器缓存
+4. 查看浏览器控制台日志
+
+### 问题2:404错误
+**检查:**
+- 确认文件在正确位置
+- 确认Web服务器配置正确
+
+### 问题3:跨域问题
+如果超图服务器与应用不在同一域名,需要:
+1. 配置超图服务器CORS
+2. 或使用Nginx反向代理
+
+## 开发环境
+
+开发环境默认使用 `public/supermap.config.js`,可以修改此文件进行本地测试。
+
+也可以使用 `.env.development` 配:
+```bash
+VITE_SUPERMAP_HOST=localhost
+VITE_SUPERMAP_PORT=8090
+```
+
+## 总结
+
+通过外部配置文件,您现在可以:
+- ✅ 一次打包,多环境部署
+- ✅ 无需修改代码即可更改超图服务地址
+- ✅ 便于自动化部署和CI/CD集成
+- ✅ 降低部署复杂度和出错风险

+ 123 - 0
docs/超图服务配置说明.md

@@ -0,0 +1,123 @@
+# 超图服务地址配置说明
+
+## 配置方式
+
+超图服务地址现在支持通过环境变量配置,无需修改代码即可适配不同的部署环境。
+
+## 配置文件位置
+
+### 开发环境配置
+文件:`.env.development`
+```bash
+VITE_SUPERMAP_HOST=localhost
+VITE_SUPERMAP_PORT=8090
+```
+
+### 生产环境配置
+文件:`.env.production`
+```bash
+VITE_SUPERMAP_HOST=106.15.124.118  # 修改为实际超图服务器地址
+VITE_SUPERMAP_PORT=8090
+```
+
+### 自定义完整地址
+如果需要使用完整的地址(例如包含域名或特定路径),可以在配置文件中添加:
+```bash
+VITE_SUPERMAP_BASE_URL=http://your-domain.com:8090
+```
+
+## 使用步骤
+
+### 1. 开发环境
+开发环境默认使用 `localhost:8090`,无需修改。
+
+如果本地超图服务地址不同,修改 `.env.development` 文件:
+```bash
+VITE_SUPERMAP_HOST=192.168.1.100
+VITE_SUPERMAP_PORT=8090
+```
+
+### 2. 生产环境部署
+
+**方式一:修改配置文件后打包**
+1. 修改 `.env.production` 文件中的超图服务器地址
+2. 执行打包命令:`npm run build:prod`
+3. 将打包后的 `dist` 目录部署到服务器
+
+**方式二:打包后动态修改(不推荐)**
+打包后的配置是静态的,无法动态修改。建议使用方式一。
+
+## 配置项说明
+
+| 配置项 | 说明 | 默认值 |
+|--------|------|--------|
+| `VITE_SUPERMAP_HOST` | 超图服务器地址 | localhost |
+| `VITE_SUPERMAP_PORT` | 超图服务器端口 | 8090 |
+| `VITE_SUPERMAP_BASE_URL` | 完整的服务地址(可选) | http://${HOST}:${PORT} |
+
+## 影响的服务
+
+以下服务地址会自动使用配置的超图服务器地址:
+
+### 基础地理实体
+- 建筑矢量瓦片
+- 土壤矢量瓦片
+- 河流矢量瓦片
+- 流域矢量瓦片
+
+### 公共服务
+- 水下地形
+
+### 注意
+超图官方在线服务(如CBD、倾斜摄影等)仍然使用固定的在线地址,不受此配置影响。
+
+## 验证配置
+
+启动前端后,在浏览器控制台可以看到超图服务配置信息:
+```
+超图服务配置: { host: "106.15.124.118", port: "8090", baseUrl: "http://106.15.124.118:8090" }
+```
+
+## 不同环境配置示例
+
+### 本地开发
+```bash
+VITE_SUPERMAP_HOST=localhost
+VITE_SUPERMAP_PORT=8090
+```
+
+### 测试环境
+```bash
+VITE_SUPERMAP_HOST=192.168.1.100
+VITE_SUPERMAP_PORT=8090
+```
+
+### 生产环境
+```bash
+VITE_SUPERMAP_HOST=supermap.example.com
+VITE_SUPERMAP_PORT=8090
+
+# 或使用完整地址
+VITE_SUPERMAP_BASE_URL=http://supermap.example.com:8090
+```
+
+## 常见问题
+
+### Q: 修改配置后需要重新打包吗?
+A: 是的。环境变量在打包时会被编译进代码,修改配置后需要重新执行 `npm run build:prod`。
+
+### Q: 可以在运行时动态修改吗?
+A: 不可以。Vite的环境变量是静态配置,在打包时就已确定。如果需要动态配置,建议使用数据库存储服务地址。
+
+### Q: 如何确认配置生效?
+A: 打开浏览器控制台,查看启动时打印的配置信息,或检查网络请求中的服务地址。
+
+## 进阶:数据库动态配置(可选)
+
+如果需要更灵活的动态配置,可以将服务地址存储在数据库中,通过后端API获取。实现方式:
+
+1. 在数据库表 `watershed_service` 中存储服务地址
+2. 前端启动时通过API获取配置
+3. 动态更新服务地址
+
+这种方式可以实现无需重新打包即可修改配置,适合多租户或需要频繁修改的场景。

+ 41 - 0
nginx-production.conf

@@ -0,0 +1,41 @@
+# 若依前后端部署Nginx配置
+# 复制此内容到 C:\nginx\conf\nginx.conf 的 http 块中
+
+server {
+    listen 80;
+    server_name localhost;
+
+    # 前端静态文件
+    location / {
+        root html;  # 指向 C:\nginx\html(前端dist文件)
+        index index.html index.htm;
+        try_files $uri $uri/ /index.html;
+    }
+
+    # 后端API代理 - 关键配置
+    location /prod-api/ {
+        proxy_pass http://localhost:8080/prod-api/;  # 代理到后端
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
+
+        # 超时设置
+        proxy_connect_timeout 60s;
+        proxy_send_timeout 60s;
+        proxy_read_timeout 60s;
+    }
+
+    # 上传文件访问
+    location /uploads/ {
+        proxy_pass http://localhost:8080/uploads/;  # 代理到后端
+        autoindex on;
+    }
+
+    # 错误页面
+    error_page 404 /index.html;
+    error_page 500 502 503 504 /50x.html;
+    location = /50x.html {
+        root html;
+    }
+}

+ 40 - 0
nginx.conf.example

@@ -0,0 +1,40 @@
+# 若依前后端部署Nginx配置示例
+
+server {
+    listen 80;
+    server_name localhost;  # 修改为您的域名或IP
+
+    # 前端静态文件
+    location / {
+        root D:/WebApp/ruoyi-ui;  # 前端打包后的dist目录
+        index index.html index.htm;
+        try_files $uri $uri/ /index.html;
+    }
+
+    # 后端API代理 - 关键配置
+    location /prod-api/ {
+        proxy_pass http://localhost:8080/prod-api/;  # 后端地址
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
+
+        # 超时设置
+        proxy_connect_timeout 60s;
+        proxy_send_timeout 60s;
+        proxy_read_timeout 60s;
+    }
+
+    # 上传文件访问
+    location /uploads/ {
+        alias D:/WebApp/ruoyi/uploads/;  # 上传文件目录
+        autoindex on;
+    }
+
+    # 错误页面
+    error_page 404 /index.html;
+    error_page 500 502 503 504 /50x.html;
+    location = /50x.html {
+        root html;
+    }
+}

+ 4 - 1
ruoyi-admin/src/main/java/com/ruoyi/web/controller/cesium/CesiumGeojsonController.java

@@ -91,7 +91,10 @@ public class CesiumGeojsonController extends BaseController {
             ws.setUpdatedAt(new java.util.Date());
             watershedServiceMapper.insertWatershedService(ws);
 
-            return AjaxResult.success("上传成功", ws.getServiceUrl());
+            Map<String, Object> result = new HashMap<>();
+            result.put("url", ws.getServiceUrl());
+            result.put("id", ws.getServiceId());
+            return AjaxResult.success("上传成功", result);
         } catch (Exception e) {
             logger.error("上传失败", e);
             return AjaxResult.error("上传失败:" + e.getMessage());

+ 6 - 0
ruoyi-admin/src/main/resources/application-deploy.yml

@@ -0,0 +1,6 @@
+# 部署环境配置
+ruoyi:
+  # 上传文件存储路径(根据实际部署环境修改)
+  profile: D:/WebApp/ruoyi/uploads/models
+
+# 其他生产环境配置可以在这里覆盖

+ 1 - 1
ruoyi-admin/src/main/resources/application-druid.yml

@@ -6,7 +6,7 @@ spring:
         druid:
             # 主库数据源
             master:
-                url: jdbc:dm://192.168.0.145:30236?charSet=utf8
+                url: jdbc:dm://106.15.124.118:5236?charSet=utf8
                 username: WATERSHED
                 password: WaterShed./1224
                 # 初始化脚本

+ 3 - 3
ruoyi-admin/src/main/resources/application-prod.yml

@@ -4,9 +4,9 @@ spring:
     druid:
       master:
         # 交给对方替换成他们服务器的达梦地址、端口、账号密码
-        url: jdbc:dm://对方服务器IP:5236?charSet=utf8
-        username: 对方达梦账号
-        password: 对方达梦密码
+        url: jdbc:dm://106.15.124.118:5236?charSet=utf8
+        username: WATERSHED
+        password: WaterShed./1224
         schema: classpath:sql/watershed_model.sql
       slave:
         enabled: false

+ 2 - 2
ruoyi-admin/src/main/resources/application.yml

@@ -70,9 +70,9 @@ spring:
   # redis 配置
   redis:
     # 地址
-    host: 192.168.0.146
+    host: 39.98.38.2
     # 端口,默认为6379
-    port: 6379
+    port: 16379
     # 数据库索引
     database: 14
     # 密码

+ 36 - 0
ruoyi-admin/src/main/resources/sql/export_config_data.sql

@@ -0,0 +1,36 @@
+-- 导出地图配置数据
+-- 在开发环境执行,导出结果保存为 INSERT 语句
+SELECT
+'INSERT INTO WATERSHED.CESIUM_MAP_CONFIG (user_id, config_name, base_layer_type, base_layer_name, base_layer_url, terrain_layer_type, terrain_layer_name, terrain_layer_url, web_services, loaded_models, is_default, create_by, create_time, update_by, update_time, remark) VALUES ('
+|| user_id || ','
+|| '''' || config_name || ''','
+|| '''' || COALESCE(base_layer_type, '') || ''','
+|| '''' || COALESCE(base_layer_name, '') || ''','
+|| '''' || COALESCE(base_layer_url, '') || ''','
+|| '''' || COALESCE(terrain_layer_type, '') || ''','
+|| '''' || COALESCE(terrain_layer_name, '') || ''','
+|| '''' || COALESCE(terrain_layer_url, '') || ''','
+|| '''' || COALESCE(web_services, '') || ''','
+|| '''' || COALESCE(loaded_models, '') || ''','
+|| is_default || ','
+|| '''' || COALESCE(create_by, '') || ''','
+|| '''' || COALESCE(create_time, '') || ''','
+|| '''' || COALESCE(update_by, '') || ''','
+|| '''' || COALESCE(update_time, '') || ''','
+|| '''' || COALESCE(remark, '') || '''
+);'
+FROM WATERSHED.CESIUM_MAP_CONFIG;
+
+-- 导出自定义服务数据
+SELECT
+'INSERT INTO WATERSHED_SERVICE (name, type, url, token_required, token, status, created_at, updated_at) VALUES ('
+|| '''' || name || ''','
+|| '''' || type || ''','
+|| '''' || url || ''','
+|| token_required || ','
+|| '''' || COALESCE(token, '') || ''','
+|| '''' || status || ''','
+|| '''' || COALESCE(created_at, '') || ''','
+|| '''' || COALESCE(updated_at, '') || '''
+);'
+FROM WATERSHED_SERVICE;

+ 1 - 1
ruoyi-admin/target/classes/application-druid.yml

@@ -6,7 +6,7 @@ spring:
         druid:
             # 主库数据源
             master:
-                url: jdbc:dm://192.168.0.145:30236?charSet=utf8
+                url: jdbc:dm://106.15.124.118:5236?charSet=utf8
                 username: WATERSHED
                 password: WaterShed./1224
                 # 初始化脚本

+ 2 - 2
ruoyi-admin/target/classes/application.yml

@@ -70,9 +70,9 @@ spring:
   # redis 配置
   redis:
     # 地址
-    host: 192.168.0.146
+    host: 39.98.38.2
     # 端口,默认为6379
-    port: 6379
+    port: 16379
     # 数据库索引
     database: 14
     # 密码

BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysProfileController.class


File diff suppressed because it is too large
+ 0 - 0
ruoyi-admin/uploads/models/geojson/12c9fe6888244d22957a13e7999cf982.geojson


BIN
ruoyi-admin/uploads/models/geojson/12c9fe6888244d22957a13e7999cf982/福建省_全省水体(河流、人工湖等)分布矢量图(Shp格式).dbf


+ 0 - 1
ruoyi-admin/uploads/models/geojson/12c9fe6888244d22957a13e7999cf982/福建省_全省水体(河流、人工湖等)分布矢量图(Shp格式).prj

@@ -1 +0,0 @@
-GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]]

BIN
ruoyi-admin/uploads/models/geojson/12c9fe6888244d22957a13e7999cf982/福建省_全省水体(河流、人工湖等)分布矢量图(Shp格式).shp


BIN
ruoyi-admin/uploads/models/geojson/12c9fe6888244d22957a13e7999cf982/福建省_全省水体(河流、人工湖等)分布矢量图(Shp格式).shx


+ 20 - 0
start-frontend-server.bat

@@ -0,0 +1,20 @@
+@echo off
+echo ========================================
+echo 启动前端静态文件服务器
+echo ========================================
+echo.
+echo 后端已运行在: http://localhost:8080/prod-api
+echo 前端将运行在: http://localhost:8088
+echo.
+echo 请访问: http://localhost:8088
+echo.
+echo 按 Ctrl+C 停止服务器
+echo ========================================
+echo.
+
+cd /d d:\Web\PlatformModel\RuoYi-Vue3\dist
+
+:: 使用Python简单HTTP服务器
+python -m http.server 8088
+
+pause

+ 100 - 0
start-test-server.js

@@ -0,0 +1,100 @@
+// 简单的Node.js测试服务器
+// 使用方法:node start-test-server.js
+
+const http = require('http');
+const fs = require('fs');
+const path = require('path');
+const { URL } = require('url');
+
+// 后端地址
+const BACKEND_HOST = 'localhost';
+const BACKEND_PORT = 8080;
+
+// 前端目录
+const FRONTEND_DIR = path.join(__dirname, 'RuoYi-Vue3', 'dist');
+
+// 创建HTTP服务器
+const server = http.createServer((req, res) => {
+    const reqUrl = new URL(req.url, `http://${req.headers.host}`);
+
+    console.log(`${new Date().toISOString()} ${req.method} ${req.url}`);
+
+    // API代理
+    if (reqUrl.pathname.startsWith('/prod-api/')) {
+        proxyRequest(req, res, reqUrl);
+        return;
+    }
+
+    // 静态文件
+    let filePath = path.join(FRONTEND_DIR, reqUrl.pathname === '/' ? 'index.html' : reqUrl.pathname);
+
+    // 如果文件不存在,返回index.html(SPA路由)
+    if (!fs.existsSync(filePath)) {
+        filePath = path.join(FRONTEND_DIR, 'index.html');
+    }
+
+    // 读取文件
+    fs.readFile(filePath, (err, data) => {
+        if (err) {
+            res.writeHead(404);
+            res.end('File not found');
+            return;
+        }
+
+        // 设置Content-Type
+        const ext = path.extname(filePath);
+        const contentTypes = {
+            '.html': 'text/html',
+            '.js': 'application/javascript',
+            '.css': 'text/css',
+            '.json': 'application/json',
+            '.png': 'image/png',
+            '.jpg': 'image/jpeg',
+            '.gif': 'image/gif',
+            '.svg': 'image/svg+xml',
+            '.ico': 'image/x-icon'
+        };
+
+        res.setHeader('Content-Type', contentTypes[ext] || 'application/octet-stream');
+        res.writeHead(200);
+        res.end(data);
+    });
+});
+
+// 代理请求到后端
+function proxyRequest(req, res, reqUrl) {
+    const options = {
+        hostname: BACKEND_HOST,
+        port: BACKEND_PORT,
+        path: req.url,
+        method: req.method,
+        headers: req.headers
+    };
+
+    const proxyReq = http.request(options, (proxyRes) => {
+        res.writeHead(proxyRes.statusCode, proxyRes.headers);
+        proxyRes.pipe(res);
+    });
+
+    proxyReq.on('error', (err) => {
+        console.error('Proxy error:', err);
+        res.writeHead(502);
+        res.end('Bad Gateway');
+    });
+
+    req.pipe(proxyReq);
+}
+
+// 启动服务器
+const PORT = 8088;
+server.listen(PORT, () => {
+    console.log('========================================');
+    console.log('测试服务器已启动');
+    console.log('========================================');
+    console.log(`前端地址: http://localhost:${PORT}`);
+    console.log(`后端地址: http://${BACKEND_HOST}:${BACKEND_PORT}/prod-api`);
+    console.log('========================================');
+    console.log('请在浏览器中打开: http://localhost:8088');
+    console.log('========================================');
+    console.log('按 Ctrl+C 停止服务器');
+});

Some files were not shown because too many files changed in this diff