Ver Fonte

加载矢量瓦片,添加点击查询功能

WQQ há 1 mês atrás
pai
commit
b402e791a7
100 ficheiros alterados com 2066 adições e 596 exclusões
  1. 9 1
      RuoYi-Vue3/package.json
  2. 42 0
      RuoYi-Vue3/playwright.config.ts
  3. 146 0
      RuoYi-Vue3/src/supermap-cesium-module/components/MvtAttributePopup.vue
  4. 266 191
      RuoYi-Vue3/src/supermap-cesium-module/config/server_config.js
  5. 155 365
      RuoYi-Vue3/src/supermap-cesium-module/js/common/layerManagement.js
  6. 258 0
      RuoYi-Vue3/src/supermap-cesium-module/js/common/mvtClickHandler.js
  7. 722 0
      RuoYi-Vue3/src/supermap-cesium-module/js/common/vectorDataLoader.js
  8. 5 2
      RuoYi-Vue3/src/supermap-cesium-module/js/common/waterLayer.js
  9. 233 2
      RuoYi-Vue3/src/supermap-cesium-module/views/layout/aside.vue
  10. 94 0
      RuoYi-Vue3/tests/README.md
  11. 108 0
      RuoYi-Vue3/tests/basic.spec.ts
  12. 20 1
      RuoYi-Vue3/vite.config.js
  13. 1 8
      pom.xml
  14. BIN
      ruoyi-admin/lib/yauaa-7.32.0.jar
  15. BIN
      ruoyi-admin/target/classes/com/ruoyi/RuoYiApplication.class
  16. BIN
      ruoyi-admin/target/classes/com/ruoyi/RuoYiServletInitializer.class
  17. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/cesium/CesiumMapConfigController.class
  18. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/common/CaptchaController.class
  19. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/common/CommonController.class
  20. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/monitor/CacheController.class
  21. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/monitor/ServerController.class
  22. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/monitor/SysLogininforController.class
  23. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/monitor/SysOperlogController.class
  24. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/monitor/SysUserOnlineController.class
  25. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysConfigController.class
  26. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysDeptController.class
  27. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysDictDataController.class
  28. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysDictTypeController.class
  29. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysIndexController.class
  30. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysLoginController.class
  31. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysMenuController.class
  32. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysNoticeController.class
  33. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysPostController.class
  34. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysProfileController.class
  35. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysRegisterController.class
  36. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysRoleController.class
  37. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysUserController.class
  38. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/tool/TestController.class
  39. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/tool/UserEntity.class
  40. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/watershed/WatershedModelController.class
  41. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/watershed/WatershedServiceController.class
  42. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/core/config/SwaggerConfig.class
  43. 0 0
      ruoyi-admin/uploads/models/geojson/12c9fe6888244d22957a13e7999cf982.geojson
  44. BIN
      ruoyi-admin/uploads/models/geojson/12c9fe6888244d22957a13e7999cf982/福建省_全省水体(河流、人工湖等)分布矢量图(Shp格式).dbf
  45. 1 0
      ruoyi-admin/uploads/models/geojson/12c9fe6888244d22957a13e7999cf982/福建省_全省水体(河流、人工湖等)分布矢量图(Shp格式).prj
  46. BIN
      ruoyi-admin/uploads/models/geojson/12c9fe6888244d22957a13e7999cf982/福建省_全省水体(河流、人工湖等)分布矢量图(Shp格式).shp
  47. BIN
      ruoyi-admin/uploads/models/geojson/12c9fe6888244d22957a13e7999cf982/福建省_全省水体(河流、人工湖等)分布矢量图(Shp格式).shx
  48. 0 6
      ruoyi-common/pom.xml
  49. 6 20
      ruoyi-common/src/main/java/com/ruoyi/common/utils/http/UserAgentUtils.java
  50. BIN
      ruoyi-common/target/classes/com/ruoyi/common/annotation/Excel$ColumnType.class
  51. BIN
      ruoyi-common/target/classes/com/ruoyi/common/annotation/Excel$Type.class
  52. BIN
      ruoyi-common/target/classes/com/ruoyi/common/annotation/Excel.class
  53. BIN
      ruoyi-common/target/classes/com/ruoyi/common/config/RuoYiConfig.class
  54. BIN
      ruoyi-common/target/classes/com/ruoyi/common/config/serializer/SensitiveJsonSerializer.class
  55. BIN
      ruoyi-common/target/classes/com/ruoyi/common/constant/CacheConstants.class
  56. BIN
      ruoyi-common/target/classes/com/ruoyi/common/constant/Constants.class
  57. BIN
      ruoyi-common/target/classes/com/ruoyi/common/constant/GenConstants.class
  58. BIN
      ruoyi-common/target/classes/com/ruoyi/common/constant/HttpStatus.class
  59. BIN
      ruoyi-common/target/classes/com/ruoyi/common/constant/ScheduleConstants$Status.class
  60. BIN
      ruoyi-common/target/classes/com/ruoyi/common/constant/ScheduleConstants.class
  61. BIN
      ruoyi-common/target/classes/com/ruoyi/common/constant/UserConstants.class
  62. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/controller/BaseController$1.class
  63. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/controller/BaseController.class
  64. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/AjaxResult.class
  65. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/BaseEntity.class
  66. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/R.class
  67. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/TreeEntity.class
  68. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/TreeSelect.class
  69. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/entity/SysDept.class
  70. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/entity/SysDictData.class
  71. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/entity/SysDictType.class
  72. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/entity/SysMenu.class
  73. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/entity/SysRole.class
  74. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/entity/SysUser.class
  75. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/model/LoginBody.class
  76. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/model/LoginUser.class
  77. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/model/RegisterBody.class
  78. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/page/PageDomain.class
  79. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/page/TableDataInfo.class
  80. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/page/TableSupport.class
  81. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/redis/RedisCache.class
  82. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/text/CharsetKit.class
  83. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/text/Convert.class
  84. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/text/StrFormatter.class
  85. BIN
      ruoyi-common/target/classes/com/ruoyi/common/enums/BusinessStatus.class
  86. BIN
      ruoyi-common/target/classes/com/ruoyi/common/enums/BusinessType.class
  87. BIN
      ruoyi-common/target/classes/com/ruoyi/common/enums/DataSourceType.class
  88. BIN
      ruoyi-common/target/classes/com/ruoyi/common/enums/DesensitizedType.class
  89. BIN
      ruoyi-common/target/classes/com/ruoyi/common/enums/HttpMethod.class
  90. BIN
      ruoyi-common/target/classes/com/ruoyi/common/enums/LimitType.class
  91. BIN
      ruoyi-common/target/classes/com/ruoyi/common/enums/OperatorType.class
  92. BIN
      ruoyi-common/target/classes/com/ruoyi/common/enums/UserStatus.class
  93. BIN
      ruoyi-common/target/classes/com/ruoyi/common/exception/DemoModeException.class
  94. BIN
      ruoyi-common/target/classes/com/ruoyi/common/exception/GlobalException.class
  95. BIN
      ruoyi-common/target/classes/com/ruoyi/common/exception/ServiceException.class
  96. BIN
      ruoyi-common/target/classes/com/ruoyi/common/exception/UtilException.class
  97. BIN
      ruoyi-common/target/classes/com/ruoyi/common/exception/base/BaseException.class
  98. BIN
      ruoyi-common/target/classes/com/ruoyi/common/exception/file/FileException.class
  99. BIN
      ruoyi-common/target/classes/com/ruoyi/common/exception/file/FileNameLengthLimitExceededException.class
  100. BIN
      ruoyi-common/target/classes/com/ruoyi/common/exception/file/FileSizeLimitExceededException.class

+ 9 - 1
RuoYi-Vue3/package.json

@@ -9,7 +9,12 @@
     "dev": "vite",
     "build:prod": "vite build",
     "build:stage": "vite build --mode staging",
