Newer
Older
urbanLifeline_YanAn / src / views / oneMap / BIM / WangJiaPIng.vue
@zhangzhihui zhangzhihui on 28 Oct 17 KB 站点对接接口
  1. <template>
  2. <div id="WangJiaPIng" ref="threeDom">
  3. <div class="YuanDian" v-for="(item, i) in labelData" :key="item.id" :id="item.id" style="display: none">
  4. <div class="iconBox" v-show="item.show">
  5. <img src="@/assets/images/ljx.png" alt="" class="iconImg" />
  6. <div class="ydBox">
  7. <div class="YuanDianText">{{ item.name }}</div>
  8. <div class="ydData" v-for="(val, k) in item.data" :key="k" v-show="val.data">
  9. <div class="leftData">{{ val.phy + ':' }}</div>
  10. <div class="rightData">{{ val.data + ' ' + val.unit }}</div>
  11. </div>
  12. <div class="ydData">
  13. <div class="leftData">{{ '检测时间:' }}</div>
  14. <div class="rightData">{{ item.dataTime }}</div>
  15. </div>
  16. </div>
  17. </div>
  18. <img class="YuanDianIcon" :src="item.icon" @mouseenter="item.show = true" @mouseleave="item.show = false" />
  19. </div>
  20. </div>
  21. </template>
  22.  
  23. <script setup name="WangJiaPIng">
  24. import { ref, reactive, toRefs, onMounted } from 'vue';
  25. import * as THREE from 'three';
  26. import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; //控制器
  27. import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; // gltf加载器
  28. import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader';
  29. import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
  30. import { Water } from 'three/examples/jsm/objects/Water.js';
  31.  
  32. import { CSS3DRenderer, CSS3DObject } from 'three/examples/jsm/renderers/CSS3DRenderer.js';
  33. import gsap from 'gsap';
  34. import { CSSRulePlugin } from 'gsap/CSSRulePlugin';
  35. import bus from '@/bus';
  36.  
  37. import { pointGetDataList } from '@/api/system/tanchuang';
  38.  
  39. const AllData = reactive({});
  40.  
  41. const show = ref('');
  42. const width = ref(null);
  43. const height = ref(null);
  44. const Camera = ref(null);
  45. const Renderer = ref(null);
  46. const Controls = ref(null);
  47. const granaryArr = ref([]);
  48. const LabelRenderer = ref(null);
  49. const animationFrameId = ref(null);
  50. let Twater = null;
  51. const labelData = ref([
  52. {
  53. name: '静力水准仪',
  54. id: `YuanDian1`,
  55. position: {
  56. x: 0,
  57. y: 0,
  58. z: -0.8,
  59. },
  60. icon: '/Three/icon/jlszy_icon.png',
  61. data: [],
  62. show: false,
  63. pointCode: 'WL62zX5',
  64. dataTime: '',
  65. },
  66. {
  67. name: '静力水准仪',
  68. id: `YuanDian2`,
  69. position: {
  70. x: 40,
  71. y: 0,
  72. z: -0.8,
  73. },
  74. icon: '/Three/icon/jlszy_icon.png',
  75. data: [],
  76. show: false,
  77. pointCode: 'qGxgnkX',
  78. dataTime: '',
  79. },
  80. {
  81. name: '应变计',
  82. id: `YuanDian3`,
  83. position: {
  84. x: -37,
  85. y: 0,
  86. z: -0.2,
  87. },
  88. icon: '/Three/icon/ybj_icon.png',
  89. data: [],
  90. show: false,
  91. // dsZCOmK
  92. pointCode: 'dsZCOmK',
  93. dataTime: '',
  94. },
  95. {
  96. name: '应变计',
  97. id: `YuanDian4`,
  98. position: {
  99. x: 37,
  100. y: 0,
  101. z: -0.2,
  102. },
  103. icon: '/Three/icon/ybj_icon.png',
  104. data: [],
  105. show: false,
  106. pointCode: 'yKBFH23',
  107. dataTime: '',
  108. },
  109. ]);
  110. const deviceCode = ref(null);
  111. const stCode = ref(null);
  112. const MiMa = ref(null);
  113. const dialogVisible = ref(false);
  114. const loading = ref(false);
  115. const status = ref(false);
  116. const timer = ref(null);
  117.  
  118. const Scene = new THREE.Scene();
  119. const clock = new THREE.Clock();
  120. const threeDom = ref(null);
  121. const cameraPosition = {
  122. // x: 457.66301930145875,
  123. // y: 397.5473109836439,
  124. // z: 943.8703438418463,
  125. x: -58.221584999469584,
  126. y: 6.590211176632594,
  127. z: 9.665579736149148,
  128. };
  129. const cameraLookat = {
  130. x: 1,
  131. y: 1,
  132. z: -20,
  133. };
  134.  
  135. /**
  136. * 初始化模态对话框
  137. * @param {string} name - 模型的名称
  138. * @param {string} url - 模型的URL
  139. */
  140. const initModal = (name, url) => {
  141. const Gltfloader = new GLTFLoader();
  142. var dracoLoader = new DRACOLoader();
  143. // dracoLoader.setDecoderPath("https://zhzz.hongshan.gov.cn:8865/file/hongshan/Three_Gltf/"); //设置解压库文件路径
  144. dracoLoader.setDecoderPath('/draco/'); //设置解压库文件路径
  145. Gltfloader.setDRACOLoader(dracoLoader);
  146. Gltfloader.load(
  147. url,
  148. gltf => {
  149. gltf.scene.name = name;
  150. gltf.scene.scale.set(1, 1, 1);
  151. // gltf.scene.position.x = -70;
  152. gltf.scene.position.y = 2;
  153.  
  154. cameraReset(cameraPosition, cameraLookat);
  155.  
  156. Scene.add(gltf.scene);
  157. Scene.traverse(function (child) {
  158. if (child.isMesh) {
  159. child.frustumCulled = false;
  160. child.material.side = THREE.DoubleSide;
  161. granaryArr.value.push(child);
  162. }
  163. });
  164. },
  165. function (xhr) {
  166. // console.log(Math.floor((xhr.loaded / xhr.total) * 100));
  167. }
  168. );
  169. };
  170.  
  171. /**
  172. * 恢复相机位置和视角
  173. *
  174. * 此函数通过GSAP动画库来调整相机的位置和视角,以实现 Smooth Transition
  175. *
  176. * @param {Object} position - 相机的新位置,包含x, y, z坐标
  177. * @param {Object} lookAt - 相机的新视角目标,包含x, y, z坐标,目前未使用
  178. * @param {number} time - 动画过渡时间,单位为秒,默认为3秒
  179. */
  180. const cameraReset = (position, lookAt, time = 3) => {
  181. gsap.to(Camera.value.position, {
  182. x: position.x,
  183. y: position.y,
  184. z: position.z,
  185. duration: time,
  186. ease: 'power4.out',
  187. });
  188. // gsap.to(Camera.value.lookAt, {
  189. // x: lookAt.x,
  190. // y: lookAt.y,
  191. // z: lookAt.z,
  192. // duration: time,
  193. // ease: "power4.out",
  194. // });
  195. gsap.to(Controls.value.target, {
  196. x: lookAt.x,
  197. y: lookAt.y,
  198. z: lookAt.z,
  199. duration: time,
  200. ease: 'power4.out',
  201. });
  202. };
  203.  
  204. /**
  205. * 初始化渲染器
  206. * 该函数负责创建和配置Three.js的WebGL渲染器,包括设置视口大小、抗锯齿、阴影、像素比、色调映射等
  207. */
  208. const initRenderer = () => {
  209. width.value = document.getElementById('WangJiaPIng').clientWidth;
  210. height.value = document.getElementById('WangJiaPIng').clientHeight;
  211. Renderer.value = new THREE.WebGLRenderer({
  212. antialias: true,
  213. // alpha: true, //开启alpha
  214. });
  215. Renderer.value.shadowMap.enabled = true;
  216. Renderer.value.setPixelRatio(window.devicePixelRatio);
  217. Renderer.value.setSize(width.value, height.value, true);
  218. // Renderer.value.toneMapping = THREE.ACESFilmicToneMapping;
  219. Renderer.value.toneMappingExposure = 1; // 曝光系数
  220. threeDom.value.appendChild(Renderer.value.domElement);
  221. };
  222.  
  223. /**
  224. * 初始化标签渲染器
  225. * 该函数用于创建和配置一个CSS3D渲染器,用于渲染标签对象
  226. * 它设置了渲染器的大小,位置,并将其添加到三维场景的DOM元素中
  227. */
  228. const initLabelRenderer = () => {
  229. LabelRenderer.value = new CSS3DRenderer();
  230. LabelRenderer.value.setSize(width.value, height.value);
  231. LabelRenderer.value.domElement.style.position = 'absolute';
  232. LabelRenderer.value.domElement.style.top = '0px';
  233. threeDom.value.appendChild(LabelRenderer.value.domElement);
  234. // LabelRenderer.value.domElement.addEventListener("click", meshOnClick);
  235. };
  236.  
  237. /**
  238. * 初始化摄像机
  239. * 创建一个透视摄像机,并设置其位置
  240. */
  241. const initCamera = () => {
  242. Camera.value = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 4000);
  243. Camera.value.position.set(500, 500, 500);
  244. };
  245.  
  246. /**
  247. * 初始化控制对象
  248. * 设置相机控制属性,以实现特定的相机行为
  249. */
  250. const initControls = () => {
  251. Controls.value = new OrbitControls(Camera.value, LabelRenderer.value.domElement);
  252. //上下翻转的最大角度
  253. Controls.value.maxPolarAngle = 1.5;
  254. // //上下翻转的最小角度
  255. Controls.value.minPolarAngle = 0.2;
  256. // 使动画循环使用时阻尼或自转 意思是否有惯性
  257. Controls.value.enableDamping = false;
  258. // 动态阻尼系数 就是鼠标拖拽旋转灵敏度
  259. Controls.value.dampingFactor = 0.04;
  260. // 是否可以旋转
  261. Controls.value.enableRotate = true;
  262. // 是否可以缩放与速度
  263. Controls.value.enableZoom = true;
  264. // 设置相机距离原点的最远距离
  265. Controls.value.minDistance = 30;
  266. // 设置相机距离原点的最远距离
  267. Controls.value.maxDistance = 3000;
  268. // 是否开启右键拖拽
  269. Controls.value.enablePan = true;
  270. // AxesHelper:辅助观察的坐标系
  271. // const axesHelper = new THREE.AxesHelper(3000);
  272. // Scene.add(axesHelper);
  273. };
  274.  
  275. /**
  276. * 初始化灯光
  277. * 创建并添加环境光到场景中,以模拟全局照明效果
  278. */
  279. const initLight = () => {
  280. // // // 设置场景的背景颜色,即天空颜色
  281. // // Scene.background = new THREE.Color(0x999999); // 设置背景,可以更换为你想要的颜色
  282. // // // 设置天空的雾气颜色和雾气近距离
  283. Scene.fog = new THREE.Fog(0xffffff, 50, 200); // 距离10开始,到200结束
  284. // // // 创建平行光源
  285. // const directionalLight = new THREE.DirectionalLight(0xffffff, 0.6);
  286. // Scene.add(directionalLight);
  287. // // 创建环境光
  288. // const ambientLight = new THREE.AmbientLight("#fff", 0.8);
  289. // Scene.add(ambientLight);
  290. // // // 创建平行光辅助线
  291. // // const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 0.5);
  292. // // Scene.add(directionalLightHelper);
  293. // // 创建点光源
  294. // const PointLight = new THREE.PointLight(0x1784f9, 1, 300);
  295. // PointLight.position.set(100, -100, 10);
  296. // Scene.add(PointLight);
  297. };
  298.  
  299. /**
  300. * 初始化天空盒
  301. * 使用PMREMGenerator生成环境贴图,并设置为场景的环境光和背景
  302. * 通过RGBELoader加载HDR纹理,用于创建环境贴图
  303. * 注意:此函数未使用TextureLoader加载背景图片,而是使用HDR纹理
  304. */
  305. const initSky = () => {
  306. var pmremGenerator = new THREE.PMREMGenerator(Renderer.value); // 使用hdr作为背景色
  307. pmremGenerator.compileEquirectangularShader();
  308. new RGBELoader().load('/Gltf/Skey_1K.hdr', function (texture) {
  309. const envMap = pmremGenerator.fromEquirectangular(texture).texture;
  310. envMap.isPmremTexture = true;
  311. Scene.environment = envMap;
  312. Scene.background = envMap;
  313. pmremGenerator.dispose();
  314. });
  315. // new THREE.TextureLoader().load("/Gltf/bg.png", function (texture) {
  316. // Scene.background = texture;
  317. // });
  318. };
  319.  
  320. /**
  321. * 渲染函数,用于不断更新和渲染场景
  322. */
  323.  
  324. const Render = () => {
  325. animationFrameId.value = requestAnimationFrame(Render);
  326. Controls.value.update(); // 轨道控制器的更新
  327. Renderer.value.clear(); // 清除画布
  328. updateLabelOrientation(Camera.value.position);
  329. Renderer.value.render(Scene, Camera.value);
  330. LabelRenderer.value.render(Scene, Camera.value);
  331. const delta = clock.getDelta();
  332. // 如需调试请打开这个获取Camera,Controls 的值
  333. console.log('Camera.value', Camera.value);
  334. console.log('Controls.value', Controls.value);
  335. };
  336.  
  337. // 创建气泡窗
  338. function createLable() {
  339. labelData.value.forEach((item, index) => {
  340. let labelCSS3D = Scene.getObjectByName(`YuanDian${index + 1}`);
  341. if (labelCSS3D === undefined) {
  342. let lableDiv = document.getElementById(`YuanDian${index + 1}`);
  343. lableDiv.style.display = 'block';
  344. labelCSS3D = new CSS3DObject(lableDiv);
  345. labelCSS3D.position.set(item.position.x, item.position.y, item.position.z);
  346. labelCSS3D.scale.set(0.1, 0.1, 0.1);
  347. labelCSS3D.name = `YuanDian${index + 1}`;
  348. // labelCSS3D.rotation.y = - Math.PI / 6; // 30度转换为弧度
  349. Scene.add(labelCSS3D);
  350. } else {
  351. labelCSS3D.visible = true;
  352. }
  353. });
  354. }
  355.  
  356. /**
  357. * 更新所有气泡框的方向,使其始终面向相机
  358. */
  359. function updateLabelOrientation(cameraPosition) {
  360. labelData.value.forEach((item, index) => {
  361. let labelCSS3D = Scene.getObjectByName(`YuanDian${index + 1}`);
  362. if (labelCSS3D) {
  363. const direction = new THREE.Vector3();
  364. direction.subVectors(cameraPosition, labelCSS3D.position).normalize();
  365. const angle = Math.atan2(direction.x, direction.z);
  366. labelCSS3D.rotation.y = angle; // 调整角度使其始终面向相机
  367. }
  368. });
  369. }
  370. const flyTo = data => {
  371. // 静立水准仪
  372. if (data.name[0] == '静力水准仪') {
  373. // 摄像机位置
  374. gsap.to(Camera.value.position, {
  375. x: -17.414766616979897,
  376. y: 7.132760656412787,
  377. z: 23.413620521050024,
  378. // x: -69.16984920750228,
  379. // y: 9.22906292776404,
  380. // z: 14.97197297303164,
  381. duration: 3,
  382. ease: 'power4.out',
  383. });
  384. // 视角
  385. gsap.to(Controls.value.target, {
  386. x: 12.660351499015919,
  387. y: 3.159510377176017,
  388. z: 0.8474660132082192,
  389. duration: 3,
  390. ease: 'power4.out',
  391. });
  392. } else if (data.name[0] == '应变计') {
  393. // 摄像机位置
  394. gsap.to(Camera.value.position, {
  395. x: -53.61576836842107,
  396. y: 7.710859842105263,
  397. z: 17.230486105263143,
  398. duration: 3,
  399. ease: 'power4.out',
  400. });
  401. // 视角
  402. gsap.to(Controls.value.target, {
  403. x: 29.03625918562505,
  404. y: -0.3313374072727649,
  405. z: -29.291435662949137,
  406. duration: 3,
  407. ease: 'power4.out',
  408. });
  409. } else if (data.name[0] == '加速度计') {
  410. // gsap.to(Camera.value.position, {
  411. // x: -17.287720948075602,
  412. // y: 8.016773260004873,
  413. // z: 11.57619028778769,
  414. // duration: 3,
  415. // ease: 'power4.out',
  416. // });
  417. // gsap.to(Controls.value.target, {
  418. // x: 4.538970140099083,
  419. // y: 2.881636781852132,
  420. // z: -8.354337289076433,
  421. // duration: 3,
  422. // ease: 'power4.out',
  423. // });
  424. }
  425.  
  426. // 打开弹窗
  427. labelData.value.forEach(element => {
  428. if (element.name == data.name[0]) {
  429. element.show = true;
  430. } else {
  431. element.show = false;
  432. }
  433. });
  434. };
  435. // const BIMClick = () => {
  436. // console.log("触发点击事件,隐藏弹窗");
  437. // labelData.value.forEach((element) => {
  438. // element.show = false;
  439. // });
  440. // };
  441.  
  442. // 获取点位信息
  443. const getPonintInfo = () => {
  444. const promises = labelData.value.map(item => {
  445. return pointGetDataList({ pointCode: item.pointCode }).then(res => {
  446. // 检测时间
  447. const timeWithDate = res.data.find(time => time.dataTime);
  448. if (timeWithDate) {
  449. item.dataTime = timeWithDate.dataTime;
  450. }
  451. return { ...item, data: res.data };
  452. });
  453. });
  454.  
  455. Promise.all(promises)
  456. .then(updatedItems => {
  457. labelData.value = updatedItems;
  458. })
  459. .catch(error => {
  460. console.error('请求出错:labelData.value', error);
  461. });
  462. };
  463.  
  464. onBeforeMount(() => {
  465. // initGltfFloor();
  466. // initModal('ChangJing', '/Gltf/ChuWangCheng.gltf');
  467. initModal('daqiao', 'https://newfiber-cloud-1255570142.cos.ap-chengdu.myqcloud.com/yanan/daqiao2.gltf');
  468. // 共同弹窗触发事件
  469. bus.on('WJP_BIM_flyTo', params => {
  470. // 打开弹窗
  471. flyTo(params);
  472. });
  473. });
  474. onMounted(() => {
  475. getPonintInfo();
  476. nextTick(() => {
  477. if (document.readyState === 'complete') {
  478. createLable();
  479. // createWaterLevel();
  480. }
  481. });
  482. initRenderer();
  483. initLabelRenderer();
  484. initCamera();
  485. initControls();
  486. initLight();
  487. initSky();
  488. createLable();
  489. // LoadWater();
  490. Render();
  491. // 添加点击事件
  492. //监听点击事件
  493. // window.addEventListener("click", BIMClick, false);
  494. });
  495.  
  496. onBeforeUnmount(() => {
  497. bus.off('WJP_BIM_flyTo');
  498. // window.removeEventListener("click", BIMClick);
  499.  
  500. // document.removeEventListener('click', meshOnClick);
  501. // window.removeEventListener("resize", onWindowResize, false);
  502.  
  503. Scene.traverse(e => {
  504. if (e.BufferGeometry) e.BufferGeometry.dispose();
  505. if (e.material) {
  506. if (Array.isArray(e.material)) {
  507. e.material.forEach(m => {
  508. m.dispose();
  509. });
  510. } else {
  511. e.material.dispose();
  512. }
  513. }
  514. if (e.isMesh) {
  515. e.remove();
  516. }
  517. });
  518. Scene.remove();
  519. Renderer.value.dispose();
  520. Renderer.value.content = null;
  521. clearInterval(timer.value);
  522.  
  523. // 清理渲染器和相机
  524. Camera.value = null;
  525.  
  526. cancelAnimationFrame(animationFrameId.value);
  527. });
  528. </script>
  529.  
  530. <style lang="scss" scoped>
  531. #WangJiaPIng {
  532. width: 100%;
  533. height: 100%;
  534. position: absolute;
  535. // background: url("@/assets/images/Sponge_screen/QiaoLiang/BIM.png") no-repeat center center;
  536.  
  537. .YuanDian {
  538. z-index: 9999 !important;
  539. position: relative;
  540. // min-height: 20px;
  541. .iconBox {
  542. position: relative;
  543. // border: 1px solid red;
  544. }
  545. .iconImg {
  546. position: absolute;
  547. left: 50%;
  548. // transform: translateX(-50%);
  549. bottom: 5px;
  550. width: 19px;
  551. height: 12px;
  552. }
  553. .ydBox {
  554. // width: 134px;
  555. // height: 70px;
  556. position: absolute;
  557. left: calc(50% + 19px);
  558. bottom: 8px;
  559. width: 60px;
  560. // height: 26px;
  561. background: linear-gradient(0deg, rgba(12, 54, 92, 0.6) 0%, rgba(12, 54, 92, 0.6) 100%);
  562. border-radius: 2px;
  563. border: 1px solid #04d8ff;
  564. .ydData {
  565. display: flex;
  566. align-items: center;
  567. justify-content: space-between;
  568. padding: 0 3px;
  569. font-size: 3px;
  570. height: 7px;
  571. line-height: 7px;
  572. color: #04d8ff;
  573. // .leftData {
  574. // }
  575. // .rightData {
  576. // }
  577. }
  578. }
  579. .YuanDianText {
  580. // padding-bottom: 10px;
  581. height: 7px;
  582. line-height: 7px;
  583. color: #fff;
  584. text-shadow: 0px 1px 3px rgba(0, 0, 0, 0.9);
  585. white-space: nowrap;
  586. word-break: keep-all;
  587. // cursor: pointer;
  588. pointer-events: none;
  589. font-size: 3px;
  590. text-align: center;
  591. background: linear-gradient(90deg, rgba(4, 216, 255, 0.8) 0%, rgba(4, 216, 255, 0.2) 100%);
  592. }
  593.  
  594. .YuanDianIcon {
  595. width: 10px;
  596. height: 10px;
  597. position: absolute;
  598. left: 50%;
  599. transform: translateX(-50%);
  600. bottom: 0;
  601. pointer-events: auto;
  602. }
  603. }
  604. }
  605. </style>