Browse Source

新增地图鼠标移入提示框

yuhang Fu 1 year ago
parent
commit
029cb07581
30 changed files with 9680 additions and 10 deletions
  1. 424 0
      zhsq_qk-ui/.history/src/map3d/index_20240618111816.vue
  2. 425 0
      zhsq_qk-ui/.history/src/map3d/index_20240618111828.vue
  3. 425 0
      zhsq_qk-ui/.history/src/map3d/index_20240618111839.vue
  4. 425 0
      zhsq_qk-ui/.history/src/map3d/index_20240618111847.vue
  5. 424 0
      zhsq_qk-ui/.history/src/map3d/index_20240618112018.vue
  6. 425 0
      zhsq_qk-ui/.history/src/map3d/index_20240618112101.vue
  7. 426 0
      zhsq_qk-ui/.history/src/map3d/index_20240618112222.vue
  8. 426 0
      zhsq_qk-ui/.history/src/map3d/index_20240618112239.vue
  9. 426 0
      zhsq_qk-ui/.history/src/map3d/index_20240618112249.vue
  10. 423 0
      zhsq_qk-ui/.history/src/map3d/index_20240618112353.vue
  11. 423 0
      zhsq_qk-ui/.history/src/map3d/index_20240618112438.vue
  12. 423 0
      zhsq_qk-ui/.history/src/map3d/index_20240618112723.vue
  13. 423 0
      zhsq_qk-ui/.history/src/map3d/index_20240618112801.vue
  14. 423 0
      zhsq_qk-ui/.history/src/map3d/index_20240618112847.vue
  15. 423 0
      zhsq_qk-ui/.history/src/map3d/index_20240618112938.vue
  16. 423 0
      zhsq_qk-ui/.history/src/map3d/index_20240618130245.vue
  17. 429 0
      zhsq_qk-ui/.history/src/map3d/index_20240618130535.vue
  18. 429 0
      zhsq_qk-ui/.history/src/map3d/index_20240618130552.vue
  19. 429 0
      zhsq_qk-ui/.history/src/map3d/index_20240618130624.vue
  20. 429 0
      zhsq_qk-ui/.history/src/map3d/index_20240618130716.vue
  21. 429 0
      zhsq_qk-ui/.history/src/map3d/index_20240618130740.vue
  22. 423 0
      zhsq_qk-ui/.history/src/map3d/index_20240618130752.vue
  23. 38 0
      zhsq_qk-ui/.history/src/tooltip/index_20240516092647.vue
  24. 37 0
      zhsq_qk-ui/.history/src/tooltip/index_20240618113028.vue
  25. 37 0
      zhsq_qk-ui/.history/src/tooltip/index_20240618113035.vue
  26. 37 0
      zhsq_qk-ui/.history/src/tooltip/index_20240618130130.vue
  27. 37 0
      zhsq_qk-ui/.history/src/tooltip/index_20240618130213.vue
  28. 131 0
      zhsq_qk-ui/.history/vue.config_20240618101926.js
  29. 8 9
      zhsq_qk-ui/src/map3d/index.vue
  30. 0 1
      zhsq_qk-ui/src/tooltip/index.vue

+ 424 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618111816.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>

+ 425 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618111828.vue