-    "preview": "vite preview"
+    "preview": "vite preview",
+    "test": "playwright test",
+    "test:ui": "playwright test --ui",
+    "test:headed": "playwright test --headed",
+    "test:debug": "playwright test --debug",
+    "test:report": "playwright show-report"
   },
   "repository": {
     "type": "git",
@@ -17,6 +22,7 @@
   },
   "dependencies": {
     "@element-plus/icons-vue": "2.3.1",
+    "@mapbox/vector-tile": "^3.0.0",
     "@supermap/iclient3d-vue-for-webgl": "^1.2.2",
     "@types/three": "^0.182.0",
     "@vueup/vue-quill": "1.2.0",
@@ -35,6 +41,7 @@
     "js-cookie": "3.0.5",
     "jsencrypt": "3.3.2",
     "nprogress": "0.2.0",
+    "pbf": "^5.1.0",
     "pinia": "3.0.2",
     "splitpanes": "4.0.4",
     "three": "^0.182.0",
@@ -44,6 +51,7 @@
     "vuedraggable": "4.1.0"
   },
   "devDependencies": {
+    "@playwright/test": "^1.60.0",
     "@vitejs/plugin-vue": "5.2.4",
     "sass-embedded": "1.89.1",
     "unplugin-auto-import": "0.18.6",

+ 42 - 0
RuoYi-Vue3/playwright.config.ts

@@ -0,0 +1,42 @@
+import { defineConfig, devices } from '@playwright/test';
+
+/**
+ * Playwright 配置文件
+ * 使用指定的 Chrome 浏览器路径
+ */
+export default defineConfig({
+  testDir: './tests',
+  fullyParallel: true,
+  forbidOnly: !!process.env.CI,
+  retries: process.env.CI ? 2 : 0,
+  workers: process.env.CI ? 1 : undefined,
+  reporter: 'html',
+  
+  use: {
+    baseURL: 'http://localhost:5173',
+    trace: 'on-first-retry',
+    screenshot: 'only-on-failure',
+    video: 'retain-on-failure',
+  },
+
+  projects: [
+    {
+      name: 'chromium',
+      use: {
+        ...devices['Desktop Chrome'],
+        // 使用指定的 Chrome 浏览器路径
+        channel: 'chrome',
+        launchOptions: {
+          executablePath: 'D:\\Software\\chrome-win64\\chrome.exe',
+        },
+      },
+    },
+  ],
+
+  webServer: {
+    command: 'npm run dev',
+    url: 'http://localhost:5173',
+    reuseExistingServer: !process.env.CI,
+    timeout: 120000,
+  },
+});

+ 146 - 0
RuoYi-Vue3/src/supermap-cesium-module/components/MvtAttributePopup.vue

@@ -0,0 +1,146 @@
+<template>
+  <div 
+    v-if="visible" 
+    class="mvt-attribute-popup"
+    :style="{ left: position.x + 'px', top: position.y + 'px' }"
+  >
+    <div class="popup-header">
+      <span class="popup-title">{{ title }}</span>
+      <button class="popup-close" @click="close">×</button>
+    </div>
+    <div class="popup-content">
+      <table class="attribute-table">
+        <thead>
+          <tr>
+            <th>字段别名</th>
+            <th>字段值</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-for="(value, key) in attributes" :key="key">
+            <td>{{ key }}</td>
+            <td>{{ formatValue(value) }}</td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'MvtAttributePopup',
+  props: {
+    visible: {
+      type: Boolean,
+      default: false
+    },
+    position: {
+      type: Object,
+      default: () => ({ x: 0, y: 0 })
+    },
+    title: {
+      type: String,
+      default: '属性信息'
+    },
+    attributes: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  methods: {
+    formatValue(value) {
+      if (value === null || value === undefined) {
+        return '-';
+      }
+      return String(value);
+    },
+    close() {
+      this.$emit('close');
+    }
+  }
+};
+</script>
+
+<style scoped>
+.mvt-attribute-popup {
+  position: fixed;
+  background: white;
+  border-radius: 8px;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
+  z-index: 9999;
+  min-width: 250px;
+  max-width: 400px;
+  overflow: hidden;
+  transform: translate(-50%, -100%);
+  transform-origin: bottom center;
+}
+
+.popup-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 12px 16px;
+  background: linear-gradient(135deg, #409eff 0%, #67c23a 100%);
+  color: white;
+}
+
+.popup-title {
+  font-weight: 600;
+  font-size: 14px;
+}
+
+.popup-close {
+  background: none;
+  border: none;
+  color: white;
+  font-size: 20px;
+  cursor: pointer;
+  padding: 0;
+  width: 24px;
+  height: 24px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.popup-close:hover {
+  background: rgba(255, 255, 255, 0.2);
+  border-radius: 50%;
+}
+
+.popup-content {
+  padding: 12px;
+  max-height: 300px;
+  overflow-y: auto;
+}
+
+.attribute-table {
+  width: 100%;
+  border-collapse: collapse;
+  font-size: 13px;
+}
+
+.attribute-table th {
+  text-align: left;
+  padding: 8px 12px;
+  background: #f5f7fa;
+  color: #606266;
+  font-weight: 500;
+  border-bottom: 1px solid #ebeef5;
+}
+
+.attribute-table td {
+  padding: 8px 12px;
+  border-bottom: 1px solid #ebeef5;
+  word-break: break-all;
+}
+
+.attribute-table tbody tr:hover {
+  background: #fafafa;
+}
+
+.attribute-table tbody tr:last-child td {
+  border-bottom: none;
+}
+</style>

+ 266 - 191
RuoYi-Vue3/src/supermap-cesium-module/config/server_config.js

@@ -1,192 +1,267 @@
 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',
-                name: "CBD",
-                layers: [{ type: 'S3M', layerName: 'Building@CBD' }, { type: 'S3M', layerName: 'Tree@CBD' }, { type: 'S3M', layerName: 'Xiaopin@CBD' },{ type: 'S3M', layerName: 'Lake@CBD' }, { type: 'S3M', layerName: 'Ground@CBD' }, { type: 'S3M', layerName: 'Ground2@CBD' }, { type: 'S3M', layerName: 'Bridge@CBD' }],
-                state: 0
-            },
-            {
-                type: "REALSPACE",
-                thumbnail: "/img/webServeImg/铁岭.png",
-                proxiedUrl: "https://www.supermapol.com/realspace/services/3D-QingXieSheYingMoXing/rest/realspace",
-                name: "倾斜摄影模型",
-                layers: [{ type: 'S3M', layerName: 'qingxie' }],
-                state: 0
-            },
-            {
-                type: "REALSPACE",
-                thumbnail: "/img/webServeImg/白模.png",
-                proxiedUrl: 'https://www.supermapol.com/realspace/services/3D-CQmodel_wireframe_2000/rest/realspace',
-                name: "白模",
-                layers: [{ type: 'S3M', layerName: 'CQmodel' }],
-                style: { fillStyle: 'Fill_And_WireFrame' },  
-                state: 0
-            },
-            {
-                type: "REALSPACE",
-                thumbnail: "/img/webServeImg/BIM.png",
-                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",
-                name: "珠峰地形影像",
-                layers: [{ type: 'IMG', layerName: 'image' }, { type: 'TERRAIN', layerName: 'srtm_54_07%40zhufeng' }],
-                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",
-                name: "水下地形",
-                layers: [{ type: 'S3M', layerName: 'SXDX@fujian' }],
-                state: 0,
-                useServerView: true,
-                transparentColor: '#000000',
-                transparentColorTolerance: 0.05
-            },
-            {
-                type: "REALSPACE",
-                thumbnail: "/img/webServeImg/点云.png",
-                proxiedUrl: "https://www.supermapol.com/realspace/services/3D-cloud/rest/realspace",
-                name: "点云",
-                layers: [{ type: 'S3M', layerName: 'POINTCLOUD23' }],
-                state: 0
-            },
-            {
-                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",
-                name: "京津地区MVT",
-                layers: [{ type: 'MVT', layerName: '京津地区MVT' }],
-                state: 0
-            },
-        ]
-    },
-    {
-        id: "baseLayer",
-        name: "在线底图",
-        children: [
-            {
-                proxiedUrl: "/img/baseLayer/baseImage.png",
-                name: "本地图片",
-                thumbnail: "/img/baseLayer/image.png",
-                title: 'Image',
-                type: 'IMAGE',
-                state: 0
-            },
-            {
-                proxiedUrl: '//dev.virtualearth.net',
-                name: 'BingMap',
-                thumbnail: '/img/baseLayer/Bing.png',
-                title: 'BingMap',
-                type: 'BINGMAP',
-                state: 0
-            },
-            {
-                proxiedUrl: 'https://[subdomain].tianditu.gov.cn/img_w/wmts',
-                name: '天地图',
-                thumbnail: '/img/baseLayer/tianditu.png',
-                title: '天地图',
-                type: 'TIANDITU',
-                state: 0
-            },
-            {
-                proxiedUrl: 'https://a.tile.openstreetmap.org/',
-                name: 'Open Street Map',
-                thumbnail: '/img/baseLayer/OSM.png',
-                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',
-                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
-            // }
-
-        ]
-    },
-    {
-        id: "onlineTerrain",
-        name: "在线地形",
-        children: [
-            {
-                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地形',
-                type: 'supermapOnlineTerrain',
-                state: 0
-            },
-            {
-                proxiedUrl: '',
-                name: '天地图地形',
-                thumbnail: '/img/baseLayer/天地图地形.png',
-                title: '天地图地形',
-                type: 'tianDiTuTerrain',
-                state: 0
-            },
-            {
-                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地形',
-                type: 'STKTerrain',
-                state: 0
-            }
-
-        ]
-    }
-]
-
-
-
-
-
+  {
+    id: "vectorData",
+    name: "矢量数据",
+    children: [
+      {
+        type: "RESTFEATURE",
+        thumbnail: "/img/componentsImg/mvt.png",
+        proxiedUrl:
+          "http://localhost:8090/iserver/services/data-vec_workspace/rest/data/datasources/vec_data/datasets/building",
+        name: "建筑",
+        state: 0,
+        style: {
+          fillColor: "#00FF00",
+          fillOpacity: 0.4,
+          strokeColor: "#0055FF",
+          strokeWidth: 2,
+        },
+      },
+      {
+        type: "MVT",
+        thumbnail: "/img/webServeImg/MVT.jpg",
+        proxiedUrl:
+          "http://localhost:8090/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: "RESTFEATURE",
+        thumbnail: "/img/componentsImg/mvt.png",
+        proxiedUrl:
+          "http://localhost:8090/iserver/services/data-vec_workspace/rest/data/datasources/vec_data/datasets/shuixi",
+        name: "水系",
+        state: 0,
+        style: {
+          fillColor: "#00BFFF",
+          fillOpacity: 0.5,
+          strokeColor: "#0066CC",
+          strokeWidth: 2,
+        },
+      },
+      {
+        type: "RESTFEATURE",
+        thumbnail: "/img/componentsImg/mvt.png",
+        proxiedUrl:
+          "http://localhost:8090/iserver/services/data-vec_workspace/rest/data/datasources/vec_data/datasets/liuyu",
+        name: "流域",
+        state: 0,
+        style: {
+          fillColor: "#98FB98",
+          fillOpacity: 0.3,
+          strokeColor: "#228B22",
+          strokeWidth: 2,
+        },
+      },
+    ],
+  },
+  {
+    id: "webServe",
+    name: "公共服务",
+    children: [
+      // layers:删除图层配置
+      // style:图层风格配置
+      {
+        type: "REALSPACE",
+        thumbnail: "/img/webServeImg/CBD.png",
+        proxiedUrl:
+          "https://www.supermapol.com/realspace/services/3D-CBD/rest/realspace",
+        name: "CBD",
+        layers: [
+          { type: "S3M", layerName: "Building@CBD" },
+          { type: "S3M", layerName: "Tree@CBD" },
+          { type: "S3M", layerName: "Xiaopin@CBD" },
+          { type: "S3M", layerName: "Lake@CBD" },
+          { type: "S3M", layerName: "Ground@CBD" },
+          { type: "S3M", layerName: "Ground2@CBD" },
+          { type: "S3M", layerName: "Bridge@CBD" },
+        ],
+        state: 0,
+      },
+      {
+        type: "REALSPACE",
+        thumbnail: "/img/webServeImg/铁岭.png",
+        proxiedUrl:
+          "https://www.supermapol.com/realspace/services/3D-QingXieSheYingMoXing/rest/realspace",
+        name: "倾斜摄影模型",
+        layers: [{ type: "S3M", layerName: "qingxie" }],
+        state: 0,
+      },
+      {
+        type: "REALSPACE",
+        thumbnail: "/img/webServeImg/白模.png",
+        proxiedUrl:
+          "https://www.supermapol.com/realspace/services/3D-CQmodel_wireframe_2000/rest/realspace",
+        name: "白模",
+        layers: [{ type: "S3M", layerName: "CQmodel" }],
+        style: { fillStyle: "Fill_And_WireFrame" },
+        state: 0,
+      },
+      {
+        type: "REALSPACE",
+        thumbnail: "/img/webServeImg/BIM.png",
+        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",
+        name: "珠峰地形影像",
+        layers: [
+          { type: "IMG", layerName: "image" },
+          { type: "TERRAIN", layerName: "srtm_54_07%40zhufeng" },
+        ],
+        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",
+        name: "水下地形",
+        layers: [{ type: "S3M", layerName: "SXDX@fujian" }],
+        state: 0,
+        useServerView: true,
+        transparentColor: "#000000",
+        transparentColorTolerance: 0.05,
+      },
+      {
+        type: "REALSPACE",
+        thumbnail: "/img/webServeImg/点云.png",
+        proxiedUrl:
+          "https://www.supermapol.com/realspace/services/3D-cloud/rest/realspace",
+        name: "点云",
+        layers: [{ type: "S3M", layerName: "POINTCLOUD23" }],
+        state: 0,
+      },
+      {
+        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",
+        name: "京津地区MVT",
+        layers: [{ type: "MVT", layerName: "京津地区MVT" }],
+        state: 0,
+      },
+    ],
+  },
+  {
+    id: "baseLayer",
+    name: "在线底图",
+    children: [
+      {
+        proxiedUrl: "/img/baseLayer/baseImage.png",
+        name: "本地图片",
+        thumbnail: "/img/baseLayer/image.png",
+        title: "Image",
+        type: "IMAGE",
+        state: 0,
+      },
+      {
+        proxiedUrl: "//dev.virtualearth.net",
+        name: "BingMap",
+        thumbnail: "/img/baseLayer/Bing.png",
+        title: "BingMap",
+        type: "BINGMAP",
+        state: 0,
+      },
+      {
+        proxiedUrl: "https://[subdomain].tianditu.gov.cn/img_w/wmts",
+        name: "天地图",
+        thumbnail: "/img/baseLayer/tianditu.png",
+        title: "天地图",
+        type: "TIANDITU",
+        state: 0,
+      },
+      {
+        proxiedUrl: "https://a.tile.openstreetmap.org/",
+        name: "Open Street Map",
+        thumbnail: "/img/baseLayer/OSM.png",
+        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",
+        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
+      // }
+    ],
+  },
+  {
+    id: "onlineTerrain",
+    name: "在线地形",
+    children: [
+      {
+        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地形",
+        type: "supermapOnlineTerrain",
+        state: 0,
+      },
+      {
+        proxiedUrl: "",
+        name: "天地图地形",
+        thumbnail: "/img/baseLayer/天地图地形.png",
+        title: "天地图地形",
+        type: "tianDiTuTerrain",
+        state: 0,
+      },
+      {
+        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地形",
+        type: "STKTerrain",
+        state: 0,
+      },
+    ],
+  },
+];

+ 155 - 365
RuoYi-Vue3/src/supermap-cesium-module/js/common/layerManagement.js

@@ -1,4 +1,5 @@
 import { actions, storeDate } from '../store/store.js'   //局部状态管理
+import { fetchFeaturesFromSuperMap, loadDatasetFeatures, loadSuperMapRestDataService } from './vectorDataLoader.js';
 
 // 添加s3m
 function addS3mLayers(scps, callback) {
@@ -150,14 +151,13 @@ function addScene(url, options, callback) {
             }, 'SCENE');
         } catch (e) {
             console.error('addScene 错误:', e);
-            let widget = viewer.cesiumWidget;
-            if (widget._showRenderLoopErrors) {
-                let title = "渲染时发生错误,已停止渲染。";
-                widget.showErrorPanel(title, undefined, e);
-            }
+            // 不显示错误面板,避免影响地图渲染
+            // 只记录错误并继续执行
+            callback(null, 'SCENE');
         }
     } else {
         console.log('URL检查失败');
+        callback(null, 'SCENE');
     }
 };
 
@@ -285,33 +285,95 @@ function flyToChinaRegion() {
 // 添加mvt
 function addMvtLayer(LayerURL, name, callback) {
     try {
+        console.log('===== addMvtLayer 开始 =====');
+        console.log('URL:', LayerURL);
+        console.log('名称:', name);
+
         let mvtMap = viewer.scene.addVectorTilesMap({
             url: LayerURL,
             canvasWidth: 512,
             name: name || 'mvt',
             viewer: viewer
         });
+
+        console.log('MVT地图对象:', mvtMap);
+        console.log('MVT地图对象属性:', Object.keys(mvtMap));
+        console.log('场景中的矢量瓦片地图:', viewer.scene.vectorTilesMaps);
+        console.log('场景中的imageryLayers:', viewer.imageryLayers);
+
+        // 检查MVT地图是否可见
+        if (mvtMap.show !== undefined) {
+            console.log('MVT地图show属性:', mvtMap.show);
+            mvtMap.show = true;
+        }
+
         Cesium.when(mvtMap.readyPromise, function (data) {
+            console.log('MVT加载完成,回调数据:', data);
+            console.log('MVT地图范围:', mvtMap.rectangle);
+
             var bounds = mvtMap.rectangle;
-            viewer.scene.camera.flyTo({
-                destination: new Cesium.Cartesian3.fromRadians(
-                    (bounds.east + bounds.west) * 0.5,
-                    (bounds.north + bounds.south) * 0.5,
-                    10000
-                ),
-                duration: 0,
-                orientation: { heading: 0, roll: 0 }
-            });
+            if (bounds) {
+                console.log('范围 - 西:', bounds.west, '东:', bounds.east, '南:', bounds.south, '北:', bounds.north);
+
+                // 转换为度数查看
+                console.log('范围(度) - 西:', Cesium.Math.toDegrees(bounds.west),
+                            '东:', Cesium.Math.toDegrees(bounds.east),
+                            '南:', Cesium.Math.toDegrees(bounds.south),
+                            '北:', Cesium.Math.toDegrees(bounds.north));
+
+                viewer.scene.camera.flyTo({
+                    destination: new Cesium.Cartesian3.fromRadians(
+                        (bounds.east + bounds.west) * 0.5,
+                        (bounds.north + bounds.south) * 0.5,
+                        10000
+                    ),
+                    duration: 0,
+                    orientation: { heading: 0, roll: 0 }
+                });
+            } else {
+                console.warn('MVT地图范围为空,无法定位');
+            }
+
+            // 检查imageryLayers中是否有MVT图层
+            console.log('MVT加载后imageryLayers数量:', viewer.imageryLayers.length);
+            for (let i = 0; i < viewer.imageryLayers.length; i++) {
+                let layer = viewer.imageryLayers.get(i);
+                console.log(`图层${i}:`, {
+                    name: layer.name,
+                    type: layer.imageryProvider?.constructor?.name,
+                    show: layer.show,
+                    alpha: layer.alpha
+                });
+            }
+
+            // 检查MVT地图的_inner属性
+            console.log('MVT地图_inner:', mvtMap._inner);
+            if (mvtMap._inner) {
+                console.log('_inner属性:', Object.keys(mvtMap._inner));
+            }
+
+            // 检查MVT地图的_provider属性
+            console.log('MVT地图_provider:', mvtMap._provider);
+            if (mvtMap._provider) {
+                console.log('_provider属性:', Object.keys(mvtMap._provider));
+            }
+
             actions.setChangeLayers();
             callback(mvtMap)
+        }, function(error) {
+            console.error('MVT加载失败:', error);
+            if (callback) callback(null, error);
         });
+
         return mvtMap
     } catch (e) {
+        console.error('addMvtLayer异常:', e);
         let widget = viewer.cesiumWidget;
         if (widget._showRenderLoopErrors) {
             let title = "渲染时发生错误,已停止渲染。";
             widget.showErrorPanel(title, undefined, e);
         }
+        if (callback) callback(null, e);
     }
 };
 
@@ -405,375 +467,101 @@ function addGeoJsonLayer(url, name, callback) {
     }
 };
 
