Преглед на файлове

threejs + Cesium前端地图引入

yuhang Fu преди 11 месеца
родител
ревизия
a4f1131a50
променени са 100 файла, в които са добавени 53579 реда и са изтрити 0 реда
  1. 91 0
      zhsq_qk-ui/.history/package_20240617095310.json
  2. 95 0
      zhsq_qk-ui/.history/package_20240617103108.json
  3. 98 0
      zhsq_qk-ui/.history/package_20240617105053.json
  4. 347 0
      zhsq_qk-ui/.history/src/assets/images/qkq_index_20240617095312.css
  5. 347 0
      zhsq_qk-ui/.history/src/assets/images/qkq_index_20240618100451.css
  6. 347 0
      zhsq_qk-ui/.history/src/assets/images/qkq_index_20240618100544.css
  7. 298 0
      zhsq_qk-ui/.history/src/map3d/drawFunc_20240513112607.js
  8. 298 0
      zhsq_qk-ui/.history/src/map3d/drawFunc_20240617135642.js
  9. 299 0
      zhsq_qk-ui/.history/src/map3d/drawFunc_20240617135657.js
  10. 300 0
      zhsq_qk-ui/.history/src/map3d/drawFunc_20240617135747.js
  11. 299 0
      zhsq_qk-ui/.history/src/map3d/drawFunc_20240617135841.js
  12. 300 0
      zhsq_qk-ui/.history/src/map3d/drawFunc_20240617135917.js
  13. 301 0
      zhsq_qk-ui/.history/src/map3d/drawFunc_20240617140209.js
  14. 301 0
      zhsq_qk-ui/.history/src/map3d/drawFunc_20240617140355.js
  15. 299 0
      zhsq_qk-ui/.history/src/map3d/drawFunc_20240617140439.js
  16. 300 0
      zhsq_qk-ui/.history/src/map3d/drawFunc_20240617140822.js
  17. 301 0
      zhsq_qk-ui/.history/src/map3d/drawFunc_20240617140932.js
  18. 301 0
      zhsq_qk-ui/.history/src/map3d/drawFunc_20240617140949.js
  19. 301 0
      zhsq_qk-ui/.history/src/map3d/drawFunc_20240617141040.js
  20. 301 0
      zhsq_qk-ui/.history/src/map3d/drawFunc_20240617141339.js
  21. 301 0
      zhsq_qk-ui/.history/src/map3d/drawFunc_20240617141423.js
  22. 301 0
      zhsq_qk-ui/.history/src/map3d/drawFunc_20240617141509.js
  23. 300 0
      zhsq_qk-ui/.history/src/map3d/drawFunc_20240617141538.js
  24. 302 0
      zhsq_qk-ui/.history/src/map3d/drawFunc_20240617141633.js
  25. 303 0
      zhsq_qk-ui/.history/src/map3d/drawFunc_20240617141718.js
  26. 519 0
      zhsq_qk-ui/.history/src/map3d/index_20240531142157.vue
  27. 370 0
      zhsq_qk-ui/.history/src/map3d/index_20240617112103.vue
  28. 370 0
      zhsq_qk-ui/.history/src/map3d/index_20240617131501.vue
  29. 370 0
      zhsq_qk-ui/.history/src/map3d/index_20240617131719.vue
  30. 519 0
      zhsq_qk-ui/.history/src/map3d/index_20240617131842.vue
  31. 370 0
      zhsq_qk-ui/.history/src/map3d/index_20240617131918.vue
  32. 370 0
      zhsq_qk-ui/.history/src/map3d/index_20240617132010.vue
  33. 370 0
      zhsq_qk-ui/.history/src/map3d/index_20240617132132.vue
  34. 370 0
      zhsq_qk-ui/.history/src/map3d/index_20240617135525.vue
  35. 370 0
      zhsq_qk-ui/.history/src/map3d/index_20240617135539.vue
  36. 374 0
      zhsq_qk-ui/.history/src/map3d/index_20240617140035.vue
  37. 370 0
      zhsq_qk-ui/.history/src/map3d/index_20240617140107.vue
  38. 373 0
      zhsq_qk-ui/.history/src/map3d/index_20240617140123.vue
  39. 370 0
      zhsq_qk-ui/.history/src/map3d/index_20240617140143.vue
  40. 370 0
      zhsq_qk-ui/.history/src/map3d/index_20240617140501.vue
  41. 371 0
      zhsq_qk-ui/.history/src/map3d/index_20240617140729.vue
  42. 371 0
      zhsq_qk-ui/.history/src/map3d/index_20240617140811.vue
  43. 371 0
      zhsq_qk-ui/.history/src/map3d/index_20240617143556.vue
  44. 371 0
      zhsq_qk-ui/.history/src/map3d/index_20240617143723.vue
  45. 371 0
      zhsq_qk-ui/.history/src/map3d/index_20240617154131.vue
  46. 416 0
      zhsq_qk-ui/.history/src/map3d/index_20240618085825.vue
  47. 417 0
      zhsq_qk-ui/.history/src/map3d/index_20240618090013.vue
  48. 417 0
      zhsq_qk-ui/.history/src/map3d/index_20240618090035.vue
  49. 417 0
      zhsq_qk-ui/.history/src/map3d/index_20240618090050.vue
  50. 417 0
      zhsq_qk-ui/.history/src/map3d/index_20240618090113.vue
  51. 418 0
      zhsq_qk-ui/.history/src/map3d/index_20240618090207.vue
  52. 419 0
      zhsq_qk-ui/.history/src/map3d/index_20240618090244.vue
  53. 420 0
      zhsq_qk-ui/.history/src/map3d/index_20240618090501.vue
  54. 423 0
      zhsq_qk-ui/.history/src/map3d/index_20240618090525.vue
  55. 424 0
      zhsq_qk-ui/.history/src/map3d/index_20240618090620.vue
  56. 424 0
      zhsq_qk-ui/.history/src/map3d/index_20240618090638.vue
  57. 424 0
      zhsq_qk-ui/.history/src/map3d/index_20240618090759.vue
  58. 424 0
      zhsq_qk-ui/.history/src/map3d/index_20240618092026.vue
  59. 424 0
      zhsq_qk-ui/.history/src/map3d/index_20240618092302.vue
  60. 424 0
      zhsq_qk-ui/.history/src/map3d/index_20240618092400.vue
  61. 424 0
      zhsq_qk-ui/.history/src/map3d/index_20240618092422.vue
  62. 421 0
      zhsq_qk-ui/.history/src/map3d/index_20240618092548.vue
  63. 422 0
      zhsq_qk-ui/.history/src/map3d/index_20240618092627.vue
  64. 424 0
      zhsq_qk-ui/.history/src/map3d/index_20240618093631.vue
  65. 25 0
      zhsq_qk-ui/.history/src/map3d/mapConfig_20240520101625.js
  66. 25 0
      zhsq_qk-ui/.history/src/map3d/mapConfig_20240617133256.js
  67. 25 0
      zhsq_qk-ui/.history/src/map3d/mapConfig_20240617133302.js
  68. 25 0
      zhsq_qk-ui/.history/src/map3d/mapConfig_20240617133310.js
  69. 25 0
      zhsq_qk-ui/.history/src/map3d/mapConfig_20240618091441.js
  70. 25 0
      zhsq_qk-ui/.history/src/map3d/mapConfig_20240618091506.js
  71. 25 0
      zhsq_qk-ui/.history/src/map3d/mapConfig_20240618091525.js
  72. 25 0
      zhsq_qk-ui/.history/src/map3d/mapConfig_20240618091537.js
  73. 1098 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617095312.vue
  74. 1139 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617103313.vue
  75. 1139 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617103345.vue
  76. 1139 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617112314.vue
  77. 1139 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617112331.vue
  78. 1139 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617112404.vue
  79. 1139 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617112419.vue
  80. 1139 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617112626.vue
  81. 1139 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617112642.vue
  82. 1139 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617112836.vue
  83. 1140 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617112918.vue
  84. 1140 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617112940.vue
  85. 1141 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617130523.vue
  86. 1130 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617130616.vue
  87. 1130 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617130648.vue
  88. 1130 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617130657.vue
  89. 1130 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617130708.vue
  90. 1130 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617130836.vue
  91. 1130 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617130901.vue
  92. 1130 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617130918.vue
  93. 1137 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617130941.vue
  94. 1137 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617131329.vue
  95. 1137 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617131340.vue
  96. 1137 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617132203.vue
  97. 1137 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617132243.vue
  98. 1137 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617132254.vue
  99. 1137 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617132301.vue
  100. 0 0
      zhsq_qk-ui/.history/src/views/fusion/index_20240617132308.vue

+ 91 - 0
zhsq_qk-ui/.history/package_20240617095310.json

@@ -0,0 +1,91 @@
+{
+  "name": "ruoyi",
+  "version": "3.8.7",
+  "description": "城市运行一网统管平台",
+  "author": "若依",
+  "license": "MIT",
+  "scripts": {
+    "dev": "vue-cli-service serve",
+    "build:prod": "vue-cli-service build",
+    "build:stage": "vue-cli-service build --mode staging",
+    "preview": "node build/index.js --preview",
+    "lint": "eslint --ext .js,.vue src"
+  },
+  "husky": {
+    "hooks": {
+      "pre-commit": "lint-staged"
+    }
+  },
+  "lint-staged": {
+    "src/**/*.{js,vue}": [
+      "eslint --fix",
+      "git add"
+    ]
+  },
+  "keywords": [
+    "vue",
+    "admin",
+    "dashboard",
+    "element-ui",
+    "boilerplate",
+    "admin-template",
+    "management-system"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "https://gitee.com/y_project/RuoYi-Vue.git"
+  },
+  "dependencies": {
+    "@riophae/vue-treeselect": "0.4.0",
+    "axios": "0.24.0",
+    "clipboard": "2.0.8",
+    "core-js": "3.25.3",
+    "echarts": "5.4.0",
+    "jquery": "2.2.1",
+    "element-ui": "2.15.14",
+    "file-saver": "2.0.5",
+    "fuse.js": "6.4.3",
+    "highlight.js": "9.18.5",
+    "js-beautify": "1.13.0",
+    "js-cookie": "3.0.1",
+    "jsencrypt": "3.0.0-rc.1",
+    "nprogress": "0.2.0",
+    "quill": "1.3.7",
+    "screenfull": "5.0.2",
+    "sortablejs": "1.10.2",
+    "vue": "2.6.12",
+    "vue-count-to": "1.0.13",
+    "vue-cropper": "0.5.5",
+    "vue-meta": "2.4.0",
+    "vue-router": "3.4.9",
+    "vuedraggable": "2.24.3",
+    "vuex": "3.6.0"
+  },
+  "devDependencies": {
+    "@vue/cli-plugin-babel": "4.4.6",
+    "@vue/cli-plugin-eslint": "4.4.6",
+    "@vue/cli-service": "4.4.6",
+    "babel-eslint": "10.1.0",
+    "babel-plugin-dynamic-import-node": "2.3.3",
+    "chalk": "4.1.0",
+    "compression-webpack-plugin": "6.1.2",
+    "connect": "3.6.6",
+    "eslint": "7.15.0",
+    "eslint-plugin-vue": "7.2.0",
+    "lint-staged": "10.5.3",
+    "runjs": "4.4.2",
+    "sass": "1.32.13",
+    "sass-loader": "10.1.1",
+    "script-ext-html-webpack-plugin": "2.1.5",
+    "svg-sprite-loader": "5.1.1",
+    "vue-template-compiler": "2.6.12"
+  },
+  "engines": {
+    "node": ">=8.9",
+    "npm": ">= 3.0.0"
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions"
+  ]
+}

+ 95 - 0
zhsq_qk-ui/.history/package_20240617103108.json

@@ -0,0 +1,95 @@
+{
+  "name": "ruoyi",
+  "version": "3.8.7",
+  "description": "城市运行一网统管平台",
+  "author": "若依",
+  "license": "MIT",
+  "scripts": {
+    "dev": "vue-cli-service serve",
+    "build:prod": "vue-cli-service build",
+    "build:stage": "vue-cli-service build --mode staging",
+    "preview": "node build/index.js --preview",
+    "lint": "eslint --ext .js,.vue src"
+  },
+  "husky": {
+    "hooks": {
+      "pre-commit": "lint-staged"
+    }
+  },
+  "lint-staged": {
+    "src/**/*.{js,vue}": [
+      "eslint --fix",
+      "git add"
+    ]
+  },
+  "keywords": [
+    "vue",
+    "admin",
+    "dashboard",
+    "element-ui",
+    "boilerplate",
+    "admin-template",
+    "management-system"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "https://gitee.com/y_project/RuoYi-Vue.git"
+  },
+  "dependencies": {
+    "@riophae/vue-treeselect": "0.4.0",
+    "axios": "0.24.0",
+    "clipboard": "2.0.8",
+    "d3": "^7.8.2",
+    "three": "^0.150.1",
+    "cesium": "^1.111.0",
+    "gsap": "^3.11.5",
+    "core-js": "3.25.3",
+    "echarts": "5.4.0",
+    "jquery": "2.2.1",
+    "element-ui": "2.15.14",
+    "file-saver": "2.0.5",
+    "fuse.js": "6.4.3",
+    "highlight.js": "9.18.5",
+    "js-beautify": "1.13.0",
+    "js-cookie": "3.0.1",
+    "jsencrypt": "3.0.0-rc.1",
+    "nprogress": "0.2.0",
+    "quill": "1.3.7",
+    "screenfull": "5.0.2",
+    "sortablejs": "1.10.2",
+    "vue": "2.6.12",
+    "vue-count-to": "1.0.13",
+    "vue-cropper": "0.5.5",
+    "vue-meta": "2.4.0",
+    "vue-router": "3.4.9",
+    "vuedraggable": "2.24.3",
+    "vuex": "3.6.0"
+  },
+  "devDependencies": {
+    "@vue/cli-plugin-babel": "4.4.6",
+    "@vue/cli-plugin-eslint": "4.4.6",
+    "@vue/cli-service": "4.4.6",
+    "babel-eslint": "10.1.0",
+    "babel-plugin-dynamic-import-node": "2.3.3",
+    "chalk": "4.1.0",
+    "compression-webpack-plugin": "6.1.2",
+    "connect": "3.6.6",
+    "eslint": "7.15.0",
+    "eslint-plugin-vue": "7.2.0",
+    "lint-staged": "10.5.3",
+    "runjs": "4.4.2",
+    "sass": "1.32.13",
+    "sass-loader": "10.1.1",
+    "script-ext-html-webpack-plugin": "2.1.5",
+    "svg-sprite-loader": "5.1.1",
+    "vue-template-compiler": "2.6.12"
+  },
+  "engines": {
+    "node": ">=8.9",
+    "npm": ">= 3.0.0"
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions"
+  ]
+}

+ 98 - 0
zhsq_qk-ui/.history/package_20240617105053.json

@@ -0,0 +1,98 @@
+{
+  "name": "ruoyi",
+  "version": "3.8.7",
+  "description": "城市运行一网统管平台",
+  "author": "若依",
+  "license": "MIT",
+  "scripts": {
+    "dev": "vue-cli-service serve",
+    "build:prod": "vue-cli-service build",
+    "build:stage": "vue-cli-service build --mode staging",
+    "preview": "node build/index.js --preview",
+    "lint": "eslint --ext .js,.vue src"
+  },
+  "husky": {
+    "hooks": {
+      "pre-commit": "lint-staged"
+    }
+  },
+  "lint-staged": {
+    "src/**/*.{js,vue}": [
+      "eslint --fix",
+      "git add"
+    ]
+  },
+  "keywords": [
+    "vue",
+    "admin",
+    "dashboard",
+    "element-ui",
+    "boilerplate",
+    "admin-template",
+    "management-system"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "https://gitee.com/y_project/RuoYi-Vue.git"
+  },
+  "dependencies": {
+    "@riophae/vue-treeselect": "0.4.0",
+    "axios": "0.24.0",
+    "cesium": "^1.111.0",
+    "clipboard": "2.0.8",
+    "core-js": "3.25.3",
+    "d3": "^7.8.2",
+    "echarts": "5.4.0",
+    "element-ui": "2.15.14",
+    "file-saver": "2.0.5",
+    "fuse.js": "6.4.3",
+    "gsap": "^3.11.5",
+    "sass": "^1.77.1",
+    "sass-loader": "^14.2.1",
+    "highlight.js": "9.18.5",
+    "jquery": "2.2.1",
+    "js-beautify": "1.13.0",
+    "js-cookie": "3.0.1",
+    "jsencrypt": "3.0.0-rc.1",
+    "nprogress": "0.2.0",
+    "quill": "1.3.7",
+    "screenfull": "5.0.2",
+    "sortablejs": "1.10.2",
+    "three": "^0.150.1",
+    "vue": "2.6.12",
+    "vue-count-to": "1.0.13",
+    "vue-cropper": "0.5.5",
+    "vue-meta": "2.4.0",
+    "vue-router": "3.4.9",
+    "vuedraggable": "2.24.3",
+    "vuex": "3.6.0",
+    "web-vitals": "^2.1.4"
+  },
+  "devDependencies": {
+    "@vue/cli-plugin-babel": "4.4.6",
+    "@vue/cli-plugin-eslint": "4.4.6",
+    "@vue/cli-service": "4.4.6",
+    "babel-eslint": "10.1.0",
+    "babel-plugin-dynamic-import-node": "2.3.3",
+    "chalk": "4.1.0",
+    "compression-webpack-plugin": "6.1.2",
+    "connect": "3.6.6",
+    "eslint": "7.15.0",
+    "eslint-plugin-vue": "7.2.0",
+    "lint-staged": "10.5.3",
+    "runjs": "4.4.2",
+    "sass": "1.32.13",
+    "sass-loader": "10.1.1",
+    "script-ext-html-webpack-plugin": "2.1.5",
+    "svg-sprite-loader": "5.1.1",
+    "vue-template-compiler": "2.6.12"
+  },
+  "engines": {
+    "node": ">=8.9",
+    "npm": ">= 3.0.0"
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions"
+  ]
+}

+ 347 - 0
zhsq_qk-ui/.history/src/assets/images/qkq_index_20240617095312.css

@@ -0,0 +1,347 @@
+@charset "utf-8";
+@font-face {
+  font-family: 'MyWebFont2'; /* 给字体命名 */
+  src:url('../fonts/FZHZGBJW.TTF')  format('truetype'), /* Safari, Android, iOS */
+}
+
+/*土地资源*/
+.qkq_tdzy{
+	margin-top: 20px;
+	margin-bottom: 30px;
+}
+.qkq_tdzy_con{
+	display: flex;
+	flex-direction: row;
+	justify-content:space-between;
+	width: 96%;
+	margin: 0px auto;
+	/* height: 170px; */
+}
+.qkq_tdzy .dt_txt{
+	text-align: center;
+	padding-top: 32px;
+}
+.qkq_tdzy .dt_txt h2{
+	font-family: MyWebFont2;
+	font-size: 20px;
+	font-weight: 900;
+	color: #6affff;
+	margin-bottom: 3px;
+}
+.qkq_tdzy .dt_txt h3{
+	font-size: 14px;
+	color: #6affff;
+	margin-bottom: 3px;
+}
+.qkq_tdzy .dt_txt h4{
+	font-size: 14px;
+	color: #fff;
+	margin-bottom: 8px;
+	text-shadow: 0px 0px 5px #6affff;
+}
+.qkq_tdzy .dt_txt h5{
+	font-size: 10px;
+	color: #fff;
+	text-shadow: 0px 0px 5px #6affff;
+}
+.qkq_tdzy .dt_con{
+	background: url(qkq_tdzybg.png) no-repeat center;
+	width: 110px;
+	height: 130px;
+	text-align: center;
+	background-size: 100% 100%;
+	position: relative;
+}
+.qkq_tdzy .dt_con h2{
+	color: #fcce17;
+	font-size: 18px;
+	font-weight: 900;
+	padding-top: 30px;
+}
+.qkq_tdzy .dt_con h3{
+	color: #fcce17;
+	font-size: 14px;
+	margin-bottom: 3px;
+}
+.qkq_tdzy .dt_con h4{
+	color: #fff;
+	font-size: 12px;
+}
+.qkq_tdzy .dt_con .dt_con_b{
+	background: url(qkq_tdzybg2.png) no-repeat center;
+	background-size: 100% 100%;
+	width: 42px;
+	height: 42px;
+	position: absolute;
+	left: 31%;
+	bottom:-13px;
+	text-align: center;
+}
+.qkq_tdzy .dt_con .dt_con_b h4{
+	font-size: 9px;
+    margin-top: 10px;
+	color: #ffe265;
+}
+.qkq_tdzy .dt_con .dt_con_b p{
+	color: #6bd8fc;
+	font-size: 9px;
+}
+
+/*营商环境*/
+.qkq_yshj_con{
+	display: flex;
+	flex-direction: row;
+	height: 170px;
+}
+.qkq_yshj_con .qkq_yshj_list p{
+	font-size: 12px;
+	color: #fff;
+	line-height: 22px;
+	position: relative;
+	padding-left: 13px;
+	text-shadow: 0px 0px 5px #6affff;
+}
+.qkq_yshj_con .qkq_yshj_list p:after{
+	content: " ";
+    display: block;
+    width: 6px;
+	height: 6px;
+	border-radius: 5px;
+	background: #0260b0;
+    position: absolute;
+    /* pointer-events: all; */
+    top: 9px;
+    left: 0px;
+}
+.qkq_yshj_con .qkq_yshj_list span{
+	padding-left: 10px;
+}
+#yshj{
+	width: 225px;
+	height: 170px;
+}
+
+
+/*网格管理*/
+#wggl{
+	width:345px;
+	height: 200px;
+}
+
+/*党建引领*/
+.qkq_djyl{
+	margin-bottom: 15px;
+}
+.qkq_djyl_con{
+	display: flex;
+	flex-direction: row;
+	justify-content: space-around;
+}
+.qkq_djyl_con img{
+	height: 100px;
+    margin-top: 20px;
+}
+.qkq_djyl_con .qkq_list{
+	width: 175px;
+}
+.qkq_djyl_con p{
+	background: url(qkq_djyl_x.png) no-repeat bottom;
+	height: 32px;
+	line-height: 32px;
+	font-size: 14px;
+	color: #fff;
+	display: flex;
+	flex-direction: row;
+	justify-content: space-between;
+}
+.qkq_djyl_con p span{
+	padding-left: 14px;
+	background: url(qkq_djyl_icon.png) no-repeat left center;
+}
+.qkq_djyl_con p i{
+	text-align: right;
+	display: inline-block;
+}
+.blue{
+	color: #01b2f4!important;
+}
+.green{
+	color: #01f49b!important;
+}
+.org{
+	color: #f4b00d!important;
+}
+
+/*经济运行*/
+#jjyx{
+	width: 345px;
+	height: 180px;
+	margin-bottom: 15px;
+}
+/*事件信息*/
+.qkq_jjyx_list{
+
+}
+.scro_list{
+	height: 190px;
+	overflow-y: scroll;
+}
+.scro_list::-webkit-scrollbar{
+	width: 0px;
+	height: 0px;
+}
+.qkq_jjyx_list dt{
+	background: rgba(3,119,227,0.1);
+	padding: 0px 10px;
+}
+.qkq_jjyx_list dd{
+	padding: 0px 10px;
+}
+.qkq_jjyx_list dt span,.qkq_jjyx_list dd span{
+	font-size: 14px;
+	color: #fff;
+	display: inline-block;
+	height: 30px;
+	line-height: 30px;
+	text-shadow: 0px 0px 5px #6affff;
+}
+.qkq_jjyx_list dt span:nth-child(1),.qkq_jjyx_list dd span:nth-child(1){
+	width: 41%;
+}
+.qkq_jjyx_list dt span:nth-child(2),.qkq_jjyx_list dd span:nth-child(2){
+	width: 26%;
+}
+.qkq_jjyx_list dt span:nth-child(3),.qkq_jjyx_list dd span:nth-child(3){
+	width: 13%;
+}
+.qkq_jjyx_list dt span:nth-child(4),.qkq_jjyx_list dd span:nth-child(4){
+	width: 15%;
+}
+.qkq_jjyx_list dd span.zt{
+	background: url(qkq_listbg.png) no-repeat center;
+	background-size: 100% auto;
+	text-align: center;
+	font-size: 10px;
+	color: #e1e4ec;
+}
+
+
+/*中间内容*/
+.qkq_mapcon{
+	position: fixed;
+	bottom:20%;
+	left: 50%;
+	transform: translateX(-50%);
+	z-index: 2;
+}
+
+.qkq_mapcon .qkq_map{
+	width: 100%;
+    height: 507px;
+    position: absolute;
+    top: 0px;
+}
+.qkq_mapcon .qkq_map a{
+	display: block;
+	position: absolute;
+	background: url(qkq_iconcon.png) no-repeat center;
+	width: 143px;
+	height: 167px;
+	transition: transform 0.7s ease-in-out; /* 设置过渡效果 */
+  transform: translateY(0); /* 初始状态不移动 */
+}
+.qkq_mapcon .qkq_map a:hover{
+	background: url(qkq_iconcon_on.png) no-repeat center;
+	transform: translateY(-10px); /* 鼠标划过时向上移动20像素 */
+}
+.qkq_mapcon .qkq_map a i{
+	display: block;
+    text-align: center;
+    padding-top: 27px;
+}
+.qkq_mapcon .qkq_map a span{
+	display: block;
+    text-align: center;
+    padding-top: 50px;
+	color: #fff;
+	font-size: 14px;
+}
+.qkq_mapcon .qkq_map a:nth-child(1){
+	top:158px;
+	left: 90px;
+}
+/*
+.qkq_mapcon .qkq_map a:nth-child(1):hover{
+	top:148px;
+}
+*/
+.qkq_mapcon .qkq_map a:nth-child(2){
+	top: 200px;
+	left: -72px;
+}
+/*
+.qkq_mapcon .qkq_map a:nth-child(2):hover{
+	top: 190px;
+}
+*/
+.qkq_mapcon .qkq_map a:nth-child(3){
+	top: 352px;
+	left: -46px;
+}
+/*
+.qkq_mapcon .qkq_map a:nth-child(3):hover{
+	top: 342px;
+}
+*/
+.qkq_mapcon .qkq_map a:nth-child(4){
+	top: 415px;
+	left: 140px;
+}
+/*
+.qkq_mapcon .qkq_map a:nth-child(4):hover{
+	top: 405px;
+}
+*/
+.qkq_mapcon .qkq_map a:nth-child(5){
+	top: 393px;
+	right: 277px;
+}
+/*
+.qkq_mapcon .qkq_map a:nth-child(5):hover{
+	top: 383px;
+}
+*/
+.qkq_mapcon .qkq_map a:nth-child(6){
+	top: 342px;
+	right: 91px;
+}
+/*
+.qkq_mapcon .qkq_map a:nth-child(6):hover{
+	top: 332px;
+}
+*/
+.qkq_mapcon .qkq_map a:nth-child(7){
+	top: 241px;
+	right: -57px;
+}
+/*
+.qkq_mapcon .qkq_map a:nth-child(7):hover{
+	top: 231px;
+}
+*/
+.qkq_mapcon .qkq_map a:nth-child(8){
+	top: 161px;
+	right: 90px;
+}
+/*
+.qkq_mapcon .qkq_map a:nth-child(8):hover{
+	top: 151px;
+}
+*/
+
+
+
+
+
+
+

+ 347 - 0
zhsq_qk-ui/.history/src/assets/images/qkq_index_20240618100451.css

@@ -0,0 +1,347 @@
+@charset "utf-8";
+@font-face {
+  font-family: 'MyWebFont2'; /* 给字体命名 */
+  src:url('../fonts/FZHZGBJW.TTF')  format('truetype'), /* Safari, Android, iOS */
+}
+
+/*土地资源*/
+.qkq_tdzy{
+	margin-top: 20px;
+	margin-bottom: 30px;
+}
+.qkq_tdzy_con{
+	display: flex;
+	flex-direction: row;
+	justify-content:space-between;
+	width: 96%;
+	margin: 0px auto;
+	/* height: 170px; */
+}
+.qkq_tdzy .dt_txt{
+	text-align: center;
+	padding-top: 32px;
+}
+.qkq_tdzy .dt_txt h2{
+	font-family: MyWebFont2;
+	font-size: 20px;
+	font-weight: 900;
+	color: #6affff;
+	margin-bottom: 3px;
+}
+.qkq_tdzy .dt_txt h3{
+	font-size: 14px;
+	color: #6affff;
+	margin-bottom: 3px;
+}
+.qkq_tdzy .dt_txt h4{
+	font-size: 14px;
+	color: #fff;
+	margin-bottom: 8px;
+	text-shadow: 0px 0px 5px #6affff;
+}
+.qkq_tdzy .dt_txt h5{
+	font-size: 10px;
+	color: #fff;
+	text-shadow: 0px 0px 5px #6affff;
+}
+.qkq_tdzy .dt_con{
+	background: url(qkq_tdzybg.png) no-repeat center;
+	width: 110px;
+	height: 130px;
+	text-align: center;
+	background-size: 100% 100%;
+	position: relative;
+}
+.qkq_tdzy .dt_con h2{
+	color: #fcce17;
+	font-size: 18px;
+	font-weight: 900;
+	padding-top: 30px;
+}
+.qkq_tdzy .dt_con h3{
+	color: #fcce17;
+	font-size: 14px;
+	margin-bottom: 3px;
+}
+.qkq_tdzy .dt_con h4{
+	color: #fff;
+	font-size: 12px;
+}
+.qkq_tdzy .dt_con .dt_con_b{
+	background: url(qkq_tdzybg2.png) no-repeat center;
+	background-size: 100% 100%;
+	width: 42px;
+	height: 42px;
+	position: absolute;
+	left: 31%;
+	bottom:-13px;
+	text-align: center;
+}
+.qkq_tdzy .dt_con .dt_con_b h4{
+	font-size: 9px;
+    margin-top: 10px;
+	color: #ffe265;
+}
+.qkq_tdzy .dt_con .dt_con_b p{
+	color: #6bd8fc;
+	font-size: 9px;
+}
+
+/*营商环境*/
+.qkq_yshj_con{
+	display: flex;
+	flex-direction: row;
+	height: 170px;
+}
+.qkq_yshj_con .qkq_yshj_list p{
+	font-size: 12px;
+	color: #fff;
+	line-height: 22px;
+	position: relative;
+	padding-left: 13px;
+	text-shadow: 0px 0px 5px #6affff;
+}
+.qkq_yshj_con .qkq_yshj_list p:after{
+	content: " ";
+    display: block;
+    width: 6px;
+	height: 6px;
+	border-radius: 5px;
+	background: #0260b0;
+    position: absolute;
+    /* pointer-events: all; */
+    top: 9px;
+    left: 0px;
+}
+.qkq_yshj_con .qkq_yshj_list span{
+	padding-left: 10px;
+}
+#yshj{
+	width: 225px;
+	height: 170px;
+}
+
+
+/*网格管理*/
+#wggl{
+	width:345px;
+	height: 200px;
+}
+
+/*党建引领*/
+.qkq_djyl{
+	margin-bottom: 15px;
+}
+.qkq_djyl_con{
+	display: flex;
+	flex-direction: row;
+	justify-content: space-around;
+}
+.qkq_djyl_con img{
+	height: 100px;
+    margin-top: 20px;
+}
+.qkq_djyl_con .qkq_list{
+	width: 175px;
+}
+.qkq_djyl_con p{
+	background: url(qkq_djyl_x.png) no-repeat bottom;
+	height: 32px;
+	line-height: 32px;
+	font-size: 14px;
+	color: #fff;
+	display: flex;
+	flex-direction: row;
+	justify-content: space-between;
+}
+.qkq_djyl_con p span{
+	padding-left: 14px;
+	background: url(qkq_djyl_icon.png) no-repeat left center;
+}
+.qkq_djyl_con p i{
+	text-align: right;
+	display: inline-block;
+}
+.blue{
+	color: #01b2f4!important;
+}
+.green{
+	color: #01f49b!important;
+}
+.org{
+	color: #f4b00d!important;
+}
+
+/*经济运行*/
+#jjyx{
+	width: 345px;
+	height: 180px;
+	margin-bottom: 15px;
+}
+/*事件信息*/
+.qkq_jjyx_list{
+
+}
+.scro_list{
+	height: 190px;
+	overflow-y: scroll;
+}
+.scro_list::-webkit-scrollbar{
+	width: 0px;
+	height: 0px;
+}
+.qkq_jjyx_list dt{
+	background: rgba(3,119,227,0.1);
+	padding: 0px 10px;
+}
+.qkq_jjyx_list dd{
+	padding: 0px 10px;
+}
+.qkq_jjyx_list dt span,.qkq_jjyx_list dd span{
+	font-size: 14px;
+	color: #fff;
+	display: inline-block;
+	height: 30px;
+	line-height: 30px;
+	text-shadow: 0px 0px 5px #6affff;
+}
+.qkq_jjyx_list dt span:nth-child(1),.qkq_jjyx_list dd span:nth-child(1){
+	width: 41%;
+}
+.qkq_jjyx_list dt span:nth-child(2),.qkq_jjyx_list dd span:nth-child(2){
+	width: 26%;
+}
+.qkq_jjyx_list dt span:nth-child(3),.qkq_jjyx_list dd span:nth-child(3){
+	width: 13%;
+}
+.qkq_jjyx_list dt span:nth-child(4),.qkq_jjyx_list dd span:nth-child(4){
+	width: 15%;
+}
+.qkq_jjyx_list dd span.zt{
+	background: url(qkq_listbg.png) no-repeat center;
+	background-size: 100% auto;
+	text-align: center;
+	font-size: 10px;
+	color: #e1e4ec;
+}
+
+
+/*中间内容*/
+.qkq_mapcon{
+	position: fixed;
+	bottom:20%;
+	left: 50%;
+	transform: translateX(-50%);
+	z-index: 2;
+}
+
+.qkq_mapcon .qkq_map{
+	width: 100%;
+    height: 507px;
+    position: absolute;
+    top: 0px;
+}
+.qkq_mapcon .qkq_map a{
+	display: block;
+	position: absolute;
+	background: url(qkq_iconcon.png) no-repeat center;
+	width: 143px;
+	height: 167px;
+	transition: transform 0.7s ease-in-out; /* 设置过渡效果 */
+  transform: translateY(0); /* 初始状态不移动 */
+}
+.qkq_mapcon .qkq_map a:hover{
+	background: url(qkq_iconcon_on.png) no-repeat center;
+	transform: translateY(-10px); /* 鼠标划过时向上移动20像素 */
+}
+.qkq_mapcon .qkq_map a i{
+	display: block;
+    text-align: center;
+    padding-top: 27px;
+}
+.qkq_mapcon .qkq_map a span{
+	display: block;
+    text-align: center;
+    padding-top: 50px;
+	color: #fff;
+	font-size: 14px;
+}
+.qkq_mapcon .qkq_map a:nth-child(1){
+	top:158px;
+	left: 90px;
+}
+/*
+.qkq_mapcon .qkq_map a:nth-child(1):hover{
+	top:148px;
+}
+*/
+.qkq_mapcon .qkq_map a:nth-child(2){
+	top: 200px;
+	left: -72px;
+}
+/*
+.qkq_mapcon .qkq_map a:nth-child(2):hover{
+	top: 190px;
+}
+*/
+.qkq_mapcon .qkq_map a:nth-child(3){
+	top: 352px;
+	left: -46px;
+}
+/*
+.qkq_mapcon .qkq_map a:nth-child(3):hover{
+	top: 342px;
+}
+*/
+.qkq_mapcon .qkq_map a:nth-child(4){
+	top: 415px;
+	left: 140px;
+}
+/*
+.qkq_mapcon .qkq_map a:nth-child(4):hover{
+	top: 405px;
+}
+*/
+.qkq_mapcon .qkq_map a:nth-child(5){
+	top: 432px;
+	right: 277px;
+}
+/*
+.qkq_mapcon .qkq_map a:nth-child(5):hover{
+	top: 383px;
+}
+*/
+.qkq_mapcon .qkq_map a:nth-child(6){
+	top: 342px;
+	right: 91px;
+}
+/*
+.qkq_mapcon .qkq_map a:nth-child(6):hover{
+	top: 332px;
+}
+*/
+.qkq_mapcon .qkq_map a:nth-child(7){
+	top: 241px;
+	right: -57px;
+}
+/*
+.qkq_mapcon .qkq_map a:nth-child(7):hover{
+	top: 231px;
+}
+*/
+.qkq_mapcon .qkq_map a:nth-child(8){
+	top: 161px;
+	right: 90px;
+}
+/*
+.qkq_mapcon .qkq_map a:nth-child(8):hover{
+	top: 151px;
+}
+*/
+
+
+
+
+
+
+

+ 347 - 0
zhsq_qk-ui/.history/src/assets/images/qkq_index_20240618100544.css

@@ -0,0 +1,347 @@
+@charset "utf-8";
+@font-face {
+  font-family: 'MyWebFont2'; /* 给字体命名 */
+  src:url('../fonts/FZHZGBJW.TTF')  format('truetype'), /* Safari, Android, iOS */
+}
+
+/*土地资源*/
+.qkq_tdzy{
+	margin-top: 20px;
+	margin-bottom: 30px;
+}
+.qkq_tdzy_con{
+	display: flex;
+	flex-direction: row;
+	justify-content:space-between;
+	width: 96%;
+	margin: 0px auto;
+	/* height: 170px; */
+}
+.qkq_tdzy .dt_txt{
+	text-align: center;
+	padding-top: 32px;
+}
+.qkq_tdzy .dt_txt h2{
+	font-family: MyWebFont2;
+	font-size: 20px;
+	font-weight: 900;
+	color: #6affff;
+	margin-bottom: 3px;
+}
+.qkq_tdzy .dt_txt h3{
+	font-size: 14px;
+	color: #6affff;
+	margin-bottom: 3px;
+}
+.qkq_tdzy .dt_txt h4{
+	font-size: 14px;
+	color: #fff;
+	margin-bottom: 8px;
+	text-shadow: 0px 0px 5px #6affff;
+}
+.qkq_tdzy .dt_txt h5{
+	font-size: 10px;
+	color: #fff;
+	text-shadow: 0px 0px 5px #6affff;
+}
+.qkq_tdzy .dt_con{
+	background: url(qkq_tdzybg.png) no-repeat center;
+	width: 110px;
+	height: 130px;
+	text-align: center;
+	background-size: 100% 100%;
+	position: relative;
+}
+.qkq_tdzy .dt_con h2{
+	color: #fcce17;
+	font-size: 18px;
+	font-weight: 900;
+	padding-top: 30px;
+}
+.qkq_tdzy .dt_con h3{
+	color: #fcce17;
+	font-size: 14px;
+	margin-bottom: 3px;
+}
+.qkq_tdzy .dt_con h4{
+	color: #fff;
+	font-size: 12px;
+}
+.qkq_tdzy .dt_con .dt_con_b{
+	background: url(qkq_tdzybg2.png) no-repeat center;
+	background-size: 100% 100%;
+	width: 42px;
+	height: 42px;
+	position: absolute;
+	left: 31%;
+	bottom:-13px;
+	text-align: center;
+}
+.qkq_tdzy .dt_con .dt_con_b h4{
+	font-size: 9px;
+    margin-top: 10px;
+	color: #ffe265;
+}
+.qkq_tdzy .dt_con .dt_con_b p{
+	color: #6bd8fc;
+	font-size: 9px;
+}
+
+/*营商环境*/
+.qkq_yshj_con{
+	display: flex;
+	flex-direction: row;
+	height: 170px;
+}
+.qkq_yshj_con .qkq_yshj_list p{
+	font-size: 12px;
+	color: #fff;
+	line-height: 22px;
+	position: relative;
+	padding-left: 13px;
+	text-shadow: 0px 0px 5px #6affff;
+}
+.qkq_yshj_con .qkq_yshj_list p:after{
+	content: " ";
+    display: block;
+    width: 6px;
+	height: 6px;
+	border-radius: 5px;
+	background: #0260b0;
+    position: absolute;
+    /* pointer-events: all; */
+    top: 9px;
+    left: 0px;
+}
+.qkq_yshj_con .qkq_yshj_list span{
+	padding-left: 10px;
+}
+#yshj{
+	width: 225px;
+	height: 170px;
+}
+
+
+/*网格管理*/
+#wggl{
+	width:345px;
+	height: 200px;
+}
+
+/*党建引领*/
+.qkq_djyl{
+	margin-bottom: 15px;
+}
+.qkq_djyl_con{
+	display: flex;
+	flex-direction: row;
+	justify-content: space-around;
+}
+.qkq_djyl_con img{
+	height: 100px;
+    margin-top: 20px;
+}
+.qkq_djyl_con .qkq_list{
+	width: 175px;
+}
+.qkq_djyl_con p{
+	background: url(qkq_djyl_x.png) no-repeat bottom;
+	height: 32px;
+	line-height: 32px;
+	font-size: 14px;
+	color: #fff;
+	display: flex;
+	flex-direction: row;
+	justify-content: space-between;
+}
+.qkq_djyl_con p span{
+	padding-left: 14px;
+	background: url(qkq_djyl_icon.png) no-repeat left center;
+}
+.qkq_djyl_con p i{
+	text-align: right;
+	display: inline-block;
+}
+.blue{
+	color: #01b2f4!important;
+}
+.green{
+	color: #01f49b!important;
+}
+.org{
+	color: #f4b00d!important;
+}
+
+/*经济运行*/
+#jjyx{
+	width: 345px;
+	height: 180px;
+	margin-bottom: 15px;
+}
+/*事件信息*/
+.qkq_jjyx_list{
+
+}
+.scro_list{
+	height: 190px;
+	overflow-y: scroll;
+}
+.scro_list::-webkit-scrollbar{
+	width: 0px;
+	height: 0px;
+}
+.qkq_jjyx_list dt{
+	background: rgba(3,119,227,0.1);
+	padding: 0px 10px;
+}
+.qkq_jjyx_list dd{
+	padding: 0px 10px;
+}
+.qkq_jjyx_list dt span,.qkq_jjyx_list dd span{
+	font-size: 14px;
+	color: #fff;
+	display: inline-block;
+	height: 30px;
+	line-height: 30px;
+	text-shadow: 0px 0px 5px #6affff;
+}
+.qkq_jjyx_list dt span:nth-child(1),.qkq_jjyx_list dd span:nth-child(1){
+	width: 41%;
+}
+.qkq_jjyx_list dt span:nth-child(2),.qkq_jjyx_list dd span:nth-child(2){
+	width: 26%;
+}
+.qkq_jjyx_list dt span:nth-child(3),.qkq_jjyx_list dd span:nth-child(3){
+	width: 13%;
+}
+.qkq_jjyx_list dt span:nth-child(4),.qkq_jjyx_list dd span:nth-child(4){
+	width: 15%;
+}
+.qkq_jjyx_list dd span.zt{
+	background: url(qkq_listbg.png) no-repeat center;
+	background-size: 100% auto;
+	text-align: center;
+	font-size: 10px;
+	color: #e1e4ec;
+}
+
+
+/*中间内容*/
+.qkq_mapcon{
+	position: fixed;
+	bottom:20%;
+	left: 50%;
+	transform: translateX(-50%);
+	z-index: 2;
+}
+
+.qkq_mapcon .qkq_map{
+	width: 100%;
+    height: 507px;
+    position: absolute;
+    top: 0px;
+}
+.qkq_mapcon .qkq_map a{
+	display: block;
+	position: absolute;
+	background: url(qkq_iconcon.png) no-repeat center;
+	width: 143px;
+	height: 167px;
+	transition: transform 0.7s ease-in-out; /* 设置过渡效果 */
+  transform: translateY(0); /* 初始状态不移动 */
+}
+.qkq_mapcon .qkq_map a:hover{
+	background: url(qkq_iconcon_on.png) no-repeat center;
+	transform: translateY(-10px); /* 鼠标划过时向上移动20像素 */
+}
+.qkq_mapcon .qkq_map a i{
+	display: block;
+    text-align: center;
+    padding-top: 27px;
+}
+.qkq_mapcon .qkq_map a span{
+	display: block;
+    text-align: center;
+    padding-top: 50px;
+	color: #fff;
+	font-size: 14px;
+}
+.qkq_mapcon .qkq_map a:nth-child(1){
+	top:158px;
+	left: 90px;
+}
+/*
+.qkq_mapcon .qkq_map a:nth-child(1):hover{
+	top:148px;
+}
+*/
+.qkq_mapcon .qkq_map a:nth-child(2){
+	top: 200px;
+	left: -72px;
+}
+/*
+.qkq_mapcon .qkq_map a:nth-child(2):hover{
+	top: 190px;
+}
+*/
+.qkq_mapcon .qkq_map a:nth-child(3){
+	top: 352px;
+	left: -46px;
+}
+/*
+.qkq_mapcon .qkq_map a:nth-child(3):hover{
+	top: 342px;
+}
+*/
+.qkq_mapcon .qkq_map a:nth-child(4){
+	top: 415px;
+	left: 140px;
+}
+/*
+.qkq_mapcon .qkq_map a:nth-child(4):hover{
+	top: 405px;
+}
+*/
+.qkq_mapcon .qkq_map a:nth-child(5){
+	top: 432px;
+	right: 277px;
+}
+/*
+.qkq_mapcon .qkq_map a:nth-child(5):hover{
+	top: 383px;
+}
+*/
+.qkq_mapcon .qkq_map a:nth-child(6){
+	top: 383px;
+	right: 56px;
+}
+/*
+.qkq_mapcon .qkq_map a:nth-child(6):hover{
+	top: 332px;
+}
+*/
+.qkq_mapcon .qkq_map a:nth-child(7){
+	top: 241px;
+	right: -57px;
+}
+/*
+.qkq_mapcon .qkq_map a:nth-child(7):hover{
+	top: 231px;
+}
+*/
+.qkq_mapcon .qkq_map a:nth-child(8){
+	top: 161px;
+	right: 90px;
+}
+/*
+.qkq_mapcon .qkq_map a:nth-child(8):hover{
+	top: 151px;
+}
+*/
+
+
+
+
+
+
+

+ 298 - 0
zhsq_qk-ui/.history/src/map3d/drawFunc_20240513112607.js

@@ -0,0 +1,298 @@
+import * as THREE from "three";
+import * as d3 from "d3";
+import { CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { Line2 } from "three/examples/jsm/lines/Line2";
+import { LineGeometry } from "three/examples/jsm/lines/LineGeometry";
+import { LineMaterial } from "three/examples/jsm/lines/LineMaterial";
+// import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
+
+// import { ProjectionFnParamType } from "./index.vue";
+import { mapConfig } from "./mapConfig";
+
+// 绘制挤出的材质
+export function drawExtrudeMesh(
+  point,
+  projectionFn
+) {
+  const shape = new THREE.Shape();
+  const pointsArray = [];
+
+  for (let i = 0; i < point.length; i++) {
+    const [x, y] = projectionFn(point[i]); // 将每一个经纬度转化为坐标点
+    if (i === 0) {
+      shape.moveTo(x, -y);
+    }
+    shape.lineTo(x, -y);
+    pointsArray.push(x, -y, mapConfig.topLineZIndex);
+  }
+
+  const geometry = new THREE.ExtrudeGeometry(shape, {
+    depth: mapConfig.mapDepth, // 挤出的形状深度
+    bevelEnabled: false, // 对挤出的形状应用是否斜角
+  });
+
+  const material = new THREE.MeshPhongMaterial({
+    // color: mapConfig.mapColor,
+    color: mapConfig.mapColorGradient[Math.floor(Math.random() * 4)], // 随机颜色
+    // transparent: mapConfig.mapTransparent,
+    // opacity: mapConfig.mapOpacity,
+  });
+
+  const materialSide = new THREE.ShaderMaterial({
+    uniforms: {
+      color1: {
+        value: new THREE.Color(mapConfig.mapSideColor1),
+      },
+      color2: {
+        value: new THREE.Color(mapConfig.mapSideColor2),
+      },
+    },
+    vertexShader: `
+      varying vec3 vPosition;
+      void main() {
+        vPosition = position;
+        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+      }
+    `,
+    fragmentShader: `
+      uniform vec3 color1;
+      uniform vec3 color2;
+      varying vec3 vPosition;
+      void main() {
+        vec3 mixColor = mix(color1, color2, 0.5 - vPosition.z * 0.2); // 使用顶点坐标 z 分量来控制混合
+        gl_FragColor = vec4(mixColor, 1.0);
+      }
+    `,
+    //   wireframe: true,
+  });
+
+  const mesh = new THREE.Mesh(geometry, [material, materialSide]);
+  // userData 存储自定义属性
+  mesh.userData = {
+    isChangeColor: true,
+  };
+
+  // 边框线,赋值空间点坐标,3个一组
+  const lineGeometry = new LineGeometry();
+  lineGeometry.setPositions(pointsArray);
+
+  const lineMaterial = new LineMaterial({
+    color: mapConfig.topLineColor,
+    linewidth: mapConfig.topLineWidth,
+  });
+  lineMaterial.resolution.set(window.innerWidth, window.innerHeight);
+  const line = new Line2(lineGeometry, lineMaterial);
+
+  return { mesh, line };
+}
+
+// 生成地图3D模型
+export function generateMapObject3D(
+  mapdata,
+  projectionFnParam
+) {
+  // 地图对象
+  const mapObject3D = new THREE.Object3D();
+  // 地图数据
+  const { features: basicFeatures } = mapdata;
+
+  const { center, scale } = projectionFnParam;
+
+  const projectionFn = d3
+    .geoMercator()
+    .center(center)
+    .scale(scale)
+    .translate([0, 0]);
+
+  const label2dData = []; // 存储自定义 2d 标签数据
+
+  // 每个省的数据
+  basicFeatures.forEach((basicFeatureItem) => {
+    // 每个省份的地图对象
+    const provinceMapObject3D = new THREE.Object3D();
+    // 将地图数据挂在到模型数据上
+    provinceMapObject3D.customProperties = basicFeatureItem.properties;
+
+    // 每个坐标类型
+    const featureType = basicFeatureItem.geometry.type;
+    // 每个坐标数组
+    const featureCoords =
+      basicFeatureItem.geometry.coordinates;
+    // 每个中心点位置
+    const featureCenterCoord =
+      basicFeatureItem.properties.centroid &&
+      projectionFn(basicFeatureItem.properties.centroid);
+    // 名字
+    const featureName = basicFeatureItem.properties.name;
+
+    if (featureCenterCoord) {
+      label2dData.push({
+        featureCenterCoord,
+        featureName,
+      });
+    }
+
+    // MultiPolygon 类型
+    if (featureType === "MultiPolygon") {
+      featureCoords.forEach((multiPolygon) => {
+        multiPolygon.forEach((polygon) => {
+          const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+          provinceMapObject3D.add(mesh);
+          provinceMapObject3D.add(line);
+        });
+      });
+    }
+
+    // Polygon 类型
+    if (featureType === "Polygon") {
+      featureCoords.forEach((polygon) => {
+        const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+        provinceMapObject3D.add(mesh);
+        provinceMapObject3D.add(line);
+      });
+    }
+
+    mapObject3D.add(provinceMapObject3D);
+  });
+
+  return { mapObject3D, label2dData };
+}
+
+// 生成地图2D标签
+export function generateMapLabel2D(label2dData) {
+  const labelObject2D = new THREE.Object3D();
+  label2dData.forEach((item) => {
+    const { featureCenterCoord, featureName } = item;
+    const labelObjectItem = draw2dLabel(featureCenterCoord, featureName);
+    if (labelObjectItem) {
+      labelObject2D.add(labelObjectItem);
+    }
+  });
+  return labelObject2D;
+}
+
+// 生成地图spot点位
+export function generateMapSpot(label2dData) {
+  const spotObject3D = new THREE.Object3D();
+  const spotList = [];
+  label2dData.forEach((item) => {
+    const { featureCenterCoord } = item;
+      const spotObjectItem = drawSpot(featureCenterCoord);
+      if (spotObjectItem && spotObjectItem.circle && spotObjectItem.ring) {
+        spotObject3D.add(spotObjectItem.circle);
+        spotObject3D.add(spotObjectItem.ring);
+        spotList.push(spotObjectItem.ring);
+      }
+
+      //TODO:渲染铁塔
+      
+
+  });
+  return { spotObject3D, spotList };
+}
+
+// 绘制二维标签
+export const draw2dLabel = (coord, proviceName) => {
+  if (coord && coord.length) {
+    // 模版字符串
+    const innerHTML = `<div class="your-classname" style="color: #fff">${proviceName}</div>`;
+    const labelDiv = document.createElement("div");
+    labelDiv.innerHTML = innerHTML;
+    labelDiv.style.pointerEvents = "none"; // 禁用事件
+    const labelObject = new CSS2DObject(labelDiv);
+    labelObject.position.set(coord[0], -coord[1], mapConfig.label2dZIndex);
+    return labelObject;
+  }
+};
+
+// 绘制圆点
+export const drawSpot = (coord) => {
+  if (coord && coord.length) {
+    /**
+     * 绘制圆点
+     */
+    const spotGeometry = new THREE.CircleGeometry(0.2, 200);
+    const spotMaterial = new THREE.MeshBasicMaterial({
+      color: "#3EC5FB",
+      side: THREE.DoubleSide,
+    });
+    const circle = new THREE.Mesh(spotGeometry, spotMaterial);
+    circle.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+
+    // 圆环
+    const ringGeometry = new THREE.RingGeometry(0.2, 0.3, 50);
+    const ringMaterial = new THREE.MeshBasicMaterial({
+      color: "#3FC5FB",
+      side: THREE.DoubleSide,
+      transparent: true,
+    });
+    const ring = new THREE.Mesh(ringGeometry, ringMaterial);
+    ring.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+    return { circle, ring };
+  }
+};
+
+/**
+ * 线上移动物体
+ */
+export const drawflySpot = (curve) => {
+  const aGeo = new THREE.SphereGeometry(0.2);
+  const aMater = new THREE.MeshBasicMaterial({
+    color: "#77f077",
+    side: THREE.DoubleSide,
+  });
+  const aMesh = new THREE.Mesh(aGeo, aMater);
+  // 保存曲线实例
+  aMesh.curve = curve;
+  aMesh._s = 0;
+  return aMesh;
+};
+
+// 绘制两点链接飞线
+export const drawLineBetween2Spot = (
+  coordStart,
+  coordEnd
+) => {
+  const [x0, y0, z0] = [...coordStart, mapConfig.spotZIndex];
+  const [x1, y1, z1] = [...coordEnd, mapConfig.spotZIndex];
+  // 使用 QuadraticBezierCurve3 创建 三维二次贝塞尔曲线
+  const curve = new THREE.QuadraticBezierCurve3(
+    new THREE.Vector3(x0, -y0, z0),
+    new THREE.Vector3((x0 + x1) / 2, -(y0 + y1) / 2, 20),
+    new THREE.Vector3(x1, -y1, z1)
+  );
+
+  const flySpot = drawflySpot(curve);
+
+  const lineGeometry = new THREE.BufferGeometry();
+  // 获取曲线上50个点
+  const points = curve.getPoints(50);
+  const positions = [];
+  const colors = [];
+  const color = new THREE.Color();
+
+  // 给每个顶点设置演示 实现渐变
+  for (let j = 0; j < points.length; j++) {
+    color.setHSL(0.21 + j, 0.77, 0.55 + j * 0.0025); // 色
+    colors.push(color.r, color.g, color.b);
+    positions.push(points[j].x, points[j].y, points[j].z);
+  }
+  // 放入顶点 和 设置顶点颜色
+  lineGeometry.setAttribute(
+    "position",
+    new THREE.BufferAttribute(new Float32Array(positions), 3, true)
+  );
+  lineGeometry.setAttribute(
+    "color",
+    new THREE.BufferAttribute(new Float32Array(colors), 3, true)
+  );
+
+  const material = new THREE.LineBasicMaterial({
+    vertexColors: true,
+    // color: "red",
+    side: THREE.DoubleSide,
+  });
+  const flyLine = new THREE.Line(lineGeometry, material);
+
+  return { flyLine, flySpot };
+};

+ 298 - 0
zhsq_qk-ui/.history/src/map3d/drawFunc_20240617135642.js

@@ -0,0 +1,298 @@
+import * as THREE from "three";
+import * as d3 from "d3";
+import { CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { Line2 } from "three/examples/jsm/lines/Line2";
+import { LineGeometry } from "three/examples/jsm/lines/LineGeometry";
+import { LineMaterial } from "three/examples/jsm/lines/LineMaterial";
+// import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
+
+// import { ProjectionFnParamType } from "./index.vue";
+import { mapConfig } from "./mapConfig";
+
+// 绘制挤出的材质
+export function drawExtrudeMesh(
+  point,
+  projectionFn
+) {
+  const shape = new THREE.Shape();
+  const pointsArray = [];
+
+  for (let i = 0; i < point.length; i++) {
+    const [x, y] = projectionFn(point[i]); // 将每一个经纬度转化为坐标点
+    if (i === 0) {
+      shape.moveTo(x, -y);
+    }
+    shape.lineTo(x, -y);
+    pointsArray.push(x, -y, mapConfig.topLineZIndex);
+  }
+
+  const geometry = new THREE.ExtrudeGeometry(shape, {
+    depth: mapConfig.mapDepth, // 挤出的形状深度
+    bevelEnabled: false, // 对挤出的形状应用是否斜角
+  });
+
+  const material = new THREE.MeshPhongMaterial({
+    // color: mapConfig.mapColor,
+    color: mapConfig.mapColorGradient[Math.floor(Math.random() * 4)], // 随机颜色
+    // transparent: mapConfig.mapTransparent,
+    // opacity: mapConfig.mapOpacity,
+  });
+
+  const materialSide = new THREE.ShaderMaterial({
+    uniforms: {
+      color1: {
+        value: new THREE.Color(mapConfig.mapSideColor1),
+      },
+      color2: {
+        value: new THREE.Color(mapConfig.mapSideColor2),
+      },
+    },
+    vertexShader: `
+      varying vec3 vPosition;
+      void main() {
+        vPosition = position;
+        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+      }
+    `,
+    fragmentShader: `
+      uniform vec3 color1;
+      uniform vec3 color2;
+      varying vec3 vPosition;
+      void main() {
+        vec3 mixColor = mix(color1, color2, 0.5 - vPosition.z * 0.2); // 使用顶点坐标 z 分量来控制混合
+        gl_FragColor = vec4(mixColor, 1.0);
+      }
+    `,
+    //   wireframe: true,
+  });
+
+  const mesh = new THREE.Mesh(geometry, [material, materialSide]);
+  // userData 存储自定义属性
+  mesh.userData = {
+    isChangeColor: true,
+  };
+
+  // 边框线,赋值空间点坐标,3个一组
+  const lineGeometry = new LineGeometry();
+  lineGeometry.setPositions(pointsArray);
+
+  const lineMaterial = new LineMaterial({
+    color: mapConfig.topLineColor,
+    linewidth: mapConfig.topLineWidth,
+  });
+  lineMaterial.resolution.set(window.innerWidth, window.innerHeight);
+  const line = new Line2(lineGeometry, lineMaterial);
+
+  return { mesh, line };
+}
+
+// 生成地图3D模型
+export function generateMapObject3D(
+  mapdata,
+  projectionFnParam
+) {
+  // 地图对象
+  const mapObject3D = new THREE.Object3D();
+  // 地图数据
+  const { features: basicFeatures } = mapdata;
+
+  const { center, scale } = projectionFnParam;
+
+  const projectionFn = d3
+    .geoMercator()
+    .center(center)
+    .scale(scale)
+    .translate([0, 0]);
+
+  const label2dData = []; // 存储自定义 2d 标签数据
+
+  // 每个省的数据
+  basicFeatures.forEach((basicFeatureItem) => {
+    // 每个省份的地图对象
+    const provinceMapObject3D = new THREE.Object3D();
+    // 将地图数据挂在到模型数据上
+    provinceMapObject3D.customProperties = basicFeatureItem.properties;
+
+    // 每个坐标类型
+    const featureType = basicFeatureItem.geometry.type;
+    // 每个坐标数组
+    const featureCoords =
+      basicFeatureItem.geometry.coordinates;
+    // 每个中心点位置
+    const featureCenterCoord =
+      basicFeatureItem.properties.centroid &&
+      projectionFn(basicFeatureItem.properties.centroid);
+    // 名字
+    const featureName = basicFeatureItem.properties.name;
+
+    if (featureCenterCoord) {
+      label2dData.push({
+        featureCenterCoord,
+        featureName,
+      });
+    }
+
+    // MultiPolygon 类型
+    if (featureType === "MultiPolygon") {
+      featureCoords.forEach((multiPolygon) => {
+        multiPolygon.forEach((polygon) => {
+          const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+          provinceMapObject3D.add(mesh);
+          provinceMapObject3D.add(line);
+        });
+      });
+    }
+
+    // Polygon 类型
+    if (featureType === "Polygon") {
+      featureCoords.forEach((polygon) => {
+        const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+        provinceMapObject3D.add(mesh);
+        provinceMapObject3D.add(line);
+      });
+    }
+
+    mapObject3D.add(provinceMapObject3D);
+  });
+
+  return { mapObject3D, label2dData };
+}
+
+// 生成地图2D标签
+export function generateMapLabel2D(label2dData) {
+  const labelObject2D = new THREE.Object3D();
+  label2dData.forEach((item) => {
+    const { featureCenterCoord, featureName } = item;
+    const labelObjectItem = draw2dLabel(featureCenterCoord,);
+    if (labelObjectItem) {
+      labelObject2D.add(labelObjectItem);
+    }
+  });
+  return labelObject2D;
+}
+
+// 生成地图spot点位
+export function generateMapSpot(label2dData) {
+  const spotObject3D = new THREE.Object3D();
+  const spotList = [];
+  label2dData.forEach((item) => {
+    const { featureCenterCoord } = item;
+      const spotObjectItem = drawSpot(featureCenterCoord);
+      if (spotObjectItem && spotObjectItem.circle && spotObjectItem.ring) {
+        spotObject3D.add(spotObjectItem.circle);
+        spotObject3D.add(spotObjectItem.ring);
+        spotList.push(spotObjectItem.ring);
+      }
+
+      //TODO:渲染铁塔
+      
+
+  });
+  return { spotObject3D, spotList };
+}
+
+// 绘制二维标签
+export const draw2dLabel = (coord, proviceName) => {
+  if (coord && coord.length) {
+    // 模版字符串
+    const innerHTML = `<div class="your-classname" style="color: #fff">${proviceName}</div>`;
+    const labelDiv = document.createElement("div");
+    labelDiv.innerHTML = innerHTML;
+    labelDiv.style.pointerEvents = "none"; // 禁用事件
+    const labelObject = new CSS2DObject(labelDiv);
+    labelObject.position.set(coord[0], -coord[1], mapConfig.label2dZIndex);
+    return labelObject;
+  }
+};
+
+// 绘制圆点
+export const drawSpot = (coord) => {
+  if (coord && coord.length) {
+    /**
+     * 绘制圆点
+     */
+    const spotGeometry = new THREE.CircleGeometry(0.2, 200);
+    const spotMaterial = new THREE.MeshBasicMaterial({
+      color: "#3EC5FB",
+      side: THREE.DoubleSide,
+    });
+    const circle = new THREE.Mesh(spotGeometry, spotMaterial);
+    circle.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+
+    // 圆环
+    const ringGeometry = new THREE.RingGeometry(0.2, 0.3, 50);
+    const ringMaterial = new THREE.MeshBasicMaterial({
+      color: "#3FC5FB",
+      side: THREE.DoubleSide,
+      transparent: true,
+    });
+    const ring = new THREE.Mesh(ringGeometry, ringMaterial);
+    ring.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+    return { circle, ring };
+  }
+};
+
+/**
+ * 线上移动物体
+ */
+export const drawflySpot = (curve) => {
+  const aGeo = new THREE.SphereGeometry(0.2);
+  const aMater = new THREE.MeshBasicMaterial({
+    color: "#77f077",
+    side: THREE.DoubleSide,
+  });
+  const aMesh = new THREE.Mesh(aGeo, aMater);
+  // 保存曲线实例
+  aMesh.curve = curve;
+  aMesh._s = 0;
+  return aMesh;
+};
+
+// 绘制两点链接飞线
+export const drawLineBetween2Spot = (
+  coordStart,
+  coordEnd
+) => {
+  const [x0, y0, z0] = [...coordStart, mapConfig.spotZIndex];
+  const [x1, y1, z1] = [...coordEnd, mapConfig.spotZIndex];
+  // 使用 QuadraticBezierCurve3 创建 三维二次贝塞尔曲线
+  const curve = new THREE.QuadraticBezierCurve3(
+    new THREE.Vector3(x0, -y0, z0),
+    new THREE.Vector3((x0 + x1) / 2, -(y0 + y1) / 2, 20),
+    new THREE.Vector3(x1, -y1, z1)
+  );
+
+  const flySpot = drawflySpot(curve);
+
+  const lineGeometry = new THREE.BufferGeometry();
+  // 获取曲线上50个点
+  const points = curve.getPoints(50);
+  const positions = [];
+  const colors = [];
+  const color = new THREE.Color();
+
+  // 给每个顶点设置演示 实现渐变
+  for (let j = 0; j < points.length; j++) {
+    color.setHSL(0.21 + j, 0.77, 0.55 + j * 0.0025); // 色
+    colors.push(color.r, color.g, color.b);
+    positions.push(points[j].x, points[j].y, points[j].z);
+  }
+  // 放入顶点 和 设置顶点颜色
+  lineGeometry.setAttribute(
+    "position",
+    new THREE.BufferAttribute(new Float32Array(positions), 3, true)
+  );
+  lineGeometry.setAttribute(
+    "color",
+    new THREE.BufferAttribute(new Float32Array(colors), 3, true)
+  );
+
+  const material = new THREE.LineBasicMaterial({
+    vertexColors: true,
+    // color: "red",
+    side: THREE.DoubleSide,
+  });
+  const flyLine = new THREE.Line(lineGeometry, material);
+
+  return { flyLine, flySpot };
+};

+ 299 - 0
zhsq_qk-ui/.history/src/map3d/drawFunc_20240617135657.js

@@ -0,0 +1,299 @@
+import * as THREE from "three";
+import * as d3 from "d3";
+import { CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { Line2 } from "three/examples/jsm/lines/Line2";
+import { LineGeometry } from "three/examples/jsm/lines/LineGeometry";
+import { LineMaterial } from "three/examples/jsm/lines/LineMaterial";
+// import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
+
+// import { ProjectionFnParamType } from "./index.vue";
+import { mapConfig } from "./mapConfig";
+
+// 绘制挤出的材质
+export function drawExtrudeMesh(
+  point,
+  projectionFn
+) {
+  const shape = new THREE.Shape();
+  const pointsArray = [];
+
+  for (let i = 0; i < point.length; i++) {
+    const [x, y] = projectionFn(point[i]); // 将每一个经纬度转化为坐标点
+    if (i === 0) {
+      shape.moveTo(x, -y);
+    }
+    shape.lineTo(x, -y);
+    pointsArray.push(x, -y, mapConfig.topLineZIndex);
+  }
+
+  const geometry = new THREE.ExtrudeGeometry(shape, {
+    depth: mapConfig.mapDepth, // 挤出的形状深度
+    bevelEnabled: false, // 对挤出的形状应用是否斜角
+  });
+
+  const material = new THREE.MeshPhongMaterial({
+    // color: mapConfig.mapColor,
+    color: mapConfig.mapColorGradient[Math.floor(Math.random() * 4)], // 随机颜色
+    // transparent: mapConfig.mapTransparent,
+    // opacity: mapConfig.mapOpacity,
+  });
+
+  const materialSide = new THREE.ShaderMaterial({
+    uniforms: {
+      color1: {
+        value: new THREE.Color(mapConfig.mapSideColor1),
+      },
+      color2: {
+        value: new THREE.Color(mapConfig.mapSideColor2),
+      },
+    },
+    vertexShader: `
+      varying vec3 vPosition;
+      void main() {
+        vPosition = position;
+        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+      }
+    `,
+    fragmentShader: `
+      uniform vec3 color1;
+      uniform vec3 color2;
+      varying vec3 vPosition;
+      void main() {
+        vec3 mixColor = mix(color1, color2, 0.5 - vPosition.z * 0.2); // 使用顶点坐标 z 分量来控制混合
+        gl_FragColor = vec4(mixColor, 1.0);
+      }
+    `,
+    //   wireframe: true,
+  });
+
+  const mesh = new THREE.Mesh(geometry, [material, materialSide]);
+  // userData 存储自定义属性
+  mesh.userData = {
+    isChangeColor: true,
+  };
+
+  // 边框线,赋值空间点坐标,3个一组
+  const lineGeometry = new LineGeometry();
+  lineGeometry.setPositions(pointsArray);
+
+  const lineMaterial = new LineMaterial({
+    color: mapConfig.topLineColor,
+    linewidth: mapConfig.topLineWidth,
+  });
+  lineMaterial.resolution.set(window.innerWidth, window.innerHeight);
+  const line = new Line2(lineGeometry, lineMaterial);
+
+  return { mesh, line };
+}
+
+// 生成地图3D模型
+export function generateMapObject3D(
+  mapdata,
+  projectionFnParam
+) {
+  // 地图对象
+  const mapObject3D = new THREE.Object3D();
+  // 地图数据
+  const { features: basicFeatures } = mapdata;
+
+  const { center, scale } = projectionFnParam;
+
+  const projectionFn = d3
+    .geoMercator()
+    .center(center)
+    .scale(scale)
+    .translate([0, 0]);
+
+  const label2dData = []; // 存储自定义 2d 标签数据
+
+  // 每个省的数据
+  basicFeatures.forEach((basicFeatureItem) => {
+    // 每个省份的地图对象
+    const provinceMapObject3D = new THREE.Object3D();
+    // 将地图数据挂在到模型数据上
+    provinceMapObject3D.customProperties = basicFeatureItem.properties;
+
+    // 每个坐标类型
+    const featureType = basicFeatureItem.geometry.type;
+    // 每个坐标数组
+    const featureCoords =
+      basicFeatureItem.geometry.coordinates;
+    // 每个中心点位置
+    const featureCenterCoord =
+      basicFeatureItem.properties.centroid &&
+      projectionFn(basicFeatureItem.properties.centroid);
+    // 名字
+    const featureName = basicFeatureItem.properties.name;
+
+    if (featureCenterCoord) {
+      label2dData.push({
+        featureCenterCoord,
+        featureName,
+      });
+    }
+
+    // MultiPolygon 类型
+    if (featureType === "MultiPolygon") {
+      featureCoords.forEach((multiPolygon) => {
+        multiPolygon.forEach((polygon) => {
+          const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+          provinceMapObject3D.add(mesh);
+          provinceMapObject3D.add(line);
+        });
+      });
+    }
+
+    // Polygon 类型
+    if (featureType === "Polygon") {
+      featureCoords.forEach((polygon) => {
+        const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+        provinceMapObject3D.add(mesh);
+        provinceMapObject3D.add(line);
+      });
+    }
+
+    mapObject3D.add(provinceMapObject3D);
+  });
+
+  return { mapObject3D, label2dData };
+}
+
+// 生成地图2D标签
+export function generateMapLabel2D(label2dData) {
+  const labelObject2D = new THREE.Object3D();
+  label2dData.forEach((item) => {
+    const { featureCenterCoord, featureName } = item;
+    debugger
+    const labelObjectItem = draw2dLabel(featureCenterCoord, featureName);
+    if (labelObjectItem) {
+      labelObject2D.add(labelObjectItem);
+    }
+  });
+  return labelObject2D;
+}
+
+// 生成地图spot点位
+export function generateMapSpot(label2dData) {
+  const spotObject3D = new THREE.Object3D();
+  const spotList = [];
+  label2dData.forEach((item) => {
+    const { featureCenterCoord } = item;
+      const spotObjectItem = drawSpot(featureCenterCoord);
+      if (spotObjectItem && spotObjectItem.circle && spotObjectItem.ring) {
+        spotObject3D.add(spotObjectItem.circle);
+        spotObject3D.add(spotObjectItem.ring);
+        spotList.push(spotObjectItem.ring);
+      }
+
+      //TODO:渲染铁塔
+      
+
+  });
+  return { spotObject3D, spotList };
+}
+
+// 绘制二维标签
+export const draw2dLabel = (coord, proviceName) => {
+  if (coord && coord.length) {
+    // 模版字符串
+    const innerHTML = `<div class="your-classname" style="color: #fff">${proviceName}</div>`;
+    const labelDiv = document.createElement("div");
+    labelDiv.innerHTML = innerHTML;
+    labelDiv.style.pointerEvents = "none"; // 禁用事件
+    const labelObject = new CSS2DObject(labelDiv);
+    labelObject.position.set(coord[0], -coord[1], mapConfig.label2dZIndex);
+    return labelObject;
+  }
+};
+
+// 绘制圆点
+export const drawSpot = (coord) => {
+  if (coord && coord.length) {
+    /**
+     * 绘制圆点
+     */
+    const spotGeometry = new THREE.CircleGeometry(0.2, 200);
+    const spotMaterial = new THREE.MeshBasicMaterial({
+      color: "#3EC5FB",
+      side: THREE.DoubleSide,
+    });
+    const circle = new THREE.Mesh(spotGeometry, spotMaterial);
+    circle.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+
+    // 圆环
+    const ringGeometry = new THREE.RingGeometry(0.2, 0.3, 50);
+    const ringMaterial = new THREE.MeshBasicMaterial({
+      color: "#3FC5FB",
+      side: THREE.DoubleSide,
+      transparent: true,
+    });
+    const ring = new THREE.Mesh(ringGeometry, ringMaterial);
+    ring.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+    return { circle, ring };
+  }
+};
+
+/**
+ * 线上移动物体
+ */
+export const drawflySpot = (curve) => {
+  const aGeo = new THREE.SphereGeometry(0.2);
+  const aMater = new THREE.MeshBasicMaterial({
+    color: "#77f077",
+    side: THREE.DoubleSide,
+  });
+  const aMesh = new THREE.Mesh(aGeo, aMater);
+  // 保存曲线实例
+  aMesh.curve = curve;
+  aMesh._s = 0;
+  return aMesh;
+};
+
+// 绘制两点链接飞线
+export const drawLineBetween2Spot = (
+  coordStart,
+  coordEnd
+) => {
+  const [x0, y0, z0] = [...coordStart, mapConfig.spotZIndex];
+  const [x1, y1, z1] = [...coordEnd, mapConfig.spotZIndex];
+  // 使用 QuadraticBezierCurve3 创建 三维二次贝塞尔曲线
+  const curve = new THREE.QuadraticBezierCurve3(
+    new THREE.Vector3(x0, -y0, z0),
+    new THREE.Vector3((x0 + x1) / 2, -(y0 + y1) / 2, 20),
+    new THREE.Vector3(x1, -y1, z1)
+  );
+
+  const flySpot = drawflySpot(curve);
+
+  const lineGeometry = new THREE.BufferGeometry();
+  // 获取曲线上50个点
+  const points = curve.getPoints(50);
+  const positions = [];
+  const colors = [];
+  const color = new THREE.Color();
+
+  // 给每个顶点设置演示 实现渐变
+  for (let j = 0; j < points.length; j++) {
+    color.setHSL(0.21 + j, 0.77, 0.55 + j * 0.0025); // 色
+    colors.push(color.r, color.g, color.b);
+    positions.push(points[j].x, points[j].y, points[j].z);
+  }
+  // 放入顶点 和 设置顶点颜色
+  lineGeometry.setAttribute(
+    "position",
+    new THREE.BufferAttribute(new Float32Array(positions), 3, true)
+  );
+  lineGeometry.setAttribute(
+    "color",
+    new THREE.BufferAttribute(new Float32Array(colors), 3, true)
+  );
+
+  const material = new THREE.LineBasicMaterial({
+    vertexColors: true,
+    // color: "red",
+    side: THREE.DoubleSide,
+  });
+  const flyLine = new THREE.Line(lineGeometry, material);
+
+  return { flyLine, flySpot };
+};

+ 300 - 0
zhsq_qk-ui/.history/src/map3d/drawFunc_20240617135747.js

@@ -0,0 +1,300 @@
+import * as THREE from "three";
+import * as d3 from "d3";
+import { CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { Line2 } from "three/examples/jsm/lines/Line2";
+import { LineGeometry } from "three/examples/jsm/lines/LineGeometry";
+import { LineMaterial } from "three/examples/jsm/lines/LineMaterial";
+// import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
+
+// import { ProjectionFnParamType } from "./index.vue";
+import { mapConfig } from "./mapConfig";
+
+// 绘制挤出的材质
+export function drawExtrudeMesh(
+  point,
+  projectionFn
+) {
+  const shape = new THREE.Shape();
+  const pointsArray = [];
+
+  for (let i = 0; i < point.length; i++) {
+    const [x, y] = projectionFn(point[i]); // 将每一个经纬度转化为坐标点
+    if (i === 0) {
+      shape.moveTo(x, -y);
+    }
+    shape.lineTo(x, -y);
+    pointsArray.push(x, -y, mapConfig.topLineZIndex);
+  }
+
+  const geometry = new THREE.ExtrudeGeometry(shape, {
+    depth: mapConfig.mapDepth, // 挤出的形状深度
+    bevelEnabled: false, // 对挤出的形状应用是否斜角
+  });
+
+  const material = new THREE.MeshPhongMaterial({
+    // color: mapConfig.mapColor,
+    color: mapConfig.mapColorGradient[Math.floor(Math.random() * 4)], // 随机颜色
+    // transparent: mapConfig.mapTransparent,
+    // opacity: mapConfig.mapOpacity,
+  });
+
+  const materialSide = new THREE.ShaderMaterial({
+    uniforms: {
+      color1: {
+        value: new THREE.Color(mapConfig.mapSideColor1),
+      },
+      color2: {
+        value: new THREE.Color(mapConfig.mapSideColor2),
+      },
+    },
+    vertexShader: `
+      varying vec3 vPosition;
+      void main() {
+        vPosition = position;
+        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+      }
+    `,
+    fragmentShader: `
+      uniform vec3 color1;
+      uniform vec3 color2;
+      varying vec3 vPosition;
+      void main() {
+        vec3 mixColor = mix(color1, color2, 0.5 - vPosition.z * 0.2); // 使用顶点坐标 z 分量来控制混合
+        gl_FragColor = vec4(mixColor, 1.0);
+      }
+    `,
+    //   wireframe: true,
+  });
+
+  const mesh = new THREE.Mesh(geometry, [material, materialSide]);
+  // userData 存储自定义属性
+  mesh.userData = {
+    isChangeColor: true,
+  };
+
+  // 边框线,赋值空间点坐标,3个一组
+  const lineGeometry = new LineGeometry();
+  lineGeometry.setPositions(pointsArray);
+
+  const lineMaterial = new LineMaterial({
+    color: mapConfig.topLineColor,
+    linewidth: mapConfig.topLineWidth,
+  });
+  lineMaterial.resolution.set(window.innerWidth, window.innerHeight);
+  const line = new Line2(lineGeometry, lineMaterial);
+
+  return { mesh, line };
+}
+
+// 生成地图3D模型
+export function generateMapObject3D(
+  mapdata,
+  projectionFnParam
+) {
+  debugger
+  // 地图对象
+  const mapObject3D = new THREE.Object3D();
+  // 地图数据
+  const { features: basicFeatures } = mapdata;
+
+  const { center, scale } = projectionFnParam;
+
+  const projectionFn = d3
+    .geoMercator()
+    .center(center)
+    .scale(scale)
+    .translate([0, 0]);
+
+  const label2dData = []; // 存储自定义 2d 标签数据
+
+  // 每个省的数据
+  basicFeatures.forEach((basicFeatureItem) => {
+    // 每个省份的地图对象
+    const provinceMapObject3D = new THREE.Object3D();
+    // 将地图数据挂在到模型数据上
+    provinceMapObject3D.customProperties = basicFeatureItem.properties;
+
+    // 每个坐标类型
+    const featureType = basicFeatureItem.geometry.type;
+    // 每个坐标数组
+    const featureCoords =
+      basicFeatureItem.geometry.coordinates;
+    // 每个中心点位置
+    const featureCenterCoord =
+      basicFeatureItem.properties.centroid &&
+      projectionFn(basicFeatureItem.properties.centroid);
+    // 名字
+    const featureName = basicFeatureItem.properties.name;
+
+    if (featureCenterCoord) {
+      label2dData.push({
+        featureCenterCoord,
+        featureName,
+      });
+    }
+
+    // MultiPolygon 类型
+    if (featureType === "MultiPolygon") {
+      featureCoords.forEach((multiPolygon) => {
+        multiPolygon.forEach((polygon) => {
+          const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+          provinceMapObject3D.add(mesh);
+          provinceMapObject3D.add(line);
+        });
+      });
+    }
+
+    // Polygon 类型
+    if (featureType === "Polygon") {
+      featureCoords.forEach((polygon) => {
+        const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+        provinceMapObject3D.add(mesh);
+        provinceMapObject3D.add(line);
+      });
+    }
+
+    mapObject3D.add(provinceMapObject3D);
+  });
+
+  return { mapObject3D, label2dData };
+}
+
+// 生成地图2D标签
+export function generateMapLabel2D(label2dData) {
+  const labelObject2D = new THREE.Object3D();
+  label2dData.forEach((item) => {
+    const { featureCenterCoord, featureName } = item;
+    debugger
+    const labelObjectItem = draw2dLabel(featureCenterCoord, featureName);
+    if (labelObjectItem) {
+      labelObject2D.add(labelObjectItem);
+    }
+  });
+  return labelObject2D;
+}
+
+// 生成地图spot点位
+export function generateMapSpot(label2dData) {
+  const spotObject3D = new THREE.Object3D();
+  const spotList = [];
+  label2dData.forEach((item) => {
+    const { featureCenterCoord } = item;
+      const spotObjectItem = drawSpot(featureCenterCoord);
+      if (spotObjectItem && spotObjectItem.circle && spotObjectItem.ring) {
+        spotObject3D.add(spotObjectItem.circle);
+        spotObject3D.add(spotObjectItem.ring);
+        spotList.push(spotObjectItem.ring);
+      }
+
+      //TODO:渲染铁塔
+      
+
+  });
+  return { spotObject3D, spotList };
+}
+
+// 绘制二维标签
+export const draw2dLabel = (coord, proviceName) => {
+  if (coord && coord.length) {
+    // 模版字符串
+    const innerHTML = `<div class="your-classname" style="color: #fff">${proviceName}</div>`;
+    const labelDiv = document.createElement("div");
+    labelDiv.innerHTML = innerHTML;
+    labelDiv.style.pointerEvents = "none"; // 禁用事件
+    const labelObject = new CSS2DObject(labelDiv);
+    labelObject.position.set(coord[0], -coord[1], mapConfig.label2dZIndex);
+    return labelObject;
+  }
+};
+
+// 绘制圆点
+export const drawSpot = (coord) => {
+  if (coord && coord.length) {
+    /**
+     * 绘制圆点
+     */
+    const spotGeometry = new THREE.CircleGeometry(0.2, 200);
+    const spotMaterial = new THREE.MeshBasicMaterial({
+      color: "#3EC5FB",
+      side: THREE.DoubleSide,
+    });
+    const circle = new THREE.Mesh(spotGeometry, spotMaterial);
+    circle.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+
+    // 圆环
+    const ringGeometry = new THREE.RingGeometry(0.2, 0.3, 50);
+    const ringMaterial = new THREE.MeshBasicMaterial({
+      color: "#3FC5FB",
+      side: THREE.DoubleSide,
+      transparent: true,
+    });
+    const ring = new THREE.Mesh(ringGeometry, ringMaterial);
+    ring.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+    return { circle, ring };
+  }
+};
+
+/**
+ * 线上移动物体
+ */
+export const drawflySpot = (curve) => {
+  const aGeo = new THREE.SphereGeometry(0.2);
+  const aMater = new THREE.MeshBasicMaterial({
+    color: "#77f077",
+    side: THREE.DoubleSide,
+  });
+  const aMesh = new THREE.Mesh(aGeo, aMater);
+  // 保存曲线实例
+  aMesh.curve = curve;
+  aMesh._s = 0;
+  return aMesh;
+};
+
+// 绘制两点链接飞线
+export const drawLineBetween2Spot = (
+  coordStart,
+  coordEnd
+) => {
+  const [x0, y0, z0] = [...coordStart, mapConfig.spotZIndex];
+  const [x1, y1, z1] = [...coordEnd, mapConfig.spotZIndex];
+  // 使用 QuadraticBezierCurve3 创建 三维二次贝塞尔曲线
+  const curve = new THREE.QuadraticBezierCurve3(
+    new THREE.Vector3(x0, -y0, z0),
+    new THREE.Vector3((x0 + x1) / 2, -(y0 + y1) / 2, 20),
+    new THREE.Vector3(x1, -y1, z1)
+  );
+
+  const flySpot = drawflySpot(curve);
+
+  const lineGeometry = new THREE.BufferGeometry();
+  // 获取曲线上50个点
+  const points = curve.getPoints(50);
+  const positions = [];
+  const colors = [];
+  const color = new THREE.Color();
+
+  // 给每个顶点设置演示 实现渐变
+  for (let j = 0; j < points.length; j++) {
+    color.setHSL(0.21 + j, 0.77, 0.55 + j * 0.0025); // 色
+    colors.push(color.r, color.g, color.b);
+    positions.push(points[j].x, points[j].y, points[j].z);
+  }
+  // 放入顶点 和 设置顶点颜色
+  lineGeometry.setAttribute(
+    "position",
+    new THREE.BufferAttribute(new Float32Array(positions), 3, true)
+  );
+  lineGeometry.setAttribute(
+    "color",
+    new THREE.BufferAttribute(new Float32Array(colors), 3, true)
+  );
+
+  const material = new THREE.LineBasicMaterial({
+    vertexColors: true,
+    // color: "red",
+    side: THREE.DoubleSide,
+  });
+  const flyLine = new THREE.Line(lineGeometry, material);
+
+  return { flyLine, flySpot };
+};

+ 299 - 0
zhsq_qk-ui/.history/src/map3d/drawFunc_20240617135841.js

@@ -0,0 +1,299 @@
+import * as THREE from "three";
+import * as d3 from "d3";
+import { CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { Line2 } from "three/examples/jsm/lines/Line2";
+import { LineGeometry } from "three/examples/jsm/lines/LineGeometry";
+import { LineMaterial } from "three/examples/jsm/lines/LineMaterial";
+// import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
+
+// import { ProjectionFnParamType } from "./index.vue";
+import { mapConfig } from "./mapConfig";
+
+// 绘制挤出的材质
+export function drawExtrudeMesh(
+  point,
+  projectionFn
+) {
+  const shape = new THREE.Shape();
+  const pointsArray = [];
+
+  for (let i = 0; i < point.length; i++) {
+    const [x, y] = projectionFn(point[i]); // 将每一个经纬度转化为坐标点
+    if (i === 0) {
+      shape.moveTo(x, -y);
+    }
+    shape.lineTo(x, -y);
+    pointsArray.push(x, -y, mapConfig.topLineZIndex);
+  }
+
+  const geometry = new THREE.ExtrudeGeometry(shape, {
+    depth: mapConfig.mapDepth, // 挤出的形状深度
+    bevelEnabled: false, // 对挤出的形状应用是否斜角
+  });
+
+  const material = new THREE.MeshPhongMaterial({
+    // color: mapConfig.mapColor,
+    color: mapConfig.mapColorGradient[Math.floor(Math.random() * 4)], // 随机颜色
+    // transparent: mapConfig.mapTransparent,
+    // opacity: mapConfig.mapOpacity,
+  });
+
+  const materialSide = new THREE.ShaderMaterial({
+    uniforms: {
+      color1: {
+        value: new THREE.Color(mapConfig.mapSideColor1),
+      },
+      color2: {
+        value: new THREE.Color(mapConfig.mapSideColor2),
+      },
+    },
+    vertexShader: `
+      varying vec3 vPosition;
+      void main() {
+        vPosition = position;
+        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+      }
+    `,
+    fragmentShader: `
+      uniform vec3 color1;
+      uniform vec3 color2;
+      varying vec3 vPosition;
+      void main() {
+        vec3 mixColor = mix(color1, color2, 0.5 - vPosition.z * 0.2); // 使用顶点坐标 z 分量来控制混合
+        gl_FragColor = vec4(mixColor, 1.0);
+      }
+    `,
+    //   wireframe: true,
+  });
+
+  const mesh = new THREE.Mesh(geometry, [material, materialSide]);
+  // userData 存储自定义属性
+  mesh.userData = {
+    isChangeColor: true,
+  };
+
+  // 边框线,赋值空间点坐标,3个一组
+  const lineGeometry = new LineGeometry();
+  lineGeometry.setPositions(pointsArray);
+
+  const lineMaterial = new LineMaterial({
+    color: mapConfig.topLineColor,
+    linewidth: mapConfig.topLineWidth,
+  });
+  lineMaterial.resolution.set(window.innerWidth, window.innerHeight);
+  const line = new Line2(lineGeometry, lineMaterial);
+
+  return { mesh, line };
+}
+
+// 生成地图3D模型
+export function generateMapObject3D(
+  mapdata,
+  projectionFnParam
+) {
+  // 地图对象
+  const mapObject3D = new THREE.Object3D();
+  // 地图数据
+  const basicFeatures = mapdata.features;
+
+  const { center, scale } = projectionFnParam;
+
+  const projectionFn = d3
+    .geoMercator()
+    .center(center)
+    .scale(scale)
+    .translate([0, 0]);
+
+  const label2dData = []; // 存储自定义 2d 标签数据
+
+  // 每个省的数据
+  basicFeatures.forEach((basicFeatureItem) => {
+    // 每个省份的地图对象
+    const provinceMapObject3D = new THREE.Object3D();
+    // 将地图数据挂在到模型数据上
+    provinceMapObject3D.customProperties = basicFeatureItem.properties;
+
+    // 每个坐标类型
+    const featureType = basicFeatureItem.geometry.type;
+    // 每个坐标数组
+    const featureCoords =
+      basicFeatureItem.geometry.coordinates;
+    // 每个中心点位置
+    const featureCenterCoord =
+      basicFeatureItem.properties.centroid &&
+      projectionFn(basicFeatureItem.properties.centroid);
+    // 名字
+    const featureName = basicFeatureItem.properties.name;
+
+    if (featureCenterCoord) {
+      label2dData.push({
+        featureCenterCoord,
+        featureName,
+      });
+    }
+
+    // MultiPolygon 类型
+    if (featureType === "MultiPolygon") {
+      featureCoords.forEach((multiPolygon) => {
+        multiPolygon.forEach((polygon) => {
+          const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+          provinceMapObject3D.add(mesh);
+          provinceMapObject3D.add(line);
+        });
+      });
+    }
+
+    // Polygon 类型
+    if (featureType === "Polygon") {
+      featureCoords.forEach((polygon) => {
+        const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+        provinceMapObject3D.add(mesh);
+        provinceMapObject3D.add(line);
+      });
+    }
+
+    mapObject3D.add(provinceMapObject3D);
+  });
+
+  return { mapObject3D, label2dData };
+}
+
+// 生成地图2D标签
+export function generateMapLabel2D(label2dData) {
+  const labelObject2D = new THREE.Object3D();
+  label2dData.forEach((item) => {
+    const { featureCenterCoord, featureName } = item;
+    debugger
+    const labelObjectItem = draw2dLabel(featureCenterCoord, featureName);
+    if (labelObjectItem) {
+      labelObject2D.add(labelObjectItem);
+    }
+  });
+  return labelObject2D;
+}
+
+// 生成地图spot点位
+export function generateMapSpot(label2dData) {
+  const spotObject3D = new THREE.Object3D();
+  const spotList = [];
+  label2dData.forEach((item) => {
+    const { featureCenterCoord } = item;
+      const spotObjectItem = drawSpot(featureCenterCoord);
+      if (spotObjectItem && spotObjectItem.circle && spotObjectItem.ring) {
+        spotObject3D.add(spotObjectItem.circle);
+        spotObject3D.add(spotObjectItem.ring);
+        spotList.push(spotObjectItem.ring);
+      }
+
+      //TODO:渲染铁塔
+      
+
+  });
+  return { spotObject3D, spotList };
+}
+
+// 绘制二维标签
+export const draw2dLabel = (coord, proviceName) => {
+  if (coord && coord.length) {
+    // 模版字符串
+    const innerHTML = `<div class="your-classname" style="color: #fff">${proviceName}</div>`;
+    const labelDiv = document.createElement("div");
+    labelDiv.innerHTML = innerHTML;
+    labelDiv.style.pointerEvents = "none"; // 禁用事件
+    const labelObject = new CSS2DObject(labelDiv);
+    labelObject.position.set(coord[0], -coord[1], mapConfig.label2dZIndex);
+    return labelObject;
+  }
+};
+
+// 绘制圆点
+export const drawSpot = (coord) => {
+  if (coord && coord.length) {
+    /**
+     * 绘制圆点
+     */
+    const spotGeometry = new THREE.CircleGeometry(0.2, 200);
+    const spotMaterial = new THREE.MeshBasicMaterial({
+      color: "#3EC5FB",
+      side: THREE.DoubleSide,
+    });
+    const circle = new THREE.Mesh(spotGeometry, spotMaterial);
+    circle.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+
+    // 圆环
+    const ringGeometry = new THREE.RingGeometry(0.2, 0.3, 50);
+    const ringMaterial = new THREE.MeshBasicMaterial({
+      color: "#3FC5FB",
+      side: THREE.DoubleSide,
+      transparent: true,
+    });
+    const ring = new THREE.Mesh(ringGeometry, ringMaterial);
+    ring.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+    return { circle, ring };
+  }
+};
+
+/**
+ * 线上移动物体
+ */
+export const drawflySpot = (curve) => {
+  const aGeo = new THREE.SphereGeometry(0.2);
+  const aMater = new THREE.MeshBasicMaterial({
+    color: "#77f077",
+    side: THREE.DoubleSide,
+  });
+  const aMesh = new THREE.Mesh(aGeo, aMater);
+  // 保存曲线实例
+  aMesh.curve = curve;
+  aMesh._s = 0;
+  return aMesh;
+};
+
+// 绘制两点链接飞线
+export const drawLineBetween2Spot = (
+  coordStart,
+  coordEnd
+) => {
+  const [x0, y0, z0] = [...coordStart, mapConfig.spotZIndex];
+  const [x1, y1, z1] = [...coordEnd, mapConfig.spotZIndex];
+  // 使用 QuadraticBezierCurve3 创建 三维二次贝塞尔曲线
+  const curve = new THREE.QuadraticBezierCurve3(
+    new THREE.Vector3(x0, -y0, z0),
+    new THREE.Vector3((x0 + x1) / 2, -(y0 + y1) / 2, 20),
+    new THREE.Vector3(x1, -y1, z1)
+  );
+
+  const flySpot = drawflySpot(curve);
+
+  const lineGeometry = new THREE.BufferGeometry();
+  // 获取曲线上50个点
+  const points = curve.getPoints(50);
+  const positions = [];
+  const colors = [];
+  const color = new THREE.Color();
+
+  // 给每个顶点设置演示 实现渐变
+  for (let j = 0; j < points.length; j++) {
+    color.setHSL(0.21 + j, 0.77, 0.55 + j * 0.0025); // 色
+    colors.push(color.r, color.g, color.b);
+    positions.push(points[j].x, points[j].y, points[j].z);
+  }
+  // 放入顶点 和 设置顶点颜色
+  lineGeometry.setAttribute(
+    "position",
+    new THREE.BufferAttribute(new Float32Array(positions), 3, true)
+  );
+  lineGeometry.setAttribute(
+    "color",
+    new THREE.BufferAttribute(new Float32Array(colors), 3, true)
+  );
+
+  const material = new THREE.LineBasicMaterial({
+    vertexColors: true,
+    // color: "red",
+    side: THREE.DoubleSide,
+  });
+  const flyLine = new THREE.Line(lineGeometry, material);
+
+  return { flyLine, flySpot };
+};

+ 300 - 0
zhsq_qk-ui/.history/src/map3d/drawFunc_20240617135917.js

@@ -0,0 +1,300 @@
+import * as THREE from "three";
+import * as d3 from "d3";
+import { CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { Line2 } from "three/examples/jsm/lines/Line2";
+import { LineGeometry } from "three/examples/jsm/lines/LineGeometry";
+import { LineMaterial } from "three/examples/jsm/lines/LineMaterial";
+// import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
+
+// import { ProjectionFnParamType } from "./index.vue";
+import { mapConfig } from "./mapConfig";
+
+// 绘制挤出的材质
+export function drawExtrudeMesh(
+  point,
+  projectionFn
+) {
+  const shape = new THREE.Shape();
+  const pointsArray = [];
+
+  for (let i = 0; i < point.length; i++) {
+    const [x, y] = projectionFn(point[i]); // 将每一个经纬度转化为坐标点
+    if (i === 0) {
+      shape.moveTo(x, -y);
+    }
+    shape.lineTo(x, -y);
+    pointsArray.push(x, -y, mapConfig.topLineZIndex);
+  }
+
+  const geometry = new THREE.ExtrudeGeometry(shape, {
+    depth: mapConfig.mapDepth, // 挤出的形状深度
+    bevelEnabled: false, // 对挤出的形状应用是否斜角
+  });
+
+  const material = new THREE.MeshPhongMaterial({
+    // color: mapConfig.mapColor,
+    color: mapConfig.mapColorGradient[Math.floor(Math.random() * 4)], // 随机颜色
+    // transparent: mapConfig.mapTransparent,
+    // opacity: mapConfig.mapOpacity,
+  });
+
+  const materialSide = new THREE.ShaderMaterial({
+    uniforms: {
+      color1: {
+        value: new THREE.Color(mapConfig.mapSideColor1),
+      },
+      color2: {
+        value: new THREE.Color(mapConfig.mapSideColor2),
+      },
+    },
+    vertexShader: `
+      varying vec3 vPosition;
+      void main() {
+        vPosition = position;
+        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+      }
+    `,
+    fragmentShader: `
+      uniform vec3 color1;
+      uniform vec3 color2;
+      varying vec3 vPosition;
+      void main() {
+        vec3 mixColor = mix(color1, color2, 0.5 - vPosition.z * 0.2); // 使用顶点坐标 z 分量来控制混合
+        gl_FragColor = vec4(mixColor, 1.0);
+      }
+    `,
+    //   wireframe: true,
+  });
+
+  const mesh = new THREE.Mesh(geometry, [material, materialSide]);
+  // userData 存储自定义属性
+  mesh.userData = {
+    isChangeColor: true,
+  };
+
+  // 边框线,赋值空间点坐标,3个一组
+  const lineGeometry = new LineGeometry();
+  lineGeometry.setPositions(pointsArray);
+
+  const lineMaterial = new LineMaterial({
+    color: mapConfig.topLineColor,
+    linewidth: mapConfig.topLineWidth,
+  });
+  lineMaterial.resolution.set(window.innerWidth, window.innerHeight);
+  const line = new Line2(lineGeometry, lineMaterial);
+
+  return { mesh, line };
+}
+
+// 生成地图3D模型
+export function generateMapObject3D(
+  mapdata,
+  projectionFnParam
+) {
+  // 地图对象
+  const mapObject3D = new THREE.Object3D();
+  // 地图数据
+  console.log('asasas',mapdata)
+  const basicFeatures = mapdata.features;
+
+  const { center, scale } = projectionFnParam;
+
+  const projectionFn = d3
+    .geoMercator()
+    .center(center)
+    .scale(scale)
+    .translate([0, 0]);
+
+  const label2dData = []; // 存储自定义 2d 标签数据
+
+  // 每个省的数据
+  basicFeatures.forEach((basicFeatureItem) => {
+    // 每个省份的地图对象
+    const provinceMapObject3D = new THREE.Object3D();
+    // 将地图数据挂在到模型数据上
+    provinceMapObject3D.customProperties = basicFeatureItem.properties;
+
+    // 每个坐标类型
+    const featureType = basicFeatureItem.geometry.type;
+    // 每个坐标数组
+    const featureCoords =
+      basicFeatureItem.geometry.coordinates;
+    // 每个中心点位置
+    const featureCenterCoord =
+      basicFeatureItem.properties.centroid &&
+      projectionFn(basicFeatureItem.properties.centroid);
+    // 名字
+    const featureName = basicFeatureItem.properties.name;
+
+    if (featureCenterCoord) {
+      label2dData.push({
+        featureCenterCoord,
+        featureName,
+      });
+    }
+
+    // MultiPolygon 类型
+    if (featureType === "MultiPolygon") {
+      featureCoords.forEach((multiPolygon) => {
+        multiPolygon.forEach((polygon) => {
+          const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+          provinceMapObject3D.add(mesh);
+          provinceMapObject3D.add(line);
+        });
+      });
+    }
+
+    // Polygon 类型
+    if (featureType === "Polygon") {
+      featureCoords.forEach((polygon) => {
+        const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+        provinceMapObject3D.add(mesh);
+        provinceMapObject3D.add(line);
+      });
+    }
+
+    mapObject3D.add(provinceMapObject3D);
+  });
+
+  return { mapObject3D, label2dData };
+}
+
+// 生成地图2D标签
+export function generateMapLabel2D(label2dData) {
+  const labelObject2D = new THREE.Object3D();
+  label2dData.forEach((item) => {
+    const { featureCenterCoord, featureName } = item;
+    debugger
+    const labelObjectItem = draw2dLabel(featureCenterCoord, featureName);
+    if (labelObjectItem) {
+      labelObject2D.add(labelObjectItem);
+    }
+  });
+  return labelObject2D;
+}
+
+// 生成地图spot点位
+export function generateMapSpot(label2dData) {
+  const spotObject3D = new THREE.Object3D();
+  const spotList = [];
+  label2dData.forEach((item) => {
+    const { featureCenterCoord } = item;
+      const spotObjectItem = drawSpot(featureCenterCoord);
+      if (spotObjectItem && spotObjectItem.circle && spotObjectItem.ring) {
+        spotObject3D.add(spotObjectItem.circle);
+        spotObject3D.add(spotObjectItem.ring);
+        spotList.push(spotObjectItem.ring);
+      }
+
+      //TODO:渲染铁塔
+      
+
+  });
+  return { spotObject3D, spotList };
+}
+
+// 绘制二维标签
+export const draw2dLabel = (coord, proviceName) => {
+  if (coord && coord.length) {
+    // 模版字符串
+    const innerHTML = `<div class="your-classname" style="color: #fff">${proviceName}</div>`;
+    const labelDiv = document.createElement("div");
+    labelDiv.innerHTML = innerHTML;
+    labelDiv.style.pointerEvents = "none"; // 禁用事件
+    const labelObject = new CSS2DObject(labelDiv);
+    labelObject.position.set(coord[0], -coord[1], mapConfig.label2dZIndex);
+    return labelObject;
+  }
+};
+
+// 绘制圆点
+export const drawSpot = (coord) => {
+  if (coord && coord.length) {
+    /**
+     * 绘制圆点
+     */
+    const spotGeometry = new THREE.CircleGeometry(0.2, 200);
+    const spotMaterial = new THREE.MeshBasicMaterial({
+      color: "#3EC5FB",
+      side: THREE.DoubleSide,
+    });
+    const circle = new THREE.Mesh(spotGeometry, spotMaterial);
+    circle.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+
+    // 圆环
+    const ringGeometry = new THREE.RingGeometry(0.2, 0.3, 50);
+    const ringMaterial = new THREE.MeshBasicMaterial({
+      color: "#3FC5FB",
+      side: THREE.DoubleSide,
+      transparent: true,
+    });
+    const ring = new THREE.Mesh(ringGeometry, ringMaterial);
+    ring.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+    return { circle, ring };
+  }
+};
+
+/**
+ * 线上移动物体
+ */
+export const drawflySpot = (curve) => {
+  const aGeo = new THREE.SphereGeometry(0.2);
+  const aMater = new THREE.MeshBasicMaterial({
+    color: "#77f077",
+    side: THREE.DoubleSide,
+  });
+  const aMesh = new THREE.Mesh(aGeo, aMater);
+  // 保存曲线实例
+  aMesh.curve = curve;
+  aMesh._s = 0;
+  return aMesh;
+};
+
+// 绘制两点链接飞线
+export const drawLineBetween2Spot = (
+  coordStart,
+  coordEnd
+) => {
+  const [x0, y0, z0] = [...coordStart, mapConfig.spotZIndex];
+  const [x1, y1, z1] = [...coordEnd, mapConfig.spotZIndex];
+  // 使用 QuadraticBezierCurve3 创建 三维二次贝塞尔曲线
+  const curve = new THREE.QuadraticBezierCurve3(
+    new THREE.Vector3(x0, -y0, z0),
+    new THREE.Vector3((x0 + x1) / 2, -(y0 + y1) / 2, 20),
+    new THREE.Vector3(x1, -y1, z1)
+  );
+
+  const flySpot = drawflySpot(curve);
+
+  const lineGeometry = new THREE.BufferGeometry();
+  // 获取曲线上50个点
+  const points = curve.getPoints(50);
+  const positions = [];
+  const colors = [];
+  const color = new THREE.Color();
+
+  // 给每个顶点设置演示 实现渐变
+  for (let j = 0; j < points.length; j++) {
+    color.setHSL(0.21 + j, 0.77, 0.55 + j * 0.0025); // 色
+    colors.push(color.r, color.g, color.b);
+    positions.push(points[j].x, points[j].y, points[j].z);
+  }
+  // 放入顶点 和 设置顶点颜色
+  lineGeometry.setAttribute(
+    "position",
+    new THREE.BufferAttribute(new Float32Array(positions), 3, true)
+  );
+  lineGeometry.setAttribute(
+    "color",
+    new THREE.BufferAttribute(new Float32Array(colors), 3, true)
+  );
+
+  const material = new THREE.LineBasicMaterial({
+    vertexColors: true,
+    // color: "red",
+    side: THREE.DoubleSide,
+  });
+  const flyLine = new THREE.Line(lineGeometry, material);
+
+  return { flyLine, flySpot };
+};

+ 301 - 0
zhsq_qk-ui/.history/src/map3d/drawFunc_20240617140209.js

@@ -0,0 +1,301 @@
+import * as THREE from "three";
+import * as d3 from "d3";
+import { CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { Line2 } from "three/examples/jsm/lines/Line2";
+import { LineGeometry } from "three/examples/jsm/lines/LineGeometry";
+import { LineMaterial } from "three/examples/jsm/lines/LineMaterial";
+// import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
+
+// import { ProjectionFnParamType } from "./index.vue";
+import { mapConfig } from "./mapConfig";
+
+// 绘制挤出的材质
+export function drawExtrudeMesh(
+  point,
+  projectionFn
+) {
+  const shape = new THREE.Shape();
+  const pointsArray = [];
+
+  for (let i = 0; i < point.length; i++) {
+    const [x, y] = projectionFn(point[i]); // 将每一个经纬度转化为坐标点
+    if (i === 0) {
+      shape.moveTo(x, -y);
+    }
+    shape.lineTo(x, -y);
+    pointsArray.push(x, -y, mapConfig.topLineZIndex);
+  }
+
+  const geometry = new THREE.ExtrudeGeometry(shape, {
+    depth: mapConfig.mapDepth, // 挤出的形状深度
+    bevelEnabled: false, // 对挤出的形状应用是否斜角
+  });
+
+  const material = new THREE.MeshPhongMaterial({
+    // color: mapConfig.mapColor,
+    color: mapConfig.mapColorGradient[Math.floor(Math.random() * 4)], // 随机颜色
+    // transparent: mapConfig.mapTransparent,
+    // opacity: mapConfig.mapOpacity,
+  });
+
+  const materialSide = new THREE.ShaderMaterial({
+    uniforms: {
+      color1: {
+        value: new THREE.Color(mapConfig.mapSideColor1),
+      },
+      color2: {
+        value: new THREE.Color(mapConfig.mapSideColor2),
+      },
+    },
+    vertexShader: `
+      varying vec3 vPosition;
+      void main() {
+        vPosition = position;
+        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+      }
+    `,
+    fragmentShader: `
+      uniform vec3 color1;
+      uniform vec3 color2;
+      varying vec3 vPosition;
+      void main() {
+        vec3 mixColor = mix(color1, color2, 0.5 - vPosition.z * 0.2); // 使用顶点坐标 z 分量来控制混合
+        gl_FragColor = vec4(mixColor, 1.0);
+      }
+    `,
+    //   wireframe: true,
+  });
+
+  const mesh = new THREE.Mesh(geometry, [material, materialSide]);
+  // userData 存储自定义属性
+  mesh.userData = {
+    isChangeColor: true,
+  };
+
+  // 边框线,赋值空间点坐标,3个一组
+  const lineGeometry = new LineGeometry();
+  lineGeometry.setPositions(pointsArray);
+
+  const lineMaterial = new LineMaterial({
+    color: mapConfig.topLineColor,
+    linewidth: mapConfig.topLineWidth,
+  });
+  lineMaterial.resolution.set(window.innerWidth, window.innerHeight);
+  const line = new Line2(lineGeometry, lineMaterial);
+
+  return { mesh, line };
+}
+
+// 生成地图3D模型
+export function generateMapObject3D(
+  mapdata,
+  projectionFnParam
+) {
+  // 地图对象
+  const mapObject3D = new THREE.Object3D();
+  // 地图数据
+  console.log('asasas',mapdata)
+  const basicFeatures = mapdata.features;
+  console.log('basicFeatures',basicFeatures)
+
+  const { center, scale } = projectionFnParam;
+
+  const projectionFn = d3
+    .geoMercator()
+    .center(center)
+    .scale(scale)
+    .translate([0, 0]);
+
+  const label2dData = []; // 存储自定义 2d 标签数据
+
+  // 每个省的数据
+  basicFeatures.forEach((basicFeatureItem) => {
+    // 每个省份的地图对象
+    const provinceMapObject3D = new THREE.Object3D();
+    // 将地图数据挂在到模型数据上
+    provinceMapObject3D.customProperties = basicFeatureItem.properties;
+
+    // 每个坐标类型
+    const featureType = basicFeatureItem.geometry.type;
+    // 每个坐标数组
+    const featureCoords =
+      basicFeatureItem.geometry.coordinates;
+    // 每个中心点位置
+    const featureCenterCoord =
+      basicFeatureItem.properties.centroid &&
+      projectionFn(basicFeatureItem.properties.centroid);
+    // 名字
+    const featureName = basicFeatureItem.properties.name;
+
+    if (featureCenterCoord) {
+      label2dData.push({
+        featureCenterCoord,
+        featureName,
+      });
+    }
+
+    // MultiPolygon 类型
+    if (featureType === "MultiPolygon") {
+      featureCoords.forEach((multiPolygon) => {
+        multiPolygon.forEach((polygon) => {
+          const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+          provinceMapObject3D.add(mesh);
+          provinceMapObject3D.add(line);
+        });
+      });
+    }
+
+    // Polygon 类型
+    if (featureType === "Polygon") {
+      featureCoords.forEach((polygon) => {
+        const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+        provinceMapObject3D.add(mesh);
+        provinceMapObject3D.add(line);
+      });
+    }
+
+    mapObject3D.add(provinceMapObject3D);
+  });
+
+  return { mapObject3D, label2dData };
+}
+
+// 生成地图2D标签
+export function generateMapLabel2D(label2dData) {
+  const labelObject2D = new THREE.Object3D();
+  label2dData.forEach((item) => {
+    const { featureCenterCoord, featureName } = item;
+    debugger
+    const labelObjectItem = draw2dLabel(featureCenterCoord, featureName);
+    if (labelObjectItem) {
+      labelObject2D.add(labelObjectItem);
+    }
+  });
+  return labelObject2D;
+}
+
+// 生成地图spot点位
+export function generateMapSpot(label2dData) {
+  const spotObject3D = new THREE.Object3D();
+  const spotList = [];
+  label2dData.forEach((item) => {
+    const { featureCenterCoord } = item;
+      const spotObjectItem = drawSpot(featureCenterCoord);
+      if (spotObjectItem && spotObjectItem.circle && spotObjectItem.ring) {
+        spotObject3D.add(spotObjectItem.circle);
+        spotObject3D.add(spotObjectItem.ring);
+        spotList.push(spotObjectItem.ring);
+      }
+
+      //TODO:渲染铁塔
+      
+
+  });
+  return { spotObject3D, spotList };
+}
+
+// 绘制二维标签
+export const draw2dLabel = (coord, proviceName) => {
+  if (coord && coord.length) {
+    // 模版字符串
+    const innerHTML = `<div class="your-classname" style="color: #fff">${proviceName}</div>`;
+    const labelDiv = document.createElement("div");
+    labelDiv.innerHTML = innerHTML;
+    labelDiv.style.pointerEvents = "none"; // 禁用事件
+    const labelObject = new CSS2DObject(labelDiv);
+    labelObject.position.set(coord[0], -coord[1], mapConfig.label2dZIndex);
+    return labelObject;
+  }
+};
+
+// 绘制圆点
+export const drawSpot = (coord) => {
+  if (coord && coord.length) {
+    /**
+     * 绘制圆点
+     */
+    const spotGeometry = new THREE.CircleGeometry(0.2, 200);
+    const spotMaterial = new THREE.MeshBasicMaterial({
+      color: "#3EC5FB",
+      side: THREE.DoubleSide,
+    });
+    const circle = new THREE.Mesh(spotGeometry, spotMaterial);
+    circle.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+
+    // 圆环
+    const ringGeometry = new THREE.RingGeometry(0.2, 0.3, 50);
+    const ringMaterial = new THREE.MeshBasicMaterial({
+      color: "#3FC5FB",
+      side: THREE.DoubleSide,
+      transparent: true,
+    });
+    const ring = new THREE.Mesh(ringGeometry, ringMaterial);
+    ring.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+    return { circle, ring };
+  }
+};
+
+/**
+ * 线上移动物体
+ */
+export const drawflySpot = (curve) => {
+  const aGeo = new THREE.SphereGeometry(0.2);
+  const aMater = new THREE.MeshBasicMaterial({
+    color: "#77f077",
+    side: THREE.DoubleSide,
+  });
+  const aMesh = new THREE.Mesh(aGeo, aMater);
+  // 保存曲线实例
+  aMesh.curve = curve;
+  aMesh._s = 0;
+  return aMesh;
+};
+
+// 绘制两点链接飞线
+export const drawLineBetween2Spot = (
+  coordStart,
+  coordEnd
+) => {
+  const [x0, y0, z0] = [...coordStart, mapConfig.spotZIndex];
+  const [x1, y1, z1] = [...coordEnd, mapConfig.spotZIndex];
+  // 使用 QuadraticBezierCurve3 创建 三维二次贝塞尔曲线
+  const curve = new THREE.QuadraticBezierCurve3(
+    new THREE.Vector3(x0, -y0, z0),
+    new THREE.Vector3((x0 + x1) / 2, -(y0 + y1) / 2, 20),
+    new THREE.Vector3(x1, -y1, z1)
+  );
+
+  const flySpot = drawflySpot(curve);
+
+  const lineGeometry = new THREE.BufferGeometry();
+  // 获取曲线上50个点
+  const points = curve.getPoints(50);
+  const positions = [];
+  const colors = [];
+  const color = new THREE.Color();
+
+  // 给每个顶点设置演示 实现渐变
+  for (let j = 0; j < points.length; j++) {
+    color.setHSL(0.21 + j, 0.77, 0.55 + j * 0.0025); // 色
+    colors.push(color.r, color.g, color.b);
+    positions.push(points[j].x, points[j].y, points[j].z);
+  }
+  // 放入顶点 和 设置顶点颜色
+  lineGeometry.setAttribute(
+    "position",
+    new THREE.BufferAttribute(new Float32Array(positions), 3, true)
+  );
+  lineGeometry.setAttribute(
+    "color",
+    new THREE.BufferAttribute(new Float32Array(colors), 3, true)
+  );
+
+  const material = new THREE.LineBasicMaterial({
+    vertexColors: true,
+    // color: "red",
+    side: THREE.DoubleSide,
+  });
+  const flyLine = new THREE.Line(lineGeometry, material);
+
+  return { flyLine, flySpot };
+};

+ 301 - 0
zhsq_qk-ui/.history/src/map3d/drawFunc_20240617140355.js

@@ -0,0 +1,301 @@
+import * as THREE from "three";
+import * as d3 from "d3";
+import { CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { Line2 } from "three/examples/jsm/lines/Line2";
+import { LineGeometry } from "three/examples/jsm/lines/LineGeometry";
+import { LineMaterial } from "three/examples/jsm/lines/LineMaterial";
+// import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
+
+// import { ProjectionFnParamType } from "./index.vue";
+import { mapConfig } from "./mapConfig";
+
+// 绘制挤出的材质
+export function drawExtrudeMesh(
+  point,
+  projectionFn
+) {
+  const shape = new THREE.Shape();
+  const pointsArray = [];
+
+  for (let i = 0; i < point.length; i++) {
+    const [x, y] = projectionFn(point[i]); // 将每一个经纬度转化为坐标点
+    if (i === 0) {
+      shape.moveTo(x, -y);
+    }
+    shape.lineTo(x, -y);
+    pointsArray.push(x, -y, mapConfig.topLineZIndex);
+  }
+
+  const geometry = new THREE.ExtrudeGeometry(shape, {
+    depth: mapConfig.mapDepth, // 挤出的形状深度
+    bevelEnabled: false, // 对挤出的形状应用是否斜角
+  });
+
+  const material = new THREE.MeshPhongMaterial({
+    // color: mapConfig.mapColor,
+    color: mapConfig.mapColorGradient[Math.floor(Math.random() * 4)], // 随机颜色
+    // transparent: mapConfig.mapTransparent,
+    // opacity: mapConfig.mapOpacity,
+  });
+
+  const materialSide = new THREE.ShaderMaterial({
+    uniforms: {
+      color1: {
+        value: new THREE.Color(mapConfig.mapSideColor1),
+      },
+      color2: {
+        value: new THREE.Color(mapConfig.mapSideColor2),
+      },
+    },
+    vertexShader: `
+      varying vec3 vPosition;
+      void main() {
+        vPosition = position;
+        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+      }
+    `,
+    fragmentShader: `
+      uniform vec3 color1;
+      uniform vec3 color2;
+      varying vec3 vPosition;
+      void main() {
+        vec3 mixColor = mix(color1, color2, 0.5 - vPosition.z * 0.2); // 使用顶点坐标 z 分量来控制混合
+        gl_FragColor = vec4(mixColor, 1.0);
+      }
+    `,
+    //   wireframe: true,
+  });
+
+  const mesh = new THREE.Mesh(geometry, [material, materialSide]);
+  // userData 存储自定义属性
+  mesh.userData = {
+    isChangeColor: true,
+  };
+
+  // 边框线,赋值空间点坐标,3个一组
+  const lineGeometry = new LineGeometry();
+  lineGeometry.setPositions(pointsArray);
+
+  const lineMaterial = new LineMaterial({
+    color: mapConfig.topLineColor,
+    linewidth: mapConfig.topLineWidth,
+  });
+  lineMaterial.resolution.set(window.innerWidth, window.innerHeight);
+  const line = new Line2(lineGeometry, lineMaterial);
+
+  return { mesh, line };
+}
+
+// 生成地图3D模型
+export function generateMapObject3D(
+  mapdata,
+  projectionFnParam
+) {
+  // 地图对象
+  const mapObject3D = new THREE.Object3D();
+  // 地图数据
+  const mapdataAssign = Object.assign(mapdata,{})
+  const basicFeatures = mapdataAssign.features;
+  console.log('basicFeatures',basicFeatures)
+
+  const { center, scale } = projectionFnParam;
+
+  const projectionFn = d3
+    .geoMercator()
+    .center(center)
+    .scale(scale)
+    .translate([0, 0]);
+
+  const label2dData = []; // 存储自定义 2d 标签数据
+
+  // 每个省的数据
+  basicFeatures.forEach((basicFeatureItem) => {
+    // 每个省份的地图对象
+    const provinceMapObject3D = new THREE.Object3D();
+    // 将地图数据挂在到模型数据上
+    provinceMapObject3D.customProperties = basicFeatureItem.properties;
+
+    // 每个坐标类型
+    const featureType = basicFeatureItem.geometry.type;
+    // 每个坐标数组
+    const featureCoords =
+      basicFeatureItem.geometry.coordinates;
+    // 每个中心点位置
+    const featureCenterCoord =
+      basicFeatureItem.properties.centroid &&
+      projectionFn(basicFeatureItem.properties.centroid);
+    // 名字
+    const featureName = basicFeatureItem.properties.name;
+
+    if (featureCenterCoord) {
+      label2dData.push({
+        featureCenterCoord,
+        featureName,
+      });
+    }
+
+    // MultiPolygon 类型
+    if (featureType === "MultiPolygon") {
+      featureCoords.forEach((multiPolygon) => {
+        multiPolygon.forEach((polygon) => {
+          const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+          provinceMapObject3D.add(mesh);
+          provinceMapObject3D.add(line);
+        });
+      });
+    }
+
+    // Polygon 类型
+    if (featureType === "Polygon") {
+      featureCoords.forEach((polygon) => {
+        const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+        provinceMapObject3D.add(mesh);
+        provinceMapObject3D.add(line);
+      });
+    }
+
+    mapObject3D.add(provinceMapObject3D);
+  });
+
+  return { mapObject3D, label2dData };
+}
+
+// 生成地图2D标签
+export function generateMapLabel2D(label2dData) {
+  const labelObject2D = new THREE.Object3D();
+  label2dData.forEach((item) => {
+    const { featureCenterCoord, featureName } = item;
+    debugger
+    const labelObjectItem = draw2dLabel(featureCenterCoord, featureName);
+    if (labelObjectItem) {
+      labelObject2D.add(labelObjectItem);
+    }
+  });
+  return labelObject2D;
+}
+
+// 生成地图spot点位
+export function generateMapSpot(label2dData) {
+  const spotObject3D = new THREE.Object3D();
+  const spotList = [];
+  label2dData.forEach((item) => {
+    const { featureCenterCoord } = item;
+      const spotObjectItem = drawSpot(featureCenterCoord);
+      if (spotObjectItem && spotObjectItem.circle && spotObjectItem.ring) {
+        spotObject3D.add(spotObjectItem.circle);
+        spotObject3D.add(spotObjectItem.ring);
+        spotList.push(spotObjectItem.ring);
+      }
+
+      //TODO:渲染铁塔
+      
+
+  });
+  return { spotObject3D, spotList };
+}
+
+// 绘制二维标签
+export const draw2dLabel = (coord, proviceName) => {
+  if (coord && coord.length) {
+    // 模版字符串
+    const innerHTML = `<div class="your-classname" style="color: #fff">${proviceName}</div>`;
+    const labelDiv = document.createElement("div");
+    labelDiv.innerHTML = innerHTML;
+    labelDiv.style.pointerEvents = "none"; // 禁用事件
+    const labelObject = new CSS2DObject(labelDiv);
+    labelObject.position.set(coord[0], -coord[1], mapConfig.label2dZIndex);
+    return labelObject;
+  }
+};
+
+// 绘制圆点
+export const drawSpot = (coord) => {
+  if (coord && coord.length) {
+    /**
+     * 绘制圆点
+     */
+    const spotGeometry = new THREE.CircleGeometry(0.2, 200);
+    const spotMaterial = new THREE.MeshBasicMaterial({
+      color: "#3EC5FB",
+      side: THREE.DoubleSide,
+    });
+    const circle = new THREE.Mesh(spotGeometry, spotMaterial);
+    circle.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+
+    // 圆环
+    const ringGeometry = new THREE.RingGeometry(0.2, 0.3, 50);
+    const ringMaterial = new THREE.MeshBasicMaterial({
+      color: "#3FC5FB",
+      side: THREE.DoubleSide,
+      transparent: true,
+    });
+    const ring = new THREE.Mesh(ringGeometry, ringMaterial);
+    ring.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+    return { circle, ring };
+  }
+};
+
+/**
+ * 线上移动物体
+ */
+export const drawflySpot = (curve) => {
+  const aGeo = new THREE.SphereGeometry(0.2);
+  const aMater = new THREE.MeshBasicMaterial({
+    color: "#77f077",
+    side: THREE.DoubleSide,
+  });
+  const aMesh = new THREE.Mesh(aGeo, aMater);
+  // 保存曲线实例
+  aMesh.curve = curve;
+  aMesh._s = 0;
+  return aMesh;
+};
+
+// 绘制两点链接飞线
+export const drawLineBetween2Spot = (
+  coordStart,
+  coordEnd
+) => {
+  const [x0, y0, z0] = [...coordStart, mapConfig.spotZIndex];
+  const [x1, y1, z1] = [...coordEnd, mapConfig.spotZIndex];
+  // 使用 QuadraticBezierCurve3 创建 三维二次贝塞尔曲线
+  const curve = new THREE.QuadraticBezierCurve3(
+    new THREE.Vector3(x0, -y0, z0),
+    new THREE.Vector3((x0 + x1) / 2, -(y0 + y1) / 2, 20),
+    new THREE.Vector3(x1, -y1, z1)
+  );
+
+  const flySpot = drawflySpot(curve);
+
+  const lineGeometry = new THREE.BufferGeometry();
+  // 获取曲线上50个点
+  const points = curve.getPoints(50);
+  const positions = [];
+  const colors = [];
+  const color = new THREE.Color();
+
+  // 给每个顶点设置演示 实现渐变
+  for (let j = 0; j < points.length; j++) {
+    color.setHSL(0.21 + j, 0.77, 0.55 + j * 0.0025); // 色
+    colors.push(color.r, color.g, color.b);
+    positions.push(points[j].x, points[j].y, points[j].z);
+  }
+  // 放入顶点 和 设置顶点颜色
+  lineGeometry.setAttribute(
+    "position",
+    new THREE.BufferAttribute(new Float32Array(positions), 3, true)
+  );
+  lineGeometry.setAttribute(
+    "color",
+    new THREE.BufferAttribute(new Float32Array(colors), 3, true)
+  );
+
+  const material = new THREE.LineBasicMaterial({
+    vertexColors: true,
+    // color: "red",
+    side: THREE.DoubleSide,
+  });
+  const flyLine = new THREE.Line(lineGeometry, material);
+
+  return { flyLine, flySpot };
+};

+ 299 - 0
zhsq_qk-ui/.history/src/map3d/drawFunc_20240617140439.js

@@ -0,0 +1,299 @@
+import * as THREE from "three";
+import * as d3 from "d3";
+import { CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { Line2 } from "three/examples/jsm/lines/Line2";
+import { LineGeometry } from "three/examples/jsm/lines/LineGeometry";
+import { LineMaterial } from "three/examples/jsm/lines/LineMaterial";
+// import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
+
+// import { ProjectionFnParamType } from "./index.vue";
+import { mapConfig } from "./mapConfig";
+
+// 绘制挤出的材质
+export function drawExtrudeMesh(
+  point,
+  projectionFn
+) {
+  const shape = new THREE.Shape();
+  const pointsArray = [];
+
+  for (let i = 0; i < point.length; i++) {
+    const [x, y] = projectionFn(point[i]); // 将每一个经纬度转化为坐标点
+    if (i === 0) {
+      shape.moveTo(x, -y);
+    }
+    shape.lineTo(x, -y);
+    pointsArray.push(x, -y, mapConfig.topLineZIndex);
+  }
+
+  const geometry = new THREE.ExtrudeGeometry(shape, {
+    depth: mapConfig.mapDepth, // 挤出的形状深度
+    bevelEnabled: false, // 对挤出的形状应用是否斜角
+  });
+
+  const material = new THREE.MeshPhongMaterial({
+    // color: mapConfig.mapColor,
+    color: mapConfig.mapColorGradient[Math.floor(Math.random() * 4)], // 随机颜色
+    // transparent: mapConfig.mapTransparent,
+    // opacity: mapConfig.mapOpacity,
+  });
+
+  const materialSide = new THREE.ShaderMaterial({
+    uniforms: {
+      color1: {
+        value: new THREE.Color(mapConfig.mapSideColor1),
+      },
+      color2: {
+        value: new THREE.Color(mapConfig.mapSideColor2),
+      },
+    },
+    vertexShader: `
+      varying vec3 vPosition;
+      void main() {
+        vPosition = position;
+        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+      }
+    `,
+    fragmentShader: `
+      uniform vec3 color1;
+      uniform vec3 color2;
+      varying vec3 vPosition;
+      void main() {
+        vec3 mixColor = mix(color1, color2, 0.5 - vPosition.z * 0.2); // 使用顶点坐标 z 分量来控制混合
+        gl_FragColor = vec4(mixColor, 1.0);
+      }
+    `,
+    //   wireframe: true,
+  });
+
+  const mesh = new THREE.Mesh(geometry, [material, materialSide]);
+  // userData 存储自定义属性
+  mesh.userData = {
+    isChangeColor: true,
+  };
+
+  // 边框线,赋值空间点坐标,3个一组
+  const lineGeometry = new LineGeometry();
+  lineGeometry.setPositions(pointsArray);
+
+  const lineMaterial = new LineMaterial({
+    color: mapConfig.topLineColor,
+    linewidth: mapConfig.topLineWidth,
+  });
+  lineMaterial.resolution.set(window.innerWidth, window.innerHeight);
+  const line = new Line2(lineGeometry, lineMaterial);
+
+  return { mesh, line };
+}
+
+// 生成地图3D模型
+export function generateMapObject3D(
+  mapdata,
+  projectionFnParam
+) {
+  // 地图对象
+  const mapObject3D = new THREE.Object3D();
+  // 地图数据
+  const basicFeatures = mapdata.features;
+
+  const { center, scale } = projectionFnParam;
+
+  const projectionFn = d3
+    .geoMercator()
+    .center(center)
+    .scale(scale)
+    .translate([0, 0]);
+
+  const label2dData = []; // 存储自定义 2d 标签数据
+
+  // 每个省的数据
+  basicFeatures.forEach((basicFeatureItem) => {
+    // 每个省份的地图对象
+    const provinceMapObject3D = new THREE.Object3D();
+    // 将地图数据挂在到模型数据上
+    provinceMapObject3D.customProperties = basicFeatureItem.properties;
+
+    // 每个坐标类型
+    const featureType = basicFeatureItem.geometry.type;
+    // 每个坐标数组
+    const featureCoords =
+      basicFeatureItem.geometry.coordinates;
+    // 每个中心点位置
+    const featureCenterCoord =
+      basicFeatureItem.properties.centroid &&
+      projectionFn(basicFeatureItem.properties.centroid);
+    // 名字
+    const featureName = basicFeatureItem.properties.name;
+
+    if (featureCenterCoord) {
+      label2dData.push({
+        featureCenterCoord,
+        featureName,
+      });
+    }
+
+    // MultiPolygon 类型
+    if (featureType === "MultiPolygon") {
+      featureCoords.forEach((multiPolygon) => {
+        multiPolygon.forEach((polygon) => {
+          const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+          provinceMapObject3D.add(mesh);
+          provinceMapObject3D.add(line);
+        });
+      });
+    }
+
+    // Polygon 类型
+    if (featureType === "Polygon") {
+      featureCoords.forEach((polygon) => {
+        const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+        provinceMapObject3D.add(mesh);
+        provinceMapObject3D.add(line);
+      });
+    }
+
+    mapObject3D.add(provinceMapObject3D);
+  });
+
+  return { mapObject3D, label2dData };
+}
+
+// 生成地图2D标签
+export function generateMapLabel2D(label2dData) {
+  const labelObject2D = new THREE.Object3D();
+  label2dData.forEach((item) => {
+    const { featureCenterCoord, featureName } = item;
+    debugger
+    const labelObjectItem = draw2dLabel(featureCenterCoord, featureName);
+    if (labelObjectItem) {
+      labelObject2D.add(labelObjectItem);
+    }
+  });
+  return labelObject2D;
+}
+
+// 生成地图spot点位
+export function generateMapSpot(label2dData) {
+  const spotObject3D = new THREE.Object3D();
+  const spotList = [];
+  label2dData.forEach((item) => {
+    const { featureCenterCoord } = item;
+      const spotObjectItem = drawSpot(featureCenterCoord);
+      if (spotObjectItem && spotObjectItem.circle && spotObjectItem.ring) {
+        spotObject3D.add(spotObjectItem.circle);
+        spotObject3D.add(spotObjectItem.ring);
+        spotList.push(spotObjectItem.ring);
+      }
+
+      //TODO:渲染铁塔
+      
+
+  });
+  return { spotObject3D, spotList };
+}
+
+// 绘制二维标签
+export const draw2dLabel = (coord, proviceName) => {
+  if (coord && coord.length) {
+    // 模版字符串
+    const innerHTML = `<div class="your-classname" style="color: #fff">${proviceName}</div>`;
+    const labelDiv = document.createElement("div");
+    labelDiv.innerHTML = innerHTML;
+    labelDiv.style.pointerEvents = "none"; // 禁用事件
+    const labelObject = new CSS2DObject(labelDiv);
+    labelObject.position.set(coord[0], -coord[1], mapConfig.label2dZIndex);
+    return labelObject;
+  }
+};
+
+// 绘制圆点
+export const drawSpot = (coord) => {
+  if (coord && coord.length) {
+    /**
+     * 绘制圆点
+     */
+    const spotGeometry = new THREE.CircleGeometry(0.2, 200);
+    const spotMaterial = new THREE.MeshBasicMaterial({
+      color: "#3EC5FB",
+      side: THREE.DoubleSide,
+    });
+    const circle = new THREE.Mesh(spotGeometry, spotMaterial);
+    circle.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+
+    // 圆环
+    const ringGeometry = new THREE.RingGeometry(0.2, 0.3, 50);
+    const ringMaterial = new THREE.MeshBasicMaterial({
+      color: "#3FC5FB",
+      side: THREE.DoubleSide,
+      transparent: true,
+    });
+    const ring = new THREE.Mesh(ringGeometry, ringMaterial);
+    ring.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+    return { circle, ring };
+  }
+};
+
+/**
+ * 线上移动物体
+ */
+export const drawflySpot = (curve) => {
+  const aGeo = new THREE.SphereGeometry(0.2);
+  const aMater = new THREE.MeshBasicMaterial({
+    color: "#77f077",
+    side: THREE.DoubleSide,
+  });
+  const aMesh = new THREE.Mesh(aGeo, aMater);
+  // 保存曲线实例
+  aMesh.curve = curve;
+  aMesh._s = 0;
+  return aMesh;
+};
+
+// 绘制两点链接飞线
+export const drawLineBetween2Spot = (
+  coordStart,
+  coordEnd
+) => {
+  const [x0, y0, z0] = [...coordStart, mapConfig.spotZIndex];
+  const [x1, y1, z1] = [...coordEnd, mapConfig.spotZIndex];
+  // 使用 QuadraticBezierCurve3 创建 三维二次贝塞尔曲线
+  const curve = new THREE.QuadraticBezierCurve3(
+    new THREE.Vector3(x0, -y0, z0),
+    new THREE.Vector3((x0 + x1) / 2, -(y0 + y1) / 2, 20),
+    new THREE.Vector3(x1, -y1, z1)
+  );
+
+  const flySpot = drawflySpot(curve);
+
+  const lineGeometry = new THREE.BufferGeometry();
+  // 获取曲线上50个点
+  const points = curve.getPoints(50);
+  const positions = [];
+  const colors = [];
+  const color = new THREE.Color();
+
+  // 给每个顶点设置演示 实现渐变
+  for (let j = 0; j < points.length; j++) {
+    color.setHSL(0.21 + j, 0.77, 0.55 + j * 0.0025); // 色
+    colors.push(color.r, color.g, color.b);
+    positions.push(points[j].x, points[j].y, points[j].z);
+  }
+  // 放入顶点 和 设置顶点颜色
+  lineGeometry.setAttribute(
+    "position",
+    new THREE.BufferAttribute(new Float32Array(positions), 3, true)
+  );
+  lineGeometry.setAttribute(
+    "color",
+    new THREE.BufferAttribute(new Float32Array(colors), 3, true)
+  );
+
+  const material = new THREE.LineBasicMaterial({
+    vertexColors: true,
+    // color: "red",
+    side: THREE.DoubleSide,
+  });
+  const flyLine = new THREE.Line(lineGeometry, material);
+
+  return { flyLine, flySpot };
+};

+ 300 - 0
zhsq_qk-ui/.history/src/map3d/drawFunc_20240617140822.js

@@ -0,0 +1,300 @@
+import * as THREE from "three";
+import * as d3 from "d3";
+import { CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { Line2 } from "three/examples/jsm/lines/Line2";
+import { LineGeometry } from "three/examples/jsm/lines/LineGeometry";
+import { LineMaterial } from "three/examples/jsm/lines/LineMaterial";
+// import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
+
+// import { ProjectionFnParamType } from "./index.vue";
+import { mapConfig } from "./mapConfig";
+
+// 绘制挤出的材质
+export function drawExtrudeMesh(
+  point,
+  projectionFn
+) {
+  const shape = new THREE.Shape();
+  const pointsArray = [];
+
+  for (let i = 0; i < point.length; i++) {
+    const [x, y] = projectionFn(point[i]); // 将每一个经纬度转化为坐标点
+    if (i === 0) {
+      shape.moveTo(x, -y);
+    }
+    shape.lineTo(x, -y);
+    pointsArray.push(x, -y, mapConfig.topLineZIndex);
+  }
+
+  const geometry = new THREE.ExtrudeGeometry(shape, {
+    depth: mapConfig.mapDepth, // 挤出的形状深度
+    bevelEnabled: false, // 对挤出的形状应用是否斜角
+  });
+
+  const material = new THREE.MeshPhongMaterial({
+    // color: mapConfig.mapColor,
+    color: mapConfig.mapColorGradient[Math.floor(Math.random() * 4)], // 随机颜色
+    // transparent: mapConfig.mapTransparent,
+    // opacity: mapConfig.mapOpacity,
+  });
+
+  const materialSide = new THREE.ShaderMaterial({
+    uniforms: {
+      color1: {
+        value: new THREE.Color(mapConfig.mapSideColor1),
+      },
+      color2: {
+        value: new THREE.Color(mapConfig.mapSideColor2),
+      },
+    },
+    vertexShader: `
+      varying vec3 vPosition;
+      void main() {
+        vPosition = position;
+        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+      }
+    `,
+    fragmentShader: `
+      uniform vec3 color1;
+      uniform vec3 color2;
+      varying vec3 vPosition;
+      void main() {
+        vec3 mixColor = mix(color1, color2, 0.5 - vPosition.z * 0.2); // 使用顶点坐标 z 分量来控制混合
+        gl_FragColor = vec4(mixColor, 1.0);
+      }
+    `,
+    //   wireframe: true,
+  });
+
+  const mesh = new THREE.Mesh(geometry, [material, materialSide]);
+  // userData 存储自定义属性
+  mesh.userData = {
+    isChangeColor: true,
+  };
+
+  // 边框线,赋值空间点坐标,3个一组
+  const lineGeometry = new LineGeometry();
+  lineGeometry.setPositions(pointsArray);
+
+  const lineMaterial = new LineMaterial({
+    color: mapConfig.topLineColor,
+    linewidth: mapConfig.topLineWidth,
+  });
+  lineMaterial.resolution.set(window.innerWidth, window.innerHeight);
+  const line = new Line2(lineGeometry, lineMaterial);
+
+  return { mesh, line };
+}
+
+// 生成地图3D模型
+export function generateMapObject3D(
+  mapdata,
+  projectionFnParam
+) {
+  // 地图对象
+  const mapObject3D = new THREE.Object3D();
+  // 地图数据
+  const basicFeatures = mapdata.features;
+  console.log('basicFeatures',basicFeatures)
+
+  const { center, scale } = projectionFnParam;
+
+  const projectionFn = d3
+    .geoMercator()
+    .center(center)
+    .scale(scale)
+    .translate([0, 0]);
+
+  const label2dData = []; // 存储自定义 2d 标签数据
+
+  // 每个省的数据
+  basicFeatures.forEach((basicFeatureItem) => {
+    // 每个省份的地图对象
+    const provinceMapObject3D = new THREE.Object3D();
+    // 将地图数据挂在到模型数据上
+    provinceMapObject3D.customProperties = basicFeatureItem.properties;
+
+    // 每个坐标类型
+    const featureType = basicFeatureItem.geometry.type;
+    // 每个坐标数组
+    const featureCoords =
+      basicFeatureItem.geometry.coordinates;
+    // 每个中心点位置
+    const featureCenterCoord =
+      basicFeatureItem.properties.centroid &&
+      projectionFn(basicFeatureItem.properties.centroid);
+    // 名字
+    const featureName = basicFeatureItem.properties.name;
+
+    if (featureCenterCoord) {
+      label2dData.push({
+        featureCenterCoord,
+        featureName,
+      });
+    }
+
+    // MultiPolygon 类型
+    if (featureType === "MultiPolygon") {
+      featureCoords.forEach((multiPolygon) => {
+        multiPolygon.forEach((polygon) => {
+          const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+          provinceMapObject3D.add(mesh);
+          provinceMapObject3D.add(line);
+        });
+      });
+    }
+
+    // Polygon 类型
+    if (featureType === "Polygon") {
+      featureCoords.forEach((polygon) => {
+        const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+        provinceMapObject3D.add(mesh);
+        provinceMapObject3D.add(line);
+      });
+    }
+
+    mapObject3D.add(provinceMapObject3D);
+  });
+
+  return { mapObject3D, label2dData };
+}
+
+// 生成地图2D标签
+export function generateMapLabel2D(label2dData) {
+  const labelObject2D = new THREE.Object3D();
+  label2dData.forEach((item) => {
+    const { featureCenterCoord, featureName } = item;
+    debugger
+    const labelObjectItem = draw2dLabel(featureCenterCoord, featureName);
+    if (labelObjectItem) {
+      labelObject2D.add(labelObjectItem);
+    }
+  });
+  return labelObject2D;
+}
+
+// 生成地图spot点位
+export function generateMapSpot(label2dData) {
+  const spotObject3D = new THREE.Object3D();
+  const spotList = [];
+  label2dData.forEach((item) => {
+    const { featureCenterCoord } = item;
+      const spotObjectItem = drawSpot(featureCenterCoord);
+      if (spotObjectItem && spotObjectItem.circle && spotObjectItem.ring) {
+        spotObject3D.add(spotObjectItem.circle);
+        spotObject3D.add(spotObjectItem.ring);
+        spotList.push(spotObjectItem.ring);
+      }
+
+      //TODO:渲染铁塔
+      
+
+  });
+  return { spotObject3D, spotList };
+}
+
+// 绘制二维标签
+export const draw2dLabel = (coord, proviceName) => {
+  if (coord && coord.length) {
+    // 模版字符串
+    const innerHTML = `<div class="your-classname" style="color: #fff">${proviceName}</div>`;
+    const labelDiv = document.createElement("div");
+    labelDiv.innerHTML = innerHTML;
+    labelDiv.style.pointerEvents = "none"; // 禁用事件
+    const labelObject = new CSS2DObject(labelDiv);
+    labelObject.position.set(coord[0], -coord[1], mapConfig.label2dZIndex);
+    return labelObject;
+  }
+};
+
+// 绘制圆点
+export const drawSpot = (coord) => {
+  if (coord && coord.length) {
+    /**
+     * 绘制圆点
+     */
+    const spotGeometry = new THREE.CircleGeometry(0.2, 200);
+    const spotMaterial = new THREE.MeshBasicMaterial({
+      color: "#3EC5FB",
+      side: THREE.DoubleSide,
+    });
+    const circle = new THREE.Mesh(spotGeometry, spotMaterial);
+    circle.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+
+    // 圆环
+    const ringGeometry = new THREE.RingGeometry(0.2, 0.3, 50);
+    const ringMaterial = new THREE.MeshBasicMaterial({
+      color: "#3FC5FB",
+      side: THREE.DoubleSide,
+      transparent: true,
+    });
+    const ring = new THREE.Mesh(ringGeometry, ringMaterial);
+    ring.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+    return { circle, ring };
+  }
+};
+
+/**
+ * 线上移动物体
+ */
+export const drawflySpot = (curve) => {
+  const aGeo = new THREE.SphereGeometry(0.2);
+  const aMater = new THREE.MeshBasicMaterial({
+    color: "#77f077",
+    side: THREE.DoubleSide,
+  });
+  const aMesh = new THREE.Mesh(aGeo, aMater);
+  // 保存曲线实例
+  aMesh.curve = curve;
+  aMesh._s = 0;
+  return aMesh;
+};
+
+// 绘制两点链接飞线
+export const drawLineBetween2Spot = (
+  coordStart,
+  coordEnd
+) => {
+  const [x0, y0, z0] = [...coordStart, mapConfig.spotZIndex];
+  const [x1, y1, z1] = [...coordEnd, mapConfig.spotZIndex];
+  // 使用 QuadraticBezierCurve3 创建 三维二次贝塞尔曲线
+  const curve = new THREE.QuadraticBezierCurve3(
+    new THREE.Vector3(x0, -y0, z0),
+    new THREE.Vector3((x0 + x1) / 2, -(y0 + y1) / 2, 20),
+    new THREE.Vector3(x1, -y1, z1)
+  );
+
+  const flySpot = drawflySpot(curve);
+
+  const lineGeometry = new THREE.BufferGeometry();
+  // 获取曲线上50个点
+  const points = curve.getPoints(50);
+  const positions = [];
+  const colors = [];
+  const color = new THREE.Color();
+
+  // 给每个顶点设置演示 实现渐变
+  for (let j = 0; j < points.length; j++) {
+    color.setHSL(0.21 + j, 0.77, 0.55 + j * 0.0025); // 色
+    colors.push(color.r, color.g, color.b);
+    positions.push(points[j].x, points[j].y, points[j].z);
+  }
+  // 放入顶点 和 设置顶点颜色
+  lineGeometry.setAttribute(
+    "position",
+    new THREE.BufferAttribute(new Float32Array(positions), 3, true)
+  );
+  lineGeometry.setAttribute(
+    "color",
+    new THREE.BufferAttribute(new Float32Array(colors), 3, true)
+  );
+
+  const material = new THREE.LineBasicMaterial({
+    vertexColors: true,
+    // color: "red",
+    side: THREE.DoubleSide,
+  });
+  const flyLine = new THREE.Line(lineGeometry, material);
+
+  return { flyLine, flySpot };
+};

+ 301 - 0
zhsq_qk-ui/.history/src/map3d/drawFunc_20240617140932.js

@@ -0,0 +1,301 @@
+import * as THREE from "three";
+import * as d3 from "d3";
+import { CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { Line2 } from "three/examples/jsm/lines/Line2";
+import { LineGeometry } from "three/examples/jsm/lines/LineGeometry";
+import { LineMaterial } from "three/examples/jsm/lines/LineMaterial";
+// import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
+
+// import { ProjectionFnParamType } from "./index.vue";
+import { mapConfig } from "./mapConfig";
+
+// 绘制挤出的材质
+export function drawExtrudeMesh(
+  point,
+  projectionFn
+) {
+  const shape = new THREE.Shape();
+  const pointsArray = [];
+
+  for (let i = 0; i < point.length; i++) {
+    const [x, y] = projectionFn(point[i]); // 将每一个经纬度转化为坐标点
+    if (i === 0) {
+      shape.moveTo(x, -y);
+    }
+    shape.lineTo(x, -y);
+    pointsArray.push(x, -y, mapConfig.topLineZIndex);
+  }
+
+  const geometry = new THREE.ExtrudeGeometry(shape, {
+    depth: mapConfig.mapDepth, // 挤出的形状深度
+    bevelEnabled: false, // 对挤出的形状应用是否斜角
+  });
+
+  const material = new THREE.MeshPhongMaterial({
+    // color: mapConfig.mapColor,
+    color: mapConfig.mapColorGradient[Math.floor(Math.random() * 4)], // 随机颜色
+    // transparent: mapConfig.mapTransparent,
+    // opacity: mapConfig.mapOpacity,
+  });
+
+  const materialSide = new THREE.ShaderMaterial({
+    uniforms: {
+      color1: {
+        value: new THREE.Color(mapConfig.mapSideColor1),
+      },
+      color2: {
+        value: new THREE.Color(mapConfig.mapSideColor2),
+      },
+    },
+    vertexShader: `
+      varying vec3 vPosition;
+      void main() {
+        vPosition = position;
+        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+      }
+    `,
+    fragmentShader: `
+      uniform vec3 color1;
+      uniform vec3 color2;
+      varying vec3 vPosition;
+      void main() {
+        vec3 mixColor = mix(color1, color2, 0.5 - vPosition.z * 0.2); // 使用顶点坐标 z 分量来控制混合
+        gl_FragColor = vec4(mixColor, 1.0);
+      }
+    `,
+    //   wireframe: true,
+  });
+
+  const mesh = new THREE.Mesh(geometry, [material, materialSide]);
+  // userData 存储自定义属性
+  mesh.userData = {
+    isChangeColor: true,
+  };
+
+  // 边框线,赋值空间点坐标,3个一组
+  const lineGeometry = new LineGeometry();
+  lineGeometry.setPositions(pointsArray);
+
+  const lineMaterial = new LineMaterial({
+    color: mapConfig.topLineColor,
+    linewidth: mapConfig.topLineWidth,
+  });
+  lineMaterial.resolution.set(window.innerWidth, window.innerHeight);
+  const line = new Line2(lineGeometry, lineMaterial);
+
+  return { mesh, line };
+}
+
+// 生成地图3D模型
+export function generateMapObject3D(
+  mapdata,
+  projectionFnParam
+) {
+  // 地图对象
+  const mapObject3D = new THREE.Object3D();
+  // 地图数据
+  const basicFeatures = mapdata.features;
+  console.log('basicFeatures',basicFeatures)
+
+  const { center, scale } = projectionFnParam;
+
+  const projectionFn = d3
+    .geoMercator()
+    .center(center)
+    .scale(scale)
+    .translate([0, 0]);
+
+  const label2dData = []; // 存储自定义 2d 标签数据
+
+  // 每个省的数据
+  basicFeatures.forEach((basicFeatureItem) => {
+    // 每个省份的地图对象
+    const provinceMapObject3D = new THREE.Object3D();
+    // 将地图数据挂在到模型数据上
+    provinceMapObject3D.customProperties = basicFeatureItem.properties;
+
+    // 每个坐标类型
+    const featureType = basicFeatureItem.geometry.type;
+    // 每个坐标数组
+    const featureCoords =
+      basicFeatureItem.geometry.coordinates;
+    // 每个中心点位置
+    const featureCenterCoord =
+      basicFeatureItem.properties.centroid &&
+      projectionFn(basicFeatureItem.properties.centroid);
+    // 名字
+    const featureName = basicFeatureItem.properties.name;
+    console.log('featureName',featureName)
+
+    if (featureCenterCoord) {
+      label2dData.push({
+        featureCenterCoord,
+        featureName,
+      });
+    }
+
+    // MultiPolygon 类型
+    if (featureType === "MultiPolygon") {
+      featureCoords.forEach((multiPolygon) => {
+        multiPolygon.forEach((polygon) => {
+          const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+          provinceMapObject3D.add(mesh);
+          provinceMapObject3D.add(line);
+        });
+      });
+    }
+
+    // Polygon 类型
+    if (featureType === "Polygon") {
+      featureCoords.forEach((polygon) => {
+        const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+        provinceMapObject3D.add(mesh);
+        provinceMapObject3D.add(line);
+      });
+    }
+
+    mapObject3D.add(provinceMapObject3D);
+  });
+
+  return { mapObject3D, label2dData };
+}
+
+// 生成地图2D标签
+export function generateMapLabel2D(label2dData) {
+  const labelObject2D = new THREE.Object3D();
+  label2dData.forEach((item) => {
+    const { featureCenterCoord, featureName } = item;
+    debugger
+    const labelObjectItem = draw2dLabel(featureCenterCoord, featureName);
+    if (labelObjectItem) {
+      labelObject2D.add(labelObjectItem);
+    }
+  });
+  return labelObject2D;
+}
+
+// 生成地图spot点位
+export function generateMapSpot(label2dData) {
+  const spotObject3D = new THREE.Object3D();
+  const spotList = [];
+  label2dData.forEach((item) => {
+    const { featureCenterCoord } = item;
+      const spotObjectItem = drawSpot(featureCenterCoord);
+      if (spotObjectItem && spotObjectItem.circle && spotObjectItem.ring) {
+        spotObject3D.add(spotObjectItem.circle);
+        spotObject3D.add(spotObjectItem.ring);
+        spotList.push(spotObjectItem.ring);
+      }
+
+      //TODO:渲染铁塔
+      
+
+  });
+  return { spotObject3D, spotList };
+}
+
+// 绘制二维标签
+export const draw2dLabel = (coord, proviceName) => {
+  if (coord && coord.length) {
+    // 模版字符串
+    const innerHTML = `<div class="your-classname" style="color: #fff">${proviceName}</div>`;
+    const labelDiv = document.createElement("div");
+    labelDiv.innerHTML = innerHTML;
+    labelDiv.style.pointerEvents = "none"; // 禁用事件
+    const labelObject = new CSS2DObject(labelDiv);
+    labelObject.position.set(coord[0], -coord[1], mapConfig.label2dZIndex);
+    return labelObject;
+  }
+};
+
+// 绘制圆点
+export const drawSpot = (coord) => {
+  if (coord && coord.length) {
+    /**
+     * 绘制圆点
+     */
+    const spotGeometry = new THREE.CircleGeometry(0.2, 200);
+    const spotMaterial = new THREE.MeshBasicMaterial({
+      color: "#3EC5FB",
+      side: THREE.DoubleSide,
+    });
+    const circle = new THREE.Mesh(spotGeometry, spotMaterial);
+    circle.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+
+    // 圆环
+    const ringGeometry = new THREE.RingGeometry(0.2, 0.3, 50);
+    const ringMaterial = new THREE.MeshBasicMaterial({
+      color: "#3FC5FB",
+      side: THREE.DoubleSide,
+      transparent: true,
+    });
+    const ring = new THREE.Mesh(ringGeometry, ringMaterial);
+    ring.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+    return { circle, ring };
+  }
+};
+
+/**
+ * 线上移动物体
+ */
+export const drawflySpot = (curve) => {
+  const aGeo = new THREE.SphereGeometry(0.2);
+  const aMater = new THREE.MeshBasicMaterial({
+    color: "#77f077",
+    side: THREE.DoubleSide,
+  });
+  const aMesh = new THREE.Mesh(aGeo, aMater);
+  // 保存曲线实例
+  aMesh.curve = curve;
+  aMesh._s = 0;
+  return aMesh;
+};
+
+// 绘制两点链接飞线
+export const drawLineBetween2Spot = (
+  coordStart,
+  coordEnd
+) => {
+  const [x0, y0, z0] = [...coordStart, mapConfig.spotZIndex];
+  const [x1, y1, z1] = [...coordEnd, mapConfig.spotZIndex];
+  // 使用 QuadraticBezierCurve3 创建 三维二次贝塞尔曲线
+  const curve = new THREE.QuadraticBezierCurve3(
+    new THREE.Vector3(x0, -y0, z0),
+    new THREE.Vector3((x0 + x1) / 2, -(y0 + y1) / 2, 20),
+    new THREE.Vector3(x1, -y1, z1)
+  );
+
+  const flySpot = drawflySpot(curve);
+
+  const lineGeometry = new THREE.BufferGeometry();
+  // 获取曲线上50个点
+  const points = curve.getPoints(50);
+  const positions = [];
+  const colors = [];
+  const color = new THREE.Color();
+
+  // 给每个顶点设置演示 实现渐变
+  for (let j = 0; j < points.length; j++) {
+    color.setHSL(0.21 + j, 0.77, 0.55 + j * 0.0025); // 色
+    colors.push(color.r, color.g, color.b);
+    positions.push(points[j].x, points[j].y, points[j].z);
+  }
+  // 放入顶点 和 设置顶点颜色
+  lineGeometry.setAttribute(
+    "position",
+    new THREE.BufferAttribute(new Float32Array(positions), 3, true)
+  );
+  lineGeometry.setAttribute(
+    "color",
+    new THREE.BufferAttribute(new Float32Array(colors), 3, true)
+  );
+
+  const material = new THREE.LineBasicMaterial({
+    vertexColors: true,
+    // color: "red",
+    side: THREE.DoubleSide,
+  });
+  const flyLine = new THREE.Line(lineGeometry, material);
+
+  return { flyLine, flySpot };
+};

+ 301 - 0
zhsq_qk-ui/.history/src/map3d/drawFunc_20240617140949.js

@@ -0,0 +1,301 @@
+import * as THREE from "three";
+import * as d3 from "d3";
+import { CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { Line2 } from "three/examples/jsm/lines/Line2";
+import { LineGeometry } from "three/examples/jsm/lines/LineGeometry";
+import { LineMaterial } from "three/examples/jsm/lines/LineMaterial";
+// import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
+
+// import { ProjectionFnParamType } from "./index.vue";
+import { mapConfig } from "./mapConfig";
+
+// 绘制挤出的材质
+export function drawExtrudeMesh(
+  point,
+  projectionFn
+) {
+  const shape = new THREE.Shape();
+  const pointsArray = [];
+
+  for (let i = 0; i < point.length; i++) {
+    const [x, y] = projectionFn(point[i]); // 将每一个经纬度转化为坐标点
+    if (i === 0) {
+      shape.moveTo(x, -y);
+    }
+    shape.lineTo(x, -y);
+    pointsArray.push(x, -y, mapConfig.topLineZIndex);
+  }
+
+  const geometry = new THREE.ExtrudeGeometry(shape, {
+    depth: mapConfig.mapDepth, // 挤出的形状深度
+    bevelEnabled: false, // 对挤出的形状应用是否斜角
+  });
+
+  const material = new THREE.MeshPhongMaterial({
+    // color: mapConfig.mapColor,
+    color: mapConfig.mapColorGradient[Math.floor(Math.random() * 4)], // 随机颜色
+    // transparent: mapConfig.mapTransparent,
+    // opacity: mapConfig.mapOpacity,
+  });
+
+  const materialSide = new THREE.ShaderMaterial({
+    uniforms: {
+      color1: {
+        value: new THREE.Color(mapConfig.mapSideColor1),
+      },
+      color2: {
+        value: new THREE.Color(mapConfig.mapSideColor2),
+      },
+    },
+    vertexShader: `
+      varying vec3 vPosition;
+      void main() {
+        vPosition = position;
+        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+      }
+    `,
+    fragmentShader: `
+      uniform vec3 color1;
+      uniform vec3 color2;
+      varying vec3 vPosition;
+      void main() {
+        vec3 mixColor = mix(color1, color2, 0.5 - vPosition.z * 0.2); // 使用顶点坐标 z 分量来控制混合
+        gl_FragColor = vec4(mixColor, 1.0);
+      }
+    `,
+    //   wireframe: true,
+  });
+
+  const mesh = new THREE.Mesh(geometry, [material, materialSide]);
+  // userData 存储自定义属性
+  mesh.userData = {
+    isChangeColor: true,
+  };
+
+  // 边框线,赋值空间点坐标,3个一组
+  const lineGeometry = new LineGeometry();
+  lineGeometry.setPositions(pointsArray);
+
+  const lineMaterial = new LineMaterial({
+    color: mapConfig.topLineColor,
+    linewidth: mapConfig.topLineWidth,
+  });
+  lineMaterial.resolution.set(window.innerWidth, window.innerHeight);
+  const line = new Line2(lineGeometry, lineMaterial);
+
+  return { mesh, line };
+}
+
+// 生成地图3D模型
+export function generateMapObject3D(
+  mapdata,
+  projectionFnParam
+) {
+  // 地图对象
+  const mapObject3D = new THREE.Object3D();
+  // 地图数据
+  const basicFeatures = mapdata.features;
+  console.log('basicFeatures',basicFeatures)
+
+  const { center, scale } = projectionFnParam;
+
+  const projectionFn = d3
+    .geoMercator()
+    .center(center)
+    .scale(scale)
+    .translate([0, 0]);
+
+  const label2dData = []; // 存储自定义 2d 标签数据
+
+  // 每个省的数据
+  basicFeatures.forEach((basicFeatureItem) => {
+    // 每个省份的地图对象
+    const provinceMapObject3D = new THREE.Object3D();
+    // 将地图数据挂在到模型数据上
+    provinceMapObject3D.customProperties = basicFeatureItem.properties;
+
+    // 每个坐标类型
+    const featureType = basicFeatureItem.geometry.type;
+    // 每个坐标数组
+    const featureCoords =
+      basicFeatureItem.geometry.coordinates;
+    // 每个中心点位置
+    const featureCenterCoord =
+      basicFeatureItem.properties.centroid &&
+      projectionFn(basicFeatureItem.properties.centroid);
+    // 名字
+    const featureName = 'asask';
+    console.log('featureName',featureName)
+
+    if (featureCenterCoord) {
+      label2dData.push({
+        featureCenterCoord,
+        featureName,
+      });
+    }
+
+    // MultiPolygon 类型
+    if (featureType === "MultiPolygon") {
+      featureCoords.forEach((multiPolygon) => {
+        multiPolygon.forEach((polygon) => {
+          const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+          provinceMapObject3D.add(mesh);
+          provinceMapObject3D.add(line);
+        });
+      });
+    }
+
+    // Polygon 类型
+    if (featureType === "Polygon") {
+      featureCoords.forEach((polygon) => {
+        const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+        provinceMapObject3D.add(mesh);
+        provinceMapObject3D.add(line);
+      });
+    }
+
+    mapObject3D.add(provinceMapObject3D);
+  });
+
+  return { mapObject3D, label2dData };
+}
+
+// 生成地图2D标签
+export function generateMapLabel2D(label2dData) {
+  const labelObject2D = new THREE.Object3D();
+  label2dData.forEach((item) => {
+    const { featureCenterCoord, featureName } = item;
+    debugger
+    const labelObjectItem = draw2dLabel(featureCenterCoord, featureName);
+    if (labelObjectItem) {
+      labelObject2D.add(labelObjectItem);
+    }
+  });
+  return labelObject2D;
+}
+
+// 生成地图spot点位
+export function generateMapSpot(label2dData) {
+  const spotObject3D = new THREE.Object3D();
+  const spotList = [];
+  label2dData.forEach((item) => {
+    const { featureCenterCoord } = item;
+      const spotObjectItem = drawSpot(featureCenterCoord);
+      if (spotObjectItem && spotObjectItem.circle && spotObjectItem.ring) {
+        spotObject3D.add(spotObjectItem.circle);
+        spotObject3D.add(spotObjectItem.ring);
+        spotList.push(spotObjectItem.ring);
+      }
+
+      //TODO:渲染铁塔
+      
+
+  });
+  return { spotObject3D, spotList };
+}
+
+// 绘制二维标签
+export const draw2dLabel = (coord, proviceName) => {
+  if (coord && coord.length) {
+    // 模版字符串
+    const innerHTML = `<div class="your-classname" style="color: #fff">${proviceName}</div>`;
+    const labelDiv = document.createElement("div");
+    labelDiv.innerHTML = innerHTML;
+    labelDiv.style.pointerEvents = "none"; // 禁用事件
+    const labelObject = new CSS2DObject(labelDiv);
+    labelObject.position.set(coord[0], -coord[1], mapConfig.label2dZIndex);
+    return labelObject;
+  }
+};
+
+// 绘制圆点
+export const drawSpot = (coord) => {
+  if (coord && coord.length) {
+    /**
+     * 绘制圆点
+     */
+    const spotGeometry = new THREE.CircleGeometry(0.2, 200);
+    const spotMaterial = new THREE.MeshBasicMaterial({
+      color: "#3EC5FB",
+      side: THREE.DoubleSide,
+    });
+    const circle = new THREE.Mesh(spotGeometry, spotMaterial);
+    circle.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+
+    // 圆环
+    const ringGeometry = new THREE.RingGeometry(0.2, 0.3, 50);
+    const ringMaterial = new THREE.MeshBasicMaterial({
+      color: "#3FC5FB",
+      side: THREE.DoubleSide,
+      transparent: true,
+    });
+    const ring = new THREE.Mesh(ringGeometry, ringMaterial);
+    ring.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+    return { circle, ring };
+  }
+};
+
+/**
+ * 线上移动物体
+ */
+export const drawflySpot = (curve) => {
+  const aGeo = new THREE.SphereGeometry(0.2);
+  const aMater = new THREE.MeshBasicMaterial({
+    color: "#77f077",
+    side: THREE.DoubleSide,
+  });
+  const aMesh = new THREE.Mesh(aGeo, aMater);
+  // 保存曲线实例
+  aMesh.curve = curve;
+  aMesh._s = 0;
+  return aMesh;
+};
+
+// 绘制两点链接飞线
+export const drawLineBetween2Spot = (
+  coordStart,
+  coordEnd
+) => {
+  const [x0, y0, z0] = [...coordStart, mapConfig.spotZIndex];
+  const [x1, y1, z1] = [...coordEnd, mapConfig.spotZIndex];
+  // 使用 QuadraticBezierCurve3 创建 三维二次贝塞尔曲线
+  const curve = new THREE.QuadraticBezierCurve3(
+    new THREE.Vector3(x0, -y0, z0),
+    new THREE.Vector3((x0 + x1) / 2, -(y0 + y1) / 2, 20),
+    new THREE.Vector3(x1, -y1, z1)
+  );
+
+  const flySpot = drawflySpot(curve);
+
+  const lineGeometry = new THREE.BufferGeometry();
+  // 获取曲线上50个点
+  const points = curve.getPoints(50);
+  const positions = [];
+  const colors = [];
+  const color = new THREE.Color();
+
+  // 给每个顶点设置演示 实现渐变
+  for (let j = 0; j < points.length; j++) {
+    color.setHSL(0.21 + j, 0.77, 0.55 + j * 0.0025); // 色
+    colors.push(color.r, color.g, color.b);
+    positions.push(points[j].x, points[j].y, points[j].z);
+  }
+  // 放入顶点 和 设置顶点颜色
+  lineGeometry.setAttribute(
+    "position",
+    new THREE.BufferAttribute(new Float32Array(positions), 3, true)
+  );
+  lineGeometry.setAttribute(
+    "color",
+    new THREE.BufferAttribute(new Float32Array(colors), 3, true)
+  );
+
+  const material = new THREE.LineBasicMaterial({
+    vertexColors: true,
+    // color: "red",
+    side: THREE.DoubleSide,
+  });
+  const flyLine = new THREE.Line(lineGeometry, material);
+
+  return { flyLine, flySpot };
+};

+ 301 - 0
zhsq_qk-ui/.history/src/map3d/drawFunc_20240617141040.js

@@ -0,0 +1,301 @@
+import * as THREE from "three";
+import * as d3 from "d3";
+import { CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { Line2 } from "three/examples/jsm/lines/Line2";
+import { LineGeometry } from "three/examples/jsm/lines/LineGeometry";
+import { LineMaterial } from "three/examples/jsm/lines/LineMaterial";
+// import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
+
+// import { ProjectionFnParamType } from "./index.vue";
+import { mapConfig } from "./mapConfig";
+
+// 绘制挤出的材质
+export function drawExtrudeMesh(
+  point,
+  projectionFn
+) {
+  const shape = new THREE.Shape();
+  const pointsArray = [];
+
+  for (let i = 0; i < point.length; i++) {
+    const [x, y] = projectionFn(point[i]); // 将每一个经纬度转化为坐标点
+    if (i === 0) {
+      shape.moveTo(x, -y);
+    }
+    shape.lineTo(x, -y);
+    pointsArray.push(x, -y, mapConfig.topLineZIndex);
+  }
+
+  const geometry = new THREE.ExtrudeGeometry(shape, {
+    depth: mapConfig.mapDepth, // 挤出的形状深度
+    bevelEnabled: false, // 对挤出的形状应用是否斜角
+  });
+
+  const material = new THREE.MeshPhongMaterial({
+    // color: mapConfig.mapColor,
+    color: mapConfig.mapColorGradient[Math.floor(Math.random() * 4)], // 随机颜色
+    // transparent: mapConfig.mapTransparent,
+    // opacity: mapConfig.mapOpacity,
+  });
+
+  const materialSide = new THREE.ShaderMaterial({
+    uniforms: {
+      color1: {
+        value: new THREE.Color(mapConfig.mapSideColor1),
+      },
+      color2: {
+        value: new THREE.Color(mapConfig.mapSideColor2),
+      },
+    },
+    vertexShader: `
+      varying vec3 vPosition;
+      void main() {
+        vPosition = position;
+        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+      }
+    `,
+    fragmentShader: `
+      uniform vec3 color1;
+      uniform vec3 color2;
+      varying vec3 vPosition;
+      void main() {
+        vec3 mixColor = mix(color1, color2, 0.5 - vPosition.z * 0.2); // 使用顶点坐标 z 分量来控制混合
+        gl_FragColor = vec4(mixColor, 1.0);
+      }
+    `,
+    //   wireframe: true,
+  });
+
+  const mesh = new THREE.Mesh(geometry, [material, materialSide]);
+  // userData 存储自定义属性
+  mesh.userData = {
+    isChangeColor: true,
+  };
+
+  // 边框线,赋值空间点坐标,3个一组
+  const lineGeometry = new LineGeometry();
+  lineGeometry.setPositions(pointsArray);
+
+  const lineMaterial = new LineMaterial({
+    color: mapConfig.topLineColor,
+    linewidth: mapConfig.topLineWidth,
+  });
+  lineMaterial.resolution.set(window.innerWidth, window.innerHeight);
+  const line = new Line2(lineGeometry, lineMaterial);
+
+  return { mesh, line };
+}
+
+// 生成地图3D模型
+export function generateMapObject3D(
+  mapdata,
+  projectionFnParam
+) {
+  // 地图对象
+  const mapObject3D = new THREE.Object3D();
+  // 地图数据
+  const basicFeatures = mapdata.features;
+  console.log('basicFeatures',basicFeatures)
+
+  const { center, scale } = projectionFnParam;
+
+  const projectionFn = d3
+    .geoMercator()
+    .center(center)
+    .scale(scale)
+    .translate([0, 0]);
+
+  const label2dData = []; // 存储自定义 2d 标签数据
+
+  // 每个省的数据
+  basicFeatures.forEach((basicFeatureItem) => {
+    // 每个省份的地图对象
+    const provinceMapObject3D = new THREE.Object3D();
+    // 将地图数据挂在到模型数据上
+    provinceMapObject3D.customProperties = basicFeatureItem.properties;
+
+    // 每个坐标类型
+    const featureType = basicFeatureItem.geometry.type;
+    // 每个坐标数组
+    const featureCoords =
+      basicFeatureItem.geometry.coordinates;
+    // 每个中心点位置
+    const featureCenterCoord =
+      basicFeatureItem.properties.centroid &&
+      projectionFn(basicFeatureItem.properties.centroid);
+    // 名字
+    const featureName = 'asask';
+    console.log('featureName',featureName)
+
+    if (featureCenterCoord) {
+      label2dData.push({
+        featureCenterCoord,
+        featureName
+      });
+    }
+
+    // MultiPolygon 类型
+    if (featureType === "MultiPolygon") {
+      featureCoords.forEach((multiPolygon) => {
+        multiPolygon.forEach((polygon) => {
+          const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+          provinceMapObject3D.add(mesh);
+          provinceMapObject3D.add(line);
+        });
+      });
+    }
+
+    // Polygon 类型
+    if (featureType === "Polygon") {
+      featureCoords.forEach((polygon) => {
+        const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+        provinceMapObject3D.add(mesh);
+        provinceMapObject3D.add(line);
+      });
+    }
+
+    mapObject3D.add(provinceMapObject3D);
+  });
+
+  return { mapObject3D, label2dData };
+}
+
+// 生成地图2D标签
+export function generateMapLabel2D(label2dData) {
+  const labelObject2D = new THREE.Object3D();
+  label2dData.forEach((item) => {
+    const { featureCenterCoord, featureName } = item;
+    debugger
+    const labelObjectItem = draw2dLabel(featureCenterCoord, featureName);
+    if (labelObjectItem) {
+      labelObject2D.add(labelObjectItem);
+    }
+  });
+  return labelObject2D;
+}
+
+// 生成地图spot点位
+export function generateMapSpot(label2dData) {
+  const spotObject3D = new THREE.Object3D();
+  const spotList = [];
+  label2dData.forEach((item) => {
+    const { featureCenterCoord } = item;
+      const spotObjectItem = drawSpot(featureCenterCoord);
+      if (spotObjectItem && spotObjectItem.circle && spotObjectItem.ring) {
+        spotObject3D.add(spotObjectItem.circle);
+        spotObject3D.add(spotObjectItem.ring);
+        spotList.push(spotObjectItem.ring);
+      }
+
+      //TODO:渲染铁塔
+      
+
+  });
+  return { spotObject3D, spotList };
+}
+
+// 绘制二维标签
+export const draw2dLabel = (coord, proviceName) => {
+  if (coord && coord.length) {
+    // 模版字符串
+    const innerHTML = `<div class="your-classname" style="color: #fff">${proviceName}</div>`;
+    const labelDiv = document.createElement("div");
+    labelDiv.innerHTML = innerHTML;
+    labelDiv.style.pointerEvents = "none"; // 禁用事件
+    const labelObject = new CSS2DObject(labelDiv);
+    labelObject.position.set(coord[0], -coord[1], mapConfig.label2dZIndex);
+    return labelObject;
+  }
+};
+
+// 绘制圆点
+export const drawSpot = (coord) => {
+  if (coord && coord.length) {
+    /**
+     * 绘制圆点
+     */
+    const spotGeometry = new THREE.CircleGeometry(0.2, 200);
+    const spotMaterial = new THREE.MeshBasicMaterial({
+      color: "#3EC5FB",
+      side: THREE.DoubleSide,
+    });
+    const circle = new THREE.Mesh(spotGeometry, spotMaterial);
+    circle.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+
+    // 圆环
+    const ringGeometry = new THREE.RingGeometry(0.2, 0.3, 50);
+    const ringMaterial = new THREE.MeshBasicMaterial({
+      color: "#3FC5FB",
+      side: THREE.DoubleSide,
+      transparent: true,
+    });
+    const ring = new THREE.Mesh(ringGeometry, ringMaterial);
+    ring.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+    return { circle, ring };
+  }
+};
+
+/**
+ * 线上移动物体
+ */
+export const drawflySpot = (curve) => {
+  const aGeo = new THREE.SphereGeometry(0.2);
+  const aMater = new THREE.MeshBasicMaterial({
+    color: "#77f077",
+    side: THREE.DoubleSide,
+  });
+  const aMesh = new THREE.Mesh(aGeo, aMater);
+  // 保存曲线实例
+  aMesh.curve = curve;
+  aMesh._s = 0;
+  return aMesh;
+};
+
+// 绘制两点链接飞线
+export const drawLineBetween2Spot = (
+  coordStart,
+  coordEnd
+) => {
+  const [x0, y0, z0] = [...coordStart, mapConfig.spotZIndex];
+  const [x1, y1, z1] = [...coordEnd, mapConfig.spotZIndex];
+  // 使用 QuadraticBezierCurve3 创建 三维二次贝塞尔曲线
+  const curve = new THREE.QuadraticBezierCurve3(
+    new THREE.Vector3(x0, -y0, z0),
+    new THREE.Vector3((x0 + x1) / 2, -(y0 + y1) / 2, 20),
+    new THREE.Vector3(x1, -y1, z1)
+  );
+
+  const flySpot = drawflySpot(curve);
+
+  const lineGeometry = new THREE.BufferGeometry();
+  // 获取曲线上50个点
+  const points = curve.getPoints(50);
+  const positions = [];
+  const colors = [];
+  const color = new THREE.Color();
+
+  // 给每个顶点设置演示 实现渐变
+  for (let j = 0; j < points.length; j++) {
+    color.setHSL(0.21 + j, 0.77, 0.55 + j * 0.0025); // 色
+    colors.push(color.r, color.g, color.b);
+    positions.push(points[j].x, points[j].y, points[j].z);
+  }
+  // 放入顶点 和 设置顶点颜色
+  lineGeometry.setAttribute(
+    "position",
+    new THREE.BufferAttribute(new Float32Array(positions), 3, true)
+  );
+  lineGeometry.setAttribute(
+    "color",
+    new THREE.BufferAttribute(new Float32Array(colors), 3, true)
+  );
+
+  const material = new THREE.LineBasicMaterial({
+    vertexColors: true,
+    // color: "red",
+    side: THREE.DoubleSide,
+  });
+  const flyLine = new THREE.Line(lineGeometry, material);
+
+  return { flyLine, flySpot };
+};

+ 301 - 0
zhsq_qk-ui/.history/src/map3d/drawFunc_20240617141339.js

@@ -0,0 +1,301 @@
+import * as THREE from "three";
+import * as d3 from "d3";
+import { CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { Line2 } from "three/examples/jsm/lines/Line2";
+import { LineGeometry } from "three/examples/jsm/lines/LineGeometry";
+import { LineMaterial } from "three/examples/jsm/lines/LineMaterial";
+// import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
+
+// import { ProjectionFnParamType } from "./index.vue";
+import { mapConfig } from "./mapConfig";
+
+// 绘制挤出的材质
+export function drawExtrudeMesh(
+  point,
+  projectionFn
+) {
+  const shape = new THREE.Shape();
+  const pointsArray = [];
+
+  for (let i = 0; i < point.length; i++) {
+    const [x, y] = projectionFn(point[i]); // 将每一个经纬度转化为坐标点
+    if (i === 0) {
+      shape.moveTo(x, -y);
+    }
+    shape.lineTo(x, -y);
+    pointsArray.push(x, -y, mapConfig.topLineZIndex);
+  }
+
+  const geometry = new THREE.ExtrudeGeometry(shape, {
+    depth: mapConfig.mapDepth, // 挤出的形状深度
+    bevelEnabled: false, // 对挤出的形状应用是否斜角
+  });
+
+  const material = new THREE.MeshPhongMaterial({
+    // color: mapConfig.mapColor,
+    color: mapConfig.mapColorGradient[Math.floor(Math.random() * 4)], // 随机颜色
+    // transparent: mapConfig.mapTransparent,
+    // opacity: mapConfig.mapOpacity,
+  });
+
+  const materialSide = new THREE.ShaderMaterial({
+    uniforms: {
+      color1: {
+        value: new THREE.Color(mapConfig.mapSideColor1),
+      },
+      color2: {
+        value: new THREE.Color(mapConfig.mapSideColor2),
+      },
+    },
+    vertexShader: `
+      varying vec3 vPosition;
+      void main() {
+        vPosition = position;
+        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+      }
+    `,
+    fragmentShader: `
+      uniform vec3 color1;
+      uniform vec3 color2;
+      varying vec3 vPosition;
+      void main() {
+        vec3 mixColor = mix(color1, color2, 0.5 - vPosition.z * 0.2); // 使用顶点坐标 z 分量来控制混合
+        gl_FragColor = vec4(mixColor, 1.0);
+      }
+    `,
+    //   wireframe: true,
+  });
+
+  const mesh = new THREE.Mesh(geometry, [material, materialSide]);
+  // userData 存储自定义属性
+  mesh.userData = {
+    isChangeColor: true,
+  };
+
+  // 边框线,赋值空间点坐标,3个一组
+  const lineGeometry = new LineGeometry();
+  lineGeometry.setPositions(pointsArray);
+
+  const lineMaterial = new LineMaterial({
+    color: mapConfig.topLineColor,
+    linewidth: mapConfig.topLineWidth,
+  });
+  lineMaterial.resolution.set(window.innerWidth, window.innerHeight);
+  const line = new Line2(lineGeometry, lineMaterial);
+
+  return { mesh, line };
+}
+
+// 生成地图3D模型
+export function generateMapObject3D(
+  mapdata,
+  projectionFnParam
+) {
+  // 地图对象
+  const mapObject3D = new THREE.Object3D();
+  // 地图数据
+  const basicFeatures = mapdata.features;
+  console.log('basicFeatures',basicFeatures)
+
+  const { center, scale } = projectionFnParam;
+
+  const projectionFn = d3
+    .geoMercator()
+    .center(center)
+    .scale(scale)
+    .translate([0, 0]);
+
+  const label2dData = []; // 存储自定义 2d 标签数据
+
+  // 每个省的数据
+  basicFeatures.forEach((basicFeatureItem) => {
+    // 每个省份的地图对象
+    const provinceMapObject3D = new THREE.Object3D();
+    // 将地图数据挂在到模型数据上
+    provinceMapObject3D.customProperties = basicFeatureItem.properties;
+
+    // 每个坐标类型
+    const featureType = basicFeatureItem.geometry.type;
+    // 每个坐标数组
+    const featureCoords =
+      basicFeatureItem.geometry.coordinates;
+    // 每个中心点位置
+    const featureCenterCoord =
+      basicFeatureItem.properties.centroid &&
+      projectionFn(basicFeatureItem.properties.centroid);
+    // 名字
+    const featureName ='汽开区';
+    console.log('featureName',featureName)
+
+    if (featureCenterCoord) {
+      label2dData.push({
+        featureCenterCoord,
+        featureName,
+      });
+    }
+
+    // MultiPolygon 类型
+    if (featureType === "MultiPolygon") {
+      featureCoords.forEach((multiPolygon) => {
+        multiPolygon.forEach((polygon) => {
+          const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+          provinceMapObject3D.add(mesh);
+          provinceMapObject3D.add(line);
+        });
+      });
+    }
+
+    // Polygon 类型
+    if (featureType === "Polygon") {
+      featureCoords.forEach((polygon) => {
+        const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+        provinceMapObject3D.add(mesh);
+        provinceMapObject3D.add(line);
+      });
+    }
+
+    mapObject3D.add(provinceMapObject3D);
+  });
+
+  return { mapObject3D, label2dData };
+}
+
+// 生成地图2D标签
+export function generateMapLabel2D(label2dData) {
+  const labelObject2D = new THREE.Object3D();
+  label2dData.forEach((item) => {
+    const { featureCenterCoord, featureName } = item;
+    debugger
+    const labelObjectItem = draw2dLabel(featureCenterCoord, featureName);
+    if (labelObjectItem) {
+      labelObject2D.add(labelObjectItem);
+    }
+  });
+  return labelObject2D;
+}
+
+// 生成地图spot点位
+export function generateMapSpot(label2dData) {
+  const spotObject3D = new THREE.Object3D();
+  const spotList = [];
+  label2dData.forEach((item) => {
+    const { featureCenterCoord } = item;
+      const spotObjectItem = drawSpot(featureCenterCoord);
+      if (spotObjectItem && spotObjectItem.circle && spotObjectItem.ring) {
+        spotObject3D.add(spotObjectItem.circle);
+        spotObject3D.add(spotObjectItem.ring);
+        spotList.push(spotObjectItem.ring);
+      }
+
+      //TODO:渲染铁塔
+      
+
+  });
+  return { spotObject3D, spotList };
+}
+
+// 绘制二维标签
+export const draw2dLabel = (coord, proviceName) => {
+  if (coord && coord.length) {
+    // 模版字符串
+    const innerHTML = `<div class="your-classname" style="color: #fff">${proviceName}</div>`;
+    const labelDiv = document.createElement("div");
+    labelDiv.innerHTML = innerHTML;
+    labelDiv.style.pointerEvents = "none"; // 禁用事件
+    const labelObject = new CSS2DObject(labelDiv);
+    labelObject.position.set(coord[0], -coord[1], mapConfig.label2dZIndex);
+    return labelObject;
+  }
+};
+
+// 绘制圆点
+export const drawSpot = (coord) => {
+  if (coord && coord.length) {
+    /**
+     * 绘制圆点
+     */
+    const spotGeometry = new THREE.CircleGeometry(0.2, 200);
+    const spotMaterial = new THREE.MeshBasicMaterial({
+      color: "#3EC5FB",
+      side: THREE.DoubleSide,
+    });
+    const circle = new THREE.Mesh(spotGeometry, spotMaterial);
+    circle.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+
+    // 圆环
+    const ringGeometry = new THREE.RingGeometry(0.2, 0.3, 50);
+    const ringMaterial = new THREE.MeshBasicMaterial({
+      color: "#3FC5FB",
+      side: THREE.DoubleSide,
+      transparent: true,
+    });
+    const ring = new THREE.Mesh(ringGeometry, ringMaterial);
+    ring.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+    return { circle, ring };
+  }
+};
+
+/**
+ * 线上移动物体
+ */
+export const drawflySpot = (curve) => {
+  const aGeo = new THREE.SphereGeometry(0.2);
+  const aMater = new THREE.MeshBasicMaterial({
+    color: "#77f077",
+    side: THREE.DoubleSide,
+  });
+  const aMesh = new THREE.Mesh(aGeo, aMater);
+  // 保存曲线实例
+  aMesh.curve = curve;
+  aMesh._s = 0;
+  return aMesh;
+};
+
+// 绘制两点链接飞线
+export const drawLineBetween2Spot = (
+  coordStart,
+  coordEnd
+) => {
+  const [x0, y0, z0] = [...coordStart, mapConfig.spotZIndex];
+  const [x1, y1, z1] = [...coordEnd, mapConfig.spotZIndex];
+  // 使用 QuadraticBezierCurve3 创建 三维二次贝塞尔曲线
+  const curve = new THREE.QuadraticBezierCurve3(
+    new THREE.Vector3(x0, -y0, z0),
+    new THREE.Vector3((x0 + x1) / 2, -(y0 + y1) / 2, 20),
+    new THREE.Vector3(x1, -y1, z1)
+  );
+
+  const flySpot = drawflySpot(curve);
+
+  const lineGeometry = new THREE.BufferGeometry();
+  // 获取曲线上50个点
+  const points = curve.getPoints(50);
+  const positions = [];
+  const colors = [];
+  const color = new THREE.Color();
+
+  // 给每个顶点设置演示 实现渐变
+  for (let j = 0; j < points.length; j++) {
+    color.setHSL(0.21 + j, 0.77, 0.55 + j * 0.0025); // 色
+    colors.push(color.r, color.g, color.b);
+    positions.push(points[j].x, points[j].y, points[j].z);
+  }
+  // 放入顶点 和 设置顶点颜色
+  lineGeometry.setAttribute(
+    "position",
+    new THREE.BufferAttribute(new Float32Array(positions), 3, true)
+  );
+  lineGeometry.setAttribute(
+    "color",
+    new THREE.BufferAttribute(new Float32Array(colors), 3, true)
+  );
+
+  const material = new THREE.LineBasicMaterial({
+    vertexColors: true,
+    // color: "red",
+    side: THREE.DoubleSide,
+  });
+  const flyLine = new THREE.Line(lineGeometry, material);
+
+  return { flyLine, flySpot };
+};

+ 301 - 0
zhsq_qk-ui/.history/src/map3d/drawFunc_20240617141423.js

@@ -0,0 +1,301 @@
+import * as THREE from "three";
+import * as d3 from "d3";
+import { CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { Line2 } from "three/examples/jsm/lines/Line2";
+import { LineGeometry } from "three/examples/jsm/lines/LineGeometry";
+import { LineMaterial } from "three/examples/jsm/lines/LineMaterial";
+// import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
+
+// import { ProjectionFnParamType } from "./index.vue";
+import { mapConfig } from "./mapConfig";
+
+// 绘制挤出的材质
+export function drawExtrudeMesh(
+  point,
+  projectionFn
+) {
+  const shape = new THREE.Shape();
+  const pointsArray = [];
+
+  for (let i = 0; i < point.length; i++) {
+    const [x, y] = projectionFn(point[i]); // 将每一个经纬度转化为坐标点
+    if (i === 0) {
+      shape.moveTo(x, -y);
+    }
+    shape.lineTo(x, -y);
+    pointsArray.push(x, -y, mapConfig.topLineZIndex);
+  }
+
+  const geometry = new THREE.ExtrudeGeometry(shape, {
+    depth: mapConfig.mapDepth, // 挤出的形状深度
+    bevelEnabled: false, // 对挤出的形状应用是否斜角
+  });
+
+  const material = new THREE.MeshPhongMaterial({
+    // color: mapConfig.mapColor,
+    color: mapConfig.mapColorGradient[Math.floor(Math.random() * 4)], // 随机颜色
+    // transparent: mapConfig.mapTransparent,
+    // opacity: mapConfig.mapOpacity,
+  });
+
+  const materialSide = new THREE.ShaderMaterial({
+    uniforms: {
+      color1: {
+        value: new THREE.Color(mapConfig.mapSideColor1),
+      },
+      color2: {
+        value: new THREE.Color(mapConfig.mapSideColor2),
+      },
+    },
+    vertexShader: `
+      varying vec3 vPosition;
+      void main() {
+        vPosition = position;
+        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+      }
+    `,
+    fragmentShader: `
+      uniform vec3 color1;
+      uniform vec3 color2;
+      varying vec3 vPosition;
+      void main() {
+        vec3 mixColor = mix(color1, color2, 0.5 - vPosition.z * 0.2); // 使用顶点坐标 z 分量来控制混合
+        gl_FragColor = vec4(mixColor, 1.0);
+      }
+    `,
+    //   wireframe: true,
+  });
+
+  const mesh = new THREE.Mesh(geometry, [material, materialSide]);
+  // userData 存储自定义属性
+  mesh.userData = {
+    isChangeColor: true,
+  };
+
+  // 边框线,赋值空间点坐标,3个一组
+  const lineGeometry = new LineGeometry();
+  lineGeometry.setPositions(pointsArray);
+
+  const lineMaterial = new LineMaterial({
+    color: mapConfig.topLineColor,
+    linewidth: mapConfig.topLineWidth,
+  });
+  lineMaterial.resolution.set(window.innerWidth, window.innerHeight);
+  const line = new Line2(lineGeometry, lineMaterial);
+
+  return { mesh, line };
+}
+
+// 生成地图3D模型
+export function generateMapObject3D(
+  mapdata,
+  projectionFnParam
+) {
+  // 地图对象
+  const mapObject3D = new THREE.Object3D();
+  // 地图数据
+  const basicFeatures = mapdata.features;
+  console.log('basicFeatures',basicFeatures)
+
+  const { center, scale } = projectionFnParam;
+
+  const projectionFn = d3
+    .geoMercator()
+    .center(center)
+    .scale(scale)
+    .translate([0, 0]);
+
+  const label2dData = []; // 存储自定义 2d 标签数据
+
+  // 每个省的数据
+  basicFeatures.forEach((basicFeatureItem) => {
+    // 每个省份的地图对象
+    const provinceMapObject3D = new THREE.Object3D();
+    // 将地图数据挂在到模型数据上
+    provinceMapObject3D.customProperties = basicFeatureItem.properties;
+
+    // 每个坐标类型
+    const featureType = basicFeatureItem.geometry.type;
+    // 每个坐标数组
+    const featureCoords =
+      basicFeatureItem.geometry.coordinates;
+    // 每个中心点位置
+    const featureCenterCoord =
+      basicFeatureItem.properties.centroid &&
+      projectionFn(basicFeatureItem.properties.centroid);
+    // 名字
+    const featureName = basicFeatureItem.properties.name;
+    console.log('featureName',featureName)
+
+    if (featureCenterCoord) {
+      label2dData.push({
+        featureCenterCoord,
+        featureName,
+      });
+    }
+
+    // MultiPolygon 类型
+    if (featureType === "MultiPolygon") {
+      featureCoords.forEach((multiPolygon) => {
+        multiPolygon.forEach((polygon) => {
+          const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+          provinceMapObject3D.add(mesh);
+          provinceMapObject3D.add(line);
+        });
+      });
+    }
+
+    // Polygon 类型
+    if (featureType === "Polygon") {
+      featureCoords.forEach((polygon) => {
+        const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+        provinceMapObject3D.add(mesh);
+        provinceMapObject3D.add(line);
+      });
+    }
+
+    mapObject3D.add(provinceMapObject3D);
+  });
+
+  return { mapObject3D, label2dData };
+}
+
+// 生成地图2D标签
+export function generateMapLabel2D(label2dData) {
+  const labelObject2D = new THREE.Object3D();
+  label2dData.forEach((item) => {
+    const { featureCenterCoord, featureName } = item;
+    debugger
+    const labelObjectItem = draw2dLabel(featureCenterCoord, featureName);
+    if (labelObjectItem) {
+      labelObject2D.add(labelObjectItem);
+    }
+  });
+  return labelObject2D;
+}
+
+// 生成地图spot点位
+export function generateMapSpot(label2dData) {
+  const spotObject3D = new THREE.Object3D();
+  const spotList = [];
+  label2dData.forEach((item) => {
+    const { featureCenterCoord } = item;
+      const spotObjectItem = drawSpot(featureCenterCoord);
+      if (spotObjectItem && spotObjectItem.circle && spotObjectItem.ring) {
+        spotObject3D.add(spotObjectItem.circle);
+        spotObject3D.add(spotObjectItem.ring);
+        spotList.push(spotObjectItem.ring);
+      }
+
+      //TODO:渲染铁塔
+      
+
+  });
+  return { spotObject3D, spotList };
+}
+
+// 绘制二维标签
+export const draw2dLabel = (coord, proviceName) => {
+  if (coord && coord.length) {
+    // 模版字符串
+    const innerHTML = `<div class="your-classname" style="color: #fff">${proviceName}</div>`;
+    const labelDiv = document.createElement("div");
+    labelDiv.innerHTML = innerHTML;
+    labelDiv.style.pointerEvents = "none"; // 禁用事件
+    const labelObject = new CSS2DObject(labelDiv);
+    labelObject.position.set(coord[0], -coord[1], mapConfig.label2dZIndex);
+    return labelObject;
+  }
+};
+
+// 绘制圆点
+export const drawSpot = (coord) => {
+  if (coord && coord.length) {
+    /**
+     * 绘制圆点
+     */
+    const spotGeometry = new THREE.CircleGeometry(0.2, 200);
+    const spotMaterial = new THREE.MeshBasicMaterial({
+      color: "#3EC5FB",
+      side: THREE.DoubleSide,
+    });
+    const circle = new THREE.Mesh(spotGeometry, spotMaterial);
+    circle.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+
+    // 圆环
+    const ringGeometry = new THREE.RingGeometry(0.2, 0.3, 50);
+    const ringMaterial = new THREE.MeshBasicMaterial({
+      color: "#3FC5FB",
+      side: THREE.DoubleSide,
+      transparent: true,
+    });
+    const ring = new THREE.Mesh(ringGeometry, ringMaterial);
+    ring.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+    return { circle, ring };
+  }
+};
+
+/**
+ * 线上移动物体
+ */
+export const drawflySpot = (curve) => {
+  const aGeo = new THREE.SphereGeometry(0.2);
+  const aMater = new THREE.MeshBasicMaterial({
+    color: "#77f077",
+    side: THREE.DoubleSide,
+  });
+  const aMesh = new THREE.Mesh(aGeo, aMater);
+  // 保存曲线实例
+  aMesh.curve = curve;
+  aMesh._s = 0;
+  return aMesh;
+};
+
+// 绘制两点链接飞线
+export const drawLineBetween2Spot = (
+  coordStart,
+  coordEnd
+) => {
+  const [x0, y0, z0] = [...coordStart, mapConfig.spotZIndex];
+  const [x1, y1, z1] = [...coordEnd, mapConfig.spotZIndex];
+  // 使用 QuadraticBezierCurve3 创建 三维二次贝塞尔曲线
+  const curve = new THREE.QuadraticBezierCurve3(
+    new THREE.Vector3(x0, -y0, z0),
+    new THREE.Vector3((x0 + x1) / 2, -(y0 + y1) / 2, 20),
+    new THREE.Vector3(x1, -y1, z1)
+  );
+
+  const flySpot = drawflySpot(curve);
+
+  const lineGeometry = new THREE.BufferGeometry();
+  // 获取曲线上50个点
+  const points = curve.getPoints(50);
+  const positions = [];
+  const colors = [];
+  const color = new THREE.Color();
+
+  // 给每个顶点设置演示 实现渐变
+  for (let j = 0; j < points.length; j++) {
+    color.setHSL(0.21 + j, 0.77, 0.55 + j * 0.0025); // 色
+    colors.push(color.r, color.g, color.b);
+    positions.push(points[j].x, points[j].y, points[j].z);
+  }
+  // 放入顶点 和 设置顶点颜色
+  lineGeometry.setAttribute(
+    "position",
+    new THREE.BufferAttribute(new Float32Array(positions), 3, true)
+  );
+  lineGeometry.setAttribute(
+    "color",
+    new THREE.BufferAttribute(new Float32Array(colors), 3, true)
+  );
+
+  const material = new THREE.LineBasicMaterial({
+    vertexColors: true,
+    // color: "red",
+    side: THREE.DoubleSide,
+  });
+  const flyLine = new THREE.Line(lineGeometry, material);
+
+  return { flyLine, flySpot };
+};

+ 301 - 0
zhsq_qk-ui/.history/src/map3d/drawFunc_20240617141509.js

@@ -0,0 +1,301 @@
+import * as THREE from "three";
+import * as d3 from "d3";
+import { CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { Line2 } from "three/examples/jsm/lines/Line2";
+import { LineGeometry } from "three/examples/jsm/lines/LineGeometry";
+import { LineMaterial } from "three/examples/jsm/lines/LineMaterial";
+// import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
+
+// import { ProjectionFnParamType } from "./index.vue";
+import { mapConfig } from "./mapConfig";
+
+// 绘制挤出的材质
+export function drawExtrudeMesh(
+  point,
+  projectionFn
+) {
+  const shape = new THREE.Shape();
+  const pointsArray = [];
+
+  for (let i = 0; i < point.length; i++) {
+    const [x, y] = projectionFn(point[i]); // 将每一个经纬度转化为坐标点
+    if (i === 0) {
+      shape.moveTo(x, -y);
+    }
+    shape.lineTo(x, -y);
+    pointsArray.push(x, -y, mapConfig.topLineZIndex);
+  }
+
+  const geometry = new THREE.ExtrudeGeometry(shape, {
+    depth: mapConfig.mapDepth, // 挤出的形状深度
+    bevelEnabled: false, // 对挤出的形状应用是否斜角
+  });
+
+  const material = new THREE.MeshPhongMaterial({
+    // color: mapConfig.mapColor,
+    color: mapConfig.mapColorGradient[Math.floor(Math.random() * 4)], // 随机颜色
+    // transparent: mapConfig.mapTransparent,
+    // opacity: mapConfig.mapOpacity,
+  });
+
+  const materialSide = new THREE.ShaderMaterial({
+    uniforms: {
+      color1: {
+        value: new THREE.Color(mapConfig.mapSideColor1),
+      },
+      color2: {
+        value: new THREE.Color(mapConfig.mapSideColor2),
+      },
+    },
+    vertexShader: `
+      varying vec3 vPosition;
+      void main() {
+        vPosition = position;
+        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+      }
+    `,
+    fragmentShader: `
+      uniform vec3 color1;
+      uniform vec3 color2;
+      varying vec3 vPosition;
+      void main() {
+        vec3 mixColor = mix(color1, color2, 0.5 - vPosition.z * 0.2); // 使用顶点坐标 z 分量来控制混合
+        gl_FragColor = vec4(mixColor, 1.0);
+      }
+    `,
+    //   wireframe: true,
+  });
+
+  const mesh = new THREE.Mesh(geometry, [material, materialSide]);
+  // userData 存储自定义属性
+  mesh.userData = {
+    isChangeColor: true,
+  };
+
+  // 边框线,赋值空间点坐标,3个一组
+  const lineGeometry = new LineGeometry();
+  lineGeometry.setPositions(pointsArray);
+
+  const lineMaterial = new LineMaterial({
+    color: mapConfig.topLineColor,
+    linewidth: mapConfig.topLineWidth,
+  });
+  lineMaterial.resolution.set(window.innerWidth, window.innerHeight);
+  const line = new Line2(lineGeometry, lineMaterial);
+
+  return { mesh, line };
+}
+
+// 生成地图3D模型
+export function generateMapObject3D(
+  mapdata,
+  projectionFnParam
+) {
+  // 地图对象
+  const mapObject3D = new THREE.Object3D();
+  // 地图数据
+  const basicFeatures = mapdata.features;
+  console.log('basicFeatures',basicFeatures)
+
+  const { center, scale } = projectionFnParam;
+
+  const projectionFn = d3
+    .geoMercator()
+    .center(center)
+    .scale(scale)
+    .translate([0, 0]);
+
+  const label2dData = []; // 存储自定义 2d 标签数据
+
+  // 每个省的数据
+  basicFeatures.forEach((basicFeatureItem) => {
+    // 每个省份的地图对象
+    const provinceMapObject3D = new THREE.Object3D();
+    // 将地图数据挂在到模型数据上
+    provinceMapObject3D.customProperties = basicFeatureItem.properties;
+
+    // 每个坐标类型
+    const featureType = basicFeatureItem.geometry.type;
+    // 每个坐标数组
+    const featureCoords =
+      basicFeatureItem.geometry.coordinates;
+    // 每个中心点位置
+    const featureCenterCoord =
+      basicFeatureItem.properties.centroid &&
+      projectionFn(basicFeatureItem.properties.centroid);
+    // 名字
+    const featureName = basicFeatureItem.properties.name;
+    console.log('featureName',featureName)
+
+    // if (featureCenterCoord) {
+      label2dData.push({
+        featureCenterCoord,
+        featureName,
+      });
+    // }
+
+    // MultiPolygon 类型
+    if (featureType === "MultiPolygon") {
+      featureCoords.forEach((multiPolygon) => {
+        multiPolygon.forEach((polygon) => {
+          const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+          provinceMapObject3D.add(mesh);
+          provinceMapObject3D.add(line);
+        });
+      });
+    }
+
+    // Polygon 类型
+    if (featureType === "Polygon") {
+      featureCoords.forEach((polygon) => {
+        const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+        provinceMapObject3D.add(mesh);
+        provinceMapObject3D.add(line);
+      });
+    }
+
+    mapObject3D.add(provinceMapObject3D);
+  });
+
+  return { mapObject3D, label2dData };
+}
+
+// 生成地图2D标签
+export function generateMapLabel2D(label2dData) {
+  const labelObject2D = new THREE.Object3D();
+  label2dData.forEach((item) => {
+    const { featureCenterCoord, featureName } = item;
+    debugger
+    const labelObjectItem = draw2dLabel(featureCenterCoord, featureName);
+    if (labelObjectItem) {
+      labelObject2D.add(labelObjectItem);
+    }
+  });
+  return labelObject2D;
+}
+
+// 生成地图spot点位
+export function generateMapSpot(label2dData) {
+  const spotObject3D = new THREE.Object3D();
+  const spotList = [];
+  label2dData.forEach((item) => {
+    const { featureCenterCoord } = item;
+      const spotObjectItem = drawSpot(featureCenterCoord);
+      if (spotObjectItem && spotObjectItem.circle && spotObjectItem.ring) {
+        spotObject3D.add(spotObjectItem.circle);
+        spotObject3D.add(spotObjectItem.ring);
+        spotList.push(spotObjectItem.ring);
+      }
+
+      //TODO:渲染铁塔
+      
+
+  });
+  return { spotObject3D, spotList };
+}
+
+// 绘制二维标签
+export const draw2dLabel = (coord, proviceName) => {
+  if (coord && coord.length) {
+    // 模版字符串
+    const innerHTML = `<div class="your-classname" style="color: #fff">${proviceName}</div>`;
+    const labelDiv = document.createElement("div");
+    labelDiv.innerHTML = innerHTML;
+    labelDiv.style.pointerEvents = "none"; // 禁用事件
+    const labelObject = new CSS2DObject(labelDiv);
+    labelObject.position.set(coord[0], -coord[1], mapConfig.label2dZIndex);
+    return labelObject;
+  }
+};
+
+// 绘制圆点
+export const drawSpot = (coord) => {
+  if (coord && coord.length) {
+    /**
+     * 绘制圆点
+     */
+    const spotGeometry = new THREE.CircleGeometry(0.2, 200);
+    const spotMaterial = new THREE.MeshBasicMaterial({
+      color: "#3EC5FB",
+      side: THREE.DoubleSide,
+    });
+    const circle = new THREE.Mesh(spotGeometry, spotMaterial);
+    circle.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+
+    // 圆环
+    const ringGeometry = new THREE.RingGeometry(0.2, 0.3, 50);
+    const ringMaterial = new THREE.MeshBasicMaterial({
+      color: "#3FC5FB",
+      side: THREE.DoubleSide,
+      transparent: true,
+    });
+    const ring = new THREE.Mesh(ringGeometry, ringMaterial);
+    ring.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+    return { circle, ring };
+  }
+};
+
+/**
+ * 线上移动物体
+ */
+export const drawflySpot = (curve) => {
+  const aGeo = new THREE.SphereGeometry(0.2);
+  const aMater = new THREE.MeshBasicMaterial({
+    color: "#77f077",
+    side: THREE.DoubleSide,
+  });
+  const aMesh = new THREE.Mesh(aGeo, aMater);
+  // 保存曲线实例
+  aMesh.curve = curve;
+  aMesh._s = 0;
+  return aMesh;
+};
+
+// 绘制两点链接飞线
+export const drawLineBetween2Spot = (
+  coordStart,
+  coordEnd
+) => {
+  const [x0, y0, z0] = [...coordStart, mapConfig.spotZIndex];
+  const [x1, y1, z1] = [...coordEnd, mapConfig.spotZIndex];
+  // 使用 QuadraticBezierCurve3 创建 三维二次贝塞尔曲线
+  const curve = new THREE.QuadraticBezierCurve3(
+    new THREE.Vector3(x0, -y0, z0),
+    new THREE.Vector3((x0 + x1) / 2, -(y0 + y1) / 2, 20),
+    new THREE.Vector3(x1, -y1, z1)
+  );
+
+  const flySpot = drawflySpot(curve);
+
+  const lineGeometry = new THREE.BufferGeometry();
+  // 获取曲线上50个点
+  const points = curve.getPoints(50);
+  const positions = [];
+  const colors = [];
+  const color = new THREE.Color();
+
+  // 给每个顶点设置演示 实现渐变
+  for (let j = 0; j < points.length; j++) {
+    color.setHSL(0.21 + j, 0.77, 0.55 + j * 0.0025); // 色
+    colors.push(color.r, color.g, color.b);
+    positions.push(points[j].x, points[j].y, points[j].z);
+  }
+  // 放入顶点 和 设置顶点颜色
+  lineGeometry.setAttribute(
+    "position",
+    new THREE.BufferAttribute(new Float32Array(positions), 3, true)
+  );
+  lineGeometry.setAttribute(
+    "color",
+    new THREE.BufferAttribute(new Float32Array(colors), 3, true)
+  );
+
+  const material = new THREE.LineBasicMaterial({
+    vertexColors: true,
+    // color: "red",
+    side: THREE.DoubleSide,
+  });
+  const flyLine = new THREE.Line(lineGeometry, material);
+
+  return { flyLine, flySpot };
+};

+ 300 - 0
zhsq_qk-ui/.history/src/map3d/drawFunc_20240617141538.js

@@ -0,0 +1,300 @@
+import * as THREE from "three";
+import * as d3 from "d3";
+import { CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { Line2 } from "three/examples/jsm/lines/Line2";
+import { LineGeometry } from "three/examples/jsm/lines/LineGeometry";
+import { LineMaterial } from "three/examples/jsm/lines/LineMaterial";
+// import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
+
+// import { ProjectionFnParamType } from "./index.vue";
+import { mapConfig } from "./mapConfig";
+
+// 绘制挤出的材质
+export function drawExtrudeMesh(
+  point,
+  projectionFn
+) {
+  const shape = new THREE.Shape();
+  const pointsArray = [];
+
+  for (let i = 0; i < point.length; i++) {
+    const [x, y] = projectionFn(point[i]); // 将每一个经纬度转化为坐标点
+    if (i === 0) {
+      shape.moveTo(x, -y);
+    }
+    shape.lineTo(x, -y);
+    pointsArray.push(x, -y, mapConfig.topLineZIndex);
+  }
+
+  const geometry = new THREE.ExtrudeGeometry(shape, {
+    depth: mapConfig.mapDepth, // 挤出的形状深度
+    bevelEnabled: false, // 对挤出的形状应用是否斜角
+  });
+
+  const material = new THREE.MeshPhongMaterial({
+    // color: mapConfig.mapColor,
+    color: mapConfig.mapColorGradient[Math.floor(Math.random() * 4)], // 随机颜色
+    // transparent: mapConfig.mapTransparent,
+    // opacity: mapConfig.mapOpacity,
+  });
+
+  const materialSide = new THREE.ShaderMaterial({
+    uniforms: {
+      color1: {
+        value: new THREE.Color(mapConfig.mapSideColor1),
+      },
+      color2: {
+        value: new THREE.Color(mapConfig.mapSideColor2),
+      },
+    },
+    vertexShader: `
+      varying vec3 vPosition;
+      void main() {
+        vPosition = position;
+        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+      }
+    `,
+    fragmentShader: `
+      uniform vec3 color1;
+      uniform vec3 color2;
+      varying vec3 vPosition;
+      void main() {
+        vec3 mixColor = mix(color1, color2, 0.5 - vPosition.z * 0.2); // 使用顶点坐标 z 分量来控制混合
+        gl_FragColor = vec4(mixColor, 1.0);
+      }
+    `,
+    //   wireframe: true,
+  });
+
+  const mesh = new THREE.Mesh(geometry, [material, materialSide]);
+  // userData 存储自定义属性
+  mesh.userData = {
+    isChangeColor: true,
+  };
+
+  // 边框线,赋值空间点坐标,3个一组
+  const lineGeometry = new LineGeometry();
+  lineGeometry.setPositions(pointsArray);
+
+  const lineMaterial = new LineMaterial({
+    color: mapConfig.topLineColor,
+    linewidth: mapConfig.topLineWidth,
+  });
+  lineMaterial.resolution.set(window.innerWidth, window.innerHeight);
+  const line = new Line2(lineGeometry, lineMaterial);
+
+  return { mesh, line };
+}
+
+// 生成地图3D模型
+export function generateMapObject3D(
+  mapdata,
+  projectionFnParam
+) {
+  // 地图对象
+  const mapObject3D = new THREE.Object3D();
+  // 地图数据
+  const basicFeatures = mapdata.features;
+  console.log('basicFeatures',basicFeatures)
+
+  const { center, scale } = projectionFnParam;
+
+  const projectionFn = d3
+    .geoMercator()
+    .center(center)
+    .scale(scale)
+    .translate([0, 0]);
+
+  const label2dData = []; // 存储自定义 2d 标签数据
+
+  // 每个省的数据
+  basicFeatures.forEach((basicFeatureItem) => {
+    // 每个省份的地图对象
+    const provinceMapObject3D = new THREE.Object3D();
+    // 将地图数据挂在到模型数据上
+    provinceMapObject3D.customProperties = basicFeatureItem.properties;
+
+    // 每个坐标类型
+    const featureType = basicFeatureItem.geometry.type;
+    // 每个坐标数组
+    const featureCoords =
+      basicFeatureItem.geometry.coordinates;
+    // 每个中心点位置
+    const featureCenterCoord =
+      basicFeatureItem.properties.centroid &&
+      projectionFn(basicFeatureItem.properties.centroid);
+    // 名字
+    const featureName = basicFeatureItem.properties.name;
+    console.log('featureName',featureName)
+
+    // if (featureCenterCoord) {
+      label2dData.push({
+        featureCenterCoord,
+        featureName,
+      });
+    // }
+
+    // MultiPolygon 类型
+    if (featureType === "MultiPolygon") {
+      featureCoords.forEach((multiPolygon) => {
+        multiPolygon.forEach((polygon) => {
+          const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+          provinceMapObject3D.add(mesh);
+          provinceMapObject3D.add(line);
+        });
+      });
+    }
+
+    // Polygon 类型
+    if (featureType === "Polygon") {
+      featureCoords.forEach((polygon) => {
+        const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+        provinceMapObject3D.add(mesh);
+        provinceMapObject3D.add(line);
+      });
+    }
+
+    mapObject3D.add(provinceMapObject3D);
+  });
+
+  return { mapObject3D, label2dData };
+}
+
+// 生成地图2D标签
+export function generateMapLabel2D(label2dData) {
+  const labelObject2D = new THREE.Object3D();
+  label2dData.forEach((item) => {
+    const { featureCenterCoord, featureName } = item;
+    const labelObjectItem = draw2dLabel(featureCenterCoord, 'asasa');
+    if (labelObjectItem) {
+      labelObject2D.add(labelObjectItem);
+    }
+  });
+  return labelObject2D;
+}
+
+// 生成地图spot点位
+export function generateMapSpot(label2dData) {
+  const spotObject3D = new THREE.Object3D();
+  const spotList = [];
+  label2dData.forEach((item) => {
+    const { featureCenterCoord } = item;
+      const spotObjectItem = drawSpot(featureCenterCoord);
+      if (spotObjectItem && spotObjectItem.circle && spotObjectItem.ring) {
+        spotObject3D.add(spotObjectItem.circle);
+        spotObject3D.add(spotObjectItem.ring);
+        spotList.push(spotObjectItem.ring);
+      }
+
+      //TODO:渲染铁塔
+      
+
+  });
+  return { spotObject3D, spotList };
+}
+
+// 绘制二维标签
+export const draw2dLabel = (coord, proviceName) => {
+  if (coord && coord.length) {
+    // 模版字符串
+    const innerHTML = `<div class="your-classname" style="color: #fff">${proviceName}</div>`;
+    const labelDiv = document.createElement("div");
+    labelDiv.innerHTML = innerHTML;
+    labelDiv.style.pointerEvents = "none"; // 禁用事件
+    const labelObject = new CSS2DObject(labelDiv);
+    labelObject.position.set(coord[0], -coord[1], mapConfig.label2dZIndex);
+    return labelObject;
+  }
+};
+
+// 绘制圆点
+export const drawSpot = (coord) => {
+  if (coord && coord.length) {
+    /**
+     * 绘制圆点
+     */
+    const spotGeometry = new THREE.CircleGeometry(0.2, 200);
+    const spotMaterial = new THREE.MeshBasicMaterial({
+      color: "#3EC5FB",
+      side: THREE.DoubleSide,
+    });
+    const circle = new THREE.Mesh(spotGeometry, spotMaterial);
+    circle.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+
+    // 圆环
+    const ringGeometry = new THREE.RingGeometry(0.2, 0.3, 50);
+    const ringMaterial = new THREE.MeshBasicMaterial({
+      color: "#3FC5FB",
+      side: THREE.DoubleSide,
+      transparent: true,
+    });
+    const ring = new THREE.Mesh(ringGeometry, ringMaterial);
+    ring.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+    return { circle, ring };
+  }
+};
+
+/**
+ * 线上移动物体
+ */
+export const drawflySpot = (curve) => {
+  const aGeo = new THREE.SphereGeometry(0.2);
+  const aMater = new THREE.MeshBasicMaterial({
+    color: "#77f077",
+    side: THREE.DoubleSide,
+  });
+  const aMesh = new THREE.Mesh(aGeo, aMater);
+  // 保存曲线实例
+  aMesh.curve = curve;
+  aMesh._s = 0;
+  return aMesh;
+};
+
+// 绘制两点链接飞线
+export const drawLineBetween2Spot = (
+  coordStart,
+  coordEnd
+) => {
+  const [x0, y0, z0] = [...coordStart, mapConfig.spotZIndex];
+  const [x1, y1, z1] = [...coordEnd, mapConfig.spotZIndex];
+  // 使用 QuadraticBezierCurve3 创建 三维二次贝塞尔曲线
+  const curve = new THREE.QuadraticBezierCurve3(
+    new THREE.Vector3(x0, -y0, z0),
+    new THREE.Vector3((x0 + x1) / 2, -(y0 + y1) / 2, 20),
+    new THREE.Vector3(x1, -y1, z1)
+  );
+
+  const flySpot = drawflySpot(curve);
+
+  const lineGeometry = new THREE.BufferGeometry();
+  // 获取曲线上50个点
+  const points = curve.getPoints(50);
+  const positions = [];
+  const colors = [];
+  const color = new THREE.Color();
+
+  // 给每个顶点设置演示 实现渐变
+  for (let j = 0; j < points.length; j++) {
+    color.setHSL(0.21 + j, 0.77, 0.55 + j * 0.0025); // 色
+    colors.push(color.r, color.g, color.b);
+    positions.push(points[j].x, points[j].y, points[j].z);
+  }
+  // 放入顶点 和 设置顶点颜色
+  lineGeometry.setAttribute(
+    "position",
+    new THREE.BufferAttribute(new Float32Array(positions), 3, true)
+  );
+  lineGeometry.setAttribute(
+    "color",
+    new THREE.BufferAttribute(new Float32Array(colors), 3, true)
+  );
+
+  const material = new THREE.LineBasicMaterial({
+    vertexColors: true,
+    // color: "red",
+    side: THREE.DoubleSide,
+  });
+  const flyLine = new THREE.Line(lineGeometry, material);
+
+  return { flyLine, flySpot };
+};

+ 302 - 0
zhsq_qk-ui/.history/src/map3d/drawFunc_20240617141633.js

@@ -0,0 +1,302 @@
+import * as THREE from "three";
+import * as d3 from "d3";
+import { CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { Line2 } from "three/examples/jsm/lines/Line2";
+import { LineGeometry } from "three/examples/jsm/lines/LineGeometry";
+import { LineMaterial } from "three/examples/jsm/lines/LineMaterial";
+// import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
+
+// import { ProjectionFnParamType } from "./index.vue";
+import { mapConfig } from "./mapConfig";
+
+// 绘制挤出的材质
+export function drawExtrudeMesh(
+  point,
+  projectionFn
+) {
+  const shape = new THREE.Shape();
+  const pointsArray = [];
+
+  for (let i = 0; i < point.length; i++) {
+    const [x, y] = projectionFn(point[i]); // 将每一个经纬度转化为坐标点
+    if (i === 0) {
+      shape.moveTo(x, -y);
+    }
+    shape.lineTo(x, -y);
+    pointsArray.push(x, -y, mapConfig.topLineZIndex);
+  }
+
+  const geometry = new THREE.ExtrudeGeometry(shape, {
+    depth: mapConfig.mapDepth, // 挤出的形状深度
+    bevelEnabled: false, // 对挤出的形状应用是否斜角
+  });
+
+  const material = new THREE.MeshPhongMaterial({
+    // color: mapConfig.mapColor,
+    color: mapConfig.mapColorGradient[Math.floor(Math.random() * 4)], // 随机颜色
+    // transparent: mapConfig.mapTransparent,
+    // opacity: mapConfig.mapOpacity,
+  });
+
+  const materialSide = new THREE.ShaderMaterial({
+    uniforms: {
+      color1: {
+        value: new THREE.Color(mapConfig.mapSideColor1),
+      },
+      color2: {
+        value: new THREE.Color(mapConfig.mapSideColor2),
+      },
+    },
+    vertexShader: `
+      varying vec3 vPosition;
+      void main() {
+        vPosition = position;
+        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+      }
+    `,
+    fragmentShader: `
+      uniform vec3 color1;
+      uniform vec3 color2;
+      varying vec3 vPosition;
+      void main() {
+        vec3 mixColor = mix(color1, color2, 0.5 - vPosition.z * 0.2); // 使用顶点坐标 z 分量来控制混合
+        gl_FragColor = vec4(mixColor, 1.0);
+      }
+    `,
+    //   wireframe: true,
+  });
+
+  const mesh = new THREE.Mesh(geometry, [material, materialSide]);
+  // userData 存储自定义属性
+  mesh.userData = {
+    isChangeColor: true,
+  };
+
+  // 边框线,赋值空间点坐标,3个一组
+  const lineGeometry = new LineGeometry();
+  lineGeometry.setPositions(pointsArray);
+
+  const lineMaterial = new LineMaterial({
+    color: mapConfig.topLineColor,
+    linewidth: mapConfig.topLineWidth,
+  });
+  lineMaterial.resolution.set(window.innerWidth, window.innerHeight);
+  const line = new Line2(lineGeometry, lineMaterial);
+
+  return { mesh, line };
+}
+
+// 生成地图3D模型
+export function generateMapObject3D(
+  mapdata,
+  projectionFnParam
+) {
+  // 地图对象
+  const mapObject3D = new THREE.Object3D();
+  // 地图数据
+  const basicFeatures = mapdata.features;
+  console.log('basicFeatures',basicFeatures)
+
+  const { center, scale } = projectionFnParam;
+
+  const projectionFn = d3
+    .geoMercator()
+    .center(center)
+    .scale(scale)
+    .translate([0, 0]);
+
+  const label2dData = []; // 存储自定义 2d 标签数据
+
+  // 每个省的数据
+  basicFeatures.forEach((basicFeatureItem) => {
+    // 每个省份的地图对象
+    const provinceMapObject3D = new THREE.Object3D();
+    // 将地图数据挂在到模型数据上
+    provinceMapObject3D.customProperties = basicFeatureItem.properties;
+
+    // 每个坐标类型
+    const featureType = basicFeatureItem.geometry.type;
+    // 每个坐标数组
+    const featureCoords =
+      basicFeatureItem.geometry.coordinates;
+    // 每个中心点位置
+    const featureCenterCoord =
+      basicFeatureItem.properties.centroid &&
+      projectionFn(basicFeatureItem.properties.centroid);
+    // 名字
+    const featureName = basicFeatureItem.properties.name;
+    console.log('featureName',featureName)
+    console.log('featureCenterCoord',featureCenterCoord)
+
+    if (featureCenterCoord) {
+      label2dData.push({
+        featureCenterCoord,
+        featureName,
+      });
+    }
+
+    // MultiPolygon 类型
+    if (featureType === "MultiPolygon") {
+      featureCoords.forEach((multiPolygon) => {
+        multiPolygon.forEach((polygon) => {
+          const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+          provinceMapObject3D.add(mesh);
+          provinceMapObject3D.add(line);
+        });
+      });
+    }
+
+    // Polygon 类型
+    if (featureType === "Polygon") {
+      featureCoords.forEach((polygon) => {
+        const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+        provinceMapObject3D.add(mesh);
+        provinceMapObject3D.add(line);
+      });
+    }
+
+    mapObject3D.add(provinceMapObject3D);
+  });
+
+  return { mapObject3D, label2dData };
+}
+
+// 生成地图2D标签
+export function generateMapLabel2D(label2dData) {
+  const labelObject2D = new THREE.Object3D();
+  label2dData.forEach((item) => {
+    const { featureCenterCoord, featureName } = item;
+    debugger
+    const labelObjectItem = draw2dLabel(featureCenterCoord, featureName);
+    if (labelObjectItem) {
+      labelObject2D.add(labelObjectItem);
+    }
+  });
+  return labelObject2D;
+}
+
+// 生成地图spot点位
+export function generateMapSpot(label2dData) {
+  const spotObject3D = new THREE.Object3D();
+  const spotList = [];
+  label2dData.forEach((item) => {
+    const { featureCenterCoord } = item;
+      const spotObjectItem = drawSpot(featureCenterCoord);
+      if (spotObjectItem && spotObjectItem.circle && spotObjectItem.ring) {
+        spotObject3D.add(spotObjectItem.circle);
+        spotObject3D.add(spotObjectItem.ring);
+        spotList.push(spotObjectItem.ring);
+      }
+
+      //TODO:渲染铁塔
+      
+
+  });
+  return { spotObject3D, spotList };
+}
+
+// 绘制二维标签
+export const draw2dLabel = (coord, proviceName) => {
+  if (coord && coord.length) {
+    // 模版字符串
+    const innerHTML = `<div class="your-classname" style="color: #fff">${proviceName}</div>`;
+    const labelDiv = document.createElement("div");
+    labelDiv.innerHTML = innerHTML;
+    labelDiv.style.pointerEvents = "none"; // 禁用事件
+    const labelObject = new CSS2DObject(labelDiv);
+    labelObject.position.set(coord[0], -coord[1], mapConfig.label2dZIndex);
+    return labelObject;
+  }
+};
+
+// 绘制圆点
+export const drawSpot = (coord) => {
+  if (coord && coord.length) {
+    /**
+     * 绘制圆点
+     */
+    const spotGeometry = new THREE.CircleGeometry(0.2, 200);
+    const spotMaterial = new THREE.MeshBasicMaterial({
+      color: "#3EC5FB",
+      side: THREE.DoubleSide,
+    });
+    const circle = new THREE.Mesh(spotGeometry, spotMaterial);
+    circle.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+
+    // 圆环
+    const ringGeometry = new THREE.RingGeometry(0.2, 0.3, 50);
+    const ringMaterial = new THREE.MeshBasicMaterial({
+      color: "#3FC5FB",
+      side: THREE.DoubleSide,
+      transparent: true,
+    });
+    const ring = new THREE.Mesh(ringGeometry, ringMaterial);
+    ring.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+    return { circle, ring };
+  }
+};
+
+/**
+ * 线上移动物体
+ */
+export const drawflySpot = (curve) => {
+  const aGeo = new THREE.SphereGeometry(0.2);
+  const aMater = new THREE.MeshBasicMaterial({
+    color: "#77f077",
+    side: THREE.DoubleSide,
+  });
+  const aMesh = new THREE.Mesh(aGeo, aMater);
+  // 保存曲线实例
+  aMesh.curve = curve;
+  aMesh._s = 0;
+  return aMesh;
+};
+
+// 绘制两点链接飞线
+export const drawLineBetween2Spot = (
+  coordStart,
+  coordEnd
+) => {
+  const [x0, y0, z0] = [...coordStart, mapConfig.spotZIndex];
+  const [x1, y1, z1] = [...coordEnd, mapConfig.spotZIndex];
+  // 使用 QuadraticBezierCurve3 创建 三维二次贝塞尔曲线
+  const curve = new THREE.QuadraticBezierCurve3(
+    new THREE.Vector3(x0, -y0, z0),
+    new THREE.Vector3((x0 + x1) / 2, -(y0 + y1) / 2, 20),
+    new THREE.Vector3(x1, -y1, z1)
+  );
+
+  const flySpot = drawflySpot(curve);
+
+  const lineGeometry = new THREE.BufferGeometry();
+  // 获取曲线上50个点
+  const points = curve.getPoints(50);
+  const positions = [];
+  const colors = [];
+  const color = new THREE.Color();
+
+  // 给每个顶点设置演示 实现渐变
+  for (let j = 0; j < points.length; j++) {
+    color.setHSL(0.21 + j, 0.77, 0.55 + j * 0.0025); // 色
+    colors.push(color.r, color.g, color.b);
+    positions.push(points[j].x, points[j].y, points[j].z);
+  }
+  // 放入顶点 和 设置顶点颜色
+  lineGeometry.setAttribute(
+    "position",
+    new THREE.BufferAttribute(new Float32Array(positions), 3, true)
+  );
+  lineGeometry.setAttribute(
+    "color",
+    new THREE.BufferAttribute(new Float32Array(colors), 3, true)
+  );
+
+  const material = new THREE.LineBasicMaterial({
+    vertexColors: true,
+    // color: "red",
+    side: THREE.DoubleSide,
+  });
+  const flyLine = new THREE.Line(lineGeometry, material);
+
+  return { flyLine, flySpot };
+};

+ 303 - 0
zhsq_qk-ui/.history/src/map3d/drawFunc_20240617141718.js

@@ -0,0 +1,303 @@
+import * as THREE from "three";
+import * as d3 from "d3";
+import { CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { Line2 } from "three/examples/jsm/lines/Line2";
+import { LineGeometry } from "three/examples/jsm/lines/LineGeometry";
+import { LineMaterial } from "three/examples/jsm/lines/LineMaterial";
+// import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
+
+// import { ProjectionFnParamType } from "./index.vue";
+import { mapConfig } from "./mapConfig";
+
+// 绘制挤出的材质
+export function drawExtrudeMesh(
+  point,
+  projectionFn
+) {
+  const shape = new THREE.Shape();
+  const pointsArray = [];
+
+  for (let i = 0; i < point.length; i++) {
+    const [x, y] = projectionFn(point[i]); // 将每一个经纬度转化为坐标点
+    if (i === 0) {
+      shape.moveTo(x, -y);
+    }
+    shape.lineTo(x, -y);
+    pointsArray.push(x, -y, mapConfig.topLineZIndex);
+  }
+
+  const geometry = new THREE.ExtrudeGeometry(shape, {
+    depth: mapConfig.mapDepth, // 挤出的形状深度
+    bevelEnabled: false, // 对挤出的形状应用是否斜角
+  });
+
+  const material = new THREE.MeshPhongMaterial({
+    // color: mapConfig.mapColor,
+    color: mapConfig.mapColorGradient[Math.floor(Math.random() * 4)], // 随机颜色
+    // transparent: mapConfig.mapTransparent,
+    // opacity: mapConfig.mapOpacity,
+  });
+
+  const materialSide = new THREE.ShaderMaterial({
+    uniforms: {
+      color1: {
+        value: new THREE.Color(mapConfig.mapSideColor1),
+      },
+      color2: {
+        value: new THREE.Color(mapConfig.mapSideColor2),
+      },
+    },
+    vertexShader: `
+      varying vec3 vPosition;
+      void main() {
+        vPosition = position;
+        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+      }
+    `,
+    fragmentShader: `
+      uniform vec3 color1;
+      uniform vec3 color2;
+      varying vec3 vPosition;
+      void main() {
+        vec3 mixColor = mix(color1, color2, 0.5 - vPosition.z * 0.2); // 使用顶点坐标 z 分量来控制混合
+        gl_FragColor = vec4(mixColor, 1.0);
+      }
+    `,
+    //   wireframe: true,
+  });
+
+  const mesh = new THREE.Mesh(geometry, [material, materialSide]);
+  // userData 存储自定义属性
+  mesh.userData = {
+    isChangeColor: true,
+  };
+
+  // 边框线,赋值空间点坐标,3个一组
+  const lineGeometry = new LineGeometry();
+  lineGeometry.setPositions(pointsArray);
+
+  const lineMaterial = new LineMaterial({
+    color: mapConfig.topLineColor,
+    linewidth: mapConfig.topLineWidth,
+  });
+  lineMaterial.resolution.set(window.innerWidth, window.innerHeight);
+  const line = new Line2(lineGeometry, lineMaterial);
+
+  return { mesh, line };
+}
+
+// 生成地图3D模型
+export function generateMapObject3D(
+  mapdata,
+  projectionFnParam
+) {
+  // 地图对象
+  const mapObject3D = new THREE.Object3D();
+  // 地图数据
+  const basicFeatures = mapdata.features;
+  console.log('basicFeatures',basicFeatures)
+
+  const { center, scale } = projectionFnParam;
+
+  const projectionFn = d3
+    .geoMercator()
+    .center(center)
+    .scale(scale)
+    .translate([0, 0]);
+
+  const label2dData = []; // 存储自定义 2d 标签数据
+
+  // 每个省的数据
+  basicFeatures.forEach((basicFeatureItem) => {
+    // 每个省份的地图对象
+    const provinceMapObject3D = new THREE.Object3D();
+    // 将地图数据挂在到模型数据上
+    provinceMapObject3D.customProperties = basicFeatureItem.properties;
+
+    // 每个坐标类型
+    const featureType = basicFeatureItem.geometry.type;
+    // 每个坐标数组
+    const featureCoords =
+      basicFeatureItem.geometry.coordinates;
+    // 每个中心点位置
+    const featureCenterCoord =
+      basicFeatureItem.properties.centroid &&
+      projectionFn(basicFeatureItem.properties.centroid);
+    // 名字
+    const featureName = basicFeatureItem.properties.name;
+    console.log('featureName',featureName)
+    console.log('featureCenterCoord',featureCenterCoord)
+    console.log('basicFeatureItem',basicFeatureItem)
+
+    if (featureCenterCoord) {
+      label2dData.push({
+        featureCenterCoord,
+        featureName,
+      });
+    }
+
+    // MultiPolygon 类型
+    if (featureType === "MultiPolygon") {
+      featureCoords.forEach((multiPolygon) => {
+        multiPolygon.forEach((polygon) => {
+          const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+          provinceMapObject3D.add(mesh);
+          provinceMapObject3D.add(line);
+        });
+      });
+    }
+
+    // Polygon 类型
+    if (featureType === "Polygon") {
+      featureCoords.forEach((polygon) => {
+        const { mesh, line } = drawExtrudeMesh(polygon, projectionFn);
+        provinceMapObject3D.add(mesh);
+        provinceMapObject3D.add(line);
+      });
+    }
+
+    mapObject3D.add(provinceMapObject3D);
+  });
+
+  return { mapObject3D, label2dData };
+}
+
+// 生成地图2D标签
+export function generateMapLabel2D(label2dData) {
+  const labelObject2D = new THREE.Object3D();
+  label2dData.forEach((item) => {
+    const { featureCenterCoord, featureName } = item;
+    debugger
+    const labelObjectItem = draw2dLabel(featureCenterCoord, featureName);
+    if (labelObjectItem) {
+      labelObject2D.add(labelObjectItem);
+    }
+  });
+  return labelObject2D;
+}
+
+// 生成地图spot点位
+export function generateMapSpot(label2dData) {
+  const spotObject3D = new THREE.Object3D();
+  const spotList = [];
+  label2dData.forEach((item) => {
+    const { featureCenterCoord } = item;
+      const spotObjectItem = drawSpot(featureCenterCoord);
+      if (spotObjectItem && spotObjectItem.circle && spotObjectItem.ring) {
+        spotObject3D.add(spotObjectItem.circle);
+        spotObject3D.add(spotObjectItem.ring);
+        spotList.push(spotObjectItem.ring);
+      }
+
+      //TODO:渲染铁塔
+      
+
+  });
+  return { spotObject3D, spotList };
+}
+
+// 绘制二维标签
+export const draw2dLabel = (coord, proviceName) => {
+  if (coord && coord.length) {
+    // 模版字符串
+    const innerHTML = `<div class="your-classname" style="color: #fff">${proviceName}</div>`;
+    const labelDiv = document.createElement("div");
+    labelDiv.innerHTML = innerHTML;
+    labelDiv.style.pointerEvents = "none"; // 禁用事件
+    const labelObject = new CSS2DObject(labelDiv);
+    labelObject.position.set(coord[0], -coord[1], mapConfig.label2dZIndex);
+    return labelObject;
+  }
+};
+
+// 绘制圆点
+export const drawSpot = (coord) => {
+  if (coord && coord.length) {
+    /**
+     * 绘制圆点
+     */
+    const spotGeometry = new THREE.CircleGeometry(0.2, 200);
+    const spotMaterial = new THREE.MeshBasicMaterial({
+      color: "#3EC5FB",
+      side: THREE.DoubleSide,
+    });
+    const circle = new THREE.Mesh(spotGeometry, spotMaterial);
+    circle.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+
+    // 圆环
+    const ringGeometry = new THREE.RingGeometry(0.2, 0.3, 50);
+    const ringMaterial = new THREE.MeshBasicMaterial({
+      color: "#3FC5FB",
+      side: THREE.DoubleSide,
+      transparent: true,
+    });
+    const ring = new THREE.Mesh(ringGeometry, ringMaterial);
+    ring.position.set(coord[0], -coord[1], mapConfig.spotZIndex);
+    return { circle, ring };
+  }
+};
+
+/**
+ * 线上移动物体
+ */
+export const drawflySpot = (curve) => {
+  const aGeo = new THREE.SphereGeometry(0.2);
+  const aMater = new THREE.MeshBasicMaterial({
+    color: "#77f077",
+    side: THREE.DoubleSide,
+  });
+  const aMesh = new THREE.Mesh(aGeo, aMater);
+  // 保存曲线实例
+  aMesh.curve = curve;
+  aMesh._s = 0;
+  return aMesh;
+};
+
+// 绘制两点链接飞线
+export const drawLineBetween2Spot = (
+  coordStart,
+  coordEnd
+) => {
+  const [x0, y0, z0] = [...coordStart, mapConfig.spotZIndex];
+  const [x1, y1, z1] = [...coordEnd, mapConfig.spotZIndex];
+  // 使用 QuadraticBezierCurve3 创建 三维二次贝塞尔曲线
+  const curve = new THREE.QuadraticBezierCurve3(
+    new THREE.Vector3(x0, -y0, z0),
+    new THREE.Vector3((x0 + x1) / 2, -(y0 + y1) / 2, 20),
+    new THREE.Vector3(x1, -y1, z1)
+  );
+
+  const flySpot = drawflySpot(curve);
+
+  const lineGeometry = new THREE.BufferGeometry();
+  // 获取曲线上50个点
+  const points = curve.getPoints(50);
+  const positions = [];
+  const colors = [];
+  const color = new THREE.Color();
+
+  // 给每个顶点设置演示 实现渐变
+  for (let j = 0; j < points.length; j++) {
+    color.setHSL(0.21 + j, 0.77, 0.55 + j * 0.0025); // 色
+    colors.push(color.r, color.g, color.b);
+    positions.push(points[j].x, points[j].y, points[j].z);
+  }
+  // 放入顶点 和 设置顶点颜色
+  lineGeometry.setAttribute(
+    "position",
+    new THREE.BufferAttribute(new Float32Array(positions), 3, true)
+  );
+  lineGeometry.setAttribute(
+    "color",
+    new THREE.BufferAttribute(new Float32Array(colors), 3, true)
+  );
+
+  const material = new THREE.LineBasicMaterial({
+    vertexColors: true,
+    // color: "red",
+    side: THREE.DoubleSide,
+  });
+  const flyLine = new THREE.Line(lineGeometry, material);
+
+  return { flyLine, flySpot };
+};

+ 519 - 0
zhsq_qk-ui/.history/src/map3d/index_20240531142157.vue

@@ -0,0 +1,519 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+   <div class="contain"
+    >
+      <div ref="map2dRef" style="width: 100%"></div>
+      <!-- <div id="cesiumContainer" style="height: 100%;"></div> -->
+      <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+      </div>
+      <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+    </div>
+</template>
+
+
+<script>
+import 'cesium/Build/Cesium/Widgets/widgets.css'
+import {
+  // Viewer,
+  // ShadowMode,
+  // Cartesian3,
+  // Math as CesiumMath,
+  // Color,
+} from 'cesium'
+// import * as Cesium from 'cesium'
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+  // drawLineBetween2Spot,
+  generateMapLabel2D,
+  generateMapObject3D,
+  generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+
+let lastPick
+let scene
+let eventFlag
+export default {
+  name:'Map3D',
+  props:['geoJson','dblClickFn','projectionFnParam'],
+  components:{
+    ToolTip
+  },
+  data(){
+    return {
+      cesium :{
+        viewer: null,
+      },
+      toolTipRef:null,
+      toolTipData:{
+        text:''
+      },
+      ratio:{
+        value: 0,
+      }
+    }
+  },
+  mounted(){
+    this.initCesium()
+    // this.init()
+    // this.startRenderLoop()
+  },
+  methods:{
+    init(){
+       /**
+       * 初始化场景
+       */
+      scene = initScene()
+      const currentDom = this.$refs.mapRef
+
+       /**
+       * 初始化摄像机
+       */
+      // const cvm = this.cesium.viewer.camera.viewMatrix
+      // const civm = this.cesium.viewer.camera.inverseViewMatrix
+      const { camera } = initCamera(currentDom);
+
+       /**
+       * 初始化渲染器
+       */
+      const renderer = new THREE.WebGLRenderer({ antialias: true });
+      renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+      // 防止开发时重复渲染
+      // if (!currentDom.hasChildNodes()) {
+      //   currentDom.appendChild(renderer.domElement);
+      // }
+      // 这里修改为下面写法,否则 onresize 不生效
+      if (currentDom.childNodes[0]) {
+        currentDom.removeChild(currentDom.childNodes[0]);
+      }
+      currentDom.appendChild(renderer.domElement);
+
+      /**
+       * 创建css2 Renderer 渲染器
+       */
+      const labelRenderer = new CSS2DRenderer();
+      labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+      labelRenderer.domElement.style.position = "absolute";
+      labelRenderer.domElement.style.top = "0px";
+      const labelRendererDom = this.$refs.map2dRef;
+      if (labelRendererDom?.childNodes[0]) {
+        labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+      }
+      labelRendererDom.appendChild(labelRenderer.domElement);
+
+       /**
+       * 初始化模型(绘制3D模型)
+       */
+      const { mapObject3D, label2dData } = generateMapObject3D(
+        this.geoJson,
+        this.projectionFnParam
+      );
+      scene.add(mapObject3D);
+
+       /**
+       * 绘制 2D 面板
+       */
+      const labelObject2D = generateMapLabel2D(label2dData);
+      mapObject3D.add(labelObject2D);
+
+      /**
+       * 绘制点位
+       */
+      const { spotList, spotObject3D } = generateMapSpot(label2dData);
+      mapObject3D.add(spotObject3D);
+
+      const modelObject3D = new THREE.Object3D();
+      // let mixer: any = null;
+      let modelMixer = [];
+      const loader = new GLTFLoader();
+      const dracoLoader = new DRACOLoader();
+      dracoLoader.setDecoderPath("/draco/");
+      loader.setDRACOLoader(dracoLoader);
+
+      loader.load("/models/su7/source/SU7.glb", (glb) => {
+        label2dData.forEach((item) => {
+          // console.log(item, "0-0-0-");
+          const { featureCenterCoord } = item;
+          const clonedModel = glb.scene.clone();
+          const mixer = new THREE.AnimationMixer(clonedModel);
+          const clonedAnimations = glb.animations.map((clip) => {
+            return clip.clone();
+          });
+          clonedAnimations.forEach((clip) => {
+            mixer.clipAction(clip).play();
+          });
+
+          // 添加每个model的mixer
+          modelMixer.push(mixer);
+
+          //添加材质
+          clonedModel.traverse(function (child) {
+              if (child.isMesh) {
+                  // Create a new material or modify the existing one
+                  const material = new THREE.MeshStandardMaterial({
+                      color: '#1B9DA7', // Green color
+                      metalness: 0.5,
+                      roughness: 0.5
+                  });
+
+                  // child.material = material;
+
+                  // Apply the material to the mesh
+                  child.material = material;
+              }
+          });
+
+          // 设置模型位置
+          clonedModel.position.set(
+            featureCenterCoord[0],
+            -featureCenterCoord[1],
+            mapConfig.spotZIndex + 10
+          );
+          // 设置模型大小
+          clonedModel.scale.set(0.3, 0.3, 0.6);
+          // clonedModel.rotateX(-Math.PI / 8);
+          modelObject3D.add(clonedModel);
+        });
+
+        mapObject3D.add(modelObject3D);
+      });
+      
+      /**
+       * 绘制连线(随机生成两个点位)
+       */
+      const MAX_LINE_COUNT = 5; // 随机生成5组线
+      let connectLine = [];
+      for (let count = 0; count < MAX_LINE_COUNT; count++) {
+        const midIndex = Math.floor(label2dData.length / 2);
+        const indexStart = Math.floor(Math.random() * midIndex);
+        const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+        connectLine.push({
+          indexStart,
+          indexEnd,
+        });
+      }
+
+      /**
+       * 绘制飞行的点
+       */
+      // const flyObject3D = new THREE.Object3D();
+      // const flySpotList = [];
+      // connectLine.forEach((item) => {
+      //   const { indexStart, indexEnd } = item;
+      //   const { flyLine, flySpot } = drawLineBetween2Spot(
+      //     label2dData[indexStart].featureCenterCoord,
+      //     label2dData[indexEnd].featureCenterCoord
+      //   );
+      //   flyObject3D.add(flyLine);
+      //   flyObject3D.add(flySpot);
+      //   flySpotList.push(flySpot);
+      // });
+      // mapObject3D.add(flyObject3D);
+
+      /**
+       * 初始化控制器
+       */
+      // new OrbitControls(camera, renderer.domElement);
+      new OrbitControls(camera, labelRenderer.domElement);
+
+      /**
+       * 新增光源
+       */
+      const light = new THREE.PointLight(0xffffff, 1.5);
+      light.position.set(0, -5, 30);
+      scene.add(light);
+
+      // 创建光圈
+      const ringGeometry = new THREE.RingGeometry(30.5, 28, 39);
+      const ringMaterial = new THREE.MeshBasicMaterial({ 
+        color: '#3E422E', 
+        side: THREE.DoubleSide ,
+        transparent:true,
+        opacity:0.8,
+        emissive: '#fff', // 设置发光颜色为白色
+        emissiveIntensity: 1, // 设置发光强度为1
+      })
+      const ringMesh = new THREE.Mesh(ringGeometry, ringMaterial);
+
+      // 创建一个蓝色边界的光圈
+      const innerRingGeometry = new THREE.RingGeometry(28, 27.6, 39);
+      const innerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+      const innerRingMesh = new THREE.Mesh(innerRingGeometry, innerRingMaterial);
+
+      // 设置光圈的位置
+      innerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+      // 翻转光圈
+      innerRingMesh.rotation.x = Math.PI / 1.7; 
+      innerRingMesh.rotation.y = Math.PI / 1.05; 
+      scene.add(innerRingMesh);
+      
+      // 创建一个蓝色边界的光圈
+      const outerRingGeometry = new THREE.RingGeometry(31, 30.6, 39);
+      const outerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+      const outerRingMesh = new THREE.Mesh(outerRingGeometry, outerRingMaterial);
+
+      // 设置光圈的位置
+      outerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+      // 翻转光圈
+      outerRingMesh.rotation.x = Math.PI / 1.7; 
+      outerRingMesh.rotation.y = Math.PI / 1.05; 
+      scene.add(outerRingMesh);
+
+      // 设置光圈的位置
+      ringMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+      // 翻转光圈
+      ringMesh.rotation.x = Math.PI / 1.7; 
+      ringMesh.rotation.y = Math.PI / 1.05; 
+      console.log('光圈参数',ringMesh)
+      scene.add(ringMesh);
+
+      // 视窗伸缩
+      function onResizeEvent() {
+        // 更新摄像头
+        camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+        // 更新摄像机的投影矩阵
+        camera.updateProjectionMatrix();
+        // 更新渲染器
+        renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+        labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+        // 设置渲染器的像素比例
+        renderer.setPixelRatio(window.devicePixelRatio);
+      }
+
+      /**
+       * 设置 raycaster
+       */
+      const raycaster = new THREE.Raycaster();
+      const pointer = new THREE.Vector2();
+
+       // 鼠标移入事件
+      const onMouseMoveEvent = (e) => {
+        const intersects = raycaster.intersectObjects(scene.children);
+        pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+        pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+        // 如果存在,则鼠标移出需要重置
+        if (lastPick) {
+          // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+          const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+          lastPick.object.material[0].color.set(color);
+          lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+        }
+        lastPick = null;
+        // lastPick = intersects.find(
+        //   (item: any) => item.object.material && item.object.material.length === 2
+        // );
+        // 优化
+        lastPick = intersects.find(
+          (item) => item.object.userData.isChangeColor
+        );
+        if (lastPick) {
+          
+          const properties = lastPick.object.parent.customProperties;
+          if (lastPick.object.material[0]) {
+            lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+            lastPick.object.material[0].opacity = 1; // 设置完全不透明
+          }
+
+          // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+          //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+          //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+          //   this.$refs.toolTipRef.style.visibility = "visible";
+          // }
+          this.toolTipData.text = properties.name
+        } else {
+          // this.$refs.toolTipRef.style.visibility = "hidden";
+        }
+      };
+
+      // 鼠标双击事件
+      const onDblclickEvent = () => {
+        const intersects = raycaster.intersectObjects(scene.children);
+        const target = intersects.find(
+          (item) => item.object.userData.isChangeColor
+        );
+        console.log('target',target);
+        if (target) {
+          const obj = target.object.parent;
+          console.log('targetobj: ' + obj)
+          console.log('dblClickFn: ' + this.dblClickFn)
+          const p = obj.customProperties;
+          this.dblClickFn(p);
+        }
+      };
+
+      /**
+       * 动画
+       */
+      gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+      /**
+       * Animate
+       */
+      const clock = new THREE.Clock();
+      // let previousTime = 0;
+
+      const animate = function () {
+        // const elapsedTime = clock.getElapsedTime();
+        // const deltaTime = elapsedTime - previousTime;
+        // previousTime = elapsedTime;
+
+        // Update mixer
+        // mixer?.update(deltaTime);
+        const delta = clock.getDelta();
+        modelMixer.map((item) => item.update(delta));
+
+        // 雷达
+        // this.ratio.value += 0.01;
+
+        requestAnimationFrame(animate);
+        // 通过摄像机和鼠标位置更新射线
+        raycaster.setFromCamera(pointer, camera);
+        renderer.render(scene, camera);
+        labelRenderer.render(scene, camera);
+
+        // 圆环
+        spotList.forEach((mesh) => {
+          mesh._s += 0.01;
+          mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+          if (mesh._s <= 2) {
+            mesh.material.opacity = 2 - mesh._s;
+          } else {
+            mesh._s = 1;
+          }
+        });
+
+        // 飞行的圆点
+        // flySpotList.forEach(function (mesh) {
+        //   mesh._s += 0.003;
+        //   let tankPosition = new THREE.Vector3();
+        //   // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+        //   tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+        //   mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+        // });
+      };
+      animate();
+
+      if(!eventFlag){
+        window.addEventListener("resize", onResizeEvent, false);
+        window.addEventListener("mousemove", onMouseMoveEvent, false);
+        window.addEventListener("dblclick", onDblclickEvent, false); 
+        eventFlag = true
+      }
+
+    },
+    initCesium(){
+      // this.cesium.viewer.useDefaultRenderLoop = false  // 禁用Cesium渲染帧
+      // this.cesium.viewer = new Viewer('cesiumContainer', {
+      //   geocoder:false, //右上角 搜索
+      //   homeButton:false, //右上角 Home
+      //   sceneModePicker:false, //右上角 2D/3D切换
+      //   baseLayerPicker:false,  //右上角 地形
+      //   navigationHelpButton:false, //右上角 Help
+      //   animation:false, // 左下角 圆盘动画控件
+      //   timeline:false, //时间轴
+      //   fullscreenButton:false, //右下角 全屏控件
+      //   vrButton:false, // 如果设置为true,将创建VRButton小部件。
+      //   scene3DOnly: true, // 每个几何实例仅以3D渲染以节省GPU内存
+      //   infoBox: false, //隐藏点击要素后的提示信息
+      // })
+      // document.getElementById("cesiumContainer").style.display="none";
+      this.init()
+      // 隐藏版权
+      // this.cesium.viewer._cesiumWidget._creditContainer.style.display = "none";
+
+      // var promise = this.cesium.viewer.scene.open('http://www.supermapol.com/realspace/services/3D-ZhongGuoDiTu/rest/realspace');
+      // this.cesium.when.all(promise, function (layers) {
+      //     console.log(layers)
+      //     var layer4 = scene.layers.find("Province_L@中国地图");
+      //     var layerEffect4 = layer4.effect;
+      //     layerEffect4.setValue('Color', new Cesium.Color(132 * 2 / 255, 143 * 2 / 255, 11 * 2 / 255, 1));
+      //     layerEffect4.setValue('Width', 1);
+
+      //     var layer3 = scene.layers.find("BorderA_L@中国地图");
+      //     var layerEffect3 = layer3.effect;
+      //     layerEffect3.setValue('Color', new Cesium.Color(6 * 2 / 255, 180 * 2 / 255, 224 * 2 / 255, 1));
+      //     layerEffect3.setValue('Width', 3);
+
+      //     var layer1 = scene.layers.find("Province_R@中国地图");
+      //     layer1.style3D.fillForeColor = new Cesium.Color(0 / 255, 180 / 255, 255 * 2 / 255, 0.6);
+
+
+      //     var layer0 = scene.layers.find("ProvinceCity@中国地图");
+      //     layer0.indexedDBSetting.isAttributesSave = true;//保存属性
+      //     layer0.showLabel = true;
+
+      //     layer0.labelStyle = new Cesium.S3MTilesLabelStyle({
+      //         fillColor: Cesium.Color.WHITE,
+      //         outlineColor: Cesium.Color.RED,
+      //         font: '44px Calibri',
+      //         outlineWidth: 2.0,
+      //         scale: 0.5,
+      //         fillStyle: Cesium.LabelStyle.FILL_AND_OUTLINE,
+      //         textField: 'Name',
+      //         pixelOffset: new Cesium.Cartesian2(0, -5)
+      //     });
+      // });
+      // const center = Cartesian3.fromDegrees(
+      //   (minWGS84[0] + maxWGS84[0]) / 2,
+      //   (minWGS84[1] + maxWGS84[1]) / 2 - 1,
+      //   200000
+      // )
+      // setTimeout(() => {
+      //   this.cesium.viewer.camera.flyTo({
+      //     destination: Cartesian3.fromDegrees(125.331345,43.891262, 50000.0),
+      //     duration:5,
+      //     complete: () => {
+      //       // 定位完成之后的回调函数
+      //       document.getElementById("cesiumContainer").style.display="none";
+      //       this.init()
+      //     },
+      //   });
+      // },3000)
+    },
+    //同步两个渲染器的渲染
+    startRenderLoop() {
+      this.init()
+      this.cesium.viewer.render()
+      requestAnimationFrame(this.startRenderLoop)
+    },
+  },
+  watch:{
+    geoJson(){
+      this.init()
+    }
+    // geoJson:{
+    //   immediate: true,
+    //   handler(){
+    //     console.log('geoJson',this.geoJson)
+    //     this.init()
+    //   }
+    // }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  position: relative;
+}
+</style>

+ 370 - 0
zhsq_qk-ui/.history/src/map3d/index_20240617112103.vue

@@ -0,0 +1,370 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       this.geoJson,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+     const flyObject3D = new THREE.Object3D();
+     const flySpotList = [];
+     connectLine.forEach((item) => {
+       const { indexStart, indexEnd } = item;
+       const { flyLine, flySpot } = drawLineBetween2Spot(
+         label2dData[indexStart].featureCenterCoord,
+         label2dData[indexEnd].featureCenterCoord
+       );
+       flyObject3D.add(flyLine);
+       flyObject3D.add(flySpot);
+       flySpotList.push(flySpot);
+     });
+     mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+       flySpotList.forEach(function (mesh) {
+         mesh._s += 0.003;
+         let tankPosition = new THREE.Vector3();
+         // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+         tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+         mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+       });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 370 - 0
zhsq_qk-ui/.history/src/map3d/index_20240617131501.vue

@@ -0,0 +1,370 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       this.geoJson,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+       flySpotList.forEach(function (mesh) {
+         mesh._s += 0.003;
+         let tankPosition = new THREE.Vector3();
+         // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+         tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+         mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+       });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 370 - 0
zhsq_qk-ui/.history/src/map3d/index_20240617131719.vue

@@ -0,0 +1,370 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       this.geoJson,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+     const flyObject3D = new THREE.Object3D();
+     const flySpotList = [];
+     connectLine.forEach((item) => {
+       const { indexStart, indexEnd } = item;
+       const { flyLine, flySpot } = drawLineBetween2Spot(
+         label2dData[indexStart].featureCenterCoord,
+         label2dData[indexEnd].featureCenterCoord
+       );
+       flyObject3D.add(flyLine);
+       flyObject3D.add(flySpot);
+       flySpotList.push(flySpot);
+     });
+     mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+       flySpotList.forEach(function (mesh) {
+         mesh._s += 0.003;
+         let tankPosition = new THREE.Vector3();
+         // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+         tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+         mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+       });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 519 - 0
zhsq_qk-ui/.history/src/map3d/index_20240617131842.vue

@@ -0,0 +1,519 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <!-- <div id="cesiumContainer" style="height: 100%;"></div> -->
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import 'cesium/Build/Cesium/Widgets/widgets.css'
+import {
+ // Viewer,
+ // ShadowMode,
+ // Cartesian3,
+ // Math as CesiumMath,
+ // Color,
+} from 'cesium'
+// import * as Cesium from 'cesium'
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ // drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     cesium :{
+       viewer: null,
+     },
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.initCesium()
+   // this.init()
+   // this.startRenderLoop()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     // const cvm = this.cesium.viewer.camera.viewMatrix
+     // const civm = this.cesium.viewer.camera.inverseViewMatrix
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       this.geoJson,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/su7/source/SU7.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         //添加材质
+         clonedModel.traverse(function (child) {
+             if (child.isMesh) {
+                 // Create a new material or modify the existing one
+                 const material = new THREE.MeshStandardMaterial({
+                     color: '#1B9DA7', // Green color
+                     metalness: 0.5,
+                     roughness: 0.5
+                 });
+
+                 // child.material = material;
+
+                 // Apply the material to the mesh
+                 child.material = material;
+             }
+         });
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex + 10
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+     // const flyObject3D = new THREE.Object3D();
+     // const flySpotList = [];
+     // connectLine.forEach((item) => {
+     //   const { indexStart, indexEnd } = item;
+     //   const { flyLine, flySpot } = drawLineBetween2Spot(
+     //     label2dData[indexStart].featureCenterCoord,
+     //     label2dData[indexEnd].featureCenterCoord
+     //   );
+     //   flyObject3D.add(flyLine);
+     //   flyObject3D.add(flySpot);
+     //   flySpotList.push(flySpot);
+     // });
+     // mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 创建光圈
+     const ringGeometry = new THREE.RingGeometry(30.5, 28, 39);
+     const ringMaterial = new THREE.MeshBasicMaterial({ 
+       color: '#3E422E', 
+       side: THREE.DoubleSide ,
+       transparent:true,
+       opacity:0.8,
+       emissive: '#fff', // 设置发光颜色为白色
+       emissiveIntensity: 1, // 设置发光强度为1
+     })
+     const ringMesh = new THREE.Mesh(ringGeometry, ringMaterial);
+
+     // 创建一个蓝色边界的光圈
+     const innerRingGeometry = new THREE.RingGeometry(28, 27.6, 39);
+     const innerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+     const innerRingMesh = new THREE.Mesh(innerRingGeometry, innerRingMaterial);
+
+     // 设置光圈的位置
+     innerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+     // 翻转光圈
+     innerRingMesh.rotation.x = Math.PI / 1.7; 
+     innerRingMesh.rotation.y = Math.PI / 1.05; 
+     scene.add(innerRingMesh);
+     
+     // 创建一个蓝色边界的光圈
+     const outerRingGeometry = new THREE.RingGeometry(31, 30.6, 39);
+     const outerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+     const outerRingMesh = new THREE.Mesh(outerRingGeometry, outerRingMaterial);
+
+     // 设置光圈的位置
+     outerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+     // 翻转光圈
+     outerRingMesh.rotation.x = Math.PI / 1.7; 
+     outerRingMesh.rotation.y = Math.PI / 1.05; 
+     scene.add(outerRingMesh);
+
+     // 设置光圈的位置
+     ringMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+     // 翻转光圈
+     ringMesh.rotation.x = Math.PI / 1.7; 
+     ringMesh.rotation.y = Math.PI / 1.05; 
+     console.log('光圈参数',ringMesh)
+     scene.add(ringMesh);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+       // flySpotList.forEach(function (mesh) {
+       //   mesh._s += 0.003;
+       //   let tankPosition = new THREE.Vector3();
+       //   // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+       //   tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+       //   mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+       // });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   },
+   initCesium(){
+     // this.cesium.viewer.useDefaultRenderLoop = false  // 禁用Cesium渲染帧
+     // this.cesium.viewer = new Viewer('cesiumContainer', {
+     //   geocoder:false, //右上角 搜索
+     //   homeButton:false, //右上角 Home
+     //   sceneModePicker:false, //右上角 2D/3D切换
+     //   baseLayerPicker:false,  //右上角 地形
+     //   navigationHelpButton:false, //右上角 Help
+     //   animation:false, // 左下角 圆盘动画控件
+     //   timeline:false, //时间轴
+     //   fullscreenButton:false, //右下角 全屏控件
+     //   vrButton:false, // 如果设置为true,将创建VRButton小部件。
+     //   scene3DOnly: true, // 每个几何实例仅以3D渲染以节省GPU内存
+     //   infoBox: false, //隐藏点击要素后的提示信息
+     // })
+     // document.getElementById("cesiumContainer").style.display="none";
+     this.init()
+     // 隐藏版权
+     // this.cesium.viewer._cesiumWidget._creditContainer.style.display = "none";
+
+     // var promise = this.cesium.viewer.scene.open('http://www.supermapol.com/realspace/services/3D-ZhongGuoDiTu/rest/realspace');
+     // this.cesium.when.all(promise, function (layers) {
+     //     console.log(layers)
+     //     var layer4 = scene.layers.find("Province_L@中国地图");
+     //     var layerEffect4 = layer4.effect;
+     //     layerEffect4.setValue('Color', new Cesium.Color(132 * 2 / 255, 143 * 2 / 255, 11 * 2 / 255, 1));
+     //     layerEffect4.setValue('Width', 1);
+
+     //     var layer3 = scene.layers.find("BorderA_L@中国地图");
+     //     var layerEffect3 = layer3.effect;
+     //     layerEffect3.setValue('Color', new Cesium.Color(6 * 2 / 255, 180 * 2 / 255, 224 * 2 / 255, 1));
+     //     layerEffect3.setValue('Width', 3);
+
+     //     var layer1 = scene.layers.find("Province_R@中国地图");
+     //     layer1.style3D.fillForeColor = new Cesium.Color(0 / 255, 180 / 255, 255 * 2 / 255, 0.6);
+
+
+     //     var layer0 = scene.layers.find("ProvinceCity@中国地图");
+     //     layer0.indexedDBSetting.isAttributesSave = true;//保存属性
+     //     layer0.showLabel = true;
+
+     //     layer0.labelStyle = new Cesium.S3MTilesLabelStyle({
+     //         fillColor: Cesium.Color.WHITE,
+     //         outlineColor: Cesium.Color.RED,
+     //         font: '44px Calibri',
+     //         outlineWidth: 2.0,
+     //         scale: 0.5,
+     //         fillStyle: Cesium.LabelStyle.FILL_AND_OUTLINE,
+     //         textField: 'Name',
+     //         pixelOffset: new Cesium.Cartesian2(0, -5)
+     //     });
+     // });
+     // const center = Cartesian3.fromDegrees(
+     //   (minWGS84[0] + maxWGS84[0]) / 2,
+     //   (minWGS84[1] + maxWGS84[1]) / 2 - 1,
+     //   200000
+     // )
+     // setTimeout(() => {
+     //   this.cesium.viewer.camera.flyTo({
+     //     destination: Cartesian3.fromDegrees(125.331345,43.891262, 50000.0),
+     //     duration:5,
+     //     complete: () => {
+     //       // 定位完成之后的回调函数
+     //       document.getElementById("cesiumContainer").style.display="none";
+     //       this.init()
+     //     },
+     //   });
+     // },3000)
+   },
+   //同步两个渲染器的渲染
+   startRenderLoop() {
+     this.init()
+     this.cesium.viewer.render()
+     requestAnimationFrame(this.startRenderLoop)
+   },
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 370 - 0
zhsq_qk-ui/.history/src/map3d/index_20240617131918.vue

@@ -0,0 +1,370 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       this.geoJson,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+     const flyObject3D = new THREE.Object3D();
+     const flySpotList = [];
+     connectLine.forEach((item) => {
+       const { indexStart, indexEnd } = item;
+       const { flyLine, flySpot } = drawLineBetween2Spot(
+         label2dData[indexStart].featureCenterCoord,
+         label2dData[indexEnd].featureCenterCoord
+       );
+       flyObject3D.add(flyLine);
+       flyObject3D.add(flySpot);
+       flySpotList.push(flySpot);
+     });
+     mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+       flySpotList.forEach(function (mesh) {
+         mesh._s += 0.003;
+         let tankPosition = new THREE.Vector3();
+         // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+         tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+         mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+       });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 370 - 0
zhsq_qk-ui/.history/src/map3d/index_20240617132010.vue

@@ -0,0 +1,370 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       this.geoJson,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+       flySpotList.forEach(function (mesh) {
+         mesh._s += 0.003;
+         let tankPosition = new THREE.Vector3();
+         // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+         tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+         mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+       });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 370 - 0
zhsq_qk-ui/.history/src/map3d/index_20240617132132.vue

@@ -0,0 +1,370 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       this.geoJson,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 370 - 0
zhsq_qk-ui/.history/src/map3d/index_20240617135525.vue

@@ -0,0 +1,370 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       this.geoJson,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+        //  this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 370 - 0
zhsq_qk-ui/.history/src/map3d/index_20240617135539.vue

@@ -0,0 +1,370 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       this.geoJson,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 374 - 0
zhsq_qk-ui/.history/src/map3d/index_20240617140035.vue

@@ -0,0 +1,374 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+import { set } from "nprogress";
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     setTimeout(() => {
+      const { mapObject3D, label2dData } = generateMapObject3D(
+       this.geoJson,
+       this.projectionFnParam
+     );
+     })
+     
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 370 - 0
zhsq_qk-ui/.history/src/map3d/index_20240617140107.vue

@@ -0,0 +1,370 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       this.geoJson,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 373 - 0
zhsq_qk-ui/.history/src/map3d/index_20240617140123.vue

@@ -0,0 +1,373 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     setTimeout(() => {
+      const { mapObject3D, label2dData } = generateMapObject3D(
+       this.geoJson,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+     })
+     
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 370 - 0
zhsq_qk-ui/.history/src/map3d/index_20240617140143.vue

@@ -0,0 +1,370 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       this.geoJson,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 370 - 0
zhsq_qk-ui/.history/src/map3d/index_20240617140501.vue

@@ -0,0 +1,370 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   async init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = await generateMapObject3D(
+       this.geoJson,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 371 - 0
zhsq_qk-ui/.history/src/map3d/index_20240617140729.vue

@@ -0,0 +1,371 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+const qikaiJSON = require('@/assets/geoJson/test.json')
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+      qikaiJSON,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 371 - 0
zhsq_qk-ui/.history/src/map3d/index_20240617140811.vue

@@ -0,0 +1,371 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+const qikaiJSON = require('@/assets/geoJson/test.json')
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       qikaiJSON,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 371 - 0
zhsq_qk-ui/.history/src/map3d/index_20240617143556.vue

@@ -0,0 +1,371 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+const qikaiJSON = require('@/assets/geoJson/seven.json')
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       qikaiJSON,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 371 - 0
zhsq_qk-ui/.history/src/map3d/index_20240617143723.vue

@@ -0,0 +1,371 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+const qikaiJSON = require('@/assets/geoJson/test.json')
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       qikaiJSON,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 371 - 0
zhsq_qk-ui/.history/src/map3d/index_20240617154131.vue

@@ -0,0 +1,371 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+const qikaiJSON = require('@/assets/geoJson/seven.json')
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       qikaiJSON,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 416 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618085825.vue

@@ -0,0 +1,416 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+const qikaiJSON = require('@/assets/geoJson/seven.json')
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       qikaiJSON,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 创建光圈
+     const ringGeometry = new THREE.RingGeometry(30.5, 28, 39);
+     const ringMaterial = new THREE.MeshBasicMaterial({ 
+       map:'@/assets/images/qkq_mapcon.png',
+       color: '#3E422E', 
+       side: THREE.DoubleSide ,
+       transparent:true,
+       opacity:0.8,
+       emissive: '#fff', // 设置发光颜色为白色
+       emissiveIntensity: 1, // 设置发光强度为1
+     })
+     const ringMesh = new THREE.Mesh(ringGeometry, ringMaterial);
+
+     // 创建一个蓝色边界的光圈
+     const innerRingGeometry = new THREE.RingGeometry(28, 27.6, 39);
+     const innerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+     const innerRingMesh = new THREE.Mesh(innerRingGeometry, innerRingMaterial);
+
+     // 设置光圈的位置
+     innerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+     // 翻转光圈
+     innerRingMesh.rotation.x = Math.PI / 1.7; 
+     innerRingMesh.rotation.y = Math.PI / 1.05; 
+     scene.add(innerRingMesh);
+     
+     // 创建一个蓝色边界的光圈
+     const outerRingGeometry = new THREE.RingGeometry(31, 30.6, 39);
+     const outerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+     const outerRingMesh = new THREE.Mesh(outerRingGeometry, outerRingMaterial);
+
+     // 设置光圈的位置
+     outerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+     // 翻转光圈
+     outerRingMesh.rotation.x = Math.PI / 1.7; 
+     outerRingMesh.rotation.y = Math.PI / 1.05; 
+     scene.add(outerRingMesh);
+
+     // 设置光圈的位置
+     ringMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+     // 翻转光圈
+     ringMesh.rotation.x = Math.PI / 1.7; 
+     ringMesh.rotation.y = Math.PI / 1.05; 
+     console.log('光圈参数',ringMesh)
+     scene.add(ringMesh);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 417 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618090013.vue

@@ -0,0 +1,417 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+const qikaiJSON = require('@/assets/geoJson/seven.json')
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       qikaiJSON,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 创建光圈
+     const ringGeometry = new THREE.RingGeometry(30.5, 28, 39);
+     const textureLoader = new THREE.TextureLoader()
+     const ringMaterial = new THREE.MeshBasicMaterial({ 
+       map:textureLoader.load('@/assets/images/qkq_mapcon.png'),
+       color: '#3E422E', 
+       side: THREE.DoubleSide ,
+       transparent:true,
+       opacity:0.8,
+       emissive: '#fff', // 设置发光颜色为白色
+       emissiveIntensity: 1, // 设置发光强度为1
+     })
+     const ringMesh = new THREE.Mesh(ringGeometry, ringMaterial);
+
+     // 创建一个蓝色边界的光圈
+     const innerRingGeometry = new THREE.RingGeometry(28, 27.6, 39);
+     const innerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+     const innerRingMesh = new THREE.Mesh(innerRingGeometry, innerRingMaterial);
+
+     // 设置光圈的位置
+     innerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+     // 翻转光圈
+     innerRingMesh.rotation.x = Math.PI / 1.7; 
+     innerRingMesh.rotation.y = Math.PI / 1.05; 
+     scene.add(innerRingMesh);
+     
+     // 创建一个蓝色边界的光圈
+     const outerRingGeometry = new THREE.RingGeometry(31, 30.6, 39);
+     const outerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+     const outerRingMesh = new THREE.Mesh(outerRingGeometry, outerRingMaterial);
+
+     // 设置光圈的位置
+     outerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+     // 翻转光圈
+     outerRingMesh.rotation.x = Math.PI / 1.7; 
+     outerRingMesh.rotation.y = Math.PI / 1.05; 
+     scene.add(outerRingMesh);
+
+     // 设置光圈的位置
+     ringMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+     // 翻转光圈
+     ringMesh.rotation.x = Math.PI / 1.7; 
+     ringMesh.rotation.y = Math.PI / 1.05; 
+     console.log('光圈参数',ringMesh)
+     scene.add(ringMesh);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 417 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618090035.vue

@@ -0,0 +1,417 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+const qikaiJSON = require('@/assets/geoJson/seven.json')
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       qikaiJSON,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 创建光圈
+     const ringGeometry = new THREE.RingGeometry(30.5, 28, 39);
+     const textureLoader = new THREE.TextureLoader()
+     const ringMaterial = new THREE.MeshBasicMaterial({ 
+       map:textureLoader.load('@/assets/images/qkq_mapcon.png'),
+       color: '#3E422E', 
+       side: THREE.DoubleSide ,
+       transparent:true,
+       opacity:0.8,
+       emissive: '#fff', // 设置发光颜色为白色
+       emissiveIntensity: 1, // 设置发光强度为1
+     })
+     const ringMesh = new THREE.Mesh(ringGeometry, ringMaterial);
+
+     // 创建一个边界的光圈
+    //  const innerRingGeometry = new THREE.RingGeometry(28, 27.6, 39);
+    //  const innerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const innerRingMesh = new THREE.Mesh(innerRingGeometry, innerRingMaterial);
+
+     // 设置光圈的位置
+     innerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+     // 翻转光圈
+     innerRingMesh.rotation.x = Math.PI / 1.7; 
+     innerRingMesh.rotation.y = Math.PI / 1.05; 
+     scene.add(innerRingMesh);
+     
+     // 创建一个蓝色边界的光圈
+     const outerRingGeometry = new THREE.RingGeometry(31, 30.6, 39);
+     const outerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+     const outerRingMesh = new THREE.Mesh(outerRingGeometry, outerRingMaterial);
+
+     // 设置光圈的位置
+     outerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+     // 翻转光圈
+     outerRingMesh.rotation.x = Math.PI / 1.7; 
+     outerRingMesh.rotation.y = Math.PI / 1.05; 
+     scene.add(outerRingMesh);
+
+     // 设置光圈的位置
+     ringMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+     // 翻转光圈
+     ringMesh.rotation.x = Math.PI / 1.7; 
+     ringMesh.rotation.y = Math.PI / 1.05; 
+     console.log('光圈参数',ringMesh)
+     scene.add(ringMesh);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 417 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618090050.vue

@@ -0,0 +1,417 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+const qikaiJSON = require('@/assets/geoJson/seven.json')
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       qikaiJSON,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 创建光圈
+     const ringGeometry = new THREE.RingGeometry(30.5, 28, 39);
+     const textureLoader = new THREE.TextureLoader()
+     const ringMaterial = new THREE.MeshBasicMaterial({ 
+       map:textureLoader.load('@/assets/images/qkq_mapcon.png'),
+       color: '#3E422E', 
+       side: THREE.DoubleSide ,
+       transparent:true,
+       opacity:0.8,
+       emissive: '#fff', // 设置发光颜色为白色
+       emissiveIntensity: 1, // 设置发光强度为1
+     })
+     const ringMesh = new THREE.Mesh(ringGeometry, ringMaterial);
+
+     // 创建一个边界的光圈
+    //  const innerRingGeometry = new THREE.RingGeometry(28, 27.6, 39);
+    //  const innerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const innerRingMesh = new THREE.Mesh(innerRingGeometry, innerRingMaterial);
+
+     // 设置光圈的位置
+     innerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+     // 翻转光圈
+     innerRingMesh.rotation.x = Math.PI / 1.7; 
+     innerRingMesh.rotation.y = Math.PI / 1.05; 
+     scene.add(innerRingMesh);
+     
+     // 创建一个蓝色边界的光圈
+    //  const outerRingGeometry = new THREE.RingGeometry(31, 30.6, 39);
+    //  const outerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const outerRingMesh = new THREE.Mesh(outerRingGeometry, outerRingMaterial);
+
+    //  // 设置光圈的位置
+    //  outerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  outerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  outerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(outerRingMesh);
+
+    //  // 设置光圈的位置
+    //  ringMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  ringMesh.rotation.x = Math.PI / 1.7; 
+    //  ringMesh.rotation.y = Math.PI / 1.05; 
+    //  console.log('光圈参数',ringMesh)
+    //  scene.add(ringMesh);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 417 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618090113.vue

@@ -0,0 +1,417 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+const qikaiJSON = require('@/assets/geoJson/seven.json')
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       qikaiJSON,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 创建光圈
+     const ringGeometry = new THREE.RingGeometry(30.5, 28, 39);
+     const textureLoader = new THREE.TextureLoader()
+     const ringMaterial = new THREE.MeshBasicMaterial({ 
+       map:textureLoader.load('@/assets/images/qkq_mapcon.png'),
+       color: '#3E422E', 
+       side: THREE.DoubleSide ,
+       transparent:true,
+       opacity:0.8,
+       emissive: '#fff', // 设置发光颜色为白色
+       emissiveIntensity: 1, // 设置发光强度为1
+     })
+     const ringMesh = new THREE.Mesh(ringGeometry, ringMaterial);
+
+     // 创建一个边界的光圈
+    //  const innerRingGeometry = new THREE.RingGeometry(28, 27.6, 39);
+    //  const innerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const innerRingMesh = new THREE.Mesh(innerRingGeometry, innerRingMaterial);
+
+     // 设置光圈的位置
+    //  innerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  innerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  innerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(innerRingMesh);
+     
+     // 创建一个蓝色边界的光圈
+    //  const outerRingGeometry = new THREE.RingGeometry(31, 30.6, 39);
+    //  const outerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const outerRingMesh = new THREE.Mesh(outerRingGeometry, outerRingMaterial);
+
+    //  // 设置光圈的位置
+    //  outerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  outerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  outerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(outerRingMesh);
+
+    //  // 设置光圈的位置
+    //  ringMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  ringMesh.rotation.x = Math.PI / 1.7; 
+    //  ringMesh.rotation.y = Math.PI / 1.05; 
+    //  console.log('光圈参数',ringMesh)
+    //  scene.add(ringMesh);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 418 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618090207.vue

@@ -0,0 +1,418 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+const qikaiJSON = require('@/assets/geoJson/seven.json')
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       qikaiJSON,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 创建光圈
+     const ringGeometry = new THREE.RingGeometry(30.5, 28, 39);
+     const textureLoader = new THREE.TextureLoader()
+     const ringMaterial = new THREE.MeshBasicMaterial({ 
+       map:textureLoader.load('@/assets/images/qkq_mapcon.png'),
+       color: '#3E422E', 
+       side: THREE.DoubleSide ,
+       transparent:true,
+       opacity:0.8,
+       emissive: '#fff', // 设置发光颜色为白色
+       emissiveIntensity: 1, // 设置发光强度为1
+     })
+     const ringMesh = new THREE.Mesh(ringGeometry, ringMaterial);
+     scene.add(ringMesh)
+
+     // 创建一个边界的光圈
+    //  const innerRingGeometry = new THREE.RingGeometry(28, 27.6, 39);
+    //  const innerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const innerRingMesh = new THREE.Mesh(innerRingGeometry, innerRingMaterial);
+
+     // 设置光圈的位置
+    //  innerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  innerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  innerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(innerRingMesh);
+     
+     // 创建一个蓝色边界的光圈
+    //  const outerRingGeometry = new THREE.RingGeometry(31, 30.6, 39);
+    //  const outerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const outerRingMesh = new THREE.Mesh(outerRingGeometry, outerRingMaterial);
+
+    //  // 设置光圈的位置
+    //  outerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  outerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  outerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(outerRingMesh);
+
+    //  // 设置光圈的位置
+    //  ringMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  ringMesh.rotation.x = Math.PI / 1.7; 
+    //  ringMesh.rotation.y = Math.PI / 1.05; 
+    //  console.log('光圈参数',ringMesh)
+    //  scene.add(ringMesh);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 419 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618090244.vue

@@ -0,0 +1,419 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+const qikaiJSON = require('@/assets/geoJson/seven.json')
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       qikaiJSON,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 创建光圈
+     const ringGeometry = new THREE.RingGeometry(30.5, 28, 39);
+     const textureLoader = new THREE.TextureLoader()
+     console.log('textureLoader',textureLoader.load('@/assets/images/qkq_mapcon.png'))
+     const ringMaterial = new THREE.MeshBasicMaterial({ 
+       map:textureLoader.load('@/assets/images/qkq_mapcon.png'),
+       color: '#3E422E', 
+       side: THREE.DoubleSide ,
+       transparent:true,
+       opacity:0.8,
+       emissive: '#fff', // 设置发光颜色为白色
+       emissiveIntensity: 1, // 设置发光强度为1
+     })
+     const ringMesh = new THREE.Mesh(ringGeometry, ringMaterial);
+     scene.add(ringMesh)
+
+     // 创建一个边界的光圈
+    //  const innerRingGeometry = new THREE.RingGeometry(28, 27.6, 39);
+    //  const innerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const innerRingMesh = new THREE.Mesh(innerRingGeometry, innerRingMaterial);
+
+     // 设置光圈的位置
+    //  innerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  innerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  innerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(innerRingMesh);
+     
+     // 创建一个蓝色边界的光圈
+    //  const outerRingGeometry = new THREE.RingGeometry(31, 30.6, 39);
+    //  const outerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const outerRingMesh = new THREE.Mesh(outerRingGeometry, outerRingMaterial);
+
+    //  // 设置光圈的位置
+    //  outerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  outerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  outerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(outerRingMesh);
+
+    //  // 设置光圈的位置
+    //  ringMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  ringMesh.rotation.x = Math.PI / 1.7; 
+    //  ringMesh.rotation.y = Math.PI / 1.05; 
+    //  console.log('光圈参数',ringMesh)
+    //  scene.add(ringMesh);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 420 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618090501.vue

@@ -0,0 +1,420 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+const qikaiJSON = require('@/assets/geoJson/seven.json')
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       qikaiJSON,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 创建光圈
+     const ringGeometry = new THREE.RingGeometry(30.5, 28, 39);
+     const textureLoader = new THREE.TextureLoader()
+     const texture = textureLoader.load('@/assets/images/qkq_mapcon.png')
+     texture.mapping = THREE.EquirectangularReflectionMapping
+     const ringMaterial = new THREE.MeshBasicMaterial({ 
+       map:texture,
+       color: '#3E422E', 
+       side: THREE.DoubleSide ,
+       transparent:true,
+       opacity:0.8,
+       emissive: '#fff', // 设置发光颜色为白色
+       emissiveIntensity: 1, // 设置发光强度为1
+     })
+     const ringMesh = new THREE.Mesh(ringGeometry, ringMaterial);
+     scene.add(ringMesh)
+
+     // 创建一个边界的光圈
+    //  const innerRingGeometry = new THREE.RingGeometry(28, 27.6, 39);
+    //  const innerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const innerRingMesh = new THREE.Mesh(innerRingGeometry, innerRingMaterial);
+
+     // 设置光圈的位置
+    //  innerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  innerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  innerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(innerRingMesh);
+     
+     // 创建一个蓝色边界的光圈
+    //  const outerRingGeometry = new THREE.RingGeometry(31, 30.6, 39);
+    //  const outerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const outerRingMesh = new THREE.Mesh(outerRingGeometry, outerRingMaterial);
+
+    //  // 设置光圈的位置
+    //  outerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  outerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  outerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(outerRingMesh);
+
+    //  // 设置光圈的位置
+    //  ringMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  ringMesh.rotation.x = Math.PI / 1.7; 
+    //  ringMesh.rotation.y = Math.PI / 1.05; 
+    //  console.log('光圈参数',ringMesh)
+    //  scene.add(ringMesh);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 423 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618090525.vue

@@ -0,0 +1,423 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+const qikaiJSON = require('@/assets/geoJson/seven.json')
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       qikaiJSON,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 创建光圈
+     const ringGeometry = new THREE.RingGeometry(30.5, 28, 39);
+     const textureLoader = new THREE.TextureLoader()
+     const texture = textureLoader.load('@/assets/images/qkq_mapcon.png')
+     texture.mapping = THREE.EquirectangularReflectionMapping
+     const ringMaterial = new THREE.MeshBasicMaterial({ 
+       map:texture,
+       color: '#3E422E', 
+       side: THREE.DoubleSide ,
+       transparent:true,
+       opacity:0.8,
+       emissive: '#fff', // 设置发光颜色为白色
+       emissiveIntensity: 1, // 设置发光强度为1
+     })
+     scene.background = texture
+		 scene.environment = texture
+
+     const ringMesh = new THREE.Mesh(ringGeometry, ringMaterial);
+     scene.add(ringMesh)
+
+     // 创建一个边界的光圈
+    //  const innerRingGeometry = new THREE.RingGeometry(28, 27.6, 39);
+    //  const innerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const innerRingMesh = new THREE.Mesh(innerRingGeometry, innerRingMaterial);
+
+     // 设置光圈的位置
+    //  innerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  innerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  innerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(innerRingMesh);
+     
+     // 创建一个蓝色边界的光圈
+    //  const outerRingGeometry = new THREE.RingGeometry(31, 30.6, 39);
+    //  const outerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const outerRingMesh = new THREE.Mesh(outerRingGeometry, outerRingMaterial);
+
+    //  // 设置光圈的位置
+    //  outerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  outerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  outerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(outerRingMesh);
+
+    //  // 设置光圈的位置
+    //  ringMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  ringMesh.rotation.x = Math.PI / 1.7; 
+    //  ringMesh.rotation.y = Math.PI / 1.05; 
+    //  console.log('光圈参数',ringMesh)
+    //  scene.add(ringMesh);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 424 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618090620.vue

@@ -0,0 +1,424 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+const qikaiJSON = require('@/assets/geoJson/seven.json')
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       qikaiJSON,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 创建光圈
+     const ringGeometry = new THREE.RingGeometry(30.5, 28, 39);
+     const textureLoader = new THREE.TextureLoader()
+     const texture = textureLoader.load('@/assets/images/qkq_mapcon.png')
+     texture.mapping = THREE.EquirectangularReflectionMapping
+     const ringMaterial = new THREE.MeshBasicMaterial({ 
+       map:texture,
+       color: '#3E422E', 
+       side: THREE.DoubleSide ,
+       transparent:true,
+       opacity:0.8,
+       emissive: '#fff', // 设置发光颜色为白色
+       emissiveIntensity: 1, // 设置发光强度为1
+     })
+     scene.background = new THREE.Color('#fff')
+     scene.background = texture
+		 scene.environment = texture
+
+     const ringMesh = new THREE.Mesh(ringGeometry, ringMaterial);
+     scene.add(ringMesh)
+
+     // 创建一个边界的光圈
+    //  const innerRingGeometry = new THREE.RingGeometry(28, 27.6, 39);
+    //  const innerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const innerRingMesh = new THREE.Mesh(innerRingGeometry, innerRingMaterial);
+
+     // 设置光圈的位置
+    //  innerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  innerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  innerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(innerRingMesh);
+     
+     // 创建一个蓝色边界的光圈
+    //  const outerRingGeometry = new THREE.RingGeometry(31, 30.6, 39);
+    //  const outerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const outerRingMesh = new THREE.Mesh(outerRingGeometry, outerRingMaterial);
+
+    //  // 设置光圈的位置
+    //  outerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  outerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  outerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(outerRingMesh);
+
+    //  // 设置光圈的位置
+    //  ringMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  ringMesh.rotation.x = Math.PI / 1.7; 
+    //  ringMesh.rotation.y = Math.PI / 1.05; 
+    //  console.log('光圈参数',ringMesh)
+    //  scene.add(ringMesh);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 424 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618090638.vue

@@ -0,0 +1,424 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+const qikaiJSON = require('@/assets/geoJson/seven.json')
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       qikaiJSON,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 创建光圈
+     const ringGeometry = new THREE.RingGeometry(30.5, 28, 39);
+     const textureLoader = new THREE.TextureLoader()
+     const texture = textureLoader.load('@/assets/images/qkq_mapcon.png')
+     texture.mapping = THREE.EquirectangularReflectionMapping
+     const ringMaterial = new THREE.MeshBasicMaterial({ 
+       map:texture,
+       color: '#3E422E', 
+       side: THREE.DoubleSide ,
+       transparent:true,
+       opacity:0.8,
+       emissive: '#fff', // 设置发光颜色为白色
+       emissiveIntensity: 1, // 设置发光强度为1
+     })
+     scene.background = new THREE.Color('#red')
+     scene.background = texture
+		 scene.environment = texture
+
+     const ringMesh = new THREE.Mesh(ringGeometry, ringMaterial);
+     scene.add(ringMesh)
+
+     // 创建一个边界的光圈
+    //  const innerRingGeometry = new THREE.RingGeometry(28, 27.6, 39);
+    //  const innerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const innerRingMesh = new THREE.Mesh(innerRingGeometry, innerRingMaterial);
+
+     // 设置光圈的位置
+    //  innerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  innerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  innerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(innerRingMesh);
+     
+     // 创建一个蓝色边界的光圈
+    //  const outerRingGeometry = new THREE.RingGeometry(31, 30.6, 39);
+    //  const outerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const outerRingMesh = new THREE.Mesh(outerRingGeometry, outerRingMaterial);
+
+    //  // 设置光圈的位置
+    //  outerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  outerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  outerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(outerRingMesh);
+
+    //  // 设置光圈的位置
+    //  ringMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  ringMesh.rotation.x = Math.PI / 1.7; 
+    //  ringMesh.rotation.y = Math.PI / 1.05; 
+    //  console.log('光圈参数',ringMesh)
+    //  scene.add(ringMesh);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 424 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618090759.vue

@@ -0,0 +1,424 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+const qikaiJSON = require('@/assets/geoJson/seven.json')
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       qikaiJSON,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 创建光圈
+    //  const ringGeometry = new THREE.RingGeometry(30.5, 28, 39);
+    //  const textureLoader = new THREE.TextureLoader()
+    //  const texture = textureLoader.load('@/assets/images/qkq_mapcon.png')
+    //  texture.mapping = THREE.EquirectangularReflectionMapping
+    //  const ringMaterial = new THREE.MeshBasicMaterial({ 
+    //    map:texture,
+    //    color: '#3E422E', 
+    //    side: THREE.DoubleSide ,
+    //    transparent:true,
+    //    opacity:0.8,
+    //    emissive: '#fff', // 设置发光颜色为白色
+    //    emissiveIntensity: 1, // 设置发光强度为1
+    //  })
+    //  scene.background = new THREE.Color('#red')
+    //  scene.background = texture
+		//  scene.environment = texture
+
+    //  const ringMesh = new THREE.Mesh(ringGeometry, ringMaterial);
+    //  scene.add(ringMesh)
+
+     // 创建一个边界的光圈
+    //  const innerRingGeometry = new THREE.RingGeometry(28, 27.6, 39);
+    //  const innerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const innerRingMesh = new THREE.Mesh(innerRingGeometry, innerRingMaterial);
+
+     // 设置光圈的位置
+    //  innerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  innerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  innerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(innerRingMesh);
+     
+     // 创建一个蓝色边界的光圈
+    //  const outerRingGeometry = new THREE.RingGeometry(31, 30.6, 39);
+    //  const outerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const outerRingMesh = new THREE.Mesh(outerRingGeometry, outerRingMaterial);
+
+    //  // 设置光圈的位置
+    //  outerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  outerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  outerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(outerRingMesh);
+
+    //  // 设置光圈的位置
+    //  ringMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  ringMesh.rotation.x = Math.PI / 1.7; 
+    //  ringMesh.rotation.y = Math.PI / 1.05; 
+    //  console.log('光圈参数',ringMesh)
+    //  scene.add(ringMesh);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 424 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618092026.vue

@@ -0,0 +1,424 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+const qikaiJSON = require('@/assets/geoJson/seven.json')
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       qikaiJSON,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 创建光圈
+     const ringGeometry = new THREE.RingGeometry(30.5, 28, 39);
+     const textureLoader = new THREE.TextureLoader()
+     const texture = textureLoader.load('@/assets/images/qkq_mapcon.png')
+     texture.mapping = THREE.EquirectangularReflectionMapping
+     const ringMaterial = new THREE.MeshBasicMaterial({ 
+       map:texture,
+       color: '#3E422E', 
+       side: THREE.DoubleSide ,
+       transparent:true,
+       opacity:0.8,
+       emissive: '#fff', // 设置发光颜色为白色
+       emissiveIntensity: 1, // 设置发光强度为1
+     })
+     scene.background = new THREE.Color('#red')
+     scene.background = texture
+		 scene.environment = texture
+     console.log('scene',scene)
+     const ringMesh = new THREE.Mesh(ringGeometry, ringMaterial);
+     scene.add(ringMesh)
+
+     // 创建一个边界的光圈
+    //  const innerRingGeometry = new THREE.RingGeometry(28, 27.6, 39);
+    //  const innerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const innerRingMesh = new THREE.Mesh(innerRingGeometry, innerRingMaterial);
+
+     // 设置光圈的位置
+    //  innerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  innerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  innerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(innerRingMesh);
+     
+     // 创建一个蓝色边界的光圈
+    //  const outerRingGeometry = new THREE.RingGeometry(31, 30.6, 39);
+    //  const outerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const outerRingMesh = new THREE.Mesh(outerRingGeometry, outerRingMaterial);
+
+    //  // 设置光圈的位置
+    //  outerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  outerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  outerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(outerRingMesh);
+
+    //  // 设置光圈的位置
+    //  ringMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  ringMesh.rotation.x = Math.PI / 1.7; 
+    //  ringMesh.rotation.y = Math.PI / 1.05; 
+    //  console.log('光圈参数',ringMesh)
+    //  scene.add(ringMesh);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 424 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618092302.vue

@@ -0,0 +1,424 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+const qikaiJSON = require('@/assets/geoJson/seven.json')
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       qikaiJSON,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 创建光圈
+     const ringGeometry = new THREE.RingGeometry(30.5, 28, 39);
+     const textureLoader = new THREE.TextureLoader()
+     const texture = textureLoader.load('@/assets/images/qkq_mapcon.png')
+     texture.mapping = THREE.EquirectangularReflectionMapping
+     const ringMaterial = new THREE.MeshBasicMaterial({ 
+       map:texture,
+      //  color: '#3E422E', 
+       side: THREE.DoubleSide ,
+       transparent:true,
+      //  opacity:0.8,
+       emissive: '#fff', // 设置发光颜色为白色
+       emissiveIntensity: 1, // 设置发光强度为1
+     })
+     scene.background = new THREE.Color('#red')
+     scene.background = texture
+		 scene.environment = texture
+     console.log('scene',scene)
+     const ringMesh = new THREE.Mesh(ringGeometry, ringMaterial);
+     scene.add(ringMesh)
+
+     // 创建一个边界的光圈
+    //  const innerRingGeometry = new THREE.RingGeometry(28, 27.6, 39);
+    //  const innerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const innerRingMesh = new THREE.Mesh(innerRingGeometry, innerRingMaterial);
+
+     // 设置光圈的位置
+    //  innerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  innerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  innerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(innerRingMesh);
+     
+     // 创建一个蓝色边界的光圈
+    //  const outerRingGeometry = new THREE.RingGeometry(31, 30.6, 39);
+    //  const outerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const outerRingMesh = new THREE.Mesh(outerRingGeometry, outerRingMaterial);
+
+    //  // 设置光圈的位置
+    //  outerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  outerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  outerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(outerRingMesh);
+
+    //  // 设置光圈的位置
+    //  ringMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  ringMesh.rotation.x = Math.PI / 1.7; 
+    //  ringMesh.rotation.y = Math.PI / 1.05; 
+    //  console.log('光圈参数',ringMesh)
+    //  scene.add(ringMesh);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 424 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618092400.vue

@@ -0,0 +1,424 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+const qikaiJSON = require('@/assets/geoJson/seven.json')
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       qikaiJSON,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 创建光圈
+     const ringGeometry = new THREE.RingGeometry(30.5, 28, 39);
+     const textureLoader = new THREE.TextureLoader()
+     const texture = textureLoader.load('../assets/images/qkq_mapcon.png')
+     texture.mapping = THREE.EquirectangularReflectionMapping
+     const ringMaterial = new THREE.MeshBasicMaterial({ 
+       map:texture,
+      //  color: '#3E422E', 
+       side: THREE.DoubleSide ,
+       transparent:true,
+      //  opacity:0.8,
+       emissive: '#fff', // 设置发光颜色为白色
+       emissiveIntensity: 1, // 设置发光强度为1
+     })
+     scene.background = new THREE.Color('#red')
+     scene.background = texture
+		 scene.environment = texture
+     console.log('scene',scene)
+     const ringMesh = new THREE.Mesh(ringGeometry, ringMaterial);
+     scene.add(ringMesh)
+
+     // 创建一个边界的光圈
+    //  const innerRingGeometry = new THREE.RingGeometry(28, 27.6, 39);
+    //  const innerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const innerRingMesh = new THREE.Mesh(innerRingGeometry, innerRingMaterial);
+
+     // 设置光圈的位置
+    //  innerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  innerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  innerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(innerRingMesh);
+     
+     // 创建一个蓝色边界的光圈
+    //  const outerRingGeometry = new THREE.RingGeometry(31, 30.6, 39);
+    //  const outerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const outerRingMesh = new THREE.Mesh(outerRingGeometry, outerRingMaterial);
+
+    //  // 设置光圈的位置
+    //  outerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  outerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  outerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(outerRingMesh);
+
+    //  // 设置光圈的位置
+    //  ringMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  ringMesh.rotation.x = Math.PI / 1.7; 
+    //  ringMesh.rotation.y = Math.PI / 1.05; 
+    //  console.log('光圈参数',ringMesh)
+    //  scene.add(ringMesh);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 424 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618092422.vue

@@ -0,0 +1,424 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+const qikaiJSON = require('@/assets/geoJson/seven.json')
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       qikaiJSON,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 创建光圈
+     const ringGeometry = new THREE.RingGeometry(30.5, 28, 39);
+     const textureLoader = new THREE.TextureLoader()
+     const texture = textureLoader.load('../assets/images/qkq_mapcon.png')
+     texture.mapping = THREE.EquirectangularReflectionMapping
+     const ringMaterial = new THREE.MeshBasicMaterial({ 
+       map:texture,
+      //  color: '#3E422E', 
+       side: THREE.DoubleSide ,
+       transparent:true,
+      //  opacity:0.8,
+       emissive: '#fff', // 设置发光颜色为白色
+       emissiveIntensity: 1, // 设置发光强度为1
+     })
+     scene.background = new THREE.Color('#red')
+     scene.background = texture
+		 scene.environment = texture
+     console.log('scene',scene)
+     const ringMesh = new THREE.Mesh(ringGeometry, ringMaterial);
+     scene.add(ringMesh)
+
+     // 创建一个边界的光圈
+    //  const innerRingGeometry = new THREE.RingGeometry(28, 27.6, 39);
+    //  const innerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const innerRingMesh = new THREE.Mesh(innerRingGeometry, innerRingMaterial);
+
+     // 设置光圈的位置
+    //  innerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  innerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  innerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(innerRingMesh);
+     
+     // 创建一个蓝色边界的光圈
+    //  const outerRingGeometry = new THREE.RingGeometry(31, 30.6, 39);
+    //  const outerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const outerRingMesh = new THREE.Mesh(outerRingGeometry, outerRingMaterial);
+
+    //  // 设置光圈的位置
+    //  outerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  outerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  outerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(outerRingMesh);
+
+    //  // 设置光圈的位置
+    //  ringMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  ringMesh.rotation.x = Math.PI / 1.7; 
+    //  ringMesh.rotation.y = Math.PI / 1.05; 
+    //  console.log('光圈参数',ringMesh)
+    //  scene.add(ringMesh);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 421 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618092548.vue

@@ -0,0 +1,421 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+const qikaiJSON = require('@/assets/geoJson/seven.json')
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       qikaiJSON,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 创建光圈
+     const ringGeometry = new THREE.RingGeometry(30.5, 28, 39);
+     const textureLoader = new THREE.TextureLoader()
+     const texture = textureLoader.load('../assets/images/qkq_mapcon.png')
+     texture.mapping = THREE.EquirectangularReflectionMapping
+     const ringMaterial = new THREE.MeshBasicMaterial({ 
+       map:texture,
+      //  color: '#3E422E', 
+      //  side: THREE.DoubleSide ,
+       transparent:true,
+      //  opacity:0.8,
+       emissive: '#fff', // 设置发光颜色为白色
+       emissiveIntensity: 1, // 设置发光强度为1
+     })
+     console.log('scene',scene)
+     const ringMesh = new THREE.Mesh(ringGeometry, ringMaterial);
+     scene.add(ringMesh)
+
+     // 创建一个边界的光圈
+    //  const innerRingGeometry = new THREE.RingGeometry(28, 27.6, 39);
+    //  const innerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const innerRingMesh = new THREE.Mesh(innerRingGeometry, innerRingMaterial);
+
+     // 设置光圈的位置
+    //  innerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  innerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  innerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(innerRingMesh);
+     
+     // 创建一个蓝色边界的光圈
+    //  const outerRingGeometry = new THREE.RingGeometry(31, 30.6, 39);
+    //  const outerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const outerRingMesh = new THREE.Mesh(outerRingGeometry, outerRingMaterial);
+
+    //  // 设置光圈的位置
+    //  outerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  outerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  outerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(outerRingMesh);
+
+    //  // 设置光圈的位置
+    //  ringMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  ringMesh.rotation.x = Math.PI / 1.7; 
+    //  ringMesh.rotation.y = Math.PI / 1.05; 
+    //  console.log('光圈参数',ringMesh)
+    //  scene.add(ringMesh);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 422 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618092627.vue

@@ -0,0 +1,422 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+const qikaiJSON = require('@/assets/geoJson/seven.json')
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       qikaiJSON,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 创建光圈
+     const ringGeometry = new THREE.RingGeometry(30.5, 28, 39);
+     const textureLoader = new THREE.TextureLoader()
+     const texture = textureLoader.load('../assets/images/qkq_mapcon.png')
+     texture.mapping = THREE.EquirectangularReflectionMapping
+     const ringMaterial = new THREE.MeshBasicMaterial({ 
+       map:texture,
+      //  color: '#3E422E', 
+      //  side: THREE.DoubleSide ,
+       transparent:true,
+      //  opacity:0.8,
+       emissive: '#fff', // 设置发光颜色为白色
+       emissiveIntensity: 1, // 设置发光强度为1
+     })
+     console.log('scene',scene)
+     const ringMesh = new THREE.Mesh(ringGeometry, ringMaterial);
+     ringMesh.rotateX(-Math.PI / 2);
+    //  scene.add(ringMesh)
+
+     // 创建一个边界的光圈
+    //  const innerRingGeometry = new THREE.RingGeometry(28, 27.6, 39);
+    //  const innerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const innerRingMesh = new THREE.Mesh(innerRingGeometry, innerRingMaterial);
+
+     // 设置光圈的位置
+    //  innerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  innerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  innerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(innerRingMesh);
+     
+     // 创建一个蓝色边界的光圈
+    //  const outerRingGeometry = new THREE.RingGeometry(31, 30.6, 39);
+    //  const outerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const outerRingMesh = new THREE.Mesh(outerRingGeometry, outerRingMaterial);
+
+    //  // 设置光圈的位置
+    //  outerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  outerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  outerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(outerRingMesh);
+
+    //  // 设置光圈的位置
+    //  ringMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  ringMesh.rotation.x = Math.PI / 1.7; 
+    //  ringMesh.rotation.y = Math.PI / 1.05; 
+    //  console.log('光圈参数',ringMesh)
+    //  scene.add(ringMesh);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 424 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618093631.vue

@@ -0,0 +1,424 @@
+ <!-- 
+ *@description: 地图入口
+ *@author: yh Fu
+ *@date: 2024-05-13 10:11:52
+ *@version: V1.0.5 
+-->
+
+<template>
+  <div class="contain"
+   >
+     <div ref="map2dRef" style="width: 100%"></div>
+     <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
+     </div>
+     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+   </div>
+</template>
+
+
+<script>
+import * as THREE from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import ToolTip from "../tooltip";
+import {
+ drawLineBetween2Spot,
+ generateMapLabel2D,
+ generateMapObject3D,
+ generateMapSpot,
+} from "./drawFunc";
+import gsap from "gsap";
+
+import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+
+import { initScene } from "./scene";
+import { mapConfig } from "./mapConfig";
+import { initCamera } from "./camera";
+const qikaiJSON = require('@/assets/geoJson/seven.json')
+
+let lastPick
+let scene
+let eventFlag
+export default {
+ name:'Map3D',
+ props:['geoJson','dblClickFn','projectionFnParam'],
+ components:{
+   ToolTip
+ },
+ data(){
+   return {
+     toolTipRef:null,
+     toolTipData:{
+       text:''
+     },
+     ratio:{
+       value: 0,
+     }
+   }
+ },
+ mounted(){
+   this.init()
+ },
+ methods:{
+   init(){
+      /**
+      * 初始化场景
+      */
+     scene = initScene()
+     const currentDom = this.$refs.mapRef
+
+      /**
+      * 初始化摄像机
+      */
+     const { camera } = initCamera(currentDom);
+
+      /**
+      * 初始化渲染器
+      */
+     const renderer = new THREE.WebGLRenderer({ antialias: true });
+     renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     // 防止开发时重复渲染
+     // if (!currentDom.hasChildNodes()) {
+     //   currentDom.appendChild(renderer.domElement);
+     // }
+     // 这里修改为下面写法,否则 onresize 不生效
+     if (currentDom.childNodes[0]) {
+       currentDom.removeChild(currentDom.childNodes[0]);
+     }
+     currentDom.appendChild(renderer.domElement);
+
+     /**
+      * 创建css2 Renderer 渲染器
+      */
+     const labelRenderer = new CSS2DRenderer();
+     labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+     labelRenderer.domElement.style.position = "absolute";
+     labelRenderer.domElement.style.top = "0px";
+     const labelRendererDom = this.$refs.map2dRef;
+     if (labelRendererDom?.childNodes[0]) {
+       labelRendererDom.removeChild(labelRendererDom.childNodes[0]);
+     }
+     labelRendererDom.appendChild(labelRenderer.domElement);
+
+      /**
+      * 初始化模型(绘制3D模型)
+      */
+     const { mapObject3D, label2dData } = generateMapObject3D(
+       qikaiJSON,
+       this.projectionFnParam
+     );
+     scene.add(mapObject3D);
+
+      /**
+      * 绘制 2D 面板
+      */
+     const labelObject2D = generateMapLabel2D(label2dData);
+     mapObject3D.add(labelObject2D);
+
+     /**
+      * 绘制点位
+      */
+     const { spotList, spotObject3D } = generateMapSpot(label2dData);
+     mapObject3D.add(spotObject3D);
+
+     const modelObject3D = new THREE.Object3D();
+     // let mixer: any = null;
+     let modelMixer = [];
+     const loader = new GLTFLoader();
+     const dracoLoader = new DRACOLoader();
+     dracoLoader.setDecoderPath("/draco/");
+     loader.setDRACOLoader(dracoLoader);
+
+     loader.load("/models/cone.glb", (glb) => {
+       label2dData.forEach((item) => {
+         // console.log(item, "0-0-0-");
+         const { featureCenterCoord } = item;
+         const clonedModel = glb.scene.clone();
+         const mixer = new THREE.AnimationMixer(clonedModel);
+         const clonedAnimations = glb.animations.map((clip) => {
+           return clip.clone();
+         });
+         clonedAnimations.forEach((clip) => {
+           mixer.clipAction(clip).play();
+         });
+
+         // 添加每个model的mixer
+         modelMixer.push(mixer);
+
+         // 设置模型位置
+         clonedModel.position.set(
+           featureCenterCoord[0],
+           -featureCenterCoord[1],
+           mapConfig.spotZIndex
+         );
+         // 设置模型大小
+         clonedModel.scale.set(0.3, 0.3, 0.6);
+         // clonedModel.rotateX(-Math.PI / 8);
+         modelObject3D.add(clonedModel);
+       });
+
+       mapObject3D.add(modelObject3D);
+     });
+     
+     /**
+      * 绘制连线(随机生成两个点位)
+      */
+     const MAX_LINE_COUNT = 5; // 随机生成5组线
+     let connectLine = [];
+     for (let count = 0; count < MAX_LINE_COUNT; count++) {
+       const midIndex = Math.floor(label2dData.length / 2);
+       const indexStart = Math.floor(Math.random() * midIndex);
+       const indexEnd = Math.floor(Math.random() * midIndex) + midIndex - 1;
+       connectLine.push({
+         indexStart,
+         indexEnd,
+       });
+     }
+
+     /**
+      * 绘制飞行的点
+      */
+    //  const flyObject3D = new THREE.Object3D();
+    //  const flySpotList = [];
+    //  connectLine.forEach((item) => {
+    //    const { indexStart, indexEnd } = item;
+    //    const { flyLine, flySpot } = drawLineBetween2Spot(
+    //      label2dData[indexStart].featureCenterCoord,
+    //      label2dData[indexEnd].featureCenterCoord
+    //    );
+    //    flyObject3D.add(flyLine);
+    //    flyObject3D.add(flySpot);
+    //    flySpotList.push(flySpot);
+    //  });
+    //  mapObject3D.add(flyObject3D);
+
+     /**
+      * 初始化控制器
+      */
+     // new OrbitControls(camera, renderer.domElement);
+     new OrbitControls(camera, labelRenderer.domElement);
+
+     /**
+      * 新增光源
+      */
+     const light = new THREE.PointLight(0xffffff, 1.5);
+     light.position.set(0, -5, 30);
+     scene.add(light);
+
+     // 创建光圈
+    //  const ringGeometry = new THREE.RingGeometry(30.5, 28, 39);
+    //  const textureLoader = new THREE.TextureLoader()
+    //  const texture = textureLoader.load('@/assets/images/qkq_mapcon.png')
+    //  texture.mapping = THREE.EquirectangularReflectionMapping
+    //  const ringMaterial = new THREE.MeshBasicMaterial({ 
+    //    map:texture,
+    //    color: '#3E422E', 
+    //    side: THREE.DoubleSide ,
+    //    transparent:true,
+    //    opacity:0.8,
+    //    emissive: '#fff', // 设置发光颜色为白色
+    //    emissiveIntensity: 1, // 设置发光强度为1
+    //  })
+    //  scene.background = new THREE.Color('#red')
+    //  scene.background = texture
+		//  scene.environment = texture
+
+    //  const ringMesh = new THREE.Mesh(ringGeometry, ringMaterial);
+    //  scene.add(ringMesh)
+
+     // 创建一个边界的光圈
+    //  const innerRingGeometry = new THREE.RingGeometry(28, 27.6, 39);
+    //  const innerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const innerRingMesh = new THREE.Mesh(innerRingGeometry, innerRingMaterial);
+
+     // 设置光圈的位置
+    //  innerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  innerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  innerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(innerRingMesh);
+     
+     // 创建一个蓝色边界的光圈
+    //  const outerRingGeometry = new THREE.RingGeometry(31, 30.6, 39);
+    //  const outerRingMaterial = new THREE.MeshBasicMaterial({ color: '#A5A973', side: THREE.DoubleSide });
+    //  const outerRingMesh = new THREE.Mesh(outerRingGeometry, outerRingMaterial);
+
+    //  // 设置光圈的位置
+    //  outerRingMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  outerRingMesh.rotation.x = Math.PI / 1.7; 
+    //  outerRingMesh.rotation.y = Math.PI / 1.05; 
+    //  scene.add(outerRingMesh);
+
+    //  // 设置光圈的位置
+    //  ringMesh.position.set(0,-10,0); // 调整位置到适当的地图坐标
+    //  // 翻转光圈
+    //  ringMesh.rotation.x = Math.PI / 1.7; 
+    //  ringMesh.rotation.y = Math.PI / 1.05; 
+    //  console.log('光圈参数',ringMesh)
+    //  scene.add(ringMesh);
+
+     // 光源辅助线
+     // const lightHelper = new THREE.PointLightHelper(light);
+     // scene.add(lightHelper);
+
+     // 视窗伸缩
+     function onResizeEvent() {
+       // 更新摄像头
+       camera.aspect = currentDom.clientWidth / currentDom.clientHeight;
+       // 更新摄像机的投影矩阵
+       camera.updateProjectionMatrix();
+       // 更新渲染器
+       renderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       labelRenderer.setSize(currentDom.clientWidth, currentDom.clientHeight);
+       // 设置渲染器的像素比例
+       renderer.setPixelRatio(window.devicePixelRatio);
+     }
+
+     /**
+      * 设置 raycaster
+      */
+     const raycaster = new THREE.Raycaster();
+     const pointer = new THREE.Vector2();
+
+      // 鼠标移入事件
+     const onMouseMoveEvent = (e) => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       pointer.x = (e.clientX / currentDom.clientWidth) * 2 - 1;
+       pointer.y = -(e.clientY / currentDom.clientHeight) * 2 + 1;
+       // 如果存在,则鼠标移出需要重置
+       if (lastPick) {
+         // lastPick.object.material[0].color.set(mapConfig.mapColor);
+
+         const color = mapConfig.mapColorGradient[Math.floor(Math.random() * 4)];
+         lastPick.object.material[0].color.set(color);
+         lastPick.object.material[0].opacity = mapConfig.mapOpacity; // 设置完全不透明
+       }
+       lastPick = null;
+       // lastPick = intersects.find(
+       //   (item: any) => item.object.material && item.object.material.length === 2
+       // );
+       // 优化
+       lastPick = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       if (lastPick) {
+         
+         const properties = lastPick.object.parent.customProperties;
+         if (lastPick.object.material[0]) {
+           lastPick.object.material[0].color.set(mapConfig.mapHoverColor);
+           lastPick.object.material[0].opacity = 1; // 设置完全不透明
+         }
+
+         // if (this.$refs.toolTipRef && this.$refs.toolTipRef.style) {
+         //   this.$refs.toolTipRef.style.left = e.clientX + 2 + "px";
+         //   this.$refs.toolTipRef.style.top = e.clientY + 2 + "px";
+         //   this.$refs.toolTipRef.style.visibility = "visible";
+         // }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     // 鼠标双击事件
+     const onDblclickEvent = () => {
+       const intersects = raycaster.intersectObjects(scene.children);
+       const target = intersects.find(
+         (item) => item.object.userData.isChangeColor
+       );
+       console.log('target',target);
+       if (target) {
+         const obj = target.object.parent;
+         console.log('targetobj: ' + obj)
+         console.log('dblClickFn: ' + this.dblClickFn)
+         const p = obj.customProperties;
+         this.dblClickFn(p);
+       }
+     };
+
+     /**
+      * 动画
+      */
+     gsap.to(mapObject3D.scale, { x: 2, y: 2, z: 1, duration: 1 });
+
+     /**
+      * Animate
+      */
+     const clock = new THREE.Clock();
+     // let previousTime = 0;
+
+     const animate = function () {
+       // const elapsedTime = clock.getElapsedTime();
+       // const deltaTime = elapsedTime - previousTime;
+       // previousTime = elapsedTime;
+
+       // Update mixer
+       // mixer?.update(deltaTime);
+       const delta = clock.getDelta();
+       modelMixer.map((item) => item.update(delta));
+
+       // 雷达
+       // this.ratio.value += 0.01;
+
+       requestAnimationFrame(animate);
+       // 通过摄像机和鼠标位置更新射线
+       raycaster.setFromCamera(pointer, camera);
+       renderer.render(scene, camera);
+       labelRenderer.render(scene, camera);
+
+       // 圆环
+       spotList.forEach((mesh) => {
+         mesh._s += 0.01;
+         mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
+         if (mesh._s <= 2) {
+           mesh.material.opacity = 2 - mesh._s;
+         } else {
+           mesh._s = 1;
+         }
+       });
+
+       // 飞行的圆点
+      //  flySpotList.forEach(function (mesh) {
+      //    mesh._s += 0.003;
+      //    let tankPosition = new THREE.Vector3();
+      //    // getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
+      //    tankPosition = mesh.curve.getPointAt(mesh._s % 1);
+      //    mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
+      //  });
+     };
+     animate();
+
+     if(!eventFlag){
+       window.addEventListener("resize", onResizeEvent, false);
+       window.addEventListener("mousemove", onMouseMoveEvent, false);
+       window.addEventListener("dblclick", onDblclickEvent, false); 
+       eventFlag = true
+     }
+
+   }
+ },
+ watch:{
+   geoJson(){
+     this.init()
+   }
+   // geoJson:{
+   //   immediate: true,
+   //   handler(){
+   //     console.log('geoJson',this.geoJson)
+   //     this.init()
+   //   }
+   // }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.contain{
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+}
+</style>

+ 25 - 0
zhsq_qk-ui/.history/src/map3d/mapConfig_20240520101625.js

@@ -0,0 +1,25 @@
+const Depth = 3;
+
+export const mapConfig = {
+  // 地图挤出厚度
+  mapDepth: Depth,
+  // 地图透明度
+  mapTransparent: true,
+  mapOpacity: 0.9,
+  // 地图颜色
+  mapColor: "#1ED760", // 地图原本颜色
+  mapHoverColor: "#0B284E", // 地图hover颜色
+  // 地图人数渐变
+  mapColorGradient: ["#1ED760", "#428220", "#096B0A", "#556238"],
+  // 地图侧面渐变
+  mapSideColor1: "#8B8B50",
+  mapSideColor2: "#394028",
+  // 上面的line
+  topLineColor: 0x41c0fb,
+  topLineWidth: 3,
+  topLineZIndex: Depth + 0.5,
+  // label 2d高度
+  label2dZIndex: Depth + 2,
+  // spot
+  spotZIndex: Depth + 0.2,
+};

+ 25 - 0
zhsq_qk-ui/.history/src/map3d/mapConfig_20240617133256.js

@@ -0,0 +1,25 @@
+const Depth = 3;
+
+export const mapConfig = {
+  // 地图挤出厚度
+  mapDepth: Depth,
+  // 地图透明度
+  mapTransparent: true,
+  mapOpacity: 0.9,
+  // 地图颜色
+  mapColor: "#9E8C3A", // 地图原本颜色
+  mapHoverColor: "#0B284E", // 地图hover颜色
+  // 地图人数渐变
+  mapColorGradient: ["#1ED760", "#428220", "#096B0A", "#556238"],
+  // 地图侧面渐变
+  mapSideColor1: "#8B8B50",
+  mapSideColor2: "#394028",
+  // 上面的line
+  topLineColor: 0x41c0fb,
+  topLineWidth: 3,
+  topLineZIndex: Depth + 0.5,
+  // label 2d高度
+  label2dZIndex: Depth + 2,
+  // spot
+  spotZIndex: Depth + 0.2,
+};

+ 25 - 0
zhsq_qk-ui/.history/src/map3d/mapConfig_20240617133302.js

@@ -0,0 +1,25 @@
+const Depth = 3;
+
+export const mapConfig = {
+  // 地图挤出厚度
+  mapDepth: Depth,
+  // 地图透明度
+  mapTransparent: true,
+  mapOpacity: 0.9,
+  // 地图颜色
+  mapColor: "#1ED760", // 地图原本颜色
+  mapHoverColor: "#0B284E", // 地图hover颜色
+  // 地图人数渐变
+  mapColorGradient: ["#1ED760", "#428220", "#096B0A", "#556238"],
+  // 地图侧面渐变
+  mapSideColor1: "#8B8B50",
+  mapSideColor2: "#394028",
+  // 上面的line
+  topLineColor: 0x41c0fb,
+  topLineWidth: 3,
+  topLineZIndex: Depth + 0.5,
+  // label 2d高度
+  label2dZIndex: Depth + 2,
+  // spot
+  spotZIndex: Depth + 0.2,
+};

+ 25 - 0
zhsq_qk-ui/.history/src/map3d/mapConfig_20240617133310.js

@@ -0,0 +1,25 @@
+const Depth = 3;
+
+export const mapConfig = {
+  // 地图挤出厚度
+  mapDepth: Depth,
+  // 地图透明度
+  mapTransparent: true,
+  mapOpacity: 0.9,
+  // 地图颜色
+  mapColor: "#1ED760", // 地图原本颜色
+  mapHoverColor: "#0B284E", // 地图hover颜色
+  // 地图人数渐变
+  mapColorGradient: ["#1ED760", "#428220", "#096B0A", "#556238"],
+  // 地图侧面渐变
+  mapSideColor1: "#8B8B50",
+  mapSideColor2: "#394028",
+  // 上面的line
+  topLineColor: '#9E8C3A',
+  topLineWidth: 3,
+  topLineZIndex: Depth + 0.5,
+  // label 2d高度
+  label2dZIndex: Depth + 2,
+  // spot
+  spotZIndex: Depth + 0.2,
+};

+ 25 - 0
zhsq_qk-ui/.history/src/map3d/mapConfig_20240618091441.js

@@ -0,0 +1,25 @@
+const Depth = 3;
+
+export const mapConfig = {
+  // 地图挤出厚度
+  mapDepth: Depth,
+  // 地图透明度
+  mapTransparent: true,
+  mapOpacity: 0.9,
+  // 地图颜色
+  mapColor: "#1ED760", // 地图原本颜色
+  mapHoverColor: "#0B284E", // 地图hover颜色
+  // 地图人数渐变
+  mapColorGradient: ["#1ED760", "#3D452B", "#096B0A", "#556238"],
+  // 地图侧面渐变
+  mapSideColor1: "#8B8B50",
+  mapSideColor2: "#394028",
+  // 上面的line
+  topLineColor: '#9E8C3A',
+  topLineWidth: 3,
+  topLineZIndex: Depth + 0.5,
+  // label 2d高度
+  label2dZIndex: Depth + 2,
+  // spot
+  spotZIndex: Depth + 0.2,
+};

+ 25 - 0
zhsq_qk-ui/.history/src/map3d/mapConfig_20240618091506.js

@@ -0,0 +1,25 @@
+const Depth = 3;
+
+export const mapConfig = {
+  // 地图挤出厚度
+  mapDepth: Depth,
+  // 地图透明度
+  mapTransparent: true,
+  mapOpacity: 0.9,
+  // 地图颜色
+  mapColor: "#1ED760", // 地图原本颜色
+  mapHoverColor: "#0B284E", // 地图hover颜色
+  // 地图人数渐变
+  mapColorGradient: ["#1ED760", "#3D452B", "#096B0A", "#556238"],
+  // 地图侧面渐变
+  mapSideColor1: "#8B8B50",
+  mapSideColor2: "#394028",
+  // 上面的line
+  topLineColor: '#6B784A',
+  topLineWidth: 3,
+  topLineZIndex: Depth + 0.5,
+  // label 2d高度
+  label2dZIndex: Depth + 2,
+  // spot
+  spotZIndex: Depth + 0.2,
+};

+ 25 - 0
zhsq_qk-ui/.history/src/map3d/mapConfig_20240618091525.js

@@ -0,0 +1,25 @@
+const Depth = 3;
+
+export const mapConfig = {
+  // 地图挤出厚度
+  mapDepth: Depth,
+  // 地图透明度
+  mapTransparent: true,
+  mapOpacity: 0.9,
+  // 地图颜色
+  mapColor: "#1ED760", // 地图原本颜色
+  mapHoverColor: "#0B284E", // 地图hover颜色
+  // 地图人数渐变
+  mapColorGradient: ["#1ED760", "#3D452B", "#096B0A", "#556238"],
+  // 地图侧面渐变
+  mapSideColor1: "#919154",
+  mapSideColor2: "#919154",
+  // 上面的line
+  topLineColor: '#6B784A',
+  topLineWidth: 3,
+  topLineZIndex: Depth + 0.5,
+  // label 2d高度
+  label2dZIndex: Depth + 2,
+  // spot
+  spotZIndex: Depth + 0.2,
+};

+ 25 - 0
zhsq_qk-ui/.history/src/map3d/mapConfig_20240618091537.js

@@ -0,0 +1,25 @@
+const Depth = 3;
+
+export const mapConfig = {
+  // 地图挤出厚度
+  mapDepth: Depth,
+  // 地图透明度
+  mapTransparent: true,
+  mapOpacity: 0.9,
+  // 地图颜色
+  mapColor: "#1ED760", // 地图原本颜色
+  mapHoverColor: "#0B284E", // 地图hover颜色
+  // 地图人数渐变
+  mapColorGradient: ["#1ED760", "#3D452B", "#AD9E67", "#556238"],
+  // 地图侧面渐变
+  mapSideColor1: "#919154",
+  mapSideColor2: "#919154",
+  // 上面的line
+  topLineColor: '#6B784A',
+  topLineWidth: 3,
+  topLineZIndex: Depth + 0.5,
+  // label 2d高度
+  label2dZIndex: Depth + 2,
+  // spot
+  spotZIndex: Depth + 0.2,
+};

Файловите разлики са ограничени, защото са твърде много
+ 1098 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617095312.vue


Файловите разлики са ограничени, защото са твърде много
+ 1139 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617103313.vue


Файловите разлики са ограничени, защото са твърде много
+ 1139 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617103345.vue


Файловите разлики са ограничени, защото са твърде много
+ 1139 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617112314.vue


Файловите разлики са ограничени, защото са твърде много
+ 1139 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617112331.vue


Файловите разлики са ограничени, защото са твърде много
+ 1139 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617112404.vue


Файловите разлики са ограничени, защото са твърде много
+ 1139 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617112419.vue


Файловите разлики са ограничени, защото са твърде много
+ 1139 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617112626.vue


Файловите разлики са ограничени, защото са твърде много
+ 1139 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617112642.vue


Файловите разлики са ограничени, защото са твърде много
+ 1139 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617112836.vue


Файловите разлики са ограничени, защото са твърде много
+ 1140 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617112918.vue


Файловите разлики са ограничени, защото са твърде много
+ 1140 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617112940.vue


Файловите разлики са ограничени, защото са твърде много
+ 1141 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617130523.vue


Файловите разлики са ограничени, защото са твърде много
+ 1130 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617130616.vue


Файловите разлики са ограничени, защото са твърде много
+ 1130 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617130648.vue


Файловите разлики са ограничени, защото са твърде много
+ 1130 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617130657.vue


Файловите разлики са ограничени, защото са твърде много
+ 1130 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617130708.vue


Файловите разлики са ограничени, защото са твърде много
+ 1130 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617130836.vue


Файловите разлики са ограничени, защото са твърде много
+ 1130 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617130901.vue


Файловите разлики са ограничени, защото са твърде много
+ 1130 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617130918.vue


Файловите разлики са ограничени, защото са твърде много
+ 1137 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617130941.vue


Файловите разлики са ограничени, защото са твърде много
+ 1137 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617131329.vue


Файловите разлики са ограничени, защото са твърде много
+ 1137 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617131340.vue


Файловите разлики са ограничени, защото са твърде много
+ 1137 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617132203.vue


Файловите разлики са ограничени, защото са твърде много
+ 1137 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617132243.vue


Файловите разлики са ограничени, защото са твърде много
+ 1137 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617132254.vue


Файловите разлики са ограничени, защото са твърде много
+ 1137 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617132301.vue


+ 0 - 0
zhsq_qk-ui/.history/src/views/fusion/index_20240617132308.vue


Някои файлове не бяха показани, защото твърде много файлове са промени