@@ -0,0 +1,425 @@
+ <!-- 
+ *@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) {
+          debugger
+           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>

+ 425 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618111839.vue

@@ -0,0 +1,425 @@
+ <!-- 
+ *@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) {
+          debugger
+           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>

+ 425 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618111847.vue

@@ -0,0 +1,425 @@
+ <!-- 
+ *@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) {
+          // debugger
+           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_20240618112018.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 {
+     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) {
+          // debugger
+           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>

+ 425 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618112101.vue

@@ -0,0 +1,425 @@
+ <!-- 
+ *@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 {
+     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) {
+          // debugger
+          console.log('$refs',this.$refs)
+           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>

+ 426 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618112222.vue

@@ -0,0 +1,426 @@
+ <!-- 
+ *@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" id="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 {
+     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) {
+          // debugger
+          const tooltipEl = document.getElementById('#toolTipRef')
+          console.log('$refs',this.$refs)
+           toolTipRef.style.left = e.clientX + 2 + "px";
+           toolTipRef.style.top = e.clientY + 2 + "px";
+           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>

+ 426 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618112239.vue

@@ -0,0 +1,426 @@
+ <!-- 
+ *@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" id="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 {
+     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) {
+          // debugger
+          const tooltipEl = document.getElementById('#toolTipRef')
+          console.log('$refs',tooltipEl)
+           toolTipRef.style.left = e.clientX + 2 + "px";
+           toolTipRef.style.top = e.clientY + 2 + "px";
+           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>

+ 426 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618112249.vue

@@ -0,0 +1,426 @@
+ <!-- 
+ *@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" id="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 {
+     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) {
+          // debugger
+          const tooltipEl = document.getElementById('toolTipRef')
+          console.log('$refs',tooltipEl)
+           toolTipRef.style.left = e.clientX + 2 + "px";
+           toolTipRef.style.top = e.clientY + 2 + "px";
+           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_20240618112353.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" id="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 {
+     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; // 设置完全不透明
+         }
+         const tooltipEl = document.getElementById('toolTipRef')
+         if (tooltipEl && tooltipEl.style) {
+           toolTipRef.style.left = e.clientX + 2 + "px";
+           toolTipRef.style.top = e.clientY + 2 + "px";
+           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_20240618112438.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" id="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 {
+     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; // 设置完全不透明
+         }
+         const tooltipEl = document.getElementById('toolTipRef')
+         if (tooltipEl && tooltipEl.style) {
+           toolTipRef.style.left = e.clientX + "px";
+           toolTipRef.style.top = e.clientY + "px";
+           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_20240618112723.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" id="toolTipRef" :data="toolTipData" style="position: absolute !important; width: 357px; height: 200px;"></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 {
+     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; // 设置完全不透明
+         }
+         const tooltipEl = document.getElementById('toolTipRef')
+         if (tooltipEl && tooltipEl.style) {
+           toolTipRef.style.left = e.clientX + "px";
+           toolTipRef.style.top = e.clientY + "px";
+           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_20240618112801.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" id="toolTipRef" :data="toolTipData" style="position: absolute !important; width: 357px; height: 200px;"></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 {
+     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; // 设置完全不透明
+         }
+         const tooltipEl = document.getElementById('toolTipRef')
+         if (tooltipEl && tooltipEl.style) {
+           toolTipRef.style.left = e.clientX + 20 + "px";
+           toolTipRef.style.top = e.clientY + 20 + "px";
+           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_20240618112847.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" id="toolTipRef" :data="toolTipData" style="position: absolute !important; width: 357px; height: 200px;"></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 {
+     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; // 设置完全不透明
+         }
+         const tooltipEl = document.getElementById('toolTipRef')
+         if (tooltipEl && tooltipEl.style) {
+           toolTipRef.style.left = e.clientX + 20 + "px";
+           toolTipRef.style.top = e.clientY + 20 + "px !important";
+           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_20240618112938.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" id="toolTipRef" :data="toolTipData" style="position: absolute !important; width: 357px; height: 200px;"></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 {
+     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; // 设置完全不透明
+         }
+         const tooltipEl = document.getElementById('toolTipRef')
+         if (tooltipEl && tooltipEl.style) {
+           toolTipRef.style.left = e.clientX + 20 + "px";
+           toolTipRef.style.top = e.clientY + 20 + "px";
+           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_20240618130245.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" id="toolTipRef" :data="toolTipData" style="position: absolute !important; width: 357px; height: 200px;background-color: #000E19;"></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 {
+     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; // 设置完全不透明
+         }
+         const tooltipEl = document.getElementById('toolTipRef')
+         if (tooltipEl && tooltipEl.style) {
+           toolTipRef.style.left = e.clientX + 20 + "px";
+           toolTipRef.style.top = e.clientY + 20 + "px";
+           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>

+ 429 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618130535.vue

@@ -0,0 +1,429 @@
+ <!-- 
+ *@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" id="toolTipRef" :data="toolTipData" style="position: absolute !important; width: 357px; height: 200px;background-color: #000E19;"></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 {
+     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; // 设置完全不透明
+         }
+         const tooltipEl = document.getElementById('toolTipRef')
+         if (tooltipEl && tooltipEl.style) {
+           toolTipRef.style.left = e.clientX + 20 + "px";
+           toolTipRef.style.top = e.clientY + 20 + "px";
+           toolTipRef.style.visibility = "visible";
+         }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     //  鼠标移出
+     const onMouseOutEvent = (e) => {
+      debugger
+     }
+
+     // 鼠标双击事件
+     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("mouseout", onMouseOutEvent, 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>

+ 429 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618130552.vue

@@ -0,0 +1,429 @@
+ <!-- 
+ *@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" id="toolTipRef" :data="toolTipData" style="position: absolute !important; width: 357px; height: 200px;background-color: #000E19;"></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 {
+     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; // 设置完全不透明
+         }
+         const tooltipEl = document.getElementById('toolTipRef')
+         if (tooltipEl && tooltipEl.style) {
+           toolTipRef.style.left = e.clientX + 20 + "px";
+           toolTipRef.style.top = e.clientY + 20 + "px";
+           toolTipRef.style.visibility = "visible";
+         }
+         this.toolTipData.text = properties.name
+       } else {
+         // this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     //  鼠标移出
+     const onMouseOutEvent = (e) => {
+      
+     }
+
+     // 鼠标双击事件
+     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("mouseout", onMouseOutEvent, 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>

+ 429 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618130624.vue

@@ -0,0 +1,429 @@
+ <!-- 
+ *@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" id="toolTipRef" :data="toolTipData" style="position: absolute !important; width: 357px; height: 200px;background-color: #000E19;"></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 {
+     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; // 设置完全不透明
+         }
+         const tooltipEl = document.getElementById('toolTipRef')
+         if (tooltipEl && tooltipEl.style) {
+           toolTipRef.style.left = e.clientX + 20 + "px";
+           toolTipRef.style.top = e.clientY + 20 + "px";
+           toolTipRef.style.visibility = "visible";
+         }
+         this.toolTipData.text = properties.name
+       } else {
+         this.$refs.toolTipRef.style.visibility = "hidden";
+       }
+     };
+
+     //  鼠标移出
+     const onMouseOutEvent = (e) => {
+      
+     }
+
+     // 鼠标双击事件
+     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("mouseout", onMouseOutEvent, 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>

+ 429 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618130716.vue

@@ -0,0 +1,429 @@
+ <!-- 
+ *@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" id="toolTipRef" :data="toolTipData" style="position: absolute !important; width: 357px; height: 200px;background-color: #000E19;"></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 {
+     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
+       );
+       const tooltipEl = document.getElementById('toolTipRef')
+       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 (tooltipEl && tooltipEl.style) {
+           toolTipRef.style.left = e.clientX + 20 + "px";
+           toolTipRef.style.top = e.clientY + 20 + "px";
+           toolTipRef.style.visibility = "visible";
+         }
+         this.toolTipData.text = properties.name
+       } else {
+        tooltipEl.style.visibility = "hidden";
+       }
+     };
+
+     //  鼠标移出
+     const onMouseOutEvent = (e) => {
+      
+     }
+
+     // 鼠标双击事件
+     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("mouseout", onMouseOutEvent, 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>

+ 429 - 0
zhsq_qk-ui/.history/src/map3d/index_20240618130740.vue

@@ -0,0 +1,429 @@
+ <!-- 
+ *@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" id="toolTipRef" :data="toolTipData" style="position: absolute !important; width: 357px; height: 200px;background-color: #000E19;"></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 {
+     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
+       );
+       const tooltipEl = document.getElementById('toolTipRef')
+       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 (tooltipEl && tooltipEl.style) {
+           toolTipRef.style.left = e.clientX + 20 + "px";
+           toolTipRef.style.top = e.clientY + 20 + "px";
+           toolTipRef.style.visibility = "visible";
+         }
+         this.toolTipData.text = properties.name
+       } else {
+         tooltipEl.style.visibility = "hidden";
+       }
+     };
+
+     //  鼠标移出
+     const onMouseOutEvent = (e) => {
+      
+     }
+
+     // 鼠标双击事件
+     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("mouseout", onMouseOutEvent, 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_20240618130752.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" id="toolTipRef" :data="toolTipData" style="position: absolute !important; width: 357px; height: 200px;background-color: #000E19;"></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 {
+     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
+       );
+       const tooltipEl = document.getElementById('toolTipRef')
+       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 (tooltipEl && tooltipEl.style) {
+           toolTipRef.style.left = e.clientX + 20 + "px";
+           toolTipRef.style.top = e.clientY + 20 + "px";
+           toolTipRef.style.visibility = "visible";
+         }
+         this.toolTipData.text = properties.name
+       } else {
+         tooltipEl.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>

+ 38 - 0
zhsq_qk-ui/.history/src/tooltip/index_20240516092647.vue

@@ -0,0 +1,38 @@
+<template>
+    <div
+       :ref="innterRef"
+       class="contain"
+    >
+       {{ data.text || "当前城市" }}
+     </div>
+</template>
+
+<script>
+export default{
+    name:'ToolTip',
+    props:['innterRef','data'],
+    data(){
+        return {
+            text:null,
+        }
+    },
+}
+</script>
+
+
+<style lang="scss" scoped>
+.contain{
+    // position: absolute;
+    position: fixed !important;
+    top: 0% !important;
+    z-index: 999;
+    background: "#010209";
+    width: 350px;
+    height: 200px;
+    padding: 10px;
+    // border: 2px solid #163FA2;
+    // visibility: hidden;
+    color: #3B93E6;
+    pointer-events: none;
+}
+</style>

+ 37 - 0
zhsq_qk-ui/.history/src/tooltip/index_20240618113028.vue

@@ -0,0 +1,37 @@
+<template>
+    <div
+       :ref="innterRef"
+       class="contain"
+    >
+       {{ data.text || "当前城市" }}
+     </div>
+</template>
+
+<script>
+export default{
+    name:'ToolTip',
+    props:['innterRef','data'],
+    data(){
+        return {
+            text:null,
+        }
+    },
+}
+</script>
+
+
+<style lang="scss" scoped>
+// .contain{
+//     // position: absolute;
+//     position: fixed !important;
+//     z-index: 999;
+//     background: "#010209";
+//     width: 350px;
+//     height: 200px;
+//     padding: 10px;
+//     // border: 2px solid #163FA2;
+//     // visibility: hidden;
+//     color: #3B93E6;
+//     pointer-events: none;
+// }
+</style>

+ 37 - 0
zhsq_qk-ui/.history/src/tooltip/index_20240618113035.vue

@@ -0,0 +1,37 @@
+<template>
+    <div
+       :ref="innterRef"
+       class="contain"
+    >
+       {{ data.text || "当前城市" }}
+     </div>
+</template>
+
+<script>
+export default{
+    name:'ToolTip',
+    props:['innterRef','data'],
+    data(){
+        return {
+            text:null,
+        }
+    },
+}
+</script>
+
+
+<style lang="scss" scoped>
+.contain{
+    // position: absolute;
+    position: fixed !important;
+    z-index: 999;
+    background: "#010209";
+    width: 350px;
+    height: 200px;
+    padding: 10px;
+    // border: 2px solid #163FA2;
+    // visibility: hidden;
+    color: #3B93E6;
+    pointer-events: none;
+}
+</style>

+ 37 - 0
zhsq_qk-ui/.history/src/tooltip/index_20240618130130.vue

@@ -0,0 +1,37 @@
+<template>
+    <div
+       :ref="innterRef"
+       class="contain"
+    >
+       {{ data.text || "当前城市" }}
+     </div>
+</template>
+
+<script>
+export default{
+    name:'ToolTip',
+    props:['innterRef','data'],
+    data(){
+        return {
+            text:null,
+        }
+    },
+}
+</script>
+
+
+<style lang="scss" scoped>
+.contain{
+    // position: absolute;
+    position: fixed !important;
+    z-index: 999;
+    background: "#000E19";
+    width: 350px;
+    height: 200px;
+    padding: 10px;
+    // border: 2px solid #163FA2;
+    // visibility: hidden;
+    color: #3B93E6;
+    pointer-events: none;
+}
+</style>

+ 37 - 0
zhsq_qk-ui/.history/src/tooltip/index_20240618130213.vue

@@ -0,0 +1,37 @@
+<template>
+    <div
+       :ref="innterRef"
+       class="contain"
+    >
+       {{ data.text || "当前城市" }}
+     </div>
+</template>
+
+<script>
+export default{
+    name:'ToolTip',
+    props:['innterRef','data'],
+    data(){
+        return {
+            text:null,
+        }
+    },
+}
+</script>
+
+
+<style lang="scss" scoped>
+.contain{
+    // position: absolute;
+    position: fixed !important;
+    z-index: 999;
+    background: "#010209";
+    width: 350px;
+    height: 200px;
+    padding: 10px;
+    // border: 2px solid #163FA2;
+    // visibility: hidden;
+    color: #3B93E6;
+    pointer-events: none;
+}
+</style>

+ 131 - 0
zhsq_qk-ui/.history/vue.config_20240618101926.js

@@ -0,0 +1,131 @@
+'use strict'
+const path = require('path')
+
+function resolve(dir) {
+  return path.join(__dirname, dir)
+}
+
+const CompressionPlugin = require('compression-webpack-plugin')
+
+const name = process.env.VUE_APP_TITLE || '汽开区城市运行一网统管平台' // 网页标题
+
+const port = process.env.port || process.env.npm_config_port || 11001 // 端口
+
+// vue.config.js 配置说明
+//官方vue.config.js 参考文档 https://cli.vuejs.org/zh/config/#css-loaderoptions
+// 这里只列一部分,具体配置参考文档
+module.exports = {
+  // 部署生产环境和开发环境下的URL。
+  // 默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上
+  // 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。
+  publicPath: process.env.NODE_ENV === "production" ? "/" : "/",
+  // 在npm run build 或 yarn build 时 ,生成文件的目录名称(要和baseUrl的生产环境路径一致)(默认dist)
+  outputDir: 'dist',
+  // 用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下)
+  assetsDir: 'static',
+  // 是否开启eslint保存检测,有效值:ture | false | 'error'
+  lintOnSave: process.env.NODE_ENV === 'development',
+  // 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。
+  productionSourceMap: false,
+  // webpack-dev-server 相关配置
+  devServer: {
+    host: '0.0.0.0',
+    port: port,
+    open: true,
+    proxy: {
+      // detail: https://cli.vuejs.org/config/#devserver-proxy
+      [process.env.VUE_APP_BASE_API]: {
+        target: `http://192.168.4.27:3042`,
+				// target: `http://192.168.4.9:8080`,
+        changeOrigin: true,
+        pathRewrite: {
+          ['^' + process.env.VUE_APP_BASE_API]: ''
+        }
+      }
+    },
+    disableHostCheck: true
+  },
+  css: {
+    loaderOptions: {
+      sass: {
+        sassOptions: {outputStyle: "expanded"}
+      }
+    }
+  },
+  configureWebpack: {
+    name: name,
+    resolve: {
+      alias: {
+        '@': resolve('src')
+      }
+    },
+    plugins: [
+      // http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件
+      new CompressionPlugin({
+        cache: false,                                  // 不启用文件缓存
+        test: /\.(js|css|html|jpe?g|png|gif|svg)?$/i,  // 压缩文件格式
+        filename: '[path][base].gz[query]',            // 压缩后的文件名
+        algorithm: 'gzip',                             // 使用gzip压缩
+        minRatio: 0.8,                                 // 压缩比例,小于 80% 的文件不会被压缩
+        deleteOriginalAssets: false                    // 压缩后删除原文件
+      })
+    ],
+  },
+  chainWebpack(config) {
+    config.plugins.delete('preload') // TODO: need test
+    config.plugins.delete('prefetch') // TODO: need test
+
+    // set svg-sprite-loader
+    config.module
+      .rule('svg')
+      .exclude.add(resolve('src/assets/icons'))
+      .end()
+    config.module
+      .rule('icons')
+      .test(/\.svg$/)
+      .include.add(resolve('src/assets/icons'))
+      .end()
+      .use('svg-sprite-loader')
+      .loader('svg-sprite-loader')
+      .options({
+        symbolId: 'icon-[name]'
+      })
+      .end()
+
+    config.when(process.env.NODE_ENV !== 'development', config => {
+      config
+        .plugin('ScriptExtHtmlWebpackPlugin')
+        .after('html')
+        .use('script-ext-html-webpack-plugin', [{
+          // `runtime` must same as runtimeChunk name. default is `runtime`
+          inline: /runtime\..*\.js$/
+        }])
+        .end()
+
+      config.optimization.splitChunks({
+        chunks: 'all',
+        cacheGroups: {
+          libs: {
+            name: 'chunk-libs',
+            test: /[\\/]node_modules[\\/]/,
+            priority: 10,
+            chunks: 'initial' // only package third parties that are initially dependent
+          },
+          elementUI: {
+            name: 'chunk-elementUI', // split elementUI into a single package
+            test: /[\\/]node_modules[\\/]_?element-ui(.*)/, // in order to adapt to cnpm
+            priority: 20 // the weight needs to be larger than libs and app or it will be packaged into libs or app
+          },
+          commons: {
+            name: 'chunk-commons',
+            test: resolve('src/components'), // can customize your rules
+            minChunks: 3, //  minimum common number
+            priority: 5,
+            reuseExistingChunk: true
+          }
+        }
+      })
+      config.optimization.runtimeChunk('single')
+    })
+  }
+}

+ 8 - 9
zhsq_qk-ui/src/map3d/index.vue

@@ -11,7 +11,7 @@
      <div ref="map2dRef" style="width: 100%"></div>
      <div ref="mapRef" style="width:100%;height: 100% ;cursor: pointer;">
      </div>
-     <ToolTip ref="toolTipRef" :data="toolTipData"></ToolTip>
+     <ToolTip ref="toolTipRef" id="toolTipRef" :data="toolTipData" style="position: absolute !important; width: 357px; height: 200px;background-color: #000E19;"></ToolTip>
    </div>
 </template>
 
@@ -48,7 +48,6 @@ export default {
  },
  data(){
    return {
-     toolTipRef:null,
      toolTipData:{
        text:''
      },
@@ -303,6 +302,7 @@ export default {
        lastPick = intersects.find(
          (item) => item.object.userData.isChangeColor
        );
+       const tooltipEl = document.getElementById('toolTipRef')
        if (lastPick) {
          
          const properties = lastPick.object.parent.customProperties;
@@ -310,15 +310,14 @@ export default {
            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";
-         // }
+         if (tooltipEl && tooltipEl.style) {
+           toolTipRef.style.left = e.clientX + 20 + "px";
+           toolTipRef.style.top = e.clientY + 20 + "px";
+           toolTipRef.style.visibility = "visible";
+         }
          this.toolTipData.text = properties.name
        } else {
-         // this.$refs.toolTipRef.style.visibility = "hidden";
+         tooltipEl.style.visibility = "hidden";
        }
      };
 

+ 0 - 1
zhsq_qk-ui/src/tooltip/index.vue

@@ -24,7 +24,6 @@ export default{
 .contain{
     // position: absolute;
     position: fixed !important;
-    top: 0% !important;
     z-index: 999;
     background: "#010209";
     width: 350px;