-// 从超图REST服务获取要素并转换为标准GeoJSON
-function fetchFeaturesFromSuperMap(url, timeout) {
-    const timeoutPromise = new Promise((_, reject) => {
-        setTimeout(() => reject(new Error('请求超时')), timeout);
-    });
+// 加载GeoJson图层(支持自定义样式)
+function addGeoJsonLayerWithStyle(url, name, style, callback) {
+    // 存储当前样式配置供processGeoJsonEntities使用
+    window.currentGeoJsonStyle = style;
     
-    return Promise.race([
-        fetch(url, { timeout: timeout }),
-        timeoutPromise
-    ])
-    .then(response => {
-        if (!response.ok) {
-            throw new Error(`HTTP错误: ${response.status}`);
-        }
-        return response.text();
-    })
-    .then(text => {
-        try {
-            return JSON.parse(text);
-        } catch (e) {
-            console.error('JSON解析失败:', text.substring(0, 200));
-            throw new Error('服务返回的不是有效的JSON数据');
-        }
-    })
-    .then(superMapData => {
-        // 超图返回的格式是: { features: [...], totalCount: N, ... }
-        // 需要转换为标准GeoJSON格式: { type: 'FeatureCollection', features: [...] }
-        
-        // 调试:查看完整返回数据结构
-        console.log('超图服务返回的数据:', JSON.stringify(superMapData, null, 2).substring(0, 2000));
-        console.log('数据类型:', typeof superMapData);
-        console.log('数据键:', Object.keys(superMapData || {}));
-        
-        let features = [];
-        
-        // 尝试多种可能的数据结构
-        if (superMapData.features && Array.isArray(superMapData.features)) {
-            features = superMapData.features;
-            console.log('从 features 属性获取要素');
-        } else if (superMapData.recordset && Array.isArray(superMapData.recordset)) {
-            features = superMapData.recordset;
-            console.log('从 recordset 属性获取要素');
-        } else if (superMapData.result && superMapData.result.features && Array.isArray(superMapData.result.features)) {
-            features = superMapData.result.features;
-            console.log('从 result.features 属性获取要素');
-        } else if (Array.isArray(superMapData)) {
-            features = superMapData;
-            console.log('数据本身是数组');
-        } else {
-            console.warn('未找到要素数据,尝试查找其他属性');
-            // 遍历所有属性查找数组
-            for (const key of Object.keys(superMapData || {})) {
-                if (Array.isArray(superMapData[key])) {
-                    console.log(`发现数组属性 ${key},长度:`, superMapData[key].length);
-                    if (superMapData[key].length > 0 && superMapData[key][0].geometry) {
-                        features = superMapData[key];
-                        console.log(`使用 ${key} 作为要素数组`);
-                        break;
-                    }
-                }
-            }
-        }
-        
-        // 转换每个要素的坐标格式(超图可能返回平面坐标,需要转换为经纬度)
-        console.log('原始要素数量:', features.length);
-        if (features.length > 0) {
-            console.log('第一个要素示例:', JSON.stringify(features[0], null, 2).substring(0, 1000));
+    try {
+        // 判断是否为超图REST数据服务
+        if (url.includes('/iserver/services/') && url.includes('/rest/data/datasources')) {
+            loadSuperMapRestDataService(url, name, callback);
+            return;
         }
-        
-        features = features.map((feature, index) => {
-            if (!feature) {
-                console.warn(`要素 ${index} 为空`);
-                return null;
-            }
-            if (!feature.geometry) {
-                console.warn(`要素 ${index} 没有geometry`);
-                return null;
-            }
-            if (!feature.geometry.coordinates) {
-                console.warn(`要素 ${index} 没有coordinates`);
-                return null;
-            }
-            
-            // 尝试转换坐标
-            try {
-                feature.geometry.coordinates = convertCoordinates(feature.geometry.coordinates);
-                return feature;
-            } catch (e) {
-                console.error(`要素 ${index} 坐标转换失败:`, e);
-                return null;
-            }
-        }).filter(Boolean);
-        
-        console.log('转换后的要素数量:', features.length);
-        
-        // 返回标准GeoJSON FeatureCollection
-        return {
-            type: 'FeatureCollection',
-            features: features
-        };
-    });
-}
 
-// 坐标转换:尝试将平面坐标转换为经纬度
-function convertCoordinates(coordinates) {
-    if (!Array.isArray(coordinates)) {
-        return coordinates;
-    }
-    
-    // 检查是否是经纬度范围(-180到180,-90到90)
-    const isLatLng = (coord) => {
-        return Math.abs(coord[0]) <= 180 && Math.abs(coord[1]) <= 90;
-    };
-    
-    // 检查是否是Web Mercator或其他大坐标值
-    const isLargeCoord = (coord) => {
-        return Math.abs(coord[0]) > 10000 || Math.abs(coord[1]) > 10000;
-    };
-    
-    // 处理点坐标
-    if (coordinates.length >= 2 && typeof coordinates[0] === 'number') {
-        if (isLargeCoord(coordinates)) {
-            console.log('检测到大坐标值,尝试Web Mercator转WGS84:', coordinates);
+        const fillColor = Cesium.Color.fromCssColorString(style.fillColor || '#00FF00').withAlpha(style.fillOpacity || 0.4);
+        const strokeColor = Cesium.Color.fromCssColorString(style.strokeColor || '#0055FF');
+
+        const loadPromise = Cesium.GeoJsonDataSource.load(url, {
+            stroke: strokeColor.withAlpha(1),
+            fill: fillColor,
+            strokeWidth: style.strokeWidth || 3,
+            clampToGround: true
+        });
+
+        Cesium.when(loadPromise, function (dataSource) {
             try {
-                const cartesian3 = Cesium.Cartesian3.fromArray(coordinates);
-                const cartographic = Cesium.Cartographic.fromCartesian(cartesian3);
-                return [
-                    Cesium.Math.toDegrees(cartographic.longitude),
-                    Cesium.Math.toDegrees(cartographic.latitude),
-                    cartographic.height || 0
-                ];
+                viewer.dataSources.add(dataSource);
+                processGeoJsonEntities(dataSource, name, style);
+                viewer.flyTo(dataSource, { duration: 2 });
+                actions.setChangeLayers();
+                if (callback) callback(dataSource);
             } catch (e) {
-                console.warn('坐标转换失败:', e);
-                return coordinates;
+                console.error("添加GeoJSON数据源失败:", e);
+                if (callback) callback(null, e);
             }
-        }
-        return coordinates;
-    }
-    
-    // 处理线坐标(二维数组)
-    if (coordinates.length > 0 && Array.isArray(coordinates[0])) {
-        return coordinates.map(coord => convertCoordinates(coord));
-    }
-    
-    // 处理多边形坐标(三维数组)
-    if (coordinates.length > 0 && Array.isArray(coordinates[0][0])) {
-        return coordinates.map(ring => ring.map(coord => convertCoordinates(coord)));
+        }, function (error) {
+            console.error("加载GeoJSON失败:", error);
+            if (callback) callback(null, error);
+        });
+    } catch (e) {
+        console.error("加载GeoJSON异常:", e);
+        if (callback) callback(null, e);
     }
-    
-    return coordinates;
-}
+};
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 
-// 加载超图REST数据服务
-function loadSuperMapRestDataService(baseUrl, name, callback) {
-    console.log('开始加载超图REST数据服务:', baseUrl);
-    
-    // 设置超时时间
-    const timeout = 30000; // 30秒
-    
-    // 创建超时Promise
-    const timeoutPromise = new Promise((_, reject) => {
-        setTimeout(() => reject(new Error('请求超时')), timeout);
-    });
-    
-    // 修正URL:添加.json后缀(如果缺少的话)
-    let datasourcesUrl = baseUrl;
-    if (!datasourcesUrl.endsWith('.json')) {
-        datasourcesUrl = `${baseUrl}.json`;
-    }
-    console.log('修正后的数据源URL:', datasourcesUrl);
-    
-    // 获取数据源列表
-    Promise.race([
-        fetch(datasourcesUrl, { timeout: timeout }),
-        timeoutPromise
-    ])
-    .then(response => {
-        if (!response.ok) {
-            // 如果.json后缀失败,尝试不加后缀
-            if (datasourcesUrl !== baseUrl) {
-                console.log(`HTTP错误: ${response.status},尝试不加.json后缀`);
-                return fetch(baseUrl, { timeout: timeout });
-            }
-            throw new Error(`HTTP错误: ${response.status}`);
-        }
-        return response;
-    })
-    .then(response => {
-        if (!response.ok) {
-            throw new Error(`HTTP错误: ${response.status}`);
-        }
-        return response.text();
-    })
-    .then(text => {
-        // 尝试解析JSON
-        try {
-            return JSON.parse(text);
-        } catch (e) {
-            // 如果JSON解析失败,检查是否是HTML错误页面
-            console.error('JSON解析失败,响应内容:', text.substring(0, 200));
-            throw new Error('服务返回的不是有效的JSON数据');
-        }
-    })
-    .then(dataSources => {
-        console.log('获取到数据源列表:', dataSources);
-        
-        // 处理超图REST数据服务返回的格式
-        // 超图返回的格式是: { datasourceNames: [...], childUriList: [...], datasourceCount: N }
-        let dataSourceNames = [];
-        if (Array.isArray(dataSources)) {
-            // 如果返回的是数组,直接使用
-            dataSourceNames = dataSources.map(ds => ds.name || ds);
-        } else if (dataSources && dataSources.datasourceNames && Array.isArray(dataSources.datasourceNames)) {
-            // 超图REST数据服务格式
-            dataSourceNames = dataSources.datasourceNames;
-        }
-        
-        if (!dataSourceNames || dataSourceNames.length === 0) {
-            console.warn('未找到数据源');
-            if (callback) callback(null);
-            return;
-        }
 
-        // 遍历所有数据源
-        const loadPromises = [];
-        
-        dataSourceNames.forEach(dataSourceName => {
-            // 确保数据源名称是字符串
-            const name = typeof dataSourceName === 'object' ? (dataSourceName.name || dataSourceName) : dataSourceName;
-            let datasetsUrl = `${baseUrl}/${encodeURIComponent(name)}/datasets`;
-            if (!datasetsUrl.endsWith('.json')) {
-                datasetsUrl += '.json';
-            }
-            
-            // 获取数据集列表
-            const datasetPromise = Promise.race([
-                fetch(datasetsUrl, { timeout: timeout }),
-                timeoutPromise
-            ])
-            .then(response => {
-                if (!response.ok) {
-                    if (datasetsUrl !== `${baseUrl}/${encodeURIComponent(name)}/datasets`) {
-                        return fetch(`${baseUrl}/${encodeURIComponent(name)}/datasets`, { timeout: timeout });
-                    }
-                    throw new Error(`HTTP错误: ${response.status}`);
-                }
-                return response;
-            })
-            .then(response => {
-                if (!response.ok) {
-                    throw new Error(`HTTP错误: ${response.status}`);
-                }
-                return response.text();
-            })
-            .then(text => {
-                try {
-                    return JSON.parse(text);
-                } catch (e) {
-                    console.error(`数据集列表JSON解析失败 (${name}):`, text.substring(0, 200));
-                    throw new Error('数据集列表不是有效的JSON');
-                }
-            })
-            .then(datasets => {
-                console.log(`数据源 ${name} 的数据集列表:`, datasets);
-                
-                // 处理数据集格式
-                let datasetNames = [];
-                if (Array.isArray(datasets)) {
-                    datasetNames = datasets.map(ds => ds.name || ds);
-                } else if (datasets && datasets.datasetNames && Array.isArray(datasets.datasetNames)) {
-                    datasetNames = datasets.datasetNames;
-                }
-                
-                if (!datasetNames || datasetNames.length === 0) {
-                    return Promise.resolve([]);
-                }
 
-                // 加载每个数据集的要素(限制同时加载的数量)
-                const featurePromises = [];
-                const maxConcurrent = 3; // 最大并发数
-                
-                for (let i = 0; i < datasetNames.length; i += maxConcurrent) {
-                    const batch = datasetNames.slice(i, i + maxConcurrent);
-                    const batchPromises = batch.map(dsName => {
-                        // 确保数据集名称是字符串
-                        const datasetName = typeof dsName === 'object' ? (dsName.name || dsName) : dsName;
-                        let featuresUrl = `${baseUrl}/${encodeURIComponent(name)}/datasets/${encodeURIComponent(datasetName)}/features.json?returnContent=true`;
-                        console.log('加载要素URL:', featuresUrl);
-                        
-                        // 先获取超图格式的要素数据,转换为标准GeoJSON后再加载
-                        return fetchFeaturesFromSuperMap(featuresUrl, timeout)
-                            .then(geoJsonData => {
-                                if (!geoJsonData) {
-                                    throw new Error('未获取到有效要素数据');
-                                }
-                                // 使用Cesium加载GeoJSON
-                                return Cesium.GeoJsonDataSource.load(geoJsonData, {
-                                    stroke: Cesium.Color.fromCssColorString('#0055FF').withAlpha(1),
-                                    fill: Cesium.Color.fromCssColorString('#00FF00').withAlpha(0.4),
-                                    strokeWidth: 3,
-                                    clampToGround: true
-                                });
-                            })
-                            .then(geoJsonDataSource => {
-                                viewer.dataSources.add(geoJsonDataSource);
-                                processGeoJsonEntities(geoJsonDataSource, `${name}-${datasetName}`);
-                                return geoJsonDataSource;
-                            })
-                            .catch(error => {
-                                console.error(`加载数据集 ${datasetName} 失败:`, error);
-                                return null;
-                            });
-                    });
-                    
-                    featurePromises.push(Promise.all(batchPromises));
-                }
-                
-                return Promise.all(featurePromises).then(results => results.flat());
-            })
-            .catch(error => {
-                console.error(`获取数据集列表失败 (${name}):`, error);
-                return [];
-            });
-            
-            loadPromises.push(datasetPromise);
-        });
 
-        Promise.all(loadPromises)
-            .then(results => {
-                const allDataSources = results.flat().filter(Boolean);
-                console.log('所有加载的GeoJSON数据源:', allDataSources);
-                
-                if (allDataSources.length > 0) {
-                    viewer.flyTo(allDataSources[0], { duration: 2 });
-                }
-                
-                actions.setChangeLayers();
-                if (callback) callback(allDataSources.length > 0 ? allDataSources : null);
-            })
-            .catch(error => {
-                console.error('加载超图REST数据服务失败:', error);
-                if (callback) callback(null);
-            });
-    })
-    .catch(error => {
-        console.error('获取数据源列表失败:', error);
-        if (callback) callback(null);
-    });
-}
 
 // 处理GeoJSON实体(异步方式,避免阻塞主线程)
-function processGeoJsonEntities(dataSource, name) {
+function processGeoJsonEntities(dataSource, name, customStyle) {
     try {
         let entities = dataSource.entities.values;
         let entitiesLength = entities.length;
         
+        console.log(`开始处理 ${entitiesLength} 个实体`);
+        
         // 限制处理的实体数量,防止大量数据导致卡死
-        const maxEntities = 5000;
+        const maxEntities = 10000;
         if (entitiesLength > maxEntities) {
             console.warn(`实体数量过多(${entitiesLength}),仅处理前${maxEntities}个`);
             entitiesLength = maxEntities;
         }
 
+        // 获取样式配置
+        const style = customStyle || window.currentGeoJsonStyle || {
+            fillColor: '#00FF00',
+            fillOpacity: 0.4,
+            strokeColor: '#0055FF',
+            strokeWidth: 2
+        };
+        
+        const fillColor = Cesium.Color.fromCssColorString(style.fillColor).withAlpha(style.fillOpacity);
+        const strokeColor = Cesium.Color.fromCssColorString(style.strokeColor);
+        const strokeWidth = style.strokeWidth || 2;
+
         // 使用异步批量处理
-        const batchSize = 50; // 每批处理50个实体
+        const batchSize = 100; // 每批处理100个实体
         let currentIndex = 0;
 
         const processBatch = () => {
             if (currentIndex >= entitiesLength) {
+                console.log(`处理完成,共处理 ${entitiesLength} 个实体`);
                 return; // 处理完成
             }
 
@@ -793,23 +581,23 @@ function processGeoJsonEntities(dataSource, name) {
                     entity.polygon.perPositionHeight = false;
                     entity.polygon.classificationType = Cesium.ClassificationType.TERRAIN;
                     entity.polygon.disableDepthTestDistance = Number.POSITIVE_INFINITY;
-                    entity.polygon.material = Cesium.Color.fromCssColorString('#00FF00').withAlpha(0.4);
-                    entity.polygon.outlineColor = Cesium.Color.fromCssColorString('#0055FF');
-                    entity.polygon.outlineWidth = 4;
+                    entity.polygon.material = fillColor;
+                    entity.polygon.outlineColor = strokeColor;
+                    entity.polygon.outlineWidth = strokeWidth;
                 }
 
                 if (entity.polyline) {
                     entity.polyline.clampToGround = true;
                     entity.polyline.classificationType = Cesium.ClassificationType.TERRAIN;
-                    entity.polyline.material = Cesium.Color.fromCssColorString('#0055FF');
-                    entity.polyline.width = 4;
+                    entity.polyline.material = strokeColor;
+                    entity.polyline.width = strokeWidth;
                 }
 
                 if (entity.point) {
                     entity.point.clampToGround = true;
-                    entity.point.color = Cesium.Color.fromCssColorString('#FF0000');
-                    entity.point.pixelSize = 10;
-                    entity.point.outlineColor = Cesium.Color.fromCssColorString('#FFFFFF');
+                    entity.point.color = strokeColor;
+                    entity.point.pixelSize = style.pointSize || 10;
+                    entity.point.outlineColor = Cesium.Color.WHITE;
                     entity.point.outlineWidth = 2;
                     entity.point.disableDepthTestDistance = Number.POSITIVE_INFINITY;
                     entity.isGeoJsonPoint = true;
@@ -977,7 +765,8 @@ export default {
     addImageLayer,
     layersDelete,
     addMvtLayer,
-    addGeoJsonLayer
+    addGeoJsonLayer,
+    addGeoJsonLayerWithStyle
 };
 export {
     addS3mLayers,
@@ -986,5 +775,6 @@ export {
     addImageLayer,
     layersDelete,
     addMvtLayer,
-    addGeoJsonLayer
+    addGeoJsonLayer,
+    addGeoJsonLayerWithStyle
 };

+ 258 - 0
RuoYi-Vue3/src/supermap-cesium-module/js/common/mvtClickHandler.js

@@ -0,0 +1,258 @@
+/**
+ * MVT 要素属性查询工具
+ * 
+ * 由于数据服务(REST)的空间查询对 ShapeFile 数据源失效,
+ * 改为直接从 MVT 瓦片(PBF)中解析要素属性。
+ * 
+ * 流程:
+ * 1. 点击位置 → WGS84 经纬度
+ * 2. 经纬度 → 瓦片坐标 (z, x, y)
+ * 3. 下载 .mvt 瓦片
+ * 4. 解析 PBF,提取要素
+ * 5. 点-面包含检测 → 获取属性
+ */
+
+// 动态导入 PBF/MVT 解析库(pbf v5 导出 PbfReader,@mapbox/vector-tile 在 new Pbf() 时传入 Reader)
+import { PbfReader as Pbf } from 'pbf';
+import { VectorTile } from '@mapbox/vector-tile';
+
+class MvtClickHandler {
+  constructor(vueInstance, options = {}) {
+    this.vueInstance = vueInstance;
+
+    // MVT 瓦片服务地址(从配置获取,默认使用土壤图层)
+    this.tileUrlTemplate = options.tileUrl || 'http://localhost:8090/iserver/services/fj_soli_vector_tile/restjsr/v1/vectortile/maps/soil/tiles/{z}/{x}/{y}.mvt';
+
+    // 备用:数据服务查询(已确认空间查询不生效,作为最后降级)
+    this.serviceRootUrl = options.serviceRoot || '';
+  }
+
+  addMvtClickHandler() {}
+
+  /**
+   * 根据经纬度查询要素属性
+   */
+  async handleClick(lon, lat, screenPos) {
+    console.log(`\n[MVT] 开始查询: 经度=${lon.toFixed(6)}, 纬度=${lat.toFixed(6)}`);
+
+    // 方案1:从 MVT 瓦片解析(主要方案)
+    const tileResult = await this._queryFromTile(lon, lat);
+    if (tileResult && Object.keys(tileResult).length > 0) {
+      console.log("[MVT] === 瓦片查询成功 ===", tileResult);
+      this.showPopup({
+        title: tileResult.type || tileResult.TURANG_NAME || "土壤要素",
+        attributes: tileResult,
+        position: screenPos
+      });
+      return;
+    }
+
+    console.log("[MVT] 瓦片未命中,该点无土壤要素数据");
+    this.setPopupVisible(false);
+  }
+
+  /**
+   * 从 MVT 瓦片查询要素属性
+   */
+  async _queryFromTile(lon, lat) {
+    try {
+      // 先在 zoom=14 查询(细节适中),没命中再降级
+      const zoomLevels = [14, 13, 12, 11, 10];
+      
+      for (const z of zoomLevels) {
+        const tileX = this._lonToTile(lon, z);
+        const tileY = this._latToTile(lat, z);
+        
+        const tileUrl = this.tileUrlTemplate
+          .replace('{z}', z)
+          .replace('{x}', tileX)
+          .replace('{y}', tileY);
+        
+        // 下载瓦片
+        const response = await fetch(tileUrl);
+        if (!response.ok) continue;
+        
+        const arrayBuffer = await response.arrayBuffer();
+        if (!arrayBuffer || arrayBuffer.byteLength === 0) continue;
+        
+        // 解析 PBF
+        const tile = new VectorTile(new Pbf(arrayBuffer));
+        console.log(`[MVT] zoom=${z} 瓦片(${tileX},${tileY}) 图层:`, Object.keys(tile.layers));
+        
+        // 遍历所有图层找匹配要素
+        for (const layerName of Object.keys(tile.layers)) {
+          const layer = tile.layers[layerName];
+          console.log(`[MVT] 图层 "${layerName}": ${layer.length} 个要素`);
+          
+          // 获取瓦片范围(WGS84)
+          const tileBounds = this._getTileBounds(z, tileX, tileY);
+          
+          for (let i = 0; i < layer.length; i++) {
+            const feature = layer.feature(i);
+            
+            // 只有当要素是面类型时才检查包含关系
+            if (feature.type !== 3) continue; // 3 = Polygon, 4 = MultiPolygon
+            
+            // 将要素坐标从瓦片坐标转为 WGS84
+            const geoJsonGeom = this._tileToGeoJSON(feature, tileBounds, layer.extent);
+            
+            // 检查点是否在面内
+            if (this._pointInPolygon(lon, lat, geoJsonGeom.coordinates)) {
+              console.log(`[MVT] 命中! zoom=${z}, 图层=${layerName}, 要素索引=${i}`);
+              
+              // 提取属性
+              const props = {};
+              const keys = Object.keys(feature.properties || {});
+              // 优先取已知字段
+              const knownFields = ['FID_', 'SOIL_', 'SOIL_ID', 'DL', 'type', 'SmID', 'SMID'];
+              const allFields = [...new Set([...knownFields, ...keys])];
+              
+              allFields.forEach(key => {
+                if (feature.properties[key] !== undefined) {
+                  props[key] = feature.properties[key];
+                }
+              });
+              
+              console.log(`[MVT] 属性:`, props);
+              return props;
+            }
+          }
+        }
+      }
+      
+      return null;
+    } catch (err) {
+      console.error("[MVT] 瓦片查询异常:", err.message);
+      return null;
+    }
+  }
+
+  // ==================== 坐标转换工具 ====================
+
+  /**
+   * 经度 → 瓦片 X 坐标
+   */
+  _lonToTile(lon, zoom) {
+    return Math.floor((lon + 180) / 360 * Math.pow(2, zoom));
+  }
+
+  /**
+   * 纬度 → 瓦片 Y 坐标
+   */
+  _latToTile(lat, zoom) {
+    return Math.floor((1 - Math.log(Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI / 180)) / Math.PI) / 2 * Math.pow(2, zoom));
+  }
+
+  /**
+   * 获取瓦片的 WGS84 范围
+   */
+  _getTileBounds(z, x, y) {
+    const n = Math.pow(2, z);
+    const west = x / n * 360 - 180;
+    const east = (x + 1) / n * 360 - 180;
+    const north = Math.atan(Math.sinh(Math.PI * (1 - 2 * y / n))) * 180 / Math.PI;
+    const south = Math.atan(Math.sinh(Math.PI * (1 - 2 * (y + 1) / n))) * 180 / Math.PI;
+    return { west, east, north, south };
+  }
+
+  /**
+   * 将 MVT 要素的瓦片坐标转为 WGS84 坐标
+   */
+  _tileToGeoJSON(feature, bounds, extent) {
+    extent = extent || 4096;
+    const scaleX = (bounds.east - bounds.west) / extent;
+    const scaleY = (bounds.south - bounds.north) / extent;
+    
+    const transformCoords = (coords) => {
+      return coords.map(ring => {
+        return ring.map(([x, y]) => [
+          bounds.west + x * scaleX,
+          bounds.north + y * scaleY  // MVT y 轴向下,WGS84 y 轴向上
+        ]);
+      });
+    };
+    
+    // 从 feature 获取几何(MVT 是 Geometry类型,需要处理)
+    let geom;
+    try {
+      // feature.loadGeometry() 返回 [Ring, Ring, ...]
+      // 每个 Ring 是 [{x, y}, ...]
+      const rawGeom = feature.loadGeometry();
+      
+      if (feature.type === 3) {
+        // Polygon: 第一个 ring 是外环,后续是内环
+        geom = {
+          type: 'Polygon',
+          coordinates: transformCoords(rawGeom.map(ring => 
+            ring.map(p => [p.x, p.y])
+          ))
+        };
+      } else if (feature.type === 4) {
+        // MultiPolygon 需要分组
+        // 简单处理:把所有 rings 当做一个 polygon
+        geom = {
+          type: 'Polygon',
+          coordinates: transformCoords(rawGeom.map(ring => 
+            ring.map(p => [p.x, p.y])
+          ))
+        };
+      }
+    } catch (e) {
+      return null;
+    }
+    
+    return geom;
+  }
+
+  /**
+   * 射线法判断点是否在多边形内
+   */
+  _pointInPolygon(lon, lat, coordinates) {
+    if (!coordinates || !coordinates.length) return false;
+    
+    // 检查外环
+    const ring = coordinates[0];
+    if (!ring || ring.length < 3) return false;
+    
+    let inside = false;
+    for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
+      const xi = ring[i][0], yi = ring[i][1];
+      const xj = ring[j][0], yj = ring[j][1];
+      
+      if ((yi > lat) !== (yj > lat) &&
+          lon < (xj - xi) * (lat - yi) / (yj - yi) + xi) {
+        inside = !inside;
+      }
+    }
+    
+    return inside;
+  }
+
+  // ==================== 弹窗控制 ====================
+
+  setPopupVisible(flag) {
+    const target = this.vueInstance.mvtPopupVisible;
+    if (typeof target === "object" && target.value !== undefined) {
+      target.value = flag;
+    } else {
+      this.vueInstance.mvtPopupVisible = flag;
+    }
+  }
+
+  showPopup(params) {
+    const setVal = (key, val) => {
+      const prop = this.vueInstance[key];
+      if (typeof prop === "object" && prop && prop.value !== undefined) {
+        prop.value = val;
+      } else {
+        this.vueInstance[key] = val;
+      }
+    };
+    setVal("mvtPopupTitle", params.title);
+    setVal("mvtPopupAttributes", params.attributes);
+    setVal("mvtPopupPosition", params.position);
+    this.setPopupVisible(true);
+  }
+}
+
+export default MvtClickHandler;

+ 722 - 0
RuoYi-Vue3/src/supermap-cesium-module/js/common/vectorDataLoader.js

@@ -0,0 +1,722 @@
+/**
+ * 矢量数据加载器
+ * 负责从超图iServer REST数据服务获取矢量数据并转换为标准GeoJSON格式
+ * 优化版:支持分页加载,避免一次性请求全量数据,无截断、无超时丢失
+ */
+
+import { actions } from '../store/store.js';
+
+// 图层映射,用于后续管理
+const layerMap = new Map();
+
+/**
+ * 检测是否为投影坐标(大数值坐标)
+ * @param {number} x - 横坐标
+ * @param {number} y - 纵坐标
+ * @returns {boolean}
+ */
+function isProjectedCoordinate(x, y) {
+    return Math.abs(x) > 200 || Math.abs(y) > 100;
+}
+
+/**
+ * 简单的高斯-克吕格投影转经纬度(简化版)
+ * 适用于中国常用的CGCS2000或WGS84高斯-克吕格投影
+ * @param {number} x - 横坐标
+ * @param {number} y - 纵坐标
+ * @returns {number[]} - [经度, 纬度]
+ */
+function gaussKrugerToWGS84(x, y) {
+    let L0 = 0;
+    const zoneNumber = Math.floor(x / 1000000);
+    
+    if (zoneNumber >= 1 && zoneNumber <= 60) {
+        L0 = (zoneNumber * 6 - 3) * Math.PI / 180;
+    } else if (x > 0) {
+        L0 = 105 * Math.PI / 180;
+    }
+    
+    const a = 6378137;
+    const f = 1 / 298.257223563;
+    const e2 = 2 * f - f * f;
+    
+    const x0 = x - zoneNumber * 1000000;
+    const N0 = 0;
+    const E0 = 500000;
+    
+    const m = (x0 - E0) / a;
+    const n = (y - N0) / a;
+    
+    const B = n + (1 + e2 * Math.cos(N0) * Math.cos(N0)) * Math.sin(N0) * Math.cos(N0) * m * m / 2;
+    const L = L0 + Math.tan(N0) * m / Math.cos(N0);
+    
+    const lat = B * 180 / Math.PI;
+    const lon = L * 180 / Math.PI;
+    
+    return [lon, lat];
+}
+
+/**
+ * 转换超图几何格式为标准GeoJSON格式
+ * @param {Object} superMapGeom - 超图几何对象
+ * @returns {Object} - GeoJSON几何对象
+ */
+function convertSuperMapGeometry(superMapGeom) {
+    const geoJsonGeom = {
+        type: 'Polygon',
+        coordinates: []
+    };
+    
+    const points = superMapGeom.points || [];
+    const parts = superMapGeom.parts || [];
+    const type = superMapGeom.type || 'REGION';
+    
+    if (points.length === 0) {
+        console.warn('几何对象没有点数据:', type);
+        return null;
+    }
+    
+    let needTransform = false;
+    if (points.length > 0) {
+        needTransform = isProjectedCoordinate(points[0].x, points[0].y);
+    }
+    
+    const transformPoint = (p) => {
+        const [x, y] = needTransform ? gaussKrugerToWGS84(p.x, p.y) : [p.x, p.y];
+        return [x, y];
+    };
+    
+    switch (type.toUpperCase()) {
+        case 'POINT':
+            geoJsonGeom.type = 'Point';
+            geoJsonGeom.coordinates = transformPoint(points[0]);
+            break;
+            
+        case 'MULTIPOINT':
+            geoJsonGeom.type = 'MultiPoint';
+            geoJsonGeom.coordinates = points.map(transformPoint);
+            break;
+            
+        case 'LINE':
+        case 'CURVE':
+            geoJsonGeom.type = 'LineString';
+            geoJsonGeom.coordinates = points.map(transformPoint);
+            break;
+            
+        case 'REGION':
+            geoJsonGeom.type = 'Polygon';
+            const rings = [];
+            let startIndex = 0;
+            
+            if (parts.length > 0) {
+                for (let i = 0; i < parts.length; i++) {
+                    const endIndex = i < parts.length - 1 ? parts[i + 1] : points.length;
+                    const ring = points.slice(startIndex, endIndex).map(transformPoint);
+                    if (ring.length >= 3) {
+                        rings.push(ring);
+                    } else if (ring.length > 0) {
+                        console.warn('多边形环点数量不足:', ring.length);
+                    }
+                    startIndex = endIndex;
+                }
+            } else {
+                if (points.length >= 3) {
+                    rings.push(points.map(transformPoint));
+                } else {
+                    console.warn('多边形点数量不足:', points.length);
+                    return null;
+                }
+            }
+            geoJsonGeom.coordinates = rings;
+            break;
+            
+        case 'MULTIPOLYGON':
+            geoJsonGeom.type = 'MultiPolygon';
+            const multiRings = [];
+            startIndex = 0;
+            
+            if (parts.length > 0) {
+                for (let i = 0; i < parts.length; i++) {
+                    const endIndex = i < parts.length - 1 ? parts[i + 1] : points.length;
+                    const ring = points.slice(startIndex, endIndex).map(transformPoint);
+                    if (ring.length >= 3) {
+                        multiRings.push([ring]);
+                    }
+                    startIndex = endIndex;
+                }
+            } else {
+                if (points.length >= 3) {
+                    multiRings.push([points.map(transformPoint)]);
+                } else {
+                    console.warn('多多边形点数量不足:', points.length);
+                    return null;
+                }
+            }
+            geoJsonGeom.coordinates = multiRings;
+            break;
+            
+        case 'LINESTRING':
+            geoJsonGeom.type = 'LineString';
+            geoJsonGeom.coordinates = points.map(transformPoint);
+            break;
+            
+        case 'MULTILINESTRING':
+            geoJsonGeom.type = 'MultiLineString';
+            const lines = [];
+            startIndex = 0;
+            
+            if (parts.length > 0) {
+                for (let i = 0; i < parts.length; i++) {
+                    const endIndex = i < parts.length - 1 ? parts[i + 1] : points.length;
+                    const line = points.slice(startIndex, endIndex).map(transformPoint);
+                    if (line.length >= 2) {
+                        lines.push(line);
+                    }
+                    startIndex = endIndex;
+                }
+            } else {
+                if (points.length >= 2) {
+                    lines.push(points.map(transformPoint));
+                }
+            }
+            geoJsonGeom.coordinates = lines;
+            break;
+            
+        default:
+            console.warn('未知几何类型:', type, '尝试作为Polygon处理');
+            geoJsonGeom.type = 'Polygon';
+            if (points.length >= 3) {
+                geoJsonGeom.coordinates = [points.map(transformPoint)];
+            } else {
+                console.warn('未知类型点数量不足,无法转换');
+                return null;
+            }
+            break;
+    }
+    
+    return geoJsonGeom;
+}
+
+/**
+ * 转换坐标数组
+ * @param {number[]|number[][]} coordinates - 坐标数组
+ * @returns {number[]|number[][]} - 转换后的坐标数组
+ */
+function convertCoordinates(coordinates) {
+    if (!Array.isArray(coordinates)) {
+        return coordinates;
+    }
+    
+    if (typeof coordinates[0] === 'number' && typeof coordinates[1] === 'number') {
+        if (isProjectedCoordinate(coordinates[0], coordinates[1])) {
+            return gaussKrugerToWGS84(coordinates[0], coordinates[1]);
+        }
+        return coordinates;
+    }
+    
+    return coordinates.map(coord => convertCoordinates(coord));
+}
+
+/**
+ * 根据URI列表批量获取要素详情(优化版:无超时限制,自动重试)
+ * @param {string[]} uriList - 要素URI列表
+ * @returns {Promise<Object>} - GeoJSON FeatureCollection
+ */
+async function fetchFeaturesByUriList(uriList) {
+    console.log('开始根据URI列表获取要素详情,总数:', uriList.length);
+    
+    const maxConcurrent = 10;
+    const allFeatures = [];
+    
+    for (let i = 0; i < uriList.length; i += maxConcurrent) {
+        const batch = uriList.slice(i, i + maxConcurrent);
+        const batchPromises = batch.map(async (uri) => {
+            const featureUrl = uri.endsWith('.json') ? uri : `${uri}.json`;
+            console.log('获取要素:', featureUrl);
+            
+            let retries = 3;
+            let lastError = null;
+            
+            while (retries > 0) {
+                try {
+                    const response = await fetch(featureUrl);
+                    if (!response.ok) {
+                        throw new Error(`HTTP错误: ${response.status}`);
+                    }
+                    const text = await response.text();
+                    let featureData;
+                    try {
+                        featureData = JSON.parse(text);
+                    } catch (e) {
+                        console.error('要素JSON解析失败:', text.substring(0, 200));
+                        throw e;
+                    }
+                    
+                    let feature = featureData;
+                    
+                    if (featureData.feature) {
+                        feature = featureData.feature;
+                    } else if (featureData.result && featureData.result.feature) {
+                        feature = featureData.result.feature;
+                    }
+                    
+                    let geometry = null;
+                    let properties = {};
+                    
+                    if (feature.geometry) {
+                        geometry = feature.geometry;
+                    } else if (feature.Geometry) {
+                        geometry = feature.Geometry;
+                    }
+                    
+                    if (feature.fieldNames && feature.fieldValues) {
+                        feature.fieldNames.forEach((name, index) => {
+                            properties[name] = feature.fieldValues[index];
+                        });
+                    } else if (feature.attributes) {
+                        properties = feature.attributes;
+                    } else if (feature.Attributes) {
+                        properties = feature.Attributes;
+                    }
+                    
+                    if (!geometry) {
+                        console.error('要素没有有效几何:', featureUrl);
+                        return null;
+                    }
+                    
+                    const geoJsonGeom = convertSuperMapGeometry(geometry);
+                    
+                    return {
+                        type: 'Feature',
+                        geometry: geoJsonGeom,
+                        properties: properties
+                    };
+                } catch (error) {
+                    lastError = error;
+                    retries--;
+                    console.warn(`获取要素失败,剩余重试次数: ${retries}`, error.message);
+                    if (retries > 0) {
+                        await new Promise(resolve => setTimeout(resolve, 1000 * (4 - retries)));
+                    }
+                }
+            }
+            
+            console.error('获取要素最终失败:', featureUrl, lastError);
+            return null;
+        });
+        
+        const results = await Promise.all(batchPromises);
+        const validFeatures = results.filter(Boolean);
+        allFeatures.push(...validFeatures);
+        console.log(`已加载 ${allFeatures.length} / ${uriList.length} 个要素`);
+    }
+    
+    console.log(`URI列表加载完成,成功: ${allFeatures.length} / ${uriList.length}`);
+    
+    return {
+        type: 'FeatureCollection',
+        features: allFeatures
+    };
+}
+
+/**
+ * 从超图服务获取要素数据并转换为标准GeoJSON(优化版)
+ * @param {string} url - 超图服务URL
+ * @returns {Promise<Object>} - GeoJSON FeatureCollection
+ */
+async function fetchFeaturesFromSuperMap(url) {
+    const response = await fetch(url);
+    if (!response.ok) {
+        throw new Error(`HTTP错误: ${response.status}`);
+    }
+    
+    const text = await response.text();
+    try {
+        return JSON.parse(text);
+    } catch (e) {
+        console.error('JSON解析失败:', text.substring(0, 200));
+        throw new Error('服务返回的不是有效的JSON数据');
+    }
+}
+
+/**
+ * 加载单个分页的数据(处理childUriList情况)
+ * @param {string} url - 请求URL
+ * @returns {Promise<Array>} - 要素数组
+ */
+async function loadPageFeatures(url) {
+    const result = await fetchFeaturesFromSuperMap(url);
+    if (result && result.features) {
+        return result.features;
+    }
+    return [];
+}
+
+/**
+ * 递归获取所有子URI的要素数据(优化版:无超时限制)
+ * @param {string} baseUrl - 数据集基础URL
+ * @param {number} startIndex - 起始索引
+ * @param {number} pageSize - 每页大小
+ * @param {number} totalCount - 总要素数
+ * @param {Array} accumulatedFeatures - 已累积的要素
+ * @returns {Promise<Array>} - 所有要素数组
+ */
+async function fetchWithRetry(url, maxRetries = 3, delayMs = 1000) {
+    let retries = 0;
+    while (retries < maxRetries) {
+        try {
+            const response = await fetch(url);
+            if (!response.ok) {
+                throw new Error(`HTTP错误: ${response.status}`);
+            }
+            return response.json();
+        } catch (error) {
+            retries++;
+            console.warn(`请求失败,第 ${retries}/${maxRetries} 次尝试`, error.message);
+            if (retries < maxRetries) {
+                await new Promise(resolve => setTimeout(resolve, delayMs * retries));
+            } else {
+                throw error;
+            }
+        }
+    }
+}
+
+async function recursiveLoadFeatures(baseUrl, startIndex, pageSize, totalCount, accumulatedFeatures) {
+    const featuresUrl = `${baseUrl}/features.json?returnContent=true&startIndex=${startIndex}&maxFeatures=${pageSize}`;
+    
+    try {
+        const superMapData = await fetchWithRetry(featuresUrl);
+        
+        const actualCount = superMapData.features?.length || superMapData.recordset?.length || superMapData.childUriList?.length || 0;
+        
+        if (actualCount === 0 && startIndex === 0) {
+            console.warn('第一页就没有数据,可能是服务配置问题');
+            return accumulatedFeatures;
+        }
+        
+        if (superMapData.childUriList && Array.isArray(superMapData.childUriList)) {
+            console.log(`页面 ${startIndex}-${startIndex + pageSize} 返回了 ${superMapData.childUriList.length} 个URI`);
+            const result = await fetchFeaturesByUriList(superMapData.childUriList);
+            const uriFeatures = result.features || [];
+            console.log(`从URI列表获取了 ${uriFeatures.length} 个要素`);
+            accumulatedFeatures.push(...uriFeatures);
+        } else {
+            let currentPageFeatures = [];
+            
+            if (superMapData.features && Array.isArray(superMapData.features)) {
+                currentPageFeatures = superMapData.features;
+                console.log(`页面 ${startIndex}-${startIndex + pageSize} 直接返回了 ${currentPageFeatures.length} 个要素`);
+            } else if (superMapData.recordset && Array.isArray(superMapData.recordset)) {
+                currentPageFeatures = superMapData.recordset;
+                console.log(`页面 ${startIndex}-${startIndex + pageSize} 从recordset返回了 ${currentPageFeatures.length} 个要素`);
+            }
+            
+            currentPageFeatures = currentPageFeatures.map(feature => {
+                if (!feature) {
+                    console.warn('发现空要素');
+                    return null;
+                }
+                
+                let geometry = feature.geometry || feature.Geometry;
+                if (!geometry) {
+                    console.warn('要素没有几何信息:', JSON.stringify(feature).substring(0, 200));
+                    return null;
+                }
+                
+                const geoJsonGeom = convertSuperMapGeometry(geometry);
+                if (!geoJsonGeom) {
+                    console.warn('几何转换失败:', geometry.type);
+                    return null;
+                }
+                
+                let properties = {};
+                if (feature.fieldNames && feature.fieldValues) {
+                    feature.fieldNames.forEach((name, index) => {
+                        properties[name] = feature.fieldValues[index];
+                    });
+                } else if (feature.attributes) {
+                    properties = feature.attributes;
+                } else if (feature.Attributes) {
+                    properties = feature.Attributes;
+                } else if (feature.properties) {
+                    properties = feature.properties;
+                }
+                
+                return {
+                    type: 'Feature',
+                    geometry: geoJsonGeom,
+                    properties: properties
+                };
+            }).filter(Boolean);
+            
+            accumulatedFeatures.push(...currentPageFeatures);
+            console.log(`本页成功转换 ${currentPageFeatures.length} 个要素`);
+        }
+        
+        if (actualCount < pageSize) {
+            console.log(`最后一页,实际获取 ${actualCount} 个要素,总共获取 ${accumulatedFeatures.length} 个要素`);
+            return accumulatedFeatures;
+        }
+        
+    } catch (error) {
+        console.error(`加载页面 ${startIndex}-${startIndex + pageSize} 失败:`, error);
+        // 如果是最后一次重试失败,记录但继续尝试下一页
+    }
+    
+    return recursiveLoadFeatures(baseUrl, startIndex + pageSize, pageSize, totalCount, accumulatedFeatures);
+}
+
+/**
+ * 直接加载数据集的要素(优化版:支持分页加载,无超时限制,自动重试)
+ * @param {string} baseUrl - 数据集URL
+ * @param {string} name - 图层名称
+ * @param {Function} callback - 回调函数
+ */
+async function loadDatasetFeatures(baseUrl, name, callback) {
+    console.log('直接加载数据集要素:', baseUrl);
+    
+    try {
+        const countUrl = `${baseUrl}/features.json?returnContent=false&maxFeatures=1`;
+        console.log('获取要素总数:', countUrl);
+        
+        const countData = await fetch(countUrl).then(response => response.json());
+        const totalCount = countData.featureCount || countData.totalCount || 10000;
+        console.log(`要素总数: ${totalCount}`);
+        
+        // 使用递归方式加载所有要素,确保正确处理childUriList情况
+        const pageSize = 50;
+        const allFeatures = await recursiveLoadFeatures(baseUrl, 0, pageSize, totalCount, []);
+        
+        console.log(`最终获取到 ${allFeatures.length} 个要素`);
+        
+        const geoJsonData = {
+            type: 'FeatureCollection',
+            features: allFeatures
+        };
+        
+        const featureCount = geoJsonData.features ? geoJsonData.features.length : 0;
+        console.log(`最终加载了 ${featureCount} 个要素`);
+        
+        const geoJsonDataSource = await Cesium.GeoJsonDataSource.load(geoJsonData, {
+            stroke: Cesium.Color.fromCssColorString('#0055FF').withAlpha(1),
+            fill: Cesium.Color.fromCssColorString('#00FF00').withAlpha(0.4),
+            strokeWidth: 3,
+            clampToGround: true
+        });
+        
+        geoJsonDataSource.name = name;
+        viewer.dataSources.add(geoJsonDataSource);
+        processGeoJsonEntities(geoJsonDataSource, name);
+        viewer.flyTo(geoJsonDataSource, { duration: 2 });
+        actions.setChangeLayers();
+        
+        if (callback) callback(geoJsonDataSource);
+        
+        return geoJsonDataSource;
+    } catch (error) {
+        console.error('加载数据集要素失败:', error);
+        if (callback) callback(null);
+        throw error;
+    }
+}
+
+/**
+ * 加载超图REST数据服务(优化版:无超时丢失)
+ * @param {string} baseUrl - 服务基础URL
+ * @param {string} name - 图层名称
+ * @param {Function} callback - 回调函数
+ */
+async function loadSuperMapRestDataService(baseUrl, name, callback) {
+    console.log('开始加载超图REST数据服务:', baseUrl);
+    
+    try {
+        let datasourceUrl = baseUrl;
+        if (!datasourceUrl.endsWith('.json')) {
+            datasourceUrl = `${datasourceUrl}.json`;
+        }
+        console.log('数据源URL:', datasourceUrl);
+        
+        const response = await fetch(datasourceUrl);
+        const data = await response.json();
+        console.log('获取到数据源信息:', data);
+        
+        let datasetUrl = baseUrl;
+        
+        if (data.childUriList && Array.isArray(data.childUriList)) {
+            if (data.datasetInfo) {
+                console.log('URL直接指向数据集');
+                datasetUrl = baseUrl;
+            } else {
+                const datasetNames = data.childUriList.map(uri => {
+                    const parts = uri.split('/');
+                    return parts[parts.length - 1].replace('.json', '');
+                });
+                
+                console.log('数据集列表:', datasetNames);
+                
+                if (datasetNames.length === 0) {
+                    console.error('未找到任何数据集');
+                    if (callback) callback(null);
+                    return;
+                }
+                
+                const firstDatasetName = datasetNames[0];
+                datasetUrl = `${baseUrl}/${firstDatasetName}`;
+                console.log('加载第一个数据集:', datasetUrl);
+            }
+        }
+        
+        const geoJsonDataSource = await loadDatasetFeatures(datasetUrl, name, callback);
+        
+        return geoJsonDataSource;
+    } catch (error) {
+        console.error('加载超图REST数据服务失败:', error);
+        if (callback) callback(null);
+        throw error;
+    }
+}
+
+/**
+ * 处理GeoJSON实体(异步方式,避免阻塞主线程)
+ * @param {Object} dataSource - Cesium GeoJsonDataSource对象
+ * @param {string} name - 图层名称
+ * @param {Object} customStyle - 自定义样式配置
+ */
+function processGeoJsonEntities(dataSource, name, customStyle) {
+    try {
+        let entities = dataSource.entities.values;
+        let entitiesLength = entities.length;
+        
+        console.log(`开始处理 ${entitiesLength} 个实体`);
+        
+        // 获取样式配置
+        const style = customStyle || window.currentGeoJsonStyle || {
+            fillColor: '#00FF00',
+            fillOpacity: 0.4,
+            strokeColor: '#0055FF',
+            strokeWidth: 2
+        };
+        
+        const fillColor = Cesium.Color.fromCssColorString(style.fillColor).withAlpha(style.fillOpacity);
+        const strokeColor = Cesium.Color.fromCssColorString(style.strokeColor);
+        const strokeWidth = style.strokeWidth || 2;
+
+        // 使用异步批量处理
+        const batchSize = 100;
+        let currentIndex = 0;
+
+        const processBatch = () => {
+            if (currentIndex >= entitiesLength) {
+                console.log(`处理完成,共处理 ${entitiesLength} 个实体`);
+                return;
+            }
+
+            const endIndex = Math.min(currentIndex + batchSize, entitiesLength);
+            
+            for (let i = currentIndex; i < endIndex; i++) {
+                let entity = entities[i];
+                if (!entity) continue;
+                
+                entity.name = name || 'GeoJSON';
+
+                if (entity.polygon) {
+                    entity.polygon.fill = true;
+                    entity.polygon.outline = true;
+                    entity.polygon.clampToGround = true;
+                    entity.polygon.arcType = Cesium.ArcType.GEODESIC;
+                    entity.polygon.perPositionHeight = false;
+                    entity.polygon.classificationType = Cesium.ClassificationType.TERRAIN;
+                    entity.polygon.disableDepthTestDistance = Number.POSITIVE_INFINITY;
+                    entity.polygon.material = fillColor;
+                    entity.polygon.outlineColor = strokeColor;
+                    entity.polygon.outlineWidth = strokeWidth;
+                }
+
+                if (entity.polyline) {
+                    entity.polyline.clampToGround = true;
+                    entity.polyline.classificationType = Cesium.ClassificationType.TERRAIN;
+                    entity.polyline.material = strokeColor;
+                    entity.polyline.width = strokeWidth;
+                }
+
+                if (entity.point) {
+                    entity.point.clampToGround = true;
+                    entity.point.color = strokeColor;
+                    entity.point.pixelSize = style.pointSize || 10;
+                    entity.point.outlineColor = Cesium.Color.WHITE;
+                    entity.point.outlineWidth = 2;
+                    entity.point.disableDepthTestDistance = Number.POSITIVE_INFINITY;
+                    entity.isGeoJsonPoint = true;
+                }
+
+                if (entity.billboard) {
+                    entity.billboard.show = false;
+                    entity.point = {
+                        color: Cesium.Color.fromCssColorString('#FF0000'),
+                        pixelSize: 10,
+                        outlineColor: Cesium.Color.fromCssColorString('#FFFFFF'),
+                        outlineWidth: 2,
+                        clampToGround: true,
+                        disableDepthTestDistance: Number.POSITIVE_INFINITY
+                    };
+                    entity.isGeoJsonPoint = true;
+                }
+            }
+
+            currentIndex = endIndex;
+            requestAnimationFrame(processBatch);
+        };
+
+        processBatch();
+    } catch (error) {
+        console.error('处理GeoJSON实体失败:', error);
+    }
+}
+
+/**
+ * 获取图层映射
+ * @returns {Map} - 图层映射
+ */
+function getLayerMap() {
+    return layerMap;
+}
+
+/**
+ * 移除指定图层
+ * @param {string} layerName - 图层名称
+ */
+function removeLayer(layerName) {
+    const layer = layerMap.get(layerName);
+    if (layer) {
+        viewer.scene.primitives.remove(layer);
+        layerMap.delete(layerName);
+        console.log(`图层 ${layerName} 已移除`);
+    }
+}
+
+// 导出函数
+export default {
+    fetchFeaturesFromSuperMap,
+    loadDatasetFeatures,
+    loadSuperMapRestDataService,
+    convertSuperMapGeometry,
+    convertCoordinates,
+    gaussKrugerToWGS84,
+    isProjectedCoordinate,
+    getLayerMap,
+    removeLayer
+};
+
+export {
+    fetchFeaturesFromSuperMap,
+    loadDatasetFeatures,
+    loadSuperMapRestDataService,
+    convertSuperMapGeometry,
+    convertCoordinates,
+    gaussKrugerToWGS84,
+    isProjectedCoordinate,
+    processGeoJsonEntities,
+    getLayerMap,
+    removeLayer
+};

+ 5 - 2
RuoYi-Vue3/src/supermap-cesium-module/js/common/waterLayer.js

@@ -144,8 +144,11 @@ function loadWaterLayer(url, name, callback) {
                         const latitude = coord[1];
                         const position = Cesium.Cartesian3.fromDegrees(longitude, latitude, clampedWaterHeight);
                         
-                        // 验证每个位置是否有效
-                        if (!Cesium.Cartesian3.validate(position)) {
+                        // 验证每个位置是否有效(Cesium新版本中Cartesian3.validate已移除)
+                        if (!Cesium.defined(position) || 
+                            !Number.isFinite(position.x) || 
+                            !Number.isFinite(position.y) || 
+                            !Number.isFinite(position.z)) {
                             console.warn('创建无效位置,跳过此点:', coord);
                             continue;
                         }

+ 233 - 2
RuoYi-Vue3/src/supermap-cesium-module/views/layout/aside.vue

@@ -261,6 +261,15 @@
         :visible="view === 'Sm3dOverviewMap'"
         @close="view = ''"
       ></Sm3dOverviewMap>
+      
+      <!-- MVT属性弹窗组件 -->
+      <MvtAttributePopup
+        :visible="mvtPopupVisible"
+        :position="mvtPopupPosition"
+        :title="mvtPopupTitle"
+        :attributes="mvtPopupAttributes"
+        @close="mvtPopupVisible = false"
+      />
     </el-main>
   </el-container>
 </template>
@@ -285,6 +294,8 @@ import { getToken } from '@/utils/auth'
 import axios from 'axios'
 import { onMounted, watch, computed } from 'vue'
 import ModelEditor from "../../components/draw/draw-solid-figure/ModelEditor";  //模型编辑器
+import MvtAttributePopup from "../../components/MvtAttributePopup.vue";  //MVT属性弹窗组件
+import MvtClickHandler from "../../js/common/mvtClickHandler.js";  //MVT点击处理器
 
 export default {
   name: "layout-aside",
@@ -293,7 +304,8 @@ export default {
     CircleClose,
     TyphoonVisualization,
     CesiumThreeFusion,
-    ThreeModelViewer
+    ThreeModelViewer,
+    MvtAttributePopup
   },
   setup() {
     // 使用 computed 来获取 window.viewer
@@ -332,6 +344,14 @@ export default {
       typhoonDataUrl: '',  //台风数据URL
       loadedTyphoonId: null,  //已加载的台风ID
       showModelDialog: false,  //是否显示模型弹框
+      // MVT属性弹窗相关
+      mvtPopupVisible: false,  //弹窗是否可见
+      mvtPopupPosition: { x: 0, y: 0 },  //弹窗位置
+      mvtPopupTitle: '属性信息',  //弹窗标题
+      mvtPopupAttributes: {},  //属性数据
+      // MVT点击处理器
+      mvtClickHandler: null,  //MVT点击处理器实例
+      _hasMvtLayer: false,  //是否有MVT图层加载
     };
   },
 
@@ -1186,9 +1206,192 @@ export default {
         case "onlineTerrain":
           this.addOnlineTerrain(obj);
           break;
+        case "vectorData":
+          this.addVectorData(obj, shouldFlyTo);
+          break;
+      }
+    },
+    
+    // 添加矢量数据服务 - 根据类型选择加载方式
+    addVectorData(obj, shouldFlyTo = true) {
+      console.log('===== addVectorData 开始 =====');
+      console.log('服务名称:', obj.name);
+      console.log('服务URL:', obj.proxiedUrl);
+      console.log('服务类型:', obj.type);
+      console.log('服务状态:', obj.state);
+
+      if (!viewer) {
+        ElMessage.error('Cesium viewer 未初始化');
+        return;
+      }
+
+      if (obj.state === 0) {
+        // 根据数据类型选择加载方式
+        if (obj.type === 'MVT') {
+          // MVT 类型使用矢量瓦片加载
+          console.log('使用MVT方式加载');
+          layerManagement.addMvtLayer(obj.proxiedUrl, obj.name, (mvtLayer) => {
+            if (mvtLayer) {
+              obj.state = 1;
+              window.store.actions.setChangeLayers();
+              this.saveLoadedServices();
+              console.log(`${obj.name} MVT加载成功`);
+              ElMessage.success(`${obj.name} 加载成功`);
+              
+              // 添加MVT图层点击事件处理
+              this.addMvtClickHandler();
+            } else {
+              ElMessage.error(`加载 ${obj.name} 失败`);
+            }
+          });
+        } else if (obj.type === 'RESTFEATURE') {
+          // RESTFEATURE 类型使用GeoJsonDataSource加载REST数据服务
+          console.log('使用RESTFEATURE方式加载');
+          this.addRestFeatureLayer(obj, shouldFlyTo);
+        } else {
+          ElMessage.warning(`不支持的数据类型: ${obj.type}`);
+        }
+      } else {
+        console.log('服务已加载');
+        if (shouldFlyTo) {
+          this.flyToVectorData(obj.name);
+        }
       }
     },
     
+    // 使用GeoJsonDataSource加载REST要素服务(参考SHP转GeoJson的加载方式)
+    addRestFeatureLayer(obj, shouldFlyTo = true) {
+      console.log('===== 使用GeoJsonDataSource加载矢量数据 =====');
+      console.log('服务名称:', obj.name);
+      console.log('服务URL:', obj.proxiedUrl);
+      
+      // 获取样式配置
+      const style = obj.style || {
+        fillColor: '#00FF00',
+        fillOpacity: 0.4,
+        strokeColor: '#0055FF',
+        strokeWidth: 2
+      };
+      
+      // 参考SHP转GeoJson的加载方式,使用Cesium.GeoJsonDataSource.load
+      layerManagement.addGeoJsonLayerWithStyle(obj.proxiedUrl, obj.name, style, (geoJsonLayer) => {
+        if (geoJsonLayer) {
+          obj.state = 1;
+          window.store.actions.setChangeLayers();
+          this.saveLoadedServices();
+          
+          console.log(`${obj.name} 矢量数据加载成功`);
+          
+          if (shouldFlyTo) {
+            viewer.flyTo(geoJsonLayer, { duration: 2 });
+          }
+          
+          // 为矢量数据添加点击弹窗事件
+          this.addVectorDataClickHandler(obj.name);
+          
+          ElMessage.success(`${obj.name} 加载成功`);
+        } else {
+          ElMessage.error(`加载 ${obj.name} 失败`);
+        }
+      });
+    },
+    
+    
+    
+    // 定位到矢量数据
+    flyToVectorData(name) {
+      if (!viewer || !viewer.dataSources) return;
+      
+      const dataSources = viewer.dataSources._dataSources;
+      for (const ds of dataSources) {
+        if (ds.name === name || (ds.name && ds.name.includes(name))) {
+          viewer.flyTo(ds, { duration: 2 });
+          break;
+        }
+      }
+    },
+    
+    // 为矢量数据添加点击弹窗事件
+    addVectorDataClickHandler(layerName) {
+      if (!viewer) return;
+      
+      // 确保只添加一次点击事件
+      if (viewer._vectorDataClickHandler) {
+        viewer._vectorDataClickHandler.destroy();
+      }
+      
+      viewer._vectorDataClickHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
+      viewer._vectorDataClickHandler.setInputAction((movement) => {
+        const picked = viewer.scene.pick(movement.position);
+        
+        if (Cesium.defined(picked) && picked.id && picked.id._dataSource) {
+          const dataSource = picked.id._dataSource;
+          const entity = picked.id;
+          
+          // 检查是否是当前加载的矢量数据层
+          if (dataSource.name === layerName || (dataSource.name && dataSource.name.includes(layerName))) {
+            this.showVectorDataPopup(entity, movement.position);
+          }
+        } else if (Cesium.defined(picked) && picked.id && picked.id._properties) {
+          // 处理通过GeoJsonDataSource加载的实体
+          const entity = picked.id;
+          this.showVectorDataPopup(entity, movement.position);
+        }
+      }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
+    },
+    
+    // 显示矢量数据弹窗
+    showVectorDataPopup(entity, position) {
+      console.log('点击矢量数据:', entity);
+      
+      // 获取实体属性
+      let properties = {};
+      if (entity._properties) {
+        properties = entity._properties.getValue(Cesium.JulianDate.now());
+      } else if (entity.properties) {
+        properties = entity.properties.getValue(Cesium.JulianDate.now()) || entity.properties;
+      }
+      
+      // 构建弹窗内容
+      let content = '<div style="padding: 10px; max-width: 300px;">';
+      content += `<h3 style="margin: 0 0 10px 0; font-size: 14px; color: #333;">${entity.name || '属性信息'}</h3>`;
+      
+      if (properties && typeof properties === 'object') {
+        for (const key in properties) {
+          if (properties.hasOwnProperty(key)) {
+            let value = properties[key];
+            if (value === null || value === undefined) value = '';
+            if (typeof value === 'object') value = JSON.stringify(value);
+            content += `<div style="margin: 5px 0; font-size: 12px;">
+              <span style="color: #666;">${key}:</span>
+              <span style="margin-left: 5px; color: #333;">${value}</span>
+            </div>`;
+          }
+        }
+      } else {
+        content += '<div style="color: #999; font-size: 12px;">暂无属性信息</div>';
+      }
+      content += '</div>';
+      
+      // 使用tooltip显示弹窗(tooltip2.js的API是 showAt(message, top))
+      if (window.tooltip) {
+        const topPosition = position.y + 'px';
+        window.tooltip.showAt(content, topPosition);
+      }
+    },
+    
+    // 添加MVT图层点击事件处理
+    addMvtClickHandler() {
+      console.log('aside.vue addMvtClickHandler 被调用');
+      
+      if (!this.mvtClickHandler) {
+        this.mvtClickHandler = new MvtClickHandler(this);
+      }
+      
+      // 标记已加载 MVT 图层,全局点击处理器会进行检测
+      this._hasMvtLayer = true;
+    },
+    
     // 初始化viewer后的传入回调:添加底图和清除加载动画
     async removeLoad() {
       this.isRestoring = true; // 标记正在恢复,禁止保存
@@ -1277,6 +1480,13 @@ export default {
           obj.state = 0;
           this.saveLoadedServices();
           break;
+        case "vectorData":
+          layerManagement.layersDelete("GEOJSON", obj.name, () => {
+            obj.state = 0;
+            window.store.actions.setChangeLayers();
+            this.saveLoadedServices();
+          });
+          break;
       }
     },
 
@@ -1406,6 +1616,8 @@ export default {
               if (flyTo) {
                 viewer.flyTo(mvtlayer, { duration: 2 });
               }
+              // 添加MVT图层点击事件处理
+              this.addMvtClickHandler();
             });
             break;
           case 'SHP':
@@ -2016,11 +2228,30 @@ export default {
       
       const handler = new Cesium.ScreenSpaceEventHandler(window.viewer.canvas);
       
-      handler.setInputAction((movement) => {
+      handler.setInputAction(async (movement) => {
         const picked = window.viewer.scene.pick(movement.position);
         
         console.log('全局点击 - picked:', picked);
         
+        // ========== MVT 矢量瓦片点击检测 ==========
+        // MVT 无法被 scene.pick() 拾取,无论 picked 为何值都尝试查询
+        if (this._hasMvtLayer && this.mvtClickHandler) {
+          // 获取点击位置经纬度
+          const ray = window.viewer.camera.getPickRay(movement.position);
+          const cartesian = window.viewer.scene.globe.pick(ray, window.viewer.scene);
+          
+          if (cartesian) {
+            const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
+            const lon = Cesium.Math.toDegrees(cartographic.longitude);
+            const lat = Cesium.Math.toDegrees(cartographic.latitude);
+            
+            await this.mvtClickHandler.handleClick(lon, lat, {
+              x: movement.position.x,
+              y: movement.position.y
+            });
+          }
+        }
+        
         // 如果点击的是GeoJSON点,不处理(让model-transform-panel.vue处理)
         if (picked && picked.id && picked.id.isGeoJsonPoint) {
           console.log('点击了GeoJSON点,不处理');

+ 94 - 0
RuoYi-Vue3/tests/README.md

@@ -0,0 +1,94 @@
+# Playwright 自动化测试
+
+本项目使用 Playwright 进行前端自动化测试,已配置使用指定的 Chrome 浏览器。
+
+## 浏览器配置
+
+- **浏览器路径**: `D:\Software\chrome-win64\chrome.exe`
+- **浏览器类型**: Chrome
+
+## 安装依赖
+
+```bash
+npm install
+```
+
+## 运行测试
+
+### 基础测试(无头模式)
+```bash
+npm test
+```
+
+### UI 模式测试
+```bash
+npm run test:ui
+```
+
+### 有头模式测试(显示浏览器窗口)
+```bash
+npm run test:headed
+```
+
+### 调试模式
+```bash
+npm run test:debug
+```
+
+### 查看测试报告
+```bash
+npm run test:report
+```
+
+## 测试文件结构
+
+```
+tests/
+├── basic.spec.ts          # 基础功能测试
+└── ...                    # 其他测试文件
+```
+
+## 配置文件
+
+- [playwright.config.ts](playwright.config.ts) - Playwright 主配置文件
+- [package.json](package.json) - 包含测试脚本
+
+## 测试覆盖范围
+
+当前测试包含以下内容:
+
+1. **基础页面测试**
+   - 页面加载测试
+   - 登录页面测试
+   - 响应式布局测试
+
+2. **Cesium 地图测试**
+   - 地图容器加载测试
+   - 地图交互测试
+
+3. **性能测试**
+   - 页面加载性能测试
+   - 资源加载测试
+
+## 添加新测试
+
+在 `tests/` 目录下创建新的测试文件,使用 `.spec.ts` 扩展名:
+
+```typescript
+import { test, expect } from '@playwright/test';
+
+test.describe('测试组名称', () => {
+  test('测试用例名称', async ({ page }) => {
+    // 测试代码
+    await page.goto('/');
+    await expect(page).toHaveTitle(/标题/);
+  });
+});
+```
+
+## 注意事项
+
+1. 确保指定的 Chrome 浏览器路径正确
+2. 测试会自动启动开发服务器(`npm run dev`)
+3. 默认端口为 `http://localhost:5173`
+4. 测试失败时会自动截图和录制视频

+ 108 - 0
RuoYi-Vue3/tests/basic.spec.ts

@@ -0,0 +1,108 @@
+import { test, expect } from '@playwright/test';
+
+/**
+ * 基础页面测试
+ */
+test.describe('基础页面测试', () => {
+  test('页面加载测试', async ({ page }) => {
+    await page.goto('/');
+    
+    // 检查页面标题
+    await expect(page).toHaveTitle(/若依管理系统/);
+    
+    // 检查页面是否成功加载
+    await expect(page.locator('body')).toBeVisible();
+  });
+
+  test('登录页面测试', async ({ page }) => {
+    await page.goto('/login');
+    
+    // 检查登录表单元素是否存在
+    await expect(page.locator('input[type="text"], input[type="username"]')).toBeVisible();
+    await expect(page.locator('input[type="password"]')).toBeVisible();
+    await expect(page.locator('button[type="submit"]')).toBeVisible();
+  });
+
+  test('响应式布局测试', async ({ page }) => {
+    await page.goto('/');
+    
+    // 测试桌面视图
+    await page.setViewportSize({ width: 1920, height: 1080 });
+    await expect(page.locator('body')).toBeVisible();
+    
+    // 测试平板视图
+    await page.setViewportSize({ width: 768, height: 1024 });
+    await expect(page.locator('body')).toBeVisible();
+    
+    // 测试移动端视图
+    await page.setViewportSize({ width: 375, height: 667 });
+    await expect(page.locator('body')).toBeVisible();
+  });
+});
+
+/**
+ * Cesium 地图测试
+ */
+test.describe('Cesium 地图测试', () => {
+  test('地图容器加载测试', async ({ page }) => {
+    await page.goto('/');
+    
+    // 等待 Cesium 容器加载
+    await page.waitForSelector('#cesiumContainer, .cesium-viewer, [class*="cesium"]', { timeout: 10000 });
+    
+    // 检查地图容器是否可见
+    const cesiumContainer = page.locator('#cesiumContainer, .cesium-viewer, [class*="cesium"]').first();
+    await expect(cesiumContainer).toBeVisible();
+  });
+
+  test('地图交互测试', async ({ page }) => {
+    await page.goto('/');
+    
+    // 等待地图加载
+    await page.waitForSelector('#cesiumContainer, .cesium-viewer, [class*="cesium"]', { timeout: 10000 });
+    
+    // 模拟鼠标交互
+    const cesiumContainer = page.locator('#cesiumContainer, .cesium-viewer, [class*="cesium"]').first();
+    await cesiumContainer.click({ position: { x: 100, y: 100 } });
+    
+    // 检查是否有交互响应
+    await page.waitForTimeout(1000);
+  });
+});
+
+/**
+ * 性能测试
+ */
+test.describe('性能测试', () => {
+  test('页面加载性能', async ({ page }) => {
+    const startTime = Date.now();
+    
+    await page.goto('/');
+    await page.waitForLoadState('networkidle');
+    
+    const loadTime = Date.now() - startTime;
+    console.log(`页面加载时间: ${loadTime}ms`);
+    
+    // 页面应该在合理时间内加载完成
+    expect(loadTime).toBeLessThan(10000);
+  });
+
+  test('资源加载测试', async ({ page }) => {
+    const failedRequests: string[] = [];
+    
+    page.on('requestfailed', (request) => {
+      failedRequests.push(request.url());
+    });
+    
+    await page.goto('/');
+    await page.waitForLoadState('networkidle');
+    
+    // 检查是否有失败的请求
+    if (failedRequests.length > 0) {
+      console.warn('失败的请求:', failedRequests);
+    }
+    
+    // 允许少量请求失败,但不能太多
+    expect(failedRequests.length).toBeLessThan(5);
+  });
+});

+ 20 - 1
RuoYi-Vue3/vite.config.js

@@ -64,12 +64,31 @@ export default defineConfig(({ mode, command }) => {
       open: true,
       // 优化开发服务器性能
       hmr: {
-        overlay: true
+        overlay: true,
+        // 优化 HMR 性能
+        protocol: 'ws',
+        host: 'localhost',
+        port: 5173
+      },
+      // 优化文件监听
+      watch: {
+        // 使用轮询方式监听文件变化(Windows 下更稳定)
+        usePolling: true,
+        // 减少轮询间隔
+        interval: 1000,
+        // 忽略 node_modules
+        ignored: ['**/node_modules/**', '**/.git/**']
       },
       // 增加服务器响应超时时间
       timeout: 60000,
       // 增加最大请求体大小
       maxBodySize: '100mb',
+      // 优化 fs.cachedChecks
+      fs: {
+        strict: false,
+        // 允许访问项目根目录
+        allow: ['..']
+      },
       proxy: {
         // https://cn.vitejs.dev/config/#server-proxy
         '/dev-api': {

+ 1 - 8
pom.xml

@@ -20,7 +20,7 @@
         <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
         <spring-boot.version>2.5.15</spring-boot.version>
         <druid.version>1.2.27</druid.version>
-        <yauaa.version>7.32.0</yauaa.version>
+
         <swagger.version>3.0.0</swagger.version>
         <kaptcha.version>2.3.3</kaptcha.version>
         <pagehelper.boot.version>1.4.7</pagehelper.boot.version>
@@ -107,13 +107,6 @@
                 <version>${druid.version}</version>
             </dependency>
 
-            <!-- 解析客户端操作系统、浏览器等 -->
-            <dependency>
-                <groupId>nl.basjes.parse.useragent</groupId>
-                <artifactId>yauaa</artifactId>
-                <version>${yauaa.version}</version>
-            </dependency>
-
             <!-- pagehelper 分页插件 -->
             <dependency>
                 <groupId>com.github.pagehelper</groupId>

BIN
ruoyi-admin/lib/yauaa-7.32.0.jar


BIN
ruoyi-admin/target/classes/com/ruoyi/RuoYiApplication.class


BIN
ruoyi-admin/target/classes/com/ruoyi/RuoYiServletInitializer.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/cesium/CesiumMapConfigController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/common/CaptchaController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/common/CommonController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/monitor/CacheController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/monitor/ServerController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/monitor/SysLogininforController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/monitor/SysOperlogController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/monitor/SysUserOnlineController.class


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


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


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


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


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


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


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


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


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


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


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


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


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


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/tool/TestController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/tool/UserEntity.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/watershed/WatershedModelController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/watershed/WatershedServiceController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/core/config/SwaggerConfig.class


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
ruoyi-admin/uploads/models/geojson/12c9fe6888244d22957a13e7999cf982.geojson


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


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

@@ -0,0 +1 @@
+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


+ 0 - 6
ruoyi-common/pom.xml

@@ -101,12 +101,6 @@
             <artifactId>commons-pool2</artifactId>
         </dependency>
 
-        <!-- 解析客户端操作系统、浏览器等 -->
-        <dependency>
-            <groupId>nl.basjes.parse.useragent</groupId>
-            <artifactId>yauaa</artifactId>
-        </dependency>
-
         <!-- servlet包 -->
         <dependency>
             <groupId>javax.servlet</groupId>

+ 6 - 20
ruoyi-common/src/main/java/com/ruoyi/common/utils/http/UserAgentUtils.java

@@ -3,8 +3,6 @@ package com.ruoyi.common.utils.http;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import com.ruoyi.common.utils.StringUtils;
-import nl.basjes.parse.useragent.UserAgent;
-import nl.basjes.parse.useragent.UserAgentAnalyzer;
 
 /**
  * UserAgent解析工具类
@@ -36,26 +34,16 @@ public class UserAgentUtils
     private static final Pattern LINUX_PATTERN = Pattern.compile("Linux");
     private static final Pattern CHROMEOS_PATTERN = Pattern.compile("CrOS");
 
-    private static final UserAgentAnalyzer userAgentAnalyzer = UserAgentAnalyzer
-            .newBuilder().hideMatcherLoadStats()
-            .withCache(5000)
-            .showMinimalVersion()
-            .withField(UserAgent.AGENT_NAME_VERSION)
-            .withField(UserAgent.OPERATING_SYSTEM_NAME_VERSION)
-            .build();
-
     /**
      * 获取客户端浏览器
      */
     public static String getBrowser(String userAgent)
     {
-        UserAgent.ImmutableUserAgent iua = userAgentAnalyzer.parse(userAgent);
-        String agentNameVersion = iua.get(UserAgent.AGENT_NAME_VERSION).getValue();
-        if (StringUtils.isBlank(agentNameVersion) || agentNameVersion.contains("??"))
+        if (StringUtils.isBlank(userAgent))
         {
-            return formatBrowser(userAgent);
+            return UNKNOWN;
         }
-        return agentNameVersion;
+        return formatBrowser(userAgent);
     }
 
     /**
@@ -63,13 +51,11 @@ public class UserAgentUtils
      */
     public static String getOperatingSystem(String userAgent)
     {
-        UserAgent.ImmutableUserAgent iua = userAgentAnalyzer.parse(userAgent);
-        String operatingSystemNameVersion = iua.get(UserAgent.OPERATING_SYSTEM_NAME_VERSION).getValue();
-        if (StringUtils.isBlank(operatingSystemNameVersion) || operatingSystemNameVersion.contains("??"))
+        if (StringUtils.isBlank(userAgent))
         {
-            return formatOperatingSystem(userAgent);
+            return UNKNOWN;
         }
-        return operatingSystemNameVersion;
+        return formatOperatingSystem(userAgent);
     }
 
     /**

BIN
ruoyi-common/target/classes/com/ruoyi/common/annotation/Excel$ColumnType.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/annotation/Excel$Type.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/annotation/Excel.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/config/RuoYiConfig.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/config/serializer/SensitiveJsonSerializer.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/constant/CacheConstants.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/constant/Constants.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/constant/GenConstants.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/constant/HttpStatus.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/constant/ScheduleConstants$Status.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/constant/ScheduleConstants.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/constant/UserConstants.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/controller/BaseController$1.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/controller/BaseController.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/AjaxResult.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/BaseEntity.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/R.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/TreeEntity.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/TreeSelect.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/entity/SysDept.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/entity/SysDictData.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/entity/SysDictType.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/entity/SysMenu.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/entity/SysRole.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/entity/SysUser.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/model/LoginBody.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/model/LoginUser.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/model/RegisterBody.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/page/PageDomain.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/page/TableDataInfo.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/page/TableSupport.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/redis/RedisCache.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/text/CharsetKit.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/text/Convert.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/text/StrFormatter.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/enums/BusinessStatus.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/enums/BusinessType.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/enums/DataSourceType.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/enums/DesensitizedType.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/enums/HttpMethod.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/enums/LimitType.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/enums/OperatorType.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/enums/UserStatus.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/exception/DemoModeException.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/exception/GlobalException.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/exception/ServiceException.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/exception/UtilException.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/exception/base/BaseException.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/exception/file/FileException.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/exception/file/FileNameLengthLimitExceededException.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/exception/file/FileSizeLimitExceededException.class


Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff